diff options
author | primalmotion <antoine.mercadal@inframonde.eu> | 2010-08-03 10:11:03 +0200 |
---|---|---|
committer | primalmotion <antoine.mercadal@inframonde.eu> | 2010-08-03 10:11:03 +0200 |
commit | 5e5d81dd86c51fb43c8ef028509211471728321f (patch) | |
tree | 0b0b0692f1e98438bf99641500ae886b0d410ef2 | |
parent | f8f5c923dde50714ebe55c20b3705049a1ea7690 (diff) | |
parent | 8db09746b7b96da6bf48d0d7ef22f40df7205f7d (diff) | |
download | websockify-5e5d81dd86c51fb43c8ef028509211471728321f.tar.gz |
Merge remote branch 'upstream/master'
Conflicts:
include/canvas.js
include/rfb.js
include/util.js
vnc.html
-rw-r--r-- | README.md | 13 | ||||
-rw-r--r-- | docs/TODO | 32 | ||||
-rw-r--r-- | include/canvas.js | 858 | ||||
-rw-r--r-- | include/default_controls.js | 78 | ||||
-rw-r--r-- | include/rfb.js | 1827 | ||||
-rw-r--r-- | include/util.js | 126 | ||||
-rw-r--r-- | tests/canvas.html | 58 | ||||
-rw-r--r-- | tests/cursor.html | 6 | ||||
-rw-r--r-- | tests/input.html | 7 | ||||
-rw-r--r-- | vnc.html | 11 | ||||
-rw-r--r-- | vnc_auto.html | 53 |
11 files changed, 1586 insertions, 1483 deletions
@@ -195,7 +195,7 @@ The client is designed to be easily integrated with existing web structure and style. At a minimum you must include the `vnc.js` and `default_controls.js` -scripts and call their load() functions. For example: +scripts and call DefaultControls.load(). For example: <head> <script src='include/vnc.js'></script> @@ -203,12 +203,13 @@ scripts and call their load() functions. For example: </head> <body> <div id='vnc'>Loading</div> + + <script> + window.onload = function () { + DefaultControls.load('vnc'); + } + </script> </body> - <script> - window.onload = function () { - DefaultControls.load('vnc'); - RFB.load(); }; - </script> See `vnc.html` and `vnc_auto.html` for examples. The file `include/plain.css` has a list of stylable elements. @@ -10,38 +10,22 @@ Short Term: - Test on IE 9 preview 3. -- Possibly support IE <= 8.0 using excanvas or fxcanvas: - 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: -- Viewport and/or scaling support. +- Viewport support -- Status bar buttons: - - Isolate menu UI in DefaultControls.js - - Icons in status area upper left - - Dialogs float over canvas - - Turn off events while menu open (but still update display) +- Status bar menu/buttons: - Explanatory hover text over buttons - - Connect/disconnect button - - Configuration menu: - - Store in cookies - - Items (move to public settings): - - Host - - Port - - Password - - Encrypt - - TrueColor - - speed vs. bandwidth selection - - b64encoding - - shared mode + - Tunable: speed vs. bandwidth selection + - Tunable: CPU use versus latency. + - shared mode + - Scaling - Keyboard menu: - Ctrl Lock, Alt Lock, SysRq Lock @@ -50,10 +34,6 @@ Medium Term: - Clipboard button -> popup: - text, clear and send buttons - - password popup: - - when none set, but password required - - session only, should not store in cookie - Longer Term: diff --git a/include/canvas.js b/include/canvas.js index f670741..7b02b3e 100644 --- a/include/canvas.js +++ b/include/canvas.js @@ -6,65 +6,300 @@ * See README.md for usage and integration instructions. */ -//"use strict"; -/*jslint white: false, bitwise: false */ -/*global window, $, Util, Base64 */ +"use strict"; +/*jslint browser: true, white: false, bitwise: false */ +/*global window, Util, Base64 */ -// Globals defined here -//var Canvas; +function Canvas(conf) { -// Everything namespaced inside Canvas -Canvas = { +conf = conf || {}; // Configuration +var that = {}, // Public API interface -prefer_js : false, // make private -force_canvas : false, // make private -cursor_uri : true, // make private + // Pre-declare functions used before definitions (jslint)jslint + setFillColor, fillRect, -true_color : false, -colourMap : [], + // Private Canvas namespace variables + c_forceCanvas = false, -scale: 1, -c_wx : 0, -c_wy : 0, -ctx : null, -defaut_canvas_w: 800, -defaut_canvas_h: 490, + c_width = 0, + c_height = 0, -prevStyle: "", + c_prevStyle = "", -focused : true, -keyPress : null, -mouseButton : null, -mouseMove : null, + c_keyPress = null, + c_mouseButton = null, + c_mouseMove = null; -onMouseButton: function(e, down) { + +// Capability settings, default can be overridden +Util.conf_default(conf, that, 'prefer_js', null); +Util.conf_default(conf, that, 'cursor_uri', null); + +// Configuration settings +Util.conf_default(conf, that, 'target', null); +Util.conf_default(conf, that, 'true_color', true); +Util.conf_default(conf, that, 'focused', true); +Util.conf_default(conf, that, 'colourMap', []); +Util.conf_default(conf, that, 'scale', 1); + +// Override some specific getters/setters +that.set_prefer_js = function(val) { + if (val && c_forceCanvas) { + Util.Warn("Preferring Javascript to Canvas ops is not supported"); + return false; + } + conf.prefer_js = val; + return true; +}; + +that.get_colourMap = function(idx) { + if (typeof idx === 'undefined') { + return conf.colourMap; + } else { + return conf.colourMap[idx]; + } +}; + +that.set_colourMap = function(val, idx) { + if (typeof idx === 'undefined') { + conf.colourMap = val; + } else { + conf.colourMap[idx] = val; + } +}; + +// Add some other getters/setters +that.get_width = function() { + return c_width; +}; +that.get_height = function() { + return c_height; +}; + + + +// +// Private functions +// + +// Create the public API interface +function constructor() { + Util.Debug(">> Canvas.init"); + + var c, ctx, imgTest, tval, i, curDat, curSave, + has_imageData = false; + + if (! conf.target) { throw("target must be set"); } + + if (typeof conf.target === 'string') { + conf.target = window.$(conf.target); + } + + c = conf.target; + + if (! c.getContext) { throw("no getContext method"); } + + if (! conf.ctx) { conf.ctx = c.getContext('2d'); } + ctx = conf.ctx; + + that.clear(); + + /* + * Determine browser Canvas feature support + * and select fastest rendering methods + */ + tval = 0; + try { + imgTest = 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]; + if (tval === 123) { + has_imageData = true; + } + } catch (exc1) {} + + if (has_imageData) { + Util.Info("Canvas supports imageData"); + c_forceCanvas = false; + if (ctx.createImageData) { + // If it's there, it's faster + Util.Info("Using Canvas createImageData"); + that.imageData = that.imageDataCreate; + } else if (ctx.getImageData) { + Util.Info("Using Canvas getImageData"); + that.imageData = that.imageDataGet; + } + Util.Info("Prefering javascript operations"); + if (conf.prefer_js === null) { + conf.prefer_js = true; + } + that.rgbxImage = that.rgbxImageData; + that.cmapImage = that.cmapImageData; + } else { + Util.Warn("Canvas lacks imageData, using fillRect (slow)"); + c_forceCanvas = true; + conf.prefer_js = false; + that.rgbxImage = that.rgbxImageFill; + that.cmapImage = that.cmapImageFill; + } + + /* + * Determine browser support for setting the cursor via data URI + * scheme + */ + curDat = []; + for (i=0; i < 8 * 8 * 4; i += 1) { + curDat.push(255); + } + try { + curSave = c.style.cursor; + that.changeCursor(curDat, curDat, 2, 2, 8, 8); + if (c.style.cursor) { + if (conf.cursor_uri === null) { + conf.cursor_uri = true; + } + Util.Info("Data URI scheme cursor supported"); + } else { + if (conf.cursor_uri === null) { + conf.cursor_uri = false; + } + Util.Warn("Data URI scheme cursor not supported"); + } + c.style.cursor = curSave; + } catch (exc2) { + Util.Error("Data URI scheme cursor test exception: " + exc2); + conf.cursor_uri = false; + } + + conf.focused = true; + + Util.Debug("<< Canvas.init"); + return that ; +} + +/* Translate DOM key event to keysym value */ +function getKeysym(e) { + var evt, keysym; + evt = (e ? e : window.event); + + /* Remap modifier and special keys */ + switch ( evt.keyCode ) { + case 8 : keysym = 0xFF08; break; // BACKSPACE + case 9 : keysym = 0xFF09; break; // TAB + case 13 : keysym = 0xFF0D; break; // ENTER + case 27 : keysym = 0xFF1B; break; // ESCAPE + case 45 : keysym = 0xFF63; break; // INSERT + case 46 : keysym = 0xFFFF; break; // DELETE + case 36 : keysym = 0xFF50; break; // HOME + case 35 : keysym = 0xFF57; break; // END + case 33 : keysym = 0xFF55; break; // PAGE_UP + case 34 : keysym = 0xFF56; break; // PAGE_DOWN + case 37 : keysym = 0xFF51; break; // LEFT + case 38 : keysym = 0xFF52; break; // UP + case 39 : keysym = 0xFF53; break; // RIGHT + case 40 : keysym = 0xFF54; break; // DOWN + case 112 : keysym = 0xFFBE; break; // F1 + case 113 : keysym = 0xFFBF; break; // F2 + case 114 : keysym = 0xFFC0; break; // F3 + case 115 : keysym = 0xFFC1; break; // F4 + case 116 : keysym = 0xFFC2; break; // F5 + case 117 : keysym = 0xFFC3; break; // F6 + case 118 : keysym = 0xFFC4; break; // F7 + case 119 : keysym = 0xFFC5; break; // F8 + case 120 : keysym = 0xFFC6; break; // F9 + case 121 : keysym = 0xFFC7; break; // F10 + case 122 : keysym = 0xFFC8; break; // F11 + case 123 : keysym = 0xFFC9; break; // F12 + case 16 : keysym = 0xFFE1; break; // SHIFT + case 17 : keysym = 0xFFE3; break; // CONTROL + //case 18 : keysym = 0xFFE7; break; // Left Meta (Mac Option) + case 18 : keysym = 0xFFE9; break; // Left ALT (Mac Command) + default : keysym = evt.keyCode; break; + } + + /* Remap symbols */ + switch (keysym) { + case 186 : keysym = 59; break; // ; (IE) + case 187 : keysym = 61; break; // = (IE) + case 188 : keysym = 44; break; // , (Mozilla, IE) + case 109 : // - (Mozilla) + if (Util.Engine.gecko) { + keysym = 45; } + break; + case 189 : keysym = 45; break; // - (IE) + case 190 : keysym = 46; break; // . (Mozilla, IE) + case 191 : keysym = 47; break; // / (Mozilla, IE) + case 192 : keysym = 96; break; // ` (Mozilla, IE) + case 219 : keysym = 91; break; // [ (Mozilla, IE) + case 220 : keysym = 92; break; // \ (Mozilla, IE) + case 221 : keysym = 93; break; // ] (Mozilla, IE) + case 222 : keysym = 39; break; // ' (Mozilla, IE) + } + + /* Remap shifted and unshifted keys */ + if (!!evt.shiftKey) { + switch (keysym) { + case 48 : keysym = 41 ; break; // ) (shifted 0) + case 49 : keysym = 33 ; break; // ! (shifted 1) + case 50 : keysym = 64 ; break; // @ (shifted 2) + case 51 : keysym = 35 ; break; // # (shifted 3) + case 52 : keysym = 36 ; break; // $ (shifted 4) + case 53 : keysym = 37 ; break; // % (shifted 5) + case 54 : keysym = 94 ; break; // ^ (shifted 6) + case 55 : keysym = 38 ; break; // & (shifted 7) + case 56 : keysym = 42 ; break; // * (shifted 8) + case 57 : keysym = 40 ; break; // ( (shifted 9) + + case 59 : keysym = 58 ; break; // : (shifted `) + case 61 : keysym = 43 ; break; // + (shifted ;) + case 44 : keysym = 60 ; break; // < (shifted ,) + case 45 : keysym = 95 ; break; // _ (shifted -) + case 46 : keysym = 62 ; break; // > (shifted .) + case 47 : keysym = 63 ; break; // ? (shifted /) + case 96 : keysym = 126; break; // ~ (shifted `) + case 91 : keysym = 123; break; // { (shifted [) + case 92 : keysym = 124; break; // | (shifted \) + case 93 : keysym = 125; break; // } (shifted ]) + case 39 : keysym = 34 ; break; // " (shifted ') + } + } else if ((keysym >= 65) && (keysym <=90)) { + /* Remap unshifted A-Z */ + keysym += 32; + } + + return keysym; +} + +function onMouseButton(e, down) { var evt, pos, bmask; - if (! Canvas.focused) { + if (! conf.focused) { return true; } evt = (e ? e : window.event); - pos = Util.getEventPosition(e, $(Canvas.id), Canvas.scale); + pos = Util.getEventPosition(e, conf.target, conf.scale); bmask = 1 << evt.button; //Util.Debug('mouse ' + pos.x + "," + pos.y + " down: " + down + " bmask: " + bmask); - if (Canvas.mouseButton) { - Canvas.mouseButton(pos.x, pos.y, down, bmask); + if (c_mouseButton) { + c_mouseButton(pos.x, pos.y, down, bmask); } Util.stopEvent(e); return false; -}, +} -onMouseDown: function (e) { - Canvas.onMouseButton(e, 1); -}, +function onMouseDown(e) { + onMouseButton(e, 1); +} -onMouseUp: function (e) { - Canvas.onMouseButton(e, 0); -}, +function onMouseUp(e) { + onMouseButton(e, 0); +} -onMouseWheel: function (e) { +function onMouseWheel(e) { var evt, pos, bmask, wheelData; evt = (e ? e : window.event); - pos = Util.getEventPosition(e, $(Canvas.id), Canvas.scale); + pos = Util.getEventPosition(e, conf.target, conf.scale); wheelData = evt.detail ? evt.detail * -1 : evt.wheelDelta / 40; if (wheelData > 0) { bmask = 1 << 3; @@ -72,220 +307,110 @@ onMouseWheel: function (e) { bmask = 1 << 4; } //Util.Debug('mouse scroll by ' + wheelData + ':' + pos.x + "," + pos.y); - if (Canvas.mouseButton) { - Canvas.mouseButton(pos.x, pos.y, 1, bmask); - Canvas.mouseButton(pos.x, pos.y, 0, bmask); + if (c_mouseButton) { + c_mouseButton(pos.x, pos.y, 1, bmask); + c_mouseButton(pos.x, pos.y, 0, bmask); } Util.stopEvent(e); return false; -}, - +} -onMouseMove: function (e) { +function onMouseMove(e) { var evt, pos; evt = (e ? e : window.event); - pos = Util.getEventPosition(e, $(Canvas.id), Canvas.scale); + pos = Util.getEventPosition(e, conf.target, conf.scale); //Util.Debug('mouse ' + evt.which + '/' + evt.button + ' up:' + pos.x + "," + pos.y); - if (Canvas.mouseMove) { - Canvas.mouseMove(pos.x, pos.y); + if (c_mouseMove) { + c_mouseMove(pos.x, pos.y); } -}, +} -onKeyDown: function (e) { - //Util.Debug("keydown: " + Canvas.getKeysym(e)); - if (! Canvas.focused) { +function onKeyDown(e) { + //Util.Debug("keydown: " + getKeysym(e)); + if (! conf.focused) { return true; } - if (Canvas.keyPress) { - Canvas.keyPress(Canvas.getKeysym(e), 1); + if (c_keyPress) { + c_keyPress(getKeysym(e), 1); } Util.stopEvent(e); return false; -}, +} -onKeyUp : function (e) { - //Util.Debug("keyup: " + Canvas.getKeysym(e)); - if (! Canvas.focused) { +function onKeyUp(e) { + //Util.Debug("keyup: " + getKeysym(e)); + if (! conf.focused) { return true; } - if (Canvas.keyPress) { - Canvas.keyPress(Canvas.getKeysym(e), 0); + if (c_keyPress) { + c_keyPress(getKeysym(e), 0); } Util.stopEvent(e); return false; -}, +} -onMouseDisable: function (e) { +function onMouseDisable(e) { var evt, pos; - if (! Canvas.focused) { + if (! conf.focused) { return true; } evt = (e ? e : window.event); - pos = Util.getPosition($(Canvas.id)); + pos = Util.getPosition(conf.target); /* Stop propagation if inside canvas area */ if ((evt.clientX >= pos.x) && (evt.clientY >= pos.y) && - (evt.clientX < (pos.x + Canvas.c_wx)) && - (evt.clientY < (pos.y + Canvas.c_wy))) { + (evt.clientX < (pos.x + c_width)) && + (evt.clientY < (pos.y + c_height))) { //Util.Debug("mouse event disabled"); Util.stopEvent(e); return false; } //Util.Debug("mouse event not disabled"); return true; -}, - - -init: function (id) { - var c, imgTest, tval, i, curDat, curSave; - Util.Debug(">> Canvas.init"); - - Canvas.id = id; - c = $(Canvas.id); - - if (! c.getContext) { throw("No getContext method"); } - Canvas.ctx = c.getContext('2d'); - - Canvas.clear(); - - /* - * Determine browser Canvas feature support - * and select fastest rendering methods - */ - tval = 0; - Canvas.has_imageData = false; - try { - imgTest = Canvas.ctx.getImageData(0, 0, 1,1); - imgTest.data[0] = 123; - imgTest.data[3] = 255; - Canvas.ctx.putImageData(imgTest, 0, 0); - tval = Canvas.ctx.getImageData(0, 0, 1, 1).data[0]; - if (tval === 123) { - Canvas.has_imageData = true; - } - } catch (exc) {} - - if (Canvas.has_imageData) { - Util.Info("Canvas supports imageData"); - Canvas.force_canvas = false; - if (Canvas.ctx.createImageData) { - // If it's there, it's faster - Util.Info("Using Canvas createImageData"); - Canvas._imageData = Canvas._imageDataCreate; - } else if (Canvas.ctx.getImageData) { - Util.Info("Using Canvas getImageData"); - Canvas._imageData = Canvas._imageDataGet; - } - Util.Info("Prefering javascript operations"); - Canvas.prefer_js = true; - Canvas._rgbxImage = Canvas._rgbxImageData; - Canvas._cmapImage = Canvas._cmapImageData; - } else { - Util.Warn("Canvas lacks imageData, using fillRect (slow)"); - Canvas.force_canvas = true; - Canvas.prefer_js = false; - Canvas._rgbxImage = Canvas._rgbxImageFill; - 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.changeCursor(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; +// +// Public API interface functions +// - Util.Debug("<< Canvas.init"); - return true; -}, - +that.getContext = function () { + return conf.ctx; +}; -start: function (keyPress, mouseButton, mouseMove) { +that.start = function(keyPressFunc, mouseButtonFunc, mouseMoveFunc) { var c; Util.Debug(">> Canvas.start"); - c = $(Canvas.id); - Canvas.keyPress = keyPress || null; - Canvas.mouseButton = mouseButton || null; - Canvas.mouseMove = mouseMove || null; + c = conf.target; + c_keyPress = keyPressFunc || null; + c_mouseButton = mouseButtonFunc || null; + c_mouseMove = mouseMoveFunc || null; - Util.addEvent(document, 'keydown', Canvas.onKeyDown); - Util.addEvent(document, 'keyup', Canvas.onKeyUp); - Util.addEvent(c, 'mousedown', Canvas.onMouseDown); - Util.addEvent(c, 'mouseup', Canvas.onMouseUp); - Util.addEvent(c, 'mousemove', Canvas.onMouseMove); + Util.addEvent(document, 'keydown', onKeyDown); + Util.addEvent(document, 'keyup', onKeyUp); + Util.addEvent(c, 'mousedown', onMouseDown); + Util.addEvent(c, 'mouseup', onMouseUp); + Util.addEvent(c, 'mousemove', onMouseMove); Util.addEvent(c, (Util.Engine.gecko) ? 'DOMMouseScroll' : 'mousewheel', - Canvas.onMouseWheel); + onMouseWheel); /* Work around right and middle click browser behaviors */ - Util.addEvent(document, 'click', Canvas.onMouseDisable); - Util.addEvent(document.body, 'contextmenu', Canvas.onMouseDisable); + Util.addEvent(document, 'click', onMouseDisable); + Util.addEvent(document.body, 'contextmenu', onMouseDisable); Util.Debug("<< Canvas.start"); -}, - -clear: function () { - Canvas.resize(RFB.defaut_canvas_w, RFB.defaut_canvas_h); - Canvas.ctx.clearRect(0, 0, Canvas.c_wx, Canvas.c_wy); -}, - -resize: function (width, height, true_color) { - var c = $(Canvas.id); - - if (typeof true_color !== "undefined") { - Canvas.true_color = true_color; - } - - c.width = width; - c.height = height; - - Canvas.c_wx = c.offsetWidth; - Canvas.c_wy = c.offsetHeight; - - Canvas.rescale(Canvas.scale); -}, +}; -rescale: function (factor) { - var c, tp, x, y, - properties = ['transform', 'WebkitTransform', 'MozTransform', 'oTransform', null]; - origin = ['transformOrigin', 'WebkitTransformOrigin', 'MozTransformOrigin', 'oTransformOrigin', null]; - - c = $(Canvas.id); - x = c.width - c.width * factor; - y = c.height - c.height * factor; - Canvas.scale = factor; - - if (typeof(c.style.zoom) != "undefined") { - c.style.zoom = Canvas.scale; - return - } - - while (tp = properties.shift()) { - if (typeof c.style[tp] != 'undefined') { - break; - } - } - - while (tpo = origin.shift()) { - if (typeof c.style[tpo] != 'undefined') { +that.rescale = function(factor) { + var c, tp, x, y, + properties = ['transform', 'WebkitTransform', 'MozTransform', null]; + c = conf.target; + tp = properties.shift(); + while (tp) { + if (typeof c.style[tp] !== 'undefined') { break; } + tp = properties.shift(); } if (tp === null) { @@ -293,34 +418,83 @@ rescale: function (factor) { return; } - if (Canvas.scale === factor) { - Util.Debug("Canvas already scaled to '" + factor + "'"); + if (conf.scale === factor) { + //Util.Debug("Canvas already scaled to '" + factor + "'"); + return; } - - c.style[tpo] = "top left"; - c.style[tp] = "scale(" + Canvas.scale + ")"; -}, -stop: function () { - var c = $(Canvas.id); - - Util.removeEvent(document, 'keydown', Canvas.onKeyDown); - Util.removeEvent(document, 'keyup', Canvas.onKeyUp); - Util.removeEvent(c, 'mousedown', Canvas.onMouseDown); - Util.removeEvent(c, 'mouseup', Canvas.onMouseUp); - Util.removeEvent(c, 'mousemove', Canvas.onMouseMove); + conf.scale = factor; + x = c.width - c.width * factor; + y = c.height - c.height * 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.width = width; + c.height = height; + + c_width = c.offsetWidth; + c_height = c.offsetHeight; + + that.rescale(conf.scale); +}; + +that.clear = function() { + that.resize(640, 20); + conf.ctx.clearRect(0, 0, c_width, c_height); +}; + +that.stop = function() { + var c = conf.target; + Util.removeEvent(document, 'keydown', onKeyDown); + Util.removeEvent(document, 'keyup', onKeyUp); + Util.removeEvent(c, 'mousedown', onMouseDown); + Util.removeEvent(c, 'mouseup', onMouseUp); + Util.removeEvent(c, 'mousemove', onMouseMove); Util.removeEvent(c, (Util.Engine.gecko) ? 'DOMMouseScroll' : 'mousewheel', - Canvas.onMouseWheel); + onMouseWheel); /* Work around right and middle click browser behaviors */ - Util.removeEvent(document, 'click', Canvas.onMouseDisable); - Util.removeEvent(document.body, 'contextmenu', Canvas.onMouseDisable); + Util.removeEvent(document, 'click', onMouseDisable); + Util.removeEvent(document.body, 'contextmenu', onMouseDisable); // Turn off cursor rendering - if (Canvas.cursor_uri) { + if (conf.cursor_uri) { c.style.cursor = "default"; } -}, +}; + +setFillColor = function(color) { + var rgb, newStyle; + if (conf.true_color) { + rgb = color; + } else { + rgb = conf.colourMap[color[0]]; + } + if (newStyle !== c_prevStyle) { + newStyle = "rgb(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + ")"; + conf.ctx.fillStyle = newStyle; + c_prevStyle = newStyle; + } +}; +that.setFillColor = setFillColor; + +fillRect = function(x, y, width, height, color) { + setFillColor(color); + conf.ctx.fillRect(x, y, width, height); +}; +that.fillRect = fillRect; + +that.copyImage = function(old_x, old_y, new_x, new_y, width, height) { + conf.ctx.drawImage(conf.target, old_x, old_y, width, height, + new_x, new_y, width, height); +}; /* * Tile rendering functions optimized for rendering engines. @@ -329,16 +503,16 @@ stop: function () { * faster than direct Canvas fillStyle, fillRect rendering. In * gecko, Javascript array handling is much slower. */ -getTile: function(x, y, width, height, color) { +that.getTile = function(x, y, width, height, color) { var img, data, p, rgb, red, green, blue, j, i; img = {'x': x, 'y': y, 'width': width, 'height': height, 'data': []}; - if (Canvas.prefer_js) { + if (conf.prefer_js) { data = img.data; - if (Canvas.true_color) { + if (conf.true_color) { rgb = color; } else { - rgb = Canvas.colourMap[color[0]]; + rgb = conf.colourMap[color[0]]; } red = rgb[0]; green = rgb[1]; @@ -353,20 +527,20 @@ getTile: function(x, y, width, height, color) { } } } else { - Canvas.fillRect(x, y, width, height, color); + fillRect(x, y, width, height, color); } return img; -}, +}; -setSubTile: function(img, x, y, w, h, color) { +that.setSubTile = function(img, x, y, w, h, color) { var data, p, rgb, red, green, blue, width, j, i; - if (Canvas.prefer_js) { + if (conf.prefer_js) { data = img.data; width = img.width; - if (Canvas.true_color) { + if (conf.true_color) { rgb = color; } else { - rgb = Canvas.colourMap[color[0]]; + rgb = conf.colourMap[color[0]]; } red = rgb[0]; green = rgb[1]; @@ -381,31 +555,28 @@ setSubTile: function(img, x, y, w, h, color) { } } } else { - Canvas.fillRect(img.x + x, img.y + y, w, h, color); + fillRect(img.x + x, img.y + y, w, h, color); } -}, +}; -putTile: function(img) { - if (Canvas.prefer_js) { - Canvas._rgbxImage(img.x, img.y, img.width, img.height, img.data, 0); +that.putTile = function(img) { + if (conf.prefer_js) { + that.rgbxImage(img.x, img.y, img.width, img.height, img.data, 0); } else { // No-op, under gecko already done by setSubTile } -}, +}; -_imageDataGet: function(width, height) { - return Canvas.ctx.getImageData(0, 0, width, height); -}, -_imageDataCreate: function(width, height) { - return Canvas.ctx.createImageData(width, height); -}, -_imageDataRaw: function(width, height) { - return {'data': [], 'width': width, 'height': height}; -}, +that.imageDataGet = function(width, height) { + return conf.ctx.getImageData(0, 0, width, height); +}; +that.imageDataCreate = function(width, height) { + return conf.ctx.createImageData(width, height); +}; -_rgbxImageData: function(x, y, width, height, arr, offset) { +that.rgbxImageData = function(x, y, width, height, arr, offset) { var img, i, j, data; - img = Canvas._imageData(width, height); + img = that.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]; @@ -413,27 +584,27 @@ _rgbxImageData: function(x, y, width, height, arr, offset) { data[i + 2] = arr[j + 2]; data[i + 3] = 255; // Set Alpha } - Canvas.ctx.putImageData(img, x, y); -}, + conf.ctx.putImageData(img, x, y); +}; // really slow fallback if we don't have imageData -_rgbxImageFill: function(x, y, width, height, arr, offset) { +that.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) { - Canvas.fillRect(x+sx, y+sy, 1, 1, [arr[j+0], arr[j+1], arr[j+2]]); + fillRect(x+sx, y+sy, 1, 1, [arr[j+0], arr[j+1], arr[j+2]]); sx += 1; if ((sx % width) === 0) { sx = 0; sy += 1; } } -}, +}; -_cmapImageData: function(x, y, width, height, arr, offset) { +that.cmapImageData = function(x, y, width, height, arr, offset) { var img, i, j, data, rgb, cmap; - img = Canvas._imageData(width, height); + img = that.imageData(width, height); data = img.data; - cmap = Canvas.colourMap; + 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]; @@ -441,168 +612,47 @@ _cmapImageData: function(x, y, width, height, arr, offset) { data[i + 2] = rgb[2]; data[i + 3] = 255; // Set Alpha } - Canvas.ctx.putImageData(img, x, y); -}, + conf.ctx.putImageData(img, x, y); +}; -_cmapImageFill: function(x, y, width, height, arr, offset) { +that.cmapImageFill = function(x, y, width, height, arr, offset) { var i, j, sx = 0, sy = 0, cmap; - cmap = Canvas.colourMap; + cmap = conf.colourMap; for (i=0, j=offset; i < (width * height); i+=1, j+=1) { - Canvas.fillRect(x+sx, y+sy, 1, 1, [arr[j]]); + fillRect(x+sx, y+sy, 1, 1, [arr[j]]); sx += 1; if ((sx % width) === 0) { sx = 0; sy += 1; } } -}, +}; -blitImage: function(x, y, width, height, arr, offset) { - if (Canvas.true_color) { - Canvas._rgbxImage(x, y, width, height, arr, offset); +that.blitImage = function(x, y, width, height, arr, offset) { + if (conf.true_color) { + that.rgbxImage(x, y, width, height, arr, offset); } else { - Canvas._cmapImage(x, y, width, height, arr, offset); + that.cmapImage(x, y, width, height, arr, offset); } -}, +}; -blitStringImage: function(str, x, y) { +that.blitStringImage = function(str, x, y) { var img = new Image(); - img.onload = function () { Canvas.ctx.drawImage(img, x, y); }; + img.onload = function () { conf.ctx.drawImage(img, x, y); }; img.src = str; -}, - -setFillColor: function(color) { - var rgb, newStyle; - if (Canvas.true_color) { - rgb = color; - } else { - rgb = Canvas.colourMap[color[0]]; - } - if (newStyle !== Canvas.prevStyle) { - newStyle = "rgb(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + ")"; - Canvas.ctx.fillStyle = newStyle; - Canvas.prevStyle = newStyle; - } -}, - -fillRect: function(x, y, width, height, color) { - Canvas.setFillColor(color); - Canvas.ctx.fillRect(x, y, width, height); -}, - -copyImage: function(old_x, old_y, new_x, new_y, width, height) { - Canvas.ctx.drawImage($(Canvas.id), old_x, old_y, width, height, - new_x, new_y, width, height); -}, - -/* Translate DOM key event to keysym value */ -getKeysym: function(e) { - var evt, keysym; - evt = (e ? e : window.event); - - /* Remap modifier and special keys */ - switch ( evt.keyCode ) { - case 8 : keysym = 0xFF08; break; // BACKSPACE - case 9 : keysym = 0xFF09; break; // TAB - case 13 : keysym = 0xFF0D; break; // ENTER - case 27 : keysym = 0xFF1B; break; // ESCAPE - case 45 : keysym = 0xFF63; break; // INSERT - case 46 : keysym = 0xFFFF; break; // DELETE - case 36 : keysym = 0xFF50; break; // HOME - case 35 : keysym = 0xFF57; break; // END - case 33 : keysym = 0xFF55; break; // PAGE_UP - case 34 : keysym = 0xFF56; break; // PAGE_DOWN - case 37 : keysym = 0xFF51; break; // LEFT - case 38 : keysym = 0xFF52; break; // UP - case 39 : keysym = 0xFF53; break; // RIGHT - case 40 : keysym = 0xFF54; break; // DOWN - case 112 : keysym = 0xFFBE; break; // F1 - case 113 : keysym = 0xFFBF; break; // F2 - case 114 : keysym = 0xFFC0; break; // F3 - case 115 : keysym = 0xFFC1; break; // F4 - case 116 : keysym = 0xFFC2; break; // F5 - case 117 : keysym = 0xFFC3; break; // F6 - case 118 : keysym = 0xFFC4; break; // F7 - case 119 : keysym = 0xFFC5; break; // F8 - case 120 : keysym = 0xFFC6; break; // F9 - case 121 : keysym = 0xFFC7; break; // F10 - case 122 : keysym = 0xFFC8; break; // F11 - case 123 : keysym = 0xFFC9; break; // F12 - case 16 : keysym = 0xFFE1; break; // SHIFT - case 17 : keysym = 0xFFE3; break; // CONTROL - //case 18 : keysym = 0xFFE7; break; // Left Meta (Mac Option) - case 18 : keysym = 0xFFE9; break; // Left ALT (Mac Command) - default : keysym = evt.keyCode; break; - } - - /* Remap symbols */ - switch (keysym) { - case 186 : keysym = 59; break; // ; (IE) - case 187 : keysym = 61; break; // = (IE) - case 188 : keysym = 44; break; // , (Mozilla, IE) - case 109 : // - (Mozilla) - if (Util.Engine.gecko) { - keysym = 45; } - break; - case 189 : keysym = 45; break; // - (IE) - case 190 : keysym = 46; break; // . (Mozilla, IE) - case 191 : keysym = 47; break; // / (Mozilla, IE) - case 192 : keysym = 96; break; // ` (Mozilla, IE) - case 219 : keysym = 91; break; // [ (Mozilla, IE) - case 220 : keysym = 92; break; // \ (Mozilla, IE) - case 221 : keysym = 93; break; // ] (Mozilla, IE) - case 222 : keysym = 39; break; // ' (Mozilla, IE) - } - - /* Remap shifted and unshifted keys */ - if (!!evt.shiftKey) { - switch (keysym) { - case 48 : keysym = 41 ; break; // ) (shifted 0) - case 49 : keysym = 33 ; break; // ! (shifted 1) - case 50 : keysym = 64 ; break; // @ (shifted 2) - case 51 : keysym = 35 ; break; // # (shifted 3) - case 52 : keysym = 36 ; break; // $ (shifted 4) - case 53 : keysym = 37 ; break; // % (shifted 5) - case 54 : keysym = 94 ; break; // ^ (shifted 6) - case 55 : keysym = 38 ; break; // & (shifted 7) - case 56 : keysym = 42 ; break; // * (shifted 8) - case 57 : keysym = 40 ; break; // ( (shifted 9) - - case 59 : keysym = 58 ; break; // : (shifted `) - case 61 : keysym = 43 ; break; // + (shifted ;) - case 44 : keysym = 60 ; break; // < (shifted ,) - case 45 : keysym = 95 ; break; // _ (shifted -) - case 46 : keysym = 62 ; break; // > (shifted .) - case 47 : keysym = 63 ; break; // ? (shifted /) - case 96 : keysym = 126; break; // ~ (shifted `) - case 91 : keysym = 123; break; // { (shifted [) - case 92 : keysym = 124; break; // | (shifted \) - case 93 : keysym = 125; break; // } (shifted ]) - case 39 : keysym = 34 ; break; // " (shifted ') - } - } else if ((keysym >= 65) && (keysym <=90)) { - /* Remap unshifted A-Z */ - keysym += 32; - } - - return keysym; -}, - +}; -isCursor: function() { - return Canvas.cursor_uri; -}, -changeCursor: function(pixels, mask, hotx, hoty, w, h) { +that.changeCursor = function(pixels, mask, hotx, hoty, w, h) { var cur = [], cmap, rgb, IHDRsz, ANDsz, XORsz, url, idx, alpha, x, y; //Util.Debug(">> changeCursor, x: " + hotx + ", y: " + hoty + ", w: " + w + ", h: " + h); - if (!Canvas.cursor_uri) { + if (conf.cursor_uri === false) { Util.Warn("changeCursor called but no cursor data URI support"); return; } - cmap = Canvas.colourMap; + cmap = conf.colourMap; IHDRsz = 40; ANDsz = w * h * 4; XORsz = Math.ceil( (w * h) / 8.0 ); @@ -636,12 +686,12 @@ changeCursor: function(pixels, mask, hotx, hoty, w, h) { cur.push32le(0); // XOR/color data - for (y = h-1; y >= 0; y--) { - for (x = 0; x < w; x++) { + for (y = h-1; y >= 0; y -= 1) { + for (x = 0; x < w; x += 1) { idx = y * Math.ceil(w / 8) + Math.floor(x/8); alpha = (mask[idx] << (x % 8)) & 0x80 ? 255 : 0; - if (Canvas.true_color) { + if (conf.true_color) { idx = ((w * y) + x) * 4; cur.push(pixels[idx + 2]); // blue cur.push(pixels[idx + 1]); // green @@ -659,16 +709,20 @@ changeCursor: function(pixels, mask, hotx, hoty, w, h) { } // AND/bitmask data (ignored, just needs to be right size) - for (y = 0; y < h; y++) { - for (x = 0; x < Math.ceil(w / 8); x++) { + for (y = 0; y < h; y += 1) { + for (x = 0; x < Math.ceil(w / 8); x += 1) { cur.push(0x00); } } url = "data:image/x-icon;base64," + Base64.encode(cur); - $(Canvas.id).style.cursor = "url(" + url + ") " + hotx + " " + hoty + ", default"; + conf.target.style.cursor = "url(" + url + ") " + hotx + " " + hoty + ", default"; //Util.Debug("<< changeCursor, cur.length: " + cur.length); -} - }; + + +return constructor(); // Return the public API interface + +} // End of Canvas() + diff --git a/include/default_controls.js b/include/default_controls.js index dd2e97d..3be50ed 100644 --- a/include/default_controls.js +++ b/include/default_controls.js @@ -6,6 +6,7 @@ * See README.md for usage and integration instructions. */ "use strict"; +/*jslint white: false */ /*global $, Util, RFB, Canvas, VNC_uri_prefix, Element, Fx */ var DefaultControls = { @@ -16,10 +17,6 @@ settingsOpen : false, load: function(target) { var html, i, DC = DefaultControls, sheet, sheets, llevels; - /* Handle state updates */ - RFB.setUpdateState(DC.updateState); - RFB.setClipboardReceive(DC.clipReceive); - /* Populate the 'target' DOM element with default controls */ if (!target) { target = 'vnc'; } @@ -61,7 +58,7 @@ load: function(target) { html += ' <option value="default">default</option>'; sheet = Util.selectStylesheet(); sheets = Util.getStylesheets(); - for (i = 0; i < sheets.length; i++) { + for (i = 0; i < sheets.length; i += 1) { html += '<option value="' + sheets[i].title + '">' + sheets[i].title + '</option>'; } html += ' </select> Style</li>'; @@ -69,7 +66,7 @@ load: function(target) { // Logging selection dropdown html += ' <li><select id="VNC_logging" name="vncLogging">'; llevels = ['error', 'warn', 'info', 'debug']; - for (i = 0; i < llevels.length; i++) { + for (i = 0; i < llevels.length; i += 1) { html += '<option value="' + llevels[i] + '">' + llevels[i] + '</option>'; } html += ' </select> Logging</li>'; @@ -107,6 +104,8 @@ load: function(target) { DC.initSetting('logging', 'warn'); Util.init_logging(DC.getSetting('logging')); DC.initSetting('stylesheet', 'default'); + + Util.selectStylesheet(null); // call twice to get around webkit bug Util.selectStylesheet(DC.getSetting('stylesheet')); /* Populate the controls if defaults are provided in the URL */ @@ -118,12 +117,19 @@ load: function(target) { DC.initSetting('true_color', true); DC.initSetting('cursor', true); + DC.rfb = RFB({'target': 'VNC_canvas', + 'updateState': DC.updateState, + 'clipboardReceive': DC.clipReceive}); + DC.rfb.init(); + + // Unfocus clipboard when over the VNC area $('VNC_screen').onmousemove = function () { - // Unfocus clipboard when over the VNC area - if (! Canvas.focused) { + var canvas = DC.rfb.get_canvas(); + if ((! canvas) || (! canvas.get_focused())) { $('VNC_clipboard_text').blur(); } }; + }, // Read form control compatible setting from cookie @@ -154,7 +160,7 @@ updateSetting: function(name, value) { if (ctrl.type === 'checkbox') { ctrl.checked = value; } else if (typeof ctrl.options !== 'undefined') { - for (i = 0; i < ctrl.options.length; i++) { + for (i = 0; i < ctrl.options.length; i += 1) { if (ctrl.options[i].value === value) { ctrl.selectedIndex = i; break; @@ -176,7 +182,7 @@ saveSetting: function(name) { val = ctrl.value; } Util.createCookie(name, val); - Util.Debug("Setting saved '" + name + "=" + val + "'"); + //Util.Debug("Setting saved '" + name + "=" + val + "'"); return val; }, @@ -190,7 +196,7 @@ initSetting: function(name, defVal) { val = Util.readCookie(name, defVal); } DefaultControls.updateSetting(name, val); - Util.Debug("Setting '" + name + "' initialized to '" + val + "'"); + //Util.Debug("Setting '" + name + "' initialized to '" + val + "'"); return val; }, @@ -208,7 +214,7 @@ clickSettingsMenu: function() { DC.updateSetting('encrypt'); DC.updateSetting('base64'); DC.updateSetting('true_color'); - if (Canvas.isCursor()) { + if (DC.rfb.get_canvas().get_cursor_uri()) { DC.updateSetting('cursor'); } else { DC.updateSetting('cursor', false); @@ -235,10 +241,11 @@ closeSettingsMenu: function() { // Disable/enable controls depending on connection state settingsDisabled: function(disabled) { + var DC = DefaultControls; $('VNC_encrypt').disabled = disabled; $('VNC_base64').disabled = disabled; $('VNC_true_color').disabled = disabled; - if (Canvas.isCursor()) { + if (DC.rfb && DC.rfb.get_canvas().get_cursor_uri()) { $('VNC_cursor').disabled = disabled; } else { DefaultControls.updateSetting('cursor', false); @@ -248,12 +255,12 @@ settingsDisabled: function(disabled) { // Save/apply settings when 'Apply' button is pressed settingsApply: function() { - Util.Debug(">> settingsApply"); + //Util.Debug(">> settingsApply"); var DC = DefaultControls; DC.saveSetting('encrypt'); DC.saveSetting('base64'); DC.saveSetting('true_color'); - if (Canvas.isCursor()) { + if (DC.rfb.get_canvas().get_cursor_uri()) { DC.saveSetting('cursor'); } DC.saveSetting('stylesheet'); @@ -263,21 +270,21 @@ settingsApply: function() { Util.selectStylesheet(DC.getSetting('stylesheet')); Util.init_logging(DC.getSetting('logging')); - Util.Debug("<< settingsApply"); + //Util.Debug("<< settingsApply"); }, setPassword: function() { - RFB.sendPassword($('VNC_password').value); + DefaultControls.rfb.sendPassword($('VNC_password').value); return false; }, sendCtrlAltDel: function() { - RFB.sendCtrlAltDel(); + DefaultControls.rfb.sendCtrlAltDel(); }, -updateState: function(state, msg) { +updateState: function(rfb, state, oldstate, msg) { var s, sb, c, cad, klass; s = $('VNC_status'); sb = $('VNC_status_bar'); @@ -334,6 +341,13 @@ updateState: function(state, msg) { }, +clipReceive: function(rfb, text) { + Util.Debug(">> DefaultControls.clipReceive: " + text.substr(0,40) + "..."); + $('VNC_clipboard_text').value = text; + Util.Debug("<< DefaultControls.clipReceive"); +}, + + connect: function() { var host, port, password, DC = DefaultControls; @@ -346,43 +360,37 @@ connect: function() { throw("Must set host and port"); } - RFB.setEncrypt(DC.getSetting('encrypt')); - RFB.setBase64(DC.getSetting('base64')); - RFB.setTrueColor(DC.getSetting('true_color')); - RFB.setCursor(DC.getSetting('cursor')); + DC.rfb.set_encrypt(DC.getSetting('encrypt')); + DC.rfb.set_b64encode(DC.getSetting('base64')); + DC.rfb.set_true_color(DC.getSetting('true_color')); + DC.rfb.set_local_cursor(DC.getSetting('cursor')); - RFB.connect(host, port, password); + DC.rfb.connect(host, port, password); }, disconnect: function() { DefaultControls.closeSettingsMenu(); - RFB.disconnect(); + DefaultControls.rfb.disconnect(); }, canvasBlur: function() { - Canvas.focused = false; + DefaultControls.rfb.get_canvas().set_focused(false); }, canvasFocus: function() { - Canvas.focused = true; + DefaultControls.rfb.get_canvas().set_focused(true); }, clipClear: function() { $('VNC_clipboard_text').value = ""; - RFB.clipboardPasteFrom(""); -}, - -clipReceive: function(text) { - Util.Debug(">> DefaultControls.clipReceive: " + text.substr(0,40) + "..."); - $('VNC_clipboard_text').value = text; - Util.Debug("<< DefaultControls.clipReceive"); + DefaultControls.rfb.clipboardPasteFrom(""); }, clipSend: function() { var text = $('VNC_clipboard_text').value; Util.Debug(">> DefaultControls.clipSend: " + text.substr(0,40) + "..."); - RFB.clipboardPasteFrom(text); + DefaultControls.rfb.clipboardPasteFrom(text); Util.Debug("<< DefaultControls.clipSend"); } diff --git a/include/rfb.js b/include/rfb.js index 9066539..766f6eb 100644 --- a/include/rfb.js +++ b/include/rfb.js @@ -6,363 +6,744 @@ * See README.md for usage and integration instructions. */ -//"use strict"; -/*jslint white: false, nomen: false, browser: true, bitwise: false */ +"use strict"; +/*jslint white: false, browser: true, bitwise: false */ /*global window, WebSocket, Util, Canvas, VNC_native_ws, Base64, DES */ -// Globals defined here -//var RFB; + +function RFB(conf) { + +conf = conf || {}; // Configuration +var that = {}, // Public API interface + + // Pre-declare private functions used before definitions (jslint) + init_vars, updateState, init_msg, normal_msg, recv_message, + framebufferUpdate, show_timings, + + pixelFormat, clientEncodings, fbUpdateRequest, + keyEvent, pointerEvent, clientCutText, + + extract_data_uri, scan_tight_imgs, + + send_array, checkEvents, // Overridable for testing + + + // + // Private RFB namespace variables + // + rfb_host = '', + rfb_port = 5900, + rfb_password = '', + + rfb_state = 'disconnected', + rfb_version = 0, + rfb_max_version= 3.8, + rfb_auth_scheme= '', + rfb_shared = 1, + + + // In preference order + encodings = [ + ['COPYRECT', 0x01 ], + ['TIGHT_PNG', -260 ], + ['HEXTILE', 0x05 ], + ['RRE', 0x02 ], + ['RAW', 0x00 ], + ['DesktopSize', -223 ], + ['Cursor', -239 ], + + // Psuedo-encoding settings + ['JPEG_quality_lo', -32 ], + //['JPEG_quality_hi', -23 ], + ['compress_lo', -255 ] + //['compress_hi', -247 ] + ], + + encHandlers = {}, + encNames = {}, + + ws = null, // Web Socket object + canvas = null, // Canvas object + sendID = null, // Send Queue check timer + + // Receive and send queues + RQ = [], // Receive Queue + SQ = "", // Send Queue + + // Frame buffer update state + FBU = { + rects : 0, + subrects : 0, // RRE and HEXTILE + lines : 0, // RAW + tiles : 0, // HEXTILE + bytes : 0, + x : 0, + y : 0, + width : 0, + height : 0, + encoding : 0, + subencoding : -1, + background : null, + imgs : [] // TIGHT_PNG image queue + }, + + fb_Bpp = 4, + fb_depth = 3, + fb_width = 0, + fb_height = 0, + fb_name = "", + + cuttext = 'none', // ServerCutText wait state + cuttext_length = 0, + + scan_imgs_rate = 100, + last_req_time = 0, + rre_chunk_sz = 100, + + timing = { + last_fbu : 0, + fbu_total : 0, + fbu_total_cnt : 0, + full_fbu_total : 0, + full_fbu_cnt : 0, + + fbu_rt_start : 0, + fbu_rt_total : 0, + fbu_rt_cnt : 0, + + history : [], + history_start : 0, + h_time : 0, + h_rects : 0, + h_fbus : 0, + h_bytes : 0, + h_pixels : 0 + }, + + test_mode = false, + + /* Mouse state */ + mouse_buttonMask = 0, + mouse_arr = []; + + +// +// Configuration settings +// + +// VNC viewport rendering Canvas +Util.conf_default(conf, that, 'target', 'VNC_canvas'); + +Util.conf_default(conf, that, 'encrypt', false, true); +Util.conf_default(conf, that, 'true_color', true, true); +// false means UTF-8 on the wire +Util.conf_default(conf, that, 'b64encode', true, true); +Util.conf_default(conf, that, 'local_cursor', true, true); + +// time to wait for connection +Util.conf_default(conf, that, 'connectTimeout', 2000); +// frequency to check for send/receive +Util.conf_default(conf, that, 'check_rate', 217); +// frequency to send frameBufferUpdate requests +Util.conf_default(conf, that, 'fbu_req_rate', 1413); + +// state update callback +Util.conf_default(conf, that, 'updateState', function () { + Util.Debug(">> externalUpdateState stub"); }); +// clipboard contents received callback +Util.conf_default(conf, that, 'clipboardReceive', function () { + Util.Debug(">> clipboardReceive stub"); }); + + +// Override/add some specific getters/setters +that.set_local_cursor = function(cursor) { + if ((!cursor) || (cursor in {'0':1, 'no':1, 'false':1})) { + conf.local_cursor = false; + } else { + if (canvas.get_cursor_uri()) { + conf.local_cursor = true; + } else { + Util.Warn("Browser does not support local cursor"); + } + } +}; + +that.get_canvas = function() { + return canvas; +}; + + + + +// +// Private functions +// + +// +// Setup routines +// + +// Create the public API interface +function constructor() { + var i; + //Util.Debug(">> init"); + + // Create lookup tables based encoding number + for (i=0; i < encodings.length; i+=1) { + encHandlers[encodings[i][1]] = encHandlers[encodings[i][0]]; + encNames[encodings[i][1]] = encodings[i][0]; + } + // Initialize canvas + try { + canvas = new Canvas({'target': conf.target}); + } catch (exc) { + Util.Error("Canvas exception: " + exc); + updateState('fatal', "No working Canvas"); + } + + //Util.Debug("<< init"); + return that; // Return the public API interface +} + +function init_ws() { + //Util.Debug(">> init_ws"); + + var uri = "", vars = []; + if (conf.encrypt) { + uri = "wss://"; + } else { + uri = "ws://"; + } + uri += rfb_host + ":" + rfb_port + "/"; + if (conf.b64encode) { + vars.push("b64encode"); + } + if (vars.length > 0) { + uri += "?" + vars.join("&"); + } + Util.Info("connecting to " + uri); + ws = new WebSocket(uri); + + ws.onmessage = recv_message; + ws.onopen = function(e) { + Util.Debug(">> WebSocket.onopen"); + if (rfb_state === "connect") { + updateState('ProtocolVersion', "Starting VNC handshake"); + } else { + updateState('failed', "Got unexpected WebSockets connection"); + } + Util.Debug("<< WebSocket.onopen"); + }; + ws.onclose = function(e) { + Util.Debug(">> WebSocket.onclose"); + if (rfb_state === 'normal') { + updateState('failed', 'Server disconnected'); + } else if (rfb_state === 'ProtocolVersion') { + updateState('failed', 'Failed to connect to server'); + } else { + updateState('disconnected', 'VNC disconnected'); + } + Util.Debug("<< WebSocket.onclose"); + }; + ws.onerror = function(e) { + Util.Debug(">> WebSocket.onerror"); + updateState('failed', "WebSocket error"); + Util.Debug("<< WebSocket.onerror"); + }; + + setTimeout(function () { + if (ws.readyState === WebSocket.CONNECTING) { + updateState('failed', "Connect timeout"); + } + }, conf.connectTimeout); + + //Util.Debug("<< init_ws"); +} + +init_vars = function() { + /* Reset state */ + cuttext = 'none'; + cuttext_length = 0; + RQ = []; + SQ = ""; + FBU.rects = 0; + FBU.subrects = 0; // RRE and HEXTILE + FBU.lines = 0; // RAW + FBU.tiles = 0; // HEXTILE + FBU.imgs = []; // TIGHT_PNG image queue + mouse_buttonMask = 0; + mouse_arr = []; + + timing.history_start = 0; + timing.history = []; + timing.h_fbus = 0; + timing.h_rects = 0; + timing.h_bytes = 0; + timing.h_pixels = 0; +}; + +// +// Utility routines +// + /* - * RFB namespace + * Running states: + * disconnected - idle state + * normal - connected + * + * Page states: + * loaded - page load, equivalent to disconnected + * connect - starting initialization + * password - waiting for password + * failed - abnormal transition to disconnected + * fatal - failed to load page, or fatal error + * + * VNC initialization states: + * ProtocolVersion + * Security + * Authentication + * SecurityResult + * ServerInitialization */ +updateState = function(state, statusMsg) { + var func, cmsg, oldstate = rfb_state; + if (state === oldstate) { + /* Already here, ignore */ + Util.Debug("Already in state '" + state + "', ignoring."); + return; + } -RFB = { + if (oldstate === 'fatal') { + Util.Error("Fatal error, cannot continue"); + } -/* - * External interface variables and methods - */ -host : '', -port : 5900, -password : '', - -encrypt : true, -true_color : false, -b64encode : true, // false means UTF-8 on the wire -local_cursor : true, -connectTimeout : 2000, // time to wait for connection -stateinvalid : false, - -// In preference order -encodings : [ - ['COPYRECT', 0x01, 'display_copy_rect'], - ['TIGHT_PNG', -260, 'display_tight_png'], - ['HEXTILE', 0x05, 'display_hextile'], - ['RRE', 0x02, 'display_rre'], - ['RAW', 0x00, 'display_raw'], - ['DesktopSize', -223, 'set_desktopsize'], - ['Cursor', -239, 'set_cursor'], - - // Psuedo-encoding settings - ['JPEG_quality_lo', -32, 'set_jpeg_quality'], -// ['JPEG_quality_hi', -23, 'set_jpeg_quality'], - ['compress_lo', -255, 'set_compress_level'] -// ['compress_hi', -247, 'set_compress_level'] - ], - - -setUpdateState: function(externalUpdateState) { - RFB.externalUpdateState = externalUpdateState; -}, - -setClipboardReceive: function(clipReceive) { - RFB.clipboardCopyTo = clipReceive; -}, - -setCanvasID: function(canvasID) { - RFB.canvasID = canvasID; -}, - -setEncrypt: function(encrypt) { - if ((!encrypt) || (encrypt in {'0':1, 'no':1, 'false':1})) { - RFB.encrypt = false; + if ((state === 'failed') || (state === 'fatal')) { + func = Util.Error; + } else { + func = Util.Warn; + } + + cmsg = typeof(statusMsg) !== 'undefined' ? (" Msg: " + statusMsg) : ""; + func("New state '" + state + "', was '" + oldstate + "'." + cmsg); + + if ((oldstate === 'failed') && (state === 'disconnected')) { + // Do disconnect action, but stay in failed state. + rfb_state = 'failed'; } else { - RFB.encrypt = true; + rfb_state = state; + } + + switch (state) { + case 'loaded': + case 'disconnected': + + if (sendID) { + clearInterval(sendID); + sendID = null; + } + + if (ws) { + if (ws.readyState === WebSocket.OPEN) { + ws.close(); + } + ws.onmessage = function (e) { return; }; + } + + if (canvas && canvas.getContext()) { + canvas.stop(); + if (! /__debug__$/i.test(document.location.href)) { + canvas.clear(); + } + } + + show_timings(); + + break; + + + case 'connect': + init_vars(); + + if ((ws) && (ws.readyState === WebSocket.OPEN)) { + ws.close(); + } + init_ws(); // onopen transitions to 'ProtocolVersion' + + break; + + + case 'password': + // Ignore password state by default + break; + + + case 'normal': + if ((oldstate === 'disconnected') || (oldstate === 'failed')) { + Util.Error("Invalid transition from 'disconnected' or 'failed' to 'normal'"); + } + + break; + + + case 'failed': + if (oldstate === 'disconnected') { + Util.Error("Invalid transition from 'disconnected' to 'failed'"); + } + if (oldstate === 'normal') { + Util.Error("Error while connected."); + } + if (oldstate === 'init') { + Util.Error("Error while initializing."); + } + + if ((ws) && (ws.readyState === WebSocket.OPEN)) { + ws.close(); + } + // Make sure we transition to disconnected + setTimeout(function() { updateState('disconnected'); }, 50); + + break; + + + default: + // Invalid state transition + } -}, -setBase64: function(b64) { - if ((!b64) || (b64 in {'0':1, 'no':1, 'false':1})) { - RFB.b64encode = false; + if ((oldstate === 'failed') && (state === 'disconnected')) { + // Leave the failed message + conf.updateState(that, state, oldstate); } else { - RFB.b64encode = true; + conf.updateState(that, state, oldstate, statusMsg); } - Util.Debug("Set b64encode to: " + RFB.b64encode); -}, +}; -setTrueColor: function(trueColor) { - if ((!trueColor) || (trueColor in {'0':1, 'no':1, 'false':1})) { - RFB.true_color = false; +function encode_message(arr) { + if (conf.b64encode) { + /* base64 encode */ + SQ = SQ + Base64.encode(arr); } else { - RFB.true_color = true; + /* UTF-8 encode. 0 -> 256 to avoid WebSockets framing */ + SQ = SQ + arr.map(function (num) { + if (num === 0) { + return String.fromCharCode(256); + } else { + return String.fromCharCode(num); + } + } ).join(''); } -}, +} -setCursor: function(cursor) { - if ((!cursor) || (cursor in {'0':1, 'no':1, 'false':1})) { - RFB.local_cursor = false; +function decode_message(data) { + var i, length; + //Util.Debug(">> decode_message: " + data); + if (conf.b64encode) { + /* base64 decode */ + RQ = RQ.concat(Base64.decode(data, 0)); } else { - if (Canvas.isCursor()) { - RFB.local_cursor = true; - } else { - Util.Warn("Browser does not support local cursor"); + /* UTF-8 decode. 256 -> 0 to WebSockets framing */ + length = data.length; + for (i=0; i < length; i += 1) { + RQ.push(data.charCodeAt(i) % 256); } } -}, + //Util.Debug(">> decode_message, RQ: " + RQ); +} -sendPassword: function(passwd) { - RFB.password = passwd; - RFB.state = "Authentication"; - setTimeout(RFB.init_msg, 1); -}, +function handle_message() { + //Util.Debug("RQ.slice(0,20): " + RQ.slice(0,20) + " (" + RQ.length + ")"); + if (RQ.length == 0) { + Util.Warn("handle_message called on empty receive queue"); + return; + } + switch (rfb_state) { + case 'disconnected': + Util.Error("Got data while disconnected"); + break; + case 'failed': + Util.Warn("Giving up!"); + that.disconnect(); + break; + case 'normal': + if (normal_msg() && RQ.length > 0) { + // true means we can continue processing + Util.Debug("More data to process"); + // Give other events a chance to run + setTimeout(handle_message, 10); + } + break; + default: + init_msg(); + break; + } +} -sendCtrlAltDel: function() { - if (RFB.state !== "normal") { return false; } - Util.Info("Sending Ctrl-Alt-Del"); - var arr = []; - arr = arr.concat(RFB.keyEvent(0xFFE3, 1)); // Control - arr = arr.concat(RFB.keyEvent(0xFFE9, 1)); // Alt - arr = arr.concat(RFB.keyEvent(0xFFFF, 1)); // Delete - arr = arr.concat(RFB.keyEvent(0xFFFF, 0)); // Delete - arr = arr.concat(RFB.keyEvent(0xFFE9, 0)); // Alt - arr = arr.concat(RFB.keyEvent(0xFFE3, 0)); // Control - arr = arr.concat(RFB.fbUpdateRequest(1)); - RFB.send_array(arr); -}, - -load: function () { - var i; - //Util.Debug(">> load"); +recv_message = function(e) { + //Util.Debug(">> recv_message"); - /* Load web-socket-js if no builtin WebSocket support */ - if (VNC_native_ws) { - Util.Info("Using native WebSockets"); - RFB.updateState('loaded', 'noVNC ready (using native WebSockets)'); - } else { - Util.Warn("Using web-socket-js flash bridge"); - if ((! Util.Flash) || - (Util.Flash.version < 9)) { - RFB.updateState('fatal', "WebSockets or Adobe Flash is required"); - } else if (document.location.href.substr(0, 7) === "file://") { - RFB.updateState('fatal', - "'file://' URL is incompatible with Adobe Flash"); + try { + decode_message(e.data); + if (RQ.length > 0) { + handle_message(); + } else { + Util.Debug("Ignoring empty message"); + } + } catch (exc) { + if (typeof exc.stack !== 'undefined') { + Util.Warn("recv_message, caught exception: " + exc.stack); + } else if (typeof exc.description !== 'undefined') { + Util.Warn("recv_message, caught exception: " + exc.description); } else { - RFB.updateState('loaded', 'noVNC ready (using Flash WebSockets emulation)'); + Util.Warn("recv_message, caught exception:" + exc); + } + if (typeof exc.name !== 'undefined') { + updateState('failed', exc.name + ": " + exc.message); + } else { + updateState('failed', exc); } } + //Util.Debug("<< recv_message"); +}; - // Initialize canvas/fxcanvas - try { - Canvas.init(RFB.canvasID); - } catch (exc) { - RFB.updateState('fatal', "No working Canvas"); +// overridable for testing +send_array = function(arr) { + //Util.Debug(">> send_array: " + arr); + encode_message(arr); + if (ws.bufferedAmount === 0) { + //Util.Debug("arr: " + arr); + //Util.Debug("SQ: " + SQ); + ws.send(SQ); + SQ = ""; + } else { + Util.Debug("Delaying send"); } +}; + +function send_string(str) { + //Util.Debug(">> send_string: " + str); + send_array(str.split('').map( + function (chr) { return chr.charCodeAt(0); } ) ); +} - // Populate encoding lookup tables - RFB.encHandlers = {}; - RFB.encNames = {}; - for (i=0; i < RFB.encodings.length; i+=1) { - RFB.encHandlers[RFB.encodings[i][1]] = RFB[RFB.encodings[i][2]]; - RFB.encNames[RFB.encodings[i][1]] = RFB.encodings[i][0]; +function genDES(password, challenge) { + var i, passwd, response; + passwd = []; + response = challenge.slice(); + for (i=0; i < password.length; i += 1) { + passwd.push(password.charCodeAt(i)); } - //Util.Debug("<< load"); -}, -connect: function (host, port, password) { - //Util.Debug(">> connect"); + DES.setKeys(passwd); + DES.encrypt(response, 0, response, 0); + DES.encrypt(response, 8, response, 8); + return response; +} - RFB.host = host; - RFB.port = port; - RFB.password = (password !== undefined) ? password : ""; +function flushClient() { + if (mouse_arr.length > 0) { + //send_array(mouse_arr.concat(fbUpdateRequest(1))); + send_array(mouse_arr); + setTimeout(function() { + send_array(fbUpdateRequest(1)); + }, 50); - if ((!RFB.host) || (!RFB.port)) { - RFB.updateState('failed', "Must set host and port"); - return; + mouse_arr = []; + return true; + } else { + return false; } +} - RFB.updateState('connect'); - //Util.Debug("<< connect"); +// overridable for testing +checkEvents = function() { + var now; + if (rfb_state === 'normal') { + if (! flushClient()) { + now = new Date().getTime(); + if (now > last_req_time + conf.fbu_req_rate) { + last_req_time = now; + send_array(fbUpdateRequest(1)); + } + } + } + setTimeout(checkEvents, conf.check_rate); +}; -}, +function keyPress(keysym, down) { + var arr; + arr = keyEvent(keysym, down); + arr = arr.concat(fbUpdateRequest(1)); + send_array(arr); +} -disconnect: function () { - //Util.Debug(">> disconnect"); - RFB.updateState('disconnected', 'Disconnected'); - //Util.Debug("<< disconnect"); -}, +function mouseButton(x, y, down, bmask) { + if (down) { + mouse_buttonMask |= bmask; + } else { + mouse_buttonMask ^= bmask; + } + mouse_arr = mouse_arr.concat( pointerEvent(x, y) ); + flushClient(); +} -clipboardPasteFrom: function (text) { - if (RFB.state !== "normal") { return; } - //Util.Debug(">> clipboardPasteFrom: " + text.substr(0,40) + "..."); - RFB.send_array(RFB.clientCutText(text)); - //Util.Debug("<< clipboardPasteFrom"); -}, +function mouseMove(x, y) { + //Util.Debug('>> mouseMove ' + x + "," + y); + mouse_arr = mouse_arr.concat( pointerEvent(x, y) ); +} -/* - * Private variables and methods - */ +function update_timings() { + var now, offset; + now = (new Date()).getTime(); + timing.history.push([now, + timing.h_fbus, + timing.h_rects, + timing.h_bytes, + timing.h_pixels]); + timing.h_fbus = 0; + timing.h_rects = 0; + timing.h_bytes = 0; + timing.h_pixels = 0; + if ((rfb_state !== 'disconnected') && (rfb_state !== 'failed')) { + // Try for every second + offset = (now - timing.history_start) % 1000; + if (offset < 500) { + setTimeout(update_timings, 1000 - offset); + } else { + setTimeout(update_timings, 2000 - offset); + } + } +} -ws : null, // Web Socket object -sendID : null, -scanID : null, // TIGHT_PNG render image scanner - -// Receive and send queues -RQ : [], // Receive Queue -SQ : "", // Send Queue - -encHandlers : {}, -encNames : {}, - -// Frame buffer update state -FBU : { - rects : 0, - subrects : 0, // RRE and HEXTILE - lines : 0, // RAW - tiles : 0, // HEXTILE - bytes : 0, - x : 0, - y : 0, - width : 0, - height : 0, - encoding : 0, - subencoding : -1, - background : null, - imgs : [] // TIGHT_PNG image queue -}, - -fb_Bpp : 4, -fb_depth : 3, - -max_version : 3.8, -version : 0, -auth_scheme : '', -state : 'disconnected', -cuttext : 'none', // ServerCutText wait state -ct_length : 0, - -shared : 1, -check_rate : 217, -scan_imgs_rate : 100, -req_rate : 1413, -last_req : 0, - -canvasID : 'VNC_canvas', -fb_width : 0, -fb_height : 0, -fb_name : "", -rre_chunk : 100, - -timing : { - last_fbu : 0, - fbu_total : 0, - fbu_total_cnt : 0, - full_fbu_total : 0, - full_fbu_cnt : 0, - - fbu_rt_start : 0, - fbu_rt_total : 0, - fbu_rt_cnt : 0, - - history : [], - history_start : 0, - h_time : 0, - h_rects : 0, - h_fbus : 0, - h_bytes : 0, - h_pixels : 0 -}, - -/* Mouse state */ -mouse_buttonmask : 0, -mouse_arr : [], +show_timings = function() { + var i, history, msg, + delta, tot_time = 0, tot_fbus = 0, tot_rects = 0, + tot_bytes = 0, tot_pixels = 0; + if (timing.history_start === 0) { return; } + //Util.Debug(">> show_timings"); + update_timings(); // Final accumulate + msg = "\nTimings\n"; + msg += " time: fbus,rects,bytes,pixels\n"; + for (i=0; i < timing.history.length; i += 1) { + history = timing.history[i]; + delta = ((history[0]-timing.history_start)/1000); + tot_time = delta; + tot_fbus += history[1]; + tot_rects += history[2]; + tot_bytes += history[3]; + tot_pixels += history[4]; -/* - * Server message handlers - */ + msg += " " + delta.toFixed(3); + msg += ": " + history.slice(1) + "\n"; + } + msg += "\nTotals:\n"; + msg += " time: fbus,rects,bytes,pixels\n"; + msg += " " + tot_time.toFixed(3); + msg += ": " + tot_fbus + "," + tot_rects; + msg += "," + tot_bytes + "," + tot_pixels; + Util.Info(msg); + //Util.Debug("<< show_timings"); +}; -/* RFB/VNC initialisation */ -init_msg: function () { - //Util.Debug(">> init_msg [RFB.state '" + RFB.state + "']"); - if (RFB.stateinvalid) - return; - var RQ = RFB.RQ, strlen, reason, reason_len, sversion, cversion, +// +// Server message handlers +// + +// RFB/VNC initialisation message handler +init_msg = function() { + //Util.Debug(">> init_msg [rfb_state '" + rfb_state + "']"); + + var strlen, reason, reason_len, sversion, cversion, i, types, num_types, challenge, response, bpp, depth, big_endian, true_color, name_length; //Util.Debug("RQ (" + RQ.length + ") " + RQ); - switch (RFB.state) { + switch (rfb_state) { case 'ProtocolVersion' : if (RQ.length < 12) { - RFB.updateState('failed', + updateState('failed', "Disconnected: incomplete protocol version"); return; } sversion = RQ.shiftStr(12).substr(4,7); Util.Info("Server ProtocolVersion: " + sversion); switch (sversion) { - case "003.003": RFB.version = 3.3; break; - case "003.007": RFB.version = 3.7; break; - case "003.008": RFB.version = 3.8; break; + case "003.003": rfb_version = 3.3; break; + case "003.007": rfb_version = 3.7; break; + case "003.008": rfb_version = 3.8; break; default: - RFB.updateState('failed', + updateState('failed', "Invalid server version " + sversion); return; } - if (RFB.version > RFB.max_version) { - RFB.version = RFB.max_version; + if (rfb_version > rfb_max_version) { + rfb_version = rfb_max_version; } - RFB.sendID = setInterval(function() { - /* - * Send updates either at a rate of one update every 50ms, - * or whatever slower rate the network can handle - */ - if (RFB.ws.bufferedAmount === 0) { - if (RFB.SQ) { - RFB.ws.send(RFB.SQ); - RFB.SQ = ""; + if (! test_mode) { + sendID = setInterval(function() { + // Send updates either at a rate of one update + // every 50ms, or whatever slower rate the network + // can handle. + if (ws.bufferedAmount === 0) { + if (SQ) { + ws.send(SQ); + SQ = ""; + } + } else { + Util.Debug("Delaying send"); } - } else { - Util.Debug("Delaying send"); - } - }, 50); + }, 50); + } - cversion = "00" + parseInt(RFB.version,10) + - ".00" + ((RFB.version * 10) % 10); - RFB.send_string("RFB " + cversion + "\n"); - RFB.updateState('Security', "Sent ProtocolVersion: " + sversion); + cversion = "00" + parseInt(rfb_version,10) + + ".00" + ((rfb_version * 10) % 10); + send_string("RFB " + cversion + "\n"); + updateState('Security', "Sent ProtocolVersion: " + sversion); break; case 'Security' : - if (RFB.version >= 3.7) { + if (rfb_version >= 3.7) { num_types = RQ.shift8(); if (num_types === 0) { strlen = RQ.shift32(); reason = RQ.shiftStr(strlen); - RFB.updateState('failed', + updateState('failed', "Disconnected: security failure: " + reason); return; } - RFB.auth_scheme = 0; + rfb_auth_scheme = 0; types = RQ.shiftBytes(num_types); + Util.Debug("Server security types: " + types); for (i=0; i < types.length; i+=1) { - if ((types[i] > RFB.auth_scheme) && (types[i] < 3)) { - RFB.auth_scheme = types[i]; + if ((types[i] > rfb_auth_scheme) && (types[i] < 3)) { + rfb_auth_scheme = types[i]; } } - if (RFB.auth_scheme === 0) { - RFB.updateState('failed', + if (rfb_auth_scheme === 0) { + updateState('failed', "Disconnected: unsupported security types: " + types); return; } - RFB.send_array([RFB.auth_scheme]); + send_array([rfb_auth_scheme]); } else { if (RQ.length < 4) { - RFB.updateState('failed', "Invalid security frame"); + updateState('failed', "Invalid security frame"); return; } - RFB.auth_scheme = RQ.shift32(); + rfb_auth_scheme = RQ.shift32(); } - RFB.updateState('Authentication', - "Authenticating using scheme: " + RFB.auth_scheme); - // Fall through + updateState('Authentication', + "Authenticating using scheme: " + rfb_auth_scheme); + init_msg(); // Recursive fallthrough (workaround JSLint complaint) + break; case 'Authentication' : - //Util.Debug("Security auth scheme: " + RFB.auth_scheme); - switch (RFB.auth_scheme) { + //Util.Debug("Security auth scheme: " + rfb_auth_scheme); + switch (rfb_auth_scheme) { case 0: // connection failed if (RQ.length < 4) { //Util.Debug(" waiting for auth reason bytes"); @@ -370,16 +751,15 @@ init_msg: function () { } strlen = RQ.shift32(); reason = RQ.shiftStr(strlen); - RFB.updateState('failed', + updateState('failed', "Disconnected: auth failure: " + reason); return; case 1: // no authentication - // RFB.send_array([RFB.shared]); // ClientInitialisation - RFB.updateState('SecurityResult'); + updateState('SecurityResult'); break; case 2: // VNC authentication - if (RFB.password.length === 0) { - RFB.updateState('password', "Password Required"); + if (rfb_password.length === 0) { + updateState('password', "Password Required"); return; } if (RQ.length < 16) { @@ -387,60 +767,60 @@ init_msg: function () { return; } challenge = RQ.shiftBytes(16); - //Util.Debug("Password: " + RFB.password); + //Util.Debug("Password: " + rfb_password); //Util.Debug("Challenge: " + challenge + // " (" + challenge.length + ")"); - response = RFB.DES(RFB.password, challenge); + response = genDES(rfb_password, challenge); //Util.Debug("Response: " + response + // " (" + response.length + ")"); //Util.Debug("Sending DES encrypted auth response"); - RFB.send_array(response); - RFB.updateState('SecurityResult'); + send_array(response); + updateState('SecurityResult'); break; default: - RFB.updateState('failed', + updateState('failed', "Disconnected: unsupported auth scheme: " + - RFB.auth_scheme); + rfb_auth_scheme); return; } break; case 'SecurityResult' : if (RQ.length < 4) { - RFB.updateState('failed', "Invalid VNC auth response"); + updateState('failed', "Invalid VNC auth response"); return; } switch (RQ.shift32()) { case 0: // OK - RFB.updateState('ServerInitialisation', "Authentication OK"); + updateState('ServerInitialisation', "Authentication OK"); break; case 1: // failed - if (RFB.version >= 3.8) { + if (rfb_version >= 3.8) { reason_len = RQ.shift32(); reason = RQ.shiftStr(reason_len); - RFB.updateState('failed', reason); + updateState('failed', reason); } else { - RFB.updateState('failed', "Authentication failed"); + updateState('failed', "Authentication failed"); } return; case 2: // too-many - RFB.updateState('failed', + updateState('failed', "Disconnected: too many auth attempts"); return; } - RFB.send_array([RFB.shared]); // ClientInitialisation + send_array([rfb_shared]); // ClientInitialisation break; case 'ServerInitialisation' : if (RQ.length < 24) { - RFB.updateState('failed', "Invalid server initialisation"); + updateState('failed', "Invalid server initialisation"); return; } /* Screen size */ - RFB.fb_width = RQ.shift16(); - RFB.fb_height = RQ.shift16(); + fb_width = RQ.shift16(); + fb_height = RQ.shift16(); /* PIXEL_FORMAT */ bpp = RQ.shift8(); @@ -448,7 +828,7 @@ init_msg: function () { big_endian = RQ.shift8(); true_color = RQ.shift8(); - Util.Info("Screen: " + RFB.fb_width + "x" + RFB.fb_height + + Util.Info("Screen: " + fb_width + "x" + fb_height + ", bpp: " + bpp + ", depth: " + depth + ", big_endian: " + big_endian + ", true_color: " + true_color); @@ -456,61 +836,61 @@ init_msg: function () { /* Connection name/title */ RQ.shiftStr(12); name_length = RQ.shift32(); - RFB.fb_name = RQ.shiftStr(name_length); + fb_name = RQ.shiftStr(name_length); - Canvas.resize(RFB.fb_width, RFB.fb_height, RFB.true_color); - Canvas.start(RFB.keyPress, RFB.mouseButton, RFB.mouseMove); + canvas.resize(fb_width, fb_height, conf.true_color); + canvas.start(keyPress, mouseButton, mouseMove); - if (RFB.true_color) { - RFB.fb_Bpp = 4; - RFB.fb_depth = 3; + if (conf.true_color) { + fb_Bpp = 4; + fb_depth = 3; } else { - RFB.fb_Bpp = 1; - RFB.fb_depth = 1; + fb_Bpp = 1; + fb_depth = 1; } - response = RFB.pixelFormat(); - response = response.concat(RFB.clientEncodings()); - response = response.concat(RFB.fbUpdateRequest(0)); - RFB.timing.fbu_rt_start = (new Date()).getTime(); - RFB.send_array(response); + response = pixelFormat(); + response = response.concat(clientEncodings()); + response = response.concat(fbUpdateRequest(0)); + timing.fbu_rt_start = (new Date()).getTime(); + send_array(response); /* Start pushing/polling */ - setTimeout(RFB.checkEvents, RFB.check_rate); - setTimeout(RFB.scan_tight_imgs, RFB.scan_imgs_rate); - RFB.timing.history_start = (new Date()).getTime(); - setTimeout(RFB.update_timings, 1000); + setTimeout(checkEvents, conf.check_rate); + setTimeout(scan_tight_imgs, scan_imgs_rate); + timing.history_start = (new Date()).getTime(); + setTimeout(update_timings, 1000); - if (RFB.encrypt) { - RFB.updateState('normal', "Connected (encrypted) to: " + RFB.fb_name); + if (conf.encrypt) { + updateState('normal', "Connected (encrypted) to: " + fb_name); } else { - RFB.updateState('normal', "Connected (unencrypted) to: " + RFB.fb_name); + updateState('normal', "Connected (unencrypted) to: " + fb_name); } break; } //Util.Debug("<< init_msg"); -}, +}; -/* Normal RFB/VNC server messages */ -normal_msg: function () { +/* Normal RFB/VNC server message handler */ +normal_msg = function() { //Util.Debug(">> normal_msg"); - var RQ = RFB.RQ, ret = true, msg_type, + var ret = true, msg_type, c, first_colour, num_colours, red, green, blue; //Util.Debug(">> msg RQ.slice(0,10): " + RQ.slice(0,20)); //Util.Debug(">> msg RQ.slice(-10,-1): " + RQ.slice(RQ.length-10,RQ.length)); - if (RFB.FBU.rects > 0) { + if (FBU.rects > 0) { msg_type = 0; - } else if (RFB.cuttext !== 'none') { + } else if (cuttext !== 'none') { msg_type = 3; } else { msg_type = RQ.shift8(); } switch (msg_type) { case 0: // FramebufferUpdate - ret = RFB.framebufferUpdate(); // false means need more data + ret = framebufferUpdate(); // false means need more data break; case 1: // SetColourMapEntries Util.Debug("SetColourMapEntries"); @@ -524,10 +904,10 @@ normal_msg: function () { //Util.Debug("red after: " + red); green = parseInt(RQ.shift16() / 256, 10); blue = parseInt(RQ.shift16() / 256, 10); - Canvas.colourMap[first_colour + c] = [red, green, blue]; + canvas.set_colourMap([red, green, blue], first_colour + c); } Util.Info("Registered " + num_colours + " colourMap entries"); - //Util.Debug("colourMap: " + Canvas.colourMap); + //Util.Debug("colourMap: " + canvas.get_colourMap()); break; case 2: // Bell Util.Warn("Bell (unsupported)"); @@ -535,39 +915,37 @@ normal_msg: function () { case 3: // ServerCutText Util.Debug("ServerCutText"); Util.Debug("RQ:" + RQ.slice(0,20)); - if (RFB.cuttext === 'none') { - RFB.cuttext = 'header'; + if (cuttext === 'none') { + cuttext = 'header'; } - if (RFB.cuttext === 'header') { + if (cuttext === 'header') { if (RQ.length < 7) { //Util.Debug("waiting for ServerCutText header"); return false; } RQ.shiftBytes(3); // Padding - RFB.ct_length = RQ.shift32(); + cuttext_length = RQ.shift32(); } - RFB.cuttext = 'bytes'; - if (RQ.length < RFB.ct_length) { + cuttext = 'bytes'; + if (RQ.length < cuttext_length) { //Util.Debug("waiting for ServerCutText bytes"); return false; } - RFB.clipboardCopyTo(RQ.shiftStr(RFB.ct_length)); - RFB.cuttext = 'none'; + conf.clipboardReceive(that, RQ.shiftStr(cuttext_length)); + cuttext = 'none'; break; default: - RFB.updateState('failed', + updateState('failed', "Disconnected: illegal server message type " + msg_type); Util.Debug("RQ.slice(0,30):" + RQ.slice(0,30)); break; } //Util.Debug("<< normal_msg"); return ret; -}, +}; -framebufferUpdate: function() { - var RQ = RFB.RQ, FBU = RFB.FBU, timing = RFB.timing, - now, fbu_rt_diff, last_bytes, last_rects, - ret = true; +framebufferUpdate = function() { + var now, fbu_rt_diff, last_bytes, last_rects, ret = true; if (FBU.rects === 0) { //Util.Debug("New FBU: RQ.slice(0,20): " + RQ.slice(0,20)); @@ -589,7 +967,7 @@ framebufferUpdate: function() { } while (FBU.rects > 0) { - if (RFB.state !== "normal") { + if (rfb_state !== "normal") { return false; } if (RQ.length < FBU.bytes) { @@ -608,19 +986,19 @@ framebufferUpdate: function() { FBU.encoding = parseInt(RQ.shift32(), 10); timing.h_bytes += 12; - if (RFB.encNames[FBU.encoding]) { + if (encNames[FBU.encoding]) { // Debug: /* var msg = "FramebufferUpdate rects:" + FBU.rects; msg += " x: " + FBU.x + " y: " + FBU.y msg += " width: " + FBU.width + " height: " + FBU.height; msg += " encoding:" + FBU.encoding; - msg += "(" + RFB.encNames[FBU.encoding] + ")"; + msg += "(" + encNames[FBU.encoding] + ")"; msg += ", RQ.length: " + RQ.length; Util.Debug(msg); */ } else { - RFB.updateState('failed', + updateState('failed', "Disconnected: unsupported encoding " + FBU.encoding); return false; @@ -632,7 +1010,7 @@ framebufferUpdate: function() { last_rects = FBU.rects; // false ret means need more data - ret = RFB.encHandlers[FBU.encoding](); + ret = encHandlers[FBU.encoding](); now = (new Date()).getTime(); timing.cur_fbu += (now - timing.last_fbu); @@ -645,8 +1023,8 @@ framebufferUpdate: function() { } if (FBU.rects === 0) { - if (((FBU.width === RFB.fb_width) && - (FBU.height === RFB.fb_height)) || + if (((FBU.width === fb_width) && + (FBU.height === fb_height)) || (timing.fbu_rt_start > 0)) { timing.full_fbu_total += timing.cur_fbu; timing.full_fbu_cnt += 1; @@ -672,21 +1050,21 @@ framebufferUpdate: function() { } } return ret; -}, +}; -/* - * FramebufferUpdate encodings - */ +// +// FramebufferUpdate encodings +// -display_raw: function () { +encHandlers.RAW = function display_raw() { //Util.Debug(">> display_raw"); - var RQ = RFB.RQ, FBU = RFB.FBU, cur_y, cur_height; + var cur_y, cur_height; if (FBU.lines === 0) { FBU.lines = FBU.height; } - FBU.bytes = FBU.width * RFB.fb_Bpp; // At least a line + FBU.bytes = FBU.width * fb_Bpp; // At least a line if (RQ.length < FBU.bytes) { //Util.Debug(" waiting for " + // (FBU.bytes - RQ.length) + " RAW bytes"); @@ -694,24 +1072,24 @@ display_raw: function () { } cur_y = FBU.y + (FBU.height - FBU.lines); cur_height = Math.min(FBU.lines, - Math.floor(RQ.length/(FBU.width * RFB.fb_Bpp))); - Canvas.blitImage(FBU.x, cur_y, FBU.width, cur_height, RQ, 0); - RQ.shiftBytes(FBU.width * cur_height * RFB.fb_Bpp); + Math.floor(RQ.length/(FBU.width * fb_Bpp))); + canvas.blitImage(FBU.x, cur_y, FBU.width, cur_height, RQ, 0); + RQ.shiftBytes(FBU.width * cur_height * fb_Bpp); FBU.lines -= cur_height; if (FBU.lines > 0) { - FBU.bytes = FBU.width * RFB.fb_Bpp; // At least another line + FBU.bytes = FBU.width * fb_Bpp; // At least another line } else { FBU.rects -= 1; FBU.bytes = 0; } return true; -}, +}; -display_copy_rect: function () { +encHandlers.COPYRECT = function display_copy_rect() { //Util.Debug(">> display_copy_rect"); - var RQ = RFB.RQ, FBU = RFB.FBU, old_x, old_y; + var old_x, old_y; if (RQ.length < 4) { //Util.Debug(" waiting for " + @@ -720,52 +1098,52 @@ display_copy_rect: function () { } old_x = RQ.shift16(); old_y = RQ.shift16(); - Canvas.copyImage(old_x, old_y, FBU.x, FBU.y, FBU.width, FBU.height); + canvas.copyImage(old_x, old_y, FBU.x, FBU.y, FBU.width, FBU.height); FBU.rects -= 1; FBU.bytes = 0; return true; -}, +}; + +encHandlers.RRE = function display_rre() { + //Util.Debug(">> display_rre (" + RQ.length + " bytes)"); + var color, x, y, width, height, chunk; -display_rre: function () { - //Util.Debug(">> display_rre (" + RFB.RQ.length + " bytes)"); - var RQ = RFB.RQ, FBU = RFB.FBU, color, x, y, width, height, chunk; if (FBU.subrects === 0) { - if (RQ.length < 4 + RFB.fb_Bpp) { + if (RQ.length < 4 + fb_Bpp) { //Util.Debug(" waiting for " + - // (4 + RFB.fb_Bpp - RQ.length) + " RRE bytes"); + // (4 + fb_Bpp - RQ.length) + " RRE bytes"); return false; } FBU.subrects = RQ.shift32(); - color = RQ.shiftBytes(RFB.fb_Bpp); // Background - Canvas.fillRect(FBU.x, FBU.y, FBU.width, FBU.height, color); + color = RQ.shiftBytes(fb_Bpp); // Background + canvas.fillRect(FBU.x, FBU.y, FBU.width, FBU.height, color); } - while ((FBU.subrects > 0) && (RQ.length >= (RFB.fb_Bpp + 8))) { - color = RQ.shiftBytes(RFB.fb_Bpp); + while ((FBU.subrects > 0) && (RQ.length >= (fb_Bpp + 8))) { + color = RQ.shiftBytes(fb_Bpp); x = RQ.shift16(); y = RQ.shift16(); width = RQ.shift16(); height = RQ.shift16(); - Canvas.fillRect(FBU.x + x, FBU.y + y, width, height, color); + canvas.fillRect(FBU.x + x, FBU.y + y, width, height, color); FBU.subrects -= 1; } //Util.Debug(" display_rre: rects: " + FBU.rects + // ", FBU.subrects: " + FBU.subrects); if (FBU.subrects > 0) { - chunk = Math.min(RFB.rre_chunk, FBU.subrects); - FBU.bytes = (RFB.fb_Bpp + 8) * chunk; + chunk = Math.min(rre_chunk_sz, FBU.subrects); + FBU.bytes = (fb_Bpp + 8) * chunk; } else { FBU.rects -= 1; FBU.bytes = 0; } //Util.Debug("<< display_rre, FBU.bytes: " + FBU.bytes); return true; -}, +}; -display_hextile: function() { +encHandlers.HEXTILE = function display_hextile() { //Util.Debug(">> display_hextile"); - var RQ = RFB.RQ, FBU = RFB.FBU, - subencoding, subrects, idx, tile, color, cur_tile, + var subencoding, subrects, idx, tile, color, cur_tile, tile_x, x, w, tile_y, y, h, xy, s, sx, sy, wh, sw, sh; if (FBU.tiles === 0) { @@ -784,7 +1162,7 @@ display_hextile: function() { } subencoding = RQ[0]; // Peek if (subencoding > 30) { // Raw - RFB.updateState('failed', + updateState('failed', "Disconnected: illegal hextile subencoding " + subencoding); //Util.Debug("RQ.slice(0,30):" + RQ.slice(0,30)); return false; @@ -801,13 +1179,13 @@ display_hextile: function() { /* Figure out how much we are expecting */ if (subencoding & 0x01) { // Raw //Util.Debug(" Raw subencoding"); - FBU.bytes += w * h * RFB.fb_Bpp; + FBU.bytes += w * h * fb_Bpp; } else { if (subencoding & 0x02) { // Background - FBU.bytes += RFB.fb_Bpp; + FBU.bytes += fb_Bpp; } if (subencoding & 0x04) { // Foreground - FBU.bytes += RFB.fb_Bpp; + FBU.bytes += fb_Bpp; } if (subencoding & 0x08) { // AnySubrects FBU.bytes += 1; // Since we aren't shifting it off @@ -818,7 +1196,7 @@ display_hextile: function() { } subrects = RQ[FBU.bytes-1]; // Peek if (subencoding & 0x10) { // SubrectsColoured - FBU.bytes += subrects * (RFB.fb_Bpp + 2); + FBU.bytes += subrects * (fb_Bpp + 2); } else { FBU.bytes += subrects * 2; } @@ -847,28 +1225,28 @@ display_hextile: function() { /* Weird: ignore blanks after RAW */ Util.Debug(" Ignoring blank after RAW"); } else { - Canvas.fillRect(x, y, w, h, FBU.background); + canvas.fillRect(x, y, w, h, FBU.background); } } else if (FBU.subencoding & 0x01) { // Raw - Canvas.blitImage(x, y, w, h, RQ, idx); + canvas.blitImage(x, y, w, h, RQ, idx); } else { if (FBU.subencoding & 0x02) { // Background - FBU.background = RQ.slice(idx, idx + RFB.fb_Bpp); - idx += RFB.fb_Bpp; + FBU.background = RQ.slice(idx, idx + fb_Bpp); + idx += fb_Bpp; } if (FBU.subencoding & 0x04) { // Foreground - FBU.foreground = RQ.slice(idx, idx + RFB.fb_Bpp); - idx += RFB.fb_Bpp; + FBU.foreground = RQ.slice(idx, idx + fb_Bpp); + idx += fb_Bpp; } - tile = Canvas.getTile(x, y, w, h, FBU.background); + tile = canvas.getTile(x, y, w, h, FBU.background); if (FBU.subencoding & 0x08) { // AnySubrects subrects = RQ[idx]; idx += 1; for (s = 0; s < subrects; s += 1) { if (FBU.subencoding & 0x10) { // SubrectsColoured - color = RQ.slice(idx, idx + RFB.fb_Bpp); - idx += RFB.fb_Bpp; + color = RQ.slice(idx, idx + fb_Bpp); + idx += fb_Bpp; } else { color = FBU.foreground; } @@ -882,10 +1260,10 @@ display_hextile: function() { sw = (wh >> 4) + 1; sh = (wh & 0x0f) + 1; - Canvas.setSubTile(tile, sx, sy, sw, sh, color); + canvas.setSubTile(tile, sx, sy, sw, sh, color); } } - Canvas.putTile(tile); + canvas.putTile(tile); } RQ.shiftBytes(FBU.bytes); FBU.lastsubencoding = FBU.subencoding; @@ -899,13 +1277,12 @@ display_hextile: function() { //Util.Debug("<< display_hextile"); return true; -}, +}; -display_tight_png: function() { +encHandlers.TIGHT_PNG = function display_tight_png() { //Util.Debug(">> display_tight_png"); - var RQ = RFB.RQ, FBU = RFB.FBU, - ctl, cmode, clength, getCLength, color, img; + var ctl, cmode, clength, getCLength, color, img; //Util.Debug(" FBU.rects: " + FBU.rects); //Util.Debug(" RQ.length: " + RQ.length); //Util.Debug(" RQ.slice(0,20): " + RQ.slice(0,20)); @@ -941,7 +1318,7 @@ display_tight_png: function() { } switch (cmode) { // fill uses fb_depth because TPIXELs drop the padding byte - case "fill": FBU.bytes += RFB.fb_depth; break; // TPIXEL + case "fill": FBU.bytes += fb_depth; break; // TPIXEL case "jpeg": FBU.bytes += 3; break; // max clength case "png": FBU.bytes += 3; break; // max clength } @@ -951,15 +1328,15 @@ display_tight_png: function() { return false; } - //Util.Debug(" RQ.slice(0,20): " + RFB.RQ.slice(0,20) + " (" + RFB.RQ.length + ")"); + //Util.Debug(" RQ.slice(0,20): " + RQ.slice(0,20) + " (" + RQ.length + ")"); //Util.Debug(" cmode: " + cmode); // Determine FBU.bytes switch (cmode) { case "fill": RQ.shift8(); // shift off ctl - color = RQ.shiftBytes(RFB.fb_depth); - Canvas.fillRect(FBU.x, FBU.y, FBU.width, FBU.height, color); + color = RQ.shiftBytes(fb_depth); + canvas.fillRect(FBU.x, FBU.y, FBU.width, FBU.height, color); break; case "jpeg": case "png": @@ -974,10 +1351,10 @@ display_tight_png: function() { //Util.Debug(" png, RQ.length: " + RQ.length + ", clength[0]: " + clength[0] + ", clength[1]: " + clength[1]); RQ.shiftBytes(1 + clength[0]); // shift off ctl + compact length img = new Image(); - img.onload = RFB.scan_tight_imgs; + img.onload = scan_tight_imgs; FBU.imgs.push([img, FBU.x, FBU.y]); img.src = "data:image/" + cmode + - RFB.extract_data_uri(RQ.shiftBytes(clength[1])); + extract_data_uri(RQ.shiftBytes(clength[1])); img = null; break; } @@ -987,90 +1364,90 @@ display_tight_png: function() { //Util.Debug(" ending RQ.slice(0,20): " + RQ.slice(0,20)); //Util.Debug("<< display_tight_png"); return true; -}, +}; -extract_data_uri : function (arr) { +extract_data_uri = function(arr) { //var i, stra = []; //for (i=0; i< arr.length; i += 1) { // stra.push(String.fromCharCode(arr[i])); //} //return "," + escape(stra.join('')); return ";base64," + Base64.encode(arr); -}, +}; -scan_tight_imgs : function () { - var img, imgs; - if (RFB.stateinvalid) - return; - if (RFB.state === 'normal') { - imgs = RFB.FBU.imgs; +scan_tight_imgs = function() { + var img, imgs, ctx; + ctx = canvas.getContext(); + if (rfb_state === 'normal') { + imgs = FBU.imgs; while ((imgs.length > 0) && (imgs[0][0].complete)) { img = imgs.shift(); - Canvas.ctx.drawImage(img[0], img[1], img[2]); + ctx.drawImage(img[0], img[1], img[2]); } - setTimeout(RFB.scan_tight_imgs, RFB.scan_imgs_rate); + setTimeout(scan_tight_imgs, scan_imgs_rate); } -}, +}; -set_desktopsize : function () { +encHandlers.DesktopSize = function set_desktopsize() { Util.Debug(">> set_desktopsize"); - RFB.fb_width = RFB.FBU.width; - RFB.fb_height = RFB.FBU.height; - Canvas.clear(); - Canvas.resize(RFB.fb_width, RFB.fb_height); - RFB.timing.fbu_rt_start = (new Date()).getTime(); + fb_width = FBU.width; + fb_height = FBU.height; + canvas.clear(); + canvas.resize(fb_width, fb_height); + timing.fbu_rt_start = (new Date()).getTime(); // Send a new non-incremental request - RFB.send_array(RFB.fbUpdateRequest(0)); + send_array(fbUpdateRequest(0)); - RFB.FBU.bytes = 0; - RFB.FBU.rects -= 1; + FBU.bytes = 0; + FBU.rects -= 1; Util.Debug("<< set_desktopsize"); return true; -}, +}; -set_cursor: function () { +encHandlers.Cursor = function set_cursor() { 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; + x = FBU.x; // hotspot-x + y = FBU.y; // hotspot-y + w = FBU.width; + h = FBU.height; - pixelslength = w * h * RFB.fb_Bpp; + pixelslength = w * h * fb_Bpp; masklength = Math.floor((w + 7) / 8) * h; - if (RFB.RQ.length < (pixelslength + masklength)) { + if (RQ.length < (pixelslength + masklength)) { //Util.Debug("waiting for cursor encoding bytes"); - RFB.FBU.bytes = pixelslength + masklength; + FBU.bytes = pixelslength + masklength; return false; } //Util.Debug(" set_cursor, x: " + x + ", y: " + y + ", w: " + w + ", h: " + h); - Canvas.changeCursor(RFB.RQ.shiftBytes(pixelslength), - RFB.RQ.shiftBytes(masklength), - x, y, w, h); + canvas.changeCursor(RQ.shiftBytes(pixelslength), + RQ.shiftBytes(masklength), + x, y, w, h); - RFB.FBU.bytes = 0; - RFB.FBU.rects -= 1; + FBU.bytes = 0; + FBU.rects -= 1; //Util.Debug("<< set_cursor"); return true; -}, +}; + +encHandlers.JPEG_quality_lo = function set_jpeg_quality() { + Util.Error("Server sent jpeg_quality pseudo-encoding"); +}; -set_jpeg_quality : function () { - Util.Debug(">> set_jpeg_quality"); -}, -set_compress_level: function () { - Util.Debug(">> set_compress_level"); -}, +encHandlers.compress_lo = function set_compress_level() { + Util.Error("Server sent compress level pseudo-encoding"); +}; /* * Client message routines */ -pixelFormat: function () { +pixelFormat = function() { //Util.Debug(">> pixelFormat"); var arr; arr = [0]; // msg-type @@ -1078,10 +1455,10 @@ pixelFormat: function () { arr.push8(0); // padding arr.push8(0); // padding - arr.push8(RFB.fb_Bpp * 8); // bits-per-pixel - arr.push8(RFB.fb_depth * 8); // depth + arr.push8(fb_Bpp * 8); // bits-per-pixel + arr.push8(fb_depth * 8); // depth arr.push8(0); // little-endian - arr.push8(RFB.true_color); // true-color + arr.push8(conf.true_color ? 1 : 0); // true-color arr.push16(255); // red-max arr.push16(255); // green-max @@ -1095,22 +1472,19 @@ pixelFormat: function () { arr.push8(0); // padding //Util.Debug("<< pixelFormat"); return arr; -}, +}; -fixColourMapEntries: function () { -}, - -clientEncodings: function () { +clientEncodings = function() { //Util.Debug(">> clientEncodings"); var arr, i, encList = []; - for (i=0; i<RFB.encodings.length; i += 1) { - if ((RFB.encodings[i][0] === "Cursor") && - (! RFB.local_cursor)) { + for (i=0; i<encodings.length; i += 1) { + if ((encodings[i][0] === "Cursor") && + (! conf.local_cursor)) { Util.Debug("Skipping Cursor pseudo-encoding"); } else { - //Util.Debug("Adding encoding: " + RFB.encodings[i][0]); - encList.push(RFB.encodings[i][1]); + //Util.Debug("Adding encoding: " + encodings[i][0]); + encList.push(encodings[i][1]); } } @@ -1123,14 +1497,14 @@ clientEncodings: function () { } //Util.Debug("<< clientEncodings: " + arr); return arr; -}, +}; -fbUpdateRequest: function (incremental, x, y, xw, yw) { +fbUpdateRequest = function(incremental, x, y, xw, yw) { //Util.Debug(">> fbUpdateRequest"); if (!x) { x = 0; } if (!y) { y = 0; } - if (!xw) { xw = RFB.fb_width; } - if (!yw) { yw = RFB.fb_height; } + if (!xw) { xw = fb_width; } + if (!yw) { yw = fb_height; } var arr; arr = [3]; // msg-type arr.push8(incremental); @@ -1140,9 +1514,9 @@ fbUpdateRequest: function (incremental, x, y, xw, yw) { arr.push16(yw); //Util.Debug("<< fbUpdateRequest"); return arr; -}, +}; -keyEvent: function (keysym, down) { +keyEvent = function(keysym, down) { //Util.Debug(">> keyEvent, keysym: " + keysym + ", down: " + down); var arr; arr = [4]; // msg-type @@ -1151,21 +1525,21 @@ keyEvent: function (keysym, down) { arr.push32(keysym); //Util.Debug("<< keyEvent"); return arr; -}, +}; -pointerEvent: function (x, y) { +pointerEvent = function(x, y) { //Util.Debug(">> pointerEvent, x,y: " + x + "," + y + - // " , mask: " + RFB.mouse_buttonMask); + // " , mask: " + mouse_buttonMask); var arr; arr = [5]; // msg-type - arr.push8(RFB.mouse_buttonMask); + arr.push8(mouse_buttonMask); arr.push16(x); arr.push16(y); //Util.Debug("<< pointerEvent"); return arr; -}, +}; -clientCutText: function (text) { +clientCutText = function(text) { //Util.Debug(">> clientCutText"); var arr; arr = [6]; // msg-type @@ -1176,470 +1550,107 @@ clientCutText: function (text) { arr.pushStr(text); //Util.Debug("<< clientCutText:" + arr); return arr; -}, +}; -/* - * Utility routines - */ -encode_message: function(arr) { - if (RFB.b64encode) { - /* base64 encode */ - RFB.SQ = RFB.SQ + Base64.encode(arr); - } else { - /* UTF-8 encode. 0 -> 256 to avoid WebSockets framing */ - RFB.SQ = RFB.SQ + arr.map(function (num) { - if (num === 0) { - return String.fromCharCode(256); - } else { - return String.fromCharCode(num); - } - } ).join(''); - } -}, +// +// Public API interface functions +// -decode_message: function(data) { - var i, length, RQ = RFB.RQ; - //Util.Debug(">> decode_message: " + data); - if (RFB.b64encode) { - /* base64 decode */ - RFB.RQ = RFB.RQ.concat(Base64.decode(data, 0)); - } else { - /* UTF-8 decode. 256 -> 0 to WebSockets framing */ - length = data.length; - for (i=0; i < length; i += 1) { - RQ.push(data.charCodeAt(i) % 256); - } - } - //Util.Debug(">> decode_message, RQ: " + RFB.RQ); -}, - -recv_message: function(e) { - //Util.Debug(">> recv_message"); - - try { - RFB.decode_message(e.data); - if (RFB.RQ.length > 0) { - RFB.handle_message(); - } else { - Util.Debug("Ignoring empty message"); - } - } catch (exc) { - if (typeof exc.stack !== 'undefined') { - Util.Warn("recv_message, caught exception: " + exc.stack); - } else if (typeof exc.description !== 'undefined') { - Util.Warn("recv_message, caught exception: " + exc.description); - } else { - Util.Warn("recv_message, caught exception:" + exc); - } - if (typeof exc.name !== 'undefined') { - RFB.updateState('failed', exc.name + ": " + exc.message); - } else { - RFB.updateState('failed', exc); - } - } - //Util.Debug("<< recv_message"); -}, +that.init = function () { -handle_message: function () { - //Util.Debug("RQ.slice(0,20): " + RFB.RQ.slice(0,20) + " (" + RFB.RQ.length + ")"); - switch (RFB.state) { - case 'disconnected': - Util.Error("Got data while disconnected"); - break; - case 'failed': - Util.Warn("Giving up!"); - RFB.disconnect(); - break; - case 'normal': - if ((RFB.state === 'normal') && (RFB.RQ.length > 0)) { - if (RFB.normal_msg()) { - // true means we can continue processing - Util.Debug("More data to process"); - // Give other events a chance to run - setTimeout(RFB.handle_message, 10); - } - } - break; - default: - RFB.init_msg(); - break; - } -}, + init_vars(); -send_string: function (str) { - //Util.Debug(">> send_string: " + str); - RFB.send_array(str.split('').map( - function (chr) { return chr.charCodeAt(0); } ) ); -}, - -send_array: function (arr) { - //Util.Debug(">> send_array: " + arr); - RFB.encode_message(arr); - if (RFB.ws.bufferedAmount === 0) { - //Util.Debug("arr: " + arr); - //Util.Debug("RFB.SQ: " + RFB.SQ); - RFB.ws.send(RFB.SQ); - RFB.SQ = ""; - } else { - Util.Debug("Delaying send"); - } -}, - -DES: function (password, challenge) { - var i, passwd, response; - passwd = []; - response = challenge.slice(); - for (i=0; i < password.length; i += 1) { - passwd.push(password.charCodeAt(i)); - } - - DES.setKeys(passwd); - DES.encrypt(response, 0, response, 0); - DES.encrypt(response, 8, response, 8); - return response; -}, - -flushClient: function () { - if (RFB.mouse_arr.length > 0) { - //RFB.send_array(RFB.mouse_arr.concat(RFB.fbUpdateRequest(1))); - RFB.send_array(RFB.mouse_arr); - setTimeout(function() { - RFB.send_array(RFB.fbUpdateRequest(1)); - }, 50); - - RFB.mouse_arr = []; - return true; + /* Check web-socket-js if no builtin WebSocket support */ + if (VNC_native_ws) { + Util.Info("Using native WebSockets"); + updateState('loaded', 'noVNC ready (using native WebSockets)'); } else { - return false; - } -}, - -checkEvents: function () { - var now; - if (RFB.stateinvalid) - return; - if (RFB.state === 'normal') { - if (! RFB.flushClient()) { - now = new Date().getTime(); - if (now > RFB.last_req + RFB.req_rate) { - RFB.last_req = now; - RFB.send_array(RFB.fbUpdateRequest(1)); - } + Util.Warn("Using web-socket-js flash bridge"); + if ((! Util.Flash) || + (Util.Flash.version < 9)) { + updateState('fatal', "WebSockets or Adobe Flash is required"); + } else if (document.location.href.substr(0, 7) === "file://") { + updateState('fatal', + "'file://' URL is incompatible with Adobe Flash"); + } else { + updateState('loaded', 'noVNC ready (using Flash WebSockets emulation)'); } } - setTimeout(RFB.checkEvents, RFB.check_rate); -}, +}; -keyPress: function (keysym, down) { - var arr; - arr = RFB.keyEvent(keysym, down); - arr = arr.concat(RFB.fbUpdateRequest(1)); - RFB.send_array(arr); -}, +that.connect = function(host, port, password) { + //Util.Debug(">> connect"); -mouseButton: function(x, y, down, bmask) { - if (down) { - RFB.mouse_buttonMask |= bmask; - } else { - RFB.mouse_buttonMask ^= bmask; + // Make sure we have done init checks + if ((rfb_state !== 'loaded') && (rfb_state !== 'fatal')) { + that.init(); } - RFB.mouse_arr = RFB.mouse_arr.concat( RFB.pointerEvent(x, y) ); - RFB.flushClient(); -}, - -mouseMove: function(x, y) { - //Util.Debug('>> mouseMove ' + x + "," + y); - RFB.mouse_arr = RFB.mouse_arr.concat( RFB.pointerEvent(x, y) ); -}, - -clipboardCopyTo: function (text) { - Util.Debug(">> clipboardCopyTo stub"); - // Stub -}, -externalUpdateState: function(state, oldstate, msg) { - Util.Debug(">> externalUpdateState stub"); - // Stub -}, + rfb_host = host; + rfb_port = port; + rfb_password = (password !== undefined) ? password : ""; -/* - * Running states: - * disconnected - idle state - * normal - connected - * - * Page states: - * loaded - page load, equivalent to disconnected - * connect - starting initialization - * password - waiting for password - * failed - abnormal transition to disconnected - * fatal - failed to load page, or fatal error - * - * VNC initialization states: - * ProtocolVersion - * Security - * Authentication - * SecurityResult - * ServerInitialization - */ -updateState: function(state, statusMsg) { - var func, cmsg, oldstate = RFB.state; - if (state === oldstate) { - /* Already here, ignore */ - Util.Debug("Already in state '" + state + "', ignoring."); + if ((!rfb_host) || (!rfb_port)) { + updateState('failed', "Must set host and port"); return; } - if (oldstate === 'fatal') { - Util.Error("Fatal error, cannot continue"); - } - - if ((state === 'failed') || (state === 'fatal')) { - func = Util.Error; - } else { - func = Util.Warn; - } - - cmsg = typeof(statusMsg) !== 'undefined' ? (" Msg: " + statusMsg) : ""; - func("New state '" + state + "', was '" + oldstate + "'." + cmsg); - - if ((oldstate === 'failed') && (state === 'disconnected')) { - // Do disconnect action, but stay in failed state. - RFB.state = 'failed'; - } else { - RFB.state = state; - } - - switch (state) { - case 'loaded': - case 'disconnected': - - if (RFB.sendID) { - clearInterval(RFB.sendID); - RFB.sendID = null; - } - - if (RFB.ws) { - if (RFB.ws.readyState === WebSocket.OPEN) { - RFB.ws.close(); - } - RFB.ws.onmessage = function (e) { return; }; - } - - if (Canvas.ctx) { - Canvas.stop(); - Canvas.clear(); - } - - RFB.show_timings(); - - break; - - - case 'connect': - RFB.init_vars(); - - if ((RFB.ws) && (RFB.ws.readyState === WebSocket.OPEN)) { - RFB.ws.close(); - } - RFB.init_ws(); // onopen transitions to 'ProtocolVersion' - - break; - - - case 'password': - // Ignore password state by default - break; - - - case 'normal': - if ((oldstate === 'disconnected') || (oldstate === 'failed')) { - Util.Error("Invalid transition from 'disconnected' or 'failed' to 'normal'"); - } - - break; - - - case 'failed': - if (oldstate === 'disconnected') { - Util.Error("Invalid transition from 'disconnected' to 'failed'"); - } - if (oldstate === 'normal') { - Util.Error("Error while connected."); - } - if (oldstate === 'init') { - Util.Error("Error while initializing."); - } - - if ((RFB.ws) && (RFB.ws.readyState === WebSocket.OPEN)) { - RFB.ws.close(); - } - // Make sure we transition to disconnected - setTimeout(function() { - // here we do not invalidate the timer, or we can be stuck in 'normal' mode - RFB.updateState('disconnected'); - }, 50); - - break; - - - default: - // Invalid state transition - - } - - if ((oldstate === 'failed') && (state === 'disconnected')) { - // Leave the failed message - RFB.externalUpdateState(state, oldstate); - } else { - RFB.externalUpdateState(state, oldstate, statusMsg); - } -}, - -update_timings: function() { - var now, timing = RFB.timing, offset; - if (RFB.stateinvalid) - return; - now = (new Date()).getTime(); - timing.history.push([now, - timing.h_fbus, - timing.h_rects, - timing.h_bytes, - timing.h_pixels]); - timing.h_fbus = 0; - timing.h_rects = 0; - timing.h_bytes = 0; - timing.h_pixels = 0; - if ((RFB.state !== 'disconnected') && (RFB.state !== 'failed')) { - // Try for every second - offset = (now - timing.history_start) % 1000; - if (offset < 500) { - setTimeout(RFB.update_timings, 1000 - offset); - } else { - setTimeout(RFB.update_timings, 2000 - offset); - } - } -}, + updateState('connect'); + //Util.Debug("<< connect"); -show_timings: function() { - var i, timing = RFB.timing, history, msg, - delta, tot_time = 0, tot_fbus = 0, tot_rects = 0, - tot_bytes = 0, tot_pixels = 0; - if (timing.history_start === 0) { return; } - //Util.Debug(">> show_timings"); - RFB.update_timings(); // Final accumulate - msg = "\nTimings\n"; - msg += " time: fbus,rects,bytes,pixels\n"; - for (i=0; i < timing.history.length; i += 1) { - history = timing.history[i]; - delta = ((history[0]-timing.history_start)/1000); - tot_time = delta; - tot_fbus += history[1]; - tot_rects += history[2]; - tot_bytes += history[3]; - tot_pixels += history[4]; +}; - msg += " " + delta.toFixed(3); - msg += ": " + history.slice(1) + "\n"; - } - msg += "\nTotals:\n"; - msg += " time: fbus,rects,bytes,pixels\n"; - msg += " " + tot_time.toFixed(3); - msg += ": " + tot_fbus + "," + tot_rects; - msg += "," + tot_bytes + "," + tot_pixels; - Util.Info(msg); - //Util.Debug("<< show_timings"); -}, +that.disconnect = function() { + //Util.Debug(">> disconnect"); + updateState('disconnected', 'Disconnected'); + //Util.Debug("<< disconnect"); +}; -/* - * Setup routines - */ +that.sendPassword = function(passwd) { + rfb_password = passwd; + rfb_state = "Authentication"; + setTimeout(init_msg, 1); +}; -init_ws: function () { - //Util.Debug(">> init_ws"); - var uri = "", vars = []; - if (RFB.encrypt) { - uri = "wss://"; - } else { - uri = "ws://"; - } - - uri += RFB.host + ":" + RFB.port + "/"; - if (RFB.b64encode) { - vars.push("b64encode"); - } - if (vars.length > 0) { - uri += "?" + vars.join("&"); - } - Util.Info("connecting to " + uri); - RFB.ws = new WebSocket(uri); +that.sendCtrlAltDel = function() { + if (rfb_state !== "normal") { return false; } + Util.Info("Sending Ctrl-Alt-Del"); + var arr = []; + arr = arr.concat(keyEvent(0xFFE3, 1)); // Control + arr = arr.concat(keyEvent(0xFFE9, 1)); // Alt + arr = arr.concat(keyEvent(0xFFFF, 1)); // Delete + arr = arr.concat(keyEvent(0xFFFF, 0)); // Delete + arr = arr.concat(keyEvent(0xFFE9, 0)); // Alt + arr = arr.concat(keyEvent(0xFFE3, 0)); // Control + arr = arr.concat(fbUpdateRequest(1)); + send_array(arr); +}; + +that.clipboardPasteFrom = function(text) { + if (rfb_state !== "normal") { return; } + //Util.Debug(">> clipboardPasteFrom: " + text.substr(0,40) + "..."); + send_array(clientCutText(text)); + //Util.Debug("<< clipboardPasteFrom"); +}; - RFB.ws.onmessage = RFB.recv_message; - RFB.ws.onopen = function(e) { - Util.Debug(">> WebSocket.onopen"); - if (RFB.state === "connect") { - RFB.updateState('ProtocolVersion', "Starting VNC handshake"); - } else { - RFB.updateState('failed', "Got unexpected WebSockets connection"); - } - Util.Debug("<< WebSocket.onopen"); - }; - RFB.ws.onclose = function(e) { - Util.Debug(">> WebSocket.onclose"); - if (RFB.state === 'normal') { - RFB.updateState('failed', 'Server disconnected'); - } else if (RFB.state === 'ProtocolVersion') { - RFB.updateState('failed', 'Failed to connect to server'); - } else { - RFB.updateState('disconnected', 'VNC disconnected'); - } - Util.Debug("<< WebSocket.onclose"); - }; - RFB.ws.onerror = function(e) { - Util.Debug(">> WebSocket.onerror"); - RFB.updateState('failed', "WebSocket error"); - Util.Debug("<< WebSocket.onerror"); - }; +that.testMode = function(override_send_array) { + // Overridable internal functions for testing + test_mode = true; + send_array = override_send_array; + that.recv_message = recv_message; // Expose it - setTimeout(function () { - if (RFB.stateinvalid) - return; - if (RFB.ws.readyState === WebSocket.CONNECTING) { - RFB.updateState('failed', "Connect timeout"); - } - }, RFB.connectTimeout); + checkEvents = function () { /* Stub Out */ }; + that.connect = function(host, port, password) { + rfb_host = host; + rfb_port = port; + rfb_password = password; + updateState('ProtocolVersion', "Starting VNC handshake"); + }; +}; - //Util.Debug("<< init_ws"); -}, -init_vars: function () { - /* Reset state */ - RFB.cuttext = 'none'; - RFB.ct_length = 0; - RFB.RQ = []; - RFB.SQ = ""; - RFB.FBU.rects = 0; - RFB.FBU.subrects = 0; // RRE and HEXTILE - RFB.FBU.lines = 0; // RAW - RFB.FBU.tiles = 0; // HEXTILE - RFB.FBU.imgs = []; // TIGHT_PNG image queue - RFB.mouse_buttonmask = 0; - RFB.mouse_arr = []; - - RFB.timing.history_start = 0; - RFB.timing.history = []; - RFB.timing.h_fbus = 0; - RFB.timing.h_rects = 0; - RFB.timing.h_bytes = 0; - RFB.timing.h_pixels = 0; - RFB.stateinvalid = false; -}, - -invalidateAllTimers: function(){ - RFB.stateinvalid = true; - if (RFB.sendID) - clearInterval(RFB.sendID); -} +return constructor(); // Return the public API interface -}; /* End of RFB */ +} // End of RFB() diff --git a/include/util.js b/include/util.js index e1d11ab..120aed4 100644 --- a/include/util.js +++ b/include/util.js @@ -6,7 +6,7 @@ * See README.md for usage and integration instructions. */ -//"use strict"; +"use strict"; /*jslint bitwise: false, white: false */ /*global window, document, navigator, ActiveXObject*/ @@ -15,38 +15,6 @@ Util = {}; /* - * Logging/debug routines - */ - -Util.init_logging = function (level) { - if (typeof window.console === "undefined") { - if (typeof window.opera !== "undefined") { - window.console = { - 'log' : window.opera.postError, - 'warn' : window.opera.postError, - 'error': window.opera.postError }; - } else { - window.console = { - 'log' : function(m) {}, - 'warn' : function(m) {}, - 'error': function(m) {}}; - } - } - - Util.Debug = Util.Info = Util.Warn = Util.Error = function (msg) {}; - switch (level) { - case 'none': break; - 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 + "'"); - } -} - -/* * Simple DOM selector by ID */ if (!window.$) { @@ -135,6 +103,42 @@ Array.prototype.shiftBytes = function (len) { * ------------------------------------------------------ */ +/* + * Logging/debug routines + */ + +Util.init_logging = function (level) { + if (typeof window.console === "undefined") { + if (typeof window.opera !== "undefined") { + window.console = { + 'log' : window.opera.postError, + 'warn' : window.opera.postError, + 'error': window.opera.postError }; + } else { + window.console = { + 'log' : function(m) {}, + 'warn' : function(m) {}, + 'error': function(m) {}}; + } + } + + Util.Debug = Util.Info = Util.Warn = Util.Error = function (msg) {}; + switch (level) { + case 'debug': Util.Debug = function (msg) { console.log(msg); }; + case 'info': Util.Info = function (msg) { console.log(msg); }; + case 'warn': Util.Warn = function (msg) { console.warn(msg); }; + case 'error': Util.Error = function (msg) { console.error(msg); }; + case 'none': + break; + default: + throw("invalid logging type '" + level + "'"); + } +}; +// Initialize logging level +Util.init_logging( (document.location.href.match( + /logging=([A-Za-z0-9\._\-]*)/) || + ['', 'warn'])[1] ); + Util.dirObj = function (obj, depth, parent) { var i, msg = "", val = ""; if (! depth) { depth=2; } @@ -157,6 +161,41 @@ Util.dirObj = function (obj, depth, parent) { return msg; }; +// Read a query string variable +Util.getQueryVar = function(name, defVal) { + var re = new RegExp('[?][^#]*' + name + '=([^&#]*)'); + if (typeof defVal === 'undefined') { defVal = null; } + return (document.location.href.match(re) || ['',defVal])[1]; +}; + +// Set defaults for Crockford style function namespaces +Util.conf_default = function(cfg, api, v, val, force_bool) { + if (typeof cfg[v] === 'undefined') { + cfg[v] = val; + } + // Default getter + if (typeof api['get_' + v] === 'undefined') { + api['get_' + v] = function () { + return cfg[v]; + }; + } + // Default setter + if (typeof api['set_' + v] === 'undefined') { + api['set_' + v] = function (val) { + if (force_bool) { + if ((!val) || (val in {'0':1, 'no':1, 'false':1})) { + val = false; + } else { + val = true; + } + } + cfg[v] = val; + }; + } +}; + + + /* * Cross-browser routines */ @@ -276,12 +315,11 @@ Util.createCookie = function(name,value,days) { }; 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); + var i, c, nameEQ = name + "=", ca = document.cookie.split(';'); + for(i=0; i < ca.length; i += 1) { + c = ca[i]; + while (c.charAt(0) === ' ') { c = c.substring(1,c.length); } + if (c.indexOf(nameEQ) === 0) { return c.substring(nameEQ.length,c.length); } } return (typeof defaultValue !== 'undefined') ? defaultValue : null; }; @@ -294,8 +332,8 @@ Util.eraseCookie = function(name) { * Alternate stylesheet selection */ Util.getStylesheets = function() { var i, links, sheets = []; - links = document.getElementsByTagName("link") - for (i = 0; i < links.length; i++) { + links = document.getElementsByTagName("link"); + for (i = 0; i < links.length; i += 1) { if (links[i].title && links[i].rel.toUpperCase().indexOf("STYLESHEET") > -1) { sheets.push(links[i]); @@ -311,17 +349,15 @@ Util.selectStylesheet = function(sheet) { if (typeof sheet === 'undefined') { sheet = 'default'; } - for (i=0; i < sheets.length; i++) { + for (i=0; i < sheets.length; i += 1) { link = sheets[i]; if (link.title === sheet) { Util.Debug("Using stylesheet " + sheet); link.disabled = false; } else { - Util.Debug("Skipping stylesheet " + link.title); + //Util.Debug("Skipping stylesheet " + link.title); link.disabled = true; } } return sheet; }; - - diff --git a/tests/canvas.html b/tests/canvas.html index e762acd..87e7781 100644 --- a/tests/canvas.html +++ b/tests/canvas.html @@ -46,14 +46,16 @@ } function test_functions () { - var img, x, y; - Canvas.fillRect(0, 0, Canvas.c_wx, Canvas.c_wy, [240,240,240]); + var img, x, y, w, h, ctx = canvas.getContext(); + w = canvas.get_width(); + h = canvas.get_height(); + canvas.fillRect(0, 0, w, h, [240,240,240]); - Canvas.blitStringImage("data:image/png;base64," + face64, 150, 10); + canvas.blitStringImage("data:image/png;base64," + face64, 150, 10); var himg = new Image(); himg.onload = function () { - Canvas.ctx.drawImage(himg, 200, 40); }; + ctx.drawImage(himg, 200, 40); }; himg.src = "face.png"; /* Test array image data */ @@ -66,15 +68,14 @@ data[(y*50 + x)*4 + 3] = 255; } } - Canvas.blitImage(30, 10, 50, 50, data, 0); + canvas.blitImage(30, 10, 50, 50, data, 0); - //Canvas.prefer_js = false; - img = Canvas.getTile(5,5,16,16,[0,128,128]); - Canvas.putTile(img); + img = canvas.getTile(5,5,16,16,[0,128,128]); + canvas.putTile(img); - img = Canvas.getTile(90,15,16,16,[0,0,0]); - Canvas.setSubTile(img, 0,0,16,16,[128,128,0]); - Canvas.putTile(img); + img = canvas.getTile(90,15,16,16,[0,0,0]); + canvas.setSubTile(img, 0,0,16,16,[128,128,0]); + canvas.putTile(img); } function begin () { @@ -85,30 +86,35 @@ } function start_delayed () { + var ret; + + ret = canvas.set_prefer_js(true); + if (ret) { + message("Running test: prefer Javascript ops"); + var time1 = run_test(); + message("prefer Javascript ops: " + time1 + "ms total, " + + (time1 / iterations) + "ms per frame"); + } else { + message("Could not run: prefer Javascript ops"); + } - message("Running test: prefer Javascript"); - Canvas.prefer_js = true; - var time1 = run_test(); - message("prefer Javascript: " + time1 + "ms total, " + - (time1 / iterations) + "ms per frame"); - + canvas.set_prefer_js(false); message("Running test: prefer Canvas ops"); - Canvas.prefer_js = false; var time2 = run_test(); message("prefer Canvas ops: " + time2 + "ms total, " + (time2 / iterations) + "ms per frame"); - Canvas.resize(start_width, start_height, true); + canvas.resize(start_width, start_height, true); test_functions(); $('startButton').disabled = false; - $('startButton').value = "Start"; + $('startButton').value = "Do Performance Test"; } function run_test () { var width, height; width = $('width').value; height = $('height').value; - Canvas.resize(width, height); + canvas.resize(width, height); var color, start_time = (new Date()).getTime(), w, h; for (var i=0; i < iterations; i++) { color = [128, 128, (255 / iterations) * i, 0]; @@ -116,9 +122,9 @@ for (var y=0; y < height; y = y + 16) { w = Math.min(16, width - x); h = Math.min(16, height - y); - var tile = Canvas.getTile(x, y, w, h, color); - Canvas.setSubTile(tile, 0, 0, w, h, color); - Canvas.putTile(tile); + var tile = canvas.getTile(x, y, w, h, color); + canvas.setSubTile(tile, 0, 0, w, h, color); + canvas.putTile(tile); } } } @@ -129,8 +135,8 @@ window.onload = function() { message("in onload"); $('iterations').value = 10; - Canvas.init('canvas'); - Canvas.resize(start_width, start_height, true); + canvas = Canvas({'target' : 'canvas'}); + canvas.resize(start_width, start_height, true); message("Canvas initialized"); test_functions(); } diff --git a/tests/cursor.html b/tests/cursor.html index d244db1..81fba82 100644 --- a/tests/cursor.html +++ b/tests/cursor.html @@ -105,10 +105,10 @@ window.onload = function() { debug("onload"); - var cross, cursor, cursor64; + var canvas, cross, cursor, cursor64; - Canvas.init("testcanvas"); - debug("Canvas.init() indicates Data URI cursor support is: " + Canvas.isCursor()); + canvas = new Canvas({'target' : "testcanvas"}); + debug("canvas indicates Data URI cursor support is: " + canvas.get_cursor_uri()); $('button1').style.cursor="url(face.png), default"; diff --git a/tests/input.html b/tests/input.html index f89768e..43791ed 100644 --- a/tests/input.html +++ b/tests/input.html @@ -19,6 +19,7 @@ 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> var msg_cnt = 0; @@ -51,9 +52,9 @@ } window.onload = function() { - Canvas.init('canvas'); - Canvas.resize(width, height); - Canvas.start(keyPress, mouseButton, mouseMove); + var canvas = Canvas({'target' : 'canvas'}); + canvas.resize(width, height, true); + canvas.start(keyPress, mouseButton, mouseMove); message("Canvas initialized"); } </script> @@ -16,13 +16,12 @@ noVNC example: simple example using default controls <body> <div id='vnc'>Loading</div> - </body> - <script> - RFB.loadExtras(); + <script> window.onload = function () { DefaultControls.load('vnc'); - RFB.load(); - } - </script> + }; + </script> + + </body> </html> diff --git a/vnc_auto.html b/vnc_auto.html index a94c9ea..126bc8c 100644 --- a/vnc_auto.html +++ b/vnc_auto.html @@ -7,8 +7,7 @@ Connect parameters are provided in query string: <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" title="plain"> <!-- <script type='text/javascript' src='http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js'></script> @@ -20,31 +19,36 @@ Connect parameters are provided in query string: <body style="margin: 0px;"> <div id="VNC_screen"> <div id="VNC_status_bar" class="VNC_status_bar" style="margin-top: 0px;"> - <table border=0 width=100%><tr> + <table border=0 width="100%"><tr> <td><div id="VNC_status">Loading</div></td> - <td width=1%><div id="VNC_buttons"> + <td width="1%"><div id="VNC_buttons"> <input type=button value="Send CtrlAltDel" - id="sendCtrlAltDelButton" - onclick="sendCtrlAltDel();"></div></td> + id="sendCtrlAltDelButton"> + </div></td> </tr></table> </div> <canvas id="VNC_canvas" width="640px" height="20px"> Canvas not supported. </canvas> </div> - </body> - <script> + <script> + /*jslint white: false */ + /*global window, $, Util, RFB, */ + "use strict"; + + var rfb; + function setPassword() { - RFB.sendPassword($('password_input').value); + rfb.sendPassword($('password_input').value); return false; } function sendCtrlAltDel() { - RFB.sendCtrlAltDel(); + rfb.sendCtrlAltDel(); return false; } - function updateState(state, msg) { - var s, sb, klass, html; + function updateState(rfb, state, oldstate, msg) { + var s, sb, cad, klass; s = $('VNC_status'); sb = $('VNC_status_bar'); cad = $('sendCtrlAltDelButton'); @@ -65,8 +69,9 @@ Connect parameters are provided in query string: msg += ' style="margin-bottom: 0px">'; msg += 'Password Required: '; msg += '<input type=password size=10 id="password_input" class="VNC_status">'; - msg += '</form>'; - // Fall through + msg += '<\/form>'; + klass = "VNC_status_warn"; + break; default: klass = "VNC_status_warn"; } @@ -83,6 +88,8 @@ Connect parameters are provided in query string: window.onload = function () { var host, port, password; + $('sendCtrlAltDelButton').onclick = sendCtrlAltDel; + host = Util.getQueryVar('host', null); port = Util.getQueryVar('port', null); password = Util.getQueryVar('password', ''); @@ -92,15 +99,15 @@ Connect parameters are provided in query string: return; } - RFB.setEncrypt(Util.getQueryVar('encrypt', true)); - RFB.setBase64(Util.getQueryVar('base64', true)); - RFB.setTrueColor(Util.getQueryVar('true_color', true)); - RFB.setCursor(Util.getQueryVar('cursor', true)); - RFB.setUpdateState(updateState); + rfb = new RFB({'encrypt': Util.getQueryVar('encrypt', true), + 'b64encode': Util.getQueryVar('base64', true), + 'true_color': Util.getQueryVar('true_color', true), + 'local_cursor': Util.getQueryVar('cursor', true), + 'updateState': updateState}); + rfb.connect(host, port, password); + }; + </script> - RFB.load(); - RFB.connect(host, port, password); - } - </script> + </body> </html> |