Commit 98f40377 authored by Christian Beier's avatar Christian Beier

Update our copy of noVNC.

Bugfixes and support for tight encoding with zlib.
parent efcdab50
...@@ -13,6 +13,8 @@ version 3 with the following exceptions (all LGPL-3 compatible): ...@@ -13,6 +13,8 @@ version 3 with the following exceptions (all LGPL-3 compatible):
include/des.js : Various BSD style licenses include/des.js : Various BSD style licenses
include/jsunzip.js : zlib/libpng license
include/web-socket-js/ : New BSD license. Source code at include/web-socket-js/ : New BSD license. Source code at
http://github.com/gimite/web-socket-js http://github.com/gimite/web-socket-js
......
...@@ -3,20 +3,28 @@ ...@@ -3,20 +3,28 @@
### Description ### Description
noVNC is a VNC client implemented using HTML5 technologies, noVNC is a HTML5 VNC client that runs well in any modern browser
specifically Canvas and WebSockets (supports 'wss://' encryption). including mobile browsers (iPhone/iPad and Android).
noVNC is licensed under the
[LGPLv3](http://www.gnu.org/licenses/lgpl.html).
Special thanks to [Sentry Data Systems](http://www.sentryds.com) for Notable commits, announcements and news are posted to
sponsoring ongoing development of this project (and for employing me). @<a href="http://www.twitter.com/noVNC">noVNC</a>
There are many companies/projects that have integrated noVNC into There are many companies/projects that have integrated noVNC into
their products including: [Sentry Data Systems](http://www.sentryds.com), [Ganeti Web Manager](http://code.osuosl.org/projects/ganeti-webmgr), [Archipel](http://archipelproject.org), [openQRM](http://www.openqrm.com/), [OpenNode](http://www.opennodecloud.com/), [OpenStack](http://www.openstack.org), [Broadway (HTML5 GDK/GTK+ backend)](http://blogs.gnome.org/alexl/2011/03/15/gtk-html-backend-update/), [OpenNebula](http://opennebula.org/), [CloudSigma](http://www.cloudsigma.com/), [Zentyal (formerly eBox)](http://www.zentyal.org/), and [SlapOS](http://www.slapos.org). See [this wiki page](https://github.com/kanaka/noVNC/wiki/ProjectsCompanies-using-noVNC) for more info and links. their products including: [Ganeti Web Manager](http://code.osuosl.org/projects/ganeti-webmgr), [Archipel](http://archipelproject.org), [openQRM](http://www.openqrm.com/), [OpenNode](http://www.opennodecloud.com/), [OpenStack](http://www.openstack.org), [Broadway (HTML5 GDK/GTK+ backend)](http://blogs.gnome.org/alexl/2011/03/15/gtk-html-backend-update/), [OpenNebula](http://opennebula.org/), [CloudSigma](http://www.cloudsigma.com/), [Zentyal (formerly eBox)](http://www.zentyal.org/), [SlapOS](http://www.slapos.org), [Intel MeshCentral](https://meshcentral.com), [Amahi](http://amahi.org), [Brightbox](http://brightbox.com/), [Foreman](http://theforeman.org) and [LibVNCServer](http://libvncserver.sourceforge.net). See [this wiki page](https://github.com/kanaka/noVNC/wiki/ProjectsCompanies-using-noVNC) for more info and links.
Notable commits, announcements and news are posted to
@<a href="http://www.twitter.com/noVNC">noVNC</a>
### Features
* Supports all modern browsers including mobile (iOS, Android)
* Supported VNC encodings: raw, copyrect, rre, hextile, tight, tightPNG
* WebSocket SSL/TLS encryption (i.e. "wss://") support
* 24-bit true color and 8 bit colour mapped
* Supports desktop resize notification/pseudo-encoding
* Local or remote cursor
* Clipboard copy/paste
* Clipping or scolling modes for large remote screens
* Easy site integration and theming (3 example themes included)
* Licensed under the [LGPLv3](http://www.gnu.org/licenses/lgpl.html)
### Screenshots ### Screenshots
...@@ -38,10 +46,8 @@ See more screenshots <a href="http://kanaka.github.com/noVNC/screenshots.html">h ...@@ -38,10 +46,8 @@ See more screenshots <a href="http://kanaka.github.com/noVNC/screenshots.html">h
a WebSockets emulator using Adobe Flash. iOS 4.2+ has built-in a WebSockets emulator using Adobe Flash. iOS 4.2+ has built-in
WebSocket support. WebSocket support.
* Fast Javascript Engine: noVNC avoids using new Javascript * Fast Javascript Engine: this is not strictly a requirement, but
functionality so it will run on older browsers, but decode and without a fast Javascript engine, noVNC might be painfully slow.
rendering happen in Javascript, so a slow Javascript engine will
mean noVNC is painfully slow.
* I maintain a more detailed browser compatibility list <a * I maintain a more detailed browser compatibility list <a
href="https://github.com/kanaka/noVNC/wiki/Browser-support">here</a>. href="https://github.com/kanaka/noVNC/wiki/Browser-support">here</a>.
...@@ -50,22 +56,9 @@ See more screenshots <a href="http://kanaka.github.com/noVNC/screenshots.html">h ...@@ -50,22 +56,9 @@ See more screenshots <a href="http://kanaka.github.com/noVNC/screenshots.html">h
### Server Requirements ### Server Requirements
Unless you are using a VNC server with support for WebSockets Unless you are using a VNC server with support for WebSockets
connections (only my [fork of libvncserver](http://github.com/kanaka/libvncserver) connections (such as [x11vnc/libvncserver](http://libvncserver.sourceforge.net/)),
currently), you need to use a WebSockets to TCP socket proxy. There is you need to use a WebSockets to TCP socket proxy. There is
a python proxy included ('websockify'). One advantage of using the a python proxy included ('websockify').
proxy is that it has builtin support for SSL/TLS encryption (i.e.
"wss://").
There a few reasons why a proxy is required:
1. WebSockets is not a pure socket protocol. There is an initial HTTP
like handshake to allow easy hand-off by web servers and allow
some origin policy exchange. Also, each WebSockets frame begins
with 0 ('\x00') and ends with 255 ('\xff').
2. Javascript itself does not have the ability to handle pure byte
arrays. The python proxy encodes the data as base64 so that the
Javascript client can decode the data as an integer array.
### Quick Start ### Quick Start
...@@ -91,3 +84,19 @@ There a few reasons why a proxy is required: ...@@ -91,3 +84,19 @@ There a few reasons why a proxy is required:
* [Troubleshooting noVNC](https://github.com/kanaka/noVNC/wiki/Troubleshooting) problems. * [Troubleshooting noVNC](https://github.com/kanaka/noVNC/wiki/Troubleshooting) problems.
### Authors/Contributors
* noVNC : Joel Martin (github.com/kanaka)
* New UI and Icons : Chris Gordon
* Original Logo : Michael Sersen
* tight encoding : Michael Tinglof (Mercuri.ca)
* Included libraries:
* web-socket-js : Hiroshi Ichikawa (github.com/gimite/web-socket-js)
* as3crypto : Henri Torgemane (code.google.com/p/as3crypto)
* base64 : Martijn Pieters (Digital Creations 2), Samuel Sieb (sieb.net)
* jsunzip : Erik Moller (github.com/operasoftware/jsunzip),
* tinflate : Joergen Ibsen (ibsensoftware.com)
* DES : Dave Zimmerman (Widget Workshop), Jef Poskanzer (ACME Labs)
...@@ -153,6 +153,7 @@ html { ...@@ -153,6 +153,7 @@ html {
} }
#noVNC_controls { #noVNC_controls {
display:none;
margin-top:77px; margin-top:77px;
right:12px; right:12px;
position:fixed; position:fixed;
...@@ -161,6 +162,23 @@ html { ...@@ -161,6 +162,23 @@ html {
right:15px; right:15px;
} }
#noVNC_description {
display:none;
position:fixed;
margin-top:77px;
right:20px;
left:20px;
padding:15px;
color:#000;
background:#eee; /* default background for browsers without gradient support */
border:2px solid #E0E0E0;
-webkit-border-radius:10px;
-moz-border-radius:10px;
border-radius:10px;
}
#noVNC_clipboard { #noVNC_clipboard {
display:none; display:none;
margin-top:77px; margin-top:77px;
......
...@@ -116,7 +116,7 @@ decode: function (data, offset) { ...@@ -116,7 +116,7 @@ decode: function (data, offset) {
padding = (data.charAt(i) === pad); padding = (data.charAt(i) === pad);
// Skip illegal characters and whitespace // Skip illegal characters and whitespace
if (c === -1) { if (c === -1) {
console.error("Illegal character '" + data.charCodeAt(i) + "'"); console.error("Illegal character code " + data.charCodeAt(i) + " at position " + i);
continue; continue;
} }
......
...@@ -20,7 +20,7 @@ var that = {}, // Public API methods ...@@ -20,7 +20,7 @@ var that = {}, // Public API methods
c_forceCanvas = false, c_forceCanvas = false,
// Predefine function variables (jslint) // Predefine function variables (jslint)
imageDataGet, rgbxImageData, cmapImageData, imageDataGet, rgbImageData, bgrxImageData, cmapImageData,
setFillColor, rescale, setFillColor, rescale,
// The full frame buffer (logical canvas) size // The full frame buffer (logical canvas) size
...@@ -183,13 +183,13 @@ rescale = function(factor) { ...@@ -183,13 +183,13 @@ rescale = function(factor) {
}; };
setFillColor = function(color) { setFillColor = function(color) {
var rgb, newStyle; var bgr, newStyle;
if (conf.true_color) { if (conf.true_color) {
rgb = color; bgr = color;
} else { } else {
rgb = conf.colourMap[color[0]]; bgr = conf.colourMap[color[0]];
} }
newStyle = "rgb(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + ")"; newStyle = "rgb(" + bgr[2] + "," + bgr[1] + "," + bgr[0] + ")";
if (newStyle !== c_prevStyle) { if (newStyle !== c_prevStyle) {
c_ctx.fillStyle = newStyle; c_ctx.fillStyle = newStyle;
c_prevStyle = newStyle; c_prevStyle = newStyle;
...@@ -386,10 +386,10 @@ that.getCleanDirtyReset = function() { ...@@ -386,10 +386,10 @@ that.getCleanDirtyReset = function() {
// Translate viewport coordinates to absolute coordinates // Translate viewport coordinates to absolute coordinates
that.absX = function(x) { that.absX = function(x) {
return x + viewport.x; return x + viewport.x;
} };
that.absY = function(y) { that.absY = function(y) {
return y + viewport.y; return y + viewport.y;
} };
that.resize = function(width, height) { that.resize = function(width, height) {
...@@ -430,7 +430,7 @@ that.copyImage = function(old_x, old_y, new_x, new_y, w, h) { ...@@ -430,7 +430,7 @@ that.copyImage = function(old_x, old_y, new_x, new_y, w, h) {
// Start updating a tile // Start updating a tile
that.startTile = function(x, y, width, height, color) { that.startTile = function(x, y, width, height, color) {
var data, rgb, red, green, blue, i; var data, bgr, red, green, blue, i;
tile_x = x; tile_x = x;
tile_y = y; tile_y = y;
if ((width === 16) && (height === 16)) { if ((width === 16) && (height === 16)) {
...@@ -441,13 +441,13 @@ that.startTile = function(x, y, width, height, color) { ...@@ -441,13 +441,13 @@ that.startTile = function(x, y, width, height, color) {
data = tile.data; data = tile.data;
if (conf.prefer_js) { if (conf.prefer_js) {
if (conf.true_color) { if (conf.true_color) {
rgb = color; bgr = color;
} else { } else {
rgb = conf.colourMap[color[0]]; bgr = conf.colourMap[color[0]];
} }
red = rgb[0]; red = bgr[2];
green = rgb[1]; green = bgr[1];
blue = rgb[2]; blue = bgr[0];
for (i = 0; i < (width * height * 4); i+=4) { for (i = 0; i < (width * height * 4); i+=4) {
data[i ] = red; data[i ] = red;
data[i + 1] = green; data[i + 1] = green;
...@@ -461,18 +461,18 @@ that.startTile = function(x, y, width, height, color) { ...@@ -461,18 +461,18 @@ that.startTile = function(x, y, width, height, color) {
// Update sub-rectangle of the current tile // Update sub-rectangle of the current tile
that.subTile = function(x, y, w, h, color) { that.subTile = function(x, y, w, h, color) {
var data, p, rgb, red, green, blue, width, j, i, xend, yend; var data, p, bgr, red, green, blue, width, j, i, xend, yend;
if (conf.prefer_js) { if (conf.prefer_js) {
data = tile.data; data = tile.data;
width = tile.width; width = tile.width;
if (conf.true_color) { if (conf.true_color) {
rgb = color; bgr = color;
} else { } else {
rgb = conf.colourMap[color[0]]; bgr = conf.colourMap[color[0]];
} }
red = rgb[0]; red = bgr[2];
green = rgb[1]; green = bgr[1];
blue = rgb[2]; blue = bgr[0];
xend = x + w; xend = x + w;
yend = y + h; yend = y + h;
for (j = y; j < yend; j += 1) { for (j = y; j < yend; j += 1) {
...@@ -492,12 +492,12 @@ that.subTile = function(x, y, w, h, color) { ...@@ -492,12 +492,12 @@ that.subTile = function(x, y, w, h, color) {
// Draw the current tile to the screen // Draw the current tile to the screen
that.finishTile = function() { that.finishTile = function() {
if (conf.prefer_js) { if (conf.prefer_js) {
c_ctx.putImageData(tile, tile_x - viewport.x, tile_y - viewport.y) c_ctx.putImageData(tile, tile_x - viewport.x, tile_y - viewport.y);
} }
// else: No-op, if not prefer_js then already done by setSubTile // else: No-op, if not prefer_js then already done by setSubTile
}; };
rgbxImageData = function(x, y, width, height, arr, offset) { rgbImageData = function(x, y, width, height, arr, offset) {
var img, i, j, data, v = viewport; var img, i, j, data, v = viewport;
/* /*
if ((x - v.x >= v.w) || (y - v.y >= v.h) || if ((x - v.x >= v.w) || (y - v.y >= v.h) ||
...@@ -508,7 +508,7 @@ rgbxImageData = function(x, y, width, height, arr, offset) { ...@@ -508,7 +508,7 @@ rgbxImageData = function(x, y, width, height, arr, offset) {
*/ */
img = c_ctx.createImageData(width, height); img = c_ctx.createImageData(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+3) {
data[i ] = arr[j ]; data[i ] = arr[j ];
data[i + 1] = arr[j + 1]; data[i + 1] = arr[j + 1];
data[i + 2] = arr[j + 2]; data[i + 2] = arr[j + 2];
...@@ -517,16 +517,36 @@ rgbxImageData = function(x, y, width, height, arr, offset) { ...@@ -517,16 +517,36 @@ rgbxImageData = function(x, y, width, height, arr, offset) {
c_ctx.putImageData(img, x - v.x, y - v.y); c_ctx.putImageData(img, x - v.x, y - v.y);
}; };
bgrxImageData = function(x, y, width, height, arr, offset) {
var img, i, j, data, v = viewport;
/*
if ((x - v.x >= v.w) || (y - v.y >= v.h) ||
(x - v.x + width < 0) || (y - v.y + height < 0)) {
// Skipping because outside of viewport
return;
}
*/
img = c_ctx.createImageData(width, height);
data = img.data;
for (i=0, j=offset; i < (width * height * 4); i=i+4, j=j+4) {
data[i ] = arr[j + 2];
data[i + 1] = arr[j + 1];
data[i + 2] = arr[j ];
data[i + 3] = 255; // Set Alpha
}
c_ctx.putImageData(img, x - v.x, y - v.y);
};
cmapImageData = function(x, y, width, height, arr, offset) { cmapImageData = function(x, y, width, height, arr, offset) {
var img, i, j, data, rgb, cmap; var img, i, j, data, bgr, cmap;
img = c_ctx.createImageData(width, height); img = c_ctx.createImageData(width, height);
data = img.data; data = img.data;
cmap = conf.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]]; bgr = cmap[arr[j]];
data[i ] = rgb[0]; data[i ] = bgr[2];
data[i + 1] = rgb[1]; data[i + 1] = bgr[1];
data[i + 2] = rgb[2]; data[i + 2] = bgr[0];
data[i + 3] = 255; // Set Alpha data[i + 3] = 255; // Set Alpha
} }
c_ctx.putImageData(img, x - viewport.x, y - viewport.y); c_ctx.putImageData(img, x - viewport.x, y - viewport.y);
...@@ -534,8 +554,17 @@ cmapImageData = function(x, y, width, height, arr, offset) { ...@@ -534,8 +554,17 @@ cmapImageData = function(x, y, width, height, arr, offset) {
that.blitImage = function(x, y, width, height, arr, offset) { that.blitImage = function(x, y, width, height, arr, offset) {
if (conf.true_color) { if (conf.true_color) {
rgbxImageData(x, y, width, height, arr, offset); bgrxImageData(x, y, width, height, arr, offset);
} else {
cmapImageData(x, y, width, height, arr, offset);
}
};
that.blitRgbImage = function(x, y, width, height, arr, offset) {
if (conf.true_color) {
rgbImageData(x, y, width, height, arr, offset);
} else { } else {
// prolly wrong...
cmapImageData(x, y, width, height, arr, offset); cmapImageData(x, y, width, height, arr, offset);
} }
}; };
......
...@@ -412,6 +412,26 @@ function onKeyUp(e) { ...@@ -412,6 +412,26 @@ function onKeyUp(e) {
return false; return false;
} }
function allKeysUp() {
Util.Debug(">> Keyboard.allKeysUp");
if (keyDownList.length > 0) {
Util.Info("Releasing pressed/down keys");
}
var i, keysym, fevt = null;
for (i = keyDownList.length-1; i >= 0; i--) {
fevt = keyDownList.splice(i, 1)[0];
keysym = fevt.keysym;
if (conf.onKeyPress && (keysym > 0)) {
Util.Debug("allKeysUp, keysym: " + keysym +
" (keyCode: " + fevt.keyCode +
", which: " + fevt.which + ")");
conf.onKeyPress(keysym, 0, fevt);
}
}
Util.Debug("<< Keyboard.allKeysUp");
return;
}
// //
// Public API interface functions // Public API interface functions
// //
...@@ -424,6 +444,9 @@ that.grab = function() { ...@@ -424,6 +444,9 @@ that.grab = function() {
Util.addEvent(c, 'keyup', onKeyUp); Util.addEvent(c, 'keyup', onKeyUp);
Util.addEvent(c, 'keypress', onKeyPress); Util.addEvent(c, 'keypress', onKeyPress);
// Release (key up) if window loses focus
Util.addEvent(window, 'blur', allKeysUp);
//Util.Debug("<< Keyboard.grab"); //Util.Debug("<< Keyboard.grab");
}; };
...@@ -434,6 +457,10 @@ that.ungrab = function() { ...@@ -434,6 +457,10 @@ that.ungrab = function() {
Util.removeEvent(c, 'keydown', onKeyDown); Util.removeEvent(c, 'keydown', onKeyDown);
Util.removeEvent(c, 'keyup', onKeyUp); Util.removeEvent(c, 'keyup', onKeyUp);
Util.removeEvent(c, 'keypress', onKeyPress); Util.removeEvent(c, 'keypress', onKeyPress);
Util.removeEvent(window, 'blur', allKeysUp);
// Release (key up) all keys that are in a down state
allKeysUp();
//Util.Debug(">> Keyboard.ungrab"); //Util.Debug(">> Keyboard.ungrab");
}; };
......
This diff is collapsed.
This diff is collapsed.
...@@ -14,7 +14,7 @@ var UI = { ...@@ -14,7 +14,7 @@ var UI = {
rfb_state : 'loaded', rfb_state : 'loaded',
settingsOpen : false, settingsOpen : false,
connSettingsOpen : true, connSettingsOpen : false,
clipboardOpen: false, clipboardOpen: false,
keyboardVisible: false, keyboardVisible: false,
...@@ -45,15 +45,16 @@ load: function() { ...@@ -45,15 +45,16 @@ load: function() {
WebUtil.selectStylesheet(UI.getSetting('stylesheet')); WebUtil.selectStylesheet(UI.getSetting('stylesheet'));
/* Populate the controls if defaults are provided in the URL */ /* Populate the controls if defaults are provided in the URL */
UI.initSetting('host', ''); UI.initSetting('host', window.location.hostname);
UI.initSetting('port', ''); UI.initSetting('port', window.location.port);
UI.initSetting('password', ''); UI.initSetting('password', '');
UI.initSetting('encrypt', false); UI.initSetting('encrypt', (window.location.protocol === "https:"));
UI.initSetting('true_color', true); UI.initSetting('true_color', true);
UI.initSetting('cursor', false); UI.initSetting('cursor', false);
UI.initSetting('shared', true); UI.initSetting('shared', true);
UI.initSetting('view_only', false);
UI.initSetting('connectTimeout', 2); UI.initSetting('connectTimeout', 2);
UI.initSetting('path', ''); UI.initSetting('path', 'websockify');
UI.rfb = RFB({'target': $D('noVNC_canvas'), UI.rfb = RFB({'target': $D('noVNC_canvas'),
'onUpdateState': UI.updateState, 'onUpdateState': UI.updateState,
...@@ -101,6 +102,14 @@ load: function() { ...@@ -101,6 +102,14 @@ load: function() {
} }
} ); } );
// Show description by default when hosted at for kanaka.github.com
if (location.host === "kanaka.github.com") {
// Open the description dialog
$D('noVNC_description').style.display = "block";
} else {
// Open the connect panel on first load
UI.toggleConnectPanel();
}
}, },
// Read form control compatible setting from cookie // Read form control compatible setting from cookie
...@@ -188,17 +197,19 @@ forceSetting: function(name, val) { ...@@ -188,17 +197,19 @@ forceSetting: function(name, val) {
// Show the clipboard panel // Show the clipboard panel
toggleClipboardPanel: function() { toggleClipboardPanel: function() {
// Close the description panel
$D('noVNC_description').style.display = "none";
//Close settings if open //Close settings if open
if (UI.settingsOpen == true) { if (UI.settingsOpen === true) {
UI.settingsApply(); UI.settingsApply();
UI.closeSettingsMenu(); UI.closeSettingsMenu();
} }
//Close connection settings if open //Close connection settings if open
if (UI.connSettingsOpen == true) { if (UI.connSettingsOpen === true) {
UI.toggleConnectPanel(); UI.toggleConnectPanel();
} }
//Toggle Clipboard Panel //Toggle Clipboard Panel
if (UI.clipboardOpen == true) { if (UI.clipboardOpen === true) {
$D('noVNC_clipboard').style.display = "none"; $D('noVNC_clipboard').style.display = "none";
$D('clipboardButton').className = "noVNC_status_button"; $D('clipboardButton').className = "noVNC_status_button";
UI.clipboardOpen = false; UI.clipboardOpen = false;
...@@ -211,18 +222,20 @@ toggleClipboardPanel: function() { ...@@ -211,18 +222,20 @@ toggleClipboardPanel: function() {
// Show the connection settings panel/menu // Show the connection settings panel/menu
toggleConnectPanel: function() { toggleConnectPanel: function() {
// Close the description panel
$D('noVNC_description').style.display = "none";
//Close connection settings if open //Close connection settings if open
if (UI.settingsOpen == true) { if (UI.settingsOpen === true) {
UI.settingsApply(); UI.settingsApply();
UI.closeSettingsMenu(); UI.closeSettingsMenu();
$D('connectButton').className = "noVNC_status_button"; $D('connectButton').className = "noVNC_status_button";
} }
if (UI.clipboardOpen == true) { if (UI.clipboardOpen === true) {
UI.toggleClipboardPanel(); UI.toggleClipboardPanel();
} }
//Toggle Connection Panel //Toggle Connection Panel
if (UI.connSettingsOpen == true) { if (UI.connSettingsOpen === true) {
$D('noVNC_controls').style.display = "none"; $D('noVNC_controls').style.display = "none";
$D('connectButton').className = "noVNC_status_button"; $D('connectButton').className = "noVNC_status_button";
UI.connSettingsOpen = false; UI.connSettingsOpen = false;
...@@ -238,6 +251,8 @@ toggleConnectPanel: function() { ...@@ -238,6 +251,8 @@ toggleConnectPanel: function() {
// On open, settings are refreshed from saved cookies. // On open, settings are refreshed from saved cookies.
// On close, settings are applied // On close, settings are applied
toggleSettingsPanel: function() { toggleSettingsPanel: function() {
// Close the description panel
$D('noVNC_description').style.display = "none";
if (UI.settingsOpen) { if (UI.settingsOpen) {
UI.settingsApply(); UI.settingsApply();
UI.closeSettingsMenu(); UI.closeSettingsMenu();
...@@ -252,6 +267,7 @@ toggleSettingsPanel: function() { ...@@ -252,6 +267,7 @@ toggleSettingsPanel: function() {
} }
UI.updateSetting('clip'); UI.updateSetting('clip');
UI.updateSetting('shared'); UI.updateSetting('shared');
UI.updateSetting('view_only');
UI.updateSetting('connectTimeout'); UI.updateSetting('connectTimeout');
UI.updateSetting('path'); UI.updateSetting('path');
UI.updateSetting('stylesheet'); UI.updateSetting('stylesheet');
...@@ -263,11 +279,13 @@ toggleSettingsPanel: function() { ...@@ -263,11 +279,13 @@ toggleSettingsPanel: function() {
// Open menu // Open menu
openSettingsMenu: function() { openSettingsMenu: function() {
if (UI.clipboardOpen == true) { // Close the description panel
$D('noVNC_description').style.display = "none";
if (UI.clipboardOpen === true) {
UI.toggleClipboardPanel(); UI.toggleClipboardPanel();
} }
//Close connection settings if open //Close connection settings if open
if (UI.connSettingsOpen == true) { if (UI.connSettingsOpen === true) {
UI.toggleConnectPanel(); UI.toggleConnectPanel();
} }
$D('noVNC_settings').style.display = "block"; $D('noVNC_settings').style.display = "block";
...@@ -292,6 +310,7 @@ settingsApply: function() { ...@@ -292,6 +310,7 @@ settingsApply: function() {
} }
UI.saveSetting('clip'); UI.saveSetting('clip');
UI.saveSetting('shared'); UI.saveSetting('shared');
UI.saveSetting('view_only');
UI.saveSetting('connectTimeout'); UI.saveSetting('connectTimeout');
UI.saveSetting('path'); UI.saveSetting('path');
UI.saveSetting('stylesheet'); UI.saveSetting('stylesheet');
...@@ -363,6 +382,7 @@ updateState: function(rfb, state, oldstate, msg) { ...@@ -363,6 +382,7 @@ updateState: function(rfb, state, oldstate, msg) {
break; break;
case 'disconnected': case 'disconnected':
$D('noVNC_logo').style.display = "block"; $D('noVNC_logo').style.display = "block";
// Fall through
case 'loaded': case 'loaded':
klass = "noVNC_status_normal"; klass = "noVNC_status_normal";
break; break;
...@@ -404,16 +424,19 @@ updateVisualState: function() { ...@@ -404,16 +424,19 @@ updateVisualState: function() {
$D('noVNC_cursor').disabled = true; $D('noVNC_cursor').disabled = true;
} }
$D('noVNC_shared').disabled = connected; $D('noVNC_shared').disabled = connected;
$D('noVNC_view_only').disabled = connected;
$D('noVNC_connectTimeout').disabled = connected; $D('noVNC_connectTimeout').disabled = connected;
$D('noVNC_path').disabled = connected; $D('noVNC_path').disabled = connected;
if (connected) { if (connected) {
UI.setViewClip(); UI.setViewClip();
UI.setMouseButton(1); UI.setMouseButton(1);
$D('clipboardButton').style.display = "inline";
$D('showKeyboard').style.display = "inline"; $D('showKeyboard').style.display = "inline";
$D('sendCtrlAltDelButton').style.display = "inline"; $D('sendCtrlAltDelButton').style.display = "inline";
} else { } else {
UI.setMouseButton(); UI.setMouseButton();
$D('clipboardButton').style.display = "none";
$D('showKeyboard').style.display = "none"; $D('showKeyboard').style.display = "none";
$D('sendCtrlAltDelButton').style.display = "none"; $D('sendCtrlAltDelButton').style.display = "none";
} }
...@@ -464,6 +487,7 @@ connect: function() { ...@@ -464,6 +487,7 @@ connect: function() {
UI.rfb.set_true_color(UI.getSetting('true_color')); UI.rfb.set_true_color(UI.getSetting('true_color'));
UI.rfb.set_local_cursor(UI.getSetting('cursor')); UI.rfb.set_local_cursor(UI.getSetting('cursor'));
UI.rfb.set_shared(UI.getSetting('shared')); UI.rfb.set_shared(UI.getSetting('shared'));
UI.rfb.set_view_only(UI.getSetting('view_only'));
UI.rfb.set_connectTimeout(UI.getSetting('connectTimeout')); UI.rfb.set_connectTimeout(UI.getSetting('connectTimeout'));
UI.rfb.connect(host, port, password, path); UI.rfb.connect(host, port, password, path);
...@@ -569,11 +593,11 @@ setViewDrag: function(drag) { ...@@ -569,11 +593,11 @@ setViewDrag: function(drag) {
// On touch devices, show the OS keyboard // On touch devices, show the OS keyboard
showKeyboard: function() { showKeyboard: function() {
if(UI.keyboardVisible == false) { if(UI.keyboardVisible === false) {
$D('keyboardinput').focus(); $D('keyboardinput').focus();
UI.keyboardVisible = true; UI.keyboardVisible = true;
$D('showKeyboard').className = "noVNC_status_button_selected"; $D('showKeyboard').className = "noVNC_status_button_selected";
} else if(UI.keyboardVisible == true) { } else if(UI.keyboardVisible === true) {
$D('keyboardinput').blur(); $D('keyboardinput').blur();
$D('showKeyboard').className = "noVNC_status_button"; $D('showKeyboard').className = "noVNC_status_button";
UI.keyboardVisible = false; UI.keyboardVisible = false;
...@@ -585,7 +609,7 @@ keyInputBlur: function() { ...@@ -585,7 +609,7 @@ keyInputBlur: function() {
//Weird bug in iOS if you change keyboardVisible //Weird bug in iOS if you change keyboardVisible
//here it does not actually occur so next time //here it does not actually occur so next time
//you click keyboard icon it doesnt work. //you click keyboard icon it doesnt work.
setTimeout("UI.setKeyboard()",100) setTimeout(function() { UI.setKeyboard(); },100);
}, },
setKeyboard: function() { setKeyboard: function() {
......
...@@ -33,6 +33,30 @@ Array.prototype.push32 = function (num) { ...@@ -33,6 +33,30 @@ Array.prototype.push32 = function (num) {
(num ) & 0xFF ); (num ) & 0xFF );
}; };
// IE does not support map (even in IE9)
//This prototype is provided by the Mozilla foundation and
//is distributed under the MIT license.
//http://www.ibiblio.org/pub/Linux/LICENSES/mit.license
if (!Array.prototype.map)
{
Array.prototype.map = function(fun /*, thisp*/)
{
var len = this.length;
if (typeof fun != "function")
throw new TypeError();
var res = new Array(len);
var thisp = arguments[1];
for (var i = 0; i < len; i++)
{
if (i in this)
res[i] = fun.call(thisp, this[i], i, this);
}
return res;
};
}
/* /*
* ------------------------------------------------------ * ------------------------------------------------------
* Namespaced in Util * Namespaced in Util
...@@ -159,7 +183,7 @@ Util.conf_defaults = function(cfg, api, defaults, arr) { ...@@ -159,7 +183,7 @@ Util.conf_defaults = function(cfg, api, defaults, arr) {
Util.conf_default(cfg, api, defaults, arr[i][0], arr[i][1], Util.conf_default(cfg, api, defaults, arr[i][0], arr[i][1],
arr[i][2], arr[i][3], arr[i][4]); arr[i][2], arr[i][3], arr[i][4]);
} }
} };
/* /*
...@@ -240,8 +264,11 @@ Util.stopEvent = function(e) { ...@@ -240,8 +264,11 @@ Util.stopEvent = function(e) {
Util.Features = {xpath: !!(document.evaluate), air: !!(window.runtime), query: !!(document.querySelector)}; Util.Features = {xpath: !!(document.evaluate), air: !!(window.runtime), query: !!(document.querySelector)};
Util.Engine = { Util.Engine = {
'presto': (function() { // Version detection break in Opera 11.60 (errors on arguments.callee.caller reference)
return (!window.opera) ? false : ((arguments.callee.caller) ? 960 : ((document.getElementsByClassName) ? 950 : 925)); }()), //'presto': (function() {
// return (!window.opera) ? false : ((arguments.callee.caller) ? 960 : ((document.getElementsByClassName) ? 950 : 925)); }()),
'presto': (function() { return (!window.opera) ? false : true; }()),
'trident': (function() { 'trident': (function() {
return (!window.ActiveXObject) ? false : ((window.XMLHttpRequest) ? ((document.querySelectorAll) ? 6 : 5) : 4); }()), return (!window.ActiveXObject) ? false : ((window.XMLHttpRequest) ? ((document.querySelectorAll) ? 6 : 5) : 4); }()),
'webkit': (function() { 'webkit': (function() {
......
...@@ -36,6 +36,7 @@ function get_INCLUDE_URI() { ...@@ -36,6 +36,7 @@ function get_INCLUDE_URI() {
extra += start + "input.js" + end; extra += start + "input.js" + end;
extra += start + "display.js" + end; extra += start + "display.js" + end;
extra += start + "rfb.js" + end; extra += start + "rfb.js" + end;
extra += start + "jsunzip.js" + end;
document.write(extra); document.write(extra);
}()); }());
......
...@@ -14,16 +14,23 @@ ...@@ -14,16 +14,23 @@
* read binary data off of the receive queue. * read binary data off of the receive queue.
*/ */
/*jslint browser: true, bitwise: false, plusplus: false */
/*global Util, Base64 */
// Load Flash WebSocket emulator if needed // Load Flash WebSocket emulator if needed
if (window.WebSocket) { if (window.WebSocket && !window.WEB_SOCKET_FORCE_FLASH) {
Websock_native = true; Websock_native = true;
} else if (window.MozWebSocket) { } else if (window.MozWebSocket && !window.WEB_SOCKET_FORCE_FLASH) {
Websock_native = true; Websock_native = true;
window.WebSocket = window.MozWebSocket; window.WebSocket = window.MozWebSocket;
} else { } else {
/* no builtin WebSocket so load web_socket.js */ /* no builtin WebSocket so load web_socket.js */
// To enable debug:
// window.WEB_SOCKET_DEBUG=1;
Websock_native = false; Websock_native = false;
(function () { (function () {
function get_INCLUDE_URI() { function get_INCLUDE_URI() {
...@@ -34,11 +41,11 @@ if (window.WebSocket) { ...@@ -34,11 +41,11 @@ if (window.WebSocket) {
var start = "<script src='" + get_INCLUDE_URI(), var start = "<script src='" + get_INCLUDE_URI(),
end = "'><\/script>", extra = ""; end = "'><\/script>", extra = "";
WEB_SOCKET_SWF_LOCATION = get_INCLUDE_URI() + window.WEB_SOCKET_SWF_LOCATION = get_INCLUDE_URI() +
"web-socket-js/WebSocketMain.swf"; "web-socket-js/WebSocketMain.swf";
if (Util.Engine.trident) { if (Util.Engine.trident) {
Util.Debug("Forcing uncached load of WebSocketMain.swf"); Util.Debug("Forcing uncached load of WebSocketMain.swf");
WEB_SOCKET_SWF_LOCATION += "?" + Math.random(); window.WEB_SOCKET_SWF_LOCATION += "?" + Math.random();
} }
extra += start + "web-socket-js/swfobject.js" + end; extra += start + "web-socket-js/swfobject.js" + end;
extra += start + "web-socket-js/web_socket.js" + end; extra += start + "web-socket-js/web_socket.js" + end;
...@@ -83,7 +90,7 @@ function get_rQi() { ...@@ -83,7 +90,7 @@ function get_rQi() {
} }
function set_rQi(val) { function set_rQi(val) {
rQi = val; rQi = val;
}; }
function rQlen() { function rQlen() {
return rQ.length - rQi; return rQ.length - rQi;
...@@ -115,6 +122,7 @@ function rQshift32() { ...@@ -115,6 +122,7 @@ function rQshift32() {
(rQ[rQi++] ); (rQ[rQi++] );
} }
function rQshiftStr(len) { function rQshiftStr(len) {
if (typeof(len) === 'undefined') { len = rQlen(); }
var arr = rQ.slice(rQi, rQi + len); var arr = rQ.slice(rQi, rQi + len);
rQi += len; rQi += len;
return arr.map(function (num) { return arr.map(function (num) {
...@@ -122,6 +130,7 @@ function rQshiftStr(len) { ...@@ -122,6 +130,7 @@ function rQshiftStr(len) {
} }
function rQshiftBytes(len) { function rQshiftBytes(len) {
if (typeof(len) === 'undefined') { len = rQlen(); }
rQi += len; rQi += len;
return rQ.slice(rQi-len, rQi); return rQ.slice(rQi-len, rQi);
} }
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
"use strict"; "use strict";
/*jslint bitwise: false, white: false */ /*jslint bitwise: false, white: false */
/*global window, document */ /*global Util, window, document */
// Globals defined here // Globals defined here
var WebUtil = {}, $D; var WebUtil = {}, $D;
...@@ -17,7 +17,7 @@ var WebUtil = {}, $D; ...@@ -17,7 +17,7 @@ var WebUtil = {}, $D;
* Simple DOM selector by ID * Simple DOM selector by ID
*/ */
if (!window.$D) { if (!window.$D) {
$D = function (id) { window.$D = function (id) {
if (document.getElementById) { if (document.getElementById) {
return document.getElementById(id); return document.getElementById(id);
} else if (document.all) { } else if (document.all) {
...@@ -42,8 +42,8 @@ WebUtil.init_logging = function() { ...@@ -42,8 +42,8 @@ WebUtil.init_logging = function() {
/logging=([A-Za-z0-9\._\-]*)/) || /logging=([A-Za-z0-9\._\-]*)/) ||
['', Util._log_level])[1]; ['', Util._log_level])[1];
Util.init_logging() Util.init_logging();
} };
WebUtil.init_logging(); WebUtil.init_logging();
......
...@@ -90,7 +90,7 @@ ...@@ -90,7 +90,7 @@
title="Settings" title="Settings"
onclick="UI.toggleSettingsPanel();" /> onclick="UI.toggleSettingsPanel();" />
<input type="image" src="images/connect.png" <input type="image" src="images/connect.png"
id="connectButton" class="noVNC_status_button_selected" id="connectButton" class="noVNC_status_button"
title="Connect" title="Connect"
onclick="UI.toggleConnectPanel()" /> onclick="UI.toggleConnectPanel()" />
<input type="image" src="images/disconnect.png" <input type="image" src="images/disconnect.png"
...@@ -99,6 +99,23 @@ ...@@ -99,6 +99,23 @@
onclick="UI.disconnect()" /> onclick="UI.disconnect()" />
</div> </div>
<!-- Description Panel -->
<!-- Shown by default when hosted at for kanaka.github.com -->
<div id="noVNC_description" style="display:none;" class="">
noVNC is a browser based VNC client implemented using HTML5 Canvas
and WebSockets. You will either need a VNC server with WebSockets
support (such as <a href="http://libvncserver.sourceforge.net/">libvncserver</a>)
or you will need to use
<a href="https://github.com/kanaka/websockify">websockify</a>
to bridge between your browser and VNC server. See the noVNC
<a href="https://github.com/kanaka/noVNC">README</a>
and <a href="http://kanaka.github.com/noVNC">website</a>
for more information.
<br />
<input type="button" value="Close"
onclick="UI.toggleConnectPanel();">
</div>
<!-- Clipboard Panel --> <!-- Clipboard Panel -->
<div id="noVNC_clipboard" class="triangle-right top"> <div id="noVNC_clipboard" class="triangle-right top">
<textarea id="noVNC_clipboard_text" rows=5 <textarea id="noVNC_clipboard_text" rows=5
...@@ -118,10 +135,11 @@ ...@@ -118,10 +135,11 @@
<li><input id="noVNC_encrypt" type="checkbox"> Encrypt</li> <li><input id="noVNC_encrypt" type="checkbox"> Encrypt</li>
<li><input id="noVNC_true_color" type="checkbox" checked> True Color</li> <li><input id="noVNC_true_color" type="checkbox" checked> True Color</li>
<li><input id="noVNC_cursor" type="checkbox"> Local Cursor</li> <li><input id="noVNC_cursor" type="checkbox"> Local Cursor</li>
<li><input id="noVNC_clip" type="checkbox"> Clip to window</li> <li><input id="noVNC_clip" type="checkbox"> Clip to Window</li>
<li><input id="noVNC_shared" type="checkbox"> Shared Mode</li> <li><input id="noVNC_shared" type="checkbox"> Shared Mode</li>
<li><input id="noVNC_view_only" type="checkbox"> View Only</li>
<li><input id="noVNC_connectTimeout" type="input"> Connect Timeout (s)</li> <li><input id="noVNC_connectTimeout" type="input"> Connect Timeout (s)</li>
<li><input id="noVNC_path" type="input"> Path</li> <li><input id="noVNC_path" type="input" value="websockify"> Path</li>
<hr> <hr>
<!-- Stylesheet selection dropdown --> <!-- Stylesheet selection dropdown -->
<li><label><strong>Style: </strong> <li><label><strong>Style: </strong>
......
...@@ -84,16 +84,25 @@ ...@@ -84,16 +84,25 @@
} }
window.onload = function () { window.onload = function () {
var host, port, password, path; var host, port, password, path, token;
$D('sendCtrlAltDelButton').style.display = "inline"; $D('sendCtrlAltDelButton').style.display = "inline";
$D('sendCtrlAltDelButton').onclick = sendCtrlAltDel; $D('sendCtrlAltDelButton').onclick = sendCtrlAltDel;
document.title = unescape(WebUtil.getQueryVar('title', 'noVNC')); document.title = unescape(WebUtil.getQueryVar('title', 'noVNC'));
host = WebUtil.getQueryVar('host', null); // By default, use the host and port of server that served this file
port = WebUtil.getQueryVar('port', null); host = WebUtil.getQueryVar('host', window.location.hostname);
port = WebUtil.getQueryVar('port', window.location.port);
// If a token variable is passed in, set the parameter in a cookie.
// This is used by nova-novncproxy.
token = WebUtil.getQueryVar('token', null);
if (token) {
WebUtil.createCookie('token', token, 1)
}
password = WebUtil.getQueryVar('password', ''); password = WebUtil.getQueryVar('password', '');
path = WebUtil.getQueryVar('path', ''); path = WebUtil.getQueryVar('path', 'websockify');
if ((!host) || (!port)) { if ((!host) || (!port)) {
updateState('failed', updateState('failed',
"Must specify host and port in URL"); "Must specify host and port in URL");
...@@ -101,10 +110,12 @@ ...@@ -101,10 +110,12 @@
} }
rfb = new RFB({'target': $D('noVNC_canvas'), rfb = new RFB({'target': $D('noVNC_canvas'),
'encrypt': WebUtil.getQueryVar('encrypt', false), 'encrypt': WebUtil.getQueryVar('encrypt',
(window.location.protocol === "https:")),
'true_color': WebUtil.getQueryVar('true_color', true), 'true_color': WebUtil.getQueryVar('true_color', true),
'local_cursor': WebUtil.getQueryVar('cursor', true), 'local_cursor': WebUtil.getQueryVar('cursor', true),
'shared': WebUtil.getQueryVar('shared', true), 'shared': WebUtil.getQueryVar('shared', true),
'view_only': WebUtil.getQueryVar('view_only', false),
'updateState': updateState, 'updateState': updateState,
'onPasswordRequired': passwordRequired}); 'onPasswordRequired': passwordRequired});
rfb.connect(host, port, password, path); rfb.connect(host, port, password, path);
......
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