From edbd5ab8d4512a7adb089f3e4791e54523748253 Mon Sep 17 00:00:00 2001 From: Christian Beier Date: Tue, 4 Oct 2011 18:26:48 +0200 Subject: [PATCH] Add noVNC HTML5 client connect possibility to our http server. Pure JavaScript, no Java plugin required anymore! (But a recent browser...) --- classes/index.vnc | 10 +- classes/novnc/LICENSE.txt | 33 + classes/novnc/README.md | 93 + classes/novnc/favicon.ico | 1 + classes/novnc/images/clipboard.png | Bin 0 -> 501 bytes classes/novnc/images/connect.png | Bin 0 -> 404 bytes classes/novnc/images/ctrlaltdel.png | Bin 0 -> 317 bytes classes/novnc/images/disconnect.png | Bin 0 -> 1378 bytes classes/novnc/images/drag.png | Bin 0 -> 963 bytes classes/novnc/images/favicon.ico | Bin 0 -> 1150 bytes classes/novnc/images/favicon.png | Bin 0 -> 453 bytes classes/novnc/images/keyboard.png | Bin 0 -> 1283 bytes classes/novnc/images/mouse_left.png | Bin 0 -> 511 bytes classes/novnc/images/mouse_middle.png | Bin 0 -> 517 bytes classes/novnc/images/mouse_none.png | Bin 0 -> 497 bytes classes/novnc/images/mouse_right.png | Bin 0 -> 513 bytes classes/novnc/images/screen_320x460.png | Bin 0 -> 12778 bytes classes/novnc/images/screen_57x57.png | Bin 0 -> 1807 bytes classes/novnc/images/screen_700x700.png | Bin 0 -> 17930 bytes classes/novnc/images/settings.png | Bin 0 -> 2495 bytes classes/novnc/include/Orbitron700.ttf | Bin 0 -> 38580 bytes classes/novnc/include/Orbitron700.woff | Bin 0 -> 17472 bytes classes/novnc/include/base.css | 380 ++++ classes/novnc/include/base64.js | 147 ++ classes/novnc/include/black.css | 45 + classes/novnc/include/blue.css | 27 + classes/novnc/include/des.js | 273 +++ classes/novnc/include/display.js | 671 ++++++ classes/novnc/include/input.js | 1884 +++++++++++++++++ classes/novnc/include/logo.js | 1 + classes/novnc/include/playback.js | 90 + classes/novnc/include/rfb.js | 1613 ++++++++++++++ classes/novnc/include/ui.js | 629 ++++++ classes/novnc/include/util.js | 276 +++ classes/novnc/include/vnc.js | 42 + .../novnc/include/web-socket-js/README.txt | 109 + .../include/web-socket-js/WebSocketMain.swf | Bin 0 -> 175746 bytes .../novnc/include/web-socket-js/swfobject.js | 4 + .../novnc/include/web-socket-js/web_socket.js | 341 +++ classes/novnc/include/websock.js | 347 +++ classes/novnc/include/webutil.js | 148 ++ classes/novnc/vnc.html | 180 ++ classes/novnc/vnc_auto.html | 116 + libvncserver/httpd.c | 10 +- 44 files changed, 7462 insertions(+), 8 deletions(-) create mode 100644 classes/novnc/LICENSE.txt create mode 100644 classes/novnc/README.md create mode 120000 classes/novnc/favicon.ico create mode 100644 classes/novnc/images/clipboard.png create mode 100644 classes/novnc/images/connect.png create mode 100644 classes/novnc/images/ctrlaltdel.png create mode 100644 classes/novnc/images/disconnect.png create mode 100644 classes/novnc/images/drag.png create mode 100644 classes/novnc/images/favicon.ico create mode 100644 classes/novnc/images/favicon.png create mode 100644 classes/novnc/images/keyboard.png create mode 100644 classes/novnc/images/mouse_left.png create mode 100644 classes/novnc/images/mouse_middle.png create mode 100644 classes/novnc/images/mouse_none.png create mode 100644 classes/novnc/images/mouse_right.png create mode 100644 classes/novnc/images/screen_320x460.png create mode 100644 classes/novnc/images/screen_57x57.png create mode 100644 classes/novnc/images/screen_700x700.png create mode 100644 classes/novnc/images/settings.png create mode 100644 classes/novnc/include/Orbitron700.ttf create mode 100644 classes/novnc/include/Orbitron700.woff create mode 100644 classes/novnc/include/base.css create mode 100644 classes/novnc/include/base64.js create mode 100644 classes/novnc/include/black.css create mode 100644 classes/novnc/include/blue.css create mode 100644 classes/novnc/include/des.js create mode 100644 classes/novnc/include/display.js create mode 100644 classes/novnc/include/input.js create mode 100644 classes/novnc/include/logo.js create mode 100644 classes/novnc/include/playback.js create mode 100644 classes/novnc/include/rfb.js create mode 100644 classes/novnc/include/ui.js create mode 100644 classes/novnc/include/util.js create mode 100644 classes/novnc/include/vnc.js create mode 100644 classes/novnc/include/web-socket-js/README.txt create mode 100644 classes/novnc/include/web-socket-js/WebSocketMain.swf create mode 100644 classes/novnc/include/web-socket-js/swfobject.js create mode 100644 classes/novnc/include/web-socket-js/web_socket.js create mode 100644 classes/novnc/include/websock.js create mode 100644 classes/novnc/include/webutil.js create mode 100644 classes/novnc/vnc.html create mode 100644 classes/novnc/vnc_auto.html diff --git a/classes/index.vnc b/classes/index.vnc index 63b2f56..6997693 100644 --- a/classes/index.vnc +++ b/classes/index.vnc @@ -13,6 +13,12 @@ $USER's $DESKTOP desktop ($DISPLAY) -
-www.TightVNC.com +
+
+If the above Java applet does not work, you can also try the new JavaScript-only viewer. You will need a HTML5-capable browser though. +Click here to connect using noVNC. +
+
+
+LibVNCServer/libVNCClient Homepage diff --git a/classes/novnc/LICENSE.txt b/classes/novnc/LICENSE.txt new file mode 100644 index 0000000..755ace3 --- /dev/null +++ b/classes/novnc/LICENSE.txt @@ -0,0 +1,33 @@ +noVNC is Copyright (C) 2011 Joel Martin + +Some portions of noVNC are copyright to their individual authors. +Please refer to the individual source files and/or to the noVNC commit +history: https://github.com/kanaka/noVNC/commits/master + +noVNC is licensed under the LGPL (GNU Lesser General Public License) +version 3 with the following exceptions (all LGPL-3 compatible): + + include/input.js : LGPL-2 or any later version + + include/base64.js : Dual GPL-2 or LGPL-2.1 + + include/des.js : Various BSD style licenses + + include/web-socket-js/ : New BSD license. Source code at + http://github.com/gimite/web-socket-js + + include/Orbitron* : SIL Open Font License 1.1 + (Copyright 2009 Matt McInerney) + + images/ : Creative Commons Attribution-ShareAlike + http://creativecommons.org/licenses/by-sa/3.0/ + +The license texts are included at: + docs/LICENSE.LGPL-3 and + docs/LICENSE.GPL-3 + docs/LICENSE.OFL-1.1 + +Or alternatively the license texts may be found here: + http://www.gnu.org/licenses/lgpl.html and + http://www.gnu.org/licenses/gpl.html + http://scripts.sil.org/OFL diff --git a/classes/novnc/README.md b/classes/novnc/README.md new file mode 100644 index 0000000..4672969 --- /dev/null +++ b/classes/novnc/README.md @@ -0,0 +1,93 @@ +## noVNC: HTML5 VNC Client + + +### Description + +noVNC is a VNC client implemented using HTML5 technologies, +specifically Canvas and WebSockets (supports 'wss://' encryption). +noVNC is licensed under the +[LGPLv3](http://www.gnu.org/licenses/lgpl.html). + +Special thanks to [Sentry Data Systems](http://www.sentryds.com) for +sponsoring ongoing development of this project (and for employing me). + +There are many companies/projects that have integrated noVNC into +their products including: [Sentry Data Systems](http://www.sentryds.com), [Ganeti Web Manager](http://code.osuosl.org/projects/ganeti-webmgr), [Archipel](http://archipelproject.org), [openQRM](http://www.openqrm.com/), [OpenNode](http://www.opennodecloud.com/), [OpenStack](http://www.openstack.org), [Broadway (HTML5 GDK/GTK+ backend)](http://blogs.gnome.org/alexl/2011/03/15/gtk-html-backend-update/), [OpenNebula](http://opennebula.org/), [CloudSigma](http://www.cloudsigma.com/), [Zentyal (formerly eBox)](http://www.zentyal.org/), and [SlapOS](http://www.slapos.org). See [this wiki page](https://github.com/kanaka/noVNC/wiki/ProjectsCompanies-using-noVNC) for more info and links. + +Notable commits, announcements and news are posted to +@noVNC + + +### Screenshots + +Running in Chrome before and after connecting: + +  + +See more screenshots here. + + +### Browser Requirements + +* HTML5 Canvas (with createImageData): Chrome, Firefox 3.6+, iOS + Safari, Opera 11+, Internet Explorer 9+, etc. + +* HTML5 WebSockets: For browsers that do not have builtin + WebSockets support, the project includes + web-socket-js, + a WebSockets emulator using Adobe Flash. iOS 4.2+ has built-in + WebSocket support. + +* Fast Javascript Engine: noVNC avoids using new Javascript + functionality so it will run on older browsers, but decode and + rendering happen in Javascript, so a slow Javascript engine will + mean noVNC is painfully slow. + +* I maintain a more detailed browser compatibility list here. + + +### Server Requirements + +Unless you are using a VNC server with support for WebSockets +connections (only my [fork of libvncserver](http://github.com/kanaka/libvncserver) +currently), you need to use a WebSockets to TCP socket proxy. There is +a python proxy included ('websockify'). One advantage of using the +proxy is that it has builtin support for SSL/TLS encryption (i.e. +"wss://"). + +There a few reasons why a proxy is required: + + 1. WebSockets is not a pure socket protocol. There is an initial HTTP + like handshake to allow easy hand-off by web servers and allow + some origin policy exchange. Also, each WebSockets frame begins + with 0 ('\x00') and ends with 255 ('\xff'). + + 2. Javascript itself does not have the ability to handle pure byte + arrays. The python proxy encodes the data as base64 so that the + Javascript client can decode the data as an integer array. + + +### Quick Start + +* Use the launch script to start a mini-webserver and the WebSockets + proxy (websockify). The `--vnc` option is used to specify the location of + a running VNC server: + + `./utils/launch.sh --vnc localhost:5901` + +* Point your browser to the cut-and-paste URL that is output by the + launch script. Enter a password if the VNC server has one + configured. Hit the Connect button and enjoy! + + +### Other Pages + +* [Advanced Usage](https://github.com/kanaka/noVNC/wiki/Advanced-usage). Generating an SSL + certificate, starting a VNC server, advanced websockify usage, etc. + +* [Integrating noVNC](https://github.com/kanaka/noVNC/wiki/Integration) into existing projects. + +* [Troubleshooting noVNC](https://github.com/kanaka/noVNC/wiki/Troubleshooting) problems. + + diff --git a/classes/novnc/favicon.ico b/classes/novnc/favicon.ico new file mode 120000 index 0000000..45399c8 --- /dev/null +++ b/classes/novnc/favicon.ico @@ -0,0 +1 @@ +images/favicon.ico \ No newline at end of file diff --git a/classes/novnc/images/clipboard.png b/classes/novnc/images/clipboard.png new file mode 100644 index 0000000000000000000000000000000000000000..24df33c1c103755c23167c9a1d5eb51f9057060e GIT binary patch literal 501 zcmVP000>X1^@s6#OZ}&0005JNklLQsoytXHG%qsDu&24?kb6(gJMZNIUjQci;%GE_^nL#i=bVU0+3)v%yWQ@~-6FHu z?1ydJziiuX0zj=+gYWxKhGG0P3X&k2nr5MT4Y|wp8MrATn@2jALL#zx6ke_*NzM*Q(LB=qxlTR{ z-;+rcMIa&o#WNuyL{XG4;(6ZLfhv-T5CWd(<%>9ubKQ3ik|dGupG+on52n*8WzD<# rPah746vwf&3n2ssgTZ}w#D9YiP?b1W?a7*e00000NkvXXu0mjf;r`$= literal 0 HcmV?d00001 diff --git a/classes/novnc/images/connect.png b/classes/novnc/images/connect.png new file mode 100644 index 0000000000000000000000000000000000000000..79e71adb85cbdd2da5b59d9e2c38609dd526d1be GIT binary patch literal 404 zcmV;F0c-w=P)P000>X1^@s6#OZ}&00047NklS9D+ncfe;N96$KSXNI`*e04{(81y?{Ei6R9Of(lMRK|`ICPyi+&eiUAZ zm9zdypa3aP8p(P)``(OKvkOZEAgLANt&n8Es+ql)h-}pOd;)L4*vyoZaUk(X(h2YZ zD6n1y173kMNkcPx$w|M`b>JGf1FjY{IRJKnLo*x0Y$q|00GDQVGp9jbXaej5Yrr!= z6v_iSix@a`oW`_)kjbxOY4Z0?^0`QVpqcM2o%j@J13gJ2vf(nCK`weuV_Im@L}d5R zDRAuZbw>Wp5CivAbYhOP!dl(+VukP6=Za^N0FPdB9#J3{kN0zN8cC>vtYm2u*aKRw zgA~{TE`S}r|8)dB0q4NbGfRO7GaHt4T@FcoWtg~-r2gDO6$V{N1I=O}sav-{^k{{! y4cr3b0^fFPm1r~rl2*vhD1?cbjhg8Ee|!N&_KPMesy;aY0000P000>X1^@s6#OZ}&00035NklEIGHbhH+fhROmqGP}QpED4br)*+f` z_?l_{ec#LO+c)rEu-peF-2wwZk-8P|U}oc72a=w3m0px=;itmLe1 z#^)NUsv0@xQqw6Qf3i;eeYVpf={%ANX10{n1-g;U+RRoliQfnwftyI?tt`uHNl#|h z+ZT|ukaT5ci zMJI${Kqr++6=)XZJ#Ys&cHgZ7UcC3Sx~_*sQC#x%+c;nbytZig#9=-FEZMBb(?pXA P00000NkvXXu0mjfvjB`6 literal 0 HcmV?d00001 diff --git a/classes/novnc/images/disconnect.png b/classes/novnc/images/disconnect.png new file mode 100644 index 0000000000000000000000000000000000000000..8832f5ea7e2bdde7016b9429917fd453e42aadb8 GIT binary patch literal 1378 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 literal 0 HcmV?d00001 diff --git a/classes/novnc/images/favicon.ico b/classes/novnc/images/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..c999634f0e9b5fd5250f34d13118104261346610 GIT binary patch literal 1150 zcmb_bK~BR!4D{}%NjX#s9JoSJg;IC}2M$OaxNv|cAf&!xD-=TC(J!=D`m@FM#>T2d zB?5_@acqyryU7ALyz3h9c@D1yz$pMUXc)psjw1kIZjC@DSJ!pvuZF$S5EEVD`sO-K zwz~vXl@a^8vuXCdXQVt?^|DBBs};`<&ZW>?Y2J;;?7QO=uH&8|a7?raW54z=y-+UK zVvcb1_^ff#m~1{1q4bC`vi{NXgX{e4l9BSnCkyj_O)k$bT-NXTtaW?GeaRR8=>9={ zI559(gopow&Y>}1=uA{k`xoEyx2KvRChM%TiVOhwVpN6^_0yNId8osL^rTNZQIULc_^3E@(-y%Spc5Q~vy(d8L!)(x1n*7)_*losOPO6XtxtI&bNw>fdLNZ8Dwf z$IIAo{quy`M$-j;DK3=%w?ZjAaObK&H{HAptRzKWIeQ7*KiU)h_)g-r{Sm_V_d7i@ ze&ymaw?pTfHml?IIZTe*dmMBUR5LfIWL5K>i#m4b+qJBy1&05xz4K!eX8XL_`Om8f zVbx5BqIT4GSlSgy?m1KRiH|?#SMyHA8TzN@`>bi|&VIbRcH2a6i~WBVl#% zWRl#M`RQ-2?~glB>Kmrt@$!yDk*4p)b3T2KBbe4c{Fv#s_FrD!=E(K=v)<*pOqy(B slDTM#cEa+F0UAq7I2|-YE9TfUc1w106`8X%0>hZW)78&qol`;+0Ig`bxBvhE literal 0 HcmV?d00001 diff --git a/classes/novnc/images/keyboard.png b/classes/novnc/images/keyboard.png new file mode 100644 index 0000000000000000000000000000000000000000..f797952513b39cc75d0f0dd4d6716608e10217ed GIT binary patch literal 1283 zcmV+e1^oJnP)X1^@s6sPETi000EaNklRNjzKsl+3H2fX*Se?>)crik)8OqXL!g(Zk)QUl+u@eZ{N?8!v_!lURzreuTz#~)o$Ou{ncK!u58uobz4={SGnacq(;4w(VW_$&Tn0? zLI}}g46&zAPcHz$ULt<47-LVhIeLJBh>%XF$y!}QAP_(_7UAhsnk>u0Krn#eSd^ug z(`1!b(Ld0S;X^TADCEg3n(+F)h>wnk)A;$yRWL9yH#>)^$thg9d>IhH?CdP2Ca3Y! z+$BH=%+FuP)Z`TAlCx;HT3FcjvlqAg;`IegPfj5@GlR{|O)M-fVtR5KKVFzYty=4~ ztKG0fWOsT+R-mdX6o&#p2t`()soJ)OkYyQ)Cd1*7006QqL)A1WiV6n6AuCW-4T`LS zfkAdSplT`{vJ3_WiE(J^mfypX-^W&bH_NSCXW|FATe*W}`{>?%qSaUdQqGj>F}4;qINgs8wq? z^3GB8_xJJpckYp8Sr|P$hDapBQ%_PN7+mkKYV`y^?C#TK0kuNAd)jltXu2&tkJ-U z58kKbOcIrH1t&i_iO8AL;_9U4fg~KzHkK$PNeru;=_E_bY=I=X`e&0s?}Fa+y5r zwvA|P7!KKiTsDVhqk(8N3e~A{qhL_AR>R<62)ak-s}+kXoMV4j#+H5vY>}A`x{LY_0?>}|w)ThJ4!xj6} t2m;U`qQbdz=l(i-_H6Z4&i6*0`Va9a7RbS?<+lI;002ovPDHLkV1f&1X|4bO literal 0 HcmV?d00001 diff --git a/classes/novnc/images/mouse_left.png b/classes/novnc/images/mouse_left.png new file mode 100644 index 0000000000000000000000000000000000000000..1de7a486c76ff1b95efd7e5ee36dd6943cc6dd3a GIT binary patch literal 511 zcmVX1^@s6HR9gx0005TNkly zKhD}f5XIlxu*TB5qC!GGMv9cV1WC&YKqwXxk_(^!S^NKxY&N7`ufy{^BuRp5wTe=yggB0|-EL8<)l{q1 zx;aUA_riL;R!Ne;vMfj`A*BRk49#Zq*`4x_>nA1i%`P>6%qjo?002ovPDHLkV1jL+ B<81%{ literal 0 HcmV?d00001 diff --git a/classes/novnc/images/mouse_middle.png b/classes/novnc/images/mouse_middle.png new file mode 100644 index 0000000000000000000000000000000000000000..81fbd9bd375b2daa88b6bac6a7972c58b708a67b GIT binary patch literal 517 zcmV+g0{Z=lP)X1^@s6HR9gx0005ZNkly zzltJ35XQfr(H;kS;J`?bd5jA#-1w4>%zXkyP6VL?HBbltU?dpHCifEe1osR^UUJ;X z1cRfFO!r0W*)ulkvb)@VSrz^Ls=KHz;G>D86P$Co+wG9g=OLv;9LE3v%d!9faU4Sk z0ZJ)M)5PI$kc1H8c^=V%XS3OlAP62f=cn9oIK=l~k4o{cpPta^bWZVPGJ%v5jYi|d zGtLd0&F0HF6aW}wnP(OX1tqT6>xiOAoaYoUrBrnQux&d7zLc{pOJOO25CUo4noE*I zi4|}h#~H_VyUjqND9Ysa`@OyurfFsjK@i*orj#nNG8Nr!Hwl7(FvbWWnMwR-A_P)O z06>x?5`bikq19@gZk9VY9FIqgMk75@QwIR)^?LeNJm2CTJ%6CI*SK`}9;JKxYG9q- z4g6|_(xBgO0(KndX)qYP2xQx~epfV`&2PT%-wkcI+i+d?rxpwVuIv8sJnv@cVzB_F zbX%!ZK3_dN9*>iLzb~(8I-N?+d2)%N{l4@0T&z|rQZAR_I1a)vM6p;zE|-Jv`&h5n zD3waGR;yi{q^o;jxm?OH3}F}sgb)xyfH8(@wR-PP`Oozit@F&YG*W3V00000NkvXX Hu0mjf=bh+1 literal 0 HcmV?d00001 diff --git a/classes/novnc/images/mouse_none.png b/classes/novnc/images/mouse_none.png new file mode 100644 index 0000000000000000000000000000000000000000..93dbf5780777973578edaf7ae35f65ed01e5718e GIT binary patch literal 497 zcmVX1^@s6HR9gx0005FNkly z!HVKQ5QhIwv}2%i@+3$eV-E8o-hBc+tO#O*=)n*Y#FOAj_Br+m_8B~RS~ikHFlwZ` z_b|+0CpM$Y>@fS^{-W!vqNpz5e-kMOIOpnkJfdE&Ln(!#C;$MC;{X7PqJWeVlv3EX zjq~}e2q7d4!wTz7%jNRNWHM3L3W7kDH6D*OAIGtZqDWn{tZTF1@1Hs6S~130rGZAH zq4|Elk37$%5lku7GXS`*TLIsSIgX>TG$W-%S@*_-5SrJ(MNw2T4u?YplIM9Pb~>HR zqp)qel8~n9U0_P7=Cu`>PNyPGQ^FV{gqXL75CWxMmOu!h04T;7hQr}oXSM5u%jJS7 zip)eq9ROrDo0&)P+QlP!{Xpeh>(=8(RG#gJflc}}@P`#DgMR%Ju;+Q-gCO`Tkn6hU zTQL|6z9mWWFmyB;!T0^2Mlb;QzW+N6!@HsD^%|7YL#NYudiQZ0$6`L8R}Oqxi^W25 z&c!W;@%ye;E4kfnNxR*K=XuDo49#W}wOS2Hl3=&nq19@sUaxm^lis}xo6SaLSq96p nAf<$q5{xl)yWK}`$`{uk{?qW@S6u}n00000NkvXXu0mjfItA$@ literal 0 HcmV?d00001 diff --git a/classes/novnc/images/mouse_right.png b/classes/novnc/images/mouse_right.png new file mode 100644 index 0000000000000000000000000000000000000000..355b25dc9a03e6d8dc4b956dc51185b5ff8f63e9 GIT binary patch literal 513 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 literal 0 HcmV?d00001 diff --git a/classes/novnc/images/screen_57x57.png b/classes/novnc/images/screen_57x57.png new file mode 100644 index 0000000000000000000000000000000000000000..e2085f29fc2ca0147251b8327d06081670943f0c GIT binary patch literal 1807 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 literal 0 HcmV?d00001 diff --git a/classes/novnc/images/settings.png b/classes/novnc/images/settings.png new file mode 100644 index 0000000000000000000000000000000000000000..a43f5e100b3dc9b5aa30812521b036e085b44a39 GIT binary patch literal 2495 zcmZWq2{ap67mm@f1(k}e3~4G-RY|Rth{RgU#IA}GOAR0Knplb(*-?ZwKD}lgz1@N<{mC zgt7iq{!H2pO9THaZ)1wiuP9-HDzwN87W5^b67wj}f|6FgVcZV{$DmL``DtHF2@-Q8d zh$%I5K+clPszf)Il|p}A@vJD$n9C{WNhDL*ayESt3FAtrNO}?%Fwlnclx;Su=g?hr zKNRRh8TTkZ0;x3fJ~1eF7Rl@^SJfjh1?8tcPLH$#DO@0bJe%9UxMr7LaZkHdTg!Fz z!=hd0p?QtY^^Kp;t~EBlA4$6<_(2C-q&j%TaJPALJtSc^JbiioiDL4=+J$tq^T^0d z&x7O7JDB!3ehcH66a~_W$uwh8Z#J=`!hU^z>uFf@9NwVdygn1Vh1Wlc3n9Y%=mI8(BcN1nS zEePZnwlVha!*sbf9kX`AfW4X-pJ2ZnaS?4STPA!y87N{KE7P9U`tFa{{2(ZQ#% zrMZqhT???c__y<<`qqSX<1X2)n;Ir=oEBy4y9ub3Kka~U0cOlRciJ8KUNE=mmB-X} zgHSml%67OZC-EzY}Wq@|xUU!dVO-_)Phm|uA*BXtD6VUSWHDOXk5 zQW@ner1m7A7+_V+um$kLs5V)vKi*mbT?0nMJyMTPE%iBn(^gG{mMu!i619A6F^s6t zPhI4%bsY5IQanRZt0(Dm@?W3NBhH)?Ajm!I3q{D}X@$)QS40p_%}b3aVRGxD)AUD( zVd1yP(_7%tX$G?7L{c#~*8WCV*!-$w%g7#4cprIH!m5EeBsC=|^yfPO=u z_x*z~5(@bSVO%tTx>(ymOlTowh&DnSaTnlwsRxA~|A*v%Jf`GOVhDxKpwNOK2fW@s zv@nJN6naqT`}#*GiTJ$^JuD>fTNM%!Ne(1a$w7=zqy|C*`JH?}7egYVL&JOm$VA3B z-QVfHHDK)Rhx}I?9Ukd+e|66pwf2;@w`F)mHky)IW5dd(g$I{fu$wG|d ze9__p6WkgfAMeb09B}J?dhyshtD+}vXUM0e9zf;!?ae{B&)zxhEoBoisbXBO1aw(x zl*GMvTX_S=d^jd{A8$~k#v&fHc)T}DPR%wg=#JtcHbZEq@Y^?@@9p%W_V&W>U55SA z#A#Rvpi(2J=jXq??(U9%IX+GZHZp4a`f@ara5j$gw!dF0pk})#DJiKmI5@b4Kp+Tb zHLG3D^a=}Ghle!m6jxNx<31D>7Mg8;{_I{@os%Q~;p4}WS=N*H0|OrXi@AW<*QQRYsojc=jg3?QgV&x94`)Xbi3K4M5j#eB{HqJOxw)QjW#!*4*0??F^CwA5 zON(A=-u=D2yxa|k!%Oz#1|QV&Q{*^0IE>%4P(BuDGkIG$vn{WW0DGDD z<(w>>;B_?i2aDQK-)hxZ?e@O&qA!N7L-o4)I!+on&f4Jd#cw+6GOfspzWpOk>Z9$l z4Vjsl>&IS4eC5r~dUSVpZym9x&(8QO$;oXAq8jR2TlYi_m@x`CnPjPYm4_uIRyNkw zkqS~$VX;m4%$ml=#_HPI4M6oA2(#IBx3Q?t-+^u>4Fa{rYkGKiRNon7Zf|enF@31) ziXp0(*W|-cO^Q22aAR(c0_=*Dvf`KxX@N7Qg9lj>L+#T%psklIsw{7K)eV@tgTFql zxu=^-@3?*YwymG9Z_?w!wqW7H^7(}Y#?&}CSqeWTk!e+!n3A%={jh}-I`?*e+7rea z_@vQ>1{5wu<)|8u$8*jYr+HOnFMRqG6{ivzb7&%ZW%Si6yuIYrJ;9^(;gOM%A;G~K z>YAEW5=`n}D@&(yMPV@5-rSV?^e+)&ChVLX{WlR40s;bOO|1CqGcz(6bp-{)=OEB! zTiae>{#7Qwgq7X?J9Kh%T$Q7vqHSAFz{OQ`JcOcKRDYZCFw}&Ux3om%I>yw`#|z|n zUlKHYQJnLb55uSVnr?6NtOE5cX-q_q{|&zGO$v7#sNWPVA1%QlSze#%Y5)KL literal 0 HcmV?d00001 diff --git a/classes/novnc/include/Orbitron700.ttf b/classes/novnc/include/Orbitron700.ttf new file mode 100644 index 0000000000000000000000000000000000000000..e28729dc56b0662b5d2506ddfde5a368784f5755 GIT binary patch literal 38580 zcmc(I34B$>+4syj$vtpEAcPQhZW0qf!j?-CZU7}@fk<`&h)Xpj7ZT0ZEG%lN^# znP;AP_IYN`AfymtG>Qb_D_&W)48QQ>ieKV;NNH(Z#c5|hpCrVUSL64jWyK|>a)vk& z&+ozS>C39CR!)89h&$5-PmL@hlI|a}GB!uyss+DtcF6sN*PXO{()IX=8 zwZ7w`_i{c*-H-76)D87r9pSJrQU7N4x1nXr`d8AgJw*ubA|aN3+7zsB{Or;36VN^z z&-0pa!>UZ2hTo0&ePUB<_vX;#8E*sD5FyNW&kJ_8Enc_e4k0S$qL1IVv^UgeTsV3? zKo_IU(boFS9kNgq;`;k&?`x}X4gUVU^&5n!{aT0xV>{Zry0@m>@r4kVjRCwXIy!?L zO`glQp*`>s7AeC|>JJYcJbUD#&%`kEUEC2*X8iRR?svE(JRxzM`6TKM5eDz#584~ZQvPe`n*ix!{OLPTc81s$&c$#4BH!j7!%k{T>Rpf{- z0pF=6#6?0(8wK*YH#)X-b_t*G9T~210B)d|f-Q_<;}5v@MVreCJerSte-Z_8bmoJM%V=LgkA{H7y7gNns@Vr+kT z1%{2!bGUaheym~Cc>Br_L%Cj;jpB2kVJe%28$2%mAUm~UK*`}d*kwIbEH2le(~9Jh-Z#&xLk z7cpOa7%nj`5qa`7G2eU^b??Uq^^B|WeYF^4i~@{1#9Y{pO^)F(#%4!fZxVVB3aRewCVRrJR9Q$ ztrVR|E9V0`ov1(IPsN|YZ$xNBI>mg9bl@2CTV0bzh2Yr>7|S4^gx$b*55~MT{(~`% z!e^d%G5j$;M~vS&v>|i@Y?`S2;)xR)hos-8a@CXrim2DV{Ss8NGF|?yxj#llkWJy99 zNhHYW_%0PoU}rqyEU{JGByJUViF?Go;(qarcuBk}J`|seJ`tAl&S3ecOiV@Z1J-gI1hTqsCdI%pjwBxOhQr}E!mo!9g!hN{gntwMb@;CEZQ);pZw>!f_~+qU!Z(Mz z!Yjke!t=tj`rht)vG0W=*B`m=$nGOo9{J&s5wHEix8JwVx7T;S9xpKZU;mNdG)cxk zlBPFme$_obG!qg%){vpzVZ%=tF>+MW=;SdeV^hbCpD=OKwJYmZtzL85+SAWC z^Q>>3eU4~r|902;-|hYWfBfLGOE14-=Z;l(!U z8!iDt>l;Of#w${87tQs~j|arLty^#T`ETyN_r81f{6yUJ3$Zs+t)=_Cp3Y61w`{!d zJK} zG4YDT-HG=nzL@y7$MB5v6nk1cS9>0_60C7no^`6#WL;|AXzjIrZ~e*o!1{8?kRh{& ztRB)mn#Y4X}^!%Y$4E@c}r-y#&P4}MaJ;(b~@BQ8vy?-4xWLVa) z@?nj`E+4jg*u%qK8}`ZYDZ@`4e&+Dr;r}^&|M1@r|MUdk3FRkjJz?(&e;qM>MD2); zBd#2A&xpe#?2)5KW{q4j^0bk?Bkv#i{-~@`=Z?B@)PYfNCk;PFGPsco);z?POa&gMul;=~v9P1lf zK6c~S8^=C7_O;XzsmoJ$rv5SY%W;#&ojk5~T;sS4$6Yz@-f_>5dwtwTtedcL!p;e|;qSu}-k+E-al*vniH#F4oOs8?*Cvga zv}jWGq;n@-I_b7ak4<`e^4Q6XCZ9Wb=j3}Pe=udll=)N2r<^lo=agHg+%x6bDW6Uq zHPtt@U~2W$3#Z;Z_0ZI>eFeU9-?_eC-__8eKc*RJzO=lwb!q2=+lDAW`JizK7Q_<5 zpONdImgG-*FvLGgjbVMCvCcwcy!@8&jINWMl98K+&qKX3BV;^tFaEHO55;GIGf_;1 zMx|s}eta_W)hE}V55W0;iyw&}#%DdVFS&DW@4UP4S$VpTKWFS@ImUyljNFX$ti{rw zGG?LFm-F**@q6|5$PEV3^2crLUIYZY&>$hyF?F0hP4JNh2lVHDH^ z%J~4rhn`N$>hxR?z4MX9J3teB$_@X++yp#N6o|+(Q&XmmkQr$jvwVf0KP_i$DkwQ0 zw?^;k;FgEbGEUt#3Tzd6+1`vO=E3ZfTUWf)--Ec{9{Rx#Wma)#CYHct}1eX z*Q9D<8fcoD23kEL_fT-i)*89j{;su9ehDQWi2HIg_@i}anL`=xR!VH)F4OG!iL5HV^1smA1`39R9WY9FPEMJgcO7XmbEaioXN5dFEo=UK+@SuD z_IIOSuQ&lcYrcbR{*lseR0e|~d!c;lg3uRV$OWN2doGYSJpFs}BT5q=Xrd4t9OEQV zD1E;bE3>piz{-V7NuPFLf0WGN#F@J#uX(nCKv)wkCu zg+4}QZ@kEaCG5|i`B6J#I#z}ZONzNRjWJ`iNb+0m@mF0yfl#DyzKw6)a?+G3Cn;K7 zKi%h>z8$uv@2l%Qp6e}{eWeB447wYbqY9%Bc#jv5M(J2O3WBQ*)0B)rhWu#?1EZV5 zfnM*Wxv@`$rW%DF`(%&Y@3BwyNMB7r2?wpniUrnAkIcHxb0a!4RBZl$Q4R{=CoNbq zv^I}g8wwfay}b$_*n@jjouQP3jNBw;748keEU=C}GinK>grE7esUH1TqwS8l*$9TPYDZ>iH}j*_ZgkH92N@g(KcgY_;dMZVh+QBqK2|NI%t z0);W>G=~As2r)rq#mRLN1jWeaf{S&$YlDUC_6g9`u=n9|MSHdioxO(JbnFu%M=}4E3S!k3jSfDVrzTazqs_udmiWb@ik9Y8= z>q^7NpOj|&7-~1Yw^#lVBbh6+?8C+(prz_Nv9a)@VgSSHn~VDipu_Wk1D<5!zQ#?3 zoj}g0+EyDN?RqV_*K?Us(zlOj)Yg)S3a0TP+A`+wrwqkd%?yQx-}u%p`y)AO*U{Uy zZM)4llwt3)_hm$|aoUehrYXES@@hZY}Df$s(kZMNzjAXZAaRJPaIE%YGs7B?X-(~cOEcCv4O6vY@CwEk%;si zU5fH1IgP=WR_=pxZ=hCFg7OGYTdI_bq|pM}cMm9}#mi8*4?!$89M&gddomKIrDQ0_ zMQZ@!F3_TC7n!|AR9K&>4Ll4pAU~HEKWtfyGBuuODmqWVsQmDOQ9~nR&@?@(`+^E1 zNKfBBs3aw;*U=$p!6^|WK`TS#gT@x{OcIkpIs|kxC8FhNoP5v-0JIz0j4`d+f@yc} zJoFbJ3k{0^H;RGn3+bPKZah?aMaWtQ^sony6?D|D^ugRl@&Z$r z&h) zy7nk4`*32k<~hdq29K~*)-^Q+= z+IRQbcmL!Q^tRakyF5WYVn40xyL_XagI|%LA0k3v_mS2e_;sa9a=)(6xc(^MgVxYv z2#!A`@4({4#-Wg%$2kM^+n~+Icd{RnAQLlGfDecdZa0b)j_6bTUBgJuh0fyRKX71? zQ4#8!8gwAwX36VO#{< zz@K~!$kk|EA((g&`~Xr2+zvu#)f{oIicQ(pAO=P-f(wkN+d9@5$k#*mA_b$+en=Le z*z=K%_kx2Tg zNl#>W;9%?sjg5$DeT1>_t2i3JCXtNIXX*uA${yvOL=a^tCObl(lii(T-_M6Cl8(3A#6i=!; z96g?=!mp}nd~Q-Hs@4R){L(_zg1+;JrtxD3rwcW`KrAyEb{Nxe!1v1!x4--D_GdPP zLK}?Y^soqv^dm=1!(InWRGvZl(GFAaWEd`$VaUpz-|Ok=q0sexpN!G)%^jfa*yxxM zm#VQl-)3T<=HelTJ-wb+thljj3ZQL-cDTmKltx8qN8#6$izS?D2ASpDg)%m%uT?X6 z8eMqY2W3Dh(c>s(XFn~{3`H-8pj>k;>Lp|b*!I&)P;L;tJOE?TUoYP_Pm0k?LyQ5u zbuPSQ&hKH5D1ph!E9j7b6S?1o?1#ubN44OtYQBE)Z6fF*=kmu8fJgpN&lSbU)*t>QJnU*{bkyL18LIpb=v)EY$rhuHW^E4Yx`LuXM9P1V^fU%!6*wn zp@`YJ-(C(0B`AzR*&E;y=Tq~H3cFig-gmdWTpkSVu%8a?kU6?OJq!W?T4Ur_v`R0Q zv*onhd^LAP5^=J$euw-~LA5m{@ucbkWKaU~%&4<7}1b#>sLDVnIvAg!tVN2&in8HQ95L%GX?a z&z#w}v%Bvlj5Rlo-+}Hlym8`H*(wLLj!Z|5RrZy#GI|FjLXnsHNP<9oZ%1wsxyI@S&zA=tM!FX3G{kS1zNs03D_Q z`0eOlL3L)l&{zJ=_2(ia0p%HumGBV zN~oguJ%mSJRM?BH_ujLNlNZ$XE%@})Piq%E{?t>}0nbzHFT7qn56V%#oc?yJ?l87w z43JZ3m4}((j7g(0&ej2nRXqc`2OaOjK}n%u7XKt5;iKBS|O zAiGw{0e#7`In(E7p13)Ec=qI3)2D42(51|rc+S|utoG51Dc=bWFSa=}gZ3PwXGih} zcKCg$!oXgZXx(t(Yo25ZjCFAM16HOW#{>F$EN@~~)B|3J(^r)24=)QC9049wIYx>TGSN9m@OdsjATknfVyFCM4 zSrA^Y=A7_lfVD{0hZCUqlf*Z+&?cVKX!&ENJI0PLSrYx z7Aw$ujazS+u7~|eLXI?l1$c-&n4vP=u?2eTqXPnDuQM1k{07>L##o30+!b5VlhL*Y zdRHmLQGbVnw38S8{Q@a>k^yc1N|+WV~T|Knup&$b)N* zali?R&ucHR>V-cX_tbUXo|}`nMC@R`bh?r2kXZ*oP%{^{D!2vS9pxnLtLEhnhnE@OfQch* zgCgJ>+QE7?hiRc?Out%wYDn}4U9^}EM97S|(4U$wKfUa-oz>MlF1u_;b+sYW?%9*p z+M2fK9+dP2PCgZKm&E9PU`EZPWs1wH-3qOXWR+EBO^eny!;+Avcv$vx1Au7(a`E~V z@N)Fb)dd~OM>IPg;AgIu$75*pmysQ6yof^v8L?}@;zoT6EI~X>m;4yF%GZI0L!6AE z&e)k8ZX|2BMj5MU=1#U!QQUReC$Ulq-bL1t-GyWK+nIv;5rsHwlh?=%m@Dd%OVw(X z(pd8|E=pd{HD=@nX4u{# z`u($P#>?8KsGQw5!UZ`7!d*8g-1Jzu%N4$e!H$8iYdr=6-|b4nK+My%(Qs*^^Nn0n z8$9FK-?|U%OD4tJ`GH+t6k{Y39A3+P%TPBIhd}G0(y%k)~F09cITZqxE2O{Gn}5J>wQ# z4|~dm9BwX0z5a2cKXg`f_(*8o;>H%?BWN?yoT*^l|M3zkaI;e zr*toxbsZ2aM$@oz*I;JE&F20##)JS9&4aU`9ycq7$7Ya? z=Nvu#CRmIV;xf@641_f)4py0t69>V|m6LdY_eKRI4#P>VzB}Wc;l_yrAt$+>ISx}r z|2UA>J7fn9Rel;YOoe2Y+sA>pLG|N8f%e6N#zFwK#hbkQCuxHz|@Lqf=JDnW)n(U;TnR z1dwgPJ|v`DoP3K;k@VUNV)r_z*eqcM`lVk+Y?c|n!kLDEDHBO`y5`uf5?1IyqKoS) zW&#%&vFhoubGf!+Sy=33v^((4+Q-qwoaN$kVJGH(9U-j?f^UVuQ_Bk4#<19GIRSF*yyvE)H zq~5ZSSSEklZaF&h``R~nSOs}3N#e; zXPL>^1F;7-ANAcA2=wYfd-}v_(M#bzWMElgHrhBiMNdHvWYf;{=(Sa_Y2via{F8> zd&~GT=Gm8hiz3g`<#XZ|ycfs4B;3WBe+xF6%IA9KS7K!iIOWMh;sMwYwbul6Q~T46 zL(V2tuCqKR&rpx zp?Z*Fqs$av$eWq_10SI1$g^Vy)i1~9=a0>kH|Hhi;U7j#c}rJ&;Z>e66XK5PhF%$J z>&0&Cx9K(c2gAa+GEo1ozyrG?V7JsPn+sCzq7?3Mj-mgKk;CO{c6y)&3sYQ~!nGy0 z+86V~od*j;fzVRU>6hYSA!1;_KiZtz_Q<`=9)?GNTiY}~3iX13dCt}6*c;bfEic}(=z`B~z12AM zUwy$->P#+k(IXNW1YI{ng8JMJE8#4j@9b@hmyAx&_tVnjHJZ)E$ODm@7KH_K`ztVMLpPy>K{AS|ItU5h26liHYc(j z6*Q$C<6cT&(%f{a=iS$14s@J%M;@ZD3)$C&YI0;&)a*k)2kmtH zvfU^_KI&h3ccf!z7BVP)c~_w3Qu{EBf0l)JNAS{zEVAUjx{0aN^D6MtT1icgRp;^4 zS@shZmQ_I#Lmrr)+KN7t@FTKr?oJPlt=gH`Se}6MKPVrz>}S-}Y@T{OlJ*PjiSu#Z z9m(ShU<$+MU9^BqzFC+zSVnH z2$i%ftNMrwxCc=E&B!yx=<2A1x<{NbUW6uyN@!Uf?jp)j?~Nevq@{8_sBw|I=g+!P zW&%xTuOBw+?3+4OVQUqH9#jMY6>1zjegys#Zxt_agK0{dF$(XD`~qp3HJpndIbswa z(KN?65l=_v>UnyuuJ?4GiTmJ%>t89|2#tZOd>K)ujefBsuE@F+uVJcT_Np4 zrqnwJ)EboP7xXN^`zCmYrpK~+5O?c+ z^DhA&_4|15n;73)-S>_XFzhhJ)@zNZyImbp5WDR3^ug=)F&nT|F+vDJ?=LuVZ@4vopcB`vHiZTQC%|e&< zW@N%;&o)SN`=%d;ey8-&NPkTe!Ja z)nhzz1+Fm$*GFUWqY!hxvBNpCCEV!(!LN?DkhTMU2yGIE0fxdac7N`7FaI<5=e~Q{ zuB$J)ktoUwY;4B0ZK8=HVtHL!5p2N{A;=?%a zoA&&^@PgX&`<4Ag+QEZq()!BT^NU!;*n_d?cV%FKkwr4bUuS<5vOl^ONlUx;i6`WR z?xSuIb#V(~RQH1+h zVp;e@?0XG_Uq!h%{1(a;csd>DKfH%B2M}h6S>c%|^I@+~0^~oVT!b~RS!nwfXnkV% z9h5V}e?eIU__IX~pv@L%;OQK^&+bK;A3hglAUp?ULHIJ1i^6B2q#v3qN`T2cKzj!# zhXukfpe#`3!tl$uE(w2xvJ5@uh%^o8%Vps^QP!w(Rd_G z>dqSV^o;OJ$d+UR+l6Yp3sL`7u?W{Mpj;My0A-CTYs1gsdR2HI$~Abp7*GEuibSz0 zO8}urjjKp;qzIgP19vJxw<1x6@fL~d@aHJ&z@s8HmLhSQYPB}}2i)PfiWE(XMLFnP zENWn3i^W=CUIKhRMp+DKC7|J-Q7!{DO3)YYQ~+iP@Z_C3z$_7~RsA*U`ZRTY2KZ6} z_;29^CblgFwntI2RViA1g_8A4!RwF3GW7Cyl%%!Z_a*V@^aewkRV?y#3KP~aYv zRlv4P^;M?&DnnoI;STXELtlJ)M)(ty99KDDeui>c_-mAFa9x4xcSWUG0m-ZsYtVZo z@O&NR+VE~sh5CO)SqyGgq2|jd%fPiN(35wnR9UUcT2-!v%v7WP8z{-~YP4cWN>&5U zuTa*irvOOsIv<|cd4O(93&b6>$3uWtl1~P=6JAWLcx0t^p>i0RL|& z*FvIJ0sdPk&qSToVl8;I1~`9=k~~@iXe^hjk~~@iD!qfUN|hYN8jPKHR)G#{Fbdu| z9g?|5!CWKu;OS`!tJ8qP$GBbveP0Xue2B6H`n48MS(X8lwUF(GXVK7;!Hqz3nkm0skHk{g~^%dk@soG9u|+-kBZ-kKiH3;d>+^Q zGfA$dJy(AMqT9LV*^LLBYeOW6=bUStAta|b*9qbTd6sjXC`QTc&b0^k_d3@@#0dGe zb3IgyFp{0?VK|$u$+^ZX(|EwSJ|Ssl=5*&8kzn4X&h=<9B#*m9WCDDY=)^}~OT=|| zIM)Vn<~y0JV?vw%=v*g=2{`jo-%k`id7g9a!Tp~)*F!*uC!Fh{xcy2 zi)`aE=lX>7)tJZY`y<5sygKK4v>26l3#7dr$Bk_Pp3P!|XaZGz@NEt7X+HRrx%eA^ zpYlN;>hX+EEAX^Tw22_vu-t-si$tsQdbLR%KeHHcH-M)&3vNoIw8ri&BeAr-1*MDcKx_+uZ+|$xNOL}Q$1R9e1gQd_ z+nJAD6e!FGqBJW4DuUcBjnZmGvM!)c)P0J$^FSC9c^-a?)xplL=Jqz<+`02+ z@jNzgcf5n~M~x#&-6(PXsmhdzZg8>yG07ZUH7VYbs~bcQuG__W@U>lRRGeN1CInGy z3t9vtTo+B<-5mvU=5#j&TY~i)dV=liTiZ7V>({jeySH=%$z7p@FWS-Z#mEKNPedSG zz~uj}JTr>hq-3%QOsvJIyYT5$^k~GLQpIj+P9e%FqzmJx)#|`#yMQGXs|DZfxVr(8T7~wE{P=g)|Chhs82Tf^0slFsDty=BsvNv2 zQg>IPJQnqRz?>E-Pqht#Qk~#5Wr22ATdMs3k0bOlMg#tZu((SxrV`*nIVLwZfESwI zK3uKG9c?}S8F{1^(j25Ga%I08aD3o&H+bC&I#71H9myfz=>VvU)B~?7X_Y989ZK`s zP}{|YGTWjctix5Og4C?E)(2c^qFI+7tX-T7C=K}Cr6k(Lq6beqR9n_`#};i%`J|mB zwuI-!uk{Lcr-J2TQV-blkwM@{t&ZvpwXIFrS=t4{;ZyBNIsP`>;a%FCSY6W?YTP&C zIbjnjM@F3VFWL()Fmr7KpJ@xVNsa0})Mx+HOIoG|lu_EyPSNXA4yp0vOA~$*KCM7h z?^_)zk{5iUVRopx-GEPP$+pzxSXtjK@QT=5F3{R@ivM*x;)Xf zC_@_K9<(D)q#fzogKKTYc!yq%zt=nW+%b8bF>0RB2CYL&%AxK{Q@BM@re0x29LYyQ zh+<00xsu(68kC|gd{c^vA)o6pwcCfN4_58 zje5Fx`6W$A4Pvi3!B(VotiSaFnwDYGxj%JXe!JBDCYnSk9WO(!Y!B9+l%%hsR0b8t zI%8<-@>x@u*ps@H3N1acUfiWjR1Zi29a*`yBVN{{_-d|v8ysv_G-BMLEgE&96SNtq z6U2rZt@Se&6VhErCh`6GAP4PeMOwQftA`eMU{qT`1#O4iajBY6& zy4oCzr^Z}A;kej8?L)j&yVjx@-UxXa_*>*V@S*2j39Qm_*R}8c*XJ|ZpueOhbA044n(I=bpl>DW46Sd!yQfz}o3BlV8`Xo+D2 zKzr7y^i=auk4*E*jV7ZM(UJ7=B*V3_^n5}193RRy_#N6uN{cJUE@ulB-AId0Si-@r zol6;Vf*jwVU~4}{=z27aBlSJ)sW;#keJ^{|_o+vu0OM4~7A@f9COjkev@Yt=$9g#G zG9y0Xt|L#zYvc;0o^4~JEj@ZyyN*pKJ$lZ$80l^$AWUKjWC6x9kB zM&(_K4jLzIKXqiv85#Mk$4(60xF{NFYuV9~z`1EqNx9269sR_Qx*bqyhbRYH7QFbS zsX&Y6`k!weHziT`q+=icb|c*_PF$j`AfIT?xfn*HJ^jqJ2yA!ksE>6iKiWr7lUo#I zx3!xkaO=?D4UDw4ZPqiJV~v*cYtl9vGqP`OBitFAJ9?K=)IN94;A)wcE!rX46Y{uA zf-kE9TsF7DGeT(&W|7@Sf;u&*N@jTOBfigY#tmD{{ zMhVkW^zY0sC@aUCVKAzq&PKIPM-0?)t(oMcD_6{6=^SXZzV;D1r>>)8T0#0BSN7Rj zTa);>dN%6RDS2bONr-HrDbzn=BNgK1(~bIwC*^DwzV!^0uxOz+qwF8sHNh`XO4?AL zu`GRaG&4up*EtH>W^%VPBAHPdF!HC@VcCrO>`U{Pl%yO72Zc_%Lu}b2zcp@@$41qg zJ6qJ65FLTWf-3@y#&k|B8nKej#C5RQcRQXCH$As^`-!D9smk_@cPX9JRr*&QS;xm# z+O~E&y5o1O2L0AIY3IMAC%U(&?9~D%j%HxanJct`cR@2h;H44wAwzm3i z+ajeI@$-$rW`Nqpx~*d4wZY-KQO`hsNN>paQ~O|dhOX_b9to|79--D;dSddP){zpx z*&H>Vx}x!-mNIw52%G%j6Iu<<_rFQnHPp?@3Nml+_DxHB9$+wMLg{Q&QBsuZ+C#@i ze_93xqsKQ(m)lBP$7nBuQ<`4fwLflMoqPBu?V&u8PVw5KBhlPAxp6r-&^wZ1USSdXA z+%*=ymM)H3>jXJ+EPtuzGyh87OUDYGli^Q}LpK6q~c*+&VRcOUu zyt@kD)u^Xe90{3N5nJX+D*>ZcePh3x!hqCbwu7|d47nP8v&J&uRj06EPaGX|5jE!}HY+JX%AJzZ`!FnQdv^`{Tjbp$c`_D`79i zx28R*#x==U>R`7Ten;hxSQn!g;>&SIc^-=|Dc_$5UWIpm86Z{J17VRnq(`};!b;E& zdk+;PeTU{ngeX<0WyFL%k=Oji9+m+YN)mSdIo#J;sQb{^_<+5Z zLOm|Fi>I%vEp9!xU33L~poP$|+`cKf(Xo@Bq?5)_+Y8d+o8e;5^uN7lDcifLxx2|% z8|(^pZVWd1O55AIeUJI%b)ij9TVt@( zhrKhtm1X6=s*Yfru3WAwWc%EWL-S_Oo2`L4Z3(8Ky`vdG)&*PIH)Z?k+ZuVZzNM?( zSHH2oxrKXhd+II)YUph4=y>r8ys?u_=*E{Rq|KnBo zSC>@!N~EDrpwn_zXgo>(Quj}c?9U|Pa z#a9opZg1PbZ}f%VAa+}Ox33F3>U`_A@MddQuw`SgYqk#@^Qz9Wfowwy`e4JhEk0;N z^F~c{4jS}nsBa@W>!2wuL?qa{F4)+}tA1zzu{rIXx;riOfY;?JokL&Hx~aZfb=B$U zt(X7(IMl{Lm*Xb7{V`EUJG3XyqR+dsy-n?#AU~Z7TaFnBcXB&iRK+I3N6;v9szPSbc zM@Fd`?28yv?{cs`*g3sx#$dRUfDNEeC*-EJzVkfFRhO@;r=f}Dz#t*rUM(5uu)U|V z0qC>CYzRU#IgPCLajUZ)vxc?=GY6<>g;6g z>V~BtNih~eufhh`=C=A4*S5vNCVgl+U?;fS6I_lXB{jdJy$!G+Z(ZJ4CF)N*-vd+VRI6l8m;A3f_}gt~ip!#8V|Yb<fStTeGwDiTJTWM--nQT-U$AXs zGdB8?#vE*6Pj^#$=fEL$HE)1Pg9TtWL0&!TbsMoCSJ%VjA;;q zL+@=cf!W$<({I2WZwM+EK?*uux3BAl5`zlfdZ5q&OQ0zm*SBf}>!XeC7T6Nhgy9V; zfXeJSW_U|>oaxg(1g5nm&Pqe?B1Rja;A~>M^}6bf?t0eIvAO!a9`f71i7W%P8sVkd z+qw$8-gz^8l|jTiPPh_>I?Pa~D>2RBd^6g?oAp3RqQa96g+^rLC=VL+7iuWC>(m^V z0a(G7^ZYzHV5uS@m$Bh){x?yIpJ7)YDquHjCTj zRWR3t3!38gP7;&W2FAa$y`!_4(M&6lgl0smw+6e>up2Q&bFihcOT~h0#|{8u9n2Ua zF>T4?+;=;Wb=o-o7rv|+#(HCOaFh0r>>SeC3GVnI8tntvd2F7bnZiBJPJ0;ZW8{JO$bJ$iIJ#SHjA=-*MP6aif{f=pFW5@!} zNxg7P0mukv1cK20(M8Zm;~j-A7$ypW_J15S1F88l7{;i2MB7p6{#~m_I>Og>Ev7DY zD58gqu&}kg8#W=>*4W+&x>3;@5o&Zd;|ENPx4#yl>gI-Ehf?SIhV$Cm zHz91=pa~u^f6(bD9s|h5M|7+tYz)$!dM<&xh#va0#~Wo2Xp^gH6K5nGUg=G~bwcfhrC3-%#vZ}OhO<`?`uWY5Sy0&U{S#e3R zFTHRjey3;q)|AyPuUb{-Lyg+P%DT0fAovO^*ZNkJRTgJ^OHRW~YvoE`RjseAqPo1S zq&VAGR#{ZOs<^CjnQtlDRaSAvS5a06pmkNMsROF41no+_z@=z8AQvtzD=({Co9!zt ztE(i;QUERVRTtLQl@+ZjFRb-dud1!C!o;z#vKU}1%PLE2(Mw51NoAcESQS-OudOXx zw!AJIjp}eS+gDdxSX@$3Si2&d7*=7(wLVp4Hn0Q?U&(6J^Q~N7SYGbKQ*Q)@Z+TUD zF={U@0rrKMNosU3a7DjtUvXhY;WCcEh0AJs1l}kGNU3Edl_j-><=MWK)g?t`ya4IS zYDKGy6)K%5iMNnB& zwz4GKS6ExN66Eog)>c&jI9P!eWcn(Q5Pd)~;IZD*a9`*dBxSRFEAtC6ug4cWuf@y6tt;wZNiH1`B=-l zKzv7BDDD^E6NHVrB9~GbeSPfl$kP1PLtE+40)2ADYNA){6AlFoJMsMdE5z7P9oHGRG*Y)mQ$+BK5FLm8Yt5g(@plS*gk@Ro1AoR+TGRdKBOs z{pC+wqwc9P$D_Xd`fGmTX{ym$Ri3WOdX@?Gv%4A+*0E?{(a0jmVm*ruESgv}vp5$; z;(4mwdHmhNqLoD(i*^8RE;nQwR*An~;FF+!Q8%L>FLVp<^|sV^w?Ru;1OHzu&*?y| zhz8e#4kD|IEJ5jqo@u+FVbemWQ}3K!)d@YgrY#ty@WPo6 z(Ap)q$C?XZEBO7a{w;Hy@5`OvZd=lFD-;Opu z16_Ox`tq{Ume<6e#S!Sv>*5V)(>vl_@fYz|=;r(4Z{h>-A@ucgK+b|SN`)Po1lyD4 z*c!eln&a3RZC?gjmBDPv8Gu&{t2YkzEDJVkKJ3#HP^lc4^9^egV|^Ib!i1%G4z|L8 zULQvJHgw$(pNNl9ek%Ts@~HR>97&G4%H=S|t zlunaxor40gcSk@HUEWe|UA|C81U$(UaHqe_DoHvIZ!<%Vbv?G!k~jqKeeYJ%Jr~w^ zv$zBn{HNkp*wFpJ=#QZHKZFgNI0beu2R5x#o+h{Azd`)D{H1&w|NG2IMlt>$*zX$; z<9}#>(RkM!Va_$zn;quG=3VB!=0~QTFh5~wLPx@-2|F40V19PcOm$Gl0ALif5Pb0yb_*P!m~erj+}sgK33;e@Wc_G|G?8mc-9C{ zd*C@EJY|F@QSgKjo-e{vI6SH*PsGr*a25n=@Jx~6!0GSMVVo#}_B=yEorB@P;wd6L zLxg8t@caaClk>&kAwz;yD`Zl_!Mod=TPg zqUC33^);S+rYQ9;;PYe-;2oCSgp&jH~nAUp$P2x`2c zD9ke<^jN>b(>EfVChgvFXm?bNQ=Rsq#`-a8y$9OitPFg3!iOF+<(Q{?@GeKb47xua zmddLz9ESdI!U}Nbi5@(AgQt1$EDxUK!E-!#iU-f|;0YcQF&U}F6Fj2S(o|<#p4vfb z@x%_E*TK^|cvc77@|+G+^~6&;ct!_DgA-;TX*{2Ur*rUZ4xY?G9i?RPOb(vN!SgtH z8VAqfAdENA(@|jf9_Xw&htp#KpC@qe{0*MI!Lv7b@&?b{;5jIiOv?CMswU-^J*cxb za4pqI8-_Y*gXe7UWQr3J-3r9Ee)z{+#ITSO#H^|4r3SWcB`oNPuu~7ikNggQ=b)~( YaN=?Mu0Qj~2_>(2Mry;V1-x;lBPyVI3Q(tVP1 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 aYg+X)x}OQee;ks?lK=p?0sn=t1dRYj;d3$o literal 0 HcmV?d00001 diff --git a/classes/novnc/include/base.css b/classes/novnc/include/base.css new file mode 100644 index 0000000..0a62a1b --- /dev/null +++ b/classes/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/classes/novnc/include/base64.js b/classes/novnc/include/base64.js new file mode 100644 index 0000000..c68b33a --- /dev/null +++ b/classes/novnc/include/base64.js @@ -0,0 +1,147 @@ +/* + * Modified from: + * http://lxr.mozilla.org/mozilla/source/extensions/xml-rpc/src/nsXmlRpcClient.js#956 + */ + +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla XML-RPC Client component. + * + * The Initial Developer of the Original Code is + * Digital Creations 2, Inc. + * Portions created by the Initial Developer are Copyright (C) 2000 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Martijn Pieters (original author) + * Samuel Sieb + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/*jslint white: false, bitwise: false, plusplus: false */ +/*global console */ + +var Base64 = { + +/* Convert data (an array of integers) to a Base64 string. */ +toBase64Table : 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/', +base64Pad : '=', + +encode: function (data) { + "use strict"; + var result = '', + chrTable = Base64.toBase64Table.split(''), + pad = Base64.base64Pad, + length = data.length, + i; + // Convert every three bytes to 4 ascii characters. + for (i = 0; i < (length - 2); i += 3) { + result += chrTable[data[i] >> 2]; + result += chrTable[((data[i] & 0x03) << 4) + (data[i+1] >> 4)]; + result += chrTable[((data[i+1] & 0x0f) << 2) + (data[i+2] >> 6)]; + result += chrTable[data[i+2] & 0x3f]; + } + + // Convert the remaining 1 or 2 bytes, pad out to 4 characters. + if (length%3) { + i = length - (length%3); + result += chrTable[data[i] >> 2]; + if ((length%3) === 2) { + result += chrTable[((data[i] & 0x03) << 4) + (data[i+1] >> 4)]; + result += chrTable[(data[i+1] & 0x0f) << 2]; + result += pad; + } else { + result += chrTable[(data[i] & 0x03) << 4]; + result += pad + pad; + } + } + + return result; +}, + +/* Convert Base64 data to a string */ +toBinaryTable : [ + -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, + -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, + -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,62, -1,-1,-1,63, + 52,53,54,55, 56,57,58,59, 60,61,-1,-1, -1, 0,-1,-1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14, + 15,16,17,18, 19,20,21,22, 23,24,25,-1, -1,-1,-1,-1, + -1,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40, + 41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1 +], + +decode: function (data, offset) { + "use strict"; + offset = typeof(offset) !== 'undefined' ? offset : 0; + var binTable = Base64.toBinaryTable, + pad = Base64.base64Pad, + result, result_length, idx, i, c, padding, + leftbits = 0, // number of bits decoded, but yet to be appended + leftdata = 0, // bits decoded, but yet to be appended + data_length = data.indexOf('=') - offset; + + if (data_length < 0) { data_length = data.length - offset; } + + /* Every four characters is 3 resulting numbers */ + result_length = (data_length >> 2) * 3 + Math.floor((data_length%4)/1.5); + result = new Array(result_length); + + // Convert one by one. + for (idx = 0, i = offset; i < data.length; i++) { + c = binTable[data.charCodeAt(i) & 0x7f]; + padding = (data.charAt(i) === pad); + // Skip illegal characters and whitespace + if (c === -1) { + console.error("Illegal character '" + data.charCodeAt(i) + "'"); + continue; + } + + // Collect data into leftdata, update bitcount + leftdata = (leftdata << 6) | c; + leftbits += 6; + + // If we have 8 or more bits, append 8 bits to the result + if (leftbits >= 8) { + leftbits -= 8; + // Append if not padding. + if (!padding) { + result[idx++] = (leftdata >> leftbits) & 0xff; + } + leftdata &= (1 << leftbits) - 1; + } + } + + // If there are any bits left, the base64 string was corrupted + if (leftbits) { + throw {name: 'Base64-Error', + message: 'Corrupted base64 string'}; + } + + return result; +} + +}; /* End of Base64 namespace */ diff --git a/classes/novnc/include/black.css b/classes/novnc/include/black.css new file mode 100644 index 0000000..8f80f66 --- /dev/null +++ b/classes/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/classes/novnc/include/blue.css b/classes/novnc/include/blue.css new file mode 100644 index 0000000..a8baf70 --- /dev/null +++ b/classes/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/classes/novnc/include/des.js b/classes/novnc/include/des.js new file mode 100644 index 0000000..1f95285 --- /dev/null +++ b/classes/novnc/include/des.js @@ -0,0 +1,273 @@ +/* + * Ported from Flashlight VNC ActionScript implementation: + * http://www.wizhelp.com/flashlight-vnc/ + * + * Full attribution follows: + * + * ------------------------------------------------------------------------- + * + * This DES class has been extracted from package Acme.Crypto for use in VNC. + * The unnecessary odd parity code has been removed. + * + * These changes are: + * Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + + * DesCipher - the DES encryption method + * + * The meat of this code is by Dave Zimmerman , and is: + * + * Copyright (c) 1996 Widget Workshop, Inc. All Rights Reserved. + * + * Permission to use, copy, modify, and distribute this software + * and its documentation for NON-COMMERCIAL or COMMERCIAL purposes and + * without fee is hereby granted, provided that this copyright notice is kept + * intact. + * + * WIDGET WORKSHOP MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY + * OF THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED + * TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A + * PARTICULAR PURPOSE, OR NON-INFRINGEMENT. WIDGET WORKSHOP SHALL NOT BE LIABLE + * FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR + * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. + * + * THIS SOFTWARE IS NOT DESIGNED OR INTENDED FOR USE OR RESALE AS ON-LINE + * CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING FAIL-SAFE + * PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES, AIRCRAFT + * NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT LIFE + * SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE + * SOFTWARE COULD LEAD DIRECTLY TO DEATH, PERSONAL INJURY, OR SEVERE + * PHYSICAL OR ENVIRONMENTAL DAMAGE ("HIGH RISK ACTIVITIES"). WIDGET WORKSHOP + * SPECIFICALLY DISCLAIMS ANY EXPRESS OR IMPLIED WARRANTY OF FITNESS FOR + * HIGH RISK ACTIVITIES. + * + * + * The rest is: + * + * Copyright (C) 1996 by Jef Poskanzer . All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Visit the ACME Labs Java page for up-to-date versions of this and other + * fine Java utilities: http://www.acme.com/java/ + */ + +"use strict"; +/*jslint white: false, bitwise: false, plusplus: false */ + +function DES(passwd) { + +// Tables, permutations, S-boxes, etc. +var PC2 = [13,16,10,23, 0, 4, 2,27,14, 5,20, 9,22,18,11, 3, + 25, 7,15, 6,26,19,12, 1,40,51,30,36,46,54,29,39, + 50,44,32,47,43,48,38,55,33,52,45,41,49,35,28,31 ], + totrot = [ 1, 2, 4, 6, 8,10,12,14,15,17,19,21,23,25,27,28], + z = 0x0, a,b,c,d,e,f, SP1,SP2,SP3,SP4,SP5,SP6,SP7,SP8, + keys = []; + +a=1<<16; b=1<<24; c=a|b; d=1<<2; e=1<<10; f=d|e; +SP1 = [c|e,z|z,a|z,c|f,c|d,a|f,z|d,a|z,z|e,c|e,c|f,z|e,b|f,c|d,b|z,z|d, + z|f,b|e,b|e,a|e,a|e,c|z,c|z,b|f,a|d,b|d,b|d,a|d,z|z,z|f,a|f,b|z, + a|z,c|f,z|d,c|z,c|e,b|z,b|z,z|e,c|d,a|z,a|e,b|d,z|e,z|d,b|f,a|f, + c|f,a|d,c|z,b|f,b|d,z|f,a|f,c|e,z|f,b|e,b|e,z|z,a|d,a|e,z|z,c|d]; +a=1<<20; b=1<<31; c=a|b; d=1<<5; e=1<<15; f=d|e; +SP2 = [c|f,b|e,z|e,a|f,a|z,z|d,c|d,b|f,b|d,c|f,c|e,b|z,b|e,a|z,z|d,c|d, + a|e,a|d,b|f,z|z,b|z,z|e,a|f,c|z,a|d,b|d,z|z,a|e,z|f,c|e,c|z,z|f, + z|z,a|f,c|d,a|z,b|f,c|z,c|e,z|e,c|z,b|e,z|d,c|f,a|f,z|d,z|e,b|z, + z|f,c|e,a|z,b|d,a|d,b|f,b|d,a|d,a|e,z|z,b|e,z|f,b|z,c|d,c|f,a|e]; +a=1<<17; b=1<<27; c=a|b; d=1<<3; e=1<<9; f=d|e; +SP3 = [z|f,c|e,z|z,c|d,b|e,z|z,a|f,b|e,a|d,b|d,b|d,a|z,c|f,a|d,c|z,z|f, + b|z,z|d,c|e,z|e,a|e,c|z,c|d,a|f,b|f,a|e,a|z,b|f,z|d,c|f,z|e,b|z, + c|e,b|z,a|d,z|f,a|z,c|e,b|e,z|z,z|e,a|d,c|f,b|e,b|d,z|e,z|z,c|d, + b|f,a|z,b|z,c|f,z|d,a|f,a|e,b|d,c|z,b|f,z|f,c|z,a|f,z|d,c|d,a|e]; +a=1<<13; b=1<<23; c=a|b; d=1<<0; e=1<<7; f=d|e; +SP4 = [c|d,a|f,a|f,z|e,c|e,b|f,b|d,a|d,z|z,c|z,c|z,c|f,z|f,z|z,b|e,b|d, + z|d,a|z,b|z,c|d,z|e,b|z,a|d,a|e,b|f,z|d,a|e,b|e,a|z,c|e,c|f,z|f, + b|e,b|d,c|z,c|f,z|f,z|z,z|z,c|z,a|e,b|e,b|f,z|d,c|d,a|f,a|f,z|e, + c|f,z|f,z|d,a|z,b|d,a|d,c|e,b|f,a|d,a|e,b|z,c|d,z|e,b|z,a|z,c|e]; +a=1<<25; b=1<<30; c=a|b; d=1<<8; e=1<<19; f=d|e; +SP5 = [z|d,a|f,a|e,c|d,z|e,z|d,b|z,a|e,b|f,z|e,a|d,b|f,c|d,c|e,z|f,b|z, + a|z,b|e,b|e,z|z,b|d,c|f,c|f,a|d,c|e,b|d,z|z,c|z,a|f,a|z,c|z,z|f, + z|e,c|d,z|d,a|z,b|z,a|e,c|d,b|f,a|d,b|z,c|e,a|f,b|f,z|d,a|z,c|e, + c|f,z|f,c|z,c|f,a|e,z|z,b|e,c|z,z|f,a|d,b|d,z|e,z|z,b|e,a|f,b|d]; +a=1<<22; b=1<<29; c=a|b; d=1<<4; e=1<<14; f=d|e; +SP6 = [b|d,c|z,z|e,c|f,c|z,z|d,c|f,a|z,b|e,a|f,a|z,b|d,a|d,b|e,b|z,z|f, + z|z,a|d,b|f,z|e,a|e,b|f,z|d,c|d,c|d,z|z,a|f,c|e,z|f,a|e,c|e,b|z, + b|e,z|d,c|d,a|e,c|f,a|z,z|f,b|d,a|z,b|e,b|z,z|f,b|d,c|f,a|e,c|z, + a|f,c|e,z|z,c|d,z|d,z|e,c|z,a|f,z|e,a|d,b|f,z|z,c|e,b|z,a|d,b|f]; +a=1<<21; b=1<<26; c=a|b; d=1<<1; e=1<<11; f=d|e; +SP7 = [a|z,c|d,b|f,z|z,z|e,b|f,a|f,c|e,c|f,a|z,z|z,b|d,z|d,b|z,c|d,z|f, + b|e,a|f,a|d,b|e,b|d,c|z,c|e,a|d,c|z,z|e,z|f,c|f,a|e,z|d,b|z,a|e, + b|z,a|e,a|z,b|f,b|f,c|d,c|d,z|d,a|d,b|z,b|e,a|z,c|e,z|f,a|f,c|e, + z|f,b|d,c|f,c|z,a|e,z|z,z|d,c|f,z|z,a|f,c|z,z|e,b|d,b|e,z|e,a|d]; +a=1<<18; b=1<<28; c=a|b; d=1<<6; e=1<<12; f=d|e; +SP8 = [b|f,z|e,a|z,c|f,b|z,b|f,z|d,b|z,a|d,c|z,c|f,a|e,c|e,a|f,z|e,z|d, + c|z,b|d,b|e,z|f,a|e,a|d,c|d,c|e,z|f,z|z,z|z,c|d,b|d,b|e,a|f,a|z, + a|f,a|z,c|e,z|e,z|d,c|d,z|e,a|f,b|e,z|d,b|d,c|z,c|d,b|z,a|z,b|f, + z|z,c|f,a|d,b|d,c|z,b|e,b|f,z|z,c|f,a|e,a|e,z|f,z|f,a|d,b|z,c|e]; + +// Set the key. +function setKeys(keyBlock) { + var i, j, l, m, n, o, pc1m = [], pcr = [], kn = [], + raw0, raw1, rawi, KnLi; + + for (j = 0, l = 56; j < 56; ++j, l-=8) { + l += l<-5 ? 65 : l<-3 ? 31 : l<-1 ? 63 : l===27 ? 35 : 0; // PC1 + m = l & 0x7; + pc1m[j] = ((keyBlock[l >>> 3] & (1<>> 10; + keys[KnLi] |= (raw1 & 0x00000fc0) >>> 6; + ++KnLi; + keys[KnLi] = (raw0 & 0x0003f000) << 12; + keys[KnLi] |= (raw0 & 0x0000003f) << 16; + keys[KnLi] |= (raw1 & 0x0003f000) >>> 4; + keys[KnLi] |= (raw1 & 0x0000003f); + ++KnLi; + } +} + +// Encrypt 8 bytes of text +function enc8(text) { + var i = 0, b = text.slice(), fval, keysi = 0, + l, r, x; // left, right, accumulator + + // Squash 8 bytes to 2 ints + l = b[i++]<<24 | b[i++]<<16 | b[i++]<<8 | b[i++]; + r = b[i++]<<24 | b[i++]<<16 | b[i++]<<8 | b[i++]; + + x = ((l >>> 4) ^ r) & 0x0f0f0f0f; + r ^= x; + l ^= (x << 4); + x = ((l >>> 16) ^ r) & 0x0000ffff; + r ^= x; + l ^= (x << 16); + x = ((r >>> 2) ^ l) & 0x33333333; + l ^= x; + r ^= (x << 2); + x = ((r >>> 8) ^ l) & 0x00ff00ff; + l ^= x; + r ^= (x << 8); + r = (r << 1) | ((r >>> 31) & 1); + x = (l ^ r) & 0xaaaaaaaa; + l ^= x; + r ^= x; + l = (l << 1) | ((l >>> 31) & 1); + + for (i = 0; i < 8; ++i) { + x = (r << 28) | (r >>> 4); + x ^= keys[keysi++]; + fval = SP7[x & 0x3f]; + fval |= SP5[(x >>> 8) & 0x3f]; + fval |= SP3[(x >>> 16) & 0x3f]; + fval |= SP1[(x >>> 24) & 0x3f]; + x = r ^ keys[keysi++]; + fval |= SP8[x & 0x3f]; + fval |= SP6[(x >>> 8) & 0x3f]; + fval |= SP4[(x >>> 16) & 0x3f]; + fval |= SP2[(x >>> 24) & 0x3f]; + l ^= fval; + x = (l << 28) | (l >>> 4); + x ^= keys[keysi++]; + fval = SP7[x & 0x3f]; + fval |= SP5[(x >>> 8) & 0x3f]; + fval |= SP3[(x >>> 16) & 0x3f]; + fval |= SP1[(x >>> 24) & 0x3f]; + x = l ^ keys[keysi++]; + fval |= SP8[x & 0x0000003f]; + fval |= SP6[(x >>> 8) & 0x3f]; + fval |= SP4[(x >>> 16) & 0x3f]; + fval |= SP2[(x >>> 24) & 0x3f]; + r ^= fval; + } + + r = (r << 31) | (r >>> 1); + x = (l ^ r) & 0xaaaaaaaa; + l ^= x; + r ^= x; + l = (l << 31) | (l >>> 1); + x = ((l >>> 8) ^ r) & 0x00ff00ff; + r ^= x; + l ^= (x << 8); + x = ((l >>> 2) ^ r) & 0x33333333; + r ^= x; + l ^= (x << 2); + x = ((r >>> 16) ^ l) & 0x0000ffff; + l ^= x; + r ^= (x << 16); + x = ((r >>> 4) ^ l) & 0x0f0f0f0f; + l ^= x; + r ^= (x << 4); + + // Spread ints to bytes + x = [r, l]; + for (i = 0; i < 8; i++) { + b[i] = (x[i>>>2] >>> (8*(3 - (i%4)))) % 256; + if (b[i] < 0) { b[i] += 256; } // unsigned + } + return b; +} + +// Encrypt 16 bytes of text using passwd as key +function encrypt(t) { + return enc8(t.slice(0,8)).concat(enc8(t.slice(8,16))); +} + +setKeys(passwd); // Setup keys +return {'encrypt': encrypt}; // Public interface + +} // function DES diff --git a/classes/novnc/include/display.js b/classes/novnc/include/display.js new file mode 100644 index 0000000..2cf262d --- /dev/null +++ b/classes/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/classes/novnc/include/input.js b/classes/novnc/include/input.js new file mode 100644 index 0000000..3124d08 --- /dev/null +++ b/classes/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/classes/novnc/include/logo.js b/classes/novnc/include/logo.js new file mode 100644 index 0000000..befa598 --- /dev/null +++ b/classes/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/classes/novnc/include/playback.js b/classes/novnc/include/playback.js new file mode 100644 index 0000000..22a00a3 --- /dev/null +++ b/classes/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/classes/novnc/include/rfb.js b/classes/novnc/include/rfb.js new file mode 100644 index 0000000..b7aa3f6 --- /dev/null +++ b/classes/novnc/include/rfb.js @@ -0,0 +1,1613 @@ +/* + * noVNC: HTML5 VNC client + * Copyright (C) 2011 Joel Martin + * Licensed under LGPL-3 (see LICENSE.txt) + * + * See README.md for usage and integration instructions. + */ + +/*jslint white: false, browser: true, bitwise: false, plusplus: false */ +/*global window, Util, Display, Keyboard, Mouse, Websock, Websock_native, Base64, DES */ + + +function RFB(defaults) { +"use strict"; + +var that = {}, // Public API methods + conf = {}, // Configuration attributes + + // Pre-declare private functions used before definitions (jslint) + init_vars, updateState, fail, handle_message, + init_msg, normal_msg, framebufferUpdate, print_stats, + + pixelFormat, clientEncodings, fbUpdateRequest, fbUpdateRequests, + keyEvent, pointerEvent, clientCutText, + + extract_data_uri, scan_tight_imgQ, + keyPress, mouseButton, mouseMove, + + checkEvents, // Overridable for testing + + + // + // Private RFB namespace variables + // + rfb_host = '', + rfb_port = 5900, + rfb_password = '', + rfb_path = '', + + rfb_state = 'disconnected', + rfb_version = 0, + rfb_max_version= 3.8, + rfb_auth_scheme= '', + + + // In preference order + encodings = [ + ['COPYRECT', 0x01 ], + ['TIGHT_PNG', -260 ], + ['HEXTILE', 0x05 ], + ['RRE', 0x02 ], + ['RAW', 0x00 ], + ['DesktopSize', -223 ], + ['Cursor', -239 ], + + // Psuedo-encoding settings + ['JPEG_quality_lo', -32 ], + //['JPEG_quality_hi', -23 ], + ['compress_lo', -255 ] + //['compress_hi', -247 ] + ], + + encHandlers = {}, + encNames = {}, + encStats = {}, // [rectCnt, rectCntTot] + + ws = null, // Websock object + display = null, // Display object + keyboard = null, // Keyboard input handler object + mouse = null, // Mouse input handler object + sendTimer = null, // Send Queue check timer + connTimer = null, // connection timer + disconnTimer = null, // disconnection timer + msgTimer = null, // queued handle_message timer + + // Frame buffer update state + FBU = { + rects : 0, + subrects : 0, // RRE + lines : 0, // RAW + tiles : 0, // HEXTILE + bytes : 0, + x : 0, + y : 0, + width : 0, + height : 0, + encoding : 0, + subencoding : -1, + background : null, + imgQ : [] // TIGHT_PNG image queue + }, + + fb_Bpp = 4, + fb_depth = 3, + fb_width = 0, + fb_height = 0, + fb_name = "", + + scan_imgQ_rate = 40, // 25 times per second or so + last_req_time = 0, + rre_chunk_sz = 100, + + timing = { + last_fbu : 0, + fbu_total : 0, + fbu_total_cnt : 0, + full_fbu_total : 0, + full_fbu_cnt : 0, + + fbu_rt_start : 0, + fbu_rt_total : 0, + fbu_rt_cnt : 0 + }, + + test_mode = false, + + def_con_timeout = Websock_native ? 2 : 5, + + /* Mouse state */ + mouse_buttonMask = 0, + mouse_arr = [], + viewportDragging = false, + viewportDragPos = {}; + +// Configuration attributes +Util.conf_defaults(conf, that, defaults, [ + ['target', 'wo', 'dom', null, 'VNC display rendering Canvas object'], + ['focusContainer', 'wo', 'dom', document, 'DOM element that captures keyboard input'], + + ['encrypt', 'rw', 'bool', false, 'Use TLS/SSL/wss encryption'], + ['true_color', 'rw', 'bool', true, 'Request true color pixel data'], + ['local_cursor', 'rw', 'bool', false, 'Request locally rendered cursor'], + ['shared', 'rw', 'bool', true, 'Request shared mode'], + + ['connectTimeout', 'rw', 'int', def_con_timeout, 'Time (s) to wait for connection'], + ['disconnectTimeout', 'rw', 'int', 3, 'Time (s) to wait for disconnection'], + + ['viewportDrag', 'rw', 'bool', false, 'Move the viewport on mouse drags'], + + ['check_rate', 'rw', 'int', 217, 'Timing (ms) of send/receive check'], + ['fbu_req_rate', 'rw', 'int', 1413, 'Timing (ms) of frameBufferUpdate requests'], + + // Callback functions + ['onUpdateState', 'rw', 'func', function() { }, + 'onUpdateState(rfb, state, oldstate, statusMsg): RFB state update/change '], + ['onPasswordRequired', 'rw', 'func', function() { }, + 'onPasswordRequired(rfb): VNC password is required '], + ['onClipboard', 'rw', 'func', function() { }, + 'onClipboard(rfb, text): RFB clipboard contents received'], + ['onBell', 'rw', 'func', function() { }, + 'onBell(rfb): RFB Bell message received '], + ['onFBUReceive', 'rw', 'func', function() { }, + 'onFBUReceive(rfb, fbu): RFB FBU received but not yet processed '], + ['onFBUComplete', 'rw', 'func', function() { }, + 'onFBUComplete(rfb, fbu): RFB FBU received and processed '], + + // These callback names are deprecated + ['updateState', 'rw', 'func', function() { }, + 'obsolete, use onUpdateState'], + ['clipboardReceive', 'rw', 'func', function() { }, + 'obsolete, use onClipboard'] + ]); + + +// Override/add some specific configuration getters/setters +that.set_local_cursor = function(cursor) { + if ((!cursor) || (cursor in {'0':1, 'no':1, 'false':1})) { + conf.local_cursor = false; + } else { + if (display.get_cursor_uri()) { + conf.local_cursor = true; + } else { + Util.Warn("Browser does not support local cursor"); + } + } +}; + +// These are fake configuration getters +that.get_display = function() { return display; }; + +that.get_keyboard = function() { return keyboard; }; + +that.get_mouse = function() { return mouse; }; + + + +// +// Setup routines +// + +// Create the public API interface and initialize values that stay +// constant across connect/disconnect +function constructor() { + var i, rmode; + Util.Debug(">> RFB.constructor"); + + // Create lookup tables based encoding number + for (i=0; i < encodings.length; i+=1) { + encHandlers[encodings[i][1]] = encHandlers[encodings[i][0]]; + encNames[encodings[i][1]] = encodings[i][0]; + encStats[encodings[i][1]] = [0, 0]; + } + // Initialize display, mouse, keyboard, and websock + try { + display = new Display({'target': conf.target}); + } catch (exc) { + Util.Error("Display exception: " + exc); + updateState('fatal', "No working Display"); + } + keyboard = new Keyboard({'target': conf.focusContainer, + 'onKeyPress': keyPress}); + mouse = new Mouse({'target': conf.target, + 'onMouseButton': mouseButton, + 'onMouseMove': mouseMove}); + + rmode = display.get_render_mode(); + + ws = new Websock(); + ws.on('message', handle_message); + ws.on('open', function() { + if (rfb_state === "connect") { + updateState('ProtocolVersion', "Starting VNC handshake"); + } else { + fail("Got unexpected WebSockets connection"); + } + }); + ws.on('close', function() { + if (rfb_state === 'disconnect') { + updateState('disconnected', 'VNC disconnected'); + } else if (rfb_state === 'ProtocolVersion') { + fail('Failed to connect to server'); + } else if (rfb_state in {'failed':1, 'disconnected':1}) { + Util.Error("Received onclose while disconnected"); + } else { + fail('Server disconnected'); + } + }); + ws.on('error', function(e) { + fail("WebSock error: " + e); + }); + + + init_vars(); + + /* Check web-socket-js if no builtin WebSocket support */ + if (Websock_native) { + Util.Info("Using native WebSockets"); + updateState('loaded', 'noVNC ready: native WebSockets, ' + rmode); + } else { + Util.Warn("Using web-socket-js bridge. Flash version: " + + Util.Flash.version); + if ((! Util.Flash) || + (Util.Flash.version < 9)) { + updateState('fatal', "WebSockets or Adobe Flash<\/a> is required"); + } else if (document.location.href.substr(0, 7) === "file://") { + updateState('fatal', + "'file://' URL is incompatible with Adobe Flash"); + } else { + updateState('loaded', 'noVNC ready: WebSockets emulation, ' + rmode); + } + } + + Util.Debug("<< RFB.constructor"); + return that; // Return the public API interface +} + +function connect() { + Util.Debug(">> RFB.connect"); + + var uri = ""; + if (conf.encrypt) { + uri = "wss://"; + } else { + uri = "ws://"; + } + uri += rfb_host + ":" + rfb_port + "/" + rfb_path; + Util.Info("connecting to " + uri); + ws.open(uri); + + Util.Debug("<< RFB.connect"); +} + +// Initialize variables that are reset before each connection +init_vars = function() { + var i; + + /* Reset state */ + ws.init(); + + FBU.rects = 0; + FBU.subrects = 0; // RRE and HEXTILE + FBU.lines = 0; // RAW + FBU.tiles = 0; // HEXTILE + FBU.imgQ = []; // TIGHT_PNG image queue + mouse_buttonMask = 0; + mouse_arr = []; + + // Clear the per connection encoding stats + for (i=0; i < encodings.length; i+=1) { + encStats[encodings[i][1]][0] = 0; + } +}; + +// Print statistics +print_stats = function() { + var i, s; + Util.Info("Encoding stats for this connection:"); + for (i=0; i < encodings.length; i+=1) { + s = encStats[encodings[i][1]]; + if ((s[0] + s[1]) > 0) { + Util.Info(" " + encodings[i][0] + ": " + + s[0] + " rects"); + } + } + Util.Info("Encoding stats since page load:"); + for (i=0; i < encodings.length; i+=1) { + s = encStats[encodings[i][1]]; + if ((s[0] + s[1]) > 0) { + Util.Info(" " + encodings[i][0] + ": " + + s[1] + " rects"); + } + } +}; + +// +// Utility routines +// + + +/* + * Page states: + * loaded - page load, equivalent to disconnected + * disconnected - idle state + * connect - starting to connect (to ProtocolVersion) + * normal - connected + * disconnect - starting to disconnect + * failed - abnormal disconnect + * fatal - failed to load page, or fatal error + * + * RFB protocol initialization states: + * ProtocolVersion + * Security + * Authentication + * password - waiting for password, not part of RFB + * SecurityResult + * ClientInitialization - not triggered by server message + * ServerInitialization (to normal) + */ +updateState = function(state, statusMsg) { + var func, cmsg, oldstate = rfb_state; + + if (state === oldstate) { + /* Already here, ignore */ + Util.Debug("Already in state '" + state + "', ignoring."); + return; + } + + /* + * These are disconnected states. A previous connect may + * asynchronously cause a connection so make sure we are closed. + */ + if (state in {'disconnected':1, 'loaded':1, 'connect':1, + 'disconnect':1, 'failed':1, 'fatal':1}) { + if (sendTimer) { + clearInterval(sendTimer); + sendTimer = null; + } + + if (msgTimer) { + clearInterval(msgTimer); + msgTimer = null; + } + + if (display && display.get_context()) { + keyboard.ungrab(); + mouse.ungrab(); + display.defaultCursor(); + if ((Util.get_logging() !== 'debug') || + (state === 'loaded')) { + // Show noVNC logo on load and when disconnected if + // debug is off + display.clear(); + } + } + + ws.close(); + } + + if (oldstate === 'fatal') { + Util.Error("Fatal error, cannot continue"); + } + + if ((state === 'failed') || (state === 'fatal')) { + func = Util.Error; + } else { + func = Util.Warn; + } + + if ((oldstate === 'failed') && (state === 'disconnected')) { + // Do disconnect action, but stay in failed state. + rfb_state = 'failed'; + } else { + rfb_state = state; + } + + cmsg = typeof(statusMsg) !== 'undefined' ? (" Msg: " + statusMsg) : ""; + func("New state '" + rfb_state + "', was '" + oldstate + "'." + cmsg); + + if (connTimer && (rfb_state !== 'connect')) { + Util.Debug("Clearing connect timer"); + clearInterval(connTimer); + connTimer = null; + } + + if (disconnTimer && (rfb_state !== 'disconnect')) { + Util.Debug("Clearing disconnect timer"); + clearInterval(disconnTimer); + disconnTimer = null; + } + + switch (state) { + case 'normal': + if ((oldstate === 'disconnected') || (oldstate === 'failed')) { + Util.Error("Invalid transition from 'disconnected' or 'failed' to 'normal'"); + } + + break; + + + case 'connect': + + connTimer = setTimeout(function () { + fail("Connect timeout"); + }, conf.connectTimeout * 1000); + + init_vars(); + connect(); + + // WebSocket.onopen transitions to 'ProtocolVersion' + break; + + + case 'disconnect': + + if (! test_mode) { + disconnTimer = setTimeout(function () { + fail("Disconnect timeout"); + }, conf.disconnectTimeout * 1000); + } + + print_stats(); + + // WebSocket.onclose transitions to 'disconnected' + break; + + + case 'failed': + if (oldstate === 'disconnected') { + Util.Error("Invalid transition from 'disconnected' to 'failed'"); + } + if (oldstate === 'normal') { + Util.Error("Error while connected."); + } + if (oldstate === 'init') { + Util.Error("Error while initializing."); + } + + // Make sure we transition to disconnected + setTimeout(function() { updateState('disconnected'); }, 50); + + break; + + + default: + // No state change action to take + + } + + if ((oldstate === 'failed') && (state === 'disconnected')) { + // Leave the failed message + conf.updateState(that, state, oldstate); // Obsolete + conf.onUpdateState(that, state, oldstate); + } else { + conf.updateState(that, state, oldstate, statusMsg); // Obsolete + conf.onUpdateState(that, state, oldstate, statusMsg); + } +}; + +fail = function(msg) { + updateState('failed', msg); + return false; +}; + +handle_message = function() { + //Util.Debug(">> handle_message ws.rQlen(): " + ws.rQlen()); + //Util.Debug("ws.rQslice(0,20): " + ws.rQslice(0,20) + " (" + ws.rQlen() + ")"); + if (ws.rQlen() === 0) { + Util.Warn("handle_message called on empty receive queue"); + return; + } + switch (rfb_state) { + case 'disconnected': + case 'failed': + Util.Error("Got data while disconnected"); + break; + case 'normal': + if (normal_msg() && ws.rQlen() > 0) { + // true means we can continue processing + // Give other events a chance to run + if (msgTimer === null) { + Util.Debug("More data to process, creating timer"); + msgTimer = setTimeout(function () { + msgTimer = null; + handle_message(); + }, 10); + } else { + Util.Debug("More data to process, existing timer"); + } + } + break; + default: + init_msg(); + break; + } +}; + + +function genDES(password, challenge) { + var i, passwd = []; + for (i=0; i < password.length; i += 1) { + passwd.push(password.charCodeAt(i)); + } + return (new DES(passwd)).encrypt(challenge); +} + +function flushClient() { + if (mouse_arr.length > 0) { + //send(mouse_arr.concat(fbUpdateRequests())); + ws.send(mouse_arr); + setTimeout(function() { + ws.send(fbUpdateRequests()); + }, 50); + + mouse_arr = []; + return true; + } else { + return false; + } +} + +// overridable for testing +checkEvents = function() { + var now; + if (rfb_state === 'normal' && !viewportDragging) { + if (! flushClient()) { + now = new Date().getTime(); + if (now > last_req_time + conf.fbu_req_rate) { + last_req_time = now; + ws.send(fbUpdateRequests()); + } + } + } + setTimeout(checkEvents, conf.check_rate); +}; + +keyPress = function(keysym, down) { + var arr; + arr = keyEvent(keysym, down); + arr = arr.concat(fbUpdateRequests()); + ws.send(arr); +}; + +mouseButton = function(x, y, down, bmask) { + if (down) { + mouse_buttonMask |= bmask; + } else { + mouse_buttonMask ^= bmask; + } + + if (conf.viewportDrag) { + if (down && !viewportDragging) { + viewportDragging = true; + viewportDragPos = {'x': x, 'y': y}; + + // Skip sending mouse events + return; + } else { + viewportDragging = false; + } + } + + mouse_arr = mouse_arr.concat( + pointerEvent(display.absX(x), display.absY(y)) ); + flushClient(); +}; + +mouseMove = function(x, y) { + //Util.Debug('>> mouseMove ' + x + "," + y); + var deltaX, deltaY; + + if (viewportDragging) { + //deltaX = x - viewportDragPos.x; // drag viewport + deltaX = viewportDragPos.x - x; // drag frame buffer + //deltaY = y - viewportDragPos.y; // drag viewport + deltaY = viewportDragPos.y - y; // drag frame buffer + viewportDragPos = {'x': x, 'y': y}; + + display.viewportChange(deltaX, deltaY); + + // Skip sending mouse events + return; + } + + mouse_arr = mouse_arr.concat( + pointerEvent(display.absX(x), display.absY(y)) ); +}; + + +// +// Server message handlers +// + +// RFB/VNC initialisation message handler +init_msg = function() { + //Util.Debug(">> init_msg [rfb_state '" + rfb_state + "']"); + + var strlen, reason, length, sversion, cversion, + i, types, num_types, challenge, response, bpp, depth, + big_endian, red_max, green_max, blue_max, red_shift, + green_shift, blue_shift, true_color, name_length; + + //Util.Debug("ws.rQ (" + ws.rQlen() + ") " + ws.rQslice(0)); + switch (rfb_state) { + + case 'ProtocolVersion' : + if (ws.rQlen() < 12) { + return fail("Incomplete protocol version"); + } + sversion = ws.rQshiftStr(12).substr(4,7); + Util.Info("Server ProtocolVersion: " + sversion); + switch (sversion) { + case "003.003": rfb_version = 3.3; break; + case "003.006": rfb_version = 3.3; break; // UltraVNC + case "003.007": rfb_version = 3.7; break; + case "003.008": rfb_version = 3.8; break; + default: + return fail("Invalid server version " + sversion); + } + if (rfb_version > rfb_max_version) { + rfb_version = rfb_max_version; + } + + if (! test_mode) { + sendTimer = setInterval(function() { + // Send updates either at a rate of one update + // every 50ms, or whatever slower rate the network + // can handle. + ws.flush(); + }, 50); + } + + cversion = "00" + parseInt(rfb_version,10) + + ".00" + ((rfb_version * 10) % 10); + ws.send_string("RFB " + cversion + "\n"); + updateState('Security', "Sent ProtocolVersion: " + cversion); + break; + + case 'Security' : + if (rfb_version >= 3.7) { + // Server sends supported list, client decides + num_types = ws.rQshift8(); + if (ws.rQwait("security type", num_types, 1)) { return false; } + if (num_types === 0) { + strlen = ws.rQshift32(); + reason = ws.rQshiftStr(strlen); + return fail("Security failure: " + reason); + } + rfb_auth_scheme = 0; + types = ws.rQshiftBytes(num_types); + Util.Debug("Server security types: " + types); + for (i=0; i < types.length; i+=1) { + if ((types[i] > rfb_auth_scheme) && (types[i] < 3)) { + rfb_auth_scheme = types[i]; + } + } + if (rfb_auth_scheme === 0) { + return fail("Unsupported security types: " + types); + } + + ws.send([rfb_auth_scheme]); + } else { + // Server decides + if (ws.rQwait("security scheme", 4)) { return false; } + rfb_auth_scheme = ws.rQshift32(); + } + updateState('Authentication', + "Authenticating using scheme: " + rfb_auth_scheme); + init_msg(); // Recursive fallthrough (workaround JSLint complaint) + break; + + // Triggered by fallthough, not by server message + case 'Authentication' : + //Util.Debug("Security auth scheme: " + rfb_auth_scheme); + switch (rfb_auth_scheme) { + case 0: // connection failed + if (ws.rQwait("auth reason", 4)) { return false; } + strlen = ws.rQshift32(); + reason = ws.rQshiftStr(strlen); + return fail("Auth failure: " + reason); + case 1: // no authentication + if (rfb_version >= 3.8) { + updateState('SecurityResult'); + return; + } + // Fall through to ClientInitialisation + break; + case 2: // VNC authentication + if (rfb_password.length === 0) { + // Notify via both callbacks since it is kind of + // a RFB state change and a UI interface issue. + updateState('password', "Password Required"); + conf.onPasswordRequired(that); + return; + } + if (ws.rQwait("auth challenge", 16)) { return false; } + challenge = ws.rQshiftBytes(16); + //Util.Debug("Password: " + rfb_password); + //Util.Debug("Challenge: " + challenge + + // " (" + challenge.length + ")"); + response = genDES(rfb_password, challenge); + //Util.Debug("Response: " + response + + // " (" + response.length + ")"); + + //Util.Debug("Sending DES encrypted auth response"); + ws.send(response); + updateState('SecurityResult'); + return; + default: + fail("Unsupported auth scheme: " + rfb_auth_scheme); + return; + } + updateState('ClientInitialisation', "No auth required"); + init_msg(); // Recursive fallthrough (workaround JSLint complaint) + break; + + case 'SecurityResult' : + if (ws.rQwait("VNC auth response ", 4)) { return false; } + switch (ws.rQshift32()) { + case 0: // OK + // Fall through to ClientInitialisation + break; + case 1: // failed + if (rfb_version >= 3.8) { + length = ws.rQshift32(); + if (ws.rQwait("SecurityResult reason", length, 8)) { + return false; + } + reason = ws.rQshiftStr(length); + fail(reason); + } else { + fail("Authentication failed"); + } + return; + case 2: // too-many + return fail("Too many auth attempts"); + } + updateState('ClientInitialisation', "Authentication OK"); + init_msg(); // Recursive fallthrough (workaround JSLint complaint) + break; + + // Triggered by fallthough, not by server message + case 'ClientInitialisation' : + ws.send([conf.shared ? 1 : 0]); // ClientInitialisation + updateState('ServerInitialisation', "Authentication OK"); + break; + + case 'ServerInitialisation' : + if (ws.rQwait("server initialization", 24)) { return false; } + + /* Screen size */ + fb_width = ws.rQshift16(); + fb_height = ws.rQshift16(); + + /* PIXEL_FORMAT */ + bpp = ws.rQshift8(); + depth = ws.rQshift8(); + big_endian = ws.rQshift8(); + true_color = ws.rQshift8(); + + red_max = ws.rQshift16(); + green_max = ws.rQshift16(); + blue_max = ws.rQshift16(); + red_shift = ws.rQshift8(); + green_shift = ws.rQshift8(); + blue_shift = ws.rQshift8(); + ws.rQshiftStr(3); // padding + + Util.Info("Screen: " + fb_width + "x" + fb_height + + ", bpp: " + bpp + ", depth: " + depth + + ", big_endian: " + big_endian + + ", true_color: " + true_color + + ", red_max: " + red_max + + ", green_max: " + green_max + + ", blue_max: " + blue_max + + ", red_shift: " + red_shift + + ", green_shift: " + green_shift + + ", blue_shift: " + blue_shift); + + /* Connection name/title */ + name_length = ws.rQshift32(); + fb_name = ws.rQshiftStr(name_length); + + display.set_true_color(conf.true_color); + display.resize(fb_width, fb_height); + keyboard.grab(); + mouse.grab(); + + if (conf.true_color) { + fb_Bpp = 4; + fb_depth = 3; + } else { + fb_Bpp = 1; + fb_depth = 1; + } + + response = pixelFormat(); + response = response.concat(clientEncodings()); + response = response.concat(fbUpdateRequests()); + timing.fbu_rt_start = (new Date()).getTime(); + ws.send(response); + + /* Start pushing/polling */ + setTimeout(checkEvents, conf.check_rate); + setTimeout(scan_tight_imgQ, scan_imgQ_rate); + + if (conf.encrypt) { + updateState('normal', "Connected (encrypted) to: " + fb_name); + } else { + updateState('normal', "Connected (unencrypted) to: " + fb_name); + } + break; + } + //Util.Debug("<< init_msg"); +}; + + +/* Normal RFB/VNC server message handler */ +normal_msg = function() { + //Util.Debug(">> normal_msg"); + + var ret = true, msg_type, length, text, + c, first_colour, num_colours, red, green, blue; + + if (FBU.rects > 0) { + msg_type = 0; + } else { + msg_type = ws.rQshift8(); + } + switch (msg_type) { + case 0: // FramebufferUpdate + ret = framebufferUpdate(); // false means need more data + break; + case 1: // SetColourMapEntries + Util.Debug("SetColourMapEntries"); + ws.rQshift8(); // Padding + first_colour = ws.rQshift16(); // First colour + num_colours = ws.rQshift16(); + for (c=0; c < num_colours; c+=1) { + red = ws.rQshift16(); + //Util.Debug("red before: " + red); + red = parseInt(red / 256, 10); + //Util.Debug("red after: " + red); + green = parseInt(ws.rQshift16() / 256, 10); + blue = parseInt(ws.rQshift16() / 256, 10); + display.set_colourMap([red, green, blue], first_colour + c); + } + Util.Debug("colourMap: " + display.get_colourMap()); + Util.Info("Registered " + num_colours + " colourMap entries"); + //Util.Debug("colourMap: " + display.get_colourMap()); + break; + case 2: // Bell + Util.Debug("Bell"); + conf.onBell(that); + break; + case 3: // ServerCutText + Util.Debug("ServerCutText"); + if (ws.rQwait("ServerCutText header", 7, 1)) { return false; } + ws.rQshiftBytes(3); // Padding + length = ws.rQshift32(); + if (ws.rQwait("ServerCutText", length, 8)) { return false; } + + text = ws.rQshiftStr(length); + conf.clipboardReceive(that, text); // Obsolete + conf.onClipboard(that, text); + break; + default: + fail("Disconnected: illegal server message type " + msg_type); + Util.Debug("ws.rQslice(0,30):" + ws.rQslice(0,30)); + break; + } + //Util.Debug("<< normal_msg"); + return ret; +}; + +framebufferUpdate = function() { + var now, hdr, fbu_rt_diff, ret = true; + + if (FBU.rects === 0) { + //Util.Debug("New FBU: ws.rQslice(0,20): " + ws.rQslice(0,20)); + if (ws.rQwait("FBU header", 3)) { + ws.rQunshift8(0); // FBU msg_type + return false; + } + ws.rQshift8(); // padding + FBU.rects = ws.rQshift16(); + //Util.Debug("FramebufferUpdate, rects:" + FBU.rects); + FBU.bytes = 0; + timing.cur_fbu = 0; + if (timing.fbu_rt_start > 0) { + now = (new Date()).getTime(); + Util.Info("First FBU latency: " + (now - timing.fbu_rt_start)); + } + } + + while (FBU.rects > 0) { + if (rfb_state !== "normal") { + return false; + } + if (ws.rQwait("FBU", FBU.bytes)) { return false; } + if (FBU.bytes === 0) { + if (ws.rQwait("rect header", 12)) { return false; } + /* New FramebufferUpdate */ + + hdr = ws.rQshiftBytes(12); + FBU.x = (hdr[0] << 8) + hdr[1]; + FBU.y = (hdr[2] << 8) + hdr[3]; + FBU.width = (hdr[4] << 8) + hdr[5]; + FBU.height = (hdr[6] << 8) + hdr[7]; + FBU.encoding = parseInt((hdr[8] << 24) + (hdr[9] << 16) + + (hdr[10] << 8) + hdr[11], 10); + + conf.onFBUReceive(that, + {'x': FBU.x, 'y': FBU.y, + 'width': FBU.width, 'height': FBU.height, + 'encoding': FBU.encoding, + 'encodingName': encNames[FBU.encoding]}); + + if (encNames[FBU.encoding]) { + // Debug: + /* + var msg = "FramebufferUpdate rects:" + FBU.rects; + msg += " x: " + FBU.x + " y: " + FBU.y; + msg += " width: " + FBU.width + " height: " + FBU.height; + msg += " encoding:" + FBU.encoding; + msg += "(" + encNames[FBU.encoding] + ")"; + msg += ", ws.rQlen(): " + ws.rQlen(); + Util.Debug(msg); + */ + } else { + fail("Disconnected: unsupported encoding " + + FBU.encoding); + return false; + } + } + + timing.last_fbu = (new Date()).getTime(); + + ret = encHandlers[FBU.encoding](); + + now = (new Date()).getTime(); + timing.cur_fbu += (now - timing.last_fbu); + + if (ret) { + encStats[FBU.encoding][0] += 1; + encStats[FBU.encoding][1] += 1; + } + + if (FBU.rects === 0) { + if (((FBU.width === fb_width) && + (FBU.height === fb_height)) || + (timing.fbu_rt_start > 0)) { + timing.full_fbu_total += timing.cur_fbu; + timing.full_fbu_cnt += 1; + Util.Info("Timing of full FBU, cur: " + + timing.cur_fbu + ", total: " + + timing.full_fbu_total + ", cnt: " + + timing.full_fbu_cnt + ", avg: " + + (timing.full_fbu_total / + timing.full_fbu_cnt)); + } + if (timing.fbu_rt_start > 0) { + fbu_rt_diff = now - timing.fbu_rt_start; + timing.fbu_rt_total += fbu_rt_diff; + timing.fbu_rt_cnt += 1; + Util.Info("full FBU round-trip, cur: " + + fbu_rt_diff + ", total: " + + timing.fbu_rt_total + ", cnt: " + + timing.fbu_rt_cnt + ", avg: " + + (timing.fbu_rt_total / + timing.fbu_rt_cnt)); + timing.fbu_rt_start = 0; + } + } + if (! ret) { + return ret; // false ret means need more data + } + } + + conf.onFBUComplete(that, + {'x': FBU.x, 'y': FBU.y, + 'width': FBU.width, 'height': FBU.height, + 'encoding': FBU.encoding, + 'encodingName': encNames[FBU.encoding]}); + + return true; // We finished this FBU +}; + +// +// FramebufferUpdate encodings +// + +encHandlers.RAW = function display_raw() { + //Util.Debug(">> display_raw (" + ws.rQlen() + " bytes)"); + + var cur_y, cur_height; + + if (FBU.lines === 0) { + FBU.lines = FBU.height; + } + FBU.bytes = FBU.width * fb_Bpp; // At least a line + if (ws.rQwait("RAW", FBU.bytes)) { return false; } + cur_y = FBU.y + (FBU.height - FBU.lines); + cur_height = Math.min(FBU.lines, + Math.floor(ws.rQlen()/(FBU.width * fb_Bpp))); + display.blitImage(FBU.x, cur_y, FBU.width, cur_height, + ws.get_rQ(), ws.get_rQi()); + ws.rQshiftBytes(FBU.width * cur_height * fb_Bpp); + FBU.lines -= cur_height; + + if (FBU.lines > 0) { + FBU.bytes = FBU.width * fb_Bpp; // At least another line + } else { + FBU.rects -= 1; + FBU.bytes = 0; + } + //Util.Debug("<< display_raw (" + ws.rQlen() + " bytes)"); + return true; +}; + +encHandlers.COPYRECT = function display_copy_rect() { + //Util.Debug(">> display_copy_rect"); + + var old_x, old_y; + + if (ws.rQwait("COPYRECT", 4)) { return false; } + old_x = ws.rQshift16(); + old_y = ws.rQshift16(); + display.copyImage(old_x, old_y, FBU.x, FBU.y, FBU.width, FBU.height); + FBU.rects -= 1; + FBU.bytes = 0; + return true; +}; + +encHandlers.RRE = function display_rre() { + //Util.Debug(">> display_rre (" + ws.rQlen() + " bytes)"); + var color, x, y, width, height, chunk; + + if (FBU.subrects === 0) { + if (ws.rQwait("RRE", 4+fb_Bpp)) { return false; } + FBU.subrects = ws.rQshift32(); + color = ws.rQshiftBytes(fb_Bpp); // Background + display.fillRect(FBU.x, FBU.y, FBU.width, FBU.height, color); + } + while ((FBU.subrects > 0) && (ws.rQlen() >= (fb_Bpp + 8))) { + color = ws.rQshiftBytes(fb_Bpp); + x = ws.rQshift16(); + y = ws.rQshift16(); + width = ws.rQshift16(); + height = ws.rQshift16(); + display.fillRect(FBU.x + x, FBU.y + y, width, height, color); + FBU.subrects -= 1; + } + //Util.Debug(" display_rre: rects: " + FBU.rects + + // ", FBU.subrects: " + FBU.subrects); + + if (FBU.subrects > 0) { + chunk = Math.min(rre_chunk_sz, FBU.subrects); + FBU.bytes = (fb_Bpp + 8) * chunk; + } else { + FBU.rects -= 1; + FBU.bytes = 0; + } + //Util.Debug("<< display_rre, FBU.bytes: " + FBU.bytes); + return true; +}; + +encHandlers.HEXTILE = function display_hextile() { + //Util.Debug(">> display_hextile"); + var subencoding, subrects, color, cur_tile, + tile_x, x, w, tile_y, y, h, xy, s, sx, sy, wh, sw, sh, + rQ = ws.get_rQ(), rQi = ws.get_rQi(); + + if (FBU.tiles === 0) { + FBU.tiles_x = Math.ceil(FBU.width/16); + FBU.tiles_y = Math.ceil(FBU.height/16); + FBU.total_tiles = FBU.tiles_x * FBU.tiles_y; + FBU.tiles = FBU.total_tiles; + } + + /* FBU.bytes comes in as 1, ws.rQlen() at least 1 */ + while (FBU.tiles > 0) { + FBU.bytes = 1; + if (ws.rQwait("HEXTILE subencoding", FBU.bytes)) { return false; } + subencoding = rQ[rQi]; // Peek + if (subencoding > 30) { // Raw + fail("Disconnected: illegal hextile subencoding " + subencoding); + //Util.Debug("ws.rQslice(0,30):" + ws.rQslice(0,30)); + return false; + } + subrects = 0; + cur_tile = FBU.total_tiles - FBU.tiles; + tile_x = cur_tile % FBU.tiles_x; + tile_y = Math.floor(cur_tile / FBU.tiles_x); + x = FBU.x + tile_x * 16; + y = FBU.y + tile_y * 16; + w = Math.min(16, (FBU.x + FBU.width) - x); + h = Math.min(16, (FBU.y + FBU.height) - y); + + /* Figure out how much we are expecting */ + if (subencoding & 0x01) { // Raw + //Util.Debug(" Raw subencoding"); + FBU.bytes += w * h * fb_Bpp; + } else { + if (subencoding & 0x02) { // Background + FBU.bytes += fb_Bpp; + } + if (subencoding & 0x04) { // Foreground + FBU.bytes += fb_Bpp; + } + if (subencoding & 0x08) { // AnySubrects + FBU.bytes += 1; // Since we aren't shifting it off + if (ws.rQwait("hextile subrects header", FBU.bytes)) { return false; } + subrects = rQ[rQi + FBU.bytes-1]; // Peek + if (subencoding & 0x10) { // SubrectsColoured + FBU.bytes += subrects * (fb_Bpp + 2); + } else { + FBU.bytes += subrects * 2; + } + } + } + + /* + Util.Debug(" tile:" + cur_tile + "/" + (FBU.total_tiles - 1) + + " (" + tile_x + "," + tile_y + ")" + + " [" + x + "," + y + "]@" + w + "x" + h + + ", subenc:" + subencoding + + "(last: " + FBU.lastsubencoding + "), subrects:" + + subrects + + ", ws.rQlen():" + ws.rQlen() + ", FBU.bytes:" + FBU.bytes + + " last:" + ws.rQslice(FBU.bytes-10, FBU.bytes) + + " next:" + ws.rQslice(FBU.bytes-1, FBU.bytes+10)); + */ + if (ws.rQwait("hextile", FBU.bytes)) { return false; } + + /* We know the encoding and have a whole tile */ + FBU.subencoding = rQ[rQi]; + rQi += 1; + if (FBU.subencoding === 0) { + if (FBU.lastsubencoding & 0x01) { + /* Weird: ignore blanks after RAW */ + Util.Debug(" Ignoring blank after RAW"); + } else { + display.fillRect(x, y, w, h, FBU.background); + } + } else if (FBU.subencoding & 0x01) { // Raw + display.blitImage(x, y, w, h, rQ, rQi); + rQi += FBU.bytes - 1; + } else { + if (FBU.subencoding & 0x02) { // Background + FBU.background = rQ.slice(rQi, rQi + fb_Bpp); + rQi += fb_Bpp; + } + if (FBU.subencoding & 0x04) { // Foreground + FBU.foreground = rQ.slice(rQi, rQi + fb_Bpp); + rQi += fb_Bpp; + } + + display.startTile(x, y, w, h, FBU.background); + if (FBU.subencoding & 0x08) { // AnySubrects + subrects = rQ[rQi]; + rQi += 1; + for (s = 0; s < subrects; s += 1) { + if (FBU.subencoding & 0x10) { // SubrectsColoured + color = rQ.slice(rQi, rQi + fb_Bpp); + rQi += fb_Bpp; + } else { + color = FBU.foreground; + } + xy = rQ[rQi]; + rQi += 1; + sx = (xy >> 4); + sy = (xy & 0x0f); + + wh = rQ[rQi]; + rQi += 1; + sw = (wh >> 4) + 1; + sh = (wh & 0x0f) + 1; + + display.subTile(sx, sy, sw, sh, color); + } + } + display.finishTile(); + } + ws.set_rQi(rQi); + FBU.lastsubencoding = FBU.subencoding; + FBU.bytes = 0; + FBU.tiles -= 1; + } + + if (FBU.tiles === 0) { + FBU.rects -= 1; + } + + //Util.Debug("<< display_hextile"); + return true; +}; + + +encHandlers.TIGHT_PNG = function display_tight_png() { + //Util.Debug(">> display_tight_png"); + var ctl, cmode, clength, getCLength, color, img; + //Util.Debug(" FBU.rects: " + FBU.rects); + //Util.Debug(" starting ws.rQslice(0,20): " + ws.rQslice(0,20) + " (" + ws.rQlen() + ")"); + + FBU.bytes = 1; // compression-control byte + if (ws.rQwait("TIGHT compression-control", FBU.bytes)) { return false; } + + // Get 'compact length' header and data size + getCLength = function (arr) { + var header = 1, data = 0; + data += arr[0] & 0x7f; + if (arr[0] & 0x80) { + header += 1; + data += (arr[1] & 0x7f) << 7; + if (arr[1] & 0x80) { + header += 1; + data += arr[2] << 14; + } + } + return [header, data]; + }; + + ctl = ws.rQpeek8(); + switch (ctl >> 4) { + case 0x08: cmode = "fill"; break; + case 0x09: cmode = "jpeg"; break; + case 0x0A: cmode = "png"; break; + default: throw("Illegal basic compression received, ctl: " + ctl); + } + switch (cmode) { + // fill uses fb_depth because TPIXELs drop the padding byte + case "fill": FBU.bytes += fb_depth; break; // TPIXEL + case "jpeg": FBU.bytes += 3; break; // max clength + case "png": FBU.bytes += 3; break; // max clength + } + + if (ws.rQwait("TIGHT " + cmode, FBU.bytes)) { return false; } + + //Util.Debug(" ws.rQslice(0,20): " + ws.rQslice(0,20) + " (" + ws.rQlen() + ")"); + //Util.Debug(" cmode: " + cmode); + + // Determine FBU.bytes + switch (cmode) { + case "fill": + ws.rQshift8(); // shift off ctl + color = ws.rQshiftBytes(fb_depth); + FBU.imgQ.push({ + 'type': 'fill', + 'img': {'complete': true}, + 'x': FBU.x, + 'y': FBU.y, + 'width': FBU.width, + 'height': FBU.height, + 'color': color}); + break; + case "jpeg": + case "png": + clength = getCLength(ws.rQslice(1, 4)); + FBU.bytes = 1 + clength[0] + clength[1]; // ctl + clength size + jpeg-data + if (ws.rQwait("TIGHT " + cmode, FBU.bytes)) { return false; } + + // We have everything, render it + //Util.Debug(" png, ws.rQlen(): " + ws.rQlen() + ", clength[0]: " + clength[0] + ", clength[1]: " + clength[1]); + ws.rQshiftBytes(1 + clength[0]); // shift off ctl + compact length + img = new Image(); + //img.onload = scan_tight_imgQ; + FBU.imgQ.push({ + 'type': 'img', + 'img': img, + 'x': FBU.x, + 'y': FBU.y}); + img.src = "data:image/" + cmode + + extract_data_uri(ws.rQshiftBytes(clength[1])); + img = null; + break; + } + FBU.bytes = 0; + FBU.rects -= 1; + //Util.Debug(" ending ws.rQslice(0,20): " + ws.rQslice(0,20) + " (" + ws.rQlen() + ")"); + //Util.Debug("<< display_tight_png"); + return true; +}; + +extract_data_uri = function(arr) { + //var i, stra = []; + //for (i=0; i< arr.length; i += 1) { + // stra.push(String.fromCharCode(arr[i])); + //} + //return "," + escape(stra.join('')); + return ";base64," + Base64.encode(arr); +}; + +scan_tight_imgQ = function() { + var data, imgQ, ctx; + ctx = display.get_context(); + if (rfb_state === 'normal') { + imgQ = FBU.imgQ; + while ((imgQ.length > 0) && (imgQ[0].img.complete)) { + data = imgQ.shift(); + if (data['type'] === 'fill') { + display.fillRect(data.x, data.y, data.width, data.height, data.color); + } else { + ctx.drawImage(data.img, data.x, data.y); + } + } + setTimeout(scan_tight_imgQ, scan_imgQ_rate); + } +}; + +encHandlers.DesktopSize = function set_desktopsize() { + Util.Debug(">> set_desktopsize"); + fb_width = FBU.width; + fb_height = FBU.height; + display.resize(fb_width, fb_height); + timing.fbu_rt_start = (new Date()).getTime(); + // Send a new non-incremental request + ws.send(fbUpdateRequests()); + + FBU.bytes = 0; + FBU.rects -= 1; + + Util.Debug("<< set_desktopsize"); + return true; +}; + +encHandlers.Cursor = function set_cursor() { + var x, y, w, h, pixelslength, masklength; + //Util.Debug(">> set_cursor"); + x = FBU.x; // hotspot-x + y = FBU.y; // hotspot-y + w = FBU.width; + h = FBU.height; + + pixelslength = w * h * fb_Bpp; + masklength = Math.floor((w + 7) / 8) * h; + + FBU.bytes = pixelslength + masklength; + if (ws.rQwait("cursor encoding", FBU.bytes)) { return false; } + + //Util.Debug(" set_cursor, x: " + x + ", y: " + y + ", w: " + w + ", h: " + h); + + display.changeCursor(ws.rQshiftBytes(pixelslength), + ws.rQshiftBytes(masklength), + x, y, w, h); + + FBU.bytes = 0; + FBU.rects -= 1; + + //Util.Debug("<< set_cursor"); + return true; +}; + +encHandlers.JPEG_quality_lo = function set_jpeg_quality() { + Util.Error("Server sent jpeg_quality pseudo-encoding"); +}; + +encHandlers.compress_lo = function set_compress_level() { + Util.Error("Server sent compress level pseudo-encoding"); +}; + +/* + * Client message routines + */ + +pixelFormat = function() { + //Util.Debug(">> pixelFormat"); + var arr; + arr = [0]; // msg-type + arr.push8(0); // padding + arr.push8(0); // padding + arr.push8(0); // padding + + arr.push8(fb_Bpp * 8); // bits-per-pixel + arr.push8(fb_depth * 8); // depth + arr.push8(0); // little-endian + arr.push8(conf.true_color ? 1 : 0); // true-color + + arr.push16(255); // red-max + arr.push16(255); // green-max + arr.push16(255); // blue-max + arr.push8(0); // red-shift + arr.push8(8); // green-shift + arr.push8(16); // blue-shift + + arr.push8(0); // padding + arr.push8(0); // padding + arr.push8(0); // padding + //Util.Debug("<< pixelFormat"); + return arr; +}; + +clientEncodings = function() { + //Util.Debug(">> clientEncodings"); + var arr, i, encList = []; + + for (i=0; i> fbUpdateRequest"); + if (typeof(x) === "undefined") { x = 0; } + if (typeof(y) === "undefined") { y = 0; } + if (typeof(xw) === "undefined") { xw = fb_width; } + if (typeof(yw) === "undefined") { yw = fb_height; } + var arr; + arr = [3]; // msg-type + arr.push8(incremental); + arr.push16(x); + arr.push16(y); + arr.push16(xw); + arr.push16(yw); + //Util.Debug("<< fbUpdateRequest"); + return arr; +}; + +// Based on clean/dirty areas, generate requests to send +fbUpdateRequests = function() { + var cleanDirty = display.getCleanDirtyReset(), + arr = [], i, cb, db; + + cb = cleanDirty.cleanBox; + if (cb.w > 0 && cb.h > 0) { + // Request incremental for clean box + arr = arr.concat(fbUpdateRequest(1, cb.x, cb.y, cb.w, cb.h)); + } + for (i = 0; i < cleanDirty.dirtyBoxes.length; i++) { + db = cleanDirty.dirtyBoxes[i]; + // Force all (non-incremental for dirty box + arr = arr.concat(fbUpdateRequest(0, db.x, db.y, db.w, db.h)); + } + return arr; +}; + + + +keyEvent = function(keysym, down) { + //Util.Debug(">> keyEvent, keysym: " + keysym + ", down: " + down); + var arr; + arr = [4]; // msg-type + arr.push8(down); + arr.push16(0); + arr.push32(keysym); + //Util.Debug("<< keyEvent"); + return arr; +}; + +pointerEvent = function(x, y) { + //Util.Debug(">> pointerEvent, x,y: " + x + "," + y + + // " , mask: " + mouse_buttonMask); + var arr; + arr = [5]; // msg-type + arr.push8(mouse_buttonMask); + arr.push16(x); + arr.push16(y); + //Util.Debug("<< pointerEvent"); + return arr; +}; + +clientCutText = function(text) { + //Util.Debug(">> clientCutText"); + var arr, i, n; + arr = [6]; // msg-type + arr.push8(0); // padding + arr.push8(0); // padding + arr.push8(0); // padding + arr.push32(text.length); + n = text.length; + for (i=0; i < n; i+=1) { + arr.push(text.charCodeAt(i)); + } + //Util.Debug("<< clientCutText:" + arr); + return arr; +}; + + + +// +// Public API interface functions +// + +that.connect = function(host, port, password, path) { + //Util.Debug(">> connect"); + + rfb_host = host; + rfb_port = port; + rfb_password = (password !== undefined) ? password : ""; + rfb_path = (path !== undefined) ? path : ""; + + if ((!rfb_host) || (!rfb_port)) { + return fail("Must set host and port"); + } + + updateState('connect'); + //Util.Debug("<< connect"); + +}; + +that.disconnect = function() { + //Util.Debug(">> disconnect"); + updateState('disconnect', 'Disconnecting'); + //Util.Debug("<< disconnect"); +}; + +that.sendPassword = function(passwd) { + rfb_password = passwd; + rfb_state = "Authentication"; + setTimeout(init_msg, 1); +}; + +that.sendCtrlAltDel = function() { + if (rfb_state !== "normal") { return false; } + Util.Info("Sending Ctrl-Alt-Del"); + var arr = []; + arr = arr.concat(keyEvent(0xFFE3, 1)); // Control + arr = arr.concat(keyEvent(0xFFE9, 1)); // Alt + arr = arr.concat(keyEvent(0xFFFF, 1)); // Delete + arr = arr.concat(keyEvent(0xFFFF, 0)); // Delete + arr = arr.concat(keyEvent(0xFFE9, 0)); // Alt + arr = arr.concat(keyEvent(0xFFE3, 0)); // Control + arr = arr.concat(fbUpdateRequests()); + ws.send(arr); +}; + +// Send a key press. If 'down' is not specified then send a down key +// followed by an up key. +that.sendKey = function(code, down) { + if (rfb_state !== "normal") { return false; } + var arr = []; + if (typeof down !== 'undefined') { + Util.Info("Sending key code (" + (down ? "down" : "up") + "): " + code); + arr = arr.concat(keyEvent(code, down ? 1 : 0)); + } else { + Util.Info("Sending key code (down + up): " + code); + arr = arr.concat(keyEvent(code, 1)); + arr = arr.concat(keyEvent(code, 0)); + } + arr = arr.concat(fbUpdateRequests()); + ws.send(arr); +}; + +that.clipboardPasteFrom = function(text) { + if (rfb_state !== "normal") { return; } + //Util.Debug(">> clipboardPasteFrom: " + text.substr(0,40) + "..."); + ws.send(clientCutText(text)); + //Util.Debug("<< clipboardPasteFrom"); +}; + +// Override internal functions for testing +that.testMode = function(override_send) { + test_mode = true; + that.recv_message = ws.testMode(override_send); + + checkEvents = function () { /* Stub Out */ }; + that.connect = function(host, port, password) { + rfb_host = host; + rfb_port = port; + rfb_password = password; + updateState('ProtocolVersion', "Starting VNC handshake"); + }; +}; + + +return constructor(); // Return the public API interface + +} // End of RFB() diff --git a/classes/novnc/include/ui.js b/classes/novnc/include/ui.js new file mode 100644 index 0000000..74a0005 --- /dev/null +++ b/classes/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/classes/novnc/include/util.js b/classes/novnc/include/util.js new file mode 100644 index 0000000..0a9e0e0 --- /dev/null +++ b/classes/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/classes/novnc/include/vnc.js b/classes/novnc/include/vnc.js new file mode 100644 index 0000000..f938be7 --- /dev/null +++ b/classes/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 = " + + + + + +
+ +
+ +
+ + + + + + +
+
+ + +
+ + + + + +
+ + +
+ +
+ +
+ + +
+ +
    +
  • Encrypt
  • +
  • True Color
  • +
  • Local Cursor
  • +
  • Clip to window
  • +
  • Shared Mode
  • +
  • Connect Timeout (s)
  • +
  • Path
  • +
    + +
  • +
  • + + +
  • +
  • +
    +
  • +
+
+
+ + +
+
    +
  • +
  • +
  • +
  • +
+
+ +
+ + +
+
+ +
+
Loading
+
+ +

no
VNC

+ + +
+ + Canvas not supported. + +
+ +
+ + + + diff --git a/classes/novnc/vnc_auto.html b/classes/novnc/vnc_auto.html new file mode 100644 index 0000000..a500b79 --- /dev/null +++ b/classes/novnc/vnc_auto.html @@ -0,0 +1,116 @@ + + + + + noVNC + + + + + + + +
+
+ + + +
Loading
+ +
+
+ + Canvas not supported. + +
+ + + + + + diff --git a/libvncserver/httpd.c b/libvncserver/httpd.c index ad2a51b..83fc520 100644 --- a/libvncserver/httpd.c +++ b/libvncserver/httpd.c @@ -346,12 +346,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, @@ -447,6 +441,10 @@ httpProcessInput(rfbScreenInfoPtr rfbScreen) sprintf(str, "%d", rfbScreen->port); rfbWriteExact(&cl, str, strlen(str)); + } else if (compareAndSkip(&ptr, "$HOST")) { + + rfbWriteExact(&cl, rfbScreen->thisHost, strlen(rfbScreen->thisHost)); + } else if (compareAndSkip(&ptr, "$DESKTOP")) { rfbWriteExact(&cl, rfbScreen->desktopName, strlen(rfbScreen->desktopName));