+ Please, wait for initialize Virtual Machine
+ Нужно найти куда воткнуть событие, что машина инициализирована и вызвать мою функцию, которая закроет перектывающий фон.
+
+
+
\ No newline at end of file
diff --git a/public/js/clonos.js b/public/js/clonos.js
index c6ed9f4f..e143db67 100644
--- a/public/js/clonos.js
+++ b/public/js/clonos.js
@@ -90,6 +90,7 @@ var clonos={
if(this.manual_close_menu) return;
var wdt=$(window).width();
if(wdt<800) $('body').addClass('gadget'); else $('body').removeClass('gadget');
+ setTimeout(graphs.onResize,500);
},
closerClick:function(event)
{
@@ -937,6 +938,13 @@ var clonos={
return;
}
for(id in data) $('#'+id).html(data[id]);
+
+ var razd=location.pathname;
+ if(['/overview/'].indexOf(razd)!=-1)
+ {
+ clonos.createGraphs();
+ console.log(data.id);
+ }
}
},
@@ -986,7 +994,7 @@ var clonos={
// ---
// Если мы в нужной таблице, то рисуем графики
- if(data.id=='bhyveslist' || data.id=='jailslist')
+ if(['bhyveslist','jailslist'].indexOf(data.id)!=-1)
{
clonos.createGraphs();
}
@@ -2643,7 +2651,7 @@ var clonos={
{
data.data['vm_ram']=ram.replace(/^\d+([gmt])$/gi,function(orig,lett)
{
- var a={'m':' MB','g':' GB','t':' TB'}[lett.toLowerCase()];
+ var a={'m':' MB','g':' GB','t':' TB'}[lett.toUpperCase()]; //!!!
return orig.replace(lett,a);
});
}else{
@@ -3044,7 +3052,7 @@ var clonos={
createGraphs:function()
{
- var grs=$('td.graph');
+ var grs=$('.graph');
for(n=0,nl=grs.length;n-1) larr.splice(res,1);
-
- //console.log(name,cpu,mem);
+ var item=items[i];
+ var gr=this.list[item.name];
+ var date = new Date();
+ date.setTime(item.time*1000);
+ if(gr)
+ {
+ var cpu,mem;
+ if(typeof item.pcpu!='undefined')
+ {
+ gr.line1.append(date.getTime(), item.pcpu);
+ }
+ if(typeof item.pmem!='undefined')
+ {
+ gr.line2.append(date.getTime(), item.pmem);
+ }
+ }else{
+ var gr=this.list[item.name+'-pcpu'];
+ {
+ if(gr) gr.line1.append(date.getTime(), item.pcpu);
+ }
+ var gr=this.list[item.name+'-pmem'];
+ {
+ if(gr) gr.line1.append(date.getTime(), item.pmem);
+ }
+ }
}
}
+ }else{
+
+ var larr=[];
+ for(l in this.list)
+ larr.push(this.list[l].name)
+
+ if(typeof msg=='object')
+ {
+ for(n in msg)
+ {
+ var inf=msg[n];
+ var name=inf['name'];
+ var gr=this.list[name];
+ var date = new Date();
+ date.setTime(inf.time*1000);
+ if(gr)
+ {
+ var cpu=inf.pcpu, mem=inf.pmem;
+ gr.line1.append(date.getTime(), cpu);
+ gr.line2.append(date.getTime(), mem);
+ var res=larr.indexOf(name);
+ if(res>-1) larr.splice(res,1);
+ }else{
+ var nname=name+'-pcpu';
+ var gr=this.list[nname];
+ {
+ if(gr) gr.line1.append(date.getTime(), inf.pcpu);
+ var res=larr.indexOf(nname);
+ if(res>-1) larr.splice(res,1);
+ }
+ var nname=name+'-pmem';
+ var gr=this.list[nname];
+ {
+ if(gr) gr.line1.append(date.getTime(), inf.pmem);
+ var res=larr.indexOf(nname);
+ if(res>-1) larr.splice(res,1);
+ }
+ }
+ }
+ }
+
+ for(n=0,nl=larr.length;n');
+ var view;
+ var cl=$(el_parent).attr('class');
+ var res=cl.match(/v-([a-z]+)/);
+ if(res!=null)
+ {
+ view=res[1];
+ }else{
+ view='white';
+ }
+ var varr=this.graphView[view];
+
this.line1 = new TimeSeries({resetBounds:false,resetBoundsInterval:3000});
this.line2 = new TimeSeries({resetBounds:false,resetBoundsInterval:3000});
var back_color='rgba(0,0,0,0.02)';
- var line1_color='rgb(17,125,187)'; //'blue'; //'rgb(0,0,255)';
- var line1_fillStyle='rgba(17,125,187,0.03)'; //'rgba(241, 240, 255, 0.4)';
- var line2_color='rgb(149,40,180)'; //'green'; //'rgb(0,255,0)';
- var line2_fillStyle='rgba(149,40,180,0.03)'; //'rgba(222,245,222,0.4)';
+ var line1_color=varr.colors.line1_color;
+ var line1_fillStyle=varr.colors.line1_fillStyle;
+ var line2_color=varr.colors.line2_color;
+ var line2_fillStyle=varr.colors.line2_fillStyle;
- this.smoothie = new SmoothieChart({interpolation:'bezier',grid:{fillStyle:back_color,sharpLines:true,strokeStyle:'transparent',borderVisible:true},labels:{fontSize:8,disabled:true,fillStyle:'#000000',precision:0},millisPerPixel:1000,enableDpiScaling:true,tooltip:true,maxValue:100,minValue:0});
+ this.smoothie = new SmoothieChart(varr.view);
- this.smoothie.addTimeSeries(this.line1, { strokeStyle: line1_color, lineWidth:0.5, fillStyle:line1_fillStyle }); //, fillStyle: 'rgba(0, 255, 0, 0.4)'
- this.smoothie.addTimeSeries(this.line2, { strokeStyle: line2_color, lineWidth:0.5, fillStyle:line2_fillStyle }); //, fillStyle: 'rgba(255, 0, 255, 0.3)'
+ this.smoothie.addTimeSeries(this.line1, { strokeStyle: line1_color, lineWidth:varr.lineWidth, fillStyle:line1_fillStyle, tooltipLabel:this.tooltip1+':' });
+ this.smoothie.addTimeSeries(this.line2, { strokeStyle: line2_color, lineWidth:varr.lineWidth, fillStyle:line2_fillStyle, tooltipLabel:this.tooltip2+':' });
this.smoothie.streamTo(document.getElementById('g-'+this.name), 1000);
graphs.list[this.name]=this;
diff --git a/public/novnc/CONTRIBUTING.md b/public/novnc/CONTRIBUTING.md
new file mode 100644
index 00000000..4bcea104
--- /dev/null
+++ b/public/novnc/CONTRIBUTING.md
@@ -0,0 +1,54 @@
+How to contribute to noVNC
+==========================
+
+We accept code via pull requests on GitHub. There are several guidelines that
+we expect contributors submitting code requests to follow. If you have issues
+following any of these guidelines, feel free to drop us a line by leaving a
+comment in the code request or sending us an email.
+
+Contributing Guidelines
+-----------------------
+
+* While we don't have an official coding style guide, please try to follow
+ the general coding style of the existing code.
+** Use four spaces instead of tabs
+** prefix private variables and functions with an `_`
+
+* Please try to include unit tests for your code. For instance, if you
+ introduce a new encoding, add a test to `tests/test.rfb.js` under the
+ "Encoding Handlers" section (basically, input a small pattern in your
+ encoding and make sure the pattern gets displayed correctly). If you
+ fix a bug, try to add a unit test that would have caught that bug
+ (if possible -- some bugs, especially visual ones, are hard to test for).
+
+* Squash your commits down in to a clean commit history. For instance, there
+ should not be "cleanup" commits where you fix issues in previous commits in
+ the same pull request. Before you go to commit, use `git rebase -i` to
+ squash these changes into the relevant commits. For instance, a good commit
+ history might look like "Added support for FOO encoding, Added support for
+ BAR message, Placed Button in UI to Trigger BAR" (where each comma denotes
+ a separate commit).
+
+* Add both a title and description to your commit, if possible. Place more
+ detail on what you did in the description.
+
+Running the unit tests
+----------------------
+
+There are two ways to run the unit tests. For both ways, you should first run
+`npm install` (not as root).
+
+The first way to run the tests is to run `npm test`. This will run all the
+tests in the headless PhantomJS browser (which uses WebKit).
+
+The second way to run the tests is using the `tests/run_from_console.js` file.
+This way is a bit more flexible, and can provide more information about what
+went wrong. To run all the tests, simply run `tests/run_from_console.js`.
+To run a specific test file, you can use the `-t path/to/test/file.js` option.
+If you wish to simply generate the HTML for the test, use the `-g` option, and
+the path to the temporary HTML file will be written to standard out. To open
+this file in your default browser automatically, pass the `-o` option as well.
+More information can be found by passing the `--help` or `-h` option.
+
+
+Thanks, and happy coding!
diff --git a/public/novnc/LICENSE.txt b/public/novnc/LICENSE.txt
new file mode 100644
index 00000000..f217929f
--- /dev/null
+++ b/public/novnc/LICENSE.txt
@@ -0,0 +1,84 @@
+noVNC is Copyright (C) 2011 Joel Martin
+
+The noVNC core library files are licensed under the MPL 2.0 (Mozilla
+Public License 2.0). The noVNC core library is composed of the
+Javascript code necessary for full noVNC operation. This includes (but
+is not limited to):
+
+ include/base64.js
+ include/des.js
+ include/display.js
+ include/input.js
+ include/keysym.js
+ include/logo.js
+ include/playback.js
+ include/rfb.js
+ include/ui.js
+ include/util.js
+ include/websock.js
+ include/webutil.js
+
+The HTML, CSS, font and images files that included with the noVNC
+source distibution (or repository) are not considered part of the
+noVNC core library and are licensed under more permissive licenses.
+The intent is to allow easy integration of noVNC into existing web
+sites and web applications.
+
+The HTML, CSS, font and image files are licensed as follows:
+
+ *.html : 2-Clause BSD license
+
+ include/*.css : 2-Clause BSD license
+
+ include/Orbitron* : SIL Open Font License 1.1
+ (Copyright 2009 Matt McInerney)
+
+ images/ : Creative Commons Attribution-ShareAlike
+ http://creativecommons.org/licenses/by-sa/3.0/
+
+Some portions of noVNC are copyright to their individual authors.
+Please refer to the individual source files and/or to the noVNC commit
+history: https://github.com/kanaka/noVNC/commits/master
+
+The are several files and projects that have been incorporated into
+the noVNC core library. Here is a list of those files and the original
+licenses (all MPL 2.0 compatible):
+
+ include/base64.js : MPL 2.0
+
+ include/des.js : Various BSD style licenses
+
+ include/chrome-app/tcp-stream.js
+ : Apache 2.0 license
+
+ utils/websockify
+ utils/websocket.py : LGPL 3
+
+ utils/inflator.partial.js
+ include/inflator.js : MIT (for pako)
+
+Any other files not mentioned above are typically marked with
+a copyright/license header at the top of the file. The default noVNC
+license is MPL-2.0.
+
+The following license texts are included:
+
+ docs/LICENSE.MPL-2.0
+ docs/LICENSE.LGPL-3 and
+ docs/LICENSE.GPL-3
+ docs/LICENSE.OFL-1.1
+ docs/LICENSE.BSD-3-Clause (New BSD)
+ docs/LICENSE.BSD-2-Clause (Simplified BSD / FreeBSD)
+ docs/LICENSE.zlib
+ docs/LICENSE.Apache-2.0
+ docs/LICENSE.pako
+
+Or alternatively the license texts may be found here:
+
+ http://www.mozilla.org/MPL/2.0/
+ http://www.gnu.org/licenses/lgpl.html and
+ http://www.gnu.org/licenses/gpl.html
+ http://scripts.sil.org/OFL
+ http://en.wikipedia.org/wiki/BSD_licenses
+ http://www.gzip.org/zlib/zlib_license.html
+ http://www.apache.org/licenses/LICENSE-2.0.html
diff --git a/public/novnc/README.md b/public/novnc/README.md
new file mode 100644
index 00000000..59a72b10
--- /dev/null
+++ b/public/novnc/README.md
@@ -0,0 +1,132 @@
+## noVNC: HTML5 VNC Client
+
+[](https://travis-ci.org/kanaka/noVNC)
+
+### Description
+
+noVNC is a HTML5 VNC client that runs well in any modern browser
+including mobile browsers (iPhone/iPad and Android).
+
+Many companies/projects have integrated noVNC including [Ganeti Web
+Manager](http://code.osuosl.org/projects/ganeti-webmgr),
+[OpenStack](http://www.openstack.org),
+[OpenNebula](http://opennebula.org/), and
+[LibVNCServer](http://libvncserver.sourceforge.net). See [the Projects
+and Companies wiki
+page](https://github.com/kanaka/noVNC/wiki/ProjectsCompanies-using-noVNC)
+for a more complete list with additional info and links.
+
+### News/help/contact
+
+Notable commits, announcements and news are posted to
+@noVNC
+
+If you are a noVNC developer/integrator/user (or want to be) please
+join the noVNC
+discussion group
+
+Bugs and feature requests can be submitted via [github
+issues](https://github.com/kanaka/noVNC/issues). If you are looking
+for a place to start contributing to noVNC, a good place to start
+would be the issues that are marked as
+["patchwelcome"](https://github.com/kanaka/noVNC/issues?labels=patchwelcome).
+
+If you want to show appreciation for noVNC you could donate to a great
+non-profits such as: [Compassion
+International](http://www.compassion.com/), [SIL](http://www.sil.org),
+[Habitat for Humanity](http://www.habitat.org), [Electronic Frontier
+Foundation](https://www.eff.org/), [Against Malaria
+Foundation](http://www.againstmalaria.com/), [Nothing But
+Nets](http://www.nothingbutnets.net/), etc. Please tweet @noVNC if you do.
+
+
+### 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 [MPL 2.0](http://www.mozilla.org/MPL/2.0/)
+
+### Screenshots
+
+Running in Chrome before and after connecting:
+
+
+
+See more screenshots here.
+
+
+### Browser Requirements
+
+* HTML5 Canvas (with createImageData): Chrome, Firefox 3.6+, iOS
+ Safari, Opera 11+, Internet Explorer 9+, etc.
+
+* HTML5 WebSockets and Typed Arrays
+
+* Fast Javascript Engine: this is not strictly a requirement, but
+ without a fast Javascript engine, noVNC might be painfully slow.
+
+* See the more detailed [browser compatibility wiki page](https://github.com/kanaka/noVNC/wiki/Browser-support).
+
+
+### Server Requirements
+
+Unless you are using a VNC server with support for WebSockets
+connections (such as
+[x11vnc/libvncserver](http://libvncserver.sourceforge.net/),
+[QEMU](http://www.qemu.org/), or
+[PocketVNC](http://www.pocketvnc.com/blog/?page_id=866)), you need to
+use a WebSockets to TCP socket proxy. There is a python proxy included
+('websockify').
+
+
+### Quick Start
+
+* Use the launch script to start a mini-webserver and the WebSockets
+ proxy (websockify). The `--vnc` option is used to specify the location of
+ a running VNC server:
+
+ `./utils/launch.sh --vnc localhost:5901`
+
+* Point your browser to the cut-and-paste URL that is output by the
+ launch script. Enter a password if the VNC server has one
+ configured. Hit the Connect button and enjoy!
+
+
+### Other Pages
+
+* [Encrypted Connections](https://github.com/kanaka/websockify/wiki/Encrypted-Connections). How to setup websockify so that you can use encrypted connections from noVNC.
+
+* [Advanced Usage](https://github.com/kanaka/noVNC/wiki/Advanced-usage). Starting a VNC server, advanced websockify usage, etc.
+
+* [Integrating noVNC](https://github.com/kanaka/noVNC/wiki/Integration) into existing projects.
+
+* [Troubleshooting noVNC](https://github.com/kanaka/noVNC/wiki/Troubleshooting) problems.
+
+
+### Authors/Contributors
+
+* Core team:
+ * [Joel Martin](https://github.com/kanaka)
+ * [Samuel Mannehed](https://github.com/samhed) (Cendio)
+ * [Peter Åstrand](https://github.com/astrand) (Cendio)
+ * [Solly Ross](https://github.com/DirectXMan12) (Red Hat / OpenStack)
+
+* Notable contributions:
+ * UI and Icons : Chris Gordon
+ * Original Logo : Michael Sersen
+ * tight encoding : Michael Tinglof (Mercuri.ca)
+
+* Included libraries:
+ * as3crypto : Henri Torgemane (code.google.com/p/as3crypto)
+ * base64 : Martijn Pieters (Digital Creations 2), Samuel Sieb (sieb.net)
+ * DES : Dave Zimmerman (Widget Workshop), Jef Poskanzer (ACME Labs)
+ * Pako : Vitaly Puzrin (https://github.com/nodeca/pako)
diff --git a/public/novnc/docs/LICENSE.Apache-2.0 b/public/novnc/docs/LICENSE.Apache-2.0
new file mode 100644
index 00000000..d6456956
--- /dev/null
+++ b/public/novnc/docs/LICENSE.Apache-2.0
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/public/novnc/docs/LICENSE.BSD-2-Clause b/public/novnc/docs/LICENSE.BSD-2-Clause
new file mode 100644
index 00000000..9d66ec91
--- /dev/null
+++ b/public/novnc/docs/LICENSE.BSD-2-Clause
@@ -0,0 +1,22 @@
+Copyright (c) ,
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/public/novnc/docs/LICENSE.BSD-3-Clause b/public/novnc/docs/LICENSE.BSD-3-Clause
new file mode 100644
index 00000000..e160466c
--- /dev/null
+++ b/public/novnc/docs/LICENSE.BSD-3-Clause
@@ -0,0 +1,24 @@
+Copyright (c) ,
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of the nor the
+ names of its contributors may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/public/novnc/docs/LICENSE.GPL-3 b/public/novnc/docs/LICENSE.GPL-3
new file mode 100644
index 00000000..94a04532
--- /dev/null
+++ b/public/novnc/docs/LICENSE.GPL-3
@@ -0,0 +1,621 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
diff --git a/public/novnc/docs/LICENSE.LGPL-3 b/public/novnc/docs/LICENSE.LGPL-3
new file mode 100644
index 00000000..65c5ca88
--- /dev/null
+++ b/public/novnc/docs/LICENSE.LGPL-3
@@ -0,0 +1,165 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+ This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+ 0. Additional Definitions.
+
+ As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+ "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+ An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+ A "Combined Work" is a work produced by combining or linking an
+Application with the Library. The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+ The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+ The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+ 1. Exception to Section 3 of the GNU GPL.
+
+ You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+ 2. Conveying Modified Versions.
+
+ If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+ a) under this License, provided that you make a good faith effort to
+ ensure that, in the event an Application does not supply the
+ function or data, the facility still operates, and performs
+ whatever part of its purpose remains meaningful, or
+
+ b) under the GNU GPL, with none of the additional permissions of
+ this License applicable to that copy.
+
+ 3. Object Code Incorporating Material from Library Header Files.
+
+ The object code form of an Application may incorporate material from
+a header file that is part of the Library. You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+ a) Give prominent notice with each copy of the object code that the
+ Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the object code with a copy of the GNU GPL and this license
+ document.
+
+ 4. Combined Works.
+
+ You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+ a) Give prominent notice with each copy of the Combined Work that
+ the Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the Combined Work with a copy of the GNU GPL and this license
+ document.
+
+ c) For a Combined Work that displays copyright notices during
+ execution, include the copyright notice for the Library among
+ these notices, as well as a reference directing the user to the
+ copies of the GNU GPL and this license document.
+
+ d) Do one of the following:
+
+ 0) Convey the Minimal Corresponding Source under the terms of this
+ License, and the Corresponding Application Code in a form
+ suitable for, and under terms that permit, the user to
+ recombine or relink the Application with a modified version of
+ the Linked Version to produce a modified Combined Work, in the
+ manner specified by section 6 of the GNU GPL for conveying
+ Corresponding Source.
+
+ 1) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (a) uses at run time
+ a copy of the Library already present on the user's computer
+ system, and (b) will operate properly with a modified version
+ of the Library that is interface-compatible with the Linked
+ Version.
+
+ e) Provide Installation Information, but only if you would otherwise
+ be required to provide such information under section 6 of the
+ GNU GPL, and only to the extent that such information is
+ necessary to install and execute a modified version of the
+ Combined Work produced by recombining or relinking the
+ Application with a modified version of the Linked Version. (If
+ you use option 4d0, the Installation Information must accompany
+ the Minimal Corresponding Source and Corresponding Application
+ Code. If you use option 4d1, you must provide the Installation
+ Information in the manner specified by section 6 of the GNU GPL
+ for conveying Corresponding Source.)
+
+ 5. Combined Libraries.
+
+ You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+ a) Accompany the combined library with a copy of the same work based
+ on the Library, uncombined with any other library facilities,
+ conveyed under the terms of this License.
+
+ b) Give prominent notice with the combined library that part of it
+ is a work based on the Library, and explaining where to find the
+ accompanying uncombined form of the same work.
+
+ 6. Revised Versions of the GNU Lesser General Public License.
+
+ The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+ If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.
diff --git a/public/novnc/docs/LICENSE.MPL-2.0 b/public/novnc/docs/LICENSE.MPL-2.0
new file mode 100644
index 00000000..14e2f777
--- /dev/null
+++ b/public/novnc/docs/LICENSE.MPL-2.0
@@ -0,0 +1,373 @@
+Mozilla Public License Version 2.0
+==================================
+
+1. Definitions
+--------------
+
+1.1. "Contributor"
+ means each individual or legal entity that creates, contributes to
+ the creation of, or owns Covered Software.
+
+1.2. "Contributor Version"
+ means the combination of the Contributions of others (if any) used
+ by a Contributor and that particular Contributor's Contribution.
+
+1.3. "Contribution"
+ means Covered Software of a particular Contributor.
+
+1.4. "Covered Software"
+ means Source Code Form to which the initial Contributor has attached
+ the notice in Exhibit A, the Executable Form of such Source Code
+ Form, and Modifications of such Source Code Form, in each case
+ including portions thereof.
+
+1.5. "Incompatible With Secondary Licenses"
+ means
+
+ (a) that the initial Contributor has attached the notice described
+ in Exhibit B to the Covered Software; or
+
+ (b) that the Covered Software was made available under the terms of
+ version 1.1 or earlier of the License, but not also under the
+ terms of a Secondary License.
+
+1.6. "Executable Form"
+ means any form of the work other than Source Code Form.
+
+1.7. "Larger Work"
+ means a work that combines Covered Software with other material, in
+ a separate file or files, that is not Covered Software.
+
+1.8. "License"
+ means this document.
+
+1.9. "Licensable"
+ means having the right to grant, to the maximum extent possible,
+ whether at the time of the initial grant or subsequently, any and
+ all of the rights conveyed by this License.
+
+1.10. "Modifications"
+ means any of the following:
+
+ (a) any file in Source Code Form that results from an addition to,
+ deletion from, or modification of the contents of Covered
+ Software; or
+
+ (b) any new file in Source Code Form that contains any Covered
+ Software.
+
+1.11. "Patent Claims" of a Contributor
+ means any patent claim(s), including without limitation, method,
+ process, and apparatus claims, in any patent Licensable by such
+ Contributor that would be infringed, but for the grant of the
+ License, by the making, using, selling, offering for sale, having
+ made, import, or transfer of either its Contributions or its
+ Contributor Version.
+
+1.12. "Secondary License"
+ means either the GNU General Public License, Version 2.0, the GNU
+ Lesser General Public License, Version 2.1, the GNU Affero General
+ Public License, Version 3.0, or any later versions of those
+ licenses.
+
+1.13. "Source Code Form"
+ means the form of the work preferred for making modifications.
+
+1.14. "You" (or "Your")
+ means an individual or a legal entity exercising rights under this
+ License. For legal entities, "You" includes any entity that
+ controls, is controlled by, or is under common control with You. For
+ purposes of this definition, "control" means (a) the power, direct
+ or indirect, to cause the direction or management of such entity,
+ whether by contract or otherwise, or (b) ownership of more than
+ fifty percent (50%) of the outstanding shares or beneficial
+ ownership of such entity.
+
+2. License Grants and Conditions
+--------------------------------
+
+2.1. Grants
+
+Each Contributor hereby grants You a world-wide, royalty-free,
+non-exclusive license:
+
+(a) under intellectual property rights (other than patent or trademark)
+ Licensable by such Contributor to use, reproduce, make available,
+ modify, display, perform, distribute, and otherwise exploit its
+ Contributions, either on an unmodified basis, with Modifications, or
+ as part of a Larger Work; and
+
+(b) under Patent Claims of such Contributor to make, use, sell, offer
+ for sale, have made, import, and otherwise transfer either its
+ Contributions or its Contributor Version.
+
+2.2. Effective Date
+
+The licenses granted in Section 2.1 with respect to any Contribution
+become effective for each Contribution on the date the Contributor first
+distributes such Contribution.
+
+2.3. Limitations on Grant Scope
+
+The licenses granted in this Section 2 are the only rights granted under
+this License. No additional rights or licenses will be implied from the
+distribution or licensing of Covered Software under this License.
+Notwithstanding Section 2.1(b) above, no patent license is granted by a
+Contributor:
+
+(a) for any code that a Contributor has removed from Covered Software;
+ or
+
+(b) for infringements caused by: (i) Your and any other third party's
+ modifications of Covered Software, or (ii) the combination of its
+ Contributions with other software (except as part of its Contributor
+ Version); or
+
+(c) under Patent Claims infringed by Covered Software in the absence of
+ its Contributions.
+
+This License does not grant any rights in the trademarks, service marks,
+or logos of any Contributor (except as may be necessary to comply with
+the notice requirements in Section 3.4).
+
+2.4. Subsequent Licenses
+
+No Contributor makes additional grants as a result of Your choice to
+distribute the Covered Software under a subsequent version of this
+License (see Section 10.2) or under the terms of a Secondary License (if
+permitted under the terms of Section 3.3).
+
+2.5. Representation
+
+Each Contributor represents that the Contributor believes its
+Contributions are its original creation(s) or it has sufficient rights
+to grant the rights to its Contributions conveyed by this License.
+
+2.6. Fair Use
+
+This License is not intended to limit any rights You have under
+applicable copyright doctrines of fair use, fair dealing, or other
+equivalents.
+
+2.7. Conditions
+
+Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
+in Section 2.1.
+
+3. Responsibilities
+-------------------
+
+3.1. Distribution of Source Form
+
+All distribution of Covered Software in Source Code Form, including any
+Modifications that You create or to which You contribute, must be under
+the terms of this License. You must inform recipients that the Source
+Code Form of the Covered Software is governed by the terms of this
+License, and how they can obtain a copy of this License. You may not
+attempt to alter or restrict the recipients' rights in the Source Code
+Form.
+
+3.2. Distribution of Executable Form
+
+If You distribute Covered Software in Executable Form then:
+
+(a) such Covered Software must also be made available in Source Code
+ Form, as described in Section 3.1, and You must inform recipients of
+ the Executable Form how they can obtain a copy of such Source Code
+ Form by reasonable means in a timely manner, at a charge no more
+ than the cost of distribution to the recipient; and
+
+(b) You may distribute such Executable Form under the terms of this
+ License, or sublicense it under different terms, provided that the
+ license for the Executable Form does not attempt to limit or alter
+ the recipients' rights in the Source Code Form under this License.
+
+3.3. Distribution of a Larger Work
+
+You may create and distribute a Larger Work under terms of Your choice,
+provided that You also comply with the requirements of this License for
+the Covered Software. If the Larger Work is a combination of Covered
+Software with a work governed by one or more Secondary Licenses, and the
+Covered Software is not Incompatible With Secondary Licenses, this
+License permits You to additionally distribute such Covered Software
+under the terms of such Secondary License(s), so that the recipient of
+the Larger Work may, at their option, further distribute the Covered
+Software under the terms of either this License or such Secondary
+License(s).
+
+3.4. Notices
+
+You may not remove or alter the substance of any license notices
+(including copyright notices, patent notices, disclaimers of warranty,
+or limitations of liability) contained within the Source Code Form of
+the Covered Software, except that You may alter any license notices to
+the extent required to remedy known factual inaccuracies.
+
+3.5. Application of Additional Terms
+
+You may choose to offer, and to charge a fee for, warranty, support,
+indemnity or liability obligations to one or more recipients of Covered
+Software. However, You may do so only on Your own behalf, and not on
+behalf of any Contributor. You must make it absolutely clear that any
+such warranty, support, indemnity, or liability obligation is offered by
+You alone, and You hereby agree to indemnify every Contributor for any
+liability incurred by such Contributor as a result of warranty, support,
+indemnity or liability terms You offer. You may include additional
+disclaimers of warranty and limitations of liability specific to any
+jurisdiction.
+
+4. Inability to Comply Due to Statute or Regulation
+---------------------------------------------------
+
+If it is impossible for You to comply with any of the terms of this
+License with respect to some or all of the Covered Software due to
+statute, judicial order, or regulation then You must: (a) comply with
+the terms of this License to the maximum extent possible; and (b)
+describe the limitations and the code they affect. Such description must
+be placed in a text file included with all distributions of the Covered
+Software under this License. Except to the extent prohibited by statute
+or regulation, such description must be sufficiently detailed for a
+recipient of ordinary skill to be able to understand it.
+
+5. Termination
+--------------
+
+5.1. The rights granted under this License will terminate automatically
+if You fail to comply with any of its terms. However, if You become
+compliant, then the rights granted under this License from a particular
+Contributor are reinstated (a) provisionally, unless and until such
+Contributor explicitly and finally terminates Your grants, and (b) on an
+ongoing basis, if such Contributor fails to notify You of the
+non-compliance by some reasonable means prior to 60 days after You have
+come back into compliance. Moreover, Your grants from a particular
+Contributor are reinstated on an ongoing basis if such Contributor
+notifies You of the non-compliance by some reasonable means, this is the
+first time You have received notice of non-compliance with this License
+from such Contributor, and You become compliant prior to 30 days after
+Your receipt of the notice.
+
+5.2. If You initiate litigation against any entity by asserting a patent
+infringement claim (excluding declaratory judgment actions,
+counter-claims, and cross-claims) alleging that a Contributor Version
+directly or indirectly infringes any patent, then the rights granted to
+You by any and all Contributors for the Covered Software under Section
+2.1 of this License shall terminate.
+
+5.3. In the event of termination under Sections 5.1 or 5.2 above, all
+end user license agreements (excluding distributors and resellers) which
+have been validly granted by You or Your distributors under this License
+prior to termination shall survive termination.
+
+************************************************************************
+* *
+* 6. Disclaimer of Warranty *
+* ------------------------- *
+* *
+* Covered Software is provided under this License on an "as is" *
+* basis, without warranty of any kind, either expressed, implied, or *
+* statutory, including, without limitation, warranties that the *
+* Covered Software is free of defects, merchantable, fit for a *
+* particular purpose or non-infringing. The entire risk as to the *
+* quality and performance of the Covered Software is with You. *
+* Should any Covered Software prove defective in any respect, You *
+* (not any Contributor) assume the cost of any necessary servicing, *
+* repair, or correction. This disclaimer of warranty constitutes an *
+* essential part of this License. No use of any Covered Software is *
+* authorized under this License except under this disclaimer. *
+* *
+************************************************************************
+
+************************************************************************
+* *
+* 7. Limitation of Liability *
+* -------------------------- *
+* *
+* Under no circumstances and under no legal theory, whether tort *
+* (including negligence), contract, or otherwise, shall any *
+* Contributor, or anyone who distributes Covered Software as *
+* permitted above, be liable to You for any direct, indirect, *
+* special, incidental, or consequential damages of any character *
+* including, without limitation, damages for lost profits, loss of *
+* goodwill, work stoppage, computer failure or malfunction, or any *
+* and all other commercial damages or losses, even if such party *
+* shall have been informed of the possibility of such damages. This *
+* limitation of liability shall not apply to liability for death or *
+* personal injury resulting from such party's negligence to the *
+* extent applicable law prohibits such limitation. Some *
+* jurisdictions do not allow the exclusion or limitation of *
+* incidental or consequential damages, so this exclusion and *
+* limitation may not apply to You. *
+* *
+************************************************************************
+
+8. Litigation
+-------------
+
+Any litigation relating to this License may be brought only in the
+courts of a jurisdiction where the defendant maintains its principal
+place of business and such litigation shall be governed by laws of that
+jurisdiction, without reference to its conflict-of-law provisions.
+Nothing in this Section shall prevent a party's ability to bring
+cross-claims or counter-claims.
+
+9. Miscellaneous
+----------------
+
+This License represents the complete agreement concerning the subject
+matter hereof. If any provision of this License is held to be
+unenforceable, such provision shall be reformed only to the extent
+necessary to make it enforceable. Any law or regulation which provides
+that the language of a contract shall be construed against the drafter
+shall not be used to construe this License against a Contributor.
+
+10. Versions of the License
+---------------------------
+
+10.1. New Versions
+
+Mozilla Foundation is the license steward. Except as provided in Section
+10.3, no one other than the license steward has the right to modify or
+publish new versions of this License. Each version will be given a
+distinguishing version number.
+
+10.2. Effect of New Versions
+
+You may distribute the Covered Software under the terms of the version
+of the License under which You originally received the Covered Software,
+or under the terms of any subsequent version published by the license
+steward.
+
+10.3. Modified Versions
+
+If you create software not governed by this License, and you want to
+create a new license for such software, you may create and use a
+modified version of this License if you rename the license and remove
+any references to the name of the license steward (except to note that
+such modified license differs from this License).
+
+10.4. Distributing Source Code Form that is Incompatible With Secondary
+Licenses
+
+If You choose to distribute Source Code Form that is Incompatible With
+Secondary Licenses under the terms of this version of the License, the
+notice described in Exhibit B of this License must be attached.
+
+Exhibit A - Source Code Form License Notice
+-------------------------------------------
+
+ This Source Code Form is subject to the terms of the Mozilla Public
+ License, v. 2.0. If a copy of the MPL was not distributed with this
+ file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+If it is not possible or desirable to put the notice in a particular
+file, then You may include the notice in a location (such as a LICENSE
+file in a relevant directory) where a recipient would be likely to look
+for such a notice.
+
+You may add additional accurate notices of copyright ownership.
+
+Exhibit B - "Incompatible With Secondary Licenses" Notice
+---------------------------------------------------------
+
+ This Source Code Form is "Incompatible With Secondary Licenses", as
+ defined by the Mozilla Public License, v. 2.0.
diff --git a/public/novnc/docs/LICENSE.OFL-1.1 b/public/novnc/docs/LICENSE.OFL-1.1
new file mode 100644
index 00000000..77b17316
--- /dev/null
+++ b/public/novnc/docs/LICENSE.OFL-1.1
@@ -0,0 +1,91 @@
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+This license is copied below, and is also available with a FAQ at:
+http://scripts.sil.org/OFL
+
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font creation
+efforts of academic and linguistic communities, and to provide a free and
+open framework in which fonts may be shared and improved in partnership
+with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded,
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply
+to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as
+distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting,
+or substituting -- in part or in whole -- any of the components of the
+Original Version, by changing formats or by porting the Font Software to a
+new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed, modify,
+redistribute, and sell modified and unmodified copies of the Font
+Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components,
+in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the corresponding
+Copyright Holder. This restriction only applies to the primary font name as
+presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created
+using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/public/novnc/docs/LICENSE.pako b/public/novnc/docs/LICENSE.pako
new file mode 100644
index 00000000..e6c9e5a5
--- /dev/null
+++ b/public/novnc/docs/LICENSE.pako
@@ -0,0 +1,21 @@
+(The MIT License)
+
+Copyright (C) 2014 by Vitaly Puzrin
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/public/novnc/docs/LICENSE.zlib b/public/novnc/docs/LICENSE.zlib
new file mode 100644
index 00000000..ca151681
--- /dev/null
+++ b/public/novnc/docs/LICENSE.zlib
@@ -0,0 +1,27 @@
+Copyright (c) ,
+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.
+
diff --git a/public/novnc/docs/VERSION b/public/novnc/docs/VERSION
new file mode 100644
index 00000000..b6160487
--- /dev/null
+++ b/public/novnc/docs/VERSION
@@ -0,0 +1 @@
+0.6.2
diff --git a/public/novnc/docs/flash_policy.txt b/public/novnc/docs/flash_policy.txt
new file mode 100644
index 00000000..df325c0d
--- /dev/null
+++ b/public/novnc/docs/flash_policy.txt
@@ -0,0 +1,4 @@
+Manual setup:
+
+DATA="echo \'\'"
+/usr/bin/socat -T 1 TCP-L:843,reuseaddr,fork,crlf SYSTEM:"$DATA"
diff --git a/public/novnc/docs/links b/public/novnc/docs/links
new file mode 100644
index 00000000..31544ce0
--- /dev/null
+++ b/public/novnc/docs/links
@@ -0,0 +1,76 @@
+New tight PNG protocol:
+ http://wiki.qemu.org/VNC_Tight_PNG
+ http://xf.iksaif.net/blog/index.php?post/2010/06/14/QEMU:-Tight-PNG-and-some-profiling
+
+RFB protocol and extensions:
+ http://tigervnc.org/cgi-bin/rfbproto
+
+Canvas Browser Compatibility:
+ http://philip.html5.org/tests/canvas/suite/tests/results.html
+
+WebSockets API standard:
+ http://www.whatwg.org/specs/web-apps/current-work/complete.html#websocket
+ http://dev.w3.org/html5/websockets/
+ http://www.ietf.org/id/draft-ietf-hybi-thewebsocketprotocol-00.txt
+
+Browser Keyboard Events detailed:
+ http://unixpapa.com/js/key.html
+
+ActionScript (Flash) WebSocket implementation:
+ http://github.com/gimite/web-socket-js
+
+ActionScript (Flash) crypto/TLS library:
+ http://code.google.com/p/as3crypto
+ http://github.com/lyokato/as3crypto_patched
+
+TLS Protocol:
+ http://en.wikipedia.org/wiki/Transport_Layer_Security
+
+Generate self-signed certificate:
+ http://docs.python.org/dev/library/ssl.html#certificates
+
+Cursor appearance/style (for Cursor pseudo-encoding):
+ http://en.wikipedia.org/wiki/ICO_(file_format)
+ http://www.daubnet.com/en/file-format-cur
+ https://developer.mozilla.org/en/Using_URL_values_for_the_cursor_property
+ http://www.fileformat.info/format/bmp/egff.htm
+
+Icon/Cursor file format:
+ http://msdn.microsoft.com/en-us/library/ms997538
+ http://msdn.microsoft.com/en-us/library/aa921550.aspx
+ http://msdn.microsoft.com/en-us/library/aa930622.aspx
+
+
+RDP Protocol specification:
+ http://msdn.microsoft.com/en-us/library/cc240445(v=PROT.10).aspx
+
+
+Related projects:
+
+ guacamole: http://guacamole.sourceforge.net/
+
+ - Web client, but Java servlet does pre-processing
+
+ jsvnc: http://code.google.com/p/jsvnc/
+
+ - No releases
+
+ webvnc: http://code.google.com/p/webvnc/
+
+ - Jetty web server gateway, no updates since April 2008.
+
+ RealVNC Java applet: http://www.realvnc.com/support/javavncviewer.html
+
+ - Java applet
+
+ Flashlight-VNC: http://www.wizhelp.com/flashlight-vnc/
+
+ - Adobe Flash implementation
+
+ FVNC: http://osflash.org/fvnc
+
+ - Adbove Flash implementation
+
+ CanVNC: http://canvnc.sourceforge.net/
+
+ - HTML client with REST to VNC python proxy. Mostly vapor.
diff --git a/public/novnc/docs/notes b/public/novnc/docs/notes
new file mode 100644
index 00000000..036cd510
--- /dev/null
+++ b/public/novnc/docs/notes
@@ -0,0 +1,5 @@
+Rebuilding inflator.js
+
+- Download pako from npm
+- Install browserify using npm
+- browserify utils/inflator.partial.js -o include/inflator.js -s inflator
diff --git a/public/novnc/docs/release.txt b/public/novnc/docs/release.txt
new file mode 100644
index 00000000..3e036354
--- /dev/null
+++ b/public/novnc/docs/release.txt
@@ -0,0 +1,34 @@
+- Decide a new version number X.Y.Z (follow SemVer)
+- Update version in package.json
+- Update version in docs/VERSION
+- Commit the change with a commit like "Release X.Y.Z"
+- Add a new release on GitHub called "vX.Y.Z", and populate it with
+ release notes of the following form (where A.B.C is the last release):
+
+Major Changes Since A.B.C
+=========================
+
+*Insert warnings here about incompatibilities*
+
+*Thanks to all the contributors who filed bugs, added features, and fixed bugs
+during this release :tada:*
+
+App-visible Changes
+-------------------
+
+- *feature* a feature which improves the app usage (#PRNUM)
+- *bugfix* a bug fix which fixes the app usage (#PRNUM)
+- *refactor* a refactor which changes the app usage (#PRNUM)
+
+Library-visible Changes
+-----------------------
+
+- *feature* a feature which improves the noVNC APIs (#PRNUM)
+- *bugfix* a bug fix which fixes the noVNC APIs (#PRNUM)
+- *refactor* a refactor which changes the noVNC APIs (#PRNUM)
+
+App-internals Changes
+---------------------
+
+- *bugfix* a bug fix with affects the internals of noVNC only (#PRNUM)
+- *refactor* a refactor which affects the internals of noVNC only (#PRNUM)
diff --git a/public/novnc/docs/rfb_notes b/public/novnc/docs/rfb_notes
new file mode 100644
index 00000000..643e16c0
--- /dev/null
+++ b/public/novnc/docs/rfb_notes
@@ -0,0 +1,147 @@
+5.1.1 ProtocolVersion: 12, 12 bytes
+
+ - Sent by server, max supported
+ 12 ascii - "RFB 003.008\n"
+ - Response by client, version to use
+ 12 ascii - "RFB 003.003\n"
+
+5.1.2 Authentication: >=4, [16, 4] bytes
+
+ - Sent by server
+ CARD32 - authentication-scheme
+ 0 - connection failed
+ CARD32 - length
+ length - reason
+ 1 - no authentication
+
+ 2 - VNC authentication
+ 16 CARD8 - challenge (random bytes)
+
+ - Response by client (if VNC authentication)
+ 16 CARD8 - client encrypts the challenge with DES, using user
+ password as key, sends resulting 16 byte response
+
+ - Response by server (if VNC authentication)
+ CARD32 - 0 - OK
+ 1 - failed
+ 2 - too-many
+
+5.1.3 ClientInitialisation: 1 byte
+ - Sent by client
+ CARD8 - shared-flag, 0 exclusive, non-zero shared
+
+5.1.4 ServerInitialisation: >=24 bytes
+ - Sent by server
+ CARD16 - framebuffer-width
+ CARD16 - framebuffer-height
+ 16 byte PIXEL_FORMAT - server-pixel-format
+ CARD8 - bits-per-pixel
+ CARD8 - depth
+ CARD8 - big-endian-flag, non-zero is big endian
+ CARD8 - true-color-flag, non-zero then next 6 apply
+ CARD16 - red-max
+ CARD16 - green-max
+ CARD16 - blue-max
+ CARD8 - red-shift
+ CARD8 - green-shift
+ CARD8 - blue-shift
+ 3 bytes - padding
+ CARD32 - name-length
+
+ CARD8[length] - name-string
+
+
+
+Client to Server Messages:
+
+5.2.1 SetPixelFormat: 20 bytes
+ CARD8: 0 - message-type
+ ...
+
+5.2.2 FixColourMapEntries: >=6 bytes
+ CARD8: 1 - message-type
+ ...
+
+5.2.3 SetEncodings: >=8 bytes
+ CARD8: 2 - message-type
+ CARD8 - padding
+ CARD16 - numer-of-encodings
+
+ CARD32 - encoding-type in preference order
+ 0 - raw
+ 1 - copy-rectangle
+ 2 - RRE
+ 4 - CoRRE
+ 5 - hextile
+
+5.2.4 FramebufferUpdateRequest (10 bytes)
+ CARD8: 3 - message-type
+ CARD8 - incremental (0 for full-update, non-zero for incremental)
+ CARD16 - x-position
+ CARD16 - y-position
+ CARD16 - width
+ CARD16 - height
+
+
+5.2.5 KeyEvent: 8 bytes
+ CARD8: 4 - message-type
+ CARD8 - down-flag
+ 2 bytes - padding
+ CARD32 - key (X-Windows keysym values)
+
+5.2.6 PointerEvent: 6 bytes
+ CARD8: 5 - message-type
+ CARD8 - button-mask
+ CARD16 - x-position
+ CARD16 - y-position
+
+5.2.7 ClientCutText: >=9 bytes
+ CARD8: 6 - message-type
+ ...
+
+
+Server to Client Messages:
+
+5.3.1 FramebufferUpdate
+ CARD8: 0 - message-type
+ 1 byte - padding
+ CARD16 - number-of-rectangles
+
+ CARD16 - x-position
+ CARD16 - y-position
+ CARD16 - width
+ CARD16 - height
+ CARD16 - encoding-type:
+ 0 - raw
+ 1 - copy rectangle
+ 2 - RRE
+ 4 - CoRRE
+ 5 - hextile
+
+ raw:
+ - width x height pixel values
+
+ copy rectangle:
+ CARD16 - src-x-position
+ CARD16 - src-y-position
+
+ RRE:
+ CARD32 - N number-of-subrectangles
+ Nxd bytes - background-pixel-value (d bits-per-pixel)
+
+ ...
+
+5.3.2 SetColourMapEntries (no support)
+ CARD8: 1 - message-type
+ ...
+
+5.3.3 Bell
+ CARD8: 2 - message-type
+
+5.3.4 ServerCutText
+ CARD8: 3 - message-type
+
+
+
+
+
diff --git a/public/novnc/docs/rfbproto-3.3.pdf b/public/novnc/docs/rfbproto-3.3.pdf
new file mode 100644
index 00000000..56b87643
Binary files /dev/null and b/public/novnc/docs/rfbproto-3.3.pdf differ
diff --git a/public/novnc/docs/rfbproto-3.7.pdf b/public/novnc/docs/rfbproto-3.7.pdf
new file mode 100644
index 00000000..1ef54623
Binary files /dev/null and b/public/novnc/docs/rfbproto-3.7.pdf differ
diff --git a/public/novnc/docs/rfbproto-3.8.pdf b/public/novnc/docs/rfbproto-3.8.pdf
new file mode 100644
index 00000000..8f0730fb
Binary files /dev/null and b/public/novnc/docs/rfbproto-3.8.pdf differ
diff --git a/public/novnc/favicon.ico b/public/novnc/favicon.ico
new file mode 120000
index 00000000..45399c8c
--- /dev/null
+++ b/public/novnc/favicon.ico
@@ -0,0 +1 @@
+images/favicon.ico
\ No newline at end of file
diff --git a/public/novnc/images/alt.png b/public/novnc/images/alt.png
new file mode 100644
index 00000000..d42af7b4
Binary files /dev/null and b/public/novnc/images/alt.png differ
diff --git a/public/novnc/images/clipboard.png b/public/novnc/images/clipboard.png
new file mode 100644
index 00000000..24df33c1
Binary files /dev/null and b/public/novnc/images/clipboard.png differ
diff --git a/public/novnc/images/connect.png b/public/novnc/images/connect.png
new file mode 100644
index 00000000..79e71adb
Binary files /dev/null and b/public/novnc/images/connect.png differ
diff --git a/public/novnc/images/ctrl.png b/public/novnc/images/ctrl.png
new file mode 100644
index 00000000..a63b601f
Binary files /dev/null and b/public/novnc/images/ctrl.png differ
diff --git a/public/novnc/images/ctrlaltdel.png b/public/novnc/images/ctrlaltdel.png
new file mode 100644
index 00000000..31922e53
Binary files /dev/null and b/public/novnc/images/ctrlaltdel.png differ
diff --git a/public/novnc/images/disconnect.png b/public/novnc/images/disconnect.png
new file mode 100644
index 00000000..8832f5ea
Binary files /dev/null and b/public/novnc/images/disconnect.png differ
diff --git a/public/novnc/images/drag.png b/public/novnc/images/drag.png
new file mode 100644
index 00000000..433f896d
Binary files /dev/null and b/public/novnc/images/drag.png differ
diff --git a/public/novnc/images/esc.png b/public/novnc/images/esc.png
new file mode 100644
index 00000000..ece5f7cb
Binary files /dev/null and b/public/novnc/images/esc.png differ
diff --git a/public/novnc/images/favicon.ico b/public/novnc/images/favicon.ico
new file mode 100644
index 00000000..c999634f
Binary files /dev/null and b/public/novnc/images/favicon.ico differ
diff --git a/public/novnc/images/favicon.png b/public/novnc/images/favicon.png
new file mode 100644
index 00000000..e2bdb194
Binary files /dev/null and b/public/novnc/images/favicon.png differ
diff --git a/public/novnc/images/fullscreen.png b/public/novnc/images/fullscreen.png
new file mode 100644
index 00000000..f4fa0ce8
Binary files /dev/null and b/public/novnc/images/fullscreen.png differ
diff --git a/public/novnc/images/keyboard.png b/public/novnc/images/keyboard.png
new file mode 100644
index 00000000..f7979525
Binary files /dev/null and b/public/novnc/images/keyboard.png differ
diff --git a/public/novnc/images/mouse_left.png b/public/novnc/images/mouse_left.png
new file mode 100644
index 00000000..1de7a486
Binary files /dev/null and b/public/novnc/images/mouse_left.png differ
diff --git a/public/novnc/images/mouse_middle.png b/public/novnc/images/mouse_middle.png
new file mode 100644
index 00000000..81fbd9bd
Binary files /dev/null and b/public/novnc/images/mouse_middle.png differ
diff --git a/public/novnc/images/mouse_none.png b/public/novnc/images/mouse_none.png
new file mode 100644
index 00000000..93dbf578
Binary files /dev/null and b/public/novnc/images/mouse_none.png differ
diff --git a/public/novnc/images/mouse_right.png b/public/novnc/images/mouse_right.png
new file mode 100644
index 00000000..355b25dc
Binary files /dev/null and b/public/novnc/images/mouse_right.png differ
diff --git a/public/novnc/images/power.png b/public/novnc/images/power.png
new file mode 100644
index 00000000..f68fd081
Binary files /dev/null and b/public/novnc/images/power.png differ
diff --git a/public/novnc/images/screen_320x460.png b/public/novnc/images/screen_320x460.png
new file mode 100644
index 00000000..172ec555
Binary files /dev/null and b/public/novnc/images/screen_320x460.png differ
diff --git a/public/novnc/images/screen_57x57.png b/public/novnc/images/screen_57x57.png
new file mode 100644
index 00000000..e2085f29
Binary files /dev/null and b/public/novnc/images/screen_57x57.png differ
diff --git a/public/novnc/images/screen_700x700.png b/public/novnc/images/screen_700x700.png
new file mode 100644
index 00000000..ae677685
Binary files /dev/null and b/public/novnc/images/screen_700x700.png differ
diff --git a/public/novnc/images/settings.png b/public/novnc/images/settings.png
new file mode 100644
index 00000000..a43f5e10
Binary files /dev/null and b/public/novnc/images/settings.png differ
diff --git a/public/novnc/images/tab.png b/public/novnc/images/tab.png
new file mode 100644
index 00000000..84134872
Binary files /dev/null and b/public/novnc/images/tab.png differ
diff --git a/public/novnc/images/toggleextrakeys.png b/public/novnc/images/toggleextrakeys.png
new file mode 100644
index 00000000..ad8e0a70
Binary files /dev/null and b/public/novnc/images/toggleextrakeys.png differ
diff --git a/public/novnc/include/Orbitron700.ttf b/public/novnc/include/Orbitron700.ttf
new file mode 100644
index 00000000..e28729dc
Binary files /dev/null and b/public/novnc/include/Orbitron700.ttf differ
diff --git a/public/novnc/include/Orbitron700.woff b/public/novnc/include/Orbitron700.woff
new file mode 100644
index 00000000..61db630c
Binary files /dev/null and b/public/novnc/include/Orbitron700.woff differ
diff --git a/public/novnc/include/base.css b/public/novnc/include/base.css
new file mode 100644
index 00000000..8d88c0f5
--- /dev/null
+++ b/public/novnc/include/base.css
@@ -0,0 +1,527 @@
+/*
+ * noVNC base CSS
+ * Copyright (C) 2012 Joel Martin
+ * Copyright (C) 2016 Samuel Mannehed for Cendio AB
+ * noVNC is licensed under the MPL 2.0 (see LICENSE.txt)
+ * This file is licensed under the 2-Clause BSD license (see LICENSE.txt).
+ */
+
+body {
+ margin:0;
+ padding:0;
+ font-family: Helvetica;
+ /*Background image with light grey curve.*/
+ background-color:#494949;
+ background-repeat:no-repeat;
+ background-position:right bottom;
+ height:100%;
+}
+
+html {
+ height:100%;
+}
+
+#noVNC_controls ul {
+ list-style: none;
+ margin: 0px;
+ padding: 0px;
+}
+#noVNC_controls li {
+ padding-bottom:8px;
+}
+
+#noVNC_setting_host {
+ width:150px;
+}
+#noVNC_setting_port {
+ width: 80px;
+}
+#noVNC_setting_password {
+ width: 150px;
+}
+#noVNC_setting_encrypt {
+}
+#noVNC_setting_path {
+ width: 100px;
+}
+#noVNC_connect_button {
+ width: 110px;
+ float:right;
+}
+
+#noVNC_buttons {
+ white-space: nowrap;
+}
+
+#noVNC_view_drag_button {
+ display: none;
+}
+#noVNC_sendCtrlAltDel_button {
+ display: none;
+}
+#noVNC_fullscreen_button {
+ display: none;
+}
+#noVNC_xvp_buttons {
+ display: none;
+}
+#noVNC_mobile_buttons {
+ display: none;
+}
+
+#noVNC_extra_keys {
+ display: inline;
+ list-style-type: none;
+ padding: 0px;
+ margin: 0px;
+ position: relative;
+}
+
+.noVNC_buttons_left {
+ float: left;
+ z-index: 1;
+ position: relative;
+}
+
+.noVNC_buttons_right {
+ float:right;
+ right: 0px;
+ z-index: 2;
+ position: absolute;
+}
+
+#noVNC_status {
+ font-size: 12px;
+ padding-top: 4px;
+ height:32px;
+ text-align: center;
+ font-weight: bold;
+ color: #fff;
+}
+
+#noVNC_settings_menu {
+ margin: 3px;
+ text-align: left;
+}
+#noVNC_settings_menu ul {
+ list-style: none;
+ margin: 0px;
+ padding: 0px;
+}
+
+#noVNC_settings_apply {
+ float:right;
+}
+
+#noVNC_container {
+ display: table;
+ width:100%;
+ height:100%;
+ background-color:#313131;
+ border-bottom-right-radius: 800px 600px;
+ /*border-top-left-radius: 800px 600px;*/
+}
+
+#noVNC_screen {
+ display: none;
+ position: absolute;
+ margin: 0px;
+ padding: 0px;
+ bottom: 0px;
+ top: 36px; /* the height of the control bar */
+ left: 0px;
+ right: 0px;
+ width: auto;
+ height: auto;
+}
+
+/* Do not set width/height for VNC_canvas or incorrect
+ * scaling will occur. Canvas size depends on remote VNC
+ * settings and noVNC settings. */
+#noVNC_canvas {
+ position: absolute;
+ left: 0;
+ right: 0;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+#VNC_clipboard_clear_button {
+ float:right;
+}
+#VNC_clipboard_text {
+ font-size: 11px;
+}
+
+#noVNC_clipboard_clear_button {
+ float:right;
+}
+
+/*Bubble contents divs*/
+#noVNC_settings {
+ display:none;
+ margin-top:73px;
+ right:20px;
+ position:fixed;
+}
+
+#noVNC_controls {
+ display:none;
+ margin-top:73px;
+ right:12px;
+ position:fixed;
+}
+#noVNC_controls.top:after {
+ right:15px;
+}
+
+#noVNC_description {
+ display:none;
+ position:fixed;
+
+ margin-top:73px;
+ 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_popup_status {
+ display:none;
+ position: fixed;
+ z-index: 1;
+
+ margin:15px;
+ margin-top:60px;
+ padding:15px;
+ width:auto;
+
+ text-align:center;
+ font-weight:bold;
+ word-wrap:break-word;
+ color:#fff;
+ background:rgba(0,0,0,0.65);
+
+ -webkit-border-radius:10px;
+ -moz-border-radius:10px;
+ border-radius:10px;
+}
+
+#noVNC_xvp {
+ display:none;
+ margin-top:73px;
+ right:30px;
+ position:fixed;
+}
+#noVNC_xvp.top:after {
+ right:125px;
+}
+
+#noVNC_clipboard {
+ display:none;
+ margin-top:73px;
+ right:30px;
+ position:fixed;
+}
+#noVNC_clipboard.top:after {
+ right:85px;
+}
+
+#noVNC_keyboardinput {
+ width:1px;
+ height:1px;
+ background-color:#fff;
+ color:#fff;
+ border:0;
+ position: relative;
+ left: -40px;
+ z-index: -1;
+ ime-mode: disabled;
+}
+
+/*
+ * Advanced Styling
+ */
+
+.noVNC_status_normal {
+ background: #b2bdcd; /* Old browsers */
+ background: -moz-linear-gradient(top, #b2bdcd 0%, #899cb3 49%, #7e93af 51%, #6e84a3 100%); /* FF3.6+ */
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#b2bdcd), color-stop(49%,#899cb3), color-stop(51%,#7e93af), color-stop(100%,#6e84a3)); /* Chrome,Safari4+ */
+ background: -webkit-linear-gradient(top, #b2bdcd 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* Chrome10+,Safari5.1+ */
+ background: -o-linear-gradient(top, #b2bdcd 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* Opera11.10+ */
+ background: -ms-linear-gradient(top, #b2bdcd 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* IE10+ */
+ background: linear-gradient(top, #b2bdcd 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* W3C */
+}
+.noVNC_status_error {
+ background: #f04040; /* Old browsers */
+ background: -moz-linear-gradient(top, #f04040 0%, #899cb3 49%, #7e93af 51%, #6e84a3 100%); /* FF3.6+ */
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#f04040), color-stop(49%,#899cb3), color-stop(51%,#7e93af), color-stop(100%,#6e84a3)); /* Chrome,Safari4+ */
+ background: -webkit-linear-gradient(top, #f04040 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* Chrome10+,Safari5.1+ */
+ background: -o-linear-gradient(top, #f04040 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* Opera11.10+ */
+ background: -ms-linear-gradient(top, #f04040 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* IE10+ */
+ background: linear-gradient(top, #f04040 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* W3C */
+}
+.noVNC_status_warn {
+ background: #f0f040; /* Old browsers */
+ background: -moz-linear-gradient(top, #f0f040 0%, #899cb3 49%, #7e93af 51%, #6e84a3 100%); /* FF3.6+ */
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#f0f040), color-stop(49%,#899cb3), color-stop(51%,#7e93af), color-stop(100%,#6e84a3)); /* Chrome,Safari4+ */
+ background: -webkit-linear-gradient(top, #f0f040 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* Chrome10+,Safari5.1+ */
+ background: -o-linear-gradient(top, #f0f040 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* Opera11.10+ */
+ background: -ms-linear-gradient(top, #f0f040 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* IE10+ */
+ background: linear-gradient(top, #f0f040 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* W3C */
+}
+
+/* Control bar */
+#noVNC_control_bar {
+ position:fixed;
+
+ display:block;
+ height:36px;
+ left:0;
+ top:0;
+ width:100%;
+ z-index:200;
+}
+
+.noVNC_status_button {
+ padding: 4px 4px;
+ vertical-align: middle;
+ border:1px solid #869dbc;
+ -webkit-border-radius: 6px;
+ -moz-border-radius: 6px;
+ border-radius: 6px;
+ background: #b2bdcd; /* Old browsers */
+ background: -moz-linear-gradient(top, #b2bdcd 0%, #899cb3 49%, #7e93af 51%, #6e84a3 100%); /* FF3.6+ */
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#b2bdcd), color-stop(49%,#899cb3), color-stop(51%,#7e93af), color-stop(100%,#6e84a3)); /* Chrome,Safari4+ */
+ background: -webkit-linear-gradient(top, #b2bdcd 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* Chrome10+,Safari5.1+ */
+ background: -o-linear-gradient(top, #b2bdcd 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* Opera11.10+ */
+ background: -ms-linear-gradient(top, #b2bdcd 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* IE10+ */
+ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#b2bdcd', endColorstr='#6e84a3',GradientType=0 ); /* IE6-9 */
+ background: linear-gradient(top, #b2bdcd 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* W3C */
+ /*box-shadow:inset 0.4px 0.4px 0.4px #000000;*/
+}
+
+.noVNC_status_button_selected {
+ padding: 4px 4px;
+ vertical-align: middle;
+ border:1px solid #4366a9;
+ -webkit-border-radius: 6px;
+ -moz-border-radius: 6px;
+ background: #779ced; /* Old browsers */
+ background: -moz-linear-gradient(top, #779ced 0%, #3970e0 49%, #2160dd 51%, #2463df 100%); /* FF3.6+ */
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#779ced), color-stop(49%,#3970e0), color-stop(51%,#2160dd), color-stop(100%,#2463df)); /* Chrome,Safari4+ */
+ background: -webkit-linear-gradient(top, #779ced 0%,#3970e0 49%,#2160dd 51%,#2463df 100%); /* Chrome10+,Safari5.1+ */
+ background: -o-linear-gradient(top, #779ced 0%,#3970e0 49%,#2160dd 51%,#2463df 100%); /* Opera11.10+ */
+ background: -ms-linear-gradient(top, #779ced 0%,#3970e0 49%,#2160dd 51%,#2463df 100%); /* IE10+ */
+ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#779ced', endColorstr='#2463df',GradientType=0 ); /* IE6-9 */
+ background: linear-gradient(top, #779ced 0%,#3970e0 49%,#2160dd 51%,#2463df 100%); /* W3C */
+ /*box-shadow:inset 0.4px 0.4px 0.4px #000000;*/
+}
+
+.noVNC_status_button:disabled {
+ opacity: 0.4;
+}
+
+
+/*Settings Bubble*/
+.triangle-right {
+ position:relative;
+ padding:15px;
+ margin:1em 0 3em;
+ color:#fff;
+ background:#fff; /* default background for browsers without gradient support */
+ /* css3 */
+ /*background:-webkit-gradient(linear, 0 0, 0 100%, from(#2e88c4), to(#075698));
+ background:-moz-linear-gradient(#2e88c4, #075698);
+ background:-o-linear-gradient(#2e88c4, #075698);
+ background:linear-gradient(#2e88c4, #075698);*/
+ -webkit-border-radius:10px;
+ -moz-border-radius:10px;
+ border-radius:10px;
+ color:#000;
+ border:2px solid #E0E0E0;
+}
+
+.triangle-right.top:after {
+ border-color: transparent #E0E0E0;
+ border-width: 20px 20px 0 0;
+ bottom: auto;
+ left: auto;
+ right: 50px;
+ top: -20px;
+}
+
+.triangle-right:after {
+ content:"";
+ position:absolute;
+ bottom:-20px; /* value = - border-top-width - border-bottom-width */
+ left:50px; /* controls horizontal position */
+ border-width:20px 0 0 20px; /* vary these values to change the angle of the vertex */
+ border-style:solid;
+ border-color:#E0E0E0 transparent;
+ /* reduce the damage in FF3.0 */
+ display:block;
+ width:0;
+}
+
+.triangle-right.top:after {
+ top:-40px; /* value = - border-top-width - border-bottom-width */
+ right:50px; /* controls horizontal position */
+ bottom:auto;
+ left:auto;
+ border-width:40px 40px 0 0; /* vary these values to change the angle of the vertex */
+ border-color:transparent #E0E0E0;
+}
+
+/*Default noVNC logo.*/
+/* From: http://fonts.googleapis.com/css?family=Orbitron:700 */
+@font-face {
+ font-family: 'Orbitron';
+ font-style: normal;
+ font-weight: 700;
+ src: local('?'), url('Orbitron700.woff') format('woff'),
+ url('Orbitron700.ttf') format('truetype');
+}
+
+#noVNC_logo {
+ margin-top: 170px;
+ margin-left: 10px;
+ color:yellow;
+ text-align:left;
+ font-family: 'Orbitron', 'OrbitronTTF', sans-serif;
+ line-height:90%;
+ text-shadow:
+ 5px 5px 0 #000,
+ -1px -1px 0 #000,
+ 1px -1px 0 #000,
+ -1px 1px 0 #000,
+ 1px 1px 0 #000;
+}
+
+
+#noVNC_logo span{
+ color:green;
+}
+
+/* ----------------------------------------
+ * Media sizing
+ * ----------------------------------------
+ */
+
+
+.noVNC_status_button {
+ font-size: 12px;
+}
+
+#noVNC_clipboard_text {
+ width: 500px;
+}
+
+#noVNC_logo {
+ font-size: 180px;
+}
+
+.noVNC_buttons_left {
+ padding-left: 10px;
+}
+
+.noVNC_buttons_right {
+ padding-right: 10px;
+}
+
+#noVNC_status {
+ z-index: 0;
+ position: absolute;
+ width: 100%;
+ margin-left: 0px;
+}
+
+#noVNC_toggleExtraKeys_button { display: none; }
+#noVNC_toggleCtrl_button { display: inline; }
+#noVNC_toggleAlt_button { display: inline; }
+#noVNC_sendTab_button { display: inline; }
+#noVNC_sendEsc_button { display: inline; }
+
+/* left-align the status text on lower resolutions */
+@media screen and (max-width: 800px){
+ #noVNC_status {
+ z-index: 1;
+ position: relative;
+ width: auto;
+ float: left;
+ margin-left: 4px;
+ }
+}
+
+@media screen and (max-width: 640px){
+ #noVNC_clipboard_text {
+ width: 410px;
+ }
+ #noVNC_logo {
+ font-size: 150px;
+ }
+ .noVNC_status_button {
+ font-size: 10px;
+ }
+ .noVNC_buttons_left {
+ padding-left: 0px;
+ }
+ .noVNC_buttons_right {
+ padding-right: 0px;
+ }
+ /* collapse the extra keys on lower resolutions */
+ #noVNC_toggleExtraKeys_button {
+ display: inline;
+ }
+ #noVNC_toggleCtrl_button {
+ display: none;
+ position: absolute;
+ top: 30px;
+ left: 0px;
+ }
+ #noVNC_toggleAlt_button {
+ display: none;
+ position: absolute;
+ top: 65px;
+ left: 0px;
+ }
+ #noVNC_sendTab_button {
+ display: none;
+ position: absolute;
+ top: 100px;
+ left: 0px;
+ }
+ #noVNC_sendEsc_button {
+ display: none;
+ position: absolute;
+ top: 135px;
+ left: 0px;
+ }
+}
+
+@media screen and (min-width: 321px) and (max-width: 480px) {
+ #noVNC_clipboard_text {
+ width: 250px;
+ }
+ #noVNC_logo {
+ font-size: 110px;
+ }
+}
+
+@media screen and (max-width: 320px) {
+ .noVNC_status_button {
+ font-size: 9px;
+ }
+ #noVNC_clipboard_text {
+ width: 220px;
+ }
+ #noVNC_logo {
+ font-size: 90px;
+ }
+}
diff --git a/public/novnc/include/base64.js b/public/novnc/include/base64.js
new file mode 100644
index 00000000..651fbadc
--- /dev/null
+++ b/public/novnc/include/base64.js
@@ -0,0 +1,113 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// From: http://hg.mozilla.org/mozilla-central/raw-file/ec10630b1a54/js/src/devtools/jint/sunspider/string-base64.js
+
+/*jslint white: false */
+/*global console */
+
+var Base64 = {
+ /* Convert data (an array of integers) to a Base64 string. */
+ toBase64Table : 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='.split(''),
+ base64Pad : '=',
+
+ encode: function (data) {
+ "use strict";
+ var result = '';
+ var toBase64Table = Base64.toBase64Table;
+ var length = data.length;
+ var lengthpad = (length % 3);
+ // Convert every three bytes to 4 ascii characters.
+
+ for (var i = 0; i < (length - 2); i += 3) {
+ result += toBase64Table[data[i] >> 2];
+ result += toBase64Table[((data[i] & 0x03) << 4) + (data[i + 1] >> 4)];
+ result += toBase64Table[((data[i + 1] & 0x0f) << 2) + (data[i + 2] >> 6)];
+ result += toBase64Table[data[i + 2] & 0x3f];
+ }
+
+ // Convert the remaining 1 or 2 bytes, pad out to 4 characters.
+ var j = 0;
+ if (lengthpad === 2) {
+ j = length - lengthpad;
+ result += toBase64Table[data[j] >> 2];
+ result += toBase64Table[((data[j] & 0x03) << 4) + (data[j + 1] >> 4)];
+ result += toBase64Table[(data[j + 1] & 0x0f) << 2];
+ result += toBase64Table[64];
+ } else if (lengthpad === 1) {
+ j = length - lengthpad;
+ result += toBase64Table[data[j] >> 2];
+ result += toBase64Table[(data[j] & 0x03) << 4];
+ result += toBase64Table[64];
+ result += toBase64Table[64];
+ }
+
+ return result;
+ },
+
+ /* Convert Base64 data to a string */
+ /* jshint -W013 */
+ toBinaryTable : [
+ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
+ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
+ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,62, -1,-1,-1,63,
+ 52,53,54,55, 56,57,58,59, 60,61,-1,-1, -1, 0,-1,-1,
+ -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14,
+ 15,16,17,18, 19,20,21,22, 23,24,25,-1, -1,-1,-1,-1,
+ -1,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40,
+ 41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1
+ ],
+ /* jshint +W013 */
+
+ decode: function (data, offset) {
+ "use strict";
+ offset = typeof(offset) !== 'undefined' ? offset : 0;
+ var toBinaryTable = Base64.toBinaryTable;
+ var base64Pad = Base64.base64Pad;
+ var result, result_length;
+ var leftbits = 0; // number of bits decoded, but yet to be appended
+ var leftdata = 0; // bits decoded, but yet to be appended
+ var data_length = data.indexOf('=') - offset;
+
+ if (data_length < 0) { data_length = data.length - offset; }
+
+ /* Every four characters is 3 resulting numbers */
+ result_length = (data_length >> 2) * 3 + Math.floor((data_length % 4) / 1.5);
+ result = new Array(result_length);
+
+ // Convert one by one.
+ for (var idx = 0, i = offset; i < data.length; i++) {
+ var c = toBinaryTable[data.charCodeAt(i) & 0x7f];
+ var padding = (data.charAt(i) === base64Pad);
+ // Skip illegal characters and whitespace
+ if (c === -1) {
+ console.error("Illegal character code " + data.charCodeAt(i) + " at position " + i);
+ continue;
+ }
+
+ // Collect data into leftdata, update bitcount
+ leftdata = (leftdata << 6) | c;
+ leftbits += 6;
+
+ // If we have 8 or more bits, append 8 bits to the result
+ if (leftbits >= 8) {
+ leftbits -= 8;
+ // Append if not padding.
+ if (!padding) {
+ result[idx++] = (leftdata >> leftbits) & 0xff;
+ }
+ leftdata &= (1 << leftbits) - 1;
+ }
+ }
+
+ // If there are any bits left, the base64 string was corrupted
+ if (leftbits) {
+ err = new Error('Corrupted base64 string');
+ err.name = 'Base64-Error';
+ throw err;
+ }
+
+ return result;
+ }
+}; /* End of Base64 namespace */
diff --git a/public/novnc/include/black.css b/public/novnc/include/black.css
new file mode 100644
index 00000000..5c4558dc
--- /dev/null
+++ b/public/novnc/include/black.css
@@ -0,0 +1,71 @@
+/*
+ * noVNC black CSS
+ * Copyright (C) 2012 Joel Martin
+ * Copyright (C) 2013 Samuel Mannehed for Cendio AB
+ * noVNC is licensed under the MPL 2.0 (see LICENSE.txt)
+ * This file is licensed under the 2-Clause BSD license (see LICENSE.txt).
+ */
+
+#noVNC_keyboardinput {
+ background-color:#000;
+}
+
+.noVNC_status_normal {
+ background: #4c4c4c; /* Old browsers */
+ background: -moz-linear-gradient(top, #4c4c4c 0%, #2c2c2c 50%, #000000 51%, #131313 100%); /* FF3.6+ */
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#4c4c4c), color-stop(50%,#2c2c2c), color-stop(51%,#000000), color-stop(100%,#131313)); /* Chrome,Safari4+ */
+ background: -webkit-linear-gradient(top, #4c4c4c 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* Chrome10+,Safari5.1+ */
+ background: -o-linear-gradient(top, #4c4c4c 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* Opera11.10+ */
+ background: -ms-linear-gradient(top, #4c4c4c 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* IE10+ */
+ background: linear-gradient(top, #4c4c4c 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* W3C */
+}
+.noVNC_status_error {
+ background: #f04040; /* Old browsers */
+ background: -moz-linear-gradient(top, #f04040 0%, #2c2c2c 50%, #000000 51%, #131313 100%); /* FF3.6+ */
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#f04040), color-stop(50%,#2c2c2c), color-stop(51%,#000000), color-stop(100%,#131313)); /* Chrome,Safari4+ */
+ background: -webkit-linear-gradient(top, #f04040 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* Chrome10+,Safari5.1+ */
+ background: -o-linear-gradient(top, #f04040 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* Opera11.10+ */
+ background: -ms-linear-gradient(top, #f04040 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* IE10+ */
+ background: linear-gradient(top, #f04040 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* W3C */
+}
+.noVNC_status_warn {
+ background: #f0f040; /* Old browsers */
+ background: -moz-linear-gradient(top, #f0f040 0%, #2c2c2c 50%, #000000 51%, #131313 100%); /* FF3.6+ */
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#f0f040), color-stop(50%,#2c2c2c), color-stop(51%,#000000), color-stop(100%,#131313)); /* Chrome,Safari4+ */
+ background: -webkit-linear-gradient(top, #f0f040 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* Chrome10+,Safari5.1+ */
+ background: -o-linear-gradient(top, #f0f040 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* Opera11.10+ */
+ background: -ms-linear-gradient(top, #f0f040 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* IE10+ */
+ background: linear-gradient(top, #f0f040 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* W3C */
+}
+
+.triangle-right {
+ border:2px solid #fff;
+ background:#000;
+ color:#fff;
+}
+
+.noVNC_status_button {
+ font-size: 12px;
+ vertical-align: middle;
+ border:1px solid #4c4c4c;
+
+ background: #4c4c4c; /* Old browsers */
+ background: -moz-linear-gradient(top, #4c4c4c 0%, #2c2c2c 50%, #000000 51%, #131313 100%); /* FF3.6+ */
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#4c4c4c), color-stop(50%,#2c2c2c), color-stop(51%,#000000), color-stop(100%,#131313)); /* Chrome,Safari4+ */
+ background: -webkit-linear-gradient(top, #4c4c4c 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* Chrome10+,Safari5.1+ */
+ background: -o-linear-gradient(top, #4c4c4c 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* Opera11.10+ */
+ background: -ms-linear-gradient(top, #4c4c4c 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* IE10+ */
+ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#4c4c4c', endColorstr='#131313',GradientType=0 ); /* IE6-9 */
+ background: linear-gradient(top, #4c4c4c 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* W3C */
+}
+
+.noVNC_status_button_selected {
+ background: #9dd53a; /* Old browsers */
+ background: -moz-linear-gradient(top, #9dd53a 0%, #a1d54f 50%, #80c217 51%, #7cbc0a 100%); /* FF3.6+ */
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#9dd53a), color-stop(50%,#a1d54f), color-stop(51%,#80c217), color-stop(100%,#7cbc0a)); /* Chrome,Safari4+ */
+ background: -webkit-linear-gradient(top, #9dd53a 0%,#a1d54f 50%,#80c217 51%,#7cbc0a 100%); /* Chrome10+,Safari5.1+ */
+ background: -o-linear-gradient(top, #9dd53a 0%,#a1d54f 50%,#80c217 51%,#7cbc0a 100%); /* Opera11.10+ */
+ background: -ms-linear-gradient(top, #9dd53a 0%,#a1d54f 50%,#80c217 51%,#7cbc0a 100%); /* IE10+ */
+ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#9dd53a', endColorstr='#7cbc0a',GradientType=0 ); /* IE6-9 */
+ background: linear-gradient(top, #9dd53a 0%,#a1d54f 50%,#80c217 51%,#7cbc0a 100%); /* W3C */
+}
diff --git a/public/novnc/include/blue.css b/public/novnc/include/blue.css
new file mode 100644
index 00000000..4ab53bde
--- /dev/null
+++ b/public/novnc/include/blue.css
@@ -0,0 +1,64 @@
+/*
+ * noVNC blue CSS
+ * Copyright (C) 2012 Joel Martin
+ * Copyright (C) 2013 Samuel Mannehed for Cendio AB
+ * noVNC is licensed under the MPL 2.0 (see LICENSE.txt)
+ * This file is licensed under the 2-Clause BSD license (see LICENSE.txt).
+ */
+
+.noVNC_status_normal {
+ background-color:#04073d;
+ background-image: -webkit-gradient(
+ linear,
+ left bottom,
+ left top,
+ color-stop(0.54, rgb(10,15,79)),
+ color-stop(0.5, rgb(4,7,61))
+ );
+ background-image: -moz-linear-gradient(
+ center bottom,
+ rgb(10,15,79) 54%,
+ rgb(4,7,61) 50%
+ );
+}
+.noVNC_status_error {
+ background-color:#f04040;
+ background-image: -webkit-gradient(
+ linear,
+ left bottom,
+ left top,
+ color-stop(0.54, rgb(240,64,64)),
+ color-stop(0.5, rgb(4,7,61))
+ );
+ background-image: -moz-linear-gradient(
+ center bottom,
+ rgb(4,7,61) 54%,
+ rgb(249,64,64) 50%
+ );
+}
+.noVNC_status_warn {
+ background-color:#f0f040;
+ background-image: -webkit-gradient(
+ linear,
+ left bottom,
+ left top,
+ color-stop(0.54, rgb(240,240,64)),
+ color-stop(0.5, rgb(4,7,61))
+ );
+ background-image: -moz-linear-gradient(
+ center bottom,
+ rgb(4,7,61) 54%,
+ rgb(240,240,64) 50%
+ );
+}
+
+.triangle-right {
+ border:2px solid #fff;
+ background:#04073d;
+ color:#fff;
+}
+
+#noVNC_keyboardinput {
+ background-color:#04073d;
+}
+
diff --git a/public/novnc/include/chrome-app/tcp-client.js b/public/novnc/include/chrome-app/tcp-client.js
new file mode 100644
index 00000000..b8c125f5
--- /dev/null
+++ b/public/novnc/include/chrome-app/tcp-client.js
@@ -0,0 +1,321 @@
+/*
+Copyright 2012 Google Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+Author: Boris Smus (smus@chromium.org)
+*/
+
+(function(exports) {
+
+ // Define some local variables here.
+ var socket = chrome.socket || chrome.experimental.socket;
+ var dns = chrome.experimental.dns;
+
+ /**
+ * Creates an instance of the client
+ *
+ * @param {String} host The remote host to connect to
+ * @param {Number} port The port to connect to at the remote host
+ */
+ function TcpClient(host, port, pollInterval) {
+ this.host = host;
+ this.port = port;
+ this.pollInterval = pollInterval || 15;
+
+ // Callback functions.
+ this.callbacks = {
+ connect: null, // Called when socket is connected.
+ disconnect: null, // Called when socket is disconnected.
+ recvBuffer: null, // Called (as ArrayBuffer) when client receives data from server.
+ recvString: null, // Called (as string) when client receives data from server.
+ sent: null // Called when client sends data to server.
+ };
+
+ // Socket.
+ this.socketId = null;
+ this.isConnected = false;
+
+ log('initialized tcp client');
+ }
+
+ /**
+ * Connects to the TCP socket, and creates an open socket.
+ *
+ * @see http://developer.chrome.com/trunk/apps/socket.html#method-create
+ * @param {Function} callback The function to call on connection
+ */
+ TcpClient.prototype.connect = function(callback) {
+ // First resolve the hostname to an IP.
+ dns.resolve(this.host, function(result) {
+ this.addr = result.address;
+ socket.create('tcp', {}, this._onCreate.bind(this));
+
+ // Register connect callback.
+ this.callbacks.connect = callback;
+ }.bind(this));
+ };
+
+ /**
+ * Sends an arraybuffer/view down the wire to the remote side
+ *
+ * @see http://developer.chrome.com/trunk/apps/socket.html#method-write
+ * @param {String} msg The arraybuffer/view to send
+ * @param {Function} callback The function to call when the message has sent
+ */
+ TcpClient.prototype.sendBuffer = function(buf, callback) {
+ if (buf.buffer) {
+ buf = buf.buffer;
+ }
+
+ /*
+ // Debug
+ var bytes = [], u8 = new Uint8Array(buf);
+ for (var i = 0; i < u8.length; i++) {
+ bytes.push(u8[i]);
+ }
+ log("sending bytes: " + (bytes.join(',')));
+ */
+
+ socket.write(this.socketId, buf, this._onWriteComplete.bind(this));
+
+ // Register sent callback.
+ this.callbacks.sent = callback;
+ };
+
+ /**
+ * Sends a string down the wire to the remote side
+ *
+ * @see http://developer.chrome.com/trunk/apps/socket.html#method-write
+ * @param {String} msg The string to send
+ * @param {Function} callback The function to call when the message has sent
+ */
+ TcpClient.prototype.sendString = function(msg, callback) {
+ /*
+ // Debug
+ log("sending string: " + msg);
+ */
+
+ this._stringToArrayBuffer(msg, function(arrayBuffer) {
+ socket.write(this.socketId, arrayBuffer, this._onWriteComplete.bind(this));
+ }.bind(this));
+
+ // Register sent callback.
+ this.callbacks.sent = callback;
+ };
+
+ /**
+ * Sets the callback for when a message is received
+ *
+ * @param {Function} callback The function to call when a message has arrived
+ * @param {String} type The callback argument type: "arraybuffer" or "string"
+ */
+ TcpClient.prototype.addResponseListener = function(callback, type) {
+ if (typeof type === "undefined") {
+ type = "arraybuffer";
+ }
+ // Register received callback.
+ if (type === "string") {
+ this.callbacks.recvString = callback;
+ } else {
+ this.callbacks.recvBuffer = callback;
+ }
+ };
+
+ /**
+ * Sets the callback for when the socket disconnects
+ *
+ * @param {Function} callback The function to call when the socket disconnects
+ * @param {String} type The callback argument type: "arraybuffer" or "string"
+ */
+ TcpClient.prototype.addDisconnectListener = function(callback) {
+ // Register disconnect callback.
+ this.callbacks.disconnect = callback;
+ };
+
+ /**
+ * Disconnects from the remote side
+ *
+ * @see http://developer.chrome.com/trunk/apps/socket.html#method-disconnect
+ */
+ TcpClient.prototype.disconnect = function() {
+ if (this.isConnected) {
+ this.isConnected = false;
+ socket.disconnect(this.socketId);
+ if (this.callbacks.disconnect) {
+ this.callbacks.disconnect();
+ }
+ log('socket disconnected');
+ }
+ };
+
+ /**
+ * The callback function used for when we attempt to have Chrome
+ * create a socket. If the socket is successfully created
+ * we go ahead and connect to the remote side.
+ *
+ * @private
+ * @see http://developer.chrome.com/trunk/apps/socket.html#method-connect
+ * @param {Object} createInfo The socket details
+ */
+ TcpClient.prototype._onCreate = function(createInfo) {
+ this.socketId = createInfo.socketId;
+ if (this.socketId > 0) {
+ socket.connect(this.socketId, this.addr, this.port, this._onConnectComplete.bind(this));
+ } else {
+ error('Unable to create socket');
+ }
+ };
+
+ /**
+ * The callback function used for when we attempt to have Chrome
+ * connect to the remote side. If a successful connection is
+ * made then polling starts to check for data to read
+ *
+ * @private
+ * @param {Number} resultCode Indicates whether the connection was successful
+ */
+ TcpClient.prototype._onConnectComplete = function(resultCode) {
+ // Start polling for reads.
+ this.isConnected = true;
+ setTimeout(this._periodicallyRead.bind(this), this.pollInterval);
+
+ if (this.callbacks.connect) {
+ log('connect complete');
+ this.callbacks.connect();
+ }
+ log('onConnectComplete');
+ };
+
+ /**
+ * Checks for new data to read from the socket
+ *
+ * @see http://developer.chrome.com/trunk/apps/socket.html#method-read
+ */
+ TcpClient.prototype._periodicallyRead = function() {
+ var that = this;
+ socket.getInfo(this.socketId, function (info) {
+ if (info.connected) {
+ setTimeout(that._periodicallyRead.bind(that), that.pollInterval);
+ socket.read(that.socketId, null, that._onDataRead.bind(that));
+ } else if (that.isConnected) {
+ log('socket disconnect detected');
+ that.disconnect();
+ }
+ });
+ };
+
+ /**
+ * Callback function for when data has been read from the socket.
+ * Converts the array buffer that is read in to a string
+ * and sends it on for further processing by passing it to
+ * the previously assigned callback function.
+ *
+ * @private
+ * @see TcpClient.prototype.addResponseListener
+ * @param {Object} readInfo The incoming message
+ */
+ TcpClient.prototype._onDataRead = function(readInfo) {
+ // Call received callback if there's data in the response.
+ if (readInfo.resultCode > 0) {
+ log('onDataRead');
+
+ /*
+ // Debug
+ var bytes = [], u8 = new Uint8Array(readInfo.data);
+ for (var i = 0; i < u8.length; i++) {
+ bytes.push(u8[i]);
+ }
+ log("received bytes: " + (bytes.join(',')));
+ */
+
+ if (this.callbacks.recvBuffer) {
+ // Return raw ArrayBuffer directly.
+ this.callbacks.recvBuffer(readInfo.data);
+ }
+ if (this.callbacks.recvString) {
+ // Convert ArrayBuffer to string.
+ this._arrayBufferToString(readInfo.data, function(str) {
+ this.callbacks.recvString(str);
+ }.bind(this));
+ }
+
+ // Trigger another read right away
+ setTimeout(this._periodicallyRead.bind(this), 0);
+ }
+ };
+
+ /**
+ * Callback for when data has been successfully
+ * written to the socket.
+ *
+ * @private
+ * @param {Object} writeInfo The outgoing message
+ */
+ TcpClient.prototype._onWriteComplete = function(writeInfo) {
+ log('onWriteComplete');
+ // Call sent callback.
+ if (this.callbacks.sent) {
+ this.callbacks.sent(writeInfo);
+ }
+ };
+
+ /**
+ * Converts an array buffer to a string
+ *
+ * @private
+ * @param {ArrayBuffer} buf The buffer to convert
+ * @param {Function} callback The function to call when conversion is complete
+ */
+ TcpClient.prototype._arrayBufferToString = function(buf, callback) {
+ var bb = new Blob([new Uint8Array(buf)]);
+ var f = new FileReader();
+ f.onload = function(e) {
+ callback(e.target.result);
+ };
+ f.readAsText(bb);
+ };
+
+ /**
+ * Converts a string to an array buffer
+ *
+ * @private
+ * @param {String} str The string to convert
+ * @param {Function} callback The function to call when conversion is complete
+ */
+ TcpClient.prototype._stringToArrayBuffer = function(str, callback) {
+ var bb = new Blob([str]);
+ var f = new FileReader();
+ f.onload = function(e) {
+ callback(e.target.result);
+ };
+ f.readAsArrayBuffer(bb);
+ };
+
+ /**
+ * Wrapper function for logging
+ */
+ function log(msg) {
+ console.log(msg);
+ }
+
+ /**
+ * Wrapper function for error logging
+ */
+ function error(msg) {
+ console.error(msg);
+ }
+
+ exports.TcpClient = TcpClient;
+
+})(window);
diff --git a/public/novnc/include/des.js b/public/novnc/include/des.js
new file mode 100644
index 00000000..ecbc819e
--- /dev/null
+++ b/public/novnc/include/des.js
@@ -0,0 +1,276 @@
+/*
+ * Ported from Flashlight VNC ActionScript implementation:
+ * http://www.wizhelp.com/flashlight-vnc/
+ *
+ * Full attribution follows:
+ *
+ * -------------------------------------------------------------------------
+ *
+ * This DES class has been extracted from package Acme.Crypto for use in VNC.
+ * The unnecessary odd parity code has been removed.
+ *
+ * These changes are:
+ * Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ *
+
+ * DesCipher - the DES encryption method
+ *
+ * The meat of this code is by Dave Zimmerman , and is:
+ *
+ * Copyright (c) 1996 Widget Workshop, Inc. All Rights Reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software
+ * and its documentation for NON-COMMERCIAL or COMMERCIAL purposes and
+ * without fee is hereby granted, provided that this copyright notice is kept
+ * intact.
+ *
+ * WIDGET WORKSHOP MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY
+ * OF THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+ * TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+ * PARTICULAR PURPOSE, OR NON-INFRINGEMENT. WIDGET WORKSHOP SHALL NOT BE LIABLE
+ * FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
+ * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES.
+ *
+ * THIS SOFTWARE IS NOT DESIGNED OR INTENDED FOR USE OR RESALE AS ON-LINE
+ * CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING FAIL-SAFE
+ * PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES, AIRCRAFT
+ * NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT LIFE
+ * SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * SOFTWARE COULD LEAD DIRECTLY TO DEATH, PERSONAL INJURY, OR SEVERE
+ * PHYSICAL OR ENVIRONMENTAL DAMAGE ("HIGH RISK ACTIVITIES"). WIDGET WORKSHOP
+ * SPECIFICALLY DISCLAIMS ANY EXPRESS OR IMPLIED WARRANTY OF FITNESS FOR
+ * HIGH RISK ACTIVITIES.
+ *
+ *
+ * The rest is:
+ *
+ * Copyright (C) 1996 by Jef Poskanzer . All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * Visit the ACME Labs Java page for up-to-date versions of this and other
+ * fine Java utilities: http://www.acme.com/java/
+ */
+
+/* jslint white: false */
+
+function DES(passwd) {
+ "use strict";
+
+ // Tables, permutations, S-boxes, etc.
+ // jshint -W013
+ var PC2 = [13,16,10,23, 0, 4, 2,27,14, 5,20, 9,22,18,11, 3,
+ 25, 7,15, 6,26,19,12, 1,40,51,30,36,46,54,29,39,
+ 50,44,32,47,43,48,38,55,33,52,45,41,49,35,28,31 ],
+ totrot = [ 1, 2, 4, 6, 8,10,12,14,15,17,19,21,23,25,27,28],
+ z = 0x0, a,b,c,d,e,f, SP1,SP2,SP3,SP4,SP5,SP6,SP7,SP8,
+ keys = [];
+
+ // jshint -W015
+ a=1<<16; b=1<<24; c=a|b; d=1<<2; e=1<<10; f=d|e;
+ SP1 = [c|e,z|z,a|z,c|f,c|d,a|f,z|d,a|z,z|e,c|e,c|f,z|e,b|f,c|d,b|z,z|d,
+ z|f,b|e,b|e,a|e,a|e,c|z,c|z,b|f,a|d,b|d,b|d,a|d,z|z,z|f,a|f,b|z,
+ a|z,c|f,z|d,c|z,c|e,b|z,b|z,z|e,c|d,a|z,a|e,b|d,z|e,z|d,b|f,a|f,
+ c|f,a|d,c|z,b|f,b|d,z|f,a|f,c|e,z|f,b|e,b|e,z|z,a|d,a|e,z|z,c|d];
+ a=1<<20; b=1<<31; c=a|b; d=1<<5; e=1<<15; f=d|e;
+ SP2 = [c|f,b|e,z|e,a|f,a|z,z|d,c|d,b|f,b|d,c|f,c|e,b|z,b|e,a|z,z|d,c|d,
+ a|e,a|d,b|f,z|z,b|z,z|e,a|f,c|z,a|d,b|d,z|z,a|e,z|f,c|e,c|z,z|f,
+ z|z,a|f,c|d,a|z,b|f,c|z,c|e,z|e,c|z,b|e,z|d,c|f,a|f,z|d,z|e,b|z,
+ z|f,c|e,a|z,b|d,a|d,b|f,b|d,a|d,a|e,z|z,b|e,z|f,b|z,c|d,c|f,a|e];
+ a=1<<17; b=1<<27; c=a|b; d=1<<3; e=1<<9; f=d|e;
+ SP3 = [z|f,c|e,z|z,c|d,b|e,z|z,a|f,b|e,a|d,b|d,b|d,a|z,c|f,a|d,c|z,z|f,
+ b|z,z|d,c|e,z|e,a|e,c|z,c|d,a|f,b|f,a|e,a|z,b|f,z|d,c|f,z|e,b|z,
+ c|e,b|z,a|d,z|f,a|z,c|e,b|e,z|z,z|e,a|d,c|f,b|e,b|d,z|e,z|z,c|d,
+ b|f,a|z,b|z,c|f,z|d,a|f,a|e,b|d,c|z,b|f,z|f,c|z,a|f,z|d,c|d,a|e];
+ a=1<<13; b=1<<23; c=a|b; d=1<<0; e=1<<7; f=d|e;
+ SP4 = [c|d,a|f,a|f,z|e,c|e,b|f,b|d,a|d,z|z,c|z,c|z,c|f,z|f,z|z,b|e,b|d,
+ z|d,a|z,b|z,c|d,z|e,b|z,a|d,a|e,b|f,z|d,a|e,b|e,a|z,c|e,c|f,z|f,
+ b|e,b|d,c|z,c|f,z|f,z|z,z|z,c|z,a|e,b|e,b|f,z|d,c|d,a|f,a|f,z|e,
+ c|f,z|f,z|d,a|z,b|d,a|d,c|e,b|f,a|d,a|e,b|z,c|d,z|e,b|z,a|z,c|e];
+ a=1<<25; b=1<<30; c=a|b; d=1<<8; e=1<<19; f=d|e;
+ SP5 = [z|d,a|f,a|e,c|d,z|e,z|d,b|z,a|e,b|f,z|e,a|d,b|f,c|d,c|e,z|f,b|z,
+ a|z,b|e,b|e,z|z,b|d,c|f,c|f,a|d,c|e,b|d,z|z,c|z,a|f,a|z,c|z,z|f,
+ z|e,c|d,z|d,a|z,b|z,a|e,c|d,b|f,a|d,b|z,c|e,a|f,b|f,z|d,a|z,c|e,
+ c|f,z|f,c|z,c|f,a|e,z|z,b|e,c|z,z|f,a|d,b|d,z|e,z|z,b|e,a|f,b|d];
+ a=1<<22; b=1<<29; c=a|b; d=1<<4; e=1<<14; f=d|e;
+ SP6 = [b|d,c|z,z|e,c|f,c|z,z|d,c|f,a|z,b|e,a|f,a|z,b|d,a|d,b|e,b|z,z|f,
+ z|z,a|d,b|f,z|e,a|e,b|f,z|d,c|d,c|d,z|z,a|f,c|e,z|f,a|e,c|e,b|z,
+ b|e,z|d,c|d,a|e,c|f,a|z,z|f,b|d,a|z,b|e,b|z,z|f,b|d,c|f,a|e,c|z,
+ a|f,c|e,z|z,c|d,z|d,z|e,c|z,a|f,z|e,a|d,b|f,z|z,c|e,b|z,a|d,b|f];
+ a=1<<21; b=1<<26; c=a|b; d=1<<1; e=1<<11; f=d|e;
+ SP7 = [a|z,c|d,b|f,z|z,z|e,b|f,a|f,c|e,c|f,a|z,z|z,b|d,z|d,b|z,c|d,z|f,
+ b|e,a|f,a|d,b|e,b|d,c|z,c|e,a|d,c|z,z|e,z|f,c|f,a|e,z|d,b|z,a|e,
+ b|z,a|e,a|z,b|f,b|f,c|d,c|d,z|d,a|d,b|z,b|e,a|z,c|e,z|f,a|f,c|e,
+ z|f,b|d,c|f,c|z,a|e,z|z,z|d,c|f,z|z,a|f,c|z,z|e,b|d,b|e,z|e,a|d];
+ a=1<<18; b=1<<28; c=a|b; d=1<<6; e=1<<12; f=d|e;
+ SP8 = [b|f,z|e,a|z,c|f,b|z,b|f,z|d,b|z,a|d,c|z,c|f,a|e,c|e,a|f,z|e,z|d,
+ c|z,b|d,b|e,z|f,a|e,a|d,c|d,c|e,z|f,z|z,z|z,c|d,b|d,b|e,a|f,a|z,
+ a|f,a|z,c|e,z|e,z|d,c|d,z|e,a|f,b|e,z|d,b|d,c|z,c|d,b|z,a|z,b|f,
+ z|z,c|f,a|d,b|d,c|z,b|e,b|f,z|z,c|f,a|e,a|e,z|f,z|f,a|d,b|z,c|e];
+ // jshint +W013,+W015
+
+ // Set the key.
+ function setKeys(keyBlock) {
+ var i, j, l, m, n, o, pc1m = [], pcr = [], kn = [],
+ raw0, raw1, rawi, KnLi;
+
+ for (j = 0, l = 56; j < 56; ++j, l -= 8) {
+ l += l < -5 ? 65 : l < -3 ? 31 : l < -1 ? 63 : l === 27 ? 35 : 0; // PC1
+ m = l & 0x7;
+ pc1m[j] = ((keyBlock[l >>> 3] & (1<>> 10;
+ keys[KnLi] |= (raw1 & 0x00000fc0) >>> 6;
+ ++KnLi;
+ keys[KnLi] = (raw0 & 0x0003f000) << 12;
+ keys[KnLi] |= (raw0 & 0x0000003f) << 16;
+ keys[KnLi] |= (raw1 & 0x0003f000) >>> 4;
+ keys[KnLi] |= (raw1 & 0x0000003f);
+ ++KnLi;
+ }
+ }
+
+ // Encrypt 8 bytes of text
+ function enc8(text) {
+ var i = 0, b = text.slice(), fval, keysi = 0,
+ l, r, x; // left, right, accumulator
+
+ // Squash 8 bytes to 2 ints
+ l = b[i++]<<24 | b[i++]<<16 | b[i++]<<8 | b[i++];
+ r = b[i++]<<24 | b[i++]<<16 | b[i++]<<8 | b[i++];
+
+ x = ((l >>> 4) ^ r) & 0x0f0f0f0f;
+ r ^= x;
+ l ^= (x << 4);
+ x = ((l >>> 16) ^ r) & 0x0000ffff;
+ r ^= x;
+ l ^= (x << 16);
+ x = ((r >>> 2) ^ l) & 0x33333333;
+ l ^= x;
+ r ^= (x << 2);
+ x = ((r >>> 8) ^ l) & 0x00ff00ff;
+ l ^= x;
+ r ^= (x << 8);
+ r = (r << 1) | ((r >>> 31) & 1);
+ x = (l ^ r) & 0xaaaaaaaa;
+ l ^= x;
+ r ^= x;
+ l = (l << 1) | ((l >>> 31) & 1);
+
+ for (i = 0; i < 8; ++i) {
+ x = (r << 28) | (r >>> 4);
+ x ^= keys[keysi++];
+ fval = SP7[x & 0x3f];
+ fval |= SP5[(x >>> 8) & 0x3f];
+ fval |= SP3[(x >>> 16) & 0x3f];
+ fval |= SP1[(x >>> 24) & 0x3f];
+ x = r ^ keys[keysi++];
+ fval |= SP8[x & 0x3f];
+ fval |= SP6[(x >>> 8) & 0x3f];
+ fval |= SP4[(x >>> 16) & 0x3f];
+ fval |= SP2[(x >>> 24) & 0x3f];
+ l ^= fval;
+ x = (l << 28) | (l >>> 4);
+ x ^= keys[keysi++];
+ fval = SP7[x & 0x3f];
+ fval |= SP5[(x >>> 8) & 0x3f];
+ fval |= SP3[(x >>> 16) & 0x3f];
+ fval |= SP1[(x >>> 24) & 0x3f];
+ x = l ^ keys[keysi++];
+ fval |= SP8[x & 0x0000003f];
+ fval |= SP6[(x >>> 8) & 0x3f];
+ fval |= SP4[(x >>> 16) & 0x3f];
+ fval |= SP2[(x >>> 24) & 0x3f];
+ r ^= fval;
+ }
+
+ r = (r << 31) | (r >>> 1);
+ x = (l ^ r) & 0xaaaaaaaa;
+ l ^= x;
+ r ^= x;
+ l = (l << 31) | (l >>> 1);
+ x = ((l >>> 8) ^ r) & 0x00ff00ff;
+ r ^= x;
+ l ^= (x << 8);
+ x = ((l >>> 2) ^ r) & 0x33333333;
+ r ^= x;
+ l ^= (x << 2);
+ x = ((r >>> 16) ^ l) & 0x0000ffff;
+ l ^= x;
+ r ^= (x << 16);
+ x = ((r >>> 4) ^ l) & 0x0f0f0f0f;
+ l ^= x;
+ r ^= (x << 4);
+
+ // Spread ints to bytes
+ x = [r, l];
+ for (i = 0; i < 8; i++) {
+ b[i] = (x[i>>>2] >>> (8 * (3 - (i % 4)))) % 256;
+ if (b[i] < 0) { b[i] += 256; } // unsigned
+ }
+ return b;
+ }
+
+ // Encrypt 16 bytes of text using passwd as key
+ function encrypt(t) {
+ return enc8(t.slice(0, 8)).concat(enc8(t.slice(8, 16)));
+ }
+
+ setKeys(passwd); // Setup keys
+ return {'encrypt': encrypt}; // Public interface
+
+} // function DES
diff --git a/public/novnc/include/display.js b/public/novnc/include/display.js
new file mode 100644
index 00000000..a492817d
--- /dev/null
+++ b/public/novnc/include/display.js
@@ -0,0 +1,908 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2012 Joel Martin
+ * Copyright (C) 2015 Samuel Mannehed for Cendio AB
+ * Licensed under MPL 2.0 (see LICENSE.txt)
+ *
+ * See README.md for usage and integration instructions.
+ */
+
+/*jslint browser: true, white: false */
+/*global Util, Base64, changeCursor */
+
+var Display;
+
+(function () {
+ "use strict";
+
+ var SUPPORTS_IMAGEDATA_CONSTRUCTOR = false;
+ try {
+ new ImageData(new Uint8ClampedArray(1), 1, 1);
+ SUPPORTS_IMAGEDATA_CONSTRUCTOR = true;
+ } catch (ex) {
+ // ignore failure
+ }
+
+ Display = function (defaults) {
+ this._drawCtx = null;
+ this._c_forceCanvas = false;
+
+ this._renderQ = []; // queue drawing actions for in-oder rendering
+
+ // the full frame buffer (logical canvas) size
+ this._fb_width = 0;
+ this._fb_height = 0;
+
+ // the size limit of the viewport (start disabled)
+ this._maxWidth = 0;
+ this._maxHeight = 0;
+
+ // the visible "physical canvas" viewport
+ this._viewportLoc = { 'x': 0, 'y': 0, 'w': 0, 'h': 0 };
+ this._cleanRect = { 'x1': 0, 'y1': 0, 'x2': -1, 'y2': -1 };
+
+ this._prevDrawStyle = "";
+ this._tile = null;
+ this._tile16x16 = null;
+ this._tile_x = 0;
+ this._tile_y = 0;
+
+ Util.set_defaults(this, defaults, {
+ 'true_color': true,
+ 'colourMap': [],
+ 'scale': 1.0,
+ 'viewport': false,
+ 'render_mode': ''
+ });
+
+ Util.Debug(">> Display.constructor");
+
+ if (!this._target) {
+ throw new Error("Target must be set");
+ }
+
+ if (typeof this._target === 'string') {
+ throw new Error('target must be a DOM element');
+ }
+
+ if (!this._target.getContext) {
+ throw new Error("no getContext method");
+ }
+
+ if (!this._drawCtx) {
+ this._drawCtx = this._target.getContext('2d');
+ }
+
+ Util.Debug("User Agent: " + navigator.userAgent);
+ if (Util.Engine.gecko) { Util.Debug("Browser: gecko " + Util.Engine.gecko); }
+ if (Util.Engine.webkit) { Util.Debug("Browser: webkit " + Util.Engine.webkit); }
+ if (Util.Engine.trident) { Util.Debug("Browser: trident " + Util.Engine.trident); }
+ if (Util.Engine.presto) { Util.Debug("Browser: presto " + Util.Engine.presto); }
+
+ this.clear();
+
+ // Check canvas features
+ if ('createImageData' in this._drawCtx) {
+ this._render_mode = 'canvas rendering';
+ } else {
+ throw new Error("Canvas does not support createImageData");
+ }
+
+ if (this._prefer_js === null) {
+ Util.Info("Prefering javascript operations");
+ this._prefer_js = true;
+ }
+
+ // Determine browser support for setting the cursor via data URI scheme
+ if (this._cursor_uri || this._cursor_uri === null ||
+ this._cursor_uri === undefined) {
+ this._cursor_uri = Util.browserSupportsCursorURIs();
+ }
+
+ Util.Debug("<< Display.constructor");
+ };
+
+ Display.prototype = {
+ // Public methods
+ viewportChangePos: function (deltaX, deltaY) {
+ var vp = this._viewportLoc;
+ deltaX = Math.floor(deltaX);
+ deltaY = Math.floor(deltaY);
+
+ if (!this._viewport) {
+ deltaX = -vp.w; // clamped later of out of bounds
+ deltaY = -vp.h;
+ }
+
+ var vx2 = vp.x + vp.w - 1;
+ var vy2 = vp.y + vp.h - 1;
+
+ // Position change
+
+ if (deltaX < 0 && vp.x + deltaX < 0) {
+ deltaX = -vp.x;
+ }
+ if (vx2 + deltaX >= this._fb_width) {
+ deltaX -= vx2 + deltaX - this._fb_width + 1;
+ }
+
+ if (vp.y + deltaY < 0) {
+ deltaY = -vp.y;
+ }
+ if (vy2 + deltaY >= this._fb_height) {
+ deltaY -= (vy2 + deltaY - this._fb_height + 1);
+ }
+
+ if (deltaX === 0 && deltaY === 0) {
+ return;
+ }
+ Util.Debug("viewportChange deltaX: " + deltaX + ", deltaY: " + deltaY);
+
+ vp.x += deltaX;
+ vx2 += deltaX;
+ vp.y += deltaY;
+ vy2 += deltaY;
+
+ // Update the clean rectangle
+ var cr = this._cleanRect;
+ if (vp.x > cr.x1) {
+ cr.x1 = vp.x;
+ }
+ if (vx2 < cr.x2) {
+ cr.x2 = vx2;
+ }
+ if (vp.y > cr.y1) {
+ cr.y1 = vp.y;
+ }
+ if (vy2 < cr.y2) {
+ cr.y2 = vy2;
+ }
+
+ var x1, w;
+ if (deltaX < 0) {
+ // Shift viewport left, redraw left section
+ x1 = 0;
+ w = -deltaX;
+ } else {
+ // Shift viewport right, redraw right section
+ x1 = vp.w - deltaX;
+ w = deltaX;
+ }
+
+ var y1, h;
+ if (deltaY < 0) {
+ // Shift viewport up, redraw top section
+ y1 = 0;
+ h = -deltaY;
+ } else {
+ // Shift viewport down, redraw bottom section
+ y1 = vp.h - deltaY;
+ h = deltaY;
+ }
+
+ var saveStyle = this._drawCtx.fillStyle;
+ var canvas = this._target;
+ this._drawCtx.fillStyle = "rgb(255,255,255)";
+
+ // Due to this bug among others [1] we need to disable the image-smoothing to
+ // avoid getting a blur effect when panning.
+ //
+ // 1. https://bugzilla.mozilla.org/show_bug.cgi?id=1194719
+ //
+ // We need to set these every time since all properties are reset
+ // when the the size is changed
+ if (this._drawCtx.mozImageSmoothingEnabled) {
+ this._drawCtx.mozImageSmoothingEnabled = false;
+ } else if (this._drawCtx.webkitImageSmoothingEnabled) {
+ this._drawCtx.webkitImageSmoothingEnabled = false;
+ } else if (this._drawCtx.msImageSmoothingEnabled) {
+ this._drawCtx.msImageSmoothingEnabled = false;
+ } else if (this._drawCtx.imageSmoothingEnabled) {
+ this._drawCtx.imageSmoothingEnabled = false;
+ }
+
+ // Copy the valid part of the viewport to the shifted location
+ this._drawCtx.drawImage(canvas, 0, 0, vp.w, vp.h, -deltaX, -deltaY, vp.w, vp.h);
+
+ if (deltaX !== 0) {
+ this._drawCtx.fillRect(x1, 0, w, vp.h);
+ }
+ if (deltaY !== 0) {
+ this._drawCtx.fillRect(0, y1, vp.w, h);
+ }
+ this._drawCtx.fillStyle = saveStyle;
+ },
+
+ viewportChangeSize: function(width, height) {
+
+ if (typeof(width) === "undefined" || typeof(height) === "undefined") {
+
+ Util.Debug("Setting viewport to full display region");
+ width = this._fb_width;
+ height = this._fb_height;
+ }
+
+ var vp = this._viewportLoc;
+ if (vp.w !== width || vp.h !== height) {
+
+ if (this._viewport) {
+ if (this._maxWidth !== 0 && width > this._maxWidth) {
+ width = this._maxWidth;
+ }
+ if (this._maxHeight !== 0 && height > this._maxHeight) {
+ height = this._maxHeight;
+ }
+ }
+
+ var cr = this._cleanRect;
+
+ if (width < vp.w && cr.x2 > vp.x + width - 1) {
+ cr.x2 = vp.x + width - 1;
+ }
+ if (height < vp.h && cr.y2 > vp.y + height - 1) {
+ cr.y2 = vp.y + height - 1;
+ }
+
+ vp.w = width;
+ vp.h = height;
+
+ var canvas = this._target;
+ if (canvas.width !== width || canvas.height !== height) {
+
+ // We have to save the canvas data since changing the size will clear it
+ var saveImg = null;
+ if (vp.w > 0 && vp.h > 0 && canvas.width > 0 && canvas.height > 0) {
+ var img_width = canvas.width < vp.w ? canvas.width : vp.w;
+ var img_height = canvas.height < vp.h ? canvas.height : vp.h;
+ saveImg = this._drawCtx.getImageData(0, 0, img_width, img_height);
+ }
+
+ if (canvas.width !== width) {
+ canvas.width = width;
+ canvas.style.width = width + 'px';
+ }
+ if (canvas.height !== height) {
+ canvas.height = height;
+ canvas.style.height = height + 'px';
+ }
+
+ if (saveImg) {
+ this._drawCtx.putImageData(saveImg, 0, 0);
+ }
+ }
+ }
+ },
+
+ // Return a map of clean and dirty areas of the viewport and reset the
+ // tracking of clean and dirty areas
+ //
+ // Returns: { 'cleanBox': { 'x': x, 'y': y, 'w': w, 'h': h},
+ // 'dirtyBoxes': [{ 'x': x, 'y': y, 'w': w, 'h': h }, ...] }
+ getCleanDirtyReset: function () {
+ var vp = this._viewportLoc;
+ var cr = this._cleanRect;
+
+ var cleanBox = { 'x': cr.x1, 'y': cr.y1,
+ 'w': cr.x2 - cr.x1 + 1, 'h': cr.y2 - cr.y1 + 1 };
+
+ var dirtyBoxes = [];
+ if (cr.x1 >= cr.x2 || cr.y1 >= cr.y2) {
+ // Whole viewport is dirty
+ dirtyBoxes.push({ 'x': vp.x, 'y': vp.y, 'w': vp.w, 'h': vp.h });
+ } else {
+ // Redraw dirty regions
+ var vx2 = vp.x + vp.w - 1;
+ var vy2 = vp.y + vp.h - 1;
+
+ if (vp.x < cr.x1) {
+ // left side dirty region
+ dirtyBoxes.push({'x': vp.x, 'y': vp.y,
+ 'w': cr.x1 - vp.x + 1, 'h': vp.h});
+ }
+ if (vx2 > cr.x2) {
+ // right side dirty region
+ dirtyBoxes.push({'x': cr.x2 + 1, 'y': vp.y,
+ 'w': vx2 - cr.x2, 'h': vp.h});
+ }
+ if(vp.y < cr.y1) {
+ // top/middle dirty region
+ dirtyBoxes.push({'x': cr.x1, 'y': vp.y,
+ 'w': cr.x2 - cr.x1 + 1, 'h': cr.y1 - vp.y});
+ }
+ if (vy2 > cr.y2) {
+ // bottom/middle dirty region
+ dirtyBoxes.push({'x': cr.x1, 'y': cr.y2 + 1,
+ 'w': cr.x2 - cr.x1 + 1, 'h': vy2 - cr.y2});
+ }
+ }
+
+ this._cleanRect = {'x1': vp.x, 'y1': vp.y,
+ 'x2': vp.x + vp.w - 1, 'y2': vp.y + vp.h - 1};
+
+ return {'cleanBox': cleanBox, 'dirtyBoxes': dirtyBoxes};
+ },
+
+ absX: function (x) {
+ return x + this._viewportLoc.x;
+ },
+
+ absY: function (y) {
+ return y + this._viewportLoc.y;
+ },
+
+ resize: function (width, height) {
+ this._prevDrawStyle = "";
+
+ this._fb_width = width;
+ this._fb_height = height;
+
+ this._rescale(this._scale);
+
+ this.viewportChangeSize();
+ },
+
+ clear: function () {
+ if (this._logo) {
+ this.resize(this._logo.width, this._logo.height);
+ this.blitStringImage(this._logo.data, 0, 0);
+ } else {
+ if (Util.Engine.trident === 6) {
+ // NB(directxman12): there's a bug in IE10 where we can fail to actually
+ // clear the canvas here because of the resize.
+ // Clearing the current viewport first fixes the issue
+ this._drawCtx.clearRect(0, 0, this._viewportLoc.w, this._viewportLoc.h);
+ }
+ this.resize(240, 20);
+ this._drawCtx.clearRect(0, 0, this._viewportLoc.w, this._viewportLoc.h);
+ }
+
+ this._renderQ = [];
+ },
+
+ fillRect: function (x, y, width, height, color, from_queue) {
+ if (this._renderQ.length !== 0 && !from_queue) {
+ this.renderQ_push({
+ 'type': 'fill',
+ 'x': x,
+ 'y': y,
+ 'width': width,
+ 'height': height,
+ 'color': color
+ });
+ } else {
+ this._setFillColor(color);
+ this._drawCtx.fillRect(x - this._viewportLoc.x, y - this._viewportLoc.y, width, height);
+ }
+ },
+
+ copyImage: function (old_x, old_y, new_x, new_y, w, h, from_queue) {
+ if (this._renderQ.length !== 0 && !from_queue) {
+ this.renderQ_push({
+ 'type': 'copy',
+ 'old_x': old_x,
+ 'old_y': old_y,
+ 'x': new_x,
+ 'y': new_y,
+ 'width': w,
+ 'height': h,
+ });
+ } else {
+ var x1 = old_x - this._viewportLoc.x;
+ var y1 = old_y - this._viewportLoc.y;
+ var x2 = new_x - this._viewportLoc.x;
+ var y2 = new_y - this._viewportLoc.y;
+
+ this._drawCtx.drawImage(this._target, x1, y1, w, h, x2, y2, w, h);
+ }
+ },
+
+ // start updating a tile
+ startTile: function (x, y, width, height, color) {
+ this._tile_x = x;
+ this._tile_y = y;
+ if (width === 16 && height === 16) {
+ this._tile = this._tile16x16;
+ } else {
+ this._tile = this._drawCtx.createImageData(width, height);
+ }
+
+ if (this._prefer_js) {
+ var bgr;
+ if (this._true_color) {
+ bgr = color;
+ } else {
+ bgr = this._colourMap[color[0]];
+ }
+ var red = bgr[2];
+ var green = bgr[1];
+ var blue = bgr[0];
+
+ var data = this._tile.data;
+ for (var i = 0; i < width * height * 4; i += 4) {
+ data[i] = red;
+ data[i + 1] = green;
+ data[i + 2] = blue;
+ data[i + 3] = 255;
+ }
+ } else {
+ this.fillRect(x, y, width, height, color, true);
+ }
+ },
+
+ // update sub-rectangle of the current tile
+ subTile: function (x, y, w, h, color) {
+ if (this._prefer_js) {
+ var bgr;
+ if (this._true_color) {
+ bgr = color;
+ } else {
+ bgr = this._colourMap[color[0]];
+ }
+ var red = bgr[2];
+ var green = bgr[1];
+ var blue = bgr[0];
+ var xend = x + w;
+ var yend = y + h;
+
+ var data = this._tile.data;
+ var width = this._tile.width;
+ for (var j = y; j < yend; j++) {
+ for (var i = x; i < xend; i++) {
+ var p = (i + (j * width)) * 4;
+ data[p] = red;
+ data[p + 1] = green;
+ data[p + 2] = blue;
+ data[p + 3] = 255;
+ }
+ }
+ } else {
+ this.fillRect(this._tile_x + x, this._tile_y + y, w, h, color, true);
+ }
+ },
+
+ // draw the current tile to the screen
+ finishTile: function () {
+ if (this._prefer_js) {
+ this._drawCtx.putImageData(this._tile, this._tile_x - this._viewportLoc.x,
+ this._tile_y - this._viewportLoc.y);
+ }
+ // else: No-op -- already done by setSubTile
+ },
+
+ blitImage: function (x, y, width, height, arr, offset, from_queue) {
+ if (this._renderQ.length !== 0 && !from_queue) {
+ // NB(directxman12): it's technically more performant here to use preallocated arrays,
+ // but it's a lot of extra work for not a lot of payoff -- if we're using the render queue,
+ // this probably isn't getting called *nearly* as much
+ var new_arr = new Uint8Array(width * height * 4);
+ new_arr.set(new Uint8Array(arr.buffer, 0, new_arr.length));
+ this.renderQ_push({
+ 'type': 'blit',
+ 'data': new_arr,
+ 'x': x,
+ 'y': y,
+ 'width': width,
+ 'height': height,
+ });
+ } else if (this._true_color) {
+ this._bgrxImageData(x, y, this._viewportLoc.x, this._viewportLoc.y, width, height, arr, offset);
+ } else {
+ this._cmapImageData(x, y, this._viewportLoc.x, this._viewportLoc.y, width, height, arr, offset);
+ }
+ },
+
+ blitRgbImage: function (x, y , width, height, arr, offset, from_queue) {
+ if (this._renderQ.length !== 0 && !from_queue) {
+ // NB(directxman12): it's technically more performant here to use preallocated arrays,
+ // but it's a lot of extra work for not a lot of payoff -- if we're using the render queue,
+ // this probably isn't getting called *nearly* as much
+ var new_arr = new Uint8Array(width * height * 4);
+ new_arr.set(new Uint8Array(arr.buffer, 0, new_arr.length));
+ this.renderQ_push({
+ 'type': 'blitRgb',
+ 'data': new_arr,
+ 'x': x,
+ 'y': y,
+ 'width': width,
+ 'height': height,
+ });
+ } else if (this._true_color) {
+ this._rgbImageData(x, y, this._viewportLoc.x, this._viewportLoc.y, width, height, arr, offset);
+ } else {
+ // probably wrong?
+ this._cmapImageData(x, y, this._viewportLoc.x, this._viewportLoc.y, width, height, arr, offset);
+ }
+ },
+
+ blitRgbxImage: function (x, y, width, height, arr, offset, from_queue) {
+ if (this._renderQ.length !== 0 && !from_queue) {
+ // NB(directxman12): it's technically more performant here to use preallocated arrays,
+ // but it's a lot of extra work for not a lot of payoff -- if we're using the render queue,
+ // this probably isn't getting called *nearly* as much
+ var new_arr = new Uint8Array(width * height * 4);
+ new_arr.set(new Uint8Array(arr.buffer, 0, new_arr.length));
+ this.renderQ_push({
+ 'type': 'blitRgbx',
+ 'data': new_arr,
+ 'x': x,
+ 'y': y,
+ 'width': width,
+ 'height': height,
+ });
+ } else {
+ this._rgbxImageData(x, y, this._viewportLoc.x, this._viewportLoc.y, width, height, arr, offset);
+ }
+ },
+
+ blitStringImage: function (str, x, y) {
+ var img = new Image();
+ img.onload = function () {
+ this._drawCtx.drawImage(img, x - this._viewportLoc.x, y - this._viewportLoc.y);
+ }.bind(this);
+ img.src = str;
+ return img; // for debugging purposes
+ },
+
+ // wrap ctx.drawImage but relative to viewport
+ drawImage: function (img, x, y) {
+ this._drawCtx.drawImage(img, x - this._viewportLoc.x, y - this._viewportLoc.y);
+ },
+
+ renderQ_push: function (action) {
+ this._renderQ.push(action);
+ if (this._renderQ.length === 1) {
+ // If this can be rendered immediately it will be, otherwise
+ // the scanner will start polling the queue (every
+ // requestAnimationFrame interval)
+ this._scan_renderQ();
+ }
+ },
+
+ changeCursor: function (pixels, mask, hotx, hoty, w, h) {
+ if (this._cursor_uri === false) {
+ Util.Warn("changeCursor called but no cursor data URI support");
+ return;
+ }
+
+ if (this._true_color) {
+ Display.changeCursor(this._target, pixels, mask, hotx, hoty, w, h);
+ } else {
+ Display.changeCursor(this._target, pixels, mask, hotx, hoty, w, h, this._colourMap);
+ }
+ },
+
+ defaultCursor: function () {
+ this._target.style.cursor = "default";
+ },
+
+ disableLocalCursor: function () {
+ this._target.style.cursor = "none";
+ },
+
+ clippingDisplay: function () {
+ var vp = this._viewportLoc;
+
+ var fbClip = this._fb_width > vp.w || this._fb_height > vp.h;
+ var limitedVp = this._maxWidth !== 0 && this._maxHeight !== 0;
+ var clipping = false;
+
+ if (limitedVp) {
+ clipping = vp.w > this._maxWidth || vp.h > this._maxHeight;
+ }
+
+ return fbClip || (limitedVp && clipping);
+ },
+
+ // Overridden getters/setters
+ get_context: function () {
+ return this._drawCtx;
+ },
+
+ set_scale: function (scale) {
+ this._rescale(scale);
+ },
+
+ set_width: function (w) {
+ this._fb_width = w;
+ },
+ get_width: function () {
+ return this._fb_width;
+ },
+
+ set_height: function (h) {
+ this._fb_height = h;
+ },
+ get_height: function () {
+ return this._fb_height;
+ },
+
+ autoscale: function (containerWidth, containerHeight, downscaleOnly) {
+ var targetAspectRatio = containerWidth / containerHeight;
+ var fbAspectRatio = this._fb_width / this._fb_height;
+
+ var scaleRatio;
+ if (fbAspectRatio >= targetAspectRatio) {
+ scaleRatio = containerWidth / this._fb_width;
+ } else {
+ scaleRatio = containerHeight / this._fb_height;
+ }
+
+ var targetW, targetH;
+ if (scaleRatio > 1.0 && downscaleOnly) {
+ targetW = this._fb_width;
+ targetH = this._fb_height;
+ scaleRatio = 1.0;
+ } else if (fbAspectRatio >= targetAspectRatio) {
+ targetW = containerWidth;
+ targetH = Math.round(containerWidth / fbAspectRatio);
+ } else {
+ targetW = Math.round(containerHeight * fbAspectRatio);
+ targetH = containerHeight;
+ }
+
+ // NB(directxman12): If you set the width directly, or set the
+ // style width to a number, the canvas is cleared.
+ // However, if you set the style width to a string
+ // ('NNNpx'), the canvas is scaled without clearing.
+ this._target.style.width = targetW + 'px';
+ this._target.style.height = targetH + 'px';
+
+ this._scale = scaleRatio;
+
+ return scaleRatio; // so that the mouse, etc scale can be set
+ },
+
+ // Private Methods
+ _rescale: function (factor) {
+ this._scale = factor;
+
+ var w;
+ var h;
+
+ if (this._viewport &&
+ this._maxWidth !== 0 && this._maxHeight !== 0) {
+ w = Math.min(this._fb_width, this._maxWidth);
+ h = Math.min(this._fb_height, this._maxHeight);
+ } else {
+ w = this._fb_width;
+ h = this._fb_height;
+ }
+
+ this._target.style.width = Math.round(factor * w) + 'px';
+ this._target.style.height = Math.round(factor * h) + 'px';
+ },
+
+ _setFillColor: function (color) {
+ var bgr;
+ if (this._true_color) {
+ bgr = color;
+ } else {
+ bgr = this._colourMap[color];
+ }
+
+ var newStyle = 'rgb(' + bgr[2] + ',' + bgr[1] + ',' + bgr[0] + ')';
+ if (newStyle !== this._prevDrawStyle) {
+ this._drawCtx.fillStyle = newStyle;
+ this._prevDrawStyle = newStyle;
+ }
+ },
+
+ _rgbImageData: function (x, y, vx, vy, width, height, arr, offset) {
+ var img = this._drawCtx.createImageData(width, height);
+ var data = img.data;
+ for (var i = 0, j = offset; i < width * height * 4; i += 4, j += 3) {
+ data[i] = arr[j];
+ data[i + 1] = arr[j + 1];
+ data[i + 2] = arr[j + 2];
+ data[i + 3] = 255; // Alpha
+ }
+ this._drawCtx.putImageData(img, x - vx, y - vy);
+ },
+
+ _bgrxImageData: function (x, y, vx, vy, width, height, arr, offset) {
+ var img = this._drawCtx.createImageData(width, height);
+ var data = img.data;
+ for (var i = 0, j = offset; i < width * height * 4; i += 4, j += 4) {
+ data[i] = arr[j + 2];
+ data[i + 1] = arr[j + 1];
+ data[i + 2] = arr[j];
+ data[i + 3] = 255; // Alpha
+ }
+ this._drawCtx.putImageData(img, x - vx, y - vy);
+ },
+
+ _rgbxImageData: function (x, y, vx, vy, width, height, arr, offset) {
+ // NB(directxman12): arr must be an Type Array view
+ var img;
+ if (SUPPORTS_IMAGEDATA_CONSTRUCTOR) {
+ img = new ImageData(new Uint8ClampedArray(arr.buffer, arr.byteOffset, width * height * 4), width, height);
+ } else {
+ img = this._drawCtx.createImageData(width, height);
+ img.data.set(new Uint8ClampedArray(arr.buffer, arr.byteOffset, width * height * 4));
+ }
+ this._drawCtx.putImageData(img, x - vx, y - vy);
+ },
+
+ _cmapImageData: function (x, y, vx, vy, width, height, arr, offset) {
+ var img = this._drawCtx.createImageData(width, height);
+ var data = img.data;
+ var cmap = this._colourMap;
+ for (var i = 0, j = offset; i < width * height * 4; i += 4, j++) {
+ var bgr = cmap[arr[j]];
+ data[i] = bgr[2];
+ data[i + 1] = bgr[1];
+ data[i + 2] = bgr[0];
+ data[i + 3] = 255; // Alpha
+ }
+ this._drawCtx.putImageData(img, x - vx, y - vy);
+ },
+
+ _scan_renderQ: function () {
+ var ready = true;
+ while (ready && this._renderQ.length > 0) {
+ var a = this._renderQ[0];
+ switch (a.type) {
+ case 'copy':
+ this.copyImage(a.old_x, a.old_y, a.x, a.y, a.width, a.height, true);
+ break;
+ case 'fill':
+ this.fillRect(a.x, a.y, a.width, a.height, a.color, true);
+ break;
+ case 'blit':
+ this.blitImage(a.x, a.y, a.width, a.height, a.data, 0, true);
+ break;
+ case 'blitRgb':
+ this.blitRgbImage(a.x, a.y, a.width, a.height, a.data, 0, true);
+ break;
+ case 'blitRgbx':
+ this.blitRgbxImage(a.x, a.y, a.width, a.height, a.data, 0, true);
+ break;
+ case 'img':
+ if (a.img.complete) {
+ this.drawImage(a.img, a.x, a.y);
+ } else {
+ // We need to wait for this image to 'load'
+ // to keep things in-order
+ ready = false;
+ }
+ break;
+ }
+
+ if (ready) {
+ this._renderQ.shift();
+ }
+ }
+
+ if (this._renderQ.length > 0) {
+ requestAnimFrame(this._scan_renderQ.bind(this));
+ }
+ },
+ };
+
+ Util.make_properties(Display, [
+ ['target', 'wo', 'dom'], // Canvas element for rendering
+ ['context', 'ro', 'raw'], // Canvas 2D context for rendering (read-only)
+ ['logo', 'rw', 'raw'], // Logo to display when cleared: {"width": w, "height": h, "data": data}
+ ['true_color', 'rw', 'bool'], // Use true-color pixel data
+ ['colourMap', 'rw', 'arr'], // Colour map array (when not true-color)
+ ['scale', 'rw', 'float'], // Display area scale factor 0.0 - 1.0
+ ['viewport', 'rw', 'bool'], // Use viewport clipping
+ ['width', 'rw', 'int'], // Display area width
+ ['height', 'rw', 'int'], // Display area height
+ ['maxWidth', 'rw', 'int'], // Viewport max width (0 if disabled)
+ ['maxHeight', 'rw', 'int'], // Viewport max height (0 if disabled)
+
+ ['render_mode', 'ro', 'str'], // Canvas rendering mode (read-only)
+
+ ['prefer_js', 'rw', 'str'], // Prefer Javascript over canvas methods
+ ['cursor_uri', 'rw', 'raw'] // Can we render cursor using data URI
+ ]);
+
+ // Class Methods
+ Display.changeCursor = function (target, pixels, mask, hotx, hoty, w0, h0, cmap) {
+ var w = w0;
+ var h = h0;
+ if (h < w) {
+ h = w; // increase h to make it square
+ } else {
+ w = h; // increase w to make it square
+ }
+
+ var cur = [];
+
+ // Push multi-byte little-endian values
+ cur.push16le = function (num) {
+ this.push(num & 0xFF, (num >> 8) & 0xFF);
+ };
+ cur.push32le = function (num) {
+ this.push(num & 0xFF,
+ (num >> 8) & 0xFF,
+ (num >> 16) & 0xFF,
+ (num >> 24) & 0xFF);
+ };
+
+ var IHDRsz = 40;
+ var RGBsz = w * h * 4;
+ var XORsz = Math.ceil((w * h) / 8.0);
+ var ANDsz = Math.ceil((w * h) / 8.0);
+
+ cur.push16le(0); // 0: Reserved
+ cur.push16le(2); // 2: .CUR type
+ cur.push16le(1); // 4: Number of images, 1 for non-animated ico
+
+ // Cursor #1 header (ICONDIRENTRY)
+ cur.push(w); // 6: width
+ cur.push(h); // 7: height
+ cur.push(0); // 8: colors, 0 -> true-color
+ cur.push(0); // 9: reserved
+ cur.push16le(hotx); // 10: hotspot x coordinate
+ cur.push16le(hoty); // 12: hotspot y coordinate
+ cur.push32le(IHDRsz + RGBsz + XORsz + ANDsz);
+ // 14: cursor data byte size
+ cur.push32le(22); // 18: offset of cursor data in the file
+
+ // Cursor #1 InfoHeader (ICONIMAGE/BITMAPINFO)
+ cur.push32le(IHDRsz); // 22: InfoHeader size
+ cur.push32le(w); // 26: Cursor width
+ cur.push32le(h * 2); // 30: XOR+AND height
+ cur.push16le(1); // 34: number of planes
+ cur.push16le(32); // 36: bits per pixel
+ cur.push32le(0); // 38: Type of compression
+
+ cur.push32le(XORsz + ANDsz);
+ // 42: Size of Image
+ cur.push32le(0); // 46: reserved
+ cur.push32le(0); // 50: reserved
+ cur.push32le(0); // 54: reserved
+ cur.push32le(0); // 58: reserved
+
+ // 62: color data (RGBQUAD icColors[])
+ var y, x;
+ for (y = h - 1; y >= 0; y--) {
+ for (x = 0; x < w; x++) {
+ if (x >= w0 || y >= h0) {
+ cur.push(0); // blue
+ cur.push(0); // green
+ cur.push(0); // red
+ cur.push(0); // alpha
+ } else {
+ var idx = y * Math.ceil(w0 / 8) + Math.floor(x / 8);
+ var alpha = (mask[idx] << (x % 8)) & 0x80 ? 255 : 0;
+ if (cmap) {
+ idx = (w0 * y) + x;
+ var rgb = cmap[pixels[idx]];
+ cur.push(rgb[2]); // blue
+ cur.push(rgb[1]); // green
+ cur.push(rgb[0]); // red
+ cur.push(alpha); // alpha
+ } else {
+ idx = ((w0 * y) + x) * 4;
+ cur.push(pixels[idx + 2]); // blue
+ cur.push(pixels[idx + 1]); // green
+ cur.push(pixels[idx]); // red
+ cur.push(alpha); // alpha
+ }
+ }
+ }
+ }
+
+ // XOR/bitmask data (BYTE icXOR[])
+ // (ignored, just needs to be the right size)
+ for (y = 0; y < h; y++) {
+ for (x = 0; x < Math.ceil(w / 8); x++) {
+ cur.push(0);
+ }
+ }
+
+ // AND/bitmask data (BYTE icAND[])
+ // (ignored, just needs to be the right size)
+ for (y = 0; y < h; y++) {
+ for (x = 0; x < Math.ceil(w / 8); x++) {
+ cur.push(0);
+ }
+ }
+
+ var url = 'data:image/x-icon;base64,' + Base64.encode(cur);
+ target.style.cursor = 'url(' + url + ')' + hotx + ' ' + hoty + ', default';
+ };
+})();
diff --git a/public/novnc/include/inflator.js b/public/novnc/include/inflator.js
new file mode 100644
index 00000000..48ede208
--- /dev/null
+++ b/public/novnc/include/inflator.js
@@ -0,0 +1,2418 @@
+(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.inflator = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o>> 16) & 0xffff) |0,
+ n = 0;
+
+ while (len !== 0) {
+ // Set limit ~ twice less than 5552, to keep
+ // s2 in 31-bits, because we force signed ints.
+ // in other case %= will fail.
+ n = len > 2000 ? 2000 : len;
+ len -= n;
+
+ do {
+ s1 = (s1 + buf[pos++]) |0;
+ s2 = (s2 + s1) |0;
+ } while (--n);
+
+ s1 %= 65521;
+ s2 %= 65521;
+ }
+
+ return (s1 | (s2 << 16)) |0;
+}
+
+
+module.exports = adler32;
+
+},{}],3:[function(require,module,exports){
+'use strict';
+
+// Note: we can't get significant speed boost here.
+// So write code to minimize size - no pregenerated tables
+// and array tools dependencies.
+
+
+// Use ordinary array, since untyped makes no boost here
+function makeTable() {
+ var c, table = [];
+
+ for (var n =0; n < 256; n++) {
+ c = n;
+ for (var k =0; k < 8; k++) {
+ c = ((c&1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1));
+ }
+ table[n] = c;
+ }
+
+ return table;
+}
+
+// Create table on load. Just 255 signed longs. Not a problem.
+var crcTable = makeTable();
+
+
+function crc32(crc, buf, len, pos) {
+ var t = crcTable,
+ end = pos + len;
+
+ crc = crc ^ (-1);
+
+ for (var i = pos; i < end; i++) {
+ crc = (crc >>> 8) ^ t[(crc ^ buf[i]) & 0xFF];
+ }
+
+ return (crc ^ (-1)); // >>> 0;
+}
+
+
+module.exports = crc32;
+
+},{}],4:[function(require,module,exports){
+'use strict';
+
+// See state defs from inflate.js
+var BAD = 30; /* got a data error -- remain here until reset */
+var TYPE = 12; /* i: waiting for type bits, including last-flag bit */
+
+/*
+ Decode literal, length, and distance codes and write out the resulting
+ literal and match bytes until either not enough input or output is
+ available, an end-of-block is encountered, or a data error is encountered.
+ When large enough input and output buffers are supplied to inflate(), for
+ example, a 16K input buffer and a 64K output buffer, more than 95% of the
+ inflate execution time is spent in this routine.
+
+ Entry assumptions:
+
+ state.mode === LEN
+ strm.avail_in >= 6
+ strm.avail_out >= 258
+ start >= strm.avail_out
+ state.bits < 8
+
+ On return, state.mode is one of:
+
+ LEN -- ran out of enough output space or enough available input
+ TYPE -- reached end of block code, inflate() to interpret next block
+ BAD -- error in block data
+
+ Notes:
+
+ - The maximum input bits used by a length/distance pair is 15 bits for the
+ length code, 5 bits for the length extra, 15 bits for the distance code,
+ and 13 bits for the distance extra. This totals 48 bits, or six bytes.
+ Therefore if strm.avail_in >= 6, then there is enough input to avoid
+ checking for available input while decoding.
+
+ - The maximum bytes that a single length/distance pair can output is 258
+ bytes, which is the maximum length that can be coded. inflate_fast()
+ requires strm.avail_out >= 258 for each loop to avoid checking for
+ output space.
+ */
+module.exports = function inflate_fast(strm, start) {
+ var state;
+ var _in; /* local strm.input */
+ var last; /* have enough input while in < last */
+ var _out; /* local strm.output */
+ var beg; /* inflate()'s initial strm.output */
+ var end; /* while out < end, enough space available */
+//#ifdef INFLATE_STRICT
+ var dmax; /* maximum distance from zlib header */
+//#endif
+ var wsize; /* window size or zero if not using window */
+ var whave; /* valid bytes in the window */
+ var wnext; /* window write index */
+ // Use `s_window` instead `window`, avoid conflict with instrumentation tools
+ var s_window; /* allocated sliding window, if wsize != 0 */
+ var hold; /* local strm.hold */
+ var bits; /* local strm.bits */
+ var lcode; /* local strm.lencode */
+ var dcode; /* local strm.distcode */
+ var lmask; /* mask for first level of length codes */
+ var dmask; /* mask for first level of distance codes */
+ var here; /* retrieved table entry */
+ var op; /* code bits, operation, extra bits, or */
+ /* window position, window bytes to copy */
+ var len; /* match length, unused bytes */
+ var dist; /* match distance */
+ var from; /* where to copy match from */
+ var from_source;
+
+
+ var input, output; // JS specific, because we have no pointers
+
+ /* copy state to local variables */
+ state = strm.state;
+ //here = state.here;
+ _in = strm.next_in;
+ input = strm.input;
+ last = _in + (strm.avail_in - 5);
+ _out = strm.next_out;
+ output = strm.output;
+ beg = _out - (start - strm.avail_out);
+ end = _out + (strm.avail_out - 257);
+//#ifdef INFLATE_STRICT
+ dmax = state.dmax;
+//#endif
+ wsize = state.wsize;
+ whave = state.whave;
+ wnext = state.wnext;
+ s_window = state.window;
+ hold = state.hold;
+ bits = state.bits;
+ lcode = state.lencode;
+ dcode = state.distcode;
+ lmask = (1 << state.lenbits) - 1;
+ dmask = (1 << state.distbits) - 1;
+
+
+ /* decode literals and length/distances until end-of-block or not enough
+ input data or output space */
+
+ top:
+ do {
+ if (bits < 15) {
+ hold += input[_in++] << bits;
+ bits += 8;
+ hold += input[_in++] << bits;
+ bits += 8;
+ }
+
+ here = lcode[hold & lmask];
+
+ dolen:
+ for (;;) { // Goto emulation
+ op = here >>> 24/*here.bits*/;
+ hold >>>= op;
+ bits -= op;
+ op = (here >>> 16) & 0xff/*here.op*/;
+ if (op === 0) { /* literal */
+ //Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ?
+ // "inflate: literal '%c'\n" :
+ // "inflate: literal 0x%02x\n", here.val));
+ output[_out++] = here & 0xffff/*here.val*/;
+ }
+ else if (op & 16) { /* length base */
+ len = here & 0xffff/*here.val*/;
+ op &= 15; /* number of extra bits */
+ if (op) {
+ if (bits < op) {
+ hold += input[_in++] << bits;
+ bits += 8;
+ }
+ len += hold & ((1 << op) - 1);
+ hold >>>= op;
+ bits -= op;
+ }
+ //Tracevv((stderr, "inflate: length %u\n", len));
+ if (bits < 15) {
+ hold += input[_in++] << bits;
+ bits += 8;
+ hold += input[_in++] << bits;
+ bits += 8;
+ }
+ here = dcode[hold & dmask];
+
+ dodist:
+ for (;;) { // goto emulation
+ op = here >>> 24/*here.bits*/;
+ hold >>>= op;
+ bits -= op;
+ op = (here >>> 16) & 0xff/*here.op*/;
+
+ if (op & 16) { /* distance base */
+ dist = here & 0xffff/*here.val*/;
+ op &= 15; /* number of extra bits */
+ if (bits < op) {
+ hold += input[_in++] << bits;
+ bits += 8;
+ if (bits < op) {
+ hold += input[_in++] << bits;
+ bits += 8;
+ }
+ }
+ dist += hold & ((1 << op) - 1);
+//#ifdef INFLATE_STRICT
+ if (dist > dmax) {
+ strm.msg = 'invalid distance too far back';
+ state.mode = BAD;
+ break top;
+ }
+//#endif
+ hold >>>= op;
+ bits -= op;
+ //Tracevv((stderr, "inflate: distance %u\n", dist));
+ op = _out - beg; /* max distance in output */
+ if (dist > op) { /* see if copy from window */
+ op = dist - op; /* distance back in window */
+ if (op > whave) {
+ if (state.sane) {
+ strm.msg = 'invalid distance too far back';
+ state.mode = BAD;
+ break top;
+ }
+
+// (!) This block is disabled in zlib defailts,
+// don't enable it for binary compatibility
+//#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR
+// if (len <= op - whave) {
+// do {
+// output[_out++] = 0;
+// } while (--len);
+// continue top;
+// }
+// len -= op - whave;
+// do {
+// output[_out++] = 0;
+// } while (--op > whave);
+// if (op === 0) {
+// from = _out - dist;
+// do {
+// output[_out++] = output[from++];
+// } while (--len);
+// continue top;
+// }
+//#endif
+ }
+ from = 0; // window index
+ from_source = s_window;
+ if (wnext === 0) { /* very common case */
+ from += wsize - op;
+ if (op < len) { /* some from window */
+ len -= op;
+ do {
+ output[_out++] = s_window[from++];
+ } while (--op);
+ from = _out - dist; /* rest from output */
+ from_source = output;
+ }
+ }
+ else if (wnext < op) { /* wrap around window */
+ from += wsize + wnext - op;
+ op -= wnext;
+ if (op < len) { /* some from end of window */
+ len -= op;
+ do {
+ output[_out++] = s_window[from++];
+ } while (--op);
+ from = 0;
+ if (wnext < len) { /* some from start of window */
+ op = wnext;
+ len -= op;
+ do {
+ output[_out++] = s_window[from++];
+ } while (--op);
+ from = _out - dist; /* rest from output */
+ from_source = output;
+ }
+ }
+ }
+ else { /* contiguous in window */
+ from += wnext - op;
+ if (op < len) { /* some from window */
+ len -= op;
+ do {
+ output[_out++] = s_window[from++];
+ } while (--op);
+ from = _out - dist; /* rest from output */
+ from_source = output;
+ }
+ }
+ while (len > 2) {
+ output[_out++] = from_source[from++];
+ output[_out++] = from_source[from++];
+ output[_out++] = from_source[from++];
+ len -= 3;
+ }
+ if (len) {
+ output[_out++] = from_source[from++];
+ if (len > 1) {
+ output[_out++] = from_source[from++];
+ }
+ }
+ }
+ else {
+ from = _out - dist; /* copy direct from output */
+ do { /* minimum length is three */
+ output[_out++] = output[from++];
+ output[_out++] = output[from++];
+ output[_out++] = output[from++];
+ len -= 3;
+ } while (len > 2);
+ if (len) {
+ output[_out++] = output[from++];
+ if (len > 1) {
+ output[_out++] = output[from++];
+ }
+ }
+ }
+ }
+ else if ((op & 64) === 0) { /* 2nd level distance code */
+ here = dcode[(here & 0xffff)/*here.val*/ + (hold & ((1 << op) - 1))];
+ continue dodist;
+ }
+ else {
+ strm.msg = 'invalid distance code';
+ state.mode = BAD;
+ break top;
+ }
+
+ break; // need to emulate goto via "continue"
+ }
+ }
+ else if ((op & 64) === 0) { /* 2nd level length code */
+ here = lcode[(here & 0xffff)/*here.val*/ + (hold & ((1 << op) - 1))];
+ continue dolen;
+ }
+ else if (op & 32) { /* end-of-block */
+ //Tracevv((stderr, "inflate: end of block\n"));
+ state.mode = TYPE;
+ break top;
+ }
+ else {
+ strm.msg = 'invalid literal/length code';
+ state.mode = BAD;
+ break top;
+ }
+
+ break; // need to emulate goto via "continue"
+ }
+ } while (_in < last && _out < end);
+
+ /* return unused bytes (on entry, bits < 8, so in won't go too far back) */
+ len = bits >> 3;
+ _in -= len;
+ bits -= len << 3;
+ hold &= (1 << bits) - 1;
+
+ /* update state and return */
+ strm.next_in = _in;
+ strm.next_out = _out;
+ strm.avail_in = (_in < last ? 5 + (last - _in) : 5 - (_in - last));
+ strm.avail_out = (_out < end ? 257 + (end - _out) : 257 - (_out - end));
+ state.hold = hold;
+ state.bits = bits;
+ return;
+};
+
+},{}],5:[function(require,module,exports){
+'use strict';
+
+
+var utils = require('../utils/common');
+var adler32 = require('./adler32');
+var crc32 = require('./crc32');
+var inflate_fast = require('./inffast');
+var inflate_table = require('./inftrees');
+
+var CODES = 0;
+var LENS = 1;
+var DISTS = 2;
+
+/* Public constants ==========================================================*/
+/* ===========================================================================*/
+
+
+/* Allowed flush values; see deflate() and inflate() below for details */
+//var Z_NO_FLUSH = 0;
+//var Z_PARTIAL_FLUSH = 1;
+//var Z_SYNC_FLUSH = 2;
+//var Z_FULL_FLUSH = 3;
+var Z_FINISH = 4;
+var Z_BLOCK = 5;
+var Z_TREES = 6;
+
+
+/* Return codes for the compression/decompression functions. Negative values
+ * are errors, positive values are used for special but normal events.
+ */
+var Z_OK = 0;
+var Z_STREAM_END = 1;
+var Z_NEED_DICT = 2;
+//var Z_ERRNO = -1;
+var Z_STREAM_ERROR = -2;
+var Z_DATA_ERROR = -3;
+var Z_MEM_ERROR = -4;
+var Z_BUF_ERROR = -5;
+//var Z_VERSION_ERROR = -6;
+
+/* The deflate compression method */
+var Z_DEFLATED = 8;
+
+
+/* STATES ====================================================================*/
+/* ===========================================================================*/
+
+
+var HEAD = 1; /* i: waiting for magic header */
+var FLAGS = 2; /* i: waiting for method and flags (gzip) */
+var TIME = 3; /* i: waiting for modification time (gzip) */
+var OS = 4; /* i: waiting for extra flags and operating system (gzip) */
+var EXLEN = 5; /* i: waiting for extra length (gzip) */
+var EXTRA = 6; /* i: waiting for extra bytes (gzip) */
+var NAME = 7; /* i: waiting for end of file name (gzip) */
+var COMMENT = 8; /* i: waiting for end of comment (gzip) */
+var HCRC = 9; /* i: waiting for header crc (gzip) */
+var DICTID = 10; /* i: waiting for dictionary check value */
+var DICT = 11; /* waiting for inflateSetDictionary() call */
+var TYPE = 12; /* i: waiting for type bits, including last-flag bit */
+var TYPEDO = 13; /* i: same, but skip check to exit inflate on new block */
+var STORED = 14; /* i: waiting for stored size (length and complement) */
+var COPY_ = 15; /* i/o: same as COPY below, but only first time in */
+var COPY = 16; /* i/o: waiting for input or output to copy stored block */
+var TABLE = 17; /* i: waiting for dynamic block table lengths */
+var LENLENS = 18; /* i: waiting for code length code lengths */
+var CODELENS = 19; /* i: waiting for length/lit and distance code lengths */
+var LEN_ = 20; /* i: same as LEN below, but only first time in */
+var LEN = 21; /* i: waiting for length/lit/eob code */
+var LENEXT = 22; /* i: waiting for length extra bits */
+var DIST = 23; /* i: waiting for distance code */
+var DISTEXT = 24; /* i: waiting for distance extra bits */
+var MATCH = 25; /* o: waiting for output space to copy string */
+var LIT = 26; /* o: waiting for output space to write literal */
+var CHECK = 27; /* i: waiting for 32-bit check value */
+var LENGTH = 28; /* i: waiting for 32-bit length (gzip) */
+var DONE = 29; /* finished check, done -- remain here until reset */
+var BAD = 30; /* got a data error -- remain here until reset */
+var MEM = 31; /* got an inflate() memory error -- remain here until reset */
+var SYNC = 32; /* looking for synchronization bytes to restart inflate() */
+
+/* ===========================================================================*/
+
+
+
+var ENOUGH_LENS = 852;
+var ENOUGH_DISTS = 592;
+//var ENOUGH = (ENOUGH_LENS+ENOUGH_DISTS);
+
+var MAX_WBITS = 15;
+/* 32K LZ77 window */
+var DEF_WBITS = MAX_WBITS;
+
+
+function ZSWAP32(q) {
+ return (((q >>> 24) & 0xff) +
+ ((q >>> 8) & 0xff00) +
+ ((q & 0xff00) << 8) +
+ ((q & 0xff) << 24));
+}
+
+
+function InflateState() {
+ this.mode = 0; /* current inflate mode */
+ this.last = false; /* true if processing last block */
+ this.wrap = 0; /* bit 0 true for zlib, bit 1 true for gzip */
+ this.havedict = false; /* true if dictionary provided */
+ this.flags = 0; /* gzip header method and flags (0 if zlib) */
+ this.dmax = 0; /* zlib header max distance (INFLATE_STRICT) */
+ this.check = 0; /* protected copy of check value */
+ this.total = 0; /* protected copy of output count */
+ // TODO: may be {}
+ this.head = null; /* where to save gzip header information */
+
+ /* sliding window */
+ this.wbits = 0; /* log base 2 of requested window size */
+ this.wsize = 0; /* window size or zero if not using window */
+ this.whave = 0; /* valid bytes in the window */
+ this.wnext = 0; /* window write index */
+ this.window = null; /* allocated sliding window, if needed */
+
+ /* bit accumulator */
+ this.hold = 0; /* input bit accumulator */
+ this.bits = 0; /* number of bits in "in" */
+
+ /* for string and stored block copying */
+ this.length = 0; /* literal or length of data to copy */
+ this.offset = 0; /* distance back to copy string from */
+
+ /* for table and code decoding */
+ this.extra = 0; /* extra bits needed */
+
+ /* fixed and dynamic code tables */
+ this.lencode = null; /* starting table for length/literal codes */
+ this.distcode = null; /* starting table for distance codes */
+ this.lenbits = 0; /* index bits for lencode */
+ this.distbits = 0; /* index bits for distcode */
+
+ /* dynamic table building */
+ this.ncode = 0; /* number of code length code lengths */
+ this.nlen = 0; /* number of length code lengths */
+ this.ndist = 0; /* number of distance code lengths */
+ this.have = 0; /* number of code lengths in lens[] */
+ this.next = null; /* next available space in codes[] */
+
+ this.lens = new utils.Buf16(320); /* temporary storage for code lengths */
+ this.work = new utils.Buf16(288); /* work area for code table building */
+
+ /*
+ because we don't have pointers in js, we use lencode and distcode directly
+ as buffers so we don't need codes
+ */
+ //this.codes = new utils.Buf32(ENOUGH); /* space for code tables */
+ this.lendyn = null; /* dynamic table for length/literal codes (JS specific) */
+ this.distdyn = null; /* dynamic table for distance codes (JS specific) */
+ this.sane = 0; /* if false, allow invalid distance too far */
+ this.back = 0; /* bits back of last unprocessed length/lit */
+ this.was = 0; /* initial length of match */
+}
+
+function inflateResetKeep(strm) {
+ var state;
+
+ if (!strm || !strm.state) { return Z_STREAM_ERROR; }
+ state = strm.state;
+ strm.total_in = strm.total_out = state.total = 0;
+ strm.msg = ''; /*Z_NULL*/
+ if (state.wrap) { /* to support ill-conceived Java test suite */
+ strm.adler = state.wrap & 1;
+ }
+ state.mode = HEAD;
+ state.last = 0;
+ state.havedict = 0;
+ state.dmax = 32768;
+ state.head = null/*Z_NULL*/;
+ state.hold = 0;
+ state.bits = 0;
+ //state.lencode = state.distcode = state.next = state.codes;
+ state.lencode = state.lendyn = new utils.Buf32(ENOUGH_LENS);
+ state.distcode = state.distdyn = new utils.Buf32(ENOUGH_DISTS);
+
+ state.sane = 1;
+ state.back = -1;
+ //Tracev((stderr, "inflate: reset\n"));
+ return Z_OK;
+}
+
+function inflateReset(strm) {
+ var state;
+
+ if (!strm || !strm.state) { return Z_STREAM_ERROR; }
+ state = strm.state;
+ state.wsize = 0;
+ state.whave = 0;
+ state.wnext = 0;
+ return inflateResetKeep(strm);
+
+}
+
+function inflateReset2(strm, windowBits) {
+ var wrap;
+ var state;
+
+ /* get the state */
+ if (!strm || !strm.state) { return Z_STREAM_ERROR; }
+ state = strm.state;
+
+ /* extract wrap request from windowBits parameter */
+ if (windowBits < 0) {
+ wrap = 0;
+ windowBits = -windowBits;
+ }
+ else {
+ wrap = (windowBits >> 4) + 1;
+ if (windowBits < 48) {
+ windowBits &= 15;
+ }
+ }
+
+ /* set number of window bits, free window if different */
+ if (windowBits && (windowBits < 8 || windowBits > 15)) {
+ return Z_STREAM_ERROR;
+ }
+ if (state.window !== null && state.wbits !== windowBits) {
+ state.window = null;
+ }
+
+ /* update state and reset the rest of it */
+ state.wrap = wrap;
+ state.wbits = windowBits;
+ return inflateReset(strm);
+}
+
+function inflateInit2(strm, windowBits) {
+ var ret;
+ var state;
+
+ if (!strm) { return Z_STREAM_ERROR; }
+ //strm.msg = Z_NULL; /* in case we return an error */
+
+ state = new InflateState();
+
+ //if (state === Z_NULL) return Z_MEM_ERROR;
+ //Tracev((stderr, "inflate: allocated\n"));
+ strm.state = state;
+ state.window = null/*Z_NULL*/;
+ ret = inflateReset2(strm, windowBits);
+ if (ret !== Z_OK) {
+ strm.state = null/*Z_NULL*/;
+ }
+ return ret;
+}
+
+function inflateInit(strm) {
+ return inflateInit2(strm, DEF_WBITS);
+}
+
+
+/*
+ Return state with length and distance decoding tables and index sizes set to
+ fixed code decoding. Normally this returns fixed tables from inffixed.h.
+ If BUILDFIXED is defined, then instead this routine builds the tables the
+ first time it's called, and returns those tables the first time and
+ thereafter. This reduces the size of the code by about 2K bytes, in
+ exchange for a little execution time. However, BUILDFIXED should not be
+ used for threaded applications, since the rewriting of the tables and virgin
+ may not be thread-safe.
+ */
+var virgin = true;
+
+var lenfix, distfix; // We have no pointers in JS, so keep tables separate
+
+function fixedtables(state) {
+ /* build fixed huffman tables if first call (may not be thread safe) */
+ if (virgin) {
+ var sym;
+
+ lenfix = new utils.Buf32(512);
+ distfix = new utils.Buf32(32);
+
+ /* literal/length table */
+ sym = 0;
+ while (sym < 144) { state.lens[sym++] = 8; }
+ while (sym < 256) { state.lens[sym++] = 9; }
+ while (sym < 280) { state.lens[sym++] = 7; }
+ while (sym < 288) { state.lens[sym++] = 8; }
+
+ inflate_table(LENS, state.lens, 0, 288, lenfix, 0, state.work, {bits: 9});
+
+ /* distance table */
+ sym = 0;
+ while (sym < 32) { state.lens[sym++] = 5; }
+
+ inflate_table(DISTS, state.lens, 0, 32, distfix, 0, state.work, {bits: 5});
+
+ /* do this just once */
+ virgin = false;
+ }
+
+ state.lencode = lenfix;
+ state.lenbits = 9;
+ state.distcode = distfix;
+ state.distbits = 5;
+}
+
+
+/*
+ Update the window with the last wsize (normally 32K) bytes written before
+ returning. If window does not exist yet, create it. This is only called
+ when a window is already in use, or when output has been written during this
+ inflate call, but the end of the deflate stream has not been reached yet.
+ It is also called to create a window for dictionary data when a dictionary
+ is loaded.
+
+ Providing output buffers larger than 32K to inflate() should provide a speed
+ advantage, since only the last 32K of output is copied to the sliding window
+ upon return from inflate(), and since all distances after the first 32K of
+ output will fall in the output data, making match copies simpler and faster.
+ The advantage may be dependent on the size of the processor's data caches.
+ */
+function updatewindow(strm, src, end, copy) {
+ var dist;
+ var state = strm.state;
+
+ /* if it hasn't been done already, allocate space for the window */
+ if (state.window === null) {
+ state.wsize = 1 << state.wbits;
+ state.wnext = 0;
+ state.whave = 0;
+
+ state.window = new utils.Buf8(state.wsize);
+ }
+
+ /* copy state->wsize or less output bytes into the circular window */
+ if (copy >= state.wsize) {
+ utils.arraySet(state.window,src, end - state.wsize, state.wsize, 0);
+ state.wnext = 0;
+ state.whave = state.wsize;
+ }
+ else {
+ dist = state.wsize - state.wnext;
+ if (dist > copy) {
+ dist = copy;
+ }
+ //zmemcpy(state->window + state->wnext, end - copy, dist);
+ utils.arraySet(state.window,src, end - copy, dist, state.wnext);
+ copy -= dist;
+ if (copy) {
+ //zmemcpy(state->window, end - copy, copy);
+ utils.arraySet(state.window,src, end - copy, copy, 0);
+ state.wnext = copy;
+ state.whave = state.wsize;
+ }
+ else {
+ state.wnext += dist;
+ if (state.wnext === state.wsize) { state.wnext = 0; }
+ if (state.whave < state.wsize) { state.whave += dist; }
+ }
+ }
+ return 0;
+}
+
+function inflate(strm, flush) {
+ var state;
+ var input, output; // input/output buffers
+ var next; /* next input INDEX */
+ var put; /* next output INDEX */
+ var have, left; /* available input and output */
+ var hold; /* bit buffer */
+ var bits; /* bits in bit buffer */
+ var _in, _out; /* save starting available input and output */
+ var copy; /* number of stored or match bytes to copy */
+ var from; /* where to copy match bytes from */
+ var from_source;
+ var here = 0; /* current decoding table entry */
+ var here_bits, here_op, here_val; // paked "here" denormalized (JS specific)
+ //var last; /* parent table entry */
+ var last_bits, last_op, last_val; // paked "last" denormalized (JS specific)
+ var len; /* length to copy for repeats, bits to drop */
+ var ret; /* return code */
+ var hbuf = new utils.Buf8(4); /* buffer for gzip header crc calculation */
+ var opts;
+
+ var n; // temporary var for NEED_BITS
+
+ var order = /* permutation of code lengths */
+ [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15];
+
+
+ if (!strm || !strm.state || !strm.output ||
+ (!strm.input && strm.avail_in !== 0)) {
+ return Z_STREAM_ERROR;
+ }
+
+ state = strm.state;
+ if (state.mode === TYPE) { state.mode = TYPEDO; } /* skip check */
+
+
+ //--- LOAD() ---
+ put = strm.next_out;
+ output = strm.output;
+ left = strm.avail_out;
+ next = strm.next_in;
+ input = strm.input;
+ have = strm.avail_in;
+ hold = state.hold;
+ bits = state.bits;
+ //---
+
+ _in = have;
+ _out = left;
+ ret = Z_OK;
+
+ inf_leave: // goto emulation
+ for (;;) {
+ switch (state.mode) {
+ case HEAD:
+ if (state.wrap === 0) {
+ state.mode = TYPEDO;
+ break;
+ }
+ //=== NEEDBITS(16);
+ while (bits < 16) {
+ if (have === 0) { break inf_leave; }
+ have--;
+ hold += input[next++] << bits;
+ bits += 8;
+ }
+ //===//
+ if ((state.wrap & 2) && hold === 0x8b1f) { /* gzip header */
+ state.check = 0/*crc32(0L, Z_NULL, 0)*/;
+ //=== CRC2(state.check, hold);
+ hbuf[0] = hold & 0xff;
+ hbuf[1] = (hold >>> 8) & 0xff;
+ state.check = crc32(state.check, hbuf, 2, 0);
+ //===//
+
+ //=== INITBITS();
+ hold = 0;
+ bits = 0;
+ //===//
+ state.mode = FLAGS;
+ break;
+ }
+ state.flags = 0; /* expect zlib header */
+ if (state.head) {
+ state.head.done = false;
+ }
+ if (!(state.wrap & 1) || /* check if zlib header allowed */
+ (((hold & 0xff)/*BITS(8)*/ << 8) + (hold >> 8)) % 31) {
+ strm.msg = 'incorrect header check';
+ state.mode = BAD;
+ break;
+ }
+ if ((hold & 0x0f)/*BITS(4)*/ !== Z_DEFLATED) {
+ strm.msg = 'unknown compression method';
+ state.mode = BAD;
+ break;
+ }
+ //--- DROPBITS(4) ---//
+ hold >>>= 4;
+ bits -= 4;
+ //---//
+ len = (hold & 0x0f)/*BITS(4)*/ + 8;
+ if (state.wbits === 0) {
+ state.wbits = len;
+ }
+ else if (len > state.wbits) {
+ strm.msg = 'invalid window size';
+ state.mode = BAD;
+ break;
+ }
+ state.dmax = 1 << len;
+ //Tracev((stderr, "inflate: zlib header ok\n"));
+ strm.adler = state.check = 1/*adler32(0L, Z_NULL, 0)*/;
+ state.mode = hold & 0x200 ? DICTID : TYPE;
+ //=== INITBITS();
+ hold = 0;
+ bits = 0;
+ //===//
+ break;
+ case FLAGS:
+ //=== NEEDBITS(16); */
+ while (bits < 16) {
+ if (have === 0) { break inf_leave; }
+ have--;
+ hold += input[next++] << bits;
+ bits += 8;
+ }
+ //===//
+ state.flags = hold;
+ if ((state.flags & 0xff) !== Z_DEFLATED) {
+ strm.msg = 'unknown compression method';
+ state.mode = BAD;
+ break;
+ }
+ if (state.flags & 0xe000) {
+ strm.msg = 'unknown header flags set';
+ state.mode = BAD;
+ break;
+ }
+ if (state.head) {
+ state.head.text = ((hold >> 8) & 1);
+ }
+ if (state.flags & 0x0200) {
+ //=== CRC2(state.check, hold);
+ hbuf[0] = hold & 0xff;
+ hbuf[1] = (hold >>> 8) & 0xff;
+ state.check = crc32(state.check, hbuf, 2, 0);
+ //===//
+ }
+ //=== INITBITS();
+ hold = 0;
+ bits = 0;
+ //===//
+ state.mode = TIME;
+ /* falls through */
+ case TIME:
+ //=== NEEDBITS(32); */
+ while (bits < 32) {
+ if (have === 0) { break inf_leave; }
+ have--;
+ hold += input[next++] << bits;
+ bits += 8;
+ }
+ //===//
+ if (state.head) {
+ state.head.time = hold;
+ }
+ if (state.flags & 0x0200) {
+ //=== CRC4(state.check, hold)
+ hbuf[0] = hold & 0xff;
+ hbuf[1] = (hold >>> 8) & 0xff;
+ hbuf[2] = (hold >>> 16) & 0xff;
+ hbuf[3] = (hold >>> 24) & 0xff;
+ state.check = crc32(state.check, hbuf, 4, 0);
+ //===
+ }
+ //=== INITBITS();
+ hold = 0;
+ bits = 0;
+ //===//
+ state.mode = OS;
+ /* falls through */
+ case OS:
+ //=== NEEDBITS(16); */
+ while (bits < 16) {
+ if (have === 0) { break inf_leave; }
+ have--;
+ hold += input[next++] << bits;
+ bits += 8;
+ }
+ //===//
+ if (state.head) {
+ state.head.xflags = (hold & 0xff);
+ state.head.os = (hold >> 8);
+ }
+ if (state.flags & 0x0200) {
+ //=== CRC2(state.check, hold);
+ hbuf[0] = hold & 0xff;
+ hbuf[1] = (hold >>> 8) & 0xff;
+ state.check = crc32(state.check, hbuf, 2, 0);
+ //===//
+ }
+ //=== INITBITS();
+ hold = 0;
+ bits = 0;
+ //===//
+ state.mode = EXLEN;
+ /* falls through */
+ case EXLEN:
+ if (state.flags & 0x0400) {
+ //=== NEEDBITS(16); */
+ while (bits < 16) {
+ if (have === 0) { break inf_leave; }
+ have--;
+ hold += input[next++] << bits;
+ bits += 8;
+ }
+ //===//
+ state.length = hold;
+ if (state.head) {
+ state.head.extra_len = hold;
+ }
+ if (state.flags & 0x0200) {
+ //=== CRC2(state.check, hold);
+ hbuf[0] = hold & 0xff;
+ hbuf[1] = (hold >>> 8) & 0xff;
+ state.check = crc32(state.check, hbuf, 2, 0);
+ //===//
+ }
+ //=== INITBITS();
+ hold = 0;
+ bits = 0;
+ //===//
+ }
+ else if (state.head) {
+ state.head.extra = null/*Z_NULL*/;
+ }
+ state.mode = EXTRA;
+ /* falls through */
+ case EXTRA:
+ if (state.flags & 0x0400) {
+ copy = state.length;
+ if (copy > have) { copy = have; }
+ if (copy) {
+ if (state.head) {
+ len = state.head.extra_len - state.length;
+ if (!state.head.extra) {
+ // Use untyped array for more conveniend processing later
+ state.head.extra = new Array(state.head.extra_len);
+ }
+ utils.arraySet(
+ state.head.extra,
+ input,
+ next,
+ // extra field is limited to 65536 bytes
+ // - no need for additional size check
+ copy,
+ /*len + copy > state.head.extra_max - len ? state.head.extra_max : copy,*/
+ len
+ );
+ //zmemcpy(state.head.extra + len, next,
+ // len + copy > state.head.extra_max ?
+ // state.head.extra_max - len : copy);
+ }
+ if (state.flags & 0x0200) {
+ state.check = crc32(state.check, input, copy, next);
+ }
+ have -= copy;
+ next += copy;
+ state.length -= copy;
+ }
+ if (state.length) { break inf_leave; }
+ }
+ state.length = 0;
+ state.mode = NAME;
+ /* falls through */
+ case NAME:
+ if (state.flags & 0x0800) {
+ if (have === 0) { break inf_leave; }
+ copy = 0;
+ do {
+ // TODO: 2 or 1 bytes?
+ len = input[next + copy++];
+ /* use constant limit because in js we should not preallocate memory */
+ if (state.head && len &&
+ (state.length < 65536 /*state.head.name_max*/)) {
+ state.head.name += String.fromCharCode(len);
+ }
+ } while (len && copy < have);
+
+ if (state.flags & 0x0200) {
+ state.check = crc32(state.check, input, copy, next);
+ }
+ have -= copy;
+ next += copy;
+ if (len) { break inf_leave; }
+ }
+ else if (state.head) {
+ state.head.name = null;
+ }
+ state.length = 0;
+ state.mode = COMMENT;
+ /* falls through */
+ case COMMENT:
+ if (state.flags & 0x1000) {
+ if (have === 0) { break inf_leave; }
+ copy = 0;
+ do {
+ len = input[next + copy++];
+ /* use constant limit because in js we should not preallocate memory */
+ if (state.head && len &&
+ (state.length < 65536 /*state.head.comm_max*/)) {
+ state.head.comment += String.fromCharCode(len);
+ }
+ } while (len && copy < have);
+ if (state.flags & 0x0200) {
+ state.check = crc32(state.check, input, copy, next);
+ }
+ have -= copy;
+ next += copy;
+ if (len) { break inf_leave; }
+ }
+ else if (state.head) {
+ state.head.comment = null;
+ }
+ state.mode = HCRC;
+ /* falls through */
+ case HCRC:
+ if (state.flags & 0x0200) {
+ //=== NEEDBITS(16); */
+ while (bits < 16) {
+ if (have === 0) { break inf_leave; }
+ have--;
+ hold += input[next++] << bits;
+ bits += 8;
+ }
+ //===//
+ if (hold !== (state.check & 0xffff)) {
+ strm.msg = 'header crc mismatch';
+ state.mode = BAD;
+ break;
+ }
+ //=== INITBITS();
+ hold = 0;
+ bits = 0;
+ //===//
+ }
+ if (state.head) {
+ state.head.hcrc = ((state.flags >> 9) & 1);
+ state.head.done = true;
+ }
+ strm.adler = state.check = 0 /*crc32(0L, Z_NULL, 0)*/;
+ state.mode = TYPE;
+ break;
+ case DICTID:
+ //=== NEEDBITS(32); */
+ while (bits < 32) {
+ if (have === 0) { break inf_leave; }
+ have--;
+ hold += input[next++] << bits;
+ bits += 8;
+ }
+ //===//
+ strm.adler = state.check = ZSWAP32(hold);
+ //=== INITBITS();
+ hold = 0;
+ bits = 0;
+ //===//
+ state.mode = DICT;
+ /* falls through */
+ case DICT:
+ if (state.havedict === 0) {
+ //--- RESTORE() ---
+ strm.next_out = put;
+ strm.avail_out = left;
+ strm.next_in = next;
+ strm.avail_in = have;
+ state.hold = hold;
+ state.bits = bits;
+ //---
+ return Z_NEED_DICT;
+ }
+ strm.adler = state.check = 1/*adler32(0L, Z_NULL, 0)*/;
+ state.mode = TYPE;
+ /* falls through */
+ case TYPE:
+ if (flush === Z_BLOCK || flush === Z_TREES) { break inf_leave; }
+ /* falls through */
+ case TYPEDO:
+ if (state.last) {
+ //--- BYTEBITS() ---//
+ hold >>>= bits & 7;
+ bits -= bits & 7;
+ //---//
+ state.mode = CHECK;
+ break;
+ }
+ //=== NEEDBITS(3); */
+ while (bits < 3) {
+ if (have === 0) { break inf_leave; }
+ have--;
+ hold += input[next++] << bits;
+ bits += 8;
+ }
+ //===//
+ state.last = (hold & 0x01)/*BITS(1)*/;
+ //--- DROPBITS(1) ---//
+ hold >>>= 1;
+ bits -= 1;
+ //---//
+
+ switch ((hold & 0x03)/*BITS(2)*/) {
+ case 0: /* stored block */
+ //Tracev((stderr, "inflate: stored block%s\n",
+ // state.last ? " (last)" : ""));
+ state.mode = STORED;
+ break;
+ case 1: /* fixed block */
+ fixedtables(state);
+ //Tracev((stderr, "inflate: fixed codes block%s\n",
+ // state.last ? " (last)" : ""));
+ state.mode = LEN_; /* decode codes */
+ if (flush === Z_TREES) {
+ //--- DROPBITS(2) ---//
+ hold >>>= 2;
+ bits -= 2;
+ //---//
+ break inf_leave;
+ }
+ break;
+ case 2: /* dynamic block */
+ //Tracev((stderr, "inflate: dynamic codes block%s\n",
+ // state.last ? " (last)" : ""));
+ state.mode = TABLE;
+ break;
+ case 3:
+ strm.msg = 'invalid block type';
+ state.mode = BAD;
+ }
+ //--- DROPBITS(2) ---//
+ hold >>>= 2;
+ bits -= 2;
+ //---//
+ break;
+ case STORED:
+ //--- BYTEBITS() ---// /* go to byte boundary */
+ hold >>>= bits & 7;
+ bits -= bits & 7;
+ //---//
+ //=== NEEDBITS(32); */
+ while (bits < 32) {
+ if (have === 0) { break inf_leave; }
+ have--;
+ hold += input[next++] << bits;
+ bits += 8;
+ }
+ //===//
+ if ((hold & 0xffff) !== ((hold >>> 16) ^ 0xffff)) {
+ strm.msg = 'invalid stored block lengths';
+ state.mode = BAD;
+ break;
+ }
+ state.length = hold & 0xffff;
+ //Tracev((stderr, "inflate: stored length %u\n",
+ // state.length));
+ //=== INITBITS();
+ hold = 0;
+ bits = 0;
+ //===//
+ state.mode = COPY_;
+ if (flush === Z_TREES) { break inf_leave; }
+ /* falls through */
+ case COPY_:
+ state.mode = COPY;
+ /* falls through */
+ case COPY:
+ copy = state.length;
+ if (copy) {
+ if (copy > have) { copy = have; }
+ if (copy > left) { copy = left; }
+ if (copy === 0) { break inf_leave; }
+ //--- zmemcpy(put, next, copy); ---
+ utils.arraySet(output, input, next, copy, put);
+ //---//
+ have -= copy;
+ next += copy;
+ left -= copy;
+ put += copy;
+ state.length -= copy;
+ break;
+ }
+ //Tracev((stderr, "inflate: stored end\n"));
+ state.mode = TYPE;
+ break;
+ case TABLE:
+ //=== NEEDBITS(14); */
+ while (bits < 14) {
+ if (have === 0) { break inf_leave; }
+ have--;
+ hold += input[next++] << bits;
+ bits += 8;
+ }
+ //===//
+ state.nlen = (hold & 0x1f)/*BITS(5)*/ + 257;
+ //--- DROPBITS(5) ---//
+ hold >>>= 5;
+ bits -= 5;
+ //---//
+ state.ndist = (hold & 0x1f)/*BITS(5)*/ + 1;
+ //--- DROPBITS(5) ---//
+ hold >>>= 5;
+ bits -= 5;
+ //---//
+ state.ncode = (hold & 0x0f)/*BITS(4)*/ + 4;
+ //--- DROPBITS(4) ---//
+ hold >>>= 4;
+ bits -= 4;
+ //---//
+//#ifndef PKZIP_BUG_WORKAROUND
+ if (state.nlen > 286 || state.ndist > 30) {
+ strm.msg = 'too many length or distance symbols';
+ state.mode = BAD;
+ break;
+ }
+//#endif
+ //Tracev((stderr, "inflate: table sizes ok\n"));
+ state.have = 0;
+ state.mode = LENLENS;
+ /* falls through */
+ case LENLENS:
+ while (state.have < state.ncode) {
+ //=== NEEDBITS(3);
+ while (bits < 3) {
+ if (have === 0) { break inf_leave; }
+ have--;
+ hold += input[next++] << bits;
+ bits += 8;
+ }
+ //===//
+ state.lens[order[state.have++]] = (hold & 0x07);//BITS(3);
+ //--- DROPBITS(3) ---//
+ hold >>>= 3;
+ bits -= 3;
+ //---//
+ }
+ while (state.have < 19) {
+ state.lens[order[state.have++]] = 0;
+ }
+ // We have separate tables & no pointers. 2 commented lines below not needed.
+ //state.next = state.codes;
+ //state.lencode = state.next;
+ // Switch to use dynamic table
+ state.lencode = state.lendyn;
+ state.lenbits = 7;
+
+ opts = {bits: state.lenbits};
+ ret = inflate_table(CODES, state.lens, 0, 19, state.lencode, 0, state.work, opts);
+ state.lenbits = opts.bits;
+
+ if (ret) {
+ strm.msg = 'invalid code lengths set';
+ state.mode = BAD;
+ break;
+ }
+ //Tracev((stderr, "inflate: code lengths ok\n"));
+ state.have = 0;
+ state.mode = CODELENS;
+ /* falls through */
+ case CODELENS:
+ while (state.have < state.nlen + state.ndist) {
+ for (;;) {
+ here = state.lencode[hold & ((1 << state.lenbits) - 1)];/*BITS(state.lenbits)*/
+ here_bits = here >>> 24;
+ here_op = (here >>> 16) & 0xff;
+ here_val = here & 0xffff;
+
+ if ((here_bits) <= bits) { break; }
+ //--- PULLBYTE() ---//
+ if (have === 0) { break inf_leave; }
+ have--;
+ hold += input[next++] << bits;
+ bits += 8;
+ //---//
+ }
+ if (here_val < 16) {
+ //--- DROPBITS(here.bits) ---//
+ hold >>>= here_bits;
+ bits -= here_bits;
+ //---//
+ state.lens[state.have++] = here_val;
+ }
+ else {
+ if (here_val === 16) {
+ //=== NEEDBITS(here.bits + 2);
+ n = here_bits + 2;
+ while (bits < n) {
+ if (have === 0) { break inf_leave; }
+ have--;
+ hold += input[next++] << bits;
+ bits += 8;
+ }
+ //===//
+ //--- DROPBITS(here.bits) ---//
+ hold >>>= here_bits;
+ bits -= here_bits;
+ //---//
+ if (state.have === 0) {
+ strm.msg = 'invalid bit length repeat';
+ state.mode = BAD;
+ break;
+ }
+ len = state.lens[state.have - 1];
+ copy = 3 + (hold & 0x03);//BITS(2);
+ //--- DROPBITS(2) ---//
+ hold >>>= 2;
+ bits -= 2;
+ //---//
+ }
+ else if (here_val === 17) {
+ //=== NEEDBITS(here.bits + 3);
+ n = here_bits + 3;
+ while (bits < n) {
+ if (have === 0) { break inf_leave; }
+ have--;
+ hold += input[next++] << bits;
+ bits += 8;
+ }
+ //===//
+ //--- DROPBITS(here.bits) ---//
+ hold >>>= here_bits;
+ bits -= here_bits;
+ //---//
+ len = 0;
+ copy = 3 + (hold & 0x07);//BITS(3);
+ //--- DROPBITS(3) ---//
+ hold >>>= 3;
+ bits -= 3;
+ //---//
+ }
+ else {
+ //=== NEEDBITS(here.bits + 7);
+ n = here_bits + 7;
+ while (bits < n) {
+ if (have === 0) { break inf_leave; }
+ have--;
+ hold += input[next++] << bits;
+ bits += 8;
+ }
+ //===//
+ //--- DROPBITS(here.bits) ---//
+ hold >>>= here_bits;
+ bits -= here_bits;
+ //---//
+ len = 0;
+ copy = 11 + (hold & 0x7f);//BITS(7);
+ //--- DROPBITS(7) ---//
+ hold >>>= 7;
+ bits -= 7;
+ //---//
+ }
+ if (state.have + copy > state.nlen + state.ndist) {
+ strm.msg = 'invalid bit length repeat';
+ state.mode = BAD;
+ break;
+ }
+ while (copy--) {
+ state.lens[state.have++] = len;
+ }
+ }
+ }
+
+ /* handle error breaks in while */
+ if (state.mode === BAD) { break; }
+
+ /* check for end-of-block code (better have one) */
+ if (state.lens[256] === 0) {
+ strm.msg = 'invalid code -- missing end-of-block';
+ state.mode = BAD;
+ break;
+ }
+
+ /* build code tables -- note: do not change the lenbits or distbits
+ values here (9 and 6) without reading the comments in inftrees.h
+ concerning the ENOUGH constants, which depend on those values */
+ state.lenbits = 9;
+
+ opts = {bits: state.lenbits};
+ ret = inflate_table(LENS, state.lens, 0, state.nlen, state.lencode, 0, state.work, opts);
+ // We have separate tables & no pointers. 2 commented lines below not needed.
+ // state.next_index = opts.table_index;
+ state.lenbits = opts.bits;
+ // state.lencode = state.next;
+
+ if (ret) {
+ strm.msg = 'invalid literal/lengths set';
+ state.mode = BAD;
+ break;
+ }
+
+ state.distbits = 6;
+ //state.distcode.copy(state.codes);
+ // Switch to use dynamic table
+ state.distcode = state.distdyn;
+ opts = {bits: state.distbits};
+ ret = inflate_table(DISTS, state.lens, state.nlen, state.ndist, state.distcode, 0, state.work, opts);
+ // We have separate tables & no pointers. 2 commented lines below not needed.
+ // state.next_index = opts.table_index;
+ state.distbits = opts.bits;
+ // state.distcode = state.next;
+
+ if (ret) {
+ strm.msg = 'invalid distances set';
+ state.mode = BAD;
+ break;
+ }
+ //Tracev((stderr, 'inflate: codes ok\n'));
+ state.mode = LEN_;
+ if (flush === Z_TREES) { break inf_leave; }
+ /* falls through */
+ case LEN_:
+ state.mode = LEN;
+ /* falls through */
+ case LEN:
+ if (have >= 6 && left >= 258) {
+ //--- RESTORE() ---
+ strm.next_out = put;
+ strm.avail_out = left;
+ strm.next_in = next;
+ strm.avail_in = have;
+ state.hold = hold;
+ state.bits = bits;
+ //---
+ inflate_fast(strm, _out);
+ //--- LOAD() ---
+ put = strm.next_out;
+ output = strm.output;
+ left = strm.avail_out;
+ next = strm.next_in;
+ input = strm.input;
+ have = strm.avail_in;
+ hold = state.hold;
+ bits = state.bits;
+ //---
+
+ if (state.mode === TYPE) {
+ state.back = -1;
+ }
+ break;
+ }
+ state.back = 0;
+ for (;;) {
+ here = state.lencode[hold & ((1 << state.lenbits) -1)]; /*BITS(state.lenbits)*/
+ here_bits = here >>> 24;
+ here_op = (here >>> 16) & 0xff;
+ here_val = here & 0xffff;
+
+ if (here_bits <= bits) { break; }
+ //--- PULLBYTE() ---//
+ if (have === 0) { break inf_leave; }
+ have--;
+ hold += input[next++] << bits;
+ bits += 8;
+ //---//
+ }
+ if (here_op && (here_op & 0xf0) === 0) {
+ last_bits = here_bits;
+ last_op = here_op;
+ last_val = here_val;
+ for (;;) {
+ here = state.lencode[last_val +
+ ((hold & ((1 << (last_bits + last_op)) -1))/*BITS(last.bits + last.op)*/ >> last_bits)];
+ here_bits = here >>> 24;
+ here_op = (here >>> 16) & 0xff;
+ here_val = here & 0xffff;
+
+ if ((last_bits + here_bits) <= bits) { break; }
+ //--- PULLBYTE() ---//
+ if (have === 0) { break inf_leave; }
+ have--;
+ hold += input[next++] << bits;
+ bits += 8;
+ //---//
+ }
+ //--- DROPBITS(last.bits) ---//
+ hold >>>= last_bits;
+ bits -= last_bits;
+ //---//
+ state.back += last_bits;
+ }
+ //--- DROPBITS(here.bits) ---//
+ hold >>>= here_bits;
+ bits -= here_bits;
+ //---//
+ state.back += here_bits;
+ state.length = here_val;
+ if (here_op === 0) {
+ //Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ?
+ // "inflate: literal '%c'\n" :
+ // "inflate: literal 0x%02x\n", here.val));
+ state.mode = LIT;
+ break;
+ }
+ if (here_op & 32) {
+ //Tracevv((stderr, "inflate: end of block\n"));
+ state.back = -1;
+ state.mode = TYPE;
+ break;
+ }
+ if (here_op & 64) {
+ strm.msg = 'invalid literal/length code';
+ state.mode = BAD;
+ break;
+ }
+ state.extra = here_op & 15;
+ state.mode = LENEXT;
+ /* falls through */
+ case LENEXT:
+ if (state.extra) {
+ //=== NEEDBITS(state.extra);
+ n = state.extra;
+ while (bits < n) {
+ if (have === 0) { break inf_leave; }
+ have--;
+ hold += input[next++] << bits;
+ bits += 8;
+ }
+ //===//
+ state.length += hold & ((1 << state.extra) -1)/*BITS(state.extra)*/;
+ //--- DROPBITS(state.extra) ---//
+ hold >>>= state.extra;
+ bits -= state.extra;
+ //---//
+ state.back += state.extra;
+ }
+ //Tracevv((stderr, "inflate: length %u\n", state.length));
+ state.was = state.length;
+ state.mode = DIST;
+ /* falls through */
+ case DIST:
+ for (;;) {
+ here = state.distcode[hold & ((1 << state.distbits) -1)];/*BITS(state.distbits)*/
+ here_bits = here >>> 24;
+ here_op = (here >>> 16) & 0xff;
+ here_val = here & 0xffff;
+
+ if ((here_bits) <= bits) { break; }
+ //--- PULLBYTE() ---//
+ if (have === 0) { break inf_leave; }
+ have--;
+ hold += input[next++] << bits;
+ bits += 8;
+ //---//
+ }
+ if ((here_op & 0xf0) === 0) {
+ last_bits = here_bits;
+ last_op = here_op;
+ last_val = here_val;
+ for (;;) {
+ here = state.distcode[last_val +
+ ((hold & ((1 << (last_bits + last_op)) -1))/*BITS(last.bits + last.op)*/ >> last_bits)];
+ here_bits = here >>> 24;
+ here_op = (here >>> 16) & 0xff;
+ here_val = here & 0xffff;
+
+ if ((last_bits + here_bits) <= bits) { break; }
+ //--- PULLBYTE() ---//
+ if (have === 0) { break inf_leave; }
+ have--;
+ hold += input[next++] << bits;
+ bits += 8;
+ //---//
+ }
+ //--- DROPBITS(last.bits) ---//
+ hold >>>= last_bits;
+ bits -= last_bits;
+ //---//
+ state.back += last_bits;
+ }
+ //--- DROPBITS(here.bits) ---//
+ hold >>>= here_bits;
+ bits -= here_bits;
+ //---//
+ state.back += here_bits;
+ if (here_op & 64) {
+ strm.msg = 'invalid distance code';
+ state.mode = BAD;
+ break;
+ }
+ state.offset = here_val;
+ state.extra = (here_op) & 15;
+ state.mode = DISTEXT;
+ /* falls through */
+ case DISTEXT:
+ if (state.extra) {
+ //=== NEEDBITS(state.extra);
+ n = state.extra;
+ while (bits < n) {
+ if (have === 0) { break inf_leave; }
+ have--;
+ hold += input[next++] << bits;
+ bits += 8;
+ }
+ //===//
+ state.offset += hold & ((1 << state.extra) -1)/*BITS(state.extra)*/;
+ //--- DROPBITS(state.extra) ---//
+ hold >>>= state.extra;
+ bits -= state.extra;
+ //---//
+ state.back += state.extra;
+ }
+//#ifdef INFLATE_STRICT
+ if (state.offset > state.dmax) {
+ strm.msg = 'invalid distance too far back';
+ state.mode = BAD;
+ break;
+ }
+//#endif
+ //Tracevv((stderr, "inflate: distance %u\n", state.offset));
+ state.mode = MATCH;
+ /* falls through */
+ case MATCH:
+ if (left === 0) { break inf_leave; }
+ copy = _out - left;
+ if (state.offset > copy) { /* copy from window */
+ copy = state.offset - copy;
+ if (copy > state.whave) {
+ if (state.sane) {
+ strm.msg = 'invalid distance too far back';
+ state.mode = BAD;
+ break;
+ }
+// (!) This block is disabled in zlib defailts,
+// don't enable it for binary compatibility
+//#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR
+// Trace((stderr, "inflate.c too far\n"));
+// copy -= state.whave;
+// if (copy > state.length) { copy = state.length; }
+// if (copy > left) { copy = left; }
+// left -= copy;
+// state.length -= copy;
+// do {
+// output[put++] = 0;
+// } while (--copy);
+// if (state.length === 0) { state.mode = LEN; }
+// break;
+//#endif
+ }
+ if (copy > state.wnext) {
+ copy -= state.wnext;
+ from = state.wsize - copy;
+ }
+ else {
+ from = state.wnext - copy;
+ }
+ if (copy > state.length) { copy = state.length; }
+ from_source = state.window;
+ }
+ else { /* copy from output */
+ from_source = output;
+ from = put - state.offset;
+ copy = state.length;
+ }
+ if (copy > left) { copy = left; }
+ left -= copy;
+ state.length -= copy;
+ do {
+ output[put++] = from_source[from++];
+ } while (--copy);
+ if (state.length === 0) { state.mode = LEN; }
+ break;
+ case LIT:
+ if (left === 0) { break inf_leave; }
+ output[put++] = state.length;
+ left--;
+ state.mode = LEN;
+ break;
+ case CHECK:
+ if (state.wrap) {
+ //=== NEEDBITS(32);
+ while (bits < 32) {
+ if (have === 0) { break inf_leave; }
+ have--;
+ // Use '|' insdead of '+' to make sure that result is signed
+ hold |= input[next++] << bits;
+ bits += 8;
+ }
+ //===//
+ _out -= left;
+ strm.total_out += _out;
+ state.total += _out;
+ if (_out) {
+ strm.adler = state.check =
+ /*UPDATE(state.check, put - _out, _out);*/
+ (state.flags ? crc32(state.check, output, _out, put - _out) : adler32(state.check, output, _out, put - _out));
+
+ }
+ _out = left;
+ // NB: crc32 stored as signed 32-bit int, ZSWAP32 returns signed too
+ if ((state.flags ? hold : ZSWAP32(hold)) !== state.check) {
+ strm.msg = 'incorrect data check';
+ state.mode = BAD;
+ break;
+ }
+ //=== INITBITS();
+ hold = 0;
+ bits = 0;
+ //===//
+ //Tracev((stderr, "inflate: check matches trailer\n"));
+ }
+ state.mode = LENGTH;
+ /* falls through */
+ case LENGTH:
+ if (state.wrap && state.flags) {
+ //=== NEEDBITS(32);
+ while (bits < 32) {
+ if (have === 0) { break inf_leave; }
+ have--;
+ hold += input[next++] << bits;
+ bits += 8;
+ }
+ //===//
+ if (hold !== (state.total & 0xffffffff)) {
+ strm.msg = 'incorrect length check';
+ state.mode = BAD;
+ break;
+ }
+ //=== INITBITS();
+ hold = 0;
+ bits = 0;
+ //===//
+ //Tracev((stderr, "inflate: length matches trailer\n"));
+ }
+ state.mode = DONE;
+ /* falls through */
+ case DONE:
+ ret = Z_STREAM_END;
+ break inf_leave;
+ case BAD:
+ ret = Z_DATA_ERROR;
+ break inf_leave;
+ case MEM:
+ return Z_MEM_ERROR;
+ case SYNC:
+ /* falls through */
+ default:
+ return Z_STREAM_ERROR;
+ }
+ }
+
+ // inf_leave <- here is real place for "goto inf_leave", emulated via "break inf_leave"
+
+ /*
+ Return from inflate(), updating the total counts and the check value.
+ If there was no progress during the inflate() call, return a buffer
+ error. Call updatewindow() to create and/or update the window state.
+ Note: a memory error from inflate() is non-recoverable.
+ */
+
+ //--- RESTORE() ---
+ strm.next_out = put;
+ strm.avail_out = left;
+ strm.next_in = next;
+ strm.avail_in = have;
+ state.hold = hold;
+ state.bits = bits;
+ //---
+
+ if (state.wsize || (_out !== strm.avail_out && state.mode < BAD &&
+ (state.mode < CHECK || flush !== Z_FINISH))) {
+ if (updatewindow(strm, strm.output, strm.next_out, _out - strm.avail_out)) {
+ state.mode = MEM;
+ return Z_MEM_ERROR;
+ }
+ }
+ _in -= strm.avail_in;
+ _out -= strm.avail_out;
+ strm.total_in += _in;
+ strm.total_out += _out;
+ state.total += _out;
+ if (state.wrap && _out) {
+ strm.adler = state.check = /*UPDATE(state.check, strm.next_out - _out, _out);*/
+ (state.flags ? crc32(state.check, output, _out, strm.next_out - _out) : adler32(state.check, output, _out, strm.next_out - _out));
+ }
+ strm.data_type = state.bits + (state.last ? 64 : 0) +
+ (state.mode === TYPE ? 128 : 0) +
+ (state.mode === LEN_ || state.mode === COPY_ ? 256 : 0);
+ if (((_in === 0 && _out === 0) || flush === Z_FINISH) && ret === Z_OK) {
+ ret = Z_BUF_ERROR;
+ }
+ return ret;
+}
+
+function inflateEnd(strm) {
+
+ if (!strm || !strm.state /*|| strm->zfree == (free_func)0*/) {
+ return Z_STREAM_ERROR;
+ }
+
+ var state = strm.state;
+ if (state.window) {
+ state.window = null;
+ }
+ strm.state = null;
+ return Z_OK;
+}
+
+function inflateGetHeader(strm, head) {
+ var state;
+
+ /* check state */
+ if (!strm || !strm.state) { return Z_STREAM_ERROR; }
+ state = strm.state;
+ if ((state.wrap & 2) === 0) { return Z_STREAM_ERROR; }
+
+ /* save header structure */
+ state.head = head;
+ head.done = false;
+ return Z_OK;
+}
+
+
+exports.inflateReset = inflateReset;
+exports.inflateReset2 = inflateReset2;
+exports.inflateResetKeep = inflateResetKeep;
+exports.inflateInit = inflateInit;
+exports.inflateInit2 = inflateInit2;
+exports.inflate = inflate;
+exports.inflateEnd = inflateEnd;
+exports.inflateGetHeader = inflateGetHeader;
+exports.inflateInfo = 'pako inflate (from Nodeca project)';
+
+/* Not implemented
+exports.inflateCopy = inflateCopy;
+exports.inflateGetDictionary = inflateGetDictionary;
+exports.inflateMark = inflateMark;
+exports.inflatePrime = inflatePrime;
+exports.inflateSetDictionary = inflateSetDictionary;
+exports.inflateSync = inflateSync;
+exports.inflateSyncPoint = inflateSyncPoint;
+exports.inflateUndermine = inflateUndermine;
+*/
+
+},{"../utils/common":1,"./adler32":2,"./crc32":3,"./inffast":4,"./inftrees":6}],6:[function(require,module,exports){
+'use strict';
+
+
+var utils = require('../utils/common');
+
+var MAXBITS = 15;
+var ENOUGH_LENS = 852;
+var ENOUGH_DISTS = 592;
+//var ENOUGH = (ENOUGH_LENS+ENOUGH_DISTS);
+
+var CODES = 0;
+var LENS = 1;
+var DISTS = 2;
+
+var lbase = [ /* Length codes 257..285 base */
+ 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31,
+ 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0
+];
+
+var lext = [ /* Length codes 257..285 extra */
+ 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18,
+ 19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 16, 72, 78
+];
+
+var dbase = [ /* Distance codes 0..29 base */
+ 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193,
+ 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145,
+ 8193, 12289, 16385, 24577, 0, 0
+];
+
+var dext = [ /* Distance codes 0..29 extra */
+ 16, 16, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22,
+ 23, 23, 24, 24, 25, 25, 26, 26, 27, 27,
+ 28, 28, 29, 29, 64, 64
+];
+
+module.exports = function inflate_table(type, lens, lens_index, codes, table, table_index, work, opts)
+{
+ var bits = opts.bits;
+ //here = opts.here; /* table entry for duplication */
+
+ var len = 0; /* a code's length in bits */
+ var sym = 0; /* index of code symbols */
+ var min = 0, max = 0; /* minimum and maximum code lengths */
+ var root = 0; /* number of index bits for root table */
+ var curr = 0; /* number of index bits for current table */
+ var drop = 0; /* code bits to drop for sub-table */
+ var left = 0; /* number of prefix codes available */
+ var used = 0; /* code entries in table used */
+ var huff = 0; /* Huffman code */
+ var incr; /* for incrementing code, index */
+ var fill; /* index for replicating entries */
+ var low; /* low bits for current root entry */
+ var mask; /* mask for low root bits */
+ var next; /* next available space in table */
+ var base = null; /* base value table to use */
+ var base_index = 0;
+// var shoextra; /* extra bits table to use */
+ var end; /* use base and extra for symbol > end */
+ var count = new utils.Buf16(MAXBITS+1); //[MAXBITS+1]; /* number of codes of each length */
+ var offs = new utils.Buf16(MAXBITS+1); //[MAXBITS+1]; /* offsets in table for each length */
+ var extra = null;
+ var extra_index = 0;
+
+ var here_bits, here_op, here_val;
+
+ /*
+ Process a set of code lengths to create a canonical Huffman code. The
+ code lengths are lens[0..codes-1]. Each length corresponds to the
+ symbols 0..codes-1. The Huffman code is generated by first sorting the
+ symbols by length from short to long, and retaining the symbol order
+ for codes with equal lengths. Then the code starts with all zero bits
+ for the first code of the shortest length, and the codes are integer
+ increments for the same length, and zeros are appended as the length
+ increases. For the deflate format, these bits are stored backwards
+ from their more natural integer increment ordering, and so when the
+ decoding tables are built in the large loop below, the integer codes
+ are incremented backwards.
+
+ This routine assumes, but does not check, that all of the entries in
+ lens[] are in the range 0..MAXBITS. The caller must assure this.
+ 1..MAXBITS is interpreted as that code length. zero means that that
+ symbol does not occur in this code.
+
+ The codes are sorted by computing a count of codes for each length,
+ creating from that a table of starting indices for each length in the
+ sorted table, and then entering the symbols in order in the sorted
+ table. The sorted table is work[], with that space being provided by
+ the caller.
+
+ The length counts are used for other purposes as well, i.e. finding
+ the minimum and maximum length codes, determining if there are any
+ codes at all, checking for a valid set of lengths, and looking ahead
+ at length counts to determine sub-table sizes when building the
+ decoding tables.
+ */
+
+ /* accumulate lengths for codes (assumes lens[] all in 0..MAXBITS) */
+ for (len = 0; len <= MAXBITS; len++) {
+ count[len] = 0;
+ }
+ for (sym = 0; sym < codes; sym++) {
+ count[lens[lens_index + sym]]++;
+ }
+
+ /* bound code lengths, force root to be within code lengths */
+ root = bits;
+ for (max = MAXBITS; max >= 1; max--) {
+ if (count[max] !== 0) { break; }
+ }
+ if (root > max) {
+ root = max;
+ }
+ if (max === 0) { /* no symbols to code at all */
+ //table.op[opts.table_index] = 64; //here.op = (var char)64; /* invalid code marker */
+ //table.bits[opts.table_index] = 1; //here.bits = (var char)1;
+ //table.val[opts.table_index++] = 0; //here.val = (var short)0;
+ table[table_index++] = (1 << 24) | (64 << 16) | 0;
+
+
+ //table.op[opts.table_index] = 64;
+ //table.bits[opts.table_index] = 1;
+ //table.val[opts.table_index++] = 0;
+ table[table_index++] = (1 << 24) | (64 << 16) | 0;
+
+ opts.bits = 1;
+ return 0; /* no symbols, but wait for decoding to report error */
+ }
+ for (min = 1; min < max; min++) {
+ if (count[min] !== 0) { break; }
+ }
+ if (root < min) {
+ root = min;
+ }
+
+ /* check for an over-subscribed or incomplete set of lengths */
+ left = 1;
+ for (len = 1; len <= MAXBITS; len++) {
+ left <<= 1;
+ left -= count[len];
+ if (left < 0) {
+ return -1;
+ } /* over-subscribed */
+ }
+ if (left > 0 && (type === CODES || max !== 1)) {
+ return -1; /* incomplete set */
+ }
+
+ /* generate offsets into symbol table for each length for sorting */
+ offs[1] = 0;
+ for (len = 1; len < MAXBITS; len++) {
+ offs[len + 1] = offs[len] + count[len];
+ }
+
+ /* sort symbols by length, by symbol order within each length */
+ for (sym = 0; sym < codes; sym++) {
+ if (lens[lens_index + sym] !== 0) {
+ work[offs[lens[lens_index + sym]]++] = sym;
+ }
+ }
+
+ /*
+ Create and fill in decoding tables. In this loop, the table being
+ filled is at next and has curr index bits. The code being used is huff
+ with length len. That code is converted to an index by dropping drop
+ bits off of the bottom. For codes where len is less than drop + curr,
+ those top drop + curr - len bits are incremented through all values to
+ fill the table with replicated entries.
+
+ root is the number of index bits for the root table. When len exceeds
+ root, sub-tables are created pointed to by the root entry with an index
+ of the low root bits of huff. This is saved in low to check for when a
+ new sub-table should be started. drop is zero when the root table is
+ being filled, and drop is root when sub-tables are being filled.
+
+ When a new sub-table is needed, it is necessary to look ahead in the
+ code lengths to determine what size sub-table is needed. The length
+ counts are used for this, and so count[] is decremented as codes are
+ entered in the tables.
+
+ used keeps track of how many table entries have been allocated from the
+ provided *table space. It is checked for LENS and DIST tables against
+ the constants ENOUGH_LENS and ENOUGH_DISTS to guard against changes in
+ the initial root table size constants. See the comments in inftrees.h
+ for more information.
+
+ sym increments through all symbols, and the loop terminates when
+ all codes of length max, i.e. all codes, have been processed. This
+ routine permits incomplete codes, so another loop after this one fills
+ in the rest of the decoding tables with invalid code markers.
+ */
+
+ /* set up for code type */
+ // poor man optimization - use if-else instead of switch,
+ // to avoid deopts in old v8
+ if (type === CODES) {
+ base = extra = work; /* dummy value--not used */
+ end = 19;
+
+ } else if (type === LENS) {
+ base = lbase;
+ base_index -= 257;
+ extra = lext;
+ extra_index -= 257;
+ end = 256;
+
+ } else { /* DISTS */
+ base = dbase;
+ extra = dext;
+ end = -1;
+ }
+
+ /* initialize opts for loop */
+ huff = 0; /* starting code */
+ sym = 0; /* starting code symbol */
+ len = min; /* starting code length */
+ next = table_index; /* current table to fill in */
+ curr = root; /* current table index bits */
+ drop = 0; /* current bits to drop from code for index */
+ low = -1; /* trigger new sub-table when len > root */
+ used = 1 << root; /* use root table entries */
+ mask = used - 1; /* mask for comparing low */
+
+ /* check available table space */
+ if ((type === LENS && used > ENOUGH_LENS) ||
+ (type === DISTS && used > ENOUGH_DISTS)) {
+ return 1;
+ }
+
+ var i=0;
+ /* process all codes and make table entries */
+ for (;;) {
+ i++;
+ /* create table entry */
+ here_bits = len - drop;
+ if (work[sym] < end) {
+ here_op = 0;
+ here_val = work[sym];
+ }
+ else if (work[sym] > end) {
+ here_op = extra[extra_index + work[sym]];
+ here_val = base[base_index + work[sym]];
+ }
+ else {
+ here_op = 32 + 64; /* end of block */
+ here_val = 0;
+ }
+
+ /* replicate for those indices with low len bits equal to huff */
+ incr = 1 << (len - drop);
+ fill = 1 << curr;
+ min = fill; /* save offset to next table */
+ do {
+ fill -= incr;
+ table[next + (huff >> drop) + fill] = (here_bits << 24) | (here_op << 16) | here_val |0;
+ } while (fill !== 0);
+
+ /* backwards increment the len-bit code huff */
+ incr = 1 << (len - 1);
+ while (huff & incr) {
+ incr >>= 1;
+ }
+ if (incr !== 0) {
+ huff &= incr - 1;
+ huff += incr;
+ } else {
+ huff = 0;
+ }
+
+ /* go to next symbol, update count, len */
+ sym++;
+ if (--count[len] === 0) {
+ if (len === max) { break; }
+ len = lens[lens_index + work[sym]];
+ }
+
+ /* create new sub-table if needed */
+ if (len > root && (huff & mask) !== low) {
+ /* if first time, transition to sub-tables */
+ if (drop === 0) {
+ drop = root;
+ }
+
+ /* increment past last table */
+ next += min; /* here min is 1 << curr */
+
+ /* determine length of next table */
+ curr = len - drop;
+ left = 1 << curr;
+ while (curr + drop < max) {
+ left -= count[curr + drop];
+ if (left <= 0) { break; }
+ curr++;
+ left <<= 1;
+ }
+
+ /* check for enough space */
+ used += 1 << curr;
+ if ((type === LENS && used > ENOUGH_LENS) ||
+ (type === DISTS && used > ENOUGH_DISTS)) {
+ return 1;
+ }
+
+ /* point entry in root table to sub-table */
+ low = huff & mask;
+ /*table.op[low] = curr;
+ table.bits[low] = root;
+ table.val[low] = next - opts.table_index;*/
+ table[low] = (root << 24) | (curr << 16) | (next - table_index) |0;
+ }
+ }
+
+ /* fill in remaining table entry if code is incomplete (guaranteed to have
+ at most one remaining entry, since if the code is incomplete, the
+ maximum code length that was allowed to get this far is one bit) */
+ if (huff !== 0) {
+ //table.op[next + huff] = 64; /* invalid code marker */
+ //table.bits[next + huff] = len - drop;
+ //table.val[next + huff] = 0;
+ table[next + huff] = ((len - drop) << 24) | (64 << 16) |0;
+ }
+
+ /* set return parameters */
+ //opts.table_index += used;
+ opts.bits = root;
+ return 0;
+};
+
+},{"../utils/common":1}],7:[function(require,module,exports){
+'use strict';
+
+
+function ZStream() {
+ /* next input byte */
+ this.input = null; // JS specific, because we have no pointers
+ this.next_in = 0;
+ /* number of bytes available at input */
+ this.avail_in = 0;
+ /* total number of input bytes read so far */
+ this.total_in = 0;
+ /* next output byte should be put there */
+ this.output = null; // JS specific, because we have no pointers
+ this.next_out = 0;
+ /* remaining free space at output */
+ this.avail_out = 0;
+ /* total number of bytes output so far */
+ this.total_out = 0;
+ /* last error message, NULL if no error */
+ this.msg = ''/*Z_NULL*/;
+ /* not visible by applications */
+ this.state = null;
+ /* best guess about the data type: binary or text */
+ this.data_type = 2/*Z_UNKNOWN*/;
+ /* adler32 value of the uncompressed data */
+ this.adler = 0;
+}
+
+module.exports = ZStream;
+
+},{}],8:[function(require,module,exports){
+var zlib = require('../node_modules/pako/lib/zlib/inflate.js');
+var ZStream = require('../node_modules/pako/lib/zlib/zstream.js');
+
+var Inflate = function () {
+ this.strm = new ZStream();
+ this.chunkSize = 1024 * 10 * 10;
+ this.strm.output = new Uint8Array(this.chunkSize);
+ this.windowBits = 5;
+
+ zlib.inflateInit(this.strm, this.windowBits);
+};
+
+Inflate.prototype = {
+ inflate: function (data, flush, expected) {
+ this.strm.input = data;
+ this.strm.avail_in = this.strm.input.length;
+ this.strm.next_in = 0;
+ this.strm.next_out = 0;
+
+ // resize our output buffer if it's too small
+ // (we could just use multiple chunks, but that would cause an extra
+ // allocation each time to flatten the chunks)
+ if (expected > this.chunkSize) {
+ this.chunkSize = expected;
+ this.strm.output = new Uint8Array(this.chunkSize);
+ }
+
+ this.strm.avail_out = this.chunkSize;
+
+ zlib.inflate(this.strm, flush);
+
+ return new Uint8Array(this.strm.output.buffer, 0, this.strm.next_out);
+ },
+
+ reset: function () {
+ zlib.inflateReset(this.strm);
+ }
+};
+
+module.exports = {Inflate: Inflate};
+
+},{"../node_modules/pako/lib/zlib/inflate.js":5,"../node_modules/pako/lib/zlib/zstream.js":7}]},{},[8])(8)
+});
\ No newline at end of file
diff --git a/public/novnc/include/input.js b/public/novnc/include/input.js
new file mode 100644
index 00000000..fa6ba44a
--- /dev/null
+++ b/public/novnc/include/input.js
@@ -0,0 +1,389 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2012 Joel Martin
+ * Copyright (C) 2013 Samuel Mannehed for Cendio AB
+ * Licensed under MPL 2.0 or any later version (see LICENSE.txt)
+ */
+
+/*jslint browser: true, white: false */
+/*global window, Util */
+
+var Keyboard, Mouse;
+
+(function () {
+ "use strict";
+
+ //
+ // Keyboard event handler
+ //
+
+ Keyboard = function (defaults) {
+ this._keyDownList = []; // List of depressed keys
+ // (even if they are happy)
+
+ Util.set_defaults(this, defaults, {
+ 'target': document,
+ 'focused': true
+ });
+
+ // create the keyboard handler
+ this._handler = new KeyEventDecoder(kbdUtil.ModifierSync(),
+ VerifyCharModifier( /* jshint newcap: false */
+ TrackKeyState(
+ EscapeModifiers(this._handleRfbEvent.bind(this))
+ )
+ )
+ ); /* jshint newcap: true */
+
+ // keep these here so we can refer to them later
+ this._eventHandlers = {
+ 'keyup': this._handleKeyUp.bind(this),
+ 'keydown': this._handleKeyDown.bind(this),
+ 'keypress': this._handleKeyPress.bind(this),
+ 'blur': this._allKeysUp.bind(this)
+ };
+ };
+
+ Keyboard.prototype = {
+ // private methods
+
+ _handleRfbEvent: function (e) {
+ if (this._onKeyPress) {
+ Util.Debug("onKeyPress " + (e.type == 'keydown' ? "down" : "up") +
+ ", keysym: " + e.keysym.keysym + "(" + e.keysym.keyname + ")");
+ this._onKeyPress(e.keysym.keysym, e.type == 'keydown');
+ }
+ },
+
+ _handleKeyDown: function (e) {
+ if (!this._focused) { return true; }
+
+ if (this._handler.keydown(e)) {
+ // Suppress bubbling/default actions
+ Util.stopEvent(e);
+ return false;
+ } else {
+ // Allow the event to bubble and become a keyPress event which
+ // will have the character code translated
+ return true;
+ }
+ },
+
+ _handleKeyPress: function (e) {
+ if (!this._focused) { return true; }
+
+ if (this._handler.keypress(e)) {
+ // Suppress bubbling/default actions
+ Util.stopEvent(e);
+ return false;
+ } else {
+ // Allow the event to bubble and become a keyPress event which
+ // will have the character code translated
+ return true;
+ }
+ },
+
+ _handleKeyUp: function (e) {
+ if (!this._focused) { return true; }
+
+ if (this._handler.keyup(e)) {
+ // Suppress bubbling/default actions
+ Util.stopEvent(e);
+ return false;
+ } else {
+ // Allow the event to bubble and become a keyPress event which
+ // will have the character code translated
+ return true;
+ }
+ },
+
+ _allKeysUp: function () {
+ Util.Debug(">> Keyboard.allKeysUp");
+ this._handler.releaseAll();
+ Util.Debug("<< Keyboard.allKeysUp");
+ },
+
+ // Public methods
+
+ grab: function () {
+ //Util.Debug(">> Keyboard.grab");
+ var c = this._target;
+
+ Util.addEvent(c, 'keydown', this._eventHandlers.keydown);
+ Util.addEvent(c, 'keyup', this._eventHandlers.keyup);
+ Util.addEvent(c, 'keypress', this._eventHandlers.keypress);
+
+ // Release (key up) if window loses focus
+ Util.addEvent(window, 'blur', this._eventHandlers.blur);
+
+ //Util.Debug("<< Keyboard.grab");
+ },
+
+ ungrab: function () {
+ //Util.Debug(">> Keyboard.ungrab");
+ var c = this._target;
+
+ Util.removeEvent(c, 'keydown', this._eventHandlers.keydown);
+ Util.removeEvent(c, 'keyup', this._eventHandlers.keyup);
+ Util.removeEvent(c, 'keypress', this._eventHandlers.keypress);
+ Util.removeEvent(window, 'blur', this._eventHandlers.blur);
+
+ // Release (key up) all keys that are in a down state
+ this._allKeysUp();
+
+ //Util.Debug(">> Keyboard.ungrab");
+ },
+
+ sync: function (e) {
+ this._handler.syncModifiers(e);
+ }
+ };
+
+ Util.make_properties(Keyboard, [
+ ['target', 'wo', 'dom'], // DOM element that captures keyboard input
+ ['focused', 'rw', 'bool'], // Capture and send key events
+
+ ['onKeyPress', 'rw', 'func'] // Handler for key press/release
+ ]);
+
+ //
+ // Mouse event handler
+ //
+
+ Mouse = function (defaults) {
+ this._mouseCaptured = false;
+
+ this._doubleClickTimer = null;
+ this._lastTouchPos = null;
+
+ // Configuration attributes
+ Util.set_defaults(this, defaults, {
+ 'target': document,
+ 'focused': true,
+ 'scale': 1.0,
+ 'touchButton': 1
+ });
+
+ this._eventHandlers = {
+ 'mousedown': this._handleMouseDown.bind(this),
+ 'mouseup': this._handleMouseUp.bind(this),
+ 'mousemove': this._handleMouseMove.bind(this),
+ 'mousewheel': this._handleMouseWheel.bind(this),
+ 'mousedisable': this._handleMouseDisable.bind(this)
+ };
+ };
+
+ Mouse.prototype = {
+ // private methods
+ _captureMouse: function () {
+ // capturing the mouse ensures we get the mouseup event
+ if (this._target.setCapture) {
+ this._target.setCapture();
+ }
+
+ // some browsers give us mouseup events regardless,
+ // so if we never captured the mouse, we can disregard the event
+ this._mouseCaptured = true;
+ },
+
+ _releaseMouse: function () {
+ if (this._target.releaseCapture) {
+ this._target.releaseCapture();
+ }
+ this._mouseCaptured = false;
+ },
+
+ _resetDoubleClickTimer: function () {
+ this._doubleClickTimer = null;
+ },
+
+ _handleMouseButton: function (e, down) {
+ if (!this._focused) { return true; }
+
+ if (this._notify) {
+ this._notify(e);
+ }
+
+ var evt = (e ? e : window.event);
+ var pos = Util.getEventPosition(e, this._target, this._scale);
+
+ var bmask;
+ if (e.touches || e.changedTouches) {
+ // Touch device
+
+ // When two touches occur within 500 ms of each other and are
+ // close enough together a double click is triggered.
+ if (down == 1) {
+ if (this._doubleClickTimer === null) {
+ this._lastTouchPos = pos;
+ } else {
+ clearTimeout(this._doubleClickTimer);
+
+ // When the distance between the two touches is small enough
+ // force the position of the latter touch to the position of
+ // the first.
+
+ var xs = this._lastTouchPos.x - pos.x;
+ var ys = this._lastTouchPos.y - pos.y;
+ var d = Math.sqrt((xs * xs) + (ys * ys));
+
+ // The goal is to trigger on a certain physical width, the
+ // devicePixelRatio brings us a bit closer but is not optimal.
+ var threshold = 20 * (window.devicePixelRatio || 1);
+ if (d < threshold) {
+ pos = this._lastTouchPos;
+ }
+ }
+ this._doubleClickTimer = setTimeout(this._resetDoubleClickTimer.bind(this), 500);
+ }
+ bmask = this._touchButton;
+ // If bmask is set
+ } else if (evt.which) {
+ /* everything except IE */
+ bmask = 1 << evt.button;
+ } else {
+ /* IE including 9 */
+ bmask = (evt.button & 0x1) + // Left
+ (evt.button & 0x2) * 2 + // Right
+ (evt.button & 0x4) / 2; // Middle
+ }
+
+ if (this._onMouseButton) {
+ Util.Debug("onMouseButton " + (down ? "down" : "up") +
+ ", x: " + pos.x + ", y: " + pos.y + ", bmask: " + bmask);
+ this._onMouseButton(pos.x, pos.y, down, bmask);
+ }
+ Util.stopEvent(e);
+ return false;
+ },
+
+ _handleMouseDown: function (e) {
+ this._captureMouse();
+ this._handleMouseButton(e, 1);
+ },
+
+ _handleMouseUp: function (e) {
+ if (!this._mouseCaptured) { return; }
+
+ this._handleMouseButton(e, 0);
+ this._releaseMouse();
+ },
+
+ _handleMouseWheel: function (e) {
+ if (!this._focused) { return true; }
+
+ if (this._notify) {
+ this._notify(e);
+ }
+
+ var evt = (e ? e : window.event);
+ var pos = Util.getEventPosition(e, this._target, this._scale);
+ var wheelData = evt.detail ? evt.detail * -1 : evt.wheelDelta / 40;
+ var bmask;
+ if (wheelData > 0) {
+ bmask = 1 << 3;
+ } else {
+ bmask = 1 << 4;
+ }
+
+ if (this._onMouseButton) {
+ this._onMouseButton(pos.x, pos.y, 1, bmask);
+ this._onMouseButton(pos.x, pos.y, 0, bmask);
+ }
+ Util.stopEvent(e);
+ return false;
+ },
+
+ _handleMouseMove: function (e) {
+ if (! this._focused) { return true; }
+
+ if (this._notify) {
+ this._notify(e);
+ }
+
+ var evt = (e ? e : window.event);
+ var pos = Util.getEventPosition(e, this._target, this._scale);
+ if (this._onMouseMove) {
+ this._onMouseMove(pos.x, pos.y);
+ }
+ Util.stopEvent(e);
+ return false;
+ },
+
+ _handleMouseDisable: function (e) {
+ if (!this._focused) { return true; }
+
+ var evt = (e ? e : window.event);
+ var pos = Util.getEventPosition(e, this._target, this._scale);
+
+ /* Stop propagation if inside canvas area */
+ if ((pos.realx >= 0) && (pos.realy >= 0) &&
+ (pos.realx < this._target.offsetWidth) &&
+ (pos.realy < this._target.offsetHeight)) {
+ //Util.Debug("mouse event disabled");
+ Util.stopEvent(e);
+ return false;
+ }
+
+ return true;
+ },
+
+
+ // Public methods
+ grab: function () {
+ var c = this._target;
+
+ if ('ontouchstart' in document.documentElement) {
+ Util.addEvent(c, 'touchstart', this._eventHandlers.mousedown);
+ Util.addEvent(window, 'touchend', this._eventHandlers.mouseup);
+ Util.addEvent(c, 'touchend', this._eventHandlers.mouseup);
+ Util.addEvent(c, 'touchmove', this._eventHandlers.mousemove);
+ } else {
+ Util.addEvent(c, 'mousedown', this._eventHandlers.mousedown);
+ Util.addEvent(window, 'mouseup', this._eventHandlers.mouseup);
+ Util.addEvent(c, 'mouseup', this._eventHandlers.mouseup);
+ Util.addEvent(c, 'mousemove', this._eventHandlers.mousemove);
+ Util.addEvent(c, (Util.Engine.gecko) ? 'DOMMouseScroll' : 'mousewheel',
+ this._eventHandlers.mousewheel);
+ }
+
+ /* Work around right and middle click browser behaviors */
+ Util.addEvent(document, 'click', this._eventHandlers.mousedisable);
+ Util.addEvent(document.body, 'contextmenu', this._eventHandlers.mousedisable);
+ },
+
+ ungrab: function () {
+ var c = this._target;
+
+ if ('ontouchstart' in document.documentElement) {
+ Util.removeEvent(c, 'touchstart', this._eventHandlers.mousedown);
+ Util.removeEvent(window, 'touchend', this._eventHandlers.mouseup);
+ Util.removeEvent(c, 'touchend', this._eventHandlers.mouseup);
+ Util.removeEvent(c, 'touchmove', this._eventHandlers.mousemove);
+ } else {
+ Util.removeEvent(c, 'mousedown', this._eventHandlers.mousedown);
+ Util.removeEvent(window, 'mouseup', this._eventHandlers.mouseup);
+ Util.removeEvent(c, 'mouseup', this._eventHandlers.mouseup);
+ Util.removeEvent(c, 'mousemove', this._eventHandlers.mousemove);
+ Util.removeEvent(c, (Util.Engine.gecko) ? 'DOMMouseScroll' : 'mousewheel',
+ this._eventHandlers.mousewheel);
+ }
+
+ /* Work around right and middle click browser behaviors */
+ Util.removeEvent(document, 'click', this._eventHandlers.mousedisable);
+ Util.removeEvent(document.body, 'contextmenu', this._eventHandlers.mousedisable);
+
+ }
+ };
+
+ Util.make_properties(Mouse, [
+ ['target', 'ro', 'dom'], // DOM element that captures mouse input
+ ['notify', 'ro', 'func'], // Function to call to notify whenever a mouse event is received
+ ['focused', 'rw', 'bool'], // Capture and send mouse clicks/movement
+ ['scale', 'rw', 'float'], // Viewport scale factor 0.0 - 1.0
+
+ ['onMouseButton', 'rw', 'func'], // Handler for mouse button click/release
+ ['onMouseMove', 'rw', 'func'], // Handler for mouse movement
+ ['touchButton', 'rw', 'int'] // Button mask (1, 2, 4) for touch devices (0 means ignore clicks)
+ ]);
+})();
diff --git a/public/novnc/include/keyboard.js b/public/novnc/include/keyboard.js
new file mode 100644
index 00000000..86670312
--- /dev/null
+++ b/public/novnc/include/keyboard.js
@@ -0,0 +1,543 @@
+var kbdUtil = (function() {
+ "use strict";
+
+ function substituteCodepoint(cp) {
+ // Any Unicode code points which do not have corresponding keysym entries
+ // can be swapped out for another code point by adding them to this table
+ var substitutions = {
+ // {S,s} with comma below -> {S,s} with cedilla
+ 0x218 : 0x15e,
+ 0x219 : 0x15f,
+ // {T,t} with comma below -> {T,t} with cedilla
+ 0x21a : 0x162,
+ 0x21b : 0x163
+ };
+
+ var sub = substitutions[cp];
+ return sub ? sub : cp;
+ }
+
+ function isMac() {
+ return navigator && !!(/mac/i).exec(navigator.platform);
+ }
+ function isWindows() {
+ return navigator && !!(/win/i).exec(navigator.platform);
+ }
+ function isLinux() {
+ return navigator && !!(/linux/i).exec(navigator.platform);
+ }
+
+ // Return true if a modifier which is not the specified char modifier (and is not shift) is down
+ function hasShortcutModifier(charModifier, currentModifiers) {
+ var mods = {};
+ for (var key in currentModifiers) {
+ if (parseInt(key) !== XK_Shift_L) {
+ mods[key] = currentModifiers[key];
+ }
+ }
+
+ var sum = 0;
+ for (var k in currentModifiers) {
+ if (mods[k]) {
+ ++sum;
+ }
+ }
+ if (hasCharModifier(charModifier, mods)) {
+ return sum > charModifier.length;
+ }
+ else {
+ return sum > 0;
+ }
+ }
+
+ // Return true if the specified char modifier is currently down
+ function hasCharModifier(charModifier, currentModifiers) {
+ if (charModifier.length === 0) { return false; }
+
+ for (var i = 0; i < charModifier.length; ++i) {
+ if (!currentModifiers[charModifier[i]]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // Helper object tracking modifier key state
+ // and generates fake key events to compensate if it gets out of sync
+ function ModifierSync(charModifier) {
+ if (!charModifier) {
+ if (isMac()) {
+ // on Mac, Option (AKA Alt) is used as a char modifier
+ charModifier = [XK_Alt_L];
+ }
+ else if (isWindows()) {
+ // on Windows, Ctrl+Alt is used as a char modifier
+ charModifier = [XK_Alt_L, XK_Control_L];
+ }
+ else if (isLinux()) {
+ // on Linux, ISO Level 3 Shift (AltGr) is used as a char modifier
+ charModifier = [XK_ISO_Level3_Shift];
+ }
+ else {
+ charModifier = [];
+ }
+ }
+
+ var state = {};
+ state[XK_Control_L] = false;
+ state[XK_Alt_L] = false;
+ state[XK_ISO_Level3_Shift] = false;
+ state[XK_Shift_L] = false;
+ state[XK_Meta_L] = false;
+
+ function sync(evt, keysym) {
+ var result = [];
+ function syncKey(keysym) {
+ return {keysym: keysyms.lookup(keysym), type: state[keysym] ? 'keydown' : 'keyup'};
+ }
+
+ if (evt.ctrlKey !== undefined &&
+ evt.ctrlKey !== state[XK_Control_L] && keysym !== XK_Control_L) {
+ state[XK_Control_L] = evt.ctrlKey;
+ result.push(syncKey(XK_Control_L));
+ }
+ if (evt.altKey !== undefined &&
+ evt.altKey !== state[XK_Alt_L] && keysym !== XK_Alt_L) {
+ state[XK_Alt_L] = evt.altKey;
+ result.push(syncKey(XK_Alt_L));
+ }
+ if (evt.altGraphKey !== undefined &&
+ evt.altGraphKey !== state[XK_ISO_Level3_Shift] && keysym !== XK_ISO_Level3_Shift) {
+ state[XK_ISO_Level3_Shift] = evt.altGraphKey;
+ result.push(syncKey(XK_ISO_Level3_Shift));
+ }
+ if (evt.shiftKey !== undefined &&
+ evt.shiftKey !== state[XK_Shift_L] && keysym !== XK_Shift_L) {
+ state[XK_Shift_L] = evt.shiftKey;
+ result.push(syncKey(XK_Shift_L));
+ }
+ if (evt.metaKey !== undefined &&
+ evt.metaKey !== state[XK_Meta_L] && keysym !== XK_Meta_L) {
+ state[XK_Meta_L] = evt.metaKey;
+ result.push(syncKey(XK_Meta_L));
+ }
+ return result;
+ }
+ function syncKeyEvent(evt, down) {
+ var obj = getKeysym(evt);
+ var keysym = obj ? obj.keysym : null;
+
+ // first, apply the event itself, if relevant
+ if (keysym !== null && state[keysym] !== undefined) {
+ state[keysym] = down;
+ }
+ return sync(evt, keysym);
+ }
+
+ return {
+ // sync on the appropriate keyboard event
+ keydown: function(evt) { return syncKeyEvent(evt, true);},
+ keyup: function(evt) { return syncKeyEvent(evt, false);},
+ // Call this with a non-keyboard event (such as mouse events) to use its modifier state to synchronize anyway
+ syncAny: function(evt) { return sync(evt);},
+
+ // is a shortcut modifier down?
+ hasShortcutModifier: function() { return hasShortcutModifier(charModifier, state); },
+ // if a char modifier is down, return the keys it consists of, otherwise return null
+ activeCharModifier: function() { return hasCharModifier(charModifier, state) ? charModifier : null; }
+ };
+ }
+
+ // Get a key ID from a keyboard event
+ // May be a string or an integer depending on the available properties
+ function getKey(evt){
+ if ('keyCode' in evt && 'key' in evt) {
+ return evt.key + ':' + evt.keyCode;
+ }
+ else if ('keyCode' in evt) {
+ return evt.keyCode;
+ }
+ else {
+ return evt.key;
+ }
+ }
+
+ // Get the most reliable keysym value we can get from a key event
+ // if char/charCode is available, prefer those, otherwise fall back to key/keyCode/which
+ function getKeysym(evt){
+ var codepoint;
+ if (evt.char && evt.char.length === 1) {
+ codepoint = evt.char.charCodeAt();
+ }
+ else if (evt.charCode) {
+ codepoint = evt.charCode;
+ }
+ else if (evt.keyCode && evt.type === 'keypress') {
+ // IE10 stores the char code as keyCode, and has no other useful properties
+ codepoint = evt.keyCode;
+ }
+ if (codepoint) {
+ var res = keysyms.fromUnicode(substituteCodepoint(codepoint));
+ if (res) {
+ return res;
+ }
+ }
+ // we could check evt.key here.
+ // Legal values are defined in http://www.w3.org/TR/DOM-Level-3-Events/#key-values-list,
+ // so we "just" need to map them to keysym, but AFAIK this is only available in IE10, which also provides evt.key
+ // so we don't *need* it yet
+ if (evt.keyCode) {
+ return keysyms.lookup(keysymFromKeyCode(evt.keyCode, evt.shiftKey));
+ }
+ if (evt.which) {
+ return keysyms.lookup(keysymFromKeyCode(evt.which, evt.shiftKey));
+ }
+ return null;
+ }
+
+ // Given a keycode, try to predict which keysym it might be.
+ // If the keycode is unknown, null is returned.
+ function keysymFromKeyCode(keycode, shiftPressed) {
+ if (typeof(keycode) !== 'number') {
+ return null;
+ }
+ // won't be accurate for azerty
+ if (keycode >= 0x30 && keycode <= 0x39) {
+ return keycode; // digit
+ }
+ if (keycode >= 0x41 && keycode <= 0x5a) {
+ // remap to lowercase unless shift is down
+ return shiftPressed ? keycode : keycode + 32; // A-Z
+ }
+ if (keycode >= 0x60 && keycode <= 0x69) {
+ return XK_KP_0 + (keycode - 0x60); // numpad 0-9
+ }
+
+ switch(keycode) {
+ case 0x20: return XK_space;
+ case 0x6a: return XK_KP_Multiply;
+ case 0x6b: return XK_KP_Add;
+ case 0x6c: return XK_KP_Separator;
+ case 0x6d: return XK_KP_Subtract;
+ case 0x6e: return XK_KP_Decimal;
+ case 0x6f: return XK_KP_Divide;
+ case 0xbb: return XK_plus;
+ case 0xbc: return XK_comma;
+ case 0xbd: return XK_minus;
+ case 0xbe: return XK_period;
+ }
+
+ return nonCharacterKey({keyCode: keycode});
+ }
+
+ // if the key is a known non-character key (any key which doesn't generate character data)
+ // return its keysym value. Otherwise return null
+ function nonCharacterKey(evt) {
+ // evt.key not implemented yet
+ if (!evt.keyCode) { return null; }
+ var keycode = evt.keyCode;
+
+ if (keycode >= 0x70 && keycode <= 0x87) {
+ return XK_F1 + keycode - 0x70; // F1-F24
+ }
+ switch (keycode) {
+
+ case 8 : return XK_BackSpace;
+ case 13 : return XK_Return;
+
+ case 9 : return XK_Tab;
+
+ case 27 : return XK_Escape;
+ case 46 : return XK_Delete;
+
+ case 36 : return XK_Home;
+ case 35 : return XK_End;
+ case 33 : return XK_Page_Up;
+ case 34 : return XK_Page_Down;
+ case 45 : return XK_Insert;
+
+ case 37 : return XK_Left;
+ case 38 : return XK_Up;
+ case 39 : return XK_Right;
+ case 40 : return XK_Down;
+
+ case 16 : return XK_Shift_L;
+ case 17 : return XK_Control_L;
+ case 18 : return XK_Alt_L; // also: Option-key on Mac
+
+ case 224 : return XK_Meta_L;
+ case 225 : return XK_ISO_Level3_Shift; // AltGr
+ case 91 : return XK_Super_L; // also: Windows-key
+ case 92 : return XK_Super_R; // also: Windows-key
+ case 93 : return XK_Menu; // also: Windows-Menu, Command on Mac
+ default: return null;
+ }
+ }
+ return {
+ hasShortcutModifier : hasShortcutModifier,
+ hasCharModifier : hasCharModifier,
+ ModifierSync : ModifierSync,
+ getKey : getKey,
+ getKeysym : getKeysym,
+ keysymFromKeyCode : keysymFromKeyCode,
+ nonCharacterKey : nonCharacterKey,
+ substituteCodepoint : substituteCodepoint
+ };
+})();
+
+// Takes a DOM keyboard event and:
+// - determines which keysym it represents
+// - determines a keyId identifying the key that was pressed (corresponding to the key/keyCode properties on the DOM event)
+// - synthesizes events to synchronize modifier key state between which modifiers are actually down, and which we thought were down
+// - marks each event with an 'escape' property if a modifier was down which should be "escaped"
+// - generates a "stall" event in cases where it might be necessary to wait and see if a keypress event follows a keydown
+// This information is collected into an object which is passed to the next() function. (one call per event)
+function KeyEventDecoder(modifierState, next) {
+ "use strict";
+ function sendAll(evts) {
+ for (var i = 0; i < evts.length; ++i) {
+ next(evts[i]);
+ }
+ }
+ function process(evt, type) {
+ var result = {type: type};
+ var keyId = kbdUtil.getKey(evt);
+ if (keyId) {
+ result.keyId = keyId;
+ }
+
+ var keysym = kbdUtil.getKeysym(evt);
+
+ var hasModifier = modifierState.hasShortcutModifier() || !!modifierState.activeCharModifier();
+ // Is this a case where we have to decide on the keysym right away, rather than waiting for the keypress?
+ // "special" keys like enter, tab or backspace don't send keypress events,
+ // and some browsers don't send keypresses at all if a modifier is down
+ if (keysym && (type !== 'keydown' || kbdUtil.nonCharacterKey(evt) || hasModifier)) {
+ result.keysym = keysym;
+ }
+
+ var isShift = evt.keyCode === 0x10 || evt.key === 'Shift';
+
+ // Should we prevent the browser from handling the event?
+ // Doing so on a keydown (in most browsers) prevents keypress from being generated
+ // so only do that if we have to.
+ var suppress = !isShift && (type !== 'keydown' || modifierState.hasShortcutModifier() || !!kbdUtil.nonCharacterKey(evt));
+
+ // If a char modifier is down on a keydown, we need to insert a stall,
+ // so VerifyCharModifier knows to wait and see if a keypress is comnig
+ var stall = type === 'keydown' && modifierState.activeCharModifier() && !kbdUtil.nonCharacterKey(evt);
+
+ // if a char modifier is pressed, get the keys it consists of (on Windows, AltGr is equivalent to Ctrl+Alt)
+ var active = modifierState.activeCharModifier();
+
+ // If we have a char modifier down, and we're able to determine a keysym reliably
+ // then (a) we know to treat the modifier as a char modifier,
+ // and (b) we'll have to "escape" the modifier to undo the modifier when sending the char.
+ if (active && keysym) {
+ var isCharModifier = false;
+ for (var i = 0; i < active.length; ++i) {
+ if (active[i] === keysym.keysym) {
+ isCharModifier = true;
+ }
+ }
+ if (type === 'keypress' && !isCharModifier) {
+ result.escape = modifierState.activeCharModifier();
+ }
+ }
+
+ if (stall) {
+ // insert a fake "stall" event
+ next({type: 'stall'});
+ }
+ next(result);
+
+ return suppress;
+ }
+
+ return {
+ keydown: function(evt) {
+ sendAll(modifierState.keydown(evt));
+ return process(evt, 'keydown');
+ },
+ keypress: function(evt) {
+ return process(evt, 'keypress');
+ },
+ keyup: function(evt) {
+ sendAll(modifierState.keyup(evt));
+ return process(evt, 'keyup');
+ },
+ syncModifiers: function(evt) {
+ sendAll(modifierState.syncAny(evt));
+ },
+ releaseAll: function() { next({type: 'releaseall'}); }
+ };
+}
+
+// Combines keydown and keypress events where necessary to handle char modifiers.
+// On some OS'es, a char modifier is sometimes used as a shortcut modifier.
+// For example, on Windows, AltGr is synonymous with Ctrl-Alt. On a Danish keyboard layout, AltGr-2 yields a @, but Ctrl-Alt-D does nothing
+// so when used with the '2' key, Ctrl-Alt counts as a char modifier (and should be escaped), but when used with 'D', it does not.
+// The only way we can distinguish these cases is to wait and see if a keypress event arrives
+// When we receive a "stall" event, wait a few ms before processing the next keydown. If a keypress has also arrived, merge the two
+function VerifyCharModifier(next) {
+ "use strict";
+ var queue = [];
+ var timer = null;
+ function process() {
+ if (timer) {
+ return;
+ }
+
+ var delayProcess = function () {
+ clearTimeout(timer);
+ timer = null;
+ process();
+ };
+
+ while (queue.length !== 0) {
+ var cur = queue[0];
+ queue = queue.splice(1);
+ switch (cur.type) {
+ case 'stall':
+ // insert a delay before processing available events.
+ /* jshint loopfunc: true */
+ timer = setTimeout(delayProcess, 5);
+ /* jshint loopfunc: false */
+ return;
+ case 'keydown':
+ // is the next element a keypress? Then we should merge the two
+ if (queue.length !== 0 && queue[0].type === 'keypress') {
+ // Firefox sends keypress even when no char is generated.
+ // so, if keypress keysym is the same as we'd have guessed from keydown,
+ // the modifier didn't have any effect, and should not be escaped
+ if (queue[0].escape && (!cur.keysym || cur.keysym.keysym !== queue[0].keysym.keysym)) {
+ cur.escape = queue[0].escape;
+ }
+ cur.keysym = queue[0].keysym;
+ queue = queue.splice(1);
+ }
+ break;
+ }
+
+ // swallow stall events, and pass all others to the next stage
+ if (cur.type !== 'stall') {
+ next(cur);
+ }
+ }
+ }
+ return function(evt) {
+ queue.push(evt);
+ process();
+ };
+}
+
+// Keeps track of which keys we (and the server) believe are down
+// When a keyup is received, match it against this list, to determine the corresponding keysym(s)
+// in some cases, a single key may produce multiple keysyms, so the corresponding keyup event must release all of these chars
+// key repeat events should be merged into a single entry.
+// Because we can't always identify which entry a keydown or keyup event corresponds to, we sometimes have to guess
+function TrackKeyState(next) {
+ "use strict";
+ var state = [];
+
+ return function (evt) {
+ var last = state.length !== 0 ? state[state.length-1] : null;
+
+ switch (evt.type) {
+ case 'keydown':
+ // insert a new entry if last seen key was different.
+ if (!last || !evt.keyId || last.keyId !== evt.keyId) {
+ last = {keyId: evt.keyId, keysyms: {}};
+ state.push(last);
+ }
+ if (evt.keysym) {
+ // make sure last event contains this keysym (a single "logical" keyevent
+ // can cause multiple key events to be sent to the VNC server)
+ last.keysyms[evt.keysym.keysym] = evt.keysym;
+ last.ignoreKeyPress = true;
+ next(evt);
+ }
+ break;
+ case 'keypress':
+ if (!last) {
+ last = {keyId: evt.keyId, keysyms: {}};
+ state.push(last);
+ }
+ if (!evt.keysym) {
+ console.log('keypress with no keysym:', evt);
+ }
+
+ // If we didn't expect a keypress, and already sent a keydown to the VNC server
+ // based on the keydown, make sure to skip this event.
+ if (evt.keysym && !last.ignoreKeyPress) {
+ last.keysyms[evt.keysym.keysym] = evt.keysym;
+ evt.type = 'keydown';
+ next(evt);
+ }
+ break;
+ case 'keyup':
+ if (state.length === 0) {
+ return;
+ }
+ var idx = null;
+ // do we have a matching key tracked as being down?
+ for (var i = 0; i !== state.length; ++i) {
+ if (state[i].keyId === evt.keyId) {
+ idx = i;
+ break;
+ }
+ }
+ // if we couldn't find a match (it happens), assume it was the last key pressed
+ if (idx === null) {
+ idx = state.length - 1;
+ }
+
+ var item = state.splice(idx, 1)[0];
+ // for each keysym tracked by this key entry, clone the current event and override the keysym
+ var clone = (function(){
+ function Clone(){}
+ return function (obj) { Clone.prototype=obj; return new Clone(); };
+ }());
+ for (var key in item.keysyms) {
+ var out = clone(evt);
+ out.keysym = item.keysyms[key];
+ next(out);
+ }
+ break;
+ case 'releaseall':
+ /* jshint shadow: true */
+ for (var i = 0; i < state.length; ++i) {
+ for (var key in state[i].keysyms) {
+ var keysym = state[i].keysyms[key];
+ next({keyId: 0, keysym: keysym, type: 'keyup'});
+ }
+ }
+ /* jshint shadow: false */
+ state = [];
+ }
+ };
+}
+
+// Handles "escaping" of modifiers: if a char modifier is used to produce a keysym (such as AltGr-2 to generate an @),
+// then the modifier must be "undone" before sending the @, and "redone" afterwards.
+function EscapeModifiers(next) {
+ "use strict";
+ return function(evt) {
+ if (evt.type !== 'keydown' || evt.escape === undefined) {
+ next(evt);
+ return;
+ }
+ // undo modifiers
+ for (var i = 0; i < evt.escape.length; ++i) {
+ next({type: 'keyup', keyId: 0, keysym: keysyms.lookup(evt.escape[i])});
+ }
+ // send the character event
+ next(evt);
+ // redo modifiers
+ /* jshint shadow: true */
+ for (var i = 0; i < evt.escape.length; ++i) {
+ next({type: 'keydown', keyId: 0, keysym: keysyms.lookup(evt.escape[i])});
+ }
+ /* jshint shadow: false */
+ };
+}
diff --git a/public/novnc/include/keysym.js b/public/novnc/include/keysym.js
new file mode 100644
index 00000000..58b107c0
--- /dev/null
+++ b/public/novnc/include/keysym.js
@@ -0,0 +1,378 @@
+var XK_VoidSymbol = 0xffffff, /* Void symbol */
+
+XK_BackSpace = 0xff08, /* Back space, back char */
+XK_Tab = 0xff09,
+XK_Linefeed = 0xff0a, /* Linefeed, LF */
+XK_Clear = 0xff0b,
+XK_Return = 0xff0d, /* Return, enter */
+XK_Pause = 0xff13, /* Pause, hold */
+XK_Scroll_Lock = 0xff14,
+XK_Sys_Req = 0xff15,
+XK_Escape = 0xff1b,
+XK_Delete = 0xffff, /* Delete, rubout */
+
+/* Cursor control & motion */
+
+XK_Home = 0xff50,
+XK_Left = 0xff51, /* Move left, left arrow */
+XK_Up = 0xff52, /* Move up, up arrow */
+XK_Right = 0xff53, /* Move right, right arrow */
+XK_Down = 0xff54, /* Move down, down arrow */
+XK_Prior = 0xff55, /* Prior, previous */
+XK_Page_Up = 0xff55,
+XK_Next = 0xff56, /* Next */
+XK_Page_Down = 0xff56,
+XK_End = 0xff57, /* EOL */
+XK_Begin = 0xff58, /* BOL */
+
+
+/* Misc functions */
+
+XK_Select = 0xff60, /* Select, mark */
+XK_Print = 0xff61,
+XK_Execute = 0xff62, /* Execute, run, do */
+XK_Insert = 0xff63, /* Insert, insert here */
+XK_Undo = 0xff65,
+XK_Redo = 0xff66, /* Redo, again */
+XK_Menu = 0xff67,
+XK_Find = 0xff68, /* Find, search */
+XK_Cancel = 0xff69, /* Cancel, stop, abort, exit */
+XK_Help = 0xff6a, /* Help */
+XK_Break = 0xff6b,
+XK_Mode_switch = 0xff7e, /* Character set switch */
+XK_script_switch = 0xff7e, /* Alias for mode_switch */
+XK_Num_Lock = 0xff7f,
+
+/* Keypad functions, keypad numbers cleverly chosen to map to ASCII */
+
+XK_KP_Space = 0xff80, /* Space */
+XK_KP_Tab = 0xff89,
+XK_KP_Enter = 0xff8d, /* Enter */
+XK_KP_F1 = 0xff91, /* PF1, KP_A, ... */
+XK_KP_F2 = 0xff92,
+XK_KP_F3 = 0xff93,
+XK_KP_F4 = 0xff94,
+XK_KP_Home = 0xff95,
+XK_KP_Left = 0xff96,
+XK_KP_Up = 0xff97,
+XK_KP_Right = 0xff98,
+XK_KP_Down = 0xff99,
+XK_KP_Prior = 0xff9a,
+XK_KP_Page_Up = 0xff9a,
+XK_KP_Next = 0xff9b,
+XK_KP_Page_Down = 0xff9b,
+XK_KP_End = 0xff9c,
+XK_KP_Begin = 0xff9d,
+XK_KP_Insert = 0xff9e,
+XK_KP_Delete = 0xff9f,
+XK_KP_Equal = 0xffbd, /* Equals */
+XK_KP_Multiply = 0xffaa,
+XK_KP_Add = 0xffab,
+XK_KP_Separator = 0xffac, /* Separator, often comma */
+XK_KP_Subtract = 0xffad,
+XK_KP_Decimal = 0xffae,
+XK_KP_Divide = 0xffaf,
+
+XK_KP_0 = 0xffb0,
+XK_KP_1 = 0xffb1,
+XK_KP_2 = 0xffb2,
+XK_KP_3 = 0xffb3,
+XK_KP_4 = 0xffb4,
+XK_KP_5 = 0xffb5,
+XK_KP_6 = 0xffb6,
+XK_KP_7 = 0xffb7,
+XK_KP_8 = 0xffb8,
+XK_KP_9 = 0xffb9,
+
+/*
+ * Auxiliary functions; note the duplicate definitions for left and right
+ * function keys; Sun keyboards and a few other manufacturers have such
+ * function key groups on the left and/or right sides of the keyboard.
+ * We've not found a keyboard with more than 35 function keys total.
+ */
+
+XK_F1 = 0xffbe,
+XK_F2 = 0xffbf,
+XK_F3 = 0xffc0,
+XK_F4 = 0xffc1,
+XK_F5 = 0xffc2,
+XK_F6 = 0xffc3,
+XK_F7 = 0xffc4,
+XK_F8 = 0xffc5,
+XK_F9 = 0xffc6,
+XK_F10 = 0xffc7,
+XK_F11 = 0xffc8,
+XK_L1 = 0xffc8,
+XK_F12 = 0xffc9,
+XK_L2 = 0xffc9,
+XK_F13 = 0xffca,
+XK_L3 = 0xffca,
+XK_F14 = 0xffcb,
+XK_L4 = 0xffcb,
+XK_F15 = 0xffcc,
+XK_L5 = 0xffcc,
+XK_F16 = 0xffcd,
+XK_L6 = 0xffcd,
+XK_F17 = 0xffce,
+XK_L7 = 0xffce,
+XK_F18 = 0xffcf,
+XK_L8 = 0xffcf,
+XK_F19 = 0xffd0,
+XK_L9 = 0xffd0,
+XK_F20 = 0xffd1,
+XK_L10 = 0xffd1,
+XK_F21 = 0xffd2,
+XK_R1 = 0xffd2,
+XK_F22 = 0xffd3,
+XK_R2 = 0xffd3,
+XK_F23 = 0xffd4,
+XK_R3 = 0xffd4,
+XK_F24 = 0xffd5,
+XK_R4 = 0xffd5,
+XK_F25 = 0xffd6,
+XK_R5 = 0xffd6,
+XK_F26 = 0xffd7,
+XK_R6 = 0xffd7,
+XK_F27 = 0xffd8,
+XK_R7 = 0xffd8,
+XK_F28 = 0xffd9,
+XK_R8 = 0xffd9,
+XK_F29 = 0xffda,
+XK_R9 = 0xffda,
+XK_F30 = 0xffdb,
+XK_R10 = 0xffdb,
+XK_F31 = 0xffdc,
+XK_R11 = 0xffdc,
+XK_F32 = 0xffdd,
+XK_R12 = 0xffdd,
+XK_F33 = 0xffde,
+XK_R13 = 0xffde,
+XK_F34 = 0xffdf,
+XK_R14 = 0xffdf,
+XK_F35 = 0xffe0,
+XK_R15 = 0xffe0,
+
+/* Modifiers */
+
+XK_Shift_L = 0xffe1, /* Left shift */
+XK_Shift_R = 0xffe2, /* Right shift */
+XK_Control_L = 0xffe3, /* Left control */
+XK_Control_R = 0xffe4, /* Right control */
+XK_Caps_Lock = 0xffe5, /* Caps lock */
+XK_Shift_Lock = 0xffe6, /* Shift lock */
+
+XK_Meta_L = 0xffe7, /* Left meta */
+XK_Meta_R = 0xffe8, /* Right meta */
+XK_Alt_L = 0xffe9, /* Left alt */
+XK_Alt_R = 0xffea, /* Right alt */
+XK_Super_L = 0xffeb, /* Left super */
+XK_Super_R = 0xffec, /* Right super */
+XK_Hyper_L = 0xffed, /* Left hyper */
+XK_Hyper_R = 0xffee, /* Right hyper */
+
+XK_ISO_Level3_Shift = 0xfe03, /* AltGr */
+
+/*
+ * Latin 1
+ * (ISO/IEC 8859-1 = Unicode U+0020..U+00FF)
+ * Byte 3 = 0
+ */
+
+XK_space = 0x0020, /* U+0020 SPACE */
+XK_exclam = 0x0021, /* U+0021 EXCLAMATION MARK */
+XK_quotedbl = 0x0022, /* U+0022 QUOTATION MARK */
+XK_numbersign = 0x0023, /* U+0023 NUMBER SIGN */
+XK_dollar = 0x0024, /* U+0024 DOLLAR SIGN */
+XK_percent = 0x0025, /* U+0025 PERCENT SIGN */
+XK_ampersand = 0x0026, /* U+0026 AMPERSAND */
+XK_apostrophe = 0x0027, /* U+0027 APOSTROPHE */
+XK_quoteright = 0x0027, /* deprecated */
+XK_parenleft = 0x0028, /* U+0028 LEFT PARENTHESIS */
+XK_parenright = 0x0029, /* U+0029 RIGHT PARENTHESIS */
+XK_asterisk = 0x002a, /* U+002A ASTERISK */
+XK_plus = 0x002b, /* U+002B PLUS SIGN */
+XK_comma = 0x002c, /* U+002C COMMA */
+XK_minus = 0x002d, /* U+002D HYPHEN-MINUS */
+XK_period = 0x002e, /* U+002E FULL STOP */
+XK_slash = 0x002f, /* U+002F SOLIDUS */
+XK_0 = 0x0030, /* U+0030 DIGIT ZERO */
+XK_1 = 0x0031, /* U+0031 DIGIT ONE */
+XK_2 = 0x0032, /* U+0032 DIGIT TWO */
+XK_3 = 0x0033, /* U+0033 DIGIT THREE */
+XK_4 = 0x0034, /* U+0034 DIGIT FOUR */
+XK_5 = 0x0035, /* U+0035 DIGIT FIVE */
+XK_6 = 0x0036, /* U+0036 DIGIT SIX */
+XK_7 = 0x0037, /* U+0037 DIGIT SEVEN */
+XK_8 = 0x0038, /* U+0038 DIGIT EIGHT */
+XK_9 = 0x0039, /* U+0039 DIGIT NINE */
+XK_colon = 0x003a, /* U+003A COLON */
+XK_semicolon = 0x003b, /* U+003B SEMICOLON */
+XK_less = 0x003c, /* U+003C LESS-THAN SIGN */
+XK_equal = 0x003d, /* U+003D EQUALS SIGN */
+XK_greater = 0x003e, /* U+003E GREATER-THAN SIGN */
+XK_question = 0x003f, /* U+003F QUESTION MARK */
+XK_at = 0x0040, /* U+0040 COMMERCIAL AT */
+XK_A = 0x0041, /* U+0041 LATIN CAPITAL LETTER A */
+XK_B = 0x0042, /* U+0042 LATIN CAPITAL LETTER B */
+XK_C = 0x0043, /* U+0043 LATIN CAPITAL LETTER C */
+XK_D = 0x0044, /* U+0044 LATIN CAPITAL LETTER D */
+XK_E = 0x0045, /* U+0045 LATIN CAPITAL LETTER E */
+XK_F = 0x0046, /* U+0046 LATIN CAPITAL LETTER F */
+XK_G = 0x0047, /* U+0047 LATIN CAPITAL LETTER G */
+XK_H = 0x0048, /* U+0048 LATIN CAPITAL LETTER H */
+XK_I = 0x0049, /* U+0049 LATIN CAPITAL LETTER I */
+XK_J = 0x004a, /* U+004A LATIN CAPITAL LETTER J */
+XK_K = 0x004b, /* U+004B LATIN CAPITAL LETTER K */
+XK_L = 0x004c, /* U+004C LATIN CAPITAL LETTER L */
+XK_M = 0x004d, /* U+004D LATIN CAPITAL LETTER M */
+XK_N = 0x004e, /* U+004E LATIN CAPITAL LETTER N */
+XK_O = 0x004f, /* U+004F LATIN CAPITAL LETTER O */
+XK_P = 0x0050, /* U+0050 LATIN CAPITAL LETTER P */
+XK_Q = 0x0051, /* U+0051 LATIN CAPITAL LETTER Q */
+XK_R = 0x0052, /* U+0052 LATIN CAPITAL LETTER R */
+XK_S = 0x0053, /* U+0053 LATIN CAPITAL LETTER S */
+XK_T = 0x0054, /* U+0054 LATIN CAPITAL LETTER T */
+XK_U = 0x0055, /* U+0055 LATIN CAPITAL LETTER U */
+XK_V = 0x0056, /* U+0056 LATIN CAPITAL LETTER V */
+XK_W = 0x0057, /* U+0057 LATIN CAPITAL LETTER W */
+XK_X = 0x0058, /* U+0058 LATIN CAPITAL LETTER X */
+XK_Y = 0x0059, /* U+0059 LATIN CAPITAL LETTER Y */
+XK_Z = 0x005a, /* U+005A LATIN CAPITAL LETTER Z */
+XK_bracketleft = 0x005b, /* U+005B LEFT SQUARE BRACKET */
+XK_backslash = 0x005c, /* U+005C REVERSE SOLIDUS */
+XK_bracketright = 0x005d, /* U+005D RIGHT SQUARE BRACKET */
+XK_asciicircum = 0x005e, /* U+005E CIRCUMFLEX ACCENT */
+XK_underscore = 0x005f, /* U+005F LOW LINE */
+XK_grave = 0x0060, /* U+0060 GRAVE ACCENT */
+XK_quoteleft = 0x0060, /* deprecated */
+XK_a = 0x0061, /* U+0061 LATIN SMALL LETTER A */
+XK_b = 0x0062, /* U+0062 LATIN SMALL LETTER B */
+XK_c = 0x0063, /* U+0063 LATIN SMALL LETTER C */
+XK_d = 0x0064, /* U+0064 LATIN SMALL LETTER D */
+XK_e = 0x0065, /* U+0065 LATIN SMALL LETTER E */
+XK_f = 0x0066, /* U+0066 LATIN SMALL LETTER F */
+XK_g = 0x0067, /* U+0067 LATIN SMALL LETTER G */
+XK_h = 0x0068, /* U+0068 LATIN SMALL LETTER H */
+XK_i = 0x0069, /* U+0069 LATIN SMALL LETTER I */
+XK_j = 0x006a, /* U+006A LATIN SMALL LETTER J */
+XK_k = 0x006b, /* U+006B LATIN SMALL LETTER K */
+XK_l = 0x006c, /* U+006C LATIN SMALL LETTER L */
+XK_m = 0x006d, /* U+006D LATIN SMALL LETTER M */
+XK_n = 0x006e, /* U+006E LATIN SMALL LETTER N */
+XK_o = 0x006f, /* U+006F LATIN SMALL LETTER O */
+XK_p = 0x0070, /* U+0070 LATIN SMALL LETTER P */
+XK_q = 0x0071, /* U+0071 LATIN SMALL LETTER Q */
+XK_r = 0x0072, /* U+0072 LATIN SMALL LETTER R */
+XK_s = 0x0073, /* U+0073 LATIN SMALL LETTER S */
+XK_t = 0x0074, /* U+0074 LATIN SMALL LETTER T */
+XK_u = 0x0075, /* U+0075 LATIN SMALL LETTER U */
+XK_v = 0x0076, /* U+0076 LATIN SMALL LETTER V */
+XK_w = 0x0077, /* U+0077 LATIN SMALL LETTER W */
+XK_x = 0x0078, /* U+0078 LATIN SMALL LETTER X */
+XK_y = 0x0079, /* U+0079 LATIN SMALL LETTER Y */
+XK_z = 0x007a, /* U+007A LATIN SMALL LETTER Z */
+XK_braceleft = 0x007b, /* U+007B LEFT CURLY BRACKET */
+XK_bar = 0x007c, /* U+007C VERTICAL LINE */
+XK_braceright = 0x007d, /* U+007D RIGHT CURLY BRACKET */
+XK_asciitilde = 0x007e, /* U+007E TILDE */
+
+XK_nobreakspace = 0x00a0, /* U+00A0 NO-BREAK SPACE */
+XK_exclamdown = 0x00a1, /* U+00A1 INVERTED EXCLAMATION MARK */
+XK_cent = 0x00a2, /* U+00A2 CENT SIGN */
+XK_sterling = 0x00a3, /* U+00A3 POUND SIGN */
+XK_currency = 0x00a4, /* U+00A4 CURRENCY SIGN */
+XK_yen = 0x00a5, /* U+00A5 YEN SIGN */
+XK_brokenbar = 0x00a6, /* U+00A6 BROKEN BAR */
+XK_section = 0x00a7, /* U+00A7 SECTION SIGN */
+XK_diaeresis = 0x00a8, /* U+00A8 DIAERESIS */
+XK_copyright = 0x00a9, /* U+00A9 COPYRIGHT SIGN */
+XK_ordfeminine = 0x00aa, /* U+00AA FEMININE ORDINAL INDICATOR */
+XK_guillemotleft = 0x00ab, /* U+00AB LEFT-POINTING DOUBLE ANGLE QUOTATION MARK */
+XK_notsign = 0x00ac, /* U+00AC NOT SIGN */
+XK_hyphen = 0x00ad, /* U+00AD SOFT HYPHEN */
+XK_registered = 0x00ae, /* U+00AE REGISTERED SIGN */
+XK_macron = 0x00af, /* U+00AF MACRON */
+XK_degree = 0x00b0, /* U+00B0 DEGREE SIGN */
+XK_plusminus = 0x00b1, /* U+00B1 PLUS-MINUS SIGN */
+XK_twosuperior = 0x00b2, /* U+00B2 SUPERSCRIPT TWO */
+XK_threesuperior = 0x00b3, /* U+00B3 SUPERSCRIPT THREE */
+XK_acute = 0x00b4, /* U+00B4 ACUTE ACCENT */
+XK_mu = 0x00b5, /* U+00B5 MICRO SIGN */
+XK_paragraph = 0x00b6, /* U+00B6 PILCROW SIGN */
+XK_periodcentered = 0x00b7, /* U+00B7 MIDDLE DOT */
+XK_cedilla = 0x00b8, /* U+00B8 CEDILLA */
+XK_onesuperior = 0x00b9, /* U+00B9 SUPERSCRIPT ONE */
+XK_masculine = 0x00ba, /* U+00BA MASCULINE ORDINAL INDICATOR */
+XK_guillemotright = 0x00bb, /* U+00BB RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK */
+XK_onequarter = 0x00bc, /* U+00BC VULGAR FRACTION ONE QUARTER */
+XK_onehalf = 0x00bd, /* U+00BD VULGAR FRACTION ONE HALF */
+XK_threequarters = 0x00be, /* U+00BE VULGAR FRACTION THREE QUARTERS */
+XK_questiondown = 0x00bf, /* U+00BF INVERTED QUESTION MARK */
+XK_Agrave = 0x00c0, /* U+00C0 LATIN CAPITAL LETTER A WITH GRAVE */
+XK_Aacute = 0x00c1, /* U+00C1 LATIN CAPITAL LETTER A WITH ACUTE */
+XK_Acircumflex = 0x00c2, /* U+00C2 LATIN CAPITAL LETTER A WITH CIRCUMFLEX */
+XK_Atilde = 0x00c3, /* U+00C3 LATIN CAPITAL LETTER A WITH TILDE */
+XK_Adiaeresis = 0x00c4, /* U+00C4 LATIN CAPITAL LETTER A WITH DIAERESIS */
+XK_Aring = 0x00c5, /* U+00C5 LATIN CAPITAL LETTER A WITH RING ABOVE */
+XK_AE = 0x00c6, /* U+00C6 LATIN CAPITAL LETTER AE */
+XK_Ccedilla = 0x00c7, /* U+00C7 LATIN CAPITAL LETTER C WITH CEDILLA */
+XK_Egrave = 0x00c8, /* U+00C8 LATIN CAPITAL LETTER E WITH GRAVE */
+XK_Eacute = 0x00c9, /* U+00C9 LATIN CAPITAL LETTER E WITH ACUTE */
+XK_Ecircumflex = 0x00ca, /* U+00CA LATIN CAPITAL LETTER E WITH CIRCUMFLEX */
+XK_Ediaeresis = 0x00cb, /* U+00CB LATIN CAPITAL LETTER E WITH DIAERESIS */
+XK_Igrave = 0x00cc, /* U+00CC LATIN CAPITAL LETTER I WITH GRAVE */
+XK_Iacute = 0x00cd, /* U+00CD LATIN CAPITAL LETTER I WITH ACUTE */
+XK_Icircumflex = 0x00ce, /* U+00CE LATIN CAPITAL LETTER I WITH CIRCUMFLEX */
+XK_Idiaeresis = 0x00cf, /* U+00CF LATIN CAPITAL LETTER I WITH DIAERESIS */
+XK_ETH = 0x00d0, /* U+00D0 LATIN CAPITAL LETTER ETH */
+XK_Eth = 0x00d0, /* deprecated */
+XK_Ntilde = 0x00d1, /* U+00D1 LATIN CAPITAL LETTER N WITH TILDE */
+XK_Ograve = 0x00d2, /* U+00D2 LATIN CAPITAL LETTER O WITH GRAVE */
+XK_Oacute = 0x00d3, /* U+00D3 LATIN CAPITAL LETTER O WITH ACUTE */
+XK_Ocircumflex = 0x00d4, /* U+00D4 LATIN CAPITAL LETTER O WITH CIRCUMFLEX */
+XK_Otilde = 0x00d5, /* U+00D5 LATIN CAPITAL LETTER O WITH TILDE */
+XK_Odiaeresis = 0x00d6, /* U+00D6 LATIN CAPITAL LETTER O WITH DIAERESIS */
+XK_multiply = 0x00d7, /* U+00D7 MULTIPLICATION SIGN */
+XK_Oslash = 0x00d8, /* U+00D8 LATIN CAPITAL LETTER O WITH STROKE */
+XK_Ooblique = 0x00d8, /* U+00D8 LATIN CAPITAL LETTER O WITH STROKE */
+XK_Ugrave = 0x00d9, /* U+00D9 LATIN CAPITAL LETTER U WITH GRAVE */
+XK_Uacute = 0x00da, /* U+00DA LATIN CAPITAL LETTER U WITH ACUTE */
+XK_Ucircumflex = 0x00db, /* U+00DB LATIN CAPITAL LETTER U WITH CIRCUMFLEX */
+XK_Udiaeresis = 0x00dc, /* U+00DC LATIN CAPITAL LETTER U WITH DIAERESIS */
+XK_Yacute = 0x00dd, /* U+00DD LATIN CAPITAL LETTER Y WITH ACUTE */
+XK_THORN = 0x00de, /* U+00DE LATIN CAPITAL LETTER THORN */
+XK_Thorn = 0x00de, /* deprecated */
+XK_ssharp = 0x00df, /* U+00DF LATIN SMALL LETTER SHARP S */
+XK_agrave = 0x00e0, /* U+00E0 LATIN SMALL LETTER A WITH GRAVE */
+XK_aacute = 0x00e1, /* U+00E1 LATIN SMALL LETTER A WITH ACUTE */
+XK_acircumflex = 0x00e2, /* U+00E2 LATIN SMALL LETTER A WITH CIRCUMFLEX */
+XK_atilde = 0x00e3, /* U+00E3 LATIN SMALL LETTER A WITH TILDE */
+XK_adiaeresis = 0x00e4, /* U+00E4 LATIN SMALL LETTER A WITH DIAERESIS */
+XK_aring = 0x00e5, /* U+00E5 LATIN SMALL LETTER A WITH RING ABOVE */
+XK_ae = 0x00e6, /* U+00E6 LATIN SMALL LETTER AE */
+XK_ccedilla = 0x00e7, /* U+00E7 LATIN SMALL LETTER C WITH CEDILLA */
+XK_egrave = 0x00e8, /* U+00E8 LATIN SMALL LETTER E WITH GRAVE */
+XK_eacute = 0x00e9, /* U+00E9 LATIN SMALL LETTER E WITH ACUTE */
+XK_ecircumflex = 0x00ea, /* U+00EA LATIN SMALL LETTER E WITH CIRCUMFLEX */
+XK_ediaeresis = 0x00eb, /* U+00EB LATIN SMALL LETTER E WITH DIAERESIS */
+XK_igrave = 0x00ec, /* U+00EC LATIN SMALL LETTER I WITH GRAVE */
+XK_iacute = 0x00ed, /* U+00ED LATIN SMALL LETTER I WITH ACUTE */
+XK_icircumflex = 0x00ee, /* U+00EE LATIN SMALL LETTER I WITH CIRCUMFLEX */
+XK_idiaeresis = 0x00ef, /* U+00EF LATIN SMALL LETTER I WITH DIAERESIS */
+XK_eth = 0x00f0, /* U+00F0 LATIN SMALL LETTER ETH */
+XK_ntilde = 0x00f1, /* U+00F1 LATIN SMALL LETTER N WITH TILDE */
+XK_ograve = 0x00f2, /* U+00F2 LATIN SMALL LETTER O WITH GRAVE */
+XK_oacute = 0x00f3, /* U+00F3 LATIN SMALL LETTER O WITH ACUTE */
+XK_ocircumflex = 0x00f4, /* U+00F4 LATIN SMALL LETTER O WITH CIRCUMFLEX */
+XK_otilde = 0x00f5, /* U+00F5 LATIN SMALL LETTER O WITH TILDE */
+XK_odiaeresis = 0x00f6, /* U+00F6 LATIN SMALL LETTER O WITH DIAERESIS */
+XK_division = 0x00f7, /* U+00F7 DIVISION SIGN */
+XK_oslash = 0x00f8, /* U+00F8 LATIN SMALL LETTER O WITH STROKE */
+XK_ooblique = 0x00f8, /* U+00F8 LATIN SMALL LETTER O WITH STROKE */
+XK_ugrave = 0x00f9, /* U+00F9 LATIN SMALL LETTER U WITH GRAVE */
+XK_uacute = 0x00fa, /* U+00FA LATIN SMALL LETTER U WITH ACUTE */
+XK_ucircumflex = 0x00fb, /* U+00FB LATIN SMALL LETTER U WITH CIRCUMFLEX */
+XK_udiaeresis = 0x00fc, /* U+00FC LATIN SMALL LETTER U WITH DIAERESIS */
+XK_yacute = 0x00fd, /* U+00FD LATIN SMALL LETTER Y WITH ACUTE */
+XK_thorn = 0x00fe, /* U+00FE LATIN SMALL LETTER THORN */
+XK_ydiaeresis = 0x00ff; /* U+00FF LATIN SMALL LETTER Y WITH DIAERESIS */
diff --git a/public/novnc/include/keysymdef.js b/public/novnc/include/keysymdef.js
new file mode 100644
index 00000000..f94445cf
--- /dev/null
+++ b/public/novnc/include/keysymdef.js
@@ -0,0 +1,15 @@
+// This file describes mappings from Unicode codepoints to the keysym values
+// (and optionally, key names) expected by the RFB protocol
+// How this file was generated:
+// node /Users/jalf/dev/mi/novnc/utils/parse.js /opt/X11/include/X11/keysymdef.h
+var keysyms = (function(){
+ "use strict";
+ var keynames = null;
+ var codepoints = {"32":32,"33":33,"34":34,"35":35,"36":36,"37":37,"38":38,"39":39,"40":40,"41":41,"42":42,"43":43,"44":44,"45":45,"46":46,"47":47,"48":48,"49":49,"50":50,"51":51,"52":52,"53":53,"54":54,"55":55,"56":56,"57":57,"58":58,"59":59,"60":60,"61":61,"62":62,"63":63,"64":64,"65":65,"66":66,"67":67,"68":68,"69":69,"70":70,"71":71,"72":72,"73":73,"74":74,"75":75,"76":76,"77":77,"78":78,"79":79,"80":80,"81":81,"82":82,"83":83,"84":84,"85":85,"86":86,"87":87,"88":88,"89":89,"90":90,"91":91,"92":92,"93":93,"94":94,"95":95,"96":96,"97":97,"98":98,"99":99,"100":100,"101":101,"102":102,"103":103,"104":104,"105":105,"106":106,"107":107,"108":108,"109":109,"110":110,"111":111,"112":112,"113":113,"114":114,"115":115,"116":116,"117":117,"118":118,"119":119,"120":120,"121":121,"122":122,"123":123,"124":124,"125":125,"126":126,"160":160,"161":161,"162":162,"163":163,"164":164,"165":165,"166":166,"167":167,"168":168,"169":169,"170":170,"171":171,"172":172,"173":173,"174":174,"175":175,"176":176,"177":177,"178":178,"179":179,"180":180,"181":181,"182":182,"183":183,"184":184,"185":185,"186":186,"187":187,"188":188,"189":189,"190":190,"191":191,"192":192,"193":193,"194":194,"195":195,"196":196,"197":197,"198":198,"199":199,"200":200,"201":201,"202":202,"203":203,"204":204,"205":205,"206":206,"207":207,"208":208,"209":209,"210":210,"211":211,"212":212,"213":213,"214":214,"215":215,"216":216,"217":217,"218":218,"219":219,"220":220,"221":221,"222":222,"223":223,"224":224,"225":225,"226":226,"227":227,"228":228,"229":229,"230":230,"231":231,"232":232,"233":233,"234":234,"235":235,"236":236,"237":237,"238":238,"239":239,"240":240,"241":241,"242":242,"243":243,"244":244,"245":245,"246":246,"247":247,"248":248,"249":249,"250":250,"251":251,"252":252,"253":253,"254":254,"255":255,"256":960,"257":992,"258":451,"259":483,"260":417,"261":433,"262":454,"263":486,"264":710,"265":742,"266":709,"267":741,"268":456,"269":488,"270":463,"271":495,"272":464,"273":496,"274":938,"275":954,"278":972,"279":1004,"280":458,"281":490,"282":460,"283":492,"284":728,"285":760,"286":683,"287":699,"288":725,"289":757,"290":939,"291":955,"292":678,"293":694,"294":673,"295":689,"296":933,"297":949,"298":975,"299":1007,"300":16777516,"301":16777517,"302":967,"303":999,"304":681,"305":697,"308":684,"309":700,"310":979,"311":1011,"312":930,"313":453,"314":485,"315":934,"316":950,"317":421,"318":437,"321":419,"322":435,"323":465,"324":497,"325":977,"326":1009,"327":466,"328":498,"330":957,"331":959,"332":978,"333":1010,"336":469,"337":501,"338":5052,"339":5053,"340":448,"341":480,"342":931,"343":947,"344":472,"345":504,"346":422,"347":438,"348":734,"349":766,"350":426,"351":442,"352":425,"353":441,"354":478,"355":510,"356":427,"357":443,"358":940,"359":956,"360":989,"361":1021,"362":990,"363":1022,"364":733,"365":765,"366":473,"367":505,"368":475,"369":507,"370":985,"371":1017,"372":16777588,"373":16777589,"374":16777590,"375":16777591,"376":5054,"377":428,"378":444,"379":431,"380":447,"381":430,"382":446,"399":16777615,"402":2294,"415":16777631,"416":16777632,"417":16777633,"431":16777647,"432":16777648,"437":16777653,"438":16777654,"439":16777655,"466":16777681,"486":16777702,"487":16777703,"601":16777817,"629":16777845,"658":16777874,"711":439,"728":418,"729":511,"731":434,"733":445,"901":1966,"902":1953,"904":1954,"905":1955,"906":1956,"908":1959,"910":1960,"911":1963,"912":1974,"913":1985,"914":1986,"915":1987,"916":1988,"917":1989,"918":1990,"919":1991,"920":1992,"921":1993,"922":1994,"923":1995,"924":1996,"925":1997,"926":1998,"927":1999,"928":2000,"929":2001,"931":2002,"932":2004,"933":2005,"934":2006,"935":2007,"936":2008,"937":2009,"938":1957,"939":1961,"940":1969,"941":1970,"942":1971,"943":1972,"944":1978,"945":2017,"946":2018,"947":2019,"948":2020,"949":2021,"950":2022,"951":2023,"952":2024,"953":2025,"954":2026,"955":2027,"956":2028,"957":2029,"958":2030,"959":2031,"960":2032,"961":2033,"962":2035,"963":2034,"964":2036,"965":2037,"966":2038,"967":2039,"968":2040,"969":2041,"970":1973,"971":1977,"972":1975,"973":1976,"974":1979,"1025":1715,"1026":1713,"1027":1714,"1028":1716,"1029":1717,"1030":1718,"1031":1719,"1032":1720,"1033":1721,"1034":1722,"1035":1723,"1036":1724,"1038":1726,"1039":1727,"1040":1761,"1041":1762,"1042":1783,"1043":1767,"1044":1764,"1045":1765,"1046":1782,"1047":1786,"1048":1769,"1049":1770,"1050":1771,"1051":1772,"1052":1773,"1053":1774,"1054":1775,"1055":1776,"1056":1778,"1057":1779,"1058":1780,"1059":1781,"1060":1766,"1061":1768,"1062":1763,"1063":1790,"1064":1787,"1065":1789,"1066":1791,"1067":1785,"1068":1784,"1069":1788,"1070":1760,"1071":1777,"1072":1729,"1073":1730,"1074":1751,"1075":1735,"1076":1732,"1077":1733,"1078":1750,"1079":1754,"1080":1737,"1081":1738,"1082":1739,"1083":1740,"1084":1741,"1085":1742,"1086":1743,"1087":1744,"1088":1746,"1089":1747,"1090":1748,"1091":1749,"1092":1734,"1093":1736,"1094":1731,"1095":1758,"1096":1755,"1097":1757,"1098":1759,"1099":1753,"1100":1752,"1101":1756,"1102":1728,"1103":1745,"1105":1699,"1106":1697,"1107":1698,"1108":1700,"1109":1701,"1110":1702,"1111":1703,"1112":1704,"1113":1705,"1114":1706,"1115":1707,"1116":1708,"1118":1710,"1119":1711,"1168":1725,"1169":1709,"1170":16778386,"1171":16778387,"1174":16778390,"1175":16778391,"1178":16778394,"1179":16778395,"1180":16778396,"1181":16778397,"1186":16778402,"1187":16778403,"1198":16778414,"1199":16778415,"1200":16778416,"1201":16778417,"1202":16778418,"1203":16778419,"1206":16778422,"1207":16778423,"1208":16778424,"1209":16778425,"1210":16778426,"1211":16778427,"1240":16778456,"1241":16778457,"1250":16778466,"1251":16778467,"1256":16778472,"1257":16778473,"1262":16778478,"1263":16778479,"1329":16778545,"1330":16778546,"1331":16778547,"1332":16778548,"1333":16778549,"1334":16778550,"1335":16778551,"1336":16778552,"1337":16778553,"1338":16778554,"1339":16778555,"1340":16778556,"1341":16778557,"1342":16778558,"1343":16778559,"1344":16778560,"1345":16778561,"1346":16778562,"1347":16778563,"1348":16778564,"1349":16778565,"1350":16778566,"1351":16778567,"1352":16778568,"1353":16778569,"1354":16778570,"1355":16778571,"1356":16778572,"1357":16778573,"1358":16778574,"1359":16778575,"1360":16778576,"1361":16778577,"1362":16778578,"1363":16778579,"1364":16778580,"1365":16778581,"1366":16778582,"1370":16778586,"1371":16778587,"1372":16778588,"1373":16778589,"1374":16778590,"1377":16778593,"1378":16778594,"1379":16778595,"1380":16778596,"1381":16778597,"1382":16778598,"1383":16778599,"1384":16778600,"1385":16778601,"1386":16778602,"1387":16778603,"1388":16778604,"1389":16778605,"1390":16778606,"1391":16778607,"1392":16778608,"1393":16778609,"1394":16778610,"1395":16778611,"1396":16778612,"1397":16778613,"1398":16778614,"1399":16778615,"1400":16778616,"1401":16778617,"1402":16778618,"1403":16778619,"1404":16778620,"1405":16778621,"1406":16778622,"1407":16778623,"1408":16778624,"1409":16778625,"1410":16778626,"1411":16778627,"1412":16778628,"1413":16778629,"1414":16778630,"1415":16778631,"1417":16778633,"1418":16778634,"1488":3296,"1489":3297,"1490":3298,"1491":3299,"1492":3300,"1493":3301,"1494":3302,"1495":3303,"1496":3304,"1497":3305,"1498":3306,"1499":3307,"1500":3308,"1501":3309,"1502":3310,"1503":3311,"1504":3312,"1505":3313,"1506":3314,"1507":3315,"1508":3316,"1509":3317,"1510":3318,"1511":3319,"1512":3320,"1513":3321,"1514":3322,"1548":1452,"1563":1467,"1567":1471,"1569":1473,"1570":1474,"1571":1475,"1572":1476,"1573":1477,"1574":1478,"1575":1479,"1576":1480,"1577":1481,"1578":1482,"1579":1483,"1580":1484,"1581":1485,"1582":1486,"1583":1487,"1584":1488,"1585":1489,"1586":1490,"1587":1491,"1588":1492,"1589":1493,"1590":1494,"1591":1495,"1592":1496,"1593":1497,"1594":1498,"1600":1504,"1601":1505,"1602":1506,"1603":1507,"1604":1508,"1605":1509,"1606":1510,"1607":1511,"1608":1512,"1609":1513,"1610":1514,"1611":1515,"1612":1516,"1613":1517,"1614":1518,"1615":1519,"1616":1520,"1617":1521,"1618":1522,"1619":16778835,"1620":16778836,"1621":16778837,"1632":16778848,"1633":16778849,"1634":16778850,"1635":16778851,"1636":16778852,"1637":16778853,"1638":16778854,"1639":16778855,"1640":16778856,"1641":16778857,"1642":16778858,"1648":16778864,"1657":16778873,"1662":16778878,"1670":16778886,"1672":16778888,"1681":16778897,"1688":16778904,"1700":16778916,"1705":16778921,"1711":16778927,"1722":16778938,"1726":16778942,"1729":16778945,"1740":16778956,"1746":16778962,"1748":16778964,"1776":16778992,"1777":16778993,"1778":16778994,"1779":16778995,"1780":16778996,"1781":16778997,"1782":16778998,"1783":16778999,"1784":16779000,"1785":16779001,"3458":16780674,"3459":16780675,"3461":16780677,"3462":16780678,"3463":16780679,"3464":16780680,"3465":16780681,"3466":16780682,"3467":16780683,"3468":16780684,"3469":16780685,"3470":16780686,"3471":16780687,"3472":16780688,"3473":16780689,"3474":16780690,"3475":16780691,"3476":16780692,"3477":16780693,"3478":16780694,"3482":16780698,"3483":16780699,"3484":16780700,"3485":16780701,"3486":16780702,"3487":16780703,"3488":16780704,"3489":16780705,"3490":16780706,"3491":16780707,"3492":16780708,"3493":16780709,"3494":16780710,"3495":16780711,"3496":16780712,"3497":16780713,"3498":16780714,"3499":16780715,"3500":16780716,"3501":16780717,"3502":16780718,"3503":16780719,"3504":16780720,"3505":16780721,"3507":16780723,"3508":16780724,"3509":16780725,"3510":16780726,"3511":16780727,"3512":16780728,"3513":16780729,"3514":16780730,"3515":16780731,"3517":16780733,"3520":16780736,"3521":16780737,"3522":16780738,"3523":16780739,"3524":16780740,"3525":16780741,"3526":16780742,"3530":16780746,"3535":16780751,"3536":16780752,"3537":16780753,"3538":16780754,"3539":16780755,"3540":16780756,"3542":16780758,"3544":16780760,"3545":16780761,"3546":16780762,"3547":16780763,"3548":16780764,"3549":16780765,"3550":16780766,"3551":16780767,"3570":16780786,"3571":16780787,"3572":16780788,"3585":3489,"3586":3490,"3587":3491,"3588":3492,"3589":3493,"3590":3494,"3591":3495,"3592":3496,"3593":3497,"3594":3498,"3595":3499,"3596":3500,"3597":3501,"3598":3502,"3599":3503,"3600":3504,"3601":3505,"3602":3506,"3603":3507,"3604":3508,"3605":3509,"3606":3510,"3607":3511,"3608":3512,"3609":3513,"3610":3514,"3611":3515,"3612":3516,"3613":3517,"3614":3518,"3615":3519,"3616":3520,"3617":3521,"3618":3522,"3619":3523,"3620":3524,"3621":3525,"3622":3526,"3623":3527,"3624":3528,"3625":3529,"3626":3530,"3627":3531,"3628":3532,"3629":3533,"3630":3534,"3631":3535,"3632":3536,"3633":3537,"3634":3538,"3635":3539,"3636":3540,"3637":3541,"3638":3542,"3639":3543,"3640":3544,"3641":3545,"3642":3546,"3647":3551,"3648":3552,"3649":3553,"3650":3554,"3651":3555,"3652":3556,"3653":3557,"3654":3558,"3655":3559,"3656":3560,"3657":3561,"3658":3562,"3659":3563,"3660":3564,"3661":3565,"3664":3568,"3665":3569,"3666":3570,"3667":3571,"3668":3572,"3669":3573,"3670":3574,"3671":3575,"3672":3576,"3673":3577,"4304":16781520,"4305":16781521,"4306":16781522,"4307":16781523,"4308":16781524,"4309":16781525,"4310":16781526,"4311":16781527,"4312":16781528,"4313":16781529,"4314":16781530,"4315":16781531,"4316":16781532,"4317":16781533,"4318":16781534,"4319":16781535,"4320":16781536,"4321":16781537,"4322":16781538,"4323":16781539,"4324":16781540,"4325":16781541,"4326":16781542,"4327":16781543,"4328":16781544,"4329":16781545,"4330":16781546,"4331":16781547,"4332":16781548,"4333":16781549,"4334":16781550,"4335":16781551,"4336":16781552,"4337":16781553,"4338":16781554,"4339":16781555,"4340":16781556,"4341":16781557,"4342":16781558,"7682":16784898,"7683":16784899,"7690":16784906,"7691":16784907,"7710":16784926,"7711":16784927,"7734":16784950,"7735":16784951,"7744":16784960,"7745":16784961,"7766":16784982,"7767":16784983,"7776":16784992,"7777":16784993,"7786":16785002,"7787":16785003,"7808":16785024,"7809":16785025,"7810":16785026,"7811":16785027,"7812":16785028,"7813":16785029,"7818":16785034,"7819":16785035,"7840":16785056,"7841":16785057,"7842":16785058,"7843":16785059,"7844":16785060,"7845":16785061,"7846":16785062,"7847":16785063,"7848":16785064,"7849":16785065,"7850":16785066,"7851":16785067,"7852":16785068,"7853":16785069,"7854":16785070,"7855":16785071,"7856":16785072,"7857":16785073,"7858":16785074,"7859":16785075,"7860":16785076,"7861":16785077,"7862":16785078,"7863":16785079,"7864":16785080,"7865":16785081,"7866":16785082,"7867":16785083,"7868":16785084,"7869":16785085,"7870":16785086,"7871":16785087,"7872":16785088,"7873":16785089,"7874":16785090,"7875":16785091,"7876":16785092,"7877":16785093,"7878":16785094,"7879":16785095,"7880":16785096,"7881":16785097,"7882":16785098,"7883":16785099,"7884":16785100,"7885":16785101,"7886":16785102,"7887":16785103,"7888":16785104,"7889":16785105,"7890":16785106,"7891":16785107,"7892":16785108,"7893":16785109,"7894":16785110,"7895":16785111,"7896":16785112,"7897":16785113,"7898":16785114,"7899":16785115,"7900":16785116,"7901":16785117,"7902":16785118,"7903":16785119,"7904":16785120,"7905":16785121,"7906":16785122,"7907":16785123,"7908":16785124,"7909":16785125,"7910":16785126,"7911":16785127,"7912":16785128,"7913":16785129,"7914":16785130,"7915":16785131,"7916":16785132,"7917":16785133,"7918":16785134,"7919":16785135,"7920":16785136,"7921":16785137,"7922":16785138,"7923":16785139,"7924":16785140,"7925":16785141,"7926":16785142,"7927":16785143,"7928":16785144,"7929":16785145,"8194":2722,"8195":2721,"8196":2723,"8197":2724,"8199":2725,"8200":2726,"8201":2727,"8202":2728,"8210":2747,"8211":2730,"8212":2729,"8213":1967,"8215":3295,"8216":2768,"8217":2769,"8218":2813,"8220":2770,"8221":2771,"8222":2814,"8224":2801,"8225":2802,"8226":2790,"8229":2735,"8230":2734,"8240":2773,"8242":2774,"8243":2775,"8248":2812,"8254":1150,"8304":16785520,"8308":16785524,"8309":16785525,"8310":16785526,"8311":16785527,"8312":16785528,"8313":16785529,"8320":16785536,"8321":16785537,"8322":16785538,"8323":16785539,"8324":16785540,"8325":16785541,"8326":16785542,"8327":16785543,"8328":16785544,"8329":16785545,"8352":16785568,"8353":16785569,"8354":16785570,"8355":16785571,"8356":16785572,"8357":16785573,"8358":16785574,"8359":16785575,"8360":16785576,"8361":3839,"8362":16785578,"8363":16785579,"8364":8364,"8453":2744,"8470":1712,"8471":2811,"8478":2772,"8482":2761,"8531":2736,"8532":2737,"8533":2738,"8534":2739,"8535":2740,"8536":2741,"8537":2742,"8538":2743,"8539":2755,"8540":2756,"8541":2757,"8542":2758,"8592":2299,"8593":2300,"8594":2301,"8595":2302,"8658":2254,"8660":2253,"8706":2287,"8709":16785925,"8711":2245,"8712":16785928,"8713":16785929,"8715":16785931,"8728":3018,"8730":2262,"8731":16785947,"8732":16785948,"8733":2241,"8734":2242,"8743":2270,"8744":2271,"8745":2268,"8746":2269,"8747":2239,"8748":16785964,"8749":16785965,"8756":2240,"8757":16785973,"8764":2248,"8771":2249,"8773":16785992,"8775":16785991,"8800":2237,"8801":2255,"8802":16786018,"8803":16786019,"8804":2236,"8805":2238,"8834":2266,"8835":2267,"8866":3068,"8867":3036,"8868":3010,"8869":3022,"8968":3027,"8970":3012,"8981":2810,"8992":2212,"8993":2213,"9109":3020,"9115":2219,"9117":2220,"9118":2221,"9120":2222,"9121":2215,"9123":2216,"9124":2217,"9126":2218,"9128":2223,"9132":2224,"9143":2209,"9146":2543,"9147":2544,"9148":2546,"9149":2547,"9225":2530,"9226":2533,"9227":2537,"9228":2531,"9229":2532,"9251":2732,"9252":2536,"9472":2211,"9474":2214,"9484":2210,"9488":2539,"9492":2541,"9496":2538,"9500":2548,"9508":2549,"9516":2551,"9524":2550,"9532":2542,"9618":2529,"9642":2791,"9643":2785,"9644":2779,"9645":2786,"9646":2783,"9647":2767,"9650":2792,"9651":2787,"9654":2781,"9655":2765,"9660":2793,"9661":2788,"9664":2780,"9665":2764,"9670":2528,"9675":2766,"9679":2782,"9702":2784,"9734":2789,"9742":2809,"9747":2762,"9756":2794,"9758":2795,"9792":2808,"9794":2807,"9827":2796,"9829":2798,"9830":2797,"9837":2806,"9839":2805,"10003":2803,"10007":2804,"10013":2777,"10016":2800,"10216":2748,"10217":2750,"10240":16787456,"10241":16787457,"10242":16787458,"10243":16787459,"10244":16787460,"10245":16787461,"10246":16787462,"10247":16787463,"10248":16787464,"10249":16787465,"10250":16787466,"10251":16787467,"10252":16787468,"10253":16787469,"10254":16787470,"10255":16787471,"10256":16787472,"10257":16787473,"10258":16787474,"10259":16787475,"10260":16787476,"10261":16787477,"10262":16787478,"10263":16787479,"10264":16787480,"10265":16787481,"10266":16787482,"10267":16787483,"10268":16787484,"10269":16787485,"10270":16787486,"10271":16787487,"10272":16787488,"10273":16787489,"10274":16787490,"10275":16787491,"10276":16787492,"10277":16787493,"10278":16787494,"10279":16787495,"10280":16787496,"10281":16787497,"10282":16787498,"10283":16787499,"10284":16787500,"10285":16787501,"10286":16787502,"10287":16787503,"10288":16787504,"10289":16787505,"10290":16787506,"10291":16787507,"10292":16787508,"10293":16787509,"10294":16787510,"10295":16787511,"10296":16787512,"10297":16787513,"10298":16787514,"10299":16787515,"10300":16787516,"10301":16787517,"10302":16787518,"10303":16787519,"10304":16787520,"10305":16787521,"10306":16787522,"10307":16787523,"10308":16787524,"10309":16787525,"10310":16787526,"10311":16787527,"10312":16787528,"10313":16787529,"10314":16787530,"10315":16787531,"10316":16787532,"10317":16787533,"10318":16787534,"10319":16787535,"10320":16787536,"10321":16787537,"10322":16787538,"10323":16787539,"10324":16787540,"10325":16787541,"10326":16787542,"10327":16787543,"10328":16787544,"10329":16787545,"10330":16787546,"10331":16787547,"10332":16787548,"10333":16787549,"10334":16787550,"10335":16787551,"10336":16787552,"10337":16787553,"10338":16787554,"10339":16787555,"10340":16787556,"10341":16787557,"10342":16787558,"10343":16787559,"10344":16787560,"10345":16787561,"10346":16787562,"10347":16787563,"10348":16787564,"10349":16787565,"10350":16787566,"10351":16787567,"10352":16787568,"10353":16787569,"10354":16787570,"10355":16787571,"10356":16787572,"10357":16787573,"10358":16787574,"10359":16787575,"10360":16787576,"10361":16787577,"10362":16787578,"10363":16787579,"10364":16787580,"10365":16787581,"10366":16787582,"10367":16787583,"10368":16787584,"10369":16787585,"10370":16787586,"10371":16787587,"10372":16787588,"10373":16787589,"10374":16787590,"10375":16787591,"10376":16787592,"10377":16787593,"10378":16787594,"10379":16787595,"10380":16787596,"10381":16787597,"10382":16787598,"10383":16787599,"10384":16787600,"10385":16787601,"10386":16787602,"10387":16787603,"10388":16787604,"10389":16787605,"10390":16787606,"10391":16787607,"10392":16787608,"10393":16787609,"10394":16787610,"10395":16787611,"10396":16787612,"10397":16787613,"10398":16787614,"10399":16787615,"10400":16787616,"10401":16787617,"10402":16787618,"10403":16787619,"10404":16787620,"10405":16787621,"10406":16787622,"10407":16787623,"10408":16787624,"10409":16787625,"10410":16787626,"10411":16787627,"10412":16787628,"10413":16787629,"10414":16787630,"10415":16787631,"10416":16787632,"10417":16787633,"10418":16787634,"10419":16787635,"10420":16787636,"10421":16787637,"10422":16787638,"10423":16787639,"10424":16787640,"10425":16787641,"10426":16787642,"10427":16787643,"10428":16787644,"10429":16787645,"10430":16787646,"10431":16787647,"10432":16787648,"10433":16787649,"10434":16787650,"10435":16787651,"10436":16787652,"10437":16787653,"10438":16787654,"10439":16787655,"10440":16787656,"10441":16787657,"10442":16787658,"10443":16787659,"10444":16787660,"10445":16787661,"10446":16787662,"10447":16787663,"10448":16787664,"10449":16787665,"10450":16787666,"10451":16787667,"10452":16787668,"10453":16787669,"10454":16787670,"10455":16787671,"10456":16787672,"10457":16787673,"10458":16787674,"10459":16787675,"10460":16787676,"10461":16787677,"10462":16787678,"10463":16787679,"10464":16787680,"10465":16787681,"10466":16787682,"10467":16787683,"10468":16787684,"10469":16787685,"10470":16787686,"10471":16787687,"10472":16787688,"10473":16787689,"10474":16787690,"10475":16787691,"10476":16787692,"10477":16787693,"10478":16787694,"10479":16787695,"10480":16787696,"10481":16787697,"10482":16787698,"10483":16787699,"10484":16787700,"10485":16787701,"10486":16787702,"10487":16787703,"10488":16787704,"10489":16787705,"10490":16787706,"10491":16787707,"10492":16787708,"10493":16787709,"10494":16787710,"10495":16787711,"12289":1188,"12290":1185,"12300":1186,"12301":1187,"12443":1246,"12444":1247,"12449":1191,"12450":1201,"12451":1192,"12452":1202,"12453":1193,"12454":1203,"12455":1194,"12456":1204,"12457":1195,"12458":1205,"12459":1206,"12461":1207,"12463":1208,"12465":1209,"12467":1210,"12469":1211,"12471":1212,"12473":1213,"12475":1214,"12477":1215,"12479":1216,"12481":1217,"12483":1199,"12484":1218,"12486":1219,"12488":1220,"12490":1221,"12491":1222,"12492":1223,"12493":1224,"12494":1225,"12495":1226,"12498":1227,"12501":1228,"12504":1229,"12507":1230,"12510":1231,"12511":1232,"12512":1233,"12513":1234,"12514":1235,"12515":1196,"12516":1236,"12517":1197,"12518":1237,"12519":1198,"12520":1238,"12521":1239,"12522":1240,"12523":1241,"12524":1242,"12525":1243,"12527":1244,"12530":1190,"12531":1245,"12539":1189,"12540":1200};
+
+ function lookup(k) { return k ? {keysym: k, keyname: keynames ? keynames[k] : k} : undefined; }
+ return {
+ fromUnicode : function(u) { return lookup(codepoints[u]); },
+ lookup : lookup
+ };
+})();
diff --git a/public/novnc/include/logo.js b/public/novnc/include/logo.js
new file mode 100644
index 00000000..befa598c
--- /dev/null
+++ b/public/novnc/include/logo.js
@@ -0,0 +1 @@
+noVNC_logo = {"width": 640, "height": 435, "data": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAoAAAAGzCAYAAAC/y6a9AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAStAAAErQBBHTWggAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAACAASURBVHic7N13fBvlwQfw3522ZMm2vPdIGCFkA4GyoYyGsCmjk+7dQksHL2/H2/dtC4W2tLTlfelu2VA2lEILFCgQIHEGJCQkdjzkLdmWZGvfvX8oOkmJEy/pNO73/Xz44DtLzz2RT7qfnnXC8uXLZUxDlqfdnUYQhIP+bjbPn+5xhypzrmUf6rGzOc5cjzVduXN9/nTPyfRrMt/jzOcY05U5n3L2f95s/34LPW4m/p6FbLp/73xe+5nKnWuZs/07ZOOcnusx5nucbJU727LneuxslDmdTBxn/2NmusyEuZS7kHMxG/XP5Gf3TOVmQzY+u/PhPMnkMcSsH5WIiIiI8goDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHG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"};
diff --git a/public/novnc/include/playback.js b/public/novnc/include/playback.js
new file mode 100644
index 00000000..203576f6
--- /dev/null
+++ b/public/novnc/include/playback.js
@@ -0,0 +1,120 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2012 Joel Martin
+ * Licensed under MPL 2.0 (see LICENSE.txt)
+ */
+
+"use strict";
+/*jslint browser: true, white: false */
+/*global Util, VNC_frame_data, finish */
+
+var rfb, mode, test_state, frame_idx, frame_length,
+ iteration, iterations, istart_time,
+
+ // Pre-declarations for jslint
+ send_array, next_iteration, queue_next_packet, do_packet, enable_test_mode;
+
+// Override send_array
+send_array = function (arr) {
+ // Stub out send_array
+};
+
+enable_test_mode = function () {
+ rfb._sock._mode = VNC_frame_encoding;
+ rfb._sock.send = send_array;
+ rfb._sock.close = function () {};
+ rfb._sock.flush = function () {};
+ rfb._checkEvents = function () {};
+ rfb.connect = function (host, port, password, path) {
+ this._rfb_host = host;
+ this._rfb_port = port;
+ this._rfb_password = (password !== undefined) ? password : "";
+ this._rfb_path = (path !== undefined) ? path : "";
+ this._sock.init('binary', 'ws');
+ this._updateState('ProtocolVersion', "Starting VNC handshake");
+ };
+};
+
+next_iteration = function () {
+ rfb = new RFB({'target': $D('VNC_canvas'),
+ 'onUpdateState': updateState});
+ enable_test_mode();
+
+ if (iteration === 0) {
+ frame_length = VNC_frame_data.length;
+ test_state = 'running';
+ }
+
+ if (test_state !== 'running') { return; }
+
+ iteration += 1;
+ if (iteration > iterations) {
+ finish();
+ return;
+ }
+
+ frame_idx = 0;
+ istart_time = (new Date()).getTime();
+ rfb.connect('test', 0, "bogus");
+
+ queue_next_packet();
+
+};
+
+queue_next_packet = function () {
+ var frame, foffset, toffset, delay;
+ if (test_state !== 'running') { return; }
+
+ frame = VNC_frame_data[frame_idx];
+ while ((frame_idx < frame_length) && (frame.charAt(0) === "}")) {
+ //Util.Debug("Send frame " + frame_idx);
+ frame_idx += 1;
+ frame = VNC_frame_data[frame_idx];
+ }
+
+ if (frame === 'EOF') {
+ Util.Debug("Finished, found EOF");
+ next_iteration();
+ return;
+ }
+ if (frame_idx >= frame_length) {
+ Util.Debug("Finished, no more frames");
+ next_iteration();
+ return;
+ }
+
+ if (mode === 'realtime') {
+ foffset = frame.slice(1, frame.indexOf('{', 1));
+ toffset = (new Date()).getTime() - istart_time;
+ delay = foffset - toffset;
+ if (delay < 1) {
+ delay = 1;
+ }
+
+ setTimeout(do_packet, delay);
+ } else {
+ setTimeout(do_packet, 1);
+ }
+};
+
+var bytes_processed = 0;
+
+do_packet = function () {
+ //Util.Debug("Processing frame: " + frame_idx);
+ var frame = VNC_frame_data[frame_idx],
+ start = frame.indexOf('{', 1) + 1;
+ bytes_processed += frame.length - start;
+ if (VNC_frame_encoding === 'binary') {
+ var u8 = new Uint8Array(frame.length - start);
+ for (var i = 0; i < frame.length - start; i++) {
+ u8[i] = frame.charCodeAt(start + i);
+ }
+ rfb._sock._recv_message({'data' : u8});
+ } else {
+ rfb._sock._recv_message({'data' : frame.slice(start)});
+ }
+ frame_idx += 1;
+
+ queue_next_packet();
+};
+
diff --git a/public/novnc/include/rfb.js b/public/novnc/include/rfb.js
new file mode 100644
index 00000000..d0e6c8f7
--- /dev/null
+++ b/public/novnc/include/rfb.js
@@ -0,0 +1,2148 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2012 Joel Martin
+ * Copyright (C) 2013 Samuel Mannehed for Cendio AB
+ * Licensed under MPL 2.0 (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 */
+/*global window, Util, Display, Keyboard, Mouse, Websock, Websock_native, Base64, DES */
+
+var RFB;
+
+(function () {
+ "use strict";
+ RFB = function (defaults) {
+ if (!defaults) {
+ defaults = {};
+ }
+
+ this._rfb_host = '';
+ this._rfb_port = 5900;
+ this._rfb_password = '';
+ this._rfb_path = '';
+
+ this._rfb_state = 'disconnected';
+ this._rfb_version = 0;
+ this._rfb_max_version = 3.8;
+ this._rfb_auth_scheme = '';
+
+ this._rfb_tightvnc = false;
+ this._rfb_xvp_ver = 0;
+
+ // In preference order
+ this._encodings = [
+ ['COPYRECT', 0x01 ],
+ ['TIGHT', 0x07 ],
+ ['TIGHT_PNG', -260 ],
+ ['HEXTILE', 0x05 ],
+ ['RRE', 0x02 ],
+ ['RAW', 0x00 ],
+ ['DesktopSize', -223 ],
+ ['Cursor', -239 ],
+
+ // Psuedo-encoding settings
+ //['JPEG_quality_lo', -32 ],
+ ['JPEG_quality_med', -26 ],
+ //['JPEG_quality_hi', -23 ],
+ //['compress_lo', -255 ],
+ ['compress_hi', -247 ],
+ ['last_rect', -224 ],
+ ['xvp', -309 ],
+ ['ExtendedDesktopSize', -308 ]
+ ];
+
+ this._encHandlers = {};
+ this._encNames = {};
+ this._encStats = {};
+
+ this._sock = null; // Websock object
+ this._display = null; // Display object
+ this._keyboard = null; // Keyboard input handler object
+ this._mouse = null; // Mouse input handler object
+ this._sendTimer = null; // Send Queue check timer
+ this._disconnTimer = null; // disconnection timer
+ this._msgTimer = null; // queued handle_msg timer
+
+ // Frame buffer update state
+ this._FBU = {
+ rects: 0,
+ subrects: 0, // RRE
+ lines: 0, // RAW
+ tiles: 0, // HEXTILE
+ bytes: 0,
+ x: 0,
+ y: 0,
+ width: 0,
+ height: 0,
+ encoding: 0,
+ subencoding: -1,
+ background: null,
+ zlib: [] // TIGHT zlib streams
+ };
+
+ this._fb_Bpp = 4;
+ this._fb_depth = 3;
+ this._fb_width = 0;
+ this._fb_height = 0;
+ this._fb_name = "";
+
+ this._destBuff = null;
+ this._paletteBuff = new Uint8Array(1024); // 256 * 4 (max palette size * max bytes-per-pixel)
+
+ this._rre_chunk_sz = 100;
+
+ this._timing = {
+ last_fbu: 0,
+ fbu_total: 0,
+ fbu_total_cnt: 0,
+ full_fbu_total: 0,
+ full_fbu_cnt: 0,
+
+ fbu_rt_start: 0,
+ fbu_rt_total: 0,
+ fbu_rt_cnt: 0,
+ pixels: 0
+ };
+
+ this._supportsSetDesktopSize = false;
+ this._screen_id = 0;
+ this._screen_flags = 0;
+
+ // Mouse state
+ this._mouse_buttonMask = 0;
+ this._mouse_arr = [];
+ this._viewportDragging = false;
+ this._viewportDragPos = {};
+ this._viewportHasMoved = false;
+
+ // set the default value on user-facing properties
+ Util.set_defaults(this, defaults, {
+ 'target': 'null', // VNC display rendering Canvas object
+ 'focusContainer': document, // DOM element that captures keyboard input
+ 'encrypt': false, // Use TLS/SSL/wss encryption
+ 'true_color': true, // Request true color pixel data
+ 'local_cursor': false, // Request locally rendered cursor
+ 'shared': true, // Request shared mode
+ 'view_only': false, // Disable client mouse/keyboard
+ 'xvp_password_sep': '@', // Separator for XVP password fields
+ 'disconnectTimeout': 3, // Time (s) to wait for disconnection
+ 'wsProtocols': ['binary'], // Protocols to use in the WebSocket connection
+ 'repeaterID': '', // [UltraVNC] RepeaterID to connect to
+ 'viewportDrag': false, // Move the viewport on mouse drags
+
+ // Callback functions
+ 'onUpdateState': function () { }, // onUpdateState(rfb, state, oldstate, statusMsg): state update/change
+ 'onPasswordRequired': function () { }, // onPasswordRequired(rfb): VNC password is required
+ 'onClipboard': function () { }, // onClipboard(rfb, text): RFB clipboard contents received
+ 'onBell': function () { }, // onBell(rfb): RFB Bell message received
+ 'onFBUReceive': function () { }, // onFBUReceive(rfb, fbu): RFB FBU received but not yet processed
+ 'onFBUComplete': function () { }, // onFBUComplete(rfb, fbu): RFB FBU received and processed
+ 'onFBResize': function () { }, // onFBResize(rfb, width, height): frame buffer resized
+ 'onDesktopName': function () { }, // onDesktopName(rfb, name): desktop name received
+ 'onXvpInit': function () { }, // onXvpInit(version): XVP extensions active for this connection
+ });
+
+ // main setup
+ Util.Debug(">> RFB.constructor");
+
+ // populate encHandlers with bound versions
+ Object.keys(RFB.encodingHandlers).forEach(function (encName) {
+ this._encHandlers[encName] = RFB.encodingHandlers[encName].bind(this);
+ }.bind(this));
+
+ // Create lookup tables based on encoding number
+ for (var i = 0; i < this._encodings.length; i++) {
+ this._encHandlers[this._encodings[i][1]] = this._encHandlers[this._encodings[i][0]];
+ this._encNames[this._encodings[i][1]] = this._encodings[i][0];
+ this._encStats[this._encodings[i][1]] = [0, 0];
+ }
+
+ // NB: nothing that needs explicit teardown should be done
+ // before this point, since this can throw an exception
+ try {
+ this._display = new Display({target: this._target});
+ } catch (exc) {
+ Util.Error("Display exception: " + exc);
+ throw exc;
+ }
+
+ this._keyboard = new Keyboard({target: this._focusContainer,
+ onKeyPress: this._handleKeyPress.bind(this)});
+
+ this._mouse = new Mouse({target: this._target,
+ onMouseButton: this._handleMouseButton.bind(this),
+ onMouseMove: this._handleMouseMove.bind(this),
+ notify: this._keyboard.sync.bind(this._keyboard)});
+
+ this._sock = new Websock();
+ this._sock.on('message', this._handle_message.bind(this));
+ this._sock.on('open', function () {
+ if (this._rfb_state === 'connect') {
+ this._updateState('ProtocolVersion', "Starting VNC handshake");
+ } else {
+ this._fail("Got unexpected WebSocket connection");
+ }
+ }.bind(this));
+ this._sock.on('close', function (e) {
+ Util.Warn("WebSocket on-close event");
+ var msg = "";
+ if (e.code) {
+ msg = " (code: " + e.code;
+ if (e.reason) {
+ msg += ", reason: " + e.reason;
+ }
+ msg += ")";
+ }
+ if (this._rfb_state === 'disconnect') {
+ this._updateState('disconnected', 'VNC disconnected' + msg);
+ } else if (this._rfb_state === 'ProtocolVersion') {
+ this._fail('Failed to connect to server' + msg);
+ } else if (this._rfb_state in {'failed': 1, 'disconnected': 1}) {
+ Util.Error("Received onclose while disconnected" + msg);
+ } else {
+ this._fail("Server disconnected" + msg);
+ }
+ this._sock.off('close');
+ }.bind(this));
+ this._sock.on('error', function (e) {
+ Util.Warn("WebSocket on-error event");
+ });
+
+ this._init_vars();
+
+ var rmode = this._display.get_render_mode();
+ if (Websock_native) {
+ Util.Info("Using native WebSockets");
+ this._updateState('loaded', 'noVNC ready: native WebSockets, ' + rmode);
+ } else {
+ this._cleanupSocket('fatal');
+ throw new Error("WebSocket support is required to use noVNC");
+ }
+
+ Util.Debug("<< RFB.constructor");
+ };
+
+ RFB.prototype = {
+ // Public methods
+ connect: function (host, port, password, path) {
+ this._rfb_host = host;
+ this._rfb_port = port;
+ this._rfb_password = (password !== undefined) ? password : "";
+ this._rfb_path = (path !== undefined) ? path : "";
+
+ if (!this._rfb_host || !this._rfb_port) {
+ return this._fail("Must set host and port");
+ }
+
+ this._updateState('connect');
+ },
+
+ disconnect: function () {
+ this._updateState('disconnect', 'Disconnecting');
+ this._sock.off('error');
+ this._sock.off('message');
+ this._sock.off('open');
+ },
+
+ sendPassword: function (passwd) {
+ this._rfb_password = passwd;
+ this._rfb_state = 'Authentication';
+ setTimeout(this._init_msg.bind(this), 1);
+ },
+
+ sendCtrlAltDel: function () {
+ if (this._rfb_state !== 'normal' || this._view_only) { return false; }
+ Util.Info("Sending Ctrl-Alt-Del");
+
+ RFB.messages.keyEvent(this._sock, XK_Control_L, 1);
+ RFB.messages.keyEvent(this._sock, XK_Alt_L, 1);
+ RFB.messages.keyEvent(this._sock, XK_Delete, 1);
+ RFB.messages.keyEvent(this._sock, XK_Delete, 0);
+ RFB.messages.keyEvent(this._sock, XK_Alt_L, 0);
+ RFB.messages.keyEvent(this._sock, XK_Control_L, 0);
+
+ this._sock.flush();
+ },
+
+ xvpOp: function (ver, op) {
+ if (this._rfb_xvp_ver < ver) { return false; }
+ Util.Info("Sending XVP operation " + op + " (version " + ver + ")");
+ this._sock.send_string("\xFA\x00" + String.fromCharCode(ver) + String.fromCharCode(op));
+ return true;
+ },
+
+ xvpShutdown: function () {
+ return this.xvpOp(1, 2);
+ },
+
+ xvpReboot: function () {
+ return this.xvpOp(1, 3);
+ },
+
+ xvpReset: function () {
+ return this.xvpOp(1, 4);
+ },
+
+ // Send a key press. If 'down' is not specified then send a down key
+ // followed by an up key.
+ sendKey: function (code, down) {
+ if (this._rfb_state !== "normal" || this._view_only) { return false; }
+ if (typeof down !== 'undefined') {
+ Util.Info("Sending key code (" + (down ? "down" : "up") + "): " + code);
+ RFB.messages.keyEvent(this._sock, code, down ? 1 : 0);
+ } else {
+ Util.Info("Sending key code (down + up): " + code);
+ RFB.messages.keyEvent(this._sock, code, 1);
+ RFB.messages.keyEvent(this._sock, code, 0);
+ }
+
+ this._sock.flush();
+ },
+
+ clipboardPasteFrom: function (text) {
+ if (this._rfb_state !== 'normal') { return; }
+ RFB.messages.clientCutText(this._sock, text);
+ this._sock.flush();
+ },
+
+ // Requests a change of remote desktop size. This message is an extension
+ // and may only be sent if we have received an ExtendedDesktopSize message
+ requestDesktopSize: function (width, height) {
+ if (this._rfb_state !== "normal") { return; }
+
+ if (this._supportsSetDesktopSize) {
+ RFB.messages.setDesktopSize(this._sock, width, height,
+ this._screen_id, this._screen_flags);
+ this._sock.flush();
+ }
+ },
+
+
+ // Private methods
+
+ _connect: function () {
+ Util.Debug(">> RFB.connect");
+
+ var uri;
+ if (typeof UsingSocketIO !== 'undefined') {
+ uri = 'http';
+ } else {
+ uri = this._encrypt ? 'wss' : 'ws';
+ }
+
+ uri += '://' + this._rfb_host + ':' + this._rfb_port + '/' + this._rfb_path;
+ Util.Info("connecting to " + uri);
+
+ this._sock.open(uri, this._wsProtocols);
+
+ Util.Debug("<< RFB.connect");
+ },
+
+ _init_vars: function () {
+ // reset state
+ this._FBU.rects = 0;
+ this._FBU.subrects = 0; // RRE and HEXTILE
+ this._FBU.lines = 0; // RAW
+ this._FBU.tiles = 0; // HEXTILE
+ this._FBU.zlibs = []; // TIGHT zlib encoders
+ this._mouse_buttonMask = 0;
+ this._mouse_arr = [];
+ this._rfb_tightvnc = false;
+
+ // Clear the per connection encoding stats
+ var i;
+ for (i = 0; i < this._encodings.length; i++) {
+ this._encStats[this._encodings[i][1]][0] = 0;
+ }
+
+ for (i = 0; i < 4; i++) {
+ this._FBU.zlibs[i] = new inflator.Inflate();
+ }
+ },
+
+ _print_stats: function () {
+ Util.Info("Encoding stats for this connection:");
+ var i, s;
+ for (i = 0; i < this._encodings.length; i++) {
+ s = this._encStats[this._encodings[i][1]];
+ if (s[0] + s[1] > 0) {
+ Util.Info(" " + this._encodings[i][0] + ": " + s[0] + " rects");
+ }
+ }
+
+ Util.Info("Encoding stats since page load:");
+ for (i = 0; i < this._encodings.length; i++) {
+ s = this._encStats[this._encodings[i][1]];
+ Util.Info(" " + this._encodings[i][0] + ": " + s[1] + " rects");
+ }
+ },
+
+ _cleanupSocket: function (state) {
+ if (this._sendTimer) {
+ clearInterval(this._sendTimer);
+ this._sendTimer = null;
+ }
+
+ if (this._msgTimer) {
+ clearInterval(this._msgTimer);
+ this._msgTimer = null;
+ }
+
+ if (this._display && this._display.get_context()) {
+ this._keyboard.ungrab();
+ this._mouse.ungrab();
+ if (state !== 'connect' && state !== 'loaded') {
+ this._display.defaultCursor();
+ }
+ if (Util.get_logging() !== 'debug' || state === 'loaded') {
+ // Show noVNC logo on load and when disconnected, unless in
+ // debug mode
+ this._display.clear();
+ }
+ }
+
+ this._sock.close();
+ },
+
+ /*
+ * Page states:
+ * loaded - page load, equivalent to disconnected
+ * disconnected - idle state
+ * connect - starting to connect (to ProtocolVersion)
+ * normal - connected
+ * disconnect - starting to disconnect
+ * failed - abnormal disconnect
+ * fatal - failed to load page, or fatal error
+ *
+ * RFB protocol initialization states:
+ * ProtocolVersion
+ * Security
+ * Authentication
+ * password - waiting for password, not part of RFB
+ * SecurityResult
+ * ClientInitialization - not triggered by server message
+ * ServerInitialization (to normal)
+ */
+ _updateState: function (state, statusMsg) {
+ var oldstate = this._rfb_state;
+
+ if (state === oldstate) {
+ // Already here, ignore
+ Util.Debug("Already in state '" + state + "', ignoring");
+ }
+
+ /*
+ * These are disconnected states. A previous connect may
+ * asynchronously cause a connection so make sure we are closed.
+ */
+ if (state in {'disconnected': 1, 'loaded': 1, 'connect': 1,
+ 'disconnect': 1, 'failed': 1, 'fatal': 1}) {
+ this._cleanupSocket(state);
+ }
+
+ if (oldstate === 'fatal') {
+ Util.Error('Fatal error, cannot continue');
+ }
+
+ var cmsg = typeof(statusMsg) !== 'undefined' ? (" Msg: " + statusMsg) : "";
+ var fullmsg = "New state '" + state + "', was '" + oldstate + "'." + cmsg;
+ if (state === 'failed' || state === 'fatal') {
+ Util.Error(cmsg);
+ } else {
+ Util.Warn(cmsg);
+ }
+
+ if (oldstate === 'failed' && state === 'disconnected') {
+ // do disconnect action, but stay in failed state
+ this._rfb_state = 'failed';
+ } else {
+ this._rfb_state = state;
+ }
+
+ if (this._disconnTimer && this._rfb_state !== 'disconnect') {
+ Util.Debug("Clearing disconnect timer");
+ clearTimeout(this._disconnTimer);
+ this._disconnTimer = null;
+ this._sock.off('close'); // make sure we don't get a double event
+ }
+
+ switch (state) {
+ case 'normal':
+ if (oldstate === 'disconnected' || oldstate === 'failed') {
+ Util.Error("Invalid transition from 'disconnected' or 'failed' to 'normal'");
+ }
+ break;
+
+ case 'connect':
+ this._init_vars();
+ this._connect();
+ // WebSocket.onopen transitions to 'ProtocolVersion'
+ break;
+
+ case 'disconnect':
+ this._disconnTimer = setTimeout(function () {
+ this._fail("Disconnect timeout");
+ }.bind(this), this._disconnectTimeout * 1000);
+
+ this._print_stats();
+
+ // WebSocket.onclose transitions to 'disconnected'
+ break;
+
+ case 'failed':
+ if (oldstate === 'disconnected') {
+ Util.Error("Invalid transition from 'disconnected' to 'failed'");
+ } else if (oldstate === 'normal') {
+ Util.Error("Error while connected.");
+ } else if (oldstate === 'init') {
+ Util.Error("Error while initializing.");
+ }
+
+ // Make sure we transition to disconnected
+ setTimeout(function () {
+ this._updateState('disconnected');
+ }.bind(this), 50);
+
+ break;
+
+ default:
+ // No state change action to take
+ }
+
+ if (oldstate === 'failed' && state === 'disconnected') {
+ this._onUpdateState(this, state, oldstate);
+ } else {
+ this._onUpdateState(this, state, oldstate, statusMsg);
+ }
+ },
+
+ _fail: function (msg) {
+ this._updateState('failed', msg);
+ return false;
+ },
+
+ _handle_message: function () {
+ if (this._sock.rQlen() === 0) {
+ Util.Warn("handle_message called on an empty receive queue");
+ return;
+ }
+
+ switch (this._rfb_state) {
+ case 'disconnected':
+ case 'failed':
+ Util.Error("Got data while disconnected");
+ break;
+ case 'normal':
+ if (this._normal_msg() && this._sock.rQlen() > 0) {
+ // true means we can continue processing
+ // Give other events a chance to run
+ if (this._msgTimer === null) {
+ Util.Debug("More data to process, creating timer");
+ this._msgTimer = setTimeout(function () {
+ this._msgTimer = null;
+ this._handle_message();
+ }.bind(this), 10);
+ } else {
+ Util.Debug("More data to process, existing timer");
+ }
+ }
+ break;
+ default:
+ this._init_msg();
+ break;
+ }
+ },
+
+ _handleKeyPress: function (keysym, down) {
+ if (this._view_only) { return; } // View only, skip keyboard, events
+ RFB.messages.keyEvent(this._sock, keysym, down);
+ this._sock.flush();
+ },
+
+ _handleMouseButton: function (x, y, down, bmask) {
+ if (down) {
+ this._mouse_buttonMask |= bmask;
+ } else {
+ this._mouse_buttonMask ^= bmask;
+ }
+
+ if (this._viewportDrag) {
+ if (down && !this._viewportDragging) {
+ this._viewportDragging = true;
+ this._viewportDragPos = {'x': x, 'y': y};
+
+ // Skip sending mouse events
+ return;
+ } else {
+ this._viewportDragging = false;
+
+ // If the viewport didn't actually move, then treat as a mouse click event
+ // Send the button down event here, as the button up event is sent at the end of this function
+ if (!this._viewportHasMoved && !this._view_only) {
+ RFB.messages.pointerEvent(this._sock, this._display.absX(x), this._display.absY(y), bmask);
+ }
+ this._viewportHasMoved = false;
+ }
+ }
+
+ if (this._view_only) { return; } // View only, skip mouse events
+
+ if (this._rfb_state !== "normal") { return; }
+ RFB.messages.pointerEvent(this._sock, this._display.absX(x), this._display.absY(y), this._mouse_buttonMask);
+ },
+
+ _handleMouseMove: function (x, y) {
+ if (this._viewportDragging) {
+ var deltaX = this._viewportDragPos.x - x;
+ var deltaY = this._viewportDragPos.y - y;
+
+ // The goal is to trigger on a certain physical width, the
+ // devicePixelRatio brings us a bit closer but is not optimal.
+ var dragThreshold = 10 * (window.devicePixelRatio || 1);
+
+ if (this._viewportHasMoved || (Math.abs(deltaX) > dragThreshold ||
+ Math.abs(deltaY) > dragThreshold)) {
+ this._viewportHasMoved = true;
+
+ this._viewportDragPos = {'x': x, 'y': y};
+ this._display.viewportChangePos(deltaX, deltaY);
+ }
+
+ // Skip sending mouse events
+ return;
+ }
+
+ if (this._view_only) { return; } // View only, skip mouse events
+
+ if (this._rfb_state !== "normal") { return; }
+ RFB.messages.pointerEvent(this._sock, this._display.absX(x), this._display.absY(y), this._mouse_buttonMask);
+ },
+
+ // Message Handlers
+
+ _negotiate_protocol_version: function () {
+ if (this._sock.rQlen() < 12) {
+ return this._fail("Incomplete protocol version");
+ }
+
+ var sversion = this._sock.rQshiftStr(12).substr(4, 7);
+ Util.Info("Server ProtocolVersion: " + sversion);
+ var is_repeater = 0;
+ switch (sversion) {
+ case "000.000": // UltraVNC repeater
+ is_repeater = 1;
+ break;
+ case "003.003":
+ case "003.006": // UltraVNC
+ case "003.889": // Apple Remote Desktop
+ this._rfb_version = 3.3;
+ break;
+ case "003.007":
+ this._rfb_version = 3.7;
+ break;
+ case "003.008":
+ case "004.000": // Intel AMT KVM
+ case "004.001": // RealVNC 4.6
+ this._rfb_version = 3.8;
+ break;
+ default:
+ return this._fail("Invalid server version " + sversion);
+ }
+
+ if (is_repeater) {
+ var repeaterID = this._repeaterID;
+ while (repeaterID.length < 250) {
+ repeaterID += "\0";
+ }
+ this._sock.send_string(repeaterID);
+ return true;
+ }
+
+ if (this._rfb_version > this._rfb_max_version) {
+ this._rfb_version = this._rfb_max_version;
+ }
+
+ // Send updates either at a rate of 1 update per 50ms, or
+ // whatever slower rate the network can handle
+ this._sendTimer = setInterval(this._sock.flush.bind(this._sock), 50);
+
+ var cversion = "00" + parseInt(this._rfb_version, 10) +
+ ".00" + ((this._rfb_version * 10) % 10);
+ this._sock.send_string("RFB " + cversion + "\n");
+ this._updateState('Security', 'Sent ProtocolVersion: ' + cversion);
+ },
+
+ _negotiate_security: function () {
+ if (this._rfb_version >= 3.7) {
+ // Server sends supported list, client decides
+ var num_types = this._sock.rQshift8();
+ if (this._sock.rQwait("security type", num_types, 1)) { return false; }
+
+ if (num_types === 0) {
+ var strlen = this._sock.rQshift32();
+ var reason = this._sock.rQshiftStr(strlen);
+ return this._fail("Security failure: " + reason);
+ }
+
+ this._rfb_auth_scheme = 0;
+ var types = this._sock.rQshiftBytes(num_types);
+ Util.Debug("Server security types: " + types);
+ for (var i = 0; i < types.length; i++) {
+ if (types[i] > this._rfb_auth_scheme && (types[i] <= 16 || types[i] == 22)) {
+ this._rfb_auth_scheme = types[i];
+ }
+ }
+
+ if (this._rfb_auth_scheme === 0) {
+ return this._fail("Unsupported security types: " + types);
+ }
+
+ this._sock.send([this._rfb_auth_scheme]);
+ } else {
+ // Server decides
+ if (this._sock.rQwait("security scheme", 4)) { return false; }
+ this._rfb_auth_scheme = this._sock.rQshift32();
+ }
+
+ this._updateState('Authentication', 'Authenticating using scheme: ' + this._rfb_auth_scheme);
+ return this._init_msg(); // jump to authentication
+ },
+
+ // authentication
+ _negotiate_xvp_auth: function () {
+ var xvp_sep = this._xvp_password_sep;
+ var xvp_auth = this._rfb_password.split(xvp_sep);
+ if (xvp_auth.length < 3) {
+ this._updateState('password', 'XVP credentials required (user' + xvp_sep +
+ 'target' + xvp_sep + 'password) -- got only ' + this._rfb_password);
+ this._onPasswordRequired(this);
+ return false;
+ }
+
+ var xvp_auth_str = String.fromCharCode(xvp_auth[0].length) +
+ String.fromCharCode(xvp_auth[1].length) +
+ xvp_auth[0] +
+ xvp_auth[1];
+ this._sock.send_string(xvp_auth_str);
+ this._rfb_password = xvp_auth.slice(2).join(xvp_sep);
+ this._rfb_auth_scheme = 2;
+ return this._negotiate_authentication();
+ },
+
+ _negotiate_std_vnc_auth: function () {
+ if (this._rfb_password.length === 0) {
+ // Notify via both callbacks since it's kind of
+ // an RFB state change and a UI interface issue
+ this._updateState('password', "Password Required");
+ this._onPasswordRequired(this);
+ return false;
+ }
+
+ if (this._sock.rQwait("auth challenge", 16)) { return false; }
+
+ // TODO(directxman12): make genDES not require an Array
+ var challenge = Array.prototype.slice.call(this._sock.rQshiftBytes(16));
+ var response = RFB.genDES(this._rfb_password, challenge);
+ this._sock.send(response);
+ this._updateState("SecurityResult");
+ return true;
+ },
+
+ _negotiate_tight_tunnels: function (numTunnels) {
+ var clientSupportedTunnelTypes = {
+ 0: { vendor: 'TGHT', signature: 'NOTUNNEL' }
+ };
+ var serverSupportedTunnelTypes = {};
+ // receive tunnel capabilities
+ for (var i = 0; i < numTunnels; i++) {
+ var cap_code = this._sock.rQshift32();
+ var cap_vendor = this._sock.rQshiftStr(4);
+ var cap_signature = this._sock.rQshiftStr(8);
+ serverSupportedTunnelTypes[cap_code] = { vendor: cap_vendor, signature: cap_signature };
+ }
+
+ // choose the notunnel type
+ if (serverSupportedTunnelTypes[0]) {
+ if (serverSupportedTunnelTypes[0].vendor != clientSupportedTunnelTypes[0].vendor ||
+ serverSupportedTunnelTypes[0].signature != clientSupportedTunnelTypes[0].signature) {
+ return this._fail("Client's tunnel type had the incorrect vendor or signature");
+ }
+ this._sock.send([0, 0, 0, 0]); // use NOTUNNEL
+ return false; // wait until we receive the sub auth count to continue
+ } else {
+ return this._fail("Server wanted tunnels, but doesn't support the notunnel type");
+ }
+ },
+
+ _negotiate_tight_auth: function () {
+ if (!this._rfb_tightvnc) { // first pass, do the tunnel negotiation
+ if (this._sock.rQwait("num tunnels", 4)) { return false; }
+ var numTunnels = this._sock.rQshift32();
+ if (numTunnels > 0 && this._sock.rQwait("tunnel capabilities", 16 * numTunnels, 4)) { return false; }
+
+ this._rfb_tightvnc = true;
+
+ if (numTunnels > 0) {
+ this._negotiate_tight_tunnels(numTunnels);
+ return false; // wait until we receive the sub auth to continue
+ }
+ }
+
+ // second pass, do the sub-auth negotiation
+ if (this._sock.rQwait("sub auth count", 4)) { return false; }
+ var subAuthCount = this._sock.rQshift32();
+ if (this._sock.rQwait("sub auth capabilities", 16 * subAuthCount, 4)) { return false; }
+
+ var clientSupportedTypes = {
+ 'STDVNOAUTH__': 1,
+ 'STDVVNCAUTH_': 2
+ };
+
+ var serverSupportedTypes = [];
+
+ for (var i = 0; i < subAuthCount; i++) {
+ var capNum = this._sock.rQshift32();
+ var capabilities = this._sock.rQshiftStr(12);
+ serverSupportedTypes.push(capabilities);
+ }
+
+ for (var authType in clientSupportedTypes) {
+ if (serverSupportedTypes.indexOf(authType) != -1) {
+ this._sock.send([0, 0, 0, clientSupportedTypes[authType]]);
+
+ switch (authType) {
+ case 'STDVNOAUTH__': // no auth
+ this._updateState('SecurityResult');
+ return true;
+ case 'STDVVNCAUTH_': // VNC auth
+ this._rfb_auth_scheme = 2;
+ return this._init_msg();
+ default:
+ return this._fail("Unsupported tiny auth scheme: " + authType);
+ }
+ }
+ }
+
+ this._fail("No supported sub-auth types!");
+ },
+
+ _negotiate_authentication: function () {
+ switch (this._rfb_auth_scheme) {
+ case 0: // connection failed
+ if (this._sock.rQwait("auth reason", 4)) { return false; }
+ var strlen = this._sock.rQshift32();
+ var reason = this._sock.rQshiftStr(strlen);
+ return this._fail("Auth failure: " + reason);
+
+ case 1: // no auth
+ if (this._rfb_version >= 3.8) {
+ this._updateState('SecurityResult');
+ return true;
+ }
+ this._updateState('ClientInitialisation', "No auth required");
+ return this._init_msg();
+
+ case 22: // XVP auth
+ return this._negotiate_xvp_auth();
+
+ case 2: // VNC authentication
+ return this._negotiate_std_vnc_auth();
+
+ case 16: // TightVNC Security Type
+ return this._negotiate_tight_auth();
+
+ default:
+ return this._fail("Unsupported auth scheme: " + this._rfb_auth_scheme);
+ }
+ },
+
+ _handle_security_result: function () {
+ if (this._sock.rQwait('VNC auth response ', 4)) { return false; }
+ switch (this._sock.rQshift32()) {
+ case 0: // OK
+ this._updateState('ClientInitialisation', 'Authentication OK');
+ return this._init_msg();
+ case 1: // failed
+ if (this._rfb_version >= 3.8) {
+ var length = this._sock.rQshift32();
+ if (this._sock.rQwait("SecurityResult reason", length, 8)) { return false; }
+ var reason = this._sock.rQshiftStr(length);
+ return this._fail(reason);
+ } else {
+ return this._fail("Authentication failure");
+ }
+ return false;
+ case 2:
+ return this._fail("Too many auth attempts");
+ }
+ },
+
+ _negotiate_server_init: function () {
+ if (this._sock.rQwait("server initialization", 24)) { return false; }
+
+ /* Screen size */
+ this._fb_width = this._sock.rQshift16();
+ this._fb_height = this._sock.rQshift16();
+ this._destBuff = new Uint8Array(this._fb_width * this._fb_height * 4);
+
+ /* PIXEL_FORMAT */
+ var bpp = this._sock.rQshift8();
+ var depth = this._sock.rQshift8();
+ var big_endian = this._sock.rQshift8();
+ var true_color = this._sock.rQshift8();
+
+ var red_max = this._sock.rQshift16();
+ var green_max = this._sock.rQshift16();
+ var blue_max = this._sock.rQshift16();
+ var red_shift = this._sock.rQshift8();
+ var green_shift = this._sock.rQshift8();
+ var blue_shift = this._sock.rQshift8();
+ this._sock.rQskipBytes(3); // padding
+
+ // NB(directxman12): we don't want to call any callbacks or print messages until
+ // *after* we're past the point where we could backtrack
+
+ /* Connection name/title */
+ var name_length = this._sock.rQshift32();
+ if (this._sock.rQwait('server init name', name_length, 24)) { return false; }
+ this._fb_name = Util.decodeUTF8(this._sock.rQshiftStr(name_length));
+
+ if (this._rfb_tightvnc) {
+ if (this._sock.rQwait('TightVNC extended server init header', 8, 24 + name_length)) { return false; }
+ // In TightVNC mode, ServerInit message is extended
+ var numServerMessages = this._sock.rQshift16();
+ var numClientMessages = this._sock.rQshift16();
+ var numEncodings = this._sock.rQshift16();
+ this._sock.rQskipBytes(2); // padding
+
+ var totalMessagesLength = (numServerMessages + numClientMessages + numEncodings) * 16;
+ if (this._sock.rQwait('TightVNC extended server init header', totalMessagesLength, 32 + name_length)) { return false; }
+
+ // we don't actually do anything with the capability information that TIGHT sends,
+ // so we just skip the all of this.
+
+ // TIGHT server message capabilities
+ this._sock.rQskipBytes(16 * numServerMessages);
+
+ // TIGHT client message capabilities
+ this._sock.rQskipBytes(16 * numClientMessages);
+
+ // TIGHT encoding capabilities
+ this._sock.rQskipBytes(16 * numEncodings);
+ }
+
+ // NB(directxman12): these are down here so that we don't run them multiple times
+ // if we backtrack
+ Util.Info("Screen: " + this._fb_width + "x" + this._fb_height +
+ ", bpp: " + bpp + ", depth: " + depth +
+ ", big_endian: " + big_endian +
+ ", true_color: " + true_color +
+ ", red_max: " + red_max +
+ ", green_max: " + green_max +
+ ", blue_max: " + blue_max +
+ ", red_shift: " + red_shift +
+ ", 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");
+ }
+
+ // we're past the point where we could backtrack, so it's safe to call this
+ this._onDesktopName(this, this._fb_name);
+
+ if (this._true_color && this._fb_name === "Intel(r) AMT KVM") {
+ Util.Warn("Intel AMT KVM only supports 8/16 bit depths. Disabling true color");
+ this._true_color = false;
+ }
+
+ this._display.set_true_color(this._true_color);
+ this._display.resize(this._fb_width, this._fb_height);
+ this._onFBResize(this, this._fb_width, this._fb_height);
+ this._keyboard.grab();
+ this._mouse.grab();
+
+ if (this._true_color) {
+ this._fb_Bpp = 4;
+ this._fb_depth = 3;
+ } else {
+ this._fb_Bpp = 1;
+ this._fb_depth = 1;
+ }
+
+ RFB.messages.pixelFormat(this._sock, this._fb_Bpp, this._fb_depth, this._true_color);
+ RFB.messages.clientEncodings(this._sock, this._encodings, this._local_cursor, this._true_color);
+ RFB.messages.fbUpdateRequests(this._sock, this._display.getCleanDirtyReset(), this._fb_width, this._fb_height);
+
+ this._timing.fbu_rt_start = (new Date()).getTime();
+ this._timing.pixels = 0;
+ this._sock.flush();
+
+ if (this._encrypt) {
+ this._updateState('normal', 'Connected (encrypted) to: ' + this._fb_name);
+ } else {
+ this._updateState('normal', 'Connected (unencrypted) to: ' + this._fb_name);
+ }
+ },
+
+ _init_msg: function () {
+ switch (this._rfb_state) {
+ case 'ProtocolVersion':
+ return this._negotiate_protocol_version();
+
+ case 'Security':
+ return this._negotiate_security();
+
+ case 'Authentication':
+ return this._negotiate_authentication();
+
+ case 'SecurityResult':
+ return this._handle_security_result();
+
+ case 'ClientInitialisation':
+ this._sock.send([this._shared ? 1 : 0]); // ClientInitialisation
+ this._updateState('ServerInitialisation', "Authentication OK");
+ return true;
+
+ case 'ServerInitialisation':
+ return this._negotiate_server_init();
+ }
+ },
+
+ _handle_set_colour_map_msg: function () {
+ Util.Debug("SetColorMapEntries");
+ this._sock.rQskip8(); // Padding
+
+ var first_colour = this._sock.rQshift16();
+ var num_colours = this._sock.rQshift16();
+ if (this._sock.rQwait('SetColorMapEntries', num_colours * 6, 6)) { return false; }
+
+ for (var c = 0; c < num_colours; c++) {
+ var red = parseInt(this._sock.rQshift16() / 256, 10);
+ var green = parseInt(this._sock.rQshift16() / 256, 10);
+ var blue = parseInt(this._sock.rQshift16() / 256, 10);
+ this._display.set_colourMap([blue, green, red], first_colour + c);
+ }
+ Util.Debug("colourMap: " + this._display.get_colourMap());
+ Util.Info("Registered " + num_colours + " colourMap entries");
+
+ return true;
+ },
+
+ _handle_server_cut_text: function () {
+ Util.Debug("ServerCutText");
+ if (this._sock.rQwait("ServerCutText header", 7, 1)) { return false; }
+ this._sock.rQskipBytes(3); // Padding
+ var length = this._sock.rQshift32();
+ if (this._sock.rQwait("ServerCutText", length, 8)) { return false; }
+
+ var text = this._sock.rQshiftStr(length);
+ this._onClipboard(this, text);
+
+ return true;
+ },
+
+ _handle_xvp_msg: function () {
+ if (this._sock.rQwait("XVP version and message", 3, 1)) { return false; }
+ this._sock.rQskip8(); // Padding
+ var xvp_ver = this._sock.rQshift8();
+ var xvp_msg = this._sock.rQshift8();
+
+ switch (xvp_msg) {
+ case 0: // XVP_FAIL
+ this._updateState(this._rfb_state, "Operation Failed");
+ break;
+ case 1: // XVP_INIT
+ this._rfb_xvp_ver = xvp_ver;
+ Util.Info("XVP extensions enabled (version " + this._rfb_xvp_ver + ")");
+ this._onXvpInit(this._rfb_xvp_ver);
+ break;
+ default:
+ this._fail("Disconnected: illegal server XVP message " + xvp_msg);
+ break;
+ }
+
+ return true;
+ },
+
+ _normal_msg: function () {
+ var msg_type;
+
+ if (this._FBU.rects > 0) {
+ msg_type = 0;
+ } else {
+ msg_type = this._sock.rQshift8();
+ }
+
+ switch (msg_type) {
+ case 0: // FramebufferUpdate
+ var ret = this._framebufferUpdate();
+ if (ret) {
+ RFB.messages.fbUpdateRequests(this._sock, this._display.getCleanDirtyReset(), this._fb_width, this._fb_height);
+ this._sock.flush();
+ }
+ return ret;
+
+ case 1: // SetColorMapEntries
+ return this._handle_set_colour_map_msg();
+
+ case 2: // Bell
+ Util.Debug("Bell");
+ this._onBell(this);
+ return true;
+
+ case 3: // ServerCutText
+ return this._handle_server_cut_text();
+
+ case 250: // XVP
+ return this._handle_xvp_msg();
+
+ default:
+ this._fail("Disconnected: illegal server message type " + msg_type);
+ Util.Debug("sock.rQslice(0, 30): " + this._sock.rQslice(0, 30));
+ return true;
+ }
+ },
+
+ _framebufferUpdate: function () {
+ var ret = true;
+ var now;
+
+ if (this._FBU.rects === 0) {
+ if (this._sock.rQwait("FBU header", 3, 1)) { return false; }
+ this._sock.rQskip8(); // Padding
+ this._FBU.rects = this._sock.rQshift16();
+ this._FBU.bytes = 0;
+ this._timing.cur_fbu = 0;
+ if (this._timing.fbu_rt_start > 0) {
+ now = (new Date()).getTime();
+ Util.Info("First FBU latency: " + (now - this._timing.fbu_rt_start));
+ }
+ }
+
+ while (this._FBU.rects > 0) {
+ if (this._rfb_state !== "normal") { return false; }
+
+ if (this._sock.rQwait("FBU", this._FBU.bytes)) { return false; }
+ if (this._FBU.bytes === 0) {
+ if (this._sock.rQwait("rect header", 12)) { return false; }
+ /* New FramebufferUpdate */
+
+ var hdr = this._sock.rQshiftBytes(12);
+ this._FBU.x = (hdr[0] << 8) + hdr[1];
+ this._FBU.y = (hdr[2] << 8) + hdr[3];
+ this._FBU.width = (hdr[4] << 8) + hdr[5];
+ this._FBU.height = (hdr[6] << 8) + hdr[7];
+ this._FBU.encoding = parseInt((hdr[8] << 24) + (hdr[9] << 16) +
+ (hdr[10] << 8) + hdr[11], 10);
+
+ this._onFBUReceive(this,
+ {'x': this._FBU.x, 'y': this._FBU.y,
+ 'width': this._FBU.width, 'height': this._FBU.height,
+ 'encoding': this._FBU.encoding,
+ 'encodingName': this._encNames[this._FBU.encoding]});
+
+ if (!this._encNames[this._FBU.encoding]) {
+ this._fail("Disconnected: unsupported encoding " +
+ this._FBU.encoding);
+ return false;
+ }
+ }
+
+ this._timing.last_fbu = (new Date()).getTime();
+
+ var handler = this._encHandlers[this._FBU.encoding];
+ try {
+ //ret = this._encHandlers[this._FBU.encoding]();
+ ret = handler();
+ } catch (ex) {
+ console.log("missed " + this._FBU.encoding + ": " + handler);
+ ret = this._encHandlers[this._FBU.encoding]();
+ }
+
+ now = (new Date()).getTime();
+ this._timing.cur_fbu += (now - this._timing.last_fbu);
+
+ if (ret) {
+ this._encStats[this._FBU.encoding][0]++;
+ this._encStats[this._FBU.encoding][1]++;
+ this._timing.pixels += this._FBU.width * this._FBU.height;
+ }
+
+ if (this._timing.pixels >= (this._fb_width * this._fb_height)) {
+ if ((this._FBU.width === this._fb_width && this._FBU.height === this._fb_height) ||
+ this._timing.fbu_rt_start > 0) {
+ this._timing.full_fbu_total += this._timing.cur_fbu;
+ this._timing.full_fbu_cnt++;
+ Util.Info("Timing of full FBU, curr: " +
+ this._timing.cur_fbu + ", total: " +
+ this._timing.full_fbu_total + ", cnt: " +
+ this._timing.full_fbu_cnt + ", avg: " +
+ (this._timing.full_fbu_total / this._timing.full_fbu_cnt));
+ }
+
+ if (this._timing.fbu_rt_start > 0) {
+ var fbu_rt_diff = now - this._timing.fbu_rt_start;
+ this._timing.fbu_rt_total += fbu_rt_diff;
+ this._timing.fbu_rt_cnt++;
+ Util.Info("full FBU round-trip, cur: " +
+ fbu_rt_diff + ", total: " +
+ this._timing.fbu_rt_total + ", cnt: " +
+ this._timing.fbu_rt_cnt + ", avg: " +
+ (this._timing.fbu_rt_total / this._timing.fbu_rt_cnt));
+ this._timing.fbu_rt_start = 0;
+ }
+ }
+
+ if (!ret) { return ret; } // need more data
+ }
+
+ this._onFBUComplete(this,
+ {'x': this._FBU.x, 'y': this._FBU.y,
+ 'width': this._FBU.width, 'height': this._FBU.height,
+ 'encoding': this._FBU.encoding,
+ 'encodingName': this._encNames[this._FBU.encoding]});
+
+ return true; // We finished this FBU
+ },
+ };
+
+ Util.make_properties(RFB, [
+ ['target', 'wo', 'dom'], // VNC display rendering Canvas object
+ ['focusContainer', 'wo', 'dom'], // DOM element that captures keyboard input
+ ['encrypt', 'rw', 'bool'], // Use TLS/SSL/wss encryption
+ ['true_color', 'rw', 'bool'], // Request true color pixel data
+ ['local_cursor', 'rw', 'bool'], // Request locally rendered cursor
+ ['shared', 'rw', 'bool'], // Request shared mode
+ ['view_only', 'rw', 'bool'], // Disable client mouse/keyboard
+ ['xvp_password_sep', 'rw', 'str'], // Separator for XVP password fields
+ ['disconnectTimeout', 'rw', 'int'], // Time (s) to wait for disconnection
+ ['wsProtocols', 'rw', 'arr'], // Protocols to use in the WebSocket connection
+ ['repeaterID', 'rw', 'str'], // [UltraVNC] RepeaterID to connect to
+ ['viewportDrag', 'rw', 'bool'], // Move the viewport on mouse drags
+
+ // Callback functions
+ ['onUpdateState', 'rw', 'func'], // onUpdateState(rfb, state, oldstate, statusMsg): RFB state update/change
+ ['onPasswordRequired', 'rw', 'func'], // onPasswordRequired(rfb): VNC password is required
+ ['onClipboard', 'rw', 'func'], // onClipboard(rfb, text): RFB clipboard contents received
+ ['onBell', 'rw', 'func'], // onBell(rfb): RFB Bell message received
+ ['onFBUReceive', 'rw', 'func'], // onFBUReceive(rfb, fbu): RFB FBU received but not yet processed
+ ['onFBUComplete', 'rw', 'func'], // onFBUComplete(rfb, fbu): RFB FBU received and processed
+ ['onFBResize', 'rw', 'func'], // onFBResize(rfb, width, height): frame buffer resized
+ ['onDesktopName', 'rw', 'func'], // onDesktopName(rfb, name): desktop name received
+ ['onXvpInit', 'rw', 'func'], // onXvpInit(version): XVP extensions active for this connection
+ ]);
+
+ RFB.prototype.set_local_cursor = function (cursor) {
+ if (!cursor || (cursor in {'0': 1, 'no': 1, 'false': 1})) {
+ this._local_cursor = false;
+ this._display.disableLocalCursor(); //Only show server-side cursor
+ } else {
+ if (this._display.get_cursor_uri()) {
+ this._local_cursor = true;
+ } else {
+ Util.Warn("Browser does not support local cursor");
+ this._display.disableLocalCursor();
+ }
+ }
+ };
+
+ RFB.prototype.get_display = function () { return this._display; };
+ RFB.prototype.get_keyboard = function () { return this._keyboard; };
+ RFB.prototype.get_mouse = function () { return this._mouse; };
+
+ // Class Methods
+ RFB.messages = {
+ keyEvent: function (sock, keysym, down) {
+ var buff = sock._sQ;
+ var offset = sock._sQlen;
+
+ buff[offset] = 4; // msg-type
+ buff[offset + 1] = down;
+
+ buff[offset + 2] = 0;
+ buff[offset + 3] = 0;
+
+ buff[offset + 4] = (keysym >> 24);
+ buff[offset + 5] = (keysym >> 16);
+ buff[offset + 6] = (keysym >> 8);
+ buff[offset + 7] = keysym;
+
+ sock._sQlen += 8;
+ },
+
+ pointerEvent: function (sock, x, y, mask) {
+ var buff = sock._sQ;
+ var offset = sock._sQlen;
+
+ buff[offset] = 5; // msg-type
+
+ buff[offset + 1] = mask;
+
+ buff[offset + 2] = x >> 8;
+ buff[offset + 3] = x;
+
+ buff[offset + 4] = y >> 8;
+ buff[offset + 5] = y;
+
+ sock._sQlen += 6;
+ },
+
+ // TODO(directxman12): make this unicode compatible?
+ clientCutText: function (sock, text) {
+ var buff = sock._sQ;
+ var offset = sock._sQlen;
+
+ buff[offset] = 6; // msg-type
+
+ buff[offset + 1] = 0; // padding
+ buff[offset + 2] = 0; // padding
+ buff[offset + 3] = 0; // padding
+
+ var n = text.length;
+
+ buff[offset + 4] = n >> 24;
+ buff[offset + 5] = n >> 16;
+ buff[offset + 6] = n >> 8;
+ buff[offset + 7] = n;
+
+ for (var i = 0; i < n; i++) {
+ buff[offset + 8 + i] = text.charCodeAt(i);
+ }
+
+ sock._sQlen += 8 + n;
+ },
+
+ setDesktopSize: function (sock, width, height, id, flags) {
+ var buff = sock._sQ;
+ var offset = sock._sQlen;
+
+ buff[offset] = 251; // msg-type
+ buff[offset + 1] = 0; // padding
+ buff[offset + 2] = width >> 8; // width
+ buff[offset + 3] = width;
+ buff[offset + 4] = height >> 8; // height
+ buff[offset + 5] = height;
+
+ buff[offset + 6] = 1; // number-of-screens
+ buff[offset + 7] = 0; // padding
+
+ // screen array
+ buff[offset + 8] = id >> 24; // id
+ buff[offset + 9] = id >> 16;
+ buff[offset + 10] = id >> 8;
+ buff[offset + 11] = id;
+ buff[offset + 12] = 0; // x-position
+ buff[offset + 13] = 0;
+ buff[offset + 14] = 0; // y-position
+ buff[offset + 15] = 0;
+ buff[offset + 16] = width >> 8; // width
+ buff[offset + 17] = width;
+ buff[offset + 18] = height >> 8; // height
+ buff[offset + 19] = height;
+ buff[offset + 20] = flags >> 24; // flags
+ buff[offset + 21] = flags >> 16;
+ buff[offset + 22] = flags >> 8;
+ buff[offset + 23] = flags;
+
+ sock._sQlen += 24;
+ },
+
+ pixelFormat: function (sock, bpp, depth, true_color) {
+ var buff = sock._sQ;
+ var offset = sock._sQlen;
+
+ buff[offset] = 0; // msg-type
+
+ buff[offset + 1] = 0; // padding
+ buff[offset + 2] = 0; // padding
+ buff[offset + 3] = 0; // padding
+
+ buff[offset + 4] = bpp * 8; // bits-per-pixel
+ buff[offset + 5] = depth * 8; // depth
+ buff[offset + 6] = 0; // little-endian
+ buff[offset + 7] = true_color ? 1 : 0; // true-color
+
+ buff[offset + 8] = 0; // red-max
+ buff[offset + 9] = 255; // red-max
+
+ buff[offset + 10] = 0; // green-max
+ buff[offset + 11] = 255; // green-max
+
+ buff[offset + 12] = 0; // blue-max
+ buff[offset + 13] = 255; // blue-max
+
+ buff[offset + 14] = 16; // red-shift
+ buff[offset + 15] = 8; // green-shift
+ buff[offset + 16] = 0; // blue-shift
+
+ buff[offset + 17] = 0; // padding
+ buff[offset + 18] = 0; // padding
+ buff[offset + 19] = 0; // padding
+
+ sock._sQlen += 20;
+ },
+
+ clientEncodings: function (sock, encodings, local_cursor, true_color) {
+ var buff = sock._sQ;
+ var offset = sock._sQlen;
+
+ buff[offset] = 2; // msg-type
+ buff[offset + 1] = 0; // padding
+
+ // offset + 2 and offset + 3 are encoding count
+
+ var i, j = offset + 4, cnt = 0;
+ for (i = 0; i < encodings.length; i++) {
+ if (encodings[i][0] === "Cursor" && !local_cursor) {
+ Util.Debug("Skipping Cursor pseudo-encoding");
+ } else if (encodings[i][0] === "TIGHT" && !true_color) {
+ // TODO: remove this when we have tight+non-true-color
+ Util.Warn("Skipping tight as it is only supported with true color");
+ } else {
+ var enc = encodings[i][1];
+ buff[j] = enc >> 24;
+ buff[j + 1] = enc >> 16;
+ buff[j + 2] = enc >> 8;
+ buff[j + 3] = enc;
+
+ j += 4;
+ cnt++;
+ }
+ }
+
+ buff[offset + 2] = cnt >> 8;
+ buff[offset + 3] = cnt;
+
+ sock._sQlen += j - offset;
+ },
+
+ fbUpdateRequests: function (sock, cleanDirty, fb_width, fb_height) {
+ var offsetIncrement = 0;
+
+ var cb = cleanDirty.cleanBox;
+ var w, h;
+ if (cb.w > 0 && cb.h > 0) {
+ w = typeof cb.w === "undefined" ? fb_width : cb.w;
+ h = typeof cb.h === "undefined" ? fb_height : cb.h;
+ // Request incremental for clean box
+ RFB.messages.fbUpdateRequest(sock, 1, cb.x, cb.y, w, h);
+ }
+
+ for (var i = 0; i < cleanDirty.dirtyBoxes.length; i++) {
+ var db = cleanDirty.dirtyBoxes[i];
+ // Force all (non-incremental) for dirty box
+ w = typeof db.w === "undefined" ? fb_width : db.w;
+ h = typeof db.h === "undefined" ? fb_height : db.h;
+ RFB.messages.fbUpdateRequest(sock, 0, db.x, db.y, w, h);
+ }
+ },
+
+ fbUpdateRequest: function (sock, incremental, x, y, w, h) {
+ var buff = sock._sQ;
+ var offset = sock._sQlen;
+
+ if (typeof(x) === "undefined") { x = 0; }
+ if (typeof(y) === "undefined") { y = 0; }
+
+ buff[offset] = 3; // msg-type
+ buff[offset + 1] = incremental;
+
+ buff[offset + 2] = (x >> 8) & 0xFF;
+ buff[offset + 3] = x & 0xFF;
+
+ buff[offset + 4] = (y >> 8) & 0xFF;
+ buff[offset + 5] = y & 0xFF;
+
+ buff[offset + 6] = (w >> 8) & 0xFF;
+ buff[offset + 7] = w & 0xFF;
+
+ buff[offset + 8] = (h >> 8) & 0xFF;
+ buff[offset + 9] = h & 0xFF;
+
+ sock._sQlen += 10;
+ }
+ };
+
+ RFB.genDES = function (password, challenge) {
+ var passwd = [];
+ for (var i = 0; i < password.length; i++) {
+ passwd.push(password.charCodeAt(i));
+ }
+ return (new DES(passwd)).encrypt(challenge);
+ };
+
+ RFB.extract_data_uri = function (arr) {
+ return ";base64," + Base64.encode(arr);
+ };
+
+ RFB.encodingHandlers = {
+ RAW: function () {
+ if (this._FBU.lines === 0) {
+ this._FBU.lines = this._FBU.height;
+ }
+
+ this._FBU.bytes = this._FBU.width * this._fb_Bpp; // at least a line
+ if (this._sock.rQwait("RAW", this._FBU.bytes)) { return false; }
+ var cur_y = this._FBU.y + (this._FBU.height - this._FBU.lines);
+ var curr_height = Math.min(this._FBU.lines,
+ Math.floor(this._sock.rQlen() / (this._FBU.width * this._fb_Bpp)));
+ this._display.blitImage(this._FBU.x, cur_y, this._FBU.width,
+ curr_height, this._sock.get_rQ(),
+ this._sock.get_rQi());
+ this._sock.rQskipBytes(this._FBU.width * curr_height * this._fb_Bpp);
+ this._FBU.lines -= curr_height;
+
+ if (this._FBU.lines > 0) {
+ this._FBU.bytes = this._FBU.width * this._fb_Bpp; // At least another line
+ } else {
+ this._FBU.rects--;
+ this._FBU.bytes = 0;
+ }
+
+ return true;
+ },
+
+ COPYRECT: function () {
+ this._FBU.bytes = 4;
+ if (this._sock.rQwait("COPYRECT", 4)) { return false; }
+ this._display.copyImage(this._sock.rQshift16(), this._sock.rQshift16(),
+ this._FBU.x, this._FBU.y, this._FBU.width,
+ this._FBU.height);
+
+ this._FBU.rects--;
+ this._FBU.bytes = 0;
+ return true;
+ },
+
+ RRE: function () {
+ var color;
+ if (this._FBU.subrects === 0) {
+ this._FBU.bytes = 4 + this._fb_Bpp;
+ if (this._sock.rQwait("RRE", 4 + this._fb_Bpp)) { return false; }
+ this._FBU.subrects = this._sock.rQshift32();
+ color = this._sock.rQshiftBytes(this._fb_Bpp); // Background
+ this._display.fillRect(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, color);
+ }
+
+ while (this._FBU.subrects > 0 && this._sock.rQlen() >= (this._fb_Bpp + 8)) {
+ color = this._sock.rQshiftBytes(this._fb_Bpp);
+ var x = this._sock.rQshift16();
+ var y = this._sock.rQshift16();
+ var width = this._sock.rQshift16();
+ var height = this._sock.rQshift16();
+ this._display.fillRect(this._FBU.x + x, this._FBU.y + y, width, height, color);
+ this._FBU.subrects--;
+ }
+
+ if (this._FBU.subrects > 0) {
+ var chunk = Math.min(this._rre_chunk_sz, this._FBU.subrects);
+ this._FBU.bytes = (this._fb_Bpp + 8) * chunk;
+ } else {
+ this._FBU.rects--;
+ this._FBU.bytes = 0;
+ }
+
+ return true;
+ },
+
+ HEXTILE: function () {
+ var rQ = this._sock.get_rQ();
+ var rQi = this._sock.get_rQi();
+
+ if (this._FBU.tiles === 0) {
+ this._FBU.tiles_x = Math.ceil(this._FBU.width / 16);
+ this._FBU.tiles_y = Math.ceil(this._FBU.height / 16);
+ this._FBU.total_tiles = this._FBU.tiles_x * this._FBU.tiles_y;
+ this._FBU.tiles = this._FBU.total_tiles;
+ }
+
+ while (this._FBU.tiles > 0) {
+ this._FBU.bytes = 1;
+ if (this._sock.rQwait("HEXTILE subencoding", this._FBU.bytes)) { return false; }
+ var subencoding = rQ[rQi]; // Peek
+ if (subencoding > 30) { // Raw
+ this._fail("Disconnected: illegal hextile subencoding " + subencoding);
+ return false;
+ }
+
+ var subrects = 0;
+ var curr_tile = this._FBU.total_tiles - this._FBU.tiles;
+ var tile_x = curr_tile % this._FBU.tiles_x;
+ var tile_y = Math.floor(curr_tile / this._FBU.tiles_x);
+ var x = this._FBU.x + tile_x * 16;
+ var y = this._FBU.y + tile_y * 16;
+ var w = Math.min(16, (this._FBU.x + this._FBU.width) - x);
+ var h = Math.min(16, (this._FBU.y + this._FBU.height) - y);
+
+ // Figure out how much we are expecting
+ if (subencoding & 0x01) { // Raw
+ this._FBU.bytes += w * h * this._fb_Bpp;
+ } else {
+ if (subencoding & 0x02) { // Background
+ this._FBU.bytes += this._fb_Bpp;
+ }
+ if (subencoding & 0x04) { // Foreground
+ this._FBU.bytes += this._fb_Bpp;
+ }
+ if (subencoding & 0x08) { // AnySubrects
+ this._FBU.bytes++; // Since we aren't shifting it off
+ if (this._sock.rQwait("hextile subrects header", this._FBU.bytes)) { return false; }
+ subrects = rQ[rQi + this._FBU.bytes - 1]; // Peek
+ if (subencoding & 0x10) { // SubrectsColoured
+ this._FBU.bytes += subrects * (this._fb_Bpp + 2);
+ } else {
+ this._FBU.bytes += subrects * 2;
+ }
+ }
+ }
+
+ if (this._sock.rQwait("hextile", this._FBU.bytes)) { return false; }
+
+ // We know the encoding and have a whole tile
+ this._FBU.subencoding = rQ[rQi];
+ rQi++;
+ if (this._FBU.subencoding === 0) {
+ if (this._FBU.lastsubencoding & 0x01) {
+ // Weird: ignore blanks are RAW
+ Util.Debug(" Ignoring blank after RAW");
+ } else {
+ this._display.fillRect(x, y, w, h, this._FBU.background);
+ }
+ } else if (this._FBU.subencoding & 0x01) { // Raw
+ this._display.blitImage(x, y, w, h, rQ, rQi);
+ rQi += this._FBU.bytes - 1;
+ } else {
+ if (this._FBU.subencoding & 0x02) { // Background
+ if (this._fb_Bpp == 1) {
+ this._FBU.background = rQ[rQi];
+ } else {
+ // fb_Bpp is 4
+ this._FBU.background = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]];
+ }
+ rQi += this._fb_Bpp;
+ }
+ if (this._FBU.subencoding & 0x04) { // Foreground
+ if (this._fb_Bpp == 1) {
+ this._FBU.foreground = rQ[rQi];
+ } else {
+ // this._fb_Bpp is 4
+ this._FBU.foreground = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]];
+ }
+ rQi += this._fb_Bpp;
+ }
+
+ this._display.startTile(x, y, w, h, this._FBU.background);
+ if (this._FBU.subencoding & 0x08) { // AnySubrects
+ subrects = rQ[rQi];
+ rQi++;
+
+ for (var s = 0; s < subrects; s++) {
+ var color;
+ if (this._FBU.subencoding & 0x10) { // SubrectsColoured
+ if (this._fb_Bpp === 1) {
+ color = rQ[rQi];
+ } else {
+ // _fb_Bpp is 4
+ color = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]];
+ }
+ rQi += this._fb_Bpp;
+ } else {
+ color = this._FBU.foreground;
+ }
+ var xy = rQ[rQi];
+ rQi++;
+ var sx = (xy >> 4);
+ var sy = (xy & 0x0f);
+
+ var wh = rQ[rQi];
+ rQi++;
+ var sw = (wh >> 4) + 1;
+ var sh = (wh & 0x0f) + 1;
+
+ this._display.subTile(sx, sy, sw, sh, color);
+ }
+ }
+ this._display.finishTile();
+ }
+ this._sock.set_rQi(rQi);
+ this._FBU.lastsubencoding = this._FBU.subencoding;
+ this._FBU.bytes = 0;
+ this._FBU.tiles--;
+ }
+
+ if (this._FBU.tiles === 0) {
+ this._FBU.rects--;
+ }
+
+ return true;
+ },
+
+ getTightCLength: function (arr) {
+ var header = 1, data = 0;
+ data += arr[0] & 0x7f;
+ if (arr[0] & 0x80) {
+ header++;
+ data += (arr[1] & 0x7f) << 7;
+ if (arr[1] & 0x80) {
+ header++;
+ data += arr[2] << 14;
+ }
+ }
+ return [header, data];
+ },
+
+ display_tight: function (isTightPNG) {
+ if (this._fb_depth === 1) {
+ this._fail("Tight protocol handler only implements true color mode");
+ }
+
+ this._FBU.bytes = 1; // compression-control byte
+ if (this._sock.rQwait("TIGHT compression-control", this._FBU.bytes)) { return false; }
+
+ var checksum = function (data) {
+ var sum = 0;
+ for (var i = 0; i < data.length; i++) {
+ sum += data[i];
+ if (sum > 65536) sum -= 65536;
+ }
+ return sum;
+ };
+
+ var resetStreams = 0;
+ var streamId = -1;
+ var decompress = function (data, expected) {
+ for (var i = 0; i < 4; i++) {
+ if ((resetStreams >> i) & 1) {
+ this._FBU.zlibs[i].reset();
+ console.debug('RESET!');
+ Util.Info("Reset zlib stream " + i);
+ }
+ }
+
+ //var uncompressed = this._FBU.zlibs[streamId].uncompress(data, 0);
+ var uncompressed = this._FBU.zlibs[streamId].inflate(data, true, expected);
+ /*if (uncompressed.status !== 0) {
+ Util.Error("Invalid data in zlib stream");
+ }*/
+
+ //return uncompressed.data;
+ return uncompressed;
+ }.bind(this);
+
+ var indexedToRGBX2Color = function (data, palette, width, height) {
+ // Convert indexed (palette based) image data to RGB
+ // TODO: reduce number of calculations inside loop
+ var dest = this._destBuff;
+ var w = Math.floor((width + 7) / 8);
+ var w1 = Math.floor(width / 8);
+
+ /*for (var y = 0; y < height; y++) {
+ var b, x, dp, sp;
+ var yoffset = y * width;
+ var ybitoffset = y * w;
+ var xoffset, targetbyte;
+ for (x = 0; x < w1; x++) {
+ xoffset = yoffset + x * 8;
+ targetbyte = data[ybitoffset + x];
+ for (b = 7; b >= 0; b--) {
+ dp = (xoffset + 7 - b) * 3;
+ sp = (targetbyte >> b & 1) * 3;
+ dest[dp] = palette[sp];
+ dest[dp + 1] = palette[sp + 1];
+ dest[dp + 2] = palette[sp + 2];
+ }
+ }
+
+ xoffset = yoffset + x * 8;
+ targetbyte = data[ybitoffset + x];
+ for (b = 7; b >= 8 - width % 8; b--) {
+ dp = (xoffset + 7 - b) * 3;
+ sp = (targetbyte >> b & 1) * 3;
+ dest[dp] = palette[sp];
+ dest[dp + 1] = palette[sp + 1];
+ dest[dp + 2] = palette[sp + 2];
+ }
+ }*/
+
+ for (var y = 0; y < height; y++) {
+ var b, x, dp, sp;
+ for (x = 0; x < w1; x++) {
+ for (b = 7; b >= 0; b--) {
+ dp = (y * width + x * 8 + 7 - b) * 4;
+ sp = (data[y * w + x] >> b & 1) * 3;
+ dest[dp] = palette[sp];
+ dest[dp + 1] = palette[sp + 1];
+ dest[dp + 2] = palette[sp + 2];
+ dest[dp + 3] = 255;
+ }
+ }
+
+ for (b = 7; b >= 8 - width % 8; b--) {
+ dp = (y * width + x * 8 + 7 - b) * 4;
+ sp = (data[y * w + x] >> b & 1) * 3;
+ dest[dp] = palette[sp];
+ dest[dp + 1] = palette[sp + 1];
+ dest[dp + 2] = palette[sp + 2];
+ dest[dp + 3] = 255;
+ }
+ }
+
+ return dest;
+ }.bind(this);
+
+ var indexedToRGBX = function (data, palette, width, height) {
+ // Convert indexed (palette based) image data to RGB
+ var dest = this._destBuff;
+ var total = width * height * 4;
+ for (var i = 0, j = 0; i < total; i += 4, j++) {
+ var sp = data[j] * 3;
+ dest[i] = palette[sp];
+ dest[i + 1] = palette[sp + 1];
+ dest[i + 2] = palette[sp + 2];
+ dest[i + 3] = 255;
+ }
+
+ return dest;
+ }.bind(this);
+
+ var rQi = this._sock.get_rQi();
+ var rQ = this._sock.rQwhole();
+ var cmode, data;
+ var cl_header, cl_data;
+
+ var handlePalette = function () {
+ var numColors = rQ[rQi + 2] + 1;
+ var paletteSize = numColors * this._fb_depth;
+ this._FBU.bytes += paletteSize;
+ if (this._sock.rQwait("TIGHT palette " + cmode, this._FBU.bytes)) { return false; }
+
+ var bpp = (numColors <= 2) ? 1 : 8;
+ var rowSize = Math.floor((this._FBU.width * bpp + 7) / 8);
+ var raw = false;
+ if (rowSize * this._FBU.height < 12) {
+ raw = true;
+ cl_header = 0;
+ cl_data = rowSize * this._FBU.height;
+ //clength = [0, rowSize * this._FBU.height];
+ } else {
+ // begin inline getTightCLength (returning two-item arrays is bad for performance with GC)
+ var cl_offset = rQi + 3 + paletteSize;
+ cl_header = 1;
+ cl_data = 0;
+ cl_data += rQ[cl_offset] & 0x7f;
+ if (rQ[cl_offset] & 0x80) {
+ cl_header++;
+ cl_data += (rQ[cl_offset + 1] & 0x7f) << 7;
+ if (rQ[cl_offset + 1] & 0x80) {
+ cl_header++;
+ cl_data += rQ[cl_offset + 2] << 14;
+ }
+ }
+ // end inline getTightCLength
+ }
+
+ this._FBU.bytes += cl_header + cl_data;
+ if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; }
+
+ // Shift ctl, filter id, num colors, palette entries, and clength off
+ this._sock.rQskipBytes(3);
+ //var palette = this._sock.rQshiftBytes(paletteSize);
+ this._sock.rQshiftTo(this._paletteBuff, paletteSize);
+ this._sock.rQskipBytes(cl_header);
+
+ if (raw) {
+ data = this._sock.rQshiftBytes(cl_data);
+ } else {
+ data = decompress(this._sock.rQshiftBytes(cl_data), rowSize * this._FBU.height);
+ }
+
+ // Convert indexed (palette based) image data to RGB
+ var rgbx;
+ if (numColors == 2) {
+ rgbx = indexedToRGBX2Color(data, this._paletteBuff, this._FBU.width, this._FBU.height);
+ this._display.blitRgbxImage(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, rgbx, 0, false);
+ } else {
+ rgbx = indexedToRGBX(data, this._paletteBuff, this._FBU.width, this._FBU.height);
+ this._display.blitRgbxImage(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, rgbx, 0, false);
+ }
+
+
+ return true;
+ }.bind(this);
+
+ var handleCopy = function () {
+ var raw = false;
+ var uncompressedSize = this._FBU.width * this._FBU.height * this._fb_depth;
+ if (uncompressedSize < 12) {
+ raw = true;
+ cl_header = 0;
+ cl_data = uncompressedSize;
+ } else {
+ // begin inline getTightCLength (returning two-item arrays is for peformance with GC)
+ var cl_offset = rQi + 1;
+ cl_header = 1;
+ cl_data = 0;
+ cl_data += rQ[cl_offset] & 0x7f;
+ if (rQ[cl_offset] & 0x80) {
+ cl_header++;
+ cl_data += (rQ[cl_offset + 1] & 0x7f) << 7;
+ if (rQ[cl_offset + 1] & 0x80) {
+ cl_header++;
+ cl_data += rQ[cl_offset + 2] << 14;
+ }
+ }
+ // end inline getTightCLength
+ }
+ this._FBU.bytes = 1 + cl_header + cl_data;
+ if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; }
+
+ // Shift ctl, clength off
+ this._sock.rQshiftBytes(1 + cl_header);
+
+ if (raw) {
+ data = this._sock.rQshiftBytes(cl_data);
+ } else {
+ data = decompress(this._sock.rQshiftBytes(cl_data), uncompressedSize);
+ }
+
+ this._display.blitRgbImage(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, data, 0, false);
+
+ return true;
+ }.bind(this);
+
+ var ctl = this._sock.rQpeek8();
+
+ // 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 return this._fail("Illegal tight compression received, ctl: " + ctl);
+
+ if (isTightPNG && (cmode === "filter" || cmode === "copy")) {
+ return this._fail("filter/copy received in tightPNG mode");
+ }
+
+ switch (cmode) {
+ // fill use fb_depth because TPIXELs drop the padding byte
+ case "fill": // TPIXEL
+ this._FBU.bytes += this._fb_depth;
+ break;
+ case "jpeg": // max clength
+ this._FBU.bytes += 3;
+ break;
+ case "png": // max clength
+ this._FBU.bytes += 3;
+ break;
+ case "filter": // filter id + num colors if palette
+ this._FBU.bytes += 2;
+ break;
+ case "copy":
+ break;
+ }
+
+ if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; }
+
+ // Determine FBU.bytes
+ switch (cmode) {
+ case "fill":
+ // skip ctl byte
+ this._display.fillRect(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, [rQ[rQi + 3], rQ[rQi + 2], rQ[rQi + 1]], false);
+ this._sock.rQskipBytes(4);
+ break;
+ case "png":
+ case "jpeg":
+ // begin inline getTightCLength (returning two-item arrays is for peformance with GC)
+ var cl_offset = rQi + 1;
+ cl_header = 1;
+ cl_data = 0;
+ cl_data += rQ[cl_offset] & 0x7f;
+ if (rQ[cl_offset] & 0x80) {
+ cl_header++;
+ cl_data += (rQ[cl_offset + 1] & 0x7f) << 7;
+ if (rQ[cl_offset + 1] & 0x80) {
+ cl_header++;
+ cl_data += rQ[cl_offset + 2] << 14;
+ }
+ }
+ // end inline getTightCLength
+ this._FBU.bytes = 1 + cl_header + cl_data; // ctl + clength size + jpeg-data
+ if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; }
+
+ // We have everything, render it
+ this._sock.rQskipBytes(1 + cl_header); // shift off clt + compact length
+ var img = new Image();
+ img.src = "data: image/" + cmode +
+ RFB.extract_data_uri(this._sock.rQshiftBytes(cl_data));
+ this._display.renderQ_push({
+ 'type': 'img',
+ 'img': img,
+ 'x': this._FBU.x,
+ 'y': this._FBU.y
+ });
+ img = null;
+ break;
+ case "filter":
+ var 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 use if jpeg is enabled
+ this._fail("Unsupported tight subencoding received, filter: " + filterId);
+ }
+ break;
+ case "copy":
+ if (!handleCopy()) { return false; }
+ break;
+ }
+
+
+ this._FBU.bytes = 0;
+ this._FBU.rects--;
+
+ return true;
+ },
+
+ TIGHT: function () { return this._encHandlers.display_tight(false); },
+ TIGHT_PNG: function () { return this._encHandlers.display_tight(true); },
+
+ last_rect: function () {
+ this._FBU.rects = 0;
+ return true;
+ },
+
+ handle_FB_resize: function () {
+ this._fb_width = this._FBU.width;
+ this._fb_height = this._FBU.height;
+ this._destBuff = new Uint8Array(this._fb_width * this._fb_height * 4);
+ this._display.resize(this._fb_width, this._fb_height);
+ this._onFBResize(this, this._fb_width, this._fb_height);
+ this._timing.fbu_rt_start = (new Date()).getTime();
+
+ this._FBU.bytes = 0;
+ this._FBU.rects -= 1;
+ return true;
+ },
+
+ ExtendedDesktopSize: function () {
+ this._FBU.bytes = 1;
+ if (this._sock.rQwait("ExtendedDesktopSize", this._FBU.bytes)) { return false; }
+
+ this._supportsSetDesktopSize = true;
+ var number_of_screens = this._sock.rQpeek8();
+
+ this._FBU.bytes = 4 + (number_of_screens * 16);
+ if (this._sock.rQwait("ExtendedDesktopSize", this._FBU.bytes)) { return false; }
+
+ this._sock.rQskipBytes(1); // number-of-screens
+ this._sock.rQskipBytes(3); // padding
+
+ for (var i = 0; i < number_of_screens; i += 1) {
+ // Save the id and flags of the first screen
+ if (i === 0) {
+ this._screen_id = this._sock.rQshiftBytes(4); // id
+ this._sock.rQskipBytes(2); // x-position
+ this._sock.rQskipBytes(2); // y-position
+ this._sock.rQskipBytes(2); // width
+ this._sock.rQskipBytes(2); // height
+ this._screen_flags = this._sock.rQshiftBytes(4); // flags
+ } else {
+ this._sock.rQskipBytes(16);
+ }
+ }
+
+ /*
+ * The x-position indicates the reason for the change:
+ *
+ * 0 - server resized on its own
+ * 1 - this client requested the resize
+ * 2 - another client requested the resize
+ */
+
+ // We need to handle errors when we requested the resize.
+ if (this._FBU.x === 1 && this._FBU.y !== 0) {
+ var msg = "";
+ // The y-position indicates the status code from the server
+ switch (this._FBU.y) {
+ case 1:
+ msg = "Resize is administratively prohibited";
+ break;
+ case 2:
+ msg = "Out of resources";
+ break;
+ case 3:
+ msg = "Invalid screen layout";
+ break;
+ default:
+ msg = "Unknown reason";
+ break;
+ }
+ Util.Info("Server did not accept the resize request: " + msg);
+ return true;
+ }
+
+ this._encHandlers.handle_FB_resize();
+ return true;
+ },
+
+ DesktopSize: function () {
+ this._encHandlers.handle_FB_resize();
+ return true;
+ },
+
+ Cursor: function () {
+ Util.Debug(">> set_cursor");
+ var x = this._FBU.x; // hotspot-x
+ var y = this._FBU.y; // hotspot-y
+ var w = this._FBU.width;
+ var h = this._FBU.height;
+
+ var pixelslength = w * h * this._fb_Bpp;
+ var masklength = Math.floor((w + 7) / 8) * h;
+
+ this._FBU.bytes = pixelslength + masklength;
+ if (this._sock.rQwait("cursor encoding", this._FBU.bytes)) { return false; }
+
+ this._display.changeCursor(this._sock.rQshiftBytes(pixelslength),
+ this._sock.rQshiftBytes(masklength),
+ x, y, w, h);
+
+ this._FBU.bytes = 0;
+ this._FBU.rects--;
+
+ Util.Debug("<< set_cursor");
+ return true;
+ },
+
+ JPEG_quality_lo: function () {
+ Util.Error("Server sent jpeg_quality pseudo-encoding");
+ },
+
+ compress_lo: function () {
+ Util.Error("Server sent compress level pseudo-encoding");
+ }
+ };
+})();
diff --git a/public/novnc/include/ui.js b/public/novnc/include/ui.js
new file mode 100644
index 00000000..327c49d3
--- /dev/null
+++ b/public/novnc/include/ui.js
@@ -0,0 +1,1287 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2012 Joel Martin
+ * Copyright (C) 2016 Samuel Mannehed for Cendio AB
+ * Licensed under MPL 2.0 (see LICENSE.txt)
+ *
+ * See README.md for usage and integration instructions.
+ */
+
+/* jslint white: false, browser: true */
+/* global window, $D, Util, WebUtil, RFB, Display */
+
+var UI;
+
+(function () {
+ "use strict";
+
+ // Load supporting scripts
+ window.onscriptsload = function () { UI.load(); };
+ Util.load_scripts(["webutil.js", "base64.js", "websock.js", "des.js",
+ "keysymdef.js", "keyboard.js", "input.js", "display.js",
+ "rfb.js", "keysym.js", "inflator.js"]);
+
+ UI = {
+
+ rfb_state: 'loaded',
+
+ resizeTimeout: null,
+ popupStatusTimeout: null,
+ hideKeyboardTimeout: null,
+
+ settingsOpen: false,
+ connSettingsOpen: false,
+ clipboardOpen: false,
+ keyboardVisible: false,
+
+ isTouchDevice: false,
+ isSafari: false,
+ rememberedClipSetting: null,
+ lastKeyboardinput: null,
+ defaultKeyboardinputLen: 100,
+
+ shiftDown: false,
+ ctrlDown: false,
+ altDown: false,
+ altGrDown: false,
+
+ // Setup rfb object, load settings from browser storage, then call
+ // UI.init to setup the UI/menus
+ load: function(callback) {
+ WebUtil.initSettings(UI.start, callback);
+ },
+
+ // Render default UI and initialize settings menu
+ start: function(callback) {
+ UI.isTouchDevice = 'ontouchstart' in document.documentElement;
+
+ // Stylesheet selection dropdown
+ var sheet = WebUtil.selectStylesheet();
+ var sheets = WebUtil.getStylesheets();
+ var i;
+ for (i = 0; i < sheets.length; i += 1) {
+ UI.addOption($D('noVNC_setting_stylesheet'),sheets[i].title, sheets[i].title);
+ }
+
+ // Logging selection dropdown
+ var llevels = ['error', 'warn', 'info', 'debug'];
+ for (i = 0; i < llevels.length; i += 1) {
+ UI.addOption($D('noVNC_setting_logging'),llevels[i], llevels[i]);
+ }
+
+ // Settings with immediate effects
+ UI.initSetting('logging', 'warn');
+ WebUtil.init_logging(UI.getSetting('logging'));
+
+ UI.initSetting('stylesheet', 'default');
+ WebUtil.selectStylesheet(null);
+ // call twice to get around webkit bug
+ WebUtil.selectStylesheet(UI.getSetting('stylesheet'));
+
+ // if port == 80 (or 443) then it won't be present and should be
+ // set manually
+ var port = window.location.port;
+ if (!port) {
+ if (window.location.protocol.substring(0,5) == 'https') {
+ port = 443;
+ }
+ else if (window.location.protocol.substring(0,4) == 'http') {
+ port = 80;
+ }
+ }
+
+ /* Populate the controls if defaults are provided in the URL */
+ UI.initSetting('host', window.location.hostname);
+ UI.initSetting('port', port);
+ UI.initSetting('password', '');
+ UI.initSetting('encrypt', (window.location.protocol === "https:"));
+ UI.initSetting('true_color', true);
+ UI.initSetting('cursor', !UI.isTouchDevice);
+ UI.initSetting('resize', 'off');
+ UI.initSetting('shared', true);
+ UI.initSetting('view_only', false);
+ UI.initSetting('path', 'websockify');
+ UI.initSetting('repeaterID', '');
+ UI.initSetting('token', '');
+
+ var autoconnect = WebUtil.getConfigVar('autoconnect', false);
+ if (autoconnect === 'true' || autoconnect == '1') {
+ autoconnect = true;
+ UI.connect();
+ } else {
+ autoconnect = false;
+ }
+
+ UI.updateVisualState();
+
+ $D('noVNC_setting_host').focus();
+
+ // Show mouse selector buttons on touch screen devices
+ if (UI.isTouchDevice) {
+ // Show mobile buttons
+ $D('noVNC_mobile_buttons').style.display = "inline";
+ UI.setMouseButton();
+ // Remove the address bar
+ setTimeout(function() { window.scrollTo(0, 1); }, 100);
+ UI.forceSetting('clip', true);
+ } else {
+ UI.initSetting('clip', false);
+ }
+
+ UI.setViewClip();
+ UI.setBarPosition();
+
+ Util.addEvent(window, 'resize', function () {
+ UI.applyResizeMode();
+ UI.setViewClip();
+ UI.updateViewDrag();
+ UI.setBarPosition();
+ } );
+
+ UI.isSafari = (navigator.userAgent.indexOf('Safari') != -1 &&
+ navigator.userAgent.indexOf('Chrome') == -1);
+
+ // Only show the button if fullscreen is properly supported
+ // * Safari doesn't support alphanumerical input while in fullscreen
+ if (!UI.isSafari &&
+ (document.documentElement.requestFullscreen ||
+ document.documentElement.mozRequestFullScreen ||
+ document.documentElement.webkitRequestFullscreen ||
+ document.body.msRequestFullscreen)) {
+ $D('noVNC_fullscreen_button').style.display = "inline";
+ Util.addEvent(window, 'fullscreenchange', UI.updateFullscreenButton);
+ Util.addEvent(window, 'mozfullscreenchange', UI.updateFullscreenButton);
+ Util.addEvent(window, 'webkitfullscreenchange', UI.updateFullscreenButton);
+ Util.addEvent(window, 'msfullscreenchange', UI.updateFullscreenButton);
+ }
+
+ Util.addEvent(window, 'load', UI.keyboardinputReset);
+
+ Util.addEvent(window, 'beforeunload', function () {
+ if (UI.rfb && UI.rfb_state === 'normal') {
+ return "You are currently connected.";
+ }
+ } );
+
+ // Show description by default when hosted at for kanaka.github.com
+ if (location.host === "kanaka.github.io") {
+ // Open the description dialog
+ $D('noVNC_description').style.display = "block";
+ } else {
+ // Show the connect panel on first load unless autoconnecting
+ if (autoconnect === UI.connSettingsOpen) {
+ UI.toggleConnectPanel();
+ }
+ }
+
+ // Add mouse event click/focus/blur event handlers to the UI
+ UI.addMouseHandlers();
+
+ if (typeof callback === "function") {
+ callback(UI.rfb);
+ }
+ },
+
+ initRFB: function() {
+ try {
+ UI.rfb = new RFB({'target': $D('noVNC_canvas'),
+ 'onUpdateState': UI.updateState,
+ 'onXvpInit': UI.updateXvpButton,
+ 'onClipboard': UI.clipboardReceive,
+ 'onFBUComplete': UI.initialResize,
+ 'onFBResize': UI.updateViewDrag,
+ 'onDesktopName': UI.updateDocumentTitle});
+ return true;
+ } catch (exc) {
+ UI.updateState(null, 'fatal', null, 'Unable to create RFB client -- ' + exc);
+ return false;
+ }
+ },
+
+ addMouseHandlers: function() {
+ // Setup interface handlers that can't be inline
+ $D("noVNC_view_drag_button").onclick = UI.toggleViewDrag;
+ $D("noVNC_mouse_button0").onclick = function () { UI.setMouseButton(1); };
+ $D("noVNC_mouse_button1").onclick = function () { UI.setMouseButton(2); };
+ $D("noVNC_mouse_button2").onclick = function () { UI.setMouseButton(4); };
+ $D("noVNC_mouse_button4").onclick = function () { UI.setMouseButton(0); };
+ $D("noVNC_keyboard_button").onclick = UI.showKeyboard;
+
+ $D("noVNC_keyboardinput").oninput = UI.keyInput;
+ $D("noVNC_keyboardinput").onblur = UI.hideKeyboard;
+ $D("noVNC_keyboardinput").onsubmit = function () { return false; };
+
+ $D("noVNC_toggleExtraKeys_button").onclick = UI.toggleExtraKeys;
+ $D("noVNC_toggleCtrl_button").onclick = UI.toggleCtrl;
+ $D("noVNC_toggleAlt_button").onclick = UI.toggleAlt;
+ $D("noVNC_sendTab_button").onclick = UI.sendTab;
+ $D("noVNC_sendEsc_button").onclick = UI.sendEsc;
+
+ $D("noVNC_sendCtrlAltDel_button").onclick = UI.sendCtrlAltDel;
+ $D("noVNC_xvpShutdown_button").onclick = function() { UI.rfb.xvpShutdown(); },
+ $D("noVNC_xvpReboot_button").onclick = function() { UI.rfb.xvpReboot(); },
+ $D("noVNC_xvpReset_button").onclick = function() { UI.rfb.xvpReset(); },
+ $D("noVNC_status").onclick = UI.popupStatus;
+ $D("noVNC_popup_status").onclick = UI.closePopup;
+ $D("noVNC_toggleXvp_button").onclick = UI.toggleXvpPanel;
+ $D("noVNC_clipboard_button").onclick = UI.toggleClipboardPanel;
+ $D("noVNC_fullscreen_button").onclick = UI.toggleFullscreen;
+ $D("noVNC_settings_button").onclick = UI.toggleSettingsPanel;
+ $D("noVNC_connectPanel_button").onclick = UI.toggleConnectPanel;
+ $D("noVNC_disconnect_button").onclick = UI.disconnect;
+ $D("noVNC_description_button").onclick = UI.toggleConnectPanel;
+
+ $D("noVNC_clipboard_text").onfocus = UI.displayBlur;
+ $D("noVNC_clipboard_text").onblur = UI.displayFocus;
+ $D("noVNC_clipboard_text").onchange = UI.clipboardSend;
+ $D("noVNC_clipboard_clear_button").onclick = UI.clipboardClear;
+
+ $D("noVNC_settings_menu").onmouseover = UI.displayBlur;
+ $D("noVNC_settings_menu").onmouseover = UI.displayFocus;
+ $D("noVNC_settings_apply").onclick = UI.settingsApply;
+
+ $D("noVNC_connect_button").onclick = UI.connect;
+
+ $D("noVNC_setting_resize").onchange = UI.enableDisableViewClip;
+ },
+
+/* ------^-------
+ * /INIT
+ * ==============
+ * VISUAL
+ * ------v------*/
+
+ updateState: function(rfb, state, oldstate, msg) {
+ UI.rfb_state = state;
+ var klass;
+ switch (state) {
+ case 'failed':
+ case 'fatal':
+ klass = "noVNC_status_error";
+ break;
+ case 'normal':
+ klass = "noVNC_status_normal";
+ break;
+ case 'disconnected':
+ $D('noVNC_logo').style.display = "block";
+ $D('noVNC_screen').style.display = "none";
+ /* falls through */
+ case 'loaded':
+ klass = "noVNC_status_normal";
+ break;
+ case 'password':
+ UI.toggleConnectPanel();
+
+ $D('noVNC_connect_button').value = "Send Password";
+ $D('noVNC_connect_button').onclick = UI.setPassword;
+ $D('noVNC_setting_password').focus();
+
+ klass = "noVNC_status_warn";
+ break;
+ default:
+ klass = "noVNC_status_warn";
+ break;
+ }
+
+ if (typeof(msg) !== 'undefined') {
+ $D('noVNC_control_bar').setAttribute("class", klass);
+ $D('noVNC_status').textContent = msg;
+ }
+
+ UI.updateVisualState();
+ },
+
+ // Disable/enable controls depending on connection state
+ updateVisualState: function() {
+ var connected = UI.rfb && UI.rfb_state === 'normal';
+
+ //Util.Debug(">> updateVisualState");
+ $D('noVNC_setting_encrypt').disabled = connected;
+ $D('noVNC_setting_true_color').disabled = connected;
+ if (Util.browserSupportsCursorURIs()) {
+ $D('noVNC_setting_cursor').disabled = connected;
+ } else {
+ UI.updateSetting('cursor', !UI.isTouchDevice);
+ $D('noVNC_setting_cursor').disabled = true;
+ }
+
+ UI.enableDisableViewClip();
+ $D('noVNC_setting_resize').disabled = connected;
+ $D('noVNC_setting_shared').disabled = connected;
+ $D('noVNC_setting_view_only').disabled = connected;
+ $D('noVNC_setting_path').disabled = connected;
+ $D('noVNC_setting_repeaterID').disabled = connected;
+
+ if (connected) {
+ UI.setViewClip();
+ UI.setMouseButton(1);
+ $D('noVNC_clipboard_button').style.display = "inline";
+ $D('noVNC_keyboard_button').style.display = "inline";
+ $D('noVNC_extra_keys').style.display = "";
+ $D('noVNC_sendCtrlAltDel_button').style.display = "inline";
+ } else {
+ UI.setMouseButton();
+ $D('noVNC_clipboard_button').style.display = "none";
+ $D('noVNC_keyboard_button').style.display = "none";
+ $D('noVNC_extra_keys').style.display = "none";
+ $D('noVNC_sendCtrlAltDel_button').style.display = "none";
+ UI.updateXvpButton(0);
+ }
+
+ // State change disables viewport dragging.
+ // It is enabled (toggled) by direct click on the button
+ UI.updateViewDrag(false);
+
+ switch (UI.rfb_state) {
+ case 'fatal':
+ case 'failed':
+ case 'disconnected':
+ $D('noVNC_connectPanel_button').style.display = "";
+ $D('noVNC_disconnect_button').style.display = "none";
+ UI.connSettingsOpen = false;
+ UI.toggleConnectPanel();
+ break;
+ case 'loaded':
+ $D('noVNC_connectPanel_button').style.display = "";
+ $D('noVNC_disconnect_button').style.display = "none";
+ break;
+ default:
+ $D('noVNC_connectPanel_button').style.display = "none";
+ $D('noVNC_disconnect_button').style.display = "";
+ break;
+ }
+
+ //Util.Debug("<< updateVisualState");
+ },
+
+ popupStatus: function(text) {
+ var psp = $D('noVNC_popup_status');
+
+ clearTimeout(UI.popupStatusTimeout);
+
+ if (typeof text === 'string') {
+ psp.textContent = text;
+ } else {
+ psp.textContent = $D('noVNC_status').textContent;
+ }
+ psp.style.display = "block";
+ psp.style.left = window.innerWidth/2 -
+ parseInt(window.getComputedStyle(psp).width)/2 -30 + "px";
+
+ // Show the popup for a maximum of 1.5 seconds
+ UI.popupStatusTimeout = setTimeout(UI.closePopup, 1500);
+ },
+
+ closePopup: function() {
+ clearTimeout(UI.popupStatusTimeout);
+ $D('noVNC_popup_status').style.display = "none";
+ },
+
+/* ------^-------
+ * /VISUAL
+ * ==============
+ * SETTINGS
+ * ------v------*/
+
+ // Initial page load read/initialization of settings
+ initSetting: function(name, defVal) {
+ // Check Query string followed by cookie
+ var val = WebUtil.getConfigVar(name);
+ if (val === null) {
+ val = WebUtil.readSetting(name, defVal);
+ }
+ UI.updateSetting(name, val);
+ return val;
+ },
+
+ // Update cookie and form control setting. If value is not set, then
+ // updates from control to current cookie setting.
+ updateSetting: function(name, value) {
+
+ // Save the cookie for this session
+ if (typeof value !== 'undefined') {
+ WebUtil.writeSetting(name, value);
+ }
+
+ // Update the settings control
+ value = UI.getSetting(name);
+
+ var ctrl = $D('noVNC_setting_' + name);
+ if (ctrl.type === 'checkbox') {
+ ctrl.checked = value;
+
+ } else if (typeof ctrl.options !== 'undefined') {
+ for (var i = 0; i < ctrl.options.length; i += 1) {
+ if (ctrl.options[i].value === value) {
+ ctrl.selectedIndex = i;
+ break;
+ }
+ }
+ } else {
+ /*Weird IE9 error leads to 'null' appearring
+ in textboxes instead of ''.*/
+ if (value === null) {
+ value = "";
+ }
+ ctrl.value = value;
+ }
+ },
+
+ // Save control setting to cookie
+ saveSetting: function(name) {
+ var val, ctrl = $D('noVNC_setting_' + name);
+ if (ctrl.type === 'checkbox') {
+ val = ctrl.checked;
+ } else if (typeof ctrl.options !== 'undefined') {
+ val = ctrl.options[ctrl.selectedIndex].value;
+ } else {
+ val = ctrl.value;
+ }
+ WebUtil.writeSetting(name, val);
+ //Util.Debug("Setting saved '" + name + "=" + val + "'");
+ return val;
+ },
+
+ // Force a setting to be a certain value
+ forceSetting: function(name, val) {
+ UI.updateSetting(name, val);
+ return val;
+ },
+
+ // Read form control compatible setting from cookie
+ getSetting: function(name) {
+ var ctrl = $D('noVNC_setting_' + name);
+ var val = WebUtil.readSetting(name);
+ if (typeof val !== 'undefined' && val !== null && ctrl.type === 'checkbox') {
+ if (val.toString().toLowerCase() in {'0':1, 'no':1, 'false':1}) {
+ val = false;
+ } else {
+ val = true;
+ }
+ }
+ return val;
+ },
+
+ // Save/apply settings when 'Apply' button is pressed
+ settingsApply: function() {
+ //Util.Debug(">> settingsApply");
+ UI.saveSetting('encrypt');
+ UI.saveSetting('true_color');
+ if (Util.browserSupportsCursorURIs()) {
+ UI.saveSetting('cursor');
+ }
+
+ UI.saveSetting('resize');
+
+ if (UI.getSetting('resize') === 'downscale' || UI.getSetting('resize') === 'scale') {
+ UI.forceSetting('clip', false);
+ }
+
+ UI.saveSetting('clip');
+ UI.saveSetting('shared');
+ UI.saveSetting('view_only');
+ UI.saveSetting('path');
+ UI.saveSetting('repeaterID');
+ UI.saveSetting('stylesheet');
+ UI.saveSetting('logging');
+
+ // Settings with immediate (non-connected related) effect
+ WebUtil.selectStylesheet(UI.getSetting('stylesheet'));
+ WebUtil.init_logging(UI.getSetting('logging'));
+ UI.setViewClip();
+ UI.updateViewDrag();
+ //Util.Debug("<< settingsApply");
+ },
+
+ // Open menu
+ openSettingsMenu: function() {
+ // Close the description panel
+ $D('noVNC_description').style.display = "none";
+ // Close clipboard panel if open
+ if (UI.clipboardOpen === true) {
+ UI.toggleClipboardPanel();
+ }
+ // Close connection settings if open
+ if (UI.connSettingsOpen === true) {
+ UI.toggleConnectPanel();
+ }
+ // Close XVP panel if open
+ if (UI.xvpOpen === true) {
+ UI.toggleXvpPanel();
+ }
+ $D('noVNC_settings').style.display = "block";
+ $D('noVNC_settings_button').className = "noVNC_status_button_selected";
+ UI.settingsOpen = true;
+ },
+
+ // Close menu (without applying settings)
+ closeSettingsMenu: function() {
+ $D('noVNC_settings').style.display = "none";
+ $D('noVNC_settings_button').className = "noVNC_status_button";
+ UI.settingsOpen = false;
+ },
+
+ // Toggle the settings menu:
+ // 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();
+ } else {
+ UI.updateSetting('encrypt');
+ UI.updateSetting('true_color');
+ if (Util.browserSupportsCursorURIs()) {
+ UI.updateSetting('cursor');
+ } else {
+ UI.updateSetting('cursor', !UI.isTouchDevice);
+ $D('noVNC_setting_cursor').disabled = true;
+ }
+ UI.updateSetting('clip');
+ UI.updateSetting('resize');
+ UI.updateSetting('shared');
+ UI.updateSetting('view_only');
+ UI.updateSetting('path');
+ UI.updateSetting('repeaterID');
+ UI.updateSetting('stylesheet');
+ UI.updateSetting('logging');
+
+ UI.openSettingsMenu();
+ }
+ },
+
+/* ------^-------
+ * /SETTINGS
+ * ==============
+ * XVP
+ * ------v------*/
+
+ // Show the XVP panel
+ toggleXvpPanel: function() {
+ // Close the description panel
+ $D('noVNC_description').style.display = "none";
+ // Close settings if open
+ if (UI.settingsOpen === true) {
+ UI.settingsApply();
+ UI.closeSettingsMenu();
+ }
+ // Close connection settings if open
+ if (UI.connSettingsOpen === true) {
+ UI.toggleConnectPanel();
+ }
+ // Close clipboard panel if open
+ if (UI.clipboardOpen === true) {
+ UI.toggleClipboardPanel();
+ }
+ // Toggle XVP panel
+ if (UI.xvpOpen === true) {
+ $D('noVNC_xvp').style.display = "none";
+ $D('noVNC_toggleXvp_button').className = "noVNC_status_button";
+ UI.xvpOpen = false;
+ } else {
+ $D('noVNC_xvp').style.display = "block";
+ $D('noVNC_toggleXvp_button').className = "noVNC_status_button_selected";
+ UI.xvpOpen = true;
+ }
+ },
+
+ // Disable/enable XVP button
+ updateXvpButton: function(ver) {
+ if (ver >= 1) {
+ $D('noVNC_toggleXvp_button').style.display = 'inline';
+ } else {
+ $D('noVNC_toggleXvp_button').style.display = 'none';
+ // Close XVP panel if open
+ if (UI.xvpOpen === true) {
+ UI.toggleXvpPanel();
+ }
+ }
+ },
+
+/* ------^-------
+ * /XVP
+ * ==============
+ * CLIPBOARD
+ * ------v------*/
+
+ // Show the clipboard panel
+ toggleClipboardPanel: function() {
+ // Close the description panel
+ $D('noVNC_description').style.display = "none";
+ // Close settings if open
+ if (UI.settingsOpen === true) {
+ UI.settingsApply();
+ UI.closeSettingsMenu();
+ }
+ // Close connection settings if open
+ if (UI.connSettingsOpen === true) {
+ UI.toggleConnectPanel();
+ }
+ // Close XVP panel if open
+ if (UI.xvpOpen === true) {
+ UI.toggleXvpPanel();
+ }
+ // Toggle Clipboard Panel
+ if (UI.clipboardOpen === true) {
+ $D('noVNC_clipboard').style.display = "none";
+ $D('noVNC_clipboard_button').className = "noVNC_status_button";
+ UI.clipboardOpen = false;
+ } else {
+ $D('noVNC_clipboard').style.display = "block";
+ $D('noVNC_clipboard_button').className = "noVNC_status_button_selected";
+ UI.clipboardOpen = true;
+ }
+ },
+
+ clipboardReceive: function(rfb, text) {
+ Util.Debug(">> UI.clipboardReceive: " + text.substr(0,40) + "...");
+ $D('noVNC_clipboard_text').value = text;
+ Util.Debug("<< UI.clipboardReceive");
+ },
+
+ clipboardClear: function() {
+ $D('noVNC_clipboard_text').value = "";
+ UI.rfb.clipboardPasteFrom("");
+ },
+
+ clipboardSend: function() {
+ var text = $D('noVNC_clipboard_text').value;
+ Util.Debug(">> UI.clipboardSend: " + text.substr(0,40) + "...");
+ UI.rfb.clipboardPasteFrom(text);
+ Util.Debug("<< UI.clipboardSend");
+ },
+
+/* ------^-------
+ * /CLIPBOARD
+ * ==============
+ * CONNECTION
+ * ------v------*/
+
+ // 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) {
+ UI.settingsApply();
+ UI.closeSettingsMenu();
+ $D('noVNC_connectPanel_button').className = "noVNC_status_button";
+ }
+ // Close clipboard panel if open
+ if (UI.clipboardOpen === true) {
+ UI.toggleClipboardPanel();
+ }
+ // Close XVP panel if open
+ if (UI.xvpOpen === true) {
+ UI.toggleXvpPanel();
+ }
+
+ // Toggle Connection Panel
+ if (UI.connSettingsOpen === true) {
+ $D('noVNC_controls').style.display = "none";
+ $D('noVNC_connectPanel_button').className = "noVNC_status_button";
+ UI.connSettingsOpen = false;
+ UI.saveSetting('host');
+ UI.saveSetting('port');
+ UI.saveSetting('token');
+ //UI.saveSetting('password');
+ } else {
+ $D('noVNC_controls').style.display = "block";
+ $D('noVNC_connectPanel_button').className = "noVNC_status_button_selected";
+ UI.connSettingsOpen = true;
+ $D('noVNC_setting_host').focus();
+ }
+ },
+
+ connect: function() {
+ UI.closeSettingsMenu();
+ UI.toggleConnectPanel();
+
+ var host = $D('noVNC_setting_host').value;
+ var port = $D('noVNC_setting_port').value;
+ var password = $D('noVNC_setting_password').value;
+ var token = $D('noVNC_setting_token').value;
+ var path = $D('noVNC_setting_path').value;
+
+ //if token is in path then ignore the new token variable
+ if (token) {
+ path = WebUtil.injectParamIfMissing(path, "token", token);
+ }
+
+ if ((!host) || (!port)) {
+ throw new Error("Must set host and port");
+ }
+
+ if (!UI.initRFB()) return;
+
+ UI.rfb.set_encrypt(UI.getSetting('encrypt'));
+ 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_repeaterID(UI.getSetting('repeaterID'));
+
+ UI.rfb.connect(host, port, password, path);
+
+ //Close dialog.
+ setTimeout(UI.setBarPosition, 100);
+ $D('noVNC_logo').style.display = "none";
+ $D('noVNC_screen').style.display = "inline";
+ },
+
+ disconnect: function() {
+ UI.closeSettingsMenu();
+ UI.rfb.disconnect();
+
+ // Restore the callback used for initial resize
+ UI.rfb.set_onFBUComplete(UI.initialResize);
+
+ $D('noVNC_logo').style.display = "block";
+ $D('noVNC_screen').style.display = "none";
+
+ // Don't display the connection settings until we're actually disconnected
+ },
+
+ setPassword: function() {
+ UI.rfb.sendPassword($D('noVNC_setting_password').value);
+ //Reset connect button.
+ $D('noVNC_connect_button').value = "Connect";
+ $D('noVNC_connect_button').onclick = UI.connect;
+ //Hide connection panel.
+ UI.toggleConnectPanel();
+ return false;
+ },
+
+/* ------^-------
+ * /CONNECTION
+ * ==============
+ * FULLSCREEN
+ * ------v------*/
+
+ toggleFullscreen: function() {
+ if (document.fullscreenElement || // alternative standard method
+ document.mozFullScreenElement || // currently working methods
+ document.webkitFullscreenElement ||
+ document.msFullscreenElement) {
+ if (document.exitFullscreen) {
+ document.exitFullscreen();
+ } else if (document.mozCancelFullScreen) {
+ document.mozCancelFullScreen();
+ } else if (document.webkitExitFullscreen) {
+ document.webkitExitFullscreen();
+ } else if (document.msExitFullscreen) {
+ document.msExitFullscreen();
+ }
+ } else {
+ if (document.documentElement.requestFullscreen) {
+ document.documentElement.requestFullscreen();
+ } else if (document.documentElement.mozRequestFullScreen) {
+ document.documentElement.mozRequestFullScreen();
+ } else if (document.documentElement.webkitRequestFullscreen) {
+ document.documentElement.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
+ } else if (document.body.msRequestFullscreen) {
+ document.body.msRequestFullscreen();
+ }
+ }
+ UI.enableDisableViewClip();
+ UI.updateFullscreenButton();
+ },
+
+ updateFullscreenButton: function() {
+ if (document.fullscreenElement || // alternative standard method
+ document.mozFullScreenElement || // currently working methods
+ document.webkitFullscreenElement ||
+ document.msFullscreenElement ) {
+ $D('noVNC_fullscreen_button').className = "noVNC_status_button_selected";
+ } else {
+ $D('noVNC_fullscreen_button').className = "noVNC_status_button";
+ }
+ },
+
+/* ------^-------
+ * /FULLSCREEN
+ * ==============
+ * RESIZE
+ * ------v------*/
+
+ // Apply remote resizing or local scaling
+ applyResizeMode: function() {
+ if (!UI.rfb) return;
+
+ var screen = UI.screenSize();
+
+ if (screen && UI.rfb_state === 'normal' && UI.rfb.get_display()) {
+
+ var display = UI.rfb.get_display();
+ var resizeMode = UI.getSetting('resize');
+
+ if (resizeMode === 'remote') {
+
+ // Request changing the resolution of the remote display to
+ // the size of the local browser viewport.
+
+ // In order to not send multiple requests before the browser-resize
+ // is finished we wait 0.5 seconds before sending the request.
+ clearTimeout(UI.resizeTimeout);
+ UI.resizeTimeout = setTimeout(function(){
+
+ // Limit the viewport to the size of the browser window
+ display.set_maxWidth(screen.w);
+ display.set_maxHeight(screen.h);
+
+ Util.Debug('Attempting requestDesktopSize(' +
+ screen.w + ', ' + screen.h + ')');
+
+ // Request a remote size covering the viewport
+ UI.rfb.requestDesktopSize(screen.w, screen.h);
+ }, 500);
+
+ } else if (resizeMode === 'scale' || resizeMode === 'downscale') {
+ var downscaleOnly = resizeMode === 'downscale';
+ var scaleRatio = display.autoscale(screen.w, screen.h, downscaleOnly);
+ UI.rfb.get_mouse().set_scale(scaleRatio);
+ Util.Debug('Scaling by ' + UI.rfb.get_mouse().get_scale());
+ }
+ }
+ },
+
+ // The screen is always the same size as the available viewport
+ // in the browser window minus the height of the control bar
+ screenSize: function() {
+ var screen = $D('noVNC_screen');
+
+ // Hide the scrollbars until the size is calculated
+ screen.style.overflow = "hidden";
+
+ var pos = Util.getPosition(screen);
+ var w = pos.width;
+ var h = pos.height;
+
+ screen.style.overflow = "visible";
+
+ if (isNaN(w) || isNaN(h)) {
+ return false;
+ } else {
+ return {w: w, h: h};
+ }
+ },
+
+ // Normally we only apply the current resize mode after a window resize
+ // event. This means that when a new connection is opened, there is no
+ // resize mode active.
+ // We have to wait until the first FBU because this is where the client
+ // will find the supported encodings of the server. Some calls later in
+ // the chain is dependant on knowing the server-capabilities.
+ initialResize: function(rfb, fbu) {
+ UI.applyResizeMode();
+ // After doing this once, we remove the callback.
+ UI.rfb.set_onFBUComplete(function() { });
+ },
+
+/* ------^-------
+ * /RESIZE
+ * ==============
+ * CLIPPING
+ * ------v------*/
+
+ // Set and configure viewport clipping
+ setViewClip: function(clip) {
+ var display;
+ if (UI.rfb) {
+ display = UI.rfb.get_display();
+ } else {
+ UI.forceSetting('clip', clip);
+ return;
+ }
+
+ var cur_clip = display.get_viewport();
+
+ if (typeof(clip) !== 'boolean') {
+ // Use current setting
+ clip = UI.getSetting('clip');
+ }
+
+ if (clip && !cur_clip) {
+ // Turn clipping on
+ UI.updateSetting('clip', true);
+ } else if (!clip && cur_clip) {
+ // Turn clipping off
+ UI.updateSetting('clip', false);
+ display.set_viewport(false);
+ // Disable max dimensions
+ display.set_maxWidth(0);
+ display.set_maxHeight(0);
+ display.viewportChangeSize();
+ }
+ if (UI.getSetting('clip')) {
+ // If clipping, update clipping settings
+ display.set_viewport(true);
+
+ var size = UI.screenSize();
+ if (size) {
+ display.set_maxWidth(size.w);
+ display.set_maxHeight(size.h);
+
+ // Hide potential scrollbars that can skew the position
+ $D('noVNC_screen').style.overflow = "hidden";
+
+ // The x position marks the left margin of the canvas,
+ // remove the margin from both sides to keep it centered
+ var new_w = size.w - (2 * Util.getPosition($D('noVNC_canvas')).x);
+
+ $D('noVNC_screen').style.overflow = "visible";
+
+ display.viewportChangeSize(new_w, size.h);
+ }
+ }
+ },
+
+ // Handle special cases where clipping is forced on/off or locked
+ enableDisableViewClip: function() {
+ var resizeSetting = $D('noVNC_setting_resize');
+ var connected = UI.rfb && UI.rfb_state === 'normal';
+
+ if (UI.isSafari) {
+ // Safari auto-hides the scrollbars which makes them
+ // impossible to use in most cases
+ UI.setViewClip(true);
+ $D('noVNC_setting_clip').disabled = true;
+ } else if (resizeSetting.value === 'downscale' || resizeSetting.value === 'scale') {
+ // Disable clipping if we are scaling
+ UI.setViewClip(false);
+ $D('noVNC_setting_clip').disabled = true;
+ } else if (document.msFullscreenElement) {
+ // The browser is IE and we are in fullscreen mode.
+ // - We need to force clipping while in fullscreen since
+ // scrollbars doesn't work.
+ UI.popupStatus("Forcing clipping mode since scrollbars aren't supported by IE in fullscreen");
+ UI.rememberedClipSetting = UI.getSetting('clip');
+ UI.setViewClip(true);
+ $D('noVNC_setting_clip').disabled = true;
+ } else if (document.body.msRequestFullscreen && UI.rememberedClip !== null) {
+ // Restore view clip to what it was before fullscreen on IE
+ UI.setViewClip(UI.rememberedClipSetting);
+ $D('noVNC_setting_clip').disabled = connected || UI.isTouchDevice;
+ } else {
+ $D('noVNC_setting_clip').disabled = connected || UI.isTouchDevice;
+ if (UI.isTouchDevice) {
+ UI.setViewClip(true);
+ }
+ }
+ },
+
+/* ------^-------
+ * /CLIPPING
+ * ==============
+ * VIEWDRAG
+ * ------v------*/
+
+ // Update the viewport drag state
+ updateViewDrag: function(drag) {
+ if (!UI.rfb) return;
+
+ var viewDragButton = $D('noVNC_view_drag_button');
+
+ // Check if viewport drag is possible. It is only possible
+ // if the remote display is clipping the client display.
+ if (UI.rfb_state === 'normal' &&
+ UI.rfb.get_display().get_viewport() &&
+ UI.rfb.get_display().clippingDisplay()) {
+
+ viewDragButton.style.display = "inline";
+ viewDragButton.disabled = false;
+
+ } else {
+ // The size of the remote display is the same or smaller
+ // than the client display. Make sure viewport drag isn't
+ // active when it can't be used.
+ if (UI.rfb.get_viewportDrag) {
+ viewDragButton.className = "noVNC_status_button";
+ UI.rfb.set_viewportDrag(false);
+ }
+
+ // The button is disabled instead of hidden on touch devices
+ if (UI.rfb_state === 'normal' && UI.isTouchDevice) {
+ viewDragButton.style.display = "inline";
+ viewDragButton.disabled = true;
+ } else {
+ viewDragButton.style.display = "none";
+ }
+ return;
+ }
+
+ if (typeof(drag) !== "undefined" &&
+ typeof(drag) !== "object") {
+ if (drag) {
+ viewDragButton.className = "noVNC_status_button_selected";
+ UI.rfb.set_viewportDrag(true);
+ } else {
+ viewDragButton.className = "noVNC_status_button";
+ UI.rfb.set_viewportDrag(false);
+ }
+ }
+ },
+
+ toggleViewDrag: function() {
+ if (!UI.rfb) return;
+
+ var viewDragButton = $D('noVNC_view_drag_button');
+ if (UI.rfb.get_viewportDrag()) {
+ viewDragButton.className = "noVNC_status_button";
+ UI.rfb.set_viewportDrag(false);
+ } else {
+ viewDragButton.className = "noVNC_status_button_selected";
+ UI.rfb.set_viewportDrag(true);
+ }
+ },
+
+/* ------^-------
+ * /VIEWDRAG
+ * ==============
+ * KEYBOARD
+ * ------v------*/
+
+ // On touch devices, show the OS keyboard
+ showKeyboard: function() {
+ var kbi = $D('noVNC_keyboardinput');
+ var skb = $D('noVNC_keyboard_button');
+ var l = kbi.value.length;
+ if(UI.keyboardVisible === false) {
+ kbi.focus();
+ try { kbi.setSelectionRange(l, l); } // Move the caret to the end
+ catch (err) {} // setSelectionRange is undefined in Google Chrome
+ UI.keyboardVisible = true;
+ skb.className = "noVNC_status_button_selected";
+ } else if(UI.keyboardVisible === true) {
+ kbi.blur();
+ skb.className = "noVNC_status_button";
+ UI.keyboardVisible = false;
+ }
+ },
+
+ hideKeyboard: function() {
+ $D('noVNC_keyboard_button').className = "noVNC_status_button";
+ //Weird bug in iOS if you change keyboardVisible
+ //here it does not actually occur so next time
+ //you click keyboard icon it doesnt work.
+ UI.hideKeyboardTimeout = setTimeout(function() {
+ UI.keyboardVisible = false;
+ },100);
+ },
+
+ keepKeyboard: function() {
+ clearTimeout(UI.hideKeyboardTimeout);
+ if(UI.keyboardVisible === true) {
+ $D('noVNC_keyboardinput').focus();
+ $D('noVNC_keyboard_button').className = "noVNC_status_button_selected";
+ } else if(UI.keyboardVisible === false) {
+ $D('noVNC_keyboardinput').blur();
+ $D('noVNC_keyboard_button').className = "noVNC_status_button";
+ }
+ },
+
+ keyboardinputReset: function() {
+ var kbi = $D('noVNC_keyboardinput');
+ kbi.value = new Array(UI.defaultKeyboardinputLen).join("_");
+ UI.lastKeyboardinput = kbi.value;
+ },
+
+ // When normal keyboard events are left uncought, use the input events from
+ // the keyboardinput element instead and generate the corresponding key events.
+ // This code is required since some browsers on Android are inconsistent in
+ // sending keyCodes in the normal keyboard events when using on screen keyboards.
+ keyInput: function(event) {
+
+ if (!UI.rfb) return;
+
+ var newValue = event.target.value;
+
+ if (!UI.lastKeyboardinput) {
+ UI.keyboardinputReset();
+ }
+ var oldValue = UI.lastKeyboardinput;
+
+ var newLen;
+ try {
+ // Try to check caret position since whitespace at the end
+ // will not be considered by value.length in some browsers
+ newLen = Math.max(event.target.selectionStart, newValue.length);
+ } catch (err) {
+ // selectionStart is undefined in Google Chrome
+ newLen = newValue.length;
+ }
+ var oldLen = oldValue.length;
+
+ var backspaces;
+ var inputs = newLen - oldLen;
+ if (inputs < 0) {
+ backspaces = -inputs;
+ } else {
+ backspaces = 0;
+ }
+
+ // Compare the old string with the new to account for
+ // text-corrections or other input that modify existing text
+ var i;
+ for (i = 0; i < Math.min(oldLen, newLen); i++) {
+ if (newValue.charAt(i) != oldValue.charAt(i)) {
+ inputs = newLen - i;
+ backspaces = oldLen - i;
+ break;
+ }
+ }
+
+ // Send the key events
+ for (i = 0; i < backspaces; i++) {
+ UI.rfb.sendKey(XK_BackSpace);
+ }
+ for (i = newLen - inputs; i < newLen; i++) {
+ UI.rfb.sendKey(newValue.charCodeAt(i));
+ }
+
+ // Control the text content length in the keyboardinput element
+ if (newLen > 2 * UI.defaultKeyboardinputLen) {
+ UI.keyboardinputReset();
+ } else if (newLen < 1) {
+ // There always have to be some text in the keyboardinput
+ // element with which backspace can interact.
+ UI.keyboardinputReset();
+ // This sometimes causes the keyboard to disappear for a second
+ // but it is required for the android keyboard to recognize that
+ // text has been added to the field
+ event.target.blur();
+ // This has to be ran outside of the input handler in order to work
+ setTimeout(UI.keepKeyboard, 0);
+ } else {
+ UI.lastKeyboardinput = newValue;
+ }
+ },
+
+ toggleExtraKeys: function() {
+ UI.keepKeyboard();
+ if(UI.extraKeysVisible === false) {
+ $D('noVNC_toggleCtrl_button').style.display = "inline";
+ $D('noVNC_toggleAlt_button').style.display = "inline";
+ $D('noVNC_sendTab_button').style.display = "inline";
+ $D('noVNC_sendEsc_button').style.display = "inline";
+ $D('noVNC_toggleExtraKeys_button').className = "noVNC_status_button_selected";
+ UI.extraKeysVisible = true;
+ } else if(UI.extraKeysVisible === true) {
+ $D('noVNC_toggleCtrl_button').style.display = "";
+ $D('noVNC_toggleAlt_button').style.display = "";
+ $D('noVNC_sendTab_button').style.display = "";
+ $D('noVNC_sendEsc_button').style.display = "";
+ $D('noVNC_toggleExtraKeys_button').className = "noVNC_status_button";
+ UI.extraKeysVisible = false;
+ }
+ },
+
+ sendEsc: function() {
+ UI.keepKeyboard();
+ UI.rfb.sendKey(XK_Escape);
+ },
+
+ sendTab: function() {
+ UI.keepKeyboard();
+ UI.rfb.sendKey(XK_Tab);
+ },
+
+ toggleCtrl: function() {
+ UI.keepKeyboard();
+ if(UI.ctrlOn === false) {
+ UI.rfb.sendKey(XK_Control_L, true);
+ $D('noVNC_toggleCtrl_button').className = "noVNC_status_button_selected";
+ UI.ctrlOn = true;
+ } else if(UI.ctrlOn === true) {
+ UI.rfb.sendKey(XK_Control_L, false);
+ $D('noVNC_toggleCtrl_button').className = "noVNC_status_button";
+ UI.ctrlOn = false;
+ }
+ },
+
+ toggleAlt: function() {
+ UI.keepKeyboard();
+ if(UI.altOn === false) {
+ UI.rfb.sendKey(XK_Alt_L, true);
+ $D('noVNC_toggleAlt_button').className = "noVNC_status_button_selected";
+ UI.altOn = true;
+ } else if(UI.altOn === true) {
+ UI.rfb.sendKey(XK_Alt_L, false);
+ $D('noVNC_toggleAlt_button').className = "noVNC_status_button";
+ UI.altOn = false;
+ }
+ },
+
+ sendCtrlAltDel: function() {
+ UI.rfb.sendCtrlAltDel();
+ },
+
+/* ------^-------
+ * /KEYBOARD
+ * ==============
+ * MISC
+ * ------v------*/
+
+ setMouseButton: function(num) {
+ if (typeof num === 'undefined') {
+ // Disable mouse buttons
+ num = -1;
+ }
+ if (UI.rfb) {
+ UI.rfb.get_mouse().set_touchButton(num);
+ }
+
+ var blist = [0, 1,2,4];
+ for (var b = 0; b < blist.length; b++) {
+ var button = $D('noVNC_mouse_button' + blist[b]);
+ if (blist[b] === num) {
+ button.style.display = "";
+ } else {
+ button.style.display = "none";
+ }
+ }
+ },
+
+ displayBlur: function() {
+ if (!UI.rfb) return;
+
+ UI.rfb.get_keyboard().set_focused(false);
+ UI.rfb.get_mouse().set_focused(false);
+ },
+
+ displayFocus: function() {
+ if (!UI.rfb) return;
+
+ UI.rfb.get_keyboard().set_focused(true);
+ UI.rfb.get_mouse().set_focused(true);
+ },
+
+ // Display the desktop name in the document title
+ updateDocumentTitle: function(rfb, name) {
+ document.title = name + " - noVNC";
+ },
+
+ //Helper to add options to dropdown.
+ addOption: function(selectbox, text, value) {
+ var optn = document.createElement("OPTION");
+ optn.text = text;
+ optn.value = value;
+ selectbox.options.add(optn);
+ },
+
+ setBarPosition: function() {
+ $D('noVNC_control_bar').style.top = (window.pageYOffset) + 'px';
+ $D('noVNC_mobile_buttons').style.left = (window.pageXOffset) + 'px';
+
+ var vncwidth = $D('noVNC_container').style.offsetWidth;
+ $D('noVNC_control_bar').style.width = vncwidth + 'px';
+ }
+
+/* ------^-------
+ * /MISC
+ * ==============
+ */
+ };
+})();
diff --git a/public/novnc/include/util.js b/public/novnc/include/util.js
new file mode 100644
index 00000000..ed0e3cde
--- /dev/null
+++ b/public/novnc/include/util.js
@@ -0,0 +1,622 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2012 Joel Martin
+ * Licensed under MPL 2.0 (see LICENSE.txt)
+ *
+ * See README.md for usage and integration instructions.
+ */
+
+/* jshint white: false, nonstandard: true */
+/*global window, console, document, navigator, ActiveXObject, INCLUDE_URI */
+
+// Globals defined here
+var Util = {};
+
+
+/*
+ * Make arrays quack
+ */
+
+var addFunc = function (cl, name, func) {
+ if (!cl.prototype[name]) {
+ Object.defineProperty(cl.prototype, name, { enumerable: false, value: func });
+ }
+};
+
+addFunc(Array, 'push8', function (num) {
+ "use strict";
+ this.push(num & 0xFF);
+});
+
+addFunc(Array, 'push16', function (num) {
+ "use strict";
+ this.push((num >> 8) & 0xFF,
+ num & 0xFF);
+});
+
+addFunc(Array, 'push32', function (num) {
+ "use strict";
+ this.push((num >> 24) & 0xFF,
+ (num >> 16) & 0xFF,
+ (num >> 8) & 0xFF,
+ num & 0xFF);
+});
+
+// IE does not support map (even in IE9)
+//This prototype is provided by the Mozilla foundation and
+//is distributed under the MIT license.
+//http://www.ibiblio.org/pub/Linux/LICENSES/mit.license
+addFunc(Array, 'map', function (fun /*, thisp*/) {
+ "use strict";
+ 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;
+});
+
+// IE <9 does not support indexOf
+//This prototype is provided by the Mozilla foundation and
+//is distributed under the MIT license.
+//http://www.ibiblio.org/pub/Linux/LICENSES/mit.license
+addFunc(Array, 'indexOf', function (elt /*, from*/) {
+ "use strict";
+ var len = this.length >>> 0;
+
+ var from = Number(arguments[1]) || 0;
+ from = (from < 0) ? Math.ceil(from) : Math.floor(from);
+ if (from < 0) {
+ from += len;
+ }
+
+ for (; from < len; from++) {
+ if (from in this &&
+ this[from] === elt) {
+ return from;
+ }
+ }
+ return -1;
+});
+
+// From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys
+if (!Object.keys) {
+ Object.keys = (function () {
+ 'use strict';
+ var hasOwnProperty = Object.prototype.hasOwnProperty,
+ hasDontEnumBug = !({toString: null}).propertyIsEnumerable('toString'),
+ dontEnums = [
+ 'toString',
+ 'toLocaleString',
+ 'valueOf',
+ 'hasOwnProperty',
+ 'isPrototypeOf',
+ 'propertyIsEnumerable',
+ 'constructor'
+ ],
+ dontEnumsLength = dontEnums.length;
+
+ return function (obj) {
+ if (typeof obj !== 'object' && (typeof obj !== 'function' || obj === null)) {
+ throw new TypeError('Object.keys called on non-object');
+ }
+
+ var result = [], prop, i;
+
+ for (prop in obj) {
+ if (hasOwnProperty.call(obj, prop)) {
+ result.push(prop);
+ }
+ }
+
+ if (hasDontEnumBug) {
+ for (i = 0; i < dontEnumsLength; i++) {
+ if (hasOwnProperty.call(obj, dontEnums[i])) {
+ result.push(dontEnums[i]);
+ }
+ }
+ }
+ return result;
+ };
+ })();
+}
+
+// PhantomJS 1.x doesn't support bind,
+// so leave this in until PhantomJS 2.0 is released
+//This prototype is provided by the Mozilla foundation and
+//is distributed under the MIT license.
+//http://www.ibiblio.org/pub/Linux/LICENSES/mit.license
+addFunc(Function, 'bind', function (oThis) {
+ if (typeof this !== "function") {
+ // closest thing possible to the ECMAScript 5
+ // internal IsCallable function
+ throw new TypeError("Function.prototype.bind - " +
+ "what is trying to be bound is not callable");
+ }
+
+ var aArgs = Array.prototype.slice.call(arguments, 1),
+ fToBind = this,
+ fNOP = function () {},
+ fBound = function () {
+ return fToBind.apply(this instanceof fNOP && oThis ? this
+ : oThis,
+ aArgs.concat(Array.prototype.slice.call(arguments)));
+ };
+
+ fNOP.prototype = this.prototype;
+ fBound.prototype = new fNOP();
+
+ return fBound;
+});
+
+//
+// requestAnimationFrame shim with setTimeout fallback
+//
+
+window.requestAnimFrame = (function () {
+ "use strict";
+ return window.requestAnimationFrame ||
+ window.webkitRequestAnimationFrame ||
+ window.mozRequestAnimationFrame ||
+ window.oRequestAnimationFrame ||
+ window.msRequestAnimationFrame ||
+ function (callback) {
+ window.setTimeout(callback, 1000 / 60);
+ };
+})();
+
+/*
+ * ------------------------------------------------------
+ * Namespaced in Util
+ * ------------------------------------------------------
+ */
+
+/*
+ * Logging/debug routines
+ */
+
+Util._log_level = 'warn';
+Util.init_logging = function (level) {
+ "use strict";
+ if (typeof level === 'undefined') {
+ level = Util._log_level;
+ } else {
+ Util._log_level = level;
+ }
+ if (typeof window.console === "undefined") {
+ if (typeof window.opera !== "undefined") {
+ window.console = {
+ 'log' : window.opera.postError,
+ 'warn' : window.opera.postError,
+ 'error': window.opera.postError
+ };
+ } else {
+ window.console = {
+ 'log' : function (m) {},
+ 'warn' : function (m) {},
+ 'error': function (m) {}
+ };
+ }
+ }
+
+ Util.Debug = Util.Info = Util.Warn = Util.Error = function (msg) {};
+ /* jshint -W086 */
+ switch (level) {
+ case 'debug':
+ Util.Debug = function (msg) { console.log(msg); };
+ case 'info':
+ Util.Info = function (msg) { console.log(msg); };
+ case 'warn':
+ Util.Warn = function (msg) { console.warn(msg); };
+ case 'error':
+ Util.Error = function (msg) { console.error(msg); };
+ case 'none':
+ break;
+ default:
+ throw new Error("invalid logging type '" + level + "'");
+ }
+ /* jshint +W086 */
+};
+Util.get_logging = function () {
+ return Util._log_level;
+};
+// Initialize logging level
+Util.init_logging();
+
+Util.make_property = function (proto, name, mode, type) {
+ "use strict";
+
+ var getter;
+ if (type === 'arr') {
+ getter = function (idx) {
+ if (typeof idx !== 'undefined') {
+ return this['_' + name][idx];
+ } else {
+ return this['_' + name];
+ }
+ };
+ } else {
+ getter = function () {
+ return this['_' + name];
+ };
+ }
+
+ var make_setter = function (process_val) {
+ if (process_val) {
+ return function (val, idx) {
+ if (typeof idx !== 'undefined') {
+ this['_' + name][idx] = process_val(val);
+ } else {
+ this['_' + name] = process_val(val);
+ }
+ };
+ } else {
+ return function (val, idx) {
+ if (typeof idx !== 'undefined') {
+ this['_' + name][idx] = val;
+ } else {
+ this['_' + name] = val;
+ }
+ };
+ }
+ };
+
+ var setter;
+ if (type === 'bool') {
+ setter = make_setter(function (val) {
+ if (!val || (val in {'0': 1, 'no': 1, 'false': 1})) {
+ return false;
+ } else {
+ return true;
+ }
+ });
+ } else if (type === 'int') {
+ setter = make_setter(function (val) { return parseInt(val, 10); });
+ } else if (type === 'float') {
+ setter = make_setter(parseFloat);
+ } else if (type === 'str') {
+ setter = make_setter(String);
+ } else if (type === 'func') {
+ setter = make_setter(function (val) {
+ if (!val) {
+ return function () {};
+ } else {
+ return val;
+ }
+ });
+ } else if (type === 'arr' || type === 'dom' || type == 'raw') {
+ setter = make_setter();
+ } else {
+ throw new Error('Unknown property type ' + type); // some sanity checking
+ }
+
+ // set the getter
+ if (typeof proto['get_' + name] === 'undefined') {
+ proto['get_' + name] = getter;
+ }
+
+ // set the setter if needed
+ if (typeof proto['set_' + name] === 'undefined') {
+ if (mode === 'rw') {
+ proto['set_' + name] = setter;
+ } else if (mode === 'wo') {
+ proto['set_' + name] = function (val, idx) {
+ if (typeof this['_' + name] !== 'undefined') {
+ throw new Error(name + " can only be set once");
+ }
+ setter.call(this, val, idx);
+ };
+ }
+ }
+
+ // make a special setter that we can use in set defaults
+ proto['_raw_set_' + name] = function (val, idx) {
+ setter.call(this, val, idx);
+ //delete this['_init_set_' + name]; // remove it after use
+ };
+};
+
+Util.make_properties = function (constructor, arr) {
+ "use strict";
+ for (var i = 0; i < arr.length; i++) {
+ Util.make_property(constructor.prototype, arr[i][0], arr[i][1], arr[i][2]);
+ }
+};
+
+Util.set_defaults = function (obj, conf, defaults) {
+ var defaults_keys = Object.keys(defaults);
+ var conf_keys = Object.keys(conf);
+ var keys_obj = {};
+ var i;
+ for (i = 0; i < defaults_keys.length; i++) { keys_obj[defaults_keys[i]] = 1; }
+ for (i = 0; i < conf_keys.length; i++) { keys_obj[conf_keys[i]] = 1; }
+ var keys = Object.keys(keys_obj);
+
+ for (i = 0; i < keys.length; i++) {
+ var setter = obj['_raw_set_' + keys[i]];
+ if (!setter) {
+ Util.Warn('Invalid property ' + keys[i]);
+ continue;
+ }
+
+ if (keys[i] in conf) {
+ setter.call(obj, conf[keys[i]]);
+ } else {
+ setter.call(obj, defaults[keys[i]]);
+ }
+ }
+};
+
+/*
+ * Decode from UTF-8
+ */
+Util.decodeUTF8 = function (utf8string) {
+ "use strict";
+ return decodeURIComponent(escape(utf8string));
+};
+
+
+
+/*
+ * Cross-browser routines
+ */
+
+
+// Dynamically load scripts without using document.write()
+// Reference: http://unixpapa.com/js/dyna.html
+//
+// Handles the case where load_scripts is invoked from a script that
+// itself is loaded via load_scripts. Once all scripts are loaded the
+// window.onscriptsloaded handler is called (if set).
+Util.get_include_uri = function () {
+ return (typeof INCLUDE_URI !== "undefined") ? INCLUDE_URI : "include/";
+};
+Util._loading_scripts = [];
+Util._pending_scripts = [];
+Util.load_scripts = function (files) {
+ "use strict";
+ var head = document.getElementsByTagName('head')[0], script,
+ ls = Util._loading_scripts, ps = Util._pending_scripts;
+
+ var loadFunc = function (e) {
+ while (ls.length > 0 && (ls[0].readyState === 'loaded' ||
+ ls[0].readyState === 'complete')) {
+ // For IE, append the script to trigger execution
+ var s = ls.shift();
+ //console.log("loaded script: " + s.src);
+ head.appendChild(s);
+ }
+ if (!this.readyState ||
+ (Util.Engine.presto && this.readyState === 'loaded') ||
+ this.readyState === 'complete') {
+ if (ps.indexOf(this) >= 0) {
+ this.onload = this.onreadystatechange = null;
+ //console.log("completed script: " + this.src);
+ ps.splice(ps.indexOf(this), 1);
+
+ // Call window.onscriptsload after last script loads
+ if (ps.length === 0 && window.onscriptsload) {
+ window.onscriptsload();
+ }
+ }
+ }
+ };
+
+ for (var f = 0; f < files.length; f++) {
+ script = document.createElement('script');
+ script.type = 'text/javascript';
+ script.src = Util.get_include_uri() + files[f];
+ //console.log("loading script: " + script.src);
+ script.onload = script.onreadystatechange = loadFunc;
+ // In-order script execution tricks
+ if (Util.Engine.trident) {
+ // For IE wait until readyState is 'loaded' before
+ // appending it which will trigger execution
+ // http://wiki.whatwg.org/wiki/Dynamic_Script_Execution_Order
+ ls.push(script);
+ } else {
+ // For webkit and firefox set async=false and append now
+ // https://developer.mozilla.org/en-US/docs/HTML/Element/script
+ script.async = false;
+ head.appendChild(script);
+ }
+ ps.push(script);
+ }
+};
+
+
+Util.getPosition = function(obj) {
+ "use strict";
+ // NB(sross): the Mozilla developer reference seems to indicate that
+ // getBoundingClientRect includes border and padding, so the canvas
+ // style should NOT include either.
+ var objPosition = obj.getBoundingClientRect();
+ return {'x': objPosition.left + window.pageXOffset, 'y': objPosition.top + window.pageYOffset,
+ 'width': objPosition.width, 'height': objPosition.height};
+};
+
+
+// Get mouse event position in DOM element
+Util.getEventPosition = function (e, obj, scale) {
+ "use strict";
+ var evt, docX, docY, pos;
+ //if (!e) evt = window.event;
+ evt = (e ? e : window.event);
+ evt = (evt.changedTouches ? evt.changedTouches[0] : evt.touches ? evt.touches[0] : evt);
+ if (evt.pageX || evt.pageY) {
+ docX = evt.pageX;
+ docY = evt.pageY;
+ } else if (evt.clientX || evt.clientY) {
+ docX = evt.clientX + document.body.scrollLeft +
+ document.documentElement.scrollLeft;
+ docY = evt.clientY + document.body.scrollTop +
+ document.documentElement.scrollTop;
+ }
+ pos = Util.getPosition(obj);
+ if (typeof scale === "undefined") {
+ scale = 1;
+ }
+ var realx = docX - pos.x;
+ var realy = docY - pos.y;
+ var x = Math.max(Math.min(realx, pos.width - 1), 0);
+ var y = Math.max(Math.min(realy, pos.height - 1), 0);
+ return {'x': x / scale, 'y': y / scale, 'realx': realx / scale, 'realy': realy / scale};
+};
+
+
+// Event registration. Based on: http://www.scottandrew.com/weblog/articles/cbs-events
+Util.addEvent = function (obj, evType, fn) {
+ "use strict";
+ if (obj.attachEvent) {
+ var r = obj.attachEvent("on" + evType, fn);
+ return r;
+ } else if (obj.addEventListener) {
+ obj.addEventListener(evType, fn, false);
+ return true;
+ } else {
+ throw new Error("Handler could not be attached");
+ }
+};
+
+Util.removeEvent = function (obj, evType, fn) {
+ "use strict";
+ if (obj.detachEvent) {
+ var r = obj.detachEvent("on" + evType, fn);
+ return r;
+ } else if (obj.removeEventListener) {
+ obj.removeEventListener(evType, fn, false);
+ return true;
+ } else {
+ throw new Error("Handler could not be removed");
+ }
+};
+
+Util.stopEvent = function (e) {
+ "use strict";
+ if (e.stopPropagation) { e.stopPropagation(); }
+ else { e.cancelBubble = true; }
+
+ if (e.preventDefault) { e.preventDefault(); }
+ else { e.returnValue = false; }
+};
+
+Util._cursor_uris_supported = null;
+
+Util.browserSupportsCursorURIs = function () {
+ if (Util._cursor_uris_supported === null) {
+ try {
+ var target = document.createElement('canvas');
+ target.style.cursor = 'url("data:image/x-icon;base64,AAACAAEACAgAAAIAAgA4AQAAFgAAACgAAAAIAAAAEAAAAAEAIAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAAAAAAAAAAAAAAAA==") 2 2, default';
+
+ if (target.style.cursor) {
+ Util.Info("Data URI scheme cursor supported");
+ Util._cursor_uris_supported = true;
+ } else {
+ Util.Warn("Data URI scheme cursor not supported");
+ Util._cursor_uris_supported = false;
+ }
+ } catch (exc) {
+ Util.Error("Data URI scheme cursor test exception: " + exc);
+ Util._cursor_uris_supported = false;
+ }
+ }
+
+ return Util._cursor_uris_supported;
+};
+
+// Set browser engine versions. Based on mootools.
+Util.Features = {xpath: !!(document.evaluate), air: !!(window.runtime), query: !!(document.querySelector)};
+
+(function () {
+ "use strict";
+ // 'presto': (function () { return (!window.opera) ? false : true; }()),
+ var detectPresto = function () {
+ return !!window.opera;
+ };
+
+ // 'trident': (function () { return (!window.ActiveXObject) ? false : ((window.XMLHttpRequest) ? ((document.querySelectorAll) ? 6 : 5) : 4);
+ var detectTrident = function () {
+ if (!window.ActiveXObject) {
+ return false;
+ } else {
+ if (window.XMLHttpRequest) {
+ return (document.querySelectorAll) ? 6 : 5;
+ } else {
+ return 4;
+ }
+ }
+ };
+
+ // 'webkit': (function () { try { return (navigator.taintEnabled) ? false : ((Util.Features.xpath) ? ((Util.Features.query) ? 525 : 420) : 419); } catch (e) { return false; } }()),
+ var detectInitialWebkit = function () {
+ try {
+ if (navigator.taintEnabled) {
+ return false;
+ } else {
+ if (Util.Features.xpath) {
+ return (Util.Features.query) ? 525 : 420;
+ } else {
+ return 419;
+ }
+ }
+ } catch (e) {
+ return false;
+ }
+ };
+
+ var detectActualWebkit = function (initial_ver) {
+ var re = /WebKit\/([0-9\.]*) /;
+ var str_ver = (navigator.userAgent.match(re) || ['', initial_ver])[1];
+ return parseFloat(str_ver, 10);
+ };
+
+ // 'gecko': (function () { return (!document.getBoxObjectFor && window.mozInnerScreenX == null) ? false : ((document.getElementsByClassName) ? 19ssName) ? 19 : 18 : 18); }())
+ var detectGecko = function () {
+ /* jshint -W041 */
+ if (!document.getBoxObjectFor && window.mozInnerScreenX == null) {
+ return false;
+ } else {
+ return (document.getElementsByClassName) ? 19 : 18;
+ }
+ /* jshint +W041 */
+ };
+
+ Util.Engine = {
+ // 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': detectPresto(),
+ 'trident': detectTrident(),
+ 'webkit': detectInitialWebkit(),
+ 'gecko': detectGecko(),
+ };
+
+ if (Util.Engine.webkit) {
+ // Extract actual webkit version if available
+ Util.Engine.webkit = detectActualWebkit(Util.Engine.webkit);
+ }
+})();
+
+Util.Flash = (function () {
+ "use strict";
+ var v, version;
+ try {
+ v = navigator.plugins['Shockwave Flash'].description;
+ } catch (err1) {
+ try {
+ v = new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version');
+ } catch (err2) {
+ v = '0 r0';
+ }
+ }
+ version = v.match(/\d+/g);
+ return {version: parseInt(version[0] || 0 + '.' + version[1], 10) || 0, build: parseInt(version[2], 10) || 0};
+}());
diff --git a/public/novnc/include/websock.js b/public/novnc/include/websock.js
new file mode 100644
index 00000000..4d7a4f9a
--- /dev/null
+++ b/public/novnc/include/websock.js
@@ -0,0 +1,440 @@
+/*
+ * Websock: high-performance binary WebSockets
+ * Copyright (C) 2012 Joel Martin
+ * Licensed under MPL 2.0 (see LICENSE.txt)
+ *
+ * Websock is similar to the standard WebSocket object but Websock
+ * enables communication with raw TCP sockets (i.e. the binary stream)
+ * via websockify. This is accomplished by base64 encoding the data
+ * stream between Websock and websockify.
+ *
+ * Websock has built-in receive queue buffering; the message event
+ * does not contain actual data but is simply a notification that
+ * there is new data available. Several rQ* methods are available to
+ * read binary data off of the receive queue.
+ */
+
+/*jslint browser: true, bitwise: true */
+/*global Util*/
+
+
+// Load Flash WebSocket emulator if needed
+
+// To force WebSocket emulator even when native WebSocket available
+//window.WEB_SOCKET_FORCE_FLASH = true;
+// To enable WebSocket emulator debug:
+//window.WEB_SOCKET_DEBUG=1;
+
+if (window.WebSocket && !window.WEB_SOCKET_FORCE_FLASH) {
+ Websock_native = true;
+} 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 */
+
+ Websock_native = false;
+}
+
+function Websock() {
+ "use strict";
+
+ this._websocket = null; // WebSocket object
+
+ this._rQi = 0; // Receive queue index
+ this._rQlen = 0; // Next write position in the receive queue
+ this._rQbufferSize = 1024 * 1024 * 4; // Receive queue buffer size (4 MiB)
+ this._rQmax = this._rQbufferSize / 8;
+ // called in init: this._rQ = new Uint8Array(this._rQbufferSize);
+ this._rQ = null; // Receive queue
+
+ this._sQbufferSize = 1024 * 10; // 10 KiB
+ // called in init: this._sQ = new Uint8Array(this._sQbufferSize);
+ this._sQlen = 0;
+ this._sQ = null; // Send queue
+
+ this._mode = 'binary'; // Current WebSocket mode: 'binary', 'base64'
+ this.maxBufferedAmount = 200;
+
+ this._eventHandlers = {
+ 'message': function () {},
+ 'open': function () {},
+ 'close': function () {},
+ 'error': function () {}
+ };
+}
+
+(function () {
+ "use strict";
+ // this has performance issues in some versions Chromium, and
+ // doesn't gain a tremendous amount of performance increase in Firefox
+ // at the moment. It may be valuable to turn it on in the future.
+ var ENABLE_COPYWITHIN = false;
+
+ var MAX_RQ_GROW_SIZE = 40 * 1024 * 1024; // 40 MiB
+
+ var typedArrayToString = (function () {
+ // This is only for PhantomJS, which doesn't like apply-ing
+ // with Typed Arrays
+ try {
+ var arr = new Uint8Array([1, 2, 3]);
+ String.fromCharCode.apply(null, arr);
+ return function (a) { return String.fromCharCode.apply(null, a); };
+ } catch (ex) {
+ return function (a) {
+ return String.fromCharCode.apply(
+ null, Array.prototype.slice.call(a));
+ };
+ }
+ })();
+
+ Websock.prototype = {
+ // Getters and Setters
+ get_sQ: function () {
+ return this._sQ;
+ },
+
+ get_rQ: function () {
+ return this._rQ;
+ },
+
+ get_rQi: function () {
+ return this._rQi;
+ },
+
+ set_rQi: function (val) {
+ this._rQi = val;
+ },
+
+ // Receive Queue
+ rQlen: function () {
+ return this._rQlen - this._rQi;
+ },
+
+ rQpeek8: function () {
+ return this._rQ[this._rQi];
+ },
+
+ rQshift8: function () {
+ return this._rQ[this._rQi++];
+ },
+
+ rQskip8: function () {
+ this._rQi++;
+ },
+
+ rQskipBytes: function (num) {
+ this._rQi += num;
+ },
+
+ // TODO(directxman12): test performance with these vs a DataView
+ rQshift16: function () {
+ return (this._rQ[this._rQi++] << 8) +
+ this._rQ[this._rQi++];
+ },
+
+ rQshift32: function () {
+ return (this._rQ[this._rQi++] << 24) +
+ (this._rQ[this._rQi++] << 16) +
+ (this._rQ[this._rQi++] << 8) +
+ this._rQ[this._rQi++];
+ },
+
+ rQshiftStr: function (len) {
+ if (typeof(len) === 'undefined') { len = this.rQlen(); }
+ var arr = new Uint8Array(this._rQ.buffer, this._rQi, len);
+ this._rQi += len;
+ return typedArrayToString(arr);
+ },
+
+ rQshiftBytes: function (len) {
+ if (typeof(len) === 'undefined') { len = this.rQlen(); }
+ this._rQi += len;
+ return new Uint8Array(this._rQ.buffer, this._rQi - len, len);
+ },
+
+ rQshiftTo: function (target, len) {
+ if (len === undefined) { len = this.rQlen(); }
+ // TODO: make this just use set with views when using a ArrayBuffer to store the rQ
+ target.set(new Uint8Array(this._rQ.buffer, this._rQi, len));
+ this._rQi += len;
+ },
+
+ rQwhole: function () {
+ return new Uint8Array(this._rQ.buffer, 0, this._rQlen);
+ },
+
+ rQslice: function (start, end) {
+ if (end) {
+ return new Uint8Array(this._rQ.buffer, this._rQi + start, end - start);
+ } else {
+ return new Uint8Array(this._rQ.buffer, this._rQi + start, this._rQlen - this._rQi - start);
+ }
+ },
+
+ // Check to see if we must wait for 'num' bytes (default to FBU.bytes)
+ // to be available in the receive queue. Return true if we need to
+ // wait (and possibly print a debug message), otherwise false.
+ rQwait: function (msg, num, goback) {
+ var rQlen = this._rQlen - this._rQi; // Skip rQlen() function call
+ if (rQlen < num) {
+ if (goback) {
+ if (this._rQi < goback) {
+ throw new Error("rQwait cannot backup " + goback + " bytes");
+ }
+ this._rQi -= goback;
+ }
+ return true; // true means need more data
+ }
+ return false;
+ },
+
+ // Send Queue
+
+ flush: function () {
+ if (this._websocket.bufferedAmount !== 0) {
+ Util.Debug("bufferedAmount: " + this._websocket.bufferedAmount);
+ }
+
+ if (this._websocket.bufferedAmount < this.maxBufferedAmount) {
+ if (this._sQlen > 0 && this._websocket.readyState === WebSocket.OPEN) {
+ this._websocket.send(this._encode_message());
+ this._sQlen = 0;
+ }
+
+ return true;
+ } else {
+ Util.Info("Delaying send, bufferedAmount: " +
+ this._websocket.bufferedAmount);
+ return false;
+ }
+ },
+
+ send: function (arr) {
+ this._sQ.set(arr, this._sQlen);
+ this._sQlen += arr.length;
+ return this.flush();
+ },
+
+ send_string: function (str) {
+ this.send(str.split('').map(function (chr) {
+ return chr.charCodeAt(0);
+ }));
+ },
+
+ // Event Handlers
+ off: function (evt) {
+ this._eventHandlers[evt] = function () {};
+ },
+
+ on: function (evt, handler) {
+ this._eventHandlers[evt] = handler;
+ },
+
+ _allocate_buffers: function () {
+ this._rQ = new Uint8Array(this._rQbufferSize);
+ this._sQ = new Uint8Array(this._sQbufferSize);
+ },
+
+ init: function (protocols, ws_schema) {
+ this._allocate_buffers();
+ this._rQi = 0;
+ this._websocket = null;
+
+ // Check for full typed array support
+ var bt = false;
+ if (('Uint8Array' in window) &&
+ ('set' in Uint8Array.prototype)) {
+ bt = true;
+ }
+
+ // Check for full binary type support in WebSockets
+ // Inspired by:
+ // https://github.com/Modernizr/Modernizr/issues/370
+ // https://github.com/Modernizr/Modernizr/blob/master/feature-detects/websockets/binary.js
+ var wsbt = false;
+ try {
+ if (bt && ('binaryType' in WebSocket.prototype ||
+ !!(new WebSocket(ws_schema + '://.').binaryType))) {
+ Util.Info("Detected binaryType support in WebSockets");
+ wsbt = true;
+ }
+ } catch (exc) {
+ // Just ignore failed test localhost connection
+ }
+
+ // Default protocols if not specified
+ if (typeof(protocols) === "undefined") {
+ protocols = 'binary';
+ }
+
+ if (Array.isArray(protocols) && protocols.indexOf('binary') > -1) {
+ protocols = 'binary';
+ }
+
+ if (!wsbt) {
+ throw new Error("noVNC no longer supports base64 WebSockets. " +
+ "Please use a browser which supports binary WebSockets.");
+ }
+
+ if (protocols != 'binary') {
+ throw new Error("noVNC no longer supports base64 WebSockets. Please " +
+ "use the binary subprotocol instead.");
+ }
+
+ return protocols;
+ },
+
+ open: function (uri, protocols) {
+ var ws_schema = uri.match(/^([a-z]+):\/\//)[1];
+ protocols = this.init(protocols, ws_schema);
+
+ this._websocket = new WebSocket(uri, protocols);
+
+ if (protocols.indexOf('binary') >= 0) {
+ this._websocket.binaryType = 'arraybuffer';
+ }
+
+ this._websocket.onmessage = this._recv_message.bind(this);
+ this._websocket.onopen = (function () {
+ Util.Debug('>> WebSock.onopen');
+ if (this._websocket.protocol) {
+ this._mode = this._websocket.protocol;
+ Util.Info("Server choose sub-protocol: " + this._websocket.protocol);
+ } else {
+ this._mode = 'binary';
+ Util.Error('Server select no sub-protocol!: ' + this._websocket.protocol);
+ }
+
+ if (this._mode != 'binary') {
+ throw new Error("noVNC no longer supports base64 WebSockets. Please " +
+ "use the binary subprotocol instead.");
+
+ }
+
+ this._eventHandlers.open();
+ Util.Debug("<< WebSock.onopen");
+ }).bind(this);
+ this._websocket.onclose = (function (e) {
+ Util.Debug(">> WebSock.onclose");
+ this._eventHandlers.close(e);
+ Util.Debug("<< WebSock.onclose");
+ }).bind(this);
+ this._websocket.onerror = (function (e) {
+ Util.Debug(">> WebSock.onerror: " + e);
+ this._eventHandlers.error(e);
+ Util.Debug("<< WebSock.onerror: " + e);
+ }).bind(this);
+ },
+
+ close: function () {
+ if (this._websocket) {
+ if ((this._websocket.readyState === WebSocket.OPEN) ||
+ (this._websocket.readyState === WebSocket.CONNECTING)) {
+ Util.Info("Closing WebSocket connection");
+ this._websocket.close();
+ }
+
+ this._websocket.onmessage = function (e) { return; };
+ }
+ },
+
+ // private methods
+ _encode_message: function () {
+ // Put in a binary arraybuffer
+ // according to the spec, you can send ArrayBufferViews with the send method
+ return new Uint8Array(this._sQ.buffer, 0, this._sQlen);
+ },
+
+ _expand_compact_rQ: function (min_fit) {
+ var resizeNeeded = min_fit || this._rQlen - this._rQi > this._rQbufferSize / 2;
+ if (resizeNeeded) {
+ if (!min_fit) {
+ // just double the size if we need to do compaction
+ this._rQbufferSize *= 2;
+ } else {
+ // otherwise, make sure we satisy rQlen - rQi + min_fit < rQbufferSize / 8
+ this._rQbufferSize = (this._rQlen - this._rQi + min_fit) * 8;
+ }
+ }
+
+ // we don't want to grow unboundedly
+ if (this._rQbufferSize > MAX_RQ_GROW_SIZE) {
+ this._rQbufferSize = MAX_RQ_GROW_SIZE;
+ if (this._rQbufferSize - this._rQlen - this._rQi < min_fit) {
+ throw new Exception("Receive Queue buffer exceeded " + MAX_RQ_GROW_SIZE + " bytes, and the new message could not fit");
+ }
+ }
+
+ if (resizeNeeded) {
+ var old_rQbuffer = this._rQ.buffer;
+ this._rQmax = this._rQbufferSize / 8;
+ this._rQ = new Uint8Array(this._rQbufferSize);
+ this._rQ.set(new Uint8Array(old_rQbuffer, this._rQi));
+ } else {
+ if (ENABLE_COPYWITHIN) {
+ this._rQ.copyWithin(0, this._rQi);
+ } else {
+ this._rQ.set(new Uint8Array(this._rQ.buffer, this._rQi));
+ }
+ }
+
+ this._rQlen = this._rQlen - this._rQi;
+ this._rQi = 0;
+ },
+
+ _decode_message: function (data) {
+ // push arraybuffer values onto the end
+ var u8 = new Uint8Array(data);
+ if (u8.length > this._rQbufferSize - this._rQlen) {
+ this._expand_compact_rQ(u8.length);
+ }
+ this._rQ.set(u8, this._rQlen);
+ this._rQlen += u8.length;
+ },
+
+ _recv_message: function (e) {
+ try {
+ this._decode_message(e.data);
+ if (this.rQlen() > 0) {
+ this._eventHandlers.message();
+ // Compact the receive queue
+ if (this._rQlen == this._rQi) {
+ this._rQlen = 0;
+ this._rQi = 0;
+ } else if (this._rQlen > this._rQmax) {
+ this._expand_compact_rQ();
+ }
+ } else {
+ Util.Debug("Ignoring empty message");
+ }
+ } catch (exc) {
+ var exception_str = "";
+ if (exc.name) {
+ exception_str += "\n name: " + exc.name + "\n";
+ exception_str += " message: " + exc.message + "\n";
+ }
+
+ if (typeof exc.description !== 'undefined') {
+ exception_str += " description: " + exc.description + "\n";
+ }
+
+ if (typeof exc.stack !== 'undefined') {
+ exception_str += exc.stack;
+ }
+
+ if (exception_str.length > 0) {
+ Util.Error("recv_message, caught exception: " + exception_str);
+ } else {
+ Util.Error("recv_message, caught exception: " + exc);
+ }
+
+ if (typeof exc.name !== 'undefined') {
+ this._eventHandlers.error(exc.name + ": " + exc.message);
+ } else {
+ this._eventHandlers.error(exc);
+ }
+ }
+ }
+ };
+})();
diff --git a/public/novnc/include/webutil.js b/public/novnc/include/webutil.js
new file mode 100644
index 00000000..9ee34730
--- /dev/null
+++ b/public/novnc/include/webutil.js
@@ -0,0 +1,292 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2012 Joel Martin
+ * Copyright (C) 2013 NTT corp.
+ * Licensed under MPL 2.0 (see LICENSE.txt)
+ *
+ * See README.md for usage and integration instructions.
+ */
+
+/*jslint bitwise: false, white: false, browser: true, devel: true */
+/*global Util, window, document */
+
+// Globals defined here
+var WebUtil = {}, $D;
+
+/*
+ * Simple DOM selector by ID
+ */
+if (!window.$D) {
+ window.$D = function (id) {
+ if (document.getElementById) {
+ return document.getElementById(id);
+ } else if (document.all) {
+ return document.all[id];
+ } else if (document.layers) {
+ return document.layers[id];
+ }
+ return undefined;
+ };
+}
+
+
+/*
+ * ------------------------------------------------------
+ * Namespaced in WebUtil
+ * ------------------------------------------------------
+ */
+
+// init log level reading the logging HTTP param
+WebUtil.init_logging = function (level) {
+ "use strict";
+ if (typeof level !== "undefined") {
+ Util._log_level = level;
+ } else {
+ var param = document.location.href.match(/logging=([A-Za-z0-9\._\-]*)/);
+ Util._log_level = (param || ['', Util._log_level])[1];
+ }
+ Util.init_logging();
+};
+
+
+WebUtil.dirObj = function (obj, depth, parent) {
+ "use strict";
+ if (! depth) { depth = 2; }
+ if (! parent) { parent = ""; }
+
+ // Print the properties of the passed-in object
+ var msg = "";
+ for (var i in obj) {
+ if ((depth > 1) && (typeof obj[i] === "object")) {
+ // Recurse attributes that are objects
+ msg += WebUtil.dirObj(obj[i], depth - 1, parent + "." + i);
+ } else {
+ //val = new String(obj[i]).replace("\n", " ");
+ var val = "";
+ if (typeof(obj[i]) === "undefined") {
+ val = "undefined";
+ } else {
+ val = obj[i].toString().replace("\n", " ");
+ }
+ if (val.length > 30) {
+ val = val.substr(0, 30) + "...";
+ }
+ msg += parent + "." + i + ": " + val + "\n";
+ }
+ }
+ return msg;
+};
+
+// Read a query string variable
+WebUtil.getQueryVar = function (name, defVal) {
+ "use strict";
+ var re = new RegExp('.*[?&]' + name + '=([^]*)'),
+ match = document.location.href.match(re);
+ if (typeof defVal === 'undefined') { defVal = null; }
+ if (match) {
+ return decodeURIComponent(match[1]);
+ } else {
+ return defVal;
+ }
+};
+
+// Read a hash fragment variable
+WebUtil.getHashVar = function (name, defVal) {
+ "use strict";
+ var re = new RegExp('.*[]' + name + '=([^&]*)'),
+ match = document.location.hash.match(re);
+ if (typeof defVal === 'undefined') { defVal = null; }
+ if (match) {
+ return decodeURIComponent(match[1]);
+ } else {
+ return defVal;
+ }
+};
+
+// Read a variable from the fragment or the query string
+// Fragment takes precedence
+WebUtil.getConfigVar = function (name, defVal) {
+ "use strict";
+ var val = WebUtil.getHashVar(name);
+ if (val === null) {
+ val = WebUtil.getQueryVar(name, defVal);
+ }
+ return val;
+};
+
+/*
+ * Cookie handling. Dervied from: http://www.quirksmode.org/js/cookies.html
+ */
+
+// No days means only for this browser session
+WebUtil.createCookie = function (name, value, days) {
+ "use strict";
+ var date, expires;
+ if (days) {
+ date = new Date();
+ date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
+ expires = "; expires=" + date.toGMTString();
+ } else {
+ expires = "";
+ }
+
+ var secure;
+ if (document.location.protocol === "https:") {
+ secure = "; secure";
+ } else {
+ secure = "";
+ }
+ document.cookie = name + "=" + value + expires + "; path=/" + secure;
+};
+
+WebUtil.readCookie = function (name, defaultValue) {
+ "use strict";
+ var nameEQ = name + "=",
+ ca = document.cookie.split(';');
+
+ for (var i = 0; i < ca.length; i += 1) {
+ var c = ca[i];
+ while (c.charAt(0) === ' ') { c = c.substring(1, c.length); }
+ if (c.indexOf(nameEQ) === 0) { return c.substring(nameEQ.length, c.length); }
+ }
+ return (typeof defaultValue !== 'undefined') ? defaultValue : null;
+};
+
+WebUtil.eraseCookie = function (name) {
+ "use strict";
+ WebUtil.createCookie(name, "", -1);
+};
+
+/*
+ * Setting handling.
+ */
+
+WebUtil.initSettings = function (callback /*, ...callbackArgs */) {
+ "use strict";
+ var callbackArgs = Array.prototype.slice.call(arguments, 1);
+ if (window.chrome && window.chrome.storage) {
+ window.chrome.storage.sync.get(function (cfg) {
+ WebUtil.settings = cfg;
+ console.log(WebUtil.settings);
+ if (callback) {
+ callback.apply(this, callbackArgs);
+ }
+ });
+ } else {
+ // No-op
+ if (callback) {
+ callback.apply(this, callbackArgs);
+ }
+ }
+};
+
+// No days means only for this browser session
+WebUtil.writeSetting = function (name, value) {
+ "use strict";
+ if (window.chrome && window.chrome.storage) {
+ //console.log("writeSetting:", name, value);
+ if (WebUtil.settings[name] !== value) {
+ WebUtil.settings[name] = value;
+ window.chrome.storage.sync.set(WebUtil.settings);
+ }
+ } else {
+ localStorage.setItem(name, value);
+ }
+};
+
+WebUtil.readSetting = function (name, defaultValue) {
+ "use strict";
+ var value;
+ if (window.chrome && window.chrome.storage) {
+ value = WebUtil.settings[name];
+ } else {
+ value = localStorage.getItem(name);
+ }
+ if (typeof value === "undefined") {
+ value = null;
+ }
+ if (value === null && typeof defaultValue !== undefined) {
+ return defaultValue;
+ } else {
+ return value;
+ }
+};
+
+WebUtil.eraseSetting = function (name) {
+ "use strict";
+ if (window.chrome && window.chrome.storage) {
+ window.chrome.storage.sync.remove(name);
+ delete WebUtil.settings[name];
+ } else {
+ localStorage.removeItem(name);
+ }
+};
+
+/*
+ * Alternate stylesheet selection
+ */
+WebUtil.getStylesheets = function () {
+ "use strict";
+ var links = document.getElementsByTagName("link");
+ var sheets = [];
+
+ for (var i = 0; i < links.length; i += 1) {
+ if (links[i].title &&
+ links[i].rel.toUpperCase().indexOf("STYLESHEET") > -1) {
+ sheets.push(links[i]);
+ }
+ }
+ return sheets;
+};
+
+// No sheet means try and use value from cookie, null sheet used to
+// clear all alternates.
+WebUtil.selectStylesheet = function (sheet) {
+ "use strict";
+ if (typeof sheet === 'undefined') {
+ sheet = 'default';
+ }
+
+ var sheets = WebUtil.getStylesheets();
+ for (var i = 0; i < sheets.length; i += 1) {
+ var link = sheets[i];
+ if (link.title === sheet) {
+ Util.Debug("Using stylesheet " + sheet);
+ link.disabled = false;
+ } else {
+ //Util.Debug("Skipping stylesheet " + link.title);
+ link.disabled = true;
+ }
+ }
+ return sheet;
+};
+
+WebUtil.injectParamIfMissing = function (path, param, value) {
+ // force pretend that we're dealing with a relative path
+ // (assume that we wanted an extra if we pass one in)
+ path = "/" + path;
+
+ var elem = document.createElement('a');
+ elem.href = path;
+
+ var param_eq = encodeURIComponent(param) + "=";
+ var query;
+ if (elem.search) {
+ query = elem.search.slice(1).split('&');
+ } else {
+ query = [];
+ }
+
+ if (!query.some(function (v) { return v.startsWith(param_eq); })) {
+ query.push(param_eq + encodeURIComponent(value));
+ elem.search = "?" + query.join("&");
+ }
+
+ // some browsers (e.g. IE11) may occasionally omit the leading slash
+ // in the elem.pathname string. Handle that case gracefully.
+ if (elem.pathname.charAt(0) == "/") {
+ return elem.pathname.slice(1) + elem.search + elem.hash;
+ } else {
+ return elem.pathname + elem.search + elem.hash;
+ }
+};
diff --git a/public/novnc/karma.conf.js b/public/novnc/karma.conf.js
new file mode 100644
index 00000000..870b8551
--- /dev/null
+++ b/public/novnc/karma.conf.js
@@ -0,0 +1,197 @@
+// Karma configuration
+
+module.exports = function(config) {
+ /*var customLaunchers = {
+ sl_chrome_win7: {
+ base: 'SauceLabs',
+ browserName: 'chrome',
+ platform: 'Windows 7'
+ },
+
+ sl_firefox30_linux: {
+ base: 'SauceLabs',
+ browserName: 'firefox',
+ version: '30',
+ platform: 'Linux'
+ },
+
+ sl_firefox26_linux: {
+ base: 'SauceLabs',
+ browserName: 'firefox',
+ version: 26,
+ platform: 'Linux'
+ },
+
+ sl_windows7_ie10: {
+ base: 'SauceLabs',
+ browserName: 'internet explorer',
+ platform: 'Windows 7',
+ version: '10'
+ },
+
+ sl_windows81_ie11: {
+ base: 'SauceLabs',
+ browserName: 'internet explorer',
+ platform: 'Windows 8.1',
+ version: '11'
+ },
+
+ sl_osxmavericks_safari7: {
+ base: 'SauceLabs',
+ browserName: 'safari',
+ platform: 'OS X 10.9',
+ version: '7'
+ },
+
+ sl_osxmtnlion_safari6: {
+ base: 'SauceLabs',
+ browserName: 'safari',
+ platform: 'OS X 10.8',
+ version: '6'
+ }
+ };*/
+
+ var customLaunchers = {};
+ var browsers = [];
+ var useSauce = false;
+
+ if (process.env.SAUCE_USERNAME && process.env.SAUCE_ACCESS_KEY) {
+ useSauce = true;
+ }
+
+ if (useSauce && process.env.TEST_BROWSER_NAME && process.env.TEST_BROWSER_NAME != 'PhantomJS') {
+ var names = process.env.TEST_BROWSER_NAME.split(',');
+ var platforms = process.env.TEST_BROWSER_OS.split(',');
+ var versions = [];
+ if (process.env.TEST_BROWSER_VERSION) {
+ versions = process.env.TEST_BROWSER_VERSION.split(',');
+ } else {
+ versions = [null];
+ }
+
+ for (var i = 0; i < names.length; i++) {
+ for (var j = 0; j < platforms.length; j++) {
+ for (var k = 0; k < versions.length; k++) {
+ var launcher_name = 'sl_' + platforms[j].replace(/[^a-zA-Z0-9]/g, '') + '_' + names[i];
+ if (versions[k]) {
+ launcher_name += '_' + versions[k];
+ }
+
+ customLaunchers[launcher_name] = {
+ base: 'SauceLabs',
+ browserName: names[i],
+ platform: platforms[j],
+ };
+
+ if (versions[i]) {
+ customLaunchers[launcher_name].version = versions[k];
+ }
+ }
+ }
+ }
+
+ browsers = Object.keys(customLaunchers);
+ } else {
+ useSauce = false;
+ browsers = ['PhantomJS'];
+ }
+
+ var my_conf = {
+
+ // base path that will be used to resolve all patterns (eg. files, exclude)
+ basePath: '',
+
+ // frameworks to use
+ // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
+ frameworks: ['mocha', 'sinon', 'chai', 'sinon-chai'],
+
+
+ // list of files / patterns to load in the browser (loaded in order)
+ files: [
+ 'tests/fake.*.js',
+ 'tests/assertions.js',
+ 'include/util.js', // load first to avoid issues, since methods are called immediately
+ //'../include/*.js',
+ 'include/base64.js',
+ 'include/keysym.js',
+ 'include/keysymdef.js',
+ 'include/keyboard.js',
+ 'include/input.js',
+ 'include/websock.js',
+ 'include/rfb.js',
+ 'include/des.js',
+ 'include/display.js',
+ 'include/inflator.js',
+ 'tests/test.*.js'
+ ],
+
+ client: {
+ mocha: {
+ 'ui': 'bdd'
+ }
+ },
+
+ // list of files to exclude
+ exclude: [
+ '../include/playback.js',
+ '../include/ui.js'
+ ],
+
+ customLaunchers: customLaunchers,
+
+ // start these browsers
+ // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
+ browsers: browsers,
+
+ // preprocess matching files before serving them to the browser
+ // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
+ preprocessors: {
+
+ },
+
+
+ // test results reporter to use
+ // possible values: 'dots', 'progress'
+ // available reporters: https://npmjs.org/browse/keyword/karma-reporter
+ reporters: ['mocha', 'saucelabs'],
+
+
+ // web server port
+ port: 9876,
+
+
+ // enable / disable colors in the output (reporters and logs)
+ colors: true,
+
+
+ // level of logging
+ // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
+ logLevel: config.LOG_INFO,
+
+
+ // enable / disable watching file and executing tests whenever any file changes
+ autoWatch: false,
+
+ // Continuous Integration mode
+ // if true, Karma captures browsers, runs the tests and exits
+ singleRun: true,
+
+ // Increase timeout in case connection is slow/we run more browsers than possible
+ // (we currently get 3 for free, and we try to run 7, so it can take a while)
+ captureTimeout: 240000,
+
+ // similarly to above
+ browserNoActivityTimeout: 100000,
+ };
+
+ if (useSauce) {
+ my_conf.captureTimeout = 0; // use SL timeout
+ my_conf.sauceLabs = {
+ testName: 'noVNC Tests (all)',
+ startConnect: false,
+ tunnelIdentifier: process.env.TRAVIS_JOB_NUMBER
+ };
+ }
+
+ config.set(my_conf);
+};
diff --git a/public/novnc/package.json b/public/novnc/package.json
new file mode 100644
index 00000000..18afa551
--- /dev/null
+++ b/public/novnc/package.json
@@ -0,0 +1,50 @@
+{
+ "name": "noVNC",
+ "version": "0.6.2",
+ "description": "An HTML5 VNC client",
+ "main": "karma.conf.js",
+ "directories": {
+ "doc": "docs",
+ "test": "tests"
+ },
+ "scripts": {
+ "test": "PATH=$PATH:node_modules/karma/bin karma start karma.conf.js"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/kanaka/noVNC.git"
+ },
+ "author": "Joel Martin (https://github.com/kanaka)",
+ "contributors": [
+ "Solly Ross (https://github.com/directxman12)",
+ "Peter Åstrand (https://github.com/astrand)",
+ "Samuel Mannehed (https://github.com/samhed)"
+ ],
+ "license": "MPL 2.0",
+ "bugs": {
+ "url": "https://github.com/kanaka/noVNC/issues"
+ },
+ "homepage": "https://github.com/kanaka/noVNC",
+ "devDependencies": {
+ "ansi": "^0.3.0",
+ "casperjs": "^1.1.0-beta3",
+ "chai": "^2.1.0",
+ "commander": "^2.6.0",
+ "karma": "^0.12.31",
+ "karma-chai": "^0.1.0",
+ "karma-mocha": "^0.1.10",
+ "karma-mocha-reporter": "^1.0.0",
+ "karma-phantomjs-launcher": "^0.1.4",
+ "karma-sauce-launcher": "^0.2.10",
+ "karma-sinon": "^1.0.4",
+ "karma-sinon-chai-latest": "^0.1.0",
+ "mocha": "^2.1.0",
+ "open": "^0.0.5",
+ "phantom": "^0.7.2",
+ "phantomjs": "^1.9.15",
+ "sinon": "^1.12.2",
+ "sinon-chai": "^2.7.0",
+ "spooky": "^0.2.5",
+ "temp": "^0.8.1"
+ }
+}
diff --git a/public/novnc/tests/arrays.html b/public/novnc/tests/arrays.html
new file mode 100644
index 00000000..257df261
--- /dev/null
+++ b/public/novnc/tests/arrays.html
@@ -0,0 +1,39 @@
+
+
+
+ Javascript Arrays Performance Test
+
+
+
+
+
+
+
+
+
Javascript Arrays Performance Test
+ Iterations:
+ Array Size: *1024
+
+
+
+
+ Results:
+
+
+
+
+
+
+
+
diff --git a/public/novnc/tests/arrays.js b/public/novnc/tests/arrays.js
new file mode 100644
index 00000000..843df7df
--- /dev/null
+++ b/public/novnc/tests/arrays.js
@@ -0,0 +1,375 @@
+/*
+ * Javascript binary array performance tests
+ * Copyright (C) 2012 Joel Martin
+ * Licensed under MPL 2.0 (see LICENSE.txt)
+ */
+
+var ctx, i, j, randlist,
+ new_normal, new_imageData, new_arrayBuffer,
+ browser = Browser.browser + " " +
+ Browser.version + " on " +
+ Browser.OS,
+ do_imageData = false,
+ do_arrayBuffer = false,
+ conf = {
+ 'create_cnt' : 2000,
+ 'read_cnt' : 5000000,
+ 'write_cnt' : 5000000,
+ 'iterations' : 0,
+ 'order_l1' : [browser],
+ 'order_l2' : ['normal',
+ 'imageData',
+ 'arrayBuffer'],
+ 'order_l3' : ['create',
+ 'sequentialRead',
+ 'randomRead',
+ 'sequentialWrite']
+ },
+ stats = {},
+ testFunc = {},
+ iteration, arraySize;
+
+var newline = "\n";
+if (Util.Engine.trident) {
+ var newline = " \n";
+}
+function message(str) {
+ //console.log(str);
+ cell = $D('messages');
+ cell.textContent += str + newline;
+ cell.scrollTop = cell.scrollHeight;
+}
+
+function vmessage(str) {
+ if (verbose) {
+ message(str);
+ } else {
+ console.log(str);
+ }
+}
+
+new_normal = function() {
+ var arr = [], i;
+ for (i = 0; i < arraySize; i++) {
+ arr[i] = 0;
+ }
+ return arr;
+}
+
+/* Will be overridden with real function */
+new_imageData = function() {
+ throw("imageData not supported");
+};
+
+new_imageData_createImageData = function() {
+ var imageData = ctx.createImageData(1024/4, arraySize / 1024);
+ return imageData.data;
+};
+
+new_imageData_getImageData = function() {
+ var imageData = ctx.getImageData(0, 0, 1024/4, arraySize / 1024),
+ arr = imageData.data;
+ for (i = 0; i < arraySize; i++) {
+ arr[i] = 0;
+ }
+ return arr;
+};
+
+new_arrayBuffer = function() {
+ var arr = new ArrayBuffer(arraySize);
+ return new Uint8Array(arr);
+}
+
+function init_randlist() {
+ randlist = [];
+ for (var i=0; i < arraySize; i++) {
+ randlist[i] = parseInt(Math.random() * 256, 10);
+ }
+}
+function copy_randlist(arr) {
+ for (var i=0; i < arraySize; i++) {
+ arr[i] = randlist[i];
+ }
+}
+
+function begin() {
+ var i, j;
+ conf.iterations = parseInt($D('iterations').value, 10);
+ arraySize = parseInt($D('arraySize').value, 10) * 1024;
+
+ init_randlist();
+
+ // TODO: randomize test_list
+
+ stats = {};
+ for (i = 0; i < conf.order_l2.length; i++) {
+ stats[conf.order_l2[i]] = {};
+ for (j = 0; j < conf.order_l3.length; j++) {
+ stats[conf.order_l2[i]][conf.order_l3[j]] = [];
+ }
+ }
+
+ $D('startButton').value = "Running";
+ $D('startButton').disabled = true;
+
+ message("running " + conf.iterations + " test iterations");
+ iteration = 1;
+ setTimeout(run_next_iteration, 250);
+}
+
+function finish() {
+ var totalTime, arrayType, testType, times;
+ message("tests finished");
+
+ for (j = 0; j < conf.order_l3.length; j++) {
+ testType = conf.order_l3[j];
+ message("Test '" + testType + "'");
+ for (i = 0; i < conf.order_l2.length; i++) {
+ arrayType = conf.order_l2[i];
+ message(" Array Type '" + arrayType);
+ times = stats[arrayType][testType];
+ message(" Average : " + times.mean() + "ms" +
+ " (Total: " + times.sum() + "ms)");
+ message(" Min/Max : " + times.min() + "ms/" +
+ times.max() + "ms");
+ message(" StdDev : " + times.stdDev() + "ms");
+ }
+ }
+
+ vmessage("array_chart.py JSON data:");
+ chart_data = {'conf' : conf, 'stats' : { } };
+ chart_data.stats[browser] = stats;
+ chart_data.stats['next_browser'] = {};
+ vmessage(JSON.stringify(chart_data, null, 2));
+
+ $D('startButton').disabled = false;
+ $D('startButton').value = "Run Tests";
+}
+
+function run_next_iteration() {
+ var arrayType, testType, deltaTime;
+
+ for (i = 0; i < conf.order_l2.length; i++) {
+ arrayType = conf.order_l2[i];
+ if (arrayType === 'imageData' && (!do_imageData)) {
+ continue;
+ }
+ if (arrayType === 'arrayBuffer' && (!do_arrayBuffer)) {
+ continue;
+ }
+ for (j = 0; j < conf.order_l3.length; j++) {
+ testType = conf.order_l3[j];
+
+ deltaTime = testFunc[arrayType + "_" + testType]();
+
+ stats[arrayType][testType].push(deltaTime);
+ vmessage("test " + (arrayType + "_" + testType) +
+ " time: " + (deltaTime) + "ms");
+ }
+ }
+
+ message("finished test iteration " + iteration);
+ if (iteration >= conf.iterations) {
+ setTimeout(finish, 1);
+ return;
+ }
+ iteration++;
+ setTimeout(run_next_iteration, 1);
+}
+
+/*
+ * Test functions
+ */
+
+testFunc["normal_create"] = function() {
+ var cnt, arrNormal, startTime, endTime;
+ vmessage("create normal array " + conf.create_cnt + "x, initialized to 0");
+
+ startTime = (new Date()).getTime();
+ for (cnt = 0; cnt < conf.create_cnt; cnt++) {
+ arrNormal = new_normal();
+ }
+ endTime = (new Date()).getTime();
+
+ return endTime - startTime;
+};
+
+testFunc["imageData_create"] = function() {
+ var cnt, arrImage, startTime, endTime;
+ vmessage("create imageData array " + conf.create_cnt + "x, initialized to 0");
+
+ startTime = (new Date()).getTime();
+ for (cnt = 0; cnt < conf.create_cnt; cnt++) {
+ arrImage = new_imageData();
+ }
+ endTime = (new Date()).getTime();
+
+ if (arrImage[103] !== 0) {
+ message("Initialization failed, arrImage[103] is: " + arrImage[103]);
+ throw("Initialization failed, arrImage[103] is: " + arrImage[103]);
+ }
+ return endTime - startTime;
+};
+
+testFunc["arrayBuffer_create"] = function() {
+ var cnt, arrBuffer, startTime, endTime;
+ vmessage("create arrayBuffer array " + conf.create_cnt + "x, initialized to 0");
+
+ startTime = (new Date()).getTime();
+ for (cnt = 0; cnt < conf.create_cnt; cnt++) {
+ arrBuffer = new_arrayBuffer();
+ }
+ endTime = (new Date()).getTime();
+
+ if (arrBuffer[103] !== 0) {
+ message("Initialization failed, arrBuffer[103] is: " + arrBuffer[103]);
+ throw("Initialization failed, arrBuffer[103] is: " + arrBuffer[103]);
+ }
+ return endTime - startTime;
+};
+
+function test_sequentialRead(arr) {
+ var i, j, cnt, startTime, endTime;
+ /* Initialize the array */
+ copy_randlist(arr);
+
+ startTime = (new Date()).getTime();
+ i = 0;
+ j = 0;
+ for (cnt = 0; cnt < conf.read_cnt; cnt++) {
+ j = arr[i];
+ i++;
+ if (i >= arraySize) {
+ i = 0;
+ }
+ }
+ endTime = (new Date()).getTime();
+
+ return endTime - startTime;
+}
+
+function test_randomRead(arr) {
+ var i, cnt, startTime, endTime;
+ /* Initialize the array */
+ copy_randlist(arr); // used as jumplist
+
+ startTime = (new Date()).getTime();
+ i = 0;
+ for (cnt = 0; cnt < conf.read_cnt; cnt++) {
+ i = (arr[i] + cnt) % arraySize;
+ }
+ endTime = (new Date()).getTime();
+
+ return endTime - startTime;
+}
+
+function test_sequentialWrite(arr) {
+ var i, cnt, startTime, endTime;
+ /* Initialize the array */
+ copy_randlist(arr);
+
+ startTime = (new Date()).getTime();
+ i = 0;
+ for (cnt = 0; cnt < conf.write_cnt; cnt++) {
+ arr[i] = (cnt % 256);
+ i++;
+ if (i >= arraySize) {
+ i = 0;
+ }
+ }
+ endTime = (new Date()).getTime();
+
+ return endTime - startTime;
+}
+
+/* Sequential Read Tests */
+testFunc["normal_sequentialRead"] = function() {
+ vmessage("read normal array " + conf.read_cnt + "x");
+ return test_sequentialRead(new_normal());
+};
+
+testFunc["imageData_sequentialRead"] = function() {
+ vmessage("read imageData array " + conf.read_cnt + "x");
+ return test_sequentialRead(new_imageData());
+};
+
+testFunc["arrayBuffer_sequentialRead"] = function() {
+ vmessage("read arrayBuffer array " + conf.read_cnt + "x");
+ return test_sequentialRead(new_arrayBuffer());
+};
+
+
+/* Random Read Tests */
+testFunc["normal_randomRead"] = function() {
+ vmessage("read normal array " + conf.read_cnt + "x");
+ return test_randomRead(new_normal());
+};
+
+testFunc["imageData_randomRead"] = function() {
+ vmessage("read imageData array " + conf.read_cnt + "x");
+ return test_randomRead(new_imageData());
+};
+
+testFunc["arrayBuffer_randomRead"] = function() {
+ vmessage("read arrayBuffer array " + conf.read_cnt + "x");
+ return test_randomRead(new_arrayBuffer());
+};
+
+
+/* Sequential Write Tests */
+testFunc["normal_sequentialWrite"] = function() {
+ vmessage("write normal array " + conf.write_cnt + "x");
+ return test_sequentialWrite(new_normal());
+};
+
+testFunc["imageData_sequentialWrite"] = function() {
+ vmessage("write imageData array " + conf.write_cnt + "x");
+ return test_sequentialWrite(new_imageData());
+};
+
+testFunc["arrayBuffer_sequentialWrite"] = function() {
+ vmessage("write arrayBuffer array " + conf.write_cnt + "x");
+ return test_sequentialWrite(new_arrayBuffer());
+};
+
+init = function() {
+ vmessage(">> init");
+
+ $D('iterations').value = 10;
+ $D('arraySize').value = 10;
+ arraySize = parseInt($D('arraySize').value, 10) * 1024;
+
+ message("Browser: " + browser);
+
+ /* Determine browser binary array support */
+ try {
+ ctx = $D('canvas').getContext('2d');
+ new_imageData = new_imageData_createImageData;
+ new_imageData();
+ do_imageData = true;
+ } catch (exc) {
+ vmessage("createImageData not supported: " + exc);
+ try {
+ ctx = $D('canvas').getContext('2d');
+ new_imageData = new_imageData_getImageData;
+ blah = new_imageData();
+ do_imageData = true;
+ } catch (exc) {
+ vmessage("getImageData not supported: " + exc);
+ }
+ }
+ if (! do_imageData) {
+ message("imageData arrays not supported");
+ }
+
+ try {
+ new_arrayBuffer();
+ do_arrayBuffer = true;
+ } catch (exc) {
+ vmessage("Typed Arrays not supported: " + exc);
+ }
+ if (! do_arrayBuffer) {
+ message("Typed Arrays (ArrayBuffers) not suppoted");
+ }
+ vmessage("<< init");
+}
diff --git a/public/novnc/tests/assertions.js b/public/novnc/tests/assertions.js
new file mode 100644
index 00000000..4bd0cf40
--- /dev/null
+++ b/public/novnc/tests/assertions.js
@@ -0,0 +1,98 @@
+// some useful assertions for noVNC
+chai.use(function (_chai, utils) {
+ _chai.Assertion.addMethod('displayed', function (target_data) {
+ var obj = this._obj;
+ var data_cl = obj._drawCtx.getImageData(0, 0, obj._viewportLoc.w, obj._viewportLoc.h).data;
+ // NB(directxman12): PhantomJS 1.x doesn't implement Uint8ClampedArray, so work around that
+ var data = new Uint8Array(data_cl);
+ var same = true;
+ var len = data_cl.length;
+ if (len != target_data.length) {
+ same = false;
+ } else {
+ for (var i = 0; i < len; i++) {
+ if (data[i] != target_data[i]) {
+ same = false;
+ break;
+ }
+ }
+ }
+ if (!same) {
+ console.log("expected data: %o, actual data: %o", target_data, data);
+ }
+ this.assert(same,
+ "expected #{this} to have displayed the image #{exp}, but instead it displayed #{act}",
+ "expected #{this} not to have displayed the image #{act}",
+ target_data,
+ data);
+ });
+
+ _chai.Assertion.addMethod('sent', function (target_data) {
+ var obj = this._obj;
+ obj.inspect = function () {
+ var res = { _websocket: obj._websocket, rQi: obj._rQi, _rQ: new Uint8Array(obj._rQ.buffer, 0, obj._rQlen),
+ _sQ: new Uint8Array(obj._sQ.buffer, 0, obj._sQlen) };
+ res.prototype = obj;
+ return res;
+ };
+ var data = obj._websocket._get_sent_data();
+ var same = true;
+ for (var i = 0; i < obj.length; i++) {
+ if (data[i] != target_data[i]) {
+ same = false;
+ break;
+ }
+ }
+ if (!same) {
+ console.log("expected data: %o, actual data: %o", target_data, data);
+ }
+ this.assert(same,
+ "expected #{this} to have sent the data #{exp}, but it actually sent #{act}",
+ "expected #{this} not to have sent the data #{act}",
+ Array.prototype.slice.call(target_data),
+ Array.prototype.slice.call(data));
+ });
+
+ _chai.Assertion.addProperty('array', function () {
+ utils.flag(this, 'array', true);
+ });
+
+ _chai.Assertion.overwriteMethod('equal', function (_super) {
+ return function assertArrayEqual(target) {
+ if (utils.flag(this, 'array')) {
+ var obj = this._obj;
+
+ var i;
+ var same = true;
+
+ if (utils.flag(this, 'deep')) {
+ for (i = 0; i < obj.length; i++) {
+ if (!utils.eql(obj[i], target[i])) {
+ same = false;
+ break;
+ }
+ }
+
+ this.assert(same,
+ "expected #{this} to have elements deeply equal to #{exp}",
+ "expected #{this} not to have elements deeply equal to #{exp}",
+ Array.prototype.slice.call(target));
+ } else {
+ for (i = 0; i < obj.length; i++) {
+ if (obj[i] != target[i]) {
+ same = false;
+ break;
+ }
+ }
+
+ this.assert(same,
+ "expected #{this} to have elements equal to #{exp}",
+ "expected #{this} not to have elements equal to #{exp}",
+ Array.prototype.slice.call(target));
+ }
+ } else {
+ _super.apply(this, arguments);
+ }
+ };
+ });
+});
diff --git a/public/novnc/tests/base64.html b/public/novnc/tests/base64.html
new file mode 100644
index 00000000..071dde63
--- /dev/null
+++ b/public/novnc/tests/base64.html
@@ -0,0 +1,91 @@
+
+
+
+ Native Base64 Tests
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/public/novnc/utils/README.md b/public/novnc/utils/README.md
new file mode 100644
index 00000000..344f199e
--- /dev/null
+++ b/public/novnc/utils/README.md
@@ -0,0 +1,14 @@
+## WebSockets Proxy/Bridge
+
+Websockify has been forked out into its own project. `launch.sh` wil
+automatically download it here if it is not already present and not
+installed as system-wide.
+
+For more detailed description and usage information please refer to
+the [websockify README](https://github.com/kanaka/websockify/blob/master/README.md).
+
+The other versions of websockify (C, Node.js) and the associated test
+programs have been moved to
+[websockify](https://github.com/kanaka/websockify). Websockify was
+formerly named wsproxy.
+
diff --git a/public/novnc/utils/b64-to-binary.pl b/public/novnc/utils/b64-to-binary.pl
new file mode 100644
index 00000000..280e28c9
--- /dev/null
+++ b/public/novnc/utils/b64-to-binary.pl
@@ -0,0 +1,17 @@
+#!/usr/bin/env perl
+use MIME::Base64;
+
+for (<>) {
+ unless (/^'([{}])(\d+)\1(.+?)',$/) {
+ print;
+ next;
+ }
+
+ my ($dir, $amt, $b64) = ($1, $2, $3);
+
+ my $decoded = MIME::Base64::decode($b64) or die "Could not base64-decode line `$_`";
+
+ my $decoded_escaped = join "", map { "\\x$_" } unpack("(H2)*", $decoded);
+
+ print "'${dir}${amt}${dir}${decoded_escaped}',\n";
+}
diff --git a/public/novnc/utils/img2js.py b/public/novnc/utils/img2js.py
new file mode 100644
index 00000000..4d213420
--- /dev/null
+++ b/public/novnc/utils/img2js.py
@@ -0,0 +1,40 @@
+#!/usr/bin/env python
+
+#
+# Convert image to Javascript compatible base64 Data URI
+# Copyright 2011 Joel Martin
+# Licensed under MPL 2.0 (see docs/LICENSE.MPL-2.0)
+#
+
+import sys, base64
+
+try:
+ from PIL import Image
+except:
+ print "python PIL module required (python-imaging package)"
+ sys.exit(1)
+
+
+if len(sys.argv) < 3:
+ print "Usage: %s IMAGE JS_VARIABLE" % sys.argv[0]
+ sys.exit(1)
+
+fname = sys.argv[1]
+var = sys.argv[2]
+
+ext = fname.lower().split('.')[-1]
+if ext == "png": mime = "image/png"
+elif ext in ["jpg", "jpeg"]: mime = "image/jpeg"
+elif ext == "gif": mime = "image/gif"
+else:
+ print "Only PNG, JPEG and GIF images are supported"
+ sys.exit(1)
+uri = "data:%s;base64," % mime
+
+im = Image.open(fname)
+w, h = im.size
+
+raw = open(fname).read()
+
+print '%s = {"width": %s, "height": %s, "data": "%s%s"};' % (
+ var, w, h, uri, base64.b64encode(raw))
diff --git a/public/novnc/utils/inflator.partial.js b/public/novnc/utils/inflator.partial.js
new file mode 100644
index 00000000..8b6d1095
--- /dev/null
+++ b/public/novnc/utils/inflator.partial.js
@@ -0,0 +1,40 @@
+var zlib = require('../node_modules/pako/lib/zlib/inflate.js');
+var ZStream = require('../node_modules/pako/lib/zlib/zstream.js');
+
+var Inflate = function () {
+ this.strm = new ZStream();
+ this.chunkSize = 1024 * 10 * 10;
+ this.strm.output = new Uint8Array(this.chunkSize);
+ this.windowBits = 5;
+
+ zlib.inflateInit(this.strm, this.windowBits);
+};
+
+Inflate.prototype = {
+ inflate: function (data, flush, expected) {
+ this.strm.input = data;
+ this.strm.avail_in = this.strm.input.length;
+ this.strm.next_in = 0;
+ this.strm.next_out = 0;
+
+ // resize our output buffer if it's too small
+ // (we could just use multiple chunks, but that would cause an extra
+ // allocation each time to flatten the chunks)
+ if (expected > this.chunkSize) {
+ this.chunkSize = expected;
+ this.strm.output = new Uint8Array(this.chunkSize);
+ }
+
+ this.strm.avail_out = this.chunkSize;
+
+ zlib.inflate(this.strm, flush);
+
+ return new Uint8Array(this.strm.output.buffer, 0, this.strm.next_out);
+ },
+
+ reset: function () {
+ zlib.inflateReset(this.strm);
+ }
+};
+
+module.exports = {Inflate: Inflate};
diff --git a/public/novnc/utils/json2graph.py b/public/novnc/utils/json2graph.py
new file mode 100644
index 00000000..f9ae27d3
--- /dev/null
+++ b/public/novnc/utils/json2graph.py
@@ -0,0 +1,206 @@
+#!/usr/bin/env python
+
+'''
+Use matplotlib to generate performance charts
+Copyright 2011 Joel Martin
+Licensed under MPL-2.0 (see docs/LICENSE.MPL-2.0)
+'''
+
+# a bar plot with errorbars
+import sys, json, pprint
+import numpy as np
+import matplotlib.pyplot as plt
+from matplotlib.font_manager import FontProperties
+
+def usage():
+ print "%s json_file level1 level2 level3 [legend_height]\n\n" % sys.argv[0]
+ print "Description:\n"
+ print "level1, level2, and level3 are one each of the following:\n";
+ print " select=ITEM - select only ITEM at this level";
+ print " bar - each item on this level becomes a graph bar";
+ print " group - items on this level become groups of bars";
+ print "\n";
+ print "json_file is a file containing json data in the following format:\n"
+ print ' {';
+ print ' "conf": {';
+ print ' "order_l1": [';
+ print ' "level1_label1",';
+ print ' "level1_label2",';
+ print ' ...';
+ print ' ],';
+ print ' "order_l2": [';
+ print ' "level2_label1",';
+ print ' "level2_label2",';
+ print ' ...';
+ print ' ],';
+ print ' "order_l3": [';
+ print ' "level3_label1",';
+ print ' "level3_label2",';
+ print ' ...';
+ print ' ]';
+ print ' },';
+ print ' "stats": {';
+ print ' "level1_label1": {';
+ print ' "level2_label1": {';
+ print ' "level3_label1": [val1, val2, val3],';
+ print ' "level3_label2": [val1, val2, val3],';
+ print ' ...';
+ print ' },';
+ print ' "level2_label2": {';
+ print ' ...';
+ print ' },';
+ print ' },';
+ print ' "level1_label2": {';
+ print ' ...';
+ print ' },';
+ print ' ...';
+ print ' },';
+ print ' }';
+ sys.exit(2)
+
+def error(msg):
+ print msg
+ sys.exit(1)
+
+
+#colors = ['#ff0000', '#0863e9', '#00f200', '#ffa100',
+# '#800000', '#805100', '#013075', '#007900']
+colors = ['#ff0000', '#00ff00', '#0000ff',
+ '#dddd00', '#dd00dd', '#00dddd',
+ '#dd6622', '#dd2266', '#66dd22',
+ '#8844dd', '#44dd88', '#4488dd']
+
+if len(sys.argv) < 5:
+ usage()
+
+filename = sys.argv[1]
+L1 = sys.argv[2]
+L2 = sys.argv[3]
+L3 = sys.argv[4]
+if len(sys.argv) > 5:
+ legendHeight = float(sys.argv[5])
+else:
+ legendHeight = 0.75
+
+# Load the JSON data from the file
+data = json.loads(file(filename).read())
+conf = data['conf']
+stats = data['stats']
+
+# Sanity check data hierarchy
+if len(conf['order_l1']) != len(stats.keys()):
+ error("conf.order_l1 does not match stats level 1")
+for l1 in stats.keys():
+ if len(conf['order_l2']) != len(stats[l1].keys()):
+ error("conf.order_l2 does not match stats level 2 for %s" % l1)
+ if conf['order_l1'].count(l1) < 1:
+ error("%s not found in conf.order_l1" % l1)
+ for l2 in stats[l1].keys():
+ if len(conf['order_l3']) != len(stats[l1][l2].keys()):
+ error("conf.order_l3 does not match stats level 3")
+ if conf['order_l2'].count(l2) < 1:
+ error("%s not found in conf.order_l2" % l2)
+ for l3 in stats[l1][l2].keys():
+ if conf['order_l3'].count(l3) < 1:
+ error("%s not found in conf.order_l3" % l3)
+
+#
+# Generate the data based on the level specifications
+#
+bar_labels = None
+group_labels = None
+bar_vals = []
+bar_sdvs = []
+if L3.startswith("select="):
+ select_label = l3 = L3.split("=")[1]
+ bar_labels = conf['order_l1']
+ group_labels = conf['order_l2']
+ bar_vals = [[0]*len(group_labels) for i in bar_labels]
+ bar_sdvs = [[0]*len(group_labels) for i in bar_labels]
+ for b in range(len(bar_labels)):
+ l1 = bar_labels[b]
+ for g in range(len(group_labels)):
+ l2 = group_labels[g]
+ bar_vals[b][g] = np.mean(stats[l1][l2][l3])
+ bar_sdvs[b][g] = np.std(stats[l1][l2][l3])
+elif L2.startswith("select="):
+ select_label = l2 = L2.split("=")[1]
+ bar_labels = conf['order_l1']
+ group_labels = conf['order_l3']
+ bar_vals = [[0]*len(group_labels) for i in bar_labels]
+ bar_sdvs = [[0]*len(group_labels) for i in bar_labels]
+ for b in range(len(bar_labels)):
+ l1 = bar_labels[b]
+ for g in range(len(group_labels)):
+ l3 = group_labels[g]
+ bar_vals[b][g] = np.mean(stats[l1][l2][l3])
+ bar_sdvs[b][g] = np.std(stats[l1][l2][l3])
+elif L1.startswith("select="):
+ select_label = l1 = L1.split("=")[1]
+ bar_labels = conf['order_l2']
+ group_labels = conf['order_l3']
+ bar_vals = [[0]*len(group_labels) for i in bar_labels]
+ bar_sdvs = [[0]*len(group_labels) for i in bar_labels]
+ for b in range(len(bar_labels)):
+ l2 = bar_labels[b]
+ for g in range(len(group_labels)):
+ l3 = group_labels[g]
+ bar_vals[b][g] = np.mean(stats[l1][l2][l3])
+ bar_sdvs[b][g] = np.std(stats[l1][l2][l3])
+else:
+ usage()
+
+# If group is before bar then flip (zip) the data
+if [L1, L2, L3].index("group") < [L1, L2, L3].index("bar"):
+ bar_labels, group_labels = group_labels, bar_labels
+ bar_vals = zip(*bar_vals)
+ bar_sdvs = zip(*bar_sdvs)
+
+print "bar_vals:", bar_vals
+
+#
+# Now render the bar graph
+#
+ind = np.arange(len(group_labels)) # the x locations for the groups
+width = 0.8 * (1.0/len(bar_labels)) # the width of the bars
+
+fig = plt.figure(figsize=(10,6), dpi=80)
+plot = fig.add_subplot(1, 1, 1)
+
+rects = []
+for i in range(len(bar_vals)):
+ rects.append(plot.bar(ind+width*i, bar_vals[i], width, color=colors[i],
+ yerr=bar_sdvs[i], align='center'))
+
+# add some
+plot.set_ylabel('Milliseconds (less is better)')
+plot.set_title("Javascript array test: %s" % select_label)
+plot.set_xticks(ind+width)
+plot.set_xticklabels( group_labels )
+
+fontP = FontProperties()
+fontP.set_size('small')
+plot.legend( [r[0] for r in rects], bar_labels, prop=fontP,
+ loc = 'center right', bbox_to_anchor = (1.0, legendHeight))
+
+def autolabel(rects):
+ # attach some text labels
+ for rect in rects:
+ height = rect.get_height()
+ if np.isnan(height):
+ height = 0.0
+ plot.text(rect.get_x()+rect.get_width()/2., height+20, '%d'%int(height),
+ ha='center', va='bottom', size='7')
+
+for rect in rects:
+ autolabel(rect)
+
+# Adjust axis sizes
+axis = list(plot.axis())
+axis[0] = -width # Make sure left side has enough for bar
+#axis[1] = axis[1] * 1.20 # Add 20% to the right to make sure it fits
+axis[2] = 0 # Make y-axis start at 0
+axis[3] = axis[3] * 1.10 # Add 10% to the top
+plot.axis(axis)
+
+plt.show()
diff --git a/public/novnc/utils/launch.sh b/public/novnc/utils/launch.sh
new file mode 100644
index 00000000..ce30aa38
--- /dev/null
+++ b/public/novnc/utils/launch.sh
@@ -0,0 +1,154 @@
+#!/usr/bin/env bash
+
+# Copyright 2016 Joel Martin
+# Copyright 2016 Solly Ross
+# Licensed under MPL 2.0 or any later version (see LICENSE.txt)
+
+usage() {
+ if [ "$*" ]; then
+ echo "$*"
+ echo
+ fi
+ echo "Usage: ${NAME} [--listen PORT] [--vnc VNC_HOST:PORT] [--cert CERT]"
+ echo
+ echo "Starts the WebSockets proxy and a mini-webserver and "
+ echo "provides a cut-and-paste URL to go to."
+ echo
+ echo " --listen PORT Port for proxy/webserver to listen on"
+ echo " Default: 6080"
+ echo " --vnc VNC_HOST:PORT VNC server host:port proxy target"
+ echo " Default: localhost:5900"
+ echo " --cert CERT Path to combined cert/key file"
+ echo " Default: self.pem"
+ echo " --web WEB Path to web files (e.g. vnc.html)"
+ echo " Default: ./"
+ exit 2
+}
+
+NAME="$(basename $0)"
+REAL_NAME="$(readlink -f $0)"
+HERE="$(cd "$(dirname "$REAL_NAME")" && pwd)"
+PORT="6080"
+VNC_DEST="localhost:5900"
+CERT=""
+WEB=""
+proxy_pid=""
+
+die() {
+ echo "$*"
+ exit 1
+}
+
+cleanup() {
+ trap - TERM QUIT INT EXIT
+ trap "true" CHLD # Ignore cleanup messages
+ echo
+ if [ -n "${proxy_pid}" ]; then
+ echo "Terminating WebSockets proxy (${proxy_pid})"
+ kill ${proxy_pid}
+ fi
+}
+
+# Process Arguments
+
+# Arguments that only apply to chrooter itself
+while [ "$*" ]; do
+ param=$1; shift; OPTARG=$1
+ case $param in
+ --listen) PORT="${OPTARG}"; shift ;;
+ --vnc) VNC_DEST="${OPTARG}"; shift ;;
+ --cert) CERT="${OPTARG}"; shift ;;
+ --web) WEB="${OPTARG}"; shift ;;
+ -h|--help) usage ;;
+ -*) usage "Unknown chrooter option: ${param}" ;;
+ *) break ;;
+ esac
+done
+
+# Sanity checks
+which netstat >/dev/null 2>&1 \
+ || die "Must have netstat installed"
+
+netstat -a -p tcp -n | grep -qs "${PORT} .*LISTEN" \
+ && die "Port ${PORT} in use. Try --listen PORT"
+
+trap "cleanup" TERM QUIT INT EXIT
+
+# Find vnc.html
+if [ -n "${WEB}" ]; then
+ if [ ! -e "${WEB}/vnc.html" ]; then
+ die "Could not find ${WEB}/vnc.html"
+ fi
+elif [ -e "$(pwd)/vnc.html" ]; then
+ WEB=$(pwd)
+elif [ -e "${HERE}/../vnc.html" ]; then
+ WEB=${HERE}/../
+elif [ -e "${HERE}/vnc.html" ]; then
+ WEB=${HERE}
+elif [ -e "${HERE}/../share/novnc/vnc.html" ]; then
+ WEB=${HERE}/../share/novnc/
+else
+ die "Could not find vnc.html"
+fi
+
+# Find self.pem
+if [ -n "${CERT}" ]; then
+ if [ ! -e "${CERT}" ]; then
+ die "Could not find ${CERT}"
+ fi
+elif [ -e "$(pwd)/self.pem" ]; then
+ CERT="$(pwd)/self.pem"
+elif [ -e "${HERE}/../self.pem" ]; then
+ CERT="${HERE}/../self.pem"
+elif [ -e "${HERE}/self.pem" ]; then
+ CERT="${HERE}/self.pem"
+else
+ echo "Warning: could not find self.pem"
+fi
+
+# try to find websockify (prefer local, try global, then download local)
+if [[ -e ${HERE}/websockify ]]; then
+ WEBSOCKIFY=${HERE}/websockify/run
+
+ if [[ ! -x $WEBSOCKIFY ]]; then
+ echo "The path ${HERE}/websockify exists, but $WEBSOCKIFY either does not exist or is not executable."
+ echo "If you intended to use an installed websockify package, please remove ${HERE}/websockify."
+ exit 1
+ fi
+
+ echo "Using local websockify at $WEBSOCKIFY"
+else
+ WEBSOCKIFY=$(which websockify 2>/dev/null)
+
+ if [[ $? -ne 0 ]]; then
+ echo "No installed websockify, attempting to clone websockify..."
+ WEBSOCKIFY=${HERE}/websockify/run
+ git clone https://github.com/kanaka/websockify ${HERE}/websockify
+
+ if [[ ! -e $WEBSOCKIFY ]]; then
+ echo "Unable to locate ${HERE}/websockify/run after downloading"
+ exit 1
+ fi
+
+ echo "Using local websockify at $WEBSOCKIFY"
+ else
+ echo "Using installed websockify at $WEBSOCKIFY"
+ fi
+fi
+
+echo "Starting webserver and WebSockets proxy on port ${PORT}"
+#${HERE}/websockify --web ${WEB} ${CERT:+--cert ${CERT}} ${PORT} ${VNC_DEST} &
+${WEBSOCKIFY} --web ${WEB} ${CERT:+--cert ${CERT}} ${PORT} ${VNC_DEST} &
+proxy_pid="$!"
+sleep 1
+if ! ps -p ${proxy_pid} >/dev/null; then
+ proxy_pid=
+ echo "Failed to start WebSockets proxy"
+ exit 1
+fi
+
+echo -e "\n\nNavigate to this URL:\n"
+echo -e " http://$(hostname):${PORT}/vnc.html?host=$(hostname)&port=${PORT}\n"
+echo -e "Press Ctrl-C to exit\n\n"
+
+wait ${proxy_pid}
diff --git a/public/novnc/utils/parse.js b/public/novnc/utils/parse.js
new file mode 100644
index 00000000..02ac66c2
--- /dev/null
+++ b/public/novnc/utils/parse.js
@@ -0,0 +1,97 @@
+// Utility to parse keysymdef.h to produce mappings from Unicode codepoints to keysyms
+"use strict";
+
+var fs = require('fs');
+
+var show_help = process.argv.length === 2;
+var use_keynames = false;
+var filename;
+
+for (var i = 2; i < process.argv.length; ++i) {
+ switch (process.argv[i]) {
+ case "--help":
+ case "-h":
+ show_help = true;
+ break;
+ case "--debug-names":
+ case "-d":
+ use_keynames = true;
+ break;
+ case "--file":
+ case "-f":
+ default:
+ filename = process.argv[i];
+ }
+}
+
+if (!filename) {
+ show_help = true;
+ console.log("Error: No filename specified\n");
+}
+
+if (show_help) {
+ console.log("Parses a *nix keysymdef.h to generate Unicode code point mappings");
+ console.log("Usage: node parse.js [options] filename:");
+ console.log(" -h [ --help ] Produce this help message");
+ console.log(" -d [ --debug-names ] Preserve keysym names for debugging (Increases file size by ~40KB)");
+ console.log(" filename The keysymdef.h file to parse");
+ return;
+}
+
+// Set this to false to omit key names from the generated keysymdef.js
+// This reduces the file size by around 40kb, but may hinder debugging
+
+var buf = fs.readFileSync(filename);
+var str = buf.toString('utf8');
+
+var re = /^\#define XK_([a-zA-Z_0-9]+)\s+0x([0-9a-fA-F]+)\s*(\/\*\s*(.*)\s*\*\/)?\s*$/m;
+
+var arr = str.split('\n');
+
+var keysyms = {};
+var codepoints = {};
+
+for (var i = 0; i < arr.length; ++i) {
+ var result = re.exec(arr[i]);
+ if (result){
+ var keyname = result[1];
+ var keysym = parseInt(result[2], 16);
+ var remainder = result[3];
+
+ keysyms[keysym] = keyname;
+
+ var unicodeRes = /U\+([0-9a-fA-F]+)/.exec(remainder);
+ if (unicodeRes) {
+ var unicode = parseInt(unicodeRes[1], 16);
+ if (!codepoints[unicode]){
+ codepoints[unicode] = keysym;
+ }
+ }
+ else {
+ console.log("no unicode codepoint found:", arr[i]);
+ }
+ }
+ else {
+ console.log("line is not a keysym:", arr[i]);
+ }
+}
+
+var out = "// This file describes mappings from Unicode codepoints to the keysym values\n" +
+"// (and optionally, key names) expected by the RFB protocol\n" +
+"// How this file was generated:\n" +
+"// " + process.argv.join(" ") + "\n" +
+"var keysyms = (function(){\n" +
+" \"use strict\";\n" +
+" var keynames = {keysyms};\n" +
+" var codepoints = {codepoints};\n" +
+"\n" +
+" function lookup(k) { return k ? {keysym: k, keyname: keynames ? keynames[k] : k} : undefined; }\n" +
+" return {\n" +
+" fromUnicode : function(u) { return lookup(codepoints[u]); },\n" +
+" lookup : lookup\n" +
+" };\n" +
+"})();\n";
+out = out.replace('{keysyms}', use_keynames ? JSON.stringify(keysyms) : "null");
+out = out.replace('{codepoints}', JSON.stringify(codepoints));
+
+fs.writeFileSync("keysymdef.js", out);
diff --git a/public/novnc/utils/u2x11 b/public/novnc/utils/u2x11
new file mode 100644
index 00000000..fd3e4ba8
--- /dev/null
+++ b/public/novnc/utils/u2x11
@@ -0,0 +1,28 @@
+#!/usr/bin/env bash
+#
+# Convert "U+..." commented entries in /usr/include/X11/keysymdef.h
+# into JavaScript for use by noVNC. Note this is likely to produce
+# a few duplicate properties with clashing values, that will need
+# resolving manually.
+#
+# Colin Dean
+#
+
+regex="^#define[ \t]+XK_[A-Za-z0-9_]+[ \t]+0x([0-9a-fA-F]+)[ \t]+\/\*[ \t]+U\+([0-9a-fA-F]+)[ \t]+[^*]+.[ \t]+\*\/[ \t]*$"
+echo "unicodeTable = {"
+while read line; do
+ if echo "${line}" | egrep -qs "${regex}"; then
+
+ x11=$(echo "${line}" | sed -r "s/${regex}/\1/")
+ vnc=$(echo "${line}" | sed -r "s/${regex}/\2/")
+
+ if echo "${vnc}" | egrep -qs "^00[2-9A-F][0-9A-F]$"; then
+ : # skip ISO Latin-1 (U+0020 to U+00FF) as 1-to-1 mapping
+ else
+ # note 1-to-1 is possible (e.g. for Euro symbol, U+20AC)
+ echo " 0x${vnc} : 0x${x11},"
+ fi
+ fi
+done < /usr/include/X11/keysymdef.h | uniq
+echo "};"
+
diff --git a/public/novnc/utils/websockify b/public/novnc/utils/websockify
new file mode 160000
index 00000000..f0bdb0a6
--- /dev/null
+++ b/public/novnc/utils/websockify
@@ -0,0 +1 @@
+Subproject commit f0bdb0a621a4f3fb328d1410adfeaff76f088bfd
diff --git a/public/novnc/vnc.html b/public/novnc/vnc.html
new file mode 100644
index 00000000..0b653065
--- /dev/null
+++ b/public/novnc/vnc.html
@@ -0,0 +1,225 @@
+
+
+
+
+
+ noVNC
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 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 libvncserver)
+ or you will need to use
+ websockify
+ to bridge between your browser and VNC server. See the noVNC
+ README
+ and website
+ for more information.
+
+
+