diff --git a/classes/index.vnc b/classes/index.vnc index 63b2f56..6997693 100644 --- a/classes/index.vnc +++ b/classes/index.vnc @@ -13,6 +13,12 @@ $USER's $DESKTOP desktop ($DISPLAY) -
-www.TightVNC.com +
+
+If the above Java applet does not work, you can also try the new JavaScript-only viewer. You will need a HTML5-capable browser though. +Click here to connect using noVNC. +
+
+
+LibVNCServer/libVNCClient Homepage diff --git a/classes/novnc/LICENSE.txt b/classes/novnc/LICENSE.txt new file mode 100644 index 0000000..755ace3 --- /dev/null +++ b/classes/novnc/LICENSE.txt @@ -0,0 +1,33 @@ +noVNC is Copyright (C) 2011 Joel Martin + +Some portions of noVNC are copyright to their individual authors. +Please refer to the individual source files and/or to the noVNC commit +history: https://github.com/kanaka/noVNC/commits/master + +noVNC is licensed under the LGPL (GNU Lesser General Public License) +version 3 with the following exceptions (all LGPL-3 compatible): + + include/input.js : LGPL-2 or any later version + + include/base64.js : Dual GPL-2 or LGPL-2.1 + + include/des.js : Various BSD style licenses + + include/web-socket-js/ : New BSD license. Source code at + http://github.com/gimite/web-socket-js + + include/Orbitron* : SIL Open Font License 1.1 + (Copyright 2009 Matt McInerney) + + images/ : Creative Commons Attribution-ShareAlike + http://creativecommons.org/licenses/by-sa/3.0/ + +The license texts are included at: + docs/LICENSE.LGPL-3 and + docs/LICENSE.GPL-3 + docs/LICENSE.OFL-1.1 + +Or alternatively the license texts may be found here: + http://www.gnu.org/licenses/lgpl.html and + http://www.gnu.org/licenses/gpl.html + http://scripts.sil.org/OFL diff --git a/classes/novnc/README.md b/classes/novnc/README.md new file mode 100644 index 0000000..4672969 --- /dev/null +++ b/classes/novnc/README.md @@ -0,0 +1,93 @@ +## noVNC: HTML5 VNC Client + + +### Description + +noVNC is a VNC client implemented using HTML5 technologies, +specifically Canvas and WebSockets (supports 'wss://' encryption). +noVNC is licensed under the +[LGPLv3](http://www.gnu.org/licenses/lgpl.html). + +Special thanks to [Sentry Data Systems](http://www.sentryds.com) for +sponsoring ongoing development of this project (and for employing me). + +There are many companies/projects that have integrated noVNC into +their products including: [Sentry Data Systems](http://www.sentryds.com), [Ganeti Web Manager](http://code.osuosl.org/projects/ganeti-webmgr), [Archipel](http://archipelproject.org), [openQRM](http://www.openqrm.com/), [OpenNode](http://www.opennodecloud.com/), [OpenStack](http://www.openstack.org), [Broadway (HTML5 GDK/GTK+ backend)](http://blogs.gnome.org/alexl/2011/03/15/gtk-html-backend-update/), [OpenNebula](http://opennebula.org/), [CloudSigma](http://www.cloudsigma.com/), [Zentyal (formerly eBox)](http://www.zentyal.org/), and [SlapOS](http://www.slapos.org). See [this wiki page](https://github.com/kanaka/noVNC/wiki/ProjectsCompanies-using-noVNC) for more info and links. + +Notable commits, announcements and news are posted to +@noVNC + + +### Screenshots + +Running in Chrome before and after connecting: + +  + +See more screenshots here. + + +### Browser Requirements + +* HTML5 Canvas (with createImageData): Chrome, Firefox 3.6+, iOS + Safari, Opera 11+, Internet Explorer 9+, etc. + +* HTML5 WebSockets: For browsers that do not have builtin + WebSockets support, the project includes + web-socket-js, + a WebSockets emulator using Adobe Flash. iOS 4.2+ has built-in + WebSocket support. + +* Fast Javascript Engine: noVNC avoids using new Javascript + functionality so it will run on older browsers, but decode and + rendering happen in Javascript, so a slow Javascript engine will + mean noVNC is painfully slow. + +* I maintain a more detailed browser compatibility list here. + + +### Server Requirements + +Unless you are using a VNC server with support for WebSockets +connections (only my [fork of libvncserver](http://github.com/kanaka/libvncserver) +currently), you need to use a WebSockets to TCP socket proxy. There is +a python proxy included ('websockify'). One advantage of using the +proxy is that it has builtin support for SSL/TLS encryption (i.e. +"wss://"). + +There a few reasons why a proxy is required: + + 1. WebSockets is not a pure socket protocol. There is an initial HTTP + like handshake to allow easy hand-off by web servers and allow + some origin policy exchange. Also, each WebSockets frame begins + with 0 ('\x00') and ends with 255 ('\xff'). + + 2. Javascript itself does not have the ability to handle pure byte + arrays. The python proxy encodes the data as base64 so that the + Javascript client can decode the data as an integer array. + + +### Quick Start + +* Use the launch script to start a mini-webserver and the WebSockets + proxy (websockify). The `--vnc` option is used to specify the location of + a running VNC server: + + `./utils/launch.sh --vnc localhost:5901` + +* Point your browser to the cut-and-paste URL that is output by the + launch script. Enter a password if the VNC server has one + configured. Hit the Connect button and enjoy! + + +### Other Pages + +* [Advanced Usage](https://github.com/kanaka/noVNC/wiki/Advanced-usage). Generating an SSL + certificate, starting a VNC server, advanced websockify usage, etc. + +* [Integrating noVNC](https://github.com/kanaka/noVNC/wiki/Integration) into existing projects. + +* [Troubleshooting noVNC](https://github.com/kanaka/noVNC/wiki/Troubleshooting) problems. + + diff --git a/classes/novnc/favicon.ico b/classes/novnc/favicon.ico new file mode 120000 index 0000000..45399c8 --- /dev/null +++ b/classes/novnc/favicon.ico @@ -0,0 +1 @@ +images/favicon.ico \ No newline at end of file diff --git a/classes/novnc/images/clipboard.png b/classes/novnc/images/clipboard.png new file mode 100644 index 0000000..24df33c Binary files /dev/null and b/classes/novnc/images/clipboard.png differ diff --git a/classes/novnc/images/connect.png b/classes/novnc/images/connect.png new file mode 100644 index 0000000..79e71ad Binary files /dev/null and b/classes/novnc/images/connect.png differ diff --git a/classes/novnc/images/ctrlaltdel.png b/classes/novnc/images/ctrlaltdel.png new file mode 100644 index 0000000..31922e5 Binary files /dev/null and b/classes/novnc/images/ctrlaltdel.png differ diff --git a/classes/novnc/images/disconnect.png b/classes/novnc/images/disconnect.png new file mode 100644 index 0000000..8832f5e Binary files /dev/null and b/classes/novnc/images/disconnect.png differ diff --git a/classes/novnc/images/drag.png b/classes/novnc/images/drag.png new file mode 100644 index 0000000..433f896 Binary files /dev/null and b/classes/novnc/images/drag.png differ diff --git a/classes/novnc/images/favicon.ico b/classes/novnc/images/favicon.ico new file mode 100644 index 0000000..c999634 Binary files /dev/null and b/classes/novnc/images/favicon.ico differ diff --git a/classes/novnc/images/favicon.png b/classes/novnc/images/favicon.png new file mode 100644 index 0000000..e2bdb19 Binary files /dev/null and b/classes/novnc/images/favicon.png differ diff --git a/classes/novnc/images/keyboard.png b/classes/novnc/images/keyboard.png new file mode 100644 index 0000000..f797952 Binary files /dev/null and b/classes/novnc/images/keyboard.png differ diff --git a/classes/novnc/images/mouse_left.png b/classes/novnc/images/mouse_left.png new file mode 100644 index 0000000..1de7a48 Binary files /dev/null and b/classes/novnc/images/mouse_left.png differ diff --git a/classes/novnc/images/mouse_middle.png b/classes/novnc/images/mouse_middle.png new file mode 100644 index 0000000..81fbd9b Binary files /dev/null and b/classes/novnc/images/mouse_middle.png differ diff --git a/classes/novnc/images/mouse_none.png b/classes/novnc/images/mouse_none.png new file mode 100644 index 0000000..93dbf57 Binary files /dev/null and b/classes/novnc/images/mouse_none.png differ diff --git a/classes/novnc/images/mouse_right.png b/classes/novnc/images/mouse_right.png new file mode 100644 index 0000000..355b25d Binary files /dev/null and b/classes/novnc/images/mouse_right.png differ diff --git a/classes/novnc/images/screen_320x460.png b/classes/novnc/images/screen_320x460.png new file mode 100644 index 0000000..172ec55 Binary files /dev/null and b/classes/novnc/images/screen_320x460.png differ diff --git a/classes/novnc/images/screen_57x57.png b/classes/novnc/images/screen_57x57.png new file mode 100644 index 0000000..e2085f2 Binary files /dev/null and b/classes/novnc/images/screen_57x57.png differ diff --git a/classes/novnc/images/screen_700x700.png b/classes/novnc/images/screen_700x700.png new file mode 100644 index 0000000..ae67768 Binary files /dev/null and b/classes/novnc/images/screen_700x700.png differ diff --git a/classes/novnc/images/settings.png b/classes/novnc/images/settings.png new file mode 100644 index 0000000..a43f5e1 Binary files /dev/null and b/classes/novnc/images/settings.png differ diff --git a/classes/novnc/include/Orbitron700.ttf b/classes/novnc/include/Orbitron700.ttf new file mode 100644 index 0000000..e28729d Binary files /dev/null and b/classes/novnc/include/Orbitron700.ttf differ diff --git a/classes/novnc/include/Orbitron700.woff b/classes/novnc/include/Orbitron700.woff new file mode 100644 index 0000000..61db630 Binary files /dev/null and b/classes/novnc/include/Orbitron700.woff differ diff --git a/classes/novnc/include/base.css b/classes/novnc/include/base.css new file mode 100644 index 0000000..0a62a1b --- /dev/null +++ b/classes/novnc/include/base.css @@ -0,0 +1,380 @@ +body { + margin:0; + padding:0; + font-family: Helvetica; + /*Background image with light grey curve.*/ + background-color:#494949; + background-repeat:no-repeat; + background-position:right bottom; + height:100%; +} + +html { + height:100%; +} + +#noVNC_controls ul { + list-style: none; + margin: 0px; + padding: 0px; +} +#noVNC_controls li { + padding-bottom:8px; +} + +#noVNC_host { + width:150px; +} +#noVNC_port { + width: 80px; +} +#noVNC_password { + width: 150px; +} +#noVNC_encrypt { +} +#noVNC_connectTimeout { + width: 30px; +} +#noVNC_path { + width: 100px; +} +#noVNC_connect_button { + width: 110px; + float:right; +} + + +#noVNC_view_drag_button { + display: none; +} +#sendCtrlAltDelButton { + display: none; +} +#noVNC_mobile_buttons { + display: none; +} + +.noVNC-buttons-left { + float: left; + padding-left:10px; + padding-top:4px; +} + +.noVNC-buttons-right { + float:right; + right: 0px; + padding-right:10px; + padding-top:4px; +} + +#noVNC_status_bar { + margin-top: 0px; + padding: 0px; +} + +#noVNC_status_bar div { + font-size: 12px; + padding-top: 4px; + width:100%; +} + +#noVNC_status { + height:20px; + text-align: center; +} +#noVNC_settings_menu { + margin: 3px; + text-align: left; +} +#noVNC_settings_menu ul { + list-style: none; + margin: 0px; + padding: 0px; +} + +#noVNC_apply { + float:right; +} + +.noVNC_status_normal { + background: #eee; +} +.noVNC_status_error { + background: #f44; +} +.noVNC_status_warn { + background: #ff4; +} + +/* Do not set width/height for VNC_screen or VNC_canvas or incorrect + * scaling will occur. Canvas resizes to remote VNC settings */ +#noVNC_screen_pad { + margin: 0px; + padding: 0px; + height: 44px; +} +#noVNC_screen { + text-align: center; + display: table; + width:100%; + height:100%; + background-color:#313131; + border-bottom-right-radius: 800px 600px; + /*border-top-left-radius: 800px 600px;*/ +} + +#noVNC_container, #noVNC_canvas { + margin: 0px; + padding: 0px; +} + +#noVNC_canvas { + left: 0px; +} + +#VNC_clipboard_clear_button { + float:right; +} +#VNC_clipboard_text { + font-size: 11px; +} + +#noVNC_clipboard_clear_button { + float:right; +} + +/*Bubble contents divs*/ +#noVNC_settings { + display:none; + margin-top:77px; + right:20px; + position:fixed; +} + +#noVNC_controls { + margin-top:77px; + right:12px; + position:fixed; +} +#noVNC_controls.top:after { + right:15px; +} + +#noVNC_clipboard { + display:none; + margin-top:77px; + right:30px; + position:fixed; +} +#noVNC_clipboard.top:after { + right:85px; +} + +#keyboardinput { + width:1px; + height:1px; + background-color:#fff; + color:#fff; + border:0; + position: relative; + left: -40px; + z-index: -1; +} + +.noVNC_status_warn { + background-color:yellow; +} + +/* + * Advanced Styling + */ + +/* Control bar */ +#noVNC-control-bar { + position:fixed; + background: #b2bdcd; /* Old browsers */ + background: -moz-linear-gradient(top, #b2bdcd 0%, #899cb3 49%, #7e93af 51%, #6e84a3 100%); /* FF3.6+ */ + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#b2bdcd), color-stop(49%,#899cb3), color-stop(51%,#7e93af), color-stop(100%,#6e84a3)); /* Chrome,Safari4+ */ + background: -webkit-linear-gradient(top, #b2bdcd 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* Chrome10+,Safari5.1+ */ + background: -o-linear-gradient(top, #b2bdcd 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* Opera11.10+ */ + background: -ms-linear-gradient(top, #b2bdcd 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* IE10+ */ + background: linear-gradient(top, #b2bdcd 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* W3C */ + + display:block; + height:44px; + left:0; + top:0; + width:100%; + z-index:200; +} + +.noVNC_status_button { + padding: 4px 4px; + vertical-align: middle; + border:1px solid #869dbc; + -webkit-border-radius: 6px; + -moz-border-radius: 6px; + border-radius: 6px; + background: #b2bdcd; /* Old browsers */ + background: -moz-linear-gradient(top, #b2bdcd 0%, #899cb3 49%, #7e93af 51%, #6e84a3 100%); /* FF3.6+ */ + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#b2bdcd), color-stop(49%,#899cb3), color-stop(51%,#7e93af), color-stop(100%,#6e84a3)); /* Chrome,Safari4+ */ + background: -webkit-linear-gradient(top, #b2bdcd 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* Chrome10+,Safari5.1+ */ + background: -o-linear-gradient(top, #b2bdcd 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* Opera11.10+ */ + background: -ms-linear-gradient(top, #b2bdcd 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* IE10+ */ + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#b2bdcd', endColorstr='#6e84a3',GradientType=0 ); /* IE6-9 */ + background: linear-gradient(top, #b2bdcd 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* W3C */ + /*box-shadow:inset 0.4px 0.4px 0.4px #000000;*/ +} + +.noVNC_status_button_selected { + padding: 4px 4px; + vertical-align: middle; + border:1px solid #4366a9; + -webkit-border-radius: 6px; + -moz-border-radius: 6px; + background: #779ced; /* Old browsers */ + background: -moz-linear-gradient(top, #779ced 0%, #3970e0 49%, #2160dd 51%, #2463df 100%); /* FF3.6+ */ + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#779ced), color-stop(49%,#3970e0), color-stop(51%,#2160dd), color-stop(100%,#2463df)); /* Chrome,Safari4+ */ + background: -webkit-linear-gradient(top, #779ced 0%,#3970e0 49%,#2160dd 51%,#2463df 100%); /* Chrome10+,Safari5.1+ */ + background: -o-linear-gradient(top, #779ced 0%,#3970e0 49%,#2160dd 51%,#2463df 100%); /* Opera11.10+ */ + background: -ms-linear-gradient(top, #779ced 0%,#3970e0 49%,#2160dd 51%,#2463df 100%); /* IE10+ */ + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#779ced', endColorstr='#2463df',GradientType=0 ); /* IE6-9 */ + background: linear-gradient(top, #779ced 0%,#3970e0 49%,#2160dd 51%,#2463df 100%); /* W3C */ + /*box-shadow:inset 0.4px 0.4px 0.4px #000000;*/ +} + + +/*Settings Bubble*/ +.triangle-right { + position:relative; + padding:15px; + margin:1em 0 3em; + color:#fff; + background:#fff; /* default background for browsers without gradient support */ + /* css3 */ + /*background:-webkit-gradient(linear, 0 0, 0 100%, from(#2e88c4), to(#075698)); + background:-moz-linear-gradient(#2e88c4, #075698); + background:-o-linear-gradient(#2e88c4, #075698); + background:linear-gradient(#2e88c4, #075698);*/ + -webkit-border-radius:10px; + -moz-border-radius:10px; + border-radius:10px; + color:#000; + border:2px solid #E0E0E0; +} + +.triangle-right.top:after { + border-color: transparent #E0E0E0; + border-width: 20px 20px 0 0; + bottom: auto; + left: auto; + right: 50px; + top: -20px; +} + +.triangle-right:after { + content:""; + position:absolute; + bottom:-20px; /* value = - border-top-width - border-bottom-width */ + left:50px; /* controls horizontal position */ + border-width:20px 0 0 20px; /* vary these values to change the angle of the vertex */ + border-style:solid; + border-color:#E0E0E0 transparent; + /* reduce the damage in FF3.0 */ + display:block; + width:0; +} + +.triangle-right.top:after { + top:-40px; /* value = - border-top-width - border-bottom-width */ + right:50px; /* controls horizontal position */ + bottom:auto; + left:auto; + border-width:40px 40px 0 0; /* vary these values to change the angle of the vertex */ + border-color:transparent #E0E0E0; +} + +/*Default noVNC logo.*/ +/* From: http://fonts.googleapis.com/css?family=Orbitron:700 */ +@font-face { + font-family: 'Orbitron'; + font-style: normal; + font-weight: 700; + src: local('?'), url('Orbitron700.woff') format('woff'), + url('Orbitron700.ttf') format('truetype'); +} + +#noVNC_logo { + margin-top: 170px; + margin-left: 10px; + color:yellow; + text-align:left; + font-family: 'Orbitron', 'OrbitronTTF', sans-serif; + line-height:90%; + text-shadow: + 5px 5px 0 #000, + -1px -1px 0 #000, + 1px -1px 0 #000, + -1px 1px 0 #000, + 1px 1px 0 #000; +} + + +#noVNC_logo span{ + color:green; +} + +/* ---------------------------------------- + * Media sizing + * ---------------------------------------- + */ + + +.noVNC_status_button { + font-size: 12px; +} + +#noVNC_clipboard_text { + width: 500px; +} + +#noVNC_logo { + font-size: 180px; +} + +@media screen and (min-width: 481px) and (max-width: 640px) { + .noVNC_status_button { + font-size: 10px; + } + #noVNC_clipboard_text { + width: 410px; + } + #noVNC_logo { + font-size: 150px; + } +} + +@media screen and (min-width: 321px) and (max-width: 480px) { + .noVNC_status_button { + font-size: 10px; + } + #noVNC_clipboard_text { + width: 250px; + } + #noVNC_logo { + font-size: 110px; + } +} + +@media screen and (max-width: 320px) { + .noVNC_status_button { + font-size: 9px; + } + #noVNC_clipboard_text { + width: 220px; + } + #noVNC_logo { + font-size: 90px; + } +} diff --git a/classes/novnc/include/base64.js b/classes/novnc/include/base64.js new file mode 100644 index 0000000..c68b33a --- /dev/null +++ b/classes/novnc/include/base64.js @@ -0,0 +1,147 @@ +/* + * Modified from: + * http://lxr.mozilla.org/mozilla/source/extensions/xml-rpc/src/nsXmlRpcClient.js#956 + */ + +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla XML-RPC Client component. + * + * The Initial Developer of the Original Code is + * Digital Creations 2, Inc. + * Portions created by the Initial Developer are Copyright (C) 2000 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Martijn Pieters (original author) + * Samuel Sieb + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/*jslint white: false, bitwise: false, plusplus: false */ +/*global console */ + +var Base64 = { + +/* Convert data (an array of integers) to a Base64 string. */ +toBase64Table : 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/', +base64Pad : '=', + +encode: function (data) { + "use strict"; + var result = '', + chrTable = Base64.toBase64Table.split(''), + pad = Base64.base64Pad, + length = data.length, + i; + // Convert every three bytes to 4 ascii characters. + for (i = 0; i < (length - 2); i += 3) { + result += chrTable[data[i] >> 2]; + result += chrTable[((data[i] & 0x03) << 4) + (data[i+1] >> 4)]; + result += chrTable[((data[i+1] & 0x0f) << 2) + (data[i+2] >> 6)]; + result += chrTable[data[i+2] & 0x3f]; + } + + // Convert the remaining 1 or 2 bytes, pad out to 4 characters. + if (length%3) { + i = length - (length%3); + result += chrTable[data[i] >> 2]; + if ((length%3) === 2) { + result += chrTable[((data[i] & 0x03) << 4) + (data[i+1] >> 4)]; + result += chrTable[(data[i+1] & 0x0f) << 2]; + result += pad; + } else { + result += chrTable[(data[i] & 0x03) << 4]; + result += pad + pad; + } + } + + return result; +}, + +/* Convert Base64 data to a string */ +toBinaryTable : [ + -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, + -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, + -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,62, -1,-1,-1,63, + 52,53,54,55, 56,57,58,59, 60,61,-1,-1, -1, 0,-1,-1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14, + 15,16,17,18, 19,20,21,22, 23,24,25,-1, -1,-1,-1,-1, + -1,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40, + 41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1 +], + +decode: function (data, offset) { + "use strict"; + offset = typeof(offset) !== 'undefined' ? offset : 0; + var binTable = Base64.toBinaryTable, + pad = Base64.base64Pad, + result, result_length, idx, i, c, padding, + leftbits = 0, // number of bits decoded, but yet to be appended + leftdata = 0, // bits decoded, but yet to be appended + data_length = data.indexOf('=') - offset; + + if (data_length < 0) { data_length = data.length - offset; } + + /* Every four characters is 3 resulting numbers */ + result_length = (data_length >> 2) * 3 + Math.floor((data_length%4)/1.5); + result = new Array(result_length); + + // Convert one by one. + for (idx = 0, i = offset; i < data.length; i++) { + c = binTable[data.charCodeAt(i) & 0x7f]; + padding = (data.charAt(i) === pad); + // Skip illegal characters and whitespace + if (c === -1) { + console.error("Illegal character '" + data.charCodeAt(i) + "'"); + continue; + } + + // Collect data into leftdata, update bitcount + leftdata = (leftdata << 6) | c; + leftbits += 6; + + // If we have 8 or more bits, append 8 bits to the result + if (leftbits >= 8) { + leftbits -= 8; + // Append if not padding. + if (!padding) { + result[idx++] = (leftdata >> leftbits) & 0xff; + } + leftdata &= (1 << leftbits) - 1; + } + } + + // If there are any bits left, the base64 string was corrupted + if (leftbits) { + throw {name: 'Base64-Error', + message: 'Corrupted base64 string'}; + } + + return result; +} + +}; /* End of Base64 namespace */ diff --git a/classes/novnc/include/black.css b/classes/novnc/include/black.css new file mode 100644 index 0000000..8f80f66 --- /dev/null +++ b/classes/novnc/include/black.css @@ -0,0 +1,45 @@ +#keyboardinput { + background-color:#000; +} + +#noVNC-control-bar { + background: #4c4c4c; /* Old browsers */ + background: -moz-linear-gradient(top, #4c4c4c 0%, #2c2c2c 50%, #000000 51%, #131313 100%); /* FF3.6+ */ + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#4c4c4c), color-stop(50%,#2c2c2c), color-stop(51%,#000000), color-stop(100%,#131313)); /* Chrome,Safari4+ */ + background: -webkit-linear-gradient(top, #4c4c4c 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* Chrome10+,Safari5.1+ */ + background: -o-linear-gradient(top, #4c4c4c 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* Opera11.10+ */ + background: -ms-linear-gradient(top, #4c4c4c 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* IE10+ */ + background: linear-gradient(top, #4c4c4c 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* W3C */ +} + +.triangle-right { + border:2px solid #fff; + background:#000; + color:#fff; +} + +.noVNC_status_button { + font-size: 12px; + vertical-align: middle; + border:1px solid #4c4c4c; + + background: #4c4c4c; /* Old browsers */ + background: -moz-linear-gradient(top, #4c4c4c 0%, #2c2c2c 50%, #000000 51%, #131313 100%); /* FF3.6+ */ + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#4c4c4c), color-stop(50%,#2c2c2c), color-stop(51%,#000000), color-stop(100%,#131313)); /* Chrome,Safari4+ */ + background: -webkit-linear-gradient(top, #4c4c4c 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* Chrome10+,Safari5.1+ */ + background: -o-linear-gradient(top, #4c4c4c 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* Opera11.10+ */ + background: -ms-linear-gradient(top, #4c4c4c 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* IE10+ */ + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#4c4c4c', endColorstr='#131313',GradientType=0 ); /* IE6-9 */ + background: linear-gradient(top, #4c4c4c 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* W3C */ +} + +.noVNC_status_button_selected { + background: #9dd53a; /* Old browsers */ + background: -moz-linear-gradient(top, #9dd53a 0%, #a1d54f 50%, #80c217 51%, #7cbc0a 100%); /* FF3.6+ */ + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#9dd53a), color-stop(50%,#a1d54f), color-stop(51%,#80c217), color-stop(100%,#7cbc0a)); /* Chrome,Safari4+ */ + background: -webkit-linear-gradient(top, #9dd53a 0%,#a1d54f 50%,#80c217 51%,#7cbc0a 100%); /* Chrome10+,Safari5.1+ */ + background: -o-linear-gradient(top, #9dd53a 0%,#a1d54f 50%,#80c217 51%,#7cbc0a 100%); /* Opera11.10+ */ + background: -ms-linear-gradient(top, #9dd53a 0%,#a1d54f 50%,#80c217 51%,#7cbc0a 100%); /* IE10+ */ + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#9dd53a', endColorstr='#7cbc0a',GradientType=0 ); /* IE6-9 */ + background: linear-gradient(top, #9dd53a 0%,#a1d54f 50%,#80c217 51%,#7cbc0a 100%); /* W3C */ +} diff --git a/classes/novnc/include/blue.css b/classes/novnc/include/blue.css new file mode 100644 index 0000000..a8baf70 --- /dev/null +++ b/classes/novnc/include/blue.css @@ -0,0 +1,27 @@ + +#noVNC-control-bar { + background-color:#04073d; + background-image: -webkit-gradient( + linear, + left bottom, + left top, + color-stop(0.54, rgb(10,15,79)), + color-stop(0.5, rgb(4,7,61)) + ); + background-image: -moz-linear-gradient( + center bottom, + rgb(10,15,79) 54%, + rgb(4,7,61) 50% + ); +} + +.triangle-right { + border:2px solid #fff; + background:#04073d; + color:#fff; +} + +#keyboardinput { + background-color:#04073d; +} + diff --git a/classes/novnc/include/des.js b/classes/novnc/include/des.js new file mode 100644 index 0000000..1f95285 --- /dev/null +++ b/classes/novnc/include/des.js @@ -0,0 +1,273 @@ +/* + * Ported from Flashlight VNC ActionScript implementation: + * http://www.wizhelp.com/flashlight-vnc/ + * + * Full attribution follows: + * + * ------------------------------------------------------------------------- + * + * This DES class has been extracted from package Acme.Crypto for use in VNC. + * The unnecessary odd parity code has been removed. + * + * These changes are: + * Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + + * DesCipher - the DES encryption method + * + * The meat of this code is by Dave Zimmerman , and is: + * + * Copyright (c) 1996 Widget Workshop, Inc. All Rights Reserved. + * + * Permission to use, copy, modify, and distribute this software + * and its documentation for NON-COMMERCIAL or COMMERCIAL purposes and + * without fee is hereby granted, provided that this copyright notice is kept + * intact. + * + * WIDGET WORKSHOP MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY + * OF THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED + * TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A + * PARTICULAR PURPOSE, OR NON-INFRINGEMENT. WIDGET WORKSHOP SHALL NOT BE LIABLE + * FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR + * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. + * + * THIS SOFTWARE IS NOT DESIGNED OR INTENDED FOR USE OR RESALE AS ON-LINE + * CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING FAIL-SAFE + * PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES, AIRCRAFT + * NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT LIFE + * SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE + * SOFTWARE COULD LEAD DIRECTLY TO DEATH, PERSONAL INJURY, OR SEVERE + * PHYSICAL OR ENVIRONMENTAL DAMAGE ("HIGH RISK ACTIVITIES"). WIDGET WORKSHOP + * SPECIFICALLY DISCLAIMS ANY EXPRESS OR IMPLIED WARRANTY OF FITNESS FOR + * HIGH RISK ACTIVITIES. + * + * + * The rest is: + * + * Copyright (C) 1996 by Jef Poskanzer . All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Visit the ACME Labs Java page for up-to-date versions of this and other + * fine Java utilities: http://www.acme.com/java/ + */ + +"use strict"; +/*jslint white: false, bitwise: false, plusplus: false */ + +function DES(passwd) { + +// Tables, permutations, S-boxes, etc. +var PC2 = [13,16,10,23, 0, 4, 2,27,14, 5,20, 9,22,18,11, 3, + 25, 7,15, 6,26,19,12, 1,40,51,30,36,46,54,29,39, + 50,44,32,47,43,48,38,55,33,52,45,41,49,35,28,31 ], + totrot = [ 1, 2, 4, 6, 8,10,12,14,15,17,19,21,23,25,27,28], + z = 0x0, a,b,c,d,e,f, SP1,SP2,SP3,SP4,SP5,SP6,SP7,SP8, + keys = []; + +a=1<<16; b=1<<24; c=a|b; d=1<<2; e=1<<10; f=d|e; +SP1 = [c|e,z|z,a|z,c|f,c|d,a|f,z|d,a|z,z|e,c|e,c|f,z|e,b|f,c|d,b|z,z|d, + z|f,b|e,b|e,a|e,a|e,c|z,c|z,b|f,a|d,b|d,b|d,a|d,z|z,z|f,a|f,b|z, + a|z,c|f,z|d,c|z,c|e,b|z,b|z,z|e,c|d,a|z,a|e,b|d,z|e,z|d,b|f,a|f, + c|f,a|d,c|z,b|f,b|d,z|f,a|f,c|e,z|f,b|e,b|e,z|z,a|d,a|e,z|z,c|d]; +a=1<<20; b=1<<31; c=a|b; d=1<<5; e=1<<15; f=d|e; +SP2 = [c|f,b|e,z|e,a|f,a|z,z|d,c|d,b|f,b|d,c|f,c|e,b|z,b|e,a|z,z|d,c|d, + a|e,a|d,b|f,z|z,b|z,z|e,a|f,c|z,a|d,b|d,z|z,a|e,z|f,c|e,c|z,z|f, + z|z,a|f,c|d,a|z,b|f,c|z,c|e,z|e,c|z,b|e,z|d,c|f,a|f,z|d,z|e,b|z, + z|f,c|e,a|z,b|d,a|d,b|f,b|d,a|d,a|e,z|z,b|e,z|f,b|z,c|d,c|f,a|e]; +a=1<<17; b=1<<27; c=a|b; d=1<<3; e=1<<9; f=d|e; +SP3 = [z|f,c|e,z|z,c|d,b|e,z|z,a|f,b|e,a|d,b|d,b|d,a|z,c|f,a|d,c|z,z|f, + b|z,z|d,c|e,z|e,a|e,c|z,c|d,a|f,b|f,a|e,a|z,b|f,z|d,c|f,z|e,b|z, + c|e,b|z,a|d,z|f,a|z,c|e,b|e,z|z,z|e,a|d,c|f,b|e,b|d,z|e,z|z,c|d, + b|f,a|z,b|z,c|f,z|d,a|f,a|e,b|d,c|z,b|f,z|f,c|z,a|f,z|d,c|d,a|e]; +a=1<<13; b=1<<23; c=a|b; d=1<<0; e=1<<7; f=d|e; +SP4 = [c|d,a|f,a|f,z|e,c|e,b|f,b|d,a|d,z|z,c|z,c|z,c|f,z|f,z|z,b|e,b|d, + z|d,a|z,b|z,c|d,z|e,b|z,a|d,a|e,b|f,z|d,a|e,b|e,a|z,c|e,c|f,z|f, + b|e,b|d,c|z,c|f,z|f,z|z,z|z,c|z,a|e,b|e,b|f,z|d,c|d,a|f,a|f,z|e, + c|f,z|f,z|d,a|z,b|d,a|d,c|e,b|f,a|d,a|e,b|z,c|d,z|e,b|z,a|z,c|e]; +a=1<<25; b=1<<30; c=a|b; d=1<<8; e=1<<19; f=d|e; +SP5 = [z|d,a|f,a|e,c|d,z|e,z|d,b|z,a|e,b|f,z|e,a|d,b|f,c|d,c|e,z|f,b|z, + a|z,b|e,b|e,z|z,b|d,c|f,c|f,a|d,c|e,b|d,z|z,c|z,a|f,a|z,c|z,z|f, + z|e,c|d,z|d,a|z,b|z,a|e,c|d,b|f,a|d,b|z,c|e,a|f,b|f,z|d,a|z,c|e, + c|f,z|f,c|z,c|f,a|e,z|z,b|e,c|z,z|f,a|d,b|d,z|e,z|z,b|e,a|f,b|d]; +a=1<<22; b=1<<29; c=a|b; d=1<<4; e=1<<14; f=d|e; +SP6 = [b|d,c|z,z|e,c|f,c|z,z|d,c|f,a|z,b|e,a|f,a|z,b|d,a|d,b|e,b|z,z|f, + z|z,a|d,b|f,z|e,a|e,b|f,z|d,c|d,c|d,z|z,a|f,c|e,z|f,a|e,c|e,b|z, + b|e,z|d,c|d,a|e,c|f,a|z,z|f,b|d,a|z,b|e,b|z,z|f,b|d,c|f,a|e,c|z, + a|f,c|e,z|z,c|d,z|d,z|e,c|z,a|f,z|e,a|d,b|f,z|z,c|e,b|z,a|d,b|f]; +a=1<<21; b=1<<26; c=a|b; d=1<<1; e=1<<11; f=d|e; +SP7 = [a|z,c|d,b|f,z|z,z|e,b|f,a|f,c|e,c|f,a|z,z|z,b|d,z|d,b|z,c|d,z|f, + b|e,a|f,a|d,b|e,b|d,c|z,c|e,a|d,c|z,z|e,z|f,c|f,a|e,z|d,b|z,a|e, + b|z,a|e,a|z,b|f,b|f,c|d,c|d,z|d,a|d,b|z,b|e,a|z,c|e,z|f,a|f,c|e, + z|f,b|d,c|f,c|z,a|e,z|z,z|d,c|f,z|z,a|f,c|z,z|e,b|d,b|e,z|e,a|d]; +a=1<<18; b=1<<28; c=a|b; d=1<<6; e=1<<12; f=d|e; +SP8 = [b|f,z|e,a|z,c|f,b|z,b|f,z|d,b|z,a|d,c|z,c|f,a|e,c|e,a|f,z|e,z|d, + c|z,b|d,b|e,z|f,a|e,a|d,c|d,c|e,z|f,z|z,z|z,c|d,b|d,b|e,a|f,a|z, + a|f,a|z,c|e,z|e,z|d,c|d,z|e,a|f,b|e,z|d,b|d,c|z,c|d,b|z,a|z,b|f, + z|z,c|f,a|d,b|d,c|z,b|e,b|f,z|z,c|f,a|e,a|e,z|f,z|f,a|d,b|z,c|e]; + +// Set the key. +function setKeys(keyBlock) { + var i, j, l, m, n, o, pc1m = [], pcr = [], kn = [], + raw0, raw1, rawi, KnLi; + + for (j = 0, l = 56; j < 56; ++j, l-=8) { + l += l<-5 ? 65 : l<-3 ? 31 : l<-1 ? 63 : l===27 ? 35 : 0; // PC1 + m = l & 0x7; + pc1m[j] = ((keyBlock[l >>> 3] & (1<>> 10; + keys[KnLi] |= (raw1 & 0x00000fc0) >>> 6; + ++KnLi; + keys[KnLi] = (raw0 & 0x0003f000) << 12; + keys[KnLi] |= (raw0 & 0x0000003f) << 16; + keys[KnLi] |= (raw1 & 0x0003f000) >>> 4; + keys[KnLi] |= (raw1 & 0x0000003f); + ++KnLi; + } +} + +// Encrypt 8 bytes of text +function enc8(text) { + var i = 0, b = text.slice(), fval, keysi = 0, + l, r, x; // left, right, accumulator + + // Squash 8 bytes to 2 ints + l = b[i++]<<24 | b[i++]<<16 | b[i++]<<8 | b[i++]; + r = b[i++]<<24 | b[i++]<<16 | b[i++]<<8 | b[i++]; + + x = ((l >>> 4) ^ r) & 0x0f0f0f0f; + r ^= x; + l ^= (x << 4); + x = ((l >>> 16) ^ r) & 0x0000ffff; + r ^= x; + l ^= (x << 16); + x = ((r >>> 2) ^ l) & 0x33333333; + l ^= x; + r ^= (x << 2); + x = ((r >>> 8) ^ l) & 0x00ff00ff; + l ^= x; + r ^= (x << 8); + r = (r << 1) | ((r >>> 31) & 1); + x = (l ^ r) & 0xaaaaaaaa; + l ^= x; + r ^= x; + l = (l << 1) | ((l >>> 31) & 1); + + for (i = 0; i < 8; ++i) { + x = (r << 28) | (r >>> 4); + x ^= keys[keysi++]; + fval = SP7[x & 0x3f]; + fval |= SP5[(x >>> 8) & 0x3f]; + fval |= SP3[(x >>> 16) & 0x3f]; + fval |= SP1[(x >>> 24) & 0x3f]; + x = r ^ keys[keysi++]; + fval |= SP8[x & 0x3f]; + fval |= SP6[(x >>> 8) & 0x3f]; + fval |= SP4[(x >>> 16) & 0x3f]; + fval |= SP2[(x >>> 24) & 0x3f]; + l ^= fval; + x = (l << 28) | (l >>> 4); + x ^= keys[keysi++]; + fval = SP7[x & 0x3f]; + fval |= SP5[(x >>> 8) & 0x3f]; + fval |= SP3[(x >>> 16) & 0x3f]; + fval |= SP1[(x >>> 24) & 0x3f]; + x = l ^ keys[keysi++]; + fval |= SP8[x & 0x0000003f]; + fval |= SP6[(x >>> 8) & 0x3f]; + fval |= SP4[(x >>> 16) & 0x3f]; + fval |= SP2[(x >>> 24) & 0x3f]; + r ^= fval; + } + + r = (r << 31) | (r >>> 1); + x = (l ^ r) & 0xaaaaaaaa; + l ^= x; + r ^= x; + l = (l << 31) | (l >>> 1); + x = ((l >>> 8) ^ r) & 0x00ff00ff; + r ^= x; + l ^= (x << 8); + x = ((l >>> 2) ^ r) & 0x33333333; + r ^= x; + l ^= (x << 2); + x = ((r >>> 16) ^ l) & 0x0000ffff; + l ^= x; + r ^= (x << 16); + x = ((r >>> 4) ^ l) & 0x0f0f0f0f; + l ^= x; + r ^= (x << 4); + + // Spread ints to bytes + x = [r, l]; + for (i = 0; i < 8; i++) { + b[i] = (x[i>>>2] >>> (8*(3 - (i%4)))) % 256; + if (b[i] < 0) { b[i] += 256; } // unsigned + } + return b; +} + +// Encrypt 16 bytes of text using passwd as key +function encrypt(t) { + return enc8(t.slice(0,8)).concat(enc8(t.slice(8,16))); +} + +setKeys(passwd); // Setup keys +return {'encrypt': encrypt}; // Public interface + +} // function DES diff --git a/classes/novnc/include/display.js b/classes/novnc/include/display.js new file mode 100644 index 0000000..2cf262d --- /dev/null +++ b/classes/novnc/include/display.js @@ -0,0 +1,671 @@ +/* + * noVNC: HTML5 VNC client + * Copyright (C) 2011 Joel Martin + * Licensed under LGPL-3 (see LICENSE.txt) + * + * See README.md for usage and integration instructions. + */ + +/*jslint browser: true, white: false, bitwise: false */ +/*global Util, Base64, changeCursor */ + +function Display(defaults) { +"use strict"; + +var that = {}, // Public API methods + conf = {}, // Configuration attributes + + // Private Display namespace variables + c_ctx = null, + c_forceCanvas = false, + + // Predefine function variables (jslint) + imageDataGet, rgbxImageData, cmapImageData, + setFillColor, rescale, + + // The full frame buffer (logical canvas) size + fb_width = 0, + fb_height = 0, + // The visible "physical canvas" viewport + viewport = {'x': 0, 'y': 0, 'w' : 0, 'h' : 0 }, + cleanRect = {'x1': 0, 'y1': 0, 'x2': -1, 'y2': -1}, + + c_prevStyle = "", + tile = null, + tile16x16 = null, + tile_x = 0, + tile_y = 0; + + +// Configuration attributes +Util.conf_defaults(conf, that, defaults, [ + ['target', 'wo', 'dom', null, 'Canvas element for rendering'], + ['context', 'ro', 'raw', null, 'Canvas 2D context for rendering (read-only)'], + ['logo', 'rw', 'raw', null, 'Logo to display when cleared: {"width": width, "height": height, "data": data}'], + ['true_color', 'rw', 'bool', true, 'Use true-color pixel data'], + ['colourMap', 'rw', 'arr', [], 'Colour map array (when not true-color)'], + ['scale', 'rw', 'float', 1.0, 'Display area scale factor 0.0 - 1.0'], + ['viewport', 'rw', 'bool', false, 'Use a viewport set with viewportChange()'], + ['width', 'rw', 'int', null, 'Display area width'], + ['height', 'rw', 'int', null, 'Display area height'], + + ['render_mode', 'ro', 'str', '', 'Canvas rendering mode (read-only)'], + + ['prefer_js', 'rw', 'str', null, 'Prefer Javascript over canvas methods'], + ['cursor_uri', 'rw', 'raw', null, 'Can we render cursor using data URI'] + ]); + +// Override some specific getters/setters +that.get_context = function () { return c_ctx; }; + +that.set_scale = function(scale) { rescale(scale); }; + +that.set_width = function (val) { that.resize(val, fb_height); }; +that.get_width = function() { return fb_width; }; + +that.set_height = function (val) { that.resize(fb_width, val); }; +that.get_height = function() { return fb_height; }; + + + +// +// Private functions +// + +// Create the public API interface +function constructor() { + Util.Debug(">> Display.constructor"); + + var c, func, i, curDat, curSave, + has_imageData = false, UE = Util.Engine; + + if (! conf.target) { throw("target must be set"); } + + if (typeof conf.target === 'string') { + throw("target must be a DOM element"); + } + + c = conf.target; + + if (! c.getContext) { throw("no getContext method"); } + + if (! c_ctx) { c_ctx = c.getContext('2d'); } + + Util.Debug("User Agent: " + navigator.userAgent); + if (UE.gecko) { Util.Debug("Browser: gecko " + UE.gecko); } + if (UE.webkit) { Util.Debug("Browser: webkit " + UE.webkit); } + if (UE.trident) { Util.Debug("Browser: trident " + UE.trident); } + if (UE.presto) { Util.Debug("Browser: presto " + UE.presto); } + + that.clear(); + + // Check canvas features + if ('createImageData' in c_ctx) { + conf.render_mode = "canvas rendering"; + } else { + throw("Canvas does not support createImageData"); + } + if (conf.prefer_js === null) { + Util.Info("Prefering javascript operations"); + conf.prefer_js = true; + } + + // Initialize cached tile imageData + tile16x16 = c_ctx.createImageData(16, 16); + + /* + * Determine browser support for setting the cursor via data URI + * scheme + */ + curDat = []; + for (i=0; i < 8 * 8 * 4; i += 1) { + curDat.push(255); + } + try { + curSave = c.style.cursor; + changeCursor(conf.target, curDat, curDat, 2, 2, 8, 8); + if (c.style.cursor) { + if (conf.cursor_uri === null) { + conf.cursor_uri = true; + } + Util.Info("Data URI scheme cursor supported"); + } else { + if (conf.cursor_uri === null) { + conf.cursor_uri = false; + } + Util.Warn("Data URI scheme cursor not supported"); + } + c.style.cursor = curSave; + } catch (exc2) { + Util.Error("Data URI scheme cursor test exception: " + exc2); + conf.cursor_uri = false; + } + + Util.Debug("<< Display.constructor"); + return that ; +} + +rescale = function(factor) { + var c, tp, x, y, + properties = ['transform', 'WebkitTransform', 'MozTransform', null]; + c = conf.target; + tp = properties.shift(); + while (tp) { + if (typeof c.style[tp] !== 'undefined') { + break; + } + tp = properties.shift(); + } + + if (tp === null) { + Util.Debug("No scaling support"); + return; + } + + + if (typeof(factor) === "undefined") { + factor = conf.scale; + } else if (factor > 1.0) { + factor = 1.0; + } else if (factor < 0.1) { + factor = 0.1; + } + + if (conf.scale === factor) { + //Util.Debug("Display already scaled to '" + factor + "'"); + return; + } + + conf.scale = factor; + x = c.width - c.width * factor; + y = c.height - c.height * factor; + c.style[tp] = "scale(" + conf.scale + ") translate(-" + x + "px, -" + y + "px)"; +}; + +setFillColor = function(color) { + var rgb, newStyle; + if (conf.true_color) { + rgb = color; + } else { + rgb = conf.colourMap[color[0]]; + } + newStyle = "rgb(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + ")"; + if (newStyle !== c_prevStyle) { + c_ctx.fillStyle = newStyle; + c_prevStyle = newStyle; + } +}; + + +// +// Public API interface functions +// + +// Shift and/or resize the visible viewport +that.viewportChange = function(deltaX, deltaY, width, height) { + var c = conf.target, v = viewport, cr = cleanRect, + saveImg = null, saveStyle, x1, y1, vx2, vy2, w, h; + + if (!conf.viewport) { + Util.Debug("Setting viewport to full display region"); + deltaX = -v.w; // Clamped later if out of bounds + deltaY = -v.h; // Clamped later if out of bounds + width = fb_width; + height = fb_height; + } + + if (typeof(deltaX) === "undefined") { deltaX = 0; } + if (typeof(deltaY) === "undefined") { deltaY = 0; } + if (typeof(width) === "undefined") { width = v.w; } + if (typeof(height) === "undefined") { height = v.h; } + + // Size change + + if (width > fb_width) { width = fb_width; } + if (height > fb_height) { height = fb_height; } + + if ((v.w !== width) || (v.h !== height)) { + // Change width + if ((width < v.w) && (cr.x2 > v.x + width -1)) { + cr.x2 = v.x + width - 1; + } + v.w = width; + + // Change height + if ((height < v.h) && (cr.y2 > v.y + height -1)) { + cr.y2 = v.y + height - 1; + } + v.h = height; + + + if (v.w > 0 && v.h > 0 && c.width > 0 && c.height > 0) { + saveImg = c_ctx.getImageData(0, 0, + (c.width < v.w) ? c.width : v.w, + (c.height < v.h) ? c.height : v.h); + } + + c.width = v.w; + c.height = v.h; + + if (saveImg) { + c_ctx.putImageData(saveImg, 0, 0); + } + } + + vx2 = v.x + v.w - 1; + vy2 = v.y + v.h - 1; + + + // Position change + + if ((deltaX < 0) && ((v.x + deltaX) < 0)) { + deltaX = - v.x; + } + if ((vx2 + deltaX) >= fb_width) { + deltaX -= ((vx2 + deltaX) - fb_width + 1); + } + + if ((v.y + deltaY) < 0) { + deltaY = - v.y; + } + if ((vy2 + deltaY) >= fb_height) { + deltaY -= ((vy2 + deltaY) - fb_height + 1); + } + + if ((deltaX === 0) && (deltaY === 0)) { + //Util.Debug("skipping viewport change"); + return; + } + Util.Debug("viewportChange deltaX: " + deltaX + ", deltaY: " + deltaY); + + v.x += deltaX; + vx2 += deltaX; + v.y += deltaY; + vy2 += deltaY; + + // Update the clean rectangle + if (v.x > cr.x1) { + cr.x1 = v.x; + } + if (vx2 < cr.x2) { + cr.x2 = vx2; + } + if (v.y > cr.y1) { + cr.y1 = v.y; + } + if (vy2 < cr.y2) { + cr.y2 = vy2; + } + + if (deltaX < 0) { + // Shift viewport left, redraw left section + x1 = 0; + w = - deltaX; + } else { + // Shift viewport right, redraw right section + x1 = v.w - deltaX; + w = deltaX; + } + if (deltaY < 0) { + // Shift viewport up, redraw top section + y1 = 0; + h = - deltaY; + } else { + // Shift viewport down, redraw bottom section + y1 = v.h - deltaY; + h = deltaY; + } + + // Copy the valid part of the viewport to the shifted location + saveStyle = c_ctx.fillStyle; + c_ctx.fillStyle = "rgb(255,255,255)"; + if (deltaX !== 0) { + //that.copyImage(0, 0, -deltaX, 0, v.w, v.h); + //that.fillRect(x1, 0, w, v.h, [255,255,255]); + c_ctx.drawImage(c, 0, 0, v.w, v.h, -deltaX, 0, v.w, v.h); + c_ctx.fillRect(x1, 0, w, v.h); + } + if (deltaY !== 0) { + //that.copyImage(0, 0, 0, -deltaY, v.w, v.h); + //that.fillRect(0, y1, v.w, h, [255,255,255]); + c_ctx.drawImage(c, 0, 0, v.w, v.h, 0, -deltaY, v.w, v.h); + c_ctx.fillRect(0, y1, v.w, h); + } + c_ctx.fillStyle = saveStyle; +}; + + +// Return a map of clean and dirty areas of the viewport and reset the +// tracking of clean and dirty areas. +// +// Returns: {'cleanBox': {'x': x, 'y': y, 'w': w, 'h': h}, +// 'dirtyBoxes': [{'x': x, 'y': y, 'w': w, 'h': h}, ...]} +that.getCleanDirtyReset = function() { + var v = viewport, c = cleanRect, cleanBox, dirtyBoxes = [], + vx2 = v.x + v.w - 1, vy2 = v.y + v.h - 1; + + + // Copy the cleanRect + cleanBox = {'x': c.x1, 'y': c.y1, + 'w': c.x2 - c.x1 + 1, 'h': c.y2 - c.y1 + 1}; + + if ((c.x1 >= c.x2) || (c.y1 >= c.y2)) { + // Whole viewport is dirty + dirtyBoxes.push({'x': v.x, 'y': v.y, 'w': v.w, 'h': v.h}); + } else { + // Redraw dirty regions + if (v.x < c.x1) { + // left side dirty region + dirtyBoxes.push({'x': v.x, 'y': v.y, + 'w': c.x1 - v.x + 1, 'h': v.h}); + } + if (vx2 > c.x2) { + // right side dirty region + dirtyBoxes.push({'x': c.x2 + 1, 'y': v.y, + 'w': vx2 - c.x2, 'h': v.h}); + } + if (v.y < c.y1) { + // top/middle dirty region + dirtyBoxes.push({'x': c.x1, 'y': v.y, + 'w': c.x2 - c.x1 + 1, 'h': c.y1 - v.y}); + } + if (vy2 > c.y2) { + // bottom/middle dirty region + dirtyBoxes.push({'x': c.x1, 'y': c.y2 + 1, + 'w': c.x2 - c.x1 + 1, 'h': vy2 - c.y2}); + } + } + + // Reset the cleanRect to the whole viewport + cleanRect = {'x1': v.x, 'y1': v.y, + 'x2': v.x + v.w - 1, 'y2': v.y + v.h - 1}; + + return {'cleanBox': cleanBox, 'dirtyBoxes': dirtyBoxes}; +}; + +// Translate viewport coordinates to absolute coordinates +that.absX = function(x) { + return x + viewport.x; +} +that.absY = function(y) { + return y + viewport.y; +} + + +that.resize = function(width, height) { + c_prevStyle = ""; + + fb_width = width; + fb_height = height; + + rescale(conf.scale); + that.viewportChange(); +}; + +that.clear = function() { + + if (conf.logo) { + that.resize(conf.logo.width, conf.logo.height); + that.blitStringImage(conf.logo.data, 0, 0); + } else { + that.resize(640, 20); + c_ctx.clearRect(0, 0, viewport.w, viewport.h); + } + + // No benefit over default ("source-over") in Chrome and firefox + //c_ctx.globalCompositeOperation = "copy"; +}; + +that.fillRect = function(x, y, width, height, color) { + setFillColor(color); + c_ctx.fillRect(x - viewport.x, y - viewport.y, width, height); +}; + +that.copyImage = function(old_x, old_y, new_x, new_y, w, h) { + var x1 = old_x - viewport.x, y1 = old_y - viewport.y, + x2 = new_x - viewport.x, y2 = new_y - viewport.y; + c_ctx.drawImage(conf.target, x1, y1, w, h, x2, y2, w, h); +}; + + +// Start updating a tile +that.startTile = function(x, y, width, height, color) { + var data, rgb, red, green, blue, i; + tile_x = x; + tile_y = y; + if ((width === 16) && (height === 16)) { + tile = tile16x16; + } else { + tile = c_ctx.createImageData(width, height); + } + data = tile.data; + if (conf.prefer_js) { + if (conf.true_color) { + rgb = color; + } else { + rgb = conf.colourMap[color[0]]; + } + red = rgb[0]; + green = rgb[1]; + blue = rgb[2]; + for (i = 0; i < (width * height * 4); i+=4) { + data[i ] = red; + data[i + 1] = green; + data[i + 2] = blue; + data[i + 3] = 255; + } + } else { + that.fillRect(x, y, width, height, color); + } +}; + +// Update sub-rectangle of the current tile +that.subTile = function(x, y, w, h, color) { + var data, p, rgb, red, green, blue, width, j, i, xend, yend; + if (conf.prefer_js) { + data = tile.data; + width = tile.width; + if (conf.true_color) { + rgb = color; + } else { + rgb = conf.colourMap[color[0]]; + } + red = rgb[0]; + green = rgb[1]; + blue = rgb[2]; + xend = x + w; + yend = y + h; + for (j = y; j < yend; j += 1) { + for (i = x; i < xend; i += 1) { + p = (i + (j * width) ) * 4; + data[p ] = red; + data[p + 1] = green; + data[p + 2] = blue; + data[p + 3] = 255; + } + } + } else { + that.fillRect(tile_x + x, tile_y + y, w, h, color); + } +}; + +// Draw the current tile to the screen +that.finishTile = function() { + if (conf.prefer_js) { + c_ctx.putImageData(tile, tile_x - viewport.x, tile_y - viewport.y) + } + // else: No-op, if not prefer_js then already done by setSubTile +}; + +rgbxImageData = function(x, y, width, height, arr, offset) { + var img, i, j, data, v = viewport; + /* + if ((x - v.x >= v.w) || (y - v.y >= v.h) || + (x - v.x + width < 0) || (y - v.y + height < 0)) { + // Skipping because outside of viewport + return; + } + */ + img = c_ctx.createImageData(width, height); + data = img.data; + for (i=0, j=offset; i < (width * height * 4); i=i+4, j=j+4) { + data[i ] = arr[j ]; + data[i + 1] = arr[j + 1]; + data[i + 2] = arr[j + 2]; + data[i + 3] = 255; // Set Alpha + } + c_ctx.putImageData(img, x - v.x, y - v.y); +}; + +cmapImageData = function(x, y, width, height, arr, offset) { + var img, i, j, data, rgb, cmap; + img = c_ctx.createImageData(width, height); + data = img.data; + cmap = conf.colourMap; + for (i=0, j=offset; i < (width * height * 4); i+=4, j+=1) { + rgb = cmap[arr[j]]; + data[i ] = rgb[0]; + data[i + 1] = rgb[1]; + data[i + 2] = rgb[2]; + data[i + 3] = 255; // Set Alpha + } + c_ctx.putImageData(img, x - viewport.x, y - viewport.y); +}; + +that.blitImage = function(x, y, width, height, arr, offset) { + if (conf.true_color) { + rgbxImageData(x, y, width, height, arr, offset); + } else { + cmapImageData(x, y, width, height, arr, offset); + } +}; + +that.blitStringImage = function(str, x, y) { + var img = new Image(); + img.onload = function () { + c_ctx.drawImage(img, x - viewport.x, y - viewport.y); + }; + img.src = str; +}; + +that.changeCursor = function(pixels, mask, hotx, hoty, w, h) { + if (conf.cursor_uri === false) { + Util.Warn("changeCursor called but no cursor data URI support"); + return; + } + + if (conf.true_color) { + changeCursor(conf.target, pixels, mask, hotx, hoty, w, h); + } else { + changeCursor(conf.target, pixels, mask, hotx, hoty, w, h, conf.colourMap); + } +}; + +that.defaultCursor = function() { + conf.target.style.cursor = "default"; +}; + +return constructor(); // Return the public API interface + +} // End of Display() + + +/* Set CSS cursor property using data URI encoded cursor file */ +function changeCursor(target, pixels, mask, hotx, hoty, w, h, cmap) { + "use strict"; + var cur = [], rgb, IHDRsz, RGBsz, ANDsz, XORsz, url, idx, alpha, x, y; + //Util.Debug(">> changeCursor, x: " + hotx + ", y: " + hoty + ", w: " + w + ", h: " + h); + + // Push multi-byte little-endian values + cur.push16le = function (num) { + this.push((num ) & 0xFF, + (num >> 8) & 0xFF ); + }; + cur.push32le = function (num) { + this.push((num ) & 0xFF, + (num >> 8) & 0xFF, + (num >> 16) & 0xFF, + (num >> 24) & 0xFF ); + }; + + IHDRsz = 40; + RGBsz = w * h * 4; + XORsz = Math.ceil( (w * h) / 8.0 ); + ANDsz = Math.ceil( (w * h) / 8.0 ); + + // Main header + cur.push16le(0); // 0: Reserved + cur.push16le(2); // 2: .CUR type + cur.push16le(1); // 4: Number of images, 1 for non-animated ico + + // Cursor #1 header (ICONDIRENTRY) + cur.push(w); // 6: width + cur.push(h); // 7: height + cur.push(0); // 8: colors, 0 -> true-color + cur.push(0); // 9: reserved + cur.push16le(hotx); // 10: hotspot x coordinate + cur.push16le(hoty); // 12: hotspot y coordinate + cur.push32le(IHDRsz + RGBsz + XORsz + ANDsz); + // 14: cursor data byte size + cur.push32le(22); // 18: offset of cursor data in the file + + + // Cursor #1 InfoHeader (ICONIMAGE/BITMAPINFO) + cur.push32le(IHDRsz); // 22: Infoheader size + cur.push32le(w); // 26: Cursor width + cur.push32le(h*2); // 30: XOR+AND height + cur.push16le(1); // 34: number of planes + cur.push16le(32); // 36: bits per pixel + cur.push32le(0); // 38: Type of compression + + cur.push32le(XORsz + ANDsz); // 43: Size of Image + // Gimp leaves this as 0 + + cur.push32le(0); // 46: reserved + cur.push32le(0); // 50: reserved + cur.push32le(0); // 54: reserved + cur.push32le(0); // 58: reserved + + // 62: color data (RGBQUAD icColors[]) + for (y = h-1; y >= 0; y -= 1) { + for (x = 0; x < w; x += 1) { + idx = y * Math.ceil(w / 8) + Math.floor(x/8); + alpha = (mask[idx] << (x % 8)) & 0x80 ? 255 : 0; + + if (cmap) { + idx = (w * y) + x; + rgb = cmap[pixels[idx]]; + cur.push(rgb[2]); // blue + cur.push(rgb[1]); // green + cur.push(rgb[0]); // red + cur.push(alpha); // alpha + } else { + idx = ((w * y) + x) * 4; + cur.push(pixels[idx + 2]); // blue + cur.push(pixels[idx + 1]); // green + cur.push(pixels[idx ]); // red + cur.push(alpha); // alpha + } + } + } + + // XOR/bitmask data (BYTE icXOR[]) + // (ignored, just needs to be right size) + for (y = 0; y < h; y += 1) { + for (x = 0; x < Math.ceil(w / 8); x += 1) { + cur.push(0x00); + } + } + + // AND/bitmask data (BYTE icAND[]) + // (ignored, just needs to be right size) + for (y = 0; y < h; y += 1) { + for (x = 0; x < Math.ceil(w / 8); x += 1) { + cur.push(0x00); + } + } + + url = "data:image/x-icon;base64," + Base64.encode(cur); + target.style.cursor = "url(" + url + ") " + hotx + " " + hoty + ", default"; + //Util.Debug("<< changeCursor, cur.length: " + cur.length); +} diff --git a/classes/novnc/include/input.js b/classes/novnc/include/input.js new file mode 100644 index 0000000..3124d08 --- /dev/null +++ b/classes/novnc/include/input.js @@ -0,0 +1,1884 @@ +/* + * noVNC: HTML5 VNC client + * Copyright (C) 2011 Joel Martin + * Licensed under LGPL-2 or any later version (see LICENSE.txt) + */ + +/*jslint browser: true, white: false, bitwise: false */ +/*global window, Util */ + + +// +// Keyboard event handler +// + +function Keyboard(defaults) { +"use strict"; + +var that = {}, // Public API methods + conf = {}, // Configuration attributes + + keyDownList = []; // List of depressed keys + // (even if they are happy) + +// Configuration attributes +Util.conf_defaults(conf, that, defaults, [ + ['target', 'wo', 'dom', document, 'DOM element that captures keyboard input'], + ['focused', 'rw', 'bool', true, 'Capture and send key events'], + + ['onKeyPress', 'rw', 'func', null, 'Handler for key press/release'] + ]); + + +// +// Private functions +// + +// From the event keyCode return the keysym value for keys that need +// to be suppressed otherwise they may trigger unintended browser +// actions +function getKeysymSpecial(evt) { + var keysym = null; + + switch ( evt.keyCode ) { + // These generate a keyDown and keyPress in Firefox and Opera + case 8 : keysym = 0xFF08; break; // BACKSPACE + case 13 : keysym = 0xFF0D; break; // ENTER + + // This generates a keyDown and keyPress in Opera + case 9 : keysym = 0xFF09; break; // TAB + default : break; + } + + if (evt.type === 'keydown') { + switch ( evt.keyCode ) { + case 27 : keysym = 0xFF1B; break; // ESCAPE + case 46 : keysym = 0xFFFF; break; // DELETE + + case 36 : keysym = 0xFF50; break; // HOME + case 35 : keysym = 0xFF57; break; // END + case 33 : keysym = 0xFF55; break; // PAGE_UP + case 34 : keysym = 0xFF56; break; // PAGE_DOWN + case 45 : keysym = 0xFF63; break; // INSERT + // '-' during keyPress + case 37 : keysym = 0xFF51; break; // LEFT + case 38 : keysym = 0xFF52; break; // UP + case 39 : keysym = 0xFF53; break; // RIGHT + case 40 : keysym = 0xFF54; break; // DOWN + case 16 : keysym = 0xFFE1; break; // SHIFT + case 17 : keysym = 0xFFE3; break; // CONTROL + //case 18 : keysym = 0xFFE7; break; // Left Meta (Mac Option) + case 18 : keysym = 0xFFE9; break; // Left ALT (Mac Command) + + case 112 : keysym = 0xFFBE; break; // F1 + case 113 : keysym = 0xFFBF; break; // F2 + case 114 : keysym = 0xFFC0; break; // F3 + case 115 : keysym = 0xFFC1; break; // F4 + case 116 : keysym = 0xFFC2; break; // F5 + case 117 : keysym = 0xFFC3; break; // F6 + case 118 : keysym = 0xFFC4; break; // F7 + case 119 : keysym = 0xFFC5; break; // F8 + case 120 : keysym = 0xFFC6; break; // F9 + case 121 : keysym = 0xFFC7; break; // F10 + case 122 : keysym = 0xFFC8; break; // F11 + case 123 : keysym = 0xFFC9; break; // F12 + + default : break; + } + } + + if ((!keysym) && (evt.ctrlKey || evt.altKey)) { + if ((typeof(evt.which) !== "undefined") && (evt.which > 0)) { + keysym = evt.which; + } else { + // IE9 always + // Firefox and Opera when ctrl/alt + special + Util.Warn("which not set, using keyCode"); + keysym = evt.keyCode; + } + + /* Remap symbols */ + switch (keysym) { + case 186 : keysym = 59; break; // ; (IE) + case 187 : keysym = 61; break; // = (IE) + case 188 : keysym = 44; break; // , (Mozilla, IE) + case 109 : // - (Mozilla, Opera) + if (Util.Engine.gecko || Util.Engine.presto) { + keysym = 45; } + break; + case 189 : keysym = 45; break; // - (IE) + case 190 : keysym = 46; break; // . (Mozilla, IE) + case 191 : keysym = 47; break; // / (Mozilla, IE) + case 192 : keysym = 96; break; // ` (Mozilla, IE) + case 219 : keysym = 91; break; // [ (Mozilla, IE) + case 220 : keysym = 92; break; // \ (Mozilla, IE) + case 221 : keysym = 93; break; // ] (Mozilla, IE) + case 222 : keysym = 39; break; // ' (Mozilla, IE) + } + + /* Remap shifted and unshifted keys */ + if (!!evt.shiftKey) { + switch (keysym) { + case 48 : keysym = 41 ; break; // ) (shifted 0) + case 49 : keysym = 33 ; break; // ! (shifted 1) + case 50 : keysym = 64 ; break; // @ (shifted 2) + case 51 : keysym = 35 ; break; // # (shifted 3) + case 52 : keysym = 36 ; break; // $ (shifted 4) + case 53 : keysym = 37 ; break; // % (shifted 5) + case 54 : keysym = 94 ; break; // ^ (shifted 6) + case 55 : keysym = 38 ; break; // & (shifted 7) + case 56 : keysym = 42 ; break; // * (shifted 8) + case 57 : keysym = 40 ; break; // ( (shifted 9) + + case 59 : keysym = 58 ; break; // : (shifted `) + case 61 : keysym = 43 ; break; // + (shifted ;) + case 44 : keysym = 60 ; break; // < (shifted ,) + case 45 : keysym = 95 ; break; // _ (shifted -) + case 46 : keysym = 62 ; break; // > (shifted .) + case 47 : keysym = 63 ; break; // ? (shifted /) + case 96 : keysym = 126; break; // ~ (shifted `) + case 91 : keysym = 123; break; // { (shifted [) + case 92 : keysym = 124; break; // | (shifted \) + case 93 : keysym = 125; break; // } (shifted ]) + case 39 : keysym = 34 ; break; // " (shifted ') + } + } else if ((keysym >= 65) && (keysym <=90)) { + /* Remap unshifted A-Z */ + keysym += 32; + } else if (evt.keyLocation === 3) { + // numpad keys + switch (keysym) { + case 96 : keysym = 48; break; // 0 + case 97 : keysym = 49; break; // 1 + case 98 : keysym = 50; break; // 2 + case 99 : keysym = 51; break; // 3 + case 100: keysym = 52; break; // 4 + case 101: keysym = 53; break; // 5 + case 102: keysym = 54; break; // 6 + case 103: keysym = 55; break; // 7 + case 104: keysym = 56; break; // 8 + case 105: keysym = 57; break; // 9 + case 109: keysym = 45; break; // - + case 110: keysym = 46; break; // . + case 111: keysym = 47; break; // / + } + } + } + + return keysym; +} + +/* Translate DOM keyPress event to keysym value */ +function getKeysym(evt) { + var keysym, msg; + + if (typeof(evt.which) !== "undefined") { + // WebKit, Firefox, Opera + keysym = evt.which; + } else { + // IE9 + Util.Warn("which not set, using keyCode"); + keysym = evt.keyCode; + } + + if ((keysym > 255) && (keysym < 0xFF00)) { + msg = "Mapping character code " + keysym; + // Map Unicode outside Latin 1 to X11 keysyms + keysym = unicodeTable[keysym]; + if (typeof(keysym) === 'undefined') { + keysym = 0; + } + Util.Debug(msg + " to " + keysym); + } + + return keysym; +} + +function show_keyDownList(kind) { + var c; + var msg = "keyDownList (" + kind + "):\n"; + for (c = 0; c < keyDownList.length; c++) { + msg = msg + " " + c + " - keyCode: " + keyDownList[c].keyCode + + " - which: " + keyDownList[c].which + "\n"; + } + Util.Debug(msg); +} + +function copyKeyEvent(evt) { + var members = ['type', 'keyCode', 'charCode', 'which', + 'altKey', 'ctrlKey', 'shiftKey', + 'keyLocation', 'keyIdentifier'], i, obj = {}; + for (i = 0; i < members.length; i++) { + if (typeof(evt[members[i]]) !== "undefined") { + obj[members[i]] = evt[members[i]]; + } + } + return obj; +} + +function pushKeyEvent(fevt) { + keyDownList.push(fevt); +} + +function getKeyEvent(keyCode, pop) { + var i, fevt = null; + for (i = keyDownList.length-1; i >= 0; i--) { + if (keyDownList[i].keyCode === keyCode) { + if ((typeof(pop) !== "undefined") && (pop)) { + fevt = keyDownList.splice(i, 1)[0]; + } else { + fevt = keyDownList[i]; + } + break; + } + } + return fevt; +} + +function ignoreKeyEvent(evt) { + // Blarg. Some keys have a different keyCode on keyDown vs keyUp + if (evt.keyCode === 229) { + // French AZERTY keyboard dead key. + // Lame thing is that the respective keyUp is 219 so we can't + // properly ignore the keyUp event + return true; + } + return false; +} + + +// +// Key Event Handling: +// +// There are several challenges when dealing with key events: +// - The meaning and use of keyCode, charCode and which depends on +// both the browser and the event type (keyDown/Up vs keyPress). +// - We cannot automatically determine the keyboard layout +// - The keyDown and keyUp events have a keyCode value that has not +// been translated by modifier keys. +// - The keyPress event has a translated (for layout and modifiers) +// character code but the attribute containing it differs. keyCode +// contains the translated value in WebKit (Chrome/Safari), Opera +// 11 and IE9. charCode contains the value in WebKit and Firefox. +// The which attribute contains the value on WebKit, Firefox and +// Opera 11. +// - The keyDown/Up keyCode value indicates (sort of) the physical +// key was pressed but only for standard US layout. On a US +// keyboard, the '-' and '_' characters are on the same key and +// generate a keyCode value of 189. But on an AZERTY keyboard even +// though they are different physical keys they both still +// generate a keyCode of 189! +// - To prevent a key event from propagating to the browser and +// causing unwanted default actions (such as closing a tab, +// opening a menu, shifting focus, etc) we must suppress this +// event in both keyDown and keyPress because not all key strokes +// generate on a keyPress event. Also, in WebKit and IE9 +// suppressing the keyDown prevents a keyPress but other browsers +// still generated a keyPress even if keyDown is suppressed. +// +// For safe key events, we wait until the keyPress event before +// reporting a key down event. For unsafe key events, we report a key +// down event when the keyDown event fires and we suppress any further +// actions (including keyPress). +// +// In order to report a key up event that matches what we reported +// for the key down event, we keep a list of keys that are currently +// down. When the keyDown event happens, we add the key event to the +// list. If it is a safe key event, then we update the which attribute +// in the most recent item on the list when we received a keyPress +// event (keyPress should immediately follow keyDown). When we +// received a keyUp event we search for the event on the list with +// a matching keyCode and we report the character code using the value +// in the 'which' attribute that was stored with that key. +// + +function onKeyDown(e) { + if (! conf.focused) { + return true; + } + var fevt = null, evt = (e ? e : window.event), + keysym = null, suppress = false; + //Util.Debug("onKeyDown kC:" + evt.keyCode + " cC:" + evt.charCode + " w:" + evt.which); + + fevt = copyKeyEvent(evt); + + keysym = getKeysymSpecial(evt); + // Save keysym decoding for use in keyUp + fevt.keysym = keysym; + if (keysym) { + // If it is a key or key combination that might trigger + // browser behaviors or it has no corresponding keyPress + // event, then send it immediately + if (conf.onKeyPress && !ignoreKeyEvent(evt)) { + Util.Debug("onKeyPress down, keysym: " + keysym + + " (onKeyDown key: " + evt.keyCode + + ", which: " + evt.which + ")"); + conf.onKeyPress(keysym, 1, evt); + } + suppress = true; + } + + if (! ignoreKeyEvent(evt)) { + // Add it to the list of depressed keys + pushKeyEvent(fevt); + //show_keyDownList('down'); + } + + if (suppress) { + // Suppress bubbling/default actions + Util.stopEvent(e); + return false; + } else { + // Allow the event to bubble and become a keyPress event which + // will have the character code translated + return true; + } +} + +function onKeyPress(e) { + if (! conf.focused) { + return true; + } + var evt = (e ? e : window.event), + kdlen = keyDownList.length, keysym = null; + //Util.Debug("onKeyPress kC:" + evt.keyCode + " cC:" + evt.charCode + " w:" + evt.which); + + if (((evt.which !== "undefined") && (evt.which === 0)) || + (getKeysymSpecial(evt))) { + // Firefox and Opera generate a keyPress event even if keyDown + // is suppressed. But the keys we want to suppress will have + // either: + // - the which attribute set to 0 + // - getKeysymSpecial() will identify it + Util.Debug("Ignoring special key in keyPress"); + Util.stopEvent(e); + return false; + } + + keysym = getKeysym(evt); + + // Modify the the which attribute in the depressed keys list so + // that the keyUp event will be able to have the character code + // translation available. + if (kdlen > 0) { + keyDownList[kdlen-1].keysym = keysym; + } else { + Util.Warn("keyDownList empty when keyPress triggered"); + } + + //show_keyDownList('press'); + + // Send the translated keysym + if (conf.onKeyPress && (keysym > 0)) { + Util.Debug("onKeyPress down, keysym: " + keysym + + " (onKeyPress key: " + evt.keyCode + + ", which: " + evt.which + ")"); + conf.onKeyPress(keysym, 1, evt); + } + + // Stop keypress events just in case + Util.stopEvent(e); + return false; +} + +function onKeyUp(e) { + if (! conf.focused) { + return true; + } + var fevt = null, evt = (e ? e : window.event), keysym; + //Util.Debug("onKeyUp kC:" + evt.keyCode + " cC:" + evt.charCode + " w:" + evt.which); + + fevt = getKeyEvent(evt.keyCode, true); + + if (fevt) { + keysym = fevt.keysym; + } else { + Util.Warn("Key event (keyCode = " + evt.keyCode + + ") not found on keyDownList"); + keysym = 0; + } + + //show_keyDownList('up'); + + if (conf.onKeyPress && (keysym > 0)) { + //Util.Debug("keyPress up, keysym: " + keysym + + // " (key: " + evt.keyCode + ", which: " + evt.which + ")"); + Util.Debug("onKeyPress up, keysym: " + keysym + + " (onKeyPress key: " + evt.keyCode + + ", which: " + evt.which + ")"); + conf.onKeyPress(keysym, 0, evt); + } + Util.stopEvent(e); + return false; +} + +// +// Public API interface functions +// + +that.grab = function() { + //Util.Debug(">> Keyboard.grab"); + var c = conf.target; + + Util.addEvent(c, 'keydown', onKeyDown); + Util.addEvent(c, 'keyup', onKeyUp); + Util.addEvent(c, 'keypress', onKeyPress); + + //Util.Debug("<< Keyboard.grab"); +}; + +that.ungrab = function() { + //Util.Debug(">> Keyboard.ungrab"); + var c = conf.target; + + Util.removeEvent(c, 'keydown', onKeyDown); + Util.removeEvent(c, 'keyup', onKeyUp); + Util.removeEvent(c, 'keypress', onKeyPress); + + //Util.Debug(">> Keyboard.ungrab"); +}; + +return that; // Return the public API interface + +} // End of Keyboard() + + +// +// Mouse event handler +// + +function Mouse(defaults) { +"use strict"; + +var that = {}, // Public API methods + conf = {}; // Configuration attributes + +// Configuration attributes +Util.conf_defaults(conf, that, defaults, [ + ['target', 'ro', 'dom', document, 'DOM element that captures mouse input'], + ['focused', 'rw', 'bool', true, 'Capture and send mouse clicks/movement'], + ['scale', 'rw', 'float', 1.0, 'Viewport scale factor 0.0 - 1.0'], + + ['onMouseButton', 'rw', 'func', null, 'Handler for mouse button click/release'], + ['onMouseMove', 'rw', 'func', null, 'Handler for mouse movement'], + ['touchButton', 'rw', 'int', 1, 'Button mask (1, 2, 4) for touch devices (0 means ignore clicks)'] + ]); + + +// +// Private functions +// + +function onMouseButton(e, down) { + var evt, pos, bmask; + if (! conf.focused) { + return true; + } + evt = (e ? e : window.event); + pos = Util.getEventPosition(e, conf.target, conf.scale); + if (e.touches || e.changedTouches) { + // Touch device + bmask = conf.touchButton; + // If bmask is set + } else if (evt.which) { + /* everything except IE */ + bmask = 1 << evt.button; + } else { + /* IE including 9 */ + bmask = (evt.button & 0x1) + // Left + (evt.button & 0x2) * 2 + // Right + (evt.button & 0x4) / 2; // Middle + } + //Util.Debug("mouse " + pos.x + "," + pos.y + " down: " + down + + // " bmask: " + bmask + "(evt.button: " + evt.button + ")"); + if (bmask > 0 && conf.onMouseButton) { + Util.Debug("onMouseButton " + (down ? "down" : "up") + + ", x: " + pos.x + ", y: " + pos.y + ", bmask: " + bmask); + conf.onMouseButton(pos.x, pos.y, down, bmask); + } + Util.stopEvent(e); + return false; +} + +function onMouseDown(e) { + onMouseButton(e, 1); +} + +function onMouseUp(e) { + onMouseButton(e, 0); +} + +function onMouseWheel(e) { + var evt, pos, bmask, wheelData; + if (! conf.focused) { + return true; + } + evt = (e ? e : window.event); + pos = Util.getEventPosition(e, conf.target, conf.scale); + wheelData = evt.detail ? evt.detail * -1 : evt.wheelDelta / 40; + if (wheelData > 0) { + bmask = 1 << 3; + } else { + bmask = 1 << 4; + } + //Util.Debug('mouse scroll by ' + wheelData + ':' + pos.x + "," + pos.y); + if (conf.onMouseButton) { + conf.onMouseButton(pos.x, pos.y, 1, bmask); + conf.onMouseButton(pos.x, pos.y, 0, bmask); + } + Util.stopEvent(e); + return false; +} + +function onMouseMove(e) { + var evt, pos; + if (! conf.focused) { + return true; + } + evt = (e ? e : window.event); + pos = Util.getEventPosition(e, conf.target, conf.scale); + //Util.Debug('mouse ' + evt.which + '/' + evt.button + ' up:' + pos.x + "," + pos.y); + if (conf.onMouseMove) { + conf.onMouseMove(pos.x, pos.y); + } + Util.stopEvent(e); + return false; +} + +function onMouseDisable(e) { + var evt, pos; + if (! conf.focused) { + return true; + } + evt = (e ? e : window.event); + pos = Util.getEventPosition(e, conf.target, conf.scale); + /* Stop propagation if inside canvas area */ + if ((pos.x >= 0) && (pos.y >= 0) && + (pos.x < conf.target.offsetWidth) && + (pos.y < conf.target.offsetHeight)) { + //Util.Debug("mouse event disabled"); + Util.stopEvent(e); + return false; + } + //Util.Debug("mouse event not disabled"); + return true; +} + +// +// Public API interface functions +// + +that.grab = function() { + //Util.Debug(">> Mouse.grab"); + var c = conf.target; + + if ('ontouchstart' in document.documentElement) { + Util.addEvent(c, 'touchstart', onMouseDown); + Util.addEvent(c, 'touchend', onMouseUp); + Util.addEvent(c, 'touchmove', onMouseMove); + } else { + Util.addEvent(c, 'mousedown', onMouseDown); + Util.addEvent(c, 'mouseup', onMouseUp); + Util.addEvent(c, 'mousemove', onMouseMove); + Util.addEvent(c, (Util.Engine.gecko) ? 'DOMMouseScroll' : 'mousewheel', + onMouseWheel); + } + + /* Work around right and middle click browser behaviors */ + Util.addEvent(document, 'click', onMouseDisable); + Util.addEvent(document.body, 'contextmenu', onMouseDisable); + + //Util.Debug("<< Mouse.grab"); +}; + +that.ungrab = function() { + //Util.Debug(">> Mouse.ungrab"); + var c = conf.target; + + if ('ontouchstart' in document.documentElement) { + Util.removeEvent(c, 'touchstart', onMouseDown); + Util.removeEvent(c, 'touchend', onMouseUp); + Util.removeEvent(c, 'touchmove', onMouseMove); + } else { + Util.removeEvent(c, 'mousedown', onMouseDown); + Util.removeEvent(c, 'mouseup', onMouseUp); + Util.removeEvent(c, 'mousemove', onMouseMove); + Util.removeEvent(c, (Util.Engine.gecko) ? 'DOMMouseScroll' : 'mousewheel', + onMouseWheel); + } + + /* Work around right and middle click browser behaviors */ + Util.removeEvent(document, 'click', onMouseDisable); + Util.removeEvent(document.body, 'contextmenu', onMouseDisable); + + //Util.Debug(">> Mouse.ungrab"); +}; + +return that; // Return the public API interface + +} // End of Mouse() + + +/* + * Browser keypress to X11 keysym for Unicode characters > U+00FF + */ +unicodeTable = { + 0x0104 : 0x01a1, + 0x02D8 : 0x01a2, + 0x0141 : 0x01a3, + 0x013D : 0x01a5, + 0x015A : 0x01a6, + 0x0160 : 0x01a9, + 0x015E : 0x01aa, + 0x0164 : 0x01ab, + 0x0179 : 0x01ac, + 0x017D : 0x01ae, + 0x017B : 0x01af, + 0x0105 : 0x01b1, + 0x02DB : 0x01b2, + 0x0142 : 0x01b3, + 0x013E : 0x01b5, + 0x015B : 0x01b6, + 0x02C7 : 0x01b7, + 0x0161 : 0x01b9, + 0x015F : 0x01ba, + 0x0165 : 0x01bb, + 0x017A : 0x01bc, + 0x02DD : 0x01bd, + 0x017E : 0x01be, + 0x017C : 0x01bf, + 0x0154 : 0x01c0, + 0x0102 : 0x01c3, + 0x0139 : 0x01c5, + 0x0106 : 0x01c6, + 0x010C : 0x01c8, + 0x0118 : 0x01ca, + 0x011A : 0x01cc, + 0x010E : 0x01cf, + 0x0110 : 0x01d0, + 0x0143 : 0x01d1, + 0x0147 : 0x01d2, + 0x0150 : 0x01d5, + 0x0158 : 0x01d8, + 0x016E : 0x01d9, + 0x0170 : 0x01db, + 0x0162 : 0x01de, + 0x0155 : 0x01e0, + 0x0103 : 0x01e3, + 0x013A : 0x01e5, + 0x0107 : 0x01e6, + 0x010D : 0x01e8, + 0x0119 : 0x01ea, + 0x011B : 0x01ec, + 0x010F : 0x01ef, + 0x0111 : 0x01f0, + 0x0144 : 0x01f1, + 0x0148 : 0x01f2, + 0x0151 : 0x01f5, + 0x0171 : 0x01fb, + 0x0159 : 0x01f8, + 0x016F : 0x01f9, + 0x0163 : 0x01fe, + 0x02D9 : 0x01ff, + 0x0126 : 0x02a1, + 0x0124 : 0x02a6, + 0x0130 : 0x02a9, + 0x011E : 0x02ab, + 0x0134 : 0x02ac, + 0x0127 : 0x02b1, + 0x0125 : 0x02b6, + 0x0131 : 0x02b9, + 0x011F : 0x02bb, + 0x0135 : 0x02bc, + 0x010A : 0x02c5, + 0x0108 : 0x02c6, + 0x0120 : 0x02d5, + 0x011C : 0x02d8, + 0x016C : 0x02dd, + 0x015C : 0x02de, + 0x010B : 0x02e5, + 0x0109 : 0x02e6, + 0x0121 : 0x02f5, + 0x011D : 0x02f8, + 0x016D : 0x02fd, + 0x015D : 0x02fe, + 0x0138 : 0x03a2, + 0x0156 : 0x03a3, + 0x0128 : 0x03a5, + 0x013B : 0x03a6, + 0x0112 : 0x03aa, + 0x0122 : 0x03ab, + 0x0166 : 0x03ac, + 0x0157 : 0x03b3, + 0x0129 : 0x03b5, + 0x013C : 0x03b6, + 0x0113 : 0x03ba, + 0x0123 : 0x03bb, + 0x0167 : 0x03bc, + 0x014A : 0x03bd, + 0x014B : 0x03bf, + 0x0100 : 0x03c0, + 0x012E : 0x03c7, + 0x0116 : 0x03cc, + 0x012A : 0x03cf, + 0x0145 : 0x03d1, + 0x014C : 0x03d2, + 0x0136 : 0x03d3, + 0x0172 : 0x03d9, + 0x0168 : 0x03dd, + 0x016A : 0x03de, + 0x0101 : 0x03e0, + 0x012F : 0x03e7, + 0x0117 : 0x03ec, + 0x012B : 0x03ef, + 0x0146 : 0x03f1, + 0x014D : 0x03f2, + 0x0137 : 0x03f3, + 0x0173 : 0x03f9, + 0x0169 : 0x03fd, + 0x016B : 0x03fe, + 0x1E02 : 0x1001e02, + 0x1E03 : 0x1001e03, + 0x1E0A : 0x1001e0a, + 0x1E80 : 0x1001e80, + 0x1E82 : 0x1001e82, + 0x1E0B : 0x1001e0b, + 0x1EF2 : 0x1001ef2, + 0x1E1E : 0x1001e1e, + 0x1E1F : 0x1001e1f, + 0x1E40 : 0x1001e40, + 0x1E41 : 0x1001e41, + 0x1E56 : 0x1001e56, + 0x1E81 : 0x1001e81, + 0x1E57 : 0x1001e57, + 0x1E83 : 0x1001e83, + 0x1E60 : 0x1001e60, + 0x1EF3 : 0x1001ef3, + 0x1E84 : 0x1001e84, + 0x1E85 : 0x1001e85, + 0x1E61 : 0x1001e61, + 0x0174 : 0x1000174, + 0x1E6A : 0x1001e6a, + 0x0176 : 0x1000176, + 0x0175 : 0x1000175, + 0x1E6B : 0x1001e6b, + 0x0177 : 0x1000177, + 0x0152 : 0x13bc, + 0x0153 : 0x13bd, + 0x0178 : 0x13be, + 0x203E : 0x047e, + 0x3002 : 0x04a1, + 0x300C : 0x04a2, + 0x300D : 0x04a3, + 0x3001 : 0x04a4, + 0x30FB : 0x04a5, + 0x30F2 : 0x04a6, + 0x30A1 : 0x04a7, + 0x30A3 : 0x04a8, + 0x30A5 : 0x04a9, + 0x30A7 : 0x04aa, + 0x30A9 : 0x04ab, + 0x30E3 : 0x04ac, + 0x30E5 : 0x04ad, + 0x30E7 : 0x04ae, + 0x30C3 : 0x04af, + 0x30FC : 0x04b0, + 0x30A2 : 0x04b1, + 0x30A4 : 0x04b2, + 0x30A6 : 0x04b3, + 0x30A8 : 0x04b4, + 0x30AA : 0x04b5, + 0x30AB : 0x04b6, + 0x30AD : 0x04b7, + 0x30AF : 0x04b8, + 0x30B1 : 0x04b9, + 0x30B3 : 0x04ba, + 0x30B5 : 0x04bb, + 0x30B7 : 0x04bc, + 0x30B9 : 0x04bd, + 0x30BB : 0x04be, + 0x30BD : 0x04bf, + 0x30BF : 0x04c0, + 0x30C1 : 0x04c1, + 0x30C4 : 0x04c2, + 0x30C6 : 0x04c3, + 0x30C8 : 0x04c4, + 0x30CA : 0x04c5, + 0x30CB : 0x04c6, + 0x30CC : 0x04c7, + 0x30CD : 0x04c8, + 0x30CE : 0x04c9, + 0x30CF : 0x04ca, + 0x30D2 : 0x04cb, + 0x30D5 : 0x04cc, + 0x30D8 : 0x04cd, + 0x30DB : 0x04ce, + 0x30DE : 0x04cf, + 0x30DF : 0x04d0, + 0x30E0 : 0x04d1, + 0x30E1 : 0x04d2, + 0x30E2 : 0x04d3, + 0x30E4 : 0x04d4, + 0x30E6 : 0x04d5, + 0x30E8 : 0x04d6, + 0x30E9 : 0x04d7, + 0x30EA : 0x04d8, + 0x30EB : 0x04d9, + 0x30EC : 0x04da, + 0x30ED : 0x04db, + 0x30EF : 0x04dc, + 0x30F3 : 0x04dd, + 0x309B : 0x04de, + 0x309C : 0x04df, + 0x06F0 : 0x10006f0, + 0x06F1 : 0x10006f1, + 0x06F2 : 0x10006f2, + 0x06F3 : 0x10006f3, + 0x06F4 : 0x10006f4, + 0x06F5 : 0x10006f5, + 0x06F6 : 0x10006f6, + 0x06F7 : 0x10006f7, + 0x06F8 : 0x10006f8, + 0x06F9 : 0x10006f9, + 0x066A : 0x100066a, + 0x0670 : 0x1000670, + 0x0679 : 0x1000679, + 0x067E : 0x100067e, + 0x0686 : 0x1000686, + 0x0688 : 0x1000688, + 0x0691 : 0x1000691, + 0x060C : 0x05ac, + 0x06D4 : 0x10006d4, + 0x0660 : 0x1000660, + 0x0661 : 0x1000661, + 0x0662 : 0x1000662, + 0x0663 : 0x1000663, + 0x0664 : 0x1000664, + 0x0665 : 0x1000665, + 0x0666 : 0x1000666, + 0x0667 : 0x1000667, + 0x0668 : 0x1000668, + 0x0669 : 0x1000669, + 0x061B : 0x05bb, + 0x061F : 0x05bf, + 0x0621 : 0x05c1, + 0x0622 : 0x05c2, + 0x0623 : 0x05c3, + 0x0624 : 0x05c4, + 0x0625 : 0x05c5, + 0x0626 : 0x05c6, + 0x0627 : 0x05c7, + 0x0628 : 0x05c8, + 0x0629 : 0x05c9, + 0x062A : 0x05ca, + 0x062B : 0x05cb, + 0x062C : 0x05cc, + 0x062D : 0x05cd, + 0x062E : 0x05ce, + 0x062F : 0x05cf, + 0x0630 : 0x05d0, + 0x0631 : 0x05d1, + 0x0632 : 0x05d2, + 0x0633 : 0x05d3, + 0x0634 : 0x05d4, + 0x0635 : 0x05d5, + 0x0636 : 0x05d6, + 0x0637 : 0x05d7, + 0x0638 : 0x05d8, + 0x0639 : 0x05d9, + 0x063A : 0x05da, + 0x0640 : 0x05e0, + 0x0641 : 0x05e1, + 0x0642 : 0x05e2, + 0x0643 : 0x05e3, + 0x0644 : 0x05e4, + 0x0645 : 0x05e5, + 0x0646 : 0x05e6, + 0x0647 : 0x05e7, + 0x0648 : 0x05e8, + 0x0649 : 0x05e9, + 0x064A : 0x05ea, + 0x064B : 0x05eb, + 0x064C : 0x05ec, + 0x064D : 0x05ed, + 0x064E : 0x05ee, + 0x064F : 0x05ef, + 0x0650 : 0x05f0, + 0x0651 : 0x05f1, + 0x0652 : 0x05f2, + 0x0653 : 0x1000653, + 0x0654 : 0x1000654, + 0x0655 : 0x1000655, + 0x0698 : 0x1000698, + 0x06A4 : 0x10006a4, + 0x06A9 : 0x10006a9, + 0x06AF : 0x10006af, + 0x06BA : 0x10006ba, + 0x06BE : 0x10006be, + 0x06CC : 0x10006cc, + 0x06D2 : 0x10006d2, + 0x06C1 : 0x10006c1, + 0x0492 : 0x1000492, + 0x0493 : 0x1000493, + 0x0496 : 0x1000496, + 0x0497 : 0x1000497, + 0x049A : 0x100049a, + 0x049B : 0x100049b, + 0x049C : 0x100049c, + 0x049D : 0x100049d, + 0x04A2 : 0x10004a2, + 0x04A3 : 0x10004a3, + 0x04AE : 0x10004ae, + 0x04AF : 0x10004af, + 0x04B0 : 0x10004b0, + 0x04B1 : 0x10004b1, + 0x04B2 : 0x10004b2, + 0x04B3 : 0x10004b3, + 0x04B6 : 0x10004b6, + 0x04B7 : 0x10004b7, + 0x04B8 : 0x10004b8, + 0x04B9 : 0x10004b9, + 0x04BA : 0x10004ba, + 0x04BB : 0x10004bb, + 0x04D8 : 0x10004d8, + 0x04D9 : 0x10004d9, + 0x04E2 : 0x10004e2, + 0x04E3 : 0x10004e3, + 0x04E8 : 0x10004e8, + 0x04E9 : 0x10004e9, + 0x04EE : 0x10004ee, + 0x04EF : 0x10004ef, + 0x0452 : 0x06a1, + 0x0453 : 0x06a2, + 0x0451 : 0x06a3, + 0x0454 : 0x06a4, + 0x0455 : 0x06a5, + 0x0456 : 0x06a6, + 0x0457 : 0x06a7, + 0x0458 : 0x06a8, + 0x0459 : 0x06a9, + 0x045A : 0x06aa, + 0x045B : 0x06ab, + 0x045C : 0x06ac, + 0x0491 : 0x06ad, + 0x045E : 0x06ae, + 0x045F : 0x06af, + 0x2116 : 0x06b0, + 0x0402 : 0x06b1, + 0x0403 : 0x06b2, + 0x0401 : 0x06b3, + 0x0404 : 0x06b4, + 0x0405 : 0x06b5, + 0x0406 : 0x06b6, + 0x0407 : 0x06b7, + 0x0408 : 0x06b8, + 0x0409 : 0x06b9, + 0x040A : 0x06ba, + 0x040B : 0x06bb, + 0x040C : 0x06bc, + 0x0490 : 0x06bd, + 0x040E : 0x06be, + 0x040F : 0x06bf, + 0x044E : 0x06c0, + 0x0430 : 0x06c1, + 0x0431 : 0x06c2, + 0x0446 : 0x06c3, + 0x0434 : 0x06c4, + 0x0435 : 0x06c5, + 0x0444 : 0x06c6, + 0x0433 : 0x06c7, + 0x0445 : 0x06c8, + 0x0438 : 0x06c9, + 0x0439 : 0x06ca, + 0x043A : 0x06cb, + 0x043B : 0x06cc, + 0x043C : 0x06cd, + 0x043D : 0x06ce, + 0x043E : 0x06cf, + 0x043F : 0x06d0, + 0x044F : 0x06d1, + 0x0440 : 0x06d2, + 0x0441 : 0x06d3, + 0x0442 : 0x06d4, + 0x0443 : 0x06d5, + 0x0436 : 0x06d6, + 0x0432 : 0x06d7, + 0x044C : 0x06d8, + 0x044B : 0x06d9, + 0x0437 : 0x06da, + 0x0448 : 0x06db, + 0x044D : 0x06dc, + 0x0449 : 0x06dd, + 0x0447 : 0x06de, + 0x044A : 0x06df, + 0x042E : 0x06e0, + 0x0410 : 0x06e1, + 0x0411 : 0x06e2, + 0x0426 : 0x06e3, + 0x0414 : 0x06e4, + 0x0415 : 0x06e5, + 0x0424 : 0x06e6, + 0x0413 : 0x06e7, + 0x0425 : 0x06e8, + 0x0418 : 0x06e9, + 0x0419 : 0x06ea, + 0x041A : 0x06eb, + 0x041B : 0x06ec, + 0x041C : 0x06ed, + 0x041D : 0x06ee, + 0x041E : 0x06ef, + 0x041F : 0x06f0, + 0x042F : 0x06f1, + 0x0420 : 0x06f2, + 0x0421 : 0x06f3, + 0x0422 : 0x06f4, + 0x0423 : 0x06f5, + 0x0416 : 0x06f6, + 0x0412 : 0x06f7, + 0x042C : 0x06f8, + 0x042B : 0x06f9, + 0x0417 : 0x06fa, + 0x0428 : 0x06fb, + 0x042D : 0x06fc, + 0x0429 : 0x06fd, + 0x0427 : 0x06fe, + 0x042A : 0x06ff, + 0x0386 : 0x07a1, + 0x0388 : 0x07a2, + 0x0389 : 0x07a3, + 0x038A : 0x07a4, + 0x03AA : 0x07a5, + 0x038C : 0x07a7, + 0x038E : 0x07a8, + 0x03AB : 0x07a9, + 0x038F : 0x07ab, + 0x0385 : 0x07ae, + 0x2015 : 0x07af, + 0x03AC : 0x07b1, + 0x03AD : 0x07b2, + 0x03AE : 0x07b3, + 0x03AF : 0x07b4, + 0x03CA : 0x07b5, + 0x0390 : 0x07b6, + 0x03CC : 0x07b7, + 0x03CD : 0x07b8, + 0x03CB : 0x07b9, + 0x03B0 : 0x07ba, + 0x03CE : 0x07bb, + 0x0391 : 0x07c1, + 0x0392 : 0x07c2, + 0x0393 : 0x07c3, + 0x0394 : 0x07c4, + 0x0395 : 0x07c5, + 0x0396 : 0x07c6, + 0x0397 : 0x07c7, + 0x0398 : 0x07c8, + 0x0399 : 0x07c9, + 0x039A : 0x07ca, + 0x039B : 0x07cb, + 0x039C : 0x07cc, + 0x039D : 0x07cd, + 0x039E : 0x07ce, + 0x039F : 0x07cf, + 0x03A0 : 0x07d0, + 0x03A1 : 0x07d1, + 0x03A3 : 0x07d2, + 0x03A4 : 0x07d4, + 0x03A5 : 0x07d5, + 0x03A6 : 0x07d6, + 0x03A7 : 0x07d7, + 0x03A8 : 0x07d8, + 0x03A9 : 0x07d9, + 0x03B1 : 0x07e1, + 0x03B2 : 0x07e2, + 0x03B3 : 0x07e3, + 0x03B4 : 0x07e4, + 0x03B5 : 0x07e5, + 0x03B6 : 0x07e6, + 0x03B7 : 0x07e7, + 0x03B8 : 0x07e8, + 0x03B9 : 0x07e9, + 0x03BA : 0x07ea, + 0x03BB : 0x07eb, + 0x03BC : 0x07ec, + 0x03BD : 0x07ed, + 0x03BE : 0x07ee, + 0x03BF : 0x07ef, + 0x03C0 : 0x07f0, + 0x03C1 : 0x07f1, + 0x03C3 : 0x07f2, + 0x03C2 : 0x07f3, + 0x03C4 : 0x07f4, + 0x03C5 : 0x07f5, + 0x03C6 : 0x07f6, + 0x03C7 : 0x07f7, + 0x03C8 : 0x07f8, + 0x03C9 : 0x07f9, + 0x23B7 : 0x08a1, + 0x2320 : 0x08a4, + 0x2321 : 0x08a5, + 0x23A1 : 0x08a7, + 0x23A3 : 0x08a8, + 0x23A4 : 0x08a9, + 0x23A6 : 0x08aa, + 0x239B : 0x08ab, + 0x239D : 0x08ac, + 0x239E : 0x08ad, + 0x23A0 : 0x08ae, + 0x23A8 : 0x08af, + 0x23AC : 0x08b0, + 0x2264 : 0x08bc, + 0x2260 : 0x08bd, + 0x2265 : 0x08be, + 0x222B : 0x08bf, + 0x2234 : 0x08c0, + 0x221D : 0x08c1, + 0x221E : 0x08c2, + 0x2207 : 0x08c5, + 0x223C : 0x08c8, + 0x2243 : 0x08c9, + 0x21D4 : 0x08cd, + 0x21D2 : 0x08ce, + 0x2261 : 0x08cf, + 0x221A : 0x08d6, + 0x2282 : 0x08da, + 0x2283 : 0x08db, + 0x2229 : 0x08dc, + 0x222A : 0x08dd, + 0x2227 : 0x08de, + 0x2228 : 0x08df, + 0x2202 : 0x08ef, + 0x0192 : 0x08f6, + 0x2190 : 0x08fb, + 0x2191 : 0x08fc, + 0x2192 : 0x08fd, + 0x2193 : 0x08fe, + 0x25C6 : 0x09e0, + 0x2592 : 0x09e1, + 0x2409 : 0x09e2, + 0x240C : 0x09e3, + 0x240D : 0x09e4, + 0x240A : 0x09e5, + 0x2424 : 0x09e8, + 0x240B : 0x09e9, + 0x2518 : 0x09ea, + 0x2510 : 0x09eb, + 0x250C : 0x09ec, + 0x2514 : 0x09ed, + 0x253C : 0x09ee, + 0x23BA : 0x09ef, + 0x23BB : 0x09f0, + 0x2500 : 0x09f1, + 0x23BC : 0x09f2, + 0x23BD : 0x09f3, + 0x251C : 0x09f4, + 0x2524 : 0x09f5, + 0x2534 : 0x09f6, + 0x252C : 0x09f7, + 0x2502 : 0x09f8, + 0x2003 : 0x0aa1, + 0x2002 : 0x0aa2, + 0x2004 : 0x0aa3, + 0x2005 : 0x0aa4, + 0x2007 : 0x0aa5, + 0x2008 : 0x0aa6, + 0x2009 : 0x0aa7, + 0x200A : 0x0aa8, + 0x2014 : 0x0aa9, + 0x2013 : 0x0aaa, + 0x2026 : 0x0aae, + 0x2025 : 0x0aaf, + 0x2153 : 0x0ab0, + 0x2154 : 0x0ab1, + 0x2155 : 0x0ab2, + 0x2156 : 0x0ab3, + 0x2157 : 0x0ab4, + 0x2158 : 0x0ab5, + 0x2159 : 0x0ab6, + 0x215A : 0x0ab7, + 0x2105 : 0x0ab8, + 0x2012 : 0x0abb, + 0x215B : 0x0ac3, + 0x215C : 0x0ac4, + 0x215D : 0x0ac5, + 0x215E : 0x0ac6, + 0x2122 : 0x0ac9, + 0x2018 : 0x0ad0, + 0x2019 : 0x0ad1, + 0x201C : 0x0ad2, + 0x201D : 0x0ad3, + 0x211E : 0x0ad4, + 0x2032 : 0x0ad6, + 0x2033 : 0x0ad7, + 0x271D : 0x0ad9, + 0x2663 : 0x0aec, + 0x2666 : 0x0aed, + 0x2665 : 0x0aee, + 0x2720 : 0x0af0, + 0x2020 : 0x0af1, + 0x2021 : 0x0af2, + 0x2713 : 0x0af3, + 0x2717 : 0x0af4, + 0x266F : 0x0af5, + 0x266D : 0x0af6, + 0x2642 : 0x0af7, + 0x2640 : 0x0af8, + 0x260E : 0x0af9, + 0x2315 : 0x0afa, + 0x2117 : 0x0afb, + 0x2038 : 0x0afc, + 0x201A : 0x0afd, + 0x201E : 0x0afe, + 0x22A4 : 0x0bc2, + 0x230A : 0x0bc4, + 0x2218 : 0x0bca, + 0x2395 : 0x0bcc, + 0x22A5 : 0x0bce, + 0x25CB : 0x0bcf, + 0x2308 : 0x0bd3, + 0x22A3 : 0x0bdc, + 0x22A2 : 0x0bfc, + 0x2017 : 0x0cdf, + 0x05D0 : 0x0ce0, + 0x05D1 : 0x0ce1, + 0x05D2 : 0x0ce2, + 0x05D3 : 0x0ce3, + 0x05D4 : 0x0ce4, + 0x05D5 : 0x0ce5, + 0x05D6 : 0x0ce6, + 0x05D7 : 0x0ce7, + 0x05D8 : 0x0ce8, + 0x05D9 : 0x0ce9, + 0x05DA : 0x0cea, + 0x05DB : 0x0ceb, + 0x05DC : 0x0cec, + 0x05DD : 0x0ced, + 0x05DE : 0x0cee, + 0x05DF : 0x0cef, + 0x05E0 : 0x0cf0, + 0x05E1 : 0x0cf1, + 0x05E2 : 0x0cf2, + 0x05E3 : 0x0cf3, + 0x05E4 : 0x0cf4, + 0x05E5 : 0x0cf5, + 0x05E6 : 0x0cf6, + 0x05E7 : 0x0cf7, + 0x05E8 : 0x0cf8, + 0x05E9 : 0x0cf9, + 0x05EA : 0x0cfa, + 0x0E01 : 0x0da1, + 0x0E02 : 0x0da2, + 0x0E03 : 0x0da3, + 0x0E04 : 0x0da4, + 0x0E05 : 0x0da5, + 0x0E06 : 0x0da6, + 0x0E07 : 0x0da7, + 0x0E08 : 0x0da8, + 0x0E09 : 0x0da9, + 0x0E0A : 0x0daa, + 0x0E0B : 0x0dab, + 0x0E0C : 0x0dac, + 0x0E0D : 0x0dad, + 0x0E0E : 0x0dae, + 0x0E0F : 0x0daf, + 0x0E10 : 0x0db0, + 0x0E11 : 0x0db1, + 0x0E12 : 0x0db2, + 0x0E13 : 0x0db3, + 0x0E14 : 0x0db4, + 0x0E15 : 0x0db5, + 0x0E16 : 0x0db6, + 0x0E17 : 0x0db7, + 0x0E18 : 0x0db8, + 0x0E19 : 0x0db9, + 0x0E1A : 0x0dba, + 0x0E1B : 0x0dbb, + 0x0E1C : 0x0dbc, + 0x0E1D : 0x0dbd, + 0x0E1E : 0x0dbe, + 0x0E1F : 0x0dbf, + 0x0E20 : 0x0dc0, + 0x0E21 : 0x0dc1, + 0x0E22 : 0x0dc2, + 0x0E23 : 0x0dc3, + 0x0E24 : 0x0dc4, + 0x0E25 : 0x0dc5, + 0x0E26 : 0x0dc6, + 0x0E27 : 0x0dc7, + 0x0E28 : 0x0dc8, + 0x0E29 : 0x0dc9, + 0x0E2A : 0x0dca, + 0x0E2B : 0x0dcb, + 0x0E2C : 0x0dcc, + 0x0E2D : 0x0dcd, + 0x0E2E : 0x0dce, + 0x0E2F : 0x0dcf, + 0x0E30 : 0x0dd0, + 0x0E31 : 0x0dd1, + 0x0E32 : 0x0dd2, + 0x0E33 : 0x0dd3, + 0x0E34 : 0x0dd4, + 0x0E35 : 0x0dd5, + 0x0E36 : 0x0dd6, + 0x0E37 : 0x0dd7, + 0x0E38 : 0x0dd8, + 0x0E39 : 0x0dd9, + 0x0E3A : 0x0dda, + 0x0E3F : 0x0ddf, + 0x0E40 : 0x0de0, + 0x0E41 : 0x0de1, + 0x0E42 : 0x0de2, + 0x0E43 : 0x0de3, + 0x0E44 : 0x0de4, + 0x0E45 : 0x0de5, + 0x0E46 : 0x0de6, + 0x0E47 : 0x0de7, + 0x0E48 : 0x0de8, + 0x0E49 : 0x0de9, + 0x0E4A : 0x0dea, + 0x0E4B : 0x0deb, + 0x0E4C : 0x0dec, + 0x0E4D : 0x0ded, + 0x0E50 : 0x0df0, + 0x0E51 : 0x0df1, + 0x0E52 : 0x0df2, + 0x0E53 : 0x0df3, + 0x0E54 : 0x0df4, + 0x0E55 : 0x0df5, + 0x0E56 : 0x0df6, + 0x0E57 : 0x0df7, + 0x0E58 : 0x0df8, + 0x0E59 : 0x0df9, + 0x0587 : 0x1000587, + 0x0589 : 0x1000589, + 0x055D : 0x100055d, + 0x058A : 0x100058a, + 0x055C : 0x100055c, + 0x055B : 0x100055b, + 0x055E : 0x100055e, + 0x0531 : 0x1000531, + 0x0561 : 0x1000561, + 0x0532 : 0x1000532, + 0x0562 : 0x1000562, + 0x0533 : 0x1000533, + 0x0563 : 0x1000563, + 0x0534 : 0x1000534, + 0x0564 : 0x1000564, + 0x0535 : 0x1000535, + 0x0565 : 0x1000565, + 0x0536 : 0x1000536, + 0x0566 : 0x1000566, + 0x0537 : 0x1000537, + 0x0567 : 0x1000567, + 0x0538 : 0x1000538, + 0x0568 : 0x1000568, + 0x0539 : 0x1000539, + 0x0569 : 0x1000569, + 0x053A : 0x100053a, + 0x056A : 0x100056a, + 0x053B : 0x100053b, + 0x056B : 0x100056b, + 0x053C : 0x100053c, + 0x056C : 0x100056c, + 0x053D : 0x100053d, + 0x056D : 0x100056d, + 0x053E : 0x100053e, + 0x056E : 0x100056e, + 0x053F : 0x100053f, + 0x056F : 0x100056f, + 0x0540 : 0x1000540, + 0x0570 : 0x1000570, + 0x0541 : 0x1000541, + 0x0571 : 0x1000571, + 0x0542 : 0x1000542, + 0x0572 : 0x1000572, + 0x0543 : 0x1000543, + 0x0573 : 0x1000573, + 0x0544 : 0x1000544, + 0x0574 : 0x1000574, + 0x0545 : 0x1000545, + 0x0575 : 0x1000575, + 0x0546 : 0x1000546, + 0x0576 : 0x1000576, + 0x0547 : 0x1000547, + 0x0577 : 0x1000577, + 0x0548 : 0x1000548, + 0x0578 : 0x1000578, + 0x0549 : 0x1000549, + 0x0579 : 0x1000579, + 0x054A : 0x100054a, + 0x057A : 0x100057a, + 0x054B : 0x100054b, + 0x057B : 0x100057b, + 0x054C : 0x100054c, + 0x057C : 0x100057c, + 0x054D : 0x100054d, + 0x057D : 0x100057d, + 0x054E : 0x100054e, + 0x057E : 0x100057e, + 0x054F : 0x100054f, + 0x057F : 0x100057f, + 0x0550 : 0x1000550, + 0x0580 : 0x1000580, + 0x0551 : 0x1000551, + 0x0581 : 0x1000581, + 0x0552 : 0x1000552, + 0x0582 : 0x1000582, + 0x0553 : 0x1000553, + 0x0583 : 0x1000583, + 0x0554 : 0x1000554, + 0x0584 : 0x1000584, + 0x0555 : 0x1000555, + 0x0585 : 0x1000585, + 0x0556 : 0x1000556, + 0x0586 : 0x1000586, + 0x055A : 0x100055a, + 0x10D0 : 0x10010d0, + 0x10D1 : 0x10010d1, + 0x10D2 : 0x10010d2, + 0x10D3 : 0x10010d3, + 0x10D4 : 0x10010d4, + 0x10D5 : 0x10010d5, + 0x10D6 : 0x10010d6, + 0x10D7 : 0x10010d7, + 0x10D8 : 0x10010d8, + 0x10D9 : 0x10010d9, + 0x10DA : 0x10010da, + 0x10DB : 0x10010db, + 0x10DC : 0x10010dc, + 0x10DD : 0x10010dd, + 0x10DE : 0x10010de, + 0x10DF : 0x10010df, + 0x10E0 : 0x10010e0, + 0x10E1 : 0x10010e1, + 0x10E2 : 0x10010e2, + 0x10E3 : 0x10010e3, + 0x10E4 : 0x10010e4, + 0x10E5 : 0x10010e5, + 0x10E6 : 0x10010e6, + 0x10E7 : 0x10010e7, + 0x10E8 : 0x10010e8, + 0x10E9 : 0x10010e9, + 0x10EA : 0x10010ea, + 0x10EB : 0x10010eb, + 0x10EC : 0x10010ec, + 0x10ED : 0x10010ed, + 0x10EE : 0x10010ee, + 0x10EF : 0x10010ef, + 0x10F0 : 0x10010f0, + 0x10F1 : 0x10010f1, + 0x10F2 : 0x10010f2, + 0x10F3 : 0x10010f3, + 0x10F4 : 0x10010f4, + 0x10F5 : 0x10010f5, + 0x10F6 : 0x10010f6, + 0x1E8A : 0x1001e8a, + 0x012C : 0x100012c, + 0x01B5 : 0x10001b5, + 0x01E6 : 0x10001e6, + 0x01D2 : 0x10001d1, + 0x019F : 0x100019f, + 0x1E8B : 0x1001e8b, + 0x012D : 0x100012d, + 0x01B6 : 0x10001b6, + 0x01E7 : 0x10001e7, + 0x01D2 : 0x10001d2, + 0x0275 : 0x1000275, + 0x018F : 0x100018f, + 0x0259 : 0x1000259, + 0x1E36 : 0x1001e36, + 0x1E37 : 0x1001e37, + 0x1EA0 : 0x1001ea0, + 0x1EA1 : 0x1001ea1, + 0x1EA2 : 0x1001ea2, + 0x1EA3 : 0x1001ea3, + 0x1EA4 : 0x1001ea4, + 0x1EA5 : 0x1001ea5, + 0x1EA6 : 0x1001ea6, + 0x1EA7 : 0x1001ea7, + 0x1EA8 : 0x1001ea8, + 0x1EA9 : 0x1001ea9, + 0x1EAA : 0x1001eaa, + 0x1EAB : 0x1001eab, + 0x1EAC : 0x1001eac, + 0x1EAD : 0x1001ead, + 0x1EAE : 0x1001eae, + 0x1EAF : 0x1001eaf, + 0x1EB0 : 0x1001eb0, + 0x1EB1 : 0x1001eb1, + 0x1EB2 : 0x1001eb2, + 0x1EB3 : 0x1001eb3, + 0x1EB4 : 0x1001eb4, + 0x1EB5 : 0x1001eb5, + 0x1EB6 : 0x1001eb6, + 0x1EB7 : 0x1001eb7, + 0x1EB8 : 0x1001eb8, + 0x1EB9 : 0x1001eb9, + 0x1EBA : 0x1001eba, + 0x1EBB : 0x1001ebb, + 0x1EBC : 0x1001ebc, + 0x1EBD : 0x1001ebd, + 0x1EBE : 0x1001ebe, + 0x1EBF : 0x1001ebf, + 0x1EC0 : 0x1001ec0, + 0x1EC1 : 0x1001ec1, + 0x1EC2 : 0x1001ec2, + 0x1EC3 : 0x1001ec3, + 0x1EC4 : 0x1001ec4, + 0x1EC5 : 0x1001ec5, + 0x1EC6 : 0x1001ec6, + 0x1EC7 : 0x1001ec7, + 0x1EC8 : 0x1001ec8, + 0x1EC9 : 0x1001ec9, + 0x1ECA : 0x1001eca, + 0x1ECB : 0x1001ecb, + 0x1ECC : 0x1001ecc, + 0x1ECD : 0x1001ecd, + 0x1ECE : 0x1001ece, + 0x1ECF : 0x1001ecf, + 0x1ED0 : 0x1001ed0, + 0x1ED1 : 0x1001ed1, + 0x1ED2 : 0x1001ed2, + 0x1ED3 : 0x1001ed3, + 0x1ED4 : 0x1001ed4, + 0x1ED5 : 0x1001ed5, + 0x1ED6 : 0x1001ed6, + 0x1ED7 : 0x1001ed7, + 0x1ED8 : 0x1001ed8, + 0x1ED9 : 0x1001ed9, + 0x1EDA : 0x1001eda, + 0x1EDB : 0x1001edb, + 0x1EDC : 0x1001edc, + 0x1EDD : 0x1001edd, + 0x1EDE : 0x1001ede, + 0x1EDF : 0x1001edf, + 0x1EE0 : 0x1001ee0, + 0x1EE1 : 0x1001ee1, + 0x1EE2 : 0x1001ee2, + 0x1EE3 : 0x1001ee3, + 0x1EE4 : 0x1001ee4, + 0x1EE5 : 0x1001ee5, + 0x1EE6 : 0x1001ee6, + 0x1EE7 : 0x1001ee7, + 0x1EE8 : 0x1001ee8, + 0x1EE9 : 0x1001ee9, + 0x1EEA : 0x1001eea, + 0x1EEB : 0x1001eeb, + 0x1EEC : 0x1001eec, + 0x1EED : 0x1001eed, + 0x1EEE : 0x1001eee, + 0x1EEF : 0x1001eef, + 0x1EF0 : 0x1001ef0, + 0x1EF1 : 0x1001ef1, + 0x1EF4 : 0x1001ef4, + 0x1EF5 : 0x1001ef5, + 0x1EF6 : 0x1001ef6, + 0x1EF7 : 0x1001ef7, + 0x1EF8 : 0x1001ef8, + 0x1EF9 : 0x1001ef9, + 0x01A0 : 0x10001a0, + 0x01A1 : 0x10001a1, + 0x01AF : 0x10001af, + 0x01B0 : 0x10001b0, + 0x20A0 : 0x10020a0, + 0x20A1 : 0x10020a1, + 0x20A2 : 0x10020a2, + 0x20A3 : 0x10020a3, + 0x20A4 : 0x10020a4, + 0x20A5 : 0x10020a5, + 0x20A6 : 0x10020a6, + 0x20A7 : 0x10020a7, + 0x20A8 : 0x10020a8, + 0x20A9 : 0x10020a9, + 0x20AA : 0x10020aa, + 0x20AB : 0x10020ab, + 0x20AC : 0x20ac, + 0x2070 : 0x1002070, + 0x2074 : 0x1002074, + 0x2075 : 0x1002075, + 0x2076 : 0x1002076, + 0x2077 : 0x1002077, + 0x2078 : 0x1002078, + 0x2079 : 0x1002079, + 0x2080 : 0x1002080, + 0x2081 : 0x1002081, + 0x2082 : 0x1002082, + 0x2083 : 0x1002083, + 0x2084 : 0x1002084, + 0x2085 : 0x1002085, + 0x2086 : 0x1002086, + 0x2087 : 0x1002087, + 0x2088 : 0x1002088, + 0x2089 : 0x1002089, + 0x2202 : 0x1002202, + 0x2205 : 0x1002205, + 0x2208 : 0x1002208, + 0x2209 : 0x1002209, + 0x220B : 0x100220B, + 0x221A : 0x100221A, + 0x221B : 0x100221B, + 0x221C : 0x100221C, + 0x222C : 0x100222C, + 0x222D : 0x100222D, + 0x2235 : 0x1002235, + 0x2245 : 0x1002248, + 0x2247 : 0x1002247, + 0x2262 : 0x1002262, + 0x2263 : 0x1002263, + 0x2800 : 0x1002800, + 0x2801 : 0x1002801, + 0x2802 : 0x1002802, + 0x2803 : 0x1002803, + 0x2804 : 0x1002804, + 0x2805 : 0x1002805, + 0x2806 : 0x1002806, + 0x2807 : 0x1002807, + 0x2808 : 0x1002808, + 0x2809 : 0x1002809, + 0x280a : 0x100280a, + 0x280b : 0x100280b, + 0x280c : 0x100280c, + 0x280d : 0x100280d, + 0x280e : 0x100280e, + 0x280f : 0x100280f, + 0x2810 : 0x1002810, + 0x2811 : 0x1002811, + 0x2812 : 0x1002812, + 0x2813 : 0x1002813, + 0x2814 : 0x1002814, + 0x2815 : 0x1002815, + 0x2816 : 0x1002816, + 0x2817 : 0x1002817, + 0x2818 : 0x1002818, + 0x2819 : 0x1002819, + 0x281a : 0x100281a, + 0x281b : 0x100281b, + 0x281c : 0x100281c, + 0x281d : 0x100281d, + 0x281e : 0x100281e, + 0x281f : 0x100281f, + 0x2820 : 0x1002820, + 0x2821 : 0x1002821, + 0x2822 : 0x1002822, + 0x2823 : 0x1002823, + 0x2824 : 0x1002824, + 0x2825 : 0x1002825, + 0x2826 : 0x1002826, + 0x2827 : 0x1002827, + 0x2828 : 0x1002828, + 0x2829 : 0x1002829, + 0x282a : 0x100282a, + 0x282b : 0x100282b, + 0x282c : 0x100282c, + 0x282d : 0x100282d, + 0x282e : 0x100282e, + 0x282f : 0x100282f, + 0x2830 : 0x1002830, + 0x2831 : 0x1002831, + 0x2832 : 0x1002832, + 0x2833 : 0x1002833, + 0x2834 : 0x1002834, + 0x2835 : 0x1002835, + 0x2836 : 0x1002836, + 0x2837 : 0x1002837, + 0x2838 : 0x1002838, + 0x2839 : 0x1002839, + 0x283a : 0x100283a, + 0x283b : 0x100283b, + 0x283c : 0x100283c, + 0x283d : 0x100283d, + 0x283e : 0x100283e, + 0x283f : 0x100283f, + 0x2840 : 0x1002840, + 0x2841 : 0x1002841, + 0x2842 : 0x1002842, + 0x2843 : 0x1002843, + 0x2844 : 0x1002844, + 0x2845 : 0x1002845, + 0x2846 : 0x1002846, + 0x2847 : 0x1002847, + 0x2848 : 0x1002848, + 0x2849 : 0x1002849, + 0x284a : 0x100284a, + 0x284b : 0x100284b, + 0x284c : 0x100284c, + 0x284d : 0x100284d, + 0x284e : 0x100284e, + 0x284f : 0x100284f, + 0x2850 : 0x1002850, + 0x2851 : 0x1002851, + 0x2852 : 0x1002852, + 0x2853 : 0x1002853, + 0x2854 : 0x1002854, + 0x2855 : 0x1002855, + 0x2856 : 0x1002856, + 0x2857 : 0x1002857, + 0x2858 : 0x1002858, + 0x2859 : 0x1002859, + 0x285a : 0x100285a, + 0x285b : 0x100285b, + 0x285c : 0x100285c, + 0x285d : 0x100285d, + 0x285e : 0x100285e, + 0x285f : 0x100285f, + 0x2860 : 0x1002860, + 0x2861 : 0x1002861, + 0x2862 : 0x1002862, + 0x2863 : 0x1002863, + 0x2864 : 0x1002864, + 0x2865 : 0x1002865, + 0x2866 : 0x1002866, + 0x2867 : 0x1002867, + 0x2868 : 0x1002868, + 0x2869 : 0x1002869, + 0x286a : 0x100286a, + 0x286b : 0x100286b, + 0x286c : 0x100286c, + 0x286d : 0x100286d, + 0x286e : 0x100286e, + 0x286f : 0x100286f, + 0x2870 : 0x1002870, + 0x2871 : 0x1002871, + 0x2872 : 0x1002872, + 0x2873 : 0x1002873, + 0x2874 : 0x1002874, + 0x2875 : 0x1002875, + 0x2876 : 0x1002876, + 0x2877 : 0x1002877, + 0x2878 : 0x1002878, + 0x2879 : 0x1002879, + 0x287a : 0x100287a, + 0x287b : 0x100287b, + 0x287c : 0x100287c, + 0x287d : 0x100287d, + 0x287e : 0x100287e, + 0x287f : 0x100287f, + 0x2880 : 0x1002880, + 0x2881 : 0x1002881, + 0x2882 : 0x1002882, + 0x2883 : 0x1002883, + 0x2884 : 0x1002884, + 0x2885 : 0x1002885, + 0x2886 : 0x1002886, + 0x2887 : 0x1002887, + 0x2888 : 0x1002888, + 0x2889 : 0x1002889, + 0x288a : 0x100288a, + 0x288b : 0x100288b, + 0x288c : 0x100288c, + 0x288d : 0x100288d, + 0x288e : 0x100288e, + 0x288f : 0x100288f, + 0x2890 : 0x1002890, + 0x2891 : 0x1002891, + 0x2892 : 0x1002892, + 0x2893 : 0x1002893, + 0x2894 : 0x1002894, + 0x2895 : 0x1002895, + 0x2896 : 0x1002896, + 0x2897 : 0x1002897, + 0x2898 : 0x1002898, + 0x2899 : 0x1002899, + 0x289a : 0x100289a, + 0x289b : 0x100289b, + 0x289c : 0x100289c, + 0x289d : 0x100289d, + 0x289e : 0x100289e, + 0x289f : 0x100289f, + 0x28a0 : 0x10028a0, + 0x28a1 : 0x10028a1, + 0x28a2 : 0x10028a2, + 0x28a3 : 0x10028a3, + 0x28a4 : 0x10028a4, + 0x28a5 : 0x10028a5, + 0x28a6 : 0x10028a6, + 0x28a7 : 0x10028a7, + 0x28a8 : 0x10028a8, + 0x28a9 : 0x10028a9, + 0x28aa : 0x10028aa, + 0x28ab : 0x10028ab, + 0x28ac : 0x10028ac, + 0x28ad : 0x10028ad, + 0x28ae : 0x10028ae, + 0x28af : 0x10028af, + 0x28b0 : 0x10028b0, + 0x28b1 : 0x10028b1, + 0x28b2 : 0x10028b2, + 0x28b3 : 0x10028b3, + 0x28b4 : 0x10028b4, + 0x28b5 : 0x10028b5, + 0x28b6 : 0x10028b6, + 0x28b7 : 0x10028b7, + 0x28b8 : 0x10028b8, + 0x28b9 : 0x10028b9, + 0x28ba : 0x10028ba, + 0x28bb : 0x10028bb, + 0x28bc : 0x10028bc, + 0x28bd : 0x10028bd, + 0x28be : 0x10028be, + 0x28bf : 0x10028bf, + 0x28c0 : 0x10028c0, + 0x28c1 : 0x10028c1, + 0x28c2 : 0x10028c2, + 0x28c3 : 0x10028c3, + 0x28c4 : 0x10028c4, + 0x28c5 : 0x10028c5, + 0x28c6 : 0x10028c6, + 0x28c7 : 0x10028c7, + 0x28c8 : 0x10028c8, + 0x28c9 : 0x10028c9, + 0x28ca : 0x10028ca, + 0x28cb : 0x10028cb, + 0x28cc : 0x10028cc, + 0x28cd : 0x10028cd, + 0x28ce : 0x10028ce, + 0x28cf : 0x10028cf, + 0x28d0 : 0x10028d0, + 0x28d1 : 0x10028d1, + 0x28d2 : 0x10028d2, + 0x28d3 : 0x10028d3, + 0x28d4 : 0x10028d4, + 0x28d5 : 0x10028d5, + 0x28d6 : 0x10028d6, + 0x28d7 : 0x10028d7, + 0x28d8 : 0x10028d8, + 0x28d9 : 0x10028d9, + 0x28da : 0x10028da, + 0x28db : 0x10028db, + 0x28dc : 0x10028dc, + 0x28dd : 0x10028dd, + 0x28de : 0x10028de, + 0x28df : 0x10028df, + 0x28e0 : 0x10028e0, + 0x28e1 : 0x10028e1, + 0x28e2 : 0x10028e2, + 0x28e3 : 0x10028e3, + 0x28e4 : 0x10028e4, + 0x28e5 : 0x10028e5, + 0x28e6 : 0x10028e6, + 0x28e7 : 0x10028e7, + 0x28e8 : 0x10028e8, + 0x28e9 : 0x10028e9, + 0x28ea : 0x10028ea, + 0x28eb : 0x10028eb, + 0x28ec : 0x10028ec, + 0x28ed : 0x10028ed, + 0x28ee : 0x10028ee, + 0x28ef : 0x10028ef, + 0x28f0 : 0x10028f0, + 0x28f1 : 0x10028f1, + 0x28f2 : 0x10028f2, + 0x28f3 : 0x10028f3, + 0x28f4 : 0x10028f4, + 0x28f5 : 0x10028f5, + 0x28f6 : 0x10028f6, + 0x28f7 : 0x10028f7, + 0x28f8 : 0x10028f8, + 0x28f9 : 0x10028f9, + 0x28fa : 0x10028fa, + 0x28fb : 0x10028fb, + 0x28fc : 0x10028fc, + 0x28fd : 0x10028fd, + 0x28fe : 0x10028fe, + 0x28ff : 0x10028ff +}; \ No newline at end of file diff --git a/classes/novnc/include/logo.js b/classes/novnc/include/logo.js new file mode 100644 index 0000000..befa598 --- /dev/null +++ b/classes/novnc/include/logo.js @@ -0,0 +1 @@ +noVNC_logo = {"width": 640, "height": 435, "data": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAoAAAAGzCAYAAAC/y6a9AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAStAAAErQBBHTWggAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAACAASURBVHic7N13fBvlwQfw3522ZMm2vPdIGCFkA4GyoYyGsCmjk+7dQksHL2/H2/dtC4W2tLTlfelu2VA2lEILFCgQIHEGJCQkdjzkLdmWZGvfvX8oOkmJEy/pNO73/Xz44DtLzz2RT7qfnnXC8uXLZUxDlqfdnUYQhIP+bjbPn+5xhypzrmUf6rGzOc5cjzVduXN9/nTPyfRrMt/jzOcY05U5n3L2f95s/34LPW4m/p6FbLp/73xe+5nKnWuZs/07ZOOcnusx5nucbJU727LneuxslDmdTBxn/2NmusyEuZS7kHMxG/XP5Gf3TOVmQzY+u/PhPMnkMcSsH5WIiIiI8goDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHG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"}; diff --git a/classes/novnc/include/playback.js b/classes/novnc/include/playback.js new file mode 100644 index 0000000..22a00a3 --- /dev/null +++ b/classes/novnc/include/playback.js @@ -0,0 +1,90 @@ +/* + * noVNC: HTML5 VNC client + * Copyright (C) 2011 Joel Martin + * Licensed under LGPL-3 (see LICENSE.LGPL-3) + */ + +"use strict"; +/*jslint browser: true, white: false */ +/*global Util, VNC_frame_data, finish */ + +var rfb, mode, test_state, frame_idx, frame_length, + iteration, iterations, istart_time, + + // Pre-declarations for jslint + send_array, next_iteration, queue_next_packet, do_packet; + +// Override send_array +send_array = function (arr) { + // Stub out send_array +}; + +next_iteration = function () { + if (iteration === 0) { + frame_length = VNC_frame_data.length; + test_state = 'running'; + } else { + rfb.disconnect(); + } + + if (test_state !== 'running') { return; } + + iteration += 1; + if (iteration > iterations) { + finish(); + return; + } + + frame_idx = 0; + istart_time = (new Date()).getTime(); + rfb.connect('test', 0, "bogus"); + + queue_next_packet(); + +}; + +queue_next_packet = function () { + var frame, foffset, toffset, delay; + if (test_state !== 'running') { return; } + + frame = VNC_frame_data[frame_idx]; + while ((frame_idx < frame_length) && (frame.charAt(0) === "}")) { + //Util.Debug("Send frame " + frame_idx); + frame_idx += 1; + frame = VNC_frame_data[frame_idx]; + } + + if (frame === 'EOF') { + Util.Debug("Finished, found EOF"); + next_iteration(); + return; + } + if (frame_idx >= frame_length) { + Util.Debug("Finished, no more frames"); + next_iteration(); + return; + } + + if (mode === 'realtime') { + foffset = frame.slice(1, frame.indexOf('{', 1)); + toffset = (new Date()).getTime() - istart_time; + delay = foffset - toffset; + if (delay < 1) { + delay = 1; + } + + setTimeout(do_packet, delay); + } else { + setTimeout(do_packet, 1); + } +}; + +do_packet = function () { + //Util.Debug("Processing frame: " + frame_idx); + var frame = VNC_frame_data[frame_idx]; + rfb.recv_message({'data' : frame.slice(frame.indexOf('{', 1) + 1)}); + frame_idx += 1; + + queue_next_packet(); +}; + diff --git a/classes/novnc/include/rfb.js b/classes/novnc/include/rfb.js new file mode 100644 index 0000000..b7aa3f6 --- /dev/null +++ b/classes/novnc/include/rfb.js @@ -0,0 +1,1613 @@ +/* + * noVNC: HTML5 VNC client + * Copyright (C) 2011 Joel Martin + * Licensed under LGPL-3 (see LICENSE.txt) + * + * See README.md for usage and integration instructions. + */ + +/*jslint white: false, browser: true, bitwise: false, plusplus: false */ +/*global window, Util, Display, Keyboard, Mouse, Websock, Websock_native, Base64, DES */ + + +function RFB(defaults) { +"use strict"; + +var that = {}, // Public API methods + conf = {}, // Configuration attributes + + // Pre-declare private functions used before definitions (jslint) + init_vars, updateState, fail, handle_message, + init_msg, normal_msg, framebufferUpdate, print_stats, + + pixelFormat, clientEncodings, fbUpdateRequest, fbUpdateRequests, + keyEvent, pointerEvent, clientCutText, + + extract_data_uri, scan_tight_imgQ, + keyPress, mouseButton, mouseMove, + + checkEvents, // Overridable for testing + + + // + // Private RFB namespace variables + // + rfb_host = '', + rfb_port = 5900, + rfb_password = '', + rfb_path = '', + + rfb_state = 'disconnected', + rfb_version = 0, + rfb_max_version= 3.8, + rfb_auth_scheme= '', + + + // In preference order + encodings = [ + ['COPYRECT', 0x01 ], + ['TIGHT_PNG', -260 ], + ['HEXTILE', 0x05 ], + ['RRE', 0x02 ], + ['RAW', 0x00 ], + ['DesktopSize', -223 ], + ['Cursor', -239 ], + + // Psuedo-encoding settings + ['JPEG_quality_lo', -32 ], + //['JPEG_quality_hi', -23 ], + ['compress_lo', -255 ] + //['compress_hi', -247 ] + ], + + encHandlers = {}, + encNames = {}, + encStats = {}, // [rectCnt, rectCntTot] + + ws = null, // Websock object + display = null, // Display object + keyboard = null, // Keyboard input handler object + mouse = null, // Mouse input handler object + sendTimer = null, // Send Queue check timer + connTimer = null, // connection timer + disconnTimer = null, // disconnection timer + msgTimer = null, // queued handle_message timer + + // Frame buffer update state + FBU = { + rects : 0, + subrects : 0, // RRE + lines : 0, // RAW + tiles : 0, // HEXTILE + bytes : 0, + x : 0, + y : 0, + width : 0, + height : 0, + encoding : 0, + subencoding : -1, + background : null, + imgQ : [] // TIGHT_PNG image queue + }, + + fb_Bpp = 4, + fb_depth = 3, + fb_width = 0, + fb_height = 0, + fb_name = "", + + scan_imgQ_rate = 40, // 25 times per second or so + last_req_time = 0, + rre_chunk_sz = 100, + + timing = { + last_fbu : 0, + fbu_total : 0, + fbu_total_cnt : 0, + full_fbu_total : 0, + full_fbu_cnt : 0, + + fbu_rt_start : 0, + fbu_rt_total : 0, + fbu_rt_cnt : 0 + }, + + test_mode = false, + + def_con_timeout = Websock_native ? 2 : 5, + + /* Mouse state */ + mouse_buttonMask = 0, + mouse_arr = [], + viewportDragging = false, + viewportDragPos = {}; + +// Configuration attributes +Util.conf_defaults(conf, that, defaults, [ + ['target', 'wo', 'dom', null, 'VNC display rendering Canvas object'], + ['focusContainer', 'wo', 'dom', document, 'DOM element that captures keyboard input'], + + ['encrypt', 'rw', 'bool', false, 'Use TLS/SSL/wss encryption'], + ['true_color', 'rw', 'bool', true, 'Request true color pixel data'], + ['local_cursor', 'rw', 'bool', false, 'Request locally rendered cursor'], + ['shared', 'rw', 'bool', true, 'Request shared mode'], + + ['connectTimeout', 'rw', 'int', def_con_timeout, 'Time (s) to wait for connection'], + ['disconnectTimeout', 'rw', 'int', 3, 'Time (s) to wait for disconnection'], + + ['viewportDrag', 'rw', 'bool', false, 'Move the viewport on mouse drags'], + + ['check_rate', 'rw', 'int', 217, 'Timing (ms) of send/receive check'], + ['fbu_req_rate', 'rw', 'int', 1413, 'Timing (ms) of frameBufferUpdate requests'], + + // Callback functions + ['onUpdateState', 'rw', 'func', function() { }, + 'onUpdateState(rfb, state, oldstate, statusMsg): RFB state update/change '], + ['onPasswordRequired', 'rw', 'func', function() { }, + 'onPasswordRequired(rfb): VNC password is required '], + ['onClipboard', 'rw', 'func', function() { }, + 'onClipboard(rfb, text): RFB clipboard contents received'], + ['onBell', 'rw', 'func', function() { }, + 'onBell(rfb): RFB Bell message received '], + ['onFBUReceive', 'rw', 'func', function() { }, + 'onFBUReceive(rfb, fbu): RFB FBU received but not yet processed '], + ['onFBUComplete', 'rw', 'func', function() { }, + 'onFBUComplete(rfb, fbu): RFB FBU received and processed '], + + // These callback names are deprecated + ['updateState', 'rw', 'func', function() { }, + 'obsolete, use onUpdateState'], + ['clipboardReceive', 'rw', 'func', function() { }, + 'obsolete, use onClipboard'] + ]); + + +// Override/add some specific configuration getters/setters +that.set_local_cursor = function(cursor) { + if ((!cursor) || (cursor in {'0':1, 'no':1, 'false':1})) { + conf.local_cursor = false; + } else { + if (display.get_cursor_uri()) { + conf.local_cursor = true; + } else { + Util.Warn("Browser does not support local cursor"); + } + } +}; + +// These are fake configuration getters +that.get_display = function() { return display; }; + +that.get_keyboard = function() { return keyboard; }; + +that.get_mouse = function() { return mouse; }; + + + +// +// Setup routines +// + +// Create the public API interface and initialize values that stay +// constant across connect/disconnect +function constructor() { + var i, rmode; + Util.Debug(">> RFB.constructor"); + + // Create lookup tables based encoding number + for (i=0; i < encodings.length; i+=1) { + encHandlers[encodings[i][1]] = encHandlers[encodings[i][0]]; + encNames[encodings[i][1]] = encodings[i][0]; + encStats[encodings[i][1]] = [0, 0]; + } + // Initialize display, mouse, keyboard, and websock + try { + display = new Display({'target': conf.target}); + } catch (exc) { + Util.Error("Display exception: " + exc); + updateState('fatal', "No working Display"); + } + keyboard = new Keyboard({'target': conf.focusContainer, + 'onKeyPress': keyPress}); + mouse = new Mouse({'target': conf.target, + 'onMouseButton': mouseButton, + 'onMouseMove': mouseMove}); + + rmode = display.get_render_mode(); + + ws = new Websock(); + ws.on('message', handle_message); + ws.on('open', function() { + if (rfb_state === "connect") { + updateState('ProtocolVersion', "Starting VNC handshake"); + } else { + fail("Got unexpected WebSockets connection"); + } + }); + ws.on('close', function() { + if (rfb_state === 'disconnect') { + updateState('disconnected', 'VNC disconnected'); + } else if (rfb_state === 'ProtocolVersion') { + fail('Failed to connect to server'); + } else if (rfb_state in {'failed':1, 'disconnected':1}) { + Util.Error("Received onclose while disconnected"); + } else { + fail('Server disconnected'); + } + }); + ws.on('error', function(e) { + fail("WebSock error: " + e); + }); + + + init_vars(); + + /* Check web-socket-js if no builtin WebSocket support */ + if (Websock_native) { + Util.Info("Using native WebSockets"); + updateState('loaded', 'noVNC ready: native WebSockets, ' + rmode); + } else { + Util.Warn("Using web-socket-js bridge. Flash version: " + + Util.Flash.version); + if ((! Util.Flash) || + (Util.Flash.version < 9)) { + updateState('fatal', "WebSockets or Adobe Flash<\/a> is required"); + } else if (document.location.href.substr(0, 7) === "file://") { + updateState('fatal', + "'file://' URL is incompatible with Adobe Flash"); + } else { + updateState('loaded', 'noVNC ready: WebSockets emulation, ' + rmode); + } + } + + Util.Debug("<< RFB.constructor"); + return that; // Return the public API interface +} + +function connect() { + Util.Debug(">> RFB.connect"); + + var uri = ""; + if (conf.encrypt) { + uri = "wss://"; + } else { + uri = "ws://"; + } + uri += rfb_host + ":" + rfb_port + "/" + rfb_path; + Util.Info("connecting to " + uri); + ws.open(uri); + + Util.Debug("<< RFB.connect"); +} + +// Initialize variables that are reset before each connection +init_vars = function() { + var i; + + /* Reset state */ + ws.init(); + + FBU.rects = 0; + FBU.subrects = 0; // RRE and HEXTILE + FBU.lines = 0; // RAW + FBU.tiles = 0; // HEXTILE + FBU.imgQ = []; // TIGHT_PNG image queue + mouse_buttonMask = 0; + mouse_arr = []; + + // Clear the per connection encoding stats + for (i=0; i < encodings.length; i+=1) { + encStats[encodings[i][1]][0] = 0; + } +}; + +// Print statistics +print_stats = function() { + var i, s; + Util.Info("Encoding stats for this connection:"); + for (i=0; i < encodings.length; i+=1) { + s = encStats[encodings[i][1]]; + if ((s[0] + s[1]) > 0) { + Util.Info(" " + encodings[i][0] + ": " + + s[0] + " rects"); + } + } + Util.Info("Encoding stats since page load:"); + for (i=0; i < encodings.length; i+=1) { + s = encStats[encodings[i][1]]; + if ((s[0] + s[1]) > 0) { + Util.Info(" " + encodings[i][0] + ": " + + s[1] + " rects"); + } + } +}; + +// +// Utility routines +// + + +/* + * Page states: + * loaded - page load, equivalent to disconnected + * disconnected - idle state + * connect - starting to connect (to ProtocolVersion) + * normal - connected + * disconnect - starting to disconnect + * failed - abnormal disconnect + * fatal - failed to load page, or fatal error + * + * RFB protocol initialization states: + * ProtocolVersion + * Security + * Authentication + * password - waiting for password, not part of RFB + * SecurityResult + * ClientInitialization - not triggered by server message + * ServerInitialization (to normal) + */ +updateState = function(state, statusMsg) { + var func, cmsg, oldstate = rfb_state; + + if (state === oldstate) { + /* Already here, ignore */ + Util.Debug("Already in state '" + state + "', ignoring."); + return; + } + + /* + * These are disconnected states. A previous connect may + * asynchronously cause a connection so make sure we are closed. + */ + if (state in {'disconnected':1, 'loaded':1, 'connect':1, + 'disconnect':1, 'failed':1, 'fatal':1}) { + if (sendTimer) { + clearInterval(sendTimer); + sendTimer = null; + } + + if (msgTimer) { + clearInterval(msgTimer); + msgTimer = null; + } + + if (display && display.get_context()) { + keyboard.ungrab(); + mouse.ungrab(); + display.defaultCursor(); + if ((Util.get_logging() !== 'debug') || + (state === 'loaded')) { + // Show noVNC logo on load and when disconnected if + // debug is off + display.clear(); + } + } + + ws.close(); + } + + if (oldstate === 'fatal') { + Util.Error("Fatal error, cannot continue"); + } + + if ((state === 'failed') || (state === 'fatal')) { + func = Util.Error; + } else { + func = Util.Warn; + } + + if ((oldstate === 'failed') && (state === 'disconnected')) { + // Do disconnect action, but stay in failed state. + rfb_state = 'failed'; + } else { + rfb_state = state; + } + + cmsg = typeof(statusMsg) !== 'undefined' ? (" Msg: " + statusMsg) : ""; + func("New state '" + rfb_state + "', was '" + oldstate + "'." + cmsg); + + if (connTimer && (rfb_state !== 'connect')) { + Util.Debug("Clearing connect timer"); + clearInterval(connTimer); + connTimer = null; + } + + if (disconnTimer && (rfb_state !== 'disconnect')) { + Util.Debug("Clearing disconnect timer"); + clearInterval(disconnTimer); + disconnTimer = null; + } + + switch (state) { + case 'normal': + if ((oldstate === 'disconnected') || (oldstate === 'failed')) { + Util.Error("Invalid transition from 'disconnected' or 'failed' to 'normal'"); + } + + break; + + + case 'connect': + + connTimer = setTimeout(function () { + fail("Connect timeout"); + }, conf.connectTimeout * 1000); + + init_vars(); + connect(); + + // WebSocket.onopen transitions to 'ProtocolVersion' + break; + + + case 'disconnect': + + if (! test_mode) { + disconnTimer = setTimeout(function () { + fail("Disconnect timeout"); + }, conf.disconnectTimeout * 1000); + } + + print_stats(); + + // WebSocket.onclose transitions to 'disconnected' + break; + + + case 'failed': + if (oldstate === 'disconnected') { + Util.Error("Invalid transition from 'disconnected' to 'failed'"); + } + if (oldstate === 'normal') { + Util.Error("Error while connected."); + } + if (oldstate === 'init') { + Util.Error("Error while initializing."); + } + + // Make sure we transition to disconnected + setTimeout(function() { updateState('disconnected'); }, 50); + + break; + + + default: + // No state change action to take + + } + + if ((oldstate === 'failed') && (state === 'disconnected')) { + // Leave the failed message + conf.updateState(that, state, oldstate); // Obsolete + conf.onUpdateState(that, state, oldstate); + } else { + conf.updateState(that, state, oldstate, statusMsg); // Obsolete + conf.onUpdateState(that, state, oldstate, statusMsg); + } +}; + +fail = function(msg) { + updateState('failed', msg); + return false; +}; + +handle_message = function() { + //Util.Debug(">> handle_message ws.rQlen(): " + ws.rQlen()); + //Util.Debug("ws.rQslice(0,20): " + ws.rQslice(0,20) + " (" + ws.rQlen() + ")"); + if (ws.rQlen() === 0) { + Util.Warn("handle_message called on empty receive queue"); + return; + } + switch (rfb_state) { + case 'disconnected': + case 'failed': + Util.Error("Got data while disconnected"); + break; + case 'normal': + if (normal_msg() && ws.rQlen() > 0) { + // true means we can continue processing + // Give other events a chance to run + if (msgTimer === null) { + Util.Debug("More data to process, creating timer"); + msgTimer = setTimeout(function () { + msgTimer = null; + handle_message(); + }, 10); + } else { + Util.Debug("More data to process, existing timer"); + } + } + break; + default: + init_msg(); + break; + } +}; + + +function genDES(password, challenge) { + var i, passwd = []; + for (i=0; i < password.length; i += 1) { + passwd.push(password.charCodeAt(i)); + } + return (new DES(passwd)).encrypt(challenge); +} + +function flushClient() { + if (mouse_arr.length > 0) { + //send(mouse_arr.concat(fbUpdateRequests())); + ws.send(mouse_arr); + setTimeout(function() { + ws.send(fbUpdateRequests()); + }, 50); + + mouse_arr = []; + return true; + } else { + return false; + } +} + +// overridable for testing +checkEvents = function() { + var now; + if (rfb_state === 'normal' && !viewportDragging) { + if (! flushClient()) { + now = new Date().getTime(); + if (now > last_req_time + conf.fbu_req_rate) { + last_req_time = now; + ws.send(fbUpdateRequests()); + } + } + } + setTimeout(checkEvents, conf.check_rate); +}; + +keyPress = function(keysym, down) { + var arr; + arr = keyEvent(keysym, down); + arr = arr.concat(fbUpdateRequests()); + ws.send(arr); +}; + +mouseButton = function(x, y, down, bmask) { + if (down) { + mouse_buttonMask |= bmask; + } else { + mouse_buttonMask ^= bmask; + } + + if (conf.viewportDrag) { + if (down && !viewportDragging) { + viewportDragging = true; + viewportDragPos = {'x': x, 'y': y}; + + // Skip sending mouse events + return; + } else { + viewportDragging = false; + } + } + + mouse_arr = mouse_arr.concat( + pointerEvent(display.absX(x), display.absY(y)) ); + flushClient(); +}; + +mouseMove = function(x, y) { + //Util.Debug('>> mouseMove ' + x + "," + y); + var deltaX, deltaY; + + if (viewportDragging) { + //deltaX = x - viewportDragPos.x; // drag viewport + deltaX = viewportDragPos.x - x; // drag frame buffer + //deltaY = y - viewportDragPos.y; // drag viewport + deltaY = viewportDragPos.y - y; // drag frame buffer + viewportDragPos = {'x': x, 'y': y}; + + display.viewportChange(deltaX, deltaY); + + // Skip sending mouse events + return; + } + + mouse_arr = mouse_arr.concat( + pointerEvent(display.absX(x), display.absY(y)) ); +}; + + +// +// Server message handlers +// + +// RFB/VNC initialisation message handler +init_msg = function() { + //Util.Debug(">> init_msg [rfb_state '" + rfb_state + "']"); + + var strlen, reason, length, sversion, cversion, + i, types, num_types, challenge, response, bpp, depth, + big_endian, red_max, green_max, blue_max, red_shift, + green_shift, blue_shift, true_color, name_length; + + //Util.Debug("ws.rQ (" + ws.rQlen() + ") " + ws.rQslice(0)); + switch (rfb_state) { + + case 'ProtocolVersion' : + if (ws.rQlen() < 12) { + return fail("Incomplete protocol version"); + } + sversion = ws.rQshiftStr(12).substr(4,7); + Util.Info("Server ProtocolVersion: " + sversion); + switch (sversion) { + case "003.003": rfb_version = 3.3; break; + case "003.006": rfb_version = 3.3; break; // UltraVNC + case "003.007": rfb_version = 3.7; break; + case "003.008": rfb_version = 3.8; break; + default: + return fail("Invalid server version " + sversion); + } + if (rfb_version > rfb_max_version) { + rfb_version = rfb_max_version; + } + + if (! test_mode) { + sendTimer = setInterval(function() { + // Send updates either at a rate of one update + // every 50ms, or whatever slower rate the network + // can handle. + ws.flush(); + }, 50); + } + + cversion = "00" + parseInt(rfb_version,10) + + ".00" + ((rfb_version * 10) % 10); + ws.send_string("RFB " + cversion + "\n"); + updateState('Security', "Sent ProtocolVersion: " + cversion); + break; + + case 'Security' : + if (rfb_version >= 3.7) { + // Server sends supported list, client decides + num_types = ws.rQshift8(); + if (ws.rQwait("security type", num_types, 1)) { return false; } + if (num_types === 0) { + strlen = ws.rQshift32(); + reason = ws.rQshiftStr(strlen); + return fail("Security failure: " + reason); + } + rfb_auth_scheme = 0; + types = ws.rQshiftBytes(num_types); + Util.Debug("Server security types: " + types); + for (i=0; i < types.length; i+=1) { + if ((types[i] > rfb_auth_scheme) && (types[i] < 3)) { + rfb_auth_scheme = types[i]; + } + } + if (rfb_auth_scheme === 0) { + return fail("Unsupported security types: " + types); + } + + ws.send([rfb_auth_scheme]); + } else { + // Server decides + if (ws.rQwait("security scheme", 4)) { return false; } + rfb_auth_scheme = ws.rQshift32(); + } + updateState('Authentication', + "Authenticating using scheme: " + rfb_auth_scheme); + init_msg(); // Recursive fallthrough (workaround JSLint complaint) + break; + + // Triggered by fallthough, not by server message + case 'Authentication' : + //Util.Debug("Security auth scheme: " + rfb_auth_scheme); + switch (rfb_auth_scheme) { + case 0: // connection failed + if (ws.rQwait("auth reason", 4)) { return false; } + strlen = ws.rQshift32(); + reason = ws.rQshiftStr(strlen); + return fail("Auth failure: " + reason); + case 1: // no authentication + if (rfb_version >= 3.8) { + updateState('SecurityResult'); + return; + } + // Fall through to ClientInitialisation + break; + case 2: // VNC authentication + if (rfb_password.length === 0) { + // Notify via both callbacks since it is kind of + // a RFB state change and a UI interface issue. + updateState('password', "Password Required"); + conf.onPasswordRequired(that); + return; + } + if (ws.rQwait("auth challenge", 16)) { return false; } + challenge = ws.rQshiftBytes(16); + //Util.Debug("Password: " + rfb_password); + //Util.Debug("Challenge: " + challenge + + // " (" + challenge.length + ")"); + response = genDES(rfb_password, challenge); + //Util.Debug("Response: " + response + + // " (" + response.length + ")"); + + //Util.Debug("Sending DES encrypted auth response"); + ws.send(response); + updateState('SecurityResult'); + return; + default: + fail("Unsupported auth scheme: " + rfb_auth_scheme); + return; + } + updateState('ClientInitialisation', "No auth required"); + init_msg(); // Recursive fallthrough (workaround JSLint complaint) + break; + + case 'SecurityResult' : + if (ws.rQwait("VNC auth response ", 4)) { return false; } + switch (ws.rQshift32()) { + case 0: // OK + // Fall through to ClientInitialisation + break; + case 1: // failed + if (rfb_version >= 3.8) { + length = ws.rQshift32(); + if (ws.rQwait("SecurityResult reason", length, 8)) { + return false; + } + reason = ws.rQshiftStr(length); + fail(reason); + } else { + fail("Authentication failed"); + } + return; + case 2: // too-many + return fail("Too many auth attempts"); + } + updateState('ClientInitialisation', "Authentication OK"); + init_msg(); // Recursive fallthrough (workaround JSLint complaint) + break; + + // Triggered by fallthough, not by server message + case 'ClientInitialisation' : + ws.send([conf.shared ? 1 : 0]); // ClientInitialisation + updateState('ServerInitialisation', "Authentication OK"); + break; + + case 'ServerInitialisation' : + if (ws.rQwait("server initialization", 24)) { return false; } + + /* Screen size */ + fb_width = ws.rQshift16(); + fb_height = ws.rQshift16(); + + /* PIXEL_FORMAT */ + bpp = ws.rQshift8(); + depth = ws.rQshift8(); + big_endian = ws.rQshift8(); + true_color = ws.rQshift8(); + + red_max = ws.rQshift16(); + green_max = ws.rQshift16(); + blue_max = ws.rQshift16(); + red_shift = ws.rQshift8(); + green_shift = ws.rQshift8(); + blue_shift = ws.rQshift8(); + ws.rQshiftStr(3); // padding + + Util.Info("Screen: " + fb_width + "x" + fb_height + + ", bpp: " + bpp + ", depth: " + depth + + ", big_endian: " + big_endian + + ", true_color: " + true_color + + ", red_max: " + red_max + + ", green_max: " + green_max + + ", blue_max: " + blue_max + + ", red_shift: " + red_shift + + ", green_shift: " + green_shift + + ", blue_shift: " + blue_shift); + + /* Connection name/title */ + name_length = ws.rQshift32(); + fb_name = ws.rQshiftStr(name_length); + + display.set_true_color(conf.true_color); + display.resize(fb_width, fb_height); + keyboard.grab(); + mouse.grab(); + + if (conf.true_color) { + fb_Bpp = 4; + fb_depth = 3; + } else { + fb_Bpp = 1; + fb_depth = 1; + } + + response = pixelFormat(); + response = response.concat(clientEncodings()); + response = response.concat(fbUpdateRequests()); + timing.fbu_rt_start = (new Date()).getTime(); + ws.send(response); + + /* Start pushing/polling */ + setTimeout(checkEvents, conf.check_rate); + setTimeout(scan_tight_imgQ, scan_imgQ_rate); + + if (conf.encrypt) { + updateState('normal', "Connected (encrypted) to: " + fb_name); + } else { + updateState('normal', "Connected (unencrypted) to: " + fb_name); + } + break; + } + //Util.Debug("<< init_msg"); +}; + + +/* Normal RFB/VNC server message handler */ +normal_msg = function() { + //Util.Debug(">> normal_msg"); + + var ret = true, msg_type, length, text, + c, first_colour, num_colours, red, green, blue; + + if (FBU.rects > 0) { + msg_type = 0; + } else { + msg_type = ws.rQshift8(); + } + switch (msg_type) { + case 0: // FramebufferUpdate + ret = framebufferUpdate(); // false means need more data + break; + case 1: // SetColourMapEntries + Util.Debug("SetColourMapEntries"); + ws.rQshift8(); // Padding + first_colour = ws.rQshift16(); // First colour + num_colours = ws.rQshift16(); + for (c=0; c < num_colours; c+=1) { + red = ws.rQshift16(); + //Util.Debug("red before: " + red); + red = parseInt(red / 256, 10); + //Util.Debug("red after: " + red); + green = parseInt(ws.rQshift16() / 256, 10); + blue = parseInt(ws.rQshift16() / 256, 10); + display.set_colourMap([red, green, blue], first_colour + c); + } + Util.Debug("colourMap: " + display.get_colourMap()); + Util.Info("Registered " + num_colours + " colourMap entries"); + //Util.Debug("colourMap: " + display.get_colourMap()); + break; + case 2: // Bell + Util.Debug("Bell"); + conf.onBell(that); + break; + case 3: // ServerCutText + Util.Debug("ServerCutText"); + if (ws.rQwait("ServerCutText header", 7, 1)) { return false; } + ws.rQshiftBytes(3); // Padding + length = ws.rQshift32(); + if (ws.rQwait("ServerCutText", length, 8)) { return false; } + + text = ws.rQshiftStr(length); + conf.clipboardReceive(that, text); // Obsolete + conf.onClipboard(that, text); + break; + default: + fail("Disconnected: illegal server message type " + msg_type); + Util.Debug("ws.rQslice(0,30):" + ws.rQslice(0,30)); + break; + } + //Util.Debug("<< normal_msg"); + return ret; +}; + +framebufferUpdate = function() { + var now, hdr, fbu_rt_diff, ret = true; + + if (FBU.rects === 0) { + //Util.Debug("New FBU: ws.rQslice(0,20): " + ws.rQslice(0,20)); + if (ws.rQwait("FBU header", 3)) { + ws.rQunshift8(0); // FBU msg_type + return false; + } + ws.rQshift8(); // padding + FBU.rects = ws.rQshift16(); + //Util.Debug("FramebufferUpdate, rects:" + FBU.rects); + FBU.bytes = 0; + timing.cur_fbu = 0; + if (timing.fbu_rt_start > 0) { + now = (new Date()).getTime(); + Util.Info("First FBU latency: " + (now - timing.fbu_rt_start)); + } + } + + while (FBU.rects > 0) { + if (rfb_state !== "normal") { + return false; + } + if (ws.rQwait("FBU", FBU.bytes)) { return false; } + if (FBU.bytes === 0) { + if (ws.rQwait("rect header", 12)) { return false; } + /* New FramebufferUpdate */ + + hdr = ws.rQshiftBytes(12); + FBU.x = (hdr[0] << 8) + hdr[1]; + FBU.y = (hdr[2] << 8) + hdr[3]; + FBU.width = (hdr[4] << 8) + hdr[5]; + FBU.height = (hdr[6] << 8) + hdr[7]; + FBU.encoding = parseInt((hdr[8] << 24) + (hdr[9] << 16) + + (hdr[10] << 8) + hdr[11], 10); + + conf.onFBUReceive(that, + {'x': FBU.x, 'y': FBU.y, + 'width': FBU.width, 'height': FBU.height, + 'encoding': FBU.encoding, + 'encodingName': encNames[FBU.encoding]}); + + if (encNames[FBU.encoding]) { + // Debug: + /* + var msg = "FramebufferUpdate rects:" + FBU.rects; + msg += " x: " + FBU.x + " y: " + FBU.y; + msg += " width: " + FBU.width + " height: " + FBU.height; + msg += " encoding:" + FBU.encoding; + msg += "(" + encNames[FBU.encoding] + ")"; + msg += ", ws.rQlen(): " + ws.rQlen(); + Util.Debug(msg); + */ + } else { + fail("Disconnected: unsupported encoding " + + FBU.encoding); + return false; + } + } + + timing.last_fbu = (new Date()).getTime(); + + ret = encHandlers[FBU.encoding](); + + now = (new Date()).getTime(); + timing.cur_fbu += (now - timing.last_fbu); + + if (ret) { + encStats[FBU.encoding][0] += 1; + encStats[FBU.encoding][1] += 1; + } + + if (FBU.rects === 0) { + if (((FBU.width === fb_width) && + (FBU.height === fb_height)) || + (timing.fbu_rt_start > 0)) { + timing.full_fbu_total += timing.cur_fbu; + timing.full_fbu_cnt += 1; + Util.Info("Timing of full FBU, cur: " + + timing.cur_fbu + ", total: " + + timing.full_fbu_total + ", cnt: " + + timing.full_fbu_cnt + ", avg: " + + (timing.full_fbu_total / + timing.full_fbu_cnt)); + } + if (timing.fbu_rt_start > 0) { + fbu_rt_diff = now - timing.fbu_rt_start; + timing.fbu_rt_total += fbu_rt_diff; + timing.fbu_rt_cnt += 1; + Util.Info("full FBU round-trip, cur: " + + fbu_rt_diff + ", total: " + + timing.fbu_rt_total + ", cnt: " + + timing.fbu_rt_cnt + ", avg: " + + (timing.fbu_rt_total / + timing.fbu_rt_cnt)); + timing.fbu_rt_start = 0; + } + } + if (! ret) { + return ret; // false ret means need more data + } + } + + conf.onFBUComplete(that, + {'x': FBU.x, 'y': FBU.y, + 'width': FBU.width, 'height': FBU.height, + 'encoding': FBU.encoding, + 'encodingName': encNames[FBU.encoding]}); + + return true; // We finished this FBU +}; + +// +// FramebufferUpdate encodings +// + +encHandlers.RAW = function display_raw() { + //Util.Debug(">> display_raw (" + ws.rQlen() + " bytes)"); + + var cur_y, cur_height; + + if (FBU.lines === 0) { + FBU.lines = FBU.height; + } + FBU.bytes = FBU.width * fb_Bpp; // At least a line + if (ws.rQwait("RAW", FBU.bytes)) { return false; } + cur_y = FBU.y + (FBU.height - FBU.lines); + cur_height = Math.min(FBU.lines, + Math.floor(ws.rQlen()/(FBU.width * fb_Bpp))); + display.blitImage(FBU.x, cur_y, FBU.width, cur_height, + ws.get_rQ(), ws.get_rQi()); + ws.rQshiftBytes(FBU.width * cur_height * fb_Bpp); + FBU.lines -= cur_height; + + if (FBU.lines > 0) { + FBU.bytes = FBU.width * fb_Bpp; // At least another line + } else { + FBU.rects -= 1; + FBU.bytes = 0; + } + //Util.Debug("<< display_raw (" + ws.rQlen() + " bytes)"); + return true; +}; + +encHandlers.COPYRECT = function display_copy_rect() { + //Util.Debug(">> display_copy_rect"); + + var old_x, old_y; + + if (ws.rQwait("COPYRECT", 4)) { return false; } + old_x = ws.rQshift16(); + old_y = ws.rQshift16(); + display.copyImage(old_x, old_y, FBU.x, FBU.y, FBU.width, FBU.height); + FBU.rects -= 1; + FBU.bytes = 0; + return true; +}; + +encHandlers.RRE = function display_rre() { + //Util.Debug(">> display_rre (" + ws.rQlen() + " bytes)"); + var color, x, y, width, height, chunk; + + if (FBU.subrects === 0) { + if (ws.rQwait("RRE", 4+fb_Bpp)) { return false; } + FBU.subrects = ws.rQshift32(); + color = ws.rQshiftBytes(fb_Bpp); // Background + display.fillRect(FBU.x, FBU.y, FBU.width, FBU.height, color); + } + while ((FBU.subrects > 0) && (ws.rQlen() >= (fb_Bpp + 8))) { + color = ws.rQshiftBytes(fb_Bpp); + x = ws.rQshift16(); + y = ws.rQshift16(); + width = ws.rQshift16(); + height = ws.rQshift16(); + display.fillRect(FBU.x + x, FBU.y + y, width, height, color); + FBU.subrects -= 1; + } + //Util.Debug(" display_rre: rects: " + FBU.rects + + // ", FBU.subrects: " + FBU.subrects); + + if (FBU.subrects > 0) { + chunk = Math.min(rre_chunk_sz, FBU.subrects); + FBU.bytes = (fb_Bpp + 8) * chunk; + } else { + FBU.rects -= 1; + FBU.bytes = 0; + } + //Util.Debug("<< display_rre, FBU.bytes: " + FBU.bytes); + return true; +}; + +encHandlers.HEXTILE = function display_hextile() { + //Util.Debug(">> display_hextile"); + var subencoding, subrects, color, cur_tile, + tile_x, x, w, tile_y, y, h, xy, s, sx, sy, wh, sw, sh, + rQ = ws.get_rQ(), rQi = ws.get_rQi(); + + if (FBU.tiles === 0) { + FBU.tiles_x = Math.ceil(FBU.width/16); + FBU.tiles_y = Math.ceil(FBU.height/16); + FBU.total_tiles = FBU.tiles_x * FBU.tiles_y; + FBU.tiles = FBU.total_tiles; + } + + /* FBU.bytes comes in as 1, ws.rQlen() at least 1 */ + while (FBU.tiles > 0) { + FBU.bytes = 1; + if (ws.rQwait("HEXTILE subencoding", FBU.bytes)) { return false; } + subencoding = rQ[rQi]; // Peek + if (subencoding > 30) { // Raw + fail("Disconnected: illegal hextile subencoding " + subencoding); + //Util.Debug("ws.rQslice(0,30):" + ws.rQslice(0,30)); + return false; + } + subrects = 0; + cur_tile = FBU.total_tiles - FBU.tiles; + tile_x = cur_tile % FBU.tiles_x; + tile_y = Math.floor(cur_tile / FBU.tiles_x); + x = FBU.x + tile_x * 16; + y = FBU.y + tile_y * 16; + w = Math.min(16, (FBU.x + FBU.width) - x); + h = Math.min(16, (FBU.y + FBU.height) - y); + + /* Figure out how much we are expecting */ + if (subencoding & 0x01) { // Raw + //Util.Debug(" Raw subencoding"); + FBU.bytes += w * h * fb_Bpp; + } else { + if (subencoding & 0x02) { // Background + FBU.bytes += fb_Bpp; + } + if (subencoding & 0x04) { // Foreground + FBU.bytes += fb_Bpp; + } + if (subencoding & 0x08) { // AnySubrects + FBU.bytes += 1; // Since we aren't shifting it off + if (ws.rQwait("hextile subrects header", FBU.bytes)) { return false; } + subrects = rQ[rQi + FBU.bytes-1]; // Peek + if (subencoding & 0x10) { // SubrectsColoured + FBU.bytes += subrects * (fb_Bpp + 2); + } else { + FBU.bytes += subrects * 2; + } + } + } + + /* + Util.Debug(" tile:" + cur_tile + "/" + (FBU.total_tiles - 1) + + " (" + tile_x + "," + tile_y + ")" + + " [" + x + "," + y + "]@" + w + "x" + h + + ", subenc:" + subencoding + + "(last: " + FBU.lastsubencoding + "), subrects:" + + subrects + + ", ws.rQlen():" + ws.rQlen() + ", FBU.bytes:" + FBU.bytes + + " last:" + ws.rQslice(FBU.bytes-10, FBU.bytes) + + " next:" + ws.rQslice(FBU.bytes-1, FBU.bytes+10)); + */ + if (ws.rQwait("hextile", FBU.bytes)) { return false; } + + /* We know the encoding and have a whole tile */ + FBU.subencoding = rQ[rQi]; + rQi += 1; + if (FBU.subencoding === 0) { + if (FBU.lastsubencoding & 0x01) { + /* Weird: ignore blanks after RAW */ + Util.Debug(" Ignoring blank after RAW"); + } else { + display.fillRect(x, y, w, h, FBU.background); + } + } else if (FBU.subencoding & 0x01) { // Raw + display.blitImage(x, y, w, h, rQ, rQi); + rQi += FBU.bytes - 1; + } else { + if (FBU.subencoding & 0x02) { // Background + FBU.background = rQ.slice(rQi, rQi + fb_Bpp); + rQi += fb_Bpp; + } + if (FBU.subencoding & 0x04) { // Foreground + FBU.foreground = rQ.slice(rQi, rQi + fb_Bpp); + rQi += fb_Bpp; + } + + display.startTile(x, y, w, h, FBU.background); + if (FBU.subencoding & 0x08) { // AnySubrects + subrects = rQ[rQi]; + rQi += 1; + for (s = 0; s < subrects; s += 1) { + if (FBU.subencoding & 0x10) { // SubrectsColoured + color = rQ.slice(rQi, rQi + fb_Bpp); + rQi += fb_Bpp; + } else { + color = FBU.foreground; + } + xy = rQ[rQi]; + rQi += 1; + sx = (xy >> 4); + sy = (xy & 0x0f); + + wh = rQ[rQi]; + rQi += 1; + sw = (wh >> 4) + 1; + sh = (wh & 0x0f) + 1; + + display.subTile(sx, sy, sw, sh, color); + } + } + display.finishTile(); + } + ws.set_rQi(rQi); + FBU.lastsubencoding = FBU.subencoding; + FBU.bytes = 0; + FBU.tiles -= 1; + } + + if (FBU.tiles === 0) { + FBU.rects -= 1; + } + + //Util.Debug("<< display_hextile"); + return true; +}; + + +encHandlers.TIGHT_PNG = function display_tight_png() { + //Util.Debug(">> display_tight_png"); + var ctl, cmode, clength, getCLength, color, img; + //Util.Debug(" FBU.rects: " + FBU.rects); + //Util.Debug(" starting ws.rQslice(0,20): " + ws.rQslice(0,20) + " (" + ws.rQlen() + ")"); + + FBU.bytes = 1; // compression-control byte + if (ws.rQwait("TIGHT compression-control", FBU.bytes)) { return false; } + + // Get 'compact length' header and data size + getCLength = function (arr) { + var header = 1, data = 0; + data += arr[0] & 0x7f; + if (arr[0] & 0x80) { + header += 1; + data += (arr[1] & 0x7f) << 7; + if (arr[1] & 0x80) { + header += 1; + data += arr[2] << 14; + } + } + return [header, data]; + }; + + ctl = ws.rQpeek8(); + switch (ctl >> 4) { + case 0x08: cmode = "fill"; break; + case 0x09: cmode = "jpeg"; break; + case 0x0A: cmode = "png"; break; + default: throw("Illegal basic compression received, ctl: " + ctl); + } + switch (cmode) { + // fill uses fb_depth because TPIXELs drop the padding byte + case "fill": FBU.bytes += fb_depth; break; // TPIXEL + case "jpeg": FBU.bytes += 3; break; // max clength + case "png": FBU.bytes += 3; break; // max clength + } + + if (ws.rQwait("TIGHT " + cmode, FBU.bytes)) { return false; } + + //Util.Debug(" ws.rQslice(0,20): " + ws.rQslice(0,20) + " (" + ws.rQlen() + ")"); + //Util.Debug(" cmode: " + cmode); + + // Determine FBU.bytes + switch (cmode) { + case "fill": + ws.rQshift8(); // shift off ctl + color = ws.rQshiftBytes(fb_depth); + FBU.imgQ.push({ + 'type': 'fill', + 'img': {'complete': true}, + 'x': FBU.x, + 'y': FBU.y, + 'width': FBU.width, + 'height': FBU.height, + 'color': color}); + break; + case "jpeg": + case "png": + clength = getCLength(ws.rQslice(1, 4)); + FBU.bytes = 1 + clength[0] + clength[1]; // ctl + clength size + jpeg-data + if (ws.rQwait("TIGHT " + cmode, FBU.bytes)) { return false; } + + // We have everything, render it + //Util.Debug(" png, ws.rQlen(): " + ws.rQlen() + ", clength[0]: " + clength[0] + ", clength[1]: " + clength[1]); + ws.rQshiftBytes(1 + clength[0]); // shift off ctl + compact length + img = new Image(); + //img.onload = scan_tight_imgQ; + FBU.imgQ.push({ + 'type': 'img', + 'img': img, + 'x': FBU.x, + 'y': FBU.y}); + img.src = "data:image/" + cmode + + extract_data_uri(ws.rQshiftBytes(clength[1])); + img = null; + break; + } + FBU.bytes = 0; + FBU.rects -= 1; + //Util.Debug(" ending ws.rQslice(0,20): " + ws.rQslice(0,20) + " (" + ws.rQlen() + ")"); + //Util.Debug("<< display_tight_png"); + return true; +}; + +extract_data_uri = function(arr) { + //var i, stra = []; + //for (i=0; i< arr.length; i += 1) { + // stra.push(String.fromCharCode(arr[i])); + //} + //return "," + escape(stra.join('')); + return ";base64," + Base64.encode(arr); +}; + +scan_tight_imgQ = function() { + var data, imgQ, ctx; + ctx = display.get_context(); + if (rfb_state === 'normal') { + imgQ = FBU.imgQ; + while ((imgQ.length > 0) && (imgQ[0].img.complete)) { + data = imgQ.shift(); + if (data['type'] === 'fill') { + display.fillRect(data.x, data.y, data.width, data.height, data.color); + } else { + ctx.drawImage(data.img, data.x, data.y); + } + } + setTimeout(scan_tight_imgQ, scan_imgQ_rate); + } +}; + +encHandlers.DesktopSize = function set_desktopsize() { + Util.Debug(">> set_desktopsize"); + fb_width = FBU.width; + fb_height = FBU.height; + display.resize(fb_width, fb_height); + timing.fbu_rt_start = (new Date()).getTime(); + // Send a new non-incremental request + ws.send(fbUpdateRequests()); + + FBU.bytes = 0; + FBU.rects -= 1; + + Util.Debug("<< set_desktopsize"); + return true; +}; + +encHandlers.Cursor = function set_cursor() { + var x, y, w, h, pixelslength, masklength; + //Util.Debug(">> set_cursor"); + x = FBU.x; // hotspot-x + y = FBU.y; // hotspot-y + w = FBU.width; + h = FBU.height; + + pixelslength = w * h * fb_Bpp; + masklength = Math.floor((w + 7) / 8) * h; + + FBU.bytes = pixelslength + masklength; + if (ws.rQwait("cursor encoding", FBU.bytes)) { return false; } + + //Util.Debug(" set_cursor, x: " + x + ", y: " + y + ", w: " + w + ", h: " + h); + + display.changeCursor(ws.rQshiftBytes(pixelslength), + ws.rQshiftBytes(masklength), + x, y, w, h); + + FBU.bytes = 0; + FBU.rects -= 1; + + //Util.Debug("<< set_cursor"); + return true; +}; + +encHandlers.JPEG_quality_lo = function set_jpeg_quality() { + Util.Error("Server sent jpeg_quality pseudo-encoding"); +}; + +encHandlers.compress_lo = function set_compress_level() { + Util.Error("Server sent compress level pseudo-encoding"); +}; + +/* + * Client message routines + */ + +pixelFormat = function() { + //Util.Debug(">> pixelFormat"); + var arr; + arr = [0]; // msg-type + arr.push8(0); // padding + arr.push8(0); // padding + arr.push8(0); // padding + + arr.push8(fb_Bpp * 8); // bits-per-pixel + arr.push8(fb_depth * 8); // depth + arr.push8(0); // little-endian + arr.push8(conf.true_color ? 1 : 0); // true-color + + arr.push16(255); // red-max + arr.push16(255); // green-max + arr.push16(255); // blue-max + arr.push8(0); // red-shift + arr.push8(8); // green-shift + arr.push8(16); // blue-shift + + arr.push8(0); // padding + arr.push8(0); // padding + arr.push8(0); // padding + //Util.Debug("<< pixelFormat"); + return arr; +}; + +clientEncodings = function() { + //Util.Debug(">> clientEncodings"); + var arr, i, encList = []; + + for (i=0; i> fbUpdateRequest"); + if (typeof(x) === "undefined") { x = 0; } + if (typeof(y) === "undefined") { y = 0; } + if (typeof(xw) === "undefined") { xw = fb_width; } + if (typeof(yw) === "undefined") { yw = fb_height; } + var arr; + arr = [3]; // msg-type + arr.push8(incremental); + arr.push16(x); + arr.push16(y); + arr.push16(xw); + arr.push16(yw); + //Util.Debug("<< fbUpdateRequest"); + return arr; +}; + +// Based on clean/dirty areas, generate requests to send +fbUpdateRequests = function() { + var cleanDirty = display.getCleanDirtyReset(), + arr = [], i, cb, db; + + cb = cleanDirty.cleanBox; + if (cb.w > 0 && cb.h > 0) { + // Request incremental for clean box + arr = arr.concat(fbUpdateRequest(1, cb.x, cb.y, cb.w, cb.h)); + } + for (i = 0; i < cleanDirty.dirtyBoxes.length; i++) { + db = cleanDirty.dirtyBoxes[i]; + // Force all (non-incremental for dirty box + arr = arr.concat(fbUpdateRequest(0, db.x, db.y, db.w, db.h)); + } + return arr; +}; + + + +keyEvent = function(keysym, down) { + //Util.Debug(">> keyEvent, keysym: " + keysym + ", down: " + down); + var arr; + arr = [4]; // msg-type + arr.push8(down); + arr.push16(0); + arr.push32(keysym); + //Util.Debug("<< keyEvent"); + return arr; +}; + +pointerEvent = function(x, y) { + //Util.Debug(">> pointerEvent, x,y: " + x + "," + y + + // " , mask: " + mouse_buttonMask); + var arr; + arr = [5]; // msg-type + arr.push8(mouse_buttonMask); + arr.push16(x); + arr.push16(y); + //Util.Debug("<< pointerEvent"); + return arr; +}; + +clientCutText = function(text) { + //Util.Debug(">> clientCutText"); + var arr, i, n; + arr = [6]; // msg-type + arr.push8(0); // padding + arr.push8(0); // padding + arr.push8(0); // padding + arr.push32(text.length); + n = text.length; + for (i=0; i < n; i+=1) { + arr.push(text.charCodeAt(i)); + } + //Util.Debug("<< clientCutText:" + arr); + return arr; +}; + + + +// +// Public API interface functions +// + +that.connect = function(host, port, password, path) { + //Util.Debug(">> connect"); + + rfb_host = host; + rfb_port = port; + rfb_password = (password !== undefined) ? password : ""; + rfb_path = (path !== undefined) ? path : ""; + + if ((!rfb_host) || (!rfb_port)) { + return fail("Must set host and port"); + } + + updateState('connect'); + //Util.Debug("<< connect"); + +}; + +that.disconnect = function() { + //Util.Debug(">> disconnect"); + updateState('disconnect', 'Disconnecting'); + //Util.Debug("<< disconnect"); +}; + +that.sendPassword = function(passwd) { + rfb_password = passwd; + rfb_state = "Authentication"; + setTimeout(init_msg, 1); +}; + +that.sendCtrlAltDel = function() { + if (rfb_state !== "normal") { return false; } + Util.Info("Sending Ctrl-Alt-Del"); + var arr = []; + arr = arr.concat(keyEvent(0xFFE3, 1)); // Control + arr = arr.concat(keyEvent(0xFFE9, 1)); // Alt + arr = arr.concat(keyEvent(0xFFFF, 1)); // Delete + arr = arr.concat(keyEvent(0xFFFF, 0)); // Delete + arr = arr.concat(keyEvent(0xFFE9, 0)); // Alt + arr = arr.concat(keyEvent(0xFFE3, 0)); // Control + arr = arr.concat(fbUpdateRequests()); + ws.send(arr); +}; + +// Send a key press. If 'down' is not specified then send a down key +// followed by an up key. +that.sendKey = function(code, down) { + if (rfb_state !== "normal") { return false; } + var arr = []; + if (typeof down !== 'undefined') { + Util.Info("Sending key code (" + (down ? "down" : "up") + "): " + code); + arr = arr.concat(keyEvent(code, down ? 1 : 0)); + } else { + Util.Info("Sending key code (down + up): " + code); + arr = arr.concat(keyEvent(code, 1)); + arr = arr.concat(keyEvent(code, 0)); + } + arr = arr.concat(fbUpdateRequests()); + ws.send(arr); +}; + +that.clipboardPasteFrom = function(text) { + if (rfb_state !== "normal") { return; } + //Util.Debug(">> clipboardPasteFrom: " + text.substr(0,40) + "..."); + ws.send(clientCutText(text)); + //Util.Debug("<< clipboardPasteFrom"); +}; + +// Override internal functions for testing +that.testMode = function(override_send) { + test_mode = true; + that.recv_message = ws.testMode(override_send); + + checkEvents = function () { /* Stub Out */ }; + that.connect = function(host, port, password) { + rfb_host = host; + rfb_port = port; + rfb_password = password; + updateState('ProtocolVersion', "Starting VNC handshake"); + }; +}; + + +return constructor(); // Return the public API interface + +} // End of RFB() diff --git a/classes/novnc/include/ui.js b/classes/novnc/include/ui.js new file mode 100644 index 0000000..74a0005 --- /dev/null +++ b/classes/novnc/include/ui.js @@ -0,0 +1,629 @@ +/* + * noVNC: HTML5 VNC client + * Copyright (C) 2011 Joel Martin + * Licensed under LGPL-3 (see LICENSE.txt) + * + * See README.md for usage and integration instructions. + */ + +"use strict"; +/*jslint white: false, browser: true */ +/*global window, $D, Util, WebUtil, RFB, Display */ + +var UI = { + +rfb_state : 'loaded', +settingsOpen : false, +connSettingsOpen : true, +clipboardOpen: false, +keyboardVisible: false, + +// Render default UI and initialize settings menu +load: function() { + var html = '', i, sheet, sheets, llevels; + + // Stylesheet selection dropdown + sheet = WebUtil.selectStylesheet(); + sheets = WebUtil.getStylesheets(); + for (i = 0; i < sheets.length; i += 1) { + UI.addOption($D('noVNC_stylesheet'),sheets[i].title, sheets[i].title); + } + + // Logging selection dropdown + llevels = ['error', 'warn', 'info', 'debug']; + for (i = 0; i < llevels.length; i += 1) { + UI.addOption($D('noVNC_logging'),llevels[i], llevels[i]); + } + + // Settings with immediate effects + UI.initSetting('logging', 'warn'); + WebUtil.init_logging(UI.getSetting('logging')); + + UI.initSetting('stylesheet', 'default'); + WebUtil.selectStylesheet(null); + // call twice to get around webkit bug + WebUtil.selectStylesheet(UI.getSetting('stylesheet')); + + /* Populate the controls if defaults are provided in the URL */ + UI.initSetting('host', ''); + UI.initSetting('port', ''); + UI.initSetting('password', ''); + UI.initSetting('encrypt', false); + UI.initSetting('true_color', true); + UI.initSetting('cursor', false); + UI.initSetting('shared', true); + UI.initSetting('connectTimeout', 2); + UI.initSetting('path', ''); + + UI.rfb = RFB({'target': $D('noVNC_canvas'), + 'onUpdateState': UI.updateState, + 'onClipboard': UI.clipReceive}); + UI.updateVisualState(); + + // Unfocus clipboard when over the VNC area + //$D('VNC_screen').onmousemove = function () { + // var keyboard = UI.rfb.get_keyboard(); + // if ((! keyboard) || (! keyboard.get_focused())) { + // $D('VNC_clipboard_text').blur(); + // } + // }; + + // Show mouse selector buttons on touch screen devices + if ('ontouchstart' in document.documentElement) { + // Show mobile buttons + $D('noVNC_mobile_buttons').style.display = "inline"; + UI.setMouseButton(); + // Remove the address bar + setTimeout(function() { window.scrollTo(0, 1); }, 100); + UI.forceSetting('clip', true); + $D('noVNC_clip').disabled = true; + } else { + UI.initSetting('clip', false); + } + + //iOS Safari does not support CSS position:fixed. + //This detects iOS devices and enables javascript workaround. + if ((navigator.userAgent.match(/iPhone/i)) || + (navigator.userAgent.match(/iPod/i)) || + (navigator.userAgent.match(/iPad/i))) { + //UI.setOnscroll(); + //UI.setResize(); + } + + $D('noVNC_host').focus(); + + UI.setViewClip(); + Util.addEvent(window, 'resize', UI.setViewClip); + + Util.addEvent(window, 'beforeunload', function () { + if (UI.rfb_state === 'normal') { + return "You are currently connected."; + } + } ); + +}, + +// Read form control compatible setting from cookie +getSetting: function(name) { + var val, ctrl = $D('noVNC_' + name); + val = WebUtil.readCookie(name); + if (ctrl.type === 'checkbox') { + if (val.toLowerCase() in {'0':1, 'no':1, 'false':1}) { + val = false; + } else { + val = true; + } + } + return val; +}, + +// Update cookie and form control setting. If value is not set, then +// updates from control to current cookie setting. +updateSetting: function(name, value) { + + var i, ctrl = $D('noVNC_' + name); + // Save the cookie for this session + if (typeof value !== 'undefined') { + WebUtil.createCookie(name, value); + } + + // Update the settings control + value = UI.getSetting(name); + + if (ctrl.type === 'checkbox') { + ctrl.checked = value; + + } else if (typeof ctrl.options !== 'undefined') { + for (i = 0; i < ctrl.options.length; i += 1) { + if (ctrl.options[i].value === value) { + ctrl.selectedIndex = i; + break; + } + } + } else { + /*Weird IE9 error leads to 'null' appearring + in textboxes instead of ''.*/ + if (value === null) { + value = ""; + } + ctrl.value = value; + } +}, + +// Save control setting to cookie +saveSetting: function(name) { + var val, ctrl = $D('noVNC_' + name); + if (ctrl.type === 'checkbox') { + val = ctrl.checked; + } else if (typeof ctrl.options !== 'undefined') { + val = ctrl.options[ctrl.selectedIndex].value; + } else { + val = ctrl.value; + } + WebUtil.createCookie(name, val); + //Util.Debug("Setting saved '" + name + "=" + val + "'"); + return val; +}, + +// Initial page load read/initialization of settings +initSetting: function(name, defVal) { + var val; + + // Check Query string followed by cookie + val = WebUtil.getQueryVar(name); + if (val === null) { + val = WebUtil.readCookie(name, defVal); + } + UI.updateSetting(name, val); + //Util.Debug("Setting '" + name + "' initialized to '" + val + "'"); + return val; +}, + +// Force a setting to be a certain value +forceSetting: function(name, val) { + UI.updateSetting(name, val); + return val; +}, + + +// Show the clipboard panel +toggleClipboardPanel: function() { + //Close settings if open + if (UI.settingsOpen == true) { + UI.settingsApply(); + UI.closeSettingsMenu(); + } + //Close connection settings if open + if (UI.connSettingsOpen == true) { + UI.toggleConnectPanel(); + } + //Toggle Clipboard Panel + if (UI.clipboardOpen == true) { + $D('noVNC_clipboard').style.display = "none"; + $D('clipboardButton').className = "noVNC_status_button"; + UI.clipboardOpen = false; + } else { + $D('noVNC_clipboard').style.display = "block"; + $D('clipboardButton').className = "noVNC_status_button_selected"; + UI.clipboardOpen = true; + } +}, + +// Show the connection settings panel/menu +toggleConnectPanel: function() { + //Close connection settings if open + if (UI.settingsOpen == true) { + UI.settingsApply(); + UI.closeSettingsMenu(); + $D('connectButton').className = "noVNC_status_button"; + } + if (UI.clipboardOpen == true) { + UI.toggleClipboardPanel(); + } + + //Toggle Connection Panel + if (UI.connSettingsOpen == true) { + $D('noVNC_controls').style.display = "none"; + $D('connectButton').className = "noVNC_status_button"; + UI.connSettingsOpen = false; + } else { + $D('noVNC_controls').style.display = "block"; + $D('connectButton').className = "noVNC_status_button_selected"; + UI.connSettingsOpen = true; + $D('noVNC_host').focus(); + } +}, + +// Toggle the settings menu: +// On open, settings are refreshed from saved cookies. +// On close, settings are applied +toggleSettingsPanel: function() { + if (UI.settingsOpen) { + UI.settingsApply(); + UI.closeSettingsMenu(); + } else { + UI.updateSetting('encrypt'); + UI.updateSetting('true_color'); + if (UI.rfb.get_display().get_cursor_uri()) { + UI.updateSetting('cursor'); + } else { + UI.updateSetting('cursor', false); + $D('noVNC_cursor').disabled = true; + } + UI.updateSetting('clip'); + UI.updateSetting('shared'); + UI.updateSetting('connectTimeout'); + UI.updateSetting('path'); + UI.updateSetting('stylesheet'); + UI.updateSetting('logging'); + + UI.openSettingsMenu(); + } +}, + +// Open menu +openSettingsMenu: function() { + if (UI.clipboardOpen == true) { + UI.toggleClipboardPanel(); + } + //Close connection settings if open + if (UI.connSettingsOpen == true) { + UI.toggleConnectPanel(); + } + $D('noVNC_settings').style.display = "block"; + $D('settingsButton').className = "noVNC_status_button_selected"; + UI.settingsOpen = true; +}, + +// Close menu (without applying settings) +closeSettingsMenu: function() { + $D('noVNC_settings').style.display = "none"; + $D('settingsButton').className = "noVNC_status_button"; + UI.settingsOpen = false; +}, + +// Save/apply settings when 'Apply' button is pressed +settingsApply: function() { + //Util.Debug(">> settingsApply"); + UI.saveSetting('encrypt'); + UI.saveSetting('true_color'); + if (UI.rfb.get_display().get_cursor_uri()) { + UI.saveSetting('cursor'); + } + UI.saveSetting('clip'); + UI.saveSetting('shared'); + UI.saveSetting('connectTimeout'); + UI.saveSetting('path'); + UI.saveSetting('stylesheet'); + UI.saveSetting('logging'); + + // Settings with immediate (non-connected related) effect + WebUtil.selectStylesheet(UI.getSetting('stylesheet')); + WebUtil.init_logging(UI.getSetting('logging')); + UI.setViewClip(); + UI.setViewDrag(UI.rfb.get_viewportDrag()); + //Util.Debug("<< settingsApply"); +}, + + + +setPassword: function() { + UI.rfb.sendPassword($D('noVNC_password').value); + //Reset connect button. + $D('noVNC_connect_button').value = "Connect"; + $D('noVNC_connect_button').onclick = UI.Connect; + //Hide connection panel. + UI.toggleConnectPanel(); + return false; +}, + +sendCtrlAltDel: function() { + UI.rfb.sendCtrlAltDel(); +}, + +setMouseButton: function(num) { + var b, blist = [0, 1,2,4], button; + + if (typeof num === 'undefined') { + // Disable mouse buttons + num = -1; + } + if (UI.rfb) { + UI.rfb.get_mouse().set_touchButton(num); + } + + for (b = 0; b < blist.length; b++) { + button = $D('noVNC_mouse_button' + blist[b]); + if (blist[b] === num) { + button.style.display = ""; + } else { + button.style.display = "none"; + /* + button.style.backgroundColor = "black"; + button.style.color = "lightgray"; + button.style.backgroundColor = ""; + button.style.color = ""; + */ + } + } +}, + +updateState: function(rfb, state, oldstate, msg) { + var s, sb, c, d, cad, vd, klass; + UI.rfb_state = state; + s = $D('noVNC_status'); + sb = $D('noVNC_status_bar'); + switch (state) { + case 'failed': + case 'fatal': + klass = "noVNC_status_error"; + break; + case 'normal': + klass = "noVNC_status_normal"; + break; + case 'disconnected': + $D('noVNC_logo').style.display = "block"; + case 'loaded': + klass = "noVNC_status_normal"; + break; + case 'password': + UI.toggleConnectPanel(); + + $D('noVNC_connect_button').value = "Send Password"; + $D('noVNC_connect_button').onclick = UI.setPassword; + $D('noVNC_password').focus(); + + klass = "noVNC_status_warn"; + break; + default: + klass = "noVNC_status_warn"; + break; + } + + if (typeof(msg) !== 'undefined') { + s.setAttribute("class", klass); + sb.setAttribute("class", klass); + s.innerHTML = msg; + } + + UI.updateVisualState(); +}, + +// Disable/enable controls depending on connection state +updateVisualState: function() { + var connected = UI.rfb_state === 'normal' ? true : false; + + //Util.Debug(">> updateVisualState"); + $D('noVNC_encrypt').disabled = connected; + $D('noVNC_true_color').disabled = connected; + if (UI.rfb && UI.rfb.get_display() && + UI.rfb.get_display().get_cursor_uri()) { + $D('noVNC_cursor').disabled = connected; + } else { + UI.updateSetting('cursor', false); + $D('noVNC_cursor').disabled = true; + } + $D('noVNC_shared').disabled = connected; + $D('noVNC_connectTimeout').disabled = connected; + $D('noVNC_path').disabled = connected; + + if (connected) { + UI.setViewClip(); + UI.setMouseButton(1); + $D('showKeyboard').style.display = "inline"; + $D('sendCtrlAltDelButton').style.display = "inline"; + } else { + UI.setMouseButton(); + $D('showKeyboard').style.display = "none"; + $D('sendCtrlAltDelButton').style.display = "none"; + } + // State change disables viewport dragging. + // It is enabled (toggled) by direct click on the button + UI.setViewDrag(false); + + switch (UI.rfb_state) { + case 'fatal': + case 'failed': + case 'loaded': + case 'disconnected': + $D('connectButton').style.display = ""; + $D('disconnectButton').style.display = "none"; + break; + default: + $D('connectButton').style.display = "none"; + $D('disconnectButton').style.display = ""; + break; + } + + //Util.Debug("<< updateVisualState"); +}, + + +clipReceive: function(rfb, text) { + Util.Debug(">> UI.clipReceive: " + text.substr(0,40) + "..."); + $D('noVNC_clipboard_text').value = text; + Util.Debug("<< UI.clipReceive"); +}, + + +connect: function() { + var host, port, password, path; + + UI.closeSettingsMenu(); + UI.toggleConnectPanel(); + + host = $D('noVNC_host').value; + port = $D('noVNC_port').value; + password = $D('noVNC_password').value; + path = $D('noVNC_path').value; + if ((!host) || (!port)) { + throw("Must set host and port"); + } + + UI.rfb.set_encrypt(UI.getSetting('encrypt')); + UI.rfb.set_true_color(UI.getSetting('true_color')); + UI.rfb.set_local_cursor(UI.getSetting('cursor')); + UI.rfb.set_shared(UI.getSetting('shared')); + UI.rfb.set_connectTimeout(UI.getSetting('connectTimeout')); + + UI.rfb.connect(host, port, password, path); + //Close dialog. + setTimeout(UI.setBarPosition, 100); + $D('noVNC_logo').style.display = "none"; +}, + +disconnect: function() { + UI.closeSettingsMenu(); + UI.rfb.disconnect(); + + $D('noVNC_logo').style.display = "block"; + UI.connSettingsOpen = false; + UI.toggleConnectPanel(); +}, + +displayBlur: function() { + UI.rfb.get_keyboard().set_focused(false); + UI.rfb.get_mouse().set_focused(false); +}, + +displayFocus: function() { + UI.rfb.get_keyboard().set_focused(true); + UI.rfb.get_mouse().set_focused(true); +}, + +clipClear: function() { + $D('noVNC_clipboard_text').value = ""; + UI.rfb.clipboardPasteFrom(""); +}, + +clipSend: function() { + var text = $D('noVNC_clipboard_text').value; + Util.Debug(">> UI.clipSend: " + text.substr(0,40) + "..."); + UI.rfb.clipboardPasteFrom(text); + Util.Debug("<< UI.clipSend"); +}, + + +// Enable/disable and configure viewport clipping +setViewClip: function(clip) { + var display, cur_clip, pos, new_w, new_h; + + if (UI.rfb) { + display = UI.rfb.get_display(); + } else { + return; + } + + cur_clip = display.get_viewport(); + + if (typeof(clip) !== 'boolean') { + // Use current setting + clip = UI.getSetting('clip'); + } + + if (clip && !cur_clip) { + // Turn clipping on + UI.updateSetting('clip', true); + } else if (!clip && cur_clip) { + // Turn clipping off + UI.updateSetting('clip', false); + display.set_viewport(false); + $D('noVNC_canvas').style.position = 'static'; + display.viewportChange(); + } + if (UI.getSetting('clip')) { + // If clipping, update clipping settings + $D('noVNC_canvas').style.position = 'absolute'; + pos = Util.getPosition($D('noVNC_canvas')); + new_w = window.innerWidth - pos.x; + new_h = window.innerHeight - pos.y; + display.set_viewport(true); + display.viewportChange(0, 0, new_w, new_h); + } +}, + +// Toggle/set/unset the viewport drag/move button +setViewDrag: function(drag) { + var vmb = $D('noVNC_view_drag_button'); + if (!UI.rfb) { return; } + + if (UI.rfb_state === 'normal' && + UI.rfb.get_display().get_viewport()) { + vmb.style.display = "inline"; + } else { + vmb.style.display = "none"; + } + + if (typeof(drag) === "undefined") { + // If not specified, then toggle + drag = !UI.rfb.get_viewportDrag(); + } + if (drag) { + vmb.className = "noVNC_status_button_selected"; + UI.rfb.set_viewportDrag(true); + } else { + vmb.className = "noVNC_status_button"; + UI.rfb.set_viewportDrag(false); + } +}, + +// On touch devices, show the OS keyboard +showKeyboard: function() { + if(UI.keyboardVisible == false) { + $D('keyboardinput').focus(); + UI.keyboardVisible = true; + $D('showKeyboard').className = "noVNC_status_button_selected"; + } else if(UI.keyboardVisible == true) { + $D('keyboardinput').blur(); + $D('showKeyboard').className = "noVNC_status_button"; + UI.keyboardVisible = false; + } +}, + +keyInputBlur: function() { + $D('showKeyboard').className = "noVNC_status_button"; + //Weird bug in iOS if you change keyboardVisible + //here it does not actually occur so next time + //you click keyboard icon it doesnt work. + setTimeout("UI.setKeyboard()",100) +}, + +setKeyboard: function() { + UI.keyboardVisible = false; +}, + +// iOS < Version 5 does not support position fixed. Javascript workaround: +setOnscroll: function() { + window.onscroll = function() { + UI.setBarPosition(); + }; +}, + +setResize: function () { + window.onResize = function() { + UI.setBarPosition(); + }; +}, + +//Helper to add options to dropdown. +addOption: function(selectbox,text,value ) +{ + var optn = document.createElement("OPTION"); + optn.text = text; + optn.value = value; + selectbox.options.add(optn); +}, + +setBarPosition: function() { + $D('noVNC-control-bar').style.top = (window.pageYOffset) + 'px'; + $D('noVNC_mobile_buttons').style.left = (window.pageXOffset) + 'px'; + + var vncwidth = $D('noVNC_screen').style.offsetWidth; + $D('noVNC-control-bar').style.width = vncwidth + 'px'; +} + +}; + + + + diff --git a/classes/novnc/include/util.js b/classes/novnc/include/util.js new file mode 100644 index 0000000..0a9e0e0 --- /dev/null +++ b/classes/novnc/include/util.js @@ -0,0 +1,276 @@ +/* + * noVNC: HTML5 VNC client + * Copyright (C) 2011 Joel Martin + * Licensed under LGPL-3 (see LICENSE.txt) + * + * See README.md for usage and integration instructions. + */ + +"use strict"; +/*jslint bitwise: false, white: false */ +/*global window, console, document, navigator, ActiveXObject */ + +// Globals defined here +var Util = {}; + + +/* + * Make arrays quack + */ + +Array.prototype.push8 = function (num) { + this.push(num & 0xFF); +}; + +Array.prototype.push16 = function (num) { + this.push((num >> 8) & 0xFF, + (num ) & 0xFF ); +}; +Array.prototype.push32 = function (num) { + this.push((num >> 24) & 0xFF, + (num >> 16) & 0xFF, + (num >> 8) & 0xFF, + (num ) & 0xFF ); +}; + +/* + * ------------------------------------------------------ + * Namespaced in Util + * ------------------------------------------------------ + */ + +/* + * Logging/debug routines + */ + +Util._log_level = 'warn'; +Util.init_logging = function (level) { + if (typeof level === 'undefined') { + level = Util._log_level; + } else { + Util._log_level = level; + } + if (typeof window.console === "undefined") { + if (typeof window.opera !== "undefined") { + window.console = { + 'log' : window.opera.postError, + 'warn' : window.opera.postError, + 'error': window.opera.postError }; + } else { + window.console = { + 'log' : function(m) {}, + 'warn' : function(m) {}, + 'error': function(m) {}}; + } + } + + Util.Debug = Util.Info = Util.Warn = Util.Error = function (msg) {}; + switch (level) { + case 'debug': Util.Debug = function (msg) { console.log(msg); }; + case 'info': Util.Info = function (msg) { console.log(msg); }; + case 'warn': Util.Warn = function (msg) { console.warn(msg); }; + case 'error': Util.Error = function (msg) { console.error(msg); }; + case 'none': + break; + default: + throw("invalid logging type '" + level + "'"); + } +}; +Util.get_logging = function () { + return Util._log_level; +}; +// Initialize logging level +Util.init_logging(); + + +// Set configuration default for Crockford style function namespaces +Util.conf_default = function(cfg, api, defaults, v, mode, type, defval, desc) { + var getter, setter; + + // Default getter function + getter = function (idx) { + if ((type in {'arr':1, 'array':1}) && + (typeof idx !== 'undefined')) { + return cfg[v][idx]; + } else { + return cfg[v]; + } + }; + + // Default setter function + setter = function (val, idx) { + if (type in {'boolean':1, 'bool':1}) { + if ((!val) || (val in {'0':1, 'no':1, 'false':1})) { + val = false; + } else { + val = true; + } + } else if (type in {'integer':1, 'int':1}) { + val = parseInt(val, 10); + } else if (type === 'func') { + if (!val) { + val = function () {}; + } + } + if (typeof idx !== 'undefined') { + cfg[v][idx] = val; + } else { + cfg[v] = val; + } + }; + + // Set the description + api[v + '_description'] = desc; + + // Set the getter function + if (typeof api['get_' + v] === 'undefined') { + api['get_' + v] = getter; + } + + // Set the setter function with extra sanity checks + if (typeof api['set_' + v] === 'undefined') { + api['set_' + v] = function (val, idx) { + if (mode in {'RO':1, 'ro':1}) { + throw(v + " is read-only"); + } else if ((mode in {'WO':1, 'wo':1}) && + (typeof cfg[v] !== 'undefined')) { + throw(v + " can only be set once"); + } + setter(val, idx); + }; + } + + // Set the default value + if (typeof defaults[v] !== 'undefined') { + defval = defaults[v]; + } else if ((type in {'arr':1, 'array':1}) && + (! (defval instanceof Array))) { + defval = []; + } + // Coerce existing setting to the right type + //Util.Debug("v: " + v + ", defval: " + defval + ", defaults[v]: " + defaults[v]); + setter(defval); +}; + +// Set group of configuration defaults +Util.conf_defaults = function(cfg, api, defaults, arr) { + var i; + for (i = 0; i < arr.length; i++) { + Util.conf_default(cfg, api, defaults, arr[i][0], arr[i][1], + arr[i][2], arr[i][3], arr[i][4]); + } +} + + +/* + * Cross-browser routines + */ + +// Get DOM element position on page +Util.getPosition = function (obj) { + var x = 0, y = 0; + if (obj.offsetParent) { + do { + x += obj.offsetLeft; + y += obj.offsetTop; + obj = obj.offsetParent; + } while (obj); + } + return {'x': x, 'y': y}; +}; + +// Get mouse event position in DOM element +Util.getEventPosition = function (e, obj, scale) { + var evt, docX, docY, pos; + //if (!e) evt = window.event; + evt = (e ? e : window.event); + evt = (evt.changedTouches ? evt.changedTouches[0] : evt.touches ? evt.touches[0] : evt); + if (evt.pageX || evt.pageY) { + docX = evt.pageX; + docY = evt.pageY; + } else if (evt.clientX || evt.clientY) { + docX = evt.clientX + document.body.scrollLeft + + document.documentElement.scrollLeft; + docY = evt.clientY + document.body.scrollTop + + document.documentElement.scrollTop; + } + pos = Util.getPosition(obj); + if (typeof scale === "undefined") { + scale = 1; + } + return {'x': (docX - pos.x) / scale, 'y': (docY - pos.y) / scale}; +}; + + +// Event registration. Based on: http://www.scottandrew.com/weblog/articles/cbs-events +Util.addEvent = function (obj, evType, fn){ + if (obj.attachEvent){ + var r = obj.attachEvent("on"+evType, fn); + return r; + } else if (obj.addEventListener){ + obj.addEventListener(evType, fn, false); + return true; + } else { + throw("Handler could not be attached"); + } +}; + +Util.removeEvent = function(obj, evType, fn){ + if (obj.detachEvent){ + var r = obj.detachEvent("on"+evType, fn); + return r; + } else if (obj.removeEventListener){ + obj.removeEventListener(evType, fn, false); + return true; + } else { + throw("Handler could not be removed"); + } +}; + +Util.stopEvent = function(e) { + if (e.stopPropagation) { e.stopPropagation(); } + else { e.cancelBubble = true; } + + if (e.preventDefault) { e.preventDefault(); } + else { e.returnValue = false; } +}; + + +// Set browser engine versions. Based on mootools. +Util.Features = {xpath: !!(document.evaluate), air: !!(window.runtime), query: !!(document.querySelector)}; + +Util.Engine = { + 'presto': (function() { + return (!window.opera) ? false : ((arguments.callee.caller) ? 960 : ((document.getElementsByClassName) ? 950 : 925)); }()), + 'trident': (function() { + return (!window.ActiveXObject) ? false : ((window.XMLHttpRequest) ? ((document.querySelectorAll) ? 6 : 5) : 4); }()), + 'webkit': (function() { + try { return (navigator.taintEnabled) ? false : ((Util.Features.xpath) ? ((Util.Features.query) ? 525 : 420) : 419); } catch (e) { return false; } }()), + //'webkit': (function() { + // return ((typeof navigator.taintEnabled !== "unknown") && navigator.taintEnabled) ? false : ((Util.Features.xpath) ? ((Util.Features.query) ? 525 : 420) : 419); }()), + 'gecko': (function() { + return (!document.getBoxObjectFor && window.mozInnerScreenX == null) ? false : ((document.getElementsByClassName) ? 19 : 18); }()) +}; +if (Util.Engine.webkit) { + // Extract actual webkit version if available + Util.Engine.webkit = (function(v) { + var re = new RegExp('WebKit/([0-9\.]*) '); + v = (navigator.userAgent.match(re) || ['', v])[1]; + return parseFloat(v, 10); + })(Util.Engine.webkit); +} + +Util.Flash = (function(){ + var v, version; + try { + v = navigator.plugins['Shockwave Flash'].description; + } catch(err1) { + try { + v = new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version'); + } catch(err2) { + v = '0 r0'; + } + } + version = v.match(/\d+/g); + return {version: parseInt(version[0] || 0 + '.' + version[1], 10) || 0, build: parseInt(version[2], 10) || 0}; +}()); diff --git a/classes/novnc/include/vnc.js b/classes/novnc/include/vnc.js new file mode 100644 index 0000000..f938be7 --- /dev/null +++ b/classes/novnc/include/vnc.js @@ -0,0 +1,42 @@ +/* + * noVNC: HTML5 VNC client + * Copyright (C) 2011 Joel Martin + * Licensed under LGPL-3 (see LICENSE.txt) + * + * See README.md for usage and integration instructions. + */ + +/*jslint evil: true */ +/*global window, document, INCLUDE_URI */ + +/* + * Load supporting scripts + */ +function get_INCLUDE_URI() { + return (typeof INCLUDE_URI !== "undefined") ? INCLUDE_URI : "include/"; +} + +(function () { + "use strict"; + + var extra = "", start, end; + + start = " + + + + + +
+ +
+ +
+ + + + + + +
+
+ + +
+ + + + + +
+ + +
+ +
+ +
+ + +
+ +
    +
  • Encrypt
  • +
  • True Color
  • +
  • Local Cursor
  • +
  • Clip to window
  • +
  • Shared Mode
  • +
  • Connect Timeout (s)
  • +
  • Path
  • +
    + +
  • +
  • + + +
  • +
  • +
    +
  • +
+
+
+ + +
+
    +
  • +
  • +
  • +
  • +
+
+ +
+ + +
+
+ +
+
Loading
+
+ +

no
VNC

+ + +
+ + Canvas not supported. + +
+ +
+ + + + diff --git a/classes/novnc/vnc_auto.html b/classes/novnc/vnc_auto.html new file mode 100644 index 0000000..a500b79 --- /dev/null +++ b/classes/novnc/vnc_auto.html @@ -0,0 +1,116 @@ + + + + + noVNC + + + + + + + +
+
+ + + +
Loading
+ +
+
+ + Canvas not supported. + +
+ + + + + + diff --git a/libvncserver/httpd.c b/libvncserver/httpd.c index ad2a51b..83fc520 100644 --- a/libvncserver/httpd.c +++ b/libvncserver/httpd.c @@ -346,12 +346,6 @@ httpProcessInput(rfbScreenInfoPtr rfbScreen) return; } - if (strchr(fname+1, '/') != NULL) { - rfbErr("httpd: asking for file in other directory\n"); - rfbWriteExact(&cl, NOT_FOUND_STR, strlen(NOT_FOUND_STR)); - httpCloseSock(rfbScreen); - return; - } getpeername(rfbScreen->httpSock, (struct sockaddr *)&addr, &addrlen); rfbLog("httpd: get '%s' for %s\n", fname+1, @@ -447,6 +441,10 @@ httpProcessInput(rfbScreenInfoPtr rfbScreen) sprintf(str, "%d", rfbScreen->port); rfbWriteExact(&cl, str, strlen(str)); + } else if (compareAndSkip(&ptr, "$HOST")) { + + rfbWriteExact(&cl, rfbScreen->thisHost, strlen(rfbScreen->thisHost)); + } else if (compareAndSkip(&ptr, "$DESKTOP")) { rfbWriteExact(&cl, rfbScreen->desktopName, strlen(rfbScreen->desktopName));