summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoel Martin <jmartin@sentryds.com>2011-08-25 12:24:52 -0500
committerJoel Martin <jmartin@sentryds.com>2011-08-25 12:24:52 -0500
commit65edee7696dc40dbc3e1a068c7767257be1f6bd1 (patch)
treeb7e54cd8a149b4ad3ceaeacc41987e51aeee2d5f
parent1d7af907209f3f1e3a7bad40ff9413b358d2c206 (diff)
downloadnovnc-html5-test.tar.gz
Fix menu. Add web-socket-js and websockify.html5-test
-rw-r--r--css/style.css45
-rw-r--r--js/ui.js9
-rw-r--r--js/web-socket-js/README.txt109
-rw-r--r--js/web-socket-js/WebSocketMain.swfbin0 -> 175746 bytes
-rw-r--r--js/web-socket-js/swfobject.js4
-rw-r--r--js/web-socket-js/web_socket.js341
-rw-r--r--utils/Makefile11
-rw-r--r--utils/README.md11
-rwxr-xr-xutils/img2js.py40
-rwxr-xr-xutils/json2graph.py206
-rwxr-xr-xutils/launch.sh108
-rwxr-xr-xutils/rebind18
-rw-r--r--utils/rebind.c94
-rwxr-xr-xutils/u2x1128
-rwxr-xr-xutils/web.py55
-rw-r--r--utils/websocket.py871
-rw-r--r--utils/websocket.pycbin0 -> 25695 bytes
-rwxr-xr-xutils/websockify278
l---------utils/wsproxy.py1
-rw-r--r--vnc.html11
20 files changed, 2225 insertions, 15 deletions
diff --git a/css/style.css b/css/style.css
index fcaed71..31e0e8a 100644
--- a/css/style.css
+++ b/css/style.css
@@ -168,8 +168,19 @@ a:hover { color: #036; }
* Author: Joel Martin
*/
+/*
+ html
+ body
+ container
+ noVNC-window
+ noVNC-control-bar
+ noVNC-status-bar
+ noVNC-viewport
+*/
+
html, body {
height: 100%;
+ width: 100%;
}
.container {
@@ -261,14 +272,6 @@ html, body {
/* -------------- */
-/*
- container
- noVNC-window
- noVNC-control-bar
- noVNC-status-bar
- noVNC-viewport
-*/
-
.noVNC-window {
background: #ccc;
@@ -348,6 +351,32 @@ html, body {
color: #cc4;
}
+.noVNC-menu {
+ position: relative;
+}
+
+.noVNC-settings-menu {
+ display: none;
+ position: absolute;
+ right: 0px;
+ width: 13em;
+ border: 1px solid #888;
+ background: rgba(192,232,192,0.90);
+ padding: 5px; margin: 3px;
+ z-index: 100; opacity: 1;
+ text-align: left; white-space: normal;
+}
+
+.noVNC-settings-menu ul {
+ list-style: none;
+ margin: 0px;
+ padding: 0px;
+}
+
+#noVNC_connectTimeout {
+ width: 2em;
+}
+
diff --git a/js/ui.js b/js/ui.js
index d0493ae..f52bebf 100644
--- a/js/ui.js
+++ b/js/ui.js
@@ -19,22 +19,21 @@ load: function() {
var html = '', i, sheet, sheets, llevels;
// Stylesheet selection dropdown
- html += ' <li><select id="noVNC_stylesheet" name="vncStyle">';
- html += ' <option value="default">default</option>';
+ html = ' <option value="default">default</option>';
sheet = WebUtil.selectStylesheet();
sheets = WebUtil.getStylesheets();
for (i = 0; i < sheets.length; i += 1) {
html += '<option value="' + sheets[i].title + '">' + sheets[i].title + '</option>';
}
- html += ' </select> Style</li>';
+ $D('noVNC_stylesheet').innerHTML = html;
// Logging selection dropdown
- html += ' <li><select id="noVNC_logging" name="vncLogging">';
+ html = '';
llevels = ['error', 'warn', 'info', 'debug'];
for (i = 0; i < llevels.length; i += 1) {
html += '<option value="' + llevels[i] + '">' + llevels[i] + '</option>';
}
- html += ' </select> Logging</li>';
+ $D('noVNC_logging').innerHTML = html;
// Settings with immediate effects
UI.initSetting('logging', 'warn');
diff --git a/js/web-socket-js/README.txt b/js/web-socket-js/README.txt
new file mode 100644
index 0000000..2e32ea7
--- /dev/null
+++ b/js/web-socket-js/README.txt
@@ -0,0 +1,109 @@
+* How to try
+
+Assuming you have Web server (e.g. Apache) running at http://example.com/ .
+
+- Download web_socket.rb from:
+ http://github.com/gimite/web-socket-ruby/tree/master
+- Run sample Web Socket server (echo server) in example.com with: (#1)
+ $ ruby web-socket-ruby/samples/echo_server.rb example.com 10081
+- If your server already provides socket policy file at port 843, modify the file to allow access to port 10081. Otherwise you can skip this step. See below for details.
+- Publish the web-socket-js directory with your Web server (e.g. put it in ~/public_html).
+- Change ws://localhost:10081 to ws://example.com:10081 in sample.html.
+- Open sample.html in your browser.
+- After "onopen" is shown, input something, click [Send] and confirm echo back.
+
+#1: First argument of echo_server.rb means that it accepts Web Socket connection from HTML pages in example.com.
+
+
+* Troubleshooting
+
+If it doesn't work, try these:
+
+1. Try Chrome and Firefox 3.x.
+- It doesn't work on Chrome:
+-- It's likely an issue of your code or the server. Debug your code as usual e.g. using console.log.
+- It works on Chrome but it doesn't work on Firefox:
+-- It's likely an issue of web-socket-js specific configuration (e.g. 3 and 4 below).
+- It works on both Chrome and Firefox, but it doesn't work on your browser:
+-- Check "Supported environment" section below. Your browser may not be supported by web-socket-js.
+
+2. Add this line before your code:
+ WEB_SOCKET_DEBUG = true;
+and use Developer Tools (Chrome/Safari) or Firebug (Firefox) to see if console.log outputs any errors.
+
+3. Make sure you do NOT open your HTML page as local file e.g. file:///.../sample.html. web-socket-js doesn't work on local file. Open it via Web server e.g. http:///.../sample.html.
+
+4. If you are NOT using web-socket-ruby as your WebSocket server, you need to place Flash socket policy file on your server. See "Flash socket policy file" section below for details.
+
+5. Check if sample.html bundled with web-socket-js works.
+
+6. Make sure the port used for WebSocket (10081 in example above) is not blocked by your server/client's firewall.
+
+7. Install debugger version of Flash Player available here to see Flash errors:
+http://www.adobe.com/support/flashplayer/downloads.html
+
+
+* Supported environments
+
+It should work on:
+- Google Chrome 4 or later (just uses native implementation)
+- Firefox 3.x, Internet Explorer 8 + Flash Player 9 or later
+
+It may or may not work on other browsers such as Safari, Opera or IE 6. Patch for these browsers are appreciated, but I will not work on fixing issues specific to these browsers by myself.
+
+
+* Flash socket policy file
+
+This implementation uses Flash's socket, which means that your server must provide Flash socket policy file to declare the server accepts connections from Flash.
+
+If you use web-socket-ruby available at
+http://github.com/gimite/web-socket-ruby/tree/master
+, you don't need anything special, because web-socket-ruby handles Flash socket policy file request. But if you already provide socket policy file at port 843, you need to modify the file to allow access to Web Socket port, because it precedes what web-socket-ruby provides.
+
+If you use other Web Socket server implementation, you need to provide socket policy file yourself. See
+http://www.lightsphere.com/dev/articles/flash_socket_policy.html
+for details and sample script to run socket policy file server. node.js implementation is available here:
+http://github.com/LearnBoost/Socket.IO-node/blob/master/lib/socket.io/transports/flashsocket.js
+
+Actually, it's still better to provide socket policy file at port 843 even if you use web-socket-ruby. Flash always try to connect to port 843 first, so providing the file at port 843 makes startup faster.
+
+
+* Cookie considerations
+
+Cookie is sent if Web Socket host is the same as the origin of JavaScript. Otherwise it is not sent, because I don't know way to send right Cookie (which is Cookie of the host of Web Socket, I heard).
+
+Note that it's technically possible that client sends arbitrary string as Cookie and any other headers (by modifying this library for example) once you place Flash socket policy file in your server. So don't trust Cookie and other headers if you allow connection from untrusted origin.
+
+
+* Proxy considerations
+
+The WebSocket spec (http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol) specifies instructions for User Agents to support proxied connections by implementing the HTTP CONNECT method.
+
+The AS3 Socket class doesn't implement this mechanism, which renders it useless for the scenarios where the user trying to open a socket is behind a proxy.
+
+The class RFC2817Socket (by Christian Cantrell) effectively lets us implement this, as long as the proxy settings are known and provided by the interface that instantiates the WebSocket. As such, if you want to support proxied conncetions, you'll have to supply this information to the WebSocket constructor when Flash is being used. One way to go about it would be to ask the user for proxy settings information if the initial connection fails.
+
+
+* How to host HTML file and SWF file in different domains
+
+By default, HTML file and SWF file must be in the same domain. You can follow steps below to allow hosting them in different domain.
+
+WARNING: If you use the method below, HTML files in ANY domains can send arbitrary TCP data to your WebSocket server, regardless of configuration in Flash socket policy file. Arbitrary TCP data means that they can even fake request headers including Origin and Cookie.
+
+- Unzip WebSocketMainInsecure.zip to extract WebSocketMainInsecure.swf.
+- Put WebSocketMainInsecure.swf on your server, instead of WebSocketMain.swf.
+- In JavaScript, set WEB_SOCKET_SWF_LOCATION to URL of your WebSocketMainInsecure.swf.
+
+
+* How to build WebSocketMain.swf
+
+Install Flex 4 SDK:
+http://opensource.adobe.com/wiki/display/flexsdk/Download+Flex+4
+
+$ cd flash-src
+$ ./build.sh
+
+
+* License
+
+New BSD License.
diff --git a/js/web-socket-js/WebSocketMain.swf b/js/web-socket-js/WebSocketMain.swf
new file mode 100644
index 0000000..244c445
--- /dev/null
+++ b/js/web-socket-js/WebSocketMain.swf
Binary files differ
diff --git a/js/web-socket-js/swfobject.js b/js/web-socket-js/swfobject.js
new file mode 100644
index 0000000..8eafe9d
--- /dev/null
+++ b/js/web-socket-js/swfobject.js
@@ -0,0 +1,4 @@
+/* SWFObject v2.2 <http://code.google.com/p/swfobject/>
+ is released under the MIT License <http://www.opensource.org/licenses/mit-license.php>
+*/
+var swfobject=function(){var D="undefined",r="object",S="Shockwave Flash",W="ShockwaveFlash.ShockwaveFlash",q="application/x-shockwave-flash",R="SWFObjectExprInst",x="onreadystatechange",O=window,j=document,t=navigator,T=false,U=[h],o=[],N=[],I=[],l,Q,E,B,J=false,a=false,n,G,m=true,M=function(){var aa=typeof j.getElementById!=D&&typeof j.getElementsByTagName!=D&&typeof j.createElement!=D,ah=t.userAgent.toLowerCase(),Y=t.platform.toLowerCase(),ae=Y?/win/.test(Y):/win/.test(ah),ac=Y?/mac/.test(Y):/mac/.test(ah),af=/webkit/.test(ah)?parseFloat(ah.replace(/^.*webkit\/(\d+(\.\d+)?).*$/,"$1")):false,X=!+"\v1",ag=[0,0,0],ab=null;if(typeof t.plugins!=D&&typeof t.plugins[S]==r){ab=t.plugins[S].description;if(ab&&!(typeof t.mimeTypes!=D&&t.mimeTypes[q]&&!t.mimeTypes[q].enabledPlugin)){T=true;X=false;ab=ab.replace(/^.*\s+(\S+\s+\S+$)/,"$1");ag[0]=parseInt(ab.replace(/^(.*)\..*$/,"$1"),10);ag[1]=parseInt(ab.replace(/^.*\.(.*)\s.*$/,"$1"),10);ag[2]=/[a-zA-Z]/.test(ab)?parseInt(ab.replace(/^.*[a-zA-Z]+(.*)$/,"$1"),10):0}}else{if(typeof O.ActiveXObject!=D){try{var ad=new ActiveXObject(W);if(ad){ab=ad.GetVariable("$version");if(ab){X=true;ab=ab.split(" ")[1].split(",");ag=[parseInt(ab[0],10),parseInt(ab[1],10),parseInt(ab[2],10)]}}}catch(Z){}}}return{w3:aa,pv:ag,wk:af,ie:X,win:ae,mac:ac}}(),k=function(){if(!M.w3){return}if((typeof j.readyState!=D&&j.readyState=="complete")||(typeof j.readyState==D&&(j.getElementsByTagName("body")[0]||j.body))){f()}if(!J){if(typeof j.addEventListener!=D){j.addEventListener("DOMContentLoaded",f,false)}if(M.ie&&M.win){j.attachEvent(x,function(){if(j.readyState=="complete"){j.detachEvent(x,arguments.callee);f()}});if(O==top){(function(){if(J){return}try{j.documentElement.doScroll("left")}catch(X){setTimeout(arguments.callee,0);return}f()})()}}if(M.wk){(function(){if(J){return}if(!/loaded|complete/.test(j.readyState)){setTimeout(arguments.callee,0);return}f()})()}s(f)}}();function f(){if(J){return}try{var Z=j.getElementsByTagName("body")[0].appendChild(C("span"));Z.parentNode.removeChild(Z)}catch(aa){return}J=true;var X=U.length;for(var Y=0;Y<X;Y++){U[Y]()}}function K(X){if(J){X()}else{U[U.length]=X}}function s(Y){if(typeof O.addEventListener!=D){O.addEventListener("load",Y,false)}else{if(typeof j.addEventListener!=D){j.addEventListener("load",Y,false)}else{if(typeof O.attachEvent!=D){i(O,"onload",Y)}else{if(typeof O.onload=="function"){var X=O.onload;O.onload=function(){X();Y()}}else{O.onload=Y}}}}}function h(){if(T){V()}else{H()}}function V(){var X=j.getElementsByTagName("body")[0];var aa=C(r);aa.setAttribute("type",q);var Z=X.appendChild(aa);if(Z){var Y=0;(function(){if(typeof Z.GetVariable!=D){var ab=Z.GetVariable("$version");if(ab){ab=ab.split(" ")[1].split(",");M.pv=[parseInt(ab[0],10),parseInt(ab[1],10),parseInt(ab[2],10)]}}else{if(Y<10){Y++;setTimeout(arguments.callee,10);return}}X.removeChild(aa);Z=null;H()})()}else{H()}}function H(){var ag=o.length;if(ag>0){for(var af=0;af<ag;af++){var Y=o[af].id;var ab=o[af].callbackFn;var aa={success:false,id:Y};if(M.pv[0]>0){var ae=c(Y);if(ae){if(F(o[af].swfVersion)&&!(M.wk&&M.wk<312)){w(Y,true);if(ab){aa.success=true;aa.ref=z(Y);ab(aa)}}else{if(o[af].expressInstall&&A()){var ai={};ai.data=o[af].expressInstall;ai.width=ae.getAttribute("width")||"0";ai.height=ae.getAttribute("height")||"0";if(ae.getAttribute("class")){ai.styleclass=ae.getAttribute("class")}if(ae.getAttribute("align")){ai.align=ae.getAttribute("align")}var ah={};var X=ae.getElementsByTagName("param");var ac=X.length;for(var ad=0;ad<ac;ad++){if(X[ad].getAttribute("name").toLowerCase()!="movie"){ah[X[ad].getAttribute("name")]=X[ad].getAttribute("value")}}P(ai,ah,Y,ab)}else{p(ae);if(ab){ab(aa)}}}}}else{w(Y,true);if(ab){var Z=z(Y);if(Z&&typeof Z.SetVariable!=D){aa.success=true;aa.ref=Z}ab(aa)}}}}}function z(aa){var X=null;var Y=c(aa);if(Y&&Y.nodeName=="OBJECT"){if(typeof Y.SetVariable!=D){X=Y}else{var Z=Y.getElementsByTagName(r)[0];if(Z){X=Z}}}return X}function A(){return !a&&F("6.0.65")&&(M.win||M.mac)&&!(M.wk&&M.wk<312)}function P(aa,ab,X,Z){a=true;E=Z||null;B={success:false,id:X};var ae=c(X);if(ae){if(ae.nodeName=="OBJECT"){l=g(ae);Q=null}else{l=ae;Q=X}aa.id=R;if(typeof aa.width==D||(!/%$/.test(aa.width)&&parseInt(aa.width,10)<310)){aa.width="310"}if(typeof aa.height==D||(!/%$/.test(aa.height)&&parseInt(aa.height,10)<137)){aa.height="137"}j.title=j.title.slice(0,47)+" - Flash Player Installation";var ad=M.ie&&M.win?"ActiveX":"PlugIn",ac="MMredirectURL="+O.location.toString().replace(/&/g,"%26")+"&MMplayerType="+ad+"&MMdoctitle="+j.title;if(typeof ab.flashvars!=D){ab.flashvars+="&"+ac}else{ab.flashvars=ac}if(M.ie&&M.win&&ae.readyState!=4){var Y=C("div");X+="SWFObjectNew";Y.setAttribute("id",X);ae.parentNode.insertBefore(Y,ae);ae.style.display="none";(function(){if(ae.readyState==4){ae.parentNode.removeChild(ae)}else{setTimeout(arguments.callee,10)}})()}u(aa,ab,X)}}function p(Y){if(M.ie&&M.win&&Y.readyState!=4){var X=C("div");Y.parentNode.insertBefore(X,Y);X.parentNode.replaceChild(g(Y),X);Y.style.display="none";(function(){if(Y.readyState==4){Y.parentNode.removeChild(Y)}else{setTimeout(arguments.callee,10)}})()}else{Y.parentNode.replaceChild(g(Y),Y)}}function g(ab){var aa=C("div");if(M.win&&M.ie){aa.innerHTML=ab.innerHTML}else{var Y=ab.getElementsByTagName(r)[0];if(Y){var ad=Y.childNodes;if(ad){var X=ad.length;for(var Z=0;Z<X;Z++){if(!(ad[Z].nodeType==1&&ad[Z].nodeName=="PARAM")&&!(ad[Z].nodeType==8)){aa.appendChild(ad[Z].cloneNode(true))}}}}}return aa}function u(ai,ag,Y){var X,aa=c(Y);if(M.wk&&M.wk<312){return X}if(aa){if(typeof ai.id==D){ai.id=Y}if(M.ie&&M.win){var ah="";for(var ae in ai){if(ai[ae]!=Object.prototype[ae]){if(ae.toLowerCase()=="data"){ag.movie=ai[ae]}else{if(ae.toLowerCase()=="styleclass"){ah+=' class="'+ai[ae]+'"'}else{if(ae.toLowerCase()!="classid"){ah+=" "+ae+'="'+ai[ae]+'"'}}}}}var af="";for(var ad in ag){if(ag[ad]!=Object.prototype[ad]){af+='<param name="'+ad+'" value="'+ag[ad]+'" />'}}aa.outerHTML='<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"'+ah+">"+af+"</object>";N[N.length]=ai.id;X=c(ai.id)}else{var Z=C(r);Z.setAttribute("type",q);for(var ac in ai){if(ai[ac]!=Object.prototype[ac]){if(ac.toLowerCase()=="styleclass"){Z.setAttribute("class",ai[ac])}else{if(ac.toLowerCase()!="classid"){Z.setAttribute(ac,ai[ac])}}}}for(var ab in ag){if(ag[ab]!=Object.prototype[ab]&&ab.toLowerCase()!="movie"){e(Z,ab,ag[ab])}}aa.parentNode.replaceChild(Z,aa);X=Z}}return X}function e(Z,X,Y){var aa=C("param");aa.setAttribute("name",X);aa.setAttribute("value",Y);Z.appendChild(aa)}function y(Y){var X=c(Y);if(X&&X.nodeName=="OBJECT"){if(M.ie&&M.win){X.style.display="none";(function(){if(X.readyState==4){b(Y)}else{setTimeout(arguments.callee,10)}})()}else{X.parentNode.removeChild(X)}}}function b(Z){var Y=c(Z);if(Y){for(var X in Y){if(typeof Y[X]=="function"){Y[X]=null}}Y.parentNode.removeChild(Y)}}function c(Z){var X=null;try{X=j.getElementById(Z)}catch(Y){}return X}function C(X){return j.createElement(X)}function i(Z,X,Y){Z.attachEvent(X,Y);I[I.length]=[Z,X,Y]}function F(Z){var Y=M.pv,X=Z.split(".");X[0]=parseInt(X[0],10);X[1]=parseInt(X[1],10)||0;X[2]=parseInt(X[2],10)||0;return(Y[0]>X[0]||(Y[0]==X[0]&&Y[1]>X[1])||(Y[0]==X[0]&&Y[1]==X[1]&&Y[2]>=X[2]))?true:false}function v(ac,Y,ad,ab){if(M.ie&&M.mac){return}var aa=j.getElementsByTagName("head")[0];if(!aa){return}var X=(ad&&typeof ad=="string")?ad:"screen";if(ab){n=null;G=null}if(!n||G!=X){var Z=C("style");Z.setAttribute("type","text/css");Z.setAttribute("media",X);n=aa.appendChild(Z);if(M.ie&&M.win&&typeof j.styleSheets!=D&&j.styleSheets.length>0){n=j.styleSheets[j.styleSheets.length-1]}G=X}if(M.ie&&M.win){if(n&&typeof n.addRule==r){n.addRule(ac,Y)}}else{if(n&&typeof j.createTextNode!=D){n.appendChild(j.createTextNode(ac+" {"+Y+"}"))}}}function w(Z,X){if(!m){return}var Y=X?"visible":"hidden";if(J&&c(Z)){c(Z).style.visibility=Y}else{v("#"+Z,"visibility:"+Y)}}function L(Y){var Z=/[\\\"<>\.;]/;var X=Z.exec(Y)!=null;return X&&typeof encodeURIComponent!=D?encodeURIComponent(Y):Y}var d=function(){if(M.ie&&M.win){window.attachEvent("onunload",function(){var ac=I.length;for(var ab=0;ab<ac;ab++){I[ab][0].detachEvent(I[ab][1],I[ab][2])}var Z=N.length;for(var aa=0;aa<Z;aa++){y(N[aa])}for(var Y in M){M[Y]=null}M=null;for(var X in swfobject){swfobject[X]=null}swfobject=null})}}();return{registerObject:function(ab,X,aa,Z){if(M.w3&&ab&&X){var Y={};Y.id=ab;Y.swfVersion=X;Y.expressInstall=aa;Y.callbackFn=Z;o[o.length]=Y;w(ab,false)}else{if(Z){Z({success:false,id:ab})}}},getObjectById:function(X){if(M.w3){return z(X)}},embedSWF:function(ab,ah,ae,ag,Y,aa,Z,ad,af,ac){var X={success:false,id:ah};if(M.w3&&!(M.wk&&M.wk<312)&&ab&&ah&&ae&&ag&&Y){w(ah,false);K(function(){ae+="";ag+="";var aj={};if(af&&typeof af===r){for(var al in af){aj[al]=af[al]}}aj.data=ab;aj.width=ae;aj.height=ag;var am={};if(ad&&typeof ad===r){for(var ak in ad){am[ak]=ad[ak]}}if(Z&&typeof Z===r){for(var ai in Z){if(typeof am.flashvars!=D){am.flashvars+="&"+ai+"="+Z[ai]}else{am.flashvars=ai+"="+Z[ai]}}}if(F(Y)){var an=u(aj,am,ah);if(aj.id==ah){w(ah,true)}X.success=true;X.ref=an}else{if(aa&&A()){aj.data=aa;P(aj,am,ah,ac);return}else{w(ah,true)}}if(ac){ac(X)}})}else{if(ac){ac(X)}}},switchOffAutoHideShow:function(){m=false},ua:M,getFlashPlayerVersion:function(){return{major:M.pv[0],minor:M.pv[1],release:M.pv[2]}},hasFlashPlayerVersion:F,createSWF:function(Z,Y,X){if(M.w3){return u(Z,Y,X)}else{return undefined}},showExpressInstall:function(Z,aa,X,Y){if(M.w3&&A()){P(Z,aa,X,Y)}},removeSWF:function(X){if(M.w3){y(X)}},createCSS:function(aa,Z,Y,X){if(M.w3){v(aa,Z,Y,X)}},addDomLoadEvent:K,addLoadEvent:s,getQueryParamValue:function(aa){var Z=j.location.search||j.location.hash;if(Z){if(/\?/.test(Z)){Z=Z.split("?")[1]}if(aa==null){return L(Z)}var Y=Z.split("&");for(var X=0;X<Y.length;X++){if(Y[X].substring(0,Y[X].indexOf("="))==aa){return L(Y[X].substring((Y[X].indexOf("=")+1)))}}}return""},expressInstallCallback:function(){if(a){var X=c(R);if(X&&l){X.parentNode.replaceChild(l,X);if(Q){w(Q,true);if(M.ie&&M.win){l.style.display="block"}}if(E){E(B)}}a=false}}}}(); \ No newline at end of file
diff --git a/js/web-socket-js/web_socket.js b/js/web-socket-js/web_socket.js
new file mode 100644
index 0000000..ec2a8b7
--- /dev/null
+++ b/js/web-socket-js/web_socket.js
@@ -0,0 +1,341 @@
+// Copyright: Hiroshi Ichikawa <http://gimite.net/en/>
+// License: New BSD License
+// Reference: http://dev.w3.org/html5/websockets/
+// Reference: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol
+
+(function() {
+
+ if (window.WebSocket) return;
+
+ var console = window.console;
+ if (!console || !console.log || !console.error) {
+ console = {log: function(){ }, error: function(){ }};
+ }
+
+ if (!swfobject.hasFlashPlayerVersion("10.0.0")) {
+ console.error("Flash Player >= 10.0.0 is required.");
+ return;
+ }
+ if (location.protocol == "file:") {
+ console.error(
+ "WARNING: web-socket-js doesn't work in file:///... URL " +
+ "unless you set Flash Security Settings properly. " +
+ "Open the page via Web server i.e. http://...");
+ }
+
+ /**
+ * This class represents a faux web socket.
+ * @param {string} url
+ * @param {string} protocol
+ * @param {string} proxyHost
+ * @param {int} proxyPort
+ * @param {string} headers
+ */
+ WebSocket = function(url, protocol, proxyHost, proxyPort, headers) {
+ var self = this;
+ self.__id = WebSocket.__nextId++;
+ WebSocket.__instances[self.__id] = self;
+ self.readyState = WebSocket.CONNECTING;
+ self.bufferedAmount = 0;
+ self.__events = {};
+ // Uses setTimeout() to make sure __createFlash() runs after the caller sets ws.onopen etc.
+ // Otherwise, when onopen fires immediately, onopen is called before it is set.
+ setTimeout(function() {
+ WebSocket.__addTask(function() {
+ WebSocket.__flash.create(
+ self.__id, url, protocol, proxyHost || null, proxyPort || 0, headers || null);
+ });
+ }, 0);
+ };
+
+ /**
+ * Send data to the web socket.
+ * @param {string} data The data to send to the socket.
+ * @return {boolean} True for success, false for failure.
+ */
+ WebSocket.prototype.send = function(data) {
+ if (this.readyState == WebSocket.CONNECTING) {
+ throw "INVALID_STATE_ERR: Web Socket connection has not been established";
+ }
+ // We use encodeURIComponent() here, because FABridge doesn't work if
+ // the argument includes some characters. We don't use escape() here
+ // because of this:
+ // https://developer.mozilla.org/en/Core_JavaScript_1.5_Guide/Functions#escape_and_unescape_Functions
+ // But it looks decodeURIComponent(encodeURIComponent(s)) doesn't
+ // preserve all Unicode characters either e.g. "\uffff" in Firefox.
+ // Note by wtritch: Hopefully this will not be necessary using ExternalInterface. Will require
+ // additional testing.
+ var result = WebSocket.__flash.send(this.__id, encodeURIComponent(data));
+ if (result < 0) { // success
+ return true;
+ } else {
+ this.bufferedAmount += result;
+ return false;
+ }
+ };
+
+ /**
+ * Close this web socket gracefully.
+ */
+ WebSocket.prototype.close = function() {
+ if (this.readyState == WebSocket.CLOSED || this.readyState == WebSocket.CLOSING) {
+ return;
+ }
+ this.readyState = WebSocket.CLOSING;
+ WebSocket.__flash.close(this.__id);
+ };
+
+ /**
+ * Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>}
+ *
+ * @param {string} type
+ * @param {function} listener
+ * @param {boolean} useCapture
+ * @return void
+ */
+ WebSocket.prototype.addEventListener = function(type, listener, useCapture) {
+ if (!(type in this.__events)) {
+ this.__events[type] = [];
+ }
+ this.__events[type].push(listener);
+ };
+
+ /**
+ * Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>}
+ *
+ * @param {string} type
+ * @param {function} listener
+ * @param {boolean} useCapture
+ * @return void
+ */
+ WebSocket.prototype.removeEventListener = function(type, listener, useCapture) {
+ if (!(type in this.__events)) return;
+ var events = this.__events[type];
+ for (var i = events.length - 1; i >= 0; --i) {
+ if (events[i] === listener) {
+ events.splice(i, 1);
+ break;
+ }
+ }
+ };
+
+ /**
+ * Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>}
+ *
+ * @param {Event} event
+ * @return void
+ */
+ WebSocket.prototype.dispatchEvent = function(event) {
+ var events = this.__events[event.type] || [];
+ for (var i = 0; i < events.length; ++i) {
+ events[i](event);
+ }
+ var handler = this["on" + event.type];
+ if (handler) handler(event);
+ };
+
+ /**
+ * Handles an event from Flash.
+ * @param {Object} flashEvent
+ */
+ WebSocket.prototype.__handleEvent = function(flashEvent) {
+ if ("readyState" in flashEvent) {
+ this.readyState = flashEvent.readyState;
+ }
+
+ var jsEvent;
+ if (flashEvent.type == "open" || flashEvent.type == "error") {
+ jsEvent = this.__createSimpleEvent(flashEvent.type);
+ } else if (flashEvent.type == "close") {
+ // TODO implement jsEvent.wasClean
+ jsEvent = this.__createSimpleEvent("close");
+ } else if (flashEvent.type == "message") {
+ var data = decodeURIComponent(flashEvent.message);
+ jsEvent = this.__createMessageEvent("message", data);
+ } else {
+ throw "unknown event type: " + flashEvent.type;
+ }
+
+ this.dispatchEvent(jsEvent);
+ };
+
+ WebSocket.prototype.__createSimpleEvent = function(type) {
+ if (document.createEvent && window.Event) {
+ var event = document.createEvent("Event");
+ event.initEvent(type, false, false);
+ return event;
+ } else {
+ return {type: type, bubbles: false, cancelable: false};
+ }
+ };
+
+ WebSocket.prototype.__createMessageEvent = function(type, data) {
+ if (document.createEvent && window.MessageEvent && !window.opera) {
+ var event = document.createEvent("MessageEvent");
+ event.initMessageEvent("message", false, false, data, null, null, window, null);
+ return event;
+ } else {
+ // IE and Opera, the latter one truncates the data parameter after any 0x00 bytes.
+ return {type: type, data: data, bubbles: false, cancelable: false};
+ }
+ };
+
+ /**
+ * Define the WebSocket readyState enumeration.
+ */
+ WebSocket.CONNECTING = 0;
+ WebSocket.OPEN = 1;
+ WebSocket.CLOSING = 2;
+ WebSocket.CLOSED = 3;
+
+ WebSocket.__flash = null;
+ WebSocket.__instances = {};
+ WebSocket.__tasks = [];
+ WebSocket.__nextId = 0;
+
+ /**
+ * Load a new flash security policy file.
+ * @param {string} url
+ */
+ WebSocket.loadFlashPolicyFile = function(url){
+ WebSocket.__addTask(function() {
+ WebSocket.__flash.loadManualPolicyFile(url);
+ });
+ };
+
+ /**
+ * Loads WebSocketMain.swf and creates WebSocketMain object in Flash.
+ */
+ WebSocket.__initialize = function() {
+ if (WebSocket.__flash) return;
+
+ if (WebSocket.__swfLocation) {
+ // For backword compatibility.
+ window.WEB_SOCKET_SWF_LOCATION = WebSocket.__swfLocation;
+ }
+ if (!window.WEB_SOCKET_SWF_LOCATION) {
+ console.error("[WebSocket] set WEB_SOCKET_SWF_LOCATION to location of WebSocketMain.swf");
+ return;
+ }
+ var container = document.createElement("div");
+ container.id = "webSocketContainer";
+ // Hides Flash box. We cannot use display: none or visibility: hidden because it prevents
+ // Flash from loading at least in IE. So we move it out of the screen at (-100, -100).
+ // But this even doesn't work with Flash Lite (e.g. in Droid Incredible). So with Flash
+ // Lite, we put it at (0, 0). This shows 1x1 box visible at left-top corner but this is
+ // the best we can do as far as we know now.
+ container.style.position = "absolute";
+ if (WebSocket.__isFlashLite()) {
+ container.style.left = "0px";
+ container.style.top = "0px";
+ } else {
+ container.style.left = "-100px";
+ container.style.top = "-100px";
+ }
+ var holder = document.createElement("div");
+ holder.id = "webSocketFlash";
+ container.appendChild(holder);
+ document.body.appendChild(container);
+ // See this article for hasPriority:
+ // http://help.adobe.com/en_US/as3/mobile/WS4bebcd66a74275c36cfb8137124318eebc6-7ffd.html
+ swfobject.embedSWF(
+ WEB_SOCKET_SWF_LOCATION,
+ "webSocketFlash",
+ "1" /* width */,
+ "1" /* height */,
+ "10.0.0" /* SWF version */,
+ null,
+ null,
+ {hasPriority: true, swliveconnect : true, allowScriptAccess: "always"},
+ null,
+ function(e) {
+ if (!e.success) {
+ console.error("[WebSocket] swfobject.embedSWF failed");
+ }
+ });
+ };
+
+ /**
+ * Called by Flash to notify JS that it's fully loaded and ready
+ * for communication.
+ */
+ WebSocket.__onFlashInitialized = function() {
+ // We need to set a timeout here to avoid round-trip calls
+ // to flash during the initialization process.
+ setTimeout(function() {
+ WebSocket.__flash = document.getElementById("webSocketFlash");
+ WebSocket.__flash.setCallerUrl(location.href);
+ WebSocket.__flash.setDebug(!!window.WEB_SOCKET_DEBUG);
+ for (var i = 0; i < WebSocket.__tasks.length; ++i) {
+ WebSocket.__tasks[i]();
+ }
+ WebSocket.__tasks = [];
+ }, 0);
+ };
+
+ /**
+ * Called by Flash to notify WebSockets events are fired.
+ */
+ WebSocket.__onFlashEvent = function() {
+ setTimeout(function() {
+ try {
+ // Gets events using receiveEvents() instead of getting it from event object
+ // of Flash event. This is to make sure to keep message order.
+ // It seems sometimes Flash events don't arrive in the same order as they are sent.
+ var events = WebSocket.__flash.receiveEvents();
+ for (var i = 0; i < events.length; ++i) {
+ WebSocket.__instances[events[i].webSocketId].__handleEvent(events[i]);
+ }
+ } catch (e) {
+ console.error(e);
+ }
+ }, 0);
+ return true;
+ };
+
+ // Called by Flash.
+ WebSocket.__log = function(message) {
+ console.log(decodeURIComponent(message));
+ };
+
+ // Called by Flash.
+ WebSocket.__error = function(message) {
+ console.error(decodeURIComponent(message));
+ };
+
+ WebSocket.__addTask = function(task) {
+ if (WebSocket.__flash) {
+ task();
+ } else {
+ WebSocket.__tasks.push(task);
+ }
+ };
+
+ /**
+ * Test if the browser is running flash lite.
+ * @return {boolean} True if flash lite is running, false otherwise.
+ */
+ WebSocket.__isFlashLite = function() {
+ if (!window.navigator || !window.navigator.mimeTypes) {
+ return false;
+ }
+ var mimeType = window.navigator.mimeTypes["application/x-shockwave-flash"];
+ if (!mimeType || !mimeType.enabledPlugin || !mimeType.enabledPlugin.filename) {
+ return false;
+ }
+ return mimeType.enabledPlugin.filename.match(/flashlite/i) ? true : false;
+ };
+
+ if (!window.WEB_SOCKET_DISABLE_AUTO_INITIALIZATION) {
+ if (window.addEventListener) {
+ window.addEventListener("load", function(){
+ WebSocket.__initialize();
+ }, false);
+ } else {
+ window.attachEvent("onload", function(){
+ WebSocket.__initialize();
+ });
+ }
+ }
+
+})();
diff --git a/utils/Makefile b/utils/Makefile
new file mode 100644
index 0000000..7dc1bc4
--- /dev/null
+++ b/utils/Makefile
@@ -0,0 +1,11 @@
+TARGETS=rebind.so
+CFLAGS += -fPIC
+
+all: $(TARGETS)
+
+rebind.so: rebind.o
+ $(CC) $(LDFLAGS) $^ -shared -fPIC -ldl -o $@
+
+clean:
+ rm -f rebind.o rebind.so
+
diff --git a/utils/README.md b/utils/README.md
new file mode 100644
index 0000000..0abbd0d
--- /dev/null
+++ b/utils/README.md
@@ -0,0 +1,11 @@
+## WebSockets Proxy
+
+wsproxy has become [websockify](https://github.com/kanaka/websockify).
+A copy of the python version of websockify (named wsproxy.py) is kept
+here for ease of use. The other versions of websockify (C, Node.js)
+and the associated test programs have been moved to
+[websockify](https://github.com/kanaka/websockify).
+
+For more detailed description and usage information please refer to
+the [websockify README](https://github.com/kanaka/websockify/blob/master/README.md).
+
diff --git a/utils/img2js.py b/utils/img2js.py
new file mode 100755
index 0000000..3ba4598
--- /dev/null
+++ b/utils/img2js.py
@@ -0,0 +1,40 @@
+#!/usr/bin/env python
+
+#
+# Convert image to Javascript compatible base64 Data URI
+# Copyright 2011 Joel Martin
+# Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3)
+#
+
+import sys, base64
+
+try:
+ from PIL import Image
+except:
+ print "python PIL module required (python-imaging package)"
+ sys.exit(1)
+
+
+if len(sys.argv) < 3:
+ print "Usage: %s IMAGE JS_VARIABLE" % sys.argv[0]
+ sys.exit(1)
+
+fname = sys.argv[1]
+var = sys.argv[2]
+
+ext = fname.lower().split('.')[-1]
+if ext == "png": mime = "image/png"
+elif ext in ["jpg", "jpeg"]: mime = "image/jpeg"
+elif ext == "gif": mime = "image/gif"
+else:
+ print "Only PNG, JPEG and GIF images are supported"
+ sys.exit(1)
+uri = "data:%s;base64," % mime
+
+im = Image.open(fname)
+w, h = im.size
+
+raw = open(fname).read()
+
+print '%s = {"width": %s, "height": %s, "data": "%s%s"};' % (
+ var, w, h, uri, base64.b64encode(raw))
diff --git a/utils/json2graph.py b/utils/json2graph.py
new file mode 100755
index 0000000..0f6a7ad
--- /dev/null
+++ b/utils/json2graph.py
@@ -0,0 +1,206 @@
+#!/usr/bin/env python
+
+'''
+Use matplotlib to generate performance charts
+Copyright 2011 Joel Martin
+Licensed under GPL version 3 (see docs/LICENSE.GPL-3)
+'''
+
+# a bar plot with errorbars
+import sys, json, pprint
+import numpy as np
+import matplotlib.pyplot as plt
+from matplotlib.font_manager import FontProperties
+
+def usage():
+ print "%s json_file level1 level2 level3 [legend_height]\n\n" % sys.argv[0]
+ print "Description:\n"
+ print "level1, level2, and level3 are one each of the following:\n";
+ print " select=ITEM - select only ITEM at this level";
+ print " bar - each item on this level becomes a graph bar";
+ print " group - items on this level become groups of bars";
+ print "\n";
+ print "json_file is a file containing json data in the following format:\n"
+ print ' {';
+ print ' "conf": {';
+ print ' "order_l1": [';
+ print ' "level1_label1",';
+ print ' "level1_label2",';
+ print ' ...';
+ print ' ],';
+ print ' "order_l2": [';
+ print ' "level2_label1",';
+ print ' "level2_label2",';
+ print ' ...';
+ print ' ],';
+ print ' "order_l3": [';
+ print ' "level3_label1",';
+ print ' "level3_label2",';
+ print ' ...';
+ print ' ]';
+ print ' },';
+ print ' "stats": {';
+ print ' "level1_label1": {';
+ print ' "level2_label1": {';
+ print ' "level3_label1": [val1, val2, val3],';
+ print ' "level3_label2": [val1, val2, val3],';
+ print ' ...';
+ print ' },';
+ print ' "level2_label2": {';
+ print ' ...';
+ print ' },';
+ print ' },';
+ print ' "level1_label2": {';
+ print ' ...';
+ print ' },';
+ print ' ...';
+ print ' },';
+ print ' }';
+ sys.exit(2)
+
+def error(msg):
+ print msg
+ sys.exit(1)
+
+
+#colors = ['#ff0000', '#0863e9', '#00f200', '#ffa100',
+# '#800000', '#805100', '#013075', '#007900']
+colors = ['#ff0000', '#00ff00', '#0000ff',
+ '#dddd00', '#dd00dd', '#00dddd',
+ '#dd6622', '#dd2266', '#66dd22',
+ '#8844dd', '#44dd88', '#4488dd']
+
+if len(sys.argv) < 5:
+ usage()
+
+filename = sys.argv[1]
+L1 = sys.argv[2]
+L2 = sys.argv[3]
+L3 = sys.argv[4]
+if len(sys.argv) > 5:
+ legendHeight = float(sys.argv[5])
+else:
+ legendHeight = 0.75
+
+# Load the JSON data from the file
+data = json.loads(file(filename).read())
+conf = data['conf']
+stats = data['stats']
+
+# Sanity check data hierarchy
+if len(conf['order_l1']) != len(stats.keys()):
+ error("conf.order_l1 does not match stats level 1")
+for l1 in stats.keys():
+ if len(conf['order_l2']) != len(stats[l1].keys()):
+ error("conf.order_l2 does not match stats level 2 for %s" % l1)
+ if conf['order_l1'].count(l1) < 1:
+ error("%s not found in conf.order_l1" % l1)
+ for l2 in stats[l1].keys():
+ if len(conf['order_l3']) != len(stats[l1][l2].keys()):
+ error("conf.order_l3 does not match stats level 3")
+ if conf['order_l2'].count(l2) < 1:
+ error("%s not found in conf.order_l2" % l2)
+ for l3 in stats[l1][l2].keys():
+ if conf['order_l3'].count(l3) < 1:
+ error("%s not found in conf.order_l3" % l3)
+
+#
+# Generate the data based on the level specifications
+#
+bar_labels = None
+group_labels = None
+bar_vals = []
+bar_sdvs = []
+if L3.startswith("select="):
+ select_label = l3 = L3.split("=")[1]
+ bar_labels = conf['order_l1']
+ group_labels = conf['order_l2']
+ bar_vals = [[0]*len(group_labels) for i in bar_labels]
+ bar_sdvs = [[0]*len(group_labels) for i in bar_labels]
+ for b in range(len(bar_labels)):
+ l1 = bar_labels[b]
+ for g in range(len(group_labels)):
+ l2 = group_labels[g]
+ bar_vals[b][g] = np.mean(stats[l1][l2][l3])
+ bar_sdvs[b][g] = np.std(stats[l1][l2][l3])
+elif L2.startswith("select="):
+ select_label = l2 = L2.split("=")[1]
+ bar_labels = conf['order_l1']
+ group_labels = conf['order_l3']
+ bar_vals = [[0]*len(group_labels) for i in bar_labels]
+ bar_sdvs = [[0]*len(group_labels) for i in bar_labels]
+ for b in range(len(bar_labels)):
+ l1 = bar_labels[b]
+ for g in range(len(group_labels)):
+ l3 = group_labels[g]
+ bar_vals[b][g] = np.mean(stats[l1][l2][l3])
+ bar_sdvs[b][g] = np.std(stats[l1][l2][l3])
+elif L1.startswith("select="):
+ select_label = l1 = L1.split("=")[1]
+ bar_labels = conf['order_l2']
+ group_labels = conf['order_l3']
+ bar_vals = [[0]*len(group_labels) for i in bar_labels]
+ bar_sdvs = [[0]*len(group_labels) for i in bar_labels]
+ for b in range(len(bar_labels)):
+ l2 = bar_labels[b]
+ for g in range(len(group_labels)):
+ l3 = group_labels[g]
+ bar_vals[b][g] = np.mean(stats[l1][l2][l3])
+ bar_sdvs[b][g] = np.std(stats[l1][l2][l3])
+else:
+ usage()
+
+# If group is before bar then flip (zip) the data
+if [L1, L2, L3].index("group") < [L1, L2, L3].index("bar"):
+ bar_labels, group_labels = group_labels, bar_labels
+ bar_vals = zip(*bar_vals)
+ bar_sdvs = zip(*bar_sdvs)
+
+print "bar_vals:", bar_vals
+
+#
+# Now render the bar graph
+#
+ind = np.arange(len(group_labels)) # the x locations for the groups
+width = 0.8 * (1.0/len(bar_labels)) # the width of the bars
+
+fig = plt.figure(figsize=(10,6), dpi=80)
+plot = fig.add_subplot(1, 1, 1)
+
+rects = []
+for i in range(len(bar_vals)):
+ rects.append(plot.bar(ind+width*i, bar_vals[i], width, color=colors[i],
+ yerr=bar_sdvs[i], align='center'))
+
+# add some
+plot.set_ylabel('Milliseconds (less is better)')
+plot.set_title("Javascript array test: %s" % select_label)
+plot.set_xticks(ind+width)
+plot.set_xticklabels( group_labels )
+
+fontP = FontProperties()
+fontP.set_size('small')
+plot.legend( [r[0] for r in rects], bar_labels, prop=fontP,
+ loc = 'center right', bbox_to_anchor = (1.0, legendHeight))
+
+def autolabel(rects):
+ # attach some text labels
+ for rect in rects:
+ height = rect.get_height()
+ if np.isnan(height):
+ height = 0.0
+ plot.text(rect.get_x()+rect.get_width()/2., height+20, '%d'%int(height),
+ ha='center', va='bottom', size='7')
+
+for rect in rects:
+ autolabel(rect)
+
+# Adjust axis sizes
+axis = list(plot.axis())
+axis[0] = -width # Make sure left side has enough for bar
+#axis[1] = axis[1] * 1.20 # Add 20% to the right to make sure it fits
+axis[2] = 0 # Make y-axis start at 0
+axis[3] = axis[3] * 1.10 # Add 10% to the top
+plot.axis(axis)
+
+plt.show()
diff --git a/utils/launch.sh b/utils/launch.sh
new file mode 100755
index 0000000..8afd6e4
--- /dev/null
+++ b/utils/launch.sh
@@ -0,0 +1,108 @@
+#!/usr/bin/env bash
+
+usage() {
+ if [ "$*" ]; then
+ echo "$*"
+ echo
+ fi
+ echo "Usage: ${NAME} [--listen PORT] [--vnc VNC_HOST:PORT] [--cert CERT]"
+ echo
+ echo "Starts the WebSockets proxy and a mini-webserver and "
+ echo "provides a cut-and-paste URL to go to."
+ echo
+ echo " --listen PORT Port for proxy/webserver to listen on"
+ echo " Default: 6080"
+ echo " --vnc VNC_HOST:PORT VNC server host:port proxy target"
+ echo " Default: localhost:5900"
+ echo " --cert CERT Path to combined cert/key file"
+ echo " Default: self.pem"
+ exit 2
+}
+
+NAME="$(basename $0)"
+HERE="$(cd "$(dirname "$0")" && pwd)"
+PORT="6080"
+VNC_DEST="localhost:5900"
+CERT=""
+proxy_pid=""
+
+die() {
+ echo "$*"
+ exit 1
+}
+
+cleanup() {
+ trap - TERM QUIT INT EXIT
+ trap "true" CHLD # Ignore cleanup messages
+ echo
+ if [ -n "${proxy_pid}" ]; then
+ echo "Terminating WebSockets proxy (${proxy_pid})"
+ kill ${proxy_pid}
+ fi
+}
+
+# Process Arguments
+
+# Arguments that only apply to chrooter itself
+while [ "$*" ]; do
+ param=$1; shift; OPTARG=$1
+ case $param in
+ --listen) PORT="${OPTARG}"; shift ;;
+ --vnc) VNC_DEST="${OPTARG}"; shift ;;
+ --cert) CERT="${OPTARG}"; shift ;;
+ -h|--help) usage ;;
+ -*) usage "Unknown chrooter option: ${param}" ;;
+ *) break ;;
+ esac
+done
+
+# Sanity checks
+which netstat >/dev/null 2>&1 \
+ || die "Must have netstat installed"
+
+netstat -ltn | grep -qs "${PORT}.*LISTEN" \
+ && die "Port ${PORT} in use. Try --listen PORT"
+
+trap "cleanup" TERM QUIT INT EXIT
+
+# Find vnc.html
+if [ -e "$(pwd)/vnc.html" ]; then
+ WEB=$(pwd)
+elif [ -e "${HERE}/../vnc.html" ]; then
+ WEB=${HERE}/../
+elif [ -e "${HERE}/vnc.html" ]; then
+ WEB=${HERE}
+else
+ die "Could not find vnc.html"
+fi
+
+# Find self.pem
+if [ -n "${CERT}" ]; then
+ if [ ! -e "${CERT}" ]; then
+ die "Could not find ${CERT}"
+ fi
+elif [ -e "$(pwd)/self.pem" ]; then
+ CERT="$(pwd)/self.pem"
+elif [ -e "${HERE}/../self.pem" ]; then
+ CERT="${HERE}/../self.pem"
+elif [ -e "${HERE}/self.pem" ]; then
+ CERT="${HERE}/self.pem"
+else
+ echo "Warning: could not find self.pem"
+fi
+
+echo "Starting webserver and WebSockets proxy on port ${PORT}"
+${HERE}/wsproxy.py --web ${WEB} ${CERT:+--cert ${CERT}} ${PORT} ${VNC_DEST} &
+proxy_pid="$!"
+sleep 1
+if ! ps -p ${proxy_pid} >/dev/null; then
+ proxy_pid=
+ echo "Failed to start WebSockets proxy"
+ exit 1
+fi
+
+echo -e "\n\nNavigate to this URL:\n"
+echo -e " http://$(hostname):${PORT}/vnc.html?host=$(hostname)&port=${PORT}\n"
+echo -e "Press Ctrl-C to exit\n\n"
+
+wait ${proxy_pid}
diff --git a/utils/rebind b/utils/rebind
new file mode 100755
index 0000000..2289aaa
--- /dev/null
+++ b/utils/rebind
@@ -0,0 +1,18 @@
+#!/usr/bin/env bash
+
+usage() {
+ echo "Usage: $(basename $0) OLD_PORT NEW_PORT COMMAND_LINE"
+ echo
+ echo "Launch COMMAND_LINE, but intercept system calls to bind"
+ echo "to OLD_PORT and instead bind them to localhost:NEW_PORT"
+ exit 2
+}
+
+# Parameter defaults
+mydir=$(readlink -f $(dirname ${0}))
+
+export REBIND_PORT_OLD="${1}"; shift
+export REBIND_PORT_NEW="${1}"; shift
+
+LD_PRELOAD=${mydir}/rebind.so "${@}"
+
diff --git a/utils/rebind.c b/utils/rebind.c
new file mode 100644
index 0000000..c7e83de
--- /dev/null
+++ b/utils/rebind.c
@@ -0,0 +1,94 @@
+/*
+ * rebind: Intercept bind calls and bind to a different port
+ * Copyright 2010 Joel Martin
+ * Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3)
+ *
+ * Overload (LD_PRELOAD) bind system call. If REBIND_PORT_OLD and
+ * REBIND_PORT_NEW environment variables are set then bind on the new
+ * port (of localhost) instead of the old port.
+ *
+ * This allows a proxy (such as wsproxy) to run on the old port and translate
+ * traffic to/from the new port.
+ *
+ * Usage:
+ * LD_PRELOAD=./rebind.so \
+ * REBIND_PORT_OLD=23 \
+ * REBIND_PORT_NEW=2023 \
+ * program
+ */
+
+//#define DO_DEBUG 1
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#define __USE_GNU 1 // Pull in RTLD_NEXT
+#include <dlfcn.h>
+
+#include <string.h>
+#include <netinet/in.h>
+
+
+#if defined(DO_DEBUG)
+#define DEBUG(...) \
+ fprintf(stderr, "wswrapper: "); \
+ fprintf(stderr, __VA_ARGS__);
+#else
+#define DEBUG(...)
+#endif
+
+
+int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
+{
+ static void * (*func)();
+ int do_move = 0;
+ struct sockaddr_in * addr_in = (struct sockaddr_in *)addr;
+ struct sockaddr_in addr_tmp;
+ socklen_t addrlen_tmp;
+ char * PORT_OLD, * PORT_NEW, * end1, * end2;
+ int ret, oldport, newport, askport = htons(addr_in->sin_port);
+ uint32_t askaddr = htons(addr_in->sin_addr.s_addr);
+ if (!func) func = (void *(*)()) dlsym(RTLD_NEXT, "bind");
+
+ DEBUG(">> bind(%d, _, %d), askaddr %d, askport %d\n",
+ sockfd, addrlen, askaddr, askport);
+
+ /* Determine if we should move this socket */
+ if (addr_in->sin_family == AF_INET) {
+ // TODO: support IPv6
+ PORT_OLD = getenv("REBIND_OLD_PORT");
+ PORT_NEW = getenv("REBIND_NEW_PORT");
+ if (PORT_OLD && (*PORT_OLD != '\0') &&
+ PORT_NEW && (*PORT_NEW != '\0')) {
+ oldport = strtol(PORT_OLD, &end1, 10);
+ newport = strtol(PORT_NEW, &end2, 10);
+ if (oldport && (*end1 == '\0') &&
+ newport && (*end2 == '\0') &&
+ (oldport == askport)) {
+ do_move = 1;
+ }
+ }
+ }
+
+ if (! do_move) {
+ /* Just pass everything right through to the real bind */
+ ret = (int) func(sockfd, addr, addrlen);
+ DEBUG("<< bind(%d, _, %d) ret %d\n", sockfd, addrlen, ret);
+ return ret;
+ }
+
+ DEBUG("binding fd %d on localhost:%d instead of 0x%x:%d\n",
+ sockfd, newport, ntohl(addr_in->sin_addr.s_addr), oldport);
+
+ /* Use a temporary location for the new address information */
+ addrlen_tmp = sizeof(addr_tmp);
+ memcpy(&addr_tmp, addr, addrlen_tmp);
+
+ /* Bind to other port on the loopback instead */
+ addr_tmp.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ addr_tmp.sin_port = htons(newport);
+ ret = (int) func(sockfd, &addr_tmp, addrlen_tmp);
+
+ DEBUG("<< bind(%d, _, %d) ret %d\n", sockfd, addrlen, ret);
+ return ret;
+}
diff --git a/utils/u2x11 b/utils/u2x11
new file mode 100755
index 0000000..fd3e4ba
--- /dev/null
+++ b/utils/u2x11
@@ -0,0 +1,28 @@
+#!/usr/bin/env bash
+#
+# Convert "U+..." commented entries in /usr/include/X11/keysymdef.h
+# into JavaScript for use by noVNC. Note this is likely to produce
+# a few duplicate properties with clashing values, that will need
+# resolving manually.
+#
+# Colin Dean <colin@xvpsource.org>
+#
+
+regex="^#define[ \t]+XK_[A-Za-z0-9_]+[ \t]+0x([0-9a-fA-F]+)[ \t]+\/\*[ \t]+U\+([0-9a-fA-F]+)[ \t]+[^*]+.[ \t]+\*\/[ \t]*$"
+echo "unicodeTable = {"
+while read line; do
+ if echo "${line}" | egrep -qs "${regex}"; then
+
+ x11=$(echo "${line}" | sed -r "s/${regex}/\1/")
+ vnc=$(echo "${line}" | sed -r "s/${regex}/\2/")
+
+ if echo "${vnc}" | egrep -qs "^00[2-9A-F][0-9A-F]$"; then
+ : # skip ISO Latin-1 (U+0020 to U+00FF) as 1-to-1 mapping
+ else
+ # note 1-to-1 is possible (e.g. for Euro symbol, U+20AC)
+ echo " 0x${vnc} : 0x${x11},"
+ fi
+ fi
+done < /usr/include/X11/keysymdef.h | uniq
+echo "};"
+
diff --git a/utils/web.py b/utils/web.py
new file mode 100755
index 0000000..23afca0
--- /dev/null
+++ b/utils/web.py
@@ -0,0 +1,55 @@
+#!/usr/bin/env python
+'''
+A super simple HTTP/HTTPS webserver for python. Automatically detect
+
+You can make a cert/key with openssl using:
+openssl req -new -x509 -days 365 -nodes -out self.pem -keyout self.pem
+as taken from http://docs.python.org/dev/library/ssl.html#certificates
+
+'''
+
+import traceback, sys
+import socket
+import ssl
+#import http.server as server # python 3.X
+import SimpleHTTPServer as server # python 2.X
+
+def do_request(connstream, from_addr):
+ x = object()
+ server.SimpleHTTPRequestHandler(connstream, from_addr, x)
+ connstream.close()
+
+def serve():
+ bindsocket = socket.socket()
+ bindsocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ #bindsocket.bind(('localhost', PORT))
+ bindsocket.bind(('', PORT))
+ bindsocket.listen(5)
+
+ print("serving on port", PORT)
+
+ while True:
+ try:
+ newsocket, from_addr = bindsocket.accept()
+ peek = newsocket.recv(1024, socket.MSG_PEEK)
+ if peek.startswith("\x16"):
+ connstream = ssl.wrap_socket(
+ newsocket,
+ server_side=True,
+ certfile='self.pem',
+ ssl_version=ssl.PROTOCOL_TLSv1)
+ else:
+ connstream = newsocket
+
+ do_request(connstream, from_addr)
+
+ except Exception:
+ traceback.print_exc()
+
+try:
+ PORT = int(sys.argv[1])
+except:
+ print "%s port" % sys.argv[0]
+ sys.exit(2)
+
+serve()
diff --git a/utils/websocket.py b/utils/websocket.py
new file mode 100644
index 0000000..8ae64d7
--- /dev/null
+++ b/utils/websocket.py
@@ -0,0 +1,871 @@
+#!/usr/bin/env python
+
+'''
+Python WebSocket library with support for "wss://" encryption.
+Copyright 2011 Joel Martin
+Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3)
+
+Supports following protocol versions:
+ - http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75
+ - http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76
+ - http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10
+
+You can make a cert/key with openssl using:
+openssl req -new -x509 -days 365 -nodes -out self.pem -keyout self.pem
+as taken from http://docs.python.org/dev/library/ssl.html#certificates
+
+'''
+
+import os, sys, time, errno, signal, socket, struct, traceback, select
+from cgi import parse_qsl
+from base64 import b64encode, b64decode
+
+# Imports that vary by python version
+if sys.hexversion > 0x3000000:
+ # python >= 3.0
+ from io import StringIO
+ from http.server import SimpleHTTPRequestHandler
+ from urllib.parse import urlsplit
+ b2s = lambda buf: buf.decode('latin_1')
+ s2b = lambda s: s.encode('latin_1')
+else:
+ # python 2.X
+ from cStringIO import StringIO
+ from SimpleHTTPServer import SimpleHTTPRequestHandler
+ from urlparse import urlsplit
+ # No-ops
+ b2s = lambda buf: buf
+ s2b = lambda s: s
+
+if sys.hexversion >= 0x2060000:
+ # python >= 2.6
+ from multiprocessing import Process
+ from hashlib import md5, sha1
+else:
+ # python < 2.6
+ Process = None
+ from md5 import md5
+ from sha import sha as sha1
+
+# Degraded functionality if these imports are missing
+for mod, sup in [('numpy', 'HyBi protocol'),
+ ('ssl', 'TLS/SSL/wss'), ('resource', 'daemonizing')]:
+ try:
+ globals()[mod] = __import__(mod)
+ except ImportError:
+ globals()[mod] = None
+ print("WARNING: no '%s' module, %s support disabled" % (
+ mod, sup))
+
+
+class WebSocketServer(object):
+ """
+ WebSockets server class.
+ Must be sub-classed with new_client method definition.
+ """
+
+ buffer_size = 65536
+
+ server_handshake_hixie = """HTTP/1.1 101 Web Socket Protocol Handshake\r
+Upgrade: WebSocket\r
+Connection: Upgrade\r
+%sWebSocket-Origin: %s\r
+%sWebSocket-Location: %s://%s%s\r
+"""
+
+ server_handshake_hybi = """HTTP/1.1 101 Switching Protocols\r
+Upgrade: websocket\r
+Connection: Upgrade\r
+Sec-WebSocket-Accept: %s\r
+"""
+
+ GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
+
+ policy_response = """<cross-domain-policy><allow-access-from domain="*" to-ports="*" /></cross-domain-policy>\n"""
+
+ class EClose(Exception):
+ pass
+
+ def __init__(self, listen_host='', listen_port=None, source_is_ipv6=False,
+ verbose=False, cert='', key='', ssl_only=None,
+ daemon=False, record='', web=''):
+
+ # settings
+ self.verbose = verbose
+ self.listen_host = listen_host
+ self.listen_port = listen_port
+ self.ssl_only = ssl_only
+ self.daemon = daemon
+ self.handler_id = 1
+
+ # Make paths settings absolute
+ self.cert = os.path.abspath(cert)
+ self.key = self.web = self.record = ''
+ if key:
+ self.key = os.path.abspath(key)
+ if web:
+ self.web = os.path.abspath(web)
+ if record:
+ self.record = os.path.abspath(record)
+
+ if self.web:
+ os.chdir(self.web)
+
+ # Sanity checks
+ if not ssl and self.ssl_only:
+ raise Exception("No 'ssl' module and SSL-only specified")
+ if self.daemon and not resource:
+ raise Exception("Module 'resource' required to daemonize")
+
+ # Show configuration
+ print("WebSocket server settings:")
+ print(" - Listen on %s:%s" % (
+ self.listen_host, self.listen_port))
+ print(" - Flash security policy server")
+ if self.web:
+ print(" - Web server. Web root: %s" % self.web)
+ if ssl:
+ if os.path.exists(self.cert):
+ print(" - SSL/TLS support")
+ if self.ssl_only:
+ print(" - Deny non-SSL/TLS connections")
+ else:
+ print(" - No SSL/TLS support (no cert file)")
+ else:
+ print(" - No SSL/TLS support (no 'ssl' module)")
+ if self.daemon:
+ print(" - Backgrounding (daemon)")
+ if self.record:
+ print(" - Recording to '%s.*'" % self.record)
+
+ #
+ # WebSocketServer static methods
+ #
+
+ @staticmethod
+ def socket(host, port=None, connect=False, prefer_ipv6=False):
+ """ Resolve a host (and optional port) to an IPv4 or IPv6
+ address. Create a socket. Bind to it if listen is set,
+ otherwise connect to it. Return the socket.
+ """
+ flags = 0
+ if host == '':
+ host = None
+ if connect and not port:
+ raise Exception("Connect mode requires a port")
+ if not connect:
+ flags = flags | socket.AI_PASSIVE
+ addrs = socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM,
+ socket.IPPROTO_TCP, flags)
+ if not addrs:
+ raise Exception("Could resolve host '%s'" % host)
+ addrs.sort(key=lambda x: x[0])
+ if prefer_ipv6:
+ addrs.reverse()
+ sock = socket.socket(addrs[0][0], addrs[0][1])
+ if connect:
+ sock.connect(addrs[0][4])
+ else:
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ sock.bind(addrs[0][4])
+ sock.listen(100)
+ return sock
+
+ @staticmethod
+ def daemonize(keepfd=None, chdir='/'):
+ os.umask(0)
+ if chdir:
+ os.chdir(chdir)
+ else:
+ os.chdir('/')
+ os.setgid(os.getgid()) # relinquish elevations
+ os.setuid(os.getuid()) # relinquish elevations
+
+ # Double fork to daemonize
+ if os.fork() > 0: os._exit(0) # Parent exits
+ os.setsid() # Obtain new process group
+ if os.fork() > 0: os._exit(0) # Parent exits
+
+ # Signal handling
+ def terminate(a,b): os._exit(0)
+ signal.signal(signal.SIGTERM, terminate)
+ signal.signal(signal.SIGINT, signal.SIG_IGN)
+
+ # Close open files
+ maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
+ if maxfd == resource.RLIM_INFINITY: maxfd = 256
+ for fd in reversed(range(maxfd)):
+ try:
+ if fd != keepfd:
+ os.close(fd)
+ except OSError:
+ _, exc, _ = sys.exc_info()
+ if exc.errno != errno.EBADF: raise
+
+ # Redirect I/O to /dev/null
+ os.dup2(os.open(os.devnull, os.O_RDWR), sys.stdin.fileno())
+ os.dup2(os.open(os.devnull, os.O_RDWR), sys.stdout.fileno())
+ os.dup2(os.open(os.devnull, os.O_RDWR), sys.stderr.fileno())
+
+ @staticmethod
+ def encode_hybi(buf, opcode, base64=False):
+ """ Encode a HyBi style WebSocket frame.
+ Optional opcode:
+ 0x0 - continuation
+ 0x1 - text frame (base64 encode buf)
+ 0x2 - binary frame (use raw buf)
+ 0x8 - connection close
+ 0x9 - ping
+ 0xA - pong
+ """
+ if base64:
+ buf = b64encode(buf)
+
+ b1 = 0x80 | (opcode & 0x0f) # FIN + opcode
+ payload_len = len(buf)
+ if payload_len <= 125:
+ header = struct.pack('>BB', b1, payload_len)
+ elif payload_len > 125 and payload_len <= 65536:
+ header = struct.pack('>BBH', b1, 126, payload_len)
+ elif payload_len >= 65536:
+ header = struct.pack('>BBQ', b1, 127, payload_len)
+
+ #print("Encoded: %s" % repr(header + buf))
+
+ return header + buf, len(header), 0
+
+ @staticmethod
+ def decode_hybi(buf, base64=False):
+ """ Decode HyBi style WebSocket packets.
+ Returns:
+ {'fin' : 0_or_1,
+ 'opcode' : number,
+ 'mask' : 32_bit_number,
+ 'hlen' : header_bytes_number,
+ 'length' : payload_bytes_number,
+ 'payload' : decoded_buffer,
+ 'left' : bytes_left_number,
+ 'close_code' : number,
+ 'close_reason' : string}
+ """
+
+ f = {'fin' : 0,
+ 'opcode' : 0,
+ 'mask' : 0,
+ 'hlen' : 2,
+ 'length' : 0,
+ 'payload' : None,
+ 'left' : 0,
+ 'close_code' : None,
+ 'close_reason' : None}
+
+ blen = len(buf)
+ f['left'] = blen
+
+ if blen < f['hlen']:
+ return f # Incomplete frame header
+
+ b1, b2 = struct.unpack_from(">BB", buf)
+ f['opcode'] = b1 & 0x0f
+ f['fin'] = (b1 & 0x80) >> 7
+ has_mask = (b2 & 0x80) >> 7
+
+ f['length'] = b2 & 0x7f
+
+ if f['length'] == 126:
+ f['hlen'] = 4
+ if blen < f['hlen']:
+ return f # Incomplete frame header
+ (f['length'],) = struct.unpack_from('>xxH', buf)
+ elif f['length'] == 127:
+ f['hlen'] = 10
+ if blen < f['hlen']:
+ return f # Incomplete frame header
+ (f['length'],) = struct.unpack_from('>xxQ', buf)
+
+ full_len = f['hlen'] + has_mask * 4 + f['length']
+
+ if blen < full_len: # Incomplete frame
+ return f # Incomplete frame header
+
+ # Number of bytes that are part of the next frame(s)
+ f['left'] = blen - full_len
+
+ # Process 1 frame
+ if has_mask:
+ # unmask payload
+ f['mask'] = buf[f['hlen']:f['hlen']+4]
+ b = c = ''
+ if f['length'] >= 4:
+ mask = numpy.frombuffer(buf, dtype=numpy.dtype('<u4'),
+ offset=f['hlen'], count=1)
+ data = numpy.frombuffer(buf, dtype=numpy.dtype('<u4'),
+ offset=f['hlen'] + 4, count=int(f['length'] / 4))
+ #b = numpy.bitwise_xor(data, mask).data
+ b = numpy.bitwise_xor(data, mask).tostring()
+
+ if f['length'] % 4:
+ print("Partial unmask")
+ mask = numpy.frombuffer(buf, dtype=numpy.dtype('B'),
+ offset=f['hlen'], count=(f['length'] % 4))
+ data = numpy.frombuffer(buf, dtype=numpy.dtype('B'),
+ offset=full_len - (f['length'] % 4),
+ count=(f['length'] % 4))
+ c = numpy.bitwise_xor(data, mask).tostring()
+ f['payload'] = b + c
+ else:
+ print("Unmasked frame: %s" % repr(buf))
+ f['payload'] = buf[(f['hlen'] + has_mask * 4):full_len]
+
+ if base64 and f['opcode'] in [1, 2]:
+ try:
+ f['payload'] = b64decode(f['payload'])
+ except:
+ print("Exception while b64decoding buffer: %s" %
+ repr(buf))
+ raise
+
+ if f['opcode'] == 0x08:
+ if f['length'] >= 2:
+ f['close_code'] = struct.unpack_from(">H", f['payload'])
+ if f['length'] > 3:
+ f['close_reason'] = f['payload'][2:]
+
+ return f
+
+ @staticmethod
+ def encode_hixie(buf):
+ return s2b("\x00" + b2s(b64encode(buf)) + "\xff"), 1, 1
+
+ @staticmethod
+ def decode_hixie(buf):
+ end = buf.find(s2b('\xff'))
+ return {'payload': b64decode(buf[1:end]),
+ 'hlen': 1,
+ 'length': end - 1,
+ 'left': len(buf) - (end + 1)}
+
+
+ @staticmethod
+ def gen_md5(keys):
+ """ Generate hash value for WebSockets hixie-76. """
+ key1 = keys['Sec-WebSocket-Key1']
+ key2 = keys['Sec-WebSocket-Key2']
+ key3 = keys['key3']
+ spaces1 = key1.count(" ")
+ spaces2 = key2.count(" ")
+ num1 = int("".join([c for c in key1 if c.isdigit()])) / spaces1
+ num2 = int("".join([c for c in key2 if c.isdigit()])) / spaces2
+
+ return b2s(md5(struct.pack('>II8s',
+ int(num1), int(num2), key3)).digest())
+
+ #
+ # WebSocketServer logging/output functions
+ #
+
+ def traffic(self, token="."):
+ """ Show traffic flow in verbose mode. """
+ if self.verbose and not self.daemon:
+ sys.stdout.write(token)
+ sys.stdout.flush()
+
+ def msg(self, msg):
+ """ Output message with handler_id prefix. """
+ if not self.daemon:
+ print("% 3d: %s" % (self.handler_id, msg))
+
+ def vmsg(self, msg):
+ """ Same as msg() but only if verbose. """
+ if self.verbose:
+ self.msg(msg)
+
+ #
+ # Main WebSocketServer methods
+ #
+ def send_frames(self, bufs=None):
+ """ Encode and send WebSocket frames. Any frames already
+ queued will be sent first. If buf is not set then only queued
+ frames will be sent. Returns the number of pending frames that
+ could not be fully sent. If returned pending frames is greater
+ than 0, then the caller should call again when the socket is
+ ready. """
+
+ tdelta = int(time.time()*1000) - self.start_time
+
+ if bufs:
+ for buf in bufs:
+ if self.version.startswith("hybi"):
+ if self.base64:
+ encbuf, lenhead, lentail = self.encode_hybi(
+ buf, opcode=1, base64=True)
+ else:
+ encbuf, lenhead, lentail = self.encode_hybi(
+ buf, opcode=2, base64=False)
+
+ else:
+ encbuf, lenhead, lentail = self.encode_hixie(buf)
+
+ if self.rec:
+ self.rec.write("%s,\n" %
+ repr("{%s{" % tdelta
+ + encbuf[lenhead:-lentail]))
+
+ self.send_parts.append(encbuf)
+
+ while self.send_parts:
+ # Send pending frames
+ buf = self.send_parts.pop(0)
+ sent = self.client.send(buf)
+
+ if sent == len(buf):
+ self.traffic("<")
+ else:
+ self.traffic("<.")
+ self.send_parts.insert(0, buf[sent:])
+ break
+
+ return len(self.send_parts)
+
+ def recv_frames(self):
+ """ Receive and decode WebSocket frames.
+
+ Returns:
+ (bufs_list, closed_string)
+ """
+
+ closed = False
+ bufs = []
+ tdelta = int(time.time()*1000) - self.start_time
+
+ buf = self.client.recv(self.buffer_size)
+ if len(buf) == 0:
+ closed = "Client closed abruptly"
+ return bufs, closed
+
+ if self.recv_part:
+ # Add partially received frames to current read buffer
+ buf = self.recv_part + buf
+ self.recv_part = None
+
+ while buf:
+ if self.version.startswith("hybi"):
+
+ frame = self.decode_hybi(buf, base64=self.base64)
+ #print("Received buf: %s, frame: %s" % (repr(buf), frame))
+
+ if frame['payload'] == None:
+ # Incomplete/partial frame
+ self.traffic("}.")
+ if frame['left'] > 0:
+ self.recv_part = buf[-frame['left']:]
+ break
+ else:
+ if frame['opcode'] == 0x8: # connection close
+ closed = "Client closed, reason: %s - %s" % (
+ frame['close_code'],
+ frame['close_reason'])
+ break
+
+ else:
+ if buf[0:2] == '\xff\x00':
+ closed = "Client sent orderly close frame"
+ break
+
+ elif buf[0:2] == '\x00\xff':
+ buf = buf[2:]
+ continue # No-op
+
+ elif buf.count(s2b('\xff')) == 0:
+ # Partial frame
+ self.traffic("}.")
+ self.recv_part = buf
+ break
+
+ frame = self.decode_hixie(buf)
+
+ self.traffic("}")
+
+ if self.rec:
+ start = frame['hlen']
+ end = frame['hlen'] + frame['length']
+ self.rec.write("%s,\n" %
+ repr("}%s}" % tdelta + buf[start:end]))
+
+
+ bufs.append(frame['payload'])
+
+ if frame['left']:
+ buf = buf[-frame['left']:]
+ else:
+ buf = ''
+
+ return bufs, closed
+
+ def send_close(self, code=None, reason=''):
+ """ Send a WebSocket orderly close frame. """
+
+ if self.version.startswith("hybi"):
+ msg = s2b('')
+ if code != None:
+ msg = struct.pack(">H%ds" % (len(reason)), code)
+
+ buf, h, t = self.encode_hybi(msg, opcode=0x08, base64=False)
+ self.client.send(buf)
+
+ elif self.version == "hixie-76":
+ buf = s2b('\xff\x00')
+ self.client.send(buf)
+
+ # No orderly close for 75
+
+ def do_handshake(self, sock, address):
+ """
+ do_handshake does the following:
+ - Peek at the first few bytes from the socket.
+ - If the connection is Flash policy request then answer it,
+ close the socket and return.
+ - If the connection is an HTTPS/SSL/TLS connection then SSL
+ wrap the socket.
+ - Read from the (possibly wrapped) socket.
+ - If we have received a HTTP GET request and the webserver
+ functionality is enabled, answer it, close the socket and
+ return.
+ - Assume we have a WebSockets connection, parse the client
+ handshake data.
+ - Send a WebSockets handshake server response.
+ - Return the socket for this WebSocket client.
+ """
+
+ stype = ""
+
+ ready = select.select([sock], [], [], 3)[0]
+ if not ready:
+ raise self.EClose("ignoring socket not ready")
+ # Peek, but do not read the data so that we have a opportunity
+ # to SSL wrap the socket first
+ handshake = sock.recv(1024, socket.MSG_PEEK)
+ #self.msg("Handshake [%s]" % handshake)
+
+ if handshake == "":
+ raise self.EClose("ignoring empty handshake")
+
+ elif handshake.startswith(s2b("<policy-file-request/>")):
+ # Answer Flash policy request
+ handshake = sock.recv(1024)
+ sock.send(s2b(self.policy_response))
+ raise self.EClose("Sending flash policy response")
+
+ elif handshake[0] in ("\x16", "\x80"):
+ # SSL wrap the connection
+ if not ssl:
+ raise self.EClose("SSL connection but no 'ssl' module")
+ if not os.path.exists(self.cert):
+ raise self.EClose("SSL connection but '%s' not found"
+ % self.cert)
+ retsock = None
+ try:
+ retsock = ssl.wrap_socket(
+ sock,
+ server_side=True,
+ certfile=self.cert,
+ keyfile=self.key)
+ except ssl.SSLError:
+ _, x, _ = sys.exc_info()
+ if x.args[0] == ssl.SSL_ERROR_EOF:
+ raise self.EClose("")
+ else:
+ raise
+
+ scheme = "wss"
+ stype = "SSL/TLS (wss://)"
+
+ elif self.ssl_only:
+ raise self.EClose("non-SSL connection received but disallowed")
+
+ else:
+ retsock = sock
+ scheme = "ws"
+ stype = "Plain non-SSL (ws://)"
+
+ wsh = WSRequestHandler(retsock, address, not self.web)
+ if wsh.last_code == 101:
+ # Continue on to handle WebSocket upgrade
+ pass
+ elif wsh.last_code == 405:
+ raise self.EClose("Normal web request received but disallowed")
+ elif wsh.last_code < 200 or wsh.last_code >= 300:
+ raise self.EClose(wsh.last_message)
+ elif self.verbose:
+ raise self.EClose(wsh.last_message)
+ else:
+ raise self.EClose("")
+
+ h = self.headers = wsh.headers
+ path = self.path = wsh.path
+
+ prot = 'WebSocket-Protocol'
+ protocols = h.get('Sec-'+prot, h.get(prot, '')).split(',')
+
+ ver = h.get('Sec-WebSocket-Version')
+ if ver:
+ # HyBi/IETF version of the protocol
+
+ if sys.hexversion < 0x2060000 or not numpy:
+ raise self.EClose("Python >= 2.6 and numpy module is required for HyBi-07 or greater")
+
+ # HyBi-07 report version 7
+ # HyBi-08 - HyBi-10 report version 8
+ if ver in ['7', '8']:
+ self.version = "hybi-0" + ver
+ else:
+ raise self.EClose('Unsupported protocol version %s' % ver)
+
+ key = h['Sec-WebSocket-Key']
+
+ # Choose binary if client supports it
+ if 'binary' in protocols:
+ self.base64 = False
+ elif 'base64' in protocols:
+ self.base64 = True
+ else:
+ raise self.EClose("Client must support 'binary' or 'base64' protocol")
+
+ # Generate the hash value for the accept header
+ accept = b64encode(sha1(s2b(key + self.GUID)).digest())
+
+ response = self.server_handshake_hybi % accept
+ if self.base64:
+ response += "Sec-WebSocket-Protocol: base64\r\n"
+ else:
+ response += "Sec-WebSocket-Protocol: binary\r\n"
+ response += "\r\n"
+
+ else:
+ # Hixie version of the protocol (75 or 76)
+
+ if h.get('key3'):
+ trailer = self.gen_md5(h)
+ pre = "Sec-"
+ self.version = "hixie-76"
+ else:
+ trailer = ""
+ pre = ""
+ self.version = "hixie-75"
+
+ # We only support base64 in Hixie era
+ self.base64 = True
+
+ response = self.server_handshake_hixie % (pre,
+ h['Origin'], pre, scheme, h['Host'], path)
+
+ if 'base64' in protocols:
+ response += "%sWebSocket-Protocol: base64\r\n" % pre
+ else:
+ self.msg("Warning: client does not report 'base64' protocol support")
+ response += "\r\n" + trailer
+
+ self.msg("%s: %s WebSocket connection" % (address[0], stype))
+ self.msg("%s: Version %s, base64: '%s'" % (address[0],
+ self.version, self.base64))
+
+ # Send server WebSockets handshake response
+ #self.msg("sending response [%s]" % response)
+ retsock.send(s2b(response))
+
+ # Return the WebSockets socket which may be SSL wrapped
+ return retsock
+
+
+ #
+ # Events that can/should be overridden in sub-classes
+ #
+ def started(self):
+ """ Called after WebSockets startup """
+ self.vmsg("WebSockets server started")
+
+ def poll(self):
+ """ Run periodically while waiting for connections. """
+ #self.vmsg("Running poll()")
+ pass
+
+ def fallback_SIGCHLD(self, sig, stack):
+ # Reap zombies when using os.fork() (python 2.4)
+ self.vmsg("Got SIGCHLD, reaping zombies")
+ try:
+ result = os.waitpid(-1, os.WNOHANG)
+ while result[0]:
+ self.vmsg("Reaped child process %s" % result[0])
+ result = os.waitpid(-1, os.WNOHANG)
+ except (OSError):
+ pass
+
+ def do_SIGINT(self, sig, stack):
+ self.msg("Got SIGINT, exiting")
+ sys.exit(0)
+
+ def top_new_client(self, startsock, address):
+ """ Do something with a WebSockets client connection. """
+ # Initialize per client settings
+ self.send_parts = []
+ self.recv_part = None
+ self.base64 = False
+ self.rec = None
+ self.start_time = int(time.time()*1000)
+
+ # handler process
+ try:
+ try:
+ self.client = self.do_handshake(startsock, address)
+
+ if self.record:
+ # Record raw frame data as JavaScript array
+ fname = "%s.%s" % (self.record,
+ self.handler_id)
+ self.msg("opening record file: %s" % fname)
+ self.rec = open(fname, 'w+')
+ self.rec.write("var VNC_frame_data = [\n")
+
+ self.new_client()
+ except self.EClose:
+ _, exc, _ = sys.exc_info()
+ # Connection was not a WebSockets connection
+ if exc.args[0]:
+ self.msg("%s: %s" % (address[0], exc.args[0]))
+ except Exception:
+ _, exc, _ = sys.exc_info()
+ self.msg("handler exception: %s" % str(exc))
+ if self.verbose:
+ self.msg(traceback.format_exc())
+ finally:
+ if self.rec:
+ self.rec.write("'EOF']\n")
+ self.rec.close()
+
+ if self.client and self.client != startsock:
+ self.client.close()
+
+ def new_client(self):
+ """ Do something with a WebSockets client connection. """
+ raise("WebSocketServer.new_client() must be overloaded")
+
+ def start_server(self):
+ """
+ Daemonize if requested. Listen for for connections. Run
+ do_handshake() method for each connection. If the connection
+ is a WebSockets client then call new_client() method (which must
+ be overridden) for each new client connection.
+ """
+ lsock = self.socket(self.listen_host, self.listen_port)
+
+ if self.daemon:
+ self.daemonize(keepfd=lsock.fileno(), chdir=self.web)
+
+ self.started() # Some things need to happen after daemonizing
+
+ # Allow override of SIGINT
+ signal.signal(signal.SIGINT, self.do_SIGINT)
+ if not Process:
+ # os.fork() (python 2.4) child reaper
+ signal.signal(signal.SIGCHLD, self.fallback_SIGCHLD)
+
+ while True:
+ try:
+ try:
+ self.client = None
+ startsock = None
+ pid = err = 0
+
+ try:
+ self.poll()
+
+ ready = select.select([lsock], [], [], 1)[0]
+ if lsock in ready:
+ startsock, address = lsock.accept()
+ else:
+ continue
+ except Exception:
+ _, exc, _ = sys.exc_info()
+ if hasattr(exc, 'errno'):
+ err = exc.errno
+ elif hasattr(exc, 'args'):
+ err = exc.args[0]
+ else:
+ err = exc[0]
+ if err == errno.EINTR:
+ self.vmsg("Ignoring interrupted syscall")
+ continue
+ else:
+ raise
+
+ if Process:
+ self.vmsg('%s: new handler Process' % address[0])
+ p = Process(target=self.top_new_client,
+ args=(startsock, address))
+ p.start()
+ # child will not return
+ else:
+ # python 2.4
+ self.vmsg('%s: forking handler' % address[0])
+ pid = os.fork()
+ if pid == 0:
+ # child handler process
+ self.top_new_client(startsock, address)
+ break # child process exits
+
+ # parent process
+ self.handler_id += 1
+
+ except KeyboardInterrupt:
+ _, exc, _ = sys.exc_info()
+ print("In KeyboardInterrupt")
+ pass
+ except SystemExit:
+ _, exc, _ = sys.exc_info()
+ print("In SystemExit")
+ break
+ except Exception:
+ _, exc, _ = sys.exc_info()
+ self.msg("handler exception: %s" % str(exc))
+ if self.verbose:
+ self.msg(traceback.format_exc())
+
+ finally:
+ if startsock:
+ startsock.close()
+
+
+# HTTP handler with WebSocket upgrade support
+class WSRequestHandler(SimpleHTTPRequestHandler):
+ def __init__(self, req, addr, only_upgrade=False):
+ self.only_upgrade = only_upgrade # only allow upgrades
+ SimpleHTTPRequestHandler.__init__(self, req, addr, object())
+
+ def do_GET(self):
+ if (self.headers.get('upgrade') and
+ self.headers.get('upgrade').lower() == 'websocket'):
+
+ if (self.headers.get('sec-websocket-key1') or
+ self.headers.get('websocket-key1')):
+ # For Hixie-76 read out the key hash
+ self.headers.__setitem__('key3', self.rfile.read(8))
+
+ # Just indicate that an WebSocket upgrade is needed
+ self.last_code = 101
+ self.last_message = "101 Switching Protocols"
+ elif self.only_upgrade:
+ # Normal web request responses are disabled
+ self.last_code = 405
+ self.last_message = "405 Method Not Allowed"
+ else:
+ SimpleHTTPRequestHandler.do_GET(self)
+
+ def send_response(self, code, message=None):
+ # Save the status code
+ self.last_code = code
+ SimpleHTTPRequestHandler.send_response(self, code, message)
+
+ def log_message(self, f, *args):
+ # Save instead of printing
+ self.last_message = f % args
+
diff --git a/utils/websocket.pyc b/utils/websocket.pyc
new file mode 100644
index 0000000..59a5592
--- /dev/null
+++ b/utils/websocket.pyc
Binary files differ
diff --git a/utils/websockify b/utils/websockify
new file mode 100755
index 0000000..59b6e0d
--- /dev/null
+++ b/utils/websockify
@@ -0,0 +1,278 @@
+#!/usr/bin/env python
+
+'''
+A WebSocket to TCP socket proxy with support for "wss://" encryption.
+Copyright 2011 Joel Martin
+Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3)
+
+You can make a cert/key with openssl using:
+openssl req -new -x509 -days 365 -nodes -out self.pem -keyout self.pem
+as taken from http://docs.python.org/dev/library/ssl.html#certificates
+
+'''
+
+import socket, optparse, time, os, sys, subprocess
+from select import select
+from websocket import WebSocketServer
+
+class WebSocketProxy(WebSocketServer):
+ """
+ Proxy traffic to and from a WebSockets client to a normal TCP
+ socket server target. All traffic to/from the client is base64
+ encoded/decoded to allow binary data to be sent/received to/from
+ the target.
+ """
+
+ buffer_size = 65536
+
+ traffic_legend = """
+Traffic Legend:
+ } - Client receive
+ }. - Client receive partial
+ { - Target receive
+
+ > - Target send
+ >. - Target send partial
+ < - Client send
+ <. - Client send partial
+"""
+
+ def __init__(self, *args, **kwargs):
+ # Save off proxy specific options
+ self.target_host = kwargs.pop('target_host')
+ self.target_port = kwargs.pop('target_port')
+ self.wrap_cmd = kwargs.pop('wrap_cmd')
+ self.wrap_mode = kwargs.pop('wrap_mode')
+ # Last 3 timestamps command was run
+ self.wrap_times = [0, 0, 0]
+
+ if self.wrap_cmd:
+ rebinder_path = ['./', os.path.dirname(sys.argv[0])]
+ self.rebinder = None
+
+ for rdir in rebinder_path:
+ rpath = os.path.join(rdir, "rebind.so")
+ if os.path.exists(rpath):
+ self.rebinder = rpath
+ break
+
+ if not self.rebinder:
+ raise Exception("rebind.so not found, perhaps you need to run make")
+
+ self.target_host = "127.0.0.1" # Loopback
+ # Find a free high port
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ sock.bind(('', 0))
+ self.target_port = sock.getsockname()[1]
+ sock.close()
+
+ os.environ.update({
+ "LD_PRELOAD": self.rebinder,
+ "REBIND_OLD_PORT": str(kwargs['listen_port']),
+ "REBIND_NEW_PORT": str(self.target_port)})
+
+ WebSocketServer.__init__(self, *args, **kwargs)
+
+ def run_wrap_cmd(self):
+ print("Starting '%s'" % " ".join(self.wrap_cmd))
+ self.wrap_times.append(time.time())
+ self.wrap_times.pop(0)
+ self.cmd = subprocess.Popen(
+ self.wrap_cmd, env=os.environ)
+ self.spawn_message = True
+
+ def started(self):
+ """
+ Called after Websockets server startup (i.e. after daemonize)
+ """
+ # Need to call wrapped command after daemonization so we can
+ # know when the wrapped command exits
+ if self.wrap_cmd:
+ print(" - proxying from %s:%s to '%s' (port %s)\n" % (
+ self.listen_host, self.listen_port,
+ " ".join(self.wrap_cmd), self.target_port))
+ self.run_wrap_cmd()
+ else:
+ print(" - proxying from %s:%s to %s:%s\n" % (
+ self.listen_host, self.listen_port,
+ self.target_host, self.target_port))
+
+ def poll(self):
+ # If we are wrapping a command, check it's status
+
+ if self.wrap_cmd and self.cmd:
+ ret = self.cmd.poll()
+ if ret != None:
+ self.vmsg("Wrapped command exited (or daemon). Returned %s" % ret)
+ self.cmd = None
+
+ if self.wrap_cmd and self.cmd == None:
+ # Response to wrapped command being gone
+ if self.wrap_mode == "ignore":
+ pass
+ elif self.wrap_mode == "exit":
+ sys.exit(ret)
+ elif self.wrap_mode == "respawn":
+ now = time.time()
+ avg = sum(self.wrap_times)/len(self.wrap_times)
+ if (now - avg) < 10:
+ # 3 times in the last 10 seconds
+ if self.spawn_message:
+ print("Command respawning too fast")
+ self.spawn_message = False
+ else:
+ self.run_wrap_cmd()
+
+ #
+ # Routines above this point are run in the master listener
+ # process.
+ #
+
+ #
+ # Routines below this point are connection handler routines and
+ # will be run in a separate forked process for each connection.
+ #
+
+ def new_client(self):
+ """
+ Called after a new WebSocket connection has been established.
+ """
+
+ # Connect to the target
+ self.msg("connecting to: %s:%s" % (
+ self.target_host, self.target_port))
+ tsock = self.socket(self.target_host, self.target_port,
+ connect=True)
+
+ if self.verbose and not self.daemon:
+ print(self.traffic_legend)
+
+ # Start proxying
+ try:
+ self.do_proxy(tsock)
+ except:
+ if tsock:
+ tsock.shutdown(socket.SHUT_RDWR)
+ tsock.close()
+ self.vmsg("%s:%s: Target closed" %(
+ self.target_host, self.target_port))
+ raise
+
+ def do_proxy(self, target):
+ """
+ Proxy client WebSocket to normal target socket.
+ """
+ cqueue = []
+ c_pend = 0
+ tqueue = []
+ rlist = [self.client, target]
+
+ while True:
+ wlist = []
+
+ if tqueue: wlist.append(target)
+ if cqueue or c_pend: wlist.append(self.client)
+ ins, outs, excepts = select(rlist, wlist, [], 1)
+ if excepts: raise Exception("Socket exception")
+
+ if target in outs:
+ # Send queued client data to the target
+ dat = tqueue.pop(0)
+ sent = target.send(dat)
+ if sent == len(dat):
+ self.traffic(">")
+ else:
+ # requeue the remaining data
+ tqueue.insert(0, dat[sent:])
+ self.traffic(".>")
+
+
+ if target in ins:
+ # Receive target data, encode it and queue for client
+ buf = target.recv(self.buffer_size)
+ if len(buf) == 0: raise self.EClose("Target closed")
+
+ cqueue.append(buf)
+ self.traffic("{")
+
+
+ if self.client in outs:
+ # Send queued target data to the client
+ c_pend = self.send_frames(cqueue)
+
+ cqueue = []
+
+
+ if self.client in ins:
+ # Receive client data, decode it, and queue for target
+ bufs, closed = self.recv_frames()
+ tqueue.extend(bufs)
+
+ if closed:
+ # TODO: What about blocking on client socket?
+ self.send_close()
+ raise self.EClose(closed)
+
+if __name__ == '__main__':
+ usage = "\n %prog [options]"
+ usage += " [source_addr:]source_port target_addr:target_port"
+ usage += "\n %prog [options]"
+ usage += " [source_addr:]source_port -- WRAP_COMMAND_LINE"
+ parser = optparse.OptionParser(usage=usage)
+ parser.add_option("--verbose", "-v", action="store_true",
+ help="verbose messages and per frame traffic")
+ parser.add_option("--record",
+ help="record sessions to FILE.[session_number]", metavar="FILE")
+ parser.add_option("--daemon", "-D",
+ dest="daemon", action="store_true",
+ help="become a daemon (background process)")
+ parser.add_option("--cert", default="self.pem",
+ help="SSL certificate file")
+ parser.add_option("--key", default=None,
+ help="SSL key file (if separate from cert)")
+ parser.add_option("--ssl-only", action="store_true",
+ help="disallow non-encrypted connections")
+ parser.add_option("--web", default=None, metavar="DIR",
+ help="run webserver on same port. Serve files from DIR.")
+ parser.add_option("--wrap-mode", default="exit", metavar="MODE",
+ choices=["exit", "ignore", "respawn"],
+ help="action to take when the wrapped program exits "
+ "or daemonizes: exit (default), ignore, respawn")
+ (opts, args) = parser.parse_args()
+
+ # Sanity checks
+ if len(args) < 2:
+ parser.error("Too few arguments")
+ if sys.argv.count('--'):
+ opts.wrap_cmd = args[1:]
+ else:
+ opts.wrap_cmd = None
+ if len(args) > 2:
+ parser.error("Too many arguments")
+
+ if opts.ssl_only and not os.path.exists(opts.cert):
+ parser.error("SSL only and %s not found" % opts.cert)
+
+ # Parse host:port and convert ports to numbers
+ if args[0].count(':') > 0:
+ opts.listen_host, opts.listen_port = args[0].rsplit(':', 1)
+ else:
+ opts.listen_host, opts.listen_port = '', args[0]
+
+ try: opts.listen_port = int(opts.listen_port)
+ except: parser.error("Error parsing listen port")
+
+ if opts.wrap_cmd:
+ opts.target_host = None
+ opts.target_port = None
+ else:
+ if args[1].count(':') > 0:
+ opts.target_host, opts.target_port = args[1].rsplit(':', 1)
+ else:
+ parser.error("Error parsing target")
+ try: opts.target_port = int(opts.target_port)
+ except: parser.error("Error parsing target port")
+
+ # Create and start the WebSockets proxy
+ server = WebSocketProxy(**opts.__dict__)
+ server.start_server()
diff --git a/utils/wsproxy.py b/utils/wsproxy.py
new file mode 120000
index 0000000..05b5af4
--- /dev/null
+++ b/utils/wsproxy.py
@@ -0,0 +1 @@
+websockify \ No newline at end of file
diff --git a/vnc.html b/vnc.html
index 8aaf715..cb91b5c 100644
--- a/vnc.html
+++ b/vnc.html
@@ -85,9 +85,11 @@
</div>
<div class="noVNC_buttons_right">
+ <div class="noVNC-menu">
<input type=button value="Settings"
id="menuButton" onclick="UI.clickSettingsMenu();">
- <span id="noVNC_settings_menu" style="display: none;"
+ <span id="noVNC_settings_menu" class="noVNC-settings-menu"
+ style="display: none;"
onmouseover="UI.displayBlur();"
onmouseout="UI.displayFocus();">
<ul>
@@ -117,6 +119,7 @@
onclick="UI.settingsApply()"></li>
</ul>
</span>
+ </div>
</div>
<div class="noVNC_buttons_right">
@@ -127,7 +130,7 @@
</div> <!--! end of #status-bar -->
<div id="noVNC_viewport">
- <canvas id="noVNC_canvas" width="640px" height="480px" style="border-style: solid;">
+ <canvas id="noVNC_canvas" width="640px" height="480px">
Canvas not supported.
</canvas>
</div>
@@ -143,6 +146,10 @@
<!-- JavaScript at the bottom for fast page loading -->
+ <script>
+ var INCLUDE_URI = "js/";
+ </script>
+
<script src="js/noVNC/util.js"></script>
<script src="js/noVNC/webutil.js"></script>
<script src="js/noVNC/logo.js"></script>