summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--AUTHORS14
-rw-r--r--CMakeLists.txt2
-rw-r--r--ChangeLog18
-rwxr-xr-xLibVNCServer.spec.in2
-rw-r--r--Makefile.am4
-rw-r--r--NEWS6
-rw-r--r--README2
-rw-r--r--classes/Makefile.am5
-rw-r--r--classes/index.vnc18
-rw-r--r--client_examples/SDLvncviewer.c4
-rw-r--r--configure.ac50
-rw-r--r--examples/Makefile.am4
-rw-r--r--examples/android/Makefile.am6
-rw-r--r--examples/android/README63
-rw-r--r--examples/android/jni/Android.mk65
-rw-r--r--examples/android/jni/fbvncserver.c522
-rw-r--r--examples/example.c2
-rw-r--r--examples/pnmshow.c2
-rw-r--r--examples/pnmshow24.c2
-rw-r--r--libvncclient/rfbproto.c4
-rw-r--r--libvncserver/Makefile.am4
-rw-r--r--libvncserver/httpd.c30
-rw-r--r--libvncserver/rfbserver.c31
-rw-r--r--rfb/rfb.h7
-rw-r--r--rfb/rfbclient.h226
-rw-r--r--vncterm/Makefile.am2
-rw-r--r--webclients/Makefile.am4
-rw-r--r--webclients/index.vnc37
-rw-r--r--webclients/java-applet/Makefile.am5
-rw-r--r--webclients/java-applet/VncViewer.jar (renamed from classes/VncViewer.jar)bin35462 -> 35462 bytes
-rw-r--r--webclients/java-applet/javaviewer.pseudo_proxy.patch (renamed from classes/javaviewer.pseudo_proxy.patch)0
-rw-r--r--webclients/java-applet/ssl/Makefile.am (renamed from classes/ssl/Makefile.am)0
-rw-r--r--webclients/java-applet/ssl/README (renamed from classes/ssl/README)0
-rw-r--r--webclients/java-applet/ssl/SignedUltraViewerSSL.jar (renamed from classes/ssl/SignedUltraViewerSSL.jar)bin113117 -> 113117 bytes
-rw-r--r--webclients/java-applet/ssl/SignedVncViewer.jar (renamed from classes/ssl/SignedVncViewer.jar)bin89208 -> 89208 bytes
-rw-r--r--webclients/java-applet/ssl/UltraViewerSSL.jar (renamed from classes/ssl/UltraViewerSSL.jar)bin110040 -> 110040 bytes
-rw-r--r--webclients/java-applet/ssl/VncViewer.jar (renamed from classes/ssl/VncViewer.jar)bin86228 -> 86228 bytes
-rw-r--r--webclients/java-applet/ssl/index.vnc (renamed from classes/ssl/index.vnc)0
-rwxr-xr-xwebclients/java-applet/ssl/onetimekey (renamed from classes/ssl/onetimekey)0
-rw-r--r--webclients/java-applet/ssl/proxy.vnc (renamed from classes/ssl/proxy.vnc)0
-rwxr-xr-xwebclients/java-applet/ssl/ss_vncviewer (renamed from classes/ssl/ss_vncviewer)0
-rw-r--r--webclients/java-applet/ssl/tightvnc-1.3dev7_javasrc-vncviewer-cursor-colors+no-tab-traversal.patch (renamed from classes/ssl/tightvnc-1.3dev7_javasrc-vncviewer-cursor-colors+no-tab-traversal.patch)0
-rw-r--r--webclients/java-applet/ssl/tightvnc-1.3dev7_javasrc-vncviewer-ssl.patch (renamed from classes/ssl/tightvnc-1.3dev7_javasrc-vncviewer-ssl.patch)0
-rw-r--r--webclients/java-applet/ssl/ultra.vnc (renamed from classes/ssl/ultra.vnc)0
-rw-r--r--webclients/java-applet/ssl/ultraproxy.vnc (renamed from classes/ssl/ultraproxy.vnc)0
-rw-r--r--webclients/java-applet/ssl/ultrasigned.vnc (renamed from classes/ssl/ultrasigned.vnc)0
-rw-r--r--webclients/java-applet/ssl/ultravnc-102-JavaViewer-ssl-etc.patch (renamed from classes/ssl/ultravnc-102-JavaViewer-ssl-etc.patch)0
-rw-r--r--webclients/novnc/LICENSE.txt33
-rw-r--r--webclients/novnc/README.md93
l---------webclients/novnc/favicon.ico1
-rw-r--r--webclients/novnc/images/clipboard.pngbin0 -> 501 bytes
-rw-r--r--webclients/novnc/images/connect.pngbin0 -> 404 bytes
-rw-r--r--webclients/novnc/images/ctrlaltdel.pngbin0 -> 317 bytes
-rw-r--r--webclients/novnc/images/disconnect.pngbin0 -> 1378 bytes
-rw-r--r--webclients/novnc/images/drag.pngbin0 -> 963 bytes
-rw-r--r--webclients/novnc/images/favicon.icobin0 -> 1150 bytes
-rw-r--r--webclients/novnc/images/favicon.pngbin0 -> 453 bytes
-rw-r--r--webclients/novnc/images/keyboard.pngbin0 -> 1283 bytes
-rw-r--r--webclients/novnc/images/mouse_left.pngbin0 -> 511 bytes
-rw-r--r--webclients/novnc/images/mouse_middle.pngbin0 -> 517 bytes
-rw-r--r--webclients/novnc/images/mouse_none.pngbin0 -> 497 bytes
-rw-r--r--webclients/novnc/images/mouse_right.pngbin0 -> 513 bytes
-rw-r--r--webclients/novnc/images/screen_320x460.pngbin0 -> 12778 bytes
-rw-r--r--webclients/novnc/images/screen_57x57.pngbin0 -> 1807 bytes
-rw-r--r--webclients/novnc/images/screen_700x700.pngbin0 -> 17930 bytes
-rw-r--r--webclients/novnc/images/settings.pngbin0 -> 2495 bytes
-rw-r--r--webclients/novnc/include/Orbitron700.ttfbin0 -> 38580 bytes
-rw-r--r--webclients/novnc/include/Orbitron700.woffbin0 -> 17472 bytes
-rw-r--r--webclients/novnc/include/base.css380
-rw-r--r--webclients/novnc/include/base64.js147
-rw-r--r--webclients/novnc/include/black.css45
-rw-r--r--webclients/novnc/include/blue.css27
-rw-r--r--webclients/novnc/include/des.js273
-rw-r--r--webclients/novnc/include/display.js671
-rw-r--r--webclients/novnc/include/input.js1884
-rw-r--r--webclients/novnc/include/logo.js1
-rw-r--r--webclients/novnc/include/playback.js90
-rw-r--r--webclients/novnc/include/rfb.js1613
-rw-r--r--webclients/novnc/include/ui.js629
-rw-r--r--webclients/novnc/include/util.js276
-rw-r--r--webclients/novnc/include/vnc.js42
-rw-r--r--webclients/novnc/include/web-socket-js/README.txt109
-rw-r--r--webclients/novnc/include/web-socket-js/WebSocketMain.swfbin0 -> 175746 bytes
-rw-r--r--webclients/novnc/include/web-socket-js/swfobject.js4
-rw-r--r--webclients/novnc/include/web-socket-js/web_socket.js341
-rw-r--r--webclients/novnc/include/websock.js347
-rw-r--r--webclients/novnc/include/webutil.js148
-rw-r--r--webclients/novnc/vnc.html180
-rw-r--r--webclients/novnc/vnc_auto.html116
90 files changed, 8476 insertions, 117 deletions
diff --git a/.gitignore b/.gitignore
index 0402019..1699401 100644
--- a/.gitignore
+++ b/.gitignore
@@ -39,7 +39,7 @@ client_examples/backchannel
client_examples/ppmtest
config.guess
config.sub
-contrib/zippy
+examples/zippy
examples/backchannel
examples/blooptest
examples/camera
diff --git a/AUTHORS b/AUTHORS
index ba75f64..c66fcda 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -33,19 +33,9 @@ Steven Carr, Uwe Völker, Charles Coffing, Guillaume Rousse,
Alessandro Praduroux, Brad Hards, Timo Ketola, Christian Ehrlicher,
Noriaki Yamazaki, Ben Klopfenstein, Vic Lee, Christian Beier,
Alexander Dorokhine, Corentin Chary, Wouter Van Meir, George Kiagiadakis,
-Joel Martin and Gernot Tenchio.
+Joel Martin, Gernot Tenchio, William Roberts, Cristian Rodríguez,
+George Fleury, Kan-Ru Chen and Steve Guo.
Probably I forgot quite a few people sending a patch here and there, which
really made a difference. Without those, some obscure bugs still would
be unfound.
-
-A hearty unthanks goes to Michael and Erick, who provided me with nothing
-but hollow words. Finally I got that configure and install working, but
-it would have been so much better for them not just to complain, but also
-help. As you showed me real egoism, you are the main reasons I am not
-working one dot to make this library less than GPL, so that nobody ever
-can make profit of my and others work without giving something back to me
-and others.
-
-Speaking of hollow words, another hearty unthanks goes to Sam, who thought
-he could let me work for him, not paying me in any way.
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 4990309..6e7b837 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -8,7 +8,7 @@ include(TestBigEndian)
set(PACKAGE_NAME "LibVNCServer")
set(FULL_PACKAGE_NAME "LibVNCServer")
-set(PACKAGE_VERSION "0.9.8")
+set(PACKAGE_VERSION "0.9.8.1")
set(PROJECT_BUGREPORT_PATH "http://sourceforge.net/projects/libvncserver")
set(CMAKE_C_FLAGS "-O2 -W -Wall -g")
set(LIBVNCSERVER_DIR ${CMAKE_SOURCE_DIR}/libvncserver)
diff --git a/ChangeLog b/ChangeLog
index 5dc55b8..cda81ed 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,21 @@
+2011-10-12 Christian Beier <dontmind@freeshell.org>
+
+ * CMakeLists.txt, NEWS, configure.ac: Update version number in
+ autotools && cmake, NEWS entry.
+
+2011-10-08 Johannes Schindelin <johannes.schindelin@gmx.de>
+
+ * rfb/rfbclient.h: Hopefully fix the crash when updating from 0.9.7
+ or earlier For backwards-compatibility reasons, we can only add struct members
+ to the end. That way, existing callers still can use newer
+ libraries, as the structs are always allocated by the library (and
+ therefore guaranteed to have the correct size) and still rely on the
+ same position of the parts the callers know about. Reported by Luca Falavigna. Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
+
+2011-03-30 Christian Beier <dontmind@freeshell.org>
+
+ * ChangeLog: Update ChangeLog for 0.9.8.
+
2011-03-29 Christian Beier <dontmind@freeshell.org>
* README: Remove RDP from the README description. We do VNC but no RDP. Pointed out by Vic Lee, thanks!
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/NEWS b/NEWS
index 4849682..78bfcee 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,9 @@
+0.9.8.2
+ - Fixed a regression that crept in with the Apple Remote Desktop support.
+
+0.9.8.1
+ - Fixed an ABI compatibility issue.
+
0.9.8
- Overall changes:
* Automagically generated API documentation using doxygen.
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 @@
-<!-- index.vnc - default html page for Java VNC viewer applet. On any file
- ending in .vnc, the HTTP server embedded in Xvnc will substitute the
- following variables when preceded by a dollar: USER, DESKTOP, DISPLAY,
- APPLETWIDTH, APPLETHEIGHT, WIDTH, HEIGHT, PORT, PARAMS. Use two dollar
- signs ($$) to get a dollar sign in the generated html. -->
-
-<HTML>
-<TITLE>
-$USER's $DESKTOP desktop ($DISPLAY)
-</TITLE>
-<APPLET CODE=VncViewer.class ARCHIVE=VncViewer.jar
- WIDTH=$APPLETWIDTH HEIGHT=$APPLETHEIGHT>
-<param name=PORT value=$PORT>
-<param name="Open New Window" value=yes>
-</APPLET>
-<BR>
-<A href="http://www.tightvnc.com/">www.TightVNC.com</A>
-</HTML>
diff --git a/client_examples/SDLvncviewer.c b/client_examples/SDLvncviewer.c
index 0f6992a..5fa8f2c 100644
--- a/client_examples/SDLvncviewer.c
+++ b/client_examples/SDLvncviewer.c
@@ -16,7 +16,7 @@ struct { int sdl; int rfb; } buttonMapping[]={
{0,0}
};
-static int enableResizable, viewOnly, listenLoop, buttonMask;
+static int enableResizable = 1, viewOnly, listenLoop, buttonMask;
#ifdef SDL_ASYNCBLIT
int sdlFlags = SDL_HWSURFACE | SDL_ASYNCBLIT | SDL_HWACCEL;
#else
@@ -487,6 +487,8 @@ int main(int argc,char** argv) {
viewOnly = 1;
else if (!strcmp(argv[i], "-resizable"))
enableResizable = 1;
+ else if (!strcmp(argv[i], "-no-resizable"))
+ enableResizable = 0;
else if (!strcmp(argv[i], "-listen")) {
listenLoop = 1;
argv[i] = "-listennofork";
diff --git a/configure.ac b/configure.ac
index a986912..31aa1f2 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,9 +1,9 @@
# Process this file with autoconf to produce a configure script.
-AC_INIT(LibVNCServer, 0.9.8, http://sourceforge.net/projects/libvncserver)
-AM_INIT_AUTOMAKE(LibVNCServer, 0.9.8)
+AC_INIT(LibVNCServer, 0.9.8.1, http://sourceforge.net/projects/libvncserver)
+AM_INIT_AUTOMAKE(LibVNCServer, 0.9.8.1)
+m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
AM_CONFIG_HEADER(rfbconfig.h)
AX_PREFIX_CONFIG_H([rfb/rfbconfig.h])
-AM_SILENT_RULES([yes])
# Checks for programs.
AC_PROG_CC
@@ -68,7 +68,6 @@ elif test "x$uname_s" = "xDarwin"; then
fi
# Check for OpenSSL
-
AH_TEMPLATE(HAVE_LIBCRYPT, [libcrypt library present])
AC_ARG_WITH(crypt,
[ --without-crypt disable support for libcrypt],,)
@@ -130,22 +129,6 @@ if test "x$with_ssl" != "xno"; then
fi
fi
AC_SUBST(SSL_LIBS)
-
- if test "x$HAVE_LIBSSL" != "xtrue" -a "x$with_ssl" != "xno"; then
- AC_MSG_WARN([
-==========================================================================
-*** The openssl encryption library libssl.so was not found. ***
-An x11vnc built this way will not support SSL encryption. To enable
-SSL install the necessary development packages (perhaps it is named
-something like libssl-dev) and run configure again.
-==========================================================================
-])
- sleep 5
- elif test "x$with_ssl" != "xno"; then
- AC_CHECK_LIB(ssl, X509_print_ex_fp,
- [AC_DEFINE(HAVE_X509_PRINT_EX_FP) HAVE_X509_PRINT_EX_FP="true"], , $SSL_LIBS
- )
- fi
AM_CONDITIONAL(HAVE_LIBSSL, test ! -z "$SSL_LIBS")
# Checks for X libraries
@@ -798,6 +781,20 @@ if test "x$with_gnutls" != "xno"; then
fi
AM_CONDITIONAL(HAVE_GNUTLS, test ! -z "$GNUTLS_LIBS")
+# warn if neither GnuTLS nor OpenSSL are available
+if test -z "$SSL_LIBS" -a -z "$GNUTLS_LIBS"; then
+ AC_MSG_WARN([
+==========================================================================
+*** No encryption library could be found. ***
+A libvncserver/libvncclient built this way will not support SSL encryption.
+To enable SSL install the necessary development packages (perhaps it is named
+something like libssl-dev or gnutls-dev) and run configure again.
+==========================================================================
+])
+ sleep 5
+fi
+
+
# IPv6
AH_TEMPLATE(IPv6, [Enable IPv6 support])
AC_ARG_WITH(ipv6,
@@ -884,6 +881,13 @@ AM_CONDITIONAL(LINUX, test -c /dev/vcsa1)
AC_CHECK_HEADER(ApplicationServices/ApplicationServices.h, HAVE_OSX="true")
AM_CONDITIONAL(OSX, test "$HAVE_OSX" = "true")
+# Check for Android specific header
+AC_CHECK_HEADER(android/api-level.h, HAVE_ANDROID="true")
+AM_CONDITIONAL(ANDROID, test "$HAVE_ANDROID" = "true")
+if test "$HAVE_ANDROID" = "true"; then
+ AC_DEFINE(HAVE_ANDROID, 1, [Android host system detected])
+fi
+
# On Solaris 2.7, write() returns ENOENT when it really means EAGAIN
AH_TEMPLATE(ENOENT_WORKAROUND, [work around when write() returns ENOENT but does not mean it])
case `(uname -sr) 2>/dev/null` in
@@ -910,9 +914,11 @@ AC_CONFIG_FILES([Makefile
libvncclient.pc
libvncserver/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/Makefile.am b/examples/Makefile.am
index 92909bf..29d3774 100644
--- a/examples/Makefile.am
+++ b/examples/Makefile.am
@@ -6,6 +6,10 @@ MAC=mac
mac_LDFLAGS=-framework ApplicationServices -framework Carbon -framework IOKit
endif
+if ANDROID
+SUBDIRS=android
+endif
+
if WITH_TIGHTVNC_FILETRANSFER
FILETRANSFER=filetransfer
endif
diff --git a/examples/android/Makefile.am b/examples/android/Makefile.am
new file mode 100644
index 0000000..23bfe8f
--- /dev/null
+++ b/examples/android/Makefile.am
@@ -0,0 +1,6 @@
+INCLUDES = -I$(top_srcdir)
+LDADD = $(top_srcdir)/libvncserver/libvncserver.la @WSOCKLIB@
+
+noinst_PROGRAMS=androidvncserver
+androidvncserver_SOURCES=jni/fbvncserver.c
+
diff --git a/examples/android/README b/examples/android/README
new file mode 100644
index 0000000..57e14cf
--- /dev/null
+++ b/examples/android/README
@@ -0,0 +1,63 @@
+
+This example VNC server for Android is adopted from
+http://code.google.com/p/android-vnc-server/ with some additional
+fixes applied.
+
+To build, you'll need the Android Native Development Kit from
+http://developer.android.com/sdk/ndk/.
+
+
+Building with autotools
+-----------------------
+
+This has the advantage that the LibVNCServer sources are properly set up
+using the configure script.
+
+1. Read <NDK location>/docs/STANDALONE-TOOLCHAIN.html.
+
+2. Setup your toolchain according to step 3 in the above file.
+
+3. Execute
+
+ ./configure --host=arm-eabi CC=arm-linux-androideabi-gcc
+
+ in the LibVNCServer root directory.
+
+4. Execute
+
+ make
+
+ in the LibVNCServer root directory. This will build the whole
+ LibVNCServer distribution for Android, including androidvncserver.
+
+
+
+
+Building with the NDK build system
+----------------------------------
+
+This is probably easier than the autotools method, but you'll have to edit
+some files manually.
+
+1. Edit rfb/rfbconfig.h to match your Android target. For instance, comment out
+ LIBVNCSERVER_HAVE_LIBJPEG if you don't have libjpeg for Android.
+
+2. Edit the HAVE_X variables in jni/Android.mk accordingly.
+
+3. Execute
+
+ ndk-build -C .
+
+ in the examples/android directory. The resulting binary will be in libs/.
+
+
+
+Installing && Running
+---------------------
+
+This can be done via
+
+ adb push androidvncserver /data/local/
+ adb shell /data/local/androidvncserver
+
+
diff --git a/examples/android/jni/Android.mk b/examples/android/jni/Android.mk
new file mode 100644
index 0000000..731a790
--- /dev/null
+++ b/examples/android/jni/Android.mk
@@ -0,0 +1,65 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LIBVNCSERVER_ROOT:=../../..
+
+HAVE_LIBZ=1
+#HAVE_LIBJPEG=1
+
+ifdef HAVE_LIBZ
+ZLIBSRCS := \
+ $(LIBVNCSERVER_ROOT)/libvncserver/zlib.c \
+ $(LIBVNCSERVER_ROOT)/libvncserver/zrle.c \
+ $(LIBVNCSERVER_ROOT)/libvncserver/zrleoutstream.c \
+ $(LIBVNCSERVER_ROOT)/libvncserver/zrlepalettehelper.c \
+ $(LIBVNCSERVER_ROOT)/common/zywrletemplate.c
+ifdef HAVE_LIBJPEG
+TIGHTSRCS := $(LIBVNCSERVER_ROOT)/libvncserver/tight.c
+endif
+endif
+
+LOCAL_SRC_FILES:= \
+ fbvncserver.c \
+ $(LIBVNCSERVER_ROOT)/libvncserver/main.c \
+ $(LIBVNCSERVER_ROOT)/libvncserver/rfbserver.c \
+ $(LIBVNCSERVER_ROOT)/libvncserver/rfbregion.c \
+ $(LIBVNCSERVER_ROOT)/libvncserver/auth.c \
+ $(LIBVNCSERVER_ROOT)/libvncserver/sockets.c \
+ $(LIBVNCSERVER_ROOT)/libvncserver/stats.c \
+ $(LIBVNCSERVER_ROOT)/libvncserver/corre.c \
+ $(LIBVNCSERVER_ROOT)/libvncserver/hextile.c \
+ $(LIBVNCSERVER_ROOT)/libvncserver/rre.c \
+ $(LIBVNCSERVER_ROOT)/libvncserver/translate.c \
+ $(LIBVNCSERVER_ROOT)/libvncserver/cutpaste.c \
+ $(LIBVNCSERVER_ROOT)/libvncserver/httpd.c \
+ $(LIBVNCSERVER_ROOT)/libvncserver/cursor.c \
+ $(LIBVNCSERVER_ROOT)/libvncserver/font.c \
+ $(LIBVNCSERVER_ROOT)/libvncserver/draw.c \
+ $(LIBVNCSERVER_ROOT)/libvncserver/selbox.c \
+ $(LIBVNCSERVER_ROOT)/common/d3des.c \
+ $(LIBVNCSERVER_ROOT)/common/vncauth.c \
+ $(LIBVNCSERVER_ROOT)/libvncserver/cargs.c \
+ $(LIBVNCSERVER_ROOT)/common/minilzo.c \
+ $(LIBVNCSERVER_ROOT)/libvncserver/ultra.c \
+ $(LIBVNCSERVER_ROOT)/libvncserver/scale.c \
+ $(ZLIBSRCS) \
+ $(TIGHTSRCS)
+
+LOCAL_C_INCLUDES := \
+ $(LOCAL_PATH) \
+ $(LOCAL_PATH)/$(LIBVNCSERVER_ROOT)/libvncserver \
+ $(LOCAL_PATH)/$(LIBVNCSERVER_ROOT)/common \
+ $(LOCAL_PATH)/$(LIBVNCSERVER_ROOT) \
+ external/jpeg
+
+ifdef HAVE_LIBZ
+LOCAL_SHARED_LIBRARIES := libz
+LOCAL_LDLIBS := -lz
+endif
+ifdef HAVE_LIBJPEG
+LOCAL_STATIC_LIBRARIES := libjpeg
+endif
+
+LOCAL_MODULE:= androidvncserver
+
+include $(BUILD_EXECUTABLE)
diff --git a/examples/android/jni/fbvncserver.c b/examples/android/jni/fbvncserver.c
new file mode 100644
index 0000000..a8c4827
--- /dev/null
+++ b/examples/android/jni/fbvncserver.c
@@ -0,0 +1,522 @@
+/*
+ * $Id$
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2, or (at your option) any
+ * later version.
+ *
+ * This program 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. See the GNU
+ * General Public License for more details.
+ *
+ * This project is an adaptation of the original fbvncserver for the iPAQ
+ * and Zaurus.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <unistd.h>
+#include <sys/mman.h>
+#include <sys/ioctl.h>
+
+#include <sys/stat.h>
+#include <sys/sysmacros.h> /* For makedev() */
+
+#include <fcntl.h>
+#include <linux/fb.h>
+#include <linux/input.h>
+
+#include <assert.h>
+#include <errno.h>
+
+/* libvncserver */
+#include "rfb/rfb.h"
+#include "rfb/keysym.h"
+
+/*****************************************************************************/
+
+/* Android does not use /dev/fb0. */
+#define FB_DEVICE "/dev/graphics/fb0"
+static char KBD_DEVICE[256] = "/dev/input/event3";
+static char TOUCH_DEVICE[256] = "/dev/input/event1";
+static struct fb_var_screeninfo scrinfo;
+static int fbfd = -1;
+static int kbdfd = -1;
+static int touchfd = -1;
+static unsigned short int *fbmmap = MAP_FAILED;
+static unsigned short int *vncbuf;
+static unsigned short int *fbbuf;
+
+/* Android already has 5900 bound natively. */
+#define VNC_PORT 5901
+static rfbScreenInfoPtr vncscr;
+
+static int xmin, xmax;
+static int ymin, ymax;
+
+/* No idea, just copied from fbvncserver as part of the frame differerencing
+ * algorithm. I will probably be later rewriting all of this. */
+static struct varblock_t
+{
+ int min_i;
+ int min_j;
+ int max_i;
+ int max_j;
+ int r_offset;
+ int g_offset;
+ int b_offset;
+ int rfb_xres;
+ int rfb_maxy;
+} varblock;
+
+/*****************************************************************************/
+
+static void keyevent(rfbBool down, rfbKeySym key, rfbClientPtr cl);
+static void ptrevent(int buttonMask, int x, int y, rfbClientPtr cl);
+
+/*****************************************************************************/
+
+static void init_fb(void)
+{
+ size_t pixels;
+ size_t bytespp;
+
+ if ((fbfd = open(FB_DEVICE, O_RDONLY)) == -1)
+ {
+ printf("cannot open fb device %s\n", FB_DEVICE);
+ exit(EXIT_FAILURE);
+ }
+
+ if (ioctl(fbfd, FBIOGET_VSCREENINFO, &scrinfo) != 0)
+ {
+ printf("ioctl error\n");
+ exit(EXIT_FAILURE);
+ }
+
+ pixels = scrinfo.xres * scrinfo.yres;
+ bytespp = scrinfo.bits_per_pixel / 8;
+
+ fprintf(stderr, "xres=%d, yres=%d, xresv=%d, yresv=%d, xoffs=%d, yoffs=%d, bpp=%d\n",
+ (int)scrinfo.xres, (int)scrinfo.yres,
+ (int)scrinfo.xres_virtual, (int)scrinfo.yres_virtual,
+ (int)scrinfo.xoffset, (int)scrinfo.yoffset,
+ (int)scrinfo.bits_per_pixel);
+
+ fbmmap = mmap(NULL, pixels * bytespp, PROT_READ, MAP_SHARED, fbfd, 0);
+
+ if (fbmmap == MAP_FAILED)
+ {
+ printf("mmap failed\n");
+ exit(EXIT_FAILURE);
+ }
+}
+
+static void cleanup_fb(void)
+{
+ if(fbfd != -1)
+ {
+ close(fbfd);
+ }
+}
+
+static void init_kbd()
+{
+ if((kbdfd = open(KBD_DEVICE, O_RDWR)) == -1)
+ {
+ printf("cannot open kbd device %s\n", KBD_DEVICE);
+ exit(EXIT_FAILURE);
+ }
+}
+
+static void cleanup_kbd()
+{
+ if(kbdfd != -1)
+ {
+ close(kbdfd);
+ }
+}
+
+static void init_touch()
+{
+ struct input_absinfo info;
+ if((touchfd = open(TOUCH_DEVICE, O_RDWR)) == -1)
+ {
+ printf("cannot open touch device %s\n", TOUCH_DEVICE);
+ exit(EXIT_FAILURE);
+ }
+ // Get the Range of X and Y
+ if(ioctl(touchfd, EVIOCGABS(ABS_X), &info)) {
+ printf("cannot get ABS_X info, %s\n", strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+ xmin = info.minimum;
+ xmax = info.maximum;
+ if(ioctl(touchfd, EVIOCGABS(ABS_Y), &info)) {
+ printf("cannot get ABS_Y, %s\n", strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+ ymin = info.minimum;
+ ymax = info.maximum;
+
+}
+
+static void cleanup_touch()
+{
+ if(touchfd != -1)
+ {
+ close(touchfd);
+ }
+}
+
+/*****************************************************************************/
+
+static void init_fb_server(int argc, char **argv)
+{
+ printf("Initializing server...\n");
+
+ /* Allocate the VNC server buffer to be managed (not manipulated) by
+ * libvncserver. */
+ vncbuf = calloc(scrinfo.xres * scrinfo.yres, scrinfo.bits_per_pixel / 8);
+ assert(vncbuf != NULL);
+
+ /* Allocate the comparison buffer for detecting drawing updates from frame
+ * to frame. */
+ fbbuf = calloc(scrinfo.xres * scrinfo.yres, scrinfo.bits_per_pixel / 8);
+ assert(fbbuf != NULL);
+
+ /* TODO: This assumes scrinfo.bits_per_pixel is 16. */
+ vncscr = rfbGetScreen(&argc, argv, scrinfo.xres, scrinfo.yres, 5, 2, (scrinfo.bits_per_pixel / 8));
+ assert(vncscr != NULL);
+
+ vncscr->desktopName = "Android";
+ vncscr->frameBuffer = (char *)vncbuf;
+ vncscr->alwaysShared = TRUE;
+ vncscr->httpDir = NULL;
+ vncscr->port = VNC_PORT;
+
+ vncscr->kbdAddEvent = keyevent;
+ vncscr->ptrAddEvent = ptrevent;
+
+ rfbInitServer(vncscr);
+
+ /* Mark as dirty since we haven't sent any updates at all yet. */
+ rfbMarkRectAsModified(vncscr, 0, 0, scrinfo.xres, scrinfo.yres);
+
+ /* No idea. */
+ varblock.r_offset = scrinfo.red.offset + scrinfo.red.length - 5;
+ varblock.g_offset = scrinfo.green.offset + scrinfo.green.length - 5;
+ varblock.b_offset = scrinfo.blue.offset + scrinfo.blue.length - 5;
+ varblock.rfb_xres = scrinfo.yres;
+ varblock.rfb_maxy = scrinfo.xres - 1;
+}
+
+/*****************************************************************************/
+void injectKeyEvent(uint16_t code, uint16_t value)
+{
+ struct input_event ev;
+ memset(&ev, 0, sizeof(ev));
+ gettimeofday(&ev.time,0);
+ ev.type = EV_KEY;
+ ev.code = code;
+ ev.value = value;
+ if(write(kbdfd, &ev, sizeof(ev)) < 0)
+ {
+ printf("write event failed, %s\n", strerror(errno));
+ }
+
+ printf("injectKey (%d, %d)\n", code , value);
+}
+
+static int keysym2scancode(rfbBool down, rfbKeySym key, rfbClientPtr cl)
+{
+ int scancode = 0;
+
+ int code = (int)key;
+ if (code>='0' && code<='9') {
+ scancode = (code & 0xF) - 1;
+ if (scancode<0) scancode += 10;
+ scancode += KEY_1;
+ } else if (code>=0xFF50 && code<=0xFF58) {
+ static const uint16_t map[] =
+ { KEY_HOME, KEY_LEFT, KEY_UP, KEY_RIGHT, KEY_DOWN,
+ KEY_SOFT1, KEY_SOFT2, KEY_END, 0 };
+ scancode = map[code & 0xF];
+ } else if (code>=0xFFE1 && code<=0xFFEE) {
+ static const uint16_t map[] =
+ { KEY_LEFTSHIFT, KEY_LEFTSHIFT,
+ KEY_COMPOSE, KEY_COMPOSE,
+ KEY_LEFTSHIFT, KEY_LEFTSHIFT,
+ 0,0,
+ KEY_LEFTALT, KEY_RIGHTALT,
+ 0, 0, 0, 0 };
+ scancode = map[code & 0xF];
+ } else if ((code>='A' && code<='Z') || (code>='a' && code<='z')) {
+ static const uint16_t map[] = {
+ KEY_A, KEY_B, KEY_C, KEY_D, KEY_E,
+ KEY_F, KEY_G, KEY_H, KEY_I, KEY_J,
+ KEY_K, KEY_L, KEY_M, KEY_N, KEY_O,
+ KEY_P, KEY_Q, KEY_R, KEY_S, KEY_T,
+ KEY_U, KEY_V, KEY_W, KEY_X, KEY_Y, KEY_Z };
+ scancode = map[(code & 0x5F) - 'A'];
+ } else {
+ switch (code) {
+ case 0x0003: scancode = KEY_CENTER; break;
+ case 0x0020: scancode = KEY_SPACE; break;
+ case 0x0023: scancode = KEY_SHARP; break;
+ case 0x0033: scancode = KEY_SHARP; break;
+ case 0x002C: scancode = KEY_COMMA; break;
+ case 0x003C: scancode = KEY_COMMA; break;
+ case 0x002E: scancode = KEY_DOT; break;
+ case 0x003E: scancode = KEY_DOT; break;
+ case 0x002F: scancode = KEY_SLASH; break;
+ case 0x003F: scancode = KEY_SLASH; break;
+ case 0x0032: scancode = KEY_EMAIL; break;
+ case 0x0040: scancode = KEY_EMAIL; break;
+ case 0xFF08: scancode = KEY_BACKSPACE; break;
+ case 0xFF1B: scancode = KEY_BACK; break;
+ case 0xFF09: scancode = KEY_TAB; break;
+ case 0xFF0D: scancode = KEY_ENTER; break;
+ case 0x002A: scancode = KEY_STAR; break;
+ case 0xFFBE: scancode = KEY_F1; break; // F1
+ case 0xFFBF: scancode = KEY_F2; break; // F2
+ case 0xFFC0: scancode = KEY_F3; break; // F3
+ case 0xFFC5: scancode = KEY_F4; break; // F8
+ case 0xFFC8: rfbShutdownServer(cl->screen,TRUE); break; // F11
+ }
+ }
+
+ return scancode;
+}
+
+static void keyevent(rfbBool down, rfbKeySym key, rfbClientPtr cl)
+{
+ int scancode;
+
+ printf("Got keysym: %04x (down=%d)\n", (unsigned int)key, (int)down);
+
+ if ((scancode = keysym2scancode(down, key, cl)))
+ {
+ injectKeyEvent(scancode, down);
+ }
+}
+
+void injectTouchEvent(int down, int x, int y)
+{
+ struct input_event ev;
+
+ // Calculate the final x and y
+ /* Fake touch screen always reports zero */
+ if (xmin != 0 && xmax != 0 && ymin != 0 && ymax != 0)
+ {
+ x = xmin + (x * (xmax - xmin)) / (scrinfo.xres);
+ y = ymin + (y * (ymax - ymin)) / (scrinfo.yres);
+ }
+
+ memset(&ev, 0, sizeof(ev));
+
+ // Then send a BTN_TOUCH
+ gettimeofday(&ev.time,0);
+ ev.type = EV_KEY;
+ ev.code = BTN_TOUCH;
+ ev.value = down;
+ if(write(touchfd, &ev, sizeof(ev)) < 0)
+ {
+ printf("write event failed, %s\n", strerror(errno));
+ }
+
+ // Then send the X
+ gettimeofday(&ev.time,0);
+ ev.type = EV_ABS;
+ ev.code = ABS_X;
+ ev.value = x;
+ if(write(touchfd, &ev, sizeof(ev)) < 0)
+ {
+ printf("write event failed, %s\n", strerror(errno));
+ }
+
+ // Then send the Y
+ gettimeofday(&ev.time,0);
+ ev.type = EV_ABS;
+ ev.code = ABS_Y;
+ ev.value = y;
+ if(write(touchfd, &ev, sizeof(ev)) < 0)
+ {
+ printf("write event failed, %s\n", strerror(errno));
+ }
+
+ // Finally send the SYN
+ gettimeofday(&ev.time,0);
+ ev.type = EV_SYN;
+ ev.code = 0;
+ ev.value = 0;
+ if(write(touchfd, &ev, sizeof(ev)) < 0)
+ {
+ printf("write event failed, %s\n", strerror(errno));
+ }
+
+ printf("injectTouchEvent (x=%d, y=%d, down=%d)\n", x , y, down);
+}
+
+static void ptrevent(int buttonMask, int x, int y, rfbClientPtr cl)
+{
+ /* Indicates either pointer movement or a pointer button press or release. The pointer is
+now at (x-position, y-position), and the current state of buttons 1 to 8 are represented
+by bits 0 to 7 of button-mask respectively, 0 meaning up, 1 meaning down (pressed).
+On a conventional mouse, buttons 1, 2 and 3 correspond to the left, middle and right
+buttons on the mouse. On a wheel mouse, each step of the wheel upwards is represented
+by a press and release of button 4, and each step downwards is represented by
+a press and release of button 5.
+ From: http://www.vislab.usyd.edu.au/blogs/index.php/2009/05/22/an-headerless-indexed-protocol-for-input-1?blog=61 */
+
+ //printf("Got ptrevent: %04x (x=%d, y=%d)\n", buttonMask, x, y);
+ if(buttonMask & 1) {
+ // Simulate left mouse event as touch event
+ injectTouchEvent(1, x, y);
+ injectTouchEvent(0, x, y);
+ }
+}
+
+#define PIXEL_FB_TO_RFB(p,r,g,b) ((p>>r)&0x1f001f)|(((p>>g)&0x1f001f)<<5)|(((p>>b)&0x1f001f)<<10)
+
+static void update_screen(void)
+{
+ unsigned int *f, *c, *r;
+ int x, y;
+
+ varblock.min_i = varblock.min_j = 9999;
+ varblock.max_i = varblock.max_j = -1;
+
+ f = (unsigned int *)fbmmap; /* -> framebuffer */
+ c = (unsigned int *)fbbuf; /* -> compare framebuffer */
+ r = (unsigned int *)vncbuf; /* -> remote framebuffer */
+
+ for (y = 0; y < scrinfo.yres; y++)
+ {
+ /* Compare every 2 pixels at a time, assuming that changes are likely
+ * in pairs. */
+ for (x = 0; x < scrinfo.xres; x += 2)
+ {
+ unsigned int pixel = *f;
+
+ if (pixel != *c)
+ {
+ *c = pixel;
+
+ /* XXX: Undo the checkered pattern to test the efficiency
+ * gain using hextile encoding. */
+ if (pixel == 0x18e320e4 || pixel == 0x20e418e3)
+ pixel = 0x18e318e3;
+
+ *r = PIXEL_FB_TO_RFB(pixel,
+ varblock.r_offset, varblock.g_offset, varblock.b_offset);
+
+ if (x < varblock.min_i)
+ varblock.min_i = x;
+ else
+ {
+ if (x > varblock.max_i)
+ varblock.max_i = x;
+
+ if (y > varblock.max_j)
+ varblock.max_j = y;
+ else if (y < varblock.min_j)
+ varblock.min_j = y;
+ }
+ }
+
+ f++, c++;
+ r++;
+ }
+ }
+
+ if (varblock.min_i < 9999)
+ {
+ if (varblock.max_i < 0)
+ varblock.max_i = varblock.min_i;
+
+ if (varblock.max_j < 0)
+ varblock.max_j = varblock.min_j;
+
+ fprintf(stderr, "Dirty page: %dx%d+%d+%d...\n",
+ (varblock.max_i+2) - varblock.min_i, (varblock.max_j+1) - varblock.min_j,
+ varblock.min_i, varblock.min_j);
+
+ rfbMarkRectAsModified(vncscr, varblock.min_i, varblock.min_j,
+ varblock.max_i + 2, varblock.max_j + 1);
+
+ rfbProcessEvents(vncscr, 10000);
+ }
+}
+
+/*****************************************************************************/
+
+void print_usage(char **argv)
+{
+ printf("%s [-k device] [-t device] [-h]\n"
+ "-k device: keyboard device node, default is /dev/input/event3\n"
+ "-t device: touch device node, default is /dev/input/event1\n"
+ "-h : print this help\n");
+}
+
+int main(int argc, char **argv)
+{
+ if(argc > 1)
+ {
+ int i=1;
+ while(i < argc)
+ {
+ if(*argv[i] == '-')
+ {
+ switch(*(argv[i] + 1))
+ {
+ case 'h':
+ print_usage(argv);
+ exit(0);
+ break;
+ case 'k':
+ i++;
+ strcpy(KBD_DEVICE, argv[i]);
+ break;
+ case 't':
+ i++;
+ strcpy(TOUCH_DEVICE, argv[i]);
+ break;
+ }
+ }
+ i++;
+ }
+ }
+
+ printf("Initializing framebuffer device " FB_DEVICE "...\n");
+ init_fb();
+ printf("Initializing keyboard device %s ...\n", KBD_DEVICE);
+ init_kbd();
+ printf("Initializing touch device %s ...\n", TOUCH_DEVICE);
+ init_touch();
+
+ printf("Initializing VNC server:\n");
+ printf(" width: %d\n", (int)scrinfo.xres);
+ printf(" height: %d\n", (int)scrinfo.yres);
+ printf(" bpp: %d\n", (int)scrinfo.bits_per_pixel);
+ printf(" port: %d\n", (int)VNC_PORT);
+ init_fb_server(argc, argv);
+
+ /* Implement our own event loop to detect changes in the framebuffer. */
+ while (1)
+ {
+ while (vncscr->clientHead == NULL)
+ rfbProcessEvents(vncscr, 100000);
+
+ rfbProcessEvents(vncscr, 100000);
+ update_screen();
+ }
+
+ printf("Cleaning up...\n");
+ cleanup_fb();
+ cleanup_kdb();
+ cleanup_touch();
+}
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/libvncclient/rfbproto.c b/libvncclient/rfbproto.c
index d424b04..a7faab1 100644
--- a/libvncclient/rfbproto.c
+++ b/libvncclient/rfbproto.c
@@ -569,8 +569,8 @@ ReadSupportedSecurityType(rfbClient* client, uint32_t *result, rfbBool subAuth)
rfbClientLog("%d) Received security type %d\n", loop, tAuth[loop]);
if (flag) continue;
if (tAuth[loop]==rfbVncAuth || tAuth[loop]==rfbNoAuth ||
- tAuth[loop]==rfbARD ||
- (!subAuth && (tAuth[loop]==rfbTLS || tAuth[loop]==rfbVeNCrypt)))
+ (tAuth[loop]==rfbARD && client->GetCredential) ||
+ (!subAuth && (tAuth[loop]==rfbTLS || (tAuth[loop]==rfbVeNCrypt && client->GetCredential))))
{
if (!subAuth && client->clientAuthSchemes)
{
diff --git a/libvncserver/Makefile.am b/libvncserver/Makefile.am
index 287f1c9..fce398d 100644
--- a/libvncserver/Makefile.am
+++ b/libvncserver/Makefile.am
@@ -47,10 +47,6 @@ if HAVE_LIBZ
ZLIBSRCS = zlib.c zrle.c zrleoutstream.c zrlepalettehelper.c ../common/zywrletemplate.c
if HAVE_LIBJPEG
TIGHTSRCS = tight.c
-else
-if HAVE_LIBPNG
-TIGHTSRCS = tight.c
-endif
endif
endif
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 <tcpd.h>
#endif
-#define connection_close
-#ifndef connection_close
-
-#define NOT_FOUND_STR "HTTP/1.0 404 Not found\r\n\r\n" \
- "<HEAD><TITLE>File Not Found</TITLE></HEAD>\n" \
- "<BODY><H1>File Not Found</H1></BODY>\n"
-
-#define INVALID_REQUEST_STR "HTTP/1.0 400 Invalid Request\r\n\r\n" \
- "<HEAD><TITLE>Invalid Request</TITLE></HEAD>\n" \
- "<BODY><H1>Invalid request</H1></BODY>\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" \
"<HEAD><TITLE>File Not Found</TITLE></HEAD>\n" \
@@ -82,9 +68,10 @@
"<HEAD><TITLE>Invalid Request</TITLE></HEAD>\n" \
"<BODY><H1>Invalid request</H1></BODY>\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/libvncserver/rfbserver.c b/libvncserver/rfbserver.c
index 63f21db..b42a5ea 100644
--- a/libvncserver/rfbserver.c
+++ b/libvncserver/rfbserver.c
@@ -494,6 +494,21 @@ rfbClientConnectionGone(rfbClientPtr cl)
if (cl->next)
cl->next->prev = cl->prev;
+ UNLOCK(rfbClientListMutex);
+
+#ifdef LIBVNCSERVER_HAVE_LIBPTHREAD
+ if(cl->screen->backgroundLoop != FALSE) {
+ int i;
+ do {
+ LOCK(cl->refCountMutex);
+ i=cl->refCount;
+ if(i>0)
+ WAIT(cl->deleteCond,cl->refCountMutex);
+ UNLOCK(cl->refCountMutex);
+ } while(i>0);
+ }
+#endif
+
if(cl->sock>=0)
close(cl->sock);
@@ -510,21 +525,6 @@ rfbClientConnectionGone(rfbClientPtr cl)
free(cl->beforeEncBuf);
free(cl->afterEncBuf);
-#ifdef LIBVNCSERVER_HAVE_LIBPTHREAD
- if(cl->screen->backgroundLoop != FALSE) {
- int i;
- do {
- LOCK(cl->refCountMutex);
- i=cl->refCount;
- if(i>0)
- WAIT(cl->deleteCond,cl->refCountMutex);
- UNLOCK(cl->refCountMutex);
- } while(i>0);
- }
-#endif
-
- UNLOCK(rfbClientListMutex);
-
if(cl->sock>=0)
FD_CLR(cl->sock,&(cl->screen->allFds));
@@ -569,6 +569,7 @@ rfbClientConnectionGone(rfbClientPtr cl)
TINI_MUTEX(cl->sendMutex);
rfbPrintStats(cl);
+ rfbResetStats(cl);
free(cl);
}
diff --git a/rfb/rfb.h b/rfb/rfb.h
index 11d1447..3317e54 100644
--- a/rfb/rfb.h
+++ b/rfb/rfb.h
@@ -43,6 +43,11 @@ extern "C"
#include <string.h>
#include <rfb/rfbproto.h>
+#if defined(ANDROID) || defined(LIBVNCSERVER_HAVE_ANDROID)
+#include <arpa/inet.h>
+#include <sys/select.h>
+#endif
+
#ifdef LIBVNCSERVER_HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
@@ -1092,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/rfb/rfbclient.h b/rfb/rfbclient.h
index f3bd11d..b3f2cd7 100644
--- a/rfb/rfbclient.h
+++ b/rfb/rfbclient.h
@@ -140,7 +140,26 @@ typedef union _rfbCredential
struct _rfbClient;
+/**
+ * Handles a text chat message. If your application should accept text messages
+ * from the server, define a function with this prototype and set
+ * client->HandleTextChat to a pointer to that function subsequent to your
+ * rfbGetClient() call.
+ * @param client The client which called the text chat handler
+ * @param value text length if text != NULL, or one of rfbTextChatOpen,
+ * rfbTextChatClose, rfbTextChatFinished if text == NULL
+ * @param text The text message from the server
+ */
typedef void (*HandleTextChatProc)(struct _rfbClient* client, int value, char *text);
+/**
+ * Handles XVP server messages. If your application sends XVP messages to the
+ * server, you'll want to handle the server's XVP_FAIL and XVP_INIT responses.
+ * Define a function with this prototype and set client->HandleXvpMsg to a
+ * pointer to that function subsequent to your rfbGetClient() call.
+ * @param client The client which called the XVP message handler
+ * @param version The highest XVP extension version that the server supports
+ * @param opcode The opcode. 0 is XVP_FAIL, 1 is XVP_INIT
+ */
typedef void (*HandleXvpMsgProc)(struct _rfbClient* client, uint8_t version, uint8_t opcode);
typedef void (*HandleKeyboardLedStateProc)(struct _rfbClient* client, int value, int pad);
typedef rfbBool (*HandleCursorPosProc)(struct _rfbClient* client, int x, int y);
@@ -192,9 +211,6 @@ typedef struct _rfbClient {
rfbPixelFormat format;
rfbServerInitMsg si;
- /* listen.c */
- int listenSock;
-
/* sockets.c */
#define RFB_BUF_SIZE 8192
char buf[RFB_BUF_SIZE];
@@ -273,7 +289,6 @@ typedef struct _rfbClient {
SoftCursorLockAreaProc SoftCursorLockArea;
SoftCursorUnlockScreenProc SoftCursorUnlockScreen;
GotFrameBufferUpdateProc GotFrameBufferUpdate;
- FinishedFrameBufferUpdateProc FinishedFrameBufferUpdate;
/** the pointer returned by GetPassword will be freed after use! */
GetPasswordProc GetPassword;
MallocFrameBufferProc MallocFrameBuffer;
@@ -325,6 +340,11 @@ typedef struct _rfbClient {
/** hook to handle xvp server messages */
HandleXvpMsgProc HandleXvpMsg;
+
+ /* listen.c */
+ int listenSock;
+
+ FinishedFrameBufferUpdateProc FinishedFrameBufferUpdate;
} rfbClient;
/* cursor.c */
@@ -345,19 +365,123 @@ extern rfbBool ConnectToRFBServer(rfbClient* client,const char *hostname, int po
extern rfbBool ConnectToRFBRepeater(rfbClient* client,const char *repeaterHost, int repeaterPort, const char *destHost, int destPort);
extern void SetClientAuthSchemes(rfbClient* client,const uint32_t *authSchemes, int size);
extern rfbBool InitialiseRFBConnection(rfbClient* client);
+/**
+ * Sends format and encoding parameters to the server. Your application can
+ * modify the 'client' data structure directly. However some changes to this
+ * structure must be communicated back to the server. For instance, if you
+ * change the encoding to hextile, the server needs to know that it should send
+ * framebuffer updates in hextile format. Likewise if you change the pixel
+ * format of the framebuffer, the server must be notified about this as well.
+ * Call this function to propagate your changes of the local 'client' structure
+ * over to the server.
+ * @li Encoding type
+ * @li RFB protocol extensions announced via pseudo-encodings
+ * @li Framebuffer pixel format (like RGB vs ARGB)
+ * @li Remote cursor support
+ * @param client The client in which the format or encodings have been changed
+ * @return true if the format or encodings were sent to the server successfully,
+ * false otherwise
+ */
extern rfbBool SetFormatAndEncodings(rfbClient* client);
extern rfbBool SendIncrementalFramebufferUpdateRequest(rfbClient* client);
+/**
+ * Sends a framebuffer update request to the server. A VNC client may request an
+ * update from the server at any time. You can also specify which portions of
+ * the screen you want updated. This can be handy if a pointer is at certain
+ * location and the user pressed a mouse button, for instance. Then you can
+ * immediately request an update of the region around the pointer from the
+ * server.
+ * @note The coordinate system is a left-handed Cartesian coordinate system with
+ * the Z axis (unused) pointing out of the screen. Alternately you can think of
+ * it as a right-handed Cartesian coordinate system with the Z axis pointing
+ * into the screen. The origin is at the upper left corner of the framebuffer.
+ * @param client The client through which to send the request
+ * @param x The horizontal position of the update request rectangle
+ * @param y The vertical position of the update request rectangle
+ * @param w The width of the update request rectangle
+ * @param h The height of the update request rectangle
+ * @param incremental false: server sends rectangle even if nothing changed.
+ * true: server only sends changed parts of rectangle.
+ * @return true if the update request was sent successfully, false otherwise
+ */
extern rfbBool SendFramebufferUpdateRequest(rfbClient* client,
int x, int y, int w, int h,
rfbBool incremental);
extern rfbBool SendScaleSetting(rfbClient* client,int scaleSetting);
+/**
+ * Sends a pointer event to the server. A pointer event includes a cursor
+ * location and a button mask. The button mask indicates which buttons on the
+ * pointing device are pressed. Each button is represented by a bit in the
+ * button mask. A 1 indicates the button is pressed while a 0 indicates that it
+ * is not pressed. You may use these pre-defined button masks by ORing them
+ * together: rfbButton1Mask, rfbButton2Mask, rfbButton3Mask, rfbButton4Mask
+ * rfbButton5Mask
+ * @note The cursor location is relative to the client's framebuffer, not the
+ * client's screen itself.
+ * @note The coordinate system is a left-handed Cartesian coordinate system with
+ * the Z axis (unused) pointing out of the screen. Alternately you can think of
+ * it as a right-handed Cartesian coordinate system with the Z axis pointing
+ * into the screen. The origin is at the upper left corner of the screen.
+ * @param client The client through which to send the pointer event
+ * @param x the horizontal location of the cursor
+ * @param y the vertical location of the cursor
+ * @param buttonMask the button mask indicating which buttons are pressed
+ * @return true if the pointer event was sent successfully, false otherwise
+ */
extern rfbBool SendPointerEvent(rfbClient* client,int x, int y, int buttonMask);
+/**
+ * Sends a key event to the server. If your application is not merely a VNC
+ * viewer (i.e. it controls the server), you'll want to send the keys that the
+ * user presses to the server. Use this function to do that.
+ * @param client The client through which to send the key event
+ * @param key An rfbKeySym defined in rfb/keysym.h
+ * @param down true if this was a key down event, false otherwise
+ * @return true if the key event was send successfully, false otherwise
+ */
extern rfbBool SendKeyEvent(rfbClient* client,uint32_t key, rfbBool down);
+/**
+ * Places a string on the server's clipboard. Use this function if you want to
+ * be able to copy and paste between the server and your application. For
+ * instance, when your application is notified that the user copied some text
+ * onto the clipboard, you would call this function to synchronize the server's
+ * clipboard with your local clipboard.
+ * @param client The client structure through which to send the client cut text
+ * message
+ * @param str The string to send (doesn't need to be NULL terminated)
+ * @param len The length of the string
+ * @return true if the client cut message was sent successfully, false otherwise
+ */
extern rfbBool SendClientCutText(rfbClient* client,char *str, int len);
+/**
+ * Handles messages from the RFB server. You must call this function
+ * intermittently so LibVNCClient can parse messages from the server. For
+ * example, if your app has a draw loop, you could place a call to this
+ * function within that draw loop.
+ * @note You must call WaitForMessage() before you call this function.
+ * @param client The client which will handle the RFB server messages
+ * @return true if the client was able to handle the RFB server messages, false
+ * otherwise
+ */
extern rfbBool HandleRFBServerMessage(rfbClient* client);
+/**
+ * Sends a text chat message to the server.
+ * @param client The client through which to send the message
+ * @param text The text to send
+ * @return true if the text was sent successfully, false otherwise
+ */
extern rfbBool TextChatSend(rfbClient* client, char *text);
+/**
+ * Opens a text chat window on the server.
+ * @param client The client through which to send the message
+ * @return true if the window was opened successfully, false otherwise
+ */
extern rfbBool TextChatOpen(rfbClient* client);
+/**
+ * Closes the text chat window on the server.
+ * @param client The client through which to send the message
+ * @return true if the window was closed successfully, false otherwise
+ */
extern rfbBool TextChatClose(rfbClient* client);
extern rfbBool TextChatFinish(rfbClient* client);
extern rfbBool PermitServerInput(rfbClient* client, int enabled);
@@ -370,7 +494,28 @@ extern rfbBool SupportsServer2Client(rfbClient* client, int messageType);
/* client data */
+/**
+ * Associates a client data tag with the given pointer. LibVNCClient has
+ * several events to which you can associate your own handlers. These handlers
+ * have the client structure as one of their parameters. Sometimes, you may want
+ * to make data from elsewhere in your application available to these handlers
+ * without using a global variable. To do this, you call
+ * rfbClientSetClientData() and associate the data with a tag. Then, your
+ * handler can call rfbClientGetClientData() and get the a pointer to the data
+ * associated with that tag.
+ * @param client The client in which to set the client data
+ * @param tag A unique tag which identifies the data
+ * @param data A pointer to the data to associate with the tag
+ */
void rfbClientSetClientData(rfbClient* client, void* tag, void* data);
+/**
+ * Returns a pointer to the client data associated with the given tag. See the
+ * the documentation for rfbClientSetClientData() for a discussion of how you
+ * can use client data.
+ * @param client The client from which to get the client data
+ * @param tag The tag which identifies the client data
+ * @return a pointer to the client data
+ */
void* rfbClientGetClientData(rfbClient* client, void* tag);
/* protocol extensions */
@@ -405,12 +550,83 @@ extern rfbBool SetDSCP(int sock, int dscp);
extern rfbBool StringToIPAddr(const char *str, unsigned int *addr);
extern rfbBool SameMachine(int sock);
+/**
+ * Waits for an RFB message to arrive from the server. Before handling a message
+ * with HandleRFBServerMessage(), you must wait for your client to receive one.
+ * This function blocks until a message is received. You may specify a timeout
+ * in microseconds. Once this number of microseconds have elapsed, the function
+ * will return.
+ * @param client The client to cause to wait until a message is received
+ * @param usecs The timeout in microseconds
+ * @return the return value of the underlying select() call
+ */
extern int WaitForMessage(rfbClient* client,unsigned int usecs);
/* vncviewer.c */
+/**
+ * Allocates and returns a pointer to an rfbClient structure. This will probably
+ * be the first LibVNCClient function your client code calls. Most libVNCClient
+ * functions operate on an rfbClient structure, and this function allocates
+ * memory for that structure. When you're done with the rfbClient structure
+ * pointer this function returns, you should free the memory rfbGetClient()
+ * allocated by calling rfbClientCleanup().
+ *
+ * A pixel is one dot on the screen. The number of bytes in a pixel will depend
+ * on the number of samples in that pixel and the number of bits in each sample.
+ * A sample represents one of the primary colors in a color model. The RGB
+ * color model uses red, green, and blue samples respectively. Suppose you
+ * wanted to use 16-bit RGB color: You would have three samples per pixel (one
+ * for each primary color), five bits per sample (the quotient of 16 RGB bits
+ * divided by three samples), and two bytes per pixel (the smallest multiple of
+ * eight bits in which the 16-bit pixel will fit). If you wanted 32-bit RGB
+ * color, you would have three samples per pixel again, eight bits per sample
+ * (since that's how 32-bit color is defined), and four bytes per pixel (the
+ * smallest multiple of eight bits in which the 32-bit pixel will fit.
+ * @param bitsPerSample The number of bits in a sample
+ * @param samplesPerPixel The number of samples in a pixel
+ * @param bytesPerPixel The number of bytes in a pixel
+ * @return a pointer to the allocated rfbClient structure
+ */
rfbClient* rfbGetClient(int bitsPerSample,int samplesPerPixel,int bytesPerPixel);
+/**
+ * Initializes the client. The format is {PROGRAM_NAME, [OPTIONS]..., HOST}. This
+ * function does not initialize the program name if the rfbClient's program
+ * name is set already. The options are as follows:
+ * <table>
+ * <tr><th>Option</th><th>Description</th></tr>
+ * <tr><td>-listen</td><td>Listen for incoming connections.</td></tr>
+ * <tr><td>-listennofork</td><td>Listen for incoming connections without forking.
+ * </td></tr>
+ * <tr><td>-play</td><td>Set this client to replay a previously recorded session.</td></tr>
+ * <tr><td>-encodings</td><td>Set the encodings to use. The next item in the
+ * argv array is the encodings string, consisting of comma separated encodings like 'tight,ultra,raw'.</td></tr>
+ * <tr><td>-compress</td><td>Set the compression level. The next item in the
+ * argv array is the compression level as an integer. Ranges from 0 (lowest) to 9 (highest).
+ * </td></tr>
+ * <tr><td>-scale</td><td>Set the scaling level. The next item in the
+ * argv array is the scaling level as an integer. The screen will be scaled down by this factor.</td></tr>
+ * <tr><td>-qosdscp</td><td>Set the Quality of Service Differentiated Services
+ * Code Point (QoS DSCP). The next item in the argv array is the code point as
+ * an integer.</td></tr>
+ * <tr><td>-repeaterdest</td><td>Set a VNC repeater address. The next item in the argv array is
+ * the repeater's address as a string.</td></tr>
+ * </table>
+ *
+ * The host may include a port number (delimited by a ':').
+ * @param client The client to initialize
+ * @param argc The number of arguments to the initializer
+ * @param argv The arguments to the initializer as an array of NULL terminated
+ * strings
+ * @return true if the client was initialized successfully, false otherwise.
+ */
rfbBool rfbInitClient(rfbClient* client,int* argc,char** argv);
-/** rfbClientCleanup() does not touch client->frameBuffer */
+/**
+ * Cleans up the client structure and releases the memory allocated for it. You
+ * should call this when you're done with the rfbClient structure that you
+ * allocated with rfbGetClient().
+ * @note rfbClientCleanup() does not touch client->frameBuffer.
+ * @param client The client to clean up
+ */
void rfbClientCleanup(rfbClient* client);
#if(defined __cplusplus)
diff --git a/vncterm/Makefile.am b/vncterm/Makefile.am
index 3ad36a3..6ff0403 100644
--- a/vncterm/Makefile.am
+++ b/vncterm/Makefile.am
@@ -8,10 +8,12 @@ LDADD=../libvncserver/libvncserver.la @WSOCKLIB@
if LINUX
if ! MINGW
+if ! ANDROID
bin_PROGRAMS=LinuxVNC
LinuxVNC_SOURCES=LinuxVNC.c $(CONSOLE_SRCS)
endif
endif
+endif
if ! MINGW
VNCOMMAND=VNCommand
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 @@
+<!-- index.vnc - default html page for Java VNC viewer applet. On any file
+ ending in .vnc, the HTTP server embedded in Xvnc will substitute the
+ following variables when preceded by a dollar: USER, DESKTOP, DISPLAY,
+ APPLETWIDTH, APPLETHEIGHT, WIDTH, HEIGHT, PORT, PARAMS. Use two dollar
+ signs ($$) to get a dollar sign in the generated html. -->
+
+<HTML>
+<head>
+<TITLE>
+$USER's $DESKTOP desktop ($DISPLAY)
+</TITLE>
+</head>
+<APPLET CODE=VncViewer.class ARCHIVE=java-applet/VncViewer.jar
+ WIDTH=$APPLETWIDTH HEIGHT=$APPLETHEIGHT>
+<param name=PORT value=$PORT>
+<param name="Open New Window" value=yes>
+</APPLET>
+<br/>
+<br/>
+
+If the above Java applet does not work, you can also try the new JavaScript-only <a href="http://kanaka.github.com/noVNC/">noVNC</a> viewer. You will need a HTML5-capable browser though.
+<script language="JavaScript">
+ <!--
+ function start_novnc(){
+ open("novnc/vnc_auto.html?host=" + document.location.hostname + "&port=$PORT&true_color=1");
+ }
+ -->
+</script>
+<form name="novnc_button_form">
+ <input type="button" name="novnc_button" value="Click here to connect using noVNC" onClick='start_novnc()'>
+</form>
+
+<br/>
+<br/>
+<br/>
+<A href="http://libvncserver.sf.net/">LibVNCServer/LibVNCClient Homepage</A>
+</HTML>
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
index 602fdb9..602fdb9 100644
--- a/classes/VncViewer.jar
+++ b/webclients/java-applet/VncViewer.jar
Binary files differ
diff --git a/classes/javaviewer.pseudo_proxy.patch b/webclients/java-applet/javaviewer.pseudo_proxy.patch
index 4d2f36e..4d2f36e 100644
--- a/classes/javaviewer.pseudo_proxy.patch
+++ b/webclients/java-applet/javaviewer.pseudo_proxy.patch
diff --git a/classes/ssl/Makefile.am b/webclients/java-applet/ssl/Makefile.am
index fd1c201..fd1c201 100644
--- a/classes/ssl/Makefile.am
+++ b/webclients/java-applet/ssl/Makefile.am
diff --git a/classes/ssl/README b/webclients/java-applet/ssl/README
index b244cf1..b244cf1 100644
--- a/classes/ssl/README
+++ b/webclients/java-applet/ssl/README
diff --git a/classes/ssl/SignedUltraViewerSSL.jar b/webclients/java-applet/ssl/SignedUltraViewerSSL.jar
index 6c18737..6c18737 100644
--- a/classes/ssl/SignedUltraViewerSSL.jar
+++ b/webclients/java-applet/ssl/SignedUltraViewerSSL.jar
Binary files differ
diff --git a/classes/ssl/SignedVncViewer.jar b/webclients/java-applet/ssl/SignedVncViewer.jar
index 95c0b0b..95c0b0b 100644
--- a/classes/ssl/SignedVncViewer.jar
+++ b/webclients/java-applet/ssl/SignedVncViewer.jar
Binary files differ
diff --git a/classes/ssl/UltraViewerSSL.jar b/webclients/java-applet/ssl/UltraViewerSSL.jar
index 45259fd..45259fd 100644
--- a/classes/ssl/UltraViewerSSL.jar
+++ b/webclients/java-applet/ssl/UltraViewerSSL.jar
Binary files differ
diff --git a/classes/ssl/VncViewer.jar b/webclients/java-applet/ssl/VncViewer.jar
index 9453c6f..9453c6f 100644
--- a/classes/ssl/VncViewer.jar
+++ b/webclients/java-applet/ssl/VncViewer.jar
Binary files differ
diff --git a/classes/ssl/index.vnc b/webclients/java-applet/ssl/index.vnc
index ec520dc..ec520dc 100644
--- a/classes/ssl/index.vnc
+++ b/webclients/java-applet/ssl/index.vnc
diff --git a/classes/ssl/onetimekey b/webclients/java-applet/ssl/onetimekey
index bf57c8f..bf57c8f 100755
--- a/classes/ssl/onetimekey
+++ b/webclients/java-applet/ssl/onetimekey
diff --git a/classes/ssl/proxy.vnc b/webclients/java-applet/ssl/proxy.vnc
index 6d3ab3d..6d3ab3d 100644
--- a/classes/ssl/proxy.vnc
+++ b/webclients/java-applet/ssl/proxy.vnc
diff --git a/classes/ssl/ss_vncviewer b/webclients/java-applet/ssl/ss_vncviewer
index 7e793ff..7e793ff 100755
--- a/classes/ssl/ss_vncviewer
+++ b/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
index bc10f3c..bc10f3c 100644
--- 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
diff --git a/classes/ssl/tightvnc-1.3dev7_javasrc-vncviewer-ssl.patch b/webclients/java-applet/ssl/tightvnc-1.3dev7_javasrc-vncviewer-ssl.patch
index 801234a..801234a 100644
--- a/classes/ssl/tightvnc-1.3dev7_javasrc-vncviewer-ssl.patch
+++ b/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
index 3c57445..3c57445 100644
--- a/classes/ssl/ultra.vnc
+++ b/webclients/java-applet/ssl/ultra.vnc
diff --git a/classes/ssl/ultraproxy.vnc b/webclients/java-applet/ssl/ultraproxy.vnc
index fd842c4..fd842c4 100644
--- a/classes/ssl/ultraproxy.vnc
+++ b/webclients/java-applet/ssl/ultraproxy.vnc
diff --git a/classes/ssl/ultrasigned.vnc b/webclients/java-applet/ssl/ultrasigned.vnc
index a711655..a711655 100644
--- a/classes/ssl/ultrasigned.vnc
+++ b/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
index 3309860..3309860 100644
--- a/classes/ssl/ultravnc-102-JavaViewer-ssl-etc.patch
+++ b/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 <github@martintribe.org>
+
+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
+@<a href="http://www.twitter.com/noVNC">noVNC</a>
+
+
+### Screenshots
+
+Running in Chrome before and after connecting:
+
+<img src="http://kanaka.github.com/noVNC/img/noVNC-5.png" width=400>&nbsp;<img src="http://kanaka.github.com/noVNC/img/noVNC-7.jpg" width=400>
+
+See more screenshots <a href="http://kanaka.github.com/noVNC/screenshots.html">here</a>.
+
+
+### 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
+ <a href="http://github.com/gimite/web-socket-js">web-socket-js</a>,
+ 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 <a
+ href="https://github.com/kanaka/noVNC/wiki/Browser-support">here</a>.
+
+
+### 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
--- /dev/null
+++ b/webclients/novnc/images/clipboard.png
Binary files differ
diff --git a/webclients/novnc/images/connect.png b/webclients/novnc/images/connect.png
new file mode 100644
index 0000000..79e71ad
--- /dev/null
+++ b/webclients/novnc/images/connect.png
Binary files differ
diff --git a/webclients/novnc/images/ctrlaltdel.png b/webclients/novnc/images/ctrlaltdel.png
new file mode 100644
index 0000000..31922e5
--- /dev/null
+++ b/webclients/novnc/images/ctrlaltdel.png
Binary files differ
diff --git a/webclients/novnc/images/disconnect.png b/webclients/novnc/images/disconnect.png
new file mode 100644
index 0000000..8832f5e
--- /dev/null
+++ b/webclients/novnc/images/disconnect.png
Binary files differ
diff --git a/webclients/novnc/images/drag.png b/webclients/novnc/images/drag.png
new file mode 100644
index 0000000..433f896
--- /dev/null
+++ b/webclients/novnc/images/drag.png
Binary files differ
diff --git a/webclients/novnc/images/favicon.ico b/webclients/novnc/images/favicon.ico
new file mode 100644
index 0000000..c999634
--- /dev/null
+++ b/webclients/novnc/images/favicon.ico
Binary files differ
diff --git a/webclients/novnc/images/favicon.png b/webclients/novnc/images/favicon.png
new file mode 100644
index 0000000..e2bdb19
--- /dev/null
+++ b/webclients/novnc/images/favicon.png
Binary files differ
diff --git a/webclients/novnc/images/keyboard.png b/webclients/novnc/images/keyboard.png
new file mode 100644
index 0000000..f797952
--- /dev/null
+++ b/webclients/novnc/images/keyboard.png
Binary files differ
diff --git a/webclients/novnc/images/mouse_left.png b/webclients/novnc/images/mouse_left.png
new file mode 100644
index 0000000..1de7a48
--- /dev/null
+++ b/webclients/novnc/images/mouse_left.png
Binary files differ
diff --git a/webclients/novnc/images/mouse_middle.png b/webclients/novnc/images/mouse_middle.png
new file mode 100644
index 0000000..81fbd9b
--- /dev/null
+++ b/webclients/novnc/images/mouse_middle.png
Binary files differ
diff --git a/webclients/novnc/images/mouse_none.png b/webclients/novnc/images/mouse_none.png
new file mode 100644
index 0000000..93dbf57
--- /dev/null
+++ b/webclients/novnc/images/mouse_none.png
Binary files differ
diff --git a/webclients/novnc/images/mouse_right.png b/webclients/novnc/images/mouse_right.png
new file mode 100644
index 0000000..355b25d
--- /dev/null
+++ b/webclients/novnc/images/mouse_right.png
Binary files differ
diff --git a/webclients/novnc/images/screen_320x460.png b/webclients/novnc/images/screen_320x460.png
new file mode 100644
index 0000000..172ec55
--- /dev/null
+++ b/webclients/novnc/images/screen_320x460.png
Binary files differ
diff --git a/webclients/novnc/images/screen_57x57.png b/webclients/novnc/images/screen_57x57.png
new file mode 100644
index 0000000..e2085f2
--- /dev/null
+++ b/webclients/novnc/images/screen_57x57.png
Binary files differ
diff --git a/webclients/novnc/images/screen_700x700.png b/webclients/novnc/images/screen_700x700.png
new file mode 100644
index 0000000..ae67768
--- /dev/null
+++ b/webclients/novnc/images/screen_700x700.png
Binary files differ
diff --git a/webclients/novnc/images/settings.png b/webclients/novnc/images/settings.png
new file mode 100644
index 0000000..a43f5e1
--- /dev/null
+++ b/webclients/novnc/images/settings.png
Binary files differ
diff --git a/webclients/novnc/include/Orbitron700.ttf b/webclients/novnc/include/Orbitron700.ttf
new file mode 100644
index 0000000..e28729d
--- /dev/null
+++ b/webclients/novnc/include/Orbitron700.ttf
Binary files differ
diff --git a/webclients/novnc/include/Orbitron700.woff b/webclients/novnc/include/Orbitron700.woff
new file mode 100644
index 0000000..61db630
--- /dev/null
+++ b/webclients/novnc/include/Orbitron700.woff
Binary files 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 <mj@digicool.com> (original author)
+ * Samuel Sieb <samuel@sieb.net>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/*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 <dzimm@widget.com>, 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 <jef@acme.com>. 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<<m)) !== 0) ? 1: 0;
+ }
+
+ for (i = 0; i < 16; ++i) {
+ m = i << 1;
+ n = m + 1;
+ kn[m] = kn[n] = 0;
+ for (o=28; o<59; o+=28) {
+ for (j = o-28; j < o; ++j) {
+ l = j + totrot[i];
+ if (l < o) {
+ pcr[j] = pc1m[l];
+ } else {
+ pcr[j] = pc1m[l - 28];
+ }
+ }
+ }
+ for (j = 0; j < 24; ++j) {
+ if (pcr[PC2[j]] !== 0) {
+ kn[m] |= 1<<(23-j);
+ }
+ if (pcr[PC2[j + 24]] !== 0) {
+ kn[n] |= 1<<(23-j);
+ }
+ }
+ }
+
+ // cookey
+ for (i = 0, rawi = 0, KnLi = 0; i < 16; ++i) {
+ raw0 = kn[rawi++];
+ raw1 = kn[rawi++];
+ keys[KnLi] = (raw0 & 0x00fc0000) << 6;
+ keys[KnLi] |= (raw0 & 0x00000fc0) << 10;
+ keys[KnLi] |= (raw1 & 0x00fc0000) >>> 10;
+ keys[KnLi] |= (raw1 & 0x00000fc0) >>> 6;
+ ++KnLi;
+ keys[KnLi] = (raw0 & 0x0003f000) << 12;
+ keys[KnLi] |= (raw0 & 0x0000003f) << 16;
+ keys[KnLi] |= (raw1 & 0x0003f000) >>> 4;
+ keys[KnLi] |= (raw1 & 0x0000003f);
+ ++KnLi;
+ }
+}
+
+// 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 <a href='http://get.adobe.com/flashplayer'>Adobe Flash<\/a> is required");
+ } else if (document.location.href.substr(0, 7) === "file://") {
+ updateState('fatal',
+ "'file://' URL is incompatible with Adobe Flash");
+ } else {
+ updateState('loaded', 'noVNC ready: WebSockets emulation, ' + rmode);
+ }
+ }
+
+ 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<encodings.length; i += 1) {
+ if ((encodings[i][0] === "Cursor") &&
+ (! conf.local_cursor)) {
+ Util.Debug("Skipping Cursor pseudo-encoding");
+ } else {
+ //Util.Debug("Adding encoding: " + encodings[i][0]);
+ encList.push(encodings[i][1]);
+ }
+ }
+
+ arr = [2]; // msg-type
+ arr.push8(0); // padding
+
+ arr.push16(encList.length); // encoding count
+ for (i=0; i < encList.length; i += 1) {
+ arr.push32(encList[i]);
+ }
+ //Util.Debug("<< clientEncodings: " + arr);
+ return arr;
+};
+
+fbUpdateRequest = function(incremental, x, y, xw, yw) {
+ //Util.Debug(">> fbUpdateRequest");
+ if (typeof(x) === "undefined") { x = 0; }
+ if (typeof(y) === "undefined") { y = 0; }
+ if (typeof(xw) === "undefined") { xw = fb_width; }
+ if (typeof(yw) === "undefined") { yw = fb_height; }
+ var arr;
+ arr = [3]; // msg-type
+ arr.push8(incremental);
+ arr.push16(x);
+ arr.push16(y);
+ arr.push16(xw);
+ arr.push16(yw);
+ //Util.Debug("<< fbUpdateRequest");
+ return arr;
+};
+
+// Based on clean/dirty areas, generate requests to send
+fbUpdateRequests = function() {
+ var cleanDirty = display.getCleanDirtyReset(),
+ arr = [], i, cb, db;
+
+ cb = cleanDirty.cleanBox;
+ if (cb.w > 0 && cb.h > 0) {
+ // Request incremental for clean box
+ arr = arr.concat(fbUpdateRequest(1, cb.x, cb.y, cb.w, cb.h));
+ }
+ for (i = 0; i < cleanDirty.dirtyBoxes.length; i++) {
+ db = cleanDirty.dirtyBoxes[i];
+ // Force all (non-incremental for dirty box
+ arr = arr.concat(fbUpdateRequest(0, db.x, db.y, db.w, db.h));
+ }
+ return arr;
+};
+
+
+
+keyEvent = function(keysym, down) {
+ //Util.Debug(">> keyEvent, keysym: " + keysym + ", down: " + down);
+ var arr;
+ arr = [4]; // msg-type
+ arr.push8(down);
+ arr.push16(0);
+ arr.push32(keysym);
+ //Util.Debug("<< keyEvent");
+ return arr;
+};
+
+pointerEvent = function(x, y) {
+ //Util.Debug(">> pointerEvent, x,y: " + x + "," + y +
+ // " , mask: " + mouse_buttonMask);
+ var arr;
+ arr = [5]; // msg-type
+ arr.push8(mouse_buttonMask);
+ arr.push16(x);
+ arr.push16(y);
+ //Util.Debug("<< pointerEvent");
+ return arr;
+};
+
+clientCutText = function(text) {
+ //Util.Debug(">> clientCutText");
+ var arr, i, n;
+ arr = [6]; // msg-type
+ arr.push8(0); // padding
+ arr.push8(0); // padding
+ arr.push8(0); // padding
+ arr.push32(text.length);
+ n = text.length;
+ for (i=0; i < n; i+=1) {
+ arr.push(text.charCodeAt(i));
+ }
+ //Util.Debug("<< clientCutText:" + arr);
+ return arr;
+};
+
+
+
+//
+// Public API interface functions
+//
+
+that.connect = function(host, port, password, path) {
+ //Util.Debug(">> connect");
+
+ rfb_host = host;
+ rfb_port = port;
+ rfb_password = (password !== undefined) ? password : "";
+ rfb_path = (path !== undefined) ? path : "";
+
+ if ((!rfb_host) || (!rfb_port)) {
+ return fail("Must set host and port");
+ }
+
+ updateState('connect');
+ //Util.Debug("<< connect");
+
+};
+
+that.disconnect = function() {
+ //Util.Debug(">> disconnect");
+ updateState('disconnect', 'Disconnecting');
+ //Util.Debug("<< disconnect");
+};
+
+that.sendPassword = function(passwd) {
+ rfb_password = passwd;
+ rfb_state = "Authentication";
+ setTimeout(init_msg, 1);
+};
+
+that.sendCtrlAltDel = function() {
+ if (rfb_state !== "normal") { 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 = "<script src='" + get_INCLUDE_URI();
+ end = "'><\/script>";
+
+ // Uncomment to activate firebug lite
+ //extra += "<script src='http://getfirebug.com/releases/lite/1.2/" +
+ // "firebug-lite-compressed.js'><\/script>";
+
+ extra += start + "util.js" + end;
+ extra += start + "webutil.js" + end;
+ extra += start + "base64.js" + end;
+ extra += start + "websock.js" + end;
+ extra += start + "des.js" + end;
+ extra += start + "input.js" + end;
+ extra += start + "display.js" + end;
+ extra += start + "rfb.js" + end;
+
+ document.write(extra);
+}());
+
diff --git a/webclients/novnc/include/web-socket-js/README.txt b/webclients/novnc/include/web-socket-js/README.txt
new file mode 100644
index 0000000..2e32ea7
--- /dev/null
+++ b/webclients/novnc/include/web-socket-js/README.txt
@@ -0,0 +1,109 @@
+* How to try
+
+Assuming you have Web server (e.g. Apache) running at http://example.com/ .
+
+- Download web_socket.rb from:
+ http://github.com/gimite/web-socket-ruby/tree/master
+- Run sample Web Socket server (echo server) in example.com with: (#1)
+ $ ruby web-socket-ruby/samples/echo_server.rb example.com 10081
+- If your server already provides socket policy file at port 843, modify the file to allow access to port 10081. Otherwise you can skip this step. See below for details.
+- Publish the web-socket-js directory with your Web server (e.g. put it in ~/public_html).
+- Change ws://localhost:10081 to ws://example.com:10081 in sample.html.
+- Open sample.html in your browser.
+- After "onopen" is shown, input something, click [Send] and confirm echo back.
+
+#1: First argument of echo_server.rb means that it accepts Web Socket connection from HTML pages in example.com.
+
+
+* Troubleshooting
+
+If it doesn't work, try these:
+
+1. Try Chrome and Firefox 3.x.
+- It doesn't work on Chrome:
+-- It's likely an issue of your code or the server. Debug your code as usual e.g. using console.log.
+- It works on Chrome but it doesn't work on Firefox:
+-- It's likely an issue of web-socket-js specific configuration (e.g. 3 and 4 below).
+- It works on both Chrome and Firefox, but it doesn't work on your browser:
+-- Check "Supported environment" section below. Your browser may not be supported by web-socket-js.
+
+2. Add this line before your code:
+ WEB_SOCKET_DEBUG = true;
+and use Developer Tools (Chrome/Safari) or Firebug (Firefox) to see if console.log outputs any errors.
+
+3. Make sure you do NOT open your HTML page as local file e.g. file:///.../sample.html. web-socket-js doesn't work on local file. Open it via Web server e.g. http:///.../sample.html.
+
+4. If you are NOT using web-socket-ruby as your WebSocket server, you need to place Flash socket policy file on your server. See "Flash socket policy file" section below for details.
+
+5. Check if sample.html bundled with web-socket-js works.
+
+6. Make sure the port used for WebSocket (10081 in example above) is not blocked by your server/client's firewall.
+
+7. Install debugger version of Flash Player available here to see Flash errors:
+http://www.adobe.com/support/flashplayer/downloads.html
+
+
+* Supported environments
+
+It should work on:
+- Google Chrome 4 or later (just uses native implementation)
+- Firefox 3.x, Internet Explorer 8 + Flash Player 9 or later
+
+It may or may not work on other browsers such as Safari, Opera or IE 6. Patch for these browsers are appreciated, but I will not work on fixing issues specific to these browsers by myself.
+
+
+* Flash socket policy file
+
+This implementation uses Flash's socket, which means that your server must provide Flash socket policy file to declare the server accepts connections from Flash.
+
+If you use web-socket-ruby available at
+http://github.com/gimite/web-socket-ruby/tree/master
+, you don't need anything special, because web-socket-ruby handles Flash socket policy file request. But if you already provide socket policy file at port 843, you need to modify the file to allow access to Web Socket port, because it precedes what web-socket-ruby provides.
+
+If you use other Web Socket server implementation, you need to provide socket policy file yourself. See
+http://www.lightsphere.com/dev/articles/flash_socket_policy.html
+for details and sample script to run socket policy file server. node.js implementation is available here:
+http://github.com/LearnBoost/Socket.IO-node/blob/master/lib/socket.io/transports/flashsocket.js
+
+Actually, it's still better to provide socket policy file at port 843 even if you use web-socket-ruby. Flash always try to connect to port 843 first, so providing the file at port 843 makes startup faster.
+
+
+* Cookie considerations
+
+Cookie is sent if Web Socket host is the same as the origin of JavaScript. Otherwise it is not sent, because I don't know way to send right Cookie (which is Cookie of the host of Web Socket, I heard).
+
+Note that it's technically possible that client sends arbitrary string as Cookie and any other headers (by modifying this library for example) once you place Flash socket policy file in your server. So don't trust Cookie and other headers if you allow connection from untrusted origin.
+
+
+* Proxy considerations
+
+The WebSocket spec (http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol) specifies instructions for User Agents to support proxied connections by implementing the HTTP CONNECT method.
+
+The AS3 Socket class doesn't implement this mechanism, which renders it useless for the scenarios where the user trying to open a socket is behind a proxy.
+
+The class RFC2817Socket (by Christian Cantrell) effectively lets us implement this, as long as the proxy settings are known and provided by the interface that instantiates the WebSocket. As such, if you want to support proxied conncetions, you'll have to supply this information to the WebSocket constructor when Flash is being used. One way to go about it would be to ask the user for proxy settings information if the initial connection fails.
+
+
+* How to host HTML file and SWF file in different domains
+
+By default, HTML file and SWF file must be in the same domain. You can follow steps below to allow hosting them in different domain.
+
+WARNING: If you use the method below, HTML files in ANY domains can send arbitrary TCP data to your WebSocket server, regardless of configuration in Flash socket policy file. Arbitrary TCP data means that they can even fake request headers including Origin and Cookie.
+
+- Unzip WebSocketMainInsecure.zip to extract WebSocketMainInsecure.swf.
+- Put WebSocketMainInsecure.swf on your server, instead of WebSocketMain.swf.
+- In JavaScript, set WEB_SOCKET_SWF_LOCATION to URL of your WebSocketMainInsecure.swf.
+
+
+* How to build WebSocketMain.swf
+
+Install Flex 4 SDK:
+http://opensource.adobe.com/wiki/display/flexsdk/Download+Flex+4
+
+$ cd flash-src
+$ ./build.sh
+
+
+* License
+
+New BSD License.
diff --git a/webclients/novnc/include/web-socket-js/WebSocketMain.swf b/webclients/novnc/include/web-socket-js/WebSocketMain.swf
new file mode 100644
index 0000000..244c445
--- /dev/null
+++ b/webclients/novnc/include/web-socket-js/WebSocketMain.swf
Binary files differ
diff --git a/webclients/novnc/include/web-socket-js/swfobject.js b/webclients/novnc/include/web-socket-js/swfobject.js
new file mode 100644
index 0000000..8eafe9d
--- /dev/null
+++ b/webclients/novnc/include/web-socket-js/swfobject.js
@@ -0,0 +1,4 @@
+/* SWFObject v2.2 <http://code.google.com/p/swfobject/>
+ is released under the MIT License <http://www.opensource.org/licenses/mit-license.php>
+*/
+var swfobject=function(){var D="undefined",r="object",S="Shockwave Flash",W="ShockwaveFlash.ShockwaveFlash",q="application/x-shockwave-flash",R="SWFObjectExprInst",x="onreadystatechange",O=window,j=document,t=navigator,T=false,U=[h],o=[],N=[],I=[],l,Q,E,B,J=false,a=false,n,G,m=true,M=function(){var aa=typeof j.getElementById!=D&&typeof j.getElementsByTagName!=D&&typeof j.createElement!=D,ah=t.userAgent.toLowerCase(),Y=t.platform.toLowerCase(),ae=Y?/win/.test(Y):/win/.test(ah),ac=Y?/mac/.test(Y):/mac/.test(ah),af=/webkit/.test(ah)?parseFloat(ah.replace(/^.*webkit\/(\d+(\.\d+)?).*$/,"$1")):false,X=!+"\v1",ag=[0,0,0],ab=null;if(typeof t.plugins!=D&&typeof t.plugins[S]==r){ab=t.plugins[S].description;if(ab&&!(typeof t.mimeTypes!=D&&t.mimeTypes[q]&&!t.mimeTypes[q].enabledPlugin)){T=true;X=false;ab=ab.replace(/^.*\s+(\S+\s+\S+$)/,"$1");ag[0]=parseInt(ab.replace(/^(.*)\..*$/,"$1"),10);ag[1]=parseInt(ab.replace(/^.*\.(.*)\s.*$/,"$1"),10);ag[2]=/[a-zA-Z]/.test(ab)?parseInt(ab.replace(/^.*[a-zA-Z]+(.*)$/,"$1"),10):0}}else{if(typeof O.ActiveXObject!=D){try{var ad=new ActiveXObject(W);if(ad){ab=ad.GetVariable("$version");if(ab){X=true;ab=ab.split(" ")[1].split(",");ag=[parseInt(ab[0],10),parseInt(ab[1],10),parseInt(ab[2],10)]}}}catch(Z){}}}return{w3:aa,pv:ag,wk:af,ie:X,win:ae,mac:ac}}(),k=function(){if(!M.w3){return}if((typeof j.readyState!=D&&j.readyState=="complete")||(typeof j.readyState==D&&(j.getElementsByTagName("body")[0]||j.body))){f()}if(!J){if(typeof j.addEventListener!=D){j.addEventListener("DOMContentLoaded",f,false)}if(M.ie&&M.win){j.attachEvent(x,function(){if(j.readyState=="complete"){j.detachEvent(x,arguments.callee);f()}});if(O==top){(function(){if(J){return}try{j.documentElement.doScroll("left")}catch(X){setTimeout(arguments.callee,0);return}f()})()}}if(M.wk){(function(){if(J){return}if(!/loaded|complete/.test(j.readyState)){setTimeout(arguments.callee,0);return}f()})()}s(f)}}();function f(){if(J){return}try{var Z=j.getElementsByTagName("body")[0].appendChild(C("span"));Z.parentNode.removeChild(Z)}catch(aa){return}J=true;var X=U.length;for(var Y=0;Y<X;Y++){U[Y]()}}function K(X){if(J){X()}else{U[U.length]=X}}function s(Y){if(typeof O.addEventListener!=D){O.addEventListener("load",Y,false)}else{if(typeof j.addEventListener!=D){j.addEventListener("load",Y,false)}else{if(typeof O.attachEvent!=D){i(O,"onload",Y)}else{if(typeof O.onload=="function"){var X=O.onload;O.onload=function(){X();Y()}}else{O.onload=Y}}}}}function h(){if(T){V()}else{H()}}function V(){var X=j.getElementsByTagName("body")[0];var aa=C(r);aa.setAttribute("type",q);var Z=X.appendChild(aa);if(Z){var Y=0;(function(){if(typeof Z.GetVariable!=D){var ab=Z.GetVariable("$version");if(ab){ab=ab.split(" ")[1].split(",");M.pv=[parseInt(ab[0],10),parseInt(ab[1],10),parseInt(ab[2],10)]}}else{if(Y<10){Y++;setTimeout(arguments.callee,10);return}}X.removeChild(aa);Z=null;H()})()}else{H()}}function H(){var ag=o.length;if(ag>0){for(var af=0;af<ag;af++){var Y=o[af].id;var ab=o[af].callbackFn;var aa={success:false,id:Y};if(M.pv[0]>0){var ae=c(Y);if(ae){if(F(o[af].swfVersion)&&!(M.wk&&M.wk<312)){w(Y,true);if(ab){aa.success=true;aa.ref=z(Y);ab(aa)}}else{if(o[af].expressInstall&&A()){var ai={};ai.data=o[af].expressInstall;ai.width=ae.getAttribute("width")||"0";ai.height=ae.getAttribute("height")||"0";if(ae.getAttribute("class")){ai.styleclass=ae.getAttribute("class")}if(ae.getAttribute("align")){ai.align=ae.getAttribute("align")}var ah={};var X=ae.getElementsByTagName("param");var ac=X.length;for(var ad=0;ad<ac;ad++){if(X[ad].getAttribute("name").toLowerCase()!="movie"){ah[X[ad].getAttribute("name")]=X[ad].getAttribute("value")}}P(ai,ah,Y,ab)}else{p(ae);if(ab){ab(aa)}}}}}else{w(Y,true);if(ab){var Z=z(Y);if(Z&&typeof Z.SetVariable!=D){aa.success=true;aa.ref=Z}ab(aa)}}}}}function z(aa){var X=null;var Y=c(aa);if(Y&&Y.nodeName=="OBJECT"){if(typeof Y.SetVariable!=D){X=Y}else{var Z=Y.getElementsByTagName(r)[0];if(Z){X=Z}}}return X}function A(){return !a&&F("6.0.65")&&(M.win||M.mac)&&!(M.wk&&M.wk<312)}function P(aa,ab,X,Z){a=true;E=Z||null;B={success:false,id:X};var ae=c(X);if(ae){if(ae.nodeName=="OBJECT"){l=g(ae);Q=null}else{l=ae;Q=X}aa.id=R;if(typeof aa.width==D||(!/%$/.test(aa.width)&&parseInt(aa.width,10)<310)){aa.width="310"}if(typeof aa.height==D||(!/%$/.test(aa.height)&&parseInt(aa.height,10)<137)){aa.height="137"}j.title=j.title.slice(0,47)+" - Flash Player Installation";var ad=M.ie&&M.win?"ActiveX":"PlugIn",ac="MMredirectURL="+O.location.toString().replace(/&/g,"%26")+"&MMplayerType="+ad+"&MMdoctitle="+j.title;if(typeof ab.flashvars!=D){ab.flashvars+="&"+ac}else{ab.flashvars=ac}if(M.ie&&M.win&&ae.readyState!=4){var Y=C("div");X+="SWFObjectNew";Y.setAttribute("id",X);ae.parentNode.insertBefore(Y,ae);ae.style.display="none";(function(){if(ae.readyState==4){ae.parentNode.removeChild(ae)}else{setTimeout(arguments.callee,10)}})()}u(aa,ab,X)}}function p(Y){if(M.ie&&M.win&&Y.readyState!=4){var X=C("div");Y.parentNode.insertBefore(X,Y);X.parentNode.replaceChild(g(Y),X);Y.style.display="none";(function(){if(Y.readyState==4){Y.parentNode.removeChild(Y)}else{setTimeout(arguments.callee,10)}})()}else{Y.parentNode.replaceChild(g(Y),Y)}}function g(ab){var aa=C("div");if(M.win&&M.ie){aa.innerHTML=ab.innerHTML}else{var Y=ab.getElementsByTagName(r)[0];if(Y){var ad=Y.childNodes;if(ad){var X=ad.length;for(var Z=0;Z<X;Z++){if(!(ad[Z].nodeType==1&&ad[Z].nodeName=="PARAM")&&!(ad[Z].nodeType==8)){aa.appendChild(ad[Z].cloneNode(true))}}}}}return aa}function u(ai,ag,Y){var X,aa=c(Y);if(M.wk&&M.wk<312){return X}if(aa){if(typeof ai.id==D){ai.id=Y}if(M.ie&&M.win){var ah="";for(var ae in ai){if(ai[ae]!=Object.prototype[ae]){if(ae.toLowerCase()=="data"){ag.movie=ai[ae]}else{if(ae.toLowerCase()=="styleclass"){ah+=' class="'+ai[ae]+'"'}else{if(ae.toLowerCase()!="classid"){ah+=" "+ae+'="'+ai[ae]+'"'}}}}}var af="";for(var ad in ag){if(ag[ad]!=Object.prototype[ad]){af+='<param name="'+ad+'" value="'+ag[ad]+'" />'}}aa.outerHTML='<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"'+ah+">"+af+"</object>";N[N.length]=ai.id;X=c(ai.id)}else{var Z=C(r);Z.setAttribute("type",q);for(var ac in ai){if(ai[ac]!=Object.prototype[ac]){if(ac.toLowerCase()=="styleclass"){Z.setAttribute("class",ai[ac])}else{if(ac.toLowerCase()!="classid"){Z.setAttribute(ac,ai[ac])}}}}for(var ab in ag){if(ag[ab]!=Object.prototype[ab]&&ab.toLowerCase()!="movie"){e(Z,ab,ag[ab])}}aa.parentNode.replaceChild(Z,aa);X=Z}}return X}function e(Z,X,Y){var aa=C("param");aa.setAttribute("name",X);aa.setAttribute("value",Y);Z.appendChild(aa)}function y(Y){var X=c(Y);if(X&&X.nodeName=="OBJECT"){if(M.ie&&M.win){X.style.display="none";(function(){if(X.readyState==4){b(Y)}else{setTimeout(arguments.callee,10)}})()}else{X.parentNode.removeChild(X)}}}function b(Z){var Y=c(Z);if(Y){for(var X in Y){if(typeof Y[X]=="function"){Y[X]=null}}Y.parentNode.removeChild(Y)}}function c(Z){var X=null;try{X=j.getElementById(Z)}catch(Y){}return X}function C(X){return j.createElement(X)}function i(Z,X,Y){Z.attachEvent(X,Y);I[I.length]=[Z,X,Y]}function F(Z){var Y=M.pv,X=Z.split(".");X[0]=parseInt(X[0],10);X[1]=parseInt(X[1],10)||0;X[2]=parseInt(X[2],10)||0;return(Y[0]>X[0]||(Y[0]==X[0]&&Y[1]>X[1])||(Y[0]==X[0]&&Y[1]==X[1]&&Y[2]>=X[2]))?true:false}function v(ac,Y,ad,ab){if(M.ie&&M.mac){return}var aa=j.getElementsByTagName("head")[0];if(!aa){return}var X=(ad&&typeof ad=="string")?ad:"screen";if(ab){n=null;G=null}if(!n||G!=X){var Z=C("style");Z.setAttribute("type","text/css");Z.setAttribute("media",X);n=aa.appendChild(Z);if(M.ie&&M.win&&typeof j.styleSheets!=D&&j.styleSheets.length>0){n=j.styleSheets[j.styleSheets.length-1]}G=X}if(M.ie&&M.win){if(n&&typeof n.addRule==r){n.addRule(ac,Y)}}else{if(n&&typeof j.createTextNode!=D){n.appendChild(j.createTextNode(ac+" {"+Y+"}"))}}}function w(Z,X){if(!m){return}var Y=X?"visible":"hidden";if(J&&c(Z)){c(Z).style.visibility=Y}else{v("#"+Z,"visibility:"+Y)}}function L(Y){var Z=/[\\\"<>\.;]/;var X=Z.exec(Y)!=null;return X&&typeof encodeURIComponent!=D?encodeURIComponent(Y):Y}var d=function(){if(M.ie&&M.win){window.attachEvent("onunload",function(){var ac=I.length;for(var ab=0;ab<ac;ab++){I[ab][0].detachEvent(I[ab][1],I[ab][2])}var Z=N.length;for(var aa=0;aa<Z;aa++){y(N[aa])}for(var Y in M){M[Y]=null}M=null;for(var X in swfobject){swfobject[X]=null}swfobject=null})}}();return{registerObject:function(ab,X,aa,Z){if(M.w3&&ab&&X){var Y={};Y.id=ab;Y.swfVersion=X;Y.expressInstall=aa;Y.callbackFn=Z;o[o.length]=Y;w(ab,false)}else{if(Z){Z({success:false,id:ab})}}},getObjectById:function(X){if(M.w3){return z(X)}},embedSWF:function(ab,ah,ae,ag,Y,aa,Z,ad,af,ac){var X={success:false,id:ah};if(M.w3&&!(M.wk&&M.wk<312)&&ab&&ah&&ae&&ag&&Y){w(ah,false);K(function(){ae+="";ag+="";var aj={};if(af&&typeof af===r){for(var al in af){aj[al]=af[al]}}aj.data=ab;aj.width=ae;aj.height=ag;var am={};if(ad&&typeof ad===r){for(var ak in ad){am[ak]=ad[ak]}}if(Z&&typeof Z===r){for(var ai in Z){if(typeof am.flashvars!=D){am.flashvars+="&"+ai+"="+Z[ai]}else{am.flashvars=ai+"="+Z[ai]}}}if(F(Y)){var an=u(aj,am,ah);if(aj.id==ah){w(ah,true)}X.success=true;X.ref=an}else{if(aa&&A()){aj.data=aa;P(aj,am,ah,ac);return}else{w(ah,true)}}if(ac){ac(X)}})}else{if(ac){ac(X)}}},switchOffAutoHideShow:function(){m=false},ua:M,getFlashPlayerVersion:function(){return{major:M.pv[0],minor:M.pv[1],release:M.pv[2]}},hasFlashPlayerVersion:F,createSWF:function(Z,Y,X){if(M.w3){return u(Z,Y,X)}else{return undefined}},showExpressInstall:function(Z,aa,X,Y){if(M.w3&&A()){P(Z,aa,X,Y)}},removeSWF:function(X){if(M.w3){y(X)}},createCSS:function(aa,Z,Y,X){if(M.w3){v(aa,Z,Y,X)}},addDomLoadEvent:K,addLoadEvent:s,getQueryParamValue:function(aa){var Z=j.location.search||j.location.hash;if(Z){if(/\?/.test(Z)){Z=Z.split("?")[1]}if(aa==null){return L(Z)}var Y=Z.split("&");for(var X=0;X<Y.length;X++){if(Y[X].substring(0,Y[X].indexOf("="))==aa){return L(Y[X].substring((Y[X].indexOf("=")+1)))}}}return""},expressInstallCallback:function(){if(a){var X=c(R);if(X&&l){X.parentNode.replaceChild(l,X);if(Q){w(Q,true);if(M.ie&&M.win){l.style.display="block"}}if(E){E(B)}}a=false}}}}(); \ No newline at end of file
diff --git a/webclients/novnc/include/web-socket-js/web_socket.js b/webclients/novnc/include/web-socket-js/web_socket.js
new file mode 100644
index 0000000..ec2a8b7
--- /dev/null
+++ b/webclients/novnc/include/web-socket-js/web_socket.js
@@ -0,0 +1,341 @@
+// Copyright: Hiroshi Ichikawa <http://gimite.net/en/>
+// License: New BSD License
+// Reference: http://dev.w3.org/html5/websockets/
+// Reference: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol
+
+(function() {
+
+ if (window.WebSocket) return;
+
+ var console = window.console;
+ if (!console || !console.log || !console.error) {
+ console = {log: function(){ }, error: function(){ }};
+ }
+
+ if (!swfobject.hasFlashPlayerVersion("10.0.0")) {
+ console.error("Flash Player >= 10.0.0 is required.");
+ return;
+ }
+ if (location.protocol == "file:") {
+ console.error(
+ "WARNING: web-socket-js doesn't work in file:///... URL " +
+ "unless you set Flash Security Settings properly. " +
+ "Open the page via Web server i.e. http://...");
+ }
+
+ /**
+ * This class represents a faux web socket.
+ * @param {string} url
+ * @param {string} protocol
+ * @param {string} proxyHost
+ * @param {int} proxyPort
+ * @param {string} headers
+ */
+ WebSocket = function(url, protocol, proxyHost, proxyPort, headers) {
+ var self = this;
+ self.__id = WebSocket.__nextId++;
+ WebSocket.__instances[self.__id] = self;
+ self.readyState = WebSocket.CONNECTING;
+ self.bufferedAmount = 0;
+ self.__events = {};
+ // Uses setTimeout() to make sure __createFlash() runs after the caller sets ws.onopen etc.
+ // Otherwise, when onopen fires immediately, onopen is called before it is set.
+ setTimeout(function() {
+ WebSocket.__addTask(function() {
+ WebSocket.__flash.create(
+ self.__id, url, protocol, proxyHost || null, proxyPort || 0, headers || null);
+ });
+ }, 0);
+ };
+
+ /**
+ * Send data to the web socket.
+ * @param {string} data The data to send to the socket.
+ * @return {boolean} True for success, false for failure.
+ */
+ WebSocket.prototype.send = function(data) {
+ if (this.readyState == WebSocket.CONNECTING) {
+ throw "INVALID_STATE_ERR: Web Socket connection has not been established";
+ }
+ // We use encodeURIComponent() here, because FABridge doesn't work if
+ // the argument includes some characters. We don't use escape() here
+ // because of this:
+ // https://developer.mozilla.org/en/Core_JavaScript_1.5_Guide/Functions#escape_and_unescape_Functions
+ // But it looks decodeURIComponent(encodeURIComponent(s)) doesn't
+ // preserve all Unicode characters either e.g. "\uffff" in Firefox.
+ // Note by wtritch: Hopefully this will not be necessary using ExternalInterface. Will require
+ // additional testing.
+ var result = WebSocket.__flash.send(this.__id, encodeURIComponent(data));
+ if (result < 0) { // success
+ return true;
+ } else {
+ this.bufferedAmount += result;
+ return false;
+ }
+ };
+
+ /**
+ * Close this web socket gracefully.
+ */
+ WebSocket.prototype.close = function() {
+ if (this.readyState == WebSocket.CLOSED || this.readyState == WebSocket.CLOSING) {
+ return;
+ }
+ this.readyState = WebSocket.CLOSING;
+ WebSocket.__flash.close(this.__id);
+ };
+
+ /**
+ * Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>}
+ *
+ * @param {string} type
+ * @param {function} listener
+ * @param {boolean} useCapture
+ * @return void
+ */
+ WebSocket.prototype.addEventListener = function(type, listener, useCapture) {
+ if (!(type in this.__events)) {
+ this.__events[type] = [];
+ }
+ this.__events[type].push(listener);
+ };
+
+ /**
+ * Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>}
+ *
+ * @param {string} type
+ * @param {function} listener
+ * @param {boolean} useCapture
+ * @return void
+ */
+ WebSocket.prototype.removeEventListener = function(type, listener, useCapture) {
+ if (!(type in this.__events)) return;
+ var events = this.__events[type];
+ for (var i = events.length - 1; i >= 0; --i) {
+ if (events[i] === listener) {
+ events.splice(i, 1);
+ break;
+ }
+ }
+ };
+
+ /**
+ * Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>}
+ *
+ * @param {Event} event
+ * @return void
+ */
+ WebSocket.prototype.dispatchEvent = function(event) {
+ var events = this.__events[event.type] || [];
+ for (var i = 0; i < events.length; ++i) {
+ events[i](event);
+ }
+ var handler = this["on" + event.type];
+ if (handler) handler(event);
+ };
+
+ /**
+ * Handles an event from Flash.
+ * @param {Object} flashEvent
+ */
+ WebSocket.prototype.__handleEvent = function(flashEvent) {
+ if ("readyState" in flashEvent) {
+ this.readyState = flashEvent.readyState;
+ }
+
+ var jsEvent;
+ if (flashEvent.type == "open" || flashEvent.type == "error") {
+ jsEvent = this.__createSimpleEvent(flashEvent.type);
+ } else if (flashEvent.type == "close") {
+ // TODO implement jsEvent.wasClean
+ jsEvent = this.__createSimpleEvent("close");
+ } else if (flashEvent.type == "message") {
+ var data = decodeURIComponent(flashEvent.message);
+ jsEvent = this.__createMessageEvent("message", data);
+ } else {
+ throw "unknown event type: " + flashEvent.type;
+ }
+
+ this.dispatchEvent(jsEvent);
+ };
+
+ WebSocket.prototype.__createSimpleEvent = function(type) {
+ if (document.createEvent && window.Event) {
+ var event = document.createEvent("Event");
+ event.initEvent(type, false, false);
+ return event;
+ } else {
+ return {type: type, bubbles: false, cancelable: false};
+ }
+ };
+
+ WebSocket.prototype.__createMessageEvent = function(type, data) {
+ if (document.createEvent && window.MessageEvent && !window.opera) {
+ var event = document.createEvent("MessageEvent");
+ event.initMessageEvent("message", false, false, data, null, null, window, null);
+ return event;
+ } else {
+ // IE and Opera, the latter one truncates the data parameter after any 0x00 bytes.
+ return {type: type, data: data, bubbles: false, cancelable: false};
+ }
+ };
+
+ /**
+ * Define the WebSocket readyState enumeration.
+ */
+ WebSocket.CONNECTING = 0;
+ WebSocket.OPEN = 1;
+ WebSocket.CLOSING = 2;
+ WebSocket.CLOSED = 3;
+
+ WebSocket.__flash = null;
+ WebSocket.__instances = {};
+ WebSocket.__tasks = [];
+ WebSocket.__nextId = 0;
+
+ /**
+ * Load a new flash security policy file.
+ * @param {string} url
+ */
+ WebSocket.loadFlashPolicyFile = function(url){
+ WebSocket.__addTask(function() {
+ WebSocket.__flash.loadManualPolicyFile(url);
+ });
+ };
+
+ /**
+ * Loads WebSocketMain.swf and creates WebSocketMain object in Flash.
+ */
+ WebSocket.__initialize = function() {
+ if (WebSocket.__flash) return;
+
+ if (WebSocket.__swfLocation) {
+ // For backword compatibility.
+ window.WEB_SOCKET_SWF_LOCATION = WebSocket.__swfLocation;
+ }
+ if (!window.WEB_SOCKET_SWF_LOCATION) {
+ console.error("[WebSocket] set WEB_SOCKET_SWF_LOCATION to location of WebSocketMain.swf");
+ return;
+ }
+ var container = document.createElement("div");
+ container.id = "webSocketContainer";
+ // Hides Flash box. We cannot use display: none or visibility: hidden because it prevents
+ // Flash from loading at least in IE. So we move it out of the screen at (-100, -100).
+ // But this even doesn't work with Flash Lite (e.g. in Droid Incredible). So with Flash
+ // Lite, we put it at (0, 0). This shows 1x1 box visible at left-top corner but this is
+ // the best we can do as far as we know now.
+ container.style.position = "absolute";
+ if (WebSocket.__isFlashLite()) {
+ container.style.left = "0px";
+ container.style.top = "0px";
+ } else {
+ container.style.left = "-100px";
+ container.style.top = "-100px";
+ }
+ var holder = document.createElement("div");
+ holder.id = "webSocketFlash";
+ container.appendChild(holder);
+ document.body.appendChild(container);
+ // See this article for hasPriority:
+ // http://help.adobe.com/en_US/as3/mobile/WS4bebcd66a74275c36cfb8137124318eebc6-7ffd.html
+ swfobject.embedSWF(
+ WEB_SOCKET_SWF_LOCATION,
+ "webSocketFlash",
+ "1" /* width */,
+ "1" /* height */,
+ "10.0.0" /* SWF version */,
+ null,
+ null,
+ {hasPriority: true, swliveconnect : true, allowScriptAccess: "always"},
+ null,
+ function(e) {
+ if (!e.success) {
+ console.error("[WebSocket] swfobject.embedSWF failed");
+ }
+ });
+ };
+
+ /**
+ * Called by Flash to notify JS that it's fully loaded and ready
+ * for communication.
+ */
+ WebSocket.__onFlashInitialized = function() {
+ // We need to set a timeout here to avoid round-trip calls
+ // to flash during the initialization process.
+ setTimeout(function() {
+ WebSocket.__flash = document.getElementById("webSocketFlash");
+ WebSocket.__flash.setCallerUrl(location.href);
+ WebSocket.__flash.setDebug(!!window.WEB_SOCKET_DEBUG);
+ for (var i = 0; i < WebSocket.__tasks.length; ++i) {
+ WebSocket.__tasks[i]();
+ }
+ WebSocket.__tasks = [];
+ }, 0);
+ };
+
+ /**
+ * Called by Flash to notify WebSockets events are fired.
+ */
+ WebSocket.__onFlashEvent = function() {
+ setTimeout(function() {
+ try {
+ // Gets events using receiveEvents() instead of getting it from event object
+ // of Flash event. This is to make sure to keep message order.
+ // It seems sometimes Flash events don't arrive in the same order as they are sent.
+ var events = WebSocket.__flash.receiveEvents();
+ for (var i = 0; i < events.length; ++i) {
+ WebSocket.__instances[events[i].webSocketId].__handleEvent(events[i]);
+ }
+ } catch (e) {
+ console.error(e);
+ }
+ }, 0);
+ return true;
+ };
+
+ // Called by Flash.
+ WebSocket.__log = function(message) {
+ console.log(decodeURIComponent(message));
+ };
+
+ // Called by Flash.
+ WebSocket.__error = function(message) {
+ console.error(decodeURIComponent(message));
+ };
+
+ WebSocket.__addTask = function(task) {
+ if (WebSocket.__flash) {
+ task();
+ } else {
+ WebSocket.__tasks.push(task);
+ }
+ };
+
+ /**
+ * Test if the browser is running flash lite.
+ * @return {boolean} True if flash lite is running, false otherwise.
+ */
+ WebSocket.__isFlashLite = function() {
+ if (!window.navigator || !window.navigator.mimeTypes) {
+ return false;
+ }
+ var mimeType = window.navigator.mimeTypes["application/x-shockwave-flash"];
+ if (!mimeType || !mimeType.enabledPlugin || !mimeType.enabledPlugin.filename) {
+ return false;
+ }
+ return mimeType.enabledPlugin.filename.match(/flashlite/i) ? true : false;
+ };
+
+ if (!window.WEB_SOCKET_DISABLE_AUTO_INITIALIZATION) {
+ if (window.addEventListener) {
+ window.addEventListener("load", function(){
+ WebSocket.__initialize();
+ }, false);
+ } else {
+ window.attachEvent("onload", function(){
+ WebSocket.__initialize();
+ });
+ }
+ }
+
+})();
diff --git a/webclients/novnc/include/websock.js b/webclients/novnc/include/websock.js
new file mode 100644
index 0000000..a688f76
--- /dev/null
+++ b/webclients/novnc/include/websock.js
@@ -0,0 +1,347 @@
+/*
+ * Websock: high-performance binary WebSockets
+ * Copyright (C) 2011 Joel Martin
+ * Licensed under LGPL-3 (see LICENSE.txt)
+ *
+ * Websock is similar to the standard WebSocket object but Websock
+ * enables communication with raw TCP sockets (i.e. the binary stream)
+ * via websockify. This is accomplished by base64 encoding the data
+ * stream between Websock and websockify.
+ *
+ * Websock has built-in receive queue buffering; the message event
+ * does not contain actual data but is simply a notification that
+ * there is new data available. Several rQ* methods are available to
+ * read binary data off of the receive queue.
+ */
+
+
+// Load Flash WebSocket emulator if needed
+
+if (window.WebSocket) {
+ Websock_native = true;
+} else if (window.MozWebSocket) {
+ Websock_native = true;
+ window.WebSocket = window.MozWebSocket;
+} else {
+ /* no builtin WebSocket so load web_socket.js */
+ Websock_native = false;
+ (function () {
+ function get_INCLUDE_URI() {
+ return (typeof INCLUDE_URI !== "undefined") ?
+ INCLUDE_URI : "include/";
+ }
+
+ var start = "<script src='" + get_INCLUDE_URI(),
+ end = "'><\/script>", extra = "";
+
+ WEB_SOCKET_SWF_LOCATION = get_INCLUDE_URI() +
+ "web-socket-js/WebSocketMain.swf";
+ if (Util.Engine.trident) {
+ Util.Debug("Forcing uncached load of WebSocketMain.swf");
+ WEB_SOCKET_SWF_LOCATION += "?" + Math.random();
+ }
+ extra += start + "web-socket-js/swfobject.js" + end;
+ extra += start + "web-socket-js/web_socket.js" + end;
+ document.write(extra);
+ }());
+}
+
+
+function Websock() {
+"use strict";
+
+var api = {}, // Public API
+ websocket = null, // WebSocket object
+ rQ = [], // Receive queue
+ rQi = 0, // Receive queue index
+ rQmax = 10000, // Max receive queue size before compacting
+ sQ = [], // Send queue
+
+ eventHandlers = {
+ 'message' : function() {},
+ 'open' : function() {},
+ 'close' : function() {},
+ 'error' : function() {}
+ },
+
+ test_mode = false;
+
+
+//
+// Queue public functions
+//
+
+function get_sQ() {
+ return sQ;
+}
+
+function get_rQ() {
+ return rQ;
+}
+function get_rQi() {
+ return rQi;
+}
+function set_rQi(val) {
+ rQi = val;
+};
+
+function rQlen() {
+ return rQ.length - rQi;
+}
+
+function rQpeek8() {
+ return (rQ[rQi] );
+}
+function rQshift8() {
+ return (rQ[rQi++] );
+}
+function rQunshift8(num) {
+ if (rQi === 0) {
+ rQ.unshift(num);
+ } else {
+ rQi -= 1;
+ rQ[rQi] = num;
+ }
+
+}
+function rQshift16() {
+ return (rQ[rQi++] << 8) +
+ (rQ[rQi++] );
+}
+function rQshift32() {
+ return (rQ[rQi++] << 24) +
+ (rQ[rQi++] << 16) +
+ (rQ[rQi++] << 8) +
+ (rQ[rQi++] );
+}
+function rQshiftStr(len) {
+ var arr = rQ.slice(rQi, rQi + len);
+ rQi += len;
+ return arr.map(function (num) {
+ return String.fromCharCode(num); } ).join('');
+
+}
+function rQshiftBytes(len) {
+ rQi += len;
+ return rQ.slice(rQi-len, rQi);
+}
+
+function rQslice(start, end) {
+ if (end) {
+ return rQ.slice(rQi + start, rQi + end);
+ } else {
+ return rQ.slice(rQi + start);
+ }
+}
+
+// Check to see if we must wait for 'num' bytes (default to FBU.bytes)
+// to be available in the receive queue. Return true if we need to
+// wait (and possibly print a debug message), otherwise false.
+function rQwait(msg, num, goback) {
+ var rQlen = rQ.length - rQi; // Skip rQlen() function call
+ if (rQlen < num) {
+ if (goback) {
+ if (rQi < goback) {
+ throw("rQwait cannot backup " + goback + " bytes");
+ }
+ rQi -= goback;
+ }
+ //Util.Debug(" waiting for " + (num-rQlen) +
+ // " " + msg + " byte(s)");
+ return true; // true means need more data
+ }
+ return false;
+}
+
+//
+// Private utility routines
+//
+
+function encode_message() {
+ /* base64 encode */
+ return Base64.encode(sQ);
+}
+
+function decode_message(data) {
+ //Util.Debug(">> decode_message: " + data);
+ /* base64 decode */
+ rQ = rQ.concat(Base64.decode(data, 0));
+ //Util.Debug(">> decode_message, rQ: " + rQ);
+}
+
+
+//
+// Public Send functions
+//
+
+function flush() {
+ if (websocket.bufferedAmount !== 0) {
+ Util.Debug("bufferedAmount: " + websocket.bufferedAmount);
+ }
+ if (websocket.bufferedAmount < api.maxBufferedAmount) {
+ //Util.Debug("arr: " + arr);
+ //Util.Debug("sQ: " + sQ);
+ if (sQ.length > 0) {
+ websocket.send(encode_message(sQ));
+ sQ = [];
+ }
+ return true;
+ } else {
+ Util.Info("Delaying send, bufferedAmount: " +
+ websocket.bufferedAmount);
+ return false;
+ }
+}
+
+// overridable for testing
+function send(arr) {
+ //Util.Debug(">> send_array: " + arr);
+ sQ = sQ.concat(arr);
+ return flush();
+}
+
+function send_string(str) {
+ //Util.Debug(">> send_string: " + str);
+ api.send(str.split('').map(
+ function (chr) { return chr.charCodeAt(0); } ) );
+}
+
+//
+// Other public functions
+
+function recv_message(e) {
+ //Util.Debug(">> recv_message: " + e.data.length);
+
+ try {
+ decode_message(e.data);
+ if (rQlen() > 0) {
+ eventHandlers.message();
+ // Compact the receive queue
+ if (rQ.length > rQmax) {
+ //Util.Debug("Compacting receive queue");
+ rQ = rQ.slice(rQi);
+ rQi = 0;
+ }
+ } else {
+ Util.Debug("Ignoring empty message");
+ }
+ } catch (exc) {
+ if (typeof exc.stack !== 'undefined') {
+ Util.Warn("recv_message, caught exception: " + exc.stack);
+ } else if (typeof exc.description !== 'undefined') {
+ Util.Warn("recv_message, caught exception: " + exc.description);
+ } else {
+ Util.Warn("recv_message, caught exception:" + exc);
+ }
+ if (typeof exc.name !== 'undefined') {
+ eventHandlers.error(exc.name + ": " + exc.message);
+ } else {
+ eventHandlers.error(exc);
+ }
+ }
+ //Util.Debug("<< recv_message");
+}
+
+
+// Set event handlers
+function on(evt, handler) {
+ eventHandlers[evt] = handler;
+}
+
+function init() {
+ rQ = [];
+ rQi = 0;
+ sQ = [];
+ websocket = null;
+}
+
+function open(uri) {
+ init();
+
+ if (test_mode) {
+ websocket = {};
+ } else {
+ websocket = new WebSocket(uri, 'base64');
+ // TODO: future native binary support
+ //websocket = new WebSocket(uri, ['binary', 'base64']);
+ }
+
+ websocket.onmessage = recv_message;
+ websocket.onopen = function() {
+ Util.Debug(">> WebSock.onopen");
+ if (websocket.protocol) {
+ Util.Info("Server chose sub-protocol: " + websocket.protocol);
+ }
+ eventHandlers.open();
+ Util.Debug("<< WebSock.onopen");
+ };
+ websocket.onclose = function(e) {
+ Util.Debug(">> WebSock.onclose");
+ eventHandlers.close(e);
+ Util.Debug("<< WebSock.onclose");
+ };
+ websocket.onerror = function(e) {
+ Util.Debug(">> WebSock.onerror: " + e);
+ eventHandlers.error(e);
+ Util.Debug("<< WebSock.onerror");
+ };
+}
+
+function close() {
+ if (websocket) {
+ if ((websocket.readyState === WebSocket.OPEN) ||
+ (websocket.readyState === WebSocket.CONNECTING)) {
+ Util.Info("Closing WebSocket connection");
+ websocket.close();
+ }
+ websocket.onmessage = function (e) { return; };
+ }
+}
+
+// Override internal functions for testing
+// Takes a send function, returns reference to recv function
+function testMode(override_send) {
+ test_mode = true;
+ api.send = override_send;
+ api.close = function () {};
+ return recv_message;
+}
+
+function constructor() {
+ // Configuration settings
+ api.maxBufferedAmount = 200;
+
+ // Direct access to send and receive queues
+ api.get_sQ = get_sQ;
+ api.get_rQ = get_rQ;
+ api.get_rQi = get_rQi;
+ api.set_rQi = set_rQi;
+
+ // Routines to read from the receive queue
+ api.rQlen = rQlen;
+ api.rQpeek8 = rQpeek8;
+ api.rQshift8 = rQshift8;
+ api.rQunshift8 = rQunshift8;
+ api.rQshift16 = rQshift16;
+ api.rQshift32 = rQshift32;
+ api.rQshiftStr = rQshiftStr;
+ api.rQshiftBytes = rQshiftBytes;
+ api.rQslice = rQslice;
+ api.rQwait = rQwait;
+
+ api.flush = flush;
+ api.send = send;
+ api.send_string = send_string;
+
+ api.on = on;
+ api.init = init;
+ api.open = open;
+ api.close = close;
+ api.testMode = testMode;
+
+ return api;
+}
+
+return constructor();
+
+}
diff --git a/webclients/novnc/include/webutil.js b/webclients/novnc/include/webutil.js
new file mode 100644
index 0000000..95138f8
--- /dev/null
+++ b/webclients/novnc/include/webutil.js
@@ -0,0 +1,148 @@
+/*
+ * 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, document */
+
+// Globals defined here
+var WebUtil = {}, $D;
+
+/*
+ * Simple DOM selector by ID
+ */
+if (!window.$D) {
+ $D = function (id) {
+ if (document.getElementById) {
+ return document.getElementById(id);
+ } else if (document.all) {
+ return document.all[id];
+ } else if (document.layers) {
+ return document.layers[id];
+ }
+ return undefined;
+ };
+}
+
+
+/*
+ * ------------------------------------------------------
+ * Namespaced in WebUtil
+ * ------------------------------------------------------
+ */
+
+// init log level reading the logging HTTP param
+WebUtil.init_logging = function() {
+ Util._log_level = (document.location.href.match(
+ /logging=([A-Za-z0-9\._\-]*)/) ||
+ ['', Util._log_level])[1];
+
+ Util.init_logging()
+}
+WebUtil.init_logging();
+
+
+WebUtil.dirObj = function (obj, depth, parent) {
+ var i, msg = "", val = "";
+ if (! depth) { depth=2; }
+ if (! parent) { parent= ""; }
+
+ // Print the properties of the passed-in object
+ for (i in obj) {
+ if ((depth > 1) && (typeof obj[i] === "object")) {
+ // Recurse attributes that are objects
+ msg += WebUtil.dirObj(obj[i], depth-1, parent + "." + i);
+ } else {
+ //val = new String(obj[i]).replace("\n", " ");
+ if (typeof(obj[i]) === "undefined") {
+ val = "undefined";
+ } else {
+ val = obj[i].toString().replace("\n", " ");
+ }
+ if (val.length > 30) {
+ val = val.substr(0,30) + "...";
+ }
+ msg += parent + "." + i + ": " + val + "\n";
+ }
+ }
+ return msg;
+};
+
+// Read a query string variable
+WebUtil.getQueryVar = function(name, defVal) {
+ var re = new RegExp('[?][^#]*' + name + '=([^&#]*)');
+ if (typeof defVal === 'undefined') { defVal = null; }
+ return (document.location.href.match(re) || ['',defVal])[1];
+};
+
+
+/*
+ * Cookie handling. Dervied from: http://www.quirksmode.org/js/cookies.html
+ */
+
+// No days means only for this browser session
+WebUtil.createCookie = function(name,value,days) {
+ var date, expires;
+ if (days) {
+ date = new Date();
+ date.setTime(date.getTime()+(days*24*60*60*1000));
+ expires = "; expires="+date.toGMTString();
+ }
+ else {
+ expires = "";
+ }
+ document.cookie = name+"="+value+expires+"; path=/";
+};
+
+WebUtil.readCookie = function(name, defaultValue) {
+ var i, c, nameEQ = name + "=", ca = document.cookie.split(';');
+ for(i=0; i < ca.length; i += 1) {
+ c = ca[i];
+ while (c.charAt(0) === ' ') { c = c.substring(1,c.length); }
+ if (c.indexOf(nameEQ) === 0) { return c.substring(nameEQ.length,c.length); }
+ }
+ return (typeof defaultValue !== 'undefined') ? defaultValue : null;
+};
+
+WebUtil.eraseCookie = function(name) {
+ WebUtil.createCookie(name,"",-1);
+};
+
+/*
+ * Alternate stylesheet selection
+ */
+WebUtil.getStylesheets = function() { var i, links, sheets = [];
+ links = document.getElementsByTagName("link");
+ for (i = 0; i < links.length; i += 1) {
+ if (links[i].title &&
+ links[i].rel.toUpperCase().indexOf("STYLESHEET") > -1) {
+ sheets.push(links[i]);
+ }
+ }
+ return sheets;
+};
+
+// No sheet means try and use value from cookie, null sheet used to
+// clear all alternates.
+WebUtil.selectStylesheet = function(sheet) {
+ var i, link, sheets = WebUtil.getStylesheets();
+ if (typeof sheet === 'undefined') {
+ sheet = 'default';
+ }
+ for (i=0; i < sheets.length; i += 1) {
+ link = sheets[i];
+ if (link.title === sheet) {
+ Util.Debug("Using stylesheet " + sheet);
+ link.disabled = false;
+ } else {
+ //Util.Debug("Skipping stylesheet " + link.title);
+ link.disabled = true;
+ }
+ }
+ return sheet;
+};
diff --git a/webclients/novnc/vnc.html b/webclients/novnc/vnc.html
new file mode 100644
index 0000000..281b4d3
--- /dev/null
+++ b/webclients/novnc/vnc.html
@@ -0,0 +1,180 @@
+<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.1//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile11.dtd">
+<html>
+<head>
+
+ <!--
+ noVNC example: simple example using default UI
+ Copyright (C) 2011 Joel Martin
+ Licensed under LGPL-3 (see LICENSE.txt)
+ -->
+ <title>noVNC</title>
+
+ <meta charset="utf-8">
+
+ <!-- Always force latest IE rendering engine (even in intranet) & Chrome Frame
+ Remove this if you use the .htaccess -->
+ <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+
+ <!-- Apple iOS Safari settings -->
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
+ <meta name="apple-mobile-web-app-capable" content="yes" />
+ <meta names="apple-mobile-web-app-status-bar-style" content="black-translucent" />
+ <!-- App Start Icon -->
+ <link rel="apple-touch-startup-image" href="images/screen_320x460.png" />
+ <!-- For iOS devices set the icon to use if user bookmarks app on their homescreen -->
+ <link rel="apple-touch-icon" href="images/screen_57x57.png">
+ <!--
+ <link rel="apple-touch-icon-precomposed" href="images/screen_57x57.png" />
+ -->
+
+
+ <!-- Stylesheets -->
+ <link rel="stylesheet" href="include/base.css" />
+ <link rel="alternate stylesheet" href="include/black.css" TITLE="Black" />
+ <link rel="alternate stylesheet" href="include/blue.css" TITLE="Blue" />
+
+ <!--
+ <script type='text/javascript'
+ src='http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js'></script>
+ -->
+
+ <script src="include/vnc.js"></script>
+ <script src="include/ui.js"></script>
+
+</head>
+
+<body>
+ <div id="noVNC-control-bar">
+ <!--noVNC Mobile Device only Buttons-->
+ <div class="noVNC-buttons-left">
+ <input type="image" src="images/drag.png"
+ id="noVNC_view_drag_button" class="noVNC_status_button"
+ title="Move/Drag Viewport"
+ onclick="UI.setViewDrag();">
+ <div id="noVNC_mobile_buttons">
+ <input type="image" src="images/mouse_none.png"
+ id="noVNC_mouse_button0" class="noVNC_status_button"
+ onclick="UI.setMouseButton(1);">
+ <input type="image" src="images/mouse_left.png"
+ id="noVNC_mouse_button1" class="noVNC_status_button"
+ onclick="UI.setMouseButton(2);">
+ <input type="image" src="images/mouse_middle.png"
+ id="noVNC_mouse_button2" class="noVNC_status_button"
+ onclick="UI.setMouseButton(4);">
+ <input type="image" src="images/mouse_right.png"
+ id="noVNC_mouse_button4" class="noVNC_status_button"
+ onclick="UI.setMouseButton(0);">
+ <input type="image" src="images/keyboard.png"
+ id="showKeyboard" class="noVNC_status_button"
+ value="Keyboard" title="Show Keyboard"
+ onclick="UI.showKeyboard()"/>
+ <input type="email"
+ autocapitalize="off" autocorrect="off"
+ id="keyboardinput" class="noVNC_status_button"
+ onKeyDown="onKeyDown(event);" onblur="UI.keyInputBlur();"/>
+ </div>
+ </div>
+
+ <!--noVNC Buttons-->
+ <div class="noVNC-buttons-right">
+ <input type="image" src="images/ctrlaltdel.png"
+ id="sendCtrlAltDelButton" class="noVNC_status_button"
+ title="Send Ctrl-Alt-Del"
+ onclick="UI.sendCtrlAltDel();" />
+ <input type="image" src="images/clipboard.png"
+ id="clipboardButton" class="noVNC_status_button"
+ title="Clipboard"
+ onclick="UI.toggleClipboardPanel();" />
+ <input type="image" src="images/settings.png"
+ id="settingsButton" class="noVNC_status_button"
+ title="Settings"
+ onclick="UI.toggleSettingsPanel();" />
+ <input type="image" src="images/connect.png"
+ id="connectButton" class="noVNC_status_button_selected"
+ title="Connect"
+ onclick="UI.toggleConnectPanel()" />
+ <input type="image" src="images/disconnect.png"
+ id="disconnectButton" class="noVNC_status_button"
+ title="Disconnect"
+ onclick="UI.disconnect()" />
+ </div>
+
+ <!-- Clipboard Panel -->
+ <div id="noVNC_clipboard" class="triangle-right top">
+ <textarea id="noVNC_clipboard_text" rows=5
+ onfocus="UI.displayBlur();" onblur="UI.displayFocus();"
+ onchange="UI.clipSend();">
+ </textarea>
+ <br />
+ <input id="noVNC_clipboard_clear_button" type="button"
+ value="Clear" onclick="UI.clipClear();">
+ </div>
+
+ <!-- Settings Panel -->
+ <div id="noVNC_settings" class="triangle-right top">
+ <span id="noVNC_settings_menu" onmouseover="UI.displayBlur();"
+ onmouseout="UI.displayFocus();">
+ <ul>
+ <li><input id="noVNC_encrypt" type="checkbox"> Encrypt</li>
+ <li><input id="noVNC_true_color" type="checkbox" checked> True Color</li>
+ <li><input id="noVNC_cursor" type="checkbox"> Local Cursor</li>
+ <li><input id="noVNC_clip" type="checkbox"> Clip to window</li>
+ <li><input id="noVNC_shared" type="checkbox"> Shared Mode</li>
+ <li><input id="noVNC_connectTimeout" type="input"> Connect Timeout (s)</li>
+ <li><input id="noVNC_path" type="input"> Path</li>
+ <hr>
+ <!-- Stylesheet selection dropdown -->
+ <li><label><strong>Style: </strong>
+ <select id="noVNC_stylesheet" name="vncStyle">
+ <option value="default">default</option>
+ </select></label>
+ </li>
+
+ <!-- Logging selection dropdown -->
+ <li><label><strong>Logging: </strong>
+ <select id="noVNC_logging" name="vncLogging">
+ </select></label>
+ </li>
+ <hr>
+ <li><input type="button" id="noVNC_apply" value="Apply"
+ onclick="UI.settingsApply()"></li>
+ </ul>
+ </span>
+ </div>
+
+ <!-- Connection Panel -->
+ <div id="noVNC_controls" class="triangle-right top">
+ <ul>
+ <li><label><strong>Host: </strong><input id="noVNC_host" /></label></li>
+ <li><label><strong>Port: </strong><input id="noVNC_port" /></label></li>
+ <li><label><strong>Password: </strong><input id="noVNC_password" type="password" /></label></li>
+ <li><input id="noVNC_connect_button" type="button" value="Connect" onclick="UI.connect();"></li>
+ </ul>
+ </div>
+
+ </div> <!-- End of noVNC-control-bar -->
+
+
+ <div id="noVNC_screen">
+ <div id="noVNC_screen_pad"></div>
+
+ <div id="noVNC_status_bar" class="noVNC_status_bar">
+ <div id="noVNC_status">Loading</div>
+ </div>
+
+ <h1 id="noVNC_logo"><span>no</span><br />VNC</h1>
+
+ <!-- HTML5 Canvas -->
+ <div id="noVNC_container">
+ <canvas id="noVNC_canvas" width="640px" height="20px">
+ Canvas not supported.
+ </canvas>
+ </div>
+
+ </div>
+
+ <script>
+ window.onload = UI.load;
+ </script>
+ </body>
+</html>
diff --git a/webclients/novnc/vnc_auto.html b/webclients/novnc/vnc_auto.html
new file mode 100644
index 0000000..a500b79
--- /dev/null
+++ b/webclients/novnc/vnc_auto.html
@@ -0,0 +1,116 @@
+<!DOCTYPE html>
+<html>
+ <!--
+ noVNC Example: Automatically connect on page load.
+ Copyright (C) 2011 Joel Martin
+ Licensed under LGPL-3 (see LICENSE.txt)
+
+ Connect parameters are provided in query string:
+ http://example.com/?host=HOST&port=PORT&encrypt=1&true_color=1
+ -->
+ <head>
+ <title>noVNC</title>
+ <meta http-equiv="X-UA-Compatible" content="chrome=1">
+ <link rel="stylesheet" href="include/base.css" title="plain">
+ <!--
+ <script type='text/javascript'
+ src='http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js'></script>
+ -->
+ <script src="include/vnc.js"></script>
+ </head>
+
+ <body style="margin: 0px;">
+ <div id="noVNC_screen">
+ <div id="noVNC_status_bar" class="noVNC_status_bar" style="margin-top: 0px;">
+ <table border=0 width="100%"><tr>
+ <td><div id="noVNC_status">Loading</div></td>
+ <td width="1%"><div id="noVNC_buttons">
+ <input type=button value="Send CtrlAltDel"
+ id="sendCtrlAltDelButton">
+ </div></td>
+ </tr></table>
+ </div>
+ <canvas id="noVNC_canvas" width="640px" height="20px">
+ Canvas not supported.
+ </canvas>
+ </div>
+
+ <script>
+ /*jslint white: false */
+ /*global window, $, Util, RFB, */
+ "use strict";
+
+ var rfb;
+
+ function passwordRequired(rfb) {
+ var msg;
+ msg = '<form onsubmit="return setPassword();"';
+ msg += ' style="margin-bottom: 0px">';
+ msg += 'Password Required: ';
+ msg += '<input type=password size=10 id="password_input" class="noVNC_status">';
+ msg += '<\/form>';
+ $D('noVNC_status_bar').setAttribute("class", "noVNC_status_warn");
+ $D('noVNC_status').innerHTML = msg;
+ }
+ function setPassword() {
+ rfb.sendPassword($D('password_input').value);
+ return false;
+ }
+ function sendCtrlAltDel() {
+ rfb.sendCtrlAltDel();
+ return false;
+ }
+ function updateState(rfb, state, oldstate, msg) {
+ var s, sb, cad, level;
+ s = $D('noVNC_status');
+ sb = $D('noVNC_status_bar');
+ cad = $D('sendCtrlAltDelButton');
+ switch (state) {
+ case 'failed': level = "error"; break;
+ case 'fatal': level = "error"; break;
+ case 'normal': level = "normal"; break;
+ case 'disconnected': level = "normal"; break;
+ case 'loaded': level = "normal"; break;
+ default: level = "warn"; break;
+ }
+
+ if (state === "normal") { cad.disabled = false; }
+ else { cad.disabled = true; }
+
+ if (typeof(msg) !== 'undefined') {
+ sb.setAttribute("class", "noVNC_status_" + level);
+ s.innerHTML = msg;
+ }
+ }
+
+ window.onload = function () {
+ var host, port, password, path;
+
+ $D('sendCtrlAltDelButton').style.display = "inline";
+ $D('sendCtrlAltDelButton').onclick = sendCtrlAltDel;
+
+ document.title = unescape(WebUtil.getQueryVar('title', 'noVNC'));
+ host = WebUtil.getQueryVar('host', null);
+ port = WebUtil.getQueryVar('port', null);
+ password = WebUtil.getQueryVar('password', '');
+ path = WebUtil.getQueryVar('path', '');
+ if ((!host) || (!port)) {
+ updateState('failed',
+ "Must specify host and port in URL");
+ return;
+ }
+
+ rfb = new RFB({'target': $D('noVNC_canvas'),
+ 'encrypt': WebUtil.getQueryVar('encrypt', false),
+ 'true_color': WebUtil.getQueryVar('true_color', true),
+ 'local_cursor': WebUtil.getQueryVar('cursor', true),
+ 'shared': WebUtil.getQueryVar('shared', true),
+ 'updateState': updateState,
+ 'onPasswordRequired': passwordRequired});
+ rfb.connect(host, port, password, path);
+ };
+ </script>
+
+ </body>
+</html>
+