Merge to keep up to date with upstream

Conflicts:
	include/display.js
	include/rfb.js
	include/ui.js
	vnc_auto.html
This commit is contained in:
samhed 2015-01-22 11:09:08 +01:00
commit 9ae9fdec5c
51 changed files with 11833 additions and 6910 deletions

1
.gitignore vendored
View File

@ -2,3 +2,4 @@
*.o
tests/data_*.js
utils/rebind.so
node_modules

16
.travis.yml Normal file
View File

@ -0,0 +1,16 @@
language: node_js
node_js:
- '0.11.13'
env:
matrix:
- TEST_BROWSER_NAME=PhantomJS
- TEST_BROWSER_NAME=chrome TEST_BROWSER_OS='Windows 7,Linux'
- TEST_BROWSER_NAME=firefox TEST_BROWSER_OS='Windows 7,Linux' TEST_BROWSER_VERSION='30,26'
- TEST_BROWSER_NAME='internet explorer' TEST_BROWSER_OS='Windows 7' TEST_BROWSER_VERSION=10
- TEST_BROWSER_NAME='internet explorer' TEST_BROWSER_OS='Windows 8.1' TEST_BROWSER_VERSION=11
- TEST_BROWSER_NAME=safari TEST_BROWSER_OS='OS X 10.8' TEST_BROWSER_VERSION=6
- TEST_BROWSER_NAME=safari TEST_BROWSER_OS='OS X 10.9' TEST_BROWSER_VERSION=7
global:
- secure: QE5GqGd2hrpQsIgd8dlv3oRUUHqZayomzzQjNXOB81VQi241uz/ru+3GtBZLB5WLZCq/Gj89vbLnR0LN4ixlmPaWv3/WJQGyDGuRD/vMnccVl+rBUP/Hh2zdYwiISIGcrywNAE+KLus/lyt/ahVgzbaRaDSzrM1HaZFT/rndGck=
- secure: g75sdctEwj0hoLW0Y08Tdv8s5scNzplB6a9EtaJ2vJD9S/bK+AsPqbWesGv1UlrFPCWdbV7Vg61vkmoUjcmb5xhqFIjcM9TlYJoKWeOTsOmnQoSIkIq6gMF1k02+LmKInbPgIzrp3m3jluS1qaOs/EzFpDnJp9hWBiAfXa12Jxk=
before_script: npm install -g karma-cli

54
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,54 @@
How to contribute to noVNC
==========================
We accept code via pull requests on GitHub. There are several guidelines that
we expect contributors submitting code requests to follow. If you have issues
following any of these guidelines, feel free to drop us a line by leaving a
comment in the code request or sending us an email.
Contributing Guidelines
-----------------------
* While we don't have an official coding style guide, please try to follow
the general coding style of the existing code.
** Use four spaces instead of tabs
** prefix private variables and functions with an `_`
* Please try to include unit tests for your code. For instance, if you
introduce a new encoding, add a test to `tests/test.rfb.js` under the
"Encoding Handlers" section (basically, input a small pattern in your
encoding and make sure the pattern gets displayed correctly). If you
fix a bug, try to add a unit test that would have caught that bug
(if possible -- some bugs, especially visual ones, are hard to test for).
* Squash your commits down in to a clean commit history. For instance, there
should not be "cleanup" commits where you fix issues in previous commits in
the same pull request. Before you go to commit, use `git rebase -i` to
squash these changes into the relevant commits. For instance, a good commit
history might look like "Added support for FOO encoding, Added support for
BAR message, Placed Button in UI to Trigger BAR" (where each comma denotes
a separate commit).
* Add both a title and description to your commit, if possible. Place more
detail on what you did in the description.
Running the unit tests
----------------------
There are two ways to run the unit tests. For both ways, you should first run
`npm install` (not as root).
The first way to run the tests is to run `npm test`. This will run all the
tests in the headless PhantomJS browser (which uses WebKit).
The second way to run the tests is using the `tests/run_from_console.js` file.
This way is a bit more flexible, and can provide more information about what
went wrong. To run all the tests, simply run `tests/run_from_console.js`.
To run a specific test file, you can use the `-t path/to/test/file.js` option.
If you wish to simply generate the HTML for the test, use the `-g` option, and
the path to the temporary HTML file will be written to standard out. To open
this file in your default browser automatically, pass the `-o` option as well.
More information can be found by passing the `--help` or `-h` option.
Thanks, and happy coding!

View File

@ -10,6 +10,7 @@ is not limited to):
include/display.js
include/input.js
include/jsunzip.js
include/keysym.js
include/logo.js
include/rfb.js
include/ui.js

View File

@ -1,23 +1,25 @@
## noVNC: HTML5 VNC Client
[![Build Status](https://travis-ci.org/kanaka/noVNC.svg?branch=master)](https://travis-ci.org/kanaka/noVNC)
### Description
noVNC is a HTML5 VNC client that runs well in any modern browser
including mobile browsers (iPhone/iPad and Android).
More than 16 companies/projects have integrated noVNC into their
products including [Ganeti Web
Many companies/projects have integrated noVNC including [Ganeti Web
Manager](http://code.osuosl.org/projects/ganeti-webmgr),
[OpenStack](http://www.openstack.org), and
[OpenNebula](http://opennebula.org/). See [the Projects and Companies
wiki page](https://github.com/kanaka/noVNC/wiki/ProjectsCompanies-using-noVNC)
for more complete list.
[OpenStack](http://www.openstack.org),
[OpenNebula](http://opennebula.org/), and
[LibVNCServer](http://libvncserver.sourceforge.net). See [the Projects
and Companies wiki
page](https://github.com/kanaka/noVNC/wiki/ProjectsCompanies-using-noVNC)
for a more complete list with additional info and links.
### News/help/contact
Notable commits, announcements and news are posted to
@<a href="http://www.twitter.com/noVNC">noVNC</a>
<a href="http://www.twitter.com/noVNC">@noVNC</a>
If you are a noVNC developer/integrator/user (or want to be) please
join the <a
@ -27,16 +29,17 @@ discussion group</a>
Bugs and feature requests can be submitted via [github
issues](https://github.com/kanaka/noVNC/issues). If you are looking
for a place to start contributing to noVNC, a good place to start
would be the issues that I have marked as
would be the issues that are marked as
["patchwelcome"](https://github.com/kanaka/noVNC/issues?labels=patchwelcome).
If you want to show appreciation for noVNC you could buy something off
my [Amazon wishlist](http://www.amazon.com/registry/wishlist/XTXFXK39IA8C/?reveal=unpurchased&sort=priority&layout=compact) or you could donate to a great non-profits such as: [Compassion
If you want to show appreciation for noVNC you could donate to a great
non-profits such as: [Compassion
International](http://www.compassion.com/), [SIL](http://www.sil.org),
[Habitat for Humanity](http://www.habitat.org), [Electronic Frontier
Foundation](https://www.eff.org/), [Against Malaria
Foundation](http://www.againstmalaria.com/), [Nothing But
Nets](http://www.nothingbutnets.net/), etc.
Nets](http://www.nothingbutnets.net/), etc. Please tweet <a
href="http://www.twitter.com/noVNC">@noVNC</a> if you do.
### Features
@ -75,17 +78,18 @@ See more screenshots <a href="http://kanaka.github.com/noVNC/screenshots.html">h
* Fast Javascript Engine: this is not strictly a requirement, but
without a fast Javascript engine, noVNC might be painfully slow.
* I maintain a more detailed browser compatibility list <a
href="https://github.com/kanaka/noVNC/wiki/Browser-support">here</a>.
* See the more detailed [browser compatibility wiki page](https://github.com/kanaka/noVNC/wiki/Browser-support).
### Server Requirements
Unless you are using a VNC server with support for WebSockets
connections (such as [x11vnc/libvncserver](http://libvncserver.sourceforge.net/) or
[PocketVNC](http://www.pocketvnc.com/blog/?page_id=866)),
you need to use a WebSockets to TCP socket proxy. There is
a python proxy included ('websockify').
connections (such as
[x11vnc/libvncserver](http://libvncserver.sourceforge.net/),
[QEMU](http://www.qemu.org/), or
[PocketVNC](http://www.pocketvnc.com/blog/?page_id=866)), you need to
use a WebSockets to TCP socket proxy. There is a python proxy included
('websockify').
### Quick Start
@ -114,7 +118,13 @@ a python proxy included ('websockify').
### Authors/Contributors
* noVNC : Joel Martin (github.com/kanaka)
* Core team:
* [Joel Martin](https://github.com/kanaka)
* [Samuel Mannehed](https://github.com/samhed) (Cendio)
* [Peter Åstrand](https://github.com/astrand) (Cendio)
* [Solly Ross](https://github.com/DirectXMan12) (Red Hat / OpenStack)
* Notable contributions:
* UI and Icons : Chris Gordon
* Original Logo : Michael Sersen
* tight encoding : Michael Tinglof (Mercuri.ca)
@ -126,5 +136,3 @@ a python proxy included ('websockify').
* jsunzip : Erik Moller (github.com/operasoftware/jsunzip),
* tinflate : Joergen Ibsen (ibsensoftware.com)
* DES : Dave Zimmerman (Widget Workshop), Jef Poskanzer (ACME Labs)

View File

@ -13,6 +13,8 @@ images/favicon.ico /usr/share/novnc
include/base64.js /usr/share/novnc/include
include/des.js /usr/share/novnc/include
include/display.js /usr/share/novnc/include
include/keysymdef.js /usr/share/novnc/include
include/keyboard.js /usr/share/novnc/include
include/input.js /usr/share/novnc/include
include/logo.js /usr/share/novnc/include
include/base.css /usr/share/novnc/include

BIN
images/alt.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 339 B

BIN
images/ctrl.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 354 B

BIN
images/esc.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 385 B

BIN
images/power.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 390 B

BIN
images/showextrakeys.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 735 B

BIN
images/tab.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 387 B

View File

@ -41,9 +41,6 @@ html {
}
#noVNC_encrypt {
}
#noVNC_connectTimeout {
width: 30px;
}
#noVNC_path {
width: 100px;
}
@ -52,16 +49,31 @@ html {
float:right;
}
#noVNC_buttons {
white-space: nowrap;
}
#noVNC_view_drag_button {
display: none;
}
#sendCtrlAltDelButton {
display: none;
}
#noVNC_xvp_buttons {
display: none;
}
#noVNC_mobile_buttons {
display: none;
}
#noVNC_extra_keys {
display: inline;
list-style-type: none;
padding: 0px;
margin: 0px;
position: relative;
}
.noVNC-buttons-left {
float: left;
z-index: 1;
@ -191,6 +203,16 @@ html {
border-radius:10px;
}
#noVNC_xvp {
display:none;
margin-top:73px;
right:30px;
position:fixed;
}
#noVNC_xvp.top:after {
right:125px;
}
#noVNC_clipboard {
display:none;
margin-top:73px;
@ -402,9 +424,33 @@ html {
z-index: 0;
position: absolute;
width: 100%;
margin-left: 0px;
}
#showExtraKeysButton { display: none; }
#toggleCtrlButton { display: inline; }
#toggleAltButton { display: inline; }
#sendTabButton { display: inline; }
#sendEscButton { display: inline; }
/* left-align the status text on lower resolutions */
@media screen and (max-width: 800px){
#noVNC_status {
z-index: 1;
position: relative;
width: auto;
float: left;
margin-left: 4px;
}
}
@media screen and (max-width: 640px){
#noVNC_clipboard_text {
width: 410px;
}
#noVNC_logo {
font-size: 150px;
}
.noVNC_status_button {
font-size: 10px;
}
@ -414,20 +460,33 @@ html {
.noVNC-buttons-right {
padding-right: 0px;
}
#noVNC_status {
z-index: 1;
position: relative;
width: auto;
float: left;
/* collapse the extra keys on lower resolutions */
#showExtraKeysButton {
display: inline;
}
}
@media screen and (min-width: 481px) and (max-width: 640px) {
#noVNC_clipboard_text {
width: 410px;
#toggleCtrlButton {
display: none;
position: absolute;
top: 30px;
left: 0px;
}
#noVNC_logo {
font-size: 150px;
#toggleAltButton {
display: none;
position: absolute;
top: 65px;
left: 0px;
}
#sendTabButton {
display: none;
position: absolute;
top: 100px;
left: 0px;
}
#sendEscButton {
display: none;
position: absolute;
top: 135px;
left: 0px;
}
}

View File

@ -4,38 +4,36 @@
// From: http://hg.mozilla.org/mozilla-central/raw-file/ec10630b1a54/js/src/devtools/jint/sunspider/string-base64.js
/*jslint white: false, bitwise: false, plusplus: false */
/*jslint white: false */
/*global console */
var Base64 = {
/* Convert data (an array of integers) to a Base64 string. */
toBase64Table : 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='.split(''),
base64Pad : '=',
/* Convert data (an array of integers) to a Base64 string. */
toBase64Table : 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='.split(''),
base64Pad : '=',
encode: function (data) {
encode: function (data) {
"use strict";
var result = '';
var toBase64Table = Base64.toBase64Table;
var length = data.length
var lengthpad = (length%3);
var i = 0, j = 0;
var length = data.length;
var lengthpad = (length % 3);
// Convert every three bytes to 4 ascii characters.
/* BEGIN LOOP */
for (i = 0; i < (length - 2); i += 3) {
for (var i = 0; i < (length - 2); i += 3) {
result += toBase64Table[data[i] >> 2];
result += toBase64Table[((data[i] & 0x03) << 4) + (data[i+1] >> 4)];
result += toBase64Table[((data[i+1] & 0x0f) << 2) + (data[i+2] >> 6)];
result += toBase64Table[data[i+2] & 0x3f];
result += toBase64Table[((data[i] & 0x03) << 4) + (data[i + 1] >> 4)];
result += toBase64Table[((data[i + 1] & 0x0f) << 2) + (data[i + 2] >> 6)];
result += toBase64Table[data[i + 2] & 0x3f];
}
/* END LOOP */
// Convert the remaining 1 or 2 bytes, pad out to 4 characters.
var j = 0;
if (lengthpad === 2) {
j = length - lengthpad;
result += toBase64Table[data[j] >> 2];
result += toBase64Table[((data[j] & 0x03) << 4) + (data[j+1] >> 4)];
result += toBase64Table[(data[j+1] & 0x0f) << 2];
result += toBase64Table[((data[j] & 0x03) << 4) + (data[j + 1] >> 4)];
result += toBase64Table[(data[j + 1] & 0x0f) << 2];
result += toBase64Table[64];
} else if (lengthpad === 1) {
j = length - lengthpad;
@ -46,10 +44,11 @@ encode: function (data) {
}
return result;
},
},
/* Convert Base64 data to a string */
toBinaryTable : [
/* Convert Base64 data to a string */
/* jshint -W013 */
toBinaryTable : [
-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,62, -1,-1,-1,63,
@ -58,14 +57,15 @@ toBinaryTable : [
15,16,17,18, 19,20,21,22, 23,24,25,-1, -1,-1,-1,-1,
-1,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40,
41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1
],
],
/* jshint +W013 */
decode: function (data, offset) {
decode: function (data, offset) {
"use strict";
offset = typeof(offset) !== 'undefined' ? offset : 0;
var toBinaryTable = Base64.toBinaryTable;
var base64Pad = Base64.base64Pad;
var result, result_length, idx, i, c, padding;
var result, result_length;
var leftbits = 0; // number of bits decoded, but yet to be appended
var leftdata = 0; // bits decoded, but yet to be appended
var data_length = data.indexOf('=') - offset;
@ -73,14 +73,13 @@ decode: function (data, offset) {
if (data_length < 0) { data_length = data.length - offset; }
/* Every four characters is 3 resulting numbers */
result_length = (data_length >> 2) * 3 + Math.floor((data_length%4)/1.5);
result_length = (data_length >> 2) * 3 + Math.floor((data_length % 4) / 1.5);
result = new Array(result_length);
// Convert one by one.
/* BEGIN LOOP */
for (idx = 0, i = offset; i < data.length; i++) {
c = toBinaryTable[data.charCodeAt(i) & 0x7f];
padding = (data.charAt(i) === base64Pad);
for (var idx = 0, i = offset; i < data.length; i++) {
var c = toBinaryTable[data.charCodeAt(i) & 0x7f];
var padding = (data.charAt(i) === base64Pad);
// Skip illegal characters and whitespace
if (c === -1) {
console.error("Illegal character code " + data.charCodeAt(i) + " at position " + i);
@ -101,15 +100,14 @@ decode: function (data, offset) {
leftdata &= (1 << leftbits) - 1;
}
}
/* END LOOP */
// If there are any bits left, the base64 string was corrupted
if (leftbits) {
throw {name: 'Base64-Error',
message: 'Corrupted base64 string'};
err = new Error('Corrupted base64 string');
err.name = 'Base64-Error';
throw err;
}
return result;
}
}
}; /* End of Base64 namespace */

View File

@ -75,67 +75,70 @@
* fine Java utilities: http://www.acme.com/java/
*/
"use strict";
/*jslint white: false, bitwise: false, plusplus: false */
/* jslint white: false */
function DES(passwd) {
"use strict";
// Tables, permutations, S-boxes, etc.
var PC2 = [13,16,10,23, 0, 4, 2,27,14, 5,20, 9,22,18,11, 3,
// Tables, permutations, S-boxes, etc.
// jshint -W013
var PC2 = [13,16,10,23, 0, 4, 2,27,14, 5,20, 9,22,18,11, 3,
25, 7,15, 6,26,19,12, 1,40,51,30,36,46,54,29,39,
50,44,32,47,43,48,38,55,33,52,45,41,49,35,28,31 ],
totrot = [ 1, 2, 4, 6, 8,10,12,14,15,17,19,21,23,25,27,28],
z = 0x0, a,b,c,d,e,f, SP1,SP2,SP3,SP4,SP5,SP6,SP7,SP8,
keys = [];
a=1<<16; b=1<<24; c=a|b; d=1<<2; e=1<<10; f=d|e;
SP1 = [c|e,z|z,a|z,c|f,c|d,a|f,z|d,a|z,z|e,c|e,c|f,z|e,b|f,c|d,b|z,z|d,
// jshint -W015
a=1<<16; b=1<<24; c=a|b; d=1<<2; e=1<<10; f=d|e;
SP1 = [c|e,z|z,a|z,c|f,c|d,a|f,z|d,a|z,z|e,c|e,c|f,z|e,b|f,c|d,b|z,z|d,
z|f,b|e,b|e,a|e,a|e,c|z,c|z,b|f,a|d,b|d,b|d,a|d,z|z,z|f,a|f,b|z,
a|z,c|f,z|d,c|z,c|e,b|z,b|z,z|e,c|d,a|z,a|e,b|d,z|e,z|d,b|f,a|f,
c|f,a|d,c|z,b|f,b|d,z|f,a|f,c|e,z|f,b|e,b|e,z|z,a|d,a|e,z|z,c|d];
a=1<<20; b=1<<31; c=a|b; d=1<<5; e=1<<15; f=d|e;
SP2 = [c|f,b|e,z|e,a|f,a|z,z|d,c|d,b|f,b|d,c|f,c|e,b|z,b|e,a|z,z|d,c|d,
a=1<<20; b=1<<31; c=a|b; d=1<<5; e=1<<15; f=d|e;
SP2 = [c|f,b|e,z|e,a|f,a|z,z|d,c|d,b|f,b|d,c|f,c|e,b|z,b|e,a|z,z|d,c|d,
a|e,a|d,b|f,z|z,b|z,z|e,a|f,c|z,a|d,b|d,z|z,a|e,z|f,c|e,c|z,z|f,
z|z,a|f,c|d,a|z,b|f,c|z,c|e,z|e,c|z,b|e,z|d,c|f,a|f,z|d,z|e,b|z,
z|f,c|e,a|z,b|d,a|d,b|f,b|d,a|d,a|e,z|z,b|e,z|f,b|z,c|d,c|f,a|e];
a=1<<17; b=1<<27; c=a|b; d=1<<3; e=1<<9; f=d|e;
SP3 = [z|f,c|e,z|z,c|d,b|e,z|z,a|f,b|e,a|d,b|d,b|d,a|z,c|f,a|d,c|z,z|f,
a=1<<17; b=1<<27; c=a|b; d=1<<3; e=1<<9; f=d|e;
SP3 = [z|f,c|e,z|z,c|d,b|e,z|z,a|f,b|e,a|d,b|d,b|d,a|z,c|f,a|d,c|z,z|f,
b|z,z|d,c|e,z|e,a|e,c|z,c|d,a|f,b|f,a|e,a|z,b|f,z|d,c|f,z|e,b|z,
c|e,b|z,a|d,z|f,a|z,c|e,b|e,z|z,z|e,a|d,c|f,b|e,b|d,z|e,z|z,c|d,
b|f,a|z,b|z,c|f,z|d,a|f,a|e,b|d,c|z,b|f,z|f,c|z,a|f,z|d,c|d,a|e];
a=1<<13; b=1<<23; c=a|b; d=1<<0; e=1<<7; f=d|e;
SP4 = [c|d,a|f,a|f,z|e,c|e,b|f,b|d,a|d,z|z,c|z,c|z,c|f,z|f,z|z,b|e,b|d,
a=1<<13; b=1<<23; c=a|b; d=1<<0; e=1<<7; f=d|e;
SP4 = [c|d,a|f,a|f,z|e,c|e,b|f,b|d,a|d,z|z,c|z,c|z,c|f,z|f,z|z,b|e,b|d,
z|d,a|z,b|z,c|d,z|e,b|z,a|d,a|e,b|f,z|d,a|e,b|e,a|z,c|e,c|f,z|f,
b|e,b|d,c|z,c|f,z|f,z|z,z|z,c|z,a|e,b|e,b|f,z|d,c|d,a|f,a|f,z|e,
c|f,z|f,z|d,a|z,b|d,a|d,c|e,b|f,a|d,a|e,b|z,c|d,z|e,b|z,a|z,c|e];
a=1<<25; b=1<<30; c=a|b; d=1<<8; e=1<<19; f=d|e;
SP5 = [z|d,a|f,a|e,c|d,z|e,z|d,b|z,a|e,b|f,z|e,a|d,b|f,c|d,c|e,z|f,b|z,
a=1<<25; b=1<<30; c=a|b; d=1<<8; e=1<<19; f=d|e;
SP5 = [z|d,a|f,a|e,c|d,z|e,z|d,b|z,a|e,b|f,z|e,a|d,b|f,c|d,c|e,z|f,b|z,
a|z,b|e,b|e,z|z,b|d,c|f,c|f,a|d,c|e,b|d,z|z,c|z,a|f,a|z,c|z,z|f,
z|e,c|d,z|d,a|z,b|z,a|e,c|d,b|f,a|d,b|z,c|e,a|f,b|f,z|d,a|z,c|e,
c|f,z|f,c|z,c|f,a|e,z|z,b|e,c|z,z|f,a|d,b|d,z|e,z|z,b|e,a|f,b|d];
a=1<<22; b=1<<29; c=a|b; d=1<<4; e=1<<14; f=d|e;
SP6 = [b|d,c|z,z|e,c|f,c|z,z|d,c|f,a|z,b|e,a|f,a|z,b|d,a|d,b|e,b|z,z|f,
a=1<<22; b=1<<29; c=a|b; d=1<<4; e=1<<14; f=d|e;
SP6 = [b|d,c|z,z|e,c|f,c|z,z|d,c|f,a|z,b|e,a|f,a|z,b|d,a|d,b|e,b|z,z|f,
z|z,a|d,b|f,z|e,a|e,b|f,z|d,c|d,c|d,z|z,a|f,c|e,z|f,a|e,c|e,b|z,
b|e,z|d,c|d,a|e,c|f,a|z,z|f,b|d,a|z,b|e,b|z,z|f,b|d,c|f,a|e,c|z,
a|f,c|e,z|z,c|d,z|d,z|e,c|z,a|f,z|e,a|d,b|f,z|z,c|e,b|z,a|d,b|f];
a=1<<21; b=1<<26; c=a|b; d=1<<1; e=1<<11; f=d|e;
SP7 = [a|z,c|d,b|f,z|z,z|e,b|f,a|f,c|e,c|f,a|z,z|z,b|d,z|d,b|z,c|d,z|f,
a=1<<21; b=1<<26; c=a|b; d=1<<1; e=1<<11; f=d|e;
SP7 = [a|z,c|d,b|f,z|z,z|e,b|f,a|f,c|e,c|f,a|z,z|z,b|d,z|d,b|z,c|d,z|f,
b|e,a|f,a|d,b|e,b|d,c|z,c|e,a|d,c|z,z|e,z|f,c|f,a|e,z|d,b|z,a|e,
b|z,a|e,a|z,b|f,b|f,c|d,c|d,z|d,a|d,b|z,b|e,a|z,c|e,z|f,a|f,c|e,
z|f,b|d,c|f,c|z,a|e,z|z,z|d,c|f,z|z,a|f,c|z,z|e,b|d,b|e,z|e,a|d];
a=1<<18; b=1<<28; c=a|b; d=1<<6; e=1<<12; f=d|e;
SP8 = [b|f,z|e,a|z,c|f,b|z,b|f,z|d,b|z,a|d,c|z,c|f,a|e,c|e,a|f,z|e,z|d,
a=1<<18; b=1<<28; c=a|b; d=1<<6; e=1<<12; f=d|e;
SP8 = [b|f,z|e,a|z,c|f,b|z,b|f,z|d,b|z,a|d,c|z,c|f,a|e,c|e,a|f,z|e,z|d,
c|z,b|d,b|e,z|f,a|e,a|d,c|d,c|e,z|f,z|z,z|z,c|d,b|d,b|e,a|f,a|z,
a|f,a|z,c|e,z|e,z|d,c|d,z|e,a|f,b|e,z|d,b|d,c|z,c|d,b|z,a|z,b|f,
z|z,c|f,a|d,b|d,c|z,b|e,b|f,z|z,c|f,a|e,a|e,z|f,z|f,a|d,b|z,c|e];
// jshint +W013,+W015
// Set the key.
function setKeys(keyBlock) {
// Set the key.
function setKeys(keyBlock) {
var i, j, l, m, n, o, pc1m = [], pcr = [], kn = [],
raw0, raw1, rawi, KnLi;
for (j = 0, l = 56; j < 56; ++j, l-=8) {
l += l<-5 ? 65 : l<-3 ? 31 : l<-1 ? 63 : l===27 ? 35 : 0; // PC1
for (j = 0, l = 56; j < 56; ++j, l -= 8) {
l += l < -5 ? 65 : l < -3 ? 31 : l < -1 ? 63 : l === 27 ? 35 : 0; // PC1
m = l & 0x7;
pc1m[j] = ((keyBlock[l >>> 3] & (1<<m)) !== 0) ? 1: 0;
}
@ -144,8 +147,8 @@ function setKeys(keyBlock) {
m = i << 1;
n = m + 1;
kn[m] = kn[n] = 0;
for (o=28; o<59; o+=28) {
for (j = o-28; j < o; ++j) {
for (o = 28; o < 59; o += 28) {
for (j = o - 28; j < o; ++j) {
l = j + totrot[i];
if (l < o) {
pcr[j] = pc1m[l];
@ -156,10 +159,10 @@ function setKeys(keyBlock) {
}
for (j = 0; j < 24; ++j) {
if (pcr[PC2[j]] !== 0) {
kn[m] |= 1<<(23-j);
kn[m] |= 1 << (23 - j);
}
if (pcr[PC2[j + 24]] !== 0) {
kn[n] |= 1<<(23-j);
kn[n] |= 1 << (23 - j);
}
}
}
@ -179,10 +182,10 @@ function setKeys(keyBlock) {
keys[KnLi] |= (raw1 & 0x0000003f);
++KnLi;
}
}
}
// Encrypt 8 bytes of text
function enc8(text) {
// Encrypt 8 bytes of text
function enc8(text) {
var i = 0, b = text.slice(), fval, keysi = 0,
l, r, x; // left, right, accumulator
@ -256,18 +259,18 @@ function enc8(text) {
// Spread ints to bytes
x = [r, l];
for (i = 0; i < 8; i++) {
b[i] = (x[i>>>2] >>> (8*(3 - (i%4)))) % 256;
b[i] = (x[i>>>2] >>> (8 * (3 - (i % 4)))) % 256;
if (b[i] < 0) { b[i] += 256; } // unsigned
}
return b;
}
}
// Encrypt 16 bytes of text using passwd as key
function encrypt(t) {
return enc8(t.slice(0,8)).concat(enc8(t.slice(8,16)));
}
// Encrypt 16 bytes of text using passwd as key
function encrypt(t) {
return enc8(t.slice(0, 8)).concat(enc8(t.slice(8, 16)));
}
setKeys(passwd); // Setup keys
return {'encrypt': encrypt}; // Public interface
setKeys(passwd); // Setup keys
return {'encrypt': encrypt}; // Public interface
} // function DES

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

543
include/keyboard.js Normal file
View File

@ -0,0 +1,543 @@
var kbdUtil = (function() {
"use strict";
function substituteCodepoint(cp) {
// Any Unicode code points which do not have corresponding keysym entries
// can be swapped out for another code point by adding them to this table
var substitutions = {
// {S,s} with comma below -> {S,s} with cedilla
0x218 : 0x15e,
0x219 : 0x15f,
// {T,t} with comma below -> {T,t} with cedilla
0x21a : 0x162,
0x21b : 0x163
};
var sub = substitutions[cp];
return sub ? sub : cp;
}
function isMac() {
return navigator && !!(/mac/i).exec(navigator.platform);
}
function isWindows() {
return navigator && !!(/win/i).exec(navigator.platform);
}
function isLinux() {
return navigator && !!(/linux/i).exec(navigator.platform);
}
// Return true if a modifier which is not the specified char modifier (and is not shift) is down
function hasShortcutModifier(charModifier, currentModifiers) {
var mods = {};
for (var key in currentModifiers) {
if (parseInt(key) !== XK_Shift_L) {
mods[key] = currentModifiers[key];
}
}
var sum = 0;
for (var k in currentModifiers) {
if (mods[k]) {
++sum;
}
}
if (hasCharModifier(charModifier, mods)) {
return sum > charModifier.length;
}
else {
return sum > 0;
}
}
// Return true if the specified char modifier is currently down
function hasCharModifier(charModifier, currentModifiers) {
if (charModifier.length === 0) { return false; }
for (var i = 0; i < charModifier.length; ++i) {
if (!currentModifiers[charModifier[i]]) {
return false;
}
}
return true;
}
// Helper object tracking modifier key state
// and generates fake key events to compensate if it gets out of sync
function ModifierSync(charModifier) {
if (!charModifier) {
if (isMac()) {
// on Mac, Option (AKA Alt) is used as a char modifier
charModifier = [XK_Alt_L];
}
else if (isWindows()) {
// on Windows, Ctrl+Alt is used as a char modifier
charModifier = [XK_Alt_L, XK_Control_L];
}
else if (isLinux()) {
// on Linux, ISO Level 3 Shift (AltGr) is used as a char modifier
charModifier = [XK_ISO_Level3_Shift];
}
else {
charModifier = [];
}
}
var state = {};
state[XK_Control_L] = false;
state[XK_Alt_L] = false;
state[XK_ISO_Level3_Shift] = false;
state[XK_Shift_L] = false;
state[XK_Meta_L] = false;
function sync(evt, keysym) {
var result = [];
function syncKey(keysym) {
return {keysym: keysyms.lookup(keysym), type: state[keysym] ? 'keydown' : 'keyup'};
}
if (evt.ctrlKey !== undefined &&
evt.ctrlKey !== state[XK_Control_L] && keysym !== XK_Control_L) {
state[XK_Control_L] = evt.ctrlKey;
result.push(syncKey(XK_Control_L));
}
if (evt.altKey !== undefined &&
evt.altKey !== state[XK_Alt_L] && keysym !== XK_Alt_L) {
state[XK_Alt_L] = evt.altKey;
result.push(syncKey(XK_Alt_L));
}
if (evt.altGraphKey !== undefined &&
evt.altGraphKey !== state[XK_ISO_Level3_Shift] && keysym !== XK_ISO_Level3_Shift) {
state[XK_ISO_Level3_Shift] = evt.altGraphKey;
result.push(syncKey(XK_ISO_Level3_Shift));
}
if (evt.shiftKey !== undefined &&
evt.shiftKey !== state[XK_Shift_L] && keysym !== XK_Shift_L) {
state[XK_Shift_L] = evt.shiftKey;
result.push(syncKey(XK_Shift_L));
}
if (evt.metaKey !== undefined &&
evt.metaKey !== state[XK_Meta_L] && keysym !== XK_Meta_L) {
state[XK_Meta_L] = evt.metaKey;
result.push(syncKey(XK_Meta_L));
}
return result;
}
function syncKeyEvent(evt, down) {
var obj = getKeysym(evt);
var keysym = obj ? obj.keysym : null;
// first, apply the event itself, if relevant
if (keysym !== null && state[keysym] !== undefined) {
state[keysym] = down;
}
return sync(evt, keysym);
}
return {
// sync on the appropriate keyboard event
keydown: function(evt) { return syncKeyEvent(evt, true);},
keyup: function(evt) { return syncKeyEvent(evt, false);},
// Call this with a non-keyboard event (such as mouse events) to use its modifier state to synchronize anyway
syncAny: function(evt) { return sync(evt);},
// is a shortcut modifier down?
hasShortcutModifier: function() { return hasShortcutModifier(charModifier, state); },
// if a char modifier is down, return the keys it consists of, otherwise return null
activeCharModifier: function() { return hasCharModifier(charModifier, state) ? charModifier : null; }
};
}
// Get a key ID from a keyboard event
// May be a string or an integer depending on the available properties
function getKey(evt){
if ('keyCode' in evt && 'key' in evt) {
return evt.key + ':' + evt.keyCode;
}
else if ('keyCode' in evt) {
return evt.keyCode;
}
else {
return evt.key;
}
}
// Get the most reliable keysym value we can get from a key event
// if char/charCode is available, prefer those, otherwise fall back to key/keyCode/which
function getKeysym(evt){
var codepoint;
if (evt.char && evt.char.length === 1) {
codepoint = evt.char.charCodeAt();
}
else if (evt.charCode) {
codepoint = evt.charCode;
}
else if (evt.keyCode && evt.type === 'keypress') {
// IE10 stores the char code as keyCode, and has no other useful properties
codepoint = evt.keyCode;
}
if (codepoint) {
var res = keysyms.fromUnicode(substituteCodepoint(codepoint));
if (res) {
return res;
}
}
// we could check evt.key here.
// Legal values are defined in http://www.w3.org/TR/DOM-Level-3-Events/#key-values-list,
// so we "just" need to map them to keysym, but AFAIK this is only available in IE10, which also provides evt.key
// so we don't *need* it yet
if (evt.keyCode) {
return keysyms.lookup(keysymFromKeyCode(evt.keyCode, evt.shiftKey));
}
if (evt.which) {
return keysyms.lookup(keysymFromKeyCode(evt.which, evt.shiftKey));
}
return null;
}
// Given a keycode, try to predict which keysym it might be.
// If the keycode is unknown, null is returned.
function keysymFromKeyCode(keycode, shiftPressed) {
if (typeof(keycode) !== 'number') {
return null;
}
// won't be accurate for azerty
if (keycode >= 0x30 && keycode <= 0x39) {
return keycode; // digit
}
if (keycode >= 0x41 && keycode <= 0x5a) {
// remap to lowercase unless shift is down
return shiftPressed ? keycode : keycode + 32; // A-Z
}
if (keycode >= 0x60 && keycode <= 0x69) {
return XK_KP_0 + (keycode - 0x60); // numpad 0-9
}
switch(keycode) {
case 0x20: return XK_space;
case 0x6a: return XK_KP_Multiply;
case 0x6b: return XK_KP_Add;
case 0x6c: return XK_KP_Separator;
case 0x6d: return XK_KP_Subtract;
case 0x6e: return XK_KP_Decimal;
case 0x6f: return XK_KP_Divide;
case 0xbb: return XK_plus;
case 0xbc: return XK_comma;
case 0xbd: return XK_minus;
case 0xbe: return XK_period;
}
return nonCharacterKey({keyCode: keycode});
}
// if the key is a known non-character key (any key which doesn't generate character data)
// return its keysym value. Otherwise return null
function nonCharacterKey(evt) {
// evt.key not implemented yet
if (!evt.keyCode) { return null; }
var keycode = evt.keyCode;
if (keycode >= 0x70 && keycode <= 0x87) {
return XK_F1 + keycode - 0x70; // F1-F24
}
switch (keycode) {
case 8 : return XK_BackSpace;
case 13 : return XK_Return;
case 9 : return XK_Tab;
case 27 : return XK_Escape;
case 46 : return XK_Delete;
case 36 : return XK_Home;
case 35 : return XK_End;
case 33 : return XK_Page_Up;
case 34 : return XK_Page_Down;
case 45 : return XK_Insert;
case 37 : return XK_Left;
case 38 : return XK_Up;
case 39 : return XK_Right;
case 40 : return XK_Down;
case 16 : return XK_Shift_L;
case 17 : return XK_Control_L;
case 18 : return XK_Alt_L; // also: Option-key on Mac
case 224 : return XK_Meta_L;
case 225 : return XK_ISO_Level3_Shift; // AltGr
case 91 : return XK_Super_L; // also: Windows-key
case 92 : return XK_Super_R; // also: Windows-key
case 93 : return XK_Menu; // also: Windows-Menu, Command on Mac
default: return null;
}
}
return {
hasShortcutModifier : hasShortcutModifier,
hasCharModifier : hasCharModifier,
ModifierSync : ModifierSync,
getKey : getKey,
getKeysym : getKeysym,
keysymFromKeyCode : keysymFromKeyCode,
nonCharacterKey : nonCharacterKey,
substituteCodepoint : substituteCodepoint
};
})();
// Takes a DOM keyboard event and:
// - determines which keysym it represents
// - determines a keyId identifying the key that was pressed (corresponding to the key/keyCode properties on the DOM event)
// - synthesizes events to synchronize modifier key state between which modifiers are actually down, and which we thought were down
// - marks each event with an 'escape' property if a modifier was down which should be "escaped"
// - generates a "stall" event in cases where it might be necessary to wait and see if a keypress event follows a keydown
// This information is collected into an object which is passed to the next() function. (one call per event)
function KeyEventDecoder(modifierState, next) {
"use strict";
function sendAll(evts) {
for (var i = 0; i < evts.length; ++i) {
next(evts[i]);
}
}
function process(evt, type) {
var result = {type: type};
var keyId = kbdUtil.getKey(evt);
if (keyId) {
result.keyId = keyId;
}
var keysym = kbdUtil.getKeysym(evt);
var hasModifier = modifierState.hasShortcutModifier() || !!modifierState.activeCharModifier();
// Is this a case where we have to decide on the keysym right away, rather than waiting for the keypress?
// "special" keys like enter, tab or backspace don't send keypress events,
// and some browsers don't send keypresses at all if a modifier is down
if (keysym && (type !== 'keydown' || kbdUtil.nonCharacterKey(evt) || hasModifier)) {
result.keysym = keysym;
}
var isShift = evt.keyCode === 0x10 || evt.key === 'Shift';
// Should we prevent the browser from handling the event?
// Doing so on a keydown (in most browsers) prevents keypress from being generated
// so only do that if we have to.
var suppress = !isShift && (type !== 'keydown' || modifierState.hasShortcutModifier() || !!kbdUtil.nonCharacterKey(evt));
// If a char modifier is down on a keydown, we need to insert a stall,
// so VerifyCharModifier knows to wait and see if a keypress is comnig
var stall = type === 'keydown' && modifierState.activeCharModifier() && !kbdUtil.nonCharacterKey(evt);
// if a char modifier is pressed, get the keys it consists of (on Windows, AltGr is equivalent to Ctrl+Alt)
var active = modifierState.activeCharModifier();
// If we have a char modifier down, and we're able to determine a keysym reliably
// then (a) we know to treat the modifier as a char modifier,
// and (b) we'll have to "escape" the modifier to undo the modifier when sending the char.
if (active && keysym) {
var isCharModifier = false;
for (var i = 0; i < active.length; ++i) {
if (active[i] === keysym.keysym) {
isCharModifier = true;
}
}
if (type === 'keypress' && !isCharModifier) {
result.escape = modifierState.activeCharModifier();
}
}
if (stall) {
// insert a fake "stall" event
next({type: 'stall'});
}
next(result);
return suppress;
}
return {
keydown: function(evt) {
sendAll(modifierState.keydown(evt));
return process(evt, 'keydown');
},
keypress: function(evt) {
return process(evt, 'keypress');
},
keyup: function(evt) {
sendAll(modifierState.keyup(evt));
return process(evt, 'keyup');
},
syncModifiers: function(evt) {
sendAll(modifierState.syncAny(evt));
},
releaseAll: function() { next({type: 'releaseall'}); }
};
}
// Combines keydown and keypress events where necessary to handle char modifiers.
// On some OS'es, a char modifier is sometimes used as a shortcut modifier.
// For example, on Windows, AltGr is synonymous with Ctrl-Alt. On a Danish keyboard layout, AltGr-2 yields a @, but Ctrl-Alt-D does nothing
// so when used with the '2' key, Ctrl-Alt counts as a char modifier (and should be escaped), but when used with 'D', it does not.
// The only way we can distinguish these cases is to wait and see if a keypress event arrives
// When we receive a "stall" event, wait a few ms before processing the next keydown. If a keypress has also arrived, merge the two
function VerifyCharModifier(next) {
"use strict";
var queue = [];
var timer = null;
function process() {
if (timer) {
return;
}
var delayProcess = function () {
clearTimeout(timer);
timer = null;
process();
};
while (queue.length !== 0) {
var cur = queue[0];
queue = queue.splice(1);
switch (cur.type) {
case 'stall':
// insert a delay before processing available events.
/* jshint loopfunc: true */
timer = setTimeout(delayProcess, 5);
/* jshint loopfunc: false */
return;
case 'keydown':
// is the next element a keypress? Then we should merge the two
if (queue.length !== 0 && queue[0].type === 'keypress') {
// Firefox sends keypress even when no char is generated.
// so, if keypress keysym is the same as we'd have guessed from keydown,
// the modifier didn't have any effect, and should not be escaped
if (queue[0].escape && (!cur.keysym || cur.keysym.keysym !== queue[0].keysym.keysym)) {
cur.escape = queue[0].escape;
}
cur.keysym = queue[0].keysym;
queue = queue.splice(1);
}
break;
}
// swallow stall events, and pass all others to the next stage
if (cur.type !== 'stall') {
next(cur);
}
}
}
return function(evt) {
queue.push(evt);
process();
};
}
// Keeps track of which keys we (and the server) believe are down
// When a keyup is received, match it against this list, to determine the corresponding keysym(s)
// in some cases, a single key may produce multiple keysyms, so the corresponding keyup event must release all of these chars
// key repeat events should be merged into a single entry.
// Because we can't always identify which entry a keydown or keyup event corresponds to, we sometimes have to guess
function TrackKeyState(next) {
"use strict";
var state = [];
return function (evt) {
var last = state.length !== 0 ? state[state.length-1] : null;
switch (evt.type) {
case 'keydown':
// insert a new entry if last seen key was different.
if (!last || !evt.keyId || last.keyId !== evt.keyId) {
last = {keyId: evt.keyId, keysyms: {}};
state.push(last);
}
if (evt.keysym) {
// make sure last event contains this keysym (a single "logical" keyevent
// can cause multiple key events to be sent to the VNC server)
last.keysyms[evt.keysym.keysym] = evt.keysym;
last.ignoreKeyPress = true;
next(evt);
}
break;
case 'keypress':
if (!last) {
last = {keyId: evt.keyId, keysyms: {}};
state.push(last);
}
if (!evt.keysym) {
console.log('keypress with no keysym:', evt);
}
// If we didn't expect a keypress, and already sent a keydown to the VNC server
// based on the keydown, make sure to skip this event.
if (evt.keysym && !last.ignoreKeyPress) {
last.keysyms[evt.keysym.keysym] = evt.keysym;
evt.type = 'keydown';
next(evt);
}
break;
case 'keyup':
if (state.length === 0) {
return;
}
var idx = null;
// do we have a matching key tracked as being down?
for (var i = 0; i !== state.length; ++i) {
if (state[i].keyId === evt.keyId) {
idx = i;
break;
}
}
// if we couldn't find a match (it happens), assume it was the last key pressed
if (idx === null) {
idx = state.length - 1;
}
var item = state.splice(idx, 1)[0];
// for each keysym tracked by this key entry, clone the current event and override the keysym
var clone = (function(){
function Clone(){}
return function (obj) { Clone.prototype=obj; return new Clone(); };
}());
for (var key in item.keysyms) {
var out = clone(evt);
out.keysym = item.keysyms[key];
next(out);
}
break;
case 'releaseall':
/* jshint shadow: true */
for (var i = 0; i < state.length; ++i) {
for (var key in state[i].keysyms) {
var keysym = state[i].keysyms[key];
next({keyId: 0, keysym: keysym, type: 'keyup'});
}
}
/* jshint shadow: false */
state = [];
}
};
}
// Handles "escaping" of modifiers: if a char modifier is used to produce a keysym (such as AltGr-2 to generate an @),
// then the modifier must be "undone" before sending the @, and "redone" afterwards.
function EscapeModifiers(next) {
"use strict";
return function(evt) {
if (evt.type !== 'keydown' || evt.escape === undefined) {
next(evt);
return;
}
// undo modifiers
for (var i = 0; i < evt.escape.length; ++i) {
next({type: 'keyup', keyId: 0, keysym: keysyms.lookup(evt.escape[i])});
}
// send the character event
next(evt);
// redo modifiers
/* jshint shadow: true */
for (var i = 0; i < evt.escape.length; ++i) {
next({type: 'keydown', keyId: 0, keysym: keysyms.lookup(evt.escape[i])});
}
/* jshint shadow: false */
};
}

378
include/keysym.js Normal file
View File

@ -0,0 +1,378 @@
var XK_VoidSymbol = 0xffffff, /* Void symbol */
XK_BackSpace = 0xff08, /* Back space, back char */
XK_Tab = 0xff09,
XK_Linefeed = 0xff0a, /* Linefeed, LF */
XK_Clear = 0xff0b,
XK_Return = 0xff0d, /* Return, enter */
XK_Pause = 0xff13, /* Pause, hold */
XK_Scroll_Lock = 0xff14,
XK_Sys_Req = 0xff15,
XK_Escape = 0xff1b,
XK_Delete = 0xffff, /* Delete, rubout */
/* Cursor control & motion */
XK_Home = 0xff50,
XK_Left = 0xff51, /* Move left, left arrow */
XK_Up = 0xff52, /* Move up, up arrow */
XK_Right = 0xff53, /* Move right, right arrow */
XK_Down = 0xff54, /* Move down, down arrow */
XK_Prior = 0xff55, /* Prior, previous */
XK_Page_Up = 0xff55,
XK_Next = 0xff56, /* Next */
XK_Page_Down = 0xff56,
XK_End = 0xff57, /* EOL */
XK_Begin = 0xff58, /* BOL */
/* Misc functions */
XK_Select = 0xff60, /* Select, mark */
XK_Print = 0xff61,
XK_Execute = 0xff62, /* Execute, run, do */
XK_Insert = 0xff63, /* Insert, insert here */
XK_Undo = 0xff65,
XK_Redo = 0xff66, /* Redo, again */
XK_Menu = 0xff67,
XK_Find = 0xff68, /* Find, search */
XK_Cancel = 0xff69, /* Cancel, stop, abort, exit */
XK_Help = 0xff6a, /* Help */
XK_Break = 0xff6b,
XK_Mode_switch = 0xff7e, /* Character set switch */
XK_script_switch = 0xff7e, /* Alias for mode_switch */
XK_Num_Lock = 0xff7f,
/* Keypad functions, keypad numbers cleverly chosen to map to ASCII */
XK_KP_Space = 0xff80, /* Space */
XK_KP_Tab = 0xff89,
XK_KP_Enter = 0xff8d, /* Enter */
XK_KP_F1 = 0xff91, /* PF1, KP_A, ... */
XK_KP_F2 = 0xff92,
XK_KP_F3 = 0xff93,
XK_KP_F4 = 0xff94,
XK_KP_Home = 0xff95,
XK_KP_Left = 0xff96,
XK_KP_Up = 0xff97,
XK_KP_Right = 0xff98,
XK_KP_Down = 0xff99,
XK_KP_Prior = 0xff9a,
XK_KP_Page_Up = 0xff9a
XK_KP_Next = 0xff9b,
XK_KP_Page_Down = 0xff9b,
XK_KP_End = 0xff9c,
XK_KP_Begin = 0xff9d,
XK_KP_Insert = 0xff9e,
XK_KP_Delete = 0xff9f,
XK_KP_Equal = 0xffbd, /* Equals */
XK_KP_Multiply = 0xffaa,
XK_KP_Add = 0xffab,
XK_KP_Separator = 0xffac, /* Separator, often comma */
XK_KP_Subtract = 0xffad,
XK_KP_Decimal = 0xffae,
XK_KP_Divide = 0xffaf,
XK_KP_0 = 0xffb0,
XK_KP_1 = 0xffb1,
XK_KP_2 = 0xffb2,
XK_KP_3 = 0xffb3,
XK_KP_4 = 0xffb4,
XK_KP_5 = 0xffb5,
XK_KP_6 = 0xffb6,
XK_KP_7 = 0xffb7,
XK_KP_8 = 0xffb8,
XK_KP_9 = 0xffb9,
/*
* Auxiliary functions; note the duplicate definitions for left and right
* function keys; Sun keyboards and a few other manufacturers have such
* function key groups on the left and/or right sides of the keyboard.
* We've not found a keyboard with more than 35 function keys total.
*/
XK_F1 = 0xffbe,
XK_F2 = 0xffbf,
XK_F3 = 0xffc0,
XK_F4 = 0xffc1,
XK_F5 = 0xffc2,
XK_F6 = 0xffc3,
XK_F7 = 0xffc4,
XK_F8 = 0xffc5,
XK_F9 = 0xffc6,
XK_F10 = 0xffc7,
XK_F11 = 0xffc8,
XK_L1 = 0xffc8,
XK_F12 = 0xffc9,
XK_L2 = 0xffc9,
XK_F13 = 0xffca,
XK_L3 = 0xffca,
XK_F14 = 0xffcb,
XK_L4 = 0xffcb,
XK_F15 = 0xffcc,
XK_L5 = 0xffcc,
XK_F16 = 0xffcd,
XK_L6 = 0xffcd,
XK_F17 = 0xffce,
XK_L7 = 0xffce,
XK_F18 = 0xffcf,
XK_L8 = 0xffcf,
XK_F19 = 0xffd0,
XK_L9 = 0xffd0,
XK_F20 = 0xffd1,
XK_L10 = 0xffd1,
XK_F21 = 0xffd2,
XK_R1 = 0xffd2,
XK_F22 = 0xffd3,
XK_R2 = 0xffd3,
XK_F23 = 0xffd4,
XK_R3 = 0xffd4,
XK_F24 = 0xffd5,
XK_R4 = 0xffd5,
XK_F25 = 0xffd6,
XK_R5 = 0xffd6,
XK_F26 = 0xffd7,
XK_R6 = 0xffd7,
XK_F27 = 0xffd8,
XK_R7 = 0xffd8,
XK_F28 = 0xffd9,
XK_R8 = 0xffd9,
XK_F29 = 0xffda,
XK_R9 = 0xffda,
XK_F30 = 0xffdb,
XK_R10 = 0xffdb,
XK_F31 = 0xffdc,
XK_R11 = 0xffdc,
XK_F32 = 0xffdd,
XK_R12 = 0xffdd,
XK_F33 = 0xffde,
XK_R13 = 0xffde,
XK_F34 = 0xffdf,
XK_R14 = 0xffdf,
XK_F35 = 0xffe0,
XK_R15 = 0xffe0,
/* Modifiers */
XK_Shift_L = 0xffe1, /* Left shift */
XK_Shift_R = 0xffe2, /* Right shift */
XK_Control_L = 0xffe3, /* Left control */
XK_Control_R = 0xffe4, /* Right control */
XK_Caps_Lock = 0xffe5, /* Caps lock */
XK_Shift_Lock = 0xffe6, /* Shift lock */
XK_Meta_L = 0xffe7, /* Left meta */
XK_Meta_R = 0xffe8, /* Right meta */
XK_Alt_L = 0xffe9, /* Left alt */
XK_Alt_R = 0xffea, /* Right alt */
XK_Super_L = 0xffeb, /* Left super */
XK_Super_R = 0xffec, /* Right super */
XK_Hyper_L = 0xffed, /* Left hyper */
XK_Hyper_R = 0xffee, /* Right hyper */
XK_ISO_Level3_Shift = 0xfe03, /* AltGr */
/*
* Latin 1
* (ISO/IEC 8859-1 = Unicode U+0020..U+00FF)
* Byte 3 = 0
*/
XK_space = 0x0020, /* U+0020 SPACE */
XK_exclam = 0x0021, /* U+0021 EXCLAMATION MARK */
XK_quotedbl = 0x0022, /* U+0022 QUOTATION MARK */
XK_numbersign = 0x0023, /* U+0023 NUMBER SIGN */
XK_dollar = 0x0024, /* U+0024 DOLLAR SIGN */
XK_percent = 0x0025, /* U+0025 PERCENT SIGN */
XK_ampersand = 0x0026, /* U+0026 AMPERSAND */
XK_apostrophe = 0x0027, /* U+0027 APOSTROPHE */
XK_quoteright = 0x0027, /* deprecated */
XK_parenleft = 0x0028, /* U+0028 LEFT PARENTHESIS */
XK_parenright = 0x0029, /* U+0029 RIGHT PARENTHESIS */
XK_asterisk = 0x002a, /* U+002A ASTERISK */
XK_plus = 0x002b, /* U+002B PLUS SIGN */
XK_comma = 0x002c, /* U+002C COMMA */
XK_minus = 0x002d, /* U+002D HYPHEN-MINUS */
XK_period = 0x002e, /* U+002E FULL STOP */
XK_slash = 0x002f, /* U+002F SOLIDUS */
XK_0 = 0x0030, /* U+0030 DIGIT ZERO */
XK_1 = 0x0031, /* U+0031 DIGIT ONE */
XK_2 = 0x0032, /* U+0032 DIGIT TWO */
XK_3 = 0x0033, /* U+0033 DIGIT THREE */
XK_4 = 0x0034, /* U+0034 DIGIT FOUR */
XK_5 = 0x0035, /* U+0035 DIGIT FIVE */
XK_6 = 0x0036, /* U+0036 DIGIT SIX */
XK_7 = 0x0037, /* U+0037 DIGIT SEVEN */
XK_8 = 0x0038, /* U+0038 DIGIT EIGHT */
XK_9 = 0x0039, /* U+0039 DIGIT NINE */
XK_colon = 0x003a, /* U+003A COLON */
XK_semicolon = 0x003b, /* U+003B SEMICOLON */
XK_less = 0x003c, /* U+003C LESS-THAN SIGN */
XK_equal = 0x003d, /* U+003D EQUALS SIGN */
XK_greater = 0x003e, /* U+003E GREATER-THAN SIGN */
XK_question = 0x003f, /* U+003F QUESTION MARK */
XK_at = 0x0040, /* U+0040 COMMERCIAL AT */
XK_A = 0x0041, /* U+0041 LATIN CAPITAL LETTER A */
XK_B = 0x0042, /* U+0042 LATIN CAPITAL LETTER B */
XK_C = 0x0043, /* U+0043 LATIN CAPITAL LETTER C */
XK_D = 0x0044, /* U+0044 LATIN CAPITAL LETTER D */
XK_E = 0x0045, /* U+0045 LATIN CAPITAL LETTER E */
XK_F = 0x0046, /* U+0046 LATIN CAPITAL LETTER F */
XK_G = 0x0047, /* U+0047 LATIN CAPITAL LETTER G */
XK_H = 0x0048, /* U+0048 LATIN CAPITAL LETTER H */
XK_I = 0x0049, /* U+0049 LATIN CAPITAL LETTER I */
XK_J = 0x004a, /* U+004A LATIN CAPITAL LETTER J */
XK_K = 0x004b, /* U+004B LATIN CAPITAL LETTER K */
XK_L = 0x004c, /* U+004C LATIN CAPITAL LETTER L */
XK_M = 0x004d, /* U+004D LATIN CAPITAL LETTER M */
XK_N = 0x004e, /* U+004E LATIN CAPITAL LETTER N */
XK_O = 0x004f, /* U+004F LATIN CAPITAL LETTER O */
XK_P = 0x0050, /* U+0050 LATIN CAPITAL LETTER P */
XK_Q = 0x0051, /* U+0051 LATIN CAPITAL LETTER Q */
XK_R = 0x0052, /* U+0052 LATIN CAPITAL LETTER R */
XK_S = 0x0053, /* U+0053 LATIN CAPITAL LETTER S */
XK_T = 0x0054, /* U+0054 LATIN CAPITAL LETTER T */
XK_U = 0x0055, /* U+0055 LATIN CAPITAL LETTER U */
XK_V = 0x0056, /* U+0056 LATIN CAPITAL LETTER V */
XK_W = 0x0057, /* U+0057 LATIN CAPITAL LETTER W */
XK_X = 0x0058, /* U+0058 LATIN CAPITAL LETTER X */
XK_Y = 0x0059, /* U+0059 LATIN CAPITAL LETTER Y */
XK_Z = 0x005a, /* U+005A LATIN CAPITAL LETTER Z */
XK_bracketleft = 0x005b, /* U+005B LEFT SQUARE BRACKET */
XK_backslash = 0x005c, /* U+005C REVERSE SOLIDUS */
XK_bracketright = 0x005d, /* U+005D RIGHT SQUARE BRACKET */
XK_asciicircum = 0x005e, /* U+005E CIRCUMFLEX ACCENT */
XK_underscore = 0x005f, /* U+005F LOW LINE */
XK_grave = 0x0060, /* U+0060 GRAVE ACCENT */
XK_quoteleft = 0x0060, /* deprecated */
XK_a = 0x0061, /* U+0061 LATIN SMALL LETTER A */
XK_b = 0x0062, /* U+0062 LATIN SMALL LETTER B */
XK_c = 0x0063, /* U+0063 LATIN SMALL LETTER C */
XK_d = 0x0064, /* U+0064 LATIN SMALL LETTER D */
XK_e = 0x0065, /* U+0065 LATIN SMALL LETTER E */
XK_f = 0x0066, /* U+0066 LATIN SMALL LETTER F */
XK_g = 0x0067, /* U+0067 LATIN SMALL LETTER G */
XK_h = 0x0068, /* U+0068 LATIN SMALL LETTER H */
XK_i = 0x0069, /* U+0069 LATIN SMALL LETTER I */
XK_j = 0x006a, /* U+006A LATIN SMALL LETTER J */
XK_k = 0x006b, /* U+006B LATIN SMALL LETTER K */
XK_l = 0x006c, /* U+006C LATIN SMALL LETTER L */
XK_m = 0x006d, /* U+006D LATIN SMALL LETTER M */
XK_n = 0x006e, /* U+006E LATIN SMALL LETTER N */
XK_o = 0x006f, /* U+006F LATIN SMALL LETTER O */
XK_p = 0x0070, /* U+0070 LATIN SMALL LETTER P */
XK_q = 0x0071, /* U+0071 LATIN SMALL LETTER Q */
XK_r = 0x0072, /* U+0072 LATIN SMALL LETTER R */
XK_s = 0x0073, /* U+0073 LATIN SMALL LETTER S */
XK_t = 0x0074, /* U+0074 LATIN SMALL LETTER T */
XK_u = 0x0075, /* U+0075 LATIN SMALL LETTER U */
XK_v = 0x0076, /* U+0076 LATIN SMALL LETTER V */
XK_w = 0x0077, /* U+0077 LATIN SMALL LETTER W */
XK_x = 0x0078, /* U+0078 LATIN SMALL LETTER X */
XK_y = 0x0079, /* U+0079 LATIN SMALL LETTER Y */
XK_z = 0x007a, /* U+007A LATIN SMALL LETTER Z */
XK_braceleft = 0x007b, /* U+007B LEFT CURLY BRACKET */
XK_bar = 0x007c, /* U+007C VERTICAL LINE */
XK_braceright = 0x007d, /* U+007D RIGHT CURLY BRACKET */
XK_asciitilde = 0x007e, /* U+007E TILDE */
XK_nobreakspace = 0x00a0, /* U+00A0 NO-BREAK SPACE */
XK_exclamdown = 0x00a1, /* U+00A1 INVERTED EXCLAMATION MARK */
XK_cent = 0x00a2, /* U+00A2 CENT SIGN */
XK_sterling = 0x00a3, /* U+00A3 POUND SIGN */
XK_currency = 0x00a4, /* U+00A4 CURRENCY SIGN */
XK_yen = 0x00a5, /* U+00A5 YEN SIGN */
XK_brokenbar = 0x00a6, /* U+00A6 BROKEN BAR */
XK_section = 0x00a7, /* U+00A7 SECTION SIGN */
XK_diaeresis = 0x00a8, /* U+00A8 DIAERESIS */
XK_copyright = 0x00a9, /* U+00A9 COPYRIGHT SIGN */
XK_ordfeminine = 0x00aa, /* U+00AA FEMININE ORDINAL INDICATOR */
XK_guillemotleft = 0x00ab, /* U+00AB LEFT-POINTING DOUBLE ANGLE QUOTATION MARK */
XK_notsign = 0x00ac, /* U+00AC NOT SIGN */
XK_hyphen = 0x00ad, /* U+00AD SOFT HYPHEN */
XK_registered = 0x00ae, /* U+00AE REGISTERED SIGN */
XK_macron = 0x00af, /* U+00AF MACRON */
XK_degree = 0x00b0, /* U+00B0 DEGREE SIGN */
XK_plusminus = 0x00b1, /* U+00B1 PLUS-MINUS SIGN */
XK_twosuperior = 0x00b2, /* U+00B2 SUPERSCRIPT TWO */
XK_threesuperior = 0x00b3, /* U+00B3 SUPERSCRIPT THREE */
XK_acute = 0x00b4, /* U+00B4 ACUTE ACCENT */
XK_mu = 0x00b5, /* U+00B5 MICRO SIGN */
XK_paragraph = 0x00b6, /* U+00B6 PILCROW SIGN */
XK_periodcentered = 0x00b7, /* U+00B7 MIDDLE DOT */
XK_cedilla = 0x00b8, /* U+00B8 CEDILLA */
XK_onesuperior = 0x00b9, /* U+00B9 SUPERSCRIPT ONE */
XK_masculine = 0x00ba, /* U+00BA MASCULINE ORDINAL INDICATOR */
XK_guillemotright = 0x00bb, /* U+00BB RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK */
XK_onequarter = 0x00bc, /* U+00BC VULGAR FRACTION ONE QUARTER */
XK_onehalf = 0x00bd, /* U+00BD VULGAR FRACTION ONE HALF */
XK_threequarters = 0x00be, /* U+00BE VULGAR FRACTION THREE QUARTERS */
XK_questiondown = 0x00bf, /* U+00BF INVERTED QUESTION MARK */
XK_Agrave = 0x00c0, /* U+00C0 LATIN CAPITAL LETTER A WITH GRAVE */
XK_Aacute = 0x00c1, /* U+00C1 LATIN CAPITAL LETTER A WITH ACUTE */
XK_Acircumflex = 0x00c2, /* U+00C2 LATIN CAPITAL LETTER A WITH CIRCUMFLEX */
XK_Atilde = 0x00c3, /* U+00C3 LATIN CAPITAL LETTER A WITH TILDE */
XK_Adiaeresis = 0x00c4, /* U+00C4 LATIN CAPITAL LETTER A WITH DIAERESIS */
XK_Aring = 0x00c5, /* U+00C5 LATIN CAPITAL LETTER A WITH RING ABOVE */
XK_AE = 0x00c6, /* U+00C6 LATIN CAPITAL LETTER AE */
XK_Ccedilla = 0x00c7, /* U+00C7 LATIN CAPITAL LETTER C WITH CEDILLA */
XK_Egrave = 0x00c8, /* U+00C8 LATIN CAPITAL LETTER E WITH GRAVE */
XK_Eacute = 0x00c9, /* U+00C9 LATIN CAPITAL LETTER E WITH ACUTE */
XK_Ecircumflex = 0x00ca, /* U+00CA LATIN CAPITAL LETTER E WITH CIRCUMFLEX */
XK_Ediaeresis = 0x00cb, /* U+00CB LATIN CAPITAL LETTER E WITH DIAERESIS */
XK_Igrave = 0x00cc, /* U+00CC LATIN CAPITAL LETTER I WITH GRAVE */
XK_Iacute = 0x00cd, /* U+00CD LATIN CAPITAL LETTER I WITH ACUTE */
XK_Icircumflex = 0x00ce, /* U+00CE LATIN CAPITAL LETTER I WITH CIRCUMFLEX */
XK_Idiaeresis = 0x00cf, /* U+00CF LATIN CAPITAL LETTER I WITH DIAERESIS */
XK_ETH = 0x00d0, /* U+00D0 LATIN CAPITAL LETTER ETH */
XK_Eth = 0x00d0, /* deprecated */
XK_Ntilde = 0x00d1, /* U+00D1 LATIN CAPITAL LETTER N WITH TILDE */
XK_Ograve = 0x00d2, /* U+00D2 LATIN CAPITAL LETTER O WITH GRAVE */
XK_Oacute = 0x00d3, /* U+00D3 LATIN CAPITAL LETTER O WITH ACUTE */
XK_Ocircumflex = 0x00d4, /* U+00D4 LATIN CAPITAL LETTER O WITH CIRCUMFLEX */
XK_Otilde = 0x00d5, /* U+00D5 LATIN CAPITAL LETTER O WITH TILDE */
XK_Odiaeresis = 0x00d6, /* U+00D6 LATIN CAPITAL LETTER O WITH DIAERESIS */
XK_multiply = 0x00d7, /* U+00D7 MULTIPLICATION SIGN */
XK_Oslash = 0x00d8, /* U+00D8 LATIN CAPITAL LETTER O WITH STROKE */
XK_Ooblique = 0x00d8, /* U+00D8 LATIN CAPITAL LETTER O WITH STROKE */
XK_Ugrave = 0x00d9, /* U+00D9 LATIN CAPITAL LETTER U WITH GRAVE */
XK_Uacute = 0x00da, /* U+00DA LATIN CAPITAL LETTER U WITH ACUTE */
XK_Ucircumflex = 0x00db, /* U+00DB LATIN CAPITAL LETTER U WITH CIRCUMFLEX */
XK_Udiaeresis = 0x00dc, /* U+00DC LATIN CAPITAL LETTER U WITH DIAERESIS */
XK_Yacute = 0x00dd, /* U+00DD LATIN CAPITAL LETTER Y WITH ACUTE */
XK_THORN = 0x00de, /* U+00DE LATIN CAPITAL LETTER THORN */
XK_Thorn = 0x00de, /* deprecated */
XK_ssharp = 0x00df, /* U+00DF LATIN SMALL LETTER SHARP S */
XK_agrave = 0x00e0, /* U+00E0 LATIN SMALL LETTER A WITH GRAVE */
XK_aacute = 0x00e1, /* U+00E1 LATIN SMALL LETTER A WITH ACUTE */
XK_acircumflex = 0x00e2, /* U+00E2 LATIN SMALL LETTER A WITH CIRCUMFLEX */
XK_atilde = 0x00e3, /* U+00E3 LATIN SMALL LETTER A WITH TILDE */
XK_adiaeresis = 0x00e4, /* U+00E4 LATIN SMALL LETTER A WITH DIAERESIS */
XK_aring = 0x00e5, /* U+00E5 LATIN SMALL LETTER A WITH RING ABOVE */
XK_ae = 0x00e6, /* U+00E6 LATIN SMALL LETTER AE */
XK_ccedilla = 0x00e7, /* U+00E7 LATIN SMALL LETTER C WITH CEDILLA */
XK_egrave = 0x00e8, /* U+00E8 LATIN SMALL LETTER E WITH GRAVE */
XK_eacute = 0x00e9, /* U+00E9 LATIN SMALL LETTER E WITH ACUTE */
XK_ecircumflex = 0x00ea, /* U+00EA LATIN SMALL LETTER E WITH CIRCUMFLEX */
XK_ediaeresis = 0x00eb, /* U+00EB LATIN SMALL LETTER E WITH DIAERESIS */
XK_igrave = 0x00ec, /* U+00EC LATIN SMALL LETTER I WITH GRAVE */
XK_iacute = 0x00ed, /* U+00ED LATIN SMALL LETTER I WITH ACUTE */
XK_icircumflex = 0x00ee, /* U+00EE LATIN SMALL LETTER I WITH CIRCUMFLEX */
XK_idiaeresis = 0x00ef, /* U+00EF LATIN SMALL LETTER I WITH DIAERESIS */
XK_eth = 0x00f0, /* U+00F0 LATIN SMALL LETTER ETH */
XK_ntilde = 0x00f1, /* U+00F1 LATIN SMALL LETTER N WITH TILDE */
XK_ograve = 0x00f2, /* U+00F2 LATIN SMALL LETTER O WITH GRAVE */
XK_oacute = 0x00f3, /* U+00F3 LATIN SMALL LETTER O WITH ACUTE */
XK_ocircumflex = 0x00f4, /* U+00F4 LATIN SMALL LETTER O WITH CIRCUMFLEX */
XK_otilde = 0x00f5, /* U+00F5 LATIN SMALL LETTER O WITH TILDE */
XK_odiaeresis = 0x00f6, /* U+00F6 LATIN SMALL LETTER O WITH DIAERESIS */
XK_division = 0x00f7, /* U+00F7 DIVISION SIGN */
XK_oslash = 0x00f8, /* U+00F8 LATIN SMALL LETTER O WITH STROKE */
XK_ooblique = 0x00f8, /* U+00F8 LATIN SMALL LETTER O WITH STROKE */
XK_ugrave = 0x00f9, /* U+00F9 LATIN SMALL LETTER U WITH GRAVE */
XK_uacute = 0x00fa, /* U+00FA LATIN SMALL LETTER U WITH ACUTE */
XK_ucircumflex = 0x00fb, /* U+00FB LATIN SMALL LETTER U WITH CIRCUMFLEX */
XK_udiaeresis = 0x00fc, /* U+00FC LATIN SMALL LETTER U WITH DIAERESIS */
XK_yacute = 0x00fd, /* U+00FD LATIN SMALL LETTER Y WITH ACUTE */
XK_thorn = 0x00fe, /* U+00FE LATIN SMALL LETTER THORN */
XK_ydiaeresis = 0x00ff; /* U+00FF LATIN SMALL LETTER Y WITH DIAERESIS */

15
include/keysymdef.js Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -7,15 +7,21 @@
* See README.md for usage and integration instructions.
*/
"use strict";
/*jslint white: false, browser: true */
/*global window, $D, Util, WebUtil, RFB, Display */
/* jslint white: false, browser: true */
/* global window, $D, Util, WebUtil, RFB, Display */
var resizeTimeout;
var UI;
// Load supporting scripts
window.onscriptsload = function () { UI.load(); };
window.onresize = function () {
(function () {
"use strict";
var resizeTimeout;
// Load supporting scripts
window.onscriptsload = function () { UI.load(); };
window.onload = function () { UI.keyboardinputReset(); };
window.onresize = function () {
// When the window has been resized, wait until the size remains
// the same for 0.5 seconds before sending the request for changing
// the resolution of the session
@ -23,48 +29,57 @@ window.onresize = function () {
resizeTimeout = setTimeout(function(){
UI.onresize();
}, 500);
};
};
Util.load_scripts(["webutil.js", "base64.js", "websock.js", "des.js",
"input.js", "display.js", "jsunzip.js", "rfb.js"]);
Util.load_scripts(["webutil.js", "base64.js", "websock.js", "des.js",
"keysymdef.js", "keyboard.js", "input.js", "display.js",
"jsunzip.js", "rfb.js", "keysym.js"]);
var UI = {
UI = {
rfb_state : 'loaded',
settingsOpen : false,
connSettingsOpen : false,
popupStatusOpen : false,
clipboardOpen: false,
keyboardVisible: false,
isTouchDevice: false,
rfb_state : 'loaded',
settingsOpen : false,
connSettingsOpen : false,
popupStatusOpen : false,
clipboardOpen: false,
keyboardVisible: false,
hideKeyboardTimeout: null,
lastKeyboardinput: null,
defaultKeyboardinputLen: 100,
extraKeysVisible: false,
ctrlOn: false,
altOn: false,
isTouchDevice: false,
// Setup rfb object, load settings from browser storage, then call
// UI.init to setup the UI/menus
load: function (callback) {
// Setup rfb object, load settings from browser storage, then call
// UI.init to setup the UI/menus
load: function (callback) {
WebUtil.initSettings(UI.start, callback);
},
},
onresize: function (callback) {
// Control-bar height: 36px +
// border height: 5px = 41px to be deducted from the height
UI.rfb.setDesktopSize(window.innerWidth, window.innerHeight - 41);
},
// Render default UI and initialize settings menu
start: function(callback) {
var html = '', i, sheet, sheets, llevels, port, autoconnect;
onresize: function (callback) {
var width = window.innerWidth;
var height = window.innerHeight;
var controlBarHeight = $D('noVNC-control-bar').scrollHeight;
var borderHeight = 5;
if (width !== undefined && height !== undefined)
UI.rfb.setDesktopSize(width, height - controlBarHeight - borderHeight);
},
// Render default UI and initialize settings menu
start: function(callback) {
UI.isTouchDevice = 'ontouchstart' in document.documentElement;
// Stylesheet selection dropdown
sheet = WebUtil.selectStylesheet();
sheets = WebUtil.getStylesheets();
var sheet = WebUtil.selectStylesheet();
var sheets = WebUtil.getStylesheets();
var i;
for (i = 0; i < sheets.length; i += 1) {
UI.addOption($D('noVNC_stylesheet'),sheets[i].title, sheets[i].title);
}
// Logging selection dropdown
llevels = ['error', 'warn', 'info', 'debug'];
var llevels = ['error', 'warn', 'info', 'debug'];
for (i = 0; i < llevels.length; i += 1) {
UI.addOption($D('noVNC_logging'),llevels[i], llevels[i]);
}
@ -80,7 +95,7 @@ start: function(callback) {
// if port == 80 (or 443) then it won't be present and should be
// set manually
port = window.location.port;
var port = window.location.port;
if (!port) {
if (window.location.protocol.substring(0,5) == 'https') {
port = 443;
@ -99,17 +114,17 @@ start: function(callback) {
UI.initSetting('cursor', !UI.isTouchDevice);
UI.initSetting('shared', true);
UI.initSetting('view_only', false);
UI.initSetting('connectTimeout', 2);
UI.initSetting('path', 'websockify');
UI.initSetting('repeaterID', '');
UI.rfb = RFB({'target': $D('noVNC_canvas'),
UI.rfb = new RFB({'target': $D('noVNC_canvas'),
'onUpdateState': UI.updateState,
'onXvpInit': UI.updateXvpVisualState,
'onClipboard': UI.clipReceive,
'onFBUComplete': UI.FBUComplete,
'onDesktopName': UI.updateDocumentTitle});
autoconnect = WebUtil.getQueryVar('autoconnect', false);
var autoconnect = WebUtil.getQueryVar('autoconnect', false);
if (autoconnect === 'true' || autoconnect == '1') {
autoconnect = true;
UI.connect();
@ -119,14 +134,6 @@ start: function(callback) {
UI.updateVisualState();
// Unfocus clipboard when over the VNC area
//$D('VNC_screen').onmousemove = function () {
// var keyboard = UI.rfb.get_keyboard();
// if ((! keyboard) || (! keyboard.get_focused())) {
// $D('VNC_clipboard_text').blur();
// }
// };
// Show mouse selector buttons on touch screen devices
if (UI.isTouchDevice) {
// Show mobile buttons
@ -162,7 +169,7 @@ start: function(callback) {
} );
// Show description by default when hosted at for kanaka.github.com
if (location.host === "kanaka.github.com") {
if (location.host === "kanaka.github.io") {
// Open the description dialog
$D('noVNC_description').style.display = "block";
} else {
@ -178,9 +185,9 @@ start: function(callback) {
if (typeof callback === "function") {
callback(UI.rfb);
}
},
},
addMouseHandlers: function() {
addMouseHandlers: function() {
// Setup interface handlers that can't be inline
$D("noVNC_view_drag_button").onclick = UI.setViewDrag;
$D("noVNC_mouse_button0").onclick = function () { UI.setMouseButton(1); };
@ -192,9 +199,19 @@ addMouseHandlers: function() {
$D("keyboardinput").oninput = UI.keyInput;
$D("keyboardinput").onblur = UI.keyInputBlur;
$D("showExtraKeysButton").onclick = UI.showExtraKeys;
$D("toggleCtrlButton").onclick = UI.toggleCtrl;
$D("toggleAltButton").onclick = UI.toggleAlt;
$D("sendTabButton").onclick = UI.sendTab;
$D("sendEscButton").onclick = UI.sendEsc;
$D("sendCtrlAltDelButton").onclick = UI.sendCtrlAltDel;
$D("xvpShutdownButton").onclick = UI.xvpShutdown;
$D("xvpRebootButton").onclick = UI.xvpReboot;
$D("xvpResetButton").onclick = UI.xvpReset;
$D("noVNC_status").onclick = UI.togglePopupStatusPanel;
$D("noVNC_popup_status_panel").onclick = UI.togglePopupStatusPanel;
$D("xvpButton").onclick = UI.toggleXvpPanel;
$D("clipboardButton").onclick = UI.toggleClipboardPanel;
$D("settingsButton").onclick = UI.toggleSettingsPanel;
$D("connectButton").onclick = UI.toggleConnectPanel;
@ -211,13 +228,13 @@ addMouseHandlers: function() {
$D("noVNC_apply").onclick = UI.settingsApply;
$D("noVNC_connect_button").onclick = UI.connect;
},
},
// Read form control compatible setting from cookie
getSetting: function(name) {
var val, ctrl = $D('noVNC_' + name);
val = WebUtil.readSetting(name);
if (val !== null && ctrl.type === 'checkbox') {
// Read form control compatible setting from cookie
getSetting: function(name) {
var ctrl = $D('noVNC_' + name);
var val = WebUtil.readSetting(name);
if (typeof val !== 'undefined' && val !== null && ctrl.type === 'checkbox') {
if (val.toString().toLowerCase() in {'0':1, 'no':1, 'false':1}) {
val = false;
} else {
@ -225,13 +242,12 @@ getSetting: function(name) {
}
}
return val;
},
},
// Update cookie and form control setting. If value is not set, then
// updates from control to current cookie setting.
updateSetting: function(name, value) {
// Update cookie and form control setting. If value is not set, then
// updates from control to current cookie setting.
updateSetting: function(name, value) {
var i, ctrl = $D('noVNC_' + name);
// Save the cookie for this session
if (typeof value !== 'undefined') {
WebUtil.writeSetting(name, value);
@ -240,11 +256,12 @@ updateSetting: function(name, value) {
// Update the settings control
value = UI.getSetting(name);
var ctrl = $D('noVNC_' + name);
if (ctrl.type === 'checkbox') {
ctrl.checked = value;
} else if (typeof ctrl.options !== 'undefined') {
for (i = 0; i < ctrl.options.length; i += 1) {
for (var i = 0; i < ctrl.options.length; i += 1) {
if (ctrl.options[i].value === value) {
ctrl.selectedIndex = i;
break;
@ -258,10 +275,10 @@ updateSetting: function(name, value) {
}
ctrl.value = value;
}
},
},
// Save control setting to cookie
saveSetting: function(name) {
// Save control setting to cookie
saveSetting: function(name) {
var val, ctrl = $D('noVNC_' + name);
if (ctrl.type === 'checkbox') {
val = ctrl.checked;
@ -273,31 +290,28 @@ saveSetting: function(name) {
WebUtil.writeSetting(name, val);
//Util.Debug("Setting saved '" + name + "=" + val + "'");
return val;
},
// Initial page load read/initialization of settings
initSetting: function(name, defVal) {
var val;
},
// Initial page load read/initialization of settings
initSetting: function(name, defVal) {
// Check Query string followed by cookie
val = WebUtil.getQueryVar(name);
var val = WebUtil.getQueryVar(name);
if (val === null) {
val = WebUtil.readSetting(name, defVal);
}
UI.updateSetting(name, val);
//Util.Debug("Setting '" + name + "' initialized to '" + val + "'");
return val;
},
},
// Force a setting to be a certain value
forceSetting: function(name, val) {
// Force a setting to be a certain value
forceSetting: function(name, val) {
UI.updateSetting(name, val);
return val;
},
},
// Show the popup status panel
togglePopupStatusPanel: function() {
// Show the popup status panel
togglePopupStatusPanel: function() {
var psp = $D('noVNC_popup_status_panel');
if (UI.popupStatusOpen === true) {
psp.style.display = "none";
@ -309,10 +323,10 @@ togglePopupStatusPanel: function() {
parseInt(window.getComputedStyle(psp, false).width)/2 -30 + "px";
UI.popupStatusOpen = true;
}
},
},
// Show the clipboard panel
toggleClipboardPanel: function() {
// Show the XVP panel
toggleXvpPanel: function() {
// Close the description panel
$D('noVNC_description').style.display = "none";
// Close settings if open
@ -328,6 +342,43 @@ toggleClipboardPanel: function() {
if (UI.popupStatusOpen === true) {
UI.togglePopupStatusPanel();
}
// Close clipboard panel if open
if (UI.clipboardOpen === true) {
UI.toggleClipboardPanel();
}
// Toggle XVP panel
if (UI.xvpOpen === true) {
$D('noVNC_xvp').style.display = "none";
$D('xvpButton').className = "noVNC_status_button";
UI.xvpOpen = false;
} else {
$D('noVNC_xvp').style.display = "block";
$D('xvpButton').className = "noVNC_status_button_selected";
UI.xvpOpen = true;
}
},
// Show the clipboard panel
toggleClipboardPanel: function() {
// Close the description panel
$D('noVNC_description').style.display = "none";
// Close settings if open
if (UI.settingsOpen === true) {
UI.settingsApply();
UI.closeSettingsMenu();
}
// Close connection settings if open
if (UI.connSettingsOpen === true) {
UI.toggleConnectPanel();
}
// Close popup status panel if open
if (UI.popupStatusOpen === true) {
UI.togglePopupStatusPanel();
}
// Close XVP panel if open
if (UI.xvpOpen === true) {
UI.toggleXvpPanel();
}
// Toggle Clipboard Panel
if (UI.clipboardOpen === true) {
$D('noVNC_clipboard').style.display = "none";
@ -338,10 +389,10 @@ toggleClipboardPanel: function() {
$D('clipboardButton').className = "noVNC_status_button_selected";
UI.clipboardOpen = true;
}
},
},
// Show the connection settings panel/menu
toggleConnectPanel: function() {
// Show the connection settings panel/menu
toggleConnectPanel: function() {
// Close the description panel
$D('noVNC_description').style.display = "none";
// Close connection settings if open
@ -358,6 +409,10 @@ toggleConnectPanel: function() {
if (UI.popupStatusOpen === true) {
UI.togglePopupStatusPanel();
}
// Close XVP panel if open
if (UI.xvpOpen === true) {
UI.toggleXvpPanel();
}
// Toggle Connection Panel
if (UI.connSettingsOpen === true) {
@ -373,12 +428,12 @@ toggleConnectPanel: function() {
UI.connSettingsOpen = true;
$D('noVNC_host').focus();
}
},
},
// Toggle the settings menu:
// On open, settings are refreshed from saved cookies.
// On close, settings are applied
toggleSettingsPanel: function() {
// Toggle the settings menu:
// On open, settings are refreshed from saved cookies.
// On close, settings are applied
toggleSettingsPanel: function() {
// Close the description panel
$D('noVNC_description').style.display = "none";
if (UI.settingsOpen) {
@ -396,7 +451,6 @@ toggleSettingsPanel: function() {
UI.updateSetting('clip');
UI.updateSetting('shared');
UI.updateSetting('view_only');
UI.updateSetting('connectTimeout');
UI.updateSetting('path');
UI.updateSetting('repeaterID');
UI.updateSetting('stylesheet');
@ -404,10 +458,10 @@ toggleSettingsPanel: function() {
UI.openSettingsMenu();
}
},
},
// Open menu
openSettingsMenu: function() {
// Open menu
openSettingsMenu: function() {
// Close the description panel
$D('noVNC_description').style.display = "none";
// Close clipboard panel if open
@ -422,20 +476,24 @@ openSettingsMenu: function() {
if (UI.popupStatusOpen === true) {
UI.togglePopupStatusPanel();
}
// Close XVP panel if open
if (UI.xvpOpen === true) {
UI.toggleXvpPanel();
}
$D('noVNC_settings').style.display = "block";
$D('settingsButton').className = "noVNC_status_button_selected";
UI.settingsOpen = true;
},
},
// Close menu (without applying settings)
closeSettingsMenu: function() {
// Close menu (without applying settings)
closeSettingsMenu: function() {
$D('noVNC_settings').style.display = "none";
$D('settingsButton').className = "noVNC_status_button";
UI.settingsOpen = false;
},
},
// Save/apply settings when 'Apply' button is pressed
settingsApply: function() {
// Save/apply settings when 'Apply' button is pressed
settingsApply: function() {
//Util.Debug(">> settingsApply");
UI.saveSetting('encrypt');
UI.saveSetting('true_color');
@ -445,7 +503,6 @@ settingsApply: function() {
UI.saveSetting('clip');
UI.saveSetting('shared');
UI.saveSetting('view_only');
UI.saveSetting('connectTimeout');
UI.saveSetting('path');
UI.saveSetting('repeaterID');
UI.saveSetting('stylesheet');
@ -457,11 +514,11 @@ settingsApply: function() {
UI.setViewClip();
UI.setViewDrag(UI.rfb.get_viewportDrag());
//Util.Debug("<< settingsApply");
},
},
setPassword: function() {
setPassword: function() {
UI.rfb.sendPassword($D('noVNC_password').value);
//Reset connect button.
$D('noVNC_connect_button').value = "Connect";
@ -469,15 +526,25 @@ setPassword: function() {
//Hide connection panel.
UI.toggleConnectPanel();
return false;
},
},
sendCtrlAltDel: function() {
sendCtrlAltDel: function() {
UI.rfb.sendCtrlAltDel();
},
},
setMouseButton: function(num) {
var b, blist = [0, 1,2,4], button;
xvpShutdown: function() {
UI.rfb.xvpShutdown();
},
xvpReboot: function() {
UI.rfb.xvpReboot();
},
xvpReset: function() {
UI.rfb.xvpReset();
},
setMouseButton: function(num) {
if (typeof num === 'undefined') {
// Disable mouse buttons
num = -1;
@ -486,25 +553,20 @@ setMouseButton: function(num) {
UI.rfb.get_mouse().set_touchButton(num);
}
for (b = 0; b < blist.length; b++) {
button = $D('noVNC_mouse_button' + blist[b]);
var blist = [0, 1,2,4];
for (var b = 0; b < blist.length; b++) {
var button = $D('noVNC_mouse_button' + blist[b]);
if (blist[b] === num) {
button.style.display = "";
} else {
button.style.display = "none";
/*
button.style.backgroundColor = "black";
button.style.color = "lightgray";
button.style.backgroundColor = "";
button.style.color = "";
*/
}
}
},
},
updateState: function(rfb, state, oldstate, msg) {
var s, sb, c, d, cad, vd, klass;
updateState: function(rfb, state, oldstate, msg) {
UI.rfb_state = state;
var klass;
switch (state) {
case 'failed':
case 'fatal':
@ -515,7 +577,7 @@ updateState: function(rfb, state, oldstate, msg) {
break;
case 'disconnected':
$D('noVNC_logo').style.display = "block";
// Fall through
/* falls through */
case 'loaded':
klass = "noVNC_status_normal";
break;
@ -539,10 +601,10 @@ updateState: function(rfb, state, oldstate, msg) {
}
UI.updateVisualState();
},
},
// Disable/enable controls depending on connection state
updateVisualState: function() {
// Disable/enable controls depending on connection state
updateVisualState: function() {
var connected = UI.rfb_state === 'normal' ? true : false;
//Util.Debug(">> updateVisualState");
@ -557,7 +619,6 @@ updateVisualState: function() {
}
$D('noVNC_shared').disabled = connected;
$D('noVNC_view_only').disabled = connected;
$D('noVNC_connectTimeout').disabled = connected;
$D('noVNC_path').disabled = connected;
$D('noVNC_repeaterID').disabled = connected;
@ -566,13 +627,17 @@ updateVisualState: function() {
UI.setMouseButton(1);
$D('clipboardButton').style.display = "inline";
$D('showKeyboard').style.display = "inline";
$D('noVNC_extra_keys').style.display = "";
$D('sendCtrlAltDelButton').style.display = "inline";
} else {
UI.setMouseButton();
$D('clipboardButton').style.display = "none";
$D('showKeyboard').style.display = "none";
$D('noVNC_extra_keys').style.display = "none";
$D('sendCtrlAltDelButton').style.display = "none";
UI.updateXvpVisualState(0);
}
// State change disables viewport dragging.
// It is enabled (toggled) by direct click on the button
UI.setViewDrag(false);
@ -592,45 +657,52 @@ updateVisualState: function() {
}
//Util.Debug("<< updateVisualState");
},
},
// Disable/enable XVP button
updateXvpVisualState: function(ver) {
if (ver >= 1) {
$D('xvpButton').style.display = 'inline';
} else {
$D('xvpButton').style.display = 'none';
// Close XVP panel if open
if (UI.xvpOpen === true) {
UI.toggleXvpPanel();
}
}
},
// This resize can not be done until we know from the first Frame Buffer Update
// if it is supported or not.
// The resize is needed to make sure the server desktop size is updated to the
// corresponding size of the current local window when reconnecting to an
// existing session.
FBUComplete: function(rfb, fbu) {
// This resize can not be done until we know from the first Frame Buffer Update
// if it is supported or not.
// The resize is needed to make sure the server desktop size is updated to the
// corresponding size of the current local window when reconnecting to an
// existing session.
FBUComplete: function(rfb, fbu) {
onresize();
UI.rfb.set_onFBUComplete(function() { });
},
},
// Display the desktop name in the document title
updateDocumentTitle: function(rfb, name) {
// Display the desktop name in the document title
updateDocumentTitle: function(rfb, name) {
document.title = name + " - noVNC";
},
},
clipReceive: function(rfb, text) {
clipReceive: function(rfb, text) {
Util.Debug(">> UI.clipReceive: " + text.substr(0,40) + "...");
$D('noVNC_clipboard_text').value = text;
Util.Debug("<< UI.clipReceive");
},
connect: function() {
var host, port, password, path;
},
connect: function() {
UI.closeSettingsMenu();
UI.toggleConnectPanel();
host = $D('noVNC_host').value;
port = $D('noVNC_port').value;
password = $D('noVNC_password').value;
path = $D('noVNC_path').value;
var host = $D('noVNC_host').value;
var port = $D('noVNC_port').value;
var password = $D('noVNC_password').value;
var path = $D('noVNC_path').value;
if ((!host) || (!port)) {
throw("Must set host and port");
throw new Error("Must set host and port");
}
UI.rfb.set_encrypt(UI.getSetting('encrypt'));
@ -638,7 +710,6 @@ connect: function() {
UI.rfb.set_local_cursor(UI.getSetting('cursor'));
UI.rfb.set_shared(UI.getSetting('shared'));
UI.rfb.set_view_only(UI.getSetting('view_only'));
UI.rfb.set_connectTimeout(UI.getSetting('connectTimeout'));
UI.rfb.set_repeaterID(UI.getSetting('repeaterID'));
UI.rfb.connect(host, port, password, path);
@ -646,51 +717,49 @@ connect: function() {
//Close dialog.
setTimeout(UI.setBarPosition, 100);
$D('noVNC_logo').style.display = "none";
},
},
disconnect: function() {
disconnect: function() {
UI.closeSettingsMenu();
UI.rfb.disconnect();
$D('noVNC_logo').style.display = "block";
UI.connSettingsOpen = false;
UI.toggleConnectPanel();
},
},
displayBlur: function() {
displayBlur: function() {
UI.rfb.get_keyboard().set_focused(false);
UI.rfb.get_mouse().set_focused(false);
},
},
displayFocus: function() {
displayFocus: function() {
UI.rfb.get_keyboard().set_focused(true);
UI.rfb.get_mouse().set_focused(true);
},
},
clipClear: function() {
clipClear: function() {
$D('noVNC_clipboard_text').value = "";
UI.rfb.clipboardPasteFrom("");
},
},
clipSend: function() {
clipSend: function() {
var text = $D('noVNC_clipboard_text').value;
Util.Debug(">> UI.clipSend: " + text.substr(0,40) + "...");
UI.rfb.clipboardPasteFrom(text);
Util.Debug("<< UI.clipSend");
},
// Enable/disable and configure viewport clipping
setViewClip: function(clip) {
var display, cur_clip, pos, new_w, new_h;
},
// Enable/disable and configure viewport clipping
setViewClip: function(clip) {
var display;
if (UI.rfb) {
display = UI.rfb.get_display();
} else {
return;
}
cur_clip = display.get_viewport();
var cur_clip = display.get_viewport();
if (typeof(clip) !== 'boolean') {
// Use current setting
@ -710,16 +779,16 @@ setViewClip: function(clip) {
if (UI.getSetting('clip')) {
// If clipping, update clipping settings
$D('noVNC_canvas').style.position = 'absolute';
pos = Util.getPosition($D('noVNC_canvas'));
new_w = window.innerWidth - pos.x;
new_h = window.innerHeight - pos.y;
var pos = Util.getPosition($D('noVNC_canvas'));
var new_w = window.innerWidth - pos.x;
var new_h = window.innerHeight - pos.y;
display.set_viewport(true);
display.viewportChange(0, 0, new_w, new_h);
display.resize(new_w, new_h);
}
},
},
// Toggle/set/unset the viewport drag/move button
setViewDrag: function(drag) {
// Toggle/set/unset the viewport drag/move button
setViewDrag: function(drag) {
var vmb = $D('noVNC_view_drag_button');
if (!UI.rfb) { return; }
@ -742,17 +811,17 @@ setViewDrag: function(drag) {
vmb.className = "noVNC_status_button";
UI.rfb.set_viewportDrag(false);
}
},
},
// On touch devices, show the OS keyboard
showKeyboard: function() {
var kbi, skb, l;
kbi = $D('keyboardinput');
skb = $D('showKeyboard');
l = kbi.value.length;
// On touch devices, show the OS keyboard
showKeyboard: function() {
var kbi = $D('keyboardinput');
var skb = $D('showKeyboard');
var l = kbi.value.length;
if(UI.keyboardVisible === false) {
kbi.focus();
kbi.setSelectionRange(l, l); // Move the caret to the end
try { kbi.setSelectionRange(l, l); } // Move the caret to the end
catch (err) {} // setSelectionRange is undefined in Google Chrome
UI.keyboardVisible = true;
skb.className = "noVNC_status_button_selected";
} else if(UI.keyboardVisible === true) {
@ -760,81 +829,184 @@ showKeyboard: function() {
skb.className = "noVNC_status_button";
UI.keyboardVisible = false;
}
},
},
// When keypress events are left uncought, catch the input events from
// the keyboardinput element instead and send the corresponding key events.
keyInput: function(event) {
var elem, input, len;
elem = $D('keyboardinput');
input = event.target.value;
len = (elem.selectionStart > input.length) ? elem.selectionStart : input.length;
keepKeyboard: function() {
clearTimeout(UI.hideKeyboardTimeout);
if(UI.keyboardVisible === true) {
$D('keyboardinput').focus();
$D('showKeyboard').className = "noVNC_status_button_selected";
} else if(UI.keyboardVisible === false) {
$D('keyboardinput').blur();
$D('showKeyboard').className = "noVNC_status_button";
}
},
if (len < 1) { // something removed?
UI.rfb.sendKey(0xff08); // send BACKSPACE
} else if (len > 1) { // new input?
for (var i = len-1; i > 0; i -= 1) {
// HTML does not consider trailing whitespaces as a part of the string
// and they are therefore undefined.
if (input[len-i] !== undefined) {
UI.rfb.sendKey(input.charCodeAt(len-i)); // send charCode
keyboardinputReset: function() {
var kbi = $D('keyboardinput');
kbi.value = new Array(UI.defaultKeyboardinputLen).join("_");
UI.lastKeyboardinput = kbi.value;
},
// When normal keyboard events are left uncought, use the input events from
// the keyboardinput element instead and generate the corresponding key events.
// This code is required since some browsers on Android are inconsistent in
// sending keyCodes in the normal keyboard events when using on screen keyboards.
keyInput: function(event) {
var newValue = event.target.value;
var oldValue = UI.lastKeyboardinput;
var newLen;
try {
// Try to check caret position since whitespace at the end
// will not be considered by value.length in some browsers
newLen = Math.max(event.target.selectionStart, newValue.length);
} catch (err) {
// selectionStart is undefined in Google Chrome
newLen = newValue.length;
}
var oldLen = oldValue.length;
var backspaces;
var inputs = newLen - oldLen;
if (inputs < 0) {
backspaces = -inputs;
} else {
UI.rfb.sendKey(0x0020); // send SPACE
backspaces = 0;
}
// Compare the old string with the new to account for
// text-corrections or other input that modify existing text
var i;
for (i = 0; i < Math.min(oldLen, newLen); i++) {
if (newValue.charAt(i) != oldValue.charAt(i)) {
inputs = newLen - i;
backspaces = oldLen - i;
break;
}
}
// In order to be able to delete text which has been written in
// another session there has to always be text in the
// keyboardinput element with which backspace can interact.
// We also need to reset the input field text to avoid overflow.
elem.value = "x";
},
// Send the key events
for (i = 0; i < backspaces; i++) {
UI.rfb.sendKey(XK_BackSpace);
}
for (i = newLen - inputs; i < newLen; i++) {
UI.rfb.sendKey(newValue.charCodeAt(i));
}
keyInputBlur: function() {
// Control the text content length in the keyboardinput element
if (newLen > 2 * UI.defaultKeyboardinputLen) {
UI.keyboardinputReset();
} else if (newLen < 1) {
// There always have to be some text in the keyboardinput
// element with which backspace can interact.
UI.keyboardinputReset();
// This sometimes causes the keyboard to disappear for a second
// but it is required for the android keyboard to recognize that
// text has been added to the field
event.target.blur();
// This has to be ran outside of the input handler in order to work
setTimeout(function() { UI.keepKeyboard(); }, 0);
} else {
UI.lastKeyboardinput = newValue;
}
},
keyInputBlur: function() {
$D('showKeyboard').className = "noVNC_status_button";
//Weird bug in iOS if you change keyboardVisible
//here it does not actually occur so next time
//you click keyboard icon it doesnt work.
setTimeout(function() { UI.setKeyboard(); },100);
},
UI.hideKeyboardTimeout = setTimeout(function() { UI.setKeyboard(); },100);
},
setKeyboard: function() {
showExtraKeys: function() {
UI.keepKeyboard();
if(UI.extraKeysVisible === false) {
$D('toggleCtrlButton').style.display = "inline";
$D('toggleAltButton').style.display = "inline";
$D('sendTabButton').style.display = "inline";
$D('sendEscButton').style.display = "inline";
$D('showExtraKeysButton').className = "noVNC_status_button_selected";
UI.extraKeysVisible = true;
} else if(UI.extraKeysVisible === true) {
$D('toggleCtrlButton').style.display = "";
$D('toggleAltButton').style.display = "";
$D('sendTabButton').style.display = "";
$D('sendEscButton').style.display = "";
$D('showExtraKeysButton').className = "noVNC_status_button";
UI.extraKeysVisible = false;
}
},
toggleCtrl: function() {
UI.keepKeyboard();
if(UI.ctrlOn === false) {
UI.rfb.sendKey(XK_Control_L, true);
$D('toggleCtrlButton').className = "noVNC_status_button_selected";
UI.ctrlOn = true;
} else if(UI.ctrlOn === true) {
UI.rfb.sendKey(XK_Control_L, false);
$D('toggleCtrlButton').className = "noVNC_status_button";
UI.ctrlOn = false;
}
},
toggleAlt: function() {
UI.keepKeyboard();
if(UI.altOn === false) {
UI.rfb.sendKey(XK_Alt_L, true);
$D('toggleAltButton').className = "noVNC_status_button_selected";
UI.altOn = true;
} else if(UI.altOn === true) {
UI.rfb.sendKey(XK_Alt_L, false);
$D('toggleAltButton').className = "noVNC_status_button";
UI.altOn = false;
}
},
sendTab: function() {
UI.keepKeyboard();
UI.rfb.sendKey(XK_Tab);
},
sendEsc: function() {
UI.keepKeyboard();
UI.rfb.sendKey(XK_Escape);
},
setKeyboard: function() {
UI.keyboardVisible = false;
},
},
// iOS < Version 5 does not support position fixed. Javascript workaround:
setOnscroll: function() {
// iOS < Version 5 does not support position fixed. Javascript workaround:
setOnscroll: function() {
window.onscroll = function() {
UI.setBarPosition();
};
},
},
setResize: function () {
setResize: function () {
window.onResize = function() {
UI.setBarPosition();
};
},
},
//Helper to add options to dropdown.
addOption: function(selectbox,text,value )
{
//Helper to add options to dropdown.
addOption: function(selectbox, text, value) {
var optn = document.createElement("OPTION");
optn.text = text;
optn.value = value;
selectbox.options.add(optn);
},
},
setBarPosition: function() {
setBarPosition: function() {
$D('noVNC-control-bar').style.top = (window.pageYOffset) + 'px';
$D('noVNC_mobile_buttons').style.left = (window.pageXOffset) + 'px';
var vncwidth = $D('noVNC_screen').style.offsetWidth;
$D('noVNC-control-bar').style.width = vncwidth + 'px';
}
};
}
};
})();

View File

@ -6,9 +6,8 @@
* See README.md for usage and integration instructions.
*/
"use strict";
/*jslint bitwise: false, white: false */
/*global window, console, document, navigator, ActiveXObject */
/* jshint white: false, nonstandard: true */
/*global window, console, document, navigator, ActiveXObject, INCLUDE_URI */
// Globals defined here
var Util = {};
@ -18,56 +17,158 @@ var Util = {};
* Make arrays quack
*/
Array.prototype.push8 = function (num) {
this.push(num & 0xFF);
var addFunc = function (cl, name, func) {
if (!cl.prototype[name]) {
Object.defineProperty(cl.prototype, name, { enumerable: false, value: func });
}
};
Array.prototype.push16 = function (num) {
addFunc(Array, 'push8', function (num) {
"use strict";
this.push(num & 0xFF);
});
addFunc(Array, 'push16', function (num) {
"use strict";
this.push((num >> 8) & 0xFF,
(num ) & 0xFF );
};
Array.prototype.push32 = function (num) {
num & 0xFF);
});
addFunc(Array, 'push32', function (num) {
"use strict";
this.push((num >> 24) & 0xFF,
(num >> 16) & 0xFF,
(num >> 8) & 0xFF,
(num ) & 0xFF );
};
num & 0xFF);
});
// IE does not support map (even in IE9)
//This prototype is provided by the Mozilla foundation and
//is distributed under the MIT license.
//http://www.ibiblio.org/pub/Linux/LICENSES/mit.license
if (!Array.prototype.map)
{
Array.prototype.map = function(fun /*, thisp*/)
{
addFunc(Array, 'map', function (fun /*, thisp*/) {
"use strict";
var len = this.length;
if (typeof fun != "function")
if (typeof fun != "function") {
throw new TypeError();
}
var res = new Array(len);
var thisp = arguments[1];
for (var i = 0; i < len; i++)
{
if (i in this)
for (var i = 0; i < len; i++) {
if (i in this) {
res[i] = fun.call(thisp, this[i], i, this);
}
}
return res;
});
// IE <9 does not support indexOf
//This prototype is provided by the Mozilla foundation and
//is distributed under the MIT license.
//http://www.ibiblio.org/pub/Linux/LICENSES/mit.license
addFunc(Array, 'indexOf', function (elt /*, from*/) {
"use strict";
var len = this.length >>> 0;
var from = Number(arguments[1]) || 0;
from = (from < 0) ? Math.ceil(from) : Math.floor(from);
if (from < 0) {
from += len;
}
for (; from < len; from++) {
if (from in this &&
this[from] === elt) {
return from;
}
}
return -1;
});
// From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys
if (!Object.keys) {
Object.keys = (function () {
'use strict';
var hasOwnProperty = Object.prototype.hasOwnProperty,
hasDontEnumBug = !({toString: null}).propertyIsEnumerable('toString'),
dontEnums = [
'toString',
'toLocaleString',
'valueOf',
'hasOwnProperty',
'isPrototypeOf',
'propertyIsEnumerable',
'constructor'
],
dontEnumsLength = dontEnums.length;
return function (obj) {
if (typeof obj !== 'object' && (typeof obj !== 'function' || obj === null)) {
throw new TypeError('Object.keys called on non-object');
}
var result = [], prop, i;
for (prop in obj) {
if (hasOwnProperty.call(obj, prop)) {
result.push(prop);
}
}
if (hasDontEnumBug) {
for (i = 0; i < dontEnumsLength; i++) {
if (hasOwnProperty.call(obj, dontEnums[i])) {
result.push(dontEnums[i]);
}
}
}
return result;
};
})();
}
// PhantomJS 1.x doesn't support bind,
// so leave this in until PhantomJS 2.0 is released
//This prototype is provided by the Mozilla foundation and
//is distributed under the MIT license.
//http://www.ibiblio.org/pub/Linux/LICENSES/mit.license
addFunc(Function, 'bind', function (oThis) {
if (typeof this !== "function") {
// closest thing possible to the ECMAScript 5
// internal IsCallable function
throw new TypeError("Function.prototype.bind - " +
"what is trying to be bound is not callable");
}
var aArgs = Array.prototype.slice.call(arguments, 1),
fToBind = this,
fNOP = function () {},
fBound = function () {
return fToBind.apply(this instanceof fNOP && oThis ? this
: oThis,
aArgs.concat(Array.prototype.slice.call(arguments)));
};
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
});
//
// requestAnimationFrame shim with setTimeout fallback
//
window.requestAnimFrame = (function(){
window.requestAnimFrame = (function () {
"use strict";
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(callback){
function (callback) {
window.setTimeout(callback, 1000 / 60);
};
})();
@ -84,6 +185,7 @@ window.requestAnimFrame = (function(){
Util._log_level = 'warn';
Util.init_logging = function (level) {
"use strict";
if (typeof level === 'undefined') {
level = Util._log_level;
} else {
@ -94,26 +196,34 @@ Util.init_logging = function (level) {
window.console = {
'log' : window.opera.postError,
'warn' : window.opera.postError,
'error': window.opera.postError };
'error': window.opera.postError
};
} else {
window.console = {
'log' : function(m) {},
'warn' : function(m) {},
'error': function(m) {}};
'log' : function (m) {},
'warn' : function (m) {},
'error': function (m) {}
};
}
}
Util.Debug = Util.Info = Util.Warn = Util.Error = function (msg) {};
/* jshint -W086 */
switch (level) {
case 'debug': Util.Debug = function (msg) { console.log(msg); };
case 'info': Util.Info = function (msg) { console.log(msg); };
case 'warn': Util.Warn = function (msg) { console.warn(msg); };
case 'error': Util.Error = function (msg) { console.error(msg); };
case 'debug':
Util.Debug = function (msg) { console.log(msg); };
case 'info':
Util.Info = function (msg) { console.log(msg); };
case 'warn':
Util.Warn = function (msg) { console.warn(msg); };
case 'error':
Util.Error = function (msg) { console.error(msg); };
case 'none':
break;
default:
throw("invalid logging type '" + level + "'");
throw new Error("invalid logging type '" + level + "'");
}
/* jshint +W086 */
};
Util.get_logging = function () {
return Util._log_level;
@ -121,87 +231,139 @@ Util.get_logging = function () {
// Initialize logging level
Util.init_logging();
Util.make_property = function (proto, name, mode, type) {
"use strict";
// Set configuration default for Crockford style function namespaces
Util.conf_default = function(cfg, api, defaults, v, mode, type, defval, desc) {
var getter, setter;
// Default getter function
var getter;
if (type === 'arr') {
getter = function (idx) {
if ((type in {'arr':1, 'array':1}) &&
(typeof idx !== 'undefined')) {
return cfg[v][idx];
} else {
return cfg[v];
}
};
// Default setter function
setter = function (val, idx) {
if (type in {'boolean':1, 'bool':1}) {
if ((!val) || (val in {'0':1, 'no':1, 'false':1})) {
val = false;
} else {
val = true;
}
} else if (type in {'integer':1, 'int':1}) {
val = parseInt(val, 10);
} else if (type === 'str') {
val = String(val);
} else if (type === 'func') {
if (!val) {
val = function () {};
}
}
if (typeof idx !== 'undefined') {
cfg[v][idx] = val;
return this['_' + name][idx];
} else {
cfg[v] = val;
return this['_' + name];
}
};
// Set the description
api[v + '_description'] = desc;
// Set the getter function
if (typeof api['get_' + v] === 'undefined') {
api['get_' + v] = getter;
}
// Set the setter function with extra sanity checks
if (typeof api['set_' + v] === 'undefined') {
api['set_' + v] = function (val, idx) {
if (mode in {'RO':1, 'ro':1}) {
throw(v + " is read-only");
} else if ((mode in {'WO':1, 'wo':1}) &&
(typeof cfg[v] !== 'undefined')) {
throw(v + " can only be set once");
}
setter(val, idx);
} else {
getter = function () {
return this['_' + name];
};
}
// Set the default value
if (typeof defaults[v] !== 'undefined') {
defval = defaults[v];
} else if ((type in {'arr':1, 'array':1}) &&
(! (defval instanceof Array))) {
defval = [];
var make_setter = function (process_val) {
if (process_val) {
return function (val, idx) {
if (typeof idx !== 'undefined') {
this['_' + name][idx] = process_val(val);
} else {
this['_' + name] = process_val(val);
}
// Coerce existing setting to the right type
//Util.Debug("v: " + v + ", defval: " + defval + ", defaults[v]: " + defaults[v]);
setter(defval);
};
} else {
return function (val, idx) {
if (typeof idx !== 'undefined') {
this['_' + name][idx] = val;
} else {
this['_' + name] = val;
}
};
}
};
var setter;
if (type === 'bool') {
setter = make_setter(function (val) {
if (!val || (val in {'0': 1, 'no': 1, 'false': 1})) {
return false;
} else {
return true;
}
});
} else if (type === 'int') {
setter = make_setter(function (val) { return parseInt(val, 10); });
} else if (type === 'float') {
setter = make_setter(parseFloat);
} else if (type === 'str') {
setter = make_setter(String);
} else if (type === 'func') {
setter = make_setter(function (val) {
if (!val) {
return function () {};
} else {
return val;
}
});
} else if (type === 'arr' || type === 'dom' || type == 'raw') {
setter = make_setter();
} else {
throw new Error('Unknown property type ' + type); // some sanity checking
}
// set the getter
if (typeof proto['get_' + name] === 'undefined') {
proto['get_' + name] = getter;
}
// set the setter if needed
if (typeof proto['set_' + name] === 'undefined') {
if (mode === 'rw') {
proto['set_' + name] = setter;
} else if (mode === 'wo') {
proto['set_' + name] = function (val, idx) {
if (typeof this['_' + name] !== 'undefined') {
throw new Error(name + " can only be set once");
}
setter.call(this, val, idx);
};
}
}
// make a special setter that we can use in set defaults
proto['_raw_set_' + name] = function (val, idx) {
setter.call(this, val, idx);
//delete this['_init_set_' + name]; // remove it after use
};
};
// Set group of configuration defaults
Util.conf_defaults = function(cfg, api, defaults, arr) {
Util.make_properties = function (constructor, arr) {
"use strict";
for (var i = 0; i < arr.length; i++) {
Util.make_property(constructor.prototype, arr[i][0], arr[i][1], arr[i][2]);
}
};
Util.set_defaults = function (obj, conf, defaults) {
var defaults_keys = Object.keys(defaults);
var conf_keys = Object.keys(conf);
var keys_obj = {};
var i;
for (i = 0; i < arr.length; i++) {
Util.conf_default(cfg, api, defaults, arr[i][0], arr[i][1],
arr[i][2], arr[i][3], arr[i][4]);
for (i = 0; i < defaults_keys.length; i++) { keys_obj[defaults_keys[i]] = 1; }
for (i = 0; i < conf_keys.length; i++) { keys_obj[conf_keys[i]] = 1; }
var keys = Object.keys(keys_obj);
for (i = 0; i < keys.length; i++) {
var setter = obj['_raw_set_' + keys[i]];
if (!setter) {
Util.Warn('Invalid property ' + keys[i]);
continue;
}
if (keys[i] in conf) {
setter.call(obj, conf[keys[i]]);
} else {
setter.call(obj, defaults[keys[i]]);
}
}
};
/*
* Decode from UTF-8
*/
Util.decodeUTF8 = function (utf8string) {
"use strict";
return decodeURIComponent(escape(utf8string));
};
/*
* Cross-browser routines
@ -214,20 +376,17 @@ Util.conf_defaults = function(cfg, api, defaults, arr) {
// Handles the case where load_scripts is invoked from a script that
// itself is loaded via load_scripts. Once all scripts are loaded the
// window.onscriptsloaded handler is called (if set).
Util.get_include_uri = function() {
Util.get_include_uri = function () {
return (typeof INCLUDE_URI !== "undefined") ? INCLUDE_URI : "include/";
}
};
Util._loading_scripts = [];
Util._pending_scripts = [];
Util.load_scripts = function(files) {
Util.load_scripts = function (files) {
"use strict";
var head = document.getElementsByTagName('head')[0], script,
ls = Util._loading_scripts, ps = Util._pending_scripts;
for (var f=0; f<files.length; f++) {
script = document.createElement('script');
script.type = 'text/javascript';
script.src = Util.get_include_uri() + files[f];
//console.log("loading script: " + script.src);
script.onload = script.onreadystatechange = function (e) {
var loadFunc = function (e) {
while (ls.length > 0 && (ls[0].readyState === 'loaded' ||
ls[0].readyState === 'complete')) {
// For IE, append the script to trigger execution
@ -250,6 +409,13 @@ Util.load_scripts = function(files) {
}
}
};
for (var f = 0; f < files.length; f++) {
script = document.createElement('script');
script.type = 'text/javascript';
script.src = Util.get_include_uri() + files[f];
//console.log("loading script: " + script.src);
script.onload = script.onreadystatechange = loadFunc;
// In-order script execution tricks
if (Util.Engine.trident) {
// For IE wait until readyState is 'loaded' before
@ -264,23 +430,80 @@ Util.load_scripts = function(files) {
}
ps.push(script);
}
}
};
// Get DOM element position on page
Util.getPosition = function (obj) {
var x = 0, y = 0;
if (obj.offsetParent) {
do {
x += obj.offsetLeft;
y += obj.offsetTop;
obj = obj.offsetParent;
} while (obj);
// This solution is based based on http://www.greywyvern.com/?post=331
// Thanks to Brian Huisman AKA GreyWyvern!
Util.getPosition = (function () {
"use strict";
function getStyle(obj, styleProp) {
var y;
if (obj.currentStyle) {
y = obj.currentStyle[styleProp];
} else if (window.getComputedStyle)
y = window.getComputedStyle(obj, null)[styleProp];
return y;
}
return {'x': x, 'y': y};
};
function scrollDist() {
var myScrollTop = 0, myScrollLeft = 0;
var html = document.getElementsByTagName('html')[0];
// get the scrollTop part
if (html.scrollTop && document.documentElement.scrollTop) {
myScrollTop = html.scrollTop;
} else if (html.scrollTop || document.documentElement.scrollTop) {
myScrollTop = html.scrollTop + document.documentElement.scrollTop;
} else if (document.body.scrollTop) {
myScrollTop = document.body.scrollTop;
} else {
myScrollTop = 0;
}
// get the scrollLeft part
if (html.scrollLeft && document.documentElement.scrollLeft) {
myScrollLeft = html.scrollLeft;
} else if (html.scrollLeft || document.documentElement.scrollLeft) {
myScrollLeft = html.scrollLeft + document.documentElement.scrollLeft;
} else if (document.body.scrollLeft) {
myScrollLeft = document.body.scrollLeft;
} else {
myScrollLeft = 0;
}
return [myScrollLeft, myScrollTop];
}
return function (obj) {
var curleft = 0, curtop = 0, scr = obj, fixed = false;
while ((scr = scr.parentNode) && scr != document.body) {
curleft -= scr.scrollLeft || 0;
curtop -= scr.scrollTop || 0;
if (getStyle(scr, "position") == "fixed") {
fixed = true;
}
}
if (fixed && !window.opera) {
var scrDist = scrollDist();
curleft += scrDist[0];
curtop += scrDist[1];
}
do {
curleft += obj.offsetLeft;
curtop += obj.offsetTop;
} while ((obj = obj.offsetParent));
return {'x': curleft, 'y': curtop};
};
})();
// Get mouse event position in DOM element
Util.getEventPosition = function (e, obj, scale) {
"use strict";
var evt, docX, docY, pos;
//if (!e) evt = window.event;
evt = (e ? e : window.event);
@ -300,38 +523,41 @@ Util.getEventPosition = function (e, obj, scale) {
}
var realx = docX - pos.x;
var realy = docY - pos.y;
var x = Math.max(Math.min(realx, obj.width-1), 0);
var y = Math.max(Math.min(realy, obj.height-1), 0);
var x = Math.max(Math.min(realx, obj.width - 1), 0);
var y = Math.max(Math.min(realy, obj.height - 1), 0);
return {'x': x / scale, 'y': y / scale, 'realx': realx / scale, 'realy': realy / scale};
};
// Event registration. Based on: http://www.scottandrew.com/weblog/articles/cbs-events
Util.addEvent = function (obj, evType, fn){
if (obj.attachEvent){
var r = obj.attachEvent("on"+evType, fn);
Util.addEvent = function (obj, evType, fn) {
"use strict";
if (obj.attachEvent) {
var r = obj.attachEvent("on" + evType, fn);
return r;
} else if (obj.addEventListener){
} else if (obj.addEventListener) {
obj.addEventListener(evType, fn, false);
return true;
} else {
throw("Handler could not be attached");
throw new Error("Handler could not be attached");
}
};
Util.removeEvent = function(obj, evType, fn){
if (obj.detachEvent){
var r = obj.detachEvent("on"+evType, fn);
Util.removeEvent = function (obj, evType, fn) {
"use strict";
if (obj.detachEvent) {
var r = obj.detachEvent("on" + evType, fn);
return r;
} else if (obj.removeEventListener){
} else if (obj.removeEventListener) {
obj.removeEventListener(evType, fn, false);
return true;
} else {
throw("Handler could not be removed");
throw new Error("Handler could not be removed");
}
};
Util.stopEvent = function(e) {
Util.stopEvent = function (e) {
"use strict";
if (e.stopPropagation) { e.stopPropagation(); }
else { e.cancelBubble = true; }
@ -343,38 +569,85 @@ Util.stopEvent = function(e) {
// Set browser engine versions. Based on mootools.
Util.Features = {xpath: !!(document.evaluate), air: !!(window.runtime), query: !!(document.querySelector)};
Util.Engine = {
(function () {
"use strict";
// 'presto': (function () { return (!window.opera) ? false : true; }()),
var detectPresto = function () {
return !!window.opera;
};
// 'trident': (function () { return (!window.ActiveXObject) ? false : ((window.XMLHttpRequest) ? ((document.querySelectorAll) ? 6 : 5) : 4);
var detectTrident = function () {
if (!window.ActiveXObject) {
return false;
} else {
if (window.XMLHttpRequest) {
return (document.querySelectorAll) ? 6 : 5;
} else {
return 4;
}
}
};
// 'webkit': (function () { try { return (navigator.taintEnabled) ? false : ((Util.Features.xpath) ? ((Util.Features.query) ? 525 : 420) : 419); } catch (e) { return false; } }()),
var detectInitialWebkit = function () {
try {
if (navigator.taintEnabled) {
return false;
} else {
if (Util.Features.xpath) {
return (Util.Features.query) ? 525 : 420;
} else {
return 419;
}
}
} catch (e) {
return false;
}
};
var detectActualWebkit = function (initial_ver) {
var re = /WebKit\/([0-9\.]*) /;
var str_ver = (navigator.userAgent.match(re) || ['', initial_ver])[1];
return parseFloat(str_ver, 10);
};
// 'gecko': (function () { return (!document.getBoxObjectFor && window.mozInnerScreenX == null) ? false : ((document.getElementsByClassName) ? 19ssName) ? 19 : 18 : 18); }())
var detectGecko = function () {
/* jshint -W041 */
if (!document.getBoxObjectFor && window.mozInnerScreenX == null) {
return false;
} else {
return (document.getElementsByClassName) ? 19 : 18;
}
/* jshint +W041 */
};
Util.Engine = {
// Version detection break in Opera 11.60 (errors on arguments.callee.caller reference)
//'presto': (function() {
// return (!window.opera) ? false : ((arguments.callee.caller) ? 960 : ((document.getElementsByClassName) ? 950 : 925)); }()),
'presto': (function() { return (!window.opera) ? false : true; }()),
'presto': detectPresto(),
'trident': detectTrident(),
'webkit': detectInitialWebkit(),
'gecko': detectGecko(),
};
'trident': (function() {
return (!window.ActiveXObject) ? false : ((window.XMLHttpRequest) ? ((document.querySelectorAll) ? 6 : 5) : 4); }()),
'webkit': (function() {
try { return (navigator.taintEnabled) ? false : ((Util.Features.xpath) ? ((Util.Features.query) ? 525 : 420) : 419); } catch (e) { return false; } }()),
//'webkit': (function() {
// return ((typeof navigator.taintEnabled !== "unknown") && navigator.taintEnabled) ? false : ((Util.Features.xpath) ? ((Util.Features.query) ? 525 : 420) : 419); }()),
'gecko': (function() {
return (!document.getBoxObjectFor && window.mozInnerScreenX == null) ? false : ((document.getElementsByClassName) ? 19 : 18); }())
};
if (Util.Engine.webkit) {
if (Util.Engine.webkit) {
// Extract actual webkit version if available
Util.Engine.webkit = (function(v) {
var re = new RegExp('WebKit/([0-9\.]*) ');
v = (navigator.userAgent.match(re) || ['', v])[1];
return parseFloat(v, 10);
})(Util.Engine.webkit);
}
Util.Engine.webkit = detectActualWebkit(Util.Engine.webkit);
}
})();
Util.Flash = (function(){
Util.Flash = (function () {
"use strict";
var v, version;
try {
v = navigator.plugins['Shockwave Flash'].description;
} catch(err1) {
} catch (err1) {
try {
v = new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version');
} catch(err2) {
} catch (err2) {
v = '0 r0';
}
}

@ -1 +1 @@
Subproject commit 7677e7a954561cc44dac7659219bef768c904b62
Subproject commit c0855c6caec589c33acc22b6ee5e562287e65f3d

View File

@ -14,7 +14,7 @@
* read binary data off of the receive queue.
*/
/*jslint browser: true, bitwise: false, plusplus: false */
/*jslint browser: true, bitwise: true */
/*global Util, Base64 */
@ -43,251 +43,193 @@ if (window.WebSocket && !window.WEB_SOCKET_FORCE_FLASH) {
}
Util.load_scripts(["web-socket-js/swfobject.js",
"web-socket-js/web_socket.js"]);
}());
})();
}
function Websock() {
"use strict";
"use strict";
var api = {}, // Public API
websocket = null, // WebSocket object
mode = 'base64', // Current WebSocket mode: 'binary', 'base64'
rQ = [], // Receive queue
rQi = 0, // Receive queue index
rQmax = 10000, // Max receive queue size before compacting
sQ = [], // Send queue
this._websocket = null; // WebSocket object
this._rQ = []; // Receive queue
this._rQi = 0; // Receive queue index
this._rQmax = 10000; // Max receive queue size before compacting
this._sQ = []; // Send queue
eventHandlers = {
'message' : function() {},
'open' : function() {},
'close' : function() {},
'error' : function() {}
this._mode = 'base64'; // Current WebSocket mode: 'binary', 'base64'
this.maxBufferedAmount = 200;
this._eventHandlers = {
'message': function () {},
'open': function () {},
'close': function () {},
'error': function () {}
};
}
(function () {
"use strict";
Websock.prototype = {
// Getters and Setters
get_sQ: function () {
return this._sQ;
},
test_mode = false;
get_rQ: function () {
return this._rQ;
},
get_rQi: function () {
return this._rQi;
},
//
// Queue public functions
//
set_rQi: function (val) {
this._rQi = val;
},
function get_sQ() {
return sQ;
}
// Receive Queue
rQlen: function () {
return this._rQ.length - this._rQi;
},
function get_rQ() {
return rQ;
}
function get_rQi() {
return rQi;
}
function set_rQi(val) {
rQi = val;
}
rQpeek8: function () {
return this._rQ[this._rQi];
},
function rQlen() {
return rQ.length - rQi;
}
rQshift8: function () {
return this._rQ[this._rQi++];
},
function rQpeek8() {
return (rQ[rQi] );
}
function rQshift8() {
return (rQ[rQi++] );
}
function rQunshift8(num) {
if (rQi === 0) {
rQ.unshift(num);
rQskip8: function () {
this._rQi++;
},
rQskipBytes: function (num) {
this._rQi += num;
},
rQunshift8: function (num) {
if (this._rQi === 0) {
this._rQ.unshift(num);
} else {
rQi -= 1;
rQ[rQi] = num;
this._rQi--;
this._rQ[this._rQi] = num;
}
},
}
function rQshift16() {
return (rQ[rQi++] << 8) +
(rQ[rQi++] );
}
function rQshift32() {
return (rQ[rQi++] << 24) +
(rQ[rQi++] << 16) +
(rQ[rQi++] << 8) +
(rQ[rQi++] );
}
function rQshiftStr(len) {
if (typeof(len) === 'undefined') { len = rQlen(); }
var arr = rQ.slice(rQi, rQi + len);
rQi += len;
rQshift16: function () {
return (this._rQ[this._rQi++] << 8) +
this._rQ[this._rQi++];
},
rQshift32: function () {
return (this._rQ[this._rQi++] << 24) +
(this._rQ[this._rQi++] << 16) +
(this._rQ[this._rQi++] << 8) +
this._rQ[this._rQi++];
},
rQshiftStr: function (len) {
if (typeof(len) === 'undefined') { len = this.rQlen(); }
var arr = this._rQ.slice(this._rQi, this._rQi + len);
this._rQi += len;
return String.fromCharCode.apply(null, arr);
}
function rQshiftBytes(len) {
if (typeof(len) === 'undefined') { len = rQlen(); }
rQi += len;
return rQ.slice(rQi-len, rQi);
}
},
function rQslice(start, end) {
rQshiftBytes: function (len) {
if (typeof(len) === 'undefined') { len = this.rQlen(); }
this._rQi += len;
return this._rQ.slice(this._rQi - len, this._rQi);
},
rQslice: function (start, end) {
if (end) {
return rQ.slice(rQi + start, rQi + end);
return this._rQ.slice(this._rQi + start, this._rQi + end);
} else {
return rQ.slice(rQi + start);
return this._rQ.slice(this._rQi + start);
}
}
},
// Check to see if we must wait for 'num' bytes (default to FBU.bytes)
// to be available in the receive queue. Return true if we need to
// wait (and possibly print a debug message), otherwise false.
function rQwait(msg, num, goback) {
var rQlen = rQ.length - rQi; // Skip rQlen() function call
// Check to see if we must wait for 'num' bytes (default to FBU.bytes)
// to be available in the receive queue. Return true if we need to
// wait (and possibly print a debug message), otherwise false.
rQwait: function (msg, num, goback) {
var rQlen = this._rQ.length - this._rQi; // Skip rQlen() function call
if (rQlen < num) {
if (goback) {
if (rQi < goback) {
throw("rQwait cannot backup " + goback + " bytes");
if (this._rQi < goback) {
throw new Error("rQwait cannot backup " + goback + " bytes");
}
rQi -= goback;
this._rQi -= goback;
}
//Util.Debug(" waiting for " + (num-rQlen) +
// " " + msg + " byte(s)");
return true; // true means need more data
}
return false;
}
},
//
// Private utility routines
//
// Send Queue
function encode_message() {
if (mode === 'binary') {
// Put in a binary arraybuffer
return (new Uint8Array(sQ)).buffer;
} else {
// base64 encode
return Base64.encode(sQ);
flush: function () {
if (this._websocket.bufferedAmount !== 0) {
Util.Debug("bufferedAmount: " + this._websocket.bufferedAmount);
}
}
function decode_message(data) {
//Util.Debug(">> decode_message: " + data);
if (mode === 'binary') {
// push arraybuffer values onto the end
var u8 = new Uint8Array(data);
for (var i = 0; i < u8.length; i++) {
rQ.push(u8[i]);
if (this._websocket.bufferedAmount < this.maxBufferedAmount) {
if (this._sQ.length > 0) {
this._websocket.send(this._encode_message());
this._sQ = [];
}
} else {
// base64 decode and concat to the end
rQ = rQ.concat(Base64.decode(data, 0));
}
//Util.Debug(">> decode_message, rQ: " + rQ);
}
//
// Public Send functions
//
function flush() {
if (websocket.bufferedAmount !== 0) {
Util.Debug("bufferedAmount: " + websocket.bufferedAmount);
}
if (websocket.bufferedAmount < api.maxBufferedAmount) {
//Util.Debug("arr: " + arr);
//Util.Debug("sQ: " + sQ);
if (sQ.length > 0) {
websocket.send(encode_message(sQ));
sQ = [];
}
return true;
} else {
Util.Info("Delaying send, bufferedAmount: " +
websocket.bufferedAmount);
this._websocket.bufferedAmount);
return false;
}
}
},
// overridable for testing
function send(arr) {
//Util.Debug(">> send_array: " + arr);
sQ = sQ.concat(arr);
return flush();
}
send: function (arr) {
this._sQ = this._sQ.concat(arr);
return this.flush();
},
function send_string(str) {
//Util.Debug(">> send_string: " + str);
api.send(str.split('').map(
function (chr) { return chr.charCodeAt(0); } ) );
}
send_string: function (str) {
this.send(str.split('').map(function (chr) {
return chr.charCodeAt(0);
}));
},
//
// Other public functions
// Event Handlers
on: function (evt, handler) {
this._eventHandlers[evt] = handler;
},
function recv_message(e) {
//Util.Debug(">> recv_message: " + e.data.length);
try {
decode_message(e.data);
if (rQlen() > 0) {
eventHandlers.message();
// Compact the receive queue
if (rQ.length > rQmax) {
//Util.Debug("Compacting receive queue");
rQ = rQ.slice(rQi);
rQi = 0;
}
} else {
Util.Debug("Ignoring empty message");
}
} catch (exc) {
if (typeof exc.stack !== 'undefined') {
Util.Warn("recv_message, caught exception: " + exc.stack);
} else if (typeof exc.description !== 'undefined') {
Util.Warn("recv_message, caught exception: " + exc.description);
} else {
Util.Warn("recv_message, caught exception:" + exc);
}
if (typeof exc.name !== 'undefined') {
eventHandlers.error(exc.name + ": " + exc.message);
} else {
eventHandlers.error(exc);
}
}
//Util.Debug("<< recv_message");
}
// Set event handlers
function on(evt, handler) {
eventHandlers[evt] = handler;
}
function init(protocols) {
rQ = [];
rQi = 0;
sQ = [];
websocket = null;
var bt = false,
wsbt = false,
try_binary = false;
init: function (protocols, ws_schema) {
this._rQ = [];
this._rQi = 0;
this._sQ = [];
this._websocket = null;
// Check for full typed array support
var bt = false;
if (('Uint8Array' in window) &&
('set' in Uint8Array.prototype)) {
bt = true;
}
// Check for full binary type support in WebSockets
// TODO: this sucks, the property should exist on the prototype
// but it does not.
// Inspired by:
// https://github.com/Modernizr/Modernizr/issues/370
// https://github.com/Modernizr/Modernizr/blob/master/feature-detects/websockets/binary.js
var wsbt = false;
try {
if (bt && ('binaryType' in (new WebSocket("ws://localhost:17523")))) {
if (bt && ('binaryType' in WebSocket.prototype ||
!!(new WebSocket(ws_schema + '://.').binaryType))) {
Util.Info("Detected binaryType support in WebSockets");
wsbt = true;
}
} catch (exc) {
// Just ignore failed test localhost connections
// Just ignore failed test localhost connection
}
// Default protocols if not specified
@ -299,124 +241,144 @@ function init(protocols) {
}
}
// If no binary support, make sure it was not requested
if (!wsbt) {
if (protocols === 'binary') {
throw("WebSocket binary sub-protocol requested but not supported");
throw new Error('WebSocket binary sub-protocol requested but not supported');
}
if (typeof(protocols) === "object") {
if (typeof(protocols) === 'object') {
var new_protocols = [];
for (var i = 0; i < protocols.length; i++) {
if (protocols[i] === 'binary') {
Util.Error("Skipping unsupported WebSocket binary sub-protocol");
Util.Error('Skipping unsupported WebSocket binary sub-protocol');
} else {
new_protocols.push(protocols[i]);
}
}
if (new_protocols.length > 0) {
protocols = new_protocols;
} else {
throw("Only WebSocket binary sub-protocol was requested and not supported.");
throw new Error("Only WebSocket binary sub-protocol was requested and is not supported.");
}
}
}
return protocols;
}
},
function open(uri, protocols) {
protocols = init(protocols);
open: function (uri, protocols) {
var ws_schema = uri.match(/^([a-z]+):\/\//)[1];
protocols = this.init(protocols, ws_schema);
this._websocket = new WebSocket(uri, protocols);
if (test_mode) {
websocket = {};
} else {
websocket = new WebSocket(uri, protocols);
if (protocols.indexOf('binary') >= 0) {
websocket.binaryType = 'arraybuffer';
}
this._websocket.binaryType = 'arraybuffer';
}
websocket.onmessage = recv_message;
websocket.onopen = function() {
Util.Debug(">> WebSock.onopen");
if (websocket.protocol) {
mode = websocket.protocol;
Util.Info("Server chose sub-protocol: " + websocket.protocol);
this._websocket.onmessage = this._recv_message.bind(this);
this._websocket.onopen = (function () {
Util.Debug('>> WebSock.onopen');
if (this._websocket.protocol) {
this._mode = this._websocket.protocol;
Util.Info("Server choose sub-protocol: " + this._websocket.protocol);
} else {
mode = 'base64';
Util.Error("Server select no sub-protocol!: " + websocket.protocol);
this._mode = 'base64';
Util.Error('Server select no sub-protocol!: ' + this._websocket.protocol);
}
eventHandlers.open();
this._eventHandlers.open();
Util.Debug("<< WebSock.onopen");
};
websocket.onclose = function(e) {
}).bind(this);
this._websocket.onclose = (function (e) {
Util.Debug(">> WebSock.onclose");
eventHandlers.close(e);
this._eventHandlers.close(e);
Util.Debug("<< WebSock.onclose");
};
websocket.onerror = function(e) {
}).bind(this);
this._websocket.onerror = (function (e) {
Util.Debug(">> WebSock.onerror: " + e);
eventHandlers.error(e);
Util.Debug("<< WebSock.onerror");
};
}
this._eventHandlers.error(e);
Util.Debug("<< WebSock.onerror: " + e);
}).bind(this);
},
function close() {
if (websocket) {
if ((websocket.readyState === WebSocket.OPEN) ||
(websocket.readyState === WebSocket.CONNECTING)) {
close: function () {
if (this._websocket) {
if ((this._websocket.readyState === WebSocket.OPEN) ||
(this._websocket.readyState === WebSocket.CONNECTING)) {
Util.Info("Closing WebSocket connection");
websocket.close();
this._websocket.close();
}
websocket.onmessage = function (e) { return; };
this._websocket.onmessage = function (e) { return; };
}
}
},
// Override internal functions for testing
// Takes a send function, returns reference to recv function
function testMode(override_send, data_mode) {
test_mode = true;
mode = data_mode;
api.send = override_send;
api.close = function () {};
return recv_message;
}
// private methods
_encode_message: function () {
if (this._mode === 'binary') {
// Put in a binary arraybuffer
return (new Uint8Array(this._sQ)).buffer;
} else {
// base64 encode
return Base64.encode(this._sQ);
}
},
function constructor() {
// Configuration settings
api.maxBufferedAmount = 200;
_decode_message: function (data) {
if (this._mode === 'binary') {
// push arraybuffer values onto the end
var u8 = new Uint8Array(data);
for (var i = 0; i < u8.length; i++) {
this._rQ.push(u8[i]);
}
} else {
// base64 decode and concat to end
this._rQ = this._rQ.concat(Base64.decode(data, 0));
}
},
// Direct access to send and receive queues
api.get_sQ = get_sQ;
api.get_rQ = get_rQ;
api.get_rQi = get_rQi;
api.set_rQi = set_rQi;
_recv_message: function (e) {
try {
this._decode_message(e.data);
if (this.rQlen() > 0) {
this._eventHandlers.message();
// Compact the receive queue
if (this._rQ.length > this._rQmax) {
this._rQ = this._rQ.slice(this._rQi);
this._rQi = 0;
}
} else {
Util.Debug("Ignoring empty message");
}
} catch (exc) {
var exception_str = "";
if (exc.name) {
exception_str += "\n name: " + exc.name + "\n";
exception_str += " message: " + exc.message + "\n";
}
// Routines to read from the receive queue
api.rQlen = rQlen;
api.rQpeek8 = rQpeek8;
api.rQshift8 = rQshift8;
api.rQunshift8 = rQunshift8;
api.rQshift16 = rQshift16;
api.rQshift32 = rQshift32;
api.rQshiftStr = rQshiftStr;
api.rQshiftBytes = rQshiftBytes;
api.rQslice = rQslice;
api.rQwait = rQwait;
if (typeof exc.description !== 'undefined') {
exception_str += " description: " + exc.description + "\n";
}
api.flush = flush;
api.send = send;
api.send_string = send_string;
if (typeof exc.stack !== 'undefined') {
exception_str += exc.stack;
}
api.on = on;
api.init = init;
api.open = open;
api.close = close;
api.testMode = testMode;
if (exception_str.length > 0) {
Util.Error("recv_message, caught exception: " + exception_str);
} else {
Util.Error("recv_message, caught exception: " + exc);
}
return api;
}
return constructor();
}
if (typeof exc.name !== 'undefined') {
this._eventHandlers.error(exc.name + ": " + exc.message);
} else {
this._eventHandlers.error(exc);
}
}
}
};
})();

View File

@ -1,13 +1,13 @@
/*
* noVNC: HTML5 VNC client
* Copyright (C) 2012 Joel Martin
* Copyright (C) 2013 NTT corp.
* Licensed under MPL 2.0 (see LICENSE.txt)
*
* See README.md for usage and integration instructions.
*/
"use strict";
/*jslint bitwise: false, white: false */
/*jslint bitwise: false, white: false, browser: true, devel: true */
/*global Util, window, document */
// Globals defined here
@ -37,37 +37,39 @@ if (!window.$D) {
*/
// init log level reading the logging HTTP param
WebUtil.init_logging = function(level) {
WebUtil.init_logging = function (level) {
"use strict";
if (typeof level !== "undefined") {
Util._log_level = level;
} else {
Util._log_level = (document.location.href.match(
/logging=([A-Za-z0-9\._\-]*)/) ||
['', Util._log_level])[1];
var param = document.location.href.match(/logging=([A-Za-z0-9\._\-]*)/);
Util._log_level = (param || ['', Util._log_level])[1];
}
Util.init_logging();
};
WebUtil.dirObj = function (obj, depth, parent) {
var i, msg = "", val = "";
if (! depth) { depth=2; }
if (! parent) { parent= ""; }
"use strict";
if (! depth) { depth = 2; }
if (! parent) { parent = ""; }
// Print the properties of the passed-in object
for (i in obj) {
var msg = "";
for (var i in obj) {
if ((depth > 1) && (typeof obj[i] === "object")) {
// Recurse attributes that are objects
msg += WebUtil.dirObj(obj[i], depth-1, parent + "." + i);
msg += WebUtil.dirObj(obj[i], depth - 1, parent + "." + i);
} else {
//val = new String(obj[i]).replace("\n", " ");
var val = "";
if (typeof(obj[i]) === "undefined") {
val = "undefined";
} else {
val = obj[i].toString().replace("\n", " ");
}
if (val.length > 30) {
val = val.substr(0,30) + "...";
val = val.substr(0, 30) + "...";
}
msg += parent + "." + i + ": " + val + "\n";
}
@ -76,8 +78,9 @@ WebUtil.dirObj = function (obj, depth, parent) {
};
// Read a query string variable
WebUtil.getQueryVar = function(name, defVal) {
var re = new RegExp('[?][^#]*' + name + '=([^&#]*)'),
WebUtil.getQueryVar = function (name, defVal) {
"use strict";
var re = new RegExp('.*[?&]' + name + '=([^&#]*)'),
match = document.location.href.match(re);
if (typeof defVal === 'undefined') { defVal = null; }
if (match) {
@ -93,38 +96,50 @@ WebUtil.getQueryVar = function(name, defVal) {
*/
// No days means only for this browser session
WebUtil.createCookie = function(name,value,days) {
WebUtil.createCookie = function (name, value, days) {
"use strict";
var date, expires;
if (days) {
date = new Date();
date.setTime(date.getTime()+(days*24*60*60*1000));
expires = "; expires="+date.toGMTString();
}
else {
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
expires = "; expires=" + date.toGMTString();
} else {
expires = "";
}
document.cookie = name+"="+value+expires+"; path=/";
var secure;
if (document.location.protocol === "https:") {
secure = "; secure";
} else {
secure = "";
}
document.cookie = name + "=" + value + expires + "; path=/" + secure;
};
WebUtil.readCookie = function(name, defaultValue) {
var i, c, nameEQ = name + "=", ca = document.cookie.split(';');
for(i=0; i < ca.length; i += 1) {
c = ca[i];
while (c.charAt(0) === ' ') { c = c.substring(1,c.length); }
if (c.indexOf(nameEQ) === 0) { return c.substring(nameEQ.length,c.length); }
WebUtil.readCookie = function (name, defaultValue) {
"use strict";
var nameEQ = name + "=",
ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i += 1) {
var c = ca[i];
while (c.charAt(0) === ' ') { c = c.substring(1, c.length); }
if (c.indexOf(nameEQ) === 0) { return c.substring(nameEQ.length, c.length); }
}
return (typeof defaultValue !== 'undefined') ? defaultValue : null;
};
WebUtil.eraseCookie = function(name) {
WebUtil.createCookie(name,"",-1);
WebUtil.eraseCookie = function (name) {
"use strict";
WebUtil.createCookie(name, "", -1);
};
/*
* Setting handling.
*/
WebUtil.initSettings = function(callback) {
WebUtil.initSettings = function (callback /*, ...callbackArgs */) {
"use strict";
var callbackArgs = Array.prototype.slice.call(arguments, 1);
if (window.chrome && window.chrome.storage) {
window.chrome.storage.sync.get(function (cfg) {
@ -143,7 +158,8 @@ WebUtil.initSettings = function(callback) {
};
// No days means only for this browser session
WebUtil.writeSetting = function(name, value) {
WebUtil.writeSetting = function (name, value) {
"use strict";
if (window.chrome && window.chrome.storage) {
//console.log("writeSetting:", name, value);
if (WebUtil.settings[name] !== value) {
@ -155,7 +171,8 @@ WebUtil.writeSetting = function(name, value) {
}
};
WebUtil.readSetting = function(name, defaultValue) {
WebUtil.readSetting = function (name, defaultValue) {
"use strict";
var value;
if (window.chrome && window.chrome.storage) {
value = WebUtil.settings[name];
@ -172,7 +189,8 @@ WebUtil.readSetting = function(name, defaultValue) {
}
};
WebUtil.eraseSetting = function(name) {
WebUtil.eraseSetting = function (name) {
"use strict";
if (window.chrome && window.chrome.storage) {
window.chrome.storage.sync.remove(name);
delete WebUtil.settings[name];
@ -184,9 +202,12 @@ WebUtil.eraseSetting = function(name) {
/*
* Alternate stylesheet selection
*/
WebUtil.getStylesheets = function() { var i, links, sheets = [];
links = document.getElementsByTagName("link");
for (i = 0; i < links.length; i += 1) {
WebUtil.getStylesheets = function () {
"use strict";
var links = document.getElementsByTagName("link");
var sheets = [];
for (var i = 0; i < links.length; i += 1) {
if (links[i].title &&
links[i].rel.toUpperCase().indexOf("STYLESHEET") > -1) {
sheets.push(links[i]);
@ -197,13 +218,15 @@ WebUtil.getStylesheets = function() { var i, links, sheets = [];
// No sheet means try and use value from cookie, null sheet used to
// clear all alternates.
WebUtil.selectStylesheet = function(sheet) {
var i, link, sheets = WebUtil.getStylesheets();
WebUtil.selectStylesheet = function (sheet) {
"use strict";
if (typeof sheet === 'undefined') {
sheet = 'default';
}
for (i=0; i < sheets.length; i += 1) {
link = sheets[i];
var sheets = WebUtil.getStylesheets();
for (var i = 0; i < sheets.length; i += 1) {
var link = sheets[i];
if (link.title === sheet) {
Util.Debug("Using stylesheet " + sheet);
link.disabled = false;

194
karma.conf.js Normal file
View File

@ -0,0 +1,194 @@
// Karma configuration
module.exports = function(config) {
/*var customLaunchers = {
sl_chrome_win7: {
base: 'SauceLabs',
browserName: 'chrome',
platform: 'Windows 7'
},
sl_firefox30_linux: {
base: 'SauceLabs',
browserName: 'firefox',
version: '30',
platform: 'Linux'
},
sl_firefox26_linux: {
base: 'SauceLabs',
browserName: 'firefox',
version: 26,
platform: 'Linux'
},
sl_windows7_ie10: {
base: 'SauceLabs',
browserName: 'internet explorer',
platform: 'Windows 7',
version: '10'
},
sl_windows81_ie11: {
base: 'SauceLabs',
browserName: 'internet explorer',
platform: 'Windows 8.1',
version: '11'
},
sl_osxmavericks_safari7: {
base: 'SauceLabs',
browserName: 'safari',
platform: 'OS X 10.9',
version: '7'
},
sl_osxmtnlion_safari6: {
base: 'SauceLabs',
browserName: 'safari',
platform: 'OS X 10.8',
version: '6'
}
};*/
var customLaunchers = {};
var browsers = [];
var useSauce = false;
if (process.env.SAUCE_USERNAME && process.env.SAUCE_ACCESS_KEY) {
useSauce = true;
}
if (useSauce && process.env.TEST_BROWSER_NAME && process.env.TEST_BROWSER_NAME != 'PhantomJS') {
var names = process.env.TEST_BROWSER_NAME.split(',');
var platforms = process.env.TEST_BROWSER_OS.split(',');
var versions = [];
if (process.env.TEST_BROWSER_VERSION) {
versions = process.env.TEST_BROWSER_VERSION.split(',');
} else {
versions = [null];
}
for (var i = 0; i < names.length; i++) {
for (var j = 0; j < platforms.length; j++) {
for (var k = 0; k < versions.length; k++) {
var launcher_name = 'sl_' + platforms[j].replace(/[^a-zA-Z0-9]/g, '') + '_' + names[i];
if (versions[k]) {
launcher_name += '_' + versions[k];
}
customLaunchers[launcher_name] = {
base: 'SauceLabs',
browserName: names[i],
platform: platforms[j],
};
if (versions[i]) {
customLaunchers[launcher_name].version = versions[k];
}
}
}
}
browsers = Object.keys(customLaunchers);
} else {
useSauce = false;
browsers = ['PhantomJS'];
}
var my_conf = {
// base path that will be used to resolve all patterns (eg. files, exclude)
basePath: '',
// frameworks to use
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
frameworks: ['mocha', 'sinon', 'chai', 'sinon-chai'],
// list of files / patterns to load in the browser (loaded in order)
files: [
'tests/fake.*.js',
'tests/assertions.js',
'include/util.js', // load first to avoid issues, since methods are called immediately
//'../include/*.js',
'include/base64.js',
'include/keysym.js',
'include/keysymdef.js',
'include/keyboard.js',
'include/input.js',
'include/websock.js',
'include/rfb.js',
'include/jsunzip.js',
'include/des.js',
'include/display.js',
'tests/test.*.js'
],
client: {
mocha: {
'ui': 'bdd'
}
},
// list of files to exclude
exclude: [
'../include/playback.js',
'../include/ui.js'
],
customLaunchers: customLaunchers,
// start these browsers
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
browsers: browsers,
// preprocess matching files before serving them to the browser
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
preprocessors: {
},
// test results reporter to use
// possible values: 'dots', 'progress'
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
reporters: ['mocha', 'saucelabs'],
// web server port
port: 9876,
// enable / disable colors in the output (reporters and logs)
colors: true,
// level of logging
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
logLevel: config.LOG_INFO,
// enable / disable watching file and executing tests whenever any file changes
autoWatch: false,
// Continuous Integration mode
// if true, Karma captures browsers, runs the tests and exits
singleRun: true,
// Increase timeout in case connection is slow/we run more browsers than possible
// (we currently get 3 for free, and we try to run 7, so it can take a while)
captureTimeout: 240000
};
if (useSauce) {
my_conf.captureTimeout = 0; // use SL timeout
my_conf.sauceLabs = {
testName: 'noVNC Tests (all)',
startConnect: true,
tunnelIdentifier: process.env.TRAVIS_JOB_NUMBER
};
}
config.set(my_conf);
};

50
package.json Normal file
View File

@ -0,0 +1,50 @@
{
"name": "noVNC",
"version": "0.5.0",
"description": "An HTML5 VNC client",
"main": "karma.conf.js",
"directories": {
"doc": "docs",
"test": "tests"
},
"scripts": {
"test": "karma start karma.conf.js"
},
"repository": {
"type": "git",
"url": "https://github.com/kanaka/noVNC.git"
},
"author": "Joel Martin <github@martintribe.org> (https://github.com/kanaka)",
"contributors": [
"Solly Ross <sross@redhat.com> (https://github.com/directxman12)",
"Peter Åstrand <astrand@cendio.se> (https://github.com/astrand)",
"Samuel Mannehed <samuel@cendio.se> (https://github.com/samhed)"
],
"license": "MPL 2.0",
"bugs": {
"url": "https://github.com/kanaka/noVNC/issues"
},
"homepage": "https://github.com/kanaka/noVNC",
"devDependencies": {
"ansi": "^0.3.0",
"casperjs": "^1.1.0-beta3",
"chai": "^1.10.0",
"commander": "^2.5.0",
"karma": "^0.12.25",
"karma-chai": "^0.1.0",
"karma-mocha": "^0.1.9",
"karma-mocha-reporter": "^0.3.1",
"karma-phantomjs-launcher": "^0.1.4",
"karma-sauce-launcher": "^0.2.10",
"karma-sinon": "^1.0.3",
"karma-sinon-chai-latest": "^0.1.0",
"mocha": "^2.0.1",
"open": "^0.0.5",
"phantom": "^0.7.0",
"phantomjs": "^1.9.12",
"sinon": "^1.12.1",
"sinon-chai": "^2.6.0",
"spooky": "^0.2.5",
"temp": "^0.8.1"
}
}

24
tests/assertions.js Normal file
View File

@ -0,0 +1,24 @@
// some useful assertions for noVNC
chai.use(function (_chai, utils) {
_chai.Assertion.addMethod('displayed', function (target_data) {
var obj = this._obj;
var data_cl = obj._drawCtx.getImageData(0, 0, obj._viewportLoc.w, obj._viewportLoc.h).data;
// NB(directxman12): PhantomJS 1.x doesn't implement Uint8ClampedArray, so work around that
var data = new Uint8Array(data_cl);
this.assert(utils.eql(data, target_data),
"expected #{this} to have displayed the image #{exp}, but instead it displayed #{act}",
"expected #{this} not to have displayed the image #{act}",
target_data,
data);
});
_chai.Assertion.addMethod('sent', function (target_data) {
var obj = this._obj;
var data = obj._websocket._get_sent_data();
this.assert(utils.eql(data, target_data),
"expected #{this} to have sent the data #{exp}, but it actually sent #{act}",
"expected #{this} not to have sent the data #{act}",
target_data,
data);
});
});

96
tests/fake.websocket.js Normal file
View File

@ -0,0 +1,96 @@
var FakeWebSocket;
(function () {
// PhantomJS can't create Event objects directly, so we need to use this
function make_event(name, props) {
var evt = document.createEvent('Event');
evt.initEvent(name, true, true);
if (props) {
for (var prop in props) {
evt[prop] = props[prop];
}
}
return evt;
}
FakeWebSocket = function (uri, protocols) {
this.url = uri;
this.binaryType = "arraybuffer";
this.extensions = "";
if (!protocols || typeof protocols === 'string') {
this.protocol = protocols;
} else {
this.protocol = protocols[0];
}
this._send_queue = new Uint8Array(20000);
this.readyState = FakeWebSocket.CONNECTING;
this.bufferedAmount = 0;
this.__is_fake = true;
};
FakeWebSocket.prototype = {
close: function (code, reason) {
this.readyState = FakeWebSocket.CLOSED;
if (this.onclose) {
this.onclose(make_event("close", { 'code': code, 'reason': reason, 'wasClean': true }));
}
},
send: function (data) {
if (this.protocol == 'base64') {
data = Base64.decode(data);
} else {
data = new Uint8Array(data);
}
this._send_queue.set(data, this.bufferedAmount);
this.bufferedAmount += data.length;
},
_get_sent_data: function () {
var arr = [];
for (var i = 0; i < this.bufferedAmount; i++) {
arr[i] = this._send_queue[i];
}
this.bufferedAmount = 0;
return arr;
},
_open: function (data) {
this.readyState = FakeWebSocket.OPEN;
if (this.onopen) {
this.onopen(make_event('open'));
}
},
_receive_data: function (data) {
this.onmessage(make_event("message", { 'data': data }));
}
};
FakeWebSocket.OPEN = WebSocket.OPEN;
FakeWebSocket.CONNECTING = WebSocket.CONNECTING;
FakeWebSocket.CLOSING = WebSocket.CLOSING;
FakeWebSocket.CLOSED = WebSocket.CLOSED;
FakeWebSocket.__is_fake = true;
FakeWebSocket.replace = function () {
if (!WebSocket.__is_fake) {
var real_version = WebSocket;
WebSocket = FakeWebSocket;
FakeWebSocket.__real_version = real_version;
}
};
FakeWebSocket.restore = function () {
if (WebSocket.__is_fake) {
WebSocket = WebSocket.__real_version;
}
};
})();

View File

@ -26,6 +26,8 @@
<script src="../include/util.js"></script>
<script src="../include/webutil.js"></script>
<script src="../include/base64.js"></script>
<script src="../include/keysymdef.js"></script>
<script src="../include/keyboard.js"></script>
<script src="../include/input.js"></script>
<script src="../include/display.js"></script>
<script>
@ -57,9 +59,17 @@
//console.log(msg);
}
function keyPress(keysym, down, e) {
function rfbKeyPress(keysym, down) {
var d = down ? "down" : " up ";
msg = "keyPress " + d + " keysym: " + keysym +
var key = keysyms.lookup(keysym);
var msg = "RFB keypress " + d + " keysym: " + keysym;
if (key && key.keyname) {
msg += " key name: " + key.keyname;
}
message(msg);
}
function rawKey(e) {
msg = "raw key event " + e.type +
" (key: " + e.keyCode + ", char: " + e.charCode +
", which: " + e.which +")";
message(msg);
@ -94,7 +104,10 @@
window.onload = function() {
canvas = new Display({'target' : $D('canvas')});
keyboard = new Keyboard({'target': document,
'onKeyPress': keyPress});
'onKeyPress': rfbKeyPress});
Util.addEvent(document, 'keypress', rawKey);
Util.addEvent(document, 'keydown', rawKey);
Util.addEvent(document, 'keyup', rawKey);
mouse = new Mouse({'target': $D('canvas'),
'onMouseButton': mouseButton,
'onMouseMove': mouseMove});

29
tests/keyboard-tests.html Normal file
View File

@ -0,0 +1,29 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Mocha Tests</title>
<link rel="stylesheet" href="node_modules/mocha/mocha.css" />
</head>
<body>
<!--
To run tests
cd .../noVNC/tests
npm install chai mocha
open keyboard-tests.html in a browser
-->
<div id="mocha"></div>
<script src="node_modules/chai/chai.js"></script>
<script src="node_modules/mocha/mocha.js"></script>
<script>mocha.setup('bdd')</script>
<script src="../include/keysymdef.js"></script>
<script src="../include/keyboard.js"></script>
<script src="test.keyboard.js"></script>
<script src="test.helper.js"></script>
<script>
mocha.checkLeaks();
mocha.globals(['navigator']);
mocha.run();
</script>
</body>
</html>

View File

@ -0,0 +1,102 @@
var Spooky = require('spooky');
var path = require('path');
var phantom_path = require('phantomjs').path;
var casper_path = path.resolve(__dirname, '../node_modules/casperjs/bin/casperjs');
process.env.PHANTOMJS_EXECUTABLE = phantom_path;
var casper_opts = {
child: {
transport: 'http',
command: casper_path
},
casper: {
logLevel: 'debug',
verbose: true
}
};
var provide_emitter = function(file_paths) {
var spooky = new Spooky(casper_opts, function(err) {
if (err) {
if (err.stack) console.warn(err.stack);
else console.warn(err);
return;
}
spooky.start('about:blank');
file_paths.forEach(function(file_path, path_ind) {
spooky.thenOpen('file://'+file_path);
spooky.waitFor(function() {
return this.getGlobal('__mocha_done') === true;
},
[{ path_ind: path_ind }, function() {
var res_json = {
file_ind: path_ind
};
res_json.num_tests = this.evaluate(function() { return document.querySelectorAll('li.test').length; });
res_json.num_passes = this.evaluate(function() { return document.querySelectorAll('li.test.pass').length; });
res_json.num_fails = this.evaluate(function() { return document.querySelectorAll('li.test.fail').length; });
res_json.num_slow = this.evaluate(function() { return document.querySelectorAll('li.test.pass:not(.fast):not(.pending)').length; });
res_json.num_skipped = this.evaluate(function () { return document.querySelectorAll('li.test.pending').length; });
res_json.duration = this.evaluate(function() { return document.querySelector('li.duration em').textContent; });
res_json.suites = this.evaluate(function() {
var traverse_node = function(elem) {
var res;
if (elem.classList.contains('suite')) {
res = {
type: 'suite',
name: elem.querySelector('h1').textContent,
has_subfailures: elem.querySelectorAll('li.test.fail').length > 0,
};
var child_elems = elem.querySelector('ul').children;
res.children = Array.prototype.map.call(child_elems, traverse_node);
return res;
}
else {
var h2_content = elem.querySelector('h2').childNodes;
res = {
type: 'test',
text: h2_content[0].textContent,
};
if (elem.classList.contains('pass')) {
res.pass = true;
if (elem.classList.contains('pending')) {
res.slow = false;
res.skipped = true;
}
else {
res.slow = !elem.classList.contains('fast');
res.skipped = false;
res.duration = h2_content[1].textContent;
}
}
else {
res.error = elem.querySelector('pre.error').textContent;
}
return res;
}
};
var top_suites = document.querySelectorAll('#mocha-report > li.suite');
return Array.prototype.map.call(top_suites, traverse_node);
});
res_json.replay = this.evaluate(function() { return document.querySelector('a.replay').textContent; });
this.emit('test_ready', res_json);
}]);
});
spooky.run();
});
return spooky;
};
module.exports = {
provide_emitter: provide_emitter,
name: 'SpookyJS (CapserJS on PhantomJS)'
};

342
tests/run_from_console.js Executable file
View File

@ -0,0 +1,342 @@
#!/usr/bin/env node
var ansi = require('ansi');
var program = require('commander');
var path = require('path');
var fs = require('fs');
var make_list = function(val) {
return val.split(',');
};
program
.option('-t, --tests <testlist>', 'Run the specified html-file-based test(s). \'testlist\' should be a comma-separated list', make_list, [])
.option('-a, --print-all', 'Print all tests, not just the failures')
.option('--disable-color', 'Explicitly disable color')
.option('-c, --color', 'Explicitly enable color (default is to use color when not outputting to a pipe)')
.option('-i, --auto-inject <includefiles>', 'Treat the test list as a set of mocha JS files, and automatically generate HTML files with which to test test. \'includefiles\' should be a comma-separated list of paths to javascript files to include in each of the generated HTML files', make_list, null)
.option('-p, --provider <name>', 'Use the given provider (defaults to "casper"). Currently, may be "casper" or "zombie"', 'casper')
.option('-g, --generate-html', 'Instead of running the tests, just return the path to the generated HTML file, then wait for user interaction to exit (should be used with .js tests).')
.option('-o, --open-in-browser', 'Open the generated HTML files in a web browser using the "open" module (must be used with the "-g"/"--generate-html" option).')
.option('--output-html', 'Instead of running the tests, just output the generated HTML source to STDOUT (should be used with .js tests)')
.option('-d, --debug', 'Show debug output (the "console" event) from the provider')
.option('-r, --relative', 'Use relative paths in the generated HTML file')
.parse(process.argv);
if (program.tests.length === 0) {
program.tests = fs.readdirSync(__dirname).filter(function(f) { return (/^test\.(\w|\.|-)+\.js$/).test(f); });
program.tests = program.tests.map(function (f) { return path.resolve(__dirname, f); }); // add full paths in
console.log('using files %s', program.tests);
}
var file_paths = [];
var all_js = program.tests.reduce(function(a,e) { return a && e.slice(-3) == '.js'; }, true);
var get_path = function (/* arguments */) {
if (program.relative) {
return path.join.apply(null, arguments);
} else {
var args = Array.prototype.slice.call(arguments);
args.unshift(__dirname, '..');
return path.resolve.apply(null, args);
}
};
var get_path_cwd = function (/* arguments */) {
if (program.relative) {
var part_path = path.join.apply(null, arguments);
return path.relative(path.join(__dirname, '..'), path.resolve(process.cwd(), part_path));
} else {
var args = Array.prototype.slice.call(arguments);
args.unshift(process.cwd());
return path.resolve.apply(null, args);
}
};
if (all_js && !program.autoInject) {
var all_modules = {};
// uses the first instance of the string 'requires local modules: '
program.tests.forEach(function (testname) {
var full_path = path.resolve(process.cwd(), testname);
var content = fs.readFileSync(full_path).toString();
var ind = content.indexOf('requires local modules: ');
if (ind > -1) {
ind += 'requires local modules: '.length;
var eol = content.indexOf('\n', ind);
var modules = content.slice(ind, eol).split(/,\s*/);
modules.forEach(function (mod) {
all_modules[get_path('include/', mod) + '.js'] = 1;
});
}
var fakes_ind = content.indexOf('requires test modules: ');
if (fakes_ind > -1) {
fakes_ind += 'requires test modules: '.length;
var fakes_eol = content.indexOf('\n', fakes_ind);
var fakes_modules = content.slice(fakes_ind, fakes_eol).split(/,\s*/);
fakes_modules.forEach(function (mod) {
all_modules[get_path('tests/', mod) + '.js'] = 1;
});
}
});
program.autoInject = Object.keys(all_modules);
}
if (program.autoInject) {
var temp = require('temp');
temp.track();
var template = {
header: "<html>\n<head>\n<meta charset='utf-8' />\n<link rel='stylesheet' href='" + get_path('node_modules/mocha/mocha.css') + "'/>\n</head>\n<body><div id='mocha'></div>",
script_tag: function(p) { return "<script src='" + p + "'></script>"; },
footer: "<script>\nmocha.checkLeaks();\nmocha.globals(['navigator', 'create', 'ClientUtils', '__utils__']);\nmocha.run(function () { window.__mocha_done = true; });\n</script>\n</body>\n</html>"
};
template.header += "\n" + template.script_tag(get_path('node_modules/chai/chai.js'));
template.header += "\n" + template.script_tag(get_path('node_modules/mocha/mocha.js'));
template.header += "\n" + template.script_tag(get_path('node_modules/sinon/pkg/sinon.js'));
template.header += "\n" + template.script_tag(get_path('node_modules/sinon-chai/lib/sinon-chai.js'));
template.header += "\n" + template.script_tag(get_path('node_modules/sinon-chai/lib/sinon-chai.js'));
template.header += "\n<script>mocha.setup('bdd');</script>";
template.header = program.autoInject.reduce(function(acc, sn) {
return acc + "\n" + template.script_tag(get_path_cwd(sn));
}, template.header);
file_paths = program.tests.map(function(jsn, ind) {
var templ = template.header;
templ += "\n";
templ += template.script_tag(get_path_cwd(jsn));
templ += template.footer;
var tempfile = temp.openSync({ prefix: 'novnc-zombie-inject-', suffix: '-file_num-'+ind+'.html' });
fs.writeSync(tempfile.fd, templ);
fs.closeSync(tempfile.fd);
return tempfile.path;
});
}
else {
file_paths = program.tests.map(function(fn) {
return path.resolve(process.cwd(), fn);
});
}
var use_ansi = false;
if (program.color) use_ansi = true;
else if (program.disableColor) use_ansi = false;
else if (process.stdout.isTTY) use_ansi = true;
var cursor = ansi(process.stdout, { enabled: use_ansi });
if (program.outputHtml) {
file_paths.forEach(function(path, path_ind) {
fs.readFile(path, function(err, data) {
if (err) {
console.warn(error.stack);
return;
}
if (use_ansi) {
cursor
.bold()
.write(program.tests[path_ind])
.reset()
.write("\n")
.write(Array(program.tests[path_ind].length+1).join('='))
.write("\n\n");
}
cursor
.write(data)
.write("\n\n");
});
});
}
if (program.generateHtml) {
var open_browser;
if (program.openInBrowser) {
open_browser = require('open');
}
file_paths.forEach(function(path, path_ind) {
cursor
.bold()
.write(program.tests[path_ind])
.write(": ")
.reset()
.write(path)
.write("\n");
if (program.openInBrowser) {
open_browser(path);
}
});
console.log('');
}
if (program.generateHtml) {
process.stdin.resume(); // pause until C-c
process.on('SIGINT', function() {
process.stdin.pause(); // exit
});
}
if (!program.outputHtml && !program.generateHtml) {
var failure_count = 0;
var prov = require(path.resolve(__dirname, 'run_from_console.'+program.provider+'.js'));
cursor
.write("Running tests ")
.bold()
.write(program.tests.join(', '))
.reset()
.grey()
.write(' using provider '+prov.name)
.reset()
.write("\n");
//console.log("Running tests %s using provider %s", program.tests.join(', '), prov.name);
var provider = prov.provide_emitter(file_paths);
provider.on('test_ready', function(test_json) {
console.log('');
filename = program.tests[test_json.file_ind];
cursor.bold();
console.log('Results for %s:', filename);
console.log(Array('Results for :'.length+filename.length+1).join('='));
cursor.reset();
console.log('');
cursor
.write(''+test_json.num_tests+' tests run, ')
.green()
.write(''+test_json.num_passes+' passed');
if (test_json.num_slow > 0) {
cursor
.reset()
.write(' (');
cursor
.yellow()
.write(''+test_json.num_slow+' slow')
.reset()
.write(')');
}
cursor
.reset()
.write(', ');
cursor
.red()
.write(''+test_json.num_fails+' failed');
if (test_json.num_skipped > 0) {
cursor
.reset()
.write(', ')
.grey()
.write(''+test_json.num_skipped+' skipped');
}
cursor
.reset()
.write(' -- duration: '+test_json.duration+"s\n");
console.log('');
if (test_json.num_fails > 0 || program.printAll) {
var traverse_tree = function(indentation, node) {
if (node.type == 'suite') {
if (!node.has_subfailures && !program.printAll) return;
if (indentation === 0) {
cursor.bold();
console.log(node.name);
console.log(Array(node.name.length+1).join('-'));
cursor.reset();
}
else {
cursor
.write(Array(indentation+3).join('#'))
.bold()
.write(' '+node.name+' ')
.reset()
.write(Array(indentation+3).join('#'))
.write("\n");
}
console.log('');
for (var i = 0; i < node.children.length; i++) {
traverse_tree(indentation+1, node.children[i]);
}
}
else {
if (!node.pass) {
cursor.magenta();
console.log('- failed: '+node.text+test_json.replay);
cursor.red();
console.log(' '+node.error.split("\n")[0]); // the split is to avoid a weird thing where in PhantomJS where we get a stack trace too
cursor.reset();
console.log('');
}
else if (program.printAll) {
if (node.skipped) {
cursor
.grey()
.write('- skipped: '+node.text);
}
else {
if (node.slow) cursor.yellow();
else cursor.green();
cursor
.write('- pass: '+node.text)
.grey()
.write(' ('+node.duration+') ');
}
/*if (node.slow) cursor.yellow();
else cursor.green();*/
cursor
//.write(test_json.replay)
.reset()
.write("\n");
console.log('');
}
}
};
for (var i = 0; i < test_json.suites.length; i++) {
traverse_tree(0, test_json.suites[i]);
}
}
if (test_json.num_fails === 0) {
cursor.fg.green();
console.log('all tests passed :-)');
cursor.reset();
}
});
if (program.debug) {
provider.on('console', function(line) {
// log to stderr
console.error(line);
});
}
provider.on('error', function(line) {
// log to stderr
console.error('ERROR: ' + line);
});
/*gprom.finally(function(ph) {
ph.exit();
// exit with a status code that actually gives information
if (program.exitWithFailureCount) process.exit(failure_count);
});*/
}

View File

@ -0,0 +1,82 @@
var Browser = require('zombie');
var path = require('path');
var EventEmitter = require('events').EventEmitter;
var Q = require('q');
var provide_emitter = function(file_paths) {
var emitter = new EventEmitter();
file_paths.reduce(function(prom, file_path, path_ind) {
return prom.then(function(browser) {
browser.visit('file://'+file_path, function() {
if (browser.error) throw new Error(browser.errors);
var res_json = {};
res_json.file_ind = path_ind;
res_json.num_tests = browser.querySelectorAll('li.test').length;
res_json.num_fails = browser.querySelectorAll('li.test.fail').length;
res_json.num_passes = browser.querySelectorAll('li.test.pass').length;
res_json.num_slow = browser.querySelectorAll('li.test.pass:not(.fast)').length;
res_json.num_skipped = browser.querySelectorAll('li.test.pending').length;
res_json.duration = browser.text('li.duration em');
var traverse_node = function(elem) {
var classList = elem.className.split(' ');
var res;
if (classList.indexOf('suite') > -1) {
res = {
type: 'suite',
name: elem.querySelector('h1').textContent,
has_subfailures: elem.querySelectorAll('li.test.fail').length > 0
};
var child_elems = elem.querySelector('ul').children;
res.children = Array.prototype.map.call(child_elems, traverse_node);
return res;
}
else {
var h2_content = elem.querySelector('h2').childNodes;
res = {
type: 'test',
text: h2_content[0].textContent
};
if (classList.indexOf('pass') > -1) {
res.pass = true;
if (classList.indexOf('pending') > -1) {
res.slow = false;
res.skipped = true;
}
else {
res.slow = classList.indexOf('fast') < 0;
res.skipped = false;
res.duration = h2_content[1].textContent;
}
}
else {
res.error = elem.querySelector('pre.error').textContent;
}
return res;
}
};
var top_suites = browser.querySelectorAll('#mocha-report > li.suite');
res_json.suites = Array.prototype.map.call(top_suites, traverse_node);
res_json.replay = browser.querySelector('a.replay').textContent;
emitter.emit('test_ready', res_json);
});
return new Browser();
});
}, Q(new Browser()));
return emitter;
};
module.exports = {
provide_emitter: provide_emitter,
name: 'ZombieJS'
};

33
tests/test.base64.js Normal file
View File

@ -0,0 +1,33 @@
// requires local modules: base64
var assert = chai.assert;
var expect = chai.expect;
describe('Base64 Tools', function() {
"use strict";
var BIN_ARR = new Array(256);
for (var i = 0; i < 256; i++) {
BIN_ARR[i] = i;
}
var B64_STR = "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w==";
describe('encode', function() {
it('should encode a binary string into Base64', function() {
var encoded = Base64.encode(BIN_ARR);
expect(encoded).to.equal(B64_STR);
});
});
describe('decode', function() {
it('should decode a Base64 string into a normal string', function() {
var decoded = Base64.decode(B64_STR);
expect(decoded).to.deep.equal(BIN_ARR);
});
it('should throw an error if we have extra characters at the end of the string', function() {
expect(function () { Base64.decode(B64_STR+'abcdef'); }).to.throw(Error);
});
});
});

353
tests/test.display.js Normal file
View File

@ -0,0 +1,353 @@
// requires local modules: util, base64, display
// requires test modules: assertions
/* jshint expr: true */
var expect = chai.expect;
describe('Display/Canvas Helper', function () {
var checked_data = [
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
];
checked_data = new Uint8Array(checked_data);
var basic_data = [0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0xff, 0xff, 0xff, 255];
basic_data = new Uint8Array(basic_data);
function make_image_canvas (input_data) {
var canvas = document.createElement('canvas');
canvas.width = 4;
canvas.height = 4;
var ctx = canvas.getContext('2d');
var data = ctx.createImageData(4, 4);
for (var i = 0; i < checked_data.length; i++) { data.data[i] = input_data[i]; }
ctx.putImageData(data, 0, 0);
return canvas;
}
describe('checking for cursor uri support', function () {
beforeEach(function () {
this._old_change_cursor = Display.changeCursor;
});
it('should disable cursor URIs if there is no support', function () {
Display.changeCursor = function(target) {
target.style.cursor = undefined;
};
var display = new Display({ target: document.createElement('canvas'), prefer_js: true, viewport: false });
expect(display._cursor_uri).to.be.false;
});
it('should enable cursor URIs if there is support', function () {
Display.changeCursor = function(target) {
target.style.cursor = 'pointer';
};
var display = new Display({ target: document.createElement('canvas'), prefer_js: true, viewport: false });
expect(display._cursor_uri).to.be.true;
});
it('respect the cursor_uri option if there is support', function () {
Display.changeCursor = function(target) {
target.style.cursor = 'pointer';
};
var display = new Display({ target: document.createElement('canvas'), prefer_js: true, viewport: false, cursor_uri: false });
expect(display._cursor_uri).to.be.false;
});
afterEach(function () {
Display.changeCursor = this._old_change_cursor;
});
});
describe('viewport handling', function () {
var display;
beforeEach(function () {
display = new Display({ target: document.createElement('canvas'), prefer_js: false, viewport: true });
display.resize(5, 5);
display.viewportChange(1, 1, 3, 3);
display.getCleanDirtyReset();
});
it('should take viewport location into consideration when drawing images', function () {
display.resize(4, 4);
display.viewportChange(0, 0, 2, 2);
display.drawImage(make_image_canvas(basic_data), 1, 1);
var expected = new Uint8Array(16);
var i;
for (i = 0; i < 8; i++) { expected[i] = basic_data[i]; }
for (i = 8; i < 16; i++) { expected[i] = 0; }
expect(display).to.have.displayed(expected);
});
it('should redraw the left side when shifted left', function () {
display.viewportChange(-1, 0, 3, 3);
var cdr = display.getCleanDirtyReset();
expect(cdr.cleanBox).to.deep.equal({ x: 1, y: 1, w: 2, h: 3 });
expect(cdr.dirtyBoxes).to.have.length(1);
expect(cdr.dirtyBoxes[0]).to.deep.equal({ x: 0, y: 1, w: 2, h: 3 });
});
it('should redraw the right side when shifted right', function () {
display.viewportChange(1, 0, 3, 3);
var cdr = display.getCleanDirtyReset();
expect(cdr.cleanBox).to.deep.equal({ x: 2, y: 1, w: 2, h: 3 });
expect(cdr.dirtyBoxes).to.have.length(1);
expect(cdr.dirtyBoxes[0]).to.deep.equal({ x: 4, y: 1, w: 1, h: 3 });
});
it('should redraw the top part when shifted up', function () {
display.viewportChange(0, -1, 3, 3);
var cdr = display.getCleanDirtyReset();
expect(cdr.cleanBox).to.deep.equal({ x: 1, y: 1, w: 3, h: 2 });
expect(cdr.dirtyBoxes).to.have.length(1);
expect(cdr.dirtyBoxes[0]).to.deep.equal({ x: 1, y: 0, w: 3, h: 1 });
});
it('should redraw the bottom part when shifted down', function () {
display.viewportChange(0, 1, 3, 3);
var cdr = display.getCleanDirtyReset();
expect(cdr.cleanBox).to.deep.equal({ x: 1, y: 2, w: 3, h: 2 });
expect(cdr.dirtyBoxes).to.have.length(1);
expect(cdr.dirtyBoxes[0]).to.deep.equal({ x: 1, y: 4, w: 3, h: 1 });
});
it('should reset the entire viewport to being clean after calculating the clean/dirty boxes', function () {
display.viewportChange(0, 1, 3, 3);
var cdr1 = display.getCleanDirtyReset();
var cdr2 = display.getCleanDirtyReset();
expect(cdr1).to.not.deep.equal(cdr2);
expect(cdr2.cleanBox).to.deep.equal({ x: 1, y: 2, w: 3, h: 3 });
expect(cdr2.dirtyBoxes).to.be.empty;
});
it('should simply mark the whole display area as dirty if not using viewports', function () {
display = new Display({ target: document.createElement('canvas'), prefer_js: false, viewport: false });
display.resize(5, 5);
var cdr = display.getCleanDirtyReset();
expect(cdr.cleanBox).to.deep.equal({ x: 0, y: 0, w: 0, h: 0 });
expect(cdr.dirtyBoxes).to.have.length(1);
expect(cdr.dirtyBoxes[0]).to.deep.equal({ x: 0, y: 0, w: 5, h: 5 });
});
});
describe('resizing', function () {
var display;
beforeEach(function () {
display = new Display({ target: document.createElement('canvas'), prefer_js: false, viewport: true });
display.resize(4, 3);
});
it('should change the size of the logical canvas', function () {
display.resize(5, 7);
expect(display._fb_width).to.equal(5);
expect(display._fb_height).to.equal(7);
});
it('should update the viewport dimensions', function () {
sinon.spy(display, 'viewportChange');
display.resize(2, 2);
expect(display.viewportChange).to.have.been.calledOnce;
});
});
describe('drawing', function () {
// TODO(directxman12): improve the tests for each of the drawing functions to cover more than just the
// basic cases
function drawing_tests (pref_js) {
var display;
beforeEach(function () {
display = new Display({ target: document.createElement('canvas'), prefer_js: pref_js });
display.resize(4, 4);
});
it('should clear the screen on #clear without a logo set', function () {
display.fillRect(0, 0, 4, 4, [0x00, 0x00, 0xff]);
display._logo = null;
display.clear();
display.resize(4, 4);
var empty = [];
for (var i = 0; i < 4 * display._fb_width * display._fb_height; i++) { empty[i] = 0; }
expect(display).to.have.displayed(new Uint8Array(empty));
});
it('should draw the logo on #clear with a logo set', function (done) {
display._logo = { width: 4, height: 4, data: make_image_canvas(checked_data).toDataURL() };
display._drawCtx._act_drawImg = display._drawCtx.drawImage;
display._drawCtx.drawImage = function (img, x, y) {
this._act_drawImg(img, x, y);
expect(display).to.have.displayed(checked_data);
done();
};
display.clear();
expect(display._fb_width).to.equal(4);
expect(display._fb_height).to.equal(4);
});
it('should support filling a rectangle with particular color via #fillRect', function () {
display.fillRect(0, 0, 4, 4, [0, 0xff, 0]);
display.fillRect(0, 0, 2, 2, [0xff, 0, 0]);
display.fillRect(2, 2, 2, 2, [0xff, 0, 0]);
expect(display).to.have.displayed(checked_data);
});
it('should support copying an portion of the canvas via #copyImage', function () {
display.fillRect(0, 0, 4, 4, [0, 0xff, 0]);
display.fillRect(0, 0, 2, 2, [0xff, 0, 0x00]);
display.copyImage(0, 0, 2, 2, 2, 2);
expect(display).to.have.displayed(checked_data);
});
it('should support drawing tile data with a background color and sub tiles', function () {
display.startTile(0, 0, 4, 4, [0, 0xff, 0]);
display.subTile(0, 0, 2, 2, [0xff, 0, 0]);
display.subTile(2, 2, 2, 2, [0xff, 0, 0]);
display.finishTile();
expect(display).to.have.displayed(checked_data);
});
it('should support drawing BGRX blit images with true color via #blitImage', function () {
var data = [];
for (var i = 0; i < 16; i++) {
data[i * 4] = checked_data[i * 4 + 2];
data[i * 4 + 1] = checked_data[i * 4 + 1];
data[i * 4 + 2] = checked_data[i * 4];
data[i * 4 + 3] = checked_data[i * 4 + 3];
}
display.blitImage(0, 0, 4, 4, data, 0);
expect(display).to.have.displayed(checked_data);
});
it('should support drawing RGB blit images with true color via #blitRgbImage', function () {
var data = [];
for (var i = 0; i < 16; i++) {
data[i * 3] = checked_data[i * 4];
data[i * 3 + 1] = checked_data[i * 4 + 1];
data[i * 3 + 2] = checked_data[i * 4 + 2];
}
display.blitRgbImage(0, 0, 4, 4, data, 0);
expect(display).to.have.displayed(checked_data);
});
it('should support drawing blit images from a data URL via #blitStringImage', function (done) {
var img_url = make_image_canvas(checked_data).toDataURL();
display._drawCtx._act_drawImg = display._drawCtx.drawImage;
display._drawCtx.drawImage = function (img, x, y) {
this._act_drawImg(img, x, y);
expect(display).to.have.displayed(checked_data);
done();
};
display.blitStringImage(img_url, 0, 0);
});
it('should support drawing solid colors with color maps', function () {
display._true_color = false;
display.set_colourMap({ 0: [0xff, 0, 0], 1: [0, 0xff, 0] });
display.fillRect(0, 0, 4, 4, [1]);
display.fillRect(0, 0, 2, 2, [0]);
display.fillRect(2, 2, 2, 2, [0]);
expect(display).to.have.displayed(checked_data);
});
it('should support drawing blit images with color maps', function () {
display._true_color = false;
display.set_colourMap({ 1: [0xff, 0, 0], 0: [0, 0xff, 0] });
var data = [1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1].map(function (elem) { return [elem]; });
display.blitImage(0, 0, 4, 4, data, 0);
expect(display).to.have.displayed(checked_data);
});
it('should support drawing an image object via #drawImage', function () {
var img = make_image_canvas(checked_data);
display.drawImage(img, 0, 0);
expect(display).to.have.displayed(checked_data);
});
}
describe('(prefering native methods)', function () { drawing_tests.call(this, false); });
describe('(prefering JavaScript)', function () { drawing_tests.call(this, true); });
});
describe('the render queue processor', function () {
var display;
beforeEach(function () {
display = new Display({ target: document.createElement('canvas'), prefer_js: false });
display.resize(4, 4);
sinon.spy(display, '_scan_renderQ');
this.old_requestAnimFrame = window.requestAnimFrame;
window.requestAnimFrame = function (cb) {
this.next_frame_cb = cb;
}.bind(this);
this.next_frame = function () { this.next_frame_cb(); };
});
afterEach(function () {
window.requestAnimFrame = this.old_requestAnimFrame;
});
it('should try to process an item when it is pushed on, if nothing else is on the queue', function () {
display.renderQ_push({ type: 'noop' }); // does nothing
expect(display._scan_renderQ).to.have.been.calledOnce;
});
it('should not try to process an item when it is pushed on if we are waiting for other items', function () {
display._renderQ.length = 2;
display.renderQ_push({ type: 'noop' });
expect(display._scan_renderQ).to.not.have.been.called;
});
it('should wait until an image is loaded to attempt to draw it and the rest of the queue', function () {
var img = { complete: false };
display._renderQ = [{ type: 'img', x: 3, y: 4, img: img },
{ type: 'fill', x: 1, y: 2, width: 3, height: 4, color: 5 }];
display.drawImage = sinon.spy();
display.fillRect = sinon.spy();
display._scan_renderQ();
expect(display.drawImage).to.not.have.been.called;
expect(display.fillRect).to.not.have.been.called;
display._renderQ[0].img.complete = true;
this.next_frame();
expect(display.drawImage).to.have.been.calledOnce;
expect(display.fillRect).to.have.been.calledOnce;
});
it('should draw a blit image on type "blit"', function () {
display.blitImage = sinon.spy();
display.renderQ_push({ type: 'blit', x: 3, y: 4, width: 5, height: 6, data: [7, 8, 9] });
expect(display.blitImage).to.have.been.calledOnce;
expect(display.blitImage).to.have.been.calledWith(3, 4, 5, 6, [7, 8, 9], 0);
});
it('should draw a blit RGB image on type "blitRgb"', function () {
display.blitRgbImage = sinon.spy();
display.renderQ_push({ type: 'blitRgb', x: 3, y: 4, width: 5, height: 6, data: [7, 8, 9] });
expect(display.blitRgbImage).to.have.been.calledOnce;
expect(display.blitRgbImage).to.have.been.calledWith(3, 4, 5, 6, [7, 8, 9], 0);
});
it('should copy a region on type "copy"', function () {
display.copyImage = sinon.spy();
display.renderQ_push({ type: 'copy', x: 3, y: 4, width: 5, height: 6, old_x: 7, old_y: 8 });
expect(display.copyImage).to.have.been.calledOnce;
expect(display.copyImage).to.have.been.calledWith(7, 8, 3, 4, 5, 6);
});
it('should fill a rect with a given color on type "fill"', function () {
display.fillRect = sinon.spy();
display.renderQ_push({ type: 'fill', x: 3, y: 4, width: 5, height: 6, color: [7, 8, 9]});
expect(display.fillRect).to.have.been.calledOnce;
expect(display.fillRect).to.have.been.calledWith(3, 4, 5, 6, [7, 8, 9]);
});
it('should draw an image from an image object on type "img" (if complete)', function () {
display.drawImage = sinon.spy();
display.renderQ_push({ type: 'img', x: 3, y: 4, img: { complete: true } });
expect(display.drawImage).to.have.been.calledOnce;
expect(display.drawImage).to.have.been.calledWith({ complete: true }, 3, 4);
});
});
});

262
tests/test.helper.js Normal file
View File

@ -0,0 +1,262 @@
// requires local modules: keysym, keysymdef, keyboard
var assert = chai.assert;
var expect = chai.expect;
describe('Helpers', function() {
"use strict";
describe('keysymFromKeyCode', function() {
it('should map known keycodes to keysyms', function() {
expect(kbdUtil.keysymFromKeyCode(0x41, false), 'a').to.be.equal(0x61);
expect(kbdUtil.keysymFromKeyCode(0x41, true), 'A').to.be.equal(0x41);
expect(kbdUtil.keysymFromKeyCode(0xd, false), 'enter').to.be.equal(0xFF0D);
expect(kbdUtil.keysymFromKeyCode(0x11, false), 'ctrl').to.be.equal(0xFFE3);
expect(kbdUtil.keysymFromKeyCode(0x12, false), 'alt').to.be.equal(0xFFE9);
expect(kbdUtil.keysymFromKeyCode(0xe1, false), 'altgr').to.be.equal(0xFE03);
expect(kbdUtil.keysymFromKeyCode(0x1b, false), 'esc').to.be.equal(0xFF1B);
expect(kbdUtil.keysymFromKeyCode(0x26, false), 'up').to.be.equal(0xFF52);
});
it('should return null for unknown keycodes', function() {
expect(kbdUtil.keysymFromKeyCode(0xc0, false), 'DK æ').to.be.null;
expect(kbdUtil.keysymFromKeyCode(0xde, false), 'DK ø').to.be.null;
});
});
describe('keysyms.fromUnicode', function() {
it('should map ASCII characters to keysyms', function() {
expect(keysyms.fromUnicode('a'.charCodeAt())).to.have.property('keysym', 0x61);
expect(keysyms.fromUnicode('A'.charCodeAt())).to.have.property('keysym', 0x41);
});
it('should map Latin-1 characters to keysyms', function() {
expect(keysyms.fromUnicode('ø'.charCodeAt())).to.have.property('keysym', 0xf8);
expect(keysyms.fromUnicode('é'.charCodeAt())).to.have.property('keysym', 0xe9);
});
it('should map characters that are in Windows-1252 but not in Latin-1 to keysyms', function() {
expect(keysyms.fromUnicode('Š'.charCodeAt())).to.have.property('keysym', 0x01a9);
});
it('should map characters which aren\'t in Latin1 *or* Windows-1252 to keysyms', function() {
expect(keysyms.fromUnicode('ŵ'.charCodeAt())).to.have.property('keysym', 0x1000175);
});
it('should return undefined for unknown codepoints', function() {
expect(keysyms.fromUnicode('\n'.charCodeAt())).to.be.undefined;
expect(keysyms.fromUnicode('\u1F686'.charCodeAt())).to.be.undefined;
});
});
describe('substituteCodepoint', function() {
it('should replace characters which don\'t have a keysym', function() {
expect(kbdUtil.substituteCodepoint('Ș'.charCodeAt())).to.equal('Ş'.charCodeAt());
expect(kbdUtil.substituteCodepoint('ș'.charCodeAt())).to.equal('ş'.charCodeAt());
expect(kbdUtil.substituteCodepoint('Ț'.charCodeAt())).to.equal('Ţ'.charCodeAt());
expect(kbdUtil.substituteCodepoint('ț'.charCodeAt())).to.equal('ţ'.charCodeAt());
});
it('should pass other characters through unchanged', function() {
expect(kbdUtil.substituteCodepoint('T'.charCodeAt())).to.equal('T'.charCodeAt());
});
});
describe('nonCharacterKey', function() {
it('should recognize the right keys', function() {
expect(kbdUtil.nonCharacterKey({keyCode: 0xd}), 'enter').to.be.defined;
expect(kbdUtil.nonCharacterKey({keyCode: 0x08}), 'backspace').to.be.defined;
expect(kbdUtil.nonCharacterKey({keyCode: 0x09}), 'tab').to.be.defined;
expect(kbdUtil.nonCharacterKey({keyCode: 0x10}), 'shift').to.be.defined;
expect(kbdUtil.nonCharacterKey({keyCode: 0x11}), 'ctrl').to.be.defined;
expect(kbdUtil.nonCharacterKey({keyCode: 0x12}), 'alt').to.be.defined;
expect(kbdUtil.nonCharacterKey({keyCode: 0xe0}), 'meta').to.be.defined;
});
it('should not recognize character keys', function() {
expect(kbdUtil.nonCharacterKey({keyCode: 'A'}), 'A').to.be.null;
expect(kbdUtil.nonCharacterKey({keyCode: '1'}), '1').to.be.null;
expect(kbdUtil.nonCharacterKey({keyCode: '.'}), '.').to.be.null;
expect(kbdUtil.nonCharacterKey({keyCode: ' '}), 'space').to.be.null;
});
});
describe('getKeysym', function() {
it('should prefer char', function() {
expect(kbdUtil.getKeysym({char : 'a', charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43})).to.have.property('keysym', 0x61);
});
it('should use charCode if no char', function() {
expect(kbdUtil.getKeysym({char : '', charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43})).to.have.property('keysym', 0x01a9);
expect(kbdUtil.getKeysym({charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43})).to.have.property('keysym', 0x01a9);
expect(kbdUtil.getKeysym({char : 'hello', charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43})).to.have.property('keysym', 0x01a9);
});
it('should use keyCode if no charCode', function() {
expect(kbdUtil.getKeysym({keyCode: 0x42, which: 0x43, shiftKey: false})).to.have.property('keysym', 0x62);
expect(kbdUtil.getKeysym({keyCode: 0x42, which: 0x43, shiftKey: true})).to.have.property('keysym', 0x42);
});
it('should use which if no keyCode', function() {
expect(kbdUtil.getKeysym({which: 0x43, shiftKey: false})).to.have.property('keysym', 0x63);
expect(kbdUtil.getKeysym({which: 0x43, shiftKey: true})).to.have.property('keysym', 0x43);
});
it('should substitute where applicable', function() {
expect(kbdUtil.getKeysym({char : 'Ș'})).to.have.property('keysym', 0x1aa);
});
});
describe('Modifier Sync', function() { // return a list of fake events necessary to fix modifier state
describe('Toggle all modifiers', function() {
var sync = kbdUtil.ModifierSync();
it ('should do nothing if all modifiers are up as expected', function() {
expect(sync.keydown({
keyCode: 0x41,
ctrlKey: false,
altKey: false,
altGraphKey: false,
shiftKey: false,
metaKey: false})
).to.have.lengthOf(0);
});
it ('should synthesize events if all keys are unexpectedly down', function() {
var result = sync.keydown({
keyCode: 0x41,
ctrlKey: true,
altKey: true,
altGraphKey: true,
shiftKey: true,
metaKey: true
});
expect(result).to.have.lengthOf(5);
var keysyms = {};
for (var i = 0; i < result.length; ++i) {
keysyms[result[i].keysym] = (result[i].type == 'keydown');
}
expect(keysyms[0xffe3]);
expect(keysyms[0xffe9]);
expect(keysyms[0xfe03]);
expect(keysyms[0xffe1]);
expect(keysyms[0xffe7]);
});
it ('should do nothing if all modifiers are down as expected', function() {
expect(sync.keydown({
keyCode: 0x41,
ctrlKey: true,
altKey: true,
altGraphKey: true,
shiftKey: true,
metaKey: true
})).to.have.lengthOf(0);
});
});
describe('Toggle Ctrl', function() {
var sync = kbdUtil.ModifierSync();
it('should sync if modifier is suddenly down', function() {
expect(sync.keydown({
keyCode: 0x41,
ctrlKey: true,
})).to.be.deep.equal([{keysym: keysyms.lookup(0xffe3), type: 'keydown'}]);
});
it('should sync if modifier is suddenly up', function() {
expect(sync.keydown({
keyCode: 0x41,
ctrlKey: false
})).to.be.deep.equal([{keysym: keysyms.lookup(0xffe3), type: 'keyup'}]);
});
});
describe('Toggle Alt', function() {
var sync = kbdUtil.ModifierSync();
it('should sync if modifier is suddenly down', function() {
expect(sync.keydown({
keyCode: 0x41,
altKey: true,
})).to.be.deep.equal([{keysym: keysyms.lookup(0xffe9), type: 'keydown'}]);
});
it('should sync if modifier is suddenly up', function() {
expect(sync.keydown({
keyCode: 0x41,
altKey: false
})).to.be.deep.equal([{keysym: keysyms.lookup(0xffe9), type: 'keyup'}]);
});
});
describe('Toggle AltGr', function() {
var sync = kbdUtil.ModifierSync();
it('should sync if modifier is suddenly down', function() {
expect(sync.keydown({
keyCode: 0x41,
altGraphKey: true,
})).to.be.deep.equal([{keysym: keysyms.lookup(0xfe03), type: 'keydown'}]);
});
it('should sync if modifier is suddenly up', function() {
expect(sync.keydown({
keyCode: 0x41,
altGraphKey: false
})).to.be.deep.equal([{keysym: keysyms.lookup(0xfe03), type: 'keyup'}]);
});
});
describe('Toggle Shift', function() {
var sync = kbdUtil.ModifierSync();
it('should sync if modifier is suddenly down', function() {
expect(sync.keydown({
keyCode: 0x41,
shiftKey: true,
})).to.be.deep.equal([{keysym: keysyms.lookup(0xffe1), type: 'keydown'}]);
});
it('should sync if modifier is suddenly up', function() {
expect(sync.keydown({
keyCode: 0x41,
shiftKey: false
})).to.be.deep.equal([{keysym: keysyms.lookup(0xffe1), type: 'keyup'}]);
});
});
describe('Toggle Meta', function() {
var sync = kbdUtil.ModifierSync();
it('should sync if modifier is suddenly down', function() {
expect(sync.keydown({
keyCode: 0x41,
metaKey: true,
})).to.be.deep.equal([{keysym: keysyms.lookup(0xffe7), type: 'keydown'}]);
});
it('should sync if modifier is suddenly up', function() {
expect(sync.keydown({
keyCode: 0x41,
metaKey: false
})).to.be.deep.equal([{keysym: keysyms.lookup(0xffe7), type: 'keyup'}]);
});
});
describe('Modifier keyevents', function() {
it('should not sync a modifier on its own events', function() {
expect(kbdUtil.ModifierSync().keydown({
keyCode: 0x11,
ctrlKey: false
})).to.be.deep.equal([]);
expect(kbdUtil.ModifierSync().keydown({
keyCode: 0x11,
ctrlKey: true
}), 'B').to.be.deep.equal([]);
})
it('should update state on modifier keyevents', function() {
var sync = kbdUtil.ModifierSync();
sync.keydown({
keyCode: 0x11,
});
expect(sync.keydown({
keyCode: 0x41,
ctrlKey: true,
})).to.be.deep.equal([]);
});
it('should sync other modifiers on ctrl events', function() {
expect(kbdUtil.ModifierSync().keydown({
keyCode: 0x11,
altKey: true
})).to.be.deep.equal([{keysym: keysyms.lookup(0xffe9), type: 'keydown'}]);
})
});
describe('sync modifiers on non-key events', function() {
it('should generate sync events when receiving non-keyboard events', function() {
expect(kbdUtil.ModifierSync().syncAny({
altKey: true
})).to.be.deep.equal([{keysym: keysyms.lookup(0xffe9), type: 'keydown'}]);
});
});
describe('do not treat shift as a modifier key', function() {
it('should not treat shift as a shortcut modifier', function() {
expect(kbdUtil.hasShortcutModifier([], {0xffe1 : true})).to.be.false;
});
it('should not treat shift as a char modifier', function() {
expect(kbdUtil.hasCharModifier([], {0xffe1 : true})).to.be.false;
});
});
});
});

842
tests/test.keyboard.js Normal file
View File

@ -0,0 +1,842 @@
// requires local modules: input, keyboard, keysymdef
var assert = chai.assert;
var expect = chai.expect;
/* jshint newcap: false, expr: true */
describe('Key Event Pipeline Stages', function() {
"use strict";
describe('Decode Keyboard Events', function() {
it('should pass events to the next stage', function(done) {
KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {
expect(evt).to.be.an.object;
done();
}).keydown({keyCode: 0x41});
});
it('should pass the right keysym through', function(done) {
KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {
expect(evt.keysym).to.be.deep.equal(keysyms.lookup(0x61));
done();
}).keypress({keyCode: 0x41});
});
it('should pass the right keyid through', function(done) {
KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {
expect(evt).to.have.property('keyId', 0x41);
done();
}).keydown({keyCode: 0x41});
});
it('should not sync modifiers on a keypress', function() {
// Firefox provides unreliable modifier state on keypress events
var count = 0;
KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {
++count;
}).keypress({keyCode: 0x41, ctrlKey: true});
expect(count).to.be.equal(1);
});
it('should sync modifiers if necessary', function(done) {
var count = 0;
KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {
switch (count) {
case 0: // fake a ctrl keydown
expect(evt).to.be.deep.equal({keysym: keysyms.lookup(0xffe3), type: 'keydown'});
++count;
break;
case 1:
expect(evt).to.be.deep.equal({keyId: 0x41, type: 'keydown', keysym: keysyms.lookup(0x61)});
done();
break;
}
}).keydown({keyCode: 0x41, ctrlKey: true});
});
it('should forward keydown events with the right type', function(done) {
KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {
expect(evt).to.be.deep.equal({keyId: 0x41, type: 'keydown'});
done();
}).keydown({keyCode: 0x41});
});
it('should forward keyup events with the right type', function(done) {
KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {
expect(evt).to.be.deep.equal({keyId: 0x41, keysym: keysyms.lookup(0x61), type: 'keyup'});
done();
}).keyup({keyCode: 0x41});
});
it('should forward keypress events with the right type', function(done) {
KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {
expect(evt).to.be.deep.equal({keyId: 0x41, keysym: keysyms.lookup(0x61), type: 'keypress'});
done();
}).keypress({keyCode: 0x41});
});
it('should generate stalls if a char modifier is down while a key is pressed', function(done) {
var count = 0;
KeyEventDecoder(kbdUtil.ModifierSync([0xfe03]), function(evt) {
switch (count) {
case 0: // fake altgr
expect(evt).to.be.deep.equal({keysym: keysyms.lookup(0xfe03), type: 'keydown'});
++count;
break;
case 1: // stall before processing the 'a' keydown
expect(evt).to.be.deep.equal({type: 'stall'});
++count;
break;
case 2: // 'a'
expect(evt).to.be.deep.equal({
type: 'keydown',
keyId: 0x41,
keysym: keysyms.lookup(0x61)
});
done();
break;
}
}).keydown({keyCode: 0x41, altGraphKey: true});
});
describe('suppress the right events at the right time', function() {
it('should suppress anything while a shortcut modifier is down', function() {
var obj = KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {});
obj.keydown({keyCode: 0x11}); // press ctrl
expect(obj.keydown({keyCode: 'A'.charCodeAt()})).to.be.true;
expect(obj.keydown({keyCode: ' '.charCodeAt()})).to.be.true;
expect(obj.keydown({keyCode: '1'.charCodeAt()})).to.be.true;
expect(obj.keydown({keyCode: 0x3c})).to.be.true; // < key on DK Windows
expect(obj.keydown({keyCode: 0xde})).to.be.true; // Ø key on DK
});
it('should suppress non-character keys', function() {
var obj = KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {});
expect(obj.keydown({keyCode: 0x08}), 'a').to.be.true;
expect(obj.keydown({keyCode: 0x09}), 'b').to.be.true;
expect(obj.keydown({keyCode: 0x11}), 'd').to.be.true;
expect(obj.keydown({keyCode: 0x12}), 'e').to.be.true;
});
it('should not suppress shift', function() {
var obj = KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {});
expect(obj.keydown({keyCode: 0x10}), 'd').to.be.false;
});
it('should generate event for shift keydown', function() {
var called = false;
var obj = KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {
expect(evt).to.have.property('keysym');
called = true;
}).keydown({keyCode: 0x10});
expect(called).to.be.true;
});
it('should not suppress character keys', function() {
var obj = KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {});
expect(obj.keydown({keyCode: 'A'.charCodeAt()})).to.be.false;
expect(obj.keydown({keyCode: ' '.charCodeAt()})).to.be.false;
expect(obj.keydown({keyCode: '1'.charCodeAt()})).to.be.false;
expect(obj.keydown({keyCode: 0x3c})).to.be.false; // < key on DK Windows
expect(obj.keydown({keyCode: 0xde})).to.be.false; // Ø key on DK
});
it('should not suppress if a char modifier is down', function() {
var obj = KeyEventDecoder(kbdUtil.ModifierSync([0xfe03]), function(evt) {});
obj.keydown({keyCode: 0xe1}); // press altgr
expect(obj.keydown({keyCode: 'A'.charCodeAt()})).to.be.false;
expect(obj.keydown({keyCode: ' '.charCodeAt()})).to.be.false;
expect(obj.keydown({keyCode: '1'.charCodeAt()})).to.be.false;
expect(obj.keydown({keyCode: 0x3c})).to.be.false; // < key on DK Windows
expect(obj.keydown({keyCode: 0xde})).to.be.false; // Ø key on DK
});
});
describe('Keypress and keyup events', function() {
it('should always suppress event propagation', function() {
var obj = KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {});
expect(obj.keypress({keyCode: 'A'.charCodeAt()})).to.be.true;
expect(obj.keypress({keyCode: 0x3c})).to.be.true; // < key on DK Windows
expect(obj.keypress({keyCode: 0x11})).to.be.true;
expect(obj.keyup({keyCode: 'A'.charCodeAt()})).to.be.true;
expect(obj.keyup({keyCode: 0x3c})).to.be.true; // < key on DK Windows
expect(obj.keyup({keyCode: 0x11})).to.be.true;
});
it('should never generate stalls', function() {
var obj = KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {
expect(evt.type).to.not.be.equal('stall');
});
obj.keypress({keyCode: 'A'.charCodeAt()});
obj.keypress({keyCode: 0x3c});
obj.keypress({keyCode: 0x11});
obj.keyup({keyCode: 'A'.charCodeAt()});
obj.keyup({keyCode: 0x3c});
obj.keyup({keyCode: 0x11});
});
});
describe('mark events if a char modifier is down', function() {
it('should not mark modifiers on a keydown event', function() {
var times_called = 0;
var obj = KeyEventDecoder(kbdUtil.ModifierSync([0xfe03]), function(evt) {
switch (times_called++) {
case 0: //altgr
break;
case 1: // 'a'
expect(evt).to.not.have.property('escape');
break;
}
});
obj.keydown({keyCode: 0xe1}); // press altgr
obj.keydown({keyCode: 'A'.charCodeAt()});
});
it('should indicate on events if a single-key char modifier is down', function(done) {
var times_called = 0;
var obj = KeyEventDecoder(kbdUtil.ModifierSync([0xfe03]), function(evt) {
switch (times_called++) {
case 0: //altgr
break;
case 1: // 'a'
expect(evt).to.be.deep.equal({
type: 'keypress',
keyId: 'A'.charCodeAt(),
keysym: keysyms.lookup('a'.charCodeAt()),
escape: [0xfe03]
});
done();
return;
}
});
obj.keydown({keyCode: 0xe1}); // press altgr
obj.keypress({keyCode: 'A'.charCodeAt()});
});
it('should indicate on events if a multi-key char modifier is down', function(done) {
var times_called = 0;
var obj = KeyEventDecoder(kbdUtil.ModifierSync([0xffe9, 0xffe3]), function(evt) {
switch (times_called++) {
case 0: //ctrl
break;
case 1: //alt
break;
case 2: // 'a'
expect(evt).to.be.deep.equal({
type: 'keypress',
keyId: 'A'.charCodeAt(),
keysym: keysyms.lookup('a'.charCodeAt()),
escape: [0xffe9, 0xffe3]
});
done();
return;
}
});
obj.keydown({keyCode: 0x11}); // press ctrl
obj.keydown({keyCode: 0x12}); // press alt
obj.keypress({keyCode: 'A'.charCodeAt()});
});
it('should not consider a char modifier to be down on the modifier key itself', function() {
var obj = KeyEventDecoder(kbdUtil.ModifierSync([0xfe03]), function(evt) {
expect(evt).to.not.have.property('escape');
});
obj.keydown({keyCode: 0xe1}); // press altgr
});
});
describe('add/remove keysym', function() {
it('should remove keysym from keydown if a char key and no modifier', function() {
KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {
expect(evt).to.be.deep.equal({keyId: 0x41, type: 'keydown'});
}).keydown({keyCode: 0x41});
});
it('should not remove keysym from keydown if a shortcut modifier is down', function() {
var times_called = 0;
KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {
switch (times_called++) {
case 1:
expect(evt).to.be.deep.equal({keyId: 0x41, keysym: keysyms.lookup(0x61), type: 'keydown'});
break;
}
}).keydown({keyCode: 0x41, ctrlKey: true});
expect(times_called).to.be.equal(2);
});
it('should not remove keysym from keydown if a char modifier is down', function() {
var times_called = 0;
KeyEventDecoder(kbdUtil.ModifierSync([0xfe03]), function(evt) {
switch (times_called++) {
case 2:
expect(evt).to.be.deep.equal({keyId: 0x41, keysym: keysyms.lookup(0x61), type: 'keydown'});
break;
}
}).keydown({keyCode: 0x41, altGraphKey: true});
expect(times_called).to.be.equal(3);
});
it('should not remove keysym from keydown if key is noncharacter', function() {
KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {
expect(evt, 'bacobjpace').to.be.deep.equal({keyId: 0x09, keysym: keysyms.lookup(0xff09), type: 'keydown'});
}).keydown({keyCode: 0x09});
KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {
expect(evt, 'ctrl').to.be.deep.equal({keyId: 0x11, keysym: keysyms.lookup(0xffe3), type: 'keydown'});
}).keydown({keyCode: 0x11});
});
it('should never remove keysym from keypress', function() {
KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {
expect(evt).to.be.deep.equal({keyId: 0x41, keysym: keysyms.lookup(0x61), type: 'keypress'});
}).keypress({keyCode: 0x41});
});
it('should never remove keysym from keyup', function() {
KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {
expect(evt).to.be.deep.equal({keyId: 0x41, keysym: keysyms.lookup(0x61), type: 'keyup'});
}).keyup({keyCode: 0x41});
});
});
// on keypress, keyup(?), always set keysym
// on keydown, only do it if we don't expect a keypress: if noncharacter OR modifier is down
});
describe('Verify that char modifiers are active', function() {
it('should pass keydown events through if there is no stall', function(done) {
var obj = VerifyCharModifier(function(evt){
expect(evt).to.deep.equal({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x41)});
done();
})({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x41)});
});
it('should pass keyup events through if there is no stall', function(done) {
var obj = VerifyCharModifier(function(evt){
expect(evt).to.deep.equal({type: 'keyup', keyId: 0x41, keysym: keysyms.lookup(0x41)});
done();
})({type: 'keyup', keyId: 0x41, keysym: keysyms.lookup(0x41)});
});
it('should pass keypress events through if there is no stall', function(done) {
var obj = VerifyCharModifier(function(evt){
expect(evt).to.deep.equal({type: 'keypress', keyId: 0x41, keysym: keysyms.lookup(0x41)});
done();
})({type: 'keypress', keyId: 0x41, keysym: keysyms.lookup(0x41)});
});
it('should not pass stall events through', function(done){
var obj = VerifyCharModifier(function(evt){
// should only be called once, for the keydown
expect(evt).to.deep.equal({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x41)});
done();
});
obj({type: 'stall'});
obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x41)});
});
it('should merge keydown and keypress events if they come after a stall', function(done) {
var next_called = false;
var obj = VerifyCharModifier(function(evt){
// should only be called once, for the keydown
expect(next_called).to.be.false;
next_called = true;
expect(evt).to.deep.equal({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x44)});
done();
});
obj({type: 'stall'});
obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)});
obj({type: 'keypress', keyId: 0x43, keysym: keysyms.lookup(0x44)});
expect(next_called).to.be.false;
});
it('should preserve modifier attribute when merging if keysyms differ', function(done) {
var next_called = false;
var obj = VerifyCharModifier(function(evt){
// should only be called once, for the keydown
expect(next_called).to.be.false;
next_called = true;
expect(evt).to.deep.equal({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x44), escape: [0xffe3]});
done();
});
obj({type: 'stall'});
obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)});
obj({type: 'keypress', keyId: 0x43, keysym: keysyms.lookup(0x44), escape: [0xffe3]});
expect(next_called).to.be.false;
});
it('should not preserve modifier attribute when merging if keysyms are the same', function() {
var obj = VerifyCharModifier(function(evt){
expect(evt).to.not.have.property('escape');
});
obj({type: 'stall'});
obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)});
obj({type: 'keypress', keyId: 0x43, keysym: keysyms.lookup(0x42), escape: [0xffe3]});
});
it('should not merge keydown and keypress events if there is no stall', function(done) {
var times_called = 0;
var obj = VerifyCharModifier(function(evt){
switch(times_called) {
case 0:
expect(evt).to.deep.equal({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)});
break;
case 1:
expect(evt).to.deep.equal({type: 'keypress', keyId: 0x43, keysym: keysyms.lookup(0x44)});
done();
break;
}
++times_called;
});
obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)});
obj({type: 'keypress', keyId: 0x43, keysym: keysyms.lookup(0x44)});
});
it('should not merge keydown and keypress events if separated by another event', function(done) {
var times_called = 0;
var obj = VerifyCharModifier(function(evt){
switch(times_called) {
case 0:
expect(evt,1).to.deep.equal({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)});
break;
case 1:
expect(evt,2).to.deep.equal({type: 'keyup', keyId: 0x43, keysym: keysyms.lookup(0x44)});
break;
case 2:
expect(evt,3).to.deep.equal({type: 'keypress', keyId: 0x45, keysym: keysyms.lookup(0x46)});
done();
break;
}
++times_called;
});
obj({type: 'stall'});
obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)});
obj({type: 'keyup', keyId: 0x43, keysym: keysyms.lookup(0x44)});
obj({type: 'keypress', keyId: 0x45, keysym: keysyms.lookup(0x46)});
});
});
describe('Track Key State', function() {
it('should do nothing on keyup events if no keys are down', function() {
var obj = TrackKeyState(function(evt) {
expect(true).to.be.false;
});
obj({type: 'keyup', keyId: 0x41});
});
it('should insert into the queue on keydown if no keys are down', function() {
var times_called = 0;
var elem = null;
var keysymsdown = {};
var obj = TrackKeyState(function(evt) {
++times_called;
if (elem.type == 'keyup') {
expect(evt).to.have.property('keysym');
expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
delete keysymsdown[evt.keysym.keysym];
}
else {
expect(evt).to.be.deep.equal(elem);
expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
}
elem = null;
});
expect(elem).to.be.null;
elem = {type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)};
keysymsdown[keysyms.lookup(0x42).keysym] = true;
obj(elem);
expect(elem).to.be.null;
elem = {type: 'keyup', keyId: 0x41};
obj(elem);
expect(elem).to.be.null;
expect(times_called).to.be.equal(2);
});
it('should insert into the queue on keypress if no keys are down', function() {
var times_called = 0;
var elem = null;
var keysymsdown = {};
var obj = TrackKeyState(function(evt) {
++times_called;
if (elem.type == 'keyup') {
expect(evt).to.have.property('keysym');
expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
delete keysymsdown[evt.keysym.keysym];
}
else {
expect(evt).to.be.deep.equal(elem);
expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
}
elem = null;
});
expect(elem).to.be.null;
elem = {type: 'keypress', keyId: 0x41, keysym: keysyms.lookup(0x42)};
keysymsdown[keysyms.lookup(0x42).keysym] = true;
obj(elem);
expect(elem).to.be.null;
elem = {type: 'keyup', keyId: 0x41};
obj(elem);
expect(elem).to.be.null;
expect(times_called).to.be.equal(2);
});
it('should add keysym to last key entry if keyId matches', function() {
// this implies that a single keyup will release both keysyms
var times_called = 0;
var elem = null;
var keysymsdown = {};
var obj = TrackKeyState(function(evt) {
++times_called;
if (elem.type == 'keyup') {
expect(evt).to.have.property('keysym');
expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
delete keysymsdown[evt.keysym.keysym];
}
else {
expect(evt).to.be.deep.equal(elem);
expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
elem = null;
}
});
expect(elem).to.be.null;
elem = {type: 'keypress', keyId: 0x41, keysym: keysyms.lookup(0x42)};
keysymsdown[keysyms.lookup(0x42).keysym] = true;
obj(elem);
expect(elem).to.be.null;
elem = {type: 'keypress', keyId: 0x41, keysym: keysyms.lookup(0x43)};
keysymsdown[keysyms.lookup(0x43).keysym] = true;
obj(elem);
expect(elem).to.be.null;
elem = {type: 'keyup', keyId: 0x41};
obj(elem);
expect(times_called).to.be.equal(4);
});
it('should create new key entry if keyId matches and keysym does not', function() {
// this implies that a single keyup will release both keysyms
var times_called = 0;
var elem = null;
var keysymsdown = {};
var obj = TrackKeyState(function(evt) {
++times_called;
if (elem.type == 'keyup') {
expect(evt).to.have.property('keysym');
expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
delete keysymsdown[evt.keysym.keysym];
}
else {
expect(evt).to.be.deep.equal(elem);
expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
elem = null;
}
});
expect(elem).to.be.null;
elem = {type: 'keydown', keyId: 0, keysym: keysyms.lookup(0x42)};
keysymsdown[keysyms.lookup(0x42).keysym] = true;
obj(elem);
expect(elem).to.be.null;
elem = {type: 'keydown', keyId: 0, keysym: keysyms.lookup(0x43)};
keysymsdown[keysyms.lookup(0x43).keysym] = true;
obj(elem);
expect(times_called).to.be.equal(2);
expect(elem).to.be.null;
elem = {type: 'keyup', keyId: 0};
obj(elem);
expect(times_called).to.be.equal(3);
elem = {type: 'keyup', keyId: 0};
obj(elem);
expect(times_called).to.be.equal(4);
});
it('should merge key entry if keyIds are zero and keysyms match', function() {
// this implies that a single keyup will release both keysyms
var times_called = 0;
var elem = null;
var keysymsdown = {};
var obj = TrackKeyState(function(evt) {
++times_called;
if (elem.type == 'keyup') {
expect(evt).to.have.property('keysym');
expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
delete keysymsdown[evt.keysym.keysym];
}
else {
expect(evt).to.be.deep.equal(elem);
expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
elem = null;
}
});
expect(elem).to.be.null;
elem = {type: 'keydown', keyId: 0, keysym: keysyms.lookup(0x42)};
keysymsdown[keysyms.lookup(0x42).keysym] = true;
obj(elem);
expect(elem).to.be.null;
elem = {type: 'keydown', keyId: 0, keysym: keysyms.lookup(0x42)};
keysymsdown[keysyms.lookup(0x42).keysym] = true;
obj(elem);
expect(times_called).to.be.equal(2);
expect(elem).to.be.null;
elem = {type: 'keyup', keyId: 0};
obj(elem);
expect(times_called).to.be.equal(3);
});
it('should add keysym as separate entry if keyId does not match last event', function() {
// this implies that separate keyups are required
var times_called = 0;
var elem = null;
var keysymsdown = {};
var obj = TrackKeyState(function(evt) {
++times_called;
if (elem.type == 'keyup') {
expect(evt).to.have.property('keysym');
expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
delete keysymsdown[evt.keysym.keysym];
}
else {
expect(evt).to.be.deep.equal(elem);
expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
elem = null;
}
});
expect(elem).to.be.null;
elem = {type: 'keypress', keyId: 0x41, keysym: keysyms.lookup(0x42)};
keysymsdown[keysyms.lookup(0x42).keysym] = true;
obj(elem);
expect(elem).to.be.null;
elem = {type: 'keypress', keyId: 0x42, keysym: keysyms.lookup(0x43)};
keysymsdown[keysyms.lookup(0x43).keysym] = true;
obj(elem);
expect(elem).to.be.null;
elem = {type: 'keyup', keyId: 0x41};
obj(elem);
expect(times_called).to.be.equal(4);
elem = {type: 'keyup', keyId: 0x42};
obj(elem);
expect(times_called).to.be.equal(4);
});
it('should add keysym as separate entry if keyId does not match last event and first is zero', function() {
// this implies that separate keyups are required
var times_called = 0;
var elem = null;
var keysymsdown = {};
var obj = TrackKeyState(function(evt) {
++times_called;
if (elem.type == 'keyup') {
expect(evt).to.have.property('keysym');
expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
delete keysymsdown[evt.keysym.keysym];
}
else {
expect(evt).to.be.deep.equal(elem);
expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
elem = null;
}
});
expect(elem).to.be.null;
elem = {type: 'keydown', keyId: 0, keysym: keysyms.lookup(0x42)};
keysymsdown[keysyms.lookup(0x42).keysym] = true;
obj(elem);
expect(elem).to.be.null;
elem = {type: 'keydown', keyId: 0x42, keysym: keysyms.lookup(0x43)};
keysymsdown[keysyms.lookup(0x43).keysym] = true;
obj(elem);
expect(elem).to.be.null;
expect(times_called).to.be.equal(2);
elem = {type: 'keyup', keyId: 0};
obj(elem);
expect(times_called).to.be.equal(3);
elem = {type: 'keyup', keyId: 0x42};
obj(elem);
expect(times_called).to.be.equal(4);
});
it('should add keysym as separate entry if keyId does not match last event and second is zero', function() {
// this implies that a separate keyups are required
var times_called = 0;
var elem = null;
var keysymsdown = {};
var obj = TrackKeyState(function(evt) {
++times_called;
if (elem.type == 'keyup') {
expect(evt).to.have.property('keysym');
expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
delete keysymsdown[evt.keysym.keysym];
}
else {
expect(evt).to.be.deep.equal(elem);
expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
elem = null;
}
});
expect(elem).to.be.null;
elem = {type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)};
keysymsdown[keysyms.lookup(0x42).keysym] = true;
obj(elem);
expect(elem).to.be.null;
elem = {type: 'keydown', keyId: 0, keysym: keysyms.lookup(0x43)};
keysymsdown[keysyms.lookup(0x43).keysym] = true;
obj(elem);
expect(elem).to.be.null;
elem = {type: 'keyup', keyId: 0x41};
obj(elem);
expect(times_called).to.be.equal(3);
elem = {type: 'keyup', keyId: 0};
obj(elem);
expect(times_called).to.be.equal(4);
});
it('should pop matching key event on keyup', function() {
var times_called = 0;
var obj = TrackKeyState(function(evt) {
switch (times_called++) {
case 0:
case 1:
case 2:
expect(evt.type).to.be.equal('keydown');
break;
case 3:
expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0x42, keysym: keysyms.lookup(0x62)});
break;
}
});
obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x61)});
obj({type: 'keydown', keyId: 0x42, keysym: keysyms.lookup(0x62)});
obj({type: 'keydown', keyId: 0x43, keysym: keysyms.lookup(0x63)});
obj({type: 'keyup', keyId: 0x42});
expect(times_called).to.equal(4);
});
it('should pop the first zero keyevent on keyup with zero keyId', function() {
var times_called = 0;
var obj = TrackKeyState(function(evt) {
switch (times_called++) {
case 0:
case 1:
case 2:
expect(evt.type).to.be.equal('keydown');
break;
case 3:
expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0, keysym: keysyms.lookup(0x61)});
break;
}
});
obj({type: 'keydown', keyId: 0, keysym: keysyms.lookup(0x61)});
obj({type: 'keydown', keyId: 0, keysym: keysyms.lookup(0x62)});
obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x63)});
obj({type: 'keyup', keyId: 0x0});
expect(times_called).to.equal(4);
});
it('should pop the last keyevents keysym if no match is found for keyId', function() {
var times_called = 0;
var obj = TrackKeyState(function(evt) {
switch (times_called++) {
case 0:
case 1:
case 2:
expect(evt.type).to.be.equal('keydown');
break;
case 3:
expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0x44, keysym: keysyms.lookup(0x63)});
break;
}
});
obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x61)});
obj({type: 'keydown', keyId: 0x42, keysym: keysyms.lookup(0x62)});
obj({type: 'keydown', keyId: 0x43, keysym: keysyms.lookup(0x63)});
obj({type: 'keyup', keyId: 0x44});
expect(times_called).to.equal(4);
});
describe('Firefox sends keypress even when keydown is suppressed', function() {
it('should discard the keypress', function() {
var times_called = 0;
var obj = TrackKeyState(function(evt) {
expect(times_called).to.be.equal(0);
++times_called;
});
obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)});
expect(times_called).to.be.equal(1);
obj({type: 'keypress', keyId: 0x41, keysym: keysyms.lookup(0x43)});
});
});
describe('releaseAll', function() {
it('should do nothing if no keys have been pressed', function() {
var times_called = 0;
var obj = TrackKeyState(function(evt) {
++times_called;
});
obj({type: 'releaseall'});
expect(times_called).to.be.equal(0);
});
it('should release the keys that have been pressed', function() {
var times_called = 0;
var obj = TrackKeyState(function(evt) {
switch (times_called++) {
case 2:
expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0, keysym: keysyms.lookup(0x41)});
break;
case 3:
expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0, keysym: keysyms.lookup(0x42)});
break;
}
});
obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x41)});
obj({type: 'keydown', keyId: 0x42, keysym: keysyms.lookup(0x42)});
expect(times_called).to.be.equal(2);
obj({type: 'releaseall'});
expect(times_called).to.be.equal(4);
obj({type: 'releaseall'});
expect(times_called).to.be.equal(4);
});
});
});
describe('Escape Modifiers', function() {
describe('Keydown', function() {
it('should pass through when a char modifier is not down', function() {
var times_called = 0;
EscapeModifiers(function(evt) {
expect(times_called).to.be.equal(0);
++times_called;
expect(evt).to.be.deep.equal({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)});
})({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)});
expect(times_called).to.be.equal(1);
});
it('should generate fake undo/redo events when a char modifier is down', function() {
var times_called = 0;
EscapeModifiers(function(evt) {
switch(times_called++) {
case 0:
expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0, keysym: keysyms.lookup(0xffe9)});
break;
case 1:
expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0, keysym: keysyms.lookup(0xffe3)});
break;
case 2:
expect(evt).to.be.deep.equal({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42), escape: [0xffe9, 0xffe3]});
break;
case 3:
expect(evt).to.be.deep.equal({type: 'keydown', keyId: 0, keysym: keysyms.lookup(0xffe9)});
break;
case 4:
expect(evt).to.be.deep.equal({type: 'keydown', keyId: 0, keysym: keysyms.lookup(0xffe3)});
break;
}
})({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42), escape: [0xffe9, 0xffe3]});
expect(times_called).to.be.equal(5);
});
});
describe('Keyup', function() {
it('should pass through when a char modifier is down', function() {
var times_called = 0;
EscapeModifiers(function(evt) {
expect(times_called).to.be.equal(0);
++times_called;
expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0x41, keysym: keysyms.lookup(0x42), escape: [0xfe03]});
})({type: 'keyup', keyId: 0x41, keysym: keysyms.lookup(0x42), escape: [0xfe03]});
expect(times_called).to.be.equal(1);
});
it('should pass through when a char modifier is not down', function() {
var times_called = 0;
EscapeModifiers(function(evt) {
expect(times_called).to.be.equal(0);
++times_called;
expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0x41, keysym: keysyms.lookup(0x42)});
})({type: 'keyup', keyId: 0x41, keysym: keysyms.lookup(0x42)});
expect(times_called).to.be.equal(1);
});
});
});
});

1716
tests/test.rfb.js Normal file

File diff suppressed because it is too large Load Diff

105
tests/test.util.js Normal file
View File

@ -0,0 +1,105 @@
// requires local modules: util
/* jshint expr: true */
var assert = chai.assert;
var expect = chai.expect;
describe('Utils', function() {
"use strict";
describe('Array instance methods', function () {
describe('push8', function () {
it('should push a byte on to the array', function () {
var arr = [1];
arr.push8(128);
expect(arr).to.deep.equal([1, 128]);
});
it('should only use the least significant byte of any number passed in', function () {
var arr = [1];
arr.push8(0xABCD);
expect(arr).to.deep.equal([1, 0xCD]);
});
});
describe('push16', function () {
it('should push two bytes on to the array', function () {
var arr = [1];
arr.push16(0xABCD);
expect(arr).to.deep.equal([1, 0xAB, 0xCD]);
});
it('should only use the two least significant bytes of any number passed in', function () {
var arr = [1];
arr.push16(0xABCDEF);
expect(arr).to.deep.equal([1, 0xCD, 0xEF]);
});
});
describe('push32', function () {
it('should push four bytes on to the array', function () {
var arr = [1];
arr.push32(0xABCDEF12);
expect(arr).to.deep.equal([1, 0xAB, 0xCD, 0xEF, 0x12]);
});
it('should only use the four least significant bytes of any number passed in', function () {
var arr = [1];
arr.push32(0xABCDEF1234);
expect(arr).to.deep.equal([1, 0xCD, 0xEF, 0x12, 0x34]);
});
});
});
describe('logging functions', function () {
beforeEach(function () {
sinon.spy(console, 'log');
sinon.spy(console, 'warn');
sinon.spy(console, 'error');
});
afterEach(function () {
console.log.restore();
console.warn.restore();
console.error.restore();
});
it('should use noop for levels lower than the min level', function () {
Util.init_logging('warn');
Util.Debug('hi');
Util.Info('hello');
expect(console.log).to.not.have.been.called;
});
it('should use console.log for Debug and Info', function () {
Util.init_logging('debug');
Util.Debug('dbg');
Util.Info('inf');
expect(console.log).to.have.been.calledWith('dbg');
expect(console.log).to.have.been.calledWith('inf');
});
it('should use console.warn for Warn', function () {
Util.init_logging('warn');
Util.Warn('wrn');
expect(console.warn).to.have.been.called;
expect(console.warn).to.have.been.calledWith('wrn');
});
it('should use console.error for Error', function () {
Util.init_logging('error');
Util.Error('err');
expect(console.error).to.have.been.called;
expect(console.error).to.have.been.calledWith('err');
});
});
// TODO(directxman12): test the conf_default and conf_defaults methods
// TODO(directxman12): test decodeUTF8
// TODO(directxman12): test the event methods (addEvent, removeEvent, stopEvent)
// TODO(directxman12): figure out a good way to test getPosition and getEventPosition
// TODO(directxman12): figure out how to test the browser detection functions properly
// (we can't really test them against the browsers, except for Gecko
// via PhantomJS, the default test driver)
// TODO(directxman12): figure out how to test Util.Flash
});

480
tests/test.websock.js Normal file
View File

@ -0,0 +1,480 @@
// requires local modules: websock, base64, util
// requires test modules: fake.websocket
/* jshint expr: true */
var assert = chai.assert;
var expect = chai.expect;
describe('Websock', function() {
"use strict";
describe('Queue methods', function () {
var sock;
var RQ_TEMPLATE = [0, 1, 2, 3, 4, 5, 6, 7];
beforeEach(function () {
sock = new Websock();
for (var i = RQ_TEMPLATE.length - 1; i >= 0; i--) {
sock.rQunshift8(RQ_TEMPLATE[i]);
}
});
describe('rQlen', function () {
it('should return the length of the receive queue', function () {
sock.set_rQi(0);
expect(sock.rQlen()).to.equal(RQ_TEMPLATE.length);
});
it("should return the proper length if we read some from the receive queue", function () {
sock.set_rQi(1);
expect(sock.rQlen()).to.equal(RQ_TEMPLATE.length - 1);
});
});
describe('rQpeek8', function () {
it('should peek at the next byte without poping it off the queue', function () {
var bef_len = sock.rQlen();
var peek = sock.rQpeek8();
expect(sock.rQpeek8()).to.equal(peek);
expect(sock.rQlen()).to.equal(bef_len);
});
});
describe('rQshift8', function () {
it('should pop a single byte from the receive queue', function () {
var peek = sock.rQpeek8();
var bef_len = sock.rQlen();
expect(sock.rQshift8()).to.equal(peek);
expect(sock.rQlen()).to.equal(bef_len - 1);
});
});
describe('rQunshift8', function () {
it('should place a byte at the front of the queue', function () {
sock.rQunshift8(255);
expect(sock.rQpeek8()).to.equal(255);
expect(sock.rQlen()).to.equal(RQ_TEMPLATE.length + 1);
});
});
describe('rQshift16', function () {
it('should pop two bytes from the receive queue and return a single number', function () {
var bef_len = sock.rQlen();
var expected = (RQ_TEMPLATE[0] << 8) + RQ_TEMPLATE[1];
expect(sock.rQshift16()).to.equal(expected);
expect(sock.rQlen()).to.equal(bef_len - 2);
});
});
describe('rQshift32', function () {
it('should pop four bytes from the receive queue and return a single number', function () {
var bef_len = sock.rQlen();
var expected = (RQ_TEMPLATE[0] << 24) +
(RQ_TEMPLATE[1] << 16) +
(RQ_TEMPLATE[2] << 8) +
RQ_TEMPLATE[3];
expect(sock.rQshift32()).to.equal(expected);
expect(sock.rQlen()).to.equal(bef_len - 4);
});
});
describe('rQshiftStr', function () {
it('should shift the given number of bytes off of the receive queue and return a string', function () {
var bef_len = sock.rQlen();
var bef_rQi = sock.get_rQi();
var shifted = sock.rQshiftStr(3);
expect(shifted).to.be.a('string');
expect(shifted).to.equal(String.fromCharCode.apply(null, RQ_TEMPLATE.slice(bef_rQi, bef_rQi + 3)));
expect(sock.rQlen()).to.equal(bef_len - 3);
});
it('should shift the entire rest of the queue off if no length is given', function () {
sock.rQshiftStr();
expect(sock.rQlen()).to.equal(0);
});
});
describe('rQshiftBytes', function () {
it('should shift the given number of bytes of the receive queue and return an array', function () {
var bef_len = sock.rQlen();
var bef_rQi = sock.get_rQi();
var shifted = sock.rQshiftBytes(3);
expect(shifted).to.be.an.instanceof(Array);
expect(shifted).to.deep.equal(RQ_TEMPLATE.slice(bef_rQi, bef_rQi + 3));
expect(sock.rQlen()).to.equal(bef_len - 3);
});
it('should shift the entire rest of the queue off if no length is given', function () {
sock.rQshiftBytes();
expect(sock.rQlen()).to.equal(0);
});
});
describe('rQslice', function () {
beforeEach(function () {
sock.set_rQi(0);
});
it('should not modify the receive queue', function () {
var bef_len = sock.rQlen();
sock.rQslice(0, 2);
expect(sock.rQlen()).to.equal(bef_len);
});
it('should return an array containing the given slice of the receive queue', function () {
var sl = sock.rQslice(0, 2);
expect(sl).to.be.an.instanceof(Array);
expect(sl).to.deep.equal(RQ_TEMPLATE.slice(0, 2));
});
it('should use the rest of the receive queue if no end is given', function () {
var sl = sock.rQslice(1);
expect(sl).to.have.length(RQ_TEMPLATE.length - 1);
expect(sl).to.deep.equal(RQ_TEMPLATE.slice(1));
});
it('should take the current rQi in to account', function () {
sock.set_rQi(1);
expect(sock.rQslice(0, 2)).to.deep.equal(RQ_TEMPLATE.slice(1, 3));
});
});
describe('rQwait', function () {
beforeEach(function () {
sock.set_rQi(0);
});
it('should return true if there are not enough bytes in the receive queue', function () {
expect(sock.rQwait('hi', RQ_TEMPLATE.length + 1)).to.be.true;
});
it('should return false if there are enough bytes in the receive queue', function () {
expect(sock.rQwait('hi', RQ_TEMPLATE.length)).to.be.false;
});
it('should return true and reduce rQi by "goback" if there are not enough bytes', function () {
sock.set_rQi(5);
expect(sock.rQwait('hi', RQ_TEMPLATE.length, 4)).to.be.true;
expect(sock.get_rQi()).to.equal(1);
});
it('should raise an error if we try to go back more than possible', function () {
sock.set_rQi(5);
expect(function () { sock.rQwait('hi', RQ_TEMPLATE.length, 6); }).to.throw(Error);
});
it('should not reduce rQi if there are enough bytes', function () {
sock.set_rQi(5);
sock.rQwait('hi', 1, 6);
expect(sock.get_rQi()).to.equal(5);
});
});
describe('flush', function () {
beforeEach(function () {
sock._websocket = {
send: sinon.spy()
};
});
it('should actually send on the websocket if the websocket does not have too much buffered', function () {
sock.maxBufferedAmount = 10;
sock._websocket.bufferedAmount = 8;
sock._sQ = [1, 2, 3];
var encoded = sock._encode_message();
sock.flush();
expect(sock._websocket.send).to.have.been.calledOnce;
expect(sock._websocket.send).to.have.been.calledWith(encoded);
});
it('should return true if the websocket did not have too much buffered', function () {
sock.maxBufferedAmount = 10;
sock._websocket.bufferedAmount = 8;
expect(sock.flush()).to.be.true;
});
it('should not call send if we do not have anything queued up', function () {
sock._sQ = [];
sock.maxBufferedAmount = 10;
sock._websocket.bufferedAmount = 8;
sock.flush();
expect(sock._websocket.send).not.to.have.been.called;
});
it('should not send and return false if the websocket has too much buffered', function () {
sock.maxBufferedAmount = 10;
sock._websocket.bufferedAmount = 12;
expect(sock.flush()).to.be.false;
expect(sock._websocket.send).to.not.have.been.called;
});
});
describe('send', function () {
beforeEach(function () {
sock.flush = sinon.spy();
});
it('should add to the send queue', function () {
sock.send([1, 2, 3]);
var sq = sock.get_sQ();
expect(sock.get_sQ().slice(sq.length - 3)).to.deep.equal([1, 2, 3]);
});
it('should call flush', function () {
sock.send([1, 2, 3]);
expect(sock.flush).to.have.been.calledOnce;
});
});
describe('send_string', function () {
beforeEach(function () {
sock.send = sinon.spy();
});
it('should call send after converting the string to an array', function () {
sock.send_string("\x01\x02\x03");
expect(sock.send).to.have.been.calledWith([1, 2, 3]);
});
});
});
describe('lifecycle methods', function () {
var old_WS;
before(function () {
old_WS = WebSocket;
});
var sock;
beforeEach(function () {
sock = new Websock();
WebSocket = sinon.spy();
WebSocket.OPEN = old_WS.OPEN;
WebSocket.CONNECTING = old_WS.CONNECTING;
WebSocket.CLOSING = old_WS.CLOSING;
WebSocket.CLOSED = old_WS.CLOSED;
});
describe('opening', function () {
it('should pick the correct protocols if none are given' , function () {
});
it('should open the actual websocket', function () {
sock.open('ws://localhost:8675', 'base64');
expect(WebSocket).to.have.been.calledWith('ws://localhost:8675', 'base64');
});
it('should fail if we try to use binary but do not support it', function () {
expect(function () { sock.open('ws:///', 'binary'); }).to.throw(Error);
});
it('should fail if we specified an array with only binary and we do not support it', function () {
expect(function () { sock.open('ws:///', ['binary']); }).to.throw(Error);
});
it('should skip binary if we have multiple options for encoding and do not support binary', function () {
sock.open('ws:///', ['binary', 'base64']);
expect(WebSocket).to.have.been.calledWith('ws:///', ['base64']);
});
// it('should initialize the event handlers')?
});
describe('closing', function () {
beforeEach(function () {
sock.open('ws://');
sock._websocket.close = sinon.spy();
});
it('should close the actual websocket if it is open', function () {
sock._websocket.readyState = WebSocket.OPEN;
sock.close();
expect(sock._websocket.close).to.have.been.calledOnce;
});
it('should close the actual websocket if it is connecting', function () {
sock._websocket.readyState = WebSocket.CONNECTING;
sock.close();
expect(sock._websocket.close).to.have.been.calledOnce;
});
it('should not try to close the actual websocket if closing', function () {
sock._websocket.readyState = WebSocket.CLOSING;
sock.close();
expect(sock._websocket.close).not.to.have.been.called;
});
it('should not try to close the actual websocket if closed', function () {
sock._websocket.readyState = WebSocket.CLOSED;
sock.close();
expect(sock._websocket.close).not.to.have.been.called;
});
it('should reset onmessage to not call _recv_message', function () {
sinon.spy(sock, '_recv_message');
sock.close();
sock._websocket.onmessage(null);
try {
expect(sock._recv_message).not.to.have.been.called;
} finally {
sock._recv_message.restore();
}
});
});
describe('event handlers', function () {
beforeEach(function () {
sock._recv_message = sinon.spy();
sock.on('open', sinon.spy());
sock.on('close', sinon.spy());
sock.on('error', sinon.spy());
sock.open('ws://');
});
it('should call _recv_message on a message', function () {
sock._websocket.onmessage(null);
expect(sock._recv_message).to.have.been.calledOnce;
});
it('should copy the mode over upon opening', function () {
sock._websocket.protocol = 'cheese';
sock._websocket.onopen();
expect(sock._mode).to.equal('cheese');
});
it('should assume base64 if no protocol was available on opening', function () {
sock._websocket.protocol = null;
sock._websocket.onopen();
expect(sock._mode).to.equal('base64');
});
it('should call the open event handler on opening', function () {
sock._websocket.onopen();
expect(sock._eventHandlers.open).to.have.been.calledOnce;
});
it('should call the close event handler on closing', function () {
sock._websocket.onclose();
expect(sock._eventHandlers.close).to.have.been.calledOnce;
});
it('should call the error event handler on error', function () {
sock._websocket.onerror();
expect(sock._eventHandlers.error).to.have.been.calledOnce;
});
});
after(function () {
WebSocket = old_WS;
});
});
describe('WebSocket Receiving', function () {
var sock;
beforeEach(function () {
sock = new Websock();
});
it('should support decoding base64 string data to add it to the receive queue', function () {
var msg = { data: Base64.encode([1, 2, 3]) };
sock._mode = 'base64';
sock._recv_message(msg);
expect(sock.rQshiftStr(3)).to.equal('\x01\x02\x03');
});
it('should support adding binary Uint8Array data to the receive queue', function () {
var msg = { data: new Uint8Array([1, 2, 3]) };
sock._mode = 'binary';
sock._recv_message(msg);
expect(sock.rQshiftStr(3)).to.equal('\x01\x02\x03');
});
it('should call the message event handler if present', function () {
sock._eventHandlers.message = sinon.spy();
var msg = { data: Base64.encode([1, 2, 3]) };
sock._mode = 'base64';
sock._recv_message(msg);
expect(sock._eventHandlers.message).to.have.been.calledOnce;
});
it('should not call the message event handler if there is nothing in the receive queue', function () {
sock._eventHandlers.message = sinon.spy();
var msg = { data: Base64.encode([]) };
sock._mode = 'base64';
sock._recv_message(msg);
expect(sock._eventHandlers.message).not.to.have.been.called;
});
it('should compact the receive queue', function () {
// NB(sross): while this is an internal implementation detail, it's important to
// test, otherwise the receive queue could become very large very quickly
sock._rQ = [0, 1, 2, 3, 4, 5];
sock.set_rQi(6);
sock._rQmax = 3;
var msg = { data: Base64.encode([1, 2, 3]) };
sock._mode = 'base64';
sock._recv_message(msg);
expect(sock._rQ.length).to.equal(3);
expect(sock.get_rQi()).to.equal(0);
});
it('should call the error event handler on an exception', function () {
sock._eventHandlers.error = sinon.spy();
sock._eventHandlers.message = sinon.stub().throws();
var msg = { data: Base64.encode([1, 2, 3]) };
sock._mode = 'base64';
sock._recv_message(msg);
expect(sock._eventHandlers.error).to.have.been.calledOnce;
});
});
describe('Data encoding', function () {
before(function () { FakeWebSocket.replace(); });
after(function () { FakeWebSocket.restore(); });
describe('as binary data', function () {
var sock;
beforeEach(function () {
sock = new Websock();
sock.open('ws://', 'binary');
sock._websocket._open();
});
it('should convert the send queue into an ArrayBuffer', function () {
sock._sQ = [1, 2, 3];
var res = sock._encode_message(); // An ArrayBuffer
expect(new Uint8Array(res)).to.deep.equal(new Uint8Array(res));
});
it('should properly pass the encoded data off to the actual WebSocket', function () {
sock.send([1, 2, 3]);
expect(sock._websocket._get_sent_data()).to.deep.equal([1, 2, 3]);
});
});
describe('as Base64 data', function () {
var sock;
beforeEach(function () {
sock = new Websock();
sock.open('ws://', 'base64');
sock._websocket._open();
});
it('should convert the send queue into a Base64-encoded string', function () {
sock._sQ = [1, 2, 3];
expect(sock._encode_message()).to.equal(Base64.encode([1, 2, 3]));
});
it('should properly pass the encoded data off to the actual WebSocket', function () {
sock.send([1, 2, 3]);
expect(sock._websocket._get_sent_data()).to.deep.equal([1, 2, 3]);
});
});
});
});

View File

@ -37,6 +37,8 @@
<script src="../include/util.js"></script>
<script src="../include/webutil.js"></script>
<script src="../include/base64.js"></script>
<script src="../include/keysymdef.js"></script>
<script src="../include/keyboard.js"></script>
<script src="../include/input.js"></script>
<script src="../include/display.js"></script>
<script>

View File

@ -47,7 +47,8 @@
<script>
// Load supporting scripts
Util.load_scripts(["webutil.js", "base64.js", "websock.js", "des.js",
"input.js", "display.js", "jsunzip.js", "rfb.js"]);
"keysymdef.js", "keyboard.js", "input.js", "display.js",
"jsunzip.js", "rfb.js"]);
var start_time, VNC_frame_data, pass, passes, encIdx,
encOrder = ['raw', 'rre', 'hextile', 'tightpng', 'copyrect'],
@ -201,7 +202,7 @@
dbgmsg(" " + enc + ": " + VNC_frame_data_multi[enc].length);
}
rfb = new RFB({'target': $D('VNC_canvas'),
'updateState': updateState});
'onUpdateState': updateState});
rfb.testMode(send_array, VNC_frame_encoding);
}
</script>

View File

@ -60,8 +60,8 @@
message("Loading " + fname);
// Load supporting scripts
Util.load_scripts(["base64.js", "websock.js", "des.js",
"input.js", "display.js", "jsunzip.js", "rfb.js",
"playback.js", fname]);
"keysymdef.js", "keyboard.js", "input.js", "display.js",
"jsunzip.js", "rfb.js", "playback.js", fname]);
} else {
message("Must specify data=FOO in query string.");
@ -131,7 +131,7 @@
if (fname) {
message("VNC_frame_data.length: " + VNC_frame_data.length);
rfb = new RFB({'target': $D('VNC_canvas'),
'updateState': updateState});
'onUpdateState': updateState});
}
}
</script>

97
utils/parse.js Normal file
View File

@ -0,0 +1,97 @@
// Utility to parse keysymdef.h to produce mappings from Unicode codepoints to keysyms
"use strict";
var fs = require('fs');
var show_help = process.argv.length === 2;
var use_keynames = false;
var filename;
for (var i = 2; i < process.argv.length; ++i) {
switch (process.argv[i]) {
case "--help":
case "-h":
show_help = true;
break;
case "--debug-names":
case "-d":
use_keynames = true;
break;
case "--file":
case "-f":
default:
filename = process.argv[i];
}
}
if (!filename) {
show_help = true;
console.log("Error: No filename specified\n");
}
if (show_help) {
console.log("Parses a *nix keysymdef.h to generate Unicode code point mappings");
console.log("Usage: node parse.js [options] filename:");
console.log(" -h [ --help ] Produce this help message");
console.log(" -d [ --debug-names ] Preserve keysym names for debugging (Increases file size by ~40KB)");
console.log(" filename The keysymdef.h file to parse");
return;
}
// Set this to false to omit key names from the generated keysymdef.js
// This reduces the file size by around 40kb, but may hinder debugging
var buf = fs.readFileSync(filename);
var str = buf.toString('utf8');
var re = /^\#define XK_([a-zA-Z_0-9]+)\s+0x([0-9a-fA-F]+)\s*(\/\*\s*(.*)\s*\*\/)?\s*$/m;
var arr = str.split('\n');
var keysyms = {};
var codepoints = {};
for (var i = 0; i < arr.length; ++i) {
var result = re.exec(arr[i]);
if (result){
var keyname = result[1];
var keysym = parseInt(result[2], 16);
var remainder = result[3];
keysyms[keysym] = keyname;
var unicodeRes = /U\+([0-9a-fA-F]+)/.exec(remainder);
if (unicodeRes) {
var unicode = parseInt(unicodeRes[1], 16);
if (!codepoints[unicode]){
codepoints[unicode] = keysym;
}
}
else {
console.log("no unicode codepoint found:", arr[i]);
}
}
else {
console.log("line is not a keysym:", arr[i]);
}
}
var out = "// This file describes mappings from Unicode codepoints to the keysym values\n" +
"// (and optionally, key names) expected by the RFB protocol\n" +
"// How this file was generated:\n" +
"// " + process.argv.join(" ") + "\n" +
"var keysyms = (function(){\n" +
" \"use strict\";\n" +
" var keynames = {keysyms};\n" +
" var codepoints = {codepoints};\n" +
"\n" +
" function lookup(k) { return k ? {keysym: k, keyname: keynames ? keynames[k] : k} : undefined; }\n" +
" return {\n" +
" fromUnicode : function(u) { return lookup(codepoints[u]); },\n" +
" lookup : lookup\n" +
" };\n" +
"})();\n";
out = out.replace('{keysyms}', use_keynames ? JSON.stringify(keysyms) : "null");
out = out.replace('{codepoints}', JSON.stringify(codepoints));
fs.writeFileSync("keysymdef.js", out);

File diff suppressed because it is too large Load Diff

View File

@ -11,7 +11,11 @@ as taken from http://docs.python.org/dev/library/ssl.html#certificates
'''
import signal, socket, optparse, time, os, sys, subprocess
import signal, socket, optparse, time, os, sys, subprocess, logging
try: from socketserver import ForkingMixIn
except: from SocketServer import ForkingMixIn
try: from http.server import HTTPServer
except: from BaseHTTPServer import HTTPServer
from select import select
import websocket
try:
@ -20,15 +24,7 @@ except:
from cgi import parse_qs
from urlparse import urlparse
class WebSocketProxy(websocket.WebSocketServer):
"""
Proxy traffic to and from a WebSockets client to a normal TCP
socket server target. All traffic to/from the client is base64
encoded/decoded to allow binary data to be sent/received to/from
the target.
"""
buffer_size = 65536
class ProxyRequestHandler(websocket.WebSocketRequestHandler):
traffic_legend = """
Traffic Legend:
@ -42,148 +38,33 @@ Traffic Legend:
<. - Client send partial
"""
def __init__(self, *args, **kwargs):
# Save off proxy specific options
self.target_host = kwargs.pop('target_host', None)
self.target_port = kwargs.pop('target_port', None)
self.wrap_cmd = kwargs.pop('wrap_cmd', None)
self.wrap_mode = kwargs.pop('wrap_mode', None)
self.unix_target = kwargs.pop('unix_target', None)
self.ssl_target = kwargs.pop('ssl_target', None)
self.target_cfg = kwargs.pop('target_cfg', None)
# Last 3 timestamps command was run
self.wrap_times = [0, 0, 0]
if self.wrap_cmd:
rebinder_path = ['./', os.path.dirname(sys.argv[0])]
self.rebinder = None
for rdir in rebinder_path:
rpath = os.path.join(rdir, "rebind.so")
if os.path.exists(rpath):
self.rebinder = rpath
break
if not self.rebinder:
raise Exception("rebind.so not found, perhaps you need to run make")
self.rebinder = os.path.abspath(self.rebinder)
self.target_host = "127.0.0.1" # Loopback
# Find a free high port
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('', 0))
self.target_port = sock.getsockname()[1]
sock.close()
os.environ.update({
"LD_PRELOAD": self.rebinder,
"REBIND_OLD_PORT": str(kwargs['listen_port']),
"REBIND_NEW_PORT": str(self.target_port)})
if self.target_cfg:
self.target_cfg = os.path.abspath(self.target_cfg)
websocket.WebSocketServer.__init__(self, *args, **kwargs)
def run_wrap_cmd(self):
print("Starting '%s'" % " ".join(self.wrap_cmd))
self.wrap_times.append(time.time())
self.wrap_times.pop(0)
self.cmd = subprocess.Popen(
self.wrap_cmd, env=os.environ, preexec_fn=_subprocess_setup)
self.spawn_message = True
def started(self):
"""
Called after Websockets server startup (i.e. after daemonize)
"""
# Need to call wrapped command after daemonization so we can
# know when the wrapped command exits
if self.wrap_cmd:
dst_string = "'%s' (port %s)" % (" ".join(self.wrap_cmd), self.target_port)
elif self.unix_target:
dst_string = self.unix_target
else:
dst_string = "%s:%s" % (self.target_host, self.target_port)
if self.target_cfg:
msg = " - proxying from %s:%s to targets in %s" % (
self.listen_host, self.listen_port, self.target_cfg)
else:
msg = " - proxying from %s:%s to %s" % (
self.listen_host, self.listen_port, dst_string)
if self.ssl_target:
msg += " (using SSL)"
print(msg + "\n")
if self.wrap_cmd:
self.run_wrap_cmd()
def poll(self):
# If we are wrapping a command, check it's status
if self.wrap_cmd and self.cmd:
ret = self.cmd.poll()
if ret != None:
self.vmsg("Wrapped command exited (or daemon). Returned %s" % ret)
self.cmd = None
if self.wrap_cmd and self.cmd == None:
# Response to wrapped command being gone
if self.wrap_mode == "ignore":
pass
elif self.wrap_mode == "exit":
sys.exit(ret)
elif self.wrap_mode == "respawn":
now = time.time()
avg = sum(self.wrap_times)/len(self.wrap_times)
if (now - avg) < 10:
# 3 times in the last 10 seconds
if self.spawn_message:
print("Command respawning too fast")
self.spawn_message = False
else:
self.run_wrap_cmd()
#
# Routines above this point are run in the master listener
# process.
#
#
# Routines below this point are connection handler routines and
# will be run in a separate forked process for each connection.
#
def new_client(self):
def new_websocket_client(self):
"""
Called after a new WebSocket connection has been established.
"""
# Checks if we receive a token, and look
# for a valid target for it then
if self.target_cfg:
(self.target_host, self.target_port) = self.get_target(self.target_cfg, self.path)
if self.server.target_cfg:
(self.server.target_host, self.server.target_port) = self.get_target(self.server.target_cfg, self.path)
# Connect to the target
if self.wrap_cmd:
msg = "connecting to command: '%s' (port %s)" % (" ".join(self.wrap_cmd), self.target_port)
elif self.unix_target:
msg = "connecting to unix socket: %s" % self.unix_target
if self.server.wrap_cmd:
msg = "connecting to command: '%s' (port %s)" % (" ".join(self.server.wrap_cmd), self.server.target_port)
elif self.server.unix_target:
msg = "connecting to unix socket: %s" % self.server.unix_target
else:
msg = "connecting to: %s:%s" % (
self.target_host, self.target_port)
self.server.target_host, self.server.target_port)
if self.ssl_target:
if self.server.ssl_target:
msg += " (using SSL)"
self.msg(msg)
self.log_message(msg)
tsock = self.socket(self.target_host, self.target_port,
connect=True, use_ssl=self.ssl_target, unix_socket=self.unix_target)
tsock = websocket.WebSocketServer.socket(self.server.target_host,
self.server.target_port,
connect=True, use_ssl=self.server.ssl_target, unix_socket=self.server.unix_target)
if self.verbose and not self.daemon:
print(self.traffic_legend)
self.print_traffic(self.traffic_legend)
# Start proxying
try:
@ -192,8 +73,9 @@ Traffic Legend:
if tsock:
tsock.shutdown(socket.SHUT_RDWR)
tsock.close()
self.vmsg("%s:%s: Closed target" %(
self.target_host, self.target_port))
if self.verbose:
self.log_message("%s:%s: Closed target",
self.server.target_host, self.server.target_port)
raise
def get_target(self, target_cfg, path):
@ -242,31 +124,32 @@ Traffic Legend:
cqueue = []
c_pend = 0
tqueue = []
rlist = [self.client, target]
rlist = [self.request, target]
while True:
wlist = []
if tqueue: wlist.append(target)
if cqueue or c_pend: wlist.append(self.client)
if cqueue or c_pend: wlist.append(self.request)
ins, outs, excepts = select(rlist, wlist, [], 1)
if excepts: raise Exception("Socket exception")
if self.client in outs:
if self.request in outs:
# Send queued target data to the client
c_pend = self.send_frames(cqueue)
cqueue = []
if self.client in ins:
if self.request in ins:
# Receive client data, decode it, and queue for target
bufs, closed = self.recv_frames()
tqueue.extend(bufs)
if closed:
# TODO: What about blocking on client socket?
self.vmsg("%s:%s: Client closed connection" %(
self.target_host, self.target_port))
if self.verbose:
self.log_message("%s:%s: Client closed connection",
self.server.target_host, self.server.target_port)
raise self.CClose(closed['code'], closed['reason'])
@ -275,24 +158,139 @@ Traffic Legend:
dat = tqueue.pop(0)
sent = target.send(dat)
if sent == len(dat):
self.traffic(">")
self.print_traffic(">")
else:
# requeue the remaining data
tqueue.insert(0, dat[sent:])
self.traffic(".>")
self.print_traffic(".>")
if target in ins:
# Receive target data, encode it and queue for client
buf = target.recv(self.buffer_size)
if len(buf) == 0:
self.vmsg("%s:%s: Target closed connection" %(
self.target_host, self.target_port))
if self.verbose:
self.log_message("%s:%s: Target closed connection",
self.server.target_host, self.server.target_port)
raise self.CClose(1000, "Target closed")
cqueue.append(buf)
self.traffic("{")
self.print_traffic("{")
class WebSocketProxy(websocket.WebSocketServer):
"""
Proxy traffic to and from a WebSockets client to a normal TCP
socket server target. All traffic to/from the client is base64
encoded/decoded to allow binary data to be sent/received to/from
the target.
"""
buffer_size = 65536
def __init__(self, RequestHandlerClass=ProxyRequestHandler, *args, **kwargs):
# Save off proxy specific options
self.target_host = kwargs.pop('target_host', None)
self.target_port = kwargs.pop('target_port', None)
self.wrap_cmd = kwargs.pop('wrap_cmd', None)
self.wrap_mode = kwargs.pop('wrap_mode', None)
self.unix_target = kwargs.pop('unix_target', None)
self.ssl_target = kwargs.pop('ssl_target', None)
self.target_cfg = kwargs.pop('target_cfg', None)
# Last 3 timestamps command was run
self.wrap_times = [0, 0, 0]
if self.wrap_cmd:
wsdir = os.path.dirname(sys.argv[0])
rebinder_path = [os.path.join(wsdir, "..", "lib"),
os.path.join(wsdir, "..", "lib", "websockify"),
wsdir]
self.rebinder = None
for rdir in rebinder_path:
rpath = os.path.join(rdir, "rebind.so")
if os.path.exists(rpath):
self.rebinder = rpath
break
if not self.rebinder:
raise Exception("rebind.so not found, perhaps you need to run make")
self.rebinder = os.path.abspath(self.rebinder)
self.target_host = "127.0.0.1" # Loopback
# Find a free high port
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('', 0))
self.target_port = sock.getsockname()[1]
sock.close()
os.environ.update({
"LD_PRELOAD": self.rebinder,
"REBIND_OLD_PORT": str(kwargs['listen_port']),
"REBIND_NEW_PORT": str(self.target_port)})
websocket.WebSocketServer.__init__(self, RequestHandlerClass, *args, **kwargs)
def run_wrap_cmd(self):
self.msg("Starting '%s'", " ".join(self.wrap_cmd))
self.wrap_times.append(time.time())
self.wrap_times.pop(0)
self.cmd = subprocess.Popen(
self.wrap_cmd, env=os.environ, preexec_fn=_subprocess_setup)
self.spawn_message = True
def started(self):
"""
Called after Websockets server startup (i.e. after daemonize)
"""
# Need to call wrapped command after daemonization so we can
# know when the wrapped command exits
if self.wrap_cmd:
dst_string = "'%s' (port %s)" % (" ".join(self.wrap_cmd), self.target_port)
elif self.unix_target:
dst_string = self.unix_target
else:
dst_string = "%s:%s" % (self.target_host, self.target_port)
if self.target_cfg:
msg = " - proxying from %s:%s to targets in %s" % (
self.listen_host, self.listen_port, self.target_cfg)
else:
msg = " - proxying from %s:%s to %s" % (
self.listen_host, self.listen_port, dst_string)
if self.ssl_target:
msg += " (using SSL)"
self.msg("%s", msg)
if self.wrap_cmd:
self.run_wrap_cmd()
def poll(self):
# If we are wrapping a command, check it's status
if self.wrap_cmd and self.cmd:
ret = self.cmd.poll()
if ret != None:
self.vmsg("Wrapped command exited (or daemon). Returned %s" % ret)
self.cmd = None
if self.wrap_cmd and self.cmd == None:
# Response to wrapped command being gone
if self.wrap_mode == "ignore":
pass
elif self.wrap_mode == "exit":
sys.exit(ret)
elif self.wrap_mode == "respawn":
now = time.time()
avg = sum(self.wrap_times)/len(self.wrap_times)
if (now - avg) < 10:
# 3 times in the last 10 seconds
if self.spawn_message:
self.warn("Command respawning too fast")
self.spawn_message = False
else:
self.run_wrap_cmd()
def _subprocess_setup():
@ -301,14 +299,28 @@ def _subprocess_setup():
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
def logger_init():
logger = logging.getLogger(WebSocketProxy.log_prefix)
logger.propagate = False
logger.setLevel(logging.INFO)
h = logging.StreamHandler()
h.setLevel(logging.DEBUG)
h.setFormatter(logging.Formatter("%(message)s"))
logger.addHandler(h)
def websockify_init():
logger_init()
usage = "\n %prog [options]"
usage += " [source_addr:]source_port [target_addr:target_port]"
usage += "\n %prog [options]"
usage += " [source_addr:]source_port -- WRAP_COMMAND_LINE"
parser = optparse.OptionParser(usage=usage)
parser.add_option("--verbose", "-v", action="store_true",
help="verbose messages and per frame traffic")
help="verbose messages")
parser.add_option("--traffic", action="store_true",
help="per frame traffic")
parser.add_option("--record",
help="record sessions to FILE.[session_number]", metavar="FILE")
parser.add_option("--daemon", "-D",
@ -345,8 +357,13 @@ def websockify_init():
help="Configuration file containing valid targets "
"in the form 'token: host:port' or, alternatively, a "
"directory containing configuration files of this form")
parser.add_option("--libserver", action="store_true",
help="use Python library SocketServer engine")
(opts, args) = parser.parse_args()
if opts.verbose:
logging.getLogger(WebSocketProxy.log_prefix).setLevel(logging.DEBUG)
# Sanity checks
if len(args) < 2 and not (opts.target_cfg or opts.unix_target):
parser.error("Too few arguments")
@ -385,9 +402,70 @@ def websockify_init():
try: opts.target_port = int(opts.target_port)
except: parser.error("Error parsing target port")
# Transform to absolute path as daemon may chdir
if opts.target_cfg:
opts.target_cfg = os.path.abspath(opts.target_cfg)
# Create and start the WebSockets proxy
libserver = opts.libserver
del opts.libserver
if libserver:
# Use standard Python SocketServer framework
server = LibProxyServer(**opts.__dict__)
server.serve_forever()
else:
# Use internal service framework
server = WebSocketProxy(**opts.__dict__)
server.start_server()
class LibProxyServer(ForkingMixIn, HTTPServer):
"""
Just like WebSocketProxy, but uses standard Python SocketServer
framework.
"""
def __init__(self, RequestHandlerClass=ProxyRequestHandler, **kwargs):
# Save off proxy specific options
self.target_host = kwargs.pop('target_host', None)
self.target_port = kwargs.pop('target_port', None)
self.wrap_cmd = kwargs.pop('wrap_cmd', None)
self.wrap_mode = kwargs.pop('wrap_mode', None)
self.unix_target = kwargs.pop('unix_target', None)
self.ssl_target = kwargs.pop('ssl_target', None)
self.target_cfg = kwargs.pop('target_cfg', None)
self.daemon = False
self.target_cfg = None
# Server configuration
listen_host = kwargs.pop('listen_host', '')
listen_port = kwargs.pop('listen_port', None)
web = kwargs.pop('web', '')
# Configuration affecting base request handler
self.only_upgrade = not web
self.verbose = kwargs.pop('verbose', False)
record = kwargs.pop('record', '')
if record:
self.record = os.path.abspath(record)
self.run_once = kwargs.pop('run_once', False)
self.handler_id = 0
for arg in kwargs.keys():
print("warning: option %s ignored when using --libserver" % arg)
if web:
os.chdir(web)
HTTPServer.__init__(self, (listen_host, listen_port),
RequestHandlerClass)
def process_request(self, request, client_address):
"""Override process_request to implement a counter"""
self.handler_id += 1
ForkingMixIn.process_request(self, request, client_address)
if __name__ == '__main__':
websockify_init()

View File

@ -47,27 +47,43 @@
<body>
<div id="noVNC-control-bar">
<div id="noVNC-menu-bar" style="display:none;">
</div>
<!--noVNC Mobile Device only Buttons-->
<div class="noVNC-buttons-left">
<input type="image" src="images/drag.png"
<input type="image" alt="viewport drag" src="images/drag.png"
id="noVNC_view_drag_button" class="noVNC_status_button"
title="Move/Drag Viewport">
<div id="noVNC_mobile_buttons">
<input type="image" src="images/mouse_none.png"
<input type="image" alt="No mousebutton" src="images/mouse_none.png"
id="noVNC_mouse_button0" class="noVNC_status_button">
<input type="image" src="images/mouse_left.png"
<input type="image" alt="Left mousebutton" src="images/mouse_left.png"
id="noVNC_mouse_button1" class="noVNC_status_button">
<input type="image" src="images/mouse_middle.png"
<input type="image" alt="Middle mousebutton" src="images/mouse_middle.png"
id="noVNC_mouse_button2" class="noVNC_status_button">
<input type="image" src="images/mouse_right.png"
<input type="image" alt="Right mousebutton" src="images/mouse_right.png"
id="noVNC_mouse_button4" class="noVNC_status_button">
<input type="image" src="images/keyboard.png"
<input type="image" alt="Keyboard" src="images/keyboard.png"
id="showKeyboard" class="noVNC_status_button"
value="Keyboard" title="Show Keyboard"/>
<input type="text" autocapitalize="off" autocorrect="off"
id="keyboardinput" class="" value="&nbsp;"/>
<!-- Note that Google Chrome on Android doesn't respect any of these,
html attributes which attempt to disable text suggestions on the
on-screen keyboard. Let's hope Chrome implements the ime-mode
style for example -->
<textarea id="keyboardinput" autocapitalize="off"
autocorrect="off" autocomplete="off" spellcheck="false"
mozactionhint="Enter" onsubmit="return false;"
style="ime-mode: disabled;"></textarea>
<div id="noVNC_extra_keys">
<input type="image" alt="Extra keys" src="images/showextrakeys.png"
id="showExtraKeysButton" class="noVNC_status_button">
<input type="image" alt="Ctrl" src="images/ctrl.png"
id="toggleCtrlButton" class="noVNC_status_button">
<input type="image" alt="Alt" src="images/alt.png"
id="toggleAltButton" class="noVNC_status_button">
<input type="image" alt="Tab" src="images/tab.png"
id="sendTabButton" class="noVNC_status_button">
<input type="image" alt="Esc" src="images/esc.png"
id="sendEscButton" class="noVNC_status_button">
</div>
</div>
</div>
@ -75,26 +91,29 @@
<!--noVNC Buttons-->
<div class="noVNC-buttons-right">
<input type="image" src="images/ctrlaltdel.png"
<input type="image" alt="Ctrl+Alt+Del" src="images/ctrlaltdel.png"
id="sendCtrlAltDelButton" class="noVNC_status_button"
title="Send Ctrl-Alt-Del" />
<input type="image" src="images/clipboard.png"
<input type="image" alt="Shutdown/Reboot" src="images/power.png"
id="xvpButton" class="noVNC_status_button"
title="Shutdown/Reboot..." />
<input type="image" alt="Clipboard" src="images/clipboard.png"
id="clipboardButton" class="noVNC_status_button"
title="Clipboard" />
<input type="image" src="images/settings.png"
<input type="image" alt="Settings" src="images/settings.png"
id="settingsButton" class="noVNC_status_button"
title="Settings" />
<input type="image" src="images/connect.png"
<input type="image" alt="Connect" src="images/connect.png"
id="connectButton" class="noVNC_status_button"
title="Connect" />
<input type="image" src="images/disconnect.png"
<input type="image" alt="Disconnect" src="images/disconnect.png"
id="disconnectButton" class="noVNC_status_button"
title="Disconnect" />
</div>
<!-- Description Panel -->
<!-- Shown by default when hosted at for kanaka.github.com -->
<div id="noVNC_description" style="display:none;" class="">
<div id="noVNC_description" class="">
noVNC is a browser based VNC client implemented using HTML5 Canvas
and WebSockets. You will either need a VNC server with WebSockets
support (such as <a href="http://libvncserver.sourceforge.net/">libvncserver</a>)
@ -121,6 +140,15 @@
value="Clear">
</div>
<!-- XVP Shutdown/Reboot Panel -->
<div id="noVNC_xvp" class="triangle-right top">
<span id="noVNC_xvp_menu">
<input type="button" id="xvpShutdownButton" value="Shutdown" />
<input type="button" id="xvpRebootButton" value="Reboot" />
<input type="button" id="xvpResetButton" value="Reset" />
</span>
</div>
<!-- Settings Panel -->
<div id="noVNC_settings" class="triangle-right top">
<span id="noVNC_settings_menu">
@ -131,7 +159,6 @@
<li><input id="noVNC_clip" type="checkbox"> Clip to Window</li>
<li><input id="noVNC_shared" type="checkbox"> Shared Mode</li>
<li><input id="noVNC_view_only" type="checkbox"> View Only</li>
<li><input id="noVNC_connectTimeout" type="input"> Connect Timeout (s)</li>
<li><input id="noVNC_path" type="input" value="websockify"> Path</li>
<li><input id="noVNC_repeaterID" type="input" value=""> Repeater ID</li>
<hr>

View File

@ -53,6 +53,14 @@
<td width="1%"><div id="noVNC_buttons">
<input type=button value="Send CtrlAltDel"
id="sendCtrlAltDelButton">
<span id="noVNC_xvp_buttons">
<input type=button value="Shutdown"
id="xvpShutdownButton">
<input type=button value="Reboot"
id="xvpRebootButton">
<input type=button value="Reset"
id="xvpResetButton">
</span>
</div></td>
</tr></table>
</div>
@ -68,7 +76,8 @@
// Load supporting scripts
Util.load_scripts(["webutil.js", "base64.js", "websock.js", "des.js",
"input.js", "display.js", "jsunzip.js", "rfb.js"]);
"keysymdef.js", "keyboard.js", "input.js", "display.js",
"jsunzip.js", "rfb.js", "keysym.js"]);
var rfb;
var resizeTimeout;
@ -101,6 +110,18 @@
rfb.sendCtrlAltDel();
return false;
}
function xvpShutdown() {
rfb.xvpShutdown();
return false;
}
function xvpReboot() {
rfb.xvpReboot();
return false;
}
function xvpReset() {
rfb.xvpReset();
return false;
}
function updateState(rfb, state, oldstate, msg) {
var s, sb, cad, level;
s = $D('noVNC_status');
@ -115,8 +136,12 @@
default: level = "warn"; break;
}
if (state === "normal") { cad.disabled = false; }
else { cad.disabled = true; }
if (state === "normal") {
cad.disabled = false;
} else {
cad.disabled = true;
xvpInit(0);
}
if (typeof(msg) !== 'undefined') {
sb.setAttribute("class", "noVNC_status_" + level);
@ -134,11 +159,24 @@
}, 500);
};
function xvpInit(ver) {
var xvpbuttons;
xvpbuttons = $D('noVNC_xvp_buttons');
if (ver >= 1) {
xvpbuttons.style.display = 'inline';
} else {
xvpbuttons.style.display = 'none';
}
}
window.onscriptsload = function () {
var host, port, password, path, token;
$D('sendCtrlAltDelButton').style.display = "inline";
$D('sendCtrlAltDelButton').onclick = sendCtrlAltDel;
$D('xvpShutdownButton').onclick = xvpShutdown;
$D('xvpRebootButton').onclick = xvpReboot;
$D('xvpResetButton').onclick = xvpReset;
WebUtil.init_logging(WebUtil.getQueryVar('logging', 'warn'));
document.title = unescape(WebUtil.getQueryVar('title', 'noVNC'));
@ -182,8 +220,12 @@
'shared': WebUtil.getQueryVar('shared', true),
'view_only': WebUtil.getQueryVar('view_only', false),
'updateState': updateState,
'onXvpInit': xvpInit,
'onPasswordRequired': passwordRequired,
'onFBUComplete': FBUComplete});
'onUpdateState': updateState,
'onXvpInit': xvpInit,
'onPasswordRequired': passwordRequired});
rfb.connect(host, port, password, path);
};
</script>