Browse Source

Update our copy of noVNC.

Bugfixes and support for tight encoding with zlib.
pull/1/head
Christian Beier 10 years ago
parent
commit
98f4037785
  1. 2
      webclients/novnc/LICENSE.txt
  2. 67
      webclients/novnc/README.md
  3. 18
      webclients/novnc/include/base.css
  4. 2
      webclients/novnc/include/base64.js
  5. 85
      webclients/novnc/include/display.js
  6. 27
      webclients/novnc/include/input.js
  7. 668
      webclients/novnc/include/jsunzip.js
  8. 334
      webclients/novnc/include/rfb.js
  9. 56
      webclients/novnc/include/ui.js
  10. 33
      webclients/novnc/include/util.js
  11. 1
      webclients/novnc/include/vnc.js
  12. 19
      webclients/novnc/include/websock.js
  13. 8
      webclients/novnc/include/webutil.js
  14. 24
      webclients/novnc/vnc.html
  15. 21
      webclients/novnc/vnc_auto.html

2
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

67
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)

18
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;

2
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;
}

85
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);
}
};

27
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");
};

668
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 };
}
};

334
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) {