From 465a5b0bd55fe7153700b3ce1b9d33697d794904 Mon Sep 17 00:00:00 2001 From: Christian Beier Date: Thu, 2 Oct 2014 18:06:18 +0200 Subject: [PATCH] Update noVNC HTML5 client to latest version from https://github.com/kanaka/noVNC. --- webclients/novnc/LICENSE.txt | 30 +- webclients/novnc/README.md | 62 +- webclients/novnc/images/alt.png | Bin 0 -> 339 bytes webclients/novnc/images/ctrl.png | Bin 0 -> 354 bytes webclients/novnc/images/esc.png | Bin 0 -> 385 bytes webclients/novnc/images/power.png | Bin 0 -> 390 bytes webclients/novnc/images/showextrakeys.png | Bin 0 -> 735 bytes webclients/novnc/images/tab.png | Bin 0 -> 387 bytes webclients/novnc/include/base.css | 203 +- webclients/novnc/include/base64.js | 232 +- webclients/novnc/include/black.css | 25 +- webclients/novnc/include/blue.css | 37 +- .../novnc/include/chrome-app/tcp-client.js | 321 ++ webclients/novnc/include/des.js | 347 +- webclients/novnc/include/display.js | 1389 ++++--- webclients/novnc/include/input.js | 2217 ++--------- webclients/novnc/include/jsunzip.js | 26 +- webclients/novnc/include/keyboard.js | 543 +++ webclients/novnc/include/keysym.js | 376 ++ webclients/novnc/include/keysymdef.js | 15 + webclients/novnc/include/playback.js | 18 +- webclients/novnc/include/rfb.js | 3500 +++++++++-------- webclients/novnc/include/ui.js | 1590 +++++--- webclients/novnc/include/util.js | 636 ++- .../include/web-socket-js/WebSocketMain.swf | Bin 175746 -> 177139 bytes .../novnc/include/web-socket-js/web_socket.js | 114 +- webclients/novnc/include/websock.js | 622 +-- webclients/novnc/include/webutil.js | 181 +- webclients/novnc/vnc.html | 143 +- webclients/novnc/vnc_auto.html | 121 +- 30 files changed, 6797 insertions(+), 5951 deletions(-) create mode 100644 webclients/novnc/images/alt.png create mode 100644 webclients/novnc/images/ctrl.png create mode 100644 webclients/novnc/images/esc.png create mode 100644 webclients/novnc/images/power.png create mode 100644 webclients/novnc/images/showextrakeys.png create mode 100644 webclients/novnc/images/tab.png create mode 100644 webclients/novnc/include/chrome-app/tcp-client.js create mode 100644 webclients/novnc/include/keyboard.js create mode 100644 webclients/novnc/include/keysym.js create mode 100644 webclients/novnc/include/keysymdef.js diff --git a/webclients/novnc/LICENSE.txt b/webclients/novnc/LICENSE.txt index 67cdca5..2d09408 100644 --- a/webclients/novnc/LICENSE.txt +++ b/webclients/novnc/LICENSE.txt @@ -1,7 +1,7 @@ noVNC is Copyright (C) 2011 Joel Martin -The noVNC core library is licensed under the LGPLv3 (GNU Lesser -General Public License). The noVNC core library is composed of the +The noVNC core library files are licensed under the MPL 2.0 (Mozilla +Public License 2.0). The noVNC core library is composed of the Javascript code necessary for full noVNC operation. This includes (but is not limited to): @@ -10,6 +10,7 @@ is not limited to): include/display.js include/input.js include/jsunzip.js + include/keysym.js include/logo.js include/rfb.js include/ui.js @@ -36,21 +37,15 @@ The HTML, CSS, font and image files are licensed as follows: images/ : Creative Commons Attribution-ShareAlike http://creativecommons.org/licenses/by-sa/3.0/ -In addition the following file, which is part of the noVNC core -library, may be licensed under either the LGPL-2, LGPL-3 or MPL 2.0 -when it used separately from the noVNC core library. - - include/input.js : LGPL-2 or any later version - Some portions of noVNC are copyright to their individual authors. Please refer to the individual source files and/or to the noVNC commit history: https://github.com/kanaka/noVNC/commits/master The are several files and projects that have been incorporated into the noVNC core library. Here is a list of those files and the original -licenses (all LGPL-3 compatible): +licenses (all MPL 2.0 compatible): - include/base64.js : MPL 1.1, GPL-2 or LGPL-2.1 + include/base64.js : MPL 2.0 include/des.js : Various BSD style licenses @@ -59,20 +54,29 @@ licenses (all LGPL-3 compatible): include/web-socket-js/ : New BSD license (3-clause). Source code at http://github.com/gimite/web-socket-js + include/chrome-app/tcp-stream.js + : Apache 2.0 license + + utils/websockify + utils/websocket.py : LGPL 3 + The following license texts are included: + docs/LICENSE.MPL-2.0 docs/LICENSE.LGPL-3 and docs/LICENSE.GPL-3 docs/LICENSE.OFL-1.1 docs/LICENSE.BSD-3-Clause (New BSD) docs/LICENSE.BSD-2-Clause (Simplified BSD / FreeBSD) docs/LICENSE.zlib - docs/LICENSE.MPL-2.0 + docs/LICENSE.Apache-2.0 Or alternatively the license texts may be found here: + http://www.mozilla.org/MPL/2.0/ http://www.gnu.org/licenses/lgpl.html and http://www.gnu.org/licenses/gpl.html http://scripts.sil.org/OFL - http://www.mozilla.org/MPL/1.1/ - http://www.mozilla.org/MPL/2.0/ + http://en.wikipedia.org/wiki/BSD_licenses + http://www.gzip.org/zlib/zlib_license.html + http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/webclients/novnc/README.md b/webclients/novnc/README.md index 9c14e7d..b5679cd 100644 --- a/webclients/novnc/README.md +++ b/webclients/novnc/README.md @@ -1,16 +1,45 @@ ## noVNC: HTML5 VNC Client +[![Build Status](https://travis-ci.org/kanaka/noVNC.svg?branch=master)](https://travis-ci.org/kanaka/noVNC) ### Description noVNC is a HTML5 VNC client that runs well in any modern browser including mobile browsers (iPhone/iPad and Android). +Many companies/projects have integrated noVNC including [Ganeti Web +Manager](http://code.osuosl.org/projects/ganeti-webmgr), +[OpenStack](http://www.openstack.org), +[OpenNebula](http://opennebula.org/), and +[LibVNCServer](http://libvncserver.sourceforge.net). See [the Projects +and Companies wiki +page](https://github.com/kanaka/noVNC/wiki/ProjectsCompanies-using-noVNC) +for a more complete list with additional info and links. + +### News/help/contact + Notable commits, announcements and news are posted to -@noVNC +@noVNC + +If you are a noVNC developer/integrator/user (or want to be) please +join the noVNC +discussion group + +Bugs and feature requests can be submitted via [github +issues](https://github.com/kanaka/noVNC/issues). If you are looking +for a place to start contributing to noVNC, a good place to start +would be the issues that are marked as +["patchwelcome"](https://github.com/kanaka/noVNC/issues?labels=patchwelcome). -There are many companies/projects that have integrated noVNC into -their products including: [Ganeti Web Manager](http://code.osuosl.org/projects/ganeti-webmgr), [Archipel](http://archipelproject.org), [openQRM](http://www.openqrm.com/), [OpenNode](http://www.opennodecloud.com/), [OpenStack](http://www.openstack.org), [Broadway (HTML5 GDK/GTK+ backend)](http://blogs.gnome.org/alexl/2011/03/15/gtk-html-backend-update/), [OpenNebula](http://opennebula.org/), [CloudSigma](http://www.cloudsigma.com/), [Zentyal (formerly eBox)](http://www.zentyal.org/), [SlapOS](http://www.slapos.org), [Intel MeshCentral](https://meshcentral.com), [Amahi](http://amahi.org), [Brightbox](http://brightbox.com/), [Foreman](http://theforeman.org), [LibVNCServer](http://libvnc.github.io/) and [PocketVNC](http://www.pocketvnc.com/blog/?page_id=866). See [this wiki page](https://github.com/kanaka/noVNC/wiki/ProjectsCompanies-using-noVNC) for more info and links. +If you want to show appreciation for noVNC you could donate to a great +non-profits such as: [Compassion +International](http://www.compassion.com/), [SIL](http://www.sil.org), +[Habitat for Humanity](http://www.habitat.org), [Electronic Frontier +Foundation](https://www.eff.org/), [Against Malaria +Foundation](http://www.againstmalaria.com/), [Nothing But +Nets](http://www.nothingbutnets.net/), etc. Please tweet @noVNC if you do. ### Features @@ -24,7 +53,7 @@ their products including: [Ganeti Web Manager](http://code.osuosl.org/projects/g * Clipboard copy/paste * Clipping or scolling modes for large remote screens * Easy site integration and theming (3 example themes included) -* Licensed under the [LGPLv3](http://www.gnu.org/licenses/lgpl.html) +* Licensed under the [MPL 2.0](http://www.mozilla.org/MPL/2.0/) ### Screenshots @@ -49,17 +78,18 @@ See more screenshots h * Fast Javascript Engine: this is not strictly a requirement, but without a fast Javascript engine, noVNC might be painfully slow. -* I maintain a more detailed browser compatibility list here. +* See the more detailed [browser compatibility wiki page](https://github.com/kanaka/noVNC/wiki/Browser-support). ### Server Requirements Unless you are using a VNC server with support for WebSockets -connections (such as [x11vnc/libvncserver](http://libvnc.github.io/) or -[PocketVNC](http://www.pocketvnc.com/blog/?page_id=866)), -you need to use a WebSockets to TCP socket proxy. There is -a python proxy included ('websockify'). +connections (such as +[x11vnc/libvncserver](http://libvncserver.sourceforge.net/), +[QEMU](http://www.qemu.org/), or +[PocketVNC](http://www.pocketvnc.com/blog/?page_id=866)), you need to +use a WebSockets to TCP socket proxy. There is a python proxy included +('websockify'). ### Quick Start @@ -88,8 +118,14 @@ a python proxy included ('websockify'). ### Authors/Contributors -* noVNC : Joel Martin (github.com/kanaka) - * New UI and Icons : Chris Gordon +* Core team: + * [Joel Martin](https://github.com/kanaka) + * [Samuel Mannehed](https://github.com/samhed) (Cendio) + * [Peter Åstrand](https://github.com/astrand) (Cendio) + * [Solly Ross](https://github.com/DirectXMan12) (Red Hat / OpenStack) + +* Notable contributions: + * UI and Icons : Chris Gordon * Original Logo : Michael Sersen * tight encoding : Michael Tinglof (Mercuri.ca) @@ -100,5 +136,3 @@ a python proxy included ('websockify'). * jsunzip : Erik Moller (github.com/operasoftware/jsunzip), * tinflate : Joergen Ibsen (ibsensoftware.com) * DES : Dave Zimmerman (Widget Workshop), Jef Poskanzer (ACME Labs) - - diff --git a/webclients/novnc/images/alt.png b/webclients/novnc/images/alt.png new file mode 100644 index 0000000000000000000000000000000000000000..d42af7b421b9f1193620cb4a7114f1df27886d1c GIT binary patch literal 339 zcmeAS@N?(olHy`uVBq!ia0vp^k|4~%1|*NXY)uAIY)RhkE)4%caKYZ?lYt_f1s;*b z3=G`DAk4@xYmNj^kiEpy*OmP)y9AGfn3c|@SfJ1YPZ!4!kK=DA?d4@Mvg-UsUjQv!4@3E(23Sx$<4+2dh>^&G&f67BAwO*^{~A)gQHlw4ThU?9=P7U#iY$ zoU@gULH+&Szpr+?q{tjL>$J>$WYr(E@cJVsj=AZ~_m3?|T-1=LlQ4TyhX?0_FWA34~+4u?EKa`a+$6maEEblBXv zqublI^Z5ssChmi=1rK>`n#>Cpc5LscXyxJQ&70LIB0IC{X&RH3tZ?$3uhC)uAAQ%~ z=dm#0#6w#pC2J)m-^*`i-jt|dOLJh#dSJNyOLkI^a{|+uvu}bz`JFkM*S+>@X!rHq z_o7j{y!=7c)kEp~_RqX&X~247rqA8G!CuF+|LYjD=OyU#_5bp}sYKbLh*2~7a?){X-J literal 0 HcmV?d00001 diff --git a/webclients/novnc/images/esc.png b/webclients/novnc/images/esc.png new file mode 100644 index 0000000000000000000000000000000000000000..ece5f7cbef684a41f4a7bb9d632e4cb2310d6b5d GIT binary patch literal 385 zcmeAS@N?(olHy`uVBq!ia0vp^k|4~%1|*NXY)uAIY)RhkE)4%caKYZ?lYt_f1s;*b z3=G`DAk4@xYmNj^kiEpy*OmP)y9AG<$XUxK0|o|0K2I0N5Rc<;r)=~(;vmpwt~763 z(rf03YMC%Gv!>p;`x=s-3jg3Sv+J08Hk7^Wk+j5=)(kI8VZ)Z0e~+b2TwvK-mAbF2 z{@d)ElGk?5n>TO1$DHDGXL8aMJSYA45_?{oD9s()th9+WhB^2Fuf*zCdB(y{77Tew z&kS1%{uE|Re6Mm~@64I2-aW6}r=)cIK&W#;RvKH+>Qzztr;B)Z{7}kVtXRpGB`8_- zo?X|~X2#5`rF$<%rB3&A;!r=3y(X+NI`nR)$B{ESr#C&d_Xb*BZ7(n}k>jDxsd>+C zaTaJQEjIC;?vf&+TE00gNaUzi!N1irSGJ>(2+?q>Rf`4Fzff;%=K%nq(i6F}{53M#?;U zLBZzq!>RXNprLZ*hUsB!Gewso}{}^ zyS5zqsqt+q$3eS=bG3syL}aJxswlO({ZMz^<8@I+aHiGz+=bUm8NM^#j@;<7{?FO# zrb}1(?6I-+nz%_ndc~vpE9T2SE8eKOh+S~6fqC~wzI*9n-{OAGRx&S`su`qNAN+(V z&&XBkX?|VBYQH4q-F|DjwzF)~vb(lB%jUA~j<{R9onkH8Tw^2Zl+T|DXQ^pge?vdW h@Q(gJk3Tz4NXMlvaGP>8%>o!=44$rjF6*2UngBV_o1g#y literal 0 HcmV?d00001 diff --git a/webclients/novnc/images/showextrakeys.png b/webclients/novnc/images/showextrakeys.png new file mode 100644 index 0000000000000000000000000000000000000000..ad8e0a70d3eb7b5f4462f808ab987053fa4b0e56 GIT binary patch literal 735 zcmV<50wDc~P)P000>X1^@s6#OZ}&00006VoOIv0RI60 z0RN!9r;`8x010qNS#tmY3ljhU3ljkVnw%H_000McNliru-3J*792=g7&T0Sv0&Gb{ zK~zY`y;U)56hRdJ-ppi!CtQjM#~p&_LBP(=X7C5tSO^h>^eIGa36M2V8-Xh|B|c z0YCsW^8Y^p1OTrA>=4nxq$KMA7Do8L0eDPA`$bOYTou6Q5WwB?-B01PwB;o+fp z@6A9JPGh|HW^;3MYIb&(lu~2>JOprWOfs+X(zO7*G{&IWY)S}WI)qTwS_1&I)`+60 zs=k~97!Nf5{J2{SKSYZsk! zq|T{@Q;fA%^7^l4S+>${x24zX0RVcv9@_1;WLdVNwO;Mx$$S4qYyEJ{)3QZGpGKVQ zol@%Q+}s@P?d=IOf3Vg*D|!~HBfiQIZhFw~y}v0UHRs&Y_V#w_oLlObbksbl7bJR= zW!a-Biry2^oe}TWL6&6?c6WEL1K79Lh9b~<$*uY=B349xlweX3xmDIsHAW{M0OxBN zM#hm`x?LGbnNFs>_XJ?74D&DZq%~Aq4WMI;iJAF}QtEy|Z#01TB4*CS_6MPNtqWrX RMSuVR002ovPDHLkV1j-yMWX-! literal 0 HcmV?d00001 diff --git a/webclients/novnc/images/tab.png b/webclients/novnc/images/tab.png new file mode 100644 index 0000000000000000000000000000000000000000..84134872a881a627f745a432a3c951254cda26cb GIT binary patch literal 387 zcmeAS@N?(olHy`uVBq!ia0vp^k|4~%1|*NXY)uAIY)RhkE)4%caKYZ?lYt_f1s;*b z3=G`DAk4@xYmNj^kiEpy*OmP)y9AGr(VqkNfCexMc)B=-cpQH_#nA7tgMe!?hZ5_{ zMsAl!Od$nK=NL4coJ!_2I4fwan^WPez236Q zxV~)Pmbreb!~RE$eG$IAmU)pz*R|Ls(Q=zi{&XKq&Aq}nY2Dc~42<~)YG*C`o4+Gi zfzhrp-BY~xz4ptrjS(^y&#NZ29sIEF7IV+@&p+#?rS3j(MVx!X=$#3b5uMxna)02 zw(86+ea1O6Bj-JL&z$wG@ph=1;^e&(PHLnW`jei;9RARTq fhprAg`;}G9rs2;o<>%Ld!NuU|>gTe~DWM4f3=5y^ literal 0 HcmV?d00001 diff --git a/webclients/novnc/include/base.css b/webclients/novnc/include/base.css index 3a2feb3..e2c9a96 100644 --- a/webclients/novnc/include/base.css +++ b/webclients/novnc/include/base.css @@ -1,7 +1,8 @@ /* * noVNC base CSS * Copyright (C) 2012 Joel Martin - * noVNC is licensed under the LGPL-3 (see LICENSE.txt) + * Copyright (C) 2013 Samuel Mannehed for Cendio AB + * noVNC is licensed under the MPL 2.0 (see LICENSE.txt) * This file is licensed under the 2-Clause BSD license (see LICENSE.txt). */ @@ -40,9 +41,6 @@ html { } #noVNC_encrypt { } -#noVNC_connectTimeout { - width: 30px; -} #noVNC_path { width: 100px; } @@ -51,6 +49,9 @@ html { float:right; } +#noVNC_buttons { + white-space: nowrap; +} #noVNC_view_drag_button { display: none; @@ -58,38 +59,43 @@ html { #sendCtrlAltDelButton { display: none; } +#noVNC_xvp_buttons { + display: none; +} #noVNC_mobile_buttons { display: none; } +#noVNC_extra_keys { + display: inline; + list-style-type: none; + padding: 0px; + margin: 0px; + position: relative; +} + .noVNC-buttons-left { float: left; - padding-left:10px; - padding-top:4px; + z-index: 1; + position: relative; } .noVNC-buttons-right { float:right; right: 0px; - padding-right:10px; - padding-top:4px; + z-index: 2; + position: absolute; } -#noVNC_status_bar { - margin-top: 0px; - padding: 0px; -} - -#noVNC_status_bar div { +#noVNC_status { font-size: 12px; padding-top: 4px; - width:100%; -} - -#noVNC_status { - height:20px; + height:32px; text-align: center; + font-weight: bold; + color: #fff; } + #noVNC_settings_menu { margin: 3px; text-align: left; @@ -104,22 +110,12 @@ html { float:right; } -.noVNC_status_normal { - background: #eee; -} -.noVNC_status_error { - background: #f44; -} -.noVNC_status_warn { - background: #ff4; -} - /* Do not set width/height for VNC_screen or VNC_canvas or incorrect * scaling will occur. Canvas resizes to remote VNC settings */ #noVNC_screen_pad { margin: 0px; padding: 0px; - height: 44px; + height: 36px; } #noVNC_screen { text-align: center; @@ -154,14 +150,14 @@ html { /*Bubble contents divs*/ #noVNC_settings { display:none; - margin-top:77px; + margin-top:73px; right:20px; position:fixed; } #noVNC_controls { display:none; - margin-top:77px; + margin-top:73px; right:12px; position:fixed; } @@ -173,7 +169,7 @@ html { display:none; position:fixed; - margin-top:77px; + margin-top:73px; right:20px; left:20px; padding:15px; @@ -186,9 +182,40 @@ html { border-radius:10px; } +#noVNC_popup_status_panel { + display:none; + position: fixed; + z-index: 1; + + margin:15px; + margin-top:60px; + padding:15px; + width:auto; + + text-align:center; + font-weight:bold; + word-wrap:break-word; + color:#fff; + background:rgba(0,0,0,0.65); + + -webkit-border-radius:10px; + -moz-border-radius:10px; + border-radius:10px; +} + +#noVNC_xvp { + display:none; + margin-top:73px; + right:30px; + position:fixed; +} +#noVNC_xvp.top:after { + right:125px; +} + #noVNC_clipboard { display:none; - margin-top:77px; + margin-top:73px; right:30px; position:fixed; } @@ -207,17 +234,11 @@ html { z-index: -1; } -.noVNC_status_warn { - background-color:yellow; -} - /* * Advanced Styling */ -/* Control bar */ -#noVNC-control-bar { - position:fixed; +.noVNC_status_normal { background: #b2bdcd; /* Old browsers */ background: -moz-linear-gradient(top, #b2bdcd 0%, #899cb3 49%, #7e93af 51%, #6e84a3 100%); /* FF3.6+ */ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#b2bdcd), color-stop(49%,#899cb3), color-stop(51%,#7e93af), color-stop(100%,#6e84a3)); /* Chrome,Safari4+ */ @@ -225,9 +246,32 @@ html { background: -o-linear-gradient(top, #b2bdcd 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* Opera11.10+ */ background: -ms-linear-gradient(top, #b2bdcd 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* IE10+ */ background: linear-gradient(top, #b2bdcd 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* W3C */ +} +.noVNC_status_error { + background: #f04040; /* Old browsers */ + background: -moz-linear-gradient(top, #f04040 0%, #899cb3 49%, #7e93af 51%, #6e84a3 100%); /* FF3.6+ */ + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#f04040), color-stop(49%,#899cb3), color-stop(51%,#7e93af), color-stop(100%,#6e84a3)); /* Chrome,Safari4+ */ + background: -webkit-linear-gradient(top, #f04040 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* Chrome10+,Safari5.1+ */ + background: -o-linear-gradient(top, #f04040 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* Opera11.10+ */ + background: -ms-linear-gradient(top, #f04040 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* IE10+ */ + background: linear-gradient(top, #f04040 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* W3C */ +} +.noVNC_status_warn { + background: #f0f040; /* Old browsers */ + background: -moz-linear-gradient(top, #f0f040 0%, #899cb3 49%, #7e93af 51%, #6e84a3 100%); /* FF3.6+ */ + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#f0f040), color-stop(49%,#899cb3), color-stop(51%,#7e93af), color-stop(100%,#6e84a3)); /* Chrome,Safari4+ */ + background: -webkit-linear-gradient(top, #f0f040 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* Chrome10+,Safari5.1+ */ + background: -o-linear-gradient(top, #f0f040 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* Opera11.10+ */ + background: -ms-linear-gradient(top, #f0f040 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* IE10+ */ + background: linear-gradient(top, #f0f040 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* W3C */ +} +/* Control bar */ +#noVNC-control-bar { + position:fixed; + display:block; - height:44px; + height:36px; left:0; top:0; width:100%; @@ -368,22 +412,85 @@ html { font-size: 180px; } -@media screen and (min-width: 481px) and (max-width: 640px) { - .noVNC_status_button { - font-size: 10px; +.noVNC-buttons-left { + padding-left: 10px; +} + +.noVNC-buttons-right { + padding-right: 10px; +} + +#noVNC_status { + z-index: 0; + position: absolute; + width: 100%; + margin-left: 0px; +} + +#showExtraKeysButton { display: none; } +#toggleCtrlButton { display: inline; } +#toggleAltButton { display: inline; } +#sendTabButton { display: inline; } +#sendEscButton { display: inline; } + +/* left-align the status text on lower resolutions */ +@media screen and (max-width: 800px){ + #noVNC_status { + z-index: 1; + position: relative; + width: auto; + float: left; + margin-left: 4px; } +} + +@media screen and (max-width: 640px){ #noVNC_clipboard_text { width: 410px; } #noVNC_logo { font-size: 150px; } -} - -@media screen and (min-width: 321px) and (max-width: 480px) { .noVNC_status_button { font-size: 10px; } + .noVNC-buttons-left { + padding-left: 0px; + } + .noVNC-buttons-right { + padding-right: 0px; + } + /* collapse the extra keys on lower resolutions */ + #showExtraKeysButton { + display: inline; + } + #toggleCtrlButton { + display: none; + position: absolute; + top: 30px; + left: 0px; + } + #toggleAltButton { + display: none; + position: absolute; + top: 65px; + left: 0px; + } + #sendTabButton { + display: none; + position: absolute; + top: 100px; + left: 0px; + } + #sendEscButton { + display: none; + position: absolute; + top: 135px; + left: 0px; + } +} + +@media screen and (min-width: 321px) and (max-width: 480px) { #noVNC_clipboard_text { width: 250px; } diff --git a/webclients/novnc/include/base64.js b/webclients/novnc/include/base64.js index e9b3c52..651fbad 100644 --- a/webclients/novnc/include/base64.js +++ b/webclients/novnc/include/base64.js @@ -1,147 +1,113 @@ -/* - * Modified from: - * http://lxr.mozilla.org/mozilla/source/extensions/xml-rpc/src/nsXmlRpcClient.js#956 - */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is Mozilla XML-RPC Client component. - * - * The Initial Developer of the Original Code is - * Digital Creations 2, Inc. - * Portions created by the Initial Developer are Copyright (C) 2000 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * Martijn Pieters (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 ***** */ +// From: http://hg.mozilla.org/mozilla-central/raw-file/ec10630b1a54/js/src/devtools/jint/sunspider/string-base64.js -/*jslint white: false, bitwise: false, plusplus: false */ +/*jslint white: false */ /*global console */ var Base64 = { - -/* Convert data (an array of integers) to a Base64 string. */ -toBase64Table : 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/', -base64Pad : '=', - -encode: function (data) { - "use strict"; - var result = '', - chrTable = Base64.toBase64Table.split(''), - pad = Base64.base64Pad, - length = data.length, - i; - // Convert every three bytes to 4 ascii characters. - for (i = 0; i < (length - 2); i += 3) { - result += chrTable[data[i] >> 2]; - result += chrTable[((data[i] & 0x03) << 4) + (data[i+1] >> 4)]; - result += chrTable[((data[i+1] & 0x0f) << 2) + (data[i+2] >> 6)]; - result += chrTable[data[i+2] & 0x3f]; - } - - // Convert the remaining 1 or 2 bytes, pad out to 4 characters. - if (length%3) { - i = length - (length%3); - result += chrTable[data[i] >> 2]; - if ((length%3) === 2) { - result += chrTable[((data[i] & 0x03) << 4) + (data[i+1] >> 4)]; - result += chrTable[(data[i+1] & 0x0f) << 2]; - result += pad; - } else { - result += chrTable[(data[i] & 0x03) << 4]; - result += pad + pad; + /* Convert data (an array of integers) to a Base64 string. */ + toBase64Table : 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='.split(''), + base64Pad : '=', + + encode: function (data) { + "use strict"; + var result = ''; + var toBase64Table = Base64.toBase64Table; + var length = data.length; + var lengthpad = (length % 3); + // Convert every three bytes to 4 ascii characters. + + for (var i = 0; i < (length - 2); i += 3) { + result += toBase64Table[data[i] >> 2]; + result += toBase64Table[((data[i] & 0x03) << 4) + (data[i + 1] >> 4)]; + result += toBase64Table[((data[i + 1] & 0x0f) << 2) + (data[i + 2] >> 6)]; + result += toBase64Table[data[i + 2] & 0x3f]; } - } - - return result; -}, - -/* Convert Base64 data to a string */ -toBinaryTable : [ - -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, - -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, - -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,62, -1,-1,-1,63, - 52,53,54,55, 56,57,58,59, 60,61,-1,-1, -1, 0,-1,-1, - -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14, - 15,16,17,18, 19,20,21,22, 23,24,25,-1, -1,-1,-1,-1, - -1,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40, - 41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1 -], - -decode: function (data, offset) { - "use strict"; - offset = typeof(offset) !== 'undefined' ? offset : 0; - var binTable = Base64.toBinaryTable, - pad = Base64.base64Pad, - result, result_length, idx, i, c, padding, - leftbits = 0, // number of bits decoded, but yet to be appended - leftdata = 0, // bits decoded, but yet to be appended - data_length = data.indexOf('=') - offset; - if (data_length < 0) { data_length = data.length - offset; } - - /* Every four characters is 3 resulting numbers */ - result_length = (data_length >> 2) * 3 + Math.floor((data_length%4)/1.5); - result = new Array(result_length); - - // Convert one by one. - for (idx = 0, i = offset; i < data.length; i++) { - c = binTable[data.charCodeAt(i) & 0x7f]; - padding = (data.charAt(i) === pad); - // Skip illegal characters and whitespace - if (c === -1) { - console.error("Illegal character code " + data.charCodeAt(i) + " at position " + i); - continue; + // Convert the remaining 1 or 2 bytes, pad out to 4 characters. + var j = 0; + if (lengthpad === 2) { + j = length - lengthpad; + result += toBase64Table[data[j] >> 2]; + result += toBase64Table[((data[j] & 0x03) << 4) + (data[j + 1] >> 4)]; + result += toBase64Table[(data[j + 1] & 0x0f) << 2]; + result += toBase64Table[64]; + } else if (lengthpad === 1) { + j = length - lengthpad; + result += toBase64Table[data[j] >> 2]; + result += toBase64Table[(data[j] & 0x03) << 4]; + result += toBase64Table[64]; + result += toBase64Table[64]; } - - // Collect data into leftdata, update bitcount - leftdata = (leftdata << 6) | c; - leftbits += 6; - // If we have 8 or more bits, append 8 bits to the result - if (leftbits >= 8) { - leftbits -= 8; - // Append if not padding. - if (!padding) { - result[idx++] = (leftdata >> leftbits) & 0xff; + return result; + }, + + /* Convert Base64 data to a string */ + /* jshint -W013 */ + toBinaryTable : [ + -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, + -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, + -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,62, -1,-1,-1,63, + 52,53,54,55, 56,57,58,59, 60,61,-1,-1, -1, 0,-1,-1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14, + 15,16,17,18, 19,20,21,22, 23,24,25,-1, -1,-1,-1,-1, + -1,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40, + 41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1 + ], + /* jshint +W013 */ + + decode: function (data, offset) { + "use strict"; + offset = typeof(offset) !== 'undefined' ? offset : 0; + var toBinaryTable = Base64.toBinaryTable; + var base64Pad = Base64.base64Pad; + var result, result_length; + var leftbits = 0; // number of bits decoded, but yet to be appended + var leftdata = 0; // bits decoded, but yet to be appended + var data_length = data.indexOf('=') - offset; + + if (data_length < 0) { data_length = data.length - offset; } + + /* Every four characters is 3 resulting numbers */ + result_length = (data_length >> 2) * 3 + Math.floor((data_length % 4) / 1.5); + result = new Array(result_length); + + // Convert one by one. + for (var idx = 0, i = offset; i < data.length; i++) { + var c = toBinaryTable[data.charCodeAt(i) & 0x7f]; + var padding = (data.charAt(i) === base64Pad); + // Skip illegal characters and whitespace + if (c === -1) { + console.error("Illegal character code " + data.charCodeAt(i) + " at position " + i); + continue; + } + + // Collect data into leftdata, update bitcount + leftdata = (leftdata << 6) | c; + leftbits += 6; + + // If we have 8 or more bits, append 8 bits to the result + if (leftbits >= 8) { + leftbits -= 8; + // Append if not padding. + if (!padding) { + result[idx++] = (leftdata >> leftbits) & 0xff; + } + leftdata &= (1 << leftbits) - 1; } - leftdata &= (1 << leftbits) - 1; } - } - // If there are any bits left, the base64 string was corrupted - if (leftbits) { - throw {name: 'Base64-Error', - message: 'Corrupted base64 string'}; - } - - return result; -} + // If there are any bits left, the base64 string was corrupted + if (leftbits) { + err = new Error('Corrupted base64 string'); + err.name = 'Base64-Error'; + throw err; + } + return result; + } }; /* End of Base64 namespace */ diff --git a/webclients/novnc/include/black.css b/webclients/novnc/include/black.css index e958ee3..7d940c5 100644 --- a/webclients/novnc/include/black.css +++ b/webclients/novnc/include/black.css @@ -1,7 +1,8 @@ /* - * noVNC base CSS + * noVNC black CSS * Copyright (C) 2012 Joel Martin - * noVNC is licensed under the LGPL-3 (see LICENSE.txt) + * Copyright (C) 2013 Samuel Mannehed for Cendio AB + * noVNC is licensed under the MPL 2.0 (see LICENSE.txt) * This file is licensed under the 2-Clause BSD license (see LICENSE.txt). */ @@ -9,7 +10,7 @@ background-color:#000; } -#noVNC-control-bar { +.noVNC_status_normal { background: #4c4c4c; /* Old browsers */ background: -moz-linear-gradient(top, #4c4c4c 0%, #2c2c2c 50%, #000000 51%, #131313 100%); /* FF3.6+ */ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#4c4c4c), color-stop(50%,#2c2c2c), color-stop(51%,#000000), color-stop(100%,#131313)); /* Chrome,Safari4+ */ @@ -18,6 +19,24 @@ background: -ms-linear-gradient(top, #4c4c4c 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* IE10+ */ background: linear-gradient(top, #4c4c4c 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* W3C */ } +.noVNC_status_error { + background: #f04040; /* Old browsers */ + background: -moz-linear-gradient(top, #f04040 0%, #2c2c2c 50%, #000000 51%, #131313 100%); /* FF3.6+ */ + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#f04040), color-stop(50%,#2c2c2c), color-stop(51%,#000000), color-stop(100%,#131313)); /* Chrome,Safari4+ */ + background: -webkit-linear-gradient(top, #f04040 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* Chrome10+,Safari5.1+ */ + background: -o-linear-gradient(top, #f04040 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* Opera11.10+ */ + background: -ms-linear-gradient(top, #f04040 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* IE10+ */ + background: linear-gradient(top, #f04040 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* W3C */ +} +.noVNC_status_warn { + background: #f0f040; /* Old browsers */ + background: -moz-linear-gradient(top, #f0f040 0%, #2c2c2c 50%, #000000 51%, #131313 100%); /* FF3.6+ */ + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#f0f040), color-stop(50%,#2c2c2c), color-stop(51%,#000000), color-stop(100%,#131313)); /* Chrome,Safari4+ */ + background: -webkit-linear-gradient(top, #f0f040 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* Chrome10+,Safari5.1+ */ + background: -o-linear-gradient(top, #f0f040 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* Opera11.10+ */ + background: -ms-linear-gradient(top, #f0f040 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* IE10+ */ + background: linear-gradient(top, #f0f040 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* W3C */ +} .triangle-right { border:2px solid #fff; diff --git a/webclients/novnc/include/blue.css b/webclients/novnc/include/blue.css index 3dad0b4..b2a0adc 100644 --- a/webclients/novnc/include/blue.css +++ b/webclients/novnc/include/blue.css @@ -1,11 +1,12 @@ /* - * noVNC base CSS + * noVNC blue CSS * Copyright (C) 2012 Joel Martin - * noVNC is licensed under the LGPL-3 (see LICENSE.txt) + * Copyright (C) 2013 Samuel Mannehed for Cendio AB + * noVNC is licensed under the MPL 2.0 (see LICENSE.txt) * This file is licensed under the 2-Clause BSD license (see LICENSE.txt). */ -#noVNC-control-bar { +.noVNC_status_normal { background-color:#04073d; background-image: -webkit-gradient( linear, @@ -20,6 +21,36 @@ rgb(4,7,61) 50% ); } +.noVNC_status_error { + background-color:#f04040; + background-image: -webkit-gradient( + linear, + left bottom, + left top, + color-stop(0.54, rgb(240,64,64)), + color-stop(0.5, rgb(4,7,61)) + ); + background-image: -moz-linear-gradient( + center bottom, + rgb(4,7,61) 54%, + rgb(249,64,64) 50% + ); +} +.noVNC_status_warn { + background-color:#f0f040; + background-image: -webkit-gradient( + linear, + left bottom, + left top, + color-stop(0.54, rgb(240,240,64)), + color-stop(0.5, rgb(4,7,61)) + ); + background-image: -moz-linear-gradient( + center bottom, + rgb(4,7,61) 54%, + rgb(240,240,64) 50% + ); +} .triangle-right { border:2px solid #fff; diff --git a/webclients/novnc/include/chrome-app/tcp-client.js b/webclients/novnc/include/chrome-app/tcp-client.js new file mode 100644 index 0000000..b8c125f --- /dev/null +++ b/webclients/novnc/include/chrome-app/tcp-client.js @@ -0,0 +1,321 @@ +/* +Copyright 2012 Google Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Author: Boris Smus (smus@chromium.org) +*/ + +(function(exports) { + + // Define some local variables here. + var socket = chrome.socket || chrome.experimental.socket; + var dns = chrome.experimental.dns; + + /** + * Creates an instance of the client + * + * @param {String} host The remote host to connect to + * @param {Number} port The port to connect to at the remote host + */ + function TcpClient(host, port, pollInterval) { + this.host = host; + this.port = port; + this.pollInterval = pollInterval || 15; + + // Callback functions. + this.callbacks = { + connect: null, // Called when socket is connected. + disconnect: null, // Called when socket is disconnected. + recvBuffer: null, // Called (as ArrayBuffer) when client receives data from server. + recvString: null, // Called (as string) when client receives data from server. + sent: null // Called when client sends data to server. + }; + + // Socket. + this.socketId = null; + this.isConnected = false; + + log('initialized tcp client'); + } + + /** + * Connects to the TCP socket, and creates an open socket. + * + * @see http://developer.chrome.com/trunk/apps/socket.html#method-create + * @param {Function} callback The function to call on connection + */ + TcpClient.prototype.connect = function(callback) { + // First resolve the hostname to an IP. + dns.resolve(this.host, function(result) { + this.addr = result.address; + socket.create('tcp', {}, this._onCreate.bind(this)); + + // Register connect callback. + this.callbacks.connect = callback; + }.bind(this)); + }; + + /** + * Sends an arraybuffer/view down the wire to the remote side + * + * @see http://developer.chrome.com/trunk/apps/socket.html#method-write + * @param {String} msg The arraybuffer/view to send + * @param {Function} callback The function to call when the message has sent + */ + TcpClient.prototype.sendBuffer = function(buf, callback) { + if (buf.buffer) { + buf = buf.buffer; + } + + /* + // Debug + var bytes = [], u8 = new Uint8Array(buf); + for (var i = 0; i < u8.length; i++) { + bytes.push(u8[i]); + } + log("sending bytes: " + (bytes.join(','))); + */ + + socket.write(this.socketId, buf, this._onWriteComplete.bind(this)); + + // Register sent callback. + this.callbacks.sent = callback; + }; + + /** + * Sends a string down the wire to the remote side + * + * @see http://developer.chrome.com/trunk/apps/socket.html#method-write + * @param {String} msg The string to send + * @param {Function} callback The function to call when the message has sent + */ + TcpClient.prototype.sendString = function(msg, callback) { + /* + // Debug + log("sending string: " + msg); + */ + + this._stringToArrayBuffer(msg, function(arrayBuffer) { + socket.write(this.socketId, arrayBuffer, this._onWriteComplete.bind(this)); + }.bind(this)); + + // Register sent callback. + this.callbacks.sent = callback; + }; + + /** + * Sets the callback for when a message is received + * + * @param {Function} callback The function to call when a message has arrived + * @param {String} type The callback argument type: "arraybuffer" or "string" + */ + TcpClient.prototype.addResponseListener = function(callback, type) { + if (typeof type === "undefined") { + type = "arraybuffer"; + } + // Register received callback. + if (type === "string") { + this.callbacks.recvString = callback; + } else { + this.callbacks.recvBuffer = callback; + } + }; + + /** + * Sets the callback for when the socket disconnects + * + * @param {Function} callback The function to call when the socket disconnects + * @param {String} type The callback argument type: "arraybuffer" or "string" + */ + TcpClient.prototype.addDisconnectListener = function(callback) { + // Register disconnect callback. + this.callbacks.disconnect = callback; + }; + + /** + * Disconnects from the remote side + * + * @see http://developer.chrome.com/trunk/apps/socket.html#method-disconnect + */ + TcpClient.prototype.disconnect = function() { + if (this.isConnected) { + this.isConnected = false; + socket.disconnect(this.socketId); + if (this.callbacks.disconnect) { + this.callbacks.disconnect(); + } + log('socket disconnected'); + } + }; + + /** + * The callback function used for when we attempt to have Chrome + * create a socket. If the socket is successfully created + * we go ahead and connect to the remote side. + * + * @private + * @see http://developer.chrome.com/trunk/apps/socket.html#method-connect + * @param {Object} createInfo The socket details + */ + TcpClient.prototype._onCreate = function(createInfo) { + this.socketId = createInfo.socketId; + if (this.socketId > 0) { + socket.connect(this.socketId, this.addr, this.port, this._onConnectComplete.bind(this)); + } else { + error('Unable to create socket'); + } + }; + + /** + * The callback function used for when we attempt to have Chrome + * connect to the remote side. If a successful connection is + * made then polling starts to check for data to read + * + * @private + * @param {Number} resultCode Indicates whether the connection was successful + */ + TcpClient.prototype._onConnectComplete = function(resultCode) { + // Start polling for reads. + this.isConnected = true; + setTimeout(this._periodicallyRead.bind(this), this.pollInterval); + + if (this.callbacks.connect) { + log('connect complete'); + this.callbacks.connect(); + } + log('onConnectComplete'); + }; + + /** + * Checks for new data to read from the socket + * + * @see http://developer.chrome.com/trunk/apps/socket.html#method-read + */ + TcpClient.prototype._periodicallyRead = function() { + var that = this; + socket.getInfo(this.socketId, function (info) { + if (info.connected) { + setTimeout(that._periodicallyRead.bind(that), that.pollInterval); + socket.read(that.socketId, null, that._onDataRead.bind(that)); + } else if (that.isConnected) { + log('socket disconnect detected'); + that.disconnect(); + } + }); + }; + + /** + * Callback function for when data has been read from the socket. + * Converts the array buffer that is read in to a string + * and sends it on for further processing by passing it to + * the previously assigned callback function. + * + * @private + * @see TcpClient.prototype.addResponseListener + * @param {Object} readInfo The incoming message + */ + TcpClient.prototype._onDataRead = function(readInfo) { + // Call received callback if there's data in the response. + if (readInfo.resultCode > 0) { + log('onDataRead'); + + /* + // Debug + var bytes = [], u8 = new Uint8Array(readInfo.data); + for (var i = 0; i < u8.length; i++) { + bytes.push(u8[i]); + } + log("received bytes: " + (bytes.join(','))); + */ + + if (this.callbacks.recvBuffer) { + // Return raw ArrayBuffer directly. + this.callbacks.recvBuffer(readInfo.data); + } + if (this.callbacks.recvString) { + // Convert ArrayBuffer to string. + this._arrayBufferToString(readInfo.data, function(str) { + this.callbacks.recvString(str); + }.bind(this)); + } + + // Trigger another read right away + setTimeout(this._periodicallyRead.bind(this), 0); + } + }; + + /** + * Callback for when data has been successfully + * written to the socket. + * + * @private + * @param {Object} writeInfo The outgoing message + */ + TcpClient.prototype._onWriteComplete = function(writeInfo) { + log('onWriteComplete'); + // Call sent callback. + if (this.callbacks.sent) { + this.callbacks.sent(writeInfo); + } + }; + + /** + * Converts an array buffer to a string + * + * @private + * @param {ArrayBuffer} buf The buffer to convert + * @param {Function} callback The function to call when conversion is complete + */ + TcpClient.prototype._arrayBufferToString = function(buf, callback) { + var bb = new Blob([new Uint8Array(buf)]); + var f = new FileReader(); + f.onload = function(e) { + callback(e.target.result); + }; + f.readAsText(bb); + }; + + /** + * Converts a string to an array buffer + * + * @private + * @param {String} str The string to convert + * @param {Function} callback The function to call when conversion is complete + */ + TcpClient.prototype._stringToArrayBuffer = function(str, callback) { + var bb = new Blob([str]); + var f = new FileReader(); + f.onload = function(e) { + callback(e.target.result); + }; + f.readAsArrayBuffer(bb); + }; + + /** + * Wrapper function for logging + */ + function log(msg) { + console.log(msg); + } + + /** + * Wrapper function for error logging + */ + function error(msg) { + console.error(msg); + } + + exports.TcpClient = TcpClient; + +})(window); diff --git a/webclients/novnc/include/des.js b/webclients/novnc/include/des.js index 1f95285..ecbc819 100644 --- a/webclients/novnc/include/des.js +++ b/webclients/novnc/include/des.js @@ -75,199 +75,202 @@ * fine Java utilities: http://www.acme.com/java/ */ -"use strict"; -/*jslint white: false, bitwise: false, plusplus: false */ +/* jslint white: false */ function DES(passwd) { + "use strict"; -// Tables, permutations, S-boxes, etc. -var PC2 = [13,16,10,23, 0, 4, 2,27,14, 5,20, 9,22,18,11, 3, - 25, 7,15, 6,26,19,12, 1,40,51,30,36,46,54,29,39, - 50,44,32,47,43,48,38,55,33,52,45,41,49,35,28,31 ], - totrot = [ 1, 2, 4, 6, 8,10,12,14,15,17,19,21,23,25,27,28], - z = 0x0, a,b,c,d,e,f, SP1,SP2,SP3,SP4,SP5,SP6,SP7,SP8, - keys = []; + // Tables, permutations, S-boxes, etc. + // jshint -W013 + var PC2 = [13,16,10,23, 0, 4, 2,27,14, 5,20, 9,22,18,11, 3, + 25, 7,15, 6,26,19,12, 1,40,51,30,36,46,54,29,39, + 50,44,32,47,43,48,38,55,33,52,45,41,49,35,28,31 ], + totrot = [ 1, 2, 4, 6, 8,10,12,14,15,17,19,21,23,25,27,28], + z = 0x0, a,b,c,d,e,f, SP1,SP2,SP3,SP4,SP5,SP6,SP7,SP8, + keys = []; -a=1<<16; b=1<<24; c=a|b; d=1<<2; e=1<<10; f=d|e; -SP1 = [c|e,z|z,a|z,c|f,c|d,a|f,z|d,a|z,z|e,c|e,c|f,z|e,b|f,c|d,b|z,z|d, - z|f,b|e,b|e,a|e,a|e,c|z,c|z,b|f,a|d,b|d,b|d,a|d,z|z,z|f,a|f,b|z, - a|z,c|f,z|d,c|z,c|e,b|z,b|z,z|e,c|d,a|z,a|e,b|d,z|e,z|d,b|f,a|f, - c|f,a|d,c|z,b|f,b|d,z|f,a|f,c|e,z|f,b|e,b|e,z|z,a|d,a|e,z|z,c|d]; -a=1<<20; b=1<<31; c=a|b; d=1<<5; e=1<<15; f=d|e; -SP2 = [c|f,b|e,z|e,a|f,a|z,z|d,c|d,b|f,b|d,c|f,c|e,b|z,b|e,a|z,z|d,c|d, - a|e,a|d,b|f,z|z,b|z,z|e,a|f,c|z,a|d,b|d,z|z,a|e,z|f,c|e,c|z,z|f, - z|z,a|f,c|d,a|z,b|f,c|z,c|e,z|e,c|z,b|e,z|d,c|f,a|f,z|d,z|e,b|z, - z|f,c|e,a|z,b|d,a|d,b|f,b|d,a|d,a|e,z|z,b|e,z|f,b|z,c|d,c|f,a|e]; -a=1<<17; b=1<<27; c=a|b; d=1<<3; e=1<<9; f=d|e; -SP3 = [z|f,c|e,z|z,c|d,b|e,z|z,a|f,b|e,a|d,b|d,b|d,a|z,c|f,a|d,c|z,z|f, - b|z,z|d,c|e,z|e,a|e,c|z,c|d,a|f,b|f,a|e,a|z,b|f,z|d,c|f,z|e,b|z, - c|e,b|z,a|d,z|f,a|z,c|e,b|e,z|z,z|e,a|d,c|f,b|e,b|d,z|e,z|z,c|d, - b|f,a|z,b|z,c|f,z|d,a|f,a|e,b|d,c|z,b|f,z|f,c|z,a|f,z|d,c|d,a|e]; -a=1<<13; b=1<<23; c=a|b; d=1<<0; e=1<<7; f=d|e; -SP4 = [c|d,a|f,a|f,z|e,c|e,b|f,b|d,a|d,z|z,c|z,c|z,c|f,z|f,z|z,b|e,b|d, - z|d,a|z,b|z,c|d,z|e,b|z,a|d,a|e,b|f,z|d,a|e,b|e,a|z,c|e,c|f,z|f, - b|e,b|d,c|z,c|f,z|f,z|z,z|z,c|z,a|e,b|e,b|f,z|d,c|d,a|f,a|f,z|e, - c|f,z|f,z|d,a|z,b|d,a|d,c|e,b|f,a|d,a|e,b|z,c|d,z|e,b|z,a|z,c|e]; -a=1<<25; b=1<<30; c=a|b; d=1<<8; e=1<<19; f=d|e; -SP5 = [z|d,a|f,a|e,c|d,z|e,z|d,b|z,a|e,b|f,z|e,a|d,b|f,c|d,c|e,z|f,b|z, - a|z,b|e,b|e,z|z,b|d,c|f,c|f,a|d,c|e,b|d,z|z,c|z,a|f,a|z,c|z,z|f, - z|e,c|d,z|d,a|z,b|z,a|e,c|d,b|f,a|d,b|z,c|e,a|f,b|f,z|d,a|z,c|e, - c|f,z|f,c|z,c|f,a|e,z|z,b|e,c|z,z|f,a|d,b|d,z|e,z|z,b|e,a|f,b|d]; -a=1<<22; b=1<<29; c=a|b; d=1<<4; e=1<<14; f=d|e; -SP6 = [b|d,c|z,z|e,c|f,c|z,z|d,c|f,a|z,b|e,a|f,a|z,b|d,a|d,b|e,b|z,z|f, - z|z,a|d,b|f,z|e,a|e,b|f,z|d,c|d,c|d,z|z,a|f,c|e,z|f,a|e,c|e,b|z, - b|e,z|d,c|d,a|e,c|f,a|z,z|f,b|d,a|z,b|e,b|z,z|f,b|d,c|f,a|e,c|z, - a|f,c|e,z|z,c|d,z|d,z|e,c|z,a|f,z|e,a|d,b|f,z|z,c|e,b|z,a|d,b|f]; -a=1<<21; b=1<<26; c=a|b; d=1<<1; e=1<<11; f=d|e; -SP7 = [a|z,c|d,b|f,z|z,z|e,b|f,a|f,c|e,c|f,a|z,z|z,b|d,z|d,b|z,c|d,z|f, - b|e,a|f,a|d,b|e,b|d,c|z,c|e,a|d,c|z,z|e,z|f,c|f,a|e,z|d,b|z,a|e, - b|z,a|e,a|z,b|f,b|f,c|d,c|d,z|d,a|d,b|z,b|e,a|z,c|e,z|f,a|f,c|e, - z|f,b|d,c|f,c|z,a|e,z|z,z|d,c|f,z|z,a|f,c|z,z|e,b|d,b|e,z|e,a|d]; -a=1<<18; b=1<<28; c=a|b; d=1<<6; e=1<<12; f=d|e; -SP8 = [b|f,z|e,a|z,c|f,b|z,b|f,z|d,b|z,a|d,c|z,c|f,a|e,c|e,a|f,z|e,z|d, - c|z,b|d,b|e,z|f,a|e,a|d,c|d,c|e,z|f,z|z,z|z,c|d,b|d,b|e,a|f,a|z, - a|f,a|z,c|e,z|e,z|d,c|d,z|e,a|f,b|e,z|d,b|d,c|z,c|d,b|z,a|z,b|f, - z|z,c|f,a|d,b|d,c|z,b|e,b|f,z|z,c|f,a|e,a|e,z|f,z|f,a|d,b|z,c|e]; + // jshint -W015 + a=1<<16; b=1<<24; c=a|b; d=1<<2; e=1<<10; f=d|e; + SP1 = [c|e,z|z,a|z,c|f,c|d,a|f,z|d,a|z,z|e,c|e,c|f,z|e,b|f,c|d,b|z,z|d, + z|f,b|e,b|e,a|e,a|e,c|z,c|z,b|f,a|d,b|d,b|d,a|d,z|z,z|f,a|f,b|z, + a|z,c|f,z|d,c|z,c|e,b|z,b|z,z|e,c|d,a|z,a|e,b|d,z|e,z|d,b|f,a|f, + c|f,a|d,c|z,b|f,b|d,z|f,a|f,c|e,z|f,b|e,b|e,z|z,a|d,a|e,z|z,c|d]; + a=1<<20; b=1<<31; c=a|b; d=1<<5; e=1<<15; f=d|e; + SP2 = [c|f,b|e,z|e,a|f,a|z,z|d,c|d,b|f,b|d,c|f,c|e,b|z,b|e,a|z,z|d,c|d, + a|e,a|d,b|f,z|z,b|z,z|e,a|f,c|z,a|d,b|d,z|z,a|e,z|f,c|e,c|z,z|f, + z|z,a|f,c|d,a|z,b|f,c|z,c|e,z|e,c|z,b|e,z|d,c|f,a|f,z|d,z|e,b|z, + z|f,c|e,a|z,b|d,a|d,b|f,b|d,a|d,a|e,z|z,b|e,z|f,b|z,c|d,c|f,a|e]; + a=1<<17; b=1<<27; c=a|b; d=1<<3; e=1<<9; f=d|e; + SP3 = [z|f,c|e,z|z,c|d,b|e,z|z,a|f,b|e,a|d,b|d,b|d,a|z,c|f,a|d,c|z,z|f, + b|z,z|d,c|e,z|e,a|e,c|z,c|d,a|f,b|f,a|e,a|z,b|f,z|d,c|f,z|e,b|z, + c|e,b|z,a|d,z|f,a|z,c|e,b|e,z|z,z|e,a|d,c|f,b|e,b|d,z|e,z|z,c|d, + b|f,a|z,b|z,c|f,z|d,a|f,a|e,b|d,c|z,b|f,z|f,c|z,a|f,z|d,c|d,a|e]; + a=1<<13; b=1<<23; c=a|b; d=1<<0; e=1<<7; f=d|e; + SP4 = [c|d,a|f,a|f,z|e,c|e,b|f,b|d,a|d,z|z,c|z,c|z,c|f,z|f,z|z,b|e,b|d, + z|d,a|z,b|z,c|d,z|e,b|z,a|d,a|e,b|f,z|d,a|e,b|e,a|z,c|e,c|f,z|f, + b|e,b|d,c|z,c|f,z|f,z|z,z|z,c|z,a|e,b|e,b|f,z|d,c|d,a|f,a|f,z|e, + c|f,z|f,z|d,a|z,b|d,a|d,c|e,b|f,a|d,a|e,b|z,c|d,z|e,b|z,a|z,c|e]; + a=1<<25; b=1<<30; c=a|b; d=1<<8; e=1<<19; f=d|e; + SP5 = [z|d,a|f,a|e,c|d,z|e,z|d,b|z,a|e,b|f,z|e,a|d,b|f,c|d,c|e,z|f,b|z, + a|z,b|e,b|e,z|z,b|d,c|f,c|f,a|d,c|e,b|d,z|z,c|z,a|f,a|z,c|z,z|f, + z|e,c|d,z|d,a|z,b|z,a|e,c|d,b|f,a|d,b|z,c|e,a|f,b|f,z|d,a|z,c|e, + c|f,z|f,c|z,c|f,a|e,z|z,b|e,c|z,z|f,a|d,b|d,z|e,z|z,b|e,a|f,b|d]; + a=1<<22; b=1<<29; c=a|b; d=1<<4; e=1<<14; f=d|e; + SP6 = [b|d,c|z,z|e,c|f,c|z,z|d,c|f,a|z,b|e,a|f,a|z,b|d,a|d,b|e,b|z,z|f, + z|z,a|d,b|f,z|e,a|e,b|f,z|d,c|d,c|d,z|z,a|f,c|e,z|f,a|e,c|e,b|z, + b|e,z|d,c|d,a|e,c|f,a|z,z|f,b|d,a|z,b|e,b|z,z|f,b|d,c|f,a|e,c|z, + a|f,c|e,z|z,c|d,z|d,z|e,c|z,a|f,z|e,a|d,b|f,z|z,c|e,b|z,a|d,b|f]; + a=1<<21; b=1<<26; c=a|b; d=1<<1; e=1<<11; f=d|e; + SP7 = [a|z,c|d,b|f,z|z,z|e,b|f,a|f,c|e,c|f,a|z,z|z,b|d,z|d,b|z,c|d,z|f, + b|e,a|f,a|d,b|e,b|d,c|z,c|e,a|d,c|z,z|e,z|f,c|f,a|e,z|d,b|z,a|e, + b|z,a|e,a|z,b|f,b|f,c|d,c|d,z|d,a|d,b|z,b|e,a|z,c|e,z|f,a|f,c|e, + z|f,b|d,c|f,c|z,a|e,z|z,z|d,c|f,z|z,a|f,c|z,z|e,b|d,b|e,z|e,a|d]; + a=1<<18; b=1<<28; c=a|b; d=1<<6; e=1<<12; f=d|e; + SP8 = [b|f,z|e,a|z,c|f,b|z,b|f,z|d,b|z,a|d,c|z,c|f,a|e,c|e,a|f,z|e,z|d, + c|z,b|d,b|e,z|f,a|e,a|d,c|d,c|e,z|f,z|z,z|z,c|d,b|d,b|e,a|f,a|z, + a|f,a|z,c|e,z|e,z|d,c|d,z|e,a|f,b|e,z|d,b|d,c|z,c|d,b|z,a|z,b|f, + z|z,c|f,a|d,b|d,c|z,b|e,b|f,z|z,c|f,a|e,a|e,z|f,z|f,a|d,b|z,c|e]; + // jshint +W013,+W015 -// Set the key. -function setKeys(keyBlock) { - var i, j, l, m, n, o, pc1m = [], pcr = [], kn = [], - raw0, raw1, rawi, KnLi; + // Set the key. + function setKeys(keyBlock) { + var i, j, l, m, n, o, pc1m = [], pcr = [], kn = [], + raw0, raw1, rawi, KnLi; - for (j = 0, l = 56; j < 56; ++j, l-=8) { - l += l<-5 ? 65 : l<-3 ? 31 : l<-1 ? 63 : l===27 ? 35 : 0; // PC1 - m = l & 0x7; - pc1m[j] = ((keyBlock[l >>> 3] & (1<>> 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; + // cookey + for (i = 0, rawi = 0, KnLi = 0; i < 16; ++i) { + raw0 = kn[rawi++]; + raw1 = kn[rawi++]; + keys[KnLi] = (raw0 & 0x00fc0000) << 6; + keys[KnLi] |= (raw0 & 0x00000fc0) << 10; + keys[KnLi] |= (raw1 & 0x00fc0000) >>> 10; + keys[KnLi] |= (raw1 & 0x00000fc0) >>> 6; + ++KnLi; + keys[KnLi] = (raw0 & 0x0003f000) << 12; + keys[KnLi] |= (raw0 & 0x0000003f) << 16; + keys[KnLi] |= (raw1 & 0x0003f000) >>> 4; + keys[KnLi] |= (raw1 & 0x0000003f); + ++KnLi; + } } -} -// Encrypt 8 bytes of text -function enc8(text) { - var i = 0, b = text.slice(), fval, keysi = 0, - l, r, x; // left, right, accumulator + // Encrypt 8 bytes of text + function enc8(text) { + var i = 0, b = text.slice(), fval, keysi = 0, + l, r, x; // left, right, accumulator - // Squash 8 bytes to 2 ints - l = b[i++]<<24 | b[i++]<<16 | b[i++]<<8 | b[i++]; - r = b[i++]<<24 | b[i++]<<16 | b[i++]<<8 | b[i++]; + // Squash 8 bytes to 2 ints + l = b[i++]<<24 | b[i++]<<16 | b[i++]<<8 | b[i++]; + r = b[i++]<<24 | b[i++]<<16 | b[i++]<<8 | b[i++]; - x = ((l >>> 4) ^ r) & 0x0f0f0f0f; - r ^= x; - l ^= (x << 4); - x = ((l >>> 16) ^ r) & 0x0000ffff; - r ^= x; - l ^= (x << 16); - x = ((r >>> 2) ^ l) & 0x33333333; - l ^= x; - r ^= (x << 2); - x = ((r >>> 8) ^ l) & 0x00ff00ff; - l ^= x; - r ^= (x << 8); - r = (r << 1) | ((r >>> 31) & 1); - x = (l ^ r) & 0xaaaaaaaa; - l ^= x; - r ^= x; - l = (l << 1) | ((l >>> 31) & 1); + x = ((l >>> 4) ^ r) & 0x0f0f0f0f; + r ^= x; + l ^= (x << 4); + x = ((l >>> 16) ^ r) & 0x0000ffff; + r ^= x; + l ^= (x << 16); + x = ((r >>> 2) ^ l) & 0x33333333; + l ^= x; + r ^= (x << 2); + x = ((r >>> 8) ^ l) & 0x00ff00ff; + l ^= x; + r ^= (x << 8); + r = (r << 1) | ((r >>> 31) & 1); + x = (l ^ r) & 0xaaaaaaaa; + l ^= x; + r ^= x; + l = (l << 1) | ((l >>> 31) & 1); - for (i = 0; i < 8; ++i) { - x = (r << 28) | (r >>> 4); - x ^= keys[keysi++]; - fval = SP7[x & 0x3f]; - fval |= SP5[(x >>> 8) & 0x3f]; - fval |= SP3[(x >>> 16) & 0x3f]; - fval |= SP1[(x >>> 24) & 0x3f]; - x = r ^ keys[keysi++]; - fval |= SP8[x & 0x3f]; - fval |= SP6[(x >>> 8) & 0x3f]; - fval |= SP4[(x >>> 16) & 0x3f]; - fval |= SP2[(x >>> 24) & 0x3f]; - l ^= fval; - x = (l << 28) | (l >>> 4); - x ^= keys[keysi++]; - fval = SP7[x & 0x3f]; - fval |= SP5[(x >>> 8) & 0x3f]; - fval |= SP3[(x >>> 16) & 0x3f]; - fval |= SP1[(x >>> 24) & 0x3f]; - x = l ^ keys[keysi++]; - fval |= SP8[x & 0x0000003f]; - fval |= SP6[(x >>> 8) & 0x3f]; - fval |= SP4[(x >>> 16) & 0x3f]; - fval |= SP2[(x >>> 24) & 0x3f]; - r ^= fval; - } + for (i = 0; i < 8; ++i) { + x = (r << 28) | (r >>> 4); + x ^= keys[keysi++]; + fval = SP7[x & 0x3f]; + fval |= SP5[(x >>> 8) & 0x3f]; + fval |= SP3[(x >>> 16) & 0x3f]; + fval |= SP1[(x >>> 24) & 0x3f]; + x = r ^ keys[keysi++]; + fval |= SP8[x & 0x3f]; + fval |= SP6[(x >>> 8) & 0x3f]; + fval |= SP4[(x >>> 16) & 0x3f]; + fval |= SP2[(x >>> 24) & 0x3f]; + l ^= fval; + x = (l << 28) | (l >>> 4); + x ^= keys[keysi++]; + fval = SP7[x & 0x3f]; + fval |= SP5[(x >>> 8) & 0x3f]; + fval |= SP3[(x >>> 16) & 0x3f]; + fval |= SP1[(x >>> 24) & 0x3f]; + x = l ^ keys[keysi++]; + fval |= SP8[x & 0x0000003f]; + fval |= SP6[(x >>> 8) & 0x3f]; + fval |= SP4[(x >>> 16) & 0x3f]; + fval |= SP2[(x >>> 24) & 0x3f]; + r ^= fval; + } - r = (r << 31) | (r >>> 1); - x = (l ^ r) & 0xaaaaaaaa; - l ^= x; - r ^= x; - l = (l << 31) | (l >>> 1); - x = ((l >>> 8) ^ r) & 0x00ff00ff; - r ^= x; - l ^= (x << 8); - x = ((l >>> 2) ^ r) & 0x33333333; - r ^= x; - l ^= (x << 2); - x = ((r >>> 16) ^ l) & 0x0000ffff; - l ^= x; - r ^= (x << 16); - x = ((r >>> 4) ^ l) & 0x0f0f0f0f; - l ^= x; - r ^= (x << 4); + r = (r << 31) | (r >>> 1); + x = (l ^ r) & 0xaaaaaaaa; + l ^= x; + r ^= x; + l = (l << 31) | (l >>> 1); + x = ((l >>> 8) ^ r) & 0x00ff00ff; + r ^= x; + l ^= (x << 8); + x = ((l >>> 2) ^ r) & 0x33333333; + r ^= x; + l ^= (x << 2); + x = ((r >>> 16) ^ l) & 0x0000ffff; + l ^= x; + r ^= (x << 16); + x = ((r >>> 4) ^ l) & 0x0f0f0f0f; + l ^= x; + r ^= (x << 4); - // Spread ints to bytes - x = [r, l]; - for (i = 0; i < 8; i++) { - b[i] = (x[i>>>2] >>> (8*(3 - (i%4)))) % 256; - if (b[i] < 0) { b[i] += 256; } // unsigned + // Spread ints to bytes + x = [r, l]; + for (i = 0; i < 8; i++) { + b[i] = (x[i>>>2] >>> (8 * (3 - (i % 4)))) % 256; + if (b[i] < 0) { b[i] += 256; } // unsigned + } + return b; } - return b; -} -// Encrypt 16 bytes of text using passwd as key -function encrypt(t) { - return enc8(t.slice(0,8)).concat(enc8(t.slice(8,16))); -} + // Encrypt 16 bytes of text using passwd as key + function encrypt(t) { + return enc8(t.slice(0, 8)).concat(enc8(t.slice(8, 16))); + } -setKeys(passwd); // Setup keys -return {'encrypt': encrypt}; // Public interface + setKeys(passwd); // Setup keys + return {'encrypt': encrypt}; // Public interface } // function DES diff --git a/webclients/novnc/include/display.js b/webclients/novnc/include/display.js index 5ad99ba..8763fa4 100644 --- a/webclients/novnc/include/display.js +++ b/webclients/novnc/include/display.js @@ -1,757 +1,740 @@ /* * noVNC: HTML5 VNC client * Copyright (C) 2012 Joel Martin - * Licensed under LGPL-3 (see LICENSE.txt) + * Licensed under MPL 2.0 (see LICENSE.txt) * * See README.md for usage and integration instructions. */ -/*jslint browser: true, white: false, bitwise: false */ +/*jslint browser: true, white: false */ /*global Util, Base64, changeCursor */ -function Display(defaults) { -"use strict"; - -var that = {}, // Public API methods - conf = {}, // Configuration attributes - - // Private Display namespace variables - c_ctx = null, - c_forceCanvas = false, - - // Queued drawing actions for in-order rendering - renderQ = [], - - // Predefine function variables (jslint) - imageDataGet, rgbImageData, bgrxImageData, cmapImageData, - setFillColor, rescale, scan_renderQ, - - // The full frame buffer (logical canvas) size - fb_width = 0, - fb_height = 0, - // The visible "physical canvas" viewport - viewport = {'x': 0, 'y': 0, 'w' : 0, 'h' : 0 }, - cleanRect = {'x1': 0, 'y1': 0, 'x2': -1, 'y2': -1}, - - c_prevStyle = "", - tile = null, - tile16x16 = null, - tile_x = 0, - tile_y = 0; - - -// Configuration attributes -Util.conf_defaults(conf, that, defaults, [ - ['target', 'wo', 'dom', null, 'Canvas element for rendering'], - ['context', 'ro', 'raw', null, 'Canvas 2D context for rendering (read-only)'], - ['logo', 'rw', 'raw', null, 'Logo to display when cleared: {"width": width, "height": height, "data": data}'], - ['true_color', 'rw', 'bool', true, 'Use true-color pixel data'], - ['colourMap', 'rw', 'arr', [], 'Colour map array (when not true-color)'], - ['scale', 'rw', 'float', 1.0, 'Display area scale factor 0.0 - 1.0'], - ['viewport', 'rw', 'bool', false, 'Use a viewport set with viewportChange()'], - ['width', 'rw', 'int', null, 'Display area width'], - ['height', 'rw', 'int', null, 'Display area height'], - - ['render_mode', 'ro', 'str', '', 'Canvas rendering mode (read-only)'], - - ['prefer_js', 'rw', 'str', null, 'Prefer Javascript over canvas methods'], - ['cursor_uri', 'rw', 'raw', null, 'Can we render cursor using data URI'] - ]); +var Display; -// Override some specific getters/setters -that.get_context = function () { return c_ctx; }; +(function () { + "use strict"; -that.set_scale = function(scale) { rescale(scale); }; + Display = function (defaults) { + this._drawCtx = null; + this._c_forceCanvas = false; -that.set_width = function (val) { that.resize(val, fb_height); }; -that.get_width = function() { return fb_width; }; + this._renderQ = []; // queue drawing actions for in-oder rendering -that.set_height = function (val) { that.resize(fb_width, val); }; -that.get_height = function() { return fb_height; }; + // the full frame buffer (logical canvas) size + this._fb_width = 0; + this._fb_height = 0; + // the visible "physical canvas" viewport + this._viewportLoc = { 'x': 0, 'y': 0, 'w': 0, 'h': 0 }; + this._cleanRect = { 'x1': 0, 'y1': 0, 'x2': -1, 'y2': -1 }; + this._prevDrawStyle = ""; + this._tile = null; + this._tile16x16 = null; + this._tile_x = 0; + this._tile_y = 0; -// -// Private functions -// + Util.set_defaults(this, defaults, { + 'true_color': true, + 'colourMap': [], + 'scale': 1.0, + 'viewport': false, + 'render_mode': '' + }); -// Create the public API interface -function constructor() { - Util.Debug(">> Display.constructor"); + Util.Debug(">> Display.constructor"); - var c, func, i, curDat, curSave, - has_imageData = false, UE = Util.Engine; + if (!this._target) { + throw new Error("Target must be set"); + } - if (! conf.target) { throw("target must be set"); } + if (typeof this._target === 'string') { + throw new Error('target must be a DOM element'); + } - if (typeof conf.target === 'string') { - throw("target must be a DOM element"); - } + if (!this._target.getContext) { + throw new Error("no getContext method"); + } - c = conf.target; + if (!this._drawCtx) { + this._drawCtx = this._target.getContext('2d'); + } - if (! c.getContext) { throw("no getContext method"); } + Util.Debug("User Agent: " + navigator.userAgent); + if (Util.Engine.gecko) { Util.Debug("Browser: gecko " + Util.Engine.gecko); } + if (Util.Engine.webkit) { Util.Debug("Browser: webkit " + Util.Engine.webkit); } + if (Util.Engine.trident) { Util.Debug("Browser: trident " + Util.Engine.trident); } + if (Util.Engine.presto) { Util.Debug("Browser: presto " + Util.Engine.presto); } - if (! c_ctx) { c_ctx = c.getContext('2d'); } + this.clear(); - Util.Debug("User Agent: " + navigator.userAgent); - if (UE.gecko) { Util.Debug("Browser: gecko " + UE.gecko); } - if (UE.webkit) { Util.Debug("Browser: webkit " + UE.webkit); } - if (UE.trident) { Util.Debug("Browser: trident " + UE.trident); } - if (UE.presto) { Util.Debug("Browser: presto " + UE.presto); } + // Check canvas features + if ('createImageData' in this._drawCtx) { + this._render_mode = 'canvas rendering'; + } else { + throw new Error("Canvas does not support createImageData"); + } - that.clear(); + if (this._prefer_js === null) { + Util.Info("Prefering javascript operations"); + this._prefer_js = true; + } - // Check canvas features - if ('createImageData' in c_ctx) { - conf.render_mode = "canvas rendering"; - } else { - throw("Canvas does not support createImageData"); - } - if (conf.prefer_js === null) { - Util.Info("Prefering javascript operations"); - conf.prefer_js = true; - } + // Determine browser support for setting the cursor via data URI scheme + var curDat = []; + for (var i = 0; i < 8 * 8 * 4; i++) { + curDat.push(255); + } + try { + var curSave = this._target.style.cursor; + Display.changeCursor(this._target, curDat, curDat, 2, 2, 8, 8); + if (this._target.style.cursor) { + if (this._cursor_uri === null || this._cursor_uri === undefined) { + this._cursor_uri = true; + } + Util.Info("Data URI scheme cursor supported"); + } else { + if (this._cursor_uri === null || this._cursor_uri === undefined) { + this._cursor_uri = false; + } + Util.Warn("Data URI scheme cursor not supported"); + } + this._target.style.cursor = curSave; + } catch (exc) { + Util.Error("Data URI scheme cursor test exception: " + exc); + this._cursor_uri = false; + } - // Initialize cached tile imageData - tile16x16 = c_ctx.createImageData(16, 16); + Util.Debug("<< Display.constructor"); + }; - /* - * Determine browser support for setting the cursor via data URI - * scheme - */ - curDat = []; - for (i=0; i < 8 * 8 * 4; i += 1) { - curDat.push(255); - } - try { - curSave = c.style.cursor; - changeCursor(conf.target, curDat, curDat, 2, 2, 8, 8); - if (c.style.cursor) { - if (conf.cursor_uri === null) { - conf.cursor_uri = true; + Display.prototype = { + // Public methods + viewportChange: function (deltaX, deltaY, width, height) { + var vp = this._viewportLoc; + var cr = this._cleanRect; + var canvas = this._target; + + if (!this._viewport) { + Util.Debug("Setting viewport to full display region"); + deltaX = -vp.w; // clamped later of out of bounds + deltaY = -vp.h; + width = this._fb_width; + height = this._fb_height; } - Util.Info("Data URI scheme cursor supported"); - } else { - if (conf.cursor_uri === null) { - conf.cursor_uri = false; + + if (typeof(deltaX) === "undefined") { deltaX = 0; } + if (typeof(deltaY) === "undefined") { deltaY = 0; } + if (typeof(width) === "undefined") { width = vp.w; } + if (typeof(height) === "undefined") { height = vp.h; } + + // Size change + if (width > this._fb_width) { width = this._fb_width; } + if (height > this._fb_height) { height = this._fb_height; } + + if (vp.w !== width || vp.h !== height) { + // Change width + if (width < vp.w && cr.x2 > vp.x + width - 1) { + cr.x2 = vp.x + width - 1; + } + vp.w = width; + + // Change height + if (height < vp.h && cr.y2 > vp.y + height - 1) { + cr.y2 = vp.y + height - 1; + } + vp.h = height; + + var saveImg = null; + if (vp.w > 0 && vp.h > 0 && canvas.width > 0 && canvas.height > 0) { + var img_width = canvas.width < vp.w ? canvas.width : vp.w; + var img_height = canvas.height < vp.h ? canvas.height : vp.h; + saveImg = this._drawCtx.getImageData(0, 0, img_width, img_height); + } + + canvas.width = vp.w; + canvas.height = vp.h; + + if (saveImg) { + this._drawCtx.putImageData(saveImg, 0, 0); + } } - Util.Warn("Data URI scheme cursor not supported"); - } - c.style.cursor = curSave; - } catch (exc2) { - Util.Error("Data URI scheme cursor test exception: " + exc2); - conf.cursor_uri = false; - } - - Util.Debug("<< Display.constructor"); - return that ; -} - -rescale = function(factor) { - var c, tp, x, y, - properties = ['transform', 'WebkitTransform', 'MozTransform', null]; - c = conf.target; - tp = properties.shift(); - while (tp) { - if (typeof c.style[tp] !== 'undefined') { - break; - } - tp = properties.shift(); - } - - if (tp === null) { - Util.Debug("No scaling support"); - return; - } - - - if (typeof(factor) === "undefined") { - factor = conf.scale; - } else if (factor > 1.0) { - factor = 1.0; - } else if (factor < 0.1) { - factor = 0.1; - } - - if (conf.scale === factor) { - //Util.Debug("Display already scaled to '" + factor + "'"); - return; - } - - conf.scale = factor; - x = c.width - c.width * factor; - y = c.height - c.height * factor; - c.style[tp] = "scale(" + conf.scale + ") translate(-" + x + "px, -" + y + "px)"; -}; - -setFillColor = function(color) { - var bgr, newStyle; - if (conf.true_color) { - bgr = color; - } else { - bgr = conf.colourMap[color[0]]; - } - newStyle = "rgb(" + bgr[2] + "," + bgr[1] + "," + bgr[0] + ")"; - if (newStyle !== c_prevStyle) { - c_ctx.fillStyle = newStyle; - c_prevStyle = newStyle; - } -}; - - -// -// Public API interface functions -// - -// Shift and/or resize the visible viewport -that.viewportChange = function(deltaX, deltaY, width, height) { - var c = conf.target, v = viewport, cr = cleanRect, - saveImg = null, saveStyle, x1, y1, vx2, vy2, w, h; - - if (!conf.viewport) { - Util.Debug("Setting viewport to full display region"); - deltaX = -v.w; // Clamped later if out of bounds - deltaY = -v.h; // Clamped later if out of bounds - width = fb_width; - height = fb_height; - } - - if (typeof(deltaX) === "undefined") { deltaX = 0; } - if (typeof(deltaY) === "undefined") { deltaY = 0; } - if (typeof(width) === "undefined") { width = v.w; } - if (typeof(height) === "undefined") { height = v.h; } - - // Size change - - if (width > fb_width) { width = fb_width; } - if (height > fb_height) { height = fb_height; } - - if ((v.w !== width) || (v.h !== height)) { - // Change width - if ((width < v.w) && (cr.x2 > v.x + width -1)) { - cr.x2 = v.x + width - 1; - } - v.w = width; - // Change height - if ((height < v.h) && (cr.y2 > v.y + height -1)) { - cr.y2 = v.y + height - 1; - } - v.h = height; + var vx2 = vp.x + vp.w - 1; + var vy2 = vp.y + vp.h - 1; + // Position change - if (v.w > 0 && v.h > 0 && c.width > 0 && c.height > 0) { - saveImg = c_ctx.getImageData(0, 0, - (c.width < v.w) ? c.width : v.w, - (c.height < v.h) ? c.height : v.h); - } + if (deltaX < 0 && vp.x + deltaX < 0) { + deltaX = -vp.x; + } + if (vx2 + deltaX >= this._fb_width) { + deltaX -= vx2 + deltaX - this._fb_width + 1; + } - c.width = v.w; - c.height = v.h; + if (vp.y + deltaY < 0) { + deltaY = -vp.y; + } + if (vy2 + deltaY >= this._fb_height) { + deltaY -= (vy2 + deltaY - this._fb_height + 1); + } - if (saveImg) { - c_ctx.putImageData(saveImg, 0, 0); - } - } - - vx2 = v.x + v.w - 1; - vy2 = v.y + v.h - 1; - - - // Position change - - if ((deltaX < 0) && ((v.x + deltaX) < 0)) { - deltaX = - v.x; - } - if ((vx2 + deltaX) >= fb_width) { - deltaX -= ((vx2 + deltaX) - fb_width + 1); - } - - if ((v.y + deltaY) < 0) { - deltaY = - v.y; - } - if ((vy2 + deltaY) >= fb_height) { - deltaY -= ((vy2 + deltaY) - fb_height + 1); - } - - if ((deltaX === 0) && (deltaY === 0)) { - //Util.Debug("skipping viewport change"); - return; - } - Util.Debug("viewportChange deltaX: " + deltaX + ", deltaY: " + deltaY); - - v.x += deltaX; - vx2 += deltaX; - v.y += deltaY; - vy2 += deltaY; - - // Update the clean rectangle - if (v.x > cr.x1) { - cr.x1 = v.x; - } - if (vx2 < cr.x2) { - cr.x2 = vx2; - } - if (v.y > cr.y1) { - cr.y1 = v.y; - } - if (vy2 < cr.y2) { - cr.y2 = vy2; - } - - if (deltaX < 0) { - // Shift viewport left, redraw left section - x1 = 0; - w = - deltaX; - } else { - // Shift viewport right, redraw right section - x1 = v.w - deltaX; - w = deltaX; - } - if (deltaY < 0) { - // Shift viewport up, redraw top section - y1 = 0; - h = - deltaY; - } else { - // Shift viewport down, redraw bottom section - y1 = v.h - deltaY; - h = deltaY; - } - - // Copy the valid part of the viewport to the shifted location - saveStyle = c_ctx.fillStyle; - c_ctx.fillStyle = "rgb(255,255,255)"; - if (deltaX !== 0) { - //that.copyImage(0, 0, -deltaX, 0, v.w, v.h); - //that.fillRect(x1, 0, w, v.h, [255,255,255]); - c_ctx.drawImage(c, 0, 0, v.w, v.h, -deltaX, 0, v.w, v.h); - c_ctx.fillRect(x1, 0, w, v.h); - } - if (deltaY !== 0) { - //that.copyImage(0, 0, 0, -deltaY, v.w, v.h); - //that.fillRect(0, y1, v.w, h, [255,255,255]); - c_ctx.drawImage(c, 0, 0, v.w, v.h, 0, -deltaY, v.w, v.h); - c_ctx.fillRect(0, y1, v.w, h); - } - c_ctx.fillStyle = saveStyle; -}; - - -// Return a map of clean and dirty areas of the viewport and reset the -// tracking of clean and dirty areas. -// -// Returns: {'cleanBox': {'x': x, 'y': y, 'w': w, 'h': h}, -// 'dirtyBoxes': [{'x': x, 'y': y, 'w': w, 'h': h}, ...]} -that.getCleanDirtyReset = function() { - var v = viewport, c = cleanRect, cleanBox, dirtyBoxes = [], - vx2 = v.x + v.w - 1, vy2 = v.y + v.h - 1; - - - // Copy the cleanRect - cleanBox = {'x': c.x1, 'y': c.y1, - 'w': c.x2 - c.x1 + 1, 'h': c.y2 - c.y1 + 1}; - - if ((c.x1 >= c.x2) || (c.y1 >= c.y2)) { - // Whole viewport is dirty - dirtyBoxes.push({'x': v.x, 'y': v.y, 'w': v.w, 'h': v.h}); - } else { - // Redraw dirty regions - if (v.x < c.x1) { - // left side dirty region - dirtyBoxes.push({'x': v.x, 'y': v.y, - 'w': c.x1 - v.x + 1, 'h': v.h}); - } - if (vx2 > c.x2) { - // right side dirty region - dirtyBoxes.push({'x': c.x2 + 1, 'y': v.y, - 'w': vx2 - c.x2, 'h': v.h}); - } - if (v.y < c.y1) { - // top/middle dirty region - dirtyBoxes.push({'x': c.x1, 'y': v.y, - 'w': c.x2 - c.x1 + 1, 'h': c.y1 - v.y}); - } - if (vy2 > c.y2) { - // bottom/middle dirty region - dirtyBoxes.push({'x': c.x1, 'y': c.y2 + 1, - 'w': c.x2 - c.x1 + 1, 'h': vy2 - c.y2}); - } - } - - // Reset the cleanRect to the whole viewport - cleanRect = {'x1': v.x, 'y1': v.y, - 'x2': v.x + v.w - 1, 'y2': v.y + v.h - 1}; - - return {'cleanBox': cleanBox, 'dirtyBoxes': dirtyBoxes}; -}; - -// Translate viewport coordinates to absolute coordinates -that.absX = function(x) { - return x + viewport.x; -}; -that.absY = function(y) { - return y + viewport.y; -}; - - -that.resize = function(width, height) { - c_prevStyle = ""; - - fb_width = width; - fb_height = height; - - rescale(conf.scale); - that.viewportChange(); -}; - -that.clear = function() { - - if (conf.logo) { - that.resize(conf.logo.width, conf.logo.height); - that.blitStringImage(conf.logo.data, 0, 0); - } else { - that.resize(640, 20); - c_ctx.clearRect(0, 0, viewport.w, viewport.h); - } - - renderQ = []; - - // No benefit over default ("source-over") in Chrome and firefox - //c_ctx.globalCompositeOperation = "copy"; -}; - -that.fillRect = function(x, y, width, height, color) { - setFillColor(color); - c_ctx.fillRect(x - viewport.x, y - viewport.y, width, height); -}; - -that.copyImage = function(old_x, old_y, new_x, new_y, w, h) { - var x1 = old_x - viewport.x, y1 = old_y - viewport.y, - x2 = new_x - viewport.x, y2 = new_y - viewport.y; - c_ctx.drawImage(conf.target, x1, y1, w, h, x2, y2, w, h); -}; - - -// Start updating a tile -that.startTile = function(x, y, width, height, color) { - var data, bgr, red, green, blue, i; - tile_x = x; - tile_y = y; - if ((width === 16) && (height === 16)) { - tile = tile16x16; - } else { - tile = c_ctx.createImageData(width, height); - } - data = tile.data; - if (conf.prefer_js) { - if (conf.true_color) { - bgr = color; - } else { - bgr = conf.colourMap[color[0]]; - } - red = bgr[2]; - green = bgr[1]; - blue = bgr[0]; - for (i = 0; i < (width * height * 4); i+=4) { - data[i ] = red; - data[i + 1] = green; - data[i + 2] = blue; - data[i + 3] = 255; - } - } else { - that.fillRect(x, y, width, height, color); - } -}; - -// Update sub-rectangle of the current tile -that.subTile = function(x, y, w, h, color) { - var data, p, bgr, red, green, blue, width, j, i, xend, yend; - if (conf.prefer_js) { - data = tile.data; - width = tile.width; - if (conf.true_color) { - bgr = color; - } else { - bgr = conf.colourMap[color[0]]; - } - red = bgr[2]; - green = bgr[1]; - blue = bgr[0]; - xend = x + w; - yend = y + h; - for (j = y; j < yend; j += 1) { - for (i = x; i < xend; i += 1) { - p = (i + (j * width) ) * 4; - data[p ] = red; - data[p + 1] = green; - data[p + 2] = blue; - data[p + 3] = 255; - } - } - } else { - that.fillRect(tile_x + x, tile_y + y, w, h, color); - } -}; - -// Draw the current tile to the screen -that.finishTile = function() { - if (conf.prefer_js) { - c_ctx.putImageData(tile, tile_x - viewport.x, tile_y - viewport.y); - } - // else: No-op, if not prefer_js then already done by setSubTile -}; - -rgbImageData = function(x, y, width, height, arr, offset) { - var img, i, j, data, v = viewport; - /* - if ((x - v.x >= v.w) || (y - v.y >= v.h) || - (x - v.x + width < 0) || (y - v.y + height < 0)) { - // Skipping because outside of viewport - return; - } - */ - img = c_ctx.createImageData(width, height); - data = img.data; - for (i=0, j=offset; i < (width * height * 4); i=i+4, j=j+3) { - data[i ] = arr[j ]; - data[i + 1] = arr[j + 1]; - data[i + 2] = arr[j + 2]; - data[i + 3] = 255; // Set Alpha - } - c_ctx.putImageData(img, x - v.x, y - v.y); -}; - -bgrxImageData = function(x, y, width, height, arr, offset) { - var img, i, j, data, v = viewport; - /* - if ((x - v.x >= v.w) || (y - v.y >= v.h) || - (x - v.x + width < 0) || (y - v.y + height < 0)) { - // Skipping because outside of viewport - return; - } - */ - img = c_ctx.createImageData(width, height); - data = img.data; - for (i=0, j=offset; i < (width * height * 4); i=i+4, j=j+4) { - data[i ] = arr[j + 2]; - data[i + 1] = arr[j + 1]; - data[i + 2] = arr[j ]; - data[i + 3] = 255; // Set Alpha - } - c_ctx.putImageData(img, x - v.x, y - v.y); -}; - -cmapImageData = function(x, y, width, height, arr, offset) { - var img, i, j, data, bgr, cmap; - img = c_ctx.createImageData(width, height); - data = img.data; - cmap = conf.colourMap; - for (i=0, j=offset; i < (width * height * 4); i+=4, j+=1) { - bgr = cmap[arr[j]]; - data[i ] = bgr[2]; - data[i + 1] = bgr[1]; - data[i + 2] = bgr[0]; - data[i + 3] = 255; // Set Alpha - } - c_ctx.putImageData(img, x - viewport.x, y - viewport.y); -}; - -that.blitImage = function(x, y, width, height, arr, offset) { - if (conf.true_color) { - bgrxImageData(x, y, width, height, arr, offset); - } else { - cmapImageData(x, y, width, height, arr, offset); - } -}; - -that.blitRgbImage = function(x, y, width, height, arr, offset) { - if (conf.true_color) { - rgbImageData(x, y, width, height, arr, offset); - } else { - // prolly wrong... - cmapImageData(x, y, width, height, arr, offset); - } -}; - -that.blitStringImage = function(str, x, y) { - var img = new Image(); - img.onload = function () { - c_ctx.drawImage(img, x - viewport.x, y - viewport.y); - }; - img.src = str; -}; - -// Wrap ctx.drawImage but relative to viewport -that.drawImage = function(img, x, y) { - c_ctx.drawImage(img, x - viewport.x, y - viewport.y); -}; - -that.renderQ_push = function(action) { - renderQ.push(action); - if (renderQ.length === 1) { - // If this can be rendered immediately it will be, otherwise - // the scanner will start polling the queue (every - // requestAnimationFrame interval) - scan_renderQ(); - } -}; - -scan_renderQ = function() { - var a, ready = true; - while (ready && renderQ.length > 0) { - a = renderQ[0]; - switch (a.type) { - case 'copy': - that.copyImage(a.old_x, a.old_y, a.x, a.y, a.width, a.height); - break; - case 'fill': - that.fillRect(a.x, a.y, a.width, a.height, a.color); - break; - case 'blit': - that.blitImage(a.x, a.y, a.width, a.height, a.data, 0); - break; - case 'blitRgb': - that.blitRgbImage(a.x, a.y, a.width, a.height, a.data, 0); - break; - case 'img': - if (a.img.complete) { - that.drawImage(a.img, a.x, a.y); + if (deltaX === 0 && deltaY === 0) { + return; + } + Util.Debug("viewportChange deltaX: " + deltaX + ", deltaY: " + deltaY); + + vp.x += deltaX; + vx2 += deltaX; + vp.y += deltaY; + vy2 += deltaY; + + // Update the clean rectangle + if (vp.x > cr.x1) { + cr.x1 = vp.x; + } + if (vx2 < cr.x2) { + cr.x2 = vx2; + } + if (vp.y > cr.y1) { + cr.y1 = vp.y; + } + if (vy2 < cr.y2) { + cr.y2 = vy2; + } + + var x1, w; + if (deltaX < 0) { + // Shift viewport left, redraw left section + x1 = 0; + w = -deltaX; + } else { + // Shift viewport right, redraw right section + x1 = vp.w - deltaX; + w = deltaX; + } + + var y1, h; + if (deltaY < 0) { + // Shift viewport up, redraw top section + y1 = 0; + h = -deltaY; + } else { + // Shift viewport down, redraw bottom section + y1 = vp.h - deltaY; + h = deltaY; + } + + // Copy the valid part of the viewport to the shifted location + var saveStyle = this._drawCtx.fillStyle; + this._drawCtx.fillStyle = "rgb(255,255,255)"; + if (deltaX !== 0) { + this._drawCtx.drawImage(canvas, 0, 0, vp.w, vp.h, -deltaX, 0, vp.w, vp.h); + this._drawCtx.fillRect(x1, 0, w, vp.h); + } + if (deltaY !== 0) { + this._drawCtx.drawImage(canvas, 0, 0, vp.w, vp.h, 0, -deltaY, vp.w, vp.h); + this._drawCtx.fillRect(0, y1, vp.w, h); + } + this._drawCtx.fillStyle = saveStyle; + }, + + // Return a map of clean and dirty areas of the viewport and reset the + // tracking of clean and dirty areas + // + // Returns: { 'cleanBox': { 'x': x, 'y': y, 'w': w, 'h': h}, + // 'dirtyBoxes': [{ 'x': x, 'y': y, 'w': w, 'h': h }, ...] } + getCleanDirtyReset: function () { + var vp = this._viewportLoc; + var cr = this._cleanRect; + + var cleanBox = { 'x': cr.x1, 'y': cr.y1, + 'w': cr.x2 - cr.x1 + 1, 'h': cr.y2 - cr.y1 + 1 }; + + var dirtyBoxes = []; + if (cr.x1 >= cr.x2 || cr.y1 >= cr.y2) { + // Whole viewport is dirty + dirtyBoxes.push({ 'x': vp.x, 'y': vp.y, 'w': vp.w, 'h': vp.h }); + } else { + // Redraw dirty regions + var vx2 = vp.x + vp.w - 1; + var vy2 = vp.y + vp.h - 1; + + if (vp.x < cr.x1) { + // left side dirty region + dirtyBoxes.push({'x': vp.x, 'y': vp.y, + 'w': cr.x1 - vp.x + 1, 'h': vp.h}); + } + if (vx2 > cr.x2) { + // right side dirty region + dirtyBoxes.push({'x': cr.x2 + 1, 'y': vp.y, + 'w': vx2 - cr.x2, 'h': vp.h}); + } + if(vp.y < cr.y1) { + // top/middle dirty region + dirtyBoxes.push({'x': cr.x1, 'y': vp.y, + 'w': cr.x2 - cr.x1 + 1, 'h': cr.y1 - vp.y}); + } + if (vy2 > cr.y2) { + // bottom/middle dirty region + dirtyBoxes.push({'x': cr.x1, 'y': cr.y2 + 1, + 'w': cr.x2 - cr.x1 + 1, 'h': vy2 - cr.y2}); + } + } + + this._cleanRect = {'x1': vp.x, 'y1': vp.y, + 'x2': vp.x + vp.w - 1, 'y2': vp.y + vp.h - 1}; + + return {'cleanBox': cleanBox, 'dirtyBoxes': dirtyBoxes}; + }, + + absX: function (x) { + return x + this._viewportLoc.x; + }, + + absY: function (y) { + return y + this._viewportLoc.y; + }, + + resize: function (width, height) { + this._prevDrawStyle = ""; + + this._fb_width = width; + this._fb_height = height; + + this._rescale(this._scale); + + this.viewportChange(); + }, + + clear: function () { + if (this._logo) { + this.resize(this._logo.width, this._logo.height); + this.blitStringImage(this._logo.data, 0, 0); + } else { + if (Util.Engine.trident === 6) { + // NB(directxman12): there's a bug in IE10 where we can fail to actually + // clear the canvas here because of the resize. + // Clearing the current viewport first fixes the issue + this._drawCtx.clearRect(0, 0, this._viewportLoc.w, this._viewportLoc.h); + } + this.resize(640, 20); + this._drawCtx.clearRect(0, 0, this._viewportLoc.w, this._viewportLoc.h); + } + + this._renderQ = []; + }, + + fillRect: function (x, y, width, height, color) { + this._setFillColor(color); + this._drawCtx.fillRect(x - this._viewportLoc.x, y - this._viewportLoc.y, width, height); + }, + + copyImage: function (old_x, old_y, new_x, new_y, w, h) { + var x1 = old_x - this._viewportLoc.x; + var y1 = old_y - this._viewportLoc.y; + var x2 = new_x - this._viewportLoc.x; + var y2 = new_y - this._viewportLoc.y; + + this._drawCtx.drawImage(this._target, x1, y1, w, h, x2, y2, w, h); + }, + + // start updating a tile + startTile: function (x, y, width, height, color) { + this._tile_x = x; + this._tile_y = y; + if (width === 16 && height === 16) { + this._tile = this._tile16x16; + } else { + this._tile = this._drawCtx.createImageData(width, height); + } + + if (this._prefer_js) { + var bgr; + if (this._true_color) { + bgr = color; } else { - // We need to wait for this image to 'load' - // to keep things in-order - ready = false; + bgr = this._colourMap[color[0]]; } - break; - } - if (ready) { - a = renderQ.shift(); - } - } - if (renderQ.length > 0) { - requestAnimFrame(scan_renderQ); - } -}; + var red = bgr[2]; + var green = bgr[1]; + var blue = bgr[0]; + + var data = this._tile.data; + for (var i = 0; i < width * height * 4; i += 4) { + data[i] = red; + data[i + 1] = green; + data[i + 2] = blue; + data[i + 3] = 255; + } + } else { + this.fillRect(x, y, width, height, color); + } + }, + + // update sub-rectangle of the current tile + subTile: function (x, y, w, h, color) { + if (this._prefer_js) { + var bgr; + if (this._true_color) { + bgr = color; + } else { + bgr = this._colourMap[color[0]]; + } + var red = bgr[2]; + var green = bgr[1]; + var blue = bgr[0]; + var xend = x + w; + var yend = y + h; + + var data = this._tile.data; + var width = this._tile.width; + for (var j = y; j < yend; j++) { + for (var i = x; i < xend; i++) { + var p = (i + (j * width)) * 4; + data[p] = red; + data[p + 1] = green; + data[p + 2] = blue; + data[p + 3] = 255; + } + } + } else { + this.fillRect(this._tile_x + x, this._tile_y + y, w, h, color); + } + }, + // draw the current tile to the screen + finishTile: function () { + if (this._prefer_js) { + this._drawCtx.putImageData(this._tile, this._tile_x - this._viewportLoc.x, + this._tile_y - this._viewportLoc.y); + } + // else: No-op -- already done by setSubTile + }, -that.changeCursor = function(pixels, mask, hotx, hoty, w, h) { - if (conf.cursor_uri === false) { - Util.Warn("changeCursor called but no cursor data URI support"); - return; - } + blitImage: function (x, y, width, height, arr, offset) { + if (this._true_color) { + this._bgrxImageData(x, y, this._viewportLoc.x, this._viewportLoc.y, width, height, arr, offset); + } else { + this._cmapImageData(x, y, this._viewportLoc.x, this._viewportLoc.y, width, height, arr, offset); + } + }, - if (conf.true_color) { - changeCursor(conf.target, pixels, mask, hotx, hoty, w, h); - } else { - changeCursor(conf.target, pixels, mask, hotx, hoty, w, h, conf.colourMap); - } -}; + blitRgbImage: function (x, y , width, height, arr, offset) { + if (this._true_color) { + this._rgbImageData(x, y, this._viewportLoc.x, this._viewportLoc.y, width, height, arr, offset); + } else { + // probably wrong? + this._cmapImageData(x, y, this._viewportLoc.x, this._viewportLoc.y, width, height, arr, offset); + } + }, + + blitStringImage: function (str, x, y) { + var img = new Image(); + img.onload = function () { + this._drawCtx.drawImage(img, x - this._viewportLoc.x, y - this._viewportLoc.y); + }.bind(this); + img.src = str; + return img; // for debugging purposes + }, + + // wrap ctx.drawImage but relative to viewport + drawImage: function (img, x, y) { + this._drawCtx.drawImage(img, x - this._viewportLoc.x, y - this._viewportLoc.y); + }, + + renderQ_push: function (action) { + this._renderQ.push(action); + if (this._renderQ.length === 1) { + // If this can be rendered immediately it will be, otherwise + // the scanner will start polling the queue (every + // requestAnimationFrame interval) + this._scan_renderQ(); + } + }, -that.defaultCursor = function() { - conf.target.style.cursor = "default"; -}; + changeCursor: function (pixels, mask, hotx, hoty, w, h) { + if (this._cursor_uri === false) { + Util.Warn("changeCursor called but no cursor data URI support"); + return; + } -return constructor(); // Return the public API interface + if (this._true_color) { + Display.changeCursor(this._target, pixels, mask, hotx, hoty, w, h); + } else { + Display.changeCursor(this._target, pixels, mask, hotx, hoty, w, h, this._colourMap); + } + }, + + defaultCursor: function () { + this._target.style.cursor = "default"; + }, + + // Overridden getters/setters + get_context: function () { + return this._drawCtx; + }, + + set_scale: function (scale) { + this._rescale(scale); + }, + + set_width: function (w) { + this.resize(w, this._fb_height); + }, + get_width: function () { + return this._fb_width; + }, + + set_height: function (h) { + this.resize(this._fb_width, h); + }, + get_height: function () { + return this._fb_height; + }, + + // Private Methods + _rescale: function (factor) { + var canvas = this._target; + var properties = ['transform', 'WebkitTransform', 'MozTransform']; + var transform_prop; + while ((transform_prop = properties.shift())) { + if (typeof canvas.style[transform_prop] !== 'undefined') { + break; + } + } -} // End of Display() + if (transform_prop === null) { + Util.Debug("No scaling support"); + return; + } + if (typeof(factor) === "undefined") { + factor = this._scale; + } else if (factor > 1.0) { + factor = 1.0; + } else if (factor < 0.1) { + factor = 0.1; + } -/* Set CSS cursor property using data URI encoded cursor file */ -function changeCursor(target, pixels, mask, hotx, hoty, w, h, cmap) { - "use strict"; - var cur = [], rgb, IHDRsz, RGBsz, ANDsz, XORsz, url, idx, alpha, x, y; - //Util.Debug(">> changeCursor, x: " + hotx + ", y: " + hoty + ", w: " + w + ", h: " + h); - - // Push multi-byte little-endian values - cur.push16le = function (num) { - this.push((num ) & 0xFF, - (num >> 8) & 0xFF ); - }; - cur.push32le = function (num) { - this.push((num ) & 0xFF, - (num >> 8) & 0xFF, - (num >> 16) & 0xFF, - (num >> 24) & 0xFF ); - }; + if (this._scale === factor) { + return; + } - IHDRsz = 40; - RGBsz = w * h * 4; - XORsz = Math.ceil( (w * h) / 8.0 ); - ANDsz = Math.ceil( (w * h) / 8.0 ); - - // Main header - cur.push16le(0); // 0: Reserved - cur.push16le(2); // 2: .CUR type - cur.push16le(1); // 4: Number of images, 1 for non-animated ico - - // Cursor #1 header (ICONDIRENTRY) - cur.push(w); // 6: width - cur.push(h); // 7: height - cur.push(0); // 8: colors, 0 -> true-color - cur.push(0); // 9: reserved - cur.push16le(hotx); // 10: hotspot x coordinate - cur.push16le(hoty); // 12: hotspot y coordinate - cur.push32le(IHDRsz + RGBsz + XORsz + ANDsz); - // 14: cursor data byte size - cur.push32le(22); // 18: offset of cursor data in the file - - - // Cursor #1 InfoHeader (ICONIMAGE/BITMAPINFO) - cur.push32le(IHDRsz); // 22: Infoheader size - cur.push32le(w); // 26: Cursor width - cur.push32le(h*2); // 30: XOR+AND height - cur.push16le(1); // 34: number of planes - cur.push16le(32); // 36: bits per pixel - cur.push32le(0); // 38: Type of compression - - cur.push32le(XORsz + ANDsz); // 43: Size of Image - // Gimp leaves this as 0 - - cur.push32le(0); // 46: reserved - cur.push32le(0); // 50: reserved - cur.push32le(0); // 54: reserved - cur.push32le(0); // 58: reserved - - // 62: color data (RGBQUAD icColors[]) - for (y = h-1; y >= 0; y -= 1) { - for (x = 0; x < w; x += 1) { - idx = y * Math.ceil(w / 8) + Math.floor(x/8); - alpha = (mask[idx] << (x % 8)) & 0x80 ? 255 : 0; - - if (cmap) { - idx = (w * y) + x; - rgb = cmap[pixels[idx]]; - cur.push(rgb[2]); // blue - cur.push(rgb[1]); // green - cur.push(rgb[0]); // red - cur.push(alpha); // alpha + this._scale = factor; + var x = canvas.width - (canvas.width * factor); + var y = canvas.height - (canvas.height * factor); + canvas.style[transform_prop] = 'scale(' + this._scale + ') translate(-' + x + 'px, -' + y + 'px)'; + }, + + _setFillColor: function (color) { + var bgr; + if (this._true_color) { + bgr = color; } else { - idx = ((w * y) + x) * 4; - cur.push(pixels[idx + 2]); // blue - cur.push(pixels[idx + 1]); // green - cur.push(pixels[idx ]); // red - cur.push(alpha); // alpha + bgr = this._colourMap[color[0]]; + } + + var newStyle = 'rgb(' + bgr[2] + ',' + bgr[1] + ',' + bgr[0] + ')'; + if (newStyle !== this._prevDrawStyle) { + this._drawCtx.fillStyle = newStyle; + this._prevDrawStyle = newStyle; + } + }, + + _rgbImageData: function (x, y, vx, vy, width, height, arr, offset) { + var img = this._drawCtx.createImageData(width, height); + var data = img.data; + for (var i = 0, j = offset; i < width * height * 4; i += 4, j += 3) { + data[i] = arr[j]; + data[i + 1] = arr[j + 1]; + data[i + 2] = arr[j + 2]; + data[i + 3] = 255; // Alpha + } + this._drawCtx.putImageData(img, x - vx, y - vy); + }, + + _bgrxImageData: function (x, y, vx, vy, width, height, arr, offset) { + var img = this._drawCtx.createImageData(width, height); + var data = img.data; + for (var i = 0, j = offset; i < width * height * 4; i += 4, j += 4) { + data[i] = arr[j + 2]; + data[i + 1] = arr[j + 1]; + data[i + 2] = arr[j]; + data[i + 3] = 255; // Alpha + } + this._drawCtx.putImageData(img, x - vx, y - vy); + }, + + _cmapImageData: function (x, y, vx, vy, width, height, arr, offset) { + var img = this._drawCtx.createImageData(width, height); + var data = img.data; + var cmap = this._colourMap; + for (var i = 0, j = offset; i < width * height * 4; i += 4, j++) { + var bgr = cmap[arr[j]]; + data[i] = bgr[2]; + data[i + 1] = bgr[1]; + data[i + 2] = bgr[0]; + data[i + 3] = 255; // Alpha + } + this._drawCtx.putImageData(img, x - vx, y - vy); + }, + + _scan_renderQ: function () { + var ready = true; + while (ready && this._renderQ.length > 0) { + var a = this._renderQ[0]; + switch (a.type) { + case 'copy': + this.copyImage(a.old_x, a.old_y, a.x, a.y, a.width, a.height); + break; + case 'fill': + this.fillRect(a.x, a.y, a.width, a.height, a.color); + break; + case 'blit': + this.blitImage(a.x, a.y, a.width, a.height, a.data, 0); + break; + case 'blitRgb': + this.blitRgbImage(a.x, a.y, a.width, a.height, a.data, 0); + break; + case 'img': + if (a.img.complete) { + this.drawImage(a.img, a.x, a.y); + } else { + // We need to wait for this image to 'load' + // to keep things in-order + ready = false; + } + break; + } + + if (ready) { + this._renderQ.shift(); + } } + + if (this._renderQ.length > 0) { + requestAnimFrame(this._scan_renderQ.bind(this)); + } + }, + }; + + Util.make_properties(Display, [ + ['target', 'wo', 'dom'], // Canvas element for rendering + ['context', 'ro', 'raw'], // Canvas 2D context for rendering (read-only) + ['logo', 'rw', 'raw'], // Logo to display when cleared: {"width": w, "height": h, "data": data} + ['true_color', 'rw', 'bool'], // Use true-color pixel data + ['colourMap', 'rw', 'arr'], // Colour map array (when not true-color) + ['scale', 'rw', 'float'], // Display area scale factor 0.0 - 1.0 + ['viewport', 'rw', 'bool'], // Use a viewport set with viewportChange() + ['width', 'rw', 'int'], // Display area width + ['height', 'rw', 'int'], // Display area height + + ['render_mode', 'ro', 'str'], // Canvas rendering mode (read-only) + + ['prefer_js', 'rw', 'str'], // Prefer Javascript over canvas methods + ['cursor_uri', 'rw', 'raw'] // Can we render cursor using data URI + ]); + + // Class Methods + Display.changeCursor = function (target, pixels, mask, hotx, hoty, w0, h0, cmap) { + var w = w0; + var h = h0; + if (h < w) { + h = w; // increase h to make it square + } else { + w = h; // increase w to make it square } - } - // XOR/bitmask data (BYTE icXOR[]) - // (ignored, just needs to be right size) - for (y = 0; y < h; y += 1) { - for (x = 0; x < Math.ceil(w / 8); x += 1) { - cur.push(0x00); + var cur = []; + + // Push multi-byte little-endian values + cur.push16le = function (num) { + this.push(num & 0xFF, (num >> 8) & 0xFF); + }; + cur.push32le = function (num) { + this.push(num & 0xFF, + (num >> 8) & 0xFF, + (num >> 16) & 0xFF, + (num >> 24) & 0xFF); + }; + + var IHDRsz = 40; + var RGBsz = w * h * 4; + var XORsz = Math.ceil((w * h) / 8.0); + var ANDsz = Math.ceil((w * h) / 8.0); + + cur.push16le(0); // 0: Reserved + cur.push16le(2); // 2: .CUR type + cur.push16le(1); // 4: Number of images, 1 for non-animated ico + + // Cursor #1 header (ICONDIRENTRY) + cur.push(w); // 6: width + cur.push(h); // 7: height + cur.push(0); // 8: colors, 0 -> true-color + cur.push(0); // 9: reserved + cur.push16le(hotx); // 10: hotspot x coordinate + cur.push16le(hoty); // 12: hotspot y coordinate + cur.push32le(IHDRsz + RGBsz + XORsz + ANDsz); + // 14: cursor data byte size + cur.push32le(22); // 18: offset of cursor data in the file + + // Cursor #1 InfoHeader (ICONIMAGE/BITMAPINFO) + cur.push32le(IHDRsz); // 22: InfoHeader size + cur.push32le(w); // 26: Cursor width + cur.push32le(h * 2); // 30: XOR+AND height + cur.push16le(1); // 34: number of planes + cur.push16le(32); // 36: bits per pixel + cur.push32le(0); // 38: Type of compression + + cur.push32le(XORsz + ANDsz); + // 42: Size of Image + cur.push32le(0); // 46: reserved + cur.push32le(0); // 50: reserved + cur.push32le(0); // 54: reserved + cur.push32le(0); // 58: reserved + + // 62: color data (RGBQUAD icColors[]) + var y, x; + for (y = h - 1; y >= 0; y--) { + for (x = 0; x < w; x++) { + if (x >= w0 || y >= h0) { + cur.push(0); // blue + cur.push(0); // green + cur.push(0); // red + cur.push(0); // alpha + } else { + var idx = y * Math.ceil(w0 / 8) + Math.floor(x / 8); + var alpha = (mask[idx] << (x % 8)) & 0x80 ? 255 : 0; + if (cmap) { + idx = (w0 * y) + x; + var rgb = cmap[pixels[idx]]; + cur.push(rgb[2]); // blue + cur.push(rgb[1]); // green + cur.push(rgb[0]); // red + cur.push(alpha); // alpha + } + } + } + } + + // XOR/bitmask data (BYTE icXOR[]) + // (ignored, just needs to be the right size) + for (y = 0; y < h; y++) { + for (x = 0; x < Math.ceil(w / 8); x++) { + cur.push(0); + } } - } - // AND/bitmask data (BYTE icAND[]) - // (ignored, just needs to be right size) - for (y = 0; y < h; y += 1) { - for (x = 0; x < Math.ceil(w / 8); x += 1) { - cur.push(0x00); + // AND/bitmask data (BYTE icAND[]) + // (ignored, just needs to be the right size) + for (y = 0; y < h; y++) { + for (x = 0; x < Math.ceil(w / 8); x++) { + cur.push(0); + } } - } - url = "data:image/x-icon;base64," + Base64.encode(cur); - target.style.cursor = "url(" + url + ") " + hotx + " " + hoty + ", default"; - //Util.Debug("<< changeCursor, cur.length: " + cur.length); -} + var url = 'data:image/x-icon;base64,' + Base64.encode(cur); + target.style.cursor = 'url(' + url + ')' + hotx + ' ' + hoty + ', default'; + }; +})(); diff --git a/webclients/novnc/include/input.js b/webclients/novnc/include/input.js index 9298dfe..5d9e209 100644 --- a/webclients/novnc/include/input.js +++ b/webclients/novnc/include/input.js @@ -1,1911 +1,388 @@ /* * noVNC: HTML5 VNC client - * Copyright (C) 2011 Joel Martin - * Licensed under LGPL-2 or any later version (see LICENSE.txt) + * Copyright (C) 2012 Joel Martin + * Copyright (C) 2013 Samuel Mannehed for Cendio AB + * Licensed under MPL 2.0 or any later version (see LICENSE.txt) */ -/*jslint browser: true, white: false, bitwise: false */ +/*jslint browser: true, white: false */ /*global window, Util */ +var Keyboard, Mouse; + +(function () { + "use strict"; + + // + // Keyboard event handler + // + + Keyboard = function (defaults) { + this._keyDownList = []; // List of depressed keys + // (even if they are happy) + + Util.set_defaults(this, defaults, { + 'target': document, + 'focused': true + }); + + // create the keyboard handler + this._handler = new KeyEventDecoder(kbdUtil.ModifierSync(), + VerifyCharModifier( /* jshint newcap: false */ + TrackKeyState( + EscapeModifiers(this._handleRfbEvent.bind(this)) + ) + ) + ); /* jshint newcap: true */ + + // keep these here so we can refer to them later + this._eventHandlers = { + 'keyup': this._handleKeyUp.bind(this), + 'keydown': this._handleKeyDown.bind(this), + 'keypress': this._handleKeyPress.bind(this), + 'blur': this._allKeysUp.bind(this) + }; + }; + + Keyboard.prototype = { + // private methods + + _handleRfbEvent: function (e) { + if (this._onKeyPress) { + Util.Debug("onKeyPress " + (e.type == 'keydown' ? "down" : "up") + + ", keysym: " + e.keysym.keysym + "(" + e.keysym.keyname + ")"); + this._onKeyPress(e.keysym.keysym, e.type == 'keydown'); + } + }, -// -// Keyboard event handler -// + _handleKeyDown: function (e) { + if (!this._focused) { return true; } -function Keyboard(defaults) { -"use strict"; + if (this._handler.keydown(e)) { + // Suppress bubbling/default actions + Util.stopEvent(e); + return false; + } else { + // Allow the event to bubble and become a keyPress event which + // will have the character code translated + return true; + } + }, -var that = {}, // Public API methods - conf = {}, // Configuration attributes + _handleKeyPress: function (e) { + if (!this._focused) { return true; } - keyDownList = []; // List of depressed keys - // (even if they are happy) + if (this._handler.keypress(e)) { + // Suppress bubbling/default actions + Util.stopEvent(e); + return false; + } else { + // Allow the event to bubble and become a keyPress event which + // will have the character code translated + return true; + } + }, -// Configuration attributes -Util.conf_defaults(conf, that, defaults, [ - ['target', 'wo', 'dom', document, 'DOM element that captures keyboard input'], - ['focused', 'rw', 'bool', true, 'Capture and send key events'], + _handleKeyUp: function (e) { + if (!this._focused) { return true; } - ['onKeyPress', 'rw', 'func', null, 'Handler for key press/release'] - ]); + if (this._handler.keyup(e)) { + // Suppress bubbling/default actions + Util.stopEvent(e); + return false; + } else { + // Allow the event to bubble and become a keyPress event which + // will have the character code translated + return true; + } + }, + _allKeysUp: function () { + Util.Debug(">> Keyboard.allKeysUp"); + this._handler.releaseAll(); + Util.Debug("<< Keyboard.allKeysUp"); + }, -// -// Private functions -// - -// From the event keyCode return the keysym value for keys that need -// to be suppressed otherwise they may trigger unintended browser -// actions -function getKeysymSpecial(evt) { - var keysym = null; - - switch ( evt.keyCode ) { - // These generate a keyDown and keyPress in Firefox and Opera - case 8 : keysym = 0xFF08; break; // BACKSPACE - case 13 : keysym = 0xFF0D; break; // ENTER - - // This generates a keyDown and keyPress in Opera - case 9 : keysym = 0xFF09; break; // TAB - default : break; - } - - if (evt.type === 'keydown') { - switch ( evt.keyCode ) { - case 27 : keysym = 0xFF1B; break; // ESCAPE - case 46 : keysym = 0xFFFF; break; // DELETE - - case 36 : keysym = 0xFF50; break; // HOME - case 35 : keysym = 0xFF57; break; // END - case 33 : keysym = 0xFF55; break; // PAGE_UP - case 34 : keysym = 0xFF56; break; // PAGE_DOWN - case 45 : keysym = 0xFF63; break; // INSERT - // '-' during keyPress - case 37 : keysym = 0xFF51; break; // LEFT - case 38 : keysym = 0xFF52; break; // UP - case 39 : keysym = 0xFF53; break; // RIGHT - case 40 : keysym = 0xFF54; break; // DOWN - case 16 : keysym = 0xFFE1; break; // SHIFT - case 17 : keysym = 0xFFE3; break; // CONTROL - //case 18 : keysym = 0xFFE7; break; // Left Meta (Mac Option) - case 18 : keysym = 0xFFE9; break; // Left ALT (Mac Command) - - case 112 : keysym = 0xFFBE; break; // F1 - case 113 : keysym = 0xFFBF; break; // F2 - case 114 : keysym = 0xFFC0; break; // F3 - case 115 : keysym = 0xFFC1; break; // F4 - case 116 : keysym = 0xFFC2; break; // F5 - case 117 : keysym = 0xFFC3; break; // F6 - case 118 : keysym = 0xFFC4; break; // F7 - case 119 : keysym = 0xFFC5; break; // F8 - case 120 : keysym = 0xFFC6; break; // F9 - case 121 : keysym = 0xFFC7; break; // F10 - case 122 : keysym = 0xFFC8; break; // F11 - case 123 : keysym = 0xFFC9; break; // F12 - - default : break; - } - } - - if ((!keysym) && (evt.ctrlKey || evt.altKey)) { - if ((typeof(evt.which) !== "undefined") && (evt.which > 0)) { - keysym = evt.which; - } else { - // IE9 always - // Firefox and Opera when ctrl/alt + special - Util.Warn("which not set, using keyCode"); - keysym = evt.keyCode; - } + // Public methods + + grab: function () { + //Util.Debug(">> Keyboard.grab"); + var c = this._target; + + Util.addEvent(c, 'keydown', this._eventHandlers.keydown); + Util.addEvent(c, 'keyup', this._eventHandlers.keyup); + Util.addEvent(c, 'keypress', this._eventHandlers.keypress); + + // Release (key up) if window loses focus + Util.addEvent(window, 'blur', this._eventHandlers.blur); + + //Util.Debug("<< Keyboard.grab"); + }, - /* Remap symbols */ - switch (keysym) { - case 186 : keysym = 59; break; // ; (IE) - case 187 : keysym = 61; break; // = (IE) - case 188 : keysym = 44; break; // , (Mozilla, IE) - case 109 : // - (Mozilla, Opera) - if (Util.Engine.gecko || Util.Engine.presto) { - keysym = 45; } - break; - case 189 : keysym = 45; break; // - (IE) - case 190 : keysym = 46; break; // . (Mozilla, IE) - case 191 : keysym = 47; break; // / (Mozilla, IE) - case 192 : keysym = 96; break; // ` (Mozilla, IE) - case 219 : keysym = 91; break; // [ (Mozilla, IE) - case 220 : keysym = 92; break; // \ (Mozilla, IE) - case 221 : keysym = 93; break; // ] (Mozilla, IE) - case 222 : keysym = 39; break; // ' (Mozilla, IE) + ungrab: function () { + //Util.Debug(">> Keyboard.ungrab"); + var c = this._target; + + Util.removeEvent(c, 'keydown', this._eventHandlers.keydown); + Util.removeEvent(c, 'keyup', this._eventHandlers.keyup); + Util.removeEvent(c, 'keypress', this._eventHandlers.keypress); + Util.removeEvent(window, 'blur', this._eventHandlers.blur); + + // Release (key up) all keys that are in a down state + this._allKeysUp(); + + //Util.Debug(">> Keyboard.ungrab"); + }, + + sync: function (e) { + this._handler.syncModifiers(e); } - - /* Remap shifted and unshifted keys */ - if (!!evt.shiftKey) { - switch (keysym) { - case 48 : keysym = 41 ; break; // ) (shifted 0) - case 49 : keysym = 33 ; break; // ! (shifted 1) - case 50 : keysym = 64 ; break; // @ (shifted 2) - case 51 : keysym = 35 ; break; // # (shifted 3) - case 52 : keysym = 36 ; break; // $ (shifted 4) - case 53 : keysym = 37 ; break; // % (shifted 5) - case 54 : keysym = 94 ; break; // ^ (shifted 6) - case 55 : keysym = 38 ; break; // & (shifted 7) - case 56 : keysym = 42 ; break; // * (shifted 8) - case 57 : keysym = 40 ; break; // ( (shifted 9) - - case 59 : keysym = 58 ; break; // : (shifted `) - case 61 : keysym = 43 ; break; // + (shifted ;) - case 44 : keysym = 60 ; break; // < (shifted ,) - case 45 : keysym = 95 ; break; // _ (shifted -) - case 46 : keysym = 62 ; break; // > (shifted .) - case 47 : keysym = 63 ; break; // ? (shifted /) - case 96 : keysym = 126; break; // ~ (shifted `) - case 91 : keysym = 123; break; // { (shifted [) - case 92 : keysym = 124; break; // | (shifted \) - case 93 : keysym = 125; break; // } (shifted ]) - case 39 : keysym = 34 ; break; // " (shifted ') + }; + + Util.make_properties(Keyboard, [ + ['target', 'wo', 'dom'], // DOM element that captures keyboard input + ['focused', 'rw', 'bool'], // Capture and send key events + + ['onKeyPress', 'rw', 'func'] // Handler for key press/release + ]); + + // + // Mouse event handler + // + + Mouse = function (defaults) { + this._mouseCaptured = false; + + this._doubleClickTimer = null; + this._lastTouchPos = null; + + // Configuration attributes + Util.set_defaults(this, defaults, { + 'target': document, + 'focused': true, + 'scale': 1.0, + 'touchButton': 1 + }); + + this._eventHandlers = { + 'mousedown': this._handleMouseDown.bind(this), + 'mouseup': this._handleMouseUp.bind(this), + 'mousemove': this._handleMouseMove.bind(this), + 'mousewheel': this._handleMouseWheel.bind(this), + 'mousedisable': this._handleMouseDisable.bind(this) + }; + }; + + Mouse.prototype = { + // private methods + _captureMouse: function () { + // capturing the mouse ensures we get the mouseup event + if (this._target.setCapture) { + this._target.setCapture(); } - } else if ((keysym >= 65) && (keysym <=90)) { - /* Remap unshifted A-Z */ - keysym += 32; - } else if (evt.keyLocation === 3) { - // numpad keys - switch (keysym) { - case 96 : keysym = 48; break; // 0 - case 97 : keysym = 49; break; // 1 - case 98 : keysym = 50; break; // 2 - case 99 : keysym = 51; break; // 3 - case 100: keysym = 52; break; // 4 - case 101: keysym = 53; break; // 5 - case 102: keysym = 54; break; // 6 - case 103: keysym = 55; break; // 7 - case 104: keysym = 56; break; // 8 - case 105: keysym = 57; break; // 9 - case 109: keysym = 45; break; // - - case 110: keysym = 46; break; // . - case 111: keysym = 47; break; // / + + // some browsers give us mouseup events regardless, + // so if we never captured the mouse, we can disregard the event + this._mouseCaptured = true; + }, + + _releaseMouse: function () { + if (this._target.releaseCapture) { + this._target.releaseCapture(); } - } - } - - return keysym; -} - -/* Translate DOM keyPress event to keysym value */ -function getKeysym(evt) { - var keysym, msg; - - if (typeof(evt.which) !== "undefined") { - // WebKit, Firefox, Opera - keysym = evt.which; - } else { - // IE9 - Util.Warn("which not set, using keyCode"); - keysym = evt.keyCode; - } - - if ((keysym > 255) && (keysym < 0xFF00)) { - msg = "Mapping character code " + keysym; - // Map Unicode outside Latin 1 to X11 keysyms - keysym = unicodeTable[keysym]; - if (typeof(keysym) === 'undefined') { - keysym = 0; - } - Util.Debug(msg + " to " + keysym); - } - - return keysym; -} - -function show_keyDownList(kind) { - var c; - var msg = "keyDownList (" + kind + "):\n"; - for (c = 0; c < keyDownList.length; c++) { - msg = msg + " " + c + " - keyCode: " + keyDownList[c].keyCode + - " - which: " + keyDownList[c].which + "\n"; - } - Util.Debug(msg); -} - -function copyKeyEvent(evt) { - var members = ['type', 'keyCode', 'charCode', 'which', - 'altKey', 'ctrlKey', 'shiftKey', - 'keyLocation', 'keyIdentifier'], i, obj = {}; - for (i = 0; i < members.length; i++) { - if (typeof(evt[members[i]]) !== "undefined") { - obj[members[i]] = evt[members[i]]; - } - } - return obj; -} - -function pushKeyEvent(fevt) { - keyDownList.push(fevt); -} - -function getKeyEvent(keyCode, pop) { - var i, fevt = null; - for (i = keyDownList.length-1; i >= 0; i--) { - if (keyDownList[i].keyCode === keyCode) { - if ((typeof(pop) !== "undefined") && (pop)) { - fevt = keyDownList.splice(i, 1)[0]; + this._mouseCaptured = false; + }, + + _resetDoubleClickTimer: function () { + this._doubleClickTimer = null; + }, + + _handleMouseButton: function (e, down) { + if (!this._focused) { return true; } + + if (this._notify) { + this._notify(e); + } + + var evt = (e ? e : window.event); + var pos = Util.getEventPosition(e, this._target, this._scale); + + var bmask; + if (e.touches || e.changedTouches) { + // Touch device + + // When two touches occur within 500 ms of each other and are + // closer than 20 pixels together a double click is triggered. + if (down == 1) { + if (this._doubleClickTimer === null) { + this._lastTouchPos = pos; + } else { + clearTimeout(this._doubleClickTimer); + + // When the distance between the two touches is small enough + // force the position of the latter touch to the position of + // the first. + + var xs = this._lastTouchPos.x - pos.x; + var ys = this._lastTouchPos.y - pos.y; + var d = Math.sqrt((xs * xs) + (ys * ys)); + + // The goal is to trigger on a certain physical width, the + // devicePixelRatio brings us a bit closer but is not optimal. + if (d < 20 * window.devicePixelRatio) { + pos = this._lastTouchPos; + } + } + this._doubleClickTimer = setTimeout(this._resetDoubleClickTimer.bind(this), 500); + } + bmask = this._touchButton; + // If bmask is set + } else if (evt.which) { + /* everything except IE */ + bmask = 1 << evt.button; } else { - fevt = keyDownList[i]; + /* IE including 9 */ + bmask = (evt.button & 0x1) + // Left + (evt.button & 0x2) * 2 + // Right + (evt.button & 0x4) / 2; // Middle } - break; - } - } - return fevt; -} - -function ignoreKeyEvent(evt) { - // Blarg. Some keys have a different keyCode on keyDown vs keyUp - if (evt.keyCode === 229) { - // French AZERTY keyboard dead key. - // Lame thing is that the respective keyUp is 219 so we can't - // properly ignore the keyUp event - return true; - } - return false; -} - - -// -// Key Event Handling: -// -// There are several challenges when dealing with key events: -// - The meaning and use of keyCode, charCode and which depends on -// both the browser and the event type (keyDown/Up vs keyPress). -// - We cannot automatically determine the keyboard layout -// - The keyDown and keyUp events have a keyCode value that has not -// been translated by modifier keys. -// - The keyPress event has a translated (for layout and modifiers) -// character code but the attribute containing it differs. keyCode -// contains the translated value in WebKit (Chrome/Safari), Opera -// 11 and IE9. charCode contains the value in WebKit and Firefox. -// The which attribute contains the value on WebKit, Firefox and -// Opera 11. -// - The keyDown/Up keyCode value indicates (sort of) the physical -// key was pressed but only for standard US layout. On a US -// keyboard, the '-' and '_' characters are on the same key and -// generate a keyCode value of 189. But on an AZERTY keyboard even -// though they are different physical keys they both still -// generate a keyCode of 189! -// - To prevent a key event from propagating to the browser and -// causing unwanted default actions (such as closing a tab, -// opening a menu, shifting focus, etc) we must suppress this -// event in both keyDown and keyPress because not all key strokes -// generate on a keyPress event. Also, in WebKit and IE9 -// suppressing the keyDown prevents a keyPress but other browsers -// still generated a keyPress even if keyDown is suppressed. -// -// For safe key events, we wait until the keyPress event before -// reporting a key down event. For unsafe key events, we report a key -// down event when the keyDown event fires and we suppress any further -// actions (including keyPress). -// -// In order to report a key up event that matches what we reported -// for the key down event, we keep a list of keys that are currently -// down. When the keyDown event happens, we add the key event to the -// list. If it is a safe key event, then we update the which attribute -// in the most recent item on the list when we received a keyPress -// event (keyPress should immediately follow keyDown). When we -// received a keyUp event we search for the event on the list with -// a matching keyCode and we report the character code using the value -// in the 'which' attribute that was stored with that key. -// - -function onKeyDown(e) { - if (! conf.focused) { - return true; - } - var fevt = null, evt = (e ? e : window.event), - keysym = null, suppress = false; - //Util.Debug("onKeyDown kC:" + evt.keyCode + " cC:" + evt.charCode + " w:" + evt.which); - - fevt = copyKeyEvent(evt); - - keysym = getKeysymSpecial(evt); - // Save keysym decoding for use in keyUp - fevt.keysym = keysym; - if (keysym) { - // If it is a key or key combination that might trigger - // browser behaviors or it has no corresponding keyPress - // event, then send it immediately - if (conf.onKeyPress && !ignoreKeyEvent(evt)) { - Util.Debug("onKeyPress down, keysym: " + keysym + - " (onKeyDown key: " + evt.keyCode + - ", which: " + evt.which + ")"); - conf.onKeyPress(keysym, 1, evt); - } - suppress = true; - } - - if (! ignoreKeyEvent(evt)) { - // Add it to the list of depressed keys - pushKeyEvent(fevt); - //show_keyDownList('down'); - } - - if (suppress) { - // Suppress bubbling/default actions - Util.stopEvent(e); - return false; - } else { - // Allow the event to bubble and become a keyPress event which - // will have the character code translated - return true; - } -} - -function onKeyPress(e) { - if (! conf.focused) { - return true; - } - var evt = (e ? e : window.event), - kdlen = keyDownList.length, keysym = null; - //Util.Debug("onKeyPress kC:" + evt.keyCode + " cC:" + evt.charCode + " w:" + evt.which); - - if (((evt.which !== "undefined") && (evt.which === 0)) || - (getKeysymSpecial(evt))) { - // Firefox and Opera generate a keyPress event even if keyDown - // is suppressed. But the keys we want to suppress will have - // either: - // - the which attribute set to 0 - // - getKeysymSpecial() will identify it - Util.Debug("Ignoring special key in keyPress"); - Util.stopEvent(e); - return false; - } - - keysym = getKeysym(evt); - - // Modify the the which attribute in the depressed keys list so - // that the keyUp event will be able to have the character code - // translation available. - if (kdlen > 0) { - keyDownList[kdlen-1].keysym = keysym; - } else { - Util.Warn("keyDownList empty when keyPress triggered"); - } - - //show_keyDownList('press'); - - // Send the translated keysym - if (conf.onKeyPress && (keysym > 0)) { - Util.Debug("onKeyPress down, keysym: " + keysym + - " (onKeyPress key: " + evt.keyCode + - ", which: " + evt.which + ")"); - conf.onKeyPress(keysym, 1, evt); - } - - // Stop keypress events just in case - Util.stopEvent(e); - return false; -} - -function onKeyUp(e) { - if (! conf.focused) { - return true; - } - var fevt = null, evt = (e ? e : window.event), keysym; - //Util.Debug("onKeyUp kC:" + evt.keyCode + " cC:" + evt.charCode + " w:" + evt.which); - - fevt = getKeyEvent(evt.keyCode, true); - - if (fevt) { - keysym = fevt.keysym; - } else { - Util.Warn("Key event (keyCode = " + evt.keyCode + - ") not found on keyDownList"); - keysym = 0; - } - - //show_keyDownList('up'); - - if (conf.onKeyPress && (keysym > 0)) { - //Util.Debug("keyPress up, keysym: " + keysym + - // " (key: " + evt.keyCode + ", which: " + evt.which + ")"); - Util.Debug("onKeyPress up, keysym: " + keysym + - " (onKeyPress key: " + evt.keyCode + - ", which: " + evt.which + ")"); - conf.onKeyPress(keysym, 0, evt); - } - Util.stopEvent(e); - return false; -} - -function allKeysUp() { - Util.Debug(">> Keyboard.allKeysUp"); - if (keyDownList.length > 0) { - Util.Info("Releasing pressed/down keys"); - } - var i, keysym, fevt = null; - for (i = keyDownList.length-1; i >= 0; i--) { - fevt = keyDownList.splice(i, 1)[0]; - keysym = fevt.keysym; - if (conf.onKeyPress && (keysym > 0)) { - Util.Debug("allKeysUp, keysym: " + keysym + - " (keyCode: " + fevt.keyCode + - ", which: " + fevt.which + ")"); - conf.onKeyPress(keysym, 0, fevt); - } - } - Util.Debug("<< Keyboard.allKeysUp"); - return; -} -// -// Public API interface functions -// + if (this._onMouseButton) { + Util.Debug("onMouseButton " + (down ? "down" : "up") + + ", x: " + pos.x + ", y: " + pos.y + ", bmask: " + bmask); + this._onMouseButton(pos.x, pos.y, down, bmask); + } + Util.stopEvent(e); + return false; + }, + + _handleMouseDown: function (e) { + this._captureMouse(); + this._handleMouseButton(e, 1); + }, -that.grab = function() { - //Util.Debug(">> Keyboard.grab"); - var c = conf.target; + _handleMouseUp: function (e) { + if (!this._mouseCaptured) { return; } - Util.addEvent(c, 'keydown', onKeyDown); - Util.addEvent(c, 'keyup', onKeyUp); - Util.addEvent(c, 'keypress', onKeyPress); + this._handleMouseButton(e, 0); + this._releaseMouse(); + }, - // Release (key up) if window loses focus - Util.addEvent(window, 'blur', allKeysUp); + _handleMouseWheel: function (e) { + if (!this._focused) { return true; } - //Util.Debug("<< Keyboard.grab"); -}; + if (this._notify) { + this._notify(e); + } -that.ungrab = function() { - //Util.Debug(">> Keyboard.ungrab"); - var c = conf.target; + var evt = (e ? e : window.event); + var pos = Util.getEventPosition(e, this._target, this._scale); + var wheelData = evt.detail ? evt.detail * -1 : evt.wheelDelta / 40; + var bmask; + if (wheelData > 0) { + bmask = 1 << 3; + } else { + bmask = 1 << 4; + } - Util.removeEvent(c, 'keydown', onKeyDown); - Util.removeEvent(c, 'keyup', onKeyUp); - Util.removeEvent(c, 'keypress', onKeyPress); - Util.removeEvent(window, 'blur', allKeysUp); + if (this._onMouseButton) { + this._onMouseButton(pos.x, pos.y, 1, bmask); + this._onMouseButton(pos.x, pos.y, 0, bmask); + } + Util.stopEvent(e); + return false; + }, - // Release (key up) all keys that are in a down state - allKeysUp(); + _handleMouseMove: function (e) { + if (! this._focused) { return true; } - //Util.Debug(">> Keyboard.ungrab"); -}; + if (this._notify) { + this._notify(e); + } -return that; // Return the public API interface + var evt = (e ? e : window.event); + var pos = Util.getEventPosition(e, this._target, this._scale); + if (this._onMouseMove) { + this._onMouseMove(pos.x, pos.y); + } + Util.stopEvent(e); + return false; + }, + + _handleMouseDisable: function (e) { + if (!this._focused) { return true; } + + var evt = (e ? e : window.event); + var pos = Util.getEventPosition(e, this._target, this._scale); + + /* Stop propagation if inside canvas area */ + if ((pos.realx >= 0) && (pos.realy >= 0) && + (pos.realx < this._target.offsetWidth) && + (pos.realy < this._target.offsetHeight)) { + //Util.Debug("mouse event disabled"); + Util.stopEvent(e); + return false; + } -} // End of Keyboard() + return true; + }, -// -// Mouse event handler -// + // Public methods + grab: function () { + var c = this._target; -function Mouse(defaults) { -"use strict"; + if ('ontouchstart' in document.documentElement) { + Util.addEvent(c, 'touchstart', this._eventHandlers.mousedown); + Util.addEvent(window, 'touchend', this._eventHandlers.mouseup); + Util.addEvent(c, 'touchend', this._eventHandlers.mouseup); + Util.addEvent(c, 'touchmove', this._eventHandlers.mousemove); + } else { + Util.addEvent(c, 'mousedown', this._eventHandlers.mousedown); + Util.addEvent(window, 'mouseup', this._eventHandlers.mouseup); + Util.addEvent(c, 'mouseup', this._eventHandlers.mouseup); + Util.addEvent(c, 'mousemove', this._eventHandlers.mousemove); + Util.addEvent(c, (Util.Engine.gecko) ? 'DOMMouseScroll' : 'mousewheel', + this._eventHandlers.mousewheel); + } -var that = {}, // Public API methods - conf = {}; // Configuration attributes + /* Work around right and middle click browser behaviors */ + Util.addEvent(document, 'click', this._eventHandlers.mousedisable); + Util.addEvent(document.body, 'contextmenu', this._eventHandlers.mousedisable); + }, -// Configuration attributes -Util.conf_defaults(conf, that, defaults, [ - ['target', 'ro', 'dom', document, 'DOM element that captures mouse input'], - ['focused', 'rw', 'bool', true, 'Capture and send mouse clicks/movement'], - ['scale', 'rw', 'float', 1.0, 'Viewport scale factor 0.0 - 1.0'], + ungrab: function () { + var c = this._target; - ['onMouseButton', 'rw', 'func', null, 'Handler for mouse button click/release'], - ['onMouseMove', 'rw', 'func', null, 'Handler for mouse movement'], - ['touchButton', 'rw', 'int', 1, 'Button mask (1, 2, 4) for touch devices (0 means ignore clicks)'] - ]); + if ('ontouchstart' in document.documentElement) { + Util.removeEvent(c, 'touchstart', this._eventHandlers.mousedown); + Util.removeEvent(window, 'touchend', this._eventHandlers.mouseup); + Util.removeEvent(c, 'touchend', this._eventHandlers.mouseup); + Util.removeEvent(c, 'touchmove', this._eventHandlers.mousemove); + } else { + Util.removeEvent(c, 'mousedown', this._eventHandlers.mousedown); + Util.removeEvent(window, 'mouseup', this._eventHandlers.mouseup); + Util.removeEvent(c, 'mouseup', this._eventHandlers.mouseup); + Util.removeEvent(c, 'mousemove', this._eventHandlers.mousemove); + Util.removeEvent(c, (Util.Engine.gecko) ? 'DOMMouseScroll' : 'mousewheel', + this._eventHandlers.mousewheel); + } + /* Work around right and middle click browser behaviors */ + Util.removeEvent(document, 'click', this._eventHandlers.mousedisable); + Util.removeEvent(document.body, 'contextmenu', this._eventHandlers.mousedisable); -// -// Private functions -// - -function onMouseButton(e, down) { - var evt, pos, bmask; - if (! conf.focused) { - return true; - } - evt = (e ? e : window.event); - pos = Util.getEventPosition(e, conf.target, conf.scale); - if (e.touches || e.changedTouches) { - // Touch device - bmask = conf.touchButton; - // If bmask is set - } else if (evt.which) { - /* everything except IE */ - bmask = 1 << evt.button; - } else { - /* IE including 9 */ - bmask = (evt.button & 0x1) + // Left - (evt.button & 0x2) * 2 + // Right - (evt.button & 0x4) / 2; // Middle - } - //Util.Debug("mouse " + pos.x + "," + pos.y + " down: " + down + - // " bmask: " + bmask + "(evt.button: " + evt.button + ")"); - if (bmask > 0 && conf.onMouseButton) { - Util.Debug("onMouseButton " + (down ? "down" : "up") + - ", x: " + pos.x + ", y: " + pos.y + ", bmask: " + bmask); - conf.onMouseButton(pos.x, pos.y, down, bmask); - } - Util.stopEvent(e); - return false; -} - -function onMouseDown(e) { - onMouseButton(e, 1); -} - -function onMouseUp(e) { - onMouseButton(e, 0); -} - -function onMouseWheel(e) { - var evt, pos, bmask, wheelData; - if (! conf.focused) { - return true; - } - evt = (e ? e : window.event); - pos = Util.getEventPosition(e, conf.target, conf.scale); - wheelData = evt.detail ? evt.detail * -1 : evt.wheelDelta / 40; - if (wheelData > 0) { - bmask = 1 << 3; - } else { - bmask = 1 << 4; - } - //Util.Debug('mouse scroll by ' + wheelData + ':' + pos.x + "," + pos.y); - if (conf.onMouseButton) { - conf.onMouseButton(pos.x, pos.y, 1, bmask); - conf.onMouseButton(pos.x, pos.y, 0, bmask); - } - Util.stopEvent(e); - return false; -} - -function onMouseMove(e) { - var evt, pos; - if (! conf.focused) { - return true; - } - evt = (e ? e : window.event); - pos = Util.getEventPosition(e, conf.target, conf.scale); - //Util.Debug('mouse ' + evt.which + '/' + evt.button + ' up:' + pos.x + "," + pos.y); - if (conf.onMouseMove) { - conf.onMouseMove(pos.x, pos.y); - } - Util.stopEvent(e); - return false; -} - -function onMouseDisable(e) { - var evt, pos; - if (! conf.focused) { - return true; - } - evt = (e ? e : window.event); - pos = Util.getEventPosition(e, conf.target, conf.scale); - /* Stop propagation if inside canvas area */ - if ((pos.x >= 0) && (pos.y >= 0) && - (pos.x < conf.target.offsetWidth) && - (pos.y < conf.target.offsetHeight)) { - //Util.Debug("mouse event disabled"); - Util.stopEvent(e); - return false; - } - //Util.Debug("mouse event not disabled"); - return true; -} - -// -// Public API interface functions -// - -that.grab = function() { - //Util.Debug(">> Mouse.grab"); - var c = conf.target; - - if ('ontouchstart' in document.documentElement) { - Util.addEvent(c, 'touchstart', onMouseDown); - Util.addEvent(c, 'touchend', onMouseUp); - Util.addEvent(c, 'touchmove', onMouseMove); - } else { - Util.addEvent(c, 'mousedown', onMouseDown); - Util.addEvent(c, 'mouseup', onMouseUp); - Util.addEvent(c, 'mousemove', onMouseMove); - Util.addEvent(c, (Util.Engine.gecko) ? 'DOMMouseScroll' : 'mousewheel', - onMouseWheel); - } - - /* Work around right and middle click browser behaviors */ - Util.addEvent(document, 'click', onMouseDisable); - Util.addEvent(document.body, 'contextmenu', onMouseDisable); - - //Util.Debug("<< Mouse.grab"); -}; - -that.ungrab = function() { - //Util.Debug(">> Mouse.ungrab"); - var c = conf.target; - - if ('ontouchstart' in document.documentElement) { - Util.removeEvent(c, 'touchstart', onMouseDown); - Util.removeEvent(c, 'touchend', onMouseUp); - Util.removeEvent(c, 'touchmove', onMouseMove); - } else { - Util.removeEvent(c, 'mousedown', onMouseDown); - Util.removeEvent(c, 'mouseup', onMouseUp); - Util.removeEvent(c, 'mousemove', onMouseMove); - Util.removeEvent(c, (Util.Engine.gecko) ? 'DOMMouseScroll' : 'mousewheel', - onMouseWheel); - } - - /* Work around right and middle click browser behaviors */ - Util.removeEvent(document, 'click', onMouseDisable); - Util.removeEvent(document.body, 'contextmenu', onMouseDisable); - - //Util.Debug(">> Mouse.ungrab"); -}; - -return that; // Return the public API interface - -} // End of Mouse() + } + }; + Util.make_properties(Mouse, [ + ['target', 'ro', 'dom'], // DOM element that captures mouse input + ['notify', 'ro', 'func'], // Function to call to notify whenever a mouse event is received + ['focused', 'rw', 'bool'], // Capture and send mouse clicks/movement + ['scale', 'rw', 'float'], // Viewport scale factor 0.0 - 1.0 -/* - * Browser keypress to X11 keysym for Unicode characters > U+00FF - */ -unicodeTable = { - 0x0104 : 0x01a1, - 0x02D8 : 0x01a2, - 0x0141 : 0x01a3, - 0x013D : 0x01a5, - 0x015A : 0x01a6, - 0x0160 : 0x01a9, - 0x015E : 0x01aa, - 0x0164 : 0x01ab, - 0x0179 : 0x01ac, - 0x017D : 0x01ae, - 0x017B : 0x01af, - 0x0105 : 0x01b1, - 0x02DB : 0x01b2, - 0x0142 : 0x01b3, - 0x013E : 0x01b5, - 0x015B : 0x01b6, - 0x02C7 : 0x01b7, - 0x0161 : 0x01b9, - 0x015F : 0x01ba, - 0x0165 : 0x01bb, - 0x017A : 0x01bc, - 0x02DD : 0x01bd, - 0x017E : 0x01be, - 0x017C : 0x01bf, - 0x0154 : 0x01c0, - 0x0102 : 0x01c3, - 0x0139 : 0x01c5, - 0x0106 : 0x01c6, - 0x010C : 0x01c8, - 0x0118 : 0x01ca, - 0x011A : 0x01cc, - 0x010E : 0x01cf, - 0x0110 : 0x01d0, - 0x0143 : 0x01d1, - 0x0147 : 0x01d2, - 0x0150 : 0x01d5, - 0x0158 : 0x01d8, - 0x016E : 0x01d9, - 0x0170 : 0x01db, - 0x0162 : 0x01de, - 0x0155 : 0x01e0, - 0x0103 : 0x01e3, - 0x013A : 0x01e5, - 0x0107 : 0x01e6, - 0x010D : 0x01e8, - 0x0119 : 0x01ea, - 0x011B : 0x01ec, - 0x010F : 0x01ef, - 0x0111 : 0x01f0, - 0x0144 : 0x01f1, - 0x0148 : 0x01f2, - 0x0151 : 0x01f5, - 0x0171 : 0x01fb, - 0x0159 : 0x01f8, - 0x016F : 0x01f9, - 0x0163 : 0x01fe, - 0x02D9 : 0x01ff, - 0x0126 : 0x02a1, - 0x0124 : 0x02a6, - 0x0130 : 0x02a9, - 0x011E : 0x02ab, - 0x0134 : 0x02ac, - 0x0127 : 0x02b1, - 0x0125 : 0x02b6, - 0x0131 : 0x02b9, - 0x011F : 0x02bb, - 0x0135 : 0x02bc, - 0x010A : 0x02c5, - 0x0108 : 0x02c6, - 0x0120 : 0x02d5, - 0x011C : 0x02d8, - 0x016C : 0x02dd, - 0x015C : 0x02de, - 0x010B : 0x02e5, - 0x0109 : 0x02e6, - 0x0121 : 0x02f5, - 0x011D : 0x02f8, - 0x016D : 0x02fd, - 0x015D : 0x02fe, - 0x0138 : 0x03a2, - 0x0156 : 0x03a3, - 0x0128 : 0x03a5, - 0x013B : 0x03a6, - 0x0112 : 0x03aa, - 0x0122 : 0x03ab, - 0x0166 : 0x03ac, - 0x0157 : 0x03b3, - 0x0129 : 0x03b5, - 0x013C : 0x03b6, - 0x0113 : 0x03ba, - 0x0123 : 0x03bb, - 0x0167 : 0x03bc, - 0x014A : 0x03bd, - 0x014B : 0x03bf, - 0x0100 : 0x03c0, - 0x012E : 0x03c7, - 0x0116 : 0x03cc, - 0x012A : 0x03cf, - 0x0145 : 0x03d1, - 0x014C : 0x03d2, - 0x0136 : 0x03d3, - 0x0172 : 0x03d9, - 0x0168 : 0x03dd, - 0x016A : 0x03de, - 0x0101 : 0x03e0, - 0x012F : 0x03e7, - 0x0117 : 0x03ec, - 0x012B : 0x03ef, - 0x0146 : 0x03f1, - 0x014D : 0x03f2, - 0x0137 : 0x03f3, - 0x0173 : 0x03f9, - 0x0169 : 0x03fd, - 0x016B : 0x03fe, - 0x1E02 : 0x1001e02, - 0x1E03 : 0x1001e03, - 0x1E0A : 0x1001e0a, - 0x1E80 : 0x1001e80, - 0x1E82 : 0x1001e82, - 0x1E0B : 0x1001e0b, - 0x1EF2 : 0x1001ef2, - 0x1E1E : 0x1001e1e, - 0x1E1F : 0x1001e1f, - 0x1E40 : 0x1001e40, - 0x1E41 : 0x1001e41, - 0x1E56 : 0x1001e56, - 0x1E81 : 0x1001e81, - 0x1E57 : 0x1001e57, - 0x1E83 : 0x1001e83, - 0x1E60 : 0x1001e60, - 0x1EF3 : 0x1001ef3, - 0x1E84 : 0x1001e84, - 0x1E85 : 0x1001e85, - 0x1E61 : 0x1001e61, - 0x0174 : 0x1000174, - 0x1E6A : 0x1001e6a, - 0x0176 : 0x1000176, - 0x0175 : 0x1000175, - 0x1E6B : 0x1001e6b, - 0x0177 : 0x1000177, - 0x0152 : 0x13bc, - 0x0153 : 0x13bd, - 0x0178 : 0x13be, - 0x203E : 0x047e, - 0x3002 : 0x04a1, - 0x300C : 0x04a2, - 0x300D : 0x04a3, - 0x3001 : 0x04a4, - 0x30FB : 0x04a5, - 0x30F2 : 0x04a6, - 0x30A1 : 0x04a7, - 0x30A3 : 0x04a8, - 0x30A5 : 0x04a9, - 0x30A7 : 0x04aa, - 0x30A9 : 0x04ab, - 0x30E3 : 0x04ac, - 0x30E5 : 0x04ad, - 0x30E7 : 0x04ae, - 0x30C3 : 0x04af, - 0x30FC : 0x04b0, - 0x30A2 : 0x04b1, - 0x30A4 : 0x04b2, - 0x30A6 : 0x04b3, - 0x30A8 : 0x04b4, - 0x30AA : 0x04b5, - 0x30AB : 0x04b6, - 0x30AD : 0x04b7, - 0x30AF : 0x04b8, - 0x30B1 : 0x04b9, - 0x30B3 : 0x04ba, - 0x30B5 : 0x04bb, - 0x30B7 : 0x04bc, - 0x30B9 : 0x04bd, - 0x30BB : 0x04be, - 0x30BD : 0x04bf, - 0x30BF : 0x04c0, - 0x30C1 : 0x04c1, - 0x30C4 : 0x04c2, - 0x30C6 : 0x04c3, - 0x30C8 : 0x04c4, - 0x30CA : 0x04c5, - 0x30CB : 0x04c6, - 0x30CC : 0x04c7, - 0x30CD : 0x04c8, - 0x30CE : 0x04c9, - 0x30CF : 0x04ca, - 0x30D2 : 0x04cb, - 0x30D5 : 0x04cc, - 0x30D8 : 0x04cd, - 0x30DB : 0x04ce, - 0x30DE : 0x04cf, - 0x30DF : 0x04d0, - 0x30E0 : 0x04d1, - 0x30E1 : 0x04d2, - 0x30E2 : 0x04d3, - 0x30E4 : 0x04d4, - 0x30E6 : 0x04d5, - 0x30E8 : 0x04d6, - 0x30E9 : 0x04d7, - 0x30EA : 0x04d8, - 0x30EB : 0x04d9, - 0x30EC : 0x04da, - 0x30ED : 0x04db, - 0x30EF : 0x04dc, - 0x30F3 : 0x04dd, - 0x309B : 0x04de, - 0x309C : 0x04df, - 0x06F0 : 0x10006f0, - 0x06F1 : 0x10006f1, - 0x06F2 : 0x10006f2, - 0x06F3 : 0x10006f3, - 0x06F4 : 0x10006f4, - 0x06F5 : 0x10006f5, - 0x06F6 : 0x10006f6, - 0x06F7 : 0x10006f7, - 0x06F8 : 0x10006f8, - 0x06F9 : 0x10006f9, - 0x066A : 0x100066a, - 0x0670 : 0x1000670, - 0x0679 : 0x1000679, - 0x067E : 0x100067e, - 0x0686 : 0x1000686, - 0x0688 : 0x1000688, - 0x0691 : 0x1000691, - 0x060C : 0x05ac, - 0x06D4 : 0x10006d4, - 0x0660 : 0x1000660, - 0x0661 : 0x1000661, - 0x0662 : 0x1000662, - 0x0663 : 0x1000663, - 0x0664 : 0x1000664, - 0x0665 : 0x1000665, - 0x0666 : 0x1000666, - 0x0667 : 0x1000667, - 0x0668 : 0x1000668, - 0x0669 : 0x1000669, - 0x061B : 0x05bb, - 0x061F : 0x05bf, - 0x0621 : 0x05c1, - 0x0622 : 0x05c2, - 0x0623 : 0x05c3, - 0x0624 : 0x05c4, - 0x0625 : 0x05c5, - 0x0626 : 0x05c6, - 0x0627 : 0x05c7, - 0x0628 : 0x05c8, - 0x0629 : 0x05c9, - 0x062A : 0x05ca, - 0x062B : 0x05cb, - 0x062C : 0x05cc, - 0x062D : 0x05cd, - 0x062E : 0x05ce, - 0x062F : 0x05cf, - 0x0630 : 0x05d0, - 0x0631 : 0x05d1, - 0x0632 : 0x05d2, - 0x0633 : 0x05d3, - 0x0634 : 0x05d4, - 0x0635 : 0x05d5, - 0x0636 : 0x05d6, - 0x0637 : 0x05d7, - 0x0638 : 0x05d8, - 0x0639 : 0x05d9, - 0x063A : 0x05da, - 0x0640 : 0x05e0, - 0x0641 : 0x05e1, - 0x0642 : 0x05e2, - 0x0643 : 0x05e3, - 0x0644 : 0x05e4, - 0x0645 : 0x05e5, - 0x0646 : 0x05e6, - 0x0647 : 0x05e7, - 0x0648 : 0x05e8, - 0x0649 : 0x05e9, - 0x064A : 0x05ea, - 0x064B : 0x05eb, - 0x064C : 0x05ec, - 0x064D : 0x05ed, - 0x064E : 0x05ee, - 0x064F : 0x05ef, - 0x0650 : 0x05f0, - 0x0651 : 0x05f1, - 0x0652 : 0x05f2, - 0x0653 : 0x1000653, - 0x0654 : 0x1000654, - 0x0655 : 0x1000655, - 0x0698 : 0x1000698, - 0x06A4 : 0x10006a4, - 0x06A9 : 0x10006a9, - 0x06AF : 0x10006af, - 0x06BA : 0x10006ba, - 0x06BE : 0x10006be, - 0x06CC : 0x10006cc, - 0x06D2 : 0x10006d2, - 0x06C1 : 0x10006c1, - 0x0492 : 0x1000492, - 0x0493 : 0x1000493, - 0x0496 : 0x1000496, - 0x0497 : 0x1000497, - 0x049A : 0x100049a, - 0x049B : 0x100049b, - 0x049C : 0x100049c, - 0x049D : 0x100049d, - 0x04A2 : 0x10004a2, - 0x04A3 : 0x10004a3, - 0x04AE : 0x10004ae, - 0x04AF : 0x10004af, - 0x04B0 : 0x10004b0, - 0x04B1 : 0x10004b1, - 0x04B2 : 0x10004b2, - 0x04B3 : 0x10004b3, - 0x04B6 : 0x10004b6, - 0x04B7 : 0x10004b7, - 0x04B8 : 0x10004b8, - 0x04B9 : 0x10004b9, - 0x04BA : 0x10004ba, - 0x04BB : 0x10004bb, - 0x04D8 : 0x10004d8, - 0x04D9 : 0x10004d9, - 0x04E2 : 0x10004e2, - 0x04E3 : 0x10004e3, - 0x04E8 : 0x10004e8, - 0x04E9 : 0x10004e9, - 0x04EE : 0x10004ee, - 0x04EF : 0x10004ef, - 0x0452 : 0x06a1, - 0x0453 : 0x06a2, - 0x0451 : 0x06a3, - 0x0454 : 0x06a4, - 0x0455 : 0x06a5, - 0x0456 : 0x06a6, - 0x0457 : 0x06a7, - 0x0458 : 0x06a8, - 0x0459 : 0x06a9, - 0x045A : 0x06aa, - 0x045B : 0x06ab, - 0x045C : 0x06ac, - 0x0491 : 0x06ad, - 0x045E : 0x06ae, - 0x045F : 0x06af, - 0x2116 : 0x06b0, - 0x0402 : 0x06b1, - 0x0403 : 0x06b2, - 0x0401 : 0x06b3, - 0x0404 : 0x06b4, - 0x0405 : 0x06b5, - 0x0406 : 0x06b6, - 0x0407 : 0x06b7, - 0x0408 : 0x06b8, - 0x0409 : 0x06b9, - 0x040A : 0x06ba, - 0x040B : 0x06bb, - 0x040C : 0x06bc, - 0x0490 : 0x06bd, - 0x040E : 0x06be, - 0x040F : 0x06bf, - 0x044E : 0x06c0, - 0x0430 : 0x06c1, - 0x0431 : 0x06c2, - 0x0446 : 0x06c3, - 0x0434 : 0x06c4, - 0x0435 : 0x06c5, - 0x0444 : 0x06c6, - 0x0433 : 0x06c7, - 0x0445 : 0x06c8, - 0x0438 : 0x06c9, - 0x0439 : 0x06ca, - 0x043A : 0x06cb, - 0x043B : 0x06cc, - 0x043C : 0x06cd, - 0x043D : 0x06ce, - 0x043E : 0x06cf, - 0x043F : 0x06d0, - 0x044F : 0x06d1, - 0x0440 : 0x06d2, - 0x0441 : 0x06d3, - 0x0442 : 0x06d4, - 0x0443 : 0x06d5, - 0x0436 : 0x06d6, - 0x0432 : 0x06d7, - 0x044C : 0x06d8, - 0x044B : 0x06d9, - 0x0437 : 0x06da, - 0x0448 : 0x06db, - 0x044D : 0x06dc, - 0x0449 : 0x06dd, - 0x0447 : 0x06de, - 0x044A : 0x06df, - 0x042E : 0x06e0, - 0x0410 : 0x06e1, - 0x0411 : 0x06e2, - 0x0426 : 0x06e3, - 0x0414 : 0x06e4, - 0x0415 : 0x06e5, - 0x0424 : 0x06e6, - 0x0413 : 0x06e7, - 0x0425 : 0x06e8, - 0x0418 : 0x06e9, - 0x0419 : 0x06ea, - 0x041A : 0x06eb, - 0x041B : 0x06ec, - 0x041C : 0x06ed, - 0x041D : 0x06ee, - 0x041E : 0x06ef, - 0x041F : 0x06f0, - 0x042F : 0x06f1, - 0x0420 : 0x06f2, - 0x0421 : 0x06f3, - 0x0422 : 0x06f4, - 0x0423 : 0x06f5, - 0x0416 : 0x06f6, - 0x0412 : 0x06f7, - 0x042C : 0x06f8, - 0x042B : 0x06f9, - 0x0417 : 0x06fa, - 0x0428 : 0x06fb, - 0x042D : 0x06fc, - 0x0429 : 0x06fd, - 0x0427 : 0x06fe, - 0x042A : 0x06ff, - 0x0386 : 0x07a1, - 0x0388 : 0x07a2, - 0x0389 : 0x07a3, - 0x038A : 0x07a4, - 0x03AA : 0x07a5, - 0x038C : 0x07a7, - 0x038E : 0x07a8, - 0x03AB : 0x07a9, - 0x038F : 0x07ab, - 0x0385 : 0x07ae, - 0x2015 : 0x07af, - 0x03AC : 0x07b1, - 0x03AD : 0x07b2, - 0x03AE : 0x07b3, - 0x03AF : 0x07b4, - 0x03CA : 0x07b5, - 0x0390 : 0x07b6, - 0x03CC : 0x07b7, - 0x03CD : 0x07b8, - 0x03CB : 0x07b9, - 0x03B0 : 0x07ba, - 0x03CE : 0x07bb, - 0x0391 : 0x07c1, - 0x0392 : 0x07c2, - 0x0393 : 0x07c3, - 0x0394 : 0x07c4, - 0x0395 : 0x07c5, - 0x0396 : 0x07c6, - 0x0397 : 0x07c7, - 0x0398 : 0x07c8, - 0x0399 : 0x07c9, - 0x039A : 0x07ca, - 0x039B : 0x07cb, - 0x039C : 0x07cc, - 0x039D : 0x07cd, - 0x039E : 0x07ce, - 0x039F : 0x07cf, - 0x03A0 : 0x07d0, - 0x03A1 : 0x07d1, - 0x03A3 : 0x07d2, - 0x03A4 : 0x07d4, - 0x03A5 : 0x07d5, - 0x03A6 : 0x07d6, - 0x03A7 : 0x07d7, - 0x03A8 : 0x07d8, - 0x03A9 : 0x07d9, - 0x03B1 : 0x07e1, - 0x03B2 : 0x07e2, - 0x03B3 : 0x07e3, - 0x03B4 : 0x07e4, - 0x03B5 : 0x07e5, - 0x03B6 : 0x07e6, - 0x03B7 : 0x07e7, - 0x03B8 : 0x07e8, - 0x03B9 : 0x07e9, - 0x03BA : 0x07ea, - 0x03BB : 0x07eb, - 0x03BC : 0x07ec, - 0x03BD : 0x07ed, - 0x03BE : 0x07ee, - 0x03BF : 0x07ef, - 0x03C0 : 0x07f0, - 0x03C1 : 0x07f1, - 0x03C3 : 0x07f2, - 0x03C2 : 0x07f3, - 0x03C4 : 0x07f4, - 0x03C5 : 0x07f5, - 0x03C6 : 0x07f6, - 0x03C7 : 0x07f7, - 0x03C8 : 0x07f8, - 0x03C9 : 0x07f9, - 0x23B7 : 0x08a1, - 0x2320 : 0x08a4, - 0x2321 : 0x08a5, - 0x23A1 : 0x08a7, - 0x23A3 : 0x08a8, - 0x23A4 : 0x08a9, - 0x23A6 : 0x08aa, - 0x239B : 0x08ab, - 0x239D : 0x08ac, - 0x239E : 0x08ad, - 0x23A0 : 0x08ae, - 0x23A8 : 0x08af, - 0x23AC : 0x08b0, - 0x2264 : 0x08bc, - 0x2260 : 0x08bd, - 0x2265 : 0x08be, - 0x222B : 0x08bf, - 0x2234 : 0x08c0, - 0x221D : 0x08c1, - 0x221E : 0x08c2, - 0x2207 : 0x08c5, - 0x223C : 0x08c8, - 0x2243 : 0x08c9, - 0x21D4 : 0x08cd, - 0x21D2 : 0x08ce, - 0x2261 : 0x08cf, - //0x221A : 0x08d6, - 0x2282 : 0x08da, - 0x2283 : 0x08db, - 0x2229 : 0x08dc, - 0x222A : 0x08dd, - 0x2227 : 0x08de, - 0x2228 : 0x08df, - //0x2202 : 0x08ef, - 0x0192 : 0x08f6, - 0x2190 : 0x08fb, - 0x2191 : 0x08fc, - 0x2192 : 0x08fd, - 0x2193 : 0x08fe, - 0x25C6 : 0x09e0, - 0x2592 : 0x09e1, - 0x2409 : 0x09e2, - 0x240C : 0x09e3, - 0x240D : 0x09e4, - 0x240A : 0x09e5, - 0x2424 : 0x09e8, - 0x240B : 0x09e9, - 0x2518 : 0x09ea, - 0x2510 : 0x09eb, - 0x250C : 0x09ec, - 0x2514 : 0x09ed, - 0x253C : 0x09ee, - 0x23BA : 0x09ef, - 0x23BB : 0x09f0, - 0x2500 : 0x09f1, - 0x23BC : 0x09f2, - 0x23BD : 0x09f3, - 0x251C : 0x09f4, - 0x2524 : 0x09f5, - 0x2534 : 0x09f6, - 0x252C : 0x09f7, - 0x2502 : 0x09f8, - 0x2003 : 0x0aa1, - 0x2002 : 0x0aa2, - 0x2004 : 0x0aa3, - 0x2005 : 0x0aa4, - 0x2007 : 0x0aa5, - 0x2008 : 0x0aa6, - 0x2009 : 0x0aa7, - 0x200A : 0x0aa8, - 0x2014 : 0x0aa9, - 0x2013 : 0x0aaa, - 0x2026 : 0x0aae, - 0x2025 : 0x0aaf, - 0x2153 : 0x0ab0, - 0x2154 : 0x0ab1, - 0x2155 : 0x0ab2, - 0x2156 : 0x0ab3, - 0x2157 : 0x0ab4, - 0x2158 : 0x0ab5, - 0x2159 : 0x0ab6, - 0x215A : 0x0ab7, - 0x2105 : 0x0ab8, - 0x2012 : 0x0abb, - 0x215B : 0x0ac3, - 0x215C : 0x0ac4, - 0x215D : 0x0ac5, - 0x215E : 0x0ac6, - 0x2122 : 0x0ac9, - 0x2018 : 0x0ad0, - 0x2019 : 0x0ad1, - 0x201C : 0x0ad2, - 0x201D : 0x0ad3, - 0x211E : 0x0ad4, - 0x2032 : 0x0ad6, - 0x2033 : 0x0ad7, - 0x271D : 0x0ad9, - 0x2663 : 0x0aec, - 0x2666 : 0x0aed, - 0x2665 : 0x0aee, - 0x2720 : 0x0af0, - 0x2020 : 0x0af1, - 0x2021 : 0x0af2, - 0x2713 : 0x0af3, - 0x2717 : 0x0af4, - 0x266F : 0x0af5, - 0x266D : 0x0af6, - 0x2642 : 0x0af7, - 0x2640 : 0x0af8, - 0x260E : 0x0af9, - 0x2315 : 0x0afa, - 0x2117 : 0x0afb, - 0x2038 : 0x0afc, - 0x201A : 0x0afd, - 0x201E : 0x0afe, - 0x22A4 : 0x0bc2, - 0x230A : 0x0bc4, - 0x2218 : 0x0bca, - 0x2395 : 0x0bcc, - 0x22A5 : 0x0bce, - 0x25CB : 0x0bcf, - 0x2308 : 0x0bd3, - 0x22A3 : 0x0bdc, - 0x22A2 : 0x0bfc, - 0x2017 : 0x0cdf, - 0x05D0 : 0x0ce0, - 0x05D1 : 0x0ce1, - 0x05D2 : 0x0ce2, - 0x05D3 : 0x0ce3, - 0x05D4 : 0x0ce4, - 0x05D5 : 0x0ce5, - 0x05D6 : 0x0ce6, - 0x05D7 : 0x0ce7, - 0x05D8 : 0x0ce8, - 0x05D9 : 0x0ce9, - 0x05DA : 0x0cea, - 0x05DB : 0x0ceb, - 0x05DC : 0x0cec, - 0x05DD : 0x0ced, - 0x05DE : 0x0cee, - 0x05DF : 0x0cef, - 0x05E0 : 0x0cf0, - 0x05E1 : 0x0cf1, - 0x05E2 : 0x0cf2, - 0x05E3 : 0x0cf3, - 0x05E4 : 0x0cf4, - 0x05E5 : 0x0cf5, - 0x05E6 : 0x0cf6, - 0x05E7 : 0x0cf7, - 0x05E8 : 0x0cf8, - 0x05E9 : 0x0cf9, - 0x05EA : 0x0cfa, - 0x0E01 : 0x0da1, - 0x0E02 : 0x0da2, - 0x0E03 : 0x0da3, - 0x0E04 : 0x0da4, - 0x0E05 : 0x0da5, - 0x0E06 : 0x0da6, - 0x0E07 : 0x0da7, - 0x0E08 : 0x0da8, - 0x0E09 : 0x0da9, - 0x0E0A : 0x0daa, - 0x0E0B : 0x0dab, - 0x0E0C : 0x0dac, - 0x0E0D : 0x0dad, - 0x0E0E : 0x0dae, - 0x0E0F : 0x0daf, - 0x0E10 : 0x0db0, - 0x0E11 : 0x0db1, - 0x0E12 : 0x0db2, - 0x0E13 : 0x0db3, - 0x0E14 : 0x0db4, - 0x0E15 : 0x0db5, - 0x0E16 : 0x0db6, - 0x0E17 : 0x0db7, - 0x0E18 : 0x0db8, - 0x0E19 : 0x0db9, - 0x0E1A : 0x0dba, - 0x0E1B : 0x0dbb, - 0x0E1C : 0x0dbc, - 0x0E1D : 0x0dbd, - 0x0E1E : 0x0dbe, - 0x0E1F : 0x0dbf, - 0x0E20 : 0x0dc0, - 0x0E21 : 0x0dc1, - 0x0E22 : 0x0dc2, - 0x0E23 : 0x0dc3, - 0x0E24 : 0x0dc4, - 0x0E25 : 0x0dc5, - 0x0E26 : 0x0dc6, - 0x0E27 : 0x0dc7, - 0x0E28 : 0x0dc8, - 0x0E29 : 0x0dc9, - 0x0E2A : 0x0dca, - 0x0E2B : 0x0dcb, - 0x0E2C : 0x0dcc, - 0x0E2D : 0x0dcd, - 0x0E2E : 0x0dce, - 0x0E2F : 0x0dcf, - 0x0E30 : 0x0dd0, - 0x0E31 : 0x0dd1, - 0x0E32 : 0x0dd2, - 0x0E33 : 0x0dd3, - 0x0E34 : 0x0dd4, - 0x0E35 : 0x0dd5, - 0x0E36 : 0x0dd6, - 0x0E37 : 0x0dd7, - 0x0E38 : 0x0dd8, - 0x0E39 : 0x0dd9, - 0x0E3A : 0x0dda, - 0x0E3F : 0x0ddf, - 0x0E40 : 0x0de0, - 0x0E41 : 0x0de1, - 0x0E42 : 0x0de2, - 0x0E43 : 0x0de3, - 0x0E44 : 0x0de4, - 0x0E45 : 0x0de5, - 0x0E46 : 0x0de6, - 0x0E47 : 0x0de7, - 0x0E48 : 0x0de8, - 0x0E49 : 0x0de9, - 0x0E4A : 0x0dea, - 0x0E4B : 0x0deb, - 0x0E4C : 0x0dec, - 0x0E4D : 0x0ded, - 0x0E50 : 0x0df0, - 0x0E51 : 0x0df1, - 0x0E52 : 0x0df2, - 0x0E53 : 0x0df3, - 0x0E54 : 0x0df4, - 0x0E55 : 0x0df5, - 0x0E56 : 0x0df6, - 0x0E57 : 0x0df7, - 0x0E58 : 0x0df8, - 0x0E59 : 0x0df9, - 0x0587 : 0x1000587, - 0x0589 : 0x1000589, - 0x055D : 0x100055d, - 0x058A : 0x100058a, - 0x055C : 0x100055c, - 0x055B : 0x100055b, - 0x055E : 0x100055e, - 0x0531 : 0x1000531, - 0x0561 : 0x1000561, - 0x0532 : 0x1000532, - 0x0562 : 0x1000562, - 0x0533 : 0x1000533, - 0x0563 : 0x1000563, - 0x0534 : 0x1000534, - 0x0564 : 0x1000564, - 0x0535 : 0x1000535, - 0x0565 : 0x1000565, - 0x0536 : 0x1000536, - 0x0566 : 0x1000566, - 0x0537 : 0x1000537, - 0x0567 : 0x1000567, - 0x0538 : 0x1000538, - 0x0568 : 0x1000568, - 0x0539 : 0x1000539, - 0x0569 : 0x1000569, - 0x053A : 0x100053a, - 0x056A : 0x100056a, - 0x053B : 0x100053b, - 0x056B : 0x100056b, - 0x053C : 0x100053c, - 0x056C : 0x100056c, - 0x053D : 0x100053d, - 0x056D : 0x100056d, - 0x053E : 0x100053e, - 0x056E : 0x100056e, - 0x053F : 0x100053f, - 0x056F : 0x100056f, - 0x0540 : 0x1000540, - 0x0570 : 0x1000570, - 0x0541 : 0x1000541, - 0x0571 : 0x1000571, - 0x0542 : 0x1000542, - 0x0572 : 0x1000572, - 0x0543 : 0x1000543, - 0x0573 : 0x1000573, - 0x0544 : 0x1000544, - 0x0574 : 0x1000574, - 0x0545 : 0x1000545, - 0x0575 : 0x1000575, - 0x0546 : 0x1000546, - 0x0576 : 0x1000576, - 0x0547 : 0x1000547, - 0x0577 : 0x1000577, - 0x0548 : 0x1000548, - 0x0578 : 0x1000578, - 0x0549 : 0x1000549, - 0x0579 : 0x1000579, - 0x054A : 0x100054a, - 0x057A : 0x100057a, - 0x054B : 0x100054b, - 0x057B : 0x100057b, - 0x054C : 0x100054c, - 0x057C : 0x100057c, - 0x054D : 0x100054d, - 0x057D : 0x100057d, - 0x054E : 0x100054e, - 0x057E : 0x100057e, - 0x054F : 0x100054f, - 0x057F : 0x100057f, - 0x0550 : 0x1000550, - 0x0580 : 0x1000580, - 0x0551 : 0x1000551, - 0x0581 : 0x1000581, - 0x0552 : 0x1000552, - 0x0582 : 0x1000582, - 0x0553 : 0x1000553, - 0x0583 : 0x1000583, - 0x0554 : 0x1000554, - 0x0584 : 0x1000584, - 0x0555 : 0x1000555, - 0x0585 : 0x1000585, - 0x0556 : 0x1000556, - 0x0586 : 0x1000586, - 0x055A : 0x100055a, - 0x10D0 : 0x10010d0, - 0x10D1 : 0x10010d1, - 0x10D2 : 0x10010d2, - 0x10D3 : 0x10010d3, - 0x10D4 : 0x10010d4, - 0x10D5 : 0x10010d5, - 0x10D6 : 0x10010d6, - 0x10D7 : 0x10010d7, - 0x10D8 : 0x10010d8, - 0x10D9 : 0x10010d9, - 0x10DA : 0x10010da, - 0x10DB : 0x10010db, - 0x10DC : 0x10010dc, - 0x10DD : 0x10010dd, - 0x10DE : 0x10010de, - 0x10DF : 0x10010df, - 0x10E0 : 0x10010e0, - 0x10E1 : 0x10010e1, - 0x10E2 : 0x10010e2, - 0x10E3 : 0x10010e3, - 0x10E4 : 0x10010e4, - 0x10E5 : 0x10010e5, - 0x10E6 : 0x10010e6, - 0x10E7 : 0x10010e7, - 0x10E8 : 0x10010e8, - 0x10E9 : 0x10010e9, - 0x10EA : 0x10010ea, - 0x10EB : 0x10010eb, - 0x10EC : 0x10010ec, - 0x10ED : 0x10010ed, - 0x10EE : 0x10010ee, - 0x10EF : 0x10010ef, - 0x10F0 : 0x10010f0, - 0x10F1 : 0x10010f1, - 0x10F2 : 0x10010f2, - 0x10F3 : 0x10010f3, - 0x10F4 : 0x10010f4, - 0x10F5 : 0x10010f5, - 0x10F6 : 0x10010f6, - 0x1E8A : 0x1001e8a, - 0x012C : 0x100012c, - 0x01B5 : 0x10001b5, - 0x01E6 : 0x10001e6, - 0x01D2 : 0x10001d1, - 0x019F : 0x100019f, - 0x1E8B : 0x1001e8b, - 0x012D : 0x100012d, - 0x01B6 : 0x10001b6, - 0x01E7 : 0x10001e7, - //0x01D2 : 0x10001d2, - 0x0275 : 0x1000275, - 0x018F : 0x100018f, - 0x0259 : 0x1000259, - 0x1E36 : 0x1001e36, - 0x1E37 : 0x1001e37, - 0x1EA0 : 0x1001ea0, - 0x1EA1 : 0x1001ea1, - 0x1EA2 : 0x1001ea2, - 0x1EA3 : 0x1001ea3, - 0x1EA4 : 0x1001ea4, - 0x1EA5 : 0x1001ea5, - 0x1EA6 : 0x1001ea6, - 0x1EA7 : 0x1001ea7, - 0x1EA8 : 0x1001ea8, - 0x1EA9 : 0x1001ea9, - 0x1EAA : 0x1001eaa, - 0x1EAB : 0x1001eab, - 0x1EAC : 0x1001eac, - 0x1EAD : 0x1001ead, - 0x1EAE : 0x1001eae, - 0x1EAF : 0x1001eaf, - 0x1EB0 : 0x1001eb0, - 0x1EB1 : 0x1001eb1, - 0x1EB2 : 0x1001eb2, - 0x1EB3 : 0x1001eb3, - 0x1EB4 : 0x1001eb4, - 0x1EB5 : 0x1001eb5, - 0x1EB6 : 0x1001eb6, - 0x1EB7 : 0x1001eb7, - 0x1EB8 : 0x1001eb8, - 0x1EB9 : 0x1001eb9, - 0x1EBA : 0x1001eba, - 0x1EBB : 0x1001ebb, - 0x1EBC : 0x1001ebc, - 0x1EBD : 0x1001ebd, - 0x1EBE : 0x1001ebe, - 0x1EBF : 0x1001ebf, - 0x1EC0 : 0x1001ec0, - 0x1EC1 : 0x1001ec1, - 0x1EC2 : 0x1001ec2, - 0x1EC3 : 0x1001ec3, - 0x1EC4 : 0x1001ec4, - 0x1EC5 : 0x1001ec5, - 0x1EC6 : 0x1001ec6, - 0x1EC7 : 0x1001ec7, - 0x1EC8 : 0x1001ec8, - 0x1EC9 : 0x1001ec9, - 0x1ECA : 0x1001eca, - 0x1ECB : 0x1001ecb, - 0x1ECC : 0x1001ecc, - 0x1ECD : 0x1001ecd, - 0x1ECE : 0x1001ece, - 0x1ECF : 0x1001ecf, - 0x1ED0 : 0x1001ed0, - 0x1ED1 : 0x1001ed1, - 0x1ED2 : 0x1001ed2, - 0x1ED3 : 0x1001ed3, - 0x1ED4 : 0x1001ed4, - 0x1ED5 : 0x1001ed5, - 0x1ED6 : 0x1001ed6, - 0x1ED7 : 0x1001ed7, - 0x1ED8 : 0x1001ed8, - 0x1ED9 : 0x1001ed9, - 0x1EDA : 0x1001eda, - 0x1EDB : 0x1001edb, - 0x1EDC : 0x1001edc, - 0x1EDD : 0x1001edd, - 0x1EDE : 0x1001ede, - 0x1EDF : 0x1001edf, - 0x1EE0 : 0x1001ee0, - 0x1EE1 : 0x1001ee1, - 0x1EE2 : 0x1001ee2, - 0x1EE3 : 0x1001ee3, - 0x1EE4 : 0x1001ee4, - 0x1EE5 : 0x1001ee5, - 0x1EE6 : 0x1001ee6, - 0x1EE7 : 0x1001ee7, - 0x1EE8 : 0x1001ee8, - 0x1EE9 : 0x1001ee9, - 0x1EEA : 0x1001eea, - 0x1EEB : 0x1001eeb, - 0x1EEC : 0x1001eec, - 0x1EED : 0x1001eed, - 0x1EEE : 0x1001eee, - 0x1EEF : 0x1001eef, - 0x1EF0 : 0x1001ef0, - 0x1EF1 : 0x1001ef1, - 0x1EF4 : 0x1001ef4, - 0x1EF5 : 0x1001ef5, - 0x1EF6 : 0x1001ef6, - 0x1EF7 : 0x1001ef7, - 0x1EF8 : 0x1001ef8, - 0x1EF9 : 0x1001ef9, - 0x01A0 : 0x10001a0, - 0x01A1 : 0x10001a1, - 0x01AF : 0x10001af, - 0x01B0 : 0x10001b0, - 0x20A0 : 0x10020a0, - 0x20A1 : 0x10020a1, - 0x20A2 : 0x10020a2, - 0x20A3 : 0x10020a3, - 0x20A4 : 0x10020a4, - 0x20A5 : 0x10020a5, - 0x20A6 : 0x10020a6, - 0x20A7 : 0x10020a7, - 0x20A8 : 0x10020a8, - 0x20A9 : 0x10020a9, - 0x20AA : 0x10020aa, - 0x20AB : 0x10020ab, - 0x20AC : 0x20ac, - 0x2070 : 0x1002070, - 0x2074 : 0x1002074, - 0x2075 : 0x1002075, - 0x2076 : 0x1002076, - 0x2077 : 0x1002077, - 0x2078 : 0x1002078, - 0x2079 : 0x1002079, - 0x2080 : 0x1002080, - 0x2081 : 0x1002081, - 0x2082 : 0x1002082, - 0x2083 : 0x1002083, - 0x2084 : 0x1002084, - 0x2085 : 0x1002085, - 0x2086 : 0x1002086, - 0x2087 : 0x1002087, - 0x2088 : 0x1002088, - 0x2089 : 0x1002089, - 0x2202 : 0x1002202, - 0x2205 : 0x1002205, - 0x2208 : 0x1002208, - 0x2209 : 0x1002209, - 0x220B : 0x100220B, - 0x221A : 0x100221A, - 0x221B : 0x100221B, - 0x221C : 0x100221C, - 0x222C : 0x100222C, - 0x222D : 0x100222D, - 0x2235 : 0x1002235, - 0x2245 : 0x1002248, - 0x2247 : 0x1002247, - 0x2262 : 0x1002262, - 0x2263 : 0x1002263, - 0x2800 : 0x1002800, - 0x2801 : 0x1002801, - 0x2802 : 0x1002802, - 0x2803 : 0x1002803, - 0x2804 : 0x1002804, - 0x2805 : 0x1002805, - 0x2806 : 0x1002806, - 0x2807 : 0x1002807, - 0x2808 : 0x1002808, - 0x2809 : 0x1002809, - 0x280a : 0x100280a, - 0x280b : 0x100280b, - 0x280c : 0x100280c, - 0x280d : 0x100280d, - 0x280e : 0x100280e, - 0x280f : 0x100280f, - 0x2810 : 0x1002810, - 0x2811 : 0x1002811, - 0x2812 : 0x1002812, - 0x2813 : 0x1002813, - 0x2814 : 0x1002814, - 0x2815 : 0x1002815, - 0x2816 : 0x1002816, - 0x2817 : 0x1002817, - 0x2818 : 0x1002818, - 0x2819 : 0x1002819, - 0x281a : 0x100281a, - 0x281b : 0x100281b, - 0x281c : 0x100281c, - 0x281d : 0x100281d, - 0x281e : 0x100281e, - 0x281f : 0x100281f, - 0x2820 : 0x1002820, - 0x2821 : 0x1002821, - 0x2822 : 0x1002822, - 0x2823 : 0x1002823, - 0x2824 : 0x1002824, - 0x2825 : 0x1002825, - 0x2826 : 0x1002826, - 0x2827 : 0x1002827, - 0x2828 : 0x1002828, - 0x2829 : 0x1002829, - 0x282a : 0x100282a, - 0x282b : 0x100282b, - 0x282c : 0x100282c, - 0x282d : 0x100282d, - 0x282e : 0x100282e, - 0x282f : 0x100282f, - 0x2830 : 0x1002830, - 0x2831 : 0x1002831, - 0x2832 : 0x1002832, - 0x2833 : 0x1002833, - 0x2834 : 0x1002834, - 0x2835 : 0x1002835, - 0x2836 : 0x1002836, - 0x2837 : 0x1002837, - 0x2838 : 0x1002838, - 0x2839 : 0x1002839, - 0x283a : 0x100283a, - 0x283b : 0x100283b, - 0x283c : 0x100283c, - 0x283d : 0x100283d, - 0x283e : 0x100283e, - 0x283f : 0x100283f, - 0x2840 : 0x1002840, - 0x2841 : 0x1002841, - 0x2842 : 0x1002842, - 0x2843 : 0x1002843, - 0x2844 : 0x1002844, - 0x2845 : 0x1002845, - 0x2846 : 0x1002846, - 0x2847 : 0x1002847, - 0x2848 : 0x1002848, - 0x2849 : 0x1002849, - 0x284a : 0x100284a, - 0x284b : 0x100284b, - 0x284c : 0x100284c, - 0x284d : 0x100284d, - 0x284e : 0x100284e, - 0x284f : 0x100284f, - 0x2850 : 0x1002850, - 0x2851 : 0x1002851, - 0x2852 : 0x1002852, - 0x2853 : 0x1002853, - 0x2854 : 0x1002854, - 0x2855 : 0x1002855, - 0x2856 : 0x1002856, - 0x2857 : 0x1002857, - 0x2858 : 0x1002858, - 0x2859 : 0x1002859, - 0x285a : 0x100285a, - 0x285b : 0x100285b, - 0x285c : 0x100285c, - 0x285d : 0x100285d, - 0x285e : 0x100285e, - 0x285f : 0x100285f, - 0x2860 : 0x1002860, - 0x2861 : 0x1002861, - 0x2862 : 0x1002862, - 0x2863 : 0x1002863, - 0x2864 : 0x1002864, - 0x2865 : 0x1002865, - 0x2866 : 0x1002866, - 0x2867 : 0x1002867, - 0x2868 : 0x1002868, - 0x2869 : 0x1002869, - 0x286a : 0x100286a, - 0x286b : 0x100286b, - 0x286c : 0x100286c, - 0x286d : 0x100286d, - 0x286e : 0x100286e, - 0x286f : 0x100286f, - 0x2870 : 0x1002870, - 0x2871 : 0x1002871, - 0x2872 : 0x1002872, - 0x2873 : 0x1002873, - 0x2874 : 0x1002874, - 0x2875 : 0x1002875, - 0x2876 : 0x1002876, - 0x2877 : 0x1002877, - 0x2878 : 0x1002878, - 0x2879 : 0x1002879, - 0x287a : 0x100287a, - 0x287b : 0x100287b, - 0x287c : 0x100287c, - 0x287d : 0x100287d, - 0x287e : 0x100287e, - 0x287f : 0x100287f, - 0x2880 : 0x1002880, - 0x2881 : 0x1002881, - 0x2882 : 0x1002882, - 0x2883 : 0x1002883, - 0x2884 : 0x1002884, - 0x2885 : 0x1002885, - 0x2886 : 0x1002886, - 0x2887 : 0x1002887, - 0x2888 : 0x1002888, - 0x2889 : 0x1002889, - 0x288a : 0x100288a, - 0x288b : 0x100288b, - 0x288c : 0x100288c, - 0x288d : 0x100288d, - 0x288e : 0x100288e, - 0x288f : 0x100288f, - 0x2890 : 0x1002890, - 0x2891 : 0x1002891, - 0x2892 : 0x1002892, - 0x2893 : 0x1002893, - 0x2894 : 0x1002894, - 0x2895 : 0x1002895, - 0x2896 : 0x1002896, - 0x2897 : 0x1002897, - 0x2898 : 0x1002898, - 0x2899 : 0x1002899, - 0x289a : 0x100289a, - 0x289b : 0x100289b, - 0x289c : 0x100289c, - 0x289d : 0x100289d, - 0x289e : 0x100289e, - 0x289f : 0x100289f, - 0x28a0 : 0x10028a0, - 0x28a1 : 0x10028a1, - 0x28a2 : 0x10028a2, - 0x28a3 : 0x10028a3, - 0x28a4 : 0x10028a4, - 0x28a5 : 0x10028a5, - 0x28a6 : 0x10028a6, - 0x28a7 : 0x10028a7, - 0x28a8 : 0x10028a8, - 0x28a9 : 0x10028a9, - 0x28aa : 0x10028aa, - 0x28ab : 0x10028ab, - 0x28ac : 0x10028ac, - 0x28ad : 0x10028ad, - 0x28ae : 0x10028ae, - 0x28af : 0x10028af, - 0x28b0 : 0x10028b0, - 0x28b1 : 0x10028b1, - 0x28b2 : 0x10028b2, - 0x28b3 : 0x10028b3, - 0x28b4 : 0x10028b4, - 0x28b5 : 0x10028b5, - 0x28b6 : 0x10028b6, - 0x28b7 : 0x10028b7, - 0x28b8 : 0x10028b8, - 0x28b9 : 0x10028b9, - 0x28ba : 0x10028ba, - 0x28bb : 0x10028bb, - 0x28bc : 0x10028bc, - 0x28bd : 0x10028bd, - 0x28be : 0x10028be, - 0x28bf : 0x10028bf, - 0x28c0 : 0x10028c0, - 0x28c1 : 0x10028c1, - 0x28c2 : 0x10028c2, - 0x28c3 : 0x10028c3, - 0x28c4 : 0x10028c4, - 0x28c5 : 0x10028c5, - 0x28c6 : 0x10028c6, - 0x28c7 : 0x10028c7, - 0x28c8 : 0x10028c8, - 0x28c9 : 0x10028c9, - 0x28ca : 0x10028ca, - 0x28cb : 0x10028cb, - 0x28cc : 0x10028cc, - 0x28cd : 0x10028cd, - 0x28ce : 0x10028ce, - 0x28cf : 0x10028cf, - 0x28d0 : 0x10028d0, - 0x28d1 : 0x10028d1, - 0x28d2 : 0x10028d2, - 0x28d3 : 0x10028d3, - 0x28d4 : 0x10028d4, - 0x28d5 : 0x10028d5, - 0x28d6 : 0x10028d6, - 0x28d7 : 0x10028d7, - 0x28d8 : 0x10028d8, - 0x28d9 : 0x10028d9, - 0x28da : 0x10028da, - 0x28db : 0x10028db, - 0x28dc : 0x10028dc, - 0x28dd : 0x10028dd, - 0x28de : 0x10028de, - 0x28df : 0x10028df, - 0x28e0 : 0x10028e0, - 0x28e1 : 0x10028e1, - 0x28e2 : 0x10028e2, - 0x28e3 : 0x10028e3, - 0x28e4 : 0x10028e4, - 0x28e5 : 0x10028e5, - 0x28e6 : 0x10028e6, - 0x28e7 : 0x10028e7, - 0x28e8 : 0x10028e8, - 0x28e9 : 0x10028e9, - 0x28ea : 0x10028ea, - 0x28eb : 0x10028eb, - 0x28ec : 0x10028ec, - 0x28ed : 0x10028ed, - 0x28ee : 0x10028ee, - 0x28ef : 0x10028ef, - 0x28f0 : 0x10028f0, - 0x28f1 : 0x10028f1, - 0x28f2 : 0x10028f2, - 0x28f3 : 0x10028f3, - 0x28f4 : 0x10028f4, - 0x28f5 : 0x10028f5, - 0x28f6 : 0x10028f6, - 0x28f7 : 0x10028f7, - 0x28f8 : 0x10028f8, - 0x28f9 : 0x10028f9, - 0x28fa : 0x10028fa, - 0x28fb : 0x10028fb, - 0x28fc : 0x10028fc, - 0x28fd : 0x10028fd, - 0x28fe : 0x10028fe, - 0x28ff : 0x10028ff -}; + ['onMouseButton', 'rw', 'func'], // Handler for mouse button click/release + ['onMouseMove', 'rw', 'func'], // Handler for mouse movement + ['touchButton', 'rw', 'int'] // Button mask (1, 2, 4) for touch devices (0 means ignore clicks) + ]); +})(); diff --git a/webclients/novnc/include/jsunzip.js b/webclients/novnc/include/jsunzip.js index f815218..8968f86 100755 --- a/webclients/novnc/include/jsunzip.js +++ b/webclients/novnc/include/jsunzip.js @@ -352,20 +352,28 @@ this.getbit = function(d) } /* read a num bit value from a stream and add base */ +function read_bits_direct(source, bitcount, tag, idx, num) +{ + var val = 0; + while (bitcount < 24) { + tag = tag | (source[idx++] & 0xff) << bitcount; + bitcount += 8; + } + val = tag & (0xffff >> (16 - num)); + tag >>= num; + bitcount -= num; + return [bitcount, tag, idx, val]; +} this.read_bits = function(d, num, base) { if (!num) return base; - var val = 0; - while (d.bitcount < 24) { - d.tag = d.tag | (d.source[d.sourceIndex++] & 0xff) << d.bitcount; - d.bitcount += 8; - } - val = d.tag & (0xffff >> (16 - num)); - d.tag >>= num; - d.bitcount -= num; - return val + base; + var ret = read_bits_direct(d.source, d.bitcount, d.tag, d.sourceIndex, num); + d.bitcount = ret[0]; + d.tag = ret[1]; + d.sourceIndex = ret[2]; + return ret[3] + base; } /* given a data stream and a tree, decode a symbol */ diff --git a/webclients/novnc/include/keyboard.js b/webclients/novnc/include/keyboard.js new file mode 100644 index 0000000..6044321 --- /dev/null +++ b/webclients/novnc/include/keyboard.js @@ -0,0 +1,543 @@ +var kbdUtil = (function() { + "use strict"; + + function substituteCodepoint(cp) { + // Any Unicode code points which do not have corresponding keysym entries + // can be swapped out for another code point by adding them to this table + var substitutions = { + // {S,s} with comma below -> {S,s} with cedilla + 0x218 : 0x15e, + 0x219 : 0x15f, + // {T,t} with comma below -> {T,t} with cedilla + 0x21a : 0x162, + 0x21b : 0x163 + }; + + var sub = substitutions[cp]; + return sub ? sub : cp; + } + + function isMac() { + return navigator && !!(/mac/i).exec(navigator.platform); + } + function isWindows() { + return navigator && !!(/win/i).exec(navigator.platform); + } + function isLinux() { + return navigator && !!(/linux/i).exec(navigator.platform); + } + + // Return true if a modifier which is not the specified char modifier (and is not shift) is down + function hasShortcutModifier(charModifier, currentModifiers) { + var mods = {}; + for (var key in currentModifiers) { + if (parseInt(key) !== 0xffe1) { + mods[key] = currentModifiers[key]; + } + } + + var sum = 0; + for (var k in currentModifiers) { + if (mods[k]) { + ++sum; + } + } + if (hasCharModifier(charModifier, mods)) { + return sum > charModifier.length; + } + else { + return sum > 0; + } + } + + // Return true if the specified char modifier is currently down + function hasCharModifier(charModifier, currentModifiers) { + if (charModifier.length === 0) { return false; } + + for (var i = 0; i < charModifier.length; ++i) { + if (!currentModifiers[charModifier[i]]) { + return false; + } + } + return true; + } + + // Helper object tracking modifier key state + // and generates fake key events to compensate if it gets out of sync + function ModifierSync(charModifier) { + var ctrl = 0xffe3; + var alt = 0xffe9; + var altGr = 0xfe03; + var shift = 0xffe1; + var meta = 0xffe7; + + if (!charModifier) { + if (isMac()) { + // on Mac, Option (AKA Alt) is used as a char modifier + charModifier = [alt]; + } + else if (isWindows()) { + // on Windows, Ctrl+Alt is used as a char modifier + charModifier = [alt, ctrl]; + } + else if (isLinux()) { + // on Linux, AltGr is used as a char modifier + charModifier = [altGr]; + } + else { + charModifier = []; + } + } + + var state = {}; + state[ctrl] = false; + state[alt] = false; + state[altGr] = false; + state[shift] = false; + state[meta] = false; + + function sync(evt, keysym) { + var result = []; + function syncKey(keysym) { + return {keysym: keysyms.lookup(keysym), type: state[keysym] ? 'keydown' : 'keyup'}; + } + + if (evt.ctrlKey !== undefined && evt.ctrlKey !== state[ctrl] && keysym !== ctrl) { + state[ctrl] = evt.ctrlKey; + result.push(syncKey(ctrl)); + } + if (evt.altKey !== undefined && evt.altKey !== state[alt] && keysym !== alt) { + state[alt] = evt.altKey; + result.push(syncKey(alt)); + } + if (evt.altGraphKey !== undefined && evt.altGraphKey !== state[altGr] && keysym !== altGr) { + state[altGr] = evt.altGraphKey; + result.push(syncKey(altGr)); + } + if (evt.shiftKey !== undefined && evt.shiftKey !== state[shift] && keysym !== shift) { + state[shift] = evt.shiftKey; + result.push(syncKey(shift)); + } + if (evt.metaKey !== undefined && evt.metaKey !== state[meta] && keysym !== meta) { + state[meta] = evt.metaKey; + result.push(syncKey(meta)); + } + return result; + } + function syncKeyEvent(evt, down) { + var obj = getKeysym(evt); + var keysym = obj ? obj.keysym : null; + + // first, apply the event itself, if relevant + if (keysym !== null && state[keysym] !== undefined) { + state[keysym] = down; + } + return sync(evt, keysym); + } + + return { + // sync on the appropriate keyboard event + keydown: function(evt) { return syncKeyEvent(evt, true);}, + keyup: function(evt) { return syncKeyEvent(evt, false);}, + // Call this with a non-keyboard event (such as mouse events) to use its modifier state to synchronize anyway + syncAny: function(evt) { return sync(evt);}, + + // is a shortcut modifier down? + hasShortcutModifier: function() { return hasShortcutModifier(charModifier, state); }, + // if a char modifier is down, return the keys it consists of, otherwise return null + activeCharModifier: function() { return hasCharModifier(charModifier, state) ? charModifier : null; } + }; + } + + // Get a key ID from a keyboard event + // May be a string or an integer depending on the available properties + function getKey(evt){ + if ('keyCode' in evt && 'key' in evt) { + return evt.key + ':' + evt.keyCode; + } + else if ('keyCode' in evt) { + return evt.keyCode; + } + else { + return evt.key; + } + } + + // Get the most reliable keysym value we can get from a key event + // if char/charCode is available, prefer those, otherwise fall back to key/keyCode/which + function getKeysym(evt){ + var codepoint; + if (evt.char && evt.char.length === 1) { + codepoint = evt.char.charCodeAt(); + } + else if (evt.charCode) { + codepoint = evt.charCode; + } + else if (evt.keyCode && evt.type === 'keypress') { + // IE10 stores the char code as keyCode, and has no other useful properties + codepoint = evt.keyCode; + } + if (codepoint) { + var res = keysyms.fromUnicode(substituteCodepoint(codepoint)); + if (res) { + return res; + } + } + // we could check evt.key here. + // Legal values are defined in http://www.w3.org/TR/DOM-Level-3-Events/#key-values-list, + // so we "just" need to map them to keysym, but AFAIK this is only available in IE10, which also provides evt.key + // so we don't *need* it yet + if (evt.keyCode) { + return keysyms.lookup(keysymFromKeyCode(evt.keyCode, evt.shiftKey)); + } + if (evt.which) { + return keysyms.lookup(keysymFromKeyCode(evt.which, evt.shiftKey)); + } + return null; + } + + // Given a keycode, try to predict which keysym it might be. + // If the keycode is unknown, null is returned. + function keysymFromKeyCode(keycode, shiftPressed) { + if (typeof(keycode) !== 'number') { + return null; + } + // won't be accurate for azerty + if (keycode >= 0x30 && keycode <= 0x39) { + return keycode; // digit + } + if (keycode >= 0x41 && keycode <= 0x5a) { + // remap to lowercase unless shift is down + return shiftPressed ? keycode : keycode + 32; // A-Z + } + if (keycode >= 0x60 && keycode <= 0x69) { + return 0xffb0 + (keycode - 0x60); // numpad 0-9 + } + + switch(keycode) { + case 0x20: return 0x20; // space + case 0x6a: return 0xffaa; // multiply + case 0x6b: return 0xffab; // add + case 0x6c: return 0xffac; // separator + case 0x6d: return 0xffad; // subtract + case 0x6e: return 0xffae; // decimal + case 0x6f: return 0xffaf; // divide + case 0xbb: return 0x2b; // + + case 0xbc: return 0x2c; // , + case 0xbd: return 0x2d; // - + case 0xbe: return 0x2e; // . + } + + return nonCharacterKey({keyCode: keycode}); + } + + // if the key is a known non-character key (any key which doesn't generate character data) + // return its keysym value. Otherwise return null + function nonCharacterKey(evt) { + // evt.key not implemented yet + if (!evt.keyCode) { return null; } + var keycode = evt.keyCode; + + if (keycode >= 0x70 && keycode <= 0x87) { + return 0xffbe + keycode - 0x70; // F1-F24 + } + switch (keycode) { + + case 8 : return 0xFF08; // BACKSPACE + case 13 : return 0xFF0D; // ENTER + + case 9 : return 0xFF09; // TAB + + case 27 : return 0xFF1B; // ESCAPE + case 46 : return 0xFFFF; // DELETE + + case 36 : return 0xFF50; // HOME + case 35 : return 0xFF57; // END + case 33 : return 0xFF55; // PAGE_UP + case 34 : return 0xFF56; // PAGE_DOWN + case 45 : return 0xFF63; // INSERT + + case 37 : return 0xFF51; // LEFT + case 38 : return 0xFF52; // UP + case 39 : return 0xFF53; // RIGHT + case 40 : return 0xFF54; // DOWN + case 16 : return 0xFFE1; // SHIFT + case 17 : return 0xFFE3; // CONTROL + case 18 : return 0xFFE9; // Left ALT (Mac Option) + + case 224 : return 0xFE07; // Meta + case 225 : return 0xFE03; // AltGr + case 91 : return 0xFFEC; // Super_L (Win Key) + case 92 : return 0xFFED; // Super_R (Win Key) + case 93 : return 0xFF67; // Menu (Win Menu), Mac Command + default: return null; + } + } + return { + hasShortcutModifier : hasShortcutModifier, + hasCharModifier : hasCharModifier, + ModifierSync : ModifierSync, + getKey : getKey, + getKeysym : getKeysym, + keysymFromKeyCode : keysymFromKeyCode, + nonCharacterKey : nonCharacterKey, + substituteCodepoint : substituteCodepoint + }; +})(); + +// Takes a DOM keyboard event and: +// - determines which keysym it represents +// - determines a keyId identifying the key that was pressed (corresponding to the key/keyCode properties on the DOM event) +// - synthesizes events to synchronize modifier key state between which modifiers are actually down, and which we thought were down +// - marks each event with an 'escape' property if a modifier was down which should be "escaped" +// - generates a "stall" event in cases where it might be necessary to wait and see if a keypress event follows a keydown +// This information is collected into an object which is passed to the next() function. (one call per event) +function KeyEventDecoder(modifierState, next) { + "use strict"; + function sendAll(evts) { + for (var i = 0; i < evts.length; ++i) { + next(evts[i]); + } + } + function process(evt, type) { + var result = {type: type}; + var keyId = kbdUtil.getKey(evt); + if (keyId) { + result.keyId = keyId; + } + + var keysym = kbdUtil.getKeysym(evt); + + var hasModifier = modifierState.hasShortcutModifier() || !!modifierState.activeCharModifier(); + // Is this a case where we have to decide on the keysym right away, rather than waiting for the keypress? + // "special" keys like enter, tab or backspace don't send keypress events, + // and some browsers don't send keypresses at all if a modifier is down + if (keysym && (type !== 'keydown' || kbdUtil.nonCharacterKey(evt) || hasModifier)) { + result.keysym = keysym; + } + + var isShift = evt.keyCode === 0x10 || evt.key === 'Shift'; + + // Should we prevent the browser from handling the event? + // Doing so on a keydown (in most browsers) prevents keypress from being generated + // so only do that if we have to. + var suppress = !isShift && (type !== 'keydown' || modifierState.hasShortcutModifier() || !!kbdUtil.nonCharacterKey(evt)); + + // If a char modifier is down on a keydown, we need to insert a stall, + // so VerifyCharModifier knows to wait and see if a keypress is comnig + var stall = type === 'keydown' && modifierState.activeCharModifier() && !kbdUtil.nonCharacterKey(evt); + + // if a char modifier is pressed, get the keys it consists of (on Windows, AltGr is equivalent to Ctrl+Alt) + var active = modifierState.activeCharModifier(); + + // If we have a char modifier down, and we're able to determine a keysym reliably + // then (a) we know to treat the modifier as a char modifier, + // and (b) we'll have to "escape" the modifier to undo the modifier when sending the char. + if (active && keysym) { + var isCharModifier = false; + for (var i = 0; i < active.length; ++i) { + if (active[i] === keysym.keysym) { + isCharModifier = true; + } + } + if (type === 'keypress' && !isCharModifier) { + result.escape = modifierState.activeCharModifier(); + } + } + + if (stall) { + // insert a fake "stall" event + next({type: 'stall'}); + } + next(result); + + return suppress; + } + + return { + keydown: function(evt) { + sendAll(modifierState.keydown(evt)); + return process(evt, 'keydown'); + }, + keypress: function(evt) { + return process(evt, 'keypress'); + }, + keyup: function(evt) { + sendAll(modifierState.keyup(evt)); + return process(evt, 'keyup'); + }, + syncModifiers: function(evt) { + sendAll(modifierState.syncAny(evt)); + }, + releaseAll: function() { next({type: 'releaseall'}); } + }; +} + +// Combines keydown and keypress events where necessary to handle char modifiers. +// On some OS'es, a char modifier is sometimes used as a shortcut modifier. +// For example, on Windows, AltGr is synonymous with Ctrl-Alt. On a Danish keyboard layout, AltGr-2 yields a @, but Ctrl-Alt-D does nothing +// so when used with the '2' key, Ctrl-Alt counts as a char modifier (and should be escaped), but when used with 'D', it does not. +// The only way we can distinguish these cases is to wait and see if a keypress event arrives +// When we receive a "stall" event, wait a few ms before processing the next keydown. If a keypress has also arrived, merge the two +function VerifyCharModifier(next) { + "use strict"; + var queue = []; + var timer = null; + function process() { + if (timer) { + return; + } + + var delayProcess = function () { + clearTimeout(timer); + timer = null; + process(); + }; + + while (queue.length !== 0) { + var cur = queue[0]; + queue = queue.splice(1); + switch (cur.type) { + case 'stall': + // insert a delay before processing available events. + /* jshint loopfunc: true */ + timer = setTimeout(delayProcess, 5); + /* jshint loopfunc: false */ + return; + case 'keydown': + // is the next element a keypress? Then we should merge the two + if (queue.length !== 0 && queue[0].type === 'keypress') { + // Firefox sends keypress even when no char is generated. + // so, if keypress keysym is the same as we'd have guessed from keydown, + // the modifier didn't have any effect, and should not be escaped + if (queue[0].escape && (!cur.keysym || cur.keysym.keysym !== queue[0].keysym.keysym)) { + cur.escape = queue[0].escape; + } + cur.keysym = queue[0].keysym; + queue = queue.splice(1); + } + break; + } + + // swallow stall events, and pass all others to the next stage + if (cur.type !== 'stall') { + next(cur); + } + } + } + return function(evt) { + queue.push(evt); + process(); + }; +} + +// Keeps track of which keys we (and the server) believe are down +// When a keyup is received, match it against this list, to determine the corresponding keysym(s) +// in some cases, a single key may produce multiple keysyms, so the corresponding keyup event must release all of these chars +// key repeat events should be merged into a single entry. +// Because we can't always identify which entry a keydown or keyup event corresponds to, we sometimes have to guess +function TrackKeyState(next) { + "use strict"; + var state = []; + + return function (evt) { + var last = state.length !== 0 ? state[state.length-1] : null; + + switch (evt.type) { + case 'keydown': + // insert a new entry if last seen key was different. + if (!last || !evt.keyId || last.keyId !== evt.keyId) { + last = {keyId: evt.keyId, keysyms: {}}; + state.push(last); + } + if (evt.keysym) { + // make sure last event contains this keysym (a single "logical" keyevent + // can cause multiple key events to be sent to the VNC server) + last.keysyms[evt.keysym.keysym] = evt.keysym; + last.ignoreKeyPress = true; + next(evt); + } + break; + case 'keypress': + if (!last) { + last = {keyId: evt.keyId, keysyms: {}}; + state.push(last); + } + if (!evt.keysym) { + console.log('keypress with no keysym:', evt); + } + + // If we didn't expect a keypress, and already sent a keydown to the VNC server + // based on the keydown, make sure to skip this event. + if (evt.keysym && !last.ignoreKeyPress) { + last.keysyms[evt.keysym.keysym] = evt.keysym; + evt.type = 'keydown'; + next(evt); + } + break; + case 'keyup': + if (state.length === 0) { + return; + } + var idx = null; + // do we have a matching key tracked as being down? + for (var i = 0; i !== state.length; ++i) { + if (state[i].keyId === evt.keyId) { + idx = i; + break; + } + } + // if we couldn't find a match (it happens), assume it was the last key pressed + if (idx === null) { + idx = state.length - 1; + } + + var item = state.splice(idx, 1)[0]; + // for each keysym tracked by this key entry, clone the current event and override the keysym + var clone = (function(){ + function Clone(){} + return function (obj) { Clone.prototype=obj; return new Clone(); }; + }()); + for (var key in item.keysyms) { + var out = clone(evt); + out.keysym = item.keysyms[key]; + next(out); + } + break; + case 'releaseall': + /* jshint shadow: true */ + for (var i = 0; i < state.length; ++i) { + for (var key in state[i].keysyms) { + var keysym = state[i].keysyms[key]; + next({keyId: 0, keysym: keysym, type: 'keyup'}); + } + } + /* jshint shadow: false */ + state = []; + } + }; +} + +// Handles "escaping" of modifiers: if a char modifier is used to produce a keysym (such as AltGr-2 to generate an @), +// then the modifier must be "undone" before sending the @, and "redone" afterwards. +function EscapeModifiers(next) { + "use strict"; + return function(evt) { + if (evt.type !== 'keydown' || evt.escape === undefined) { + next(evt); + return; + } + // undo modifiers + for (var i = 0; i < evt.escape.length; ++i) { + next({type: 'keyup', keyId: 0, keysym: keysyms.lookup(evt.escape[i])}); + } + // send the character event + next(evt); + // redo modifiers + /* jshint shadow: true */ + for (var i = 0; i < evt.escape.length; ++i) { + next({type: 'keydown', keyId: 0, keysym: keysyms.lookup(evt.escape[i])}); + } + /* jshint shadow: false */ + }; +} diff --git a/webclients/novnc/include/keysym.js b/webclients/novnc/include/keysym.js new file mode 100644 index 0000000..a00d595 --- /dev/null +++ b/webclients/novnc/include/keysym.js @@ -0,0 +1,376 @@ +var XK_VoidSymbol = 0xffffff, /* Void symbol */ + +XK_BackSpace = 0xff08, /* Back space, back char */ +XK_Tab = 0xff09, +XK_Linefeed = 0xff0a, /* Linefeed, LF */ +XK_Clear = 0xff0b, +XK_Return = 0xff0d, /* Return, enter */ +XK_Pause = 0xff13, /* Pause, hold */ +XK_Scroll_Lock = 0xff14, +XK_Sys_Req = 0xff15, +XK_Escape = 0xff1b, +XK_Delete = 0xffff, /* Delete, rubout */ + +/* Cursor control & motion */ + +XK_Home = 0xff50, +XK_Left = 0xff51, /* Move left, left arrow */ +XK_Up = 0xff52, /* Move up, up arrow */ +XK_Right = 0xff53, /* Move right, right arrow */ +XK_Down = 0xff54, /* Move down, down arrow */ +XK_Prior = 0xff55, /* Prior, previous */ +XK_Page_Up = 0xff55, +XK_Next = 0xff56, /* Next */ +XK_Page_Down = 0xff56, +XK_End = 0xff57, /* EOL */ +XK_Begin = 0xff58, /* BOL */ + + +/* Misc functions */ + +XK_Select = 0xff60, /* Select, mark */ +XK_Print = 0xff61, +XK_Execute = 0xff62, /* Execute, run, do */ +XK_Insert = 0xff63, /* Insert, insert here */ +XK_Undo = 0xff65, +XK_Redo = 0xff66, /* Redo, again */ +XK_Menu = 0xff67, +XK_Find = 0xff68, /* Find, search */ +XK_Cancel = 0xff69, /* Cancel, stop, abort, exit */ +XK_Help = 0xff6a, /* Help */ +XK_Break = 0xff6b, +XK_Mode_switch = 0xff7e, /* Character set switch */ +XK_script_switch = 0xff7e, /* Alias for mode_switch */ +XK_Num_Lock = 0xff7f, + +/* Keypad functions, keypad numbers cleverly chosen to map to ASCII */ + +XK_KP_Space = 0xff80, /* Space */ +XK_KP_Tab = 0xff89, +XK_KP_Enter = 0xff8d, /* Enter */ +XK_KP_F1 = 0xff91, /* PF1, KP_A, ... */ +XK_KP_F2 = 0xff92, +XK_KP_F3 = 0xff93, +XK_KP_F4 = 0xff94, +XK_KP_Home = 0xff95, +XK_KP_Left = 0xff96, +XK_KP_Up = 0xff97, +XK_KP_Right = 0xff98, +XK_KP_Down = 0xff99, +XK_KP_Prior = 0xff9a, +XK_KP_Page_Up = 0xff9a +XK_KP_Next = 0xff9b, +XK_KP_Page_Down = 0xff9b, +XK_KP_End = 0xff9c, +XK_KP_Begin = 0xff9d, +XK_KP_Insert = 0xff9e, +XK_KP_Delete = 0xff9f, +XK_KP_Equal = 0xffbd, /* Equals */ +XK_KP_Multiply = 0xffaa, +XK_KP_Add = 0xffab, +XK_KP_Separator = 0xffac, /* Separator, often comma */ +XK_KP_Subtract = 0xffad, +XK_KP_Decimal = 0xffae, +XK_KP_Divide = 0xffaf, + +XK_KP_0 = 0xffb0, +XK_KP_1 = 0xffb1, +XK_KP_2 = 0xffb2, +XK_KP_3 = 0xffb3, +XK_KP_4 = 0xffb4, +XK_KP_5 = 0xffb5, +XK_KP_6 = 0xffb6, +XK_KP_7 = 0xffb7, +XK_KP_8 = 0xffb8, +XK_KP_9 = 0xffb9, + +/* + * Auxiliary functions; note the duplicate definitions for left and right + * function keys; Sun keyboards and a few other manufacturers have such + * function key groups on the left and/or right sides of the keyboard. + * We've not found a keyboard with more than 35 function keys total. + */ + +XK_F1 = 0xffbe, +XK_F2 = 0xffbf, +XK_F3 = 0xffc0, +XK_F4 = 0xffc1, +XK_F5 = 0xffc2, +XK_F6 = 0xffc3, +XK_F7 = 0xffc4, +XK_F8 = 0xffc5, +XK_F9 = 0xffc6, +XK_F10 = 0xffc7, +XK_F11 = 0xffc8, +XK_L1 = 0xffc8, +XK_F12 = 0xffc9, +XK_L2 = 0xffc9, +XK_F13 = 0xffca, +XK_L3 = 0xffca, +XK_F14 = 0xffcb, +XK_L4 = 0xffcb, +XK_F15 = 0xffcc, +XK_L5 = 0xffcc, +XK_F16 = 0xffcd, +XK_L6 = 0xffcd, +XK_F17 = 0xffce, +XK_L7 = 0xffce, +XK_F18 = 0xffcf, +XK_L8 = 0xffcf, +XK_F19 = 0xffd0, +XK_L9 = 0xffd0, +XK_F20 = 0xffd1, +XK_L10 = 0xffd1, +XK_F21 = 0xffd2, +XK_R1 = 0xffd2, +XK_F22 = 0xffd3, +XK_R2 = 0xffd3, +XK_F23 = 0xffd4, +XK_R3 = 0xffd4, +XK_F24 = 0xffd5, +XK_R4 = 0xffd5, +XK_F25 = 0xffd6, +XK_R5 = 0xffd6, +XK_F26 = 0xffd7, +XK_R6 = 0xffd7, +XK_F27 = 0xffd8, +XK_R7 = 0xffd8, +XK_F28 = 0xffd9, +XK_R8 = 0xffd9, +XK_F29 = 0xffda, +XK_R9 = 0xffda, +XK_F30 = 0xffdb, +XK_R10 = 0xffdb, +XK_F31 = 0xffdc, +XK_R11 = 0xffdc, +XK_F32 = 0xffdd, +XK_R12 = 0xffdd, +XK_F33 = 0xffde, +XK_R13 = 0xffde, +XK_F34 = 0xffdf, +XK_R14 = 0xffdf, +XK_F35 = 0xffe0, +XK_R15 = 0xffe0, + +/* Modifiers */ + +XK_Shift_L = 0xffe1, /* Left shift */ +XK_Shift_R = 0xffe2, /* Right shift */ +XK_Control_L = 0xffe3, /* Left control */ +XK_Control_R = 0xffe4, /* Right control */ +XK_Caps_Lock = 0xffe5, /* Caps lock */ +XK_Shift_Lock = 0xffe6, /* Shift lock */ + +XK_Meta_L = 0xffe7, /* Left meta */ +XK_Meta_R = 0xffe8, /* Right meta */ +XK_Alt_L = 0xffe9, /* Left alt */ +XK_Alt_R = 0xffea, /* Right alt */ +XK_Super_L = 0xffeb, /* Left super */ +XK_Super_R = 0xffec, /* Right super */ +XK_Hyper_L = 0xffed, /* Left hyper */ +XK_Hyper_R = 0xffee, /* Right hyper */ + +/* + * Latin 1 + * (ISO/IEC 8859-1 = Unicode U+0020..U+00FF) + * Byte 3 = 0 + */ + +XK_space = 0x0020, /* U+0020 SPACE */ +XK_exclam = 0x0021, /* U+0021 EXCLAMATION MARK */ +XK_quotedbl = 0x0022, /* U+0022 QUOTATION MARK */ +XK_numbersign = 0x0023, /* U+0023 NUMBER SIGN */ +XK_dollar = 0x0024, /* U+0024 DOLLAR SIGN */ +XK_percent = 0x0025, /* U+0025 PERCENT SIGN */ +XK_ampersand = 0x0026, /* U+0026 AMPERSAND */ +XK_apostrophe = 0x0027, /* U+0027 APOSTROPHE */ +XK_quoteright = 0x0027, /* deprecated */ +XK_parenleft = 0x0028, /* U+0028 LEFT PARENTHESIS */ +XK_parenright = 0x0029, /* U+0029 RIGHT PARENTHESIS */ +XK_asterisk = 0x002a, /* U+002A ASTERISK */ +XK_plus = 0x002b, /* U+002B PLUS SIGN */ +XK_comma = 0x002c, /* U+002C COMMA */ +XK_minus = 0x002d, /* U+002D HYPHEN-MINUS */ +XK_period = 0x002e, /* U+002E FULL STOP */ +XK_slash = 0x002f, /* U+002F SOLIDUS */ +XK_0 = 0x0030, /* U+0030 DIGIT ZERO */ +XK_1 = 0x0031, /* U+0031 DIGIT ONE */ +XK_2 = 0x0032, /* U+0032 DIGIT TWO */ +XK_3 = 0x0033, /* U+0033 DIGIT THREE */ +XK_4 = 0x0034, /* U+0034 DIGIT FOUR */ +XK_5 = 0x0035, /* U+0035 DIGIT FIVE */ +XK_6 = 0x0036, /* U+0036 DIGIT SIX */ +XK_7 = 0x0037, /* U+0037 DIGIT SEVEN */ +XK_8 = 0x0038, /* U+0038 DIGIT EIGHT */ +XK_9 = 0x0039, /* U+0039 DIGIT NINE */ +XK_colon = 0x003a, /* U+003A COLON */ +XK_semicolon = 0x003b, /* U+003B SEMICOLON */ +XK_less = 0x003c, /* U+003C LESS-THAN SIGN */ +XK_equal = 0x003d, /* U+003D EQUALS SIGN */ +XK_greater = 0x003e, /* U+003E GREATER-THAN SIGN */ +XK_question = 0x003f, /* U+003F QUESTION MARK */ +XK_at = 0x0040, /* U+0040 COMMERCIAL AT */ +XK_A = 0x0041, /* U+0041 LATIN CAPITAL LETTER A */ +XK_B = 0x0042, /* U+0042 LATIN CAPITAL LETTER B */ +XK_C = 0x0043, /* U+0043 LATIN CAPITAL LETTER C */ +XK_D = 0x0044, /* U+0044 LATIN CAPITAL LETTER D */ +XK_E = 0x0045, /* U+0045 LATIN CAPITAL LETTER E */ +XK_F = 0x0046, /* U+0046 LATIN CAPITAL LETTER F */ +XK_G = 0x0047, /* U+0047 LATIN CAPITAL LETTER G */ +XK_H = 0x0048, /* U+0048 LATIN CAPITAL LETTER H */ +XK_I = 0x0049, /* U+0049 LATIN CAPITAL LETTER I */ +XK_J = 0x004a, /* U+004A LATIN CAPITAL LETTER J */ +XK_K = 0x004b, /* U+004B LATIN CAPITAL LETTER K */ +XK_L = 0x004c, /* U+004C LATIN CAPITAL LETTER L */ +XK_M = 0x004d, /* U+004D LATIN CAPITAL LETTER M */ +XK_N = 0x004e, /* U+004E LATIN CAPITAL LETTER N */ +XK_O = 0x004f, /* U+004F LATIN CAPITAL LETTER O */ +XK_P = 0x0050, /* U+0050 LATIN CAPITAL LETTER P */ +XK_Q = 0x0051, /* U+0051 LATIN CAPITAL LETTER Q */ +XK_R = 0x0052, /* U+0052 LATIN CAPITAL LETTER R */ +XK_S = 0x0053, /* U+0053 LATIN CAPITAL LETTER S */ +XK_T = 0x0054, /* U+0054 LATIN CAPITAL LETTER T */ +XK_U = 0x0055, /* U+0055 LATIN CAPITAL LETTER U */ +XK_V = 0x0056, /* U+0056 LATIN CAPITAL LETTER V */ +XK_W = 0x0057, /* U+0057 LATIN CAPITAL LETTER W */ +XK_X = 0x0058, /* U+0058 LATIN CAPITAL LETTER X */ +XK_Y = 0x0059, /* U+0059 LATIN CAPITAL LETTER Y */ +XK_Z = 0x005a, /* U+005A LATIN CAPITAL LETTER Z */ +XK_bracketleft = 0x005b, /* U+005B LEFT SQUARE BRACKET */ +XK_backslash = 0x005c, /* U+005C REVERSE SOLIDUS */ +XK_bracketright = 0x005d, /* U+005D RIGHT SQUARE BRACKET */ +XK_asciicircum = 0x005e, /* U+005E CIRCUMFLEX ACCENT */ +XK_underscore = 0x005f, /* U+005F LOW LINE */ +XK_grave = 0x0060, /* U+0060 GRAVE ACCENT */ +XK_quoteleft = 0x0060, /* deprecated */ +XK_a = 0x0061, /* U+0061 LATIN SMALL LETTER A */ +XK_b = 0x0062, /* U+0062 LATIN SMALL LETTER B */ +XK_c = 0x0063, /* U+0063 LATIN SMALL LETTER C */ +XK_d = 0x0064, /* U+0064 LATIN SMALL LETTER D */ +XK_e = 0x0065, /* U+0065 LATIN SMALL LETTER E */ +XK_f = 0x0066, /* U+0066 LATIN SMALL LETTER F */ +XK_g = 0x0067, /* U+0067 LATIN SMALL LETTER G */ +XK_h = 0x0068, /* U+0068 LATIN SMALL LETTER H */ +XK_i = 0x0069, /* U+0069 LATIN SMALL LETTER I */ +XK_j = 0x006a, /* U+006A LATIN SMALL LETTER J */ +XK_k = 0x006b, /* U+006B LATIN SMALL LETTER K */ +XK_l = 0x006c, /* U+006C LATIN SMALL LETTER L */ +XK_m = 0x006d, /* U+006D LATIN SMALL LETTER M */ +XK_n = 0x006e, /* U+006E LATIN SMALL LETTER N */ +XK_o = 0x006f, /* U+006F LATIN SMALL LETTER O */ +XK_p = 0x0070, /* U+0070 LATIN SMALL LETTER P */ +XK_q = 0x0071, /* U+0071 LATIN SMALL LETTER Q */ +XK_r = 0x0072, /* U+0072 LATIN SMALL LETTER R */ +XK_s = 0x0073, /* U+0073 LATIN SMALL LETTER S */ +XK_t = 0x0074, /* U+0074 LATIN SMALL LETTER T */ +XK_u = 0x0075, /* U+0075 LATIN SMALL LETTER U */ +XK_v = 0x0076, /* U+0076 LATIN SMALL LETTER V */ +XK_w = 0x0077, /* U+0077 LATIN SMALL LETTER W */ +XK_x = 0x0078, /* U+0078 LATIN SMALL LETTER X */ +XK_y = 0x0079, /* U+0079 LATIN SMALL LETTER Y */ +XK_z = 0x007a, /* U+007A LATIN SMALL LETTER Z */ +XK_braceleft = 0x007b, /* U+007B LEFT CURLY BRACKET */ +XK_bar = 0x007c, /* U+007C VERTICAL LINE */ +XK_braceright = 0x007d, /* U+007D RIGHT CURLY BRACKET */ +XK_asciitilde = 0x007e, /* U+007E TILDE */ + +XK_nobreakspace = 0x00a0, /* U+00A0 NO-BREAK SPACE */ +XK_exclamdown = 0x00a1, /* U+00A1 INVERTED EXCLAMATION MARK */ +XK_cent = 0x00a2, /* U+00A2 CENT SIGN */ +XK_sterling = 0x00a3, /* U+00A3 POUND SIGN */ +XK_currency = 0x00a4, /* U+00A4 CURRENCY SIGN */ +XK_yen = 0x00a5, /* U+00A5 YEN SIGN */ +XK_brokenbar = 0x00a6, /* U+00A6 BROKEN BAR */ +XK_section = 0x00a7, /* U+00A7 SECTION SIGN */ +XK_diaeresis = 0x00a8, /* U+00A8 DIAERESIS */ +XK_copyright = 0x00a9, /* U+00A9 COPYRIGHT SIGN */ +XK_ordfeminine = 0x00aa, /* U+00AA FEMININE ORDINAL INDICATOR */ +XK_guillemotleft = 0x00ab, /* U+00AB LEFT-POINTING DOUBLE ANGLE QUOTATION MARK */ +XK_notsign = 0x00ac, /* U+00AC NOT SIGN */ +XK_hyphen = 0x00ad, /* U+00AD SOFT HYPHEN */ +XK_registered = 0x00ae, /* U+00AE REGISTERED SIGN */ +XK_macron = 0x00af, /* U+00AF MACRON */ +XK_degree = 0x00b0, /* U+00B0 DEGREE SIGN */ +XK_plusminus = 0x00b1, /* U+00B1 PLUS-MINUS SIGN */ +XK_twosuperior = 0x00b2, /* U+00B2 SUPERSCRIPT TWO */ +XK_threesuperior = 0x00b3, /* U+00B3 SUPERSCRIPT THREE */ +XK_acute = 0x00b4, /* U+00B4 ACUTE ACCENT */ +XK_mu = 0x00b5, /* U+00B5 MICRO SIGN */ +XK_paragraph = 0x00b6, /* U+00B6 PILCROW SIGN */ +XK_periodcentered = 0x00b7, /* U+00B7 MIDDLE DOT */ +XK_cedilla = 0x00b8, /* U+00B8 CEDILLA */ +XK_onesuperior = 0x00b9, /* U+00B9 SUPERSCRIPT ONE */ +XK_masculine = 0x00ba, /* U+00BA MASCULINE ORDINAL INDICATOR */ +XK_guillemotright = 0x00bb, /* U+00BB RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK */ +XK_onequarter = 0x00bc, /* U+00BC VULGAR FRACTION ONE QUARTER */ +XK_onehalf = 0x00bd, /* U+00BD VULGAR FRACTION ONE HALF */ +XK_threequarters = 0x00be, /* U+00BE VULGAR FRACTION THREE QUARTERS */ +XK_questiondown = 0x00bf, /* U+00BF INVERTED QUESTION MARK */ +XK_Agrave = 0x00c0, /* U+00C0 LATIN CAPITAL LETTER A WITH GRAVE */ +XK_Aacute = 0x00c1, /* U+00C1 LATIN CAPITAL LETTER A WITH ACUTE */ +XK_Acircumflex = 0x00c2, /* U+00C2 LATIN CAPITAL LETTER A WITH CIRCUMFLEX */ +XK_Atilde = 0x00c3, /* U+00C3 LATIN CAPITAL LETTER A WITH TILDE */ +XK_Adiaeresis = 0x00c4, /* U+00C4 LATIN CAPITAL LETTER A WITH DIAERESIS */ +XK_Aring = 0x00c5, /* U+00C5 LATIN CAPITAL LETTER A WITH RING ABOVE */ +XK_AE = 0x00c6, /* U+00C6 LATIN CAPITAL LETTER AE */ +XK_Ccedilla = 0x00c7, /* U+00C7 LATIN CAPITAL LETTER C WITH CEDILLA */ +XK_Egrave = 0x00c8, /* U+00C8 LATIN CAPITAL LETTER E WITH GRAVE */ +XK_Eacute = 0x00c9, /* U+00C9 LATIN CAPITAL LETTER E WITH ACUTE */ +XK_Ecircumflex = 0x00ca, /* U+00CA LATIN CAPITAL LETTER E WITH CIRCUMFLEX */ +XK_Ediaeresis = 0x00cb, /* U+00CB LATIN CAPITAL LETTER E WITH DIAERESIS */ +XK_Igrave = 0x00cc, /* U+00CC LATIN CAPITAL LETTER I WITH GRAVE */ +XK_Iacute = 0x00cd, /* U+00CD LATIN CAPITAL LETTER I WITH ACUTE */ +XK_Icircumflex = 0x00ce, /* U+00CE LATIN CAPITAL LETTER I WITH CIRCUMFLEX */ +XK_Idiaeresis = 0x00cf, /* U+00CF LATIN CAPITAL LETTER I WITH DIAERESIS */ +XK_ETH = 0x00d0, /* U+00D0 LATIN CAPITAL LETTER ETH */ +XK_Eth = 0x00d0, /* deprecated */ +XK_Ntilde = 0x00d1, /* U+00D1 LATIN CAPITAL LETTER N WITH TILDE */ +XK_Ograve = 0x00d2, /* U+00D2 LATIN CAPITAL LETTER O WITH GRAVE */ +XK_Oacute = 0x00d3, /* U+00D3 LATIN CAPITAL LETTER O WITH ACUTE */ +XK_Ocircumflex = 0x00d4, /* U+00D4 LATIN CAPITAL LETTER O WITH CIRCUMFLEX */ +XK_Otilde = 0x00d5, /* U+00D5 LATIN CAPITAL LETTER O WITH TILDE */ +XK_Odiaeresis = 0x00d6, /* U+00D6 LATIN CAPITAL LETTER O WITH DIAERESIS */ +XK_multiply = 0x00d7, /* U+00D7 MULTIPLICATION SIGN */ +XK_Oslash = 0x00d8, /* U+00D8 LATIN CAPITAL LETTER O WITH STROKE */ +XK_Ooblique = 0x00d8, /* U+00D8 LATIN CAPITAL LETTER O WITH STROKE */ +XK_Ugrave = 0x00d9, /* U+00D9 LATIN CAPITAL LETTER U WITH GRAVE */ +XK_Uacute = 0x00da, /* U+00DA LATIN CAPITAL LETTER U WITH ACUTE */ +XK_Ucircumflex = 0x00db, /* U+00DB LATIN CAPITAL LETTER U WITH CIRCUMFLEX */ +XK_Udiaeresis = 0x00dc, /* U+00DC LATIN CAPITAL LETTER U WITH DIAERESIS */ +XK_Yacute = 0x00dd, /* U+00DD LATIN CAPITAL LETTER Y WITH ACUTE */ +XK_THORN = 0x00de, /* U+00DE LATIN CAPITAL LETTER THORN */ +XK_Thorn = 0x00de, /* deprecated */ +XK_ssharp = 0x00df, /* U+00DF LATIN SMALL LETTER SHARP S */ +XK_agrave = 0x00e0, /* U+00E0 LATIN SMALL LETTER A WITH GRAVE */ +XK_aacute = 0x00e1, /* U+00E1 LATIN SMALL LETTER A WITH ACUTE */ +XK_acircumflex = 0x00e2, /* U+00E2 LATIN SMALL LETTER A WITH CIRCUMFLEX */ +XK_atilde = 0x00e3, /* U+00E3 LATIN SMALL LETTER A WITH TILDE */ +XK_adiaeresis = 0x00e4, /* U+00E4 LATIN SMALL LETTER A WITH DIAERESIS */ +XK_aring = 0x00e5, /* U+00E5 LATIN SMALL LETTER A WITH RING ABOVE */ +XK_ae = 0x00e6, /* U+00E6 LATIN SMALL LETTER AE */ +XK_ccedilla = 0x00e7, /* U+00E7 LATIN SMALL LETTER C WITH CEDILLA */ +XK_egrave = 0x00e8, /* U+00E8 LATIN SMALL LETTER E WITH GRAVE */ +XK_eacute = 0x00e9, /* U+00E9 LATIN SMALL LETTER E WITH ACUTE */ +XK_ecircumflex = 0x00ea, /* U+00EA LATIN SMALL LETTER E WITH CIRCUMFLEX */ +XK_ediaeresis = 0x00eb, /* U+00EB LATIN SMALL LETTER E WITH DIAERESIS */ +XK_igrave = 0x00ec, /* U+00EC LATIN SMALL LETTER I WITH GRAVE */ +XK_iacute = 0x00ed, /* U+00ED LATIN SMALL LETTER I WITH ACUTE */ +XK_icircumflex = 0x00ee, /* U+00EE LATIN SMALL LETTER I WITH CIRCUMFLEX */ +XK_idiaeresis = 0x00ef, /* U+00EF LATIN SMALL LETTER I WITH DIAERESIS */ +XK_eth = 0x00f0, /* U+00F0 LATIN SMALL LETTER ETH */ +XK_ntilde = 0x00f1, /* U+00F1 LATIN SMALL LETTER N WITH TILDE */ +XK_ograve = 0x00f2, /* U+00F2 LATIN SMALL LETTER O WITH GRAVE */ +XK_oacute = 0x00f3, /* U+00F3 LATIN SMALL LETTER O WITH ACUTE */ +XK_ocircumflex = 0x00f4, /* U+00F4 LATIN SMALL LETTER O WITH CIRCUMFLEX */ +XK_otilde = 0x00f5, /* U+00F5 LATIN SMALL LETTER O WITH TILDE */ +XK_odiaeresis = 0x00f6, /* U+00F6 LATIN SMALL LETTER O WITH DIAERESIS */ +XK_division = 0x00f7, /* U+00F7 DIVISION SIGN */ +XK_oslash = 0x00f8, /* U+00F8 LATIN SMALL LETTER O WITH STROKE */ +XK_ooblique = 0x00f8, /* U+00F8 LATIN SMALL LETTER O WITH STROKE */ +XK_ugrave = 0x00f9, /* U+00F9 LATIN SMALL LETTER U WITH GRAVE */ +XK_uacute = 0x00fa, /* U+00FA LATIN SMALL LETTER U WITH ACUTE */ +XK_ucircumflex = 0x00fb, /* U+00FB LATIN SMALL LETTER U WITH CIRCUMFLEX */ +XK_udiaeresis = 0x00fc, /* U+00FC LATIN SMALL LETTER U WITH DIAERESIS */ +XK_yacute = 0x00fd, /* U+00FD LATIN SMALL LETTER Y WITH ACUTE */ +XK_thorn = 0x00fe, /* U+00FE LATIN SMALL LETTER THORN */ +XK_ydiaeresis = 0x00ff; /* U+00FF LATIN SMALL LETTER Y WITH DIAERESIS */ diff --git a/webclients/novnc/include/keysymdef.js b/webclients/novnc/include/keysymdef.js new file mode 100644 index 0000000..f94445c --- /dev/null +++ b/webclients/novnc/include/keysymdef.js @@ -0,0 +1,15 @@ +// This file describes mappings from Unicode codepoints to the keysym values +// (and optionally, key names) expected by the RFB protocol +// How this file was generated: +// node /Users/jalf/dev/mi/novnc/utils/parse.js /opt/X11/include/X11/keysymdef.h +var keysyms = (function(){ + "use strict"; + var keynames = null; + var codepoints = {"32":32,"33":33,"34":34,"35":35,"36":36,"37":37,"38":38,"39":39,"40":40,"41":41,"42":42,"43":43,"44":44,"45":45,"46":46,"47":47,"48":48,"49":49,"50":50,"51":51,"52":52,"53":53,"54":54,"55":55,"56":56,"57":57,"58":58,"59":59,"60":60,"61":61,"62":62,"63":63,"64":64,"65":65,"66":66,"67":67,"68":68,"69":69,"70":70,"71":71,"72":72,"73":73,"74":74,"75":75,"76":76,"77":77,"78":78,"79":79,"80":80,"81":81,"82":82,"83":83,"84":84,"85":85,"86":86,"87":87,"88":88,"89":89,"90":90,"91":91,"92":92,"93":93,"94":94,"95":95,"96":96,"97":97,"98":98,"99":99,"100":100,"101":101,"102":102,"103":103,"104":104,"105":105,"106":106,"107":107,"108":108,"109":109,"110":110,"111":111,"112":112,"113":113,"114":114,"115":115,"116":116,"117":117,"118":118,"119":119,"120":120,"121":121,"122":122,"123":123,"124":124,"125":125,"126":126,"160":160,"161":161,"162":162,"163":163,"164":164,"165":165,"166":166,"167":167,"168":168,"169":169,"170":170,"171":171,"172":172,"173":173,"174":174,"175":175,"176":176,"177":177,"178":178,"179":179,"180":180,"181":181,"182":182,"183":183,"184":184,"185":185,"186":186,"187":187,"188":188,"189":189,"190":190,"191":191,"192":192,"193":193,"194":194,"195":195,"196":196,"197":197,"198":198,"199":199,"200":200,"201":201,"202":202,"203":203,"204":204,"205":205,"206":206,"207":207,"208":208,"209":209,"210":210,"211":211,"212":212,"213":213,"214":214,"215":215,"216":216,"217":217,"218":218,"219":219,"220":220,"221":221,"222":222,"223":223,"224":224,"225":225,"226":226,"227":227,"228":228,"229":229,"230":230,"231":231,"232":232,"233":233,"234":234,"235":235,"236":236,"237":237,"238":238,"239":239,"240":240,"241":241,"242":242,"243":243,"244":244,"245":245,"246":246,"247":247,"248":248,"249":249,"250":250,"251":251,"252":252,"253":253,"254":254,"255":255,"256":960,"257":992,"258":451,"259":483,"260":417,"261":433,"262":454,"263":486,"264":710,"265":742,"266":709,"267":741,"268":456,"269":488,"270":463,"271":495,"272":464,"273":496,"274":938,"275":954,"278":972,"279":1004,"280":458,"281":490,"282":460,"283":492,"284":728,"285":760,"286":683,"287":699,"288":725,"289":757,"290":939,"291":955,"292":678,"293":694,"294":673,"295":689,"296":933,"297":949,"298":975,"299":1007,"300":16777516,"301":16777517,"302":967,"303":999,"304":681,"305":697,"308":684,"309":700,"310":979,"311":1011,"312":930,"313":453,"314":485,"315":934,"316":950,"317":421,"318":437,"321":419,"322":435,"323":465,"324":497,"325":977,"326":1009,"327":466,"328":498,"330":957,"331":959,"332":978,"333":1010,"336":469,"337":501,"338":5052,"339":5053,"340":448,"341":480,"342":931,"343":947,"344":472,"345":504,"346":422,"347":438,"348":734,"349":766,"350":426,"351":442,"352":425,"353":441,"354":478,"355":510,"356":427,"357":443,"358":940,"359":956,"360":989,"361":1021,"362":990,"363":1022,"364":733,"365":765,"366":473,"367":505,"368":475,"369":507,"370":985,"371":1017,"372":16777588,"373":16777589,"374":16777590,"375":16777591,"376":5054,"377":428,"378":444,"379":431,"380":447,"381":430,"382":446,"399":16777615,"402":2294,"415":16777631,"416":16777632,"417":16777633,"431":16777647,"432":16777648,"437":16777653,"438":16777654,"439":16777655,"466":16777681,"486":16777702,"487":16777703,"601":16777817,"629":16777845,"658":16777874,"711":439,"728":418,"729":511,"731":434,"733":445,"901":1966,"902":1953,"904":1954,"905":1955,"906":1956,"908":1959,"910":1960,"911":1963,"912":1974,"913":1985,"914":1986,"915":1987,"916":1988,"917":1989,"918":1990,"919":1991,"920":1992,"921":1993,"922":1994,"923":1995,"924":1996,"925":1997,"926":1998,"927":1999,"928":2000,"929":2001,"931":2002,"932":2004,"933":2005,"934":2006,"935":2007,"936":2008,"937":2009,"938":1957,"939":1961,"940":1969,"941":1970,"942":1971,"943":1972,"944":1978,"945":2017,"946":2018,"947":2019,"948":2020,"949":2021,"950":2022,"951":2023,"952":2024,"953":2025,"954":2026,"955":2027,"956":2028,"957":2029,"958":2030,"959":2031,"960":2032,"961":2033,"962":2035,"963":2034,"964":2036,"965":2037,"966":2038,"967":2039,"968":2040,"969":2041,"970":1973,"971":1977,"972":1975,"973":1976,"974":1979,"1025":1715,"1026":1713,"1027":1714,"1028":1716,"1029":1717,"1030":1718,"1031":1719,"1032":1720,"1033":1721,"1034":1722,"1035":1723,"1036":1724,"1038":1726,"1039":1727,"1040":1761,"1041":1762,"1042":1783,"1043":1767,"1044":1764,"1045":1765,"1046":1782,"1047":1786,"1048":1769,"1049":1770,"1050":1771,"1051":1772,"1052":1773,"1053":1774,"1054":1775,"1055":1776,"1056":1778,"1057":1779,"1058":1780,"1059":1781,"1060":1766,"1061":1768,"1062":1763,"1063":1790,"1064":1787,"1065":1789,"1066":1791,"1067":1785,"1068":1784,"1069":1788,"1070":1760,"1071":1777,"1072":1729,"1073":1730,"1074":1751,"1075":1735,"1076":1732,"1077":1733,"1078":1750,"1079":1754,"1080":1737,"1081":1738,"1082":1739,"1083":1740,"1084":1741,"1085":1742,"1086":1743,"1087":1744,"1088":1746,"1089":1747,"1090":1748,"1091":1749,"1092":1734,"1093":1736,"1094":1731,"1095":1758,"1096":1755,"1097":1757,"1098":1759,"1099":1753,"1100":1752,"1101":1756,"1102":1728,"1103":1745,"1105":1699,"1106":1697,"1107":1698,"1108":1700,"1109":1701,"1110":1702,"1111":1703,"1112":1704,"1113":1705,"1114":1706,"1115":1707,"1116":1708,"1118":1710,"1119":1711,"1168":1725,"1169":1709,"1170":16778386,"1171":16778387,"1174":16778390,"1175":16778391,"1178":16778394,"1179":16778395,"1180":16778396,"1181":16778397,"1186":16778402,"1187":16778403,"1198":16778414,"1199":16778415,"1200":16778416,"1201":16778417,"1202":16778418,"1203":16778419,"1206":16778422,"1207":16778423,"1208":16778424,"1209":16778425,"1210":16778426,"1211":16778427,"1240":16778456,"1241":16778457,"1250":16778466,"1251":16778467,"1256":16778472,"1257":16778473,"1262":16778478,"1263":16778479,"1329":16778545,"1330":16778546,"1331":16778547,"1332":16778548,"1333":16778549,"1334":16778550,"1335":16778551,"1336":16778552,"1337":16778553,"1338":16778554,"1339":16778555,"1340":16778556,"1341":16778557,"1342":16778558,"1343":16778559,"1344":16778560,"1345":16778561,"1346":16778562,"1347":16778563,"1348":16778564,"1349":16778565,"1350":16778566,"1351":16778567,"1352":16778568,"1353":16778569,"1354":16778570,"1355":16778571,"1356":16778572,"1357":16778573,"1358":16778574,"1359":16778575,"1360":16778576,"1361":16778577,"1362":16778578,"1363":16778579,"1364":16778580,"1365":16778581,"1366":16778582,"1370":16778586,"1371":16778587,"1372":16778588,"1373":16778589,"1374":16778590,"1377":16778593,"1378":16778594,"1379":16778595,"1380":16778596,"1381":16778597,"1382":16778598,"1383":16778599,"1384":16778600,"1385":16778601,"1386":16778602,"1387":16778603,"1388":16778604,"1389":16778605,"1390":16778606,"1391":16778607,"1392":16778608,"1393":16778609,"1394":16778610,"1395":16778611,"1396":16778612,"1397":16778613,"1398":16778614,"1399":16778615,"1400":16778616,"1401":16778617,"1402":16778618,"1403":16778619,"1404":16778620,"1405":16778621,"1406":16778622,"1407":16778623,"1408":16778624,"1409":16778625,"1410":16778626,"1411":16778627,"1412":16778628,"1413":16778629,"1414":16778630,"1415":16778631,"1417":16778633,"1418":16778634,"1488":3296,"1489":3297,"1490":3298,"1491":3299,"1492":3300,"1493":3301,"1494":3302,"1495":3303,"1496":3304,"1497":3305,"1498":3306,"1499":3307,"1500":3308,"1501":3309,"1502":3310,"1503":3311,"1504":3312,"1505":3313,"1506":3314,"1507":3315,"1508":3316,"1509":3317,"1510":3318,"1511":3319,"1512":3320,"1513":3321,"1514":3322,"1548":1452,"1563":1467,"1567":1471,"1569":1473,"1570":1474,"1571":1475,"1572":1476,"1573":1477,"1574":1478,"1575":1479,"1576":1480,"1577":1481,"1578":1482,"1579":1483,"1580":1484,"1581":1485,"1582":1486,"1583":1487,"1584":1488,"1585":1489,"1586":1490,"1587":1491,"1588":1492,"1589":1493,"1590":1494,"1591":1495,"1592":1496,"1593":1497,"1594":1498,"1600":1504,"1601":1505,"1602":1506,"1603":1507,"1604":1508,"1605":1509,"1606":1510,"1607":1511,"1608":1512,"1609":1513,"1610":1514,"1611":1515,"1612":1516,"1613":1517,"1614":1518,"1615":1519,"1616":1520,"1617":1521,"1618":1522,"1619":16778835,"1620":16778836,"1621":16778837,"1632":16778848,"1633":16778849,"1634":16778850,"1635":16778851,"1636":16778852,"1637":16778853,"1638":16778854,"1639":16778855,"1640":16778856,"1641":16778857,"1642":16778858,"1648":16778864,"1657":16778873,"1662":16778878,"1670":16778886,"1672":16778888,"1681":16778897,"1688":16778904,"1700":16778916,"1705":16778921,"1711":16778927,"1722":16778938,"1726":16778942,"1729":16778945,"1740":16778956,"1746":16778962,"1748":16778964,"1776":16778992,"1777":16778993,"1778":16778994,"1779":16778995,"1780":16778996,"1781":16778997,"1782":16778998,"1783":16778999,"1784":16779000,"1785":16779001,"3458":16780674,"3459":16780675,"3461":16780677,"3462":16780678,"3463":16780679,"3464":16780680,"3465":16780681,"3466":16780682,"3467":16780683,"3468":16780684,"3469":16780685,"3470":16780686,"3471":16780687,"3472":16780688,"3473":16780689,"3474":16780690,"3475":16780691,"3476":16780692,"3477":16780693,"3478":16780694,"3482":16780698,"3483":16780699,"3484":16780700,"3485":16780701,"3486":16780702,"3487":16780703,"3488":16780704,"3489":16780705,"3490":16780706,"3491":16780707,"3492":16780708,"3493":16780709,"3494":16780710,"3495":16780711,"3496":16780712,"3497":16780713,"3498":16780714,"3499":16780715,"3500":16780716,"3501":16780717,"3502":16780718,"3503":16780719,"3504":16780720,"3505":16780721,"3507":16780723,"3508":16780724,"3509":16780725,"3510":16780726,"3511":16780727,"3512":16780728,"3513":16780729,"3514":16780730,"3515":16780731,"3517":16780733,"3520":16780736,"3521":16780737,"3522":16780738,"3523":16780739,"3524":16780740,"3525":16780741,"3526":16780742,"3530":16780746,"3535":16780751,"3536":16780752,"3537":16780753,"3538":16780754,"3539":16780755,"3540":16780756,"3542":16780758,"3544":16780760,"3545":16780761,"3546":16780762,"3547":16780763,"3548":16780764,"3549":16780765,"3550":16780766,"3551":16780767,"3570":16780786,"3571":16780787,"3572":16780788,"3585":3489,"3586":3490,"3587":3491,"3588":3492,"3589":3493,"3590":3494,"3591":3495,"3592":3496,"3593":3497,"3594":3498,"3595":3499,"3596":3500,"3597":3501,"3598":3502,"3599":3503,"3600":3504,"3601":3505,"3602":3506,"3603":3507,"3604":3508,"3605":3509,"3606":3510,"3607":3511,"3608":3512,"3609":3513,"3610":3514,"3611":3515,"3612":3516,"3613":3517,"3614":3518,"3615":3519,"3616":3520,"3617":3521,"3618":3522,"3619":3523,"3620":3524,"3621":3525,"3622":3526,"3623":3527,"3624":3528,"3625":3529,"3626":3530,"3627":3531,"3628":3532,"3629":3533,"3630":3534,"3631":3535,"3632":3536,"3633":3537,"3634":3538,"3635":3539,"3636":3540,"3637":3541,"3638":3542,"3639":3543,"3640":3544,"3641":3545,"3642":3546,"3647":3551,"3648":3552,"3649":3553,"3650":3554,"3651":3555,"3652":3556,"3653":3557,"3654":3558,"3655":3559,"3656":3560,"3657":3561,"3658":3562,"3659":3563,"3660":3564,"3661":3565,"3664":3568,"3665":3569,"3666":3570,"3667":3571,"3668":3572,"3669":3573,"3670":3574,"3671":3575,"3672":3576,"3673":3577,"4304":16781520,"4305":16781521,"4306":16781522,"4307":16781523,"4308":16781524,"4309":16781525,"4310":16781526,"4311":16781527,"4312":16781528,"4313":16781529,"4314":16781530,"4315":16781531,"4316":16781532,"4317":16781533,"4318":16781534,"4319":16781535,"4320":16781536,"4321":16781537,"4322":16781538,"4323":16781539,"4324":16781540,"4325":16781541,"4326":16781542,"4327":16781543,"4328":16781544,"4329":16781545,"4330":16781546,"4331":16781547,"4332":16781548,"4333":16781549,"4334":16781550,"4335":16781551,"4336":16781552,"4337":16781553,"4338":16781554,"4339":16781555,"4340":16781556,"4341":16781557,"4342":16781558,"7682":16784898,"7683":16784899,"7690":16784906,"7691":16784907,"7710":16784926,"7711":16784927,"7734":16784950,"7735":16784951,"7744":16784960,"7745":16784961,"7766":16784982,"7767":16784983,"7776":16784992,"7777":16784993,"7786":16785002,"7787":16785003,"7808":16785024,"7809":16785025,"7810":16785026,"7811":16785027,"7812":16785028,"7813":16785029,"7818":16785034,"7819":16785035,"7840":16785056,"7841":16785057,"7842":16785058,"7843":16785059,"7844":16785060,"7845":16785061,"7846":16785062,"7847":16785063,"7848":16785064,"7849":16785065,"7850":16785066,"7851":16785067,"7852":16785068,"7853":16785069,"7854":16785070,"7855":16785071,"7856":16785072,"7857":16785073,"7858":16785074,"7859":16785075,"7860":16785076,"7861":16785077,"7862":16785078,"7863":16785079,"7864":16785080,"7865":16785081,"7866":16785082,"7867":16785083,"7868":16785084,"7869":16785085,"7870":16785086,"7871":16785087,"7872":16785088,"7873":16785089,"7874":16785090,"7875":16785091,"7876":16785092,"7877":16785093,"7878":16785094,"7879":16785095,"7880":16785096,"7881":16785097,"7882":16785098,"7883":16785099,"7884":16785100,"7885":16785101,"7886":16785102,"7887":16785103,"7888":16785104,"7889":16785105,"7890":16785106,"7891":16785107,"7892":16785108,"7893":16785109,"7894":16785110,"7895":16785111,"7896":16785112,"7897":16785113,"7898":16785114,"7899":16785115,"7900":16785116,"7901":16785117,"7902":16785118,"7903":16785119,"7904":16785120,"7905":16785121,"7906":16785122,"7907":16785123,"7908":16785124,"7909":16785125,"7910":16785126,"7911":16785127,"7912":16785128,"7913":16785129,"7914":16785130,"7915":16785131,"7916":16785132,"7917":16785133,"7918":16785134,"7919":16785135,"7920":16785136,"7921":16785137,"7922":16785138,"7923":16785139,"7924":16785140,"7925":16785141,"7926":16785142,"7927":16785143,"7928":16785144,"7929":16785145,"8194":2722,"8195":2721,"8196":2723,"8197":2724,"8199":2725,"8200":2726,"8201":2727,"8202":2728,"8210":2747,"8211":2730,"8212":2729,"8213":1967,"8215":3295,"8216":2768,"8217":2769,"8218":2813,"8220":2770,"8221":2771,"8222":2814,"8224":2801,"8225":2802,"8226":2790,"8229":2735,"8230":2734,"8240":2773,"8242":2774,"8243":2775,"8248":2812,"8254":1150,"8304":16785520,"8308":16785524,"8309":16785525,"8310":16785526,"8311":16785527,"8312":16785528,"8313":16785529,"8320":16785536,"8321":16785537,"8322":16785538,"8323":16785539,"8324":16785540,"8325":16785541,"8326":16785542,"8327":16785543,"8328":16785544,"8329":16785545,"8352":16785568,"8353":16785569,"8354":16785570,"8355":16785571,"8356":16785572,"8357":16785573,"8358":16785574,"8359":16785575,"8360":16785576,"8361":3839,"8362":16785578,"8363":16785579,"8364":8364,"8453":2744,"8470":1712,"8471":2811,"8478":2772,"8482":2761,"8531":2736,"8532":2737,"8533":2738,"8534":2739,"8535":2740,"8536":2741,"8537":2742,"8538":2743,"8539":2755,"8540":2756,"8541":2757,"8542":2758,"8592":2299,"8593":2300,"8594":2301,"8595":2302,"8658":2254,"8660":2253,"8706":2287,"8709":16785925,"8711":2245,"8712":16785928,"8713":16785929,"8715":16785931,"8728":3018,"8730":2262,"8731":16785947,"8732":16785948,"8733":2241,"8734":2242,"8743":2270,"8744":2271,"8745":2268,"8746":2269,"8747":2239,"8748":16785964,"8749":16785965,"8756":2240,"8757":16785973,"8764":2248,"8771":2249,"8773":16785992,"8775":16785991,"8800":2237,"8801":2255,"8802":16786018,"8803":16786019,"8804":2236,"8805":2238,"8834":2266,"8835":2267,"8866":3068,"8867":3036,"8868":3010,"8869":3022,"8968":3027,"8970":3012,"8981":2810,"8992":2212,"8993":2213,"9109":3020,"9115":2219,"9117":2220,"9118":2221,"9120":2222,"9121":2215,"9123":2216,"9124":2217,"9126":2218,"9128":2223,"9132":2224,"9143":2209,"9146":2543,"9147":2544,"9148":2546,"9149":2547,"9225":2530,"9226":2533,"9227":2537,"9228":2531,"9229":2532,"9251":2732,"9252":2536,"9472":2211,"9474":2214,"9484":2210,"9488":2539,"9492":2541,"9496":2538,"9500":2548,"9508":2549,"9516":2551,"9524":2550,"9532":2542,"9618":2529,"9642":2791,"9643":2785,"9644":2779,"9645":2786,"9646":2783,"9647":2767,"9650":2792,"9651":2787,"9654":2781,"9655":2765,"9660":2793,"9661":2788,"9664":2780,"9665":2764,"9670":2528,"9675":2766,"9679":2782,"9702":2784,"9734":2789,"9742":2809,"9747":2762,"9756":2794,"9758":2795,"9792":2808,"9794":2807,"9827":2796,"9829":2798,"9830":2797,"9837":2806,"9839":2805,"10003":2803,"10007":2804,"10013":2777,"10016":2800,"10216":2748,"10217":2750,"10240":16787456,"10241":16787457,"10242":16787458,"10243":16787459,"10244":16787460,"10245":16787461,"10246":16787462,"10247":16787463,"10248":16787464,"10249":16787465,"10250":16787466,"10251":16787467,"10252":16787468,"10253":16787469,"10254":16787470,"10255":16787471,"10256":16787472,"10257":16787473,"10258":16787474,"10259":16787475,"10260":16787476,"10261":16787477,"10262":16787478,"10263":16787479,"10264":16787480,"10265":16787481,"10266":16787482,"10267":16787483,"10268":16787484,"10269":16787485,"10270":16787486,"10271":16787487,"10272":16787488,"10273":16787489,"10274":16787490,"10275":16787491,"10276":16787492,"10277":16787493,"10278":16787494,"10279":16787495,"10280":16787496,"10281":16787497,"10282":16787498,"10283":16787499,"10284":16787500,"10285":16787501,"10286":16787502,"10287":16787503,"10288":16787504,"10289":16787505,"10290":16787506,"10291":16787507,"10292":16787508,"10293":16787509,"10294":16787510,"10295":16787511,"10296":16787512,"10297":16787513,"10298":16787514,"10299":16787515,"10300":16787516,"10301":16787517,"10302":16787518,"10303":16787519,"10304":16787520,"10305":16787521,"10306":16787522,"10307":16787523,"10308":16787524,"10309":16787525,"10310":16787526,"10311":16787527,"10312":16787528,"10313":16787529,"10314":16787530,"10315":16787531,"10316":16787532,"10317":16787533,"10318":16787534,"10319":16787535,"10320":16787536,"10321":16787537,"10322":16787538,"10323":16787539,"10324":16787540,"10325":16787541,"10326":16787542,"10327":16787543,"10328":16787544,"10329":16787545,"10330":16787546,"10331":16787547,"10332":16787548,"10333":16787549,"10334":16787550,"10335":16787551,"10336":16787552,"10337":16787553,"10338":16787554,"10339":16787555,"10340":16787556,"10341":16787557,"10342":16787558,"10343":16787559,"10344":16787560,"10345":16787561,"10346":16787562,"10347":16787563,"10348":16787564,"10349":16787565,"10350":16787566,"10351":16787567,"10352":16787568,"10353":16787569,"10354":16787570,"10355":16787571,"10356":16787572,"10357":16787573,"10358":16787574,"10359":16787575,"10360":16787576,"10361":16787577,"10362":16787578,"10363":16787579,"10364":16787580,"10365":16787581,"10366":16787582,"10367":16787583,"10368":16787584,"10369":16787585,"10370":16787586,"10371":16787587,"10372":16787588,"10373":16787589,"10374":16787590,"10375":16787591,"10376":16787592,"10377":16787593,"10378":16787594,"10379":16787595,"10380":16787596,"10381":16787597,"10382":16787598,"10383":16787599,"10384":16787600,"10385":16787601,"10386":16787602,"10387":16787603,"10388":16787604,"10389":16787605,"10390":16787606,"10391":16787607,"10392":16787608,"10393":16787609,"10394":16787610,"10395":16787611,"10396":16787612,"10397":16787613,"10398":16787614,"10399":16787615,"10400":16787616,"10401":16787617,"10402":16787618,"10403":16787619,"10404":16787620,"10405":16787621,"10406":16787622,"10407":16787623,"10408":16787624,"10409":16787625,"10410":16787626,"10411":16787627,"10412":16787628,"10413":16787629,"10414":16787630,"10415":16787631,"10416":16787632,"10417":16787633,"10418":16787634,"10419":16787635,"10420":16787636,"10421":16787637,"10422":16787638,"10423":16787639,"10424":16787640,"10425":16787641,"10426":16787642,"10427":16787643,"10428":16787644,"10429":16787645,"10430":16787646,"10431":16787647,"10432":16787648,"10433":16787649,"10434":16787650,"10435":16787651,"10436":16787652,"10437":16787653,"10438":16787654,"10439":16787655,"10440":16787656,"10441":16787657,"10442":16787658,"10443":16787659,"10444":16787660,"10445":16787661,"10446":16787662,"10447":16787663,"10448":16787664,"10449":16787665,"10450":16787666,"10451":16787667,"10452":16787668,"10453":16787669,"10454":16787670,"10455":16787671,"10456":16787672,"10457":16787673,"10458":16787674,"10459":16787675,"10460":16787676,"10461":16787677,"10462":16787678,"10463":16787679,"10464":16787680,"10465":16787681,"10466":16787682,"10467":16787683,"10468":16787684,"10469":16787685,"10470":16787686,"10471":16787687,"10472":16787688,"10473":16787689,"10474":16787690,"10475":16787691,"10476":16787692,"10477":16787693,"10478":16787694,"10479":16787695,"10480":16787696,"10481":16787697,"10482":16787698,"10483":16787699,"10484":16787700,"10485":16787701,"10486":16787702,"10487":16787703,"10488":16787704,"10489":16787705,"10490":16787706,"10491":16787707,"10492":16787708,"10493":16787709,"10494":16787710,"10495":16787711,"12289":1188,"12290":1185,"12300":1186,"12301":1187,"12443":1246,"12444":1247,"12449":1191,"12450":1201,"12451":1192,"12452":1202,"12453":1193,"12454":1203,"12455":1194,"12456":1204,"12457":1195,"12458":1205,"12459":1206,"12461":1207,"12463":1208,"12465":1209,"12467":1210,"12469":1211,"12471":1212,"12473":1213,"12475":1214,"12477":1215,"12479":1216,"12481":1217,"12483":1199,"12484":1218,"12486":1219,"12488":1220,"12490":1221,"12491":1222,"12492":1223,"12493":1224,"12494":1225,"12495":1226,"12498":1227,"12501":1228,"12504":1229,"12507":1230,"12510":1231,"12511":1232,"12512":1233,"12513":1234,"12514":1235,"12515":1196,"12516":1236,"12517":1197,"12518":1237,"12519":1198,"12520":1238,"12521":1239,"12522":1240,"12523":1241,"12524":1242,"12525":1243,"12527":1244,"12530":1190,"12531":1245,"12539":1189,"12540":1200}; + + function lookup(k) { return k ? {keysym: k, keyname: keynames ? keynames[k] : k} : undefined; } + return { + fromUnicode : function(u) { return lookup(codepoints[u]); }, + lookup : lookup + }; +})(); diff --git a/webclients/novnc/include/playback.js b/webclients/novnc/include/playback.js index a21c7b6..7756529 100644 --- a/webclients/novnc/include/playback.js +++ b/webclients/novnc/include/playback.js @@ -1,7 +1,7 @@ /* * noVNC: HTML5 VNC client * Copyright (C) 2012 Joel Martin - * Licensed under LGPL-3 (see LICENSE.LGPL-3) + * Licensed under MPL 2.0 (see LICENSE.txt) */ "use strict"; @@ -79,10 +79,22 @@ queue_next_packet = function () { } }; +var bytes_processed = 0; + do_packet = function () { //Util.Debug("Processing frame: " + frame_idx); - var frame = VNC_frame_data[frame_idx]; - rfb.recv_message({'data' : frame.slice(frame.indexOf('{', 1) + 1)}); + var frame = VNC_frame_data[frame_idx], + start = frame.indexOf('{', 1) + 1; + bytes_processed += frame.length - start; + if (VNC_frame_encoding === 'binary') { + var u8 = new Uint8Array(frame.length - start); + for (var i = 0; i < frame.length - start; i++) { + u8[i] = frame.charCodeAt(start + i); + } + rfb.recv_message({'data' : u8}); + } else { + rfb.recv_message({'data' : frame.slice(start)}); + } frame_idx += 1; queue_next_packet(); diff --git a/webclients/novnc/include/rfb.js b/webclients/novnc/include/rfb.js index 00fb7d8..0afe656 100644 --- a/webclients/novnc/include/rfb.js +++ b/webclients/novnc/include/rfb.js @@ -1,7 +1,8 @@ /* * noVNC: HTML5 VNC client * Copyright (C) 2012 Joel Martin - * Licensed under LGPL-3 (see LICENSE.txt) + * Copyright (C) 2013 Samuel Mannehed for Cendio AB + * Licensed under MPL 2.0 (see LICENSE.txt) * * See README.md for usage and integration instructions. * @@ -9,1841 +10,1874 @@ * (c) 2012 Michael Tinglof, Joe Balaz, Les Piech (Mercuri.ca) */ -/*jslint white: false, browser: true, bitwise: false, plusplus: false */ +/*jslint white: false, browser: true */ /*global window, Util, Display, Keyboard, Mouse, Websock, Websock_native, Base64, DES */ +var RFB; -function RFB(defaults) { -"use strict"; - -var that = {}, // Public API methods - conf = {}, // Configuration attributes - - // Pre-declare private functions used before definitions (jslint) - init_vars, updateState, fail, handle_message, - init_msg, normal_msg, framebufferUpdate, print_stats, - - pixelFormat, clientEncodings, fbUpdateRequest, fbUpdateRequests, - keyEvent, pointerEvent, clientCutText, - - getTightCLength, extract_data_uri, - keyPress, mouseButton, mouseMove, - - checkEvents, // Overridable for testing - - - // - // Private RFB namespace variables - // - rfb_host = '', - rfb_port = 5900, - rfb_password = '', - rfb_path = '', - - rfb_state = 'disconnected', - rfb_version = 0, - rfb_max_version= 3.8, - rfb_auth_scheme= '', - - - // In preference order - encodings = [ - ['COPYRECT', 0x01 ], - ['TIGHT', 0x07 ], - ['TIGHT_PNG', -260 ], - ['HEXTILE', 0x05 ], - ['RRE', 0x02 ], - ['RAW', 0x00 ], - ['DesktopSize', -223 ], - ['Cursor', -239 ], - - // Psuedo-encoding settings - //['JPEG_quality_lo', -32 ], - ['JPEG_quality_med', -26 ], - //['JPEG_quality_hi', -23 ], - //['compress_lo', -255 ], - ['compress_hi', -247 ], - ['last_rect', -224 ] - ], - - encHandlers = {}, - encNames = {}, - encStats = {}, // [rectCnt, rectCntTot] - - ws = null, // Websock object - display = null, // Display object - keyboard = null, // Keyboard input handler object - mouse = null, // Mouse input handler object - sendTimer = null, // Send Queue check timer - connTimer = null, // connection timer - disconnTimer = null, // disconnection timer - msgTimer = null, // queued handle_message timer - - // Frame buffer update state - FBU = { - rects : 0, - subrects : 0, // RRE - lines : 0, // RAW - tiles : 0, // HEXTILE - bytes : 0, - x : 0, - y : 0, - width : 0, - height : 0, - encoding : 0, - subencoding : -1, - background : null, - zlibs : [] // TIGHT zlib streams - }, - - fb_Bpp = 4, - fb_depth = 3, - fb_width = 0, - fb_height = 0, - fb_name = "", - - last_req_time = 0, - rre_chunk_sz = 100, - - timing = { - last_fbu : 0, - fbu_total : 0, - fbu_total_cnt : 0, - full_fbu_total : 0, - full_fbu_cnt : 0, - - fbu_rt_start : 0, - fbu_rt_total : 0, - fbu_rt_cnt : 0, - pixels : 0 - }, - - test_mode = false, - - def_con_timeout = Websock_native ? 2 : 5, - - /* Mouse state */ - mouse_buttonMask = 0, - mouse_arr = [], - viewportDragging = false, - viewportDragPos = {}; - -// Configuration attributes -Util.conf_defaults(conf, that, defaults, [ - ['target', 'wo', 'dom', null, 'VNC display rendering Canvas object'], - ['focusContainer', 'wo', 'dom', document, 'DOM element that captures keyboard input'], - - ['encrypt', 'rw', 'bool', false, 'Use TLS/SSL/wss encryption'], - ['true_color', 'rw', 'bool', true, 'Request true color pixel data'], - ['local_cursor', 'rw', 'bool', false, 'Request locally rendered cursor'], - ['shared', 'rw', 'bool', true, 'Request shared mode'], - ['view_only', 'rw', 'bool', false, 'Disable client mouse/keyboard'], - - ['connectTimeout', 'rw', 'int', def_con_timeout, 'Time (s) to wait for connection'], - ['disconnectTimeout', 'rw', 'int', 3, 'Time (s) to wait for disconnection'], - - // UltraVNC repeater ID to connect to - ['repeaterID', 'rw', 'str', '', 'RepeaterID to connect to'], - - ['viewportDrag', 'rw', 'bool', false, 'Move the viewport on mouse drags'], - - ['check_rate', 'rw', 'int', 217, 'Timing (ms) of send/receive check'], - ['fbu_req_rate', 'rw', 'int', 1413, 'Timing (ms) of frameBufferUpdate requests'], - - // Callback functions - ['onUpdateState', 'rw', 'func', function() { }, - 'onUpdateState(rfb, state, oldstate, statusMsg): RFB state update/change '], - ['onPasswordRequired', 'rw', 'func', function() { }, - 'onPasswordRequired(rfb): VNC password is required '], - ['onClipboard', 'rw', 'func', function() { }, - 'onClipboard(rfb, text): RFB clipboard contents received'], - ['onBell', 'rw', 'func', function() { }, - 'onBell(rfb): RFB Bell message received '], - ['onFBUReceive', 'rw', 'func', function() { }, - 'onFBUReceive(rfb, fbu): RFB FBU received but not yet processed '], - ['onFBUComplete', 'rw', 'func', function() { }, - 'onFBUComplete(rfb, fbu): RFB FBU received and processed '], - - // These callback names are deprecated - ['updateState', 'rw', 'func', function() { }, - 'obsolete, use onUpdateState'], - ['clipboardReceive', 'rw', 'func', function() { }, - 'obsolete, use onClipboard'] - ]); +(function () { + "use strict"; + RFB = function (defaults) { + if (!defaults) { + defaults = {}; + } + this._rfb_host = ''; + this._rfb_port = 5900; + this._rfb_password = ''; + this._rfb_path = ''; + + this._rfb_state = 'disconnected'; + this._rfb_version = 0; + this._rfb_max_version = 3.8; + this._rfb_auth_scheme = ''; + + this._rfb_tightvnc = false; + this._rfb_xvp_ver = 0; + + // In preference order + this._encodings = [ + ['COPYRECT', 0x01 ], + ['TIGHT', 0x07 ], + ['TIGHT_PNG', -260 ], + ['HEXTILE', 0x05 ], + ['RRE', 0x02 ], + ['RAW', 0x00 ], + ['DesktopSize', -223 ], + ['Cursor', -239 ], + + // Psuedo-encoding settings + //['JPEG_quality_lo', -32 ], + ['JPEG_quality_med', -26 ], + //['JPEG_quality_hi', -23 ], + //['compress_lo', -255 ], + ['compress_hi', -247 ], + ['last_rect', -224 ], + ['xvp', -309 ] + ]; + + this._encHandlers = {}; + this._encNames = {}; + this._encStats = {}; + + this._sock = null; // Websock object + this._display = null; // Display object + this._keyboard = null; // Keyboard input handler object + this._mouse = null; // Mouse input handler object + this._sendTimer = null; // Send Queue check timer + this._disconnTimer = null; // disconnection timer + this._msgTimer = null; // queued handle_msg timer + + // Frame buffer update state + this._FBU = { + rects: 0, + subrects: 0, // RRE + lines: 0, // RAW + tiles: 0, // HEXTILE + bytes: 0, + x: 0, + y: 0, + width: 0, + height: 0, + encoding: 0, + subencoding: -1, + background: null, + zlib: [] // TIGHT zlib streams + }; -// Override/add some specific configuration getters/setters -that.set_local_cursor = function(cursor) { - if ((!cursor) || (cursor in {'0':1, 'no':1, 'false':1})) { - conf.local_cursor = false; - } else { - if (display.get_cursor_uri()) { - conf.local_cursor = true; - } else { - Util.Warn("Browser does not support local cursor"); - } - } -}; - -// These are fake configuration getters -that.get_display = function() { return display; }; - -that.get_keyboard = function() { return keyboard; }; - -that.get_mouse = function() { return mouse; }; - - - -// -// Setup routines -// - -// Create the public API interface and initialize values that stay -// constant across connect/disconnect -function constructor() { - var i, rmode; - Util.Debug(">> RFB.constructor"); - - // Create lookup tables based encoding number - for (i=0; i < encodings.length; i+=1) { - encHandlers[encodings[i][1]] = encHandlers[encodings[i][0]]; - encNames[encodings[i][1]] = encodings[i][0]; - encStats[encodings[i][1]] = [0, 0]; - } - // Initialize display, mouse, keyboard, and websock - try { - display = new Display({'target': conf.target}); - } catch (exc) { - Util.Error("Display exception: " + exc); - updateState('fatal', "No working Display"); - } - keyboard = new Keyboard({'target': conf.focusContainer, - 'onKeyPress': keyPress}); - mouse = new Mouse({'target': conf.target, - 'onMouseButton': mouseButton, - 'onMouseMove': mouseMove}); - - rmode = display.get_render_mode(); - - ws = new Websock(); - ws.on('message', handle_message); - ws.on('open', function() { - if (rfb_state === "connect") { - updateState('ProtocolVersion', "Starting VNC handshake"); - } else { - fail("Got unexpected WebSockets connection"); - } - }); - ws.on('close', function(e) { - Util.Warn("WebSocket on-close event"); - var msg = ""; - if (e.code) { - msg = " (code: " + e.code; - if (e.reason) { - msg += ", reason: " + e.reason; - } - msg += ")"; - } - if (rfb_state === 'disconnect') { - updateState('disconnected', 'VNC disconnected' + msg); - } else if (rfb_state === 'ProtocolVersion') { - fail('Failed to connect to server' + msg); - } else if (rfb_state in {'failed':1, 'disconnected':1}) { - Util.Error("Received onclose while disconnected" + msg); - } else { - fail('Server disconnected' + msg); + this._fb_Bpp = 4; + this._fb_depth = 3; + this._fb_width = 0; + this._fb_height = 0; + this._fb_name = ""; + + this._rre_chunk_sz = 100; + + this._timing = { + last_fbu: 0, + fbu_total: 0, + fbu_total_cnt: 0, + full_fbu_total: 0, + full_fbu_cnt: 0, + + fbu_rt_start: 0, + fbu_rt_total: 0, + fbu_rt_cnt: 0, + pixels: 0 + }; + + // Mouse state + this._mouse_buttonMask = 0; + this._mouse_arr = []; + this._viewportDragging = false; + this._viewportDragPos = {}; + + // set the default value on user-facing properties + Util.set_defaults(this, defaults, { + 'target': 'null', // VNC display rendering Canvas object + 'focusContainer': document, // DOM element that captures keyboard input + 'encrypt': false, // Use TLS/SSL/wss encryption + 'true_color': true, // Request true color pixel data + 'local_cursor': false, // Request locally rendered cursor + 'shared': true, // Request shared mode + 'view_only': false, // Disable client mouse/keyboard + 'xvp_password_sep': '@', // Separator for XVP password fields + 'disconnectTimeout': 3, // Time (s) to wait for disconnection + 'wsProtocols': ['binary', 'base64'], // Protocols to use in the WebSocket connection + 'repeaterID': '', // [UltraVNC] RepeaterID to connect to + 'viewportDrag': false, // Move the viewport on mouse drags + + // Callback functions + 'onUpdateState': function () { }, // onUpdateState(rfb, state, oldstate, statusMsg): state update/change + 'onPasswordRequired': function () { }, // onPasswordRequired(rfb): VNC password is required + 'onClipboard': function () { }, // onClipboard(rfb, text): RFB clipboard contents received + 'onBell': function () { }, // onBell(rfb): RFB Bell message received + 'onFBUReceive': function () { }, // onFBUReceive(rfb, fbu): RFB FBU received but not yet processed + 'onFBUComplete': function () { }, // onFBUComplete(rfb, fbu): RFB FBU received and processed + 'onFBResize': function () { }, // onFBResize(rfb, width, height): frame buffer resized + 'onDesktopName': function () { }, // onDesktopName(rfb, name): desktop name received + 'onXvpInit': function () { }, // onXvpInit(version): XVP extensions active for this connection + }); + + // main setup + Util.Debug(">> RFB.constructor"); + + // populate encHandlers with bound versions + Object.keys(RFB.encodingHandlers).forEach(function (encName) { + this._encHandlers[encName] = RFB.encodingHandlers[encName].bind(this); + }.bind(this)); + + // Create lookup tables based on encoding number + for (var i = 0; i < this._encodings.length; i++) { + this._encHandlers[this._encodings[i][1]] = this._encHandlers[this._encodings[i][0]]; + this._encNames[this._encodings[i][1]] = this._encodings[i][0]; + this._encStats[this._encodings[i][1]] = [0, 0]; } - }); - ws.on('error', function(e) { - Util.Warn("WebSocket on-error event"); - //fail("WebSock reported an error"); - }); - - - init_vars(); - - /* Check web-socket-js if no builtin WebSocket support */ - if (Websock_native) { - Util.Info("Using native WebSockets"); - updateState('loaded', 'noVNC ready: native WebSockets, ' + rmode); - } else { - Util.Warn("Using web-socket-js bridge. Flash version: " + - Util.Flash.version); - if ((! Util.Flash) || - (Util.Flash.version < 9)) { - updateState('fatal', "WebSockets or Adobe Flash<\/a> is required"); - } else if (document.location.href.substr(0, 7) === "file://") { - updateState('fatal', - "'file://' URL is incompatible with Adobe Flash"); - } else { - updateState('loaded', 'noVNC ready: WebSockets emulation, ' + rmode); + + try { + this._display = new Display({target: this._target}); + } catch (exc) { + Util.Error("Display exception: " + exc); + this._updateState('fatal', "No working Display"); } - } - - Util.Debug("<< RFB.constructor"); - return that; // Return the public API interface -} - -function connect() { - Util.Debug(">> RFB.connect"); - var uri; - - if (typeof UsingSocketIO !== "undefined") { - uri = "http://" + rfb_host + ":" + rfb_port + "/" + rfb_path; - } else { - if (conf.encrypt) { - uri = "wss://"; + + this._keyboard = new Keyboard({target: this._focusContainer, + onKeyPress: this._handleKeyPress.bind(this)}); + + this._mouse = new Mouse({target: this._target, + onMouseButton: this._handleMouseButton.bind(this), + onMouseMove: this._handleMouseMove.bind(this), + notify: this._keyboard.sync.bind(this._keyboard)}); + + this._sock = new Websock(); + this._sock.on('message', this._handle_message.bind(this)); + this._sock.on('open', function () { + if (this._rfb_state === 'connect') { + this._updateState('ProtocolVersion', "Starting VNC handshake"); + } else { + this._fail("Got unexpected WebSocket connection"); + } + }.bind(this)); + this._sock.on('close', function (e) { + Util.Warn("WebSocket on-close event"); + var msg = ""; + if (e.code) { + msg = " (code: " + e.code; + if (e.reason) { + msg += ", reason: " + e.reason; + } + msg += ")"; + } + if (this._rfb_state === 'disconnect') { + this._updateState('disconnected', 'VNC disconnected' + msg); + } else if (this._rfb_state === 'ProtocolVersion') { + this._fail('Failed to connect to server' + msg); + } else if (this._rfb_state in {'failed': 1, 'disconnected': 1}) { + Util.Error("Received onclose while disconnected" + msg); + } else { + this._fail("Server disconnected" + msg); + } + }.bind(this)); + this._sock.on('error', function (e) { + Util.Warn("WebSocket on-error event"); + }); + + this._init_vars(); + + var rmode = this._display.get_render_mode(); + if (Websock_native) { + Util.Info("Using native WebSockets"); + this._updateState('loaded', 'noVNC ready: native WebSockets, ' + rmode); } else { - uri = "ws://"; - } - uri += rfb_host + ":" + rfb_port + "/" + rfb_path; - } - Util.Info("connecting to " + uri); - ws.open(uri); - - Util.Debug("<< RFB.connect"); -} - -// Initialize variables that are reset before each connection -init_vars = function() { - var i; - - /* Reset state */ - ws.init(); - - FBU.rects = 0; - FBU.subrects = 0; // RRE and HEXTILE - FBU.lines = 0; // RAW - FBU.tiles = 0; // HEXTILE - FBU.zlibs = []; // TIGHT zlib encoders - mouse_buttonMask = 0; - mouse_arr = []; - - // Clear the per connection encoding stats - for (i=0; i < encodings.length; i+=1) { - encStats[encodings[i][1]][0] = 0; - } - - for (i=0; i < 4; i++) { - //FBU.zlibs[i] = new InflateStream(); - FBU.zlibs[i] = new TINF(); - FBU.zlibs[i].init(); - } -}; - -// Print statistics -print_stats = function() { - var i, s; - Util.Info("Encoding stats for this connection:"); - for (i=0; i < encodings.length; i+=1) { - s = encStats[encodings[i][1]]; - if ((s[0] + s[1]) > 0) { - Util.Info(" " + encodings[i][0] + ": " + - s[0] + " rects"); - } - } - Util.Info("Encoding stats since page load:"); - for (i=0; i < encodings.length; i+=1) { - s = encStats[encodings[i][1]]; - if ((s[0] + s[1]) > 0) { - Util.Info(" " + encodings[i][0] + ": " + - s[1] + " rects"); + Util.Warn("Using web-socket-js bridge. Flash version: " + Util.Flash.version); + if (!Util.Flash || Util.Flash.version < 9) { + this._updateState('fatal', "WebSockets or Adobe Flash is required"); + } else if (document.location.href.substr(0, 7) === 'file://') { + this._updateState('fatal', "'file://' URL is incompatible with Adobe Flash"); + } else { + this._updateState('loaded', 'noVNC ready: WebSockets emulation, ' + rmode); + } } - } -}; -// -// Utility routines -// + Util.Debug("<< RFB.constructor"); + }; + RFB.prototype = { + // Public methods + connect: function (host, port, password, path) { + this._rfb_host = host; + this._rfb_port = port; + this._rfb_password = (password !== undefined) ? password : ""; + this._rfb_path = (path !== undefined) ? path : ""; -/* - * Page states: - * loaded - page load, equivalent to disconnected - * disconnected - idle state - * connect - starting to connect (to ProtocolVersion) - * normal - connected - * disconnect - starting to disconnect - * failed - abnormal disconnect - * fatal - failed to load page, or fatal error - * - * RFB protocol initialization states: - * ProtocolVersion - * Security - * Authentication - * password - waiting for password, not part of RFB - * SecurityResult - * ClientInitialization - not triggered by server message - * ServerInitialization (to normal) - */ -updateState = function(state, statusMsg) { - var func, cmsg, oldstate = rfb_state; - - if (state === oldstate) { - /* Already here, ignore */ - Util.Debug("Already in state '" + state + "', ignoring."); - return; - } - - /* - * These are disconnected states. A previous connect may - * asynchronously cause a connection so make sure we are closed. - */ - if (state in {'disconnected':1, 'loaded':1, 'connect':1, - 'disconnect':1, 'failed':1, 'fatal':1}) { - if (sendTimer) { - clearInterval(sendTimer); - sendTimer = null; - } + if (!this._rfb_host || !this._rfb_port) { + return this._fail("Must set host and port"); + } - if (msgTimer) { - clearInterval(msgTimer); - msgTimer = null; - } + this._updateState('connect'); + }, + + disconnect: function () { + this._updateState('disconnect', 'Disconnecting'); + }, + + sendPassword: function (passwd) { + this._rfb_password = passwd; + this._rfb_state = 'Authentication'; + setTimeout(this._init_msg.bind(this), 1); + }, + + sendCtrlAltDel: function () { + if (this._rfb_state !== 'normal' || this._view_only) { return false; } + Util.Info("Sending Ctrl-Alt-Del"); + + var arr = []; + arr = arr.concat(RFB.messages.keyEvent(0xFFE3, 1)); // Control + arr = arr.concat(RFB.messages.keyEvent(0xFFE9, 1)); // Alt + arr = arr.concat(RFB.messages.keyEvent(0xFFFF, 1)); // Delete + arr = arr.concat(RFB.messages.keyEvent(0xFFFF, 0)); // Delete + arr = arr.concat(RFB.messages.keyEvent(0xFFE9, 0)); // Alt + arr = arr.concat(RFB.messages.keyEvent(0xFFE3, 0)); // Control + this._sock.send(arr); + }, + + xvpOp: function (ver, op) { + if (this._rfb_xvp_ver < ver) { return false; } + Util.Info("Sending XVP operation " + op + " (version " + ver + ")"); + this._sock.send_string("\xFA\x00" + String.fromCharCode(ver) + String.fromCharCode(op)); + return true; + }, + + xvpShutdown: function () { + return this.xvpOp(1, 2); + }, + + xvpReboot: function () { + return this.xvpOp(1, 3); + }, + + xvpReset: function () { + return this.xvpOp(1, 4); + }, + + // Send a key press. If 'down' is not specified then send a down key + // followed by an up key. + sendKey: function (code, down) { + if (this._rfb_state !== "normal" || this._view_only) { return false; } + var arr = []; + if (typeof down !== 'undefined') { + Util.Info("Sending key code (" + (down ? "down" : "up") + "): " + code); + arr = arr.concat(RFB.messages.keyEvent(code, down ? 1 : 0)); + } else { + Util.Info("Sending key code (down + up): " + code); + arr = arr.concat(RFB.messages.keyEvent(code, 1)); + arr = arr.concat(RFB.messages.keyEvent(code, 0)); + } + this._sock.send(arr); + }, + + clipboardPasteFrom: function (text) { + if (this._rfb_state !== 'normal') { return; } + this._sock.send(RFB.messages.clientCutText(text)); + }, - if (display && display.get_context()) { - keyboard.ungrab(); - mouse.ungrab(); - display.defaultCursor(); - if ((Util.get_logging() !== 'debug') || - (state === 'loaded')) { - // Show noVNC logo on load and when disconnected if - // debug is off - display.clear(); + // Private methods + + _connect: function () { + Util.Debug(">> RFB.connect"); + + var uri; + if (typeof UsingSocketIO !== 'undefined') { + uri = 'http'; + } else { + uri = this._encrypt ? 'wss' : 'ws'; } - } - ws.close(); - } - - if (oldstate === 'fatal') { - Util.Error("Fatal error, cannot continue"); - } - - if ((state === 'failed') || (state === 'fatal')) { - func = Util.Error; - } else { - func = Util.Warn; - } - - cmsg = typeof(statusMsg) !== 'undefined' ? (" Msg: " + statusMsg) : ""; - func("New state '" + state + "', was '" + oldstate + "'." + cmsg); - - if ((oldstate === 'failed') && (state === 'disconnected')) { - // Do disconnect action, but stay in failed state - rfb_state = 'failed'; - } else { - rfb_state = state; - } - - if (connTimer && (rfb_state !== 'connect')) { - Util.Debug("Clearing connect timer"); - clearInterval(connTimer); - connTimer = null; - } - - if (disconnTimer && (rfb_state !== 'disconnect')) { - Util.Debug("Clearing disconnect timer"); - clearInterval(disconnTimer); - disconnTimer = null; - } - - switch (state) { - case 'normal': - if ((oldstate === 'disconnected') || (oldstate === 'failed')) { - Util.Error("Invalid transition from 'disconnected' or 'failed' to 'normal'"); - } + uri += '://' + this._rfb_host + ':' + this._rfb_port + '/' + this._rfb_path; + Util.Info("connecting to " + uri); - break; + this._sock.open(uri, this._wsProtocols); + Util.Debug("<< RFB.connect"); + }, - case 'connect': - - connTimer = setTimeout(function () { - fail("Connect timeout"); - }, conf.connectTimeout * 1000); + _init_vars: function () { + // reset state + this._sock.init(); - init_vars(); - connect(); + this._FBU.rects = 0; + this._FBU.subrects = 0; // RRE and HEXTILE + this._FBU.lines = 0; // RAW + this._FBU.tiles = 0; // HEXTILE + this._FBU.zlibs = []; // TIGHT zlib encoders + this._mouse_buttonMask = 0; + this._mouse_arr = []; + this._rfb_tightvnc = false; - // WebSocket.onopen transitions to 'ProtocolVersion' - break; + // Clear the per connection encoding stats + var i; + for (i = 0; i < this._encodings.length; i++) { + this._encStats[this._encodings[i][1]][0] = 0; + } + + for (i = 0; i < 4; i++) { + this._FBU.zlibs[i] = new TINF(); + this._FBU.zlibs[i].init(); + } + }, + + _print_stats: function () { + Util.Info("Encoding stats for this connection:"); + var i, s; + for (i = 0; i < this._encodings.length; i++) { + s = this._encStats[this._encodings[i][1]]; + if (s[0] + s[1] > 0) { + Util.Info(" " + this._encodings[i][0] + ": " + s[0] + " rects"); + } + } + Util.Info("Encoding stats since page load:"); + for (i = 0; i < this._encodings.length; i++) { + s = this._encStats[this._encodings[i][1]]; + Util.Info(" " + this._encodings[i][0] + ": " + s[1] + " rects"); + } + }, - case 'disconnect': - if (! test_mode) { - disconnTimer = setTimeout(function () { - fail("Disconnect timeout"); - }, conf.disconnectTimeout * 1000); - } + /* + * Page states: + * loaded - page load, equivalent to disconnected + * disconnected - idle state + * connect - starting to connect (to ProtocolVersion) + * normal - connected + * disconnect - starting to disconnect + * failed - abnormal disconnect + * fatal - failed to load page, or fatal error + * + * RFB protocol initialization states: + * ProtocolVersion + * Security + * Authentication + * password - waiting for password, not part of RFB + * SecurityResult + * ClientInitialization - not triggered by server message + * ServerInitialization (to normal) + */ + _updateState: function (state, statusMsg) { + var oldstate = this._rfb_state; + + if (state === oldstate) { + // Already here, ignore + Util.Debug("Already in state '" + state + "', ignoring"); + } - print_stats(); + /* + * These are disconnected states. A previous connect may + * asynchronously cause a connection so make sure we are closed. + */ + if (state in {'disconnected': 1, 'loaded': 1, 'connect': 1, + 'disconnect': 1, 'failed': 1, 'fatal': 1}) { - // WebSocket.onclose transitions to 'disconnected' - break; + if (this._sendTimer) { + clearInterval(this._sendTimer); + this._sendTimer = null; + } + if (this._msgTimer) { + clearInterval(this._msgTimer); + this._msgTimer = null; + } - case 'failed': - if (oldstate === 'disconnected') { - Util.Error("Invalid transition from 'disconnected' to 'failed'"); - } - if (oldstate === 'normal') { - Util.Error("Error while connected."); - } - if (oldstate === 'init') { - Util.Error("Error while initializing."); - } + if (this._display && this._display.get_context()) { + this._keyboard.ungrab(); + this._mouse.ungrab(); + this._display.defaultCursor(); + if (Util.get_logging() !== 'debug' || state === 'loaded') { + // Show noVNC logo on load and when disconnected, unless in + // debug mode + this._display.clear(); + } + } - // Make sure we transition to disconnected - setTimeout(function() { updateState('disconnected'); }, 50); - - break; - - - default: - // No state change action to take - - } - - if ((oldstate === 'failed') && (state === 'disconnected')) { - // Leave the failed message - conf.updateState(that, state, oldstate); // Obsolete - conf.onUpdateState(that, state, oldstate); - } else { - conf.updateState(that, state, oldstate, statusMsg); // Obsolete - conf.onUpdateState(that, state, oldstate, statusMsg); - } -}; - -fail = function(msg) { - updateState('failed', msg); - return false; -}; - -handle_message = function() { - //Util.Debug(">> handle_message ws.rQlen(): " + ws.rQlen()); - //Util.Debug("ws.rQslice(0,20): " + ws.rQslice(0,20) + " (" + ws.rQlen() + ")"); - if (ws.rQlen() === 0) { - Util.Warn("handle_message called on empty receive queue"); - return; - } - switch (rfb_state) { - case 'disconnected': - case 'failed': - Util.Error("Got data while disconnected"); - break; - case 'normal': - if (normal_msg() && ws.rQlen() > 0) { - // true means we can continue processing - // Give other events a chance to run - if (msgTimer === null) { - Util.Debug("More data to process, creating timer"); - msgTimer = setTimeout(function () { - msgTimer = null; - handle_message(); - }, 10); - } else { - Util.Debug("More data to process, existing timer"); + this._sock.close(); } - } - break; - default: - init_msg(); - break; - } -}; - - -function genDES(password, challenge) { - var i, passwd = []; - for (i=0; i < password.length; i += 1) { - passwd.push(password.charCodeAt(i)); - } - return (new DES(passwd)).encrypt(challenge); -} - -function flushClient() { - if (mouse_arr.length > 0) { - //send(mouse_arr.concat(fbUpdateRequests())); - ws.send(mouse_arr); - setTimeout(function() { - ws.send(fbUpdateRequests()); - }, 50); - - mouse_arr = []; - return true; - } else { - return false; - } -} - -// overridable for testing -checkEvents = function() { - var now; - if (rfb_state === 'normal' && !viewportDragging) { - if (! flushClient()) { - now = new Date().getTime(); - if (now > last_req_time + conf.fbu_req_rate) { - last_req_time = now; - ws.send(fbUpdateRequests()); + + if (oldstate === 'fatal') { + Util.Error('Fatal error, cannot continue'); } - } - } - setTimeout(checkEvents, conf.check_rate); -}; - -keyPress = function(keysym, down) { - var arr; - - if (conf.view_only) { return; } // View only, skip keyboard events - - arr = keyEvent(keysym, down); - arr = arr.concat(fbUpdateRequests()); - ws.send(arr); -}; - -mouseButton = function(x, y, down, bmask) { - if (down) { - mouse_buttonMask |= bmask; - } else { - mouse_buttonMask ^= bmask; - } - - if (conf.viewportDrag) { - if (down && !viewportDragging) { - viewportDragging = true; - viewportDragPos = {'x': x, 'y': y}; - - // Skip sending mouse events - return; - } else { - viewportDragging = false; - ws.send(fbUpdateRequests()); // Force immediate redraw - } - } - if (conf.view_only) { return; } // View only, skip mouse events + var cmsg = typeof(statusMsg) !== 'undefined' ? (" Msg: " + statusMsg) : ""; + var fullmsg = "New state '" + state + "', was '" + oldstate + "'." + cmsg; + if (state === 'failed' || state === 'fatal') { + Util.Error(cmsg); + } else { + Util.Warn(cmsg); + } - mouse_arr = mouse_arr.concat( - pointerEvent(display.absX(x), display.absY(y)) ); - flushClient(); -}; + if (oldstate === 'failed' && state === 'disconnected') { + // do disconnect action, but stay in failed state + this._rfb_state = 'failed'; + } else { + this._rfb_state = state; + } -mouseMove = function(x, y) { - //Util.Debug('>> mouseMove ' + x + "," + y); - var deltaX, deltaY; + if (this._disconnTimer && this._rfb_state !== 'disconnect') { + Util.Debug("Clearing disconnect timer"); + clearTimeout(this._disconnTimer); + this._disconnTimer = null; + } - if (viewportDragging) { - //deltaX = x - viewportDragPos.x; // drag viewport - deltaX = viewportDragPos.x - x; // drag frame buffer - //deltaY = y - viewportDragPos.y; // drag viewport - deltaY = viewportDragPos.y - y; // drag frame buffer - viewportDragPos = {'x': x, 'y': y}; + switch (state) { + case 'normal': + if (oldstate === 'disconnected' || oldstate === 'failed') { + Util.Error("Invalid transition from 'disconnected' or 'failed' to 'normal'"); + } + break; + + case 'connect': + this._init_vars(); + this._connect(); + // WebSocket.onopen transitions to 'ProtocolVersion' + break; + + case 'disconnect': + this._disconnTimer = setTimeout(function () { + this._fail("Disconnect timeout"); + }.bind(this), this._disconnectTimeout * 1000); + + this._print_stats(); + + // WebSocket.onclose transitions to 'disconnected' + break; + + case 'failed': + if (oldstate === 'disconnected') { + Util.Error("Invalid transition from 'disconnected' to 'failed'"); + } else if (oldstate === 'normal') { + Util.Error("Error while connected."); + } else if (oldstate === 'init') { + Util.Error("Error while initializing."); + } - display.viewportChange(deltaX, deltaY); + // Make sure we transition to disconnected + setTimeout(function () { + this._updateState('disconnected'); + }.bind(this), 50); - // Skip sending mouse events - return; - } + break; - if (conf.view_only) { return; } // View only, skip mouse events + default: + // No state change action to take + } - mouse_arr = mouse_arr.concat( - pointerEvent(display.absX(x), display.absY(y)) ); -}; + if (oldstate === 'failed' && state === 'disconnected') { + this._onUpdateState(this, state, oldstate); + } else { + this._onUpdateState(this, state, oldstate, statusMsg); + } + }, + _fail: function (msg) { + this._updateState('failed', msg); + return false; + }, -// -// Server message handlers -// + _handle_message: function () { + if (this._sock.rQlen() === 0) { + Util.Warn("handle_message called on an empty receive queue"); + return; + } -// RFB/VNC initialisation message handler -init_msg = function() { - //Util.Debug(">> init_msg [rfb_state '" + rfb_state + "']"); + switch (this._rfb_state) { + case 'disconnected': + case 'failed': + Util.Error("Got data while disconnected"); + break; + case 'normal': + if (this._normal_msg() && this._sock.rQlen() > 0) { + // true means we can continue processing + // Give other events a chance to run + if (this._msgTimer === null) { + Util.Debug("More data to process, creating timer"); + this._msgTimer = setTimeout(function () { + this._msgTimer = null; + this._handle_message(); + }.bind(this), 10); + } else { + Util.Debug("More data to process, existing timer"); + } + } + break; + default: + this._init_msg(); + break; + } + }, - var strlen, reason, length, sversion, cversion, repeaterID, - i, types, num_types, challenge, response, bpp, depth, - big_endian, red_max, green_max, blue_max, red_shift, - green_shift, blue_shift, true_color, name_length, is_repeater; + _checkEvents: function () { + if (this._rfb_state === 'normal' && !this._viewportDragging && this._mouse_arr.length > 0) { + this._sock.send(this._mouse_arr); + this._mouse_arr = []; + } + }, - //Util.Debug("ws.rQ (" + ws.rQlen() + ") " + ws.rQslice(0)); - switch (rfb_state) { + _handleKeyPress: function (keysym, down) { + if (this._view_only) { return; } // View only, skip keyboard, events + this._sock.send(RFB.messages.keyEvent(keysym, down)); + }, - case 'ProtocolVersion' : - if (ws.rQlen() < 12) { - return fail("Incomplete protocol version"); - } - sversion = ws.rQshiftStr(12).substr(4,7); - Util.Info("Server ProtocolVersion: " + sversion); - is_repeater = 0; - switch (sversion) { - case "000.000": is_repeater = 1; break; // UltraVNC repeater - case "003.003": rfb_version = 3.3; break; - case "003.006": rfb_version = 3.3; break; // UltraVNC - case "003.889": rfb_version = 3.3; break; // Apple Remote Desktop - case "003.007": rfb_version = 3.7; break; - case "003.008": rfb_version = 3.8; break; - case "004.000": rfb_version = 3.8; break; // Intel AMT KVM - case "004.001": rfb_version = 3.8; break; // RealVNC 4.6 - default: - return fail("Invalid server version " + sversion); - } - if (is_repeater) { - repeaterID = conf.repeaterID; - while (repeaterID.length < 250) { - repeaterID += "\0"; + _handleMouseButton: function (x, y, down, bmask) { + if (down) { + this._mouse_buttonMask |= bmask; + } else { + this._mouse_buttonMask ^= bmask; } - ws.send_string(repeaterID); - break; - } - if (rfb_version > rfb_max_version) { - rfb_version = rfb_max_version; - } - if (! test_mode) { - sendTimer = setInterval(function() { - // Send updates either at a rate of one update - // every 50ms, or whatever slower rate the network - // can handle. - ws.flush(); - }, 50); - } + if (this._viewportDrag) { + if (down && !this._viewportDragging) { + this._viewportDragging = true; + this._viewportDragPos = {'x': x, 'y': y}; - cversion = "00" + parseInt(rfb_version,10) + - ".00" + ((rfb_version * 10) % 10); - ws.send_string("RFB " + cversion + "\n"); - updateState('Security', "Sent ProtocolVersion: " + cversion); - break; - - case 'Security' : - if (rfb_version >= 3.7) { - // Server sends supported list, client decides - num_types = ws.rQshift8(); - if (ws.rQwait("security type", num_types, 1)) { return false; } - if (num_types === 0) { - strlen = ws.rQshift32(); - reason = ws.rQshiftStr(strlen); - return fail("Security failure: " + reason); - } - rfb_auth_scheme = 0; - types = ws.rQshiftBytes(num_types); - Util.Debug("Server security types: " + types); - for (i=0; i < types.length; i+=1) { - if ((types[i] > rfb_auth_scheme) && (types[i] < 3)) { - rfb_auth_scheme = types[i]; + // Skip sending mouse events + return; + } else { + this._viewportDragging = false; } } - if (rfb_auth_scheme === 0) { - return fail("Unsupported security types: " + types); + + if (this._view_only) { return; } // View only, skip mouse events + + this._mouse_arr = this._mouse_arr.concat( + RFB.messages.pointerEvent(this._display.absX(x), this._display.absY(y), this._mouse_buttonMask)); + this._sock.send(this._mouse_arr); + this._mouse_arr = []; + }, + + _handleMouseMove: function (x, y) { + if (this._viewportDragging) { + var deltaX = this._viewportDragPos.x - x; + var deltaY = this._viewportDragPos.y - y; + this._viewportDragPos = {'x': x, 'y': y}; + + this._display.viewportChange(deltaX, deltaY); + + // Skip sending mouse events + return; } - - ws.send([rfb_auth_scheme]); - } else { - // Server decides - if (ws.rQwait("security scheme", 4)) { return false; } - rfb_auth_scheme = ws.rQshift32(); - } - updateState('Authentication', - "Authenticating using scheme: " + rfb_auth_scheme); - init_msg(); // Recursive fallthrough (workaround JSLint complaint) - break; - - // Triggered by fallthough, not by server message - case 'Authentication' : - //Util.Debug("Security auth scheme: " + rfb_auth_scheme); - switch (rfb_auth_scheme) { - case 0: // connection failed - if (ws.rQwait("auth reason", 4)) { return false; } - strlen = ws.rQshift32(); - reason = ws.rQshiftStr(strlen); - return fail("Auth failure: " + reason); - case 1: // no authentication - if (rfb_version >= 3.8) { - updateState('SecurityResult'); - return; + + if (this._view_only) { return; } // View only, skip mouse events + + this._mouse_arr = this._mouse_arr.concat( + RFB.messages.pointerEvent(this._display.absX(x), this._display.absY(y), this._mouse_buttonMask)); + + this._checkEvents(); + }, + + // Message Handlers + + _negotiate_protocol_version: function () { + if (this._sock.rQlen() < 12) { + return this._fail("Incomplete protocol version"); + } + + var sversion = this._sock.rQshiftStr(12).substr(4, 7); + Util.Info("Server ProtocolVersion: " + sversion); + var is_repeater = 0; + switch (sversion) { + case "000.000": // UltraVNC repeater + is_repeater = 1; + break; + case "003.003": + case "003.006": // UltraVNC + case "003.889": // Apple Remote Desktop + this._rfb_version = 3.3; + break; + case "003.007": + this._rfb_version = 3.7; + break; + case "003.008": + case "004.000": // Intel AMT KVM + case "004.001": // RealVNC 4.6 + this._rfb_version = 3.8; + break; + default: + return this._fail("Invalid server version " + sversion); + } + + if (is_repeater) { + var repeaterID = this._repeaterID; + while (repeaterID.length < 250) { + repeaterID += "\0"; } - // Fall through to ClientInitialisation - break; - case 2: // VNC authentication - if (rfb_password.length === 0) { - // Notify via both callbacks since it is kind of - // a RFB state change and a UI interface issue. - updateState('password', "Password Required"); - conf.onPasswordRequired(that); - return; + this._sock.send_string(repeaterID); + return true; + } + + if (this._rfb_version > this._rfb_max_version) { + this._rfb_version = this._rfb_max_version; + } + + // Send updates either at a rate of 1 update per 50ms, or + // whatever slower rate the network can handle + this._sendTimer = setInterval(this._sock.flush.bind(this._sock), 50); + + var cversion = "00" + parseInt(this._rfb_version, 10) + + ".00" + ((this._rfb_version * 10) % 10); + this._sock.send_string("RFB " + cversion + "\n"); + this._updateState('Security', 'Sent ProtocolVersion: ' + cversion); + }, + + _negotiate_security: function () { + if (this._rfb_version >= 3.7) { + // Server sends supported list, client decides + var num_types = this._sock.rQshift8(); + if (this._sock.rQwait("security type", num_types, 1)) { return false; } + + if (num_types === 0) { + var strlen = this._sock.rQshift32(); + var reason = this._sock.rQshiftStr(strlen); + return this._fail("Security failure: " + reason); } - if (ws.rQwait("auth challenge", 16)) { return false; } - challenge = ws.rQshiftBytes(16); - //Util.Debug("Password: " + rfb_password); - //Util.Debug("Challenge: " + challenge + - // " (" + challenge.length + ")"); - response = genDES(rfb_password, challenge); - //Util.Debug("Response: " + response + - // " (" + response.length + ")"); - - //Util.Debug("Sending DES encrypted auth response"); - ws.send(response); - updateState('SecurityResult'); - return; - default: - fail("Unsupported auth scheme: " + rfb_auth_scheme); - return; - } - updateState('ClientInitialisation', "No auth required"); - init_msg(); // Recursive fallthrough (workaround JSLint complaint) - break; - - case 'SecurityResult' : - if (ws.rQwait("VNC auth response ", 4)) { return false; } - switch (ws.rQshift32()) { - case 0: // OK - // Fall through to ClientInitialisation - break; - case 1: // failed - if (rfb_version >= 3.8) { - length = ws.rQshift32(); - if (ws.rQwait("SecurityResult reason", length, 8)) { - return false; + + this._rfb_auth_scheme = 0; + var types = this._sock.rQshiftBytes(num_types); + Util.Debug("Server security types: " + types); + for (var i = 0; i < types.length; i++) { + if (types[i] > this._rfb_auth_scheme && (types[i] <= 16 || types[i] == 22)) { + this._rfb_auth_scheme = types[i]; } - reason = ws.rQshiftStr(length); - fail(reason); - } else { - fail("Authentication failed"); } - return; - case 2: // too-many - return fail("Too many auth attempts"); - } - updateState('ClientInitialisation', "Authentication OK"); - init_msg(); // Recursive fallthrough (workaround JSLint complaint) - break; - - // Triggered by fallthough, not by server message - case 'ClientInitialisation' : - ws.send([conf.shared ? 1 : 0]); // ClientInitialisation - updateState('ServerInitialisation', "Authentication OK"); - break; - - case 'ServerInitialisation' : - if (ws.rQwait("server initialization", 24)) { return false; } - - /* Screen size */ - fb_width = ws.rQshift16(); - fb_height = ws.rQshift16(); - - /* PIXEL_FORMAT */ - bpp = ws.rQshift8(); - depth = ws.rQshift8(); - big_endian = ws.rQshift8(); - true_color = ws.rQshift8(); - - red_max = ws.rQshift16(); - green_max = ws.rQshift16(); - blue_max = ws.rQshift16(); - red_shift = ws.rQshift8(); - green_shift = ws.rQshift8(); - blue_shift = ws.rQshift8(); - ws.rQshiftStr(3); // padding - - Util.Info("Screen: " + fb_width + "x" + fb_height + - ", bpp: " + bpp + ", depth: " + depth + - ", big_endian: " + big_endian + - ", true_color: " + true_color + - ", red_max: " + red_max + - ", green_max: " + green_max + - ", blue_max: " + blue_max + - ", red_shift: " + red_shift + - ", green_shift: " + green_shift + - ", blue_shift: " + blue_shift); - - if (big_endian !== 0) { - Util.Warn("Server native endian is not little endian"); - } - if (red_shift !== 16) { - Util.Warn("Server native red-shift is not 16"); - } - if (blue_shift !== 0) { - Util.Warn("Server native blue-shift is not 0"); - } - /* Connection name/title */ - name_length = ws.rQshift32(); - fb_name = ws.rQshiftStr(name_length); - - if (conf.true_color && fb_name === "Intel(r) AMT KVM") - { - Util.Warn("Intel AMT KVM only support 8/16 bit depths. Disabling true color"); - conf.true_color = false; - } + if (this._rfb_auth_scheme === 0) { + return this._fail("Unsupported security types: " + types); + } - display.set_true_color(conf.true_color); - display.resize(fb_width, fb_height); - keyboard.grab(); - mouse.grab(); + this._sock.send([this._rfb_auth_scheme]); + } else { + // Server decides + if (this._sock.rQwait("security scheme", 4)) { return false; } + this._rfb_auth_scheme = this._sock.rQshift32(); + } - if (conf.true_color) { - fb_Bpp = 4; - fb_depth = 3; - } else { - fb_Bpp = 1; - fb_depth = 1; - } + this._updateState('Authentication', 'Authenticating using scheme: ' + this._rfb_auth_scheme); + return this._init_msg(); // jump to authentication + }, + + // authentication + _negotiate_xvp_auth: function () { + var xvp_sep = this._xvp_password_sep; + var xvp_auth = this._rfb_password.split(xvp_sep); + if (xvp_auth.length < 3) { + this._updateState('password', 'XVP credentials required (user' + xvp_sep + + 'target' + xvp_sep + 'password) -- got only ' + this._rfb_password); + this._onPasswordRequired(this); + return false; + } - response = pixelFormat(); - response = response.concat(clientEncodings()); - response = response.concat(fbUpdateRequests()); - timing.fbu_rt_start = (new Date()).getTime(); - timing.pixels = 0; - ws.send(response); - - /* Start pushing/polling */ - setTimeout(checkEvents, conf.check_rate); - - if (conf.encrypt) { - updateState('normal', "Connected (encrypted) to: " + fb_name); - } else { - updateState('normal', "Connected (unencrypted) to: " + fb_name); - } - break; - } - //Util.Debug("<< init_msg"); -}; - - -/* Normal RFB/VNC server message handler */ -normal_msg = function() { - //Util.Debug(">> normal_msg"); - - var ret = true, msg_type, length, text, - c, first_colour, num_colours, red, green, blue; - - if (FBU.rects > 0) { - msg_type = 0; - } else { - msg_type = ws.rQshift8(); - } - switch (msg_type) { - case 0: // FramebufferUpdate - ret = framebufferUpdate(); // false means need more data - break; - case 1: // SetColourMapEntries - Util.Debug("SetColourMapEntries"); - ws.rQshift8(); // Padding - first_colour = ws.rQshift16(); // First colour - num_colours = ws.rQshift16(); - if (ws.rQwait("SetColourMapEntries", num_colours*6, 6)) { return false; } - - for (c=0; c < num_colours; c+=1) { - red = ws.rQshift16(); - //Util.Debug("red before: " + red); - red = parseInt(red / 256, 10); - //Util.Debug("red after: " + red); - green = parseInt(ws.rQshift16() / 256, 10); - blue = parseInt(ws.rQshift16() / 256, 10); - display.set_colourMap([blue, green, red], first_colour + c); - } - Util.Debug("colourMap: " + display.get_colourMap()); - Util.Info("Registered " + num_colours + " colourMap entries"); - //Util.Debug("colourMap: " + display.get_colourMap()); - break; - case 2: // Bell - Util.Debug("Bell"); - conf.onBell(that); - break; - case 3: // ServerCutText - Util.Debug("ServerCutText"); - if (ws.rQwait("ServerCutText header", 7, 1)) { return false; } - ws.rQshiftBytes(3); // Padding - length = ws.rQshift32(); - if (ws.rQwait("ServerCutText", length, 8)) { return false; } - - text = ws.rQshiftStr(length); - conf.clipboardReceive(that, text); // Obsolete - conf.onClipboard(that, text); - break; - default: - fail("Disconnected: illegal server message type " + msg_type); - Util.Debug("ws.rQslice(0,30):" + ws.rQslice(0,30)); - break; - } - //Util.Debug("<< normal_msg"); - return ret; -}; - -framebufferUpdate = function() { - var now, hdr, fbu_rt_diff, ret = true; - - if (FBU.rects === 0) { - //Util.Debug("New FBU: ws.rQslice(0,20): " + ws.rQslice(0,20)); - if (ws.rQwait("FBU header", 3)) { - ws.rQunshift8(0); // FBU msg_type - return false; - } - ws.rQshift8(); // padding - FBU.rects = ws.rQshift16(); - //Util.Debug("FramebufferUpdate, rects:" + FBU.rects); - FBU.bytes = 0; - timing.cur_fbu = 0; - if (timing.fbu_rt_start > 0) { - now = (new Date()).getTime(); - Util.Info("First FBU latency: " + (now - timing.fbu_rt_start)); - } - } + var xvp_auth_str = String.fromCharCode(xvp_auth[0].length) + + String.fromCharCode(xvp_auth[1].length) + + xvp_auth[0] + + xvp_auth[1]; + this._sock.send_string(xvp_auth_str); + this._rfb_password = xvp_auth.slice(2).join(xvp_sep); + this._rfb_auth_scheme = 2; + return this._negotiate_authentication(); + }, + + _negotiate_std_vnc_auth: function () { + if (this._rfb_password.length === 0) { + // Notify via both callbacks since it's kind of + // an RFB state change and a UI interface issue + this._updateState('password', "Password Required"); + this._onPasswordRequired(this); + } - while (FBU.rects > 0) { - if (rfb_state !== "normal") { - return false; - } - if (ws.rQwait("FBU", FBU.bytes)) { return false; } - if (FBU.bytes === 0) { - if (ws.rQwait("rect header", 12)) { return false; } - /* New FramebufferUpdate */ - - hdr = ws.rQshiftBytes(12); - FBU.x = (hdr[0] << 8) + hdr[1]; - FBU.y = (hdr[2] << 8) + hdr[3]; - FBU.width = (hdr[4] << 8) + hdr[5]; - FBU.height = (hdr[6] << 8) + hdr[7]; - FBU.encoding = parseInt((hdr[8] << 24) + (hdr[9] << 16) + - (hdr[10] << 8) + hdr[11], 10); - - conf.onFBUReceive(that, - {'x': FBU.x, 'y': FBU.y, - 'width': FBU.width, 'height': FBU.height, - 'encoding': FBU.encoding, - 'encodingName': encNames[FBU.encoding]}); - - if (encNames[FBU.encoding]) { - // Debug: - /* - var msg = "FramebufferUpdate rects:" + FBU.rects; - msg += " x: " + FBU.x + " y: " + FBU.y; - msg += " width: " + FBU.width + " height: " + FBU.height; - msg += " encoding:" + FBU.encoding; - msg += "(" + encNames[FBU.encoding] + ")"; - msg += ", ws.rQlen(): " + ws.rQlen(); - Util.Debug(msg); - */ + if (this._sock.rQwait("auth challenge", 16)) { return false; } + + var challenge = this._sock.rQshiftBytes(16); + var response = RFB.genDES(this._rfb_password, challenge); + this._sock.send(response); + this._updateState("SecurityResult"); + return true; + }, + + _negotiate_tight_tunnels: function (numTunnels) { + var clientSupportedTunnelTypes = { + 0: { vendor: 'TGHT', signature: 'NOTUNNEL' } + }; + var serverSupportedTunnelTypes = {}; + // receive tunnel capabilities + for (var i = 0; i < numTunnels; i++) { + var cap_code = this._sock.rQshift32(); + var cap_vendor = this._sock.rQshiftStr(4); + var cap_signature = this._sock.rQshiftStr(8); + serverSupportedTunnelTypes[cap_code] = { vendor: cap_vendor, signature: cap_signature }; + } + + // choose the notunnel type + if (serverSupportedTunnelTypes[0]) { + if (serverSupportedTunnelTypes[0].vendor != clientSupportedTunnelTypes[0].vendor || + serverSupportedTunnelTypes[0].signature != clientSupportedTunnelTypes[0].signature) { + return this._fail("Client's tunnel type had the incorrect vendor or signature"); + } + this._sock.send([0, 0, 0, 0]); // use NOTUNNEL + return false; // wait until we receive the sub auth count to continue } else { - fail("Disconnected: unsupported encoding " + - FBU.encoding); - return false; + return this._fail("Server wanted tunnels, but doesn't support the notunnel type"); } - } + }, - timing.last_fbu = (new Date()).getTime(); + _negotiate_tight_auth: function () { + if (!this._rfb_tightvnc) { // first pass, do the tunnel negotiation + if (this._sock.rQwait("num tunnels", 4)) { return false; } + var numTunnels = this._sock.rQshift32(); + if (numTunnels > 0 && this._sock.rQwait("tunnel capabilities", 16 * numTunnels, 4)) { return false; } - ret = encHandlers[FBU.encoding](); + this._rfb_tightvnc = true; - now = (new Date()).getTime(); - timing.cur_fbu += (now - timing.last_fbu); + if (numTunnels > 0) { + this._negotiate_tight_tunnels(numTunnels); + return false; // wait until we receive the sub auth to continue + } + } - if (ret) { - encStats[FBU.encoding][0] += 1; - encStats[FBU.encoding][1] += 1; - timing.pixels += FBU.width * FBU.height; - } + // second pass, do the sub-auth negotiation + if (this._sock.rQwait("sub auth count", 4)) { return false; } + var subAuthCount = this._sock.rQshift32(); + if (this._sock.rQwait("sub auth capabilities", 16 * subAuthCount, 4)) { return false; } - if (timing.pixels >= (fb_width * fb_height)) { - if (((FBU.width === fb_width) && - (FBU.height === fb_height)) || - (timing.fbu_rt_start > 0)) { - timing.full_fbu_total += timing.cur_fbu; - timing.full_fbu_cnt += 1; - Util.Info("Timing of full FBU, cur: " + - timing.cur_fbu + ", total: " + - timing.full_fbu_total + ", cnt: " + - timing.full_fbu_cnt + ", avg: " + - (timing.full_fbu_total / - timing.full_fbu_cnt)); - } - if (timing.fbu_rt_start > 0) { - fbu_rt_diff = now - timing.fbu_rt_start; - timing.fbu_rt_total += fbu_rt_diff; - timing.fbu_rt_cnt += 1; - Util.Info("full FBU round-trip, cur: " + - fbu_rt_diff + ", total: " + - timing.fbu_rt_total + ", cnt: " + - timing.fbu_rt_cnt + ", avg: " + - (timing.fbu_rt_total / - timing.fbu_rt_cnt)); - timing.fbu_rt_start = 0; + var clientSupportedTypes = { + 'STDVNOAUTH__': 1, + 'STDVVNCAUTH_': 2 + }; + + var serverSupportedTypes = []; + + for (var i = 0; i < subAuthCount; i++) { + var capNum = this._sock.rQshift32(); + var capabilities = this._sock.rQshiftStr(12); + serverSupportedTypes.push(capabilities); } - } - if (! ret) { - return ret; // false ret means need more data - } - } - - conf.onFBUComplete(that, - {'x': FBU.x, 'y': FBU.y, - 'width': FBU.width, 'height': FBU.height, - 'encoding': FBU.encoding, - 'encodingName': encNames[FBU.encoding]}); - - return true; // We finished this FBU -}; - -// -// FramebufferUpdate encodings -// - -encHandlers.RAW = function display_raw() { - //Util.Debug(">> display_raw (" + ws.rQlen() + " bytes)"); - - var cur_y, cur_height; - - if (FBU.lines === 0) { - FBU.lines = FBU.height; - } - FBU.bytes = FBU.width * fb_Bpp; // At least a line - if (ws.rQwait("RAW", FBU.bytes)) { return false; } - cur_y = FBU.y + (FBU.height - FBU.lines); - cur_height = Math.min(FBU.lines, - Math.floor(ws.rQlen()/(FBU.width * fb_Bpp))); - display.blitImage(FBU.x, cur_y, FBU.width, cur_height, - ws.get_rQ(), ws.get_rQi()); - ws.rQshiftBytes(FBU.width * cur_height * fb_Bpp); - FBU.lines -= cur_height; - - if (FBU.lines > 0) { - FBU.bytes = FBU.width * fb_Bpp; // At least another line - } else { - FBU.rects -= 1; - FBU.bytes = 0; - } - //Util.Debug("<< display_raw (" + ws.rQlen() + " bytes)"); - return true; -}; - -encHandlers.COPYRECT = function display_copy_rect() { - //Util.Debug(">> display_copy_rect"); - - var old_x, old_y; - - if (ws.rQwait("COPYRECT", 4)) { return false; } - display.renderQ_push({ - 'type': 'copy', - 'old_x': ws.rQshift16(), - 'old_y': ws.rQshift16(), - 'x': FBU.x, - 'y': FBU.y, - 'width': FBU.width, - 'height': FBU.height}); - FBU.rects -= 1; - FBU.bytes = 0; - return true; -}; - -encHandlers.RRE = function display_rre() { - //Util.Debug(">> display_rre (" + ws.rQlen() + " bytes)"); - var color, x, y, width, height, chunk; - - if (FBU.subrects === 0) { - if (ws.rQwait("RRE", 4+fb_Bpp)) { return false; } - FBU.subrects = ws.rQshift32(); - color = ws.rQshiftBytes(fb_Bpp); // Background - display.fillRect(FBU.x, FBU.y, FBU.width, FBU.height, color); - } - while ((FBU.subrects > 0) && (ws.rQlen() >= (fb_Bpp + 8))) { - color = ws.rQshiftBytes(fb_Bpp); - x = ws.rQshift16(); - y = ws.rQshift16(); - width = ws.rQshift16(); - height = ws.rQshift16(); - display.fillRect(FBU.x + x, FBU.y + y, width, height, color); - FBU.subrects -= 1; - } - //Util.Debug(" display_rre: rects: " + FBU.rects + - // ", FBU.subrects: " + FBU.subrects); - - if (FBU.subrects > 0) { - chunk = Math.min(rre_chunk_sz, FBU.subrects); - FBU.bytes = (fb_Bpp + 8) * chunk; - } else { - FBU.rects -= 1; - FBU.bytes = 0; - } - //Util.Debug("<< display_rre, FBU.bytes: " + FBU.bytes); - return true; -}; - -encHandlers.HEXTILE = function display_hextile() { - //Util.Debug(">> display_hextile"); - var subencoding, subrects, color, cur_tile, - tile_x, x, w, tile_y, y, h, xy, s, sx, sy, wh, sw, sh, - rQ = ws.get_rQ(), rQi = ws.get_rQi(); - - if (FBU.tiles === 0) { - FBU.tiles_x = Math.ceil(FBU.width/16); - FBU.tiles_y = Math.ceil(FBU.height/16); - FBU.total_tiles = FBU.tiles_x * FBU.tiles_y; - FBU.tiles = FBU.total_tiles; - } - - /* FBU.bytes comes in as 1, ws.rQlen() at least 1 */ - while (FBU.tiles > 0) { - FBU.bytes = 1; - if (ws.rQwait("HEXTILE subencoding", FBU.bytes)) { return false; } - subencoding = rQ[rQi]; // Peek - if (subencoding > 30) { // Raw - fail("Disconnected: illegal hextile subencoding " + subencoding); - //Util.Debug("ws.rQslice(0,30):" + ws.rQslice(0,30)); - return false; - } - subrects = 0; - cur_tile = FBU.total_tiles - FBU.tiles; - tile_x = cur_tile % FBU.tiles_x; - tile_y = Math.floor(cur_tile / FBU.tiles_x); - x = FBU.x + tile_x * 16; - y = FBU.y + tile_y * 16; - w = Math.min(16, (FBU.x + FBU.width) - x); - h = Math.min(16, (FBU.y + FBU.height) - y); - - /* Figure out how much we are expecting */ - if (subencoding & 0x01) { // Raw - //Util.Debug(" Raw subencoding"); - FBU.bytes += w * h * fb_Bpp; - } else { - if (subencoding & 0x02) { // Background - FBU.bytes += fb_Bpp; - } - if (subencoding & 0x04) { // Foreground - FBU.bytes += fb_Bpp; - } - if (subencoding & 0x08) { // AnySubrects - FBU.bytes += 1; // Since we aren't shifting it off - if (ws.rQwait("hextile subrects header", FBU.bytes)) { return false; } - subrects = rQ[rQi + FBU.bytes-1]; // Peek - if (subencoding & 0x10) { // SubrectsColoured - FBU.bytes += subrects * (fb_Bpp + 2); - } else { - FBU.bytes += subrects * 2; + + for (var authType in clientSupportedTypes) { + if (serverSupportedTypes.indexOf(authType) != -1) { + this._sock.send([0, 0, 0, clientSupportedTypes[authType]]); + + switch (authType) { + case 'STDVNOAUTH__': // no auth + this._updateState('SecurityResult'); + return true; + case 'STDVVNCAUTH_': // VNC auth + this._rfb_auth_scheme = 2; + return this._init_msg(); + default: + return this._fail("Unsupported tiny auth scheme: " + authType); + } } } - } - /* - Util.Debug(" tile:" + cur_tile + "/" + (FBU.total_tiles - 1) + - " (" + tile_x + "," + tile_y + ")" + - " [" + x + "," + y + "]@" + w + "x" + h + - ", subenc:" + subencoding + - "(last: " + FBU.lastsubencoding + "), subrects:" + - subrects + - ", ws.rQlen():" + ws.rQlen() + ", FBU.bytes:" + FBU.bytes + - " last:" + ws.rQslice(FBU.bytes-10, FBU.bytes) + - " next:" + ws.rQslice(FBU.bytes-1, FBU.bytes+10)); - */ - if (ws.rQwait("hextile", FBU.bytes)) { return false; } - - /* We know the encoding and have a whole tile */ - FBU.subencoding = rQ[rQi]; - rQi += 1; - if (FBU.subencoding === 0) { - if (FBU.lastsubencoding & 0x01) { - /* Weird: ignore blanks after RAW */ - Util.Debug(" Ignoring blank after RAW"); - } else { - display.fillRect(x, y, w, h, FBU.background); + this._fail("No supported sub-auth types!"); + }, + + _negotiate_authentication: function () { + switch (this._rfb_auth_scheme) { + case 0: // connection failed + if (this._sock.rQwait("auth reason", 4)) { return false; } + var strlen = this._sock.rQshift32(); + var reason = this._sock.rQshiftStr(strlen); + return this._fail("Auth failure: " + reason); + + case 1: // no auth + if (this._rfb_version >= 3.8) { + this._updateState('SecurityResult'); + return true; + } + this._updateState('ClientInitialisation', "No auth required"); + return this._init_msg(); + + case 22: // XVP auth + return this._negotiate_xvp_auth(); + + case 2: // VNC authentication + return this._negotiate_std_vnc_auth(); + + case 16: // TightVNC Security Type + return this._negotiate_tight_auth(); + + default: + return this._fail("Unsupported auth scheme: " + this._rfb_auth_scheme); } - } else if (FBU.subencoding & 0x01) { // Raw - display.blitImage(x, y, w, h, rQ, rQi); - rQi += FBU.bytes - 1; - } else { - if (FBU.subencoding & 0x02) { // Background - FBU.background = rQ.slice(rQi, rQi + fb_Bpp); - rQi += fb_Bpp; - } - if (FBU.subencoding & 0x04) { // Foreground - FBU.foreground = rQ.slice(rQi, rQi + fb_Bpp); - rQi += fb_Bpp; - } - - display.startTile(x, y, w, h, FBU.background); - if (FBU.subencoding & 0x08) { // AnySubrects - subrects = rQ[rQi]; - rQi += 1; - for (s = 0; s < subrects; s += 1) { - if (FBU.subencoding & 0x10) { // SubrectsColoured - color = rQ.slice(rQi, rQi + fb_Bpp); - rQi += fb_Bpp; + }, + + _handle_security_result: function () { + if (this._sock.rQwait('VNC auth response ', 4)) { return false; } + switch (this._sock.rQshift32()) { + case 0: // OK + this._updateState('ClientInitialisation', 'Authentication OK'); + return this._init_msg(); + case 1: // failed + if (this._rfb_version >= 3.8) { + var length = this._sock.rQshift32(); + if (this._sock.rQwait("SecurityResult reason", length, 8)) { return false; } + var reason = this._sock.rQshiftStr(length); + return this._fail(reason); } else { - color = FBU.foreground; + return this._fail("Authentication failure"); } - xy = rQ[rQi]; - rQi += 1; - sx = (xy >> 4); - sy = (xy & 0x0f); + return false; + case 2: + return this._fail("Too many auth attempts"); + } + }, + + _negotiate_server_init: function () { + if (this._sock.rQwait("server initialization", 24)) { return false; } + + /* Screen size */ + this._fb_width = this._sock.rQshift16(); + this._fb_height = this._sock.rQshift16(); + + /* PIXEL_FORMAT */ + var bpp = this._sock.rQshift8(); + var depth = this._sock.rQshift8(); + var big_endian = this._sock.rQshift8(); + var true_color = this._sock.rQshift8(); + + var red_max = this._sock.rQshift16(); + var green_max = this._sock.rQshift16(); + var blue_max = this._sock.rQshift16(); + var red_shift = this._sock.rQshift8(); + var green_shift = this._sock.rQshift8(); + var blue_shift = this._sock.rQshift8(); + this._sock.rQskipBytes(3); // padding + + // NB(directxman12): we don't want to call any callbacks or print messages until + // *after* we're past the point where we could backtrack + + /* Connection name/title */ + var name_length = this._sock.rQshift32(); + if (this._sock.rQwait('server init name', name_length, 24)) { return false; } + this._fb_name = Util.decodeUTF8(this._sock.rQshiftStr(name_length)); + + if (this._rfb_tightvnc) { + if (this._sock.rQwait('TightVNC extended server init header', 8, 24 + name_length)) { return false; } + // In TightVNC mode, ServerInit message is extended + var numServerMessages = this._sock.rQshift16(); + var numClientMessages = this._sock.rQshift16(); + var numEncodings = this._sock.rQshift16(); + this._sock.rQskipBytes(2); // padding + + var totalMessagesLength = (numServerMessages + numClientMessages + numEncodings) * 16; + if (this._sock.rQwait('TightVNC extended server init header', totalMessagesLength, 32 + name_length)) { return false; } + + var i; + for (i = 0; i < numServerMessages; i++) { + var srvMsg = this._sock.rQshiftStr(16); + } - wh = rQ[rQi]; - rQi += 1; - sw = (wh >> 4) + 1; - sh = (wh & 0x0f) + 1; + for (i = 0; i < numClientMessages; i++) { + var clientMsg = this._sock.rQshiftStr(16); + } - display.subTile(sx, sy, sw, sh, color); + for (i = 0; i < numEncodings; i++) { + var encoding = this._sock.rQshiftStr(16); } } - display.finishTile(); - } - ws.set_rQi(rQi); - FBU.lastsubencoding = FBU.subencoding; - FBU.bytes = 0; - FBU.tiles -= 1; - } - - if (FBU.tiles === 0) { - FBU.rects -= 1; - } - - //Util.Debug("<< display_hextile"); - return true; -}; - - -// Get 'compact length' header and data size -getTightCLength = function (arr) { - var header = 1, data = 0; - data += arr[0] & 0x7f; - if (arr[0] & 0x80) { - header += 1; - data += (arr[1] & 0x7f) << 7; - if (arr[1] & 0x80) { - header += 1; - data += arr[2] << 14; - } - } - return [header, data]; -}; -function display_tight(isTightPNG) { - //Util.Debug(">> display_tight"); + // NB(directxman12): these are down here so that we don't run them multiple times + // if we backtrack + Util.Info("Screen: " + this._fb_width + "x" + this._fb_height + + ", bpp: " + bpp + ", depth: " + depth + + ", big_endian: " + big_endian + + ", true_color: " + true_color + + ", red_max: " + red_max + + ", green_max: " + green_max + + ", blue_max: " + blue_max + + ", red_shift: " + red_shift + + ", green_shift: " + green_shift + + ", blue_shift: " + blue_shift); + + if (big_endian !== 0) { + Util.Warn("Server native endian is not little endian"); + } - if (fb_depth === 1) { - fail("Tight protocol handler only implements true color mode"); - } + if (red_shift !== 16) { + Util.Warn("Server native red-shift is not 16"); + } - var ctl, cmode, clength, color, img, data; - var filterId = -1, resetStreams = 0, streamId = -1; - var rQ = ws.get_rQ(), rQi = ws.get_rQi(); + if (blue_shift !== 0) { + Util.Warn("Server native blue-shift is not 0"); + } - FBU.bytes = 1; // compression-control byte - if (ws.rQwait("TIGHT compression-control", FBU.bytes)) { return false; } + // we're past the point where we could backtrack, so it's safe to call this + this._onDesktopName(this, this._fb_name); - var checksum = function(data) { - var sum=0, i; - for (i=0; i 65536) sum -= 65536; - } - return sum; - } + if (this._true_color && this._fb_name === "Intel(r) AMT KVM") { + Util.Warn("Intel AMT KVM only supports 8/16 bit depths. Disabling true color"); + this._true_color = false; + } - var decompress = function(data) { - for (var i=0; i<4; i++) { - if ((resetStreams >> i) & 1) { - FBU.zlibs[i].reset(); - Util.Info("Reset zlib stream " + i); + this._display.set_true_color(this._true_color); + this._onFBResize(this, this._fb_width, this._fb_height); + this._display.resize(this._fb_width, this._fb_height); + this._keyboard.grab(); + this._mouse.grab(); + + if (this._true_color) { + this._fb_Bpp = 4; + this._fb_depth = 3; + } else { + this._fb_Bpp = 1; + this._fb_depth = 1; } - } - var uncompressed = FBU.zlibs[streamId].uncompress(data, 0); - if (uncompressed.status !== 0) { - Util.Error("Invalid data in zlib stream"); - } - //Util.Warn("Decompressed " + data.length + " to " + - // uncompressed.data.length + " checksums " + - // checksum(data) + ":" + checksum(uncompressed.data)); - - return uncompressed.data; - } - - var handlePalette = function() { - var numColors = rQ[rQi + 2] + 1; - var paletteSize = numColors * fb_depth; - FBU.bytes += paletteSize; - if (ws.rQwait("TIGHT palette " + cmode, FBU.bytes)) { return false; } - - var bpp = (numColors <= 2) ? 1 : 8; - var rowSize = Math.floor((FBU.width * bpp + 7) / 8); - var raw = false; - if (rowSize * FBU.height < 12) { - raw = true; - clength = [0, rowSize * FBU.height]; - } else { - clength = getTightCLength(ws.rQslice(3 + paletteSize, - 3 + paletteSize + 3)); - } - FBU.bytes += clength[0] + clength[1]; - if (ws.rQwait("TIGHT " + cmode, FBU.bytes)) { return false; } - // Shift ctl, filter id, num colors, palette entries, and clength off - ws.rQshiftBytes(3); - var palette = ws.rQshiftBytes(paletteSize); - ws.rQshiftBytes(clength[0]); + var response = RFB.messages.pixelFormat(this._fb_Bpp, this._fb_depth, this._true_color); + response = response.concat( + RFB.messages.clientEncodings(this._encodings, this._local_cursor, this._true_color)); + response = response.concat( + RFB.messages.fbUpdateRequests(this._display.getCleanDirtyReset(), + this._fb_width, this._fb_height)); - if (raw) { - data = ws.rQshiftBytes(clength[1]); - } else { - data = decompress(ws.rQshiftBytes(clength[1])); - } + this._timing.fbu_rt_start = (new Date()).getTime(); + this._timing.pixels = 0; + this._sock.send(response); + + this._checkEvents(); + + if (this._encrypt) { + this._updateState('normal', 'Connected (encrypted) to: ' + this._fb_name); + } else { + this._updateState('normal', 'Connected (unencrypted) to: ' + this._fb_name); + } + }, + + _init_msg: function () { + switch (this._rfb_state) { + case 'ProtocolVersion': + return this._negotiate_protocol_version(); + + case 'Security': + return this._negotiate_security(); + + case 'Authentication': + return this._negotiate_authentication(); + + case 'SecurityResult': + return this._handle_security_result(); + + case 'ClientInitialisation': + this._sock.send([this._shared ? 1 : 0]); // ClientInitialisation + this._updateState('ServerInitialisation', "Authentication OK"); + return true; + + case 'ServerInitialisation': + return this._negotiate_server_init(); + } + }, + + _handle_set_colour_map_msg: function () { + Util.Debug("SetColorMapEntries"); + this._sock.rQskip8(); // Padding + + var first_colour = this._sock.rQshift16(); + var num_colours = this._sock.rQshift16(); + if (this._sock.rQwait('SetColorMapEntries', num_colours * 6, 6)) { return false; } + + for (var c = 0; c < num_colours; c++) { + var red = parseInt(this._sock.rQshift16() / 256, 10); + var green = parseInt(this._sock.rQshift16() / 256, 10); + var blue = parseInt(this._sock.rQshift16() / 256, 10); + this._display.set_colourMap([blue, green, red], first_colour + c); + } + Util.Debug("colourMap: " + this._display.get_colourMap()); + Util.Info("Registered " + num_colours + " colourMap entries"); + + return true; + }, + + _handle_server_cut_text: function () { + Util.Debug("ServerCutText"); + if (this._sock.rQwait("ServerCutText header", 7, 1)) { return false; } + this._sock.rQskipBytes(3); // Padding + var length = this._sock.rQshift32(); + if (this._sock.rQwait("ServerCutText", length, 8)) { return false; } + + var text = this._sock.rQshiftStr(length); + this._onClipboard(this, text); + + return true; + }, + + _handle_xvp_msg: function () { + if (this._sock.rQwait("XVP version and message", 3, 1)) { return false; } + this._sock.rQskip8(); // Padding + var xvp_ver = this._sock.rQshift8(); + var xvp_msg = this._sock.rQshift8(); + + switch (xvp_msg) { + case 0: // XVP_FAIL + this._updateState(this._rfb_state, "Operation Failed"); + break; + case 1: // XVP_INIT + this._rfb_xvp_ver = xvp_ver; + Util.Info("XVP extensions enabled (version " + this._rfb_xvp_ver + ")"); + this._onXvpInit(this._rfb_xvp_ver); + break; + default: + this._fail("Disconnected: illegal server XVP message " + xvp_msg); + break; + } + + return true; + }, - // Convert indexed (palette based) image data to RGB - // TODO: reduce number of calculations inside loop - var dest = []; - var x, y, b, w, w1, dp, sp; - if (numColors === 2) { - w = Math.floor((FBU.width + 7) / 8); - w1 = Math.floor(FBU.width / 8); - for (y = 0; y < FBU.height; y++) { - for (x = 0; x < w1; x++) { - for (b = 7; b >= 0; b--) { - dp = (y*FBU.width + x*8 + 7-b) * 3; - sp = (data[y*w + x] >> b & 1) * 3; - dest[dp ] = palette[sp ]; - dest[dp+1] = palette[sp+1]; - dest[dp+2] = palette[sp+2]; + _normal_msg: function () { + var msg_type; + + if (this._FBU.rects > 0) { + msg_type = 0; + } else { + msg_type = this._sock.rQshift8(); + } + + switch (msg_type) { + case 0: // FramebufferUpdate + var ret = this._framebufferUpdate(); + if (ret) { + this._sock.send(RFB.messages.fbUpdateRequests(this._display.getCleanDirtyReset(), + this._fb_width, this._fb_height)); } - } - for (b = 7; b >= 8 - FBU.width % 8; b--) { - dp = (y*FBU.width + x*8 + 7-b) * 3; - sp = (data[y*w + x] >> b & 1) * 3; - dest[dp ] = palette[sp ]; - dest[dp+1] = palette[sp+1]; - dest[dp+2] = palette[sp+2]; + return ret; + + case 1: // SetColorMapEntries + return this._handle_set_colour_map_msg(); + + case 2: // Bell + Util.Debug("Bell"); + this._onBell(this); + return true; + + case 3: // ServerCutText + return this._handle_server_cut_text(); + + case 250: // XVP + return this._handle_xvp_msg(); + + default: + this._fail("Disconnected: illegal server message type " + msg_type); + Util.Debug("sock.rQslice(0, 30): " + this._sock.rQslice(0, 30)); + return true; + } + }, + + _framebufferUpdate: function () { + var ret = true; + var now; + + if (this._FBU.rects === 0) { + if (this._sock.rQwait("FBU header", 3, 1)) { return false; } + this._sock.rQskip8(); // Padding + this._FBU.rects = this._sock.rQshift16(); + this._FBU.bytes = 0; + this._timing.cur_fbu = 0; + if (this._timing.fbu_rt_start > 0) { + now = (new Date()).getTime(); + Util.Info("First FBU latency: " + (now - this._timing.fbu_rt_start)); } } - } else { - for (y = 0; y < FBU.height; y++) { - for (x = 0; x < FBU.width; x++) { - dp = (y*FBU.width + x) * 3; - sp = data[y*FBU.width + x] * 3; - dest[dp ] = palette[sp ]; - dest[dp+1] = palette[sp+1]; - dest[dp+2] = palette[sp+2]; + + while (this._FBU.rects > 0) { + if (this._rfb_state !== "normal") { return false; } + + if (this._sock.rQwait("FBU", this._FBU.bytes)) { return false; } + if (this._FBU.bytes === 0) { + if (this._sock.rQwait("rect header", 12)) { return false; } + /* New FramebufferUpdate */ + + var hdr = this._sock.rQshiftBytes(12); + this._FBU.x = (hdr[0] << 8) + hdr[1]; + this._FBU.y = (hdr[2] << 8) + hdr[3]; + this._FBU.width = (hdr[4] << 8) + hdr[5]; + this._FBU.height = (hdr[6] << 8) + hdr[7]; + this._FBU.encoding = parseInt((hdr[8] << 24) + (hdr[9] << 16) + + (hdr[10] << 8) + hdr[11], 10); + + this._onFBUReceive(this, + {'x': this._FBU.x, 'y': this._FBU.y, + 'width': this._FBU.width, 'height': this._FBU.height, + 'encoding': this._FBU.encoding, + 'encodingName': this._encNames[this._FBU.encoding]}); + + if (!this._encNames[this._FBU.encoding]) { + this._fail("Disconnected: unsupported encoding " + + this._FBU.encoding); + return false; + } + } + + this._timing.last_fbu = (new Date()).getTime(); + + ret = this._encHandlers[this._FBU.encoding](); + + now = (new Date()).getTime(); + this._timing.cur_fbu += (now - this._timing.last_fbu); + + if (ret) { + this._encStats[this._FBU.encoding][0]++; + this._encStats[this._FBU.encoding][1]++; + this._timing.pixels += this._FBU.width * this._FBU.height; + } + + if (this._timing.pixels >= (this._fb_width * this._fb_height)) { + if ((this._FBU.width === this._fb_width && this._FBU.height === this._fb_height) || + this._timing.fbu_rt_start > 0) { + this._timing.full_fbu_total += this._timing.cur_fbu; + this._timing.full_fbu_cnt++; + Util.Info("Timing of full FBU, curr: " + + this._timing.cur_fbu + ", total: " + + this._timing.full_fbu_total + ", cnt: " + + this._timing.full_fbu_cnt + ", avg: " + + (this._timing.full_fbu_total / this._timing.full_fbu_cnt)); + } + + if (this._timing.fbu_rt_start > 0) { + var fbu_rt_diff = now - this._timing.fbu_rt_start; + this._timing.fbu_rt_total += fbu_rt_diff; + this._timing.fbu_rt_cnt++; + Util.Info("full FBU round-trip, cur: " + + fbu_rt_diff + ", total: " + + this._timing.fbu_rt_total + ", cnt: " + + this._timing.fbu_rt_cnt + ", avg: " + + (this._timing.fbu_rt_total / this._timing.fbu_rt_cnt)); + this._timing.fbu_rt_start = 0; + } } + + if (!ret) { return ret; } // need more data } - } - display.renderQ_push({ - 'type': 'blitRgb', - 'data': dest, - 'x': FBU.x, - 'y': FBU.y, - 'width': FBU.width, - 'height': FBU.height}); - return true; - } - - var handleCopy = function() { - var raw = false; - var uncompressedSize = FBU.width * FBU.height * fb_depth; - if (uncompressedSize < 12) { - raw = true; - clength = [0, uncompressedSize]; + this._onFBUComplete(this, + {'x': this._FBU.x, 'y': this._FBU.y, + 'width': this._FBU.width, 'height': this._FBU.height, + 'encoding': this._FBU.encoding, + 'encodingName': this._encNames[this._FBU.encoding]}); + + return true; // We finished this FBU + }, + }; + + Util.make_properties(RFB, [ + ['target', 'wo', 'dom'], // VNC display rendering Canvas object + ['focusContainer', 'wo', 'dom'], // DOM element that captures keyboard input + ['encrypt', 'rw', 'bool'], // Use TLS/SSL/wss encryption + ['true_color', 'rw', 'bool'], // Request true color pixel data + ['local_cursor', 'rw', 'bool'], // Request locally rendered cursor + ['shared', 'rw', 'bool'], // Request shared mode + ['view_only', 'rw', 'bool'], // Disable client mouse/keyboard + ['xvp_password_sep', 'rw', 'str'], // Separator for XVP password fields + ['disconnectTimeout', 'rw', 'int'], // Time (s) to wait for disconnection + ['wsProtocols', 'rw', 'arr'], // Protocols to use in the WebSocket connection + ['repeaterID', 'rw', 'str'], // [UltraVNC] RepeaterID to connect to + ['viewportDrag', 'rw', 'bool'], // Move the viewport on mouse drags + + // Callback functions + ['onUpdateState', 'rw', 'func'], // onUpdateState(rfb, state, oldstate, statusMsg): RFB state update/change + ['onPasswordRequired', 'rw', 'func'], // onPasswordRequired(rfb): VNC password is required + ['onClipboard', 'rw', 'func'], // onClipboard(rfb, text): RFB clipboard contents received + ['onBell', 'rw', 'func'], // onBell(rfb): RFB Bell message received + ['onFBUReceive', 'rw', 'func'], // onFBUReceive(rfb, fbu): RFB FBU received but not yet processed + ['onFBUComplete', 'rw', 'func'], // onFBUComplete(rfb, fbu): RFB FBU received and processed + ['onFBResize', 'rw', 'func'], // onFBResize(rfb, width, height): frame buffer resized + ['onDesktopName', 'rw', 'func'], // onDesktopName(rfb, name): desktop name received + ['onXvpInit', 'rw', 'func'], // onXvpInit(version): XVP extensions active for this connection + ]); + + RFB.prototype.set_local_cursor = function (cursor) { + if (!cursor || (cursor in {'0': 1, 'no': 1, 'false': 1})) { + this._local_cursor = false; } else { - clength = getTightCLength(ws.rQslice(1, 4)); + if (this._display.get_cursor_uri()) { + this._local_cursor = true; + } else { + Util.Warn("Browser does not support local cursor"); + } } - FBU.bytes = 1 + clength[0] + clength[1]; - if (ws.rQwait("TIGHT " + cmode, FBU.bytes)) { return false; } + }; + + RFB.prototype.get_display = function () { return this._display; }; + RFB.prototype.get_keyboard = function () { return this._keyboard; }; + RFB.prototype.get_mouse = function () { return this._mouse; }; + + // Class Methods + RFB.messages = { + keyEvent: function (keysym, down) { + var arr = [4]; + arr.push8(down); + arr.push16(0); + arr.push32(keysym); + return arr; + }, + + pointerEvent: function (x, y, mask) { + var arr = [5]; // msg-type + arr.push8(mask); + arr.push16(x); + arr.push16(y); + return arr; + }, + + // TODO(directxman12): make this unicode compatible? + clientCutText: function (text) { + var arr = [6]; // msg-type + arr.push8(0); // padding + arr.push8(0); // padding + arr.push8(0); // padding + arr.push32(text.length); + var n = text.length; + for (var i = 0; i < n; i++) { + arr.push(text.charCodeAt(i)); + } - // Shift ctl, clength off - ws.rQshiftBytes(1 + clength[0]); + return arr; + }, + + pixelFormat: function (bpp, depth, true_color) { + var arr = [0]; // msg-type + arr.push8(0); // padding + arr.push8(0); // padding + arr.push8(0); // padding + + arr.push8(bpp * 8); // bits-per-pixel + arr.push8(depth * 8); // depth + arr.push8(0); // little-endian + arr.push8(true_color ? 1 : 0); // true-color + + arr.push16(255); // red-max + arr.push16(255); // green-max + arr.push16(255); // blue-max + arr.push8(16); // red-shift + arr.push8(8); // green-shift + arr.push8(0); // blue-shift + + arr.push8(0); // padding + arr.push8(0); // padding + arr.push8(0); // padding + return arr; + }, + + clientEncodings: function (encodings, local_cursor, true_color) { + var i, encList = []; + + for (i = 0; i < encodings.length; i++) { + if (encodings[i][0] === "Cursor" && !local_cursor) { + Util.Debug("Skipping Cursor pseudo-encoding"); + } else if (encodings[i][0] === "TIGHT" && !true_color) { + // TODO: remove this when we have tight+non-true-color + Util.Warn("Skipping tight as it is only supported with true color"); + } else { + encList.push(encodings[i][1]); + } + } - if (raw) { - data = ws.rQshiftBytes(clength[1]); - } else { - data = decompress(ws.rQshiftBytes(clength[1])); + var arr = [2]; // msg-type + arr.push8(0); // padding + + arr.push16(encList.length); // encoding count + for (i = 0; i < encList.length; i++) { + arr.push32(encList[i]); + } + + return arr; + }, + + fbUpdateRequests: function (cleanDirty, fb_width, fb_height) { + var arr = []; + + var cb = cleanDirty.cleanBox; + var w, h; + if (cb.w > 0 && cb.h > 0) { + w = typeof cb.w === "undefined" ? fb_width : cb.w; + h = typeof cb.h === "undefined" ? fb_height : cb.h; + // Request incremental for clean box + arr = arr.concat(RFB.messages.fbUpdateRequest(1, cb.x, cb.y, w, h)); + } + + for (var i = 0; i < cleanDirty.dirtyBoxes.length; i++) { + var db = cleanDirty.dirtyBoxes[i]; + // Force all (non-incremental) for dirty box + w = typeof db.w === "undefined" ? fb_width : db.w; + h = typeof db.h === "undefined" ? fb_height : db.h; + arr = arr.concat(RFB.messages.fbUpdateRequest(0, db.x, db.y, w, h)); + } + + return arr; + }, + + fbUpdateRequest: function (incremental, x, y, w, h) { + if (typeof(x) === "undefined") { x = 0; } + if (typeof(y) === "undefined") { y = 0; } + + var arr = [3]; // msg-type + arr.push8(incremental); + arr.push16(x); + arr.push16(y); + arr.push16(w); + arr.push16(h); + + return arr; } + }; - display.renderQ_push({ - 'type': 'blitRgb', - 'data': data, - 'x': FBU.x, - 'y': FBU.y, - 'width': FBU.width, - 'height': FBU.height}); - return true; - } - - ctl = ws.rQpeek8(); - - // Keep tight reset bits - resetStreams = ctl & 0xF; - - // Figure out filter - ctl = ctl >> 4; - streamId = ctl & 0x3; - - if (ctl === 0x08) cmode = "fill"; - else if (ctl === 0x09) cmode = "jpeg"; - else if (ctl === 0x0A) cmode = "png"; - else if (ctl & 0x04) cmode = "filter"; - else if (ctl < 0x04) cmode = "copy"; - else return fail("Illegal tight compression received, ctl: " + ctl); - - if (isTightPNG && (cmode === "filter" || cmode === "copy")) { - return fail("filter/copy received in tightPNG mode"); - } - - switch (cmode) { - // fill uses fb_depth because TPIXELs drop the padding byte - case "fill": FBU.bytes += fb_depth; break; // TPIXEL - case "jpeg": FBU.bytes += 3; break; // max clength - case "png": FBU.bytes += 3; break; // max clength - case "filter": FBU.bytes += 2; break; // filter id + num colors if palette - case "copy": break; - } - - if (ws.rQwait("TIGHT " + cmode, FBU.bytes)) { return false; } - - //Util.Debug(" ws.rQslice(0,20): " + ws.rQslice(0,20) + " (" + ws.rQlen() + ")"); - //Util.Debug(" cmode: " + cmode); - - // Determine FBU.bytes - switch (cmode) { - case "fill": - ws.rQshift8(); // shift off ctl - color = ws.rQshiftBytes(fb_depth); - display.renderQ_push({ - 'type': 'fill', - 'x': FBU.x, - 'y': FBU.y, - 'width': FBU.width, - 'height': FBU.height, - 'color': [color[2], color[1], color[0]] }); - break; - case "png": - case "jpeg": - clength = getTightCLength(ws.rQslice(1, 4)); - FBU.bytes = 1 + clength[0] + clength[1]; // ctl + clength size + jpeg-data - if (ws.rQwait("TIGHT " + cmode, FBU.bytes)) { return false; } - - // We have everything, render it - //Util.Debug(" jpeg, ws.rQlen(): " + ws.rQlen() + ", clength[0]: " + - // clength[0] + ", clength[1]: " + clength[1]); - ws.rQshiftBytes(1 + clength[0]); // shift off ctl + compact length - img = new Image(); - img.src = "data:image/" + cmode + - extract_data_uri(ws.rQshiftBytes(clength[1])); - display.renderQ_push({ - 'type': 'img', - 'img': img, - 'x': FBU.x, - 'y': FBU.y}); - img = null; - break; - case "filter": - filterId = rQ[rQi + 1]; - if (filterId === 1) { - if (!handlePalette()) { return false; } - } else { - // Filter 0, Copy could be valid here, but servers don't send it as an explicit filter - // Filter 2, Gradient is valid but not used if jpeg is enabled - throw("Unsupported tight subencoding received, filter: " + filterId); + RFB.genDES = function (password, challenge) { + var passwd = []; + for (var i = 0; i < password.length; i++) { + passwd.push(password.charCodeAt(i)); } - break; - case "copy": - if (!handleCopy()) { return false; } - break; - } - - FBU.bytes = 0; - FBU.rects -= 1; - //Util.Debug(" ending ws.rQslice(0,20): " + ws.rQslice(0,20) + " (" + ws.rQlen() + ")"); - //Util.Debug("<< display_tight_png"); - return true; -} - -extract_data_uri = function(arr) { - //var i, stra = []; - //for (i=0; i< arr.length; i += 1) { - // stra.push(String.fromCharCode(arr[i])); - //} - //return "," + escape(stra.join('')); - return ";base64," + Base64.encode(arr); -}; - -encHandlers.TIGHT = function () { return display_tight(false); }; -encHandlers.TIGHT_PNG = function () { return display_tight(true); }; - -encHandlers.last_rect = function last_rect() { - //Util.Debug(">> last_rect"); - FBU.rects = 0; - //Util.Debug("<< last_rect"); - return true; -}; - -encHandlers.DesktopSize = function set_desktopsize() { - Util.Debug(">> set_desktopsize"); - fb_width = FBU.width; - fb_height = FBU.height; - display.resize(fb_width, fb_height); - timing.fbu_rt_start = (new Date()).getTime(); - // Send a new non-incremental request - ws.send(fbUpdateRequests()); - - FBU.bytes = 0; - FBU.rects -= 1; - - Util.Debug("<< set_desktopsize"); - return true; -}; - -encHandlers.Cursor = function set_cursor() { - var x, y, w, h, pixelslength, masklength; - Util.Debug(">> set_cursor"); - x = FBU.x; // hotspot-x - y = FBU.y; // hotspot-y - w = FBU.width; - h = FBU.height; - - pixelslength = w * h * fb_Bpp; - masklength = Math.floor((w + 7) / 8) * h; - - FBU.bytes = pixelslength + masklength; - if (ws.rQwait("cursor encoding", FBU.bytes)) { return false; } - - //Util.Debug(" set_cursor, x: " + x + ", y: " + y + ", w: " + w + ", h: " + h); - - display.changeCursor(ws.rQshiftBytes(pixelslength), - ws.rQshiftBytes(masklength), - x, y, w, h); - - FBU.bytes = 0; - FBU.rects -= 1; - - Util.Debug("<< set_cursor"); - return true; -}; - -encHandlers.JPEG_quality_lo = function set_jpeg_quality() { - Util.Error("Server sent jpeg_quality pseudo-encoding"); -}; - -encHandlers.compress_lo = function set_compress_level() { - Util.Error("Server sent compress level pseudo-encoding"); -}; + return (new DES(passwd)).encrypt(challenge); + }; -/* - * Client message routines - */ + RFB.extract_data_uri = function (arr) { + return ";base64," + Base64.encode(arr); + }; -pixelFormat = function() { - //Util.Debug(">> pixelFormat"); - var arr; - arr = [0]; // msg-type - arr.push8(0); // padding - arr.push8(0); // padding - arr.push8(0); // padding - - arr.push8(fb_Bpp * 8); // bits-per-pixel - arr.push8(fb_depth * 8); // depth - arr.push8(0); // little-endian - arr.push8(conf.true_color ? 1 : 0); // true-color - - arr.push16(255); // red-max - arr.push16(255); // green-max - arr.push16(255); // blue-max - arr.push8(16); // red-shift - arr.push8(8); // green-shift - arr.push8(0); // blue-shift - - arr.push8(0); // padding - arr.push8(0); // padding - arr.push8(0); // padding - //Util.Debug("<< pixelFormat"); - return arr; -}; - -clientEncodings = function() { - //Util.Debug(">> clientEncodings"); - var arr, i, encList = []; - - for (i=0; i> fbUpdateRequest"); - if (typeof(x) === "undefined") { x = 0; } - if (typeof(y) === "undefined") { y = 0; } - if (typeof(xw) === "undefined") { xw = fb_width; } - if (typeof(yw) === "undefined") { yw = fb_height; } - var arr; - arr = [3]; // msg-type - arr.push8(incremental); - arr.push16(x); - arr.push16(y); - arr.push16(xw); - arr.push16(yw); - //Util.Debug("<< fbUpdateRequest"); - return arr; -}; - -// Based on clean/dirty areas, generate requests to send -fbUpdateRequests = function() { - var cleanDirty = display.getCleanDirtyReset(), - arr = [], i, cb, db; - - cb = cleanDirty.cleanBox; - if (cb.w > 0 && cb.h > 0) { - // Request incremental for clean box - arr = arr.concat(fbUpdateRequest(1, cb.x, cb.y, cb.w, cb.h)); - } - for (i = 0; i < cleanDirty.dirtyBoxes.length; i++) { - db = cleanDirty.dirtyBoxes[i]; - // Force all (non-incremental for dirty box - arr = arr.concat(fbUpdateRequest(0, db.x, db.y, db.w, db.h)); - } - return arr; -}; - - - -keyEvent = function(keysym, down) { - //Util.Debug(">> keyEvent, keysym: " + keysym + ", down: " + down); - var arr; - arr = [4]; // msg-type - arr.push8(down); - arr.push16(0); - arr.push32(keysym); - //Util.Debug("<< keyEvent"); - return arr; -}; - -pointerEvent = function(x, y) { - //Util.Debug(">> pointerEvent, x,y: " + x + "," + y + - // " , mask: " + mouse_buttonMask); - var arr; - arr = [5]; // msg-type - arr.push8(mouse_buttonMask); - arr.push16(x); - arr.push16(y); - //Util.Debug("<< pointerEvent"); - return arr; -}; - -clientCutText = function(text) { - //Util.Debug(">> clientCutText"); - var arr, i, n; - arr = [6]; // msg-type - arr.push8(0); // padding - arr.push8(0); // padding - arr.push8(0); // padding - arr.push32(text.length); - n = text.length; - for (i=0; i < n; i+=1) { - arr.push(text.charCodeAt(i)); - } - //Util.Debug("<< clientCutText:" + arr); - return arr; -}; - - - -// -// Public API interface functions -// - -that.connect = function(host, port, password, path) { - //Util.Debug(">> connect"); - - rfb_host = host; - rfb_port = port; - rfb_password = (password !== undefined) ? password : ""; - rfb_path = (path !== undefined) ? path : ""; - - if ((!rfb_host) || (!rfb_port)) { - return fail("Must set host and port"); - } - - updateState('connect'); - //Util.Debug("<< connect"); - -}; - -that.disconnect = function() { - //Util.Debug(">> disconnect"); - updateState('disconnect', 'Disconnecting'); - //Util.Debug("<< disconnect"); -}; - -that.sendPassword = function(passwd) { - rfb_password = passwd; - rfb_state = "Authentication"; - setTimeout(init_msg, 1); -}; - -that.sendCtrlAltDel = function() { - if (rfb_state !== "normal" || conf.view_only) { return false; } - Util.Info("Sending Ctrl-Alt-Del"); - var arr = []; - arr = arr.concat(keyEvent(0xFFE3, 1)); // Control - arr = arr.concat(keyEvent(0xFFE9, 1)); // Alt - arr = arr.concat(keyEvent(0xFFFF, 1)); // Delete - arr = arr.concat(keyEvent(0xFFFF, 0)); // Delete - arr = arr.concat(keyEvent(0xFFE9, 0)); // Alt - arr = arr.concat(keyEvent(0xFFE3, 0)); // Control - arr = arr.concat(fbUpdateRequests()); - ws.send(arr); -}; - -// Send a key press. If 'down' is not specified then send a down key -// followed by an up key. -that.sendKey = function(code, down) { - if (rfb_state !== "normal" || conf.view_only) { return false; } - var arr = []; - if (typeof down !== 'undefined') { - Util.Info("Sending key code (" + (down ? "down" : "up") + "): " + code); - arr = arr.concat(keyEvent(code, down ? 1 : 0)); - } else { - Util.Info("Sending key code (down + up): " + code); - arr = arr.concat(keyEvent(code, 1)); - arr = arr.concat(keyEvent(code, 0)); - } - arr = arr.concat(fbUpdateRequests()); - ws.send(arr); -}; - -that.clipboardPasteFrom = function(text) { - if (rfb_state !== "normal") { return; } - //Util.Debug(">> clipboardPasteFrom: " + text.substr(0,40) + "..."); - ws.send(clientCutText(text)); - //Util.Debug("<< clipboardPasteFrom"); -}; - -// Override internal functions for testing -that.testMode = function(override_send) { - test_mode = true; - that.recv_message = ws.testMode(override_send); - - checkEvents = function () { /* Stub Out */ }; - that.connect = function(host, port, password) { - rfb_host = host; - rfb_port = port; - rfb_password = password; - updateState('ProtocolVersion', "Starting VNC handshake"); - }; -}; + RFB.encodingHandlers = { + RAW: function () { + if (this._FBU.lines === 0) { + this._FBU.lines = this._FBU.height; + } + this._FBU.bytes = this._FBU.width * this._fb_Bpp; // at least a line + if (this._sock.rQwait("RAW", this._FBU.bytes)) { return false; } + var cur_y = this._FBU.y + (this._FBU.height - this._FBU.lines); + var curr_height = Math.min(this._FBU.lines, + Math.floor(this._sock.rQlen() / (this._FBU.width * this._fb_Bpp))); + this._display.blitImage(this._FBU.x, cur_y, this._FBU.width, + curr_height, this._sock.get_rQ(), + this._sock.get_rQi()); + this._sock.rQskipBytes(this._FBU.width * curr_height * this._fb_Bpp); + this._FBU.lines -= curr_height; + + if (this._FBU.lines > 0) { + this._FBU.bytes = this._FBU.width * this._fb_Bpp; // At least another line + } else { + this._FBU.rects--; + this._FBU.bytes = 0; + } -return constructor(); // Return the public API interface + return true; + }, + + COPYRECT: function () { + this._FBU.bytes = 4; + if (this._sock.rQwait("COPYRECT", 4)) { return false; } + this._display.renderQ_push({ + 'type': 'copy', + 'old_x': this._sock.rQshift16(), + 'old_y': this._sock.rQshift16(), + 'x': this._FBU.x, + 'y': this._FBU.y, + 'width': this._FBU.width, + 'height': this._FBU.height + }); + this._FBU.rects--; + this._FBU.bytes = 0; + return true; + }, + + RRE: function () { + var color; + if (this._FBU.subrects === 0) { + this._FBU.bytes = 4 + this._fb_Bpp; + if (this._sock.rQwait("RRE", 4 + this._fb_Bpp)) { return false; } + this._FBU.subrects = this._sock.rQshift32(); + color = this._sock.rQshiftBytes(this._fb_Bpp); // Background + this._display.fillRect(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, color); + } + + while (this._FBU.subrects > 0 && this._sock.rQlen() >= (this._fb_Bpp + 8)) { + color = this._sock.rQshiftBytes(this._fb_Bpp); + var x = this._sock.rQshift16(); + var y = this._sock.rQshift16(); + var width = this._sock.rQshift16(); + var height = this._sock.rQshift16(); + this._display.fillRect(this._FBU.x + x, this._FBU.y + y, width, height, color); + this._FBU.subrects--; + } + + if (this._FBU.subrects > 0) { + var chunk = Math.min(this._rre_chunk_sz, this._FBU.subrects); + this._FBU.bytes = (this._fb_Bpp + 8) * chunk; + } else { + this._FBU.rects--; + this._FBU.bytes = 0; + } -} // End of RFB() + return true; + }, + + HEXTILE: function () { + var rQ = this._sock.get_rQ(); + var rQi = this._sock.get_rQi(); + + if (this._FBU.tiles === 0) { + this._FBU.tiles_x = Math.ceil(this._FBU.width / 16); + this._FBU.tiles_y = Math.ceil(this._FBU.height / 16); + this._FBU.total_tiles = this._FBU.tiles_x * this._FBU.tiles_y; + this._FBU.tiles = this._FBU.total_tiles; + } + + while (this._FBU.tiles > 0) { + this._FBU.bytes = 1; + if (this._sock.rQwait("HEXTILE subencoding", this._FBU.bytes)) { return false; } + var subencoding = rQ[rQi]; // Peek + if (subencoding > 30) { // Raw + this._fail("Disconnected: illegal hextile subencoding " + subencoding); + return false; + } + + var subrects = 0; + var curr_tile = this._FBU.total_tiles - this._FBU.tiles; + var tile_x = curr_tile % this._FBU.tiles_x; + var tile_y = Math.floor(curr_tile / this._FBU.tiles_x); + var x = this._FBU.x + tile_x * 16; + var y = this._FBU.y + tile_y * 16; + var w = Math.min(16, (this._FBU.x + this._FBU.width) - x); + var h = Math.min(16, (this._FBU.y + this._FBU.height) - y); + + // Figure out how much we are expecting + if (subencoding & 0x01) { // Raw + this._FBU.bytes += w * h * this._fb_Bpp; + } else { + if (subencoding & 0x02) { // Background + this._FBU.bytes += this._fb_Bpp; + } + if (subencoding & 0x04) { // Foreground + this._FBU.bytes += this._fb_Bpp; + } + if (subencoding & 0x08) { // AnySubrects + this._FBU.bytes++; // Since we aren't shifting it off + if (this._sock.rQwait("hextile subrects header", this._FBU.bytes)) { return false; } + subrects = rQ[rQi + this._FBU.bytes - 1]; // Peek + if (subencoding & 0x10) { // SubrectsColoured + this._FBU.bytes += subrects * (this._fb_Bpp + 2); + } else { + this._FBU.bytes += subrects * 2; + } + } + } + + if (this._sock.rQwait("hextile", this._FBU.bytes)) { return false; } + + // We know the encoding and have a whole tile + this._FBU.subencoding = rQ[rQi]; + rQi++; + if (this._FBU.subencoding === 0) { + if (this._FBU.lastsubencoding & 0x01) { + // Weird: ignore blanks are RAW + Util.Debug(" Ignoring blank after RAW"); + } else { + this._display.fillRect(x, y, w, h, rQ, rQi); + rQi += this._FBU.bytes - 1; + } + } else if (this._FBU.subencoding & 0x01) { // Raw + this._display.blitImage(x, y, w, h, rQ, rQi); + rQi += this._FBU.bytes - 1; + } else { + if (this._FBU.subencoding & 0x02) { // Background + this._FBU.background = rQ.slice(rQi, rQi + this._fb_Bpp); + rQi += this._fb_Bpp; + } + if (this._FBU.subencoding & 0x04) { // Foreground + this._FBU.foreground = rQ.slice(rQi, rQi + this._fb_Bpp); + rQi += this._fb_Bpp; + } + + this._display.startTile(x, y, w, h, this._FBU.background); + if (this._FBU.subencoding & 0x08) { // AnySubrects + subrects = rQ[rQi]; + rQi++; + + for (var s = 0; s < subrects; s++) { + var color; + if (this._FBU.subencoding & 0x10) { // SubrectsColoured + color = rQ.slice(rQi, rQi + this._fb_Bpp); + rQi += this._fb_Bpp; + } else { + color = this._FBU.foreground; + } + var xy = rQ[rQi]; + rQi++; + var sx = (xy >> 4); + var sy = (xy & 0x0f); + + var wh = rQ[rQi]; + rQi++; + var sw = (wh >> 4) + 1; + var sh = (wh & 0x0f) + 1; + + this._display.subTile(sx, sy, sw, sh, color); + } + } + this._display.finishTile(); + } + this._sock.set_rQi(rQi); + this._FBU.lastsubencoding = this._FBU.subencoding; + this._FBU.bytes = 0; + this._FBU.tiles--; + } + + if (this._FBU.tiles === 0) { + this._FBU.rects--; + } + + return true; + }, + + getTightCLength: function (arr) { + var header = 1, data = 0; + data += arr[0] & 0x7f; + if (arr[0] & 0x80) { + header++; + data += (arr[1] & 0x7f) << 7; + if (arr[1] & 0x80) { + header++; + data += arr[2] << 14; + } + } + return [header, data]; + }, + + display_tight: function (isTightPNG) { + if (this._fb_depth === 1) { + this._fail("Tight protocol handler only implements true color mode"); + } + + this._FBU.bytes = 1; // compression-control byte + if (this._sock.rQwait("TIGHT compression-control", this._FBU.bytes)) { return false; } + + var checksum = function (data) { + var sum = 0; + for (var i = 0; i < data.length; i++) { + sum += data[i]; + if (sum > 65536) sum -= 65536; + } + return sum; + }; + + var resetStreams = 0; + var streamId = -1; + var decompress = function (data) { + for (var i = 0; i < 4; i++) { + if ((resetStreams >> i) & 1) { + this._FBU.zlibs[i].reset(); + Util.Info("Reset zlib stream " + i); + } + } + + var uncompressed = this._FBU.zlibs[streamId].uncompress(data, 0); + if (uncompressed.status !== 0) { + Util.Error("Invalid data in zlib stream"); + } + + return uncompressed.data; + }.bind(this); + + var indexedToRGB = function (data, numColors, palette, width, height) { + // Convert indexed (palette based) image data to RGB + // TODO: reduce number of calculations inside loop + var dest = []; + var x, y, dp, sp; + if (numColors === 2) { + var w = Math.floor((width + 7) / 8); + var w1 = Math.floor(width / 8); + + for (y = 0; y < height; y++) { + var b; + for (x = 0; x < w1; x++) { + for (b = 7; b >= 0; b--) { + dp = (y * width + x * 8 + 7 - b) * 3; + sp = (data[y * w + x] >> b & 1) * 3; + dest[dp] = palette[sp]; + dest[dp + 1] = palette[sp + 1]; + dest[dp + 2] = palette[sp + 2]; + } + } + + for (b = 7; b >= 8 - width % 8; b--) { + dp = (y * width + x * 8 + 7 - b) * 3; + sp = (data[y * w + x] >> b & 1) * 3; + dest[dp] = palette[sp]; + dest[dp + 1] = palette[sp + 1]; + dest[dp + 2] = palette[sp + 2]; + } + } + } else { + for (y = 0; y < height; y++) { + for (x = 0; x < width; x++) { + dp = (y * width + x) * 3; + sp = data[y * width + x] * 3; + dest[dp] = palette[sp]; + dest[dp + 1] = palette[sp + 1]; + dest[dp + 2] = palette[sp + 2]; + } + } + } + + return dest; + }.bind(this); + + var rQ = this._sock.get_rQ(); + var rQi = this._sock.get_rQi(); + var cmode, clength, data; + + var handlePalette = function () { + var numColors = rQ[rQi + 2] + 1; + var paletteSize = numColors * this._fb_depth; + this._FBU.bytes += paletteSize; + if (this._sock.rQwait("TIGHT palette " + cmode, this._FBU.bytes)) { return false; } + + var bpp = (numColors <= 2) ? 1 : 8; + var rowSize = Math.floor((this._FBU.width * bpp + 7) / 8); + var raw = false; + if (rowSize * this._FBU.height < 12) { + raw = true; + clength = [0, rowSize * this._FBU.height]; + } else { + clength = RFB.encodingHandlers.getTightCLength(this._sock.rQslice(3 + paletteSize, + 3 + paletteSize + 3)); + } + + this._FBU.bytes += clength[0] + clength[1]; + if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; } + + // Shift ctl, filter id, num colors, palette entries, and clength off + this._sock.rQskipBytes(3); + var palette = this._sock.rQshiftBytes(paletteSize); + this._sock.rQskipBytes(clength[0]); + + if (raw) { + data = this._sock.rQshiftBytes(clength[1]); + } else { + data = decompress(this._sock.rQshiftBytes(clength[1])); + } + + // Convert indexed (palette based) image data to RGB + var rgb = indexedToRGB(data, numColors, palette, this._FBU.width, this._FBU.height); + + this._display.renderQ_push({ + 'type': 'blitRgb', + 'data': rgb, + 'x': this._FBU.x, + 'y': this._FBU.y, + 'width': this._FBU.width, + 'height': this._FBU.height + }); + + return true; + }.bind(this); + + var handleCopy = function () { + var raw = false; + var uncompressedSize = this._FBU.width * this._FBU.height * this._fb_depth; + if (uncompressedSize < 12) { + raw = true; + clength = [0, uncompressedSize]; + } else { + clength = RFB.encodingHandlers.getTightCLength(this._sock.rQslice(1, 4)); + } + this._FBU.bytes = 1 + clength[0] + clength[1]; + if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; } + + // Shift ctl, clength off + this._sock.rQshiftBytes(1 + clength[0]); + + if (raw) { + data = this._sock.rQshiftBytes(clength[1]); + } else { + data = decompress(this._sock.rQshiftBytes(clength[1])); + } + + this._display.renderQ_push({ + 'type': 'blitRgb', + 'data': data, + 'x': this._FBU.x, + 'y': this._FBU.y, + 'width': this._FBU.width, + 'height': this._FBU.height + }); + + return true; + }.bind(this); + + var ctl = this._sock.rQpeek8(); + + // Keep tight reset bits + resetStreams = ctl & 0xF; + + // Figure out filter + ctl = ctl >> 4; + streamId = ctl & 0x3; + + if (ctl === 0x08) cmode = "fill"; + else if (ctl === 0x09) cmode = "jpeg"; + else if (ctl === 0x0A) cmode = "png"; + else if (ctl & 0x04) cmode = "filter"; + else if (ctl < 0x04) cmode = "copy"; + else return this._fail("Illegal tight compression received, ctl: " + ctl); + + if (isTightPNG && (cmode === "filter" || cmode === "copy")) { + return this._fail("filter/copy received in tightPNG mode"); + } + + switch (cmode) { + // fill use fb_depth because TPIXELs drop the padding byte + case "fill": // TPIXEL + this._FBU.bytes += this._fb_depth; + break; + case "jpeg": // max clength + this._FBU.bytes += 3; + break; + case "png": // max clength + this._FBU.bytes += 3; + break; + case "filter": // filter id + num colors if palette + this._FBU.bytes += 2; + break; + case "copy": + break; + } + + if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; } + + // Determine FBU.bytes + switch (cmode) { + case "fill": + this._sock.rQskip8(); // shift off ctl + var color = this._sock.rQshiftBytes(this._fb_depth); + this._display.renderQ_push({ + 'type': 'fill', + 'x': this._FBU.x, + 'y': this._FBU.y, + 'width': this._FBU.width, + 'height': this._FBU.height, + 'color': [color[2], color[1], color[0]] + }); + break; + case "png": + case "jpeg": + clength = RFB.encodingHandlers.getTightCLength(this._sock.rQslice(1, 4)); + this._FBU.bytes = 1 + clength[0] + clength[1]; // ctl + clength size + jpeg-data + if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; } + + // We have everything, render it + this._sock.rQskipBytes(1 + clength[0]); // shift off clt + compact length + var img = new Image(); + img.src = "data: image/" + cmode + + RFB.extract_data_uri(this._sock.rQshiftBytes(clength[1])); + this._display.renderQ_push({ + 'type': 'img', + 'img': img, + 'x': this._FBU.x, + 'y': this._FBU.y + }); + img = null; + break; + case "filter": + var filterId = rQ[rQi + 1]; + if (filterId === 1) { + if (!handlePalette()) { return false; } + } else { + // Filter 0, Copy could be valid here, but servers don't send it as an explicit filter + // Filter 2, Gradient is valid but not use if jpeg is enabled + // TODO(directxman12): why aren't we just calling '_fail' here + throw new Error("Unsupported tight subencoding received, filter: " + filterId); + } + break; + case "copy": + if (!handleCopy()) { return false; } + break; + } + + + this._FBU.bytes = 0; + this._FBU.rects--; + + return true; + }, + + TIGHT: function () { return this._encHandlers.display_tight(false); }, + TIGHT_PNG: function () { return this._encHandlers.display_tight(true); }, + + last_rect: function () { + this._FBU.rects = 0; + return true; + }, + + DesktopSize: function () { + Util.Debug(">> set_desktopsize"); + this._fb_width = this._FBU.width; + this._fb_height = this._FBU.height; + this._onFBResize(this, this._fb_width, this._fb_height); + this._display.resize(this._fb_width, this._fb_height); + this._timing.fbu_rt_start = (new Date()).getTime(); + + this._FBU.bytes = 0; + this._FBU.rects--; + + Util.Debug("<< set_desktopsize"); + return true; + }, + + Cursor: function () { + Util.Debug(">> set_cursor"); + var x = this._FBU.x; // hotspot-x + var y = this._FBU.y; // hotspot-y + var w = this._FBU.width; + var h = this._FBU.height; + + var pixelslength = w * h * this._fb_Bpp; + var masklength = Math.floor((w + 7) / 8) * h; + + this._FBU.bytes = pixelslength + masklength; + if (this._sock.rQwait("cursor encoding", this._FBU.bytes)) { return false; } + + this._display.changeCursor(this._sock.rQshiftBytes(pixelslength), + this._sock.rQshiftBytes(masklength), + x, y, w, h); + + this._FBU.bytes = 0; + this._FBU.rects--; + + Util.Debug("<< set_cursor"); + return true; + }, + + JPEG_quality_lo: function () { + Util.Error("Server sent jpeg_quality pseudo-encoding"); + }, + + compress_lo: function () { + Util.Error("Server sent compress level pseudo-encoding"); + } + }; +})(); diff --git a/webclients/novnc/include/ui.js b/webclients/novnc/include/ui.js index dc837e6..e869aa6 100644 --- a/webclients/novnc/include/ui.js +++ b/webclients/novnc/include/ui.js @@ -1,659 +1,979 @@ /* * noVNC: HTML5 VNC client * Copyright (C) 2012 Joel Martin - * Licensed under LGPL-3 (see LICENSE.txt) + * Copyright (C) 2013 Samuel Mannehed for Cendio AB + * Licensed under MPL 2.0 (see LICENSE.txt) * * See README.md for usage and integration instructions. */ -"use strict"; -/*jslint white: false, browser: true */ -/*global window, $D, Util, WebUtil, RFB, Display */ - -var UI = { - -rfb_state : 'loaded', -settingsOpen : false, -connSettingsOpen : false, -clipboardOpen: false, -keyboardVisible: false, - -// Render default UI and initialize settings menu -load: function() { - var html = '', i, sheet, sheets, llevels; - - // Stylesheet selection dropdown - sheet = WebUtil.selectStylesheet(); - sheets = WebUtil.getStylesheets(); - for (i = 0; i < sheets.length; i += 1) { - UI.addOption($D('noVNC_stylesheet'),sheets[i].title, sheets[i].title); - } - - // Logging selection dropdown - llevels = ['error', 'warn', 'info', 'debug']; - for (i = 0; i < llevels.length; i += 1) { - UI.addOption($D('noVNC_logging'),llevels[i], llevels[i]); - } - - // Settings with immediate effects - UI.initSetting('logging', 'warn'); - WebUtil.init_logging(UI.getSetting('logging')); - - UI.initSetting('stylesheet', 'default'); - WebUtil.selectStylesheet(null); - // call twice to get around webkit bug - WebUtil.selectStylesheet(UI.getSetting('stylesheet')); - - /* Populate the controls if defaults are provided in the URL */ - UI.initSetting('host', window.location.hostname); - UI.initSetting('port', window.location.port); - UI.initSetting('password', ''); - UI.initSetting('encrypt', (window.location.protocol === "https:")); - UI.initSetting('true_color', true); - UI.initSetting('cursor', false); - UI.initSetting('shared', true); - UI.initSetting('view_only', false); - UI.initSetting('connectTimeout', 2); - UI.initSetting('path', 'websockify'); - UI.initSetting('repeaterID', ''); - - UI.rfb = RFB({'target': $D('noVNC_canvas'), - 'onUpdateState': UI.updateState, - 'onClipboard': UI.clipReceive}); - UI.updateVisualState(); - - // Unfocus clipboard when over the VNC area - //$D('VNC_screen').onmousemove = function () { - // var keyboard = UI.rfb.get_keyboard(); - // if ((! keyboard) || (! keyboard.get_focused())) { - // $D('VNC_clipboard_text').blur(); - // } - // }; - - // Show mouse selector buttons on touch screen devices - if ('ontouchstart' in document.documentElement) { - // Show mobile buttons - $D('noVNC_mobile_buttons').style.display = "inline"; - UI.setMouseButton(); - // Remove the address bar - setTimeout(function() { window.scrollTo(0, 1); }, 100); - UI.forceSetting('clip', true); - $D('noVNC_clip').disabled = true; - } else { - UI.initSetting('clip', false); - } - - //iOS Safari does not support CSS position:fixed. - //This detects iOS devices and enables javascript workaround. - if ((navigator.userAgent.match(/iPhone/i)) || - (navigator.userAgent.match(/iPod/i)) || - (navigator.userAgent.match(/iPad/i))) { - //UI.setOnscroll(); - //UI.setResize(); - } - - $D('noVNC_host').focus(); - - UI.setViewClip(); - Util.addEvent(window, 'resize', UI.setViewClip); - - Util.addEvent(window, 'beforeunload', function () { - if (UI.rfb_state === 'normal') { - return "You are currently connected."; - } - } ); - - // Show description by default when hosted at for kanaka.github.com - if (location.host === "kanaka.github.com") { - // Open the description dialog - $D('noVNC_description').style.display = "block"; - } else { - // Open the connect panel on first load - UI.toggleConnectPanel(); - } -}, - -// Read form control compatible setting from cookie -getSetting: function(name) { - var val, ctrl = $D('noVNC_' + name); - val = WebUtil.readCookie(name); - if (ctrl.type === 'checkbox') { - if (val.toLowerCase() in {'0':1, 'no':1, 'false':1}) { - val = false; - } else { - val = true; - } - } - return val; -}, +/* jslint white: false, browser: true */ +/* global window, $D, Util, WebUtil, RFB, Display */ + +var UI; + +(function () { + "use strict"; + + // Load supporting scripts + window.onscriptsload = function () { UI.load(); }; + window.onload = function () { UI.keyboardinputReset(); }; + Util.load_scripts(["webutil.js", "base64.js", "websock.js", "des.js", + "keysymdef.js", "keyboard.js", "input.js", "display.js", + "jsunzip.js", "rfb.js", "keysym.js"]); + + var UI = { + + rfb_state : 'loaded', + settingsOpen : false, + connSettingsOpen : false, + popupStatusOpen : false, + clipboardOpen: false, + keyboardVisible: false, + hideKeyboardTimeout: null, + lastKeyboardinput: null, + defaultKeyboardinputLen: 100, + extraKeysVisible: false, + ctrlOn: false, + altOn: false, + isTouchDevice: false, + + // Setup rfb object, load settings from browser storage, then call + // UI.init to setup the UI/menus + load: function (callback) { + WebUtil.initSettings(UI.start, callback); + }, + + // Render default UI and initialize settings menu + start: function(callback) { + UI.isTouchDevice = 'ontouchstart' in document.documentElement; + + // Stylesheet selection dropdown + var sheet = WebUtil.selectStylesheet(); + var sheets = WebUtil.getStylesheets(); + var i; + for (i = 0; i < sheets.length; i += 1) { + UI.addOption($D('noVNC_stylesheet'),sheets[i].title, sheets[i].title); + } -// Update cookie and form control setting. If value is not set, then -// updates from control to current cookie setting. -updateSetting: function(name, value) { + // Logging selection dropdown + var llevels = ['error', 'warn', 'info', 'debug']; + for (i = 0; i < llevels.length; i += 1) { + UI.addOption($D('noVNC_logging'),llevels[i], llevels[i]); + } - var i, ctrl = $D('noVNC_' + name); - // Save the cookie for this session - if (typeof value !== 'undefined') { - WebUtil.createCookie(name, value); - } + // Settings with immediate effects + UI.initSetting('logging', 'warn'); + WebUtil.init_logging(UI.getSetting('logging')); + + UI.initSetting('stylesheet', 'default'); + WebUtil.selectStylesheet(null); + // call twice to get around webkit bug + WebUtil.selectStylesheet(UI.getSetting('stylesheet')); + + // if port == 80 (or 443) then it won't be present and should be + // set manually + var port = window.location.port; + if (!port) { + if (window.location.protocol.substring(0,5) == 'https') { + port = 443; + } + else if (window.location.protocol.substring(0,4) == 'http') { + port = 80; + } + } - // Update the settings control - value = UI.getSetting(name); + /* Populate the controls if defaults are provided in the URL */ + UI.initSetting('host', window.location.hostname); + UI.initSetting('port', port); + UI.initSetting('password', ''); + UI.initSetting('encrypt', (window.location.protocol === "https:")); + UI.initSetting('true_color', true); + UI.initSetting('cursor', !UI.isTouchDevice); + UI.initSetting('shared', true); + UI.initSetting('view_only', false); + UI.initSetting('path', 'websockify'); + UI.initSetting('repeaterID', ''); + + UI.rfb = new RFB({'target': $D('noVNC_canvas'), + 'onUpdateState': UI.updateState, + 'onXvpInit': UI.updateXvpVisualState, + 'onClipboard': UI.clipReceive, + 'onDesktopName': UI.updateDocumentTitle}); + + var autoconnect = WebUtil.getQueryVar('autoconnect', false); + if (autoconnect === 'true' || autoconnect == '1') { + autoconnect = true; + UI.connect(); + } else { + autoconnect = false; + } - if (ctrl.type === 'checkbox') { - ctrl.checked = value; + UI.updateVisualState(); + + // Show mouse selector buttons on touch screen devices + if (UI.isTouchDevice) { + // Show mobile buttons + $D('noVNC_mobile_buttons').style.display = "inline"; + UI.setMouseButton(); + // Remove the address bar + setTimeout(function() { window.scrollTo(0, 1); }, 100); + UI.forceSetting('clip', true); + $D('noVNC_clip').disabled = true; + } else { + UI.initSetting('clip', false); + } - } else if (typeof ctrl.options !== 'undefined') { - for (i = 0; i < ctrl.options.length; i += 1) { - if (ctrl.options[i].value === value) { - ctrl.selectedIndex = i; - break; + //iOS Safari does not support CSS position:fixed. + //This detects iOS devices and enables javascript workaround. + if ((navigator.userAgent.match(/iPhone/i)) || + (navigator.userAgent.match(/iPod/i)) || + (navigator.userAgent.match(/iPad/i))) { + //UI.setOnscroll(); + //UI.setResize(); } - } - } else { - /*Weird IE9 error leads to 'null' appearring - in textboxes instead of ''.*/ - if (value === null) { - value = ""; - } - ctrl.value = value; - } -}, - -// Save control setting to cookie -saveSetting: function(name) { - var val, ctrl = $D('noVNC_' + name); - if (ctrl.type === 'checkbox') { - val = ctrl.checked; - } else if (typeof ctrl.options !== 'undefined') { - val = ctrl.options[ctrl.selectedIndex].value; - } else { - val = ctrl.value; - } - WebUtil.createCookie(name, val); - //Util.Debug("Setting saved '" + name + "=" + val + "'"); - return val; -}, - -// Initial page load read/initialization of settings -initSetting: function(name, defVal) { - var val; - - // Check Query string followed by cookie - val = WebUtil.getQueryVar(name); - if (val === null) { - val = WebUtil.readCookie(name, defVal); - } - UI.updateSetting(name, val); - //Util.Debug("Setting '" + name + "' initialized to '" + val + "'"); - return val; -}, - -// Force a setting to be a certain value -forceSetting: function(name, val) { - UI.updateSetting(name, val); - return val; -}, - - -// Show the clipboard panel -toggleClipboardPanel: function() { - // Close the description panel - $D('noVNC_description').style.display = "none"; - //Close settings if open - if (UI.settingsOpen === true) { - UI.settingsApply(); - UI.closeSettingsMenu(); - } - //Close connection settings if open - if (UI.connSettingsOpen === true) { - UI.toggleConnectPanel(); - } - //Toggle Clipboard Panel - if (UI.clipboardOpen === true) { - $D('noVNC_clipboard').style.display = "none"; - $D('clipboardButton').className = "noVNC_status_button"; - UI.clipboardOpen = false; - } else { - $D('noVNC_clipboard').style.display = "block"; - $D('clipboardButton').className = "noVNC_status_button_selected"; - UI.clipboardOpen = true; - } -}, - -// Show the connection settings panel/menu -toggleConnectPanel: function() { - // Close the description panel - $D('noVNC_description').style.display = "none"; - //Close connection settings if open - if (UI.settingsOpen === true) { - UI.settingsApply(); - UI.closeSettingsMenu(); - $D('connectButton').className = "noVNC_status_button"; - } - if (UI.clipboardOpen === true) { - UI.toggleClipboardPanel(); - } - - //Toggle Connection Panel - if (UI.connSettingsOpen === true) { - $D('noVNC_controls').style.display = "none"; - $D('connectButton').className = "noVNC_status_button"; - UI.connSettingsOpen = false; - } else { - $D('noVNC_controls').style.display = "block"; - $D('connectButton').className = "noVNC_status_button_selected"; - UI.connSettingsOpen = true; - $D('noVNC_host').focus(); - } -}, - -// Toggle the settings menu: -// On open, settings are refreshed from saved cookies. -// On close, settings are applied -toggleSettingsPanel: function() { - // Close the description panel - $D('noVNC_description').style.display = "none"; - if (UI.settingsOpen) { - UI.settingsApply(); - UI.closeSettingsMenu(); - } else { - UI.updateSetting('encrypt'); - UI.updateSetting('true_color'); - if (UI.rfb.get_display().get_cursor_uri()) { - UI.updateSetting('cursor'); - } else { - UI.updateSetting('cursor', false); - $D('noVNC_cursor').disabled = true; - } - UI.updateSetting('clip'); - UI.updateSetting('shared'); - UI.updateSetting('view_only'); - UI.updateSetting('connectTimeout'); - UI.updateSetting('path'); - UI.updateSetting('repeaterID'); - UI.updateSetting('stylesheet'); - UI.updateSetting('logging'); - - UI.openSettingsMenu(); - } -}, - -// Open menu -openSettingsMenu: function() { - // Close the description panel - $D('noVNC_description').style.display = "none"; - if (UI.clipboardOpen === true) { - UI.toggleClipboardPanel(); - } - //Close connection settings if open - if (UI.connSettingsOpen === true) { - UI.toggleConnectPanel(); - } - $D('noVNC_settings').style.display = "block"; - $D('settingsButton').className = "noVNC_status_button_selected"; - UI.settingsOpen = true; -}, - -// Close menu (without applying settings) -closeSettingsMenu: function() { - $D('noVNC_settings').style.display = "none"; - $D('settingsButton').className = "noVNC_status_button"; - UI.settingsOpen = false; -}, - -// Save/apply settings when 'Apply' button is pressed -settingsApply: function() { - //Util.Debug(">> settingsApply"); - UI.saveSetting('encrypt'); - UI.saveSetting('true_color'); - if (UI.rfb.get_display().get_cursor_uri()) { - UI.saveSetting('cursor'); - } - UI.saveSetting('clip'); - UI.saveSetting('shared'); - UI.saveSetting('view_only'); - UI.saveSetting('connectTimeout'); - UI.saveSetting('path'); - UI.saveSetting('repeaterID'); - UI.saveSetting('stylesheet'); - UI.saveSetting('logging'); - - // Settings with immediate (non-connected related) effect - WebUtil.selectStylesheet(UI.getSetting('stylesheet')); - WebUtil.init_logging(UI.getSetting('logging')); - UI.setViewClip(); - UI.setViewDrag(UI.rfb.get_viewportDrag()); - //Util.Debug("<< settingsApply"); -}, - - - -setPassword: function() { - UI.rfb.sendPassword($D('noVNC_password').value); - //Reset connect button. - $D('noVNC_connect_button').value = "Connect"; - $D('noVNC_connect_button').onclick = UI.Connect; - //Hide connection panel. - UI.toggleConnectPanel(); - return false; -}, - -sendCtrlAltDel: function() { - UI.rfb.sendCtrlAltDel(); -}, - -setMouseButton: function(num) { - var b, blist = [0, 1,2,4], button; - - if (typeof num === 'undefined') { - // Disable mouse buttons - num = -1; - } - if (UI.rfb) { - UI.rfb.get_mouse().set_touchButton(num); - } - - for (b = 0; b < blist.length; b++) { - button = $D('noVNC_mouse_button' + blist[b]); - if (blist[b] === num) { - button.style.display = ""; - } else { - button.style.display = "none"; - /* - button.style.backgroundColor = "black"; - button.style.color = "lightgray"; - button.style.backgroundColor = ""; - button.style.color = ""; - */ - } - } -}, - -updateState: function(rfb, state, oldstate, msg) { - var s, sb, c, d, cad, vd, klass; - UI.rfb_state = state; - s = $D('noVNC_status'); - sb = $D('noVNC_status_bar'); - switch (state) { - case 'failed': - case 'fatal': - klass = "noVNC_status_error"; - break; - case 'normal': - klass = "noVNC_status_normal"; - break; - case 'disconnected': - $D('noVNC_logo').style.display = "block"; - // Fall through - case 'loaded': - klass = "noVNC_status_normal"; - break; - case 'password': + UI.setBarPosition(); + + $D('noVNC_host').focus(); + + UI.setViewClip(); + Util.addEvent(window, 'resize', UI.setViewClip); + + Util.addEvent(window, 'beforeunload', function () { + if (UI.rfb_state === 'normal') { + return "You are currently connected."; + } + } ); + + // Show description by default when hosted at for kanaka.github.com + if (location.host === "kanaka.github.io") { + // Open the description dialog + $D('noVNC_description').style.display = "block"; + } else { + // Show the connect panel on first load unless autoconnecting + if (autoconnect === UI.connSettingsOpen) { + UI.toggleConnectPanel(); + } + } + + // Add mouse event click/focus/blur event handlers to the UI + UI.addMouseHandlers(); + + if (typeof callback === "function") { + callback(UI.rfb); + } + }, + + addMouseHandlers: function() { + // Setup interface handlers that can't be inline + $D("noVNC_view_drag_button").onclick = UI.setViewDrag; + $D("noVNC_mouse_button0").onclick = function () { UI.setMouseButton(1); }; + $D("noVNC_mouse_button1").onclick = function () { UI.setMouseButton(2); }; + $D("noVNC_mouse_button2").onclick = function () { UI.setMouseButton(4); }; + $D("noVNC_mouse_button4").onclick = function () { UI.setMouseButton(0); }; + $D("showKeyboard").onclick = UI.showKeyboard; + + $D("keyboardinput").oninput = UI.keyInput; + $D("keyboardinput").onblur = UI.keyInputBlur; + + $D("showExtraKeysButton").onclick = UI.showExtraKeys; + $D("toggleCtrlButton").onclick = UI.toggleCtrl; + $D("toggleAltButton").onclick = UI.toggleAlt; + $D("sendTabButton").onclick = UI.sendTab; + $D("sendEscButton").onclick = UI.sendEsc; + + $D("sendCtrlAltDelButton").onclick = UI.sendCtrlAltDel; + $D("xvpShutdownButton").onclick = UI.xvpShutdown; + $D("xvpRebootButton").onclick = UI.xvpReboot; + $D("xvpResetButton").onclick = UI.xvpReset; + $D("noVNC_status").onclick = UI.togglePopupStatusPanel; + $D("noVNC_popup_status_panel").onclick = UI.togglePopupStatusPanel; + $D("xvpButton").onclick = UI.toggleXvpPanel; + $D("clipboardButton").onclick = UI.toggleClipboardPanel; + $D("settingsButton").onclick = UI.toggleSettingsPanel; + $D("connectButton").onclick = UI.toggleConnectPanel; + $D("disconnectButton").onclick = UI.disconnect; + $D("descriptionButton").onclick = UI.toggleConnectPanel; + + $D("noVNC_clipboard_text").onfocus = UI.displayBlur; + $D("noVNC_clipboard_text").onblur = UI.displayFocus; + $D("noVNC_clipboard_text").onchange = UI.clipSend; + $D("noVNC_clipboard_clear_button").onclick = UI.clipClear; + + $D("noVNC_settings_menu").onmouseover = UI.displayBlur; + $D("noVNC_settings_menu").onmouseover = UI.displayFocus; + $D("noVNC_apply").onclick = UI.settingsApply; + + $D("noVNC_connect_button").onclick = UI.connect; + }, + + // Read form control compatible setting from cookie + getSetting: function(name) { + var ctrl = $D('noVNC_' + name); + var val = WebUtil.readSetting(name); + if (val !== null && ctrl.type === 'checkbox') { + if (val.toString().toLowerCase() in {'0':1, 'no':1, 'false':1}) { + val = false; + } else { + val = true; + } + } + return val; + }, + + // Update cookie and form control setting. If value is not set, then + // updates from control to current cookie setting. + updateSetting: function(name, value) { + + // Save the cookie for this session + if (typeof value !== 'undefined') { + WebUtil.writeSetting(name, value); + } + + // Update the settings control + value = UI.getSetting(name); + + var ctrl = $D('noVNC_' + name); + if (ctrl.type === 'checkbox') { + ctrl.checked = value; + + } else if (typeof ctrl.options !== 'undefined') { + for (var i = 0; i < ctrl.options.length; i += 1) { + if (ctrl.options[i].value === value) { + ctrl.selectedIndex = i; + break; + } + } + } else { + /*Weird IE9 error leads to 'null' appearring + in textboxes instead of ''.*/ + if (value === null) { + value = ""; + } + ctrl.value = value; + } + }, + + // Save control setting to cookie + saveSetting: function(name) { + var val, ctrl = $D('noVNC_' + name); + if (ctrl.type === 'checkbox') { + val = ctrl.checked; + } else if (typeof ctrl.options !== 'undefined') { + val = ctrl.options[ctrl.selectedIndex].value; + } else { + val = ctrl.value; + } + WebUtil.writeSetting(name, val); + //Util.Debug("Setting saved '" + name + "=" + val + "'"); + return val; + }, + + // Initial page load read/initialization of settings + initSetting: function(name, defVal) { + // Check Query string followed by cookie + var val = WebUtil.getQueryVar(name); + if (val === null) { + val = WebUtil.readSetting(name, defVal); + } + UI.updateSetting(name, val); + return val; + }, + + // Force a setting to be a certain value + forceSetting: function(name, val) { + UI.updateSetting(name, val); + return val; + }, + + + // Show the popup status panel + togglePopupStatusPanel: function() { + var psp = $D('noVNC_popup_status_panel'); + if (UI.popupStatusOpen === true) { + psp.style.display = "none"; + UI.popupStatusOpen = false; + } else { + psp.innerHTML = $D('noVNC_status').innerHTML; + psp.style.display = "block"; + psp.style.left = window.innerWidth/2 - + parseInt(window.getComputedStyle(psp, false).width)/2 -30 + "px"; + UI.popupStatusOpen = true; + } + }, + + // Show the XVP panel + toggleXvpPanel: function() { + // Close the description panel + $D('noVNC_description').style.display = "none"; + // Close settings if open + if (UI.settingsOpen === true) { + UI.settingsApply(); + UI.closeSettingsMenu(); + } + // Close connection settings if open + if (UI.connSettingsOpen === true) { + UI.toggleConnectPanel(); + } + // Close popup status panel if open + if (UI.popupStatusOpen === true) { + UI.togglePopupStatusPanel(); + } + // Close clipboard panel if open + if (UI.clipboardOpen === true) { + UI.toggleClipboardPanel(); + } + // Toggle XVP panel + if (UI.xvpOpen === true) { + $D('noVNC_xvp').style.display = "none"; + $D('xvpButton').className = "noVNC_status_button"; + UI.xvpOpen = false; + } else { + $D('noVNC_xvp').style.display = "block"; + $D('xvpButton').className = "noVNC_status_button_selected"; + UI.xvpOpen = true; + } + }, + + // Show the clipboard panel + toggleClipboardPanel: function() { + // Close the description panel + $D('noVNC_description').style.display = "none"; + // Close settings if open + if (UI.settingsOpen === true) { + UI.settingsApply(); + UI.closeSettingsMenu(); + } + // Close connection settings if open + if (UI.connSettingsOpen === true) { + UI.toggleConnectPanel(); + } + // Close popup status panel if open + if (UI.popupStatusOpen === true) { + UI.togglePopupStatusPanel(); + } + // Close XVP panel if open + if (UI.xvpOpen === true) { + UI.toggleXvpPanel(); + } + // Toggle Clipboard Panel + if (UI.clipboardOpen === true) { + $D('noVNC_clipboard').style.display = "none"; + $D('clipboardButton').className = "noVNC_status_button"; + UI.clipboardOpen = false; + } else { + $D('noVNC_clipboard').style.display = "block"; + $D('clipboardButton').className = "noVNC_status_button_selected"; + UI.clipboardOpen = true; + } + }, + + // Show the connection settings panel/menu + toggleConnectPanel: function() { + // Close the description panel + $D('noVNC_description').style.display = "none"; + // Close connection settings if open + if (UI.settingsOpen === true) { + UI.settingsApply(); + UI.closeSettingsMenu(); + $D('connectButton').className = "noVNC_status_button"; + } + // Close clipboard panel if open + if (UI.clipboardOpen === true) { + UI.toggleClipboardPanel(); + } + // Close popup status panel if open + if (UI.popupStatusOpen === true) { + UI.togglePopupStatusPanel(); + } + // Close XVP panel if open + if (UI.xvpOpen === true) { + UI.toggleXvpPanel(); + } + + // Toggle Connection Panel + if (UI.connSettingsOpen === true) { + $D('noVNC_controls').style.display = "none"; + $D('connectButton').className = "noVNC_status_button"; + UI.connSettingsOpen = false; + UI.saveSetting('host'); + UI.saveSetting('port'); + //UI.saveSetting('password'); + } else { + $D('noVNC_controls').style.display = "block"; + $D('connectButton').className = "noVNC_status_button_selected"; + UI.connSettingsOpen = true; + $D('noVNC_host').focus(); + } + }, + + // Toggle the settings menu: + // On open, settings are refreshed from saved cookies. + // On close, settings are applied + toggleSettingsPanel: function() { + // Close the description panel + $D('noVNC_description').style.display = "none"; + if (UI.settingsOpen) { + UI.settingsApply(); + UI.closeSettingsMenu(); + } else { + UI.updateSetting('encrypt'); + UI.updateSetting('true_color'); + if (UI.rfb.get_display().get_cursor_uri()) { + UI.updateSetting('cursor'); + } else { + UI.updateSetting('cursor', !UI.isTouchDevice); + $D('noVNC_cursor').disabled = true; + } + UI.updateSetting('clip'); + UI.updateSetting('shared'); + UI.updateSetting('view_only'); + UI.updateSetting('path'); + UI.updateSetting('repeaterID'); + UI.updateSetting('stylesheet'); + UI.updateSetting('logging'); + + UI.openSettingsMenu(); + } + }, + + // Open menu + openSettingsMenu: function() { + // Close the description panel + $D('noVNC_description').style.display = "none"; + // Close clipboard panel if open + if (UI.clipboardOpen === true) { + UI.toggleClipboardPanel(); + } + // Close connection settings if open + if (UI.connSettingsOpen === true) { + UI.toggleConnectPanel(); + } + // Close popup status panel if open + if (UI.popupStatusOpen === true) { + UI.togglePopupStatusPanel(); + } + // Close XVP panel if open + if (UI.xvpOpen === true) { + UI.toggleXvpPanel(); + } + $D('noVNC_settings').style.display = "block"; + $D('settingsButton').className = "noVNC_status_button_selected"; + UI.settingsOpen = true; + }, + + // Close menu (without applying settings) + closeSettingsMenu: function() { + $D('noVNC_settings').style.display = "none"; + $D('settingsButton').className = "noVNC_status_button"; + UI.settingsOpen = false; + }, + + // Save/apply settings when 'Apply' button is pressed + settingsApply: function() { + //Util.Debug(">> settingsApply"); + UI.saveSetting('encrypt'); + UI.saveSetting('true_color'); + if (UI.rfb.get_display().get_cursor_uri()) { + UI.saveSetting('cursor'); + } + UI.saveSetting('clip'); + UI.saveSetting('shared'); + UI.saveSetting('view_only'); + UI.saveSetting('path'); + UI.saveSetting('repeaterID'); + UI.saveSetting('stylesheet'); + UI.saveSetting('logging'); + + // Settings with immediate (non-connected related) effect + WebUtil.selectStylesheet(UI.getSetting('stylesheet')); + WebUtil.init_logging(UI.getSetting('logging')); + UI.setViewClip(); + UI.setViewDrag(UI.rfb.get_viewportDrag()); + //Util.Debug("<< settingsApply"); + }, + + + + setPassword: function() { + UI.rfb.sendPassword($D('noVNC_password').value); + //Reset connect button. + $D('noVNC_connect_button').value = "Connect"; + $D('noVNC_connect_button').onclick = UI.Connect; + //Hide connection panel. UI.toggleConnectPanel(); + return false; + }, - $D('noVNC_connect_button').value = "Send Password"; - $D('noVNC_connect_button').onclick = UI.setPassword; - $D('noVNC_password').focus(); - - klass = "noVNC_status_warn"; - break; - default: - klass = "noVNC_status_warn"; - break; - } - - if (typeof(msg) !== 'undefined') { - s.setAttribute("class", klass); - sb.setAttribute("class", klass); - s.innerHTML = msg; - } - - UI.updateVisualState(); -}, - -// Disable/enable controls depending on connection state -updateVisualState: function() { - var connected = UI.rfb_state === 'normal' ? true : false; - - //Util.Debug(">> updateVisualState"); - $D('noVNC_encrypt').disabled = connected; - $D('noVNC_true_color').disabled = connected; - if (UI.rfb && UI.rfb.get_display() && - UI.rfb.get_display().get_cursor_uri()) { - $D('noVNC_cursor').disabled = connected; - } else { - UI.updateSetting('cursor', false); - $D('noVNC_cursor').disabled = true; - } - $D('noVNC_shared').disabled = connected; - $D('noVNC_view_only').disabled = connected; - $D('noVNC_connectTimeout').disabled = connected; - $D('noVNC_path').disabled = connected; - $D('noVNC_repeaterID').disabled = connected; - - if (connected) { - UI.setViewClip(); - UI.setMouseButton(1); - $D('clipboardButton').style.display = "inline"; - $D('showKeyboard').style.display = "inline"; - $D('sendCtrlAltDelButton').style.display = "inline"; - } else { - UI.setMouseButton(); - $D('clipboardButton').style.display = "none"; - $D('showKeyboard').style.display = "none"; - $D('sendCtrlAltDelButton').style.display = "none"; - } - // State change disables viewport dragging. - // It is enabled (toggled) by direct click on the button - UI.setViewDrag(false); - - switch (UI.rfb_state) { - case 'fatal': - case 'failed': - case 'loaded': - case 'disconnected': - $D('connectButton').style.display = ""; - $D('disconnectButton').style.display = "none"; - break; - default: - $D('connectButton').style.display = "none"; - $D('disconnectButton').style.display = ""; - break; - } - - //Util.Debug("<< updateVisualState"); -}, - - -clipReceive: function(rfb, text) { - Util.Debug(">> UI.clipReceive: " + text.substr(0,40) + "..."); - $D('noVNC_clipboard_text').value = text; - Util.Debug("<< UI.clipReceive"); -}, - - -connect: function() { - var host, port, password, path; - - UI.closeSettingsMenu(); - UI.toggleConnectPanel(); - - host = $D('noVNC_host').value; - port = $D('noVNC_port').value; - password = $D('noVNC_password').value; - path = $D('noVNC_path').value; - if ((!host) || (!port)) { - throw("Must set host and port"); - } - - UI.rfb.set_encrypt(UI.getSetting('encrypt')); - UI.rfb.set_true_color(UI.getSetting('true_color')); - UI.rfb.set_local_cursor(UI.getSetting('cursor')); - UI.rfb.set_shared(UI.getSetting('shared')); - UI.rfb.set_view_only(UI.getSetting('view_only')); - UI.rfb.set_connectTimeout(UI.getSetting('connectTimeout')); - UI.rfb.set_repeaterID(UI.getSetting('repeaterID')); - - UI.rfb.connect(host, port, password, path); - - //Close dialog. - setTimeout(UI.setBarPosition, 100); - $D('noVNC_logo').style.display = "none"; -}, - -disconnect: function() { - UI.closeSettingsMenu(); - UI.rfb.disconnect(); - - $D('noVNC_logo').style.display = "block"; - UI.connSettingsOpen = false; - UI.toggleConnectPanel(); -}, - -displayBlur: function() { - UI.rfb.get_keyboard().set_focused(false); - UI.rfb.get_mouse().set_focused(false); -}, - -displayFocus: function() { - UI.rfb.get_keyboard().set_focused(true); - UI.rfb.get_mouse().set_focused(true); -}, - -clipClear: function() { - $D('noVNC_clipboard_text').value = ""; - UI.rfb.clipboardPasteFrom(""); -}, - -clipSend: function() { - var text = $D('noVNC_clipboard_text').value; - Util.Debug(">> UI.clipSend: " + text.substr(0,40) + "..."); - UI.rfb.clipboardPasteFrom(text); - Util.Debug("<< UI.clipSend"); -}, - - -// Enable/disable and configure viewport clipping -setViewClip: function(clip) { - var display, cur_clip, pos, new_w, new_h; - - if (UI.rfb) { - display = UI.rfb.get_display(); - } else { - return; - } - - cur_clip = display.get_viewport(); - - if (typeof(clip) !== 'boolean') { - // Use current setting - clip = UI.getSetting('clip'); - } - - if (clip && !cur_clip) { - // Turn clipping on - UI.updateSetting('clip', true); - } else if (!clip && cur_clip) { - // Turn clipping off - UI.updateSetting('clip', false); - display.set_viewport(false); - $D('noVNC_canvas').style.position = 'static'; - display.viewportChange(); - } - if (UI.getSetting('clip')) { - // If clipping, update clipping settings - $D('noVNC_canvas').style.position = 'absolute'; - pos = Util.getPosition($D('noVNC_canvas')); - new_w = window.innerWidth - pos.x; - new_h = window.innerHeight - pos.y; - display.set_viewport(true); - display.viewportChange(0, 0, new_w, new_h); - } -}, - -// Toggle/set/unset the viewport drag/move button -setViewDrag: function(drag) { - var vmb = $D('noVNC_view_drag_button'); - if (!UI.rfb) { return; } - - if (UI.rfb_state === 'normal' && - UI.rfb.get_display().get_viewport()) { - vmb.style.display = "inline"; - } else { - vmb.style.display = "none"; - } - - if (typeof(drag) === "undefined") { - // If not specified, then toggle - drag = !UI.rfb.get_viewportDrag(); - } - if (drag) { - vmb.className = "noVNC_status_button_selected"; - UI.rfb.set_viewportDrag(true); - } else { - vmb.className = "noVNC_status_button"; - UI.rfb.set_viewportDrag(false); - } -}, - -// On touch devices, show the OS keyboard -showKeyboard: function() { - if(UI.keyboardVisible === false) { - $D('keyboardinput').focus(); - UI.keyboardVisible = true; - $D('showKeyboard').className = "noVNC_status_button_selected"; - } else if(UI.keyboardVisible === true) { - $D('keyboardinput').blur(); - $D('showKeyboard').className = "noVNC_status_button"; - UI.keyboardVisible = false; - } -}, - -keyInputBlur: function() { - $D('showKeyboard').className = "noVNC_status_button"; - //Weird bug in iOS if you change keyboardVisible - //here it does not actually occur so next time - //you click keyboard icon it doesnt work. - setTimeout(function() { UI.setKeyboard(); },100); -}, - -setKeyboard: function() { - UI.keyboardVisible = false; -}, - -// iOS < Version 5 does not support position fixed. Javascript workaround: -setOnscroll: function() { - window.onscroll = function() { - UI.setBarPosition(); - }; -}, + sendCtrlAltDel: function() { + UI.rfb.sendCtrlAltDel(); + }, -setResize: function () { - window.onResize = function() { - UI.setBarPosition(); - }; -}, + xvpShutdown: function() { + UI.rfb.xvpShutdown(); + }, -//Helper to add options to dropdown. -addOption: function(selectbox,text,value ) -{ - var optn = document.createElement("OPTION"); - optn.text = text; - optn.value = value; - selectbox.options.add(optn); -}, + xvpReboot: function() { + UI.rfb.xvpReboot(); + }, -setBarPosition: function() { - $D('noVNC-control-bar').style.top = (window.pageYOffset) + 'px'; - $D('noVNC_mobile_buttons').style.left = (window.pageXOffset) + 'px'; + xvpReset: function() { + UI.rfb.xvpReset(); + }, - var vncwidth = $D('noVNC_screen').style.offsetWidth; - $D('noVNC-control-bar').style.width = vncwidth + 'px'; -} + setMouseButton: function(num) { + if (typeof num === 'undefined') { + // Disable mouse buttons + num = -1; + } + if (UI.rfb) { + UI.rfb.get_mouse().set_touchButton(num); + } -}; + var blist = [0, 1,2,4]; + for (var b = 0; b < blist.length; b++) { + var button = $D('noVNC_mouse_button' + blist[b]); + if (blist[b] === num) { + button.style.display = ""; + } else { + button.style.display = "none"; + } + } + }, + + updateState: function(rfb, state, oldstate, msg) { + UI.rfb_state = state; + var klass; + switch (state) { + case 'failed': + case 'fatal': + klass = "noVNC_status_error"; + break; + case 'normal': + klass = "noVNC_status_normal"; + break; + case 'disconnected': + $D('noVNC_logo').style.display = "block"; + /* falls through */ + case 'loaded': + klass = "noVNC_status_normal"; + break; + case 'password': + UI.toggleConnectPanel(); + + $D('noVNC_connect_button').value = "Send Password"; + $D('noVNC_connect_button').onclick = UI.setPassword; + $D('noVNC_password').focus(); + + klass = "noVNC_status_warn"; + break; + default: + klass = "noVNC_status_warn"; + break; + } + if (typeof(msg) !== 'undefined') { + $D('noVNC-control-bar').setAttribute("class", klass); + $D('noVNC_status').innerHTML = msg; + } + UI.updateVisualState(); + }, + + // Disable/enable controls depending on connection state + updateVisualState: function() { + var connected = UI.rfb_state === 'normal' ? true : false; + + //Util.Debug(">> updateVisualState"); + $D('noVNC_encrypt').disabled = connected; + $D('noVNC_true_color').disabled = connected; + if (UI.rfb && UI.rfb.get_display() && + UI.rfb.get_display().get_cursor_uri()) { + $D('noVNC_cursor').disabled = connected; + } else { + UI.updateSetting('cursor', !UI.isTouchDevice); + $D('noVNC_cursor').disabled = true; + } + $D('noVNC_shared').disabled = connected; + $D('noVNC_view_only').disabled = connected; + $D('noVNC_path').disabled = connected; + $D('noVNC_repeaterID').disabled = connected; + + if (connected) { + UI.setViewClip(); + UI.setMouseButton(1); + $D('clipboardButton').style.display = "inline"; + $D('showKeyboard').style.display = "inline"; + $D('noVNC_extra_keys').style.display = ""; + $D('sendCtrlAltDelButton').style.display = "inline"; + } else { + UI.setMouseButton(); + $D('clipboardButton').style.display = "none"; + $D('showKeyboard').style.display = "none"; + $D('noVNC_extra_keys').style.display = "none"; + $D('sendCtrlAltDelButton').style.display = "none"; + UI.updateXvpVisualState(0); + } + // State change disables viewport dragging. + // It is enabled (toggled) by direct click on the button + UI.setViewDrag(false); + + switch (UI.rfb_state) { + case 'fatal': + case 'failed': + case 'loaded': + case 'disconnected': + $D('connectButton').style.display = ""; + $D('disconnectButton').style.display = "none"; + break; + default: + $D('connectButton').style.display = "none"; + $D('disconnectButton').style.display = ""; + break; + } + + //Util.Debug("<< updateVisualState"); + }, + + // Disable/enable XVP button + updateXvpVisualState: function(ver) { + if (ver >= 1) { + $D('xvpButton').style.display = 'inline'; + } else { + $D('xvpButton').style.display = 'none'; + // Close XVP panel if open + if (UI.xvpOpen === true) { + UI.toggleXvpPanel(); + } + } + }, + + // Display the desktop name in the document title + updateDocumentTitle: function(rfb, name) { + document.title = name + " - noVNC"; + }, + + clipReceive: function(rfb, text) { + Util.Debug(">> UI.clipReceive: " + text.substr(0,40) + "..."); + $D('noVNC_clipboard_text').value = text; + Util.Debug("<< UI.clipReceive"); + }, + + connect: function() { + UI.closeSettingsMenu(); + UI.toggleConnectPanel(); + var host = $D('noVNC_host').value; + var port = $D('noVNC_port').value; + var password = $D('noVNC_password').value; + var path = $D('noVNC_path').value; + if ((!host) || (!port)) { + throw new Error("Must set host and port"); + } + + UI.rfb.set_encrypt(UI.getSetting('encrypt')); + UI.rfb.set_true_color(UI.getSetting('true_color')); + UI.rfb.set_local_cursor(UI.getSetting('cursor')); + UI.rfb.set_shared(UI.getSetting('shared')); + UI.rfb.set_view_only(UI.getSetting('view_only')); + UI.rfb.set_repeaterID(UI.getSetting('repeaterID')); + + UI.rfb.connect(host, port, password, path); + + //Close dialog. + setTimeout(UI.setBarPosition, 100); + $D('noVNC_logo').style.display = "none"; + }, + + disconnect: function() { + UI.closeSettingsMenu(); + UI.rfb.disconnect(); + + $D('noVNC_logo').style.display = "block"; + UI.connSettingsOpen = false; + UI.toggleConnectPanel(); + }, + + displayBlur: function() { + UI.rfb.get_keyboard().set_focused(false); + UI.rfb.get_mouse().set_focused(false); + }, + + displayFocus: function() { + UI.rfb.get_keyboard().set_focused(true); + UI.rfb.get_mouse().set_focused(true); + }, + + clipClear: function() { + $D('noVNC_clipboard_text').value = ""; + UI.rfb.clipboardPasteFrom(""); + }, + + clipSend: function() { + var text = $D('noVNC_clipboard_text').value; + Util.Debug(">> UI.clipSend: " + text.substr(0,40) + "..."); + UI.rfb.clipboardPasteFrom(text); + Util.Debug("<< UI.clipSend"); + }, + + // Enable/disable and configure viewport clipping + setViewClip: function(clip) { + var display; + if (UI.rfb) { + display = UI.rfb.get_display(); + } else { + return; + } + + var cur_clip = display.get_viewport(); + + if (typeof(clip) !== 'boolean') { + // Use current setting + clip = UI.getSetting('clip'); + } + + if (clip && !cur_clip) { + // Turn clipping on + UI.updateSetting('clip', true); + } else if (!clip && cur_clip) { + // Turn clipping off + UI.updateSetting('clip', false); + display.set_viewport(false); + $D('noVNC_canvas').style.position = 'static'; + display.viewportChange(); + } + if (UI.getSetting('clip')) { + // If clipping, update clipping settings + $D('noVNC_canvas').style.position = 'absolute'; + var pos = Util.getPosition($D('noVNC_canvas')); + var new_w = window.innerWidth - pos.x; + var new_h = window.innerHeight - pos.y; + display.set_viewport(true); + display.viewportChange(0, 0, new_w, new_h); + } + }, + + // Toggle/set/unset the viewport drag/move button + setViewDrag: function(drag) { + var vmb = $D('noVNC_view_drag_button'); + if (!UI.rfb) { return; } + + if (UI.rfb_state === 'normal' && + UI.rfb.get_display().get_viewport()) { + vmb.style.display = "inline"; + } else { + vmb.style.display = "none"; + } + + if (typeof(drag) === "undefined" || + typeof(drag) === "object") { + // If not specified, then toggle + drag = !UI.rfb.get_viewportDrag(); + } + if (drag) { + vmb.className = "noVNC_status_button_selected"; + UI.rfb.set_viewportDrag(true); + } else { + vmb.className = "noVNC_status_button"; + UI.rfb.set_viewportDrag(false); + } + }, + + // On touch devices, show the OS keyboard + showKeyboard: function() { + var kbi = $D('keyboardinput'); + var skb = $D('showKeyboard'); + var l = kbi.value.length; + if(UI.keyboardVisible === false) { + kbi.focus(); + try { kbi.setSelectionRange(l, l); } // Move the caret to the end + catch (err) {} // setSelectionRange is undefined in Google Chrome + UI.keyboardVisible = true; + skb.className = "noVNC_status_button_selected"; + } else if(UI.keyboardVisible === true) { + kbi.blur(); + skb.className = "noVNC_status_button"; + UI.keyboardVisible = false; + } + }, + + keepKeyboard: function() { + clearTimeout(UI.hideKeyboardTimeout); + if(UI.keyboardVisible === true) { + $D('keyboardinput').focus(); + $D('showKeyboard').className = "noVNC_status_button_selected"; + } else if(UI.keyboardVisible === false) { + $D('keyboardinput').blur(); + $D('showKeyboard').className = "noVNC_status_button"; + } + }, + + keyboardinputReset: function() { + var kbi = $D('keyboardinput'); + kbi.value = new Array(UI.defaultKeyboardinputLen).join("_"); + UI.lastKeyboardinput = kbi.value; + }, + + // When normal keyboard events are left uncought, use the input events from + // the keyboardinput element instead and generate the corresponding key events. + // This code is required since some browsers on Android are inconsistent in + // sending keyCodes in the normal keyboard events when using on screen keyboards. + keyInput: function(event) { + var newValue = event.target.value; + var oldValue = UI.lastKeyboardinput; + + var newLen; + try { + // Try to check caret position since whitespace at the end + // will not be considered by value.length in some browsers + newLen = Math.max(event.target.selectionStart, newValue.length); + } catch (err) { + // selectionStart is undefined in Google Chrome + newLen = newValue.length; + } + var oldLen = oldValue.length; + + var backspaces; + var inputs = newLen - oldLen; + if (inputs < 0) { + backspaces = -inputs; + } else { + backspaces = 0; + } + + // Compare the old string with the new to account for + // text-corrections or other input that modify existing text + var i; + for (i = 0; i < Math.min(oldLen, newLen); i++) { + if (newValue.charAt(i) != oldValue.charAt(i)) { + inputs = newLen - i; + backspaces = oldLen - i; + break; + } + } + + // Send the key events + for (i = 0; i < backspaces; i++) { + UI.rfb.sendKey(XK_BackSpace); + } + for (i = newLen - inputs; i < newLen; i++) { + UI.rfb.sendKey(newValue.charCodeAt(i)); + } + + // Control the text content length in the keyboardinput element + if (newLen > 2 * UI.defaultKeyboardinputLen) { + UI.keyboardinputReset(); + } else if (newLen < 1) { + // There always have to be some text in the keyboardinput + // element with which backspace can interact. + UI.keyboardinputReset(); + // This sometimes causes the keyboard to disappear for a second + // but it is required for the android keyboard to recognize that + // text has been added to the field + event.target.blur(); + // This has to be ran outside of the input handler in order to work + setTimeout(function() { UI.keepKeyboard(); }, 0); + } else { + UI.lastKeyboardinput = newValue; + } + }, + + keyInputBlur: function() { + $D('showKeyboard').className = "noVNC_status_button"; + //Weird bug in iOS if you change keyboardVisible + //here it does not actually occur so next time + //you click keyboard icon it doesnt work. + UI.hideKeyboardTimeout = setTimeout(function() { UI.setKeyboard(); },100); + }, + + showExtraKeys: function() { + UI.keepKeyboard(); + if(UI.extraKeysVisible === false) { + $D('toggleCtrlButton').style.display = "inline"; + $D('toggleAltButton').style.display = "inline"; + $D('sendTabButton').style.display = "inline"; + $D('sendEscButton').style.display = "inline"; + $D('showExtraKeysButton').className = "noVNC_status_button_selected"; + UI.extraKeysVisible = true; + } else if(UI.extraKeysVisible === true) { + $D('toggleCtrlButton').style.display = ""; + $D('toggleAltButton').style.display = ""; + $D('sendTabButton').style.display = ""; + $D('sendEscButton').style.display = ""; + $D('showExtraKeysButton').className = "noVNC_status_button"; + UI.extraKeysVisible = false; + } + }, + + toggleCtrl: function() { + UI.keepKeyboard(); + if(UI.ctrlOn === false) { + UI.rfb.sendKey(XK_Control_L, true); + $D('toggleCtrlButton').className = "noVNC_status_button_selected"; + UI.ctrlOn = true; + } else if(UI.ctrlOn === true) { + UI.rfb.sendKey(XK_Control_L, false); + $D('toggleCtrlButton').className = "noVNC_status_button"; + UI.ctrlOn = false; + } + }, + + toggleAlt: function() { + UI.keepKeyboard(); + if(UI.altOn === false) { + UI.rfb.sendKey(XK_Alt_L, true); + $D('toggleAltButton').className = "noVNC_status_button_selected"; + UI.altOn = true; + } else if(UI.altOn === true) { + UI.rfb.sendKey(XK_Alt_L, false); + $D('toggleAltButton').className = "noVNC_status_button"; + UI.altOn = false; + } + }, + + sendTab: function() { + UI.keepKeyboard(); + UI.rfb.sendKey(XK_Tab); + }, + + sendEsc: function() { + UI.keepKeyboard(); + UI.rfb.sendKey(XK_Escape); + }, + + setKeyboard: function() { + UI.keyboardVisible = false; + }, + + // iOS < Version 5 does not support position fixed. Javascript workaround: + setOnscroll: function() { + window.onscroll = function() { + UI.setBarPosition(); + }; + }, + + setResize: function () { + window.onResize = function() { + UI.setBarPosition(); + }; + }, + + //Helper to add options to dropdown. + addOption: function(selectbox, text, value) { + var optn = document.createElement("OPTION"); + optn.text = text; + optn.value = value; + selectbox.options.add(optn); + }, + + setBarPosition: function() { + $D('noVNC-control-bar').style.top = (window.pageYOffset) + 'px'; + $D('noVNC_mobile_buttons').style.left = (window.pageXOffset) + 'px'; + + var vncwidth = $D('noVNC_screen').style.offsetWidth; + $D('noVNC-control-bar').style.width = vncwidth + 'px'; + } + + }; +})(); diff --git a/webclients/novnc/include/util.js b/webclients/novnc/include/util.js index 57ccb54..909d04b 100644 --- a/webclients/novnc/include/util.js +++ b/webclients/novnc/include/util.js @@ -1,14 +1,13 @@ /* * noVNC: HTML5 VNC client * Copyright (C) 2012 Joel Martin - * Licensed under LGPL-3 (see LICENSE.txt) + * Licensed under MPL 2.0 (see LICENSE.txt) * * See README.md for usage and integration instructions. */ -"use strict"; -/*jslint bitwise: false, white: false */ -/*global window, console, document, navigator, ActiveXObject */ +/* jshint white: false, nonstandard: true */ +/*global window, console, document, navigator, ActiveXObject, INCLUDE_URI */ // Globals defined here var Util = {}; @@ -18,61 +17,163 @@ var Util = {}; * Make arrays quack */ -Array.prototype.push8 = function (num) { - this.push(num & 0xFF); +var addFunc = function (cl, name, func) { + if (!cl.prototype[name]) { + Object.defineProperty(cl.prototype, name, { enumerable: false, value: func }); + } }; -Array.prototype.push16 = function (num) { +addFunc(Array, 'push8', function (num) { + "use strict"; + this.push(num & 0xFF); +}); + +addFunc(Array, 'push16', function (num) { + "use strict"; this.push((num >> 8) & 0xFF, - (num ) & 0xFF ); -}; -Array.prototype.push32 = function (num) { + num & 0xFF); +}); + +addFunc(Array, 'push32', function (num) { + "use strict"; this.push((num >> 24) & 0xFF, (num >> 16) & 0xFF, (num >> 8) & 0xFF, - (num ) & 0xFF ); -}; + num & 0xFF); +}); // IE does not support map (even in IE9) //This prototype is provided by the Mozilla foundation and //is distributed under the MIT license. //http://www.ibiblio.org/pub/Linux/LICENSES/mit.license -if (!Array.prototype.map) -{ - Array.prototype.map = function(fun /*, thisp*/) - { +addFunc(Array, 'map', function (fun /*, thisp*/) { + "use strict"; var len = this.length; - if (typeof fun != "function") - throw new TypeError(); + if (typeof fun != "function") { + throw new TypeError(); + } var res = new Array(len); var thisp = arguments[1]; - for (var i = 0; i < len; i++) - { - if (i in this) - res[i] = fun.call(thisp, this[i], i, this); + for (var i = 0; i < len; i++) { + if (i in this) { + res[i] = fun.call(thisp, this[i], i, this); + } } return res; - }; +}); + +// IE <9 does not support indexOf +//This prototype is provided by the Mozilla foundation and +//is distributed under the MIT license. +//http://www.ibiblio.org/pub/Linux/LICENSES/mit.license +addFunc(Array, 'indexOf', function (elt /*, from*/) { + "use strict"; + var len = this.length >>> 0; + + var from = Number(arguments[1]) || 0; + from = (from < 0) ? Math.ceil(from) : Math.floor(from); + if (from < 0) { + from += len; + } + + for (; from < len; from++) { + if (from in this && + this[from] === elt) { + return from; + } + } + return -1; +}); + +// From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys +if (!Object.keys) { + Object.keys = (function () { + 'use strict'; + var hasOwnProperty = Object.prototype.hasOwnProperty, + hasDontEnumBug = !({toString: null}).propertyIsEnumerable('toString'), + dontEnums = [ + 'toString', + 'toLocaleString', + 'valueOf', + 'hasOwnProperty', + 'isPrototypeOf', + 'propertyIsEnumerable', + 'constructor' + ], + dontEnumsLength = dontEnums.length; + + return function (obj) { + if (typeof obj !== 'object' && (typeof obj !== 'function' || obj === null)) { + throw new TypeError('Object.keys called on non-object'); + } + + var result = [], prop, i; + + for (prop in obj) { + if (hasOwnProperty.call(obj, prop)) { + result.push(prop); + } + } + + if (hasDontEnumBug) { + for (i = 0; i < dontEnumsLength; i++) { + if (hasOwnProperty.call(obj, dontEnums[i])) { + result.push(dontEnums[i]); + } + } + } + return result; + }; + })(); } -// +// PhantomJS 1.x doesn't support bind, +// so leave this in until PhantomJS 2.0 is released +//This prototype is provided by the Mozilla foundation and +//is distributed under the MIT license. +//http://www.ibiblio.org/pub/Linux/LICENSES/mit.license +addFunc(Function, 'bind', function (oThis) { + if (typeof this !== "function") { + // closest thing possible to the ECMAScript 5 + // internal IsCallable function + throw new TypeError("Function.prototype.bind - " + + "what is trying to be bound is not callable"); + } + + var aArgs = Array.prototype.slice.call(arguments, 1), + fToBind = this, + fNOP = function () {}, + fBound = function () { + return fToBind.apply(this instanceof fNOP && oThis ? this + : oThis, + aArgs.concat(Array.prototype.slice.call(arguments))); + }; + + fNOP.prototype = this.prototype; + fBound.prototype = new fNOP(); + + return fBound; +}); + +// // requestAnimationFrame shim with setTimeout fallback // -window.requestAnimFrame = (function(){ - return window.requestAnimationFrame || - window.webkitRequestAnimationFrame || - window.mozRequestAnimationFrame || - window.oRequestAnimationFrame || - window.msRequestAnimationFrame || - function(callback){ +window.requestAnimFrame = (function () { + "use strict"; + return window.requestAnimationFrame || + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + window.oRequestAnimationFrame || + window.msRequestAnimationFrame || + function (callback) { window.setTimeout(callback, 1000 / 60); }; })(); -/* +/* * ------------------------------------------------------ * Namespaced in Util * ------------------------------------------------------ @@ -84,6 +185,7 @@ window.requestAnimFrame = (function(){ Util._log_level = 'warn'; Util.init_logging = function (level) { + "use strict"; if (typeof level === 'undefined') { level = Util._log_level; } else { @@ -94,26 +196,34 @@ Util.init_logging = function (level) { window.console = { 'log' : window.opera.postError, 'warn' : window.opera.postError, - 'error': window.opera.postError }; + 'error': window.opera.postError + }; } else { window.console = { - 'log' : function(m) {}, - 'warn' : function(m) {}, - 'error': function(m) {}}; + 'log' : function (m) {}, + 'warn' : function (m) {}, + 'error': function (m) {} + }; } } Util.Debug = Util.Info = Util.Warn = Util.Error = function (msg) {}; + /* jshint -W086 */ switch (level) { - case 'debug': Util.Debug = function (msg) { console.log(msg); }; - case 'info': Util.Info = function (msg) { console.log(msg); }; - case 'warn': Util.Warn = function (msg) { console.warn(msg); }; - case 'error': Util.Error = function (msg) { console.error(msg); }; + case 'debug': + Util.Debug = function (msg) { console.log(msg); }; + case 'info': + Util.Info = function (msg) { console.log(msg); }; + case 'warn': + Util.Warn = function (msg) { console.warn(msg); }; + case 'error': + Util.Error = function (msg) { console.error(msg); }; case 'none': break; default: - throw("invalid logging type '" + level + "'"); + throw new Error("invalid logging type '" + level + "'"); } + /* jshint +W086 */ }; Util.get_logging = function () { return Util._log_level; @@ -121,107 +231,279 @@ Util.get_logging = function () { // Initialize logging level Util.init_logging(); +Util.make_property = function (proto, name, mode, type) { + "use strict"; -// Set configuration default for Crockford style function namespaces -Util.conf_default = function(cfg, api, defaults, v, mode, type, defval, desc) { - var getter, setter; + var getter; + if (type === 'arr') { + getter = function (idx) { + if (typeof idx !== 'undefined') { + return this['_' + name][idx]; + } else { + return this['_' + name]; + } + }; + } else { + getter = function () { + return this['_' + name]; + }; + } - // Default getter function - getter = function (idx) { - if ((type in {'arr':1, 'array':1}) && - (typeof idx !== 'undefined')) { - return cfg[v][idx]; + var make_setter = function (process_val) { + if (process_val) { + return function (val, idx) { + if (typeof idx !== 'undefined') { + this['_' + name][idx] = process_val(val); + } else { + this['_' + name] = process_val(val); + } + }; } else { - return cfg[v]; + return function (val, idx) { + if (typeof idx !== 'undefined') { + this['_' + name][idx] = val; + } else { + this['_' + name] = val; + } + }; } }; - // Default setter function - setter = function (val, idx) { - if (type in {'boolean':1, 'bool':1}) { - if ((!val) || (val in {'0':1, 'no':1, 'false':1})) { - val = false; + var setter; + if (type === 'bool') { + setter = make_setter(function (val) { + if (!val || (val in {'0': 1, 'no': 1, 'false': 1})) { + return false; } else { - val = true; + return true; } - } else if (type in {'integer':1, 'int':1}) { - val = parseInt(val, 10); - } else if (type === 'str') { - val = String(val); - } else if (type === 'func') { + }); + } else if (type === 'int') { + setter = make_setter(function (val) { return parseInt(val, 10); }); + } else if (type === 'float') { + setter = make_setter(parseFloat); + } else if (type === 'str') { + setter = make_setter(String); + } else if (type === 'func') { + setter = make_setter(function (val) { if (!val) { - val = function () {}; + return function () {}; + } else { + return val; } - } - if (typeof idx !== 'undefined') { - cfg[v][idx] = val; - } else { - cfg[v] = val; - } - }; - - // Set the description - api[v + '_description'] = desc; + }); + } else if (type === 'arr' || type === 'dom' || type == 'raw') { + setter = make_setter(); + } else { + throw new Error('Unknown property type ' + type); // some sanity checking + } - // Set the getter function - if (typeof api['get_' + v] === 'undefined') { - api['get_' + v] = getter; + // set the getter + if (typeof proto['get_' + name] === 'undefined') { + proto['get_' + name] = getter; } - // Set the setter function with extra sanity checks - if (typeof api['set_' + v] === 'undefined') { - api['set_' + v] = function (val, idx) { - if (mode in {'RO':1, 'ro':1}) { - throw(v + " is read-only"); - } else if ((mode in {'WO':1, 'wo':1}) && - (typeof cfg[v] !== 'undefined')) { - throw(v + " can only be set once"); - } - setter(val, idx); - }; + // set the setter if needed + if (typeof proto['set_' + name] === 'undefined') { + if (mode === 'rw') { + proto['set_' + name] = setter; + } else if (mode === 'wo') { + proto['set_' + name] = function (val, idx) { + if (typeof this['_' + name] !== 'undefined') { + throw new Error(name + " can only be set once"); + } + setter.call(this, val, idx); + }; + } } - // Set the default value - if (typeof defaults[v] !== 'undefined') { - defval = defaults[v]; - } else if ((type in {'arr':1, 'array':1}) && - (! (defval instanceof Array))) { - defval = []; + // make a special setter that we can use in set defaults + proto['_raw_set_' + name] = function (val, idx) { + setter.call(this, val, idx); + //delete this['_init_set_' + name]; // remove it after use + }; +}; + +Util.make_properties = function (constructor, arr) { + "use strict"; + for (var i = 0; i < arr.length; i++) { + Util.make_property(constructor.prototype, arr[i][0], arr[i][1], arr[i][2]); } - // Coerce existing setting to the right type - //Util.Debug("v: " + v + ", defval: " + defval + ", defaults[v]: " + defaults[v]); - setter(defval); }; -// Set group of configuration defaults -Util.conf_defaults = function(cfg, api, defaults, arr) { +Util.set_defaults = function (obj, conf, defaults) { + var defaults_keys = Object.keys(defaults); + var conf_keys = Object.keys(conf); + var keys_obj = {}; var i; - for (i = 0; i < arr.length; i++) { - Util.conf_default(cfg, api, defaults, arr[i][0], arr[i][1], - arr[i][2], arr[i][3], arr[i][4]); + for (i = 0; i < defaults_keys.length; i++) { keys_obj[defaults_keys[i]] = 1; } + for (i = 0; i < conf_keys.length; i++) { keys_obj[conf_keys[i]] = 1; } + var keys = Object.keys(keys_obj); + + for (i = 0; i < keys.length; i++) { + var setter = obj['_raw_set_' + keys[i]]; + if (!setter) { + Util.Warn('Invalid property ' + keys[i]); + continue; + } + + if (keys[i] in conf) { + setter.call(obj, conf[keys[i]]); + } else { + setter.call(obj, defaults[keys[i]]); + } } }; +/* + * Decode from UTF-8 + */ +Util.decodeUTF8 = function (utf8string) { + "use strict"; + return decodeURIComponent(escape(utf8string)); +}; + + /* * Cross-browser routines */ -// Get DOM element position on page -Util.getPosition = function (obj) { - var x = 0, y = 0; - if (obj.offsetParent) { - do { - x += obj.offsetLeft; - y += obj.offsetTop; - obj = obj.offsetParent; - } while (obj); + +// Dynamically load scripts without using document.write() +// Reference: http://unixpapa.com/js/dyna.html +// +// Handles the case where load_scripts is invoked from a script that +// itself is loaded via load_scripts. Once all scripts are loaded the +// window.onscriptsloaded handler is called (if set). +Util.get_include_uri = function () { + return (typeof INCLUDE_URI !== "undefined") ? INCLUDE_URI : "include/"; +}; +Util._loading_scripts = []; +Util._pending_scripts = []; +Util.load_scripts = function (files) { + "use strict"; + var head = document.getElementsByTagName('head')[0], script, + ls = Util._loading_scripts, ps = Util._pending_scripts; + + var loadFunc = function (e) { + while (ls.length > 0 && (ls[0].readyState === 'loaded' || + ls[0].readyState === 'complete')) { + // For IE, append the script to trigger execution + var s = ls.shift(); + //console.log("loaded script: " + s.src); + head.appendChild(s); + } + if (!this.readyState || + (Util.Engine.presto && this.readyState === 'loaded') || + this.readyState === 'complete') { + if (ps.indexOf(this) >= 0) { + this.onload = this.onreadystatechange = null; + //console.log("completed script: " + this.src); + ps.splice(ps.indexOf(this), 1); + + // Call window.onscriptsload after last script loads + if (ps.length === 0 && window.onscriptsload) { + window.onscriptsload(); + } + } + } + }; + + for (var f = 0; f < files.length; f++) { + script = document.createElement('script'); + script.type = 'text/javascript'; + script.src = Util.get_include_uri() + files[f]; + //console.log("loading script: " + script.src); + script.onload = script.onreadystatechange = loadFunc; + // In-order script execution tricks + if (Util.Engine.trident) { + // For IE wait until readyState is 'loaded' before + // appending it which will trigger execution + // http://wiki.whatwg.org/wiki/Dynamic_Script_Execution_Order + ls.push(script); + } else { + // For webkit and firefox set async=false and append now + // https://developer.mozilla.org/en-US/docs/HTML/Element/script + script.async = false; + head.appendChild(script); + } + ps.push(script); } - return {'x': x, 'y': y}; }; + +// Get DOM element position on page +// This solution is based based on http://www.greywyvern.com/?post=331 +// Thanks to Brian Huisman AKA GreyWyvern! +Util.getPosition = (function () { + "use strict"; + function getStyle(obj, styleProp) { + var y; + if (obj.currentStyle) { + y = obj.currentStyle[styleProp]; + } else if (window.getComputedStyle) + y = window.getComputedStyle(obj, null)[styleProp]; + return y; + } + + function scrollDist() { + var myScrollTop = 0, myScrollLeft = 0; + var html = document.getElementsByTagName('html')[0]; + + // get the scrollTop part + if (html.scrollTop && document.documentElement.scrollTop) { + myScrollTop = html.scrollTop; + } else if (html.scrollTop || document.documentElement.scrollTop) { + myScrollTop = html.scrollTop + document.documentElement.scrollTop; + } else if (document.body.scrollTop) { + myScrollTop = document.body.scrollTop; + } else { + myScrollTop = 0; + } + + // get the scrollLeft part + if (html.scrollLeft && document.documentElement.scrollLeft) { + myScrollLeft = html.scrollLeft; + } else if (html.scrollLeft || document.documentElement.scrollLeft) { + myScrollLeft = html.scrollLeft + document.documentElement.scrollLeft; + } else if (document.body.scrollLeft) { + myScrollLeft = document.body.scrollLeft; + } else { + myScrollLeft = 0; + } + + return [myScrollLeft, myScrollTop]; + } + + return function (obj) { + var curleft = 0, curtop = 0, scr = obj, fixed = false; + while ((scr = scr.parentNode) && scr != document.body) { + curleft -= scr.scrollLeft || 0; + curtop -= scr.scrollTop || 0; + if (getStyle(scr, "position") == "fixed") { + fixed = true; + } + } + if (fixed && !window.opera) { + var scrDist = scrollDist(); + curleft += scrDist[0]; + curtop += scrDist[1]; + } + + do { + curleft += obj.offsetLeft; + curtop += obj.offsetTop; + } while ((obj = obj.offsetParent)); + + return {'x': curleft, 'y': curtop}; + }; +})(); + + // Get mouse event position in DOM element Util.getEventPosition = function (e, obj, scale) { + "use strict"; var evt, docX, docY, pos; //if (!e) evt = window.event; evt = (e ? e : window.event); @@ -239,36 +521,43 @@ Util.getEventPosition = function (e, obj, scale) { if (typeof scale === "undefined") { scale = 1; } - return {'x': (docX - pos.x) / scale, 'y': (docY - pos.y) / scale}; + var realx = docX - pos.x; + var realy = docY - pos.y; + var x = Math.max(Math.min(realx, obj.width - 1), 0); + var y = Math.max(Math.min(realy, obj.height - 1), 0); + return {'x': x / scale, 'y': y / scale, 'realx': realx / scale, 'realy': realy / scale}; }; // Event registration. Based on: http://www.scottandrew.com/weblog/articles/cbs-events -Util.addEvent = function (obj, evType, fn){ - if (obj.attachEvent){ - var r = obj.attachEvent("on"+evType, fn); +Util.addEvent = function (obj, evType, fn) { + "use strict"; + if (obj.attachEvent) { + var r = obj.attachEvent("on" + evType, fn); return r; - } else if (obj.addEventListener){ - obj.addEventListener(evType, fn, false); + } else if (obj.addEventListener) { + obj.addEventListener(evType, fn, false); return true; } else { - throw("Handler could not be attached"); + throw new Error("Handler could not be attached"); } }; -Util.removeEvent = function(obj, evType, fn){ - if (obj.detachEvent){ - var r = obj.detachEvent("on"+evType, fn); +Util.removeEvent = function (obj, evType, fn) { + "use strict"; + if (obj.detachEvent) { + var r = obj.detachEvent("on" + evType, fn); return r; - } else if (obj.removeEventListener){ + } else if (obj.removeEventListener) { obj.removeEventListener(evType, fn, false); return true; } else { - throw("Handler could not be removed"); + throw new Error("Handler could not be removed"); } }; -Util.stopEvent = function(e) { +Util.stopEvent = function (e) { + "use strict"; if (e.stopPropagation) { e.stopPropagation(); } else { e.cancelBubble = true; } @@ -280,41 +569,88 @@ Util.stopEvent = function(e) { // Set browser engine versions. Based on mootools. Util.Features = {xpath: !!(document.evaluate), air: !!(window.runtime), query: !!(document.querySelector)}; -Util.Engine = { - // Version detection break in Opera 11.60 (errors on arguments.callee.caller reference) - //'presto': (function() { - // return (!window.opera) ? false : ((arguments.callee.caller) ? 960 : ((document.getElementsByClassName) ? 950 : 925)); }()), - 'presto': (function() { return (!window.opera) ? false : true; }()), - - 'trident': (function() { - return (!window.ActiveXObject) ? false : ((window.XMLHttpRequest) ? ((document.querySelectorAll) ? 6 : 5) : 4); }()), - 'webkit': (function() { - try { return (navigator.taintEnabled) ? false : ((Util.Features.xpath) ? ((Util.Features.query) ? 525 : 420) : 419); } catch (e) { return false; } }()), - //'webkit': (function() { - // return ((typeof navigator.taintEnabled !== "unknown") && navigator.taintEnabled) ? false : ((Util.Features.xpath) ? ((Util.Features.query) ? 525 : 420) : 419); }()), - 'gecko': (function() { - return (!document.getBoxObjectFor && window.mozInnerScreenX == null) ? false : ((document.getElementsByClassName) ? 19 : 18); }()) -}; -if (Util.Engine.webkit) { - // Extract actual webkit version if available - Util.Engine.webkit = (function(v) { - var re = new RegExp('WebKit/([0-9\.]*) '); - v = (navigator.userAgent.match(re) || ['', v])[1]; - return parseFloat(v, 10); - })(Util.Engine.webkit); -} +(function () { + "use strict"; + // 'presto': (function () { return (!window.opera) ? false : true; }()), + var detectPresto = function () { + return !!window.opera; + }; + + // 'trident': (function () { return (!window.ActiveXObject) ? false : ((window.XMLHttpRequest) ? ((document.querySelectorAll) ? 6 : 5) : 4); + var detectTrident = function () { + if (!window.ActiveXObject) { + return false; + } else { + if (window.XMLHttpRequest) { + return (document.querySelectorAll) ? 6 : 5; + } else { + return 4; + } + } + }; + + // 'webkit': (function () { try { return (navigator.taintEnabled) ? false : ((Util.Features.xpath) ? ((Util.Features.query) ? 525 : 420) : 419); } catch (e) { return false; } }()), + var detectInitialWebkit = function () { + try { + if (navigator.taintEnabled) { + return false; + } else { + if (Util.Features.xpath) { + return (Util.Features.query) ? 525 : 420; + } else { + return 419; + } + } + } catch (e) { + return false; + } + }; + + var detectActualWebkit = function (initial_ver) { + var re = /WebKit\/([0-9\.]*) /; + var str_ver = (navigator.userAgent.match(re) || ['', initial_ver])[1]; + return parseFloat(str_ver, 10); + }; + + // 'gecko': (function () { return (!document.getBoxObjectFor && window.mozInnerScreenX == null) ? false : ((document.getElementsByClassName) ? 19ssName) ? 19 : 18 : 18); }()) + var detectGecko = function () { + /* jshint -W041 */ + if (!document.getBoxObjectFor && window.mozInnerScreenX == null) { + return false; + } else { + return (document.getElementsByClassName) ? 19 : 18; + } + /* jshint +W041 */ + }; + + Util.Engine = { + // Version detection break in Opera 11.60 (errors on arguments.callee.caller reference) + //'presto': (function() { + // return (!window.opera) ? false : ((arguments.callee.caller) ? 960 : ((document.getElementsByClassName) ? 950 : 925)); }()), + 'presto': detectPresto(), + 'trident': detectTrident(), + 'webkit': detectInitialWebkit(), + 'gecko': detectGecko(), + }; + + if (Util.Engine.webkit) { + // Extract actual webkit version if available + Util.Engine.webkit = detectActualWebkit(Util.Engine.webkit); + } +})(); -Util.Flash = (function(){ +Util.Flash = (function () { + "use strict"; var v, version; try { v = navigator.plugins['Shockwave Flash'].description; - } catch(err1) { + } catch (err1) { try { v = new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version'); - } catch(err2) { + } catch (err2) { v = '0 r0'; } } version = v.match(/\d+/g); return {version: parseInt(version[0] || 0 + '.' + version[1], 10) || 0, build: parseInt(version[2], 10) || 0}; -}()); +}()); diff --git a/webclients/novnc/include/web-socket-js/WebSocketMain.swf b/webclients/novnc/include/web-socket-js/WebSocketMain.swf index 244c445bad43199306bc668e1bd34ff639606bf8..f286c81aacc954953086a97dfff7e4e18e78b6bc 100644 GIT binary patch literal 177139 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�{?S0&?Rq)c&c-(}SFHOPy4tz`b5gun?zakh{ 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:-!a@ddza^dI1O zq@S9?plh}@V&kJPM+)fUNddkd`4sY+9<-B8?`1&P7ND?;M$*Qv^bwZXV(n)xC@9Dx zvvRZZGIR5Vxn_$wixlPL3G?!EGOeOqYk}34Y0EFLagmv$;Zrk((f{iwLM)NWqNS7BWl*J{bDz!#irqdgY8h!9d%Tpd&L0Sd>cb6lB&|{t zDy8K4=QB`5jNchX|3#x@RH;`?A8>%bRLaWD}N$SH6eML@9X^kwTl7x#0p;4AEX;8V1qVgJBWwBc+ zSG%p=YPZ!TmR465XPPUjeN`ekdKQF~GP%%J zNy^Jr4N56N(vzhOa$&KdOfQyIRqC9W->R(gMsuYur&J;?ws=$_l|DOMN>oYt1QJE5 ztg$>YeWL@-veD)#Z&b?gEOowWkGlr-;{|(1o@yjRv635CSi;BoBlIZJ-^h!&X zv`i)@%8Ue&T~v%DDV2zwWyNVkajAw36onj3Ip#XbBQ4JjyNSvwmEWAJDV5qaxlQHS z)$%fFPL+-@T0I#x;To5VQY!gGrqf)c7FPNKX!Gi?tfm~*l$@%qFV;7fdz9|#fQult z1=;dyOI4Yox{@$hE348xSvFO+P*Pm(bD|zXI+3?TM;S_MDP2W%06sv$znGNU%8Sb- z4NV%4M-r&E=6H*Q;p)s9ca}b@qEVcaM>tesf{?{@gipk2iPY{8yR$0uoPkhdIJ?I0 z$WzFRJkDBONUc>+rDf7GNtHzF@L02TB~6*-evvx2B8yK{m1#s34b?tPezip>Zgh@{PnZ&+zXqGROpy zMhodT;D2XBfG@U!CCiMrTC&a&mPai)T%ahI869P!f<`M^?8xFbD5Qj$Zi7@b(`MA^ z#nRqnMX}rJ)-~EH)tO3palIw0>=|r`;F{ARgb2*`|l!s;z=vPr~xi>M$I znT!24=Ay>N;wmcq$O zM3&Z*M!BloTNKdemq@iCyRRwDC8;pW%AMI+CAyFzw=!GBCmMZqc3)+NP+qOC5t@sO zPKgL0-0omT4xbQ~xHL#O zwPA6tt~j$!Xel!jRZU(Q<<8C~!U%TC2ys^{jAkY##3fA#POQ~-1u1uyS0RK|%1g;6 znN}&%dbM_yG|gTnsw=Ip&d#l{Yu+N2d`uST2`kQ7q|+J@|s3OAon){y3^{9>iD z)L^lOw90~@JHOsvRBtSxj6q3JwlG*xk)4sFk;;)z^Z82BjHR}kqBL<`r6Qw%@HO)L zTPlPysV}R^rqEMDgRVHYJeSP1=yJ2ka^^;E75~0YW>e&*EM_-YPpL}r zznM;P6XB-Y1PqE=T4hT+>a4onq9_NQvNpy%W{pZB1N|c!ROX2O{!_tyyIb zov5L{$|DaX#@n8x0^6PyY|q`L?J53O+v{o~o#%mU@Bd*Yovqgq)<|Vd$Qd?jqShNN zYqwtE|7g8>Yq+>9;shIVy0%_}Mbv1K;FElDN|G6GY~GHK_L#f%G~*`>}s(u z*X{}D8`ET2B?`5}o>x+743lWDONh9`Fgq4 zmJ<}`%S)SrE>GE?*nd%$pHCEpy}rtzTPG4(T~+3?I<3r^m7Ocj_XTsBC})|}X~@c} z3ulKLs#F!KY?(n)oRg=P<=M+r&P-vRyMRw*X?3FVN|B{XSXPy5m)7MA8yiGbL5afe zCEUi`+_Ye&%dRb_sw`=mJffnqQWy#gb2W0cs;t%*&eZbzyQ{VJjX8!YuQSb7sVomR zRh%#;^dzStaaS;28C6{VdN&0J=X zm8--mTFt~NRk;W$O;LHH1x2$}m14I_rwq&JI7=Z5yP-@e*VUKWkTn(65LM9-Q&z5{ zsw!=n)ku>pqMRmMMFY~&9Is062p2n|I$CgRh>U`}QWQUgNC}VFQCpYktaTL?gfqpGGFe3t zV%O?L*n}*zw4&H)6}fB56j4rdYAm8mfE=mPfFOseb0CKxCLHJ|CDgdLXlF!OnH;Nm zW2eZqqE^b2hl67BBDcp}p>~2{yTk@Qfdnf;C2nh@ zSZ1!Q_MxfPTWVxZl{h~NTvg>oPNxm=rj)mfMJc1jf}E60EsiW#+#aJ=EO)xYsP`tc ztOyQ-uBuA4uPRfV8R0clB`OHw_@7Up9e@bO`l=4`zY^t9uwrPB{~gvqCjj?}RXSss zkD^ytaiuKWP+5(D2#t$&QEI2P$k-sOAlE!SSL+x+K&dK)WHRuZ@wT3(d zoWej=rX|0owh?U}5RMRLl|dy&I~2Uix=aI6rpQ208%A~FjHX*H4(-Fxyd7mkFx%($ z2c*JWm(5m9`O3tK#(JBqHfRXf2{Y>ItVCt8oIuXFL{m~Hvm(#nAky5VunF;C%{Lba zvwhA=LR(X+CnS}T3^MZvt{U?@vppCipE_2rS_Y`G|nRI1!=b*aTsR*)kW z3AOy30%>h|gJW5wiCuDWV|9hlhqS#SllB5N1QkCnIDxD< zCs0YZuvG{nUQ@{Ot{{4?gL4Q_K+3kX*KKP_R@ zm13*^e{-e?!|Btg|5+oD96IU5f2$IF0{p>W3Kylt)j@BzIMbjFRfR+OC5n1kW;qHx z#kLHxxS?97u9fL5Md7?Wdv=*!S3>4g2PK~R9EZxP_5`z3x%!eEO{P?t$E_3Ckg|dlUBlgJEE~`#%2-Q_&lvHIIswIJ7ozfNw7pVqSSY{x!c)b)laJi@c+tog*P!DOw-Ww&OQ)TkRvMVWa*Nl9aGiWk6cz$R~1LM6utV-%wU=@z*Pz<#kmKtv93ITV~VRwP`9z zrB2%=e(ScYXTvFCK5Nn7J#5`6{IWj-F&mz0U{OIG@zZQ(yWF3Mr@pZb<`5?OJ*;Vx;#XQMnP&VIc{Y& z=_ma}rPtyY>GU;~6*;+fqS37vmS>2awLW`JuDLi^UdJawO^#YgQC(F-p2AG!xO5q) zNlv+6rYNobOF>!ZV6;q52W3)4UU6}np}t1wt}FLex$0{3WtqAvwYa{fLR?j44tqjE zrLdBy(IS1&hA6$yT%r&aOO)B+N@<2Zx4Jl7>6cl3SvjJr%3yJzN+GvWbxl^1)Osxr zu|kn&RF`NR5-+Jo8kM2bX8MBe{D8g2TG^=bNUDnnYXM;_D#(%6RjJ(hA+xo#p|T{S z&g^$NY(iJK$!4t5`E#^2x+X8_;&U80EK7|*THK2vr9$?9x+_)~2utk6i@a+`t}Ig5 z{FyT^y{#|T)rRGX7ZdI9(T~OtTN6g8>Pls>3KJ_jZ9kBN00d=C z2n4;2?I39A8U!1p|F^s0%y3b-eK)M@dNAOj5!rGT}v-s zb{IbXd+Ei?4nf%eAia3mAsGE1q!%wcG>g9|Jx6tZ=l7dp{N{>JII1#{UKSM-6pEtM znp$H~o-#vN?av7m*n%NSWmc3cs5-wyM3uQSN@_B7LWS9BF*4+m)X%E)2Oo2>O<;UNu^Yt;g|bMELHV-s{_SoR#~x5 zQpqPu)5tQ5m7vOe-b#Z^ONKLyPD{DoUTIZVWm~1SqViBUq;Aao|9E?|9=Ew}P3$JQ zXaNB-AwdI;;0w?P$$>gY6lVhcU2vW$&eNSpN}NSe6vg4!hg1zN+vV5x>(gz(Wfvt; zkB-hsOJjaw1C!7AE)(@?aZR1As~>sgKHT$K zxbE{iDB2}@T6`3(Z)QdUdJose{xF;5Bh2gVQ4af%r&A0#X{0P&kGkgVk!e{*cw4qG zbaj$TZqsA9`M=wwMY@X<5v4L=_<7>>T6)O(F3;6IX5)W;2|&I&yk9}>;ol8vf9&b} zAa3}@-?d*s@I7t_f9Ep3yNSU4O>IA=i_(geSNf+&ITxQHBzl+-~XZZV6F;S`RwQgadip~$HVj6(zAJD}g z#qQo@vue_sE_uhV&trG*`m=vEqSAK}gkPhHKoNk)=HU-je*znh3K|^7%5oulZW1jX z>JU|CC}3kIYWCG4=zB{wIHqi`9*l1JB7}VcK^F--w0+ts^ij;5B#!|8Y%F9i1uZHw z=e?vW)zpv|Zagr`jB>7m&AO=HJM7n1nD zux+28kK<&*U{xZvpVu^9AdTIax- z=0TI=^MKBTa;PB^P9oO4mhH4>z{{@ph(MOsK2+t=6pH>K6Hy-k zE;c6B+0m53A#pT6oOYZJr6X3-%UwLz-+^9GeMnQ5Q-vo7OoYL_s(d_zN2+LezVv4* z`BoQJO<11AALzoW2^;cLT`+)wezUcM$vEXOs#NpBa7bgp3!Y-;)sCdYhCDFhq}lgBgw57^Sl!k4O(I=0R1~GFNN!vRV-Sqz zyAKo|N4>lOH`(0-=Rlm6FP{%VDeWAU99|jMnooG+Tl~AN3<(NYcowwkedaZ%Kl&4$w53=hn|Mst6%BvQh{fQ2}ui}NI(I&gB;)Tt= zTlFf*VlZ25bposwmzzmh8DU0F`hrZ zCEeUwOu0H6<@ZqkMIZ~skyvA9S7Z3*h{-O<&BxWfg3Of&6s)gO&f9`J7>-0KrOVg= zuvTCkj0qljoAlqSz}PPmdaoaOlGB$+*Vo*X^SE*$BE7*cKnQ8e-Nun#u%Q*nDYJDUp?wqQuZ4U``c%|xJ-ZWwAY8dA%F6?S1`;!Pcw)V zGd!PW%F1N3=z&3xz$m0A*JY?7Q>VeD$%Ck+KtLeU0llY@&A2t8cf!E{V=B-22E%4` zLjtm*cAV!quvt?Eb2QnS1UW;#S{`2=_J#W(hDbhSd?LY*I&Do3e$4m~_~i*d+_*)O zx`_?FH2^+5;x?IHFAn9R^GKu%eBk;?P6srz6$8CS+S$uxAEWAMFx+N$Izny8JBuU_ zCT!8hQ8G&uMZEZ6wDJtd6Hd*pzq{WsFY``w*~LXZVAsPHfbNifxQ`@S!d4b`8#1?U zGFYhgDVE&g0LJ}JQM{3Xm73j;d8NvCvJD8X@**3a&li9cEzkvI?qbUe2w#_L*zO3} zh$bSo3ZO@5e1`E9kV>A<GB+lLNFnPILb zWo09HJQ0vMiJlfPP3@o%#7huv$N@i{cr=7gdUme&k%{Y~e-qKfI|b)mbpk;b%lwew zFrI68N65OItanv2B)Ppa1QFkmr;xzKGQXJ}6m@XW6&TJLiB7?zT^MFu1HyEPogqZo zMvtp>JfFt&)T+6x0HMT_yA5%GlX>7D<%FV8JGo3(IrkIV@yx|dw1b<(Ab9F7E_rn? zl09={uGQjsZ~Sxf|2*g2AFF??`xhM7Hl%;NlnK(!zrB?T(&Rt(3*ikush5Ax$jIz? zXP4D@P53npyPyz=WQpKTYdGq)@{EZVoLkdm|N9%OTWj9kWwDE_Gk%sfX0;3HR>h|z zm;aRHzGqezoQ)Qz@3PR6HU4ie*VZo-@kwIdYV-^cU$E%ag;~h?T5giD&fa7Ui{jK* z>^!W=UYlI}CU#JZCvuKojv3EmZ_;^7+5Ua`EZ6XwNc>hn3pyu1NTDjv^7P?ZahERE zLgcUWY@8$w+Gcs7LXG;^x|n0!l_ue-nnhAxkgEl_s)y=&h~h>1su3k6zQIB+qZz z6{(Gdql+XpQ_V|)6=wsRI;$L*dW#Qv<>JA$zjrG(+JWWn>jI|5IW=F<;p{T2crdZ9g7k!XlD5|j-;z4XkUA4=v z^kpHKi*}?2zLpHYtNnfXmz>mn^FozPKwoZgA($-x8sbUB!3b39NOJsjvE1h?cQv!r zrc(*Jx>XcNX~nb(tWv=9-ArnD6lJz2A2p9o5U@i-C#V~GARR~snlDJrtO>YhYWhoB zntIQEfoCIGu+~sjvWX}z0F+{RPih&Tq^^zE)HS`B1EEPEk>&4cRdvl-6!s{fY%i?v z5b+`E*oBM5 zcqH2kX_alJsmV@&wwr&ZJW)kvI#=+6KEjtiU?8#H5<7X8@)*$-{h(p+C4|jr%F4mQ zvMY{yfQAn6wfa?@gFb_%>2qkRF?;@HX!;6RiVwiD(658dLTV9y0hZ@6<9X~0u>1fP zreB1G-G(e-+%J24X2;^j3dDiC?RHdyw^=lvww6^(Vh)nnnHu&u;}P8Bc0p*g4vL;X zFz%yk7TR%Q;T*I3mK3r%o4}lIaPda5$33>tDQQQD z@&#JXU>mwRVv;px@j@c|y`4P7QI8mAGFmX55(MF*R0W;O4`JcUk`#Y$Nxn*YU6SF) zCArXG7E$eZ(yCurk`%i5-?o)iDKkrkhjyBU947+nw@ej%y(EoOS^`bDaE8g@`Wxf) zUiUa*k91ko-u&;rB-xXeqWc`b_rs-haw@9xeX1S~f^xY$Mhv+gz`FGoYL@~+D3}{^ zOwItEJG$=Y)Fs`HA#NEquP1(c&im_bPXJ)0`h6^0dv#YETTBVZ@OQiX0ca5#J>@!#Ge|XQff@`F&BWc!P456fz#W$tTPd`;$e^8&r zA>L%PA^%iL8*-J?pNMI>hHnJ*yK-9Z;>VJTJ`%I#)62iGX1gyg?ee=?q~jW0dO7-c zwP&G|pXkr;$mLl<{g(>#Gr25h_zew;Rtsf`U%6Ayh$r>xQ0e5VSw=%Yohwb@rCW5l z+7LDJh3oKyJ$i)14mEt6MzJu>wpk!z!K%l_RxZv1q_Z3d)yIy7oEAP;w0a5=MBF72 zbdw^(9l|cu6NxR71Q2-uA{*jb?t6Jb8mdkRMLg`Nug*;DOO=20UVCF03Ps|U`FS~O~QG&vTLAPfD>&Sy_?xW~S+FO!t9Q+bXQ+NzFL zq#v>L5_CE+`R&4A0Xu}{-n0;ILn1yh>9Chnyl5cBWH!e=L>@>N(+_MaNN7_n8o9jX*EYfhC*bH0j_4+ruH^vgKIW>J2AYiDRog5RX+y3Av++301{g z<&@4FGE~s0ZTIIzyiOf@d5+wq;3Dsjr(5XB-5xW&+#rtNgG`XnGv{-XAk*dHdX>P# zuDVFFY5;gfRsY6R<8cs}e6Qlc_R^nXrhHt*DHPgSKu%6@gWAPab*ppl_T-Awm_|_>v^r8CO^Vh~J3C`v}s`<}gxo%6Yr1i5R|ey>JH0 z&A$fsq}etIY7?ReUiBE4MDuiK;`pknF{0&}R~W?FHTsKI2L5+64C2oWgZNv9;RiN> zf}&^Gl70U8iB0(C`hNHFei@HPbzWSX!T?n}{D8;tw6d<3qkkWdep0KvrVqYCfu zwQedqgXjYcI_ne6YnmeP4%^hg$=ELLoJ&6<8O}8tbB`d(^p_e=YF7l)@*47L-k0@uaI(kwSNCPIv2>3d(VfOGS?T0x{bC7TbDzK2e=ZnQQiqMd- z62hV9fQ;Hm8?O3W}up^W`GB<;jt53?GWA-R|FF%cfPzDOEilUbe1Xl9WG3->_Hutu~+ zVJT2&NyWKiCtQbzd%a?V)5O>F-qkt`uSy8$bf&w56OCE;SUZy1MxwU)*Pe0{JlUdC zJO9P_Q7b{dB8x{(i=Yz!`7ZZo~_H>H5UV{JlvQQW@uMrS=B6_;;Ka%i6?byqn;y3 z*U00hllAjq8P8T=edRdEV8ubLzYrd-?aBvdZ^d?+$MnMT%1oU790FU)jU9NUIxg6w}_FTbYg|4p`QnfR9y_P6NOlSzbIH zscUC6zJ9)QDRKlcipYGomD63DiF{%6-5Dc+J&Xe(oJzB4EdDE8F6^-`&nHqQ!TTtb zR*h+0Rrso(?k7+=b zwd)p7c8{vNt;O#Q@RsDRScJ`nWW5nLx_Q3P7)#B#(~*R}Lue~`T=EBdyB0W@rsNG8 zt;?9^Qqn6zl3Is__c^XvP>VirkD{fV-Z_sDEvQ9t-x-F%4EC_c&(1az5%CC|P|pPqywDb#?Aj&k)Nz>@E6xMuI7KT9G~N99GazpXva2waXgsl_Pis4 zJNHPt-xyExRo1WytL1B+G5hxcFLpd=Z@ujk!bChbuf?e3By!X~sK>R{(pWxq4}1yv z#ql)YT9nI9Uyr8|;g{04zaCHXcD*m-X@2#JKfB%y`Ab*)7Y$S|<7r+y?m@5V3&v}T z6sB(TTnhxv+UgLh(owm)rtFW$6WO0zX0YgVo8vCyPYbXG{C#_}W|T^C^!5m%2otQ` zgf~vLWzNonJ>AZ5m~BZ{pP->ZLMt$NF1ImyjxLm5ogUD|KW|wyhAKVjz zY#+-hh%Q~&q|^RTWv7^UT)^WlH#wCULGA?T&4nv%-sw{>b|A&a+I7mog{td*`Fjj#(^96%9!N4qeL$YE0zi!0Cg159&b(C>Q1}KNm(I z5_SkdW}dpga$AiRq#-n!TJp6iUVEg8#ru^*>?j+iGnf5Hm-?BU$N?MYN6}0!uOhX4 zMy@;Z93|65Ex|+GpY68TEljoC{MWz96aVr5{exe_wF31?70Fi>FmCNmM|aJF!tXOk zbcRIV5*#X^b%C$Vipa3bVsP__uZp$W`RMJl_)?~y^eB8RnM@UnT8t@+E>Ql)hek!_ z)$4LXsvK4c=A!T7@*Vk3lDmK%_g_?D;js&;IA-jNXTnyeF0I+65AD z`S9=>&L!9BHFZa`>@1*d8|W9oE=LB|?N%y7@zbiKG-m%EWyMvddAs5H4=Jlf-bME5 zy4%fgcpI)ZvrRCS=`*cDQ{P`hzJ4lv#5t9{J&+A~s@+l)+&Q$UM^ul9YT_>id(U2{ z@WWZ&v*Cx(H=*UzxA##Ji(Pic1M}Cm`b~1*%5AYvzbUzI<+dSzS90IVZLzYyDY_ptM^qU$mI8Wgy++Ng>FUmL?@UdgN^mqw)H1im*@IKwitz z^|JxT8S8oSx!Dq8g51;sk21z`sc=ter`|%5C~2#5N4na*c6N8gty3}aO8+_C9SGHdoykrm*Dsb zr0FuwR&MQY%xLJo`Y*R*LNts`47y@#i*`?-$5?;TaYU<;=db8zF^kc z#!?T9n@uAM$f&ur%}!+?_1t}k#P;!dxbX$|ze+*8jcmYYv7>6P6qmFHVTd$&u; z;+eYm7JbmCX@{yZ%T7~!Je3lmo~~qT(hJGgt$VR(HPb4X>dg{TZ$oJ~?~#Y8o+RXs zE_c1X)%@hzQ`cFKJsZScSGuZ>Ymx5UnHoBrq4=$A-~Oazl2vufF1I}^DML@2~dp%8;tM=Z=zi2|?QSJ9p0!oy8fINh)y z@RSlVFohgCniui1`JW0(!bZmeZfi*Kzy3{c=MA^ie`nl!<5~g~?$Wc~QYgd<)n$vG zBux!|!nK|jfBF-q^tU+1^pRtHG89Gi@2u&&;}wt}cJE!)+9bl7p$}Z+q-MPrbi(39 z(axeshd1Q)0I0Yb>xW%^*;>9o#9KDlNqB&7b91Ng-h4QOEXf{Kp6op%HvO{z^4Pwz zO58c5z=qsI>A6*|cLU+w0&so*f^l2CEvdt!t>(m0`7K5|3%|jk*hTK;(G%c$1mfHw z;&Aw>b>=v2j*xgl((pog6KPklQUZDiQ9~UEhIh5fg=+zOC$jnnp!l8i*1EhEuunP*9vwXdc$5461 zrp+9V<9J0_DcYJNbvvM*8`md;0kG@(P!vDd`Zl)u%(W0|e?|)2-5n_b9;wq{ zYIC6qx+~XW+iT2<=LewiAe1=Xr@6Fj+3-YsM~c*A96U8NxC&Y@vIU92Ogg)ud7NV* zxnhKtmnn$5FD$n0`1HAz=h)&!1ytgEg={a~0n-|~IR{4@VG{k=k3oGz_8N}* z5764_R2b-|B4}e>%(a7i!P^4Qi=#k=6-~wEd>3m|-~0y~Z^<$WuABe$B^|yd%D;5~ zaPimfR4H*E+Bal&q=bkk^1vIPaUYRKK*iUx5515IJ0#CM?09Y)q++T92BAw5=c8-6Ck_vW1P>rXp1@kpK{W!|#jB%(gE-fO$bE9DzChJ4l~tw+~m zzbe;t0MO4R`&WWpJN3R2?N6IzzO=|NN2Win<=`<|z`#~MKOSQatJQIP6@?w!c1rEC zhDG4KZo+j^&*PlT<6V@usFnxlf{Zm89v{~YIZl8zp& zS8UJpBaK13LuKJtj(~Z{)ol9Um)eY%!g7YacPqN4MBSoL;v z?@D~?eIh?QmM%$fb&ukNM{ScA%wqZ9hU_lRA?wpHzq|KHb7Ai42okHKJh^iPXJ#h%3;Tw|3xdz+bCg)1*N`%qhnq-WEdh5wdn3pD*m!kKA zOkVmTVU|k`XVC}YUh}1+Rd_2oG(k`*0JQoJ-#Ohc%~4!o%XrVa-`e)PQ_j{j66b?e zHoOD_lQ-(ocpE}l8Wv7qXV-fwOIWk0dT=&cY(pXuo#=KH6@zQ;hH{j%U6T8!6Eh)Q zW!U$u>j9G4vSbO8$Go|p$i9!JK2HIQ5%9gSA=7dwZx{q!BU@aN3;~jp6~IvSK#w`; zpkT~Ia&WHsFchwHTNh%7Cp&}>(8LDwwR-g?EcjSH>H$nb1v|*ht?M4gx-~Aten_(2 z{uqyk5-E(ie$dK77{uAG7Ym^2fu2p}au=6a@;oVnhFu)p^kcGR(Du4FIb8RVz0Fm- z`W{>aeirVDg%+Z}W2sb#D@0NhFw((h^DkkF(ok3^U?;Kmgr=vR0v)FjT!(Mum%M<7 zg(Cl--^1#{3rtW^+2O;k44usZKR}k%@N(j$n(Dy5o8IeJ(D|P}yvTg};uA~zl{B>P z_3H}t0;LoK2Q`ucpOOZfSu3yMx8}c5_20%?zlNON(MP0#)G5Rdje(6#e?ZC&x!lo* zSJkgxU0!?6%2AnMK}dH04C=j4%3+_!RxEuzT2ShTtvVETOnziWRt{w?pMExHzR#7A zzq*U!OY7!>(cirGbw%a-4ho-F5y_D*k?2;b@uqfMdX&4Z=vom?XuC{cQ1+%P8}b1s z=kc=T0ZbfxEh27b4ucLp97uswX3QSo^wSo)Z(}&`oF>DpHj=GlcxvU7>Sg@eF4icw zM>AhLbn+zQ+Pm{Xo1zA?&0~Q7*rFU{jzE|~lY8*#qSLmRqoV3e1D(OS@&+U(E`>9yjFNZg2bZ zsilwC^s&yNB8aK=NZU_IYI1I%R7b%C zVy1a0pf*J`a#c}%y%170(RaIrLh6!Gb7z_ zG#~J&W+U6_u0)7)f)!Eyp=L;4;_huJmRNmNfi9>!1;0M1G6P!NrF{p$GO;uD4udoc%N6nT&F}xQFAz+K|nC3*`hurc@c5F?lZz z1*{xRz%4Hv6}dW>AiXK1NQP$Hey1V4#A5`%O@=eAYQdPS&7cn^iY^b6-y>$hZRrQP zmVWIp^xliAX-w`mvBS=rz?sRG!!N?6qOh*GQ$HAS!; z_Y#5LPF0MjCsj(>`(mKPvE2y>ePF4QeVk6|iMN$CnDM{4$0}c4x%a%6`|N+mJ~Z+P z?0ENJ_`-xf4vi}%MUtAF(+HR=rN`&f|i<4NDrq~qc) zK32DJ@y)*ZB5tpIsnq#;I?%h8LJ5>i2)KUmIo|E1N&4}MonN(9|0gcvu2CI64B>gH z#q&x(%z&ejH6&b-t1f=-U`E}C>iF2^_b>Ono@fn2K7T9UvdCY;ecy(Mx$fw!S!d%t z$f?=n{FVsoY=!%B-o^5~hCJES;fck$)R1q#$@mb^HIZ+oK{bhB4u>3nERuqh%Qv@f z;ji}jW6k2T64tBwuqMc4z1rvWGNb=6W^>{H!KB%X#V%Cf@UrL8^Vo*`aP%)F_9J;M zckzo7`;olfg6lt**pKA3A%9h3Ka$rvUFlU~zcv>*@B#_VqulG;;IUj=upyN;aRf5# zi9@Sd$}m>cdB6b_?{4Y7yc`EW_9`-o$z>7cyivfrku7Q*7Kn(r@_Dpf@S?w%X9VaB zY9AhXwBB<0a4}mQn0F&Ym3(?k4={5}CQjpMBdQsQVYVe*Griz&5))`s|ds1`{=C=bu|8-1G} zD67fv^laUaiM6{<7!n-E56^0bb0OLwOk1zZ&L&oV0LW%5Drgz~i zxeH;(lO?YUvEIYjhr1)tqvpi=u=$sZZ$Yh%rq}Zpv964ICktHsg$4dw%X4+a?Q*Yuy*$4h{hQ14SFiTRi|aR+#^G=gWaRh| zy-;%Z=wngKgv1c+egR!42}377P_7@NNDAuQX#0se%yZE_9A3gKm!>WAMpx*3*7la- z%=KOii4Eyh^)*aPiJgGbHb*1Ed@6GdY0(RE|jB-62G2YT}_-cCRCAzH;LBHw2mN3yWlD%d?? zC)^|4TmbZB^M9-x48C}FA9jS?|L*FT|6p~f|1DO>x}(_syVdb>^p94D`rmJL6g9_t z!l7_G<47H=Gqaw-vs-+7x<2Nas}xUhz_aF9jgp!g1=R9^zU-6ve7U3d1e(KX~g0#$fTAovbwd2rTk_)fX{&sGO_#Kk8JjG52U!^wo(+hr~8RM>QXS8w%T?oqTr~-zqSm;CAkQcz~b+Ns60z3P0!MVit z(K6nAhHC*mrB!*$cA4My``d8R_Gug9wK-2MrS}kjNOt7rU-Ybw`FODHK%(x!e1e&M zx{yTO^pRV0*%k%m_kUDO2CcY6JGtjXI$mAE>U?24!pH+eWB`yf`s3*=vG$9 z7C9c4m9S{%cLj}Wpu?<*NfG(?)Cd}S#^TdymBE&C5{_iX1 zhWryLzC4RRk>V=DbM)y**QnqLxkFE&pC)AZFg*B-a2}__6XvvRk?UVdK6liBQat;14f}O z^33JFE5*QFe%fqloLHDj3b$a3C<$)^MKQ<1^$K@GHbo%a26wn!2QLBIy+zbH9*MwJ%&qwTQ+IwCEI zRK7u-7qc4dj~lYR*R7ccdVB(_o2z+2$_a95Uv~uo(BmQM&!)y>is@rA2sByTAvjOh{ep$-slNx zI;9y~dwYYHyXY`2gho9ylwBFPPK=*-+T+A4r`hn!-Hr{3MQBHue{d>eg%w;{q|lwt zixxHX{kE9ut3@xXD_?iy%Z@lM80VHLZUYQOWY0$2tt%C^zRie5ONf<~dY27bQLw{f zci7^2w%i4~4-p&O8)()KclR`EU>JxyJ!S}8&T#vYrM`Qh%(Omg3X~qb*qSF03(nUA zzQaB%kpX|p0REYo`MQGc#cHfM)q$m1o*O2hR(&Mn(vNPNgVERHOQ)Sq^@b$X!@$z2ZQ#dnVe6 z)rH%dT-tItdp?UtB6sP)*eyfl#K-O+R1#X>e)NO^;1hISRYIqbc~CkdL_k%@#yNpMQoD~_;!Vnn5sYCgGl-m6YsDNK_FJ602lf4$ocBr0@(2+TID}!8dQo#RxCg1zjYZ#U<+; zJ(blhgzT>!3*Mz}w<~yIlJZ$gZjA08Oi&^?@Klt>GCkj6bHNJ2<4LT58 zwYpW5IM_Q9GXC;Y7KwkpmDeC!UWt5{6$I6h} z79K!XSw#7Xb4PYzR9GQpoYRz^=LWj0ikAk`@&dD3Qilj6bmJ{&QT5{U(Ob8~wyimB z8}oV|*k0KVX>)TPjj>lXbZ3*$O1%M#AMtEEp!Uv>M%Y?e01XHp-d?AOhCLKm^xr@Bc5f_Pn%1l|) z@`ht?(R#d7@Os+y=J5jB3&wyGr)Sp^NQ%&h64F(0?|vSgvj$4Dezz|^4*Mk0?YH?O zy49rQkI1- zy&=6k-=+5*+-GXyh(~%u?gDryri$+4@iW(4WcSmp< z?zab7#&+H@V>k1QM%m+>*~s)@hAAPSR0DNm?>5Zw4g!9G0H@Tmvt8$MeY?N)#uc?o zLdgz@f-h2IjZc^<{NE90jK9JerfcMr5?$PujCTwD86Fjf{T9v`e}pr}e=nSoA?0dv z*RiT^c(C1XNZug4xtsRF#FUpjsH`nDhd~uWcbY+TN54n?9rx!(JkKRc&i$Ena^62$ zcj^F%+*+fyt@*yy<+Wj#mhaIiEF!BO6rh!)^em0nyNH*`x8(dL^2(e zlddc{qm9V`Nee=!ZJRHH>!>)EP8;GX^uL5DkIQmta zw*|w5EwoO%oIoEN)Su!^v1_U$Ktzaa9;Cf@6Eio|)pmIn^xc#}^pP|!VUI`3Kg7zx zocisd%e)7!gwjZ&ov|<>As+krW>fzCUT4Elh>Ws$1nZnDwBwvWq)Cdq9CrsXaFR2L z=&mt5Ja*zPy;Xv;Z=5E1qu?WCN z#u^%KCUD52)3v^+j#u@%VHM>5l-}*5d76*65R0*RK}*fSjSTPyQ%X#`U=F&DXBNts zWj^gE>Rc8_9f3-{=Fkw;8RM!v_6`QLb5##F5LqM{DGy6X=|Ma}u;IycpXAf1+<8Y= z$1%XER?z8+69XFB*gnR zGkguDDW-?K&3W%b-xv=wiM$8KF3&QDD!Q1H@-ma^S%(L43>4jKj0VRe5%5S`@dQ`OAqZ_4#4mEkE>kGVIKK z`@Z*0GuDlW{`2e3On=!=|AfJH)EC9_Uk1$rH_P~RqZgVQzL`w1A)m7;7}9z#!UNns zd2tf^XmJu%yBiK~{iCx4B~aXlAEdkPS!z6tJ!Ol0wPsqTn1<6}fxozRYb7U}MP&Eo zVrP*buYo7_vdva1t-iUO@yCv_GX3VsrS{9GtC0EPs4i~hXnuPQUp9I9u$kPijMzEY zP-(@!s@qIl~`n-deK!z6s>d z=8JqfMtJkhAYn$6MHO(VUc!g%LqphchvH+P)(xtN#_<525kv)X<68E>c~4RC0w5xg zXrmQ}sTHWMAZj@cg#yx87i^m2d7-w{1q1(3{~(xSSh5$o|DkGM)y8^g;O;pHxpct}&h zej6nG{dDogESu%)Hagbw|7Rh*y6DNN5rxGCSbhdqQE-*8}eO+QzyZw3>U+_o)G|Q%mr<7g`CT}l; zkx6#VFQ+8_OnIn`Gif#ps!Rzicfqx<8wr(zW{Z1u-DJ}?hF{r62&a;K4eL5Yx5H(U ze_)Pi`S^C9161?%HbOe-2bt#Tk-no->bBj}Y_-$x!JS<9H2$!W(EBz*#<$xD4a4&A zShq0x3{+b8nkFmfA3ok87Ez@`Y|)Tnqq!Lkr9tu$|;j8av!DM9TLVJj0{8 z4zMB+d5g!oyAR!#Qo3%=12Bx!aqQ2%zSaCBFj4kI@b*{$0Fq;^7*G900ri7OT!=W? z{1c3|)PTo;$z@Lga*gD2@*Oz_c%GU_E#?0PbvgNF7aov3eFe6?qj9Pt>xCxLhgN zO8IE8e$iabD)U|?-hO=Fe_7vXfa7(wqrYV0F?%ub-20~D?14O(Sn>Hzk(2$~Cf-R0 z^R+gfV~8J__}2^e0gdv3wIl@p3$sssWwB>VOCi0sP)Se(Rl zKIr%s|5k0e{PvyN@;%Ej`sb$pwY`4L*4pCoy&|B_Z>Ik3nDadLBQ0Fp>wjMlKdRwc z|NUo*xb7?bXPUSnzkTPgs^Xi;|7L2@cY6mg;^A7IRP%hk(aEC}9wmP!xMsF#5a%0v zM>QJ~ZEWHKxyX$3vJ+a5aUU}#xRCrnjJ*4Gr$L#}&!>RBMu#!e2#gR_mE*665OKG9 zKW<25Ud%TpqxxfUz3mRmqP9)wd8^M{e?xdTN+X8?_KL!V0#BM2n8HSh5k)CIG6dnJMYNm7%8chVoXR$!0VhP-mU zev%r?k}e3r_$P)51hVdoLsL3wIE>fJG#Qc9B123y&I7qj4pwz-ILv6~nDe}l=Lum6 zjJlX@Ya#aIC{U$yU>Fx}#Zj`b5n~+~JEy!yYIATS4K*Oh2WY20gK_q_J8u3XTk1Re z5b#o)NBr^1>cVmI55qq<|N4;tylwCd0xF`7IbIGvc5z>Gf&v{WazXSYPZte?Y?GbC>?k ze2pvL;rnPX*JK)-FJ4KAUJY)@SyP5Ju95jdgyVEhUr}F6cDyz z4$Pf0+{JYB2>&;i7i5NZkpNTb?e{7D_`^Q?CcgB(}ci1GF@DQrmiHCx_5#t;{4cWaeqWlCj_ zjDBMKYJypxzrnZp`PnZB1^CL$*-MI&M(u1HB$u(@~9M>c@3Qy z5_hYHwJzkHy}j)jwC?j9(Oj@s3KG?5cvw(}Etwxjz&E7loH{YEWDc+6+*MC~Lvwr= z;C6wI7bV2UtCaA(LBoW~FOExg=Ji5GfRThK5NOqf|24XW)wMQb9#qNq$DNbQ+gJo9 zKtFC2+*jZgiTQBvu@Bdgz_Og(D^q&) zUPVPj7+X=!kIkZ=eo2eBslDEhJ%o{tkL3cO?ePtHh90}yIP1v06K*aRWg%q912BVw zZ6akwveO;h&e-)LmO2;1K_|Mf=nyx1z)-C|uk)aH%8u5U9J<##x!uAU9Ar9a%Xh?V z&Nz;vVc{S2T{mczfzycM$p{}2WIuq+F|OQc^B*71$X#k4eZUq~rn(8#6+W)Hw*N>S zt9xEI>-OPObh^E4La}xYC@R-{GYg{PR8t&S@(tT6I`GD|NOSP zCCIt}tnk+gTACR@y=A1Lc>;XEH&Vk#r}?wmYN@g^7h7{RWQzK^I3&(} z2PI0S@8%RdL2yw*)mzdmdm+GOKgGgb?xkTF=Xg=wRaT>9#pje;WX7$~7cKc({$7$Wi%bHIEV4N!+U>dh zbuxt*_#as-C7aPxid3L(n~j5pnaYIQmE!ekGjE2RZ&Z0kN>^ROYhf2UEzWDyK%Osc z&>52c@^2b0Y^07}MZqw6@ebanv$Z~kB(IgU$VfM5F6x*6lezWD)f?YjkJsToA=$S{ z8Mf_{&Zzo=9d=m8zrWJ%D6D_BfWymfmb{aLJ|?t2Tex)SIb3j2R+Gj;U)=c0Lor0I z33LIH1ItdC7k&)Mcv2cs*>do&#ha=ne@soQU#Zz8+~6+(tLjb78rqqKJ==}L4^jKq zg3(T<+BapDeY_8-xz{`2Xf6cKqJT24fjK+_SAbS^X#Cg-2HbAw1K;k0(&*FGAJps; zp5$r5d#781tTC|j!Z}BeoE|vdOIGi-A!4Cdc*84^`F!W|zE_$Mip*7@iamRqKRL`) z2)ySnhE#myitKy;oDpK)8k%)tO6r9$gY&h<88pTDGCC`ySx}eKW6olkbmN|{#9E!Y zU^Ayibw}0*FU)J8Z54la?GOIgfp4+Jj_Zhl$eqY%>t;kyln4i`5(!_=w^YkV>6=WaOV;TErVL+HSg;mI$x&Wx? z2rj_N%SHu&@l-d9`T05u=}kes2O_>VH}AJ30*;UBK{`6=Cug-w*DV|X!0@=wK@V2i zRTZyICf6|?mZiw4uhx6z>>+XcLjex)nja(ynobCdyN#>*iZrF_s@MRhdym#M@ltb! zII80wGf>GqZ}xJyt};du_PV~(GVla<50MRqM*em5X2f7NqUS&dh1f*gVL$miW}9S^ zsU2BZu-Q@^*q!){j17%UJ*7?e7Z|$CU5{E%BO|LHqr@li`G)2>OYo`r$#*^W_SEXMMm;|HTS_ zwZ6!G4mto1?)N5_11;mwM4|$+DsS%GWjJ@}HOzMT)s7(GNva+#rjXF(E-Y)nq0#OX zr1&;VOf=U(b}m>ftA?#@1MPQNiK>KT> z{l*jA^VZBp%Wlonk-@oDfo`PqZ1d;2+1#liaA@JC9ou`A-jBtuIZxHbi4I^Vnex6$ zcMknV66U64_VZx&b38Dw$xkvIgLp6$E@NfC8)3`T8#xy=1kP+F-(Lq!MO3clugnyG zH&YszgCQ;IOwhS(>V_!Bm`X<8G<1}!uW;+hV+-56!~OI$g&^IE0K2$2k^CTVQ&w@jzRFog}w>467^qz`@-$ zy6sWEyvrw~HFIv$g^{`&Ps+PS7Z(MD`x+Grz>>A8mu_lWj0Bp?4KhtZ!Z7Z`WQ3aCI=!pHI$t z5aVP&BL18He7J?uqCtO4fu_I98@;8oZ8mOoGHxs7o=nwLvU_JYF&A{TTZK>{A5QDx zTq5ogT)B^ua2oa#+%Z`j^C5JW#iY|mNsL5V+p1b~plfiDoFduBS{wQF;ik$+k>_j1 zHQxU)$M+6 zvPeI;s@L9egd%tKMnpcQX(+e%tyKw?&B9u7ntW`O zmw#AE>-|G0yRa!AmDs?md3A0ctP?;zabFC*`1WytdeUs&Qb<|H2I+kok868pC0-2i zfU~A$#_5u0$DTt^m+GH91t*R4QRVI+`7;M9yQ!_w5Y8jc~e^y zF>b!>D0GY;6~hw6d7I~686@k9Cd7==OmuIeDdukNBg|qZK<-)WpZSFC;v5*@&&W~* zt^k*ToqYsPJ^T4aA7W`hOmp6RjV1vl`^q{D?vnQnQro7; zWk6BThYjpP6+mNP2(m*&_Po7EIrO*=o53zs-P1=>xk46l5YG|`OPZf~p+Y%FqYo6X ziP(#iP-(nEpyY*E>k6io%ITVGGk=R(%ZlL+nrAz+b0ThbNR85(FDK{wQNnDZhT7oT z5yQ@OP5|9gb8)e*)2c2~A*WU=)kLcu+7<1o*bC96i?foO6DeZQw)d}W;@>eYl{#Tf z!Gx7=@oF8t5V4ob)LGavT|eVKegtNlH^!NDEaYIP1y*Ga$%$4c?H=dWzH0WfCwuqC zMN9*_bqMo9zysvBx!%f^4Y>rc7B2m$$PO8&Vg|c!oP-J`GvCGS#VxMSIsgFQLn-p= z(7@0;-b~CJDNX*Fs#@+KU(0XUDXzMJJ-v>2xxz3lLc9)~5y0AIRjyPWDo~ zSg1j*=+3xq{XUitt`n%r?hN(J)M065Be5Sy;L9KqO z6K-aUV&L9gYZ!97x6eM?&dKcUvXgz#$=WRL_-ebQsKQ4Y@_r9ZO>vdWR7qgV6<|`3 z1mbbEH`uolHJmz?q9K8>_%sl`MHBq7xgEChF`T*tM)>FtyQwdLS)bg6 z6rtAAB_a@GeIKNOmuZYh+^gPfvTZD>N9yV_c4yEQt8Bw@OJmh_YM2AQe1!4^CRbP; z_sJysezcx0L9oKN8VL?;0PRQhcnPLcTOFspofhzX00zMSbO{a3)QvFsz={2R5Ggo+ z7q3rTOVce&F_--4C-&$<%q{dY<1|3K-HfaPbS~%p*5$lOcSVai?g!td9hh( zDG?_>7Q-{bYb zFr*nsPMJY@jn^}8SLfHOKL(Ou|8u_n^Q%(By0cHbQo-n=+^$!t7Ga42@8pH>ibszcR* zV@Ih2D{Nij3_K%ZwdZ1F zm0PEiAYQ3?-ehJm0QV{_EO$KfYbPpBx7I$*92NOcb3W3TlgCb3gQ)LS?%RfPdP*Z5`#M<@KkoG0|9U0-UEA^fu}*spFf-|DwqX6$eo0o zm)Zr1A|G{J{`}4<`cW@D=(r@*jRSjxBvgAa1Hq4K2WkmJ13g3nJ;f&0Z$IUQSf2BI ziu&s6s7p|LICw-^QFbQGMEonA3(n^E8v0XcL%sEGS4(MsL1Z*Mqzt2Q_X9a5jXOUX zdl!j%gu279* zv(Bi1roG@Rc@L%AH51);I#kCQ@FOQhIgfeGU}CLY9APwBW*5+*x4u-v?h>MrhYdPs zw-;a$1HYI*GPz3D@Z`?TP!Pv1F9K#wV=C>q%9bGZ2VjC7=iZGAaHC-O401qMZSBI* z(k%<;GCA9@?6DTH3AfESRRqB#;A>RmfDkwO6jDmK72^7m1&WIAB6jQ}4OS0feVv!; zp(3}>2ixvB%go8M$V02hL8Y{AAY~>r5vo?e#~-agJe{`Nla9EEuWu1GN$y^*dXE3{ z?_EKrnp3HrRCCN>OInE4o`3^-ni$o-Tr*XH3Yt10x7+5AmwymJ@XW7E)$s3@m-o*? zw8RhF*OWVFeh1+S`&~H^>2lgz@|jdqCXmVY&7?O~Tg%?kz5mT(HT5HFlVCh0=#heF z*vx%rcKXJ!v~ZI@u!ndf^IBBaK>Z>w^nmmHJ2*Eql zF`>i0+t}X8-khCnanr=K8leW+WyS5}7PznhUU_uwzpHXi=VTBqymu#=IiUz&P@A5*1%Z81MP6M#)HdV^M^Vywcrjp+f$hKcLp}&coSTXGB7Moabh{m&BiAq%jOqigNoQ8 zIeK?`zdle+SFJH$*w?f*!@Y71#9{9LVR$ZYi59MaQ9#oD}v@Y)Yr?g=c)5-%Eee+De~zYZ*) zgT`MkXA$1-#r|3K^;+}wac9_&e@4hIkEth5KXO^ad)3!sRW4Pq^YOJD;k#?Y<-3Mx z6EcA<__iBf0sUAF7?R7BbbtTtZXlfJHI+omdz&E8^6DMNVI$j;J z!YYh+?9EhWph${rcPB5$!gqDwzbL9hG(59~Hsr>kZpWhXbjQgKo!l~5dYkK0tfbeu zUdNuj+QX0pp5Q@7Htx{=VzO61uSbdw=g_t@+oMcn?Gw^~Qduc^5FJm*s8^b=1&0zr zJLSUcZ!_>VnMV=BJx~WCUlrWevuj`jb7(yuPgZHUxDVAZ(9odMx2mt7 z_@-|5nt0X8%m5BsMZ7=xh4}H=x7golyuxp6L2>Fl0x|rfSw7qcLxtrw6e+i5U6hABaXI>f-JO0l^13=PxrJF?E6-=M;`dAf}*u2VAOPuiFZ@rg)E6VI75LFMm+fqc>-7wd|dlG+u*w=ML+sSmZB7R=}1`$QPfc}o+FpvuD)IS zot*-v+i&jG&$jB><@#rv^{al4<%E6i=-6r1kZz6w3oKcLo-XOwtq4@#BSmRwxAP`# z1(|V4t=93vVo0!tUDsaq{L+_`(&Ka~RhRsZmHX}4@e9Y@_^YGT))l`Id^4L#Mn2(f z;g=0hxIWLWOCa4GH4ImEyWa_Zmu+CxCvl#+eemVXo5Z$d@QNmn+jfoGG{!&a=)D9wOwcu9&U8w#G*ss|>F% z!wwY)VcdoM5{0m@IWQO}lFjQo6sZG6at{@X#yd;M6d*J5idfn7Dhzc2 z>mXvc)mX3(h;4c%wHp*0>ZKYdNv_(>44c>5(Wba#iIdU#t)F89oCtwGevBF|4v9{l z(yNm-#5LIBes}2RTYp6qj<~PBNNImRiRzKcC)YE;g6K8bEpQxgPUz@YPrk8L%{hZh zGt!a^`fT0&(zel{fqSQj&_3hF_x8+V(%Y*&bN(pZ?_5}~NU)sEKXFEg6Uv&+MpUZQ ziajIfAmktwJ-1J3%+_}RdB}K^>se_HHg=^NsJB=PseB5JFq@tE&8^ION;~eGy%2Gl zlxOQ)S~;gmQ|w9A?ezUmA?_Om-*wHDEQC%#-%t7rD7lAZFCRS{`) zNl#omh3mr=lP);ACMqD+OgfCstlnpx$XoHy+Q@O-k}R6wWy6?Eg*LwBN}`5b9Il%A zy*nO@p3wWfbu$D5ScMy1P~w5i*42nRX`a_TPIVQLGX+E5DAn)@LKE}#PO)fKmj_N; zMJ2*h${8_=%2bON{G3Q_)}m{*t{K6G{V|lmi{U)eUyo-P-64nDC{L&x+{NQDmF2@; z?lQvpG%%t|O+G1lrH)5gU~$89KGYgszCG&$T}h>^UkH^*_@ zZ5o)d_nk2y8WanTWM&qywlX0;@r-S(h4|q0&eoTJckbJ_*&SFHvtaIp!1m;(zB)&> zY|`s^m!T7d9!}70SMTOl%9z_7MaI28_S7v}!Rg3(~2=A71Kt1X`ZmOQ5@* zdS`SGQtmp+nX6O{_L|a96}c`byzT9?v-<`PCIAQR*?#vSsj$tRE=k~7gavA66RY!S zGzAr#oO=E_I6SK~zYQv+$V`0)2N8e+7yatVJGOZtYAzexFI?Z>1qUG2uki2_a7bn9 zy&2m31P*6wncz|^r8idgD`(*oK#)Zfgv};FtT@+>J5MzLti)|)RC0JT5cP5zQ{+ZL zt>s{BH`6z@sDgpAG|3Csv~=qBL%hDwBggfXqvAL+pO!Vm_0W2!bIq=!u^m$e&SG4E z#%eh4_JpS0#~|;~re)Nk+2HwzZ-ZtRuUbX1#1ayhNLdis#%X)ziO{VrBVIEc=T4z= zt=no96^d}{1wp5GH>^3Z&dq~ryug+1SamvVUoRB;LY#KgNaB~;z~@jqGlN`W7bz;Y zclZ$R+XHcmTwls9V$kle&)5UXqC314y#N=}*UjDCldQT%M!hh(USJX)v zec4FRw59-m;1gw+_B}H)Q7yil$@)_GpPS2pJU;5u=EWD8-(g2J z5pHr0`-|`F%3P8Sd9Cu4@pVW3GRfdi%2@Ls2gS(7FdU=z<25TE=RCdn04n%bopTnq zf5x-{ET(0;$1MD8CdlSj!)g|)Rv_9xA#43=CdpS5NtV2B`>kTm5Rm7Ll-GJW<8ke^ zUe4UwDMi-C=Q89M;&*4F{A?o13-O79FMno+= z?u?ZK=$ji=6f&(&?on=c_l~~!$TizX{Z?INl+6zX%O8RQx1>0osS)=s>a5&Vl-xEX zYLWo@)<<$vdX&+t87{!=8n`QFptOg3dlzL|BMN%2!jq{+u8xz?%7=5cM5kSK$9gpt zVDJ|Deol6b&d;GsQi;{?PHIN9mQEx4w&2Zg5i$TtDE*#VVJMp57RUm?6)}X&n9nE| zp{`{5A@lNEvy!GVJUcM7OjW~y_*iqn*gVhy3l-e2wE$28gRH(y;{{KliK3$PNT{4> znR5zc^4mP#Pv80Rl{qDrr)}yN!?PClW7Lij9`y+e{4Ny=3`x??pwPrbMo~jX3t0IB z;W-7HXzx5W7etwT>MMzQ{tdp=b3nma{u=s#97H8q<-rP}eS!4J`~c+|@GSY;JZa{{ z_a_tHp|M1IrBE8JE9l!nVQvZTlo5Nj*zFXd1g(~bFhcdUbl1e7VPVDtItuoJ6L~w} zSBziqK#zU_wV zKCv$YdFeW=zF?&K$IJg>#NN<_-@(WeJf!aob%5lIC6ll07^Ey)2rwuX3`oHKp2_@! zG0g*0nhDNg;@De6^6as74d%4uWnUErXK`^Hpj!T&BWrpbiw2zeBWcDfQvH`#Q^kIJ zz!NBzm9U-9<-NPaU*3AIss$XP+c%$t{+fy{`Odm~c@*z2@gqn)l;<~(f_-~} z{#0H5M_h#7I=KAdL8kOUc`xKD3%!y&epKIIz38k3c`|}Fjb<^&!5`Y4D&6qDl5ML!9 zmSNi1PidxH{GJTG$Dd4!j#D4LYU^Xs%CehKRe!6P#JNx}1g;SReOXIYWFH^~LQZ$% zyhlpz-0MTm;fFrFhBmO@L;RkKD+?Lc1U&dXEN!$BoP$`>jsy|k4$z69Ks;X;{mIt`!t$l=Dz#~%9}(zkbx!pk!I?(_2Dam zsok$Bfta;Szge*=@+H9(tQZE1)6(DNj$R62Wa>%8D*avV=#M7Nlzon``mCz|@2v(_-7@%ly^`9l2jd|XQ^mxz8OGyEOC=#OmCOTzgt z@kK8`cY34+mQE#_^d7{L)3>YU6W0Gcm!d0n(cre=Z2lhpg>3AE(2`UtJ8~3LX{MzKNzs zF4Xbp>UMHr3l8Os8xO<0lXOTvOd4w!byW&=d9vJXjHps_m0avdT>TA-pNb{}>Wf0a zKG?oNZP%4GUxc$tAZX#3NU=qD2t!jrFp~F}j6l{)3f&_<^~(cX`p_{i3w-)CV=*QQ z+93)e@XL;(;1EUr3JC2mM1J-uZUVmM4;<>)a8ln0R^1;6Ru_6z6ISH&LN7M`O#-$> z5{F+Cuzhn0fg@{vci(HzB-qJ8H z#52-nNEexGHv4upt%syM?f1#{;I}8S7g7L#ua3%l^%war^+2Nj8co)FS95}FIBjkd zpdSKAxbG#XtYGgCSkXK3HywiI3tUYyUj{J6>*MI{=K zeIdkBOcCBvozgVBy`pmI`oO4!6q{Ck0qEA`e5`j9bwVatkwnzPGYT)U{oR1WPW6_FnuZBZp&Yd`~FG4nB$HAh9KmK_6x1ZzYXPV}J1vO(pFC?bY-x(e7 z5eohWHRIRhrT^oXh#4)^SF7m*V(yXcPZ0B8Le0pl55na)&G0vf6(3Z|KpLsPf^*AA zLI!!Aits?WB~iXyRgvdhgntdO;!_%aR~UXptoThF{#jzh-y^eKh!uYaAt@t}bYv@A z5`1H;D2G&|8{U8D`_>RcNm^yOF=dyR9#>jDcBd9v`s! zuwAFoQF137LpLk&8;Tc^Km_5C*z{H zv(?pyEVnOkfnT%oh1iN#38yPPV)v86LrwevAPfMW zCuvumkG0fqkH?7f8hR>mLr2yx1Xs6*Otu@O5DG8A;q{9Ud>y3%-_E)lk6`*rekDFAMNV^~7LdfqIQei4jEA@pa_cxzJq6XIX_wKa7i6#R|SVX+KT zOwKDLgLcFj4Wwdi*S_29EAxeb>QgQ`lH8A|Dakr<1vV44N}Pz+P@JR_eqck~a^c}QshYWZ+p??cYo zBO2ZR z@Sc2i_uIW6=>eAF!UZun?&tIX)IlJV{NBj-?%**5Lkx%O4Ptpk4ow@_3V2CJJARw- z(apMYbv*R=k>Bsvlbc;hggtC5Se`w!hge}ktZcd~_15p)Jrzb&WSke@xWhEw&$pF8 zD(k#CTivCjr`x?AV^|*M+h8-Sf!w_0dQf^7>-T%|Y6Ja$zahYJSvXnT{Vb8kRp`p+ zdZID*jyZR7oN1-xxG9Yby+!SwxpS982&|sUknBzBIAQEShjwTeD(Mnot9gQDQbf<9 zi5=(+QIj#YZ=xbt_;_y`By`~bO)P4@$J|A;LpqKN9@-{2MOO9;&cg{stvK6LEh+6M z+B6s*4)p69&i6NN$IiGS-OOS*?}_b!i;g}H5Y4FeG|#%-dllRBx(#m{pf3}qG4e+AdcXC zkE|}USxp8T&kEF$Cu}!bljo6MH89JkJlzF-q>bSn-j~Ouaq`h0uHZaTA~ZMq#05T) zLrFK$TCvKYUk+gXN1V`A0a@9*8jca$iu9?SAls_E69T?LB*P`cA%k(EJ*~OiUIA@;5W5bF8}ZM9IZ+1-ULQ0!X71 z9CBW{>+;|?0gwCKu1UpS&EgURFVvp(ohEVwv)xn2jyma`aH-)EHCt-S7z_RDSmYwM zt;1$pLhDhjsi_%|ehZYdJP%f^Pj{c&7X{Q&H(c-L6}D3q7n99+TAwKvXlur5Eq#|| z=5@->C;wbtXnhr3fvQvMHm{wN zkDvGGo{ZC#vr8MbzxEgFOL{K z%y$cc7Nc&12JqHQHv4H;1g95bI_pekIU*cdyQ1Qd$Uk&SZ;Ten8rZC{gnkjW+6_|} zHAZhxzUCb}m^d{O<8x{5fmH9Q)$K%TdkbS%9I2K2#Nh6i%4!wCxnBY+@?XJ~zBAnTqp`R3ZuKm(qe zO|gQIOj08r?P9-DCKA}B+sqL)7Ohuz+$OTNu-i*=SM<|ZD>_Gy)ybKO{3z{pQ|t+- z+q=QMS3;ZLY$Lj>RG_0K%n?ss0bX(ozA!wr!+lP)dQz(-&5IJ>2r`TZjpB|p!8J^7 zhr5zFOFgkcaeM^wK$xo))i^Y?Lk`;Y>WL6kw`O|Xl6;nD9)2N&6`b=zuK6VEmDBCk zO!o=TIb^N&+70X^?rh*up!id*-re4)Zzr5AdE#bF)s_>@@OtlJ!WcD*+Em;@wdtA1 zkHwH?6NzlD5Dz3OU@s?J@cmf|R!zA{WsCwJ-phNVGd6`{LdS-VPXwXaP-vu=HMhY_ zu!5dE0yVYXzWm!&$rAqR2BS8R(l)KaM0M_hab9)fWUsH}v55Y7`G0;O^7;2~l>97$9g|oUPR`O~lJk0_x zT_(mI-oun9$;7oUC&-Y+_q-Uu#QKtrsQrPB=mQzicQeL*LPyknX2Q=qpBtZyBit+Z z+N;s=?8cD|0+r_omLG(X!t~v>!A*b8Oaz|rjhjgN88^|wO!O&iT6|_U`Sy;|=ivzA z1Wj zj6vJmd84W;#3P5Twv~f}wmFAKj%sLD*=o4CN)P+NVfzD&p36|UC)Bm=Nq!BLlCa*r z5IL}J(|fvhyd&7|ot|P;MFp71gcEu-IFIXZw4gdzxv%~8*0_01N_(&g88bBTI094; zd!(6@j0Ey28gA~@*tb&(k@%G-k0eYcO|G!%u-&eKCJfJzaoeBNwWc#s|)IF2X z8~c#^U>`Cc+(FB~^!X)CsKib&KDm8tfFEZOoC|?}tAc#;0$q@8<~^AaKU78zPZpvt zg5WQN$&0arY7ZvDrIa>!RY9*$e5_-6|ImI^PN}b~N2Ckh&V3Z#TuQYS_@DwStOQP3 z-ZcSlQCv`KdBRea`FP4FJ@SEL=!JM=8TvLYPRyS{CEsw&@g*V4vHw~rSPu1JBSPv`Og%iyZbF4`>3U7P7UE zr#!~){0EE zVNA$CEwPdTBI4W#F{Kw$=pRjnfBgGDUQ?>ynGAn|@ZHJ*FnXgHk86K|(H{}^QGNcN^!fV;+dvv|yfMWMR#&5y zHO_D~tTV0LXIc+>;}obju>(f!0a;&g5At;wnQuP73L&GhcQQ+BYgVD!fuPDUWT>e= zVLP1Q1Qy*FFm*BdY`)3&js`k;8}b5nzNotse7Gsvxo7rJVb#~%vcGsO}Nk0H%n`rV~H4Tb}1=+A&M_b z2>cRV#-MKqJD30omomZyIlE_gG9tn6o8^mn)q=1E8DWctWrz#20zgVpdO~Ll34g?C z%fHiAM|hNsd}G;*m_4_|31Kl`(s`HS*@FOnO|RAauc(*LKgvk=ht04p~O zUN)BkN5MhLTm*ik@0=%}-6#INMXUcl1K(HtdrMgDI|jbxN%|LQcb1&q`S;jw{CnR} z{;>`gyg#2mid0STbC=+k3S3;*eyRk3gecg9)B{-lc++Be3%9`G$Ewb+PvJh++J*PiAkEHD59D?0?t*syY0?z`lS|7&er7&n&wLjrtU0NPe+bZDT z_N_{O(K;{0gU2s*f8_D|0!_cdQh@T`q3O>P(VY06hIW|YI+J+C_h27FPeWzi{HOi1 zrHS;jhxS)h_cHbQlk)!kq;N_=vays>9cclQ)8H*B{DYSLRZ@7dC4ddj!^76wY?8rQ z-{Qeb*X2p1saq_l_IG0(aQ|rcN1^3TyUORP^>&_?K^?bBXV~;FC54fX#Ztfv_{CJg zLld3n7*q3OzbTqLt9kX(+;lv4uGQQ_TfC0;8@dCJqxrfs1koF63h8s^D$ z@K)l$(Pi4;NxyScsBYlBeLl{&wSgdzv#x+l613V&Heyfb`=PLpyZhZhS%Nm-PrPc83PfuZ%Gm%hcwZV00F3Xj;MT=N9>?C;X$ODZp9&M6v`=xc5bPj zO2LNT!RsT{p@(Z)<6_Gu9^Dkue4Qi5iC?zkAB8ehu0{_n5s6HO<63}*;`Kb-MO9sggSr^WsGX<# zEab)PJJX?hgiAie;ET20;R<|cg9gan*P)GM2?G2M(elqj8$b=t-{{$iQ7(RAHt8nB z{9vs;Dr;Zc;2t%O;NUVN@Tlh}^Wo##v>bC~7GOIilYdD-=F^&gY3zNu>SfIzSr1=` z&lOJJvmVk}pQ;1pY|oICaT0)OQ6Q2#4Ab?!J~e_=z7RYY53$y#m&U$@O5T%DyWRzB zc-O~fWpf8Q!RtUvQ2J<^>6z^dDYn+cWI zFZFsew#q7J8^@MSOo`%}JmhULTN1Tr*MzSHrTIbtYdEu_%EhrcUT?fRa6!U`zo|Ty zRY!9ng}?A6x%T2fjpIBUvi;VO=PR;@_>+u<&7&CGlCk zC}(M9$3}l9EC!Qc2^g5l+3em~4H?@B0Uca_Q{=&ij=3c*&D}Q@BwX73>T707j!~LE_`R+f>rZ+0&dYK>c z(R2d$rG8;ODy|*`%Kc)*dC2x2AY<-w!N*`k>>$9P;I%jNv3NDcfZ4bhTHvn1U&>1n zHo0(S^ymmGwAg<;{%6eR?Xm zSHttU4t=Rz8@G>UQI?E>5xW%_wgm6@dsNK)GJOg>=aYgB5GxUQ1*~?Ve_ooWiBHDe z_om${qJ59kSBlWt=23ilmp+ASQ+>%DYU zY%~s|4jhnqeI0BkCuc4DITL`=pIiQXqk-jA$hA%SRMqIN&`c08_I3i#cO6a-`Va)> z-Bc^I3~w=naxP^%;f&jPhj>NUqL459JZv3BS7ge3-$2{C=$CP(jIl3p{}ye1Nb&<* zF^5i(q805ME%3AMWi8<*$5A(q;92%0yV{8aJI8o|u(UJ;Kpq-dLbID<&qzc20&Lzy zTfv4YnG#m5Cr#z+;V?%C9I`VPuk%&8y`qZeA7}c8VC3b65aHSIhbuYU6}doGR}QPf z+b!D2L+-S zPk4SG)A+>g*T$nqZ}U>F^_kH0hkG8y|C|3#V4e90)Ec zTRBx8h0jX*`P$oVd~3%1XZPa~McFJH@>k}xcN^UDg9Cw{yL$0)t^>ys(}@#HJi1vV zjuzbd7$Sj4T3p|i=(OT|bg$hy(jzy{Emfa+Jx)-M$9Go@2I;Heo)ZHGgMdL_> z)vB|V{dKzFT!0e5v}Yh4K54CTM3o3V8g12JeZciq(@Y#tgM%(7y%b&S_{dwElZH`r zxnrDjap31Zp~=l%T9rz%PEzgid%}~=ER}!Oa*8os1+lZNhF*#`SW}~H$r&$||GPZGh3{dP_jq&H__97v!HyINj9s;K$hAJF%s7f~ z%UQ_PQe`75S>Jso%k#);wS|;==j0MHgo3+Pt>#RuBnV-T*a1b>JIAAr;^wTL(7VCL zoLqC*ERk47)Q9psB;v`%1N7xT>?wkEg3b3z5r&Y>zfdWTz%zKzT~X)7Sn}oQ(6*3lR9gBC?NF7$Yh1!z^*Jt%CciIdLtZmKq5h4-8LhRKi+n4H$1UDK1( zQ_T8ZmMbyk%fQj?%E|5Ug^E=81|svCO){r%gPj)+xjoCCsr{N=c#(Pf7@PTI7Z&DC zP8qp1@Yw`Oh&8-Q8?Q)>vUd7fr2lFeMwtjI;$i{;q!6KQ!nG-%@m)t+I z*$SrcUhc^{vz<+ed8Ggg0>CVg4Ci{f(^a&;;|TNjr~iYyl+P-4FV$yjwHYIPky+>r z?3;%S@ve=VZ()N+cKL_4L~oz}%W}h8&+XUtNVK(Xy7aB4izZUt{whcIt0!4W$nQPN z>!Yl-Ov9Vn!sCp<_}nk|)3ubO-WM0F8gq~G0$V1v*FcM+!&Yy-9Ya2{%s(_hdjI(o z@z&paZNu~@hzQ&ajx*ECmchv&<#Kmp;~3`CX%_e)nuRB<3}bmC#&jF0m2qNtN=QAM z!i(dnz{ztW-Hfy({jgpj7f$j6s!TE9)#-bibI66HHw4ON({etA`6j>SyML)GQ^u5qhocgC@~?EVo1q(eim0 z91255upP~vdC}BnN*bjOO$Ttd27L|W+7 zu-_c1z0Pa4hi5DB`2)uD9@g+B9vv^zPRV8C80wcp()+3xnY|(1IYKVYNMGE(W>zl@ z+6@65b7FA0l=iZsRQ)a8Tk5$KT-2u8Nf{vUaCf@f=88Y90{%VBQA|QqQ)^Bq$douyb zwE0dwEcEJB;rPzGS5|?=FjAdk{E4F(hmi$hi5LR!O1Pr#h z7_w2t@BrV9YJH`ywol^c?dd??6Ue^kpde@v5=#q$Pc3!ymz`x&QA5(kk%_rPWaMrx zXR#2S%|L|=lmq_UPIb2{Xw=`5Heb64CM#QS8DU#wwm$OI0m|rR=SD-q5>{VFBLeQ2 zjoMh)Wrpfqm^d^-T!IF#ocpp0w~}%rwt~BVB2MTa2c;-(#;Zbb!;YD!Ar?Z3y_Hpy z0X`pU5(Ya+AEvOiZ!ZVFMpdEPZYsg!%G2eEfccYJ$1QN~jg5X1;@?Py<{4D!1xmIQ zUW9kMh5c0wX*mL69=^-G`QqMXMZMnrXOf|pj|t!ciMw`Tbc{OgoBQE%KQzZ{uAsKD zrAT!Pl{>hJ%>9`t+sjBHf_d|Ki3G675FOC$xvY|B#b6eI1C%J#6(owzn{em!v3p<> zYR-gI9KoA)b1!**%13U?-r8Odk2Y+uIFUN)jc&VT(${yuL@WeRRDMqTxT71IpFU9T}U5c=s{azExgl+b1T`ya;xC8Fa zLnW_E1VPaNRqGjOQ{202(&RH;>RS$8RWtVonD-E^xy3iG*0wy$NG{~jfDwmuun#e* zx#ux(xy*+Xpu_uC^Wr99FIDJS9uEvpMB(lOU5hqbTsY8%Z6XeNG`H7ttL2QqX*N5^ zl)=i1r|hlzc2F`HrWOMc^uY$ipmDKO7GG7A>)Y_ilj+$mTN&FBuqJ(mOgue z4ttpA?mSvrRoc;Z8#>FM<_v~+7k2=UNf!|lQNg2VS6n8($4o?-?5k~D;t-~rJ+ifC zmx4^NtSi5@D|0)lqP3aM{#9R4gG_KXfl^Z#z0;O-JCRddS-~~J9n(awukd&=qZ@$? z)9F%VSKDr?W6c$ZAl~S{(k^8F7I*D9;xlp@Rn9)+*R4V@)c%AWLF|^GrCMZ#j9L31 z;n8BTe}G7=Qtzd@9%v~M4mk^+Igkc1+DVbS{Y6RtAKm>EJX&0{PvtrCIW5Nh`Pvygr;Zh$BIaUflZ} zz~?;Rj=O`p2Mi9`vw_Z-wlAxUm(VxTn5Tt`6Uvzf#I}v7!%%5+bk?+r=zFO=@cLEp z>1$3~H!Im_MtbkgCdP(^KbbXc2efAb;Ji3Bv*Q$~sj?x`N!|!&`dC!c&{MtnNf(qiEZlNAEAmy?{F|X9C)d*%^mqR-s*zpoTyff~?fEt#PB(hXlD8&}#(F^o?h6)k z3I}hpZMk}~pdHejoe1Z3G9uoTqYeT1>VLty` znE#=-#vfz;A9_jYcV5!l^E{ega=+*$efd1AeCD?&djC8NrvJ!OqMkh^VfBbkZ;d-uq*&2h0b-@abXM647NOjJqr?X zQT-iKA#o&l+4J_j4TAau8g)bnu@DQE~5GeqUnA-` zAM7a2clI9a6TJgU-zWiOqofK(V>WrT7dpJ#$pAxjbP=&L?CR|*{6gH^dL9CXIB1pF z8e3U$v|K+Z$qCXr)A2>V=@6hu&p>P$;@;HhJK11t(F&)i3|9LZU9)Cc6T}(7nrK;q zr@9U%H?M}?K9V@<@Af1V!?aP+!Rm-Z8yMa-ICa>@Y*ty5YvsM z?jc>|qQ;(^MXlOSJEo4D;ljISFeQ#{q!B?t2?!o=!QFJKQOnS^LTEj@FMBiEUX_l= z#Jf2hWMKK;xVOpsrtm9@xpO+sk0siq<$ap#>AdT@sl;jh94H4H=J->&Fqa76TU9g0 zqwepu`>8sFve<#WPl^Zi%G?66stHJlZy;5=5Itc~c|~w6#oG}zI?q{SAm+~8s6E!7 zt|PJ_frwQ?bwN=H)}=cMuWV)BV3n?_ReY`@XQPKQnw?>TtKrt4*VGtmn$h4c!Eze2 zFq6{wxfD{YaGCN@6fPue&bqWaLS`~nR8(QQ4WbwOf?N}|DzM^-fQAe^#)D4RZleH~ zRu_Usl&?-Gh%?CHqP1@z@pOrjO%zvXm?=3T!=%@Yd*P-X4$F!P`;g^A!{^!H zQ0hu()$p|-?b?Lf&9=GZfULlwc((Yfr|KetlKY+_yk_Ir{1dVBq~MOA4pXYQ6^EqM znpJdzU7Y6Oz`Lmu!Q3j%-q!bnU?lhcwpC81Yhvc@c(k%%;eeV<9K_>E^0L@~H?v|{ z=W*=Qs!T??YMJ7U?0P$!J$~m;JT-3akzj+luSrm7=$qv9^xsTF`498N=zn9Ad(-}h za%qS1p8bues{BPhqgl0=(l1c=&D*8O)z{VDbz}$(I$yk2ewB%1?`x+1LjYAr2Z?_r z4x?qRSzeZmxzn8dR5qhwizZXGyp zAsZnDz4bp8n70JAN6kPmymSC}R{{dQMXfYlLgfS^h=Zi*;Oq=1jF`juM=dJKFZ4jZ z5vOP+PUgElA3QT`jkWSvV8+;clu2mtsulR{cw(Urmh~uCc>Iv(Y}2p%7fZ_^qQ^<4cC#i*|W7 zC^NB@jR-u_^7i%U_~qI{ACy^pXqW%)e5^;+&olD7^YI{Jj~V&h`FJ8;XXKCOV7n8D2B1YQnP7A=M z0+S+>(vDNA7=wkld-`5o^0#bWml+}l{ZPKKw#neRUpdrf)tZ~aKI*z0#m%mS;`0?# z)Oh(7v4+Xa^9cN)3o_#jx=2zbLvd>HW}VhX7jM?-%{ZO#^)QY>+lQ^o+$y*&pO~+< zuKm2SItMv4pI6os@lI7L!=|$gh9^CmByFa}d$Dzo%7HZn$^;z?tBLp=C%ZdvwRL?S z?m*8Y3-Ahdg-#Q$`#?f-@CABAayH>}$rXuQ=OTr-kGX(q1+; zo1HW_i;bdsln8bEcr-*^5FzJ8#4db>F`~aIYLXAtl%5&l8oNh}Y_}>zU8=S|V{8M> z%P8oYS$kC>R`xJ)>~0{&(hKLRP^}u-8h6=nx*NT!j7d?*UQ6DMfD&Z}Iu%)7l_wXh zl^#3;6369gT$Hs z)4yxXUGb!R+Vku}Hcm$#HD$uS@2WIuccU9 z;(I;5@74I6L3uqKpCI!8{o=+Cf`dQR8+@p6m($N2>8~I?SRLu|)!P1%z8<`|$FFeP zzQ1qP+J3(Cy@uc~D+tmAyrw*T&=4Gt##+?JN@RtiQ--{u{z#|_0sd!3kq#mhP9do*K8=fYgC z*OWlioLC%bc$^JyTXW`cS^V#TyQm3MT+ui+C!&oe3NPH@qTdZ;r>amXCzL8OH;%v9*s6brUZs`n~S@wF!4Yune4$)SXZxFz6j z@nQ=uCTdn4<_v+eD&y#Pnt;m)e{lDERlpx<0=~E3B$l+ym7^~dDkVd?f$Thp6K6ru zvOy>(apPGhYzUh`*Dy53soPr2R;9iU2Qe7B-rSL9w7vHo2n=&j@q?F zj)#84*aW*M6BHRj$OY1-Ocn>S)K+;F!o1_6J6oJ_?ZECRU+_6|HT5i|my;b9Xmn>N zI;{L?x}iuUhFBa59tBSuYT4bg&nu2MZ(R4Pw~ee{k=c}$ery!sZh8fJ3VA99kHy}n zLIh8-j>Nh99zzx2Jnq8g>E9c*=(=>q!-bje*hq<_*{B6#S6m=yX59Usv)}MvFu5qw=VpoW*zs0Fv^ z+%!tN$N2zP2g^jlF`9Rc?XX@0p{+733j+psB!*vZSDwQ{`xu=sGEXyUa|kqAPz``i zn-j3@6g2QHZm+jOcN}2^i(PTU^=yzl5n}7|krgsg?i#)0n1t42SLBr0^?vMP4l1D7 zizE%X5ZQdC4?M9fm&#EDoDFTiWR{scjIp{uH098qKnNLDfu1{pl?;S=$6X#DYw?tG zE-f=$?_`fa?Q3nVf&__EDQ;U?*%L;4m<`dJST<2^f&8ZOBJukusg+3%5Bgf=TkV|W9_2#@218hLbTM$_N@&bYl z0?|-M1o@$}vG$5$#Qlgo5gTc{U_qeg2Q>A`i`WU}jb?Qo6N`DRnk%X6o!4(EiEfcV zF7G%G!6+Z!Ufqd>Z2n?C49h_5U`J?&94pUbqV4iTLd`5Au*&7~y%NpWcR%j^6n%Wi zfB&I4pbIx#XW{H|sw8=@798BVMk2|bf5{F(aeE?0l{E9H&9FV|7|>P+B=LNi!hGn? z;&uS#!?{gRpuV=k1<_5fk9PL?BusgKWo&WVF8e~_OUU5{JvvWAEAa|1|eJ~X5&JC=M?QWe=5 z@=O&5u+`gTW(?|ZofZ-#86i?mc64-GnONNVR6g1J$=VvCR!b`93&%EGoTFv8jJ4Mr z=#_&9iLaM!djq$0B_2fc0>}G&Dn)mK7Z4}XNZS%h3@K%vi-I87ERm-c5)l&~8eX|wF?!Mk~0=uiRR_r%8W^TBl`78KL?$}#hGMi|CUeY2l7 z?(LYm-JP?x(LoYC)`tRCO?TVMx739O5bhB=lcj^l5<;YzNAWfK=l`GX^ zf&JWfYH$Ydb6i9;*y+pbF?+%rp8glz!HUcik3gOM{ptU-lE(Yng(`~$xP!}U^YrF> zw@L$_o%=`ZQMXT)LcW&9KaPDS*Y?gUl;_=}+_ZScP=1X!ny(eLCz1>hrFY=`wQJ#< zuK41Q7*>(bi!YOFFy5}*;33Pkf-~`w%~`H!SiUtZ)ELx(3>^f>ULrTP$wDp>IA530 zFBIwv7NES8g05~CV`aIa8Q%)dsZlOo9_irUR;{j}*gik_noY^R>U)?AIZ1&gcWn#!ud@o?FcSBK|Ys(iY3l@-P#EyHiA@~HqmDqB)!vQzTDKL4^C z|H3a=pZ}%3!*Biq|GC}ZyI=6@*`!o&;ai(8cFQxs1-1BKSBb?NUPUR^z>=VCY0w7& zwFuJ~#ty=nuA8Hw@;qEihZMXkS4(JIo+L7KvCetL?i*Si8eKm!_6E{K%-20j+#@^q zu8I*4MF5kzkt)D3$|I^Zqch*PJm!^FK7`h?m+Hl8p7u_}7E?9Ah~5pc17J|SGpTvH zgNC}@>-DZj_-7fwagIR+I^DZa97<`|Fz38lEpZ=KwQX0DUX2*HZF_DuJJ!rbjnh?qg(6 zvyOtSs?z`jw0xOOe?y`}geq6M{o){gEy91!5IqAdc|DH-C1rP1u}?Zs-Oi$E41*jO zQ!6K?ui7SZ0BVb93sphROqtt({zS~wsR2Q~2|Sb(xP~rE%xR}=cfFVkS5;6|rh3*< zkrWcx4SaQN#Y0}Tkg_Tq*xuE{X=9iznKJt%!09xj4zq?K^L|s! zb7qT;=pF!Y#BXO#73vK4e&e6O*n~8Fu2Fu>uY-r!DWrFaLLQ-n|yd${;gz6g&;)rH(z|8|nqQtfY zT^#mTAf}K@mk75!Cdb_*wNL+!;MjbVf%NX}j*ze~j0K_CN{Y^^!#`SE{WpX9+aqIy zy4>2!{eS$8yW9)!HvNykQMuPNe}i8dTVbE99itnJ_QG-2$Ps_y*w(o%7(IWxPL3b0 zKN=58FUDoG82P2^xlXUT1gq+~yEfT=>9qFn%oZK1CVD%=N90I{q2xp}$=XKX z)iSwX4XD9m94udulQr{_sbng6Z&?n6?s(3N&-7oPohFNT(Q`;&2I*YRe8!7M6=L&N zg-8*b@~ifsL*x%15iir(wIGUk-YuFYFheLfHk5?t!|mO5{=@Zetu4g`iOt2ko5<%3 zoyDPj*xzJTh7>;3c6&A&=^P6#Hu^235=XMT%{j`Crt81I^lJcZRS3=%i1oU?Y#K13mltU#mJxALXOXkbD6iRe`bX( zmi8xW>}N~txw+-9F0!xI*rP@6Z>_S|MfUX7GW*3wyCu3ig3GTUsC;0Q(kezo5|`kj;#8#Un9xm_>Go1%BHvz!EqZ#AQxEp&`12GZQ4ti zW|`v$Ta*etm*5ccY(0M*1fAs{E2J%4V_`w>OB#tsH7-dA`e`$zS75{;!XL;!((N3s zKrPY62*|lc^TZ_B*b;pNo`_Vku9uq_$C`hP`-n}ubbAQyUW5+Z{n)_1W9!T7gmFe1 zHhk7mSsc{Zs2fNOmrBt*5u6X!pq(+|fftpSxJ7K}gZT^%d3SdOHVs)G%=du8Bq?Ip zE>HnPK%88(DO^5a+3csb9C_ICU$vOi&)4z~jAHX=jN-2wr1@)u^t%uL57>y32M)$c zSfmJ6_997m@o+rsTrDeFjT^2PsSDxxNPB>AD$nJZdP%FmCQ5}dPf~!WT!NBldv5L3 z(O&lz8Q}L_TV1MUNA0k=bj#ZI?o4Y00v3z8?7K8>h<0#_${GdBxZUjcoh)As+Fl#i zi}w@)Z#S+d%1yv}PH@z zzuc>CKqiU{dA{-ag*?Iod@Mi`Q-*##Us9akQbMI|j|!~CHM3LN127_4(n6Nwm`Ne& zvv#7w(>_bjJ&iZu)Bn1j4|m0@lA9#c=qK-!l;4#ei_CaflUa8QR`v@jtgZ8Q3mA3& zguWAdlMTvL^8Y!w>5zZ=*7BaF*TsK5eJhdHmMQ+KMEZ-T_@Ko8r=H{WF&0Mf+LSUS z;5Ec1(;6(#FKPz!$7^3dgZ7gE*oW|!yc^f|Z6LF92vA4OfaRs9+=lROs$fGrTHsR9 z4P?-GG!|-V7hNe~pD3r@#j#`*ZY1r-hJAKFI@Do%2&GK4%O~QZ#<$KmHSvcKtCHF=UaWD!$hc`#Ca`el zJD{#O`@C0`(`>bE72;qtv+N1p;dwtdumIJK4_D6qiQuj5B>*NHGWo-%SU;?&dm=gk zIqo-s*)>4dFsG9*X;Dc4izK zye`6C3JnD)%rTHnzlsF0E6BZXrBspKHqI@~ifumDsyDZ!~lr~3-DbjmH#Z}a}K#LztrM1Cq zn>|a(hO-?2&6$9o<-4iEWy>9r~iA$5p{uD zqNXbMBrzH?cA7QE?CHNyWl@IZ-xe?RZ>P(>Pl8MHxBKIui~n@cm4(#1EiTaHfnW96 zR*E7p(5k#vx~tslSr{4mIcb$70-1!ASX%Z#9~9cirG)bmHyyg?M7c*SXsmx0?b?6h<-C?M{+eL$k~K&SC;e5OKOvP> zu+HNiGHTO%&xR(#H8E`QR}Pu1mkZ=^FTM>;va}CFy~|;bQi69aY2%j@cZL`SXVu7i zAIf_o9(u;?H(lIsb$-mM?=rlr^Fw7rnAiUlBZH1sIb3E&TDj-9h?))x??facnaRQz zrDP#89vutfe3khN(r@0!s^vTICd@XgU$H!~CzCJ!M>Ob#W~A7dP$SaZdu)y;B=g+-;D$nQaHc=Ts@#V&T`d+?+48 zt3MI0_oT}7ejuVkty#~()2Y6U^rM4A|I%oCu_o-twQ0TU>QF4tK>yJh z9>w|?(Nq3T-DI2A-H+Lcm>1qKULC@gr=q%PDWBjVHT zoyzz34HmLG*#ij1+*96w?qFgQ>=mXE(!9@j&r>~i+J=G$OK0dlE7JT#=q=cf9a#^U z3=-3b=Pl$7(e6f7Ho)fz174m1A25_Ht+Cp> zn3W4M#pVP&H@g!%?+*=KUI^sw5f&!^yDpNB)ihNMDL2s36b^NhtX*bkG-J0QB#NH7 zN2c0dh(WQ?XLuURVRPv>ZMlW~>E27JI7d6m;UEt^pZf%9)v-pN{=F&G1h<7IYW136 zSPe4@`nEk+O657L8t~Kq^MXh(E$Kgl68fJ7O4=Yqub|Yv$YFic1O3<6-Y(w(le!wb z>GCsR+I$U6zW^(lObM^B;=df8NJxoUZQRpm9XgG`rg zcyEU57)~esJlQIK)9|f8QyY3vu!WKB`m12J?Bs6%p$0kBPtdbiSrsiKr-c$%`d)Ii zu)dwo%{G4FI*voOK--|8aOc;9 z8F}j;5eEmv{t#YP;g?VCogC&h4|*r_(Qw=m+mgO;iv2wxtSfC5U16Tv61=V|^Fv-$ z{%#{b=RVU#r0fVNUXXe{uv=((g7xbsk9_Kf&3Nu%|GEc%23p_sH2o&_C zoUZ{J%gsjJ0!J}t^8!7fWzFu57SO;aVq2HnrV8xQIrMUERO#{30~dhWNx4+lD!6TH z#Xa%+uB7jWBw@K8-KNUcCy5hisYSXfIBm&Il%Hn85^6aE1WmilS$qeCb2;3){<^=) z8y4FE#+jopn=`^pcjm(0rgx}!|_Me3_<{-Q|Kco4o;rwdP|Me?#{=gaSfivCDa0d8o zoXKTMe#M!iaEHGV$OQRqj2Lw_QK*yUgL0L zqp4U>+ds;{!MO)Oc{t`V$WJXo5+R;!Sbk1xncSsao_Z~#7L9+xg=WVR`tmEFBaF`6 z9@=b##+D$!fSWrG6PZmJ$3usKnQ^yJl-HV`V2C#K+j)1obImE~ZyD(m`zInZ^PuTk zW7j8F9re3@dnN|LyMlRpVe}px2MrR#gLOU{?e0~|7v&GKz&}FvFY=tvdU@;xO7MMH z=3XeR8AfF@OT=XgeVqQHz*ch}$5qRE`0l`pBBM7~W*9Chft7R}9R&ye%9UB{>pN%W zZPzeA!ak^P3hjl-Vc#sqX~sWC+L!Q`(`+FC1%V zT6e;M1+f=8k=Rt6GzWxcNwF9x2^M-#yGUAuRw7SoTq7zad0uw97#hW}uTC}Tcy@5S zSIFXgRHu>b=YjJ!x;t~AhPiIfqy`l0ga}|}wVVt4fayD&^ z!or?XG5~Vg-<%g8jpPHPQ#A`RFmyg>J1e9X&%)jjED*(UL0%}V9QeI4GlGbFTdh>q zeh6dDj(d6&3Bp($%K}VW!)Z(~*EeXN$ct02S?nO8W)N<7fV16SO;E^d^ArF?JEg(x zX&aKl&5%1d-`w1L6t{Mo6NqN%M$N1J`ZK16Ptzf6c3^hKyjV1Yn4$^CQ zeXPN^i`zSN@~Iibm&1FBr4CiVD}-;e)v}P6 zf6HssG18{(LCTy?ph!wGoZ)B)TZJ~KcRiCNW~v^4d<72;j$>)9U~!+Du>P#u`^P4& zS+1GScAECM{}xjff;hjIUiEj9(%uF-o4A<^SkV1YIr$yqhdKfS)}nTKhOdN zo`jrYtO7WG?g{p7Lq`jNY^3yCb@4iamVEww>(3JyxjUV9=0K z)T5X0^WR_HFQs(Wj-K{&i=UsB8@#^zXXyO=_W#VwUyRH{ckJ7|QyOE_a6SYe*)Gqt z7{BG;)Ao&4$i_k+R`nCwT-??8IU8eA9#@06tAA#ntzEqSc&Ghrqb+XYpV(?&?Xuf&}e|591^&b!N^V_>^A%tJ7$6wS753K62xCKq6Mg1XG&0||u3(`}2;3QvC z-oQkT0;&=)6))+PC`)bkD7DI}@L&T3QW)o0JFNxqvBR)|!-eg^bZZNTl?9fS**d3p z?W(2NLLJ!MU||MF3*+R$n-aUwZoJ#x2+f$_+j?x!q62wv=5lXdF#+%Aeq-<0)5a~S z&cj3=U2r(PcOKI2g zZ+(qnnjG=^ohRa}ZfzxoE}-E!6#bJn)A0cyx0fw(<=UEv-r<8OG=66wGQWC zKGwxy?dAK(kj+Di976lhcxR=2ZfEn25DUxd^(pjS343V@#q<7M1+d@k;AK@wS=Q+Z z8VQWhDk-sDb~5-7zdT6Hxat|n zM)(&Pxzc`Opo!;5=esDU{6aC;7>l;JK){BOx#vbZ2TjjpPI%WkNq*m{m?k1ecz2lcT$8SKJFwqN?F@GX`lW-dj9|QIBmUBF01za9VJcDgic0z6lW0`_c8TI zcUcKB5j+!41O^6*434x@g#!=F>u!^I*QGRcu&+i#LuowfIW2V@l8nLl90_s4uy zfGE)V-ke*uSlUnhn_qcKzvANiJgWu!0yeHbwYtjJhDeXKxV}kw?UOJ`Gl6`3E{zdm zr~J`hdbVz}=z=Gi) ze%%_i(;7DU{`YySgf>>tFjM_M!Gm&RS#G7rk|A)jpVT1#8}ByjTjhNbn5&ccGP zLLYGWDiW~RsCQ2|_}Ytu#5sdY6J1G7ky!qC7T1v%9`)l{oJyLCq|#|Vi~t{E2X5Y; z>(NK_?aAI|GE+$H<-koE-tDAiWeYR$h{?X|WjYgN94l`#>BGt#Q14dOmrfK%5{y2{ z2WWA7ZSg>IQ-UnsQ^8f%wL)S-+yH)_-7$k_Q}}$edQD2o$mjbis56uR<4D~hN`&*# zRayJGDD;7&A>QT#Zx5$LB2EnS%^zXXe~>@&r(K9| zv`TuVRS#MNj-+IQ=J-=9{cQmH^8oL^PydRU|6?Qn9N6J^>4Nmo@fW-X;R~8q!~BTBJm*jU3Wxa-gIRgSM-KDt?>Dc1|AA))!y3B5 ze8IJecHj#5C& z4!VukHo|E?4W}n!=KU?17ybFfn_ym}rDDtgQb4W0UXO^L0~IGV1h*Xu_m{-Z&UO); zQgJ&8C%GsFHQwHFc5RdB6cX;tEq?9h?s%*coSVwPLE@d$A5_ePW>=?t9Hw+*E|K9@ z?uW{%Ethx9@!;~Z_C$y@Aojxz7!&NRdpXIhD@^}X$z$A`G2dbsm-FHrbTM9(chO%k zJtR@;(a=(RsgPZGw$ZF>6p4QDt;cs3w}jB^?1@8O8 z!k0|^S9$nitG{OA%+K%KMF~$l0VHBreQ}65N zdmDB28lKnL3(xQ0?V{zCYpF~E=+k| z!>uaI$^KxGYcp8qhFa3)=E|Ym;Z%?UPSn=qZP`xeN+D-sbK z#mly=07y)gE*1u5A-mj{soGG(!Qe!{o)K~`n;JMt#lh$5;z&0$Yk`)|9`hqFBzbl{ zr8v6PC$ZQ({ac*Mn{9#%evO+Mvum4^`S*7W9eHNx%S_FGkq5th|9kQ`{bl+%{S)cq z8b1b=kM!};i12asFVM$7N8RMVOx?a3;b+9F9)Eg=E+e{tOX`d zXO0lJCfUS4BVH#o1F&wwhbFD1+oeZg^Rx<^6l^0aKO|mXf9vs`H{$gtD9ikpDa#K= z_zj?)KLYKaH^=fPkbAxQPk^ZD9}iLTKLw)JP|EB4{__Ebg{`1)t>sBB{h>SGK}kLf zK)6_#y15S5%b<>R(cLRY4U;TDBd5J4KC7yAJUIN(+>;dOhG#AgHxFovqdth7bfUF7j6zGuDVK5m*P3i0CqQWrn2lc?|J8*9)R1X>4 z^z?6c1QB-K&9vI=$m}Inw}`(#{mUD`5bw@_So%Nh4CsSo%I7m6i~NQ2i8pnR1u%-w z0HwY7&HNvQ#eUgsevxSY6VYSsA9D`$LBZsE7}mZx9rzBSW}Qg&E~^?NP-HiXl(WN-pMl4S4|h^VRh0nfh6j{VnNqCfKoZ|i68>QP7ZR}ME9)BkJb!p#8Tvt~&kWK;la2vQAN1g39OYyon( z2ty3&P#WlV^Wl2l0w<8a<@s4*1Vf6bwhJL_r__)riAWk1P>bU#)KV9_L+xShi##18 z7U#EDFXr@$Nowg(jWvScSKBO?cid~Vtn)xO9`=Xf;;B^XlfX&C36F*@o5S)97tW;< z5O=YvcQ;uvzsWI23p5Av z_-ZMS4)NW$zI}&^ALl?pwcN?kzq_jc+S@!nP7eM!BKfm(kTt&a#}Ub&orAo_8-E;; z{Mk9kVqty~k^I>?$P@9ih~$^?$OnCRMxKRC{3Igzvv}mYvxgrhUhAn|P$he_V&90S_K9fHge`qK@p+D# zelhk3qWON*w?$X7AXZ*S_K9I@ngyQH3oWkm^^MYXnLYGXFK17!$yDZP(Y2^`x`EGYd%WPJy{V~~$N_)NT(ycS| zg17Y(F*x|;@p4uw2sx+h9cYg?c)Bk{(UYG3I}iJpHc|<3ub@zF@>>VnnhMGb%dY58 zgn8PZxc7AR?LcIis_Czi!e@`1_|XS{iAT-d$i+`LA=B`6Gw< zB?fZb5rpb^F12gDCZQXryX{NBZQEXXBDj6Wk;ws;hvDR#vF(IX5G7V3Elk8S$eaL$ z)Rsmb;d(o?z_WVf0W6Pi7mp?*pEsWftCprz=Um|G;#hEH&XKSJFtsOx7h;pzz%PjDheKem0p4?6~q4oaTkY zz&VJMXB#Or;fD<5^S8czM~wd>>P;)^{UyYiR>b)pFXVp;UdaCpyqMnb;)Au9|B=@I z|A+jP5*L`jTUJzg120K4UM|Bz*9H;~7v3zVGzJlr>5`6E0XIdwd^vcxu8v|?2zRmoH)sqM9_%dBNw5C931 zSbPi212NCQ4g-mKAP8dm^}1k0WJX3tzNxCL7m@J{j{yRAILF&kfA(Z%>=cw5NrCXC~7W5>tChmR{ z9H*abDJq!X{h;}8yV!1bZQJ(0?tnH(GL8mce zjh_6~kOJASwOkqgdWiXz`cxqO64;*!5&Y+u{gjAKScS}(G^PNv#g~Za-9gsg(j*zV z^jASSM^B%rcqZ*hg!fJX>PNWukX0}h7~0?aCQ)mBCi;xuteuPOqNGjuIZ=NqG5oxvg>|bk?c>pnh*x)t(zjk$6&jh|*`*J*fPR#$SYx~XGKEUVdL6& z&axy90l-zFLp8S27-f6UkYGT^*X^d`(ouBsR7oygsR`CDg7SV(nD8mt=`P@8;(VYH zI?XB+J}VSMEhZ-SDRbSh`xjh(Y?giGCHW(R6mdz_!P9rm9OG}P(?0>%yJ8o)EaR>O zZz0G>R>8PLtE&C6G8Wb0iASi=yIIC|ey-BzdV|YJRwkB7z`8pH*UOAm`-~QmD!v0k zkEac?%`9vm@EiP45l{x-^{wOR&6wRzxkPCP*KK;Drv{o#e4LA676bo28vdbD@c!lB zA^|Je299*KC_VazLJUdX+%{9MS#VJH6n4wzlcZ15|T8 zF#VK)_q_U+e|Pya2dHb|{NJk~c+bBB8}V<}5WI8cd*1f1YY1duU45$|fWCPfRekC;r5JL1!h$S%jRcXbP6ZK9y8{JtLm|j^oMi60ARiygaDWQ@G<4&ZtGoW z*hi{^-R#?3nG)qJ%e|C9Y;_By@5jRY8d23xpUZR&5dt2Y(d)izZTdwf1;>M1|_ zt3PYw-;af@X!VGw1pLhl{>#6*u_SH;#Z!mRb zL-h5y^E(v(9Y_c3;R{gzE13R*|J4k=&vEJ3x%ev?dY|Lc&$d>7DMRmbTzc(E{MRz{ zKF6h>d53=~L+>*ZyrJAeaG!m2CH3}S%nk8btNJx^~H{hYD4XLA3qX6*fp)!*U-TJkyiU^x=}|L z22W3$z4{0cL5CJa?;QfYCZ^}Q#qI?^NOE|W^jdV%;9i~-a_D4rX_@BIAj|As+KFx^ z-%wL}GD*oX@7pE2ZXNk_WZTr<@Gt+^wXAbY1ld)h!g)}j^M`||I}jcpTD}nv%eg)+T&vOZjaMS=WE&Wr$lGKZZJOu9xT@$ z?20J@zP{nfbx`1(7u_vIZv$=1B!AMDGYWNQY5g-csG*xTiBsv=QIyDRMxMH!m< zAi;-a>j7jcvwdOHc%i6Kkj9f|N{>Q-&MqWusO+v5U~EGCZ0lT&)3G8FFm>zT19T*& zgvkelTGcMw3el4=;5*o!Zr)>X`#6VBNPMt@ycd}hXXi(_RH&N`QLrr+$Lqz8rgM7q z9A~w^9Fp{|xBhx!LVGpvwTtL2kpW}WeFPuVEF(Wg z4`4kp8)ovOeeCDlE>38F<{-)u1KZG%iq67jN+x8l$kn`Q<2DN)Xf$01=ThTFq%`*y z@XQo4hf;?pu4vjU0fSKhU?s{+xPGM@GE~==iGl4pZSIU^6D?Y z+95m+tZJPepIHm?a}BJB`)c!S92_Sko;!jc>w_gl;yFo7(~X!IVeL+^iZVUz4zQy* zz3~k~|AX~e@~nXJEb{>N$q0N%2>ec0A$%ELc@_Mh9N63=;Md36iz!6BBwib?uXlI$ ziRTg{_}<(D{v`Oem4_cbyZWiX=2>m=9(*r&D!}vz2L4a^-0k4n;AaFJA=aH|f8H+< zC!}lYpZ=-~O1|FV`MnGShp?Z5J1swpLlMLJEEljxd0>ma;NNC3p&jPasm3~6uX80m zybAiUb06P};bmunJA?(xUPe?2>apDGw|TR@N<91cb`lhLV-hI%`(J$i{0qMRMdPt_ z%d^Da!K(EWBW22O_%gimTTCJ+_Lyl*+8+@(8ukG)UHD)DwuIS zz2HX@GjFwg#1|L}AD&Y`9;J9Uwssb4XXJbsihHCocBJW0F*JOl!#e(zqw z)x%3&&Xg`&?d;e1B9n=&PREDRa_+&XkSXE!g56aSTTvS~;@nF=>KB=E+=$)5!P+II z(xgLnpDY3JQsUlCzGM%2HKz~scp%tG5Nbn(YPDM^GD9D&&o|e7xwVw-rIU)A<^YUh zc<+#L%Kge!DbJOUTg9GLfpSX7sDtr>1bInTxB7Y@xpTCgGizrZB{xIsPLng`{PMpV zwT5i8fBfU+fBS65{Dz3YZ2x<7_+9CZq9@_`nScJYR)}xSZJ#G3=;m9W4nixw%FG>g z^GAnYE1>BG|Ka54hYkH*d-S`)sbtdShtDv=n)APVo)0C!{C!op%hyAB4VVS|?oHut zdA2Kfz4kO8K90)SmF>a1}Ez5 zbtARs_^ufJREPNbaBp?qUBo@xOX)$=yG@>EUce3L26&_gSD04HoSQ?;1M`T3Q=8z# zV=yFWf6lIh-Gx)OBa?f-y%d|{*s=GCYIl;rGTSuX24=8D(UrE_rII#YQ4Z1eQQkX_ zYv8f>>+P+tEWYp0EZ!ZlI2lhDbKoJQxXRm&eAnrd5yiU@xawUOkwL>8Zk9vs6SsQU zAyHBn)!>+%e~d05d^;W|)1%(FLMCCZG6Y9` zv&4D?P()HL>|FX*uQgW}4i!y1#&>$lU3S-y-tfg1a$WcZFYbyCdp8~usQZrHU9w0h zBR>!+O~q;v3z^$&Ae(M;ap(SU*zTD75H5z*q5Ae%wcG4vjb|OKapKYBfX$(f5T#hr zy23;iSWgO0%h^3)HL;1$(Y3#qjz18v5Kr}}p{d7bz!+6~w%MN5fD0-8ZphXS(vk22 z1OJen`OY^v`2=(EoW^rAq2vxmFK#$1)E4z9mqZjWo7+W6sbh0zboW*sRio*g&8e9k zF1@LoGZ&tDLDO1Vz@IvGCS%D>my*jlg~GOWQHy)Qn>o^tsi$3z6Jr$fOvq<2doD&X zoMN#JFI_7gNoLh&fIYI@M4rMPxf5YZA8xMp15oB{ACC*mMcE?nAV`~Kt;`64cfbH# z1n7EHW-i|-_t^PnIA2&zlahI2u8W@7x zOy1&`JyK$_SY75+-TAsY?JS`r$J8hDM#k~9iJ#=sD9DHeu9_jmT=^VaUq8B_B?^%dZ zdsOn!gJ$rmq#O>geF#0$7gYO>Ux$5pBrRt%VBk5X7)fN=(eh4A?M71p@tYR!@6;4_ z>>%F+sc^Yw1JxCJ!_B+k0YQ-HRbHM1>A36)vL^$=_2^>Ubnp~`POF~xp0?8@}CjL|-F}Y|y3uPlH zGNr&Hf-{}IZ_OKL^2oW3ZMNL38}_on z1HoT*Tt&?#vOO}+#j?5=oI}xrtz`Mxw3`-ZRO+BT(A`nj(SnIl z8}~*s?p(1kF)|28U{eiFN-u1M7tM|__2vZP|MNWy`TDZb|Okla0p?nq(ZG|L4` z8jf4$Dm+-;yrvhh9{Q`#uzO!Zs#@6_LUGbfTUS!cP4SJG3k|TdAEiJh=-xVNw&9ee z)1uc?ZJy5OI0sl^qO?LEmrEI#*9@&Top4_=WdkR{2^d6zIjbReY$NL~*{2I5@+S#> z#0W{ZkKz_*R1Xjjr(H=s9YY@=~x@KhzuD1U3Zr!%2l||r+-H| zcmL|*CIrv<63;o{#{>@C8cd+^5cO~z?44-sSNEgL8BYR+XW53OGzUd_&R&44zdG>1 zQT)OUX93IA@*3cCbr{&szYZcWzu?&U3sr0FH7H*s;#bG?3^99-46O%zJR?KvbA*ol zSP#4RYgHTnF%-Z%zX}D&b;x3w=e#P_i-`QJ#KgsKM`Ca4#6JWJz+BRRd9~$uJZ4xe zwI1xskNph3S7P7#FU0()*AnaCzxV0JstSD}n7V2E$uW(my2@e#GiT@L$>q>z}>fQ@lHA!Mz5_-sNtrh!901&A;hVj_zGg(-3zlyoYPiXaanv|3 z@Xd3#L@8^cN{3X~t#N}`e!+WmPjxt3=H-@Jk{Y0$G7^yZIdoG zwzGm6VG-5@8RqI;^3heB`@|KqQv_;6bY>>>$6N22QK)GV-5rXPk;2DLkJCHmCnR*B zJlkv7O0TkpyXul~xUW({?jzYcqnFoe$Xqj1KI`O&!e`1D3FBN z=_l>Dn1)nc-Sf-8?hQMYj)Ko_ga^FDZl546-8cVu`LECZHCaFZMmAOTsIw^Ov!?!j z;KSgb!X&LG>B-0>Z=rw@Gj$7C1jbtnQSvGHJqZh5zGt>@ zp>Iuc$~B_9HWldCvkLSBWUhd#ugR2ebq&&}tm?LuHiIAZwXWgEYhcpzub=MEKHD=E z`Q_97>e-&TBOi>pr)G;bp~u!&d+FG8 z<7qBWFF1DHKF$xSJ%nxOljoqSPIo2qTT63%pC0p5YwW1N4w6wY4j1@DAiTHFos=63lKIFEs*qb9h zG9d$a&b6`(~UCt&cjp@jESaH~JZ0h*8 zEdV0T-tl(`y4eKMf_EYXq5dNY7$D9T*| z#qw=CJT?`AY^Txg9RTQ@O74(G_wY(R7(_Uj2TR1Dn|I1L7xn;i0ir4-eFf&Xm1Fz* zjLF@Q+vaH#RAi%RW@$|F&acMfP>k&3o*mBt%@%Ewdd1x_Hu5wcpBfP{-Gnz8`yIqqizeR= zunFNyFvQVMaKjqr+NN-Ygg3xpt@UWh719r}T#BBE_h)+VbXc?W5|t3lyC3&!9oMpm-hZl;5+Z0D=%va>*w~k-%=^Q zZjdWF0yp=nKep)hWL-Ag>2!>l4bo9^Xq><=6D)oND+^{!a{%92RxFRayQ`EX*s~8k z+RJv=?icEItlW8j9Ms9KZ@e&ju%v3`Xt2-<(N$Z3O zOnO~rsl3m-M|yYlt=N(RbmvLM_01iT5aJ7di}j0`+ZuUQ@N%n3%wdlr%vP38bTl_L z$qz9@Ki~`9DzSHXkYZPNTEWmW)C4%s12C+IO0*YLnx<?OqbJbG`By9!mW-Jrg1kgLC!h>?lL>lY2B0nEi2ZP1aQ}KYd@Q-SV$h-Xk?JRJn3b&Kybilf z?FqKAe8J{%=U&j85*mD-al~Z$+av1qSBL{|kx%xVhEjX*ck*@!T>?`aE#MlkrZGL} z`n`dfkIhvwucJONc;C}f>zI2F-lqs`E5O%S7^EhMH1B*tP!}=?6z`AUgQEbhn zw%jxodzj1vbg}h)opc=Dp&6uumq)At0J7bYH~uo&Rc2kTMTwm_yGw8T$AYLguh}!# zc!8KUfjn|N$%}1CuySk#KDZz=KkdgCc#$$|7DHIs`&;$kJ?gEu5kg~34h7Ilp7ftK9cQy@J=Z9*GPm`Rpr&-5rA~LgT~~2*gQ`35qLm5v$D-*4bH@ZCglts3YJrOv3gz5gE^k-hTTUh zPGlP`x4DJyhD_rU(usQAS`Zp|K!eg9Ywc`9VSq+v+mh5t?G=Wc_BpJgS<@byXwNaz z5l-F2CJrv!Lwa%mt0SlKTl#pxPqjCP*@YTwb=eVl(+lb?w)Bo*@g%Y@TzyqGjezH> z*A)A>Ns*KEH&DyCkvE4oO*bF^5^52_(p*ud=4~6g$A~}FP3Db9L9Tb0*F(86U`tj4 zU-EW$9>IOMQm)v^xZ2ZWx8qQ4}#A2Sb}G7py~P1}2iLM|WDva|9WAT%ZaKGTi)-a@u>9ZuHBxMvu_>0C2B3 zTo&Nfm-6E4{&YFgz+pKYAvZE6x?j%9A$iog9<0T^M>)>-T?6-Ebc;(K)c%6JzV}?*a5D$~{&`_4dtCrEwv1viwVD)&7KZD}zw4WioF_XIncx^;|9S zpPl2-a{V1X6NT)DWO$?So=`Wz&-ZzjsC@NC-x3J5_Y}dLo+UF{)AARYD%bOZX>C9Q zR`oMkpN6F+4gStJJ_UTg9LYaBkT3XG9qN9S=vT>S*wUFp5_nd{G5v5~PXOs@aD)*( z+>yrPL_c+UJ0-*Ir65$!-yGu;)-e=uzR0$<5y=a$vBPdBLz#DV$!R>oz}4+-#DWkt z7e_D7FHC*O&qj1LOf)gcfZy;<7G1G$rdbF0oF@;~ulYuDv3-kVM_2;0nYx#Z+R&rE z+2iOUV>%lJB+ND0fxK8k+QPyQXsEW-;vEsPQA5-FDQ2+5x^Gs*Z$>Mhv+J zJ=h*I@BOlPO!WCkDvx*?NdUoJIOQ1yoK>XT@Y~x7rE|!i`x~v}U_}|^jy90TY7o%b&u5_l^UDt<#F;3Iz;a@`~^P>VFE2CA&S_2L;c!WCOm_)T@CRf z_6Wr$%LIwp3mdSix83kyLTw2-SUF5HG_OWY8>5)CI*^VF1qcvcKirB+xyq1UZ!eG}nJVhZzno@q%O;cEs-CVO8 z4^abkjvTPNk3LS1A>9^{HHIg)jdE4mmXO2DqaIx1&Gzn4q`W!pNd6#ZM9VLw2QPQoEeorwPCrhV)s8pJO2^qe}6d6XRwYO@$ ze|nS4{{fom^O|}!Dc|$H?{jH>9Y>@-K{E}u!9v>-A-zMhWYaqJQISp3p^^QTXQ+RE z){h4X-?OwKiL!t#2TT$(e$Uc=y|&=bYad7DJ@xAEkITA|ejJ#;0<@Euu9bTNXM5Au ztpZY{X#5h3F<)DOgm+>hFx!#Um0I4*of}=0ji-%E+8j5-pkxS?Pj`9k64q{nHr^&$ zNz|JZyK{r75pl4cdtX7>Gzsb8#xZcd*DfqoS$I1iswfIh507n>8VX);XsGp$9>k?~ zh8;H?5?8gh;tkWh+=@jNb@Sw)(nzDLh>VdZd4>Zz5j74(EUq^cF+pM*Si&Da?QkK|)WDPrnx<+VKyLc?z<7ie~9gxIc3Xl1o$*#j#oiJeZ++n?rRt2Ak2P=*!y<|H_GbJ<5y1HIt6CFAsd z!75F*E|K>*G(n2xokfNVG_awh+PP*!#rc@InReNm8~?tSWg5raV_g!v)62gEnw&j^ zf+0+}yAWsW&SK@uKdq#pkhJkm7}P2!^%G%ez)$;j5GAk6cw#8rc(*`F_z8xp2HEC2 zFEH$yh=YHt&!^QQ!=|r8s?(ReAW3-hzPG4#Z%Mi~((hseWB=SuCL}Y@8tixU2Og7T zu8yceNvO2ErryeLSCv&P1YG+wFA0$34_@<+Q2ylyobOl>@eFcaP86HQ*Zgt`rQ?-H zi<{w!m64~+gRAKUYgLtYXBM+lssn3pMpNM6w)=xB4Sr1#*O7fl4Z^7hEiX#&fI%R_ z!D-purIUkkZ30u~MQk~BAv0F5xz@>6o z^i$<*mC;AVvnv$n*_0lt!1rJrSYqNmcd#=y58TkbEqXJK9!bR|c{4H2+L`%yoI+@? zO&ZZ{Z<}2!G)B^KYItJIbH3u?O}#~Oc6Qg3wnyO+@g-KmfCeAsrc(%oLKB@lP3CNt zCaAo?2}F|~Vh>3If8Bs_Fk6*%l&Vbnem7A!|L&e`Lo>&I9u60{Qg+{aE|T(Uk>t`3 z6)r2we+x!@+2A?3jPG?X1WJKDY?8!oHL)8;ON`>RmG_tcl>0%KX-(hwR3YkptNibQ z3x^iLymoIRR)ZTUp>QXlz~GNRSIra@erA&A<{F=SM!yk>^(~d9d#&IF4qmke<5pKS ztgL5xO6=&*=HzRAc)U7@tIG(LG*8hFDcb|iNFKobM8$mV(jXc837DWiDp!KP^lM=V zjc+M51kW<_M@7r`JA=9S#l4?*&VOA8op|(HN-kj6gTy7HaVFS?HVQ2VpZS?xe~AEZ z6(m1lz>h5Y#-aCyyd`Jet3Ed5e!)W$9iG)Z)yFZ`9zJ_tb+pRpO@-_Hx{5S{)JDF+ z)n|q4f~G#-|O)63-e+}>b~xaMr!Hg?KG*%rp?l&zczaFP#^s}>WhESrSyLz3|jFb4;j z+^?&ME`G-;BfF+rRSH8hIwZmgxr>reW7&goFDY;>1k)vT;?W%GJ&v)+sgQ}YbFQO4 zKVEUSl3cvDi4<^jR!<5VbLX7P_+=W*f*&e=n}}2{Q&)e<$h1;tjo!oz49(s>!Z(&T z+KSiHGlzpHqQ48sdGHKQ~1-=r#7bB z%8XkIatc>v-CKBPLIcKuSKsk-mR!lOGQpRSWRtP8=KEMXG z4Gis?c-YIo5NDYZW5DN#coShPvsSn4|Flf`Hwrg#vgz9243_-vn(?gpY0=x(@;y>c z5)x_K+Qcll{jN<#m2w(#S^_-i&R?7JzR4^=@hY~nHiI{ROuDjdUExV6(P_rgEP04( zJh7mddb)1z8dZaWcZ$|@aW7>sj`N|WLvz~@x2g}mJWp7g=@!e6QGMI~=xQb=UfR|N z6)=t-QPXoK;2=$DfLO_=z6Q0utdR7U+_IJ^eyX{#Ei=P=p4JJQW+(uo<5QW8(_V9R z(KR3UwZ6vMNjdY3y?wj3{$7dy)0nT-0e^qgx%XnWHDYH4td-dID)pv=VVA8nvKVvm z5LNeyH}UJc>&UX7AX8V^rWu$c%q6~ ze*oivYTa}QCJN!^v!(l0`(x))P7eTk9NV^b%sXAS-tRK!Ngev zz>I>n&WO&?&}zQ7Jy*2WbrzIG!@#%76gKRQklK30RrrydEryN&b-8t#i@Gybt3rv$ABW(JrRM)f1vfBc0fK6P_R*L?7(RJW)FF5ut z^dRSJNaLJHXZ(?7(>G~`RkGm)|6(@{>nakh@4IJRL2q&lx7Vr`>nSn$sma+Vp%eTE zDNT*|mHYM-8n0zP!n5Yy43W1AP_m^T*} zW^x9GXO5jnR3Rp1U?F~YPHqWz-J!{bVaP->0^sPCfkgW7#_bc1-z|#`m_*a+s^8j_ zWpluVb?$ex4*i=^Vp6{ohhlJB9&*MmMwL>7>!qc4SbHJ&GCT!W3{eq6R;BIk z0S0{* zp{ai^D%&=7TVMlM0E-Q#Lbfc!T6?S9wI4D2Jq2++!1ymbZ9ipgo+Q+)B(%T%L_&fH zNNDj&!MO_AFG;A&|FcPG-DvM51l|;o5Nxlqje1qi(M|))R70lLcTng}GXlK3{ylSW zJys$K=Bai>eIu&fng<{+{9ZPR;01r9s<*2WBAaROSElcKw~ODSs^253bsYYfs=gqq zZ>s@p_`kUx-j~B)JD|Gf71OE$c7y1nS9rVbz60yQ3O-RjeQFTLNW0U_JXH24POFX2?XNEakrwlCGM$B>{F{s zbD+&-c$dtD#HuqcW*hs#9t3t%XQ!sxbdG%hBPpJ?XQV=I;^uL0Liu*{7!mIj$6O#X8~6AsQse2a!rY2de25Gw#H63s4FCOGQ-&_rhC`#b0#{E zM_}F^E5;5-ZL=)(Wzu`0RLqX2X9#UkO{_&fzdE<(D4&tii#*|i@Az}pSvM@phAlZ( z;4P*;N(kC577r&LUDJj4!wp8o4SL>o80p1K)^0^BDO?oAYT7z=H zyXmgMwQdxXOJVXs2Sb2_W3}7f_M=3inxKzCXUjf3Yq$a&EjpfbqOscJwP{?&#k2L8 zP4P|h$TdACS#_&aBVy(qhfYg4Uk{oTF6z*B6i0Ns(T?%6KOP0Hz4NmdU%3!UiWk%} zE-CEA_NjiT6$Lr<1BLq`*YZJJrrW-84%hLtcF;8`yp zSm%5G$dZyqjW--dks;``^hYqhTnm$}pOnz<9M7%%ytEB1Qd3p_b z?FYFQ6@1Uru%9(q&tV59pl#YDq4=)81J-LN8T4y#<$1J{eioSf8}I-A_Umz;^;4Sv z?)+5apCI2LJ$SGvv z{ucL}4KXpuBg@n~zT57BQ6agJR3*&eZi@MNq&bB>Qmwtu2igfQFh$HJsUjPzyYzCn zS!A5MQFcXKPTBrSWw8tZyx|WsU@s)EDxSEzhs@v>Qh-}ADVAz3WIfqP3td2Ww$z7f z!7fhL`^9`xZ&NyGHy%*HJ-@0K^hD5eOqOU@#NlJwKusR>a=I@Uh&>b#v3Ih`OyfeM zu849+C0M8>$H30_89alPhTj?`gvFSI-lv=~j(xB7|6J+MX>XN>uK~3)GrO}Fo`sYu zZHSXGhbdcFgzhpU(b`+{7Y&oB`?w0;HI&1y;(y;n57%(u&nA$8**3lJp9WRow^0qQ zTPfH>`VK%uxsna=Dk*$=r<-$*H$TwL$7;aQ`Y2M#{veY_5x-N8pihOmt4+4@FT?f> zHE!)FhZ(Mq1$sPV5ikk1+wd~OPDVZ$$z~nXV;@(#oqM;D?g#kx7!|F@a3bfz91m4d zHNsvlkQ3V`O?EHJ>;)(3<93!3>)s@edx;2?INPXYLHB*%4$%fmJq)&d6jSo#z|e^? z4UbxxZZhQwII&4q1$BW;v*_+y^7hQ(?1|Gf|1uFsxeSj&pw1J%zcLjH9~X5GAy~+@ zVZP_|UCcesO|=IYwR1y9F-h`teni&5-=+g5xb=|>6o*Wr^d@Y9g-3R_!;m(&#g80w zIbL-Q^|D7W`-bs?oo`Y`sT)D4;VVD)96VX^&ADAH4Z2zWf!M^_(XDagI^lCo9FFxh zt#0@~{_*mE|42njm|vp)BN5sEQAD)=Kt!=bd<$t?Ul5V%Rc-0*v{w>(>f%C&Uy)G1 z7v(Fv{E~#s|8x@imO}wRnE8r?OhmZ(jtspC{-YALrqj`TfHd=Kf-(K=noQw~TsC9k z|0JaY=;Fg{@at^Y)N6BwH~n74LYR9Drp0smlBeZws?6`+RAVysQ?u;(gf;`_6V}#b zJSax6IaugtH;y+oLhqxFCm4qrg`@Qx=&j~#E#Li+4M5S8{snAcSy0*eYDfSo{gC4P zy9v(H;g*=2yg``#EvIqYxAZ!T)EAtamaEb}$bh17qoU)JaZ)xNied*VKf<_a{`(Gq$~LlOCDFBg8-S`Vrg%x4sgoc8w`# z-X4>Xi;cBMuu2?TMGI2V%rRGwY?-TuIX!S|0urj;* z1i6@DaE(qJHYA9f1-A#6*48=~uR7I4w~$2lxq2+}exarJy_?UfAFNr(11@61h!|?_ zc7D|k_PsH^`)Q7oRe5&3!=LzU{-v>{wR!8r2E)TcS0YzHJsm`tK^CavP zRLlo1GhXGZNH4q&x9#DP1CLHNT?iuGWj+T*3f(aF$SkC|v&Pd6k;C3k>u6en3(lPP zCLo*1P|Hp*tlkQ}duX}qNspAB?GoA?3t3UCd!6_A!Pr}(i>ZBZi{Tk#f^(UkS;mN&2m{A2B4k=A(x`U`=T6B#Uj927RG03W zbye^inFdvO>F&QZ*JlawrHfvjpf9vy0c?LSy*<+tN@z1dd8@c@1)!#aQ=WOsLkoIF z5UWO+Oi8msMU(g%0Dg=9RG-ha(=>DhVE)LhkL(}746h}CQvA1c>5>Xq;1O5$yecaG zv!A_|()w#F0OK`1?#WWanum38{;2Nwvra5<1!!PgOQiMgB3v|U?KZ}&Ur_-<>^*}p z*FIu~YyDxO?d6kfOW^z&Aa!^q4Gs!u<$dvq^f%4&+zMAL%&S_gST)t1--ZmpN|2_sitEBpu(%gt%0C{N6~(x# z-UD=g)Go6LX=~uj7zYJ&zCqXMstPs* zt-WEZ7FRn-N-t={?rPGz8+*Cg!>&e4{HSwsa0@;9kofsd6p~w{ojJ($vuzYSN}Nq@ zFZj`u57dFT`dG{Edva9qDX^TPcZf_q`qkwW9JBHgBKjeBD10+IOdgJix2xNSZc`HA zd#Kf-pvZ;!dM=ThCGp!Rr`mD0ZFH)6jj{Zu-(Gy>oa@2v-r_P6NjAyYG1i>H6uK{X z*HBSO`DcteDl%Bv*2cJ-dBYs#$0KUTeTLzvvMXx>4Jpk^`TR)L^ z7l@Z|Z*_sdT#Re8_cv~_*0nh`lLPW;+c4xj=4>}giNMg?gathY;| zPx-g!+CA&=@h?|28GQh7K##xg>_P2e$> zCq78;cy<-G$3m3?NK>Zcq@uiiqHDPmS`Vs2A>fJ-B?_FGi%oR7+FK-xd@v3zgbQjg z3A*J*VgrqV$P15Qpv2RBAhFXn-u3L&Yz%=vIQcR%WVJO)GKoWN!Nn#44j&xnDyOU5 ziOyYkbn4|4b9H_{$x?=!m}Wh;qim_y!+kT6?xBvuD2L(*JB}`2-8C9m(k{7Rm*N27 zk;T}@x>nB(gLrvMKt8cNFd_+1Z?{)~L)E$Qw5@S!j>G-2kdh)c-DK@?Lusc{xK~FD zo2Ilp3K-`FQ!)o^=A=r;=Mw zdIOcg(K3r0dHl!A|NDVo*|G_P@*B-&N&U{Q(Em(!#YgTv<8g|)H!+}O<)VN7@Zneb z!QTRr)0171KVnzxYj*vU+z58q{+%0L9hnBNV(#Fjn^gks`={Szlzvq7Fuh5p=>0RC zxI{l*%ZXKs;)^HyE36Of%rAKVue{l}?o220q)gn z?Z-ilf(@pOTPLn|3mb?P!#AWRUMFJ%cI(ZJkK6kTt_e^X-5#*IoW0Ev_*C4PMoov? z*zTd3PD(iMo1If7h{bEukiohnYT9la4vLYfYzw@G3*PQ-aeiaX^G@8Nkva{JyPx0Z z8+-^XH!l;+#vWlGU)u6O2~Gh8!i{v!z7{@$BerT$VARwfw!KIr#^Xdb?jf$PnmIFC zI(CBSfd2;E-b;OQ7dKNUOgu#0Tr@hBWJlke^aWp_RBJC=V{2usJseUZ31@P}K z`u?)(w^h3J>|Lojwki#@NBH`UllRw1zHcrIPs*YtfoC|g=j+*p^6l0_&X5Zm5Dl4_ zmndh#@v_aR$ZRp2n?i)DCdd1O5LNc#2(jRKSn7iH(9N1}Cp>xY8kJExHmgD+1>}$& z55~~W1tGbyUdbZ3sSX zri2ib3ia+irwb!g&Xfq&H78$9elY-}oAM5@>m}00dh<{oj03~_G@6{IP(#rqG+CdL z>#z{|98>Tky6?R+Kb;%yT9QW3==9~@Bk}g--*B&CoL1R15x3nak>Ve8&T#!W_VEBS z0`;^8KL4A1>BS~0egpGSX4mWz|Mov#g$*?P|9hiJo)bQyHV;r+e+$j8@itzo?ZR`3 zGTMFAt!hlJK+Bj0D2v<^gJtewxNFmx71~R!ugZO?j}(c4_>h>>fkY(lAvfJ-nKd5Y z#+;vqb=d_DQ-sKUTGNTYrEDnH_q&@;309oEifBVINvvOV^PL#IR9MTC>8rBB{Qooe zW<8E8+nU&`oxc6h?rzvX_puNBX2`ZEk<ska;`oq(ne+?ui2G(pAZkdb1$CaBl{Vhi9wvu~nmNkULce-jx08zkv>KChxFNJFC+=%j0 z-V2GCnpR0Kr)_wg`R#lNg!R0^(0xd`$=&wTI}27X_IAA$E}ar@jbyC&J*;`?!B(KY zp$&Dut_y|bFcrn4fxFw3M66b~T|a7+2Q-O(t|4V?=?etjW}&Y(u~2s{w{wfQVjU@P z$ALQ6XD#%00&2`RD_yEo1oOC5sk~t0f>LfjP|8<~g8u>^B*}TR z)L1NU^G6praJ4v9Ea6<;v%1Mo;CPFyhM%IsTzap3I_WG;J>nPulRVBE&by`3e?8s6 zpdWlXOnq>72==50PgZgPyf@z!M!?NW9`$0$czspO=n_E*L#{ z+Qz5N8GO#YX-pnHZonPjxuueTrxpG-1d`?iBp;fB*%ja#)nrv=A5@4T?!Ltn}bmz!0HKc z@f0rm;jJ^yS6W{BD=qJ%zGx1uXRHG3(P8Q01ID1?KQzwi1fG3g?pnGQl1nd2$9BP( z?%@60m#0ANBTEsV-|V`jJ0BQ5{gAc*K^g;C%}X#H5+7z0rvz)SZ629m&%1xpJdbry zK3C&ldE=y>)}ETZsqFbsXLmwxO>A(jzDt3Ac_Z3Vy_^WPaE2H2idTAJzvg^$f0dJY zEoTX7o>ki+kX{(@z`k>Rj48TIclDqwTCWsGu}^Q-2xHf~qv*qwqdcu~7ZG(TG_x4g zsN;HweKcqEDo}QZ5b;U8y?ZfLuYeVN!bX1R)evFe<^6b<#scY^q8)ixm`P+W@HVKo3;tQ0&rmw>>~& zA#0bCS7+hcSVg=$#D^reb%?Z z226k8QhN9X6)3O$gO;c~qcLXhZckJ6CwP_rt6<@;Kn4CIR``@-`~k223SWIi2LJN@ zu<_{wqT>a3T0dy;F-7?5Yorz${KWejg-C(TFR0klYx{_thiqt}C6HC9pg!rmpRxax z2rVEjfg}#e&;A*pSHNj{)h#SIzr#ac6!|WskA1K`l|Yv~bdI1_BaOZJXHEu?Bb!As zKV*6zSzYZ|>fp_vv(7&}{a5G-Sc#m5zd!He(|%+Yu#>nnLVtCAmUr>}3jNjfS(+Mrze2xUpWo{U$Y=t&?}Q^6M2%t*TgeBA zfovk)>>)j+NG}6c#GJ!G=34k29Bp*d8v)__61r)0numi}ylx6=FGt;6&9OVL8sT~w z#N!wTZmHQv$^iB@9IFAsAG^aI&4ra>O1HINoN-eU^sdg$3$e!$T-GmsORX=%U5)US zR#q~x?uxMqio*+LCFHvCGAERWEl(n*q-?kN$*RaYNtBy(KYN>1Q5-{Kzcd4yZ-^}J z+FdrUfu3}BJdkBlo$kA4I<5CuuEXjDy$ejOG%_B?7|=1RdWx{T+Z%iZAm_Hd*`>HY zts+(4D#Dg(!7N0g_l4oiGUzHLDs_jnT$j&VX%jH0*@((6@Ss(lW&7GLxI_}*4;=x^ z`H>e=x8FomFnWmreIEf*K6#X0uuqH^EI^FdCx4TOgKLKB5!iJ@Yewj*#{HygRHEAT zf@q+{!~0>^0ImNhb$gdt?gQs7sBt0H@)vvX3z7sjp@!2xP?=df3reK{YO>EjBb%+a z^wze;!CSiZ0jn4M4gS9avbf&(%yKRklmu|a18Y6896*T0v5l-#x1czHUcMXGqX(dC zb;T2R0*yZhTeIY?r}$o;1b#OKNGW&e>nj$^OMq5W|AAtDVAIDF{upZlYz6)v-e%=5 zRon0xphmG?{s!ytn5U2cbXLA0$7S`R><5~DT^E2$sn_oBl#Z; zIshFFe({dQ(?3Q_%N?(%@eT7O;4PQnEdvyPuE5Lynmp}3&SpVN;wSvHaGU(VSWoN; zW_8w0TsCqQ*K_3)dVK7t2Y&qP=n-6F0PcJyzb!TZeR$sa;hifRTnD4wF5`IZ2)XQw zPpJ6~GcPv48}2;0wR5rQLt;|HJfF9B+XHgh>S37I7v z*|AN{<7cCF5PZE`5lJCH?nR!u$`HJh{DMK3@<86HRyoLFgUXDlqfCq^9wyFnbi7RQ z=G?Nf!rR+iJ4$<`&GKZo63>E0iJO}NaHkTTal)E2x~?q^4v1qz_(c{myOU7|0jHFZ ziO@Y|x+JWSU6zFPs=tR=zv~>??C(o&#ZBX%P6>X75#12N8`ry)%pZdatAL0>ce{wg z##LD#hfPbldj-{!lG`Ygl4?X4x!lYDdmy?Zv?QS#T1u`2shItdmXv}OB;i0L%uOF{ zx5_qa-(z3?&o>N+_ia{20RFwAK>P;^bpMC(PA&ujZ(#4heL#DrUAJH-e$>&#uXOZ- zVnv&mmdo0^bMfa|ZRO!yEWULv{!^|&ogIZAuEFVpYw%?5fIxsg!PM+~H>&?yu?-Ze zj!nYFSVDiJa5=x#Y=djq`?WuMdB5+L`?&htC%4@1EI$}i{ATt&NEklU2hifO=9$9b zi9+0WiQcjtb{VS!;s|R-y~!rM-tU3U8R6m>#K@g{!4mh<*P_m4X@s)niX=)?C=PhN zr4_VVZMAB>@oh%&Mk?l{BHtFbTc+aiLFZ(1eaq|@EaO%zhFs%XW+}N%7-Hq4?R4BZ zsd83RopWW2Pk1wH#Pqf)X`T0_HDRjGUh!`YGX&t+@9ET2KEsU#Z2VRC)RK6+5PkeQ zfc0l!1FYmXaN~Et#sYSQ?;KZu8Ek+jd;>Rr2W$Xk_6^+l9kB6mng0Ygeg|wUu;6F7 z@jGB+acKPvH+~0fJY0i6!HwSm8xMEXPjKURz{U&q9o+aGumRrYH*n*3z{WEv{wv)0 z9kB88E!_ASY|uD{>He)6?b|w$uH;FYb>uwK5P)c{oXkrx0Rmerbp|?;Ow4QG)OkCm z;2Ve9*5xhB%AH!6&knG{u1+?+IAGo#SOYi z!$5`B7e>F_%A>i@y`(BrlLN0_;YPx)Bu;zzFJjhRZU7c|vrrqXM;??D`|^LhDR1gt z-#+MW;)m|`|A*1tB8g3J+T&k2=*QDlI~|t@aR65d_exgIQo?%_>`M&n*|k`I=6H{XPHt>2GBek{|lJ`w-eQk$^daB@}lbG$8p$PLvny-HoDCkU%DP!F+e4Oal3S zZA{$u8z;&;U-+N6lm5tAvdF1FcPD*umVj}wZ_bk2Cuh|uV( z`4{{v_+@dgIaeZaC~UWyMxUlRcCrSCATL{YeYlQ;Z#&zu@|P1XRlKye?Ia>uGR+%0 zcbU#uH=!cW#ZH7>3$Dn)glB1-Ny9Gun`tGx5F;t>!1bw0ykJ|CEhvp=po|ofM)R6@ zha|Qadbd~;OJmhK&iPoNU3}faQ9t#8qi=CiV9z>#3%SvH!N5+qDrX^#chD=Vy95xx zVN4Rv|0DOpn>*qy&F0M^@qLOXzTk=i`}BXLq7hh5fiWb>SyZn%V;NwB7!c5^=I)^v zKCS1>Mw7{S2N^9Ko!X8J&8^Y7_UvtlqTcGK;r)0pw8}rwE8Aa85pG`bS79f6PV$ ztM|kYZ$YVcac?Zh1K^Xd!Kdk46!sv)eWk*!fOKjX-HA62cza(%RlnAPp956@3B84? zz;8Zr%8%J7^k*#bL3dufC3u!0$$}|2ioQ7;ih&v|kY)@0g$GV}Xg#|>J}4-d2RZ<< z;=LQ>vg((gx;`#fe+pTB3RqF1Vt}y%iBp61$`)j{tb>v~(ybZcJ>m0wNjkH~L}$54A%d?F)F(b~-HJRM>wXKyP)ksMjN!u&qoOz_!XQaj zGVvQt?bh1VL#9Au@i~{6kP!AA(V{ND3r&CJa+rSEXUNOnPW2|mtq7Y$(y5hz5YGv9 z(FJO=q#6BZTlI?iJrfsu*Z;&fb(KE|{egmcRvV_1V{JwNurHx~UaI+?Rd<>)vah_J zKqqvLQJlJfz&|#8)0fcl(yKFAD4h%8&c9OcE_(iaO10&|EER(NdQQRadXOjnw@(c} zpWH9Q%`C0&qx)1ly4fRWo+V*FU1OxD>j1*UMn;f@MXDMQATTQNe z_L7F?j`TMTB9-8rM0+Le$|kcr)e+LQz?@^z%w8~gxJ`Er*4Lq$zcT~40HwI&Lf?LV zXDhoa1i+dRj^QLmQSYn)i=SdaDD)rl2D}IU1y`;v5bYq!xfIY0#WbMTF6Ok^`HnA; z&KqyQAP`_@?XbEcBKmB7NPI2EK4O-=iO+8`=LL(jCyk9og#i3KsYWU6 zTO_g|rKP(OWlD;G)7yBjr87ztxL<{}m~VOXd=knHa=z*{){oHD=g?cQ=2FC+?K=}= z>I*jbA&cC}Eg2rxP+M%9+`4LfMQo2ia;K&*p^z0Du`RCZShpQbytVOWN8>o9xc(`al{wfNh_(#G~l$XCr zt39R5dV7ZN#8|3_q^i!7uaIYd{Kw0GqN~4?UKmdAy}XuUfBH*Xg{9p52O|kb-O~ro zP`8IfrB7EE$RZU!521~Rg8hVW8 zbu3I!95(?mK4dkX0Q*%@{!$+OD9cZtW%B4Ymzs^zOC~*DHN+?=!&a*=)jeVwcf=?{?>%O#mxUHXX+q(_uUC0z@9y zSgvFyPC9MH7$ajGHF(X02VoU)t%vOaj-8GTw`Wcnv#0v|4%pLUn4ZqtRU>XLa60++ zmVmd78o>n}es^5W=vm>Xr!rHF(1v!FIVABP6+MI&aOL zuv)0x)3BQ+=@x4u_hUA6H9QZXslLXM`dZ;Un$1eTx7~dkWjFZ z_1>(n?1~Dd#-?|R&F91%8&te9F zN*n{gYYtoP`1<`9?Cp-qimtBy{ptCmv$KS#|NQj)(b;+U9)CJLV0U5PwOsfEEkU07 z!=C5lnmJIdm*U7Ig*$A!80HfmL4v-WerOvtL8C8z5R)s{8`WS`HxZ6*oVv2b2r{A{ZSd!TQ1p_Ddgg5dpnssXE37m3jue!-M0sv&^WHEdY_fcN zR6R9Hu=JefK$E0%xs`#7|fFfQtJ=LegxouDoF{ zb6)&{9Rw-ecDmzdoqmy0~ovrdpX2`oeW)Hi%> z?_+iDpg0{=-}L5Sb0-P1DOhwS@N4qQ=g-m{P$f+A`u!K|+dForwx+AmBY%c4XC1q| z$0<(Y;<#L@SuL}53ETjNLgg37*58Ipv$h|g@jZMEFyqqr@5eplBXs$f0NAfo$g(GY z!E}jPp^Ri96kI*iqgyYQXDe)c%n3Yw6V;V{J#*c#WE1V#XcofA>oPLk(e@s?Zsanc z6*J8S1a*3>F6GrU!`i7}4!N-^c|kVr*p{V-5Z!@UM7KV>uMOyg>|M$rp*1l!x@xZL z?K$K#TgR|Aq0>#ZQX}NZZ+T$(xUd0P**zjB{tRT)9av|z*BlJQ?O-#yPD7F?N>?3Z zeIN3uW!p7+jIUuD30>~*H-Wn}QG((k!8ouH!Ra=&vx5}C2qBH9CYs_n@A^EsT0FKE60=aYglaOKY zg|U=7@vdbg#zqwW6y`q-q7VD&il2bWb(ac|D?Tqu>|a89;m~`Os6K;Yrwc*g%<2!c z(=#a6emPS>t=_@@eQhheu$AH0d=83J?vvD8d&MPMcv6=Dt*zXjysys_NIuUJw6HlJ zJC?db?$?l-u@v=xP^TJBxi;1d+I2j?JCtyGp^r3k_9W^$@n;6b6F`2c;?f=lRp(hK z7Cc|s;wX6X0Xz+-jX(-_aRw&R96-|3Ye^u`=LKANFUA+@|PTnOo(!fm9Y{m^+!*c%wVG=RSs zs(xkXSORW|TAp)YuIUE!#}38E6JAZd&((rdak~0e-Yd-Wtvz_K9+k?^fZVs4%bx;r zdq+($LW(A;Ryi$>q=rH}tGE$9JBacog-KX%g%#LQn*&dcH|7e!7Bw`LLH4mAT= zTsZEe*0OtPpBo-SMXL%8&~|SR>@>1FF&lzu15g-K?`IYnP3pr--Rlg}g z7D(32!z-aP0BEA_U7dv&Exlfrjz`Lyeq&6a9kt~3`#`^acZWygU8CXc{QY_Scp6`@ zf95=XJdH2dKXV>Gp2kNN|8JkikEaow?hohjp+{fq{V7EO@)iu@mwWTcnFEX&m3?qd zC+cTe_(2r@I)#c@lHNXtPL=GGeV*qZ&vNNq^tne9n5gPpKJWnnxvunIr4P8C^41$y z(4#lf=VUGd$6Jg3;QCy~!IxjwVLFV`qdVY-J6W6pJCF6!)Hov0(POanBfL1u zI<>gceh~=6+d#9^@}jA1wDx@-$d8jJXJU#=xx4|h=nka`b=y=9t6JXQE2Vv{5a0_e4+sbe+ z6mwAynY?wS4N;CxJKqmy?UZt+u{E;&zTxMB_h0^FKuGsQJa{a5DdXfH|9JWPI}#wG zTbIRI0KimL{vGSPw7O;8^bIQw5bb{^thILJ-l$Nf^K1OeNO<)vzx~V3S!DFW0Ri@Y z`Q=o7eE6q({`0S_PG)*k!qY$Wy8f3~X%@_5Elx`1&FcIP$lL_t`UYgO*8ors0L*I$ zeW?-stZ4v(Sy(2Dym#l%+UeudKi;!snE%QqeYEz51^o(%7va?+{c`O)UYp05cbgm1v|l@wF6;uxYKm#jtZl!iPR#Nx)eXgi***G#qU_qwi7L1fJ)d+aZ= z1siZg%pIqpYvKhvXX{<3IzezT;puV@(e{>}tJV`sH^9OUIuZ72?^{WRt;0yDI%bS) z0i(L0EO%<>=nFe)hSp6_YAT!``gMq6v_xZ#>}&)pwYZv0ue+Hyi@H7ELM*;=tJ7A# zsohp~Da01R+hpbeUgXO(Ho)RrZKbB6N6+Nd;B4Q(5)}NY@%B7}*mh!y7L90(!t_*%iTBHSP0Q=UQND?O z%~ayGSOKkkP3!EyS-M(jZE>|nrBOCRjk4El-IraxVn}9HR`6C#M5uISbG=f!m83!) zJd6baY({s!h3lpX4x+}#Fq_T5zRdSNazmPg;x6v$exbXY85o!8+@;qB=tqtmrI{+o zDauy(!aFDS>&Y4OwZ+3uZg&lIY#IGbLiz_j*U!AK9`ecU{DPe*gx}wrt8(h|)3#s)p@KY0RupeP z3G^GmxEczlcH}v%sfdg|f6#ePbn=bXo83~pINIhBN_=n(5q-jWSea(#PmQ1c!rd&3 z+sZ5F9r$c6VGDCP&nMv%_uWPW^Tw92hu;2C*UL}ZdP#qN|LwQd@_j(7g+Lmg8T7LJ z$LJSnM4%5Gv`E@_Bs6%>e#zaE8nvY2@+E$CF9Z%@AIHOvNg#~pyYnvPln?xzInZrb z^+CGUubA?^(`F9cYk=CIrPHNO=imuWk++Xj0|K91>-3f)w1jYaxA6_X2WFxnAB*;Y~$Jre!wQ51qO zr+r*!#z{k|X^fKnL5D*X%Y7UwI4=xaNA;=5tdU(Mu0H(sa$8b=;_e248FWcnY z#*^eyq2cvJ1n|;v;(@Z2|14}&f#EWq>HNo@WbhnaCRPVoi^NIiCql%ZgZ&j zP@@&(if0XEi&QD3lUMu}N!;~tqeRiAYzfw~+Ljka$7s}%(<-~QvC!MJ+6ObWqi)%u zy))O?S>c9T?JrIV>8@Br^GNPP$z$Bx?3(1J5^;bvOQ6*bQbQ={1@YYJ90e+Qxw;dw zRkl>KhR-U3WG)WJt$6cpB{@MVe`7KwTg42IqYH1Xp|j$NEoY?;OT@-AF#Aa6OzI>| zutQL8X}g`3RI4r350zF)0^w4Xmor6py(vx)Q=k(`csjeWB`F(4YL44HAq&F+ z>V9%vAe#j3s2b=`dO zfW%bCvLMHkb(Y(B7*Vt_lfDm>GiL9A1=SZ2hF^Bm?pi5Qcu}~uwcaz{fS2d40+ar6~lunU8D|Bp7@M zs#}$8kbk`VFS?hFUio)gUE(?9ms;>#wSnz1@~`=2BoQ(`>@G==-J>JelOyn3to1Kn z!}zCQ(L9?UozNICY^w`~F>$Hd!B{U6qvPe<4%+LVu%%(n)F=?IEC^1|0^087YUL-nWNE|6INYSjy8C>+9KDHYi~ zI)XiDD?iAWMR)un@SgpO4WE6o;U^0l{-f>SxypNK7OK*!qIE1;n}P$2Pg6;1?9cOr zLjE?B#lz0CW1R}NHQwS3bBIi&5yykAzF=%5duQ^FF#>9O@$8{iY zbl9PrEiEF)F4g^pZoNvm2DwJxI}T{TDIKNAVXO`uass1z{d$l#Dm^sbIxlYPU}nSa z-0Np^$M5r@6cGKWU+B9K*PTva9Y6x7byrRT*R{bG>&|fy0-b$|@@BKs(};?tw6pg@ zEbjy#ftu{H52!KJf@t8+o6c>#Es(mFy6QB%DfJ%jsw0NcA)h%OGSl|zWXd%zFqVDa zP@AzD&S*`?9wmm}#z<2;(DdQ{g5@nD7dx`8;5r;SqX`W@kB+;%TYxS%wO4LLx}#Z% zn!AJLV1HO|PQ{VlD{$KT>l2U_Cfzs(Y}@6lT(kSq^h!SRhQN1l9jYU38(2}ptZtbe zoCrlmOP~`G9tzfhJN2l>a9~Gin-1S)R7}9Vq1kr_C8Bk7vNL)ZvP|sS$UDkASe0Fe zPaRnFrfA=dmcQqFwNWZ&;S=U;JtLHZd=Q!bj7vumlln-s!@58*Zp1g7V`Do>Kxd+^ z?Y*hyz0U4>ZCo8kJr!RtyIN6<<%@TVu)5RP!<|CP?Zz2h>X2*FL=Gz23Hf0(s`M?T z&0AuaUwWzO!Yr)?&LXbTp4Pg{yb}sez7d8g%}@U=qY6Jdv5{9AXz~Uh7v9Dt-AEuH zV69<`oH&7{C$Y3zqz8j1X2~T2wh${~=rFRWyw0^`3mvCuB>BCdH((_V%lHLfF_2dj zfoVfkOaU4R;zmw^ys>1fZxLaRICU4|ON&^&&$Z4k6QWeJrnqnshYLl8wCuDuy_CHU zN40{Go3cGmafcrS_>!*7`Es~KXW33BVD~lipsiuco_6}h9WQG%z^u+l@Hi?_VmSDy zRo4TYq>aFp?5?02*tK>TA{vU<45NPkuPU_U)V}qG849%w=s+E z#?jY8-lI{T%SVDBb$`Y9R~PIWfJVqwCbVbn?of`P;#O1X1FziHJ${6t+bXv>JVV8h zzuK0VIfN4zFmFI;bX$8V=#JUTfynU7P^j0^1`n&wbQ zV%y6wUu&$*_tRCs+!4g|OMt1`Rv8OTldKB)6t2{o|}P-$AlBLP8i5d?^W z^+$X;i3TbdQO8vTSS$r6{uX_B>7G2t&h+2K%OhV75j1B!V_!pU;l(lVCqc4}PyW_jYFC$5 zi0vbP`Yj*CT=GHY)t`9g2)z7KMy{FyknzusBG1SXZ3qKPZ8koT!#>dSEXhJ5ffLTs zN5Hy+pQgU>BAzyjodmFkrKTBpl0T45pK!j1s7jL&v=!slz3U#-JmPYWkY zfSU$?0xF=qNVzt5Ilgd?5jBF}SJen7n<7PeArF*)sA=+yUVk4p>u$d3B(Mw6Jik-;^9Ng!P zolkkqbk!BOvJT~W&C9zw zDy}!Aqi#d42R^`nU(&+4XV!-LWIlG{kG-103H|AaECsi(heXG;k6liQB^Lc>JO))CS->?!7XQh2%4S_vopLU%vJX z62?ApJEqyT`^c#UiaT+e6LUa3KKON4QYxI+6;uNBB$JyB69s2Dx6OTEn*GddPZ^9U zJS42Py1`~qOnLV~KN$X+o>eI0FXw>Xtm;*@S**Ptf(CWPBa-5u%OxV70L03V$UWnD zVP-}!MkE7)9miLAXn!AhwXI| z9Ua;fRw;6YkR8-1UCJ;|k)Oj?--K$G3Zx}q=s>`H zeYPODfP<-1%aMJ%`it76KbzqT_D6I4D>Hm=JMirs|7?bf3IenEYf8v$NMV0o(qs=? zKkf}mJs=oTZr~RTof(49qFlE!RPY;7*%bC2mYR6p1j-pXp&pZEVJ`IK%3moHG|{Zf zQ&xXf(K#>}AiA`>d<+COzY+W zu$;|QR1^>ifb0b0^=3F`haIt*4s1YQ@CLh~A$4GyosJ@vxH`ikB;PKmMDd5(OMQ)y z=G{uj+lm0{H18Cy6wXM|x)seKuyY9`!?r4mmN6qjv547Kw|kQyK8}RAwBe08b!h?2&-_*>SFZZe&qD_oDmU zE6=uY4>$4BMElhR|7&$2vL)<}TsZZ6c3_FlsJUxgs=U9y_V>Da!S>_sib-XUuGal= zDD#lhiS6m^+!=j87yU+>a+E&ZhGGT9-t6}pVdN*?wW^e?_D#5c!60&Vfdnx;_Xsl! zGJYm#hUpFsm5YO-`sLo+8LR3(tx}OWkoVQCgj6Y&+kISv^PvKhskAri^RU^iCac?B zkUECu3w8>omY3_jDcD=5pH3Ju?j+l!4(|1KP_RvyI%j#_TvjsxXm@y|BvazAG3??=Y<2pvSmH4eXz^^~~9xopGgP#NFJodv+f5>}n zb>=mSy=1(77sq}tPrcJK=Dt}CVAH@$0r#@a-#n}@4+?DIl`&q_fyuu#t-fGe;etQ- z;$N>#Z`Z!AKRB%ahBxrTd->12f`u4qVch(dNP@tU2jeF3VBAb74C0Hi!DhFg59taY zOaYa!Q(+{b&mLc}VvBcbn+bw-u*Y@d`6s`V1xqy#ve=;R`ASr-cHdomGY$`0mvM}F zvYVS_tt7hz!p;JE8q9(SB4+BN{uDn(-JeYrjMm-QVir;A0Hdf2qg0o$VZa4*q46rsDQ zi5l`04mAy*maYT+SchdNq!VNp5pRn3IbGTM3BDHwa}GCn?Tle5Z`mv1@4|COx*^2r z5k5`Z&<}vnx`v)TWS32W?;C6K!8Rf6;Mnw6&A)j9gG#Dxxlqgb#+sk^{h_UktGQVT#0}zwSf{Vfd4!?t53SY+ck+o9cCBm*MG2og$g9EaXnmiZFKU z;i&sy7Mmj`MTC%CgX@(mjM|!bm<@KM-RLuX9dwU9(xEw#goV`JK^-r$6>cVMSVO+X z%@+X8c-RPJd+*iJA#zx=R={pp(ejG93TWziTL+?)78asGly_4JS?Y=_Hz9G!l8ViX zvjr5$MfbM~hYE~+!9~8j9JrA(FzzOrcdP(*fWHs#ndk_{ndEI9z1O=UOV;)Jwj$B1 zXai-=-v;%fHJdw&OVT^J2%auLYyO7i-f`Yy>nV;qK#4t}SD2)>f*T`-fBgJwYLeht zkf}nKm*tl6RAB}-83SCb2u!a7UL0?gRczd&CP3>iWn`F;^G9?5DspENa{$2I?o@YH zY+OY7_#Nl>k{5#ktwRYzTPKQ0D(i*6`_{-Dn~!r0_T+RFV+ywf`*y3bV=~*fd1!_5 zk_f9>gtR@3L?PxJy2LHftM-`aym0fK^9Ec06@{kEwJZr_wvY@{bjGvN5^D5X_a=i zPag2I%eTFO=RB0@2&^KE*m-tUAwBzG~WCdJgX9FyrA)r1*b zU$vZ!^`x|eNMW#IE#6$QS5cHV*2B5Whg+*Iae=je&k|HhE|R+Gbb$Zdb-u0P=qPLi zFTW{#alrzODGnrLt&(aj)Pu@12FHytk^xCu*pb%;L@sG)5YuJG^JF7#n=4}8(xXY{ z$!eANQ^TUj?60j&l;4XgF5Ha=GtIDP7_h5%By?%?Ry>{j3*nFfcY-$xrI{nP*-mmq zR#jER+dD-c`SqN14u!O()RmpLIq$D6c$+tFy(eAVa|?X5y_*<`%5~qScy$IczrHu+ zFrjC`norJoH8eYuTlM!*yj?I0>-B|8=mgU1vuXyO zVP*Rpm-)nWUr+M(q;0NSE}Yw=yP6^A1)H-QFRB({$47(BySp*;@D!s-G2*x@6Ajl- zTjeTCndRXV+sBfrkmpdaL}y1qr}ggTzX* zmc1!?8hSv3hjIm$(j36%v?OO}#*eU}@@Bkt530g<52iv&ae}I`qYgV6wa5d##+I(i z-=UZMC-kyd?52RfSGfrfU2iUBK5m2uwjg`xru`Ugv}^~}xq!aD*C&hF!i z;0p;|P?%Si%BcmxEVPA60uanwKH+^S6*?v75Fvnn3JM5d#+iQrZcUDp=>fa$_}nyceX{vVFloi12-2qWFUL2u!!h4nC^5`>-8i z8JM2TwiU!Au;Mv9G-lUD1Er}2L2jmi8m@zs?|~wj>WT_i7&?x#dpqc)s`dx?5Ic7) zdilqyg8Z|!X)ikqb`<&!H9}FT_`UStY+RRQVbU-+2JD>e3xns%RZ^e$9X}qHeDeS8 zU7EjgEB%n<)W4e~KYWzrOXJ=*Nq+hy$=^AY-h7kzLxSHP68ya;Ss}9PqZ*&@3 zx3lFkjP||#nf^-te(Ih2aUSnvKW~ZzSdVX(sWW;4#wd_dO?uPz={dVHPKkM!~i{^8>WU7J&VNAuzEH@5B<0-=^WR&2dL) z@(nH8YIQ>yBnzA4xtdqhm37#`u=mO?B5DsjPZJ6&8ZI0Bk8#Az5Fc%#{exX@L?Mp^?2o`lx^VkYZO9V$)BvOuivxCp~- z>Q6dWOZe{6X_rmZ*{jP{kcRVHpN6e4f*m2fVBgs^tU`G~Vr*cWdVL+uwcn52|^EJ`i_AeN%X5LDOw)+qP}YOl)Uj+qN^Y z?POxxeq-C##Cl^VC*Ob0f4*mT-E`ko*IM1ZtGbsA8pA|&2cnv?M0Lu;bLvUfXio#k z62EmtH|QTa16|_vNv~N)Gv_^TFMQErJuHa$`W|0c9a~E@)hx_ioX#M= zSV3@lEhguKOiv_`@8jt3z)u$?=%>Q0WXZ;6Yc-#-7h1tV7WK``4o;-798eU@9eo<~ zacsO_F-}~O%VPglr7`^~e}6bAKX^@~deoLOD(9`+@aTE-k4|L_o=A0)?CA_BdYt$i zzBXvCk6lGvCae-G421s3(&)j&Dc%Z-?p}d4AxEKx_v?0KTtH6vKGW)7v!#tC-oqAl zvxR@kXAlowKVMV%WyI~LW!1lSn>@WLMHcNkC(^)wkxWFo=AifM%Lqh!FGFn~w%f+Rwv zJtOGW1g3o*gF|Y42IOnImdoojv!@Puo-;*dZR~VOx^Ck#+j8{ecrXJ^uZ}-q-%J3g z?#g+PST6cS12y)*qeC~y<68Ik(a0O7reY(R8?Rr!H1g~*rDsD?2h?O5epqjA(~(Sv zHG+(-ls)?>+mpkKBMi*AqZSVI7P#2y6AQhcu zA*Y$uFag64``n^3-DtcKm#20u8^g7%`KE6b*x zDmC246JaAOUPt5J+aBO5*<465sz+8fZJM}0sg~opPAuRRM)>;|p(*UZqRgv5nz{0;|DMJs=6(yWLyI>awi$kO6`b8D-d0*n?fXUk=M^x+sP^Ls(2KV4VhN8R3&_}*AC1$9inJ?3;t^q3CK%U>nje>5-8FZKL^ zB}%0Dd!1E_^o6`BAg%as>#NBH)*HHeg>tXcN8wqWb(zn~dTs5|$hXQBKYK!l^3#8L z8@#T<$QplJ<#BSGl$W+CJ{$rO3kK~6L{%DAKVVjTVI8xWL zubTmXmNu0;t?fL*lU&8?O9ebmxobV{nK!>pzT!1Jj>$l&4y>P~}^w2{`^J2Ht; zR_pcla}r((KBq#Feets&%J@Gut9w#A$ti7B%NN5K;jdrDTUf+GuQ9|2d*@5C_HWOv z4(r}u4oE&Cm#>#vi`KKe`KGeSulyQc5`GOgBldGop4J@nzdO|$wH}o9#!qJddWWQ9 zcK9hb`z{b+(yRNio+eF?U1oCDq<=cL4xS8GZ+kYrQ8%x1E7U60YZ-NFO=YS_C=~nu zV>z7CTAEloQSNqJsk!FxF8`VHirGoQ@qUtor%Ttn#q)9b8sp6&RgJY!?i7WL-(0cK zY`EZlrb@TM(jmWqO>e-va~8Nz;%gN1C$_qy=gqBCdaJ^$OS9T?=pS;DmP^@so8Ma# z)I%0^u`vPTxhms|K%tjU>!T}s$Ko?fiCs*^Rslc9Y36L3y7nvS0pFCc%|_uc|5oDm zXPH>z>(soF(N2VAhHAM)p?lJ{U#Z^Z`jT*!TD`;*cBd*WcG6~{SYaeBr|~&w?cF~A zA)fjbYMe@|-cl-}ab-*OR)guv72>uJccJfxohw?Wo83m4wACTeQY8gt3jIof*22A$ z;QU6Zi-OIId`z5>boDlAq0x1=-IbM6@1ogOvikf!v| zxl6VRmkRYlsg&rZGu@qvO&;FYoapD!2-cK^ims)mPYFhqikGF61Ok;xt>QR(pUn#A zW-t4d>K)EPA*N&74R!hI)h18z!lUbDIqRJ|>!c&KiX!e4oV?kZfzBW-fWq0I|ki{l>UYAz2*nB!`; zN|naz{5-K}^JwR;R&x!?R&Rfj7sYvOy(nC4<2P@L@ya>%FR&JX)$54cHY?=$-Znl9 zL|dKig}q=zy1nW~Q+CANOFR$7nf9DldUwTUjP$By8of$SOtdLGHWAL9zQNJMDcXj5 zYWQ4sOEiatNMq%M748)}rD~OK51qxuE7hw))lyqW*F9>zBT)~PE`F>fo>7DDbX;;L z+1p$_$~nV~m9OoD6{#{GbutTBeuvgWrNw*8_&Y7OqDG(0&D6ybCN7bql2YSeJsw`- zrA$)Hs%1IJtHlz>(cwi*x_qt?G{kebm&yz4VifLjG90~dJCwAA)@e;eEvS~d{4O+% z9YrH|!?ibjwKwavH&nGZt$K~t+*Zm7&x{Tqt$3t&A1>SVx&~2AkCr(drD7B%Zt_xR zXrz2C)np4Q`}A<}CyCDmc3H`KLad~7#jEx#jau?zDkWQ)VP~G zJ;sSL31+y0d=wc~M}mCRmI7?+Zf6=o4Srg!&q&|5f`n?&bh`n0sFFc+oqsLGmDAG5 zBTGtKf|1~p{o>;@kEn`q52>475BQr3d%5H1>lOW~{5E!lo9Pm_Irj#N1`(bckjf+M zsUFJmQa5Q&Ze%k;{WOi$OXF+*@ZHG_4bYUsi@{@`kkiJPP`f%#QYf=~wXx)8N{z7~ zLD%Dr;q*R6l&iBh>wYvv7rAL94$51e;_7yI*%tAeh`B`AXT5$4-_!MABxDl(#7f_8 zc0QKZW$DD$@WiUt=CoR6;S>yhFwpflj_q=WnhD4#ifX#Y*e1_;3O^ELdzP>j8yHRj z!*`R8zE-SM(b7sh{XyY=W?~$moAkaxMLFVr8Gpk?nJu5gJ~js&30v{I;O}?cjK{BrU~Rdl ze~b4Rph?LlX=r5(!qbwG*b!r}Xk}D3T!*r#N{UdT2`)%Tn+Lo zjGW4G3`I*#8g*I!4n9sP3rkhZbs08(al>Nzp!tB^UU1ou;D7X>B-}fi(-6LW2=*>3 zp^oKrl#pt12{9IYZlL7kq9(*+B;<;Mu1P{HmxDLkoj-gh(wKJ?zE&o>bR}>DnOimI zC3FoBrLJ$5uOpVPUoZ;{y}7r%L0A3n@4g=RTrIK|L%2+=e8Prx`Kc&e9mj4D_B;}k zS8P7&`lT#Y`L~i1;`Iz5~@ny-W9v@8S%-3;xxVU_NmSiNN%gLM(p;`|LZwk%~F4qz!ERvR!vyq`GZ_FW$ zNe0e$P?KIPHtR|c!=o|_mv;8AswA;fRP-JpWR;cs8mxmR7#^R&^`%f4N;YyXPghT9PbPESIaXgW*Y!JCF9*-CgX>#mbeQo^bq1 z9ZA)mj;c4RNY}6LABx0;XT(y!7&9`B6#QLfQ-3=|hvi?hDl=5U%lXULhWNlw9DR1NkGf;Mn8D z6V5wwR*$SH&*3#{mXyq;?Z-9_Dqm_pt*Fe;X*|@9STt?16C@>@QkX5qUr&m|h6UuJ zJ5jUn{q3T2a%}hPxE*Stc4bo_pKsfYaEjNE(9Se5hWY>jTf`L7&+!)ObsGw ztJBK-F71RJskh4;`Pwv_W?@%rn}pt?v$ciV*J-}{r}(%<9MuUPJ(qXAa&=o|mxzG*qD zK6k(9lV#o)=TwMcL;D_asJGFYb((-GLm1v5M1$nc1CCuNZps{9WAs-`IHQ@&s&Y#- zqxls?=wt!?s93iT3pk%+B){_S7v{avG00B^%HFh z{g2p0`#R_Cv)a7Ue^1f62ChtGgw7GeDkIgu!#@CJep|%lzeEP_F=h9rDi^iUAJOmb z7!2xTYL#4t-T6X$19V3qGCyP=%vsz9=<8nOtbiJPqqgk<_1aZK%omsXQorDzbp743 z=EApMVjz3~S9JlMk}WSeiLe*Q_H3U-l-G@eAddk`;tH`YZ0AE$7l~df4agEr%o6^W zucVhNd9CJw;5?S@B*dbeP|jF9gC<3Y#V%CF18qu@rF+SrF!iVcELpBZQeZ-{55|Q& zRGYpWlq0~53t^k8)JfhSQnDx%o#=?-3DW zP!r5Dndk&rCiaq>W3{veVa*!4R@$OGEhBjoLv>Cmhqo4g!n&{c#*9_(v1`85dc^!E zv&9$Odc-nl3P)wtTGYCV7_|D*&m|e5kz^zfP3W!yaJ{9^+Ec)aD*&Hwmzb}u1gWOj z)7EyyQBW4UrV@T-A>{nLAJJuxo71{XlU%p0-mE@zX|XDmX;ZYZmBYG1lYJ`lyr0o! zua(ofT$6rkd+9=OYI~)s+02&X?FV+uihxp73m=DOSj$2_Egy$km|vO5#`!D~UsEBH z+(Leii-kGiYl2(H$*c=uX1+-0*{mI3Qzz1QqV91vKksDL|E%C^{z7HyvwF?n#X_M( z#2d6bQ0RyLU*cI}91sKuDA3?QQh=fX4hsQJpUYf=Y*>t8W(+hH2zsCtz{CKK1v4E= z7TOFX10ZXFwLp2yqH9GIUm-Ik0nBF#sn)LOG=o<%L@kH{MJQQBGoYQ)eLr zB}J_aSOVY~FxDYyAlra&fn^0u0n7nd2K4K8##eCZ(<=Pwepb{0fx^x3tB4n%LeP(3 zZvo5!9{|u@5+J1m3=e_{=o*d!Qh+1C+d9+{U~m0wJH>(&>c|oeo~y+k9c)MdT*8yr zY9t3J?cz8P@PKBU@ZHkyVmRPENYNnTV1)s502~7b7NiY#fqqzkr~w)aZaSE(*cyic-#&YQ ztN|5E)VcYw1NX1C;t(eOyz>TlkjW+nGc7mL{U{KNvJ0aoJun8yRM3fpUN8;VDlm2M zlV%1l#wfkD|LeNAE<_ma|N5>DaTVzT#0{)7U<*J5q8;oT>%d%x{0z2=OaOuerVkbt zFz^E_9`PSY`~PWA2d^p3CzVh7gRR`U!Gq<6r7M)dq2hgdv8J0Q2sxbA}gdk8JP1UDGr@eQeA1HpkI zsU3&z_`Zww0S*7e$#alj7NQ)g9D0!!ns0%$qK2xD4;l?24tp7m^_t=c@Gt;lK?U@9 zn{EookQS@OLz_1EuD1Y_VBmBs-XMk1* zp$}98Sml3#%Dsc*IWEIJk-h(vdk)A1f^x3F>pFu6;I3@K0FW0q|2VJ%aqA#`ZNXdM zbNV!0B4*D&p$xli-Otu??@x_#cc!Af%F5bA^qa(&kMu{$Q9Xx_JZ_+wFd44 z@B?&r-FK09i5!SMK<<1`l+9*3zu2DJ4t(%O^ zUqt2FeV^KwV&OR}XW;H}$*;OLsqVc$R7`r~p5MZI;|_g^C*D3m2a*31b&AJzi`9*R z1SG%D0?6lm*OrM5+*wT6zrPVf`;orL1A{5Y6WS|qLI<5Q2vwOub-8!xv%zog?OF$9 z=KXv|bWlHQ*#UKMwVfdqy=$@X)9XmF(LM1#y}}$9WVX%mQ9Ao413(7&u~B9UWrJxg zy(n27M=qALT&kwK(X}Z2w@vb4Zcsl^FZMl34L!a9I~IvQ5jNxP)T%cI;G23O6Gn|_ zjJN~z|1RklUxhcs(!jqXYIR2ek;{dDZA*;4o%{#&mgfMymaCw5rqS#9k>x7p*_q{2 z@PI%7i2)1C;Bsz$i}ui*ZrDc0Ig9}X3*3@YOg@|lW~dp0+`D+;tA@6JbO&+V|1xN@ zT>S$z4ScrQxqrc%|2&7;Ut6kMGq6V65Bq;#_%EN%wjX&fAOw5n7dwn_Y=-JA7B+BC9F1#G3GIS5`ZSgS!G;TdrofP;4pI zMVoLjg&H4zs2I|tZP*I_xujSZYs#`fo;xlH2z*qwA)dn)eTzUsA*6Uk`-QaTg%gYm zX|m=8!HL)BM3X85q0bXUn7^u|jch&=9*dqs!Y6f)X_DRWAo<9NT1Byo;55DNg#e2` zU{HUNT|H8N*Yfy9RM7?V4)PA8162d^J1{T68sNHXz6<4m-Uj&&_71lR$KTF!A^jB;A-@B;Y?dcxc!+z$%)VSvJdg9C;D845ZOhzgK2NS;Su!;!>; zj{aW{e%DUZsv6TNFVL_NCg5U>i53z1U!38?tH77R@dIK2R0jXU3afB7!Bv+4Bm?R?_*HNpqzlMLP<@cMKybj%U71}J z2b@u`hVLlG<^dysbXaaMhKU2G4oCw0=2%$>_d(27@?v`nJz_~6_AAz=kZ$kP(e}Y_rK1m79qR$NK{jmRwJd1q- zeGd%<0v`AiU|;~lf`J2t04)we9;gJcFo<4WSk)kF>$kKaXcz`Z4~PIzfc!@3@->7E zR{$*mTL?G>AdsM^!%l*kfjbAZ0|X3&>OeI9Lzzhe2Ib4O4B)eO^e4}lXYRap|8G&T z4rm7O8GzTJu0q&A34ynOXb0K?+zg=W&{rWZpdLZ;z`X-~0oVY)T@VM9XEYuN0;q-n z8xrVI$XF1GKx2U8E`-B3;qgHFAo_s&z-NQ^Kr?{}gQNvk0yw|toC*l<@!!n`;g%Cje?EZ4fG6M_0YA{;8$dXS(T*-u1BTp5 z*|Q8>%b+pjcnTbW-@>UB1!JNr;U#EtITFz9?76e!F3#;3K}f&xXFb z+&w7s-CGX+RRvL)5TPI zf^kjGK1dcHb3SNhvY~Ay1$<54e-HWHFNUbsmKjGi2JHiaH)^w?dzcSXRjx>zdR6J> zKR|iL$hU}5^`L~pUEab+fs*dTY_%i@bU<_m!e`tzGO{gv7<;hR0Q~^{KwP&XZ~Q&` zA7e&Wo(Me%v6;$O-O8q$Q3cVDyW%H(#+%N)$&UgcUg8!rf`7VE9-&@Ad;|S{_=C)7 zBIh;|37iPl(Ia226A8=+fd35%M7?5E8utCfy+n$1;2tm45#ZS*MV;vo3A8TGMf6`L z3|(~qnTZD;Z=TI`Zv0c(Gu_JhPK_qN1-a0V0&lNSEyH?uiJ*Tw?9>euZe16{)t>k% z&UKS{dQd*OUhel_=DN~#vTK@RV_yNWw>p(We+TwIiMO?Vr~FMjMQle1ZH3W46Zx^~ zX+fBJnV34i+n(B1dkaD>mfn`G&Bh*c=LDekPw!cc$g|e-`IXxr`?fH^7fdax-oi&% zPC26w>#*FccHR+pdIS!JWtxp}ZocWe;#A)xR~ynpUM)XU;U31DT6iZI}k?o%Bhh-stNt5uI?dLo3AkG(} z9Dc`m3m9Zcny_wkX63sSe422N#!sP?&N!>Yo~A>qOIRdFMv0eW|DPPNZt1=LdSbGw zKRJ8Y&Zg_^OF)I#&S6%gv9Tht=4Rq(g?qs&?w1{jgX+Wn(MmSyY#w;su1@9y(&}^+ z>TuCRU%pBW8X*wY!yH1JS?Ng}ME{xMFtX)w!Kc@#{V~C0RNpM+hhofMMRpwiB}ZgD zB$;}Cv=%UOS9ZG@%%+>*P7iHyy|ke4ly z0??QfFn*w7Ok$A6=OzgJ9Y~QLf*q2Q<<{RhLagbGumZ4YewQmDYUZugcE0OAF#d&8 zDCIrY8P;YmNp+be^LZbMk)7;r=6^Tzj2=fLJVRb4{5!nQ9UedBD^6($PDN)@f&|yEu<&Zpb?4@;{s1kxqPxn61qzv zD!LisIdyLEwIfAtQ{t{qQ~+_1?4jg_F7H zdgSjDudBdZ;XG&LsF0DnHY{>qCJ|4>7#+4dfs&*ky1&NbN$#>AUyh>n0$5}eMbYjY z1qh6uENax1EkV8qqawJ(aC(%xdT`U(DXJyfls-t|DJ0hHYaibV7;vkwUzM=67HQN2c1l|VJ+4C^@e-b9SP(TrzL*HRxV+Vb3dV;27$X{@&7 zImzT1nIvMj^vmvA#X+*mNC^TkJ{nmO7DDf1ps?7~rc8yv`0pQnqBQKI{c)=FFAhfj zG94zL?SF6A4FW1m`~WgSRXM^W3;C_TuStk%SXwuGh1s7QXT$1=4sW)k9j|>hw=Ea& zsb7GA*4(H3-M$-+r{o9eLhzr-2TDCAo>o{tRWr*NX84teo-j9)uBDz2p6YLfT0dKt zZYw>Q=lr#6cZHmZch)`8K06}cN+;01gwX(udD6GBBDH^mJi031tr@?VC3+9X(q8N4 z{0XZx6{ZEZyEEJ)?k*R_H?`7=}h2fZiSZ?{FRu$zDrYixt9*0 zF9xuK-hl0Ao;LCCj_^*zHSNCp#ZD>64 z%emZ{XH~~F%#$u@^WV3)siqN~Kc8W&K;gCQ-$HK_d+)>(ulCbtz48OzY2em*Ae&WF zKDWQ@(&b&BZf?GQQ>O}de-+$}7{C#q7xs(nJo-0p$34$~?Vl99=$D`M;*F9MCv_d0 zuchu{W(?8o@l?MT;P+RQkufX9RLth(>2nS&of-n!=IAHqYuP3|5hHzaH2v&ZDrbe$ z5A}mJK{;8IwI<=JrmFt(O~S<*s!QscVb+9oYWSLAHWRjnV^-kB-*a1raZ>CohbP9! zXj2-=XdB8=RiAu|CUo(pw32vbl~GRp#T?ou^-6PlryZHnQe0UU9A_qC4I^gFm+o0=RL) zNe1yuJXpwSMSx9y_B8PBc}?pcp$W9FZ^%zjE$QtD zjBXBo!lM6(K>uk=9hgW}$l(bWZ0YhO8!{oA6S|398vI?d<2nlAnhh(R1$oASzvzi{ zQrP~N>28+HSI7t#{)aAhPzR&YJmVBrqk=RuE4#jxSO>!3~C(;B5$D3-Gu%Tlld1Y9yt66MAHob zat$sIib6YydJU+LV-mj5!^~nKBPh2;AZwCyHmB=^2D-)+I@Mj#UM&kdA$cVFxFE`MRXbLdQPl;%!wls0zsnvThR{siTumB3q{#y}j^aYKZD-w9Pi z<*pRMk#NR`NvJC#uF9&b@h@R=p_DC_o2*cK+|QxPaDBPpjWUjy52)Pfs{r%5&Ry9@ zeh;cQaDk{T@ENnGKIc_%pw4m64{&_w?a|?2 z?t0kWDI2n9b}rbi5V@my-|6wbX;}BXW0hxBzvOPVn5&d!7jhpc>KJppTuUc2$6LWh zUd9~)prdS}a7b}3w9xcXutGrIFdy@ecY5H0#&O8WHHY?~Wd?TeOdVwrh;=z?NAyAm z^KsKfvyAcFZwD>s_37hP>A{&swOx1{&Sx@h@J~=P4ZQQN)U;u~T5x^C^S<9}P(TG6 z@@Kpah@NS#V1Ki`K>Iqz-7FjWXRHmV&k$SauW`2k`^KEzomD0vVGF)b+9l}Mv=H=H z-(%2qz3=YZ1w0VEg?KZG802Xmy07~sZCB_WrHxrQ+VSx~bN4el)flAo5N8y;SbG|; zUyY0VC}kjqd!iVCDG}Wem#CM{bJU!8pixR=&CvR*NP9aIa0n}ZF14g)m@cJMx@fxH99i2nBKCqa8r zCV+P|T$Zmug!C`T)CuOK!&`cj_IEl>rmqNwG=1`k!<8|omznP_C{3oYUX3aH#Iaj# zlZJO}HqB4^42IqqOZA2pr^;j(dTsCa=P8X5!U1>0uUI-}tuUb7AAI5AOW|3TtR3)U z;M=awCy39VCkzC>3(BH5v~xyzr>!s{@*8yDgfGl%XrZCEV1+w=lO!LIeRB(!9pHBc z&Y`kkiL&N3VqS5Q1js9dqc(j!L9G(ahoZTy5;5*HkT*7z@pDFWIZ3Ngb`a9B9x zZoUzzN4f?C5ffIhL^$Pc0+?R3`a5f{ zmbDA(y{Q>Vr%%gXn)_Q=#7rwocG_&f#3~6gV+#ZIsI8gB|1qN+zeB98xjLhNJ@V72 zs=eP!xx=*@vfm$nycOO$j0NAKw7fg2#P*V_YeXB!E8eMi0m+P3G&U5((JmTKl{SB$ z8sUqgOC0u2ce99X#`A_q%;GZ(ObbQMkVne$NZqp;;0<*~BxZGu#S=1nZrei-Re(D{ z>A8))O!l}a)2BU|)nz=X(`#cTv6aLC&ZpD5kN31SwANXzZ9&mrA>A;l$+RWppJ4!4C~9$?&5yf*X4rC9#c(6aVF~C2(e%ITG;JF6}*^h)O zzei!Pus4ThZ1*e7yG0A%6C z7SNdnkBh(SBR>QK(zK%pXfc7sB}DZZAEE(y+A#$5n7|d{C;HG2;eZtFXabrwUmU2EL?>hlg54R-xVJgirai7mW}Z;{A9=l!%CJ6GKr>R^YgyQDZPoH`kCWlvWEkSEqebAD<6yk zc_}pS_3)4Na6u8H-UL(D!Run4(D&NpoYhV<-P` z+4x>K*0+-%xlQbE8P>AiQ!nKFBp#_|61vCk;?a?Ld!=7|d$Kpgr|re32{nIqL@X1} ze}Rn`Rz>YLEK>rLPU5J5g?kevoH>V2lMq)E^vW8@fXy>YeMZ#}ekD!TNS$b_HLwFh>dz9N@1w8pnd zxLNE_^EVO46RvY))^qVbIdbv7-f+d(1R*ZTH+iqm+nP_+u`->O>vGN}+d0R2`#wv0NOeWaKv0a~G6>4kAwxPQA`$2i}TW`R}^O!P2A!Cm*v=wjLEIsqnNc7VRVv~!^pMC#&b%V4D`p<`9 zC80QuTCaJ<4)+pqdgOPt?pd*0!K-loc+dEnX>!{JkSMFiSEAygR5Imb>3A0uPMV-h zG9q|U?+x-;--1__HGPn!d4}oKFY;cpxQB)9@fd%WhTOR6S2t_PgN7?$SnWZWB^!2J z$U;4jsS155&CSv}k?#**Qu!-}+VsW3Dcpo!-{qJ_s-Q{+{1Ss{3mBqh?6|lgGhOyI zqmBU_w(=esX2_vlu>oTnPPxuH#2Jh&m}@duARm-od8`PkFAl$9t(qV8)YaQK!g;dc z`q_E(g*ZChUv{GspVQ++__(6&K3pmj^8y z#O`Q^KGuKy&7ZUP?pmJYmDbmzn54DeW_y|*AWw~YUy1sm|r-K$SqcQhn2|EB4LMP3>iO+(Qca&1ib#r7s zEQ6~;x?`00=5dKH?_ePP?GN-KF^PQ~Cn$Iz5er*05DUW{Bd93vV_7h0qk17PTazSY z+^Xj|t=B?U;xT_8QcN*-IFK#8DVc* zj@EBjgDK{*AFjiP^iPXXmcG@xh#)IetE1r+hn%1usW9kV%grnc z+A5_kH|QN5qia*lpru>=$|$Gf*HmN*D5@UC1=e+cU@qWoeAjXJ0iYGevZR7yjN1Vg_45PQ8UZA^oy zG%t;IO>!mJ5M1%QYP?3dDyeCdn)Hh?SY-;FqE404c;T->tzs}WL!}u_3Evns8^Maj zod900)o|XPc35YdHe6?$d)%W{5BMXT4UXPhUtESvTQ#haL-~-=qm2|w9h+5@&LMeI zsjKT=nUJ#9oq8>8T6JV^?#w-m?O-U6E1%eLkK>Et$kmE*x2W*G$Vo081grR1eAPHhAkx@3PP9kDRV|1iW`8eFN1c7FXcQhQq znIrMXaE=6-j)MCT9Kj7qvk2ooL1v0>aG8z+LzWi9zQ25`RpYglvz5|clJ@b7e{Cx7 zS1E_+()OrT!0@dP_xIi*9cR1q9FN)Iyyae)wHjet2n6nv#ZW8iC}YJOL%)K`4f_>c zuR|G0Kpc3w<67{TUsI#;!+wL!+?lHR`QW|k8olfM$c|vc+zAT5F=E$0*kjOY zVEnem%+dgU@mpi*ryIW61LYf{n``)1pdq1|yPCru!(%5LQ{q%C%OmvZbFxuzJ7@NJ z*HRaBjP=E?0c(&jKvDcQmv5D5$otfQGxKS=&kKQgrn-DHbIg>OkiqPxhfibr=tI#K z>HFbq=NIuS?jIuCkV8;b>93SSP?lu}689zfarBXhbK^(Cv|Oqpx+|FYP)b?P4&m#Q z5w9LVI^^- zzQD3-8fVBv(&A0%NXss1n9-IP7PcuwoZ?NqH>Ky-Pv(=U7-vL9W#CE2Vl;B3wnwK| z*H6xb^L~$4rB|vc{f_X|!gMBN)vw&YayAN7&%e4e%2%h{Y%oetH?-re*Qj*hmS&hc zgKh27s8Ex7f~=RVq~3xvYF97bw5b23jk>NCk#W6S$#R$D!w=u$ezTPRV%G!Ju|2SI zW^nBM35hjY8Sbb@Su_MwAKav&bYa}L&-N98y>n{YKC$6M*lc3N-Hd?ZT~>^e zry>)#D#O^kD)AbJq96ZBANf)AHNOvXDy&FA@c|vdobPW-IG-w^9hM(pEdUSKHa)W5 z_c$YfpVZdp{5K#=02{7tDDK4I3EM9L`t4&p?ris0>x-`4UV+UK^$^a_yc{|7eVmLf zuD;l$wM+C1h>R_-DK@eCq-{$dv4y%J+Dvg+M=gB&8&0{xHXf0MGK!vj%jbwjj+pU% z?abk*ef~^wOvl)gn?xRkg)+JxYIZMm9TIlseX@)Ma;5}aL25q|b}zkb1C~OoeF?V0 zvwgOVxru$aj5+#!x{SHXd#NoPo;hBlo;r1ytGoEO(YP)uHyHYf$>2Ewr;slNjzj7F z%#ys)j#x{YbMwhxDVW?WQE1C@vS4DZEU?KRK~l+0enSM(TCMpfVn!P*7;cv^qIV819?NTBw8mdW^lfAJ2LO5i*9 zR45M}jIp5zA?D=Q-yH@O()S)S`Xa&2dYrC*Fw`1V$>cc(?b5MML6=KTP3Tq2qYv}A z2Q-geoXbk@p4J2vw($o_wuJ(JE}Nv}e>6`y6`)rMCnaSYm`80gXeCoah0-=W6oqv(X=X z#piZ_;SFUX-)tOwY3W8KDnH^qnQ5*qV`Ci?6Bdyj(Pw3i1yxzS##7eJIijR|VB7Dz zEU=P%*<5Xtbv4_zwV&v@P!gp>Y>KF~3}YJe+a?#4LrycpMYk@M#yaM9k~;%F`cH`X zD;c?Oy-3&*T2*FCD&IZ&=|r8;x>oGN{4!jajS=Otw032k^19aLER=4r+B_DLJHZNc zN=ju_(zvd5hlY8FhJDAXLC2~^$Lb%ZT?6hyWSgg%S=ii9?b#@L%-HTc)%t1+Waj!Q zm{mK73l=apJkZWb9QQ2ND^}z8&1lD3+ykMJewa zmnxDt*U|bQ!DYh{xEl@4ShZDSQ+{>SxRe78F+uejW6S|p4Nc!>P7UqCQ}^2-$12%5 zb?dQrhZLq^I+@}A3`Si4K+bQ~`ibqtRG%ncI>t`Sx7Mb`x2m!x7k;i}sfm|#xt~8~ zLd9S{9Ejyo)bp#O2_@!*)8@E0Gyo*iLh-5QH0FVckVpp?Wx-izqDRpO7d&Glg~=qTuC6urn}R>0KeCm=TT3hDO4^NRY~0sI z{ihZ)QvI46sb}nnw#(>IDz-IKUTgCeJ*#Fj(&wfEbNcbi^jS7s zpzXDG&A42}4|qh|DkwdM_JNEf`~&9wD|rG~lo6jGI{M zr**SzMdfV;{9DMY*fm(Y9;?&$A}_@>+_R0|P?obQRzd$fnA51sYat%dx#1LgJJ%bn zjoP(o)zesVt{33sj;b5$K+XK+c?>WzPq0fYTz!)4b^XQKC8)U&6}gvU$=O+fqvxxB zw}Uw_5!-wMeq2G}?TwO6+n-vvD~sBaS*6+3w4DK(7xAytd@?Q-PCd;}CX)3k4Kv3? ztf@iy{cIjtigg52bI$cNEHvXhEfq88p(RB*=j5>JRejTuy24QXmcE{vK>kKtbFB7g zXlGoyCIj0(Gew7hZ^>0L4vkXl$-f9JYCEhY3=XFDz?-vXGD}IyF zXVU%qR0{!N((R4aoLd{y=XbP6Jb78J$5L1D6yWB6iAMj5Ny?||*X_4B5!xfbz$z7`SN3Dy<14g)hEl))thg5?aYcuBzIYj9M`ziTPkRF1ciu99faVCY^6r)^x*Fntwz!`btJm zT@30Zv%0B%*jS)#95HP`f!L5(OZGfwX)uxdICsIYI+(Q^dV|;(vyY!3G{Dn{*&?+l z7N%t?uIt1$$AK=khttE5&FvGtW;w*Ffv+n_nh{@XP`9AANXT#6VZ|;Y_F-2#sc3st zw(P97h%CPthqQ^QkWsik$*!4p*LXBXAPqUk^k-x#@l1?Q z9S_7rat^xlmf=SbLF5tH^qZlLg7W!m`ng4l9Q@9;hWsKY`AAbyUe07h$JIYP&7U>g zrjg|YZgJU?6ZBUaND!%s(xcOwb2F6dU)*fu)zP9-L3zQr$)jU2e`t+XSt&Js!@RFa z)fxMRCj8x4hI1TJ6#QAR1Cu$b8X6mSEXqKn&jsO>7iXP_9<06k9G|;5hFI%YMYd>4 z!F|Rfo*F|(YC9=vWXFS0L9dJ^{R>Bm(c?FsE9&!w4UWyq!B5of{`4%AVYc!`Sm4u< zDbd4nKCWV_0w-^V-eK~Q&ggNyy4jzTe=pzvD18dGbhuV`Ox`oo0-+R?f@iy+{F(eS z&9-j$_7}!?{L0P_O&vAsb7!VBrO!t3<+?A*c0Nb=%;+WHNGY% zg0z6!S)YR=!`SWhpGuO)|A(=!jEXDE+Q!}83GVJzxVr|YaF<{~gS)%C2X~hWZoxG` zaJQfV0(|t$^z`&h&%EDSRcqB<_3NDb>_<-B+B=qQwe1`W{zuI|UM7&6iGG|i;=0Zd zaFjmX*!1OtCroJ3iRY>i8EwQ9r+`KG()hgq!M(N8r)_QS3j1i~qZ`6t!||z6Pey^T zhB)(s_yWN2)*-^yyC0uz1RHW&?a4l*Hs@9}xR5)WTF|ec?gu3{j2Vp-ty$?v-y}z% zkUL<>H{}(0(ma~qD=}hegz7km6+rdUsM3kfJp8gNL=cMyn)Zq&!1F)657Rve1v0r5 zy*ROBx?1$U3!v{HrKFZwRcjNcY;z6BC!&e1MWK^s$5<<0r^nO`?ABCa_qVy_KFit2 zn3q$4&afyOuwQzM3VB1kRH=C^b9O+|KjlKbXB^O zew5x0>l+RW!HENuA`Q238` zIux>O8d3*t`hWU(XhN+Qkw~W)ZxrAVFSND$ZQ{MEujZJ~f(5u1t$Bb=BigXynAm~^ zdTe^K1mDkFjagO2PkWH>TUMD%nY*m%~lXolNy;*?h-& zsZD3!s9VM@)2yb(8dU|wQ$lQLG5PkWb1r7sH)EDo>ZphQPJ&M$W}&6WHjn7%D08Zx zAj5M$Iwsm7x;u9*=csbxSOopLifGp)xjL)?Mm!?~K27mv*g>V<^pfBz4pHWlf0P^U^}8{-OO`CWc#J%> zO{n4~l;hx`>VCptJ-TY&?PFD?nYJCqhq^+)1`2Wqboq@k0;~nP4?3eCrUvd!sSn2+ zbp56`AsI>(wVbo0J7pY_Fi16$VU{230~hIjBp*@6spyPaZ{QM8d3WfH5??2VBzlvr z(6Joltr3oX?J?70$NJ9h&R8_8C>a_Cxnkzbfz+w?i5|aV?4bgx*bqw%n;*KS)NbWplXw$n>=uyoO3}QDiAGKnuG>qaZ3!Bc8UXyRkKCxsezQnWzMHNO?Y2{-Z`2I(iD>CH1vmV7f>*C5gr2c_|q^vqoSZta_Z91V}MBQ$2WDS9+u~9_l z2(!-rnUPUbj-6ct&LOE9w!QkSTo>66M_|NU)N%Hm;vSdZq6Lp`bQcyAH+>20iiM%a zft#xpO!+K`P5qcT5TaW=Q%rm;u)COY$-I9B3J~Im?rUIKC+~+RgG81Dw4=2Pgl#Zp zMG!4DhSlvic%*tluPF)PN_@GBy-h5#=Zc-Xv&L;sPSUUSrrkQT2|c23SKNq=jxv}W zH1ZlS-5|~~ZP?m8M2}(MZeaJ`F^|Wd>;F;>a9|&9Cw_&T=gy!RX?vi5YjbTv7vb7F zCiiK{XKf3<;1P1R+6qTI932MAt= zrVYgiliF7LLCd*v4QhEyKxgz~>#R=p=u^bl1VLJx0>`+AhgxI7j%g!)&L>T()&)$% zJ%ua!92|iYohl8i{>XeKE@nH;sPnDiE^e{UQM#e^JX6uS=N1+;aYJBQ9T~$Tno`w| zQGr$2%W;R-y~mWF9Yj^BNF#M~mQ0ZA?$|z5zRInQ3p(EHDot3*q|0zC2qNO(>SY~w z720W9WixNGNcQwfeBs^@%V@iMh_2dgaSb|xV?n0*zPP;a^)+Y%4F@I(i|)CX;+rh{ zl$-sUjvD3g%WZm_wSN&SJMaTs4zYP04UN5WvUb)_N@;%b^3+x?hH2ej?akl6Hm ze3+79r=Xk7=EnS5)RNFt!P^}qjGj)xbjZK+WnxU7k~#;P5c`3C=u>pN6rhiDtF3N- zc`@y@%hw<;X)WF1p$AY*-x}>5W^<*N>578X!3vY7xi3xJyb)798HAu}i$%Oc<_S%x z07Bkp8re(u8rg6YF@NoO)MsvpoqmI^^tAh(p=+}Cuso*p!NvkQr5~EFa>AUrdDP6D zJY%52r<%MvY%+%2E_1QdLomL!xK$i7D+?a*iMgf5U_Uc@NF;3B~$l={fz9)46=g)T@;f)|aP``OU`epT?fTO!*$Hq%IR{sh*Hcvi~%E_mugC7~Bnt#$s^@C~_ zgbz!P$G^qq^7Tl}xw^eYIkD;y9UUz_F+n+xEWhTr)D0U*z+{OQ zZ+1KLxp)qZ`F4(+aL^s&T{<50ao8+g@~yk}mn3YoGud6WZH)#06FFnWk5> z7~8u}#CsrU;@c7DdcN-pxjQP0Q^9+{`|UaXU#jQo@6BsFG&YzvD)acX9gXaFM9=x6 zwiT7YnE=UJuGc}ZybP0Wu5s;#@e)SK4g@y^yF0bsL%VmzfS1%QoS(^SQ8VK+UBYANyR}V9Y1|Z7;PmoRCRQNN*IgnX&;t{@6m4?CLlO^~ut3bwbfRp9G;1367YHK;{`bEx5i}${o_uFS%WZ(+q zzWV@?_5P*R47`*WesuC?5{IqI!+j$Na*)qZc4a?&Z17fU8WzOG+oT=ZbA-K61@3!6 zf?9R@lLymG$)_JGgYY|C>noJ(0nKSM_1URqLt&%u_nzwvg2{K^7=&P*b+nY^^yX{K zi`)61c*z$E#hG!K8W^J?mJ6zNsgtOJ#4JY>gp9?Ga@FQ8q`8a zZHn+Lwd8$YzfPQ8xwi`dpP+=pn6O5J59G6qhZ4n@z=ZS90E6F0+7EwvuJnRIc3R~B z-Z}NuHD%c|XHalrq-kLATsy5fNj52zXp@e^L$7K{8$GP~M4YSj8C6w4rJ_TrWNhRc zg@^&Wn5QWC;OOPr zMivO%E22*zx2la8D0Wo!W_eGnYXa7%{9fsVuc7II&NQEL5olthJs%MZoS{^=DkSV- z9Z6=zG1GAr$7p<2fBF)fkBc~0X74-gDGMhxHbm#WV*r#oTn=o)UEoNvs7Eu+k6%OT zr0(XafM}tt*eL-xu14Srw(vMeJ-`ekbS$zppJOEXt7Q-?Fh>`B5@Dnt!t zKF7n5`weSC^QE@lG?bKSvVL1DMxU=k+3K+ai^r#)WiYe=$R41m!!dHesM8g(Eej~+ z3FW^{Pe?%>oJAAfLm_-$FcD#v`(<6s@K=ac+AgXpWTE~QZE&Psfz!x*sH>Ei6@Yuh zV~m}2`yBKdN2!^J=xJUW>|#YXIz4P`z{>J6ANqvZu$Fi43A$(eh|u=VuXS7fOYVB) z$UZ~<*k}YN0s%Tr7hpkn{@7_X?%F?H)+9czM~6ipLg9p{l(bu;IID=`->|Qvh>^ zf^8*JoZj7Y_LMtR1kA&sZGYaM7_;R?8@YD?glJIWBX$-t3Usx}Nbl^CXOv}u=#NDPv#SeLsYQm3?zK8){Pr~F7l!a8vhI9C zQGGx}=3U~^TzN%+g5NqtZl2^$`&e{YqWLexQ$MMt%N0>!T;hgj(q(8+{0{+*7Sj;m z03MCXcU$zAvmv}+3`3Lw{KjQ)w2HJ>pg_ARiv^gegzRVu_9gfisXcY@_m9a$1f9tH zdAKf9QQZ3Q#EV02c{zzEJg4- z_S6!S?Ci#Q$_812WQMY8^TskVJ=qN3qSeUKS@y*q83W?v=zsA^Qb{6js{DkVw|`_X zOMPU>>|k@|H{_&{zYL|t)_z~@7lZj(h(oQGk)selCn;Cz=p8BgtFGlh@7IT-TcuH& z16IxShucvn5b4Ljbco1rMyk&MgOSm9ll+bVLpe`GsL0W{F{p%jS_u;vNt04EPJzG! zIN`z`!^RU2W**gdqJ9yV6dTt9Y8cpVaEcLLkqj0h-k(HVq%5{~@#n3MtI3cfWq$#> zXE&cI=06)1Sr|!z&_&P7cq+vtBP(&vSZna%jb+imX&}QsFK{mHi@e7b zqnD^xx16DMR~2JGgv)ofIPON9rks+ZQ9Q^8Ga7`L_z82a3vsgYRT0g}T*B|Hm=gMi zXC96gG^)p+A<5HT)&s0~>&Y9$*58! zDqXNCWE80M8`S2Pc!O4jHV1hUIxVQ-i^#Sr@iHlqc$Km=dXmP^9~(R9tO~~tUJJ-r zGLtngd;Fqos<02udTpxuM;Q{TxVNg%u_?}z>r)_pS|a$D!apu0#{OVo+u>(_VYXP4 z7$O(NFn{`<;C~u#S^Lfj-L<-Gf|-?X4NGdU9$yfYSXNUFSqPPNHeumCPu;74 zaAc~#wZ<+>PoJbaDcV*oMtS(1-iqQ10%P|aN2PUIoc}AiDCc#9ZnCXRE+>Mf|lmCiS+HwW-dPRC=n-5|4m1{|2nPE;Mw>yy)arZ6q4m_M}b6 zL=CI3L>#wP>Gh%|aw@DnG*X>N!pL67*94Bv_AFjEG(xZ1E(;|-NL1@?p9hSw=(T)KbN0x431xgW? z9S&_$AzNDiJo@*!uOy|+&&Wcj$#u#{_GO{IHq*WJVL241E(52FayLZN6jG8xVoA`> zOL7SEKo)5Y{>UkGH_0zcaQiXJUx-;sN5L`m0S7cz0Km&ZSydC*Qc0j zcr{yk6PE}=VR>0mbUE^XDw zI?pQ4#;@A9^jYDlom=hNI%KuB`L+0=`Lzmv^}Cf{<#W!RpN(Jbv(c*G%uD5+pY3bD zZ%xdslx^?Kr^;uZGty?iSt%={xfhiuinEhdo+d{dzbaq(C(JX3)l21GQ=&32=g;qU zj3)->Dpo>Ko;BEYp+2)$v;?1UY8n3u=E-&qxTRwa5jc#4Gk)dX(6B<*nLif!;L_%b zwZMo@56Cd)G!t&dM-N~GG$-&YRRU4;7Fr%K3^SGm48Cda<+eQ0UUUO81fkTQL=`)mzTrJ}-Dgy%%e8Wj&usSDgeNQ3PS z>I~LZtOBhFC}nC?jxGoScCQ0Ox{VkVY8e;AuADN>tPCZ3ny{dTSulTRW|ZUH~3BAB@5dTQE}T^wX2$Qv`q6yF7^Ffw|uAr(F@)`g^`JU27_hJ0kIiN0EwedIp(Dwd8tJkl*6e3T2@*S42T&lGV)W?d4c4b&QL@~y>=m<*m40GDzdkHF(oh#X)O4;~v zs+?aDUg*R`lY()qr1+bavnV^LTj4u4JV7~@%_0 zhA-LIdWPaUq$HH(4Z2_EY10ZUN zjOxydxyXXg{WbdAx&#*yWwEx8J4vP1{Kdzg@?i9y@1X#@H}|ef7x1}4N`#8w8qeN2 z$tbS?8aGL3pU=IZ46(-S6@62S;-sstXC*o~)1strdqNWHP9qjY=w#<SU8^^4jN+5s4 zpRruJm)TOLG~{W>lJ_O6dWb;d^0c?v(et8V!Ljd-wm*a8`rE%)8OccFDUf7DL?4HmTpc9n$O-cZ zUTQAQ!;Y0KWa&PT!M8q~MUTn;g4Ao$5wVe-$jwPkV&oKdR*`2BH(jg|v6c|wFwmfY zGAUTG1K&Tks3~EN5^o$$xVTELYaKYEY-BYesQoaOd-CJB{%X4m1RgD$MEz>WiR#joPI6JRD z(K(2@lXOiV;?eA?*OclIH`G-nfQnvbgHvWQ5FtER9C%&U6!(xE+Pg_+jy~snSg?C5 zT0msmRzHJGpejlxVw$@2?f{yayf#}p*&z8n2sxq4rWA4sE|#-XZK#xfMB^Eodd>*p zxt*?6&687)qPqzHA<$~}Fy*bI!JW2k;`T7b=jndYPck%&8j~H9pvRvT--+WO;sEqG zd6<#4bJ856gK`9u#!GDz)0m7)PMsu6lxBt}$FlKmWrr>X08Jy>e0)0fL`f!^ ze0cEr>`a;O@Y9}RKn-K^q=a30#y*OBM0zU5z)?<=b53Ns{TX6KBb$-5NY{3q$&9K| zz{h-|rJso^dXH-59j-+kHzik(7yvuprH^hQzOocqZgb9B+L_Sf z>BXLeRuTX|mGgIcGwR7q*4O`*BBiz5I8VxRNT zR%8?r2rCK#3fGkycKg}qWxAw_a@w4UdJ}cj>MjfymrvLYlVA|P);)41^P~tNHbWrq zf9fYfU$Uz%*5Vb{K#esa#;Pi6Wyk>Z#S1&cliN+)`n*qD3^}$Bc~|Ma$3=&2;U)Ic zuQa8t`m2ko%FX<;%n=~un>O)r)RSx?7ls3y>=pey4`x;2718T4PJ%Q&{mic{-) z>|!H=WRD11(4FU!esD+1fOv*5P&#-WeT?FKibnlrW@D>eX^5gU;n%1IO0K4M={Ej> zr}nUom`$`VFuH8xF{Nr^VmF24y@i4~Cbs)WWo4zu`+WKzi-O%(#b8_oblm5eRs*5F zovD(8BoR7NCho?Wf};JpH!>~^M1&6slD9<80o`d6)RslvQ%oE96`uIZ(1*wMc zOct2kn~7re2wCrIaI7$_gvV2$alMm^xvCf^qHmj#y~-*TOMCbY_wwpjUYdvDQAgh& zg$7p%`BtOjiCah$UO?^+7O`tl(&p77=AuM+3Cl)}c8;DmHup|Dj1lS=t1wvs$OP|4 z7nEtq8FZl@*c8x9w;)!8VdWUTSM+hb286ZIBt)s!3CU_K2?o=uIT+d;NEzW6$V&5J zpU)t;?409K^y>q#x2@N)jS7g2Kc_PF&o14NRtz2BD>PEb#Bk;@<<^AbLt6yQN{~Pl z>G~)*GFuwZxh{Sr%p(E9>@*G|L)EX2AY2Y7r={h8@miE2R}ZW~DCjcf)N<7fPc7S7 zlL_Hx^#_%(mdE&u)lch#V38W zqe(pDe9Zb{(xzEQvyy^4jT$mw=pzmS)`>%e6~;#Y9_KS?@uTv4T=>R0*sk*e zI40qsV^xi)c6^9I^26THkDePkUct`G(nicz-}8TZC}w!h``BV5THE0nl?2FIaERtl zLHpeO0<{O(=eo=OL?+_+IdEs&WeH{-Q%!Kfm*;NKB|-5jb|;b~ zkkFQws*z^`Q?S6ZQG#Cgn@+~=WN_v=qFoCygyBh5V>rucU6RlQR~_ITTk948g8ily8t z<=(04CR(X8ay8yi1QoxGor3S#Z!-+Ey55&hoknh-sknj;Rl43|cdN41&~Mn@gsgE5 zdgsJ0VP7B<9JLpCIDXj~$%?$kIhEch{MyhB;fs_9me86Kf6yXEZ1DsoaQXt~&XV#= ziRzV4KbHaG1i!mP!-F4t<~s4gF8smgHUwbAmiJ@|F!Cvw!kw_BIA-Fu4K5ZgsL)k5 zxk4FOGt^mY3l3sy#l_(v*(XbNt6)rdM8GXkuXUNBnO6*?&`d;?X=({P>HiXpiij!9 zsPI_F`q2L+85MEe3TxGNIZybsShnc-i>{KU=WS$yF-=u3=5#h!rhat^)R(%^_sygS zpE*}-mp@A3j`jBlbW8(Ii)KQ`>V%06%2A#gXXZ`tkk9hm^{+;Y5Pv{LGb>!u6(M5! z9$Ot=#LmnY;~S)JI}|TTM(!&ql!1?7%?tJ~!P?YOvwJG1q#T7v zmV~Tyr-ifuMf#{nmW(W-w^pbDGxI7YW4G>)5GlhO2S1a0NCAv8?y@haoDu942^~x1 z&0C&;cK5+yva^1P6vks8XJUm{09%GYl5Maj+qzu~#c2T^NE{^D-@~(-qe%MF_%75_ zYR;boTD0^cu5@d|m^5xEL>jtNJdr1X8i?cMvYoeYVIY+wH=w({6;8fmAqkU#A<(Rj zuCgYcKD60ypyfmp`kasQF<#9!(UB!wEkSbNUcTHkqqgre=(_5 z?rnBu>k{k-4G}?V6z^J{&mdXrKJ66ei!3S!QMeYg;^horg%^|~+18CyH0SD;q0u@V zVYj5JCwPX#5}S?w9Nr8MBs@*>^~EM#GJ5;7>J!iF-Y&0p7kstAaaSJTGGgVhg4R^H;S0qQRqX-oG<_l8m*v#GwEZK){etWrsJVg_* zZu2HQR50!F6^ir& z-cg4^g4;VTmQg89NB=<~Uk+g#;h0i#!D^erx0~dqh;!GzaM3DcBnk}i4&}t<%wM;> zym$9LM^TC){dB_AWffQ|>#4=z>*&BWw;a7NK{Kcs@zM#=IL<~-EA_s>hm(ccCR2iu>cc6{X6uoLSbB{1u+BY?0u?@DfonAiA>N73=)+ zAIii?cW2q@NeN?s9B$#wsY$Fz_E!?zR)k*^4G1$>6uT`54XT3;5!O|ruLuc#rc;c% z#;Qy9px$Mp6?@V4%bZFGp8K(sa}_0u z9%R2GAxMQR7tKq|5i)V9x-`v8OwOo5UiTjL$n+lcXglA0cPeu5`4Ln26!`Q3=H4{l zQ&@L7EVxx$@t~A@PM7UYfjrESBMi#0$dfyYbA4M0wUcS$oVl}LbS+kDNhR0Vw>E6a ze&Xbr#Qj^wdJu`bK>E5kfwBb`KH_YndNF@nW7w3z#6b~>duPV_4vG8i#JMbLr_jW? zHEL(~#JLV?r|-l$Uu6o$`42`qbTX(3yVS6<%7~ zG3Ds4<3JO(v({^FZcn9k{97)w;ySYdBjj9R;uT6%{k>?y@=hVeI&RF-k#o~Gb35fU z({<(@uMVOMcl_^jdlIek^?2UGMLruqbpgqUUc)2kk9zFH&W`dk8^sZY?=tV9HO27} zE!9vv`6e9LnS6m|{$KCX0oe+3VSbWxmjmZhkfo7A^U2n()ycEfrNw6WbalhF$SG*O zW-w=lA~6x?weL1)AF0!NR!ST{U`MM8qmMSFWDI?|so=drw&tQLJWbxkSmfClOO;}f zE2Fb}O4&{g)S7vf+DhvDNkce-LDPC$@#3QN>S_7GPQ%2iVCPyZuYYU0!^)v$;TyE2 z>f^N7Q*D33MdWno!mMhDk6WIbl_FL}F8P8~CC#ppYp4)z&X?h0{3$G1mYgX7$}Z_d zaFm{SFMcL>HWsr>1ivn1Ty-~YEOP;#K{QL1h6Tb&YUn+hvBdBFUD-+Q+!fsURk-w(O5L9Kq zE2(8oM~Z=t#Dxj^W3CKG8%BHNuZT%rMUwp}PV6Khr?Nq(Pg0aT;&`4J7L!c*ag(P< zoaf$}4Kv3WWk;B%`z;{h=_5w3xM z1PR$%-5WeYroEtP_bn77I15&i;Dlu*r;2#OJtD#TYl5ctKzz9Lq zb@+DRdLZsP;!`jI5OW>je2@^3W!W-iIzIp60D}Mo&b*Z``Gss63MGhQla?1IHVAW* z5-t!8E(ng-JY_mNzm@W^T5xxc_xJ4p!v#@I!>oeLouFO)D>n(+P#oTd(q`m;mz#F% zKSn`)W=6U1^Sy#52~@g-6D;+H%nh{MWeNJlF5|Mx9u3>6Fu5ljmcoI>*K!M}p2Lt})9Vj^P!mj|(twX7TCG3-`gSUgx>(i=3wu2SuQ>sHeZIP?8 z#JKOXacmv%#fd;RgCV?sm$emJ5b26A@FIWpZ`>r}MP>Gn z+QhJeJX?%sZj7(FjOTZ1L#PQj*@S6>cL?~liPDC69`LlNLQBy$|>5%(@oTs$cPe3r0^D%4{Ssf(~L&k{92rU6;K-gt?_ki_$l2dSR z5MWv)El|n{j0h@B1d|sjR=b2GMpkH%>DHsRDK|W6G3gzg*gGWhO)M)IrhM#d7|T8# zr?2$4J_^Sn4i&iGvfWtHHiTBNX&~%r@R~r|P5w4;hd{keZfw-b`OeX|xrEOE9aZ!# zHGvNv5+zWA1!@qv0SBAV9`J?%OPk0Zh#exT2xBaGh(iHC^8dLP;Ogp(_35dF%E~jA zeV^YzVq8xe= zEgs4+2;K<;N)#?w6e=5HG9S7raBdTc7x8Pr^(I7{Rf-dxhZ zDAFT6SsZR}KMxmYO9!J4lm)Vm5bD$t)rk+o55KnRt={H;B+)wSCgT03C^AWxw>`y5nI@BF)gq*nR)q7=MfD1qYnqXbd?4$t9w!egc%kiz~^ z6Hs5?K_~@SOhf2`tes%8p%3~zoxt^>(nJWYTID)V7i-&YQJBx@QI`7LoFLbrTKnvs zVAr7EcoG3z5b8}b{0Ll2aFogMZd0P8;$YA#9SgDJS-cPs5W03~xqo!6>WWT2?%|Xm^9Lcp@2YU9KXX z{T=@Va;X1-e-I}t@dOwZkeU{{ah8!SJ*PCS$#f;g-p#KT~2(XwZqWYmztISV~io#z_-5|pLs{B#1VR8E;o#2}S z+@0|MgnaXuQG1*u0YU1Ilj&=a_mk;@V5p)r9#AAfG@EoDFu6gvn^YdqhCw`=OotGX zN3_yFMm*S|AQM(_1*oJxGgfE?=($;@`mEfVDVZhtS)EghZ50HIfR%it32+yXAS)>& z7$S(g4v+B*%32Fd8>)Knc4ALN3E`2TTJ0lK6lrb;1UY(XzZix!)HL zE)8OHBFYA%5uGMbqcuP+J3%Lzr6bA|MYMu01K~U2WJ4Z+pqE>Q_d9!PJdW~P4+ej{yt47WPMnkK1C$ zO;qr;pmucGVnv*QF$bcgBkl)00Kv84Z~_K_@Y;WvL!A;0%VVyLM`!JYKx}FXSr1$P3e};VTH`wcxaGD>7aooO*vnt{xf*hXQ z`77GDMSjpP{RB|&1*?n_ypiYqPxSwj_tg(K$qu37`b;ke=x?n{Ul`T>P%3ftkLA^m z87>LBz#T<#X^(@hV1@!e00n7bF#9A};T2%i`!raQ6<|3;WJ#muGkkAm0gsH@ljr@# zskd8d2b;YAm+pmV+8B`fnv?osS7`-t0e{oIy8mz8`%`Rq2N%E^?*FH#U>o`Meum`| z%K$2k7Sq#am8qirUyP6w<{CsRh}a4Dt>|SDJmC026qk5{;L0G5OQJ3?7SJE7j+%w| z)2h#HWhaw+7UvbtoWy_Q%9G?oxNq`Tk&*ekLo6-*Tx+_y=vGSwVz>(4DZ}z))SmH$%Zi)62{C zH!Y|ixWuyJR-gNa7M`VKmVfuC;IovS^yJ|BjTJ`~6#r?5bk$v|Yd>|@euC8E@uYW9 ztpNY6N>JCz7|Q4Ue+v*W%5NVmH`}@9aeZ&o1mg@4t8lW&fue(DJiImGtYb>IIYuNA2ko{TFWd+tT&KYtL$H4{MixOuyg$U!lo%r1SBl z)0Av!=O1oRC+J`Nuz%4e9xsZLwm4wL2=#7BTyU2a1>FgOGw8pEg9Z9MUB729zh{^< zXUwoSFWd%s9_YOZFaZC1Y=rsG{A`}0{Wc(n!@oF&*#8Rh-xRUWl@&7CKO-M&0-{m$ z-xCshA4X6KDDn?O2u}nf-KXk=L1+o#lz7+jgTxq?9!~ zwdwH}viRr3Xq)2W=>I8C{!<^7@Ib*_@-MjI;Z1lCgn!QhGa~cie|Q2Ss{}#oe1K;@ z09s2OmVI>2=|7JUP$ys!`^4(t7-8i5)asC+MEJ_VQt}Zr^h{MaREz*BwIK@#BtiTf zF#l5@{y02L#4k^bta zRM~*rfBL5tNg`oyB2AW zB`*H>flhKoe+-Xk_^*u+d^)@wsLIYZie8UpeDT2S8YhL$oFw*trvDI4HG`HOc0Hp( zs_G%*zpMTqocHg?ADt!BSMfLIp@~>TqDC_3;}%9Tt9IiOx|YSQ_9D^D+_NCSicJ4S zV^bC5j-W4rn!|&8u~?IQ;#wY{ViLjn9M!q8_*W~6?!!g3_^t+Up$ zkyiW2VuycK!L>fhJpMFto0=+g>z?57K2?awsi@}&!!LyVP>!(&CF@`k1!*xuf2-5< zOBsjhHA(Z3eQDeV*;)*li9NMw#Tk$osTPK_+F8VU1!01|Z@f)D_>3AjdGtc|HgtR{ zFF(}B*>iwH6kEC_0?^s3$T)H(tSMA!4l46#8HL~hq%M~!SMq$ps6>5ZCX*nS%N5Wx{h;K^{*uWAx5$JIkT%3xF(Fc#LcwE>m>`B-xK_ZBGK|e2 zp74?6EExV-31n#?b#@Qjj zdx=%U?x4lT4%&T*j~`GTS;B8tGC#x+)kO4e*$%@b(EntsnN4cU_O%`aeD0w2%9Z0* zM7D~RlPFAWH>-F1A-HVxzI$AIb@UE(`cicyW+~9HRkt{Y^wT&g;Y;20ss_sA`L75e>evz!)&5Z*T1+2ZLT8w0#Cl&_BKBq&i*#;6`Ogj1N#rEV-P7O)pSbA!)k_A>^ya7}g(->ny<_$NV zh(-y1cLyQ#_AQ}dLhRm$)+`K1lr__d=Ke^j@T>wa0ChZ7oC?UmWr z)b$jf_^Cdypx>G>7{G+GgF|Z;R;J(Jt3(*SGJBy{qjbpz509>RN+^}X0T-v$!9@@M z_K0+PoUKqzvaG{=`bd#z+%CVWn)wq>%ezy?3?{cFQ=Rw48P&c+?r)7zZCWxN6(+RA zIuY(uD$bfP;Ku8(F6{*MD{$^d#@emzK5WB;NWWhZxVaW1QA2PEhfbaeHKS^z%y73G z6&{DqNLvZZv}+cOeDzZ83wlTK$ZCgX9Rm!@z|ppg{-jfJF%|kDiM9xaKX~9cX62@7 zn<;@tMBYg(WeTNk%%6opUTaeHRg=x0*>}o|HOnU0;d_7yZ_5vx;3eEMGnpsCD<9gd zH7^g8BgK{7va~&8ZNqbWPpH)QUV0=U=F%+klva&0gfQ(1z(|40yj=O4mkn~ck<>xE zQJ2VrX-+O2#Z`8Gi6i5e)-iJXy9fkAH(trGoc#{c~OxJCZ z8#GK?aDEZfvB^y>EsQFzN7%E(?0zK@Ke2u8N2=e3H*wVpq*lE8MN*NsG2LOGa$Pel z-!M|7q~>8-Ki3Px$6hLF+}cL6sopB7-4aH!$=)hy-RecM>D(%*-U>vrN!}`H-g-x} zY2GTS7ktB?(+I8;cil4h$+G`7>Vab+Fv12z$+eqNA=iXK_ic{#U?;HKd0BlMZKy@c z>S<74w;-&gxsR$s&gRRAo)Q$0fC<#K#K6Z*A{rp6 zA_29|NR(y&{{V`QBkR9A)>1**vL zDv={$)6E`rV_S}~kIjdo&aYPVW7+!g?2klUU^`U8ZXYTk&s+x;h5DW!Y43@Bqcdn|b z#$44qPt}~pJk>j2)m+AW)w@8|5{(6_ccH2!84FeKB2`N<7OCFFP?(Lys&|R1r5Q_9 z?^0DuHmW-qou1En~InU88D$VyscUYgO&v8Ee)2^=jUFruyt-OC`|4xZZW@r?!#n)B^lU zRlb25-!rXXx`h9aq>*==UNxMaopD1v5Yw$h=1iAi{m?2&SQ>;;ycb~ zj!p3$tC-`__>OhV@z}eL$e9LKpoauUz+=dN$61t3k6Jok3g<+2bPEC2w4)-vU!)c6 zl2P6^py+Q<1^;uZ=NsR#enHj;-m!j3)(6?`=nJ8BlrF>``GR~y27F3;h8+@VL%htW zQvIBi>KCL`zodfQ6srvHnngY{%gBbqVOHV?(t;RN;J2vWtyJK*s@`o>;I~nAcSNN9 zK~rdj3C*o#d@6k*?*h+pxT19iLl=E3%V;JmCTUyX( zdIoux8SO~LcW9xon2B_d^I~k1zj3rE&IM6&$@YDuanW)gEg}DMvA8u9+r~PqIV#Yg zbtlkzaBNXE+sFq_zPef!RHPfx^~~8Cw|tinS~eE?P>5EwVN1I!nj9EKtF(noohe8f zm1m;F7YvNE97U^nMg_Z9MOX98vaaUY=^Q&W6)oN`YqqhF<^DQz z44SKYc1BnA?2N7I*%@2avy-jrIYkn2U68HjiSuA=EzhZvOxpv{8lIib0PH=}BmuYB zO%ZcY6xASGwG&s-4U=hoY4<3$YUe0()y^N1lJ0YvORdu`0~B^5D=4ZjDL^a5IDE;0 zQRcdwqhjlF-hXFZ&JK|lqwc*@l|fSt#i0@6tzu; zyGG%{>(Nq#8UV%{Q6qmyZ?gU|)PDJoScx1X5uf$X$}%QVxxB@yvH!zTjqRdpY?tcY zP1V?L)myJ>l2NaE_t5)$kLuk^@9({;cb}>$#y%>?w^_wYEs%m;DlH9jr)NcRH}+IK zHlKodc@(AVue(>ODl!Iiz}< zC^}85_b^50u!-5mU(lPg@%z+e z99Q#Cs(B~b8~ncLM6{l>jl$>+enKt4ZwuaR6w9GQ!AHu-Cr+|$wZHA*9unq!_iTMNyUQL!v6d3<)Yhi3F8krbPX0r4seC&622}Z8jC-7cnK_sae1WW*P6( zl4zE<=PQx+b(|owP}$~Bc)5v{o@Y+sb4&k&WjhPDE)?Hfh`keiayd80b_r0!w7!^6 zjj&}ieX22sMQtvN+B_+kpJVR5nwujSOPQJPwMyn?v;z22iB?{pBYBR=qA{OrmPxex zsA;nxx}xC1=*gn!$>QkA5-I3kEEQBs{+%jY$rCQPo<+&OL@JmRb665{poQP)_R&Vf z{G}hH{vTS>KRk3_OX^+LYxRb`G}%)63njiwHCr*iQnCg8i>x+PWV2B6u+`2Nu_INg zZ$;XEaRJ+}gr%uM5-5*gn9B`wnPDC`%wvZ6+%TUR7I4D?W?0A#3z=aNH!Na?#oVx% z8J2JZrFN-g_KhO*k+Y^@nbh4;+7;6vPE++=M2l)s(PJBmCQ{B&H{o+9+d7oJmu_=i zs97WhWDJUzZ2uW$(PGJ5Y?bnVzM22Xw>i$wn<*xgW)@T2SlUCO+irgB_QQWTdSQrf zbvd?M!jh;2O4pR>xIGxmyLbGZobCvv;eYa@bSDG*G4cv5glBZPRM^zvK$2x$CGVa|Kj$RK|2r zO4k(0kto}n)3~b06}WDt(V`o^B;AlSLnwwXql)2jQVf%!7`}8uCu}8^P$reoPAZ|f z5MPIb7G_rQLl_qQ?j_l3LV*9ra}xvnKc1Tu;Q#U5`z zo|_}U|Kqti1N=Xpn=8QoTicf}={;xgihOE$%2#u1mo#SP1gBaYu7`|v)Vy58b= zhB=4#WllZC@f0Kv`!RRxA&wV^jN$#bTMuz|Q(XUd#qkVt4*N+QaXbad!w19>C!691 z#u3MFkbT%s-xZf^rtZBs;v`dCRvdA>IAjd_%Q)ha%+w8fSDa{y%Z?*XH8bGj-oI#YKFMz^GQ*-^%RzahY8|3B2ovb@iyw za7a;0d)8&f?o%3~o61z)-u*n&BpNlQj^c1A;FAMBCE&ArmP=xU*0Y^1sZcFxdQq(Z zhxRpN{aCdUbu`alJ_e8vxI*5%cf6D^FMU`b)GyS_c_wr23+D)en75+0duder)lX7e zVpdGAer@X2Sy@Ik>D9BPXrJ#q@*^g~&pIjRvq8p1wym|)-E3VT*;UJ&ll9aV-au{P zt*kA)O$rvg5+l?rZER@?a%m*g61xQ=6=x0G!=^aZv7jig7^ImYQ`X%Q?XuRBO@6!-^NK6 zo25uzyDZQiXx&@sMQHDupnntX8c}W@`ToGBcS^`69u*l;3HrB6e3&MEV=SCPI}c^M zMBT^bREE%S-YIc)J?P(Ip7DMA?7Z{A++~m-p;@Q14G1G_qh}TYWSM^(n&7;}6zilO zTIh&SE1U}>uLboIo61Bx_fl&4tdFzVu^oEhA58>vo5Z1oPDH2_s-M&68o5JqxExN3 zA{sQb69YN#j5+UQ&a~k3B8j>v*7Udmw5g*dL;j03V`J%;W9g6XTWCo?+q;L(*ru1Z z^d#C^G`cgNH5X)_3$1KdZkl((x;4yMGQao6hx6L_jgJ{(5w)`rBbK& zR9L=ITTG>VQyof=d{bDyap#@PnR>q##~!pd=7W}ZCkFeWhnq?^zl`J*#@p(GbHq)q9?XM$fC>3p6x(LG@l#H9X;}_mZl8Xk1dg zZK^iZXj8rIs`eG5UG-j8HQl(ZdatP3Fyo5q?NGI^8XcrNZroA5cUA4z#$8Hq zxkS6uvkgzeKcG>l3YJvVeED50sk_J z^Hy5R-JQvI74jFk!t9@WXpnt!gxb>|(5$?ky$Ch2J2=M0@8J7cMm^nwO+t_-o=4-1~kMOt$(^pUM#uQbuvL%nEwB+q@>X6%o4k~K&)U9M4b{|s>(_8$w% zot_G(t%CNzffJr3f1@;XFHJu}NoHEf2*cvov|6V!rYmw$qbYN`+|YCq9MJ2!;#C%HV6 zTsCGM%NQ~hjCXzDnd}m1`!5u%@Yq7Td$2iE>q``HDB_>O!-)8&Mo*@(lRvb}si(TS zD06rZ66<3M^1h;jk`SWqOS#`|*3-FCiqjcNyGM6-hOT1I`*)}>YoCdq5L=8l>2EODs?JNvuRZWb?yTOEe)YpB3U}cRz|0E(n=La&VDYfm{uh zj-s7<8zgi4=k73bs4OG2yiS?=GWW28z27X}-Tj(tSaegMi(JcQ!XL>t(v#Lo(Z2HO z=9BiNEMpMufy?^(P}zAKX=BJn#oMF|E>espn%=oS*00w@ip~a!#_ZiurQ|Tm`5v}+ z%SI;kD{x@%2jd8-A^!cUag^yPWEH|$qhdrPME(?&l}+89gh!~8aC5i$D4Dl(n?q#Y z%DQMPs8hKhJLK6b2wV(Z?kTeiOcpnVp%opAnL;ofkC{%vbW&nLY{F^1z)m;fG{oXc z((v{djBvCLX^ozo#;s_f-O6z1S|#n9Y_AnRY=6mi0T;vC24&xIrfZRca+VDo{)MczB}T7_rE3L(MUwt zp3VnEu}ADrSYz+boX&3E4$IAJrkl6ZV$nhtyI66D-cT?d&t$#eX%cAe$8JeZlI?d) zJx(>Z4^A$0b>Goe%MFb)_!OHg5j{x=@tvJA5}~~%4Eq(;j&27#FJSxPAbI8lRo1;v zW9|GH%Y8d5tTydKwx2h2UY>fD6kXGc!b-8}h3bBuV~Xk3Y0>EZH6iRw?v@a{td}Hy z#--5IThqVG=b*CVb451>cd!kUou;v%$PPt(nL{WpO!PT;qG@7Cy=0jfvPV+!oZVx) z%QWL@rOj`wI<>V?W8R-JcA7Gd{{)IcUBO(i+>O`-^X$ z`p#W+Qb{xQ=@V?f?GW3Ajg>#E0rf#RW>BpLBsdZ?I4uVL?ZOy4Rc=&sQvF4l>T~3d zu`4WfyKEm&yxmpeQq4$-(sZ{hP2 z&CaC}^y}FnK6Va~jgst=?fdC+JWY1k>kJ=Y8``?niS%q=? zG0gr~ut$A8d zp{q;!+pey$U0n;ix;Bs|bW!q`xOzNsA!a$#Mb!#B@t3;t?uhI;qRJn)=Pj@5X-2R@ z1*3`;j3OEL!9UC8pY8IL(S7Aj71rAK&!Mfw(cn*IcGPM)Bq_+h{g(Xu-jLX{b-mU2K;u_CgLf2>J8ux%2GiB?6rA z7Mt6D)O7xlAjv(FLH(bi(Q^2E*nkXw%HD|mvVnI+=6{_H0Y%+&m1vbcjnFEXRJ zE2!*lAa3+`ey8s3?g32x@B4o5_dMSt_T1X5PW?_Tr|O)lP7TIgQ9F2lt6X@IwefB| z2Gho;Vvt;>hvBo{I*3=uZhBt8``^{LxTQSYZ4_-q`4OY372eU21QvVIMnL^Deb7*07&qOIx}!Ci}>~a)2BphsbJqtn}rH z@+7I{XXWYg4EYs#uKcx<=<`j_gel+%U^H#S<8RM@}IN(mn{D^%im-9doBM1%P&~|1lzyb z_NUqYY}=n}`yIBw#P)Bu{kv`dKHFbo`;XcF6Slw6_Vc#?g6+R%`#Ws^ZQFm>_CK=E z8m7UsS^Hg~vA>iWh{o=%;nFE72Pv0}TyR?kT*1utk@mL=5400 zZfxxURupCZDyMlyMcdf1fxtgM>H`}9>gIje_Xu7_rEC*lnK_zwMSJXA7JKM1U2%5& zx+{Jn-=KjDTiT~-JgT9ZV%xfle|MK{UVChp=H%!2DlRPpcGKbpS2ye4KQ>eUmL17L zBWXl}_TUv#bSGNz*XmA8!e6^PaVh>f-HE^BuiKp%3njPTQRA8@TCyS-RKH5UPT&%W z&(W_JxU9qp^qUA=HZ<>KP2h5h1Fg#gt?N}BJ+Dx3|4}GhRwzs^6s8vna|#9Wk|Nj9 zY6N1#n%p{p#qTbAY!etJiJ$h|ytc8J_o2uqBmJJG)x1aki)#lS#TrR_FbQ?hYPhqu zsRAz+R(Q1FTLLAe<5^Ko;u0XOG!nD<26e9TY*x24$s}!pI$FDONw2Mgr9JRpQ%e=# zJgJ~uTbeWWka6b%nuJa%OHcW=l?CU!r~W}53xRmU>6pKa>oqH<8!Gj&T( z;G>?%(4Rwr4(edZ=mdWM`~w`2JjLC7Ef(osH}QZM63kG_1*O~~rJ}5mdsUxJg zh}i8S+SZKgVa-rxtUT2t+n&CN*m+N^{i03zY2zRl++|liXI`V=(0>pH53cDT0b*!vh;u3*lyaKW7(7&!QIxA z&(fcA??iPlCUE_NzvU6~L}%*vp-&OlVE%i~wp_=ZY2GX)4$sQOeCm_2JxRwS>J1{I z<;QNKK`lMD8{*Exg?Sw295&41Fzc{k7Ke$$h6xG-8K1XZBGjoM!Y(3IUmF9;2 z;1aM-HkU(Bp#m>*FPwqV|M;G)-(oE`T8z2+iB9BVym3BywVNBc+7%QIk>==o$$2k3 zGueFo6%H0kFL6!2V4Bf7wwv$4p??@mIjTGj-?=sDr=eR5g)E|t*=1)@$uYYz1-#6n z%4IB%CiS9pSZlZUZ zGCuLK`39MB4aP!6aCfYKMKFf6nK1-|Qqmb(St#UbYe=1aB2k$V`z@l2{$DikR8X=& zbV0lO9se<9m0A!sVAO{ML|Wt}iPE5D{dC7NS46ik;Lb5@4>)g13n=@*oCF=}ocCYMS z<9+jkB7h^&ZQj@(`pr|1I5cxOG;uY#@Si&t^Zaf*-vKH$nGpdt1x^iWfF4fMNWv3` zhc0(Ild%v##>ef-cR(#>4I!7N+@TNBoDk()1{8B)DwLu?yBPjBuOX&Iv8J4|hUCz{ zc-o#1qqdpW#KKThdoy43j~VL-#EYh}O%AZ)w1NME=0)mbQyMGa4Cqhf^e66=T2x45srsZodQG!nFgQ1C|j1J2|&|>CU4`D z!&fXbqtFV26##Z|4nR4z3roO?q;tqT1vOrVPVMTRS|~Z8V?le6q_fHKqO_Z?dVo+G zB%jzyQru{4qX*BrCO9K%}WK+*D{ngBpv zh03E0k%P==Xihp9oV_Sk8Qcv6d&NjP9LfVgNbKz_{=VJd3qt!i{og0&7Yc$nA}9NI z5|S?o)&yA$TpVJYwW~K_Y@#?A3@h4UZKIu<-3PMD_GBN9EBkk(^MH#3k|td>rYp_1 zD?Twl*+=%DpN0gvJq_Ko`Is|ITpX{~HdQ7oC^~9gnT%pAr7?2op9;oU%nrLE8k{X* zR&J^8$(p}SPTs6ZapT9KPcRni2QYkH!;WTbHtdK!S>mK(1SC)6=7|%o7;{Jk@A~mh z6o7YwtkG3D?h>YGxYEKhgjNJ*6+mR(Y4?em+akPVHuc21CP#U+*EYFS!=&rEV}slE zWn-sVZf!>;dlEOGj`>`Oduip{C2fgND?$nX8!G5i8rUtKup0+GE?Tp)56S{I1?h4uyX{aay3dZ9z#j#=qJ8hwEX zXnmne0WJju;gvMg(x+83C~iJv1lx7`vTM+J8GMa;&lcyh7nbzMi&udtGW?9vC-wK( z3uC4l{Ow(U={mzj?FmanyRoip8+%~|c@eMmlUy{a44>$zf^DIef!l@~XU;*KD5?{L z+vXd-fck~;;G0W}qo}bL_My){w3ae-V25aYJJ!*TC{px0igOOrNUIH<`%bKLe1g}= z6_C!oG zEFR(4Qcd(5h$_(k6z{hKM}5QuKz#<9#dQBKa$MU~(#BZNkC$Z~78gYXmg8cH3Xi{xr``U7c%AsNpt0bzK+EJ)wM`z4RmU_gjAOO9C_XJ% zEG~*;u_z`Y00_7Xfid&Q*_P#)zbYY^I z>)!|G88j!1MFmD5Sr!lAIT8u`w)gUHEe7;Y(@#Gde#e4CiV=F}f%-^LbWlL80qlw& zGy)((<@XIf4%ay$iFu84u;(Eq4hkAMs4811AiKa#We?qRpkUbWB-7DBe_5jraB1j2 zZ%}_8OySKzK^cfThG}bKvdMMn z;}M(wEQv>)>d0;Jh+7@GJsycxNA8G6V$sObc*KiF?uq~DQ|mU(t0VWvBZH!m-Mp8ZAc8%q zs5E<0v&91IZlXMU~8PaA&NC)ZA+ z^}IIz)s4mfzru zkD>LhSXO_2NiD9{(8fkA#>pOW-H3qIdxF2Th{@D)UA28TIW!%$r!RMtq4}Jxt;|sP6OU#N#{V^1N5@F`nNnG zB`p0x!OhYhD%S(*Rm$*PkWk*3Yc~CHHfdXBA0c8ffyr zwRV6JKXlRERVAYbb1IVH~fg!30OAZ3+a%`Bxqu#@ zVJT+PHPsoi0b4f2=jarXD*Zm6H1`DS;*41xWO7`$9+-d2bKO{89Nr1Selzh)zPOh3 z>aV1o3Y%!Fln`=wfMVVI$1V!&3I?PYXoRtelY#bFd$IysvcR5MzCTU=)U;7|;Mz51 zKWeFCg@vu9y(HhqzXr7@enDJRjG96<>y;+yLeSD!-MlxJ$pkCX59}!IvR)2Xq?o!l zh4*@LlEFih0l@;B%693^Yh!V*o2^~534#g{$s2L*aY5H(bCB<(85+Xes22^T5BbhT zGhxSxme9Bd$@>rv!HwSR5cH4+! zVj3fLSolL|GJp^LxgiuVpm+1QO|+05dZH2Ke#qlYCGH{xraj5?BlbM9xnE?$%~&@4ILE^B-N6l%s_wGQ z@mH~YH@ygT5bS4&GmQnH*-R)S$}Oy)N9B&)6v`)GqE3Q#5_kV;Jj1fI z4wLHGZtlqb!Q>3PvLnKtmV%{sSo)A}Br=uDC+24pyxICREEM)+^0_<^1PzpF4i1R= zVt4>>>*w=9!L{YSH-^5g<-RwBzSEumT}fN||3K1~{{KSKmi}Lrw52~@(iZ7=#Np&< zI2d3#+z}rqhwCM2o58RfR6Zc&MlCWtHuG>{!THgw!-ZXH!meYK4ek|IgS8C;XCE49 z4z3V*J;UqRQeg16hjPesr2Kh+$PG>f#rrH4fkuW)OQCEl{nTbk(lB$hu)6GMA!zd) zCmMC!6*NC3WWc_x60xhK60s{#iC}4$$3^6h$b);!&bG3A=+qQmR|vfWQB;mrOWn*^ zpDG?3{pT|F*tI=5MCyvK#tkb(q7awr?v!OdbP6AX+~@>GR5RJp_qZePaU;Q@+)YkJ z%$SP}rd~dkfJ`iB*oQAP(}YEcp{ekQ;=G5Ai^JEr7$?Bk5E|X{0k1%GpQtUyhF2PN z4`)ig7hOhwp1R#CTE_+qoC~@47?AAXlmzp(QIs-B_AC_~^w-T!ocgPc8kAepoiDYf ztxQ50Hi`wRrh-PGYFcFks#u6}OUwz)SaSf+(fPQ;p**45*;cAq04WYCYZr6rgFzMZ zv?zpR04iOyadX^fT(k74X*r%(I+fUy_Qy{m3rpN%dx|%iuxZGf1K^}PA}-w14_D*6vG zVq6X*cUxyN-qlo@_(GXCb&M2g9I#fnnVQI=Ec)MM-*aCKQ|sJFG*L%eNPIJ!!uV5BI-{9z>qE$V?hcOGr1!k1I%>D;FYjrS@!6~pkfD6 zm4Z?|t{<#}lcz~n**B~+wGP|!q*n?H;@`sY@ob5M>!AC2)zBU8+ruNwd?-;Hisu1} za>nxOrne5P1$Mj5dLj(Rk_9`b<44fm$waPtp-_O%112QC7LN;<7gC}iI##@V2f#IE z_Z{*#QVg6Df?Zvc(PCpAo+7*#TMR`_biut2Z1Hg=IAyMObvLD(XEt!dM$kY}^$IR) zn7Ukn{e1ljwwm6-Jn~^ELWbNdQ}z-`w;7SJUh8YJ%GbB?6D4@zBTduArmV>o1AzG& zyD6NU59ahE4-dUMxu5cw|xzJP}vt{|?K_*7{CI41n6a+3mGryN69( z2Ae$|K_E5ENMQteyg1BAD{%4kp7NA{bO!66?2s+Al+NMtn3)6C^JXly$rrJn`ZbX0sE$F7@(k6s%exi%iDyQ*f?bRl}XAeg72Zun0kwcWMDPr6W4 zd)B!k`pM%X&smX$oMQ#;VOf=!+Pui=rAmH_6KWtm5Y**_>eT(PZ?V{chHD2j_CJi( zJsV4X7;E?_R`+}?^--+hK&eX$Jr7ravF7xVk#!{Dg4HLY&-LcdJuiUfqYW)MT&WYOn4$yKZwrD}7VEIx%Jn65sAPN7~LcUbqc6I&Yywxu)f0XuQRs z@b-w?)TgFpmWy?6yW7;are&?$)UT$+Y3e_$DKV^RK+S}vfya$+8dTFVtZ6W%u0mEv zWMShG{8Yoz1*~0Sp1Lksd_SApRd3|H=g(i3Nxr>vKX|Ag5c4=X3!W~wDI#u z{hJ?(H^!DWB}Px90dtxsE^Zn?3)%sLPM{e~M~;+!Dj97N<+ zHa{At0qrPS8`U-qA@{VVp~)f1p%mF0>J8z`&=sR+acyT&Z2`e7t=7YQD@Rn+P?7B% zp`9YMyR}Vyvq`sZYEAupmNlx=70JGbk1?%xjI|tNEyY+%G5VF|@i4{cHzM&W&H7D4 zsOTYqc0ea<01OGV1Foar0fEa(T!r7jWhefDty|!7pk@$g2mAt|fqyh{9%~yIS1d6C zF#?wdn<;Wo;HpT}(eEb%R~+&nIXH0j!N4eo1g^?J|6s_`tp^H~>pm>dzu1AoIR^^A zK2T`oe=80YHXkTFcc4(3&9>_ApIz#OXtTwGeL4tb5G>oY2HZ8!|U!$nxT(p+L5(Fh@dGvk_2B6+6+C9thD zB!_z6c{X4Jvm&_hOQmD;kvCW56N}BUiUsi)El4SJ0sO<@DF{|)R@NZYDhrifXL-D4 z6Az{qDr*tfE{~h&JbbF*i0PEa9NPI`(&Sl9I?zCeX@lNF`m*AXzCIYzYpYYMF{Izk zLwYq0>8;-A>%98wyhy6mYnbkhp6%7oCdYIP^7lA+f@G^XZ;O^r+AMy4#27eEOM70M zblRm26Q;Nh3vYWKU+rR>##pQU8Uknt8=D`;iEi=J>sSzeFKCK3w&EuB!1)Ln+F@Lk z^W|AxCe;Gt6AgX5(1)E1SR;1SqacpUCycbhAABZ_4tWY48|H5H%H8ggPDhZtJOeq( zGfEl3mj-a~bnH!S>Y`Lg@t%W7#y^a&Noym5v z$nFW(+2QdiE0ePs9G>wGQR1&{s>s4#bmDU&cy+6Bx#4(G_OI|ry zSTx{i+yyMKp{1CJd`zmIvQ+sXRX#|S#a4kZ(UF(KiJF~^Ui0QxDHDFZ=8VB+mx25p z?h_}-H_9t@Wc(=io5}glh@A%koeuK+5)txab9Z~??sD#KdG2nQyBp+wBx2)jp)MKi zNV*Hmqg=^Y)!GAva<%{EJir`T96_KYM?@Yy!^;KAocD1I9GlIg33;Xmwej+PUE$#J z%y3##J`rz>eqVe!(sJUax?AIXG}Did&+_&2D|qf)Yv<+JYyui~9-I~!(VpS-PMVd4 z|11BU8T>s{pKPNvgPm^DeUzDfIFA;C_j!Ux^D_AXr-BQJ}J5lMGMkS=HJZu7HO8{@E^W&B6q;01o8_q(}~`(2T*zrnM^WM#px zQ}F8t1qWeu*Ye|ZMsd{mwBm)jp^@?odMJaQFauUFrFj4X8mDf04S4L(GGC-5FMfQL zhUZ2OKPYgYlN-6u5hE%R??%GvHAU-Y)Fh+93h(wvq;Au(fm0;f$vHC&wV+d4H!F17 ziOa}&UFdWY7m;&z=yb83sJlLNMiW0G=MABg7khPcLZ=4_cioMlvjV&Bx|>309QPRN za$&9d6qQZQVz1JR9zDEJs1yeZUnu+c2mW`>fx;I4XBP@Jg#yKc9h9{^w&6Y75|FwR z>mhbzI$iJO0wC32PlIKJOwj*B>3>>!n;3Z47SyXh;0e4y%27B7%h6H-Z$#oOmUvR! z9zsGspfj!i2?hCp%wdBpZjj~KM?X?1SgQ(!2MPuH3bU%}Ke9)(# z<37y?U3vm{X+G%Dqqs-&AJd`s(PvVJ{^dYn%7MbQ2MRL}6s|u|xSjvqb)e9%fPVuE zg(C`uGXv$w2YAVx!};gAY36F30BNL7u7aLEBC#+0Pge8q63GTH2tGtD_^Yj^bmz zJ90hZB>qLs=&qYrlM5Pw4%>KaE@%TfEE~8i?yyYYvbn=Lf^-gdSZClmwEou7v#9>R zE)>QV3jg4LRQV}|!YspY4>^#)eWw?@8GP2=XXC95<{YT)@<=TD^*;#M<9bHdjGhzn z+#FBjN7nE&Y9F8RZs&iP@o@)bd>k@o1M5iH_bA8`hPh&Dps8+(JrgtSz(qXY3B1LPMTa8?pG#8{@1v2;*lw*3s@e zFy<0Vx?!kmtZH#^XzRr@1XxlFCH)f(4U!%uXkQFDbJ7dQLiW|~z!>csdolb~R+)xoEV0^(fI!zWj#YVf9ASr2Y@p!VG& zidTnBPdE9>0r10Bd0!6}weh0xQSr9w#Apg>L)+tMnHwlQa+)~0xf^mYzW}0)3 zK?>hh58^OZ;DS2(M9T>FHh!854JHU~<~r=TaFVc`d>;XdQcYa3?_Df9~; zp~;PTGS0>ma!2fBKx}tld;A~fy%)!f4wP^l+b}S;pcmB?Z9#J$t<6uyDV(xBKFpTA zC^_V9FU(e2-#I6)H_8& z?Il8;lKK?+?JXnJzTSk|iq!9x2(`6DsI7-3RCjMe?Po&WBzjS5Yi~;3atNim14@l3 zQL6i6DV0k)F!4qiEw0I>>u#-qG5ESYUbM2LiAodz@ zK5XTrUU!H|wuR%sc9ilFPl@P<=2so$iObr2No=tp=B7;ZHm7FL=P?J5+r-y;;giiY zzwWT9ou@*hvD=*BpcbE@(O4?2dHW&hxBr*)+l%S9JE+*GO)iYizKZ%&Yba^(q}J+LEt+-Rj`8CX7mYo;D97RUVyMW5i4r_+ypAdpNz=B~ zD{CI%uzTHx*$?>{%2FG zVHjXCg*pyGr?}zzWIyrQ=Th&WQ?GGL(3#5BI?6!Dbo>WS7Q(MgcGLmRV_JzMAA%&; z;+1*#3ej8OXr3j@G=Jdaa$`2*Cb!>WRqbSDUW7vTKF`6;o8##$KUy{qpSVfg69$aM z6h)u-$&;IM;?Ja0%5xc;u^#gtM)NW6qd~1MZcmRhH<&Zc?>RIwAlnqh$uz&uS;9Lk zF4fBmb~KH{{C(dk6%1E4_|6`j4gJN{VmEwh3{txpE^eEi99+3Rip6vn)lM7NO^Y%# zC{tf>A`2-;&o=M2J;wvoOzwsXfOFI~2I4HY%pNmJt{L>jfJuf1gzjaWd2`4oA2>zh z%pVl_z7toG6BW4G0(=yogN2U zJ=BCAb3J{I3;lubQx!!IkKS1NSLBgp(rJD+PHCTui?lpovPw_r$w5oSymsD-JI?vK zH@tLD(wT-QKiIo!rMbd%qZ`U#->}*2*g-Pbn_S2#j_P#|1S#&#UOrcUGppL&?DpC; zq(hFqAYur3T6L-gLwj2p0o&bPuV;*RX%xQH&E!*8z?NC+O4q!eu{M|Fv zXH0N|*E3qo>lv5RsD8OydOZW*6H^n(ccL4ls(r|MB&VOWm?Z6vn#;J)&^A zYhKUz5l)(nmDut)XCLNKjR~%KQi1=nVc#HOkQZ7x!PL_{t#O4Lyd*LIkf$}Sa?79A znCym6YfN%`J+1K{p3@*2Hecy}>~k8Xfd9ScG$y&>dl{2l^PI*cx9mBM$;IHwZup$W zJQqrWa}M*IMv(}kdq1ZUwAm@PIebpzpeHn9{Dg*CMh9*F&yQ#Pw|*~sJR@)%@_5D} zJq@@9U0ZxS15`44>_jDgJfneih9#ndp|vtf2LY_^!V+nAtaHk$@EyB1m8>f$wIp3$y_eT^k0_cXX~gThkuN9dtp z#a99_i9z!slWTq)H^OGStz09GHR}t7#?=n8&emVc zW5(?S0XMpZX3${-fC+5>wRws+VH5o|pLKWCq{rai44?L}RgydvTf@Eche%`?pYcUA zALL|>GXu|<7q@C`)7u8XH&YMv+rg9I`#+2mVr{&g(RD8 zJ{nsn{>`2UX|~)AjikQB)$K?B&i@iscNVI0pF5c5k`^a3yrIp5_1T@r)=jB7zHTht zbyO7J+W>G8kdp2ei3J1+L23X25y=5ox>G?UMTYK1!bMUSkywzDPNkLxr9*lFDTQ4^ z0g0C{zxR(hb9d%*X6MYE-I+Ug?(^uAv-wPoTUflTiRFyje>lIIRc+MJDh=CHAwvEg zXUePnJ4sOas?D$LC^vFPvKNZ$R|0MG3e+lc{79D2m`3}!N^R26D z{j#w6^}C0o@bkQb`fiC>U+F%8IC$S#Fn!}YOPoFGM@!Tz*Ymi~?025~NzUf;*G`^V zSb80Gn_Pr0^3l=X|Hi)&Lnl2gw! z)&qrx?$*kT0&}&-%T0AJa)1+i=3;i=D#@U+4K)k(?YkZD`EW_&k)^otqa7F5@d1yN zq1V2=-_+1w7=`~b63q&?Tl+uq-txQk;;lbPd4QIWX43EbFQL7MzE!7j7a7>u$cxnE zi)3Xamm(~9sh%VlgN4?VzX$U0^8CV?1JvUIMz}1t=47XbKy8WXhZ8bZ}jtoX% za~SuwgR)-#+@U|GTV90a|M4L^mI@9{7C1Hv&Pp~oXIJiCH93E|TtP3sY$agNd8~fy z<;#YYzo=*XZ6&e_o)#-3Z%6li@4t7}6M4ZhyA{aj7*L>H66l-Hwj)CC8L`+N6;R!= zOI_98YS?Rf!NB!>pON&!hD+`EFR^b{4(++t@^{+3oO5w5kw1!bds62E9XZ^q(wmpB z+8#?@JmR{z6)Y6Yb+G}`Rdu(T_vW0iMz*vC8$6Cg%?tb1xTGo!5-kdz&PD`RVE>Z% zCX}YztyZiS@3|a{%vhc4BDcTO@4?T#l!Kc9Bv4M1pU*FR2Nak7_z|8cF?O@L)lvEiGqXl`%!{|n zWuRN?Jla{CVm)g7sdI(+6&;@gF-n@5FLWh7+7oGx`S$F4-~X!Oz@zZOl4K7Xygv!q z5&aH%W0|FZ7>PV+{#;i~xt9HF<*?mvD*JxoTZqOWY3Zldb+f#DxHl_(n&ft4sqZZU zpD&{hkv$D_W^rps+q*tfx+c&{@_EEs8mT?I;HT^F3c@(MZzz07NI!B+wquKiXH@0- z(n|@e8xoMW;diET?!|(gDxDrOErM|VVcF@GzB?L1#tEmZi0CsPluws^o!~2i=Rl z&#Xh8LbbRWzC?{?Ri!}{$>rxalPc+o6zkT#IsK&9HRcm&rW$qKkK`sjemY|sT=`oL zSJjMGS4q6KhkmoIWHB}ja{Q@oOHog`H_6N}l4W|{{M=lM)#lmvOd>%Kg0ScQHp)Sq zg5vh%FLw6}DYi95TS>N)do*`gmxDUIJ#J8Cv_3er?ob#DxW`pJeS$)ma&+r?I|tR!Ekx~rvA%5dnd^zn0b3OJEq~AW_hrW2U^B~TBb@o z<=>IHJR&J1v+mqMw1&XlTty|%?!XKX3T|0hifckk2e@1+Dea;e==oNbE8UHS7N@u?B&N6PF^ z%-7!qmy_X-`u7M~j{E)trdU$1+00Nan*O`RvvW%;uhMH3-7{aNYxjN`imWZasZe#O z$(GX8S+FId6l!M9maZiKdaL$rPdGt>;c6}%*_ZT@5J+^%Fh8Rf-4gHZ#J`v_=g-^c z5Pc$TZZ3DLUGR0Yi@Mn&uV~fOSAsZ0lZLIu+AiXKhXYDsQYJkK`IPW>UWEMphKlCl zSd;Mjzbqv%WqSVJ%D1fEZI0owp*eJ$LE*;EoU$t3|FrH&X3(T6ix1T|=cUQKqg!0# zO2t_u4w*{G2xxE(m-TT?kF)8&dR3KnJBQnA{-%cJ8*yG|EFw#Rbed#(EKn~2t0RjDH# zXuen&ru!H-___9S57>O#_JqA@`p(NUzg{KzeVlY9`AiOeXZ~hcaY?pMkNrhm;^g?* z>V0CZGG!V6#(S-B$7ci@u9-jAKDbd354GDQ&_q~c_IKE}=OX{q%p^~G*4yj*bJ99I zGwCL3->{I{k=gn5#)hnpql1B8aWcZ%ez3e@oyCj3WRCm3ZQQO-Dfy6#)UJ3TX!Fu1 za#pAH&io(Ih@$qd1cm=B>L3mBkS&hRSJU@RjL(if+;rk>`a&T-b?cP- zj~og3_C_bEz2uW@Nu@PcvhLK&a71A$^`4geq5)2~eo9N4d?0;lG?G-j#XHhj{~|!b zr33ka#HvK6>=C`l>$EI)lbWfBy=#1_MAS~oHPVVe%<=Nxn}2u>utzr4n}Rk%<0FP4 zga-lJC^;C|ZhCwD=?SXGTjko@eV;#hV50K`gWaFc(eSjiv_@=Pnt$W3wYUDN_wvrn zzfKq{v2HdON9(TN`IynE!>?h-(P{qgf%K*VQxLEkh1hvJQZ@7Y)9v;JVq!z9_ondy zFjX#cae{2N+TYJge=*ON%;0;G#2-=I*7TWg zYOR%?9%SdbZ`+^voHtHPOcbl>etG_pmt<<3KNKb#vVPn%oVh)xb-o=i$|xLHGntED zA>7Cr-Yn^C=2}ztxaIKHgdNw=Bq66bu2xS~Y4YW_+qV`Ip7Brk#c!KvUwq?K@(!T$ ze#a~yy(LW;m;4^7@rf@m>pbivvgtvvUQT&ye%6GDfyS9ePVV>dr<>J>GFwwyQd{*~ z%7=1?{$~zn^k)WVk`@);4T5!pp9JgMO-OsEG#Q_nBn?|laUV(@NFCN6)F1Mnu^%!X z8XZa<`W#Xof`=l9ZWFi1d6i!?o;08MpZq%^I*C4M+5e{FZHth(W2&V6fzb4kh2TNb zxPx*|1t0O5op+-yZI`d*mdBLV!zmBD%8Q53pQ?Syo7?c4auNN9L2(A1tiVEZ@Q#>c za+Hpk6LJJk*+O%;j+oE$Q-e`O+?baL=(^_hO)KML+t{*AwkB z&88J=#W2(K1ikGy9JO~%l*%;qSC#q{Y_@3@mE#06i%PpMleA`+Y8e&?4B*=D%84UQ zuf_UADLd5_++r?STrzdo33V3{YW#gn7OT(JZkvSHMbIr@l+o6(^4OiHarteCk>@j( zMX>-BLanfvhM6Dd{^(#N-K%Yi`ZUs}fNW~DiNQ8t)AaFdE%%#1@tT6ySrW)!wE4Kz zlvBcgi}V&XPyaaTop$Spm_&~5{iO7kel|h$BtAT7+KN)$E?dj~b!eMj@(*nab&mGz zPQf24MtTL`%S{O!&h4$L9kfP8`%Q%;sxZ0PJP+|}8bOMsLBTipW4!>9E%lLl;G6I< z{<=4+#BW9Il+Ds@-f@4gFOn3(%ZZ-#nNq_&^s$jW3zMPhCQ2 zKfTyWzIFx##lF7yej)2|;^%(ydITHPF*M?SCpovQt#@2qT3ML2~f1J691cg^3yvE1xqA6eo@v*|s*{KZNYJ-@%VY08XSPmi(cMDNBmiAk|o z=84nXcQ~Aq7v6H10n+Y!Z$L;(I1jLM61aBAFp(<#qq=x4!P}rmA;$3!%g$+ zj-{k_lL4rtPxl5vXcbMhM7onhs5j-6HxTBhOgu$Lw#EF)V6))K82N=z#b^Hv(&i%3 z*$T|_auWrQLKgR*O0;wKLN9_ENh&{+R61EdFpWx=J8`ADMTpE<8dOZoz%Z@Yv(qA2a9Ew1`DzlLycL3@1btGC<^H^1fMY zcfk+Ixoiw6R>?VOu>O_R6wi>-4<`XBTgG=31(M$2JG|3J-c{rVKbj&16k%Xm&j8rV zZLeqwyKINud3)ecTlyxj;7%(K_|{v$-)_~~MBRZyn5a)-RAIMfT)Dy~rAOA8qF|qY zN@G&9QzgW}llW~w4e+z&p>xWVd%T)2FaHhgG4tI#fkI6gL)~Kh6wa=~-?!Ji=nBg7 z&A|^RfnojfnOjD}(R#nPi@dY8fOpRSe4S^CpM0k+ew*{LiQRtzJ1%V5X$xu>2;)(^ zXX#FnP6+bcA?eI+!HT`dchn6D&O|wqo@RGll&=KSDVP;)A1}J}lU?SV4!KwGi~rk` zSKpT|A0)I1J%lk5f@;P5L9a5{BQt=)INOvbJv02N=azS#{jQmQ-Y_wyIHFj9G?KbT zUiIigD`}E^_1J6vFB;>OY1N7< zZ91Lr^rCM`_C!Qd$UbZ>{*;{4;SMQqW!$33O|vZx+1Gis(52ENe*WP@b7Yg+tqsfE zv=z?U4TW4oY^wuX5a+2Osl8f?p=Vg%;{>f;dZn+LGCdBzTC%wp=^r&@-)oK@D}P|$ z@8+8^CQkR@n7MU!`Q!b$9m%=n;(w8*U3S@~uSGK3tXS}-h-=dn`8)7`1?V2Z&60^^ zIK|aDx*2(W9}X*$=F%m33Pk{MJgLJGmw8yb+5Ccy(c!9pofE4xG64PxHTUUQ<>{o> z25VArnd%exTS6zfP64 zoFuf>I{v(23=Y0gBQMO>R`w?I?61bvs}-T9cb#(YlO5i))gwNZzccRDVPz3c6?X`9 z^pSi}y*pOK0dI!lXDoUhumae%M{fpym45Z>zAq; z)a0{@ici_R*AjkGW7g#3=VtO+Dt!v_mH4sfQ7`Sd$*|J-=rrSu&n|BvPRMX8Vta1?5+O9eMgD>iT}3#QA=L~dppul|GKXsqsuehfFtP` z(2}kGx>0lnT+jv8auS>O{{b~3#t zE%%6O3KCFPdZFj-~;)A|Ig{;H-ZIkQ z!sC76v>8>@^LLV5cg%Ro83V+Ynd~2ZQI%1v9#_$6&bpM4x zq!h@epOe&=rBB&H8j?_k8E&;XivG8Uh~aB?qa;j_=IO z`HAdVq?(DH0=juQ=N@42X<7;p$A7jB(M3BgtCMY|Rx{3T9;&wQMGp$f-#u5nkQ-kYxqY(Y z5O|Jm|F}Z_D-`vCB84Sf%FW?O_tkyD70k>`$6v*hSN1L?;bvs*Wcc(i*r4M~_>2p8waJdC%O8u| z$A(`Gp1jgOwXb=pW(S|~@tN@fYo_L3qdpRCY6e!w9sp@!_KA5Ahl7FlyLtB3t=byo(F&R*w^C zf>bA-xiC`i#sWUmEmBPjr{WT!z(`$$kmSs0O>FBAeYjBI+hIfmQb^)AS4HIkU2dW2 z2}^FFiQgyPDO0~J-LkZ=hDDocQ3u~f$0Ctj5)p1`Sq@IU|AYeH>sAYUe<^rM7WhS1 z%Gf!vz_sRpTv;W zRj^ZaV4Hi{wdbn2D0o1V>qfi9p{er3Zc{G8)OE8!dH!iJecD&}MstLz)24&+qVV`x z+E=v=|437Z%{Apk*a2y-p_yNS?sK8q$xxNMi#Pxga#Yl2-WTf;KJ4@Y#M3;H6)S;JqnH-0OB zdA7-}{KdXFUHA*U$Vf;V;uE4xT%>Z2FCghQ6F&SDd1qvxC8}BeFsp!M+Kuciao9gr zPe{9n@8K?^b9O*V7fxaFcwZ+by@)5$vRt2EKfP%-R5KxDX7RLS zrmkthP-;4>!8)7OzpSLgukFi(d_lKEhMvO$-1v&qdwHT(_4mS zBGm`;Xr;QQA;bFFEef+myW)bnrm@MtG_BKF(qYp!nPsuuF^oH~g-XfFHfPOwyAuBw z+DlfhCI4J%I}Go<&`x-81Y1?KFaGyJJ0lpaU*b~x!|=Jw8Gk!6qsx$TdP~Wy;_@0< zXX#cP{o<^lfTMCEug-69l3oilhlAafb5oIKUCYtLp+(O!-3)UsC>-a={ z%(9Z-RUX3A`hT1_X}a;%%nG`dz+Tk$8P3=7S^u5h5-^j0Q8HBL*KPRy$Ciwl;`7aQ zp~IFk%g>T{=c;D3KdoxUHS2DSztDab+@)VJokjKaEfitgsc+_T)>KgUW6QwIt@_}V z<`>73sTbPT3y#k__U~%$IzDQcecsSBM&P6@-ku@aGH)p7a#mNcSSi8fiz<6H<6K|% zXZrFiiYmK2_kLu3K1(ZGs5|tJ@HP8gJE34k+q+mRxqPZ}y|rwidg5Q*c}%j~^Xtda zbtk=sL@wIT+$s&)C)20>TFOLf4dV0edPs!6`z*hpmoB>{uBPoSv3%;OMmr9ZJs~x- zQlC@$>?4tTrlDrBw$WGj^@kyXO}=Y#YC6kZ=T^?Iq7rs(UANi^IyDf+;C^t^jJ6IX zo3$iAq&Fq|X+m#|U9xjzY9?z~VN9=@&HH^G$)9R=?=N|-mtT_8rr4t=CA-C^M$0(& zkWH!OLBi6;{mWxEmdH02%$hcTUFK8g2*EGj zy01f*Rj8vjAc33Z-O}mW`pnc=!aqy+i^jDD?do6l|U~`tu zyE$*ieOzB{e)E~~kwv2E&kg!IOQ(`%c5N)5_2SCL1ey>{mT0=OQCr97vbg>%Ov)xZ zM7zjxx#(@;<$~Rf9+%qwk%-@i5xy8QHQG9o!RKtXlOvZE<(bBW)}xe_eY)LJmc#RI zc8g0*{@1hX_qdL!)f82AVL=|_c8R9zMTd!gbC(-{v1wx$mM(YVk(vXWwlpl_Df?Mi znP*~9igD*i1U5ZZiLJIzZ$5F$V;r7%>2NY&sHj2{OzKl&qET^*{#^u#XJD247%w}OvZ-~X@S$ee*|Aol^`fYjtx6{+J zY_;=xa%|ef!9A1yB&QJpm;V6^hH~s@3b@F;S8`dOwc8d}ilo>M3-d^p-DI|2sNt{I zxq2M%Ci?a0`LoL}Tps_x2ro7KSppa3+B}lJUD*>swcr2M4trp2-h$WozX)eF+LtKV zEqpbQ`?1`Xidx|6v#=e!>veggiOwFEZ7EKp*FJ|@VN#VzB_!Pu%LC@xqR6FB0~XrW zNR?}^#vd9jt`^-f_bw=LO#DX6m1kkL8WWqkCkUpB%lXi+X!6-5 zhYY5gK21zmBw8*#3)^^UG~_OccgUbN^m0PTNQ4if^QWPSh=o^1BHj{sM|YK*^L{pEeeh4p``LJ6*&&0MZQO0}zC3I9(_Nzq z_X7=EDx*aZzq!s`>-TfBGI|aEHfIeVy~`tsBT!ae5;SUEEt)6~%DncgNV0qZ^X&R^Nn+4z zcg+~yZ=RCNzwwOZH=LE(@e+m)lS?kM+PkXBV?L=_eCZj9sO&L6;}c6QnQT50?m*>{ zSR^%Xkr=&jpLk?MJNk#(tWj{=xnm+oZO#0x&jh0U$Lc3nqgJ&I-Z>hu@kB*g$mVUv zfC1QS9pV2aY7MECpXN{g*LPHNeqFM(ZPU&U%D=~P+;&PFyyt{=5IeDv-O9k{<>#mUc7Ei)K8@Ml4< zj+3uHaY64D8(;so%NYk>fBEI??eD|-W&5XcL9dpc!h&AC8XatIm3pY!sMskr5PfI; zEmyt%^$lma%cHK8RN3o)Lx;9!GhNO&jBX|0@1<*SH}|=ZxO1$eFu0I?D@rSS=&vUw zC4hM^#b_+1*X_NIa7}!c@-))t(|Xjkh(Ru*q^O?Y_socjv#iFok3jUoJPggqx;R_>DzO9Zo`hq*vdDY)+)P| zd2bc#BdO~eU*CzNo*rR2cu8gB?;G*EfLN}BSgxP@7r*LsAN2<*I&b-&FD9aGTZuFJTBE+RDlHf2&>h+3awG$KmLO+WqZI!nhUDZOEy$HBgW!4fKs&GI1yf0 z6YVRS_j>*o)9oS4fiQBS)U1Y&Qs?z?Y3nzLoc(`XG}M)?dB0#~Y_Y47Ep>hzd7(Qb zEc$yTh~(3Cyl1P!i7wy91%OZe80y*K-<7iz^9>YDlBC>^1@FB}YHwo-codcHIV9x~ zua)6u8yxU}wuGOdm80tGRGckN`4+=PkoT5%Oq8Vvt;~z~Kcf{;dTpXHrLrC5J0)4S zu;5Mkl()PfsglOXp%nc)rbA<{;7b`=Ps5J)t}e?U!DM5Cd_$T-_rtj<4L(fowqiyM ztMg^bhC1pVlc+z-JFES_64GjL4VSU(4jS^4a(gI0!T(WJ&BpXIEXD3e{gdC!-}%d8 z=v$?Ju5xa2J|C>vyV$$Hvf*db! zA7;`ROd>Iv+kFn(z%B}Z$XO)S-uvEH{ekVGAsMC26;u+U@sHEy=F(V|hOF-23%bdBJ&6 zwEzQ((Bsf!<#SCWS1^~_0QZ{lOiVVI_d)zZ-DDf2Kc`oo{MSr)6YpkJY}kz%BIjQs z9belR9n6C)9u=f2?oW{xEiT@+AI<6ZJ1~nly>pWi5L{hfi#(su%}Za55(r=H-#_&+ zj=i{kEOlYXB_G_F9GvnMFyUD7A>9~M6gr(E)Vh{{=;?QRpdS1t1Q(&$d+)3{!X=G6 zpL`qdY?bNQWHKx8_B1g_$8Xvryk!aXhO&*|>?TKBZ*bhj37tx~R$ zd}a_eY5UgsR{2k4Y=lwdX_Z!=*XfM1;LKvHb}wfeMJKhfPuU)eq5pU>{rKfwLF;#F z(-=(2FuvDJR41?2FOHqM;1AzhuRT}@`HBk4AP&)1iVmXB54L3bcYTj2VtezUZcU(Y zc_9+7nsSjxuwP+sfWRSScXkv3gIg5;hUsSK6Kvg0yBzTjhnW+fP*nxkMTGG0lVw;x z4kcE*cCl}*5=y0DYI)7wJl^#PUk@j@6lUR)U~|u}USpD0X1!AbZ#kBVj0)&FrN^r@ z{E*|ocB(8NpitQ4UXX-%yib1GcRm`{MQGdnR{00IEPOb;HN`4|D;{r; zO-AFKX6j{q=Uu`kN;lDKmXWcqGZTtD_7K-bZMhUR&94>uQ!=nyM*oh9FQSZ{ z|3=uu!F$195<9_xIX{)xlHhSrw7qR>jw#QxsmB1meC?YyqL+1Bxpa8_ndQ(8j`Qmb z$0P}AY?{6JK7lu|Nud{tLO|GXuwlRv+);L;@> zfs2}!9y;}GI%R0HB%}C39-w4L^!boCfpw7x)7{Ip$FtcR9LjmFr4kKryb=uJB^ty2 zRl)q!I0mnI)J}cwR^cu}`Q%mOmAl@c)2mDA{fc)Vund1eDfkkmrK_7=9{dFwGXWx#bgiU~$Xl%Puldbv z#Sh3|_=XUHwE|vC)pe~pCf$#rFWJabr)*2v@g^HGM=t0wKIX^WOvenYbeRu76ZY7U zhH$;D2=8-UUbf>tA`%7Cv48xTJ}?Ue^MMG(+ld)nH`ITK%8ovkrp?&YTp5r5Y__kK z;g=~GL%qm5+4gwv`L!J*iQ(_vg!?v3q)uODV~=7lv(;yGE2wnNrQ6egmZjb&e@(=0 zJ;^!7??XpUFAosdi&pg0u7RttXGOQW8+0e}VxG4|)p8UL4k&l>1>*Y)Dt?gZe?)ze z@5D>OV##S7E5q7pP5&9(f5|C2Dl)=8t3oSzFBDPx?ApZH)RR>iiM7`s+vABn1(y?L zpOu~}c)ReDcQH~$=(K&9=G3jYl|O&&fCKD2r~M$$wa0h!7L4}kLi^wz9%bU<8@o2{ zVARM`(uYmU4}fx>5jUcIgee@TWrb3s4=y4E11XbgA zLN?2A-&|%O!n_Cnw3X!GBJ6f&0nm?(89CBKONfk(tBtj9N^}HfcUvplnx8YM96alo z;b1Dt4Z^<&GjZ%9NoW?a(>LpVl=|$s@}2xfW9OD0Y-UuWEtB+_1m?Ja$<%4%AAWcK zv2hs{PwZ3Y7;;i;OsfseOw#j7f`uWbAkJJ+Lq2D*JF$TmafkD&{%(o?hYh`f@ zO%x<7p*IG&V=muqD#p;Jp23`wWi62p@QvARY^6s+AE7Q#Rm~xBc4U);34=GOZlmX8 zo12?y{!%H=y>58la9M6{Rtbx`ENstSQ0B+U$o&1J#Xh5H8kq)dg}Ih8GDZDE>QPB^>92%poIU-@hD1w;RbosY>}<}3Ci zHk`3-wa^frEfMEd{w;RseMob0k+fq_d+lr&x+bIVn|@)-UgkfHGT)2pOaZR+%m<^{ z>h}?uPmmS8zx$Ir>vc^xrd&B0Q(OJy7>5aO7B^`$Q2DRt4 zsB6TljV~iN3M~m$MM!bnOTNcH6jNc((o>#qsb7u4qBuWlImmKb1(j?Di7L~ zYguPdis9FZLR<;kBvGr}d528(6uv{cD6w{R`wkm1eCf8ECjR$&7aWaCRwB~9gz7vq z9(fTwrTLq%BLWmWK>jA5yH6n*JJ*%(14kSfXl*fy6Q+a)&)IRzq=-~x-cro;z|*QM z8PvA?E!w-n9N#GPo2AgjG(&mQTEqT>0BMOpE4pxLbnkmaoNbGUX9(!^aM7K3SFDbV zd9W{G!7}Q({MZ2(Z1}s@f!*MyXj(s?A>sL>P<65QQo`X{;@*-1u#FQ$2_P_q?)_P#MGkQVW$M9- z`=6T<4qL+u6ymD8*>tEtlg>qxaz^9hdYSb#F}R`My;xp}v8?#ajg#!+B1Go9tnAJH zf$G~dxcrj80z4m^)iYE18Zrmj(tB>|T6j6r4=P1x4awcIq7Xg4FGl0VG-<<@{qJU* z8i!;I>J>fX;11u5A6e+qKd)w@L=g>xnX6 z?z)f`hpHEh@~MwyI>A~q+ptq1>VuK9IbBdV?K=0jV7H8%F`3-gtG~Q5#yU;M#c6P< zC4ZfGY=!QjW3`{EYF!3+@iJodx~-zkGI3w2EGp$oU*_)3=RXAyyYg^rqw#_4SgoO% zgk8MEIdSAP-Opa3LSrWUz(kXO88-j={#EJlIr(R#I@#PPx^Bsl0oQO2gZHMYQXsBb zXtkYWjk&o97QGXoE>SNTccl1kr<^gp^BDNCSbnRf8;_(!K>oaSy)QPM9Sj^W~p^ahTPaG&M zBhz~QFJaE;=OQ)5A3?`+f;5~&eq_9QR%)Tder89nZQPe^_3Z_|&)V8?X<}<*Gjv{@ z2<6vW9jTJ)&SGA{f^e$Fp(iQyv0ll?jad$IX9|s$;_V=BxKXE!xa{5tqXB zmyUdHFUo~gw45fzV@)RJSQeM;J+h_>VJ)UDb_%=p2ChFNS2=Zl?~9djQG?BXIZC&? z@8h$7e`!8^Q_E0=$u_EED*vG>?FpZBo{`XrPr@%=$2W?dH5mfZ?>gCWY0ipyY~M=d zdx~1}F~ZTV^wAESa0lwKmrQrp2nVk{6(eC9;4wCb+tF*qux7(qU6jYg_tG3#J7wdG z6%w6jJO(@y8vi8Q9n;%=Z!Y=4SLUDX{z|knuDQC#v8|LZ_f1w<=e(2Jy#UF}lS6DR zs(Hnp52u;+0JT5J;j1Z&6!?<4Jo{}}ZB9JM4mpn*^m+Fk20ag#YdJ48|B+GEZ%4!6 z6XMFZOq119=8z&1koedkzTN}w<+*00SHGjI9!Gj`#55?8 zucgxVu4C~Eh1`iOC-W=o)uWmCJUpUvrs$k?_T4YS4*x{$_o!Mv`6doBhzDNSIG5Wq z@2+{NX(1pEKHPqG?5*ldRiaF%zMfPoUxedptwuU=TEqMBWDo3lKiv~vM7g&1%uUHL z^*i717WJ15cEC&bn%~t1d44kPadKwVe9AIlOGArGDEVtL)q4?G)D_X$IjC5GD?-dw zTjT#tdus9JyXxtMiy6}E@K4g|#_kq7)v;LH#J_;mp8fI##@mXP?uQf!r%W4L>~zOs zy%TcMdv>2od>*g%P#NWTJzDZYpE}FUeOYt&;S3hye7kl{h!e54Z>(^4fbnyHavFYR zfPPx|=Kzhg@X7$wG?mW*kJ4WEP;;o^q&rslIn*Gwt`*{>Jcw=CgD#01NW^$V^ymSJ zSn0U`q4pIX4jA+fy+V+LfZi>6&?M0UIv9_z9(6zmD;?WI!U4lc|A(GJ@48lQCRIc4 zmOU7f?g8I09#K6ez&EUPd=E3nQ=D|?iqI9Z3+=hhK?X43jF&27dafs_0t}E4rn1eB zI*E`Y9umSDqp!#vzz}D=Tp8Cxnq&YNLZz6>4mgN7uh4o@gJF9s*LTq zk)#dWhg>j~?RKA6B3D}o$Gka^b%X;KMfzU)DtbO$KMctrLX0Oi<*xE^{ABV5CO zND7)muiQw=hUS($Xp>k0SByt^k2c_nZHVn5=a9lRw6E}8AzFMS6DAf9yop1Hz@i}A zHd+-xY$=ut$%I8yfj1%B7}yO!41L8sqI)cW9c)8<4?BksuAy_~E{6{^*R?{C)C|on zdoU)+0Xvv0B(@wYfXu+6=|C;WHV(!Ih;`A@A~P_tB%l@!9RVwZY&&S-z!fvbqG>=O z$Tk+n092xB$&kjFSVB+;hYo|KLALF*nn2}JEHCnkxj?q@u=_w|7cDDt8WT$ny5P`J zusX=LlhzEVT#gk+PGivw;1#0)JkYd6NNG$gKFEechr$veOdG8l;IS0Tjg-cssX;af z69fB?t>7g^L%e-q(?M$lcr3@>K{jB~ z^q@I}iG$q*Ji2HZkqwwwGSD1{j)av%R}8ll3qwL!G%W}~m{=GGfJ4(#A}@!lD_$007#JQv zf<|0J5@6J?f!A?7A+S)0rVa4`AX!qsg(Sf8P=QyB3n1x2P$45Q>cpTjjwb?^3(<5S zbODlOb$(<7mWK{hhG^noSBx0>5Ti~2-p28S!BQZacEn>Kc1fKF`4G!P1Kx&cVqw%k zEE;jeZi2Qro+wx)MAL~d24a`hg^*}04+Cfm(Zs{VfY>es6B3P4zha@VSm;d~;vt~E zq<$O8gXN(HX`nYTFcLr=jUYhsVAQXJG&r6s)`55es4uGvBJ;64^q@ZUCJrV5sCOag zkog#OQcxeq6A3GZ-u%b_E;WTP#k&_3u6ZQ(1@EzdyM)GP#nh-4$Fkzv?H|s z$5ydCjG!m3hBqd2mt{!;yRKJ zqmBoX;_^dak&u2HLKzTPQs+X_Ve_fLE5-{5bRlSvsTg$Kim`HY|+ zG#U?+2R6G99LNKVIwk0b%a4YAfkrzKmcZt+x(M<=mIMQk7+gdT96(~BxE@*#5C?Uv z2ylQ9(6vI6go1!&9NiV_y9VCC*@wWwA@MeZ62QFFcMEwFYflBd2t9y#xsM;2fVHOsA3@@AFm8ak3qg%c!1NJ=k8t)^tR0~aWG?mbAhode zG$0=&9t)!dGSLVUq!y-+0OZ5jhrtk#_!V336G9GR?HNEvNIV{P56J97Fe3*seK$cz zoP89m8WQhBm;jmoF(It|c6$2v!Owb5Iq!6a>I>>;t4~4};Rc#1Wz+|cK_Wzh6 zR22ui1DJFn=#izEK2p#SXCDbGfvP$X27t+OpCGanYfleeF;-v(jUY$5VES%=_i^^& zuxzNR9ia`(EcNjrU9k4F;C-kn7Iwwhk-so~6yPhIeKf2Ys_H~o05i*dcagua_Ke^w zs45;N2h4OKt{5Ijfm;oMMMIu#2o*qPsgDcEhFzrsDIm`n7!e?YM&KjaFnw1n0)~P- zI}mU{X1PxQ`3}2E2kJncaWEJl(}kc#zQgp9fI7I9;lECqJ-q$IE>p$W!bpBNznj#lsYV<1WO1 zOyC-bhocKYg+Qcj@CN|llE5wGbu1kfhzF6zpsoRgX!tec_5WuPs1Fcn2V55*To&L* zhGFUGKqZJY4s{D4?1EDv!!QEGpc0PminYTZ1CdJtJV<3M9Sz6@k;bB^fJii)7^#dA zAON{=bYZAui1dmr3kV@QuyhQd4MZA`5(Of=;7rI4jKEFM21gf#`V5hF!i|B*|JZFT z9W_V|#l@hA0aY}d0C^iDa2=$^(S@R7ptv^pLqK&&;P(HR9uyac;s;c_;B?537y(jH z4@Vb?DuUuV;7RNCIiZpa=k7H2gY}1|x9AB2f8|Mh9FU;9V9FK)%7I(t+xb zMjVO<;O&CbAm3mFNI-R5>J@8;YXAjH0=!6lY$^>X0BOXc=zsz=oD`{#5g-HwaH(Oa zR7m5BEei-EN3p34pcAAKkCFfiy5KCxQH%gN=!8p+Le)STop4j2;6EmgO{E5zq5c@u zO~3&Szkw9T2;hUvxYSTo0@U9Ie*`!z32^_9nLzzX zaUl0F0+gUHE;Sm}0`+&oErGRV0TJXLHkA?dh5F-B0I=2t|Bn@40|{|YLQr9lU>jTs zpkFG!g(Ss3p#ljZ!5GwafF2FULz4bKi$HyZ1Uuk*0R3_?KQa#cgbq}N1mjS*0s1aD zH8KuUObn{xo?NkZxH|B5sh9_;fqg;)@r1`8cu@Lz!VdJytpS}s5g+{6NU#%b0=)f?31Xj6gY?j+7!)aBfQAzy z1u@0fL3-SiP*fcBsSU0Q7%Uav{vR`dKE=SzMim?C_XgE328B=@%l*By=M`b~u+Tq&3#8NRI(i!`N7LR7EJLKi$E1Z&K+<#Ah}#DfXu><(}CKMa~z5fknDofBC{~X zB%n5K{6E&RgriAf1S~PQupUjo5(~xlka39Mp!OAB4iTsYy+W9jeuY3`$T=3p0Mw%4 zWJps?F(D|78xKRJLC)=PO`vwEm=|e^9jEypyARZM!C8?%FvaBH3*2}Vst$7Qgqs1i z%f-UTAJ}mQ@CD=?kGf(+NLfrVKFE$64@D(HTWxSPz(o1&uUgcvW z%m=*5AuQ2BF;5TQE5D?L`OuHmSpzQy9h6;i#)p-UO|b*uO&;Nk4oY}d_^|wv9(I6! zoJG`vH(xRxsbChasdUrFG=O^%OEa+LXrwWSIGqP z@rz0~V~ht1a|wHNm4ssA7v%zVmPY&x3hSwrXtPu>7mroC8DXxVFo!Tho5d6ZkCh8F zF<076oi$Kv(Po)o0p6u_GsSj;!aTwYZI)0o@h-VQ7u!v{Wf8xF!Y^5e667_`u-s0A zhE6BT?cfhdr;e}+{2`-Fgq~=JoX%{FR7b=V)JaTD!cF9YiP$u{@*t4`BI+oA)JZC^ z#!Zx!`q(rOk*&@;HH1R2UrHMY^-y0qeW)=A^;HgKH)>g0(L;5eHkMXkzgQhbkQ5hn zk_qhbd}SpQ3kDInge~eMp$zeSxd6d}>B@BCSD?;nl|#9Wrj{}E5ZUQq83Xo9I;RQC zz*$#^^OMrHzNm1ye8wy7Dk_8bs7nE6{6Ffh``YtTeP!QhL029kj)L|&DiBSO z3T$u>Wu*Z&1GHxoI%tBJ(!@RF0(F)_Tm$VS6^bUv1P=IZWu-9|3EFcBCp1Aqnc%nO zf@xSJU8&CMsnuwoRA7fEDJzYzMW8*0ut597lrEkm7ieLN=*l#r1hlI&nZOzUpsX~- z_JQ_1!Uye>P?q=yxj+xwM^|PMcR+gs6^-_N$#j*8`j{5*&L%Wa3o$hj*OeU5~*SizFL`Rh}i+}973I$;Sb~r1{OyrW)c;^yMYQv zOJoX1{DCsj1d9XSd4wxkBB9jTB+QgfJVfMy>N;vE8YNZC#uq3P4KPzsolQ(bqr}t{ ze1Tk{&N7HApqixCpiwf#eEhmH(HP@{>RiGBjgnBt_;tBL8{^Z7>a3nxg}#(3=HlCw ziAIS>SKw3SBIj zPRt@|Ky?EZg}(ffStAavM+KlhQib)`3T!CO1J&;ZiU@uAWP#>AX9>OMciLI{fswD# z-UccXy(3dN;T_5W6Kp%!n@4z}cO=vdyhEa12dV2s6p;atKQJs_b>@mjfJ8WuwjWDsRwn56jV z0hz)9S7&};IES!8N5zyLo+4LhVSe;L8c_;{>nRK!l`8D;6y<;srq2A(Q3+**kI5B! zSSme`Mf?ed8>kp`RHkso$CLx6SSlFKBfey=6Yz<0vk5H|XvV>~gFv&+bup?THJgP` zR5J8iCIHRsxCy9+_-i(`Wiic2k2?!ANmn+iAv2qYA5t=yEsKF>Zrm(XL*lBBACjA? zv%_)6fo8qyGBizUHV6M+$uMl01vGQw^wBi2>s0)Ex!L5FSu{hPHMoYMX)-fMyk5yL zX;}v}^Wt34G>PkUyk2g`Xjw-yGUILl%`aJi)ND3xtz;Oq=!4AcxT$D>*mW{)EjOFg zqE9mp#btxcI@cv%vvW#@aSIz{=EluK10=3Y{G8lOyM;|NGU9#%nWSq78Xz;9kB?`g zo%fV_wL2O|#%%nXI8O}g@;6?7g+?B`l0Plp>s-X7XS=w!Cf|H@Y6}wEBzR&-=DS{& zOwHo3RwB^wSZk3iNP^fbe*_vXi;D!o!eOmIf?(mXB9I_R8riHs1R5?Y6lsKo!&;3r z!op)kBaM*Qv02|E&~RBC#10k?YdK;E3y&3!*g-rMBf^l6u<%4N$VW)@*kCaN4HvKxJy9lkOJX@O+aet_?vsy%x8VX1$r?EvvG2KmN_oS?07%H<$A!Ip8>Wig^LkHP-d zNQq{2pN3{qhUqCKBreCv#kP!JT-b<)JtKr%tgUhSo1^nC%aF8r3@$lKJMEN9HaW|d z@vV_PBa~dHop##g47tvhanU6VN&Ch~UpwHGOFF4<%Q)w<1PPeO;E>-Mog&$`j9*0-klqI0!5a_6$C`L%ZR$OEI~_ zmO;3zMZPr(W=3;KSels8p(HF0%xDe?%N1ty8WNVR%xE47OV9YpjwCEIm>p~qmLg_H z2nkChvx7^*@|@WbO2V>;*})-UsbO}kAz|6a?BJ2Gd}em=Nm#U)zHAbfET(S=2}=;u zmrKIZ$n*^*VXB}c!F=f`WNmvS)wIL)dd}b||gr$vH z8%o0B$*kp&uv9T?*O0IzGi!MyEFYP*d=eHtW(u2xMaWDEAz{IoDO?hk7G_E)35zo` zg+szp#!Oj5!m^#2!XsgM$4udquvjt2*d#2a%&`y>mKf$3mxSe?@gtW=C<1SRtZENo zl>6;~CN{s6&BwG#E4ujq-i>``&~&W4H+_>`c+%bS3dhJB@h3JpwF4yV-c=qpZA!Q9b-Dii{w*rX{F5xpo%KtC zi1&FrMQVh4S!lfYzp|}h^{WDBqQj9-bcQ{LB6Hp77fHm1h$CTZ;?| z+>fc+|2Bxs2>j=7MNFjE`YShHM|!EYI>g1l+FBo5ds`4&d?3L$D=O`IN>yQlBgT@o zZ8xf^@(2pbd$Ps9oAaz*R?@uXW|)@C@mP?0=Had4SDAX&=VD{9rd>wK-Rmsx)o)F2 zzDGS`MCxj3Rk;kzpcs)i+FXj#vW%@#A}vIMGtc&TeQ;ltzN)`wriow~==%8XID7X& z{h1TZHZ6X){Pky5z+%tKJ?qL+<8?Q?{=&>0c(<|9W~->#`f75kSxP}?^yenp?`d&~ zQ>*JSgL^N~HBUlxRloTAXYCX3&R#|xf0)};Y;pfa=F0wQdO0h5(?6!Dbi1@qwr=US zQnil+J?`3arBv1arn_{t*Hfp4lEXe0>`a4z4JOTV4Xplib{=&$ggU#!$6DzXPY-t; zelxPUqVNrF5ME+XQ&fC(Xx+j&8_cEWZ7;txyz|oi@=N!<`|kht;Y@1#g@~Cp5wrDP zPk$1BwBykDiqqbj$dG+kU-#Yge{5ZR-h#Bh>=if=wc&MC{G+6sOWhlf)dt7k2`=7T zBhJ~9B&=;JZH_u$RkP&QqM+@`x;ba|`i(lV##9C~RjR4SRH>62RWbFn(Btw6ts}-y zM#^Rlse&GkT#&FXzx-~A1?fAU2J9G7S&rw{tg%Od?z>F8f9pz}Qm^tm+-2o-m(#a% zc3S_lSl8X1-k!VHkMOpRtlQ?Bn0~(`%aRyO`dQSm;PmXSzS7I{&krLvdb&qcF@CE* zj22h)*wtMx?N5Ez=(R;_@1F>7Jg=$(E`}znguZz2e%igHe9nst&C9 zH{<``_N&#N0H&AO=c`jG2aVo;OmXBmI6i7@+>&hRQIn#I z%0KkzGjg-X?yu{m((!oFmJHkBp|HLO4Y6%yO@Ce-Jo|cd^7BEX&28BD`v-O&jgXmt z`N|&HWJdj|TF_MYdNlp%pwSO)m~wnI_rF))stQ}v&VMwDZ^O<;`B|sFvx|M4d*)H2 zUH;1-#kYGt{!=yOe=fZ-FT%iect~8nNcc2q^zNs&N=xRQ(seZ{llq2{Pkg`q|5D*t=oj~QU(XYw7su{ji16=U8QXQU%%kW>?fa%QGuujd>cqY*+7$n_!M`9dsU>R# zCQSDfz4I)p_&p)dqIl6hum#)w%l6h+!OazY$wenGT&M`z@zz7vH~)oie&8VQwrYd> zbCrv*?t#hfY1@2Nm0m@kTvNnXjvxPUA->+)SJ7B$mEhWwLUW#3B&S`7H?~^jb!K&H z(Q2nwra?k9Ry^~*&6#KRN1p}Ybz?hsE)<^n@#dq=k=bn)c{VY>ju?IB&P&?*nLHfV z!mi~ZvR+F=^G|Nx_az&WuFWj|++rD}ex)8%o9! zSudtYS+P6(sdYuUADwikml!NaxDtxin27ncm5UASa{==2BTeV2V8i;vA+$VwZ$`L9z<#H$KX2Q-2xxxc zL#FRaU-X$?_CwOt!CRT1F64EAljw#httLb&q! ze3{$D`st=u9yuK;r)@*}`T4nJW0QI}A2WSa@a(;6)~%&064uvxoCv%V|A-rN<<73z zr#Ci>A`|9@H=mdvUGDvZUU1ruBLOT&9&{vO zb9m5&BE?}-eY2naPNZ3K>FkqfFUvIZKNzjYUl|ToKYY@QKA+;8(zfJun7GC*{%x|C zyfk~q(_N#thi>i4$zFM2$=x=&Abe9wIKS-UEyuxIQ>scfYu}}QOn83cZE&0OV9k-_ zvS7~Ae3yz%(>)j``(E&yE?&fE$7M=y8^t39KVQ`rENW_|_L#Y>XRV+7e*CQ3EUAvl zXnN~a5am;U8#VX#mSiwZmxgQR=9M*%^lV5y%!yPz8+fmp=+ic3`y7YD7umq+5a+Sl@~&AW5{GJMzHx>gmodu>IRXj`-U{lcvx_ePzq$BJ6l z+FaFH`@FQ#c+ZJvPr42}E?WEnnfgYTlYJEkRJ1t7wGcUp8bLxVnVe9`7 D+>;X~ literal 175746 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%0AaRsC^bRe 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ℑ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
27g5fl0wB{FT0HO09+&s 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 Mm2e{e4)~@Bjb+ diff --git a/webclients/novnc/include/web-socket-js/web_socket.js b/webclients/novnc/include/web-socket-js/web_socket.js index a133013..06cc5d0 100644 --- a/webclients/novnc/include/web-socket-js/web_socket.js +++ b/webclients/novnc/include/web-socket-js/web_socket.js @@ -1,49 +1,69 @@ // Copyright: Hiroshi Ichikawa // License: New BSD License // Reference: http://dev.w3.org/html5/websockets/ -// Reference: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol +// Reference: http://tools.ietf.org/html/rfc6455 (function() { - if (window.WebSocket && !window.WEB_SOCKET_FORCE_FLASH) return; - - var console = window.console; - if (!console || !console.log || !console.error) { - console = {log: function(){ }, error: function(){ }}; + if (window.WEB_SOCKET_FORCE_FLASH) { + // Keeps going. + } else if (window.WebSocket) { + return; + } else if (window.MozWebSocket) { + // Firefox. + window.WebSocket = MozWebSocket; + return; } - if (!swfobject.hasFlashPlayerVersion("10.0.0")) { - console.error("Flash Player >= 10.0.0 is required."); + var logger; + if (window.WEB_SOCKET_LOGGER) { + logger = WEB_SOCKET_LOGGER; + } else if (window.console && window.console.log && window.console.error) { + // In some environment, console is defined but console.log or console.error is missing. + logger = window.console; + } else { + logger = {log: function(){ }, error: function(){ }}; + } + + // swfobject.hasFlashPlayerVersion("10.0.0") doesn't work with Gnash. + if (swfobject.getFlashPlayerVersion().major < 10) { + logger.error("Flash Player >= 10.0.0 is required."); return; } if (location.protocol == "file:") { - console.error( + logger.error( "WARNING: web-socket-js doesn't work in file:///... URL " + "unless you set Flash Security Settings properly. " + "Open the page via Web server i.e. http://..."); } /** - * This class represents a faux web socket. + * Our own implementation of WebSocket class using Flash. * @param {string} url - * @param {string} protocol + * @param {array or string} protocols * @param {string} proxyHost * @param {int} proxyPort * @param {string} headers */ - WebSocket = function(url, protocol, proxyHost, proxyPort, headers) { + window.WebSocket = function(url, protocols, proxyHost, proxyPort, headers) { var self = this; self.__id = WebSocket.__nextId++; WebSocket.__instances[self.__id] = self; self.readyState = WebSocket.CONNECTING; self.bufferedAmount = 0; self.__events = {}; + if (!protocols) { + protocols = []; + } else if (typeof protocols == "string") { + protocols = [protocols]; + } // Uses setTimeout() to make sure __createFlash() runs after the caller sets ws.onopen etc. // Otherwise, when onopen fires immediately, onopen is called before it is set. - setTimeout(function() { + self.__createTask = setTimeout(function() { WebSocket.__addTask(function() { + self.__createTask = null; WebSocket.__flash.create( - self.__id, url, protocol, proxyHost || null, proxyPort || 0, headers || null); + self.__id, url, protocols, proxyHost || null, proxyPort || 0, headers || null); }); }, 0); }; @@ -78,6 +98,12 @@ * Close this web socket gracefully. */ WebSocket.prototype.close = function() { + if (this.__createTask) { + clearTimeout(this.__createTask); + this.__createTask = null; + this.readyState = WebSocket.CLOSED; + return; + } if (this.readyState == WebSocket.CLOSED || this.readyState == WebSocket.CLOSING) { return; } @@ -131,7 +157,7 @@ events[i](event); } var handler = this["on" + event.type]; - if (handler) handler(event); + if (handler) handler.apply(this, [event]); }; /** @@ -139,16 +165,22 @@ * @param {Object} flashEvent */ WebSocket.prototype.__handleEvent = function(flashEvent) { + if ("readyState" in flashEvent) { this.readyState = flashEvent.readyState; } + if ("protocol" in flashEvent) { + this.protocol = flashEvent.protocol; + } var jsEvent; if (flashEvent.type == "open" || flashEvent.type == "error") { jsEvent = this.__createSimpleEvent(flashEvent.type); } else if (flashEvent.type == "close") { - // TODO implement jsEvent.wasClean jsEvent = this.__createSimpleEvent("close"); + jsEvent.wasClean = flashEvent.wasClean ? true : false; + jsEvent.code = flashEvent.code; + jsEvent.reason = flashEvent.reason; } else if (flashEvent.type == "message") { var data = decodeURIComponent(flashEvent.message); jsEvent = this.__createMessageEvent("message", data); @@ -157,6 +189,7 @@ } this.dispatchEvent(jsEvent); + }; WebSocket.prototype.__createSimpleEvent = function(type) { @@ -188,6 +221,9 @@ WebSocket.CLOSING = 2; WebSocket.CLOSED = 3; + // Field to check implementation of WebSocket. + WebSocket.__isFlashImplementation = true; + WebSocket.__initialized = false; WebSocket.__flash = null; WebSocket.__instances = {}; WebSocket.__tasks = []; @@ -207,16 +243,31 @@ * Loads WebSocketMain.swf and creates WebSocketMain object in Flash. */ WebSocket.__initialize = function() { - if (WebSocket.__flash) return; + + if (WebSocket.__initialized) return; + WebSocket.__initialized = true; if (WebSocket.__swfLocation) { // For backword compatibility. window.WEB_SOCKET_SWF_LOCATION = WebSocket.__swfLocation; } if (!window.WEB_SOCKET_SWF_LOCATION) { - console.error("[WebSocket] set WEB_SOCKET_SWF_LOCATION to location of WebSocketMain.swf"); + logger.error("[WebSocket] set WEB_SOCKET_SWF_LOCATION to location of WebSocketMain.swf"); return; } + if (!window.WEB_SOCKET_SUPPRESS_CROSS_DOMAIN_SWF_ERROR && + !WEB_SOCKET_SWF_LOCATION.match(/(^|\/)WebSocketMainInsecure\.swf(\?.*)?$/) && + WEB_SOCKET_SWF_LOCATION.match(/^\w+:\/\/([^\/]+)/)) { + var swfHost = RegExp.$1; + if (location.host != swfHost) { + logger.error( + "[WebSocket] You must host HTML and WebSocketMain.swf in the same host " + + "('" + location.host + "' != '" + swfHost + "'). " + + "See also 'How to host HTML file and SWF file in different domains' section " + + "in README.md. If you use WebSocketMainInsecure.swf, you can suppress this message " + + "by WEB_SOCKET_SUPPRESS_CROSS_DOMAIN_SWF_ERROR = true;"); + } + } var container = document.createElement("div"); container.id = "webSocketContainer"; // Hides Flash box. We cannot use display: none or visibility: hidden because it prevents @@ -250,9 +301,11 @@ null, function(e) { if (!e.success) { - console.error("[WebSocket] swfobject.embedSWF failed"); + logger.error("[WebSocket] swfobject.embedSWF failed"); } - }); + } + ); + }; /** @@ -287,7 +340,7 @@ WebSocket.__instances[events[i].webSocketId].__handleEvent(events[i]); } } catch (e) { - console.error(e); + logger.error(e); } }, 0); return true; @@ -295,12 +348,12 @@ // Called by Flash. WebSocket.__log = function(message) { - console.log(decodeURIComponent(message)); + logger.log(decodeURIComponent(message)); }; // Called by Flash. WebSocket.__error = function(message) { - console.error(decodeURIComponent(message)); + logger.error(decodeURIComponent(message)); }; WebSocket.__addTask = function(task) { @@ -327,15 +380,12 @@ }; if (!window.WEB_SOCKET_DISABLE_AUTO_INITIALIZATION) { - if (window.addEventListener) { - window.addEventListener("load", function(){ - WebSocket.__initialize(); - }, false); - } else { - window.attachEvent("onload", function(){ - WebSocket.__initialize(); - }); - } + // NOTE: + // This fires immediately if web_socket.js is dynamically loaded after + // the document is loaded. + swfobject.addDomLoadEvent(function() { + WebSocket.__initialize(); + }); } })(); diff --git a/webclients/novnc/include/websock.js b/webclients/novnc/include/websock.js index 20d51d6..1b89a91 100644 --- a/webclients/novnc/include/websock.js +++ b/webclients/novnc/include/websock.js @@ -1,7 +1,7 @@ /* * Websock: high-performance binary WebSockets * Copyright (C) 2012 Joel Martin - * Licensed under LGPL-3 (see LICENSE.txt) + * Licensed under MPL 2.0 (see LICENSE.txt) * * Websock is similar to the standard WebSocket object but Websock * enables communication with raw TCP sockets (i.e. the binary stream) @@ -14,7 +14,7 @@ * read binary data off of the receive queue. */ -/*jslint browser: true, bitwise: false, plusplus: false */ +/*jslint browser: true, bitwise: true */ /*global Util, Base64 */ @@ -35,326 +35,350 @@ if (window.WebSocket && !window.WEB_SOCKET_FORCE_FLASH) { Websock_native = false; (function () { - function get_INCLUDE_URI() { - return (typeof INCLUDE_URI !== "undefined") ? - INCLUDE_URI : "include/"; - } - - var start = " --> - - -
- + title="Move/Drag Viewport">
- - - - - + + + + - + value="Keyboard" title="Show Keyboard"/> + + +
+ + + + + +
+
Loading
+
- - + + - + - + - + + title="Disconnect" />
- @@ -178,10 +196,6 @@
-
-
Loading
-
-

no
VNC

@@ -192,9 +206,8 @@
+ + - diff --git a/webclients/novnc/vnc_auto.html b/webclients/novnc/vnc_auto.html index 2b60d77..ff376fe 100644 --- a/webclients/novnc/vnc_auto.html +++ b/webclients/novnc/vnc_auto.html @@ -1,33 +1,66 @@ - - - noVNC - - - - - - - -
+ noVNC + + + + + + + + + + + + + + + + + + + + + + + + + +
- +
Loading
+ Loading +
+ + + + +
@@ -41,6 +74,11 @@ /*global window, $, Util, RFB, */ "use strict"; + // Load supporting scripts + Util.load_scripts(["webutil.js", "base64.js", "websock.js", "des.js", + "keysymdef.js", "keyboard.js", "input.js", "display.js", + "jsunzip.js", "rfb.js"]); + var rfb; function passwordRequired(rfb) { @@ -61,6 +99,18 @@ rfb.sendCtrlAltDel(); return false; } + function xvpShutdown() { + rfb.xvpShutdown(); + return false; + } + function xvpReboot() { + rfb.xvpReboot(); + return false; + } + function xvpReset() { + rfb.xvpReset(); + return false; + } function updateState(rfb, state, oldstate, msg) { var s, sb, cad, level; s = $D('noVNC_status'); @@ -75,8 +125,12 @@ default: level = "warn"; break; } - if (state === "normal") { cad.disabled = false; } - else { cad.disabled = true; } + if (state === "normal") { + cad.disabled = false; + } else { + cad.disabled = true; + xvpInit(0); + } if (typeof(msg) !== 'undefined') { sb.setAttribute("class", "noVNC_status_" + level); @@ -84,17 +138,42 @@ } } - window.onload = function () { + function xvpInit(ver) { + var xvpbuttons; + xvpbuttons = $D('noVNC_xvp_buttons'); + if (ver >= 1) { + xvpbuttons.style.display = 'inline'; + } else { + xvpbuttons.style.display = 'none'; + } + } + + window.onscriptsload = function () { var host, port, password, path, token; $D('sendCtrlAltDelButton').style.display = "inline"; $D('sendCtrlAltDelButton').onclick = sendCtrlAltDel; + $D('xvpShutdownButton').onclick = xvpShutdown; + $D('xvpRebootButton').onclick = xvpReboot; + $D('xvpResetButton').onclick = xvpReset; + WebUtil.init_logging(WebUtil.getQueryVar('logging', 'warn')); document.title = unescape(WebUtil.getQueryVar('title', 'noVNC')); // By default, use the host and port of server that served this file host = WebUtil.getQueryVar('host', window.location.hostname); port = WebUtil.getQueryVar('port', window.location.port); + // if port == 80 (or 443) then it won't be present and should be + // set manually + if (!port) { + if (window.location.protocol.substring(0,5) == 'https') { + port = 443; + } + else if (window.location.protocol.substring(0,4) == 'http') { + port = 80; + } + } + // If a token variable is passed in, set the parameter in a cookie. // This is used by nova-novncproxy. token = WebUtil.getQueryVar('token', null); @@ -119,7 +198,8 @@ 'local_cursor': WebUtil.getQueryVar('cursor', true), 'shared': WebUtil.getQueryVar('shared', true), 'view_only': WebUtil.getQueryVar('view_only', false), - 'updateState': updateState, + 'onUpdateState': updateState, + 'onXvpInit': xvpInit, 'onPasswordRequired': passwordRequired}); rfb.connect(host, port, password, path); }; @@ -127,4 +207,3 @@ -