Commit 8db09746 authored by Joel Martin's avatar Joel Martin

New API. Refactor Canvas and RFB objects.

New API:

To use the RFB object, you now must instantiate it (this allows more
than one instance of it on the same page).

    rfb = new RFB(settings);

The 'settings' variable is a namespace that contains initial default
settings. These can also be set and read using 'rfb.set_FOO()' and
'rfb.get_FOO()' where FOO is the setting name. The current settings
are (and defaults) are:
    - target: the DOM Canvas element to use ('VNC_canvas').
    - encrypt: whether to encrypt the connection (false)
    - true_color: true_color or palette (true)
    - b64encode: base64 encode the WebSockets data (true)
    - local_cursor: use local cursor rendering (true if supported)
    - connectTimeout: milliseconds to wait for connect (2000)
    - updateState: callback when RFB state changes (none)
    - clipboardReceive: callback when clipboard data received (none)

The parameters to the updateState callback have also changed. The
function spec is now updateState(rfb, state, oldstate, msg):
    - rfb: the RFB object that this state change is for.
    - state: the new state
    - oldstate: the previous state
    - msg: a message associate with the state (not always set).

The clipboardReceive spec is clipboardReceive(rfb, text):
    - rfb: the RFB object that this text is from.
    - text: the clipboard text received.

Changes:

- The RFB and Canvas namespaces are now more proper objects. Private
  implementation is no longer exposed and the public API has been made
  explicit. Also, instantiation allows more than one VNC connection
  on the same page (to complete this, DefaultControls will also need
  this same refactoring).

- Added 'none' logging level.

- Removed automatic stylesheet selection workaround in util.js and
  move it to defaultcontrols so that it doesn't interfere with
  intergration.

- Also, some major JSLinting.

- Fix input, canvas, and cursor tests to work with new model.
parent 8171f4d8
...@@ -195,7 +195,7 @@ The client is designed to be easily integrated with existing web ...@@ -195,7 +195,7 @@ The client is designed to be easily integrated with existing web
structure and style. structure and style.
At a minimum you must include the `vnc.js` and `default_controls.js` 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> <head>
<script src='include/vnc.js'></script> <script src='include/vnc.js'></script>
...@@ -203,12 +203,13 @@ scripts and call their load() functions. For example: ...@@ -203,12 +203,13 @@ scripts and call their load() functions. For example:
</head> </head>
<body> <body>
<div id='vnc'>Loading</div> <div id='vnc'>Loading</div>
</body>
<script> <script>
window.onload = function () { window.onload = function () {
DefaultControls.load('vnc'); DefaultControls.load('vnc');
RFB.load(); }; }
</script> </script>
</body>
See `vnc.html` and `vnc_auto.html` for examples. The file See `vnc.html` and `vnc_auto.html` for examples. The file
`include/plain.css` has a list of stylable elements. `include/plain.css` has a list of stylable elements.
......
...@@ -7,183 +7,142 @@ ...@@ -7,183 +7,142 @@
*/ */
"use strict"; "use strict";
/*jslint white: false, bitwise: false */ /*jslint browser: true, white: false, bitwise: false */
/*global window, $, Util, Base64 */ /*global window, Util, Base64 */
// Globals defined here function Canvas(conf) {
var Canvas;
// Everything namespaced inside Canvas conf = conf || {}; // Configuration
Canvas = { var that = {}, // Public API interface
prefer_js : false, // make private // Pre-declare functions used before definitions (jslint)jslint
force_canvas : false, // make private setFillColor, fillRect,
cursor_uri : true, // make private
true_color : true, // Private Canvas namespace variables
colourMap : [], c_forceCanvas = false,
scale : 1, c_width = 0,
c_wx : 0, c_height = 0,
c_wy : 0,
ctx : null,
prevStyle : "", c_prevStyle = "",
focused : true, c_keyPress = null,
keyPress : null, c_mouseButton = null,
mouseButton : null, c_mouseMove = null;
mouseMove : null,
onMouseButton: function(e, down) {
var evt, pos, bmask;
if (! Canvas.focused) {
return true;
}
evt = (e ? e : window.event);
pos = Util.getEventPosition(e, $(Canvas.id), Canvas.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);
}
Util.stopEvent(e);
return false;
},
onMouseDown: function (e) { // Capability settings, default can be overridden
Canvas.onMouseButton(e, 1); Util.conf_default(conf, that, 'prefer_js', null);
}, Util.conf_default(conf, that, 'cursor_uri', null);
onMouseUp: function (e) { // Configuration settings
Canvas.onMouseButton(e, 0); 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);
onMouseWheel: function (e) { // Override some specific getters/setters
var evt, pos, bmask, wheelData; that.set_prefer_js = function(val) {
evt = (e ? e : window.event); if (val && c_forceCanvas) {
pos = Util.getEventPosition(e, $(Canvas.id), Canvas.scale); Util.Warn("Preferring Javascript to Canvas ops is not supported");
wheelData = evt.detail ? evt.detail * -1 : evt.wheelDelta / 40;
if (wheelData > 0) {
bmask = 1 << 3;
} else {
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);
}
Util.stopEvent(e);
return false; return false;
},
onMouseMove: function (e) {
var evt, pos;
evt = (e ? e : window.event);
pos = Util.getEventPosition(e, $(Canvas.id), Canvas.scale);
//Util.Debug('mouse ' + evt.which + '/' + evt.button + ' up:' + pos.x + "," + pos.y);
if (Canvas.mouseMove) {
Canvas.mouseMove(pos.x, pos.y);
} }
}, conf.prefer_js = val;
onKeyDown: function (e) {
//Util.Debug("keydown: " + Canvas.getKeysym(e));
if (! Canvas.focused) {
return true; return true;
} };
if (Canvas.keyPress) {
Canvas.keyPress(Canvas.getKeysym(e), 1);
}
Util.stopEvent(e);
return false;
},
onKeyUp : function (e) { that.get_colourMap = function(idx) {
//Util.Debug("keyup: " + Canvas.getKeysym(e)); if (typeof idx === 'undefined') {
if (! Canvas.focused) { return conf.colourMap;
return true; } else {
} return conf.colourMap[idx];
if (Canvas.keyPress) {
Canvas.keyPress(Canvas.getKeysym(e), 0);
} }
Util.stopEvent(e); };
return false;
},
onMouseDisable: function (e) { that.set_colourMap = function(val, idx) {
var evt, pos; if (typeof idx === 'undefined') {
if (! Canvas.focused) { conf.colourMap = val;
return true; } else {
} conf.colourMap[idx] = val;
evt = (e ? e : window.event);
pos = Util.getPosition($(Canvas.id));
/* 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))) {
//Util.Debug("mouse event disabled");
Util.stopEvent(e);
return false;
} }
//Util.Debug("mouse event not disabled"); };
return true;
}, // Add some other getters/setters
that.get_width = function() {
return c_width;
};
that.get_height = function() {
return c_height;
};
init: function (id) {
var c, imgTest, tval, i, curDat, curSave; //
// Private functions
//
// Create the public API interface
function constructor() {
Util.Debug(">> Canvas.init"); Util.Debug(">> Canvas.init");
Canvas.id = id; var c, ctx, imgTest, tval, i, curDat, curSave,
c = $(Canvas.id); 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 (! c.getContext) { throw("no getContext method"); }
Canvas.ctx = c.getContext('2d');
Canvas.clear(); if (! conf.ctx) { conf.ctx = c.getContext('2d'); }
ctx = conf.ctx;
that.clear();
/* /*
* Determine browser Canvas feature support * Determine browser Canvas feature support
* and select fastest rendering methods * and select fastest rendering methods
*/ */
tval = 0; tval = 0;
Canvas.has_imageData = false;
try { try {
imgTest = Canvas.ctx.getImageData(0, 0, 1,1); imgTest = ctx.getImageData(0, 0, 1,1);
imgTest.data[0] = 123; imgTest.data[0] = 123;
imgTest.data[3] = 255; imgTest.data[3] = 255;
Canvas.ctx.putImageData(imgTest, 0, 0); ctx.putImageData(imgTest, 0, 0);
tval = Canvas.ctx.getImageData(0, 0, 1, 1).data[0]; tval = ctx.getImageData(0, 0, 1, 1).data[0];
if (tval === 123) { if (tval === 123) {
Canvas.has_imageData = true; has_imageData = true;
} }
} catch (exc) {} } catch (exc1) {}
if (Canvas.has_imageData) { if (has_imageData) {
Util.Info("Canvas supports imageData"); Util.Info("Canvas supports imageData");
Canvas.force_canvas = false; c_forceCanvas = false;
if (Canvas.ctx.createImageData) { if (ctx.createImageData) {
// If it's there, it's faster // If it's there, it's faster
Util.Info("Using Canvas createImageData"); Util.Info("Using Canvas createImageData");
Canvas._imageData = Canvas._imageDataCreate; that.imageData = that.imageDataCreate;
} else if (Canvas.ctx.getImageData) { } else if (ctx.getImageData) {
Util.Info("Using Canvas getImageData"); Util.Info("Using Canvas getImageData");
Canvas._imageData = Canvas._imageDataGet; that.imageData = that.imageDataGet;
} }
Util.Info("Prefering javascript operations"); Util.Info("Prefering javascript operations");
Canvas.prefer_js = true; if (conf.prefer_js === null) {
Canvas._rgbxImage = Canvas._rgbxImageData; conf.prefer_js = true;
Canvas._cmapImage = Canvas._cmapImageData; }
that.rgbxImage = that.rgbxImageData;
that.cmapImage = that.cmapImageData;
} else { } else {
Util.Warn("Canvas lacks imageData, using fillRect (slow)"); Util.Warn("Canvas lacks imageData, using fillRect (slow)");
Canvas.force_canvas = true; c_forceCanvas = true;
Canvas.prefer_js = false; conf.prefer_js = false;
Canvas._rgbxImage = Canvas._rgbxImageFill; that.rgbxImage = that.rgbxImageFill;
Canvas._cmapImage = Canvas._cmapImageFill; that.cmapImage = that.cmapImageFill;
} }
/* /*
...@@ -191,16 +150,21 @@ init: function (id) { ...@@ -191,16 +150,21 @@ init: function (id) {
* scheme * scheme
*/ */
curDat = []; curDat = [];
for (i=0; i < 8 * 8 * 4; i++) { for (i=0; i < 8 * 8 * 4; i += 1) {
curDat.push(255); curDat.push(255);
} }
try { try {
curSave = c.style.cursor; curSave = c.style.cursor;
Canvas.changeCursor(curDat, curDat, 2, 2, 8, 8); that.changeCursor(curDat, curDat, 2, 2, 8, 8);
if (c.style.cursor) { if (c.style.cursor) {
if (conf.cursor_uri === null) {
conf.cursor_uri = true;
}
Util.Info("Data URI scheme cursor supported"); Util.Info("Data URI scheme cursor supported");
} else { } else {
Canvas.cursor_uri = false; if (conf.cursor_uri === null) {
conf.cursor_uri = false;
}
Util.Warn("Data URI scheme cursor not supported"); Util.Warn("Data URI scheme cursor not supported");
} }
c.style.cursor = curSave; c.style.cursor = curSave;
...@@ -209,68 +173,244 @@ init: function (id) { ...@@ -209,68 +173,244 @@ init: function (id) {
conf.cursor_uri = false; conf.cursor_uri = false;
} }
Canvas.colourMap = []; conf.focused = true;
Canvas.prevStyle = "";
Canvas.focused = true;
Util.Debug("<< Canvas.init"); Util.Debug("<< Canvas.init");
return true; return that ;
}, }
/* Translate DOM key event to keysym value */
function getKeysym(e) {
var evt, keysym;
evt = (e ? e : window.event);
start: function (keyPress, mouseButton, mouseMove) { /* Remap modifier and special keys */
var c; switch ( evt.keyCode ) {
Util.Debug(">> Canvas.start"); 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;
}
c = $(Canvas.id); /* Remap symbols */
Canvas.keyPress = keyPress || null; switch (keysym) {
Canvas.mouseButton = mouseButton || null; case 186 : keysym = 59; break; // ; (IE)
Canvas.mouseMove = mouseMove || null; 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)
}
Util.addEvent(document, 'keydown', Canvas.onKeyDown); /* Remap shifted and unshifted keys */
Util.addEvent(document, 'keyup', Canvas.onKeyUp); if (!!evt.shiftKey) {
Util.addEvent(c, 'mousedown', Canvas.onMouseDown); switch (keysym) {
Util.addEvent(c, 'mouseup', Canvas.onMouseUp); case 48 : keysym = 41 ; break; // ) (shifted 0)
Util.addEvent(c, 'mousemove', Canvas.onMouseMove); case 49 : keysym = 33 ; break; // ! (shifted 1)
Util.addEvent(c, (Util.Engine.gecko) ? 'DOMMouseScroll' : 'mousewheel', case 50 : keysym = 64 ; break; // @ (shifted 2)
Canvas.onMouseWheel); 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)
/* Work around right and middle click browser behaviors */ case 59 : keysym = 58 ; break; // : (shifted `)
Util.addEvent(document, 'click', Canvas.onMouseDisable); case 61 : keysym = 43 ; break; // + (shifted ;)
Util.addEvent(document.body, 'contextmenu', Canvas.onMouseDisable); 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;
}
Util.Debug("<< Canvas.start"); return keysym;
}, }
clear: function () { function onMouseButton(e, down) {
Canvas.resize(640, 20); var evt, pos, bmask;
Canvas.ctx.clearRect(0, 0, Canvas.c_wx, Canvas.c_wy); if (! conf.focused) {
}, return true;
}
evt = (e ? e : window.event);
pos = Util.getEventPosition(e, conf.target, conf.scale);
bmask = 1 << evt.button;
//Util.Debug('mouse ' + pos.x + "," + pos.y + " down: " + down + " bmask: " + bmask);
if (c_mouseButton) {
c_mouseButton(pos.x, pos.y, down, bmask);
}
Util.stopEvent(e);
return false;
}
resize: function (width, height, true_color) { function onMouseDown(e) {
var c = $(Canvas.id); onMouseButton(e, 1);
}
if (typeof true_color !== "undefined") { function onMouseUp(e) {
Canvas.true_color = true_color; onMouseButton(e, 0);
}
function onMouseWheel(e) {
var evt, pos, bmask, wheelData;
evt = (e ? e : window.event);
pos = Util.getEventPosition(e, conf.target, conf.scale);
wheelData = evt.detail ? evt.detail * -1 : evt.wheelDelta / 40;
if (wheelData > 0) {
bmask = 1 << 3;
} else {
bmask = 1 << 4;
} }
//Util.Debug('mouse scroll by ' + wheelData + ':' + pos.x + "," + pos.y);
if (c_mouseButton) {
c_mouseButton(pos.x, pos.y, 1, bmask);
c_mouseButton(pos.x, pos.y, 0, bmask);
}
Util.stopEvent(e);
return false;
}
c.width = width; function onMouseMove(e) {
c.height = height; var evt, pos;
evt = (e ? e : window.event);
pos = Util.getEventPosition(e, conf.target, conf.scale);
//Util.Debug('mouse ' + evt.which + '/' + evt.button + ' up:' + pos.x + "," + pos.y);
if (c_mouseMove) {
c_mouseMove(pos.x, pos.y);
}
}
function onKeyDown(e) {
//Util.Debug("keydown: " + getKeysym(e));
if (! conf.focused) {
return true;
}
if (c_keyPress) {
c_keyPress(getKeysym(e), 1);
}
Util.stopEvent(e);
return false;
}
function onKeyUp(e) {
//Util.Debug("keyup: " + getKeysym(e));
if (! conf.focused) {
return true;
}
if (c_keyPress) {
c_keyPress(getKeysym(e), 0);
}
Util.stopEvent(e);
return false;
}
function onMouseDisable(e) {
var evt, pos;
if (! conf.focused) {
return true;
}
evt = (e ? e : window.event);
pos = Util.getPosition(conf.target);
/* Stop propagation if inside canvas area */
if ((evt.clientX >= pos.x) &&
(evt.clientY >= pos.y) &&
(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;
}
//
// Public API interface functions
//
that.getContext = function () {
return conf.ctx;
};
that.start = function(keyPressFunc, mouseButtonFunc, mouseMoveFunc) {
var c;
Util.Debug(">> Canvas.start");
Canvas.c_wx = c.offsetWidth; c = conf.target;
Canvas.c_wy = c.offsetHeight; c_keyPress = keyPressFunc || null;
c_mouseButton = mouseButtonFunc || null;
c_mouseMove = mouseMoveFunc || null;
//Canvas.rescale(Canvas.scale); 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',
onMouseWheel);
/* Work around right and middle click browser behaviors */
Util.addEvent(document, 'click', onMouseDisable);
Util.addEvent(document.body, 'contextmenu', onMouseDisable);
Util.Debug("<< Canvas.start");
};
rescale: function (factor) { that.rescale = function(factor) {
var c, tp, x, y, var c, tp, x, y,
properties = ['transform', 'WebkitTransform', 'MozTransform', null]; properties = ['transform', 'WebkitTransform', 'MozTransform', null];
c = $(Canvas.id); c = conf.target;
while (tp = properties.shift()) { tp = properties.shift();
if (typeof c.style[tp] != 'undefined') { while (tp) {
if (typeof c.style[tp] !== 'undefined') {
break; break;
} }
tp = properties.shift();
} }
if (tp === null) { if (tp === null) {
...@@ -278,36 +418,83 @@ rescale: function (factor) { ...@@ -278,36 +418,83 @@ rescale: function (factor) {
return; return;
} }
if (Canvas.scale === factor) { if (conf.scale === factor) {
//Util.Debug("Canvas already scaled to '" + factor + "'"); //Util.Debug("Canvas already scaled to '" + factor + "'");
return; return;
} }
Canvas.scale = factor; conf.scale = factor;
x = c.width - c.width * factor; x = c.width - c.width * factor;
y = c.height - c.height * factor; y = c.height - c.height * factor;
c.style[tp] = "scale(" + Canvas.scale + ") translate(-" + x + "px, -" + y + "px)"; c.style[tp] = "scale(" + conf.scale + ") translate(-" + x + "px, -" + y + "px)";
}, };
stop: function () { that.resize = function(width, height, true_color) {
var c = $(Canvas.id); var c = conf.target;
Util.removeEvent(document, 'keydown', Canvas.onKeyDown);
Util.removeEvent(document, 'keyup', Canvas.onKeyUp); if (typeof true_color !== "undefined") {
Util.removeEvent(c, 'mousedown', Canvas.onMouseDown); conf.true_color = true_color;
Util.removeEvent(c, 'mouseup', Canvas.onMouseUp); }
Util.removeEvent(c, 'mousemove', Canvas.onMouseMove);
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', Util.removeEvent(c, (Util.Engine.gecko) ? 'DOMMouseScroll' : 'mousewheel',
Canvas.onMouseWheel); onMouseWheel);
/* Work around right and middle click browser behaviors */ /* Work around right and middle click browser behaviors */
Util.removeEvent(document, 'click', Canvas.onMouseDisable); Util.removeEvent(document, 'click', onMouseDisable);
Util.removeEvent(document.body, 'contextmenu', Canvas.onMouseDisable); Util.removeEvent(document.body, 'contextmenu', onMouseDisable);
// Turn off cursor rendering // Turn off cursor rendering
if (Canvas.cursor_uri) { if (conf.cursor_uri) {
c.style.cursor = "default"; 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. * Tile rendering functions optimized for rendering engines.
...@@ -316,16 +503,16 @@ stop: function () { ...@@ -316,16 +503,16 @@ stop: function () {
* faster than direct Canvas fillStyle, fillRect rendering. In * faster than direct Canvas fillStyle, fillRect rendering. In
* gecko, Javascript array handling is much slower. * 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; var img, data, p, rgb, red, green, blue, j, i;
img = {'x': x, 'y': y, 'width': width, 'height': height, img = {'x': x, 'y': y, 'width': width, 'height': height,
'data': []}; 'data': []};
if (Canvas.prefer_js) { if (conf.prefer_js) {
data = img.data; data = img.data;
if (Canvas.true_color) { if (conf.true_color) {
rgb = color; rgb = color;
} else { } else {
rgb = Canvas.colourMap[color[0]]; rgb = conf.colourMap[color[0]];
} }
red = rgb[0]; red = rgb[0];
green = rgb[1]; green = rgb[1];
...@@ -340,20 +527,20 @@ getTile: function(x, y, width, height, color) { ...@@ -340,20 +527,20 @@ getTile: function(x, y, width, height, color) {
} }
} }
} else { } else {
Canvas.fillRect(x, y, width, height, color); fillRect(x, y, width, height, color);
} }
return img; 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; var data, p, rgb, red, green, blue, width, j, i;
if (Canvas.prefer_js) { if (conf.prefer_js) {
data = img.data; data = img.data;
width = img.width; width = img.width;
if (Canvas.true_color) { if (conf.true_color) {
rgb = color; rgb = color;
} else { } else {
rgb = Canvas.colourMap[color[0]]; rgb = conf.colourMap[color[0]];
} }
red = rgb[0]; red = rgb[0];
green = rgb[1]; green = rgb[1];
...@@ -368,31 +555,28 @@ setSubTile: function(img, x, y, w, h, color) { ...@@ -368,31 +555,28 @@ setSubTile: function(img, x, y, w, h, color) {
} }
} }
} else { } 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) { that.putTile = function(img) {
if (Canvas.prefer_js) { if (conf.prefer_js) {
Canvas._rgbxImage(img.x, img.y, img.width, img.height, img.data, 0); that.rgbxImage(img.x, img.y, img.width, img.height, img.data, 0);
} else { } else {
// No-op, under gecko already done by setSubTile // No-op, under gecko already done by setSubTile
} }
}, };
_imageDataGet: function(width, height) { that.imageDataGet = function(width, height) {
return Canvas.ctx.getImageData(0, 0, width, height); return conf.ctx.getImageData(0, 0, width, height);
}, };
_imageDataCreate: function(width, height) { that.imageDataCreate = function(width, height) {
return Canvas.ctx.createImageData(width, height); return conf.ctx.createImageData(width, height);
}, };
_imageDataRaw: function(width, height) {
return {'data': [], 'width': width, 'height': height};
},
_rgbxImageData: function(x, y, width, height, arr, offset) { that.rgbxImageData = function(x, y, width, height, arr, offset) {
var img, i, j, data; var img, i, j, data;
img = Canvas._imageData(width, height); img = that.imageData(width, height);
data = img.data; data = img.data;
for (i=0, j=offset; i < (width * height * 4); i=i+4, j=j+4) { for (i=0, j=offset; i < (width * height * 4); i=i+4, j=j+4) {
data[i + 0] = arr[j + 0]; data[i + 0] = arr[j + 0];
...@@ -400,27 +584,27 @@ _rgbxImageData: function(x, y, width, height, arr, offset) { ...@@ -400,27 +584,27 @@ _rgbxImageData: function(x, y, width, height, arr, offset) {
data[i + 2] = arr[j + 2]; data[i + 2] = arr[j + 2];
data[i + 3] = 255; // Set Alpha 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 // 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; var i, j, sx = 0, sy = 0;
for (i=0, j=offset; i < (width * height); i+=1, j+=4) { 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; sx += 1;
if ((sx % width) === 0) { if ((sx % width) === 0) {
sx = 0; sx = 0;
sy += 1; 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; var img, i, j, data, rgb, cmap;
img = Canvas._imageData(width, height); img = that.imageData(width, height);
data = img.data; data = img.data;
cmap = Canvas.colourMap; cmap = conf.colourMap;
for (i=0, j=offset; i < (width * height * 4); i+=4, j+=1) { for (i=0, j=offset; i < (width * height * 4); i+=4, j+=1) {
rgb = cmap[arr[j]]; rgb = cmap[arr[j]];
data[i + 0] = rgb[0]; data[i + 0] = rgb[0];
...@@ -428,168 +612,47 @@ _cmapImageData: function(x, y, width, height, arr, offset) { ...@@ -428,168 +612,47 @@ _cmapImageData: function(x, y, width, height, arr, offset) {
data[i + 2] = rgb[2]; data[i + 2] = rgb[2];
data[i + 3] = 255; // Set Alpha 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; 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) { 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; sx += 1;
if ((sx % width) === 0) { if ((sx % width) === 0) {
sx = 0; sx = 0;
sy += 1; sy += 1;
} }
} }
}, };
blitImage: function(x, y, width, height, arr, offset) { that.blitImage = function(x, y, width, height, arr, offset) {
if (Canvas.true_color) { if (conf.true_color) {
Canvas._rgbxImage(x, y, width, height, arr, offset); that.rgbxImage(x, y, width, height, arr, offset);
} else { } 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(); 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; 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() { that.changeCursor = function(pixels, mask, hotx, hoty, w, h) {
return Canvas.cursor_uri;
},
changeCursor: function(pixels, mask, hotx, hoty, w, h) {
var cur = [], cmap, rgb, IHDRsz, ANDsz, XORsz, url, idx, alpha, x, y; var cur = [], cmap, rgb, IHDRsz, ANDsz, XORsz, url, idx, alpha, x, y;
//Util.Debug(">> changeCursor, x: " + hotx + ", y: " + hoty + ", w: " + w + ", h: " + h); //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"); Util.Warn("changeCursor called but no cursor data URI support");
return; return;
} }
cmap = Canvas.colourMap; cmap = conf.colourMap;
IHDRsz = 40; IHDRsz = 40;
ANDsz = w * h * 4; ANDsz = w * h * 4;
XORsz = Math.ceil( (w * h) / 8.0 ); XORsz = Math.ceil( (w * h) / 8.0 );
...@@ -623,12 +686,12 @@ changeCursor: function(pixels, mask, hotx, hoty, w, h) { ...@@ -623,12 +686,12 @@ changeCursor: function(pixels, mask, hotx, hoty, w, h) {
cur.push32le(0); cur.push32le(0);
// XOR/color data // XOR/color data
for (y = h-1; y >= 0; y--) { for (y = h-1; y >= 0; y -= 1) {
for (x = 0; x < w; x++) { for (x = 0; x < w; x += 1) {
idx = y * Math.ceil(w / 8) + Math.floor(x/8); idx = y * Math.ceil(w / 8) + Math.floor(x/8);
alpha = (mask[idx] << (x % 8)) & 0x80 ? 255 : 0; alpha = (mask[idx] << (x % 8)) & 0x80 ? 255 : 0;
if (Canvas.true_color) { if (conf.true_color) {
idx = ((w * y) + x) * 4; idx = ((w * y) + x) * 4;
cur.push(pixels[idx + 2]); // blue cur.push(pixels[idx + 2]); // blue
cur.push(pixels[idx + 1]); // green cur.push(pixels[idx + 1]); // green
...@@ -646,16 +709,20 @@ changeCursor: function(pixels, mask, hotx, hoty, w, h) { ...@@ -646,16 +709,20 @@ changeCursor: function(pixels, mask, hotx, hoty, w, h) {
} }
// AND/bitmask data (ignored, just needs to be right size) // AND/bitmask data (ignored, just needs to be right size)
for (y = 0; y < h; y++) { for (y = 0; y < h; y += 1) {
for (x = 0; x < Math.ceil(w / 8); x++) { for (x = 0; x < Math.ceil(w / 8); x += 1) {
cur.push(0x00); cur.push(0x00);
} }
} }
url = "data:image/x-icon;base64," + Base64.encode(cur); 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); //Util.Debug("<< changeCursor, cur.length: " + cur.length);
}
}; };
return constructor(); // Return the public API interface
} // End of Canvas()
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
* See README.md for usage and integration instructions. * See README.md for usage and integration instructions.
*/ */
"use strict"; "use strict";
/*jslint white: false */
/*global $, Util, RFB, Canvas, VNC_uri_prefix, Element, Fx */ /*global $, Util, RFB, Canvas, VNC_uri_prefix, Element, Fx */
var DefaultControls = { var DefaultControls = {
...@@ -16,10 +17,6 @@ settingsOpen : false, ...@@ -16,10 +17,6 @@ settingsOpen : false,
load: function(target) { load: function(target) {
var html, i, DC = DefaultControls, sheet, sheets, llevels; 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 */ /* Populate the 'target' DOM element with default controls */
if (!target) { target = 'vnc'; } if (!target) { target = 'vnc'; }
...@@ -61,7 +58,7 @@ load: function(target) { ...@@ -61,7 +58,7 @@ load: function(target) {
html += ' <option value="default">default</option>'; html += ' <option value="default">default</option>';
sheet = Util.selectStylesheet(); sheet = Util.selectStylesheet();
sheets = Util.getStylesheets(); 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 += '<option value="' + sheets[i].title + '">' + sheets[i].title + '</option>';
} }
html += ' </select> Style</li>'; html += ' </select> Style</li>';
...@@ -69,7 +66,7 @@ load: function(target) { ...@@ -69,7 +66,7 @@ load: function(target) {
// Logging selection dropdown // Logging selection dropdown
html += ' <li><select id="VNC_logging" name="vncLogging">'; html += ' <li><select id="VNC_logging" name="vncLogging">';
llevels = ['error', 'warn', 'info', 'debug']; 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 += '<option value="' + llevels[i] + '">' + llevels[i] + '</option>';
} }
html += ' </select> Logging</li>'; html += ' </select> Logging</li>';
...@@ -107,6 +104,8 @@ load: function(target) { ...@@ -107,6 +104,8 @@ load: function(target) {
DC.initSetting('logging', 'warn'); DC.initSetting('logging', 'warn');
Util.init_logging(DC.getSetting('logging')); Util.init_logging(DC.getSetting('logging'));
DC.initSetting('stylesheet', 'default'); DC.initSetting('stylesheet', 'default');
Util.selectStylesheet(null); // call twice to get around webkit bug
Util.selectStylesheet(DC.getSetting('stylesheet')); Util.selectStylesheet(DC.getSetting('stylesheet'));
/* Populate the controls if defaults are provided in the URL */ /* Populate the controls if defaults are provided in the URL */
...@@ -118,12 +117,19 @@ load: function(target) { ...@@ -118,12 +117,19 @@ load: function(target) {
DC.initSetting('true_color', true); DC.initSetting('true_color', true);
DC.initSetting('cursor', true); DC.initSetting('cursor', true);
$('VNC_screen').onmousemove = function () { DC.rfb = RFB({'target': 'VNC_canvas',
'updateState': DC.updateState,
'clipboardReceive': DC.clipReceive});
DC.rfb.init();
// Unfocus clipboard when over the VNC area // Unfocus clipboard when over the VNC area
if (! Canvas.focused) { $('VNC_screen').onmousemove = function () {
var canvas = DC.rfb.get_canvas();
if ((! canvas) || (! canvas.get_focused())) {
$('VNC_clipboard_text').blur(); $('VNC_clipboard_text').blur();
} }
}; };
}, },
// Read form control compatible setting from cookie // Read form control compatible setting from cookie
...@@ -154,7 +160,7 @@ updateSetting: function(name, value) { ...@@ -154,7 +160,7 @@ updateSetting: function(name, value) {
if (ctrl.type === 'checkbox') { if (ctrl.type === 'checkbox') {
ctrl.checked = value; ctrl.checked = value;
} else if (typeof ctrl.options !== 'undefined') { } 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) { if (ctrl.options[i].value === value) {
ctrl.selectedIndex = i; ctrl.selectedIndex = i;
break; break;
...@@ -176,7 +182,7 @@ saveSetting: function(name) { ...@@ -176,7 +182,7 @@ saveSetting: function(name) {
val = ctrl.value; val = ctrl.value;
} }
Util.createCookie(name, val); Util.createCookie(name, val);
Util.Debug("Setting saved '" + name + "=" + val + "'"); //Util.Debug("Setting saved '" + name + "=" + val + "'");
return val; return val;
}, },
...@@ -190,7 +196,7 @@ initSetting: function(name, defVal) { ...@@ -190,7 +196,7 @@ initSetting: function(name, defVal) {
val = Util.readCookie(name, defVal); val = Util.readCookie(name, defVal);
} }
DefaultControls.updateSetting(name, val); DefaultControls.updateSetting(name, val);
Util.Debug("Setting '" + name + "' initialized to '" + val + "'"); //Util.Debug("Setting '" + name + "' initialized to '" + val + "'");
return val; return val;
}, },
...@@ -208,7 +214,7 @@ clickSettingsMenu: function() { ...@@ -208,7 +214,7 @@ clickSettingsMenu: function() {
DC.updateSetting('encrypt'); DC.updateSetting('encrypt');
DC.updateSetting('base64'); DC.updateSetting('base64');
DC.updateSetting('true_color'); DC.updateSetting('true_color');
if (Canvas.isCursor()) { if (DC.rfb.get_canvas().get_cursor_uri()) {
DC.updateSetting('cursor'); DC.updateSetting('cursor');
} else { } else {
DC.updateSetting('cursor', false); DC.updateSetting('cursor', false);
...@@ -235,10 +241,11 @@ closeSettingsMenu: function() { ...@@ -235,10 +241,11 @@ closeSettingsMenu: function() {
// Disable/enable controls depending on connection state // Disable/enable controls depending on connection state
settingsDisabled: function(disabled) { settingsDisabled: function(disabled) {
var DC = DefaultControls;
$('VNC_encrypt').disabled = disabled; $('VNC_encrypt').disabled = disabled;
$('VNC_base64').disabled = disabled; $('VNC_base64').disabled = disabled;
$('VNC_true_color').disabled = disabled; $('VNC_true_color').disabled = disabled;
if (Canvas.isCursor()) { if (DC.rfb && DC.rfb.get_canvas().get_cursor_uri()) {
$('VNC_cursor').disabled = disabled; $('VNC_cursor').disabled = disabled;
} else { } else {
DefaultControls.updateSetting('cursor', false); DefaultControls.updateSetting('cursor', false);
...@@ -248,12 +255,12 @@ settingsDisabled: function(disabled) { ...@@ -248,12 +255,12 @@ settingsDisabled: function(disabled) {
// Save/apply settings when 'Apply' button is pressed // Save/apply settings when 'Apply' button is pressed
settingsApply: function() { settingsApply: function() {
Util.Debug(">> settingsApply"); //Util.Debug(">> settingsApply");
var DC = DefaultControls; var DC = DefaultControls;
DC.saveSetting('encrypt'); DC.saveSetting('encrypt');
DC.saveSetting('base64'); DC.saveSetting('base64');
DC.saveSetting('true_color'); DC.saveSetting('true_color');
if (Canvas.isCursor()) { if (DC.rfb.get_canvas().get_cursor_uri()) {
DC.saveSetting('cursor'); DC.saveSetting('cursor');
} }
DC.saveSetting('stylesheet'); DC.saveSetting('stylesheet');
...@@ -263,21 +270,21 @@ settingsApply: function() { ...@@ -263,21 +270,21 @@ settingsApply: function() {
Util.selectStylesheet(DC.getSetting('stylesheet')); Util.selectStylesheet(DC.getSetting('stylesheet'));
Util.init_logging(DC.getSetting('logging')); Util.init_logging(DC.getSetting('logging'));
Util.Debug("<< settingsApply"); //Util.Debug("<< settingsApply");
}, },
setPassword: function() { setPassword: function() {
RFB.sendPassword($('VNC_password').value); DefaultControls.rfb.sendPassword($('VNC_password').value);
return false; return false;
}, },
sendCtrlAltDel: function() { sendCtrlAltDel: function() {
RFB.sendCtrlAltDel(); DefaultControls.rfb.sendCtrlAltDel();
}, },
updateState: function(state, msg) { updateState: function(rfb, state, oldstate, msg) {
var s, sb, c, cad, klass; var s, sb, c, cad, klass;
s = $('VNC_status'); s = $('VNC_status');
sb = $('VNC_status_bar'); sb = $('VNC_status_bar');
...@@ -334,6 +341,13 @@ updateState: function(state, msg) { ...@@ -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() { connect: function() {
var host, port, password, DC = DefaultControls; var host, port, password, DC = DefaultControls;
...@@ -346,43 +360,37 @@ connect: function() { ...@@ -346,43 +360,37 @@ connect: function() {
throw("Must set host and port"); throw("Must set host and port");
} }
RFB.setEncrypt(DC.getSetting('encrypt')); DC.rfb.set_encrypt(DC.getSetting('encrypt'));
RFB.setBase64(DC.getSetting('base64')); DC.rfb.set_b64encode(DC.getSetting('base64'));
RFB.setTrueColor(DC.getSetting('true_color')); DC.rfb.set_true_color(DC.getSetting('true_color'));
RFB.setCursor(DC.getSetting('cursor')); DC.rfb.set_local_cursor(DC.getSetting('cursor'));
RFB.connect(host, port, password); DC.rfb.connect(host, port, password);
}, },
disconnect: function() { disconnect: function() {
DefaultControls.closeSettingsMenu(); DefaultControls.closeSettingsMenu();
RFB.disconnect(); DefaultControls.rfb.disconnect();
}, },
canvasBlur: function() { canvasBlur: function() {
Canvas.focused = false; DefaultControls.rfb.get_canvas().set_focused(false);
}, },
canvasFocus: function() { canvasFocus: function() {
Canvas.focused = true; DefaultControls.rfb.get_canvas().set_focused(true);
}, },
clipClear: function() { clipClear: function() {
$('VNC_clipboard_text').value = ""; $('VNC_clipboard_text').value = "";
RFB.clipboardPasteFrom(""); DefaultControls.rfb.clipboardPasteFrom("");
},
clipReceive: function(text) {
Util.Debug(">> DefaultControls.clipReceive: " + text.substr(0,40) + "...");
$('VNC_clipboard_text').value = text;
Util.Debug("<< DefaultControls.clipReceive");
}, },
clipSend: function() { clipSend: function() {
var text = $('VNC_clipboard_text').value; var text = $('VNC_clipboard_text').value;
Util.Debug(">> DefaultControls.clipSend: " + text.substr(0,40) + "..."); Util.Debug(">> DefaultControls.clipSend: " + text.substr(0,40) + "...");
RFB.clipboardPasteFrom(text); DefaultControls.rfb.clipboardPasteFrom(text);
Util.Debug("<< DefaultControls.clipSend"); Util.Debug("<< DefaultControls.clipSend");
} }
......
...@@ -7,205 +7,71 @@ ...@@ -7,205 +7,71 @@
*/ */
"use strict"; "use strict";
/*jslint white: false, nomen: false, browser: true, bitwise: false */ /*jslint white: false, browser: true, bitwise: false */
/*global window, WebSocket, Util, Canvas, VNC_native_ws, Base64, DES */ /*global window, WebSocket, Util, Canvas, VNC_native_ws, Base64, DES */
// Globals defined here
var RFB;
/* function RFB(conf) {
* RFB namespace
*/
RFB = {
/*
* 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
// 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;
} else {
RFB.encrypt = true;
}
},
setBase64: function(b64) {
if ((!b64) || (b64 in {'0':1, 'no':1, 'false':1})) {
RFB.b64encode = false;
} else {
RFB.b64encode = true;
}
Util.Debug("Set b64encode to: " + RFB.b64encode);
},
setTrueColor: function(trueColor) {
if ((!trueColor) || (trueColor in {'0':1, 'no':1, 'false':1})) {
RFB.true_color = false;
} else {
RFB.true_color = true;
}
},
setCursor: function(cursor) {
if ((!cursor) || (cursor in {'0':1, 'no':1, 'false':1})) {
RFB.local_cursor = false;
} else {
if (Canvas.isCursor()) {
RFB.local_cursor = true;
} else {
Util.Warn("Browser does not support local cursor");
}
}
},
sendPassword: function(passwd) {
RFB.password = passwd;
RFB.state = "Authentication";
setTimeout(RFB.init_msg, 1);
},
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");
/* 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");
} else {
RFB.updateState('loaded', 'noVNC ready (using Flash WebSockets emulation)');
}
}
// Initialize canvas/fxcanvas conf = conf || {}; // Configuration
try { var that = {}, // Public API interface
Canvas.init(RFB.canvasID);
} catch (exc) {
RFB.updateState('fatal', "No working Canvas");
}
// Populate encoding lookup tables // Pre-declare private functions used before definitions (jslint)
RFB.encHandlers = {}; init_vars, updateState, init_msg, normal_msg, recv_message,
RFB.encNames = {}; framebufferUpdate, show_timings,
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];
}
//Util.Debug("<< load");
},
connect: function (host, port, password) { pixelFormat, clientEncodings, fbUpdateRequest,
//Util.Debug(">> connect"); keyEvent, pointerEvent, clientCutText,
RFB.host = host; extract_data_uri, scan_tight_imgs,
RFB.port = port;
RFB.password = (password !== undefined) ? password : "";
if ((!RFB.host) || (!RFB.port)) { send_array, checkEvents, // Overridable for testing
RFB.updateState('failed', "Must set host and port");
return;
}
RFB.updateState('connect');
//Util.Debug("<< connect");
}, //
// Private RFB namespace variables
//
rfb_host = '',
rfb_port = 5900,
rfb_password = '',
disconnect: function () { rfb_state = 'disconnected',
//Util.Debug(">> disconnect"); rfb_version = 0,
RFB.updateState('disconnected', 'Disconnected'); rfb_max_version= 3.8,
//Util.Debug("<< disconnect"); rfb_auth_scheme= '',
}, rfb_shared = 1,
clipboardPasteFrom: function (text) {
if (RFB.state !== "normal") { return; }
//Util.Debug(">> clipboardPasteFrom: " + text.substr(0,40) + "...");
RFB.send_array(RFB.clientCutText(text));
//Util.Debug("<< clipboardPasteFrom");
},
// In preference order
encodings = [
['COPYRECT', 0x01 ],
['TIGHT_PNG', -260 ],
['HEXTILE', 0x05 ],
['RRE', 0x02 ],
['RAW', 0x00 ],
['DesktopSize', -223 ],
['Cursor', -239 ],
/* // Psuedo-encoding settings
* Private variables and methods ['JPEG_quality_lo', -32 ],
*/ //['JPEG_quality_hi', -23 ],
['compress_lo', -255 ]
//['compress_hi', -247 ]
],
ws : null, // Web Socket object encHandlers = {},
sendID : null, encNames = {},
scanID : null, // TIGHT_PNG render image scanner
// Receive and send queues ws = null, // Web Socket object
RQ : [], // Receive Queue canvas = null, // Canvas object
SQ : "", // Send Queue sendID = null, // Send Queue check timer
encHandlers : {}, // Receive and send queues
encNames : {}, RQ = [], // Receive Queue
SQ = "", // Send Queue
// Frame buffer update state // Frame buffer update state
FBU : { FBU = {
rects : 0, rects : 0,
subrects : 0, // RRE and HEXTILE subrects : 0, // RRE and HEXTILE
lines : 0, // RAW lines : 0, // RAW
...@@ -219,31 +85,22 @@ FBU : { ...@@ -219,31 +85,22 @@ FBU : {
subencoding : -1, subencoding : -1,
background : null, background : null,
imgs : [] // TIGHT_PNG image queue imgs : [] // TIGHT_PNG image queue
}, },
fb_Bpp : 4, fb_Bpp = 4,
fb_depth : 3, fb_depth = 3,
fb_width = 0,
max_version : 3.8, fb_height = 0,
version : 0, fb_name = "",
auth_scheme : '',
state : 'disconnected', cuttext = 'none', // ServerCutText wait state
cuttext : 'none', // ServerCutText wait state cuttext_length = 0,
ct_length : 0,
scan_imgs_rate = 100,
shared : 1, last_req_time = 0,
check_rate : 217, rre_chunk_sz = 100,
scan_imgs_rate : 100,
req_rate : 1413, timing = {
last_req : 0,
canvasID : 'VNC_canvas',
fb_width : 0,
fb_height : 0,
fb_name : "",
rre_chunk : 100,
timing : {
last_fbu : 0, last_fbu : 0,
fbu_total : 0, fbu_total : 0,
fbu_total_cnt : 0, fbu_total_cnt : 0,
...@@ -261,1368 +118,1539 @@ timing : { ...@@ -261,1368 +118,1539 @@ timing : {
h_fbus : 0, h_fbus : 0,
h_bytes : 0, h_bytes : 0,
h_pixels : 0 h_pixels : 0
}, },
/* Mouse state */ test_mode = false,
mouse_buttonmask : 0,
mouse_arr : [],
/* /* Mouse state */
* Server message handlers mouse_buttonMask = 0,
*/ mouse_arr = [];
/* RFB/VNC initialisation */
init_msg: function () {
//Util.Debug(">> init_msg [RFB.state '" + RFB.state + "']");
var RQ = RFB.RQ, strlen, reason, reason_len, sversion, cversion, //
i, types, num_types, challenge, response, bpp, depth, // Configuration settings
big_endian, true_color, name_length; //
//Util.Debug("RQ (" + RQ.length + ") " + RQ); // VNC viewport rendering Canvas
switch (RFB.state) { Util.conf_default(conf, that, 'target', 'VNC_canvas');
case 'ProtocolVersion' : Util.conf_default(conf, that, 'encrypt', false, true);
if (RQ.length < 12) { Util.conf_default(conf, that, 'true_color', true, true);
RFB.updateState('failed', // false means UTF-8 on the wire
"Disconnected: incomplete protocol version"); Util.conf_default(conf, that, 'b64encode', true, true);
return; Util.conf_default(conf, that, 'local_cursor', true, true);
}
sversion = RQ.shiftStr(12).substr(4,7); // time to wait for connection
Util.Info("Server ProtocolVersion: " + sversion); Util.conf_default(conf, that, 'connectTimeout', 2000);
switch (sversion) { // frequency to check for send/receive
case "003.003": RFB.version = 3.3; break; Util.conf_default(conf, that, 'check_rate', 217);
case "003.007": RFB.version = 3.7; break; // frequency to send frameBufferUpdate requests
case "003.008": RFB.version = 3.8; break; Util.conf_default(conf, that, 'fbu_req_rate', 1413);
default:
RFB.updateState('failed', // state update callback
"Invalid server version " + sversion); Util.conf_default(conf, that, 'updateState', function () {
return; 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");
} }
if (RFB.version > RFB.max_version) {
RFB.version = RFB.max_version;
} }
};
RFB.sendID = setInterval(function() { that.get_canvas = function() {
/* return canvas;
* 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 = ""; // 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];
} }
} else { // Initialize canvas
Util.Debug("Delaying send"); try {
canvas = new Canvas({'target': conf.target});
} catch (exc) {
Util.Error("Canvas exception: " + exc);
updateState('fatal', "No working Canvas");
} }
}, 50);
cversion = "00" + parseInt(RFB.version,10) + //Util.Debug("<< init");
".00" + ((RFB.version * 10) % 10); return that; // Return the public API interface
RFB.send_string("RFB " + cversion + "\n"); }
RFB.updateState('Security', "Sent ProtocolVersion: " + sversion);
break;
case 'Security' : function init_ws() {
if (RFB.version >= 3.7) { //Util.Debug(">> init_ws");
num_types = RQ.shift8();
if (num_types === 0) { var uri = "", vars = [];
strlen = RQ.shift32(); if (conf.encrypt) {
reason = RQ.shiftStr(strlen); uri = "wss://";
RFB.updateState('failed', } else {
"Disconnected: security failure: " + reason); uri = "ws://";
return;
}
RFB.auth_scheme = 0;
types = RQ.shiftBytes(num_types);
for (i=0; i < types.length; i+=1) {
if ((types[i] > RFB.auth_scheme) && (types[i] < 3)) {
RFB.auth_scheme = types[i];
} }
uri += rfb_host + ":" + rfb_port + "/";
if (conf.b64encode) {
vars.push("b64encode");
} }
if (RFB.auth_scheme === 0) { if (vars.length > 0) {
RFB.updateState('failed', uri += "?" + vars.join("&");
"Disconnected: unsupported security types: " + types);
return;
} }
Util.Info("connecting to " + uri);
ws = new WebSocket(uri);
RFB.send_array([RFB.auth_scheme]); ws.onmessage = recv_message;
ws.onopen = function(e) {
Util.Debug(">> WebSocket.onopen");
if (rfb_state === "connect") {
updateState('ProtocolVersion', "Starting VNC handshake");
} else { } else {
if (RQ.length < 4) { updateState('failed', "Got unexpected WebSockets connection");
RFB.updateState('failed', "Invalid security frame");
return;
} }
RFB.auth_scheme = RQ.shift32(); 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');
} }
RFB.updateState('Authentication', Util.Debug("<< WebSocket.onclose");
"Authenticating using scheme: " + RFB.auth_scheme); };
// Fall through ws.onerror = function(e) {
Util.Debug(">> WebSocket.onerror");
updateState('failed', "WebSocket error");
Util.Debug("<< WebSocket.onerror");
};
case 'Authentication' : setTimeout(function () {
//Util.Debug("Security auth scheme: " + RFB.auth_scheme); if (ws.readyState === WebSocket.CONNECTING) {
switch (RFB.auth_scheme) { updateState('failed', "Connect timeout");
case 0: // connection failed
if (RQ.length < 4) {
//Util.Debug(" waiting for auth reason bytes");
return;
}
strlen = RQ.shift32();
reason = RQ.shiftStr(strlen);
RFB.updateState('failed',
"Disconnected: auth failure: " + reason);
return;
case 1: // no authentication
// RFB.send_array([RFB.shared]); // ClientInitialisation
RFB.updateState('SecurityResult');
break;
case 2: // VNC authentication
if (RFB.password.length === 0) {
RFB.updateState('password', "Password Required");
return;
} }
if (RQ.length < 16) { }, conf.connectTimeout);
//Util.Debug(" waiting for auth challenge bytes");
//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
//
/*
* 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; return;
} }
challenge = RQ.shiftBytes(16);
//Util.Debug("Password: " + RFB.password);
//Util.Debug("Challenge: " + challenge +
// " (" + challenge.length + ")");
response = RFB.DES(RFB.password, challenge);
//Util.Debug("Response: " + response +
// " (" + response.length + ")");
//Util.Debug("Sending DES encrypted auth response"); if (oldstate === 'fatal') {
RFB.send_array(response); Util.Error("Fatal error, cannot continue");
RFB.updateState('SecurityResult');
break;
default:
RFB.updateState('failed',
"Disconnected: unsupported auth scheme: " +
RFB.auth_scheme);
return;
} }
break;
case 'SecurityResult' : if ((state === 'failed') || (state === 'fatal')) {
if (RQ.length < 4) { func = Util.Error;
RFB.updateState('failed', "Invalid VNC auth response"); } else {
return; func = Util.Warn;
} }
switch (RQ.shift32()) {
case 0: // OK cmsg = typeof(statusMsg) !== 'undefined' ? (" Msg: " + statusMsg) : "";
RFB.updateState('ServerInitialisation', "Authentication OK"); func("New state '" + state + "', was '" + oldstate + "'." + cmsg);
break;
case 1: // failed if ((oldstate === 'failed') && (state === 'disconnected')) {
if (RFB.version >= 3.8) { // Do disconnect action, but stay in failed state.
reason_len = RQ.shift32(); rfb_state = 'failed';
reason = RQ.shiftStr(reason_len);
RFB.updateState('failed', reason);
} else { } else {
RFB.updateState('failed', "Authentication failed"); rfb_state = state;
} }
return;
case 2: // too-many switch (state) {
RFB.updateState('failed', case 'loaded':
"Disconnected: too many auth attempts"); case 'disconnected':
return;
if (sendID) {
clearInterval(sendID);
sendID = null;
} }
RFB.send_array([RFB.shared]); // ClientInitialisation
break;
case 'ServerInitialisation' : if (ws) {
if (RQ.length < 24) { if (ws.readyState === WebSocket.OPEN) {
RFB.updateState('failed', "Invalid server initialisation"); ws.close();
return; }
ws.onmessage = function (e) { return; };
} }
/* Screen size */ if (canvas && canvas.getContext()) {
RFB.fb_width = RQ.shift16(); canvas.stop();
RFB.fb_height = RQ.shift16(); if (! /__debug__$/i.test(document.location.href)) {
canvas.clear();
}
}
/* PIXEL_FORMAT */ show_timings();
bpp = RQ.shift8();
depth = RQ.shift8();
big_endian = RQ.shift8();
true_color = RQ.shift8();
Util.Info("Screen: " + RFB.fb_width + "x" + RFB.fb_height + break;
", bpp: " + bpp + ", depth: " + depth +
", big_endian: " + big_endian +
", true_color: " + true_color);
/* Connection name/title */
RQ.shiftStr(12);
name_length = RQ.shift32();
RFB.fb_name = RQ.shiftStr(name_length);
Canvas.resize(RFB.fb_width, RFB.fb_height, RFB.true_color); case 'connect':
Canvas.start(RFB.keyPress, RFB.mouseButton, RFB.mouseMove); init_vars();
if (RFB.true_color) { if ((ws) && (ws.readyState === WebSocket.OPEN)) {
RFB.fb_Bpp = 4; ws.close();
RFB.fb_depth = 3;
} else {
RFB.fb_Bpp = 1;
RFB.fb_depth = 1;
} }
init_ws(); // onopen transitions to 'ProtocolVersion'
response = RFB.pixelFormat(); break;
response = response.concat(RFB.clientEncodings());
response = response.concat(RFB.fbUpdateRequest(0));
RFB.timing.fbu_rt_start = (new Date()).getTime();
RFB.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);
if (RFB.encrypt) { case 'password':
RFB.updateState('normal', "Connected (encrypted) to: " + RFB.fb_name); // Ignore password state by default
} else { break;
RFB.updateState('normal', "Connected (unencrypted) to: " + RFB.fb_name);
case 'normal':
if ((oldstate === 'disconnected') || (oldstate === 'failed')) {
Util.Error("Invalid transition from 'disconnected' or 'failed' to 'normal'");
} }
break; 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.");
} }
//Util.Debug("<< init_msg");
},
if ((ws) && (ws.readyState === WebSocket.OPEN)) {
ws.close();
}
// Make sure we transition to disconnected
setTimeout(function() { updateState('disconnected'); }, 50);
/* Normal RFB/VNC server messages */ break;
normal_msg: function () {
//Util.Debug(">> normal_msg");
var RQ = RFB.RQ, ret = true, msg_type,
c, first_colour, num_colours, red, green, blue;
//Util.Debug(">> msg RQ.slice(0,10): " + RQ.slice(0,20)); default:
//Util.Debug(">> msg RQ.slice(-10,-1): " + RQ.slice(RQ.length-10,RQ.length)); // Invalid state transition
if (RFB.FBU.rects > 0) {
msg_type = 0; }
} else if (RFB.cuttext !== 'none') {
msg_type = 3; if ((oldstate === 'failed') && (state === 'disconnected')) {
// Leave the failed message
conf.updateState(that, state, oldstate);
} else { } else {
msg_type = RQ.shift8(); conf.updateState(that, state, oldstate, statusMsg);
} }
switch (msg_type) { };
case 0: // FramebufferUpdate
ret = RFB.framebufferUpdate(); // false means need more data function encode_message(arr) {
break; if (conf.b64encode) {
case 1: // SetColourMapEntries /* base64 encode */
Util.Debug("SetColourMapEntries"); SQ = SQ + Base64.encode(arr);
RQ.shift8(); // Padding } else {
first_colour = RQ.shift16(); // First colour /* UTF-8 encode. 0 -> 256 to avoid WebSockets framing */
num_colours = RQ.shift16(); SQ = SQ + arr.map(function (num) {
for (c=0; c < num_colours; c+=1) { if (num === 0) {
red = RQ.shift16(); return String.fromCharCode(256);
//Util.Debug("red before: " + red); } else {
red = parseInt(red / 256, 10); return String.fromCharCode(num);
//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];
} }
Util.Info("Registered " + num_colours + " colourMap entries"); } ).join('');
//Util.Debug("colourMap: " + Canvas.colourMap);
break;
case 2: // Bell
Util.Warn("Bell (unsupported)");
break;
case 3: // ServerCutText
Util.Debug("ServerCutText");
Util.Debug("RQ:" + RQ.slice(0,20));
if (RFB.cuttext === 'none') {
RFB.cuttext = 'header';
} }
if (RFB.cuttext === 'header') { }
if (RQ.length < 7) {
//Util.Debug("waiting for ServerCutText header"); function decode_message(data) {
return false; var i, length;
//Util.Debug(">> decode_message: " + data);
if (conf.b64encode) {
/* base64 decode */
RQ = 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);
} }
RQ.shiftBytes(3); // Padding
RFB.ct_length = RQ.shift32();
} }
RFB.cuttext = 'bytes'; //Util.Debug(">> decode_message, RQ: " + RQ);
if (RQ.length < RFB.ct_length) { }
//Util.Debug("waiting for ServerCutText bytes");
return false; 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);
} }
RFB.clipboardCopyTo(RQ.shiftStr(RFB.ct_length));
RFB.cuttext = 'none';
break; break;
default: default:
RFB.updateState('failed', init_msg();
"Disconnected: illegal server message type " + msg_type);
Util.Debug("RQ.slice(0,30):" + RQ.slice(0,30));
break; break;
} }
//Util.Debug("<< normal_msg"); }
return ret;
},
framebufferUpdate: function() { recv_message = function(e) {
var RQ = RFB.RQ, FBU = RFB.FBU, timing = RFB.timing, //Util.Debug(">> recv_message");
now, fbu_rt_diff, last_bytes, last_rects,
ret = true;
if (FBU.rects === 0) { try {
//Util.Debug("New FBU: RQ.slice(0,20): " + RQ.slice(0,20)); decode_message(e.data);
if (RQ.length < 3) { if (RQ.length > 0) {
RQ.unshift(0); // FBU msg_type handle_message();
Util.Debug(" waiting for FBU header bytes"); } else {
return false; Util.Debug("Ignoring empty message");
}
RQ.shift8();
FBU.rects = RQ.shift16();
//Util.Debug("FramebufferUpdate, rects:" + FBU.rects);
FBU.bytes = 0;
timing.cur_fbu = 0;
timing.h_fbus += 1;
if (timing.fbu_rt_start > 0) {
now = (new Date()).getTime();
Util.Info("First FBU latency: " + (now - timing.fbu_rt_start));
}
} }
} catch (exc) {
while (FBU.rects > 0) { if (typeof exc.stack !== 'undefined') {
if (RFB.state !== "normal") { Util.Warn("recv_message, caught exception: " + exc.stack);
return false; } else if (typeof exc.description !== 'undefined') {
Util.Warn("recv_message, caught exception: " + exc.description);
} else {
Util.Warn("recv_message, caught exception:" + exc);
} }
if (RQ.length < FBU.bytes) { if (typeof exc.name !== 'undefined') {
return false; updateState('failed', exc.name + ": " + exc.message);
} else {
updateState('failed', exc);
} }
if (FBU.bytes === 0) {
if (RQ.length < 12) {
//Util.Debug(" waiting for rect header bytes");
return false;
} }
/* New FramebufferUpdate */ //Util.Debug("<< recv_message");
FBU.x = RQ.shift16(); };
FBU.y = RQ.shift16();
FBU.width = RQ.shift16();
FBU.height = RQ.shift16();
FBU.encoding = parseInt(RQ.shift32(), 10);
timing.h_bytes += 12;
if (RFB.encNames[FBU.encoding]) { // overridable for testing
// Debug: send_array = function(arr) {
/* //Util.Debug(">> send_array: " + arr);
var msg = "FramebufferUpdate rects:" + FBU.rects; encode_message(arr);
msg += " x: " + FBU.x + " y: " + FBU.y if (ws.bufferedAmount === 0) {
msg += " width: " + FBU.width + " height: " + FBU.height; //Util.Debug("arr: " + arr);
msg += " encoding:" + FBU.encoding; //Util.Debug("SQ: " + SQ);
msg += "(" + RFB.encNames[FBU.encoding] + ")"; ws.send(SQ);
msg += ", RQ.length: " + RQ.length; SQ = "";
Util.Debug(msg);
*/
} else { } else {
RFB.updateState('failed', Util.Debug("Delaying send");
"Disconnected: unsupported encoding " +
FBU.encoding);
return false;
}
} }
};
timing.last_fbu = (new Date()).getTime(); function send_string(str) {
last_bytes = RQ.length; //Util.Debug(">> send_string: " + str);
last_rects = FBU.rects; send_array(str.split('').map(
function (chr) { return chr.charCodeAt(0); } ) );
}
// false ret means need more data function genDES(password, challenge) {
ret = RFB.encHandlers[FBU.encoding](); var i, passwd, response;
passwd = [];
response = challenge.slice();
for (i=0; i < password.length; i += 1) {
passwd.push(password.charCodeAt(i));
}
now = (new Date()).getTime(); DES.setKeys(passwd);
timing.cur_fbu += (now - timing.last_fbu); DES.encrypt(response, 0, response, 0);
timing.h_bytes += last_bytes-RQ.length; DES.encrypt(response, 8, response, 8);
return response;
}
if (FBU.rects < last_rects) { function flushClient() {
// Some work was done if (mouse_arr.length > 0) {
timing.h_rects += last_rects-FBU.rects; //send_array(mouse_arr.concat(fbUpdateRequest(1)));
timing.h_pixels += FBU.width*FBU.height; send_array(mouse_arr);
} setTimeout(function() {
send_array(fbUpdateRequest(1));
}, 50);
if (FBU.rects === 0) { mouse_arr = [];
if (((FBU.width === RFB.fb_width) && return true;
(FBU.height === RFB.fb_height)) || } else {
(timing.fbu_rt_start > 0)) { return false;
timing.full_fbu_total += timing.cur_fbu;
timing.full_fbu_cnt += 1;
Util.Info("Timing of full FBU, cur: " +
timing.cur_fbu + ", total: " +
timing.full_fbu_total + ", cnt: " +
timing.full_fbu_cnt + ", avg: " +
(timing.full_fbu_total /
timing.full_fbu_cnt));
} }
if (timing.fbu_rt_start > 0) { }
fbu_rt_diff = now - timing.fbu_rt_start;
timing.fbu_rt_total += fbu_rt_diff; // overridable for testing
timing.fbu_rt_cnt += 1; checkEvents = function() {
Util.Info("full FBU round-trip, cur: " + var now;
fbu_rt_diff + ", total: " + if (rfb_state === 'normal') {
timing.fbu_rt_total + ", cnt: " + if (! flushClient()) {
timing.fbu_rt_cnt + ", avg: " + now = new Date().getTime();
(timing.fbu_rt_total / if (now > last_req_time + conf.fbu_req_rate) {
timing.fbu_rt_cnt)); last_req_time = now;
timing.fbu_rt_start = 0; send_array(fbUpdateRequest(1));
} }
} }
} }
return ret; setTimeout(checkEvents, conf.check_rate);
}, };
/*
* FramebufferUpdate encodings
*/
display_raw: function () {
//Util.Debug(">> display_raw");
var RQ = RFB.RQ, FBU = RFB.FBU, cur_y, cur_height; function keyPress(keysym, down) {
var arr;
if (FBU.lines === 0) { arr = keyEvent(keysym, down);
FBU.lines = FBU.height; arr = arr.concat(fbUpdateRequest(1));
} send_array(arr);
FBU.bytes = FBU.width * RFB.fb_Bpp; // At least a line }
if (RQ.length < FBU.bytes) {
//Util.Debug(" waiting for " +
// (FBU.bytes - RQ.length) + " RAW bytes");
return false;
}
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);
FBU.lines -= cur_height;
if (FBU.lines > 0) { function mouseButton(x, y, down, bmask) {
FBU.bytes = FBU.width * RFB.fb_Bpp; // At least another line if (down) {
mouse_buttonMask |= bmask;
} else { } else {
FBU.rects -= 1; mouse_buttonMask ^= bmask;
FBU.bytes = 0;
} }
return true; mouse_arr = mouse_arr.concat( pointerEvent(x, y) );
}, flushClient();
}
display_copy_rect: function () { function mouseMove(x, y) {
//Util.Debug(">> display_copy_rect"); //Util.Debug('>> mouseMove ' + x + "," + y);
mouse_arr = mouse_arr.concat( pointerEvent(x, y) );
}
var RQ = RFB.RQ, FBU = RFB.FBU, old_x, old_y;
if (RQ.length < 4) { function update_timings() {
//Util.Debug(" waiting for " + var now, offset;
// (FBU.bytes - RQ.length) + " COPYRECT bytes"); now = (new Date()).getTime();
return false; timing.history.push([now,
} timing.h_fbus,
old_x = RQ.shift16(); timing.h_rects,
old_y = RQ.shift16(); timing.h_bytes,
Canvas.copyImage(old_x, old_y, FBU.x, FBU.y, FBU.width, FBU.height); timing.h_pixels]);
FBU.rects -= 1; timing.h_fbus = 0;
FBU.bytes = 0; timing.h_rects = 0;
return true; timing.h_bytes = 0;
}, timing.h_pixels = 0;
if ((rfb_state !== 'disconnected') && (rfb_state !== 'failed')) {
display_rre: function () { // Try for every second
//Util.Debug(">> display_rre (" + RFB.RQ.length + " bytes)"); offset = (now - timing.history_start) % 1000;
var RQ = RFB.RQ, FBU = RFB.FBU, color, x, y, width, height, chunk; if (offset < 500) {
if (FBU.subrects === 0) { setTimeout(update_timings, 1000 - offset);
if (RQ.length < 4 + RFB.fb_Bpp) { } else {
//Util.Debug(" waiting for " + setTimeout(update_timings, 2000 - offset);
// (4 + RFB.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);
} }
while ((FBU.subrects > 0) && (RQ.length >= (RFB.fb_Bpp + 8))) {
color = RQ.shiftBytes(RFB.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);
FBU.subrects -= 1;
} }
//Util.Debug(" display_rre: rects: " + FBU.rects + }
// ", FBU.subrects: " + FBU.subrects);
if (FBU.subrects > 0) { show_timings = function() {
chunk = Math.min(RFB.rre_chunk, FBU.subrects); var i, history, msg,
FBU.bytes = (RFB.fb_Bpp + 8) * chunk; delta, tot_time = 0, tot_fbus = 0, tot_rects = 0,
} else { tot_bytes = 0, tot_pixels = 0;
FBU.rects -= 1; if (timing.history_start === 0) { return; }
FBU.bytes = 0; //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];
msg += " " + delta.toFixed(3);
msg += ": " + history.slice(1) + "\n";
} }
//Util.Debug("<< display_rre, FBU.bytes: " + FBU.bytes); msg += "\nTotals:\n";
return true; 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");
};
display_hextile: function() { //
//Util.Debug(">> display_hextile"); // Server message handlers
var RQ = RFB.RQ, FBU = RFB.FBU, //
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) { // RFB/VNC initialisation message handler
FBU.tiles_x = Math.ceil(FBU.width/16); init_msg = function() {
FBU.tiles_y = Math.ceil(FBU.height/16); //Util.Debug(">> init_msg [rfb_state '" + rfb_state + "']");
FBU.total_tiles = FBU.tiles_x * FBU.tiles_y;
FBU.tiles = FBU.total_tiles;
}
/* FBU.bytes comes in as 1, RQ.length at least 1 */ var strlen, reason, reason_len, sversion, cversion,
while (FBU.tiles > 0) { i, types, num_types, challenge, response, bpp, depth,
FBU.bytes = 1; big_endian, true_color, name_length;
if (RQ.length < FBU.bytes) {
//Util.Debug(" waiting for HEXTILE subencoding byte"); //Util.Debug("RQ (" + RQ.length + ") " + RQ);
return false; switch (rfb_state) {
case 'ProtocolVersion' :
if (RQ.length < 12) {
updateState('failed',
"Disconnected: incomplete protocol version");
return;
} }
subencoding = RQ[0]; // Peek sversion = RQ.shiftStr(12).substr(4,7);
if (subencoding > 30) { // Raw Util.Info("Server ProtocolVersion: " + sversion);
RFB.updateState('failed', switch (sversion) {
"Disconnected: illegal hextile subencoding " + subencoding); case "003.003": rfb_version = 3.3; break;
//Util.Debug("RQ.slice(0,30):" + RQ.slice(0,30)); case "003.007": rfb_version = 3.7; break;
return false; case "003.008": rfb_version = 3.8; break;
default:
updateState('failed',
"Invalid server version " + sversion);
return;
}
if (rfb_version > rfb_max_version) {
rfb_version = rfb_max_version;
} }
subrects = 0;
cur_tile = FBU.total_tiles - FBU.tiles;
tile_x = cur_tile % FBU.tiles_x;
tile_y = Math.floor(cur_tile / FBU.tiles_x);
x = FBU.x + tile_x * 16;
y = FBU.y + tile_y * 16;
w = Math.min(16, (FBU.x + FBU.width) - x);
h = Math.min(16, (FBU.y + FBU.height) - y);
/* Figure out how much we are expecting */ if (! test_mode) {
if (subencoding & 0x01) { // Raw sendID = setInterval(function() {
//Util.Debug(" Raw subencoding"); // Send updates either at a rate of one update
FBU.bytes += w * h * RFB.fb_Bpp; // every 50ms, or whatever slower rate the network
// can handle.
if (ws.bufferedAmount === 0) {
if (SQ) {
ws.send(SQ);
SQ = "";
}
} else { } else {
if (subencoding & 0x02) { // Background Util.Debug("Delaying send");
FBU.bytes += RFB.fb_Bpp;
} }
if (subencoding & 0x04) { // Foreground }, 50);
FBU.bytes += RFB.fb_Bpp;
} }
if (subencoding & 0x08) { // AnySubrects
FBU.bytes += 1; // Since we aren't shifting it off cversion = "00" + parseInt(rfb_version,10) +
if (RQ.length < FBU.bytes) { ".00" + ((rfb_version * 10) % 10);
/* Wait for subrects byte */ send_string("RFB " + cversion + "\n");
//Util.Debug(" waiting for hextile subrects header byte"); updateState('Security', "Sent ProtocolVersion: " + sversion);
return false; break;
case 'Security' :
if (rfb_version >= 3.7) {
num_types = RQ.shift8();
if (num_types === 0) {
strlen = RQ.shift32();
reason = RQ.shiftStr(strlen);
updateState('failed',
"Disconnected: security failure: " + reason);
return;
} }
subrects = RQ[FBU.bytes-1]; // Peek rfb_auth_scheme = 0;
if (subencoding & 0x10) { // SubrectsColoured types = RQ.shiftBytes(num_types);
FBU.bytes += subrects * (RFB.fb_Bpp + 2); Util.Debug("Server security types: " + types);
} else { for (i=0; i < types.length; i+=1) {
FBU.bytes += subrects * 2; if ((types[i] > rfb_auth_scheme) && (types[i] < 3)) {
rfb_auth_scheme = types[i];
} }
} }
if (rfb_auth_scheme === 0) {
updateState('failed',
"Disconnected: unsupported security types: " + types);
return;
} }
//Util.Debug(" tile:" + cur_tile + "/" + (FBU.total_tiles - 1) + send_array([rfb_auth_scheme]);
// ", subencoding:" + subencoding + } else {
// "(last: " + FBU.lastsubencoding + "), subrects:" + if (RQ.length < 4) {
// subrects + ", tile:" + tile_x + "," + tile_y + updateState('failed', "Invalid security frame");
// " [" + x + "," + y + "]@" + w + "x" + h + return;
// ", d.length:" + RQ.length + ", bytes:" + FBU.bytes + }
// " last:" + RQ.slice(FBU.bytes-10, FBU.bytes) + rfb_auth_scheme = RQ.shift32();
// " next:" + RQ.slice(FBU.bytes-1, FBU.bytes+10));
if (RQ.length < FBU.bytes) {
//Util.Debug(" waiting for " +
// (FBU.bytes - RQ.length) + " hextile bytes");
return false;
} }
updateState('Authentication',
"Authenticating using scheme: " + rfb_auth_scheme);
init_msg(); // Recursive fallthrough (workaround JSLint complaint)
break;
/* We know the encoding and have a whole tile */ case 'Authentication' :
FBU.subencoding = RQ[0]; //Util.Debug("Security auth scheme: " + rfb_auth_scheme);
idx = 1; switch (rfb_auth_scheme) {
if (FBU.subencoding === 0) { case 0: // connection failed
if (FBU.lastsubencoding & 0x01) { if (RQ.length < 4) {
/* Weird: ignore blanks after RAW */ //Util.Debug(" waiting for auth reason bytes");
Util.Debug(" Ignoring blank after RAW"); return;
} else {
Canvas.fillRect(x, y, w, h, FBU.background);
} }
} else if (FBU.subencoding & 0x01) { // Raw strlen = RQ.shift32();
Canvas.blitImage(x, y, w, h, RQ, idx); reason = RQ.shiftStr(strlen);
} else { updateState('failed',
if (FBU.subencoding & 0x02) { // Background "Disconnected: auth failure: " + reason);
FBU.background = RQ.slice(idx, idx + RFB.fb_Bpp); return;
idx += RFB.fb_Bpp; case 1: // no authentication
updateState('SecurityResult');
break;
case 2: // VNC authentication
if (rfb_password.length === 0) {
updateState('password', "Password Required");
return;
} }
if (FBU.subencoding & 0x04) { // Foreground if (RQ.length < 16) {
FBU.foreground = RQ.slice(idx, idx + RFB.fb_Bpp); //Util.Debug(" waiting for auth challenge bytes");
idx += RFB.fb_Bpp; return;
} }
challenge = RQ.shiftBytes(16);
//Util.Debug("Password: " + rfb_password);
//Util.Debug("Challenge: " + challenge +
// " (" + challenge.length + ")");
response = genDES(rfb_password, challenge);
//Util.Debug("Response: " + response +
// " (" + response.length + ")");
tile = Canvas.getTile(x, y, w, h, FBU.background); //Util.Debug("Sending DES encrypted auth response");
if (FBU.subencoding & 0x08) { // AnySubrects send_array(response);
subrects = RQ[idx]; updateState('SecurityResult');
idx += 1; break;
for (s = 0; s < subrects; s += 1) { default:
if (FBU.subencoding & 0x10) { // SubrectsColoured updateState('failed',
color = RQ.slice(idx, idx + RFB.fb_Bpp); "Disconnected: unsupported auth scheme: " +
idx += RFB.fb_Bpp; rfb_auth_scheme);
} else { return;
color = FBU.foreground;
} }
xy = RQ[idx]; break;
idx += 1;
sx = (xy >> 4);
sy = (xy & 0x0f);
wh = RQ[idx];
idx += 1;
sw = (wh >> 4) + 1;
sh = (wh & 0x0f) + 1;
Canvas.setSubTile(tile, sx, sy, sw, sh, color); case 'SecurityResult' :
} if (RQ.length < 4) {
updateState('failed', "Invalid VNC auth response");
return;
} }
Canvas.putTile(tile); switch (RQ.shift32()) {
case 0: // OK
updateState('ServerInitialisation', "Authentication OK");
break;
case 1: // failed
if (rfb_version >= 3.8) {
reason_len = RQ.shift32();
reason = RQ.shiftStr(reason_len);
updateState('failed', reason);
} else {
updateState('failed', "Authentication failed");
} }
RQ.shiftBytes(FBU.bytes); return;
FBU.lastsubencoding = FBU.subencoding; case 2: // too-many
FBU.bytes = 0; updateState('failed',
FBU.tiles -= 1; "Disconnected: too many auth attempts");
return;
} }
send_array([rfb_shared]); // ClientInitialisation
break;
if (FBU.tiles === 0) { case 'ServerInitialisation' :
FBU.rects -= 1; if (RQ.length < 24) {
updateState('failed', "Invalid server initialisation");
return;
} }
//Util.Debug("<< display_hextile"); /* Screen size */
return true; fb_width = RQ.shift16();
}, fb_height = RQ.shift16();
display_tight_png: function() { /* PIXEL_FORMAT */
//Util.Debug(">> display_tight_png"); bpp = RQ.shift8();
var RQ = RFB.RQ, FBU = RFB.FBU, depth = RQ.shift8();
ctl, cmode, clength, getCLength, color, img; big_endian = RQ.shift8();
//Util.Debug(" FBU.rects: " + FBU.rects); true_color = RQ.shift8();
//Util.Debug(" RQ.length: " + RQ.length);
//Util.Debug(" RQ.slice(0,20): " + RQ.slice(0,20));
Util.Info("Screen: " + fb_width + "x" + fb_height +
", bpp: " + bpp + ", depth: " + depth +
", big_endian: " + big_endian +
", true_color: " + true_color);
FBU.bytes = 1; // compression-control byte /* Connection name/title */
if (RQ.length < FBU.bytes) { RQ.shiftStr(12);
Util.Debug(" waiting for TIGHT compression-control byte"); name_length = RQ.shift32();
return false; fb_name = RQ.shiftStr(name_length);
}
// Get 'compact length' header and data size canvas.resize(fb_width, fb_height, conf.true_color);
getCLength = function (arr, offset) { canvas.start(keyPress, mouseButton, mouseMove);
var header = 1, data = 0;
data += arr[offset + 0] & 0x7f; if (conf.true_color) {
if (arr[offset + 0] & 0x80) { fb_Bpp = 4;
header += 1; fb_depth = 3;
data += (arr[offset + 1] & 0x7f) << 7; } else {
if (arr[offset + 1] & 0x80) { fb_Bpp = 1;
header += 1; fb_depth = 1;
data += arr[offset + 2] << 14;
}
} }
return [header, data];
};
ctl = RQ[0]; response = pixelFormat();
switch (ctl >> 4) { response = response.concat(clientEncodings());
case 0x08: cmode = "fill"; break; response = response.concat(fbUpdateRequest(0));
case 0x09: cmode = "jpeg"; break; timing.fbu_rt_start = (new Date()).getTime();
case 0x0A: cmode = "png"; break; send_array(response);
default: throw("Illegal basic compression received, ctl: " + ctl);
/* Start pushing/polling */
setTimeout(checkEvents, conf.check_rate);
setTimeout(scan_tight_imgs, scan_imgs_rate);
timing.history_start = (new Date()).getTime();
setTimeout(update_timings, 1000);
if (conf.encrypt) {
updateState('normal', "Connected (encrypted) to: " + fb_name);
} else {
updateState('normal', "Connected (unencrypted) to: " + fb_name);
} }
switch (cmode) { break;
// fill uses fb_depth because TPIXELs drop the padding byte
case "fill": FBU.bytes += RFB.fb_depth; break; // TPIXEL
case "jpeg": FBU.bytes += 3; break; // max clength
case "png": FBU.bytes += 3; break; // max clength
} }
//Util.Debug("<< init_msg");
};
if (RQ.length < FBU.bytes) {
Util.Debug(" waiting for TIGHT " + cmode + " bytes");
return false;
}
//Util.Debug(" RQ.slice(0,20): " + RFB.RQ.slice(0,20) + " (" + RFB.RQ.length + ")"); /* Normal RFB/VNC server message handler */
//Util.Debug(" cmode: " + cmode); normal_msg = function() {
//Util.Debug(">> normal_msg");
// Determine FBU.bytes var ret = true, msg_type,
switch (cmode) { c, first_colour, num_colours, red, green, blue;
case "fill":
RQ.shift8(); // shift off ctl //Util.Debug(">> msg RQ.slice(0,10): " + RQ.slice(0,20));
color = RQ.shiftBytes(RFB.fb_depth); //Util.Debug(">> msg RQ.slice(-10,-1): " + RQ.slice(RQ.length-10,RQ.length));
Canvas.fillRect(FBU.x, FBU.y, FBU.width, FBU.height, color); if (FBU.rects > 0) {
msg_type = 0;
} else if (cuttext !== 'none') {
msg_type = 3;
} else {
msg_type = RQ.shift8();
}
switch (msg_type) {
case 0: // FramebufferUpdate
ret = framebufferUpdate(); // false means need more data
break; break;
case "jpeg": case 1: // SetColourMapEntries
case "png": Util.Debug("SetColourMapEntries");
clength = getCLength(RQ, 1); RQ.shift8(); // Padding
FBU.bytes = 1 + clength[0] + clength[1]; // ctl + clength size + jpeg-data first_colour = RQ.shift16(); // First colour
if (RQ.length < FBU.bytes) { num_colours = RQ.shift16();
Util.Debug(" waiting for TIGHT " + cmode + " bytes"); for (c=0; c < num_colours; c+=1) {
red = RQ.shift16();
//Util.Debug("red before: " + red);
red = parseInt(red / 256, 10);
//Util.Debug("red after: " + red);
green = parseInt(RQ.shift16() / 256, 10);
blue = parseInt(RQ.shift16() / 256, 10);
canvas.set_colourMap([red, green, blue], first_colour + c);
}
Util.Info("Registered " + num_colours + " colourMap entries");
//Util.Debug("colourMap: " + canvas.get_colourMap());
break;
case 2: // Bell
Util.Warn("Bell (unsupported)");
break;
case 3: // ServerCutText
Util.Debug("ServerCutText");
Util.Debug("RQ:" + RQ.slice(0,20));
if (cuttext === 'none') {
cuttext = 'header';
}
if (cuttext === 'header') {
if (RQ.length < 7) {
//Util.Debug("waiting for ServerCutText header");
return false; return false;
} }
RQ.shiftBytes(3); // Padding
// We have everything, render it cuttext_length = RQ.shift32();
//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 cuttext = 'bytes';
img = new Image(); if (RQ.length < cuttext_length) {
img.onload = RFB.scan_tight_imgs; //Util.Debug("waiting for ServerCutText bytes");
FBU.imgs.push([img, FBU.x, FBU.y]); return false;
img.src = "data:image/" + cmode + }
RFB.extract_data_uri(RQ.shiftBytes(clength[1])); conf.clipboardReceive(that, RQ.shiftStr(cuttext_length));
img = null; cuttext = 'none';
break;
default:
updateState('failed',
"Disconnected: illegal server message type " + msg_type);
Util.Debug("RQ.slice(0,30):" + RQ.slice(0,30));
break; break;
} }
FBU.bytes = 0; //Util.Debug("<< normal_msg");
FBU.rects -= 1; return ret;
//Util.Debug(" ending RQ.length: " + RQ.length); };
//Util.Debug(" ending RQ.slice(0,20): " + RQ.slice(0,20));
//Util.Debug("<< display_tight_png");
return true;
},
extract_data_uri : function (arr) { framebufferUpdate = function() {
//var i, stra = []; var now, fbu_rt_diff, last_bytes, last_rects, ret = true;
//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 () { if (FBU.rects === 0) {
var img, imgs; //Util.Debug("New FBU: RQ.slice(0,20): " + RQ.slice(0,20));
if (RFB.state === 'normal') { if (RQ.length < 3) {
imgs = RFB.FBU.imgs; RQ.unshift(0); // FBU msg_type
while ((imgs.length > 0) && (imgs[0][0].complete)) { Util.Debug(" waiting for FBU header bytes");
img = imgs.shift(); return false;
Canvas.ctx.drawImage(img[0], img[1], img[2]); }
RQ.shift8();
FBU.rects = RQ.shift16();
//Util.Debug("FramebufferUpdate, rects:" + FBU.rects);
FBU.bytes = 0;
timing.cur_fbu = 0;
timing.h_fbus += 1;
if (timing.fbu_rt_start > 0) {
now = (new Date()).getTime();
Util.Info("First FBU latency: " + (now - timing.fbu_rt_start));
} }
setTimeout(RFB.scan_tight_imgs, RFB.scan_imgs_rate);
} }
},
set_desktopsize : function () {
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();
// Send a new non-incremental request
RFB.send_array(RFB.fbUpdateRequest(0));
RFB.FBU.bytes = 0;
RFB.FBU.rects -= 1;
Util.Debug("<< set_desktopsize");
return true;
},
set_cursor: function () {
var x, y, w, h, pixelslength, masklength;
//Util.Debug(">> set_cursor");
x = RFB.FBU.x; // hotspot-x
y = RFB.FBU.y; // hotspot-y
w = RFB.FBU.width;
h = RFB.FBU.height;
pixelslength = w * h * RFB.fb_Bpp;
masklength = Math.floor((w + 7) / 8) * h;
if (RFB.RQ.length < (pixelslength + masklength)) { while (FBU.rects > 0) {
//Util.Debug("waiting for cursor encoding bytes"); if (rfb_state !== "normal") {
RFB.FBU.bytes = pixelslength + masklength;
return false; return false;
} }
if (RQ.length < FBU.bytes) {
return false;
}
if (FBU.bytes === 0) {
if (RQ.length < 12) {
//Util.Debug(" waiting for rect header bytes");
return false;
}
/* New FramebufferUpdate */
FBU.x = RQ.shift16();
FBU.y = RQ.shift16();
FBU.width = RQ.shift16();
FBU.height = RQ.shift16();
FBU.encoding = parseInt(RQ.shift32(), 10);
timing.h_bytes += 12;
//Util.Debug(" set_cursor, x: " + x + ", y: " + y + ", w: " + w + ", h: " + h); if (encNames[FBU.encoding]) {
// Debug:
Canvas.changeCursor(RFB.RQ.shiftBytes(pixelslength), /*
RFB.RQ.shiftBytes(masklength), var msg = "FramebufferUpdate rects:" + FBU.rects;
x, y, w, h); msg += " x: " + FBU.x + " y: " + FBU.y
msg += " width: " + FBU.width + " height: " + FBU.height;
RFB.FBU.bytes = 0; msg += " encoding:" + FBU.encoding;
RFB.FBU.rects -= 1; msg += "(" + encNames[FBU.encoding] + ")";
msg += ", RQ.length: " + RQ.length;
//Util.Debug("<< set_cursor"); Util.Debug(msg);
return true; */
}, } else {
updateState('failed',
"Disconnected: unsupported encoding " +
FBU.encoding);
return false;
}
}
set_jpeg_quality : function () { timing.last_fbu = (new Date()).getTime();
Util.Debug(">> set_jpeg_quality"); last_bytes = RQ.length;
}, last_rects = FBU.rects;
set_compress_level: function () {
Util.Debug(">> set_compress_level");
},
/* // false ret means need more data
* Client message routines ret = encHandlers[FBU.encoding]();
*/
pixelFormat: function () { now = (new Date()).getTime();
//Util.Debug(">> pixelFormat"); timing.cur_fbu += (now - timing.last_fbu);
var arr; timing.h_bytes += last_bytes-RQ.length;
arr = [0]; // msg-type
arr.push8(0); // padding
arr.push8(0); // padding
arr.push8(0); // padding
arr.push8(RFB.fb_Bpp * 8); // bits-per-pixel if (FBU.rects < last_rects) {
arr.push8(RFB.fb_depth * 8); // depth // Some work was done
arr.push8(0); // little-endian timing.h_rects += last_rects-FBU.rects;
arr.push8(RFB.true_color); // true-color timing.h_pixels += FBU.width*FBU.height;
}
arr.push16(255); // red-max if (FBU.rects === 0) {
arr.push16(255); // green-max if (((FBU.width === fb_width) &&
arr.push16(255); // blue-max (FBU.height === fb_height)) ||
arr.push8(0); // red-shift (timing.fbu_rt_start > 0)) {
arr.push8(8); // green-shift timing.full_fbu_total += timing.cur_fbu;
arr.push8(16); // blue-shift timing.full_fbu_cnt += 1;
Util.Info("Timing of full FBU, cur: " +
timing.cur_fbu + ", total: " +
timing.full_fbu_total + ", cnt: " +
timing.full_fbu_cnt + ", avg: " +
(timing.full_fbu_total /
timing.full_fbu_cnt));
}
if (timing.fbu_rt_start > 0) {
fbu_rt_diff = now - timing.fbu_rt_start;
timing.fbu_rt_total += fbu_rt_diff;
timing.fbu_rt_cnt += 1;
Util.Info("full FBU round-trip, cur: " +
fbu_rt_diff + ", total: " +
timing.fbu_rt_total + ", cnt: " +
timing.fbu_rt_cnt + ", avg: " +
(timing.fbu_rt_total /
timing.fbu_rt_cnt));
timing.fbu_rt_start = 0;
}
}
}
return ret;
};
arr.push8(0); // padding //
arr.push8(0); // padding // FramebufferUpdate encodings
arr.push8(0); // padding //
//Util.Debug("<< pixelFormat");
return arr;
},
fixColourMapEntries: function () { encHandlers.RAW = function display_raw() {
}, //Util.Debug(">> display_raw");
clientEncodings: function () { var cur_y, cur_height;
//Util.Debug(">> clientEncodings");
var arr, i, encList = [];
for (i=0; i<RFB.encodings.length; i += 1) { if (FBU.lines === 0) {
if ((RFB.encodings[i][0] === "Cursor") && FBU.lines = FBU.height;
(! RFB.local_cursor)) {
Util.Debug("Skipping Cursor pseudo-encoding");
} else {
//Util.Debug("Adding encoding: " + RFB.encodings[i][0]);
encList.push(RFB.encodings[i][1]);
} }
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");
return false;
} }
cur_y = FBU.y + (FBU.height - FBU.lines);
cur_height = Math.min(FBU.lines,
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;
arr = [2]; // msg-type if (FBU.lines > 0) {
arr.push8(0); // padding FBU.bytes = FBU.width * fb_Bpp; // At least another line
} else {
arr.push16(encList.length); // encoding count FBU.rects -= 1;
for (i=0; i < encList.length; i += 1) { FBU.bytes = 0;
arr.push32(encList[i]);
} }
//Util.Debug("<< clientEncodings: " + arr); return true;
return arr; };
},
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; }
var arr;
arr = [3]; // msg-type
arr.push8(incremental);
arr.push16(x);
arr.push16(y);
arr.push16(xw);
arr.push16(yw);
//Util.Debug("<< fbUpdateRequest");
return arr;
},
keyEvent: function (keysym, down) {
//Util.Debug(">> keyEvent, keysym: " + keysym + ", down: " + down);
var arr;
arr = [4]; // msg-type
arr.push8(down);
arr.push16(0);
arr.push32(keysym);
//Util.Debug("<< keyEvent");
return arr;
},
pointerEvent: function (x, y) { encHandlers.COPYRECT = function display_copy_rect() {
//Util.Debug(">> pointerEvent, x,y: " + x + "," + y + //Util.Debug(">> display_copy_rect");
// " , mask: " + RFB.mouse_buttonMask);
var arr;
arr = [5]; // msg-type
arr.push8(RFB.mouse_buttonMask);
arr.push16(x);
arr.push16(y);
//Util.Debug("<< pointerEvent");
return arr;
},
clientCutText: function (text) { var old_x, old_y;
//Util.Debug(">> clientCutText");
var arr;
arr = [6]; // msg-type
arr.push8(0); // padding
arr.push8(0); // padding
arr.push8(0); // padding
arr.push32(text.length);
arr.pushStr(text);
//Util.Debug("<< clientCutText:" + arr);
return arr;
},
if (RQ.length < 4) {
//Util.Debug(" waiting for " +
// (FBU.bytes - RQ.length) + " COPYRECT bytes");
return false;
}
old_x = RQ.shift16();
old_y = RQ.shift16();
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() {
* Utility routines //Util.Debug(">> display_rre (" + RQ.length + " bytes)");
*/ var color, x, y, width, height, chunk;
encode_message: function(arr) { if (FBU.subrects === 0) {
if (RFB.b64encode) { if (RQ.length < 4 + fb_Bpp) {
/* base64 encode */ //Util.Debug(" waiting for " +
RFB.SQ = RFB.SQ + Base64.encode(arr); // (4 + fb_Bpp - RQ.length) + " RRE bytes");
} else { return false;
/* 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(''); FBU.subrects = RQ.shift32();
color = RQ.shiftBytes(fb_Bpp); // Background
canvas.fillRect(FBU.x, FBU.y, FBU.width, FBU.height, color);
}
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);
FBU.subrects -= 1;
} }
}, //Util.Debug(" display_rre: rects: " + FBU.rects +
// ", FBU.subrects: " + FBU.subrects);
decode_message: function(data) { if (FBU.subrects > 0) {
var i, length, RQ = RFB.RQ; chunk = Math.min(rre_chunk_sz, FBU.subrects);
//Util.Debug(">> decode_message: " + data); FBU.bytes = (fb_Bpp + 8) * chunk;
if (RFB.b64encode) {
/* base64 decode */
RFB.RQ = RFB.RQ.concat(Base64.decode(data, 0));
} else { } else {
/* UTF-8 decode. 256 -> 0 to WebSockets framing */ FBU.rects -= 1;
length = data.length; FBU.bytes = 0;
for (i=0; i < length; i += 1) {
RQ.push(data.charCodeAt(i) % 256);
} }
//Util.Debug("<< display_rre, FBU.bytes: " + FBU.bytes);
return true;
};
encHandlers.HEXTILE = function display_hextile() {
//Util.Debug(">> display_hextile");
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) {
FBU.tiles_x = Math.ceil(FBU.width/16);
FBU.tiles_y = Math.ceil(FBU.height/16);
FBU.total_tiles = FBU.tiles_x * FBU.tiles_y;
FBU.tiles = FBU.total_tiles;
} }
//Util.Debug(">> decode_message, RQ: " + RFB.RQ);
},
recv_message: function(e) { /* FBU.bytes comes in as 1, RQ.length at least 1 */
//Util.Debug(">> recv_message"); while (FBU.tiles > 0) {
FBU.bytes = 1;
if (RQ.length < FBU.bytes) {
//Util.Debug(" waiting for HEXTILE subencoding byte");
return false;
}
subencoding = RQ[0]; // Peek
if (subencoding > 30) { // Raw
updateState('failed',
"Disconnected: illegal hextile subencoding " + subencoding);
//Util.Debug("RQ.slice(0,30):" + RQ.slice(0,30));
return false;
}
subrects = 0;
cur_tile = FBU.total_tiles - FBU.tiles;
tile_x = cur_tile % FBU.tiles_x;
tile_y = Math.floor(cur_tile / FBU.tiles_x);
x = FBU.x + tile_x * 16;
y = FBU.y + tile_y * 16;
w = Math.min(16, (FBU.x + FBU.width) - x);
h = Math.min(16, (FBU.y + FBU.height) - y);
try { /* Figure out how much we are expecting */
RFB.decode_message(e.data); if (subencoding & 0x01) { // Raw
if (RFB.RQ.length > 0) { //Util.Debug(" Raw subencoding");
RFB.handle_message(); FBU.bytes += w * h * fb_Bpp;
} else { } else {
Util.Debug("Ignoring empty message"); if (subencoding & 0x02) { // Background
FBU.bytes += fb_Bpp;
} }
} catch (exc) { if (subencoding & 0x04) { // Foreground
if (typeof exc.stack !== 'undefined') { FBU.bytes += fb_Bpp;
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') { if (subencoding & 0x08) { // AnySubrects
RFB.updateState('failed', exc.name + ": " + exc.message); FBU.bytes += 1; // Since we aren't shifting it off
if (RQ.length < FBU.bytes) {
/* Wait for subrects byte */
//Util.Debug(" waiting for hextile subrects header byte");
return false;
}
subrects = RQ[FBU.bytes-1]; // Peek
if (subencoding & 0x10) { // SubrectsColoured
FBU.bytes += subrects * (fb_Bpp + 2);
} else { } else {
RFB.updateState('failed', exc); FBU.bytes += subrects * 2;
}
} }
} }
//Util.Debug("<< recv_message");
},
handle_message: function () { //Util.Debug(" tile:" + cur_tile + "/" + (FBU.total_tiles - 1) +
//Util.Debug("RQ.slice(0,20): " + RFB.RQ.slice(0,20) + " (" + RFB.RQ.length + ")"); // ", subencoding:" + subencoding +
switch (RFB.state) { // "(last: " + FBU.lastsubencoding + "), subrects:" +
case 'disconnected': // subrects + ", tile:" + tile_x + "," + tile_y +
Util.Error("Got data while disconnected"); // " [" + x + "," + y + "]@" + w + "x" + h +
break; // ", d.length:" + RQ.length + ", bytes:" + FBU.bytes +
case 'failed': // " last:" + RQ.slice(FBU.bytes-10, FBU.bytes) +
Util.Warn("Giving up!"); // " next:" + RQ.slice(FBU.bytes-1, FBU.bytes+10));
RFB.disconnect(); if (RQ.length < FBU.bytes) {
break; //Util.Debug(" waiting for " +
case 'normal': // (FBU.bytes - RQ.length) + " hextile bytes");
if ((RFB.state === 'normal') && (RFB.RQ.length > 0)) { return false;
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);
} }
/* We know the encoding and have a whole tile */
FBU.subencoding = RQ[0];
idx = 1;
if (FBU.subencoding === 0) {
if (FBU.lastsubencoding & 0x01) {
/* Weird: ignore blanks after RAW */
Util.Debug(" Ignoring blank after RAW");
} else {
canvas.fillRect(x, y, w, h, FBU.background);
} }
break; } else if (FBU.subencoding & 0x01) { // Raw
default: canvas.blitImage(x, y, w, h, RQ, idx);
RFB.init_msg(); } else {
break; if (FBU.subencoding & 0x02) { // Background
FBU.background = RQ.slice(idx, idx + fb_Bpp);
idx += fb_Bpp;
}
if (FBU.subencoding & 0x04) { // Foreground
FBU.foreground = RQ.slice(idx, idx + fb_Bpp);
idx += fb_Bpp;
} }
},
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) { tile = canvas.getTile(x, y, w, h, FBU.background);
//Util.Debug(">> send_array: " + arr); if (FBU.subencoding & 0x08) { // AnySubrects
RFB.encode_message(arr); subrects = RQ[idx];
if (RFB.ws.bufferedAmount === 0) { idx += 1;
//Util.Debug("arr: " + arr); for (s = 0; s < subrects; s += 1) {
//Util.Debug("RFB.SQ: " + RFB.SQ); if (FBU.subencoding & 0x10) { // SubrectsColoured
RFB.ws.send(RFB.SQ); color = RQ.slice(idx, idx + fb_Bpp);
RFB.SQ = ""; idx += fb_Bpp;
} else { } else {
Util.Debug("Delaying send"); color = FBU.foreground;
} }
}, xy = RQ[idx];
idx += 1;
sx = (xy >> 4);
sy = (xy & 0x0f);
DES: function (password, challenge) { wh = RQ[idx];
var i, passwd, response; idx += 1;
passwd = []; sw = (wh >> 4) + 1;
response = challenge.slice(); sh = (wh & 0x0f) + 1;
for (i=0; i < password.length; i += 1) {
passwd.push(password.charCodeAt(i));
}
DES.setKeys(passwd); canvas.setSubTile(tile, sx, sy, sw, sh, color);
DES.encrypt(response, 0, response, 0); }
DES.encrypt(response, 8, response, 8); }
return response; canvas.putTile(tile);
}, }
RQ.shiftBytes(FBU.bytes);
FBU.lastsubencoding = FBU.subencoding;
FBU.bytes = 0;
FBU.tiles -= 1;
}
flushClient: function () { if (FBU.tiles === 0) {
if (RFB.mouse_arr.length > 0) { FBU.rects -= 1;
//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 = []; //Util.Debug("<< display_hextile");
return true; return true;
} else { };
encHandlers.TIGHT_PNG = function display_tight_png() {
//Util.Debug(">> display_tight_png");
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));
FBU.bytes = 1; // compression-control byte
if (RQ.length < FBU.bytes) {
Util.Debug(" waiting for TIGHT compression-control byte");
return false; return false;
} }
},
checkEvents: function () { // Get 'compact length' header and data size
var now; getCLength = function (arr, offset) {
if (RFB.state === 'normal') { var header = 1, data = 0;
if (! RFB.flushClient()) { data += arr[offset + 0] & 0x7f;
now = new Date().getTime(); if (arr[offset + 0] & 0x80) {
if (now > RFB.last_req + RFB.req_rate) { header += 1;
RFB.last_req = now; data += (arr[offset + 1] & 0x7f) << 7;
RFB.send_array(RFB.fbUpdateRequest(1)); if (arr[offset + 1] & 0x80) {
} header += 1;
data += arr[offset + 2] << 14;
} }
} }
setTimeout(RFB.checkEvents, RFB.check_rate); return [header, data];
}, };
keyPress: function (keysym, down) {
var arr;
arr = RFB.keyEvent(keysym, down);
arr = arr.concat(RFB.fbUpdateRequest(1));
RFB.send_array(arr);
},
mouseButton: function(x, y, down, bmask) { ctl = RQ[0];
if (down) { switch (ctl >> 4) {
RFB.mouse_buttonMask |= bmask; case 0x08: cmode = "fill"; break;
} else { case 0x09: cmode = "jpeg"; break;
RFB.mouse_buttonMask ^= bmask; case 0x0A: cmode = "png"; break;
default: throw("Illegal basic compression received, ctl: " + ctl);
}
switch (cmode) {
// fill uses fb_depth because TPIXELs drop the padding byte
case "fill": FBU.bytes += fb_depth; break; // TPIXEL
case "jpeg": FBU.bytes += 3; break; // max clength
case "png": FBU.bytes += 3; break; // max clength
} }
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) { if (RQ.length < FBU.bytes) {
Util.Debug(">> clipboardCopyTo stub"); Util.Debug(" waiting for TIGHT " + cmode + " bytes");
// Stub return false;
}, }
externalUpdateState: function(state, msg) { //Util.Debug(" RQ.slice(0,20): " + RQ.slice(0,20) + " (" + RQ.length + ")");
Util.Debug(">> externalUpdateState stub"); //Util.Debug(" cmode: " + cmode);
// Stub
},
/* // Determine FBU.bytes
* Running states: switch (cmode) {
* disconnected - idle state case "fill":
* normal - connected RQ.shift8(); // shift off ctl
* color = RQ.shiftBytes(fb_depth);
* Page states: canvas.fillRect(FBU.x, FBU.y, FBU.width, FBU.height, color);
* loaded - page load, equivalent to disconnected break;
* connect - starting initialization case "jpeg":
* password - waiting for password case "png":
* failed - abnormal transition to disconnected clength = getCLength(RQ, 1);
* fatal - failed to load page, or fatal error FBU.bytes = 1 + clength[0] + clength[1]; // ctl + clength size + jpeg-data
* if (RQ.length < FBU.bytes) {
* VNC initialization states: Util.Debug(" waiting for TIGHT " + cmode + " bytes");
* ProtocolVersion return false;
* 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;
} }
if (oldstate === 'fatal') { // We have everything, render it
Util.Error("Fatal error, cannot continue"); //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 = scan_tight_imgs;
FBU.imgs.push([img, FBU.x, FBU.y]);
img.src = "data:image/" + cmode +
extract_data_uri(RQ.shiftBytes(clength[1]));
img = null;
break;
} }
FBU.bytes = 0;
FBU.rects -= 1;
//Util.Debug(" ending RQ.length: " + RQ.length);
//Util.Debug(" ending RQ.slice(0,20): " + RQ.slice(0,20));
//Util.Debug("<< display_tight_png");
return true;
};
if ((state === 'failed') || (state === 'fatal')) { extract_data_uri = function(arr) {
func = Util.Error; //var i, stra = [];
} else { //for (i=0; i< arr.length; i += 1) {
func = Util.Warn; // stra.push(String.fromCharCode(arr[i]));
//}
//return "," + escape(stra.join(''));
return ";base64," + Base64.encode(arr);
};
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();
ctx.drawImage(img[0], img[1], img[2]);
} }
setTimeout(scan_tight_imgs, scan_imgs_rate);
}
};
cmsg = typeof(statusMsg) !== 'undefined' ? (" Msg: " + statusMsg) : ""; encHandlers.DesktopSize = function set_desktopsize() {
func("New state '" + state + "', was '" + oldstate + "'." + cmsg); Util.Debug(">> set_desktopsize");
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
send_array(fbUpdateRequest(0));
if ((oldstate === 'failed') && (state === 'disconnected')) { FBU.bytes = 0;
// Do disconnect action, but stay in failed state. FBU.rects -= 1;
RFB.state = 'failed';
} else {
RFB.state = state;
}
switch (state) { Util.Debug("<< set_desktopsize");
case 'loaded': return true;
case 'disconnected': };
if (RFB.sendID) { encHandlers.Cursor = function set_cursor() {
clearInterval(RFB.sendID); var x, y, w, h, pixelslength, masklength;
RFB.sendID = null; //Util.Debug(">> set_cursor");
} x = FBU.x; // hotspot-x
y = FBU.y; // hotspot-y
w = FBU.width;
h = FBU.height;
if (RFB.ws) { pixelslength = w * h * fb_Bpp;
if (RFB.ws.readyState === WebSocket.OPEN) { masklength = Math.floor((w + 7) / 8) * h;
RFB.ws.close();
}
RFB.ws.onmessage = function (e) { return; };
}
if (Canvas.ctx) { if (RQ.length < (pixelslength + masklength)) {
Canvas.stop(); //Util.Debug("waiting for cursor encoding bytes");
if (! /__debug__$/i.test(document.location.href)) { FBU.bytes = pixelslength + masklength;
Canvas.clear(); return false;
}
} }
RFB.show_timings(); //Util.Debug(" set_cursor, x: " + x + ", y: " + y + ", w: " + w + ", h: " + h);
break; canvas.changeCursor(RQ.shiftBytes(pixelslength),
RQ.shiftBytes(masklength),
x, y, w, h);
FBU.bytes = 0;
FBU.rects -= 1;
case 'connect': //Util.Debug("<< set_cursor");
RFB.init_vars(); return true;
};
if ((RFB.ws) && (RFB.ws.readyState === WebSocket.OPEN)) { encHandlers.JPEG_quality_lo = function set_jpeg_quality() {
RFB.ws.close(); Util.Error("Server sent jpeg_quality pseudo-encoding");
} };
RFB.init_ws(); // onopen transitions to 'ProtocolVersion'
break; encHandlers.compress_lo = function set_compress_level() {
Util.Error("Server sent compress level pseudo-encoding");
};
/*
* Client message routines
*/
case 'password': pixelFormat = function() {
// Ignore password state by default //Util.Debug(">> pixelFormat");
break; var arr;
arr = [0]; // msg-type
arr.push8(0); // padding
arr.push8(0); // padding
arr.push8(0); // padding
arr.push8(fb_Bpp * 8); // bits-per-pixel
arr.push8(fb_depth * 8); // depth
arr.push8(0); // little-endian
arr.push8(conf.true_color ? 1 : 0); // true-color
case 'normal': arr.push16(255); // red-max
if ((oldstate === 'disconnected') || (oldstate === 'failed')) { arr.push16(255); // green-max
Util.Error("Invalid transition from 'disconnected' or 'failed' to 'normal'"); arr.push16(255); // blue-max
} arr.push8(0); // red-shift
arr.push8(8); // green-shift
arr.push8(16); // blue-shift
break; arr.push8(0); // padding
arr.push8(0); // padding
arr.push8(0); // padding
//Util.Debug("<< pixelFormat");
return arr;
};
clientEncodings = function() {
//Util.Debug(">> clientEncodings");
var arr, i, encList = [];
case 'failed': for (i=0; i<encodings.length; i += 1) {
if (oldstate === 'disconnected') { if ((encodings[i][0] === "Cursor") &&
Util.Error("Invalid transition from 'disconnected' to 'failed'"); (! conf.local_cursor)) {
} Util.Debug("Skipping Cursor pseudo-encoding");
if (oldstate === 'normal') { } else {
Util.Error("Error while connected."); //Util.Debug("Adding encoding: " + encodings[i][0]);
encList.push(encodings[i][1]);
} }
if (oldstate === 'init') {
Util.Error("Error while initializing.");
} }
if ((RFB.ws) && (RFB.ws.readyState === WebSocket.OPEN)) { arr = [2]; // msg-type
RFB.ws.close(); arr.push8(0); // padding
}
// Make sure we transition to disconnected
setTimeout(function() { RFB.updateState('disconnected'); }, 50);
break; arr.push16(encList.length); // encoding count
for (i=0; i < encList.length; i += 1) {
arr.push32(encList[i]);
}
//Util.Debug("<< clientEncodings: " + arr);
return arr;
};
fbUpdateRequest = function(incremental, x, y, xw, yw) {
//Util.Debug(">> fbUpdateRequest");
if (!x) { x = 0; }
if (!y) { y = 0; }
if (!xw) { xw = fb_width; }
if (!yw) { yw = fb_height; }
var arr;
arr = [3]; // msg-type
arr.push8(incremental);
arr.push16(x);
arr.push16(y);
arr.push16(xw);
arr.push16(yw);
//Util.Debug("<< fbUpdateRequest");
return arr;
};
default: keyEvent = function(keysym, down) {
// Invalid state transition //Util.Debug(">> keyEvent, keysym: " + keysym + ", down: " + down);
var arr;
arr = [4]; // msg-type
arr.push8(down);
arr.push16(0);
arr.push32(keysym);
//Util.Debug("<< keyEvent");
return arr;
};
} pointerEvent = function(x, y) {
//Util.Debug(">> pointerEvent, x,y: " + x + "," + y +
// " , mask: " + mouse_buttonMask);
var arr;
arr = [5]; // msg-type
arr.push8(mouse_buttonMask);
arr.push16(x);
arr.push16(y);
//Util.Debug("<< pointerEvent");
return arr;
};
if ((oldstate === 'failed') && (state === 'disconnected')) { clientCutText = function(text) {
// Leave the failed message //Util.Debug(">> clientCutText");
RFB.externalUpdateState(state); var arr;
} else { arr = [6]; // msg-type
RFB.externalUpdateState(state, statusMsg); arr.push8(0); // padding
} arr.push8(0); // padding
}, arr.push8(0); // padding
arr.push32(text.length);
arr.pushStr(text);
//Util.Debug("<< clientCutText:" + arr);
return arr;
};
update_timings: function() {
var now, timing = RFB.timing, 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(RFB.update_timings, 1000 - offset);
} else {
setTimeout(RFB.update_timings, 2000 - offset);
}
}
},
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"; // Public API interface functions
} //
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.init = function () {
* Setup routines
*/
init_ws: function () { init_vars();
//Util.Debug(">> init_ws");
var uri = "", vars = []; /* Check web-socket-js if no builtin WebSocket support */
if (RFB.encrypt) { if (VNC_native_ws) {
uri = "wss://"; Util.Info("Using native WebSockets");
updateState('loaded', 'noVNC ready (using native WebSockets)');
} else { } else {
uri = "ws://"; Util.Warn("Using web-socket-js flash bridge");
} if ((! Util.Flash) ||
uri += RFB.host + ":" + RFB.port + "/"; (Util.Flash.version < 9)) {
if (RFB.b64encode) { updateState('fatal', "WebSockets or Adobe Flash is required");
vars.push("b64encode"); } 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)');
} }
if (vars.length > 0) {
uri += "?" + vars.join("&");
} }
Util.Info("connecting to " + uri); };
RFB.ws = new WebSocket(uri);
RFB.ws.onmessage = RFB.recv_message; that.connect = function(host, port, password) {
RFB.ws.onopen = function(e) { //Util.Debug(">> connect");
Util.Debug(">> WebSocket.onopen");
if (RFB.state === "connect") { // Make sure we have done init checks
RFB.updateState('ProtocolVersion', "Starting VNC handshake"); if ((rfb_state !== 'loaded') && (rfb_state !== 'fatal')) {
} else { that.init();
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");
};
setTimeout(function () { rfb_host = host;
if (RFB.ws.readyState === WebSocket.CONNECTING) { rfb_port = port;
RFB.updateState('failed', "Connect timeout"); rfb_password = (password !== undefined) ? password : "";
if ((!rfb_host) || (!rfb_port)) {
updateState('failed', "Must set host and port");
return;
} }
}, RFB.connectTimeout);
//Util.Debug("<< init_ws"); updateState('connect');
}, //Util.Debug("<< connect");
init_vars: function () { };
/* Reset state */
RFB.cuttext = 'none'; that.disconnect = function() {
RFB.ct_length = 0; //Util.Debug(">> disconnect");
RFB.RQ = []; updateState('disconnected', 'Disconnected');
RFB.SQ = ""; //Util.Debug("<< disconnect");
RFB.FBU.rects = 0; };
RFB.FBU.subrects = 0; // RRE and HEXTILE
RFB.FBU.lines = 0; // RAW that.sendPassword = function(passwd) {
RFB.FBU.tiles = 0; // HEXTILE rfb_password = passwd;
RFB.FBU.imgs = []; // TIGHT_PNG image queue rfb_state = "Authentication";
RFB.mouse_buttonmask = 0; setTimeout(init_msg, 1);
RFB.mouse_arr = []; };
RFB.timing.history_start = 0; that.sendCtrlAltDel = function() {
RFB.timing.history = []; if (rfb_state !== "normal") { return false; }
RFB.timing.h_fbus = 0; Util.Info("Sending Ctrl-Alt-Del");
RFB.timing.h_rects = 0; var arr = [];
RFB.timing.h_bytes = 0; arr = arr.concat(keyEvent(0xFFE3, 1)); // Control
RFB.timing.h_pixels = 0; 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");
};
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
checkEvents = function () { /* Stub Out */ };
that.connect = function(host, port, password) {
rfb_host = host;
rfb_port = port;
rfb_password = password;
updateState('ProtocolVersion', "Starting VNC handshake");
};
};
return constructor(); // Return the public API interface
}; /* End of RFB */ } // End of RFB()
...@@ -14,41 +14,6 @@ ...@@ -14,41 +14,6 @@
var Util = {}, $; var 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 'debug': Util.Debug = function (msg) { console.log(msg); };
case 'info': Util.Info = function (msg) { console.log(msg); };
case 'warn': Util.Warn = function (msg) { console.warn(msg); };
case 'error': Util.Error = function (msg) { console.error(msg); };
break;
default:
throw("invalid logging type '" + level + "'");
}
};
// Initialize logging level
Util.init_logging( (document.location.href.match(
/logging=([A-Za-z0-9\._\-]*)/) ||
['', 'warn'])[1] );
/* /*
* Simple DOM selector by ID * Simple DOM selector by ID
*/ */
...@@ -138,6 +103,42 @@ Array.prototype.shiftBytes = function (len) { ...@@ -138,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) { Util.dirObj = function (obj, depth, parent) {
var i, msg = "", val = ""; var i, msg = "", val = "";
if (! depth) { depth=2; } if (! depth) { depth=2; }
...@@ -168,7 +169,7 @@ Util.getQueryVar = function(name, defVal) { ...@@ -168,7 +169,7 @@ Util.getQueryVar = function(name, defVal) {
}; };
// Set defaults for Crockford style function namespaces // Set defaults for Crockford style function namespaces
Util.conf_default = function(cfg, api, v, val) { Util.conf_default = function(cfg, api, v, val, force_bool) {
if (typeof cfg[v] === 'undefined') { if (typeof cfg[v] === 'undefined') {
cfg[v] = val; cfg[v] = val;
} }
...@@ -181,6 +182,13 @@ Util.conf_default = function(cfg, api, v, val) { ...@@ -181,6 +182,13 @@ Util.conf_default = function(cfg, api, v, val) {
// Default setter // Default setter
if (typeof api['set_' + v] === 'undefined') { if (typeof api['set_' + v] === 'undefined') {
api['set_' + v] = function (val) { 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; cfg[v] = val;
}; };
} }
...@@ -311,10 +319,9 @@ Util.createCookie = function(name,value,days) { ...@@ -311,10 +319,9 @@ Util.createCookie = function(name,value,days) {
}; };
Util.readCookie = function(name, defaultValue) { Util.readCookie = function(name, defaultValue) {
var nameEQ = name + "="; var i, c, nameEQ = name + "=", ca = document.cookie.split(';');
var ca = document.cookie.split(';'); for(i=0; i < ca.length; i += 1) {
for(var i=0;i < ca.length;i++) { c = ca[i];
var c = ca[i];
while (c.charAt(0) === ' ') { c = c.substring(1,c.length); } while (c.charAt(0) === ' ') { c = c.substring(1,c.length); }
if (c.indexOf(nameEQ) === 0) { return c.substring(nameEQ.length,c.length); } if (c.indexOf(nameEQ) === 0) { return c.substring(nameEQ.length,c.length); }
} }
...@@ -330,7 +337,7 @@ Util.eraseCookie = function(name) { ...@@ -330,7 +337,7 @@ Util.eraseCookie = function(name) {
*/ */
Util.getStylesheets = function() { var i, links, sheets = []; Util.getStylesheets = function() { var i, links, sheets = [];
links = document.getElementsByTagName("link"); links = document.getElementsByTagName("link");
for (i = 0; i < links.length; i++) { for (i = 0; i < links.length; i += 1) {
if (links[i].title && if (links[i].title &&
links[i].rel.toUpperCase().indexOf("STYLESHEET") > -1) { links[i].rel.toUpperCase().indexOf("STYLESHEET") > -1) {
sheets.push(links[i]); sheets.push(links[i]);
...@@ -346,19 +353,15 @@ Util.selectStylesheet = function(sheet) { ...@@ -346,19 +353,15 @@ Util.selectStylesheet = function(sheet) {
if (typeof sheet === 'undefined') { if (typeof sheet === 'undefined') {
sheet = 'default'; sheet = 'default';
} }
for (i=0; i < sheets.length; i++) { for (i=0; i < sheets.length; i += 1) {
link = sheets[i]; link = sheets[i];
if (link.title === sheet) { if (link.title === sheet) {
Util.Debug("Using stylesheet " + sheet); Util.Debug("Using stylesheet " + sheet);
link.disabled = false; link.disabled = false;
} else { } else {
Util.Debug("Skipping stylesheet " + link.title); //Util.Debug("Skipping stylesheet " + link.title);
link.disabled = true; link.disabled = true;
} }
} }
return sheet; return sheet;
}; };
// call once to disable alternates and get around webkit bug
Util.selectStylesheet(null);
...@@ -46,14 +46,16 @@ ...@@ -46,14 +46,16 @@
} }
function test_functions () { function test_functions () {
var img, x, y; var img, x, y, w, h, ctx = canvas.getContext();
Canvas.fillRect(0, 0, Canvas.c_wx, Canvas.c_wy, [240,240,240]); 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(); var himg = new Image();
himg.onload = function () { himg.onload = function () {
Canvas.ctx.drawImage(himg, 200, 40); }; ctx.drawImage(himg, 200, 40); };
himg.src = "face.png"; himg.src = "face.png";
/* Test array image data */ /* Test array image data */
...@@ -66,15 +68,14 @@ ...@@ -66,15 +68,14 @@
data[(y*50 + x)*4 + 3] = 255; 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]);
img = Canvas.getTile(5,5,16,16,[0,128,128]); canvas.putTile(img);
Canvas.putTile(img);
img = Canvas.getTile(90,15,16,16,[0,0,0]); img = canvas.getTile(90,15,16,16,[0,0,0]);
Canvas.setSubTile(img, 0,0,16,16,[128,128,0]); canvas.setSubTile(img, 0,0,16,16,[128,128,0]);
Canvas.putTile(img); canvas.putTile(img);
} }
function begin () { function begin () {
...@@ -85,30 +86,35 @@ ...@@ -85,30 +86,35 @@
} }
function start_delayed () { function start_delayed () {
var ret;
message("Running test: prefer Javascript"); ret = canvas.set_prefer_js(true);
Canvas.prefer_js = true; if (ret) {
message("Running test: prefer Javascript ops");
var time1 = run_test(); var time1 = run_test();
message("prefer Javascript: " + time1 + "ms total, " + message("prefer Javascript ops: " + time1 + "ms total, " +
(time1 / iterations) + "ms per frame"); (time1 / iterations) + "ms per frame");
} else {
message("Could not run: prefer Javascript ops");
}
canvas.set_prefer_js(false);
message("Running test: prefer Canvas ops"); message("Running test: prefer Canvas ops");
Canvas.prefer_js = false;
var time2 = run_test(); var time2 = run_test();
message("prefer Canvas ops: " + time2 + "ms total, " + message("prefer Canvas ops: " + time2 + "ms total, " +
(time2 / iterations) + "ms per frame"); (time2 / iterations) + "ms per frame");
Canvas.resize(start_width, start_height, true); canvas.resize(start_width, start_height, true);
test_functions(); test_functions();
$('startButton').disabled = false; $('startButton').disabled = false;
$('startButton').value = "Start"; $('startButton').value = "Do Performance Test";
} }
function run_test () { function run_test () {
var width, height; var width, height;
width = $('width').value; width = $('width').value;
height = $('height').value; height = $('height').value;
Canvas.resize(width, height); canvas.resize(width, height);
var color, start_time = (new Date()).getTime(), w, h; var color, start_time = (new Date()).getTime(), w, h;
for (var i=0; i < iterations; i++) { for (var i=0; i < iterations; i++) {
color = [128, 128, (255 / iterations) * i, 0]; color = [128, 128, (255 / iterations) * i, 0];
...@@ -116,9 +122,9 @@ ...@@ -116,9 +122,9 @@
for (var y=0; y < height; y = y + 16) { for (var y=0; y < height; y = y + 16) {
w = Math.min(16, width - x); w = Math.min(16, width - x);
h = Math.min(16, height - y); h = Math.min(16, height - y);
var tile = Canvas.getTile(x, y, w, h, color); var tile = canvas.getTile(x, y, w, h, color);
Canvas.setSubTile(tile, 0, 0, w, h, color); canvas.setSubTile(tile, 0, 0, w, h, color);
Canvas.putTile(tile); canvas.putTile(tile);
} }
} }
} }
...@@ -129,8 +135,8 @@ ...@@ -129,8 +135,8 @@
window.onload = function() { window.onload = function() {
message("in onload"); message("in onload");
$('iterations').value = 10; $('iterations').value = 10;
Canvas.init('canvas'); canvas = Canvas({'target' : 'canvas'});
Canvas.resize(start_width, start_height, true); canvas.resize(start_width, start_height, true);
message("Canvas initialized"); message("Canvas initialized");
test_functions(); test_functions();
} }
......
...@@ -105,10 +105,10 @@ ...@@ -105,10 +105,10 @@
window.onload = function() { window.onload = function() {
debug("onload"); debug("onload");
var cross, cursor, cursor64; var canvas, cross, cursor, cursor64;
Canvas.init("testcanvas"); canvas = new Canvas({'target' : "testcanvas"});
debug("Canvas.init() indicates Data URI cursor support is: " + Canvas.isCursor()); debug("canvas indicates Data URI cursor support is: " + canvas.get_cursor_uri());
$('button1').style.cursor="url(face.png), default"; $('button1').style.cursor="url(face.png), default";
......
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
src='http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js'></script> src='http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js'></script>
--> -->
<script src="include/util.js"></script> <script src="include/util.js"></script>
<script src="include/base64.js"></script>
<script src="include/canvas.js"></script> <script src="include/canvas.js"></script>
<script> <script>
var msg_cnt = 0; var msg_cnt = 0;
...@@ -51,9 +52,9 @@ ...@@ -51,9 +52,9 @@
} }
window.onload = function() { window.onload = function() {
Canvas.init('canvas'); var canvas = Canvas({'target' : 'canvas'});
Canvas.resize(width, height); canvas.resize(width, height, true);
Canvas.start(keyPress, mouseButton, mouseMove); canvas.start(keyPress, mouseButton, mouseMove);
message("Canvas initialized"); message("Canvas initialized");
} }
</script> </script>
......
...@@ -16,12 +16,12 @@ noVNC example: simple example using default controls ...@@ -16,12 +16,12 @@ noVNC example: simple example using default controls
<body> <body>
<div id='vnc'>Loading</div> <div id='vnc'>Loading</div>
</body>
<script> <script>
window.onload = function () { window.onload = function () {
DefaultControls.load('vnc'); DefaultControls.load('vnc');
RFB.load(); };
}
</script> </script>
</body>
</html> </html>
...@@ -7,8 +7,7 @@ Connect parameters are provided in query string: ...@@ -7,8 +7,7 @@ Connect parameters are provided in query string:
<html> <html>
<head> <head>
<title>VNC Client</title> <title>VNC Client</title>
<link rel="stylesheet" href="include/plain.css" TITLE="plain"> <link rel="stylesheet" href="include/plain.css" title="plain">
<link rel="Alternate StyleSheet" href="include/black.css" TITLE="Black">
<!-- <!--
<script type='text/javascript' <script type='text/javascript'
src='http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js'></script> src='http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js'></script>
...@@ -19,31 +18,36 @@ Connect parameters are provided in query string: ...@@ -19,31 +18,36 @@ Connect parameters are provided in query string:
<body style="margin: 0px;"> <body style="margin: 0px;">
<div id="VNC_screen"> <div id="VNC_screen">
<div id="VNC_status_bar" class="VNC_status_bar" style="margin-top: 0px;"> <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><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" <input type=button value="Send CtrlAltDel"
id="sendCtrlAltDelButton" id="sendCtrlAltDelButton">
onclick="sendCtrlAltDel();"></div></td> </div></td>
</tr></table> </tr></table>
</div> </div>
<canvas id="VNC_canvas" width="640px" height="20px"> <canvas id="VNC_canvas" width="640px" height="20px">
Canvas not supported. Canvas not supported.
</canvas> </canvas>
</div> </div>
</body>
<script> <script>
/*jslint white: false */
/*global window, $, Util, RFB, */
"use strict";
var rfb;
function setPassword() { function setPassword() {
RFB.sendPassword($('password_input').value); rfb.sendPassword($('password_input').value);
return false; return false;
} }
function sendCtrlAltDel() { function sendCtrlAltDel() {
RFB.sendCtrlAltDel(); rfb.sendCtrlAltDel();
return false; return false;
} }
function updateState(state, msg) { function updateState(rfb, state, oldstate, msg) {
var s, sb, klass, html; var s, sb, cad, klass;
s = $('VNC_status'); s = $('VNC_status');
sb = $('VNC_status_bar'); sb = $('VNC_status_bar');
cad = $('sendCtrlAltDelButton'); cad = $('sendCtrlAltDelButton');
...@@ -64,8 +68,9 @@ Connect parameters are provided in query string: ...@@ -64,8 +68,9 @@ Connect parameters are provided in query string:
msg += ' style="margin-bottom: 0px">'; msg += ' style="margin-bottom: 0px">';
msg += 'Password Required: '; msg += 'Password Required: ';
msg += '<input type=password size=10 id="password_input" class="VNC_status">'; msg += '<input type=password size=10 id="password_input" class="VNC_status">';
msg += '</form>'; msg += '<\/form>';
// Fall through klass = "VNC_status_warn";
break;
default: default:
klass = "VNC_status_warn"; klass = "VNC_status_warn";
} }
...@@ -82,6 +87,8 @@ Connect parameters are provided in query string: ...@@ -82,6 +87,8 @@ Connect parameters are provided in query string:
window.onload = function () { window.onload = function () {
var host, port, password; var host, port, password;
$('sendCtrlAltDelButton').onclick = sendCtrlAltDel;
host = Util.getQueryVar('host', null); host = Util.getQueryVar('host', null);
port = Util.getQueryVar('port', null); port = Util.getQueryVar('port', null);
password = Util.getQueryVar('password', ''); password = Util.getQueryVar('password', '');
...@@ -91,15 +98,15 @@ Connect parameters are provided in query string: ...@@ -91,15 +98,15 @@ Connect parameters are provided in query string:
return; return;
} }
RFB.setEncrypt(Util.getQueryVar('encrypt', true)); rfb = new RFB({'encrypt': Util.getQueryVar('encrypt', true),
RFB.setBase64(Util.getQueryVar('base64', true)); 'b64encode': Util.getQueryVar('base64', true),
RFB.setTrueColor(Util.getQueryVar('true_color', true)); 'true_color': Util.getQueryVar('true_color', true),
RFB.setCursor(Util.getQueryVar('cursor', true)); 'local_cursor': Util.getQueryVar('cursor', true),
RFB.setUpdateState(updateState); 'updateState': updateState});
rfb.connect(host, port, password);
RFB.load(); };
RFB.connect(host, port, password);
}
</script> </script>
</body>
</html> </html>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment