summaryrefslogtreecommitdiffstats
path: root/webclients
diff options
context:
space:
mode:
Diffstat (limited to 'webclients')
-rw-r--r--webclients/novnc/LICENSE.txt2
-rw-r--r--webclients/novnc/README.md67
-rw-r--r--webclients/novnc/include/base.css18
-rw-r--r--webclients/novnc/include/base64.js2
-rw-r--r--webclients/novnc/include/display.js85
-rw-r--r--webclients/novnc/include/input.js27
-rwxr-xr-xwebclients/novnc/include/jsunzip.js668
-rw-r--r--webclients/novnc/include/rfb.js334
-rw-r--r--webclients/novnc/include/ui.js56
-rw-r--r--webclients/novnc/include/util.js33
-rw-r--r--webclients/novnc/include/vnc.js1
-rw-r--r--webclients/novnc/include/websock.js19
-rw-r--r--webclients/novnc/include/webutil.js8
-rw-r--r--webclients/novnc/vnc.html24
-rw-r--r--webclients/novnc/vnc_auto.html21
15 files changed, 1221 insertions, 144 deletions
diff --git a/webclients/novnc/LICENSE.txt b/webclients/novnc/LICENSE.txt
index 755ace3..6a1131b 100644
--- a/webclients/novnc/LICENSE.txt
+++ b/webclients/novnc/LICENSE.txt
@@ -13,6 +13,8 @@ version 3 with the following exceptions (all LGPL-3 compatible):
include/des.js : Various BSD style licenses
+ include/jsunzip.js : zlib/libpng license
+
include/web-socket-js/ : New BSD license. Source code at
http://github.com/gimite/web-socket-js
diff --git a/webclients/novnc/README.md b/webclients/novnc/README.md
index 4672969..887c96c 100644
--- a/webclients/novnc/README.md
+++ b/webclients/novnc/README.md
@@ -3,20 +3,28 @@
### Description
-noVNC is a VNC client implemented using HTML5 technologies,
-specifically Canvas and WebSockets (supports 'wss://' encryption).
-noVNC is licensed under the
-[LGPLv3](http://www.gnu.org/licenses/lgpl.html).
+noVNC is a HTML5 VNC client that runs well in any modern browser
+including mobile browsers (iPhone/iPad and Android).
-Special thanks to [Sentry Data Systems](http://www.sentryds.com) for
-sponsoring ongoing development of this project (and for employing me).
+Notable commits, announcements and news are posted to
+@<a href="http://www.twitter.com/noVNC">noVNC</a>
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
@@ -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
WebSocket support.
-* Fast Javascript Engine: noVNC avoids using new Javascript
- functionality so it will run on older browsers, but decode and
- rendering happen in Javascript, so a slow Javascript engine will
- mean noVNC is painfully slow.
+* Fast Javascript Engine: this is not strictly a requirement, but
+ without a fast Javascript engine, noVNC might be painfully slow.
* I maintain a more detailed browser compatibility list <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
### Server Requirements
Unless you are using a VNC server with support for WebSockets
-connections (only my [fork of libvncserver](http://github.com/kanaka/libvncserver)
-currently), you need to use a WebSockets to TCP socket proxy. There is
-a python proxy included ('websockify'). One advantage of using the
-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.
+connections (such as [x11vnc/libvncserver](http://libvncserver.sourceforge.net/)),
+you need to use a WebSockets to TCP socket proxy. There is
+a python proxy included ('websockify').
### Quick Start
@@ -91,3 +84,19 @@ There a few reasons why a proxy is required:
* [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)
+
+
diff --git a/webclients/novnc/include/base.css b/webclients/novnc/include/base.css
index 0a62a1b..105984d 100644
--- a/webclients/novnc/include/base.css
+++ b/webclients/novnc/include/base.css
@@ -153,6 +153,7 @@ html {
}
#noVNC_controls {
+ display:none;
margin-top:77px;
right:12px;
position:fixed;
@@ -161,6 +162,23 @@ html {
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 {
display:none;
margin-top:77px;
diff --git a/webclients/novnc/include/base64.js b/webclients/novnc/include/base64.js
index c68b33a..e9b3c52 100644
--- a/webclients/novnc/include/base64.js
+++ b/webclients/novnc/include/base64.js
@@ -116,7 +116,7 @@ decode: function (data, offset) {
padding = (data.charAt(i) === pad);
// Skip illegal characters and whitespace
if (c === -1) {
- console.error("Illegal character '" + data.charCodeAt(i) + "'");
+ console.error("Illegal character code " + data.charCodeAt(i) + " at position " + i);
continue;
}
diff --git a/webclients/novnc/include/display.js b/webclients/novnc/include/display.js
index 2cf262d..f2ecdba 100644
--- a/webclients/novnc/include/display.js
+++ b/webclients/novnc/include/display.js
@@ -20,7 +20,7 @@ var that = {}, // Public API methods
c_forceCanvas = false,
// Predefine function variables (jslint)
- imageDataGet, rgbxImageData, cmapImageData,
+ imageDataGet, rgbImageData, bgrxImageData, cmapImageData,
setFillColor, rescale,
// The full frame buffer (logical canvas) size
@@ -183,13 +183,13 @@ rescale = function(factor) {
};
setFillColor = function(color) {
- var rgb, newStyle;
+ var bgr, newStyle;
if (conf.true_color) {
- rgb = color;
+ bgr = color;
} 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) {
c_ctx.fillStyle = newStyle;
c_prevStyle = newStyle;
@@ -386,10 +386,10 @@ that.getCleanDirtyReset = function() {
// Translate viewport coordinates to absolute coordinates
that.absX = function(x) {
return x + viewport.x;
-}
+};
that.absY = function(y) {
return y + viewport.y;
-}
+};
that.resize = function(width, height) {
@@ -430,7 +430,7 @@ that.copyImage = function(old_x, old_y, new_x, new_y, w, h) {
// Start updating a tile
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_y = y;
if ((width === 16) && (height === 16)) {
@@ -441,13 +441,13 @@ that.startTile = function(x, y, width, height, color) {
data = tile.data;
if (conf.prefer_js) {
if (conf.true_color) {
- rgb = color;
+ bgr = color;
} else {
- rgb = conf.colourMap[color[0]];
+ bgr = conf.colourMap[color[0]];
}
- red = rgb[0];
- green = rgb[1];
- blue = rgb[2];
+ red = bgr[2];
+ green = bgr[1];
+ blue = bgr[0];
for (i = 0; i < (width * height * 4); i+=4) {
data[i ] = red;
data[i + 1] = green;
@@ -461,18 +461,18 @@ that.startTile = function(x, y, width, height, color) {
// Update sub-rectangle of the current tile
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) {
data = tile.data;
width = tile.width;
if (conf.true_color) {
- rgb = color;
+ bgr = color;
} else {
- rgb = conf.colourMap[color[0]];
+ bgr = conf.colourMap[color[0]];
}
- red = rgb[0];
- green = rgb[1];
- blue = rgb[2];
+ red = bgr[2];
+ green = bgr[1];
+ blue = bgr[0];
xend = x + w;
yend = y + h;
for (j = y; j < yend; j += 1) {
@@ -492,12 +492,12 @@ that.subTile = function(x, y, w, h, color) {
// Draw the current tile to the screen
that.finishTile = function() {
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
};
-rgbxImageData = function(x, y, width, height, arr, offset) {
+rgbImageData = 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) ||
@@ -508,7 +508,7 @@ rgbxImageData = function(x, y, width, height, arr, offset) {
*/
img = c_ctx.createImageData(width, height);
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 + 1] = arr[j + 1];
data[i + 2] = arr[j + 2];
@@ -517,16 +517,36 @@ rgbxImageData = function(x, y, width, height, arr, offset) {
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) {
- var img, i, j, data, rgb, cmap;
+ var img, i, j, data, bgr, cmap;
img = c_ctx.createImageData(width, height);
data = img.data;
cmap = conf.colourMap;
for (i=0, j=offset; i < (width * height * 4); i+=4, j+=1) {
- rgb = cmap[arr[j]];
- data[i ] = rgb[0];
- data[i + 1] = rgb[1];
- data[i + 2] = rgb[2];
+ bgr = cmap[arr[j]];
+ data[i ] = bgr[2];
+ data[i + 1] = bgr[1];
+ data[i + 2] = bgr[0];
data[i + 3] = 255; // Set Alpha
}
c_ctx.putImageData(img, x - viewport.x, y - viewport.y);
@@ -534,8 +554,17 @@ cmapImageData = function(x, y, width, height, arr, offset) {
that.blitImage = function(x, y, width, height, arr, offset) {
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 {
+ // prolly wrong...
cmapImageData(x, y, width, height, arr, offset);
}
};
diff --git a/webclients/novnc/include/input.js b/webclients/novnc/include/input.js
index 3124d08..1dfe719 100644
--- a/webclients/novnc/include/input.js
+++ b/webclients/novnc/include/input.js
@@ -412,6 +412,26 @@ function onKeyUp(e) {
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
//
@@ -424,6 +444,9 @@ that.grab = function() {
Util.addEvent(c, 'keyup', onKeyUp);
Util.addEvent(c, 'keypress', onKeyPress);
+ // Release (key up) if window loses focus
+ Util.addEvent(window, 'blur', allKeysUp);
+
//Util.Debug("<< Keyboard.grab");
};
@@ -434,6 +457,10 @@ that.ungrab = function() {
Util.removeEvent(c, 'keydown', onKeyDown);
Util.removeEvent(c, 'keyup', onKeyUp);
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");
};
diff --git a/webclients/novnc/include/jsunzip.js b/webclients/novnc/include/jsunzip.js
new file mode 100755
index 0000000..f815218
--- /dev/null
+++ b/webclients/novnc/include/jsunzip.js
@@ -0,0 +1,668 @@
+/*
+ * JSUnzip
+ *
+ * Copyright (c) 2011 by Erik Moller
+ * All Rights Reserved
+ *
+ * This software is provided 'as-is', without any express
+ * or implied warranty. In no event will the authors be
+ * held liable for any damages arising from the use of
+ * this software.
+ *
+ * Permission is granted to anyone to use this software
+ * for any purpose, including commercial applications,
+ * and to alter it and redistribute it freely, subject to
+ * the following restrictions:
+ *
+ * 1. The origin of this software must not be
+ * misrepresented; you must not claim that you
+ * wrote the original software. If you use this
+ * software in a product, an acknowledgment in
+ * the product documentation would be appreciated
+ * but is not required.
+ *
+ * 2. Altered source versions must be plainly marked
+ * as such, and must not be misrepresented as
+ * being the original software.
+ *
+ * 3. This notice may not be removed or altered from
+ * any source distribution.
+ */
+
+var tinf;
+
+function JSUnzip() {
+
+ this.getInt = function(offset, size) {
+ switch (size) {
+ case 4:
+ return (this.data.charCodeAt(offset + 3) & 0xff) << 24 |
+ (this.data.charCodeAt(offset + 2) & 0xff) << 16 |
+ (this.data.charCodeAt(offset + 1) & 0xff) << 8 |
+ (this.data.charCodeAt(offset + 0) & 0xff);
+ break;
+ case 2:
+ return (this.data.charCodeAt(offset + 1) & 0xff) << 8 |
+ (this.data.charCodeAt(offset + 0) & 0xff);
+ break;
+ default:
+ return this.data.charCodeAt(offset) & 0xff;
+ break;
+ }
+ };
+
+ this.getDOSDate = function(dosdate, dostime) {
+ var day = dosdate & 0x1f;
+ var month = ((dosdate >> 5) & 0xf) - 1;
+ var year = 1980 + ((dosdate >> 9) & 0x7f)
+ var second = (dostime & 0x1f) * 2;
+ var minute = (dostime >> 5) & 0x3f;
+ hour = (dostime >> 11) & 0x1f;
+ return new Date(year, month, day, hour, minute, second);
+ }
+
+ this.open = function(data) {
+ this.data = data;
+ this.files = [];
+
+ if (this.data.length < 22)
+ return { 'status' : false, 'error' : 'Invalid data' };
+ var endOfCentralDirectory = this.data.length - 22;
+ while (endOfCentralDirectory >= 0 && this.getInt(endOfCentralDirectory, 4) != 0x06054b50)
+ --endOfCentralDirectory;
+ if (endOfCentralDirectory < 0)
+ return { 'status' : false, 'error' : 'Invalid data' };
+ if (this.getInt(endOfCentralDirectory + 4, 2) != 0 || this.getInt(endOfCentralDirectory + 6, 2) != 0)
+ return { 'status' : false, 'error' : 'No multidisk support' };
+
+ var entriesInThisDisk = this.getInt(endOfCentralDirectory + 8, 2);
+ var centralDirectoryOffset = this.getInt(endOfCentralDirectory + 16, 4);
+ var globalCommentLength = this.getInt(endOfCentralDirectory + 20, 2);
+ this.comment = this.data.slice(endOfCentralDirectory + 22, endOfCentralDirectory + 22 + globalCommentLength);
+
+ var fileOffset = centralDirectoryOffset;
+
+ for (var i = 0; i < entriesInThisDisk; ++i) {
+ if (this.getInt(fileOffset + 0, 4) != 0x02014b50)
+ return { 'status' : false, 'error' : 'Invalid data' };
+ if (this.getInt(fileOffset + 6, 2) > 20)
+ return { 'status' : false, 'error' : 'Unsupported version' };
+ if (this.getInt(fileOffset + 8, 2) & 1)
+ return { 'status' : false, 'error' : 'Encryption not implemented' };
+
+ var compressionMethod = this.getInt(fileOffset + 10, 2);
+ if (compressionMethod != 0 && compressionMethod != 8)
+ return { 'status' : false, 'error' : 'Unsupported compression method' };
+
+ var lastModFileTime = this.getInt(fileOffset + 12, 2);
+ var lastModFileDate = this.getInt(fileOffset + 14, 2);
+ var lastModifiedDate = this.getDOSDate(lastModFileDate, lastModFileTime);
+
+ var crc = this.getInt(fileOffset + 16, 4);
+ // TODO: crc
+
+ var compressedSize = this.getInt(fileOffset + 20, 4);
+ var uncompressedSize = this.getInt(fileOffset + 24, 4);
+
+ var fileNameLength = this.getInt(fileOffset + 28, 2);
+ var extraFieldLength = this.getInt(fileOffset + 30, 2);
+ var fileCommentLength = this.getInt(fileOffset + 32, 2);
+
+ var relativeOffsetOfLocalHeader = this.getInt(fileOffset + 42, 4);
+
+ var fileName = this.data.slice(fileOffset + 46, fileOffset + 46 + fileNameLength);
+ var fileComment = this.data.slice(fileOffset + 46 + fileNameLength + extraFieldLength, fileOffset + 46 + fileNameLength + extraFieldLength + fileCommentLength);
+
+ if (this.getInt(relativeOffsetOfLocalHeader + 0, 4) != 0x04034b50)
+ return { 'status' : false, 'error' : 'Invalid data' };
+ var localFileNameLength = this.getInt(relativeOffsetOfLocalHeader + 26, 2);
+ var localExtraFieldLength = this.getInt(relativeOffsetOfLocalHeader + 28, 2);
+ var localFileContent = relativeOffsetOfLocalHeader + 30 + localFileNameLength + localExtraFieldLength;
+
+ this.files[fileName] =
+ {
+ 'fileComment' : fileComment,
+ 'compressionMethod' : compressionMethod,
+ 'compressedSize' : compressedSize,
+ 'uncompressedSize' : uncompressedSize,
+ 'localFileContent' : localFileContent,
+ 'lastModifiedDate' : lastModifiedDate
+ };
+
+ fileOffset += 46 + fileNameLength + extraFieldLength + fileCommentLength;
+ }
+ return { 'status' : true }
+ };
+
+
+ this.read = function(fileName) {
+ var fileInfo = this.files[fileName];
+ if (fileInfo) {
+ if (fileInfo.compressionMethod == 8) {
+ if (!tinf) {
+ tinf = new TINF();
+ tinf.init();
+ }
+ var result = tinf.uncompress(this.data, fileInfo.localFileContent);
+ if (result.status == tinf.OK)
+ return { 'status' : true, 'data' : result.data };
+ else
+ return { 'status' : false, 'error' : result.error };
+ } else {
+ return { 'status' : true, 'data' : this.data.slice(fileInfo.localFileContent, fileInfo.localFileContent + fileInfo.uncompressedSize) };
+ }
+ }
+ return { 'status' : false, 'error' : "File '" + fileName + "' doesn't exist in zip" };
+ };
+
+};
+
+
+
+/*
+ * tinflate - tiny inflate
+ *
+ * Copyright (c) 2003 by Joergen Ibsen / Jibz
+ * All Rights Reserved
+ *
+ * http://www.ibsensoftware.com/
+ *
+ * This software is provided 'as-is', without any express
+ * or implied warranty. In no event will the authors be
+ * held liable for any damages arising from the use of
+ * this software.
+ *
+ * Permission is granted to anyone to use this software
+ * for any purpose, including commercial applications,
+ * and to alter it and redistribute it freely, subject to
+ * the following restrictions:
+ *
+ * 1. The origin of this software must not be
+ * misrepresented; you must not claim that you
+ * wrote the original software. If you use this
+ * software in a product, an acknowledgment in
+ * the product documentation would be appreciated
+ * but is not required.
+ *
+ * 2. Altered source versions must be plainly marked
+ * as such, and must not be misrepresented as
+ * being the original software.
+ *
+ * 3. This notice may not be removed or altered from
+ * any source distribution.
+ */
+
+/*
+ * tinflate javascript port by Erik Moller in May 2011.
+ * emoller@opera.com
+ *
+ * read_bits() patched by mike@imidio.com to allow
+ * reading more then 8 bits (needed in some zlib streams)
+ */
+
+"use strict";
+
+function TINF() {
+
+this.OK = 0;
+this.DATA_ERROR = (-3);
+this.WINDOW_SIZE = 32768;
+
+/* ------------------------------ *
+ * -- internal data structures -- *
+ * ------------------------------ */
+
+this.TREE = function() {
+ this.table = new Array(16); /* table of code length counts */
+ this.trans = new Array(288); /* code -> symbol translation table */
+};
+
+this.DATA = function(that) {
+ this.source = '';
+ this.sourceIndex = 0;
+ this.tag = 0;
+ this.bitcount = 0;
+
+ this.dest = [];
+
+ this.history = [];
+
+ this.ltree = new that.TREE(); /* dynamic length/symbol tree */
+ this.dtree = new that.TREE(); /* dynamic distance tree */
+};
+
+/* --------------------------------------------------- *
+ * -- uninitialized global data (static structures) -- *
+ * --------------------------------------------------- */
+
+this.sltree = new this.TREE(); /* fixed length/symbol tree */
+this.sdtree = new this.TREE(); /* fixed distance tree */
+
+/* extra bits and base tables for length codes */
+this.length_bits = new Array(30);
+this.length_base = new Array(30);
+
+/* extra bits and base tables for distance codes */
+this.dist_bits = new Array(30);
+this.dist_base = new Array(30);
+
+/* special ordering of code length codes */
+this.clcidx = [
+ 16, 17, 18, 0, 8, 7, 9, 6,
+ 10, 5, 11, 4, 12, 3, 13, 2,
+ 14, 1, 15
+];
+
+/* ----------------------- *
+ * -- utility functions -- *
+ * ----------------------- */
+
+/* build extra bits and base tables */
+this.build_bits_base = function(bits, base, delta, first)
+{
+ var i, sum;
+
+ /* build bits table */
+ for (i = 0; i < delta; ++i) bits[i] = 0;
+ for (i = 0; i < 30 - delta; ++i) bits[i + delta] = Math.floor(i / delta);
+
+ /* build base table */
+ for (sum = first, i = 0; i < 30; ++i)
+ {
+ base[i] = sum;
+ sum += 1 << bits[i];
+ }
+}
+
+/* build the fixed huffman trees */
+this.build_fixed_trees = function(lt, dt)
+{
+ var i;
+
+ /* build fixed length tree */
+ for (i = 0; i < 7; ++i) lt.table[i] = 0;
+
+ lt.table[7] = 24;
+ lt.table[8] = 152;
+ lt.table[9] = 112;
+
+ for (i = 0; i < 24; ++i) lt.trans[i] = 256 + i;
+ for (i = 0; i < 144; ++i) lt.trans[24 + i] = i;
+ for (i = 0; i < 8; ++i) lt.trans[24 + 144 + i] = 280 + i;
+ for (i = 0; i < 112; ++i) lt.trans[24 + 144 + 8 + i] = 144 + i;
+
+ /* build fixed distance tree */
+ for (i = 0; i < 5; ++i) dt.table[i] = 0;
+
+ dt.table[5] = 32;
+
+ for (i = 0; i < 32; ++i) dt.trans[i] = i;
+}
+
+/* given an array of code lengths, build a tree */
+this.build_tree = function(t, lengths, loffset, num)
+{
+ var offs = new Array(16);
+ var i, sum;
+
+ /* clear code length count table */
+ for (i = 0; i < 16; ++i) t.table[i] = 0;
+
+ /* scan symbol lengths, and sum code length counts */
+ for (i = 0; i < num; ++i) t.table[lengths[loffset + i]]++;
+
+ t.table[0] = 0;
+
+ /* compute offset table for distribution sort */
+ for (sum = 0, i = 0; i < 16; ++i)
+ {
+ offs[i] = sum;
+ sum += t.table[i];
+ }
+
+ /* create code->symbol translation table (symbols sorted by code) */
+ for (i = 0; i < num; ++i)
+ {
+ if (lengths[loffset + i]) t.trans[offs[lengths[loffset + i]]++] = i;
+ }
+}
+
+/* ---------------------- *
+ * -- decode functions -- *
+ * ---------------------- */
+
+/* get one bit from source stream */
+this.getbit = function(d)
+{
+ var bit;
+
+ /* check if tag is empty */
+ if (!d.bitcount--)
+ {
+ /* load next tag */
+ d.tag = d.source[d.sourceIndex++] & 0xff;
+ d.bitcount = 7;
+ }
+
+ /* shift bit out of tag */
+ bit = d.tag & 0x01;
+ d.tag >>= 1;
+
+ return bit;
+}
+
+/* read a num bit value from a stream and add base */
+this.read_bits = function(d, num, base)
+{
+ if (!num)
+ return base;
+
+ var val = 0;
+ while (d.bitcount < 24) {
+ d.tag = d.tag | (d.source[d.sourceIndex++] & 0xff) << d.bitcount;
+ d.bitcount += 8;
+ }
+ val = d.tag & (0xffff >> (16 - num));
+ d.tag >>= num;
+ d.bitcount -= num;
+ return val + base;
+}
+
+/* given a data stream and a tree, decode a symbol */
+this.decode_symbol = function(d, t)
+{
+ while (d.bitcount < 16) {
+ d.tag = d.tag | (d.source[d.sourceIndex++] & 0xff) << d.bitcount;
+ d.bitcount += 8;
+ }
+
+ var sum = 0, cur = 0, len = 0;
+ do {
+ cur = 2 * cur + ((d.tag & (1 << len)) >> len);
+
+ ++len;
+
+ sum += t.table[len];
+ cur -= t.table[len];
+
+ } while (cur >= 0);
+
+ d.tag >>= len;
+ d.bitcount -= len;
+
+ return t.trans[sum + cur];
+}
+
+/* given a data stream, decode dynamic trees from it */
+this.decode_trees = function(d, lt, dt)
+{
+ var code_tree = new this.TREE();
+ var lengths = new Array(288+32);
+ var hlit, hdist, hclen;
+ var i, num, length;
+
+ /* get 5 bits HLIT (257-286) */
+ hlit = this.read_bits(d, 5, 257);
+
+ /* get 5 bits HDIST (1-32) */
+ hdist = this.read_bits(d, 5, 1);
+
+ /* get 4 bits HCLEN (4-19) */
+ hclen = this.read_bits(d, 4, 4);
+
+ for (i = 0; i < 19; ++i) lengths[i] = 0;
+
+ /* read code lengths for code length alphabet */
+ for (i = 0; i < hclen; ++i)
+ {
+ /* get 3 bits code length (0-7) */
+ var clen = this.read_bits(d, 3, 0);
+
+ lengths[this.clcidx[i]] = clen;
+ }
+
+ /* build code length tree */
+ this.build_tree(code_tree, lengths, 0, 19);
+
+ /* decode code lengths for the dynamic trees */
+ for (num = 0; num < hlit + hdist; )
+ {
+ var sym = this.decode_symbol(d, code_tree);
+
+ switch (sym)
+ {
+ case 16:
+ /* copy previous code length 3-6 times (read 2 bits) */
+ {
+ var prev = lengths[num - 1];
+ for (length = this.read_bits(d, 2, 3); length; --length)
+ {
+ lengths[num++] = prev;
+ }
+ }
+ break;
+ case 17:
+ /* repeat code length 0 for 3-10 times (read 3 bits) */
+ for (length = this.read_bits(d, 3, 3); length; --length)
+ {
+ lengths[num++] = 0;
+ }
+ break;
+ case 18:
+ /* repeat code length 0 for 11-138 times (read 7 bits) */
+ for (length = this.read_bits(d, 7, 11); length; --length)
+ {
+ lengths[num++] = 0;
+ }
+ break;
+ default:
+ /* values 0-15 represent the actual code lengths */
+ lengths[num++] = sym;
+ break;
+ }
+ }
+
+ /* build dynamic trees */
+ this.build_tree(lt, lengths, 0, hlit);
+ this.build_tree(dt, lengths, hlit, hdist);
+}
+
+/* ----------------------------- *
+ * -- block inflate functions -- *
+ * ----------------------------- */
+
+/* given a stream and two trees, inflate a block of data */
+this.inflate_block_data = function(d, lt, dt)
+{
+ // js optimization.
+ var ddest = d.dest;
+ var ddestlength = ddest.length;
+
+ while (1)
+ {
+ var sym = this.decode_symbol(d, lt);
+
+ /* check for end of block */
+ if (sym == 256)
+ {
+ return this.OK;
+ }
+
+ if (sym < 256)
+ {
+ ddest[ddestlength++] = sym; // ? String.fromCharCode(sym);
+ d.history.push(sym);
+ } else {
+
+ var length, dist, offs;
+ var i;
+
+ sym -= 257;
+
+ /* possibly get more bits from length code */
+ length = this.read_bits(d, this.length_bits[sym], this.length_base[sym]);
+
+ dist = this.decode_symbol(d, dt);
+
+ /* possibly get more bits from distance code */
+ offs = d.history.length - this.read_bits(d, this.dist_bits[dist], this.dist_base[dist]);
+
+ if (offs < 0)
+ throw ("Invalid zlib offset " + offs);
+
+ /* copy match */
+ for (i = offs; i < offs + length; ++i) {
+ //ddest[ddestlength++] = ddest[i];
+ ddest[ddestlength++] = d.history[i];
+ d.history.push(d.history[i]);
+ }
+ }
+ }
+}
+
+/* inflate an uncompressed block of data */
+this.inflate_uncompressed_block = function(d)
+{
+ var length, invlength;
+ var i;
+
+ if (d.bitcount > 7) {
+ var overflow = Math.floor(d.bitcount / 8);
+ d.sourceIndex -= overflow;
+ d.bitcount = 0;
+ d.tag = 0;
+ }
+
+ /* get length */
+ length = d.source[d.sourceIndex+1];
+ length = 256*length + d.source[d.sourceIndex];
+
+ /* get one's complement of length */
+ invlength = d.source[d.sourceIndex+3];
+ invlength = 256*invlength + d.source[d.sourceIndex+2];
+
+ /* check length */
+ if (length != (~invlength & 0x0000ffff)) return this.DATA_ERROR;
+
+ d.sourceIndex += 4;
+
+ /* copy block */
+ for (i = length; i; --i) {
+ d.history.push(d.source[d.sourceIndex]);
+ d.dest[d.dest.length] = d.source[d.sourceIndex++];
+ }
+
+ /* make sure we start next block on a byte boundary */
+ d.bitcount = 0;
+
+ return this.OK;
+}
+
+/* inflate a block of data compressed with fixed huffman trees */
+this.inflate_fixed_block = function(d)
+{
+ /* decode block using fixed trees */
+ return this.inflate_block_data(d, this.sltree, this.sdtree);
+}
+
+/* inflate a block of data compressed with dynamic huffman trees */
+this.inflate_dynamic_block = function(d)
+{
+ /* decode trees from stream */
+ this.decode_trees(d, d.ltree, d.dtree);
+
+ /* decode block using decoded trees */
+ return this.inflate_block_data(d, d.ltree, d.dtree);
+}
+
+/* ---------------------- *
+ * -- public functions -- *
+ * ---------------------- */
+
+/* initialize global (static) data */
+this.init = function()
+{
+ /* build fixed huffman trees */
+ this.build_fixed_trees(this.sltree, this.sdtree);
+
+ /* build extra bits and base tables */
+ this.build_bits_base(this.length_bits, this.length_base, 4, 3);
+ this.build_bits_base(this.dist_bits, this.dist_base, 2, 1);
+
+ /* fix a special case */
+ this.length_bits[28] = 0;
+ this.length_base[28] = 258;
+
+ this.reset();
+}
+
+this.reset = function()
+{
+ this.d = new this.DATA(this);
+ delete this.header;
+}
+
+/* inflate stream from source to dest */
+this.uncompress = function(source, offset)
+{
+
+ var d = this.d;
+ var bfinal;
+
+ /* initialise data */
+ d.source = source;
+ d.sourceIndex = offset;
+ d.bitcount = 0;
+
+ d.dest = [];
+
+ // Skip zlib header at start of stream
+ if (typeof this.header == 'undefined') {
+ this.header = this.read_bits(d, 16, 0);
+ /* byte 0: 0x78, 7 = 32k window size, 8 = deflate */
+ /* byte 1: check bits for header and other flags */
+ }
+
+ var blocks = 0;
+
+ do {
+
+ var btype;
+ var res;
+
+ /* read final block flag */
+ bfinal = this.getbit(d);
+
+ /* read block type (2 bits) */
+ btype = this.read_bits(d, 2, 0);
+
+ /* decompress block */
+ switch (btype)
+ {
+ case 0:
+ /* decompress uncompressed block */
+ res = this.inflate_uncompressed_block(d);
+ break;
+ case 1:
+ /* decompress block with fixed huffman trees */
+ res = this.inflate_fixed_block(d);
+ break;
+ case 2:
+ /* decompress block with dynamic huffman trees */
+ res = this.inflate_dynamic_block(d);
+ break;
+ default:
+ return { 'status' : this.DATA_ERROR };
+ }
+
+ if (res != this.OK) return { 'status' : this.DATA_ERROR };
+ blocks++;
+
+ } while (!bfinal && d.sourceIndex < d.source.length);
+
+ d.history = d.history.slice(-this.WINDOW_SIZE);
+
+ return { 'status' : this.OK, 'data' : d.dest };
+}
+
+};
diff --git a/webclients/novnc/include/rfb.js b/webclients/novnc/include/rfb.js
index b7aa3f6..75b9797 100644
--- a/webclients/novnc/include/rfb.js
+++ b/webclients/novnc/include/rfb.js
@@ -4,6 +4,9 @@
* Licensed under LGPL-3 (see LICENSE.txt)
*
* See README.md for usage and integration instructions.
+ *
+ * TIGHT decoder portion:
+ * (c) 2012 Michael Tinglof, Joe Balaz, Les Piech (Mercuri.ca)
*/
/*jslint white: false, browser: true, bitwise: false, plusplus: false */
@@ -23,7 +26,7 @@ var that = {}, // Public API methods
pixelFormat, clientEncodings, fbUpdateRequest, fbUpdateRequests,
keyEvent, pointerEvent, clientCutText,
- extract_data_uri, scan_tight_imgQ,
+ getTightCLength, extract_data_uri, scan_tight_imgQ,
keyPress, mouseButton, mouseMove,
checkEvents, // Overridable for testing
@@ -46,6 +49,7 @@ var that = {}, // Public API methods
// In preference order
encodings = [
['COPYRECT', 0x01 ],
+ ['TIGHT', 0x07 ],
['TIGHT_PNG', -260 ],
['HEXTILE', 0x05 ],
['RRE', 0x02 ],
@@ -54,10 +58,12 @@ var that = {}, // Public API methods
['Cursor', -239 ],
// Psuedo-encoding settings
- ['JPEG_quality_lo', -32 ],
+ //['JPEG_quality_lo', -32 ],
+ ['JPEG_quality_med', -26 ],
//['JPEG_quality_hi', -23 ],
- ['compress_lo', -255 ]
- //['compress_hi', -247 ]
+ //['compress_lo', -255 ],
+ ['compress_hi', -247 ],
+ ['last_rect', -224 ]
],
encHandlers = {},
@@ -87,7 +93,8 @@ var that = {}, // Public API methods
encoding : 0,
subencoding : -1,
background : null,
- imgQ : [] // TIGHT_PNG image queue
+ imgQ : [], // TIGHT_PNG image queue
+ zlibs : [] // TIGHT zlib streams
},
fb_Bpp = 4,
@@ -109,7 +116,8 @@ var that = {}, // Public API methods
fbu_rt_start : 0,
fbu_rt_total : 0,
- fbu_rt_cnt : 0
+ fbu_rt_cnt : 0,
+ pixels : 0
},
test_mode = false,
@@ -131,6 +139,7 @@ Util.conf_defaults(conf, that, defaults, [
['true_color', 'rw', 'bool', true, 'Request true color pixel data'],
['local_cursor', 'rw', 'bool', false, 'Request locally rendered cursor'],
['shared', 'rw', 'bool', true, 'Request shared mode'],
+ ['view_only', 'rw', 'bool', false, 'Disable client mouse/keyboard'],
['connectTimeout', 'rw', 'int', def_con_timeout, 'Time (s) to wait for connection'],
['disconnectTimeout', 'rw', 'int', 3, 'Time (s) to wait for disconnection'],
@@ -224,7 +233,10 @@ function constructor() {
fail("Got unexpected WebSockets connection");
}
});
- ws.on('close', function() {
+ ws.on('close', function(e) {
+ if (e.code) {
+ Util.Info("Close code: " + e.code + ", reason: " + e.reason + ", wasClean: " + e.wasClean);
+ }
if (rfb_state === 'disconnect') {
updateState('disconnected', 'VNC disconnected');
} else if (rfb_state === 'ProtocolVersion') {
@@ -266,14 +278,18 @@ function constructor() {
function connect() {
Util.Debug(">> RFB.connect");
-
- var uri = "";
- if (conf.encrypt) {
- uri = "wss://";
+ var uri;
+
+ if (typeof UsingSocketIO !== "undefined") {
+ uri = "http://" + rfb_host + ":" + rfb_port + "/" + rfb_path;
} else {
- uri = "ws://";
+ if (conf.encrypt) {
+ uri = "wss://";
+ } else {
+ uri = "ws://";
+ }
+ uri += rfb_host + ":" + rfb_port + "/" + rfb_path;
}
- uri += rfb_host + ":" + rfb_port + "/" + rfb_path;
Util.Info("connecting to " + uri);
ws.open(uri);
@@ -292,6 +308,7 @@ init_vars = function() {
FBU.lines = 0; // RAW
FBU.tiles = 0; // HEXTILE
FBU.imgQ = []; // TIGHT_PNG image queue
+ FBU.zlibs = []; // TIGHT zlib encoders
mouse_buttonMask = 0;
mouse_arr = [];
@@ -299,6 +316,12 @@ init_vars = function() {
for (i=0; i < encodings.length; i+=1) {
encStats[encodings[i][1]][0] = 0;
}
+
+ for (i=0; i < 4; i++) {
+ //FBU.zlibs[i] = new InflateStream();
+ FBU.zlibs[i] = new TINF();
+ FBU.zlibs[i].init();
+ }
};
// Print statistics
@@ -565,6 +588,9 @@ checkEvents = function() {
keyPress = function(keysym, down) {
var arr;
+
+ if (conf.view_only) { return; } // View only, skip keyboard events
+
arr = keyEvent(keysym, down);
arr = arr.concat(fbUpdateRequests());
ws.send(arr);
@@ -586,9 +612,12 @@ mouseButton = function(x, y, down, bmask) {
return;
} else {
viewportDragging = false;
+ ws.send(fbUpdateRequests()); // Force immediate redraw
}
}
+ if (conf.view_only) { return; } // View only, skip mouse events
+
mouse_arr = mouse_arr.concat(
pointerEvent(display.absX(x), display.absY(y)) );
flushClient();
@@ -611,6 +640,8 @@ mouseMove = function(x, y) {
return;
}
+ if (conf.view_only) { return; } // View only, skip mouse events
+
mouse_arr = mouse_arr.concat(
pointerEvent(display.absX(x), display.absY(y)) );
};
@@ -641,8 +672,10 @@ init_msg = function() {
switch (sversion) {
case "003.003": rfb_version = 3.3; break;
case "003.006": rfb_version = 3.3; break; // UltraVNC
+ case "003.889": rfb_version = 3.3; break; // Apple Remote Desktop
case "003.007": rfb_version = 3.7; break;
case "003.008": rfb_version = 3.8; break;
+ case "004.000": rfb_version = 3.8; break; // Intel AMT KVM
default:
return fail("Invalid server version " + sversion);
}
@@ -806,9 +839,25 @@ init_msg = function() {
", green_shift: " + green_shift +
", blue_shift: " + blue_shift);
+ if (big_endian !== 0) {
+ Util.Warn("Server native endian is not little endian");
+ }
+ if (red_shift !== 16) {
+ Util.Warn("Server native red-shift is not 16");
+ }
+ if (blue_shift !== 0) {
+ Util.Warn("Server native blue-shift is not 0");
+ }
+
/* Connection name/title */
name_length = ws.rQshift32();
fb_name = ws.rQshiftStr(name_length);
+
+ if (conf.true_color && fb_name === "Intel(r) AMT KVM")
+ {
+ Util.Warn("Intel AMT KVM only support 8/16 bit depths. Disabling true color");
+ conf.true_color = false;
+ }
display.set_true_color(conf.true_color);
display.resize(fb_width, fb_height);
@@ -865,6 +914,8 @@ normal_msg = function() {
ws.rQshift8(); // Padding
first_colour = ws.rQshift16(); // First colour
num_colours = ws.rQshift16();
+ if (ws.rQwait("SetColourMapEntries", num_colours*6, 6)) { return false; }
+
for (c=0; c < num_colours; c+=1) {
red = ws.rQshift16();
//Util.Debug("red before: " + red);
@@ -872,7 +923,7 @@ normal_msg = function() {
//Util.Debug("red after: " + red);
green = parseInt(ws.rQshift16() / 256, 10);
blue = parseInt(ws.rQshift16() / 256, 10);
- display.set_colourMap([red, green, blue], first_colour + c);
+ display.set_colourMap([blue, green, red], first_colour + c);
}
Util.Debug("colourMap: " + display.get_colourMap());
Util.Info("Registered " + num_colours + " colourMap entries");
@@ -973,9 +1024,10 @@ framebufferUpdate = function() {
if (ret) {
encStats[FBU.encoding][0] += 1;
encStats[FBU.encoding][1] += 1;
+ timing.pixels += FBU.width * FBU.height;
}
- if (FBU.rects === 0) {
+ if (FBU.rects === 0 || (timing.pixels >= (fb_width * fb_height))) {
if (((FBU.width === fb_width) &&
(FBU.height === fb_height)) ||
(timing.fbu_rt_start > 0)) {
@@ -1226,42 +1278,197 @@ encHandlers.HEXTILE = function display_hextile() {
};
-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(" starting ws.rQslice(0,20): " + ws.rQslice(0,20) + " (" + ws.rQlen() + ")");
+// Get 'compact length' header and data size
+getTightCLength = function (arr) {
+ var header = 1, data = 0;
+ data += arr[0] & 0x7f;
+ if (arr[0] & 0x80) {
+ header += 1;
+ data += (arr[1] & 0x7f) << 7;
+ if (arr[1] & 0x80) {
+ header += 1;
+ data += arr[2] << 14;
+ }
+ }
+ return [header, data];
+};
+
+function display_tight(isTightPNG) {
+ //Util.Debug(">> display_tight");
+
+ if (fb_depth === 1) {
+ fail("Tight protocol handler only implements true color mode");
+ }
+
+ var ctl, cmode, clength, color, img, data;
+ var filterId = -1, resetStreams = 0, streamId = -1;
+ var rQ = ws.get_rQ(), rQi = ws.get_rQi();
FBU.bytes = 1; // compression-control byte
if (ws.rQwait("TIGHT compression-control", FBU.bytes)) { return false; }
- // Get 'compact length' header and data size
- getCLength = function (arr) {
- var header = 1, data = 0;
- data += arr[0] & 0x7f;
- if (arr[0] & 0x80) {
- header += 1;
- data += (arr[1] & 0x7f) << 7;
- if (arr[1] & 0x80) {
- header += 1;
- data += arr[2] << 14;
+ var checksum = function(data) {
+ var sum=0, i;
+ for (i=0; i<data.length;i++) {
+ sum += data[i];
+ if (sum > 65536) sum -= 65536;
+ }
+ return sum;
+ }
+
+ var decompress = function(data) {
+ for (var i=0; i<4; i++) {
+ if ((resetStreams >> i) & 1) {
+ FBU.zlibs[i].reset();
+ Util.Info("Reset zlib stream " + i);
+ }
+ }
+ var uncompressed = FBU.zlibs[streamId].uncompress(data, 0);
+ if (uncompressed.status !== 0) {
+ Util.Error("Invalid data in zlib stream");
+ }
+ //Util.Warn("Decompressed " + data.length + " to " +
+ // uncompressed.data.length + " checksums " +
+ // checksum(data) + ":" + checksum(uncompressed.data));
+
+ return uncompressed.data;
+ }
+
+ var handlePalette = function() {
+ var numColors = rQ[rQi + 2] + 1;
+ var paletteSize = numColors * fb_depth;
+ FBU.bytes += paletteSize;
+ if (ws.rQwait("TIGHT palette " + cmode, FBU.bytes)) { return false; }
+
+ var bpp = (numColors <= 2) ? 1 : 8;
+ var rowSize = Math.floor((FBU.width * bpp + 7) / 8);
+ var raw = false;
+ if (rowSize * FBU.height < 12) {
+ raw = true;
+ clength = [0, rowSize * FBU.height];
+ } else {
+ clength = getTightCLength(ws.rQslice(3 + paletteSize,
+ 3 + paletteSize + 3));
+ }
+ FBU.bytes += clength[0] + clength[1];
+ if (ws.rQwait("TIGHT " + cmode, FBU.bytes)) { return false; }
+
+ // Shift ctl, filter id, num colors, palette entries, and clength off
+ ws.rQshiftBytes(3);
+ var palette = ws.rQshiftBytes(paletteSize);
+ ws.rQshiftBytes(clength[0]);
+
+ if (raw) {
+ data = ws.rQshiftBytes(clength[1]);
+ } else {
+ data = decompress(ws.rQshiftBytes(clength[1]));
+ }
+
+ // Convert indexed (palette based) image data to RGB
+ // TODO: reduce number of calculations inside loop
+ var dest = [];
+ var x, y, b, w, w1, dp, sp;
+ if (numColors === 2) {
+ w = Math.floor((FBU.width + 7) / 8);
+ w1 = Math.floor(FBU.width / 8);
+ for (y = 0; y < FBU.height; y++) {
+ for (x = 0; x < w1; x++) {
+ for (b = 7; b >= 0; b--) {
+ dp = (y*FBU.width + x*8 + 7-b) * 3;
+ sp = (data[y*w + x] >> b & 1) * 3;
+ dest[dp ] = palette[sp ];
+ dest[dp+1] = palette[sp+1];
+ dest[dp+2] = palette[sp+2];
+ }
+ }
+ for (b = 7; b >= 8 - FBU.width % 8; b--) {
+ dp = (y*FBU.width + x*8 + 7-b) * 3;
+ sp = (data[y*w + x] >> b & 1) * 3;
+ dest[dp ] = palette[sp ];
+ dest[dp+1] = palette[sp+1];
+ dest[dp+2] = palette[sp+2];
+ }
+ }
+ } else {
+ for (y = 0; y < FBU.height; y++) {
+ for (x = 0; x < FBU.width; x++) {
+ dp = (y*FBU.width + x) * 3;
+ sp = data[y*FBU.width + x] * 3;
+ dest[dp ] = palette[sp ];
+ dest[dp+1] = palette[sp+1];
+ dest[dp+2] = palette[sp+2];
+ }
}
}
- return [header, data];
- };
+
+ FBU.imgQ.push({
+ 'type': 'rgb',
+ 'img': {'complete': true, 'data': dest},
+ 'x': FBU.x,
+ 'y': FBU.y,
+ 'width': FBU.width,
+ 'height': FBU.height});
+ return true;
+ }
+
+ var handleCopy = function() {
+ var raw = false;
+ var uncompressedSize = FBU.width * FBU.height * fb_depth;
+ if (uncompressedSize < 12) {
+ raw = true;
+ clength = [0, uncompressedSize];
+ } else {
+ clength = getTightCLength(ws.rQslice(1, 4));
+ }
+ FBU.bytes = 1 + clength[0] + clength[1];
+ if (ws.rQwait("TIGHT " + cmode, FBU.bytes)) { return false; }
+
+ // Shift ctl, clength off
+ ws.rQshiftBytes(1 + clength[0]);
+
+ if (raw) {
+ data = ws.rQshiftBytes(clength[1]);
+ } else {
+ data = decompress(ws.rQshiftBytes(clength[1]));
+ }
+
+ FBU.imgQ.push({
+ 'type': 'rgb',
+ 'img': {'complete': true, 'data': data},
+ 'x': FBU.x,
+ 'y': FBU.y,
+ 'width': FBU.width,
+ 'height': FBU.height});
+ return true;
+ }
ctl = ws.rQpeek8();
- switch (ctl >> 4) {
- case 0x08: cmode = "fill"; break;
- case 0x09: cmode = "jpeg"; break;
- case 0x0A: cmode = "png"; break;
- default: throw("Illegal basic compression received, ctl: " + ctl);
+
+ // Keep tight reset bits
+ resetStreams = ctl & 0xF;
+
+ // Figure out filter
+ ctl = ctl >> 4;
+ streamId = ctl & 0x3;
+
+ if (ctl === 0x08) cmode = "fill";
+ else if (ctl === 0x09) cmode = "jpeg";
+ else if (ctl === 0x0A) cmode = "png";
+ else if (ctl & 0x04) cmode = "filter";
+ else if (ctl < 0x04) cmode = "copy";
+ else throw("Illegal tight compression received, ctl: " + ctl);
+
+ if (isTightPNG && (cmode === "filter" || cmode === "copy")) {
+ throw("filter/copy received in tightPNG mode");
}
+
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
+ case "fill": FBU.bytes += fb_depth; break; // TPIXEL
+ case "jpeg": FBU.bytes += 3; break; // max clength
+ case "png": FBU.bytes += 3; break; // max clength
+ case "filter": FBU.bytes += 2; break; // filter id + num colors if palette
+ case "copy": break;
}
if (ws.rQwait("TIGHT " + cmode, FBU.bytes)) { return false; }
@@ -1281,16 +1488,17 @@ encHandlers.TIGHT_PNG = function display_tight_png() {
'y': FBU.y,
'width': FBU.width,
'height': FBU.height,
- 'color': color});
+ 'color': [color[2], color[1], color[0]] });
break;
- case "jpeg":
case "png":
- clength = getCLength(ws.rQslice(1, 4));
+ case "jpeg":
+ clength = getTightCLength(ws.rQslice(1, 4));
FBU.bytes = 1 + clength[0] + clength[1]; // ctl + clength size + jpeg-data
if (ws.rQwait("TIGHT " + cmode, FBU.bytes)) { return false; }
// We have everything, render it
- //Util.Debug(" png, ws.rQlen(): " + ws.rQlen() + ", clength[0]: " + clength[0] + ", clength[1]: " + clength[1]);
+ //Util.Debug(" jpeg, ws.rQlen(): " + ws.rQlen() + ", clength[0]: " +
+ // clength[0] + ", clength[1]: " + clength[1]);
ws.rQshiftBytes(1 + clength[0]); // shift off ctl + compact length
img = new Image();
//img.onload = scan_tight_imgQ;
@@ -1303,13 +1511,27 @@ encHandlers.TIGHT_PNG = function display_tight_png() {
extract_data_uri(ws.rQshiftBytes(clength[1]));
img = null;
break;
+ case "filter":
+ filterId = rQ[rQi + 1];
+ if (filterId === 1) {
+ if (!handlePalette()) { return false; }
+ } else {
+ // Filter 0, Copy could be valid here, but servers don't send it as an explicit filter
+ // Filter 2, Gradient is valid but not used if jpeg is enabled
+ throw("Unsupported tight subencoding received, filter: " + filterId);
+ }
+ break;
+ case "copy":
+ if (!handleCopy()) { return false; }
+ break;
}
+
FBU.bytes = 0;
FBU.rects -= 1;
//Util.Debug(" ending ws.rQslice(0,20): " + ws.rQslice(0,20) + " (" + ws.rQlen() + ")");
//Util.Debug("<< display_tight_png");
return true;
-};
+}
extract_data_uri = function(arr) {
//var i, stra = [];
@@ -1327,8 +1549,10 @@ scan_tight_imgQ = function() {
imgQ = FBU.imgQ;
while ((imgQ.length > 0) && (imgQ[0].img.complete)) {
data = imgQ.shift();
- if (data['type'] === 'fill') {
+ if (data.type === 'fill') {
display.fillRect(data.x, data.y, data.width, data.height, data.color);
+ } else if (data.type === 'rgb') {
+ display.blitRgbImage(data.x, data.y, data.width, data.height, data.img.data, 0);
} else {
ctx.drawImage(data.img, data.x, data.y);
}
@@ -1337,6 +1561,16 @@ scan_tight_imgQ = function() {
}
};
+encHandlers.TIGHT = function () { return display_tight(false); };
+encHandlers.TIGHT_PNG = function () { return display_tight(true); };
+
+encHandlers.last_rect = function last_rect() {
+ Util.Debug(">> set_desktopsize");
+ FBU.rects = 0;
+ Util.Debug("<< set_desktopsize");
+ return true;
+};
+
encHandlers.DesktopSize = function set_desktopsize() {
Util.Debug(">> set_desktopsize");
fb_width = FBU.width;
@@ -1408,9 +1642,9 @@ pixelFormat = function() {
arr.push16(255); // red-max
arr.push16(255); // green-max
arr.push16(255); // blue-max
- arr.push8(0); // red-shift
+ arr.push8(16); // red-shift
arr.push8(8); // green-shift
- arr.push8(16); // blue-shift
+ arr.push8(0); // blue-shift
arr.push8(0); // padding
arr.push8(0); // padding
@@ -1556,7 +1790,7 @@ that.sendPassword = function(passwd) {
};
that.sendCtrlAltDel = function() {
- if (rfb_state !== "normal") { return false; }
+ if (rfb_state !== "normal" || conf.view_only) { return false; }
Util.Info("Sending Ctrl-Alt-Del");
var arr = [];
arr = arr.concat(keyEvent(0xFFE3, 1)); // Control
@@ -1572,7 +1806,7 @@ that.sendCtrlAltDel = function() {
// Send a key press. If 'down' is not specified then send a down key
// followed by an up key.
that.sendKey = function(code, down) {
- if (rfb_state !== "normal") { return false; }
+ if (rfb_state !== "normal" || conf.view_only) { return false; }
var arr = [];
if (typeof down !== 'undefined') {
Util.Info("Sending key code (" + (down ? "down" : "up") + "): " + code);
diff --git a/webclients/novnc/include/ui.js b/webclients/novnc/include/ui.js
index 74a0005..eddfa6c 100644
--- a/webclients/novnc/include/ui.js
+++ b/webclients/novnc/include/ui.js
@@ -14,7 +14,7 @@ var UI = {
rfb_state : 'loaded',
settingsOpen : false,
-connSettingsOpen : true,
+connSettingsOpen : false,
clipboardOpen: false,
keyboardVisible: false,
@@ -45,15 +45,16 @@ load: function() {
WebUtil.selectStylesheet(UI.getSetting('stylesheet'));
/* Populate the controls if defaults are provided in the URL */
- UI.initSetting('host', '');
- UI.initSetting('port', '');
+ UI.initSetting('host', window.location.hostname);
+ UI.initSetting('port', window.location.port);
UI.initSetting('password', '');
- UI.initSetting('encrypt', false);
+ UI.initSetting('encrypt', (window.location.protocol === "https:"));
UI.initSetting('true_color', true);
UI.initSetting('cursor', false);
UI.initSetting('shared', true);
+ UI.initSetting('view_only', false);
UI.initSetting('connectTimeout', 2);
- UI.initSetting('path', '');
+ UI.initSetting('path', 'websockify');
UI.rfb = RFB({'target': $D('noVNC_canvas'),
'onUpdateState': UI.updateState,
@@ -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
@@ -188,17 +197,19 @@ forceSetting: function(name, val) {
// Show the clipboard panel
toggleClipboardPanel: function() {
+ // Close the description panel
+ $D('noVNC_description').style.display = "none";
//Close settings if open
- if (UI.settingsOpen == true) {
+ if (UI.settingsOpen === true) {
UI.settingsApply();
UI.closeSettingsMenu();
}
//Close connection settings if open
- if (UI.connSettingsOpen == true) {
+ if (UI.connSettingsOpen === true) {
UI.toggleConnectPanel();
}
//Toggle Clipboard Panel
- if (UI.clipboardOpen == true) {
+ if (UI.clipboardOpen === true) {
$D('noVNC_clipboard').style.display = "none";
$D('clipboardButton').className = "noVNC_status_button";
UI.clipboardOpen = false;
@@ -211,18 +222,20 @@ toggleClipboardPanel: function() {
// Show the connection settings panel/menu
toggleConnectPanel: function() {
+ // Close the description panel
+ $D('noVNC_description').style.display = "none";
//Close connection settings if open
- if (UI.settingsOpen == true) {
+ if (UI.settingsOpen === true) {
UI.settingsApply();
UI.closeSettingsMenu();
$D('connectButton').className = "noVNC_status_button";
}
- if (UI.clipboardOpen == true) {
+ if (UI.clipboardOpen === true) {
UI.toggleClipboardPanel();
}
//Toggle Connection Panel
- if (UI.connSettingsOpen == true) {
+ if (UI.connSettingsOpen === true) {
$D('noVNC_controls').style.display = "none";
$D('connectButton').className = "noVNC_status_button";
UI.connSettingsOpen = false;
@@ -238,6 +251,8 @@ toggleConnectPanel: function() {
// On open, settings are refreshed from saved cookies.
// On close, settings are applied
toggleSettingsPanel: function() {
+ // Close the description panel
+ $D('noVNC_description').style.display = "none";
if (UI.settingsOpen) {
UI.settingsApply();
UI.closeSettingsMenu();
@@ -252,6 +267,7 @@ toggleSettingsPanel: function() {
}
UI.updateSetting('clip');
UI.updateSetting('shared');
+ UI.updateSetting('view_only');
UI.updateSetting('connectTimeout');
UI.updateSetting('path');
UI.updateSetting('stylesheet');
@@ -263,11 +279,13 @@ toggleSettingsPanel: function() {
// Open menu
openSettingsMenu: function() {
- if (UI.clipboardOpen == true) {
+ // Close the description panel
+ $D('noVNC_description').style.display = "none";
+ if (UI.clipboardOpen === true) {
UI.toggleClipboardPanel();
}
//Close connection settings if open
- if (UI.connSettingsOpen == true) {
+ if (UI.connSettingsOpen === true) {
UI.toggleConnectPanel();
}
$D('noVNC_settings').style.display = "block";
@@ -292,6 +310,7 @@ settingsApply: function() {
}
UI.saveSetting('clip');
UI.saveSetting('shared');
+ UI.saveSetting('view_only');
UI.saveSetting('connectTimeout');
UI.saveSetting('path');
UI.saveSetting('stylesheet');
@@ -363,6 +382,7 @@ updateState: function(rfb, state, oldstate, msg) {
break;
case 'disconnected':
$D('noVNC_logo').style.display = "block";
+ // Fall through
case 'loaded':
klass = "noVNC_status_normal";
break;
@@ -404,16 +424,19 @@ updateVisualState: function() {
$D('noVNC_cursor').disabled = true;
}
$D('noVNC_shared').disabled = connected;
+ $D('noVNC_view_only').disabled = connected;
$D('noVNC_connectTimeout').disabled = connected;
$D('noVNC_path').disabled = connected;
if (connected) {
UI.setViewClip();
UI.setMouseButton(1);
+ $D('clipboardButton').style.display = "inline";
$D('showKeyboard').style.display = "inline";
$D('sendCtrlAltDelButton').style.display = "inline";
} else {
UI.setMouseButton();
+ $D('clipboardButton').style.display = "none";
$D('showKeyboard').style.display = "none";
$D('sendCtrlAltDelButton').style.display = "none";
}
@@ -464,6 +487,7 @@ connect: function() {
UI.rfb.set_true_color(UI.getSetting('true_color'));
UI.rfb.set_local_cursor(UI.getSetting('cursor'));
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.connect(host, port, password, path);
@@ -569,11 +593,11 @@ setViewDrag: function(drag) {
// On touch devices, show the OS keyboard
showKeyboard: function() {
- if(UI.keyboardVisible == false) {
+ if(UI.keyboardVisible === false) {
$D('keyboardinput').focus();
UI.keyboardVisible = true;
$D('showKeyboard').className = "noVNC_status_button_selected";
- } else if(UI.keyboardVisible == true) {
+ } else if(UI.keyboardVisible === true) {
$D('keyboardinput').blur();
$D('showKeyboard').className = "noVNC_status_button";
UI.keyboardVisible = false;
@@ -585,7 +609,7 @@ keyInputBlur: function() {
//Weird bug in iOS if you change keyboardVisible
//here it does not actually occur so next time
//you click keyboard icon it doesnt work.
- setTimeout("UI.setKeyboard()",100)
+ setTimeout(function() { UI.setKeyboard(); },100);
},
setKeyboard: function() {
diff --git a/webclients/novnc/include/util.js b/webclients/novnc/include/util.js
index 0a9e0e0..ddc1914 100644
--- a/webclients/novnc/include/util.js
+++ b/webclients/novnc/include/util.js
@@ -33,6 +33,30 @@ Array.prototype.push32 = function (num) {
(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
@@ -159,7 +183,7 @@ Util.conf_defaults = function(cfg, api, defaults, arr) {
Util.conf_default(cfg, api, defaults, arr[i][0], arr[i][1],
arr[i][2], arr[i][3], arr[i][4]);
}
-}
+};
/*
@@ -240,8 +264,11 @@ Util.stopEvent = function(e) {
Util.Features = {xpath: !!(document.evaluate), air: !!(window.runtime), query: !!(document.querySelector)};
Util.Engine = {
- 'presto': (function() {
- return (!window.opera) ? false : ((arguments.callee.caller) ? 960 : ((document.getElementsByClassName) ? 950 : 925)); }()),
+ // Version detection break in Opera 11.60 (errors on arguments.callee.caller reference)
+ //'presto': (function() {
+ // return (!window.opera) ? false : ((arguments.callee.caller) ? 960 : ((document.getElementsByClassName) ? 950 : 925)); }()),
+ 'presto': (function() { return (!window.opera) ? false : true; }()),
+
'trident': (function() {
return (!window.ActiveXObject) ? false : ((window.XMLHttpRequest) ? ((document.querySelectorAll) ? 6 : 5) : 4); }()),
'webkit': (function() {
diff --git a/webclients/novnc/include/vnc.js b/webclients/novnc/include/vnc.js
index f938be7..2b31f45 100644
--- a/webclients/novnc/include/vnc.js
+++ b/webclients/novnc/include/vnc.js
@@ -36,6 +36,7 @@ function get_INCLUDE_URI() {
extra += start + "input.js" + end;
extra += start + "display.js" + end;
extra += start + "rfb.js" + end;
+ extra += start + "jsunzip.js" + end;
document.write(extra);
}());
diff --git a/webclients/novnc/include/websock.js b/webclients/novnc/include/websock.js
index a688f76..33350df 100644
--- a/webclients/novnc/include/websock.js
+++ b/webclients/novnc/include/websock.js
@@ -14,16 +14,23 @@
* read binary data off of the receive queue.
*/
+/*jslint browser: true, bitwise: false, plusplus: false */
+/*global Util, Base64 */
+
// Load Flash WebSocket emulator if needed
-if (window.WebSocket) {
+if (window.WebSocket && !window.WEB_SOCKET_FORCE_FLASH) {
Websock_native = true;
-} else if (window.MozWebSocket) {
+} else if (window.MozWebSocket && !window.WEB_SOCKET_FORCE_FLASH) {
Websock_native = true;
window.WebSocket = window.MozWebSocket;
} else {
/* no builtin WebSocket so load web_socket.js */
+
+ // To enable debug:
+ // window.WEB_SOCKET_DEBUG=1;
+
Websock_native = false;
(function () {
function get_INCLUDE_URI() {
@@ -34,11 +41,11 @@ if (window.WebSocket) {
var start = "<script src='" + get_INCLUDE_URI(),
end = "'><\/script>", extra = "";
- WEB_SOCKET_SWF_LOCATION = get_INCLUDE_URI() +
+ window.WEB_SOCKET_SWF_LOCATION = get_INCLUDE_URI() +
"web-socket-js/WebSocketMain.swf";
if (Util.Engine.trident) {
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/web_socket.js" + end;
@@ -83,7 +90,7 @@ function get_rQi() {
}
function set_rQi(val) {
rQi = val;
-};
+}
function rQlen() {
return rQ.length - rQi;
@@ -115,6 +122,7 @@ function rQshift32() {
(rQ[rQi++] );
}
function rQshiftStr(len) {
+ if (typeof(len) === 'undefined') { len = rQlen(); }
var arr = rQ.slice(rQi, rQi + len);
rQi += len;
return arr.map(function (num) {
@@ -122,6 +130,7 @@ function rQshiftStr(len) {
}
function rQshiftBytes(len) {
+ if (typeof(len) === 'undefined') { len = rQlen(); }
rQi += len;
return rQ.slice(rQi-len, rQi);
}
diff --git a/webclients/novnc/include/webutil.js b/webclients/novnc/include/webutil.js
index 95138f8..7a0a076 100644
--- a/webclients/novnc/include/webutil.js
+++ b/webclients/novnc/include/webutil.js
@@ -8,7 +8,7 @@
"use strict";
/*jslint bitwise: false, white: false */
-/*global window, document */
+/*global Util, window, document */
// Globals defined here
var WebUtil = {}, $D;
@@ -17,7 +17,7 @@ var WebUtil = {}, $D;
* Simple DOM selector by ID
*/
if (!window.$D) {
- $D = function (id) {
+ window.$D = function (id) {
if (document.getElementById) {
return document.getElementById(id);
} else if (document.all) {
@@ -42,8 +42,8 @@ WebUtil.init_logging = function() {
/logging=([A-Za-z0-9\._\-]*)/) ||
['', Util._log_level])[1];
- Util.init_logging()
-}
+ Util.init_logging();
+};
WebUtil.init_logging();
diff --git a/webclients/novnc/vnc.html b/webclients/novnc/vnc.html
index 281b4d3..b6cf85b 100644
--- a/webclients/novnc/vnc.html
+++ b/webclients/novnc/vnc.html
@@ -90,7 +90,7 @@
title="Settings"
onclick="UI.toggleSettingsPanel();" />
<input type="image" src="images/connect.png"
- id="connectButton" class="noVNC_status_button_selected"
+ id="connectButton" class="noVNC_status_button"
title="Connect"
onclick="UI.toggleConnectPanel()" />
<input type="image" src="images/disconnect.png"
@@ -99,6 +99,23 @@
onclick="UI.disconnect()" />
</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 -->
<div id="noVNC_clipboard" class="triangle-right top">
<textarea id="noVNC_clipboard_text" rows=5
@@ -118,10 +135,11 @@
<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_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_view_only" type="checkbox"> View Only</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>
<!-- Stylesheet selection dropdown -->
<li><label><strong>Style: </strong>
diff --git a/webclients/novnc/vnc_auto.html b/webclients/novnc/vnc_auto.html
index a500b79..8d370b5 100644
--- a/webclients/novnc/vnc_auto.html
+++ b/webclients/novnc/vnc_auto.html
@@ -84,16 +84,25 @@
}
window.onload = function () {
- var host, port, password, path;
+ var host, port, password, path, token;
$D('sendCtrlAltDelButton').style.display = "inline";
$D('sendCtrlAltDelButton').onclick = sendCtrlAltDel;
document.title = unescape(WebUtil.getQueryVar('title', 'noVNC'));
- host = WebUtil.getQueryVar('host', null);
- port = WebUtil.getQueryVar('port', null);
+ // By default, use the host and port of server that served this file
+ 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', '');
- path = WebUtil.getQueryVar('path', '');
+ path = WebUtil.getQueryVar('path', 'websockify');
if ((!host) || (!port)) {
updateState('failed',
"Must specify host and port in URL");
@@ -101,10 +110,12 @@
}
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),
'local_cursor': WebUtil.getQueryVar('cursor', true),
'shared': WebUtil.getQueryVar('shared', true),
+ 'view_only': WebUtil.getQueryVar('view_only', false),
'updateState': updateState,
'onPasswordRequired': passwordRequired});
rfb.connect(host, port, password, path);