Compare commits
387 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fc00821eba | ||
|
|
0331495382 | ||
|
|
282834caf1 | ||
|
|
ed7f8c3886 | ||
|
|
4865278dee | ||
|
|
b2e8311de6 | ||
|
|
df89129ff0 | ||
|
|
1f84c99938 | ||
|
|
40ac6f0ab6 | ||
|
|
c42ea22525 | ||
|
|
fda40d8927 | ||
|
|
bd6874e087 | ||
|
|
795fca23dc | ||
|
|
a86f97e284 | ||
|
|
dbaf49f570 | ||
|
|
ec31f82eda | ||
|
|
e2f1ff8048 | ||
|
|
58ca1978ea | ||
|
|
3257d9f265 | ||
|
|
2930403a11 | ||
|
|
f9fd0313b8 | ||
|
|
58529d347b | ||
|
|
b11bb5c385 | ||
|
|
0b0b0433b5 | ||
|
|
3b4fd003c2 | ||
|
|
53762c31fe | ||
|
|
cfc02e5e2b | ||
|
|
d02a99f0c8 | ||
|
|
f0e4548b16 | ||
|
|
e4e9a9b97f | ||
|
|
2c9623b5a7 | ||
|
|
f8f95d6023 | ||
|
|
77bd04f833 | ||
|
|
7caa9c20c2 | ||
|
|
0ca7cf4867 | ||
|
|
e6af0f60b0 | ||
|
|
bbbf42bb5a | ||
|
|
b1dee94788 | ||
|
|
1e13775bd5 | ||
|
|
2cccf7530c | ||
|
|
ee7d4c61c6 | ||
|
|
31f169e86f | ||
|
|
d6e281baf6 | ||
|
|
d21cd6c164 | ||
|
|
fb64ed2135 | ||
|
|
f8e9b9f1bf | ||
|
|
95eb681bbb | ||
|
|
91127741be | ||
|
|
960752ea53 | ||
|
|
9b731d3a58 | ||
|
|
93af721a27 | ||
|
|
7187bc121f | ||
|
|
d906dfc953 | ||
|
|
4a4643c05d | ||
|
|
dd3a8a1b6d | ||
|
|
d8c0953567 | ||
|
|
c77938efc9 | ||
|
|
082027dc87 | ||
|
|
60a415ae1c | ||
|
|
8f4a291b16 | ||
|
|
eeda79d3c3 | ||
|
|
0bb6a8c50e | ||
|
|
de8edde4a0 | ||
|
|
74d3d75a4a | ||
|
|
b98fed2f69 | ||
|
|
df0ee70e36 | ||
|
|
7cad15425e | ||
|
|
23817e1516 | ||
|
|
0fe30338a0 | ||
|
|
2fa1c0988a | ||
|
|
2db62a9528 | ||
|
|
b7996b048b | ||
|
|
6227a91c01 | ||
|
|
f1e6daf3c8 | ||
|
|
230784066c | ||
|
|
c3f6052435 | ||
|
|
97362c3980 | ||
|
|
c70000bac4 | ||
|
|
c7fc3c082f | ||
|
|
e791b87572 | ||
|
|
fb35d50f6a | ||
|
|
a856a051da | ||
|
|
03ab251587 | ||
|
|
df6b7d7341 | ||
|
|
afbeb98cfb | ||
|
|
20074a49eb | ||
|
|
d86cc2d911 | ||
|
|
98c1275d80 | ||
|
|
f6a1d98a3a | ||
|
|
85e8991664 | ||
|
|
d823e8956e | ||
|
|
8eb88937cc | ||
|
|
466a09f0f3 | ||
|
|
bf47095944 | ||
|
|
2ea40fdf9a | ||
|
|
fa30469cda | ||
|
|
9ceef041c6 | ||
|
|
f00b6fb69a | ||
|
|
4ef7566b10 | ||
|
|
ae52883b93 | ||
|
|
1e570156f9 | ||
|
|
e0dc102e5b | ||
|
|
2af865923c | ||
|
|
75d69b9f62 | ||
|
|
f0d30a90f3 | ||
|
|
ad941fadde | ||
|
|
f901d9c9b8 | ||
|
|
be09828537 | ||
|
|
142aa4583c | ||
|
|
f4f72e9db9 | ||
|
|
69127447ac | ||
|
|
3346f9229b | ||
|
|
270ae2f9f5 | ||
|
|
5eed5a4e17 | ||
|
|
7c76fd32a1 | ||
|
|
4c0b680a0b | ||
|
|
f8ddfc732d | ||
|
|
9b9e741b8c | ||
|
|
97aefe5f83 | ||
|
|
82744aa8ee | ||
|
|
eb955f8c20 | ||
|
|
968431dd46 | ||
|
|
b4a979a07e | ||
|
|
04d6a8347f | ||
|
|
76e262134e | ||
|
|
3cb89f5a14 | ||
|
|
c39df031d8 | ||
|
|
292f6a5da7 | ||
|
|
bd88b94393 | ||
|
|
9d04096e58 | ||
|
|
53c01a2353 | ||
|
|
41c66fbfcd | ||
|
|
7e24f50b66 | ||
|
|
f3ff971db9 | ||
|
|
31164434b2 | ||
|
|
fb4394b10b | ||
|
|
dfcedffc16 | ||
|
|
df4d6dde30 | ||
|
|
0e3d505e54 | ||
|
|
df9d3d9cf2 | ||
|
|
406a8b4e96 | ||
|
|
0019d3b055 | ||
|
|
35b29c98ea | ||
|
|
6ca8a2c05c | ||
|
|
7ab02c7fc7 | ||
|
|
c6ad20992d | ||
|
|
cf19ad3798 | ||
|
|
a4ec2f5c7d | ||
|
|
9e97231acf | ||
|
|
cc704b712d | ||
|
|
0fa4e0a90a | ||
|
|
b2f1961a3a | ||
|
|
ca9a9964a0 | ||
|
|
c3c51ed32b | ||
|
|
8f12ca7a5a | ||
|
|
b66ffcddcf | ||
|
|
e6500004de | ||
|
|
58873b3222 | ||
|
|
c42136d537 | ||
|
|
44ff863273 | ||
|
|
1e04775831 | ||
|
|
9f554fcdf7 | ||
|
|
7c1cd93744 | ||
|
|
e83b9e03eb | ||
|
|
7dc038efd9 | ||
|
|
cededc4d14 | ||
|
|
de0f8773b1 | ||
|
|
60d991774b | ||
|
|
ec34af8f61 | ||
|
|
33f5d3bd92 | ||
|
|
9f0d23de4d | ||
|
|
b597bdd1ec | ||
|
|
c2b1409a43 | ||
|
|
e84101b3e7 | ||
|
|
3b2acc2258 | ||
|
|
301fc915a6 | ||
|
|
b1b342a97e | ||
|
|
0d0f754aad | ||
|
|
8a147535de | ||
|
|
49d5cee91e | ||
|
|
37a3081f05 | ||
|
|
d55f5377bb | ||
|
|
902c6fb37f | ||
|
|
705c54edb6 | ||
|
|
361cde7c96 | ||
|
|
f167ea0ecc | ||
|
|
3516bdf3fa | ||
|
|
51562999c7 | ||
|
|
e7e6660272 | ||
|
|
49578da448 | ||
|
|
c95456c0e1 | ||
|
|
73ee4fa7cc | ||
|
|
5514d29950 | ||
|
|
57430b8c12 | ||
|
|
e881ce5a1b | ||
|
|
8e2d749605 | ||
|
|
60106f240a | ||
|
|
c527482ebf | ||
|
|
35785a9081 | ||
|
|
6f4b1e4071 | ||
|
|
6f4cbb3f3c | ||
|
|
7a6c94b6b9 | ||
|
|
e16ad2fd02 | ||
|
|
1d728ace69 | ||
|
|
ab7674ff73 | ||
|
|
c76b2a8101 | ||
|
|
079504a6d2 | ||
|
|
8dfd916946 | ||
|
|
8534fea664 | ||
|
|
d5fe15096b | ||
|
|
6f955236e6 | ||
|
|
5f03c3d093 | ||
|
|
2cde6e4380 | ||
|
|
26945049be | ||
|
|
ee1af44161 | ||
|
|
3af1c2751b | ||
|
|
51fc3b5f03 | ||
|
|
dbec398406 | ||
|
|
8ec3cfaacd | ||
|
|
07392d4fb8 | ||
|
|
204675c85d | ||
|
|
3435491edb | ||
|
|
39188f4ea8 | ||
|
|
ceec05260e | ||
|
|
cbb55df039 | ||
|
|
625040fc90 | ||
|
|
af6e9e2408 | ||
|
|
cf068be2d8 | ||
|
|
fa5b334dcb | ||
|
|
fcff386b92 | ||
|
|
fc003a13e7 | ||
|
|
ca6b3f4cb1 | ||
|
|
14717eb468 | ||
|
|
4dd1bb1ecb | ||
|
|
50b81d4470 | ||
|
|
af1527b276 | ||
|
|
e5d5a7d3fd | ||
|
|
d58f8b5144 | ||
|
|
18232af2a6 | ||
|
|
b50f340674 | ||
|
|
2090f1af67 | ||
|
|
9450f132c6 | ||
|
|
9ebc84f2e9 | ||
|
|
a997c5fd94 | ||
|
|
12acb663eb | ||
|
|
d5575ef6f3 | ||
|
|
90f18a8a96 | ||
|
|
bee36506e1 | ||
|
|
cb3ad1b5b5 | ||
|
|
b1d2225eee | ||
|
|
8d0c7fb5ac | ||
|
|
9e2351f5eb | ||
|
|
ab44059b8b | ||
|
|
a04087e44c | ||
|
|
9f54c23629 | ||
|
|
72a5596e50 | ||
|
|
34d8b844ae | ||
|
|
a0726a4b56 | ||
|
|
57044a3c0a | ||
|
|
dc6e501f20 | ||
|
|
e472e6ce3a | ||
|
|
17bb6ca38e | ||
|
|
df4fa13b89 | ||
|
|
53dfab7fb7 | ||
|
|
bc28395abf | ||
|
|
a117514183 | ||
|
|
58ad83878c | ||
|
|
02d1f19b5f | ||
|
|
4910600b71 | ||
|
|
ca78183171 | ||
|
|
901e565503 | ||
|
|
483157f87f | ||
|
|
dcf1994d3e | ||
|
|
c390011099 | ||
|
|
f8380ff939 | ||
|
|
f736c3316a | ||
|
|
cbd3435a07 | ||
|
|
bd96e91932 | ||
|
|
0139b2562c | ||
|
|
4c75210a4d | ||
|
|
f84504bc63 | ||
|
|
c76b3e4b26 | ||
|
|
6671c7624d | ||
|
|
ce86f5c954 | ||
|
|
c0c20581f5 | ||
|
|
f2a495c944 | ||
|
|
b8dd87c757 | ||
|
|
d065cad99e | ||
|
|
a09a75e8f2 | ||
|
|
4cd0070a1c | ||
|
|
35d7574b09 | ||
|
|
d38db74abd | ||
|
|
0c4f4b598c | ||
|
|
c6f3919cb0 | ||
|
|
3b302463f8 | ||
|
|
23ff806774 | ||
|
|
94bcf652b3 | ||
|
|
0ce93900ae | ||
|
|
9e6e6662c7 | ||
|
|
32f135d730 | ||
|
|
1af3e54bef | ||
|
|
6a02f88e4d | ||
|
|
9b75bcaada | ||
|
|
a14b8fae2a | ||
|
|
2cedf48397 | ||
|
|
c514fd5e1c | ||
|
|
5ca5e2d8cd | ||
|
|
6fbc37489f | ||
|
|
b0ac240f31 | ||
|
|
523cc4d6ab | ||
|
|
0fa6748c52 | ||
|
|
a820f1267a | ||
|
|
c577ca2305 | ||
|
|
de84e09854 | ||
|
|
682f33a790 | ||
|
|
e79917c3db | ||
|
|
a9a7f0e18c | ||
|
|
ff4bfcb773 | ||
|
|
f66bcda31b | ||
|
|
86ebeef709 | ||
|
|
f2d856767d | ||
|
|
2fa565b375 | ||
|
|
b688a909b0 | ||
|
|
06a9ef0c6c | ||
|
|
c3a172b98c | ||
|
|
aa67056785 | ||
|
|
91b0dc3915 | ||
|
|
4f545a5ba6 | ||
|
|
83ed7b8c25 | ||
|
|
1c92807a8e | ||
|
|
bc71506553 | ||
|
|
2e7d81ce24 | ||
|
|
fa4607dbf5 | ||
|
|
a538be4350 | ||
|
|
a367e84a92 | ||
|
|
1e50871599 | ||
|
|
65db2d8243 | ||
|
|
b29e06f36e | ||
|
|
f4bce78307 | ||
|
|
5299db1a00 | ||
|
|
938daad1c9 | ||
|
|
1709ee3ab7 | ||
|
|
479bfa9964 | ||
|
|
a7f5589946 | ||
|
|
6209639f2c | ||
|
|
7cd6118ce5 | ||
|
|
490d471c53 | ||
|
|
859fc7f18f | ||
|
|
1310606305 | ||
|
|
a7db50597c | ||
|
|
c506a48154 | ||
|
|
a5df24b488 | ||
|
|
e0b23efe95 | ||
|
|
608e0f52ee | ||
|
|
7e5f81f255 | ||
|
|
832c744578 | ||
|
|
b70ce07706 | ||
|
|
1c3df652de | ||
|
|
9192cbe39c | ||
|
|
54e7cbdf8f | ||
|
|
afecb83988 | ||
|
|
6ea8bece9b | ||
|
|
01a9eee991 | ||
|
|
0911173a89 | ||
|
|
42e04907ff | ||
|
|
d29c54c476 | ||
|
|
89a76c785c | ||
|
|
a7eb596d89 | ||
|
|
8e0f008841 | ||
|
|
c1e8b30877 | ||
|
|
2c6b8a481e | ||
|
|
c327865b4b | ||
|
|
53fc7392bb | ||
|
|
1734b5e465 | ||
|
|
7825b9ee9f | ||
|
|
1150d0f70f | ||
|
|
7b10dc8a48 | ||
|
|
94568bc08d | ||
|
|
fa74a6e60c | ||
|
|
48f26d799f | ||
|
|
ec40268e55 | ||
|
|
46c621175c | ||
|
|
676fbbb662 | ||
|
|
4245363cf4 | ||
|
|
ce3bdbccc2 | ||
|
|
ad3f762409 | ||
|
|
ac99a1f791 |
2
.gitignore
vendored
@@ -1,5 +1,5 @@
|
||||
*.pyc
|
||||
*.o
|
||||
wsproxy
|
||||
tests/data_*.js
|
||||
utils/rebind.so
|
||||
node_modules
|
||||
|
||||
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
[submodule "include/web-socket-js-project"]
|
||||
path = include/web-socket-js-project
|
||||
url = https://github.com/gimite/web-socket-js.git
|
||||
16
.travis.yml
Normal file
@@ -0,0 +1,16 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- '0.11.13'
|
||||
env:
|
||||
matrix:
|
||||
- TEST_BROWSER_NAME=PhantomJS
|
||||
- TEST_BROWSER_NAME=chrome TEST_BROWSER_OS='Windows 7,Linux'
|
||||
- TEST_BROWSER_NAME=firefox TEST_BROWSER_OS='Windows 7,Linux' TEST_BROWSER_VERSION='30,26'
|
||||
- TEST_BROWSER_NAME='internet explorer' TEST_BROWSER_OS='Windows 7' TEST_BROWSER_VERSION=10
|
||||
- TEST_BROWSER_NAME='internet explorer' TEST_BROWSER_OS='Windows 8.1' TEST_BROWSER_VERSION=11
|
||||
- TEST_BROWSER_NAME=safari TEST_BROWSER_OS='OS X 10.8' TEST_BROWSER_VERSION=6
|
||||
- TEST_BROWSER_NAME=safari TEST_BROWSER_OS='OS X 10.9' TEST_BROWSER_VERSION=7
|
||||
global:
|
||||
- secure: QE5GqGd2hrpQsIgd8dlv3oRUUHqZayomzzQjNXOB81VQi241uz/ru+3GtBZLB5WLZCq/Gj89vbLnR0LN4ixlmPaWv3/WJQGyDGuRD/vMnccVl+rBUP/Hh2zdYwiISIGcrywNAE+KLus/lyt/ahVgzbaRaDSzrM1HaZFT/rndGck=
|
||||
- secure: g75sdctEwj0hoLW0Y08Tdv8s5scNzplB6a9EtaJ2vJD9S/bK+AsPqbWesGv1UlrFPCWdbV7Vg61vkmoUjcmb5xhqFIjcM9TlYJoKWeOTsOmnQoSIkIq6gMF1k02+LmKInbPgIzrp3m3jluS1qaOs/EzFpDnJp9hWBiAfXa12Jxk=
|
||||
before_script: npm install -g karma-cli
|
||||
54
CONTRIBUTING.md
Normal file
@@ -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!
|
||||
78
LICENSE.txt
@@ -1,30 +1,82 @@
|
||||
noVNC is Copyright (C) 2011 Joel Martin <github@martintribe.org>
|
||||
|
||||
The noVNC core library files are licensed under the MPL 2.0 (Mozilla
|
||||
Public License 2.0). The noVNC core library is composed of the
|
||||
Javascript code necessary for full noVNC operation. This includes (but
|
||||
is not limited to):
|
||||
|
||||
include/base64.js
|
||||
include/des.js
|
||||
include/display.js
|
||||
include/input.js
|
||||
include/jsunzip.js
|
||||
include/keysym.js
|
||||
include/logo.js
|
||||
include/rfb.js
|
||||
include/ui.js
|
||||
include/util.js
|
||||
include/vnc.js
|
||||
include/websock.js
|
||||
include/webutil.js
|
||||
|
||||
The HTML, CSS, font and images files that included with the noVNC
|
||||
source distibution (or repository) are not considered part of the
|
||||
noVNC core library and are licensed under more permissive licenses.
|
||||
The intent is to allow easy integration of noVNC into existing web
|
||||
sites and web applications.
|
||||
|
||||
The HTML, CSS, font and image files are licensed as follows:
|
||||
|
||||
*.html : 2-Clause BSD license
|
||||
|
||||
include/*.css : 2-Clause BSD license
|
||||
|
||||
include/Orbitron* : SIL Open Font License 1.1
|
||||
(Copyright 2009 Matt McInerney)
|
||||
|
||||
images/ : Creative Commons Attribution-ShareAlike
|
||||
http://creativecommons.org/licenses/by-sa/3.0/
|
||||
|
||||
Some portions of noVNC are copyright to their individual authors.
|
||||
Please refer to the individual source files and/or to the noVNC commit
|
||||
history: https://github.com/kanaka/noVNC/commits/master
|
||||
|
||||
noVNC is licensed under the LGPL (GNU Lesser General Public License)
|
||||
version 3 with the following exceptions:
|
||||
The are several files and projects that have been incorporated into
|
||||
the noVNC core library. Here is a list of those files and the original
|
||||
licenses (all MPL 2.0 compatible):
|
||||
|
||||
include/input.js : LGPL-2 or any later version
|
||||
include/base64.js : MPL 2.0
|
||||
|
||||
include/base64.js : Dual GPL-2 or LGPL-2.1
|
||||
|
||||
incluee/des.js : Various BSD style licenses
|
||||
include/des.js : Various BSD style licenses
|
||||
|
||||
include/web-socket-js/ : New BSD license. Source code at
|
||||
include/jsunzip.js : zlib/libpng license
|
||||
|
||||
include/web-socket-js/ : New BSD license (3-clause). Source code at
|
||||
http://github.com/gimite/web-socket-js
|
||||
|
||||
images/ : Creative Commons Attribution-ShareAlike
|
||||
http://creativecommons.org/licenses/by-sa/3.0/
|
||||
Uses of the work must be attributed
|
||||
to the noVNC project.
|
||||
include/chrome-app/tcp-stream.js
|
||||
: Apache 2.0 license
|
||||
|
||||
The LGPL-3 license text is included at:
|
||||
utils/websockify
|
||||
utils/websocket.py : LGPL 3
|
||||
|
||||
The following license texts are included:
|
||||
|
||||
docs/LICENSE.MPL-2.0
|
||||
docs/LICENSE.LGPL-3 and
|
||||
docs/LICENSE.GPL-3
|
||||
docs/LICENSE.OFL-1.1
|
||||
docs/LICENSE.BSD-3-Clause (New BSD)
|
||||
docs/LICENSE.BSD-2-Clause (Simplified BSD / FreeBSD)
|
||||
docs/LICENSE.zlib
|
||||
docs/LICENSE.Apache-2.0
|
||||
|
||||
Or alternatively the LGPL-3 license text may be found here:
|
||||
Or alternatively the license texts may be found here:
|
||||
|
||||
http://www.mozilla.org/MPL/2.0/
|
||||
http://www.gnu.org/licenses/lgpl.html and
|
||||
http://www.gnu.org/licenses/gpl.html
|
||||
http://scripts.sil.org/OFL
|
||||
http://en.wikipedia.org/wiki/BSD_licenses
|
||||
http://www.gzip.org/zlib/zlib_license.html
|
||||
http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
||||
121
README.md
@@ -1,71 +1,95 @@
|
||||
## noVNC: HTML5 VNC Client
|
||||
|
||||
[](https://travis-ci.org/kanaka/noVNC)
|
||||
|
||||
### Description
|
||||
|
||||
noVNC is a VNC client implemented using HTML5 technologies,
|
||||
specifically Canvas and WebSockets (supports 'wss://' encryption).
|
||||
noVNC is licensed under the
|
||||
[LGPLv3](http://www.gnu.org/licenses/lgpl.html).
|
||||
noVNC is a HTML5 VNC client that runs well in any modern browser
|
||||
including mobile browsers (iPhone/iPad and Android).
|
||||
|
||||
Special thanks to [Sentry Data Systems](http://www.sentryds.com) for
|
||||
sponsoring ongoing development of this project (and for employing me).
|
||||
Many companies/projects have integrated noVNC including [Ganeti Web
|
||||
Manager](http://code.osuosl.org/projects/ganeti-webmgr),
|
||||
[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.
|
||||
|
||||
There are many companies/projects that have integrated noVNC into
|
||||
their products including: [Sentry Data Systems](http://www.sentryds.com), [Ganeti Web Manager](http://code.osuosl.org/projects/ganeti-webmgr), [Archipel](http://archipelproject.org), [openQRM](http://www.openqrm.com/), [OpenNode](http://www.opennodecloud.com/), [OpenStack](http://www.openstack.org), [Broadway (HTML5 GDK/GTK+ backend)](http://blogs.gnome.org/alexl/2011/03/15/gtk-html-backend-update/), [OpenNebula](http://opennebula.org/) and [CloudSigma](http://www.cloudsigma.com/). See [this wiki page](https://github.com/kanaka/noVNC/wiki/ProjectsCompanies-using-noVNC) for more 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
|
||||
href="https://groups.google.com/forum/?fromgroups#!forum/novnc">noVNC
|
||||
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 are marked as
|
||||
["patchwelcome"](https://github.com/kanaka/noVNC/issues?labels=patchwelcome).
|
||||
|
||||
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. Please tweet <a
|
||||
href="http://www.twitter.com/noVNC">@noVNC</a> if you do.
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Supports all modern browsers including mobile (iOS, Android)
|
||||
* Supported VNC encodings: raw, copyrect, rre, hextile, tight, tightPNG
|
||||
* WebSocket SSL/TLS encryption (i.e. "wss://") support
|
||||
* 24-bit true color and 8 bit colour mapped
|
||||
* Supports desktop resize notification/pseudo-encoding
|
||||
* Local or remote cursor
|
||||
* Clipboard copy/paste
|
||||
* Clipping or scolling modes for large remote screens
|
||||
* Easy site integration and theming (3 example themes included)
|
||||
* Licensed under the [MPL 2.0](http://www.mozilla.org/MPL/2.0/)
|
||||
|
||||
### Screenshots
|
||||
|
||||
Running in Chrome before and after connecting:
|
||||
|
||||
<img src="http://kanaka.github.com/noVNC/img/noVNC-1.jpg" width=400> <img src="http://kanaka.github.com/noVNC/img/noVNC-2.jpg" width=400>
|
||||
<img src="http://kanaka.github.com/noVNC/img/noVNC-5.png" width=400> <img src="http://kanaka.github.com/noVNC/img/noVNC-7.jpg" width=400>
|
||||
|
||||
See more screenshots <a href="http://kanaka.github.com/noVNC/screenshots.html">here</a>.
|
||||
|
||||
|
||||
### Browser Requirements
|
||||
|
||||
* HTML5 Canvas: Except for Internet Explorer, most
|
||||
browsers have had Canvas support for quite some time. Internet
|
||||
Explorer 9 will have Canvas support (finally).
|
||||
* HTML5 Canvas (with createImageData): Chrome, Firefox 3.6+, iOS
|
||||
Safari, Opera 11+, Internet Explorer 9+, etc.
|
||||
|
||||
* HTML5 WebSockets: For browsers that do not have builtin
|
||||
WebSockets support, the project includes
|
||||
<a href="http://github.com/gimite/web-socket-js">web-socket-js</a>,
|
||||
a WebSockets emulator using Adobe Flash.
|
||||
a WebSockets emulator using Adobe Flash. iOS 4.2+ has built-in
|
||||
WebSocket support.
|
||||
|
||||
* Fast Javascript Engine: noVNC avoids using new Javascript
|
||||
functionality so it will run on older browsers, but decode and
|
||||
rendering happen in Javascript, so a slow Javascript engine will
|
||||
mean noVNC is painfully slow.
|
||||
* 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 list of browser compatibility <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 (only my [fork of libvncserver](http://github.com/kanaka/libvncserver)
|
||||
currently), you need to use a WebSockets to TCP socket proxy. There is
|
||||
a python proxy included ('websockify'). One advantage of using the
|
||||
proxy is that it has builtin support for SSL/TLS encryption (i.e.
|
||||
"wss://").
|
||||
|
||||
There a few reasons why a proxy is required:
|
||||
|
||||
1. WebSockets is not a pure socket protocol. There is an initial HTTP
|
||||
like handshake to allow easy hand-off by web servers and allow
|
||||
some origin policy exchange. Also, each WebSockets frame begins
|
||||
with 0 ('\x00') and ends with 255 ('\xff').
|
||||
|
||||
2. Javascript itself does not have the ability to handle pure byte
|
||||
arrays. The python proxy encodes the data as base64 so that the
|
||||
Javascript client can decode the data as an integer array.
|
||||
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
|
||||
@@ -83,11 +107,32 @@ There a few reasons why a proxy is required:
|
||||
|
||||
### Other Pages
|
||||
|
||||
* [Advanced Usage](https://github.com/kanaka/noVNC/wiki/Advanced-usage). Generating an SSL
|
||||
certificate, starting a VNC server, advanced websockify usage, etc.
|
||||
* [Encrypted Connections](https://github.com/kanaka/websockify/wiki/Encrypted-Connections). How to setup websockify so that you can use encrypted connections from noVNC.
|
||||
|
||||
* [Advanced Usage](https://github.com/kanaka/noVNC/wiki/Advanced-usage). Starting a VNC server, advanced websockify usage, etc.
|
||||
|
||||
* [Integrating noVNC](https://github.com/kanaka/noVNC/wiki/Integration) into existing projects.
|
||||
|
||||
* [Troubleshooting noVNC](https://github.com/kanaka/noVNC/wiki/Troubleshooting) problems.
|
||||
|
||||
|
||||
### Authors/Contributors
|
||||
|
||||
* 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)
|
||||
|
||||
* Included libraries:
|
||||
* web-socket-js : Hiroshi Ichikawa (github.com/gimite/web-socket-js)
|
||||
* as3crypto : Henri Torgemane (code.google.com/p/as3crypto)
|
||||
* base64 : Martijn Pieters (Digital Creations 2), Samuel Sieb (sieb.net)
|
||||
* jsunzip : Erik Moller (github.com/operasoftware/jsunzip),
|
||||
* tinflate : Joergen Ibsen (ibsensoftware.com)
|
||||
* DES : Dave Zimmerman (Widget Workshop), Jef Poskanzer (ACME Labs)
|
||||
|
||||
30
debian/changelog
vendored
@@ -1,3 +1,33 @@
|
||||
novnc (0.4) maverick; urgency=low
|
||||
|
||||
* Clarify permissive licenses of HTML, CSS, images.
|
||||
* Use render queue and requestAnimationFrame
|
||||
* UltraVNC repeater support
|
||||
|
||||
-- Joel Martin <github@martintribe.org> Fri, 14 Sep 2012 05:00:00 -0600
|
||||
|
||||
novnc (0.3) maverick; urgency=low
|
||||
|
||||
* add tight encoding support
|
||||
* release pressed key when focus lost (fixes locked Alt key)
|
||||
* Support Apple Remote Desktop
|
||||
* Add nova/openstack proxy wrapper
|
||||
* Better connection close handling/reporting
|
||||
|
||||
-- Joel Martin <github@martintribe.org> Fri, 11 May 2012 03:00:00 -0600
|
||||
|
||||
novnc (0.2) maverick; urgency=low
|
||||
|
||||
* Mobile device support with viewport clipping
|
||||
* Much better styling that also works on mobile devices well
|
||||
* Update websockify to support latest WebSocket protocol HyBi 13
|
||||
(i.e. support IETF 6455)
|
||||
* Better support in websockify for python 2.4 through 3.2
|
||||
* Support VMWare ESX and Intel AMT KVM
|
||||
* View only mode
|
||||
|
||||
-- Joel Martin <github@martintribe.org> Tue, 05 Jul 2011 01:00:00 -0600
|
||||
|
||||
novnc (0.1) maverick; urgency=low
|
||||
|
||||
* First upstream release
|
||||
|
||||
39
debian/copyright
vendored
@@ -2,35 +2,36 @@ Upstream Project: https://github.com/kanaka/noVNC/
|
||||
|
||||
--------------------- Original LICENSE.txt ---------------------------
|
||||
|
||||
noVNC is Copyright (C) 2011 Joel Martin <github@martintribe.org>
|
||||
noVNC is Copyright (C) 2012 Joel Martin <github@martintribe.org>
|
||||
|
||||
Some portions of noVNC are copyright to their individual authors.
|
||||
Please refer to the individual source files and/or to the noVNC commit
|
||||
history: https://github.com/kanaka/noVNC/commits/master
|
||||
|
||||
noVNC is licensed under the LGPL (GNU Lesser General Public License)
|
||||
version 3 with the following exceptions:
|
||||
noVNC is licensed under the MPL 2.0 (Mozilla Public License) with the
|
||||
following exceptions:
|
||||
|
||||
include/input.js : LGPL-2 or any later version
|
||||
|
||||
include/base64.js : Dual GPL-2 or LGPL-2.1
|
||||
|
||||
incluee/des.js : Various BSD style licenses
|
||||
*.html, *.css : 2-Clause BSD license
|
||||
|
||||
include/web-socket-js/ : New BSD license. Source code at
|
||||
http://github.com/gimite/web-socket-js
|
||||
include/Orbitron* : SIL Open Font License 1.1
|
||||
(Copyright 2009 Matt McInerney)
|
||||
|
||||
images/ : Creative Commons Attribution-ShareAlike
|
||||
http://creativecommons.org/licenses/by-sa/3.0/
|
||||
Uses of the work must be attributed
|
||||
to the noVNC project.
|
||||
|
||||
include/base64.js : MPL 2.0
|
||||
|
||||
include/des.js : Various BSD style licenses
|
||||
|
||||
include/jsunzip.js : zlib/libpng license
|
||||
|
||||
include/web-socket-js/ : New BSD license (3-clause). Source code at
|
||||
http://github.com/gimite/web-socket-js
|
||||
|
||||
include/chrome-app/tcp-stream.js
|
||||
: Apache 2.0 license
|
||||
|
||||
----------------------------------------------------------------------
|
||||
|
||||
The LGPL-3 license text is located at:
|
||||
/usr/share/common-licenses/LGPL-3 and
|
||||
/usr/share/common-licenses/GPL-3
|
||||
|
||||
Or alternatively the LGPL-3 license text may be found here:
|
||||
http://www.gnu.org/licenses/lgpl.html and
|
||||
http://www.gnu.org/licenses/gpl.html
|
||||
The MPL-2.0 license text may be found here:
|
||||
http://www.mozilla.org/MPL/2.0/
|
||||
|
||||
11
debian/novnc.install
vendored
@@ -6,22 +6,25 @@ utils/Makefile /usr/share/novnc/utils
|
||||
utils/launch.sh /usr/share/novnc/utils
|
||||
utils/websocket.py /usr/share/novnc/utils
|
||||
utils/websockify /usr/share/novnc/utils
|
||||
utils/wsproxy.py /usr/share/novnc/utils
|
||||
utils/rebind.c /usr/share/novnc/utils
|
||||
utils/rebind.so /usr/share/novnc/utils
|
||||
images /usr/share/novnc
|
||||
images/favicon.ico /usr/share/novnc
|
||||
include/base64.js /usr/share/novnc/include
|
||||
include/black.css /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/plain.css /usr/share/novnc/include
|
||||
include/base.css /usr/share/novnc/include
|
||||
include/blue.css /usr/share/novnc/include
|
||||
include/black.css /usr/share/novnc/include
|
||||
include/playback.js /usr/share/novnc/include
|
||||
include/rfb.js /usr/share/novnc/include
|
||||
include/ui.js /usr/share/novnc/include
|
||||
include/util.js /usr/share/novnc/include
|
||||
include/vnc.js /usr/share/novnc/include
|
||||
include/websock.js /usr/share/novnc/include
|
||||
include/webutil.js /usr/share/novnc/include
|
||||
include/jsunzip.js /usr/share/novnc/include
|
||||
include/web-socket-js/* /usr/share/novnc/include/web-socket-js
|
||||
|
||||
202
docs/LICENSE.Apache-2.0
Normal file
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
22
docs/LICENSE.BSD-2-Clause
Normal file
@@ -0,0 +1,22 @@
|
||||
Copyright (c) <year>, <copyright holder>
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
24
docs/LICENSE.BSD-3-Clause
Normal file
@@ -0,0 +1,24 @@
|
||||
Copyright (c) <year>, <copyright holder>
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the <organization> nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
373
docs/LICENSE.MPL-2.0
Normal file
@@ -0,0 +1,373 @@
|
||||
Mozilla Public License Version 2.0
|
||||
==================================
|
||||
|
||||
1. Definitions
|
||||
--------------
|
||||
|
||||
1.1. "Contributor"
|
||||
means each individual or legal entity that creates, contributes to
|
||||
the creation of, or owns Covered Software.
|
||||
|
||||
1.2. "Contributor Version"
|
||||
means the combination of the Contributions of others (if any) used
|
||||
by a Contributor and that particular Contributor's Contribution.
|
||||
|
||||
1.3. "Contribution"
|
||||
means Covered Software of a particular Contributor.
|
||||
|
||||
1.4. "Covered Software"
|
||||
means Source Code Form to which the initial Contributor has attached
|
||||
the notice in Exhibit A, the Executable Form of such Source Code
|
||||
Form, and Modifications of such Source Code Form, in each case
|
||||
including portions thereof.
|
||||
|
||||
1.5. "Incompatible With Secondary Licenses"
|
||||
means
|
||||
|
||||
(a) that the initial Contributor has attached the notice described
|
||||
in Exhibit B to the Covered Software; or
|
||||
|
||||
(b) that the Covered Software was made available under the terms of
|
||||
version 1.1 or earlier of the License, but not also under the
|
||||
terms of a Secondary License.
|
||||
|
||||
1.6. "Executable Form"
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. "Larger Work"
|
||||
means a work that combines Covered Software with other material, in
|
||||
a separate file or files, that is not Covered Software.
|
||||
|
||||
1.8. "License"
|
||||
means this document.
|
||||
|
||||
1.9. "Licensable"
|
||||
means having the right to grant, to the maximum extent possible,
|
||||
whether at the time of the initial grant or subsequently, any and
|
||||
all of the rights conveyed by this License.
|
||||
|
||||
1.10. "Modifications"
|
||||
means any of the following:
|
||||
|
||||
(a) any file in Source Code Form that results from an addition to,
|
||||
deletion from, or modification of the contents of Covered
|
||||
Software; or
|
||||
|
||||
(b) any new file in Source Code Form that contains any Covered
|
||||
Software.
|
||||
|
||||
1.11. "Patent Claims" of a Contributor
|
||||
means any patent claim(s), including without limitation, method,
|
||||
process, and apparatus claims, in any patent Licensable by such
|
||||
Contributor that would be infringed, but for the grant of the
|
||||
License, by the making, using, selling, offering for sale, having
|
||||
made, import, or transfer of either its Contributions or its
|
||||
Contributor Version.
|
||||
|
||||
1.12. "Secondary License"
|
||||
means either the GNU General Public License, Version 2.0, the GNU
|
||||
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||
Public License, Version 3.0, or any later versions of those
|
||||
licenses.
|
||||
|
||||
1.13. "Source Code Form"
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
1.14. "You" (or "Your")
|
||||
means an individual or a legal entity exercising rights under this
|
||||
License. For legal entities, "You" includes any entity that
|
||||
controls, is controlled by, or is under common control with You. For
|
||||
purposes of this definition, "control" means (a) the power, direct
|
||||
or indirect, to cause the direction or management of such entity,
|
||||
whether by contract or otherwise, or (b) ownership of more than
|
||||
fifty percent (50%) of the outstanding shares or beneficial
|
||||
ownership of such entity.
|
||||
|
||||
2. License Grants and Conditions
|
||||
--------------------------------
|
||||
|
||||
2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
(a) under intellectual property rights (other than patent or trademark)
|
||||
Licensable by such Contributor to use, reproduce, make available,
|
||||
modify, display, perform, distribute, and otherwise exploit its
|
||||
Contributions, either on an unmodified basis, with Modifications, or
|
||||
as part of a Larger Work; and
|
||||
|
||||
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
||||
for sale, have made, import, and otherwise transfer either its
|
||||
Contributions or its Contributor Version.
|
||||
|
||||
2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution
|
||||
become effective for each Contribution on the date the Contributor first
|
||||
distributes such Contribution.
|
||||
|
||||
2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under
|
||||
this License. No additional rights or licenses will be implied from the
|
||||
distribution or licensing of Covered Software under this License.
|
||||
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||
Contributor:
|
||||
|
||||
(a) for any code that a Contributor has removed from Covered Software;
|
||||
or
|
||||
|
||||
(b) for infringements caused by: (i) Your and any other third party's
|
||||
modifications of Covered Software, or (ii) the combination of its
|
||||
Contributions with other software (except as part of its Contributor
|
||||
Version); or
|
||||
|
||||
(c) under Patent Claims infringed by Covered Software in the absence of
|
||||
its Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks,
|
||||
or logos of any Contributor (except as may be necessary to comply with
|
||||
the notice requirements in Section 3.4).
|
||||
|
||||
2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to
|
||||
distribute the Covered Software under a subsequent version of this
|
||||
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||
permitted under the terms of Section 3.3).
|
||||
|
||||
2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its
|
||||
Contributions are its original creation(s) or it has sufficient rights
|
||||
to grant the rights to its Contributions conveyed by this License.
|
||||
|
||||
2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under
|
||||
applicable copyright doctrines of fair use, fair dealing, or other
|
||||
equivalents.
|
||||
|
||||
2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||
in Section 2.1.
|
||||
|
||||
3. Responsibilities
|
||||
-------------------
|
||||
|
||||
3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any
|
||||
Modifications that You create or to which You contribute, must be under
|
||||
the terms of this License. You must inform recipients that the Source
|
||||
Code Form of the Covered Software is governed by the terms of this
|
||||
License, and how they can obtain a copy of this License. You may not
|
||||
attempt to alter or restrict the recipients' rights in the Source Code
|
||||
Form.
|
||||
|
||||
3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
(a) such Covered Software must also be made available in Source Code
|
||||
Form, as described in Section 3.1, and You must inform recipients of
|
||||
the Executable Form how they can obtain a copy of such Source Code
|
||||
Form by reasonable means in a timely manner, at a charge no more
|
||||
than the cost of distribution to the recipient; and
|
||||
|
||||
(b) You may distribute such Executable Form under the terms of this
|
||||
License, or sublicense it under different terms, provided that the
|
||||
license for the Executable Form does not attempt to limit or alter
|
||||
the recipients' rights in the Source Code Form under this License.
|
||||
|
||||
3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice,
|
||||
provided that You also comply with the requirements of this License for
|
||||
the Covered Software. If the Larger Work is a combination of Covered
|
||||
Software with a work governed by one or more Secondary Licenses, and the
|
||||
Covered Software is not Incompatible With Secondary Licenses, this
|
||||
License permits You to additionally distribute such Covered Software
|
||||
under the terms of such Secondary License(s), so that the recipient of
|
||||
the Larger Work may, at their option, further distribute the Covered
|
||||
Software under the terms of either this License or such Secondary
|
||||
License(s).
|
||||
|
||||
3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices
|
||||
(including copyright notices, patent notices, disclaimers of warranty,
|
||||
or limitations of liability) contained within the Source Code Form of
|
||||
the Covered Software, except that You may alter any license notices to
|
||||
the extent required to remedy known factual inaccuracies.
|
||||
|
||||
3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of Covered
|
||||
Software. However, You may do so only on Your own behalf, and not on
|
||||
behalf of any Contributor. You must make it absolutely clear that any
|
||||
such warranty, support, indemnity, or liability obligation is offered by
|
||||
You alone, and You hereby agree to indemnify every Contributor for any
|
||||
liability incurred by such Contributor as a result of warranty, support,
|
||||
indemnity or liability terms You offer. You may include additional
|
||||
disclaimers of warranty and limitations of liability specific to any
|
||||
jurisdiction.
|
||||
|
||||
4. Inability to Comply Due to Statute or Regulation
|
||||
---------------------------------------------------
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this
|
||||
License with respect to some or all of the Covered Software due to
|
||||
statute, judicial order, or regulation then You must: (a) comply with
|
||||
the terms of this License to the maximum extent possible; and (b)
|
||||
describe the limitations and the code they affect. Such description must
|
||||
be placed in a text file included with all distributions of the Covered
|
||||
Software under this License. Except to the extent prohibited by statute
|
||||
or regulation, such description must be sufficiently detailed for a
|
||||
recipient of ordinary skill to be able to understand it.
|
||||
|
||||
5. Termination
|
||||
--------------
|
||||
|
||||
5.1. The rights granted under this License will terminate automatically
|
||||
if You fail to comply with any of its terms. However, if You become
|
||||
compliant, then the rights granted under this License from a particular
|
||||
Contributor are reinstated (a) provisionally, unless and until such
|
||||
Contributor explicitly and finally terminates Your grants, and (b) on an
|
||||
ongoing basis, if such Contributor fails to notify You of the
|
||||
non-compliance by some reasonable means prior to 60 days after You have
|
||||
come back into compliance. Moreover, Your grants from a particular
|
||||
Contributor are reinstated on an ongoing basis if such Contributor
|
||||
notifies You of the non-compliance by some reasonable means, this is the
|
||||
first time You have received notice of non-compliance with this License
|
||||
from such Contributor, and You become compliant prior to 30 days after
|
||||
Your receipt of the notice.
|
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent
|
||||
infringement claim (excluding declaratory judgment actions,
|
||||
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||
directly or indirectly infringes any patent, then the rights granted to
|
||||
You by any and all Contributors for the Covered Software under Section
|
||||
2.1 of this License shall terminate.
|
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
||||
end user license agreements (excluding distributors and resellers) which
|
||||
have been validly granted by You or Your distributors under this License
|
||||
prior to termination shall survive termination.
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 6. Disclaimer of Warranty *
|
||||
* ------------------------- *
|
||||
* *
|
||||
* Covered Software is provided under this License on an "as is" *
|
||||
* basis, without warranty of any kind, either expressed, implied, or *
|
||||
* statutory, including, without limitation, warranties that the *
|
||||
* Covered Software is free of defects, merchantable, fit for a *
|
||||
* particular purpose or non-infringing. The entire risk as to the *
|
||||
* quality and performance of the Covered Software is with You. *
|
||||
* Should any Covered Software prove defective in any respect, You *
|
||||
* (not any Contributor) assume the cost of any necessary servicing, *
|
||||
* repair, or correction. This disclaimer of warranty constitutes an *
|
||||
* essential part of this License. No use of any Covered Software is *
|
||||
* authorized under this License except under this disclaimer. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 7. Limitation of Liability *
|
||||
* -------------------------- *
|
||||
* *
|
||||
* Under no circumstances and under no legal theory, whether tort *
|
||||
* (including negligence), contract, or otherwise, shall any *
|
||||
* Contributor, or anyone who distributes Covered Software as *
|
||||
* permitted above, be liable to You for any direct, indirect, *
|
||||
* special, incidental, or consequential damages of any character *
|
||||
* including, without limitation, damages for lost profits, loss of *
|
||||
* goodwill, work stoppage, computer failure or malfunction, or any *
|
||||
* and all other commercial damages or losses, even if such party *
|
||||
* shall have been informed of the possibility of such damages. This *
|
||||
* limitation of liability shall not apply to liability for death or *
|
||||
* personal injury resulting from such party's negligence to the *
|
||||
* extent applicable law prohibits such limitation. Some *
|
||||
* jurisdictions do not allow the exclusion or limitation of *
|
||||
* incidental or consequential damages, so this exclusion and *
|
||||
* limitation may not apply to You. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
8. Litigation
|
||||
-------------
|
||||
|
||||
Any litigation relating to this License may be brought only in the
|
||||
courts of a jurisdiction where the defendant maintains its principal
|
||||
place of business and such litigation shall be governed by laws of that
|
||||
jurisdiction, without reference to its conflict-of-law provisions.
|
||||
Nothing in this Section shall prevent a party's ability to bring
|
||||
cross-claims or counter-claims.
|
||||
|
||||
9. Miscellaneous
|
||||
----------------
|
||||
|
||||
This License represents the complete agreement concerning the subject
|
||||
matter hereof. If any provision of this License is held to be
|
||||
unenforceable, such provision shall be reformed only to the extent
|
||||
necessary to make it enforceable. Any law or regulation which provides
|
||||
that the language of a contract shall be construed against the drafter
|
||||
shall not be used to construe this License against a Contributor.
|
||||
|
||||
10. Versions of the License
|
||||
---------------------------
|
||||
|
||||
10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section
|
||||
10.3, no one other than the license steward has the right to modify or
|
||||
publish new versions of this License. Each version will be given a
|
||||
distinguishing version number.
|
||||
|
||||
10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version
|
||||
of the License under which You originally received the Covered Software,
|
||||
or under the terms of any subsequent version published by the license
|
||||
steward.
|
||||
|
||||
10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to
|
||||
create a new license for such software, you may create and use a
|
||||
modified version of this License if you rename the license and remove
|
||||
any references to the name of the license steward (except to note that
|
||||
such modified license differs from this License).
|
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||
Licenses
|
||||
|
||||
If You choose to distribute Source Code Form that is Incompatible With
|
||||
Secondary Licenses under the terms of this version of the License, the
|
||||
notice described in Exhibit B of this License must be attached.
|
||||
|
||||
Exhibit A - Source Code Form License Notice
|
||||
-------------------------------------------
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular
|
||||
file, then You may include the notice in a location (such as a LICENSE
|
||||
file in a relevant directory) where a recipient would be likely to look
|
||||
for such a notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
|
||||
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||
---------------------------------------------------------
|
||||
|
||||
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
defined by the Mozilla Public License, v. 2.0.
|
||||
91
docs/LICENSE.OFL-1.1
Normal file
@@ -0,0 +1,91 @@
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
27
docs/LICENSE.zlib
Normal file
@@ -0,0 +1,27 @@
|
||||
Copyright (c) <year>, <copyright holder>
|
||||
All rights reserved.
|
||||
|
||||
This software is provided 'as-is', without any express
|
||||
or implied warranty. In no event will the authors be
|
||||
held liable for any damages arising from the use of
|
||||
this software.
|
||||
|
||||
Permission is granted to anyone to use this software
|
||||
for any purpose, including commercial applications,
|
||||
and to alter it and redistribute it freely, subject to
|
||||
the following restrictions:
|
||||
|
||||
1. The origin of this software must not be
|
||||
misrepresented; you must not claim that you
|
||||
wrote the original software. If you use this
|
||||
software in a product, an acknowledgment in
|
||||
the product documentation would be appreciated
|
||||
but is not required.
|
||||
|
||||
2. Altered source versions must be plainly marked
|
||||
as such, and must not be misrepresented as
|
||||
being the original software.
|
||||
|
||||
3. This notice may not be removed or altered from
|
||||
any source distribution.
|
||||
|
||||
53
docs/TODO
@@ -1,53 +0,0 @@
|
||||
Short Term:
|
||||
|
||||
- Test normal arrays vs TypedArrays vs imageData arrays.
|
||||
- Blog post about it.
|
||||
|
||||
- VNC performance and regression playback suite.
|
||||
- WebSockets
|
||||
- expand latency test
|
||||
- add absolute timers (every 500 packets)
|
||||
- try 1 ms delay
|
||||
- stop at 4000 packets
|
||||
- small and large packets test
|
||||
|
||||
- JavaScript
|
||||
- just base64 decode
|
||||
- everything except Canvas
|
||||
|
||||
- Full test
|
||||
- Without WebSockets
|
||||
- With replay from python tester
|
||||
- add higher-resolution multi test
|
||||
|
||||
- websockify test with echo and playback functionality
|
||||
- choosen by client test page on connect
|
||||
|
||||
|
||||
- Keyboard layout/internationalization support
|
||||
- convert keyCode into proper charCode
|
||||
|
||||
- IE 9 improvements.
|
||||
- https://github.com/gimite/web-socket-js/issues#issue/41
|
||||
- try window.atob instead of decode for better performance
|
||||
|
||||
- Status bar menu/buttons:
|
||||
- Explanatory hover text over buttons
|
||||
|
||||
- Configuration menu:
|
||||
- Tunable: speed vs. bandwidth selection
|
||||
- Tunable: CPU use versus latency.
|
||||
- Scaling
|
||||
|
||||
- Keyboard menu:
|
||||
- Ctrl Lock, Alt Lock, SysRq Lock
|
||||
- Highlight menu icon when keys are locked
|
||||
|
||||
- Clipboard button -> popup:
|
||||
- text, clear and send buttons
|
||||
|
||||
Medium Term:
|
||||
|
||||
- Viewport support
|
||||
|
||||
- Touchscreen testing/support.
|
||||
@@ -1 +1 @@
|
||||
0.1
|
||||
0.4
|
||||
|
||||
@@ -8,7 +8,7 @@ Javascript doesn't have a bytearray type, so what you get out of
|
||||
a WebSocket object is just Javascript strings. Javascript has UTF-16
|
||||
unicode strings and anything sent through the WebSocket gets converted
|
||||
to UTF-8 and vice-versa. So, one additional (and necessary) function
|
||||
of wsproxy is base64 encoding/decoding what is sent to/from the
|
||||
of websockify is base64 encoding/decoding what is sent to/from the
|
||||
browser.
|
||||
|
||||
Building web-socket-js emulator:
|
||||
|
||||
9
docs/release.txt
Normal file
@@ -0,0 +1,9 @@
|
||||
- Update and commit docs/VERSION and debian/changelog
|
||||
- Create version tag and tarball from tag
|
||||
WVER=0.3
|
||||
git tag v${WVER}
|
||||
git push origin master
|
||||
git push origin v${WVER}
|
||||
git archive --format=tar --prefix=novnc-${WVER}/ v${WVER} > novnc-${WVER}.tar
|
||||
gzip novnc-${WVER}.tar
|
||||
- Upload tarball to repo
|
||||
687
images/Logo.svg
@@ -1,687 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="512"
|
||||
height="512"
|
||||
id="svg2"
|
||||
sodipodi:version="0.32"
|
||||
inkscape:version="0.47pre4 r22446"
|
||||
version="1.0"
|
||||
sodipodi:docname="noVNC_Logo_13.svg"
|
||||
inkscape:output_extension="org.inkscape.output.svg.inkscape"
|
||||
inkscape:export-filename="/home/joelm/Downloads/noVNC_Logo12.png"
|
||||
inkscape:export-xdpi="90"
|
||||
inkscape:export-ydpi="90"
|
||||
style="display:inline">
|
||||
<defs
|
||||
id="defs4">
|
||||
<linearGradient
|
||||
id="linearGradient7584">
|
||||
<stop
|
||||
id="stop7586"
|
||||
offset="0"
|
||||
style="stop-color:#171717;stop-opacity:1;" />
|
||||
<stop
|
||||
id="stop7588"
|
||||
offset="1"
|
||||
style="stop-color:#000000;stop-opacity:1;" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient5965">
|
||||
<stop
|
||||
style="stop-color:#030303;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop5967" />
|
||||
<stop
|
||||
id="stop5973"
|
||||
offset="0.5"
|
||||
style="stop-color:#090909;stop-opacity:1;" />
|
||||
<stop
|
||||
style="stop-color:#000000;stop-opacity:1;"
|
||||
offset="1"
|
||||
id="stop5969" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient5932">
|
||||
<stop
|
||||
style="stop-color:#000000;stop-opacity:0.87450981;"
|
||||
offset="0"
|
||||
id="stop5934" />
|
||||
<stop
|
||||
style="stop-color:#000000;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop5936" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient5333">
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:0.05;"
|
||||
offset="0"
|
||||
id="stop5335" />
|
||||
<stop
|
||||
style="stop-color:#4c4c4c;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop5337" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient4694">
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:0.03529412;"
|
||||
offset="0"
|
||||
id="stop4696" />
|
||||
<stop
|
||||
style="stop-color:#4c4c4c;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop4698" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient6989">
|
||||
<stop
|
||||
style="stop-color:#000000;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop6991" />
|
||||
<stop
|
||||
style="stop-color:#0f0f0f;stop-opacity:1;"
|
||||
offset="1"
|
||||
id="stop6993" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient6790">
|
||||
<stop
|
||||
style="stop-color:#434343;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop6792" />
|
||||
<stop
|
||||
style="stop-color:#000000;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop6794" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient6736">
|
||||
<stop
|
||||
style="stop-color:#000000;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop6738" />
|
||||
<stop
|
||||
style="stop-color:#000000;stop-opacity:1;"
|
||||
offset="1"
|
||||
id="stop6740" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient6720">
|
||||
<stop
|
||||
style="stop-color:#303030;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop6722" />
|
||||
<stop
|
||||
style="stop-color:#000000;stop-opacity:1;"
|
||||
offset="1"
|
||||
id="stop6724" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient6702">
|
||||
<stop
|
||||
id="stop4688"
|
||||
offset="0"
|
||||
style="stop-color:#000000;stop-opacity:0;" />
|
||||
<stop
|
||||
id="stop6714"
|
||||
offset="1"
|
||||
style="stop-color:#ffffff;stop-opacity:0;" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient6662">
|
||||
<stop
|
||||
style="stop-color:#010000;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop6664" />
|
||||
<stop
|
||||
style="stop-color:#000000;stop-opacity:1;"
|
||||
offset="1"
|
||||
id="stop6666" />
|
||||
</linearGradient>
|
||||
<marker
|
||||
inkscape:stockid="Arrow1Send"
|
||||
orient="auto"
|
||||
refY="0"
|
||||
refX="0"
|
||||
id="Arrow1Send"
|
||||
style="overflow:visible">
|
||||
<path
|
||||
id="path4730"
|
||||
d="M 0,0 5,-5 -12.5,0 5,5 0,0 z"
|
||||
style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;marker-start:none"
|
||||
transform="matrix(-0.2,0,0,-0.2,-1.2,0)" />
|
||||
</marker>
|
||||
<marker
|
||||
inkscape:stockid="Arrow1Mstart"
|
||||
orient="auto"
|
||||
refY="0"
|
||||
refX="0"
|
||||
id="Arrow1Mstart"
|
||||
style="overflow:visible">
|
||||
<path
|
||||
id="path4721"
|
||||
d="M 0,0 5,-5 -12.5,0 5,5 0,0 z"
|
||||
style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;marker-start:none"
|
||||
transform="matrix(0.4,0,0,0.4,4,0)" />
|
||||
</marker>
|
||||
<marker
|
||||
inkscape:stockid="Arrow1Lstart"
|
||||
orient="auto"
|
||||
refY="0"
|
||||
refX="0"
|
||||
id="Arrow1Lstart"
|
||||
style="overflow:visible">
|
||||
<path
|
||||
id="path4715"
|
||||
d="M 0,0 5,-5 -12.5,0 5,5 0,0 z"
|
||||
style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;marker-start:none"
|
||||
transform="matrix(0.8,0,0,0.8,10,0)" />
|
||||
</marker>
|
||||
<inkscape:perspective
|
||||
sodipodi:type="inkscape:persp3d"
|
||||
inkscape:vp_x="-24.105461 : 505.9325 : 1"
|
||||
inkscape:vp_y="6.1230318e-14 : 1000 : 0"
|
||||
inkscape:vp_z="719.98902 : 505.9325 : 1"
|
||||
inkscape:persp3d-origin="347.94178 : 330.5388 : 1"
|
||||
id="perspective10" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient6720"
|
||||
id="linearGradient6742"
|
||||
x1="341.66687"
|
||||
y1="-253.40732"
|
||||
x2="470.19839"
|
||||
y2="-253.40732"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(0.874106,0,0,1.0453135,43.679874,11.482781)" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient6790"
|
||||
id="linearGradient6796"
|
||||
x1="-32.526917"
|
||||
y1="294.91821"
|
||||
x2="653.36664"
|
||||
y2="294.91821"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient7584"
|
||||
id="linearGradient6987"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(0.6520109,0,0,1.5543006,133.44336,136.96388)"
|
||||
x1="341.66687"
|
||||
y1="-253.40732"
|
||||
x2="470.19839"
|
||||
y2="-253.40732" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient6702"
|
||||
id="linearGradient4686"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="159.54247"
|
||||
y1="443.13766"
|
||||
x2="430.90103"
|
||||
y2="443.13766" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient4694"
|
||||
id="linearGradient4700"
|
||||
x1="229.03033"
|
||||
y1="461.81128"
|
||||
x2="393.28391"
|
||||
y2="461.81128"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient4694"
|
||||
id="linearGradient4966"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="229.03033"
|
||||
y1="461.81128"
|
||||
x2="393.28391"
|
||||
y2="461.81128"
|
||||
spreadMethod="reflect" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient5333"
|
||||
id="linearGradient5339"
|
||||
x1="271.28635"
|
||||
y1="294.12686"
|
||||
x2="490.23206"
|
||||
y2="294.12686"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient5932"
|
||||
id="linearGradient5938"
|
||||
x1="-245.06445"
|
||||
y1="7.705513"
|
||||
x2="-62.328537"
|
||||
y2="7.705513"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(2.5245666,0,0,2.6282551,644.92825,162.55033)" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient5965"
|
||||
id="linearGradient5971"
|
||||
x1="159.54247"
|
||||
y1="443.13766"
|
||||
x2="430.90103"
|
||||
y2="443.13766"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
spreadMethod="pad" />
|
||||
<radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient6720"
|
||||
id="radialGradient7582"
|
||||
cx="295.22174"
|
||||
cy="443.13766"
|
||||
fx="295.22174"
|
||||
fy="443.13766"
|
||||
r="135.67928"
|
||||
gradientTransform="matrix(1,0,0,0.1356784,0,383.01346)"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient6720"
|
||||
id="radialGradient7611"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(1,0,0,0.1356784,0,383.01346)"
|
||||
cx="295.22174"
|
||||
cy="443.13766"
|
||||
fx="295.22174"
|
||||
fy="443.13766"
|
||||
r="135.67928" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient4694"
|
||||
id="linearGradient7613"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
spreadMethod="reflect"
|
||||
x1="229.03033"
|
||||
y1="461.81128"
|
||||
x2="393.28391"
|
||||
y2="461.81128" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient5333"
|
||||
id="linearGradient2913"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="271.28635"
|
||||
y1="294.12686"
|
||||
x2="490.23206"
|
||||
y2="294.12686"
|
||||
gradientTransform="matrix(1.0217389,0,0,1.0105488,-8.1719712,-6.8195787)" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient5932"
|
||||
id="linearGradient2916"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(2.5891218,0,0,2.7050059,653.14704,159.9845)"
|
||||
x1="-245.06445"
|
||||
y1="7.705513"
|
||||
x2="-62.328537"
|
||||
y2="7.705513" />
|
||||
<inkscape:perspective
|
||||
id="perspective2920"
|
||||
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
|
||||
inkscape:vp_z="1 : 0.5 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_x="0 : 0.5 : 1"
|
||||
sodipodi:type="inkscape:persp3d" />
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
gridtolerance="10000"
|
||||
guidetolerance="10"
|
||||
objecttolerance="10"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="1"
|
||||
inkscape:cx="198.11269"
|
||||
inkscape:cy="276.37194"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="svg2"
|
||||
showgrid="false"
|
||||
inkscape:window-width="1680"
|
||||
inkscape:window-height="1026"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
showborder="false"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer2"
|
||||
inkscape:label="Screen"
|
||||
style="display:inline">
|
||||
<rect
|
||||
style="fill:#303030;fill-opacity:1;stroke:none;display:inline"
|
||||
id="rect6684"
|
||||
width="473.59906"
|
||||
height="321.92017"
|
||||
x="18.594318"
|
||||
y="19.852146"
|
||||
inkscape:export-filename="/home/joelm/Downloads/noVNC_screen.png"
|
||||
inkscape:export-xdpi="121.61401"
|
||||
inkscape:export-ydpi="121.61401" />
|
||||
</g>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer15"
|
||||
inkscape:label="Favicon_Box"
|
||||
style="display:none">
|
||||
<rect
|
||||
style="opacity:0;fill:#e6e6e6;fill-opacity:1;fill-rule:nonzero;stroke:none;display:inline"
|
||||
id="rect3899"
|
||||
width="342.5874"
|
||||
height="342.62888"
|
||||
x="86.710419"
|
||||
y="14.318709"
|
||||
inkscape:export-filename="/home/joelm/Downloads/noVNC_favicon.png"
|
||||
inkscape:export-xdpi="4.1999998"
|
||||
inkscape:export-ydpi="4.1999998" />
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;display:inline"
|
||||
id="rect3899-5"
|
||||
width="342.5874"
|
||||
height="342.62888"
|
||||
x="86.710419"
|
||||
y="14.31871"
|
||||
inkscape:export-filename="/home/joelm/Downloads/noVNC_favicon.png"
|
||||
inkscape:export-xdpi="4.1999998"
|
||||
inkscape:export-ydpi="4.1999998" />
|
||||
</g>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer6"
|
||||
inkscape:label="Base"
|
||||
style="display:inline">
|
||||
<path
|
||||
sodipodi:type="arc"
|
||||
style="fill:url(#linearGradient5971);fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path6944"
|
||||
sodipodi:cx="295.22174"
|
||||
sodipodi:cy="443.13766"
|
||||
sodipodi:rx="135.67928"
|
||||
sodipodi:ry="18.408747"
|
||||
d="m 430.90102,443.13766 a 135.67928,18.408747 0 1 1 -271.35855,0 135.67928,18.408747 0 1 1 271.35855,0 z"
|
||||
transform="matrix(0.01302703,-0.3700754,8.066322,-0.0237182,-3322.837,581.39212)" />
|
||||
<path
|
||||
sodipodi:type="arc"
|
||||
style="fill:url(#linearGradient4686);fill-opacity:1;fill-rule:nonzero;stroke:none;display:inline"
|
||||
id="path4684"
|
||||
sodipodi:cx="295.22174"
|
||||
sodipodi:cy="443.13766"
|
||||
sodipodi:rx="135.67928"
|
||||
sodipodi:ry="18.408747"
|
||||
d="m 430.90102,443.13766 a 135.67928,18.408747 0 1 1 -271.35855,0 135.67928,18.408747 0 1 1 271.35855,0 z"
|
||||
transform="matrix(0.01219366,-0.2777519,7.5503003,-0.01780117,-2984.4223,654.88677)" />
|
||||
</g>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer14"
|
||||
inkscape:label="BaseHighlight"
|
||||
style="display:inline">
|
||||
<path
|
||||
style="fill:none;stroke:#0f0626;stroke-width:3.4000001;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:0.9254902;stroke-dasharray:none;marker-mid:none"
|
||||
d="m 113.22547,451.27721 c -6.48144,25.47482 27.77284,36.00307 33.46034,37.48107 75.48368,19.61581 142.61203,18.18957 201.82073,2.0728 19.20235,-5.22693 38.29352,-12.81118 45.54545,-22.87742 8.91645,-12.37672 -0.92272,-23.26137 -0.92272,-23.26137"
|
||||
id="path5997"
|
||||
sodipodi:nodetypes="csssc" />
|
||||
</g>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer10"
|
||||
inkscape:label="BaseShadow_1"
|
||||
style="display:inline">
|
||||
<g
|
||||
id="g7591"
|
||||
transform="translate(0,-2.1213203)">
|
||||
<path
|
||||
transform="matrix(0.01219366,-0.2777519,7.5503003,-0.01780117,-3095.3084,545.23419)"
|
||||
d="m 430.90102,443.13766 a 135.67928,18.408747 0 1 1 -271.35855,0 135.67928,18.408747 0 1 1 271.35855,0 z"
|
||||
sodipodi:ry="18.408747"
|
||||
sodipodi:rx="135.67928"
|
||||
sodipodi:cy="443.13766"
|
||||
sodipodi:cx="295.22174"
|
||||
id="path6716"
|
||||
style="fill:url(#radialGradient7611);fill-opacity:1;fill-rule:nonzero;stroke:none;display:inline"
|
||||
sodipodi:type="arc" />
|
||||
<path
|
||||
sodipodi:nodetypes="cscc"
|
||||
id="path4964"
|
||||
d="m 316.59702,421.17478 c 0,0 58.23544,7.94555 73.90264,26.394 24.79157,29.19259 -118.30811,67.5501 -242.96933,33.90089 213.50314,-1.39087 169.09283,-60.31728 169.09283,-60.31728"
|
||||
style="fill:url(#linearGradient7613);fill-opacity:1;fill-rule:evenodd;stroke:none;display:inline" />
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer5"
|
||||
inkscape:label="Support"
|
||||
style="display:inline">
|
||||
<rect
|
||||
style="fill:#000000;fill-opacity:0.98453603;fill-rule:nonzero;stroke:url(#linearGradient6987);stroke-width:30;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;display:inline"
|
||||
id="rect6686"
|
||||
width="72.335358"
|
||||
height="30.264278"
|
||||
x="361.94818"
|
||||
y="-272.03949"
|
||||
transform="matrix(0,1,-1,0,0,0)" />
|
||||
</g>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer13"
|
||||
inkscape:label="ScreenShade"
|
||||
style="display:inline">
|
||||
<rect
|
||||
style="fill:url(#linearGradient2916);fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="rect5930"
|
||||
width="473.12555"
|
||||
height="321.87091"
|
||||
x="18.64533"
|
||||
y="19.892525"
|
||||
inkscape:export-filename="/home/joelm/Downloads/noVNC_screen.png"
|
||||
inkscape:export-xdpi="121.61401"
|
||||
inkscape:export-ydpi="121.61401" />
|
||||
<path
|
||||
style="fill:url(#linearGradient2913);fill-opacity:1;fill-rule:evenodd;stroke:none;display:inline"
|
||||
d="m 492.23643,341.65103 -222.74382,-0.21858 c 0,0 42.71861,-8.28882 90.56766,-25.15824 28.31396,-9.98224 57.59439,-24.1182 83.13673,-38.1986 45.98594,-25.35007 49.03937,-38.90668 49.03937,-38.90668 l 6e-5,102.4821 z"
|
||||
id="path5331"
|
||||
sodipodi:nodetypes="ccsscc"
|
||||
inkscape:export-filename="/home/joelm/Downloads/noVNC_screen.png"
|
||||
inkscape:export-xdpi="121.61401"
|
||||
inkscape:export-ydpi="121.61401" />
|
||||
</g>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer9"
|
||||
inkscape:label="noVNC_Black"
|
||||
style="display:inline">
|
||||
<path
|
||||
sodipodi:nodetypes="cccc"
|
||||
style="fill:none;stroke:#000000;stroke-width:22.07362747;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline"
|
||||
d="m 416.69139,186.74564 c -77.95059,0 -77.95059,0 -77.95059,0 l 0,73.23957 80.00192,0"
|
||||
id="path5134"
|
||||
inkscape:export-filename="/home/joelm/Downloads/noVNC_screen.png"
|
||||
inkscape:export-xdpi="121.61401"
|
||||
inkscape:export-ydpi="121.61401" />
|
||||
<path
|
||||
sodipodi:nodetypes="cccc"
|
||||
style="fill:none;stroke:#000000;stroke-width:22.07362747;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline"
|
||||
d="m 221.52501,259.96552 0,-74.30876 75.89924,74.67652 0,-73.6827"
|
||||
id="path5132"
|
||||
inkscape:export-filename="/home/joelm/Downloads/noVNC_screen.png"
|
||||
inkscape:export-xdpi="121.61401"
|
||||
inkscape:export-ydpi="121.61401" />
|
||||
<g
|
||||
style="fill:none;stroke:#000000;stroke-width:25.32735443;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline"
|
||||
id="g5128"
|
||||
transform="matrix(1.0637273,0,0,0.7140646,-87.846677,11.859872)"
|
||||
inkscape:export-filename="/home/joelm/Downloads/noVNC_screen.png"
|
||||
inkscape:export-xdpi="121.61401"
|
||||
inkscape:export-ydpi="121.61401">
|
||||
<path
|
||||
sodipodi:nodetypes="cccc"
|
||||
id="path5130"
|
||||
d="m 181.27307,243.94727 0,42.42561 69.52825,61.54333 0,-103.59063"
|
||||
style="fill:none;stroke:#000000;stroke-width:25.32735443;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
</g>
|
||||
<path
|
||||
sodipodi:nodetypes="cccc"
|
||||
style="fill:none;stroke:#000000;stroke-width:21.4314537;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline"
|
||||
d="m 106.33831,159.40077 0,-72.146954 73.69116,72.504004 0,-71.539108"
|
||||
id="path5136"
|
||||
inkscape:export-filename="/home/joelm/Downloads/noVNC_screen.png"
|
||||
inkscape:export-xdpi="121.61401"
|
||||
inkscape:export-ydpi="121.61401" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:21.4314537;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline"
|
||||
d="m 219.23536,92.259872 0,71.123858 77.68903,0 -2.0092,-71.123858 c -75.6828,0 -75.67983,0 -75.67983,0 z"
|
||||
id="path5138"
|
||||
inkscape:export-filename="/home/joelm/Downloads/noVNC_screen.png"
|
||||
inkscape:export-xdpi="121.61401"
|
||||
inkscape:export-ydpi="121.61401" />
|
||||
</g>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer4"
|
||||
inkscape:label="noVNC"
|
||||
style="display:inline">
|
||||
<g
|
||||
style="fill:none;display:inline"
|
||||
id="g5142"
|
||||
transform="matrix(1.0637273,0,0,0.7140646,-87.924897,11.633929)"
|
||||
inkscape:export-filename="/home/joelm/Downloads/noVNC_favicon.png"
|
||||
inkscape:export-xdpi="4.1999998"
|
||||
inkscape:export-ydpi="4.1999998">
|
||||
<path
|
||||
sodipodi:nodetypes="cccc"
|
||||
id="path5144"
|
||||
d="m 181.27307,243.94727 0,42.42561 69.52825,61.54333 0,-103.59063"
|
||||
style="fill:none;stroke:#f2eb41;stroke-width:19.83890915;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
</g>
|
||||
<path
|
||||
sodipodi:nodetypes="cccc"
|
||||
style="fill:none;stroke:#f2eb41;stroke-width:17.29026794;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline"
|
||||
d="m 221.44679,259.73958 0,-74.30877 75.89925,74.67651 0,-73.68269"
|
||||
id="path5146"
|
||||
inkscape:export-filename="/home/joelm/Downloads/noVNC_favicon.png"
|
||||
inkscape:export-xdpi="4.1999998"
|
||||
inkscape:export-ydpi="4.1999998" />
|
||||
<path
|
||||
sodipodi:nodetypes="cccc"
|
||||
style="fill:none;stroke:#f2eb41;stroke-width:17.29026794;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline"
|
||||
d="m 416.61319,186.51969 c -77.95061,0 -77.95061,0 -77.95061,0 l 0,73.23958 80.00193,0"
|
||||
id="path5148"
|
||||
inkscape:export-filename="/home/joelm/Downloads/noVNC_favicon.png"
|
||||
inkscape:export-xdpi="4.1999998"
|
||||
inkscape:export-ydpi="4.1999998" />
|
||||
<path
|
||||
sodipodi:nodetypes="cccc"
|
||||
style="fill:none;stroke:#00df00;stroke-width:16.78725243;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline"
|
||||
d="m 106.26238,159.1814 0,-72.146955 73.69115,72.503995 0,-71.539084"
|
||||
id="path5150"
|
||||
inkscape:export-filename="/home/joelm/Downloads/noVNC_favicon.png"
|
||||
inkscape:export-xdpi="4.1999998"
|
||||
inkscape:export-ydpi="4.1999998" />
|
||||
<path
|
||||
style="fill:none;stroke:#00df00;stroke-width:16.78725243;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline"
|
||||
d="m 219.15941,92.040508 0,71.123862 77.68904,0 -2.00919,-71.123862 c -75.68283,0 -75.67985,0 -75.67985,0 z"
|
||||
id="path5152"
|
||||
inkscape:export-filename="/home/joelm/Downloads/noVNC_favicon.png"
|
||||
inkscape:export-xdpi="4.1999998"
|
||||
inkscape:export-ydpi="4.1999998" />
|
||||
</g>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer7"
|
||||
inkscape:label="noVNC_Save"
|
||||
style="display:none">
|
||||
<g
|
||||
style="display:inline"
|
||||
id="g3903"
|
||||
transform="matrix(0.9560363,0,0,0.9560363,-6.710991,-337.27245)">
|
||||
<g
|
||||
transform="matrix(1.0081199,0,0,1.0081199,-11.487266,-121.24134)"
|
||||
id="g3905"
|
||||
style="fill:none">
|
||||
<path
|
||||
style="fill:none;stroke:#00ff00;stroke-width:19.83890915;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
|
||||
d="m 83.455645,199.50003 101.674295,0"
|
||||
id="path3907"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="fill:none;stroke:#00ff00;stroke-width:19.83890915;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
|
||||
d="m 81.687109,243.94727 99.585961,0 0,42.42561 79.06591,61.54333 0,-103.59063"
|
||||
id="path3909"
|
||||
sodipodi:nodetypes="ccccc" />
|
||||
<path
|
||||
style="fill:none;stroke:#00ff00;stroke-width:19.83890915;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
|
||||
d="M 87.323416,283.46826 175.10772,160.81511"
|
||||
id="path3911"
|
||||
sodipodi:nodetypes="cc" />
|
||||
</g>
|
||||
<path
|
||||
id="path3913"
|
||||
d="m 289.84756,229.03542 0,-104.90947 71.93153,105.42866 0,-104.02559"
|
||||
style="fill:none;stroke:#00ff00;stroke-width:20;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
<path
|
||||
id="path3915"
|
||||
d="m 472.59255,125.66322 c -73.87564,0 -73.87564,0 -73.87564,0 l 0,103.4 75.81973,0"
|
||||
style="fill:none;stroke:#00ff00;stroke-width:20;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer3"
|
||||
inkscape:label="Bezel"
|
||||
style="display:inline">
|
||||
<rect
|
||||
style="fill:none;stroke:#040404;stroke-width:24.66699982;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;display:inline"
|
||||
id="rect6660"
|
||||
width="485.7326"
|
||||
height="337.33023"
|
||||
x="14.179501"
|
||||
y="14.179514" />
|
||||
</g>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer8"
|
||||
inkscape:label="BezelHighlight"
|
||||
style="display:inline">
|
||||
<rect
|
||||
style="fill:none;stroke:#100626;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:0.89803922;stroke-dasharray:none;stroke-dashoffset:0;display:inline"
|
||||
id="rect3888"
|
||||
width="485.7326"
|
||||
height="337.33023"
|
||||
x="13.819346"
|
||||
y="14.179423" />
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 25 KiB |
BIN
images/alt.png
Normal file
|
After Width: | Height: | Size: 339 B |
BIN
images/clipboard.png
Normal file
|
After Width: | Height: | Size: 501 B |
BIN
images/connect.png
Normal file
|
After Width: | Height: | Size: 404 B |
BIN
images/ctrl.png
Normal file
|
After Width: | Height: | Size: 354 B |
BIN
images/ctrlaltdel.png
Normal file
|
After Width: | Height: | Size: 317 B |
BIN
images/disconnect.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
images/drag.png
Normal file
|
After Width: | Height: | Size: 963 B |
BIN
images/esc.png
Normal file
|
After Width: | Height: | Size: 385 B |
|
Before Width: | Height: | Size: 23 KiB |
BIN
images/keyboard.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
images/mouse_left.png
Normal file
|
After Width: | Height: | Size: 511 B |
BIN
images/mouse_middle.png
Normal file
|
After Width: | Height: | Size: 517 B |
BIN
images/mouse_none.png
Normal file
|
After Width: | Height: | Size: 497 B |
BIN
images/mouse_right.png
Normal file
|
After Width: | Height: | Size: 513 B |
BIN
images/power.png
Normal file
|
After Width: | Height: | Size: 390 B |
BIN
images/screen_320x460.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
images/screen_57x57.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 20 KiB |
BIN
images/screen_700x700.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
images/settings.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
images/showextrakeys.png
Normal file
|
After Width: | Height: | Size: 735 B |
BIN
images/tab.png
Normal file
|
After Width: | Height: | Size: 387 B |
BIN
include/Orbitron700.ttf
Normal file
BIN
include/Orbitron700.woff
Normal file
512
include/base.css
Normal file
@@ -0,0 +1,512 @@
|
||||
/*
|
||||
* noVNC base CSS
|
||||
* Copyright (C) 2012 Joel Martin
|
||||
* Copyright (C) 2013 Samuel Mannehed for Cendio AB
|
||||
* noVNC is licensed under the MPL 2.0 (see LICENSE.txt)
|
||||
* This file is licensed under the 2-Clause BSD license (see LICENSE.txt).
|
||||
*/
|
||||
|
||||
body {
|
||||
margin:0;
|
||||
padding:0;
|
||||
font-family: Helvetica;
|
||||
/*Background image with light grey curve.*/
|
||||
background-color:#494949;
|
||||
background-repeat:no-repeat;
|
||||
background-position:right bottom;
|
||||
height:100%;
|
||||
}
|
||||
|
||||
html {
|
||||
height:100%;
|
||||
}
|
||||
|
||||
#noVNC_controls ul {
|
||||
list-style: none;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
#noVNC_controls li {
|
||||
padding-bottom:8px;
|
||||
}
|
||||
|
||||
#noVNC_host {
|
||||
width:150px;
|
||||
}
|
||||
#noVNC_port {
|
||||
width: 80px;
|
||||
}
|
||||
#noVNC_password {
|
||||
width: 150px;
|
||||
}
|
||||
#noVNC_encrypt {
|
||||
}
|
||||
#noVNC_path {
|
||||
width: 100px;
|
||||
}
|
||||
#noVNC_connect_button {
|
||||
width: 110px;
|
||||
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;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.noVNC-buttons-right {
|
||||
float:right;
|
||||
right: 0px;
|
||||
z-index: 2;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
#noVNC_status {
|
||||
font-size: 12px;
|
||||
padding-top: 4px;
|
||||
height:32px;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#noVNC_settings_menu {
|
||||
margin: 3px;
|
||||
text-align: left;
|
||||
}
|
||||
#noVNC_settings_menu ul {
|
||||
list-style: none;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
#noVNC_apply {
|
||||
float:right;
|
||||
}
|
||||
|
||||
/* Do not set width/height for VNC_screen or VNC_canvas or incorrect
|
||||
* scaling will occur. Canvas resizes to remote VNC settings */
|
||||
#noVNC_screen_pad {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
height: 36px;
|
||||
}
|
||||
#noVNC_screen {
|
||||
text-align: center;
|
||||
display: table;
|
||||
width:100%;
|
||||
height:100%;
|
||||
background-color:#313131;
|
||||
border-bottom-right-radius: 800px 600px;
|
||||
/*border-top-left-radius: 800px 600px;*/
|
||||
}
|
||||
|
||||
#noVNC_container, #noVNC_canvas {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
#noVNC_canvas {
|
||||
left: 0px;
|
||||
}
|
||||
|
||||
#VNC_clipboard_clear_button {
|
||||
float:right;
|
||||
}
|
||||
#VNC_clipboard_text {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
#noVNC_clipboard_clear_button {
|
||||
float:right;
|
||||
}
|
||||
|
||||
/*Bubble contents divs*/
|
||||
#noVNC_settings {
|
||||
display:none;
|
||||
margin-top:73px;
|
||||
right:20px;
|
||||
position:fixed;
|
||||
}
|
||||
|
||||
#noVNC_controls {
|
||||
display:none;
|
||||
margin-top:73px;
|
||||
right:12px;
|
||||
position:fixed;
|
||||
}
|
||||
#noVNC_controls.top:after {
|
||||
right:15px;
|
||||
}
|
||||
|
||||
#noVNC_description {
|
||||
display:none;
|
||||
position:fixed;
|
||||
|
||||
margin-top:73px;
|
||||
right:20px;
|
||||
left:20px;
|
||||
padding:15px;
|
||||
color:#000;
|
||||
background:#eee; /* default background for browsers without gradient support */
|
||||
|
||||
border:2px solid #E0E0E0;
|
||||
-webkit-border-radius:10px;
|
||||
-moz-border-radius:10px;
|
||||
border-radius:10px;
|
||||
}
|
||||
|
||||
#noVNC_popup_status_panel {
|
||||
display:none;
|
||||
position: fixed;
|
||||
z-index: 1;
|
||||
|
||||
margin:15px;
|
||||
margin-top:60px;
|
||||
padding:15px;
|
||||
width:auto;
|
||||
|
||||
text-align:center;
|
||||
font-weight:bold;
|
||||
word-wrap:break-word;
|
||||
color:#fff;
|
||||
background:rgba(0,0,0,0.65);
|
||||
|
||||
-webkit-border-radius:10px;
|
||||
-moz-border-radius:10px;
|
||||
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;
|
||||
right:30px;
|
||||
position:fixed;
|
||||
}
|
||||
#noVNC_clipboard.top:after {
|
||||
right:85px;
|
||||
}
|
||||
|
||||
#keyboardinput {
|
||||
width:1px;
|
||||
height:1px;
|
||||
background-color:#fff;
|
||||
color:#fff;
|
||||
border:0;
|
||||
position: relative;
|
||||
left: -40px;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Advanced Styling
|
||||
*/
|
||||
|
||||
.noVNC_status_normal {
|
||||
background: #b2bdcd; /* Old browsers */
|
||||
background: -moz-linear-gradient(top, #b2bdcd 0%, #899cb3 49%, #7e93af 51%, #6e84a3 100%); /* FF3.6+ */
|
||||
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#b2bdcd), color-stop(49%,#899cb3), color-stop(51%,#7e93af), color-stop(100%,#6e84a3)); /* Chrome,Safari4+ */
|
||||
background: -webkit-linear-gradient(top, #b2bdcd 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* Chrome10+,Safari5.1+ */
|
||||
background: -o-linear-gradient(top, #b2bdcd 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* Opera11.10+ */
|
||||
background: -ms-linear-gradient(top, #b2bdcd 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* IE10+ */
|
||||
background: linear-gradient(top, #b2bdcd 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* W3C */
|
||||
}
|
||||
.noVNC_status_error {
|
||||
background: #f04040; /* Old browsers */
|
||||
background: -moz-linear-gradient(top, #f04040 0%, #899cb3 49%, #7e93af 51%, #6e84a3 100%); /* FF3.6+ */
|
||||
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#f04040), color-stop(49%,#899cb3), color-stop(51%,#7e93af), color-stop(100%,#6e84a3)); /* Chrome,Safari4+ */
|
||||
background: -webkit-linear-gradient(top, #f04040 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* Chrome10+,Safari5.1+ */
|
||||
background: -o-linear-gradient(top, #f04040 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* Opera11.10+ */
|
||||
background: -ms-linear-gradient(top, #f04040 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* IE10+ */
|
||||
background: linear-gradient(top, #f04040 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* W3C */
|
||||
}
|
||||
.noVNC_status_warn {
|
||||
background: #f0f040; /* Old browsers */
|
||||
background: -moz-linear-gradient(top, #f0f040 0%, #899cb3 49%, #7e93af 51%, #6e84a3 100%); /* FF3.6+ */
|
||||
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#f0f040), color-stop(49%,#899cb3), color-stop(51%,#7e93af), color-stop(100%,#6e84a3)); /* Chrome,Safari4+ */
|
||||
background: -webkit-linear-gradient(top, #f0f040 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* Chrome10+,Safari5.1+ */
|
||||
background: -o-linear-gradient(top, #f0f040 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* Opera11.10+ */
|
||||
background: -ms-linear-gradient(top, #f0f040 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* IE10+ */
|
||||
background: linear-gradient(top, #f0f040 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* W3C */
|
||||
}
|
||||
|
||||
/* Control bar */
|
||||
#noVNC-control-bar {
|
||||
position:fixed;
|
||||
|
||||
display:block;
|
||||
height:36px;
|
||||
left:0;
|
||||
top:0;
|
||||
width:100%;
|
||||
z-index:200;
|
||||
}
|
||||
|
||||
.noVNC_status_button {
|
||||
padding: 4px 4px;
|
||||
vertical-align: middle;
|
||||
border:1px solid #869dbc;
|
||||
-webkit-border-radius: 6px;
|
||||
-moz-border-radius: 6px;
|
||||
border-radius: 6px;
|
||||
background: #b2bdcd; /* Old browsers */
|
||||
background: -moz-linear-gradient(top, #b2bdcd 0%, #899cb3 49%, #7e93af 51%, #6e84a3 100%); /* FF3.6+ */
|
||||
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#b2bdcd), color-stop(49%,#899cb3), color-stop(51%,#7e93af), color-stop(100%,#6e84a3)); /* Chrome,Safari4+ */
|
||||
background: -webkit-linear-gradient(top, #b2bdcd 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* Chrome10+,Safari5.1+ */
|
||||
background: -o-linear-gradient(top, #b2bdcd 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* Opera11.10+ */
|
||||
background: -ms-linear-gradient(top, #b2bdcd 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* IE10+ */
|
||||
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#b2bdcd', endColorstr='#6e84a3',GradientType=0 ); /* IE6-9 */
|
||||
background: linear-gradient(top, #b2bdcd 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* W3C */
|
||||
/*box-shadow:inset 0.4px 0.4px 0.4px #000000;*/
|
||||
}
|
||||
|
||||
.noVNC_status_button_selected {
|
||||
padding: 4px 4px;
|
||||
vertical-align: middle;
|
||||
border:1px solid #4366a9;
|
||||
-webkit-border-radius: 6px;
|
||||
-moz-border-radius: 6px;
|
||||
background: #779ced; /* Old browsers */
|
||||
background: -moz-linear-gradient(top, #779ced 0%, #3970e0 49%, #2160dd 51%, #2463df 100%); /* FF3.6+ */
|
||||
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#779ced), color-stop(49%,#3970e0), color-stop(51%,#2160dd), color-stop(100%,#2463df)); /* Chrome,Safari4+ */
|
||||
background: -webkit-linear-gradient(top, #779ced 0%,#3970e0 49%,#2160dd 51%,#2463df 100%); /* Chrome10+,Safari5.1+ */
|
||||
background: -o-linear-gradient(top, #779ced 0%,#3970e0 49%,#2160dd 51%,#2463df 100%); /* Opera11.10+ */
|
||||
background: -ms-linear-gradient(top, #779ced 0%,#3970e0 49%,#2160dd 51%,#2463df 100%); /* IE10+ */
|
||||
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#779ced', endColorstr='#2463df',GradientType=0 ); /* IE6-9 */
|
||||
background: linear-gradient(top, #779ced 0%,#3970e0 49%,#2160dd 51%,#2463df 100%); /* W3C */
|
||||
/*box-shadow:inset 0.4px 0.4px 0.4px #000000;*/
|
||||
}
|
||||
|
||||
|
||||
/*Settings Bubble*/
|
||||
.triangle-right {
|
||||
position:relative;
|
||||
padding:15px;
|
||||
margin:1em 0 3em;
|
||||
color:#fff;
|
||||
background:#fff; /* default background for browsers without gradient support */
|
||||
/* css3 */
|
||||
/*background:-webkit-gradient(linear, 0 0, 0 100%, from(#2e88c4), to(#075698));
|
||||
background:-moz-linear-gradient(#2e88c4, #075698);
|
||||
background:-o-linear-gradient(#2e88c4, #075698);
|
||||
background:linear-gradient(#2e88c4, #075698);*/
|
||||
-webkit-border-radius:10px;
|
||||
-moz-border-radius:10px;
|
||||
border-radius:10px;
|
||||
color:#000;
|
||||
border:2px solid #E0E0E0;
|
||||
}
|
||||
|
||||
.triangle-right.top:after {
|
||||
border-color: transparent #E0E0E0;
|
||||
border-width: 20px 20px 0 0;
|
||||
bottom: auto;
|
||||
left: auto;
|
||||
right: 50px;
|
||||
top: -20px;
|
||||
}
|
||||
|
||||
.triangle-right:after {
|
||||
content:"";
|
||||
position:absolute;
|
||||
bottom:-20px; /* value = - border-top-width - border-bottom-width */
|
||||
left:50px; /* controls horizontal position */
|
||||
border-width:20px 0 0 20px; /* vary these values to change the angle of the vertex */
|
||||
border-style:solid;
|
||||
border-color:#E0E0E0 transparent;
|
||||
/* reduce the damage in FF3.0 */
|
||||
display:block;
|
||||
width:0;
|
||||
}
|
||||
|
||||
.triangle-right.top:after {
|
||||
top:-40px; /* value = - border-top-width - border-bottom-width */
|
||||
right:50px; /* controls horizontal position */
|
||||
bottom:auto;
|
||||
left:auto;
|
||||
border-width:40px 40px 0 0; /* vary these values to change the angle of the vertex */
|
||||
border-color:transparent #E0E0E0;
|
||||
}
|
||||
|
||||
/*Default noVNC logo.*/
|
||||
/* From: http://fonts.googleapis.com/css?family=Orbitron:700 */
|
||||
@font-face {
|
||||
font-family: 'Orbitron';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: local('?'), url('Orbitron700.woff') format('woff'),
|
||||
url('Orbitron700.ttf') format('truetype');
|
||||
}
|
||||
|
||||
#noVNC_logo {
|
||||
margin-top: 170px;
|
||||
margin-left: 10px;
|
||||
color:yellow;
|
||||
text-align:left;
|
||||
font-family: 'Orbitron', 'OrbitronTTF', sans-serif;
|
||||
line-height:90%;
|
||||
text-shadow:
|
||||
5px 5px 0 #000,
|
||||
-1px -1px 0 #000,
|
||||
1px -1px 0 #000,
|
||||
-1px 1px 0 #000,
|
||||
1px 1px 0 #000;
|
||||
}
|
||||
|
||||
|
||||
#noVNC_logo span{
|
||||
color:green;
|
||||
}
|
||||
|
||||
/* ----------------------------------------
|
||||
* Media sizing
|
||||
* ----------------------------------------
|
||||
*/
|
||||
|
||||
|
||||
.noVNC_status_button {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
#noVNC_clipboard_text {
|
||||
width: 500px;
|
||||
}
|
||||
|
||||
#noVNC_logo {
|
||||
font-size: 180px;
|
||||
}
|
||||
|
||||
.noVNC-buttons-left {
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.noVNC-buttons-right {
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
#noVNC_status {
|
||||
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;
|
||||
}
|
||||
.noVNC-buttons-left {
|
||||
padding-left: 0px;
|
||||
}
|
||||
.noVNC-buttons-right {
|
||||
padding-right: 0px;
|
||||
}
|
||||
/* collapse the extra keys on lower resolutions */
|
||||
#showExtraKeysButton {
|
||||
display: inline;
|
||||
}
|
||||
#toggleCtrlButton {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 30px;
|
||||
left: 0px;
|
||||
}
|
||||
#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;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 321px) and (max-width: 480px) {
|
||||
#noVNC_clipboard_text {
|
||||
width: 250px;
|
||||
}
|
||||
#noVNC_logo {
|
||||
font-size: 110px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 320px) {
|
||||
.noVNC_status_button {
|
||||
font-size: 9px;
|
||||
}
|
||||
#noVNC_clipboard_text {
|
||||
width: 220px;
|
||||
}
|
||||
#noVNC_logo {
|
||||
font-size: 90px;
|
||||
}
|
||||
}
|
||||
@@ -1,147 +1,113 @@
|
||||
/*
|
||||
* Modified from:
|
||||
* http://lxr.mozilla.org/mozilla/source/extensions/xml-rpc/src/nsXmlRpcClient.js#956
|
||||
*/
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is Mozilla XML-RPC Client component.
|
||||
*
|
||||
* The Initial Developer of the Original Code is
|
||||
* Digital Creations 2, Inc.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2000
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Martijn Pieters <mj@digicool.com> (original author)
|
||||
* Samuel Sieb <samuel@sieb.net>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
// 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+/',
|
||||
base64Pad : '=',
|
||||
encode: function (data) {
|
||||
"use strict";
|
||||
var result = '';
|
||||
var toBase64Table = Base64.toBase64Table;
|
||||
var length = data.length;
|
||||
var lengthpad = (length % 3);
|
||||
// Convert every three bytes to 4 ascii characters.
|
||||
|
||||
encode: function (data) {
|
||||
"use strict";
|
||||
var result = '',
|
||||
chrTable = Base64.toBase64Table.split(''),
|
||||
pad = Base64.base64Pad,
|
||||
length = data.length,
|
||||
i;
|
||||
// Convert every three bytes to 4 ascii characters.
|
||||
for (i = 0; i < (length - 2); i += 3) {
|
||||
result += chrTable[data[i] >> 2];
|
||||
result += chrTable[((data[i] & 0x03) << 4) + (data[i+1] >> 4)];
|
||||
result += chrTable[((data[i+1] & 0x0f) << 2) + (data[i+2] >> 6)];
|
||||
result += chrTable[data[i+2] & 0x3f];
|
||||
}
|
||||
|
||||
// Convert the remaining 1 or 2 bytes, pad out to 4 characters.
|
||||
if (length%3) {
|
||||
i = length - (length%3);
|
||||
result += chrTable[data[i] >> 2];
|
||||
if ((length%3) === 2) {
|
||||
result += chrTable[((data[i] & 0x03) << 4) + (data[i+1] >> 4)];
|
||||
result += chrTable[(data[i+1] & 0x0f) << 2];
|
||||
result += pad;
|
||||
} else {
|
||||
result += chrTable[(data[i] & 0x03) << 4];
|
||||
result += pad + pad;
|
||||
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];
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
/* Convert Base64 data to a string */
|
||||
toBinaryTable : [
|
||||
-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
|
||||
-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
|
||||
-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,62, -1,-1,-1,63,
|
||||
52,53,54,55, 56,57,58,59, 60,61,-1,-1, -1, 0,-1,-1,
|
||||
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14,
|
||||
15,16,17,18, 19,20,21,22, 23,24,25,-1, -1,-1,-1,-1,
|
||||
-1,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40,
|
||||
41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1
|
||||
],
|
||||
|
||||
decode: function (data, offset) {
|
||||
"use strict";
|
||||
offset = typeof(offset) !== 'undefined' ? offset : 0;
|
||||
var binTable = Base64.toBinaryTable,
|
||||
pad = Base64.base64Pad,
|
||||
result, result_length, idx, i, c, padding,
|
||||
leftbits = 0, // number of bits decoded, but yet to be appended
|
||||
leftdata = 0, // bits decoded, but yet to be appended
|
||||
data_length = data.indexOf('=') - offset;
|
||||
|
||||
if (data_length < 0) { data_length = data.length - offset; }
|
||||
|
||||
/* Every four characters is 3 resulting numbers */
|
||||
result_length = (data_length >> 2) * 3 + Math.floor((data_length%4)/1.5);
|
||||
result = new Array(result_length);
|
||||
|
||||
// Convert one by one.
|
||||
for (idx = 0, i = offset; i < data.length; i++) {
|
||||
c = binTable[data.charCodeAt(i) & 0x7f];
|
||||
padding = (data.charAt(i) === pad);
|
||||
// Skip illegal characters and whitespace
|
||||
if (c === -1) {
|
||||
console.error("Illegal character '" + data.charCodeAt(i) + "'");
|
||||
continue;
|
||||
// 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[64];
|
||||
} else if (lengthpad === 1) {
|
||||
j = length - lengthpad;
|
||||
result += toBase64Table[data[j] >> 2];
|
||||
result += toBase64Table[(data[j] & 0x03) << 4];
|
||||
result += toBase64Table[64];
|
||||
result += toBase64Table[64];
|
||||
}
|
||||
|
||||
// Collect data into leftdata, update bitcount
|
||||
leftdata = (leftdata << 6) | c;
|
||||
leftbits += 6;
|
||||
|
||||
// If we have 8 or more bits, append 8 bits to the result
|
||||
if (leftbits >= 8) {
|
||||
leftbits -= 8;
|
||||
// Append if not padding.
|
||||
if (!padding) {
|
||||
result[idx++] = (leftdata >> leftbits) & 0xff;
|
||||
return result;
|
||||
},
|
||||
|
||||
/* 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,
|
||||
52,53,54,55, 56,57,58,59, 60,61,-1,-1, -1, 0,-1,-1,
|
||||
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14,
|
||||
15,16,17,18, 19,20,21,22, 23,24,25,-1, -1,-1,-1,-1,
|
||||
-1,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40,
|
||||
41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1
|
||||
],
|
||||
/* jshint +W013 */
|
||||
|
||||
decode: function (data, offset) {
|
||||
"use strict";
|
||||
offset = typeof(offset) !== 'undefined' ? offset : 0;
|
||||
var toBinaryTable = Base64.toBinaryTable;
|
||||
var base64Pad = Base64.base64Pad;
|
||||
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;
|
||||
|
||||
if (data_length < 0) { data_length = data.length - offset; }
|
||||
|
||||
/* Every four characters is 3 resulting numbers */
|
||||
result_length = (data_length >> 2) * 3 + Math.floor((data_length % 4) / 1.5);
|
||||
result = new Array(result_length);
|
||||
|
||||
// Convert one by one.
|
||||
for (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);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Collect data into leftdata, update bitcount
|
||||
leftdata = (leftdata << 6) | c;
|
||||
leftbits += 6;
|
||||
|
||||
// If we have 8 or more bits, append 8 bits to the result
|
||||
if (leftbits >= 8) {
|
||||
leftbits -= 8;
|
||||
// Append if not padding.
|
||||
if (!padding) {
|
||||
result[idx++] = (leftdata >> leftbits) & 0xff;
|
||||
}
|
||||
leftdata &= (1 << leftbits) - 1;
|
||||
}
|
||||
leftdata &= (1 << leftbits) - 1;
|
||||
}
|
||||
|
||||
// If there are any bits left, the base64 string was corrupted
|
||||
if (leftbits) {
|
||||
err = new Error('Corrupted base64 string');
|
||||
err.name = 'Base64-Error';
|
||||
throw err;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// If there are any bits left, the base64 string was corrupted
|
||||
if (leftbits) {
|
||||
throw {name: 'Base64-Error',
|
||||
message: 'Corrupted base64 string'};
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}; /* End of Base64 namespace */
|
||||
|
||||
@@ -1,124 +1,71 @@
|
||||
body {
|
||||
margin: 0px;
|
||||
font-size: 13px;
|
||||
color: #111;
|
||||
font-family: "Helvetica";
|
||||
/*
|
||||
* noVNC black CSS
|
||||
* Copyright (C) 2012 Joel Martin
|
||||
* Copyright (C) 2013 Samuel Mannehed for Cendio AB
|
||||
* noVNC is licensed under the MPL 2.0 (see LICENSE.txt)
|
||||
* This file is licensed under the 2-Clause BSD license (see LICENSE.txt).
|
||||
*/
|
||||
|
||||
#keyboardinput {
|
||||
background-color:#000;
|
||||
}
|
||||
|
||||
#VNC_controls {
|
||||
background: #111;
|
||||
line-height: 1em;
|
||||
color: #FFF;
|
||||
overflow: hidden;
|
||||
padding: 4px 24px;
|
||||
.noVNC_status_normal {
|
||||
background: #4c4c4c; /* Old browsers */
|
||||
background: -moz-linear-gradient(top, #4c4c4c 0%, #2c2c2c 50%, #000000 51%, #131313 100%); /* FF3.6+ */
|
||||
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#4c4c4c), color-stop(50%,#2c2c2c), color-stop(51%,#000000), color-stop(100%,#131313)); /* Chrome,Safari4+ */
|
||||
background: -webkit-linear-gradient(top, #4c4c4c 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* Chrome10+,Safari5.1+ */
|
||||
background: -o-linear-gradient(top, #4c4c4c 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* Opera11.10+ */
|
||||
background: -ms-linear-gradient(top, #4c4c4c 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* IE10+ */
|
||||
background: linear-gradient(top, #4c4c4c 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* W3C */
|
||||
}
|
||||
.noVNC_status_error {
|
||||
background: #f04040; /* Old browsers */
|
||||
background: -moz-linear-gradient(top, #f04040 0%, #2c2c2c 50%, #000000 51%, #131313 100%); /* FF3.6+ */
|
||||
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#f04040), color-stop(50%,#2c2c2c), color-stop(51%,#000000), color-stop(100%,#131313)); /* Chrome,Safari4+ */
|
||||
background: -webkit-linear-gradient(top, #f04040 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* Chrome10+,Safari5.1+ */
|
||||
background: -o-linear-gradient(top, #f04040 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* Opera11.10+ */
|
||||
background: -ms-linear-gradient(top, #f04040 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* IE10+ */
|
||||
background: linear-gradient(top, #f04040 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* W3C */
|
||||
}
|
||||
.noVNC_status_warn {
|
||||
background: #f0f040; /* Old browsers */
|
||||
background: -moz-linear-gradient(top, #f0f040 0%, #2c2c2c 50%, #000000 51%, #131313 100%); /* FF3.6+ */
|
||||
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#f0f040), color-stop(50%,#2c2c2c), color-stop(51%,#000000), color-stop(100%,#131313)); /* Chrome,Safari4+ */
|
||||
background: -webkit-linear-gradient(top, #f0f040 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* Chrome10+,Safari5.1+ */
|
||||
background: -o-linear-gradient(top, #f0f040 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* Opera11.10+ */
|
||||
background: -ms-linear-gradient(top, #f0f040 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* IE10+ */
|
||||
background: linear-gradient(top, #f0f040 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* W3C */
|
||||
}
|
||||
|
||||
#VNC_controls ul {
|
||||
list-style:none;
|
||||
list-style-position: outside;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
#VNC_controls li {
|
||||
margin-right: 15px;
|
||||
padding: 2px 0px;
|
||||
float: left;
|
||||
}
|
||||
#VNC_controls li input[type=text],
|
||||
#VNC_controls li input[type=password] {
|
||||
border: 2px solid #333;
|
||||
.triangle-right {
|
||||
border:2px solid #fff;
|
||||
background:#000;
|
||||
color:#fff;
|
||||
}
|
||||
|
||||
#VNC_host {
|
||||
width: 100px;
|
||||
}
|
||||
#VNC_port {
|
||||
width: 50px;
|
||||
}
|
||||
#VNC_password {
|
||||
width: 80px;
|
||||
}
|
||||
#VNC_encrypt {
|
||||
}
|
||||
#VNC_connect_button {
|
||||
width: 100px;
|
||||
.noVNC_status_button {
|
||||
font-size: 12px;
|
||||
vertical-align: middle;
|
||||
border:1px solid #4c4c4c;
|
||||
|
||||
background: #4c4c4c; /* Old browsers */
|
||||
background: -moz-linear-gradient(top, #4c4c4c 0%, #2c2c2c 50%, #000000 51%, #131313 100%); /* FF3.6+ */
|
||||
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#4c4c4c), color-stop(50%,#2c2c2c), color-stop(51%,#000000), color-stop(100%,#131313)); /* Chrome,Safari4+ */
|
||||
background: -webkit-linear-gradient(top, #4c4c4c 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* Chrome10+,Safari5.1+ */
|
||||
background: -o-linear-gradient(top, #4c4c4c 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* Opera11.10+ */
|
||||
background: -ms-linear-gradient(top, #4c4c4c 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* IE10+ */
|
||||
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#4c4c4c', endColorstr='#131313',GradientType=0 ); /* IE6-9 */
|
||||
background: linear-gradient(top, #4c4c4c 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* W3C */
|
||||
}
|
||||
|
||||
#VNC_status_bar td {
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
}
|
||||
#VNC_status_bar div {
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
margin: 0px;
|
||||
padding: 1em;
|
||||
}
|
||||
.VNC_status_button {
|
||||
font-size: 10px;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
#VNC_status {
|
||||
text-align: center;
|
||||
}
|
||||
#VNC_settings_menu {
|
||||
display: none;
|
||||
position: absolute;
|
||||
width: 13em;
|
||||
border: 1px solid #888;
|
||||
color: #111;
|
||||
font-weight: normal;
|
||||
background-color: #f0f2f6;
|
||||
padding: 5px; margin: 3px;
|
||||
z-index: 100; opacity: 1;
|
||||
text-align: left; white-space: normal;
|
||||
}
|
||||
#VNC_settings_menu ul {
|
||||
list-style: none;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.VNC_buttons_right {
|
||||
text-align: right;
|
||||
}
|
||||
.VNC_buttons_left {
|
||||
text-align: left;
|
||||
}
|
||||
.VNC_status_normal {
|
||||
background: #111;
|
||||
color: #fff;
|
||||
}
|
||||
.VNC_status_error {
|
||||
background: #111;
|
||||
color: #f44;
|
||||
}
|
||||
.VNC_status_warn {
|
||||
background: #111;
|
||||
color: #ff4;
|
||||
}
|
||||
|
||||
#VNC_screen {
|
||||
-webkit-border-radius: 10px;
|
||||
-moz-border-radius: 10px;
|
||||
border-radius: 10px;
|
||||
background: #111;
|
||||
padding: 20px;
|
||||
margin: 0 auto;
|
||||
color: #FFF;
|
||||
margin-top: 20px;
|
||||
text-align: center;
|
||||
|
||||
/* This causes the width of the outer div(#screen) honor the size of the inner (#vnc) div */
|
||||
display: table;
|
||||
table-layout: auto;
|
||||
}
|
||||
#VNC_canvas {
|
||||
background: #111;
|
||||
margin: 0 auto;
|
||||
}
|
||||
#VNC_clipboard {
|
||||
display: none;
|
||||
.noVNC_status_button_selected {
|
||||
background: #9dd53a; /* Old browsers */
|
||||
background: -moz-linear-gradient(top, #9dd53a 0%, #a1d54f 50%, #80c217 51%, #7cbc0a 100%); /* FF3.6+ */
|
||||
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#9dd53a), color-stop(50%,#a1d54f), color-stop(51%,#80c217), color-stop(100%,#7cbc0a)); /* Chrome,Safari4+ */
|
||||
background: -webkit-linear-gradient(top, #9dd53a 0%,#a1d54f 50%,#80c217 51%,#7cbc0a 100%); /* Chrome10+,Safari5.1+ */
|
||||
background: -o-linear-gradient(top, #9dd53a 0%,#a1d54f 50%,#80c217 51%,#7cbc0a 100%); /* Opera11.10+ */
|
||||
background: -ms-linear-gradient(top, #9dd53a 0%,#a1d54f 50%,#80c217 51%,#7cbc0a 100%); /* IE10+ */
|
||||
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#9dd53a', endColorstr='#7cbc0a',GradientType=0 ); /* IE6-9 */
|
||||
background: linear-gradient(top, #9dd53a 0%,#a1d54f 50%,#80c217 51%,#7cbc0a 100%); /* W3C */
|
||||
}
|
||||
|
||||
64
include/blue.css
Normal file
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* noVNC blue CSS
|
||||
* Copyright (C) 2012 Joel Martin
|
||||
* Copyright (C) 2013 Samuel Mannehed for Cendio AB
|
||||
* noVNC is licensed under the MPL 2.0 (see LICENSE.txt)
|
||||
* This file is licensed under the 2-Clause BSD license (see LICENSE.txt).
|
||||
*/
|
||||
|
||||
.noVNC_status_normal {
|
||||
background-color:#04073d;
|
||||
background-image: -webkit-gradient(
|
||||
linear,
|
||||
left bottom,
|
||||
left top,
|
||||
color-stop(0.54, rgb(10,15,79)),
|
||||
color-stop(0.5, rgb(4,7,61))
|
||||
);
|
||||
background-image: -moz-linear-gradient(
|
||||
center bottom,
|
||||
rgb(10,15,79) 54%,
|
||||
rgb(4,7,61) 50%
|
||||
);
|
||||
}
|
||||
.noVNC_status_error {
|
||||
background-color:#f04040;
|
||||
background-image: -webkit-gradient(
|
||||
linear,
|
||||
left bottom,
|
||||
left top,
|
||||
color-stop(0.54, rgb(240,64,64)),
|
||||
color-stop(0.5, rgb(4,7,61))
|
||||
);
|
||||
background-image: -moz-linear-gradient(
|
||||
center bottom,
|
||||
rgb(4,7,61) 54%,
|
||||
rgb(249,64,64) 50%
|
||||
);
|
||||
}
|
||||
.noVNC_status_warn {
|
||||
background-color:#f0f040;
|
||||
background-image: -webkit-gradient(
|
||||
linear,
|
||||
left bottom,
|
||||
left top,
|
||||
color-stop(0.54, rgb(240,240,64)),
|
||||
color-stop(0.5, rgb(4,7,61))
|
||||
);
|
||||
background-image: -moz-linear-gradient(
|
||||
center bottom,
|
||||
rgb(4,7,61) 54%,
|
||||
rgb(240,240,64) 50%
|
||||
);
|
||||
}
|
||||
|
||||
.triangle-right {
|
||||
border:2px solid #fff;
|
||||
background:#04073d;
|
||||
color:#fff;
|
||||
}
|
||||
|
||||
#keyboardinput {
|
||||
background-color:#04073d;
|
||||
}
|
||||
|
||||
321
include/chrome-app/tcp-client.js
Normal file
@@ -0,0 +1,321 @@
|
||||
/*
|
||||
Copyright 2012 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
Author: Boris Smus (smus@chromium.org)
|
||||
*/
|
||||
|
||||
(function(exports) {
|
||||
|
||||
// Define some local variables here.
|
||||
var socket = chrome.socket || chrome.experimental.socket;
|
||||
var dns = chrome.experimental.dns;
|
||||
|
||||
/**
|
||||
* Creates an instance of the client
|
||||
*
|
||||
* @param {String} host The remote host to connect to
|
||||
* @param {Number} port The port to connect to at the remote host
|
||||
*/
|
||||
function TcpClient(host, port, pollInterval) {
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
this.pollInterval = pollInterval || 15;
|
||||
|
||||
// Callback functions.
|
||||
this.callbacks = {
|
||||
connect: null, // Called when socket is connected.
|
||||
disconnect: null, // Called when socket is disconnected.
|
||||
recvBuffer: null, // Called (as ArrayBuffer) when client receives data from server.
|
||||
recvString: null, // Called (as string) when client receives data from server.
|
||||
sent: null // Called when client sends data to server.
|
||||
};
|
||||
|
||||
// Socket.
|
||||
this.socketId = null;
|
||||
this.isConnected = false;
|
||||
|
||||
log('initialized tcp client');
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects to the TCP socket, and creates an open socket.
|
||||
*
|
||||
* @see http://developer.chrome.com/trunk/apps/socket.html#method-create
|
||||
* @param {Function} callback The function to call on connection
|
||||
*/
|
||||
TcpClient.prototype.connect = function(callback) {
|
||||
// First resolve the hostname to an IP.
|
||||
dns.resolve(this.host, function(result) {
|
||||
this.addr = result.address;
|
||||
socket.create('tcp', {}, this._onCreate.bind(this));
|
||||
|
||||
// Register connect callback.
|
||||
this.callbacks.connect = callback;
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
/**
|
||||
* Sends an arraybuffer/view down the wire to the remote side
|
||||
*
|
||||
* @see http://developer.chrome.com/trunk/apps/socket.html#method-write
|
||||
* @param {String} msg The arraybuffer/view to send
|
||||
* @param {Function} callback The function to call when the message has sent
|
||||
*/
|
||||
TcpClient.prototype.sendBuffer = function(buf, callback) {
|
||||
if (buf.buffer) {
|
||||
buf = buf.buffer;
|
||||
}
|
||||
|
||||
/*
|
||||
// Debug
|
||||
var bytes = [], u8 = new Uint8Array(buf);
|
||||
for (var i = 0; i < u8.length; i++) {
|
||||
bytes.push(u8[i]);
|
||||
}
|
||||
log("sending bytes: " + (bytes.join(',')));
|
||||
*/
|
||||
|
||||
socket.write(this.socketId, buf, this._onWriteComplete.bind(this));
|
||||
|
||||
// Register sent callback.
|
||||
this.callbacks.sent = callback;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sends a string down the wire to the remote side
|
||||
*
|
||||
* @see http://developer.chrome.com/trunk/apps/socket.html#method-write
|
||||
* @param {String} msg The string to send
|
||||
* @param {Function} callback The function to call when the message has sent
|
||||
*/
|
||||
TcpClient.prototype.sendString = function(msg, callback) {
|
||||
/*
|
||||
// Debug
|
||||
log("sending string: " + msg);
|
||||
*/
|
||||
|
||||
this._stringToArrayBuffer(msg, function(arrayBuffer) {
|
||||
socket.write(this.socketId, arrayBuffer, this._onWriteComplete.bind(this));
|
||||
}.bind(this));
|
||||
|
||||
// Register sent callback.
|
||||
this.callbacks.sent = callback;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the callback for when a message is received
|
||||
*
|
||||
* @param {Function} callback The function to call when a message has arrived
|
||||
* @param {String} type The callback argument type: "arraybuffer" or "string"
|
||||
*/
|
||||
TcpClient.prototype.addResponseListener = function(callback, type) {
|
||||
if (typeof type === "undefined") {
|
||||
type = "arraybuffer";
|
||||
}
|
||||
// Register received callback.
|
||||
if (type === "string") {
|
||||
this.callbacks.recvString = callback;
|
||||
} else {
|
||||
this.callbacks.recvBuffer = callback;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the callback for when the socket disconnects
|
||||
*
|
||||
* @param {Function} callback The function to call when the socket disconnects
|
||||
* @param {String} type The callback argument type: "arraybuffer" or "string"
|
||||
*/
|
||||
TcpClient.prototype.addDisconnectListener = function(callback) {
|
||||
// Register disconnect callback.
|
||||
this.callbacks.disconnect = callback;
|
||||
};
|
||||
|
||||
/**
|
||||
* Disconnects from the remote side
|
||||
*
|
||||
* @see http://developer.chrome.com/trunk/apps/socket.html#method-disconnect
|
||||
*/
|
||||
TcpClient.prototype.disconnect = function() {
|
||||
if (this.isConnected) {
|
||||
this.isConnected = false;
|
||||
socket.disconnect(this.socketId);
|
||||
if (this.callbacks.disconnect) {
|
||||
this.callbacks.disconnect();
|
||||
}
|
||||
log('socket disconnected');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The callback function used for when we attempt to have Chrome
|
||||
* create a socket. If the socket is successfully created
|
||||
* we go ahead and connect to the remote side.
|
||||
*
|
||||
* @private
|
||||
* @see http://developer.chrome.com/trunk/apps/socket.html#method-connect
|
||||
* @param {Object} createInfo The socket details
|
||||
*/
|
||||
TcpClient.prototype._onCreate = function(createInfo) {
|
||||
this.socketId = createInfo.socketId;
|
||||
if (this.socketId > 0) {
|
||||
socket.connect(this.socketId, this.addr, this.port, this._onConnectComplete.bind(this));
|
||||
} else {
|
||||
error('Unable to create socket');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The callback function used for when we attempt to have Chrome
|
||||
* connect to the remote side. If a successful connection is
|
||||
* made then polling starts to check for data to read
|
||||
*
|
||||
* @private
|
||||
* @param {Number} resultCode Indicates whether the connection was successful
|
||||
*/
|
||||
TcpClient.prototype._onConnectComplete = function(resultCode) {
|
||||
// Start polling for reads.
|
||||
this.isConnected = true;
|
||||
setTimeout(this._periodicallyRead.bind(this), this.pollInterval);
|
||||
|
||||
if (this.callbacks.connect) {
|
||||
log('connect complete');
|
||||
this.callbacks.connect();
|
||||
}
|
||||
log('onConnectComplete');
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks for new data to read from the socket
|
||||
*
|
||||
* @see http://developer.chrome.com/trunk/apps/socket.html#method-read
|
||||
*/
|
||||
TcpClient.prototype._periodicallyRead = function() {
|
||||
var that = this;
|
||||
socket.getInfo(this.socketId, function (info) {
|
||||
if (info.connected) {
|
||||
setTimeout(that._periodicallyRead.bind(that), that.pollInterval);
|
||||
socket.read(that.socketId, null, that._onDataRead.bind(that));
|
||||
} else if (that.isConnected) {
|
||||
log('socket disconnect detected');
|
||||
that.disconnect();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback function for when data has been read from the socket.
|
||||
* Converts the array buffer that is read in to a string
|
||||
* and sends it on for further processing by passing it to
|
||||
* the previously assigned callback function.
|
||||
*
|
||||
* @private
|
||||
* @see TcpClient.prototype.addResponseListener
|
||||
* @param {Object} readInfo The incoming message
|
||||
*/
|
||||
TcpClient.prototype._onDataRead = function(readInfo) {
|
||||
// Call received callback if there's data in the response.
|
||||
if (readInfo.resultCode > 0) {
|
||||
log('onDataRead');
|
||||
|
||||
/*
|
||||
// Debug
|
||||
var bytes = [], u8 = new Uint8Array(readInfo.data);
|
||||
for (var i = 0; i < u8.length; i++) {
|
||||
bytes.push(u8[i]);
|
||||
}
|
||||
log("received bytes: " + (bytes.join(',')));
|
||||
*/
|
||||
|
||||
if (this.callbacks.recvBuffer) {
|
||||
// Return raw ArrayBuffer directly.
|
||||
this.callbacks.recvBuffer(readInfo.data);
|
||||
}
|
||||
if (this.callbacks.recvString) {
|
||||
// Convert ArrayBuffer to string.
|
||||
this._arrayBufferToString(readInfo.data, function(str) {
|
||||
this.callbacks.recvString(str);
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
// Trigger another read right away
|
||||
setTimeout(this._periodicallyRead.bind(this), 0);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback for when data has been successfully
|
||||
* written to the socket.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} writeInfo The outgoing message
|
||||
*/
|
||||
TcpClient.prototype._onWriteComplete = function(writeInfo) {
|
||||
log('onWriteComplete');
|
||||
// Call sent callback.
|
||||
if (this.callbacks.sent) {
|
||||
this.callbacks.sent(writeInfo);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts an array buffer to a string
|
||||
*
|
||||
* @private
|
||||
* @param {ArrayBuffer} buf The buffer to convert
|
||||
* @param {Function} callback The function to call when conversion is complete
|
||||
*/
|
||||
TcpClient.prototype._arrayBufferToString = function(buf, callback) {
|
||||
var bb = new Blob([new Uint8Array(buf)]);
|
||||
var f = new FileReader();
|
||||
f.onload = function(e) {
|
||||
callback(e.target.result);
|
||||
};
|
||||
f.readAsText(bb);
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts a string to an array buffer
|
||||
*
|
||||
* @private
|
||||
* @param {String} str The string to convert
|
||||
* @param {Function} callback The function to call when conversion is complete
|
||||
*/
|
||||
TcpClient.prototype._stringToArrayBuffer = function(str, callback) {
|
||||
var bb = new Blob([str]);
|
||||
var f = new FileReader();
|
||||
f.onload = function(e) {
|
||||
callback(e.target.result);
|
||||
};
|
||||
f.readAsArrayBuffer(bb);
|
||||
};
|
||||
|
||||
/**
|
||||
* Wrapper function for logging
|
||||
*/
|
||||
function log(msg) {
|
||||
console.log(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper function for error logging
|
||||
*/
|
||||
function error(msg) {
|
||||
console.error(msg);
|
||||
}
|
||||
|
||||
exports.TcpClient = TcpClient;
|
||||
|
||||
})(window);
|
||||
349
include/des.js
@@ -75,199 +75,202 @@
|
||||
* 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,
|
||||
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 = [];
|
||||
// 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,
|
||||
z|f,b|e,b|e,a|e,a|e,c|z,c|z,b|f,a|d,b|d,b|d,a|d,z|z,z|f,a|f,b|z,
|
||||
a|z,c|f,z|d,c|z,c|e,b|z,b|z,z|e,c|d,a|z,a|e,b|d,z|e,z|d,b|f,a|f,
|
||||
c|f,a|d,c|z,b|f,b|d,z|f,a|f,c|e,z|f,b|e,b|e,z|z,a|d,a|e,z|z,c|d];
|
||||
a=1<<20; b=1<<31; c=a|b; d=1<<5; e=1<<15; f=d|e;
|
||||
SP2 = [c|f,b|e,z|e,a|f,a|z,z|d,c|d,b|f,b|d,c|f,c|e,b|z,b|e,a|z,z|d,c|d,
|
||||
a|e,a|d,b|f,z|z,b|z,z|e,a|f,c|z,a|d,b|d,z|z,a|e,z|f,c|e,c|z,z|f,
|
||||
z|z,a|f,c|d,a|z,b|f,c|z,c|e,z|e,c|z,b|e,z|d,c|f,a|f,z|d,z|e,b|z,
|
||||
z|f,c|e,a|z,b|d,a|d,b|f,b|d,a|d,a|e,z|z,b|e,z|f,b|z,c|d,c|f,a|e];
|
||||
a=1<<17; b=1<<27; c=a|b; d=1<<3; e=1<<9; f=d|e;
|
||||
SP3 = [z|f,c|e,z|z,c|d,b|e,z|z,a|f,b|e,a|d,b|d,b|d,a|z,c|f,a|d,c|z,z|f,
|
||||
b|z,z|d,c|e,z|e,a|e,c|z,c|d,a|f,b|f,a|e,a|z,b|f,z|d,c|f,z|e,b|z,
|
||||
c|e,b|z,a|d,z|f,a|z,c|e,b|e,z|z,z|e,a|d,c|f,b|e,b|d,z|e,z|z,c|d,
|
||||
b|f,a|z,b|z,c|f,z|d,a|f,a|e,b|d,c|z,b|f,z|f,c|z,a|f,z|d,c|d,a|e];
|
||||
a=1<<13; b=1<<23; c=a|b; d=1<<0; e=1<<7; f=d|e;
|
||||
SP4 = [c|d,a|f,a|f,z|e,c|e,b|f,b|d,a|d,z|z,c|z,c|z,c|f,z|f,z|z,b|e,b|d,
|
||||
z|d,a|z,b|z,c|d,z|e,b|z,a|d,a|e,b|f,z|d,a|e,b|e,a|z,c|e,c|f,z|f,
|
||||
b|e,b|d,c|z,c|f,z|f,z|z,z|z,c|z,a|e,b|e,b|f,z|d,c|d,a|f,a|f,z|e,
|
||||
c|f,z|f,z|d,a|z,b|d,a|d,c|e,b|f,a|d,a|e,b|z,c|d,z|e,b|z,a|z,c|e];
|
||||
a=1<<25; b=1<<30; c=a|b; d=1<<8; e=1<<19; f=d|e;
|
||||
SP5 = [z|d,a|f,a|e,c|d,z|e,z|d,b|z,a|e,b|f,z|e,a|d,b|f,c|d,c|e,z|f,b|z,
|
||||
a|z,b|e,b|e,z|z,b|d,c|f,c|f,a|d,c|e,b|d,z|z,c|z,a|f,a|z,c|z,z|f,
|
||||
z|e,c|d,z|d,a|z,b|z,a|e,c|d,b|f,a|d,b|z,c|e,a|f,b|f,z|d,a|z,c|e,
|
||||
c|f,z|f,c|z,c|f,a|e,z|z,b|e,c|z,z|f,a|d,b|d,z|e,z|z,b|e,a|f,b|d];
|
||||
a=1<<22; b=1<<29; c=a|b; d=1<<4; e=1<<14; f=d|e;
|
||||
SP6 = [b|d,c|z,z|e,c|f,c|z,z|d,c|f,a|z,b|e,a|f,a|z,b|d,a|d,b|e,b|z,z|f,
|
||||
z|z,a|d,b|f,z|e,a|e,b|f,z|d,c|d,c|d,z|z,a|f,c|e,z|f,a|e,c|e,b|z,
|
||||
b|e,z|d,c|d,a|e,c|f,a|z,z|f,b|d,a|z,b|e,b|z,z|f,b|d,c|f,a|e,c|z,
|
||||
a|f,c|e,z|z,c|d,z|d,z|e,c|z,a|f,z|e,a|d,b|f,z|z,c|e,b|z,a|d,b|f];
|
||||
a=1<<21; b=1<<26; c=a|b; d=1<<1; e=1<<11; f=d|e;
|
||||
SP7 = [a|z,c|d,b|f,z|z,z|e,b|f,a|f,c|e,c|f,a|z,z|z,b|d,z|d,b|z,c|d,z|f,
|
||||
b|e,a|f,a|d,b|e,b|d,c|z,c|e,a|d,c|z,z|e,z|f,c|f,a|e,z|d,b|z,a|e,
|
||||
b|z,a|e,a|z,b|f,b|f,c|d,c|d,z|d,a|d,b|z,b|e,a|z,c|e,z|f,a|f,c|e,
|
||||
z|f,b|d,c|f,c|z,a|e,z|z,z|d,c|f,z|z,a|f,c|z,z|e,b|d,b|e,z|e,a|d];
|
||||
a=1<<18; b=1<<28; c=a|b; d=1<<6; e=1<<12; f=d|e;
|
||||
SP8 = [b|f,z|e,a|z,c|f,b|z,b|f,z|d,b|z,a|d,c|z,c|f,a|e,c|e,a|f,z|e,z|d,
|
||||
c|z,b|d,b|e,z|f,a|e,a|d,c|d,c|e,z|f,z|z,z|z,c|d,b|d,b|e,a|f,a|z,
|
||||
a|f,a|z,c|e,z|e,z|d,c|d,z|e,a|f,b|e,z|d,b|d,c|z,c|d,b|z,a|z,b|f,
|
||||
z|z,c|f,a|d,b|d,c|z,b|e,b|f,z|z,c|f,a|e,a|e,z|f,z|f,a|d,b|z,c|e];
|
||||
// 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|e,a|d,b|f,z|z,b|z,z|e,a|f,c|z,a|d,b|d,z|z,a|e,z|f,c|e,c|z,z|f,
|
||||
z|z,a|f,c|d,a|z,b|f,c|z,c|e,z|e,c|z,b|e,z|d,c|f,a|f,z|d,z|e,b|z,
|
||||
z|f,c|e,a|z,b|d,a|d,b|f,b|d,a|d,a|e,z|z,b|e,z|f,b|z,c|d,c|f,a|e];
|
||||
a=1<<17; b=1<<27; c=a|b; d=1<<3; e=1<<9; f=d|e;
|
||||
SP3 = [z|f,c|e,z|z,c|d,b|e,z|z,a|f,b|e,a|d,b|d,b|d,a|z,c|f,a|d,c|z,z|f,
|
||||
b|z,z|d,c|e,z|e,a|e,c|z,c|d,a|f,b|f,a|e,a|z,b|f,z|d,c|f,z|e,b|z,
|
||||
c|e,b|z,a|d,z|f,a|z,c|e,b|e,z|z,z|e,a|d,c|f,b|e,b|d,z|e,z|z,c|d,
|
||||
b|f,a|z,b|z,c|f,z|d,a|f,a|e,b|d,c|z,b|f,z|f,c|z,a|f,z|d,c|d,a|e];
|
||||
a=1<<13; b=1<<23; c=a|b; d=1<<0; e=1<<7; f=d|e;
|
||||
SP4 = [c|d,a|f,a|f,z|e,c|e,b|f,b|d,a|d,z|z,c|z,c|z,c|f,z|f,z|z,b|e,b|d,
|
||||
z|d,a|z,b|z,c|d,z|e,b|z,a|d,a|e,b|f,z|d,a|e,b|e,a|z,c|e,c|f,z|f,
|
||||
b|e,b|d,c|z,c|f,z|f,z|z,z|z,c|z,a|e,b|e,b|f,z|d,c|d,a|f,a|f,z|e,
|
||||
c|f,z|f,z|d,a|z,b|d,a|d,c|e,b|f,a|d,a|e,b|z,c|d,z|e,b|z,a|z,c|e];
|
||||
a=1<<25; b=1<<30; c=a|b; d=1<<8; e=1<<19; f=d|e;
|
||||
SP5 = [z|d,a|f,a|e,c|d,z|e,z|d,b|z,a|e,b|f,z|e,a|d,b|f,c|d,c|e,z|f,b|z,
|
||||
a|z,b|e,b|e,z|z,b|d,c|f,c|f,a|d,c|e,b|d,z|z,c|z,a|f,a|z,c|z,z|f,
|
||||
z|e,c|d,z|d,a|z,b|z,a|e,c|d,b|f,a|d,b|z,c|e,a|f,b|f,z|d,a|z,c|e,
|
||||
c|f,z|f,c|z,c|f,a|e,z|z,b|e,c|z,z|f,a|d,b|d,z|e,z|z,b|e,a|f,b|d];
|
||||
a=1<<22; b=1<<29; c=a|b; d=1<<4; e=1<<14; f=d|e;
|
||||
SP6 = [b|d,c|z,z|e,c|f,c|z,z|d,c|f,a|z,b|e,a|f,a|z,b|d,a|d,b|e,b|z,z|f,
|
||||
z|z,a|d,b|f,z|e,a|e,b|f,z|d,c|d,c|d,z|z,a|f,c|e,z|f,a|e,c|e,b|z,
|
||||
b|e,z|d,c|d,a|e,c|f,a|z,z|f,b|d,a|z,b|e,b|z,z|f,b|d,c|f,a|e,c|z,
|
||||
a|f,c|e,z|z,c|d,z|d,z|e,c|z,a|f,z|e,a|d,b|f,z|z,c|e,b|z,a|d,b|f];
|
||||
a=1<<21; b=1<<26; c=a|b; d=1<<1; e=1<<11; f=d|e;
|
||||
SP7 = [a|z,c|d,b|f,z|z,z|e,b|f,a|f,c|e,c|f,a|z,z|z,b|d,z|d,b|z,c|d,z|f,
|
||||
b|e,a|f,a|d,b|e,b|d,c|z,c|e,a|d,c|z,z|e,z|f,c|f,a|e,z|d,b|z,a|e,
|
||||
b|z,a|e,a|z,b|f,b|f,c|d,c|d,z|d,a|d,b|z,b|e,a|z,c|e,z|f,a|f,c|e,
|
||||
z|f,b|d,c|f,c|z,a|e,z|z,z|d,c|f,z|z,a|f,c|z,z|e,b|d,b|e,z|e,a|d];
|
||||
a=1<<18; b=1<<28; c=a|b; d=1<<6; e=1<<12; f=d|e;
|
||||
SP8 = [b|f,z|e,a|z,c|f,b|z,b|f,z|d,b|z,a|d,c|z,c|f,a|e,c|e,a|f,z|e,z|d,
|
||||
c|z,b|d,b|e,z|f,a|e,a|d,c|d,c|e,z|f,z|z,z|z,c|d,b|d,b|e,a|f,a|z,
|
||||
a|f,a|z,c|e,z|e,z|d,c|d,z|e,a|f,b|e,z|d,b|d,c|z,c|d,b|z,a|z,b|f,
|
||||
z|z,c|f,a|d,b|d,c|z,b|e,b|f,z|z,c|f,a|e,a|e,z|f,z|f,a|d,b|z,c|e];
|
||||
// jshint +W013,+W015
|
||||
|
||||
// Set the key.
|
||||
function setKeys(keyBlock) {
|
||||
var i, j, l, m, n, o, pc1m = [], pcr = [], kn = [],
|
||||
raw0, raw1, rawi, KnLi;
|
||||
// Set the key.
|
||||
function setKeys(keyBlock) {
|
||||
var i, j, l, m, n, o, pc1m = [], pcr = [], kn = [],
|
||||
raw0, raw1, rawi, KnLi;
|
||||
|
||||
for (j = 0, l = 56; j < 56; ++j, l-=8) {
|
||||
l += l<-5 ? 65 : l<-3 ? 31 : l<-1 ? 63 : l===27 ? 35 : 0; // PC1
|
||||
m = l & 0x7;
|
||||
pc1m[j] = ((keyBlock[l >>> 3] & (1<<m)) !== 0) ? 1: 0;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
for (i = 0; i < 16; ++i) {
|
||||
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) {
|
||||
l = j + totrot[i];
|
||||
if (l < o) {
|
||||
pcr[j] = pc1m[l];
|
||||
} else {
|
||||
pcr[j] = pc1m[l - 28];
|
||||
for (i = 0; i < 16; ++i) {
|
||||
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) {
|
||||
l = j + totrot[i];
|
||||
if (l < o) {
|
||||
pcr[j] = pc1m[l];
|
||||
} else {
|
||||
pcr[j] = pc1m[l - 28];
|
||||
}
|
||||
}
|
||||
}
|
||||
for (j = 0; j < 24; ++j) {
|
||||
if (pcr[PC2[j]] !== 0) {
|
||||
kn[m] |= 1 << (23 - j);
|
||||
}
|
||||
if (pcr[PC2[j + 24]] !== 0) {
|
||||
kn[n] |= 1 << (23 - j);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (j = 0; j < 24; ++j) {
|
||||
if (pcr[PC2[j]] !== 0) {
|
||||
kn[m] |= 1<<(23-j);
|
||||
}
|
||||
if (pcr[PC2[j + 24]] !== 0) {
|
||||
kn[n] |= 1<<(23-j);
|
||||
}
|
||||
|
||||
// cookey
|
||||
for (i = 0, rawi = 0, KnLi = 0; i < 16; ++i) {
|
||||
raw0 = kn[rawi++];
|
||||
raw1 = kn[rawi++];
|
||||
keys[KnLi] = (raw0 & 0x00fc0000) << 6;
|
||||
keys[KnLi] |= (raw0 & 0x00000fc0) << 10;
|
||||
keys[KnLi] |= (raw1 & 0x00fc0000) >>> 10;
|
||||
keys[KnLi] |= (raw1 & 0x00000fc0) >>> 6;
|
||||
++KnLi;
|
||||
keys[KnLi] = (raw0 & 0x0003f000) << 12;
|
||||
keys[KnLi] |= (raw0 & 0x0000003f) << 16;
|
||||
keys[KnLi] |= (raw1 & 0x0003f000) >>> 4;
|
||||
keys[KnLi] |= (raw1 & 0x0000003f);
|
||||
++KnLi;
|
||||
}
|
||||
}
|
||||
|
||||
// cookey
|
||||
for (i = 0, rawi = 0, KnLi = 0; i < 16; ++i) {
|
||||
raw0 = kn[rawi++];
|
||||
raw1 = kn[rawi++];
|
||||
keys[KnLi] = (raw0 & 0x00fc0000) << 6;
|
||||
keys[KnLi] |= (raw0 & 0x00000fc0) << 10;
|
||||
keys[KnLi] |= (raw1 & 0x00fc0000) >>> 10;
|
||||
keys[KnLi] |= (raw1 & 0x00000fc0) >>> 6;
|
||||
++KnLi;
|
||||
keys[KnLi] = (raw0 & 0x0003f000) << 12;
|
||||
keys[KnLi] |= (raw0 & 0x0000003f) << 16;
|
||||
keys[KnLi] |= (raw1 & 0x0003f000) >>> 4;
|
||||
keys[KnLi] |= (raw1 & 0x0000003f);
|
||||
++KnLi;
|
||||
}
|
||||
}
|
||||
// Encrypt 8 bytes of text
|
||||
function enc8(text) {
|
||||
var i = 0, b = text.slice(), fval, keysi = 0,
|
||||
l, r, x; // left, right, accumulator
|
||||
|
||||
// Encrypt 8 bytes of text
|
||||
function enc8(text) {
|
||||
var i = 0, b = text.slice(), fval, keysi = 0,
|
||||
l, r, x; // left, right, accumulator
|
||||
// Squash 8 bytes to 2 ints
|
||||
l = b[i++]<<24 | b[i++]<<16 | b[i++]<<8 | b[i++];
|
||||
r = b[i++]<<24 | b[i++]<<16 | b[i++]<<8 | b[i++];
|
||||
|
||||
// Squash 8 bytes to 2 ints
|
||||
l = b[i++]<<24 | b[i++]<<16 | b[i++]<<8 | b[i++];
|
||||
r = b[i++]<<24 | b[i++]<<16 | b[i++]<<8 | b[i++];
|
||||
x = ((l >>> 4) ^ r) & 0x0f0f0f0f;
|
||||
r ^= x;
|
||||
l ^= (x << 4);
|
||||
x = ((l >>> 16) ^ r) & 0x0000ffff;
|
||||
r ^= x;
|
||||
l ^= (x << 16);
|
||||
x = ((r >>> 2) ^ l) & 0x33333333;
|
||||
l ^= x;
|
||||
r ^= (x << 2);
|
||||
x = ((r >>> 8) ^ l) & 0x00ff00ff;
|
||||
l ^= x;
|
||||
r ^= (x << 8);
|
||||
r = (r << 1) | ((r >>> 31) & 1);
|
||||
x = (l ^ r) & 0xaaaaaaaa;
|
||||
l ^= x;
|
||||
r ^= x;
|
||||
l = (l << 1) | ((l >>> 31) & 1);
|
||||
|
||||
x = ((l >>> 4) ^ r) & 0x0f0f0f0f;
|
||||
r ^= x;
|
||||
l ^= (x << 4);
|
||||
x = ((l >>> 16) ^ r) & 0x0000ffff;
|
||||
r ^= x;
|
||||
l ^= (x << 16);
|
||||
x = ((r >>> 2) ^ l) & 0x33333333;
|
||||
l ^= x;
|
||||
r ^= (x << 2);
|
||||
x = ((r >>> 8) ^ l) & 0x00ff00ff;
|
||||
l ^= x;
|
||||
r ^= (x << 8);
|
||||
r = (r << 1) | ((r >>> 31) & 1);
|
||||
x = (l ^ r) & 0xaaaaaaaa;
|
||||
l ^= x;
|
||||
r ^= x;
|
||||
l = (l << 1) | ((l >>> 31) & 1);
|
||||
for (i = 0; i < 8; ++i) {
|
||||
x = (r << 28) | (r >>> 4);
|
||||
x ^= keys[keysi++];
|
||||
fval = SP7[x & 0x3f];
|
||||
fval |= SP5[(x >>> 8) & 0x3f];
|
||||
fval |= SP3[(x >>> 16) & 0x3f];
|
||||
fval |= SP1[(x >>> 24) & 0x3f];
|
||||
x = r ^ keys[keysi++];
|
||||
fval |= SP8[x & 0x3f];
|
||||
fval |= SP6[(x >>> 8) & 0x3f];
|
||||
fval |= SP4[(x >>> 16) & 0x3f];
|
||||
fval |= SP2[(x >>> 24) & 0x3f];
|
||||
l ^= fval;
|
||||
x = (l << 28) | (l >>> 4);
|
||||
x ^= keys[keysi++];
|
||||
fval = SP7[x & 0x3f];
|
||||
fval |= SP5[(x >>> 8) & 0x3f];
|
||||
fval |= SP3[(x >>> 16) & 0x3f];
|
||||
fval |= SP1[(x >>> 24) & 0x3f];
|
||||
x = l ^ keys[keysi++];
|
||||
fval |= SP8[x & 0x0000003f];
|
||||
fval |= SP6[(x >>> 8) & 0x3f];
|
||||
fval |= SP4[(x >>> 16) & 0x3f];
|
||||
fval |= SP2[(x >>> 24) & 0x3f];
|
||||
r ^= fval;
|
||||
}
|
||||
|
||||
for (i = 0; i < 8; ++i) {
|
||||
x = (r << 28) | (r >>> 4);
|
||||
x ^= keys[keysi++];
|
||||
fval = SP7[x & 0x3f];
|
||||
fval |= SP5[(x >>> 8) & 0x3f];
|
||||
fval |= SP3[(x >>> 16) & 0x3f];
|
||||
fval |= SP1[(x >>> 24) & 0x3f];
|
||||
x = r ^ keys[keysi++];
|
||||
fval |= SP8[x & 0x3f];
|
||||
fval |= SP6[(x >>> 8) & 0x3f];
|
||||
fval |= SP4[(x >>> 16) & 0x3f];
|
||||
fval |= SP2[(x >>> 24) & 0x3f];
|
||||
l ^= fval;
|
||||
x = (l << 28) | (l >>> 4);
|
||||
x ^= keys[keysi++];
|
||||
fval = SP7[x & 0x3f];
|
||||
fval |= SP5[(x >>> 8) & 0x3f];
|
||||
fval |= SP3[(x >>> 16) & 0x3f];
|
||||
fval |= SP1[(x >>> 24) & 0x3f];
|
||||
x = l ^ keys[keysi++];
|
||||
fval |= SP8[x & 0x0000003f];
|
||||
fval |= SP6[(x >>> 8) & 0x3f];
|
||||
fval |= SP4[(x >>> 16) & 0x3f];
|
||||
fval |= SP2[(x >>> 24) & 0x3f];
|
||||
r ^= fval;
|
||||
r = (r << 31) | (r >>> 1);
|
||||
x = (l ^ r) & 0xaaaaaaaa;
|
||||
l ^= x;
|
||||
r ^= x;
|
||||
l = (l << 31) | (l >>> 1);
|
||||
x = ((l >>> 8) ^ r) & 0x00ff00ff;
|
||||
r ^= x;
|
||||
l ^= (x << 8);
|
||||
x = ((l >>> 2) ^ r) & 0x33333333;
|
||||
r ^= x;
|
||||
l ^= (x << 2);
|
||||
x = ((r >>> 16) ^ l) & 0x0000ffff;
|
||||
l ^= x;
|
||||
r ^= (x << 16);
|
||||
x = ((r >>> 4) ^ l) & 0x0f0f0f0f;
|
||||
l ^= x;
|
||||
r ^= (x << 4);
|
||||
|
||||
// Spread ints to bytes
|
||||
x = [r, l];
|
||||
for (i = 0; i < 8; i++) {
|
||||
b[i] = (x[i>>>2] >>> (8 * (3 - (i % 4)))) % 256;
|
||||
if (b[i] < 0) { b[i] += 256; } // unsigned
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
r = (r << 31) | (r >>> 1);
|
||||
x = (l ^ r) & 0xaaaaaaaa;
|
||||
l ^= x;
|
||||
r ^= x;
|
||||
l = (l << 31) | (l >>> 1);
|
||||
x = ((l >>> 8) ^ r) & 0x00ff00ff;
|
||||
r ^= x;
|
||||
l ^= (x << 8);
|
||||
x = ((l >>> 2) ^ r) & 0x33333333;
|
||||
r ^= x;
|
||||
l ^= (x << 2);
|
||||
x = ((r >>> 16) ^ l) & 0x0000ffff;
|
||||
l ^= x;
|
||||
r ^= (x << 16);
|
||||
x = ((r >>> 4) ^ l) & 0x0f0f0f0f;
|
||||
l ^= x;
|
||||
r ^= (x << 4);
|
||||
|
||||
// Spread ints to bytes
|
||||
x = [r, l];
|
||||
for (i = 0; i < 8; i++) {
|
||||
b[i] = (x[i>>>2] >>> (8*(3 - (i%4)))) % 256;
|
||||
if (b[i] < 0) { b[i] += 256; } // unsigned
|
||||
// Encrypt 16 bytes of text using passwd as key
|
||||
function encrypt(t) {
|
||||
return enc8(t.slice(0, 8)).concat(enc8(t.slice(8, 16)));
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
// Encrypt 16 bytes of text using passwd as key
|
||||
function encrypt(t) {
|
||||
return enc8(t.slice(0,8)).concat(enc8(t.slice(8,16)));
|
||||
}
|
||||
|
||||
setKeys(passwd); // Setup keys
|
||||
return {'encrypt': encrypt}; // Public interface
|
||||
setKeys(passwd); // Setup keys
|
||||
return {'encrypt': encrypt}; // Public interface
|
||||
|
||||
} // function DES
|
||||
|
||||
1252
include/display.js
2165
include/input.js
676
include/jsunzip.js
Executable file
@@ -0,0 +1,676 @@
|
||||
/*
|
||||
* JSUnzip
|
||||
*
|
||||
* Copyright (c) 2011 by Erik Moller
|
||||
* All Rights Reserved
|
||||
*
|
||||
* This software is provided 'as-is', without any express
|
||||
* or implied warranty. In no event will the authors be
|
||||
* held liable for any damages arising from the use of
|
||||
* this software.
|
||||
*
|
||||
* Permission is granted to anyone to use this software
|
||||
* for any purpose, including commercial applications,
|
||||
* and to alter it and redistribute it freely, subject to
|
||||
* the following restrictions:
|
||||
*
|
||||
* 1. The origin of this software must not be
|
||||
* misrepresented; you must not claim that you
|
||||
* wrote the original software. If you use this
|
||||
* software in a product, an acknowledgment in
|
||||
* the product documentation would be appreciated
|
||||
* but is not required.
|
||||
*
|
||||
* 2. Altered source versions must be plainly marked
|
||||
* as such, and must not be misrepresented as
|
||||
* being the original software.
|
||||
*
|
||||
* 3. This notice may not be removed or altered from
|
||||
* any source distribution.
|
||||
*/
|
||||
|
||||
var tinf;
|
||||
|
||||
function JSUnzip() {
|
||||
|
||||
this.getInt = function(offset, size) {
|
||||
switch (size) {
|
||||
case 4:
|
||||
return (this.data.charCodeAt(offset + 3) & 0xff) << 24 |
|
||||
(this.data.charCodeAt(offset + 2) & 0xff) << 16 |
|
||||
(this.data.charCodeAt(offset + 1) & 0xff) << 8 |
|
||||
(this.data.charCodeAt(offset + 0) & 0xff);
|
||||
break;
|
||||
case 2:
|
||||
return (this.data.charCodeAt(offset + 1) & 0xff) << 8 |
|
||||
(this.data.charCodeAt(offset + 0) & 0xff);
|
||||
break;
|
||||
default:
|
||||
return this.data.charCodeAt(offset) & 0xff;
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
this.getDOSDate = function(dosdate, dostime) {
|
||||
var day = dosdate & 0x1f;
|
||||
var month = ((dosdate >> 5) & 0xf) - 1;
|
||||
var year = 1980 + ((dosdate >> 9) & 0x7f)
|
||||
var second = (dostime & 0x1f) * 2;
|
||||
var minute = (dostime >> 5) & 0x3f;
|
||||
hour = (dostime >> 11) & 0x1f;
|
||||
return new Date(year, month, day, hour, minute, second);
|
||||
}
|
||||
|
||||
this.open = function(data) {
|
||||
this.data = data;
|
||||
this.files = [];
|
||||
|
||||
if (this.data.length < 22)
|
||||
return { 'status' : false, 'error' : 'Invalid data' };
|
||||
var endOfCentralDirectory = this.data.length - 22;
|
||||
while (endOfCentralDirectory >= 0 && this.getInt(endOfCentralDirectory, 4) != 0x06054b50)
|
||||
--endOfCentralDirectory;
|
||||
if (endOfCentralDirectory < 0)
|
||||
return { 'status' : false, 'error' : 'Invalid data' };
|
||||
if (this.getInt(endOfCentralDirectory + 4, 2) != 0 || this.getInt(endOfCentralDirectory + 6, 2) != 0)
|
||||
return { 'status' : false, 'error' : 'No multidisk support' };
|
||||
|
||||
var entriesInThisDisk = this.getInt(endOfCentralDirectory + 8, 2);
|
||||
var centralDirectoryOffset = this.getInt(endOfCentralDirectory + 16, 4);
|
||||
var globalCommentLength = this.getInt(endOfCentralDirectory + 20, 2);
|
||||
this.comment = this.data.slice(endOfCentralDirectory + 22, endOfCentralDirectory + 22 + globalCommentLength);
|
||||
|
||||
var fileOffset = centralDirectoryOffset;
|
||||
|
||||
for (var i = 0; i < entriesInThisDisk; ++i) {
|
||||
if (this.getInt(fileOffset + 0, 4) != 0x02014b50)
|
||||
return { 'status' : false, 'error' : 'Invalid data' };
|
||||
if (this.getInt(fileOffset + 6, 2) > 20)
|
||||
return { 'status' : false, 'error' : 'Unsupported version' };
|
||||
if (this.getInt(fileOffset + 8, 2) & 1)
|
||||
return { 'status' : false, 'error' : 'Encryption not implemented' };
|
||||
|
||||
var compressionMethod = this.getInt(fileOffset + 10, 2);
|
||||
if (compressionMethod != 0 && compressionMethod != 8)
|
||||
return { 'status' : false, 'error' : 'Unsupported compression method' };
|
||||
|
||||
var lastModFileTime = this.getInt(fileOffset + 12, 2);
|
||||
var lastModFileDate = this.getInt(fileOffset + 14, 2);
|
||||
var lastModifiedDate = this.getDOSDate(lastModFileDate, lastModFileTime);
|
||||
|
||||
var crc = this.getInt(fileOffset + 16, 4);
|
||||
// TODO: crc
|
||||
|
||||
var compressedSize = this.getInt(fileOffset + 20, 4);
|
||||
var uncompressedSize = this.getInt(fileOffset + 24, 4);
|
||||
|
||||
var fileNameLength = this.getInt(fileOffset + 28, 2);
|
||||
var extraFieldLength = this.getInt(fileOffset + 30, 2);
|
||||
var fileCommentLength = this.getInt(fileOffset + 32, 2);
|
||||
|
||||
var relativeOffsetOfLocalHeader = this.getInt(fileOffset + 42, 4);
|
||||
|
||||
var fileName = this.data.slice(fileOffset + 46, fileOffset + 46 + fileNameLength);
|
||||
var fileComment = this.data.slice(fileOffset + 46 + fileNameLength + extraFieldLength, fileOffset + 46 + fileNameLength + extraFieldLength + fileCommentLength);
|
||||
|
||||
if (this.getInt(relativeOffsetOfLocalHeader + 0, 4) != 0x04034b50)
|
||||
return { 'status' : false, 'error' : 'Invalid data' };
|
||||
var localFileNameLength = this.getInt(relativeOffsetOfLocalHeader + 26, 2);
|
||||
var localExtraFieldLength = this.getInt(relativeOffsetOfLocalHeader + 28, 2);
|
||||
var localFileContent = relativeOffsetOfLocalHeader + 30 + localFileNameLength + localExtraFieldLength;
|
||||
|
||||
this.files[fileName] =
|
||||
{
|
||||
'fileComment' : fileComment,
|
||||
'compressionMethod' : compressionMethod,
|
||||
'compressedSize' : compressedSize,
|
||||
'uncompressedSize' : uncompressedSize,
|
||||
'localFileContent' : localFileContent,
|
||||
'lastModifiedDate' : lastModifiedDate
|
||||
};
|
||||
|
||||
fileOffset += 46 + fileNameLength + extraFieldLength + fileCommentLength;
|
||||
}
|
||||
return { 'status' : true }
|
||||
};
|
||||
|
||||
|
||||
this.read = function(fileName) {
|
||||
var fileInfo = this.files[fileName];
|
||||
if (fileInfo) {
|
||||
if (fileInfo.compressionMethod == 8) {
|
||||
if (!tinf) {
|
||||
tinf = new TINF();
|
||||
tinf.init();
|
||||
}
|
||||
var result = tinf.uncompress(this.data, fileInfo.localFileContent);
|
||||
if (result.status == tinf.OK)
|
||||
return { 'status' : true, 'data' : result.data };
|
||||
else
|
||||
return { 'status' : false, 'error' : result.error };
|
||||
} else {
|
||||
return { 'status' : true, 'data' : this.data.slice(fileInfo.localFileContent, fileInfo.localFileContent + fileInfo.uncompressedSize) };
|
||||
}
|
||||
}
|
||||
return { 'status' : false, 'error' : "File '" + fileName + "' doesn't exist in zip" };
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* tinflate - tiny inflate
|
||||
*
|
||||
* Copyright (c) 2003 by Joergen Ibsen / Jibz
|
||||
* All Rights Reserved
|
||||
*
|
||||
* http://www.ibsensoftware.com/
|
||||
*
|
||||
* This software is provided 'as-is', without any express
|
||||
* or implied warranty. In no event will the authors be
|
||||
* held liable for any damages arising from the use of
|
||||
* this software.
|
||||
*
|
||||
* Permission is granted to anyone to use this software
|
||||
* for any purpose, including commercial applications,
|
||||
* and to alter it and redistribute it freely, subject to
|
||||
* the following restrictions:
|
||||
*
|
||||
* 1. The origin of this software must not be
|
||||
* misrepresented; you must not claim that you
|
||||
* wrote the original software. If you use this
|
||||
* software in a product, an acknowledgment in
|
||||
* the product documentation would be appreciated
|
||||
* but is not required.
|
||||
*
|
||||
* 2. Altered source versions must be plainly marked
|
||||
* as such, and must not be misrepresented as
|
||||
* being the original software.
|
||||
*
|
||||
* 3. This notice may not be removed or altered from
|
||||
* any source distribution.
|
||||
*/
|
||||
|
||||
/*
|
||||
* tinflate javascript port by Erik Moller in May 2011.
|
||||
* emoller@opera.com
|
||||
*
|
||||
* read_bits() patched by mike@imidio.com to allow
|
||||
* reading more then 8 bits (needed in some zlib streams)
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
function TINF() {
|
||||
|
||||
this.OK = 0;
|
||||
this.DATA_ERROR = (-3);
|
||||
this.WINDOW_SIZE = 32768;
|
||||
|
||||
/* ------------------------------ *
|
||||
* -- internal data structures -- *
|
||||
* ------------------------------ */
|
||||
|
||||
this.TREE = function() {
|
||||
this.table = new Array(16); /* table of code length counts */
|
||||
this.trans = new Array(288); /* code -> symbol translation table */
|
||||
};
|
||||
|
||||
this.DATA = function(that) {
|
||||
this.source = '';
|
||||
this.sourceIndex = 0;
|
||||
this.tag = 0;
|
||||
this.bitcount = 0;
|
||||
|
||||
this.dest = [];
|
||||
|
||||
this.history = [];
|
||||
|
||||
this.ltree = new that.TREE(); /* dynamic length/symbol tree */
|
||||
this.dtree = new that.TREE(); /* dynamic distance tree */
|
||||
};
|
||||
|
||||
/* --------------------------------------------------- *
|
||||
* -- uninitialized global data (static structures) -- *
|
||||
* --------------------------------------------------- */
|
||||
|
||||
this.sltree = new this.TREE(); /* fixed length/symbol tree */
|
||||
this.sdtree = new this.TREE(); /* fixed distance tree */
|
||||
|
||||
/* extra bits and base tables for length codes */
|
||||
this.length_bits = new Array(30);
|
||||
this.length_base = new Array(30);
|
||||
|
||||
/* extra bits and base tables for distance codes */
|
||||
this.dist_bits = new Array(30);
|
||||
this.dist_base = new Array(30);
|
||||
|
||||
/* special ordering of code length codes */
|
||||
this.clcidx = [
|
||||
16, 17, 18, 0, 8, 7, 9, 6,
|
||||
10, 5, 11, 4, 12, 3, 13, 2,
|
||||
14, 1, 15
|
||||
];
|
||||
|
||||
/* ----------------------- *
|
||||
* -- utility functions -- *
|
||||
* ----------------------- */
|
||||
|
||||
/* build extra bits and base tables */
|
||||
this.build_bits_base = function(bits, base, delta, first)
|
||||
{
|
||||
var i, sum;
|
||||
|
||||
/* build bits table */
|
||||
for (i = 0; i < delta; ++i) bits[i] = 0;
|
||||
for (i = 0; i < 30 - delta; ++i) bits[i + delta] = Math.floor(i / delta);
|
||||
|
||||
/* build base table */
|
||||
for (sum = first, i = 0; i < 30; ++i)
|
||||
{
|
||||
base[i] = sum;
|
||||
sum += 1 << bits[i];
|
||||
}
|
||||
}
|
||||
|
||||
/* build the fixed huffman trees */
|
||||
this.build_fixed_trees = function(lt, dt)
|
||||
{
|
||||
var i;
|
||||
|
||||
/* build fixed length tree */
|
||||
for (i = 0; i < 7; ++i) lt.table[i] = 0;
|
||||
|
||||
lt.table[7] = 24;
|
||||
lt.table[8] = 152;
|
||||
lt.table[9] = 112;
|
||||
|
||||
for (i = 0; i < 24; ++i) lt.trans[i] = 256 + i;
|
||||
for (i = 0; i < 144; ++i) lt.trans[24 + i] = i;
|
||||
for (i = 0; i < 8; ++i) lt.trans[24 + 144 + i] = 280 + i;
|
||||
for (i = 0; i < 112; ++i) lt.trans[24 + 144 + 8 + i] = 144 + i;
|
||||
|
||||
/* build fixed distance tree */
|
||||
for (i = 0; i < 5; ++i) dt.table[i] = 0;
|
||||
|
||||
dt.table[5] = 32;
|
||||
|
||||
for (i = 0; i < 32; ++i) dt.trans[i] = i;
|
||||
}
|
||||
|
||||
/* given an array of code lengths, build a tree */
|
||||
this.build_tree = function(t, lengths, loffset, num)
|
||||
{
|
||||
var offs = new Array(16);
|
||||
var i, sum;
|
||||
|
||||
/* clear code length count table */
|
||||
for (i = 0; i < 16; ++i) t.table[i] = 0;
|
||||
|
||||
/* scan symbol lengths, and sum code length counts */
|
||||
for (i = 0; i < num; ++i) t.table[lengths[loffset + i]]++;
|
||||
|
||||
t.table[0] = 0;
|
||||
|
||||
/* compute offset table for distribution sort */
|
||||
for (sum = 0, i = 0; i < 16; ++i)
|
||||
{
|
||||
offs[i] = sum;
|
||||
sum += t.table[i];
|
||||
}
|
||||
|
||||
/* create code->symbol translation table (symbols sorted by code) */
|
||||
for (i = 0; i < num; ++i)
|
||||
{
|
||||
if (lengths[loffset + i]) t.trans[offs[lengths[loffset + i]]++] = i;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------------------- *
|
||||
* -- decode functions -- *
|
||||
* ---------------------- */
|
||||
|
||||
/* get one bit from source stream */
|
||||
this.getbit = function(d)
|
||||
{
|
||||
var bit;
|
||||
|
||||
/* check if tag is empty */
|
||||
if (!d.bitcount--)
|
||||
{
|
||||
/* load next tag */
|
||||
d.tag = d.source[d.sourceIndex++] & 0xff;
|
||||
d.bitcount = 7;
|
||||
}
|
||||
|
||||
/* shift bit out of tag */
|
||||
bit = d.tag & 0x01;
|
||||
d.tag >>= 1;
|
||||
|
||||
return bit;
|
||||
}
|
||||
|
||||
/* read a num bit value from a stream and add base */
|
||||
function read_bits_direct(source, bitcount, tag, idx, num)
|
||||
{
|
||||
var val = 0;
|
||||
while (bitcount < 24) {
|
||||
tag = tag | (source[idx++] & 0xff) << bitcount;
|
||||
bitcount += 8;
|
||||
}
|
||||
val = tag & (0xffff >> (16 - num));
|
||||
tag >>= num;
|
||||
bitcount -= num;
|
||||
return [bitcount, tag, idx, val];
|
||||
}
|
||||
this.read_bits = function(d, num, base)
|
||||
{
|
||||
if (!num)
|
||||
return base;
|
||||
|
||||
var ret = read_bits_direct(d.source, d.bitcount, d.tag, d.sourceIndex, num);
|
||||
d.bitcount = ret[0];
|
||||
d.tag = ret[1];
|
||||
d.sourceIndex = ret[2];
|
||||
return ret[3] + base;
|
||||
}
|
||||
|
||||
/* given a data stream and a tree, decode a symbol */
|
||||
this.decode_symbol = function(d, t)
|
||||
{
|
||||
while (d.bitcount < 16) {
|
||||
d.tag = d.tag | (d.source[d.sourceIndex++] & 0xff) << d.bitcount;
|
||||
d.bitcount += 8;
|
||||
}
|
||||
|
||||
var sum = 0, cur = 0, len = 0;
|
||||
do {
|
||||
cur = 2 * cur + ((d.tag & (1 << len)) >> len);
|
||||
|
||||
++len;
|
||||
|
||||
sum += t.table[len];
|
||||
cur -= t.table[len];
|
||||
|
||||
} while (cur >= 0);
|
||||
|
||||
d.tag >>= len;
|
||||
d.bitcount -= len;
|
||||
|
||||
return t.trans[sum + cur];
|
||||
}
|
||||
|
||||
/* given a data stream, decode dynamic trees from it */
|
||||
this.decode_trees = function(d, lt, dt)
|
||||
{
|
||||
var code_tree = new this.TREE();
|
||||
var lengths = new Array(288+32);
|
||||
var hlit, hdist, hclen;
|
||||
var i, num, length;
|
||||
|
||||
/* get 5 bits HLIT (257-286) */
|
||||
hlit = this.read_bits(d, 5, 257);
|
||||
|
||||
/* get 5 bits HDIST (1-32) */
|
||||
hdist = this.read_bits(d, 5, 1);
|
||||
|
||||
/* get 4 bits HCLEN (4-19) */
|
||||
hclen = this.read_bits(d, 4, 4);
|
||||
|
||||
for (i = 0; i < 19; ++i) lengths[i] = 0;
|
||||
|
||||
/* read code lengths for code length alphabet */
|
||||
for (i = 0; i < hclen; ++i)
|
||||
{
|
||||
/* get 3 bits code length (0-7) */
|
||||
var clen = this.read_bits(d, 3, 0);
|
||||
|
||||
lengths[this.clcidx[i]] = clen;
|
||||
}
|
||||
|
||||
/* build code length tree */
|
||||
this.build_tree(code_tree, lengths, 0, 19);
|
||||
|
||||
/* decode code lengths for the dynamic trees */
|
||||
for (num = 0; num < hlit + hdist; )
|
||||
{
|
||||
var sym = this.decode_symbol(d, code_tree);
|
||||
|
||||
switch (sym)
|
||||
{
|
||||
case 16:
|
||||
/* copy previous code length 3-6 times (read 2 bits) */
|
||||
{
|
||||
var prev = lengths[num - 1];
|
||||
for (length = this.read_bits(d, 2, 3); length; --length)
|
||||
{
|
||||
lengths[num++] = prev;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 17:
|
||||
/* repeat code length 0 for 3-10 times (read 3 bits) */
|
||||
for (length = this.read_bits(d, 3, 3); length; --length)
|
||||
{
|
||||
lengths[num++] = 0;
|
||||
}
|
||||
break;
|
||||
case 18:
|
||||
/* repeat code length 0 for 11-138 times (read 7 bits) */
|
||||
for (length = this.read_bits(d, 7, 11); length; --length)
|
||||
{
|
||||
lengths[num++] = 0;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
/* values 0-15 represent the actual code lengths */
|
||||
lengths[num++] = sym;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* build dynamic trees */
|
||||
this.build_tree(lt, lengths, 0, hlit);
|
||||
this.build_tree(dt, lengths, hlit, hdist);
|
||||
}
|
||||
|
||||
/* ----------------------------- *
|
||||
* -- block inflate functions -- *
|
||||
* ----------------------------- */
|
||||
|
||||
/* given a stream and two trees, inflate a block of data */
|
||||
this.inflate_block_data = function(d, lt, dt)
|
||||
{
|
||||
// js optimization.
|
||||
var ddest = d.dest;
|
||||
var ddestlength = ddest.length;
|
||||
|
||||
while (1)
|
||||
{
|
||||
var sym = this.decode_symbol(d, lt);
|
||||
|
||||
/* check for end of block */
|
||||
if (sym == 256)
|
||||
{
|
||||
return this.OK;
|
||||
}
|
||||
|
||||
if (sym < 256)
|
||||
{
|
||||
ddest[ddestlength++] = sym; // ? String.fromCharCode(sym);
|
||||
d.history.push(sym);
|
||||
} else {
|
||||
|
||||
var length, dist, offs;
|
||||
var i;
|
||||
|
||||
sym -= 257;
|
||||
|
||||
/* possibly get more bits from length code */
|
||||
length = this.read_bits(d, this.length_bits[sym], this.length_base[sym]);
|
||||
|
||||
dist = this.decode_symbol(d, dt);
|
||||
|
||||
/* possibly get more bits from distance code */
|
||||
offs = d.history.length - this.read_bits(d, this.dist_bits[dist], this.dist_base[dist]);
|
||||
|
||||
if (offs < 0)
|
||||
throw ("Invalid zlib offset " + offs);
|
||||
|
||||
/* copy match */
|
||||
for (i = offs; i < offs + length; ++i) {
|
||||
//ddest[ddestlength++] = ddest[i];
|
||||
ddest[ddestlength++] = d.history[i];
|
||||
d.history.push(d.history[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* inflate an uncompressed block of data */
|
||||
this.inflate_uncompressed_block = function(d)
|
||||
{
|
||||
var length, invlength;
|
||||
var i;
|
||||
|
||||
if (d.bitcount > 7) {
|
||||
var overflow = Math.floor(d.bitcount / 8);
|
||||
d.sourceIndex -= overflow;
|
||||
d.bitcount = 0;
|
||||
d.tag = 0;
|
||||
}
|
||||
|
||||
/* get length */
|
||||
length = d.source[d.sourceIndex+1];
|
||||
length = 256*length + d.source[d.sourceIndex];
|
||||
|
||||
/* get one's complement of length */
|
||||
invlength = d.source[d.sourceIndex+3];
|
||||
invlength = 256*invlength + d.source[d.sourceIndex+2];
|
||||
|
||||
/* check length */
|
||||
if (length != (~invlength & 0x0000ffff)) return this.DATA_ERROR;
|
||||
|
||||
d.sourceIndex += 4;
|
||||
|
||||
/* copy block */
|
||||
for (i = length; i; --i) {
|
||||
d.history.push(d.source[d.sourceIndex]);
|
||||
d.dest[d.dest.length] = d.source[d.sourceIndex++];
|
||||
}
|
||||
|
||||
/* make sure we start next block on a byte boundary */
|
||||
d.bitcount = 0;
|
||||
|
||||
return this.OK;
|
||||
}
|
||||
|
||||
/* inflate a block of data compressed with fixed huffman trees */
|
||||
this.inflate_fixed_block = function(d)
|
||||
{
|
||||
/* decode block using fixed trees */
|
||||
return this.inflate_block_data(d, this.sltree, this.sdtree);
|
||||
}
|
||||
|
||||
/* inflate a block of data compressed with dynamic huffman trees */
|
||||
this.inflate_dynamic_block = function(d)
|
||||
{
|
||||
/* decode trees from stream */
|
||||
this.decode_trees(d, d.ltree, d.dtree);
|
||||
|
||||
/* decode block using decoded trees */
|
||||
return this.inflate_block_data(d, d.ltree, d.dtree);
|
||||
}
|
||||
|
||||
/* ---------------------- *
|
||||
* -- public functions -- *
|
||||
* ---------------------- */
|
||||
|
||||
/* initialize global (static) data */
|
||||
this.init = function()
|
||||
{
|
||||
/* build fixed huffman trees */
|
||||
this.build_fixed_trees(this.sltree, this.sdtree);
|
||||
|
||||
/* build extra bits and base tables */
|
||||
this.build_bits_base(this.length_bits, this.length_base, 4, 3);
|
||||
this.build_bits_base(this.dist_bits, this.dist_base, 2, 1);
|
||||
|
||||
/* fix a special case */
|
||||
this.length_bits[28] = 0;
|
||||
this.length_base[28] = 258;
|
||||
|
||||
this.reset();
|
||||
}
|
||||
|
||||
this.reset = function()
|
||||
{
|
||||
this.d = new this.DATA(this);
|
||||
delete this.header;
|
||||
}
|
||||
|
||||
/* inflate stream from source to dest */
|
||||
this.uncompress = function(source, offset)
|
||||
{
|
||||
|
||||
var d = this.d;
|
||||
var bfinal;
|
||||
|
||||
/* initialise data */
|
||||
d.source = source;
|
||||
d.sourceIndex = offset;
|
||||
d.bitcount = 0;
|
||||
|
||||
d.dest = [];
|
||||
|
||||
// Skip zlib header at start of stream
|
||||
if (typeof this.header == 'undefined') {
|
||||
this.header = this.read_bits(d, 16, 0);
|
||||
/* byte 0: 0x78, 7 = 32k window size, 8 = deflate */
|
||||
/* byte 1: check bits for header and other flags */
|
||||
}
|
||||
|
||||
var blocks = 0;
|
||||
|
||||
do {
|
||||
|
||||
var btype;
|
||||
var res;
|
||||
|
||||
/* read final block flag */
|
||||
bfinal = this.getbit(d);
|
||||
|
||||
/* read block type (2 bits) */
|
||||
btype = this.read_bits(d, 2, 0);
|
||||
|
||||
/* decompress block */
|
||||
switch (btype)
|
||||
{
|
||||
case 0:
|
||||
/* decompress uncompressed block */
|
||||
res = this.inflate_uncompressed_block(d);
|
||||
break;
|
||||
case 1:
|
||||
/* decompress block with fixed huffman trees */
|
||||
res = this.inflate_fixed_block(d);
|
||||
break;
|
||||
case 2:
|
||||
/* decompress block with dynamic huffman trees */
|
||||
res = this.inflate_dynamic_block(d);
|
||||
break;
|
||||
default:
|
||||
return { 'status' : this.DATA_ERROR };
|
||||
}
|
||||
|
||||
if (res != this.OK) return { 'status' : this.DATA_ERROR };
|
||||
blocks++;
|
||||
|
||||
} while (!bfinal && d.sourceIndex < d.source.length);
|
||||
|
||||
d.history = d.history.slice(-this.WINDOW_SIZE);
|
||||
|
||||
return { 'status' : this.OK, 'data' : d.dest };
|
||||
}
|
||||
|
||||
};
|
||||
543
include/keyboard.js
Normal file
@@ -0,0 +1,543 @@
|
||||
var kbdUtil = (function() {
|
||||
"use strict";
|
||||
|
||||
function substituteCodepoint(cp) {
|
||||
// Any Unicode code points which do not have corresponding keysym entries
|
||||
// can be swapped out for another code point by adding them to this table
|
||||
var substitutions = {
|
||||
// {S,s} with comma below -> {S,s} with cedilla
|
||||
0x218 : 0x15e,
|
||||
0x219 : 0x15f,
|
||||
// {T,t} with comma below -> {T,t} with cedilla
|
||||
0x21a : 0x162,
|
||||
0x21b : 0x163
|
||||
};
|
||||
|
||||
var sub = substitutions[cp];
|
||||
return sub ? sub : cp;
|
||||
}
|
||||
|
||||
function isMac() {
|
||||
return navigator && !!(/mac/i).exec(navigator.platform);
|
||||
}
|
||||
function isWindows() {
|
||||
return navigator && !!(/win/i).exec(navigator.platform);
|
||||
}
|
||||
function isLinux() {
|
||||
return navigator && !!(/linux/i).exec(navigator.platform);
|
||||
}
|
||||
|
||||
// Return true if a modifier which is not the specified char modifier (and is not shift) is down
|
||||
function hasShortcutModifier(charModifier, currentModifiers) {
|
||||
var mods = {};
|
||||
for (var key in currentModifiers) {
|
||||
if (parseInt(key) !== XK_Shift_L) {
|
||||
mods[key] = currentModifiers[key];
|
||||
}
|
||||
}
|
||||
|
||||
var sum = 0;
|
||||
for (var k in currentModifiers) {
|
||||
if (mods[k]) {
|
||||
++sum;
|
||||
}
|
||||
}
|
||||
if (hasCharModifier(charModifier, mods)) {
|
||||
return sum > charModifier.length;
|
||||
}
|
||||
else {
|
||||
return sum > 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Return true if the specified char modifier is currently down
|
||||
function hasCharModifier(charModifier, currentModifiers) {
|
||||
if (charModifier.length === 0) { return false; }
|
||||
|
||||
for (var i = 0; i < charModifier.length; ++i) {
|
||||
if (!currentModifiers[charModifier[i]]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Helper object tracking modifier key state
|
||||
// and generates fake key events to compensate if it gets out of sync
|
||||
function ModifierSync(charModifier) {
|
||||
if (!charModifier) {
|
||||
if (isMac()) {
|
||||
// on Mac, Option (AKA Alt) is used as a char modifier
|
||||
charModifier = [XK_Alt_L];
|
||||
}
|
||||
else if (isWindows()) {
|
||||
// on Windows, Ctrl+Alt is used as a char modifier
|
||||
charModifier = [XK_Alt_L, XK_Control_L];
|
||||
}
|
||||
else if (isLinux()) {
|
||||
// on Linux, ISO Level 3 Shift (AltGr) is used as a char modifier
|
||||
charModifier = [XK_ISO_Level3_Shift];
|
||||
}
|
||||
else {
|
||||
charModifier = [];
|
||||
}
|
||||
}
|
||||
|
||||
var state = {};
|
||||
state[XK_Control_L] = false;
|
||||
state[XK_Alt_L] = false;
|
||||
state[XK_ISO_Level3_Shift] = false;
|
||||
state[XK_Shift_L] = false;
|
||||
state[XK_Meta_L] = false;
|
||||
|
||||
function sync(evt, keysym) {
|
||||
var result = [];
|
||||
function syncKey(keysym) {
|
||||
return {keysym: keysyms.lookup(keysym), type: state[keysym] ? 'keydown' : 'keyup'};
|
||||
}
|
||||
|
||||
if (evt.ctrlKey !== undefined &&
|
||||
evt.ctrlKey !== state[XK_Control_L] && keysym !== XK_Control_L) {
|
||||
state[XK_Control_L] = evt.ctrlKey;
|
||||
result.push(syncKey(XK_Control_L));
|
||||
}
|
||||
if (evt.altKey !== undefined &&
|
||||
evt.altKey !== state[XK_Alt_L] && keysym !== XK_Alt_L) {
|
||||
state[XK_Alt_L] = evt.altKey;
|
||||
result.push(syncKey(XK_Alt_L));
|
||||
}
|
||||
if (evt.altGraphKey !== undefined &&
|
||||
evt.altGraphKey !== state[XK_ISO_Level3_Shift] && keysym !== XK_ISO_Level3_Shift) {
|
||||
state[XK_ISO_Level3_Shift] = evt.altGraphKey;
|
||||
result.push(syncKey(XK_ISO_Level3_Shift));
|
||||
}
|
||||
if (evt.shiftKey !== undefined &&
|
||||
evt.shiftKey !== state[XK_Shift_L] && keysym !== XK_Shift_L) {
|
||||
state[XK_Shift_L] = evt.shiftKey;
|
||||
result.push(syncKey(XK_Shift_L));
|
||||
}
|
||||
if (evt.metaKey !== undefined &&
|
||||
evt.metaKey !== state[XK_Meta_L] && keysym !== XK_Meta_L) {
|
||||
state[XK_Meta_L] = evt.metaKey;
|
||||
result.push(syncKey(XK_Meta_L));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
function syncKeyEvent(evt, down) {
|
||||
var obj = getKeysym(evt);
|
||||
var keysym = obj ? obj.keysym : null;
|
||||
|
||||
// first, apply the event itself, if relevant
|
||||
if (keysym !== null && state[keysym] !== undefined) {
|
||||
state[keysym] = down;
|
||||
}
|
||||
return sync(evt, keysym);
|
||||
}
|
||||
|
||||
return {
|
||||
// sync on the appropriate keyboard event
|
||||
keydown: function(evt) { return syncKeyEvent(evt, true);},
|
||||
keyup: function(evt) { return syncKeyEvent(evt, false);},
|
||||
// Call this with a non-keyboard event (such as mouse events) to use its modifier state to synchronize anyway
|
||||
syncAny: function(evt) { return sync(evt);},
|
||||
|
||||
// is a shortcut modifier down?
|
||||
hasShortcutModifier: function() { return hasShortcutModifier(charModifier, state); },
|
||||
// if a char modifier is down, return the keys it consists of, otherwise return null
|
||||
activeCharModifier: function() { return hasCharModifier(charModifier, state) ? charModifier : null; }
|
||||
};
|
||||
}
|
||||
|
||||
// Get a key ID from a keyboard event
|
||||
// May be a string or an integer depending on the available properties
|
||||
function getKey(evt){
|
||||
if ('keyCode' in evt && 'key' in evt) {
|
||||
return evt.key + ':' + evt.keyCode;
|
||||
}
|
||||
else if ('keyCode' in evt) {
|
||||
return evt.keyCode;
|
||||
}
|
||||
else {
|
||||
return evt.key;
|
||||
}
|
||||
}
|
||||
|
||||
// Get the most reliable keysym value we can get from a key event
|
||||
// if char/charCode is available, prefer those, otherwise fall back to key/keyCode/which
|
||||
function getKeysym(evt){
|
||||
var codepoint;
|
||||
if (evt.char && evt.char.length === 1) {
|
||||
codepoint = evt.char.charCodeAt();
|
||||
}
|
||||
else if (evt.charCode) {
|
||||
codepoint = evt.charCode;
|
||||
}
|
||||
else if (evt.keyCode && evt.type === 'keypress') {
|
||||
// IE10 stores the char code as keyCode, and has no other useful properties
|
||||
codepoint = evt.keyCode;
|
||||
}
|
||||
if (codepoint) {
|
||||
var res = keysyms.fromUnicode(substituteCodepoint(codepoint));
|
||||
if (res) {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
// we could check evt.key here.
|
||||
// Legal values are defined in http://www.w3.org/TR/DOM-Level-3-Events/#key-values-list,
|
||||
// so we "just" need to map them to keysym, but AFAIK this is only available in IE10, which also provides evt.key
|
||||
// so we don't *need* it yet
|
||||
if (evt.keyCode) {
|
||||
return keysyms.lookup(keysymFromKeyCode(evt.keyCode, evt.shiftKey));
|
||||
}
|
||||
if (evt.which) {
|
||||
return keysyms.lookup(keysymFromKeyCode(evt.which, evt.shiftKey));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Given a keycode, try to predict which keysym it might be.
|
||||
// If the keycode is unknown, null is returned.
|
||||
function keysymFromKeyCode(keycode, shiftPressed) {
|
||||
if (typeof(keycode) !== 'number') {
|
||||
return null;
|
||||
}
|
||||
// won't be accurate for azerty
|
||||
if (keycode >= 0x30 && keycode <= 0x39) {
|
||||
return keycode; // digit
|
||||
}
|
||||
if (keycode >= 0x41 && keycode <= 0x5a) {
|
||||
// remap to lowercase unless shift is down
|
||||
return shiftPressed ? keycode : keycode + 32; // A-Z
|
||||
}
|
||||
if (keycode >= 0x60 && keycode <= 0x69) {
|
||||
return XK_KP_0 + (keycode - 0x60); // numpad 0-9
|
||||
}
|
||||
|
||||
switch(keycode) {
|
||||
case 0x20: return XK_space;
|
||||
case 0x6a: return XK_KP_Multiply;
|
||||
case 0x6b: return XK_KP_Add;
|
||||
case 0x6c: return XK_KP_Separator;
|
||||
case 0x6d: return XK_KP_Subtract;
|
||||
case 0x6e: return XK_KP_Decimal;
|
||||
case 0x6f: return XK_KP_Divide;
|
||||
case 0xbb: return XK_plus;
|
||||
case 0xbc: return XK_comma;
|
||||
case 0xbd: return XK_minus;
|
||||
case 0xbe: return XK_period;
|
||||
}
|
||||
|
||||
return nonCharacterKey({keyCode: keycode});
|
||||
}
|
||||
|
||||
// if the key is a known non-character key (any key which doesn't generate character data)
|
||||
// return its keysym value. Otherwise return null
|
||||
function nonCharacterKey(evt) {
|
||||
// evt.key not implemented yet
|
||||
if (!evt.keyCode) { return null; }
|
||||
var keycode = evt.keyCode;
|
||||
|
||||
if (keycode >= 0x70 && keycode <= 0x87) {
|
||||
return XK_F1 + keycode - 0x70; // F1-F24
|
||||
}
|
||||
switch (keycode) {
|
||||
|
||||
case 8 : return XK_BackSpace;
|
||||
case 13 : return XK_Return;
|
||||
|
||||
case 9 : return XK_Tab;
|
||||
|
||||
case 27 : return XK_Escape;
|
||||
case 46 : return XK_Delete;
|
||||
|
||||
case 36 : return XK_Home;
|
||||
case 35 : return XK_End;
|
||||
case 33 : return XK_Page_Up;
|
||||
case 34 : return XK_Page_Down;
|
||||
case 45 : return XK_Insert;
|
||||
|
||||
case 37 : return XK_Left;
|
||||
case 38 : return XK_Up;
|
||||
case 39 : return XK_Right;
|
||||
case 40 : return XK_Down;
|
||||
|
||||
case 16 : return XK_Shift_L;
|
||||
case 17 : return XK_Control_L;
|
||||
case 18 : return XK_Alt_L; // also: Option-key on Mac
|
||||
|
||||
case 224 : return XK_Meta_L;
|
||||
case 225 : return XK_ISO_Level3_Shift; // AltGr
|
||||
case 91 : return XK_Super_L; // also: Windows-key
|
||||
case 92 : return XK_Super_R; // also: Windows-key
|
||||
case 93 : return XK_Menu; // also: Windows-Menu, Command on Mac
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
return {
|
||||
hasShortcutModifier : hasShortcutModifier,
|
||||
hasCharModifier : hasCharModifier,
|
||||
ModifierSync : ModifierSync,
|
||||
getKey : getKey,
|
||||
getKeysym : getKeysym,
|
||||
keysymFromKeyCode : keysymFromKeyCode,
|
||||
nonCharacterKey : nonCharacterKey,
|
||||
substituteCodepoint : substituteCodepoint
|
||||
};
|
||||
})();
|
||||
|
||||
// Takes a DOM keyboard event and:
|
||||
// - determines which keysym it represents
|
||||
// - determines a keyId identifying the key that was pressed (corresponding to the key/keyCode properties on the DOM event)
|
||||
// - synthesizes events to synchronize modifier key state between which modifiers are actually down, and which we thought were down
|
||||
// - marks each event with an 'escape' property if a modifier was down which should be "escaped"
|
||||
// - generates a "stall" event in cases where it might be necessary to wait and see if a keypress event follows a keydown
|
||||
// This information is collected into an object which is passed to the next() function. (one call per event)
|
||||
function KeyEventDecoder(modifierState, next) {
|
||||
"use strict";
|
||||
function sendAll(evts) {
|
||||
for (var i = 0; i < evts.length; ++i) {
|
||||
next(evts[i]);
|
||||
}
|
||||
}
|
||||
function process(evt, type) {
|
||||
var result = {type: type};
|
||||
var keyId = kbdUtil.getKey(evt);
|
||||
if (keyId) {
|
||||
result.keyId = keyId;
|
||||
}
|
||||
|
||||
var keysym = kbdUtil.getKeysym(evt);
|
||||
|
||||
var hasModifier = modifierState.hasShortcutModifier() || !!modifierState.activeCharModifier();
|
||||
// Is this a case where we have to decide on the keysym right away, rather than waiting for the keypress?
|
||||
// "special" keys like enter, tab or backspace don't send keypress events,
|
||||
// and some browsers don't send keypresses at all if a modifier is down
|
||||
if (keysym && (type !== 'keydown' || kbdUtil.nonCharacterKey(evt) || hasModifier)) {
|
||||
result.keysym = keysym;
|
||||
}
|
||||
|
||||
var isShift = evt.keyCode === 0x10 || evt.key === 'Shift';
|
||||
|
||||
// Should we prevent the browser from handling the event?
|
||||
// Doing so on a keydown (in most browsers) prevents keypress from being generated
|
||||
// so only do that if we have to.
|
||||
var suppress = !isShift && (type !== 'keydown' || modifierState.hasShortcutModifier() || !!kbdUtil.nonCharacterKey(evt));
|
||||
|
||||
// If a char modifier is down on a keydown, we need to insert a stall,
|
||||
// so VerifyCharModifier knows to wait and see if a keypress is comnig
|
||||
var stall = type === 'keydown' && modifierState.activeCharModifier() && !kbdUtil.nonCharacterKey(evt);
|
||||
|
||||
// if a char modifier is pressed, get the keys it consists of (on Windows, AltGr is equivalent to Ctrl+Alt)
|
||||
var active = modifierState.activeCharModifier();
|
||||
|
||||
// If we have a char modifier down, and we're able to determine a keysym reliably
|
||||
// then (a) we know to treat the modifier as a char modifier,
|
||||
// and (b) we'll have to "escape" the modifier to undo the modifier when sending the char.
|
||||
if (active && keysym) {
|
||||
var isCharModifier = false;
|
||||
for (var i = 0; i < active.length; ++i) {
|
||||
if (active[i] === keysym.keysym) {
|
||||
isCharModifier = true;
|
||||
}
|
||||
}
|
||||
if (type === 'keypress' && !isCharModifier) {
|
||||
result.escape = modifierState.activeCharModifier();
|
||||
}
|
||||
}
|
||||
|
||||
if (stall) {
|
||||
// insert a fake "stall" event
|
||||
next({type: 'stall'});
|
||||
}
|
||||
next(result);
|
||||
|
||||
return suppress;
|
||||
}
|
||||
|
||||
return {
|
||||
keydown: function(evt) {
|
||||
sendAll(modifierState.keydown(evt));
|
||||
return process(evt, 'keydown');
|
||||
},
|
||||
keypress: function(evt) {
|
||||
return process(evt, 'keypress');
|
||||
},
|
||||
keyup: function(evt) {
|
||||
sendAll(modifierState.keyup(evt));
|
||||
return process(evt, 'keyup');
|
||||
},
|
||||
syncModifiers: function(evt) {
|
||||
sendAll(modifierState.syncAny(evt));
|
||||
},
|
||||
releaseAll: function() { next({type: 'releaseall'}); }
|
||||
};
|
||||
}
|
||||
|
||||
// Combines keydown and keypress events where necessary to handle char modifiers.
|
||||
// On some OS'es, a char modifier is sometimes used as a shortcut modifier.
|
||||
// For example, on Windows, AltGr is synonymous with Ctrl-Alt. On a Danish keyboard layout, AltGr-2 yields a @, but Ctrl-Alt-D does nothing
|
||||
// so when used with the '2' key, Ctrl-Alt counts as a char modifier (and should be escaped), but when used with 'D', it does not.
|
||||
// The only way we can distinguish these cases is to wait and see if a keypress event arrives
|
||||
// When we receive a "stall" event, wait a few ms before processing the next keydown. If a keypress has also arrived, merge the two
|
||||
function VerifyCharModifier(next) {
|
||||
"use strict";
|
||||
var queue = [];
|
||||
var timer = null;
|
||||
function process() {
|
||||
if (timer) {
|
||||
return;
|
||||
}
|
||||
|
||||
var delayProcess = function () {
|
||||
clearTimeout(timer);
|
||||
timer = null;
|
||||
process();
|
||||
};
|
||||
|
||||
while (queue.length !== 0) {
|
||||
var cur = queue[0];
|
||||
queue = queue.splice(1);
|
||||
switch (cur.type) {
|
||||
case 'stall':
|
||||
// insert a delay before processing available events.
|
||||
/* jshint loopfunc: true */
|
||||
timer = setTimeout(delayProcess, 5);
|
||||
/* jshint loopfunc: false */
|
||||
return;
|
||||
case 'keydown':
|
||||
// is the next element a keypress? Then we should merge the two
|
||||
if (queue.length !== 0 && queue[0].type === 'keypress') {
|
||||
// Firefox sends keypress even when no char is generated.
|
||||
// so, if keypress keysym is the same as we'd have guessed from keydown,
|
||||
// the modifier didn't have any effect, and should not be escaped
|
||||
if (queue[0].escape && (!cur.keysym || cur.keysym.keysym !== queue[0].keysym.keysym)) {
|
||||
cur.escape = queue[0].escape;
|
||||
}
|
||||
cur.keysym = queue[0].keysym;
|
||||
queue = queue.splice(1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// swallow stall events, and pass all others to the next stage
|
||||
if (cur.type !== 'stall') {
|
||||
next(cur);
|
||||
}
|
||||
}
|
||||
}
|
||||
return function(evt) {
|
||||
queue.push(evt);
|
||||
process();
|
||||
};
|
||||
}
|
||||
|
||||
// Keeps track of which keys we (and the server) believe are down
|
||||
// When a keyup is received, match it against this list, to determine the corresponding keysym(s)
|
||||
// in some cases, a single key may produce multiple keysyms, so the corresponding keyup event must release all of these chars
|
||||
// key repeat events should be merged into a single entry.
|
||||
// Because we can't always identify which entry a keydown or keyup event corresponds to, we sometimes have to guess
|
||||
function TrackKeyState(next) {
|
||||
"use strict";
|
||||
var state = [];
|
||||
|
||||
return function (evt) {
|
||||
var last = state.length !== 0 ? state[state.length-1] : null;
|
||||
|
||||
switch (evt.type) {
|
||||
case 'keydown':
|
||||
// insert a new entry if last seen key was different.
|
||||
if (!last || !evt.keyId || last.keyId !== evt.keyId) {
|
||||
last = {keyId: evt.keyId, keysyms: {}};
|
||||
state.push(last);
|
||||
}
|
||||
if (evt.keysym) {
|
||||
// make sure last event contains this keysym (a single "logical" keyevent
|
||||
// can cause multiple key events to be sent to the VNC server)
|
||||
last.keysyms[evt.keysym.keysym] = evt.keysym;
|
||||
last.ignoreKeyPress = true;
|
||||
next(evt);
|
||||
}
|
||||
break;
|
||||
case 'keypress':
|
||||
if (!last) {
|
||||
last = {keyId: evt.keyId, keysyms: {}};
|
||||
state.push(last);
|
||||
}
|
||||
if (!evt.keysym) {
|
||||
console.log('keypress with no keysym:', evt);
|
||||
}
|
||||
|
||||
// If we didn't expect a keypress, and already sent a keydown to the VNC server
|
||||
// based on the keydown, make sure to skip this event.
|
||||
if (evt.keysym && !last.ignoreKeyPress) {
|
||||
last.keysyms[evt.keysym.keysym] = evt.keysym;
|
||||
evt.type = 'keydown';
|
||||
next(evt);
|
||||
}
|
||||
break;
|
||||
case 'keyup':
|
||||
if (state.length === 0) {
|
||||
return;
|
||||
}
|
||||
var idx = null;
|
||||
// do we have a matching key tracked as being down?
|
||||
for (var i = 0; i !== state.length; ++i) {
|
||||
if (state[i].keyId === evt.keyId) {
|
||||
idx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// if we couldn't find a match (it happens), assume it was the last key pressed
|
||||
if (idx === null) {
|
||||
idx = state.length - 1;
|
||||
}
|
||||
|
||||
var item = state.splice(idx, 1)[0];
|
||||
// for each keysym tracked by this key entry, clone the current event and override the keysym
|
||||
var clone = (function(){
|
||||
function Clone(){}
|
||||
return function (obj) { Clone.prototype=obj; return new Clone(); };
|
||||
}());
|
||||
for (var key in item.keysyms) {
|
||||
var out = clone(evt);
|
||||
out.keysym = item.keysyms[key];
|
||||
next(out);
|
||||
}
|
||||
break;
|
||||
case 'releaseall':
|
||||
/* jshint shadow: true */
|
||||
for (var i = 0; i < state.length; ++i) {
|
||||
for (var key in state[i].keysyms) {
|
||||
var keysym = state[i].keysyms[key];
|
||||
next({keyId: 0, keysym: keysym, type: 'keyup'});
|
||||
}
|
||||
}
|
||||
/* jshint shadow: false */
|
||||
state = [];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Handles "escaping" of modifiers: if a char modifier is used to produce a keysym (such as AltGr-2 to generate an @),
|
||||
// then the modifier must be "undone" before sending the @, and "redone" afterwards.
|
||||
function EscapeModifiers(next) {
|
||||
"use strict";
|
||||
return function(evt) {
|
||||
if (evt.type !== 'keydown' || evt.escape === undefined) {
|
||||
next(evt);
|
||||
return;
|
||||
}
|
||||
// undo modifiers
|
||||
for (var i = 0; i < evt.escape.length; ++i) {
|
||||
next({type: 'keyup', keyId: 0, keysym: keysyms.lookup(evt.escape[i])});
|
||||
}
|
||||
// send the character event
|
||||
next(evt);
|
||||
// redo modifiers
|
||||
/* jshint shadow: true */
|
||||
for (var i = 0; i < evt.escape.length; ++i) {
|
||||
next({type: 'keydown', keyId: 0, keysym: keysyms.lookup(evt.escape[i])});
|
||||
}
|
||||
/* jshint shadow: false */
|
||||
};
|
||||
}
|
||||
378
include/keysym.js
Normal file
@@ -0,0 +1,378 @@
|
||||
var XK_VoidSymbol = 0xffffff, /* Void symbol */
|
||||
|
||||
XK_BackSpace = 0xff08, /* Back space, back char */
|
||||
XK_Tab = 0xff09,
|
||||
XK_Linefeed = 0xff0a, /* Linefeed, LF */
|
||||
XK_Clear = 0xff0b,
|
||||
XK_Return = 0xff0d, /* Return, enter */
|
||||
XK_Pause = 0xff13, /* Pause, hold */
|
||||
XK_Scroll_Lock = 0xff14,
|
||||
XK_Sys_Req = 0xff15,
|
||||
XK_Escape = 0xff1b,
|
||||
XK_Delete = 0xffff, /* Delete, rubout */
|
||||
|
||||
/* Cursor control & motion */
|
||||
|
||||
XK_Home = 0xff50,
|
||||
XK_Left = 0xff51, /* Move left, left arrow */
|
||||
XK_Up = 0xff52, /* Move up, up arrow */
|
||||
XK_Right = 0xff53, /* Move right, right arrow */
|
||||
XK_Down = 0xff54, /* Move down, down arrow */
|
||||
XK_Prior = 0xff55, /* Prior, previous */
|
||||
XK_Page_Up = 0xff55,
|
||||
XK_Next = 0xff56, /* Next */
|
||||
XK_Page_Down = 0xff56,
|
||||
XK_End = 0xff57, /* EOL */
|
||||
XK_Begin = 0xff58, /* BOL */
|
||||
|
||||
|
||||
/* Misc functions */
|
||||
|
||||
XK_Select = 0xff60, /* Select, mark */
|
||||
XK_Print = 0xff61,
|
||||
XK_Execute = 0xff62, /* Execute, run, do */
|
||||
XK_Insert = 0xff63, /* Insert, insert here */
|
||||
XK_Undo = 0xff65,
|
||||
XK_Redo = 0xff66, /* Redo, again */
|
||||
XK_Menu = 0xff67,
|
||||
XK_Find = 0xff68, /* Find, search */
|
||||
XK_Cancel = 0xff69, /* Cancel, stop, abort, exit */
|
||||
XK_Help = 0xff6a, /* Help */
|
||||
XK_Break = 0xff6b,
|
||||
XK_Mode_switch = 0xff7e, /* Character set switch */
|
||||
XK_script_switch = 0xff7e, /* Alias for mode_switch */
|
||||
XK_Num_Lock = 0xff7f,
|
||||
|
||||
/* Keypad functions, keypad numbers cleverly chosen to map to ASCII */
|
||||
|
||||
XK_KP_Space = 0xff80, /* Space */
|
||||
XK_KP_Tab = 0xff89,
|
||||
XK_KP_Enter = 0xff8d, /* Enter */
|
||||
XK_KP_F1 = 0xff91, /* PF1, KP_A, ... */
|
||||
XK_KP_F2 = 0xff92,
|
||||
XK_KP_F3 = 0xff93,
|
||||
XK_KP_F4 = 0xff94,
|
||||
XK_KP_Home = 0xff95,
|
||||
XK_KP_Left = 0xff96,
|
||||
XK_KP_Up = 0xff97,
|
||||
XK_KP_Right = 0xff98,
|
||||
XK_KP_Down = 0xff99,
|
||||
XK_KP_Prior = 0xff9a,
|
||||
XK_KP_Page_Up = 0xff9a
|
||||
XK_KP_Next = 0xff9b,
|
||||
XK_KP_Page_Down = 0xff9b,
|
||||
XK_KP_End = 0xff9c,
|
||||
XK_KP_Begin = 0xff9d,
|
||||
XK_KP_Insert = 0xff9e,
|
||||
XK_KP_Delete = 0xff9f,
|
||||
XK_KP_Equal = 0xffbd, /* Equals */
|
||||
XK_KP_Multiply = 0xffaa,
|
||||
XK_KP_Add = 0xffab,
|
||||
XK_KP_Separator = 0xffac, /* Separator, often comma */
|
||||
XK_KP_Subtract = 0xffad,
|
||||
XK_KP_Decimal = 0xffae,
|
||||
XK_KP_Divide = 0xffaf,
|
||||
|
||||
XK_KP_0 = 0xffb0,
|
||||
XK_KP_1 = 0xffb1,
|
||||
XK_KP_2 = 0xffb2,
|
||||
XK_KP_3 = 0xffb3,
|
||||
XK_KP_4 = 0xffb4,
|
||||
XK_KP_5 = 0xffb5,
|
||||
XK_KP_6 = 0xffb6,
|
||||
XK_KP_7 = 0xffb7,
|
||||
XK_KP_8 = 0xffb8,
|
||||
XK_KP_9 = 0xffb9,
|
||||
|
||||
/*
|
||||
* Auxiliary functions; note the duplicate definitions for left and right
|
||||
* function keys; Sun keyboards and a few other manufacturers have such
|
||||
* function key groups on the left and/or right sides of the keyboard.
|
||||
* We've not found a keyboard with more than 35 function keys total.
|
||||
*/
|
||||
|
||||
XK_F1 = 0xffbe,
|
||||
XK_F2 = 0xffbf,
|
||||
XK_F3 = 0xffc0,
|
||||
XK_F4 = 0xffc1,
|
||||
XK_F5 = 0xffc2,
|
||||
XK_F6 = 0xffc3,
|
||||
XK_F7 = 0xffc4,
|
||||
XK_F8 = 0xffc5,
|
||||
XK_F9 = 0xffc6,
|
||||
XK_F10 = 0xffc7,
|
||||
XK_F11 = 0xffc8,
|
||||
XK_L1 = 0xffc8,
|
||||
XK_F12 = 0xffc9,
|
||||
XK_L2 = 0xffc9,
|
||||
XK_F13 = 0xffca,
|
||||
XK_L3 = 0xffca,
|
||||
XK_F14 = 0xffcb,
|
||||
XK_L4 = 0xffcb,
|
||||
XK_F15 = 0xffcc,
|
||||
XK_L5 = 0xffcc,
|
||||
XK_F16 = 0xffcd,
|
||||
XK_L6 = 0xffcd,
|
||||
XK_F17 = 0xffce,
|
||||
XK_L7 = 0xffce,
|
||||
XK_F18 = 0xffcf,
|
||||
XK_L8 = 0xffcf,
|
||||
XK_F19 = 0xffd0,
|
||||
XK_L9 = 0xffd0,
|
||||
XK_F20 = 0xffd1,
|
||||
XK_L10 = 0xffd1,
|
||||
XK_F21 = 0xffd2,
|
||||
XK_R1 = 0xffd2,
|
||||
XK_F22 = 0xffd3,
|
||||
XK_R2 = 0xffd3,
|
||||
XK_F23 = 0xffd4,
|
||||
XK_R3 = 0xffd4,
|
||||
XK_F24 = 0xffd5,
|
||||
XK_R4 = 0xffd5,
|
||||
XK_F25 = 0xffd6,
|
||||
XK_R5 = 0xffd6,
|
||||
XK_F26 = 0xffd7,
|
||||
XK_R6 = 0xffd7,
|
||||
XK_F27 = 0xffd8,
|
||||
XK_R7 = 0xffd8,
|
||||
XK_F28 = 0xffd9,
|
||||
XK_R8 = 0xffd9,
|
||||
XK_F29 = 0xffda,
|
||||
XK_R9 = 0xffda,
|
||||
XK_F30 = 0xffdb,
|
||||
XK_R10 = 0xffdb,
|
||||
XK_F31 = 0xffdc,
|
||||
XK_R11 = 0xffdc,
|
||||
XK_F32 = 0xffdd,
|
||||
XK_R12 = 0xffdd,
|
||||
XK_F33 = 0xffde,
|
||||
XK_R13 = 0xffde,
|
||||
XK_F34 = 0xffdf,
|
||||
XK_R14 = 0xffdf,
|
||||
XK_F35 = 0xffe0,
|
||||
XK_R15 = 0xffe0,
|
||||
|
||||
/* Modifiers */
|
||||
|
||||
XK_Shift_L = 0xffe1, /* Left shift */
|
||||
XK_Shift_R = 0xffe2, /* Right shift */
|
||||
XK_Control_L = 0xffe3, /* Left control */
|
||||
XK_Control_R = 0xffe4, /* Right control */
|
||||
XK_Caps_Lock = 0xffe5, /* Caps lock */
|
||||
XK_Shift_Lock = 0xffe6, /* Shift lock */
|
||||
|
||||
XK_Meta_L = 0xffe7, /* Left meta */
|
||||
XK_Meta_R = 0xffe8, /* Right meta */
|
||||
XK_Alt_L = 0xffe9, /* Left alt */
|
||||
XK_Alt_R = 0xffea, /* Right alt */
|
||||
XK_Super_L = 0xffeb, /* Left super */
|
||||
XK_Super_R = 0xffec, /* Right super */
|
||||
XK_Hyper_L = 0xffed, /* Left hyper */
|
||||
XK_Hyper_R = 0xffee, /* Right hyper */
|
||||
|
||||
XK_ISO_Level3_Shift = 0xfe03, /* AltGr */
|
||||
|
||||
/*
|
||||
* Latin 1
|
||||
* (ISO/IEC 8859-1 = Unicode U+0020..U+00FF)
|
||||
* Byte 3 = 0
|
||||
*/
|
||||
|
||||
XK_space = 0x0020, /* U+0020 SPACE */
|
||||
XK_exclam = 0x0021, /* U+0021 EXCLAMATION MARK */
|
||||
XK_quotedbl = 0x0022, /* U+0022 QUOTATION MARK */
|
||||
XK_numbersign = 0x0023, /* U+0023 NUMBER SIGN */
|
||||
XK_dollar = 0x0024, /* U+0024 DOLLAR SIGN */
|
||||
XK_percent = 0x0025, /* U+0025 PERCENT SIGN */
|
||||
XK_ampersand = 0x0026, /* U+0026 AMPERSAND */
|
||||
XK_apostrophe = 0x0027, /* U+0027 APOSTROPHE */
|
||||
XK_quoteright = 0x0027, /* deprecated */
|
||||
XK_parenleft = 0x0028, /* U+0028 LEFT PARENTHESIS */
|
||||
XK_parenright = 0x0029, /* U+0029 RIGHT PARENTHESIS */
|
||||
XK_asterisk = 0x002a, /* U+002A ASTERISK */
|
||||
XK_plus = 0x002b, /* U+002B PLUS SIGN */
|
||||
XK_comma = 0x002c, /* U+002C COMMA */
|
||||
XK_minus = 0x002d, /* U+002D HYPHEN-MINUS */
|
||||
XK_period = 0x002e, /* U+002E FULL STOP */
|
||||
XK_slash = 0x002f, /* U+002F SOLIDUS */
|
||||
XK_0 = 0x0030, /* U+0030 DIGIT ZERO */
|
||||
XK_1 = 0x0031, /* U+0031 DIGIT ONE */
|
||||
XK_2 = 0x0032, /* U+0032 DIGIT TWO */
|
||||
XK_3 = 0x0033, /* U+0033 DIGIT THREE */
|
||||
XK_4 = 0x0034, /* U+0034 DIGIT FOUR */
|
||||
XK_5 = 0x0035, /* U+0035 DIGIT FIVE */
|
||||
XK_6 = 0x0036, /* U+0036 DIGIT SIX */
|
||||
XK_7 = 0x0037, /* U+0037 DIGIT SEVEN */
|
||||
XK_8 = 0x0038, /* U+0038 DIGIT EIGHT */
|
||||
XK_9 = 0x0039, /* U+0039 DIGIT NINE */
|
||||
XK_colon = 0x003a, /* U+003A COLON */
|
||||
XK_semicolon = 0x003b, /* U+003B SEMICOLON */
|
||||
XK_less = 0x003c, /* U+003C LESS-THAN SIGN */
|
||||
XK_equal = 0x003d, /* U+003D EQUALS SIGN */
|
||||
XK_greater = 0x003e, /* U+003E GREATER-THAN SIGN */
|
||||
XK_question = 0x003f, /* U+003F QUESTION MARK */
|
||||
XK_at = 0x0040, /* U+0040 COMMERCIAL AT */
|
||||
XK_A = 0x0041, /* U+0041 LATIN CAPITAL LETTER A */
|
||||
XK_B = 0x0042, /* U+0042 LATIN CAPITAL LETTER B */
|
||||
XK_C = 0x0043, /* U+0043 LATIN CAPITAL LETTER C */
|
||||
XK_D = 0x0044, /* U+0044 LATIN CAPITAL LETTER D */
|
||||
XK_E = 0x0045, /* U+0045 LATIN CAPITAL LETTER E */
|
||||
XK_F = 0x0046, /* U+0046 LATIN CAPITAL LETTER F */
|
||||
XK_G = 0x0047, /* U+0047 LATIN CAPITAL LETTER G */
|
||||
XK_H = 0x0048, /* U+0048 LATIN CAPITAL LETTER H */
|
||||
XK_I = 0x0049, /* U+0049 LATIN CAPITAL LETTER I */
|
||||
XK_J = 0x004a, /* U+004A LATIN CAPITAL LETTER J */
|
||||
XK_K = 0x004b, /* U+004B LATIN CAPITAL LETTER K */
|
||||
XK_L = 0x004c, /* U+004C LATIN CAPITAL LETTER L */
|
||||
XK_M = 0x004d, /* U+004D LATIN CAPITAL LETTER M */
|
||||
XK_N = 0x004e, /* U+004E LATIN CAPITAL LETTER N */
|
||||
XK_O = 0x004f, /* U+004F LATIN CAPITAL LETTER O */
|
||||
XK_P = 0x0050, /* U+0050 LATIN CAPITAL LETTER P */
|
||||
XK_Q = 0x0051, /* U+0051 LATIN CAPITAL LETTER Q */
|
||||
XK_R = 0x0052, /* U+0052 LATIN CAPITAL LETTER R */
|
||||
XK_S = 0x0053, /* U+0053 LATIN CAPITAL LETTER S */
|
||||
XK_T = 0x0054, /* U+0054 LATIN CAPITAL LETTER T */
|
||||
XK_U = 0x0055, /* U+0055 LATIN CAPITAL LETTER U */
|
||||
XK_V = 0x0056, /* U+0056 LATIN CAPITAL LETTER V */
|
||||
XK_W = 0x0057, /* U+0057 LATIN CAPITAL LETTER W */
|
||||
XK_X = 0x0058, /* U+0058 LATIN CAPITAL LETTER X */
|
||||
XK_Y = 0x0059, /* U+0059 LATIN CAPITAL LETTER Y */
|
||||
XK_Z = 0x005a, /* U+005A LATIN CAPITAL LETTER Z */
|
||||
XK_bracketleft = 0x005b, /* U+005B LEFT SQUARE BRACKET */
|
||||
XK_backslash = 0x005c, /* U+005C REVERSE SOLIDUS */
|
||||
XK_bracketright = 0x005d, /* U+005D RIGHT SQUARE BRACKET */
|
||||
XK_asciicircum = 0x005e, /* U+005E CIRCUMFLEX ACCENT */
|
||||
XK_underscore = 0x005f, /* U+005F LOW LINE */
|
||||
XK_grave = 0x0060, /* U+0060 GRAVE ACCENT */
|
||||
XK_quoteleft = 0x0060, /* deprecated */
|
||||
XK_a = 0x0061, /* U+0061 LATIN SMALL LETTER A */
|
||||
XK_b = 0x0062, /* U+0062 LATIN SMALL LETTER B */
|
||||
XK_c = 0x0063, /* U+0063 LATIN SMALL LETTER C */
|
||||
XK_d = 0x0064, /* U+0064 LATIN SMALL LETTER D */
|
||||
XK_e = 0x0065, /* U+0065 LATIN SMALL LETTER E */
|
||||
XK_f = 0x0066, /* U+0066 LATIN SMALL LETTER F */
|
||||
XK_g = 0x0067, /* U+0067 LATIN SMALL LETTER G */
|
||||
XK_h = 0x0068, /* U+0068 LATIN SMALL LETTER H */
|
||||
XK_i = 0x0069, /* U+0069 LATIN SMALL LETTER I */
|
||||
XK_j = 0x006a, /* U+006A LATIN SMALL LETTER J */
|
||||
XK_k = 0x006b, /* U+006B LATIN SMALL LETTER K */
|
||||
XK_l = 0x006c, /* U+006C LATIN SMALL LETTER L */
|
||||
XK_m = 0x006d, /* U+006D LATIN SMALL LETTER M */
|
||||
XK_n = 0x006e, /* U+006E LATIN SMALL LETTER N */
|
||||
XK_o = 0x006f, /* U+006F LATIN SMALL LETTER O */
|
||||
XK_p = 0x0070, /* U+0070 LATIN SMALL LETTER P */
|
||||
XK_q = 0x0071, /* U+0071 LATIN SMALL LETTER Q */
|
||||
XK_r = 0x0072, /* U+0072 LATIN SMALL LETTER R */
|
||||
XK_s = 0x0073, /* U+0073 LATIN SMALL LETTER S */
|
||||
XK_t = 0x0074, /* U+0074 LATIN SMALL LETTER T */
|
||||
XK_u = 0x0075, /* U+0075 LATIN SMALL LETTER U */
|
||||
XK_v = 0x0076, /* U+0076 LATIN SMALL LETTER V */
|
||||
XK_w = 0x0077, /* U+0077 LATIN SMALL LETTER W */
|
||||
XK_x = 0x0078, /* U+0078 LATIN SMALL LETTER X */
|
||||
XK_y = 0x0079, /* U+0079 LATIN SMALL LETTER Y */
|
||||
XK_z = 0x007a, /* U+007A LATIN SMALL LETTER Z */
|
||||
XK_braceleft = 0x007b, /* U+007B LEFT CURLY BRACKET */
|
||||
XK_bar = 0x007c, /* U+007C VERTICAL LINE */
|
||||
XK_braceright = 0x007d, /* U+007D RIGHT CURLY BRACKET */
|
||||
XK_asciitilde = 0x007e, /* U+007E TILDE */
|
||||
|
||||
XK_nobreakspace = 0x00a0, /* U+00A0 NO-BREAK SPACE */
|
||||
XK_exclamdown = 0x00a1, /* U+00A1 INVERTED EXCLAMATION MARK */
|
||||
XK_cent = 0x00a2, /* U+00A2 CENT SIGN */
|
||||
XK_sterling = 0x00a3, /* U+00A3 POUND SIGN */
|
||||
XK_currency = 0x00a4, /* U+00A4 CURRENCY SIGN */
|
||||
XK_yen = 0x00a5, /* U+00A5 YEN SIGN */
|
||||
XK_brokenbar = 0x00a6, /* U+00A6 BROKEN BAR */
|
||||
XK_section = 0x00a7, /* U+00A7 SECTION SIGN */
|
||||
XK_diaeresis = 0x00a8, /* U+00A8 DIAERESIS */
|
||||
XK_copyright = 0x00a9, /* U+00A9 COPYRIGHT SIGN */
|
||||
XK_ordfeminine = 0x00aa, /* U+00AA FEMININE ORDINAL INDICATOR */
|
||||
XK_guillemotleft = 0x00ab, /* U+00AB LEFT-POINTING DOUBLE ANGLE QUOTATION MARK */
|
||||
XK_notsign = 0x00ac, /* U+00AC NOT SIGN */
|
||||
XK_hyphen = 0x00ad, /* U+00AD SOFT HYPHEN */
|
||||
XK_registered = 0x00ae, /* U+00AE REGISTERED SIGN */
|
||||
XK_macron = 0x00af, /* U+00AF MACRON */
|
||||
XK_degree = 0x00b0, /* U+00B0 DEGREE SIGN */
|
||||
XK_plusminus = 0x00b1, /* U+00B1 PLUS-MINUS SIGN */
|
||||
XK_twosuperior = 0x00b2, /* U+00B2 SUPERSCRIPT TWO */
|
||||
XK_threesuperior = 0x00b3, /* U+00B3 SUPERSCRIPT THREE */
|
||||
XK_acute = 0x00b4, /* U+00B4 ACUTE ACCENT */
|
||||
XK_mu = 0x00b5, /* U+00B5 MICRO SIGN */
|
||||
XK_paragraph = 0x00b6, /* U+00B6 PILCROW SIGN */
|
||||
XK_periodcentered = 0x00b7, /* U+00B7 MIDDLE DOT */
|
||||
XK_cedilla = 0x00b8, /* U+00B8 CEDILLA */
|
||||
XK_onesuperior = 0x00b9, /* U+00B9 SUPERSCRIPT ONE */
|
||||
XK_masculine = 0x00ba, /* U+00BA MASCULINE ORDINAL INDICATOR */
|
||||
XK_guillemotright = 0x00bb, /* U+00BB RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK */
|
||||
XK_onequarter = 0x00bc, /* U+00BC VULGAR FRACTION ONE QUARTER */
|
||||
XK_onehalf = 0x00bd, /* U+00BD VULGAR FRACTION ONE HALF */
|
||||
XK_threequarters = 0x00be, /* U+00BE VULGAR FRACTION THREE QUARTERS */
|
||||
XK_questiondown = 0x00bf, /* U+00BF INVERTED QUESTION MARK */
|
||||
XK_Agrave = 0x00c0, /* U+00C0 LATIN CAPITAL LETTER A WITH GRAVE */
|
||||
XK_Aacute = 0x00c1, /* U+00C1 LATIN CAPITAL LETTER A WITH ACUTE */
|
||||
XK_Acircumflex = 0x00c2, /* U+00C2 LATIN CAPITAL LETTER A WITH CIRCUMFLEX */
|
||||
XK_Atilde = 0x00c3, /* U+00C3 LATIN CAPITAL LETTER A WITH TILDE */
|
||||
XK_Adiaeresis = 0x00c4, /* U+00C4 LATIN CAPITAL LETTER A WITH DIAERESIS */
|
||||
XK_Aring = 0x00c5, /* U+00C5 LATIN CAPITAL LETTER A WITH RING ABOVE */
|
||||
XK_AE = 0x00c6, /* U+00C6 LATIN CAPITAL LETTER AE */
|
||||
XK_Ccedilla = 0x00c7, /* U+00C7 LATIN CAPITAL LETTER C WITH CEDILLA */
|
||||
XK_Egrave = 0x00c8, /* U+00C8 LATIN CAPITAL LETTER E WITH GRAVE */
|
||||
XK_Eacute = 0x00c9, /* U+00C9 LATIN CAPITAL LETTER E WITH ACUTE */
|
||||
XK_Ecircumflex = 0x00ca, /* U+00CA LATIN CAPITAL LETTER E WITH CIRCUMFLEX */
|
||||
XK_Ediaeresis = 0x00cb, /* U+00CB LATIN CAPITAL LETTER E WITH DIAERESIS */
|
||||
XK_Igrave = 0x00cc, /* U+00CC LATIN CAPITAL LETTER I WITH GRAVE */
|
||||
XK_Iacute = 0x00cd, /* U+00CD LATIN CAPITAL LETTER I WITH ACUTE */
|
||||
XK_Icircumflex = 0x00ce, /* U+00CE LATIN CAPITAL LETTER I WITH CIRCUMFLEX */
|
||||
XK_Idiaeresis = 0x00cf, /* U+00CF LATIN CAPITAL LETTER I WITH DIAERESIS */
|
||||
XK_ETH = 0x00d0, /* U+00D0 LATIN CAPITAL LETTER ETH */
|
||||
XK_Eth = 0x00d0, /* deprecated */
|
||||
XK_Ntilde = 0x00d1, /* U+00D1 LATIN CAPITAL LETTER N WITH TILDE */
|
||||
XK_Ograve = 0x00d2, /* U+00D2 LATIN CAPITAL LETTER O WITH GRAVE */
|
||||
XK_Oacute = 0x00d3, /* U+00D3 LATIN CAPITAL LETTER O WITH ACUTE */
|
||||
XK_Ocircumflex = 0x00d4, /* U+00D4 LATIN CAPITAL LETTER O WITH CIRCUMFLEX */
|
||||
XK_Otilde = 0x00d5, /* U+00D5 LATIN CAPITAL LETTER O WITH TILDE */
|
||||
XK_Odiaeresis = 0x00d6, /* U+00D6 LATIN CAPITAL LETTER O WITH DIAERESIS */
|
||||
XK_multiply = 0x00d7, /* U+00D7 MULTIPLICATION SIGN */
|
||||
XK_Oslash = 0x00d8, /* U+00D8 LATIN CAPITAL LETTER O WITH STROKE */
|
||||
XK_Ooblique = 0x00d8, /* U+00D8 LATIN CAPITAL LETTER O WITH STROKE */
|
||||
XK_Ugrave = 0x00d9, /* U+00D9 LATIN CAPITAL LETTER U WITH GRAVE */
|
||||
XK_Uacute = 0x00da, /* U+00DA LATIN CAPITAL LETTER U WITH ACUTE */
|
||||
XK_Ucircumflex = 0x00db, /* U+00DB LATIN CAPITAL LETTER U WITH CIRCUMFLEX */
|
||||
XK_Udiaeresis = 0x00dc, /* U+00DC LATIN CAPITAL LETTER U WITH DIAERESIS */
|
||||
XK_Yacute = 0x00dd, /* U+00DD LATIN CAPITAL LETTER Y WITH ACUTE */
|
||||
XK_THORN = 0x00de, /* U+00DE LATIN CAPITAL LETTER THORN */
|
||||
XK_Thorn = 0x00de, /* deprecated */
|
||||
XK_ssharp = 0x00df, /* U+00DF LATIN SMALL LETTER SHARP S */
|
||||
XK_agrave = 0x00e0, /* U+00E0 LATIN SMALL LETTER A WITH GRAVE */
|
||||
XK_aacute = 0x00e1, /* U+00E1 LATIN SMALL LETTER A WITH ACUTE */
|
||||
XK_acircumflex = 0x00e2, /* U+00E2 LATIN SMALL LETTER A WITH CIRCUMFLEX */
|
||||
XK_atilde = 0x00e3, /* U+00E3 LATIN SMALL LETTER A WITH TILDE */
|
||||
XK_adiaeresis = 0x00e4, /* U+00E4 LATIN SMALL LETTER A WITH DIAERESIS */
|
||||
XK_aring = 0x00e5, /* U+00E5 LATIN SMALL LETTER A WITH RING ABOVE */
|
||||
XK_ae = 0x00e6, /* U+00E6 LATIN SMALL LETTER AE */
|
||||
XK_ccedilla = 0x00e7, /* U+00E7 LATIN SMALL LETTER C WITH CEDILLA */
|
||||
XK_egrave = 0x00e8, /* U+00E8 LATIN SMALL LETTER E WITH GRAVE */
|
||||
XK_eacute = 0x00e9, /* U+00E9 LATIN SMALL LETTER E WITH ACUTE */
|
||||
XK_ecircumflex = 0x00ea, /* U+00EA LATIN SMALL LETTER E WITH CIRCUMFLEX */
|
||||
XK_ediaeresis = 0x00eb, /* U+00EB LATIN SMALL LETTER E WITH DIAERESIS */
|
||||
XK_igrave = 0x00ec, /* U+00EC LATIN SMALL LETTER I WITH GRAVE */
|
||||
XK_iacute = 0x00ed, /* U+00ED LATIN SMALL LETTER I WITH ACUTE */
|
||||
XK_icircumflex = 0x00ee, /* U+00EE LATIN SMALL LETTER I WITH CIRCUMFLEX */
|
||||
XK_idiaeresis = 0x00ef, /* U+00EF LATIN SMALL LETTER I WITH DIAERESIS */
|
||||
XK_eth = 0x00f0, /* U+00F0 LATIN SMALL LETTER ETH */
|
||||
XK_ntilde = 0x00f1, /* U+00F1 LATIN SMALL LETTER N WITH TILDE */
|
||||
XK_ograve = 0x00f2, /* U+00F2 LATIN SMALL LETTER O WITH GRAVE */
|
||||
XK_oacute = 0x00f3, /* U+00F3 LATIN SMALL LETTER O WITH ACUTE */
|
||||
XK_ocircumflex = 0x00f4, /* U+00F4 LATIN SMALL LETTER O WITH CIRCUMFLEX */
|
||||
XK_otilde = 0x00f5, /* U+00F5 LATIN SMALL LETTER O WITH TILDE */
|
||||
XK_odiaeresis = 0x00f6, /* U+00F6 LATIN SMALL LETTER O WITH DIAERESIS */
|
||||
XK_division = 0x00f7, /* U+00F7 DIVISION SIGN */
|
||||
XK_oslash = 0x00f8, /* U+00F8 LATIN SMALL LETTER O WITH STROKE */
|
||||
XK_ooblique = 0x00f8, /* U+00F8 LATIN SMALL LETTER O WITH STROKE */
|
||||
XK_ugrave = 0x00f9, /* U+00F9 LATIN SMALL LETTER U WITH GRAVE */
|
||||
XK_uacute = 0x00fa, /* U+00FA LATIN SMALL LETTER U WITH ACUTE */
|
||||
XK_ucircumflex = 0x00fb, /* U+00FB LATIN SMALL LETTER U WITH CIRCUMFLEX */
|
||||
XK_udiaeresis = 0x00fc, /* U+00FC LATIN SMALL LETTER U WITH DIAERESIS */
|
||||
XK_yacute = 0x00fd, /* U+00FD LATIN SMALL LETTER Y WITH ACUTE */
|
||||
XK_thorn = 0x00fe, /* U+00FE LATIN SMALL LETTER THORN */
|
||||
XK_ydiaeresis = 0x00ff; /* U+00FF LATIN SMALL LETTER Y WITH DIAERESIS */
|
||||
15
include/keysymdef.js
Normal file
@@ -1,97 +0,0 @@
|
||||
#VNC_controls {
|
||||
overflow: hidden;
|
||||
}
|
||||
#VNC_controls ul {
|
||||
list-style: none;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
#VNC_controls li {
|
||||
float: left;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
#VNC_host {
|
||||
width: 100px;
|
||||
}
|
||||
#VNC_port {
|
||||
width: 50px;
|
||||
}
|
||||
#VNC_password {
|
||||
width: 80px;
|
||||
}
|
||||
#VNC_encrypt {
|
||||
}
|
||||
#VNC_connectTimeout {
|
||||
width: 30px;
|
||||
}
|
||||
#VNC_connect_button {
|
||||
width: 110px;
|
||||
}
|
||||
|
||||
#VNC_status_bar td {
|
||||
margin-top: 15px;
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
}
|
||||
#VNC_status_bar div {
|
||||
font-size: 12px;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
.VNC_status_button {
|
||||
font-size: 10px;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
#VNC_status {
|
||||
text-align: center;
|
||||
}
|
||||
#VNC_settings_menu {
|
||||
display: none;
|
||||
position: absolute;
|
||||
width: 12em;
|
||||
border: 1px solid #888;
|
||||
background-color: #f0f2f6;
|
||||
padding: 5px; margin: 3px;
|
||||
z-index: 100; opacity: 1;
|
||||
text-align: left; white-space: normal;
|
||||
}
|
||||
#VNC_settings_menu ul {
|
||||
list-style: none;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.VNC_buttons_right {
|
||||
text-align: right;
|
||||
}
|
||||
.VNC_buttons_left {
|
||||
text-align: left;
|
||||
}
|
||||
.VNC_status_normal {
|
||||
background: #eee;
|
||||
}
|
||||
.VNC_status_error {
|
||||
background: #f44;
|
||||
}
|
||||
.VNC_status_warn {
|
||||
background: #ff4;
|
||||
}
|
||||
|
||||
/* Do not set width/height for VNC_screen or VNC_canvas or incorrect
|
||||
* scaling will occur. Canvas resizes to remote VNC settings */
|
||||
#VNC_screen {
|
||||
text-align: center;
|
||||
display: table;
|
||||
}
|
||||
#VNC_canvas {
|
||||
background: #eee;
|
||||
}
|
||||
|
||||
#VNC_clipboard_clear_button {
|
||||
}
|
||||
#VNC_clipboard_text {
|
||||
font-size: 9px;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* noVNC: HTML5 VNC client
|
||||
* Copyright (C) 2011 Joel Martin
|
||||
* Licensed under LGPL-3 (see LICENSE.LGPL-3)
|
||||
* Copyright (C) 2012 Joel Martin
|
||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
@@ -79,10 +79,22 @@ queue_next_packet = function () {
|
||||
}
|
||||
};
|
||||
|
||||
var bytes_processed = 0;
|
||||
|
||||
do_packet = function () {
|
||||
//Util.Debug("Processing frame: " + frame_idx);
|
||||
var frame = VNC_frame_data[frame_idx];
|
||||
rfb.recv_message({'data' : frame.slice(frame.indexOf('{', 1) + 1)});
|
||||
var frame = VNC_frame_data[frame_idx],
|
||||
start = frame.indexOf('{', 1) + 1;
|
||||
bytes_processed += frame.length - start;
|
||||
if (VNC_frame_encoding === 'binary') {
|
||||
var u8 = new Uint8Array(frame.length - start);
|
||||
for (var i = 0; i < frame.length - start; i++) {
|
||||
u8[i] = frame.charCodeAt(start + i);
|
||||
}
|
||||
rfb.recv_message({'data' : u8});
|
||||
} else {
|
||||
rfb.recv_message({'data' : frame.slice(start)});
|
||||
}
|
||||
frame_idx += 1;
|
||||
|
||||
queue_next_packet();
|
||||
|
||||
3283
include/rfb.js
1364
include/ui.js
659
include/util.js
@@ -1,14 +1,13 @@
|
||||
/*
|
||||
* noVNC: HTML5 VNC client
|
||||
* Copyright (C) 2011 Joel Martin
|
||||
* Licensed under LGPL-3 (see LICENSE.txt)
|
||||
* Copyright (C) 2012 Joel Martin
|
||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||
*
|
||||
* 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,22 +17,163 @@ 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
|
||||
addFunc(Array, 'map', function (fun /*, thisp*/) {
|
||||
"use strict";
|
||||
var len = this.length;
|
||||
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) {
|
||||
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 () {
|
||||
"use strict";
|
||||
return window.requestAnimationFrame ||
|
||||
window.webkitRequestAnimationFrame ||
|
||||
window.mozRequestAnimationFrame ||
|
||||
window.oRequestAnimationFrame ||
|
||||
window.msRequestAnimationFrame ||
|
||||
function (callback) {
|
||||
window.setTimeout(callback, 1000 / 60);
|
||||
};
|
||||
})();
|
||||
|
||||
/*
|
||||
* ------------------------------------------------------
|
||||
* Namespaced in Util
|
||||
* ------------------------------------------------------
|
||||
@@ -45,6 +185,7 @@ Array.prototype.push32 = function (num) {
|
||||
|
||||
Util._log_level = 'warn';
|
||||
Util.init_logging = function (level) {
|
||||
"use strict";
|
||||
if (typeof level === 'undefined') {
|
||||
level = Util._log_level;
|
||||
} else {
|
||||
@@ -55,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;
|
||||
@@ -82,108 +231,283 @@ 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
|
||||
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;
|
||||
var getter;
|
||||
if (type === 'arr') {
|
||||
getter = function (idx) {
|
||||
if (typeof idx !== 'undefined') {
|
||||
return this['_' + name][idx];
|
||||
} else {
|
||||
val = true;
|
||||
return this['_' + name];
|
||||
}
|
||||
} else if (type in {'integer':1, 'int':1}) {
|
||||
val = parseInt(val, 10);
|
||||
} else if (type === 'func') {
|
||||
if (!val) {
|
||||
val = function () {};
|
||||
}
|
||||
}
|
||||
if (typeof idx !== 'undefined') {
|
||||
cfg[v][idx] = val;
|
||||
} else {
|
||||
cfg[v] = val;
|
||||
}
|
||||
};
|
||||
|
||||
// Set the description
|
||||
api[v + '_description'] = desc;
|
||||
|
||||
// Set the getter function
|
||||
if (typeof api['get_' + v] === 'undefined') {
|
||||
api['get_' + v] = getter;
|
||||
}
|
||||
|
||||
// Set the setter function with extra sanity checks
|
||||
if (typeof api['set_' + v] === 'undefined') {
|
||||
api['set_' + v] = function (val, idx) {
|
||||
if (mode in {'RO':1, 'ro':1}) {
|
||||
throw(v + " is read-only");
|
||||
} else if ((mode in {'WO':1, 'wo':1}) &&
|
||||
(typeof cfg[v] !== 'undefined')) {
|
||||
throw(v + " can only be set once");
|
||||
}
|
||||
setter(val, idx);
|
||||
};
|
||||
} 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);
|
||||
}
|
||||
};
|
||||
} 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
|
||||
}
|
||||
// Coerce existing setting to the right type
|
||||
//Util.Debug("v: " + v + ", defval: " + defval + ", defaults[v]: " + defaults[v]);
|
||||
setter(defval);
|
||||
|
||||
// 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) {
|
||||
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]);
|
||||
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 < 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
|
||||
*/
|
||||
|
||||
// Get DOM element position on page
|
||||
Util.getPosition = function (obj) {
|
||||
var x = 0, y = 0;
|
||||
if (obj.offsetParent) {
|
||||
do {
|
||||
x += obj.offsetLeft;
|
||||
y += obj.offsetTop;
|
||||
obj = obj.offsetParent;
|
||||
} while (obj);
|
||||
}
|
||||
return {'x': x, 'y': y};
|
||||
|
||||
// Dynamically load scripts without using document.write()
|
||||
// Reference: http://unixpapa.com/js/dyna.html
|
||||
//
|
||||
// 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 () {
|
||||
return (typeof INCLUDE_URI !== "undefined") ? INCLUDE_URI : "include/";
|
||||
};
|
||||
Util._loading_scripts = [];
|
||||
Util._pending_scripts = [];
|
||||
Util.load_scripts = function (files) {
|
||||
"use strict";
|
||||
var head = document.getElementsByTagName('head')[0], script,
|
||||
ls = Util._loading_scripts, ps = Util._pending_scripts;
|
||||
|
||||
var loadFunc = function (e) {
|
||||
while (ls.length > 0 && (ls[0].readyState === 'loaded' ||
|
||||
ls[0].readyState === 'complete')) {
|
||||
// For IE, append the script to trigger execution
|
||||
var s = ls.shift();
|
||||
//console.log("loaded script: " + s.src);
|
||||
head.appendChild(s);
|
||||
}
|
||||
if (!this.readyState ||
|
||||
(Util.Engine.presto && this.readyState === 'loaded') ||
|
||||
this.readyState === 'complete') {
|
||||
if (ps.indexOf(this) >= 0) {
|
||||
this.onload = this.onreadystatechange = null;
|
||||
//console.log("completed script: " + this.src);
|
||||
ps.splice(ps.indexOf(this), 1);
|
||||
|
||||
// Call window.onscriptsload after last script loads
|
||||
if (ps.length === 0 && window.onscriptsload) {
|
||||
window.onscriptsload();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
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
|
||||
// appending it which will trigger execution
|
||||
// http://wiki.whatwg.org/wiki/Dynamic_Script_Execution_Order
|
||||
ls.push(script);
|
||||
} else {
|
||||
// For webkit and firefox set async=false and append now
|
||||
// https://developer.mozilla.org/en-US/docs/HTML/Element/script
|
||||
script.async = false;
|
||||
head.appendChild(script);
|
||||
}
|
||||
ps.push(script);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Get DOM element position on page
|
||||
// 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;
|
||||
}
|
||||
|
||||
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);
|
||||
evt = (evt.changedTouches ? evt.changedTouches[0] : evt.touches ? evt.touches[0] : evt);
|
||||
if (evt.pageX || evt.pageY) {
|
||||
docX = evt.pageX;
|
||||
docY = evt.pageY;
|
||||
@@ -197,36 +521,43 @@ Util.getEventPosition = function (e, obj, scale) {
|
||||
if (typeof scale === "undefined") {
|
||||
scale = 1;
|
||||
}
|
||||
return {'x': (docX - pos.x) / scale, 'y': (docY - pos.y) / 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);
|
||||
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){
|
||||
obj.addEventListener(evType, fn, false);
|
||||
} 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; }
|
||||
|
||||
@@ -238,38 +569,88 @@ Util.stopEvent = function(e) {
|
||||
// Set browser engine versions. Based on mootools.
|
||||
Util.Features = {xpath: !!(document.evaluate), air: !!(window.runtime), query: !!(document.querySelector)};
|
||||
|
||||
Util.Engine = {
|
||||
'presto': (function() {
|
||||
return (!window.opera) ? false : ((arguments.callee.caller) ? 960 : ((document.getElementsByClassName) ? 950 : 925)); }()),
|
||||
'trident': (function() {
|
||||
return (!window.ActiveXObject) ? false : ((window.XMLHttpRequest) ? ((document.querySelectorAll) ? 6 : 5) : 4); }()),
|
||||
'webkit': (function() {
|
||||
try { return (navigator.taintEnabled) ? false : ((Util.Features.xpath) ? ((Util.Features.query) ? 525 : 420) : 419); } catch (e) { return false; } }()),
|
||||
//'webkit': (function() {
|
||||
// return ((typeof navigator.taintEnabled !== "unknown") && navigator.taintEnabled) ? false : ((Util.Features.xpath) ? ((Util.Features.query) ? 525 : 420) : 419); }()),
|
||||
'gecko': (function() {
|
||||
return (!document.getBoxObjectFor && window.mozInnerScreenX == null) ? false : ((document.getElementsByClassName) ? 19 : 18); }())
|
||||
};
|
||||
if (Util.Engine.webkit) {
|
||||
// Extract actual webkit version if available
|
||||
Util.Engine.webkit = (function(v) {
|
||||
var re = new RegExp('WebKit/([0-9\.]*) ');
|
||||
v = (navigator.userAgent.match(re) || ['', v])[1];
|
||||
return parseFloat(v, 10);
|
||||
})(Util.Engine.webkit);
|
||||
}
|
||||
(function () {
|
||||
"use strict";
|
||||
// 'presto': (function () { return (!window.opera) ? false : true; }()),
|
||||
var detectPresto = function () {
|
||||
return !!window.opera;
|
||||
};
|
||||
|
||||
Util.Flash = (function(){
|
||||
// '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': detectPresto(),
|
||||
'trident': detectTrident(),
|
||||
'webkit': detectInitialWebkit(),
|
||||
'gecko': detectGecko(),
|
||||
};
|
||||
|
||||
if (Util.Engine.webkit) {
|
||||
// Extract actual webkit version if available
|
||||
Util.Engine.webkit = detectActualWebkit(Util.Engine.webkit);
|
||||
}
|
||||
})();
|
||||
|
||||
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';
|
||||
}
|
||||
}
|
||||
version = v.match(/\d+/g);
|
||||
return {version: parseInt(version[0] || 0 + '.' + version[1], 10) || 0, build: parseInt(version[2], 10) || 0};
|
||||
}());
|
||||
}());
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
* noVNC: HTML5 VNC client
|
||||
* Copyright (C) 2011 Joel Martin
|
||||
* Licensed under LGPL-3 (see LICENSE.txt)
|
||||
*
|
||||
* See README.md for usage and integration instructions.
|
||||
*/
|
||||
|
||||
/*jslint evil: true */
|
||||
/*global window, document, INCLUDE_URI */
|
||||
|
||||
/*
|
||||
* Load supporting scripts
|
||||
*/
|
||||
function get_INCLUDE_URI() {
|
||||
return (typeof INCLUDE_URI !== "undefined") ? INCLUDE_URI : "include/";
|
||||
}
|
||||
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
var extra = "", start, end;
|
||||
|
||||
start = "<script src='" + get_INCLUDE_URI();
|
||||
end = "'><\/script>";
|
||||
|
||||
// Uncomment to activate firebug lite
|
||||
//extra += "<script src='http://getfirebug.com/releases/lite/1.2/" +
|
||||
// "firebug-lite-compressed.js'><\/script>";
|
||||
|
||||
extra += start + "util.js" + end;
|
||||
extra += start + "webutil.js" + end;
|
||||
extra += start + "logo.js" + end;
|
||||
extra += start + "base64.js" + end;
|
||||
extra += start + "websock.js" + end;
|
||||
extra += start + "des.js" + end;
|
||||
extra += start + "input.js" + end;
|
||||
extra += start + "display.js" + end;
|
||||
extra += start + "rfb.js" + end;
|
||||
|
||||
document.write(extra);
|
||||
}());
|
||||
|
||||
1
include/web-socket-js-project
Submodule
@@ -1,49 +1,69 @@
|
||||
// Copyright: Hiroshi Ichikawa <http://gimite.net/en/>
|
||||
// License: New BSD License
|
||||
// Reference: http://dev.w3.org/html5/websockets/
|
||||
// Reference: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol
|
||||
// Reference: http://tools.ietf.org/html/rfc6455
|
||||
|
||||
(function() {
|
||||
|
||||
if (window.WebSocket) return;
|
||||
|
||||
var console = window.console;
|
||||
if (!console || !console.log || !console.error) {
|
||||
console = {log: function(){ }, error: function(){ }};
|
||||
if (window.WEB_SOCKET_FORCE_FLASH) {
|
||||
// Keeps going.
|
||||
} else if (window.WebSocket) {
|
||||
return;
|
||||
} else if (window.MozWebSocket) {
|
||||
// Firefox.
|
||||
window.WebSocket = MozWebSocket;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!swfobject.hasFlashPlayerVersion("10.0.0")) {
|
||||
console.error("Flash Player >= 10.0.0 is required.");
|
||||
var logger;
|
||||
if (window.WEB_SOCKET_LOGGER) {
|
||||
logger = WEB_SOCKET_LOGGER;
|
||||
} else if (window.console && window.console.log && window.console.error) {
|
||||
// In some environment, console is defined but console.log or console.error is missing.
|
||||
logger = window.console;
|
||||
} else {
|
||||
logger = {log: function(){ }, error: function(){ }};
|
||||
}
|
||||
|
||||
// swfobject.hasFlashPlayerVersion("10.0.0") doesn't work with Gnash.
|
||||
if (swfobject.getFlashPlayerVersion().major < 10) {
|
||||
logger.error("Flash Player >= 10.0.0 is required.");
|
||||
return;
|
||||
}
|
||||
if (location.protocol == "file:") {
|
||||
console.error(
|
||||
logger.error(
|
||||
"WARNING: web-socket-js doesn't work in file:///... URL " +
|
||||
"unless you set Flash Security Settings properly. " +
|
||||
"Open the page via Web server i.e. http://...");
|
||||
}
|
||||
|
||||
/**
|
||||
* This class represents a faux web socket.
|
||||
* Our own implementation of WebSocket class using Flash.
|
||||
* @param {string} url
|
||||
* @param {string} protocol
|
||||
* @param {array or string} protocols
|
||||
* @param {string} proxyHost
|
||||
* @param {int} proxyPort
|
||||
* @param {string} headers
|
||||
*/
|
||||
WebSocket = function(url, protocol, proxyHost, proxyPort, headers) {
|
||||
window.WebSocket = function(url, protocols, proxyHost, proxyPort, headers) {
|
||||
var self = this;
|
||||
self.__id = WebSocket.__nextId++;
|
||||
WebSocket.__instances[self.__id] = self;
|
||||
self.readyState = WebSocket.CONNECTING;
|
||||
self.bufferedAmount = 0;
|
||||
self.__events = {};
|
||||
if (!protocols) {
|
||||
protocols = [];
|
||||
} else if (typeof protocols == "string") {
|
||||
protocols = [protocols];
|
||||
}
|
||||
// Uses setTimeout() to make sure __createFlash() runs after the caller sets ws.onopen etc.
|
||||
// Otherwise, when onopen fires immediately, onopen is called before it is set.
|
||||
setTimeout(function() {
|
||||
self.__createTask = setTimeout(function() {
|
||||
WebSocket.__addTask(function() {
|
||||
self.__createTask = null;
|
||||
WebSocket.__flash.create(
|
||||
self.__id, url, protocol, proxyHost || null, proxyPort || 0, headers || null);
|
||||
self.__id, url, protocols, proxyHost || null, proxyPort || 0, headers || null);
|
||||
});
|
||||
}, 0);
|
||||
};
|
||||
@@ -78,6 +98,12 @@
|
||||
* Close this web socket gracefully.
|
||||
*/
|
||||
WebSocket.prototype.close = function() {
|
||||
if (this.__createTask) {
|
||||
clearTimeout(this.__createTask);
|
||||
this.__createTask = null;
|
||||
this.readyState = WebSocket.CLOSED;
|
||||
return;
|
||||
}
|
||||
if (this.readyState == WebSocket.CLOSED || this.readyState == WebSocket.CLOSING) {
|
||||
return;
|
||||
}
|
||||
@@ -131,7 +157,7 @@
|
||||
events[i](event);
|
||||
}
|
||||
var handler = this["on" + event.type];
|
||||
if (handler) handler(event);
|
||||
if (handler) handler.apply(this, [event]);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -139,16 +165,22 @@
|
||||
* @param {Object} flashEvent
|
||||
*/
|
||||
WebSocket.prototype.__handleEvent = function(flashEvent) {
|
||||
|
||||
if ("readyState" in flashEvent) {
|
||||
this.readyState = flashEvent.readyState;
|
||||
}
|
||||
if ("protocol" in flashEvent) {
|
||||
this.protocol = flashEvent.protocol;
|
||||
}
|
||||
|
||||
var jsEvent;
|
||||
if (flashEvent.type == "open" || flashEvent.type == "error") {
|
||||
jsEvent = this.__createSimpleEvent(flashEvent.type);
|
||||
} else if (flashEvent.type == "close") {
|
||||
// TODO implement jsEvent.wasClean
|
||||
jsEvent = this.__createSimpleEvent("close");
|
||||
jsEvent.wasClean = flashEvent.wasClean ? true : false;
|
||||
jsEvent.code = flashEvent.code;
|
||||
jsEvent.reason = flashEvent.reason;
|
||||
} else if (flashEvent.type == "message") {
|
||||
var data = decodeURIComponent(flashEvent.message);
|
||||
jsEvent = this.__createMessageEvent("message", data);
|
||||
@@ -157,6 +189,7 @@
|
||||
}
|
||||
|
||||
this.dispatchEvent(jsEvent);
|
||||
|
||||
};
|
||||
|
||||
WebSocket.prototype.__createSimpleEvent = function(type) {
|
||||
@@ -188,6 +221,9 @@
|
||||
WebSocket.CLOSING = 2;
|
||||
WebSocket.CLOSED = 3;
|
||||
|
||||
// Field to check implementation of WebSocket.
|
||||
WebSocket.__isFlashImplementation = true;
|
||||
WebSocket.__initialized = false;
|
||||
WebSocket.__flash = null;
|
||||
WebSocket.__instances = {};
|
||||
WebSocket.__tasks = [];
|
||||
@@ -207,16 +243,31 @@
|
||||
* Loads WebSocketMain.swf and creates WebSocketMain object in Flash.
|
||||
*/
|
||||
WebSocket.__initialize = function() {
|
||||
if (WebSocket.__flash) return;
|
||||
|
||||
if (WebSocket.__initialized) return;
|
||||
WebSocket.__initialized = true;
|
||||
|
||||
if (WebSocket.__swfLocation) {
|
||||
// For backword compatibility.
|
||||
window.WEB_SOCKET_SWF_LOCATION = WebSocket.__swfLocation;
|
||||
}
|
||||
if (!window.WEB_SOCKET_SWF_LOCATION) {
|
||||
console.error("[WebSocket] set WEB_SOCKET_SWF_LOCATION to location of WebSocketMain.swf");
|
||||
logger.error("[WebSocket] set WEB_SOCKET_SWF_LOCATION to location of WebSocketMain.swf");
|
||||
return;
|
||||
}
|
||||
if (!window.WEB_SOCKET_SUPPRESS_CROSS_DOMAIN_SWF_ERROR &&
|
||||
!WEB_SOCKET_SWF_LOCATION.match(/(^|\/)WebSocketMainInsecure\.swf(\?.*)?$/) &&
|
||||
WEB_SOCKET_SWF_LOCATION.match(/^\w+:\/\/([^\/]+)/)) {
|
||||
var swfHost = RegExp.$1;
|
||||
if (location.host != swfHost) {
|
||||
logger.error(
|
||||
"[WebSocket] You must host HTML and WebSocketMain.swf in the same host " +
|
||||
"('" + location.host + "' != '" + swfHost + "'). " +
|
||||
"See also 'How to host HTML file and SWF file in different domains' section " +
|
||||
"in README.md. If you use WebSocketMainInsecure.swf, you can suppress this message " +
|
||||
"by WEB_SOCKET_SUPPRESS_CROSS_DOMAIN_SWF_ERROR = true;");
|
||||
}
|
||||
}
|
||||
var container = document.createElement("div");
|
||||
container.id = "webSocketContainer";
|
||||
// Hides Flash box. We cannot use display: none or visibility: hidden because it prevents
|
||||
@@ -250,9 +301,11 @@
|
||||
null,
|
||||
function(e) {
|
||||
if (!e.success) {
|
||||
console.error("[WebSocket] swfobject.embedSWF failed");
|
||||
logger.error("[WebSocket] swfobject.embedSWF failed");
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -287,7 +340,7 @@
|
||||
WebSocket.__instances[events[i].webSocketId].__handleEvent(events[i]);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
logger.error(e);
|
||||
}
|
||||
}, 0);
|
||||
return true;
|
||||
@@ -295,12 +348,12 @@
|
||||
|
||||
// Called by Flash.
|
||||
WebSocket.__log = function(message) {
|
||||
console.log(decodeURIComponent(message));
|
||||
logger.log(decodeURIComponent(message));
|
||||
};
|
||||
|
||||
// Called by Flash.
|
||||
WebSocket.__error = function(message) {
|
||||
console.error(decodeURIComponent(message));
|
||||
logger.error(decodeURIComponent(message));
|
||||
};
|
||||
|
||||
WebSocket.__addTask = function(task) {
|
||||
@@ -327,15 +380,12 @@
|
||||
};
|
||||
|
||||
if (!window.WEB_SOCKET_DISABLE_AUTO_INITIALIZATION) {
|
||||
if (window.addEventListener) {
|
||||
window.addEventListener("load", function(){
|
||||
WebSocket.__initialize();
|
||||
}, false);
|
||||
} else {
|
||||
window.attachEvent("onload", function(){
|
||||
WebSocket.__initialize();
|
||||
});
|
||||
}
|
||||
// NOTE:
|
||||
// This fires immediately if web_socket.js is dynamically loaded after
|
||||
// the document is loaded.
|
||||
swfobject.addDomLoadEvent(function() {
|
||||
WebSocket.__initialize();
|
||||
});
|
||||
}
|
||||
|
||||
})();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Websock: high-performance binary WebSockets
|
||||
* Copyright (C) 2011 Joel Martin
|
||||
* Licensed under LGPL-3 (see LICENSE.txt)
|
||||
* Copyright (C) 2012 Joel Martin
|
||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||
*
|
||||
* Websock is similar to the standard WebSocket object but Websock
|
||||
* enables communication with raw TCP sockets (i.e. the binary stream)
|
||||
@@ -14,331 +14,371 @@
|
||||
* read binary data off of the receive queue.
|
||||
*/
|
||||
|
||||
/*jslint browser: true, bitwise: true */
|
||||
/*global Util, Base64 */
|
||||
|
||||
|
||||
// Load Flash WebSocket emulator if needed
|
||||
|
||||
if (window.WebSocket) {
|
||||
// To force WebSocket emulator even when native WebSocket available
|
||||
//window.WEB_SOCKET_FORCE_FLASH = true;
|
||||
// To enable WebSocket emulator debug:
|
||||
//window.WEB_SOCKET_DEBUG=1;
|
||||
|
||||
if (window.WebSocket && !window.WEB_SOCKET_FORCE_FLASH) {
|
||||
Websock_native = true;
|
||||
} else if (window.MozWebSocket && !window.WEB_SOCKET_FORCE_FLASH) {
|
||||
Websock_native = true;
|
||||
window.WebSocket = window.MozWebSocket;
|
||||
} else {
|
||||
/* no builtin WebSocket so load web_socket.js */
|
||||
|
||||
Websock_native = false;
|
||||
(function () {
|
||||
function get_INCLUDE_URI() {
|
||||
return (typeof INCLUDE_URI !== "undefined") ?
|
||||
INCLUDE_URI : "include/";
|
||||
}
|
||||
|
||||
var start = "<script src='" + get_INCLUDE_URI(),
|
||||
end = "'><\/script>", extra = "";
|
||||
|
||||
WEB_SOCKET_SWF_LOCATION = get_INCLUDE_URI() +
|
||||
window.WEB_SOCKET_SWF_LOCATION = Util.get_include_uri() +
|
||||
"web-socket-js/WebSocketMain.swf";
|
||||
if (Util.Engine.trident) {
|
||||
Util.Debug("Forcing uncached load of WebSocketMain.swf");
|
||||
WEB_SOCKET_SWF_LOCATION += "?" + Math.random();
|
||||
window.WEB_SOCKET_SWF_LOCATION += "?" + Math.random();
|
||||
}
|
||||
extra += start + "web-socket-js/swfobject.js" + end;
|
||||
extra += start + "web-socket-js/web_socket.js" + end;
|
||||
document.write(extra);
|
||||
}());
|
||||
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
|
||||
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;
|
||||
|
||||
test_mode = false;
|
||||
|
||||
|
||||
//
|
||||
// Queue public functions
|
||||
//
|
||||
|
||||
function get_sQ() {
|
||||
return sQ;
|
||||
this._eventHandlers = {
|
||||
'message': function () {},
|
||||
'open': function () {},
|
||||
'close': function () {},
|
||||
'error': function () {}
|
||||
};
|
||||
}
|
||||
|
||||
function get_rQ() {
|
||||
return rQ;
|
||||
}
|
||||
function get_rQi() {
|
||||
return rQi;
|
||||
}
|
||||
function set_rQi(val) {
|
||||
rQi = val;
|
||||
};
|
||||
(function () {
|
||||
"use strict";
|
||||
Websock.prototype = {
|
||||
// Getters and Setters
|
||||
get_sQ: function () {
|
||||
return this._sQ;
|
||||
},
|
||||
|
||||
function rQlen() {
|
||||
return rQ.length - rQi;
|
||||
}
|
||||
get_rQ: function () {
|
||||
return this._rQ;
|
||||
},
|
||||
|
||||
function rQpeek8() {
|
||||
return (rQ[rQi] );
|
||||
}
|
||||
function rQshift8() {
|
||||
return (rQ[rQi++] );
|
||||
}
|
||||
function rQunshift8(num) {
|
||||
if (rQi === 0) {
|
||||
rQ.unshift(num);
|
||||
} else {
|
||||
rQi -= 1;
|
||||
rQ[rQi] = num;
|
||||
}
|
||||
get_rQi: function () {
|
||||
return this._rQi;
|
||||
},
|
||||
|
||||
}
|
||||
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) {
|
||||
var arr = rQ.slice(rQi, rQi + len);
|
||||
rQi += len;
|
||||
return arr.map(function (num) {
|
||||
return String.fromCharCode(num); } ).join('');
|
||||
set_rQi: function (val) {
|
||||
this._rQi = val;
|
||||
},
|
||||
|
||||
}
|
||||
function rQshiftBytes(len) {
|
||||
rQi += len;
|
||||
return rQ.slice(rQi-len, rQi);
|
||||
}
|
||||
// Receive Queue
|
||||
rQlen: function () {
|
||||
return this._rQ.length - this._rQi;
|
||||
},
|
||||
|
||||
function rQslice(start, end) {
|
||||
if (end) {
|
||||
return rQ.slice(rQi + start, rQi + end);
|
||||
} else {
|
||||
return rQ.slice(rQi + start);
|
||||
}
|
||||
}
|
||||
rQpeek8: function () {
|
||||
return this._rQ[this._rQi];
|
||||
},
|
||||
|
||||
// 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
|
||||
if (rQlen < num) {
|
||||
if (goback) {
|
||||
if (rQi < goback) {
|
||||
throw("rQwait cannot backup " + goback + " bytes");
|
||||
rQshift8: function () {
|
||||
return this._rQ[this._rQi++];
|
||||
},
|
||||
|
||||
rQskip8: function () {
|
||||
this._rQi++;
|
||||
},
|
||||
|
||||
rQskipBytes: function (num) {
|
||||
this._rQi += num;
|
||||
},
|
||||
|
||||
rQunshift8: function (num) {
|
||||
if (this._rQi === 0) {
|
||||
this._rQ.unshift(num);
|
||||
} else {
|
||||
this._rQi--;
|
||||
this._rQ[this._rQi] = num;
|
||||
}
|
||||
rQi -= goback;
|
||||
}
|
||||
//Util.Debug(" waiting for " + (num-rQlen) +
|
||||
// " " + msg + " byte(s)");
|
||||
return true; // true means need more data
|
||||
}
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
//
|
||||
// Private utility routines
|
||||
//
|
||||
rQshift16: function () {
|
||||
return (this._rQ[this._rQi++] << 8) +
|
||||
this._rQ[this._rQi++];
|
||||
},
|
||||
|
||||
function encode_message() {
|
||||
/* base64 encode */
|
||||
return Base64.encode(sQ);
|
||||
}
|
||||
rQshift32: function () {
|
||||
return (this._rQ[this._rQi++] << 24) +
|
||||
(this._rQ[this._rQi++] << 16) +
|
||||
(this._rQ[this._rQi++] << 8) +
|
||||
this._rQ[this._rQi++];
|
||||
},
|
||||
|
||||
function decode_message(data) {
|
||||
//Util.Debug(">> decode_message: " + data);
|
||||
/* base64 decode */
|
||||
rQ = rQ.concat(Base64.decode(data, 0));
|
||||
//Util.Debug(">> decode_message, rQ: " + rQ);
|
||||
}
|
||||
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);
|
||||
},
|
||||
|
||||
rQshiftBytes: function (len) {
|
||||
if (typeof(len) === 'undefined') { len = this.rQlen(); }
|
||||
this._rQi += len;
|
||||
return this._rQ.slice(this._rQi - len, this._rQi);
|
||||
},
|
||||
|
||||
//
|
||||
// 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);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// overridable for testing
|
||||
function send(arr) {
|
||||
//Util.Debug(">> send_array: " + arr);
|
||||
sQ = sQ.concat(arr);
|
||||
return flush();
|
||||
}
|
||||
|
||||
function send_string(str) {
|
||||
//Util.Debug(">> send_string: " + str);
|
||||
api.send(str.split('').map(
|
||||
function (chr) { return chr.charCodeAt(0); } ) );
|
||||
}
|
||||
|
||||
//
|
||||
// Other public functions
|
||||
|
||||
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;
|
||||
rQslice: function (start, end) {
|
||||
if (end) {
|
||||
return this._rQ.slice(this._rQi + start, this._rQi + end);
|
||||
} else {
|
||||
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.
|
||||
rQwait: function (msg, num, goback) {
|
||||
var rQlen = this._rQ.length - this._rQi; // Skip rQlen() function call
|
||||
if (rQlen < num) {
|
||||
if (goback) {
|
||||
if (this._rQi < goback) {
|
||||
throw new Error("rQwait cannot backup " + goback + " bytes");
|
||||
}
|
||||
this._rQi -= goback;
|
||||
}
|
||||
return true; // true means need more data
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
// Send Queue
|
||||
|
||||
flush: function () {
|
||||
if (this._websocket.bufferedAmount !== 0) {
|
||||
Util.Debug("bufferedAmount: " + this._websocket.bufferedAmount);
|
||||
}
|
||||
|
||||
if (this._websocket.bufferedAmount < this.maxBufferedAmount) {
|
||||
if (this._sQ.length > 0) {
|
||||
this._websocket.send(this._encode_message());
|
||||
this._sQ = [];
|
||||
}
|
||||
|
||||
return true;
|
||||
} else {
|
||||
Util.Info("Delaying send, bufferedAmount: " +
|
||||
this._websocket.bufferedAmount);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
send: function (arr) {
|
||||
this._sQ = this._sQ.concat(arr);
|
||||
return this.flush();
|
||||
},
|
||||
|
||||
send_string: function (str) {
|
||||
this.send(str.split('').map(function (chr) {
|
||||
return chr.charCodeAt(0);
|
||||
}));
|
||||
},
|
||||
|
||||
// Event Handlers
|
||||
on: function (evt, handler) {
|
||||
this._eventHandlers[evt] = handler;
|
||||
},
|
||||
|
||||
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
|
||||
// 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 WebSocket.prototype ||
|
||||
!!(new WebSocket(ws_schema + '://.').binaryType))) {
|
||||
Util.Info("Detected binaryType support in WebSockets");
|
||||
wsbt = true;
|
||||
}
|
||||
} catch (exc) {
|
||||
// Just ignore failed test localhost connection
|
||||
}
|
||||
|
||||
// Default protocols if not specified
|
||||
if (typeof(protocols) === "undefined") {
|
||||
if (wsbt) {
|
||||
protocols = ['binary', 'base64'];
|
||||
} else {
|
||||
protocols = 'base64';
|
||||
}
|
||||
}
|
||||
|
||||
if (!wsbt) {
|
||||
if (protocols === 'binary') {
|
||||
throw new Error('WebSocket binary sub-protocol requested but not supported');
|
||||
}
|
||||
|
||||
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');
|
||||
} else {
|
||||
new_protocols.push(protocols[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (new_protocols.length > 0) {
|
||||
protocols = new_protocols;
|
||||
} else {
|
||||
throw new Error("Only WebSocket binary sub-protocol was requested and is not supported.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 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 (protocols.indexOf('binary') >= 0) {
|
||||
this._websocket.binaryType = 'arraybuffer';
|
||||
}
|
||||
|
||||
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 {
|
||||
this._mode = 'base64';
|
||||
Util.Error('Server select no sub-protocol!: ' + this._websocket.protocol);
|
||||
}
|
||||
this._eventHandlers.open();
|
||||
Util.Debug("<< WebSock.onopen");
|
||||
}).bind(this);
|
||||
this._websocket.onclose = (function (e) {
|
||||
Util.Debug(">> WebSock.onclose");
|
||||
this._eventHandlers.close(e);
|
||||
Util.Debug("<< WebSock.onclose");
|
||||
}).bind(this);
|
||||
this._websocket.onerror = (function (e) {
|
||||
Util.Debug(">> WebSock.onerror: " + e);
|
||||
this._eventHandlers.error(e);
|
||||
Util.Debug("<< WebSock.onerror: " + e);
|
||||
}).bind(this);
|
||||
},
|
||||
|
||||
close: function () {
|
||||
if (this._websocket) {
|
||||
if ((this._websocket.readyState === WebSocket.OPEN) ||
|
||||
(this._websocket.readyState === WebSocket.CONNECTING)) {
|
||||
Util.Info("Closing WebSocket connection");
|
||||
this._websocket.close();
|
||||
}
|
||||
|
||||
this._websocket.onmessage = function (e) { return; };
|
||||
}
|
||||
},
|
||||
|
||||
// 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);
|
||||
}
|
||||
},
|
||||
|
||||
_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));
|
||||
}
|
||||
},
|
||||
|
||||
_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";
|
||||
}
|
||||
|
||||
if (typeof exc.description !== 'undefined') {
|
||||
exception_str += " description: " + exc.description + "\n";
|
||||
}
|
||||
|
||||
if (typeof exc.stack !== 'undefined') {
|
||||
exception_str += exc.stack;
|
||||
}
|
||||
|
||||
if (exception_str.length > 0) {
|
||||
Util.Error("recv_message, caught exception: " + exception_str);
|
||||
} else {
|
||||
Util.Error("recv_message, caught exception: " + exc);
|
||||
}
|
||||
|
||||
if (typeof exc.name !== 'undefined') {
|
||||
this._eventHandlers.error(exc.name + ": " + exc.message);
|
||||
} else {
|
||||
this._eventHandlers.error(exc);
|
||||
}
|
||||
}
|
||||
} 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() {
|
||||
rQ = [];
|
||||
rQi = 0;
|
||||
sQ = [];
|
||||
websocket = null;
|
||||
}
|
||||
|
||||
function open(uri) {
|
||||
init();
|
||||
|
||||
if (test_mode) {
|
||||
websocket = {};
|
||||
} else {
|
||||
websocket = new WebSocket(uri, 'base64');
|
||||
// TODO: future native binary support
|
||||
//websocket = new WebSocket(uri, ['binary', 'base64']);
|
||||
}
|
||||
|
||||
websocket.onmessage = recv_message;
|
||||
websocket.onopen = function() {
|
||||
Util.Debug(">> WebSock.onopen");
|
||||
if (websocket.protocol) {
|
||||
Util.Info("Server chose sub-protocol: " + websocket.protocol);
|
||||
}
|
||||
eventHandlers.open();
|
||||
Util.Debug("<< WebSock.onopen");
|
||||
};
|
||||
websocket.onclose = function(e) {
|
||||
Util.Debug(">> WebSock.onclose");
|
||||
eventHandlers.close(e);
|
||||
Util.Debug("<< WebSock.onclose");
|
||||
};
|
||||
websocket.onerror = function(e) {
|
||||
Util.Debug(">> WebSock.onerror: " + e);
|
||||
eventHandlers.error(e);
|
||||
Util.Debug("<< WebSock.onerror");
|
||||
};
|
||||
}
|
||||
|
||||
function close() {
|
||||
if (websocket) {
|
||||
if ((websocket.readyState === WebSocket.OPEN) ||
|
||||
(websocket.readyState === WebSocket.CONNECTING)) {
|
||||
Util.Info("Closing WebSocket connection");
|
||||
websocket.close();
|
||||
}
|
||||
websocket.onmessage = function (e) { return; };
|
||||
}
|
||||
}
|
||||
|
||||
// Override internal functions for testing
|
||||
// Takes a send function, returns reference to recv function
|
||||
function testMode(override_send) {
|
||||
test_mode = true;
|
||||
api.send = override_send;
|
||||
api.close = function () {};
|
||||
return recv_message;
|
||||
}
|
||||
|
||||
function constructor() {
|
||||
// Configuration settings
|
||||
api.maxBufferedAmount = 200;
|
||||
|
||||
// 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;
|
||||
|
||||
// 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;
|
||||
|
||||
api.flush = flush;
|
||||
api.send = send;
|
||||
api.send_string = send_string;
|
||||
|
||||
api.on = on;
|
||||
api.init = init;
|
||||
api.open = open;
|
||||
api.close = close;
|
||||
api.testMode = testMode;
|
||||
|
||||
return api;
|
||||
}
|
||||
|
||||
return constructor();
|
||||
|
||||
}
|
||||
})();
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
/*
|
||||
* noVNC: HTML5 VNC client
|
||||
* Copyright (C) 2011 Joel Martin
|
||||
* Licensed under LGPL-3 (see LICENSE.txt)
|
||||
* 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 */
|
||||
/*global window, document */
|
||||
/*jslint bitwise: false, white: false, browser: true, devel: true */
|
||||
/*global Util, window, document */
|
||||
|
||||
// Globals defined here
|
||||
var WebUtil = {}, $D;
|
||||
@@ -17,7 +17,7 @@ var WebUtil = {}, $D;
|
||||
* Simple DOM selector by ID
|
||||
*/
|
||||
if (!window.$D) {
|
||||
$D = function (id) {
|
||||
window.$D = function (id) {
|
||||
if (document.getElementById) {
|
||||
return document.getElementById(id);
|
||||
} else if (document.all) {
|
||||
@@ -30,43 +30,47 @@ if (!window.$D) {
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
/*
|
||||
* ------------------------------------------------------
|
||||
* Namespaced in WebUtil
|
||||
* ------------------------------------------------------
|
||||
*/
|
||||
|
||||
// init log level reading the logging HTTP param
|
||||
WebUtil.init_logging = function() {
|
||||
Util._log_level = (document.location.href.match(
|
||||
/logging=([A-Za-z0-9\._\-]*)/) ||
|
||||
['', Util._log_level])[1];
|
||||
|
||||
Util.init_logging()
|
||||
}
|
||||
WebUtil.init_logging();
|
||||
WebUtil.init_logging = function (level) {
|
||||
"use strict";
|
||||
if (typeof level !== "undefined") {
|
||||
Util._log_level = level;
|
||||
} else {
|
||||
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) {
|
||||
if ((depth > 1) && (typeof obj[i] === "object")) {
|
||||
// Print the properties of the passed-in object
|
||||
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";
|
||||
}
|
||||
}
|
||||
@@ -74,10 +78,16 @@ 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; }
|
||||
return (document.location.href.match(re) || ['',defVal])[1];
|
||||
if (match) {
|
||||
return decodeURIComponent(match[1]);
|
||||
} else {
|
||||
return defVal;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -86,39 +96,118 @@ 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 /*, ...callbackArgs */) {
|
||||
"use strict";
|
||||
var callbackArgs = Array.prototype.slice.call(arguments, 1);
|
||||
if (window.chrome && window.chrome.storage) {
|
||||
window.chrome.storage.sync.get(function (cfg) {
|
||||
WebUtil.settings = cfg;
|
||||
console.log(WebUtil.settings);
|
||||
if (callback) {
|
||||
callback.apply(this, callbackArgs);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// No-op
|
||||
if (callback) {
|
||||
callback.apply(this, callbackArgs);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// No days means only for this browser session
|
||||
WebUtil.writeSetting = function (name, value) {
|
||||
"use strict";
|
||||
if (window.chrome && window.chrome.storage) {
|
||||
//console.log("writeSetting:", name, value);
|
||||
if (WebUtil.settings[name] !== value) {
|
||||
WebUtil.settings[name] = value;
|
||||
window.chrome.storage.sync.set(WebUtil.settings);
|
||||
}
|
||||
} else {
|
||||
localStorage.setItem(name, value);
|
||||
}
|
||||
};
|
||||
|
||||
WebUtil.readSetting = function (name, defaultValue) {
|
||||
"use strict";
|
||||
var value;
|
||||
if (window.chrome && window.chrome.storage) {
|
||||
value = WebUtil.settings[name];
|
||||
} else {
|
||||
value = localStorage.getItem(name);
|
||||
}
|
||||
if (typeof value === "undefined") {
|
||||
value = null;
|
||||
}
|
||||
if (value === null && typeof defaultValue !== undefined) {
|
||||
return defaultValue;
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
};
|
||||
|
||||
WebUtil.eraseSetting = function (name) {
|
||||
"use strict";
|
||||
if (window.chrome && window.chrome.storage) {
|
||||
window.chrome.storage.sync.remove(name);
|
||||
delete WebUtil.settings[name];
|
||||
} else {
|
||||
localStorage.removeItem(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]);
|
||||
@@ -129,14 +218,16 @@ 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];
|
||||
if (link.title === sheet) {
|
||||
|
||||
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;
|
||||
} else {
|
||||
|
||||
194
karma.conf.js
Normal file
@@ -0,0 +1,194 @@
|
||||
// Karma configuration
|
||||
|
||||
module.exports = function(config) {
|
||||
/*var customLaunchers = {
|
||||
sl_chrome_win7: {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'chrome',
|
||||
platform: 'Windows 7'
|
||||
},
|
||||
|
||||
sl_firefox30_linux: {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'firefox',
|
||||
version: '30',
|
||||
platform: 'Linux'
|
||||
},
|
||||
|
||||
sl_firefox26_linux: {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'firefox',
|
||||
version: 26,
|
||||
platform: 'Linux'
|
||||
},
|
||||
|
||||
sl_windows7_ie10: {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'internet explorer',
|
||||
platform: 'Windows 7',
|
||||
version: '10'
|
||||
},
|
||||
|
||||
sl_windows81_ie11: {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'internet explorer',
|
||||
platform: 'Windows 8.1',
|
||||
version: '11'
|
||||
},
|
||||
|
||||
sl_osxmavericks_safari7: {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'safari',
|
||||
platform: 'OS X 10.9',
|
||||
version: '7'
|
||||
},
|
||||
|
||||
sl_osxmtnlion_safari6: {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'safari',
|
||||
platform: 'OS X 10.8',
|
||||
version: '6'
|
||||
}
|
||||
};*/
|
||||
|
||||
var customLaunchers = {};
|
||||
var browsers = [];
|
||||
var useSauce = false;
|
||||
|
||||
if (process.env.SAUCE_USERNAME && process.env.SAUCE_ACCESS_KEY) {
|
||||
useSauce = true;
|
||||
}
|
||||
|
||||
if (useSauce && process.env.TEST_BROWSER_NAME && process.env.TEST_BROWSER_NAME != 'PhantomJS') {
|
||||
var names = process.env.TEST_BROWSER_NAME.split(',');
|
||||
var platforms = process.env.TEST_BROWSER_OS.split(',');
|
||||
var versions = [];
|
||||
if (process.env.TEST_BROWSER_VERSION) {
|
||||
versions = process.env.TEST_BROWSER_VERSION.split(',');
|
||||
} else {
|
||||
versions = [null];
|
||||
}
|
||||
|
||||
for (var i = 0; i < names.length; i++) {
|
||||
for (var j = 0; j < platforms.length; j++) {
|
||||
for (var k = 0; k < versions.length; k++) {
|
||||
var launcher_name = 'sl_' + platforms[j].replace(/[^a-zA-Z0-9]/g, '') + '_' + names[i];
|
||||
if (versions[k]) {
|
||||
launcher_name += '_' + versions[k];
|
||||
}
|
||||
|
||||
customLaunchers[launcher_name] = {
|
||||
base: 'SauceLabs',
|
||||
browserName: names[i],
|
||||
platform: platforms[j],
|
||||
};
|
||||
|
||||
if (versions[i]) {
|
||||
customLaunchers[launcher_name].version = versions[k];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
browsers = Object.keys(customLaunchers);
|
||||
} else {
|
||||
useSauce = false;
|
||||
browsers = ['PhantomJS'];
|
||||
}
|
||||
|
||||
var my_conf = {
|
||||
|
||||
// base path that will be used to resolve all patterns (eg. files, exclude)
|
||||
basePath: '',
|
||||
|
||||
// frameworks to use
|
||||
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
|
||||
frameworks: ['mocha', 'sinon', 'chai', 'sinon-chai'],
|
||||
|
||||
|
||||
// list of files / patterns to load in the browser (loaded in order)
|
||||
files: [
|
||||
'tests/fake.*.js',
|
||||
'tests/assertions.js',
|
||||
'include/util.js', // load first to avoid issues, since methods are called immediately
|
||||
//'../include/*.js',
|
||||
'include/base64.js',
|
||||
'include/keysym.js',
|
||||
'include/keysymdef.js',
|
||||
'include/keyboard.js',
|
||||
'include/input.js',
|
||||
'include/websock.js',
|
||||
'include/rfb.js',
|
||||
'include/jsunzip.js',
|
||||
'include/des.js',
|
||||
'include/display.js',
|
||||
'tests/test.*.js'
|
||||
],
|
||||
|
||||
client: {
|
||||
mocha: {
|
||||
'ui': 'bdd'
|
||||
}
|
||||
},
|
||||
|
||||
// list of files to exclude
|
||||
exclude: [
|
||||
'../include/playback.js',
|
||||
'../include/ui.js'
|
||||
],
|
||||
|
||||
customLaunchers: customLaunchers,
|
||||
|
||||
// start these browsers
|
||||
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
|
||||
browsers: browsers,
|
||||
|
||||
// preprocess matching files before serving them to the browser
|
||||
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
|
||||
preprocessors: {
|
||||
|
||||
},
|
||||
|
||||
|
||||
// test results reporter to use
|
||||
// possible values: 'dots', 'progress'
|
||||
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
|
||||
reporters: ['mocha', 'saucelabs'],
|
||||
|
||||
|
||||
// web server port
|
||||
port: 9876,
|
||||
|
||||
|
||||
// enable / disable colors in the output (reporters and logs)
|
||||
colors: true,
|
||||
|
||||
|
||||
// level of logging
|
||||
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
|
||||
logLevel: config.LOG_INFO,
|
||||
|
||||
|
||||
// enable / disable watching file and executing tests whenever any file changes
|
||||
autoWatch: false,
|
||||
|
||||
// Continuous Integration mode
|
||||
// if true, Karma captures browsers, runs the tests and exits
|
||||
singleRun: true,
|
||||
|
||||
// Increase timeout in case connection is slow/we run more browsers than possible
|
||||
// (we currently get 3 for free, and we try to run 7, so it can take a while)
|
||||
captureTimeout: 240000
|
||||
};
|
||||
|
||||
if (useSauce) {
|
||||
my_conf.captureTimeout = 0; // use SL timeout
|
||||
my_conf.sauceLabs = {
|
||||
testName: 'noVNC Tests (all)',
|
||||
startConnect: true,
|
||||
tunnelIdentifier: process.env.TRAVIS_JOB_NUMBER
|
||||
};
|
||||
}
|
||||
|
||||
config.set(my_conf);
|
||||
};
|
||||
50
package.json
Normal file
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Javascript binary array performance tests
|
||||
* Copyright (C) 2011 Joel Martin
|
||||
* Licensed under LGPL-3 (see LICENSE.txt)
|
||||
* Copyright (C) 2012 Joel Martin
|
||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||
*/
|
||||
|
||||
var ctx, i, j, randlist,
|
||||
|
||||
24
tests/assertions.js
Normal file
@@ -0,0 +1,24 @@
|
||||
// some useful assertions for noVNC
|
||||
chai.use(function (_chai, utils) {
|
||||
_chai.Assertion.addMethod('displayed', function (target_data) {
|
||||
var obj = this._obj;
|
||||
var data_cl = obj._drawCtx.getImageData(0, 0, obj._viewportLoc.w, obj._viewportLoc.h).data;
|
||||
// NB(directxman12): PhantomJS 1.x doesn't implement Uint8ClampedArray, so work around that
|
||||
var data = new Uint8Array(data_cl);
|
||||
this.assert(utils.eql(data, target_data),
|
||||
"expected #{this} to have displayed the image #{exp}, but instead it displayed #{act}",
|
||||
"expected #{this} not to have displayed the image #{act}",
|
||||
target_data,
|
||||
data);
|
||||
});
|
||||
|
||||
_chai.Assertion.addMethod('sent', function (target_data) {
|
||||
var obj = this._obj;
|
||||
var data = obj._websocket._get_sent_data();
|
||||
this.assert(utils.eql(data, target_data),
|
||||
"expected #{this} to have sent the data #{exp}, but it actually sent #{act}",
|
||||
"expected #{this} not to have sent the data #{act}",
|
||||
target_data,
|
||||
data);
|
||||
});
|
||||
});
|
||||
@@ -9,7 +9,7 @@
|
||||
<script src="../include/util.js"></script>
|
||||
<script src="../include/webutil.js"></script>
|
||||
<script src="../include/base64.js"></script>
|
||||
<script src="../include/canvas.js"></script>
|
||||
<script src="../include/display.js"></script>
|
||||
<script src="face.png.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
@@ -36,7 +36,7 @@
|
||||
|
||||
<script>
|
||||
var msg_cnt = 0;
|
||||
var start_width = 300, start_height = 100;
|
||||
var display, start_width = 300, start_height = 100;
|
||||
var iterations;
|
||||
|
||||
function message(str) {
|
||||
@@ -48,12 +48,12 @@
|
||||
}
|
||||
|
||||
function test_functions () {
|
||||
var img, x, y, w, h, ctx = canvas.getContext();
|
||||
w = canvas.get_width();
|
||||
h = canvas.get_height();
|
||||
canvas.fillRect(0, 0, w, h, [240,240,240]);
|
||||
var img, x, y, w, h, ctx = display.get_context();
|
||||
w = display.get_width();
|
||||
h = display.get_height();
|
||||
display.fillRect(0, 0, w, h, [240,240,240]);
|
||||
|
||||
canvas.blitStringImage("data:image/png;base64," + face64, 150, 10);
|
||||
display.blitStringImage("data:image/png;base64," + face64, 150, 10);
|
||||
|
||||
var himg = new Image();
|
||||
himg.onload = function () {
|
||||
@@ -70,14 +70,14 @@
|
||||
data[(y*50 + x)*4 + 3] = 255;
|
||||
}
|
||||
}
|
||||
canvas.blitImage(30, 10, 50, 50, data, 0);
|
||||
display.blitImage(30, 10, 50, 50, data, 0);
|
||||
|
||||
img = canvas.getTile(5,5,16,16,[0,128,128]);
|
||||
canvas.putTile(img);
|
||||
img = display.getTile(5,5,16,16,[0,128,128]);
|
||||
display.putTile(img);
|
||||
|
||||
img = canvas.getTile(90,15,16,16,[0,0,0]);
|
||||
canvas.setSubTile(img, 0,0,16,16,[128,128,0]);
|
||||
canvas.putTile(img);
|
||||
img = display.getTile(90,15,16,16,[0,0,0]);
|
||||
display.setSubTile(img, 0,0,16,16,[128,128,0]);
|
||||
display.putTile(img);
|
||||
}
|
||||
|
||||
function begin () {
|
||||
@@ -90,7 +90,7 @@
|
||||
function start_delayed () {
|
||||
var ret;
|
||||
|
||||
ret = canvas.set_prefer_js(true);
|
||||
ret = display.set_prefer_js(true);
|
||||
if (ret) {
|
||||
message("Running test: prefer Javascript ops");
|
||||
var time1 = run_test();
|
||||
@@ -100,14 +100,14 @@
|
||||
message("Could not run: prefer Javascript ops");
|
||||
}
|
||||
|
||||
canvas.set_prefer_js(false);
|
||||
display.set_prefer_js(false);
|
||||
message("Running test: prefer Canvas ops");
|
||||
var time2 = run_test();
|
||||
message("prefer Canvas ops: " + time2 + "ms total, " +
|
||||
(time2 / iterations) + "ms per frame");
|
||||
|
||||
if (Util.get_logging() !== 'debug') {
|
||||
canvas.resize(start_width, start_height, true);
|
||||
display.resize(start_width, start_height, true);
|
||||
test_functions();
|
||||
}
|
||||
$D('startButton').disabled = false;
|
||||
@@ -118,7 +118,7 @@
|
||||
var width, height;
|
||||
width = $D('width').value;
|
||||
height = $D('height').value;
|
||||
canvas.resize(width, height);
|
||||
display.resize(width, height);
|
||||
var color, start_time = (new Date()).getTime(), w, h;
|
||||
for (var i=0; i < iterations; i++) {
|
||||
color = [128, 128, (255 / iterations) * i, 0];
|
||||
@@ -126,9 +126,9 @@
|
||||
for (var y=0; y < height; y = y + 16) {
|
||||
w = Math.min(16, width - x);
|
||||
h = Math.min(16, height - y);
|
||||
var tile = canvas.getTile(x, y, w, h, color);
|
||||
canvas.setSubTile(tile, 0, 0, w, h, color);
|
||||
canvas.putTile(tile);
|
||||
var tile = display.getTile(x, y, w, h, color);
|
||||
display.setSubTile(tile, 0, 0, w, h, color);
|
||||
display.putTile(tile);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -139,8 +139,8 @@
|
||||
window.onload = function() {
|
||||
message("in onload");
|
||||
$D('iterations').value = 10;
|
||||
canvas = new Canvas({'target' : $D('canvas')});
|
||||
canvas.resize(start_width, start_height, true);
|
||||
display = new Display({'target' : $D('canvas')});
|
||||
display.resize(start_width, start_height, true);
|
||||
message("Canvas initialized");
|
||||
test_functions();
|
||||
}
|
||||
|
||||
96
tests/fake.websocket.js
Normal file
@@ -0,0 +1,96 @@
|
||||
var FakeWebSocket;
|
||||
|
||||
(function () {
|
||||
// PhantomJS can't create Event objects directly, so we need to use this
|
||||
function make_event(name, props) {
|
||||
var evt = document.createEvent('Event');
|
||||
evt.initEvent(name, true, true);
|
||||
if (props) {
|
||||
for (var prop in props) {
|
||||
evt[prop] = props[prop];
|
||||
}
|
||||
}
|
||||
return evt;
|
||||
}
|
||||
|
||||
FakeWebSocket = function (uri, protocols) {
|
||||
this.url = uri;
|
||||
this.binaryType = "arraybuffer";
|
||||
this.extensions = "";
|
||||
|
||||
if (!protocols || typeof protocols === 'string') {
|
||||
this.protocol = protocols;
|
||||
} else {
|
||||
this.protocol = protocols[0];
|
||||
}
|
||||
|
||||
this._send_queue = new Uint8Array(20000);
|
||||
|
||||
this.readyState = FakeWebSocket.CONNECTING;
|
||||
this.bufferedAmount = 0;
|
||||
|
||||
this.__is_fake = true;
|
||||
};
|
||||
|
||||
FakeWebSocket.prototype = {
|
||||
close: function (code, reason) {
|
||||
this.readyState = FakeWebSocket.CLOSED;
|
||||
if (this.onclose) {
|
||||
this.onclose(make_event("close", { 'code': code, 'reason': reason, 'wasClean': true }));
|
||||
}
|
||||
},
|
||||
|
||||
send: function (data) {
|
||||
if (this.protocol == 'base64') {
|
||||
data = Base64.decode(data);
|
||||
} else {
|
||||
data = new Uint8Array(data);
|
||||
}
|
||||
this._send_queue.set(data, this.bufferedAmount);
|
||||
this.bufferedAmount += data.length;
|
||||
},
|
||||
|
||||
_get_sent_data: function () {
|
||||
var arr = [];
|
||||
for (var i = 0; i < this.bufferedAmount; i++) {
|
||||
arr[i] = this._send_queue[i];
|
||||
}
|
||||
|
||||
this.bufferedAmount = 0;
|
||||
|
||||
return arr;
|
||||
},
|
||||
|
||||
_open: function (data) {
|
||||
this.readyState = FakeWebSocket.OPEN;
|
||||
if (this.onopen) {
|
||||
this.onopen(make_event('open'));
|
||||
}
|
||||
},
|
||||
|
||||
_receive_data: function (data) {
|
||||
this.onmessage(make_event("message", { 'data': data }));
|
||||
}
|
||||
};
|
||||
|
||||
FakeWebSocket.OPEN = WebSocket.OPEN;
|
||||
FakeWebSocket.CONNECTING = WebSocket.CONNECTING;
|
||||
FakeWebSocket.CLOSING = WebSocket.CLOSING;
|
||||
FakeWebSocket.CLOSED = WebSocket.CLOSED;
|
||||
|
||||
FakeWebSocket.__is_fake = true;
|
||||
|
||||
FakeWebSocket.replace = function () {
|
||||
if (!WebSocket.__is_fake) {
|
||||
var real_version = WebSocket;
|
||||
WebSocket = FakeWebSocket;
|
||||
FakeWebSocket.__real_version = real_version;
|
||||
}
|
||||
};
|
||||
|
||||
FakeWebSocket.restore = function () {
|
||||
if (WebSocket.__is_fake) {
|
||||
WebSocket = WebSocket.__real_version;
|
||||
}
|
||||
};
|
||||
})();
|
||||
@@ -4,7 +4,11 @@
|
||||
<body>
|
||||
<br><br>
|
||||
|
||||
Canvas:<br>
|
||||
Canvas:
|
||||
<span id="button-selection" style="display: none;">
|
||||
<input id="button1" type="button" value="L"><input id="button2" type="button" value="M"><input id="button4" type="button" value="R">
|
||||
</span>
|
||||
<br>
|
||||
<canvas id="canvas" width="640" height="20"
|
||||
style="border-style: dotted; border-width: 1px;">
|
||||
Canvas not supported.
|
||||
@@ -22,12 +26,14 @@
|
||||
<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/canvas.js"></script>
|
||||
<script src="../include/display.js"></script>
|
||||
<script>
|
||||
var msg_cnt = 0;
|
||||
var width = 400, height = 200;
|
||||
var iterations;
|
||||
var msg_cnt = 0, iterations,
|
||||
width = 400, height = 200,
|
||||
canvas, keyboard, mouse;
|
||||
|
||||
var newline = "\n";
|
||||
if (Util.Engine.trident) {
|
||||
@@ -53,25 +59,73 @@
|
||||
//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);
|
||||
}
|
||||
|
||||
function selectButton(num) {
|
||||
var b, blist = [1,2,4];
|
||||
|
||||
if (typeof num === 'undefined') {
|
||||
// Show the default
|
||||
num = mouse.get_touchButton();
|
||||
} else if (num === mouse.get_touchButton()) {
|
||||
// Set all buttons off (no clicks)
|
||||
mouse.set_touchButton(0);
|
||||
num = 0;
|
||||
} else {
|
||||
// Turn on one button
|
||||
mouse.set_touchButton(num);
|
||||
}
|
||||
|
||||
for (b = 0; b < blist.length; b++) {
|
||||
if (blist[b] === num) {
|
||||
$D('button' + blist[b]).style.backgroundColor = "black";
|
||||
$D('button' + blist[b]).style.color = "lightgray";
|
||||
} else {
|
||||
$D('button' + blist[b]).style.backgroundColor = "";
|
||||
$D('button' + blist[b]).style.color = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.onload = function() {
|
||||
var canvas = new Canvas({'target' : $D('canvas')});
|
||||
canvas = new Display({'target' : $D('canvas')});
|
||||
keyboard = new Keyboard({'target': document,
|
||||
'keyPress': keyPress});
|
||||
'onKeyPress': rfbKeyPress});
|
||||
Util.addEvent(document, 'keypress', rawKey);
|
||||
Util.addEvent(document, 'keydown', rawKey);
|
||||
Util.addEvent(document, 'keyup', rawKey);
|
||||
mouse = new Mouse({'target': $D('canvas'),
|
||||
'mouseButton': mouseButton,
|
||||
'mouseMove': mouseMove});
|
||||
'onMouseButton': mouseButton,
|
||||
'onMouseMove': mouseMove});
|
||||
|
||||
canvas.resize(width, height, true);
|
||||
keyboard.grab();
|
||||
mouse.grab();
|
||||
message("Canvas initialized");
|
||||
message("Display initialized");
|
||||
|
||||
if ('ontouchstart' in document.documentElement) {
|
||||
message("Touch device detected");
|
||||
$D('button-selection').style.display = "inline";
|
||||
$D('button1').onclick = function(){ selectButton(1) };
|
||||
$D('button2').onclick = function(){ selectButton(2) };
|
||||
$D('button4').onclick = function(){ selectButton(4) };
|
||||
selectButton();
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
</html>
|
||||
|
||||
29
tests/keyboard-tests.html
Normal file
@@ -0,0 +1,29 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Mocha Tests</title>
|
||||
<link rel="stylesheet" href="node_modules/mocha/mocha.css" />
|
||||
</head>
|
||||
<body>
|
||||
<!--
|
||||
To run tests
|
||||
cd .../noVNC/tests
|
||||
npm install chai mocha
|
||||
open keyboard-tests.html in a browser
|
||||
-->
|
||||
<div id="mocha"></div>
|
||||
<script src="node_modules/chai/chai.js"></script>
|
||||
<script src="node_modules/mocha/mocha.js"></script>
|
||||
<script>mocha.setup('bdd')</script>
|
||||
<script src="../include/keysymdef.js"></script>
|
||||
<script src="../include/keyboard.js"></script>
|
||||
<script src="test.keyboard.js"></script>
|
||||
<script src="test.helper.js"></script>
|
||||
<script>
|
||||
mocha.checkLeaks();
|
||||
mocha.globals(['navigator']);
|
||||
mocha.run();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
102
tests/run_from_console.casper.js
Normal file
@@ -0,0 +1,102 @@
|
||||
var Spooky = require('spooky');
|
||||
var path = require('path');
|
||||
|
||||
var phantom_path = require('phantomjs').path;
|
||||
var casper_path = path.resolve(__dirname, '../node_modules/casperjs/bin/casperjs');
|
||||
process.env.PHANTOMJS_EXECUTABLE = phantom_path;
|
||||
var casper_opts = {
|
||||
child: {
|
||||
transport: 'http',
|
||||
command: casper_path
|
||||
},
|
||||
casper: {
|
||||
logLevel: 'debug',
|
||||
verbose: true
|
||||
}
|
||||
};
|
||||
|
||||
var provide_emitter = function(file_paths) {
|
||||
var spooky = new Spooky(casper_opts, function(err) {
|
||||
if (err) {
|
||||
if (err.stack) console.warn(err.stack);
|
||||
else console.warn(err);
|
||||
return;
|
||||
}
|
||||
spooky.start('about:blank');
|
||||
|
||||
file_paths.forEach(function(file_path, path_ind) {
|
||||
spooky.thenOpen('file://'+file_path);
|
||||
spooky.waitFor(function() {
|
||||
return this.getGlobal('__mocha_done') === true;
|
||||
},
|
||||
[{ path_ind: path_ind }, function() {
|
||||
var res_json = {
|
||||
file_ind: path_ind
|
||||
};
|
||||
|
||||
res_json.num_tests = this.evaluate(function() { return document.querySelectorAll('li.test').length; });
|
||||
res_json.num_passes = this.evaluate(function() { return document.querySelectorAll('li.test.pass').length; });
|
||||
res_json.num_fails = this.evaluate(function() { return document.querySelectorAll('li.test.fail').length; });
|
||||
res_json.num_slow = this.evaluate(function() { return document.querySelectorAll('li.test.pass:not(.fast):not(.pending)').length; });
|
||||
res_json.num_skipped = this.evaluate(function () { return document.querySelectorAll('li.test.pending').length; });
|
||||
res_json.duration = this.evaluate(function() { return document.querySelector('li.duration em').textContent; });
|
||||
|
||||
res_json.suites = this.evaluate(function() {
|
||||
var traverse_node = function(elem) {
|
||||
var res;
|
||||
if (elem.classList.contains('suite')) {
|
||||
res = {
|
||||
type: 'suite',
|
||||
name: elem.querySelector('h1').textContent,
|
||||
has_subfailures: elem.querySelectorAll('li.test.fail').length > 0,
|
||||
};
|
||||
|
||||
var child_elems = elem.querySelector('ul').children;
|
||||
res.children = Array.prototype.map.call(child_elems, traverse_node);
|
||||
return res;
|
||||
}
|
||||
else {
|
||||
var h2_content = elem.querySelector('h2').childNodes;
|
||||
res = {
|
||||
type: 'test',
|
||||
text: h2_content[0].textContent,
|
||||
};
|
||||
|
||||
if (elem.classList.contains('pass')) {
|
||||
res.pass = true;
|
||||
if (elem.classList.contains('pending')) {
|
||||
res.slow = false;
|
||||
res.skipped = true;
|
||||
}
|
||||
else {
|
||||
res.slow = !elem.classList.contains('fast');
|
||||
res.skipped = false;
|
||||
res.duration = h2_content[1].textContent;
|
||||
}
|
||||
}
|
||||
else {
|
||||
res.error = elem.querySelector('pre.error').textContent;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
};
|
||||
var top_suites = document.querySelectorAll('#mocha-report > li.suite');
|
||||
return Array.prototype.map.call(top_suites, traverse_node);
|
||||
});
|
||||
|
||||
res_json.replay = this.evaluate(function() { return document.querySelector('a.replay').textContent; });
|
||||
|
||||
this.emit('test_ready', res_json);
|
||||
}]);
|
||||
});
|
||||
spooky.run();
|
||||
});
|
||||
|
||||
return spooky;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
provide_emitter: provide_emitter,
|
||||
name: 'SpookyJS (CapserJS on PhantomJS)'
|
||||
};
|
||||
342
tests/run_from_console.js
Executable file
@@ -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);
|
||||
});*/
|
||||
}
|
||||
82
tests/run_from_console.zombie.js
Normal file
@@ -0,0 +1,82 @@
|
||||
var Browser = require('zombie');
|
||||
var path = require('path');
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var Q = require('q');
|
||||
|
||||
var provide_emitter = function(file_paths) {
|
||||
var emitter = new EventEmitter();
|
||||
|
||||
file_paths.reduce(function(prom, file_path, path_ind) {
|
||||
return prom.then(function(browser) {
|
||||
browser.visit('file://'+file_path, function() {
|
||||
if (browser.error) throw new Error(browser.errors);
|
||||
|
||||
var res_json = {};
|
||||
res_json.file_ind = path_ind;
|
||||
|
||||
res_json.num_tests = browser.querySelectorAll('li.test').length;
|
||||
res_json.num_fails = browser.querySelectorAll('li.test.fail').length;
|
||||
res_json.num_passes = browser.querySelectorAll('li.test.pass').length;
|
||||
res_json.num_slow = browser.querySelectorAll('li.test.pass:not(.fast)').length;
|
||||
res_json.num_skipped = browser.querySelectorAll('li.test.pending').length;
|
||||
res_json.duration = browser.text('li.duration em');
|
||||
|
||||
var traverse_node = function(elem) {
|
||||
var classList = elem.className.split(' ');
|
||||
var res;
|
||||
if (classList.indexOf('suite') > -1) {
|
||||
res = {
|
||||
type: 'suite',
|
||||
name: elem.querySelector('h1').textContent,
|
||||
has_subfailures: elem.querySelectorAll('li.test.fail').length > 0
|
||||
};
|
||||
|
||||
var child_elems = elem.querySelector('ul').children;
|
||||
res.children = Array.prototype.map.call(child_elems, traverse_node);
|
||||
return res;
|
||||
}
|
||||
else {
|
||||
var h2_content = elem.querySelector('h2').childNodes;
|
||||
res = {
|
||||
type: 'test',
|
||||
text: h2_content[0].textContent
|
||||
};
|
||||
|
||||
if (classList.indexOf('pass') > -1) {
|
||||
res.pass = true;
|
||||
if (classList.indexOf('pending') > -1) {
|
||||
res.slow = false;
|
||||
res.skipped = true;
|
||||
}
|
||||
else {
|
||||
res.slow = classList.indexOf('fast') < 0;
|
||||
res.skipped = false;
|
||||
res.duration = h2_content[1].textContent;
|
||||
}
|
||||
}
|
||||
else {
|
||||
res.error = elem.querySelector('pre.error').textContent;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
};
|
||||
|
||||
var top_suites = browser.querySelectorAll('#mocha-report > li.suite');
|
||||
res_json.suites = Array.prototype.map.call(top_suites, traverse_node);
|
||||
res_json.replay = browser.querySelector('a.replay').textContent;
|
||||
|
||||
emitter.emit('test_ready', res_json);
|
||||
});
|
||||
|
||||
return new Browser();
|
||||
});
|
||||
}, Q(new Browser()));
|
||||
|
||||
return emitter;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
provide_emitter: provide_emitter,
|
||||
name: 'ZombieJS'
|
||||
};
|
||||
33
tests/test.base64.js
Normal file
@@ -0,0 +1,33 @@
|
||||
// requires local modules: base64
|
||||
var assert = chai.assert;
|
||||
var expect = chai.expect;
|
||||
|
||||
describe('Base64 Tools', function() {
|
||||
"use strict";
|
||||
|
||||
var BIN_ARR = new Array(256);
|
||||
for (var i = 0; i < 256; i++) {
|
||||
BIN_ARR[i] = i;
|
||||
}
|
||||
|
||||
var B64_STR = "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w==";
|
||||
|
||||
|
||||
describe('encode', function() {
|
||||
it('should encode a binary string into Base64', function() {
|
||||
var encoded = Base64.encode(BIN_ARR);
|
||||
expect(encoded).to.equal(B64_STR);
|
||||
});
|
||||
});
|
||||
|
||||
describe('decode', function() {
|
||||
it('should decode a Base64 string into a normal string', function() {
|
||||
var decoded = Base64.decode(B64_STR);
|
||||
expect(decoded).to.deep.equal(BIN_ARR);
|
||||
});
|
||||
|
||||
it('should throw an error if we have extra characters at the end of the string', function() {
|
||||
expect(function () { Base64.decode(B64_STR+'abcdef'); }).to.throw(Error);
|
||||
});
|
||||
});
|
||||
});
|
||||
353
tests/test.display.js
Normal file
@@ -0,0 +1,353 @@
|
||||
// requires local modules: util, base64, display
|
||||
// requires test modules: assertions
|
||||
/* jshint expr: true */
|
||||
var expect = chai.expect;
|
||||
|
||||
describe('Display/Canvas Helper', function () {
|
||||
var checked_data = [
|
||||
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
||||
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
||||
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
|
||||
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
|
||||
];
|
||||
checked_data = new Uint8Array(checked_data);
|
||||
|
||||
var basic_data = [0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0xff, 0xff, 0xff, 255];
|
||||
basic_data = new Uint8Array(basic_data);
|
||||
|
||||
function make_image_canvas (input_data) {
|
||||
var canvas = document.createElement('canvas');
|
||||
canvas.width = 4;
|
||||
canvas.height = 4;
|
||||
var ctx = canvas.getContext('2d');
|
||||
var data = ctx.createImageData(4, 4);
|
||||
for (var i = 0; i < checked_data.length; i++) { data.data[i] = input_data[i]; }
|
||||
ctx.putImageData(data, 0, 0);
|
||||
return canvas;
|
||||
}
|
||||
|
||||
describe('checking for cursor uri support', function () {
|
||||
beforeEach(function () {
|
||||
this._old_change_cursor = Display.changeCursor;
|
||||
});
|
||||
|
||||
it('should disable cursor URIs if there is no support', function () {
|
||||
Display.changeCursor = function(target) {
|
||||
target.style.cursor = undefined;
|
||||
};
|
||||
var display = new Display({ target: document.createElement('canvas'), prefer_js: true, viewport: false });
|
||||
expect(display._cursor_uri).to.be.false;
|
||||
});
|
||||
|
||||
it('should enable cursor URIs if there is support', function () {
|
||||
Display.changeCursor = function(target) {
|
||||
target.style.cursor = 'pointer';
|
||||
};
|
||||
var display = new Display({ target: document.createElement('canvas'), prefer_js: true, viewport: false });
|
||||
expect(display._cursor_uri).to.be.true;
|
||||
});
|
||||
|
||||
it('respect the cursor_uri option if there is support', function () {
|
||||
Display.changeCursor = function(target) {
|
||||
target.style.cursor = 'pointer';
|
||||
};
|
||||
var display = new Display({ target: document.createElement('canvas'), prefer_js: true, viewport: false, cursor_uri: false });
|
||||
expect(display._cursor_uri).to.be.false;
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
Display.changeCursor = this._old_change_cursor;
|
||||
});
|
||||
});
|
||||
|
||||
describe('viewport handling', function () {
|
||||
var display;
|
||||
beforeEach(function () {
|
||||
display = new Display({ target: document.createElement('canvas'), prefer_js: false, viewport: true });
|
||||
display.resize(5, 5);
|
||||
display.viewportChange(1, 1, 3, 3);
|
||||
display.getCleanDirtyReset();
|
||||
});
|
||||
|
||||
it('should take viewport location into consideration when drawing images', function () {
|
||||
display.resize(4, 4);
|
||||
display.viewportChange(0, 0, 2, 2);
|
||||
display.drawImage(make_image_canvas(basic_data), 1, 1);
|
||||
|
||||
var expected = new Uint8Array(16);
|
||||
var i;
|
||||
for (i = 0; i < 8; i++) { expected[i] = basic_data[i]; }
|
||||
for (i = 8; i < 16; i++) { expected[i] = 0; }
|
||||
expect(display).to.have.displayed(expected);
|
||||
});
|
||||
|
||||
it('should redraw the left side when shifted left', function () {
|
||||
display.viewportChange(-1, 0, 3, 3);
|
||||
var cdr = display.getCleanDirtyReset();
|
||||
expect(cdr.cleanBox).to.deep.equal({ x: 1, y: 1, w: 2, h: 3 });
|
||||
expect(cdr.dirtyBoxes).to.have.length(1);
|
||||
expect(cdr.dirtyBoxes[0]).to.deep.equal({ x: 0, y: 1, w: 2, h: 3 });
|
||||
});
|
||||
|
||||
it('should redraw the right side when shifted right', function () {
|
||||
display.viewportChange(1, 0, 3, 3);
|
||||
var cdr = display.getCleanDirtyReset();
|
||||
expect(cdr.cleanBox).to.deep.equal({ x: 2, y: 1, w: 2, h: 3 });
|
||||
expect(cdr.dirtyBoxes).to.have.length(1);
|
||||
expect(cdr.dirtyBoxes[0]).to.deep.equal({ x: 4, y: 1, w: 1, h: 3 });
|
||||
});
|
||||
|
||||
it('should redraw the top part when shifted up', function () {
|
||||
display.viewportChange(0, -1, 3, 3);
|
||||
var cdr = display.getCleanDirtyReset();
|
||||
expect(cdr.cleanBox).to.deep.equal({ x: 1, y: 1, w: 3, h: 2 });
|
||||
expect(cdr.dirtyBoxes).to.have.length(1);
|
||||
expect(cdr.dirtyBoxes[0]).to.deep.equal({ x: 1, y: 0, w: 3, h: 1 });
|
||||
});
|
||||
|
||||
it('should redraw the bottom part when shifted down', function () {
|
||||
display.viewportChange(0, 1, 3, 3);
|
||||
var cdr = display.getCleanDirtyReset();
|
||||
expect(cdr.cleanBox).to.deep.equal({ x: 1, y: 2, w: 3, h: 2 });
|
||||
expect(cdr.dirtyBoxes).to.have.length(1);
|
||||
expect(cdr.dirtyBoxes[0]).to.deep.equal({ x: 1, y: 4, w: 3, h: 1 });
|
||||
});
|
||||
|
||||
it('should reset the entire viewport to being clean after calculating the clean/dirty boxes', function () {
|
||||
display.viewportChange(0, 1, 3, 3);
|
||||
var cdr1 = display.getCleanDirtyReset();
|
||||
var cdr2 = display.getCleanDirtyReset();
|
||||
expect(cdr1).to.not.deep.equal(cdr2);
|
||||
expect(cdr2.cleanBox).to.deep.equal({ x: 1, y: 2, w: 3, h: 3 });
|
||||
expect(cdr2.dirtyBoxes).to.be.empty;
|
||||
});
|
||||
|
||||
it('should simply mark the whole display area as dirty if not using viewports', function () {
|
||||
display = new Display({ target: document.createElement('canvas'), prefer_js: false, viewport: false });
|
||||
display.resize(5, 5);
|
||||
var cdr = display.getCleanDirtyReset();
|
||||
expect(cdr.cleanBox).to.deep.equal({ x: 0, y: 0, w: 0, h: 0 });
|
||||
expect(cdr.dirtyBoxes).to.have.length(1);
|
||||
expect(cdr.dirtyBoxes[0]).to.deep.equal({ x: 0, y: 0, w: 5, h: 5 });
|
||||
});
|
||||
});
|
||||
|
||||
describe('resizing', function () {
|
||||
var display;
|
||||
beforeEach(function () {
|
||||
display = new Display({ target: document.createElement('canvas'), prefer_js: false, viewport: true });
|
||||
display.resize(4, 3);
|
||||
});
|
||||
|
||||
it('should change the size of the logical canvas', function () {
|
||||
display.resize(5, 7);
|
||||
expect(display._fb_width).to.equal(5);
|
||||
expect(display._fb_height).to.equal(7);
|
||||
});
|
||||
|
||||
it('should update the viewport dimensions', function () {
|
||||
sinon.spy(display, 'viewportChange');
|
||||
display.resize(2, 2);
|
||||
expect(display.viewportChange).to.have.been.calledOnce;
|
||||
});
|
||||
});
|
||||
|
||||
describe('drawing', function () {
|
||||
|
||||
// TODO(directxman12): improve the tests for each of the drawing functions to cover more than just the
|
||||
// basic cases
|
||||
function drawing_tests (pref_js) {
|
||||
var display;
|
||||
beforeEach(function () {
|
||||
display = new Display({ target: document.createElement('canvas'), prefer_js: pref_js });
|
||||
display.resize(4, 4);
|
||||
});
|
||||
|
||||
it('should clear the screen on #clear without a logo set', function () {
|
||||
display.fillRect(0, 0, 4, 4, [0x00, 0x00, 0xff]);
|
||||
display._logo = null;
|
||||
display.clear();
|
||||
display.resize(4, 4);
|
||||
var empty = [];
|
||||
for (var i = 0; i < 4 * display._fb_width * display._fb_height; i++) { empty[i] = 0; }
|
||||
expect(display).to.have.displayed(new Uint8Array(empty));
|
||||
});
|
||||
|
||||
it('should draw the logo on #clear with a logo set', function (done) {
|
||||
display._logo = { width: 4, height: 4, data: make_image_canvas(checked_data).toDataURL() };
|
||||
display._drawCtx._act_drawImg = display._drawCtx.drawImage;
|
||||
display._drawCtx.drawImage = function (img, x, y) {
|
||||
this._act_drawImg(img, x, y);
|
||||
expect(display).to.have.displayed(checked_data);
|
||||
done();
|
||||
};
|
||||
display.clear();
|
||||
expect(display._fb_width).to.equal(4);
|
||||
expect(display._fb_height).to.equal(4);
|
||||
});
|
||||
|
||||
it('should support filling a rectangle with particular color via #fillRect', function () {
|
||||
display.fillRect(0, 0, 4, 4, [0, 0xff, 0]);
|
||||
display.fillRect(0, 0, 2, 2, [0xff, 0, 0]);
|
||||
display.fillRect(2, 2, 2, 2, [0xff, 0, 0]);
|
||||
expect(display).to.have.displayed(checked_data);
|
||||
});
|
||||
|
||||
it('should support copying an portion of the canvas via #copyImage', function () {
|
||||
display.fillRect(0, 0, 4, 4, [0, 0xff, 0]);
|
||||
display.fillRect(0, 0, 2, 2, [0xff, 0, 0x00]);
|
||||
display.copyImage(0, 0, 2, 2, 2, 2);
|
||||
expect(display).to.have.displayed(checked_data);
|
||||
});
|
||||
|
||||
it('should support drawing tile data with a background color and sub tiles', function () {
|
||||
display.startTile(0, 0, 4, 4, [0, 0xff, 0]);
|
||||
display.subTile(0, 0, 2, 2, [0xff, 0, 0]);
|
||||
display.subTile(2, 2, 2, 2, [0xff, 0, 0]);
|
||||
display.finishTile();
|
||||
expect(display).to.have.displayed(checked_data);
|
||||
});
|
||||
|
||||
it('should support drawing BGRX blit images with true color via #blitImage', function () {
|
||||
var data = [];
|
||||
for (var i = 0; i < 16; i++) {
|
||||
data[i * 4] = checked_data[i * 4 + 2];
|
||||
data[i * 4 + 1] = checked_data[i * 4 + 1];
|
||||
data[i * 4 + 2] = checked_data[i * 4];
|
||||
data[i * 4 + 3] = checked_data[i * 4 + 3];
|
||||
}
|
||||
display.blitImage(0, 0, 4, 4, data, 0);
|
||||
expect(display).to.have.displayed(checked_data);
|
||||
});
|
||||
|
||||
it('should support drawing RGB blit images with true color via #blitRgbImage', function () {
|
||||
var data = [];
|
||||
for (var i = 0; i < 16; i++) {
|
||||
data[i * 3] = checked_data[i * 4];
|
||||
data[i * 3 + 1] = checked_data[i * 4 + 1];
|
||||
data[i * 3 + 2] = checked_data[i * 4 + 2];
|
||||
}
|
||||
display.blitRgbImage(0, 0, 4, 4, data, 0);
|
||||
expect(display).to.have.displayed(checked_data);
|
||||
});
|
||||
|
||||
it('should support drawing blit images from a data URL via #blitStringImage', function (done) {
|
||||
var img_url = make_image_canvas(checked_data).toDataURL();
|
||||
display._drawCtx._act_drawImg = display._drawCtx.drawImage;
|
||||
display._drawCtx.drawImage = function (img, x, y) {
|
||||
this._act_drawImg(img, x, y);
|
||||
expect(display).to.have.displayed(checked_data);
|
||||
done();
|
||||
};
|
||||
display.blitStringImage(img_url, 0, 0);
|
||||
});
|
||||
|
||||
it('should support drawing solid colors with color maps', function () {
|
||||
display._true_color = false;
|
||||
display.set_colourMap({ 0: [0xff, 0, 0], 1: [0, 0xff, 0] });
|
||||
display.fillRect(0, 0, 4, 4, [1]);
|
||||
display.fillRect(0, 0, 2, 2, [0]);
|
||||
display.fillRect(2, 2, 2, 2, [0]);
|
||||
expect(display).to.have.displayed(checked_data);
|
||||
});
|
||||
|
||||
it('should support drawing blit images with color maps', function () {
|
||||
display._true_color = false;
|
||||
display.set_colourMap({ 1: [0xff, 0, 0], 0: [0, 0xff, 0] });
|
||||
var data = [1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1].map(function (elem) { return [elem]; });
|
||||
display.blitImage(0, 0, 4, 4, data, 0);
|
||||
expect(display).to.have.displayed(checked_data);
|
||||
});
|
||||
|
||||
it('should support drawing an image object via #drawImage', function () {
|
||||
var img = make_image_canvas(checked_data);
|
||||
display.drawImage(img, 0, 0);
|
||||
expect(display).to.have.displayed(checked_data);
|
||||
});
|
||||
}
|
||||
|
||||
describe('(prefering native methods)', function () { drawing_tests.call(this, false); });
|
||||
describe('(prefering JavaScript)', function () { drawing_tests.call(this, true); });
|
||||
});
|
||||
|
||||
describe('the render queue processor', function () {
|
||||
var display;
|
||||
beforeEach(function () {
|
||||
display = new Display({ target: document.createElement('canvas'), prefer_js: false });
|
||||
display.resize(4, 4);
|
||||
sinon.spy(display, '_scan_renderQ');
|
||||
this.old_requestAnimFrame = window.requestAnimFrame;
|
||||
window.requestAnimFrame = function (cb) {
|
||||
this.next_frame_cb = cb;
|
||||
}.bind(this);
|
||||
this.next_frame = function () { this.next_frame_cb(); };
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
window.requestAnimFrame = this.old_requestAnimFrame;
|
||||
});
|
||||
|
||||
it('should try to process an item when it is pushed on, if nothing else is on the queue', function () {
|
||||
display.renderQ_push({ type: 'noop' }); // does nothing
|
||||
expect(display._scan_renderQ).to.have.been.calledOnce;
|
||||
});
|
||||
|
||||
it('should not try to process an item when it is pushed on if we are waiting for other items', function () {
|
||||
display._renderQ.length = 2;
|
||||
display.renderQ_push({ type: 'noop' });
|
||||
expect(display._scan_renderQ).to.not.have.been.called;
|
||||
});
|
||||
|
||||
it('should wait until an image is loaded to attempt to draw it and the rest of the queue', function () {
|
||||
var img = { complete: false };
|
||||
display._renderQ = [{ type: 'img', x: 3, y: 4, img: img },
|
||||
{ type: 'fill', x: 1, y: 2, width: 3, height: 4, color: 5 }];
|
||||
display.drawImage = sinon.spy();
|
||||
display.fillRect = sinon.spy();
|
||||
|
||||
display._scan_renderQ();
|
||||
expect(display.drawImage).to.not.have.been.called;
|
||||
expect(display.fillRect).to.not.have.been.called;
|
||||
|
||||
display._renderQ[0].img.complete = true;
|
||||
this.next_frame();
|
||||
expect(display.drawImage).to.have.been.calledOnce;
|
||||
expect(display.fillRect).to.have.been.calledOnce;
|
||||
});
|
||||
|
||||
it('should draw a blit image on type "blit"', function () {
|
||||
display.blitImage = sinon.spy();
|
||||
display.renderQ_push({ type: 'blit', x: 3, y: 4, width: 5, height: 6, data: [7, 8, 9] });
|
||||
expect(display.blitImage).to.have.been.calledOnce;
|
||||
expect(display.blitImage).to.have.been.calledWith(3, 4, 5, 6, [7, 8, 9], 0);
|
||||
});
|
||||
|
||||
it('should draw a blit RGB image on type "blitRgb"', function () {
|
||||
display.blitRgbImage = sinon.spy();
|
||||
display.renderQ_push({ type: 'blitRgb', x: 3, y: 4, width: 5, height: 6, data: [7, 8, 9] });
|
||||
expect(display.blitRgbImage).to.have.been.calledOnce;
|
||||
expect(display.blitRgbImage).to.have.been.calledWith(3, 4, 5, 6, [7, 8, 9], 0);
|
||||
});
|
||||
|
||||
it('should copy a region on type "copy"', function () {
|
||||
display.copyImage = sinon.spy();
|
||||
display.renderQ_push({ type: 'copy', x: 3, y: 4, width: 5, height: 6, old_x: 7, old_y: 8 });
|
||||
expect(display.copyImage).to.have.been.calledOnce;
|
||||
expect(display.copyImage).to.have.been.calledWith(7, 8, 3, 4, 5, 6);
|
||||
});
|
||||
|
||||
it('should fill a rect with a given color on type "fill"', function () {
|
||||
display.fillRect = sinon.spy();
|
||||
display.renderQ_push({ type: 'fill', x: 3, y: 4, width: 5, height: 6, color: [7, 8, 9]});
|
||||
expect(display.fillRect).to.have.been.calledOnce;
|
||||
expect(display.fillRect).to.have.been.calledWith(3, 4, 5, 6, [7, 8, 9]);
|
||||
});
|
||||
|
||||
it('should draw an image from an image object on type "img" (if complete)', function () {
|
||||
display.drawImage = sinon.spy();
|
||||
display.renderQ_push({ type: 'img', x: 3, y: 4, img: { complete: true } });
|
||||
expect(display.drawImage).to.have.been.calledOnce;
|
||||
expect(display.drawImage).to.have.been.calledWith({ complete: true }, 3, 4);
|
||||
});
|
||||
});
|
||||
});
|
||||
262
tests/test.helper.js
Normal file
@@ -0,0 +1,262 @@
|
||||
// requires local modules: keysym, keysymdef, keyboard
|
||||
|
||||
var assert = chai.assert;
|
||||
var expect = chai.expect;
|
||||
|
||||
describe('Helpers', function() {
|
||||
"use strict";
|
||||
describe('keysymFromKeyCode', function() {
|
||||
it('should map known keycodes to keysyms', function() {
|
||||
expect(kbdUtil.keysymFromKeyCode(0x41, false), 'a').to.be.equal(0x61);
|
||||
expect(kbdUtil.keysymFromKeyCode(0x41, true), 'A').to.be.equal(0x41);
|
||||
expect(kbdUtil.keysymFromKeyCode(0xd, false), 'enter').to.be.equal(0xFF0D);
|
||||
expect(kbdUtil.keysymFromKeyCode(0x11, false), 'ctrl').to.be.equal(0xFFE3);
|
||||
expect(kbdUtil.keysymFromKeyCode(0x12, false), 'alt').to.be.equal(0xFFE9);
|
||||
expect(kbdUtil.keysymFromKeyCode(0xe1, false), 'altgr').to.be.equal(0xFE03);
|
||||
expect(kbdUtil.keysymFromKeyCode(0x1b, false), 'esc').to.be.equal(0xFF1B);
|
||||
expect(kbdUtil.keysymFromKeyCode(0x26, false), 'up').to.be.equal(0xFF52);
|
||||
});
|
||||
it('should return null for unknown keycodes', function() {
|
||||
expect(kbdUtil.keysymFromKeyCode(0xc0, false), 'DK æ').to.be.null;
|
||||
expect(kbdUtil.keysymFromKeyCode(0xde, false), 'DK ø').to.be.null;
|
||||
});
|
||||
});
|
||||
|
||||
describe('keysyms.fromUnicode', function() {
|
||||
it('should map ASCII characters to keysyms', function() {
|
||||
expect(keysyms.fromUnicode('a'.charCodeAt())).to.have.property('keysym', 0x61);
|
||||
expect(keysyms.fromUnicode('A'.charCodeAt())).to.have.property('keysym', 0x41);
|
||||
});
|
||||
it('should map Latin-1 characters to keysyms', function() {
|
||||
expect(keysyms.fromUnicode('ø'.charCodeAt())).to.have.property('keysym', 0xf8);
|
||||
|
||||
expect(keysyms.fromUnicode('é'.charCodeAt())).to.have.property('keysym', 0xe9);
|
||||
});
|
||||
it('should map characters that are in Windows-1252 but not in Latin-1 to keysyms', function() {
|
||||
expect(keysyms.fromUnicode('Š'.charCodeAt())).to.have.property('keysym', 0x01a9);
|
||||
});
|
||||
it('should map characters which aren\'t in Latin1 *or* Windows-1252 to keysyms', function() {
|
||||
expect(keysyms.fromUnicode('ŵ'.charCodeAt())).to.have.property('keysym', 0x1000175);
|
||||
});
|
||||
it('should return undefined for unknown codepoints', function() {
|
||||
expect(keysyms.fromUnicode('\n'.charCodeAt())).to.be.undefined;
|
||||
expect(keysyms.fromUnicode('\u1F686'.charCodeAt())).to.be.undefined;
|
||||
});
|
||||
});
|
||||
|
||||
describe('substituteCodepoint', function() {
|
||||
it('should replace characters which don\'t have a keysym', function() {
|
||||
expect(kbdUtil.substituteCodepoint('Ș'.charCodeAt())).to.equal('Ş'.charCodeAt());
|
||||
expect(kbdUtil.substituteCodepoint('ș'.charCodeAt())).to.equal('ş'.charCodeAt());
|
||||
expect(kbdUtil.substituteCodepoint('Ț'.charCodeAt())).to.equal('Ţ'.charCodeAt());
|
||||
expect(kbdUtil.substituteCodepoint('ț'.charCodeAt())).to.equal('ţ'.charCodeAt());
|
||||
});
|
||||
it('should pass other characters through unchanged', function() {
|
||||
expect(kbdUtil.substituteCodepoint('T'.charCodeAt())).to.equal('T'.charCodeAt());
|
||||
});
|
||||
});
|
||||
|
||||
describe('nonCharacterKey', function() {
|
||||
it('should recognize the right keys', function() {
|
||||
expect(kbdUtil.nonCharacterKey({keyCode: 0xd}), 'enter').to.be.defined;
|
||||
expect(kbdUtil.nonCharacterKey({keyCode: 0x08}), 'backspace').to.be.defined;
|
||||
expect(kbdUtil.nonCharacterKey({keyCode: 0x09}), 'tab').to.be.defined;
|
||||
expect(kbdUtil.nonCharacterKey({keyCode: 0x10}), 'shift').to.be.defined;
|
||||
expect(kbdUtil.nonCharacterKey({keyCode: 0x11}), 'ctrl').to.be.defined;
|
||||
expect(kbdUtil.nonCharacterKey({keyCode: 0x12}), 'alt').to.be.defined;
|
||||
expect(kbdUtil.nonCharacterKey({keyCode: 0xe0}), 'meta').to.be.defined;
|
||||
});
|
||||
it('should not recognize character keys', function() {
|
||||
expect(kbdUtil.nonCharacterKey({keyCode: 'A'}), 'A').to.be.null;
|
||||
expect(kbdUtil.nonCharacterKey({keyCode: '1'}), '1').to.be.null;
|
||||
expect(kbdUtil.nonCharacterKey({keyCode: '.'}), '.').to.be.null;
|
||||
expect(kbdUtil.nonCharacterKey({keyCode: ' '}), 'space').to.be.null;
|
||||
});
|
||||
});
|
||||
|
||||
describe('getKeysym', function() {
|
||||
it('should prefer char', function() {
|
||||
expect(kbdUtil.getKeysym({char : 'a', charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43})).to.have.property('keysym', 0x61);
|
||||
});
|
||||
it('should use charCode if no char', function() {
|
||||
expect(kbdUtil.getKeysym({char : '', charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43})).to.have.property('keysym', 0x01a9);
|
||||
expect(kbdUtil.getKeysym({charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43})).to.have.property('keysym', 0x01a9);
|
||||
expect(kbdUtil.getKeysym({char : 'hello', charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43})).to.have.property('keysym', 0x01a9);
|
||||
});
|
||||
it('should use keyCode if no charCode', function() {
|
||||
expect(kbdUtil.getKeysym({keyCode: 0x42, which: 0x43, shiftKey: false})).to.have.property('keysym', 0x62);
|
||||
expect(kbdUtil.getKeysym({keyCode: 0x42, which: 0x43, shiftKey: true})).to.have.property('keysym', 0x42);
|
||||
});
|
||||
it('should use which if no keyCode', function() {
|
||||
expect(kbdUtil.getKeysym({which: 0x43, shiftKey: false})).to.have.property('keysym', 0x63);
|
||||
expect(kbdUtil.getKeysym({which: 0x43, shiftKey: true})).to.have.property('keysym', 0x43);
|
||||
});
|
||||
it('should substitute where applicable', function() {
|
||||
expect(kbdUtil.getKeysym({char : 'Ș'})).to.have.property('keysym', 0x1aa);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Modifier Sync', function() { // return a list of fake events necessary to fix modifier state
|
||||
describe('Toggle all modifiers', function() {
|
||||
var sync = kbdUtil.ModifierSync();
|
||||
it ('should do nothing if all modifiers are up as expected', function() {
|
||||
expect(sync.keydown({
|
||||
keyCode: 0x41,
|
||||
ctrlKey: false,
|
||||
altKey: false,
|
||||
altGraphKey: false,
|
||||
shiftKey: false,
|
||||
metaKey: false})
|
||||
).to.have.lengthOf(0);
|
||||
});
|
||||
it ('should synthesize events if all keys are unexpectedly down', function() {
|
||||
var result = sync.keydown({
|
||||
keyCode: 0x41,
|
||||
ctrlKey: true,
|
||||
altKey: true,
|
||||
altGraphKey: true,
|
||||
shiftKey: true,
|
||||
metaKey: true
|
||||
});
|
||||
expect(result).to.have.lengthOf(5);
|
||||
var keysyms = {};
|
||||
for (var i = 0; i < result.length; ++i) {
|
||||
keysyms[result[i].keysym] = (result[i].type == 'keydown');
|
||||
}
|
||||
expect(keysyms[0xffe3]);
|
||||
expect(keysyms[0xffe9]);
|
||||
expect(keysyms[0xfe03]);
|
||||
expect(keysyms[0xffe1]);
|
||||
expect(keysyms[0xffe7]);
|
||||
});
|
||||
it ('should do nothing if all modifiers are down as expected', function() {
|
||||
expect(sync.keydown({
|
||||
keyCode: 0x41,
|
||||
ctrlKey: true,
|
||||
altKey: true,
|
||||
altGraphKey: true,
|
||||
shiftKey: true,
|
||||
metaKey: true
|
||||
})).to.have.lengthOf(0);
|
||||
});
|
||||
});
|
||||
describe('Toggle Ctrl', function() {
|
||||
var sync = kbdUtil.ModifierSync();
|
||||
it('should sync if modifier is suddenly down', function() {
|
||||
expect(sync.keydown({
|
||||
keyCode: 0x41,
|
||||
ctrlKey: true,
|
||||
})).to.be.deep.equal([{keysym: keysyms.lookup(0xffe3), type: 'keydown'}]);
|
||||
});
|
||||
it('should sync if modifier is suddenly up', function() {
|
||||
expect(sync.keydown({
|
||||
keyCode: 0x41,
|
||||
ctrlKey: false
|
||||
})).to.be.deep.equal([{keysym: keysyms.lookup(0xffe3), type: 'keyup'}]);
|
||||
});
|
||||
});
|
||||
describe('Toggle Alt', function() {
|
||||
var sync = kbdUtil.ModifierSync();
|
||||
it('should sync if modifier is suddenly down', function() {
|
||||
expect(sync.keydown({
|
||||
keyCode: 0x41,
|
||||
altKey: true,
|
||||
})).to.be.deep.equal([{keysym: keysyms.lookup(0xffe9), type: 'keydown'}]);
|
||||
});
|
||||
it('should sync if modifier is suddenly up', function() {
|
||||
expect(sync.keydown({
|
||||
keyCode: 0x41,
|
||||
altKey: false
|
||||
})).to.be.deep.equal([{keysym: keysyms.lookup(0xffe9), type: 'keyup'}]);
|
||||
});
|
||||
});
|
||||
describe('Toggle AltGr', function() {
|
||||
var sync = kbdUtil.ModifierSync();
|
||||
it('should sync if modifier is suddenly down', function() {
|
||||
expect(sync.keydown({
|
||||
keyCode: 0x41,
|
||||
altGraphKey: true,
|
||||
})).to.be.deep.equal([{keysym: keysyms.lookup(0xfe03), type: 'keydown'}]);
|
||||
});
|
||||
it('should sync if modifier is suddenly up', function() {
|
||||
expect(sync.keydown({
|
||||
keyCode: 0x41,
|
||||
altGraphKey: false
|
||||
})).to.be.deep.equal([{keysym: keysyms.lookup(0xfe03), type: 'keyup'}]);
|
||||
});
|
||||
});
|
||||
describe('Toggle Shift', function() {
|
||||
var sync = kbdUtil.ModifierSync();
|
||||
it('should sync if modifier is suddenly down', function() {
|
||||
expect(sync.keydown({
|
||||
keyCode: 0x41,
|
||||
shiftKey: true,
|
||||
})).to.be.deep.equal([{keysym: keysyms.lookup(0xffe1), type: 'keydown'}]);
|
||||
});
|
||||
it('should sync if modifier is suddenly up', function() {
|
||||
expect(sync.keydown({
|
||||
keyCode: 0x41,
|
||||
shiftKey: false
|
||||
})).to.be.deep.equal([{keysym: keysyms.lookup(0xffe1), type: 'keyup'}]);
|
||||
});
|
||||
});
|
||||
describe('Toggle Meta', function() {
|
||||
var sync = kbdUtil.ModifierSync();
|
||||
it('should sync if modifier is suddenly down', function() {
|
||||
expect(sync.keydown({
|
||||
keyCode: 0x41,
|
||||
metaKey: true,
|
||||
})).to.be.deep.equal([{keysym: keysyms.lookup(0xffe7), type: 'keydown'}]);
|
||||
});
|
||||
it('should sync if modifier is suddenly up', function() {
|
||||
expect(sync.keydown({
|
||||
keyCode: 0x41,
|
||||
metaKey: false
|
||||
})).to.be.deep.equal([{keysym: keysyms.lookup(0xffe7), type: 'keyup'}]);
|
||||
});
|
||||
});
|
||||
describe('Modifier keyevents', function() {
|
||||
it('should not sync a modifier on its own events', function() {
|
||||
expect(kbdUtil.ModifierSync().keydown({
|
||||
keyCode: 0x11,
|
||||
ctrlKey: false
|
||||
})).to.be.deep.equal([]);
|
||||
expect(kbdUtil.ModifierSync().keydown({
|
||||
keyCode: 0x11,
|
||||
ctrlKey: true
|
||||
}), 'B').to.be.deep.equal([]);
|
||||
})
|
||||
it('should update state on modifier keyevents', function() {
|
||||
var sync = kbdUtil.ModifierSync();
|
||||
sync.keydown({
|
||||
keyCode: 0x11,
|
||||
});
|
||||
expect(sync.keydown({
|
||||
keyCode: 0x41,
|
||||
ctrlKey: true,
|
||||
})).to.be.deep.equal([]);
|
||||
});
|
||||
it('should sync other modifiers on ctrl events', function() {
|
||||
expect(kbdUtil.ModifierSync().keydown({
|
||||
keyCode: 0x11,
|
||||
altKey: true
|
||||
})).to.be.deep.equal([{keysym: keysyms.lookup(0xffe9), type: 'keydown'}]);
|
||||
})
|
||||
});
|
||||
describe('sync modifiers on non-key events', function() {
|
||||
it('should generate sync events when receiving non-keyboard events', function() {
|
||||
expect(kbdUtil.ModifierSync().syncAny({
|
||||
altKey: true
|
||||
})).to.be.deep.equal([{keysym: keysyms.lookup(0xffe9), type: 'keydown'}]);
|
||||
});
|
||||
});
|
||||
describe('do not treat shift as a modifier key', function() {
|
||||
it('should not treat shift as a shortcut modifier', function() {
|
||||
expect(kbdUtil.hasShortcutModifier([], {0xffe1 : true})).to.be.false;
|
||||
});
|
||||
it('should not treat shift as a char modifier', function() {
|
||||
expect(kbdUtil.hasCharModifier([], {0xffe1 : true})).to.be.false;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
842
tests/test.keyboard.js
Normal file
@@ -0,0 +1,842 @@
|
||||
// requires local modules: input, keyboard, keysymdef
|
||||
var assert = chai.assert;
|
||||
var expect = chai.expect;
|
||||
|
||||
/* jshint newcap: false, expr: true */
|
||||
describe('Key Event Pipeline Stages', function() {
|
||||
"use strict";
|
||||
describe('Decode Keyboard Events', function() {
|
||||
it('should pass events to the next stage', function(done) {
|
||||
KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {
|
||||
expect(evt).to.be.an.object;
|
||||
done();
|
||||
}).keydown({keyCode: 0x41});
|
||||
});
|
||||
it('should pass the right keysym through', function(done) {
|
||||
KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {
|
||||
expect(evt.keysym).to.be.deep.equal(keysyms.lookup(0x61));
|
||||
done();
|
||||
}).keypress({keyCode: 0x41});
|
||||
});
|
||||
it('should pass the right keyid through', function(done) {
|
||||
KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {
|
||||
expect(evt).to.have.property('keyId', 0x41);
|
||||
done();
|
||||
}).keydown({keyCode: 0x41});
|
||||
});
|
||||
it('should not sync modifiers on a keypress', function() {
|
||||
// Firefox provides unreliable modifier state on keypress events
|
||||
var count = 0;
|
||||
KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {
|
||||
++count;
|
||||
}).keypress({keyCode: 0x41, ctrlKey: true});
|
||||
expect(count).to.be.equal(1);
|
||||
});
|
||||
it('should sync modifiers if necessary', function(done) {
|
||||
var count = 0;
|
||||
KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {
|
||||
switch (count) {
|
||||
case 0: // fake a ctrl keydown
|
||||
expect(evt).to.be.deep.equal({keysym: keysyms.lookup(0xffe3), type: 'keydown'});
|
||||
++count;
|
||||
break;
|
||||
case 1:
|
||||
expect(evt).to.be.deep.equal({keyId: 0x41, type: 'keydown', keysym: keysyms.lookup(0x61)});
|
||||
done();
|
||||
break;
|
||||
}
|
||||
}).keydown({keyCode: 0x41, ctrlKey: true});
|
||||
});
|
||||
it('should forward keydown events with the right type', function(done) {
|
||||
KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {
|
||||
expect(evt).to.be.deep.equal({keyId: 0x41, type: 'keydown'});
|
||||
done();
|
||||
}).keydown({keyCode: 0x41});
|
||||
});
|
||||
it('should forward keyup events with the right type', function(done) {
|
||||
KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {
|
||||
expect(evt).to.be.deep.equal({keyId: 0x41, keysym: keysyms.lookup(0x61), type: 'keyup'});
|
||||
done();
|
||||
}).keyup({keyCode: 0x41});
|
||||
});
|
||||
it('should forward keypress events with the right type', function(done) {
|
||||
KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {
|
||||
expect(evt).to.be.deep.equal({keyId: 0x41, keysym: keysyms.lookup(0x61), type: 'keypress'});
|
||||
done();
|
||||
}).keypress({keyCode: 0x41});
|
||||
});
|
||||
it('should generate stalls if a char modifier is down while a key is pressed', function(done) {
|
||||
var count = 0;
|
||||
KeyEventDecoder(kbdUtil.ModifierSync([0xfe03]), function(evt) {
|
||||
switch (count) {
|
||||
case 0: // fake altgr
|
||||
expect(evt).to.be.deep.equal({keysym: keysyms.lookup(0xfe03), type: 'keydown'});
|
||||
++count;
|
||||
break;
|
||||
case 1: // stall before processing the 'a' keydown
|
||||
expect(evt).to.be.deep.equal({type: 'stall'});
|
||||
++count;
|
||||
break;
|
||||
case 2: // 'a'
|
||||
expect(evt).to.be.deep.equal({
|
||||
type: 'keydown',
|
||||
keyId: 0x41,
|
||||
keysym: keysyms.lookup(0x61)
|
||||
});
|
||||
|
||||
done();
|
||||
break;
|
||||
}
|
||||
}).keydown({keyCode: 0x41, altGraphKey: true});
|
||||
|
||||
});
|
||||
describe('suppress the right events at the right time', function() {
|
||||
it('should suppress anything while a shortcut modifier is down', function() {
|
||||
var obj = KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {});
|
||||
|
||||
obj.keydown({keyCode: 0x11}); // press ctrl
|
||||
expect(obj.keydown({keyCode: 'A'.charCodeAt()})).to.be.true;
|
||||
expect(obj.keydown({keyCode: ' '.charCodeAt()})).to.be.true;
|
||||
expect(obj.keydown({keyCode: '1'.charCodeAt()})).to.be.true;
|
||||
expect(obj.keydown({keyCode: 0x3c})).to.be.true; // < key on DK Windows
|
||||
expect(obj.keydown({keyCode: 0xde})).to.be.true; // Ø key on DK
|
||||
});
|
||||
it('should suppress non-character keys', function() {
|
||||
var obj = KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {});
|
||||
|
||||
expect(obj.keydown({keyCode: 0x08}), 'a').to.be.true;
|
||||
expect(obj.keydown({keyCode: 0x09}), 'b').to.be.true;
|
||||
expect(obj.keydown({keyCode: 0x11}), 'd').to.be.true;
|
||||
expect(obj.keydown({keyCode: 0x12}), 'e').to.be.true;
|
||||
});
|
||||
it('should not suppress shift', function() {
|
||||
var obj = KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {});
|
||||
|
||||
expect(obj.keydown({keyCode: 0x10}), 'd').to.be.false;
|
||||
});
|
||||
it('should generate event for shift keydown', function() {
|
||||
var called = false;
|
||||
var obj = KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {
|
||||
expect(evt).to.have.property('keysym');
|
||||
called = true;
|
||||
}).keydown({keyCode: 0x10});
|
||||
expect(called).to.be.true;
|
||||
});
|
||||
it('should not suppress character keys', function() {
|
||||
var obj = KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {});
|
||||
|
||||
expect(obj.keydown({keyCode: 'A'.charCodeAt()})).to.be.false;
|
||||
expect(obj.keydown({keyCode: ' '.charCodeAt()})).to.be.false;
|
||||
expect(obj.keydown({keyCode: '1'.charCodeAt()})).to.be.false;
|
||||
expect(obj.keydown({keyCode: 0x3c})).to.be.false; // < key on DK Windows
|
||||
expect(obj.keydown({keyCode: 0xde})).to.be.false; // Ø key on DK
|
||||
});
|
||||
it('should not suppress if a char modifier is down', function() {
|
||||
var obj = KeyEventDecoder(kbdUtil.ModifierSync([0xfe03]), function(evt) {});
|
||||
|
||||
obj.keydown({keyCode: 0xe1}); // press altgr
|
||||
expect(obj.keydown({keyCode: 'A'.charCodeAt()})).to.be.false;
|
||||
expect(obj.keydown({keyCode: ' '.charCodeAt()})).to.be.false;
|
||||
expect(obj.keydown({keyCode: '1'.charCodeAt()})).to.be.false;
|
||||
expect(obj.keydown({keyCode: 0x3c})).to.be.false; // < key on DK Windows
|
||||
expect(obj.keydown({keyCode: 0xde})).to.be.false; // Ø key on DK
|
||||
});
|
||||
});
|
||||
describe('Keypress and keyup events', function() {
|
||||
it('should always suppress event propagation', function() {
|
||||
var obj = KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {});
|
||||
|
||||
expect(obj.keypress({keyCode: 'A'.charCodeAt()})).to.be.true;
|
||||
expect(obj.keypress({keyCode: 0x3c})).to.be.true; // < key on DK Windows
|
||||
expect(obj.keypress({keyCode: 0x11})).to.be.true;
|
||||
|
||||
expect(obj.keyup({keyCode: 'A'.charCodeAt()})).to.be.true;
|
||||
expect(obj.keyup({keyCode: 0x3c})).to.be.true; // < key on DK Windows
|
||||
expect(obj.keyup({keyCode: 0x11})).to.be.true;
|
||||
});
|
||||
it('should never generate stalls', function() {
|
||||
var obj = KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {
|
||||
expect(evt.type).to.not.be.equal('stall');
|
||||
});
|
||||
|
||||
obj.keypress({keyCode: 'A'.charCodeAt()});
|
||||
obj.keypress({keyCode: 0x3c});
|
||||
obj.keypress({keyCode: 0x11});
|
||||
|
||||
obj.keyup({keyCode: 'A'.charCodeAt()});
|
||||
obj.keyup({keyCode: 0x3c});
|
||||
obj.keyup({keyCode: 0x11});
|
||||
});
|
||||
});
|
||||
describe('mark events if a char modifier is down', function() {
|
||||
it('should not mark modifiers on a keydown event', function() {
|
||||
var times_called = 0;
|
||||
var obj = KeyEventDecoder(kbdUtil.ModifierSync([0xfe03]), function(evt) {
|
||||
switch (times_called++) {
|
||||
case 0: //altgr
|
||||
break;
|
||||
case 1: // 'a'
|
||||
expect(evt).to.not.have.property('escape');
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
obj.keydown({keyCode: 0xe1}); // press altgr
|
||||
obj.keydown({keyCode: 'A'.charCodeAt()});
|
||||
});
|
||||
|
||||
it('should indicate on events if a single-key char modifier is down', function(done) {
|
||||
var times_called = 0;
|
||||
var obj = KeyEventDecoder(kbdUtil.ModifierSync([0xfe03]), function(evt) {
|
||||
switch (times_called++) {
|
||||
case 0: //altgr
|
||||
break;
|
||||
case 1: // 'a'
|
||||
expect(evt).to.be.deep.equal({
|
||||
type: 'keypress',
|
||||
keyId: 'A'.charCodeAt(),
|
||||
keysym: keysyms.lookup('a'.charCodeAt()),
|
||||
escape: [0xfe03]
|
||||
});
|
||||
done();
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
obj.keydown({keyCode: 0xe1}); // press altgr
|
||||
obj.keypress({keyCode: 'A'.charCodeAt()});
|
||||
});
|
||||
it('should indicate on events if a multi-key char modifier is down', function(done) {
|
||||
var times_called = 0;
|
||||
var obj = KeyEventDecoder(kbdUtil.ModifierSync([0xffe9, 0xffe3]), function(evt) {
|
||||
switch (times_called++) {
|
||||
case 0: //ctrl
|
||||
break;
|
||||
case 1: //alt
|
||||
break;
|
||||
case 2: // 'a'
|
||||
expect(evt).to.be.deep.equal({
|
||||
type: 'keypress',
|
||||
keyId: 'A'.charCodeAt(),
|
||||
keysym: keysyms.lookup('a'.charCodeAt()),
|
||||
escape: [0xffe9, 0xffe3]
|
||||
});
|
||||
done();
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
obj.keydown({keyCode: 0x11}); // press ctrl
|
||||
obj.keydown({keyCode: 0x12}); // press alt
|
||||
obj.keypress({keyCode: 'A'.charCodeAt()});
|
||||
});
|
||||
it('should not consider a char modifier to be down on the modifier key itself', function() {
|
||||
var obj = KeyEventDecoder(kbdUtil.ModifierSync([0xfe03]), function(evt) {
|
||||
expect(evt).to.not.have.property('escape');
|
||||
});
|
||||
|
||||
obj.keydown({keyCode: 0xe1}); // press altgr
|
||||
|
||||
});
|
||||
});
|
||||
describe('add/remove keysym', function() {
|
||||
it('should remove keysym from keydown if a char key and no modifier', function() {
|
||||
KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {
|
||||
expect(evt).to.be.deep.equal({keyId: 0x41, type: 'keydown'});
|
||||
}).keydown({keyCode: 0x41});
|
||||
});
|
||||
it('should not remove keysym from keydown if a shortcut modifier is down', function() {
|
||||
var times_called = 0;
|
||||
KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {
|
||||
switch (times_called++) {
|
||||
case 1:
|
||||
expect(evt).to.be.deep.equal({keyId: 0x41, keysym: keysyms.lookup(0x61), type: 'keydown'});
|
||||
break;
|
||||
}
|
||||
}).keydown({keyCode: 0x41, ctrlKey: true});
|
||||
expect(times_called).to.be.equal(2);
|
||||
});
|
||||
it('should not remove keysym from keydown if a char modifier is down', function() {
|
||||
var times_called = 0;
|
||||
KeyEventDecoder(kbdUtil.ModifierSync([0xfe03]), function(evt) {
|
||||
switch (times_called++) {
|
||||
case 2:
|
||||
expect(evt).to.be.deep.equal({keyId: 0x41, keysym: keysyms.lookup(0x61), type: 'keydown'});
|
||||
break;
|
||||
}
|
||||
}).keydown({keyCode: 0x41, altGraphKey: true});
|
||||
expect(times_called).to.be.equal(3);
|
||||
});
|
||||
it('should not remove keysym from keydown if key is noncharacter', function() {
|
||||
KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {
|
||||
expect(evt, 'bacobjpace').to.be.deep.equal({keyId: 0x09, keysym: keysyms.lookup(0xff09), type: 'keydown'});
|
||||
}).keydown({keyCode: 0x09});
|
||||
|
||||
KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {
|
||||
expect(evt, 'ctrl').to.be.deep.equal({keyId: 0x11, keysym: keysyms.lookup(0xffe3), type: 'keydown'});
|
||||
}).keydown({keyCode: 0x11});
|
||||
});
|
||||
it('should never remove keysym from keypress', function() {
|
||||
KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {
|
||||
expect(evt).to.be.deep.equal({keyId: 0x41, keysym: keysyms.lookup(0x61), type: 'keypress'});
|
||||
}).keypress({keyCode: 0x41});
|
||||
});
|
||||
it('should never remove keysym from keyup', function() {
|
||||
KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {
|
||||
expect(evt).to.be.deep.equal({keyId: 0x41, keysym: keysyms.lookup(0x61), type: 'keyup'});
|
||||
}).keyup({keyCode: 0x41});
|
||||
});
|
||||
});
|
||||
// on keypress, keyup(?), always set keysym
|
||||
// on keydown, only do it if we don't expect a keypress: if noncharacter OR modifier is down
|
||||
});
|
||||
|
||||
describe('Verify that char modifiers are active', function() {
|
||||
it('should pass keydown events through if there is no stall', function(done) {
|
||||
var obj = VerifyCharModifier(function(evt){
|
||||
expect(evt).to.deep.equal({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x41)});
|
||||
done();
|
||||
})({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x41)});
|
||||
});
|
||||
it('should pass keyup events through if there is no stall', function(done) {
|
||||
var obj = VerifyCharModifier(function(evt){
|
||||
expect(evt).to.deep.equal({type: 'keyup', keyId: 0x41, keysym: keysyms.lookup(0x41)});
|
||||
done();
|
||||
})({type: 'keyup', keyId: 0x41, keysym: keysyms.lookup(0x41)});
|
||||
});
|
||||
it('should pass keypress events through if there is no stall', function(done) {
|
||||
var obj = VerifyCharModifier(function(evt){
|
||||
expect(evt).to.deep.equal({type: 'keypress', keyId: 0x41, keysym: keysyms.lookup(0x41)});
|
||||
done();
|
||||
})({type: 'keypress', keyId: 0x41, keysym: keysyms.lookup(0x41)});
|
||||
});
|
||||
it('should not pass stall events through', function(done){
|
||||
var obj = VerifyCharModifier(function(evt){
|
||||
// should only be called once, for the keydown
|
||||
expect(evt).to.deep.equal({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x41)});
|
||||
done();
|
||||
});
|
||||
|
||||
obj({type: 'stall'});
|
||||
obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x41)});
|
||||
});
|
||||
it('should merge keydown and keypress events if they come after a stall', function(done) {
|
||||
var next_called = false;
|
||||
var obj = VerifyCharModifier(function(evt){
|
||||
// should only be called once, for the keydown
|
||||
expect(next_called).to.be.false;
|
||||
next_called = true;
|
||||
expect(evt).to.deep.equal({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x44)});
|
||||
done();
|
||||
});
|
||||
|
||||
obj({type: 'stall'});
|
||||
obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)});
|
||||
obj({type: 'keypress', keyId: 0x43, keysym: keysyms.lookup(0x44)});
|
||||
expect(next_called).to.be.false;
|
||||
});
|
||||
it('should preserve modifier attribute when merging if keysyms differ', function(done) {
|
||||
var next_called = false;
|
||||
var obj = VerifyCharModifier(function(evt){
|
||||
// should only be called once, for the keydown
|
||||
expect(next_called).to.be.false;
|
||||
next_called = true;
|
||||
expect(evt).to.deep.equal({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x44), escape: [0xffe3]});
|
||||
done();
|
||||
});
|
||||
|
||||
obj({type: 'stall'});
|
||||
obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)});
|
||||
obj({type: 'keypress', keyId: 0x43, keysym: keysyms.lookup(0x44), escape: [0xffe3]});
|
||||
expect(next_called).to.be.false;
|
||||
});
|
||||
it('should not preserve modifier attribute when merging if keysyms are the same', function() {
|
||||
var obj = VerifyCharModifier(function(evt){
|
||||
expect(evt).to.not.have.property('escape');
|
||||
});
|
||||
|
||||
obj({type: 'stall'});
|
||||
obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)});
|
||||
obj({type: 'keypress', keyId: 0x43, keysym: keysyms.lookup(0x42), escape: [0xffe3]});
|
||||
});
|
||||
it('should not merge keydown and keypress events if there is no stall', function(done) {
|
||||
var times_called = 0;
|
||||
var obj = VerifyCharModifier(function(evt){
|
||||
switch(times_called) {
|
||||
case 0:
|
||||
expect(evt).to.deep.equal({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)});
|
||||
break;
|
||||
case 1:
|
||||
expect(evt).to.deep.equal({type: 'keypress', keyId: 0x43, keysym: keysyms.lookup(0x44)});
|
||||
done();
|
||||
break;
|
||||
}
|
||||
|
||||
++times_called;
|
||||
});
|
||||
|
||||
obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)});
|
||||
obj({type: 'keypress', keyId: 0x43, keysym: keysyms.lookup(0x44)});
|
||||
});
|
||||
it('should not merge keydown and keypress events if separated by another event', function(done) {
|
||||
var times_called = 0;
|
||||
var obj = VerifyCharModifier(function(evt){
|
||||
switch(times_called) {
|
||||
case 0:
|
||||
expect(evt,1).to.deep.equal({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)});
|
||||
break;
|
||||
case 1:
|
||||
expect(evt,2).to.deep.equal({type: 'keyup', keyId: 0x43, keysym: keysyms.lookup(0x44)});
|
||||
break;
|
||||
case 2:
|
||||
expect(evt,3).to.deep.equal({type: 'keypress', keyId: 0x45, keysym: keysyms.lookup(0x46)});
|
||||
done();
|
||||
break;
|
||||
}
|
||||
|
||||
++times_called;
|
||||
});
|
||||
|
||||
obj({type: 'stall'});
|
||||
obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)});
|
||||
obj({type: 'keyup', keyId: 0x43, keysym: keysyms.lookup(0x44)});
|
||||
obj({type: 'keypress', keyId: 0x45, keysym: keysyms.lookup(0x46)});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Track Key State', function() {
|
||||
it('should do nothing on keyup events if no keys are down', function() {
|
||||
var obj = TrackKeyState(function(evt) {
|
||||
expect(true).to.be.false;
|
||||
});
|
||||
obj({type: 'keyup', keyId: 0x41});
|
||||
});
|
||||
it('should insert into the queue on keydown if no keys are down', function() {
|
||||
var times_called = 0;
|
||||
var elem = null;
|
||||
var keysymsdown = {};
|
||||
var obj = TrackKeyState(function(evt) {
|
||||
++times_called;
|
||||
if (elem.type == 'keyup') {
|
||||
expect(evt).to.have.property('keysym');
|
||||
expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
|
||||
delete keysymsdown[evt.keysym.keysym];
|
||||
}
|
||||
else {
|
||||
expect(evt).to.be.deep.equal(elem);
|
||||
expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
|
||||
}
|
||||
elem = null;
|
||||
});
|
||||
|
||||
expect(elem).to.be.null;
|
||||
elem = {type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)};
|
||||
keysymsdown[keysyms.lookup(0x42).keysym] = true;
|
||||
obj(elem);
|
||||
expect(elem).to.be.null;
|
||||
elem = {type: 'keyup', keyId: 0x41};
|
||||
obj(elem);
|
||||
expect(elem).to.be.null;
|
||||
expect(times_called).to.be.equal(2);
|
||||
});
|
||||
it('should insert into the queue on keypress if no keys are down', function() {
|
||||
var times_called = 0;
|
||||
var elem = null;
|
||||
var keysymsdown = {};
|
||||
var obj = TrackKeyState(function(evt) {
|
||||
++times_called;
|
||||
if (elem.type == 'keyup') {
|
||||
expect(evt).to.have.property('keysym');
|
||||
expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
|
||||
delete keysymsdown[evt.keysym.keysym];
|
||||
}
|
||||
else {
|
||||
expect(evt).to.be.deep.equal(elem);
|
||||
expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
|
||||
}
|
||||
elem = null;
|
||||
});
|
||||
|
||||
expect(elem).to.be.null;
|
||||
elem = {type: 'keypress', keyId: 0x41, keysym: keysyms.lookup(0x42)};
|
||||
keysymsdown[keysyms.lookup(0x42).keysym] = true;
|
||||
obj(elem);
|
||||
expect(elem).to.be.null;
|
||||
elem = {type: 'keyup', keyId: 0x41};
|
||||
obj(elem);
|
||||
expect(elem).to.be.null;
|
||||
expect(times_called).to.be.equal(2);
|
||||
});
|
||||
it('should add keysym to last key entry if keyId matches', function() {
|
||||
// this implies that a single keyup will release both keysyms
|
||||
var times_called = 0;
|
||||
var elem = null;
|
||||
var keysymsdown = {};
|
||||
var obj = TrackKeyState(function(evt) {
|
||||
++times_called;
|
||||
if (elem.type == 'keyup') {
|
||||
expect(evt).to.have.property('keysym');
|
||||
expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
|
||||
delete keysymsdown[evt.keysym.keysym];
|
||||
}
|
||||
else {
|
||||
expect(evt).to.be.deep.equal(elem);
|
||||
expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
|
||||
elem = null;
|
||||
}
|
||||
});
|
||||
|
||||
expect(elem).to.be.null;
|
||||
elem = {type: 'keypress', keyId: 0x41, keysym: keysyms.lookup(0x42)};
|
||||
keysymsdown[keysyms.lookup(0x42).keysym] = true;
|
||||
obj(elem);
|
||||
expect(elem).to.be.null;
|
||||
elem = {type: 'keypress', keyId: 0x41, keysym: keysyms.lookup(0x43)};
|
||||
keysymsdown[keysyms.lookup(0x43).keysym] = true;
|
||||
obj(elem);
|
||||
expect(elem).to.be.null;
|
||||
elem = {type: 'keyup', keyId: 0x41};
|
||||
obj(elem);
|
||||
expect(times_called).to.be.equal(4);
|
||||
});
|
||||
it('should create new key entry if keyId matches and keysym does not', function() {
|
||||
// this implies that a single keyup will release both keysyms
|
||||
var times_called = 0;
|
||||
var elem = null;
|
||||
var keysymsdown = {};
|
||||
var obj = TrackKeyState(function(evt) {
|
||||
++times_called;
|
||||
if (elem.type == 'keyup') {
|
||||
expect(evt).to.have.property('keysym');
|
||||
expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
|
||||
delete keysymsdown[evt.keysym.keysym];
|
||||
}
|
||||
else {
|
||||
expect(evt).to.be.deep.equal(elem);
|
||||
expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
|
||||
elem = null;
|
||||
}
|
||||
});
|
||||
|
||||
expect(elem).to.be.null;
|
||||
elem = {type: 'keydown', keyId: 0, keysym: keysyms.lookup(0x42)};
|
||||
keysymsdown[keysyms.lookup(0x42).keysym] = true;
|
||||
obj(elem);
|
||||
expect(elem).to.be.null;
|
||||
elem = {type: 'keydown', keyId: 0, keysym: keysyms.lookup(0x43)};
|
||||
keysymsdown[keysyms.lookup(0x43).keysym] = true;
|
||||
obj(elem);
|
||||
expect(times_called).to.be.equal(2);
|
||||
expect(elem).to.be.null;
|
||||
elem = {type: 'keyup', keyId: 0};
|
||||
obj(elem);
|
||||
expect(times_called).to.be.equal(3);
|
||||
elem = {type: 'keyup', keyId: 0};
|
||||
obj(elem);
|
||||
expect(times_called).to.be.equal(4);
|
||||
});
|
||||
it('should merge key entry if keyIds are zero and keysyms match', function() {
|
||||
// this implies that a single keyup will release both keysyms
|
||||
var times_called = 0;
|
||||
var elem = null;
|
||||
var keysymsdown = {};
|
||||
var obj = TrackKeyState(function(evt) {
|
||||
++times_called;
|
||||
if (elem.type == 'keyup') {
|
||||
expect(evt).to.have.property('keysym');
|
||||
expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
|
||||
delete keysymsdown[evt.keysym.keysym];
|
||||
}
|
||||
else {
|
||||
expect(evt).to.be.deep.equal(elem);
|
||||
expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
|
||||
elem = null;
|
||||
}
|
||||
});
|
||||
|
||||
expect(elem).to.be.null;
|
||||
elem = {type: 'keydown', keyId: 0, keysym: keysyms.lookup(0x42)};
|
||||
keysymsdown[keysyms.lookup(0x42).keysym] = true;
|
||||
obj(elem);
|
||||
expect(elem).to.be.null;
|
||||
elem = {type: 'keydown', keyId: 0, keysym: keysyms.lookup(0x42)};
|
||||
keysymsdown[keysyms.lookup(0x42).keysym] = true;
|
||||
obj(elem);
|
||||
expect(times_called).to.be.equal(2);
|
||||
expect(elem).to.be.null;
|
||||
elem = {type: 'keyup', keyId: 0};
|
||||
obj(elem);
|
||||
expect(times_called).to.be.equal(3);
|
||||
});
|
||||
it('should add keysym as separate entry if keyId does not match last event', function() {
|
||||
// this implies that separate keyups are required
|
||||
var times_called = 0;
|
||||
var elem = null;
|
||||
var keysymsdown = {};
|
||||
var obj = TrackKeyState(function(evt) {
|
||||
++times_called;
|
||||
if (elem.type == 'keyup') {
|
||||
expect(evt).to.have.property('keysym');
|
||||
expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
|
||||
delete keysymsdown[evt.keysym.keysym];
|
||||
}
|
||||
else {
|
||||
expect(evt).to.be.deep.equal(elem);
|
||||
expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
|
||||
elem = null;
|
||||
}
|
||||
});
|
||||
|
||||
expect(elem).to.be.null;
|
||||
elem = {type: 'keypress', keyId: 0x41, keysym: keysyms.lookup(0x42)};
|
||||
keysymsdown[keysyms.lookup(0x42).keysym] = true;
|
||||
obj(elem);
|
||||
expect(elem).to.be.null;
|
||||
elem = {type: 'keypress', keyId: 0x42, keysym: keysyms.lookup(0x43)};
|
||||
keysymsdown[keysyms.lookup(0x43).keysym] = true;
|
||||
obj(elem);
|
||||
expect(elem).to.be.null;
|
||||
elem = {type: 'keyup', keyId: 0x41};
|
||||
obj(elem);
|
||||
expect(times_called).to.be.equal(4);
|
||||
elem = {type: 'keyup', keyId: 0x42};
|
||||
obj(elem);
|
||||
expect(times_called).to.be.equal(4);
|
||||
});
|
||||
it('should add keysym as separate entry if keyId does not match last event and first is zero', function() {
|
||||
// this implies that separate keyups are required
|
||||
var times_called = 0;
|
||||
var elem = null;
|
||||
var keysymsdown = {};
|
||||
var obj = TrackKeyState(function(evt) {
|
||||
++times_called;
|
||||
if (elem.type == 'keyup') {
|
||||
expect(evt).to.have.property('keysym');
|
||||
expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
|
||||
delete keysymsdown[evt.keysym.keysym];
|
||||
}
|
||||
else {
|
||||
expect(evt).to.be.deep.equal(elem);
|
||||
expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
|
||||
elem = null;
|
||||
}
|
||||
});
|
||||
|
||||
expect(elem).to.be.null;
|
||||
elem = {type: 'keydown', keyId: 0, keysym: keysyms.lookup(0x42)};
|
||||
keysymsdown[keysyms.lookup(0x42).keysym] = true;
|
||||
obj(elem);
|
||||
expect(elem).to.be.null;
|
||||
elem = {type: 'keydown', keyId: 0x42, keysym: keysyms.lookup(0x43)};
|
||||
keysymsdown[keysyms.lookup(0x43).keysym] = true;
|
||||
obj(elem);
|
||||
expect(elem).to.be.null;
|
||||
expect(times_called).to.be.equal(2);
|
||||
elem = {type: 'keyup', keyId: 0};
|
||||
obj(elem);
|
||||
expect(times_called).to.be.equal(3);
|
||||
elem = {type: 'keyup', keyId: 0x42};
|
||||
obj(elem);
|
||||
expect(times_called).to.be.equal(4);
|
||||
});
|
||||
it('should add keysym as separate entry if keyId does not match last event and second is zero', function() {
|
||||
// this implies that a separate keyups are required
|
||||
var times_called = 0;
|
||||
var elem = null;
|
||||
var keysymsdown = {};
|
||||
var obj = TrackKeyState(function(evt) {
|
||||
++times_called;
|
||||
if (elem.type == 'keyup') {
|
||||
expect(evt).to.have.property('keysym');
|
||||
expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
|
||||
delete keysymsdown[evt.keysym.keysym];
|
||||
}
|
||||
else {
|
||||
expect(evt).to.be.deep.equal(elem);
|
||||
expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
|
||||
elem = null;
|
||||
}
|
||||
});
|
||||
|
||||
expect(elem).to.be.null;
|
||||
elem = {type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)};
|
||||
keysymsdown[keysyms.lookup(0x42).keysym] = true;
|
||||
obj(elem);
|
||||
expect(elem).to.be.null;
|
||||
elem = {type: 'keydown', keyId: 0, keysym: keysyms.lookup(0x43)};
|
||||
keysymsdown[keysyms.lookup(0x43).keysym] = true;
|
||||
obj(elem);
|
||||
expect(elem).to.be.null;
|
||||
elem = {type: 'keyup', keyId: 0x41};
|
||||
obj(elem);
|
||||
expect(times_called).to.be.equal(3);
|
||||
elem = {type: 'keyup', keyId: 0};
|
||||
obj(elem);
|
||||
expect(times_called).to.be.equal(4);
|
||||
});
|
||||
it('should pop matching key event on keyup', function() {
|
||||
var times_called = 0;
|
||||
var obj = TrackKeyState(function(evt) {
|
||||
switch (times_called++) {
|
||||
case 0:
|
||||
case 1:
|
||||
case 2:
|
||||
expect(evt.type).to.be.equal('keydown');
|
||||
break;
|
||||
case 3:
|
||||
expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0x42, keysym: keysyms.lookup(0x62)});
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x61)});
|
||||
obj({type: 'keydown', keyId: 0x42, keysym: keysyms.lookup(0x62)});
|
||||
obj({type: 'keydown', keyId: 0x43, keysym: keysyms.lookup(0x63)});
|
||||
obj({type: 'keyup', keyId: 0x42});
|
||||
expect(times_called).to.equal(4);
|
||||
});
|
||||
it('should pop the first zero keyevent on keyup with zero keyId', function() {
|
||||
var times_called = 0;
|
||||
var obj = TrackKeyState(function(evt) {
|
||||
switch (times_called++) {
|
||||
case 0:
|
||||
case 1:
|
||||
case 2:
|
||||
expect(evt.type).to.be.equal('keydown');
|
||||
break;
|
||||
case 3:
|
||||
expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0, keysym: keysyms.lookup(0x61)});
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
obj({type: 'keydown', keyId: 0, keysym: keysyms.lookup(0x61)});
|
||||
obj({type: 'keydown', keyId: 0, keysym: keysyms.lookup(0x62)});
|
||||
obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x63)});
|
||||
obj({type: 'keyup', keyId: 0x0});
|
||||
expect(times_called).to.equal(4);
|
||||
});
|
||||
it('should pop the last keyevents keysym if no match is found for keyId', function() {
|
||||
var times_called = 0;
|
||||
var obj = TrackKeyState(function(evt) {
|
||||
switch (times_called++) {
|
||||
case 0:
|
||||
case 1:
|
||||
case 2:
|
||||
expect(evt.type).to.be.equal('keydown');
|
||||
break;
|
||||
case 3:
|
||||
expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0x44, keysym: keysyms.lookup(0x63)});
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x61)});
|
||||
obj({type: 'keydown', keyId: 0x42, keysym: keysyms.lookup(0x62)});
|
||||
obj({type: 'keydown', keyId: 0x43, keysym: keysyms.lookup(0x63)});
|
||||
obj({type: 'keyup', keyId: 0x44});
|
||||
expect(times_called).to.equal(4);
|
||||
});
|
||||
describe('Firefox sends keypress even when keydown is suppressed', function() {
|
||||
it('should discard the keypress', function() {
|
||||
var times_called = 0;
|
||||
var obj = TrackKeyState(function(evt) {
|
||||
expect(times_called).to.be.equal(0);
|
||||
++times_called;
|
||||
});
|
||||
|
||||
obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)});
|
||||
expect(times_called).to.be.equal(1);
|
||||
obj({type: 'keypress', keyId: 0x41, keysym: keysyms.lookup(0x43)});
|
||||
});
|
||||
});
|
||||
describe('releaseAll', function() {
|
||||
it('should do nothing if no keys have been pressed', function() {
|
||||
var times_called = 0;
|
||||
var obj = TrackKeyState(function(evt) {
|
||||
++times_called;
|
||||
});
|
||||
obj({type: 'releaseall'});
|
||||
expect(times_called).to.be.equal(0);
|
||||
});
|
||||
it('should release the keys that have been pressed', function() {
|
||||
var times_called = 0;
|
||||
var obj = TrackKeyState(function(evt) {
|
||||
switch (times_called++) {
|
||||
case 2:
|
||||
expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0, keysym: keysyms.lookup(0x41)});
|
||||
break;
|
||||
case 3:
|
||||
expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0, keysym: keysyms.lookup(0x42)});
|
||||
break;
|
||||
}
|
||||
});
|
||||
obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x41)});
|
||||
obj({type: 'keydown', keyId: 0x42, keysym: keysyms.lookup(0x42)});
|
||||
expect(times_called).to.be.equal(2);
|
||||
obj({type: 'releaseall'});
|
||||
expect(times_called).to.be.equal(4);
|
||||
obj({type: 'releaseall'});
|
||||
expect(times_called).to.be.equal(4);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('Escape Modifiers', function() {
|
||||
describe('Keydown', function() {
|
||||
it('should pass through when a char modifier is not down', function() {
|
||||
var times_called = 0;
|
||||
EscapeModifiers(function(evt) {
|
||||
expect(times_called).to.be.equal(0);
|
||||
++times_called;
|
||||
expect(evt).to.be.deep.equal({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)});
|
||||
})({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)});
|
||||
expect(times_called).to.be.equal(1);
|
||||
});
|
||||
it('should generate fake undo/redo events when a char modifier is down', function() {
|
||||
var times_called = 0;
|
||||
EscapeModifiers(function(evt) {
|
||||
switch(times_called++) {
|
||||
case 0:
|
||||
expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0, keysym: keysyms.lookup(0xffe9)});
|
||||
break;
|
||||
case 1:
|
||||
expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0, keysym: keysyms.lookup(0xffe3)});
|
||||
break;
|
||||
case 2:
|
||||
expect(evt).to.be.deep.equal({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42), escape: [0xffe9, 0xffe3]});
|
||||
break;
|
||||
case 3:
|
||||
expect(evt).to.be.deep.equal({type: 'keydown', keyId: 0, keysym: keysyms.lookup(0xffe9)});
|
||||
break;
|
||||
case 4:
|
||||
expect(evt).to.be.deep.equal({type: 'keydown', keyId: 0, keysym: keysyms.lookup(0xffe3)});
|
||||
break;
|
||||
}
|
||||
})({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42), escape: [0xffe9, 0xffe3]});
|
||||
expect(times_called).to.be.equal(5);
|
||||
});
|
||||
});
|
||||
describe('Keyup', function() {
|
||||
it('should pass through when a char modifier is down', function() {
|
||||
var times_called = 0;
|
||||
EscapeModifiers(function(evt) {
|
||||
expect(times_called).to.be.equal(0);
|
||||
++times_called;
|
||||
expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0x41, keysym: keysyms.lookup(0x42), escape: [0xfe03]});
|
||||
})({type: 'keyup', keyId: 0x41, keysym: keysyms.lookup(0x42), escape: [0xfe03]});
|
||||
expect(times_called).to.be.equal(1);
|
||||
});
|
||||
it('should pass through when a char modifier is not down', function() {
|
||||
var times_called = 0;
|
||||
EscapeModifiers(function(evt) {
|
||||
expect(times_called).to.be.equal(0);
|
||||
++times_called;
|
||||
expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0x41, keysym: keysyms.lookup(0x42)});
|
||||
})({type: 'keyup', keyId: 0x41, keysym: keysyms.lookup(0x42)});
|
||||
expect(times_called).to.be.equal(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
1716
tests/test.rfb.js
Normal file
105
tests/test.util.js
Normal file
@@ -0,0 +1,105 @@
|
||||
// requires local modules: util
|
||||
/* jshint expr: true */
|
||||
|
||||
var assert = chai.assert;
|
||||
var expect = chai.expect;
|
||||
|
||||
describe('Utils', function() {
|
||||
"use strict";
|
||||
|
||||
describe('Array instance methods', function () {
|
||||
describe('push8', function () {
|
||||
it('should push a byte on to the array', function () {
|
||||
var arr = [1];
|
||||
arr.push8(128);
|
||||
expect(arr).to.deep.equal([1, 128]);
|
||||
});
|
||||
|
||||
it('should only use the least significant byte of any number passed in', function () {
|
||||
var arr = [1];
|
||||
arr.push8(0xABCD);
|
||||
expect(arr).to.deep.equal([1, 0xCD]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('push16', function () {
|
||||
it('should push two bytes on to the array', function () {
|
||||
var arr = [1];
|
||||
arr.push16(0xABCD);
|
||||
expect(arr).to.deep.equal([1, 0xAB, 0xCD]);
|
||||
});
|
||||
|
||||
it('should only use the two least significant bytes of any number passed in', function () {
|
||||
var arr = [1];
|
||||
arr.push16(0xABCDEF);
|
||||
expect(arr).to.deep.equal([1, 0xCD, 0xEF]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('push32', function () {
|
||||
it('should push four bytes on to the array', function () {
|
||||
var arr = [1];
|
||||
arr.push32(0xABCDEF12);
|
||||
expect(arr).to.deep.equal([1, 0xAB, 0xCD, 0xEF, 0x12]);
|
||||
});
|
||||
|
||||
it('should only use the four least significant bytes of any number passed in', function () {
|
||||
var arr = [1];
|
||||
arr.push32(0xABCDEF1234);
|
||||
expect(arr).to.deep.equal([1, 0xCD, 0xEF, 0x12, 0x34]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('logging functions', function () {
|
||||
beforeEach(function () {
|
||||
sinon.spy(console, 'log');
|
||||
sinon.spy(console, 'warn');
|
||||
sinon.spy(console, 'error');
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
console.log.restore();
|
||||
console.warn.restore();
|
||||
console.error.restore();
|
||||
});
|
||||
|
||||
it('should use noop for levels lower than the min level', function () {
|
||||
Util.init_logging('warn');
|
||||
Util.Debug('hi');
|
||||
Util.Info('hello');
|
||||
expect(console.log).to.not.have.been.called;
|
||||
});
|
||||
|
||||
it('should use console.log for Debug and Info', function () {
|
||||
Util.init_logging('debug');
|
||||
Util.Debug('dbg');
|
||||
Util.Info('inf');
|
||||
expect(console.log).to.have.been.calledWith('dbg');
|
||||
expect(console.log).to.have.been.calledWith('inf');
|
||||
});
|
||||
|
||||
it('should use console.warn for Warn', function () {
|
||||
Util.init_logging('warn');
|
||||
Util.Warn('wrn');
|
||||
expect(console.warn).to.have.been.called;
|
||||
expect(console.warn).to.have.been.calledWith('wrn');
|
||||
});
|
||||
|
||||
it('should use console.error for Error', function () {
|
||||
Util.init_logging('error');
|
||||
Util.Error('err');
|
||||
expect(console.error).to.have.been.called;
|
||||
expect(console.error).to.have.been.calledWith('err');
|
||||
});
|
||||
});
|
||||
|
||||
// TODO(directxman12): test the conf_default and conf_defaults methods
|
||||
// TODO(directxman12): test decodeUTF8
|
||||
// TODO(directxman12): test the event methods (addEvent, removeEvent, stopEvent)
|
||||
// TODO(directxman12): figure out a good way to test getPosition and getEventPosition
|
||||
// TODO(directxman12): figure out how to test the browser detection functions properly
|
||||
// (we can't really test them against the browsers, except for Gecko
|
||||
// via PhantomJS, the default test driver)
|
||||
// TODO(directxman12): figure out how to test Util.Flash
|
||||
});
|
||||
480
tests/test.websock.js
Normal file
@@ -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]);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
43
tests/viewport.css
Normal file
@@ -0,0 +1,43 @@
|
||||
html,body {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.flex-layout {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
display: box;
|
||||
display: -webkit-box;
|
||||
display: -moz-box;
|
||||
display: -ms-box;
|
||||
|
||||
box-orient: vertical;
|
||||
-webkit-box-orient: vertical;
|
||||
-moz-box-orient: vertical;
|
||||
-ms-box-orient: vertical;
|
||||
|
||||
box-align: stretch;
|
||||
-webkit-box-align: stretch;
|
||||
-moz-box-align: stretch;
|
||||
-ms-box-align: stretch;
|
||||
}
|
||||
.flex-box {
|
||||
box-flex: 1;
|
||||
-webkit-box-flex: 1;
|
||||
-moz-box-flex: 1;
|
||||
-ms-box-flex: 1;
|
||||
}
|
||||
|
||||
.container {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.canvas {
|
||||
position: absolute;
|
||||
border-style: dotted;
|
||||
border-width: 1px;
|
||||
}
|
||||
204
tests/viewport.html
Normal file
@@ -0,0 +1,204 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head><title>Viewport Test</title>
|
||||
<link rel="stylesheet" href="viewport.css">
|
||||
<!--
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||
-->
|
||||
<meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=no">
|
||||
</head>
|
||||
<body>
|
||||
<div class="flex-layout">
|
||||
<div>
|
||||
Canvas:
|
||||
<input id="move-selector" type="button" value="Move"
|
||||
onclick="toggleMove();">
|
||||
<br>
|
||||
</div>
|
||||
<div class="container flex-box">
|
||||
<canvas id="canvas" class="canvas">
|
||||
Canvas not supported.
|
||||
</canvas>
|
||||
<br>
|
||||
</div>
|
||||
<div>
|
||||
<br>
|
||||
Results:<br>
|
||||
<textarea id="messages" style="font-size: 9;" cols=80 rows=8></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
<!--
|
||||
<script type='text/javascript'
|
||||
src='http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js'></script>
|
||||
-->
|
||||
<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>
|
||||
var msg_cnt = 0, iterations,
|
||||
penDown = false, doMove = false,
|
||||
inMove = false, lastPos = {},
|
||||
padW = 0, padH = 0,
|
||||
display, ctx, keyboard, mouse;
|
||||
|
||||
var newline = "\n";
|
||||
if (Util.Engine.trident) {
|
||||
var newline = "<br>\n";
|
||||
}
|
||||
|
||||
function message(str) {
|
||||
console.log(str);
|
||||
cell = $D('messages');
|
||||
cell.innerHTML += msg_cnt + ": " + str + newline;
|
||||
cell.scrollTop = cell.scrollHeight;
|
||||
msg_cnt++;
|
||||
}
|
||||
|
||||
function mouseButton(x, y, down, bmask) {
|
||||
//msg = 'mouse x,y: ' + x + ',' + y + ' down: ' + down;
|
||||
//msg += ' bmask: ' + bmask;
|
||||
//message(msg);
|
||||
|
||||
if (doMove) {
|
||||
if (down && !inMove) {
|
||||
inMove = true;
|
||||
lastPos = {'x': x, 'y': y};
|
||||
} else if (!down && inMove) {
|
||||
inMove = false;
|
||||
//dirtyRedraw();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (down && ! penDown) {
|
||||
penDown = true;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x, y);
|
||||
} else if (!down && penDown) {
|
||||
penDown = false;
|
||||
ctx.closePath();
|
||||
}
|
||||
}
|
||||
|
||||
function mouseMove(x, y) {
|
||||
var deltaX, deltaY;
|
||||
|
||||
if (inMove) {
|
||||
//deltaX = x - lastPos.x; // drag viewport
|
||||
deltaX = lastPos.x - x; // drag frame buffer
|
||||
//deltaY = y - lastPos.y; // drag viewport
|
||||
deltaY = lastPos.y - y; // drag frame buffer
|
||||
lastPos = {'x': x, 'y': y};
|
||||
|
||||
display.viewportChange(deltaX, deltaY);
|
||||
return;
|
||||
}
|
||||
|
||||
if (penDown) {
|
||||
ctx.lineTo(x, y);
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
|
||||
function dirtyRedraw() {
|
||||
if (inMove) {
|
||||
// Wait for user to stop moving viewport
|
||||
return;
|
||||
}
|
||||
|
||||
var d = display.getCleanDirtyReset();
|
||||
|
||||
for (i = 0; i < d.dirtyBoxes.length; i++) {
|
||||
//showBox(d.dirtyBoxes[i], "dirty[" + i + "]: ");
|
||||
drawArea(d.dirtyBoxes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
function drawArea(b) {
|
||||
var data = [], pixel, x, y;
|
||||
|
||||
//message("draw "+b.x+","+b.y+" ("+b.w+","+b.h+")");
|
||||
|
||||
for (var i = 0; i < b.w; i++) {
|
||||
x = b.x + i;
|
||||
for (var j = 0; j < b.h; j++) {
|
||||
y = b.y + j;
|
||||
pixel = (j * b.w * 4 + i * 4);
|
||||
data[pixel + 0] = ((x * y) / 13) % 256;
|
||||
data[pixel + 1] = ((x * y) + 392) % 256;
|
||||
data[pixel + 2] = ((x + y) + 256) % 256;
|
||||
data[pixel + 3] = 255;
|
||||
}
|
||||
}
|
||||
//message("i: " + i + ", j: " + j + ", pixel: " + pixel);
|
||||
display.blitImage(b.x, b.y, b.w, b.h, data, 0);
|
||||
}
|
||||
|
||||
function toggleMove() {
|
||||
if (doMove) {
|
||||
doMove = false;
|
||||
$D('move-selector').style.backgroundColor = "";
|
||||
$D('move-selector').style.color = "";
|
||||
} else {
|
||||
doMove = true;
|
||||
$D('move-selector').style.backgroundColor = "black";
|
||||
$D('move-selector').style.color = "lightgray";
|
||||
}
|
||||
}
|
||||
|
||||
function detectPad() {
|
||||
var c = $D('canvas'), p = c.parentNode;
|
||||
c.width = 10;
|
||||
c.height = 10;
|
||||
padW = c.offsetWidth - 10;
|
||||
padH = c.offsetHeight - 10;
|
||||
message("padW: " + padW + ", padH: " + padH);
|
||||
}
|
||||
|
||||
function doResize() {
|
||||
var p = $D('canvas').parentNode;
|
||||
message("doResize1: [" + (p.offsetWidth - padW) +
|
||||
"," + (p.offsetHeight - padH) + "]");
|
||||
display.viewportChange(0, 0,
|
||||
p.offsetWidth - padW, p.offsetHeight - padH);
|
||||
/*
|
||||
var pos, new_w, new_h;pos
|
||||
pos = Util.getPosition($D('canvas').parentNode);
|
||||
new_w = window.innerWidth - pos.x;
|
||||
new_h = window.innerHeight - pos.y;
|
||||
display.viewportChange(0, 0, new_w, new_h);
|
||||
*/
|
||||
}
|
||||
|
||||
window.onload = function() {
|
||||
detectPad();
|
||||
|
||||
display = new Display({'target': $D('canvas')});
|
||||
display.resize(1600, 1024);
|
||||
display.set_viewport(true);
|
||||
ctx = display.get_context();
|
||||
|
||||
mouse = new Mouse({'target': $D('canvas'),
|
||||
'onMouseButton': mouseButton,
|
||||
'onMouseMove': mouseMove});
|
||||
mouse.grab();
|
||||
|
||||
|
||||
Util.addEvent(window, 'resize', doResize);
|
||||
// Shrink viewport for first resize call so that the
|
||||
// scrollbars are disabled
|
||||
display.viewportChange(0, 0, 10, 10);
|
||||
setTimeout(doResize, 1);
|
||||
setInterval(dirtyRedraw, 50);
|
||||
|
||||
message("Display initialized");
|
||||
};
|
||||
</script>
|
||||
</html>
|
||||
@@ -2,7 +2,6 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>VNC Performance Benchmark</title>
|
||||
<link rel="stylesheet" href="../include/plain.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -38,12 +37,19 @@
|
||||
|
||||
<script type="text/javascript">
|
||||
var INCLUDE_URI= "../include/";
|
||||
// TODO: Data file should override
|
||||
var VNC_frame_encoding = "base64";
|
||||
</script>
|
||||
<script src="../include/vnc.js"></script>
|
||||
<script src="../include/util.js"></script>
|
||||
<script src="../include/playback.js"></script>
|
||||
<script src="data/multi.js"></script>
|
||||
<script src="../data/multi.js"></script>
|
||||
|
||||
<script>
|
||||
// Load supporting scripts
|
||||
Util.load_scripts(["webutil.js", "base64.js", "websock.js", "des.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'],
|
||||
encTot = {}, encMin = {}, encMax = {},
|
||||
@@ -126,7 +132,9 @@
|
||||
pass += 1;
|
||||
if (pass > passes) {
|
||||
// We are finished
|
||||
rfb.get_canvas().stop(); // Shut-off event interception
|
||||
// Shut-off event interception
|
||||
rfb.get_mouse().ungrab();
|
||||
rfb.get_keyboard().ungrab();
|
||||
$D('startButton').disabled = false;
|
||||
$D('startButton').value = "Start";
|
||||
finish_passes();
|
||||
@@ -186,7 +194,7 @@
|
||||
" (min/avg/max)");
|
||||
}
|
||||
|
||||
window.onload = function() {
|
||||
window.onscriptsload = function() {
|
||||
var i, enc;
|
||||
dbgmsg("Frame lengths:");
|
||||
for (i = 0; i < encOrder.length; i++) {
|
||||
@@ -194,8 +202,8 @@
|
||||
dbgmsg(" " + enc + ": " + VNC_frame_data_multi[enc].length);
|
||||
}
|
||||
rfb = new RFB({'target': $D('VNC_canvas'),
|
||||
'updateState': updateState});
|
||||
rfb.testMode(send_array);
|
||||
'onUpdateState': updateState});
|
||||
rfb.testMode(send_array, VNC_frame_encoding);
|
||||
}
|
||||
</script>
|
||||
</html>
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>VNC Playback</title>
|
||||
<link rel="stylesheet" href="../include/plain.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -40,9 +39,11 @@
|
||||
|
||||
<script type="text/javascript">
|
||||
var INCLUDE_URI= "../include/";
|
||||
// TODO: Data file should override
|
||||
var VNC_frame_encoding = "base64";
|
||||
</script>
|
||||
<script src="../include/vnc.js"></script>
|
||||
<script src="../include/playback.js"></script>
|
||||
<script src="../include/util.js"></script>
|
||||
<script src="../include/webutil.js"></script>
|
||||
|
||||
<script>
|
||||
var fname, start_time;
|
||||
@@ -55,10 +56,13 @@
|
||||
}
|
||||
|
||||
fname = WebUtil.getQueryVar('data', null);
|
||||
|
||||
if (fname) {
|
||||
message("Loading " + fname);
|
||||
document.write('<script src="' + fname + '"><\/script>');
|
||||
// Load supporting scripts
|
||||
Util.load_scripts(["base64.js", "websock.js", "des.js",
|
||||
"keysymdef.js", "keyboard.js", "input.js", "display.js",
|
||||
"jsunzip.js", "rfb.js", "playback.js", fname]);
|
||||
|
||||
} else {
|
||||
message("Must specify data=FOO in query string.");
|
||||
}
|
||||
@@ -95,7 +99,7 @@
|
||||
mode = 'realtime';
|
||||
}
|
||||
|
||||
recv_message = rfb.testMode(send_array);
|
||||
recv_message = rfb.testMode(send_array, VNC_frame_encoding);
|
||||
next_iteration();
|
||||
}
|
||||
|
||||
@@ -115,7 +119,7 @@
|
||||
|
||||
}
|
||||
|
||||
window.onload = function() {
|
||||
window.onscriptsload = function () {
|
||||
iterations = WebUtil.getQueryVar('iterations', 3);
|
||||
$D('iterations').value = iterations;
|
||||
mode = WebUtil.getQueryVar('mode', 3);
|
||||
@@ -127,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>
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
## WebSockets Proxy
|
||||
|
||||
wsproxy has become [websockify](https://github.com/kanaka/websockify).
|
||||
A copy of the python version of websockify (named wsproxy.py) is kept
|
||||
here for ease of use. The other versions of websockify (C, Node.js)
|
||||
and the associated test programs have been moved to
|
||||
[websockify](https://github.com/kanaka/websockify).
|
||||
## WebSockets Proxy/Bridge
|
||||
|
||||
For more detailed description and usage information please refer to
|
||||
the [websockify README](https://github.com/kanaka/websockify/blob/master/README.md).
|
||||
|
||||
The other versions of websockify (C, Node.js) and the associated test
|
||||
programs have been moved to
|
||||
[websockify](https://github.com/kanaka/websockify). Websockify was
|
||||
formerly named wsproxy.
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#
|
||||
# Convert image to Javascript compatible base64 Data URI
|
||||
# Copyright 2011 Joel Martin
|
||||
# Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3)
|
||||
# Licensed under MPL 2.0 (see docs/LICENSE.MPL-2.0)
|
||||
#
|
||||
|
||||
import sys, base64
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
'''
|
||||
Use matplotlib to generate performance charts
|
||||
Copyright 2011 Joel Martin
|
||||
Licensed under GPL version 3 (see docs/LICENSE.GPL-3)
|
||||
Licensed under MPL-2.0 (see docs/LICENSE.MPL-2.0)
|
||||
'''
|
||||
|
||||
# a bar plot with errorbars
|
||||
|
||||
@@ -16,6 +16,8 @@ usage() {
|
||||
echo " Default: localhost:5900"
|
||||
echo " --cert CERT Path to combined cert/key file"
|
||||
echo " Default: self.pem"
|
||||
echo " --web WEB Path to web files (e.g. vnc.html)"
|
||||
echo " Default: ./"
|
||||
exit 2
|
||||
}
|
||||
|
||||
@@ -24,6 +26,7 @@ HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||
PORT="6080"
|
||||
VNC_DEST="localhost:5900"
|
||||
CERT=""
|
||||
WEB=""
|
||||
proxy_pid=""
|
||||
|
||||
die() {
|
||||
@@ -50,6 +53,7 @@ while [ "$*" ]; do
|
||||
--listen) PORT="${OPTARG}"; shift ;;
|
||||
--vnc) VNC_DEST="${OPTARG}"; shift ;;
|
||||
--cert) CERT="${OPTARG}"; shift ;;
|
||||
--web) WEB="${OPTARG}"; shift ;;
|
||||
-h|--help) usage ;;
|
||||
-*) usage "Unknown chrooter option: ${param}" ;;
|
||||
*) break ;;
|
||||
@@ -60,18 +64,24 @@ done
|
||||
which netstat >/dev/null 2>&1 \
|
||||
|| die "Must have netstat installed"
|
||||
|
||||
netstat -ltn | grep -qs "${PORT}.*LISTEN" \
|
||||
netstat -ltn | grep -qs "${PORT} .*LISTEN" \
|
||||
&& die "Port ${PORT} in use. Try --listen PORT"
|
||||
|
||||
trap "cleanup" TERM QUIT INT EXIT
|
||||
|
||||
# Find vnc.html
|
||||
if [ -e "$(pwd)/vnc.html" ]; then
|
||||
if [ -n "${WEB}" ]; then
|
||||
if [ ! -e "${WEB}/vnc.html" ]; then
|
||||
die "Could not find ${WEB}/vnc.html"
|
||||
fi
|
||||
elif [ -e "$(pwd)/vnc.html" ]; then
|
||||
WEB=$(pwd)
|
||||
elif [ -e "${HERE}/../vnc.html" ]; then
|
||||
WEB=${HERE}/../
|
||||
elif [ -e "${HERE}/vnc.html" ]; then
|
||||
WEB=${HERE}
|
||||
elif [ -e "${HERE}/../share/novnc/vnc.html" ]; then
|
||||
WEB=${HERE}/../share/novnc/
|
||||
else
|
||||
die "Could not find vnc.html"
|
||||
fi
|
||||
@@ -92,7 +102,7 @@ else
|
||||
fi
|
||||
|
||||
echo "Starting webserver and WebSockets proxy on port ${PORT}"
|
||||
${HERE}/wsproxy.py --web ${WEB} ${CERT:+--cert ${CERT}} ${PORT} ${VNC_DEST} &
|
||||
${HERE}/websockify --web ${WEB} ${CERT:+--cert ${CERT}} ${PORT} ${VNC_DEST} &
|
||||
proxy_pid="$!"
|
||||
sleep 1
|
||||
if ! ps -p ${proxy_pid} >/dev/null; then
|
||||
|
||||
152
utils/nova-novncproxy
Executable file
@@ -0,0 +1,152 @@
|
||||
#!/usr/bin/env python
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright (c) 2012 Openstack, LLC.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#!/usr/bin/env python
|
||||
|
||||
'''
|
||||
Websocket proxy that is compatible with Openstack Nova.
|
||||
Leverages websockify by Joel Martin
|
||||
'''
|
||||
|
||||
import Cookie
|
||||
from oslo.config import cfg
|
||||
import socket
|
||||
import sys
|
||||
|
||||
import websockify
|
||||
|
||||
from nova import config
|
||||
from nova import context
|
||||
from nova import utils
|
||||
from nova.openstack.common import rpc
|
||||
|
||||
|
||||
opts = [
|
||||
cfg.BoolOpt('record',
|
||||
default=False,
|
||||
help='Record sessions to FILE.[session_number]'),
|
||||
cfg.BoolOpt('daemon',
|
||||
default=False,
|
||||
help='Become a daemon (background process)'),
|
||||
cfg.BoolOpt('ssl_only',
|
||||
default=False,
|
||||
help='Disallow non-encrypted connections'),
|
||||
cfg.BoolOpt('source_is_ipv6',
|
||||
default=False,
|
||||
help='Source is ipv6'),
|
||||
cfg.StrOpt('cert',
|
||||
default='self.pem',
|
||||
help='SSL certificate file'),
|
||||
cfg.StrOpt('key',
|
||||
default=None,
|
||||
help='SSL key file (if separate from cert)'),
|
||||
cfg.StrOpt('web',
|
||||
default='.',
|
||||
help='Run webserver on same port. Serve files from DIR.'),
|
||||
cfg.StrOpt('novncproxy_host',
|
||||
default='0.0.0.0',
|
||||
help='Host on which to listen for incoming requests'),
|
||||
cfg.IntOpt('novncproxy_port',
|
||||
default=6080,
|
||||
help='Port on which to listen for incoming requests'),
|
||||
]
|
||||
CONF = cfg.CONF
|
||||
CONF.register_cli_opts(opts)
|
||||
|
||||
# As of nova commit 0b11668e64450039dc071a4a123abd02206f865f we must
|
||||
# manually register the rpc library
|
||||
if hasattr(rpc, 'register_opts'):
|
||||
rpc.register_opts(CONF)
|
||||
|
||||
|
||||
class NovaWebSocketProxy(websockify.WebSocketProxy):
|
||||
def __init__(self, *args, **kwargs):
|
||||
websockify.WebSocketProxy.__init__(self, *args, **kwargs)
|
||||
|
||||
def new_client(self):
|
||||
"""
|
||||
Called after a new WebSocket connection has been established.
|
||||
"""
|
||||
cookie = Cookie.SimpleCookie()
|
||||
cookie.load(self.headers.getheader('cookie'))
|
||||
token = cookie['token'].value
|
||||
ctxt = context.get_admin_context()
|
||||
connect_info = rpc.call(ctxt, 'consoleauth',
|
||||
{'method': 'check_token',
|
||||
'args': {'token': token}})
|
||||
|
||||
if not connect_info:
|
||||
raise Exception("Invalid Token")
|
||||
|
||||
host = connect_info['host']
|
||||
port = int(connect_info['port'])
|
||||
|
||||
# Connect to the target
|
||||
self.msg("connecting to: %s:%s" % (
|
||||
host, port))
|
||||
tsock = self.socket(host, port,
|
||||
connect=True)
|
||||
|
||||
# Handshake as necessary
|
||||
if connect_info.get('internal_access_path'):
|
||||
tsock.send("CONNECT %s HTTP/1.1\r\n\r\n" %
|
||||
connect_info['internal_access_path'])
|
||||
while True:
|
||||
data = tsock.recv(4096, socket.MSG_PEEK)
|
||||
if data.find("\r\n\r\n") != -1:
|
||||
if not data.split("\r\n")[0].find("200"):
|
||||
raise Exception("Invalid Connection Info")
|
||||
tsock.recv(len(data))
|
||||
break
|
||||
|
||||
if self.verbose and not self.daemon:
|
||||
print(self.traffic_legend)
|
||||
|
||||
# Start proxying
|
||||
try:
|
||||
self.do_proxy(tsock)
|
||||
except:
|
||||
if tsock:
|
||||
tsock.shutdown(socket.SHUT_RDWR)
|
||||
tsock.close()
|
||||
self.vmsg("%s:%s: Target closed" % (host, port))
|
||||
raise
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if CONF.ssl_only and not os.path.exists(CONF.cert):
|
||||
parser.error("SSL only and %s not found" % CONF.cert)
|
||||
|
||||
# Setup flags
|
||||
config.parse_args(sys.argv)
|
||||
|
||||
# Create and start the NovaWebSockets proxy
|
||||
server = NovaWebSocketProxy(listen_host=CONF.novncproxy_host,
|
||||
listen_port=CONF.novncproxy_port,
|
||||
source_is_ipv6=CONF.source_is_ipv6,
|
||||
verbose=CONF.verbose,
|
||||
cert=CONF.cert,
|
||||
key=CONF.key,
|
||||
ssl_only=CONF.ssl_only,
|
||||
daemon=CONF.daemon,
|
||||
record=CONF.record,
|
||||
web=CONF.web,
|
||||
target_host='ignore',
|
||||
target_port='ignore',
|
||||
wrap_mode='exit',
|
||||
wrap_cmd=None)
|
||||
server.start_server()
|
||||
97
utils/parse.js
Normal file
@@ -0,0 +1,97 @@
|
||||
// Utility to parse keysymdef.h to produce mappings from Unicode codepoints to keysyms
|
||||
"use strict";
|
||||
|
||||
var fs = require('fs');
|
||||
|
||||
var show_help = process.argv.length === 2;
|
||||
var use_keynames = false;
|
||||
var filename;
|
||||
|
||||
for (var i = 2; i < process.argv.length; ++i) {
|
||||
switch (process.argv[i]) {
|
||||
case "--help":
|
||||
case "-h":
|
||||
show_help = true;
|
||||
break;
|
||||
case "--debug-names":
|
||||
case "-d":
|
||||
use_keynames = true;
|
||||
break;
|
||||
case "--file":
|
||||
case "-f":
|
||||
default:
|
||||
filename = process.argv[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (!filename) {
|
||||
show_help = true;
|
||||
console.log("Error: No filename specified\n");
|
||||
}
|
||||
|
||||
if (show_help) {
|
||||
console.log("Parses a *nix keysymdef.h to generate Unicode code point mappings");
|
||||
console.log("Usage: node parse.js [options] filename:");
|
||||
console.log(" -h [ --help ] Produce this help message");
|
||||
console.log(" -d [ --debug-names ] Preserve keysym names for debugging (Increases file size by ~40KB)");
|
||||
console.log(" filename The keysymdef.h file to parse");
|
||||
return;
|
||||
}
|
||||
|
||||
// Set this to false to omit key names from the generated keysymdef.js
|
||||
// This reduces the file size by around 40kb, but may hinder debugging
|
||||
|
||||
var buf = fs.readFileSync(filename);
|
||||
var str = buf.toString('utf8');
|
||||
|
||||
var re = /^\#define XK_([a-zA-Z_0-9]+)\s+0x([0-9a-fA-F]+)\s*(\/\*\s*(.*)\s*\*\/)?\s*$/m;
|
||||
|
||||
var arr = str.split('\n');
|
||||
|
||||
var keysyms = {};
|
||||
var codepoints = {};
|
||||
|
||||
for (var i = 0; i < arr.length; ++i) {
|
||||
var result = re.exec(arr[i]);
|
||||
if (result){
|
||||
var keyname = result[1];
|
||||
var keysym = parseInt(result[2], 16);
|
||||
var remainder = result[3];
|
||||
|
||||
keysyms[keysym] = keyname;
|
||||
|
||||
var unicodeRes = /U\+([0-9a-fA-F]+)/.exec(remainder);
|
||||
if (unicodeRes) {
|
||||
var unicode = parseInt(unicodeRes[1], 16);
|
||||
if (!codepoints[unicode]){
|
||||
codepoints[unicode] = keysym;
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.log("no unicode codepoint found:", arr[i]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.log("line is not a keysym:", arr[i]);
|
||||
}
|
||||
}
|
||||
|
||||
var out = "// This file describes mappings from Unicode codepoints to the keysym values\n" +
|
||||
"// (and optionally, key names) expected by the RFB protocol\n" +
|
||||
"// How this file was generated:\n" +
|
||||
"// " + process.argv.join(" ") + "\n" +
|
||||
"var keysyms = (function(){\n" +
|
||||
" \"use strict\";\n" +
|
||||
" var keynames = {keysyms};\n" +
|
||||
" var codepoints = {codepoints};\n" +
|
||||
"\n" +
|
||||
" function lookup(k) { return k ? {keysym: k, keyname: keynames ? keynames[k] : k} : undefined; }\n" +
|
||||
" return {\n" +
|
||||
" fromUnicode : function(u) { return lookup(codepoints[u]); },\n" +
|
||||
" lookup : lookup\n" +
|
||||
" };\n" +
|
||||
"})();\n";
|
||||
out = out.replace('{keysyms}', use_keynames ? JSON.stringify(keysyms) : "null");
|
||||
out = out.replace('{codepoints}', JSON.stringify(codepoints));
|
||||
|
||||
fs.writeFileSync("keysymdef.js", out);
|
||||
@@ -1,14 +1,14 @@
|
||||
/*
|
||||
* rebind: Intercept bind calls and bind to a different port
|
||||
* Copyright 2010 Joel Martin
|
||||
* Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3)
|
||||
* Licensed under MPL-2.0 (see docs/LICENSE.MPL-2.0)
|
||||
*
|
||||
* Overload (LD_PRELOAD) bind system call. If REBIND_PORT_OLD and
|
||||
* REBIND_PORT_NEW environment variables are set then bind on the new
|
||||
* port (of localhost) instead of the old port.
|
||||
*
|
||||
* This allows a proxy (such as wsproxy) to run on the old port and translate
|
||||
* traffic to/from the new port.
|
||||
* This allows a bridge/proxy (such as websockify) to run on the old port and
|
||||
* translate traffic to/from the new port.
|
||||
*
|
||||
* Usage:
|
||||
* LD_PRELOAD=./rebind.so \
|
||||
|
||||
1206
utils/websocket.py
437
utils/websockify
@@ -11,19 +11,20 @@ as taken from http://docs.python.org/dev/library/ssl.html#certificates
|
||||
|
||||
'''
|
||||
|
||||
import 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
|
||||
from websocket import WebSocketServer
|
||||
import websocket
|
||||
try:
|
||||
from urllib.parse import parse_qs, urlparse
|
||||
except:
|
||||
from cgi import parse_qs
|
||||
from urlparse import urlparse
|
||||
|
||||
class WebSocketProxy(WebSocketServer):
|
||||
"""
|
||||
Proxy traffic to and from a WebSockets client to a normal TCP
|
||||
socket server target. All traffic to/from the client is base64
|
||||
encoded/decoded to allow binary data to be sent/received to/from
|
||||
the target.
|
||||
"""
|
||||
|
||||
buffer_size = 65536
|
||||
class ProxyRequestHandler(websocket.WebSocketRequestHandler):
|
||||
|
||||
traffic_legend = """
|
||||
Traffic Legend:
|
||||
@@ -37,17 +38,172 @@ Traffic Legend:
|
||||
<. - Client send partial
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
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.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.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.server.target_host, self.server.target_port)
|
||||
|
||||
if self.server.ssl_target:
|
||||
msg += " (using SSL)"
|
||||
self.log_message(msg)
|
||||
|
||||
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)
|
||||
|
||||
self.print_traffic(self.traffic_legend)
|
||||
|
||||
# Start proxying
|
||||
try:
|
||||
self.do_proxy(tsock)
|
||||
except:
|
||||
if tsock:
|
||||
tsock.shutdown(socket.SHUT_RDWR)
|
||||
tsock.close()
|
||||
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):
|
||||
"""
|
||||
Parses the path, extracts a token, and looks for a valid
|
||||
target for that token in the configuration file(s). Sets
|
||||
target_host and target_port if successful
|
||||
"""
|
||||
# The files in targets contain the lines
|
||||
# in the form of token: host:port
|
||||
|
||||
# Extract the token parameter from url
|
||||
args = parse_qs(urlparse(path)[4]) # 4 is the query from url
|
||||
|
||||
if not args.has_key('token') or not len(args['token']):
|
||||
raise self.EClose("Token not present")
|
||||
|
||||
token = args['token'][0].rstrip('\n')
|
||||
|
||||
# target_cfg can be a single config file or directory of
|
||||
# config files
|
||||
if os.path.isdir(target_cfg):
|
||||
cfg_files = [os.path.join(target_cfg, f)
|
||||
for f in os.listdir(target_cfg)]
|
||||
else:
|
||||
cfg_files = [target_cfg]
|
||||
|
||||
targets = {}
|
||||
for f in cfg_files:
|
||||
for line in [l.strip() for l in file(f).readlines()]:
|
||||
if line and not line.startswith('#'):
|
||||
ttoken, target = line.split(': ')
|
||||
targets[ttoken] = target.strip()
|
||||
|
||||
self.vmsg("Target config: %s" % repr(targets))
|
||||
|
||||
if targets.has_key(token):
|
||||
return targets[token].split(':')
|
||||
else:
|
||||
raise self.EClose("Token '%s' not found" % token)
|
||||
|
||||
def do_proxy(self, target):
|
||||
"""
|
||||
Proxy client WebSocket to normal target socket.
|
||||
"""
|
||||
cqueue = []
|
||||
c_pend = 0
|
||||
tqueue = []
|
||||
rlist = [self.request, target]
|
||||
|
||||
while True:
|
||||
wlist = []
|
||||
|
||||
if tqueue: wlist.append(target)
|
||||
if cqueue or c_pend: wlist.append(self.request)
|
||||
ins, outs, excepts = select(rlist, wlist, [], 1)
|
||||
if excepts: raise Exception("Socket exception")
|
||||
|
||||
if self.request in outs:
|
||||
# Send queued target data to the client
|
||||
c_pend = self.send_frames(cqueue)
|
||||
|
||||
cqueue = []
|
||||
|
||||
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?
|
||||
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'])
|
||||
|
||||
|
||||
if target in outs:
|
||||
# Send queued client data to the target
|
||||
dat = tqueue.pop(0)
|
||||
sent = target.send(dat)
|
||||
if sent == len(dat):
|
||||
self.print_traffic(">")
|
||||
else:
|
||||
# requeue the remaining data
|
||||
tqueue.insert(0, dat[sent:])
|
||||
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:
|
||||
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.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')
|
||||
self.target_port = kwargs.pop('target_port')
|
||||
self.wrap_cmd = kwargs.pop('wrap_cmd')
|
||||
self.wrap_mode = kwargs.pop('wrap_mode')
|
||||
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])]
|
||||
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:
|
||||
@@ -58,6 +214,7 @@ Traffic Legend:
|
||||
|
||||
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
|
||||
@@ -71,14 +228,14 @@ Traffic Legend:
|
||||
"REBIND_OLD_PORT": str(kwargs['listen_port']),
|
||||
"REBIND_NEW_PORT": str(self.target_port)})
|
||||
|
||||
WebSocketServer.__init__(self, *args, **kwargs)
|
||||
websocket.WebSocketServer.__init__(self, RequestHandlerClass, *args, **kwargs)
|
||||
|
||||
def run_wrap_cmd(self):
|
||||
print("Starting '%s'" % " ".join(self.wrap_cmd))
|
||||
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)
|
||||
self.wrap_cmd, env=os.environ, preexec_fn=_subprocess_setup)
|
||||
self.spawn_message = True
|
||||
|
||||
def started(self):
|
||||
@@ -88,14 +245,26 @@ Traffic Legend:
|
||||
# Need to call wrapped command after daemonization so we can
|
||||
# know when the wrapped command exits
|
||||
if self.wrap_cmd:
|
||||
print(" - proxying from %s:%s to '%s' (port %s)\n" % (
|
||||
self.listen_host, self.listen_port,
|
||||
" ".join(self.wrap_cmd), self.target_port))
|
||||
self.run_wrap_cmd()
|
||||
dst_string = "'%s' (port %s)" % (" ".join(self.wrap_cmd), self.target_port)
|
||||
elif self.unix_target:
|
||||
dst_string = self.unix_target
|
||||
else:
|
||||
print(" - proxying from %s:%s to %s:%s\n" % (
|
||||
self.listen_host, self.listen_port,
|
||||
self.target_host, self.target_port))
|
||||
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
|
||||
@@ -118,130 +287,85 @@ Traffic Legend:
|
||||
if (now - avg) < 10:
|
||||
# 3 times in the last 10 seconds
|
||||
if self.spawn_message:
|
||||
print("Command respawning too fast")
|
||||
self.warn("Command respawning too fast")
|
||||
self.spawn_message = False
|
||||
else:
|
||||
self.run_wrap_cmd()
|
||||
|
||||
#
|
||||
# Routines above this point are run in the master listener
|
||||
# process.
|
||||
#
|
||||
|
||||
#
|
||||
# Routines below this point are connection handler routines and
|
||||
# will be run in a separate forked process for each connection.
|
||||
#
|
||||
|
||||
def new_client(self):
|
||||
"""
|
||||
Called after a new WebSocket connection has been established.
|
||||
"""
|
||||
|
||||
# Connect to the target
|
||||
self.msg("connecting to: %s:%s" % (
|
||||
self.target_host, self.target_port))
|
||||
tsock = self.socket(self.target_host, self.target_port,
|
||||
connect=True)
|
||||
|
||||
if self.verbose and not self.daemon:
|
||||
print(self.traffic_legend)
|
||||
|
||||
# Start proxying
|
||||
try:
|
||||
self.do_proxy(tsock)
|
||||
except:
|
||||
if tsock:
|
||||
tsock.shutdown(socket.SHUT_RDWR)
|
||||
tsock.close()
|
||||
self.vmsg("%s:%s: Target closed" %(
|
||||
self.target_host, self.target_port))
|
||||
raise
|
||||
|
||||
def do_proxy(self, target):
|
||||
"""
|
||||
Proxy client WebSocket to normal target socket.
|
||||
"""
|
||||
cqueue = []
|
||||
c_pend = 0
|
||||
tqueue = []
|
||||
rlist = [self.client, target]
|
||||
|
||||
while True:
|
||||
wlist = []
|
||||
|
||||
if tqueue: wlist.append(target)
|
||||
if cqueue or c_pend: wlist.append(self.client)
|
||||
ins, outs, excepts = select(rlist, wlist, [], 1)
|
||||
if excepts: raise Exception("Socket exception")
|
||||
|
||||
if target in outs:
|
||||
# Send queued client data to the target
|
||||
dat = tqueue.pop(0)
|
||||
sent = target.send(dat)
|
||||
if sent == len(dat):
|
||||
self.traffic(">")
|
||||
else:
|
||||
# requeue the remaining data
|
||||
tqueue.insert(0, dat[sent:])
|
||||
self.traffic(".>")
|
||||
def _subprocess_setup():
|
||||
# Python installs a SIGPIPE handler by default. This is usually not what
|
||||
# non-Python successfulbprocesses expect.
|
||||
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
|
||||
|
||||
|
||||
if target in ins:
|
||||
# Receive target data, encode it and queue for client
|
||||
buf = target.recv(self.buffer_size)
|
||||
if len(buf) == 0: raise self.EClose("Target closed")
|
||||
|
||||
cqueue.append(buf)
|
||||
self.traffic("{")
|
||||
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)
|
||||
|
||||
|
||||
if self.client in outs:
|
||||
# Send queued target data to the client
|
||||
c_pend = self.send_frames(cqueue)
|
||||
def websockify_init():
|
||||
logger_init()
|
||||
|
||||
cqueue = []
|
||||
|
||||
|
||||
if self.client in ins:
|
||||
# Receive client data, decode it, and queue for target
|
||||
bufs, closed = self.recv_frames()
|
||||
tqueue.extend(bufs)
|
||||
|
||||
if closed:
|
||||
# TODO: What about blocking on client socket?
|
||||
self.send_close()
|
||||
raise self.EClose(closed)
|
||||
|
||||
if __name__ == '__main__':
|
||||
usage = "\n %prog [options]"
|
||||
usage += " [source_addr:]source_port target_addr:target_port"
|
||||
usage += " [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",
|
||||
dest="daemon", action="store_true",
|
||||
help="become a daemon (background process)")
|
||||
parser.add_option("--run-once", action="store_true",
|
||||
help="handle a single WebSocket connection and exit")
|
||||
parser.add_option("--timeout", type=int, default=0,
|
||||
help="after TIMEOUT seconds exit when not connected")
|
||||
parser.add_option("--idle-timeout", type=int, default=0,
|
||||
help="server exits after TIMEOUT seconds if there are no "
|
||||
"active connections")
|
||||
parser.add_option("--cert", default="self.pem",
|
||||
help="SSL certificate file")
|
||||
parser.add_option("--key", default=None,
|
||||
help="SSL key file (if separate from cert)")
|
||||
parser.add_option("--ssl-only", action="store_true",
|
||||
help="disallow non-encrypted connections")
|
||||
help="disallow non-encrypted client connections")
|
||||
parser.add_option("--ssl-target", action="store_true",
|
||||
help="connect to SSL target as SSL client")
|
||||
parser.add_option("--unix-target",
|
||||
help="connect to unix socket target", metavar="FILE")
|
||||
parser.add_option("--web", default=None, metavar="DIR",
|
||||
help="run webserver on same port. Serve files from DIR.")
|
||||
parser.add_option("--wrap-mode", default="exit", metavar="MODE",
|
||||
choices=["exit", "ignore", "respawn"],
|
||||
help="action to take when the wrapped program exits "
|
||||
"or daemonizes: exit (default), ignore, respawn")
|
||||
parser.add_option("--prefer-ipv6", "-6",
|
||||
action="store_true", dest="source_is_ipv6",
|
||||
help="prefer IPv6 when resolving source_addr")
|
||||
parser.add_option("--target-config", metavar="FILE",
|
||||
dest="target_cfg",
|
||||
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:
|
||||
if len(args) < 2 and not (opts.target_cfg or opts.unix_target):
|
||||
parser.error("Too few arguments")
|
||||
if sys.argv.count('--'):
|
||||
opts.wrap_cmd = args[1:]
|
||||
@@ -250,29 +374,98 @@ if __name__ == '__main__':
|
||||
if len(args) > 2:
|
||||
parser.error("Too many arguments")
|
||||
|
||||
if not websocket.ssl and opts.ssl_target:
|
||||
parser.error("SSL target requested and Python SSL module not loaded.");
|
||||
|
||||
if opts.ssl_only and not os.path.exists(opts.cert):
|
||||
parser.error("SSL only and %s not found" % opts.cert)
|
||||
|
||||
# Parse host:port and convert ports to numbers
|
||||
if args[0].count(':') > 0:
|
||||
opts.listen_host, sep, opts.listen_port = args[0].rpartition(':')
|
||||
opts.listen_host, opts.listen_port = args[0].rsplit(':', 1)
|
||||
opts.listen_host = opts.listen_host.strip('[]')
|
||||
else:
|
||||
opts.listen_host, opts.listen_port = '', args[0]
|
||||
|
||||
try: opts.listen_port = int(opts.listen_port)
|
||||
except: parser.error("Error parsing listen port")
|
||||
|
||||
if opts.wrap_cmd:
|
||||
if opts.wrap_cmd or opts.unix_target or opts.target_cfg:
|
||||
opts.target_host = None
|
||||
opts.target_port = None
|
||||
else:
|
||||
if args[1].count(':') > 0:
|
||||
opts.target_host, sep, opts.target_port = args[1].rpartition(':')
|
||||
opts.target_host, opts.target_port = args[1].rsplit(':', 1)
|
||||
opts.target_host = opts.target_host.strip('[]')
|
||||
else:
|
||||
parser.error("Error parsing target")
|
||||
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
|
||||
server = WebSocketProxy(**opts.__dict__)
|
||||
server.start_server()
|
||||
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()
|
||||
|
||||
1
utils/websockify.py
Symbolic link
@@ -0,0 +1 @@
|
||||
websockify
|
||||
228
vnc.html
@@ -1,31 +1,213 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
<head>
|
||||
|
||||
<!--
|
||||
noVNC example: simple example using default UI
|
||||
Copyright (C) 2011 Joel Martin
|
||||
Licensed under LGPL-3 (see LICENSE.txt)
|
||||
Copyright (C) 2012 Joel Martin
|
||||
Copyright (C) 2013 Samuel Mannehed for Cendio AB
|
||||
noVNC is licensed under the MPL 2.0 (see LICENSE.txt)
|
||||
This file is licensed under the 2-Clause BSD license (see LICENSE.txt).
|
||||
|
||||
Connect parameters are provided in query string:
|
||||
http://example.com/?host=HOST&port=PORT&encrypt=1&true_color=1
|
||||
-->
|
||||
<head>
|
||||
<title>noVNC</title>
|
||||
<meta http-equiv="X-UA-Compatible" content="chrome=1">
|
||||
<link rel="stylesheet" href="include/plain.css">
|
||||
<link rel="alternate stylesheet" href="include/black.css" TITLE="Black">
|
||||
<!--
|
||||
<script type='text/javascript'
|
||||
src='http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js'></script>
|
||||
-->
|
||||
<script src="include/vnc.js"></script>
|
||||
<script src="include/ui.js"></script>
|
||||
</head>
|
||||
<title>noVNC</title>
|
||||
|
||||
<body>
|
||||
<div id='vnc'>Loading</div>
|
||||
<meta charset="utf-8">
|
||||
|
||||
<script>
|
||||
window.onload = function () {
|
||||
UI.load('vnc');
|
||||
};
|
||||
</script>
|
||||
<!-- Always force latest IE rendering engine (even in intranet) & Chrome Frame
|
||||
Remove this if you use the .htaccess -->
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
|
||||
</body>
|
||||
<!-- Apple iOS Safari settings -->
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||
<!-- App Start Icon -->
|
||||
<link rel="apple-touch-startup-image" href="images/screen_320x460.png" />
|
||||
<!-- For iOS devices set the icon to use if user bookmarks app on their homescreen -->
|
||||
<link rel="apple-touch-icon" href="images/screen_57x57.png">
|
||||
<!--
|
||||
<link rel="apple-touch-icon-precomposed" href="images/screen_57x57.png" />
|
||||
-->
|
||||
|
||||
|
||||
<!-- Stylesheets -->
|
||||
<link rel="stylesheet" href="include/base.css" />
|
||||
<link rel="alternate stylesheet" href="include/black.css" TITLE="Black" />
|
||||
<link rel="alternate stylesheet" href="include/blue.css" TITLE="Blue" />
|
||||
|
||||
<!--
|
||||
<script type='text/javascript'
|
||||
src='http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js'></script>
|
||||
-->
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="noVNC-control-bar">
|
||||
<!--noVNC Mobile Device only Buttons-->
|
||||
<div class="noVNC-buttons-left">
|
||||
<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" alt="No mousebutton" src="images/mouse_none.png"
|
||||
id="noVNC_mouse_button0" class="noVNC_status_button">
|
||||
<input type="image" alt="Left mousebutton" src="images/mouse_left.png"
|
||||
id="noVNC_mouse_button1" class="noVNC_status_button">
|
||||
<input type="image" alt="Middle mousebutton" src="images/mouse_middle.png"
|
||||
id="noVNC_mouse_button2" class="noVNC_status_button">
|
||||
<input type="image" alt="Right mousebutton" src="images/mouse_right.png"
|
||||
id="noVNC_mouse_button4" class="noVNC_status_button">
|
||||
<input type="image" alt="Keyboard" src="images/keyboard.png"
|
||||
id="showKeyboard" class="noVNC_status_button"
|
||||
value="Keyboard" title="Show Keyboard"/>
|
||||
<!-- 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>
|
||||
|
||||
<div id="noVNC_status">Loading</div>
|
||||
|
||||
<!--noVNC Buttons-->
|
||||
<div class="noVNC-buttons-right">
|
||||
<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" 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" alt="Settings" src="images/settings.png"
|
||||
id="settingsButton" class="noVNC_status_button"
|
||||
title="Settings" />
|
||||
<input type="image" alt="Connect" src="images/connect.png"
|
||||
id="connectButton" class="noVNC_status_button"
|
||||
title="Connect" />
|
||||
<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" 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>)
|
||||
or you will need to use
|
||||
<a href="https://github.com/kanaka/websockify">websockify</a>
|
||||
to bridge between your browser and VNC server. See the noVNC
|
||||
<a href="https://github.com/kanaka/noVNC">README</a>
|
||||
and <a href="http://kanaka.github.com/noVNC">website</a>
|
||||
for more information.
|
||||
<br />
|
||||
<input id="descriptionButton" type="button" value="Close">
|
||||
</div>
|
||||
|
||||
<!-- Popup Status Panel -->
|
||||
<div id="noVNC_popup_status_panel" class="">
|
||||
</div>
|
||||
|
||||
<!-- Clipboard Panel -->
|
||||
<div id="noVNC_clipboard" class="triangle-right top">
|
||||
<textarea id="noVNC_clipboard_text" rows=5>
|
||||
</textarea>
|
||||
<br />
|
||||
<input id="noVNC_clipboard_clear_button" type="button"
|
||||
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">
|
||||
<ul>
|
||||
<li><input id="noVNC_encrypt" type="checkbox"> Encrypt</li>
|
||||
<li><input id="noVNC_true_color" type="checkbox" checked> True Color</li>
|
||||
<li><input id="noVNC_cursor" type="checkbox"> Local Cursor</li>
|
||||
<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_path" type="input" value="websockify"> Path</li>
|
||||
<li><input id="noVNC_repeaterID" type="input" value=""> Repeater ID</li>
|
||||
<hr>
|
||||
<!-- Stylesheet selection dropdown -->
|
||||
<li><label><strong>Style: </strong>
|
||||
<select id="noVNC_stylesheet" name="vncStyle">
|
||||
<option value="default">default</option>
|
||||
</select></label>
|
||||
</li>
|
||||
|
||||
<!-- Logging selection dropdown -->
|
||||
<li><label><strong>Logging: </strong>
|
||||
<select id="noVNC_logging" name="vncLogging">
|
||||
</select></label>
|
||||
</li>
|
||||
<hr>
|
||||
<li><input type="button" id="noVNC_apply" value="Apply"></li>
|
||||
</ul>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Connection Panel -->
|
||||
<div id="noVNC_controls" class="triangle-right top">
|
||||
<ul>
|
||||
<li><label><strong>Host: </strong><input id="noVNC_host" /></label></li>
|
||||
<li><label><strong>Port: </strong><input id="noVNC_port" /></label></li>
|
||||
<li><label><strong>Password: </strong><input id="noVNC_password" type="password" /></label></li>
|
||||
<li><input id="noVNC_connect_button" type="button" value="Connect"></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div> <!-- End of noVNC-control-bar -->
|
||||
|
||||
|
||||
<div id="noVNC_screen">
|
||||
<div id="noVNC_screen_pad"></div>
|
||||
|
||||
<h1 id="noVNC_logo"><span>no</span><br />VNC</h1>
|
||||
|
||||
<!-- HTML5 Canvas -->
|
||||
<div id="noVNC_container">
|
||||
<canvas id="noVNC_canvas" width="640px" height="20px">
|
||||
Canvas not supported.
|
||||
</canvas>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<script src="include/util.js"></script>
|
||||
<script src="include/ui.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||