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:
commit
9ae9fdec5c
|
|
@ -2,3 +2,4 @@
|
|||
*.o
|
||||
tests/data_*.js
|
||||
utils/rebind.so
|
||||
node_modules
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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!
|
||||
|
|
@ -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
|
||||
|
|
|
|||
48
README.md
48
README.md
|
|
@ -1,23 +1,25 @@
|
|||
## noVNC: HTML5 VNC Client
|
||||
|
||||
[](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)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 339 B |
Binary file not shown.
|
After Width: | Height: | Size: 354 B |
Binary file not shown.
|
After Width: | Height: | Size: 385 B |
Binary file not shown.
|
After Width: | Height: | Size: 390 B |
Binary file not shown.
|
After Width: | Height: | Size: 735 B |
Binary file not shown.
|
After Width: | Height: | Size: 387 B |
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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
2064
include/input.js
2064
include/input.js
File diff suppressed because it is too large
Load Diff
|
|
@ -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 */
|
||||
};
|
||||
}
|
||||
|
|
@ -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 */
|
||||
File diff suppressed because one or more lines are too long
2746
include/rfb.js
2746
include/rfb.js
File diff suppressed because it is too large
Load Diff
618
include/ui.js
618
include/ui.js
|
|
@ -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';
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
})();
|
||||
|
|
|
|||
571
include/util.js
571
include/util.js
|
|
@ -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
|
||||
Binary file not shown.
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
};
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
|
@ -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});
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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)'
|
||||
};
|
||||
|
|
@ -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);
|
||||
});*/
|
||||
}
|
||||
|
|
@ -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'
|
||||
};
|
||||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -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;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -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
|
||||
});
|
||||
|
|
@ -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]);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
384
utils/websockify
384
utils/websockify
|
|
@ -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()
|
||||
|
|
|
|||
61
vnc.html
61
vnc.html
|
|
@ -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=" "/>
|
||||
<!-- 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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in New Issue