diff --git a/LibVNCServer.spec.in b/LibVNCServer.spec.in
index 3e93334..13fe351 100755
--- a/LibVNCServer.spec.in
+++ b/LibVNCServer.spec.in
@@ -59,7 +59,7 @@ make
%makeinstall includedir="%{buildroot}%{_includedir}/rfb"
%{__install} -d -m0755 %{buildroot}%{_datadir}/x11vnc/classes
-%{__install} classes/VncViewer.jar classes/index.vnc \
+%{__install} webclients/VncViewer.jar webclients/index.vnc \
%{buildroot}%{_datadir}/x11vnc/classes
%clean
diff --git a/Makefile.am b/Makefile.am
index 0125b5b..e244fe8 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -2,8 +2,8 @@ if WITH_X11VNC
X11VNC=x11vnc
endif
-SUBDIRS=libvncserver examples libvncclient vncterm classes client_examples test $(X11VNC)
-DIST_SUBDIRS=libvncserver examples libvncclient vncterm classes client_examples test
+SUBDIRS=libvncserver examples libvncclient vncterm webclients client_examples test $(X11VNC)
+DIST_SUBDIRS=libvncserver examples libvncclient vncterm webclients client_examples test
EXTRA_DIST = CMakeLists.txt rfb/rfbint.h.cmake rfb/rfbconfig.h.cmake
bin_SCRIPTS = libvncserver-config
diff --git a/README b/README
index f062225..499b72b 100644
--- a/README
+++ b/README
@@ -163,7 +163,7 @@ If you already have a socket to talk to, just set rfbScreen->inetdSock
To also start an HTTP server (running on port 5800+display_number), you have
to set rfbScreen->httpdDir to a directory containing vncviewer.jar and
-index.vnc (like the included "classes" directory).
+index.vnc (like the included "webclients" directory).
Hooks and IO functions
----------------------
diff --git a/classes/Makefile.am b/classes/Makefile.am
deleted file mode 100644
index c5497a8..0000000
--- a/classes/Makefile.am
+++ /dev/null
@@ -1,5 +0,0 @@
-EXTRA_DIST=VncViewer.jar index.vnc javaviewer.pseudo_proxy.patch
-
-SUBDIRS = ssl
-DIST_SUBDIRS = ssl
-
diff --git a/classes/index.vnc b/classes/index.vnc
deleted file mode 100644
index 63b2f56..0000000
--- a/classes/index.vnc
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
-$USER's $DESKTOP desktop ($DISPLAY)
-
-
-
-www.TightVNC.com
-
diff --git a/configure.ac b/configure.ac
index bffd537..8a598b0 100644
--- a/configure.ac
+++ b/configure.ac
@@ -916,8 +916,9 @@ AC_CONFIG_FILES([Makefile
examples/Makefile
examples/android/Makefile
vncterm/Makefile
- classes/Makefile
- classes/ssl/Makefile
+ webclients/Makefile
+ webclients/java-applet/Makefile
+ webclients/java-applet/ssl/Makefile
libvncclient/Makefile
client_examples/Makefile
test/Makefile
diff --git a/examples/example.c b/examples/example.c
index 93fdf28..fc156c0 100644
--- a/examples/example.c
+++ b/examples/example.c
@@ -288,7 +288,7 @@ int main(int argc,char** argv)
rfbScreen->ptrAddEvent = doptr;
rfbScreen->kbdAddEvent = dokey;
rfbScreen->newClientHook = newclient;
- rfbScreen->httpDir = "../classes";
+ rfbScreen->httpDir = "../webclients";
rfbScreen->httpEnableProxyConnect = TRUE;
initBuffer((unsigned char*)rfbScreen->frameBuffer);
diff --git a/examples/pnmshow.c b/examples/pnmshow.c
index 6ced92a..dbb66ab 100644
--- a/examples/pnmshow.c
+++ b/examples/pnmshow.c
@@ -75,7 +75,7 @@ int main(int argc,char** argv)
rfbScreen->kbdAddEvent = HandleKey;
/* enable http */
- rfbScreen->httpDir = "../classes";
+ rfbScreen->httpDir = "../webclients";
/* allocate picture and read it */
rfbScreen->frameBuffer = (char*)malloc(paddedWidth*bytesPerPixel*height);
diff --git a/examples/pnmshow24.c b/examples/pnmshow24.c
index 81389d7..0c772ea 100644
--- a/examples/pnmshow24.c
+++ b/examples/pnmshow24.c
@@ -70,7 +70,7 @@ int main(int argc,char** argv)
rfbScreen->kbdAddEvent = HandleKey;
/* enable http */
- rfbScreen->httpDir = "../classes";
+ rfbScreen->httpDir = "../webclients";
/* allocate picture and read it */
rfbScreen->frameBuffer = (char*)malloc(paddedWidth*3*height);
diff --git a/libvncserver/httpd.c b/libvncserver/httpd.c
index ad2a51b..3025aae 100644
--- a/libvncserver/httpd.c
+++ b/libvncserver/httpd.c
@@ -59,20 +59,6 @@
#include
#endif
-#define connection_close
-#ifndef connection_close
-
-#define NOT_FOUND_STR "HTTP/1.0 404 Not found\r\n\r\n" \
- "File Not Found\n" \
- "File Not Found
\n"
-
-#define INVALID_REQUEST_STR "HTTP/1.0 400 Invalid Request\r\n\r\n" \
- "Invalid Request\n" \
- "Invalid request
\n"
-
-#define OK_STR "HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n"
-
-#else
#define NOT_FOUND_STR "HTTP/1.0 404 Not found\r\nConnection: close\r\n\r\n" \
"File Not Found\n" \
@@ -82,9 +68,10 @@
"Invalid Request\n" \
"Invalid request
\n"
-#define OK_STR "HTTP/1.0 200 OK\r\nConnection: close\r\nContent-Type: text/html\r\n\r\n"
+#define OK_STR "HTTP/1.0 200 OK\r\nConnection: close\r\n\r\n"
+#define OK_STR_HTML "HTTP/1.0 200 OK\r\nConnection: close\r\nContent-Type: text/html\r\n\r\n"
+
-#endif
static void httpProcessInput(rfbScreenInfoPtr screen);
static rfbBool compareAndSkip(char **ptr, const char *str);
@@ -346,12 +333,6 @@ httpProcessInput(rfbScreenInfoPtr rfbScreen)
return;
}
- if (strchr(fname+1, '/') != NULL) {
- rfbErr("httpd: asking for file in other directory\n");
- rfbWriteExact(&cl, NOT_FOUND_STR, strlen(NOT_FOUND_STR));
- httpCloseSock(rfbScreen);
- return;
- }
getpeername(rfbScreen->httpSock, (struct sockaddr *)&addr, &addrlen);
rfbLog("httpd: get '%s' for %s\n", fname+1,
@@ -392,7 +373,10 @@ httpProcessInput(rfbScreenInfoPtr rfbScreen)
return;
}
- rfbWriteExact(&cl, OK_STR, strlen(OK_STR));
+ if(performSubstitutions) /* is the 'index.vnc' file */
+ rfbWriteExact(&cl, OK_STR_HTML, strlen(OK_STR_HTML));
+ else
+ rfbWriteExact(&cl, OK_STR, strlen(OK_STR));
while (1) {
int n = fread(buf, 1, BUF_SIZE-1, fd);
diff --git a/rfb/rfb.h b/rfb/rfb.h
index 004383d..3317e54 100644
--- a/rfb/rfb.h
+++ b/rfb/rfb.h
@@ -1097,7 +1097,7 @@ rfbBool rfbUpdateClient(rfbClientPtr cl);
To also start an HTTP server (running on port 5800+display_number), you have
to set rfbScreenInfo::httpDir to a directory containing vncviewer.jar and
- index.vnc (like the included "classes" directory).
+ index.vnc (like the included "webclients" directory).
@section making_it_interactive Making it interactive
diff --git a/webclients/Makefile.am b/webclients/Makefile.am
new file mode 100644
index 0000000..6c2db84
--- /dev/null
+++ b/webclients/Makefile.am
@@ -0,0 +1,4 @@
+SUBDIRS = java-applet
+DIST_SUBDIRS = java-applet
+EXTRA_DIST=index.vnc novnc
+
diff --git a/webclients/index.vnc b/webclients/index.vnc
new file mode 100644
index 0000000..8254a70
--- /dev/null
+++ b/webclients/index.vnc
@@ -0,0 +1,37 @@
+
+
+
+
+
+$USER's $DESKTOP desktop ($DISPLAY)
+
+
+
+
+
+
+If the above Java applet does not work, you can also try the new JavaScript-only noVNC viewer. You will need a HTML5-capable browser though.
+
+
+
+
+
+
+LibVNCServer/LibVNCClient Homepage
+
diff --git a/webclients/java-applet/Makefile.am b/webclients/java-applet/Makefile.am
new file mode 100644
index 0000000..d6d10e4
--- /dev/null
+++ b/webclients/java-applet/Makefile.am
@@ -0,0 +1,5 @@
+EXTRA_DIST=VncViewer.jar javaviewer.pseudo_proxy.patch
+
+SUBDIRS = ssl
+DIST_SUBDIRS = ssl
+
diff --git a/classes/VncViewer.jar b/webclients/java-applet/VncViewer.jar
similarity index 100%
rename from classes/VncViewer.jar
rename to webclients/java-applet/VncViewer.jar
diff --git a/classes/javaviewer.pseudo_proxy.patch b/webclients/java-applet/javaviewer.pseudo_proxy.patch
similarity index 100%
rename from classes/javaviewer.pseudo_proxy.patch
rename to webclients/java-applet/javaviewer.pseudo_proxy.patch
diff --git a/classes/ssl/Makefile.am b/webclients/java-applet/ssl/Makefile.am
similarity index 100%
rename from classes/ssl/Makefile.am
rename to webclients/java-applet/ssl/Makefile.am
diff --git a/classes/ssl/README b/webclients/java-applet/ssl/README
similarity index 100%
rename from classes/ssl/README
rename to webclients/java-applet/ssl/README
diff --git a/classes/ssl/SignedUltraViewerSSL.jar b/webclients/java-applet/ssl/SignedUltraViewerSSL.jar
similarity index 100%
rename from classes/ssl/SignedUltraViewerSSL.jar
rename to webclients/java-applet/ssl/SignedUltraViewerSSL.jar
diff --git a/classes/ssl/SignedVncViewer.jar b/webclients/java-applet/ssl/SignedVncViewer.jar
similarity index 100%
rename from classes/ssl/SignedVncViewer.jar
rename to webclients/java-applet/ssl/SignedVncViewer.jar
diff --git a/classes/ssl/UltraViewerSSL.jar b/webclients/java-applet/ssl/UltraViewerSSL.jar
similarity index 100%
rename from classes/ssl/UltraViewerSSL.jar
rename to webclients/java-applet/ssl/UltraViewerSSL.jar
diff --git a/classes/ssl/VncViewer.jar b/webclients/java-applet/ssl/VncViewer.jar
similarity index 100%
rename from classes/ssl/VncViewer.jar
rename to webclients/java-applet/ssl/VncViewer.jar
diff --git a/classes/ssl/index.vnc b/webclients/java-applet/ssl/index.vnc
similarity index 100%
rename from classes/ssl/index.vnc
rename to webclients/java-applet/ssl/index.vnc
diff --git a/classes/ssl/onetimekey b/webclients/java-applet/ssl/onetimekey
similarity index 100%
rename from classes/ssl/onetimekey
rename to webclients/java-applet/ssl/onetimekey
diff --git a/classes/ssl/proxy.vnc b/webclients/java-applet/ssl/proxy.vnc
similarity index 100%
rename from classes/ssl/proxy.vnc
rename to webclients/java-applet/ssl/proxy.vnc
diff --git a/classes/ssl/ss_vncviewer b/webclients/java-applet/ssl/ss_vncviewer
similarity index 100%
rename from classes/ssl/ss_vncviewer
rename to webclients/java-applet/ssl/ss_vncviewer
diff --git a/classes/ssl/tightvnc-1.3dev7_javasrc-vncviewer-cursor-colors+no-tab-traversal.patch b/webclients/java-applet/ssl/tightvnc-1.3dev7_javasrc-vncviewer-cursor-colors+no-tab-traversal.patch
similarity index 100%
rename from classes/ssl/tightvnc-1.3dev7_javasrc-vncviewer-cursor-colors+no-tab-traversal.patch
rename to webclients/java-applet/ssl/tightvnc-1.3dev7_javasrc-vncviewer-cursor-colors+no-tab-traversal.patch
diff --git a/classes/ssl/tightvnc-1.3dev7_javasrc-vncviewer-ssl.patch b/webclients/java-applet/ssl/tightvnc-1.3dev7_javasrc-vncviewer-ssl.patch
similarity index 100%
rename from classes/ssl/tightvnc-1.3dev7_javasrc-vncviewer-ssl.patch
rename to webclients/java-applet/ssl/tightvnc-1.3dev7_javasrc-vncviewer-ssl.patch
diff --git a/classes/ssl/ultra.vnc b/webclients/java-applet/ssl/ultra.vnc
similarity index 100%
rename from classes/ssl/ultra.vnc
rename to webclients/java-applet/ssl/ultra.vnc
diff --git a/classes/ssl/ultraproxy.vnc b/webclients/java-applet/ssl/ultraproxy.vnc
similarity index 100%
rename from classes/ssl/ultraproxy.vnc
rename to webclients/java-applet/ssl/ultraproxy.vnc
diff --git a/classes/ssl/ultrasigned.vnc b/webclients/java-applet/ssl/ultrasigned.vnc
similarity index 100%
rename from classes/ssl/ultrasigned.vnc
rename to webclients/java-applet/ssl/ultrasigned.vnc
diff --git a/classes/ssl/ultravnc-102-JavaViewer-ssl-etc.patch b/webclients/java-applet/ssl/ultravnc-102-JavaViewer-ssl-etc.patch
similarity index 100%
rename from classes/ssl/ultravnc-102-JavaViewer-ssl-etc.patch
rename to webclients/java-applet/ssl/ultravnc-102-JavaViewer-ssl-etc.patch
diff --git a/webclients/novnc/LICENSE.txt b/webclients/novnc/LICENSE.txt
new file mode 100644
index 0000000..755ace3
--- /dev/null
+++ b/webclients/novnc/LICENSE.txt
@@ -0,0 +1,33 @@
+noVNC is Copyright (C) 2011 Joel Martin
+
+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
+
+noVNC is licensed under the LGPL (GNU Lesser General Public License)
+version 3 with the following exceptions (all LGPL-3 compatible):
+
+ include/input.js : LGPL-2 or any later version
+
+ include/base64.js : Dual GPL-2 or LGPL-2.1
+
+ include/des.js : Various BSD style licenses
+
+ include/web-socket-js/ : New BSD license. Source code at
+ http://github.com/gimite/web-socket-js
+
+ include/Orbitron* : SIL Open Font License 1.1
+ (Copyright 2009 Matt McInerney)
+
+ images/ : Creative Commons Attribution-ShareAlike
+ http://creativecommons.org/licenses/by-sa/3.0/
+
+The license texts are included at:
+ docs/LICENSE.LGPL-3 and
+ docs/LICENSE.GPL-3
+ docs/LICENSE.OFL-1.1
+
+Or alternatively the license texts may be found here:
+ http://www.gnu.org/licenses/lgpl.html and
+ http://www.gnu.org/licenses/gpl.html
+ http://scripts.sil.org/OFL
diff --git a/webclients/novnc/README.md b/webclients/novnc/README.md
new file mode 100644
index 0000000..4672969
--- /dev/null
+++ b/webclients/novnc/README.md
@@ -0,0 +1,93 @@
+## noVNC: HTML5 VNC Client
+
+
+### Description
+
+noVNC is a VNC client implemented using HTML5 technologies,
+specifically Canvas and WebSockets (supports 'wss://' encryption).
+noVNC is licensed under the
+[LGPLv3](http://www.gnu.org/licenses/lgpl.html).
+
+Special thanks to [Sentry Data Systems](http://www.sentryds.com) for
+sponsoring ongoing development of this project (and for employing me).
+
+There are many companies/projects that have integrated noVNC into
+their products including: [Sentry Data Systems](http://www.sentryds.com), [Ganeti Web Manager](http://code.osuosl.org/projects/ganeti-webmgr), [Archipel](http://archipelproject.org), [openQRM](http://www.openqrm.com/), [OpenNode](http://www.opennodecloud.com/), [OpenStack](http://www.openstack.org), [Broadway (HTML5 GDK/GTK+ backend)](http://blogs.gnome.org/alexl/2011/03/15/gtk-html-backend-update/), [OpenNebula](http://opennebula.org/), [CloudSigma](http://www.cloudsigma.com/), [Zentyal (formerly eBox)](http://www.zentyal.org/), and [SlapOS](http://www.slapos.org). See [this wiki page](https://github.com/kanaka/noVNC/wiki/ProjectsCompanies-using-noVNC) for more info and links.
+
+Notable commits, announcements and news are posted to
+@noVNC
+
+
+### Screenshots
+
+Running in Chrome before and after connecting:
+
+
+
+See more screenshots here.
+
+
+### Browser Requirements
+
+* HTML5 Canvas (with createImageData): Chrome, Firefox 3.6+, iOS
+ Safari, Opera 11+, Internet Explorer 9+, etc.
+
+* HTML5 WebSockets: For browsers that do not have builtin
+ WebSockets support, the project includes
+ web-socket-js,
+ a WebSockets emulator using Adobe Flash. iOS 4.2+ has built-in
+ WebSocket support.
+
+* Fast Javascript Engine: noVNC avoids using new Javascript
+ functionality so it will run on older browsers, but decode and
+ rendering happen in Javascript, so a slow Javascript engine will
+ mean noVNC is painfully slow.
+
+* I maintain a more detailed browser compatibility list here.
+
+
+### Server Requirements
+
+Unless you are using a VNC server with support for WebSockets
+connections (only my [fork of libvncserver](http://github.com/kanaka/libvncserver)
+currently), you need to use a WebSockets to TCP socket proxy. There is
+a python proxy included ('websockify'). One advantage of using the
+proxy is that it has builtin support for SSL/TLS encryption (i.e.
+"wss://").
+
+There a few reasons why a proxy is required:
+
+ 1. WebSockets is not a pure socket protocol. There is an initial HTTP
+ like handshake to allow easy hand-off by web servers and allow
+ some origin policy exchange. Also, each WebSockets frame begins
+ with 0 ('\x00') and ends with 255 ('\xff').
+
+ 2. Javascript itself does not have the ability to handle pure byte
+ arrays. The python proxy encodes the data as base64 so that the
+ Javascript client can decode the data as an integer array.
+
+
+### Quick Start
+
+* Use the launch script to start a mini-webserver and the WebSockets
+ proxy (websockify). The `--vnc` option is used to specify the location of
+ a running VNC server:
+
+ `./utils/launch.sh --vnc localhost:5901`
+
+* Point your browser to the cut-and-paste URL that is output by the
+ launch script. Enter a password if the VNC server has one
+ configured. Hit the Connect button and enjoy!
+
+
+### Other Pages
+
+* [Advanced Usage](https://github.com/kanaka/noVNC/wiki/Advanced-usage). Generating an SSL
+ certificate, starting a VNC server, advanced websockify usage, etc.
+
+* [Integrating noVNC](https://github.com/kanaka/noVNC/wiki/Integration) into existing projects.
+
+* [Troubleshooting noVNC](https://github.com/kanaka/noVNC/wiki/Troubleshooting) problems.
+
+
diff --git a/webclients/novnc/favicon.ico b/webclients/novnc/favicon.ico
new file mode 120000
index 0000000..45399c8
--- /dev/null
+++ b/webclients/novnc/favicon.ico
@@ -0,0 +1 @@
+images/favicon.ico
\ No newline at end of file
diff --git a/webclients/novnc/images/clipboard.png b/webclients/novnc/images/clipboard.png
new file mode 100644
index 0000000..24df33c
Binary files /dev/null and b/webclients/novnc/images/clipboard.png differ
diff --git a/webclients/novnc/images/connect.png b/webclients/novnc/images/connect.png
new file mode 100644
index 0000000..79e71ad
Binary files /dev/null and b/webclients/novnc/images/connect.png differ
diff --git a/webclients/novnc/images/ctrlaltdel.png b/webclients/novnc/images/ctrlaltdel.png
new file mode 100644
index 0000000..31922e5
Binary files /dev/null and b/webclients/novnc/images/ctrlaltdel.png differ
diff --git a/webclients/novnc/images/disconnect.png b/webclients/novnc/images/disconnect.png
new file mode 100644
index 0000000..8832f5e
Binary files /dev/null and b/webclients/novnc/images/disconnect.png differ
diff --git a/webclients/novnc/images/drag.png b/webclients/novnc/images/drag.png
new file mode 100644
index 0000000..433f896
Binary files /dev/null and b/webclients/novnc/images/drag.png differ
diff --git a/webclients/novnc/images/favicon.ico b/webclients/novnc/images/favicon.ico
new file mode 100644
index 0000000..c999634
Binary files /dev/null and b/webclients/novnc/images/favicon.ico differ
diff --git a/webclients/novnc/images/favicon.png b/webclients/novnc/images/favicon.png
new file mode 100644
index 0000000..e2bdb19
Binary files /dev/null and b/webclients/novnc/images/favicon.png differ
diff --git a/webclients/novnc/images/keyboard.png b/webclients/novnc/images/keyboard.png
new file mode 100644
index 0000000..f797952
Binary files /dev/null and b/webclients/novnc/images/keyboard.png differ
diff --git a/webclients/novnc/images/mouse_left.png b/webclients/novnc/images/mouse_left.png
new file mode 100644
index 0000000..1de7a48
Binary files /dev/null and b/webclients/novnc/images/mouse_left.png differ
diff --git a/webclients/novnc/images/mouse_middle.png b/webclients/novnc/images/mouse_middle.png
new file mode 100644
index 0000000..81fbd9b
Binary files /dev/null and b/webclients/novnc/images/mouse_middle.png differ
diff --git a/webclients/novnc/images/mouse_none.png b/webclients/novnc/images/mouse_none.png
new file mode 100644
index 0000000..93dbf57
Binary files /dev/null and b/webclients/novnc/images/mouse_none.png differ
diff --git a/webclients/novnc/images/mouse_right.png b/webclients/novnc/images/mouse_right.png
new file mode 100644
index 0000000..355b25d
Binary files /dev/null and b/webclients/novnc/images/mouse_right.png differ
diff --git a/webclients/novnc/images/screen_320x460.png b/webclients/novnc/images/screen_320x460.png
new file mode 100644
index 0000000..172ec55
Binary files /dev/null and b/webclients/novnc/images/screen_320x460.png differ
diff --git a/webclients/novnc/images/screen_57x57.png b/webclients/novnc/images/screen_57x57.png
new file mode 100644
index 0000000..e2085f2
Binary files /dev/null and b/webclients/novnc/images/screen_57x57.png differ
diff --git a/webclients/novnc/images/screen_700x700.png b/webclients/novnc/images/screen_700x700.png
new file mode 100644
index 0000000..ae67768
Binary files /dev/null and b/webclients/novnc/images/screen_700x700.png differ
diff --git a/webclients/novnc/images/settings.png b/webclients/novnc/images/settings.png
new file mode 100644
index 0000000..a43f5e1
Binary files /dev/null and b/webclients/novnc/images/settings.png differ
diff --git a/webclients/novnc/include/Orbitron700.ttf b/webclients/novnc/include/Orbitron700.ttf
new file mode 100644
index 0000000..e28729d
Binary files /dev/null and b/webclients/novnc/include/Orbitron700.ttf differ
diff --git a/webclients/novnc/include/Orbitron700.woff b/webclients/novnc/include/Orbitron700.woff
new file mode 100644
index 0000000..61db630
Binary files /dev/null and b/webclients/novnc/include/Orbitron700.woff differ
diff --git a/webclients/novnc/include/base.css b/webclients/novnc/include/base.css
new file mode 100644
index 0000000..0a62a1b
--- /dev/null
+++ b/webclients/novnc/include/base.css
@@ -0,0 +1,380 @@
+body {
+ margin:0;
+ padding:0;
+ font-family: Helvetica;
+ /*Background image with light grey curve.*/
+ background-color:#494949;
+ background-repeat:no-repeat;
+ background-position:right bottom;
+ height:100%;
+}
+
+html {
+ height:100%;
+}
+
+#noVNC_controls ul {
+ list-style: none;
+ margin: 0px;
+ padding: 0px;
+}
+#noVNC_controls li {
+ padding-bottom:8px;
+}
+
+#noVNC_host {
+ width:150px;
+}
+#noVNC_port {
+ width: 80px;
+}
+#noVNC_password {
+ width: 150px;
+}
+#noVNC_encrypt {
+}
+#noVNC_connectTimeout {
+ width: 30px;
+}
+#noVNC_path {
+ width: 100px;
+}
+#noVNC_connect_button {
+ width: 110px;
+ float:right;
+}
+
+
+#noVNC_view_drag_button {
+ display: none;
+}
+#sendCtrlAltDelButton {
+ display: none;
+}
+#noVNC_mobile_buttons {
+ display: none;
+}
+
+.noVNC-buttons-left {
+ float: left;
+ padding-left:10px;
+ padding-top:4px;
+}
+
+.noVNC-buttons-right {
+ float:right;
+ right: 0px;
+ padding-right:10px;
+ padding-top:4px;
+}
+
+#noVNC_status_bar {
+ margin-top: 0px;
+ padding: 0px;
+}
+
+#noVNC_status_bar div {
+ font-size: 12px;
+ padding-top: 4px;
+ width:100%;
+}
+
+#noVNC_status {
+ height:20px;
+ text-align: center;
+}
+#noVNC_settings_menu {
+ margin: 3px;
+ text-align: left;
+}
+#noVNC_settings_menu ul {
+ list-style: none;
+ margin: 0px;
+ padding: 0px;
+}
+
+#noVNC_apply {
+ 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;
+}
+#noVNC_screen {
+ text-align: center;
+ display: table;
+ width:100%;
+ height:100%;
+ background-color:#313131;
+ border-bottom-right-radius: 800px 600px;
+ /*border-top-left-radius: 800px 600px;*/
+}
+
+#noVNC_container, #noVNC_canvas {
+ margin: 0px;
+ padding: 0px;
+}
+
+#noVNC_canvas {
+ left: 0px;
+}
+
+#VNC_clipboard_clear_button {
+ float:right;
+}
+#VNC_clipboard_text {
+ font-size: 11px;
+}
+
+#noVNC_clipboard_clear_button {
+ float:right;
+}
+
+/*Bubble contents divs*/
+#noVNC_settings {
+ display:none;
+ margin-top:77px;
+ right:20px;
+ position:fixed;
+}
+
+#noVNC_controls {
+ margin-top:77px;
+ right:12px;
+ position:fixed;
+}
+#noVNC_controls.top:after {
+ right:15px;
+}
+
+#noVNC_clipboard {
+ display:none;
+ margin-top:77px;
+ right:30px;
+ position:fixed;
+}
+#noVNC_clipboard.top:after {
+ right:85px;
+}
+
+#keyboardinput {
+ width:1px;
+ height:1px;
+ background-color:#fff;
+ color:#fff;
+ border:0;
+ position: relative;
+ left: -40px;
+ z-index: -1;
+}
+
+.noVNC_status_warn {
+ background-color:yellow;
+}
+
+/*
+ * Advanced Styling
+ */
+
+/* Control bar */
+#noVNC-control-bar {
+ position:fixed;
+ background: #b2bdcd; /* Old browsers */
+ background: -moz-linear-gradient(top, #b2bdcd 0%, #899cb3 49%, #7e93af 51%, #6e84a3 100%); /* FF3.6+ */
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#b2bdcd), color-stop(49%,#899cb3), color-stop(51%,#7e93af), color-stop(100%,#6e84a3)); /* Chrome,Safari4+ */
+ background: -webkit-linear-gradient(top, #b2bdcd 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* Chrome10+,Safari5.1+ */
+ background: -o-linear-gradient(top, #b2bdcd 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* Opera11.10+ */
+ background: -ms-linear-gradient(top, #b2bdcd 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* IE10+ */
+ background: linear-gradient(top, #b2bdcd 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* W3C */
+
+ display:block;
+ height:44px;
+ left:0;
+ top:0;
+ width:100%;
+ z-index:200;
+}
+
+.noVNC_status_button {
+ padding: 4px 4px;
+ vertical-align: middle;
+ border:1px solid #869dbc;
+ -webkit-border-radius: 6px;
+ -moz-border-radius: 6px;
+ border-radius: 6px;
+ background: #b2bdcd; /* Old browsers */
+ background: -moz-linear-gradient(top, #b2bdcd 0%, #899cb3 49%, #7e93af 51%, #6e84a3 100%); /* FF3.6+ */
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#b2bdcd), color-stop(49%,#899cb3), color-stop(51%,#7e93af), color-stop(100%,#6e84a3)); /* Chrome,Safari4+ */
+ background: -webkit-linear-gradient(top, #b2bdcd 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* Chrome10+,Safari5.1+ */
+ background: -o-linear-gradient(top, #b2bdcd 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* Opera11.10+ */
+ background: -ms-linear-gradient(top, #b2bdcd 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* IE10+ */
+ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#b2bdcd', endColorstr='#6e84a3',GradientType=0 ); /* IE6-9 */
+ background: linear-gradient(top, #b2bdcd 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* W3C */
+ /*box-shadow:inset 0.4px 0.4px 0.4px #000000;*/
+}
+
+.noVNC_status_button_selected {
+ padding: 4px 4px;
+ vertical-align: middle;
+ border:1px solid #4366a9;
+ -webkit-border-radius: 6px;
+ -moz-border-radius: 6px;
+ background: #779ced; /* Old browsers */
+ background: -moz-linear-gradient(top, #779ced 0%, #3970e0 49%, #2160dd 51%, #2463df 100%); /* FF3.6+ */
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#779ced), color-stop(49%,#3970e0), color-stop(51%,#2160dd), color-stop(100%,#2463df)); /* Chrome,Safari4+ */
+ background: -webkit-linear-gradient(top, #779ced 0%,#3970e0 49%,#2160dd 51%,#2463df 100%); /* Chrome10+,Safari5.1+ */
+ background: -o-linear-gradient(top, #779ced 0%,#3970e0 49%,#2160dd 51%,#2463df 100%); /* Opera11.10+ */
+ background: -ms-linear-gradient(top, #779ced 0%,#3970e0 49%,#2160dd 51%,#2463df 100%); /* IE10+ */
+ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#779ced', endColorstr='#2463df',GradientType=0 ); /* IE6-9 */
+ background: linear-gradient(top, #779ced 0%,#3970e0 49%,#2160dd 51%,#2463df 100%); /* W3C */
+ /*box-shadow:inset 0.4px 0.4px 0.4px #000000;*/
+}
+
+
+/*Settings Bubble*/
+.triangle-right {
+ position:relative;
+ padding:15px;
+ margin:1em 0 3em;
+ color:#fff;
+ background:#fff; /* default background for browsers without gradient support */
+ /* css3 */
+ /*background:-webkit-gradient(linear, 0 0, 0 100%, from(#2e88c4), to(#075698));
+ background:-moz-linear-gradient(#2e88c4, #075698);
+ background:-o-linear-gradient(#2e88c4, #075698);
+ background:linear-gradient(#2e88c4, #075698);*/
+ -webkit-border-radius:10px;
+ -moz-border-radius:10px;
+ border-radius:10px;
+ color:#000;
+ border:2px solid #E0E0E0;
+}
+
+.triangle-right.top:after {
+ border-color: transparent #E0E0E0;
+ border-width: 20px 20px 0 0;
+ bottom: auto;
+ left: auto;
+ right: 50px;
+ top: -20px;
+}
+
+.triangle-right:after {
+ content:"";
+ position:absolute;
+ bottom:-20px; /* value = - border-top-width - border-bottom-width */
+ left:50px; /* controls horizontal position */
+ border-width:20px 0 0 20px; /* vary these values to change the angle of the vertex */
+ border-style:solid;
+ border-color:#E0E0E0 transparent;
+ /* reduce the damage in FF3.0 */
+ display:block;
+ width:0;
+}
+
+.triangle-right.top:after {
+ top:-40px; /* value = - border-top-width - border-bottom-width */
+ right:50px; /* controls horizontal position */
+ bottom:auto;
+ left:auto;
+ border-width:40px 40px 0 0; /* vary these values to change the angle of the vertex */
+ border-color:transparent #E0E0E0;
+}
+
+/*Default noVNC logo.*/
+/* From: http://fonts.googleapis.com/css?family=Orbitron:700 */
+@font-face {
+ font-family: 'Orbitron';
+ font-style: normal;
+ font-weight: 700;
+ src: local('?'), url('Orbitron700.woff') format('woff'),
+ url('Orbitron700.ttf') format('truetype');
+}
+
+#noVNC_logo {
+ margin-top: 170px;
+ margin-left: 10px;
+ color:yellow;
+ text-align:left;
+ font-family: 'Orbitron', 'OrbitronTTF', sans-serif;
+ line-height:90%;
+ text-shadow:
+ 5px 5px 0 #000,
+ -1px -1px 0 #000,
+ 1px -1px 0 #000,
+ -1px 1px 0 #000,
+ 1px 1px 0 #000;
+}
+
+
+#noVNC_logo span{
+ color:green;
+}
+
+/* ----------------------------------------
+ * Media sizing
+ * ----------------------------------------
+ */
+
+
+.noVNC_status_button {
+ font-size: 12px;
+}
+
+#noVNC_clipboard_text {
+ width: 500px;
+}
+
+#noVNC_logo {
+ font-size: 180px;
+}
+
+@media screen and (min-width: 481px) and (max-width: 640px) {
+ .noVNC_status_button {
+ font-size: 10px;
+ }
+ #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_clipboard_text {
+ width: 250px;
+ }
+ #noVNC_logo {
+ font-size: 110px;
+ }
+}
+
+@media screen and (max-width: 320px) {
+ .noVNC_status_button {
+ font-size: 9px;
+ }
+ #noVNC_clipboard_text {
+ width: 220px;
+ }
+ #noVNC_logo {
+ font-size: 90px;
+ }
+}
diff --git a/webclients/novnc/include/base64.js b/webclients/novnc/include/base64.js
new file mode 100644
index 0000000..c68b33a
--- /dev/null
+++ b/webclients/novnc/include/base64.js
@@ -0,0 +1,147 @@
+/*
+ * Modified from:
+ * http://lxr.mozilla.org/mozilla/source/extensions/xml-rpc/src/nsXmlRpcClient.js#956
+ */
+
+/* ***** 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 (original author)
+ * Samuel Sieb
+ *
+ * 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 ***** */
+
+/*jslint white: false, bitwise: false, plusplus: 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;
+ }
+ }
+
+ 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 '" + data.charCodeAt(i) + "'");
+ continue;
+ }
+
+ // Collect data into leftdata, update bitcount
+ leftdata = (leftdata << 6) | c;
+ leftbits += 6;
+
+ // If we have 8 or more bits, append 8 bits to the result
+ if (leftbits >= 8) {
+ leftbits -= 8;
+ // Append if not padding.
+ if (!padding) {
+ result[idx++] = (leftdata >> leftbits) & 0xff;
+ }
+ leftdata &= (1 << leftbits) - 1;
+ }
+ }
+
+ // If there are any bits left, the base64 string was corrupted
+ if (leftbits) {
+ throw {name: 'Base64-Error',
+ message: 'Corrupted base64 string'};
+ }
+
+ return result;
+}
+
+}; /* End of Base64 namespace */
diff --git a/webclients/novnc/include/black.css b/webclients/novnc/include/black.css
new file mode 100644
index 0000000..8f80f66
--- /dev/null
+++ b/webclients/novnc/include/black.css
@@ -0,0 +1,45 @@
+#keyboardinput {
+ background-color:#000;
+}
+
+#noVNC-control-bar {
+ background: #4c4c4c; /* Old browsers */
+ background: -moz-linear-gradient(top, #4c4c4c 0%, #2c2c2c 50%, #000000 51%, #131313 100%); /* FF3.6+ */
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#4c4c4c), color-stop(50%,#2c2c2c), color-stop(51%,#000000), color-stop(100%,#131313)); /* Chrome,Safari4+ */
+ background: -webkit-linear-gradient(top, #4c4c4c 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* Chrome10+,Safari5.1+ */
+ background: -o-linear-gradient(top, #4c4c4c 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* Opera11.10+ */
+ background: -ms-linear-gradient(top, #4c4c4c 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* IE10+ */
+ background: linear-gradient(top, #4c4c4c 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* W3C */
+}
+
+.triangle-right {
+ border:2px solid #fff;
+ background:#000;
+ color:#fff;
+}
+
+.noVNC_status_button {
+ font-size: 12px;
+ vertical-align: middle;
+ border:1px solid #4c4c4c;
+
+ background: #4c4c4c; /* Old browsers */
+ background: -moz-linear-gradient(top, #4c4c4c 0%, #2c2c2c 50%, #000000 51%, #131313 100%); /* FF3.6+ */
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#4c4c4c), color-stop(50%,#2c2c2c), color-stop(51%,#000000), color-stop(100%,#131313)); /* Chrome,Safari4+ */
+ background: -webkit-linear-gradient(top, #4c4c4c 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* Chrome10+,Safari5.1+ */
+ background: -o-linear-gradient(top, #4c4c4c 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* Opera11.10+ */
+ background: -ms-linear-gradient(top, #4c4c4c 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* IE10+ */
+ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#4c4c4c', endColorstr='#131313',GradientType=0 ); /* IE6-9 */
+ background: linear-gradient(top, #4c4c4c 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* W3C */
+}
+
+.noVNC_status_button_selected {
+ background: #9dd53a; /* Old browsers */
+ background: -moz-linear-gradient(top, #9dd53a 0%, #a1d54f 50%, #80c217 51%, #7cbc0a 100%); /* FF3.6+ */
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#9dd53a), color-stop(50%,#a1d54f), color-stop(51%,#80c217), color-stop(100%,#7cbc0a)); /* Chrome,Safari4+ */
+ background: -webkit-linear-gradient(top, #9dd53a 0%,#a1d54f 50%,#80c217 51%,#7cbc0a 100%); /* Chrome10+,Safari5.1+ */
+ background: -o-linear-gradient(top, #9dd53a 0%,#a1d54f 50%,#80c217 51%,#7cbc0a 100%); /* Opera11.10+ */
+ background: -ms-linear-gradient(top, #9dd53a 0%,#a1d54f 50%,#80c217 51%,#7cbc0a 100%); /* IE10+ */
+ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#9dd53a', endColorstr='#7cbc0a',GradientType=0 ); /* IE6-9 */
+ background: linear-gradient(top, #9dd53a 0%,#a1d54f 50%,#80c217 51%,#7cbc0a 100%); /* W3C */
+}
diff --git a/webclients/novnc/include/blue.css b/webclients/novnc/include/blue.css
new file mode 100644
index 0000000..a8baf70
--- /dev/null
+++ b/webclients/novnc/include/blue.css
@@ -0,0 +1,27 @@
+
+#noVNC-control-bar {
+ background-color:#04073d;
+ background-image: -webkit-gradient(
+ linear,
+ left bottom,
+ left top,
+ color-stop(0.54, rgb(10,15,79)),
+ color-stop(0.5, rgb(4,7,61))
+ );
+ background-image: -moz-linear-gradient(
+ center bottom,
+ rgb(10,15,79) 54%,
+ rgb(4,7,61) 50%
+ );
+}
+
+.triangle-right {
+ border:2px solid #fff;
+ background:#04073d;
+ color:#fff;
+}
+
+#keyboardinput {
+ background-color:#04073d;
+}
+
diff --git a/webclients/novnc/include/des.js b/webclients/novnc/include/des.js
new file mode 100644
index 0000000..1f95285
--- /dev/null
+++ b/webclients/novnc/include/des.js
@@ -0,0 +1,273 @@
+/*
+ * Ported from Flashlight VNC ActionScript implementation:
+ * http://www.wizhelp.com/flashlight-vnc/
+ *
+ * Full attribution follows:
+ *
+ * -------------------------------------------------------------------------
+ *
+ * This DES class has been extracted from package Acme.Crypto for use in VNC.
+ * The unnecessary odd parity code has been removed.
+ *
+ * These changes are:
+ * Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ *
+
+ * DesCipher - the DES encryption method
+ *
+ * The meat of this code is by Dave Zimmerman , and is:
+ *
+ * Copyright (c) 1996 Widget Workshop, Inc. All Rights Reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software
+ * and its documentation for NON-COMMERCIAL or COMMERCIAL purposes and
+ * without fee is hereby granted, provided that this copyright notice is kept
+ * intact.
+ *
+ * WIDGET WORKSHOP MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY
+ * OF THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+ * TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+ * PARTICULAR PURPOSE, OR NON-INFRINGEMENT. WIDGET WORKSHOP SHALL NOT BE LIABLE
+ * FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
+ * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES.
+ *
+ * THIS SOFTWARE IS NOT DESIGNED OR INTENDED FOR USE OR RESALE AS ON-LINE
+ * CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING FAIL-SAFE
+ * PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES, AIRCRAFT
+ * NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT LIFE
+ * SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
+ * SOFTWARE COULD LEAD DIRECTLY TO DEATH, PERSONAL INJURY, OR SEVERE
+ * PHYSICAL OR ENVIRONMENTAL DAMAGE ("HIGH RISK ACTIVITIES"). WIDGET WORKSHOP
+ * SPECIFICALLY DISCLAIMS ANY EXPRESS OR IMPLIED WARRANTY OF FITNESS FOR
+ * HIGH RISK ACTIVITIES.
+ *
+ *
+ * The rest is:
+ *
+ * Copyright (C) 1996 by Jef Poskanzer . All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * Visit the ACME Labs Java page for up-to-date versions of this and other
+ * fine Java utilities: http://www.acme.com/java/
+ */
+
+"use strict";
+/*jslint white: false, bitwise: false, plusplus: false */
+
+function DES(passwd) {
+
+// 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 = [];
+
+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];
+
+// Set the key.
+function setKeys(keyBlock) {
+ var i, j, l, m, n, o, pc1m = [], pcr = [], kn = [],
+ raw0, raw1, rawi, KnLi;
+
+ for (j = 0, l = 56; j < 56; ++j, l-=8) {
+ l += l<-5 ? 65 : l<-3 ? 31 : l<-1 ? 63 : l===27 ? 35 : 0; // PC1
+ m = l & 0x7;
+ pc1m[j] = ((keyBlock[l >>> 3] & (1<>> 10;
+ keys[KnLi] |= (raw1 & 0x00000fc0) >>> 6;
+ ++KnLi;
+ keys[KnLi] = (raw0 & 0x0003f000) << 12;
+ keys[KnLi] |= (raw0 & 0x0000003f) << 16;
+ keys[KnLi] |= (raw1 & 0x0003f000) >>> 4;
+ keys[KnLi] |= (raw1 & 0x0000003f);
+ ++KnLi;
+ }
+}
+
+// Encrypt 8 bytes of text
+function enc8(text) {
+ var i = 0, b = text.slice(), fval, keysi = 0,
+ l, r, x; // left, right, accumulator
+
+ // Squash 8 bytes to 2 ints
+ l = b[i++]<<24 | b[i++]<<16 | b[i++]<<8 | b[i++];
+ r = b[i++]<<24 | b[i++]<<16 | b[i++]<<8 | b[i++];
+
+ x = ((l >>> 4) ^ r) & 0x0f0f0f0f;
+ r ^= x;
+ l ^= (x << 4);
+ x = ((l >>> 16) ^ r) & 0x0000ffff;
+ r ^= x;
+ l ^= (x << 16);
+ x = ((r >>> 2) ^ l) & 0x33333333;
+ l ^= x;
+ r ^= (x << 2);
+ x = ((r >>> 8) ^ l) & 0x00ff00ff;
+ l ^= x;
+ r ^= (x << 8);
+ r = (r << 1) | ((r >>> 31) & 1);
+ x = (l ^ r) & 0xaaaaaaaa;
+ l ^= x;
+ r ^= x;
+ l = (l << 1) | ((l >>> 31) & 1);
+
+ for (i = 0; i < 8; ++i) {
+ x = (r << 28) | (r >>> 4);
+ x ^= keys[keysi++];
+ fval = SP7[x & 0x3f];
+ fval |= SP5[(x >>> 8) & 0x3f];
+ fval |= SP3[(x >>> 16) & 0x3f];
+ fval |= SP1[(x >>> 24) & 0x3f];
+ x = r ^ keys[keysi++];
+ fval |= SP8[x & 0x3f];
+ fval |= SP6[(x >>> 8) & 0x3f];
+ fval |= SP4[(x >>> 16) & 0x3f];
+ fval |= SP2[(x >>> 24) & 0x3f];
+ l ^= fval;
+ x = (l << 28) | (l >>> 4);
+ x ^= keys[keysi++];
+ fval = SP7[x & 0x3f];
+ fval |= SP5[(x >>> 8) & 0x3f];
+ fval |= SP3[(x >>> 16) & 0x3f];
+ fval |= SP1[(x >>> 24) & 0x3f];
+ x = l ^ keys[keysi++];
+ fval |= SP8[x & 0x0000003f];
+ fval |= SP6[(x >>> 8) & 0x3f];
+ fval |= SP4[(x >>> 16) & 0x3f];
+ fval |= SP2[(x >>> 24) & 0x3f];
+ r ^= fval;
+ }
+
+ r = (r << 31) | (r >>> 1);
+ x = (l ^ r) & 0xaaaaaaaa;
+ l ^= x;
+ r ^= x;
+ l = (l << 31) | (l >>> 1);
+ x = ((l >>> 8) ^ r) & 0x00ff00ff;
+ r ^= x;
+ l ^= (x << 8);
+ x = ((l >>> 2) ^ r) & 0x33333333;
+ r ^= x;
+ l ^= (x << 2);
+ x = ((r >>> 16) ^ l) & 0x0000ffff;
+ l ^= x;
+ r ^= (x << 16);
+ x = ((r >>> 4) ^ l) & 0x0f0f0f0f;
+ l ^= x;
+ r ^= (x << 4);
+
+ // Spread ints to bytes
+ x = [r, l];
+ for (i = 0; i < 8; i++) {
+ b[i] = (x[i>>>2] >>> (8*(3 - (i%4)))) % 256;
+ if (b[i] < 0) { b[i] += 256; } // unsigned
+ }
+ return b;
+}
+
+// Encrypt 16 bytes of text using passwd as key
+function encrypt(t) {
+ return enc8(t.slice(0,8)).concat(enc8(t.slice(8,16)));
+}
+
+setKeys(passwd); // Setup keys
+return {'encrypt': encrypt}; // Public interface
+
+} // function DES
diff --git a/webclients/novnc/include/display.js b/webclients/novnc/include/display.js
new file mode 100644
index 0000000..2cf262d
--- /dev/null
+++ b/webclients/novnc/include/display.js
@@ -0,0 +1,671 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2011 Joel Martin
+ * Licensed under LGPL-3 (see LICENSE.txt)
+ *
+ * See README.md for usage and integration instructions.
+ */
+
+/*jslint browser: true, white: false, bitwise: 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,
+
+ // Predefine function variables (jslint)
+ imageDataGet, rgbxImageData, cmapImageData,
+ setFillColor, rescale,
+
+ // 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']
+ ]);
+
+// Override some specific getters/setters
+that.get_context = function () { return c_ctx; };
+
+that.set_scale = function(scale) { rescale(scale); };
+
+that.set_width = function (val) { that.resize(val, fb_height); };
+that.get_width = function() { return fb_width; };
+
+that.set_height = function (val) { that.resize(fb_width, val); };
+that.get_height = function() { return fb_height; };
+
+
+
+//
+// Private functions
+//
+
+// Create the public API interface
+function constructor() {
+ Util.Debug(">> Display.constructor");
+
+ var c, func, i, curDat, curSave,
+ has_imageData = false, UE = Util.Engine;
+
+ if (! conf.target) { throw("target must be set"); }
+
+ if (typeof conf.target === 'string') {
+ throw("target must be a DOM element");
+ }
+
+ c = conf.target;
+
+ if (! c.getContext) { throw("no getContext method"); }
+
+ if (! c_ctx) { c_ctx = c.getContext('2d'); }
+
+ 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); }
+
+ that.clear();
+
+ // 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;
+ }
+
+ // Initialize cached tile imageData
+ tile16x16 = c_ctx.createImageData(16, 16);
+
+ /*
+ * 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;
+ }
+ Util.Info("Data URI scheme cursor supported");
+ } else {
+ if (conf.cursor_uri === null) {
+ conf.cursor_uri = false;
+ }
+ 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 rgb, newStyle;
+ if (conf.true_color) {
+ rgb = color;
+ } else {
+ rgb = conf.colourMap[color[0]];
+ }
+ newStyle = "rgb(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + ")";
+ 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;
+
+
+ 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);
+ }
+
+ c.width = v.w;
+ c.height = v.h;
+
+ 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);
+ }
+
+ // 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, rgb, 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) {
+ rgb = color;
+ } else {
+ rgb = conf.colourMap[color[0]];
+ }
+ red = rgb[0];
+ green = rgb[1];
+ blue = rgb[2];
+ 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, rgb, red, green, blue, width, j, i, xend, yend;
+ if (conf.prefer_js) {
+ data = tile.data;
+ width = tile.width;
+ if (conf.true_color) {
+ rgb = color;
+ } else {
+ rgb = conf.colourMap[color[0]];
+ }
+ red = rgb[0];
+ green = rgb[1];
+ blue = rgb[2];
+ 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
+};
+
+rgbxImageData = 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 ];
+ 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);
+};
+
+cmapImageData = function(x, y, width, height, arr, offset) {
+ var img, i, j, data, rgb, cmap;
+ img = c_ctx.createImageData(width, height);
+ data = img.data;
+ cmap = conf.colourMap;
+ for (i=0, j=offset; i < (width * height * 4); i+=4, j+=1) {
+ rgb = cmap[arr[j]];
+ data[i ] = rgb[0];
+ data[i + 1] = rgb[1];
+ data[i + 2] = rgb[2];
+ 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) {
+ rgbxImageData(x, y, width, height, arr, offset);
+ } else {
+ 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;
+};
+
+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;
+ }
+
+ 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);
+ }
+};
+
+that.defaultCursor = function() {
+ conf.target.style.cursor = "default";
+};
+
+return constructor(); // Return the public API interface
+
+} // End of Display()
+
+
+/* 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 );
+ };
+
+ 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
+ } 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
+ }
+ }
+ }
+
+ // 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);
+ }
+ }
+
+ // 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);
+ }
+ }
+
+ url = "data:image/x-icon;base64," + Base64.encode(cur);
+ target.style.cursor = "url(" + url + ") " + hotx + " " + hoty + ", default";
+ //Util.Debug("<< changeCursor, cur.length: " + cur.length);
+}
diff --git a/webclients/novnc/include/input.js b/webclients/novnc/include/input.js
new file mode 100644
index 0000000..3124d08
--- /dev/null
+++ b/webclients/novnc/include/input.js
@@ -0,0 +1,1884 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2011 Joel Martin
+ * Licensed under LGPL-2 or any later version (see LICENSE.txt)
+ */
+
+/*jslint browser: true, white: false, bitwise: false */
+/*global window, Util */
+
+
+//
+// Keyboard event handler
+//
+
+function Keyboard(defaults) {
+"use strict";
+
+var that = {}, // Public API methods
+ conf = {}, // Configuration attributes
+
+ keyDownList = []; // List of depressed keys
+ // (even if they are happy)
+
+// 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'],
+
+ ['onKeyPress', 'rw', 'func', null, 'Handler for key press/release']
+ ]);
+
+
+//
+// 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;
+ }
+
+ /* 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)
+ }
+
+ /* 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 ')
+ }
+ } 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; // /
+ }
+ }
+ }
+
+ 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];
+ } else {
+ fevt = keyDownList[i];
+ }
+ 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;
+}
+
+//
+// Public API interface functions
+//
+
+that.grab = function() {
+ //Util.Debug(">> Keyboard.grab");
+ var c = conf.target;
+
+ Util.addEvent(c, 'keydown', onKeyDown);
+ Util.addEvent(c, 'keyup', onKeyUp);
+ Util.addEvent(c, 'keypress', onKeyPress);
+
+ //Util.Debug("<< Keyboard.grab");
+};
+
+that.ungrab = function() {
+ //Util.Debug(">> Keyboard.ungrab");
+ var c = conf.target;
+
+ Util.removeEvent(c, 'keydown', onKeyDown);
+ Util.removeEvent(c, 'keyup', onKeyUp);
+ Util.removeEvent(c, 'keypress', onKeyPress);
+
+ //Util.Debug(">> Keyboard.ungrab");
+};
+
+return that; // Return the public API interface
+
+} // End of Keyboard()
+
+
+//
+// Mouse event handler
+//
+
+function Mouse(defaults) {
+"use strict";
+
+var that = {}, // Public API methods
+ conf = {}; // Configuration attributes
+
+// 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'],
+
+ ['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)']
+ ]);
+
+
+//
+// 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()
+
+
+/*
+ * 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
+};
\ No newline at end of file
diff --git a/webclients/novnc/include/logo.js b/webclients/novnc/include/logo.js
new file mode 100644
index 0000000..befa598
--- /dev/null
+++ b/webclients/novnc/include/logo.js
@@ -0,0 +1 @@
+noVNC_logo = {"width": 640, "height": 435, "data": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAoAAAAGzCAYAAAC/y6a9AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAStAAAErQBBHTWggAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAACAASURBVHic7N13fBvlwQfw3522ZMm2vPdIGCFkA4GyoYyGsCmjk+7dQksHL2/H2/dtC4W2tLTlfelu2VA2lEILFCgQIHEGJCQkdjzkLdmWZGvfvX8oOkmJEy/pNO73/Xz44DtLzz2RT7qfnnXC8uXLZUxDlqfdnUYQhIP+bjbPn+5xhypzrmUf6rGzOc5cjzVduXN9/nTPyfRrMt/jzOcY05U5n3L2f95s/34LPW4m/p6FbLp/73xe+5nKnWuZs/07ZOOcnusx5nucbJU727LneuxslDmdTBxn/2NmusyEuZS7kHMxG/XP5Gf3TOVmQzY+u/PhPMnkMcSsH5WIiIiI8goDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHG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"};
diff --git a/webclients/novnc/include/playback.js b/webclients/novnc/include/playback.js
new file mode 100644
index 0000000..22a00a3
--- /dev/null
+++ b/webclients/novnc/include/playback.js
@@ -0,0 +1,90 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2011 Joel Martin
+ * Licensed under LGPL-3 (see LICENSE.LGPL-3)
+ */
+
+"use strict";
+/*jslint browser: true, white: false */
+/*global Util, VNC_frame_data, finish */
+
+var rfb, mode, test_state, frame_idx, frame_length,
+ iteration, iterations, istart_time,
+
+ // Pre-declarations for jslint
+ send_array, next_iteration, queue_next_packet, do_packet;
+
+// Override send_array
+send_array = function (arr) {
+ // Stub out send_array
+};
+
+next_iteration = function () {
+ if (iteration === 0) {
+ frame_length = VNC_frame_data.length;
+ test_state = 'running';
+ } else {
+ rfb.disconnect();
+ }
+
+ if (test_state !== 'running') { return; }
+
+ iteration += 1;
+ if (iteration > iterations) {
+ finish();
+ return;
+ }
+
+ frame_idx = 0;
+ istart_time = (new Date()).getTime();
+ rfb.connect('test', 0, "bogus");
+
+ queue_next_packet();
+
+};
+
+queue_next_packet = function () {
+ var frame, foffset, toffset, delay;
+ if (test_state !== 'running') { return; }
+
+ frame = VNC_frame_data[frame_idx];
+ while ((frame_idx < frame_length) && (frame.charAt(0) === "}")) {
+ //Util.Debug("Send frame " + frame_idx);
+ frame_idx += 1;
+ frame = VNC_frame_data[frame_idx];
+ }
+
+ if (frame === 'EOF') {
+ Util.Debug("Finished, found EOF");
+ next_iteration();
+ return;
+ }
+ if (frame_idx >= frame_length) {
+ Util.Debug("Finished, no more frames");
+ next_iteration();
+ return;
+ }
+
+ if (mode === 'realtime') {
+ foffset = frame.slice(1, frame.indexOf('{', 1));
+ toffset = (new Date()).getTime() - istart_time;
+ delay = foffset - toffset;
+ if (delay < 1) {
+ delay = 1;
+ }
+
+ setTimeout(do_packet, delay);
+ } else {
+ setTimeout(do_packet, 1);
+ }
+};
+
+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)});
+ frame_idx += 1;
+
+ queue_next_packet();
+};
+
diff --git a/webclients/novnc/include/rfb.js b/webclients/novnc/include/rfb.js
new file mode 100644
index 0000000..b7aa3f6
--- /dev/null
+++ b/webclients/novnc/include/rfb.js
@@ -0,0 +1,1613 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2011 Joel Martin
+ * Licensed under LGPL-3 (see LICENSE.txt)
+ *
+ * See README.md for usage and integration instructions.
+ */
+
+/*jslint white: false, browser: true, bitwise: false, plusplus: false */
+/*global window, Util, Display, Keyboard, Mouse, Websock, Websock_native, Base64, DES */
+
+
+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,
+
+ extract_data_uri, scan_tight_imgQ,
+ 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_PNG', -260 ],
+ ['HEXTILE', 0x05 ],
+ ['RRE', 0x02 ],
+ ['RAW', 0x00 ],
+ ['DesktopSize', -223 ],
+ ['Cursor', -239 ],
+
+ // Psuedo-encoding settings
+ ['JPEG_quality_lo', -32 ],
+ //['JPEG_quality_hi', -23 ],
+ ['compress_lo', -255 ]
+ //['compress_hi', -247 ]
+ ],
+
+ 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,
+ imgQ : [] // TIGHT_PNG image queue
+ },
+
+ fb_Bpp = 4,
+ fb_depth = 3,
+ fb_width = 0,
+ fb_height = 0,
+ fb_name = "",
+
+ scan_imgQ_rate = 40, // 25 times per second or so
+ 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
+ },
+
+ 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'],
+
+ ['connectTimeout', 'rw', 'int', def_con_timeout, 'Time (s) to wait for connection'],
+ ['disconnectTimeout', 'rw', 'int', 3, 'Time (s) to wait for disconnection'],
+
+ ['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']
+ ]);
+
+
+// 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() {
+ if (rfb_state === 'disconnect') {
+ updateState('disconnected', 'VNC disconnected');
+ } else if (rfb_state === 'ProtocolVersion') {
+ fail('Failed to connect to server');
+ } else if (rfb_state in {'failed':1, 'disconnected':1}) {
+ Util.Error("Received onclose while disconnected");
+ } else {
+ fail('Server disconnected');
+ }
+ });
+ ws.on('error', function(e) {
+ fail("WebSock error: " + e);
+ });
+
+
+ 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 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);
+ }
+ }
+
+ Util.Debug("<< RFB.constructor");
+ return that; // Return the public API interface
+}
+
+function connect() {
+ Util.Debug(">> RFB.connect");
+
+ var uri = "";
+ if (conf.encrypt) {
+ uri = "wss://";
+ } 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.imgQ = []; // TIGHT_PNG image queue
+ 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;
+ }
+};
+
+// 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");
+ }
+ }
+};
+
+//
+// Utility routines
+//
+
+
+/*
+ * 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 (msgTimer) {
+ clearInterval(msgTimer);
+ msgTimer = null;
+ }
+
+ 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();
+ }
+ }
+
+ ws.close();
+ }
+
+ if (oldstate === 'fatal') {
+ Util.Error("Fatal error, cannot continue");
+ }
+
+ if ((state === 'failed') || (state === 'fatal')) {
+ func = Util.Error;
+ } else {
+ func = Util.Warn;
+ }
+
+ if ((oldstate === 'failed') && (state === 'disconnected')) {
+ // Do disconnect action, but stay in failed state.
+ rfb_state = 'failed';
+ } else {
+ rfb_state = state;
+ }
+
+ cmsg = typeof(statusMsg) !== 'undefined' ? (" Msg: " + statusMsg) : "";
+ func("New state '" + rfb_state + "', was '" + oldstate + "'." + cmsg);
+
+ 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'");
+ }
+
+ break;
+
+
+ case 'connect':
+
+ connTimer = setTimeout(function () {
+ fail("Connect timeout");
+ }, conf.connectTimeout * 1000);
+
+ init_vars();
+ connect();
+
+ // WebSocket.onopen transitions to 'ProtocolVersion'
+ break;
+
+
+ case 'disconnect':
+
+ if (! test_mode) {
+ disconnTimer = setTimeout(function () {
+ fail("Disconnect timeout");
+ }, conf.disconnectTimeout * 1000);
+ }
+
+ print_stats();
+
+ // WebSocket.onclose transitions to 'disconnected'
+ break;
+
+
+ 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.");
+ }
+
+ // 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");
+ }
+ }
+ 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());
+ }
+ }
+ }
+ setTimeout(checkEvents, conf.check_rate);
+};
+
+keyPress = function(keysym, down) {
+ var arr;
+ 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;
+ }
+ }
+
+ mouse_arr = mouse_arr.concat(
+ pointerEvent(display.absX(x), display.absY(y)) );
+ flushClient();
+};
+
+mouseMove = function(x, y) {
+ //Util.Debug('>> mouseMove ' + x + "," + y);
+ var deltaX, deltaY;
+
+ 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};
+
+ display.viewportChange(deltaX, deltaY);
+
+ // Skip sending mouse events
+ return;
+ }
+
+ mouse_arr = mouse_arr.concat(
+ pointerEvent(display.absX(x), display.absY(y)) );
+};
+
+
+//
+// Server message handlers
+//
+
+// RFB/VNC initialisation message handler
+init_msg = function() {
+ //Util.Debug(">> init_msg [rfb_state '" + rfb_state + "']");
+
+ var strlen, reason, length, sversion, cversion,
+ 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;
+
+ //Util.Debug("ws.rQ (" + ws.rQlen() + ") " + ws.rQslice(0));
+ switch (rfb_state) {
+
+ case 'ProtocolVersion' :
+ if (ws.rQlen() < 12) {
+ return fail("Incomplete protocol version");
+ }
+ sversion = ws.rQshiftStr(12).substr(4,7);
+ Util.Info("Server ProtocolVersion: " + sversion);
+ switch (sversion) {
+ case "003.003": rfb_version = 3.3; break;
+ case "003.006": rfb_version = 3.3; break; // UltraVNC
+ case "003.007": rfb_version = 3.7; break;
+ case "003.008": rfb_version = 3.8; break;
+ default:
+ return fail("Invalid server version " + sversion);
+ }
+ 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);
+ }
+
+ 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];
+ }
+ }
+ if (rfb_auth_scheme === 0) {
+ return fail("Unsupported security types: " + types);
+ }
+
+ 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;
+ }
+ // 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;
+ }
+ 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;
+ }
+ 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);
+
+ /* Connection name/title */
+ name_length = ws.rQshift32();
+ fb_name = ws.rQshiftStr(name_length);
+
+ display.set_true_color(conf.true_color);
+ display.resize(fb_width, fb_height);
+ keyboard.grab();
+ mouse.grab();
+
+ if (conf.true_color) {
+ fb_Bpp = 4;
+ fb_depth = 3;
+ } else {
+ fb_Bpp = 1;
+ fb_depth = 1;
+ }
+
+ response = pixelFormat();
+ response = response.concat(clientEncodings());
+ response = response.concat(fbUpdateRequests());
+ timing.fbu_rt_start = (new Date()).getTime();
+ ws.send(response);
+
+ /* Start pushing/polling */
+ setTimeout(checkEvents, conf.check_rate);
+ setTimeout(scan_tight_imgQ, scan_imgQ_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();
+ 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([red, green, blue], 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));
+ }
+ }
+
+ 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);
+ */
+ } else {
+ fail("Disconnected: unsupported encoding " +
+ FBU.encoding);
+ return false;
+ }
+ }
+
+ timing.last_fbu = (new Date()).getTime();
+
+ ret = encHandlers[FBU.encoding]();
+
+ now = (new Date()).getTime();
+ timing.cur_fbu += (now - timing.last_fbu);
+
+ if (ret) {
+ encStats[FBU.encoding][0] += 1;
+ encStats[FBU.encoding][1] += 1;
+ }
+
+ if (FBU.rects === 0) {
+ 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;
+ }
+ }
+ 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; }
+ old_x = ws.rQshift16();
+ old_y = ws.rQshift16();
+ display.copyImage(old_x, old_y, FBU.x, FBU.y, FBU.width, 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;
+ }
+ }
+ }
+
+ /*
+ 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);
+ }
+ } 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;
+ } else {
+ color = FBU.foreground;
+ }
+ xy = rQ[rQi];
+ rQi += 1;
+ sx = (xy >> 4);
+ sy = (xy & 0x0f);
+
+ wh = rQ[rQi];
+ rQi += 1;
+ sw = (wh >> 4) + 1;
+ sh = (wh & 0x0f) + 1;
+
+ display.subTile(sx, sy, sw, sh, color);
+ }
+ }
+ 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;
+};
+
+
+encHandlers.TIGHT_PNG = function display_tight_png() {
+ //Util.Debug(">> display_tight_png");
+ var ctl, cmode, clength, getCLength, color, img;
+ //Util.Debug(" FBU.rects: " + FBU.rects);
+ //Util.Debug(" starting ws.rQslice(0,20): " + ws.rQslice(0,20) + " (" + ws.rQlen() + ")");
+
+ FBU.bytes = 1; // compression-control byte
+ if (ws.rQwait("TIGHT compression-control", FBU.bytes)) { return false; }
+
+ // Get 'compact length' header and data size
+ getCLength = function (arr) {
+ var header = 1, data = 0;
+ data += arr[0] & 0x7f;
+ if (arr[0] & 0x80) {
+ header += 1;
+ data += (arr[1] & 0x7f) << 7;
+ if (arr[1] & 0x80) {
+ header += 1;
+ data += arr[2] << 14;
+ }
+ }
+ return [header, data];
+ };
+
+ ctl = ws.rQpeek8();
+ switch (ctl >> 4) {
+ case 0x08: cmode = "fill"; break;
+ case 0x09: cmode = "jpeg"; break;
+ case 0x0A: cmode = "png"; break;
+ default: throw("Illegal basic compression received, ctl: " + ctl);
+ }
+ 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
+ }
+
+ 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);
+ FBU.imgQ.push({
+ 'type': 'fill',
+ 'img': {'complete': true},
+ 'x': FBU.x,
+ 'y': FBU.y,
+ 'width': FBU.width,
+ 'height': FBU.height,
+ 'color': color});
+ break;
+ case "jpeg":
+ case "png":
+ clength = getCLength(ws.rQslice(1, 4));
+ FBU.bytes = 1 + clength[0] + clength[1]; // ctl + clength size + jpeg-data
+ if (ws.rQwait("TIGHT " + cmode, FBU.bytes)) { return false; }
+
+ // We have everything, render it
+ //Util.Debug(" png, ws.rQlen(): " + ws.rQlen() + ", clength[0]: " + clength[0] + ", clength[1]: " + clength[1]);
+ ws.rQshiftBytes(1 + clength[0]); // shift off ctl + compact length
+ img = new Image();
+ //img.onload = scan_tight_imgQ;
+ FBU.imgQ.push({
+ 'type': 'img',
+ 'img': img,
+ 'x': FBU.x,
+ 'y': FBU.y});
+ img.src = "data:image/" + cmode +
+ extract_data_uri(ws.rQshiftBytes(clength[1]));
+ img = null;
+ 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);
+};
+
+scan_tight_imgQ = function() {
+ var data, imgQ, ctx;
+ ctx = display.get_context();
+ if (rfb_state === 'normal') {
+ imgQ = FBU.imgQ;
+ while ((imgQ.length > 0) && (imgQ[0].img.complete)) {
+ data = imgQ.shift();
+ if (data['type'] === 'fill') {
+ display.fillRect(data.x, data.y, data.width, data.height, data.color);
+ } else {
+ ctx.drawImage(data.img, data.x, data.y);
+ }
+ }
+ setTimeout(scan_tight_imgQ, scan_imgQ_rate);
+ }
+};
+
+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");
+};
+
+/*
+ * Client message routines
+ */
+
+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(0); // red-shift
+ arr.push8(8); // green-shift
+ arr.push8(16); // 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> 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") { 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") { 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");
+ };
+};
+
+
+return constructor(); // Return the public API interface
+
+} // End of RFB()
diff --git a/webclients/novnc/include/ui.js b/webclients/novnc/include/ui.js
new file mode 100644
index 0000000..74a0005
--- /dev/null
+++ b/webclients/novnc/include/ui.js
@@ -0,0 +1,629 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2011 Joel Martin
+ * Licensed under LGPL-3 (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 : true,
+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', '');
+ UI.initSetting('port', '');
+ UI.initSetting('password', '');
+ UI.initSetting('encrypt', false);
+ UI.initSetting('true_color', true);
+ UI.initSetting('cursor', false);
+ UI.initSetting('shared', true);
+ UI.initSetting('connectTimeout', 2);
+ UI.initSetting('path', '');
+
+ 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.";
+ }
+ } );
+
+},
+
+// 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;
+},
+
+// Update cookie and form control setting. If value is not set, then
+// updates from control to current cookie setting.
+updateSetting: function(name, value) {
+
+ var i, ctrl = $D('noVNC_' + name);
+ // Save the cookie for this session
+ if (typeof value !== 'undefined') {
+ WebUtil.createCookie(name, value);
+ }
+
+ // Update the settings control
+ value = UI.getSetting(name);
+
+ if (ctrl.type === 'checkbox') {
+ ctrl.checked = value;
+
+ } 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;
+ }
+ }
+ } 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 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 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() {
+ 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('connectTimeout');
+ UI.updateSetting('path');
+ UI.updateSetting('stylesheet');
+ UI.updateSetting('logging');
+
+ UI.openSettingsMenu();
+ }
+},
+
+// Open menu
+openSettingsMenu: function() {
+ 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('connectTimeout');
+ UI.saveSetting('path');
+ 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";
+ 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') {
+ 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_connectTimeout').disabled = connected;
+ $D('noVNC_path').disabled = connected;
+
+ if (connected) {
+ UI.setViewClip();
+ UI.setMouseButton(1);
+ $D('showKeyboard').style.display = "inline";
+ $D('sendCtrlAltDelButton').style.display = "inline";
+ } else {
+ UI.setMouseButton();
+ $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_connectTimeout(UI.getSetting('connectTimeout'));
+
+ 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("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();
+ };
+},
+
+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
new file mode 100644
index 0000000..0a9e0e0
--- /dev/null
+++ b/webclients/novnc/include/util.js
@@ -0,0 +1,276 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2011 Joel Martin
+ * Licensed under LGPL-3 (see LICENSE.txt)
+ *
+ * See README.md for usage and integration instructions.
+ */
+
+"use strict";
+/*jslint bitwise: false, white: false */
+/*global window, console, document, navigator, ActiveXObject */
+
+// Globals defined here
+var Util = {};
+
+
+/*
+ * Make arrays quack
+ */
+
+Array.prototype.push8 = function (num) {
+ this.push(num & 0xFF);
+};
+
+Array.prototype.push16 = function (num) {
+ this.push((num >> 8) & 0xFF,
+ (num ) & 0xFF );
+};
+Array.prototype.push32 = function (num) {
+ this.push((num >> 24) & 0xFF,
+ (num >> 16) & 0xFF,
+ (num >> 8) & 0xFF,
+ (num ) & 0xFF );
+};
+
+/*
+ * ------------------------------------------------------
+ * Namespaced in Util
+ * ------------------------------------------------------
+ */
+
+/*
+ * Logging/debug routines
+ */
+
+Util._log_level = 'warn';
+Util.init_logging = function (level) {
+ if (typeof level === 'undefined') {
+ level = Util._log_level;
+ } else {
+ Util._log_level = level;
+ }
+ if (typeof window.console === "undefined") {
+ if (typeof window.opera !== "undefined") {
+ window.console = {
+ 'log' : window.opera.postError,
+ 'warn' : window.opera.postError,
+ 'error': window.opera.postError };
+ } else {
+ window.console = {
+ 'log' : function(m) {},
+ 'warn' : function(m) {},
+ 'error': function(m) {}};
+ }
+ }
+
+ Util.Debug = Util.Info = Util.Warn = Util.Error = function (msg) {};
+ switch (level) {
+ case 'debug': Util.Debug = function (msg) { console.log(msg); };
+ case 'info': Util.Info = function (msg) { console.log(msg); };
+ case 'warn': Util.Warn = function (msg) { console.warn(msg); };
+ case 'error': Util.Error = function (msg) { console.error(msg); };
+ case 'none':
+ break;
+ default:
+ throw("invalid logging type '" + level + "'");
+ }
+};
+Util.get_logging = function () {
+ return Util._log_level;
+};
+// Initialize logging level
+Util.init_logging();
+
+
+// Set configuration default for Crockford style function namespaces
+Util.conf_default = function(cfg, api, defaults, v, mode, type, defval, desc) {
+ var getter, setter;
+
+ // Default getter function
+ getter = function (idx) {
+ if ((type in {'arr':1, 'array':1}) &&
+ (typeof idx !== 'undefined')) {
+ return cfg[v][idx];
+ } else {
+ return cfg[v];
+ }
+ };
+
+ // 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;
+ } else {
+ val = true;
+ }
+ } else if (type in {'integer':1, 'int':1}) {
+ val = parseInt(val, 10);
+ } else if (type === 'func') {
+ if (!val) {
+ val = function () {};
+ }
+ }
+ if (typeof idx !== 'undefined') {
+ cfg[v][idx] = val;
+ } else {
+ cfg[v] = val;
+ }
+ };
+
+ // Set the description
+ api[v + '_description'] = desc;
+
+ // Set the getter function
+ if (typeof api['get_' + v] === 'undefined') {
+ api['get_' + v] = 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 default value
+ if (typeof defaults[v] !== 'undefined') {
+ defval = defaults[v];
+ } else if ((type in {'arr':1, 'array':1}) &&
+ (! (defval instanceof Array))) {
+ defval = [];
+ }
+ // 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) {
+ 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]);
+ }
+}
+
+
+/*
+ * 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);
+ }
+ return {'x': x, 'y': y};
+};
+
+// Get mouse event position in DOM element
+Util.getEventPosition = function (e, obj, scale) {
+ var evt, docX, docY, pos;
+ //if (!e) evt = window.event;
+ evt = (e ? e : window.event);
+ evt = (evt.changedTouches ? evt.changedTouches[0] : evt.touches ? evt.touches[0] : evt);
+ if (evt.pageX || evt.pageY) {
+ docX = evt.pageX;
+ docY = evt.pageY;
+ } else if (evt.clientX || evt.clientY) {
+ docX = evt.clientX + document.body.scrollLeft +
+ document.documentElement.scrollLeft;
+ docY = evt.clientY + document.body.scrollTop +
+ document.documentElement.scrollTop;
+ }
+ pos = Util.getPosition(obj);
+ if (typeof scale === "undefined") {
+ scale = 1;
+ }
+ return {'x': (docX - pos.x) / scale, 'y': (docY - pos.y) / 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);
+ return r;
+ } else if (obj.addEventListener){
+ obj.addEventListener(evType, fn, false);
+ return true;
+ } else {
+ throw("Handler could not be attached");
+ }
+};
+
+Util.removeEvent = function(obj, evType, fn){
+ if (obj.detachEvent){
+ var r = obj.detachEvent("on"+evType, fn);
+ return r;
+ } else if (obj.removeEventListener){
+ obj.removeEventListener(evType, fn, false);
+ return true;
+ } else {
+ throw("Handler could not be removed");
+ }
+};
+
+Util.stopEvent = function(e) {
+ if (e.stopPropagation) { e.stopPropagation(); }
+ else { e.cancelBubble = true; }
+
+ if (e.preventDefault) { e.preventDefault(); }
+ else { e.returnValue = false; }
+};
+
+
+// Set browser engine versions. Based on mootools.
+Util.Features = {xpath: !!(document.evaluate), air: !!(window.runtime), query: !!(document.querySelector)};
+
+Util.Engine = {
+ 'presto': (function() {
+ return (!window.opera) ? false : ((arguments.callee.caller) ? 960 : ((document.getElementsByClassName) ? 950 : 925)); }()),
+ '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);
+}
+
+Util.Flash = (function(){
+ var v, version;
+ try {
+ v = navigator.plugins['Shockwave Flash'].description;
+ } catch(err1) {
+ try {
+ v = new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version');
+ } catch(err2) {
+ v = '0 r0';
+ }
+ }
+ version = v.match(/\d+/g);
+ return {version: parseInt(version[0] || 0 + '.' + version[1], 10) || 0, build: parseInt(version[2], 10) || 0};
+}());
diff --git a/webclients/novnc/include/vnc.js b/webclients/novnc/include/vnc.js
new file mode 100644
index 0000000..f938be7
--- /dev/null
+++ b/webclients/novnc/include/vnc.js
@@ -0,0 +1,42 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2011 Joel Martin
+ * Licensed under LGPL-3 (see LICENSE.txt)
+ *
+ * See README.md for usage and integration instructions.
+ */
+
+/*jslint evil: true */
+/*global window, document, INCLUDE_URI */
+
+/*
+ * Load supporting scripts
+ */
+function get_INCLUDE_URI() {
+ return (typeof INCLUDE_URI !== "undefined") ? INCLUDE_URI : "include/";
+}
+
+(function () {
+ "use strict";
+
+ var extra = "", start, end;
+
+ start = "
+ -->
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
no
VNC
+
+
+
+
+
+
+
+
+
+
+