From d890e8640f20fba3215ba7be8e0ff145aeb8c17c Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Wed, 11 May 2011 15:55:44 -0500 Subject: [PATCH] API changes/cleanup. API changes: - include/canvas.js renamed to include/display.js - Display.rescale() method removed from API. Use Display.set_scale() instead. - Make logo configuration attribute of Display and display it when clear() is called if it is set. API deprecations: - use RFB onUpdateState instead of updateState. - use RFB onClipboard instead of clipboardReceive. See https://github.com/kanaka/noVNC/wiki/ModuleAPI for detailed noVNC modules and API description. Expand and normalize the event/callback interfaces. Standize on "onEventName" form for callbacks. Callback Renames: - RFB updateState -> onUpdateState - RFB clipboardReceive -> onClipboard - Keyboard keyPress -> onKeyPress - Mouse mouseButton -> onMouseButton - Mouse mouseMove -> onMouseMove Callback Additions: - RFB onPasswordRequired - RFB onBell - RFB onFBUReceive - RFB onFBUComplete Other: - Add array type support to Util.conf_default() - Removed a bunch of routines from the Display API that were just used internally and not actually by noVNC: flush, setFillColor, imageDataGet, imageDataCreate, rgbxImageData, rgbxImageFill, cmapImageData, cmapImageFill. - More keyboard/mouse logging when debug turned on. - Some JSLinting --- include/base64.js | 3 +- include/{canvas.js => display.js} | 229 +++++++++++++++--------------- include/input.js | 88 ++++++------ include/rfb.js | 216 ++++++++++++++++------------ include/ui.js | 24 ++-- include/util.js | 47 +++--- include/vnc.js | 5 +- include/websock.js | 19 ++- vnc_auto.html | 44 +++--- 9 files changed, 366 insertions(+), 309 deletions(-) rename include/{canvas.js => display.js} (76%) diff --git a/include/base64.js b/include/base64.js index 0b14332..c68b33a 100644 --- a/include/base64.js +++ b/include/base64.js @@ -41,7 +41,6 @@ * * ***** END LICENSE BLOCK ***** */ -"use strict"; /*jslint white: false, bitwise: false, plusplus: false */ /*global console */ @@ -52,6 +51,7 @@ toBase64Table : 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+ base64Pad : '=', encode: function (data) { + "use strict"; var result = '', chrTable = Base64.toBase64Table.split(''), pad = Base64.base64Pad, @@ -95,6 +95,7 @@ toBinaryTable : [ ], decode: function (data, offset) { + "use strict"; offset = typeof(offset) !== 'undefined' ? offset : 0; var binTable = Base64.toBinaryTable, pad = Base64.base64Pad, diff --git a/include/canvas.js b/include/display.js similarity index 76% rename from include/canvas.js rename to include/display.js index ad273fb..9cd0d68 100644 --- a/include/canvas.js +++ b/include/display.js @@ -9,15 +9,22 @@ /*jslint browser: true, white: false, bitwise: false */ /*global Util, Base64, changeCursor */ -function Canvas(conf) { - "use strict"; +function Display(conf) { +"use strict"; conf = conf || {}; // Configuration var that = {}, // Public API interface - // Private Canvas namespace variables + // Private Display namespace variables + c_ctx = null, c_forceCanvas = false, + c_imageData, c_rgbxImage, c_cmapImage, + + // Predefine function variables (jslint) + imageDataCreate, imageDataGet, rgbxImageData, cmapImageData, + rgbxImageFill, cmapImageFill, setFillColor, rescale, flush, + c_width = 0, c_height = 0, @@ -29,19 +36,24 @@ var that = {}, // Public API interface // Configuration settings function cdef(v, type, defval, desc) { Util.conf_default(conf, that, v, type, defval, desc); } +function cdef_ro(v, type, defval, desc) { + Util.conf_default({}, that, v, type, defval, desc); } // Capability settings, default can be overridden -cdef('prefer_js', 'raw', null, 'Prefer Javascript over canvas methods'); +cdef('target', 'dom', null, 'Canvas element for rendering'); +cdef_ro('context', 'raw', null, 'Canvas 2D context for rendering (read-only)'); +cdef('logo', 'raw', null, 'Logo to display when cleared: {"width": width, "height": height, "data": data}'); +cdef('true_color', 'bool', true, 'Use true-color pixel data'); +cdef('colourMap', 'arr', [], 'Colour map array (when not true-color)'); +cdef('scale', 'float', 1.0, 'Display area scale factor 0.0 - 1.0'); +cdef_ro('width', 'int', null, 'Display area width (read-only)'); +cdef_ro('height', 'int', null, 'Display area height (read-only)'); + +cdef_ro('render_mode', 'str', '', 'Canvas rendering mode (read-only)'); + +cdef('prefer_js', 'str', null, 'Prefer Javascript over canvas methods'); cdef('cursor_uri', 'raw', null, 'Can we render cursor using data URI'); -cdef('target', 'dom', null, 'Canvas element for VNC viewport'); -cdef('focusContainer', 'dom', document, 'DOM element that traps keyboard input'); -cdef('true_color', 'bool', true, 'Request true color pixel data'); -cdef('colourMap', 'raw', [], 'Colour map array (not true color)'); -cdef('scale', 'float', 1.0, 'Viewport scale factor 0.1 - 1.0'); - -cdef('render_mode', 'str', '', 'Canvas rendering mode (read-only)'); - // Override some specific getters/setters that.set_prefer_js = function(val) { if (val && c_forceCanvas) { @@ -52,34 +64,19 @@ that.set_prefer_js = function(val) { return true; }; -that.get_colourMap = function(idx) { - if (typeof idx === 'undefined') { - return conf.colourMap; - } else { - return conf.colourMap[idx]; - } -}; +that.set_render_mode = function () { throw("render_mode is read-only"); }; -that.set_colourMap = function(val, idx) { - if (typeof idx === 'undefined') { - conf.colourMap = val; - } else { - conf.colourMap[idx] = val; - } -}; +that.set_scale = function(scale) { rescale(scale); }; -that.set_render_mode = function () { throw("render_mode is read-only"); }; +that.set_width = function (val) { that.resize(val, c_height); }; +that.get_width = function() { return c_width; }; -that.set_scale = function(scale) { that.rescale(scale); }; +that.set_height = function (val) { that.resize(c_width, val); }; +that.get_height = function() { return c_height; }; +that.set_context = function () { throw("context is read-only"); }; +that.get_context = function () { return c_ctx; }; -// Add some other getters/setters -that.get_width = function() { - return c_width; -}; -that.get_height = function() { - return c_height; -}; // // Private functions @@ -87,9 +84,9 @@ that.get_height = function() { // Create the public API interface function constructor() { - Util.Debug(">> Canvas.init"); + Util.Debug(">> Display.constructor"); - var c, ctx, func, imgTest, tval, i, curDat, curSave, + var c, func, imgTest, tval, i, curDat, curSave, has_imageData = false, UE = Util.Engine; if (! conf.target) { throw("target must be set"); } @@ -102,8 +99,7 @@ function constructor() { if (! c.getContext) { throw("no getContext method"); } - if (! conf.ctx) { conf.ctx = c.getContext('2d'); } - ctx = conf.ctx; + if (! c_ctx) { c_ctx = c.getContext('2d'); } Util.Debug("User Agent: " + navigator.userAgent); if (UE.gecko) { Util.Debug("Browser: gecko " + UE.gecko); } @@ -119,11 +115,11 @@ function constructor() { */ tval = 0; try { - imgTest = ctx.getImageData(0, 0, 1,1); + imgTest = c_ctx.getImageData(0, 0, 1,1); imgTest.data[0] = 123; imgTest.data[3] = 255; - ctx.putImageData(imgTest, 0, 0); - tval = ctx.getImageData(0, 0, 1, 1).data[0]; + c_ctx.putImageData(imgTest, 0, 0); + tval = c_ctx.getImageData(0, 0, 1, 1).data[0]; if (tval === 123) { has_imageData = true; } @@ -132,30 +128,30 @@ function constructor() { if (has_imageData) { Util.Info("Canvas supports imageData"); c_forceCanvas = false; - if (ctx.createImageData) { + if (c_ctx.createImageData) { // If it's there, it's faster Util.Info("Using Canvas createImageData"); conf.render_mode = "createImageData rendering"; - that.imageData = that.imageDataCreate; - } else if (ctx.getImageData) { + c_imageData = imageDataCreate; + } else if (c_ctx.getImageData) { // I think this is mostly just Opera Util.Info("Using Canvas getImageData"); conf.render_mode = "getImageData rendering"; - that.imageData = that.imageDataGet; + c_imageData = imageDataGet; } Util.Info("Prefering javascript operations"); if (conf.prefer_js === null) { conf.prefer_js = true; } - that.rgbxImage = that.rgbxImageData; - that.cmapImage = that.cmapImageData; + c_rgbxImage = rgbxImageData; + c_cmapImage = cmapImageData; } else { Util.Warn("Canvas lacks imageData, using fillRect (slow)"); conf.render_mode = "fillRect rendering (slow)"; c_forceCanvas = true; conf.prefer_js = false; - that.rgbxImage = that.rgbxImageFill; - that.cmapImage = that.cmapImageFill; + c_rgbxImage = rgbxImageFill; + c_cmapImage = cmapImageFill; } if (UE.webkit && UE.webkit >= 534.7 && UE.webkit <= 534.9) { @@ -171,7 +167,7 @@ function constructor() { return function() { myfunc.apply(this, arguments); if (!c_flush_timer) { - c_flush_timer = setTimeout(that.flush, 100); + c_flush_timer = setTimeout(flush, 100); } }; }()); @@ -206,19 +202,11 @@ function constructor() { conf.cursor_uri = false; } - Util.Debug("<< Canvas.init"); + Util.Debug("<< Display.constructor"); return that ; } -// -// Public API interface functions -// - -that.getContext = function () { - return conf.ctx; -}; - -that.rescale = function(factor) { +rescale = function(factor) { var c, tp, x, y, properties = ['transform', 'WebkitTransform', 'MozTransform', null]; c = conf.target; @@ -242,7 +230,7 @@ that.rescale = function(factor) { } if (conf.scale === factor) { - //Util.Debug("Canvas already scaled to '" + factor + "'"); + //Util.Debug("Display already scaled to '" + factor + "'"); return; } @@ -252,35 +240,10 @@ that.rescale = function(factor) { 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_prevStyle = ""; - - 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); - - // No benefit over default ("source-over") in Chrome and firefox - //conf.ctx.globalCompositeOperation = "copy"; -}; - -that.flush = function() { +// Force canvas redraw (for webkit bug #46319 workaround) +flush = function() { var old_val; //Util.Debug(">> flush"); - // Force canvas redraw (for webkit bug #46319 workaround) old_val = conf.target.style.marginRight; conf.target.style.marginRight = "1px"; c_flush_timer = null; @@ -289,7 +252,7 @@ that.flush = function() { }, 1); }; -that.setFillColor = function(color) { +setFillColor = function(color) { var rgb, newStyle; if (conf.true_color) { rgb = color; @@ -298,18 +261,51 @@ that.setFillColor = function(color) { } newStyle = "rgb(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + ")"; if (newStyle !== c_prevStyle) { - conf.ctx.fillStyle = newStyle; + c_ctx.fillStyle = newStyle; c_prevStyle = newStyle; } }; + +// +// Public API interface functions +// + +that.resize = function(width, height) { + var c = conf.target; + + c_prevStyle = ""; + + c.width = width; + c.height = height; + + c_width = c.offsetWidth; + c_height = c.offsetHeight; + + rescale(conf.scale); +}; + +that.clear = function() { + + if (conf.logo) { + that.resize(conf.logo.width, conf.logo.height); + that.blitStringImage(conf.logo.data, 0, 0); + } else { + that.resize(640, 20); + c_ctx.clearRect(0, 0, c_width, c_height); + } + + // No benefit over default ("source-over") in Chrome and firefox + //c_ctx.globalCompositeOperation = "copy"; +}; + that.fillRect = function(x, y, width, height, color) { - that.setFillColor(color); - conf.ctx.fillRect(x, y, width, height); + setFillColor(color); + c_ctx.fillRect(x, y, width, height); }; that.copyImage = function(old_x, old_y, new_x, new_y, width, height) { - conf.ctx.drawImage(conf.target, old_x, old_y, width, height, + c_ctx.drawImage(conf.target, old_x, old_y, width, height, new_x, new_y, width, height); }; @@ -374,36 +370,36 @@ that.setSubTile = function(img, x, y, w, h, color) { that.putTile = function(img) { if (conf.prefer_js) { - that.rgbxImage(img.x, img.y, img.width, img.height, img.data, 0); + c_rgbxImage(img.x, img.y, img.width, img.height, img.data, 0); } // else: No-op, under gecko already done by setSubTile }; -that.imageDataGet = function(width, height) { - return conf.ctx.getImageData(0, 0, width, height); +imageDataGet = function(width, height) { + return c_ctx.getImageData(0, 0, width, height); }; -that.imageDataCreate = function(width, height) { - return conf.ctx.createImageData(width, height); +imageDataCreate = function(width, height) { + return c_ctx.createImageData(width, height); }; -that.rgbxImageData = function(x, y, width, height, arr, offset) { +rgbxImageData = function(x, y, width, height, arr, offset) { var img, i, j, data; - img = that.imageData(width, height); + img = c_imageData(width, height); data = img.data; for (i=0, j=offset; i < (width * height * 4); i=i+4, j=j+4) { - data[i + 0] = arr[j + 0]; + data[i ] = arr[j ]; data[i + 1] = arr[j + 1]; data[i + 2] = arr[j + 2]; data[i + 3] = 255; // Set Alpha } - conf.ctx.putImageData(img, x, y); + c_ctx.putImageData(img, x, y); }; // really slow fallback if we don't have imageData -that.rgbxImageFill = function(x, y, width, height, arr, offset) { +rgbxImageFill = function(x, y, width, height, arr, offset) { var i, j, sx = 0, sy = 0; for (i=0, j=offset; i < (width * height); i+=1, j+=4) { - that.fillRect(x+sx, y+sy, 1, 1, [arr[j+0], arr[j+1], arr[j+2]]); + that.fillRect(x+sx, y+sy, 1, 1, [arr[j], arr[j+1], arr[j+2]]); sx += 1; if ((sx % width) === 0) { sx = 0; @@ -412,22 +408,22 @@ that.rgbxImageFill = function(x, y, width, height, arr, offset) { } }; -that.cmapImageData = function(x, y, width, height, arr, offset) { +cmapImageData = function(x, y, width, height, arr, offset) { var img, i, j, data, rgb, cmap; - img = that.imageData(width, height); + img = c_imageData(width, height); data = img.data; cmap = conf.colourMap; for (i=0, j=offset; i < (width * height * 4); i+=4, j+=1) { rgb = cmap[arr[j]]; - data[i + 0] = rgb[0]; + data[i ] = rgb[0]; data[i + 1] = rgb[1]; data[i + 2] = rgb[2]; data[i + 3] = 255; // Set Alpha } - conf.ctx.putImageData(img, x, y); + c_ctx.putImageData(img, x, y); }; -that.cmapImageFill = function(x, y, width, height, arr, offset) { +cmapImageFill = function(x, y, width, height, arr, offset) { var i, j, sx = 0, sy = 0, cmap; cmap = conf.colourMap; for (i=0, j=offset; i < (width * height); i+=1, j+=1) { @@ -443,15 +439,15 @@ that.cmapImageFill = function(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); + c_rgbxImage(x, y, width, height, arr, offset); } else { - that.cmapImage(x, y, width, height, arr, offset); + c_cmapImage(x, y, width, height, arr, offset); } }; that.blitStringImage = function(str, x, y) { var img = new Image(); - img.onload = function () { conf.ctx.drawImage(img, x, y); }; + img.onload = function () { c_ctx.drawImage(img, x, y); }; img.src = str; }; @@ -469,16 +465,17 @@ that.changeCursor = function(pixels, mask, hotx, hoty, w, h) { }; that.defaultCursor = function() { - conf.target.style.cursor = "default"; + conf.target.style.cursor = "default"; }; return constructor(); // Return the public API interface -} // End of Canvas() +} // End of Display() /* Set CSS cursor property using data URI encoded cursor file */ function changeCursor(target, pixels, mask, hotx, hoty, w, h, cmap) { + "use strict"; var cur = [], rgb, IHDRsz, RGBsz, ANDsz, XORsz, url, idx, alpha, x, y; //Util.Debug(">> changeCursor, x: " + hotx + ", y: " + hoty + ", w: " + w + ", h: " + h); @@ -549,7 +546,7 @@ function changeCursor(target, pixels, mask, hotx, hoty, w, h, cmap) { 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(pixels[idx ]); // red cur.push(alpha); // alpha } } diff --git a/include/input.js b/include/input.js index f155594..43c114e 100644 --- a/include/input.js +++ b/include/input.js @@ -13,7 +13,7 @@ // function Keyboard(conf) { - "use strict"; +"use strict"; conf = conf || {}; // Configuration var that = {}, // Public API interface @@ -27,12 +27,12 @@ function cdef(v, type, defval, desc) { Util.conf_default(conf, that, v, type, defval, desc); } // Capability settings, default can be overridden -cdef('target', 'dom', document, 'DOM element that grabs keyboard input'); -cdef('focused', 'bool', true, 'Capture and send key strokes'); +cdef('target', 'dom', document, 'DOM element that captures keyboard input'); +cdef('focused', 'bool', true, 'Capture and send key events'); -cdef('keyPress', 'func', null, 'Handler for key press/release'); +cdef('onKeyPress', 'func', null, 'Handler for key press/release'); -that.set_target = function () { throw("target cannot be changed"); } +that.set_target = function() { throw("target cannot be changed"); }; // // Private functions @@ -187,7 +187,7 @@ function getKeysym(evt) { } if ((keysym > 255) && (keysym < 0xFF00)) { - msg = "Mapping keysym " + keysym; + msg = "Mapping character code " + keysym; // Map Unicode outside Latin 1 to X11 keysyms keysym = unicodeTable[keysym]; if (typeof(keysym) === 'undefined') { @@ -200,12 +200,13 @@ function getKeysym(evt) { } function show_keyDownList(kind) { + var c; var msg = "keyDownList (" + kind + "):\n"; - for (var c = 0; c < keyDownList.length; c++) { + for (c = 0; c < keyDownList.length; c++) { msg = msg + " " + c + " - keyCode: " + keyDownList[c].keyCode + " - which: " + keyDownList[c].which + "\n"; } - //Util.Debug(msg); + Util.Debug(msg); } function copyKeyEvent(evt) { @@ -302,7 +303,7 @@ function onKeyDown(e) { } var fevt = null, evt = (e ? e : window.event), keysym = null, suppress = false; - Util.Debug("onKeyDown kC:" + evt.keyCode + " cC:" + evt.charCode + " w:" + evt.which); + //Util.Debug("onKeyDown kC:" + evt.keyCode + " cC:" + evt.charCode + " w:" + evt.which); fevt = copyKeyEvent(evt); @@ -313,10 +314,11 @@ function onKeyDown(e) { // If it is a key or key combination that might trigger // browser behaviors or it has no corresponding keyPress // event, then send it immediately - if (conf.keyPress && !ignoreKeyEvent(evt)) { - Util.Debug("keyPress down 1, keysym: " + keysym + - " (key: " + evt.keyCode + ", which: " + evt.which + ")"); - conf.keyPress(keysym, 1, evt); + if (conf.onKeyPress && !ignoreKeyEvent(evt)) { + Util.Debug("onKeyPress down, keysym: " + keysym + + " (onKeyDown key: " + evt.keyCode + + ", which: " + evt.which + ")"); + conf.onKeyPress(keysym, 1, evt); } suppress = true; } @@ -324,7 +326,7 @@ function onKeyDown(e) { if (! ignoreKeyEvent(evt)) { // Add it to the list of depressed keys pushKeyEvent(fevt); - show_keyDownList('down'); + //show_keyDownList('down'); } if (suppress) { @@ -344,7 +346,7 @@ function onKeyPress(e) { } var evt = (e ? e : window.event), kdlen = keyDownList.length, keysym = null; - Util.Debug("onKeyPress kC:" + evt.keyCode + " cC:" + evt.charCode + " w:" + evt.which); + //Util.Debug("onKeyPress kC:" + evt.keyCode + " cC:" + evt.charCode + " w:" + evt.which); if (((evt.which !== "undefined") && (evt.which === 0)) || (getKeysymSpecial(evt))) { @@ -369,13 +371,14 @@ function onKeyPress(e) { Util.Warn("keyDownList empty when keyPress triggered"); } - show_keyDownList('press'); + //show_keyDownList('press'); // Send the translated keysym - if (conf.keyPress && (keysym > 0)) { - Util.Debug("keyPress down 2, keysym: " + keysym + - " (key: " + evt.keyCode + ", which: " + evt.which + ")"); - conf.keyPress(keysym, 1, evt); + if (conf.onKeyPress && (keysym > 0)) { + Util.Debug("onKeyPress down, keysym: " + keysym + + " (onKeyPress key: " + evt.keyCode + + ", which: " + evt.which + ")"); + conf.onKeyPress(keysym, 1, evt); } // Stop keypress events just in case @@ -387,8 +390,8 @@ function onKeyUp(e) { if (! conf.focused) { return true; } - var fevt = null, evt = (e ? e : window.event), i, keysym; - Util.Debug("onKeyUp kC:" + evt.keyCode + " cC:" + evt.charCode + " w:" + evt.which); + var fevt = null, evt = (e ? e : window.event), keysym; + //Util.Debug("onKeyUp kC:" + evt.keyCode + " cC:" + evt.charCode + " w:" + evt.which); fevt = getKeyEvent(evt.keyCode, true); @@ -400,12 +403,15 @@ function onKeyUp(e) { keysym = 0; } - show_keyDownList('up'); + //show_keyDownList('up'); - if (conf.keyPress && (keysym > 0)) { - Util.Debug("keyPress up, keysym: " + keysym + - " (key: " + evt.keyCode + ", which: " + evt.which + ")"); - conf.keyPress(keysym, 0, evt); + if (conf.onKeyPress && (keysym > 0)) { + //Util.Debug("keyPress up, keysym: " + keysym + + // " (key: " + evt.keyCode + ", which: " + evt.which + ")"); + Util.Debug("onKeyPress up, keysym: " + keysym + + " (onKeyPress key: " + evt.keyCode + + ", which: " + evt.which + ")"); + conf.onKeyPress(keysym, 0, evt); } Util.stopEvent(e); return false; @@ -447,7 +453,7 @@ return that; // Return the public API interface // function Mouse(conf) { - "use strict"; +"use strict"; conf = conf || {}; // Configuration var that = {}; // Public API interface @@ -458,14 +464,14 @@ function cdef(v, type, defval, desc) { Util.conf_default(conf, that, v, type, defval, desc); } // Capability settings, default can be overridden -cdef('target', 'dom', document, 'DOM element that grabs mouse input'); +cdef('target', 'dom', document, 'DOM element that captures mouse input'); cdef('focused', 'bool', true, 'Capture and send mouse clicks/movement'); cdef('scale', 'float', 1.0, 'Viewport scale factor 0.0 - 1.0'); -cdef('mouseButton', 'func', null, 'Handler for mouse button click/release'); -cdef('mouseMove', 'func', null, 'Handler for mouse movement'); +cdef('onMouseButton', 'func', null, 'Handler for mouse button click/release'); +cdef('onMouseMove', 'func', null, 'Handler for mouse movement'); -that.set_target = function () { throw("target cannot be changed"); } +that.set_target = function() { throw("target cannot be changed"); }; // // Private functions @@ -489,8 +495,10 @@ function onMouseButton(e, down) { } //Util.Debug("mouse " + pos.x + "," + pos.y + " down: " + down + // " bmask: " + bmask + "(evt.button: " + evt.button + ")"); - if (conf.mouseButton) { - conf.mouseButton(pos.x, pos.y, down, bmask); + if (conf.onMouseButton) { + Util.Debug("onMouseButton " + (down ? "down" : "up") + + ", x: " + pos.x + ", y: " + pos.y + ", bmask: " + bmask); + conf.onMouseButton(pos.x, pos.y, down, bmask); } Util.stopEvent(e); return false; @@ -518,9 +526,9 @@ function onMouseWheel(e) { bmask = 1 << 4; } //Util.Debug('mouse scroll by ' + wheelData + ':' + pos.x + "," + pos.y); - if (conf.mouseButton) { - conf.mouseButton(pos.x, pos.y, 1, bmask); - conf.mouseButton(pos.x, pos.y, 0, bmask); + if (conf.onMouseButton) { + conf.onMouseButton(pos.x, pos.y, 1, bmask); + conf.onMouseButton(pos.x, pos.y, 0, bmask); } Util.stopEvent(e); return false; @@ -534,8 +542,8 @@ function onMouseMove(e) { 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 (conf.mouseMove) { - conf.mouseMove(pos.x, pos.y); + if (conf.onMouseMove) { + conf.onMouseMove(pos.x, pos.y); } } @@ -1862,5 +1870,5 @@ unicodeTable = { 0x28fc : 0x10028fc, 0x28fd : 0x10028fd, 0x28fe : 0x10028fe, - 0x28ff : 0x10028ff, + 0x28ff : 0x10028ff }; diff --git a/include/rfb.js b/include/rfb.js index 3c65a5c..485adfb 100644 --- a/include/rfb.js +++ b/include/rfb.js @@ -7,11 +7,11 @@ */ /*jslint white: false, browser: true, bitwise: false, plusplus: false */ -/*global window, Util, Canvas, Keyboard, Mouse, Websock, Websock_native, Base64, DES, noVNC_logo */ +/*global window, Util, Display, Keyboard, Mouse, Websock, Websock_native, Base64, DES, noVNC_logo */ function RFB(conf) { - "use strict"; +"use strict"; conf = conf || {}; // Configuration var that = {}, // Public API interface @@ -64,7 +64,7 @@ var that = {}, // Public API interface encStats = {}, // [rectCnt, rectCntTot] ws = null, // Websock object - canvas = null, // Canvas object + display = null, // Display object keyboard = null, // Keyboard input handler object mouse = null, // Mouse input handler object sendTimer = null, // Send Queue check timer @@ -124,8 +124,8 @@ var that = {}, // Public API interface function cdef(v, type, defval, desc) { Util.conf_default(conf, that, v, type, defval, desc); } -cdef('target', 'str', null, 'VNC viewport rendering Canvas'); -cdef('focusContainer', 'dom', document, 'Area that traps keyboard input'); +cdef('target', 'dom', null, 'VNC display rendering Canvas object'); +cdef('focusContainer', 'dom', document, 'DOM element that captures keyboard input'); cdef('encrypt', 'bool', false, 'Use TLS/SSL/wss encryption'); cdef('true_color', 'bool', true, 'Request true color pixel data'); @@ -141,12 +141,25 @@ cdef('disconnectTimeout', 'int', 3, 'Time (s) to wait for disconnection'); cdef('check_rate', 'int', 217, 'Timing (ms) of send/receive check'); cdef('fbu_req_rate', 'int', 1413, 'Timing (ms) of frameBufferUpdate requests'); -cdef('updateState', - 'func', function() { Util.Debug("updateState stub"); }, - 'callback: state update'); -cdef('clipboardReceive', - 'func', function() { Util.Debug("clipboardReceive stub"); }, - 'callback: clipboard contents received'); +// Callback functions +cdef('onUpdateState', 'func', function() { }, + 'onUpdateState(rfb, state, oldstate, statusMsg): RFB state update/change '); +cdef('onPasswordRequired', 'func', function() { }, + 'onPasswordRequired(rfb): VNC password is required '); +cdef('onClipboard', 'func', function() { }, + 'onClipboard(rfb, text): RFB clipboard contents received'); +cdef('onBell', 'func', function() { }, + 'onBell(rfb): RFB Bell message received '); +cdef('onFBUReceive', 'func', function() { }, + 'onFBUReceive(rfb, fbu): RFB FBU received but not yet processed '); +cdef('onFBUComplete', 'func', function() { }, + 'onFBUComplete(rfb, fbu): RFB FBU received and processed '); + +// These callback names are deprecated +cdef('updateState', 'func', function() { }, + 'obsolete, use onUpdateState'); +cdef('clipboardReceive', 'func', function() { }, + 'obsolete, use onClipboard'); // Override/add some specific getters/setters @@ -154,7 +167,7 @@ that.set_local_cursor = function(cursor) { if ((!cursor) || (cursor in {'0':1, 'no':1, 'false':1})) { conf.local_cursor = false; } else { - if (canvas.get_cursor_uri()) { + if (display.get_cursor_uri()) { conf.local_cursor = true; } else { Util.Warn("Browser does not support local cursor"); @@ -162,8 +175,8 @@ that.set_local_cursor = function(cursor) { } }; -that.get_canvas = function() { - return canvas; +that.get_display = function() { + return display; }; that.get_keyboard = function() { return keyboard; @@ -181,7 +194,8 @@ that.get_mouse = function() { // Setup routines // -// Create the public API interface and initialize +// Create the public API interface and initialize values that stay +// constant across connect/disconnect function constructor() { var i, rmode; Util.Debug(">> RFB.constructor"); @@ -192,19 +206,49 @@ function constructor() { encNames[encodings[i][1]] = encodings[i][0]; encStats[encodings[i][1]] = [0, 0]; } - // Initialize canvas, mouse and keyboard + // Initialize display, mouse, keyboard, and websock try { - canvas = new Canvas({'target': conf.target}); - keyboard = new Keyboard({'target': conf.focusContainer, - 'keyPress': keyPress}); - mouse = new Mouse({'target': conf.target, - 'mouseButton': mouseButton, - 'mouseMove': mouseMove}); + display = new Display({'target': conf.target}); } catch (exc) { - Util.Error("Canvas exception: " + exc); - updateState('fatal', "No working Canvas"); + Util.Error("Display exception: " + exc); + updateState('fatal', "No working Display"); } - rmode = canvas.get_render_mode(); + keyboard = new Keyboard({'target': conf.focusContainer, + 'onKeyPress': keyPress}); + mouse = new Mouse({'target': conf.target, + 'onMouseButton': mouseButton, + 'onMouseMove': mouseMove}); + + rmode = display.get_render_mode(); + + if (typeof noVNC_logo !== 'undefined') { + display.set_logo(noVNC_logo); + } + + ws = new Websock(); + ws.on('message', handle_message); + ws.on('open', function() { + if (rfb_state === "connect") { + updateState('ProtocolVersion', "Starting VNC handshake"); + } else { + fail("Got unexpected WebSockets connection"); + } + }); + ws.on('close', function() { + if (rfb_state === 'disconnect') { + updateState('disconnected', 'VNC disconnected'); + } else if (rfb_state === 'ProtocolVersion') { + fail('Failed to connect to server'); + } else if (rfb_state in {'failed':1, 'disconnected':1}) { + Util.Error("Received onclose while disconnected"); + } else { + fail('Server disconnected'); + } + }); + ws.on('error', function(e) { + fail("WebSock error: " + e); + }); + init_vars(); @@ -246,36 +290,13 @@ function connect() { Util.Debug("<< RFB.connect"); } +// Initialize variables that are reset before each connection init_vars = function() { var i; /* Reset state */ - ws = new Websock(); ws.init(); - ws.on('message', handle_message); - ws.on('open', function() { - if (rfb_state === "connect") { - updateState('ProtocolVersion', "Starting VNC handshake"); - } else { - fail("Got unexpected WebSockets connection"); - } - }); - ws.on('close', function() { - if (rfb_state === 'disconnect') { - updateState('disconnected', 'VNC disconnected'); - } else if (rfb_state === 'ProtocolVersion') { - fail('Failed to connect to server'); - } else if (rfb_state in {'failed':1, 'disconnected':1}) { - Util.Error("Received onclose while disconnected"); - } else { - fail('Server disconnected'); - } - }); - ws.on('error', function(e) { - fail("WebSock error: " + e); - }); - FBU.rects = 0; FBU.subrects = 0; // RRE and HEXTILE FBU.lines = 0; // RAW @@ -317,25 +338,23 @@ print_stats = function() { /* - * Running states: - * disconnected - idle state - * normal - connected - * * Page states: * loaded - page load, equivalent to disconnected - * connect - starting initialization - * disconnect - starting disconnect - * failed - abnormal transition to disconnected + * disconnected - idle state + * connect - starting to connect (to ProtocolVersion) + * normal - connected + * disconnect - starting to disconnect + * failed - abnormal disconnect * fatal - failed to load page, or fatal error * - * VNC initialization states: - * ProtocolVersion + * RFB protocol initialization states: + * ProtocolVersion * Security * Authentication * password - waiting for password, not part of RFB * SecurityResult * ClientInitialization - not triggered by server message - * ServerInitialization + * ServerInitialization (to normal) */ updateState = function(state, statusMsg) { var func, cmsg, oldstate = rfb_state; @@ -362,22 +381,15 @@ updateState = function(state, statusMsg) { msgTimer = null; } - if (canvas && canvas.getContext()) { + if (display && display.get_context()) { keyboard.ungrab(); mouse.ungrab(); - canvas.defaultCursor(); - if (Util.get_logging() !== 'debug') { - canvas.clear(); - } - + display.defaultCursor(); if ((Util.get_logging() !== 'debug') || (state === 'loaded')) { // Show noVNC logo on load and when disconnected if // debug is off - if (typeof noVNC_logo !== 'undefined' && noVNC_logo) { - canvas.resize(noVNC_logo.width, noVNC_logo.height); - canvas.blitStringImage(noVNC_logo.data, 0, 0); - } + display.clear(); } } @@ -476,9 +488,11 @@ updateState = function(state, statusMsg) { if ((oldstate === 'failed') && (state === 'disconnected')) { // Leave the failed message - conf.updateState(that, state, oldstate); + conf.updateState(that, state, oldstate); // Obsolete + conf.onUpdateState(that, state, oldstate); } else { - conf.updateState(that, state, oldstate, statusMsg); + conf.updateState(that, state, oldstate, statusMsg); // Obsolete + conf.onUpdateState(that, state, oldstate, statusMsg); } }; @@ -681,7 +695,10 @@ init_msg = function() { break; case 2: // VNC authentication if (rfb_password.length === 0) { + // Notify via both callbacks since it is kind of + // a RFB state change and a UI interface issue. updateState('password', "Password Required"); + conf.onPasswordRequired(that); return; } if (ws.rQwait("auth challenge", 16)) { return false; } @@ -759,7 +776,8 @@ init_msg = function() { name_length = ws.rQshift32(); fb_name = ws.rQshiftStr(name_length); - canvas.resize(fb_width, fb_height, conf.true_color); + display.set_true_color(conf.true_color); + display.resize(fb_width, fb_height); keyboard.grab(); mouse.grab(); @@ -796,7 +814,7 @@ init_msg = function() { normal_msg = function() { //Util.Debug(">> normal_msg"); - var ret = true, msg_type, length, + var ret = true, msg_type, length, text, c, first_colour, num_colours, red, green, blue; if (FBU.rects > 0) { @@ -820,13 +838,15 @@ normal_msg = function() { //Util.Debug("red after: " + red); green = parseInt(ws.rQshift16() / 256, 10); blue = parseInt(ws.rQshift16() / 256, 10); - canvas.set_colourMap([red, green, blue], first_colour + c); + Util.Debug("*** colourMap: " + display.get_colourMap()); + display.set_colourMap([red, green, blue], first_colour + c); } Util.Info("Registered " + num_colours + " colourMap entries"); - //Util.Debug("colourMap: " + canvas.get_colourMap()); + //Util.Debug("colourMap: " + display.get_colourMap()); break; case 2: // Bell - Util.Warn("Bell (unsupported)"); + Util.Debug("Bell"); + conf.onBell(that); break; case 3: // ServerCutText Util.Debug("ServerCutText"); @@ -835,7 +855,9 @@ normal_msg = function() { length = ws.rQshift32(); if (ws.rQwait("ServerCutText", length, 8)) { return false; } - conf.clipboardReceive(that, ws.rQshiftStr(length)); + text = ws.rQshiftStr(length); + conf.clipboardReceive(that, text); // Obsolete + conf.onClipboard(that, text); break; default: fail("Disconnected: illegal server message type " + msg_type); @@ -883,6 +905,12 @@ framebufferUpdate = function() { FBU.encoding = parseInt((hdr[8] << 24) + (hdr[9] << 16) + (hdr[10] << 8) + hdr[11], 10); + conf.onFBUReceive(that, + {'x': FBU.x, 'y': FBU.y, + 'width': FBU.width, 'height': FBU.height, + 'encoding': FBU.encoding, + 'encodingName': encNames[FBU.encoding]}); + if (encNames[FBU.encoding]) { // Debug: /* @@ -943,6 +971,13 @@ framebufferUpdate = function() { return ret; // false ret means need more data } } + + conf.onFBUComplete(that, + {'x': FBU.x, 'y': FBU.y, + 'width': FBU.width, 'height': FBU.height, + 'encoding': FBU.encoding, + 'encodingName': encNames[FBU.encoding]}); + return true; // We finished this FBU }; @@ -963,7 +998,7 @@ encHandlers.RAW = function display_raw() { cur_y = FBU.y + (FBU.height - FBU.lines); cur_height = Math.min(FBU.lines, Math.floor(ws.rQlen()/(FBU.width * fb_Bpp))); - canvas.blitImage(FBU.x, cur_y, FBU.width, cur_height, + display.blitImage(FBU.x, cur_y, FBU.width, cur_height, ws.get_rQ(), ws.get_rQi()); ws.rQshiftBytes(FBU.width * cur_height * fb_Bpp); FBU.lines -= cur_height; @@ -986,7 +1021,7 @@ encHandlers.COPYRECT = function display_copy_rect() { if (ws.rQwait("COPYRECT", 4)) { return false; } old_x = ws.rQshift16(); old_y = ws.rQshift16(); - canvas.copyImage(old_x, old_y, FBU.x, FBU.y, FBU.width, FBU.height); + display.copyImage(old_x, old_y, FBU.x, FBU.y, FBU.width, FBU.height); FBU.rects -= 1; FBU.bytes = 0; return true; @@ -1000,7 +1035,7 @@ encHandlers.RRE = function display_rre() { if (ws.rQwait("RRE", 4+fb_Bpp)) { return false; } FBU.subrects = ws.rQshift32(); color = ws.rQshiftBytes(fb_Bpp); // Background - canvas.fillRect(FBU.x, FBU.y, FBU.width, FBU.height, color); + display.fillRect(FBU.x, FBU.y, FBU.width, FBU.height, color); } while ((FBU.subrects > 0) && (ws.rQlen() >= (fb_Bpp + 8))) { color = ws.rQshiftBytes(fb_Bpp); @@ -1008,7 +1043,7 @@ encHandlers.RRE = function display_rre() { y = ws.rQshift16(); width = ws.rQshift16(); height = ws.rQshift16(); - canvas.fillRect(FBU.x + x, FBU.y + y, width, height, color); + display.fillRect(FBU.x + x, FBU.y + y, width, height, color); FBU.subrects -= 1; } //Util.Debug(" display_rre: rects: " + FBU.rects + @@ -1101,10 +1136,10 @@ encHandlers.HEXTILE = function display_hextile() { /* Weird: ignore blanks after RAW */ Util.Debug(" Ignoring blank after RAW"); } else { - canvas.fillRect(x, y, w, h, FBU.background); + display.fillRect(x, y, w, h, FBU.background); } } else if (FBU.subencoding & 0x01) { // Raw - canvas.blitImage(x, y, w, h, rQ, rQi); + display.blitImage(x, y, w, h, rQ, rQi); rQi += FBU.bytes - 1; } else { if (FBU.subencoding & 0x02) { // Background @@ -1116,7 +1151,7 @@ encHandlers.HEXTILE = function display_hextile() { rQi += fb_Bpp; } - tile = canvas.getTile(x, y, w, h, FBU.background); + tile = display.getTile(x, y, w, h, FBU.background); if (FBU.subencoding & 0x08) { // AnySubrects subrects = rQ[rQi]; rQi += 1; @@ -1137,10 +1172,10 @@ encHandlers.HEXTILE = function display_hextile() { sw = (wh >> 4) + 1; sh = (wh & 0x0f) + 1; - canvas.setSubTile(tile, sx, sy, sw, sh, color); + display.setSubTile(tile, sx, sy, sw, sh, color); } } - canvas.putTile(tile); + display.putTile(tile); } ws.set_rQi(rQi); FBU.lastsubencoding = FBU.subencoding; @@ -1205,7 +1240,7 @@ encHandlers.TIGHT_PNG = function display_tight_png() { case "fill": ws.rQshift8(); // shift off ctl color = ws.rQshiftBytes(fb_depth); - canvas.fillRect(FBU.x, FBU.y, FBU.width, FBU.height, color); + display.fillRect(FBU.x, FBU.y, FBU.width, FBU.height, color); break; case "jpeg": case "png": @@ -1242,7 +1277,7 @@ extract_data_uri = function(arr) { scan_tight_imgQ = function() { var img, imgQ, ctx; - ctx = canvas.getContext(); + ctx = display.get_context(); if (rfb_state === 'normal') { imgQ = FBU.imgQ; while ((imgQ.length > 0) && (imgQ[0][0].complete)) { @@ -1257,8 +1292,7 @@ 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); + display.resize(fb_width, fb_height); timing.fbu_rt_start = (new Date()).getTime(); // Send a new non-incremental request ws.send(fbUpdateRequest(0)); @@ -1286,7 +1320,7 @@ encHandlers.Cursor = function set_cursor() { //Util.Debug(" set_cursor, x: " + x + ", y: " + y + ", w: " + w + ", h: " + h); - canvas.changeCursor(ws.rQshiftBytes(pixelslength), + display.changeCursor(ws.rQshiftBytes(pixelslength), ws.rQshiftBytes(masklength), x, y, w, h); diff --git a/include/ui.js b/include/ui.js index 3e30420..df66da8 100644 --- a/include/ui.js +++ b/include/ui.js @@ -8,7 +8,7 @@ "use strict"; /*jslint white: false, browser: true */ -/*global window, $D, Util, WebUtil, RFB, Canvas, Element, Fx */ +/*global window, $D, Util, WebUtil, RFB, Display */ var UI = { @@ -59,8 +59,8 @@ load: function(target) { html += ' id="menuButton"'; html += ' onclick="UI.clickSettingsMenu();">'; html += ' > settingsDisabled"); $D('VNC_encrypt').disabled = disabled; $D('VNC_true_color').disabled = disabled; - if (rfb && rfb.get_canvas() && rfb.get_canvas().get_cursor_uri()) { + if (rfb && rfb.get_display() && rfb.get_display().get_cursor_uri()) { $D('VNC_cursor').disabled = disabled; } else { UI.updateSetting('cursor', false); @@ -281,7 +281,7 @@ settingsApply: function() { //Util.Debug(">> settingsApply"); UI.saveSetting('encrypt'); UI.saveSetting('true_color'); - if (UI.rfb.get_canvas().get_cursor_uri()) { + if (UI.rfb.get_display().get_cursor_uri()) { UI.saveSetting('cursor'); } UI.saveSetting('shared'); @@ -398,12 +398,12 @@ disconnect: function() { UI.rfb.disconnect(); }, -canvasBlur: function() { +displayBlur: function() { UI.rfb.get_keyboard().set_focused(false); UI.rfb.get_mouse().set_focused(false); }, -canvasFocus: function() { +displayFocus: function() { UI.rfb.get_keyboard().set_focused(true); UI.rfb.get_mouse().set_focused(true); }, diff --git a/include/util.js b/include/util.js index 34840f6..835ea0a 100644 --- a/include/util.js +++ b/include/util.js @@ -89,32 +89,47 @@ Util.conf_default = function(cfg, api, v, type, defval, desc) { api['get_' + v + '_desc'] = desc; // Default getter if (typeof api['get_' + v] === 'undefined') { - api['get_' + v] = function () { + api['get_' + v] = function (idx) { + if ((type in {'arr':1, 'array':1}) && + (typeof idx !== 'undefined')) { + return cfg[v][idx]; + } else { return cfg[v]; - }; + } + }; } + // Default setter if (typeof api['set_' + v] === 'undefined') { - api['set_' + v] = function (val) { - if (type in {'boolean':1, 'bool':1}) { - if ((!val) || (val in {'0':1, 'no':1, 'false':1})) { - val = false; - } else { - val = true; - } - } else if (type in {'integer':1, 'int':1}) { - val = parseInt(val, 10); - } else if (type === 'func') { - if (!val) { - val = function () {}; - } + api['set_' + v] = function (val, idx) { + if (type in {'boolean':1, 'bool':1}) { + if ((!val) || (val in {'0':1, 'no':1, 'false':1})) { + val = false; + } else { + val = true; + } + } else if (type in {'integer':1, 'int':1}) { + val = parseInt(val, 10); + } else if (type === 'func') { + if (!val) { + val = function () {}; } + } + if (typeof idx !== 'undefined') { + cfg[v][idx] = val; + } else { cfg[v] = val; - }; + } + }; } if (typeof cfg[v] === 'undefined') { // Set to default + if (type in {'arr':1, 'array':1}) { + if (! (defval instanceof Array)) { + defval = []; + } + } api['set_' + v](defval); } else { // Coerce existing setting to the right type diff --git a/include/vnc.js b/include/vnc.js index 0bd78a0..9e4cd1b 100644 --- a/include/vnc.js +++ b/include/vnc.js @@ -6,7 +6,6 @@ * See README.md for usage and integration instructions. */ -"use strict"; /*jslint evil: true */ /*global window, document, INCLUDE_URI */ @@ -18,6 +17,8 @@ function get_INCLUDE_URI() { } (function () { + "use strict"; + var extra = "", start, end; start = " -- 2.39.5