├── _config.yml ├── bin ├── certs │ └── README.md ├── genCert.bash ├── peer-vnc └── peer-vnc-direct ├── upload └── README.md ├── front ├── images │ ├── alt.png │ ├── esc.png │ ├── tab.png │ ├── ctrl.png │ ├── drag.png │ ├── power.png │ ├── connect.png │ ├── keyboard.png │ ├── settings.png │ ├── clipboard.png │ ├── ctrlaltdel.png │ ├── disconnect.png │ ├── fullscreen.png │ ├── mouse_left.png │ ├── mouse_none.png │ ├── mouse_middle.png │ ├── mouse_right.png │ ├── screen_57x57.png │ ├── screen_320x460.png │ ├── screen_700x700.png │ └── toggleextrakeys.png ├── .gitignore ├── include │ ├── Orbitron700.ttf │ ├── Orbitron700.woff │ ├── blue.css │ ├── playback.js │ ├── black.css │ ├── base64.js │ ├── webutil.js │ ├── chrome-app │ │ └── tcp-client.js │ ├── des.js │ ├── base.css │ ├── input.js │ ├── websock.js │ └── keysymdef.js ├── bower.json ├── dist │ ├── angular-notify.min.css │ ├── angular-spinner.min.js │ ├── angular-notify.min.js │ ├── spin.min.js │ ├── angular-file-upload-shim.min.js │ └── angular-file-upload.min.js ├── vnc_auto.html ├── vnc.html └── novnc.html ├── examples ├── proxy-local.js ├── http-srv.js ├── https-srv.js ├── httpp-srv.js ├── certs │ ├── server-key.pem │ └── server-cert.pem └── httpps-srv.js ├── package.json ├── LICENSE.txt ├── README.md ├── novnc.js └── index.js /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /bin/certs/README.md: -------------------------------------------------------------------------------- 1 | ssl certs storage 2 | -------------------------------------------------------------------------------- /upload/README.md: -------------------------------------------------------------------------------- 1 | file upload temp dir 2 | -------------------------------------------------------------------------------- /front/images/alt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InstantWebP2P/peer-vnc/HEAD/front/images/alt.png -------------------------------------------------------------------------------- /front/images/esc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InstantWebP2P/peer-vnc/HEAD/front/images/esc.png -------------------------------------------------------------------------------- /front/images/tab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InstantWebP2P/peer-vnc/HEAD/front/images/tab.png -------------------------------------------------------------------------------- /front/images/ctrl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InstantWebP2P/peer-vnc/HEAD/front/images/ctrl.png -------------------------------------------------------------------------------- /front/images/drag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InstantWebP2P/peer-vnc/HEAD/front/images/drag.png -------------------------------------------------------------------------------- /front/images/power.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InstantWebP2P/peer-vnc/HEAD/front/images/power.png -------------------------------------------------------------------------------- /front/images/connect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InstantWebP2P/peer-vnc/HEAD/front/images/connect.png -------------------------------------------------------------------------------- /front/images/keyboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InstantWebP2P/peer-vnc/HEAD/front/images/keyboard.png -------------------------------------------------------------------------------- /front/images/settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InstantWebP2P/peer-vnc/HEAD/front/images/settings.png -------------------------------------------------------------------------------- /front/.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.o 3 | tests/data_*.js 4 | utils/rebind.so 5 | utils/websockify 6 | node_modules 7 | -------------------------------------------------------------------------------- /front/images/clipboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InstantWebP2P/peer-vnc/HEAD/front/images/clipboard.png -------------------------------------------------------------------------------- /front/images/ctrlaltdel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InstantWebP2P/peer-vnc/HEAD/front/images/ctrlaltdel.png -------------------------------------------------------------------------------- /front/images/disconnect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InstantWebP2P/peer-vnc/HEAD/front/images/disconnect.png -------------------------------------------------------------------------------- /front/images/fullscreen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InstantWebP2P/peer-vnc/HEAD/front/images/fullscreen.png -------------------------------------------------------------------------------- /front/images/mouse_left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InstantWebP2P/peer-vnc/HEAD/front/images/mouse_left.png -------------------------------------------------------------------------------- /front/images/mouse_none.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InstantWebP2P/peer-vnc/HEAD/front/images/mouse_none.png -------------------------------------------------------------------------------- /front/images/mouse_middle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InstantWebP2P/peer-vnc/HEAD/front/images/mouse_middle.png -------------------------------------------------------------------------------- /front/images/mouse_right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InstantWebP2P/peer-vnc/HEAD/front/images/mouse_right.png -------------------------------------------------------------------------------- /front/images/screen_57x57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InstantWebP2P/peer-vnc/HEAD/front/images/screen_57x57.png -------------------------------------------------------------------------------- /front/include/Orbitron700.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InstantWebP2P/peer-vnc/HEAD/front/include/Orbitron700.ttf -------------------------------------------------------------------------------- /front/include/Orbitron700.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InstantWebP2P/peer-vnc/HEAD/front/include/Orbitron700.woff -------------------------------------------------------------------------------- /front/images/screen_320x460.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InstantWebP2P/peer-vnc/HEAD/front/images/screen_320x460.png -------------------------------------------------------------------------------- /front/images/screen_700x700.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InstantWebP2P/peer-vnc/HEAD/front/images/screen_700x700.png -------------------------------------------------------------------------------- /front/images/toggleextrakeys.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InstantWebP2P/peer-vnc/HEAD/front/images/toggleextrakeys.png -------------------------------------------------------------------------------- /bin/genCert.bash: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | rm -rf ./certs/*.pem 3 | openssl req -x509 -nodes -days 1868 -subj "/C=CN/ST=SH/L=SH/CN=$1" -newkey rsa:2048 -keyout ./certs/server-key.pem -out ./certs/server-cert.pem 4 | -------------------------------------------------------------------------------- /examples/proxy-local.js: -------------------------------------------------------------------------------- 1 | var Proxy = require('../index'); 2 | 3 | 4 | var server = new Proxy(['192.188.1.168:5901'], function(err, proxyURL){ 5 | console.log('VNC Proxy URL(please open it on browser)'); 6 | for (var k in proxyURL) { 7 | console.log(k+' '+proxyURL[k]); 8 | } 9 | }); 10 | -------------------------------------------------------------------------------- /examples/http-srv.js: -------------------------------------------------------------------------------- 1 | var noVNC = require('../novnc'), 2 | http = require('http'), 3 | WebSocket = require('wspp'), 4 | WebSocketServer = WebSocket.Server; 5 | 6 | 7 | var srv = http.createServer(noVNC.webServer()); 8 | srv.listen(5080); 9 | console.log('noVNC http server listening on 5080'); 10 | 11 | var wss = new WebSocketServer({server: srv, path: '/peervnc'}); 12 | wss.on('connection', noVNC.tcpProxy({host: 'localhost', port: 5901})); 13 | console.log('please access http://localhost:5080/peervnc'); 14 | -------------------------------------------------------------------------------- /front/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "peer-vnc", 3 | "version": "1.3.0", 4 | "homepage": "https://github.com/InstantWebP2P/peer-vnc", 5 | "description": "peer-vnc front view", 6 | "main": "spa.js", 7 | "moduleType": [ 8 | "node" 9 | ], 10 | "keywords": [ 11 | "peer-vnc" 12 | ], 13 | "authors": [ 14 | "tom zhou, iwebpp@gmail.com" 15 | ], 16 | "license": "MIT", 17 | "ignore": [ 18 | "**/.*", 19 | "node_modules", 20 | "bower_components", 21 | "test", 22 | "tests" 23 | ], 24 | "dependencies": { 25 | "angular": "~1.3.15", 26 | "angular-fullscreen": "~1.0.1" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /examples/https-srv.js: -------------------------------------------------------------------------------- 1 | var noVNC = require('../novnc'), 2 | https = require('https'), 3 | WebSocket = require('wspp'), 4 | WebSocketServer = WebSocket.Server, 5 | fs = require('fs'); 6 | 7 | 8 | var options = { 9 | key: fs.readFileSync('./certs/server-key.pem'), 10 | cert: fs.readFileSync('./certs/server-cert.pem') 11 | }; 12 | 13 | var srv = https.createServer(options, noVNC.webServer()); 14 | srv.listen(5443); 15 | console.log('noVNC https server listening on 5443'); 16 | 17 | var wss = new WebSocketServer({server: srv, path: '/peervnc'}); 18 | wss.on('connection', noVNC.tcpProxy({host: 'localhost', port: 5901})); 19 | console.log('please access https://localhost:5443/peervnc'); 20 | 21 | -------------------------------------------------------------------------------- /examples/httpp-srv.js: -------------------------------------------------------------------------------- 1 | var noVNC = require('../novnc'), 2 | httpp = require('httpp'), 3 | WebSocket = require('wspp'), 4 | WebSocketServer = WebSocket.Server; 5 | 6 | var connect = require('connect'); 7 | var app = connect(); 8 | var forward = require('httpp-forward'); 9 | 10 | // set httpp capacity middleware 11 | app.use(forward.connect_httpp(5080)); // pass listening port 12 | 13 | // noVNC server 14 | app.use(noVNC.webServer()); 15 | 16 | var srv = httpp.createServer(app); 17 | srv.listen(5080); 18 | console.log('noVNC httpp server listening on udp port 5080'); 19 | 20 | var wss = new WebSocketServer({httpp: true, server: srv, path: '/peervnc'}); 21 | wss.on('connection', noVNC.tcpProxy({host: 'localhost', port: 5901})); 22 | console.log('please access http://localhost:5080/peervnc'); 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Tom Zhou (https://appnet.link)", 3 | "name": "peer-vnc", 4 | "description": "access to VNC desktop from anywhere", 5 | "version": "3.3.3", 6 | "repository": { 7 | "type": "git", 8 | "url": "git://github.com/InstantWebP2P/peer-vnc.git" 9 | }, 10 | "keywords": [ 11 | "peer", 12 | "p2p", 13 | "webp2p", 14 | "http", 15 | "vnc" 16 | ], 17 | "engines": { 18 | "node": "0.8.x" 19 | }, 20 | "main": "index", 21 | "bin": { 22 | "peer-vnc": "./bin/peer-vnc", 23 | "peer-vnc-direct": "./bin/peer-vnc-direct" 24 | }, 25 | "dependencies": { 26 | "commander": "1.1.1", 27 | "connect": "2.30.2", 28 | "appnet.io": "3.3.7", 29 | "optimist": "0.3.x", 30 | "qrcode-terminal": "0.9.2", 31 | "wspp": "latest", 32 | "http-auth": "2.2.5", 33 | "connect-multiparty": "1.2.5" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /examples/certs/server-key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICXQIBAAKBgQCrdl1jjLSDvPAp3dR42WmvsZeEA1vwvde8aOw4balBqaxNQUcB 3 | iZCeZg8F7xy+r0FuNYZvdBNpPSKyhtwD8icHQAAPpdpbV4aTxdR5QUWKXrzI7s0s 4 | CWX885nyjrpzYOX41GnwqIsoZntaMaUyijyC21+MU1IEcWZp+SpY+w4zawIDAQAB 5 | AoGBAJ97gzX7RFRcww5OFTkrEJCpIQNblIMCMc4/0ahS5+H5jhh1pTQa3ndW+pZM 6 | aHI81SFLi/XTA9MZWFKx+ad57y3aZQ6Let8wzOmLSqj8A6d0SrYME1HVfQJmzlaZ 7 | aIVMKAJ4INT2gtapQ70E7RPkgm3rMB8DLq37SGPvNjbXTu4hAkEA4Ueqy8UenHF/ 8 | Ry8emSo+ZJYxoy+Q0/H3Sgo1nCyiA9FLnh22LZVhG4QUGKJII8oOBdNuFQufeGNB 9 | BnHk++jpgwJBAMLX+Jm0kVZqzRVpQaqIBMKl4T0lfUVD8gaWk9njnxDx16cefXVg 10 | G5ZHtB/wulVJP3vghWfNUraDnNNU/UAbMfkCQQCZT2R0eBl07ehoUTDJm24KlgwB 11 | ugUdt7VKtdf2CInZfz2uxRmNu/Lx/tiOSyI8+OzCGjj4KYjFLQa7lBb5OHBxAkBS 12 | tnYdYe70vX/0H+u1ZeqHav20BG8qcU6UB2YacCt9ePzwP9xIcJUldaj8iie/vsmJ 13 | X+WiDCKZwkNX31Nm3GSZAkAmPZ6FFkBSSenxBi/GVYwYAaVFjVt9+EXOyl5Oa9ky 14 | rfanR55ytbh3NMfnFveXb11ewERh212ilzZV+hqUyBma 15 | -----END RSA PRIVATE KEY----- 16 | -------------------------------------------------------------------------------- /examples/httpps-srv.js: -------------------------------------------------------------------------------- 1 | var noVNC = require('../novnc'), 2 | httpps = require('httpps'), 3 | WebSocket = require('wspp'), 4 | WebSocketServer = WebSocket.Server, 5 | fs = require('fs'); 6 | 7 | var connect = require('connect'); 8 | var app = connect(); 9 | var forward = require('httpp-forward'); 10 | 11 | // set httpp capacity middleware 12 | app.use(forward.connect_httpp(5443)); // pass listening port 13 | 14 | // noVNC server 15 | app.use(noVNC.webServer()); 16 | 17 | 18 | var options = { 19 | key: fs.readFileSync('./certs/server-key.pem'), 20 | cert: fs.readFileSync('./certs/server-cert.pem') 21 | }; 22 | 23 | var srv = httpps.createServer(options, app); 24 | srv.listen(5443); 25 | console.log('noVNC httpps server listening on udp port 5443'); 26 | 27 | var wss = new WebSocketServer({httpp: true, server: srv, path: '/peervnc'}); 28 | wss.on('connection', noVNC.tcpProxy({host: 'localhost', port: 5901})); 29 | console.log('please access https://localhost:5443/peervnc'); 30 | 31 | -------------------------------------------------------------------------------- /examples/certs/server-cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICozCCAgwCCQCuGQKgzz71jjANBgkqhkiG9w0BAQUFADCBlTELMAkGA1UEBhMC 3 | Q04xCzAJBgNVBAgMAlNIMRQwEgYDVQQHDAtwZWVydm5jLmNvbTEUMBIGA1UECgwL 4 | cGVlcnZuYy5jb20xFDASBgNVBAsMC3BlZXJ2bmMuY29tMRQwEgYDVQQDDAtwZWVy 5 | dm5jLmNvbTEhMB8GCSqGSIb3DQEJARYSenM2OGoyZWVAZ21haWwuY29tMB4XDTEz 6 | MDQzMDAxNDcwOVoXDTEzMDUzMDAxNDcwOVowgZUxCzAJBgNVBAYTAkNOMQswCQYD 7 | VQQIDAJTSDEUMBIGA1UEBwwLcGVlcnZuYy5jb20xFDASBgNVBAoMC3BlZXJ2bmMu 8 | Y29tMRQwEgYDVQQLDAtwZWVydm5jLmNvbTEUMBIGA1UEAwwLcGVlcnZuYy5jb20x 9 | ITAfBgkqhkiG9w0BCQEWEnpzNjhqMmVlQGdtYWlsLmNvbTCBnzANBgkqhkiG9w0B 10 | AQEFAAOBjQAwgYkCgYEAq3ZdY4y0g7zwKd3UeNlpr7GXhANb8L3XvGjsOG2pQams 11 | TUFHAYmQnmYPBe8cvq9BbjWGb3QTaT0isobcA/InB0AAD6XaW1eGk8XUeUFFil68 12 | yO7NLAll/POZ8o66c2Dl+NRp8KiLKGZ7WjGlMoo8gttfjFNSBHFmafkqWPsOM2sC 13 | AwEAATANBgkqhkiG9w0BAQUFAAOBgQAY3FmtFgv65b3Qa2RjVsaf43ooEsx6SgKU 14 | N2lf/sAqbnB6TVT/YgS9AUCe2wuh4/X6px3hH3n6JXzXVPObrQxoFYUKBruF16Wi 15 | otoEdT0TX0SbddNSZFwvKyt9hx+/bf7U4d3fHFHjlnMO3EtA/WzsTlBgjHX2ZSsO 16 | q6LgvXJ0UQ== 17 | -----END CERTIFICATE----- 18 | -------------------------------------------------------------------------------- /front/dist/angular-notify.min.css: -------------------------------------------------------------------------------- 1 | .cg-notify-message{position:fixed;left:50%;top:0;z-index:9999;max-width:400px;text-align:center;background-color:#d9edf7;color:#31708f;padding:15px;border:1px solid #bce8f1;border-radius:4px;-webkit-transition:top .5s ease-out,opacity .2s ease-out;-moz-transition:top .5s ease-out,opacity .2s ease-out;-o-transition:top .5s ease-out,opacity .2s ease-out;transition:top .5s ease-out,opacity .2s ease-out;visibility:hidden;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175)}.cg-notify-message a{font-weight:700;color:inherit}.cg-notify-message a:hover{color:inherit}.cg-notify-close{-webkit-appearance:none;padding:0;cursor:pointer;background:0 0;border:0;font-size:21px;font-weight:700;color:#000;text-shadow:0 1px 0 #fff;filter:alpha(opacity=20);opacity:.2;position:absolute;top:0;right:3px;line-height:15px}.cg-notify-close:focus,.cg-notify-close:hover{color:#000;text-decoration:none;cursor:pointer;filter:alpha(opacity=50);opacity:.5}.cg-notify-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0} -------------------------------------------------------------------------------- /front/dist/angular-spinner.min.js: -------------------------------------------------------------------------------- 1 | !function(a){"use strict";function b(a,b){return a.module("angularSpinner",[]).provider("usSpinnerConfig",function(){var a={};return{setDefaults:function(b){a=b||a},$get:function(){return{config:a}}}}).factory("usSpinnerService",["$rootScope",function(a){var b={};return b.spin=function(b){a.$broadcast("us-spinner:spin",b)},b.stop=function(b){a.$broadcast("us-spinner:stop",b)},b}]).directive("usSpinner",["$window","usSpinnerConfig",function(c,d){return{scope:!0,link:function(e,f,g){function h(){e.spinner&&e.spinner.stop()}var i=b||c.Spinner;e.spinner=null,e.key=a.isDefined(g.spinnerKey)?g.spinnerKey:!1,e.startActive=a.isDefined(g.spinnerStartActive)?e.$eval(g.spinnerStartActive):e.key?!1:!0,e.spin=function(){e.spinner&&e.spinner.spin(f[0])},e.stop=function(){e.startActive=!1,h()},e.$watch(g.usSpinner,function(a){h(),a=a||{};for(var b in d.config)void 0===a[b]&&(a[b]=d.config[b]);e.spinner=new i(a),(!e.key||e.startActive)&&e.spinner.spin(f[0])},!0),e.$on("us-spinner:spin",function(a,b){b===e.key&&e.spin()}),e.$on("us-spinner:stop",function(a,b){b===e.key&&e.stop()}),e.$on("$destroy",function(){e.stop(),e.spinner=null})}}}])}"function"==typeof define&&define.amd?define(["angular","spin"],b):b(a.angular)}(window); 2 | //# sourceMappingURL=angular-spinner.min.js.map -------------------------------------------------------------------------------- /front/include/blue.css: -------------------------------------------------------------------------------- 1 | /* 2 | * noVNC blue CSS 3 | * Copyright (C) 2012 Joel Martin 4 | * Copyright (C) 2013 Samuel Mannehed for Cendio AB 5 | * noVNC is licensed under the MPL 2.0 (see LICENSE.txt) 6 | * This file is licensed under the 2-Clause BSD license (see LICENSE.txt). 7 | */ 8 | 9 | .noVNC_status_normal { 10 | background-color:#04073d; 11 | background-image: -webkit-gradient( 12 | linear, 13 | left bottom, 14 | left top, 15 | color-stop(0.54, rgb(10,15,79)), 16 | color-stop(0.5, rgb(4,7,61)) 17 | ); 18 | background-image: -moz-linear-gradient( 19 | center bottom, 20 | rgb(10,15,79) 54%, 21 | rgb(4,7,61) 50% 22 | ); 23 | } 24 | .noVNC_status_error { 25 | background-color:#f04040; 26 | background-image: -webkit-gradient( 27 | linear, 28 | left bottom, 29 | left top, 30 | color-stop(0.54, rgb(240,64,64)), 31 | color-stop(0.5, rgb(4,7,61)) 32 | ); 33 | background-image: -moz-linear-gradient( 34 | center bottom, 35 | rgb(4,7,61) 54%, 36 | rgb(249,64,64) 50% 37 | ); 38 | } 39 | .noVNC_status_warn { 40 | background-color:#f0f040; 41 | background-image: -webkit-gradient( 42 | linear, 43 | left bottom, 44 | left top, 45 | color-stop(0.54, rgb(240,240,64)), 46 | color-stop(0.5, rgb(4,7,61)) 47 | ); 48 | background-image: -moz-linear-gradient( 49 | center bottom, 50 | rgb(4,7,61) 54%, 51 | rgb(240,240,64) 50% 52 | ); 53 | } 54 | 55 | .triangle-right { 56 | border:2px solid #fff; 57 | background:#04073d; 58 | color:#fff; 59 | } 60 | 61 | #noVNC_keyboardinput { 62 | background-color:#04073d; 63 | } 64 | 65 | -------------------------------------------------------------------------------- /front/dist/angular-notify.min.js: -------------------------------------------------------------------------------- 1 | angular.module("cgNotify",[]).factory("notify",["$timeout","$http","$compile","$templateCache","$rootScope",function(a,b,c,d,e){var f=10,g=15,h=1e4,i="angular-notify.html",j="center",k=document.body,l=[],m=function(m){"object"!=typeof m&&(m={message:m}),m.templateUrl=m.templateUrl?m.templateUrl:i,m.position=m.position?m.position:j,m.container=m.container?m.container:k,m.classes=m.classes?m.classes:"";var n=m.scope?m.scope.$new():e.$new();n.$message=m.message,n.$classes=m.classes,n.$messageTemplate=m.messageTemplate,b.get(m.templateUrl,{cache:d}).success(function(b){var d=c(b)(n);if(d.bind("webkitTransitionEnd oTransitionEnd otransitionend transitionend msTransitionEnd",function(a){("opacity"===a.propertyName||0===a.currentTarget.style.opacity||a.originalEvent&&"opacity"===a.originalEvent.propertyName)&&(d.remove(),l.splice(l.indexOf(d),1),j())}),m.messageTemplate){for(var e,i=0;i=0;c--){var d=10,e=l[c],h=e[0].offsetHeight,i=b+h+d;e.attr("data-closing")?i+=20:b+=h+g,e.css("top",i+"px").css("margin-top","-"+(h+d)+"px").css("visibility","visible"),a++}};a(function(){j()}),h>0&&a(function(){n.$close()},h)}).error(function(a){throw new Error("Template specified for cgNotify ("+m.templateUrl+") could not be loaded. "+a)});var o={};return o.close=function(){n.$close&&n.$close()},Object.defineProperty(o,"message",{get:function(){return n.$message},set:function(a){n.$message=a}}),o};return m.config=function(a){f=angular.isUndefined(a.startTop)?f:a.startTop,g=angular.isUndefined(a.verticalSpacing)?g:a.verticalSpacing,h=angular.isUndefined(a.duration)?h:a.duration,i=a.templateUrl?a.templateUrl:i,j=angular.isUndefined(a.position)?j:a.position,k=a.container?a.container:k},m.closeAll=function(){for(var a=l.length-1;a>=0;a--){var b=l[a];b.css("opacity",0)}},m}]),angular.module("cgNotify").run(["$templateCache",function(a){"use strict";a.put("angular-notify.html",'
\n\n
\n {{$message}}\n
\n\n
\n \n
\n\n \n\n
')}]); -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | peer-vnc is Copyright (C) 2012-present Tom Zhou 2 | 3 | noVNC is Copyright (C) 2011 Joel Martin 4 | 5 | The noVNC core library files are licensed under the MPL 2.0 (Mozilla 6 | Public License 2.0). The noVNC core library is composed of the 7 | Javascript code necessary for full noVNC operation. This includes (but 8 | is not limited to): 9 | 10 | include/base64.js 11 | include/des.js 12 | include/display.js 13 | include/input.js 14 | include/jsunzip.js 15 | include/keysym.js 16 | include/logo.js 17 | include/rfb.js 18 | include/ui.js 19 | include/util.js 20 | include/vnc.js 21 | include/websock.js 22 | include/webutil.js 23 | 24 | The HTML, CSS, font and images files that included with the noVNC 25 | source distibution (or repository) are not considered part of the 26 | noVNC core library and are licensed under more permissive licenses. 27 | The intent is to allow easy integration of noVNC into existing web 28 | sites and web applications. 29 | 30 | The HTML, CSS, font and image files are licensed as follows: 31 | 32 | *.html : 2-Clause BSD license 33 | 34 | include/*.css : 2-Clause BSD license 35 | 36 | include/Orbitron* : SIL Open Font License 1.1 37 | (Copyright 2009 Matt McInerney) 38 | 39 | images/ : Creative Commons Attribution-ShareAlike 40 | http://creativecommons.org/licenses/by-sa/3.0/ 41 | 42 | Some portions of noVNC are copyright to their individual authors. 43 | Please refer to the individual source files and/or to the noVNC commit 44 | history: https://github.com/kanaka/noVNC/commits/master 45 | 46 | The are several files and projects that have been incorporated into 47 | the noVNC core library. Here is a list of those files and the original 48 | licenses (all MPL 2.0 compatible): 49 | 50 | include/base64.js : MPL 2.0 51 | 52 | include/des.js : Various BSD style licenses 53 | 54 | include/jsunzip.js : zlib/libpng license 55 | 56 | include/web-socket-js/ : New BSD license (3-clause). Source code at 57 | http://github.com/gimite/web-socket-js 58 | 59 | include/chrome-app/tcp-stream.js 60 | : Apache 2.0 license 61 | 62 | utils/websockify 63 | utils/websocket.py : LGPL 3 64 | 65 | The following license texts are included: 66 | 67 | docs/LICENSE.MPL-2.0 68 | docs/LICENSE.LGPL-3 and 69 | docs/LICENSE.GPL-3 70 | docs/LICENSE.OFL-1.1 71 | docs/LICENSE.BSD-3-Clause (New BSD) 72 | docs/LICENSE.BSD-2-Clause (Simplified BSD / FreeBSD) 73 | docs/LICENSE.zlib 74 | docs/LICENSE.Apache-2.0 75 | 76 | Or alternatively the license texts may be found here: 77 | 78 | http://www.mozilla.org/MPL/2.0/ 79 | http://www.gnu.org/licenses/lgpl.html and 80 | http://www.gnu.org/licenses/gpl.html 81 | http://scripts.sil.org/OFL 82 | http://en.wikipedia.org/wiki/BSD_licenses 83 | http://www.gzip.org/zlib/zlib_license.html 84 | http://www.apache.org/licenses/LICENSE-2.0.html 85 | -------------------------------------------------------------------------------- /front/include/playback.js: -------------------------------------------------------------------------------- 1 | /* 2 | * noVNC: HTML5 VNC client 3 | * Copyright (C) 2012 Joel Martin 4 | * Licensed under MPL 2.0 (see LICENSE.txt) 5 | */ 6 | 7 | "use strict"; 8 | /*jslint browser: true, white: false */ 9 | /*global Util, VNC_frame_data, finish */ 10 | 11 | var rfb, mode, test_state, frame_idx, frame_length, 12 | iteration, iterations, istart_time, 13 | 14 | // Pre-declarations for jslint 15 | send_array, next_iteration, queue_next_packet, do_packet, enable_test_mode; 16 | 17 | // Override send_array 18 | send_array = function (arr) { 19 | // Stub out send_array 20 | }; 21 | 22 | enable_test_mode = function () { 23 | rfb._sock._mode = VNC_frame_encoding; 24 | rfb._sock.send = send_array; 25 | rfb._sock.close = function () {}; 26 | rfb._sock.flush = function () {}; 27 | rfb._checkEvents = function () {}; 28 | rfb.connect = function (host, port, password, path) { 29 | this._rfb_host = host; 30 | this._rfb_port = port; 31 | this._rfb_password = (password !== undefined) ? password : ""; 32 | this._rfb_path = (path !== undefined) ? path : ""; 33 | this._sock.init('binary', 'ws'); 34 | this._updateState('ProtocolVersion', "Starting VNC handshake"); 35 | }; 36 | }; 37 | 38 | next_iteration = function () { 39 | rfb = new RFB({'target': $D('VNC_canvas'), 40 | 'onUpdateState': updateState}); 41 | enable_test_mode(); 42 | 43 | if (iteration === 0) { 44 | frame_length = VNC_frame_data.length; 45 | test_state = 'running'; 46 | } 47 | 48 | if (test_state !== 'running') { return; } 49 | 50 | iteration += 1; 51 | if (iteration > iterations) { 52 | finish(); 53 | return; 54 | } 55 | 56 | frame_idx = 0; 57 | istart_time = (new Date()).getTime(); 58 | rfb.connect('test', 0, "bogus"); 59 | 60 | queue_next_packet(); 61 | 62 | }; 63 | 64 | queue_next_packet = function () { 65 | var frame, foffset, toffset, delay; 66 | if (test_state !== 'running') { return; } 67 | 68 | frame = VNC_frame_data[frame_idx]; 69 | while ((frame_idx < frame_length) && (frame.charAt(0) === "}")) { 70 | //Util.Debug("Send frame " + frame_idx); 71 | frame_idx += 1; 72 | frame = VNC_frame_data[frame_idx]; 73 | } 74 | 75 | if (frame === 'EOF') { 76 | Util.Debug("Finished, found EOF"); 77 | next_iteration(); 78 | return; 79 | } 80 | if (frame_idx >= frame_length) { 81 | Util.Debug("Finished, no more frames"); 82 | next_iteration(); 83 | return; 84 | } 85 | 86 | if (mode === 'realtime') { 87 | foffset = frame.slice(1, frame.indexOf('{', 1)); 88 | toffset = (new Date()).getTime() - istart_time; 89 | delay = foffset - toffset; 90 | if (delay < 1) { 91 | delay = 1; 92 | } 93 | 94 | setTimeout(do_packet, delay); 95 | } else { 96 | setTimeout(do_packet, 1); 97 | } 98 | }; 99 | 100 | var bytes_processed = 0; 101 | 102 | do_packet = function () { 103 | //Util.Debug("Processing frame: " + frame_idx); 104 | var frame = VNC_frame_data[frame_idx], 105 | start = frame.indexOf('{', 1) + 1; 106 | bytes_processed += frame.length - start; 107 | if (VNC_frame_encoding === 'binary') { 108 | var u8 = new Uint8Array(frame.length - start); 109 | for (var i = 0; i < frame.length - start; i++) { 110 | u8[i] = frame.charCodeAt(start + i); 111 | } 112 | rfb._sock._recv_message({'data' : u8}); 113 | } else { 114 | rfb._sock._recv_message({'data' : frame.slice(start)}); 115 | } 116 | frame_idx += 1; 117 | 118 | queue_next_packet(); 119 | }; 120 | 121 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | peer-vnc 2 | ========== 3 | 4 | Access VNC from anywhere using web browser even behind NAT/FW based on noVNC 5 | 6 | ### Features 7 | 8 | * Support all HTML5-enabled web browsers 9 | * Access VNC desktop from anywhere 10 | * Expose VNC desktop behind NAT/FW 11 | * Proxy to multiple VNC server in single [appnet.link](https://github.com/InstantWebP2P/appnet.link) client 12 | * Provide end-to-end security 13 | * Run over STUN session with papp-pac 14 | * Support http basic-auth 15 | * Support upload file to remote server by drag and drop 16 | * Support VNC fullscreen mode 17 | * Support UTF-8 clipboard in combined with [zvnc](https://github.com/5GApp/zvnc) Xvnc server 18 | 19 | ### Install manually 20 | 21 | * peer-vnc depend on node-httpp, please build it from repo [nodejs-httpp](https://github.com/InstantWebP2P/nodejs-httpp) 22 | * npm install peer-vnc, or git clone [peer-vnc](https://github.com/InstantWebP2P/peer-vnc.git) && cd peer-vnc && npm install 23 | * setup your own AppNet.link backend controller services refer to [AppNet.link-controller](https://github.com/InstantWebP2P/appnet.link-controller) 24 | 25 | ### Usage/API: 26 | 27 | 1. create vnc-example.js 28 | var Proxy = require('peer-vnc'); 29 | 30 | new Proxy('192.168.1.1:5900', function(err, proxyURL){ 31 | console.log('VNC Proxy URL(please open it on browser)'); 32 | for (var k in proxyURL) { 33 | console.log(k+' '+proxyURL[k]); 34 | } 35 | }); 36 | 37 | 2. launch proxy server by node-httpp-binary-directory/node.exe vnc-example.js in case Windows machine. 38 | console dump like below: 39 | VNC Proxy URL(please open it on browser) 40 | 192.168.1.1:5900 https://af5e83731df02546.vurl.51dese.com:51688/vtoken/bb39fb0eb29f081e/peervnc 41 | 42 | 3. use peer-vnc binary on Linux, like ./bin/peer-vnc -t 192.168.1.1:5900 -s acl -a srv -b user:pass -d /auto/vshare/ 43 | VNC Proxy URL(please open it on browser) 44 | 192.168.1.1:5900 https://41c522dab4ae47f9.vurl.51dese.com:51688/vtoken/516c97b3070de2e1/peervnc 45 | 46 | 4. run over STUN with appnet.io-stun-proxy, just embed 'vlocal.' as sub-domain in origin vURL, 47 | like https://41c522dab4ae47f9.vurl.vlocal.51dese.com:51688/vtoken/516c97b3070de2e1/peervnc 48 | 49 | 5. use peer-vnc-direct binary without P2P, like ./bin/peer-vnc-direct -t 192.168.1.1:5900 -s acl -a srv -b user:pass -d /auto/vshare/ 50 | VNC Proxy URL(please open it on browser) 51 | 192.168.1.1:5900 https://41c522dab4ae47f9.vurl.51dese.com:51688/192.168.1.1-5900 52 | 53 | ### Support us 54 | 55 | * Welcome contributing on document, codes, tests and issues 56 | 57 | 58 | ### License 59 | 60 | (The MIT License) 61 | 62 | Copyright (c) 2012-present Tom Zhou(appnet.link@gmail.com) 63 | 64 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 65 | 66 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 67 | 68 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 69 | -------------------------------------------------------------------------------- /bin/peer-vnc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 4 | // 5 | // Please install node-httpp binary in your system exec dir, like /usr/bin/ or /usr/local/bin/ 6 | // 7 | 8 | var argv = require('optimist').argv, 9 | Proxy = require('../index'); 10 | 11 | var help = [ 12 | "usage: peer-vnc [options] ", 13 | "", 14 | "Starts a peer-vnc server using the specified command-line options", 15 | "", 16 | "options:", 17 | " -t, --target localhost:5900,localhost:5901-5918 Host:port pairs of VNC server, the multiple VNC separate by comma", 18 | " -k, --key User key Key for the registered user on ivncbox.com", 19 | " -s --secmode ssl or acl Secure mode: ssl or host-based ACL", 20 | " -a --authmode srv or both SSL auth mode: srv or both", 21 | " -b --basicauth user:pass Http basic-auth as username:password", 22 | " -d --directory /xxx/yy/ File upload directory", 23 | " -u, --user USER User to drop privileges once server socket is bound", 24 | " -h, --help You're staring at it" 25 | ].join('\n'); 26 | 27 | if (argv.h || argv.help || Object.keys(argv).length === 2) { 28 | return console.log(help); 29 | } 30 | 31 | var target = argv.t || argv.target, 32 | usrkey = argv.k || argv.key, 33 | secmode = argv.s || argv.secmode, 34 | authmode = argv.a || argv.authmode, 35 | basicauth = argv.b || argv.basicauth, 36 | directory = argv.d || argv.directory, 37 | user = argv.u || argv.user; 38 | 39 | // 40 | // Check target VNC server 41 | // 42 | var vncsrvs = [], options = {}; 43 | 44 | if (target) { 45 | var vncs = target.split(','); 46 | 47 | if (!Array.isArray(vncs)) vncs = [vncs]; 48 | 49 | // normalize port range 50 | for (var idx = 0; idx < vncs.length; idx ++) { 51 | var vncstrs = vncs[idx].split(':'); 52 | var vnchost = vncstrs[0]; 53 | 54 | // support port range like localhost:5901-5910 55 | var port_range = []; 56 | if (vncstrs[1]) { 57 | var pstr = vncstrs[1].split('-'); 58 | 59 | if (pstr.length > 1) { 60 | port_range.push(parseInt(pstr[0], 10)); 61 | port_range.push(parseInt(pstr[1], 10)); 62 | } else { 63 | port_range.push(parseInt(pstr[0], 10)); 64 | port_range.push(parseInt(pstr[0], 10)); 65 | } 66 | } else { 67 | port_range.push(5900); 68 | port_range.push(5900); 69 | } 70 | for (var vncport = port_range[0]; vncport <= port_range[1]; vncport ++) { 71 | vncsrvs.push(vnchost + ':' + vncport) 72 | } 73 | } 74 | } else { 75 | return console.log(help); 76 | } 77 | 78 | if (secmode) { 79 | options.secmode = secmode; 80 | } 81 | 82 | if (authmode) { 83 | options.sslmode = authmode; 84 | } 85 | 86 | if (basicauth) { 87 | options.auth = basicauth; 88 | } else { 89 | console.log('Warning: Please set http auth with -b username:password'); 90 | } 91 | 92 | if (directory) { 93 | options.upload = directory; 94 | console.log('Warning: File will upload to ' + directory); 95 | } 96 | 97 | if (usrkey) { 98 | options.usrkey = usrkey; 99 | } 100 | 101 | // 102 | // Create the VNC client server with the specified options 103 | // 104 | var server = new Proxy(vncsrvs, function(err, proxyURL){ 105 | console.log('VNC Proxy URL(please open it on browser)'); 106 | for (var k in proxyURL) { 107 | console.log(k+' '+proxyURL[k]); 108 | } 109 | }, options); 110 | 111 | // 112 | // Drop privileges if requested 113 | // 114 | if (typeof user === 'string') { 115 | process.setuid(user); 116 | } 117 | 118 | 119 | -------------------------------------------------------------------------------- /front/dist/spin.min.js: -------------------------------------------------------------------------------- 1 | //fgnass.github.com/spin.js#v2.0.1 2 | !function(a,b){"object"==typeof exports?module.exports=b():"function"==typeof define&&define.amd?define(b):a.Spinner=b()}(this,function(){"use strict";function a(a,b){var c,d=document.createElement(a||"div");for(c in b)d[c]=b[c];return d}function b(a){for(var b=1,c=arguments.length;c>b;b++)a.appendChild(arguments[b]);return a}function c(a,b,c,d){var e=["opacity",b,~~(100*a),c,d].join("-"),f=.01+c/d*100,g=Math.max(1-(1-a)/b*(100-f),a),h=j.substring(0,j.indexOf("Animation")).toLowerCase(),i=h&&"-"+h+"-"||"";return l[e]||(m.insertRule("@"+i+"keyframes "+e+"{0%{opacity:"+g+"}"+f+"%{opacity:"+a+"}"+(f+.01)+"%{opacity:1}"+(f+b)%100+"%{opacity:"+a+"}100%{opacity:"+g+"}}",m.cssRules.length),l[e]=1),e}function d(a,b){var c,d,e=a.style;for(b=b.charAt(0).toUpperCase()+b.slice(1),d=0;d',c)}m.addRule(".spin-vml","behavior:url(#default#VML)"),h.prototype.lines=function(a,d){function f(){return e(c("group",{coordsize:k+" "+k,coordorigin:-j+" "+-j}),{width:k,height:k})}function h(a,h,i){b(m,b(e(f(),{rotation:360/d.lines*a+"deg",left:~~h}),b(e(c("roundrect",{arcsize:d.corners}),{width:j,height:d.width,left:d.radius,top:-d.width>>1,filter:i}),c("fill",{color:g(d.color,a),opacity:d.opacity}),c("stroke",{opacity:0}))))}var i,j=d.length+d.width,k=2*j,l=2*-(d.width+d.length)+"px",m=e(f(),{position:"absolute",top:l,left:l});if(d.shadow)for(i=1;i<=d.lines;i++)h(i,-2,"progid:DXImageTransform.Microsoft.Blur(pixelradius=2,makeshadow=1,shadowopacity=.3)");for(i=1;i<=d.lines;i++)h(i);return b(a,m)},h.prototype.opacity=function(a,b,c,d){var e=a.firstChild;d=d.shadow&&d.lines||0,e&&b+d>1)+"px"})}for(var i,k=0,l=(f.lines-1)*(1-f.direction)/2;k> 2]; 25 | result += toBase64Table[((data[i] & 0x03) << 4) + (data[i + 1] >> 4)]; 26 | result += toBase64Table[((data[i + 1] & 0x0f) << 2) + (data[i + 2] >> 6)]; 27 | result += toBase64Table[data[i + 2] & 0x3f]; 28 | } 29 | 30 | // Convert the remaining 1 or 2 bytes, pad out to 4 characters. 31 | var j = 0; 32 | if (lengthpad === 2) { 33 | j = length - lengthpad; 34 | result += toBase64Table[data[j] >> 2]; 35 | result += toBase64Table[((data[j] & 0x03) << 4) + (data[j + 1] >> 4)]; 36 | result += toBase64Table[(data[j + 1] & 0x0f) << 2]; 37 | result += toBase64Table[64]; 38 | } else if (lengthpad === 1) { 39 | j = length - lengthpad; 40 | result += toBase64Table[data[j] >> 2]; 41 | result += toBase64Table[(data[j] & 0x03) << 4]; 42 | result += toBase64Table[64]; 43 | result += toBase64Table[64]; 44 | } 45 | 46 | return result; 47 | }, 48 | 49 | /* Convert Base64 data to a string */ 50 | /* jshint -W013 */ 51 | toBinaryTable : [ 52 | -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, 53 | -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, 54 | -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,62, -1,-1,-1,63, 55 | 52,53,54,55, 56,57,58,59, 60,61,-1,-1, -1, 0,-1,-1, 56 | -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14, 57 | 15,16,17,18, 19,20,21,22, 23,24,25,-1, -1,-1,-1,-1, 58 | -1,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40, 59 | 41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1 60 | ], 61 | /* jshint +W013 */ 62 | 63 | decode: function (data, offset) { 64 | "use strict"; 65 | offset = typeof(offset) !== 'undefined' ? offset : 0; 66 | var toBinaryTable = Base64.toBinaryTable; 67 | var base64Pad = Base64.base64Pad; 68 | var result, result_length; 69 | var leftbits = 0; // number of bits decoded, but yet to be appended 70 | var leftdata = 0; // bits decoded, but yet to be appended 71 | var data_length = data.indexOf('=') - offset; 72 | 73 | if (data_length < 0) { data_length = data.length - offset; } 74 | 75 | /* Every four characters is 3 resulting numbers */ 76 | result_length = (data_length >> 2) * 3 + Math.floor((data_length % 4) / 1.5); 77 | result = new Array(result_length); 78 | 79 | // Convert one by one. 80 | for (var idx = 0, i = offset; i < data.length; i++) { 81 | var c = toBinaryTable[data.charCodeAt(i) & 0x7f]; 82 | var padding = (data.charAt(i) === base64Pad); 83 | // Skip illegal characters and whitespace 84 | if (c === -1) { 85 | console.error("Illegal character code " + data.charCodeAt(i) + " at position " + i); 86 | continue; 87 | } 88 | 89 | // Collect data into leftdata, update bitcount 90 | leftdata = (leftdata << 6) | c; 91 | leftbits += 6; 92 | 93 | // If we have 8 or more bits, append 8 bits to the result 94 | if (leftbits >= 8) { 95 | leftbits -= 8; 96 | // Append if not padding. 97 | if (!padding) { 98 | result[idx++] = (leftdata >> leftbits) & 0xff; 99 | } 100 | leftdata &= (1 << leftbits) - 1; 101 | } 102 | } 103 | 104 | // If there are any bits left, the base64 string was corrupted 105 | if (leftbits) { 106 | err = new Error('Corrupted base64 string'); 107 | err.name = 'Base64-Error'; 108 | throw err; 109 | } 110 | 111 | return result; 112 | } 113 | }; /* End of Base64 namespace */ 114 | -------------------------------------------------------------------------------- /novnc.js: -------------------------------------------------------------------------------- 1 | // noVNC port of Node.js 2 | // Copyright (c) 2013 Tom Zhou 3 | 4 | var Connect = require('connect'), 5 | Net = require('net'), 6 | Buffer = require('buffer').Buffer, 7 | Fs = require('fs'), 8 | WSS = require('wspp').createWebSocketStream; 9 | 10 | // authentication module 11 | var httpauth = require('http-auth'); 12 | 13 | // file upload module 14 | var multipart = require('connect-multiparty'); 15 | 16 | 17 | // Debug level 18 | var Debug = 0; 19 | 20 | // web server 21 | var webServer = module.exports.webServer = function(options) { 22 | var app = Connect(); 23 | var auth = (options && options.auth) || false; 24 | var upload = (options && options.upload) || __dirname + '/upload/'; 25 | 26 | // rewrite req.url to remove vToken string 27 | var vtokenregex = /\/vtoken\/([0-9]|[a-f]){16}/gi; 28 | 29 | app.use(function(req, res, next){ 30 | if (vtokenregex.test(req.url)) { 31 | res.writeHead(301, {'location': req.url.replace(vtokenregex, '')}); 32 | res.end(); 33 | ///req.url = req.url.replace(vtokenregex, ''); 34 | } else next(); 35 | }); 36 | 37 | // basic-auth middleware 38 | if (auth) { 39 | var basic = httpauth.basic({ 40 | realm: "51dese.com" 41 | }, function (username, password, callback) { 42 | callback(username === auth.username && password === auth.password); 43 | }); 44 | 45 | app.use(httpauth.connect(basic)); 46 | } 47 | 48 | // file upload middleware 49 | if (upload) { 50 | var upapp = Connect(); 51 | var multipartMiddleware = multipart({uploadDir: upload}); 52 | 53 | upapp.use(multipartMiddleware); 54 | upapp.use(function(req, res) { 55 | ///console.log(req.body, req.files); 56 | 57 | if (typeof req.files.file === 'object') { 58 | // rename file as timestamp_originalfilename 59 | var newname = '' + Date.now() + '__' + req.files.file.originalFilename; 60 | var newpath = upload + '/' + newname; 61 | Fs.rename(req.files.file.path, newpath, function(err) { 62 | if (err) { 63 | res.writeHeader(501); 64 | res.end('upload rename failed'); 65 | } else { 66 | res.writeHeader(200, {'content-type': 'application/json'}); 67 | req.files.file.newname = newname; 68 | req.files.file.newpath = newpath; 69 | res.end(JSON.stringify(req.files)); 70 | } 71 | }); 72 | } else { 73 | res.writeHeader(200, {'content-type': 'application/json'}); 74 | res.end(JSON.stringify(req.files)); 75 | } 76 | }); 77 | 78 | app.use('/upload', upapp); 79 | } 80 | 81 | ///app(Connect.staticCache({maxLength: 256*1024, maxObjects: 8})) 82 | app.use(Connect.static(__dirname+'/front')); 83 | 84 | app.use(function(req, res){ 85 | res.writeHeader(200, {'content-type': 'text/html'}); 86 | res.end(Fs.readFileSync(__dirname+'/front/novnc.html')); 87 | }); 88 | 89 | return app; 90 | }; 91 | 92 | // ws2tcp proxy 93 | // vnc: {host: ..., port: ...}, VNC server info 94 | var tcpProxy = module.exports.tcpProxy = function(vnc){ 95 | vnc = vnc || {}; 96 | 97 | vnc.host = vnc.host || 'localhost'; 98 | vnc.port = vnc.port || 5900; 99 | 100 | if (Debug) console.log('connect to vnc %j ...', vnc); 101 | 102 | return function(ws){ 103 | // create tcp connection to VNC server 104 | var ts = Net.connect(vnc, function(){ 105 | if (Debug) console.log('tcp connection...'); 106 | 107 | // wrap stream on ws 108 | var wss = WSS(ws); 109 | 110 | // pipe each other 111 | wss.pipe(ts); 112 | ts.pipe(wss); 113 | 114 | // check error 115 | wss.on('error', function(){ 116 | if (Debug) console.log('ws.onerror...'); 117 | ts.end(); 118 | }); 119 | }); 120 | 121 | // check error 122 | ws.on('error', function(){ 123 | if (Debug) console.log('ws.onerror...'); 124 | ts.end(); 125 | }); 126 | ts.on('error', function(err){ 127 | if (Debug) console.log('tcp connection error '+err); 128 | ws.close(); 129 | }); 130 | }; 131 | }; 132 | 133 | // simple test 134 | /*var http = require('http'), 135 | WebSocket = require('wspp'), 136 | WebSocketServer = WebSocket.Server; 137 | 138 | var srv = http.createServer(webServer()); 139 | srv.listen(5600); 140 | console.log('noVNC proxy server listening on 5600'); 141 | 142 | var wss = new WebSocketServer({server: srv, path: '/peervnc'}); 143 | wss.on('connection', tcpProxy({host: '192.188.1.101', port: 5900})); 144 | */ 145 | -------------------------------------------------------------------------------- /bin/peer-vnc-direct: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 4 | // 5 | // Please install node-httpp binary in your system exec dir, like /usr/bin/ or /usr/local/bin/ 6 | // 7 | 8 | var argv = require('optimist').argv, 9 | noVNC = require('../novnc'), 10 | http = require('http'), 11 | https = require('https'), 12 | //httpp = require('httpp'), 13 | //httpps = require('httpps'), 14 | fs = require('fs'); 15 | WebSocket = require('ws'), 16 | WebSocketServer = WebSocket.Server, 17 | Connect = require('connect'); 18 | 19 | var help = [ 20 | "usage: peer-vnc-direct [options] ", 21 | "", 22 | "Starts a peer-vnc server directly using the specified command-line options", 23 | "", 24 | "options:", 25 | " -t, --target localhost:5900,localhost:5901-5918 Host:port pairs of VNC server, the multiple VNC separate by comma", 26 | " -s --secmode https or http Secure mode: 1 - https; 0 - http server", 27 | " -b --basicauth user:pass Http basic-auth as username:password", 28 | " -d --directory /xxx/yy/ File upload directory", 29 | " -p --port 51686 tcp and udp port to listen", 30 | " -u, --user USER User to drop privileges once server socket is bound", 31 | " -h, --help You're staring at it" 32 | ].join('\n'); 33 | 34 | if (argv.h || argv.help || Object.keys(argv).length === 2) { 35 | return console.log(help); 36 | } 37 | 38 | var target = argv.t || argv.target, 39 | secmode = argv.s || argv.secmode, 40 | basicauth = argv.b || argv.basicauth, 41 | directory = argv.d || argv.directory, 42 | port = argv.p || argv.port || '51686'; 43 | user = argv.u || argv.user; 44 | 45 | // 46 | // Check target VNC server 47 | // 48 | var vncsrvs = [], options = {}, isHttps = false; 49 | 50 | if (target) { 51 | var vncs = target.split(','); 52 | 53 | if (!Array.isArray(vncs)) vncs = [vncs]; 54 | 55 | // normalize port range 56 | for (var idx = 0; idx < vncs.length; idx ++) { 57 | var vncstrs = vncs[idx].split(':'); 58 | var vnchost = vncstrs[0]; 59 | 60 | // support port range like localhost:5901-5910 61 | var port_range = []; 62 | if (vncstrs[1]) { 63 | var pstr = vncstrs[1].split('-'); 64 | 65 | if (pstr.length > 1) { 66 | port_range.push(parseInt(pstr[0], 10)); 67 | port_range.push(parseInt(pstr[1], 10)); 68 | } else { 69 | port_range.push(parseInt(pstr[0], 10)); 70 | port_range.push(parseInt(pstr[0], 10)); 71 | } 72 | } else { 73 | port_range.push(5900); 74 | port_range.push(5900); 75 | } 76 | for (var vncport = port_range[0]; vncport <= port_range[1]; vncport ++) { 77 | vncsrvs.push(vnchost + ':' + vncport) 78 | } 79 | } 80 | } else { 81 | return console.log(help); 82 | } 83 | 84 | if (secmode) { 85 | isHttps = true; 86 | } 87 | 88 | if (basicauth) { 89 | var astr = basicauth.split(':'); 90 | options.auth = {username: astr && astr[0], password: astr && astr[1]}; 91 | } else { 92 | console.log('Warning: Please set http auth with -b username:password'); 93 | } 94 | 95 | if (directory) { 96 | options.upload = directory; 97 | console.log('Warning: File will upload to ' + directory); 98 | } 99 | 100 | // 101 | // Create the VNC client server with the specified options 102 | // 103 | var proxes = {}; 104 | 105 | port = parseInt(port, 10); 106 | 107 | var srv, srvpp; 108 | if (isHttps) { 109 | var certs = { 110 | key: fs.readFileSync(__dirname+'/certs/server-key.pem'), 111 | cert: fs.readFileSync(__dirname+'/certs/server-cert.pem') 112 | }; 113 | 114 | srv = https.createServer(certs, noVNC.webServer(options)); 115 | srv.listen(port); 116 | ///console.log('peerVNC https server listening on ' + port); 117 | 118 | // srvpp = httpps.createServer(certs, noVNC.webServer(options)); 119 | // srvpp.listen(port); 120 | ///console.log('peerVNC httpps server listening on ' + port); 121 | } else { 122 | srv = http.createServer(noVNC.webServer(options)); 123 | srv.listen(port); 124 | ///console.log('peerVNC http server listening on ' + port); 125 | 126 | //srvpp = httpp.createServer(noVNC.webServer(options)); 127 | //srvpp.listen(port); 128 | ///console.log('peerVNC httpp server listening on ' + port); 129 | } 130 | 131 | // hook websocket 132 | for (var idx = 0; idx < vncsrvs.length; idx ++) { 133 | var vncstrs = vncsrvs[idx].split(':'); 134 | var vnchost = vncstrs[0]; 135 | var vncport = parseInt(vncstrs[1], 10); 136 | 137 | // create ws server to proxy VNC/RFB data 138 | var vncstr = vnchost+':'+vncport; 139 | var wspath = '/'+vnchost+'-'+vncport; 140 | 141 | var vncwss = new WebSocketServer({httpp: false, server: srv, path: wspath}); 142 | vncwss.on('connection', noVNC.tcpProxy({host: vnchost, port: vncport})); 143 | 144 | ///var vncwsspp = new WebSocketServer({httpp: true, server: srvpp, path: wspath}); 145 | ///vncwsspp.on('connection', noVNC.tcpProxy({host: vnchost, port: vncport})); 146 | 147 | proxes[vncstr] = (isHttps ? 'https' : 'http') + '://localhost:'+port + wspath; 148 | } 149 | 150 | console.log('VNC Proxy URL(please open it on browser, to external access replace localhost with external ip)'); 151 | for (var k in proxes) { 152 | console.log(k+' '+proxes[k]); 153 | } 154 | 155 | // 156 | // Drop privileges if requested 157 | // 158 | if (typeof user === 'string') { 159 | process.setuid(user); 160 | } 161 | 162 | 163 | -------------------------------------------------------------------------------- /front/dist/angular-file-upload-shim.min.js: -------------------------------------------------------------------------------- 1 | /*! 3.2.5 */ 2 | !function(){function a(a,b){window.XMLHttpRequest.prototype[a]=b(window.XMLHttpRequest.prototype[a])}function b(a,b,c){try{Object.defineProperty(a,b,{get:c})}catch(d){}}function c(a){return"input"===a[0].tagName.toLowerCase()&&a.attr("type")&&"file"===a.attr("type").toLowerCase()}var d=function(){try{var a=new ActiveXObject("ShockwaveFlash.ShockwaveFlash");if(a)return!0}catch(b){if(void 0!=navigator.mimeTypes["application/x-shockwave-flash"])return!0}return!1};if(window.XMLHttpRequest&&!window.FormData||window.FileAPI&&FileAPI.forceLoad){var e=function(a){if(!a.__listeners){a.upload||(a.upload={}),a.__listeners=[];var b=a.upload.addEventListener;a.upload.addEventListener=function(c,d){a.__listeners[c]=d,b&&b.apply(this,arguments)}}};a("open",function(a){return function(b,c,d){e(this),this.__url=c;try{a.apply(this,[b,c,d])}catch(f){f.message.indexOf("Access is denied")>-1&&(this.__origError=f,a.apply(this,[b,"_fix_for_ie_crossdomain__",d]))}}}),a("getResponseHeader",function(a){return function(b){return this.__fileApiXHR&&this.__fileApiXHR.getResponseHeader?this.__fileApiXHR.getResponseHeader(b):null==a?null:a.apply(this,[b])}}),a("getAllResponseHeaders",function(a){return function(){return this.__fileApiXHR&&this.__fileApiXHR.getAllResponseHeaders?this.__fileApiXHR.getAllResponseHeaders():null==a?null:a.apply(this)}}),a("abort",function(a){return function(){return this.__fileApiXHR&&this.__fileApiXHR.abort?this.__fileApiXHR.abort():null==a?null:a.apply(this)}}),a("setRequestHeader",function(a){return function(b,c){if("__setXHR_"===b){e(this);var d=c(this);d instanceof Function&&d(this)}else this.__requestHeaders=this.__requestHeaders||{},this.__requestHeaders[b]=c,a.apply(this,arguments)}}),a("send",function(a){return function(){var c=this;if(arguments[0]&&arguments[0].__isFileAPIShim){var e=arguments[0],f={url:c.__url,jsonp:!1,cache:!0,complete:function(a,d){c.__completed=!0,!a&&c.__listeners.load&&c.__listeners.load({type:"load",loaded:c.__loaded,total:c.__total,target:c,lengthComputable:!0}),!a&&c.__listeners.loadend&&c.__listeners.loadend({type:"loadend",loaded:c.__loaded,total:c.__total,target:c,lengthComputable:!0}),"abort"===a&&c.__listeners.abort&&c.__listeners.abort({type:"abort",loaded:c.__loaded,total:c.__total,target:c,lengthComputable:!0}),void 0!==d.status&&b(c,"status",function(){return 0==d.status&&a&&"abort"!==a?500:d.status}),void 0!==d.statusText&&b(c,"statusText",function(){return d.statusText}),b(c,"readyState",function(){return 4}),void 0!==d.response&&b(c,"response",function(){return d.response});var e=d.responseText||(a&&0==d.status&&"abort"!==a?a:void 0);b(c,"responseText",function(){return e}),b(c,"response",function(){return e}),a&&b(c,"err",function(){return a}),c.__fileApiXHR=d,c.onreadystatechange&&c.onreadystatechange(),c.onload&&c.onload()},fileprogress:function(a){if(a.target=c,c.__listeners.progress&&c.__listeners.progress(a),c.__total=a.total,c.__loaded=a.loaded,a.total===a.loaded){var b=this;setTimeout(function(){c.__completed||(c.getAllResponseHeaders=function(){},b.complete(null,{status:204,statusText:"No Content"}))},FileAPI.noContentTimeout||1e4)}},headers:c.__requestHeaders};f.data={},f.files={};for(var g=0;g-1){b=f.substring(0,e+1);break}null==FileAPI.staticPath&&(FileAPI.staticPath=b),g.setAttribute("src",a||b+"FileAPI.min.js"),document.getElementsByTagName("head")[0].appendChild(g),FileAPI.hasFlash=d()}}(),FileAPI.disableFileInput=function(a,b){b?a.removeClass("js-fileapi-wrapper"):a.addClass("js-fileapi-wrapper")}}window.FileReader||(window.FileReader=function(){var a=this,b=!1;this.listeners={},this.addEventListener=function(b,c){a.listeners[b]=a.listeners[b]||[],a.listeners[b].push(c)},this.removeEventListener=function(b,c){a.listeners[b]&&a.listeners[b].splice(a.listeners[b].indexOf(c),1)},this.dispatchEvent=function(b){var c=a.listeners[b.type];if(c)for(var d=0;d'),c=0;c0&&"file"!=k.protocol())for(i=0;i0)break}else{var r=b.dataTransfer.files;if(null!=r)for(i=0;i0));i++);}var t=0;!function u(a){j(function(){if(o)10*t++<2e4&&u(10);else{if(!e&&l.length>1){for(i=0;"directory"==l[i].type;)i++;l=[l[i]]}c(l,m)}},a||0)}()}var n=d();if(c.dropAvailable&&j(function(){a.dropAvailable?a.dropAvailable.value=n:a.dropAvailable=n}),!n)return 1==h(c.hideOnDropNotAvailable)(a)&&b.css("display","none"),void 0;var o,p=null,q=h(c.stopPropagation),r=1,s=h(c.ngAccept),t=h(c.ngDisabled);b[0].addEventListener("dragover",function(d){if(!t(a)){if(d.preventDefault(),q(a)&&d.stopPropagation(),navigator.userAgent.indexOf("Chrome")>-1){var e=d.dataTransfer.effectAllowed;d.dataTransfer.dropEffect="move"===e||"linkMove"===e?"move":"copy"}j.cancel(p),a.actualDragOverClass||(o=l(a,c,d)),b.addClass(o)}},!1),b[0].addEventListener("dragenter",function(b){t(a)||(b.preventDefault(),q(a)&&b.stopPropagation())},!1),b[0].addEventListener("dragleave",function(){t(a)||(p=j(function(){b.removeClass(o),o=null},r||1))},!1),b[0].addEventListener("drop",function(d){t(a)||(d.preventDefault(),q(a)&&d.stopPropagation(),b.removeClass(o),o=null,m(d,function(b,f){e(h,j,a,g,c,c.ngFileChange||c.ngFileDrop,b,f,d)},0!=h(c.allowDir)(a),c.multiple||h(c.ngMultiple)(a)))},!1)}function d(){var a=document.createElement("div");return"draggable"in a&&"ondrop"in a}function e(a,b,c,d,e,f,g,h,i,j){function k(){d&&(a(e.ngModel).assign(c,g),b(function(){d&&d.$setViewValue(null!=g&&0==g.length?null:g)})),e.ngModelRejected&&a(e.ngModelRejected).assign(c,h),f&&a(f)(c,{$files:g,$rejectedFiles:h,$event:i})}j?k():b(function(){k()})}function f(a,b,c,d){var e=b(a,{$file:c,$event:d});if(null==e)return!0;if(angular.isString(e)){var f=new RegExp(g(e),"gi");e=null!=c.type&&c.type.match(f)||null!=c.name&&c.name.match(f)}return e}function g(a){if(a.length>2&&"/"===a[0]&&"/"===a[a.length-1])return a.substring(1,a.length-1);var b=a.split(","),c="";if(b.length>1)for(i=0;i|:\\-]","g"),"\\$&")+"$",c=c.replace(/\\\*/g,".*").replace(/\\\?/g,".");return c}var h,i;window.XMLHttpRequest&&!window.XMLHttpRequest.__isFileAPIShim&&a("setRequestHeader",function(a){return function(b,c){if("__setXHR_"===b){var d=c(this);d instanceof Function&&d(this)}else a.apply(this,arguments)}});var j=angular.module("angularFileUpload",[]);j.version="3.2.5",j.service("$upload",["$http","$q","$timeout",function(a,b,c){function d(d){d.method=d.method||"POST",d.headers=d.headers||{},d.transformRequest=d.transformRequest||function(b,c){return window.ArrayBuffer&&b instanceof window.ArrayBuffer?b:a.defaults.transformRequest[0](b,c)};var e=b.defer(),f=e.promise;return d.headers.__setXHR_=function(){return function(a){a&&(d.__XHR=a,d.xhrFn&&d.xhrFn(a),a.upload.addEventListener("progress",function(a){a.config=d,e.notify?e.notify(a):f.progress_fn&&c(function(){f.progress_fn(a)})},!1),a.upload.addEventListener("load",function(a){a.lengthComputable&&(a.config=d,e.notify?e.notify(a):f.progress_fn&&c(function(){f.progress_fn(a)}))},!1))}},a(d).then(function(a){e.resolve(a)},function(a){e.reject(a)},function(a){e.notify(a)}),f.success=function(a){return f.then(function(b){a(b.data,b.status,b.headers,d)}),f},f.error=function(a){return f.then(null,function(b){a(b.data,b.status,b.headers,d)}),f},f.progress=function(a){return f.progress_fn=a,f.then(null,null,function(b){a(b)}),f},f.abort=function(){return d.__XHR&&c(function(){d.__XHR.abort()}),f},f.xhr=function(a){return d.xhrFn=function(b){return function(){b&&b.apply(f,arguments),a.apply(f,arguments)}}(d.xhrFn),f},f}this.upload=function(a){return a.headers=a.headers||{},a.headers["Content-Type"]=void 0,a.transformRequest=a.transformRequest?"[object Array]"===Object.prototype.toString.call(a.transformRequest)?a.transformRequest:[a.transformRequest]:[],a.transformRequest.push(function(b){var c=new FormData,d={};for(h in a.fields)a.fields.hasOwnProperty(h)&&(d[h]=a.fields[h]);if(b&&(d.data=b),a.formDataAppender)for(h in d)d.hasOwnProperty(h)&&a.formDataAppender(c,h,d[h]);else for(h in d)if(d.hasOwnProperty(h)){var e=d[h];void 0!==e&&("[object String]"===Object.prototype.toString.call(e)?c.append(h,e):a.sendObjectsAsJsonBlob&&"object"==typeof e?c.append(h,new Blob([e],{type:"application/json"})):c.append(h,JSON.stringify(e)))}if(null!=a.file){var f=a.fileFormDataName||"file";if("[object Array]"===Object.prototype.toString.call(a.file)){var g="[object String]"===Object.prototype.toString.call(f);for(i=0;i 1) && (typeof obj[i] === "object")) { 61 | // Recurse attributes that are objects 62 | msg += WebUtil.dirObj(obj[i], depth - 1, parent + "." + i); 63 | } else { 64 | //val = new String(obj[i]).replace("\n", " "); 65 | var val = ""; 66 | if (typeof(obj[i]) === "undefined") { 67 | val = "undefined"; 68 | } else { 69 | val = obj[i].toString().replace("\n", " "); 70 | } 71 | if (val.length > 30) { 72 | val = val.substr(0, 30) + "..."; 73 | } 74 | msg += parent + "." + i + ": " + val + "\n"; 75 | } 76 | } 77 | return msg; 78 | }; 79 | 80 | // Read a query string variable 81 | WebUtil.getQueryVar = function (name, defVal) { 82 | "use strict"; 83 | var re = new RegExp('.*[?&]' + name + '=([^&#]*)'), 84 | match = document.location.href.match(re); 85 | if (typeof defVal === 'undefined') { defVal = null; } 86 | if (match) { 87 | return decodeURIComponent(match[1]); 88 | } else { 89 | return defVal; 90 | } 91 | }; 92 | 93 | // Read a hash fragment variable 94 | WebUtil.getHashVar = function (name, defVal) { 95 | "use strict"; 96 | var re = new RegExp('.*[&#]' + name + '=([^&]*)'), 97 | match = document.location.hash.match(re); 98 | if (typeof defVal === 'undefined') { defVal = null; } 99 | if (match) { 100 | return decodeURIComponent(match[1]); 101 | } else { 102 | return defVal; 103 | } 104 | }; 105 | 106 | // Read a variable from the fragment or the query string 107 | // Fragment takes precedence 108 | WebUtil.getConfigVar = function (name, defVal) { 109 | "use strict"; 110 | var val = WebUtil.getHashVar(name); 111 | if (val === null) { 112 | val = WebUtil.getQueryVar(name, defVal); 113 | } 114 | return val; 115 | }; 116 | 117 | /* 118 | * Cookie handling. Dervied from: http://www.quirksmode.org/js/cookies.html 119 | */ 120 | 121 | // No days means only for this browser session 122 | WebUtil.createCookie = function (name, value, days) { 123 | "use strict"; 124 | var date, expires; 125 | if (days) { 126 | date = new Date(); 127 | date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); 128 | expires = "; expires=" + date.toGMTString(); 129 | } else { 130 | expires = ""; 131 | } 132 | 133 | var secure; 134 | if (document.location.protocol === "https:") { 135 | secure = "; secure"; 136 | } else { 137 | secure = ""; 138 | } 139 | document.cookie = name + "=" + value + expires + "; path=/" + secure; 140 | }; 141 | 142 | WebUtil.readCookie = function (name, defaultValue) { 143 | "use strict"; 144 | var nameEQ = name + "=", 145 | ca = document.cookie.split(';'); 146 | 147 | for (var i = 0; i < ca.length; i += 1) { 148 | var c = ca[i]; 149 | while (c.charAt(0) === ' ') { c = c.substring(1, c.length); } 150 | if (c.indexOf(nameEQ) === 0) { return c.substring(nameEQ.length, c.length); } 151 | } 152 | return (typeof defaultValue !== 'undefined') ? defaultValue : null; 153 | }; 154 | 155 | WebUtil.eraseCookie = function (name) { 156 | "use strict"; 157 | WebUtil.createCookie(name, "", -1); 158 | }; 159 | 160 | /* 161 | * Setting handling. 162 | */ 163 | 164 | WebUtil.initSettings = function (callback /*, ...callbackArgs */) { 165 | "use strict"; 166 | var callbackArgs = Array.prototype.slice.call(arguments, 1); 167 | if (window.chrome && window.chrome.storage) { 168 | window.chrome.storage.sync.get(function (cfg) { 169 | WebUtil.settings = cfg; 170 | console.log(WebUtil.settings); 171 | if (callback) { 172 | callback.apply(this, callbackArgs); 173 | } 174 | }); 175 | } else { 176 | // No-op 177 | if (callback) { 178 | callback.apply(this, callbackArgs); 179 | } 180 | } 181 | }; 182 | 183 | // No days means only for this browser session 184 | WebUtil.writeSetting = function (name, value) { 185 | "use strict"; 186 | if (window.chrome && window.chrome.storage) { 187 | //console.log("writeSetting:", name, value); 188 | if (WebUtil.settings[name] !== value) { 189 | WebUtil.settings[name] = value; 190 | window.chrome.storage.sync.set(WebUtil.settings); 191 | } 192 | } else { 193 | localStorage.setItem(name, value); 194 | } 195 | }; 196 | 197 | WebUtil.readSetting = function (name, defaultValue) { 198 | "use strict"; 199 | var value; 200 | if (window.chrome && window.chrome.storage) { 201 | value = WebUtil.settings[name]; 202 | } else { 203 | value = localStorage.getItem(name); 204 | } 205 | if (typeof value === "undefined") { 206 | value = null; 207 | } 208 | if (value === null && typeof defaultValue !== undefined) { 209 | return defaultValue; 210 | } else { 211 | return value; 212 | } 213 | }; 214 | 215 | WebUtil.eraseSetting = function (name) { 216 | "use strict"; 217 | if (window.chrome && window.chrome.storage) { 218 | window.chrome.storage.sync.remove(name); 219 | delete WebUtil.settings[name]; 220 | } else { 221 | localStorage.removeItem(name); 222 | } 223 | }; 224 | 225 | /* 226 | * Alternate stylesheet selection 227 | */ 228 | WebUtil.getStylesheets = function () { 229 | "use strict"; 230 | var links = document.getElementsByTagName("link"); 231 | var sheets = []; 232 | 233 | for (var i = 0; i < links.length; i += 1) { 234 | if (links[i].title && 235 | links[i].rel.toUpperCase().indexOf("STYLESHEET") > -1) { 236 | sheets.push(links[i]); 237 | } 238 | } 239 | return sheets; 240 | }; 241 | 242 | // No sheet means try and use value from cookie, null sheet used to 243 | // clear all alternates. 244 | WebUtil.selectStylesheet = function (sheet) { 245 | "use strict"; 246 | if (typeof sheet === 'undefined') { 247 | sheet = 'default'; 248 | } 249 | 250 | var sheets = WebUtil.getStylesheets(); 251 | for (var i = 0; i < sheets.length; i += 1) { 252 | var link = sheets[i]; 253 | if (link.title === sheet) { 254 | Util.Debug("Using stylesheet " + sheet); 255 | link.disabled = false; 256 | } else { 257 | //Util.Debug("Skipping stylesheet " + link.title); 258 | link.disabled = true; 259 | } 260 | } 261 | return sheet; 262 | }; 263 | 264 | WebUtil.injectParamIfMissing = function (path, param, value) { 265 | // force pretend that we're dealing with a relative path 266 | // (assume that we wanted an extra if we pass one in) 267 | path = "/" + path; 268 | 269 | var elem = document.createElement('a'); 270 | elem.href = path; 271 | 272 | var param_eq = encodeURIComponent(param) + "="; 273 | var query; 274 | if (elem.search) { 275 | query = elem.search.slice(1).split('&'); 276 | } else { 277 | query = []; 278 | } 279 | 280 | if (!query.some(function (v) { return v.startsWith(param_eq); })) { 281 | query.push(param_eq + encodeURIComponent(value)); 282 | elem.search = "?" + query.join("&"); 283 | } 284 | 285 | // some browsers (e.g. IE11) may occasionally omit the leading slash 286 | // in the elem.pathname string. Handle that case gracefully. 287 | if (elem.pathname.charAt(0) == "/") { 288 | return elem.pathname.slice(1) + elem.search + elem.hash; 289 | } else { 290 | return elem.pathname + elem.search + elem.hash; 291 | } 292 | }; 293 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // noVNC proxy implementation with AppNet.io 2 | // Copyright (c) 2013-present Tom Zhou 3 | 4 | var WEBPP = require('appnet.io'), 5 | noVNC = require('./novnc'), 6 | http = require('http'), 7 | https = require('https'), 8 | WebSocket = require('wspp'), 9 | WebSocketServer = WebSocket.Server, 10 | Connect = require('connect'), 11 | OS = require('os'), 12 | UDT = require('udt'); 13 | 14 | 15 | // Debug level 16 | // 1: display error, proxy entry 17 | // 2: display vnc rfb info 18 | var Debug = 0; 19 | 20 | // Proxy class 21 | // a proxy will contain one appnet.io name-client 22 | // - vncs: array of VNC server host:port pair, like ['localhost:5900', '51dese.com:5901'}] 23 | // - fn: callback to pass proxy informations 24 | // - options: user custom parameters, like {usrkey: ..., domain: ..., endpoints: ..., turn: ...} 25 | // - options.secmode: ssl, enable ssl/https; acl, enable ssl/https,host-based ACL 26 | // - options.sslmode: srv, only verify server side cert; both, verify both server and client side cert 27 | // - options.auth: http basic-auth as username:password 28 | // - options.upload: file upload directory 29 | var Proxy = module.exports = function(vncs, fn, options){ 30 | var self = this; 31 | 32 | if (!(this instanceof Proxy)) return new Proxy(vncs, fn, options); 33 | 34 | // check arguments 35 | if (typeof vncs === 'function') { 36 | options = fn || {}; 37 | fn = vncs; 38 | vncs = []; 39 | } 40 | 41 | if (!Array.isArray(vncs)) vncs = [vncs]; 42 | 43 | // check basic auth 44 | var basicauth = false; 45 | if (options && options.auth) { 46 | var astr = options.auth.split(':'); 47 | basicauth = {username: astr && astr[0], password: astr && astr[1]}; 48 | } 49 | 50 | // check upload 51 | var fileupload = (options && options.upload) || false; 52 | 53 | // 1. 54 | // proxy URLs 55 | self.proxyURL = {}; // vURL for VNC server 56 | 57 | // websocket proxy servers 58 | self.proxyWss = {}; 59 | 60 | // 2. 61 | // create name client 62 | var nmcln = self.nmcln = new WEBPP({ 63 | usrinfo: { 64 | domain: (options && options.domain) || '51dese.com', 65 | usrkey: (options && options.usrkey) || ('peervnc@'+Date.now()) 66 | }, 67 | 68 | srvinfo: { 69 | timeout: 20, 70 | endpoints: (options && options.endpoints) || [ 71 | {ip: '51dese.com', port: 51686}, 72 | {ip: '51dese.com', port: 51868} 73 | ], 74 | turn: (options && options.turn) || [ 75 | {ip: '51dese.com', agent: 51866, proxy: 51688} 76 | ] 77 | }, 78 | 79 | vmode: WEBPP.vURL.URL_MODE_PATH, 80 | 81 | // secure mode 82 | secmode: (options && options.secmode === 'ssl') ? WEBPP.SEP.SEP_SEC_SSL : 83 | WEBPP.SEP.SEP_SEC_SSL_ACL_HOST, 84 | // ssl mode 85 | sslmode: (options && options.sslmode === 'both') ? WEBPP.SEP.SEP_SSL_AUTH_SRV_CLNT : 86 | WEBPP.SEP.SEP_SSL_AUTH_SRV_ONLY 87 | }); 88 | 89 | // 2.1 90 | // check ready 91 | nmcln.once('ready', function(){ 92 | if (Debug) console.log('name-client ready on vURL:'+nmcln.vurl); 93 | 94 | // 3. 95 | // setup noVNC proxy 96 | for (var idx = 0; idx < vncs.length; idx ++) { 97 | var vncstrs = vncs[idx].split(':'); 98 | var vnchost = vncstrs[0]; 99 | var vncport = vncstrs[1] ? 100 | parseInt(vncstrs[1], 10) : 101 | 5900; // default VNC port 102 | 103 | // assume vncserver listen on 5900 above 104 | vncport = (vncport < 5900) ? 5900 + vncport : vncport; 105 | 106 | // add VNC host proxy entry 107 | self.addVNC({host: vnchost, port: vncport}); 108 | } 109 | 110 | // 4. 111 | // create http App 112 | var appHttp = Connect(); 113 | 114 | // 4.1 115 | // add third-party connect middle-ware 116 | // TBD... 117 | 118 | // 4.2 119 | // add noVNC web service in App 120 | appHttp.use(noVNC.webServer({auth: basicauth, upload: fileupload})); 121 | 122 | // 5. 123 | // hook http App on name-client 124 | nmcln.bsrv.srv.on('request', appHttp); 125 | 126 | // 5.1 127 | // handle http CONNECT request in case come from forward proxy 128 | // !!! just create connection to peer-vnc httpps server self. 129 | nmcln.bsrv.srv.on('connect', function(req, socket, head) { 130 | var roptions = { 131 | port: nmcln.port, 132 | host: nmcln.ipaddr, 133 | localAddress: { 134 | addr: nmcln.ipaddr 135 | } 136 | }; 137 | 138 | // check req.url 139 | if (!(req.url && nmcln.vurl.match((req.url.split(':'))[0]))) { 140 | console.log('invalid proxed url: '+req.url); 141 | socket.end(); 142 | return; 143 | } 144 | 145 | if (Debug) console.log('http tunnel proxy, connect to self %s:%d for %s', nmcln.ipaddr, nmcln.port, req.url); 146 | 147 | var srvSocket = UDT.connect(roptions, function() { 148 | if (Debug) console.log('http tunnel proxy, got connected!'); 149 | 150 | ///srvSocket.write(head); 151 | socket.pipe(srvSocket); 152 | 153 | socket.write('HTTP/1.1 200 Connection Established\r\n' + 154 | 'Proxy-agent: Node-Proxy\r\n' + 155 | '\r\n'); 156 | srvSocket.pipe(socket); 157 | }); 158 | 159 | srvSocket.on('error', function(e) { 160 | console.log("http tunnel proxy, socket error: " + e); 161 | socket.end(); 162 | }); 163 | }); 164 | 165 | // 6. 166 | // pass proxy URLs back 167 | fn(null, self.proxyURL); 168 | }); 169 | 170 | // 2.2 171 | // check error 172 | nmcln.on('error', function(err){ 173 | console.log('name-client create failed:'+JSON.stringify(err)); 174 | fn(err); 175 | }); 176 | }; 177 | 178 | // add VNC host:port entry 179 | // - vnc: {host: x, port: x} 180 | // - return proxy vURL 181 | Proxy.prototype.addVNC = function(vnc) { 182 | var self = this; 183 | 184 | 185 | // 0. 186 | // check vnc host/port 187 | if (!(vnc && 188 | (typeof vnc.host === 'string') && 189 | (typeof vnc.port === 'number'))) { 190 | console.log('invalid VNC host '+JSON.stringify(vnc)); 191 | return self; 192 | } 193 | 194 | // 1. 195 | // create ws server to proxy VNC/RFB data 196 | var vncstr = vnc.host+':'+vnc.port; 197 | var wspath = '/'+vnc.host+'-'+vnc.port; 198 | var vncwss = new WebSocketServer({httpp: true, server: self.nmcln.bsrv.srv, path: wspath}); 199 | 200 | vncwss.on('connection', noVNC.tcpProxy({host: vnc.host, port: vnc.port})); 201 | 202 | self.proxyWss[vncstr] = vncwss; 203 | self.proxyURL[vncstr] = self.nmcln.vurl + wspath; 204 | 205 | // 2. 206 | // report peer-service 207 | // like {vurl:x,cate:x,name:x,desc:x,tags:x,acls:x,accounting:x,meta:x} 208 | self.nmcln.reportService({ 209 | vurl: self.proxyURL[vncstr], 210 | cate: 'peer-vnc', 211 | name: 'vnc'+Object.keys(self.proxyWss).length, 212 | meta: { 213 | vnchost: vnc.host === 'localhost' ? OS.hostname() : vnc.host, 214 | vncport: vnc.port 215 | } 216 | }); 217 | 218 | // 3. 219 | // update peer-service: connection loss, etc 220 | // TBD... 221 | 222 | return self.proxyURL[vncstr]; 223 | }; 224 | 225 | // remove VNC host:port entry 226 | // - vnc: {host: x, port: x} 227 | Proxy.prototype.removeVNC = function(vnc, fn) { 228 | var self = this; 229 | 230 | 231 | // 0. 232 | // check vnc host/port 233 | if (!(vnc && 234 | (typeof vnc.host === 'string') && 235 | (typeof vnc.port === 'number'))) { 236 | console.log('invalid VNC host '+JSON.stringify(vnc)); 237 | return self; 238 | } 239 | 240 | // 1. 241 | // close websocket proxy server 242 | var vncstr = vnc.host+':'+vnc.port; 243 | 244 | if (self.proxyWss[vncstr]) { 245 | self.proxyWss[vncstr].close(); 246 | 247 | // 2. 248 | // remove proxy URL after 2s 249 | setTimeout(function(){ 250 | self.proxyWss[vncstr] = null; 251 | self.proxyURL[vncstr] = null; 252 | }, 2000); 253 | } 254 | 255 | return self; 256 | }; 257 | 258 | // simple test 259 | /* 260 | var server = new Proxy(['192.188.1.101:5900'], function(err, proxyURL){ 261 | console.log('VNC Proxy URL(please open it on browser)'); 262 | for (var k in proxyURL) { 263 | console.log(k+' '+proxyURL[k]); 264 | } 265 | }); 266 | */ 267 | -------------------------------------------------------------------------------- /front/vnc_auto.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 17 | noVNC 18 | 19 | 20 | 21 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 36 | 37 | 38 | 39 | 40 | 41 | 45 | 46 | 47 | 48 | 49 |
50 |
51 | 52 | 55 | 67 |
53 | Loading 54 |
56 | 58 | 59 | 61 | 63 | 65 | 66 |
68 |
69 | 70 | Canvas not supported. 71 | 72 |
73 | 74 | 245 | 246 | 247 | 248 | -------------------------------------------------------------------------------- /front/include/chrome-app/tcp-client.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2012 Google Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | 16 | Author: Boris Smus (smus@chromium.org) 17 | */ 18 | 19 | (function(exports) { 20 | 21 | // Define some local variables here. 22 | var socket = chrome.socket || chrome.experimental.socket; 23 | var dns = chrome.experimental.dns; 24 | 25 | /** 26 | * Creates an instance of the client 27 | * 28 | * @param {String} host The remote host to connect to 29 | * @param {Number} port The port to connect to at the remote host 30 | */ 31 | function TcpClient(host, port, pollInterval) { 32 | this.host = host; 33 | this.port = port; 34 | this.pollInterval = pollInterval || 15; 35 | 36 | // Callback functions. 37 | this.callbacks = { 38 | connect: null, // Called when socket is connected. 39 | disconnect: null, // Called when socket is disconnected. 40 | recvBuffer: null, // Called (as ArrayBuffer) when client receives data from server. 41 | recvString: null, // Called (as string) when client receives data from server. 42 | sent: null // Called when client sends data to server. 43 | }; 44 | 45 | // Socket. 46 | this.socketId = null; 47 | this.isConnected = false; 48 | 49 | log('initialized tcp client'); 50 | } 51 | 52 | /** 53 | * Connects to the TCP socket, and creates an open socket. 54 | * 55 | * @see http://developer.chrome.com/trunk/apps/socket.html#method-create 56 | * @param {Function} callback The function to call on connection 57 | */ 58 | TcpClient.prototype.connect = function(callback) { 59 | // First resolve the hostname to an IP. 60 | dns.resolve(this.host, function(result) { 61 | this.addr = result.address; 62 | socket.create('tcp', {}, this._onCreate.bind(this)); 63 | 64 | // Register connect callback. 65 | this.callbacks.connect = callback; 66 | }.bind(this)); 67 | }; 68 | 69 | /** 70 | * Sends an arraybuffer/view down the wire to the remote side 71 | * 72 | * @see http://developer.chrome.com/trunk/apps/socket.html#method-write 73 | * @param {String} msg The arraybuffer/view to send 74 | * @param {Function} callback The function to call when the message has sent 75 | */ 76 | TcpClient.prototype.sendBuffer = function(buf, callback) { 77 | if (buf.buffer) { 78 | buf = buf.buffer; 79 | } 80 | 81 | /* 82 | // Debug 83 | var bytes = [], u8 = new Uint8Array(buf); 84 | for (var i = 0; i < u8.length; i++) { 85 | bytes.push(u8[i]); 86 | } 87 | log("sending bytes: " + (bytes.join(','))); 88 | */ 89 | 90 | socket.write(this.socketId, buf, this._onWriteComplete.bind(this)); 91 | 92 | // Register sent callback. 93 | this.callbacks.sent = callback; 94 | }; 95 | 96 | /** 97 | * Sends a string down the wire to the remote side 98 | * 99 | * @see http://developer.chrome.com/trunk/apps/socket.html#method-write 100 | * @param {String} msg The string to send 101 | * @param {Function} callback The function to call when the message has sent 102 | */ 103 | TcpClient.prototype.sendString = function(msg, callback) { 104 | /* 105 | // Debug 106 | log("sending string: " + msg); 107 | */ 108 | 109 | this._stringToArrayBuffer(msg, function(arrayBuffer) { 110 | socket.write(this.socketId, arrayBuffer, this._onWriteComplete.bind(this)); 111 | }.bind(this)); 112 | 113 | // Register sent callback. 114 | this.callbacks.sent = callback; 115 | }; 116 | 117 | /** 118 | * Sets the callback for when a message is received 119 | * 120 | * @param {Function} callback The function to call when a message has arrived 121 | * @param {String} type The callback argument type: "arraybuffer" or "string" 122 | */ 123 | TcpClient.prototype.addResponseListener = function(callback, type) { 124 | if (typeof type === "undefined") { 125 | type = "arraybuffer"; 126 | } 127 | // Register received callback. 128 | if (type === "string") { 129 | this.callbacks.recvString = callback; 130 | } else { 131 | this.callbacks.recvBuffer = callback; 132 | } 133 | }; 134 | 135 | /** 136 | * Sets the callback for when the socket disconnects 137 | * 138 | * @param {Function} callback The function to call when the socket disconnects 139 | * @param {String} type The callback argument type: "arraybuffer" or "string" 140 | */ 141 | TcpClient.prototype.addDisconnectListener = function(callback) { 142 | // Register disconnect callback. 143 | this.callbacks.disconnect = callback; 144 | }; 145 | 146 | /** 147 | * Disconnects from the remote side 148 | * 149 | * @see http://developer.chrome.com/trunk/apps/socket.html#method-disconnect 150 | */ 151 | TcpClient.prototype.disconnect = function() { 152 | if (this.isConnected) { 153 | this.isConnected = false; 154 | socket.disconnect(this.socketId); 155 | if (this.callbacks.disconnect) { 156 | this.callbacks.disconnect(); 157 | } 158 | log('socket disconnected'); 159 | } 160 | }; 161 | 162 | /** 163 | * The callback function used for when we attempt to have Chrome 164 | * create a socket. If the socket is successfully created 165 | * we go ahead and connect to the remote side. 166 | * 167 | * @private 168 | * @see http://developer.chrome.com/trunk/apps/socket.html#method-connect 169 | * @param {Object} createInfo The socket details 170 | */ 171 | TcpClient.prototype._onCreate = function(createInfo) { 172 | this.socketId = createInfo.socketId; 173 | if (this.socketId > 0) { 174 | socket.connect(this.socketId, this.addr, this.port, this._onConnectComplete.bind(this)); 175 | } else { 176 | error('Unable to create socket'); 177 | } 178 | }; 179 | 180 | /** 181 | * The callback function used for when we attempt to have Chrome 182 | * connect to the remote side. If a successful connection is 183 | * made then polling starts to check for data to read 184 | * 185 | * @private 186 | * @param {Number} resultCode Indicates whether the connection was successful 187 | */ 188 | TcpClient.prototype._onConnectComplete = function(resultCode) { 189 | // Start polling for reads. 190 | this.isConnected = true; 191 | setTimeout(this._periodicallyRead.bind(this), this.pollInterval); 192 | 193 | if (this.callbacks.connect) { 194 | log('connect complete'); 195 | this.callbacks.connect(); 196 | } 197 | log('onConnectComplete'); 198 | }; 199 | 200 | /** 201 | * Checks for new data to read from the socket 202 | * 203 | * @see http://developer.chrome.com/trunk/apps/socket.html#method-read 204 | */ 205 | TcpClient.prototype._periodicallyRead = function() { 206 | var that = this; 207 | socket.getInfo(this.socketId, function (info) { 208 | if (info.connected) { 209 | setTimeout(that._periodicallyRead.bind(that), that.pollInterval); 210 | socket.read(that.socketId, null, that._onDataRead.bind(that)); 211 | } else if (that.isConnected) { 212 | log('socket disconnect detected'); 213 | that.disconnect(); 214 | } 215 | }); 216 | }; 217 | 218 | /** 219 | * Callback function for when data has been read from the socket. 220 | * Converts the array buffer that is read in to a string 221 | * and sends it on for further processing by passing it to 222 | * the previously assigned callback function. 223 | * 224 | * @private 225 | * @see TcpClient.prototype.addResponseListener 226 | * @param {Object} readInfo The incoming message 227 | */ 228 | TcpClient.prototype._onDataRead = function(readInfo) { 229 | // Call received callback if there's data in the response. 230 | if (readInfo.resultCode > 0) { 231 | log('onDataRead'); 232 | 233 | /* 234 | // Debug 235 | var bytes = [], u8 = new Uint8Array(readInfo.data); 236 | for (var i = 0; i < u8.length; i++) { 237 | bytes.push(u8[i]); 238 | } 239 | log("received bytes: " + (bytes.join(','))); 240 | */ 241 | 242 | if (this.callbacks.recvBuffer) { 243 | // Return raw ArrayBuffer directly. 244 | this.callbacks.recvBuffer(readInfo.data); 245 | } 246 | if (this.callbacks.recvString) { 247 | // Convert ArrayBuffer to string. 248 | this._arrayBufferToString(readInfo.data, function(str) { 249 | this.callbacks.recvString(str); 250 | }.bind(this)); 251 | } 252 | 253 | // Trigger another read right away 254 | setTimeout(this._periodicallyRead.bind(this), 0); 255 | } 256 | }; 257 | 258 | /** 259 | * Callback for when data has been successfully 260 | * written to the socket. 261 | * 262 | * @private 263 | * @param {Object} writeInfo The outgoing message 264 | */ 265 | TcpClient.prototype._onWriteComplete = function(writeInfo) { 266 | log('onWriteComplete'); 267 | // Call sent callback. 268 | if (this.callbacks.sent) { 269 | this.callbacks.sent(writeInfo); 270 | } 271 | }; 272 | 273 | /** 274 | * Converts an array buffer to a string 275 | * 276 | * @private 277 | * @param {ArrayBuffer} buf The buffer to convert 278 | * @param {Function} callback The function to call when conversion is complete 279 | */ 280 | TcpClient.prototype._arrayBufferToString = function(buf, callback) { 281 | var bb = new Blob([new Uint8Array(buf)]); 282 | var f = new FileReader(); 283 | f.onload = function(e) { 284 | callback(e.target.result); 285 | }; 286 | f.readAsText(bb); 287 | }; 288 | 289 | /** 290 | * Converts a string to an array buffer 291 | * 292 | * @private 293 | * @param {String} str The string to convert 294 | * @param {Function} callback The function to call when conversion is complete 295 | */ 296 | TcpClient.prototype._stringToArrayBuffer = function(str, callback) { 297 | var bb = new Blob([str]); 298 | var f = new FileReader(); 299 | f.onload = function(e) { 300 | callback(e.target.result); 301 | }; 302 | f.readAsArrayBuffer(bb); 303 | }; 304 | 305 | /** 306 | * Wrapper function for logging 307 | */ 308 | function log(msg) { 309 | console.log(msg); 310 | } 311 | 312 | /** 313 | * Wrapper function for error logging 314 | */ 315 | function error(msg) { 316 | console.error(msg); 317 | } 318 | 319 | exports.TcpClient = TcpClient; 320 | 321 | })(window); 322 | -------------------------------------------------------------------------------- /front/vnc.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 17 | noVNC 18 | 19 | 20 | 21 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 47 | 48 | 49 | 50 | 51 |
52 | 53 |
54 | 57 |
58 | 60 | 62 | 64 | 66 | 69 | 73 | 76 |
77 | 79 | 81 | 83 | 85 | 87 |
88 |
89 |
90 | 91 |
92 | 93 | 94 |
95 | 98 | 101 | 104 | 107 | 110 | 113 | 116 |
117 | 118 | 119 | 120 |
121 | noVNC is a browser based VNC client implemented using HTML5 Canvas 122 | and WebSockets. You will either need a VNC server with WebSockets 123 | support (such as libvncserver) 124 | or you will need to use 125 | websockify 126 | to bridge between your browser and VNC server. See the noVNC 127 | README 128 | and website 129 | for more information. 130 |
131 | 132 |
133 | 134 | 135 |
136 |
137 | 138 | 139 |
140 | 142 |
143 | 145 |
146 | 147 | 148 |
149 | 150 | 151 | 152 | 153 | 154 |
155 | 156 | 157 |
158 | 159 |
    160 |
  • Encrypt
  • 161 |
  • True Color
  • 162 |
  • Local Cursor
  • 163 |
  • Clip to Window
  • 164 |
  • Shared Mode
  • 165 |
  • View Only
  • 166 |
    167 |
  • Path
  • 168 |
  • 175 |
  • 176 |
  • Repeater ID
  • 177 |
    178 | 179 |
  • 183 |
  • 184 | 185 | 186 |
  • 189 |
  • 190 |
    191 |
  • 192 |
193 |
194 |
195 | 196 | 197 |
198 |
    199 |
  • 200 |
  • 201 |
  • 202 |
  • 203 |
  • 204 |
205 |
206 | 207 |
208 | 209 | 210 |
211 |

no
VNC

212 | 213 | 214 |
215 | 216 | Canvas not supported. 217 | 218 |
219 | 220 |
221 | 222 | 223 | 224 | 225 | 226 | -------------------------------------------------------------------------------- /front/include/des.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Ported from Flashlight VNC ActionScript implementation: 3 | * http://www.wizhelp.com/flashlight-vnc/ 4 | * 5 | * Full attribution follows: 6 | * 7 | * ------------------------------------------------------------------------- 8 | * 9 | * This DES class has been extracted from package Acme.Crypto for use in VNC. 10 | * The unnecessary odd parity code has been removed. 11 | * 12 | * These changes are: 13 | * Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved. 14 | * 15 | * This software is distributed in the hope that it will be useful, 16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 18 | * 19 | 20 | * DesCipher - the DES encryption method 21 | * 22 | * The meat of this code is by Dave Zimmerman , and is: 23 | * 24 | * Copyright (c) 1996 Widget Workshop, Inc. All Rights Reserved. 25 | * 26 | * Permission to use, copy, modify, and distribute this software 27 | * and its documentation for NON-COMMERCIAL or COMMERCIAL purposes and 28 | * without fee is hereby granted, provided that this copyright notice is kept 29 | * intact. 30 | * 31 | * WIDGET WORKSHOP MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY 32 | * OF THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 33 | * TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 34 | * PARTICULAR PURPOSE, OR NON-INFRINGEMENT. WIDGET WORKSHOP SHALL NOT BE LIABLE 35 | * FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR 36 | * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. 37 | * 38 | * THIS SOFTWARE IS NOT DESIGNED OR INTENDED FOR USE OR RESALE AS ON-LINE 39 | * CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING FAIL-SAFE 40 | * PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES, AIRCRAFT 41 | * NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT LIFE 42 | * SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE 43 | * SOFTWARE COULD LEAD DIRECTLY TO DEATH, PERSONAL INJURY, OR SEVERE 44 | * PHYSICAL OR ENVIRONMENTAL DAMAGE ("HIGH RISK ACTIVITIES"). WIDGET WORKSHOP 45 | * SPECIFICALLY DISCLAIMS ANY EXPRESS OR IMPLIED WARRANTY OF FITNESS FOR 46 | * HIGH RISK ACTIVITIES. 47 | * 48 | * 49 | * The rest is: 50 | * 51 | * Copyright (C) 1996 by Jef Poskanzer . All rights reserved. 52 | * 53 | * Redistribution and use in source and binary forms, with or without 54 | * modification, are permitted provided that the following conditions 55 | * are met: 56 | * 1. Redistributions of source code must retain the above copyright 57 | * notice, this list of conditions and the following disclaimer. 58 | * 2. Redistributions in binary form must reproduce the above copyright 59 | * notice, this list of conditions and the following disclaimer in the 60 | * documentation and/or other materials provided with the distribution. 61 | * 62 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 63 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 64 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 65 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 66 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 67 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 68 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 69 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 70 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 71 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 72 | * SUCH DAMAGE. 73 | * 74 | * Visit the ACME Labs Java page for up-to-date versions of this and other 75 | * fine Java utilities: http://www.acme.com/java/ 76 | */ 77 | 78 | /* jslint white: false */ 79 | 80 | function DES(passwd) { 81 | "use strict"; 82 | 83 | // Tables, permutations, S-boxes, etc. 84 | // jshint -W013 85 | var PC2 = [13,16,10,23, 0, 4, 2,27,14, 5,20, 9,22,18,11, 3, 86 | 25, 7,15, 6,26,19,12, 1,40,51,30,36,46,54,29,39, 87 | 50,44,32,47,43,48,38,55,33,52,45,41,49,35,28,31 ], 88 | totrot = [ 1, 2, 4, 6, 8,10,12,14,15,17,19,21,23,25,27,28], 89 | z = 0x0, a,b,c,d,e,f, SP1,SP2,SP3,SP4,SP5,SP6,SP7,SP8, 90 | keys = []; 91 | 92 | // jshint -W015 93 | a=1<<16; b=1<<24; c=a|b; d=1<<2; e=1<<10; f=d|e; 94 | 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, 95 | 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, 96 | 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, 97 | 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]; 98 | a=1<<20; b=1<<31; c=a|b; d=1<<5; e=1<<15; f=d|e; 99 | 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, 100 | 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, 101 | 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, 102 | 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]; 103 | a=1<<17; b=1<<27; c=a|b; d=1<<3; e=1<<9; f=d|e; 104 | 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, 105 | 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, 106 | 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, 107 | 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]; 108 | a=1<<13; b=1<<23; c=a|b; d=1<<0; e=1<<7; f=d|e; 109 | 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, 110 | 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, 111 | 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, 112 | 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]; 113 | a=1<<25; b=1<<30; c=a|b; d=1<<8; e=1<<19; f=d|e; 114 | 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, 115 | 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, 116 | 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, 117 | 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]; 118 | a=1<<22; b=1<<29; c=a|b; d=1<<4; e=1<<14; f=d|e; 119 | 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, 120 | 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, 121 | 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, 122 | 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]; 123 | a=1<<21; b=1<<26; c=a|b; d=1<<1; e=1<<11; f=d|e; 124 | 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, 125 | 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, 126 | 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, 127 | 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]; 128 | a=1<<18; b=1<<28; c=a|b; d=1<<6; e=1<<12; f=d|e; 129 | 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, 130 | 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, 131 | 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, 132 | 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]; 133 | // jshint +W013,+W015 134 | 135 | // Set the key. 136 | function setKeys(keyBlock) { 137 | var i, j, l, m, n, o, pc1m = [], pcr = [], kn = [], 138 | raw0, raw1, rawi, KnLi; 139 | 140 | for (j = 0, l = 56; j < 56; ++j, l -= 8) { 141 | l += l < -5 ? 65 : l < -3 ? 31 : l < -1 ? 63 : l === 27 ? 35 : 0; // PC1 142 | m = l & 0x7; 143 | pc1m[j] = ((keyBlock[l >>> 3] & (1<>> 10; 177 | keys[KnLi] |= (raw1 & 0x00000fc0) >>> 6; 178 | ++KnLi; 179 | keys[KnLi] = (raw0 & 0x0003f000) << 12; 180 | keys[KnLi] |= (raw0 & 0x0000003f) << 16; 181 | keys[KnLi] |= (raw1 & 0x0003f000) >>> 4; 182 | keys[KnLi] |= (raw1 & 0x0000003f); 183 | ++KnLi; 184 | } 185 | } 186 | 187 | // Encrypt 8 bytes of text 188 | function enc8(text) { 189 | var i = 0, b = text.slice(), fval, keysi = 0, 190 | l, r, x; // left, right, accumulator 191 | 192 | // Squash 8 bytes to 2 ints 193 | l = b[i++]<<24 | b[i++]<<16 | b[i++]<<8 | b[i++]; 194 | r = b[i++]<<24 | b[i++]<<16 | b[i++]<<8 | b[i++]; 195 | 196 | x = ((l >>> 4) ^ r) & 0x0f0f0f0f; 197 | r ^= x; 198 | l ^= (x << 4); 199 | x = ((l >>> 16) ^ r) & 0x0000ffff; 200 | r ^= x; 201 | l ^= (x << 16); 202 | x = ((r >>> 2) ^ l) & 0x33333333; 203 | l ^= x; 204 | r ^= (x << 2); 205 | x = ((r >>> 8) ^ l) & 0x00ff00ff; 206 | l ^= x; 207 | r ^= (x << 8); 208 | r = (r << 1) | ((r >>> 31) & 1); 209 | x = (l ^ r) & 0xaaaaaaaa; 210 | l ^= x; 211 | r ^= x; 212 | l = (l << 1) | ((l >>> 31) & 1); 213 | 214 | for (i = 0; i < 8; ++i) { 215 | x = (r << 28) | (r >>> 4); 216 | x ^= keys[keysi++]; 217 | fval = SP7[x & 0x3f]; 218 | fval |= SP5[(x >>> 8) & 0x3f]; 219 | fval |= SP3[(x >>> 16) & 0x3f]; 220 | fval |= SP1[(x >>> 24) & 0x3f]; 221 | x = r ^ keys[keysi++]; 222 | fval |= SP8[x & 0x3f]; 223 | fval |= SP6[(x >>> 8) & 0x3f]; 224 | fval |= SP4[(x >>> 16) & 0x3f]; 225 | fval |= SP2[(x >>> 24) & 0x3f]; 226 | l ^= fval; 227 | x = (l << 28) | (l >>> 4); 228 | x ^= keys[keysi++]; 229 | fval = SP7[x & 0x3f]; 230 | fval |= SP5[(x >>> 8) & 0x3f]; 231 | fval |= SP3[(x >>> 16) & 0x3f]; 232 | fval |= SP1[(x >>> 24) & 0x3f]; 233 | x = l ^ keys[keysi++]; 234 | fval |= SP8[x & 0x0000003f]; 235 | fval |= SP6[(x >>> 8) & 0x3f]; 236 | fval |= SP4[(x >>> 16) & 0x3f]; 237 | fval |= SP2[(x >>> 24) & 0x3f]; 238 | r ^= fval; 239 | } 240 | 241 | r = (r << 31) | (r >>> 1); 242 | x = (l ^ r) & 0xaaaaaaaa; 243 | l ^= x; 244 | r ^= x; 245 | l = (l << 31) | (l >>> 1); 246 | x = ((l >>> 8) ^ r) & 0x00ff00ff; 247 | r ^= x; 248 | l ^= (x << 8); 249 | x = ((l >>> 2) ^ r) & 0x33333333; 250 | r ^= x; 251 | l ^= (x << 2); 252 | x = ((r >>> 16) ^ l) & 0x0000ffff; 253 | l ^= x; 254 | r ^= (x << 16); 255 | x = ((r >>> 4) ^ l) & 0x0f0f0f0f; 256 | l ^= x; 257 | r ^= (x << 4); 258 | 259 | // Spread ints to bytes 260 | x = [r, l]; 261 | for (i = 0; i < 8; i++) { 262 | b[i] = (x[i>>>2] >>> (8 * (3 - (i % 4)))) % 256; 263 | if (b[i] < 0) { b[i] += 256; } // unsigned 264 | } 265 | return b; 266 | } 267 | 268 | // Encrypt 16 bytes of text using passwd as key 269 | function encrypt(t) { 270 | return enc8(t.slice(0, 8)).concat(enc8(t.slice(8, 16))); 271 | } 272 | 273 | setKeys(passwd); // Setup keys 274 | return {'encrypt': encrypt}; // Public interface 275 | 276 | } // function DES 277 | -------------------------------------------------------------------------------- /front/novnc.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 17 | peerVNC 18 | 19 | 20 | 21 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 101 | 102 | 103 | 104 | 105 |
106 | 107 |
108 | 111 |
112 | 114 | 116 | 118 | 120 | 123 | 127 | 130 |
131 | 133 | 135 | 137 | 139 | 141 |
142 |
143 |
144 | 145 |
146 | 147 | 148 |
149 | 152 | 155 | 158 | 161 | 164 | 167 | 170 |
171 | 172 | 173 | 174 |
175 | noVNC is a browser based VNC client implemented using HTML5 Canvas 176 | and WebSockets. You will either need a VNC server with WebSockets 177 | support (such as libvncserver) 178 | or you will need to use 179 | websockify 180 | to bridge between your browser and VNC server. See the noVNC 181 | README 182 | and website 183 | for more information. 184 |
185 | 186 |
187 | 188 | 189 |
190 |
191 | 192 | 193 |
194 | 196 |
197 | 199 |
200 | 201 | 202 |
203 | 204 | 205 | 206 | 207 | 208 |
209 | 210 | 211 |
212 | 213 |
    214 |
  • Encrypt
  • 215 |
  • True Color
  • 216 |
  • Local Cursor
  • 217 |
  • Clip to Window
  • 218 |
  • Shared Mode
  • 219 |
  • View Only
  • 220 |
    221 | 222 |
  • 229 |
  • 230 |
  • Repeater ID
  • 231 |
    232 | 233 |
  • 237 |
  • 238 | 239 | 240 |
  • 243 |
  • 244 |
    245 |
  • 246 |
247 |
248 |
249 | 250 | 251 |
252 |
    253 | 254 | 255 |
  • 256 | 257 |
  • 258 |
259 |
260 | 261 |
262 | 263 |
264 | 265 | 266 |
268 | 269 |

peer
VNC

270 | 271 |
272 | 273 | Canvas not supported. 274 | 275 |
276 |
277 |
278 | 279 | 280 | 281 | 282 | 283 | 284 | -------------------------------------------------------------------------------- /front/include/base.css: -------------------------------------------------------------------------------- 1 | /* 2 | * noVNC base CSS 3 | * Copyright (C) 2012 Joel Martin 4 | * Copyright (C) 2016 Samuel Mannehed for Cendio AB 5 | * noVNC is licensed under the MPL 2.0 (see LICENSE.txt) 6 | * This file is licensed under the 2-Clause BSD license (see LICENSE.txt). 7 | */ 8 | 9 | body { 10 | margin:0; 11 | padding:0; 12 | font-family: Helvetica; 13 | /*Background image with light grey curve.*/ 14 | background-color:#494949; 15 | background-repeat:no-repeat; 16 | background-position:right bottom; 17 | height:100%; 18 | } 19 | 20 | html { 21 | height:100%; 22 | } 23 | 24 | #noVNC_controls ul { 25 | list-style: none; 26 | margin: 0px; 27 | padding: 0px; 28 | } 29 | #noVNC_controls li { 30 | padding-bottom:8px; 31 | } 32 | 33 | #noVNC_setting_host { 34 | width:150px; 35 | } 36 | #noVNC_setting_port { 37 | width: 80px; 38 | } 39 | #noVNC_setting_password { 40 | width: 150px; 41 | } 42 | #noVNC_setting_encrypt { 43 | } 44 | #noVNC_setting_path { 45 | width: 100px; 46 | } 47 | #noVNC_connect_button { 48 | width: 110px; 49 | float:right; 50 | } 51 | 52 | #noVNC_buttons { 53 | white-space: nowrap; 54 | } 55 | 56 | #noVNC_view_drag_button { 57 | display: none; 58 | } 59 | #noVNC_sendCtrlAltDel_button { 60 | display: none; 61 | } 62 | #noVNC_fullscreen_button { 63 | display: none; 64 | } 65 | #noVNC_xvp_buttons { 66 | display: none; 67 | } 68 | #noVNC_mobile_buttons { 69 | display: none; 70 | } 71 | 72 | #noVNC_extra_keys { 73 | display: inline; 74 | list-style-type: none; 75 | padding: 0px; 76 | margin: 0px; 77 | position: relative; 78 | } 79 | 80 | .noVNC_buttons_left { 81 | float: left; 82 | z-index: 1; 83 | position: relative; 84 | } 85 | 86 | .noVNC_buttons_right { 87 | float:right; 88 | right: 0px; 89 | z-index: 2; 90 | position: absolute; 91 | } 92 | 93 | #noVNC_status { 94 | font-size: 12px; 95 | padding-top: 4px; 96 | height:32px; 97 | text-align: center; 98 | font-weight: bold; 99 | color: #fff; 100 | } 101 | 102 | #noVNC_settings_menu { 103 | margin: 3px; 104 | text-align: left; 105 | } 106 | #noVNC_settings_menu ul { 107 | list-style: none; 108 | margin: 0px; 109 | padding: 0px; 110 | } 111 | 112 | #noVNC_settings_apply { 113 | float:right; 114 | } 115 | 116 | #noVNC_container { 117 | display: table; 118 | width:100%; 119 | height:100%; 120 | background-color:#313131; 121 | border-bottom-right-radius: 800px 600px; 122 | /*border-top-left-radius: 800px 600px;*/ 123 | } 124 | 125 | #noVNC_screen { 126 | display: none; 127 | position: absolute; 128 | margin: 0px; 129 | padding: 0px; 130 | bottom: 0px; 131 | top: 36px; /* the height of the control bar */ 132 | left: 0px; 133 | right: 0px; 134 | width: auto; 135 | height: auto; 136 | } 137 | 138 | /* Do not set width/height for VNC_canvas or incorrect 139 | * scaling will occur. Canvas size depends on remote VNC 140 | * settings and noVNC settings. */ 141 | #noVNC_canvas { 142 | position: absolute; 143 | left: 0; 144 | right: 0; 145 | margin-left: auto; 146 | margin-right: auto; 147 | } 148 | 149 | #VNC_clipboard_clear_button { 150 | float:right; 151 | } 152 | #VNC_clipboard_text { 153 | font-size: 11px; 154 | } 155 | 156 | #noVNC_clipboard_clear_button { 157 | float:right; 158 | } 159 | 160 | /*Bubble contents divs*/ 161 | #noVNC_settings { 162 | display:none; 163 | margin-top:73px; 164 | right:20px; 165 | position:fixed; 166 | } 167 | 168 | #noVNC_controls { 169 | display:none; 170 | margin-top:73px; 171 | right:12px; 172 | position:fixed; 173 | } 174 | #noVNC_controls.top:after { 175 | right:15px; 176 | } 177 | 178 | #noVNC_description { 179 | display:none; 180 | position:fixed; 181 | 182 | margin-top:73px; 183 | right:20px; 184 | left:20px; 185 | padding:15px; 186 | color:#000; 187 | background:#eee; /* default background for browsers without gradient support */ 188 | 189 | border:2px solid #E0E0E0; 190 | -webkit-border-radius:10px; 191 | -moz-border-radius:10px; 192 | border-radius:10px; 193 | } 194 | 195 | #noVNC_popup_status { 196 | display:none; 197 | position: fixed; 198 | z-index: 1; 199 | 200 | margin:15px; 201 | margin-top:60px; 202 | padding:15px; 203 | width:auto; 204 | 205 | text-align:center; 206 | font-weight:bold; 207 | word-wrap:break-word; 208 | color:#fff; 209 | background:rgba(0,0,0,0.65); 210 | 211 | -webkit-border-radius:10px; 212 | -moz-border-radius:10px; 213 | border-radius:10px; 214 | } 215 | 216 | #noVNC_xvp { 217 | display:none; 218 | margin-top:73px; 219 | right:30px; 220 | position:fixed; 221 | } 222 | #noVNC_xvp.top:after { 223 | right:125px; 224 | } 225 | 226 | #noVNC_clipboard { 227 | display:none; 228 | margin-top:73px; 229 | right:30px; 230 | position:fixed; 231 | } 232 | #noVNC_clipboard.top:after { 233 | right:85px; 234 | } 235 | 236 | #noVNC_keyboardinput { 237 | width:1px; 238 | height:1px; 239 | background-color:#fff; 240 | color:#fff; 241 | border:0; 242 | position: relative; 243 | left: -40px; 244 | z-index: -1; 245 | ime-mode: disabled; 246 | } 247 | 248 | /* 249 | * Advanced Styling 250 | */ 251 | 252 | .noVNC_status_normal { 253 | background: #b2bdcd; /* Old browsers */ 254 | background: -moz-linear-gradient(top, #b2bdcd 0%, #899cb3 49%, #7e93af 51%, #6e84a3 100%); /* FF3.6+ */ 255 | 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+ */ 256 | background: -webkit-linear-gradient(top, #b2bdcd 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* Chrome10+,Safari5.1+ */ 257 | background: -o-linear-gradient(top, #b2bdcd 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* Opera11.10+ */ 258 | background: -ms-linear-gradient(top, #b2bdcd 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* IE10+ */ 259 | background: linear-gradient(top, #b2bdcd 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* W3C */ 260 | } 261 | .noVNC_status_error { 262 | background: #f04040; /* Old browsers */ 263 | background: -moz-linear-gradient(top, #f04040 0%, #899cb3 49%, #7e93af 51%, #6e84a3 100%); /* FF3.6+ */ 264 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#f04040), color-stop(49%,#899cb3), color-stop(51%,#7e93af), color-stop(100%,#6e84a3)); /* Chrome,Safari4+ */ 265 | background: -webkit-linear-gradient(top, #f04040 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* Chrome10+,Safari5.1+ */ 266 | background: -o-linear-gradient(top, #f04040 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* Opera11.10+ */ 267 | background: -ms-linear-gradient(top, #f04040 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* IE10+ */ 268 | background: linear-gradient(top, #f04040 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* W3C */ 269 | } 270 | .noVNC_status_warn { 271 | background: #f0f040; /* Old browsers */ 272 | background: -moz-linear-gradient(top, #f0f040 0%, #899cb3 49%, #7e93af 51%, #6e84a3 100%); /* FF3.6+ */ 273 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#f0f040), color-stop(49%,#899cb3), color-stop(51%,#7e93af), color-stop(100%,#6e84a3)); /* Chrome,Safari4+ */ 274 | background: -webkit-linear-gradient(top, #f0f040 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* Chrome10+,Safari5.1+ */ 275 | background: -o-linear-gradient(top, #f0f040 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* Opera11.10+ */ 276 | background: -ms-linear-gradient(top, #f0f040 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* IE10+ */ 277 | background: linear-gradient(top, #f0f040 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* W3C */ 278 | } 279 | 280 | /* Control bar */ 281 | #noVNC_control_bar { 282 | position:fixed; 283 | 284 | display:block; 285 | height:36px; 286 | left:0; 287 | top:0; 288 | width:100%; 289 | z-index:200; 290 | } 291 | 292 | .noVNC_status_button { 293 | padding: 4px 4px; 294 | vertical-align: middle; 295 | border:1px solid #869dbc; 296 | -webkit-border-radius: 6px; 297 | -moz-border-radius: 6px; 298 | border-radius: 6px; 299 | background: #b2bdcd; /* Old browsers */ 300 | background: -moz-linear-gradient(top, #b2bdcd 0%, #899cb3 49%, #7e93af 51%, #6e84a3 100%); /* FF3.6+ */ 301 | 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+ */ 302 | background: -webkit-linear-gradient(top, #b2bdcd 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* Chrome10+,Safari5.1+ */ 303 | background: -o-linear-gradient(top, #b2bdcd 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* Opera11.10+ */ 304 | background: -ms-linear-gradient(top, #b2bdcd 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* IE10+ */ 305 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#b2bdcd', endColorstr='#6e84a3',GradientType=0 ); /* IE6-9 */ 306 | background: linear-gradient(top, #b2bdcd 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* W3C */ 307 | /*box-shadow:inset 0.4px 0.4px 0.4px #000000;*/ 308 | } 309 | 310 | .noVNC_status_button_selected { 311 | padding: 4px 4px; 312 | vertical-align: middle; 313 | border:1px solid #4366a9; 314 | -webkit-border-radius: 6px; 315 | -moz-border-radius: 6px; 316 | background: #779ced; /* Old browsers */ 317 | background: -moz-linear-gradient(top, #779ced 0%, #3970e0 49%, #2160dd 51%, #2463df 100%); /* FF3.6+ */ 318 | 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+ */ 319 | background: -webkit-linear-gradient(top, #779ced 0%,#3970e0 49%,#2160dd 51%,#2463df 100%); /* Chrome10+,Safari5.1+ */ 320 | background: -o-linear-gradient(top, #779ced 0%,#3970e0 49%,#2160dd 51%,#2463df 100%); /* Opera11.10+ */ 321 | background: -ms-linear-gradient(top, #779ced 0%,#3970e0 49%,#2160dd 51%,#2463df 100%); /* IE10+ */ 322 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#779ced', endColorstr='#2463df',GradientType=0 ); /* IE6-9 */ 323 | background: linear-gradient(top, #779ced 0%,#3970e0 49%,#2160dd 51%,#2463df 100%); /* W3C */ 324 | /*box-shadow:inset 0.4px 0.4px 0.4px #000000;*/ 325 | } 326 | 327 | .noVNC_status_button:disabled { 328 | opacity: 0.4; 329 | } 330 | 331 | 332 | /*Settings Bubble*/ 333 | .triangle-right { 334 | position:relative; 335 | padding:15px; 336 | margin:1em 0 3em; 337 | color:#fff; 338 | background:#fff; /* default background for browsers without gradient support */ 339 | /* css3 */ 340 | /*background:-webkit-gradient(linear, 0 0, 0 100%, from(#2e88c4), to(#075698)); 341 | background:-moz-linear-gradient(#2e88c4, #075698); 342 | background:-o-linear-gradient(#2e88c4, #075698); 343 | background:linear-gradient(#2e88c4, #075698);*/ 344 | -webkit-border-radius:10px; 345 | -moz-border-radius:10px; 346 | border-radius:10px; 347 | color:#000; 348 | border:2px solid #E0E0E0; 349 | } 350 | 351 | .triangle-right.top:after { 352 | border-color: transparent #E0E0E0; 353 | border-width: 20px 20px 0 0; 354 | bottom: auto; 355 | left: auto; 356 | right: 50px; 357 | top: -20px; 358 | } 359 | 360 | .triangle-right:after { 361 | content:""; 362 | position:absolute; 363 | bottom:-20px; /* value = - border-top-width - border-bottom-width */ 364 | left:50px; /* controls horizontal position */ 365 | border-width:20px 0 0 20px; /* vary these values to change the angle of the vertex */ 366 | border-style:solid; 367 | border-color:#E0E0E0 transparent; 368 | /* reduce the damage in FF3.0 */ 369 | display:block; 370 | width:0; 371 | } 372 | 373 | .triangle-right.top:after { 374 | top:-40px; /* value = - border-top-width - border-bottom-width */ 375 | right:50px; /* controls horizontal position */ 376 | bottom:auto; 377 | left:auto; 378 | border-width:40px 40px 0 0; /* vary these values to change the angle of the vertex */ 379 | border-color:transparent #E0E0E0; 380 | } 381 | 382 | /*Default noVNC logo.*/ 383 | /* From: http://fonts.googleapis.com/css?family=Orbitron:700 */ 384 | @font-face { 385 | font-family: 'Orbitron'; 386 | font-style: normal; 387 | font-weight: 700; 388 | src: local('?'), url('Orbitron700.woff') format('woff'), 389 | url('Orbitron700.ttf') format('truetype'); 390 | } 391 | 392 | #noVNC_logo { 393 | margin-top: 170px; 394 | margin-left: 10px; 395 | color:yellow; 396 | text-align:left; 397 | font-family: 'Orbitron', 'OrbitronTTF', sans-serif; 398 | line-height:90%; 399 | text-shadow: 400 | 5px 5px 0 #000, 401 | -1px -1px 0 #000, 402 | 1px -1px 0 #000, 403 | -1px 1px 0 #000, 404 | 1px 1px 0 #000; 405 | } 406 | 407 | 408 | #noVNC_logo span{ 409 | color:green; 410 | } 411 | 412 | /* ---------------------------------------- 413 | * Media sizing 414 | * ---------------------------------------- 415 | */ 416 | 417 | 418 | .noVNC_status_button { 419 | font-size: 12px; 420 | } 421 | 422 | #noVNC_clipboard_text { 423 | width: 500px; 424 | } 425 | 426 | #noVNC_logo { 427 | font-size: 180px; 428 | } 429 | 430 | .noVNC_buttons_left { 431 | padding-left: 10px; 432 | } 433 | 434 | .noVNC_buttons_right { 435 | padding-right: 10px; 436 | } 437 | 438 | #noVNC_status { 439 | z-index: 0; 440 | position: absolute; 441 | width: 100%; 442 | margin-left: 0px; 443 | } 444 | 445 | #noVNC_toggleExtraKeys_button { display: none; } 446 | #noVNC_toggleCtrl_button { display: inline; } 447 | #noVNC_toggleAlt_button { display: inline; } 448 | #noVNC_sendTab_button { display: inline; } 449 | #noVNC_sendEsc_button { display: inline; } 450 | 451 | /* left-align the status text on lower resolutions */ 452 | @media screen and (max-width: 800px){ 453 | #noVNC_status { 454 | z-index: 1; 455 | position: relative; 456 | width: auto; 457 | float: left; 458 | margin-left: 4px; 459 | } 460 | } 461 | 462 | @media screen and (max-width: 640px){ 463 | #noVNC_clipboard_text { 464 | width: 410px; 465 | } 466 | #noVNC_logo { 467 | font-size: 150px; 468 | } 469 | .noVNC_status_button { 470 | font-size: 10px; 471 | } 472 | .noVNC_buttons_left { 473 | padding-left: 0px; 474 | } 475 | .noVNC_buttons_right { 476 | padding-right: 0px; 477 | } 478 | /* collapse the extra keys on lower resolutions */ 479 | #noVNC_toggleExtraKeys_button { 480 | display: inline; 481 | } 482 | #noVNC_toggleCtrl_button { 483 | display: none; 484 | position: absolute; 485 | top: 30px; 486 | left: 0px; 487 | } 488 | #noVNC_toggleAlt_button { 489 | display: none; 490 | position: absolute; 491 | top: 65px; 492 | left: 0px; 493 | } 494 | #noVNC_sendTab_button { 495 | display: none; 496 | position: absolute; 497 | top: 100px; 498 | left: 0px; 499 | } 500 | #noVNC_sendEsc_button { 501 | display: none; 502 | position: absolute; 503 | top: 135px; 504 | left: 0px; 505 | } 506 | } 507 | 508 | @media screen and (min-width: 321px) and (max-width: 480px) { 509 | #noVNC_clipboard_text { 510 | width: 250px; 511 | } 512 | #noVNC_logo { 513 | font-size: 110px; 514 | } 515 | } 516 | 517 | @media screen and (max-width: 320px) { 518 | .noVNC_status_button { 519 | font-size: 9px; 520 | } 521 | #noVNC_clipboard_text { 522 | width: 220px; 523 | } 524 | #noVNC_logo { 525 | font-size: 90px; 526 | } 527 | } 528 | -------------------------------------------------------------------------------- /front/include/input.js: -------------------------------------------------------------------------------- 1 | /* 2 | * noVNC: HTML5 VNC client 3 | * Copyright (C) 2012 Joel Martin 4 | * Copyright (C) 2013 Samuel Mannehed for Cendio AB 5 | * Licensed under MPL 2.0 or any later version (see LICENSE.txt) 6 | */ 7 | 8 | /*jslint browser: true, white: false */ 9 | /*global window, Util */ 10 | 11 | var Keyboard, Mouse; 12 | 13 | (function () { 14 | "use strict"; 15 | 16 | // 17 | // Keyboard event handler 18 | // 19 | 20 | Keyboard = function (defaults) { 21 | this._keyDownList = []; // List of depressed keys 22 | // (even if they are happy) 23 | 24 | Util.set_defaults(this, defaults, { 25 | 'target': document, 26 | 'focused': true 27 | }); 28 | 29 | // create the keyboard handler 30 | this._handler = new KeyEventDecoder(kbdUtil.ModifierSync(), 31 | VerifyCharModifier( /* jshint newcap: false */ 32 | TrackKeyState( 33 | EscapeModifiers(this._handleRfbEvent.bind(this)) 34 | ) 35 | ) 36 | ); /* jshint newcap: true */ 37 | 38 | // keep these here so we can refer to them later 39 | this._eventHandlers = { 40 | 'keyup': this._handleKeyUp.bind(this), 41 | 'keydown': this._handleKeyDown.bind(this), 42 | 'keypress': this._handleKeyPress.bind(this), 43 | 'blur': this._allKeysUp.bind(this) 44 | }; 45 | }; 46 | 47 | Keyboard.prototype = { 48 | // private methods 49 | 50 | _handleRfbEvent: function (e) { 51 | if (this._onKeyPress) { 52 | Util.Debug("onKeyPress " + (e.type == 'keydown' ? "down" : "up") + 53 | ", keysym: " + e.keysym.keysym + "(" + e.keysym.keyname + ")"); 54 | this._onKeyPress(e.keysym.keysym, e.type == 'keydown'); 55 | } 56 | }, 57 | 58 | _handleKeyDown: function (e) { 59 | if (!this._focused) { return true; } 60 | 61 | if (this._handler.keydown(e)) { 62 | // Suppress bubbling/default actions 63 | Util.stopEvent(e); 64 | return false; 65 | } else { 66 | // Allow the event to bubble and become a keyPress event which 67 | // will have the character code translated 68 | return true; 69 | } 70 | }, 71 | 72 | _handleKeyPress: function (e) { 73 | if (!this._focused) { return true; } 74 | 75 | if (this._handler.keypress(e)) { 76 | // Suppress bubbling/default actions 77 | Util.stopEvent(e); 78 | return false; 79 | } else { 80 | // Allow the event to bubble and become a keyPress event which 81 | // will have the character code translated 82 | return true; 83 | } 84 | }, 85 | 86 | _handleKeyUp: function (e) { 87 | if (!this._focused) { return true; } 88 | 89 | if (this._handler.keyup(e)) { 90 | // Suppress bubbling/default actions 91 | Util.stopEvent(e); 92 | return false; 93 | } else { 94 | // Allow the event to bubble and become a keyPress event which 95 | // will have the character code translated 96 | return true; 97 | } 98 | }, 99 | 100 | _allKeysUp: function () { 101 | Util.Debug(">> Keyboard.allKeysUp"); 102 | this._handler.releaseAll(); 103 | Util.Debug("<< Keyboard.allKeysUp"); 104 | }, 105 | 106 | // Public methods 107 | 108 | grab: function () { 109 | //Util.Debug(">> Keyboard.grab"); 110 | var c = this._target; 111 | 112 | Util.addEvent(c, 'keydown', this._eventHandlers.keydown); 113 | Util.addEvent(c, 'keyup', this._eventHandlers.keyup); 114 | Util.addEvent(c, 'keypress', this._eventHandlers.keypress); 115 | 116 | // Release (key up) if window loses focus 117 | Util.addEvent(window, 'blur', this._eventHandlers.blur); 118 | 119 | //Util.Debug("<< Keyboard.grab"); 120 | }, 121 | 122 | ungrab: function () { 123 | //Util.Debug(">> Keyboard.ungrab"); 124 | var c = this._target; 125 | 126 | Util.removeEvent(c, 'keydown', this._eventHandlers.keydown); 127 | Util.removeEvent(c, 'keyup', this._eventHandlers.keyup); 128 | Util.removeEvent(c, 'keypress', this._eventHandlers.keypress); 129 | Util.removeEvent(window, 'blur', this._eventHandlers.blur); 130 | 131 | // Release (key up) all keys that are in a down state 132 | this._allKeysUp(); 133 | 134 | //Util.Debug(">> Keyboard.ungrab"); 135 | }, 136 | 137 | sync: function (e) { 138 | this._handler.syncModifiers(e); 139 | } 140 | }; 141 | 142 | Util.make_properties(Keyboard, [ 143 | ['target', 'wo', 'dom'], // DOM element that captures keyboard input 144 | ['focused', 'rw', 'bool'], // Capture and send key events 145 | 146 | ['onKeyPress', 'rw', 'func'] // Handler for key press/release 147 | ]); 148 | 149 | // 150 | // Mouse event handler 151 | // 152 | 153 | Mouse = function (defaults) { 154 | this._mouseCaptured = false; 155 | 156 | this._doubleClickTimer = null; 157 | this._lastTouchPos = null; 158 | 159 | // Configuration attributes 160 | Util.set_defaults(this, defaults, { 161 | 'target': document, 162 | 'focused': true, 163 | 'scale': 1.0, 164 | 'touchButton': 1 165 | }); 166 | 167 | this._eventHandlers = { 168 | 'mousedown': this._handleMouseDown.bind(this), 169 | 'mouseup': this._handleMouseUp.bind(this), 170 | 'mousemove': this._handleMouseMove.bind(this), 171 | 'mousewheel': this._handleMouseWheel.bind(this), 172 | 'mousedisable': this._handleMouseDisable.bind(this) 173 | }; 174 | }; 175 | 176 | Mouse.prototype = { 177 | // private methods 178 | _captureMouse: function () { 179 | // capturing the mouse ensures we get the mouseup event 180 | if (this._target.setCapture) { 181 | this._target.setCapture(); 182 | } 183 | 184 | // some browsers give us mouseup events regardless, 185 | // so if we never captured the mouse, we can disregard the event 186 | this._mouseCaptured = true; 187 | }, 188 | 189 | _releaseMouse: function () { 190 | if (this._target.releaseCapture) { 191 | this._target.releaseCapture(); 192 | } 193 | this._mouseCaptured = false; 194 | }, 195 | 196 | _resetDoubleClickTimer: function () { 197 | this._doubleClickTimer = null; 198 | }, 199 | 200 | _handleMouseButton: function (e, down) { 201 | if (!this._focused) { return true; } 202 | 203 | if (this._notify) { 204 | this._notify(e); 205 | } 206 | 207 | var evt = (e ? e : window.event); 208 | var pos = Util.getEventPosition(e, this._target, this._scale); 209 | 210 | var bmask; 211 | if (e.touches || e.changedTouches) { 212 | // Touch device 213 | 214 | // When two touches occur within 500 ms of each other and are 215 | // close enough together a double click is triggered. 216 | if (down == 1) { 217 | if (this._doubleClickTimer === null) { 218 | this._lastTouchPos = pos; 219 | } else { 220 | clearTimeout(this._doubleClickTimer); 221 | 222 | // When the distance between the two touches is small enough 223 | // force the position of the latter touch to the position of 224 | // the first. 225 | 226 | var xs = this._lastTouchPos.x - pos.x; 227 | var ys = this._lastTouchPos.y - pos.y; 228 | var d = Math.sqrt((xs * xs) + (ys * ys)); 229 | 230 | // The goal is to trigger on a certain physical width, the 231 | // devicePixelRatio brings us a bit closer but is not optimal. 232 | var threshold = 20 * (window.devicePixelRatio || 1); 233 | if (d < threshold) { 234 | pos = this._lastTouchPos; 235 | } 236 | } 237 | this._doubleClickTimer = setTimeout(this._resetDoubleClickTimer.bind(this), 500); 238 | } 239 | bmask = this._touchButton; 240 | // If bmask is set 241 | } else if (evt.which) { 242 | /* everything except IE */ 243 | bmask = 1 << evt.button; 244 | } else { 245 | /* IE including 9 */ 246 | bmask = (evt.button & 0x1) + // Left 247 | (evt.button & 0x2) * 2 + // Right 248 | (evt.button & 0x4) / 2; // Middle 249 | } 250 | 251 | if (this._onMouseButton) { 252 | Util.Debug("onMouseButton " + (down ? "down" : "up") + 253 | ", x: " + pos.x + ", y: " + pos.y + ", bmask: " + bmask); 254 | this._onMouseButton(pos.x, pos.y, down, bmask); 255 | } 256 | Util.stopEvent(e); 257 | return false; 258 | }, 259 | 260 | _handleMouseDown: function (e) { 261 | this._captureMouse(); 262 | this._handleMouseButton(e, 1); 263 | }, 264 | 265 | _handleMouseUp: function (e) { 266 | if (!this._mouseCaptured) { return; } 267 | 268 | this._handleMouseButton(e, 0); 269 | this._releaseMouse(); 270 | }, 271 | 272 | _handleMouseWheel: function (e) { 273 | if (!this._focused) { return true; } 274 | 275 | if (this._notify) { 276 | this._notify(e); 277 | } 278 | 279 | var evt = (e ? e : window.event); 280 | var pos = Util.getEventPosition(e, this._target, this._scale); 281 | var wheelData = evt.detail ? evt.detail * -1 : evt.wheelDelta / 40; 282 | var bmask; 283 | if (wheelData > 0) { 284 | bmask = 1 << 3; 285 | } else { 286 | bmask = 1 << 4; 287 | } 288 | 289 | if (this._onMouseButton) { 290 | this._onMouseButton(pos.x, pos.y, 1, bmask); 291 | this._onMouseButton(pos.x, pos.y, 0, bmask); 292 | } 293 | Util.stopEvent(e); 294 | return false; 295 | }, 296 | 297 | _handleMouseMove: function (e) { 298 | if (! this._focused) { return true; } 299 | 300 | if (this._notify) { 301 | this._notify(e); 302 | } 303 | 304 | var evt = (e ? e : window.event); 305 | var pos = Util.getEventPosition(e, this._target, this._scale); 306 | if (this._onMouseMove) { 307 | this._onMouseMove(pos.x, pos.y); 308 | } 309 | Util.stopEvent(e); 310 | return false; 311 | }, 312 | 313 | _handleMouseDisable: function (e) { 314 | if (!this._focused) { return true; } 315 | 316 | var evt = (e ? e : window.event); 317 | var pos = Util.getEventPosition(e, this._target, this._scale); 318 | 319 | /* Stop propagation if inside canvas area */ 320 | if ((pos.realx >= 0) && (pos.realy >= 0) && 321 | (pos.realx < this._target.offsetWidth) && 322 | (pos.realy < this._target.offsetHeight)) { 323 | //Util.Debug("mouse event disabled"); 324 | Util.stopEvent(e); 325 | return false; 326 | } 327 | 328 | return true; 329 | }, 330 | 331 | 332 | // Public methods 333 | grab: function () { 334 | var c = this._target; 335 | 336 | if ('ontouchstart' in document.documentElement) { 337 | Util.addEvent(c, 'touchstart', this._eventHandlers.mousedown); 338 | Util.addEvent(window, 'touchend', this._eventHandlers.mouseup); 339 | Util.addEvent(c, 'touchend', this._eventHandlers.mouseup); 340 | Util.addEvent(c, 'touchmove', this._eventHandlers.mousemove); 341 | } else { 342 | Util.addEvent(c, 'mousedown', this._eventHandlers.mousedown); 343 | Util.addEvent(window, 'mouseup', this._eventHandlers.mouseup); 344 | Util.addEvent(c, 'mouseup', this._eventHandlers.mouseup); 345 | Util.addEvent(c, 'mousemove', this._eventHandlers.mousemove); 346 | Util.addEvent(c, (Util.Engine.gecko) ? 'DOMMouseScroll' : 'mousewheel', 347 | this._eventHandlers.mousewheel); 348 | } 349 | 350 | /* Work around right and middle click browser behaviors */ 351 | Util.addEvent(document, 'click', this._eventHandlers.mousedisable); 352 | Util.addEvent(document.body, 'contextmenu', this._eventHandlers.mousedisable); 353 | }, 354 | 355 | ungrab: function () { 356 | var c = this._target; 357 | 358 | if ('ontouchstart' in document.documentElement) { 359 | Util.removeEvent(c, 'touchstart', this._eventHandlers.mousedown); 360 | Util.removeEvent(window, 'touchend', this._eventHandlers.mouseup); 361 | Util.removeEvent(c, 'touchend', this._eventHandlers.mouseup); 362 | Util.removeEvent(c, 'touchmove', this._eventHandlers.mousemove); 363 | } else { 364 | Util.removeEvent(c, 'mousedown', this._eventHandlers.mousedown); 365 | Util.removeEvent(window, 'mouseup', this._eventHandlers.mouseup); 366 | Util.removeEvent(c, 'mouseup', this._eventHandlers.mouseup); 367 | Util.removeEvent(c, 'mousemove', this._eventHandlers.mousemove); 368 | Util.removeEvent(c, (Util.Engine.gecko) ? 'DOMMouseScroll' : 'mousewheel', 369 | this._eventHandlers.mousewheel); 370 | } 371 | 372 | /* Work around right and middle click browser behaviors */ 373 | Util.removeEvent(document, 'click', this._eventHandlers.mousedisable); 374 | Util.removeEvent(document.body, 'contextmenu', this._eventHandlers.mousedisable); 375 | 376 | } 377 | }; 378 | 379 | Util.make_properties(Mouse, [ 380 | ['target', 'ro', 'dom'], // DOM element that captures mouse input 381 | ['notify', 'ro', 'func'], // Function to call to notify whenever a mouse event is received 382 | ['focused', 'rw', 'bool'], // Capture and send mouse clicks/movement 383 | ['scale', 'rw', 'float'], // Viewport scale factor 0.0 - 1.0 384 | 385 | ['onMouseButton', 'rw', 'func'], // Handler for mouse button click/release 386 | ['onMouseMove', 'rw', 'func'], // Handler for mouse movement 387 | ['touchButton', 'rw', 'int'] // Button mask (1, 2, 4) for touch devices (0 means ignore clicks) 388 | ]); 389 | })(); 390 | -------------------------------------------------------------------------------- /front/include/websock.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Websock: high-performance binary WebSockets 3 | * Copyright (C) 2012 Joel Martin 4 | * Licensed under MPL 2.0 (see LICENSE.txt) 5 | * 6 | * Websock is similar to the standard WebSocket object but Websock 7 | * enables communication with raw TCP sockets (i.e. the binary stream) 8 | * via websockify. This is accomplished by base64 encoding the data 9 | * stream between Websock and websockify. 10 | * 11 | * Websock has built-in receive queue buffering; the message event 12 | * does not contain actual data but is simply a notification that 13 | * there is new data available. Several rQ* methods are available to 14 | * read binary data off of the receive queue. 15 | */ 16 | 17 | /*jslint browser: true, bitwise: true */ 18 | /*global Util*/ 19 | 20 | 21 | // Load Flash WebSocket emulator if needed 22 | 23 | // To force WebSocket emulator even when native WebSocket available 24 | //window.WEB_SOCKET_FORCE_FLASH = true; 25 | // To enable WebSocket emulator debug: 26 | //window.WEB_SOCKET_DEBUG=1; 27 | 28 | if (window.WebSocket && !window.WEB_SOCKET_FORCE_FLASH) { 29 | Websock_native = true; 30 | } else if (window.MozWebSocket && !window.WEB_SOCKET_FORCE_FLASH) { 31 | Websock_native = true; 32 | window.WebSocket = window.MozWebSocket; 33 | } else { 34 | /* no builtin WebSocket so load web_socket.js */ 35 | 36 | Websock_native = false; 37 | } 38 | 39 | function Websock() { 40 | "use strict"; 41 | 42 | this._websocket = null; // WebSocket object 43 | 44 | this._rQi = 0; // Receive queue index 45 | this._rQlen = 0; // Next write position in the receive queue 46 | this._rQbufferSize = 1024 * 1024 * 4; // Receive queue buffer size (4 MiB) 47 | this._rQmax = this._rQbufferSize / 8; 48 | // called in init: this._rQ = new Uint8Array(this._rQbufferSize); 49 | this._rQ = null; // Receive queue 50 | 51 | this._sQbufferSize = 1024 * 10; // 10 KiB 52 | // called in init: this._sQ = new Uint8Array(this._sQbufferSize); 53 | this._sQlen = 0; 54 | this._sQ = null; // Send queue 55 | 56 | this._mode = 'binary'; // Current WebSocket mode: 'binary', 'base64' 57 | this.maxBufferedAmount = 200; 58 | 59 | this._eventHandlers = { 60 | 'message': function () {}, 61 | 'open': function () {}, 62 | 'close': function () {}, 63 | 'error': function () {} 64 | }; 65 | } 66 | 67 | (function () { 68 | "use strict"; 69 | // this has performance issues in some versions Chromium, and 70 | // doesn't gain a tremendous amount of performance increase in Firefox 71 | // at the moment. It may be valuable to turn it on in the future. 72 | var ENABLE_COPYWITHIN = false; 73 | 74 | var MAX_RQ_GROW_SIZE = 40 * 1024 * 1024; // 40 MiB 75 | 76 | /*var typedArrayToString = (function () { 77 | // This is only for PhantomJS, which doesn't like apply-ing 78 | // with Typed Arrays 79 | try { 80 | var arr = new Uint8Array([1, 2, 3]); 81 | String.fromCharCode.apply(null, arr); 82 | return function (a) { return String.fromCharCode.apply(null, a); }; 83 | } catch (ex) { 84 | return function (a) { 85 | return String.fromCharCode.apply( 86 | null, Array.prototype.slice.call(a)); 87 | }; 88 | } 89 | })();*/ 90 | // http://www.onicos.com/staff/iz/amuse/javascript/expert/utf.txt 91 | 92 | function Utf8ArrayToString(array) { 93 | var out, i, len, c; 94 | var char2, char3, char4; 95 | var fromCode = String.fromCodePoint ? String.fromCodePoint : String.fromCharCode; 96 | 97 | out = ""; 98 | len = array.length; 99 | i = 0; 100 | while (i < len) { 101 | c = array[i++]; 102 | switch (c >> 4) { 103 | case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: 104 | // 0xxxxxxx 105 | out += fromCode(c); 106 | break; 107 | case 12: case 13: 108 | // 110x xxxx 10xx xxxx 109 | char2 = array[i++]; 110 | out += fromCode((( c & 0x1F) << 6) | (char2 & 0x3F)); 111 | break; 112 | case 14: 113 | // 1110 xxxx 10xx xxxx 10xx xxxx 114 | char2 = array[i++]; 115 | char3 = array[i++]; 116 | out += fromCode((( c & 0x0F) << 12) | 117 | ((char2 & 0x3F) << 6) | 118 | ((char3 & 0x3F) << 0)); 119 | break; 120 | case 15: /// ???? 121 | // 1111 0xxx 10xx xxxx 10xx xxxx 10xx xxxx 122 | char2 = array[i++]; 123 | char3 = array[i++]; 124 | char4 = array[i++]; 125 | out += fromCode((( c & 0x07) << 18) | 126 | ((char2 & 0x3F) << 12) | 127 | ((char3 & 0x3F) << 6) | 128 | ((char4 & 0x3F) << 0)); 129 | break; 130 | } 131 | } 132 | 133 | return out; 134 | } 135 | var typedArrayToString = Utf8ArrayToString; 136 | 137 | Websock.prototype = { 138 | // Getters and Setters 139 | get_sQ: function () { 140 | return this._sQ; 141 | }, 142 | 143 | get_rQ: function () { 144 | return this._rQ; 145 | }, 146 | 147 | get_rQi: function () { 148 | return this._rQi; 149 | }, 150 | 151 | set_rQi: function (val) { 152 | this._rQi = val; 153 | }, 154 | 155 | // Receive Queue 156 | rQlen: function () { 157 | return this._rQlen - this._rQi; 158 | }, 159 | 160 | rQpeek8: function () { 161 | return this._rQ[this._rQi]; 162 | }, 163 | 164 | rQshift8: function () { 165 | return this._rQ[this._rQi++]; 166 | }, 167 | 168 | rQskip8: function () { 169 | this._rQi++; 170 | }, 171 | 172 | rQskipBytes: function (num) { 173 | this._rQi += num; 174 | }, 175 | 176 | // TODO(directxman12): test performance with these vs a DataView 177 | rQshift16: function () { 178 | return (this._rQ[this._rQi++] << 8) + 179 | this._rQ[this._rQi++]; 180 | }, 181 | 182 | rQshift32: function () { 183 | return (this._rQ[this._rQi++] << 24) + 184 | (this._rQ[this._rQi++] << 16) + 185 | (this._rQ[this._rQi++] << 8) + 186 | this._rQ[this._rQi++]; 187 | }, 188 | 189 | rQshiftStr: function (len) { 190 | if (typeof(len) === 'undefined') { len = this.rQlen(); } 191 | var arr = new Uint8Array(this._rQ.buffer, this._rQi, len); 192 | this._rQi += len; 193 | return typedArrayToString(arr); 194 | }, 195 | 196 | rQshiftBytes: function (len) { 197 | if (typeof(len) === 'undefined') { len = this.rQlen(); } 198 | this._rQi += len; 199 | return new Uint8Array(this._rQ.buffer, this._rQi - len, len); 200 | }, 201 | 202 | rQshiftTo: function (target, len) { 203 | if (len === undefined) { len = this.rQlen(); } 204 | // TODO: make this just use set with views when using a ArrayBuffer to store the rQ 205 | target.set(new Uint8Array(this._rQ.buffer, this._rQi, len)); 206 | this._rQi += len; 207 | }, 208 | 209 | rQwhole: function () { 210 | return new Uint8Array(this._rQ.buffer, 0, this._rQlen); 211 | }, 212 | 213 | rQslice: function (start, end) { 214 | if (end) { 215 | return new Uint8Array(this._rQ.buffer, this._rQi + start, end - start); 216 | } else { 217 | return new Uint8Array(this._rQ.buffer, this._rQi + start, this._rQlen - this._rQi - start); 218 | } 219 | }, 220 | 221 | // Check to see if we must wait for 'num' bytes (default to FBU.bytes) 222 | // to be available in the receive queue. Return true if we need to 223 | // wait (and possibly print a debug message), otherwise false. 224 | rQwait: function (msg, num, goback) { 225 | var rQlen = this._rQlen - this._rQi; // Skip rQlen() function call 226 | if (rQlen < num) { 227 | if (goback) { 228 | if (this._rQi < goback) { 229 | throw new Error("rQwait cannot backup " + goback + " bytes"); 230 | } 231 | this._rQi -= goback; 232 | } 233 | return true; // true means need more data 234 | } 235 | return false; 236 | }, 237 | 238 | // Send Queue 239 | 240 | flush: function () { 241 | if (this._websocket.bufferedAmount !== 0) { 242 | Util.Debug("bufferedAmount: " + this._websocket.bufferedAmount); 243 | } 244 | 245 | if (this._websocket.bufferedAmount < this.maxBufferedAmount) { 246 | if (this._sQlen > 0 && this._websocket.readyState === WebSocket.OPEN) { 247 | this._websocket.send(this._encode_message()); 248 | this._sQlen = 0; 249 | } 250 | 251 | return true; 252 | } else { 253 | Util.Info("Delaying send, bufferedAmount: " + 254 | this._websocket.bufferedAmount); 255 | return false; 256 | } 257 | }, 258 | 259 | send: function (arr) { 260 | this._sQ.set(arr, this._sQlen); 261 | this._sQlen += arr.length; 262 | return this.flush(); 263 | }, 264 | 265 | send_string: function (str) { 266 | this.send(str.split('').map(function (chr) { 267 | return chr.charCodeAt(0); 268 | })); 269 | }, 270 | 271 | // Event Handlers 272 | off: function (evt) { 273 | this._eventHandlers[evt] = function () {}; 274 | }, 275 | 276 | on: function (evt, handler) { 277 | this._eventHandlers[evt] = handler; 278 | }, 279 | 280 | _allocate_buffers: function () { 281 | this._rQ = new Uint8Array(this._rQbufferSize); 282 | this._sQ = new Uint8Array(this._sQbufferSize); 283 | }, 284 | 285 | init: function (protocols, ws_schema) { 286 | this._allocate_buffers(); 287 | this._rQi = 0; 288 | this._websocket = null; 289 | 290 | // Check for full typed array support 291 | var bt = false; 292 | if (('Uint8Array' in window) && 293 | ('set' in Uint8Array.prototype)) { 294 | bt = true; 295 | } 296 | 297 | // Check for full binary type support in WebSockets 298 | // Inspired by: 299 | // https://github.com/Modernizr/Modernizr/issues/370 300 | // https://github.com/Modernizr/Modernizr/blob/master/feature-detects/websockets/binary.js 301 | // !!! hardcode in binary support mode 302 | ///var wsbt = false; 303 | var wsbt = true; 304 | /*try { 305 | if (bt && ('binaryType' in WebSocket.prototype || 306 | !!(new WebSocket(ws_schema + '://.').binaryType))) { 307 | Util.Info("Detected binaryType support in WebSockets"); 308 | wsbt = true; 309 | } 310 | } catch (exc) { 311 | // Just ignore failed test localhost connection 312 | }*/ 313 | 314 | // Default protocols if not specified 315 | if (typeof(protocols) === "undefined") { 316 | protocols = 'binary'; 317 | } 318 | 319 | if (Array.isArray(protocols) && protocols.indexOf('binary') > -1) { 320 | protocols = 'binary'; 321 | } 322 | 323 | if (!wsbt) { 324 | throw new Error("noVNC no longer supports base64 WebSockets. " + 325 | "Please use a browser which supports binary WebSockets."); 326 | } 327 | 328 | if (protocols != 'binary') { 329 | throw new Error("noVNC no longer supports base64 WebSockets. Please " + 330 | "use the binary subprotocol instead."); 331 | } 332 | 333 | return protocols; 334 | }, 335 | 336 | open: function (uri, protocols) { 337 | var ws_schema = uri.match(/^([a-z]+):\/\//)[1]; 338 | protocols = this.init(protocols, ws_schema); 339 | 340 | this._websocket = new WebSocket(uri, protocols); 341 | 342 | if (protocols.indexOf('binary') >= 0) { 343 | this._websocket.binaryType = 'arraybuffer'; 344 | } 345 | 346 | this._websocket.onmessage = this._recv_message.bind(this); 347 | this._websocket.onopen = (function () { 348 | Util.Debug('>> WebSock.onopen'); 349 | if (this._websocket.protocol) { 350 | this._mode = this._websocket.protocol; 351 | Util.Info("Server choose sub-protocol: " + this._websocket.protocol); 352 | } else { 353 | this._mode = 'binary'; 354 | Util.Error('Server select no sub-protocol!: ' + this._websocket.protocol); 355 | } 356 | 357 | if (this._mode != 'binary') { 358 | throw new Error("noVNC no longer supports base64 WebSockets. Please " + 359 | "use the binary subprotocol instead."); 360 | 361 | } 362 | 363 | this._eventHandlers.open(); 364 | Util.Debug("<< WebSock.onopen"); 365 | }).bind(this); 366 | this._websocket.onclose = (function (e) { 367 | Util.Debug(">> WebSock.onclose"); 368 | this._eventHandlers.close(e); 369 | Util.Debug("<< WebSock.onclose"); 370 | }).bind(this); 371 | this._websocket.onerror = (function (e) { 372 | Util.Debug(">> WebSock.onerror: " + e); 373 | this._eventHandlers.error(e); 374 | Util.Debug("<< WebSock.onerror: " + e); 375 | }).bind(this); 376 | }, 377 | 378 | close: function () { 379 | if (this._websocket) { 380 | if ((this._websocket.readyState === WebSocket.OPEN) || 381 | (this._websocket.readyState === WebSocket.CONNECTING)) { 382 | Util.Info("Closing WebSocket connection"); 383 | this._websocket.close(); 384 | } 385 | 386 | this._websocket.onmessage = function (e) { return; }; 387 | } 388 | }, 389 | 390 | // private methods 391 | _encode_message: function () { 392 | // Put in a binary arraybuffer 393 | // according to the spec, you can send ArrayBufferViews with the send method 394 | return new Uint8Array(this._sQ.buffer, 0, this._sQlen); 395 | }, 396 | 397 | _expand_compact_rQ: function (min_fit) { 398 | var resizeNeeded = min_fit || this._rQlen - this._rQi > this._rQbufferSize / 2; 399 | if (resizeNeeded) { 400 | if (!min_fit) { 401 | // just double the size if we need to do compaction 402 | this._rQbufferSize *= 2; 403 | } else { 404 | // otherwise, make sure we satisy rQlen - rQi + min_fit < rQbufferSize / 8 405 | this._rQbufferSize = (this._rQlen - this._rQi + min_fit) * 8; 406 | } 407 | } 408 | 409 | // we don't want to grow unboundedly 410 | if (this._rQbufferSize > MAX_RQ_GROW_SIZE) { 411 | this._rQbufferSize = MAX_RQ_GROW_SIZE; 412 | if (this._rQbufferSize - this._rQlen - this._rQi < min_fit) { 413 | throw new Exception("Receive Queue buffer exceeded " + MAX_RQ_GROW_SIZE + " bytes, and the new message could not fit"); 414 | } 415 | } 416 | 417 | if (resizeNeeded) { 418 | var old_rQbuffer = this._rQ.buffer; 419 | this._rQmax = this._rQbufferSize / 8; 420 | this._rQ = new Uint8Array(this._rQbufferSize); 421 | this._rQ.set(new Uint8Array(old_rQbuffer, this._rQi)); 422 | } else { 423 | if (ENABLE_COPYWITHIN) { 424 | this._rQ.copyWithin(0, this._rQi); 425 | } else { 426 | this._rQ.set(new Uint8Array(this._rQ.buffer, this._rQi)); 427 | } 428 | } 429 | 430 | this._rQlen = this._rQlen - this._rQi; 431 | this._rQi = 0; 432 | }, 433 | 434 | _decode_message: function (data) { 435 | // push arraybuffer values onto the end 436 | var u8 = new Uint8Array(data); 437 | if (u8.length > this._rQbufferSize - this._rQlen) { 438 | this._expand_compact_rQ(u8.length); 439 | } 440 | this._rQ.set(u8, this._rQlen); 441 | this._rQlen += u8.length; 442 | }, 443 | 444 | _recv_message: function (e) { 445 | try { 446 | this._decode_message(e.data); 447 | if (this.rQlen() > 0) { 448 | this._eventHandlers.message(); 449 | // Compact the receive queue 450 | if (this._rQlen == this._rQi) { 451 | this._rQlen = 0; 452 | this._rQi = 0; 453 | } else if (this._rQlen > this._rQmax) { 454 | this._expand_compact_rQ(); 455 | } 456 | } else { 457 | Util.Debug("Ignoring empty message"); 458 | } 459 | } catch (exc) { 460 | var exception_str = ""; 461 | if (exc.name) { 462 | exception_str += "\n name: " + exc.name + "\n"; 463 | exception_str += " message: " + exc.message + "\n"; 464 | } 465 | 466 | if (typeof exc.description !== 'undefined') { 467 | exception_str += " description: " + exc.description + "\n"; 468 | } 469 | 470 | if (typeof exc.stack !== 'undefined') { 471 | exception_str += exc.stack; 472 | } 473 | 474 | if (exception_str.length > 0) { 475 | Util.Error("recv_message, caught exception: " + exception_str); 476 | } else { 477 | Util.Error("recv_message, caught exception: " + exc); 478 | } 479 | 480 | if (typeof exc.name !== 'undefined') { 481 | this._eventHandlers.error(exc.name + ": " + exc.message); 482 | } else { 483 | this._eventHandlers.error(exc); 484 | } 485 | } 486 | } 487 | }; 488 | })(); 489 | -------------------------------------------------------------------------------- /front/include/keysymdef.js: -------------------------------------------------------------------------------- 1 | // This file describes mappings from Unicode codepoints to the keysym values 2 | // (and optionally, key names) expected by the RFB protocol 3 | // How this file was generated: 4 | // node /Users/jalf/dev/mi/novnc/utils/parse.js /opt/X11/include/X11/keysymdef.h 5 | var keysyms = (function(){ 6 | "use strict"; 7 | var keynames = null; 8 | var codepoints = {"32":32,"33":33,"34":34,"35":35,"36":36,"37":37,"38":38,"39":39,"40":40,"41":41,"42":42,"43":43,"44":44,"45":45,"46":46,"47":47,"48":48,"49":49,"50":50,"51":51,"52":52,"53":53,"54":54,"55":55,"56":56,"57":57,"58":58,"59":59,"60":60,"61":61,"62":62,"63":63,"64":64,"65":65,"66":66,"67":67,"68":68,"69":69,"70":70,"71":71,"72":72,"73":73,"74":74,"75":75,"76":76,"77":77,"78":78,"79":79,"80":80,"81":81,"82":82,"83":83,"84":84,"85":85,"86":86,"87":87,"88":88,"89":89,"90":90,"91":91,"92":92,"93":93,"94":94,"95":95,"96":96,"97":97,"98":98,"99":99,"100":100,"101":101,"102":102,"103":103,"104":104,"105":105,"106":106,"107":107,"108":108,"109":109,"110":110,"111":111,"112":112,"113":113,"114":114,"115":115,"116":116,"117":117,"118":118,"119":119,"120":120,"121":121,"122":122,"123":123,"124":124,"125":125,"126":126,"160":160,"161":161,"162":162,"163":163,"164":164,"165":165,"166":166,"167":167,"168":168,"169":169,"170":170,"171":171,"172":172,"173":173,"174":174,"175":175,"176":176,"177":177,"178":178,"179":179,"180":180,"181":181,"182":182,"183":183,"184":184,"185":185,"186":186,"187":187,"188":188,"189":189,"190":190,"191":191,"192":192,"193":193,"194":194,"195":195,"196":196,"197":197,"198":198,"199":199,"200":200,"201":201,"202":202,"203":203,"204":204,"205":205,"206":206,"207":207,"208":208,"209":209,"210":210,"211":211,"212":212,"213":213,"214":214,"215":215,"216":216,"217":217,"218":218,"219":219,"220":220,"221":221,"222":222,"223":223,"224":224,"225":225,"226":226,"227":227,"228":228,"229":229,"230":230,"231":231,"232":232,"233":233,"234":234,"235":235,"236":236,"237":237,"238":238,"239":239,"240":240,"241":241,"242":242,"243":243,"244":244,"245":245,"246":246,"247":247,"248":248,"249":249,"250":250,"251":251,"252":252,"253":253,"254":254,"255":255,"256":960,"257":992,"258":451,"259":483,"260":417,"261":433,"262":454,"263":486,"264":710,"265":742,"266":709,"267":741,"268":456,"269":488,"270":463,"271":495,"272":464,"273":496,"274":938,"275":954,"278":972,"279":1004,"280":458,"281":490,"282":460,"283":492,"284":728,"285":760,"286":683,"287":699,"288":725,"289":757,"290":939,"291":955,"292":678,"293":694,"294":673,"295":689,"296":933,"297":949,"298":975,"299":1007,"300":16777516,"301":16777517,"302":967,"303":999,"304":681,"305":697,"308":684,"309":700,"310":979,"311":1011,"312":930,"313":453,"314":485,"315":934,"316":950,"317":421,"318":437,"321":419,"322":435,"323":465,"324":497,"325":977,"326":1009,"327":466,"328":498,"330":957,"331":959,"332":978,"333":1010,"336":469,"337":501,"338":5052,"339":5053,"340":448,"341":480,"342":931,"343":947,"344":472,"345":504,"346":422,"347":438,"348":734,"349":766,"350":426,"351":442,"352":425,"353":441,"354":478,"355":510,"356":427,"357":443,"358":940,"359":956,"360":989,"361":1021,"362":990,"363":1022,"364":733,"365":765,"366":473,"367":505,"368":475,"369":507,"370":985,"371":1017,"372":16777588,"373":16777589,"374":16777590,"375":16777591,"376":5054,"377":428,"378":444,"379":431,"380":447,"381":430,"382":446,"399":16777615,"402":2294,"415":16777631,"416":16777632,"417":16777633,"431":16777647,"432":16777648,"437":16777653,"438":16777654,"439":16777655,"466":16777681,"486":16777702,"487":16777703,"601":16777817,"629":16777845,"658":16777874,"711":439,"728":418,"729":511,"731":434,"733":445,"901":1966,"902":1953,"904":1954,"905":1955,"906":1956,"908":1959,"910":1960,"911":1963,"912":1974,"913":1985,"914":1986,"915":1987,"916":1988,"917":1989,"918":1990,"919":1991,"920":1992,"921":1993,"922":1994,"923":1995,"924":1996,"925":1997,"926":1998,"927":1999,"928":2000,"929":2001,"931":2002,"932":2004,"933":2005,"934":2006,"935":2007,"936":2008,"937":2009,"938":1957,"939":1961,"940":1969,"941":1970,"942":1971,"943":1972,"944":1978,"945":2017,"946":2018,"947":2019,"948":2020,"949":2021,"950":2022,"951":2023,"952":2024,"953":2025,"954":2026,"955":2027,"956":2028,"957":2029,"958":2030,"959":2031,"960":2032,"961":2033,"962":2035,"963":2034,"964":2036,"965":2037,"966":2038,"967":2039,"968":2040,"969":2041,"970":1973,"971":1977,"972":1975,"973":1976,"974":1979,"1025":1715,"1026":1713,"1027":1714,"1028":1716,"1029":1717,"1030":1718,"1031":1719,"1032":1720,"1033":1721,"1034":1722,"1035":1723,"1036":1724,"1038":1726,"1039":1727,"1040":1761,"1041":1762,"1042":1783,"1043":1767,"1044":1764,"1045":1765,"1046":1782,"1047":1786,"1048":1769,"1049":1770,"1050":1771,"1051":1772,"1052":1773,"1053":1774,"1054":1775,"1055":1776,"1056":1778,"1057":1779,"1058":1780,"1059":1781,"1060":1766,"1061":1768,"1062":1763,"1063":1790,"1064":1787,"1065":1789,"1066":1791,"1067":1785,"1068":1784,"1069":1788,"1070":1760,"1071":1777,"1072":1729,"1073":1730,"1074":1751,"1075":1735,"1076":1732,"1077":1733,"1078":1750,"1079":1754,"1080":1737,"1081":1738,"1082":1739,"1083":1740,"1084":1741,"1085":1742,"1086":1743,"1087":1744,"1088":1746,"1089":1747,"1090":1748,"1091":1749,"1092":1734,"1093":1736,"1094":1731,"1095":1758,"1096":1755,"1097":1757,"1098":1759,"1099":1753,"1100":1752,"1101":1756,"1102":1728,"1103":1745,"1105":1699,"1106":1697,"1107":1698,"1108":1700,"1109":1701,"1110":1702,"1111":1703,"1112":1704,"1113":1705,"1114":1706,"1115":1707,"1116":1708,"1118":1710,"1119":1711,"1168":1725,"1169":1709,"1170":16778386,"1171":16778387,"1174":16778390,"1175":16778391,"1178":16778394,"1179":16778395,"1180":16778396,"1181":16778397,"1186":16778402,"1187":16778403,"1198":16778414,"1199":16778415,"1200":16778416,"1201":16778417,"1202":16778418,"1203":16778419,"1206":16778422,"1207":16778423,"1208":16778424,"1209":16778425,"1210":16778426,"1211":16778427,"1240":16778456,"1241":16778457,"1250":16778466,"1251":16778467,"1256":16778472,"1257":16778473,"1262":16778478,"1263":16778479,"1329":16778545,"1330":16778546,"1331":16778547,"1332":16778548,"1333":16778549,"1334":16778550,"1335":16778551,"1336":16778552,"1337":16778553,"1338":16778554,"1339":16778555,"1340":16778556,"1341":16778557,"1342":16778558,"1343":16778559,"1344":16778560,"1345":16778561,"1346":16778562,"1347":16778563,"1348":16778564,"1349":16778565,"1350":16778566,"1351":16778567,"1352":16778568,"1353":16778569,"1354":16778570,"1355":16778571,"1356":16778572,"1357":16778573,"1358":16778574,"1359":16778575,"1360":16778576,"1361":16778577,"1362":16778578,"1363":16778579,"1364":16778580,"1365":16778581,"1366":16778582,"1370":16778586,"1371":16778587,"1372":16778588,"1373":16778589,"1374":16778590,"1377":16778593,"1378":16778594,"1379":16778595,"1380":16778596,"1381":16778597,"1382":16778598,"1383":16778599,"1384":16778600,"1385":16778601,"1386":16778602,"1387":16778603,"1388":16778604,"1389":16778605,"1390":16778606,"1391":16778607,"1392":16778608,"1393":16778609,"1394":16778610,"1395":16778611,"1396":16778612,"1397":16778613,"1398":16778614,"1399":16778615,"1400":16778616,"1401":16778617,"1402":16778618,"1403":16778619,"1404":16778620,"1405":16778621,"1406":16778622,"1407":16778623,"1408":16778624,"1409":16778625,"1410":16778626,"1411":16778627,"1412":16778628,"1413":16778629,"1414":16778630,"1415":16778631,"1417":16778633,"1418":16778634,"1488":3296,"1489":3297,"1490":3298,"1491":3299,"1492":3300,"1493":3301,"1494":3302,"1495":3303,"1496":3304,"1497":3305,"1498":3306,"1499":3307,"1500":3308,"1501":3309,"1502":3310,"1503":3311,"1504":3312,"1505":3313,"1506":3314,"1507":3315,"1508":3316,"1509":3317,"1510":3318,"1511":3319,"1512":3320,"1513":3321,"1514":3322,"1548":1452,"1563":1467,"1567":1471,"1569":1473,"1570":1474,"1571":1475,"1572":1476,"1573":1477,"1574":1478,"1575":1479,"1576":1480,"1577":1481,"1578":1482,"1579":1483,"1580":1484,"1581":1485,"1582":1486,"1583":1487,"1584":1488,"1585":1489,"1586":1490,"1587":1491,"1588":1492,"1589":1493,"1590":1494,"1591":1495,"1592":1496,"1593":1497,"1594":1498,"1600":1504,"1601":1505,"1602":1506,"1603":1507,"1604":1508,"1605":1509,"1606":1510,"1607":1511,"1608":1512,"1609":1513,"1610":1514,"1611":1515,"1612":1516,"1613":1517,"1614":1518,"1615":1519,"1616":1520,"1617":1521,"1618":1522,"1619":16778835,"1620":16778836,"1621":16778837,"1632":16778848,"1633":16778849,"1634":16778850,"1635":16778851,"1636":16778852,"1637":16778853,"1638":16778854,"1639":16778855,"1640":16778856,"1641":16778857,"1642":16778858,"1648":16778864,"1657":16778873,"1662":16778878,"1670":16778886,"1672":16778888,"1681":16778897,"1688":16778904,"1700":16778916,"1705":16778921,"1711":16778927,"1722":16778938,"1726":16778942,"1729":16778945,"1740":16778956,"1746":16778962,"1748":16778964,"1776":16778992,"1777":16778993,"1778":16778994,"1779":16778995,"1780":16778996,"1781":16778997,"1782":16778998,"1783":16778999,"1784":16779000,"1785":16779001,"3458":16780674,"3459":16780675,"3461":16780677,"3462":16780678,"3463":16780679,"3464":16780680,"3465":16780681,"3466":16780682,"3467":16780683,"3468":16780684,"3469":16780685,"3470":16780686,"3471":16780687,"3472":16780688,"3473":16780689,"3474":16780690,"3475":16780691,"3476":16780692,"3477":16780693,"3478":16780694,"3482":16780698,"3483":16780699,"3484":16780700,"3485":16780701,"3486":16780702,"3487":16780703,"3488":16780704,"3489":16780705,"3490":16780706,"3491":16780707,"3492":16780708,"3493":16780709,"3494":16780710,"3495":16780711,"3496":16780712,"3497":16780713,"3498":16780714,"3499":16780715,"3500":16780716,"3501":16780717,"3502":16780718,"3503":16780719,"3504":16780720,"3505":16780721,"3507":16780723,"3508":16780724,"3509":16780725,"3510":16780726,"3511":16780727,"3512":16780728,"3513":16780729,"3514":16780730,"3515":16780731,"3517":16780733,"3520":16780736,"3521":16780737,"3522":16780738,"3523":16780739,"3524":16780740,"3525":16780741,"3526":16780742,"3530":16780746,"3535":16780751,"3536":16780752,"3537":16780753,"3538":16780754,"3539":16780755,"3540":16780756,"3542":16780758,"3544":16780760,"3545":16780761,"3546":16780762,"3547":16780763,"3548":16780764,"3549":16780765,"3550":16780766,"3551":16780767,"3570":16780786,"3571":16780787,"3572":16780788,"3585":3489,"3586":3490,"3587":3491,"3588":3492,"3589":3493,"3590":3494,"3591":3495,"3592":3496,"3593":3497,"3594":3498,"3595":3499,"3596":3500,"3597":3501,"3598":3502,"3599":3503,"3600":3504,"3601":3505,"3602":3506,"3603":3507,"3604":3508,"3605":3509,"3606":3510,"3607":3511,"3608":3512,"3609":3513,"3610":3514,"3611":3515,"3612":3516,"3613":3517,"3614":3518,"3615":3519,"3616":3520,"3617":3521,"3618":3522,"3619":3523,"3620":3524,"3621":3525,"3622":3526,"3623":3527,"3624":3528,"3625":3529,"3626":3530,"3627":3531,"3628":3532,"3629":3533,"3630":3534,"3631":3535,"3632":3536,"3633":3537,"3634":3538,"3635":3539,"3636":3540,"3637":3541,"3638":3542,"3639":3543,"3640":3544,"3641":3545,"3642":3546,"3647":3551,"3648":3552,"3649":3553,"3650":3554,"3651":3555,"3652":3556,"3653":3557,"3654":3558,"3655":3559,"3656":3560,"3657":3561,"3658":3562,"3659":3563,"3660":3564,"3661":3565,"3664":3568,"3665":3569,"3666":3570,"3667":3571,"3668":3572,"3669":3573,"3670":3574,"3671":3575,"3672":3576,"3673":3577,"4304":16781520,"4305":16781521,"4306":16781522,"4307":16781523,"4308":16781524,"4309":16781525,"4310":16781526,"4311":16781527,"4312":16781528,"4313":16781529,"4314":16781530,"4315":16781531,"4316":16781532,"4317":16781533,"4318":16781534,"4319":16781535,"4320":16781536,"4321":16781537,"4322":16781538,"4323":16781539,"4324":16781540,"4325":16781541,"4326":16781542,"4327":16781543,"4328":16781544,"4329":16781545,"4330":16781546,"4331":16781547,"4332":16781548,"4333":16781549,"4334":16781550,"4335":16781551,"4336":16781552,"4337":16781553,"4338":16781554,"4339":16781555,"4340":16781556,"4341":16781557,"4342":16781558,"7682":16784898,"7683":16784899,"7690":16784906,"7691":16784907,"7710":16784926,"7711":16784927,"7734":16784950,"7735":16784951,"7744":16784960,"7745":16784961,"7766":16784982,"7767":16784983,"7776":16784992,"7777":16784993,"7786":16785002,"7787":16785003,"7808":16785024,"7809":16785025,"7810":16785026,"7811":16785027,"7812":16785028,"7813":16785029,"7818":16785034,"7819":16785035,"7840":16785056,"7841":16785057,"7842":16785058,"7843":16785059,"7844":16785060,"7845":16785061,"7846":16785062,"7847":16785063,"7848":16785064,"7849":16785065,"7850":16785066,"7851":16785067,"7852":16785068,"7853":16785069,"7854":16785070,"7855":16785071,"7856":16785072,"7857":16785073,"7858":16785074,"7859":16785075,"7860":16785076,"7861":16785077,"7862":16785078,"7863":16785079,"7864":16785080,"7865":16785081,"7866":16785082,"7867":16785083,"7868":16785084,"7869":16785085,"7870":16785086,"7871":16785087,"7872":16785088,"7873":16785089,"7874":16785090,"7875":16785091,"7876":16785092,"7877":16785093,"7878":16785094,"7879":16785095,"7880":16785096,"7881":16785097,"7882":16785098,"7883":16785099,"7884":16785100,"7885":16785101,"7886":16785102,"7887":16785103,"7888":16785104,"7889":16785105,"7890":16785106,"7891":16785107,"7892":16785108,"7893":16785109,"7894":16785110,"7895":16785111,"7896":16785112,"7897":16785113,"7898":16785114,"7899":16785115,"7900":16785116,"7901":16785117,"7902":16785118,"7903":16785119,"7904":16785120,"7905":16785121,"7906":16785122,"7907":16785123,"7908":16785124,"7909":16785125,"7910":16785126,"7911":16785127,"7912":16785128,"7913":16785129,"7914":16785130,"7915":16785131,"7916":16785132,"7917":16785133,"7918":16785134,"7919":16785135,"7920":16785136,"7921":16785137,"7922":16785138,"7923":16785139,"7924":16785140,"7925":16785141,"7926":16785142,"7927":16785143,"7928":16785144,"7929":16785145,"8194":2722,"8195":2721,"8196":2723,"8197":2724,"8199":2725,"8200":2726,"8201":2727,"8202":2728,"8210":2747,"8211":2730,"8212":2729,"8213":1967,"8215":3295,"8216":2768,"8217":2769,"8218":2813,"8220":2770,"8221":2771,"8222":2814,"8224":2801,"8225":2802,"8226":2790,"8229":2735,"8230":2734,"8240":2773,"8242":2774,"8243":2775,"8248":2812,"8254":1150,"8304":16785520,"8308":16785524,"8309":16785525,"8310":16785526,"8311":16785527,"8312":16785528,"8313":16785529,"8320":16785536,"8321":16785537,"8322":16785538,"8323":16785539,"8324":16785540,"8325":16785541,"8326":16785542,"8327":16785543,"8328":16785544,"8329":16785545,"8352":16785568,"8353":16785569,"8354":16785570,"8355":16785571,"8356":16785572,"8357":16785573,"8358":16785574,"8359":16785575,"8360":16785576,"8361":3839,"8362":16785578,"8363":16785579,"8364":8364,"8453":2744,"8470":1712,"8471":2811,"8478":2772,"8482":2761,"8531":2736,"8532":2737,"8533":2738,"8534":2739,"8535":2740,"8536":2741,"8537":2742,"8538":2743,"8539":2755,"8540":2756,"8541":2757,"8542":2758,"8592":2299,"8593":2300,"8594":2301,"8595":2302,"8658":2254,"8660":2253,"8706":2287,"8709":16785925,"8711":2245,"8712":16785928,"8713":16785929,"8715":16785931,"8728":3018,"8730":2262,"8731":16785947,"8732":16785948,"8733":2241,"8734":2242,"8743":2270,"8744":2271,"8745":2268,"8746":2269,"8747":2239,"8748":16785964,"8749":16785965,"8756":2240,"8757":16785973,"8764":2248,"8771":2249,"8773":16785992,"8775":16785991,"8800":2237,"8801":2255,"8802":16786018,"8803":16786019,"8804":2236,"8805":2238,"8834":2266,"8835":2267,"8866":3068,"8867":3036,"8868":3010,"8869":3022,"8968":3027,"8970":3012,"8981":2810,"8992":2212,"8993":2213,"9109":3020,"9115":2219,"9117":2220,"9118":2221,"9120":2222,"9121":2215,"9123":2216,"9124":2217,"9126":2218,"9128":2223,"9132":2224,"9143":2209,"9146":2543,"9147":2544,"9148":2546,"9149":2547,"9225":2530,"9226":2533,"9227":2537,"9228":2531,"9229":2532,"9251":2732,"9252":2536,"9472":2211,"9474":2214,"9484":2210,"9488":2539,"9492":2541,"9496":2538,"9500":2548,"9508":2549,"9516":2551,"9524":2550,"9532":2542,"9618":2529,"9642":2791,"9643":2785,"9644":2779,"9645":2786,"9646":2783,"9647":2767,"9650":2792,"9651":2787,"9654":2781,"9655":2765,"9660":2793,"9661":2788,"9664":2780,"9665":2764,"9670":2528,"9675":2766,"9679":2782,"9702":2784,"9734":2789,"9742":2809,"9747":2762,"9756":2794,"9758":2795,"9792":2808,"9794":2807,"9827":2796,"9829":2798,"9830":2797,"9837":2806,"9839":2805,"10003":2803,"10007":2804,"10013":2777,"10016":2800,"10216":2748,"10217":2750,"10240":16787456,"10241":16787457,"10242":16787458,"10243":16787459,"10244":16787460,"10245":16787461,"10246":16787462,"10247":16787463,"10248":16787464,"10249":16787465,"10250":16787466,"10251":16787467,"10252":16787468,"10253":16787469,"10254":16787470,"10255":16787471,"10256":16787472,"10257":16787473,"10258":16787474,"10259":16787475,"10260":16787476,"10261":16787477,"10262":16787478,"10263":16787479,"10264":16787480,"10265":16787481,"10266":16787482,"10267":16787483,"10268":16787484,"10269":16787485,"10270":16787486,"10271":16787487,"10272":16787488,"10273":16787489,"10274":16787490,"10275":16787491,"10276":16787492,"10277":16787493,"10278":16787494,"10279":16787495,"10280":16787496,"10281":16787497,"10282":16787498,"10283":16787499,"10284":16787500,"10285":16787501,"10286":16787502,"10287":16787503,"10288":16787504,"10289":16787505,"10290":16787506,"10291":16787507,"10292":16787508,"10293":16787509,"10294":16787510,"10295":16787511,"10296":16787512,"10297":16787513,"10298":16787514,"10299":16787515,"10300":16787516,"10301":16787517,"10302":16787518,"10303":16787519,"10304":16787520,"10305":16787521,"10306":16787522,"10307":16787523,"10308":16787524,"10309":16787525,"10310":16787526,"10311":16787527,"10312":16787528,"10313":16787529,"10314":16787530,"10315":16787531,"10316":16787532,"10317":16787533,"10318":16787534,"10319":16787535,"10320":16787536,"10321":16787537,"10322":16787538,"10323":16787539,"10324":16787540,"10325":16787541,"10326":16787542,"10327":16787543,"10328":16787544,"10329":16787545,"10330":16787546,"10331":16787547,"10332":16787548,"10333":16787549,"10334":16787550,"10335":16787551,"10336":16787552,"10337":16787553,"10338":16787554,"10339":16787555,"10340":16787556,"10341":16787557,"10342":16787558,"10343":16787559,"10344":16787560,"10345":16787561,"10346":16787562,"10347":16787563,"10348":16787564,"10349":16787565,"10350":16787566,"10351":16787567,"10352":16787568,"10353":16787569,"10354":16787570,"10355":16787571,"10356":16787572,"10357":16787573,"10358":16787574,"10359":16787575,"10360":16787576,"10361":16787577,"10362":16787578,"10363":16787579,"10364":16787580,"10365":16787581,"10366":16787582,"10367":16787583,"10368":16787584,"10369":16787585,"10370":16787586,"10371":16787587,"10372":16787588,"10373":16787589,"10374":16787590,"10375":16787591,"10376":16787592,"10377":16787593,"10378":16787594,"10379":16787595,"10380":16787596,"10381":16787597,"10382":16787598,"10383":16787599,"10384":16787600,"10385":16787601,"10386":16787602,"10387":16787603,"10388":16787604,"10389":16787605,"10390":16787606,"10391":16787607,"10392":16787608,"10393":16787609,"10394":16787610,"10395":16787611,"10396":16787612,"10397":16787613,"10398":16787614,"10399":16787615,"10400":16787616,"10401":16787617,"10402":16787618,"10403":16787619,"10404":16787620,"10405":16787621,"10406":16787622,"10407":16787623,"10408":16787624,"10409":16787625,"10410":16787626,"10411":16787627,"10412":16787628,"10413":16787629,"10414":16787630,"10415":16787631,"10416":16787632,"10417":16787633,"10418":16787634,"10419":16787635,"10420":16787636,"10421":16787637,"10422":16787638,"10423":16787639,"10424":16787640,"10425":16787641,"10426":16787642,"10427":16787643,"10428":16787644,"10429":16787645,"10430":16787646,"10431":16787647,"10432":16787648,"10433":16787649,"10434":16787650,"10435":16787651,"10436":16787652,"10437":16787653,"10438":16787654,"10439":16787655,"10440":16787656,"10441":16787657,"10442":16787658,"10443":16787659,"10444":16787660,"10445":16787661,"10446":16787662,"10447":16787663,"10448":16787664,"10449":16787665,"10450":16787666,"10451":16787667,"10452":16787668,"10453":16787669,"10454":16787670,"10455":16787671,"10456":16787672,"10457":16787673,"10458":16787674,"10459":16787675,"10460":16787676,"10461":16787677,"10462":16787678,"10463":16787679,"10464":16787680,"10465":16787681,"10466":16787682,"10467":16787683,"10468":16787684,"10469":16787685,"10470":16787686,"10471":16787687,"10472":16787688,"10473":16787689,"10474":16787690,"10475":16787691,"10476":16787692,"10477":16787693,"10478":16787694,"10479":16787695,"10480":16787696,"10481":16787697,"10482":16787698,"10483":16787699,"10484":16787700,"10485":16787701,"10486":16787702,"10487":16787703,"10488":16787704,"10489":16787705,"10490":16787706,"10491":16787707,"10492":16787708,"10493":16787709,"10494":16787710,"10495":16787711,"12289":1188,"12290":1185,"12300":1186,"12301":1187,"12443":1246,"12444":1247,"12449":1191,"12450":1201,"12451":1192,"12452":1202,"12453":1193,"12454":1203,"12455":1194,"12456":1204,"12457":1195,"12458":1205,"12459":1206,"12461":1207,"12463":1208,"12465":1209,"12467":1210,"12469":1211,"12471":1212,"12473":1213,"12475":1214,"12477":1215,"12479":1216,"12481":1217,"12483":1199,"12484":1218,"12486":1219,"12488":1220,"12490":1221,"12491":1222,"12492":1223,"12493":1224,"12494":1225,"12495":1226,"12498":1227,"12501":1228,"12504":1229,"12507":1230,"12510":1231,"12511":1232,"12512":1233,"12513":1234,"12514":1235,"12515":1196,"12516":1236,"12517":1197,"12518":1237,"12519":1198,"12520":1238,"12521":1239,"12522":1240,"12523":1241,"12524":1242,"12525":1243,"12527":1244,"12530":1190,"12531":1245,"12539":1189,"12540":1200}; 9 | 10 | function lookup(k) { return k ? {keysym: k, keyname: keynames ? keynames[k] : k} : undefined; } 11 | return { 12 | fromUnicode : function(u) { return lookup(codepoints[u]); }, 13 | lookup : lookup 14 | }; 15 | })(); 16 | --------------------------------------------------------------------------------