summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--webclients/novnc/LICENSE.txt30
-rw-r--r--webclients/novnc/README.md62
-rw-r--r--webclients/novnc/images/alt.pngbin0 -> 339 bytes
-rw-r--r--webclients/novnc/images/ctrl.pngbin0 -> 354 bytes
-rw-r--r--webclients/novnc/images/esc.pngbin0 -> 385 bytes
-rw-r--r--webclients/novnc/images/power.pngbin0 -> 390 bytes
-rw-r--r--webclients/novnc/images/showextrakeys.pngbin0 -> 735 bytes
-rw-r--r--webclients/novnc/images/tab.pngbin0 -> 387 bytes
-rw-r--r--webclients/novnc/include/base.css203
-rw-r--r--webclients/novnc/include/base64.js232
-rw-r--r--webclients/novnc/include/black.css25
-rw-r--r--webclients/novnc/include/blue.css37
-rw-r--r--webclients/novnc/include/chrome-app/tcp-client.js321
-rw-r--r--webclients/novnc/include/des.js347
-rw-r--r--webclients/novnc/include/display.js1389
-rw-r--r--webclients/novnc/include/input.js2217
-rwxr-xr-xwebclients/novnc/include/jsunzip.js26
-rw-r--r--webclients/novnc/include/keyboard.js543
-rw-r--r--webclients/novnc/include/keysym.js376
-rw-r--r--webclients/novnc/include/keysymdef.js15
-rw-r--r--webclients/novnc/include/playback.js18
-rw-r--r--webclients/novnc/include/rfb.js3500
-rw-r--r--webclients/novnc/include/ui.js1590
-rw-r--r--webclients/novnc/include/util.js636
-rw-r--r--webclients/novnc/include/web-socket-js/WebSocketMain.swfbin175746 -> 177139 bytes
-rw-r--r--webclients/novnc/include/web-socket-js/web_socket.js114
-rw-r--r--webclients/novnc/include/websock.js622
-rw-r--r--webclients/novnc/include/webutil.js181
-rw-r--r--webclients/novnc/vnc.html143
-rw-r--r--webclients/novnc/vnc_auto.html121
30 files changed, 6797 insertions, 5951 deletions
diff --git a/webclients/novnc/LICENSE.txt b/webclients/novnc/LICENSE.txt
index 67cdca5..2d09408 100644
--- a/webclients/novnc/LICENSE.txt
+++ b/webclients/novnc/LICENSE.txt
@@ -1,7 +1,7 @@
noVNC is Copyright (C) 2011 Joel Martin <github@martintribe.org>
-The noVNC core library is licensed under the LGPLv3 (GNU Lesser
-General Public License). The noVNC core library is composed of the
+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):
@@ -10,6 +10,7 @@ is not limited to):
include/display.js
include/input.js
include/jsunzip.js
+ include/keysym.js
include/logo.js
include/rfb.js
include/ui.js
@@ -36,21 +37,15 @@ The HTML, CSS, font and image files are licensed as follows:
images/ : Creative Commons Attribution-ShareAlike
http://creativecommons.org/licenses/by-sa/3.0/
-In addition the following file, which is part of the noVNC core
-library, may be licensed under either the LGPL-2, LGPL-3 or MPL 2.0
-when it used separately from the noVNC core library.
-
- include/input.js : LGPL-2 or any later version
-
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 LGPL-3 compatible):
+licenses (all MPL 2.0 compatible):
- include/base64.js : MPL 1.1, GPL-2 or LGPL-2.1
+ include/base64.js : MPL 2.0
include/des.js : Various BSD style licenses
@@ -59,20 +54,29 @@ licenses (all LGPL-3 compatible):
include/web-socket-js/ : New BSD license (3-clause). Source code at
http://github.com/gimite/web-socket-js
+ include/chrome-app/tcp-stream.js
+ : Apache 2.0 license
+
+ utils/websockify
+ utils/websocket.py : LGPL 3
+
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.MPL-2.0
+ docs/LICENSE.Apache-2.0
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://www.mozilla.org/MPL/1.1/
- http://www.mozilla.org/MPL/2.0/
+ 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/webclients/novnc/README.md b/webclients/novnc/README.md
index 9c14e7d..b5679cd 100644
--- a/webclients/novnc/README.md
+++ b/webclients/novnc/README.md
@@ -1,16 +1,45 @@
## noVNC: HTML5 VNC Client
+[![Build Status](https://travis-ci.org/kanaka/noVNC.svg?branch=master)](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
-@<a href="http://www.twitter.com/noVNC">noVNC</a>
+<a href="http://www.twitter.com/noVNC">@noVNC</a>
+
+If you are a noVNC developer/integrator/user (or want to be) please
+join the <a
+href="https://groups.google.com/forum/?fromgroups#!forum/novnc">noVNC
+discussion group</a>
+
+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).
-There are many companies/projects that have integrated noVNC into
-their products including: [Ganeti Web Manager](http://code.osuosl.org/projects/ganeti-webmgr), [Archipel](http://archipelproject.org), [openQRM](http://www.openqrm.com/), [OpenNode](http://www.opennodecloud.com/), [OpenStack](http://www.openstack.org), [Broadway (HTML5 GDK/GTK+ backend)](http://blogs.gnome.org/alexl/2011/03/15/gtk-html-backend-update/), [OpenNebula](http://opennebula.org/), [CloudSigma](http://www.cloudsigma.com/), [Zentyal (formerly eBox)](http://www.zentyal.org/), [SlapOS](http://www.slapos.org), [Intel MeshCentral](https://meshcentral.com), [Amahi](http://amahi.org), [Brightbox](http://brightbox.com/), [Foreman](http://theforeman.org), [LibVNCServer](http://libvnc.github.io/) and [PocketVNC](http://www.pocketvnc.com/blog/?page_id=866). See [this wiki page](https://github.com/kanaka/noVNC/wiki/ProjectsCompanies-using-noVNC) for more info and links.
+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 <a
+href="http://www.twitter.com/noVNC">@noVNC</a> if you do.
### Features
@@ -24,7 +53,7 @@ their products including: [Ganeti Web Manager](http://code.osuosl.org/projects/g
* Clipboard copy/paste
* Clipping or scolling modes for large remote screens
* Easy site integration and theming (3 example themes included)
-* Licensed under the [LGPLv3](http://www.gnu.org/licenses/lgpl.html)
+* Licensed under the [MPL 2.0](http://www.mozilla.org/MPL/2.0/)
### Screenshots
@@ -49,17 +78,18 @@ See more screenshots <a href="http://kanaka.github.com/noVNC/screenshots.html">h
* Fast Javascript Engine: this is not strictly a requirement, but
without a fast Javascript engine, noVNC might be painfully slow.
-* I maintain a more detailed browser compatibility list <a
- href="https://github.com/kanaka/noVNC/wiki/Browser-support">here</a>.
+* 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://libvnc.github.io/) 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').
+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
@@ -88,8 +118,14 @@ a python proxy included ('websockify').
### Authors/Contributors
-* noVNC : Joel Martin (github.com/kanaka)
- * New UI and Icons : Chris Gordon
+* 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)
@@ -100,5 +136,3 @@ a python proxy included ('websockify').
* jsunzip : Erik Moller (github.com/operasoftware/jsunzip),
* tinflate : Joergen Ibsen (ibsensoftware.com)
* DES : Dave Zimmerman (Widget Workshop), Jef Poskanzer (ACME Labs)
-
-
diff --git a/webclients/novnc/images/alt.png b/webclients/novnc/images/alt.png
new file mode 100644
index 0000000..d42af7b
--- /dev/null
+++ b/webclients/novnc/images/alt.png
Binary files differ
diff --git a/webclients/novnc/images/ctrl.png b/webclients/novnc/images/ctrl.png
new file mode 100644
index 0000000..a63b601
--- /dev/null
+++ b/webclients/novnc/images/ctrl.png
Binary files differ
diff --git a/webclients/novnc/images/esc.png b/webclients/novnc/images/esc.png
new file mode 100644
index 0000000..ece5f7c
--- /dev/null
+++ b/webclients/novnc/images/esc.png
Binary files differ
diff --git a/webclients/novnc/images/power.png b/webclients/novnc/images/power.png
new file mode 100644
index 0000000..f68fd08
--- /dev/null
+++ b/webclients/novnc/images/power.png
Binary files differ
diff --git a/webclients/novnc/images/showextrakeys.png b/webclients/novnc/images/showextrakeys.png
new file mode 100644
index 0000000..ad8e0a7
--- /dev/null
+++ b/webclients/novnc/images/showextrakeys.png
Binary files differ
diff --git a/webclients/novnc/images/tab.png b/webclients/novnc/images/tab.png
new file mode 100644
index 0000000..8413487
--- /dev/null
+++ b/webclients/novnc/images/tab.png
Binary files differ
diff --git a/webclients/novnc/include/base.css b/webclients/novnc/include/base.css
index 3a2feb3..e2c9a96 100644
--- a/webclients/novnc/include/base.css
+++ b/webclients/novnc/include/base.css
@@ -1,7 +1,8 @@
/*
* noVNC base CSS
* Copyright (C) 2012 Joel Martin
- * noVNC is licensed under the LGPL-3 (see LICENSE.txt)
+ * 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).
*/
@@ -40,9 +41,6 @@ html {
}
#noVNC_encrypt {
}
-#noVNC_connectTimeout {
- width: 30px;
-}
#noVNC_path {
width: 100px;
}
@@ -51,6 +49,9 @@ html {
float:right;
}
+#noVNC_buttons {
+ white-space: nowrap;
+}
#noVNC_view_drag_button {
display: none;
@@ -58,38 +59,43 @@ html {
#sendCtrlAltDelButton {
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;
- padding-left:10px;
- padding-top:4px;
+ z-index: 1;
+ position: relative;
}
.noVNC-buttons-right {
float:right;
right: 0px;
- padding-right:10px;
- padding-top:4px;
+ z-index: 2;
+ position: absolute;
}
-#noVNC_status_bar {
- margin-top: 0px;
- padding: 0px;
-}
-
-#noVNC_status_bar div {
+#noVNC_status {
font-size: 12px;
padding-top: 4px;
- width:100%;
-}
-
-#noVNC_status {
- height:20px;
+ height:32px;
text-align: center;
+ font-weight: bold;
+ color: #fff;
}
+
#noVNC_settings_menu {
margin: 3px;
text-align: left;
@@ -104,22 +110,12 @@ html {
float:right;
}
-.noVNC_status_normal {
- background: #eee;
-}
-.noVNC_status_error {
- background: #f44;
-}
-.noVNC_status_warn {
- background: #ff4;
-}
-
/* Do not set width/height for VNC_screen or VNC_canvas or incorrect
* scaling will occur. Canvas resizes to remote VNC settings */
#noVNC_screen_pad {
margin: 0px;
padding: 0px;
- height: 44px;
+ height: 36px;
}
#noVNC_screen {
text-align: center;
@@ -154,14 +150,14 @@ html {
/*Bubble contents divs*/
#noVNC_settings {
display:none;
- margin-top:77px;
+ margin-top:73px;
right:20px;
position:fixed;
}
#noVNC_controls {
display:none;
- margin-top:77px;
+ margin-top:73px;
right:12px;
position:fixed;
}
@@ -173,7 +169,7 @@ html {
display:none;
position:fixed;
- margin-top:77px;
+ margin-top:73px;
right:20px;
left:20px;
padding:15px;
@@ -186,9 +182,40 @@ html {
border-radius:10px;
}
+#noVNC_popup_status_panel {
+ 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:77px;
+ margin-top:73px;
right:30px;
position:fixed;
}
@@ -207,17 +234,11 @@ html {
z-index: -1;
}
-.noVNC_status_warn {
- background-color:yellow;
-}
-
/*
* Advanced Styling
*/
-/* Control bar */
-#noVNC-control-bar {
- position:fixed;
+.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+ */
@@ -225,9 +246,32 @@ html {
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:44px;
+ height:36px;
left:0;
top:0;
width:100%;
@@ -368,22 +412,85 @@ html {
font-size: 180px;
}
-@media screen and (min-width: 481px) and (max-width: 640px) {
- .noVNC_status_button {
- font-size: 10px;
+.noVNC-buttons-left {
+ padding-left: 10px;
+}
+
+.noVNC-buttons-right {
+ padding-right: 10px;
+}
+
+#noVNC_status {
+ z-index: 0;
+ position: absolute;
+ width: 100%;
+ margin-left: 0px;
+}
+
+#showExtraKeysButton { display: none; }
+#toggleCtrlButton { display: inline; }
+#toggleAltButton { display: inline; }
+#sendTabButton { display: inline; }
+#sendEscButton { 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;
}
-}
-
-@media screen and (min-width: 321px) and (max-width: 480px) {
.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 */
+ #showExtraKeysButton {
+ display: inline;
+ }
+ #toggleCtrlButton {
+ display: none;
+ position: absolute;
+ top: 30px;
+ left: 0px;
+ }
+ #toggleAltButton {
+ display: none;
+ position: absolute;
+ top: 65px;
+ left: 0px;
+ }
+ #sendTabButton {
+ display: none;
+ position: absolute;
+ top: 100px;
+ left: 0px;
+ }
+ #sendEscButton {
+ display: none;
+ position: absolute;
+ top: 135px;
+ left: 0px;
+ }
+}
+
+@media screen and (min-width: 321px) and (max-width: 480px) {
#noVNC_clipboard_text {
width: 250px;
}
diff --git a/webclients/novnc/include/base64.js b/webclients/novnc/include/base64.js
index e9b3c52..651fbad 100644
--- a/webclients/novnc/include/base64.js
+++ b/webclients/novnc/include/base64.js
@@ -1,147 +1,113 @@
-/*
- * Modified from:
- * http://lxr.mozilla.org/mozilla/source/extensions/xml-rpc/src/nsXmlRpcClient.js#956
- */
+/* 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/. */
-/* ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (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.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is Mozilla XML-RPC Client component.
- *
- * The Initial Developer of the Original Code is
- * Digital Creations 2, Inc.
- * Portions created by the Initial Developer are Copyright (C) 2000
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- * Martijn Pieters <mj@digicool.com> (original author)
- * Samuel Sieb <samuel@sieb.net>
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either the GNU General Public License Version 2 or later (the "GPL"), or
- * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK ***** */
+// From: http://hg.mozilla.org/mozilla-central/raw-file/ec10630b1a54/js/src/devtools/jint/sunspider/string-base64.js
-/*jslint white: false, bitwise: false, plusplus: false */
+/*jslint white: false */
/*global console */
var Base64 = {
-
-/* Convert data (an array of integers) to a Base64 string. */
-toBase64Table : 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/',
-base64Pad : '=',
-
-encode: function (data) {
- "use strict";
- var result = '',
- chrTable = Base64.toBase64Table.split(''),
- pad = Base64.base64Pad,
- length = data.length,
- i;
- // Convert every three bytes to 4 ascii characters.
- for (i = 0; i < (length - 2); i += 3) {
- result += chrTable[data[i] >> 2];
- result += chrTable[((data[i] & 0x03) << 4) + (data[i+1] >> 4)];
- result += chrTable[((data[i+1] & 0x0f) << 2) + (data[i+2] >> 6)];
- result += chrTable[data[i+2] & 0x3f];
- }
-
- // Convert the remaining 1 or 2 bytes, pad out to 4 characters.
- if (length%3) {
- i = length - (length%3);
- result += chrTable[data[i] >> 2];
- if ((length%3) === 2) {
- result += chrTable[((data[i] & 0x03) << 4) + (data[i+1] >> 4)];
- result += chrTable[(data[i+1] & 0x0f) << 2];
- result += pad;
- } else {
- result += chrTable[(data[i] & 0x03) << 4];
- result += pad + pad;
+ /* 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];
}
- }
-
- return result;
-},
-
-/* Convert Base64 data to a string */
-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
-],
-
-decode: function (data, offset) {
- "use strict";
- offset = typeof(offset) !== 'undefined' ? offset : 0;
- var binTable = Base64.toBinaryTable,
- pad = Base64.base64Pad,
- result, result_length, idx, i, c, padding,
- leftbits = 0, // number of bits decoded, but yet to be appended
- leftdata = 0, // bits decoded, but yet to be appended
- 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 (idx = 0, i = offset; i < data.length; i++) {
- c = binTable[data.charCodeAt(i) & 0x7f];
- padding = (data.charAt(i) === pad);
- // Skip illegal characters and whitespace
- if (c === -1) {
- console.error("Illegal character code " + data.charCodeAt(i) + " at position " + i);
- continue;
+ // 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];
}
-
- // 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;
+ 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;
}
- leftdata &= (1 << leftbits) - 1;
}
- }
- // If there are any bits left, the base64 string was corrupted
- if (leftbits) {
- throw {name: 'Base64-Error',
- message: 'Corrupted base64 string'};
- }
-
- return result;
-}
+ // 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/webclients/novnc/include/black.css b/webclients/novnc/include/black.css
index e958ee3..7d940c5 100644
--- a/webclients/novnc/include/black.css
+++ b/webclients/novnc/include/black.css
@@ -1,7 +1,8 @@
/*
- * noVNC base CSS
+ * noVNC black CSS
* Copyright (C) 2012 Joel Martin
- * noVNC is licensed under the LGPL-3 (see LICENSE.txt)
+ * 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).
*/
@@ -9,7 +10,7 @@
background-color:#000;
}
-#noVNC-control-bar {
+.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+ */
@@ -18,6 +19,24 @@
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;
diff --git a/webclients/novnc/include/blue.css b/webclients/novnc/include/blue.css
index 3dad0b4..b2a0adc 100644
--- a/webclients/novnc/include/blue.css
+++ b/webclients/novnc/include/blue.css
@@ -1,11 +1,12 @@
/*
- * noVNC base CSS
+ * noVNC blue CSS
* Copyright (C) 2012 Joel Martin
- * noVNC is licensed under the LGPL-3 (see LICENSE.txt)
+ * 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-control-bar {
+.noVNC_status_normal {
background-color:#04073d;
background-image: -webkit-gradient(
linear,
@@ -20,6 +21,36 @@
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;
diff --git a/webclients/novnc/include/chrome-app/tcp-client.js b/webclients/novnc/include/chrome-app/tcp-client.js
new file mode 100644
index 0000000..b8c125f
--- /dev/null
+++ b/webclients/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/webclients/novnc/include/des.js b/webclients/novnc/include/des.js
index 1f95285..ecbc819 100644
--- a/webclients/novnc/include/des.js
+++ b/webclients/novnc/include/des.js
@@ -75,199 +75,202 @@
* fine Java utilities: http://www.acme.com/java/
*/
-"use strict";
-/*jslint white: false, bitwise: false, plusplus: false */
+/* jslint white: false */
function DES(passwd) {
+ "use strict";
-// Tables, permutations, S-boxes, etc.
-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 = [];
+ // 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 = [];
-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 -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;
+ // 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<<m)) !== 0) ? 1: 0;
- }
+ 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<<m)) !== 0) ? 1: 0;
+ }
- for (i = 0; i < 16; ++i) {
- m = i << 1;
- n = m + 1;
- kn[m] = kn[n] = 0;
- for (o=28; o<59; o+=28) {
- for (j = o-28; j < o; ++j) {
- l = j + totrot[i];
- if (l < o) {
- pcr[j] = pc1m[l];
- } else {
- pcr[j] = pc1m[l - 28];
+ for (i = 0; i < 16; ++i) {
+ m = i << 1;
+ n = m + 1;
+ kn[m] = kn[n] = 0;
+ for (o = 28; o < 59; o += 28) {
+ for (j = o - 28; j < o; ++j) {
+ l = j + totrot[i];
+ if (l < o) {
+ pcr[j] = pc1m[l];
+ } else {
+ pcr[j] = pc1m[l - 28];
+ }
}
}
- }
- for (j = 0; j < 24; ++j) {
- if (pcr[PC2[j]] !== 0) {
- kn[m] |= 1<<(23-j);
- }
- if (pcr[PC2[j + 24]] !== 0) {
- kn[n] |= 1<<(23-j);
+ for (j = 0; j < 24; ++j) {
+ if (pcr[PC2[j]] !== 0) {
+ kn[m] |= 1 << (23 - j);
+ }
+ if (pcr[PC2[j + 24]] !== 0) {
+ kn[n] |= 1 << (23 - j);
+ }
}
}
- }
- // cookey
- for (i = 0, rawi = 0, KnLi = 0; i < 16; ++i) {
- raw0 = kn[rawi++];
- raw1 = kn[rawi++];
- keys[KnLi] = (raw0 & 0x00fc0000) << 6;
- keys[KnLi] |= (raw0 & 0x00000fc0) << 10;
- keys[KnLi] |= (raw1 & 0x00fc0000) >>> 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;
+ // cookey
+ for (i = 0, rawi = 0, KnLi = 0; i < 16; ++i) {
+ raw0 = kn[rawi++];
+ raw1 = kn[rawi++];
+ keys[KnLi] = (raw0 & 0x00fc0000) << 6;
+ keys[KnLi] |= (raw0 & 0x00000fc0) << 10;
+ keys[KnLi] |= (raw1 & 0x00fc0000) >>> 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
+ // 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++];
+ // 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);
+ 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;
- }
+ 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);
+ 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
+ // 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;
}
- 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)));
-}
+ // 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
+ setKeys(passwd); // Setup keys
+ return {'encrypt': encrypt}; // Public interface
} // function DES
diff --git a/webclients/novnc/include/display.js b/webclients/novnc/include/display.js
index 5ad99ba..8763fa4 100644
--- a/webclients/novnc/include/display.js
+++ b/webclients/novnc/include/display.js
@@ -1,757 +1,740 @@
/*
* noVNC: HTML5 VNC client
* Copyright (C) 2012 Joel Martin
- * Licensed under LGPL-3 (see LICENSE.txt)
+ * Licensed under MPL 2.0 (see LICENSE.txt)
*
* See README.md for usage and integration instructions.
*/
-/*jslint browser: true, white: false, bitwise: false */
+/*jslint browser: true, white: false */
/*global Util, Base64, changeCursor */
-function Display(defaults) {
-"use strict";
-
-var that = {}, // Public API methods
- conf = {}, // Configuration attributes
-
- // Private Display namespace variables
- c_ctx = null,
- c_forceCanvas = false,
-
- // Queued drawing actions for in-order rendering
- renderQ = [],
-
- // Predefine function variables (jslint)
- imageDataGet, rgbImageData, bgrxImageData, cmapImageData,
- setFillColor, rescale, scan_renderQ,
-
- // The full frame buffer (logical canvas) size
- fb_width = 0,
- fb_height = 0,
- // The visible "physical canvas" viewport
- viewport = {'x': 0, 'y': 0, 'w' : 0, 'h' : 0 },
- cleanRect = {'x1': 0, 'y1': 0, 'x2': -1, 'y2': -1},
-
- c_prevStyle = "",
- tile = null,
- tile16x16 = null,
- tile_x = 0,
- tile_y = 0;
-
-
-// Configuration attributes
-Util.conf_defaults(conf, that, defaults, [
- ['target', 'wo', 'dom', null, 'Canvas element for rendering'],
- ['context', 'ro', 'raw', null, 'Canvas 2D context for rendering (read-only)'],
- ['logo', 'rw', 'raw', null, 'Logo to display when cleared: {"width": width, "height": height, "data": data}'],
- ['true_color', 'rw', 'bool', true, 'Use true-color pixel data'],
- ['colourMap', 'rw', 'arr', [], 'Colour map array (when not true-color)'],
- ['scale', 'rw', 'float', 1.0, 'Display area scale factor 0.0 - 1.0'],
- ['viewport', 'rw', 'bool', false, 'Use a viewport set with viewportChange()'],
- ['width', 'rw', 'int', null, 'Display area width'],
- ['height', 'rw', 'int', null, 'Display area height'],
-
- ['render_mode', 'ro', 'str', '', 'Canvas rendering mode (read-only)'],
-
- ['prefer_js', 'rw', 'str', null, 'Prefer Javascript over canvas methods'],
- ['cursor_uri', 'rw', 'raw', null, 'Can we render cursor using data URI']
- ]);
+var Display;
-// Override some specific getters/setters
-that.get_context = function () { return c_ctx; };
+(function () {
+ "use strict";
-that.set_scale = function(scale) { rescale(scale); };
+ Display = function (defaults) {
+ this._drawCtx = null;
+ this._c_forceCanvas = false;
-that.set_width = function (val) { that.resize(val, fb_height); };
-that.get_width = function() { return fb_width; };
+ this._renderQ = []; // queue drawing actions for in-oder rendering
-that.set_height = function (val) { that.resize(fb_width, val); };
-that.get_height = function() { return fb_height; };
+ // the full frame buffer (logical canvas) size
+ this._fb_width = 0;
+ this._fb_height = 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;
-//
-// Private functions
-//
+ Util.set_defaults(this, defaults, {
+ 'true_color': true,
+ 'colourMap': [],
+ 'scale': 1.0,
+ 'viewport': false,
+ 'render_mode': ''
+ });
-// Create the public API interface
-function constructor() {
- Util.Debug(">> Display.constructor");
+ Util.Debug(">> Display.constructor");
- var c, func, i, curDat, curSave,
- has_imageData = false, UE = Util.Engine;
+ if (!this._target) {
+ throw new Error("Target must be set");
+ }
- if (! conf.target) { throw("target must be set"); }
+ if (typeof this._target === 'string') {
+ throw new Error('target must be a DOM element');
+ }
- if (typeof conf.target === 'string') {
- throw("target must be a DOM element");
- }
+ if (!this._target.getContext) {
+ throw new Error("no getContext method");
+ }
- c = conf.target;
+ if (!this._drawCtx) {
+ this._drawCtx = this._target.getContext('2d');
+ }
- if (! c.getContext) { throw("no getContext method"); }
+ 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); }
- if (! c_ctx) { c_ctx = c.getContext('2d'); }
+ this.clear();
- Util.Debug("User Agent: " + navigator.userAgent);
- if (UE.gecko) { Util.Debug("Browser: gecko " + UE.gecko); }
- if (UE.webkit) { Util.Debug("Browser: webkit " + UE.webkit); }
- if (UE.trident) { Util.Debug("Browser: trident " + UE.trident); }
- if (UE.presto) { Util.Debug("Browser: presto " + UE.presto); }
+ // Check canvas features
+ if ('createImageData' in this._drawCtx) {
+ this._render_mode = 'canvas rendering';
+ } else {
+ throw new Error("Canvas does not support createImageData");
+ }
- that.clear();
+ if (this._prefer_js === null) {
+ Util.Info("Prefering javascript operations");
+ this._prefer_js = true;
+ }
- // Check canvas features
- if ('createImageData' in c_ctx) {
- conf.render_mode = "canvas rendering";
- } else {
- throw("Canvas does not support createImageData");
- }
- if (conf.prefer_js === null) {
- Util.Info("Prefering javascript operations");
- conf.prefer_js = true;
- }
+ // Determine browser support for setting the cursor via data URI scheme
+ var curDat = [];
+ for (var i = 0; i < 8 * 8 * 4; i++) {
+ curDat.push(255);
+ }
+ try {
+ var curSave = this._target.style.cursor;
+ Display.changeCursor(this._target, curDat, curDat, 2, 2, 8, 8);
+ if (this._target.style.cursor) {
+ if (this._cursor_uri === null || this._cursor_uri === undefined) {
+ this._cursor_uri = true;
+ }
+ Util.Info("Data URI scheme cursor supported");
+ } else {
+ if (this._cursor_uri === null || this._cursor_uri === undefined) {
+ this._cursor_uri = false;
+ }
+ Util.Warn("Data URI scheme cursor not supported");
+ }
+ this._target.style.cursor = curSave;
+ } catch (exc) {
+ Util.Error("Data URI scheme cursor test exception: " + exc);
+ this._cursor_uri = false;
+ }
- // Initialize cached tile imageData
- tile16x16 = c_ctx.createImageData(16, 16);
+ Util.Debug("<< Display.constructor");
+ };
- /*
- * Determine browser support for setting the cursor via data URI
- * scheme
- */
- curDat = [];
- for (i=0; i < 8 * 8 * 4; i += 1) {
- curDat.push(255);
- }
- try {
- curSave = c.style.cursor;
- changeCursor(conf.target, curDat, curDat, 2, 2, 8, 8);
- if (c.style.cursor) {
- if (conf.cursor_uri === null) {
- conf.cursor_uri = true;
+ Display.prototype = {
+ // Public methods
+ viewportChange: function (deltaX, deltaY, width, height) {
+ var vp = this._viewportLoc;
+ var cr = this._cleanRect;
+ var canvas = this._target;
+
+ if (!this._viewport) {
+ Util.Debug("Setting viewport to full display region");
+ deltaX = -vp.w; // clamped later of out of bounds
+ deltaY = -vp.h;
+ width = this._fb_width;
+ height = this._fb_height;
}
- Util.Info("Data URI scheme cursor supported");
- } else {
- if (conf.cursor_uri === null) {
- conf.cursor_uri = false;
+
+ if (typeof(deltaX) === "undefined") { deltaX = 0; }
+ if (typeof(deltaY) === "undefined") { deltaY = 0; }
+ if (typeof(width) === "undefined") { width = vp.w; }
+ if (typeof(height) === "undefined") { height = vp.h; }
+
+ // Size change
+ if (width > this._fb_width) { width = this._fb_width; }
+ if (height > this._fb_height) { height = this._fb_height; }
+
+ if (vp.w !== width || vp.h !== height) {
+ // Change width
+ if (width < vp.w && cr.x2 > vp.x + width - 1) {
+ cr.x2 = vp.x + width - 1;
+ }
+ vp.w = width;
+
+ // Change height
+ if (height < vp.h && cr.y2 > vp.y + height - 1) {
+ cr.y2 = vp.y + height - 1;
+ }
+ vp.h = height;
+
+ 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);
+ }
+
+ canvas.width = vp.w;
+ canvas.height = vp.h;
+
+ if (saveImg) {
+ this._drawCtx.putImageData(saveImg, 0, 0);
+ }
}
- Util.Warn("Data URI scheme cursor not supported");
- }
- c.style.cursor = curSave;
- } catch (exc2) {
- Util.Error("Data URI scheme cursor test exception: " + exc2);
- conf.cursor_uri = false;
- }
-
- Util.Debug("<< Display.constructor");
- return that ;
-}
-
-rescale = function(factor) {
- var c, tp, x, y,
- properties = ['transform', 'WebkitTransform', 'MozTransform', null];
- c = conf.target;
- tp = properties.shift();
- while (tp) {
- if (typeof c.style[tp] !== 'undefined') {
- break;
- }
- tp = properties.shift();
- }
-
- if (tp === null) {
- Util.Debug("No scaling support");
- return;
- }
-
-
- if (typeof(factor) === "undefined") {
- factor = conf.scale;
- } else if (factor > 1.0) {
- factor = 1.0;
- } else if (factor < 0.1) {
- factor = 0.1;
- }
-
- if (conf.scale === factor) {
- //Util.Debug("Display already scaled to '" + factor + "'");
- return;
- }
-
- conf.scale = factor;
- x = c.width - c.width * factor;
- y = c.height - c.height * factor;
- c.style[tp] = "scale(" + conf.scale + ") translate(-" + x + "px, -" + y + "px)";
-};
-
-setFillColor = function(color) {
- var bgr, newStyle;
- if (conf.true_color) {
- bgr = color;
- } else {
- bgr = conf.colourMap[color[0]];
- }
- newStyle = "rgb(" + bgr[2] + "," + bgr[1] + "," + bgr[0] + ")";
- if (newStyle !== c_prevStyle) {
- c_ctx.fillStyle = newStyle;
- c_prevStyle = newStyle;
- }
-};
-
-
-//
-// Public API interface functions
-//
-
-// Shift and/or resize the visible viewport
-that.viewportChange = function(deltaX, deltaY, width, height) {
- var c = conf.target, v = viewport, cr = cleanRect,
- saveImg = null, saveStyle, x1, y1, vx2, vy2, w, h;
-
- if (!conf.viewport) {
- Util.Debug("Setting viewport to full display region");
- deltaX = -v.w; // Clamped later if out of bounds
- deltaY = -v.h; // Clamped later if out of bounds
- width = fb_width;
- height = fb_height;
- }
-
- if (typeof(deltaX) === "undefined") { deltaX = 0; }
- if (typeof(deltaY) === "undefined") { deltaY = 0; }
- if (typeof(width) === "undefined") { width = v.w; }
- if (typeof(height) === "undefined") { height = v.h; }
-
- // Size change
-
- if (width > fb_width) { width = fb_width; }
- if (height > fb_height) { height = fb_height; }
-
- if ((v.w !== width) || (v.h !== height)) {
- // Change width
- if ((width < v.w) && (cr.x2 > v.x + width -1)) {
- cr.x2 = v.x + width - 1;
- }
- v.w = width;
- // Change height
- if ((height < v.h) && (cr.y2 > v.y + height -1)) {
- cr.y2 = v.y + height - 1;
- }
- v.h = height;
+ var vx2 = vp.x + vp.w - 1;
+ var vy2 = vp.y + vp.h - 1;
+ // Position change
- if (v.w > 0 && v.h > 0 && c.width > 0 && c.height > 0) {
- saveImg = c_ctx.getImageData(0, 0,
- (c.width < v.w) ? c.width : v.w,
- (c.height < v.h) ? c.height : v.h);
- }
+ if (deltaX < 0 && vp.x + deltaX < 0) {
+ deltaX = -vp.x;
+ }
+ if (vx2 + deltaX >= this._fb_width) {
+ deltaX -= vx2 + deltaX - this._fb_width + 1;
+ }
- c.width = v.w;
- c.height = v.h;
+ if (vp.y + deltaY < 0) {
+ deltaY = -vp.y;
+ }
+ if (vy2 + deltaY >= this._fb_height) {
+ deltaY -= (vy2 + deltaY - this._fb_height + 1);
+ }
- if (saveImg) {
- c_ctx.putImageData(saveImg, 0, 0);
- }
- }
-
- vx2 = v.x + v.w - 1;
- vy2 = v.y + v.h - 1;
-
-
- // Position change
-
- if ((deltaX < 0) && ((v.x + deltaX) < 0)) {
- deltaX = - v.x;
- }
- if ((vx2 + deltaX) >= fb_width) {
- deltaX -= ((vx2 + deltaX) - fb_width + 1);
- }
-
- if ((v.y + deltaY) < 0) {
- deltaY = - v.y;
- }
- if ((vy2 + deltaY) >= fb_height) {
- deltaY -= ((vy2 + deltaY) - fb_height + 1);
- }
-
- if ((deltaX === 0) && (deltaY === 0)) {
- //Util.Debug("skipping viewport change");
- return;
- }
- Util.Debug("viewportChange deltaX: " + deltaX + ", deltaY: " + deltaY);
-
- v.x += deltaX;
- vx2 += deltaX;
- v.y += deltaY;
- vy2 += deltaY;
-
- // Update the clean rectangle
- if (v.x > cr.x1) {
- cr.x1 = v.x;
- }
- if (vx2 < cr.x2) {
- cr.x2 = vx2;
- }
- if (v.y > cr.y1) {
- cr.y1 = v.y;
- }
- if (vy2 < cr.y2) {
- cr.y2 = vy2;
- }
-
- if (deltaX < 0) {
- // Shift viewport left, redraw left section
- x1 = 0;
- w = - deltaX;
- } else {
- // Shift viewport right, redraw right section
- x1 = v.w - deltaX;
- w = deltaX;
- }
- if (deltaY < 0) {
- // Shift viewport up, redraw top section
- y1 = 0;
- h = - deltaY;
- } else {
- // Shift viewport down, redraw bottom section
- y1 = v.h - deltaY;
- h = deltaY;
- }
-
- // Copy the valid part of the viewport to the shifted location
- saveStyle = c_ctx.fillStyle;
- c_ctx.fillStyle = "rgb(255,255,255)";
- if (deltaX !== 0) {
- //that.copyImage(0, 0, -deltaX, 0, v.w, v.h);
- //that.fillRect(x1, 0, w, v.h, [255,255,255]);
- c_ctx.drawImage(c, 0, 0, v.w, v.h, -deltaX, 0, v.w, v.h);
- c_ctx.fillRect(x1, 0, w, v.h);
- }
- if (deltaY !== 0) {
- //that.copyImage(0, 0, 0, -deltaY, v.w, v.h);
- //that.fillRect(0, y1, v.w, h, [255,255,255]);
- c_ctx.drawImage(c, 0, 0, v.w, v.h, 0, -deltaY, v.w, v.h);
- c_ctx.fillRect(0, y1, v.w, h);
- }
- c_ctx.fillStyle = saveStyle;
-};
-
-
-// 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}, ...]}
-that.getCleanDirtyReset = function() {
- var v = viewport, c = cleanRect, cleanBox, dirtyBoxes = [],
- vx2 = v.x + v.w - 1, vy2 = v.y + v.h - 1;
-
-
- // Copy the cleanRect
- cleanBox = {'x': c.x1, 'y': c.y1,
- 'w': c.x2 - c.x1 + 1, 'h': c.y2 - c.y1 + 1};
-
- if ((c.x1 >= c.x2) || (c.y1 >= c.y2)) {
- // Whole viewport is dirty
- dirtyBoxes.push({'x': v.x, 'y': v.y, 'w': v.w, 'h': v.h});
- } else {
- // Redraw dirty regions
- if (v.x < c.x1) {
- // left side dirty region
- dirtyBoxes.push({'x': v.x, 'y': v.y,
- 'w': c.x1 - v.x + 1, 'h': v.h});
- }
- if (vx2 > c.x2) {
- // right side dirty region
- dirtyBoxes.push({'x': c.x2 + 1, 'y': v.y,
- 'w': vx2 - c.x2, 'h': v.h});
- }
- if (v.y < c.y1) {
- // top/middle dirty region
- dirtyBoxes.push({'x': c.x1, 'y': v.y,
- 'w': c.x2 - c.x1 + 1, 'h': c.y1 - v.y});
- }
- if (vy2 > c.y2) {
- // bottom/middle dirty region
- dirtyBoxes.push({'x': c.x1, 'y': c.y2 + 1,
- 'w': c.x2 - c.x1 + 1, 'h': vy2 - c.y2});
- }
- }
-
- // Reset the cleanRect to the whole viewport
- cleanRect = {'x1': v.x, 'y1': v.y,
- 'x2': v.x + v.w - 1, 'y2': v.y + v.h - 1};
-
- return {'cleanBox': cleanBox, 'dirtyBoxes': dirtyBoxes};
-};
-
-// Translate viewport coordinates to absolute coordinates
-that.absX = function(x) {
- return x + viewport.x;
-};
-that.absY = function(y) {
- return y + viewport.y;
-};
-
-
-that.resize = function(width, height) {
- c_prevStyle = "";
-
- fb_width = width;
- fb_height = height;
-
- rescale(conf.scale);
- that.viewportChange();
-};
-
-that.clear = function() {
-
- if (conf.logo) {
- that.resize(conf.logo.width, conf.logo.height);
- that.blitStringImage(conf.logo.data, 0, 0);
- } else {
- that.resize(640, 20);
- c_ctx.clearRect(0, 0, viewport.w, viewport.h);
- }
-
- renderQ = [];
-
- // No benefit over default ("source-over") in Chrome and firefox
- //c_ctx.globalCompositeOperation = "copy";
-};
-
-that.fillRect = function(x, y, width, height, color) {
- setFillColor(color);
- c_ctx.fillRect(x - viewport.x, y - viewport.y, width, height);
-};
-
-that.copyImage = function(old_x, old_y, new_x, new_y, w, h) {
- var x1 = old_x - viewport.x, y1 = old_y - viewport.y,
- x2 = new_x - viewport.x, y2 = new_y - viewport.y;
- c_ctx.drawImage(conf.target, x1, y1, w, h, x2, y2, w, h);
-};
-
-
-// Start updating a tile
-that.startTile = function(x, y, width, height, color) {
- var data, bgr, red, green, blue, i;
- tile_x = x;
- tile_y = y;
- if ((width === 16) && (height === 16)) {
- tile = tile16x16;
- } else {
- tile = c_ctx.createImageData(width, height);
- }
- data = tile.data;
- if (conf.prefer_js) {
- if (conf.true_color) {
- bgr = color;
- } else {
- bgr = conf.colourMap[color[0]];
- }
- red = bgr[2];
- green = bgr[1];
- blue = bgr[0];
- for (i = 0; i < (width * height * 4); i+=4) {
- data[i ] = red;
- data[i + 1] = green;
- data[i + 2] = blue;
- data[i + 3] = 255;
- }
- } else {
- that.fillRect(x, y, width, height, color);
- }
-};
-
-// Update sub-rectangle of the current tile
-that.subTile = function(x, y, w, h, color) {
- var data, p, bgr, red, green, blue, width, j, i, xend, yend;
- if (conf.prefer_js) {
- data = tile.data;
- width = tile.width;
- if (conf.true_color) {
- bgr = color;
- } else {
- bgr = conf.colourMap[color[0]];
- }
- red = bgr[2];
- green = bgr[1];
- blue = bgr[0];
- xend = x + w;
- yend = y + h;
- for (j = y; j < yend; j += 1) {
- for (i = x; i < xend; i += 1) {
- p = (i + (j * width) ) * 4;
- data[p ] = red;
- data[p + 1] = green;
- data[p + 2] = blue;
- data[p + 3] = 255;
- }
- }
- } else {
- that.fillRect(tile_x + x, tile_y + y, w, h, color);
- }
-};
-
-// Draw the current tile to the screen
-that.finishTile = function() {
- if (conf.prefer_js) {
- c_ctx.putImageData(tile, tile_x - viewport.x, tile_y - viewport.y);
- }
- // else: No-op, if not prefer_js then already done by setSubTile
-};
-
-rgbImageData = function(x, y, width, height, arr, offset) {
- var img, i, j, data, v = viewport;
- /*
- if ((x - v.x >= v.w) || (y - v.y >= v.h) ||
- (x - v.x + width < 0) || (y - v.y + height < 0)) {
- // Skipping because outside of viewport
- return;
- }
- */
- img = c_ctx.createImageData(width, height);
- data = img.data;
- for (i=0, j=offset; i < (width * height * 4); i=i+4, j=j+3) {
- data[i ] = arr[j ];
- data[i + 1] = arr[j + 1];
- data[i + 2] = arr[j + 2];
- data[i + 3] = 255; // Set Alpha
- }
- c_ctx.putImageData(img, x - v.x, y - v.y);
-};
-
-bgrxImageData = function(x, y, width, height, arr, offset) {
- var img, i, j, data, v = viewport;
- /*
- if ((x - v.x >= v.w) || (y - v.y >= v.h) ||
- (x - v.x + width < 0) || (y - v.y + height < 0)) {
- // Skipping because outside of viewport
- return;
- }
- */
- img = c_ctx.createImageData(width, height);
- data = img.data;
- for (i=0, j=offset; i < (width * height * 4); i=i+4, j=j+4) {
- data[i ] = arr[j + 2];
- data[i + 1] = arr[j + 1];
- data[i + 2] = arr[j ];
- data[i + 3] = 255; // Set Alpha
- }
- c_ctx.putImageData(img, x - v.x, y - v.y);
-};
-
-cmapImageData = function(x, y, width, height, arr, offset) {
- var img, i, j, data, bgr, cmap;
- img = c_ctx.createImageData(width, height);
- data = img.data;
- cmap = conf.colourMap;
- for (i=0, j=offset; i < (width * height * 4); i+=4, j+=1) {
- bgr = cmap[arr[j]];
- data[i ] = bgr[2];
- data[i + 1] = bgr[1];
- data[i + 2] = bgr[0];
- data[i + 3] = 255; // Set Alpha
- }
- c_ctx.putImageData(img, x - viewport.x, y - viewport.y);
-};
-
-that.blitImage = function(x, y, width, height, arr, offset) {
- if (conf.true_color) {
- bgrxImageData(x, y, width, height, arr, offset);
- } else {
- cmapImageData(x, y, width, height, arr, offset);
- }
-};
-
-that.blitRgbImage = function(x, y, width, height, arr, offset) {
- if (conf.true_color) {
- rgbImageData(x, y, width, height, arr, offset);
- } else {
- // prolly wrong...
- cmapImageData(x, y, width, height, arr, offset);
- }
-};
-
-that.blitStringImage = function(str, x, y) {
- var img = new Image();
- img.onload = function () {
- c_ctx.drawImage(img, x - viewport.x, y - viewport.y);
- };
- img.src = str;
-};
-
-// Wrap ctx.drawImage but relative to viewport
-that.drawImage = function(img, x, y) {
- c_ctx.drawImage(img, x - viewport.x, y - viewport.y);
-};
-
-that.renderQ_push = function(action) {
- renderQ.push(action);
- if (renderQ.length === 1) {
- // If this can be rendered immediately it will be, otherwise
- // the scanner will start polling the queue (every
- // requestAnimationFrame interval)
- scan_renderQ();
- }
-};
-
-scan_renderQ = function() {
- var a, ready = true;
- while (ready && renderQ.length > 0) {
- a = renderQ[0];
- switch (a.type) {
- case 'copy':
- that.copyImage(a.old_x, a.old_y, a.x, a.y, a.width, a.height);
- break;
- case 'fill':
- that.fillRect(a.x, a.y, a.width, a.height, a.color);
- break;
- case 'blit':
- that.blitImage(a.x, a.y, a.width, a.height, a.data, 0);
- break;
- case 'blitRgb':
- that.blitRgbImage(a.x, a.y, a.width, a.height, a.data, 0);
- break;
- case 'img':
- if (a.img.complete) {
- that.drawImage(a.img, a.x, a.y);
+ 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
+ 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;
+ }
+
+ // Copy the valid part of the viewport to the shifted location
+ var saveStyle = this._drawCtx.fillStyle;
+ this._drawCtx.fillStyle = "rgb(255,255,255)";
+ if (deltaX !== 0) {
+ this._drawCtx.drawImage(canvas, 0, 0, vp.w, vp.h, -deltaX, 0, vp.w, vp.h);
+ this._drawCtx.fillRect(x1, 0, w, vp.h);
+ }
+ if (deltaY !== 0) {
+ this._drawCtx.drawImage(canvas, 0, 0, vp.w, vp.h, 0, -deltaY, vp.w, vp.h);
+ this._drawCtx.fillRect(0, y1, vp.w, h);
+ }
+ this._drawCtx.fillStyle = saveStyle;
+ },
+
+ // 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.viewportChange();
+ },
+
+ 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(640, 20);
+ this._drawCtx.clearRect(0, 0, this._viewportLoc.w, this._viewportLoc.h);
+ }
+
+ this._renderQ = [];
+ },
+
+ fillRect: function (x, y, width, height, color) {
+ 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) {
+ 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 {
- // We need to wait for this image to 'load'
- // to keep things in-order
- ready = false;
+ bgr = this._colourMap[color[0]];
}
- break;
- }
- if (ready) {
- a = renderQ.shift();
- }
- }
- if (renderQ.length > 0) {
- requestAnimFrame(scan_renderQ);
- }
-};
+ 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);
+ }
+ },
+
+ // 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);
+ }
+ },
+ // 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
+ },
-that.changeCursor = function(pixels, mask, hotx, hoty, w, h) {
- if (conf.cursor_uri === false) {
- Util.Warn("changeCursor called but no cursor data URI support");
- return;
- }
+ blitImage: function (x, y, width, height, arr, offset) {
+ 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);
+ }
+ },
- if (conf.true_color) {
- changeCursor(conf.target, pixels, mask, hotx, hoty, w, h);
- } else {
- changeCursor(conf.target, pixels, mask, hotx, hoty, w, h, conf.colourMap);
- }
-};
+ blitRgbImage: function (x, y , width, height, arr, offset) {
+ 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);
+ }
+ },
+
+ 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();
+ }
+ },
-that.defaultCursor = function() {
- conf.target.style.cursor = "default";
-};
+ changeCursor: function (pixels, mask, hotx, hoty, w, h) {
+ if (this._cursor_uri === false) {
+ Util.Warn("changeCursor called but no cursor data URI support");
+ return;
+ }
-return constructor(); // Return the public API interface
+ 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";
+ },
+
+ // Overridden getters/setters
+ get_context: function () {
+ return this._drawCtx;
+ },
+
+ set_scale: function (scale) {
+ this._rescale(scale);
+ },
+
+ set_width: function (w) {
+ this.resize(w, this._fb_height);
+ },
+ get_width: function () {
+ return this._fb_width;
+ },
+
+ set_height: function (h) {
+ this.resize(this._fb_width, h);
+ },
+ get_height: function () {
+ return this._fb_height;
+ },
+
+ // Private Methods
+ _rescale: function (factor) {
+ var canvas = this._target;
+ var properties = ['transform', 'WebkitTransform', 'MozTransform'];
+ var transform_prop;
+ while ((transform_prop = properties.shift())) {
+ if (typeof canvas.style[transform_prop] !== 'undefined') {
+ break;
+ }
+ }
-} // End of Display()
+ if (transform_prop === null) {
+ Util.Debug("No scaling support");
+ return;
+ }
+ if (typeof(factor) === "undefined") {
+ factor = this._scale;
+ } else if (factor > 1.0) {
+ factor = 1.0;
+ } else if (factor < 0.1) {
+ factor = 0.1;
+ }
-/* Set CSS cursor property using data URI encoded cursor file */
-function changeCursor(target, pixels, mask, hotx, hoty, w, h, cmap) {
- "use strict";
- var cur = [], rgb, IHDRsz, RGBsz, ANDsz, XORsz, url, idx, alpha, x, y;
- //Util.Debug(">> changeCursor, x: " + hotx + ", y: " + hoty + ", w: " + w + ", h: " + h);
-
- // 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 );
- };
+ if (this._scale === factor) {
+ return;
+ }
- IHDRsz = 40;
- RGBsz = w * h * 4;
- XORsz = Math.ceil( (w * h) / 8.0 );
- ANDsz = Math.ceil( (w * h) / 8.0 );
-
- // Main header
- 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); // 43: Size of Image
- // Gimp leaves this as 0
-
- 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[])
- for (y = h-1; y >= 0; y -= 1) {
- for (x = 0; x < w; x += 1) {
- idx = y * Math.ceil(w / 8) + Math.floor(x/8);
- alpha = (mask[idx] << (x % 8)) & 0x80 ? 255 : 0;
-
- if (cmap) {
- idx = (w * y) + x;
- rgb = cmap[pixels[idx]];
- cur.push(rgb[2]); // blue
- cur.push(rgb[1]); // green
- cur.push(rgb[0]); // red
- cur.push(alpha); // alpha
+ this._scale = factor;
+ var x = canvas.width - (canvas.width * factor);
+ var y = canvas.height - (canvas.height * factor);
+ canvas.style[transform_prop] = 'scale(' + this._scale + ') translate(-' + x + 'px, -' + y + 'px)';
+ },
+
+ _setFillColor: function (color) {
+ var bgr;
+ if (this._true_color) {
+ bgr = color;
} else {
- idx = ((w * y) + x) * 4;
- cur.push(pixels[idx + 2]); // blue
- cur.push(pixels[idx + 1]); // green
- cur.push(pixels[idx ]); // red
- cur.push(alpha); // alpha
+ bgr = this._colourMap[color[0]];
+ }
+
+ 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);
+ },
+
+ _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);
+ break;
+ case 'fill':
+ this.fillRect(a.x, a.y, a.width, a.height, a.color);
+ break;
+ case 'blit':
+ this.blitImage(a.x, a.y, a.width, a.height, a.data, 0);
+ break;
+ case 'blitRgb':
+ this.blitRgbImage(a.x, a.y, a.width, a.height, a.data, 0);
+ 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 a viewport set with viewportChange()
+ ['width', 'rw', 'int'], // Display area width
+ ['height', 'rw', 'int'], // Display area height
+
+ ['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
}
- }
- // XOR/bitmask data (BYTE icXOR[])
- // (ignored, just needs to be right size)
- for (y = 0; y < h; y += 1) {
- for (x = 0; x < Math.ceil(w / 8); x += 1) {
- cur.push(0x00);
+ 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
+ }
+ }
+ }
+ }
+
+ // 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 right size)
- for (y = 0; y < h; y += 1) {
- for (x = 0; x < Math.ceil(w / 8); x += 1) {
- cur.push(0x00);
+ // 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);
+ }
}
- }
- url = "data:image/x-icon;base64," + Base64.encode(cur);
- target.style.cursor = "url(" + url + ") " + hotx + " " + hoty + ", default";
- //Util.Debug("<< changeCursor, cur.length: " + cur.length);
-}
+ var url = 'data:image/x-icon;base64,' + Base64.encode(cur);
+ target.style.cursor = 'url(' + url + ')' + hotx + ' ' + hoty + ', default';
+ };
+})();
diff --git a/webclients/novnc/include/input.js b/webclients/novnc/include/input.js
index 9298dfe..5d9e209 100644
--- a/webclients/novnc/include/input.js
+++ b/webclients/novnc/include/input.js
@@ -1,1911 +1,388 @@
/*
* noVNC: HTML5 VNC client
- * Copyright (C) 2011 Joel Martin
- * Licensed under LGPL-2 or any later version (see LICENSE.txt)
+ * 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, bitwise: false */
+/*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');
+ }
+ },
-//
-// Keyboard event handler
-//
+ _handleKeyDown: function (e) {
+ if (!this._focused) { return true; }
-function Keyboard(defaults) {
-"use strict";
+ 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;
+ }
+ },
-var that = {}, // Public API methods
- conf = {}, // Configuration attributes
+ _handleKeyPress: function (e) {
+ if (!this._focused) { return true; }
- keyDownList = []; // List of depressed keys
- // (even if they are happy)
+ 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;
+ }
+ },
-// Configuration attributes
-Util.conf_defaults(conf, that, defaults, [
- ['target', 'wo', 'dom', document, 'DOM element that captures keyboard input'],
- ['focused', 'rw', 'bool', true, 'Capture and send key events'],
+ _handleKeyUp: function (e) {
+ if (!this._focused) { return true; }
- ['onKeyPress', 'rw', 'func', null, 'Handler for key press/release']
- ]);
+ 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");
+ },
-//
-// Private functions
-//
-
-// From the event keyCode return the keysym value for keys that need
-// to be suppressed otherwise they may trigger unintended browser
-// actions
-function getKeysymSpecial(evt) {
- var keysym = null;
-
- switch ( evt.keyCode ) {
- // These generate a keyDown and keyPress in Firefox and Opera
- case 8 : keysym = 0xFF08; break; // BACKSPACE
- case 13 : keysym = 0xFF0D; break; // ENTER
-
- // This generates a keyDown and keyPress in Opera
- case 9 : keysym = 0xFF09; break; // TAB
- default : break;
- }
-
- if (evt.type === 'keydown') {
- switch ( evt.keyCode ) {
- case 27 : keysym = 0xFF1B; break; // ESCAPE
- case 46 : keysym = 0xFFFF; break; // DELETE
-
- case 36 : keysym = 0xFF50; break; // HOME
- case 35 : keysym = 0xFF57; break; // END
- case 33 : keysym = 0xFF55; break; // PAGE_UP
- case 34 : keysym = 0xFF56; break; // PAGE_DOWN
- case 45 : keysym = 0xFF63; break; // INSERT
- // '-' during keyPress
- case 37 : keysym = 0xFF51; break; // LEFT
- case 38 : keysym = 0xFF52; break; // UP
- case 39 : keysym = 0xFF53; break; // RIGHT
- case 40 : keysym = 0xFF54; break; // DOWN
- case 16 : keysym = 0xFFE1; break; // SHIFT
- case 17 : keysym = 0xFFE3; break; // CONTROL
- //case 18 : keysym = 0xFFE7; break; // Left Meta (Mac Option)
- case 18 : keysym = 0xFFE9; break; // Left ALT (Mac Command)
-
- case 112 : keysym = 0xFFBE; break; // F1
- case 113 : keysym = 0xFFBF; break; // F2
- case 114 : keysym = 0xFFC0; break; // F3
- case 115 : keysym = 0xFFC1; break; // F4
- case 116 : keysym = 0xFFC2; break; // F5
- case 117 : keysym = 0xFFC3; break; // F6
- case 118 : keysym = 0xFFC4; break; // F7
- case 119 : keysym = 0xFFC5; break; // F8
- case 120 : keysym = 0xFFC6; break; // F9
- case 121 : keysym = 0xFFC7; break; // F10
- case 122 : keysym = 0xFFC8; break; // F11
- case 123 : keysym = 0xFFC9; break; // F12
-
- default : break;
- }
- }
-
- if ((!keysym) && (evt.ctrlKey || evt.altKey)) {
- if ((typeof(evt.which) !== "undefined") && (evt.which > 0)) {
- keysym = evt.which;
- } else {
- // IE9 always
- // Firefox and Opera when ctrl/alt + special
- Util.Warn("which not set, using keyCode");
- keysym = evt.keyCode;
- }
+ // 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");
+ },
- /* Remap symbols */
- switch (keysym) {
- case 186 : keysym = 59; break; // ; (IE)
- case 187 : keysym = 61; break; // = (IE)
- case 188 : keysym = 44; break; // , (Mozilla, IE)
- case 109 : // - (Mozilla, Opera)
- if (Util.Engine.gecko || Util.Engine.presto) {
- keysym = 45; }
- break;
- case 189 : keysym = 45; break; // - (IE)
- case 190 : keysym = 46; break; // . (Mozilla, IE)
- case 191 : keysym = 47; break; // / (Mozilla, IE)
- case 192 : keysym = 96; break; // ` (Mozilla, IE)
- case 219 : keysym = 91; break; // [ (Mozilla, IE)
- case 220 : keysym = 92; break; // \ (Mozilla, IE)
- case 221 : keysym = 93; break; // ] (Mozilla, IE)
- case 222 : keysym = 39; break; // ' (Mozilla, IE)
+ 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);
}
-
- /* Remap shifted and unshifted keys */
- if (!!evt.shiftKey) {
- switch (keysym) {
- case 48 : keysym = 41 ; break; // ) (shifted 0)
- case 49 : keysym = 33 ; break; // ! (shifted 1)
- case 50 : keysym = 64 ; break; // @ (shifted 2)
- case 51 : keysym = 35 ; break; // # (shifted 3)
- case 52 : keysym = 36 ; break; // $ (shifted 4)
- case 53 : keysym = 37 ; break; // % (shifted 5)
- case 54 : keysym = 94 ; break; // ^ (shifted 6)
- case 55 : keysym = 38 ; break; // & (shifted 7)
- case 56 : keysym = 42 ; break; // * (shifted 8)
- case 57 : keysym = 40 ; break; // ( (shifted 9)
-
- case 59 : keysym = 58 ; break; // : (shifted `)
- case 61 : keysym = 43 ; break; // + (shifted ;)
- case 44 : keysym = 60 ; break; // < (shifted ,)
- case 45 : keysym = 95 ; break; // _ (shifted -)
- case 46 : keysym = 62 ; break; // > (shifted .)
- case 47 : keysym = 63 ; break; // ? (shifted /)
- case 96 : keysym = 126; break; // ~ (shifted `)
- case 91 : keysym = 123; break; // { (shifted [)
- case 92 : keysym = 124; break; // | (shifted \)
- case 93 : keysym = 125; break; // } (shifted ])
- case 39 : keysym = 34 ; break; // " (shifted ')
+ };
+
+ 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();
}
- } else if ((keysym >= 65) && (keysym <=90)) {
- /* Remap unshifted A-Z */
- keysym += 32;
- } else if (evt.keyLocation === 3) {
- // numpad keys
- switch (keysym) {
- case 96 : keysym = 48; break; // 0
- case 97 : keysym = 49; break; // 1
- case 98 : keysym = 50; break; // 2
- case 99 : keysym = 51; break; // 3
- case 100: keysym = 52; break; // 4
- case 101: keysym = 53; break; // 5
- case 102: keysym = 54; break; // 6
- case 103: keysym = 55; break; // 7
- case 104: keysym = 56; break; // 8
- case 105: keysym = 57; break; // 9
- case 109: keysym = 45; break; // -
- case 110: keysym = 46; break; // .
- case 111: keysym = 47; break; // /
+
+ // 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();
}
- }
- }
-
- return keysym;
-}
-
-/* Translate DOM keyPress event to keysym value */
-function getKeysym(evt) {
- var keysym, msg;
-
- if (typeof(evt.which) !== "undefined") {
- // WebKit, Firefox, Opera
- keysym = evt.which;
- } else {
- // IE9
- Util.Warn("which not set, using keyCode");
- keysym = evt.keyCode;
- }
-
- if ((keysym > 255) && (keysym < 0xFF00)) {
- msg = "Mapping character code " + keysym;
- // Map Unicode outside Latin 1 to X11 keysyms
- keysym = unicodeTable[keysym];
- if (typeof(keysym) === 'undefined') {
- keysym = 0;
- }
- Util.Debug(msg + " to " + keysym);
- }
-
- return keysym;
-}
-
-function show_keyDownList(kind) {
- var c;
- var msg = "keyDownList (" + kind + "):\n";
- for (c = 0; c < keyDownList.length; c++) {
- msg = msg + " " + c + " - keyCode: " + keyDownList[c].keyCode +
- " - which: " + keyDownList[c].which + "\n";
- }
- Util.Debug(msg);
-}
-
-function copyKeyEvent(evt) {
- var members = ['type', 'keyCode', 'charCode', 'which',
- 'altKey', 'ctrlKey', 'shiftKey',
- 'keyLocation', 'keyIdentifier'], i, obj = {};
- for (i = 0; i < members.length; i++) {
- if (typeof(evt[members[i]]) !== "undefined") {
- obj[members[i]] = evt[members[i]];
- }
- }
- return obj;
-}
-
-function pushKeyEvent(fevt) {
- keyDownList.push(fevt);
-}
-
-function getKeyEvent(keyCode, pop) {
- var i, fevt = null;
- for (i = keyDownList.length-1; i >= 0; i--) {
- if (keyDownList[i].keyCode === keyCode) {
- if ((typeof(pop) !== "undefined") && (pop)) {
- fevt = keyDownList.splice(i, 1)[0];
+ 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
+ // closer than 20 pixels 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.
+ if (d < 20 * window.devicePixelRatio) {
+ 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 {
- fevt = keyDownList[i];
+ /* IE including 9 */
+ bmask = (evt.button & 0x1) + // Left
+ (evt.button & 0x2) * 2 + // Right
+ (evt.button & 0x4) / 2; // Middle
}
- break;
- }
- }
- return fevt;
-}
-
-function ignoreKeyEvent(evt) {
- // Blarg. Some keys have a different keyCode on keyDown vs keyUp
- if (evt.keyCode === 229) {
- // French AZERTY keyboard dead key.
- // Lame thing is that the respective keyUp is 219 so we can't
- // properly ignore the keyUp event
- return true;
- }
- return false;
-}
-
-
-//
-// Key Event Handling:
-//
-// There are several challenges when dealing with key events:
-// - The meaning and use of keyCode, charCode and which depends on
-// both the browser and the event type (keyDown/Up vs keyPress).
-// - We cannot automatically determine the keyboard layout
-// - The keyDown and keyUp events have a keyCode value that has not
-// been translated by modifier keys.
-// - The keyPress event has a translated (for layout and modifiers)
-// character code but the attribute containing it differs. keyCode
-// contains the translated value in WebKit (Chrome/Safari), Opera
-// 11 and IE9. charCode contains the value in WebKit and Firefox.
-// The which attribute contains the value on WebKit, Firefox and
-// Opera 11.
-// - The keyDown/Up keyCode value indicates (sort of) the physical
-// key was pressed but only for standard US layout. On a US
-// keyboard, the '-' and '_' characters are on the same key and
-// generate a keyCode value of 189. But on an AZERTY keyboard even
-// though they are different physical keys they both still
-// generate a keyCode of 189!
-// - To prevent a key event from propagating to the browser and
-// causing unwanted default actions (such as closing a tab,
-// opening a menu, shifting focus, etc) we must suppress this
-// event in both keyDown and keyPress because not all key strokes
-// generate on a keyPress event. Also, in WebKit and IE9
-// suppressing the keyDown prevents a keyPress but other browsers
-// still generated a keyPress even if keyDown is suppressed.
-//
-// For safe key events, we wait until the keyPress event before
-// reporting a key down event. For unsafe key events, we report a key
-// down event when the keyDown event fires and we suppress any further
-// actions (including keyPress).
-//
-// In order to report a key up event that matches what we reported
-// for the key down event, we keep a list of keys that are currently
-// down. When the keyDown event happens, we add the key event to the
-// list. If it is a safe key event, then we update the which attribute
-// in the most recent item on the list when we received a keyPress
-// event (keyPress should immediately follow keyDown). When we
-// received a keyUp event we search for the event on the list with
-// a matching keyCode and we report the character code using the value
-// in the 'which' attribute that was stored with that key.
-//
-
-function onKeyDown(e) {
- if (! conf.focused) {
- return true;
- }
- var fevt = null, evt = (e ? e : window.event),
- keysym = null, suppress = false;
- //Util.Debug("onKeyDown kC:" + evt.keyCode + " cC:" + evt.charCode + " w:" + evt.which);
-
- fevt = copyKeyEvent(evt);
-
- keysym = getKeysymSpecial(evt);
- // Save keysym decoding for use in keyUp
- fevt.keysym = keysym;
- if (keysym) {
- // If it is a key or key combination that might trigger
- // browser behaviors or it has no corresponding keyPress
- // event, then send it immediately
- if (conf.onKeyPress && !ignoreKeyEvent(evt)) {
- Util.Debug("onKeyPress down, keysym: " + keysym +
- " (onKeyDown key: " + evt.keyCode +
- ", which: " + evt.which + ")");
- conf.onKeyPress(keysym, 1, evt);
- }
- suppress = true;
- }
-
- if (! ignoreKeyEvent(evt)) {
- // Add it to the list of depressed keys
- pushKeyEvent(fevt);
- //show_keyDownList('down');
- }
-
- if (suppress) {
- // 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;
- }
-}
-
-function onKeyPress(e) {
- if (! conf.focused) {
- return true;
- }
- var evt = (e ? e : window.event),
- kdlen = keyDownList.length, keysym = null;
- //Util.Debug("onKeyPress kC:" + evt.keyCode + " cC:" + evt.charCode + " w:" + evt.which);
-
- if (((evt.which !== "undefined") && (evt.which === 0)) ||
- (getKeysymSpecial(evt))) {
- // Firefox and Opera generate a keyPress event even if keyDown
- // is suppressed. But the keys we want to suppress will have
- // either:
- // - the which attribute set to 0
- // - getKeysymSpecial() will identify it
- Util.Debug("Ignoring special key in keyPress");
- Util.stopEvent(e);
- return false;
- }
-
- keysym = getKeysym(evt);
-
- // Modify the the which attribute in the depressed keys list so
- // that the keyUp event will be able to have the character code
- // translation available.
- if (kdlen > 0) {
- keyDownList[kdlen-1].keysym = keysym;
- } else {
- Util.Warn("keyDownList empty when keyPress triggered");
- }
-
- //show_keyDownList('press');
-
- // Send the translated keysym
- if (conf.onKeyPress && (keysym > 0)) {
- Util.Debug("onKeyPress down, keysym: " + keysym +
- " (onKeyPress key: " + evt.keyCode +
- ", which: " + evt.which + ")");
- conf.onKeyPress(keysym, 1, evt);
- }
-
- // Stop keypress events just in case
- Util.stopEvent(e);
- return false;
-}
-
-function onKeyUp(e) {
- if (! conf.focused) {
- return true;
- }
- var fevt = null, evt = (e ? e : window.event), keysym;
- //Util.Debug("onKeyUp kC:" + evt.keyCode + " cC:" + evt.charCode + " w:" + evt.which);
-
- fevt = getKeyEvent(evt.keyCode, true);
-
- if (fevt) {
- keysym = fevt.keysym;
- } else {
- Util.Warn("Key event (keyCode = " + evt.keyCode +
- ") not found on keyDownList");
- keysym = 0;
- }
-
- //show_keyDownList('up');
-
- if (conf.onKeyPress && (keysym > 0)) {
- //Util.Debug("keyPress up, keysym: " + keysym +
- // " (key: " + evt.keyCode + ", which: " + evt.which + ")");
- Util.Debug("onKeyPress up, keysym: " + keysym +
- " (onKeyPress key: " + evt.keyCode +
- ", which: " + evt.which + ")");
- conf.onKeyPress(keysym, 0, evt);
- }
- Util.stopEvent(e);
- return false;
-}
-
-function allKeysUp() {
- Util.Debug(">> Keyboard.allKeysUp");
- if (keyDownList.length > 0) {
- Util.Info("Releasing pressed/down keys");
- }
- var i, keysym, fevt = null;
- for (i = keyDownList.length-1; i >= 0; i--) {
- fevt = keyDownList.splice(i, 1)[0];
- keysym = fevt.keysym;
- if (conf.onKeyPress && (keysym > 0)) {
- Util.Debug("allKeysUp, keysym: " + keysym +
- " (keyCode: " + fevt.keyCode +
- ", which: " + fevt.which + ")");
- conf.onKeyPress(keysym, 0, fevt);
- }
- }
- Util.Debug("<< Keyboard.allKeysUp");
- return;
-}
-//
-// Public API interface functions
-//
+ 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);
+ },
-that.grab = function() {
- //Util.Debug(">> Keyboard.grab");
- var c = conf.target;
+ _handleMouseUp: function (e) {
+ if (!this._mouseCaptured) { return; }
- Util.addEvent(c, 'keydown', onKeyDown);
- Util.addEvent(c, 'keyup', onKeyUp);
- Util.addEvent(c, 'keypress', onKeyPress);
+ this._handleMouseButton(e, 0);
+ this._releaseMouse();
+ },
- // Release (key up) if window loses focus
- Util.addEvent(window, 'blur', allKeysUp);
+ _handleMouseWheel: function (e) {
+ if (!this._focused) { return true; }
- //Util.Debug("<< Keyboard.grab");
-};
+ if (this._notify) {
+ this._notify(e);
+ }
-that.ungrab = function() {
- //Util.Debug(">> Keyboard.ungrab");
- var c = conf.target;
+ 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;
+ }
- Util.removeEvent(c, 'keydown', onKeyDown);
- Util.removeEvent(c, 'keyup', onKeyUp);
- Util.removeEvent(c, 'keypress', onKeyPress);
- Util.removeEvent(window, 'blur', allKeysUp);
+ if (this._onMouseButton) {
+ this._onMouseButton(pos.x, pos.y, 1, bmask);
+ this._onMouseButton(pos.x, pos.y, 0, bmask);
+ }
+ Util.stopEvent(e);
+ return false;
+ },
- // Release (key up) all keys that are in a down state
- allKeysUp();
+ _handleMouseMove: function (e) {
+ if (! this._focused) { return true; }
- //Util.Debug(">> Keyboard.ungrab");
-};
+ if (this._notify) {
+ this._notify(e);
+ }
-return that; // Return the public API interface
+ 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;
+ }
-} // End of Keyboard()
+ return true;
+ },
-//
-// Mouse event handler
-//
+ // Public methods
+ grab: function () {
+ var c = this._target;
-function Mouse(defaults) {
-"use strict";
+ 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);
+ }
-var that = {}, // Public API methods
- conf = {}; // Configuration attributes
+ /* Work around right and middle click browser behaviors */
+ Util.addEvent(document, 'click', this._eventHandlers.mousedisable);
+ Util.addEvent(document.body, 'contextmenu', this._eventHandlers.mousedisable);
+ },
-// Configuration attributes
-Util.conf_defaults(conf, that, defaults, [
- ['target', 'ro', 'dom', document, 'DOM element that captures mouse input'],
- ['focused', 'rw', 'bool', true, 'Capture and send mouse clicks/movement'],
- ['scale', 'rw', 'float', 1.0, 'Viewport scale factor 0.0 - 1.0'],
+ ungrab: function () {
+ var c = this._target;
- ['onMouseButton', 'rw', 'func', null, 'Handler for mouse button click/release'],
- ['onMouseMove', 'rw', 'func', null, 'Handler for mouse movement'],
- ['touchButton', 'rw', 'int', 1, 'Button mask (1, 2, 4) for touch devices (0 means ignore clicks)']
- ]);
+ 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);
-//
-// Private functions
-//
-
-function onMouseButton(e, down) {
- var evt, pos, bmask;
- if (! conf.focused) {
- return true;
- }
- evt = (e ? e : window.event);
- pos = Util.getEventPosition(e, conf.target, conf.scale);
- if (e.touches || e.changedTouches) {
- // Touch device
- bmask = conf.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
- }
- //Util.Debug("mouse " + pos.x + "," + pos.y + " down: " + down +
- // " bmask: " + bmask + "(evt.button: " + evt.button + ")");
- if (bmask > 0 && conf.onMouseButton) {
- Util.Debug("onMouseButton " + (down ? "down" : "up") +
- ", x: " + pos.x + ", y: " + pos.y + ", bmask: " + bmask);
- conf.onMouseButton(pos.x, pos.y, down, bmask);
- }
- Util.stopEvent(e);
- return false;
-}
-
-function onMouseDown(e) {
- onMouseButton(e, 1);
-}
-
-function onMouseUp(e) {
- onMouseButton(e, 0);
-}
-
-function onMouseWheel(e) {
- var evt, pos, bmask, wheelData;
- if (! conf.focused) {
- return true;
- }
- evt = (e ? e : window.event);
- pos = Util.getEventPosition(e, conf.target, conf.scale);
- wheelData = evt.detail ? evt.detail * -1 : evt.wheelDelta / 40;
- if (wheelData > 0) {
- bmask = 1 << 3;
- } else {
- bmask = 1 << 4;
- }
- //Util.Debug('mouse scroll by ' + wheelData + ':' + pos.x + "," + pos.y);
- if (conf.onMouseButton) {
- conf.onMouseButton(pos.x, pos.y, 1, bmask);
- conf.onMouseButton(pos.x, pos.y, 0, bmask);
- }
- Util.stopEvent(e);
- return false;
-}
-
-function onMouseMove(e) {
- var evt, pos;
- if (! conf.focused) {
- return true;
- }
- evt = (e ? e : window.event);
- pos = Util.getEventPosition(e, conf.target, conf.scale);
- //Util.Debug('mouse ' + evt.which + '/' + evt.button + ' up:' + pos.x + "," + pos.y);
- if (conf.onMouseMove) {
- conf.onMouseMove(pos.x, pos.y);
- }
- Util.stopEvent(e);
- return false;
-}
-
-function onMouseDisable(e) {
- var evt, pos;
- if (! conf.focused) {
- return true;
- }
- evt = (e ? e : window.event);
- pos = Util.getEventPosition(e, conf.target, conf.scale);
- /* Stop propagation if inside canvas area */
- if ((pos.x >= 0) && (pos.y >= 0) &&
- (pos.x < conf.target.offsetWidth) &&
- (pos.y < conf.target.offsetHeight)) {
- //Util.Debug("mouse event disabled");
- Util.stopEvent(e);
- return false;
- }
- //Util.Debug("mouse event not disabled");
- return true;
-}
-
-//
-// Public API interface functions
-//
-
-that.grab = function() {
- //Util.Debug(">> Mouse.grab");
- var c = conf.target;
-
- if ('ontouchstart' in document.documentElement) {
- Util.addEvent(c, 'touchstart', onMouseDown);
- Util.addEvent(c, 'touchend', onMouseUp);
- Util.addEvent(c, 'touchmove', onMouseMove);
- } else {
- Util.addEvent(c, 'mousedown', onMouseDown);
- Util.addEvent(c, 'mouseup', onMouseUp);
- Util.addEvent(c, 'mousemove', onMouseMove);
- Util.addEvent(c, (Util.Engine.gecko) ? 'DOMMouseScroll' : 'mousewheel',
- onMouseWheel);
- }
-
- /* Work around right and middle click browser behaviors */
- Util.addEvent(document, 'click', onMouseDisable);
- Util.addEvent(document.body, 'contextmenu', onMouseDisable);
-
- //Util.Debug("<< Mouse.grab");
-};
-
-that.ungrab = function() {
- //Util.Debug(">> Mouse.ungrab");
- var c = conf.target;
-
- if ('ontouchstart' in document.documentElement) {
- Util.removeEvent(c, 'touchstart', onMouseDown);
- Util.removeEvent(c, 'touchend', onMouseUp);
- Util.removeEvent(c, 'touchmove', onMouseMove);
- } else {
- Util.removeEvent(c, 'mousedown', onMouseDown);
- Util.removeEvent(c, 'mouseup', onMouseUp);
- Util.removeEvent(c, 'mousemove', onMouseMove);
- Util.removeEvent(c, (Util.Engine.gecko) ? 'DOMMouseScroll' : 'mousewheel',
- onMouseWheel);
- }
-
- /* Work around right and middle click browser behaviors */
- Util.removeEvent(document, 'click', onMouseDisable);
- Util.removeEvent(document.body, 'contextmenu', onMouseDisable);
-
- //Util.Debug(">> Mouse.ungrab");
-};
-
-return that; // Return the public API interface
-
-} // End of Mouse()
+ }
+ };
+ 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
-/*
- * Browser keypress to X11 keysym for Unicode characters > U+00FF
- */
-unicodeTable = {
- 0x0104 : 0x01a1,
- 0x02D8 : 0x01a2,
- 0x0141 : 0x01a3,
- 0x013D : 0x01a5,
- 0x015A : 0x01a6,
- 0x0160 : 0x01a9,
- 0x015E : 0x01aa,
- 0x0164 : 0x01ab,
- 0x0179 : 0x01ac,
- 0x017D : 0x01ae,
- 0x017B : 0x01af,
- 0x0105 : 0x01b1,
- 0x02DB : 0x01b2,
- 0x0142 : 0x01b3,
- 0x013E : 0x01b5,
- 0x015B : 0x01b6,
- 0x02C7 : 0x01b7,
- 0x0161 : 0x01b9,
- 0x015F : 0x01ba,
- 0x0165 : 0x01bb,
- 0x017A : 0x01bc,
- 0x02DD : 0x01bd,
- 0x017E : 0x01be,
- 0x017C : 0x01bf,
- 0x0154 : 0x01c0,
- 0x0102 : 0x01c3,
- 0x0139 : 0x01c5,
- 0x0106 : 0x01c6,
- 0x010C : 0x01c8,
- 0x0118 : 0x01ca,
- 0x011A : 0x01cc,
- 0x010E : 0x01cf,
- 0x0110 : 0x01d0,
- 0x0143 : 0x01d1,
- 0x0147 : 0x01d2,
- 0x0150 : 0x01d5,
- 0x0158 : 0x01d8,
- 0x016E : 0x01d9,
- 0x0170 : 0x01db,
- 0x0162 : 0x01de,
- 0x0155 : 0x01e0,
- 0x0103 : 0x01e3,
- 0x013A : 0x01e5,
- 0x0107 : 0x01e6,
- 0x010D : 0x01e8,
- 0x0119 : 0x01ea,
- 0x011B : 0x01ec,
- 0x010F : 0x01ef,
- 0x0111 : 0x01f0,
- 0x0144 : 0x01f1,
- 0x0148 : 0x01f2,
- 0x0151 : 0x01f5,
- 0x0171 : 0x01fb,
- 0x0159 : 0x01f8,
- 0x016F : 0x01f9,
- 0x0163 : 0x01fe,
- 0x02D9 : 0x01ff,
- 0x0126 : 0x02a1,
- 0x0124 : 0x02a6,
- 0x0130 : 0x02a9,
- 0x011E : 0x02ab,
- 0x0134 : 0x02ac,
- 0x0127 : 0x02b1,
- 0x0125 : 0x02b6,
- 0x0131 : 0x02b9,
- 0x011F : 0x02bb,
- 0x0135 : 0x02bc,
- 0x010A : 0x02c5,
- 0x0108 : 0x02c6,
- 0x0120 : 0x02d5,
- 0x011C : 0x02d8,
- 0x016C : 0x02dd,
- 0x015C : 0x02de,
- 0x010B : 0x02e5,
- 0x0109 : 0x02e6,
- 0x0121 : 0x02f5,
- 0x011D : 0x02f8,
- 0x016D : 0x02fd,
- 0x015D : 0x02fe,
- 0x0138 : 0x03a2,
- 0x0156 : 0x03a3,
- 0x0128 : 0x03a5,
- 0x013B : 0x03a6,
- 0x0112 : 0x03aa,
- 0x0122 : 0x03ab,
- 0x0166 : 0x03ac,
- 0x0157 : 0x03b3,
- 0x0129 : 0x03b5,
- 0x013C : 0x03b6,
- 0x0113 : 0x03ba,
- 0x0123 : 0x03bb,
- 0x0167 : 0x03bc,
- 0x014A : 0x03bd,
- 0x014B : 0x03bf,
- 0x0100 : 0x03c0,
- 0x012E : 0x03c7,
- 0x0116 : 0x03cc,
- 0x012A : 0x03cf,
- 0x0145 : 0x03d1,
- 0x014C : 0x03d2,
- 0x0136 : 0x03d3,
- 0x0172 : 0x03d9,
- 0x0168 : 0x03dd,
- 0x016A : 0x03de,
- 0x0101 : 0x03e0,
- 0x012F : 0x03e7,
- 0x0117 : 0x03ec,
- 0x012B : 0x03ef,
- 0x0146 : 0x03f1,
- 0x014D : 0x03f2,
- 0x0137 : 0x03f3,
- 0x0173 : 0x03f9,
- 0x0169 : 0x03fd,
- 0x016B : 0x03fe,
- 0x1E02 : 0x1001e02,
- 0x1E03 : 0x1001e03,
- 0x1E0A : 0x1001e0a,
- 0x1E80 : 0x1001e80,
- 0x1E82 : 0x1001e82,
- 0x1E0B : 0x1001e0b,
- 0x1EF2 : 0x1001ef2,
- 0x1E1E : 0x1001e1e,
- 0x1E1F : 0x1001e1f,
- 0x1E40 : 0x1001e40,
- 0x1E41 : 0x1001e41,
- 0x1E56 : 0x1001e56,
- 0x1E81 : 0x1001e81,
- 0x1E57 : 0x1001e57,
- 0x1E83 : 0x1001e83,
- 0x1E60 : 0x1001e60,
- 0x1EF3 : 0x1001ef3,
- 0x1E84 : 0x1001e84,
- 0x1E85 : 0x1001e85,
- 0x1E61 : 0x1001e61,
- 0x0174 : 0x1000174,
- 0x1E6A : 0x1001e6a,
- 0x0176 : 0x1000176,
- 0x0175 : 0x1000175,
- 0x1E6B : 0x1001e6b,
- 0x0177 : 0x1000177,
- 0x0152 : 0x13bc,
- 0x0153 : 0x13bd,
- 0x0178 : 0x13be,
- 0x203E : 0x047e,
- 0x3002 : 0x04a1,
- 0x300C : 0x04a2,
- 0x300D : 0x04a3,
- 0x3001 : 0x04a4,
- 0x30FB : 0x04a5,
- 0x30F2 : 0x04a6,
- 0x30A1 : 0x04a7,
- 0x30A3 : 0x04a8,
- 0x30A5 : 0x04a9,
- 0x30A7 : 0x04aa,
- 0x30A9 : 0x04ab,
- 0x30E3 : 0x04ac,
- 0x30E5 : 0x04ad,
- 0x30E7 : 0x04ae,
- 0x30C3 : 0x04af,
- 0x30FC : 0x04b0,
- 0x30A2 : 0x04b1,
- 0x30A4 : 0x04b2,
- 0x30A6 : 0x04b3,
- 0x30A8 : 0x04b4,
- 0x30AA : 0x04b5,
- 0x30AB : 0x04b6,
- 0x30AD : 0x04b7,
- 0x30AF : 0x04b8,
- 0x30B1 : 0x04b9,
- 0x30B3 : 0x04ba,
- 0x30B5 : 0x04bb,
- 0x30B7 : 0x04bc,
- 0x30B9 : 0x04bd,
- 0x30BB : 0x04be,
- 0x30BD : 0x04bf,
- 0x30BF : 0x04c0,
- 0x30C1 : 0x04c1,
- 0x30C4 : 0x04c2,
- 0x30C6 : 0x04c3,
- 0x30C8 : 0x04c4,
- 0x30CA : 0x04c5,
- 0x30CB : 0x04c6,
- 0x30CC : 0x04c7,
- 0x30CD : 0x04c8,
- 0x30CE : 0x04c9,
- 0x30CF : 0x04ca,
- 0x30D2 : 0x04cb,
- 0x30D5 : 0x04cc,
- 0x30D8 : 0x04cd,
- 0x30DB : 0x04ce,
- 0x30DE : 0x04cf,
- 0x30DF : 0x04d0,
- 0x30E0 : 0x04d1,
- 0x30E1 : 0x04d2,
- 0x30E2 : 0x04d3,
- 0x30E4 : 0x04d4,
- 0x30E6 : 0x04d5,
- 0x30E8 : 0x04d6,
- 0x30E9 : 0x04d7,
- 0x30EA : 0x04d8,
- 0x30EB : 0x04d9,
- 0x30EC : 0x04da,
- 0x30ED : 0x04db,
- 0x30EF : 0x04dc,
- 0x30F3 : 0x04dd,
- 0x309B : 0x04de,
- 0x309C : 0x04df,
- 0x06F0 : 0x10006f0,
- 0x06F1 : 0x10006f1,
- 0x06F2 : 0x10006f2,
- 0x06F3 : 0x10006f3,
- 0x06F4 : 0x10006f4,
- 0x06F5 : 0x10006f5,
- 0x06F6 : 0x10006f6,
- 0x06F7 : 0x10006f7,
- 0x06F8 : 0x10006f8,
- 0x06F9 : 0x10006f9,
- 0x066A : 0x100066a,
- 0x0670 : 0x1000670,
- 0x0679 : 0x1000679,
- 0x067E : 0x100067e,
- 0x0686 : 0x1000686,
- 0x0688 : 0x1000688,
- 0x0691 : 0x1000691,
- 0x060C : 0x05ac,
- 0x06D4 : 0x10006d4,
- 0x0660 : 0x1000660,
- 0x0661 : 0x1000661,
- 0x0662 : 0x1000662,
- 0x0663 : 0x1000663,
- 0x0664 : 0x1000664,
- 0x0665 : 0x1000665,
- 0x0666 : 0x1000666,
- 0x0667 : 0x1000667,
- 0x0668 : 0x1000668,
- 0x0669 : 0x1000669,
- 0x061B : 0x05bb,
- 0x061F : 0x05bf,
- 0x0621 : 0x05c1,
- 0x0622 : 0x05c2,
- 0x0623 : 0x05c3,
- 0x0624 : 0x05c4,
- 0x0625 : 0x05c5,
- 0x0626 : 0x05c6,
- 0x0627 : 0x05c7,
- 0x0628 : 0x05c8,
- 0x0629 : 0x05c9,
- 0x062A : 0x05ca,
- 0x062B : 0x05cb,
- 0x062C : 0x05cc,
- 0x062D : 0x05cd,
- 0x062E : 0x05ce,
- 0x062F : 0x05cf,
- 0x0630 : 0x05d0,
- 0x0631 : 0x05d1,
- 0x0632 : 0x05d2,
- 0x0633 : 0x05d3,
- 0x0634 : 0x05d4,
- 0x0635 : 0x05d5,
- 0x0636 : 0x05d6,
- 0x0637 : 0x05d7,
- 0x0638 : 0x05d8,
- 0x0639 : 0x05d9,
- 0x063A : 0x05da,
- 0x0640 : 0x05e0,
- 0x0641 : 0x05e1,
- 0x0642 : 0x05e2,
- 0x0643 : 0x05e3,
- 0x0644 : 0x05e4,
- 0x0645 : 0x05e5,
- 0x0646 : 0x05e6,
- 0x0647 : 0x05e7,
- 0x0648 : 0x05e8,
- 0x0649 : 0x05e9,
- 0x064A : 0x05ea,
- 0x064B : 0x05eb,
- 0x064C : 0x05ec,
- 0x064D : 0x05ed,
- 0x064E : 0x05ee,
- 0x064F : 0x05ef,
- 0x0650 : 0x05f0,
- 0x0651 : 0x05f1,
- 0x0652 : 0x05f2,
- 0x0653 : 0x1000653,
- 0x0654 : 0x1000654,
- 0x0655 : 0x1000655,
- 0x0698 : 0x1000698,
- 0x06A4 : 0x10006a4,
- 0x06A9 : 0x10006a9,
- 0x06AF : 0x10006af,
- 0x06BA : 0x10006ba,
- 0x06BE : 0x10006be,
- 0x06CC : 0x10006cc,
- 0x06D2 : 0x10006d2,
- 0x06C1 : 0x10006c1,
- 0x0492 : 0x1000492,
- 0x0493 : 0x1000493,
- 0x0496 : 0x1000496,
- 0x0497 : 0x1000497,
- 0x049A : 0x100049a,
- 0x049B : 0x100049b,
- 0x049C : 0x100049c,
- 0x049D : 0x100049d,
- 0x04A2 : 0x10004a2,
- 0x04A3 : 0x10004a3,
- 0x04AE : 0x10004ae,
- 0x04AF : 0x10004af,
- 0x04B0 : 0x10004b0,
- 0x04B1 : 0x10004b1,
- 0x04B2 : 0x10004b2,
- 0x04B3 : 0x10004b3,
- 0x04B6 : 0x10004b6,
- 0x04B7 : 0x10004b7,
- 0x04B8 : 0x10004b8,
- 0x04B9 : 0x10004b9,
- 0x04BA : 0x10004ba,
- 0x04BB : 0x10004bb,
- 0x04D8 : 0x10004d8,
- 0x04D9 : 0x10004d9,
- 0x04E2 : 0x10004e2,
- 0x04E3 : 0x10004e3,
- 0x04E8 : 0x10004e8,
- 0x04E9 : 0x10004e9,
- 0x04EE : 0x10004ee,
- 0x04EF : 0x10004ef,
- 0x0452 : 0x06a1,
- 0x0453 : 0x06a2,
- 0x0451 : 0x06a3,
- 0x0454 : 0x06a4,
- 0x0455 : 0x06a5,
- 0x0456 : 0x06a6,
- 0x0457 : 0x06a7,
- 0x0458 : 0x06a8,
- 0x0459 : 0x06a9,
- 0x045A : 0x06aa,
- 0x045B : 0x06ab,
- 0x045C : 0x06ac,
- 0x0491 : 0x06ad,
- 0x045E : 0x06ae,
- 0x045F : 0x06af,
- 0x2116 : 0x06b0,
- 0x0402 : 0x06b1,
- 0x0403 : 0x06b2,
- 0x0401 : 0x06b3,
- 0x0404 : 0x06b4,
- 0x0405 : 0x06b5,
- 0x0406 : 0x06b6,
- 0x0407 : 0x06b7,
- 0x0408 : 0x06b8,
- 0x0409 : 0x06b9,
- 0x040A : 0x06ba,
- 0x040B : 0x06bb,
- 0x040C : 0x06bc,
- 0x0490 : 0x06bd,
- 0x040E : 0x06be,
- 0x040F : 0x06bf,
- 0x044E : 0x06c0,
- 0x0430 : 0x06c1,
- 0x0431 : 0x06c2,
- 0x0446 : 0x06c3,
- 0x0434 : 0x06c4,
- 0x0435 : 0x06c5,
- 0x0444 : 0x06c6,
- 0x0433 : 0x06c7,
- 0x0445 : 0x06c8,
- 0x0438 : 0x06c9,
- 0x0439 : 0x06ca,
- 0x043A : 0x06cb,
- 0x043B : 0x06cc,
- 0x043C : 0x06cd,
- 0x043D : 0x06ce,
- 0x043E : 0x06cf,
- 0x043F : 0x06d0,
- 0x044F : 0x06d1,
- 0x0440 : 0x06d2,
- 0x0441 : 0x06d3,
- 0x0442 : 0x06d4,
- 0x0443 : 0x06d5,
- 0x0436 : 0x06d6,
- 0x0432 : 0x06d7,
- 0x044C : 0x06d8,
- 0x044B : 0x06d9,
- 0x0437 : 0x06da,
- 0x0448 : 0x06db,
- 0x044D : 0x06dc,
- 0x0449 : 0x06dd,
- 0x0447 : 0x06de,
- 0x044A : 0x06df,
- 0x042E : 0x06e0,
- 0x0410 : 0x06e1,
- 0x0411 : 0x06e2,
- 0x0426 : 0x06e3,
- 0x0414 : 0x06e4,
- 0x0415 : 0x06e5,
- 0x0424 : 0x06e6,
- 0x0413 : 0x06e7,
- 0x0425 : 0x06e8,
- 0x0418 : 0x06e9,
- 0x0419 : 0x06ea,
- 0x041A : 0x06eb,
- 0x041B : 0x06ec,
- 0x041C : 0x06ed,
- 0x041D : 0x06ee,
- 0x041E : 0x06ef,
- 0x041F : 0x06f0,
- 0x042F : 0x06f1,
- 0x0420 : 0x06f2,
- 0x0421 : 0x06f3,
- 0x0422 : 0x06f4,
- 0x0423 : 0x06f5,
- 0x0416 : 0x06f6,
- 0x0412 : 0x06f7,
- 0x042C : 0x06f8,
- 0x042B : 0x06f9,
- 0x0417 : 0x06fa,
- 0x0428 : 0x06fb,
- 0x042D : 0x06fc,
- 0x0429 : 0x06fd,
- 0x0427 : 0x06fe,
- 0x042A : 0x06ff,
- 0x0386 : 0x07a1,
- 0x0388 : 0x07a2,
- 0x0389 : 0x07a3,
- 0x038A : 0x07a4,
- 0x03AA : 0x07a5,
- 0x038C : 0x07a7,
- 0x038E : 0x07a8,
- 0x03AB : 0x07a9,
- 0x038F : 0x07ab,
- 0x0385 : 0x07ae,
- 0x2015 : 0x07af,
- 0x03AC : 0x07b1,
- 0x03AD : 0x07b2,
- 0x03AE : 0x07b3,
- 0x03AF : 0x07b4,
- 0x03CA : 0x07b5,
- 0x0390 : 0x07b6,
- 0x03CC : 0x07b7,
- 0x03CD : 0x07b8,
- 0x03CB : 0x07b9,
- 0x03B0 : 0x07ba,
- 0x03CE : 0x07bb,
- 0x0391 : 0x07c1,
- 0x0392 : 0x07c2,
- 0x0393 : 0x07c3,
- 0x0394 : 0x07c4,
- 0x0395 : 0x07c5,
- 0x0396 : 0x07c6,
- 0x0397 : 0x07c7,
- 0x0398 : 0x07c8,
- 0x0399 : 0x07c9,
- 0x039A : 0x07ca,
- 0x039B : 0x07cb,
- 0x039C : 0x07cc,
- 0x039D : 0x07cd,
- 0x039E : 0x07ce,
- 0x039F : 0x07cf,
- 0x03A0 : 0x07d0,
- 0x03A1 : 0x07d1,
- 0x03A3 : 0x07d2,
- 0x03A4 : 0x07d4,
- 0x03A5 : 0x07d5,
- 0x03A6 : 0x07d6,
- 0x03A7 : 0x07d7,
- 0x03A8 : 0x07d8,
- 0x03A9 : 0x07d9,
- 0x03B1 : 0x07e1,
- 0x03B2 : 0x07e2,
- 0x03B3 : 0x07e3,
- 0x03B4 : 0x07e4,
- 0x03B5 : 0x07e5,
- 0x03B6 : 0x07e6,
- 0x03B7 : 0x07e7,
- 0x03B8 : 0x07e8,
- 0x03B9 : 0x07e9,
- 0x03BA : 0x07ea,
- 0x03BB : 0x07eb,
- 0x03BC : 0x07ec,
- 0x03BD : 0x07ed,
- 0x03BE : 0x07ee,
- 0x03BF : 0x07ef,
- 0x03C0 : 0x07f0,
- 0x03C1 : 0x07f1,
- 0x03C3 : 0x07f2,
- 0x03C2 : 0x07f3,
- 0x03C4 : 0x07f4,
- 0x03C5 : 0x07f5,
- 0x03C6 : 0x07f6,
- 0x03C7 : 0x07f7,
- 0x03C8 : 0x07f8,
- 0x03C9 : 0x07f9,
- 0x23B7 : 0x08a1,
- 0x2320 : 0x08a4,
- 0x2321 : 0x08a5,
- 0x23A1 : 0x08a7,
- 0x23A3 : 0x08a8,
- 0x23A4 : 0x08a9,
- 0x23A6 : 0x08aa,
- 0x239B : 0x08ab,
- 0x239D : 0x08ac,
- 0x239E : 0x08ad,
- 0x23A0 : 0x08ae,
- 0x23A8 : 0x08af,
- 0x23AC : 0x08b0,
- 0x2264 : 0x08bc,
- 0x2260 : 0x08bd,
- 0x2265 : 0x08be,
- 0x222B : 0x08bf,
- 0x2234 : 0x08c0,
- 0x221D : 0x08c1,
- 0x221E : 0x08c2,
- 0x2207 : 0x08c5,
- 0x223C : 0x08c8,
- 0x2243 : 0x08c9,
- 0x21D4 : 0x08cd,
- 0x21D2 : 0x08ce,
- 0x2261 : 0x08cf,
- //0x221A : 0x08d6,
- 0x2282 : 0x08da,
- 0x2283 : 0x08db,
- 0x2229 : 0x08dc,
- 0x222A : 0x08dd,
- 0x2227 : 0x08de,
- 0x2228 : 0x08df,
- //0x2202 : 0x08ef,
- 0x0192 : 0x08f6,
- 0x2190 : 0x08fb,
- 0x2191 : 0x08fc,
- 0x2192 : 0x08fd,
- 0x2193 : 0x08fe,
- 0x25C6 : 0x09e0,
- 0x2592 : 0x09e1,
- 0x2409 : 0x09e2,
- 0x240C : 0x09e3,
- 0x240D : 0x09e4,
- 0x240A : 0x09e5,
- 0x2424 : 0x09e8,
- 0x240B : 0x09e9,
- 0x2518 : 0x09ea,
- 0x2510 : 0x09eb,
- 0x250C : 0x09ec,
- 0x2514 : 0x09ed,
- 0x253C : 0x09ee,
- 0x23BA : 0x09ef,
- 0x23BB : 0x09f0,
- 0x2500 : 0x09f1,
- 0x23BC : 0x09f2,
- 0x23BD : 0x09f3,
- 0x251C : 0x09f4,
- 0x2524 : 0x09f5,
- 0x2534 : 0x09f6,
- 0x252C : 0x09f7,
- 0x2502 : 0x09f8,
- 0x2003 : 0x0aa1,
- 0x2002 : 0x0aa2,
- 0x2004 : 0x0aa3,
- 0x2005 : 0x0aa4,
- 0x2007 : 0x0aa5,
- 0x2008 : 0x0aa6,
- 0x2009 : 0x0aa7,
- 0x200A : 0x0aa8,
- 0x2014 : 0x0aa9,
- 0x2013 : 0x0aaa,
- 0x2026 : 0x0aae,
- 0x2025 : 0x0aaf,
- 0x2153 : 0x0ab0,
- 0x2154 : 0x0ab1,
- 0x2155 : 0x0ab2,
- 0x2156 : 0x0ab3,
- 0x2157 : 0x0ab4,
- 0x2158 : 0x0ab5,
- 0x2159 : 0x0ab6,
- 0x215A : 0x0ab7,
- 0x2105 : 0x0ab8,
- 0x2012 : 0x0abb,
- 0x215B : 0x0ac3,
- 0x215C : 0x0ac4,
- 0x215D : 0x0ac5,
- 0x215E : 0x0ac6,
- 0x2122 : 0x0ac9,
- 0x2018 : 0x0ad0,
- 0x2019 : 0x0ad1,
- 0x201C : 0x0ad2,
- 0x201D : 0x0ad3,
- 0x211E : 0x0ad4,
- 0x2032 : 0x0ad6,
- 0x2033 : 0x0ad7,
- 0x271D : 0x0ad9,
- 0x2663 : 0x0aec,
- 0x2666 : 0x0aed,
- 0x2665 : 0x0aee,
- 0x2720 : 0x0af0,
- 0x2020 : 0x0af1,
- 0x2021 : 0x0af2,
- 0x2713 : 0x0af3,
- 0x2717 : 0x0af4,
- 0x266F : 0x0af5,
- 0x266D : 0x0af6,
- 0x2642 : 0x0af7,
- 0x2640 : 0x0af8,
- 0x260E : 0x0af9,
- 0x2315 : 0x0afa,
- 0x2117 : 0x0afb,
- 0x2038 : 0x0afc,
- 0x201A : 0x0afd,
- 0x201E : 0x0afe,
- 0x22A4 : 0x0bc2,
- 0x230A : 0x0bc4,
- 0x2218 : 0x0bca,
- 0x2395 : 0x0bcc,
- 0x22A5 : 0x0bce,
- 0x25CB : 0x0bcf,
- 0x2308 : 0x0bd3,
- 0x22A3 : 0x0bdc,
- 0x22A2 : 0x0bfc,
- 0x2017 : 0x0cdf,
- 0x05D0 : 0x0ce0,
- 0x05D1 : 0x0ce1,
- 0x05D2 : 0x0ce2,
- 0x05D3 : 0x0ce3,
- 0x05D4 : 0x0ce4,
- 0x05D5 : 0x0ce5,
- 0x05D6 : 0x0ce6,
- 0x05D7 : 0x0ce7,
- 0x05D8 : 0x0ce8,
- 0x05D9 : 0x0ce9,
- 0x05DA : 0x0cea,
- 0x05DB : 0x0ceb,
- 0x05DC : 0x0cec,
- 0x05DD : 0x0ced,
- 0x05DE : 0x0cee,
- 0x05DF : 0x0cef,
- 0x05E0 : 0x0cf0,
- 0x05E1 : 0x0cf1,
- 0x05E2 : 0x0cf2,
- 0x05E3 : 0x0cf3,
- 0x05E4 : 0x0cf4,
- 0x05E5 : 0x0cf5,
- 0x05E6 : 0x0cf6,
- 0x05E7 : 0x0cf7,
- 0x05E8 : 0x0cf8,
- 0x05E9 : 0x0cf9,
- 0x05EA : 0x0cfa,
- 0x0E01 : 0x0da1,
- 0x0E02 : 0x0da2,
- 0x0E03 : 0x0da3,
- 0x0E04 : 0x0da4,
- 0x0E05 : 0x0da5,
- 0x0E06 : 0x0da6,
- 0x0E07 : 0x0da7,
- 0x0E08 : 0x0da8,
- 0x0E09 : 0x0da9,
- 0x0E0A : 0x0daa,
- 0x0E0B : 0x0dab,
- 0x0E0C : 0x0dac,
- 0x0E0D : 0x0dad,
- 0x0E0E : 0x0dae,
- 0x0E0F : 0x0daf,
- 0x0E10 : 0x0db0,
- 0x0E11 : 0x0db1,
- 0x0E12 : 0x0db2,
- 0x0E13 : 0x0db3,
- 0x0E14 : 0x0db4,
- 0x0E15 : 0x0db5,
- 0x0E16 : 0x0db6,
- 0x0E17 : 0x0db7,
- 0x0E18 : 0x0db8,
- 0x0E19 : 0x0db9,
- 0x0E1A : 0x0dba,
- 0x0E1B : 0x0dbb,
- 0x0E1C : 0x0dbc,
- 0x0E1D : 0x0dbd,
- 0x0E1E : 0x0dbe,
- 0x0E1F : 0x0dbf,
- 0x0E20 : 0x0dc0,
- 0x0E21 : 0x0dc1,
- 0x0E22 : 0x0dc2,
- 0x0E23 : 0x0dc3,
- 0x0E24 : 0x0dc4,
- 0x0E25 : 0x0dc5,
- 0x0E26 : 0x0dc6,
- 0x0E27 : 0x0dc7,
- 0x0E28 : 0x0dc8,
- 0x0E29 : 0x0dc9,
- 0x0E2A : 0x0dca,
- 0x0E2B : 0x0dcb,
- 0x0E2C : 0x0dcc,
- 0x0E2D : 0x0dcd,
- 0x0E2E : 0x0dce,
- 0x0E2F : 0x0dcf,
- 0x0E30 : 0x0dd0,
- 0x0E31 : 0x0dd1,
- 0x0E32 : 0x0dd2,
- 0x0E33 : 0x0dd3,
- 0x0E34 : 0x0dd4,
- 0x0E35 : 0x0dd5,
- 0x0E36 : 0x0dd6,
- 0x0E37 : 0x0dd7,
- 0x0E38 : 0x0dd8,
- 0x0E39 : 0x0dd9,
- 0x0E3A : 0x0dda,
- 0x0E3F : 0x0ddf,
- 0x0E40 : 0x0de0,
- 0x0E41 : 0x0de1,
- 0x0E42 : 0x0de2,
- 0x0E43 : 0x0de3,
- 0x0E44 : 0x0de4,
- 0x0E45 : 0x0de5,
- 0x0E46 : 0x0de6,
- 0x0E47 : 0x0de7,
- 0x0E48 : 0x0de8,
- 0x0E49 : 0x0de9,
- 0x0E4A : 0x0dea,
- 0x0E4B : 0x0deb,
- 0x0E4C : 0x0dec,
- 0x0E4D : 0x0ded,
- 0x0E50 : 0x0df0,
- 0x0E51 : 0x0df1,
- 0x0E52 : 0x0df2,
- 0x0E53 : 0x0df3,
- 0x0E54 : 0x0df4,
- 0x0E55 : 0x0df5,
- 0x0E56 : 0x0df6,
- 0x0E57 : 0x0df7,
- 0x0E58 : 0x0df8,
- 0x0E59 : 0x0df9,
- 0x0587 : 0x1000587,
- 0x0589 : 0x1000589,
- 0x055D : 0x100055d,
- 0x058A : 0x100058a,
- 0x055C : 0x100055c,
- 0x055B : 0x100055b,
- 0x055E : 0x100055e,
- 0x0531 : 0x1000531,
- 0x0561 : 0x1000561,
- 0x0532 : 0x1000532,
- 0x0562 : 0x1000562,
- 0x0533 : 0x1000533,
- 0x0563 : 0x1000563,
- 0x0534 : 0x1000534,
- 0x0564 : 0x1000564,
- 0x0535 : 0x1000535,
- 0x0565 : 0x1000565,
- 0x0536 : 0x1000536,
- 0x0566 : 0x1000566,
- 0x0537 : 0x1000537,
- 0x0567 : 0x1000567,
- 0x0538 : 0x1000538,
- 0x0568 : 0x1000568,
- 0x0539 : 0x1000539,
- 0x0569 : 0x1000569,
- 0x053A : 0x100053a,
- 0x056A : 0x100056a,
- 0x053B : 0x100053b,
- 0x056B : 0x100056b,
- 0x053C : 0x100053c,
- 0x056C : 0x100056c,
- 0x053D : 0x100053d,
- 0x056D : 0x100056d,
- 0x053E : 0x100053e,
- 0x056E : 0x100056e,
- 0x053F : 0x100053f,
- 0x056F : 0x100056f,
- 0x0540 : 0x1000540,
- 0x0570 : 0x1000570,
- 0x0541 : 0x1000541,
- 0x0571 : 0x1000571,
- 0x0542 : 0x1000542,
- 0x0572 : 0x1000572,
- 0x0543 : 0x1000543,
- 0x0573 : 0x1000573,
- 0x0544 : 0x1000544,
- 0x0574 : 0x1000574,
- 0x0545 : 0x1000545,
- 0x0575 : 0x1000575,
- 0x0546 : 0x1000546,
- 0x0576 : 0x1000576,
- 0x0547 : 0x1000547,
- 0x0577 : 0x1000577,
- 0x0548 : 0x1000548,
- 0x0578 : 0x1000578,
- 0x0549 : 0x1000549,
- 0x0579 : 0x1000579,
- 0x054A : 0x100054a,
- 0x057A : 0x100057a,
- 0x054B : 0x100054b,
- 0x057B : 0x100057b,
- 0x054C : 0x100054c,
- 0x057C : 0x100057c,
- 0x054D : 0x100054d,
- 0x057D : 0x100057d,
- 0x054E : 0x100054e,
- 0x057E : 0x100057e,
- 0x054F : 0x100054f,
- 0x057F : 0x100057f,
- 0x0550 : 0x1000550,
- 0x0580 : 0x1000580,
- 0x0551 : 0x1000551,
- 0x0581 : 0x1000581,
- 0x0552 : 0x1000552,
- 0x0582 : 0x1000582,
- 0x0553 : 0x1000553,
- 0x0583 : 0x1000583,
- 0x0554 : 0x1000554,
- 0x0584 : 0x1000584,
- 0x0555 : 0x1000555,
- 0x0585 : 0x1000585,
- 0x0556 : 0x1000556,
- 0x0586 : 0x1000586,
- 0x055A : 0x100055a,
- 0x10D0 : 0x10010d0,
- 0x10D1 : 0x10010d1,
- 0x10D2 : 0x10010d2,
- 0x10D3 : 0x10010d3,
- 0x10D4 : 0x10010d4,
- 0x10D5 : 0x10010d5,
- 0x10D6 : 0x10010d6,
- 0x10D7 : 0x10010d7,
- 0x10D8 : 0x10010d8,
- 0x10D9 : 0x10010d9,
- 0x10DA : 0x10010da,
- 0x10DB : 0x10010db,
- 0x10DC : 0x10010dc,
- 0x10DD : 0x10010dd,
- 0x10DE : 0x10010de,
- 0x10DF : 0x10010df,
- 0x10E0 : 0x10010e0,
- 0x10E1 : 0x10010e1,
- 0x10E2 : 0x10010e2,
- 0x10E3 : 0x10010e3,
- 0x10E4 : 0x10010e4,
- 0x10E5 : 0x10010e5,
- 0x10E6 : 0x10010e6,
- 0x10E7 : 0x10010e7,
- 0x10E8 : 0x10010e8,
- 0x10E9 : 0x10010e9,
- 0x10EA : 0x10010ea,
- 0x10EB : 0x10010eb,
- 0x10EC : 0x10010ec,
- 0x10ED : 0x10010ed,
- 0x10EE : 0x10010ee,
- 0x10EF : 0x10010ef,
- 0x10F0 : 0x10010f0,
- 0x10F1 : 0x10010f1,
- 0x10F2 : 0x10010f2,
- 0x10F3 : 0x10010f3,
- 0x10F4 : 0x10010f4,
- 0x10F5 : 0x10010f5,
- 0x10F6 : 0x10010f6,
- 0x1E8A : 0x1001e8a,
- 0x012C : 0x100012c,
- 0x01B5 : 0x10001b5,
- 0x01E6 : 0x10001e6,
- 0x01D2 : 0x10001d1,
- 0x019F : 0x100019f,
- 0x1E8B : 0x1001e8b,
- 0x012D : 0x100012d,
- 0x01B6 : 0x10001b6,
- 0x01E7 : 0x10001e7,
- //0x01D2 : 0x10001d2,
- 0x0275 : 0x1000275,
- 0x018F : 0x100018f,
- 0x0259 : 0x1000259,
- 0x1E36 : 0x1001e36,
- 0x1E37 : 0x1001e37,
- 0x1EA0 : 0x1001ea0,
- 0x1EA1 : 0x1001ea1,
- 0x1EA2 : 0x1001ea2,
- 0x1EA3 : 0x1001ea3,
- 0x1EA4 : 0x1001ea4,
- 0x1EA5 : 0x1001ea5,
- 0x1EA6 : 0x1001ea6,
- 0x1EA7 : 0x1001ea7,
- 0x1EA8 : 0x1001ea8,
- 0x1EA9 : 0x1001ea9,
- 0x1EAA : 0x1001eaa,
- 0x1EAB : 0x1001eab,
- 0x1EAC : 0x1001eac,
- 0x1EAD : 0x1001ead,
- 0x1EAE : 0x1001eae,
- 0x1EAF : 0x1001eaf,
- 0x1EB0 : 0x1001eb0,
- 0x1EB1 : 0x1001eb1,
- 0x1EB2 : 0x1001eb2,
- 0x1EB3 : 0x1001eb3,
- 0x1EB4 : 0x1001eb4,
- 0x1EB5 : 0x1001eb5,
- 0x1EB6 : 0x1001eb6,
- 0x1EB7 : 0x1001eb7,
- 0x1EB8 : 0x1001eb8,
- 0x1EB9 : 0x1001eb9,
- 0x1EBA : 0x1001eba,
- 0x1EBB : 0x1001ebb,
- 0x1EBC : 0x1001ebc,
- 0x1EBD : 0x1001ebd,
- 0x1EBE : 0x1001ebe,
- 0x1EBF : 0x1001ebf,
- 0x1EC0 : 0x1001ec0,
- 0x1EC1 : 0x1001ec1,
- 0x1EC2 : 0x1001ec2,
- 0x1EC3 : 0x1001ec3,
- 0x1EC4 : 0x1001ec4,
- 0x1EC5 : 0x1001ec5,
- 0x1EC6 : 0x1001ec6,
- 0x1EC7 : 0x1001ec7,
- 0x1EC8 : 0x1001ec8,
- 0x1EC9 : 0x1001ec9,
- 0x1ECA : 0x1001eca,
- 0x1ECB : 0x1001ecb,
- 0x1ECC : 0x1001ecc,
- 0x1ECD : 0x1001ecd,
- 0x1ECE : 0x1001ece,
- 0x1ECF : 0x1001ecf,
- 0x1ED0 : 0x1001ed0,
- 0x1ED1 : 0x1001ed1,
- 0x1ED2 : 0x1001ed2,
- 0x1ED3 : 0x1001ed3,
- 0x1ED4 : 0x1001ed4,
- 0x1ED5 : 0x1001ed5,
- 0x1ED6 : 0x1001ed6,
- 0x1ED7 : 0x1001ed7,
- 0x1ED8 : 0x1001ed8,
- 0x1ED9 : 0x1001ed9,
- 0x1EDA : 0x1001eda,
- 0x1EDB : 0x1001edb,
- 0x1EDC : 0x1001edc,
- 0x1EDD : 0x1001edd,
- 0x1EDE : 0x1001ede,
- 0x1EDF : 0x1001edf,
- 0x1EE0 : 0x1001ee0,
- 0x1EE1 : 0x1001ee1,
- 0x1EE2 : 0x1001ee2,
- 0x1EE3 : 0x1001ee3,
- 0x1EE4 : 0x1001ee4,
- 0x1EE5 : 0x1001ee5,
- 0x1EE6 : 0x1001ee6,
- 0x1EE7 : 0x1001ee7,
- 0x1EE8 : 0x1001ee8,
- 0x1EE9 : 0x1001ee9,
- 0x1EEA : 0x1001eea,
- 0x1EEB : 0x1001eeb,
- 0x1EEC : 0x1001eec,
- 0x1EED : 0x1001eed,
- 0x1EEE : 0x1001eee,
- 0x1EEF : 0x1001eef,
- 0x1EF0 : 0x1001ef0,
- 0x1EF1 : 0x1001ef1,
- 0x1EF4 : 0x1001ef4,
- 0x1EF5 : 0x1001ef5,
- 0x1EF6 : 0x1001ef6,
- 0x1EF7 : 0x1001ef7,
- 0x1EF8 : 0x1001ef8,
- 0x1EF9 : 0x1001ef9,
- 0x01A0 : 0x10001a0,
- 0x01A1 : 0x10001a1,
- 0x01AF : 0x10001af,
- 0x01B0 : 0x10001b0,
- 0x20A0 : 0x10020a0,
- 0x20A1 : 0x10020a1,
- 0x20A2 : 0x10020a2,
- 0x20A3 : 0x10020a3,
- 0x20A4 : 0x10020a4,
- 0x20A5 : 0x10020a5,
- 0x20A6 : 0x10020a6,
- 0x20A7 : 0x10020a7,
- 0x20A8 : 0x10020a8,
- 0x20A9 : 0x10020a9,
- 0x20AA : 0x10020aa,
- 0x20AB : 0x10020ab,
- 0x20AC : 0x20ac,
- 0x2070 : 0x1002070,
- 0x2074 : 0x1002074,
- 0x2075 : 0x1002075,
- 0x2076 : 0x1002076,
- 0x2077 : 0x1002077,
- 0x2078 : 0x1002078,
- 0x2079 : 0x1002079,
- 0x2080 : 0x1002080,
- 0x2081 : 0x1002081,
- 0x2082 : 0x1002082,
- 0x2083 : 0x1002083,
- 0x2084 : 0x1002084,
- 0x2085 : 0x1002085,
- 0x2086 : 0x1002086,
- 0x2087 : 0x1002087,
- 0x2088 : 0x1002088,
- 0x2089 : 0x1002089,
- 0x2202 : 0x1002202,
- 0x2205 : 0x1002205,
- 0x2208 : 0x1002208,
- 0x2209 : 0x1002209,
- 0x220B : 0x100220B,
- 0x221A : 0x100221A,
- 0x221B : 0x100221B,
- 0x221C : 0x100221C,
- 0x222C : 0x100222C,
- 0x222D : 0x100222D,
- 0x2235 : 0x1002235,
- 0x2245 : 0x1002248,
- 0x2247 : 0x1002247,
- 0x2262 : 0x1002262,
- 0x2263 : 0x1002263,
- 0x2800 : 0x1002800,
- 0x2801 : 0x1002801,
- 0x2802 : 0x1002802,
- 0x2803 : 0x1002803,
- 0x2804 : 0x1002804,
- 0x2805 : 0x1002805,
- 0x2806 : 0x1002806,
- 0x2807 : 0x1002807,
- 0x2808 : 0x1002808,
- 0x2809 : 0x1002809,
- 0x280a : 0x100280a,
- 0x280b : 0x100280b,
- 0x280c : 0x100280c,
- 0x280d : 0x100280d,
- 0x280e : 0x100280e,
- 0x280f : 0x100280f,
- 0x2810 : 0x1002810,
- 0x2811 : 0x1002811,
- 0x2812 : 0x1002812,
- 0x2813 : 0x1002813,
- 0x2814 : 0x1002814,
- 0x2815 : 0x1002815,
- 0x2816 : 0x1002816,
- 0x2817 : 0x1002817,
- 0x2818 : 0x1002818,
- 0x2819 : 0x1002819,
- 0x281a : 0x100281a,
- 0x281b : 0x100281b,
- 0x281c : 0x100281c,
- 0x281d : 0x100281d,
- 0x281e : 0x100281e,
- 0x281f : 0x100281f,
- 0x2820 : 0x1002820,
- 0x2821 : 0x1002821,
- 0x2822 : 0x1002822,
- 0x2823 : 0x1002823,
- 0x2824 : 0x1002824,
- 0x2825 : 0x1002825,
- 0x2826 : 0x1002826,
- 0x2827 : 0x1002827,
- 0x2828 : 0x1002828,
- 0x2829 : 0x1002829,
- 0x282a : 0x100282a,
- 0x282b : 0x100282b,
- 0x282c : 0x100282c,
- 0x282d : 0x100282d,
- 0x282e : 0x100282e,
- 0x282f : 0x100282f,
- 0x2830 : 0x1002830,
- 0x2831 : 0x1002831,
- 0x2832 : 0x1002832,
- 0x2833 : 0x1002833,
- 0x2834 : 0x1002834,
- 0x2835 : 0x1002835,
- 0x2836 : 0x1002836,
- 0x2837 : 0x1002837,
- 0x2838 : 0x1002838,
- 0x2839 : 0x1002839,
- 0x283a : 0x100283a,
- 0x283b : 0x100283b,
- 0x283c : 0x100283c,
- 0x283d : 0x100283d,
- 0x283e : 0x100283e,
- 0x283f : 0x100283f,
- 0x2840 : 0x1002840,
- 0x2841 : 0x1002841,
- 0x2842 : 0x1002842,
- 0x2843 : 0x1002843,
- 0x2844 : 0x1002844,
- 0x2845 : 0x1002845,
- 0x2846 : 0x1002846,
- 0x2847 : 0x1002847,
- 0x2848 : 0x1002848,
- 0x2849 : 0x1002849,
- 0x284a : 0x100284a,
- 0x284b : 0x100284b,
- 0x284c : 0x100284c,
- 0x284d : 0x100284d,
- 0x284e : 0x100284e,
- 0x284f : 0x100284f,
- 0x2850 : 0x1002850,
- 0x2851 : 0x1002851,
- 0x2852 : 0x1002852,
- 0x2853 : 0x1002853,
- 0x2854 : 0x1002854,
- 0x2855 : 0x1002855,
- 0x2856 : 0x1002856,
- 0x2857 : 0x1002857,
- 0x2858 : 0x1002858,
- 0x2859 : 0x1002859,
- 0x285a : 0x100285a,
- 0x285b : 0x100285b,
- 0x285c : 0x100285c,
- 0x285d : 0x100285d,
- 0x285e : 0x100285e,
- 0x285f : 0x100285f,
- 0x2860 : 0x1002860,
- 0x2861 : 0x1002861,
- 0x2862 : 0x1002862,
- 0x2863 : 0x1002863,
- 0x2864 : 0x1002864,
- 0x2865 : 0x1002865,
- 0x2866 : 0x1002866,
- 0x2867 : 0x1002867,
- 0x2868 : 0x1002868,
- 0x2869 : 0x1002869,
- 0x286a : 0x100286a,
- 0x286b : 0x100286b,
- 0x286c : 0x100286c,
- 0x286d : 0x100286d,
- 0x286e : 0x100286e,
- 0x286f : 0x100286f,
- 0x2870 : 0x1002870,
- 0x2871 : 0x1002871,
- 0x2872 : 0x1002872,
- 0x2873 : 0x1002873,
- 0x2874 : 0x1002874,
- 0x2875 : 0x1002875,
- 0x2876 : 0x1002876,
- 0x2877 : 0x1002877,
- 0x2878 : 0x1002878,
- 0x2879 : 0x1002879,
- 0x287a : 0x100287a,
- 0x287b : 0x100287b,
- 0x287c : 0x100287c,
- 0x287d : 0x100287d,
- 0x287e : 0x100287e,
- 0x287f : 0x100287f,
- 0x2880 : 0x1002880,
- 0x2881 : 0x1002881,
- 0x2882 : 0x1002882,
- 0x2883 : 0x1002883,
- 0x2884 : 0x1002884,
- 0x2885 : 0x1002885,
- 0x2886 : 0x1002886,
- 0x2887 : 0x1002887,
- 0x2888 : 0x1002888,
- 0x2889 : 0x1002889,
- 0x288a : 0x100288a,
- 0x288b : 0x100288b,
- 0x288c : 0x100288c,
- 0x288d : 0x100288d,
- 0x288e : 0x100288e,
- 0x288f : 0x100288f,
- 0x2890 : 0x1002890,
- 0x2891 : 0x1002891,
- 0x2892 : 0x1002892,
- 0x2893 : 0x1002893,
- 0x2894 : 0x1002894,
- 0x2895 : 0x1002895,
- 0x2896 : 0x1002896,
- 0x2897 : 0x1002897,
- 0x2898 : 0x1002898,
- 0x2899 : 0x1002899,
- 0x289a : 0x100289a,
- 0x289b : 0x100289b,
- 0x289c : 0x100289c,
- 0x289d : 0x100289d,
- 0x289e : 0x100289e,
- 0x289f : 0x100289f,
- 0x28a0 : 0x10028a0,
- 0x28a1 : 0x10028a1,
- 0x28a2 : 0x10028a2,
- 0x28a3 : 0x10028a3,
- 0x28a4 : 0x10028a4,
- 0x28a5 : 0x10028a5,
- 0x28a6 : 0x10028a6,
- 0x28a7 : 0x10028a7,
- 0x28a8 : 0x10028a8,
- 0x28a9 : 0x10028a9,
- 0x28aa : 0x10028aa,
- 0x28ab : 0x10028ab,
- 0x28ac : 0x10028ac,
- 0x28ad : 0x10028ad,
- 0x28ae : 0x10028ae,
- 0x28af : 0x10028af,
- 0x28b0 : 0x10028b0,
- 0x28b1 : 0x10028b1,
- 0x28b2 : 0x10028b2,
- 0x28b3 : 0x10028b3,
- 0x28b4 : 0x10028b4,
- 0x28b5 : 0x10028b5,
- 0x28b6 : 0x10028b6,
- 0x28b7 : 0x10028b7,
- 0x28b8 : 0x10028b8,
- 0x28b9 : 0x10028b9,
- 0x28ba : 0x10028ba,
- 0x28bb : 0x10028bb,
- 0x28bc : 0x10028bc,
- 0x28bd : 0x10028bd,
- 0x28be : 0x10028be,
- 0x28bf : 0x10028bf,
- 0x28c0 : 0x10028c0,
- 0x28c1 : 0x10028c1,
- 0x28c2 : 0x10028c2,
- 0x28c3 : 0x10028c3,
- 0x28c4 : 0x10028c4,
- 0x28c5 : 0x10028c5,
- 0x28c6 : 0x10028c6,
- 0x28c7 : 0x10028c7,
- 0x28c8 : 0x10028c8,
- 0x28c9 : 0x10028c9,
- 0x28ca : 0x10028ca,
- 0x28cb : 0x10028cb,
- 0x28cc : 0x10028cc,
- 0x28cd : 0x10028cd,
- 0x28ce : 0x10028ce,
- 0x28cf : 0x10028cf,
- 0x28d0 : 0x10028d0,
- 0x28d1 : 0x10028d1,
- 0x28d2 : 0x10028d2,
- 0x28d3 : 0x10028d3,
- 0x28d4 : 0x10028d4,
- 0x28d5 : 0x10028d5,
- 0x28d6 : 0x10028d6,
- 0x28d7 : 0x10028d7,
- 0x28d8 : 0x10028d8,
- 0x28d9 : 0x10028d9,
- 0x28da : 0x10028da,
- 0x28db : 0x10028db,
- 0x28dc : 0x10028dc,
- 0x28dd : 0x10028dd,
- 0x28de : 0x10028de,
- 0x28df : 0x10028df,
- 0x28e0 : 0x10028e0,
- 0x28e1 : 0x10028e1,
- 0x28e2 : 0x10028e2,
- 0x28e3 : 0x10028e3,
- 0x28e4 : 0x10028e4,
- 0x28e5 : 0x10028e5,
- 0x28e6 : 0x10028e6,
- 0x28e7 : 0x10028e7,
- 0x28e8 : 0x10028e8,
- 0x28e9 : 0x10028e9,
- 0x28ea : 0x10028ea,
- 0x28eb : 0x10028eb,
- 0x28ec : 0x10028ec,
- 0x28ed : 0x10028ed,
- 0x28ee : 0x10028ee,
- 0x28ef : 0x10028ef,
- 0x28f0 : 0x10028f0,
- 0x28f1 : 0x10028f1,
- 0x28f2 : 0x10028f2,
- 0x28f3 : 0x10028f3,
- 0x28f4 : 0x10028f4,
- 0x28f5 : 0x10028f5,
- 0x28f6 : 0x10028f6,
- 0x28f7 : 0x10028f7,
- 0x28f8 : 0x10028f8,
- 0x28f9 : 0x10028f9,
- 0x28fa : 0x10028fa,
- 0x28fb : 0x10028fb,
- 0x28fc : 0x10028fc,
- 0x28fd : 0x10028fd,
- 0x28fe : 0x10028fe,
- 0x28ff : 0x10028ff
-};
+ ['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/webclients/novnc/include/jsunzip.js b/webclients/novnc/include/jsunzip.js
index f815218..8968f86 100755
--- a/webclients/novnc/include/jsunzip.js
+++ b/webclients/novnc/include/jsunzip.js
@@ -352,20 +352,28 @@ this.getbit = function(d)
}
/* read a num bit value from a stream and add base */
+function read_bits_direct(source, bitcount, tag, idx, num)
+{
+ var val = 0;
+ while (bitcount < 24) {
+ tag = tag | (source[idx++] & 0xff) << bitcount;
+ bitcount += 8;
+ }
+ val = tag & (0xffff >> (16 - num));
+ tag >>= num;
+ bitcount -= num;
+ return [bitcount, tag, idx, val];
+}
this.read_bits = function(d, num, base)
{
if (!num)
return base;
- var val = 0;
- while (d.bitcount < 24) {
- d.tag = d.tag | (d.source[d.sourceIndex++] & 0xff) << d.bitcount;
- d.bitcount += 8;
- }
- val = d.tag & (0xffff >> (16 - num));
- d.tag >>= num;
- d.bitcount -= num;
- return val + base;
+ var ret = read_bits_direct(d.source, d.bitcount, d.tag, d.sourceIndex, num);
+ d.bitcount = ret[0];
+ d.tag = ret[1];
+ d.sourceIndex = ret[2];
+ return ret[3] + base;
}
/* given a data stream and a tree, decode a symbol */
diff --git a/webclients/novnc/include/keyboard.js b/webclients/novnc/include/keyboard.js
new file mode 100644
index 0000000..6044321
--- /dev/null
+++ b/webclients/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) !== 0xffe1) {
+ 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) {
+ var ctrl = 0xffe3;
+ var alt = 0xffe9;
+ var altGr = 0xfe03;
+ var shift = 0xffe1;
+ var meta = 0xffe7;
+
+ if (!charModifier) {
+ if (isMac()) {
+ // on Mac, Option (AKA Alt) is used as a char modifier
+ charModifier = [alt];
+ }
+ else if (isWindows()) {
+ // on Windows, Ctrl+Alt is used as a char modifier
+ charModifier = [alt, ctrl];
+ }
+ else if (isLinux()) {
+ // on Linux, AltGr is used as a char modifier
+ charModifier = [altGr];
+ }
+ else {
+ charModifier = [];
+ }
+ }
+
+ var state = {};
+ state[ctrl] = false;
+ state[alt] = false;
+ state[altGr] = false;
+ state[shift] = false;
+ state[meta] = 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[ctrl] && keysym !== ctrl) {
+ state[ctrl] = evt.ctrlKey;
+ result.push(syncKey(ctrl));
+ }
+ if (evt.altKey !== undefined && evt.altKey !== state[alt] && keysym !== alt) {
+ state[alt] = evt.altKey;
+ result.push(syncKey(alt));
+ }
+ if (evt.altGraphKey !== undefined && evt.altGraphKey !== state[altGr] && keysym !== altGr) {
+ state[altGr] = evt.altGraphKey;
+ result.push(syncKey(altGr));
+ }
+ if (evt.shiftKey !== undefined && evt.shiftKey !== state[shift] && keysym !== shift) {
+ state[shift] = evt.shiftKey;
+ result.push(syncKey(shift));
+ }
+ if (evt.metaKey !== undefined && evt.metaKey !== state[meta] && keysym !== meta) {
+ state[meta] = evt.metaKey;
+ result.push(syncKey(meta));
+ }
+ 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 0xffb0 + (keycode - 0x60); // numpad 0-9
+ }
+
+ switch(keycode) {
+ case 0x20: return 0x20; // space
+ case 0x6a: return 0xffaa; // multiply
+ case 0x6b: return 0xffab; // add
+ case 0x6c: return 0xffac; // separator
+ case 0x6d: return 0xffad; // subtract
+ case 0x6e: return 0xffae; // decimal
+ case 0x6f: return 0xffaf; // divide
+ case 0xbb: return 0x2b; // +
+ case 0xbc: return 0x2c; // ,
+ case 0xbd: return 0x2d; // -
+ case 0xbe: return 0x2e; // .
+ }
+
+ 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 0xffbe + keycode - 0x70; // F1-F24
+ }
+ switch (keycode) {
+
+ case 8 : return 0xFF08; // BACKSPACE
+ case 13 : return 0xFF0D; // ENTER
+
+ case 9 : return 0xFF09; // TAB
+
+ case 27 : return 0xFF1B; // ESCAPE
+ case 46 : return 0xFFFF; // DELETE
+
+ case 36 : return 0xFF50; // HOME
+ case 35 : return 0xFF57; // END
+ case 33 : return 0xFF55; // PAGE_UP
+ case 34 : return 0xFF56; // PAGE_DOWN
+ case 45 : return 0xFF63; // INSERT
+
+ case 37 : return 0xFF51; // LEFT
+ case 38 : return 0xFF52; // UP
+ case 39 : return 0xFF53; // RIGHT
+ case 40 : return 0xFF54; // DOWN
+ case 16 : return 0xFFE1; // SHIFT
+ case 17 : return 0xFFE3; // CONTROL
+ case 18 : return 0xFFE9; // Left ALT (Mac Option)
+
+ case 224 : return 0xFE07; // Meta
+ case 225 : return 0xFE03; // AltGr
+ case 91 : return 0xFFEC; // Super_L (Win Key)
+ case 92 : return 0xFFED; // Super_R (Win Key)
+ case 93 : return 0xFF67; // Menu (Win Menu), Mac Command
+ 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/webclients/novnc/include/keysym.js b/webclients/novnc/include/keysym.js
new file mode 100644
index 0000000..a00d595
--- /dev/null
+++ b/webclients/novnc/include/keysym.js
@@ -0,0 +1,376 @@
+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 */
+
+/*
+ * 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/webclients/novnc/include/keysymdef.js b/webclients/novnc/include/keysymdef.js
new file mode 100644
index 0000000..f94445c
--- /dev/null
+++ b/webclients/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/webclients/novnc/include/playback.js b/webclients/novnc/include/playback.js
index a21c7b6..7756529 100644
--- a/webclients/novnc/include/playback.js
+++ b/webclients/novnc/include/playback.js
@@ -1,7 +1,7 @@
/*
* noVNC: HTML5 VNC client
* Copyright (C) 2012 Joel Martin
- * Licensed under LGPL-3 (see LICENSE.LGPL-3)
+ * Licensed under MPL 2.0 (see LICENSE.txt)
*/
"use strict";
@@ -79,10 +79,22 @@ queue_next_packet = function () {
}
};
+var bytes_processed = 0;
+
do_packet = function () {
//Util.Debug("Processing frame: " + frame_idx);
- var frame = VNC_frame_data[frame_idx];
- rfb.recv_message({'data' : frame.slice(frame.indexOf('{', 1) + 1)});
+ 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.recv_message({'data' : u8});
+ } else {
+ rfb.recv_message({'data' : frame.slice(start)});
+ }
frame_idx += 1;
queue_next_packet();
diff --git a/webclients/novnc/include/rfb.js b/webclients/novnc/include/rfb.js
index 00fb7d8..0afe656 100644
--- a/webclients/novnc/include/rfb.js
+++ b/webclients/novnc/include/rfb.js
@@ -1,7 +1,8 @@
/*
* noVNC: HTML5 VNC client
* Copyright (C) 2012 Joel Martin
- * Licensed under LGPL-3 (see LICENSE.txt)
+ * Copyright (C) 2013 Samuel Mannehed for Cendio AB
+ * Licensed under MPL 2.0 (see LICENSE.txt)
*
* See README.md for usage and integration instructions.
*
@@ -9,1841 +10,1874 @@
* (c) 2012 Michael Tinglof, Joe Balaz, Les Piech (Mercuri.ca)
*/
-/*jslint white: false, browser: true, bitwise: false, plusplus: false */
+/*jslint white: false, browser: true */
/*global window, Util, Display, Keyboard, Mouse, Websock, Websock_native, Base64, DES */
+var RFB;
-function RFB(defaults) {
-"use strict";
-
-var that = {}, // Public API methods
- conf = {}, // Configuration attributes
-
- // Pre-declare private functions used before definitions (jslint)
- init_vars, updateState, fail, handle_message,
- init_msg, normal_msg, framebufferUpdate, print_stats,
-
- pixelFormat, clientEncodings, fbUpdateRequest, fbUpdateRequests,
- keyEvent, pointerEvent, clientCutText,
-
- getTightCLength, extract_data_uri,
- keyPress, mouseButton, mouseMove,
-
- checkEvents, // Overridable for testing
-
-
- //
- // Private RFB namespace variables
- //
- rfb_host = '',
- rfb_port = 5900,
- rfb_password = '',
- rfb_path = '',
-
- rfb_state = 'disconnected',
- rfb_version = 0,
- rfb_max_version= 3.8,
- rfb_auth_scheme= '',
-
-
- // In preference order
- 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 ]
- ],
-
- encHandlers = {},
- encNames = {},
- encStats = {}, // [rectCnt, rectCntTot]
-
- ws = null, // Websock object
- display = null, // Display object
- keyboard = null, // Keyboard input handler object
- mouse = null, // Mouse input handler object
- sendTimer = null, // Send Queue check timer
- connTimer = null, // connection timer
- disconnTimer = null, // disconnection timer
- msgTimer = null, // queued handle_message timer
-
- // Frame buffer update state
- 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,
- zlibs : [] // TIGHT zlib streams
- },
-
- fb_Bpp = 4,
- fb_depth = 3,
- fb_width = 0,
- fb_height = 0,
- fb_name = "",
-
- last_req_time = 0,
- rre_chunk_sz = 100,
-
- 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
- },
-
- test_mode = false,
-
- def_con_timeout = Websock_native ? 2 : 5,
-
- /* Mouse state */
- mouse_buttonMask = 0,
- mouse_arr = [],
- viewportDragging = false,
- viewportDragPos = {};
-
-// Configuration attributes
-Util.conf_defaults(conf, that, defaults, [
- ['target', 'wo', 'dom', null, 'VNC display rendering Canvas object'],
- ['focusContainer', 'wo', 'dom', document, 'DOM element that captures keyboard input'],
-
- ['encrypt', 'rw', 'bool', false, 'Use TLS/SSL/wss encryption'],
- ['true_color', 'rw', 'bool', true, 'Request true color pixel data'],
- ['local_cursor', 'rw', 'bool', false, 'Request locally rendered cursor'],
- ['shared', 'rw', 'bool', true, 'Request shared mode'],
- ['view_only', 'rw', 'bool', false, 'Disable client mouse/keyboard'],
-
- ['connectTimeout', 'rw', 'int', def_con_timeout, 'Time (s) to wait for connection'],
- ['disconnectTimeout', 'rw', 'int', 3, 'Time (s) to wait for disconnection'],
-
- // UltraVNC repeater ID to connect to
- ['repeaterID', 'rw', 'str', '', 'RepeaterID to connect to'],
-
- ['viewportDrag', 'rw', 'bool', false, 'Move the viewport on mouse drags'],
-
- ['check_rate', 'rw', 'int', 217, 'Timing (ms) of send/receive check'],
- ['fbu_req_rate', 'rw', 'int', 1413, 'Timing (ms) of frameBufferUpdate requests'],
-
- // Callback functions
- ['onUpdateState', 'rw', 'func', function() { },
- 'onUpdateState(rfb, state, oldstate, statusMsg): RFB state update/change '],
- ['onPasswordRequired', 'rw', 'func', function() { },
- 'onPasswordRequired(rfb): VNC password is required '],
- ['onClipboard', 'rw', 'func', function() { },
- 'onClipboard(rfb, text): RFB clipboard contents received'],
- ['onBell', 'rw', 'func', function() { },
- 'onBell(rfb): RFB Bell message received '],
- ['onFBUReceive', 'rw', 'func', function() { },
- 'onFBUReceive(rfb, fbu): RFB FBU received but not yet processed '],
- ['onFBUComplete', 'rw', 'func', function() { },
- 'onFBUComplete(rfb, fbu): RFB FBU received and processed '],
-
- // These callback names are deprecated
- ['updateState', 'rw', 'func', function() { },
- 'obsolete, use onUpdateState'],
- ['clipboardReceive', 'rw', 'func', function() { },
- 'obsolete, use onClipboard']
- ]);
+(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 ]
+ ];
+
+ 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
+ };
-// Override/add some specific configuration getters/setters
-that.set_local_cursor = function(cursor) {
- if ((!cursor) || (cursor in {'0':1, 'no':1, 'false':1})) {
- conf.local_cursor = false;
- } else {
- if (display.get_cursor_uri()) {
- conf.local_cursor = true;
- } else {
- Util.Warn("Browser does not support local cursor");
- }
- }
-};
-
-// These are fake configuration getters
-that.get_display = function() { return display; };
-
-that.get_keyboard = function() { return keyboard; };
-
-that.get_mouse = function() { return mouse; };
-
-
-
-//
-// Setup routines
-//
-
-// Create the public API interface and initialize values that stay
-// constant across connect/disconnect
-function constructor() {
- var i, rmode;
- Util.Debug(">> RFB.constructor");
-
- // Create lookup tables based encoding number
- for (i=0; i < encodings.length; i+=1) {
- encHandlers[encodings[i][1]] = encHandlers[encodings[i][0]];
- encNames[encodings[i][1]] = encodings[i][0];
- encStats[encodings[i][1]] = [0, 0];
- }
- // Initialize display, mouse, keyboard, and websock
- try {
- display = new Display({'target': conf.target});
- } catch (exc) {
- Util.Error("Display exception: " + exc);
- updateState('fatal', "No working Display");
- }
- keyboard = new Keyboard({'target': conf.focusContainer,
- 'onKeyPress': keyPress});
- mouse = new Mouse({'target': conf.target,
- 'onMouseButton': mouseButton,
- 'onMouseMove': mouseMove});
-
- rmode = display.get_render_mode();
-
- ws = new Websock();
- ws.on('message', handle_message);
- ws.on('open', function() {
- if (rfb_state === "connect") {
- updateState('ProtocolVersion', "Starting VNC handshake");
- } else {
- fail("Got unexpected WebSockets connection");
- }
- });
- ws.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 (rfb_state === 'disconnect') {
- updateState('disconnected', 'VNC disconnected' + msg);
- } else if (rfb_state === 'ProtocolVersion') {
- fail('Failed to connect to server' + msg);
- } else if (rfb_state in {'failed':1, 'disconnected':1}) {
- Util.Error("Received onclose while disconnected" + msg);
- } else {
- fail('Server disconnected' + msg);
+ this._fb_Bpp = 4;
+ this._fb_depth = 3;
+ this._fb_width = 0;
+ this._fb_height = 0;
+ this._fb_name = "";
+
+ 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
+ };
+
+ // Mouse state
+ this._mouse_buttonMask = 0;
+ this._mouse_arr = [];
+ this._viewportDragging = false;
+ this._viewportDragPos = {};
+
+ // 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', 'base64'], // 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];
}
- });
- ws.on('error', function(e) {
- Util.Warn("WebSocket on-error event");
- //fail("WebSock reported an error");
- });
-
-
- init_vars();
-
- /* Check web-socket-js if no builtin WebSocket support */
- if (Websock_native) {
- Util.Info("Using native WebSockets");
- updateState('loaded', 'noVNC ready: native WebSockets, ' + rmode);
- } else {
- Util.Warn("Using web-socket-js bridge. Flash version: " +
- Util.Flash.version);
- if ((! Util.Flash) ||
- (Util.Flash.version < 9)) {
- updateState('fatal', "WebSockets or <a href='http://get.adobe.com/flashplayer'>Adobe Flash<\/a> is required");
- } else if (document.location.href.substr(0, 7) === "file://") {
- updateState('fatal',
- "'file://' URL is incompatible with Adobe Flash");
- } else {
- updateState('loaded', 'noVNC ready: WebSockets emulation, ' + rmode);
+
+ try {
+ this._display = new Display({target: this._target});
+ } catch (exc) {
+ Util.Error("Display exception: " + exc);
+ this._updateState('fatal', "No working Display");
}
- }
-
- Util.Debug("<< RFB.constructor");
- return that; // Return the public API interface
-}
-
-function connect() {
- Util.Debug(">> RFB.connect");
- var uri;
-
- if (typeof UsingSocketIO !== "undefined") {
- uri = "http://" + rfb_host + ":" + rfb_port + "/" + rfb_path;
- } else {
- if (conf.encrypt) {
- uri = "wss://";
+
+ 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);
+ }
+ }.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 {
- uri = "ws://";
- }
- uri += rfb_host + ":" + rfb_port + "/" + rfb_path;
- }
- Util.Info("connecting to " + uri);
- ws.open(uri);
-
- Util.Debug("<< RFB.connect");
-}
-
-// Initialize variables that are reset before each connection
-init_vars = function() {
- var i;
-
- /* Reset state */
- ws.init();
-
- FBU.rects = 0;
- FBU.subrects = 0; // RRE and HEXTILE
- FBU.lines = 0; // RAW
- FBU.tiles = 0; // HEXTILE
- FBU.zlibs = []; // TIGHT zlib encoders
- mouse_buttonMask = 0;
- mouse_arr = [];
-
- // Clear the per connection encoding stats
- for (i=0; i < encodings.length; i+=1) {
- encStats[encodings[i][1]][0] = 0;
- }
-
- for (i=0; i < 4; i++) {
- //FBU.zlibs[i] = new InflateStream();
- FBU.zlibs[i] = new TINF();
- FBU.zlibs[i].init();
- }
-};
-
-// Print statistics
-print_stats = function() {
- var i, s;
- Util.Info("Encoding stats for this connection:");
- for (i=0; i < encodings.length; i+=1) {
- s = encStats[encodings[i][1]];
- if ((s[0] + s[1]) > 0) {
- Util.Info(" " + encodings[i][0] + ": " +
- s[0] + " rects");
- }
- }
- Util.Info("Encoding stats since page load:");
- for (i=0; i < encodings.length; i+=1) {
- s = encStats[encodings[i][1]];
- if ((s[0] + s[1]) > 0) {
- Util.Info(" " + encodings[i][0] + ": " +
- s[1] + " rects");
+ Util.Warn("Using web-socket-js bridge. Flash version: " + Util.Flash.version);
+ if (!Util.Flash || Util.Flash.version < 9) {
+ this._updateState('fatal', "WebSockets or <a href='http://get.adobe.com/flashplayer'>Adobe Flash</a> is required");
+ } else if (document.location.href.substr(0, 7) === 'file://') {
+ this._updateState('fatal', "'file://' URL is incompatible with Adobe Flash");
+ } else {
+ this._updateState('loaded', 'noVNC ready: WebSockets emulation, ' + rmode);
+ }
}
- }
-};
-//
-// Utility routines
-//
+ 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 : "";
-/*
- * 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 func, cmsg, oldstate = rfb_state;
-
- if (state === oldstate) {
- /* Already here, ignore */
- Util.Debug("Already in state '" + state + "', ignoring.");
- return;
- }
-
- /*
- * 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}) {
- if (sendTimer) {
- clearInterval(sendTimer);
- sendTimer = null;
- }
+ if (!this._rfb_host || !this._rfb_port) {
+ return this._fail("Must set host and port");
+ }
- if (msgTimer) {
- clearInterval(msgTimer);
- msgTimer = null;
- }
+ this._updateState('connect');
+ },
+
+ disconnect: function () {
+ this._updateState('disconnect', 'Disconnecting');
+ },
+
+ 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");
+
+ var arr = [];
+ arr = arr.concat(RFB.messages.keyEvent(0xFFE3, 1)); // Control
+ arr = arr.concat(RFB.messages.keyEvent(0xFFE9, 1)); // Alt
+ arr = arr.concat(RFB.messages.keyEvent(0xFFFF, 1)); // Delete
+ arr = arr.concat(RFB.messages.keyEvent(0xFFFF, 0)); // Delete
+ arr = arr.concat(RFB.messages.keyEvent(0xFFE9, 0)); // Alt
+ arr = arr.concat(RFB.messages.keyEvent(0xFFE3, 0)); // Control
+ this._sock.send(arr);
+ },
+
+ 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; }
+ var arr = [];
+ if (typeof down !== 'undefined') {
+ Util.Info("Sending key code (" + (down ? "down" : "up") + "): " + code);
+ arr = arr.concat(RFB.messages.keyEvent(code, down ? 1 : 0));
+ } else {
+ Util.Info("Sending key code (down + up): " + code);
+ arr = arr.concat(RFB.messages.keyEvent(code, 1));
+ arr = arr.concat(RFB.messages.keyEvent(code, 0));
+ }
+ this._sock.send(arr);
+ },
+
+ clipboardPasteFrom: function (text) {
+ if (this._rfb_state !== 'normal') { return; }
+ this._sock.send(RFB.messages.clientCutText(text));
+ },
- if (display && display.get_context()) {
- keyboard.ungrab();
- mouse.ungrab();
- display.defaultCursor();
- if ((Util.get_logging() !== 'debug') ||
- (state === 'loaded')) {
- // Show noVNC logo on load and when disconnected if
- // debug is off
- display.clear();
+ // Private methods
+
+ _connect: function () {
+ Util.Debug(">> RFB.connect");
+
+ var uri;
+ if (typeof UsingSocketIO !== 'undefined') {
+ uri = 'http';
+ } else {
+ uri = this._encrypt ? 'wss' : 'ws';
}
- }
- ws.close();
- }
-
- if (oldstate === 'fatal') {
- Util.Error("Fatal error, cannot continue");
- }
-
- if ((state === 'failed') || (state === 'fatal')) {
- func = Util.Error;
- } else {
- func = Util.Warn;
- }
-
- cmsg = typeof(statusMsg) !== 'undefined' ? (" Msg: " + statusMsg) : "";
- func("New state '" + state + "', was '" + oldstate + "'." + cmsg);
-
- if ((oldstate === 'failed') && (state === 'disconnected')) {
- // Do disconnect action, but stay in failed state
- rfb_state = 'failed';
- } else {
- rfb_state = state;
- }
-
- if (connTimer && (rfb_state !== 'connect')) {
- Util.Debug("Clearing connect timer");
- clearInterval(connTimer);
- connTimer = null;
- }
-
- if (disconnTimer && (rfb_state !== 'disconnect')) {
- Util.Debug("Clearing disconnect timer");
- clearInterval(disconnTimer);
- disconnTimer = null;
- }
-
- switch (state) {
- case 'normal':
- if ((oldstate === 'disconnected') || (oldstate === 'failed')) {
- Util.Error("Invalid transition from 'disconnected' or 'failed' to 'normal'");
- }
+ uri += '://' + this._rfb_host + ':' + this._rfb_port + '/' + this._rfb_path;
+ Util.Info("connecting to " + uri);
- break;
+ this._sock.open(uri, this._wsProtocols);
+ Util.Debug("<< RFB.connect");
+ },
- case 'connect':
-
- connTimer = setTimeout(function () {
- fail("Connect timeout");
- }, conf.connectTimeout * 1000);
+ _init_vars: function () {
+ // reset state
+ this._sock.init();
- init_vars();
- connect();
+ 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;
- // WebSocket.onopen transitions to 'ProtocolVersion'
- break;
+ // 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 TINF();
+ this._FBU.zlibs[i].init();
+ }
+ },
+
+ _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");
+ }
+ },
- case 'disconnect':
- if (! test_mode) {
- disconnTimer = setTimeout(function () {
- fail("Disconnect timeout");
- }, conf.disconnectTimeout * 1000);
- }
+ /*
+ * 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");
+ }
- print_stats();
+ /*
+ * 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}) {
- // WebSocket.onclose transitions to 'disconnected'
- break;
+ if (this._sendTimer) {
+ clearInterval(this._sendTimer);
+ this._sendTimer = null;
+ }
+ if (this._msgTimer) {
+ clearInterval(this._msgTimer);
+ this._msgTimer = null;
+ }
- case 'failed':
- if (oldstate === 'disconnected') {
- Util.Error("Invalid transition from 'disconnected' to 'failed'");
- }
- if (oldstate === 'normal') {
- Util.Error("Error while connected.");
- }
- if (oldstate === 'init') {
- Util.Error("Error while initializing.");
- }
+ if (this._display && this._display.get_context()) {
+ this._keyboard.ungrab();
+ this._mouse.ungrab();
+ 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();
+ }
+ }
- // Make sure we transition to disconnected
- setTimeout(function() { updateState('disconnected'); }, 50);
-
- break;
-
-
- default:
- // No state change action to take
-
- }
-
- if ((oldstate === 'failed') && (state === 'disconnected')) {
- // Leave the failed message
- conf.updateState(that, state, oldstate); // Obsolete
- conf.onUpdateState(that, state, oldstate);
- } else {
- conf.updateState(that, state, oldstate, statusMsg); // Obsolete
- conf.onUpdateState(that, state, oldstate, statusMsg);
- }
-};
-
-fail = function(msg) {
- updateState('failed', msg);
- return false;
-};
-
-handle_message = function() {
- //Util.Debug(">> handle_message ws.rQlen(): " + ws.rQlen());
- //Util.Debug("ws.rQslice(0,20): " + ws.rQslice(0,20) + " (" + ws.rQlen() + ")");
- if (ws.rQlen() === 0) {
- Util.Warn("handle_message called on empty receive queue");
- return;
- }
- switch (rfb_state) {
- case 'disconnected':
- case 'failed':
- Util.Error("Got data while disconnected");
- break;
- case 'normal':
- if (normal_msg() && ws.rQlen() > 0) {
- // true means we can continue processing
- // Give other events a chance to run
- if (msgTimer === null) {
- Util.Debug("More data to process, creating timer");
- msgTimer = setTimeout(function () {
- msgTimer = null;
- handle_message();
- }, 10);
- } else {
- Util.Debug("More data to process, existing timer");
+ this._sock.close();
}
- }
- break;
- default:
- init_msg();
- break;
- }
-};
-
-
-function genDES(password, challenge) {
- var i, passwd = [];
- for (i=0; i < password.length; i += 1) {
- passwd.push(password.charCodeAt(i));
- }
- return (new DES(passwd)).encrypt(challenge);
-}
-
-function flushClient() {
- if (mouse_arr.length > 0) {
- //send(mouse_arr.concat(fbUpdateRequests()));
- ws.send(mouse_arr);
- setTimeout(function() {
- ws.send(fbUpdateRequests());
- }, 50);
-
- mouse_arr = [];
- return true;
- } else {
- return false;
- }
-}
-
-// overridable for testing
-checkEvents = function() {
- var now;
- if (rfb_state === 'normal' && !viewportDragging) {
- if (! flushClient()) {
- now = new Date().getTime();
- if (now > last_req_time + conf.fbu_req_rate) {
- last_req_time = now;
- ws.send(fbUpdateRequests());
+
+ if (oldstate === 'fatal') {
+ Util.Error('Fatal error, cannot continue');
}
- }
- }
- setTimeout(checkEvents, conf.check_rate);
-};
-
-keyPress = function(keysym, down) {
- var arr;
-
- if (conf.view_only) { return; } // View only, skip keyboard events
-
- arr = keyEvent(keysym, down);
- arr = arr.concat(fbUpdateRequests());
- ws.send(arr);
-};
-
-mouseButton = function(x, y, down, bmask) {
- if (down) {
- mouse_buttonMask |= bmask;
- } else {
- mouse_buttonMask ^= bmask;
- }
-
- if (conf.viewportDrag) {
- if (down && !viewportDragging) {
- viewportDragging = true;
- viewportDragPos = {'x': x, 'y': y};
-
- // Skip sending mouse events
- return;
- } else {
- viewportDragging = false;
- ws.send(fbUpdateRequests()); // Force immediate redraw
- }
- }
- if (conf.view_only) { return; } // View only, skip mouse events
+ 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);
+ }
- mouse_arr = mouse_arr.concat(
- pointerEvent(display.absX(x), display.absY(y)) );
- flushClient();
-};
+ if (oldstate === 'failed' && state === 'disconnected') {
+ // do disconnect action, but stay in failed state
+ this._rfb_state = 'failed';
+ } else {
+ this._rfb_state = state;
+ }
-mouseMove = function(x, y) {
- //Util.Debug('>> mouseMove ' + x + "," + y);
- var deltaX, deltaY;
+ if (this._disconnTimer && this._rfb_state !== 'disconnect') {
+ Util.Debug("Clearing disconnect timer");
+ clearTimeout(this._disconnTimer);
+ this._disconnTimer = null;
+ }
- if (viewportDragging) {
- //deltaX = x - viewportDragPos.x; // drag viewport
- deltaX = viewportDragPos.x - x; // drag frame buffer
- //deltaY = y - viewportDragPos.y; // drag viewport
- deltaY = viewportDragPos.y - y; // drag frame buffer
- viewportDragPos = {'x': x, 'y': y};
+ 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.");
+ }
- display.viewportChange(deltaX, deltaY);
+ // Make sure we transition to disconnected
+ setTimeout(function () {
+ this._updateState('disconnected');
+ }.bind(this), 50);
- // Skip sending mouse events
- return;
- }
+ break;
- if (conf.view_only) { return; } // View only, skip mouse events
+ default:
+ // No state change action to take
+ }
- mouse_arr = mouse_arr.concat(
- pointerEvent(display.absX(x), display.absY(y)) );
-};
+ 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;
+ },
-//
-// Server message handlers
-//
+ _handle_message: function () {
+ if (this._sock.rQlen() === 0) {
+ Util.Warn("handle_message called on an empty receive queue");
+ return;
+ }
-// RFB/VNC initialisation message handler
-init_msg = function() {
- //Util.Debug(">> init_msg [rfb_state '" + rfb_state + "']");
+ 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;
+ }
+ },
- var strlen, reason, length, sversion, cversion, repeaterID,
- i, types, num_types, challenge, response, bpp, depth,
- big_endian, red_max, green_max, blue_max, red_shift,
- green_shift, blue_shift, true_color, name_length, is_repeater;
+ _checkEvents: function () {
+ if (this._rfb_state === 'normal' && !this._viewportDragging && this._mouse_arr.length > 0) {
+ this._sock.send(this._mouse_arr);
+ this._mouse_arr = [];
+ }
+ },
- //Util.Debug("ws.rQ (" + ws.rQlen() + ") " + ws.rQslice(0));
- switch (rfb_state) {
+ _handleKeyPress: function (keysym, down) {
+ if (this._view_only) { return; } // View only, skip keyboard, events
+ this._sock.send(RFB.messages.keyEvent(keysym, down));
+ },
- case 'ProtocolVersion' :
- if (ws.rQlen() < 12) {
- return fail("Incomplete protocol version");
- }
- sversion = ws.rQshiftStr(12).substr(4,7);
- Util.Info("Server ProtocolVersion: " + sversion);
- is_repeater = 0;
- switch (sversion) {
- case "000.000": is_repeater = 1; break; // UltraVNC repeater
- case "003.003": rfb_version = 3.3; break;
- case "003.006": rfb_version = 3.3; break; // UltraVNC
- case "003.889": rfb_version = 3.3; break; // Apple Remote Desktop
- case "003.007": rfb_version = 3.7; break;
- case "003.008": rfb_version = 3.8; break;
- case "004.000": rfb_version = 3.8; break; // Intel AMT KVM
- case "004.001": rfb_version = 3.8; break; // RealVNC 4.6
- default:
- return fail("Invalid server version " + sversion);
- }
- if (is_repeater) {
- repeaterID = conf.repeaterID;
- while (repeaterID.length < 250) {
- repeaterID += "\0";
+ _handleMouseButton: function (x, y, down, bmask) {
+ if (down) {
+ this._mouse_buttonMask |= bmask;
+ } else {
+ this._mouse_buttonMask ^= bmask;
}
- ws.send_string(repeaterID);
- break;
- }
- if (rfb_version > rfb_max_version) {
- rfb_version = rfb_max_version;
- }
- if (! test_mode) {
- sendTimer = setInterval(function() {
- // Send updates either at a rate of one update
- // every 50ms, or whatever slower rate the network
- // can handle.
- ws.flush();
- }, 50);
- }
+ if (this._viewportDrag) {
+ if (down && !this._viewportDragging) {
+ this._viewportDragging = true;
+ this._viewportDragPos = {'x': x, 'y': y};
- cversion = "00" + parseInt(rfb_version,10) +
- ".00" + ((rfb_version * 10) % 10);
- ws.send_string("RFB " + cversion + "\n");
- updateState('Security', "Sent ProtocolVersion: " + cversion);
- break;
-
- case 'Security' :
- if (rfb_version >= 3.7) {
- // Server sends supported list, client decides
- num_types = ws.rQshift8();
- if (ws.rQwait("security type", num_types, 1)) { return false; }
- if (num_types === 0) {
- strlen = ws.rQshift32();
- reason = ws.rQshiftStr(strlen);
- return fail("Security failure: " + reason);
- }
- rfb_auth_scheme = 0;
- types = ws.rQshiftBytes(num_types);
- Util.Debug("Server security types: " + types);
- for (i=0; i < types.length; i+=1) {
- if ((types[i] > rfb_auth_scheme) && (types[i] < 3)) {
- rfb_auth_scheme = types[i];
+ // Skip sending mouse events
+ return;
+ } else {
+ this._viewportDragging = false;
}
}
- if (rfb_auth_scheme === 0) {
- return fail("Unsupported security types: " + types);
+
+ if (this._view_only) { return; } // View only, skip mouse events
+
+ this._mouse_arr = this._mouse_arr.concat(
+ RFB.messages.pointerEvent(this._display.absX(x), this._display.absY(y), this._mouse_buttonMask));
+ this._sock.send(this._mouse_arr);
+ this._mouse_arr = [];
+ },
+
+ _handleMouseMove: function (x, y) {
+ if (this._viewportDragging) {
+ var deltaX = this._viewportDragPos.x - x;
+ var deltaY = this._viewportDragPos.y - y;
+ this._viewportDragPos = {'x': x, 'y': y};
+
+ this._display.viewportChange(deltaX, deltaY);
+
+ // Skip sending mouse events
+ return;
}
-
- ws.send([rfb_auth_scheme]);
- } else {
- // Server decides
- if (ws.rQwait("security scheme", 4)) { return false; }
- rfb_auth_scheme = ws.rQshift32();
- }
- updateState('Authentication',
- "Authenticating using scheme: " + rfb_auth_scheme);
- init_msg(); // Recursive fallthrough (workaround JSLint complaint)
- break;
-
- // Triggered by fallthough, not by server message
- case 'Authentication' :
- //Util.Debug("Security auth scheme: " + rfb_auth_scheme);
- switch (rfb_auth_scheme) {
- case 0: // connection failed
- if (ws.rQwait("auth reason", 4)) { return false; }
- strlen = ws.rQshift32();
- reason = ws.rQshiftStr(strlen);
- return fail("Auth failure: " + reason);
- case 1: // no authentication
- if (rfb_version >= 3.8) {
- updateState('SecurityResult');
- return;
+
+ if (this._view_only) { return; } // View only, skip mouse events
+
+ this._mouse_arr = this._mouse_arr.concat(
+ RFB.messages.pointerEvent(this._display.absX(x), this._display.absY(y), this._mouse_buttonMask));
+
+ this._checkEvents();
+ },
+
+ // 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";
}
- // Fall through to ClientInitialisation
- break;
- case 2: // VNC authentication
- if (rfb_password.length === 0) {
- // Notify via both callbacks since it is kind of
- // a RFB state change and a UI interface issue.
- updateState('password', "Password Required");
- conf.onPasswordRequired(that);
- return;
+ 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);
}
- if (ws.rQwait("auth challenge", 16)) { return false; }
- challenge = ws.rQshiftBytes(16);
- //Util.Debug("Password: " + rfb_password);
- //Util.Debug("Challenge: " + challenge +
- // " (" + challenge.length + ")");
- response = genDES(rfb_password, challenge);
- //Util.Debug("Response: " + response +
- // " (" + response.length + ")");
-
- //Util.Debug("Sending DES encrypted auth response");
- ws.send(response);
- updateState('SecurityResult');
- return;
- default:
- fail("Unsupported auth scheme: " + rfb_auth_scheme);
- return;
- }
- updateState('ClientInitialisation', "No auth required");
- init_msg(); // Recursive fallthrough (workaround JSLint complaint)
- break;
-
- case 'SecurityResult' :
- if (ws.rQwait("VNC auth response ", 4)) { return false; }
- switch (ws.rQshift32()) {
- case 0: // OK
- // Fall through to ClientInitialisation
- break;
- case 1: // failed
- if (rfb_version >= 3.8) {
- length = ws.rQshift32();
- if (ws.rQwait("SecurityResult reason", length, 8)) {
- return false;
+
+ 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];
}
- reason = ws.rQshiftStr(length);
- fail(reason);
- } else {
- fail("Authentication failed");
}
- return;
- case 2: // too-many
- return fail("Too many auth attempts");
- }
- updateState('ClientInitialisation', "Authentication OK");
- init_msg(); // Recursive fallthrough (workaround JSLint complaint)
- break;
-
- // Triggered by fallthough, not by server message
- case 'ClientInitialisation' :
- ws.send([conf.shared ? 1 : 0]); // ClientInitialisation
- updateState('ServerInitialisation', "Authentication OK");
- break;
-
- case 'ServerInitialisation' :
- if (ws.rQwait("server initialization", 24)) { return false; }
-
- /* Screen size */
- fb_width = ws.rQshift16();
- fb_height = ws.rQshift16();
-
- /* PIXEL_FORMAT */
- bpp = ws.rQshift8();
- depth = ws.rQshift8();
- big_endian = ws.rQshift8();
- true_color = ws.rQshift8();
-
- red_max = ws.rQshift16();
- green_max = ws.rQshift16();
- blue_max = ws.rQshift16();
- red_shift = ws.rQshift8();
- green_shift = ws.rQshift8();
- blue_shift = ws.rQshift8();
- ws.rQshiftStr(3); // padding
-
- Util.Info("Screen: " + fb_width + "x" + 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");
- }
- /* Connection name/title */
- name_length = ws.rQshift32();
- fb_name = ws.rQshiftStr(name_length);
-
- if (conf.true_color && fb_name === "Intel(r) AMT KVM")
- {
- Util.Warn("Intel AMT KVM only support 8/16 bit depths. Disabling true color");
- conf.true_color = false;
- }
+ if (this._rfb_auth_scheme === 0) {
+ return this._fail("Unsupported security types: " + types);
+ }
- display.set_true_color(conf.true_color);
- display.resize(fb_width, fb_height);
- keyboard.grab();
- mouse.grab();
+ 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();
+ }
- if (conf.true_color) {
- fb_Bpp = 4;
- fb_depth = 3;
- } else {
- fb_Bpp = 1;
- fb_depth = 1;
- }
+ 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;
+ }
- response = pixelFormat();
- response = response.concat(clientEncodings());
- response = response.concat(fbUpdateRequests());
- timing.fbu_rt_start = (new Date()).getTime();
- timing.pixels = 0;
- ws.send(response);
-
- /* Start pushing/polling */
- setTimeout(checkEvents, conf.check_rate);
-
- if (conf.encrypt) {
- updateState('normal', "Connected (encrypted) to: " + fb_name);
- } else {
- updateState('normal', "Connected (unencrypted) to: " + fb_name);
- }
- break;
- }
- //Util.Debug("<< init_msg");
-};
-
-
-/* Normal RFB/VNC server message handler */
-normal_msg = function() {
- //Util.Debug(">> normal_msg");
-
- var ret = true, msg_type, length, text,
- c, first_colour, num_colours, red, green, blue;
-
- if (FBU.rects > 0) {
- msg_type = 0;
- } else {
- msg_type = ws.rQshift8();
- }
- switch (msg_type) {
- case 0: // FramebufferUpdate
- ret = framebufferUpdate(); // false means need more data
- break;
- case 1: // SetColourMapEntries
- Util.Debug("SetColourMapEntries");
- ws.rQshift8(); // Padding
- first_colour = ws.rQshift16(); // First colour
- num_colours = ws.rQshift16();
- if (ws.rQwait("SetColourMapEntries", num_colours*6, 6)) { return false; }
-
- for (c=0; c < num_colours; c+=1) {
- red = ws.rQshift16();
- //Util.Debug("red before: " + red);
- red = parseInt(red / 256, 10);
- //Util.Debug("red after: " + red);
- green = parseInt(ws.rQshift16() / 256, 10);
- blue = parseInt(ws.rQshift16() / 256, 10);
- display.set_colourMap([blue, green, red], first_colour + c);
- }
- Util.Debug("colourMap: " + display.get_colourMap());
- Util.Info("Registered " + num_colours + " colourMap entries");
- //Util.Debug("colourMap: " + display.get_colourMap());
- break;
- case 2: // Bell
- Util.Debug("Bell");
- conf.onBell(that);
- break;
- case 3: // ServerCutText
- Util.Debug("ServerCutText");
- if (ws.rQwait("ServerCutText header", 7, 1)) { return false; }
- ws.rQshiftBytes(3); // Padding
- length = ws.rQshift32();
- if (ws.rQwait("ServerCutText", length, 8)) { return false; }
-
- text = ws.rQshiftStr(length);
- conf.clipboardReceive(that, text); // Obsolete
- conf.onClipboard(that, text);
- break;
- default:
- fail("Disconnected: illegal server message type " + msg_type);
- Util.Debug("ws.rQslice(0,30):" + ws.rQslice(0,30));
- break;
- }
- //Util.Debug("<< normal_msg");
- return ret;
-};
-
-framebufferUpdate = function() {
- var now, hdr, fbu_rt_diff, ret = true;
-
- if (FBU.rects === 0) {
- //Util.Debug("New FBU: ws.rQslice(0,20): " + ws.rQslice(0,20));
- if (ws.rQwait("FBU header", 3)) {
- ws.rQunshift8(0); // FBU msg_type
- return false;
- }
- ws.rQshift8(); // padding
- FBU.rects = ws.rQshift16();
- //Util.Debug("FramebufferUpdate, rects:" + FBU.rects);
- FBU.bytes = 0;
- timing.cur_fbu = 0;
- if (timing.fbu_rt_start > 0) {
- now = (new Date()).getTime();
- Util.Info("First FBU latency: " + (now - timing.fbu_rt_start));
- }
- }
+ 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);
+ }
- while (FBU.rects > 0) {
- if (rfb_state !== "normal") {
- return false;
- }
- if (ws.rQwait("FBU", FBU.bytes)) { return false; }
- if (FBU.bytes === 0) {
- if (ws.rQwait("rect header", 12)) { return false; }
- /* New FramebufferUpdate */
-
- hdr = ws.rQshiftBytes(12);
- FBU.x = (hdr[0] << 8) + hdr[1];
- FBU.y = (hdr[2] << 8) + hdr[3];
- FBU.width = (hdr[4] << 8) + hdr[5];
- FBU.height = (hdr[6] << 8) + hdr[7];
- FBU.encoding = parseInt((hdr[8] << 24) + (hdr[9] << 16) +
- (hdr[10] << 8) + hdr[11], 10);
-
- conf.onFBUReceive(that,
- {'x': FBU.x, 'y': FBU.y,
- 'width': FBU.width, 'height': FBU.height,
- 'encoding': FBU.encoding,
- 'encodingName': encNames[FBU.encoding]});
-
- if (encNames[FBU.encoding]) {
- // Debug:
- /*
- var msg = "FramebufferUpdate rects:" + FBU.rects;
- msg += " x: " + FBU.x + " y: " + FBU.y;
- msg += " width: " + FBU.width + " height: " + FBU.height;
- msg += " encoding:" + FBU.encoding;
- msg += "(" + encNames[FBU.encoding] + ")";
- msg += ", ws.rQlen(): " + ws.rQlen();
- Util.Debug(msg);
- */
+ if (this._sock.rQwait("auth challenge", 16)) { return false; }
+
+ var challenge = 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 {
- fail("Disconnected: unsupported encoding " +
- FBU.encoding);
- return false;
+ return this._fail("Server wanted tunnels, but doesn't support the notunnel type");
}
- }
+ },
- timing.last_fbu = (new Date()).getTime();
+ _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; }
- ret = encHandlers[FBU.encoding]();
+ this._rfb_tightvnc = true;
- now = (new Date()).getTime();
- timing.cur_fbu += (now - timing.last_fbu);
+ if (numTunnels > 0) {
+ this._negotiate_tight_tunnels(numTunnels);
+ return false; // wait until we receive the sub auth to continue
+ }
+ }
- if (ret) {
- encStats[FBU.encoding][0] += 1;
- encStats[FBU.encoding][1] += 1;
- timing.pixels += FBU.width * FBU.height;
- }
+ // 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; }
- if (timing.pixels >= (fb_width * fb_height)) {
- if (((FBU.width === fb_width) &&
- (FBU.height === fb_height)) ||
- (timing.fbu_rt_start > 0)) {
- timing.full_fbu_total += timing.cur_fbu;
- timing.full_fbu_cnt += 1;
- Util.Info("Timing of full FBU, cur: " +
- timing.cur_fbu + ", total: " +
- timing.full_fbu_total + ", cnt: " +
- timing.full_fbu_cnt + ", avg: " +
- (timing.full_fbu_total /
- timing.full_fbu_cnt));
- }
- if (timing.fbu_rt_start > 0) {
- fbu_rt_diff = now - timing.fbu_rt_start;
- timing.fbu_rt_total += fbu_rt_diff;
- timing.fbu_rt_cnt += 1;
- Util.Info("full FBU round-trip, cur: " +
- fbu_rt_diff + ", total: " +
- timing.fbu_rt_total + ", cnt: " +
- timing.fbu_rt_cnt + ", avg: " +
- (timing.fbu_rt_total /
- timing.fbu_rt_cnt));
- timing.fbu_rt_start = 0;
+ 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);
}
- }
- if (! ret) {
- return ret; // false ret means need more data
- }
- }
-
- conf.onFBUComplete(that,
- {'x': FBU.x, 'y': FBU.y,
- 'width': FBU.width, 'height': FBU.height,
- 'encoding': FBU.encoding,
- 'encodingName': encNames[FBU.encoding]});
-
- return true; // We finished this FBU
-};
-
-//
-// FramebufferUpdate encodings
-//
-
-encHandlers.RAW = function display_raw() {
- //Util.Debug(">> display_raw (" + ws.rQlen() + " bytes)");
-
- var cur_y, cur_height;
-
- if (FBU.lines === 0) {
- FBU.lines = FBU.height;
- }
- FBU.bytes = FBU.width * fb_Bpp; // At least a line
- if (ws.rQwait("RAW", FBU.bytes)) { return false; }
- cur_y = FBU.y + (FBU.height - FBU.lines);
- cur_height = Math.min(FBU.lines,
- Math.floor(ws.rQlen()/(FBU.width * fb_Bpp)));
- display.blitImage(FBU.x, cur_y, FBU.width, cur_height,
- ws.get_rQ(), ws.get_rQi());
- ws.rQshiftBytes(FBU.width * cur_height * fb_Bpp);
- FBU.lines -= cur_height;
-
- if (FBU.lines > 0) {
- FBU.bytes = FBU.width * fb_Bpp; // At least another line
- } else {
- FBU.rects -= 1;
- FBU.bytes = 0;
- }
- //Util.Debug("<< display_raw (" + ws.rQlen() + " bytes)");
- return true;
-};
-
-encHandlers.COPYRECT = function display_copy_rect() {
- //Util.Debug(">> display_copy_rect");
-
- var old_x, old_y;
-
- if (ws.rQwait("COPYRECT", 4)) { return false; }
- display.renderQ_push({
- 'type': 'copy',
- 'old_x': ws.rQshift16(),
- 'old_y': ws.rQshift16(),
- 'x': FBU.x,
- 'y': FBU.y,
- 'width': FBU.width,
- 'height': FBU.height});
- FBU.rects -= 1;
- FBU.bytes = 0;
- return true;
-};
-
-encHandlers.RRE = function display_rre() {
- //Util.Debug(">> display_rre (" + ws.rQlen() + " bytes)");
- var color, x, y, width, height, chunk;
-
- if (FBU.subrects === 0) {
- if (ws.rQwait("RRE", 4+fb_Bpp)) { return false; }
- FBU.subrects = ws.rQshift32();
- color = ws.rQshiftBytes(fb_Bpp); // Background
- display.fillRect(FBU.x, FBU.y, FBU.width, FBU.height, color);
- }
- while ((FBU.subrects > 0) && (ws.rQlen() >= (fb_Bpp + 8))) {
- color = ws.rQshiftBytes(fb_Bpp);
- x = ws.rQshift16();
- y = ws.rQshift16();
- width = ws.rQshift16();
- height = ws.rQshift16();
- display.fillRect(FBU.x + x, FBU.y + y, width, height, color);
- FBU.subrects -= 1;
- }
- //Util.Debug(" display_rre: rects: " + FBU.rects +
- // ", FBU.subrects: " + FBU.subrects);
-
- if (FBU.subrects > 0) {
- chunk = Math.min(rre_chunk_sz, FBU.subrects);
- FBU.bytes = (fb_Bpp + 8) * chunk;
- } else {
- FBU.rects -= 1;
- FBU.bytes = 0;
- }
- //Util.Debug("<< display_rre, FBU.bytes: " + FBU.bytes);
- return true;
-};
-
-encHandlers.HEXTILE = function display_hextile() {
- //Util.Debug(">> display_hextile");
- var subencoding, subrects, color, cur_tile,
- tile_x, x, w, tile_y, y, h, xy, s, sx, sy, wh, sw, sh,
- rQ = ws.get_rQ(), rQi = ws.get_rQi();
-
- if (FBU.tiles === 0) {
- FBU.tiles_x = Math.ceil(FBU.width/16);
- FBU.tiles_y = Math.ceil(FBU.height/16);
- FBU.total_tiles = FBU.tiles_x * FBU.tiles_y;
- FBU.tiles = FBU.total_tiles;
- }
-
- /* FBU.bytes comes in as 1, ws.rQlen() at least 1 */
- while (FBU.tiles > 0) {
- FBU.bytes = 1;
- if (ws.rQwait("HEXTILE subencoding", FBU.bytes)) { return false; }
- subencoding = rQ[rQi]; // Peek
- if (subencoding > 30) { // Raw
- fail("Disconnected: illegal hextile subencoding " + subencoding);
- //Util.Debug("ws.rQslice(0,30):" + ws.rQslice(0,30));
- return false;
- }
- subrects = 0;
- cur_tile = FBU.total_tiles - FBU.tiles;
- tile_x = cur_tile % FBU.tiles_x;
- tile_y = Math.floor(cur_tile / FBU.tiles_x);
- x = FBU.x + tile_x * 16;
- y = FBU.y + tile_y * 16;
- w = Math.min(16, (FBU.x + FBU.width) - x);
- h = Math.min(16, (FBU.y + FBU.height) - y);
-
- /* Figure out how much we are expecting */
- if (subencoding & 0x01) { // Raw
- //Util.Debug(" Raw subencoding");
- FBU.bytes += w * h * fb_Bpp;
- } else {
- if (subencoding & 0x02) { // Background
- FBU.bytes += fb_Bpp;
- }
- if (subencoding & 0x04) { // Foreground
- FBU.bytes += fb_Bpp;
- }
- if (subencoding & 0x08) { // AnySubrects
- FBU.bytes += 1; // Since we aren't shifting it off
- if (ws.rQwait("hextile subrects header", FBU.bytes)) { return false; }
- subrects = rQ[rQi + FBU.bytes-1]; // Peek
- if (subencoding & 0x10) { // SubrectsColoured
- FBU.bytes += subrects * (fb_Bpp + 2);
- } else {
- FBU.bytes += subrects * 2;
+
+ 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);
+ }
}
}
- }
- /*
- Util.Debug(" tile:" + cur_tile + "/" + (FBU.total_tiles - 1) +
- " (" + tile_x + "," + tile_y + ")" +
- " [" + x + "," + y + "]@" + w + "x" + h +
- ", subenc:" + subencoding +
- "(last: " + FBU.lastsubencoding + "), subrects:" +
- subrects +
- ", ws.rQlen():" + ws.rQlen() + ", FBU.bytes:" + FBU.bytes +
- " last:" + ws.rQslice(FBU.bytes-10, FBU.bytes) +
- " next:" + ws.rQslice(FBU.bytes-1, FBU.bytes+10));
- */
- if (ws.rQwait("hextile", FBU.bytes)) { return false; }
-
- /* We know the encoding and have a whole tile */
- FBU.subencoding = rQ[rQi];
- rQi += 1;
- if (FBU.subencoding === 0) {
- if (FBU.lastsubencoding & 0x01) {
- /* Weird: ignore blanks after RAW */
- Util.Debug(" Ignoring blank after RAW");
- } else {
- display.fillRect(x, y, w, h, FBU.background);
+ 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);
}
- } else if (FBU.subencoding & 0x01) { // Raw
- display.blitImage(x, y, w, h, rQ, rQi);
- rQi += FBU.bytes - 1;
- } else {
- if (FBU.subencoding & 0x02) { // Background
- FBU.background = rQ.slice(rQi, rQi + fb_Bpp);
- rQi += fb_Bpp;
- }
- if (FBU.subencoding & 0x04) { // Foreground
- FBU.foreground = rQ.slice(rQi, rQi + fb_Bpp);
- rQi += fb_Bpp;
- }
-
- display.startTile(x, y, w, h, FBU.background);
- if (FBU.subencoding & 0x08) { // AnySubrects
- subrects = rQ[rQi];
- rQi += 1;
- for (s = 0; s < subrects; s += 1) {
- if (FBU.subencoding & 0x10) { // SubrectsColoured
- color = rQ.slice(rQi, rQi + fb_Bpp);
- rQi += fb_Bpp;
+ },
+
+ _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 {
- color = FBU.foreground;
+ return this._fail("Authentication failure");
}
- xy = rQ[rQi];
- rQi += 1;
- sx = (xy >> 4);
- sy = (xy & 0x0f);
+ 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();
+
+ /* 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; }
+
+ var i;
+ for (i = 0; i < numServerMessages; i++) {
+ var srvMsg = this._sock.rQshiftStr(16);
+ }
- wh = rQ[rQi];
- rQi += 1;
- sw = (wh >> 4) + 1;
- sh = (wh & 0x0f) + 1;
+ for (i = 0; i < numClientMessages; i++) {
+ var clientMsg = this._sock.rQshiftStr(16);
+ }
- display.subTile(sx, sy, sw, sh, color);
+ for (i = 0; i < numEncodings; i++) {
+ var encoding = this._sock.rQshiftStr(16);
}
}
- display.finishTile();
- }
- ws.set_rQi(rQi);
- FBU.lastsubencoding = FBU.subencoding;
- FBU.bytes = 0;
- FBU.tiles -= 1;
- }
-
- if (FBU.tiles === 0) {
- FBU.rects -= 1;
- }
-
- //Util.Debug("<< display_hextile");
- return true;
-};
-
-
-// Get 'compact length' header and data size
-getTightCLength = function (arr) {
- var header = 1, data = 0;
- data += arr[0] & 0x7f;
- if (arr[0] & 0x80) {
- header += 1;
- data += (arr[1] & 0x7f) << 7;
- if (arr[1] & 0x80) {
- header += 1;
- data += arr[2] << 14;
- }
- }
- return [header, data];
-};
-function display_tight(isTightPNG) {
- //Util.Debug(">> display_tight");
+ // 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 (fb_depth === 1) {
- fail("Tight protocol handler only implements true color mode");
- }
+ if (red_shift !== 16) {
+ Util.Warn("Server native red-shift is not 16");
+ }
- var ctl, cmode, clength, color, img, data;
- var filterId = -1, resetStreams = 0, streamId = -1;
- var rQ = ws.get_rQ(), rQi = ws.get_rQi();
+ if (blue_shift !== 0) {
+ Util.Warn("Server native blue-shift is not 0");
+ }
- FBU.bytes = 1; // compression-control byte
- if (ws.rQwait("TIGHT compression-control", FBU.bytes)) { return false; }
+ // we're past the point where we could backtrack, so it's safe to call this
+ this._onDesktopName(this, this._fb_name);
- var checksum = function(data) {
- var sum=0, i;
- for (i=0; i<data.length;i++) {
- sum += data[i];
- if (sum > 65536) sum -= 65536;
- }
- return sum;
- }
+ 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;
+ }
- var decompress = function(data) {
- for (var i=0; i<4; i++) {
- if ((resetStreams >> i) & 1) {
- FBU.zlibs[i].reset();
- Util.Info("Reset zlib stream " + i);
+ this._display.set_true_color(this._true_color);
+ this._onFBResize(this, this._fb_width, this._fb_height);
+ this._display.resize(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;
}
- }
- var uncompressed = FBU.zlibs[streamId].uncompress(data, 0);
- if (uncompressed.status !== 0) {
- Util.Error("Invalid data in zlib stream");
- }
- //Util.Warn("Decompressed " + data.length + " to " +
- // uncompressed.data.length + " checksums " +
- // checksum(data) + ":" + checksum(uncompressed.data));
-
- return uncompressed.data;
- }
-
- var handlePalette = function() {
- var numColors = rQ[rQi + 2] + 1;
- var paletteSize = numColors * fb_depth;
- FBU.bytes += paletteSize;
- if (ws.rQwait("TIGHT palette " + cmode, FBU.bytes)) { return false; }
-
- var bpp = (numColors <= 2) ? 1 : 8;
- var rowSize = Math.floor((FBU.width * bpp + 7) / 8);
- var raw = false;
- if (rowSize * FBU.height < 12) {
- raw = true;
- clength = [0, rowSize * FBU.height];
- } else {
- clength = getTightCLength(ws.rQslice(3 + paletteSize,
- 3 + paletteSize + 3));
- }
- FBU.bytes += clength[0] + clength[1];
- if (ws.rQwait("TIGHT " + cmode, FBU.bytes)) { return false; }
- // Shift ctl, filter id, num colors, palette entries, and clength off
- ws.rQshiftBytes(3);
- var palette = ws.rQshiftBytes(paletteSize);
- ws.rQshiftBytes(clength[0]);
+ var response = RFB.messages.pixelFormat(this._fb_Bpp, this._fb_depth, this._true_color);
+ response = response.concat(
+ RFB.messages.clientEncodings(this._encodings, this._local_cursor, this._true_color));
+ response = response.concat(
+ RFB.messages.fbUpdateRequests(this._display.getCleanDirtyReset(),
+ this._fb_width, this._fb_height));
- if (raw) {
- data = ws.rQshiftBytes(clength[1]);
- } else {
- data = decompress(ws.rQshiftBytes(clength[1]));
- }
+ this._timing.fbu_rt_start = (new Date()).getTime();
+ this._timing.pixels = 0;
+ this._sock.send(response);
+
+ this._checkEvents();
+
+ 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;
+ },
- // Convert indexed (palette based) image data to RGB
- // TODO: reduce number of calculations inside loop
- var dest = [];
- var x, y, b, w, w1, dp, sp;
- if (numColors === 2) {
- w = Math.floor((FBU.width + 7) / 8);
- w1 = Math.floor(FBU.width / 8);
- for (y = 0; y < FBU.height; y++) {
- for (x = 0; x < w1; x++) {
- for (b = 7; b >= 0; b--) {
- dp = (y*FBU.width + x*8 + 7-b) * 3;
- sp = (data[y*w + x] >> b & 1) * 3;
- dest[dp ] = palette[sp ];
- dest[dp+1] = palette[sp+1];
- dest[dp+2] = palette[sp+2];
+ _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) {
+ this._sock.send(RFB.messages.fbUpdateRequests(this._display.getCleanDirtyReset(),
+ this._fb_width, this._fb_height));
}
- }
- for (b = 7; b >= 8 - FBU.width % 8; b--) {
- dp = (y*FBU.width + x*8 + 7-b) * 3;
- sp = (data[y*w + x] >> b & 1) * 3;
- dest[dp ] = palette[sp ];
- dest[dp+1] = palette[sp+1];
- dest[dp+2] = palette[sp+2];
+ 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));
}
}
- } else {
- for (y = 0; y < FBU.height; y++) {
- for (x = 0; x < FBU.width; x++) {
- dp = (y*FBU.width + x) * 3;
- sp = data[y*FBU.width + x] * 3;
- dest[dp ] = palette[sp ];
- dest[dp+1] = palette[sp+1];
- dest[dp+2] = palette[sp+2];
+
+ 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();
+
+ 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
}
- }
- display.renderQ_push({
- 'type': 'blitRgb',
- 'data': dest,
- 'x': FBU.x,
- 'y': FBU.y,
- 'width': FBU.width,
- 'height': FBU.height});
- return true;
- }
-
- var handleCopy = function() {
- var raw = false;
- var uncompressedSize = FBU.width * FBU.height * fb_depth;
- if (uncompressedSize < 12) {
- raw = true;
- clength = [0, uncompressedSize];
+ 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;
} else {
- clength = getTightCLength(ws.rQslice(1, 4));
+ if (this._display.get_cursor_uri()) {
+ this._local_cursor = true;
+ } else {
+ Util.Warn("Browser does not support local cursor");
+ }
}
- FBU.bytes = 1 + clength[0] + clength[1];
- if (ws.rQwait("TIGHT " + cmode, FBU.bytes)) { return false; }
+ };
+
+ 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 (keysym, down) {
+ var arr = [4];
+ arr.push8(down);
+ arr.push16(0);
+ arr.push32(keysym);
+ return arr;
+ },
+
+ pointerEvent: function (x, y, mask) {
+ var arr = [5]; // msg-type
+ arr.push8(mask);
+ arr.push16(x);
+ arr.push16(y);
+ return arr;
+ },
+
+ // TODO(directxman12): make this unicode compatible?
+ clientCutText: function (text) {
+ var arr = [6]; // msg-type
+ arr.push8(0); // padding
+ arr.push8(0); // padding
+ arr.push8(0); // padding
+ arr.push32(text.length);
+ var n = text.length;
+ for (var i = 0; i < n; i++) {
+ arr.push(text.charCodeAt(i));
+ }
- // Shift ctl, clength off
- ws.rQshiftBytes(1 + clength[0]);
+ return arr;
+ },
+
+ pixelFormat: function (bpp, depth, true_color) {
+ var arr = [0]; // msg-type
+ arr.push8(0); // padding
+ arr.push8(0); // padding
+ arr.push8(0); // padding
+
+ arr.push8(bpp * 8); // bits-per-pixel
+ arr.push8(depth * 8); // depth
+ arr.push8(0); // little-endian
+ arr.push8(true_color ? 1 : 0); // true-color
+
+ arr.push16(255); // red-max
+ arr.push16(255); // green-max
+ arr.push16(255); // blue-max
+ arr.push8(16); // red-shift
+ arr.push8(8); // green-shift
+ arr.push8(0); // blue-shift
+
+ arr.push8(0); // padding
+ arr.push8(0); // padding
+ arr.push8(0); // padding
+ return arr;
+ },
+
+ clientEncodings: function (encodings, local_cursor, true_color) {
+ var i, encList = [];
+
+ 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 {
+ encList.push(encodings[i][1]);
+ }
+ }
- if (raw) {
- data = ws.rQshiftBytes(clength[1]);
- } else {
- data = decompress(ws.rQshiftBytes(clength[1]));
+ var arr = [2]; // msg-type
+ arr.push8(0); // padding
+
+ arr.push16(encList.length); // encoding count
+ for (i = 0; i < encList.length; i++) {
+ arr.push32(encList[i]);
+ }
+
+ return arr;
+ },
+
+ fbUpdateRequests: function (cleanDirty, fb_width, fb_height) {
+ var arr = [];
+
+ 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
+ arr = arr.concat(RFB.messages.fbUpdateRequest(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;
+ arr = arr.concat(RFB.messages.fbUpdateRequest(0, db.x, db.y, w, h));
+ }
+
+ return arr;
+ },
+
+ fbUpdateRequest: function (incremental, x, y, w, h) {
+ if (typeof(x) === "undefined") { x = 0; }
+ if (typeof(y) === "undefined") { y = 0; }
+
+ var arr = [3]; // msg-type
+ arr.push8(incremental);
+ arr.push16(x);
+ arr.push16(y);
+ arr.push16(w);
+ arr.push16(h);
+
+ return arr;
}
+ };
- display.renderQ_push({
- 'type': 'blitRgb',
- 'data': data,
- 'x': FBU.x,
- 'y': FBU.y,
- 'width': FBU.width,
- 'height': FBU.height});
- return true;
- }
-
- ctl = ws.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 fail("Illegal tight compression received, ctl: " + ctl);
-
- if (isTightPNG && (cmode === "filter" || cmode === "copy")) {
- return fail("filter/copy received in tightPNG mode");
- }
-
- switch (cmode) {
- // fill uses fb_depth because TPIXELs drop the padding byte
- case "fill": FBU.bytes += fb_depth; break; // TPIXEL
- case "jpeg": FBU.bytes += 3; break; // max clength
- case "png": FBU.bytes += 3; break; // max clength
- case "filter": FBU.bytes += 2; break; // filter id + num colors if palette
- case "copy": break;
- }
-
- if (ws.rQwait("TIGHT " + cmode, FBU.bytes)) { return false; }
-
- //Util.Debug(" ws.rQslice(0,20): " + ws.rQslice(0,20) + " (" + ws.rQlen() + ")");
- //Util.Debug(" cmode: " + cmode);
-
- // Determine FBU.bytes
- switch (cmode) {
- case "fill":
- ws.rQshift8(); // shift off ctl
- color = ws.rQshiftBytes(fb_depth);
- display.renderQ_push({
- 'type': 'fill',
- 'x': FBU.x,
- 'y': FBU.y,
- 'width': FBU.width,
- 'height': FBU.height,
- 'color': [color[2], color[1], color[0]] });
- break;
- case "png":
- case "jpeg":
- clength = getTightCLength(ws.rQslice(1, 4));
- FBU.bytes = 1 + clength[0] + clength[1]; // ctl + clength size + jpeg-data
- if (ws.rQwait("TIGHT " + cmode, FBU.bytes)) { return false; }
-
- // We have everything, render it
- //Util.Debug(" jpeg, ws.rQlen(): " + ws.rQlen() + ", clength[0]: " +
- // clength[0] + ", clength[1]: " + clength[1]);
- ws.rQshiftBytes(1 + clength[0]); // shift off ctl + compact length
- img = new Image();
- img.src = "data:image/" + cmode +
- extract_data_uri(ws.rQshiftBytes(clength[1]));
- display.renderQ_push({
- 'type': 'img',
- 'img': img,
- 'x': FBU.x,
- 'y': FBU.y});
- img = null;
- break;
- case "filter":
- filterId = rQ[rQi + 1];
- if (filterId === 1) {
- if (!handlePalette()) { return false; }
- } else {
- // Filter 0, Copy could be valid here, but servers don't send it as an explicit filter
- // Filter 2, Gradient is valid but not used if jpeg is enabled
- throw("Unsupported tight subencoding received, filter: " + filterId);
+ RFB.genDES = function (password, challenge) {
+ var passwd = [];
+ for (var i = 0; i < password.length; i++) {
+ passwd.push(password.charCodeAt(i));
}
- break;
- case "copy":
- if (!handleCopy()) { return false; }
- break;
- }
-
- FBU.bytes = 0;
- FBU.rects -= 1;
- //Util.Debug(" ending ws.rQslice(0,20): " + ws.rQslice(0,20) + " (" + ws.rQlen() + ")");
- //Util.Debug("<< display_tight_png");
- return true;
-}
-
-extract_data_uri = function(arr) {
- //var i, stra = [];
- //for (i=0; i< arr.length; i += 1) {
- // stra.push(String.fromCharCode(arr[i]));
- //}
- //return "," + escape(stra.join(''));
- return ";base64," + Base64.encode(arr);
-};
-
-encHandlers.TIGHT = function () { return display_tight(false); };
-encHandlers.TIGHT_PNG = function () { return display_tight(true); };
-
-encHandlers.last_rect = function last_rect() {
- //Util.Debug(">> last_rect");
- FBU.rects = 0;
- //Util.Debug("<< last_rect");
- return true;
-};
-
-encHandlers.DesktopSize = function set_desktopsize() {
- Util.Debug(">> set_desktopsize");
- fb_width = FBU.width;
- fb_height = FBU.height;
- display.resize(fb_width, fb_height);
- timing.fbu_rt_start = (new Date()).getTime();
- // Send a new non-incremental request
- ws.send(fbUpdateRequests());
-
- FBU.bytes = 0;
- FBU.rects -= 1;
-
- Util.Debug("<< set_desktopsize");
- return true;
-};
-
-encHandlers.Cursor = function set_cursor() {
- var x, y, w, h, pixelslength, masklength;
- Util.Debug(">> set_cursor");
- x = FBU.x; // hotspot-x
- y = FBU.y; // hotspot-y
- w = FBU.width;
- h = FBU.height;
-
- pixelslength = w * h * fb_Bpp;
- masklength = Math.floor((w + 7) / 8) * h;
-
- FBU.bytes = pixelslength + masklength;
- if (ws.rQwait("cursor encoding", FBU.bytes)) { return false; }
-
- //Util.Debug(" set_cursor, x: " + x + ", y: " + y + ", w: " + w + ", h: " + h);
-
- display.changeCursor(ws.rQshiftBytes(pixelslength),
- ws.rQshiftBytes(masklength),
- x, y, w, h);
-
- FBU.bytes = 0;
- FBU.rects -= 1;
-
- Util.Debug("<< set_cursor");
- return true;
-};
-
-encHandlers.JPEG_quality_lo = function set_jpeg_quality() {
- Util.Error("Server sent jpeg_quality pseudo-encoding");
-};
-
-encHandlers.compress_lo = function set_compress_level() {
- Util.Error("Server sent compress level pseudo-encoding");
-};
+ return (new DES(passwd)).encrypt(challenge);
+ };
-/*
- * Client message routines
- */
+ RFB.extract_data_uri = function (arr) {
+ return ";base64," + Base64.encode(arr);
+ };
-pixelFormat = function() {
- //Util.Debug(">> pixelFormat");
- var arr;
- arr = [0]; // msg-type
- arr.push8(0); // padding
- arr.push8(0); // padding
- arr.push8(0); // padding
-
- arr.push8(fb_Bpp * 8); // bits-per-pixel
- arr.push8(fb_depth * 8); // depth
- arr.push8(0); // little-endian
- arr.push8(conf.true_color ? 1 : 0); // true-color
-
- arr.push16(255); // red-max
- arr.push16(255); // green-max
- arr.push16(255); // blue-max
- arr.push8(16); // red-shift
- arr.push8(8); // green-shift
- arr.push8(0); // blue-shift
-
- arr.push8(0); // padding
- arr.push8(0); // padding
- arr.push8(0); // padding
- //Util.Debug("<< pixelFormat");
- return arr;
-};
-
-clientEncodings = function() {
- //Util.Debug(">> clientEncodings");
- var arr, i, encList = [];
-
- for (i=0; i<encodings.length; i += 1) {
- if ((encodings[i][0] === "Cursor") &&
- (! conf.local_cursor)) {
- Util.Debug("Skipping Cursor pseudo-encoding");
- } else {
- //Util.Debug("Adding encoding: " + encodings[i][0]);
- encList.push(encodings[i][1]);
- }
- }
-
- arr = [2]; // msg-type
- arr.push8(0); // padding
-
- arr.push16(encList.length); // encoding count
- for (i=0; i < encList.length; i += 1) {
- arr.push32(encList[i]);
- }
- //Util.Debug("<< clientEncodings: " + arr);
- return arr;
-};
-
-fbUpdateRequest = function(incremental, x, y, xw, yw) {
- //Util.Debug(">> fbUpdateRequest");
- if (typeof(x) === "undefined") { x = 0; }
- if (typeof(y) === "undefined") { y = 0; }
- if (typeof(xw) === "undefined") { xw = fb_width; }
- if (typeof(yw) === "undefined") { yw = fb_height; }
- var arr;
- arr = [3]; // msg-type
- arr.push8(incremental);
- arr.push16(x);
- arr.push16(y);
- arr.push16(xw);
- arr.push16(yw);
- //Util.Debug("<< fbUpdateRequest");
- return arr;
-};
-
-// Based on clean/dirty areas, generate requests to send
-fbUpdateRequests = function() {
- var cleanDirty = display.getCleanDirtyReset(),
- arr = [], i, cb, db;
-
- cb = cleanDirty.cleanBox;
- if (cb.w > 0 && cb.h > 0) {
- // Request incremental for clean box
- arr = arr.concat(fbUpdateRequest(1, cb.x, cb.y, cb.w, cb.h));
- }
- for (i = 0; i < cleanDirty.dirtyBoxes.length; i++) {
- db = cleanDirty.dirtyBoxes[i];
- // Force all (non-incremental for dirty box
- arr = arr.concat(fbUpdateRequest(0, db.x, db.y, db.w, db.h));
- }
- return arr;
-};
-
-
-
-keyEvent = function(keysym, down) {
- //Util.Debug(">> keyEvent, keysym: " + keysym + ", down: " + down);
- var arr;
- arr = [4]; // msg-type
- arr.push8(down);
- arr.push16(0);
- arr.push32(keysym);
- //Util.Debug("<< keyEvent");
- return arr;
-};
-
-pointerEvent = function(x, y) {
- //Util.Debug(">> pointerEvent, x,y: " + x + "," + y +
- // " , mask: " + mouse_buttonMask);
- var arr;
- arr = [5]; // msg-type
- arr.push8(mouse_buttonMask);
- arr.push16(x);
- arr.push16(y);
- //Util.Debug("<< pointerEvent");
- return arr;
-};
-
-clientCutText = function(text) {
- //Util.Debug(">> clientCutText");
- var arr, i, n;
- arr = [6]; // msg-type
- arr.push8(0); // padding
- arr.push8(0); // padding
- arr.push8(0); // padding
- arr.push32(text.length);
- n = text.length;
- for (i=0; i < n; i+=1) {
- arr.push(text.charCodeAt(i));
- }
- //Util.Debug("<< clientCutText:" + arr);
- return arr;
-};
-
-
-
-//
-// Public API interface functions
-//
-
-that.connect = function(host, port, password, path) {
- //Util.Debug(">> connect");
-
- rfb_host = host;
- rfb_port = port;
- rfb_password = (password !== undefined) ? password : "";
- rfb_path = (path !== undefined) ? path : "";
-
- if ((!rfb_host) || (!rfb_port)) {
- return fail("Must set host and port");
- }
-
- updateState('connect');
- //Util.Debug("<< connect");
-
-};
-
-that.disconnect = function() {
- //Util.Debug(">> disconnect");
- updateState('disconnect', 'Disconnecting');
- //Util.Debug("<< disconnect");
-};
-
-that.sendPassword = function(passwd) {
- rfb_password = passwd;
- rfb_state = "Authentication";
- setTimeout(init_msg, 1);
-};
-
-that.sendCtrlAltDel = function() {
- if (rfb_state !== "normal" || conf.view_only) { return false; }
- Util.Info("Sending Ctrl-Alt-Del");
- var arr = [];
- arr = arr.concat(keyEvent(0xFFE3, 1)); // Control
- arr = arr.concat(keyEvent(0xFFE9, 1)); // Alt
- arr = arr.concat(keyEvent(0xFFFF, 1)); // Delete
- arr = arr.concat(keyEvent(0xFFFF, 0)); // Delete
- arr = arr.concat(keyEvent(0xFFE9, 0)); // Alt
- arr = arr.concat(keyEvent(0xFFE3, 0)); // Control
- arr = arr.concat(fbUpdateRequests());
- ws.send(arr);
-};
-
-// Send a key press. If 'down' is not specified then send a down key
-// followed by an up key.
-that.sendKey = function(code, down) {
- if (rfb_state !== "normal" || conf.view_only) { return false; }
- var arr = [];
- if (typeof down !== 'undefined') {
- Util.Info("Sending key code (" + (down ? "down" : "up") + "): " + code);
- arr = arr.concat(keyEvent(code, down ? 1 : 0));
- } else {
- Util.Info("Sending key code (down + up): " + code);
- arr = arr.concat(keyEvent(code, 1));
- arr = arr.concat(keyEvent(code, 0));
- }
- arr = arr.concat(fbUpdateRequests());
- ws.send(arr);
-};
-
-that.clipboardPasteFrom = function(text) {
- if (rfb_state !== "normal") { return; }
- //Util.Debug(">> clipboardPasteFrom: " + text.substr(0,40) + "...");
- ws.send(clientCutText(text));
- //Util.Debug("<< clipboardPasteFrom");
-};
-
-// Override internal functions for testing
-that.testMode = function(override_send) {
- test_mode = true;
- that.recv_message = ws.testMode(override_send);
-
- checkEvents = function () { /* Stub Out */ };
- that.connect = function(host, port, password) {
- rfb_host = host;
- rfb_port = port;
- rfb_password = password;
- updateState('ProtocolVersion', "Starting VNC handshake");
- };
-};
+ 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 constructor(); // Return the public API interface
+ return true;
+ },
+
+ COPYRECT: function () {
+ this._FBU.bytes = 4;
+ if (this._sock.rQwait("COPYRECT", 4)) { return false; }
+ this._display.renderQ_push({
+ 'type': 'copy',
+ 'old_x': this._sock.rQshift16(),
+ 'old_y': this._sock.rQshift16(),
+ 'x': this._FBU.x,
+ 'y': this._FBU.y,
+ 'width': this._FBU.width,
+ 'height': 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;
+ }
-} // End of RFB()
+ 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, rQ, rQi);
+ rQi += this._FBU.bytes - 1;
+ }
+ } 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
+ this._FBU.background = rQ.slice(rQi, rQi + this._fb_Bpp);
+ rQi += this._fb_Bpp;
+ }
+ if (this._FBU.subencoding & 0x04) { // Foreground
+ this._FBU.foreground = rQ.slice(rQi, rQi + this._fb_Bpp);
+ 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
+ color = rQ.slice(rQi, rQi + this._fb_Bpp);
+ 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) {
+ for (var i = 0; i < 4; i++) {
+ if ((resetStreams >> i) & 1) {
+ this._FBU.zlibs[i].reset();
+ Util.Info("Reset zlib stream " + i);
+ }
+ }
+
+ var uncompressed = this._FBU.zlibs[streamId].uncompress(data, 0);
+ if (uncompressed.status !== 0) {
+ Util.Error("Invalid data in zlib stream");
+ }
+
+ return uncompressed.data;
+ }.bind(this);
+
+ var indexedToRGB = function (data, numColors, palette, width, height) {
+ // Convert indexed (palette based) image data to RGB
+ // TODO: reduce number of calculations inside loop
+ var dest = [];
+ var x, y, dp, sp;
+ if (numColors === 2) {
+ var w = Math.floor((width + 7) / 8);
+ var w1 = Math.floor(width / 8);
+
+ for (y = 0; y < height; y++) {
+ var b;
+ for (x = 0; x < w1; x++) {
+ for (b = 7; b >= 0; b--) {
+ dp = (y * width + x * 8 + 7 - b) * 3;
+ sp = (data[y * w + x] >> b & 1) * 3;
+ dest[dp] = palette[sp];
+ dest[dp + 1] = palette[sp + 1];
+ dest[dp + 2] = palette[sp + 2];
+ }
+ }
+
+ for (b = 7; b >= 8 - width % 8; b--) {
+ dp = (y * width + x * 8 + 7 - b) * 3;
+ sp = (data[y * w + x] >> b & 1) * 3;
+ dest[dp] = palette[sp];
+ dest[dp + 1] = palette[sp + 1];
+ dest[dp + 2] = palette[sp + 2];
+ }
+ }
+ } else {
+ for (y = 0; y < height; y++) {
+ for (x = 0; x < width; x++) {
+ dp = (y * width + x) * 3;
+ sp = data[y * width + x] * 3;
+ dest[dp] = palette[sp];
+ dest[dp + 1] = palette[sp + 1];
+ dest[dp + 2] = palette[sp + 2];
+ }
+ }
+ }
+
+ return dest;
+ }.bind(this);
+
+ var rQ = this._sock.get_rQ();
+ var rQi = this._sock.get_rQi();
+ var cmode, clength, 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;
+ clength = [0, rowSize * this._FBU.height];
+ } else {
+ clength = RFB.encodingHandlers.getTightCLength(this._sock.rQslice(3 + paletteSize,
+ 3 + paletteSize + 3));
+ }
+
+ this._FBU.bytes += clength[0] + clength[1];
+ 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.rQskipBytes(clength[0]);
+
+ if (raw) {
+ data = this._sock.rQshiftBytes(clength[1]);
+ } else {
+ data = decompress(this._sock.rQshiftBytes(clength[1]));
+ }
+
+ // Convert indexed (palette based) image data to RGB
+ var rgb = indexedToRGB(data, numColors, palette, this._FBU.width, this._FBU.height);
+
+ this._display.renderQ_push({
+ 'type': 'blitRgb',
+ 'data': rgb,
+ 'x': this._FBU.x,
+ 'y': this._FBU.y,
+ 'width': this._FBU.width,
+ 'height': this._FBU.height
+ });
+
+ 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;
+ clength = [0, uncompressedSize];
+ } else {
+ clength = RFB.encodingHandlers.getTightCLength(this._sock.rQslice(1, 4));
+ }
+ this._FBU.bytes = 1 + clength[0] + clength[1];
+ if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; }
+
+ // Shift ctl, clength off
+ this._sock.rQshiftBytes(1 + clength[0]);
+
+ if (raw) {
+ data = this._sock.rQshiftBytes(clength[1]);
+ } else {
+ data = decompress(this._sock.rQshiftBytes(clength[1]));
+ }
+
+ this._display.renderQ_push({
+ 'type': 'blitRgb',
+ 'data': data,
+ 'x': this._FBU.x,
+ 'y': this._FBU.y,
+ 'width': this._FBU.width,
+ 'height': this._FBU.height
+ });
+
+ 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":
+ this._sock.rQskip8(); // shift off ctl
+ var color = this._sock.rQshiftBytes(this._fb_depth);
+ this._display.renderQ_push({
+ 'type': 'fill',
+ 'x': this._FBU.x,
+ 'y': this._FBU.y,
+ 'width': this._FBU.width,
+ 'height': this._FBU.height,
+ 'color': [color[2], color[1], color[0]]
+ });
+ break;
+ case "png":
+ case "jpeg":
+ clength = RFB.encodingHandlers.getTightCLength(this._sock.rQslice(1, 4));
+ this._FBU.bytes = 1 + clength[0] + clength[1]; // 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 + clength[0]); // shift off clt + compact length
+ var img = new Image();
+ img.src = "data: image/" + cmode +
+ RFB.extract_data_uri(this._sock.rQshiftBytes(clength[1]));
+ 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
+ // TODO(directxman12): why aren't we just calling '_fail' here
+ throw new Error("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;
+ },
+
+ DesktopSize: function () {
+ Util.Debug(">> set_desktopsize");
+ this._fb_width = this._FBU.width;
+ this._fb_height = this._FBU.height;
+ this._onFBResize(this, this._fb_width, this._fb_height);
+ this._display.resize(this._fb_width, this._fb_height);
+ this._timing.fbu_rt_start = (new Date()).getTime();
+
+ this._FBU.bytes = 0;
+ this._FBU.rects--;
+
+ Util.Debug("<< set_desktopsize");
+ 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/webclients/novnc/include/ui.js b/webclients/novnc/include/ui.js
index dc837e6..e869aa6 100644
--- a/webclients/novnc/include/ui.js
+++ b/webclients/novnc/include/ui.js
@@ -1,659 +1,979 @@
/*
* noVNC: HTML5 VNC client
* Copyright (C) 2012 Joel Martin
- * Licensed under LGPL-3 (see LICENSE.txt)
+ * Copyright (C) 2013 Samuel Mannehed for Cendio AB
+ * Licensed under MPL 2.0 (see LICENSE.txt)
*
* See README.md for usage and integration instructions.
*/
-"use strict";
-/*jslint white: false, browser: true */
-/*global window, $D, Util, WebUtil, RFB, Display */
-
-var UI = {
-
-rfb_state : 'loaded',
-settingsOpen : false,
-connSettingsOpen : false,
-clipboardOpen: false,
-keyboardVisible: false,
-
-// Render default UI and initialize settings menu
-load: function() {
- var html = '', i, sheet, sheets, llevels;
-
- // Stylesheet selection dropdown
- sheet = WebUtil.selectStylesheet();
- sheets = WebUtil.getStylesheets();
- for (i = 0; i < sheets.length; i += 1) {
- UI.addOption($D('noVNC_stylesheet'),sheets[i].title, sheets[i].title);
- }
-
- // Logging selection dropdown
- llevels = ['error', 'warn', 'info', 'debug'];
- for (i = 0; i < llevels.length; i += 1) {
- UI.addOption($D('noVNC_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'));
-
- /* Populate the controls if defaults are provided in the URL */
- UI.initSetting('host', window.location.hostname);
- UI.initSetting('port', window.location.port);
- UI.initSetting('password', '');
- UI.initSetting('encrypt', (window.location.protocol === "https:"));
- UI.initSetting('true_color', true);
- UI.initSetting('cursor', false);
- UI.initSetting('shared', true);
- UI.initSetting('view_only', false);
- UI.initSetting('connectTimeout', 2);
- UI.initSetting('path', 'websockify');
- UI.initSetting('repeaterID', '');
-
- UI.rfb = RFB({'target': $D('noVNC_canvas'),
- 'onUpdateState': UI.updateState,
- 'onClipboard': UI.clipReceive});
- UI.updateVisualState();
-
- // Unfocus clipboard when over the VNC area
- //$D('VNC_screen').onmousemove = function () {
- // var keyboard = UI.rfb.get_keyboard();
- // if ((! keyboard) || (! keyboard.get_focused())) {
- // $D('VNC_clipboard_text').blur();
- // }
- // };
-
- // Show mouse selector buttons on touch screen devices
- if ('ontouchstart' in document.documentElement) {
- // 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);
- $D('noVNC_clip').disabled = true;
- } else {
- UI.initSetting('clip', false);
- }
-
- //iOS Safari does not support CSS position:fixed.
- //This detects iOS devices and enables javascript workaround.
- if ((navigator.userAgent.match(/iPhone/i)) ||
- (navigator.userAgent.match(/iPod/i)) ||
- (navigator.userAgent.match(/iPad/i))) {
- //UI.setOnscroll();
- //UI.setResize();
- }
-
- $D('noVNC_host').focus();
-
- UI.setViewClip();
- Util.addEvent(window, 'resize', UI.setViewClip);
-
- Util.addEvent(window, 'beforeunload', function () {
- if (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.com") {
- // Open the description dialog
- $D('noVNC_description').style.display = "block";
- } else {
- // Open the connect panel on first load
- UI.toggleConnectPanel();
- }
-},
-
-// Read form control compatible setting from cookie
-getSetting: function(name) {
- var val, ctrl = $D('noVNC_' + name);
- val = WebUtil.readCookie(name);
- if (ctrl.type === 'checkbox') {
- if (val.toLowerCase() in {'0':1, 'no':1, 'false':1}) {
- val = false;
- } else {
- val = true;
- }
- }
- return val;
-},
+/* 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(); };
+ window.onload = function () { UI.keyboardinputReset(); };
+ Util.load_scripts(["webutil.js", "base64.js", "websock.js", "des.js",
+ "keysymdef.js", "keyboard.js", "input.js", "display.js",
+ "jsunzip.js", "rfb.js", "keysym.js"]);
+
+ var UI = {
+
+ rfb_state : 'loaded',
+ settingsOpen : false,
+ connSettingsOpen : false,
+ popupStatusOpen : false,
+ clipboardOpen: false,
+ keyboardVisible: false,
+ hideKeyboardTimeout: null,
+ lastKeyboardinput: null,
+ defaultKeyboardinputLen: 100,
+ extraKeysVisible: false,
+ ctrlOn: false,
+ altOn: false,
+ isTouchDevice: 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_stylesheet'),sheets[i].title, sheets[i].title);
+ }
-// Update cookie and form control setting. If value is not set, then
-// updates from control to current cookie setting.
-updateSetting: function(name, value) {
+ // Logging selection dropdown
+ var llevels = ['error', 'warn', 'info', 'debug'];
+ for (i = 0; i < llevels.length; i += 1) {
+ UI.addOption($D('noVNC_logging'),llevels[i], llevels[i]);
+ }
- var i, ctrl = $D('noVNC_' + name);
- // Save the cookie for this session
- if (typeof value !== 'undefined') {
- WebUtil.createCookie(name, value);
- }
+ // 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;
+ }
+ }
- // Update the settings control
- value = UI.getSetting(name);
+ /* 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('shared', true);
+ UI.initSetting('view_only', false);
+ UI.initSetting('path', 'websockify');
+ UI.initSetting('repeaterID', '');
+
+ UI.rfb = new RFB({'target': $D('noVNC_canvas'),
+ 'onUpdateState': UI.updateState,
+ 'onXvpInit': UI.updateXvpVisualState,
+ 'onClipboard': UI.clipReceive,
+ 'onDesktopName': UI.updateDocumentTitle});
+
+ var autoconnect = WebUtil.getQueryVar('autoconnect', false);
+ if (autoconnect === 'true' || autoconnect == '1') {
+ autoconnect = true;
+ UI.connect();
+ } else {
+ autoconnect = false;
+ }
- if (ctrl.type === 'checkbox') {
- ctrl.checked = value;
+ UI.updateVisualState();
+
+ // 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);
+ $D('noVNC_clip').disabled = true;
+ } else {
+ UI.initSetting('clip', false);
+ }
- } else if (typeof ctrl.options !== 'undefined') {
- for (i = 0; i < ctrl.options.length; i += 1) {
- if (ctrl.options[i].value === value) {
- ctrl.selectedIndex = i;
- break;
+ //iOS Safari does not support CSS position:fixed.
+ //This detects iOS devices and enables javascript workaround.
+ if ((navigator.userAgent.match(/iPhone/i)) ||
+ (navigator.userAgent.match(/iPod/i)) ||
+ (navigator.userAgent.match(/iPad/i))) {
+ //UI.setOnscroll();
+ //UI.setResize();
}
- }
- } 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_' + 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.createCookie(name, val);
- //Util.Debug("Setting saved '" + name + "=" + val + "'");
- return val;
-},
-
-// Initial page load read/initialization of settings
-initSetting: function(name, defVal) {
- var val;
-
- // Check Query string followed by cookie
- val = WebUtil.getQueryVar(name);
- if (val === null) {
- val = WebUtil.readCookie(name, defVal);
- }
- UI.updateSetting(name, val);
- //Util.Debug("Setting '" + name + "' initialized to '" + val + "'");
- return val;
-},
-
-// Force a setting to be a certain value
-forceSetting: function(name, val) {
- UI.updateSetting(name, val);
- return val;
-},
-
-
-// Show the clipboard panel
-toggleClipboardPanel: function() {
- // Close the description panel
- $D('noVNC_description').style.display = "none";
- //Close settings if open
- if (UI.settingsOpen === true) {
- UI.settingsApply();
- UI.closeSettingsMenu();
- }
- //Close connection settings if open
- if (UI.connSettingsOpen === true) {
- UI.toggleConnectPanel();
- }
- //Toggle Clipboard Panel
- if (UI.clipboardOpen === true) {
- $D('noVNC_clipboard').style.display = "none";
- $D('clipboardButton').className = "noVNC_status_button";
- UI.clipboardOpen = false;
- } else {
- $D('noVNC_clipboard').style.display = "block";
- $D('clipboardButton').className = "noVNC_status_button_selected";
- UI.clipboardOpen = true;
- }
-},
-
-// 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('connectButton').className = "noVNC_status_button";
- }
- if (UI.clipboardOpen === true) {
- UI.toggleClipboardPanel();
- }
-
- //Toggle Connection Panel
- if (UI.connSettingsOpen === true) {
- $D('noVNC_controls').style.display = "none";
- $D('connectButton').className = "noVNC_status_button";
- UI.connSettingsOpen = false;
- } else {
- $D('noVNC_controls').style.display = "block";
- $D('connectButton').className = "noVNC_status_button_selected";
- UI.connSettingsOpen = true;
- $D('noVNC_host').focus();
- }
-},
-
-// 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 (UI.rfb.get_display().get_cursor_uri()) {
- UI.updateSetting('cursor');
- } else {
- UI.updateSetting('cursor', false);
- $D('noVNC_cursor').disabled = true;
- }
- UI.updateSetting('clip');
- UI.updateSetting('shared');
- UI.updateSetting('view_only');
- UI.updateSetting('connectTimeout');
- UI.updateSetting('path');
- UI.updateSetting('repeaterID');
- UI.updateSetting('stylesheet');
- UI.updateSetting('logging');
-
- UI.openSettingsMenu();
- }
-},
-
-// Open menu
-openSettingsMenu: function() {
- // Close the description panel
- $D('noVNC_description').style.display = "none";
- if (UI.clipboardOpen === true) {
- UI.toggleClipboardPanel();
- }
- //Close connection settings if open
- if (UI.connSettingsOpen === true) {
- UI.toggleConnectPanel();
- }
- $D('noVNC_settings').style.display = "block";
- $D('settingsButton').className = "noVNC_status_button_selected";
- UI.settingsOpen = true;
-},
-
-// Close menu (without applying settings)
-closeSettingsMenu: function() {
- $D('noVNC_settings').style.display = "none";
- $D('settingsButton').className = "noVNC_status_button";
- UI.settingsOpen = false;
-},
-
-// Save/apply settings when 'Apply' button is pressed
-settingsApply: function() {
- //Util.Debug(">> settingsApply");
- UI.saveSetting('encrypt');
- UI.saveSetting('true_color');
- if (UI.rfb.get_display().get_cursor_uri()) {
- UI.saveSetting('cursor');
- }
- UI.saveSetting('clip');
- UI.saveSetting('shared');
- UI.saveSetting('view_only');
- UI.saveSetting('connectTimeout');
- 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.setViewDrag(UI.rfb.get_viewportDrag());
- //Util.Debug("<< settingsApply");
-},
-
-
-
-setPassword: function() {
- UI.rfb.sendPassword($D('noVNC_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;
-},
-
-sendCtrlAltDel: function() {
- UI.rfb.sendCtrlAltDel();
-},
-
-setMouseButton: function(num) {
- var b, blist = [0, 1,2,4], button;
-
- if (typeof num === 'undefined') {
- // Disable mouse buttons
- num = -1;
- }
- if (UI.rfb) {
- UI.rfb.get_mouse().set_touchButton(num);
- }
-
- for (b = 0; b < blist.length; b++) {
- button = $D('noVNC_mouse_button' + blist[b]);
- if (blist[b] === num) {
- button.style.display = "";
- } else {
- button.style.display = "none";
- /*
- button.style.backgroundColor = "black";
- button.style.color = "lightgray";
- button.style.backgroundColor = "";
- button.style.color = "";
- */
- }
- }
-},
-
-updateState: function(rfb, state, oldstate, msg) {
- var s, sb, c, d, cad, vd, klass;
- UI.rfb_state = state;
- s = $D('noVNC_status');
- sb = $D('noVNC_status_bar');
- 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";
- // Fall through
- case 'loaded':
- klass = "noVNC_status_normal";
- break;
- case 'password':
+ UI.setBarPosition();
+
+ $D('noVNC_host').focus();
+
+ UI.setViewClip();
+ Util.addEvent(window, 'resize', UI.setViewClip);
+
+ Util.addEvent(window, 'beforeunload', function () {
+ if (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);
+ }
+ },
+
+ addMouseHandlers: function() {
+ // Setup interface handlers that can't be inline
+ $D("noVNC_view_drag_button").onclick = UI.setViewDrag;
+ $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("showKeyboard").onclick = UI.showKeyboard;
+
+ $D("keyboardinput").oninput = UI.keyInput;
+ $D("keyboardinput").onblur = UI.keyInputBlur;
+
+ $D("showExtraKeysButton").onclick = UI.showExtraKeys;
+ $D("toggleCtrlButton").onclick = UI.toggleCtrl;
+ $D("toggleAltButton").onclick = UI.toggleAlt;
+ $D("sendTabButton").onclick = UI.sendTab;
+ $D("sendEscButton").onclick = UI.sendEsc;
+
+ $D("sendCtrlAltDelButton").onclick = UI.sendCtrlAltDel;
+ $D("xvpShutdownButton").onclick = UI.xvpShutdown;
+ $D("xvpRebootButton").onclick = UI.xvpReboot;
+ $D("xvpResetButton").onclick = UI.xvpReset;
+ $D("noVNC_status").onclick = UI.togglePopupStatusPanel;
+ $D("noVNC_popup_status_panel").onclick = UI.togglePopupStatusPanel;
+ $D("xvpButton").onclick = UI.toggleXvpPanel;
+ $D("clipboardButton").onclick = UI.toggleClipboardPanel;
+ $D("settingsButton").onclick = UI.toggleSettingsPanel;
+ $D("connectButton").onclick = UI.toggleConnectPanel;
+ $D("disconnectButton").onclick = UI.disconnect;
+ $D("descriptionButton").onclick = UI.toggleConnectPanel;
+
+ $D("noVNC_clipboard_text").onfocus = UI.displayBlur;
+ $D("noVNC_clipboard_text").onblur = UI.displayFocus;
+ $D("noVNC_clipboard_text").onchange = UI.clipSend;
+ $D("noVNC_clipboard_clear_button").onclick = UI.clipClear;
+
+ $D("noVNC_settings_menu").onmouseover = UI.displayBlur;
+ $D("noVNC_settings_menu").onmouseover = UI.displayFocus;
+ $D("noVNC_apply").onclick = UI.settingsApply;
+
+ $D("noVNC_connect_button").onclick = UI.connect;
+ },
+
+ // Read form control compatible setting from cookie
+ getSetting: function(name) {
+ var ctrl = $D('noVNC_' + name);
+ var val = WebUtil.readSetting(name);
+ if (val !== null && ctrl.type === 'checkbox') {
+ if (val.toString().toLowerCase() in {'0':1, 'no':1, 'false':1}) {
+ val = false;
+ } else {
+ val = true;
+ }
+ }
+ 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_' + 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_' + 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;
+ },
+
+ // Initial page load read/initialization of settings
+ initSetting: function(name, defVal) {
+ // Check Query string followed by cookie
+ var val = WebUtil.getQueryVar(name);
+ if (val === null) {
+ val = WebUtil.readSetting(name, defVal);
+ }
+ UI.updateSetting(name, val);
+ return val;
+ },
+
+ // Force a setting to be a certain value
+ forceSetting: function(name, val) {
+ UI.updateSetting(name, val);
+ return val;
+ },
+
+
+ // Show the popup status panel
+ togglePopupStatusPanel: function() {
+ var psp = $D('noVNC_popup_status_panel');
+ if (UI.popupStatusOpen === true) {
+ psp.style.display = "none";
+ UI.popupStatusOpen = false;
+ } else {
+ psp.innerHTML = $D('noVNC_status').innerHTML;
+ psp.style.display = "block";
+ psp.style.left = window.innerWidth/2 -
+ parseInt(window.getComputedStyle(psp, false).width)/2 -30 + "px";
+ UI.popupStatusOpen = true;
+ }
+ },
+
+ // 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 popup status panel if open
+ if (UI.popupStatusOpen === true) {
+ UI.togglePopupStatusPanel();
+ }
+ // 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('xvpButton').className = "noVNC_status_button";
+ UI.xvpOpen = false;
+ } else {
+ $D('noVNC_xvp').style.display = "block";
+ $D('xvpButton').className = "noVNC_status_button_selected";
+ UI.xvpOpen = true;
+ }
+ },
+
+ // 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 popup status panel if open
+ if (UI.popupStatusOpen === true) {
+ UI.togglePopupStatusPanel();
+ }
+ // 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('clipboardButton').className = "noVNC_status_button";
+ UI.clipboardOpen = false;
+ } else {
+ $D('noVNC_clipboard').style.display = "block";
+ $D('clipboardButton').className = "noVNC_status_button_selected";
+ UI.clipboardOpen = true;
+ }
+ },
+
+ // 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('connectButton').className = "noVNC_status_button";
+ }
+ // Close clipboard panel if open
+ if (UI.clipboardOpen === true) {
+ UI.toggleClipboardPanel();
+ }
+ // Close popup status panel if open
+ if (UI.popupStatusOpen === true) {
+ UI.togglePopupStatusPanel();
+ }
+ // 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('connectButton').className = "noVNC_status_button";
+ UI.connSettingsOpen = false;
+ UI.saveSetting('host');
+ UI.saveSetting('port');
+ //UI.saveSetting('password');
+ } else {
+ $D('noVNC_controls').style.display = "block";
+ $D('connectButton').className = "noVNC_status_button_selected";
+ UI.connSettingsOpen = true;
+ $D('noVNC_host').focus();
+ }
+ },
+
+ // 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 (UI.rfb.get_display().get_cursor_uri()) {
+ UI.updateSetting('cursor');
+ } else {
+ UI.updateSetting('cursor', !UI.isTouchDevice);
+ $D('noVNC_cursor').disabled = true;
+ }
+ UI.updateSetting('clip');
+ UI.updateSetting('shared');
+ UI.updateSetting('view_only');
+ UI.updateSetting('path');
+ UI.updateSetting('repeaterID');
+ UI.updateSetting('stylesheet');
+ UI.updateSetting('logging');
+
+ UI.openSettingsMenu();
+ }
+ },
+
+ // 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 popup status panel if open
+ if (UI.popupStatusOpen === true) {
+ UI.togglePopupStatusPanel();
+ }
+ // Close XVP panel if open
+ if (UI.xvpOpen === true) {
+ UI.toggleXvpPanel();
+ }
+ $D('noVNC_settings').style.display = "block";
+ $D('settingsButton').className = "noVNC_status_button_selected";
+ UI.settingsOpen = true;
+ },
+
+ // Close menu (without applying settings)
+ closeSettingsMenu: function() {
+ $D('noVNC_settings').style.display = "none";
+ $D('settingsButton').className = "noVNC_status_button";
+ UI.settingsOpen = false;
+ },
+
+ // Save/apply settings when 'Apply' button is pressed
+ settingsApply: function() {
+ //Util.Debug(">> settingsApply");
+ UI.saveSetting('encrypt');
+ UI.saveSetting('true_color');
+ if (UI.rfb.get_display().get_cursor_uri()) {
+ UI.saveSetting('cursor');
+ }
+ 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.setViewDrag(UI.rfb.get_viewportDrag());
+ //Util.Debug("<< settingsApply");
+ },
+
+
+
+ setPassword: function() {
+ UI.rfb.sendPassword($D('noVNC_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;
+ },
- $D('noVNC_connect_button').value = "Send Password";
- $D('noVNC_connect_button').onclick = UI.setPassword;
- $D('noVNC_password').focus();
-
- klass = "noVNC_status_warn";
- break;
- default:
- klass = "noVNC_status_warn";
- break;
- }
-
- if (typeof(msg) !== 'undefined') {
- s.setAttribute("class", klass);
- sb.setAttribute("class", klass);
- s.innerHTML = msg;
- }
-
- UI.updateVisualState();
-},
-
-// Disable/enable controls depending on connection state
-updateVisualState: function() {
- var connected = UI.rfb_state === 'normal' ? true : false;
-
- //Util.Debug(">> updateVisualState");
- $D('noVNC_encrypt').disabled = connected;
- $D('noVNC_true_color').disabled = connected;
- if (UI.rfb && UI.rfb.get_display() &&
- UI.rfb.get_display().get_cursor_uri()) {
- $D('noVNC_cursor').disabled = connected;
- } else {
- UI.updateSetting('cursor', false);
- $D('noVNC_cursor').disabled = true;
- }
- $D('noVNC_shared').disabled = connected;
- $D('noVNC_view_only').disabled = connected;
- $D('noVNC_connectTimeout').disabled = connected;
- $D('noVNC_path').disabled = connected;
- $D('noVNC_repeaterID').disabled = connected;
-
- if (connected) {
- UI.setViewClip();
- UI.setMouseButton(1);
- $D('clipboardButton').style.display = "inline";
- $D('showKeyboard').style.display = "inline";
- $D('sendCtrlAltDelButton').style.display = "inline";
- } else {
- UI.setMouseButton();
- $D('clipboardButton').style.display = "none";
- $D('showKeyboard').style.display = "none";
- $D('sendCtrlAltDelButton').style.display = "none";
- }
- // State change disables viewport dragging.
- // It is enabled (toggled) by direct click on the button
- UI.setViewDrag(false);
-
- switch (UI.rfb_state) {
- case 'fatal':
- case 'failed':
- case 'loaded':
- case 'disconnected':
- $D('connectButton').style.display = "";
- $D('disconnectButton').style.display = "none";
- break;
- default:
- $D('connectButton').style.display = "none";
- $D('disconnectButton').style.display = "";
- break;
- }
-
- //Util.Debug("<< updateVisualState");
-},
-
-
-clipReceive: function(rfb, text) {
- Util.Debug(">> UI.clipReceive: " + text.substr(0,40) + "...");
- $D('noVNC_clipboard_text').value = text;
- Util.Debug("<< UI.clipReceive");
-},
-
-
-connect: function() {
- var host, port, password, path;
-
- UI.closeSettingsMenu();
- UI.toggleConnectPanel();
-
- host = $D('noVNC_host').value;
- port = $D('noVNC_port').value;
- password = $D('noVNC_password').value;
- path = $D('noVNC_path').value;
- if ((!host) || (!port)) {
- throw("Must set host and port");
- }
-
- 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_connectTimeout(UI.getSetting('connectTimeout'));
- 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";
-},
-
-disconnect: function() {
- UI.closeSettingsMenu();
- UI.rfb.disconnect();
-
- $D('noVNC_logo').style.display = "block";
- UI.connSettingsOpen = false;
- UI.toggleConnectPanel();
-},
-
-displayBlur: function() {
- UI.rfb.get_keyboard().set_focused(false);
- UI.rfb.get_mouse().set_focused(false);
-},
-
-displayFocus: function() {
- UI.rfb.get_keyboard().set_focused(true);
- UI.rfb.get_mouse().set_focused(true);
-},
-
-clipClear: function() {
- $D('noVNC_clipboard_text').value = "";
- UI.rfb.clipboardPasteFrom("");
-},
-
-clipSend: function() {
- var text = $D('noVNC_clipboard_text').value;
- Util.Debug(">> UI.clipSend: " + text.substr(0,40) + "...");
- UI.rfb.clipboardPasteFrom(text);
- Util.Debug("<< UI.clipSend");
-},
-
-
-// Enable/disable and configure viewport clipping
-setViewClip: function(clip) {
- var display, cur_clip, pos, new_w, new_h;
-
- if (UI.rfb) {
- display = UI.rfb.get_display();
- } else {
- return;
- }
-
- 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);
- $D('noVNC_canvas').style.position = 'static';
- display.viewportChange();
- }
- if (UI.getSetting('clip')) {
- // If clipping, update clipping settings
- $D('noVNC_canvas').style.position = 'absolute';
- pos = Util.getPosition($D('noVNC_canvas'));
- new_w = window.innerWidth - pos.x;
- new_h = window.innerHeight - pos.y;
- display.set_viewport(true);
- display.viewportChange(0, 0, new_w, new_h);
- }
-},
-
-// Toggle/set/unset the viewport drag/move button
-setViewDrag: function(drag) {
- var vmb = $D('noVNC_view_drag_button');
- if (!UI.rfb) { return; }
-
- if (UI.rfb_state === 'normal' &&
- UI.rfb.get_display().get_viewport()) {
- vmb.style.display = "inline";
- } else {
- vmb.style.display = "none";
- }
-
- if (typeof(drag) === "undefined") {
- // If not specified, then toggle
- drag = !UI.rfb.get_viewportDrag();
- }
- if (drag) {
- vmb.className = "noVNC_status_button_selected";
- UI.rfb.set_viewportDrag(true);
- } else {
- vmb.className = "noVNC_status_button";
- UI.rfb.set_viewportDrag(false);
- }
-},
-
-// On touch devices, show the OS keyboard
-showKeyboard: function() {
- if(UI.keyboardVisible === false) {
- $D('keyboardinput').focus();
- UI.keyboardVisible = true;
- $D('showKeyboard').className = "noVNC_status_button_selected";
- } else if(UI.keyboardVisible === true) {
- $D('keyboardinput').blur();
- $D('showKeyboard').className = "noVNC_status_button";
- UI.keyboardVisible = false;
- }
-},
-
-keyInputBlur: function() {
- $D('showKeyboard').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.
- setTimeout(function() { UI.setKeyboard(); },100);
-},
-
-setKeyboard: function() {
- UI.keyboardVisible = false;
-},
-
-// iOS < Version 5 does not support position fixed. Javascript workaround:
-setOnscroll: function() {
- window.onscroll = function() {
- UI.setBarPosition();
- };
-},
+ sendCtrlAltDel: function() {
+ UI.rfb.sendCtrlAltDel();
+ },
-setResize: function () {
- window.onResize = function() {
- UI.setBarPosition();
- };
-},
+ xvpShutdown: function() {
+ UI.rfb.xvpShutdown();
+ },
-//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);
-},
+ xvpReboot: function() {
+ UI.rfb.xvpReboot();
+ },
-setBarPosition: function() {
- $D('noVNC-control-bar').style.top = (window.pageYOffset) + 'px';
- $D('noVNC_mobile_buttons').style.left = (window.pageXOffset) + 'px';
+ xvpReset: function() {
+ UI.rfb.xvpReset();
+ },
- var vncwidth = $D('noVNC_screen').style.offsetWidth;
- $D('noVNC-control-bar').style.width = vncwidth + 'px';
-}
+ 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";
+ }
+ }
+ },
+
+ 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";
+ /* 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_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').innerHTML = msg;
+ }
+ UI.updateVisualState();
+ },
+
+ // Disable/enable controls depending on connection state
+ updateVisualState: function() {
+ var connected = UI.rfb_state === 'normal' ? true : false;
+
+ //Util.Debug(">> updateVisualState");
+ $D('noVNC_encrypt').disabled = connected;
+ $D('noVNC_true_color').disabled = connected;
+ if (UI.rfb && UI.rfb.get_display() &&
+ UI.rfb.get_display().get_cursor_uri()) {
+ $D('noVNC_cursor').disabled = connected;
+ } else {
+ UI.updateSetting('cursor', !UI.isTouchDevice);
+ $D('noVNC_cursor').disabled = true;
+ }
+ $D('noVNC_shared').disabled = connected;
+ $D('noVNC_view_only').disabled = connected;
+ $D('noVNC_path').disabled = connected;
+ $D('noVNC_repeaterID').disabled = connected;
+
+ if (connected) {
+ UI.setViewClip();
+ UI.setMouseButton(1);
+ $D('clipboardButton').style.display = "inline";
+ $D('showKeyboard').style.display = "inline";
+ $D('noVNC_extra_keys').style.display = "";
+ $D('sendCtrlAltDelButton').style.display = "inline";
+ } else {
+ UI.setMouseButton();
+ $D('clipboardButton').style.display = "none";
+ $D('showKeyboard').style.display = "none";
+ $D('noVNC_extra_keys').style.display = "none";
+ $D('sendCtrlAltDelButton').style.display = "none";
+ UI.updateXvpVisualState(0);
+ }
+ // State change disables viewport dragging.
+ // It is enabled (toggled) by direct click on the button
+ UI.setViewDrag(false);
+
+ switch (UI.rfb_state) {
+ case 'fatal':
+ case 'failed':
+ case 'loaded':
+ case 'disconnected':
+ $D('connectButton').style.display = "";
+ $D('disconnectButton').style.display = "none";
+ break;
+ default:
+ $D('connectButton').style.display = "none";
+ $D('disconnectButton').style.display = "";
+ break;
+ }
+
+ //Util.Debug("<< updateVisualState");
+ },
+
+ // Disable/enable XVP button
+ updateXvpVisualState: function(ver) {
+ if (ver >= 1) {
+ $D('xvpButton').style.display = 'inline';
+ } else {
+ $D('xvpButton').style.display = 'none';
+ // Close XVP panel if open
+ if (UI.xvpOpen === true) {
+ UI.toggleXvpPanel();
+ }
+ }
+ },
+
+ // Display the desktop name in the document title
+ updateDocumentTitle: function(rfb, name) {
+ document.title = name + " - noVNC";
+ },
+
+ clipReceive: function(rfb, text) {
+ Util.Debug(">> UI.clipReceive: " + text.substr(0,40) + "...");
+ $D('noVNC_clipboard_text').value = text;
+ Util.Debug("<< UI.clipReceive");
+ },
+
+ connect: function() {
+ UI.closeSettingsMenu();
+ UI.toggleConnectPanel();
+ var host = $D('noVNC_host').value;
+ var port = $D('noVNC_port').value;
+ var password = $D('noVNC_password').value;
+ var path = $D('noVNC_path').value;
+ if ((!host) || (!port)) {
+ throw new Error("Must set host and port");
+ }
+
+ 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";
+ },
+
+ disconnect: function() {
+ UI.closeSettingsMenu();
+ UI.rfb.disconnect();
+
+ $D('noVNC_logo').style.display = "block";
+ UI.connSettingsOpen = false;
+ UI.toggleConnectPanel();
+ },
+
+ displayBlur: function() {
+ UI.rfb.get_keyboard().set_focused(false);
+ UI.rfb.get_mouse().set_focused(false);
+ },
+
+ displayFocus: function() {
+ UI.rfb.get_keyboard().set_focused(true);
+ UI.rfb.get_mouse().set_focused(true);
+ },
+
+ clipClear: function() {
+ $D('noVNC_clipboard_text').value = "";
+ UI.rfb.clipboardPasteFrom("");
+ },
+
+ clipSend: function() {
+ var text = $D('noVNC_clipboard_text').value;
+ Util.Debug(">> UI.clipSend: " + text.substr(0,40) + "...");
+ UI.rfb.clipboardPasteFrom(text);
+ Util.Debug("<< UI.clipSend");
+ },
+
+ // Enable/disable and configure viewport clipping
+ setViewClip: function(clip) {
+ var display;
+ if (UI.rfb) {
+ display = UI.rfb.get_display();
+ } else {
+ 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);
+ $D('noVNC_canvas').style.position = 'static';
+ display.viewportChange();
+ }
+ if (UI.getSetting('clip')) {
+ // If clipping, update clipping settings
+ $D('noVNC_canvas').style.position = 'absolute';
+ var pos = Util.getPosition($D('noVNC_canvas'));
+ var new_w = window.innerWidth - pos.x;
+ var new_h = window.innerHeight - pos.y;
+ display.set_viewport(true);
+ display.viewportChange(0, 0, new_w, new_h);
+ }
+ },
+
+ // Toggle/set/unset the viewport drag/move button
+ setViewDrag: function(drag) {
+ var vmb = $D('noVNC_view_drag_button');
+ if (!UI.rfb) { return; }
+
+ if (UI.rfb_state === 'normal' &&
+ UI.rfb.get_display().get_viewport()) {
+ vmb.style.display = "inline";
+ } else {
+ vmb.style.display = "none";
+ }
+
+ if (typeof(drag) === "undefined" ||
+ typeof(drag) === "object") {
+ // If not specified, then toggle
+ drag = !UI.rfb.get_viewportDrag();
+ }
+ if (drag) {
+ vmb.className = "noVNC_status_button_selected";
+ UI.rfb.set_viewportDrag(true);
+ } else {
+ vmb.className = "noVNC_status_button";
+ UI.rfb.set_viewportDrag(false);
+ }
+ },
+
+ // On touch devices, show the OS keyboard
+ showKeyboard: function() {
+ var kbi = $D('keyboardinput');
+ var skb = $D('showKeyboard');
+ 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;
+ }
+ },
+
+ keepKeyboard: function() {
+ clearTimeout(UI.hideKeyboardTimeout);
+ if(UI.keyboardVisible === true) {
+ $D('keyboardinput').focus();
+ $D('showKeyboard').className = "noVNC_status_button_selected";
+ } else if(UI.keyboardVisible === false) {
+ $D('keyboardinput').blur();
+ $D('showKeyboard').className = "noVNC_status_button";
+ }
+ },
+
+ keyboardinputReset: function() {
+ var kbi = $D('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) {
+ var newValue = event.target.value;
+ 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(function() { UI.keepKeyboard(); }, 0);
+ } else {
+ UI.lastKeyboardinput = newValue;
+ }
+ },
+
+ keyInputBlur: function() {
+ $D('showKeyboard').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.setKeyboard(); },100);
+ },
+
+ showExtraKeys: function() {
+ UI.keepKeyboard();
+ if(UI.extraKeysVisible === false) {
+ $D('toggleCtrlButton').style.display = "inline";
+ $D('toggleAltButton').style.display = "inline";
+ $D('sendTabButton').style.display = "inline";
+ $D('sendEscButton').style.display = "inline";
+ $D('showExtraKeysButton').className = "noVNC_status_button_selected";
+ UI.extraKeysVisible = true;
+ } else if(UI.extraKeysVisible === true) {
+ $D('toggleCtrlButton').style.display = "";
+ $D('toggleAltButton').style.display = "";
+ $D('sendTabButton').style.display = "";
+ $D('sendEscButton').style.display = "";
+ $D('showExtraKeysButton').className = "noVNC_status_button";
+ UI.extraKeysVisible = false;
+ }
+ },
+
+ toggleCtrl: function() {
+ UI.keepKeyboard();
+ if(UI.ctrlOn === false) {
+ UI.rfb.sendKey(XK_Control_L, true);
+ $D('toggleCtrlButton').className = "noVNC_status_button_selected";
+ UI.ctrlOn = true;
+ } else if(UI.ctrlOn === true) {
+ UI.rfb.sendKey(XK_Control_L, false);
+ $D('toggleCtrlButton').className = "noVNC_status_button";
+ UI.ctrlOn = false;
+ }
+ },
+
+ toggleAlt: function() {
+ UI.keepKeyboard();
+ if(UI.altOn === false) {
+ UI.rfb.sendKey(XK_Alt_L, true);
+ $D('toggleAltButton').className = "noVNC_status_button_selected";
+ UI.altOn = true;
+ } else if(UI.altOn === true) {
+ UI.rfb.sendKey(XK_Alt_L, false);
+ $D('toggleAltButton').className = "noVNC_status_button";
+ UI.altOn = false;
+ }
+ },
+
+ sendTab: function() {
+ UI.keepKeyboard();
+ UI.rfb.sendKey(XK_Tab);
+ },
+
+ sendEsc: function() {
+ UI.keepKeyboard();
+ UI.rfb.sendKey(XK_Escape);
+ },
+
+ setKeyboard: function() {
+ UI.keyboardVisible = false;
+ },
+
+ // iOS < Version 5 does not support position fixed. Javascript workaround:
+ setOnscroll: function() {
+ window.onscroll = function() {
+ UI.setBarPosition();
+ };
+ },
+
+ setResize: function () {
+ window.onResize = function() {
+ UI.setBarPosition();
+ };
+ },
+
+ //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_screen').style.offsetWidth;
+ $D('noVNC-control-bar').style.width = vncwidth + 'px';
+ }
+
+ };
+})();
diff --git a/webclients/novnc/include/util.js b/webclients/novnc/include/util.js
index 57ccb54..909d04b 100644
--- a/webclients/novnc/include/util.js
+++ b/webclients/novnc/include/util.js
@@ -1,14 +1,13 @@
/*
* noVNC: HTML5 VNC client
* Copyright (C) 2012 Joel Martin
- * Licensed under LGPL-3 (see LICENSE.txt)
+ * Licensed under MPL 2.0 (see LICENSE.txt)
*
* See README.md for usage and integration instructions.
*/
-"use strict";
-/*jslint bitwise: false, white: false */
-/*global window, console, document, navigator, ActiveXObject */
+/* jshint white: false, nonstandard: true */
+/*global window, console, document, navigator, ActiveXObject, INCLUDE_URI */
// Globals defined here
var Util = {};
@@ -18,61 +17,163 @@ var Util = {};
* Make arrays quack
*/
-Array.prototype.push8 = function (num) {
- this.push(num & 0xFF);
+var addFunc = function (cl, name, func) {
+ if (!cl.prototype[name]) {
+ Object.defineProperty(cl.prototype, name, { enumerable: false, value: func });
+ }
};
-Array.prototype.push16 = function (num) {
+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 );
-};
-Array.prototype.push32 = function (num) {
+ num & 0xFF);
+});
+
+addFunc(Array, 'push32', function (num) {
+ "use strict";
this.push((num >> 24) & 0xFF,
(num >> 16) & 0xFF,
(num >> 8) & 0xFF,
- (num ) & 0xFF );
-};
+ num & 0xFF);
+});
// IE does not support map (even in IE9)
//This prototype is provided by the Mozilla foundation and
//is distributed under the MIT license.
//http://www.ibiblio.org/pub/Linux/LICENSES/mit.license
-if (!Array.prototype.map)
-{
- Array.prototype.map = function(fun /*, thisp*/)
- {
+addFunc(Array, 'map', function (fun /*, thisp*/) {
+ "use strict";
var len = this.length;
- if (typeof fun != "function")
- throw new TypeError();
+ 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);
+ 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(){
- return window.requestAnimationFrame ||
- window.webkitRequestAnimationFrame ||
- window.mozRequestAnimationFrame ||
- window.oRequestAnimationFrame ||
- window.msRequestAnimationFrame ||
- function(callback){
+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
* ------------------------------------------------------
@@ -84,6 +185,7 @@ window.requestAnimFrame = (function(){
Util._log_level = 'warn';
Util.init_logging = function (level) {
+ "use strict";
if (typeof level === 'undefined') {
level = Util._log_level;
} else {
@@ -94,26 +196,34 @@ Util.init_logging = function (level) {
window.console = {
'log' : window.opera.postError,
'warn' : window.opera.postError,
- 'error': window.opera.postError };
+ 'error': window.opera.postError
+ };
} else {
window.console = {
- 'log' : function(m) {},
- 'warn' : function(m) {},
- 'error': function(m) {}};
+ '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 'debug':
+ Util.Debug = function (msg) { console.log(msg); };
+ case 'info':
+ Util.Info = function (msg) { console.log(msg); };
+ case 'warn':
+ Util.Warn = function (msg) { console.warn(msg); };
+ case 'error':
+ Util.Error = function (msg) { console.error(msg); };
case 'none':
break;
default:
- throw("invalid logging type '" + level + "'");
+ throw new Error("invalid logging type '" + level + "'");
}
+ /* jshint +W086 */
};
Util.get_logging = function () {
return Util._log_level;
@@ -121,107 +231,279 @@ Util.get_logging = function () {
// Initialize logging level
Util.init_logging();
+Util.make_property = function (proto, name, mode, type) {
+ "use strict";
-// Set configuration default for Crockford style function namespaces
-Util.conf_default = function(cfg, api, defaults, v, mode, type, defval, desc) {
- var getter, setter;
+ 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];
+ };
+ }
- // Default getter function
- getter = function (idx) {
- if ((type in {'arr':1, 'array':1}) &&
- (typeof idx !== 'undefined')) {
- return cfg[v][idx];
+ 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 cfg[v];
+ return function (val, idx) {
+ if (typeof idx !== 'undefined') {
+ this['_' + name][idx] = val;
+ } else {
+ this['_' + name] = val;
+ }
+ };
}
};
- // Default setter function
- setter = function (val, idx) {
- if (type in {'boolean':1, 'bool':1}) {
- if ((!val) || (val in {'0':1, 'no':1, 'false':1})) {
- val = false;
+ var setter;
+ if (type === 'bool') {
+ setter = make_setter(function (val) {
+ if (!val || (val in {'0': 1, 'no': 1, 'false': 1})) {
+ return false;
} else {
- val = true;
+ return true;
}
- } else if (type in {'integer':1, 'int':1}) {
- val = parseInt(val, 10);
- } else if (type === 'str') {
- val = String(val);
- } else if (type === 'func') {
+ });
+ } 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) {
- val = function () {};
+ return function () {};
+ } else {
+ return val;
}
- }
- if (typeof idx !== 'undefined') {
- cfg[v][idx] = val;
- } else {
- cfg[v] = val;
- }
- };
-
- // Set the description
- api[v + '_description'] = desc;
+ });
+ } 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 function
- if (typeof api['get_' + v] === 'undefined') {
- api['get_' + v] = getter;
+ // set the getter
+ if (typeof proto['get_' + name] === 'undefined') {
+ proto['get_' + name] = getter;
}
- // Set the setter function with extra sanity checks
- if (typeof api['set_' + v] === 'undefined') {
- api['set_' + v] = function (val, idx) {
- if (mode in {'RO':1, 'ro':1}) {
- throw(v + " is read-only");
- } else if ((mode in {'WO':1, 'wo':1}) &&
- (typeof cfg[v] !== 'undefined')) {
- throw(v + " can only be set once");
- }
- setter(val, idx);
- };
+ // 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);
+ };
+ }
}
- // Set the default value
- if (typeof defaults[v] !== 'undefined') {
- defval = defaults[v];
- } else if ((type in {'arr':1, 'array':1}) &&
- (! (defval instanceof Array))) {
- defval = [];
+ // 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]);
}
- // Coerce existing setting to the right type
- //Util.Debug("v: " + v + ", defval: " + defval + ", defaults[v]: " + defaults[v]);
- setter(defval);
};
-// Set group of configuration defaults
-Util.conf_defaults = function(cfg, api, defaults, arr) {
+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 < arr.length; i++) {
- Util.conf_default(cfg, api, defaults, arr[i][0], arr[i][1],
- arr[i][2], arr[i][3], arr[i][4]);
+ 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
*/
-// Get DOM element position on page
-Util.getPosition = function (obj) {
- var x = 0, y = 0;
- if (obj.offsetParent) {
- do {
- x += obj.offsetLeft;
- y += obj.offsetTop;
- obj = obj.offsetParent;
- } while (obj);
+
+// 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);
}
- return {'x': x, 'y': y};
};
+
+// Get DOM element position on page
+// This solution is based based on http://www.greywyvern.com/?post=331
+// Thanks to Brian Huisman AKA GreyWyvern!
+Util.getPosition = (function () {
+ "use strict";
+ function getStyle(obj, styleProp) {
+ var y;
+ if (obj.currentStyle) {
+ y = obj.currentStyle[styleProp];
+ } else if (window.getComputedStyle)
+ y = window.getComputedStyle(obj, null)[styleProp];
+ return y;
+ }
+
+ function scrollDist() {
+ var myScrollTop = 0, myScrollLeft = 0;
+ var html = document.getElementsByTagName('html')[0];
+
+ // get the scrollTop part
+ if (html.scrollTop && document.documentElement.scrollTop) {
+ myScrollTop = html.scrollTop;
+ } else if (html.scrollTop || document.documentElement.scrollTop) {
+ myScrollTop = html.scrollTop + document.documentElement.scrollTop;
+ } else if (document.body.scrollTop) {
+ myScrollTop = document.body.scrollTop;
+ } else {
+ myScrollTop = 0;
+ }
+
+ // get the scrollLeft part
+ if (html.scrollLeft && document.documentElement.scrollLeft) {
+ myScrollLeft = html.scrollLeft;
+ } else if (html.scrollLeft || document.documentElement.scrollLeft) {
+ myScrollLeft = html.scrollLeft + document.documentElement.scrollLeft;
+ } else if (document.body.scrollLeft) {
+ myScrollLeft = document.body.scrollLeft;
+ } else {
+ myScrollLeft = 0;
+ }
+
+ return [myScrollLeft, myScrollTop];
+ }
+
+ return function (obj) {
+ var curleft = 0, curtop = 0, scr = obj, fixed = false;
+ while ((scr = scr.parentNode) && scr != document.body) {
+ curleft -= scr.scrollLeft || 0;
+ curtop -= scr.scrollTop || 0;
+ if (getStyle(scr, "position") == "fixed") {
+ fixed = true;
+ }
+ }
+ if (fixed && !window.opera) {
+ var scrDist = scrollDist();
+ curleft += scrDist[0];
+ curtop += scrDist[1];
+ }
+
+ do {
+ curleft += obj.offsetLeft;
+ curtop += obj.offsetTop;
+ } while ((obj = obj.offsetParent));
+
+ return {'x': curleft, 'y': curtop};
+ };
+})();
+
+
// 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);
@@ -239,36 +521,43 @@ Util.getEventPosition = function (e, obj, scale) {
if (typeof scale === "undefined") {
scale = 1;
}
- return {'x': (docX - pos.x) / scale, 'y': (docY - pos.y) / scale};
+ var realx = docX - pos.x;
+ var realy = docY - pos.y;
+ var x = Math.max(Math.min(realx, obj.width - 1), 0);
+ var y = Math.max(Math.min(realy, obj.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){
- if (obj.attachEvent){
- var r = obj.attachEvent("on"+evType, fn);
+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);
+ } else if (obj.addEventListener) {
+ obj.addEventListener(evType, fn, false);
return true;
} else {
- throw("Handler could not be attached");
+ throw new Error("Handler could not be attached");
}
};
-Util.removeEvent = function(obj, evType, fn){
- if (obj.detachEvent){
- var r = obj.detachEvent("on"+evType, fn);
+Util.removeEvent = function (obj, evType, fn) {
+ "use strict";
+ if (obj.detachEvent) {
+ var r = obj.detachEvent("on" + evType, fn);
return r;
- } else if (obj.removeEventListener){
+ } else if (obj.removeEventListener) {
obj.removeEventListener(evType, fn, false);
return true;
} else {
- throw("Handler could not be removed");
+ throw new Error("Handler could not be removed");
}
};
-Util.stopEvent = function(e) {
+Util.stopEvent = function (e) {
+ "use strict";
if (e.stopPropagation) { e.stopPropagation(); }
else { e.cancelBubble = true; }
@@ -280,41 +569,88 @@ Util.stopEvent = function(e) {
// Set browser engine versions. Based on mootools.
Util.Features = {xpath: !!(document.evaluate), air: !!(window.runtime), query: !!(document.querySelector)};
-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': (function() { return (!window.opera) ? false : true; }()),
-
- 'trident': (function() {
- return (!window.ActiveXObject) ? false : ((window.XMLHttpRequest) ? ((document.querySelectorAll) ? 6 : 5) : 4); }()),
- 'webkit': (function() {
- try { return (navigator.taintEnabled) ? false : ((Util.Features.xpath) ? ((Util.Features.query) ? 525 : 420) : 419); } catch (e) { return false; } }()),
- //'webkit': (function() {
- // return ((typeof navigator.taintEnabled !== "unknown") && navigator.taintEnabled) ? false : ((Util.Features.xpath) ? ((Util.Features.query) ? 525 : 420) : 419); }()),
- 'gecko': (function() {
- return (!document.getBoxObjectFor && window.mozInnerScreenX == null) ? false : ((document.getElementsByClassName) ? 19 : 18); }())
-};
-if (Util.Engine.webkit) {
- // Extract actual webkit version if available
- Util.Engine.webkit = (function(v) {
- var re = new RegExp('WebKit/([0-9\.]*) ');
- v = (navigator.userAgent.match(re) || ['', v])[1];
- return parseFloat(v, 10);
- })(Util.Engine.webkit);
-}
+(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(){
+Util.Flash = (function () {
+ "use strict";
var v, version;
try {
v = navigator.plugins['Shockwave Flash'].description;
- } catch(err1) {
+ } catch (err1) {
try {
v = new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version');
- } catch(err2) {
+ } 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/webclients/novnc/include/web-socket-js/WebSocketMain.swf b/webclients/novnc/include/web-socket-js/WebSocketMain.swf
index 244c445..f286c81 100644
--- a/webclients/novnc/include/web-socket-js/WebSocketMain.swf
+++ b/webclients/novnc/include/web-socket-js/WebSocketMain.swf
Binary files differ
diff --git a/webclients/novnc/include/web-socket-js/web_socket.js b/webclients/novnc/include/web-socket-js/web_socket.js
index a133013..06cc5d0 100644
--- a/webclients/novnc/include/web-socket-js/web_socket.js
+++ b/webclients/novnc/include/web-socket-js/web_socket.js
@@ -1,49 +1,69 @@
// Copyright: Hiroshi Ichikawa <http://gimite.net/en/>
// License: New BSD License
// Reference: http://dev.w3.org/html5/websockets/
-// Reference: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol
+// Reference: http://tools.ietf.org/html/rfc6455
(function() {
- if (window.WebSocket && !window.WEB_SOCKET_FORCE_FLASH) return;
-
- var console = window.console;
- if (!console || !console.log || !console.error) {
- console = {log: function(){ }, error: function(){ }};
+ if (window.WEB_SOCKET_FORCE_FLASH) {
+ // Keeps going.
+ } else if (window.WebSocket) {
+ return;
+ } else if (window.MozWebSocket) {
+ // Firefox.
+ window.WebSocket = MozWebSocket;
+ return;
}
- if (!swfobject.hasFlashPlayerVersion("10.0.0")) {
- console.error("Flash Player >= 10.0.0 is required.");
+ var logger;
+ if (window.WEB_SOCKET_LOGGER) {
+ logger = WEB_SOCKET_LOGGER;
+ } else if (window.console && window.console.log && window.console.error) {
+ // In some environment, console is defined but console.log or console.error is missing.
+ logger = window.console;
+ } else {
+ logger = {log: function(){ }, error: function(){ }};
+ }
+
+ // swfobject.hasFlashPlayerVersion("10.0.0") doesn't work with Gnash.
+ if (swfobject.getFlashPlayerVersion().major < 10) {
+ logger.error("Flash Player >= 10.0.0 is required.");
return;
}
if (location.protocol == "file:") {
- console.error(
+ logger.error(
"WARNING: web-socket-js doesn't work in file:///... URL " +
"unless you set Flash Security Settings properly. " +
"Open the page via Web server i.e. http://...");
}
/**
- * This class represents a faux web socket.
+ * Our own implementation of WebSocket class using Flash.
* @param {string} url
- * @param {string} protocol
+ * @param {array or string} protocols
* @param {string} proxyHost
* @param {int} proxyPort
* @param {string} headers
*/
- WebSocket = function(url, protocol, proxyHost, proxyPort, headers) {
+ window.WebSocket = function(url, protocols, proxyHost, proxyPort, headers) {
var self = this;
self.__id = WebSocket.__nextId++;
WebSocket.__instances[self.__id] = self;
self.readyState = WebSocket.CONNECTING;
self.bufferedAmount = 0;
self.__events = {};
+ if (!protocols) {
+ protocols = [];
+ } else if (typeof protocols == "string") {
+ protocols = [protocols];
+ }
// Uses setTimeout() to make sure __createFlash() runs after the caller sets ws.onopen etc.
// Otherwise, when onopen fires immediately, onopen is called before it is set.
- setTimeout(function() {
+ self.__createTask = setTimeout(function() {
WebSocket.__addTask(function() {
+ self.__createTask = null;
WebSocket.__flash.create(
- self.__id, url, protocol, proxyHost || null, proxyPort || 0, headers || null);
+ self.__id, url, protocols, proxyHost || null, proxyPort || 0, headers || null);
});
}, 0);
};
@@ -78,6 +98,12 @@
* Close this web socket gracefully.
*/
WebSocket.prototype.close = function() {
+ if (this.__createTask) {
+ clearTimeout(this.__createTask);
+ this.__createTask = null;
+ this.readyState = WebSocket.CLOSED;
+ return;
+ }
if (this.readyState == WebSocket.CLOSED || this.readyState == WebSocket.CLOSING) {
return;
}
@@ -131,7 +157,7 @@
events[i](event);
}
var handler = this["on" + event.type];
- if (handler) handler(event);
+ if (handler) handler.apply(this, [event]);
};
/**
@@ -139,16 +165,22 @@
* @param {Object} flashEvent
*/
WebSocket.prototype.__handleEvent = function(flashEvent) {
+
if ("readyState" in flashEvent) {
this.readyState = flashEvent.readyState;
}
+ if ("protocol" in flashEvent) {
+ this.protocol = flashEvent.protocol;
+ }
var jsEvent;
if (flashEvent.type == "open" || flashEvent.type == "error") {
jsEvent = this.__createSimpleEvent(flashEvent.type);
} else if (flashEvent.type == "close") {
- // TODO implement jsEvent.wasClean
jsEvent = this.__createSimpleEvent("close");
+ jsEvent.wasClean = flashEvent.wasClean ? true : false;
+ jsEvent.code = flashEvent.code;
+ jsEvent.reason = flashEvent.reason;
} else if (flashEvent.type == "message") {
var data = decodeURIComponent(flashEvent.message);
jsEvent = this.__createMessageEvent("message", data);
@@ -157,6 +189,7 @@
}
this.dispatchEvent(jsEvent);
+
};
WebSocket.prototype.__createSimpleEvent = function(type) {
@@ -188,6 +221,9 @@
WebSocket.CLOSING = 2;
WebSocket.CLOSED = 3;
+ // Field to check implementation of WebSocket.
+ WebSocket.__isFlashImplementation = true;
+ WebSocket.__initialized = false;
WebSocket.__flash = null;
WebSocket.__instances = {};
WebSocket.__tasks = [];
@@ -207,16 +243,31 @@
* Loads WebSocketMain.swf and creates WebSocketMain object in Flash.
*/
WebSocket.__initialize = function() {
- if (WebSocket.__flash) return;
+
+ if (WebSocket.__initialized) return;
+ WebSocket.__initialized = true;
if (WebSocket.__swfLocation) {
// For backword compatibility.
window.WEB_SOCKET_SWF_LOCATION = WebSocket.__swfLocation;
}
if (!window.WEB_SOCKET_SWF_LOCATION) {
- console.error("[WebSocket] set WEB_SOCKET_SWF_LOCATION to location of WebSocketMain.swf");
+ logger.error("[WebSocket] set WEB_SOCKET_SWF_LOCATION to location of WebSocketMain.swf");
return;
}
+ if (!window.WEB_SOCKET_SUPPRESS_CROSS_DOMAIN_SWF_ERROR &&
+ !WEB_SOCKET_SWF_LOCATION.match(/(^|\/)WebSocketMainInsecure\.swf(\?.*)?$/) &&
+ WEB_SOCKET_SWF_LOCATION.match(/^\w+:\/\/([^\/]+)/)) {
+ var swfHost = RegExp.$1;
+ if (location.host != swfHost) {
+ logger.error(
+ "[WebSocket] You must host HTML and WebSocketMain.swf in the same host " +
+ "('" + location.host + "' != '" + swfHost + "'). " +
+ "See also 'How to host HTML file and SWF file in different domains' section " +
+ "in README.md. If you use WebSocketMainInsecure.swf, you can suppress this message " +
+ "by WEB_SOCKET_SUPPRESS_CROSS_DOMAIN_SWF_ERROR = true;");
+ }
+ }
var container = document.createElement("div");
container.id = "webSocketContainer";
// Hides Flash box. We cannot use display: none or visibility: hidden because it prevents
@@ -250,9 +301,11 @@
null,
function(e) {
if (!e.success) {
- console.error("[WebSocket] swfobject.embedSWF failed");
+ logger.error("[WebSocket] swfobject.embedSWF failed");
}
- });
+ }
+ );
+
};
/**
@@ -287,7 +340,7 @@
WebSocket.__instances[events[i].webSocketId].__handleEvent(events[i]);
}
} catch (e) {
- console.error(e);
+ logger.error(e);
}
}, 0);
return true;
@@ -295,12 +348,12 @@
// Called by Flash.
WebSocket.__log = function(message) {
- console.log(decodeURIComponent(message));
+ logger.log(decodeURIComponent(message));
};
// Called by Flash.
WebSocket.__error = function(message) {
- console.error(decodeURIComponent(message));
+ logger.error(decodeURIComponent(message));
};
WebSocket.__addTask = function(task) {
@@ -327,15 +380,12 @@
};
if (!window.WEB_SOCKET_DISABLE_AUTO_INITIALIZATION) {
- if (window.addEventListener) {
- window.addEventListener("load", function(){
- WebSocket.__initialize();
- }, false);
- } else {
- window.attachEvent("onload", function(){
- WebSocket.__initialize();
- });
- }
+ // NOTE:
+ // This fires immediately if web_socket.js is dynamically loaded after
+ // the document is loaded.
+ swfobject.addDomLoadEvent(function() {
+ WebSocket.__initialize();
+ });
}
})();
diff --git a/webclients/novnc/include/websock.js b/webclients/novnc/include/websock.js
index 20d51d6..1b89a91 100644
--- a/webclients/novnc/include/websock.js
+++ b/webclients/novnc/include/websock.js
@@ -1,7 +1,7 @@
/*
* Websock: high-performance binary WebSockets
* Copyright (C) 2012 Joel Martin
- * Licensed under LGPL-3 (see LICENSE.txt)
+ * 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)
@@ -14,7 +14,7 @@
* read binary data off of the receive queue.
*/
-/*jslint browser: true, bitwise: false, plusplus: false */
+/*jslint browser: true, bitwise: true */
/*global Util, Base64 */
@@ -35,326 +35,350 @@ if (window.WebSocket && !window.WEB_SOCKET_FORCE_FLASH) {
Websock_native = false;
(function () {
- function get_INCLUDE_URI() {
- return (typeof INCLUDE_URI !== "undefined") ?
- INCLUDE_URI : "include/";
- }
-
- var start = "<script src='" + get_INCLUDE_URI(),
- end = "'><\/script>", extra = "";
-
- window.WEB_SOCKET_SWF_LOCATION = get_INCLUDE_URI() +
+ window.WEB_SOCKET_SWF_LOCATION = Util.get_include_uri() +
"web-socket-js/WebSocketMain.swf";
if (Util.Engine.trident) {
Util.Debug("Forcing uncached load of WebSocketMain.swf");
window.WEB_SOCKET_SWF_LOCATION += "?" + Math.random();
}
- extra += start + "web-socket-js/swfobject.js" + end;
- extra += start + "web-socket-js/web_socket.js" + end;
- document.write(extra);
- }());
+ Util.load_scripts(["web-socket-js/swfobject.js",
+ "web-socket-js/web_socket.js"]);
+ })();
}
function Websock() {
-"use strict";
-
-var api = {}, // Public API
- websocket = null, // WebSocket object
- rQ = [], // Receive queue
- rQi = 0, // Receive queue index
- rQmax = 10000, // Max receive queue size before compacting
- sQ = [], // Send queue
-
- eventHandlers = {
- 'message' : function() {},
- 'open' : function() {},
- 'close' : function() {},
- 'error' : function() {}
- },
-
- test_mode = false;
-
-
-//
-// Queue public functions
-//
-
-function get_sQ() {
- return sQ;
-}
-
-function get_rQ() {
- return rQ;
-}
-function get_rQi() {
- return rQi;
-}
-function set_rQi(val) {
- rQi = val;
-}
-
-function rQlen() {
- return rQ.length - rQi;
-}
-
-function rQpeek8() {
- return (rQ[rQi] );
-}
-function rQshift8() {
- return (rQ[rQi++] );
-}
-function rQunshift8(num) {
- if (rQi === 0) {
- rQ.unshift(num);
- } else {
- rQi -= 1;
- rQ[rQi] = num;
- }
-
-}
-function rQshift16() {
- return (rQ[rQi++] << 8) +
- (rQ[rQi++] );
-}
-function rQshift32() {
- return (rQ[rQi++] << 24) +
- (rQ[rQi++] << 16) +
- (rQ[rQi++] << 8) +
- (rQ[rQi++] );
+ "use strict";
+
+ this._websocket = null; // WebSocket object
+ this._rQ = []; // Receive queue
+ this._rQi = 0; // Receive queue index
+ this._rQmax = 10000; // Max receive queue size before compacting
+ this._sQ = []; // Send queue
+
+ this._mode = 'base64'; // Current WebSocket mode: 'binary', 'base64'
+ this.maxBufferedAmount = 200;
+
+ this._eventHandlers = {
+ 'message': function () {},
+ 'open': function () {},
+ 'close': function () {},
+ 'error': function () {}
+ };
}
-function rQshiftStr(len) {
- if (typeof(len) === 'undefined') { len = rQlen(); }
- var arr = rQ.slice(rQi, rQi + len);
- rQi += len;
- return arr.map(function (num) {
- return String.fromCharCode(num); } ).join('');
-}
-function rQshiftBytes(len) {
- if (typeof(len) === 'undefined') { len = rQlen(); }
- rQi += len;
- return rQ.slice(rQi-len, rQi);
-}
+(function () {
+ "use strict";
+ 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._rQ.length - 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;
+ },
+
+ rQunshift8: function (num) {
+ if (this._rQi === 0) {
+ this._rQ.unshift(num);
+ } else {
+ this._rQi--;
+ this._rQ[this._rQi] = num;
+ }
+ },
+
+ 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 = this._rQ.slice(this._rQi, this._rQi + len);
+ this._rQi += len;
+ return String.fromCharCode.apply(null, arr);
+ },
+
+ rQshiftBytes: function (len) {
+ if (typeof(len) === 'undefined') { len = this.rQlen(); }
+ this._rQi += len;
+ return this._rQ.slice(this._rQi - len, this._rQi);
+ },
+
+ rQslice: function (start, end) {
+ if (end) {
+ return this._rQ.slice(this._rQi + start, this._rQi + end);
+ } else {
+ return this._rQ.slice(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._rQ.length - 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;
+ },
-function rQslice(start, end) {
- if (end) {
- return rQ.slice(rQi + start, rQi + end);
- } else {
- return rQ.slice(rQi + start);
- }
-}
+ // Send Queue
-// 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.
-function rQwait(msg, num, goback) {
- var rQlen = rQ.length - rQi; // Skip rQlen() function call
- if (rQlen < num) {
- if (goback) {
- if (rQi < goback) {
- throw("rQwait cannot backup " + goback + " bytes");
+ flush: function () {
+ if (this._websocket.bufferedAmount !== 0) {
+ Util.Debug("bufferedAmount: " + this._websocket.bufferedAmount);
}
- rQi -= goback;
- }
- //Util.Debug(" waiting for " + (num-rQlen) +
- // " " + msg + " byte(s)");
- return true; // true means need more data
- }
- return false;
-}
-//
-// Private utility routines
-//
+ if (this._websocket.bufferedAmount < this.maxBufferedAmount) {
+ if (this._sQ.length > 0) {
+ this._websocket.send(this._encode_message());
+ this._sQ = [];
+ }
+
+ return true;
+ } else {
+ Util.Info("Delaying send, bufferedAmount: " +
+ this._websocket.bufferedAmount);
+ return false;
+ }
+ },
+
+ send: function (arr) {
+ this._sQ = this._sQ.concat(arr);
+ return this.flush();
+ },
+
+ send_string: function (str) {
+ this.send(str.split('').map(function (chr) {
+ return chr.charCodeAt(0);
+ }));
+ },
+
+ // Event Handlers
+ on: function (evt, handler) {
+ this._eventHandlers[evt] = handler;
+ },
+
+ init: function (protocols, ws_schema) {
+ this._rQ = [];
+ this._rQi = 0;
+ this._sQ = [];
+ this._websocket = null;
+
+ // Check for full typed array support
+ var bt = false;
+ if (('Uint8Array' in window) &&
+ ('set' in Uint8Array.prototype)) {
+ bt = true;
+ }
-function encode_message() {
- /* base64 encode */
- return Base64.encode(sQ);
-}
+ // 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
+ }
-function decode_message(data) {
- //Util.Debug(">> decode_message: " + data);
- /* base64 decode */
- rQ = rQ.concat(Base64.decode(data, 0));
- //Util.Debug(">> decode_message, rQ: " + rQ);
-}
+ // Default protocols if not specified
+ if (typeof(protocols) === "undefined") {
+ if (wsbt) {
+ protocols = ['binary', 'base64'];
+ } else {
+ protocols = 'base64';
+ }
+ }
+ if (!wsbt) {
+ if (protocols === 'binary') {
+ throw new Error('WebSocket binary sub-protocol requested but not supported');
+ }
+
+ if (typeof(protocols) === 'object') {
+ var new_protocols = [];
+
+ for (var i = 0; i < protocols.length; i++) {
+ if (protocols[i] === 'binary') {
+ Util.Error('Skipping unsupported WebSocket binary sub-protocol');
+ } else {
+ new_protocols.push(protocols[i]);
+ }
+ }
+
+ if (new_protocols.length > 0) {
+ protocols = new_protocols;
+ } else {
+ throw new Error("Only WebSocket binary sub-protocol was requested and is not supported.");
+ }
+ }
+ }
-//
-// Public Send functions
-//
-
-function flush() {
- if (websocket.bufferedAmount !== 0) {
- Util.Debug("bufferedAmount: " + websocket.bufferedAmount);
- }
- if (websocket.bufferedAmount < api.maxBufferedAmount) {
- //Util.Debug("arr: " + arr);
- //Util.Debug("sQ: " + sQ);
- if (sQ.length > 0) {
- websocket.send(encode_message(sQ));
- sQ = [];
- }
- return true;
- } else {
- Util.Info("Delaying send, bufferedAmount: " +
- websocket.bufferedAmount);
- return false;
- }
-}
+ return protocols;
+ },
-// overridable for testing
-function send(arr) {
- //Util.Debug(">> send_array: " + arr);
- sQ = sQ.concat(arr);
- return flush();
-}
+ open: function (uri, protocols) {
+ var ws_schema = uri.match(/^([a-z]+):\/\//)[1];
+ protocols = this.init(protocols, ws_schema);
-function send_string(str) {
- //Util.Debug(">> send_string: " + str);
- api.send(str.split('').map(
- function (chr) { return chr.charCodeAt(0); } ) );
-}
+ this._websocket = new WebSocket(uri, protocols);
-//
-// Other public functions
-
-function recv_message(e) {
- //Util.Debug(">> recv_message: " + e.data.length);
-
- try {
- decode_message(e.data);
- if (rQlen() > 0) {
- eventHandlers.message();
- // Compact the receive queue
- if (rQ.length > rQmax) {
- //Util.Debug("Compacting receive queue");
- rQ = rQ.slice(rQi);
- rQi = 0;
+ if (protocols.indexOf('binary') >= 0) {
+ this._websocket.binaryType = 'arraybuffer';
}
- } else {
- Util.Debug("Ignoring empty message");
- }
- } catch (exc) {
- if (typeof exc.stack !== 'undefined') {
- Util.Warn("recv_message, caught exception: " + exc.stack);
- } else if (typeof exc.description !== 'undefined') {
- Util.Warn("recv_message, caught exception: " + exc.description);
- } else {
- Util.Warn("recv_message, caught exception:" + exc);
- }
- if (typeof exc.name !== 'undefined') {
- eventHandlers.error(exc.name + ": " + exc.message);
- } else {
- eventHandlers.error(exc);
- }
- }
- //Util.Debug("<< recv_message");
-}
-
-
-// Set event handlers
-function on(evt, handler) {
- eventHandlers[evt] = handler;
-}
-
-function init() {
- rQ = [];
- rQi = 0;
- sQ = [];
- websocket = null;
-}
-function open(uri) {
- init();
-
- if (test_mode) {
- websocket = {};
- } else {
- websocket = new WebSocket(uri, 'base64');
- // TODO: future native binary support
- //websocket = new WebSocket(uri, ['binary', 'base64']);
- }
-
- websocket.onmessage = recv_message;
- websocket.onopen = function() {
- Util.Debug(">> WebSock.onopen");
- if (websocket.protocol) {
- Util.Info("Server chose sub-protocol: " + websocket.protocol);
- } else {
- Util.Error("Server select no sub-protocol!: " + websocket.protocol);
+ 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 = 'base64';
+ Util.Error('Server select no sub-protocol!: ' + this._websocket.protocol);
+ }
+ 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 () {
+ if (this._mode === 'binary') {
+ // Put in a binary arraybuffer
+ return (new Uint8Array(this._sQ)).buffer;
+ } else {
+ // base64 encode
+ return Base64.encode(this._sQ);
+ }
+ },
+
+ _decode_message: function (data) {
+ if (this._mode === 'binary') {
+ // push arraybuffer values onto the end
+ var u8 = new Uint8Array(data);
+ for (var i = 0; i < u8.length; i++) {
+ this._rQ.push(u8[i]);
+ }
+ } else {
+ // base64 decode and concat to end
+ this._rQ = this._rQ.concat(Base64.decode(data, 0));
+ }
+ },
+
+ _recv_message: function (e) {
+ try {
+ this._decode_message(e.data);
+ if (this.rQlen() > 0) {
+ this._eventHandlers.message();
+ // Compact the receive queue
+ if (this._rQ.length > this._rQmax) {
+ this._rQ = this._rQ.slice(this._rQi);
+ this._rQi = 0;
+ }
+ } 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);
+ }
+ }
}
- eventHandlers.open();
- Util.Debug("<< WebSock.onopen");
- };
- websocket.onclose = function(e) {
- Util.Debug(">> WebSock.onclose");
- eventHandlers.close(e);
- Util.Debug("<< WebSock.onclose");
};
- websocket.onerror = function(e) {
- Util.Debug(">> WebSock.onerror: " + e);
- eventHandlers.error(e);
- Util.Debug("<< WebSock.onerror");
- };
-}
-
-function close() {
- if (websocket) {
- if ((websocket.readyState === WebSocket.OPEN) ||
- (websocket.readyState === WebSocket.CONNECTING)) {
- Util.Info("Closing WebSocket connection");
- websocket.close();
- }
- websocket.onmessage = function (e) { return; };
- }
-}
-
-// Override internal functions for testing
-// Takes a send function, returns reference to recv function
-function testMode(override_send) {
- test_mode = true;
- api.send = override_send;
- api.close = function () {};
- return recv_message;
-}
-
-function constructor() {
- // Configuration settings
- api.maxBufferedAmount = 200;
-
- // Direct access to send and receive queues
- api.get_sQ = get_sQ;
- api.get_rQ = get_rQ;
- api.get_rQi = get_rQi;
- api.set_rQi = set_rQi;
-
- // Routines to read from the receive queue
- api.rQlen = rQlen;
- api.rQpeek8 = rQpeek8;
- api.rQshift8 = rQshift8;
- api.rQunshift8 = rQunshift8;
- api.rQshift16 = rQshift16;
- api.rQshift32 = rQshift32;
- api.rQshiftStr = rQshiftStr;
- api.rQshiftBytes = rQshiftBytes;
- api.rQslice = rQslice;
- api.rQwait = rQwait;
-
- api.flush = flush;
- api.send = send;
- api.send_string = send_string;
-
- api.on = on;
- api.init = init;
- api.open = open;
- api.close = close;
- api.testMode = testMode;
-
- return api;
-}
-
-return constructor();
-
-}
+})();
diff --git a/webclients/novnc/include/webutil.js b/webclients/novnc/include/webutil.js
index 6722fe3..e674bf9 100644
--- a/webclients/novnc/include/webutil.js
+++ b/webclients/novnc/include/webutil.js
@@ -1,13 +1,13 @@
/*
* noVNC: HTML5 VNC client
* Copyright (C) 2012 Joel Martin
- * Licensed under LGPL-3 (see LICENSE.txt)
+ * Copyright (C) 2013 NTT corp.
+ * Licensed under MPL 2.0 (see LICENSE.txt)
*
* See README.md for usage and integration instructions.
*/
-"use strict";
-/*jslint bitwise: false, white: false */
+/*jslint bitwise: false, white: false, browser: true, devel: true */
/*global Util, window, document */
// Globals defined here
@@ -30,43 +30,47 @@ if (!window.$D) {
}
-/*
+/*
* ------------------------------------------------------
* Namespaced in WebUtil
* ------------------------------------------------------
*/
// init log level reading the logging HTTP param
-WebUtil.init_logging = function() {
- Util._log_level = (document.location.href.match(
- /logging=([A-Za-z0-9\._\-]*)/) ||
- ['', Util._log_level])[1];
-
+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.init_logging();
WebUtil.dirObj = function (obj, depth, parent) {
- var i, msg = "", val = "";
- if (! depth) { depth=2; }
- if (! parent) { parent= ""; }
-
- // Print the properties of the passed-in object
- for (i in obj) {
- if ((depth > 1) && (typeof obj[i] === "object")) {
+ "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);
+ 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) + "...";
- }
+ val = val.substr(0, 30) + "...";
+ }
msg += parent + "." + i + ": " + val + "\n";
}
}
@@ -74,10 +78,16 @@ WebUtil.dirObj = function (obj, depth, parent) {
};
// Read a query string variable
-WebUtil.getQueryVar = function(name, defVal) {
- var re = new RegExp('[?][^#]*' + name + '=([^&#]*)');
+WebUtil.getQueryVar = function (name, defVal) {
+ "use strict";
+ var re = new RegExp('.*[?&]' + name + '=([^&#]*)'),
+ match = document.location.href.match(re);
if (typeof defVal === 'undefined') { defVal = null; }
- return (document.location.href.match(re) || ['',defVal])[1];
+ if (match) {
+ return decodeURIComponent(match[1]);
+ } else {
+ return defVal;
+ }
};
@@ -86,39 +96,118 @@ WebUtil.getQueryVar = function(name, defVal) {
*/
// No days means only for this browser session
-WebUtil.createCookie = function(name,value,days) {
+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 {
+ date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
+ expires = "; expires=" + date.toGMTString();
+ } else {
expires = "";
}
- document.cookie = name+"="+value+expires+"; path=/";
+
+ var secure;
+ if (document.location.protocol === "https:") {
+ secure = "; secure";
+ } else {
+ secure = "";
+ }
+ document.cookie = name + "=" + value + expires + "; path=/" + secure;
};
-WebUtil.readCookie = function(name, defaultValue) {
- var i, c, nameEQ = name + "=", ca = document.cookie.split(';');
- for(i=0; i < ca.length; i += 1) {
- 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); }
+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) {
- WebUtil.createCookie(name,"",-1);
+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() { var i, links, sheets = [];
- links = document.getElementsByTagName("link");
- for (i = 0; i < links.length; i += 1) {
+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]);
@@ -129,14 +218,16 @@ WebUtil.getStylesheets = function() { var i, links, sheets = [];
// No sheet means try and use value from cookie, null sheet used to
// clear all alternates.
-WebUtil.selectStylesheet = function(sheet) {
- var i, link, sheets = WebUtil.getStylesheets();
+WebUtil.selectStylesheet = function (sheet) {
+ "use strict";
if (typeof sheet === 'undefined') {
sheet = 'default';
}
- for (i=0; i < sheets.length; i += 1) {
- link = sheets[i];
- if (link.title === sheet) {
+
+ 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 {
diff --git a/webclients/novnc/vnc.html b/webclients/novnc/vnc.html
index eb73c92..adb0157 100644
--- a/webclients/novnc/vnc.html
+++ b/webclients/novnc/vnc.html
@@ -1,12 +1,16 @@
-<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.1//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile11.dtd">
+<!DOCTYPE html>
<html>
<head>
<!--
noVNC example: simple example using default UI
Copyright (C) 2012 Joel Martin
- noVNC is licensed under the LGPL-3 (see LICENSE.txt)
+ 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).
+
+ Connect parameters are provided in query string:
+ http://example.com/?host=HOST&port=PORT&encrypt=1&true_color=1
-->
<title>noVNC</title>
@@ -19,7 +23,7 @@
<!-- Apple iOS Safari settings -->
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta name="apple-mobile-web-app-capable" content="yes" />
- <meta names="apple-mobile-web-app-status-bar-style" content="black-translucent" />
+ <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<!-- App Start Icon -->
<link rel="apple-touch-startup-image" href="images/screen_320x460.png" />
<!-- For iOS devices set the icon to use if user bookmarks app on their homescreen -->
@@ -39,73 +43,80 @@
src='http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js'></script>
-->
- <script src="include/vnc.js"></script>
- <script src="include/ui.js"></script>
-
</head>
<body>
<div id="noVNC-control-bar">
<!--noVNC Mobile Device only Buttons-->
<div class="noVNC-buttons-left">
- <input type="image" src="images/drag.png"
+ <input type="image" alt="viewport drag" src="images/drag.png"
id="noVNC_view_drag_button" class="noVNC_status_button"
- title="Move/Drag Viewport"
- onclick="UI.setViewDrag();">
+ title="Move/Drag Viewport">
<div id="noVNC_mobile_buttons">
- <input type="image" src="images/mouse_none.png"
- id="noVNC_mouse_button0" class="noVNC_status_button"
- onclick="UI.setMouseButton(1);">
- <input type="image" src="images/mouse_left.png"
- id="noVNC_mouse_button1" class="noVNC_status_button"
- onclick="UI.setMouseButton(2);">
- <input type="image" src="images/mouse_middle.png"
- id="noVNC_mouse_button2" class="noVNC_status_button"
- onclick="UI.setMouseButton(4);">
- <input type="image" src="images/mouse_right.png"
- id="noVNC_mouse_button4" class="noVNC_status_button"
- onclick="UI.setMouseButton(0);">
- <input type="image" src="images/keyboard.png"
+ <input type="image" alt="No mousebutton" src="images/mouse_none.png"
+ id="noVNC_mouse_button0" class="noVNC_status_button">
+ <input type="image" alt="Left mousebutton" src="images/mouse_left.png"
+ id="noVNC_mouse_button1" class="noVNC_status_button">
+ <input type="image" alt="Middle mousebutton" src="images/mouse_middle.png"
+ id="noVNC_mouse_button2" class="noVNC_status_button">
+ <input type="image" alt="Right mousebutton" src="images/mouse_right.png"
+ id="noVNC_mouse_button4" class="noVNC_status_button">
+ <input type="image" alt="Keyboard" src="images/keyboard.png"
id="showKeyboard" class="noVNC_status_button"
- value="Keyboard" title="Show Keyboard"
- onclick="UI.showKeyboard()"/>
- <input type="email"
- autocapitalize="off" autocorrect="off"
- id="keyboardinput" class="noVNC_status_button"
- onKeyDown="onKeyDown(event);" onblur="UI.keyInputBlur();"/>
+ value="Keyboard" title="Show Keyboard"/>
+ <!-- Note that Google Chrome on Android doesn't respect any of these,
+ html attributes which attempt to disable text suggestions on the
+ on-screen keyboard. Let's hope Chrome implements the ime-mode
+ style for example -->
+ <textarea id="keyboardinput" autocapitalize="off"
+ autocorrect="off" autocomplete="off" spellcheck="false"
+ mozactionhint="Enter" onsubmit="return false;"
+ style="ime-mode: disabled;"></textarea>
+ <div id="noVNC_extra_keys">
+ <input type="image" alt="Extra keys" src="images/showextrakeys.png"
+ id="showExtraKeysButton" class="noVNC_status_button">
+ <input type="image" alt="Ctrl" src="images/ctrl.png"
+ id="toggleCtrlButton" class="noVNC_status_button">
+ <input type="image" alt="Alt" src="images/alt.png"
+ id="toggleAltButton" class="noVNC_status_button">
+ <input type="image" alt="Tab" src="images/tab.png"
+ id="sendTabButton" class="noVNC_status_button">
+ <input type="image" alt="Esc" src="images/esc.png"
+ id="sendEscButton" class="noVNC_status_button">
+ </div>
</div>
</div>
+ <div id="noVNC_status">Loading</div>
+
<!--noVNC Buttons-->
<div class="noVNC-buttons-right">
- <input type="image" src="images/ctrlaltdel.png"
- id="sendCtrlAltDelButton" class="noVNC_status_button"
- title="Send Ctrl-Alt-Del"
- onclick="UI.sendCtrlAltDel();" />
- <input type="image" src="images/clipboard.png"
+ <input type="image" alt="Ctrl+Alt+Del" src="images/ctrlaltdel.png"
+ id="sendCtrlAltDelButton" class="noVNC_status_button"
+ title="Send Ctrl-Alt-Del" />
+ <input type="image" alt="Shutdown/Reboot" src="images/power.png"
+ id="xvpButton" class="noVNC_status_button"
+ title="Shutdown/Reboot..." />
+ <input type="image" alt="Clipboard" src="images/clipboard.png"
id="clipboardButton" class="noVNC_status_button"
- title="Clipboard"
- onclick="UI.toggleClipboardPanel();" />
- <input type="image" src="images/settings.png"
+ title="Clipboard" />
+ <input type="image" alt="Settings" src="images/settings.png"
id="settingsButton" class="noVNC_status_button"
- title="Settings"
- onclick="UI.toggleSettingsPanel();" />
- <input type="image" src="images/connect.png"
+ title="Settings" />
+ <input type="image" alt="Connect" src="images/connect.png"
id="connectButton" class="noVNC_status_button"
- title="Connect"
- onclick="UI.toggleConnectPanel()" />
- <input type="image" src="images/disconnect.png"
+ title="Connect" />
+ <input type="image" alt="Disconnect" src="images/disconnect.png"
id="disconnectButton" class="noVNC_status_button"
- title="Disconnect"
- onclick="UI.disconnect()" />
+ title="Disconnect" />
</div>
<!-- Description Panel -->
<!-- Shown by default when hosted at for kanaka.github.com -->
- <div id="noVNC_description" style="display:none;" class="">
+ <div id="noVNC_description" class="">
noVNC is a browser based VNC client implemented using HTML5 Canvas
and WebSockets. You will either need a VNC server with WebSockets
- support (such as <a href="http://libvnc.gtihub.io">libvncserver</a>)
+ support (such as <a href="http://libvncserver.sourceforge.net/">libvncserver</a>)
or you will need to use
<a href="https://github.com/kanaka/websockify">websockify</a>
to bridge between your browser and VNC server. See the noVNC
@@ -113,25 +124,34 @@
and <a href="http://kanaka.github.com/noVNC">website</a>
for more information.
<br />
- <input type="button" value="Close"
- onclick="UI.toggleConnectPanel();">
+ <input id="descriptionButton" type="button" value="Close">
+ </div>
+
+ <!-- Popup Status Panel -->
+ <div id="noVNC_popup_status_panel" class="">
</div>
<!-- Clipboard Panel -->
<div id="noVNC_clipboard" class="triangle-right top">
- <textarea id="noVNC_clipboard_text" rows=5
- onfocus="UI.displayBlur();" onblur="UI.displayFocus();"
- onchange="UI.clipSend();">
+ <textarea id="noVNC_clipboard_text" rows=5>
</textarea>
<br />
<input id="noVNC_clipboard_clear_button" type="button"
- value="Clear" onclick="UI.clipClear();">
+ value="Clear">
+ </div>
+
+ <!-- XVP Shutdown/Reboot Panel -->
+ <div id="noVNC_xvp" class="triangle-right top">
+ <span id="noVNC_xvp_menu">
+ <input type="button" id="xvpShutdownButton" value="Shutdown" />
+ <input type="button" id="xvpRebootButton" value="Reboot" />
+ <input type="button" id="xvpResetButton" value="Reset" />
+ </span>
</div>
<!-- Settings Panel -->
<div id="noVNC_settings" class="triangle-right top">
- <span id="noVNC_settings_menu" onmouseover="UI.displayBlur();"
- onmouseout="UI.displayFocus();">
+ <span id="noVNC_settings_menu">
<ul>
<li><input id="noVNC_encrypt" type="checkbox"> Encrypt</li>
<li><input id="noVNC_true_color" type="checkbox" checked> True Color</li>
@@ -139,7 +159,6 @@
<li><input id="noVNC_clip" type="checkbox"> Clip to Window</li>
<li><input id="noVNC_shared" type="checkbox"> Shared Mode</li>
<li><input id="noVNC_view_only" type="checkbox"> View Only</li>
- <li><input id="noVNC_connectTimeout" type="input"> Connect Timeout (s)</li>
<li><input id="noVNC_path" type="input" value="websockify"> Path</li>
<li><input id="noVNC_repeaterID" type="input" value=""> Repeater ID</li>
<hr>
@@ -156,8 +175,7 @@
</select></label>
</li>
<hr>
- <li><input type="button" id="noVNC_apply" value="Apply"
- onclick="UI.settingsApply()"></li>
+ <li><input type="button" id="noVNC_apply" value="Apply"></li>
</ul>
</span>
</div>
@@ -168,7 +186,7 @@
<li><label><strong>Host: </strong><input id="noVNC_host" /></label></li>
<li><label><strong>Port: </strong><input id="noVNC_port" /></label></li>
<li><label><strong>Password: </strong><input id="noVNC_password" type="password" /></label></li>
- <li><input id="noVNC_connect_button" type="button" value="Connect" onclick="UI.connect();"></li>
+ <li><input id="noVNC_connect_button" type="button" value="Connect"></li>
</ul>
</div>
@@ -178,10 +196,6 @@
<div id="noVNC_screen">
<div id="noVNC_screen_pad"></div>
- <div id="noVNC_status_bar" class="noVNC_status_bar">
- <div id="noVNC_status">Loading</div>
- </div>
-
<h1 id="noVNC_logo"><span>no</span><br />VNC</h1>
<!-- HTML5 Canvas -->
@@ -192,9 +206,8 @@
</div>
</div>
+ <script src="include/util.js"></script>
+ <script src="include/ui.js"></script>
- <script>
- window.onload = UI.load;
- </script>
</body>
</html>
diff --git a/webclients/novnc/vnc_auto.html b/webclients/novnc/vnc_auto.html
index 2b60d77..ff376fe 100644
--- a/webclients/novnc/vnc_auto.html
+++ b/webclients/novnc/vnc_auto.html
@@ -1,33 +1,66 @@
<!DOCTYPE html>
<html>
- <!--
+<head>
+
+ <!--
noVNC example: simple example using default UI
Copyright (C) 2012 Joel Martin
- noVNC is licensed under the LGPL-3 (see LICENSE.txt)
+ 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).
Connect parameters are provided in query string:
http://example.com/?host=HOST&port=PORT&encrypt=1&true_color=1
-->
- <head>
- <title>noVNC</title>
- <meta http-equiv="X-UA-Compatible" content="chrome=1">
- <link rel="stylesheet" href="include/base.css" title="plain">
- <!--
- <script type='text/javascript'
- src='http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js'></script>
- -->
- <script src="include/vnc.js"></script>
- </head>
-
- <body style="margin: 0px;">
- <div id="noVNC_screen">
+ <title>noVNC</title>
+
+ <meta charset="utf-8">
+
+ <!-- Always force latest IE rendering engine (even in intranet) & Chrome Frame
+ Remove this if you use the .htaccess -->
+ <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+
+ <!-- Apple iOS Safari settings -->
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
+ <meta name="apple-mobile-web-app-capable" content="yes" />
+ <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
+ <!-- App Start Icon -->
+ <link rel="apple-touch-startup-image" href="images/screen_320x460.png" />
+ <!-- For iOS devices set the icon to use if user bookmarks app on their homescreen -->
+ <link rel="apple-touch-icon" href="images/screen_57x57.png">
+ <!--
+ <link rel="apple-touch-icon-precomposed" href="images/screen_57x57.png" />
+ -->
+
+
+ <!-- Stylesheets -->
+ <link rel="stylesheet" href="include/base.css" title="plain">
+
+ <!--
+ <script type='text/javascript'
+ src='http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js'></script>
+ -->
+ <script src="include/util.js"></script>
+</head>
+
+<body style="margin: 0px;">
+ <div id="noVNC_screen">
<div id="noVNC_status_bar" class="noVNC_status_bar" style="margin-top: 0px;">
<table border=0 width="100%"><tr>
- <td><div id="noVNC_status">Loading</div></td>
+ <td><div id="noVNC_status" style="position: relative; height: auto;">
+ Loading
+ </div></td>
<td width="1%"><div id="noVNC_buttons">
<input type=button value="Send CtrlAltDel"
id="sendCtrlAltDelButton">
+ <span id="noVNC_xvp_buttons">
+ <input type=button value="Shutdown"
+ id="xvpShutdownButton">
+ <input type=button value="Reboot"
+ id="xvpRebootButton">
+ <input type=button value="Reset"
+ id="xvpResetButton">
+ </span>
</div></td>
</tr></table>
</div>
@@ -41,6 +74,11 @@
/*global window, $, Util, RFB, */
"use strict";
+ // Load supporting scripts
+ Util.load_scripts(["webutil.js", "base64.js", "websock.js", "des.js",
+ "keysymdef.js", "keyboard.js", "input.js", "display.js",
+ "jsunzip.js", "rfb.js"]);
+
var rfb;
function passwordRequired(rfb) {
@@ -61,6 +99,18 @@
rfb.sendCtrlAltDel();
return false;
}
+ function xvpShutdown() {
+ rfb.xvpShutdown();
+ return false;
+ }
+ function xvpReboot() {
+ rfb.xvpReboot();
+ return false;
+ }
+ function xvpReset() {
+ rfb.xvpReset();
+ return false;
+ }
function updateState(rfb, state, oldstate, msg) {
var s, sb, cad, level;
s = $D('noVNC_status');
@@ -75,8 +125,12 @@
default: level = "warn"; break;
}
- if (state === "normal") { cad.disabled = false; }
- else { cad.disabled = true; }
+ if (state === "normal") {
+ cad.disabled = false;
+ } else {
+ cad.disabled = true;
+ xvpInit(0);
+ }
if (typeof(msg) !== 'undefined') {
sb.setAttribute("class", "noVNC_status_" + level);
@@ -84,17 +138,42 @@
}
}
- window.onload = function () {
+ function xvpInit(ver) {
+ var xvpbuttons;
+ xvpbuttons = $D('noVNC_xvp_buttons');
+ if (ver >= 1) {
+ xvpbuttons.style.display = 'inline';
+ } else {
+ xvpbuttons.style.display = 'none';
+ }
+ }
+
+ window.onscriptsload = function () {
var host, port, password, path, token;
$D('sendCtrlAltDelButton').style.display = "inline";
$D('sendCtrlAltDelButton').onclick = sendCtrlAltDel;
+ $D('xvpShutdownButton').onclick = xvpShutdown;
+ $D('xvpRebootButton').onclick = xvpReboot;
+ $D('xvpResetButton').onclick = xvpReset;
+ WebUtil.init_logging(WebUtil.getQueryVar('logging', 'warn'));
document.title = unescape(WebUtil.getQueryVar('title', 'noVNC'));
// By default, use the host and port of server that served this file
host = WebUtil.getQueryVar('host', window.location.hostname);
port = WebUtil.getQueryVar('port', window.location.port);
+ // if port == 80 (or 443) then it won't be present and should be
+ // set manually
+ if (!port) {
+ if (window.location.protocol.substring(0,5) == 'https') {
+ port = 443;
+ }
+ else if (window.location.protocol.substring(0,4) == 'http') {
+ port = 80;
+ }
+ }
+
// If a token variable is passed in, set the parameter in a cookie.
// This is used by nova-novncproxy.
token = WebUtil.getQueryVar('token', null);
@@ -119,7 +198,8 @@
'local_cursor': WebUtil.getQueryVar('cursor', true),
'shared': WebUtil.getQueryVar('shared', true),
'view_only': WebUtil.getQueryVar('view_only', false),
- 'updateState': updateState,
+ 'onUpdateState': updateState,
+ 'onXvpInit': xvpInit,
'onPasswordRequired': passwordRequired});
rfb.connect(host, port, password, path);
};
@@ -127,4 +207,3 @@
</body>
</html>
-