209 Commits

Author SHA1 Message Date
Solly Ross 3257d9f265 Force Travis to use Node 0.11.13
The latest version of Node.js has a bug that
affects the Karma test runner.  A patch has been
merged to Karma, but has not landed in a version
yet.  Until a new version of Karma is released,
we should keep node at 0.11.13.

See karma-runner/karma#1182

(cherry picked from commit 9af2346a0cead634f3af5f390770ea65929c1f4a)
2014-09-30 12:21:03 -04:00
Solly 2930403a11 Merge pull request #397 from kanaka/refactor/use-object-defineproperty
Make Utils.js use Object.define to modify Array
2014-09-30 11:57:10 -04:00
Solly Ross f9fd0313b8 Make Utils.js use Object.define to modify Array
Previously, we were modifying Array's prototype using
simple assignment.  This can mess with enumeration/iteration.
Thus, we now use Object.defineProperty with enumerable set to
false.

See #366
2014-09-22 22:30:19 -04:00
Solly Ross 58529d347b Correct Travis CI Badge URL
The readme was using the URL for the 'refactor/cleanup'
branch, which doesn't exist anymore.  It now uses
the badge for the master branch as it should.
2014-09-22 21:49:17 -04:00
Solly Ross b11bb5c385 Add support for Relative Paths in the Test Runner
This patch adds support for using relative paths
with the '-r' or '--relative' methods.  This can
be useful if you want to output HTML (with the
'--output-html' option) and use it in a webpage.

Additionally, the '-o' was removed from the documentation
of '--output-html', since it hasn't worked for that in a
while ('-o' means open in browser instead).
2014-09-22 21:42:32 -04:00
Solly Ross 0b0b0433b5 Fix Race Condition in Display#clear on IE10
There was a race condition in Display#clear on IE10
because we resize and then clear that causes the canvas
to not actually end up cleared.  Clearing the current
viewport first solves the issue.  It doesn't appear to
affect other platforms, so it's inside a engine check
(`Util.Engine.trident === 6`).  Once we stop supporting
IE10, we should just remove this, because it's not the best
to have Engine-specific code.
2014-09-22 21:37:04 -04:00
Solly Ross 3b4fd003c2 Fixed broken mouse test in test.rfb.js
Because we use the XOR (`^`) operator, the button mask must be
set before a MouseUp event happens, otherwise we'll send a pointer
event like it was a MouseDown event.  The button mask was not set
in one of the tests, so the test was failing.
2014-09-19 15:14:34 -04:00
Solly Ross 53762c31fe Fixed Cursor URI Support Detection
There was a bug in cursor URI support detection due to the way
set_defaults now works -- the code was checking for `null`, whereas
when not set, options default to `undefined` unless otherwise
specified.  The code now checks for either `null` or `undefined`.
Tests have been added to ensure that this works properly.
2014-09-19 14:51:15 -04:00
Solly Ross cfc02e5e2b Fixed presence detection bug in utils.set_defaults
Previously, Utils.set_defaults was using `if(conf[keys[i]])`
to check for the presence of a configuration key.  This would
fail if `conf[keys[i]]` happened to be false.  Instead, we now
use `if(keys[i] in conf)`, which simply checks for the presence
of the key in the conf object.
2014-09-19 14:48:00 -04:00
Solly Ross d02a99f0c8 Fixed Typo Causing MouseUp to not Register
There was a typo in one of the instances of the _buttonMask field
(it was written as _buttonMaks), causing MouseUp to never be sent.
This has been rectified, and the unit tests for the mouse handler
have been changed to check for explicitly sending mouseup and
mousedown.

Fixes #393
2014-09-19 14:18:45 -04:00
Solly Ross f0e4548b16 Fix Travis Sauce Tunnel Issues
Travis is failing because Sauce can't find the appropriate tunnelid.
This should fix that but setting the tunnel id to be the Travis
job number.
2014-09-19 12:56:53 -04:00
Solly e4e9a9b97f Merge pull request #391 from mandre/fix_invalid_property
Fix invalid updateState property on RFB preventing noVNC to load
2014-09-19 12:35:27 -04:00
Solly Ross 2c9623b5a7 Fixed assertion collision issues
When run via karma, all the tests are loaded into the same page.
This was causing a collision in the 'displayed' assertion dealing
with using viewportLoc.

The assertions are now in their own file, pulled in by tests that
need them.  Additionally, several tests which only set fb_width
and fb_height were correct to set viewportLoc as well.

Closes #392

Also-Authored-By: Martin André (github: mandre)
2014-09-19 12:16:06 -04:00
Solly Ross f8f95d6023 Added Sauce Auth Details for Travis CI
This will enable Travis CI to run tests on Sauce Labs
(thanks to OpenSauce, their free program for Open
Source Software)
2014-09-19 11:43:58 -04:00
Martin André 77bd04f833 Fix invalid updateState property of RFB
It was changed to `onUpdateState` in
b1dee94788.
2014-09-17 16:56:13 +09:00
Martin André 7caa9c20c2 Prevent noVNC loading error when invalid property is set on object
Util.set_defaults should accommodate with missing properties to prevent
'Uncaught TypeError: Cannot read property 'call' of undefined' error.
2014-09-17 16:50:05 +09:00
Solly 0ca7cf4867 Merge pull request #368 from DirectXMan12/refactor/cleanup
Cleanup and test all the things (plus ditching Crockford)!
2014-09-15 16:50:20 -04:00
Solly Ross e6af0f60b0 Add support for Travis CI and SauceLabs Testing
This adds support for Travis CI and SauceLabs
testing.  Testing on SauceLabs in done via
the Karma test runner.  Note that encrypted
Sauce username and access key values need
to be inserted into .travis.yml as global
environment variables.  Additionally, the
local test runner (which is still useful
for debugging tests and code) was updated
to reflect that the 'node_modules' folder
now gets placed in the root directory.
2014-09-15 16:46:02 -04:00
Solly Ross bbbf42bb5a Cleanup: UI code
File: ui.js
Tests Added: False

Changes:
- Fix JSHint errors
- add some curly braces to improve clarity
- move variable declarations to relevant locations instead of at the top
  of methods
2014-09-15 16:46:02 -04:00
Solly Ross b1dee94788 Cleanup: RFB Client
File: rfb.js (also websock.js)
Tests Added: True

Changes:
- De-Crockford-ified rfb.js
- Added methods to websock.js to skip bytes in the receive queue
2014-09-15 16:46:02 -04:00
Solly Ross 1e13775bd5 Cleanup: Display Helper
File: display.js
Tests Added: True (preliminary)

Changes:
- De-crockford-ified the file

NOTE: the tests included for display.js cover basic functionality, but
are by no means nearly as comprehensive as the ones presented for
rfb.js.
2014-09-15 16:46:02 -04:00
Solly Ross 2cccf7530c Cleanup: WebSocket Helper
File: websock.js
Tests Added: True
Changes:
- Cleaned up JSHint errors
- Converted to normal JS constructor pattern with "private" fields and
  methods now simply being prepended by underscores
- Added a "bind" polyfill for use in PhantomJS 1.x in util.js
- Added FakeWebSocket to fill in for actual WebSocket objects when
  testing
- Made exception handler actually log exception name and message,
  to console, in addition to stack trace
2014-09-15 16:46:02 -04:00
Solly Ross ee7d4c61c6 Cleanup: Webutil code
File: webutil.js
Tests Added: False
Changes:
- Fixed JSHint Errors (global "use strict", spaces)
- added some newline characters when appropriate for readability
- moved variable declarations to the places they were actually used
  for readability
2014-09-15 16:46:02 -04:00
Solly Ross 31f169e86f Cleanup: Keyboard code
File: keyboard.js
Tests Added: False (already present)
Changes:
- Fixed JSHint Errors
- Moved functions outside loops
- Added proper include directives to tests
2014-09-15 16:46:02 -04:00
Solly Ross d6e281baf6 Cleanup: Input code
File: input.js
Tests Added: False (already present partially -- see below)
Changes:
- Fixed JSHint Errors
- Converted to normal non-Crockford constructors

NOTE: while there are tests for the actual key-detecting functionality,
      the tests do not cover the actual Keyboard and Mouse objects
      themselves.
2014-09-15 16:46:02 -04:00
Solly Ross d21cd6c164 Cleanup: Util code
File: util.js
Tests Added: True (partial -- for logging and array push methods)
Changes:
- Fixed JSHint Errors (indentation, semicolons, global "use strict")
- Made browser detection methods more readable
- added some newline characters when appropriate for readability
- throw Errors not strings!
- Removed conf_defaults, and added make_properties and set_defaults
  instead (see below)

The removal of conf_defaults and switch to make_properties and
set_defaults is to facilitate the switch over to normal Javascript
constructors instead of Crockford-style constructors.  Now, methods
are added to the objects prototype (and thus make properties is called
outside the constructor).
2014-09-15 16:46:02 -04:00
Solly Ross fb64ed2135 Cleanup: DES code
File: des.js
Tests Added: False
Changes:
- Fixed JSHint Errors
- Improved whitespace around operators
2014-09-15 16:46:02 -04:00
Solly Ross f8e9b9f1bf Cleanup and Test: base64.js
This is the first commit in a series of commits
which improve the readability of some of the code
and add tests.

File: base64.js
Tests Added: True
Changes:
- Improved indentation
- Fixed JSHint errors
- Moved loop variables to be declared in the loop for better readability
  (N.B. Javascript does not have block scoping, so the variables are
  still technically available outside the loop -- it just makes the code
  clearer to place them inside the loop, since they are only used there)
2014-09-15 16:46:02 -04:00
Solly Ross 95eb681bbb Support the "NOTUNNEL" tunnel type for TightVNC
Previously, tight auth was supported without any support for tunnels,
even the no-op tunnel.  No, the no-op tunnel type is supported.
2014-09-15 16:46:02 -04:00
Solly Ross 91127741be Support running all tests from the root directory
Previously, if you did not specify a tests file,
you had to be in the 'tests' directory for the
"run all tests" functionality to work.  Now it
will work in any directory.
2014-09-15 16:44:36 -04:00
samhed 960752ea53 fixes an error that was made in the merge with the last sync with websockify. 2014-08-19 13:49:55 +02:00
Solly Ross 9b731d3a58 Fix race condition in test runner
Previously, there would be a case where if your tests took
too long to run, the casper test runner would only report
on certain tests.  This has been fixed.
2014-06-03 17:42:28 -04:00
Solly Ross 93af721a27 Output error events from provider in test runner
Now, 'error' events from the test runner are output to stderr.
Additionally, when debug is enabled, debug output is logged to
stderr instead of stdout (as was the case previously).
2014-06-03 16:58:37 -04:00
Solly Ross 7187bc121f Fixed typo in run_from_console.js 2014-06-03 13:15:02 -04:00
Solly Ross d906dfc953 Add support for injecting test helper files
Now, the phrase `requires test modules: ` may be place in a comment
in a file to require modules local to the test directory, similarly
to the way the `require local modules: ` line may be used to inject
files in the 'include' directory.  This is useful for when common
fakes need to be injected into a test.
2014-06-03 11:17:33 -04:00
Solly Ross 4a4643c05d Support automatically opening test HTML in browser
When using the '-g' option with run_from_console.js, you can
now pass the '-o' option to automatically open the generated
HTML file in your default browser.  This relies on the 'open'
NPM module.
2014-06-03 11:17:29 -04:00
Samuel dd3a8a1b6d Merge pull request #363 from kosmasgiannis/query_parse
Better parsing of query string variables
2014-05-06 09:13:18 +02:00
Giannis Kosmas d8c0953567 Better parsing of query string variables 2014-05-05 23:23:48 +03:00
Solly Ross c77938efc9 Fix for scroll offset in Util.getPosition()
Previously, Util.getPosition didn't deal with scrolling
particularly well.  This fixes that by calculating the
scroll offset when dealing with getting mouse positions.

Credit to @erikgull and @emmar for the initial version of
the fix.  Credit to Brian Huismanfor the initial code.

Closes #295
Relevant to #258
2014-05-05 14:51:32 -04:00
samhed 082027dc87 Sync with websockify
Pull 90b519edf0c1857d
2014-04-14 14:45:15 +02:00
samhed 60a415ae1c Credit for this fix goes to Jesper Dam (https://github.com/Medical-Insight/noVNC/tree/fix-ie10-keyboard).
Fix keyboard handling for IE10 (issue #352)
* Keyboard events in IE10 do not provide any useful information on the properties 'which', 'char' or 'charCode'. Instead, it seems to store the char code in the keyCode property.
2014-04-14 13:26:52 +02:00
samhed 8f4a291b16 Updated the comment in vnc.html to reflect the changes made in last commit. 2014-04-07 16:58:59 +02:00
samhed eeda79d3c3 Changing the type of the keyboardinput element to textarea, fixes issue 356.
* The on-screen keyboard not closing when pressing Enter is more important than not having textsuggestions etc.
2014-04-07 16:41:25 +02:00
samhed 0bb6a8c50e Fixes issue 344 - problem with backspace on Android. 2014-04-04 17:45:04 +02:00
samhed de8edde4a0 Attempt to change the behavior of the enter/go-key on touch devices, we want it to be Enter.
* This code works in Firefox on Android and on Chrome and Safari on iOS.
* It does not work in Chrome on Android, the enter key is labled "Go" and closes the on screen keyboard when pressed.
2014-04-04 17:42:12 +02:00
samhed 74d3d75a4a Change type of keyboardinput to password to make sure that you can write parenthesis on iOS devices. 2014-04-04 17:36:17 +02:00
samhed b98fed2f69 Aim to disable text suggestions and the like 2014-04-04 17:28:44 +02:00
samhed df0ee70e36 Make sure that the default value of the hidden input field is a space. 2014-03-28 08:54:49 +01:00
Solly 7cad15425e Merge pull request #347 from dol/fix/better-ws-binarytype-detection
Improved websocket binary support detection
2014-03-26 10:58:11 -04:00
samhed 23817e1516 Added alt-values to the control-bar buttons.
* On slow servers where the button-images can be slowly loaded it can be useful to see some relevant text instead of "Submit query" on every button.
2014-03-26 15:24:50 +01:00
samhed 0fe30338a0 Add a prototype from MDN for indexOf to be used in browsers where it is not supported. 2014-03-26 15:22:45 +01:00
samhed 2fa1c0988a Removed unused code. 2014-03-26 11:18:48 +01:00
samhed 2db62a9528 Fixed typo in README. 2014-03-17 14:24:42 +01:00
samhed b7996b048b Added a wrapper for the UTF-8 decoding. 2014-03-14 15:18:05 +01:00
samhed 6227a91c01 Decode from UTF-8 to allow unicode characters in the connection name 2014-03-14 13:44:10 +01:00
Samuel f1e6daf3c8 Merge pull request #348 from Medical-Insight/fix-altgr-firefox
Fix altgr firefox
2014-03-12 16:51:20 +01:00
Jesper Dam 230784066c Better browser OS detection.
Apparently Firefox on Linux changed the value of navigator.appVersion,
causing our OS detection (used to determine how to interpret different
modifier keys) to fail.

Use navigator.platform instead, which should be more stable.

http://stackoverflow.com/a/19883965/33213
2014-03-12 11:19:57 +01:00
Jesper Dam c3f6052435 Better key identifiers
Previously we identified keys in keyboard events by the 'key' property
if it was set, and 'keyCode' otherwise.

This turns out to be problematic as Firefox no longer leaves 'key'
undefined (so we fall back to using 'keyCode'), but instead sets 'key'
to 'MozPrintableKey' for all printable keys.

This meant that when (printable) keys are released, we can't match it
against the corresponding keydown event, and instead just send a keyup
event for the last keydown received.

Now, if both 'key' and 'keyCode' are set, use the concatenation of both.
Otherwise prefer 'keyCode', as that is at least unique for every key.

This should let us release the right keys on keyup events.
2014-03-12 11:19:56 +01:00
Dominic Luechinger 97362c3980 Improved websocket binary support detection
A facke connection to 'wss://localhost:17523' (randomly chosen) to detect
the WebSocket binary support is not the best solution.
First of all, check of prototype has the property 'binaryType'. If not,
perform a dummy connection to 'wss://.' instead of 'wss://localhost:17523'.

This patch was inspired by the discussion and implementation of Modernizr:
https://github.com/Modernizr/Modernizr/issues/370
https://github.com/Modernizr/Modernizr/blob/master/feature-detects/websockets/binary.js
2014-03-11 14:29:58 +01:00
Solly c70000bac4 Merge pull request #338 from vatesfr/configurable-ws-protocols
WebSocket protocols are now configurable
2014-02-19 18:10:44 -05:00
Julien Fontanet c7fc3c082f WebSocket protocols are now configurable. 2014-02-19 19:29:06 +01:00
Solly e791b87572 Merge pull request #242 from Medical-Insight/use-wss-for-binary-detect
Use wss when creating localhost connection to detect binary support (closes #242)
2014-02-17 18:09:16 -05:00
Malcolm Scott fb35d50f6a Implement XVP UI in HTML 2014-02-17 17:55:28 -05:00
Malcolm Scott a856a051da Implement XVP extension (shutdown/reboot/reset) 2014-02-17 17:55:28 -05:00
Malcolm Scott 03ab251587 Implement XVP auth 2014-02-17 17:55:28 -05:00
Solly df6b7d7341 Merge pull request #327 from devicenull/master
Add support for connecting to TightVNC servers
2014-02-10 17:21:29 -05:00
Samuel afbeb98cfb Merge pull request #336 from samhed/removeConnectionTimeout
Remove the connection timeouts
2014-02-07 10:15:04 +01:00
samhed 20074a49eb Removes the connection timeouts to enable slower clients, connections or servers. 2014-02-06 16:08:00 +01:00
Brian Rak d86cc2d911 Add support for TightVNC auth type 2014-01-30 19:20:41 -05:00
Solly 98c1275d80 Merge pull request #329 from Medical-Insight/fix-shift-chars
Fixes #326: correct handling of shift key
2014-01-13 13:01:16 -08:00
Jesper Dam f6a1d98a3a Fix issue #326: correct handling of shift key
When shortcut modifiers (modifier keys such as CTRL, which do not participate in
composing character input) are pressed, we try to suppress the keypress
event, as browsers do not reliably generate it. This means that
subsequent key events are decoded only based on the keydown event.

Due to a type error (comparing a string to a number), shift was
mistakenly treated as a shortcut modifier, preventing text input which
relied on shift, such as _ and %, from being generated.
2014-01-06 13:59:25 +01:00
Solly Ross 85e8991664 Support 'requires' Line in Test Runner
If the files passed to the '-t' option are all '.js' files (or
the 'run all tests' option is used) and the '-i' option is not
passed, all tests will be search for the string
'require local modules: '.  Only the first instance of this string
will be used.  Following the colon should be a list of either local
modules (i.e. files in the '../include/' folder relative to the
test runner's directory, without the '.js' extension) or paths
to other Javascript files.  The list of modules and/or files should
be comma-separated.  These files will then be included in the generated
HTML file for the appropriate tests as if the '-i' option had been used.
2013-12-17 18:00:57 -05:00
Solly Ross d823e8956e Support Running All Tests
Now, if the '-t' option is passed but no tests are listed,
all tests in the same directory as the launcher will be run.
A file is considered a test if it matches the RegEx
/^test\.(\w|\.|-)+\.js$/ (for those who cannot read PCRE,
that's roughly 'test.*.js').
2013-12-17 18:00:45 -05:00
Solly Ross 8eb88937cc Support Skipped Tests and Fix JSHint Issues
The test runner now will not break when Mocha skips tests,
and will properly report them.  Additionally, several JSHint
warnings were fixed, and a `--debug` option was added to see
output from the provider.
2013-12-17 17:56:18 -05:00
jalf 466a09f0f3 Keyboard Handling [8/8]: Introduce substituteCodepoint() to replace code points which don't have a keysym with ones that do
For now, the only code points this is done for are {s, S, t, T} with comma below (used in Romanian),
 which are replaced by {s, S, t, T} Cedilla.
2013-12-05 12:31:50 -05:00
jalf bf47095944 Keyboard Handling [7/8]: Replace keysym table with optimized version
Regenerate the keysymdef table without -d.

This eliminates keysym names, which are only used for debugging, and shaves ~40kb off the file size
2013-12-05 12:31:50 -05:00
jalf 2ea40fdf9a Keyboard Handling [6/8]: Update everything to include the new keyboard handling scripts 2013-12-05 12:31:48 -05:00
jalf fa30469cda Keyboard Handling [5/8]: Update rfb.js to connect mouse events to keyboard handler
This allows the keyboard handler to check modifier key state much more frequently

Since some browsers never send keyup events for modifier keys, we have to
synchronize modifier state whenever we get a mouse or keyboard event
2013-12-05 12:25:31 -05:00
jalf 9ceef041c6 Keyboard Handling [4/8]: Update input.html to work with new keyboard handling
Plug new keyboard handling into input.js (which breaks everything else), and update input.html to work with this
2013-12-05 12:25:30 -05:00
jalf f00b6fb69a Keyboard Handling [3/8]: Add unit tests for new keyboard handling
Relies on the libraries chai and mocha (available via npm from Node.JS).

Add anything installed via npm to the .gitignore file.
2013-12-05 12:25:30 -05:00
jalf 4ef7566b10 Keyboard Handling [2/8]: Core implementation of new keyboard handling
Add keyboard.js, containing the actual keyboard event parsing code.
2013-12-05 12:25:15 -05:00
jalf ae52883b93 Keyboard Handling [1/8]: Parse keysymdef.h to produce Unicode -> keysym mappings table
Add a node.js-based tool (utils/parse.js) to read keysymdef.h and produce a JavaScript file
mapping Unicode code points to keysyms.

Also add the generated table (include/keysymdef.js).
2013-12-05 12:24:20 -05:00
Solly Ross 1e570156f9 Add support for Outputting Autogenerated Test HTML
This commit introduces two flags, '-g' and '-o' to
the `run_from_console.js`.  Both flags do not run
the tests.  Instead, deal with the autogenerated
HTML.  The former outputs the paths to the autogenerated
HTML temp files, and then pauses the program until Ctrl-C
is pressed (or SIGINT is sent).  The latter outputs the
generated HTML for each files to STDIN with the names
of the tests to which they belong.
2013-12-04 15:44:55 -05:00
Solly e0dc102e5b Merge pull request #322 from DirectXMan12/master
Support Running Mocha Tests from the Console
2013-12-04 11:09:54 -08:00
Solly Ross 2af865923c Support Running Mocha Tests from the Console
Previously, the only way to run the Mocha tests
(in 'test.*.js') is to write a web page to wrap
them (or use a provided one), and then load that
file in a browser.

This commit introduces a series of files to allow
you to run the Mocha tests from the command line
instead.

Normally, Mocha tests can be run from
the command line anyway.  However, since this
project was designed to work in web browsers
and not node, the code doesn't contain the
proper `require` calls, nor does it contain the
proper `module.exports` declarations.  Additionally,
some of the code is dependent on having a browser
environment.

To overcome these issues, a headless browser environment
is used.  The command file introduced in the commit,
`run_from_console.js`, can use one of two environments:
ZombieJS, a pure-javascript headless browser simulator, or
SpookyJS/CasperJS/PhantomJS, an actually WebKit-based
environment.

Because the environment-dependent code is separated
out in to different files ('run_from_console.zombie.js'
and 'run_from_console.casper.js'), the program can be
safely used if only one of the supported environments
is installed.

Additionally, the command will automatically generate
HTML and inject the required tests if there is no
pre-existing HTML file (although you can still use
pre-existing HTML files if you want to).

The required NPM modules for the base program are:

- commander
- ansi
- mocha (must be installed locally for the HTML files to use)
- chai (must be installed locally for the HTML files to use)
- temp

For Zombie, you need:

- zombie
- q

For Casper, you need:

- casperjs (must be installed locally in order to work properly)
- phantomjs
- phantom
- spooky

The command itself can be invoked as

   $ node run_from_console.js -t html_files

or

   $ node run_from_console.js -t js_test_files -i js_required_files

In both cases, the 'files' options should be a comma-separated list of
files.  The first case runs pre-existing HTML files.  The second case
generates HTML files to run the specified Mocha tests, and injects
the requirements specified as well.

Additionally, there are extra arguments that apply to both forms:
'-a' can be used to print all test results, not just the failures,
'-c' may be used to force color to be enabled (when outputting to
a pipe, such as when `less -R` is in use), and '-e' is used to
set the environment.  Use the '-h' or '--help' options to see
a detailed description of all options, and their long-form versions.
2013-12-04 01:08:44 -05:00
Joel Martin 75d69b9f62 Fix to relfect new github.io page base URL. 2013-10-29 14:38:24 -05:00
Joel Martin f0d30a90f3 Merge pull request #317 from dosaboy/bugs/add_secure_property_to_token_cookie
Adds 'secure' property to 'token' cookie
2013-10-29 07:19:23 -07:00
Takashi Natsume ad941fadde Adds support for secure attribute on token cookie
This patch adds support for the secure attribute on token
cookies (sent by nova-novncproxy). If the https is used
to transfer the cookie, the secure attribute is set thus
restricting server requestes to secure conections only.
This should prevent man-in-the-middle attacks.
2013-10-29 10:41:51 +00:00
Joel Martin f901d9c9b8 README: add core team and other cleanup.
- Make the ProjectsCompanies-using-noVNC the canonical source for
  companies/project using noVNC and only give a brief summary here.

- Drop the personal Amazon donate/thanks link. Keep the charities and
  ask for a tweet if so.
2013-10-28 16:01:40 -05:00
Joel Martin be09828537 Merge branch 'master' of github.com:kanaka/noVNC 2013-10-11 14:27:12 -05:00
Samuel 142aa4583c Merge pull request #284 from samhed/ctrlalttabesc
Support for extra keys such as Ctrl, Alt, Tab and Esc on mobile devices
2013-10-11 08:21:59 -07:00
samhed f4f72e9db9 Merge branch 'master' of https://github.com/kanaka/noVNC into ctrlalttabesc
Conflicts:
	include/ui.js
	vnc.html
2013-10-11 17:20:49 +02:00
Samuel 69127447ac Merge pull request #282 from samhed/framebufferupdate
Allow higher frame rates by using a new approach for framebufferUpdate requests
2013-10-10 02:45:08 -07:00
samhed 3346f9229b Fix so that parentheses can be written from iOS devices.
* changed the type of the input field (which is used for bringing up the
      on-screen keyboards on touch devices) from email to text
    * when typing in an input field of type email the user is not allowed to
      write parentheses on iOS devices, the keys are disabled.
    * tested on iOS 6, iOS 7 and Android 4.3.
2013-10-09 11:35:43 +02:00
Samuel 270ae2f9f5 Merge pull request #304 from samhed/localcursor
Enables local cursor by default
2013-10-09 01:44:41 -07:00
Samuel 5eed5a4e17 Merge pull request #303 from retr0h/https-first
The https check should come first.
2013-10-09 01:38:55 -07:00
Joel Martin 7c76fd32a1 Merge pull request #308 from samhed/autoconnect
Autoconnect option as URL query
2013-10-04 13:29:24 -07:00
Joel Martin 4c0b680a0b Merge pull request #301 from samhed/chromkeyboard
Fixes the onscreen keyboard in Chrome on Android
2013-10-04 13:17:47 -07:00
samhed f8ddfc732d Autoconnect option as URL query 2013-10-04 17:59:03 +02:00
samhed 9b9e741b8c On touch devices the default should be to use a remote cursor. 2013-09-24 11:19:17 +02:00
samhed 97aefe5f83 Enables local cursor by default. 2013-09-23 11:48:16 +02:00
John Dewey 82744aa8ee Removed trailing white space terds 2013-09-21 12:19:59 -07:00
John Dewey eb955f8c20 The https check should come first
A similar change was made to ui.js in #252.
2013-09-21 12:19:09 -07:00
samhed 968431dd46 Catch input events to make the onscreen keyboard work in chrome on android. 2013-09-17 15:01:52 +02:00
samhed b4a979a07e Added a new file for the key symbols, using the well recognized names for the constants from X11:
* The list is found in /usr/include/X11/keysymdef.h
* I did only include the most common symbols for now..
2013-08-30 15:41:46 +02:00
samhed 04d6a8347f Added variables for the keysums to make the code easier to read. 2013-08-29 17:35:22 +02:00
samhed 76e262134e * Removed fbu-requests from the places I missed earlier.
* Added a few clarifying comments.
2013-08-29 13:38:12 +02:00
Joel Martin 3cb89f5a14 Merge pull request #285 from glance-/rfb_js_fixes
Rfb js fixes
2013-08-26 12:15:55 -07:00
Anton Lundin c39df031d8 clearTimeout instead of clearInterval for timers
We create timeouts, not intervals. Then we need to clear them with
clearTimeout.
2013-08-02 09:58:15 +02:00
Joel Martin 292f6a5da7 Merge pull request #269 from phildriscoll/master
Fix to onMouseDisable
2013-07-31 08:35:02 -07:00
samhed bd88b94393 Switched from using a list to absolute positions to achieve more
consistant behavior across different browsers.
2013-07-25 15:34:16 +02:00
samhed 9d04096e58 Tweaked the media sizing rules to match the new buttons to the left. 2013-07-25 14:05:52 +02:00
samhed 53c01a2353 Added the extra keys Ctrl, Alt, Tab and Esc to the control bar. 2013-07-25 12:49:37 +02:00
samhed 41c66fbfcd Added new images for the extra keys 2013-07-25 12:48:44 +02:00
samhed 7e24f50b66 Merge branch 'master' of https://github.com/kanaka/noVNC into framebufferupdate 2013-07-24 10:55:21 +02:00
Joel Martin f3ff971db9 Merge pull request #272 from samhed/doubleclick
Allow for double taps to trigger double clicks even if the second touch is a slight bit off
2013-07-23 08:45:47 -07:00
Joel Martin 31164434b2 Merge pull request #283 from samhed/nomousedrag
Fixed so that viewportDrag works even if no mouse button is selected.
2013-07-23 08:43:31 -07:00
samhed fb4394b10b Fixed so that viewportDrag works even if no mouse button is selected. 2013-07-23 14:26:11 +02:00
samhed dfcedffc16 Make noVNC follow the RFB protocol and keep only one outstanding
framebufferUpdate request at a time.
2013-07-22 15:46:59 +02:00
samhed df4d6dde30 Merge branch 'master' of https://github.com/kanaka/noVNC into doubleclick 2013-07-19 09:47:47 +02:00
Joel Martin 0e3d505e54 Merge pull request #276 from samhed/screenrealestate
Improved use of screen real estate
2013-07-18 09:58:02 -07:00
Joel Martin df9d3d9cf2 Merge pull request #280 from samhed/documenttitle
Display the desktop name in the document title
2013-07-18 09:52:36 -07:00
samhed 406a8b4e96 Display the desktop name in the document title 2013-07-18 15:51:23 +02:00
samhed 0019d3b055 Added Copyright information to all modified files. 2013-07-17 16:44:13 +02:00
samhed 35b29c98ea * On low resolution devices the status text is now justified to the left
button group instead of centered.
* On low resolution devices the padding towards the edges of the screen of
  the left and right button groups are now removed.
* Since the status_bar was merged into the control-bar the actual status_bar
  element became redundant and was therefor removed.
* Improved the style of the control-bar when there is an error or a warning.
* Implemented a fix so that vnc_auto.html works as intended with the improved
  screen real estate patch.
2013-07-17 16:11:19 +02:00
samhed 6ca8a2c05c Moved the popup_status_panel to the front. 2013-07-12 14:41:57 +02:00
samhed 7ab02c7fc7 Changed the color and weight of the status text to white and bold. 2013-07-12 11:47:41 +02:00
samhed c6ad20992d Improved comments 2013-07-11 12:37:01 +02:00
samhed cf19ad3798 * Changed the trigger distance between touches from 50 to 20.
* The trigger distance now takes devicePixelRatio into account.
2013-07-08 17:14:50 +02:00
samhed a4ec2f5c7d Limited the double click fix to touch devices. 2013-07-03 14:41:09 +02:00
Samuel Mannehed 9e97231acf Made the control bar thinner by removing the space below and above the control buttons. 2013-06-28 16:31:45 +02:00
Samuel Mannehed cc704b712d Removed duplicate of .noVNC_status_warn from the stylesheet. 2013-06-28 15:32:37 +02:00
Samuel Mannehed 0fa4e0a90a Merged the control-bar and the status-bar together.
Also added a popup with the status text.
2013-06-28 15:13:20 +02:00
Samuel Mannehed b2f1961a3a to make double clicking on touch devices easier a double click is now triggered when two mouse clicks occur within 500 milli seconds and closer than 50 pixels together 2013-06-25 17:55:45 +02:00
Phil Driscoll ca9a9964a0 Fix onMouseDisable so that clicks outside the canvas are propagated 2013-06-13 11:10:04 +01:00
Joel Martin c3c51ed32b Sync with websockify.
Pull in websockify 4725aa7.

- Update to c0855c6cae of web-socket-js

- Update both the submodule and the swf build. The submodule now
  contains the unobfuscated source for swfobject.js which should make
  websockify more DFSG compliant.

- Remove Hixie support. iOS 6 now includes HyBi support which means
  there is no remaining platform that needs Hixie.
2013-04-25 09:52:10 -05:00
Joel Martin 8f12ca7a5a Merge pull request #252 from astrand/port-80-443
Re-fix pull req #245:
2013-04-25 06:14:47 -07:00
Peter Åstrand (astrand) b66ffcddcf Re-fix pull req #245:
* Must declare port variable.

* Must look for https first, then http.
2013-04-25 09:07:09 +02:00
Joel Martin e6500004de Merge pull request #245 from wrouesnel/vnc-auto-fix
Detect port properly in vnc_auto.html when used on port 80 or 443.
2013-04-23 09:19:59 -07:00
Will Rouesnel 58873b3222 Use port in UI.initSetting in include/ui.js
Because apparently it is simple enough for me to forget to include in the previous commit.
2013-04-23 14:32:02 +10:00
Will Rouesnel c42136d537 Add default port logic to include/ui.js
Correctly identifies the use of http/https and forces the value of port to 80 and 443
respectively, as in this situation window.location.port is blank.

This is patch includes the same changes as made in vnc_auto.html
2013-04-22 20:07:20 +10:00
Joel Martin 44ff863273 rfb.js: set FBU.bytes before waiting from RRE.
Also, in COPYRECT, set FBU.bytes to the number we know we will need.
2013-04-17 09:34:33 -05:00
Joel Martin 1e04775831 Merge pull request #249 from Medical-Insight/copy-rect-bytes
Set FBU.bytes to 1 in encHandlers.COPYRECT
2013-04-16 11:18:23 -07:00
Vagn Johansen 9f554fcdf7 Set FBU.bytes to 1 in encHandlers.COPYRECT
FBU.bytes must be set before return false to indicate that the FBU
header has been read. If not set then "if (FBU.bytes == 0) { ..}"
in framebufferUpdate will be entered and the copyrect header will
be interpreted as a new FBU leading to a "unsupported encoding"
disconnect error.
2013-04-16 19:17:50 +02:00
Joel Martin 7c1cd93744 Remove references to wsproxy.
Also, update nova-novncproxy to import websockify instead of wsproxy.
2013-04-15 12:22:34 -05:00
Will Rouesnel e83b9e03eb Detect port properly in vnc_auto.html when used on port 80 or 443.
Browsers (such as Chrome) don't report port numbers in window.location.port when
used on standard ports such as 80 and 443. This causes vnc_auto.html to not
automatically find the port as it should.

This simple change checks if window.location.port is blank, and sets port as
appropriate from the name of the protocol in use.
2013-04-13 18:45:56 +10:00
Joel Martin 7dc038efd9 Add gimite/web-socket-js submodule for DFSG compliance. 2013-04-12 08:21:33 -05:00
Joel Martin cededc4d14 Merge pull request #244 from djipko/common_conf_to_oslo_conf
Import cfg from oslo in nova-novncproxy
2013-04-08 10:32:03 -07:00
Nikola Dipanov de0f8773b1 Import cfg from oslo in nova-novncproxy
Upstram OpenStack Nova package has moved the config engine into a
separate package. Mirror that change in the novnc script.
2013-04-08 15:42:56 +02:00
Joel Martin 60d991774b Merge pull request #241 from astrand/html-cleanup
Minimize differences between vnc.html and vnc_auto.html
2013-04-05 06:46:52 -07:00
jalf ec34af8f61 Use wss when creating localhost connection to detect binary support
Some browsers (Firefox) reject unencrypted ws connections if the hosting
page uses https by throwing an exception when you create the websocket
object.

NoVNC interprets this as binary being unsupported, and falls back to
base64.

If we instead connect to wss://localhost, the websocket object can be
created regardless of whether the hosting page is on http or https.
2013-04-05 13:49:17 +02:00
Peter Åstrand (astrand) 33f5d3bd92 Sync more header meta stuff from vnc.html:
* UTF-8 charset defintition.

* Define IE=edge for X-UA-Compatible.

* Add Apple iOS Safari settings.
2013-04-05 09:30:10 +02:00
Peter Åstrand (astrand) 9f0d23de4d Move over comment about URL connect parameters from vnc_auto.html. 2013-04-05 09:29:08 +02:00
Peter Åstrand (astrand) b597bdd1ec Fix typo:
names="apple-mobile-web-app-status-bar-style"

should be:

name="apple-mobile-web-app-status-bar-style"
2013-04-05 09:22:19 +02:00
Joel Martin c2b1409a43 Merge pull request #239 from astrand/html-cleanup
Tweak order/whitespace/style to match vnc.html more closely.
2013-04-04 07:22:37 -07:00
Peter Åstrand (astrand) e84101b3e7 Tweak order/whitespace/style to match vnc.html more closely. No
functional changes.
2013-04-03 16:22:21 +02:00
Joel Martin 3b2acc2258 Merge pull request #225 from Medical-Insight/crlf-to-lf
Consistent Unix line endings
2013-01-31 06:58:33 -08:00
jalf 301fc915a6 Converted to unix line endings 2013-01-31 11:24:04 +01:00
jalf b1b342a97e Capture mouse events and filter irrelevant ones 2013-01-30 13:17:50 +01:00
jalf 0d0f754aad Listen for mouseup events from window as well as target element
Pressing an holding a mouse button and then moving the mouse out of the
canvas before releasing meant that onmouseup was never triggered.
2013-01-30 13:17:32 +01:00
jalf 8a147535de Clamp mouseclick coordinates to target element bounds 2013-01-30 13:17:32 +01:00
Joel Martin 49d5cee91e Merge pull request #220 from vaj/keyinput
Ctrl+'-' won't work on a US keyboad with the latest Firefox.
2012-12-23 21:09:16 -08:00
Hirokazu Takahashi 37a3081f05 Ctrl+'-' won't work on a US keyboad with Firefox.
Firefox 17 generates 173 as the keycode for the minus key.
2012-12-23 18:32:28 +09:00
Joel Martin d55f5377bb Merge pull request #216 from komawar/config-module
Changed the config module which includes CONF
2012-11-30 11:58:50 -08:00
Nikhil Komawar 902c6fb37f Changed the config module which includes CONF
currently CONF was attempted of being imported from nova config.py
rather than openstack/common/cfg.py. this commit fixes that.
2012-11-30 11:50:57 -05:00
Joel Martin 705c54edb6 Sync with websockify.
Pull c33f0b52e7 from websockify.

- Fix for python2.4 with URL parsing.
- Set binaryType earlier in Opera 12.10 to avoid receiving a blob.
- Re-order client and target processing so that pending client data
  has an opportunity to be sent when the target closes.
2012-11-14 12:28:22 -05:00
Joel Martin 361cde7c96 Merge pull request #214 from otherwiseguy/master
Update utils/nova-novncproxy to work with nova master branch
2012-11-08 15:12:24 -08:00
Terry Wilson f167ea0ecc Mirror FLAGS->CONF change in Nova
Nova recently removed parse_args from flags.py. This updates the
nova proxy to properly use CONF instead of FLAGS.
2012-11-08 17:49:12 -05:00
Joel Martin 3516bdf3fa util/launch.sh: add --web option and try /usr/share/novnc 2012-11-01 11:35:35 -05:00
Joel Martin 51562999c7 util.js: script load sequential execution changes.
Related to issue: https://github.com/kanaka/noVNC/issues/205

Split out the function to load a single script to Util.load_script.

In order to get sequential load, when on IE set the script defer flag.
It is currently working on webkit and firefox but just in case also
set the script.async flag to make sure that scripts execute in the
order they are added. Scripts should still load in parallel.
2012-11-01 10:47:37 -05:00
Joel Martin e7e6660272 include/playback.js: support binary (non-base64) data files.
Data files should now set the variable VNC_frame_encoding to either
"binary" or "base64". The python websockify recording mode adds this
automatically based on what is negotiated with the client being
recorded.
2012-10-26 18:08:00 -05:00
Joel Martin 49578da448 sync with websockify
Pull in websockify 1669139
2012-10-26 18:07:25 -05:00
Joel Martin c95456c0e1 rfb.js: skip tight when true color is disabled.
Generally this means falling back to 8-bit colour mapped hextile which
is uses more bandwidth and is likely slower that tight with 24-bit
true color.

This is related to this query:
http://superuser.com/questions/484790/how-to-use-8-bit-mode-with-novnc
2012-10-23 16:21:22 -05:00
Joel Martin 73ee4fa7cc util.js: fix dynamic script load on IE 9 (again).
Use a mechanism described here to serialize the execution of scripts
under IE: http://wiki.whatwg.org/wiki/Dynamic_Script_Execution_Order
Basically, we set the src attribute but do not append the script to
the body/head until after readyState is "loaded". Unfortunately, Opera
uses readyState but in a different way (always "loaded" for both load
and execution).

This is related to (and hopefully fixes) these issues:
https://github.com/kanaka/noVNC/issues/194,
https://github.com/kanaka/noVNC/issues/205,
https://github.com/kanaka/noVNC/issues/208.
2012-10-23 15:54:08 -05:00
Joel Martin 5514d29950 vnc.html: use standard HTML5 doctype.
The mobile doctype doesn't not seem to make any difference on iOS so
I'm dropping it and just going with the standard HTML5 version since
the wapforum mobile version may cause issues with IE 10.
2012-10-19 12:18:48 -05:00
Joel Martin 57430b8c12 ui.js: fix drag button so it can be toggled off. 2012-10-19 12:18:21 -05:00
Joel Martin e881ce5a1b Merge remote branch 'Medical-Insight/non-square-cursor-fix' 2012-10-18 17:44:13 -05:00
Joel Martin 8e2d749605 util.js: script load sequential execution changes.
Related to issue: https://github.com/kanaka/noVNC/issues/205

Split out the function to load a single script to Util.load_script.

In order to get sequential load, when on IE set the script defer flag.
It is currently working on webkit and firefox but just in case also
set the script.async flag to make sure that scripts execute in the
order they are added. Scripts should still load in parallel.
2012-10-18 16:13:00 -05:00
Joel Martin 60106f240a Revert "supprt ALTGR key event"
This reverts commit 079504a6d2.
2012-10-17 15:37:24 -05:00
Joel Martin c527482ebf Merge pull request #195 from moobyfr/keyboard-altgr
Keyboard altgr
2012-10-17 13:30:50 -07:00
Joel Martin 35785a9081 Add binary/base64 data mode to perf/playback tests. 2012-10-17 11:58:13 -05:00
Joel Martin 6f4b1e4071 Refactor dynamic script loading. Add util.js:load_scripts()
Related to issue/pulls:
https://github.com/kanaka/noVNC/issues/194
https://github.com/kanaka/noVNC/pull/201
https://github.com/kanaka/noVNC/pull/202

In IE9, the window.onload event can fire before dynamically loaded
scripts have finished loading. This can result in either WebSocket (in
the case of vnc_auto.html) or RFB (in the case of vnc.html) not being
defined at the point when window.onload is called.

- Move the load_scripts routine from vnc.js to util.js (so that
  websockify can use it too). Also, refactor to work when load_scripts
  is called by a script that itself uses load_scripts. When the whole
  chain of dynamically loaded scripts is finished then call
  window.onscriptsload. Use this mechanism in all the places that
  depend on dynamic loading of scripts: vnc.html, vnc_auto.html,
  websock.js, tests/vnc_playback.html, and tests/vnc_perf.html.

- Use the new window.onscriptsload handler instead of window.onload.

- Remove include/start.js and do the script loading and startup event
  handling in include/ui.js instead.
2012-10-17 11:58:12 -05:00
vj 6f4cbb3f3c Always generate square cursor images in changeCursor (fix portrait mode issue).
This is a workaround for corrupted non-square cursors seen on some
Windows 7 machines with NVIDIA cards. The corruption only happens when
the screen is in portrait mode.
2012-10-17 09:56:20 +02:00
Joel Martin 7a6c94b6b9 Merge pull request #191 from takaha/altgr
support ALTGR key event
2012-10-16 08:09:35 -07:00
Joel Martin e16ad2fd02 Refactoring hot blocks to help Chrome optimizing compiler. 2012-10-15 13:48:07 -05:00
Joel Martin 1d728ace69 Change noVNC license to from LGPLv3 to MPL 2.0
The MPL 2.0 license is a "file-level" copyleft license vs the
"project-level" nature of the L/GPL. The intention of noVNC has
always been that it should be easy to incorporate into existing
projects and sites whether free/open or proprietary/commercial. The MPL
2.0 is designed for this sort of combination project but still
requires that any distributed modifications to noVNC source files must
also be published under the same license.

In addition, the MPL 2.0 allows the code to be used in L/GPL projects
(the secondary license clause). This means that any projects that are
already incorporating noVNC should not be impacted by this change and
in fact it should clarify the licensing situation (the exact
application of the L/GPL to web applications and interpreted code is
somewhat ambiguous).

The HTML, CSS, image and font files continue to be under more
permissive licenses (see LICENSE.txt). The included websockify python
code remains under a LGPLv3 license although the include/websock.js
file from the websockify component is now under MPL 2.0 as well.

Permission was received from other noVNC authors to make this change to their
code license on the following dates:

    - Chris Gordon (UI): Jun 24, 2012
    - Antoine Mercadal (DOM,*util.js): Oct 10, 2012
    - William Lightning (UltraVNC repeater): Oct 10, 2012
    - Mike Tinglof (tight encoding): Oct 15, 2012
2012-10-15 13:35:00 -05:00
Joel Martin ab7674ff73 include/base64.js: update to MPL 2.0 base64 code from Mozilla
This updated version is imported from:
http://hg.mozilla.org/mozilla-central/raw-file/ec10630b1a54/js/src/devtools/jint/sunspider/string-base64.js
2012-10-10 14:36:44 -05:00
Blindauer Emmanuel c76b2a8101 Handle Super and Menu keys (Windows keys) 2012-10-06 10:31:47 +02:00
Hirokazu Takahashi 079504a6d2 supprt ALTGR key event 2012-09-25 15:55:06 +09:00
Joel Martin 8dfd916946 Update websockify
Update to websockify febaeee85c

Gracefully handle errors when popping kwargs:
https://github.com/kanaka/websockify/pull/53
2012-09-21 07:09:45 -05:00
Joel Martin 8534fea664 debian/novnc.install: pick up include/start.js 2012-09-20 14:04:35 -05:00
Joel Martin d5fe15096b include/webutil.js: fix when 'chrome' global not present. 2012-09-17 17:18:44 -05:00
Joel Martin 6f955236e6 include/ui.js: call setBarPosition on initial load.
Call setBarPosition on initial load and not just when the window size
changes or scrolling happens.
2012-09-17 17:15:50 -05:00
Joel Martin 5f03c3d093 tcp-client.js: after read, try read again.
After a read completes it's possible more data is pending so issue an
immediate read again to pick this up quickly.
2012-09-17 17:15:50 -05:00
Joel Martin 2cde6e4380 include/vnc.js: dynamic load without document.write.
Instead of using document.write to load scripts, use createElement to
create and append script tags. document.write is problematic in a lot
of situation and in particular is not allowed in a Chrome
extension/packaged app.

Also, in webutil.js, instead of calling init_logging during parsing of
include/webutil.js, rely on the caller to do this. The problem is that
calling init_logging on parse tries to call Util logging functions and
the new model of dynamic load may not having Util loaded by the time
webutil is parsed.
2012-09-17 17:15:49 -05:00
Joel Martin 26945049be vnc.html, ui.js: move all Javascript to ui.js.
Move all the inline Javascript event handlers from vnc.html to
include/ui.js except the load handler which is moved to
include/start.js). This is on the path towards a Chrome
extension/packaged app since inline Javascript is prohibited in that
situation.
2012-09-17 17:15:49 -05:00
Joel Martin ee1af44161 ui.js: use localStorage/chrome.storage for settings.
Switch from using cookies to store setting to using localStorage (or
chrome.storage.sync if available in extension/app mode) for the
settings. Also refactor to make the initializing of the setting and
and loading of the UI to be more asynchronous.
2012-09-17 17:15:49 -05:00
Joel Martin 3af1c2751b webutil.js: add localStorage/chrome.storage settings.
Add routines to store/read settings in either localStorage or in
chrome.storage.sync (which is synchronized between browsers for
extensions/apps).

Before using chrome.storage.sync the initSettings routine must to
called setup the intermediate cache which speeds up access and allows
multiple setting changes to be coallesced to avoid hitting storage
change frequency limits/quotas.
2012-09-17 17:15:49 -05:00
Joel Martin 51fc3b5f03 rfb.js: add onFBResize event callback. 2012-09-17 17:15:48 -05:00
Joel Martin dbec398406 Note that tcp-stream.js is Apache 2.0 LICENSE.
Also, include Apache-2.0 license text.
2012-09-17 17:15:48 -05:00
Joel Martin 8ec3cfaacd tcp-client.js: read/write arraybuffer. Read/disconnect handling.
- enable sending and receiving of raw array buffers in addition to
  strings.

- add a read poll interval and set it to 15ms by default to detect
  and handle quickly when a message is pending.

- also, detect a disconnected state and add call registration for
  disconnect events.
2012-09-17 17:15:48 -05:00
Joel Martin 07392d4fb8 include/chrome-app/tcp-client.js: import upstream.
Import upstream code from Chrome extension telnet demo:
https://raw.github.com/GoogleChrome/chrome-app-samples/0ce28768087863c08aa04a2b9b27bd50796a77ec/telnet/tcp-client.js
2012-09-17 17:15:47 -05:00
Joel Martin 204675c85d Update websockify/websock.js.
This change pulls websockify 6d9deda9c5.

Most note worthy changes:
- Pulls in web-socket-js 7677e7a954 which updates to IETF 6455 (from
  Hixie)
- Binary support detection and use in include/websock.js
- Add ssl and unix target support
- Add multiple target support via config file/dir.
- Idle timeout exit
2012-09-17 17:00:01 -05:00
Joel Martin 3435491edb Merge branch 'binary', fix encode_message
Conflicts:
	include/websock.js

For some reason encode_message was missed. But I'm just punting and
doing the fixup in the merge. Yeah, tsk, tsk.
2012-09-14 17:46:48 -05:00
Joel Martin 39188f4ea8 doc/release.txt: fix small error in process.
Push master and version tag separately.
2012-09-14 17:07:13 -05:00
Joel Martin 625040fc90 README: simplify projects/companies. News/help/contact section.
Instead of continuing to maintain the full list of project/companies
that use noVNC in multiple places (README, wiki, web page) just link
to the wiki page.

Link to noVNC discussion group page. Link to issues page. Link to
Amazon wishlist and non-profits for appreciation.
2012-08-28 17:39:02 -05:00
Joel Martin fa5b334dcb webutil.js: use decodeURIComponent on getQueryVar values. 2012-08-21 09:46:11 -05:00
Joel Martin fcff386b92 websock.js: simpler binary support, protocols param.
Use a simpler method of enabling binary transfer over WebSockets. This
still presents the user of websock.js with a plain javascript array
for the receive queue data. However, if binary support is supported
and requested then the transfer will be raw frames instead of base64
encoded.

Lots of room for optimization here but for now correct is better than
fast.

Pull from websockify 17175afd7311c55abd8d
2012-08-16 13:31:31 -05:00
BLINDAUER EMMANUEL 50b81d4470 AltGR support (Firefox 15 currently) 2012-08-02 09:09:35 +02:00
70 changed files with 12911 additions and 6809 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
*.pyc
*.o
wsproxy
tests/data_*.js
utils/rebind.so
node_modules
+3
View 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
View File
@@ -0,0 +1,16 @@
language: node_js
node_js:
- '0.11.13'
env:
matrix:
- TEST_BROWSER_NAME=PhantomJS
- TEST_BROWSER_NAME=chrome TEST_BROWSER_OS='Windows 7,Linux'
- TEST_BROWSER_NAME=firefox TEST_BROWSER_OS='Windows 7,Linux' TEST_BROWSER_VERSION='30,26'
- TEST_BROWSER_NAME='internet explorer' TEST_BROWSER_OS='Windows 7' TEST_BROWSER_VERSION=10
- TEST_BROWSER_NAME='internet explorer' TEST_BROWSER_OS='Windows 8.1' TEST_BROWSER_VERSION=11
- TEST_BROWSER_NAME=safari TEST_BROWSER_OS='OS X 10.8' TEST_BROWSER_VERSION=6
- TEST_BROWSER_NAME=safari TEST_BROWSER_OS='OS X 10.9' TEST_BROWSER_VERSION=7
global:
- secure: QE5GqGd2hrpQsIgd8dlv3oRUUHqZayomzzQjNXOB81VQi241uz/ru+3GtBZLB5WLZCq/Gj89vbLnR0LN4ixlmPaWv3/WJQGyDGuRD/vMnccVl+rBUP/Hh2zdYwiISIGcrywNAE+KLus/lyt/ahVgzbaRaDSzrM1HaZFT/rndGck=
- secure: g75sdctEwj0hoLW0Y08Tdv8s5scNzplB6a9EtaJ2vJD9S/bK+AsPqbWesGv1UlrFPCWdbV7Vg61vkmoUjcmb5xhqFIjcM9TlYJoKWeOTsOmnQoSIkIq6gMF1k02+LmKInbPgIzrp3m3jluS1qaOs/EzFpDnJp9hWBiAfXa12Jxk=
before_script: npm install -g karma-cli
+17 -13
View File
@@ -1,7 +1,7 @@
noVNC is Copyright (C) 2011 Joel Martin <github@martintribe.org>
The noVNC core library is licensed under the LGPLv3 (GNU Lesser
General Public License). The noVNC core library is composed of the
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):
@@ -10,6 +10,7 @@ is not limited to):
include/display.js
include/input.js
include/jsunzip.js
include/keysym.js
include/logo.js
include/rfb.js
include/ui.js
@@ -36,21 +37,15 @@ The HTML, CSS, font and image files are licensed as follows:
images/ : Creative Commons Attribution-ShareAlike
http://creativecommons.org/licenses/by-sa/3.0/
In addition the following file, which is part of the noVNC core
library, may be licensed under either the LGPL-2, LGPL-3 or MPL 2.0
when it used separately from the noVNC core library.
include/input.js : LGPL-2 or any later version
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
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 LGPL-3 compatible):
licenses (all MPL 2.0 compatible):
include/base64.js : MPL 1.1, GPL-2 or LGPL-2.1
include/base64.js : MPL 2.0
include/des.js : Various BSD style licenses
@@ -59,20 +54,29 @@ licenses (all LGPL-3 compatible):
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
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.MPL-2.0
docs/LICENSE.Apache-2.0
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://www.mozilla.org/MPL/1.1/
http://www.mozilla.org/MPL/2.0/
http://en.wikipedia.org/wiki/BSD_licenses
http://www.gzip.org/zlib/zlib_license.html
http://www.apache.org/licenses/LICENSE-2.0.html
+29 -21
View File
@@ -1,23 +1,25 @@
## noVNC: HTML5 VNC Client
[![Build Status](https://travis-ci.org/kanaka/noVNC.svg?branch=master)](https://travis-ci.org/kanaka/noVNC)
### Description
noVNC is a HTML5 VNC client that runs well in any modern browser
including mobile browsers (iPhone/iPad and Android).
More than 16 companies/projects have integrated noVNC into their
products including [Ganeti Web
Many companies/projects have integrated noVNC including [Ganeti Web
Manager](http://code.osuosl.org/projects/ganeti-webmgr),
[OpenStack](http://www.openstack.org), and
[OpenNebula](http://opennebula.org/). See [the Projects and Companies
wiki page](https://github.com/kanaka/noVNC/wiki/ProjectsCompanies-using-noVNC)
for more complete list.
[OpenStack](http://www.openstack.org),
[OpenNebula](http://opennebula.org/), and
[LibVNCServer](http://libvncserver.sourceforge.net). See [the Projects
and Companies wiki
page](https://github.com/kanaka/noVNC/wiki/ProjectsCompanies-using-noVNC)
for a more complete list with additional info and links.
### News/help/contact
Notable commits, announcements and news are posted to
@<a href="http://www.twitter.com/noVNC">noVNC</a>
<a href="http://www.twitter.com/noVNC">@noVNC</a>
If you are a noVNC developer/integrator/user (or want to be) please
join the <a
@@ -27,16 +29,17 @@ discussion group</a>
Bugs and feature requests can be submitted via [github
issues](https://github.com/kanaka/noVNC/issues). If you are looking
for a place to start contributing to noVNC, a good place to start
would be the issues that I have marked as
would be the issues that are marked as
["patchwelcome"](https://github.com/kanaka/noVNC/issues?labels=patchwelcome).
If you want to show appreciation for noVNC you could buy something off
my [Amazon wishlist](http://www.amazon.com/registry/wishlist/XTXFXK39IA8C/?reveal=unpurchased&sort=priority&layout=compact) or you could donate to a great non-profits such as: [Compassion
If you want to show appreciation for noVNC you could donate to a great
non-profits such as: [Compassion
International](http://www.compassion.com/), [SIL](http://www.sil.org),
[Habitat for Humanity](http://www.habitat.org), [Electronic Frontier
Foundation](https://www.eff.org/), [Against Malaria
Foundation](http://www.againstmalaria.com/), [Nothing But
Nets](http://www.nothingbutnets.net/), etc.
Nets](http://www.nothingbutnets.net/), etc. Please tweet <a
href="http://www.twitter.com/noVNC">@noVNC</a> if you do.
### Features
@@ -50,7 +53,7 @@ Nets](http://www.nothingbutnets.net/), etc.
* Clipboard copy/paste
* Clipping or scolling modes for large remote screens
* Easy site integration and theming (3 example themes included)
* Licensed under the [LGPLv3](http://www.gnu.org/licenses/lgpl.html)
* Licensed under the [MPL 2.0](http://www.mozilla.org/MPL/2.0/)
### Screenshots
@@ -75,17 +78,18 @@ See more screenshots <a href="http://kanaka.github.com/noVNC/screenshots.html">h
* Fast Javascript Engine: this is not strictly a requirement, but
without a fast Javascript engine, noVNC might be painfully slow.
* I maintain a more detailed browser compatibility list <a
href="https://github.com/kanaka/noVNC/wiki/Browser-support">here</a>.
* See the more detailed [browser compatibility wiki page](https://github.com/kanaka/noVNC/wiki/Browser-support).
### Server Requirements
Unless you are using a VNC server with support for WebSockets
connections (such as [x11vnc/libvncserver](http://libvncserver.sourceforge.net/) or
[PocketVNC](http://www.pocketvnc.com/blog/?page_id=866)),
you need to use a WebSockets to TCP socket proxy. There is
a python proxy included ('websockify').
connections (such as
[x11vnc/libvncserver](http://libvncserver.sourceforge.net/),
[QEMU](http://www.qemu.org/), or
[PocketVNC](http://www.pocketvnc.com/blog/?page_id=866)), you need to
use a WebSockets to TCP socket proxy. There is a python proxy included
('websockify').
### Quick Start
@@ -114,7 +118,13 @@ a python proxy included ('websockify').
### Authors/Contributors
* noVNC : Joel Martin (github.com/kanaka)
* Core team:
* [Joel Martin](https://github.com/kanaka)
* [Samuel Mannehed](https://github.com/samhed) (Cendio)
* [Peter Åstrand](https://github.com/astrand) (Cendio)
* [Solly Ross](https://github.com/DirectXMan12) (Red Hat / OpenStack)
* Notable contributions:
* UI and Icons : Chris Gordon
* Original Logo : Michael Sersen
* tight encoding : Michael Tinglof (Mercuri.ca)
@@ -126,5 +136,3 @@ a python proxy included ('websockify').
* jsunzip : Erik Moller (github.com/operasoftware/jsunzip),
* tinflate : Joergen Ibsen (ibsensoftware.com)
* DES : Dave Zimmerman (Widget Workshop), Jef Poskanzer (ACME Labs)
+20 -19
View File
@@ -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/
+2 -2
View File
@@ -6,7 +6,6 @@ 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
@@ -14,6 +13,8 @@ images/favicon.ico /usr/share/novnc
include/base64.js /usr/share/novnc/include
include/des.js /usr/share/novnc/include
include/display.js /usr/share/novnc/include
include/keysymdef.js /usr/share/novnc/include
include/keyboard.js /usr/share/novnc/include
include/input.js /usr/share/novnc/include
include/logo.js /usr/share/novnc/include
include/base.css /usr/share/novnc/include
@@ -23,7 +24,6 @@ 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
+202
View 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.
+91 -91
View File
@@ -1,91 +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.
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.
+1 -1
View File
@@ -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:
+2 -1
View File
@@ -2,7 +2,8 @@
- Create version tag and tarball from tag
WVER=0.3
git tag v${WVER}
git push origin master 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
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 339 B

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 354 B

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 385 B

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 390 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 735 B

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 387 B

+156 -49
View File
@@ -1,7 +1,8 @@
/*
* noVNC base CSS
* Copyright (C) 2012 Joel Martin
* noVNC is licensed under the LGPL-3 (see LICENSE.txt)
* 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).
*/
@@ -40,9 +41,6 @@ html {
}
#noVNC_encrypt {
}
#noVNC_connectTimeout {
width: 30px;
}
#noVNC_path {
width: 100px;
}
@@ -51,6 +49,9 @@ html {
float:right;
}
#noVNC_buttons {
white-space: nowrap;
}
#noVNC_view_drag_button {
display: none;
@@ -58,38 +59,43 @@ html {
#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;
padding-left:10px;
padding-top:4px;
z-index: 1;
position: relative;
}
.noVNC-buttons-right {
float:right;
right: 0px;
padding-right:10px;
padding-top:4px;
}
#noVNC_status_bar {
margin-top: 0px;
padding: 0px;
}
#noVNC_status_bar div {
font-size: 12px;
padding-top: 4px;
width:100%;
z-index: 2;
position: absolute;
}
#noVNC_status {
height:20px;
font-size: 12px;
padding-top: 4px;
height:32px;
text-align: center;
font-weight: bold;
color: #fff;
}
#noVNC_settings_menu {
margin: 3px;
text-align: left;
@@ -104,22 +110,12 @@ html {
float:right;
}
.noVNC_status_normal {
background: #eee;
}
.noVNC_status_error {
background: #f44;
}
.noVNC_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 */
#noVNC_screen_pad {
margin: 0px;
padding: 0px;
height: 44px;
height: 36px;
}
#noVNC_screen {
text-align: center;
@@ -154,14 +150,14 @@ html {
/*Bubble contents divs*/
#noVNC_settings {
display:none;
margin-top:77px;
margin-top:73px;
right:20px;
position:fixed;
}
#noVNC_controls {
display:none;
margin-top:77px;
margin-top:73px;
right:12px;
position:fixed;
}
@@ -173,7 +169,7 @@ html {
display:none;
position:fixed;
margin-top:77px;
margin-top:73px;
right:20px;
left:20px;
padding:15px;
@@ -186,9 +182,40 @@ html {
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:77px;
margin-top:73px;
right:30px;
position:fixed;
}
@@ -207,17 +234,11 @@ html {
z-index: -1;
}
.noVNC_status_warn {
background-color:yellow;
}
/*
* Advanced Styling
*/
/* Control bar */
#noVNC-control-bar {
position:fixed;
.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+ */
@@ -225,9 +246,32 @@ html {
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:44px;
height:36px;
left:0;
top:0;
width:100%;
@@ -368,22 +412,85 @@ html {
font-size: 180px;
}
@media screen and (min-width: 481px) and (max-width: 640px) {
.noVNC_status_button {
font-size: 10px;
.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;
}
}
@media screen and (min-width: 321px) and (max-width: 480px) {
.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;
}
+98 -132
View File
@@ -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 code " + data.charCodeAt(i) + " at position " + 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 */
+22 -3
View File
@@ -1,7 +1,8 @@
/*
* noVNC base CSS
* noVNC black CSS
* Copyright (C) 2012 Joel Martin
* noVNC is licensed under the LGPL-3 (see LICENSE.txt)
* 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).
*/
@@ -9,7 +10,7 @@
background-color:#000;
}
#noVNC-control-bar {
.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+ */
@@ -18,6 +19,24 @@
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 */
}
.triangle-right {
border:2px solid #fff;
+34 -3
View File
@@ -1,11 +1,12 @@
/*
* noVNC base CSS
* noVNC blue CSS
* Copyright (C) 2012 Joel Martin
* noVNC is licensed under the LGPL-3 (see LICENSE.txt)
* 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-control-bar {
.noVNC_status_normal {
background-color:#04073d;
background-image: -webkit-gradient(
linear,
@@ -20,6 +21,36 @@
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;
+321
View 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);
+176 -173
View File
@@ -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
+700 -717
View File
File diff suppressed because it is too large Load Diff
+344 -1867
View File
File diff suppressed because it is too large Load Diff
+17 -9
View File
@@ -352,20 +352,28 @@ this.getbit = function(d)
}
/* 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 val = 0;
while (d.bitcount < 24) {
d.tag = d.tag | (d.source[d.sourceIndex++] & 0xff) << d.bitcount;
d.bitcount += 8;
}
val = d.tag & (0xffff >> (16 - num));
d.tag >>= num;
d.bitcount -= num;
return val + 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 */
+543
View File
@@ -0,0 +1,543 @@
var kbdUtil = (function() {
"use strict";
function substituteCodepoint(cp) {
// Any Unicode code points which do not have corresponding keysym entries
// can be swapped out for another code point by adding them to this table
var substitutions = {
// {S,s} with comma below -> {S,s} with cedilla
0x218 : 0x15e,
0x219 : 0x15f,
// {T,t} with comma below -> {T,t} with cedilla
0x21a : 0x162,
0x21b : 0x163
};
var sub = substitutions[cp];
return sub ? sub : cp;
}
function isMac() {
return navigator && !!(/mac/i).exec(navigator.platform);
}
function isWindows() {
return navigator && !!(/win/i).exec(navigator.platform);
}
function isLinux() {
return navigator && !!(/linux/i).exec(navigator.platform);
}
// Return true if a modifier which is not the specified char modifier (and is not shift) is down
function hasShortcutModifier(charModifier, currentModifiers) {
var mods = {};
for (var key in currentModifiers) {
if (parseInt(key) !== 0xffe1) {
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) {
var ctrl = 0xffe3;
var alt = 0xffe9;
var altGr = 0xfe03;
var shift = 0xffe1;
var meta = 0xffe7;
if (!charModifier) {
if (isMac()) {
// on Mac, Option (AKA Alt) is used as a char modifier
charModifier = [alt];
}
else if (isWindows()) {
// on Windows, Ctrl+Alt is used as a char modifier
charModifier = [alt, ctrl];
}
else if (isLinux()) {
// on Linux, AltGr is used as a char modifier
charModifier = [altGr];
}
else {
charModifier = [];
}
}
var state = {};
state[ctrl] = false;
state[alt] = false;
state[altGr] = false;
state[shift] = false;
state[meta] = 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[ctrl] && keysym !== ctrl) {
state[ctrl] = evt.ctrlKey;
result.push(syncKey(ctrl));
}
if (evt.altKey !== undefined && evt.altKey !== state[alt] && keysym !== alt) {
state[alt] = evt.altKey;
result.push(syncKey(alt));
}
if (evt.altGraphKey !== undefined && evt.altGraphKey !== state[altGr] && keysym !== altGr) {
state[altGr] = evt.altGraphKey;
result.push(syncKey(altGr));
}
if (evt.shiftKey !== undefined && evt.shiftKey !== state[shift] && keysym !== shift) {
state[shift] = evt.shiftKey;
result.push(syncKey(shift));
}
if (evt.metaKey !== undefined && evt.metaKey !== state[meta] && keysym !== meta) {
state[meta] = evt.metaKey;
result.push(syncKey(meta));
}
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 0xffb0 + (keycode - 0x60); // numpad 0-9
}
switch(keycode) {
case 0x20: return 0x20; // space
case 0x6a: return 0xffaa; // multiply
case 0x6b: return 0xffab; // add
case 0x6c: return 0xffac; // separator
case 0x6d: return 0xffad; // subtract
case 0x6e: return 0xffae; // decimal
case 0x6f: return 0xffaf; // divide
case 0xbb: return 0x2b; // +
case 0xbc: return 0x2c; // ,
case 0xbd: return 0x2d; // -
case 0xbe: return 0x2e; // .
}
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 0xffbe + keycode - 0x70; // F1-F24
}
switch (keycode) {
case 8 : return 0xFF08; // BACKSPACE
case 13 : return 0xFF0D; // ENTER
case 9 : return 0xFF09; // TAB
case 27 : return 0xFF1B; // ESCAPE
case 46 : return 0xFFFF; // DELETE
case 36 : return 0xFF50; // HOME
case 35 : return 0xFF57; // END
case 33 : return 0xFF55; // PAGE_UP
case 34 : return 0xFF56; // PAGE_DOWN
case 45 : return 0xFF63; // INSERT
case 37 : return 0xFF51; // LEFT
case 38 : return 0xFF52; // UP
case 39 : return 0xFF53; // RIGHT
case 40 : return 0xFF54; // DOWN
case 16 : return 0xFFE1; // SHIFT
case 17 : return 0xFFE3; // CONTROL
case 18 : return 0xFFE9; // Left ALT (Mac Option)
case 224 : return 0xFE07; // Meta
case 225 : return 0xFE03; // AltGr
case 91 : return 0xFFEC; // Super_L (Win Key)
case 92 : return 0xFFED; // Super_R (Win Key)
case 93 : return 0xFF67; // Menu (Win Menu), Mac Command
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 */
};
}
+376
View File
@@ -0,0 +1,376 @@
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 */
/*
* Latin 1
* (ISO/IEC 8859-1 = Unicode U+0020..U+00FF)
* Byte 3 = 0
*/
XK_space = 0x0020, /* U+0020 SPACE */
XK_exclam = 0x0021, /* U+0021 EXCLAMATION MARK */
XK_quotedbl = 0x0022, /* U+0022 QUOTATION MARK */
XK_numbersign = 0x0023, /* U+0023 NUMBER SIGN */
XK_dollar = 0x0024, /* U+0024 DOLLAR SIGN */
XK_percent = 0x0025, /* U+0025 PERCENT SIGN */
XK_ampersand = 0x0026, /* U+0026 AMPERSAND */
XK_apostrophe = 0x0027, /* U+0027 APOSTROPHE */
XK_quoteright = 0x0027, /* deprecated */
XK_parenleft = 0x0028, /* U+0028 LEFT PARENTHESIS */
XK_parenright = 0x0029, /* U+0029 RIGHT PARENTHESIS */
XK_asterisk = 0x002a, /* U+002A ASTERISK */
XK_plus = 0x002b, /* U+002B PLUS SIGN */
XK_comma = 0x002c, /* U+002C COMMA */
XK_minus = 0x002d, /* U+002D HYPHEN-MINUS */
XK_period = 0x002e, /* U+002E FULL STOP */
XK_slash = 0x002f, /* U+002F SOLIDUS */
XK_0 = 0x0030, /* U+0030 DIGIT ZERO */
XK_1 = 0x0031, /* U+0031 DIGIT ONE */
XK_2 = 0x0032, /* U+0032 DIGIT TWO */
XK_3 = 0x0033, /* U+0033 DIGIT THREE */
XK_4 = 0x0034, /* U+0034 DIGIT FOUR */
XK_5 = 0x0035, /* U+0035 DIGIT FIVE */
XK_6 = 0x0036, /* U+0036 DIGIT SIX */
XK_7 = 0x0037, /* U+0037 DIGIT SEVEN */
XK_8 = 0x0038, /* U+0038 DIGIT EIGHT */
XK_9 = 0x0039, /* U+0039 DIGIT NINE */
XK_colon = 0x003a, /* U+003A COLON */
XK_semicolon = 0x003b, /* U+003B SEMICOLON */
XK_less = 0x003c, /* U+003C LESS-THAN SIGN */
XK_equal = 0x003d, /* U+003D EQUALS SIGN */
XK_greater = 0x003e, /* U+003E GREATER-THAN SIGN */
XK_question = 0x003f, /* U+003F QUESTION MARK */
XK_at = 0x0040, /* U+0040 COMMERCIAL AT */
XK_A = 0x0041, /* U+0041 LATIN CAPITAL LETTER A */
XK_B = 0x0042, /* U+0042 LATIN CAPITAL LETTER B */
XK_C = 0x0043, /* U+0043 LATIN CAPITAL LETTER C */
XK_D = 0x0044, /* U+0044 LATIN CAPITAL LETTER D */
XK_E = 0x0045, /* U+0045 LATIN CAPITAL LETTER E */
XK_F = 0x0046, /* U+0046 LATIN CAPITAL LETTER F */
XK_G = 0x0047, /* U+0047 LATIN CAPITAL LETTER G */
XK_H = 0x0048, /* U+0048 LATIN CAPITAL LETTER H */
XK_I = 0x0049, /* U+0049 LATIN CAPITAL LETTER I */
XK_J = 0x004a, /* U+004A LATIN CAPITAL LETTER J */
XK_K = 0x004b, /* U+004B LATIN CAPITAL LETTER K */
XK_L = 0x004c, /* U+004C LATIN CAPITAL LETTER L */
XK_M = 0x004d, /* U+004D LATIN CAPITAL LETTER M */
XK_N = 0x004e, /* U+004E LATIN CAPITAL LETTER N */
XK_O = 0x004f, /* U+004F LATIN CAPITAL LETTER O */
XK_P = 0x0050, /* U+0050 LATIN CAPITAL LETTER P */
XK_Q = 0x0051, /* U+0051 LATIN CAPITAL LETTER Q */
XK_R = 0x0052, /* U+0052 LATIN CAPITAL LETTER R */
XK_S = 0x0053, /* U+0053 LATIN CAPITAL LETTER S */
XK_T = 0x0054, /* U+0054 LATIN CAPITAL LETTER T */
XK_U = 0x0055, /* U+0055 LATIN CAPITAL LETTER U */
XK_V = 0x0056, /* U+0056 LATIN CAPITAL LETTER V */
XK_W = 0x0057, /* U+0057 LATIN CAPITAL LETTER W */
XK_X = 0x0058, /* U+0058 LATIN CAPITAL LETTER X */
XK_Y = 0x0059, /* U+0059 LATIN CAPITAL LETTER Y */
XK_Z = 0x005a, /* U+005A LATIN CAPITAL LETTER Z */
XK_bracketleft = 0x005b, /* U+005B LEFT SQUARE BRACKET */
XK_backslash = 0x005c, /* U+005C REVERSE SOLIDUS */
XK_bracketright = 0x005d, /* U+005D RIGHT SQUARE BRACKET */
XK_asciicircum = 0x005e, /* U+005E CIRCUMFLEX ACCENT */
XK_underscore = 0x005f, /* U+005F LOW LINE */
XK_grave = 0x0060, /* U+0060 GRAVE ACCENT */
XK_quoteleft = 0x0060, /* deprecated */
XK_a = 0x0061, /* U+0061 LATIN SMALL LETTER A */
XK_b = 0x0062, /* U+0062 LATIN SMALL LETTER B */
XK_c = 0x0063, /* U+0063 LATIN SMALL LETTER C */
XK_d = 0x0064, /* U+0064 LATIN SMALL LETTER D */
XK_e = 0x0065, /* U+0065 LATIN SMALL LETTER E */
XK_f = 0x0066, /* U+0066 LATIN SMALL LETTER F */
XK_g = 0x0067, /* U+0067 LATIN SMALL LETTER G */
XK_h = 0x0068, /* U+0068 LATIN SMALL LETTER H */
XK_i = 0x0069, /* U+0069 LATIN SMALL LETTER I */
XK_j = 0x006a, /* U+006A LATIN SMALL LETTER J */
XK_k = 0x006b, /* U+006B LATIN SMALL LETTER K */
XK_l = 0x006c, /* U+006C LATIN SMALL LETTER L */
XK_m = 0x006d, /* U+006D LATIN SMALL LETTER M */
XK_n = 0x006e, /* U+006E LATIN SMALL LETTER N */
XK_o = 0x006f, /* U+006F LATIN SMALL LETTER O */
XK_p = 0x0070, /* U+0070 LATIN SMALL LETTER P */
XK_q = 0x0071, /* U+0071 LATIN SMALL LETTER Q */
XK_r = 0x0072, /* U+0072 LATIN SMALL LETTER R */
XK_s = 0x0073, /* U+0073 LATIN SMALL LETTER S */
XK_t = 0x0074, /* U+0074 LATIN SMALL LETTER T */
XK_u = 0x0075, /* U+0075 LATIN SMALL LETTER U */
XK_v = 0x0076, /* U+0076 LATIN SMALL LETTER V */
XK_w = 0x0077, /* U+0077 LATIN SMALL LETTER W */
XK_x = 0x0078, /* U+0078 LATIN SMALL LETTER X */
XK_y = 0x0079, /* U+0079 LATIN SMALL LETTER Y */
XK_z = 0x007a, /* U+007A LATIN SMALL LETTER Z */
XK_braceleft = 0x007b, /* U+007B LEFT CURLY BRACKET */
XK_bar = 0x007c, /* U+007C VERTICAL LINE */
XK_braceright = 0x007d, /* U+007D RIGHT CURLY BRACKET */
XK_asciitilde = 0x007e, /* U+007E TILDE */
XK_nobreakspace = 0x00a0, /* U+00A0 NO-BREAK SPACE */
XK_exclamdown = 0x00a1, /* U+00A1 INVERTED EXCLAMATION MARK */
XK_cent = 0x00a2, /* U+00A2 CENT SIGN */
XK_sterling = 0x00a3, /* U+00A3 POUND SIGN */
XK_currency = 0x00a4, /* U+00A4 CURRENCY SIGN */
XK_yen = 0x00a5, /* U+00A5 YEN SIGN */
XK_brokenbar = 0x00a6, /* U+00A6 BROKEN BAR */
XK_section = 0x00a7, /* U+00A7 SECTION SIGN */
XK_diaeresis = 0x00a8, /* U+00A8 DIAERESIS */
XK_copyright = 0x00a9, /* U+00A9 COPYRIGHT SIGN */
XK_ordfeminine = 0x00aa, /* U+00AA FEMININE ORDINAL INDICATOR */
XK_guillemotleft = 0x00ab, /* U+00AB LEFT-POINTING DOUBLE ANGLE QUOTATION MARK */
XK_notsign = 0x00ac, /* U+00AC NOT SIGN */
XK_hyphen = 0x00ad, /* U+00AD SOFT HYPHEN */
XK_registered = 0x00ae, /* U+00AE REGISTERED SIGN */
XK_macron = 0x00af, /* U+00AF MACRON */
XK_degree = 0x00b0, /* U+00B0 DEGREE SIGN */
XK_plusminus = 0x00b1, /* U+00B1 PLUS-MINUS SIGN */
XK_twosuperior = 0x00b2, /* U+00B2 SUPERSCRIPT TWO */
XK_threesuperior = 0x00b3, /* U+00B3 SUPERSCRIPT THREE */
XK_acute = 0x00b4, /* U+00B4 ACUTE ACCENT */
XK_mu = 0x00b5, /* U+00B5 MICRO SIGN */
XK_paragraph = 0x00b6, /* U+00B6 PILCROW SIGN */
XK_periodcentered = 0x00b7, /* U+00B7 MIDDLE DOT */
XK_cedilla = 0x00b8, /* U+00B8 CEDILLA */
XK_onesuperior = 0x00b9, /* U+00B9 SUPERSCRIPT ONE */
XK_masculine = 0x00ba, /* U+00BA MASCULINE ORDINAL INDICATOR */
XK_guillemotright = 0x00bb, /* U+00BB RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK */
XK_onequarter = 0x00bc, /* U+00BC VULGAR FRACTION ONE QUARTER */
XK_onehalf = 0x00bd, /* U+00BD VULGAR FRACTION ONE HALF */
XK_threequarters = 0x00be, /* U+00BE VULGAR FRACTION THREE QUARTERS */
XK_questiondown = 0x00bf, /* U+00BF INVERTED QUESTION MARK */
XK_Agrave = 0x00c0, /* U+00C0 LATIN CAPITAL LETTER A WITH GRAVE */
XK_Aacute = 0x00c1, /* U+00C1 LATIN CAPITAL LETTER A WITH ACUTE */
XK_Acircumflex = 0x00c2, /* U+00C2 LATIN CAPITAL LETTER A WITH CIRCUMFLEX */
XK_Atilde = 0x00c3, /* U+00C3 LATIN CAPITAL LETTER A WITH TILDE */
XK_Adiaeresis = 0x00c4, /* U+00C4 LATIN CAPITAL LETTER A WITH DIAERESIS */
XK_Aring = 0x00c5, /* U+00C5 LATIN CAPITAL LETTER A WITH RING ABOVE */
XK_AE = 0x00c6, /* U+00C6 LATIN CAPITAL LETTER AE */
XK_Ccedilla = 0x00c7, /* U+00C7 LATIN CAPITAL LETTER C WITH CEDILLA */
XK_Egrave = 0x00c8, /* U+00C8 LATIN CAPITAL LETTER E WITH GRAVE */
XK_Eacute = 0x00c9, /* U+00C9 LATIN CAPITAL LETTER E WITH ACUTE */
XK_Ecircumflex = 0x00ca, /* U+00CA LATIN CAPITAL LETTER E WITH CIRCUMFLEX */
XK_Ediaeresis = 0x00cb, /* U+00CB LATIN CAPITAL LETTER E WITH DIAERESIS */
XK_Igrave = 0x00cc, /* U+00CC LATIN CAPITAL LETTER I WITH GRAVE */
XK_Iacute = 0x00cd, /* U+00CD LATIN CAPITAL LETTER I WITH ACUTE */
XK_Icircumflex = 0x00ce, /* U+00CE LATIN CAPITAL LETTER I WITH CIRCUMFLEX */
XK_Idiaeresis = 0x00cf, /* U+00CF LATIN CAPITAL LETTER I WITH DIAERESIS */
XK_ETH = 0x00d0, /* U+00D0 LATIN CAPITAL LETTER ETH */
XK_Eth = 0x00d0, /* deprecated */
XK_Ntilde = 0x00d1, /* U+00D1 LATIN CAPITAL LETTER N WITH TILDE */
XK_Ograve = 0x00d2, /* U+00D2 LATIN CAPITAL LETTER O WITH GRAVE */
XK_Oacute = 0x00d3, /* U+00D3 LATIN CAPITAL LETTER O WITH ACUTE */
XK_Ocircumflex = 0x00d4, /* U+00D4 LATIN CAPITAL LETTER O WITH CIRCUMFLEX */
XK_Otilde = 0x00d5, /* U+00D5 LATIN CAPITAL LETTER O WITH TILDE */
XK_Odiaeresis = 0x00d6, /* U+00D6 LATIN CAPITAL LETTER O WITH DIAERESIS */
XK_multiply = 0x00d7, /* U+00D7 MULTIPLICATION SIGN */
XK_Oslash = 0x00d8, /* U+00D8 LATIN CAPITAL LETTER O WITH STROKE */
XK_Ooblique = 0x00d8, /* U+00D8 LATIN CAPITAL LETTER O WITH STROKE */
XK_Ugrave = 0x00d9, /* U+00D9 LATIN CAPITAL LETTER U WITH GRAVE */
XK_Uacute = 0x00da, /* U+00DA LATIN CAPITAL LETTER U WITH ACUTE */
XK_Ucircumflex = 0x00db, /* U+00DB LATIN CAPITAL LETTER U WITH CIRCUMFLEX */
XK_Udiaeresis = 0x00dc, /* U+00DC LATIN CAPITAL LETTER U WITH DIAERESIS */
XK_Yacute = 0x00dd, /* U+00DD LATIN CAPITAL LETTER Y WITH ACUTE */
XK_THORN = 0x00de, /* U+00DE LATIN CAPITAL LETTER THORN */
XK_Thorn = 0x00de, /* deprecated */
XK_ssharp = 0x00df, /* U+00DF LATIN SMALL LETTER SHARP S */
XK_agrave = 0x00e0, /* U+00E0 LATIN SMALL LETTER A WITH GRAVE */
XK_aacute = 0x00e1, /* U+00E1 LATIN SMALL LETTER A WITH ACUTE */
XK_acircumflex = 0x00e2, /* U+00E2 LATIN SMALL LETTER A WITH CIRCUMFLEX */
XK_atilde = 0x00e3, /* U+00E3 LATIN SMALL LETTER A WITH TILDE */
XK_adiaeresis = 0x00e4, /* U+00E4 LATIN SMALL LETTER A WITH DIAERESIS */
XK_aring = 0x00e5, /* U+00E5 LATIN SMALL LETTER A WITH RING ABOVE */
XK_ae = 0x00e6, /* U+00E6 LATIN SMALL LETTER AE */
XK_ccedilla = 0x00e7, /* U+00E7 LATIN SMALL LETTER C WITH CEDILLA */
XK_egrave = 0x00e8, /* U+00E8 LATIN SMALL LETTER E WITH GRAVE */
XK_eacute = 0x00e9, /* U+00E9 LATIN SMALL LETTER E WITH ACUTE */
XK_ecircumflex = 0x00ea, /* U+00EA LATIN SMALL LETTER E WITH CIRCUMFLEX */
XK_ediaeresis = 0x00eb, /* U+00EB LATIN SMALL LETTER E WITH DIAERESIS */
XK_igrave = 0x00ec, /* U+00EC LATIN SMALL LETTER I WITH GRAVE */
XK_iacute = 0x00ed, /* U+00ED LATIN SMALL LETTER I WITH ACUTE */
XK_icircumflex = 0x00ee, /* U+00EE LATIN SMALL LETTER I WITH CIRCUMFLEX */
XK_idiaeresis = 0x00ef, /* U+00EF LATIN SMALL LETTER I WITH DIAERESIS */
XK_eth = 0x00f0, /* U+00F0 LATIN SMALL LETTER ETH */
XK_ntilde = 0x00f1, /* U+00F1 LATIN SMALL LETTER N WITH TILDE */
XK_ograve = 0x00f2, /* U+00F2 LATIN SMALL LETTER O WITH GRAVE */
XK_oacute = 0x00f3, /* U+00F3 LATIN SMALL LETTER O WITH ACUTE */
XK_ocircumflex = 0x00f4, /* U+00F4 LATIN SMALL LETTER O WITH CIRCUMFLEX */
XK_otilde = 0x00f5, /* U+00F5 LATIN SMALL LETTER O WITH TILDE */
XK_odiaeresis = 0x00f6, /* U+00F6 LATIN SMALL LETTER O WITH DIAERESIS */
XK_division = 0x00f7, /* U+00F7 DIVISION SIGN */
XK_oslash = 0x00f8, /* U+00F8 LATIN SMALL LETTER O WITH STROKE */
XK_ooblique = 0x00f8, /* U+00F8 LATIN SMALL LETTER O WITH STROKE */
XK_ugrave = 0x00f9, /* U+00F9 LATIN SMALL LETTER U WITH GRAVE */
XK_uacute = 0x00fa, /* U+00FA LATIN SMALL LETTER U WITH ACUTE */
XK_ucircumflex = 0x00fb, /* U+00FB LATIN SMALL LETTER U WITH CIRCUMFLEX */
XK_udiaeresis = 0x00fc, /* U+00FC LATIN SMALL LETTER U WITH DIAERESIS */
XK_yacute = 0x00fd, /* U+00FD LATIN SMALL LETTER Y WITH ACUTE */
XK_thorn = 0x00fe, /* U+00FE LATIN SMALL LETTER THORN */
XK_ydiaeresis = 0x00ff; /* U+00FF LATIN SMALL LETTER Y WITH DIAERESIS */
File diff suppressed because one or more lines are too long
+15 -3
View File
@@ -1,7 +1,7 @@
/*
* noVNC: HTML5 VNC client
* Copyright (C) 2012 Joel Martin
* Licensed under LGPL-3 (see LICENSE.LGPL-3)
* 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();
+1804 -1770
View File
File diff suppressed because it is too large Load Diff
+902 -582
View File
File diff suppressed because it is too large Load Diff
+496 -160
View File
@@ -1,14 +1,13 @@
/*
* noVNC: HTML5 VNC client
* Copyright (C) 2012 Joel Martin
* Licensed under LGPL-3 (see LICENSE.txt)
* 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,61 +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
if (!Array.prototype.map)
{
Array.prototype.map = function(fun /*, thisp*/)
{
addFunc(Array, 'map', function (fun /*, thisp*/) {
"use strict";
var len = this.length;
if (typeof fun != "function")
throw new TypeError();
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);
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(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(callback){
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
* ------------------------------------------------------
@@ -84,6 +185,7 @@ window.requestAnimFrame = (function(){
Util._log_level = 'warn';
Util.init_logging = function (level) {
"use strict";
if (typeof level === 'undefined') {
level = Util._log_level;
} else {
@@ -94,26 +196,34 @@ Util.init_logging = function (level) {
window.console = {
'log' : window.opera.postError,
'warn' : window.opera.postError,
'error': window.opera.postError };
'error': window.opera.postError
};
} else {
window.console = {
'log' : function(m) {},
'warn' : function(m) {},
'error': function(m) {}};
'log' : function (m) {},
'warn' : function (m) {},
'error': function (m) {}
};
}
}
Util.Debug = Util.Info = Util.Warn = Util.Error = function (msg) {};
/* jshint -W086 */
switch (level) {
case 'debug': Util.Debug = function (msg) { console.log(msg); };
case 'info': Util.Info = function (msg) { console.log(msg); };
case 'warn': Util.Warn = function (msg) { console.warn(msg); };
case 'error': Util.Error = function (msg) { console.error(msg); };
case 'debug':
Util.Debug = function (msg) { console.log(msg); };
case 'info':
Util.Info = function (msg) { console.log(msg); };
case 'warn':
Util.Warn = function (msg) { console.warn(msg); };
case 'error':
Util.Error = function (msg) { console.error(msg); };
case 'none':
break;
default:
throw("invalid logging type '" + level + "'");
throw new Error("invalid logging type '" + level + "'");
}
/* jshint +W086 */
};
Util.get_logging = function () {
return Util._log_level;
@@ -121,107 +231,279 @@ 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 === 'str') {
val = String(val);
} 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);
@@ -239,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; }
@@ -280,41 +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 = {
// Version detection break in Opera 11.60 (errors on arguments.callee.caller reference)
//'presto': (function() {
// return (!window.opera) ? false : ((arguments.callee.caller) ? 960 : ((document.getElementsByClassName) ? 950 : 925)); }()),
'presto': (function() { return (!window.opera) ? false : true; }()),
(function () {
"use strict";
// 'presto': (function () { return (!window.opera) ? false : true; }()),
var detectPresto = function () {
return !!window.opera;
};
'trident': (function() {
return (!window.ActiveXObject) ? false : ((window.XMLHttpRequest) ? ((document.querySelectorAll) ? 6 : 5) : 4); }()),
'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);
}
// '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;
}
}
};
Util.Flash = (function(){
// '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};
}());
}());
-43
View File
@@ -1,43 +0,0 @@
/*
* noVNC: HTML5 VNC client
* Copyright (C) 2012 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 + "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;
extra += start + "jsunzip.js" + end;
document.write(extra);
}());
Binary file not shown.
+82 -32
View File
@@ -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 && !window.WEB_SOCKET_FORCE_FLASH) 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();
});
}
})();
+320 -296
View File
@@ -1,7 +1,7 @@
/*
* Websock: high-performance binary WebSockets
* Copyright (C) 2012 Joel Martin
* Licensed under LGPL-3 (see LICENSE.txt)
* 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,7 +14,7 @@
* read binary data off of the receive queue.
*/
/*jslint browser: true, bitwise: false, plusplus: false */
/*jslint browser: true, bitwise: true */
/*global Util, Base64 */
@@ -35,326 +35,350 @@ if (window.WebSocket && !window.WEB_SOCKET_FORCE_FLASH) {
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 = "";
window.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");
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) {
if (typeof(len) === 'undefined') { len = rQlen(); }
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) {
if (typeof(len) === 'undefined') { len = rQlen(); }
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);
} else {
Util.Error("Server select no 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();
}
})();
+135 -44
View File
@@ -1,13 +1,13 @@
/*
* noVNC: HTML5 VNC client
* Copyright (C) 2012 Joel Martin
* Licensed under LGPL-3 (see LICENSE.txt)
* Copyright (C) 2013 NTT corp.
* Licensed under MPL 2.0 (see LICENSE.txt)
*
* See README.md for usage and integration instructions.
*/
"use strict";
/*jslint bitwise: false, white: false */
/*jslint bitwise: false, white: false, browser: true, devel: true */
/*global Util, window, document */
// Globals defined here
@@ -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];
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.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
View File
@@ -0,0 +1,194 @@
// Karma configuration
module.exports = function(config) {
/*var customLaunchers = {
sl_chrome_win7: {
base: 'SauceLabs',
browserName: 'chrome',
platform: 'Windows 7'
},
sl_firefox30_linux: {
base: 'SauceLabs',
browserName: 'firefox',
version: '30',
platform: 'Linux'
},
sl_firefox26_linux: {
base: 'SauceLabs',
browserName: 'firefox',
version: 26,
platform: 'Linux'
},
sl_windows7_ie10: {
base: 'SauceLabs',
browserName: 'internet explorer',
platform: 'Windows 7',
version: '10'
},
sl_windows81_ie11: {
base: 'SauceLabs',
browserName: 'internet explorer',
platform: 'Windows 8.1',
version: '11'
},
sl_osxmavericks_safari7: {
base: 'SauceLabs',
browserName: 'safari',
platform: 'OS X 10.9',
version: '7'
},
sl_osxmtnlion_safari6: {
base: 'SauceLabs',
browserName: 'safari',
platform: 'OS X 10.8',
version: '6'
}
};*/
var customLaunchers = {};
var browsers = [];
var useSauce = false;
if (process.env.SAUCE_USERNAME && process.env.SAUCE_ACCESS_KEY) {
useSauce = true;
}
if (useSauce && process.env.TEST_BROWSER_NAME && process.env.TEST_BROWSER_NAME != 'PhantomJS') {
var names = process.env.TEST_BROWSER_NAME.split(',');
var platforms = process.env.TEST_BROWSER_OS.split(',');
var versions = [];
if (process.env.TEST_BROWSER_VERSION) {
versions = process.env.TEST_BROWSER_VERSION.split(',');
} else {
versions = [null];
}
for (var i = 0; i < names.length; i++) {
for (var j = 0; j < platforms.length; j++) {
for (var k = 0; k < versions.length; k++) {
var launcher_name = 'sl_' + platforms[j].replace(/[^a-zA-Z0-9]/g, '') + '_' + names[i];
if (versions[k]) {
launcher_name += '_' + versions[k];
}
customLaunchers[launcher_name] = {
base: 'SauceLabs',
browserName: names[i],
platform: platforms[j],
};
if (versions[i]) {
customLaunchers[launcher_name].version = versions[k];
}
}
}
}
browsers = Object.keys(customLaunchers);
} else {
useSauce = false;
browsers = ['PhantomJS'];
}
var my_conf = {
// base path that will be used to resolve all patterns (eg. files, exclude)
basePath: '',
// frameworks to use
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
frameworks: ['mocha', 'sinon', 'chai', 'sinon-chai'],
// list of files / patterns to load in the browser (loaded in order)
files: [
'tests/fake.*.js',
'tests/assertions.js',
'include/util.js', // load first to avoid issues, since methods are called immediately
//'../include/*.js',
'include/base64.js',
'include/keysym.js',
'include/keysymdef.js',
'include/keyboard.js',
'include/input.js',
'include/websock.js',
'include/rfb.js',
'include/jsunzip.js',
'include/des.js',
'include/display.js',
'tests/test.*.js'
],
client: {
mocha: {
'ui': 'bdd'
}
},
// list of files to exclude
exclude: [
'../include/playback.js',
'../include/ui.js'
],
customLaunchers: customLaunchers,
// start these browsers
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
browsers: browsers,
// preprocess matching files before serving them to the browser
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
preprocessors: {
},
// test results reporter to use
// possible values: 'dots', 'progress'
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
reporters: ['mocha', 'saucelabs'],
// web server port
port: 9876,
// enable / disable colors in the output (reporters and logs)
colors: true,
// level of logging
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
logLevel: config.LOG_INFO,
// enable / disable watching file and executing tests whenever any file changes
autoWatch: false,
// Continuous Integration mode
// if true, Karma captures browsers, runs the tests and exits
singleRun: true,
// Increase timeout in case connection is slow/we run more browsers than possible
// (we currently get 3 for free, and we try to run 7, so it can take a while)
captureTimeout: 240000
};
if (useSauce) {
my_conf.captureTimeout = 0; // use SL timeout
my_conf.sauceLabs = {
testName: 'noVNC Tests (all)',
startConnect: true,
tunnelIdentifier: process.env.TRAVIS_JOB_NUMBER
};
}
config.set(my_conf);
};
+50
View File
@@ -0,0 +1,50 @@
{
"name": "noVNC",
"version": "0.5.0",
"description": "An HTML5 VNC client",
"main": "karma.conf.js",
"directories": {
"doc": "docs",
"test": "tests"
},
"scripts": {
"test": "karma start karma.conf.js"
},
"repository": {
"type": "git",
"url": "https://github.com/kanaka/noVNC.git"
},
"author": "Joel Martin <github@martintribe.org> (https://github.com/kanaka)",
"contributors": [
"Solly Ross <sross@redhat.com> (https://github.com/directxman12)",
"Peter Åstrand <astrand@cendio.se> (https://github.com/astrand)",
"Samuel Mannehed <samuel@cendio.se> (https://github.com/samhed)"
],
"license": "MPL 2.0",
"bugs": {
"url": "https://github.com/kanaka/noVNC/issues"
},
"homepage": "https://github.com/kanaka/noVNC",
"devDependencies": {
"ansi": "^0.3.0",
"casperjs": "^1.1.0-beta3",
"chai": "^1.9.1",
"commander": "^2.2.0",
"karma": "^0.12.16",
"karma-chai": "^0.1.0",
"karma-mocha": "^0.1.4",
"karma-mocha-reporter": "^0.2.5",
"karma-phantomjs-launcher": "^0.1.4",
"karma-sauce-launcher": "^0.2.8",
"karma-sinon": "^1.0.3",
"karma-sinon-chai": "^0.1.6",
"mocha": "^1.20.1",
"open": "0.0.5",
"phantom": "^0.6.3",
"phantomjs": "^1.9.7-9",
"sinon": "^1.10.2",
"sinon-chai": "^2.5.0",
"spooky": "^0.2.4",
"temp": "^0.8.0"
}
}
+2 -2
View File
@@ -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
View File
@@ -0,0 +1,24 @@
// some useful assertions for noVNC
chai.use(function (_chai, utils) {
_chai.Assertion.addMethod('displayed', function (target_data) {
var obj = this._obj;
var data_cl = obj._drawCtx.getImageData(0, 0, obj._viewportLoc.w, obj._viewportLoc.h).data;
// NB(directxman12): PhantomJS 1.x doesn't implement Uint8ClampedArray, so work around that
var data = new Uint8Array(data_cl);
this.assert(utils.eql(data, target_data),
"expected #{this} to have displayed the image #{exp}, but instead it displayed #{act}",
"expected #{this} not to have displayed the image #{act}",
target_data,
data);
});
_chai.Assertion.addMethod('sent', function (target_data) {
var obj = this._obj;
var data = obj._websocket._get_sent_data();
this.assert(utils.eql(data, target_data),
"expected #{this} to have sent the data #{exp}, but it actually sent #{act}",
"expected #{this} not to have sent the data #{act}",
target_data,
data);
});
});
+96
View File
@@ -0,0 +1,96 @@
var FakeWebSocket;
(function () {
// PhantomJS can't create Event objects directly, so we need to use this
function make_event(name, props) {
var evt = document.createEvent('Event');
evt.initEvent(name, true, true);
if (props) {
for (var prop in props) {
evt[prop] = props[prop];
}
}
return evt;
}
FakeWebSocket = function (uri, protocols) {
this.url = uri;
this.binaryType = "arraybuffer";
this.extensions = "";
if (!protocols || typeof protocols === 'string') {
this.protocol = protocols;
} else {
this.protocol = protocols[0];
}
this._send_queue = new Uint8Array(20000);
this.readyState = FakeWebSocket.CONNECTING;
this.bufferedAmount = 0;
this.__is_fake = true;
};
FakeWebSocket.prototype = {
close: function (code, reason) {
this.readyState = FakeWebSocket.CLOSED;
if (this.onclose) {
this.onclose(make_event("close", { 'code': code, 'reason': reason, 'wasClean': true }));
}
},
send: function (data) {
if (this.protocol == 'base64') {
data = Base64.decode(data);
} else {
data = new Uint8Array(data);
}
this._send_queue.set(data, this.bufferedAmount);
this.bufferedAmount += data.length;
},
_get_sent_data: function () {
var arr = [];
for (var i = 0; i < this.bufferedAmount; i++) {
arr[i] = this._send_queue[i];
}
this.bufferedAmount = 0;
return arr;
},
_open: function (data) {
this.readyState = FakeWebSocket.OPEN;
if (this.onopen) {
this.onopen(make_event('open'));
}
},
_receive_data: function (data) {
this.onmessage(make_event("message", { 'data': data }));
}
};
FakeWebSocket.OPEN = WebSocket.OPEN;
FakeWebSocket.CONNECTING = WebSocket.CONNECTING;
FakeWebSocket.CLOSING = WebSocket.CLOSING;
FakeWebSocket.CLOSED = WebSocket.CLOSED;
FakeWebSocket.__is_fake = true;
FakeWebSocket.replace = function () {
if (!WebSocket.__is_fake) {
var real_version = WebSocket;
WebSocket = FakeWebSocket;
FakeWebSocket.__real_version = real_version;
}
};
FakeWebSocket.restore = function () {
if (WebSocket.__is_fake) {
WebSocket = WebSocket.__real_version;
}
};
})();
+16 -3
View File
@@ -26,6 +26,8 @@
<script src="../include/util.js"></script>
<script src="../include/webutil.js"></script>
<script src="../include/base64.js"></script>
<script src="../include/keysymdef.js"></script>
<script src="../include/keyboard.js"></script>
<script src="../include/input.js"></script>
<script src="../include/display.js"></script>
<script>
@@ -57,9 +59,17 @@
//console.log(msg);
}
function keyPress(keysym, down, e) {
function rfbKeyPress(keysym, down) {
var d = down ? "down" : " up ";
msg = "keyPress " + d + " keysym: " + keysym +
var key = keysyms.lookup(keysym);
var msg = "RFB keypress " + d + " keysym: " + keysym;
if (key && key.keyname) {
msg += " key name: " + key.keyname;
}
message(msg);
}
function rawKey(e) {
msg = "raw key event " + e.type +
" (key: " + e.keyCode + ", char: " + e.charCode +
", which: " + e.which +")";
message(msg);
@@ -94,7 +104,10 @@
window.onload = function() {
canvas = new Display({'target' : $D('canvas')});
keyboard = new Keyboard({'target': document,
'onKeyPress': keyPress});
'onKeyPress': rfbKeyPress});
Util.addEvent(document, 'keypress', rawKey);
Util.addEvent(document, 'keydown', rawKey);
Util.addEvent(document, 'keyup', rawKey);
mouse = new Mouse({'target': $D('canvas'),
'onMouseButton': mouseButton,
'onMouseMove': mouseMove});
+29
View File
@@ -0,0 +1,29 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Mocha Tests</title>
<link rel="stylesheet" href="node_modules/mocha/mocha.css" />
</head>
<body>
<!--
To run tests
cd .../noVNC/tests
npm install chai mocha
open keyboard-tests.html in a browser
-->
<div id="mocha"></div>
<script src="node_modules/chai/chai.js"></script>
<script src="node_modules/mocha/mocha.js"></script>
<script>mocha.setup('bdd')</script>
<script src="../include/keysymdef.js"></script>
<script src="../include/keyboard.js"></script>
<script src="test.keyboard.js"></script>
<script src="test.helper.js"></script>
<script>
mocha.checkLeaks();
mocha.globals(['navigator']);
mocha.run();
</script>
</body>
</html>
+102
View File
@@ -0,0 +1,102 @@
var Spooky = require('spooky');
var path = require('path');
var phantom_path = require('phantomjs').path;
var casper_path = path.resolve(__dirname, '../node_modules/casperjs/bin/casperjs');
process.env.PHANTOMJS_EXECUTABLE = phantom_path;
var casper_opts = {
child: {
transport: 'http',
command: casper_path
},
casper: {
logLevel: 'debug',
verbose: true
}
};
var provide_emitter = function(file_paths) {
var spooky = new Spooky(casper_opts, function(err) {
if (err) {
if (err.stack) console.warn(err.stack);
else console.warn(err);
return;
}
spooky.start('about:blank');
file_paths.forEach(function(file_path, path_ind) {
spooky.thenOpen('file://'+file_path);
spooky.waitFor(function() {
return this.getGlobal('__mocha_done') === true;
},
[{ path_ind: path_ind }, function() {
var res_json = {
file_ind: path_ind
};
res_json.num_tests = this.evaluate(function() { return document.querySelectorAll('li.test').length; });
res_json.num_passes = this.evaluate(function() { return document.querySelectorAll('li.test.pass').length; });
res_json.num_fails = this.evaluate(function() { return document.querySelectorAll('li.test.fail').length; });
res_json.num_slow = this.evaluate(function() { return document.querySelectorAll('li.test.pass:not(.fast):not(.pending)').length; });
res_json.num_skipped = this.evaluate(function () { return document.querySelectorAll('li.test.pending').length; });
res_json.duration = this.evaluate(function() { return document.querySelector('li.duration em').textContent; });
res_json.suites = this.evaluate(function() {
var traverse_node = function(elem) {
var res;
if (elem.classList.contains('suite')) {
res = {
type: 'suite',
name: elem.querySelector('h1').textContent,
has_subfailures: elem.querySelectorAll('li.test.fail').length > 0,
};
var child_elems = elem.querySelector('ul').children;
res.children = Array.prototype.map.call(child_elems, traverse_node);
return res;
}
else {
var h2_content = elem.querySelector('h2').childNodes;
res = {
type: 'test',
text: h2_content[0].textContent,
};
if (elem.classList.contains('pass')) {
res.pass = true;
if (elem.classList.contains('pending')) {
res.slow = false;
res.skipped = true;
}
else {
res.slow = !elem.classList.contains('fast');
res.skipped = false;
res.duration = h2_content[1].textContent;
}
}
else {
res.error = elem.querySelector('pre.error').textContent;
}
return res;
}
};
var top_suites = document.querySelectorAll('#mocha-report > li.suite');
return Array.prototype.map.call(top_suites, traverse_node);
});
res_json.replay = this.evaluate(function() { return document.querySelector('a.replay').textContent; });
this.emit('test_ready', res_json);
}]);
});
spooky.run();
});
return spooky;
};
module.exports = {
provide_emitter: provide_emitter,
name: 'SpookyJS (CapserJS on PhantomJS)'
};
+342
View File
@@ -0,0 +1,342 @@
#!/usr/bin/env node
var ansi = require('ansi');
var program = require('commander');
var path = require('path');
var fs = require('fs');
var make_list = function(val) {
return val.split(',');
};
program
.option('-t, --tests <testlist>', 'Run the specified html-file-based test(s). \'testlist\' should be a comma-separated list', make_list, [])
.option('-a, --print-all', 'Print all tests, not just the failures')
.option('--disable-color', 'Explicitly disable color')
.option('-c, --color', 'Explicitly enable color (default is to use color when not outputting to a pipe)')
.option('-i, --auto-inject <includefiles>', 'Treat the test list as a set of mocha JS files, and automatically generate HTML files with which to test test. \'includefiles\' should be a comma-separated list of paths to javascript files to include in each of the generated HTML files', make_list, null)
.option('-p, --provider <name>', 'Use the given provider (defaults to "casper"). Currently, may be "casper" or "zombie"', 'casper')
.option('-g, --generate-html', 'Instead of running the tests, just return the path to the generated HTML file, then wait for user interaction to exit (should be used with .js tests).')
.option('-o, --open-in-browser', 'Open the generated HTML files in a web browser using the "open" module (must be used with the "-g"/"--generate-html" option).')
.option('--output-html', 'Instead of running the tests, just output the generated HTML source to STDOUT (should be used with .js tests)')
.option('-d, --debug', 'Show debug output (the "console" event) from the provider')
.option('-r, --relative', 'Use relative paths in the generated HTML file')
.parse(process.argv);
if (program.tests.length === 0) {
program.tests = fs.readdirSync(__dirname).filter(function(f) { return (/^test\.(\w|\.|-)+\.js$/).test(f); });
program.tests = program.tests.map(function (f) { return path.resolve(__dirname, f); }); // add full paths in
console.log('using files %s', program.tests);
}
var file_paths = [];
var all_js = program.tests.reduce(function(a,e) { return a && e.slice(-3) == '.js'; }, true);
var get_path = function (/* arguments */) {
if (program.relative) {
return path.join.apply(null, arguments);
} else {
var args = Array.prototype.slice.call(arguments);
args.unshift(__dirname, '..');
return path.resolve.apply(null, args);
}
};
var get_path_cwd = function (/* arguments */) {
if (program.relative) {
var part_path = path.join.apply(null, arguments);
return path.relative(path.join(__dirname, '..'), path.resolve(process.cwd(), part_path));
} else {
var args = Array.prototype.slice.call(arguments);
args.unshift(process.cwd());
return path.resolve.apply(null, args);
}
};
if (all_js && !program.autoInject) {
var all_modules = {};
// uses the first instance of the string 'requires local modules: '
program.tests.forEach(function (testname) {
var full_path = path.resolve(process.cwd(), testname);
var content = fs.readFileSync(full_path).toString();
var ind = content.indexOf('requires local modules: ');
if (ind > -1) {
ind += 'requires local modules: '.length;
var eol = content.indexOf('\n', ind);
var modules = content.slice(ind, eol).split(/,\s*/);
modules.forEach(function (mod) {
all_modules[get_path('include/', mod) + '.js'] = 1;
});
}
var fakes_ind = content.indexOf('requires test modules: ');
if (fakes_ind > -1) {
fakes_ind += 'requires test modules: '.length;
var fakes_eol = content.indexOf('\n', fakes_ind);
var fakes_modules = content.slice(fakes_ind, fakes_eol).split(/,\s*/);
fakes_modules.forEach(function (mod) {
all_modules[get_path('tests/', mod) + '.js'] = 1;
});
}
});
program.autoInject = Object.keys(all_modules);
}
if (program.autoInject) {
var temp = require('temp');
temp.track();
var template = {
header: "<html>\n<head>\n<meta charset='utf-8' />\n<link rel='stylesheet' href='" + get_path('node_modules/mocha/mocha.css') + "'/>\n</head>\n<body><div id='mocha'></div>",
script_tag: function(p) { return "<script src='" + p + "'></script>"; },
footer: "<script>\nmocha.checkLeaks();\nmocha.globals(['navigator', 'create', 'ClientUtils', '__utils__']);\nmocha.run(function () { window.__mocha_done = true; });\n</script>\n</body>\n</html>"
};
template.header += "\n" + template.script_tag(get_path('node_modules/chai/chai.js'));
template.header += "\n" + template.script_tag(get_path('node_modules/mocha/mocha.js'));
template.header += "\n" + template.script_tag(get_path('node_modules/sinon/pkg/sinon.js'));
template.header += "\n" + template.script_tag(get_path('node_modules/sinon-chai/lib/sinon-chai.js'));
template.header += "\n" + template.script_tag(get_path('node_modules/sinon-chai/lib/sinon-chai.js'));
template.header += "\n<script>mocha.setup('bdd');</script>";
template.header = program.autoInject.reduce(function(acc, sn) {
return acc + "\n" + template.script_tag(get_path_cwd(sn));
}, template.header);
file_paths = program.tests.map(function(jsn, ind) {
var templ = template.header;
templ += "\n";
templ += template.script_tag(get_path_cwd(jsn));
templ += template.footer;
var tempfile = temp.openSync({ prefix: 'novnc-zombie-inject-', suffix: '-file_num-'+ind+'.html' });
fs.writeSync(tempfile.fd, templ);
fs.closeSync(tempfile.fd);
return tempfile.path;
});
}
else {
file_paths = program.tests.map(function(fn) {
return path.resolve(process.cwd(), fn);
});
}
var use_ansi = false;
if (program.color) use_ansi = true;
else if (program.disableColor) use_ansi = false;
else if (process.stdout.isTTY) use_ansi = true;
var cursor = ansi(process.stdout, { enabled: use_ansi });
if (program.outputHtml) {
file_paths.forEach(function(path, path_ind) {
fs.readFile(path, function(err, data) {
if (err) {
console.warn(error.stack);
return;
}
if (use_ansi) {
cursor
.bold()
.write(program.tests[path_ind])
.reset()
.write("\n")
.write(Array(program.tests[path_ind].length+1).join('='))
.write("\n\n");
}
cursor
.write(data)
.write("\n\n");
});
});
}
if (program.generateHtml) {
var open_browser;
if (program.openInBrowser) {
open_browser = require('open');
}
file_paths.forEach(function(path, path_ind) {
cursor
.bold()
.write(program.tests[path_ind])
.write(": ")
.reset()
.write(path)
.write("\n");
if (program.openInBrowser) {
open_browser(path);
}
});
console.log('');
}
if (program.generateHtml) {
process.stdin.resume(); // pause until C-c
process.on('SIGINT', function() {
process.stdin.pause(); // exit
});
}
if (!program.outputHtml && !program.generateHtml) {
var failure_count = 0;
var prov = require(path.resolve(__dirname, 'run_from_console.'+program.provider+'.js'));
cursor
.write("Running tests ")
.bold()
.write(program.tests.join(', '))
.reset()
.grey()
.write(' using provider '+prov.name)
.reset()
.write("\n");
//console.log("Running tests %s using provider %s", program.tests.join(', '), prov.name);
var provider = prov.provide_emitter(file_paths);
provider.on('test_ready', function(test_json) {
console.log('');
filename = program.tests[test_json.file_ind];
cursor.bold();
console.log('Results for %s:', filename);
console.log(Array('Results for :'.length+filename.length+1).join('='));
cursor.reset();
console.log('');
cursor
.write(''+test_json.num_tests+' tests run, ')
.green()
.write(''+test_json.num_passes+' passed');
if (test_json.num_slow > 0) {
cursor
.reset()
.write(' (');
cursor
.yellow()
.write(''+test_json.num_slow+' slow')
.reset()
.write(')');
}
cursor
.reset()
.write(', ');
cursor
.red()
.write(''+test_json.num_fails+' failed');
if (test_json.num_skipped > 0) {
cursor
.reset()
.write(', ')
.grey()
.write(''+test_json.num_skipped+' skipped');
}
cursor
.reset()
.write(' -- duration: '+test_json.duration+"s\n");
console.log('');
if (test_json.num_fails > 0 || program.printAll) {
var traverse_tree = function(indentation, node) {
if (node.type == 'suite') {
if (!node.has_subfailures && !program.printAll) return;
if (indentation === 0) {
cursor.bold();
console.log(node.name);
console.log(Array(node.name.length+1).join('-'));
cursor.reset();
}
else {
cursor
.write(Array(indentation+3).join('#'))
.bold()
.write(' '+node.name+' ')
.reset()
.write(Array(indentation+3).join('#'))
.write("\n");
}
console.log('');
for (var i = 0; i < node.children.length; i++) {
traverse_tree(indentation+1, node.children[i]);
}
}
else {
if (!node.pass) {
cursor.magenta();
console.log('- failed: '+node.text+test_json.replay);
cursor.red();
console.log(' '+node.error.split("\n")[0]); // the split is to avoid a weird thing where in PhantomJS where we get a stack trace too
cursor.reset();
console.log('');
}
else if (program.printAll) {
if (node.skipped) {
cursor
.grey()
.write('- skipped: '+node.text);
}
else {
if (node.slow) cursor.yellow();
else cursor.green();
cursor
.write('- pass: '+node.text)
.grey()
.write(' ('+node.duration+') ');
}
/*if (node.slow) cursor.yellow();
else cursor.green();*/
cursor
//.write(test_json.replay)
.reset()
.write("\n");
console.log('');
}
}
};
for (var i = 0; i < test_json.suites.length; i++) {
traverse_tree(0, test_json.suites[i]);
}
}
if (test_json.num_fails === 0) {
cursor.fg.green();
console.log('all tests passed :-)');
cursor.reset();
}
});
if (program.debug) {
provider.on('console', function(line) {
// log to stderr
console.error(line);
});
}
provider.on('error', function(line) {
// log to stderr
console.error('ERROR: ' + line);
});
/*gprom.finally(function(ph) {
ph.exit();
// exit with a status code that actually gives information
if (program.exitWithFailureCount) process.exit(failure_count);
});*/
}
+82
View File
@@ -0,0 +1,82 @@
var Browser = require('zombie');
var path = require('path');
var EventEmitter = require('events').EventEmitter;
var Q = require('q');
var provide_emitter = function(file_paths) {
var emitter = new EventEmitter();
file_paths.reduce(function(prom, file_path, path_ind) {
return prom.then(function(browser) {
browser.visit('file://'+file_path, function() {
if (browser.error) throw new Error(browser.errors);
var res_json = {};
res_json.file_ind = path_ind;
res_json.num_tests = browser.querySelectorAll('li.test').length;
res_json.num_fails = browser.querySelectorAll('li.test.fail').length;
res_json.num_passes = browser.querySelectorAll('li.test.pass').length;
res_json.num_slow = browser.querySelectorAll('li.test.pass:not(.fast)').length;
res_json.num_skipped = browser.querySelectorAll('li.test.pending').length;
res_json.duration = browser.text('li.duration em');
var traverse_node = function(elem) {
var classList = elem.className.split(' ');
var res;
if (classList.indexOf('suite') > -1) {
res = {
type: 'suite',
name: elem.querySelector('h1').textContent,
has_subfailures: elem.querySelectorAll('li.test.fail').length > 0
};
var child_elems = elem.querySelector('ul').children;
res.children = Array.prototype.map.call(child_elems, traverse_node);
return res;
}
else {
var h2_content = elem.querySelector('h2').childNodes;
res = {
type: 'test',
text: h2_content[0].textContent
};
if (classList.indexOf('pass') > -1) {
res.pass = true;
if (classList.indexOf('pending') > -1) {
res.slow = false;
res.skipped = true;
}
else {
res.slow = classList.indexOf('fast') < 0;
res.skipped = false;
res.duration = h2_content[1].textContent;
}
}
else {
res.error = elem.querySelector('pre.error').textContent;
}
return res;
}
};
var top_suites = browser.querySelectorAll('#mocha-report > li.suite');
res_json.suites = Array.prototype.map.call(top_suites, traverse_node);
res_json.replay = browser.querySelector('a.replay').textContent;
emitter.emit('test_ready', res_json);
});
return new Browser();
});
}, Q(new Browser()));
return emitter;
};
module.exports = {
provide_emitter: provide_emitter,
name: 'ZombieJS'
};
+33
View File
@@ -0,0 +1,33 @@
// requires local modules: base64
var assert = chai.assert;
var expect = chai.expect;
describe('Base64 Tools', function() {
"use strict";
var BIN_ARR = new Array(256);
for (var i = 0; i < 256; i++) {
BIN_ARR[i] = i;
}
var B64_STR = "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w==";
describe('encode', function() {
it('should encode a binary string into Base64', function() {
var encoded = Base64.encode(BIN_ARR);
expect(encoded).to.equal(B64_STR);
});
});
describe('decode', function() {
it('should decode a Base64 string into a normal string', function() {
var decoded = Base64.decode(B64_STR);
expect(decoded).to.deep.equal(BIN_ARR);
});
it('should throw an error if we have extra characters at the end of the string', function() {
expect(function () { Base64.decode(B64_STR+'abcdef'); }).to.throw(Error);
});
});
});
+353
View File
@@ -0,0 +1,353 @@
// requires local modules: util, base64, display
// requires test modules: assertions
/* jshint expr: true */
var expect = chai.expect;
describe('Display/Canvas Helper', function () {
var checked_data = [
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
];
checked_data = new Uint8Array(checked_data);
var basic_data = [0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0xff, 0xff, 0xff, 255];
basic_data = new Uint8Array(basic_data);
function make_image_canvas (input_data) {
var canvas = document.createElement('canvas');
canvas.width = 4;
canvas.height = 4;
var ctx = canvas.getContext('2d');
var data = ctx.createImageData(4, 4);
for (var i = 0; i < checked_data.length; i++) { data.data[i] = input_data[i]; }
ctx.putImageData(data, 0, 0);
return canvas;
}
describe('checking for cursor uri support', function () {
beforeEach(function () {
this._old_change_cursor = Display.changeCursor;
});
it('should disable cursor URIs if there is no support', function () {
Display.changeCursor = function(target) {
target.style.cursor = undefined;
};
var display = new Display({ target: document.createElement('canvas'), prefer_js: true, viewport: false });
expect(display._cursor_uri).to.be.false;
});
it('should enable cursor URIs if there is support', function () {
Display.changeCursor = function(target) {
target.style.cursor = 'pointer';
};
var display = new Display({ target: document.createElement('canvas'), prefer_js: true, viewport: false });
expect(display._cursor_uri).to.be.true;
});
it('respect the cursor_uri option if there is support', function () {
Display.changeCursor = function(target) {
target.style.cursor = 'pointer';
};
var display = new Display({ target: document.createElement('canvas'), prefer_js: true, viewport: false, cursor_uri: false });
expect(display._cursor_uri).to.be.false;
});
afterEach(function () {
Display.changeCursor = this._old_change_cursor;
});
});
describe('viewport handling', function () {
var display;
beforeEach(function () {
display = new Display({ target: document.createElement('canvas'), prefer_js: false, viewport: true });
display.resize(5, 5);
display.viewportChange(1, 1, 3, 3);
display.getCleanDirtyReset();
});
it('should take viewport location into consideration when drawing images', function () {
display.resize(4, 4);
display.viewportChange(0, 0, 2, 2);
display.drawImage(make_image_canvas(basic_data), 1, 1);
var expected = new Uint8Array(16);
var i;
for (i = 0; i < 8; i++) { expected[i] = basic_data[i]; }
for (i = 8; i < 16; i++) { expected[i] = 0; }
expect(display).to.have.displayed(expected);
});
it('should redraw the left side when shifted left', function () {
display.viewportChange(-1, 0, 3, 3);
var cdr = display.getCleanDirtyReset();
expect(cdr.cleanBox).to.deep.equal({ x: 1, y: 1, w: 2, h: 3 });
expect(cdr.dirtyBoxes).to.have.length(1);
expect(cdr.dirtyBoxes[0]).to.deep.equal({ x: 0, y: 1, w: 2, h: 3 });
});
it('should redraw the right side when shifted right', function () {
display.viewportChange(1, 0, 3, 3);
var cdr = display.getCleanDirtyReset();
expect(cdr.cleanBox).to.deep.equal({ x: 2, y: 1, w: 2, h: 3 });
expect(cdr.dirtyBoxes).to.have.length(1);
expect(cdr.dirtyBoxes[0]).to.deep.equal({ x: 4, y: 1, w: 1, h: 3 });
});
it('should redraw the top part when shifted up', function () {
display.viewportChange(0, -1, 3, 3);
var cdr = display.getCleanDirtyReset();
expect(cdr.cleanBox).to.deep.equal({ x: 1, y: 1, w: 3, h: 2 });
expect(cdr.dirtyBoxes).to.have.length(1);
expect(cdr.dirtyBoxes[0]).to.deep.equal({ x: 1, y: 0, w: 3, h: 1 });
});
it('should redraw the bottom part when shifted down', function () {
display.viewportChange(0, 1, 3, 3);
var cdr = display.getCleanDirtyReset();
expect(cdr.cleanBox).to.deep.equal({ x: 1, y: 2, w: 3, h: 2 });
expect(cdr.dirtyBoxes).to.have.length(1);
expect(cdr.dirtyBoxes[0]).to.deep.equal({ x: 1, y: 4, w: 3, h: 1 });
});
it('should reset the entire viewport to being clean after calculating the clean/dirty boxes', function () {
display.viewportChange(0, 1, 3, 3);
var cdr1 = display.getCleanDirtyReset();
var cdr2 = display.getCleanDirtyReset();
expect(cdr1).to.not.deep.equal(cdr2);
expect(cdr2.cleanBox).to.deep.equal({ x: 1, y: 2, w: 3, h: 3 });
expect(cdr2.dirtyBoxes).to.be.empty;
});
it('should simply mark the whole display area as dirty if not using viewports', function () {
display = new Display({ target: document.createElement('canvas'), prefer_js: false, viewport: false });
display.resize(5, 5);
var cdr = display.getCleanDirtyReset();
expect(cdr.cleanBox).to.deep.equal({ x: 0, y: 0, w: 0, h: 0 });
expect(cdr.dirtyBoxes).to.have.length(1);
expect(cdr.dirtyBoxes[0]).to.deep.equal({ x: 0, y: 0, w: 5, h: 5 });
});
});
describe('resizing', function () {
var display;
beforeEach(function () {
display = new Display({ target: document.createElement('canvas'), prefer_js: false, viewport: true });
display.resize(4, 3);
});
it('should change the size of the logical canvas', function () {
display.resize(5, 7);
expect(display._fb_width).to.equal(5);
expect(display._fb_height).to.equal(7);
});
it('should update the viewport dimensions', function () {
sinon.spy(display, 'viewportChange');
display.resize(2, 2);
expect(display.viewportChange).to.have.been.calledOnce;
});
});
describe('drawing', function () {
// TODO(directxman12): improve the tests for each of the drawing functions to cover more than just the
// basic cases
function drawing_tests (pref_js) {
var display;
beforeEach(function () {
display = new Display({ target: document.createElement('canvas'), prefer_js: pref_js });
display.resize(4, 4);
});
it('should clear the screen on #clear without a logo set', function () {
display.fillRect(0, 0, 4, 4, [0x00, 0x00, 0xff]);
display._logo = null;
display.clear();
display.resize(4, 4);
var empty = [];
for (var i = 0; i < 4 * display._fb_width * display._fb_height; i++) { empty[i] = 0; }
expect(display).to.have.displayed(new Uint8Array(empty));
});
it('should draw the logo on #clear with a logo set', function (done) {
display._logo = { width: 4, height: 4, data: make_image_canvas(checked_data).toDataURL() };
display._drawCtx._act_drawImg = display._drawCtx.drawImage;
display._drawCtx.drawImage = function (img, x, y) {
this._act_drawImg(img, x, y);
expect(display).to.have.displayed(checked_data);
done();
};
display.clear();
expect(display._fb_width).to.equal(4);
expect(display._fb_height).to.equal(4);
});
it('should support filling a rectangle with particular color via #fillRect', function () {
display.fillRect(0, 0, 4, 4, [0, 0xff, 0]);
display.fillRect(0, 0, 2, 2, [0xff, 0, 0]);
display.fillRect(2, 2, 2, 2, [0xff, 0, 0]);
expect(display).to.have.displayed(checked_data);
});
it('should support copying an portion of the canvas via #copyImage', function () {
display.fillRect(0, 0, 4, 4, [0, 0xff, 0]);
display.fillRect(0, 0, 2, 2, [0xff, 0, 0x00]);
display.copyImage(0, 0, 2, 2, 2, 2);
expect(display).to.have.displayed(checked_data);
});
it('should support drawing tile data with a background color and sub tiles', function () {
display.startTile(0, 0, 4, 4, [0, 0xff, 0]);
display.subTile(0, 0, 2, 2, [0xff, 0, 0]);
display.subTile(2, 2, 2, 2, [0xff, 0, 0]);
display.finishTile();
expect(display).to.have.displayed(checked_data);
});
it('should support drawing BGRX blit images with true color via #blitImage', function () {
var data = [];
for (var i = 0; i < 16; i++) {
data[i * 4] = checked_data[i * 4 + 2];
data[i * 4 + 1] = checked_data[i * 4 + 1];
data[i * 4 + 2] = checked_data[i * 4];
data[i * 4 + 3] = checked_data[i * 4 + 3];
}
display.blitImage(0, 0, 4, 4, data, 0);
expect(display).to.have.displayed(checked_data);
});
it('should support drawing RGB blit images with true color via #blitRgbImage', function () {
var data = [];
for (var i = 0; i < 16; i++) {
data[i * 3] = checked_data[i * 4];
data[i * 3 + 1] = checked_data[i * 4 + 1];
data[i * 3 + 2] = checked_data[i * 4 + 2];
}
display.blitRgbImage(0, 0, 4, 4, data, 0);
expect(display).to.have.displayed(checked_data);
});
it('should support drawing blit images from a data URL via #blitStringImage', function (done) {
var img_url = make_image_canvas(checked_data).toDataURL();
display._drawCtx._act_drawImg = display._drawCtx.drawImage;
display._drawCtx.drawImage = function (img, x, y) {
this._act_drawImg(img, x, y);
expect(display).to.have.displayed(checked_data);
done();
};
display.blitStringImage(img_url, 0, 0);
});
it('should support drawing solid colors with color maps', function () {
display._true_color = false;
display.set_colourMap({ 0: [0xff, 0, 0], 1: [0, 0xff, 0] });
display.fillRect(0, 0, 4, 4, [1]);
display.fillRect(0, 0, 2, 2, [0]);
display.fillRect(2, 2, 2, 2, [0]);
expect(display).to.have.displayed(checked_data);
});
it('should support drawing blit images with color maps', function () {
display._true_color = false;
display.set_colourMap({ 1: [0xff, 0, 0], 0: [0, 0xff, 0] });
var data = [1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1].map(function (elem) { return [elem]; });
display.blitImage(0, 0, 4, 4, data, 0);
expect(display).to.have.displayed(checked_data);
});
it('should support drawing an image object via #drawImage', function () {
var img = make_image_canvas(checked_data);
display.drawImage(img, 0, 0);
expect(display).to.have.displayed(checked_data);
});
}
describe('(prefering native methods)', function () { drawing_tests.call(this, false); });
describe('(prefering JavaScript)', function () { drawing_tests.call(this, true); });
});
describe('the render queue processor', function () {
var display;
beforeEach(function () {
display = new Display({ target: document.createElement('canvas'), prefer_js: false });
display.resize(4, 4);
sinon.spy(display, '_scan_renderQ');
this.old_requestAnimFrame = window.requestAnimFrame;
window.requestAnimFrame = function (cb) {
this.next_frame_cb = cb;
}.bind(this);
this.next_frame = function () { this.next_frame_cb(); };
});
afterEach(function () {
window.requestAnimFrame = this.old_requestAnimFrame;
});
it('should try to process an item when it is pushed on, if nothing else is on the queue', function () {
display.renderQ_push({ type: 'noop' }); // does nothing
expect(display._scan_renderQ).to.have.been.calledOnce;
});
it('should not try to process an item when it is pushed on if we are waiting for other items', function () {
display._renderQ.length = 2;
display.renderQ_push({ type: 'noop' });
expect(display._scan_renderQ).to.not.have.been.called;
});
it('should wait until an image is loaded to attempt to draw it and the rest of the queue', function () {
var img = { complete: false };
display._renderQ = [{ type: 'img', x: 3, y: 4, img: img },
{ type: 'fill', x: 1, y: 2, width: 3, height: 4, color: 5 }];
display.drawImage = sinon.spy();
display.fillRect = sinon.spy();
display._scan_renderQ();
expect(display.drawImage).to.not.have.been.called;
expect(display.fillRect).to.not.have.been.called;
display._renderQ[0].img.complete = true;
this.next_frame();
expect(display.drawImage).to.have.been.calledOnce;
expect(display.fillRect).to.have.been.calledOnce;
});
it('should draw a blit image on type "blit"', function () {
display.blitImage = sinon.spy();
display.renderQ_push({ type: 'blit', x: 3, y: 4, width: 5, height: 6, data: [7, 8, 9] });
expect(display.blitImage).to.have.been.calledOnce;
expect(display.blitImage).to.have.been.calledWith(3, 4, 5, 6, [7, 8, 9], 0);
});
it('should draw a blit RGB image on type "blitRgb"', function () {
display.blitRgbImage = sinon.spy();
display.renderQ_push({ type: 'blitRgb', x: 3, y: 4, width: 5, height: 6, data: [7, 8, 9] });
expect(display.blitRgbImage).to.have.been.calledOnce;
expect(display.blitRgbImage).to.have.been.calledWith(3, 4, 5, 6, [7, 8, 9], 0);
});
it('should copy a region on type "copy"', function () {
display.copyImage = sinon.spy();
display.renderQ_push({ type: 'copy', x: 3, y: 4, width: 5, height: 6, old_x: 7, old_y: 8 });
expect(display.copyImage).to.have.been.calledOnce;
expect(display.copyImage).to.have.been.calledWith(7, 8, 3, 4, 5, 6);
});
it('should fill a rect with a given color on type "fill"', function () {
display.fillRect = sinon.spy();
display.renderQ_push({ type: 'fill', x: 3, y: 4, width: 5, height: 6, color: [7, 8, 9]});
expect(display.fillRect).to.have.been.calledOnce;
expect(display.fillRect).to.have.been.calledWith(3, 4, 5, 6, [7, 8, 9]);
});
it('should draw an image from an image object on type "img" (if complete)', function () {
display.drawImage = sinon.spy();
display.renderQ_push({ type: 'img', x: 3, y: 4, img: { complete: true } });
expect(display.drawImage).to.have.been.calledOnce;
expect(display.drawImage).to.have.been.calledWith({ complete: true }, 3, 4);
});
});
});
+262
View File
@@ -0,0 +1,262 @@
// requires local modules: keysym, keysymdef, keyboard
var assert = chai.assert;
var expect = chai.expect;
describe('Helpers', function() {
"use strict";
describe('keysymFromKeyCode', function() {
it('should map known keycodes to keysyms', function() {
expect(kbdUtil.keysymFromKeyCode(0x41, false), 'a').to.be.equal(0x61);
expect(kbdUtil.keysymFromKeyCode(0x41, true), 'A').to.be.equal(0x41);
expect(kbdUtil.keysymFromKeyCode(0xd, false), 'enter').to.be.equal(0xFF0D);
expect(kbdUtil.keysymFromKeyCode(0x11, false), 'ctrl').to.be.equal(0xFFE3);
expect(kbdUtil.keysymFromKeyCode(0x12, false), 'alt').to.be.equal(0xFFE9);
expect(kbdUtil.keysymFromKeyCode(0xe1, false), 'altgr').to.be.equal(0xFE03);
expect(kbdUtil.keysymFromKeyCode(0x1b, false), 'esc').to.be.equal(0xFF1B);
expect(kbdUtil.keysymFromKeyCode(0x26, false), 'up').to.be.equal(0xFF52);
});
it('should return null for unknown keycodes', function() {
expect(kbdUtil.keysymFromKeyCode(0xc0, false), 'DK æ').to.be.null;
expect(kbdUtil.keysymFromKeyCode(0xde, false), 'DK ø').to.be.null;
});
});
describe('keysyms.fromUnicode', function() {
it('should map ASCII characters to keysyms', function() {
expect(keysyms.fromUnicode('a'.charCodeAt())).to.have.property('keysym', 0x61);
expect(keysyms.fromUnicode('A'.charCodeAt())).to.have.property('keysym', 0x41);
});
it('should map Latin-1 characters to keysyms', function() {
expect(keysyms.fromUnicode('ø'.charCodeAt())).to.have.property('keysym', 0xf8);
expect(keysyms.fromUnicode('é'.charCodeAt())).to.have.property('keysym', 0xe9);
});
it('should map characters that are in Windows-1252 but not in Latin-1 to keysyms', function() {
expect(keysyms.fromUnicode('Š'.charCodeAt())).to.have.property('keysym', 0x01a9);
});
it('should map characters which aren\'t in Latin1 *or* Windows-1252 to keysyms', function() {
expect(keysyms.fromUnicode('ŵ'.charCodeAt())).to.have.property('keysym', 0x1000175);
});
it('should return undefined for unknown codepoints', function() {
expect(keysyms.fromUnicode('\n'.charCodeAt())).to.be.undefined;
expect(keysyms.fromUnicode('\u1F686'.charCodeAt())).to.be.undefined;
});
});
describe('substituteCodepoint', function() {
it('should replace characters which don\'t have a keysym', function() {
expect(kbdUtil.substituteCodepoint('Ș'.charCodeAt())).to.equal('Ş'.charCodeAt());
expect(kbdUtil.substituteCodepoint('ș'.charCodeAt())).to.equal('ş'.charCodeAt());
expect(kbdUtil.substituteCodepoint('Ț'.charCodeAt())).to.equal('Ţ'.charCodeAt());
expect(kbdUtil.substituteCodepoint('ț'.charCodeAt())).to.equal('ţ'.charCodeAt());
});
it('should pass other characters through unchanged', function() {
expect(kbdUtil.substituteCodepoint('T'.charCodeAt())).to.equal('T'.charCodeAt());
});
});
describe('nonCharacterKey', function() {
it('should recognize the right keys', function() {
expect(kbdUtil.nonCharacterKey({keyCode: 0xd}), 'enter').to.be.defined;
expect(kbdUtil.nonCharacterKey({keyCode: 0x08}), 'backspace').to.be.defined;
expect(kbdUtil.nonCharacterKey({keyCode: 0x09}), 'tab').to.be.defined;
expect(kbdUtil.nonCharacterKey({keyCode: 0x10}), 'shift').to.be.defined;
expect(kbdUtil.nonCharacterKey({keyCode: 0x11}), 'ctrl').to.be.defined;
expect(kbdUtil.nonCharacterKey({keyCode: 0x12}), 'alt').to.be.defined;
expect(kbdUtil.nonCharacterKey({keyCode: 0xe0}), 'meta').to.be.defined;
});
it('should not recognize character keys', function() {
expect(kbdUtil.nonCharacterKey({keyCode: 'A'}), 'A').to.be.null;
expect(kbdUtil.nonCharacterKey({keyCode: '1'}), '1').to.be.null;
expect(kbdUtil.nonCharacterKey({keyCode: '.'}), '.').to.be.null;
expect(kbdUtil.nonCharacterKey({keyCode: ' '}), 'space').to.be.null;
});
});
describe('getKeysym', function() {
it('should prefer char', function() {
expect(kbdUtil.getKeysym({char : 'a', charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43})).to.have.property('keysym', 0x61);
});
it('should use charCode if no char', function() {
expect(kbdUtil.getKeysym({char : '', charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43})).to.have.property('keysym', 0x01a9);
expect(kbdUtil.getKeysym({charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43})).to.have.property('keysym', 0x01a9);
expect(kbdUtil.getKeysym({char : 'hello', charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43})).to.have.property('keysym', 0x01a9);
});
it('should use keyCode if no charCode', function() {
expect(kbdUtil.getKeysym({keyCode: 0x42, which: 0x43, shiftKey: false})).to.have.property('keysym', 0x62);
expect(kbdUtil.getKeysym({keyCode: 0x42, which: 0x43, shiftKey: true})).to.have.property('keysym', 0x42);
});
it('should use which if no keyCode', function() {
expect(kbdUtil.getKeysym({which: 0x43, shiftKey: false})).to.have.property('keysym', 0x63);
expect(kbdUtil.getKeysym({which: 0x43, shiftKey: true})).to.have.property('keysym', 0x43);
});
it('should substitute where applicable', function() {
expect(kbdUtil.getKeysym({char : 'Ș'})).to.have.property('keysym', 0x1aa);
});
});
describe('Modifier Sync', function() { // return a list of fake events necessary to fix modifier state
describe('Toggle all modifiers', function() {
var sync = kbdUtil.ModifierSync();
it ('should do nothing if all modifiers are up as expected', function() {
expect(sync.keydown({
keyCode: 0x41,
ctrlKey: false,
altKey: false,
altGraphKey: false,
shiftKey: false,
metaKey: false})
).to.have.lengthOf(0);
});
it ('should synthesize events if all keys are unexpectedly down', function() {
var result = sync.keydown({
keyCode: 0x41,
ctrlKey: true,
altKey: true,
altGraphKey: true,
shiftKey: true,
metaKey: true
});
expect(result).to.have.lengthOf(5);
var keysyms = {};
for (var i = 0; i < result.length; ++i) {
keysyms[result[i].keysym] = (result[i].type == 'keydown');
}
expect(keysyms[0xffe3]);
expect(keysyms[0xffe9]);
expect(keysyms[0xfe03]);
expect(keysyms[0xffe1]);
expect(keysyms[0xffe7]);
});
it ('should do nothing if all modifiers are down as expected', function() {
expect(sync.keydown({
keyCode: 0x41,
ctrlKey: true,
altKey: true,
altGraphKey: true,
shiftKey: true,
metaKey: true
})).to.have.lengthOf(0);
});
});
describe('Toggle Ctrl', function() {
var sync = kbdUtil.ModifierSync();
it('should sync if modifier is suddenly down', function() {
expect(sync.keydown({
keyCode: 0x41,
ctrlKey: true,
})).to.be.deep.equal([{keysym: keysyms.lookup(0xffe3), type: 'keydown'}]);
});
it('should sync if modifier is suddenly up', function() {
expect(sync.keydown({
keyCode: 0x41,
ctrlKey: false
})).to.be.deep.equal([{keysym: keysyms.lookup(0xffe3), type: 'keyup'}]);
});
});
describe('Toggle Alt', function() {
var sync = kbdUtil.ModifierSync();
it('should sync if modifier is suddenly down', function() {
expect(sync.keydown({
keyCode: 0x41,
altKey: true,
})).to.be.deep.equal([{keysym: keysyms.lookup(0xffe9), type: 'keydown'}]);
});
it('should sync if modifier is suddenly up', function() {
expect(sync.keydown({
keyCode: 0x41,
altKey: false
})).to.be.deep.equal([{keysym: keysyms.lookup(0xffe9), type: 'keyup'}]);
});
});
describe('Toggle AltGr', function() {
var sync = kbdUtil.ModifierSync();
it('should sync if modifier is suddenly down', function() {
expect(sync.keydown({
keyCode: 0x41,
altGraphKey: true,
})).to.be.deep.equal([{keysym: keysyms.lookup(0xfe03), type: 'keydown'}]);
});
it('should sync if modifier is suddenly up', function() {
expect(sync.keydown({
keyCode: 0x41,
altGraphKey: false
})).to.be.deep.equal([{keysym: keysyms.lookup(0xfe03), type: 'keyup'}]);
});
});
describe('Toggle Shift', function() {
var sync = kbdUtil.ModifierSync();
it('should sync if modifier is suddenly down', function() {
expect(sync.keydown({
keyCode: 0x41,
shiftKey: true,
})).to.be.deep.equal([{keysym: keysyms.lookup(0xffe1), type: 'keydown'}]);
});
it('should sync if modifier is suddenly up', function() {
expect(sync.keydown({
keyCode: 0x41,
shiftKey: false
})).to.be.deep.equal([{keysym: keysyms.lookup(0xffe1), type: 'keyup'}]);
});
});
describe('Toggle Meta', function() {
var sync = kbdUtil.ModifierSync();
it('should sync if modifier is suddenly down', function() {
expect(sync.keydown({
keyCode: 0x41,
metaKey: true,
})).to.be.deep.equal([{keysym: keysyms.lookup(0xffe7), type: 'keydown'}]);
});
it('should sync if modifier is suddenly up', function() {
expect(sync.keydown({
keyCode: 0x41,
metaKey: false
})).to.be.deep.equal([{keysym: keysyms.lookup(0xffe7), type: 'keyup'}]);
});
});
describe('Modifier keyevents', function() {
it('should not sync a modifier on its own events', function() {
expect(kbdUtil.ModifierSync().keydown({
keyCode: 0x11,
ctrlKey: false
})).to.be.deep.equal([]);
expect(kbdUtil.ModifierSync().keydown({
keyCode: 0x11,
ctrlKey: true
}), 'B').to.be.deep.equal([]);
})
it('should update state on modifier keyevents', function() {
var sync = kbdUtil.ModifierSync();
sync.keydown({
keyCode: 0x11,
});
expect(sync.keydown({
keyCode: 0x41,
ctrlKey: true,
})).to.be.deep.equal([]);
});
it('should sync other modifiers on ctrl events', function() {
expect(kbdUtil.ModifierSync().keydown({
keyCode: 0x11,
altKey: true
})).to.be.deep.equal([{keysym: keysyms.lookup(0xffe9), type: 'keydown'}]);
})
});
describe('sync modifiers on non-key events', function() {
it('should generate sync events when receiving non-keyboard events', function() {
expect(kbdUtil.ModifierSync().syncAny({
altKey: true
})).to.be.deep.equal([{keysym: keysyms.lookup(0xffe9), type: 'keydown'}]);
});
});
describe('do not treat shift as a modifier key', function() {
it('should not treat shift as a shortcut modifier', function() {
expect(kbdUtil.hasShortcutModifier([], {0xffe1 : true})).to.be.false;
});
it('should not treat shift as a char modifier', function() {
expect(kbdUtil.hasCharModifier([], {0xffe1 : true})).to.be.false;
});
});
});
});
+842
View File
@@ -0,0 +1,842 @@
// requires local modules: input, keyboard, keysymdef
var assert = chai.assert;
var expect = chai.expect;
/* jshint newcap: false, expr: true */
describe('Key Event Pipeline Stages', function() {
"use strict";
describe('Decode Keyboard Events', function() {
it('should pass events to the next stage', function(done) {
KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {
expect(evt).to.be.an.object;
done();
}).keydown({keyCode: 0x41});
});
it('should pass the right keysym through', function(done) {
KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {
expect(evt.keysym).to.be.deep.equal(keysyms.lookup(0x61));
done();
}).keypress({keyCode: 0x41});
});
it('should pass the right keyid through', function(done) {
KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {
expect(evt).to.have.property('keyId', 0x41);
done();
}).keydown({keyCode: 0x41});
});
it('should not sync modifiers on a keypress', function() {
// Firefox provides unreliable modifier state on keypress events
var count = 0;
KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {
++count;
}).keypress({keyCode: 0x41, ctrlKey: true});
expect(count).to.be.equal(1);
});
it('should sync modifiers if necessary', function(done) {
var count = 0;
KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {
switch (count) {
case 0: // fake a ctrl keydown
expect(evt).to.be.deep.equal({keysym: keysyms.lookup(0xffe3), type: 'keydown'});
++count;
break;
case 1:
expect(evt).to.be.deep.equal({keyId: 0x41, type: 'keydown', keysym: keysyms.lookup(0x61)});
done();
break;
}
}).keydown({keyCode: 0x41, ctrlKey: true});
});
it('should forward keydown events with the right type', function(done) {
KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {
expect(evt).to.be.deep.equal({keyId: 0x41, type: 'keydown'});
done();
}).keydown({keyCode: 0x41});
});
it('should forward keyup events with the right type', function(done) {
KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {
expect(evt).to.be.deep.equal({keyId: 0x41, keysym: keysyms.lookup(0x61), type: 'keyup'});
done();
}).keyup({keyCode: 0x41});
});
it('should forward keypress events with the right type', function(done) {
KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {
expect(evt).to.be.deep.equal({keyId: 0x41, keysym: keysyms.lookup(0x61), type: 'keypress'});
done();
}).keypress({keyCode: 0x41});
});
it('should generate stalls if a char modifier is down while a key is pressed', function(done) {
var count = 0;
KeyEventDecoder(kbdUtil.ModifierSync([0xfe03]), function(evt) {
switch (count) {
case 0: // fake altgr
expect(evt).to.be.deep.equal({keysym: keysyms.lookup(0xfe03), type: 'keydown'});
++count;
break;
case 1: // stall before processing the 'a' keydown
expect(evt).to.be.deep.equal({type: 'stall'});
++count;
break;
case 2: // 'a'
expect(evt).to.be.deep.equal({
type: 'keydown',
keyId: 0x41,
keysym: keysyms.lookup(0x61)
});
done();
break;
}
}).keydown({keyCode: 0x41, altGraphKey: true});
});
describe('suppress the right events at the right time', function() {
it('should suppress anything while a shortcut modifier is down', function() {
var obj = KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {});
obj.keydown({keyCode: 0x11}); // press ctrl
expect(obj.keydown({keyCode: 'A'.charCodeAt()})).to.be.true;
expect(obj.keydown({keyCode: ' '.charCodeAt()})).to.be.true;
expect(obj.keydown({keyCode: '1'.charCodeAt()})).to.be.true;
expect(obj.keydown({keyCode: 0x3c})).to.be.true; // < key on DK Windows
expect(obj.keydown({keyCode: 0xde})).to.be.true; // Ø key on DK
});
it('should suppress non-character keys', function() {
var obj = KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {});
expect(obj.keydown({keyCode: 0x08}), 'a').to.be.true;
expect(obj.keydown({keyCode: 0x09}), 'b').to.be.true;
expect(obj.keydown({keyCode: 0x11}), 'd').to.be.true;
expect(obj.keydown({keyCode: 0x12}), 'e').to.be.true;
});
it('should not suppress shift', function() {
var obj = KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {});
expect(obj.keydown({keyCode: 0x10}), 'd').to.be.false;
});
it('should generate event for shift keydown', function() {
var called = false;
var obj = KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {
expect(evt).to.have.property('keysym');
called = true;
}).keydown({keyCode: 0x10});
expect(called).to.be.true;
});
it('should not suppress character keys', function() {
var obj = KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {});
expect(obj.keydown({keyCode: 'A'.charCodeAt()})).to.be.false;
expect(obj.keydown({keyCode: ' '.charCodeAt()})).to.be.false;
expect(obj.keydown({keyCode: '1'.charCodeAt()})).to.be.false;
expect(obj.keydown({keyCode: 0x3c})).to.be.false; // < key on DK Windows
expect(obj.keydown({keyCode: 0xde})).to.be.false; // Ø key on DK
});
it('should not suppress if a char modifier is down', function() {
var obj = KeyEventDecoder(kbdUtil.ModifierSync([0xfe03]), function(evt) {});
obj.keydown({keyCode: 0xe1}); // press altgr
expect(obj.keydown({keyCode: 'A'.charCodeAt()})).to.be.false;
expect(obj.keydown({keyCode: ' '.charCodeAt()})).to.be.false;
expect(obj.keydown({keyCode: '1'.charCodeAt()})).to.be.false;
expect(obj.keydown({keyCode: 0x3c})).to.be.false; // < key on DK Windows
expect(obj.keydown({keyCode: 0xde})).to.be.false; // Ø key on DK
});
});
describe('Keypress and keyup events', function() {
it('should always suppress event propagation', function() {
var obj = KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {});
expect(obj.keypress({keyCode: 'A'.charCodeAt()})).to.be.true;
expect(obj.keypress({keyCode: 0x3c})).to.be.true; // < key on DK Windows
expect(obj.keypress({keyCode: 0x11})).to.be.true;
expect(obj.keyup({keyCode: 'A'.charCodeAt()})).to.be.true;
expect(obj.keyup({keyCode: 0x3c})).to.be.true; // < key on DK Windows
expect(obj.keyup({keyCode: 0x11})).to.be.true;
});
it('should never generate stalls', function() {
var obj = KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {
expect(evt.type).to.not.be.equal('stall');
});
obj.keypress({keyCode: 'A'.charCodeAt()});
obj.keypress({keyCode: 0x3c});
obj.keypress({keyCode: 0x11});
obj.keyup({keyCode: 'A'.charCodeAt()});
obj.keyup({keyCode: 0x3c});
obj.keyup({keyCode: 0x11});
});
});
describe('mark events if a char modifier is down', function() {
it('should not mark modifiers on a keydown event', function() {
var times_called = 0;
var obj = KeyEventDecoder(kbdUtil.ModifierSync([0xfe03]), function(evt) {
switch (times_called++) {
case 0: //altgr
break;
case 1: // 'a'
expect(evt).to.not.have.property('escape');
break;
}
});
obj.keydown({keyCode: 0xe1}); // press altgr
obj.keydown({keyCode: 'A'.charCodeAt()});
});
it('should indicate on events if a single-key char modifier is down', function(done) {
var times_called = 0;
var obj = KeyEventDecoder(kbdUtil.ModifierSync([0xfe03]), function(evt) {
switch (times_called++) {
case 0: //altgr
break;
case 1: // 'a'
expect(evt).to.be.deep.equal({
type: 'keypress',
keyId: 'A'.charCodeAt(),
keysym: keysyms.lookup('a'.charCodeAt()),
escape: [0xfe03]
});
done();
return;
}
});
obj.keydown({keyCode: 0xe1}); // press altgr
obj.keypress({keyCode: 'A'.charCodeAt()});
});
it('should indicate on events if a multi-key char modifier is down', function(done) {
var times_called = 0;
var obj = KeyEventDecoder(kbdUtil.ModifierSync([0xffe9, 0xffe3]), function(evt) {
switch (times_called++) {
case 0: //ctrl
break;
case 1: //alt
break;
case 2: // 'a'
expect(evt).to.be.deep.equal({
type: 'keypress',
keyId: 'A'.charCodeAt(),
keysym: keysyms.lookup('a'.charCodeAt()),
escape: [0xffe9, 0xffe3]
});
done();
return;
}
});
obj.keydown({keyCode: 0x11}); // press ctrl
obj.keydown({keyCode: 0x12}); // press alt
obj.keypress({keyCode: 'A'.charCodeAt()});
});
it('should not consider a char modifier to be down on the modifier key itself', function() {
var obj = KeyEventDecoder(kbdUtil.ModifierSync([0xfe03]), function(evt) {
expect(evt).to.not.have.property('escape');
});
obj.keydown({keyCode: 0xe1}); // press altgr
});
});
describe('add/remove keysym', function() {
it('should remove keysym from keydown if a char key and no modifier', function() {
KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {
expect(evt).to.be.deep.equal({keyId: 0x41, type: 'keydown'});
}).keydown({keyCode: 0x41});
});
it('should not remove keysym from keydown if a shortcut modifier is down', function() {
var times_called = 0;
KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {
switch (times_called++) {
case 1:
expect(evt).to.be.deep.equal({keyId: 0x41, keysym: keysyms.lookup(0x61), type: 'keydown'});
break;
}
}).keydown({keyCode: 0x41, ctrlKey: true});
expect(times_called).to.be.equal(2);
});
it('should not remove keysym from keydown if a char modifier is down', function() {
var times_called = 0;
KeyEventDecoder(kbdUtil.ModifierSync([0xfe03]), function(evt) {
switch (times_called++) {
case 2:
expect(evt).to.be.deep.equal({keyId: 0x41, keysym: keysyms.lookup(0x61), type: 'keydown'});
break;
}
}).keydown({keyCode: 0x41, altGraphKey: true});
expect(times_called).to.be.equal(3);
});
it('should not remove keysym from keydown if key is noncharacter', function() {
KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {
expect(evt, 'bacobjpace').to.be.deep.equal({keyId: 0x09, keysym: keysyms.lookup(0xff09), type: 'keydown'});
}).keydown({keyCode: 0x09});
KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {
expect(evt, 'ctrl').to.be.deep.equal({keyId: 0x11, keysym: keysyms.lookup(0xffe3), type: 'keydown'});
}).keydown({keyCode: 0x11});
});
it('should never remove keysym from keypress', function() {
KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {
expect(evt).to.be.deep.equal({keyId: 0x41, keysym: keysyms.lookup(0x61), type: 'keypress'});
}).keypress({keyCode: 0x41});
});
it('should never remove keysym from keyup', function() {
KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) {
expect(evt).to.be.deep.equal({keyId: 0x41, keysym: keysyms.lookup(0x61), type: 'keyup'});
}).keyup({keyCode: 0x41});
});
});
// on keypress, keyup(?), always set keysym
// on keydown, only do it if we don't expect a keypress: if noncharacter OR modifier is down
});
describe('Verify that char modifiers are active', function() {
it('should pass keydown events through if there is no stall', function(done) {
var obj = VerifyCharModifier(function(evt){
expect(evt).to.deep.equal({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x41)});
done();
})({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x41)});
});
it('should pass keyup events through if there is no stall', function(done) {
var obj = VerifyCharModifier(function(evt){
expect(evt).to.deep.equal({type: 'keyup', keyId: 0x41, keysym: keysyms.lookup(0x41)});
done();
})({type: 'keyup', keyId: 0x41, keysym: keysyms.lookup(0x41)});
});
it('should pass keypress events through if there is no stall', function(done) {
var obj = VerifyCharModifier(function(evt){
expect(evt).to.deep.equal({type: 'keypress', keyId: 0x41, keysym: keysyms.lookup(0x41)});
done();
})({type: 'keypress', keyId: 0x41, keysym: keysyms.lookup(0x41)});
});
it('should not pass stall events through', function(done){
var obj = VerifyCharModifier(function(evt){
// should only be called once, for the keydown
expect(evt).to.deep.equal({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x41)});
done();
});
obj({type: 'stall'});
obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x41)});
});
it('should merge keydown and keypress events if they come after a stall', function(done) {
var next_called = false;
var obj = VerifyCharModifier(function(evt){
// should only be called once, for the keydown
expect(next_called).to.be.false;
next_called = true;
expect(evt).to.deep.equal({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x44)});
done();
});
obj({type: 'stall'});
obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)});
obj({type: 'keypress', keyId: 0x43, keysym: keysyms.lookup(0x44)});
expect(next_called).to.be.false;
});
it('should preserve modifier attribute when merging if keysyms differ', function(done) {
var next_called = false;
var obj = VerifyCharModifier(function(evt){
// should only be called once, for the keydown
expect(next_called).to.be.false;
next_called = true;
expect(evt).to.deep.equal({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x44), escape: [0xffe3]});
done();
});
obj({type: 'stall'});
obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)});
obj({type: 'keypress', keyId: 0x43, keysym: keysyms.lookup(0x44), escape: [0xffe3]});
expect(next_called).to.be.false;
});
it('should not preserve modifier attribute when merging if keysyms are the same', function() {
var obj = VerifyCharModifier(function(evt){
expect(evt).to.not.have.property('escape');
});
obj({type: 'stall'});
obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)});
obj({type: 'keypress', keyId: 0x43, keysym: keysyms.lookup(0x42), escape: [0xffe3]});
});
it('should not merge keydown and keypress events if there is no stall', function(done) {
var times_called = 0;
var obj = VerifyCharModifier(function(evt){
switch(times_called) {
case 0:
expect(evt).to.deep.equal({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)});
break;
case 1:
expect(evt).to.deep.equal({type: 'keypress', keyId: 0x43, keysym: keysyms.lookup(0x44)});
done();
break;
}
++times_called;
});
obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)});
obj({type: 'keypress', keyId: 0x43, keysym: keysyms.lookup(0x44)});
});
it('should not merge keydown and keypress events if separated by another event', function(done) {
var times_called = 0;
var obj = VerifyCharModifier(function(evt){
switch(times_called) {
case 0:
expect(evt,1).to.deep.equal({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)});
break;
case 1:
expect(evt,2).to.deep.equal({type: 'keyup', keyId: 0x43, keysym: keysyms.lookup(0x44)});
break;
case 2:
expect(evt,3).to.deep.equal({type: 'keypress', keyId: 0x45, keysym: keysyms.lookup(0x46)});
done();
break;
}
++times_called;
});
obj({type: 'stall'});
obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)});
obj({type: 'keyup', keyId: 0x43, keysym: keysyms.lookup(0x44)});
obj({type: 'keypress', keyId: 0x45, keysym: keysyms.lookup(0x46)});
});
});
describe('Track Key State', function() {
it('should do nothing on keyup events if no keys are down', function() {
var obj = TrackKeyState(function(evt) {
expect(true).to.be.false;
});
obj({type: 'keyup', keyId: 0x41});
});
it('should insert into the queue on keydown if no keys are down', function() {
var times_called = 0;
var elem = null;
var keysymsdown = {};
var obj = TrackKeyState(function(evt) {
++times_called;
if (elem.type == 'keyup') {
expect(evt).to.have.property('keysym');
expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
delete keysymsdown[evt.keysym.keysym];
}
else {
expect(evt).to.be.deep.equal(elem);
expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
}
elem = null;
});
expect(elem).to.be.null;
elem = {type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)};
keysymsdown[keysyms.lookup(0x42).keysym] = true;
obj(elem);
expect(elem).to.be.null;
elem = {type: 'keyup', keyId: 0x41};
obj(elem);
expect(elem).to.be.null;
expect(times_called).to.be.equal(2);
});
it('should insert into the queue on keypress if no keys are down', function() {
var times_called = 0;
var elem = null;
var keysymsdown = {};
var obj = TrackKeyState(function(evt) {
++times_called;
if (elem.type == 'keyup') {
expect(evt).to.have.property('keysym');
expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
delete keysymsdown[evt.keysym.keysym];
}
else {
expect(evt).to.be.deep.equal(elem);
expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
}
elem = null;
});
expect(elem).to.be.null;
elem = {type: 'keypress', keyId: 0x41, keysym: keysyms.lookup(0x42)};
keysymsdown[keysyms.lookup(0x42).keysym] = true;
obj(elem);
expect(elem).to.be.null;
elem = {type: 'keyup', keyId: 0x41};
obj(elem);
expect(elem).to.be.null;
expect(times_called).to.be.equal(2);
});
it('should add keysym to last key entry if keyId matches', function() {
// this implies that a single keyup will release both keysyms
var times_called = 0;
var elem = null;
var keysymsdown = {};
var obj = TrackKeyState(function(evt) {
++times_called;
if (elem.type == 'keyup') {
expect(evt).to.have.property('keysym');
expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
delete keysymsdown[evt.keysym.keysym];
}
else {
expect(evt).to.be.deep.equal(elem);
expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
elem = null;
}
});
expect(elem).to.be.null;
elem = {type: 'keypress', keyId: 0x41, keysym: keysyms.lookup(0x42)};
keysymsdown[keysyms.lookup(0x42).keysym] = true;
obj(elem);
expect(elem).to.be.null;
elem = {type: 'keypress', keyId: 0x41, keysym: keysyms.lookup(0x43)};
keysymsdown[keysyms.lookup(0x43).keysym] = true;
obj(elem);
expect(elem).to.be.null;
elem = {type: 'keyup', keyId: 0x41};
obj(elem);
expect(times_called).to.be.equal(4);
});
it('should create new key entry if keyId matches and keysym does not', function() {
// this implies that a single keyup will release both keysyms
var times_called = 0;
var elem = null;
var keysymsdown = {};
var obj = TrackKeyState(function(evt) {
++times_called;
if (elem.type == 'keyup') {
expect(evt).to.have.property('keysym');
expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
delete keysymsdown[evt.keysym.keysym];
}
else {
expect(evt).to.be.deep.equal(elem);
expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
elem = null;
}
});
expect(elem).to.be.null;
elem = {type: 'keydown', keyId: 0, keysym: keysyms.lookup(0x42)};
keysymsdown[keysyms.lookup(0x42).keysym] = true;
obj(elem);
expect(elem).to.be.null;
elem = {type: 'keydown', keyId: 0, keysym: keysyms.lookup(0x43)};
keysymsdown[keysyms.lookup(0x43).keysym] = true;
obj(elem);
expect(times_called).to.be.equal(2);
expect(elem).to.be.null;
elem = {type: 'keyup', keyId: 0};
obj(elem);
expect(times_called).to.be.equal(3);
elem = {type: 'keyup', keyId: 0};
obj(elem);
expect(times_called).to.be.equal(4);
});
it('should merge key entry if keyIds are zero and keysyms match', function() {
// this implies that a single keyup will release both keysyms
var times_called = 0;
var elem = null;
var keysymsdown = {};
var obj = TrackKeyState(function(evt) {
++times_called;
if (elem.type == 'keyup') {
expect(evt).to.have.property('keysym');
expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
delete keysymsdown[evt.keysym.keysym];
}
else {
expect(evt).to.be.deep.equal(elem);
expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
elem = null;
}
});
expect(elem).to.be.null;
elem = {type: 'keydown', keyId: 0, keysym: keysyms.lookup(0x42)};
keysymsdown[keysyms.lookup(0x42).keysym] = true;
obj(elem);
expect(elem).to.be.null;
elem = {type: 'keydown', keyId: 0, keysym: keysyms.lookup(0x42)};
keysymsdown[keysyms.lookup(0x42).keysym] = true;
obj(elem);
expect(times_called).to.be.equal(2);
expect(elem).to.be.null;
elem = {type: 'keyup', keyId: 0};
obj(elem);
expect(times_called).to.be.equal(3);
});
it('should add keysym as separate entry if keyId does not match last event', function() {
// this implies that separate keyups are required
var times_called = 0;
var elem = null;
var keysymsdown = {};
var obj = TrackKeyState(function(evt) {
++times_called;
if (elem.type == 'keyup') {
expect(evt).to.have.property('keysym');
expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
delete keysymsdown[evt.keysym.keysym];
}
else {
expect(evt).to.be.deep.equal(elem);
expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
elem = null;
}
});
expect(elem).to.be.null;
elem = {type: 'keypress', keyId: 0x41, keysym: keysyms.lookup(0x42)};
keysymsdown[keysyms.lookup(0x42).keysym] = true;
obj(elem);
expect(elem).to.be.null;
elem = {type: 'keypress', keyId: 0x42, keysym: keysyms.lookup(0x43)};
keysymsdown[keysyms.lookup(0x43).keysym] = true;
obj(elem);
expect(elem).to.be.null;
elem = {type: 'keyup', keyId: 0x41};
obj(elem);
expect(times_called).to.be.equal(4);
elem = {type: 'keyup', keyId: 0x42};
obj(elem);
expect(times_called).to.be.equal(4);
});
it('should add keysym as separate entry if keyId does not match last event and first is zero', function() {
// this implies that separate keyups are required
var times_called = 0;
var elem = null;
var keysymsdown = {};
var obj = TrackKeyState(function(evt) {
++times_called;
if (elem.type == 'keyup') {
expect(evt).to.have.property('keysym');
expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
delete keysymsdown[evt.keysym.keysym];
}
else {
expect(evt).to.be.deep.equal(elem);
expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
elem = null;
}
});
expect(elem).to.be.null;
elem = {type: 'keydown', keyId: 0, keysym: keysyms.lookup(0x42)};
keysymsdown[keysyms.lookup(0x42).keysym] = true;
obj(elem);
expect(elem).to.be.null;
elem = {type: 'keydown', keyId: 0x42, keysym: keysyms.lookup(0x43)};
keysymsdown[keysyms.lookup(0x43).keysym] = true;
obj(elem);
expect(elem).to.be.null;
expect(times_called).to.be.equal(2);
elem = {type: 'keyup', keyId: 0};
obj(elem);
expect(times_called).to.be.equal(3);
elem = {type: 'keyup', keyId: 0x42};
obj(elem);
expect(times_called).to.be.equal(4);
});
it('should add keysym as separate entry if keyId does not match last event and second is zero', function() {
// this implies that a separate keyups are required
var times_called = 0;
var elem = null;
var keysymsdown = {};
var obj = TrackKeyState(function(evt) {
++times_called;
if (elem.type == 'keyup') {
expect(evt).to.have.property('keysym');
expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
delete keysymsdown[evt.keysym.keysym];
}
else {
expect(evt).to.be.deep.equal(elem);
expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
elem = null;
}
});
expect(elem).to.be.null;
elem = {type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)};
keysymsdown[keysyms.lookup(0x42).keysym] = true;
obj(elem);
expect(elem).to.be.null;
elem = {type: 'keydown', keyId: 0, keysym: keysyms.lookup(0x43)};
keysymsdown[keysyms.lookup(0x43).keysym] = true;
obj(elem);
expect(elem).to.be.null;
elem = {type: 'keyup', keyId: 0x41};
obj(elem);
expect(times_called).to.be.equal(3);
elem = {type: 'keyup', keyId: 0};
obj(elem);
expect(times_called).to.be.equal(4);
});
it('should pop matching key event on keyup', function() {
var times_called = 0;
var obj = TrackKeyState(function(evt) {
switch (times_called++) {
case 0:
case 1:
case 2:
expect(evt.type).to.be.equal('keydown');
break;
case 3:
expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0x42, keysym: keysyms.lookup(0x62)});
break;
}
});
obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x61)});
obj({type: 'keydown', keyId: 0x42, keysym: keysyms.lookup(0x62)});
obj({type: 'keydown', keyId: 0x43, keysym: keysyms.lookup(0x63)});
obj({type: 'keyup', keyId: 0x42});
expect(times_called).to.equal(4);
});
it('should pop the first zero keyevent on keyup with zero keyId', function() {
var times_called = 0;
var obj = TrackKeyState(function(evt) {
switch (times_called++) {
case 0:
case 1:
case 2:
expect(evt.type).to.be.equal('keydown');
break;
case 3:
expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0, keysym: keysyms.lookup(0x61)});
break;
}
});
obj({type: 'keydown', keyId: 0, keysym: keysyms.lookup(0x61)});
obj({type: 'keydown', keyId: 0, keysym: keysyms.lookup(0x62)});
obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x63)});
obj({type: 'keyup', keyId: 0x0});
expect(times_called).to.equal(4);
});
it('should pop the last keyevents keysym if no match is found for keyId', function() {
var times_called = 0;
var obj = TrackKeyState(function(evt) {
switch (times_called++) {
case 0:
case 1:
case 2:
expect(evt.type).to.be.equal('keydown');
break;
case 3:
expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0x44, keysym: keysyms.lookup(0x63)});
break;
}
});
obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x61)});
obj({type: 'keydown', keyId: 0x42, keysym: keysyms.lookup(0x62)});
obj({type: 'keydown', keyId: 0x43, keysym: keysyms.lookup(0x63)});
obj({type: 'keyup', keyId: 0x44});
expect(times_called).to.equal(4);
});
describe('Firefox sends keypress even when keydown is suppressed', function() {
it('should discard the keypress', function() {
var times_called = 0;
var obj = TrackKeyState(function(evt) {
expect(times_called).to.be.equal(0);
++times_called;
});
obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)});
expect(times_called).to.be.equal(1);
obj({type: 'keypress', keyId: 0x41, keysym: keysyms.lookup(0x43)});
});
});
describe('releaseAll', function() {
it('should do nothing if no keys have been pressed', function() {
var times_called = 0;
var obj = TrackKeyState(function(evt) {
++times_called;
});
obj({type: 'releaseall'});
expect(times_called).to.be.equal(0);
});
it('should release the keys that have been pressed', function() {
var times_called = 0;
var obj = TrackKeyState(function(evt) {
switch (times_called++) {
case 2:
expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0, keysym: keysyms.lookup(0x41)});
break;
case 3:
expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0, keysym: keysyms.lookup(0x42)});
break;
}
});
obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x41)});
obj({type: 'keydown', keyId: 0x42, keysym: keysyms.lookup(0x42)});
expect(times_called).to.be.equal(2);
obj({type: 'releaseall'});
expect(times_called).to.be.equal(4);
obj({type: 'releaseall'});
expect(times_called).to.be.equal(4);
});
});
});
describe('Escape Modifiers', function() {
describe('Keydown', function() {
it('should pass through when a char modifier is not down', function() {
var times_called = 0;
EscapeModifiers(function(evt) {
expect(times_called).to.be.equal(0);
++times_called;
expect(evt).to.be.deep.equal({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)});
})({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)});
expect(times_called).to.be.equal(1);
});
it('should generate fake undo/redo events when a char modifier is down', function() {
var times_called = 0;
EscapeModifiers(function(evt) {
switch(times_called++) {
case 0:
expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0, keysym: keysyms.lookup(0xffe9)});
break;
case 1:
expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0, keysym: keysyms.lookup(0xffe3)});
break;
case 2:
expect(evt).to.be.deep.equal({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42), escape: [0xffe9, 0xffe3]});
break;
case 3:
expect(evt).to.be.deep.equal({type: 'keydown', keyId: 0, keysym: keysyms.lookup(0xffe9)});
break;
case 4:
expect(evt).to.be.deep.equal({type: 'keydown', keyId: 0, keysym: keysyms.lookup(0xffe3)});
break;
}
})({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42), escape: [0xffe9, 0xffe3]});
expect(times_called).to.be.equal(5);
});
});
describe('Keyup', function() {
it('should pass through when a char modifier is down', function() {
var times_called = 0;
EscapeModifiers(function(evt) {
expect(times_called).to.be.equal(0);
++times_called;
expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0x41, keysym: keysyms.lookup(0x42), escape: [0xfe03]});
})({type: 'keyup', keyId: 0x41, keysym: keysyms.lookup(0x42), escape: [0xfe03]});
expect(times_called).to.be.equal(1);
});
it('should pass through when a char modifier is not down', function() {
var times_called = 0;
EscapeModifiers(function(evt) {
expect(times_called).to.be.equal(0);
++times_called;
expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0x41, keysym: keysyms.lookup(0x42)});
})({type: 'keyup', keyId: 0x41, keysym: keysyms.lookup(0x42)});
expect(times_called).to.be.equal(1);
});
});
});
});
+1690
View File
File diff suppressed because it is too large Load Diff
+105
View File
@@ -0,0 +1,105 @@
// requires local modules: util
/* jshint expr: true */
var assert = chai.assert;
var expect = chai.expect;
describe('Utils', function() {
"use strict";
describe('Array instance methods', function () {
describe('push8', function () {
it('should push a byte on to the array', function () {
var arr = [1];
arr.push8(128);
expect(arr).to.deep.equal([1, 128]);
});
it('should only use the least significant byte of any number passed in', function () {
var arr = [1];
arr.push8(0xABCD);
expect(arr).to.deep.equal([1, 0xCD]);
});
});
describe('push16', function () {
it('should push two bytes on to the array', function () {
var arr = [1];
arr.push16(0xABCD);
expect(arr).to.deep.equal([1, 0xAB, 0xCD]);
});
it('should only use the two least significant bytes of any number passed in', function () {
var arr = [1];
arr.push16(0xABCDEF);
expect(arr).to.deep.equal([1, 0xCD, 0xEF]);
});
});
describe('push32', function () {
it('should push four bytes on to the array', function () {
var arr = [1];
arr.push32(0xABCDEF12);
expect(arr).to.deep.equal([1, 0xAB, 0xCD, 0xEF, 0x12]);
});
it('should only use the four least significant bytes of any number passed in', function () {
var arr = [1];
arr.push32(0xABCDEF1234);
expect(arr).to.deep.equal([1, 0xCD, 0xEF, 0x12, 0x34]);
});
});
});
describe('logging functions', function () {
beforeEach(function () {
sinon.spy(console, 'log');
sinon.spy(console, 'warn');
sinon.spy(console, 'error');
});
afterEach(function () {
console.log.restore();
console.warn.restore();
console.error.restore();
});
it('should use noop for levels lower than the min level', function () {
Util.init_logging('warn');
Util.Debug('hi');
Util.Info('hello');
expect(console.log).to.not.have.been.called;
});
it('should use console.log for Debug and Info', function () {
Util.init_logging('debug');
Util.Debug('dbg');
Util.Info('inf');
expect(console.log).to.have.been.calledWith('dbg');
expect(console.log).to.have.been.calledWith('inf');
});
it('should use console.warn for Warn', function () {
Util.init_logging('warn');
Util.Warn('wrn');
expect(console.warn).to.have.been.called;
expect(console.warn).to.have.been.calledWith('wrn');
});
it('should use console.error for Error', function () {
Util.init_logging('error');
Util.Error('err');
expect(console.error).to.have.been.called;
expect(console.error).to.have.been.calledWith('err');
});
});
// TODO(directxman12): test the conf_default and conf_defaults methods
// TODO(directxman12): test decodeUTF8
// TODO(directxman12): test the event methods (addEvent, removeEvent, stopEvent)
// TODO(directxman12): figure out a good way to test getPosition and getEventPosition
// TODO(directxman12): figure out how to test the browser detection functions properly
// (we can't really test them against the browsers, except for Gecko
// via PhantomJS, the default test driver)
// TODO(directxman12): figure out how to test Util.Flash
});
+480
View File
@@ -0,0 +1,480 @@
// requires local modules: websock, base64, util
// requires test modules: fake.websocket
/* jshint expr: true */
var assert = chai.assert;
var expect = chai.expect;
describe('Websock', function() {
"use strict";
describe('Queue methods', function () {
var sock;
var RQ_TEMPLATE = [0, 1, 2, 3, 4, 5, 6, 7];
beforeEach(function () {
sock = new Websock();
for (var i = RQ_TEMPLATE.length - 1; i >= 0; i--) {
sock.rQunshift8(RQ_TEMPLATE[i]);
}
});
describe('rQlen', function () {
it('should return the length of the receive queue', function () {
sock.set_rQi(0);
expect(sock.rQlen()).to.equal(RQ_TEMPLATE.length);
});
it("should return the proper length if we read some from the receive queue", function () {
sock.set_rQi(1);
expect(sock.rQlen()).to.equal(RQ_TEMPLATE.length - 1);
});
});
describe('rQpeek8', function () {
it('should peek at the next byte without poping it off the queue', function () {
var bef_len = sock.rQlen();
var peek = sock.rQpeek8();
expect(sock.rQpeek8()).to.equal(peek);
expect(sock.rQlen()).to.equal(bef_len);
});
});
describe('rQshift8', function () {
it('should pop a single byte from the receive queue', function () {
var peek = sock.rQpeek8();
var bef_len = sock.rQlen();
expect(sock.rQshift8()).to.equal(peek);
expect(sock.rQlen()).to.equal(bef_len - 1);
});
});
describe('rQunshift8', function () {
it('should place a byte at the front of the queue', function () {
sock.rQunshift8(255);
expect(sock.rQpeek8()).to.equal(255);
expect(sock.rQlen()).to.equal(RQ_TEMPLATE.length + 1);
});
});
describe('rQshift16', function () {
it('should pop two bytes from the receive queue and return a single number', function () {
var bef_len = sock.rQlen();
var expected = (RQ_TEMPLATE[0] << 8) + RQ_TEMPLATE[1];
expect(sock.rQshift16()).to.equal(expected);
expect(sock.rQlen()).to.equal(bef_len - 2);
});
});
describe('rQshift32', function () {
it('should pop four bytes from the receive queue and return a single number', function () {
var bef_len = sock.rQlen();
var expected = (RQ_TEMPLATE[0] << 24) +
(RQ_TEMPLATE[1] << 16) +
(RQ_TEMPLATE[2] << 8) +
RQ_TEMPLATE[3];
expect(sock.rQshift32()).to.equal(expected);
expect(sock.rQlen()).to.equal(bef_len - 4);
});
});
describe('rQshiftStr', function () {
it('should shift the given number of bytes off of the receive queue and return a string', function () {
var bef_len = sock.rQlen();
var bef_rQi = sock.get_rQi();
var shifted = sock.rQshiftStr(3);
expect(shifted).to.be.a('string');
expect(shifted).to.equal(String.fromCharCode.apply(null, RQ_TEMPLATE.slice(bef_rQi, bef_rQi + 3)));
expect(sock.rQlen()).to.equal(bef_len - 3);
});
it('should shift the entire rest of the queue off if no length is given', function () {
sock.rQshiftStr();
expect(sock.rQlen()).to.equal(0);
});
});
describe('rQshiftBytes', function () {
it('should shift the given number of bytes of the receive queue and return an array', function () {
var bef_len = sock.rQlen();
var bef_rQi = sock.get_rQi();
var shifted = sock.rQshiftBytes(3);
expect(shifted).to.be.an.instanceof(Array);
expect(shifted).to.deep.equal(RQ_TEMPLATE.slice(bef_rQi, bef_rQi + 3));
expect(sock.rQlen()).to.equal(bef_len - 3);
});
it('should shift the entire rest of the queue off if no length is given', function () {
sock.rQshiftBytes();
expect(sock.rQlen()).to.equal(0);
});
});
describe('rQslice', function () {
beforeEach(function () {
sock.set_rQi(0);
});
it('should not modify the receive queue', function () {
var bef_len = sock.rQlen();
sock.rQslice(0, 2);
expect(sock.rQlen()).to.equal(bef_len);
});
it('should return an array containing the given slice of the receive queue', function () {
var sl = sock.rQslice(0, 2);
expect(sl).to.be.an.instanceof(Array);
expect(sl).to.deep.equal(RQ_TEMPLATE.slice(0, 2));
});
it('should use the rest of the receive queue if no end is given', function () {
var sl = sock.rQslice(1);
expect(sl).to.have.length(RQ_TEMPLATE.length - 1);
expect(sl).to.deep.equal(RQ_TEMPLATE.slice(1));
});
it('should take the current rQi in to account', function () {
sock.set_rQi(1);
expect(sock.rQslice(0, 2)).to.deep.equal(RQ_TEMPLATE.slice(1, 3));
});
});
describe('rQwait', function () {
beforeEach(function () {
sock.set_rQi(0);
});
it('should return true if there are not enough bytes in the receive queue', function () {
expect(sock.rQwait('hi', RQ_TEMPLATE.length + 1)).to.be.true;
});
it('should return false if there are enough bytes in the receive queue', function () {
expect(sock.rQwait('hi', RQ_TEMPLATE.length)).to.be.false;
});
it('should return true and reduce rQi by "goback" if there are not enough bytes', function () {
sock.set_rQi(5);
expect(sock.rQwait('hi', RQ_TEMPLATE.length, 4)).to.be.true;
expect(sock.get_rQi()).to.equal(1);
});
it('should raise an error if we try to go back more than possible', function () {
sock.set_rQi(5);
expect(function () { sock.rQwait('hi', RQ_TEMPLATE.length, 6); }).to.throw(Error);
});
it('should not reduce rQi if there are enough bytes', function () {
sock.set_rQi(5);
sock.rQwait('hi', 1, 6);
expect(sock.get_rQi()).to.equal(5);
});
});
describe('flush', function () {
beforeEach(function () {
sock._websocket = {
send: sinon.spy()
};
});
it('should actually send on the websocket if the websocket does not have too much buffered', function () {
sock.maxBufferedAmount = 10;
sock._websocket.bufferedAmount = 8;
sock._sQ = [1, 2, 3];
var encoded = sock._encode_message();
sock.flush();
expect(sock._websocket.send).to.have.been.calledOnce;
expect(sock._websocket.send).to.have.been.calledWith(encoded);
});
it('should return true if the websocket did not have too much buffered', function () {
sock.maxBufferedAmount = 10;
sock._websocket.bufferedAmount = 8;
expect(sock.flush()).to.be.true;
});
it('should not call send if we do not have anything queued up', function () {
sock._sQ = [];
sock.maxBufferedAmount = 10;
sock._websocket.bufferedAmount = 8;
sock.flush();
expect(sock._websocket.send).not.to.have.been.called;
});
it('should not send and return false if the websocket has too much buffered', function () {
sock.maxBufferedAmount = 10;
sock._websocket.bufferedAmount = 12;
expect(sock.flush()).to.be.false;
expect(sock._websocket.send).to.not.have.been.called;
});
});
describe('send', function () {
beforeEach(function () {
sock.flush = sinon.spy();
});
it('should add to the send queue', function () {
sock.send([1, 2, 3]);
var sq = sock.get_sQ();
expect(sock.get_sQ().slice(sq.length - 3)).to.deep.equal([1, 2, 3]);
});
it('should call flush', function () {
sock.send([1, 2, 3]);
expect(sock.flush).to.have.been.calledOnce;
});
});
describe('send_string', function () {
beforeEach(function () {
sock.send = sinon.spy();
});
it('should call send after converting the string to an array', function () {
sock.send_string("\x01\x02\x03");
expect(sock.send).to.have.been.calledWith([1, 2, 3]);
});
});
});
describe('lifecycle methods', function () {
var old_WS;
before(function () {
old_WS = WebSocket;
});
var sock;
beforeEach(function () {
sock = new Websock();
WebSocket = sinon.spy();
WebSocket.OPEN = old_WS.OPEN;
WebSocket.CONNECTING = old_WS.CONNECTING;
WebSocket.CLOSING = old_WS.CLOSING;
WebSocket.CLOSED = old_WS.CLOSED;
});
describe('opening', function () {
it('should pick the correct protocols if none are given' , function () {
});
it('should open the actual websocket', function () {
sock.open('ws://localhost:8675', 'base64');
expect(WebSocket).to.have.been.calledWith('ws://localhost:8675', 'base64');
});
it('should fail if we try to use binary but do not support it', function () {
expect(function () { sock.open('ws:///', 'binary'); }).to.throw(Error);
});
it('should fail if we specified an array with only binary and we do not support it', function () {
expect(function () { sock.open('ws:///', ['binary']); }).to.throw(Error);
});
it('should skip binary if we have multiple options for encoding and do not support binary', function () {
sock.open('ws:///', ['binary', 'base64']);
expect(WebSocket).to.have.been.calledWith('ws:///', ['base64']);
});
// it('should initialize the event handlers')?
});
describe('closing', function () {
beforeEach(function () {
sock.open('ws://');
sock._websocket.close = sinon.spy();
});
it('should close the actual websocket if it is open', function () {
sock._websocket.readyState = WebSocket.OPEN;
sock.close();
expect(sock._websocket.close).to.have.been.calledOnce;
});
it('should close the actual websocket if it is connecting', function () {
sock._websocket.readyState = WebSocket.CONNECTING;
sock.close();
expect(sock._websocket.close).to.have.been.calledOnce;
});
it('should not try to close the actual websocket if closing', function () {
sock._websocket.readyState = WebSocket.CLOSING;
sock.close();
expect(sock._websocket.close).not.to.have.been.called;
});
it('should not try to close the actual websocket if closed', function () {
sock._websocket.readyState = WebSocket.CLOSED;
sock.close();
expect(sock._websocket.close).not.to.have.been.called;
});
it('should reset onmessage to not call _recv_message', function () {
sinon.spy(sock, '_recv_message');
sock.close();
sock._websocket.onmessage(null);
try {
expect(sock._recv_message).not.to.have.been.called;
} finally {
sock._recv_message.restore();
}
});
});
describe('event handlers', function () {
beforeEach(function () {
sock._recv_message = sinon.spy();
sock.on('open', sinon.spy());
sock.on('close', sinon.spy());
sock.on('error', sinon.spy());
sock.open('ws://');
});
it('should call _recv_message on a message', function () {
sock._websocket.onmessage(null);
expect(sock._recv_message).to.have.been.calledOnce;
});
it('should copy the mode over upon opening', function () {
sock._websocket.protocol = 'cheese';
sock._websocket.onopen();
expect(sock._mode).to.equal('cheese');
});
it('should assume base64 if no protocol was available on opening', function () {
sock._websocket.protocol = null;
sock._websocket.onopen();
expect(sock._mode).to.equal('base64');
});
it('should call the open event handler on opening', function () {
sock._websocket.onopen();
expect(sock._eventHandlers.open).to.have.been.calledOnce;
});
it('should call the close event handler on closing', function () {
sock._websocket.onclose();
expect(sock._eventHandlers.close).to.have.been.calledOnce;
});
it('should call the error event handler on error', function () {
sock._websocket.onerror();
expect(sock._eventHandlers.error).to.have.been.calledOnce;
});
});
after(function () {
WebSocket = old_WS;
});
});
describe('WebSocket Receiving', function () {
var sock;
beforeEach(function () {
sock = new Websock();
});
it('should support decoding base64 string data to add it to the receive queue', function () {
var msg = { data: Base64.encode([1, 2, 3]) };
sock._mode = 'base64';
sock._recv_message(msg);
expect(sock.rQshiftStr(3)).to.equal('\x01\x02\x03');
});
it('should support adding binary Uint8Array data to the receive queue', function () {
var msg = { data: new Uint8Array([1, 2, 3]) };
sock._mode = 'binary';
sock._recv_message(msg);
expect(sock.rQshiftStr(3)).to.equal('\x01\x02\x03');
});
it('should call the message event handler if present', function () {
sock._eventHandlers.message = sinon.spy();
var msg = { data: Base64.encode([1, 2, 3]) };
sock._mode = 'base64';
sock._recv_message(msg);
expect(sock._eventHandlers.message).to.have.been.calledOnce;
});
it('should not call the message event handler if there is nothing in the receive queue', function () {
sock._eventHandlers.message = sinon.spy();
var msg = { data: Base64.encode([]) };
sock._mode = 'base64';
sock._recv_message(msg);
expect(sock._eventHandlers.message).not.to.have.been.called;
});
it('should compact the receive queue', function () {
// NB(sross): while this is an internal implementation detail, it's important to
// test, otherwise the receive queue could become very large very quickly
sock._rQ = [0, 1, 2, 3, 4, 5];
sock.set_rQi(6);
sock._rQmax = 3;
var msg = { data: Base64.encode([1, 2, 3]) };
sock._mode = 'base64';
sock._recv_message(msg);
expect(sock._rQ.length).to.equal(3);
expect(sock.get_rQi()).to.equal(0);
});
it('should call the error event handler on an exception', function () {
sock._eventHandlers.error = sinon.spy();
sock._eventHandlers.message = sinon.stub().throws();
var msg = { data: Base64.encode([1, 2, 3]) };
sock._mode = 'base64';
sock._recv_message(msg);
expect(sock._eventHandlers.error).to.have.been.calledOnce;
});
});
describe('Data encoding', function () {
before(function () { FakeWebSocket.replace(); });
after(function () { FakeWebSocket.restore(); });
describe('as binary data', function () {
var sock;
beforeEach(function () {
sock = new Websock();
sock.open('ws://', 'binary');
sock._websocket._open();
});
it('should convert the send queue into an ArrayBuffer', function () {
sock._sQ = [1, 2, 3];
var res = sock._encode_message(); // An ArrayBuffer
expect(new Uint8Array(res)).to.deep.equal(new Uint8Array(res));
});
it('should properly pass the encoded data off to the actual WebSocket', function () {
sock.send([1, 2, 3]);
expect(sock._websocket._get_sent_data()).to.deep.equal([1, 2, 3]);
});
});
describe('as Base64 data', function () {
var sock;
beforeEach(function () {
sock = new Websock();
sock.open('ws://', 'base64');
sock._websocket._open();
});
it('should convert the send queue into a Base64-encoded string', function () {
sock._sQ = [1, 2, 3];
expect(sock._encode_message()).to.equal(Base64.encode([1, 2, 3]));
});
it('should properly pass the encoded data off to the actual WebSocket', function () {
sock.send([1, 2, 3]);
expect(sock._websocket._get_sent_data()).to.deep.equal([1, 2, 3]);
});
});
});
});
+2
View File
@@ -37,6 +37,8 @@
<script src="../include/util.js"></script>
<script src="../include/webutil.js"></script>
<script src="../include/base64.js"></script>
<script src="../include/keysymdef.js"></script>
<script src="../include/keyboard.js"></script>
<script src="../include/input.js"></script>
<script src="../include/display.js"></script>
<script>
+11 -5
View File
@@ -2,7 +2,6 @@
<html>
<head>
<title>VNC Performance Benchmark</title>
<link rel="stylesheet" href="../include/base.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>
// 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 = {},
@@ -188,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++) {
@@ -196,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>
+12 -8
View File
@@ -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>
+6 -7
View File
@@ -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.
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+12 -2
View File
@@ -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 ;;
@@ -66,12 +70,18 @@ netstat -ltn | grep -qs "${PORT} .*LISTEN" \
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
+22 -22
View File
@@ -19,19 +19,19 @@
'''
Websocket proxy that is compatible with Openstack Nova.
Leverages wsproxy.py by Joel Martin
Leverages websockify by Joel Martin
'''
import Cookie
from oslo.config import cfg
import socket
import sys
import wsproxy
import websockify
from nova import config
from nova import context
from nova import flags
from nova import utils
from nova.openstack.common import cfg
from nova.openstack.common import rpc
@@ -64,18 +64,18 @@ opts = [
default=6080,
help='Port on which to listen for incoming requests'),
]
FLAGS = flags.FLAGS
FLAGS.register_cli_opts(opts)
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(FLAGS)
rpc.register_opts(CONF)
class NovaWebSocketProxy(wsproxy.WebSocketProxy):
class NovaWebSocketProxy(websockify.WebSocketProxy):
def __init__(self, *args, **kwargs):
wsproxy.WebSocketProxy.__init__(self, *args, **kwargs)
websockify.WebSocketProxy.__init__(self, *args, **kwargs)
def new_client(self):
"""
@@ -128,23 +128,23 @@ class NovaWebSocketProxy(wsproxy.WebSocketProxy):
if __name__ == '__main__':
if FLAGS.ssl_only and not os.path.exists(FLAGS.cert):
parser.error("SSL only and %s not found" % FLAGS.cert)
if CONF.ssl_only and not os.path.exists(CONF.cert):
parser.error("SSL only and %s not found" % CONF.cert)
# Setup flags
flags.parse_args(sys.argv)
config.parse_args(sys.argv)
# Create and start the NovaWebSockets proxy
server = NovaWebSocketProxy(listen_host=FLAGS.novncproxy_host,
listen_port=FLAGS.novncproxy_port,
source_is_ipv6=FLAGS.source_is_ipv6,
verbose=FLAGS.verbose,
cert=FLAGS.cert,
key=FLAGS.key,
ssl_only=FLAGS.ssl_only,
daemon=FLAGS.daemon,
record=FLAGS.record,
web=FLAGS.web,
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',
+97
View File
@@ -0,0 +1,97 @@
// Utility to parse keysymdef.h to produce mappings from Unicode codepoints to keysyms
"use strict";
var fs = require('fs');
var show_help = process.argv.length === 2;
var use_keynames = false;
var filename;
for (var i = 2; i < process.argv.length; ++i) {
switch (process.argv[i]) {
case "--help":
case "-h":
show_help = true;
break;
case "--debug-names":
case "-d":
use_keynames = true;
break;
case "--file":
case "-f":
default:
filename = process.argv[i];
}
}
if (!filename) {
show_help = true;
console.log("Error: No filename specified\n");
}
if (show_help) {
console.log("Parses a *nix keysymdef.h to generate Unicode code point mappings");
console.log("Usage: node parse.js [options] filename:");
console.log(" -h [ --help ] Produce this help message");
console.log(" -d [ --debug-names ] Preserve keysym names for debugging (Increases file size by ~40KB)");
console.log(" filename The keysymdef.h file to parse");
return;
}
// Set this to false to omit key names from the generated keysymdef.js
// This reduces the file size by around 40kb, but may hinder debugging
var buf = fs.readFileSync(filename);
var str = buf.toString('utf8');
var re = /^\#define XK_([a-zA-Z_0-9]+)\s+0x([0-9a-fA-F]+)\s*(\/\*\s*(.*)\s*\*\/)?\s*$/m;
var arr = str.split('\n');
var keysyms = {};
var codepoints = {};
for (var i = 0; i < arr.length; ++i) {
var result = re.exec(arr[i]);
if (result){
var keyname = result[1];
var keysym = parseInt(result[2], 16);
var remainder = result[3];
keysyms[keysym] = keyname;
var unicodeRes = /U\+([0-9a-fA-F]+)/.exec(remainder);
if (unicodeRes) {
var unicode = parseInt(unicodeRes[1], 16);
if (!codepoints[unicode]){
codepoints[unicode] = keysym;
}
}
else {
console.log("no unicode codepoint found:", arr[i]);
}
}
else {
console.log("line is not a keysym:", arr[i]);
}
}
var out = "// This file describes mappings from Unicode codepoints to the keysym values\n" +
"// (and optionally, key names) expected by the RFB protocol\n" +
"// How this file was generated:\n" +
"// " + process.argv.join(" ") + "\n" +
"var keysyms = (function(){\n" +
" \"use strict\";\n" +
" var keynames = {keysyms};\n" +
" var codepoints = {codepoints};\n" +
"\n" +
" function lookup(k) { return k ? {keysym: k, keyname: keynames ? keynames[k] : k} : undefined; }\n" +
" return {\n" +
" fromUnicode : function(u) { return lookup(codepoints[u]); },\n" +
" lookup : lookup\n" +
" };\n" +
"})();\n";
out = out.replace('{keysyms}', use_keynames ? JSON.stringify(keysyms) : "null");
out = out.replace('{codepoints}', JSON.stringify(codepoints));
fs.writeFileSync("keysymdef.js", out);
+3 -3
View File
@@ -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 \
+619 -519
View File
File diff suppressed because it is too large Load Diff
+306 -120
View File
@@ -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,109 +287,40 @@ 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.CClose(1000, "Target closed")
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)
cqueue.append(buf)
self.traffic("{")
if self.client in outs:
# Send queued target data to the client
c_pend = self.send_frames(cqueue)
cqueue = []
if self.client in ins:
# Receive client data, decode it, and queue for target
bufs, closed = self.recv_frames()
tqueue.extend(bufs)
if closed:
# TODO: What about blocking on client socket?
raise self.CClose(closed['code'], closed['reason'])
def websockify_init():
logger_init()
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",
@@ -230,22 +330,42 @@ def websockify_init():
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:]
@@ -254,32 +374,98 @@ def websockify_init():
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, 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, 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()
+77 -64
View File
@@ -1,12 +1,16 @@
<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.1//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile11.dtd">
<!DOCTYPE html>
<html>
<head>
<!--
noVNC example: simple example using default UI
Copyright (C) 2012 Joel Martin
noVNC is licensed under the LGPL-3 (see LICENSE.txt)
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
-->
<title>noVNC</title>
@@ -19,7 +23,7 @@
<!-- 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 names="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<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 -->
@@ -39,70 +43,77 @@
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>
<body>
<div id="noVNC-control-bar">
<!--noVNC Mobile Device only Buttons-->
<div class="noVNC-buttons-left">
<input type="image" src="images/drag.png"
<input type="image" alt="viewport drag" src="images/drag.png"
id="noVNC_view_drag_button" class="noVNC_status_button"
title="Move/Drag Viewport"
onclick="UI.setViewDrag();">
title="Move/Drag Viewport">
<div id="noVNC_mobile_buttons">
<input type="image" src="images/mouse_none.png"
id="noVNC_mouse_button0" class="noVNC_status_button"
onclick="UI.setMouseButton(1);">
<input type="image" src="images/mouse_left.png"
id="noVNC_mouse_button1" class="noVNC_status_button"
onclick="UI.setMouseButton(2);">
<input type="image" src="images/mouse_middle.png"
id="noVNC_mouse_button2" class="noVNC_status_button"
onclick="UI.setMouseButton(4);">
<input type="image" src="images/mouse_right.png"
id="noVNC_mouse_button4" class="noVNC_status_button"
onclick="UI.setMouseButton(0);">
<input type="image" src="images/keyboard.png"
<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"
onclick="UI.showKeyboard()"/>
<input type="email"
autocapitalize="off" autocorrect="off"
id="keyboardinput" class="noVNC_status_button"
onKeyDown="onKeyDown(event);" onblur="UI.keyInputBlur();"/>
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" src="images/ctrlaltdel.png"
id="sendCtrlAltDelButton" class="noVNC_status_button"
title="Send Ctrl-Alt-Del"
onclick="UI.sendCtrlAltDel();" />
<input type="image" src="images/clipboard.png"
<input type="image" alt="Ctrl+Alt+Del" src="images/ctrlaltdel.png"
id="sendCtrlAltDelButton" class="noVNC_status_button"
title="Send Ctrl-Alt-Del" />
<input type="image" 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"
onclick="UI.toggleClipboardPanel();" />
<input type="image" src="images/settings.png"
title="Clipboard" />
<input type="image" alt="Settings" src="images/settings.png"
id="settingsButton" class="noVNC_status_button"
title="Settings"
onclick="UI.toggleSettingsPanel();" />
<input type="image" src="images/connect.png"
title="Settings" />
<input type="image" alt="Connect" src="images/connect.png"
id="connectButton" class="noVNC_status_button"
title="Connect"
onclick="UI.toggleConnectPanel()" />
<input type="image" src="images/disconnect.png"
title="Connect" />
<input type="image" alt="Disconnect" src="images/disconnect.png"
id="disconnectButton" class="noVNC_status_button"
title="Disconnect"
onclick="UI.disconnect()" />
title="Disconnect" />
</div>
<!-- Description Panel -->
<!-- Shown by default when hosted at for kanaka.github.com -->
<div id="noVNC_description" style="display:none;" class="">
<div id="noVNC_description" class="">
noVNC is a browser based VNC client implemented using HTML5 Canvas
and WebSockets. You will either need a VNC server with WebSockets
support (such as <a href="http://libvncserver.sourceforge.net/">libvncserver</a>)
@@ -113,25 +124,34 @@
and <a href="http://kanaka.github.com/noVNC">website</a>
for more information.
<br />
<input type="button" value="Close"
onclick="UI.toggleConnectPanel();">
<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
onfocus="UI.displayBlur();" onblur="UI.displayFocus();"
onchange="UI.clipSend();">
<textarea id="noVNC_clipboard_text" rows=5>
</textarea>
<br />
<input id="noVNC_clipboard_clear_button" type="button"
value="Clear" onclick="UI.clipClear();">
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" onmouseover="UI.displayBlur();"
onmouseout="UI.displayFocus();">
<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>
@@ -139,7 +159,6 @@
<li><input id="noVNC_clip" type="checkbox"> Clip to Window</li>
<li><input id="noVNC_shared" type="checkbox"> Shared Mode</li>
<li><input id="noVNC_view_only" type="checkbox"> View Only</li>
<li><input id="noVNC_connectTimeout" type="input"> Connect Timeout (s)</li>
<li><input id="noVNC_path" type="input" value="websockify"> Path</li>
<li><input id="noVNC_repeaterID" type="input" value=""> Repeater ID</li>
<hr>
@@ -156,8 +175,7 @@
</select></label>
</li>
<hr>
<li><input type="button" id="noVNC_apply" value="Apply"
onclick="UI.settingsApply()"></li>
<li><input type="button" id="noVNC_apply" value="Apply"></li>
</ul>
</span>
</div>
@@ -168,7 +186,7 @@
<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" onclick="UI.connect();"></li>
<li><input id="noVNC_connect_button" type="button" value="Connect"></li>
</ul>
</div>
@@ -178,10 +196,6 @@
<div id="noVNC_screen">
<div id="noVNC_screen_pad"></div>
<div id="noVNC_status_bar" class="noVNC_status_bar">
<div id="noVNC_status">Loading</div>
</div>
<h1 id="noVNC_logo"><span>no</span><br />VNC</h1>
<!-- HTML5 Canvas -->
@@ -192,9 +206,8 @@
</div>
</div>
<script src="include/util.js"></script>
<script src="include/ui.js"></script>
<script>
window.onload = UI.load;
</script>
</body>
</html>
+99 -20
View File
@@ -1,33 +1,66 @@
<!DOCTYPE html>
<html>
<!--
<head>
<!--
noVNC example: simple example using default UI
Copyright (C) 2012 Joel Martin
noVNC is licensed under the LGPL-3 (see LICENSE.txt)
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/base.css" title="plain">
<!--
<script type='text/javascript'
src='http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js'></script>
-->
<script src="include/vnc.js"></script>
</head>
<title>noVNC</title>
<body style="margin: 0px;">
<div id="noVNC_screen">
<meta charset="utf-8">
<!-- 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">
<!-- 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" title="plain">
<!--
<script type='text/javascript'
src='http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js'></script>
-->
<script src="include/util.js"></script>
</head>
<body style="margin: 0px;">
<div id="noVNC_screen">
<div id="noVNC_status_bar" class="noVNC_status_bar" style="margin-top: 0px;">
<table border=0 width="100%"><tr>
<td><div id="noVNC_status">Loading</div></td>
<td><div id="noVNC_status" style="position: relative; height: auto;">
Loading
</div></td>
<td width="1%"><div id="noVNC_buttons">
<input type=button value="Send CtrlAltDel"
id="sendCtrlAltDelButton">
<span id="noVNC_xvp_buttons">
<input type=button value="Shutdown"
id="xvpShutdownButton">
<input type=button value="Reboot"
id="xvpRebootButton">
<input type=button value="Reset"
id="xvpResetButton">
</span>
</div></td>
</tr></table>
</div>
@@ -41,6 +74,11 @@
/*global window, $, Util, RFB, */
"use strict";
// 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 rfb;
function passwordRequired(rfb) {
@@ -61,6 +99,18 @@
rfb.sendCtrlAltDel();
return false;
}
function xvpShutdown() {
rfb.xvpShutdown();
return false;
}
function xvpReboot() {
rfb.xvpReboot();
return false;
}
function xvpReset() {
rfb.xvpReset();
return false;
}
function updateState(rfb, state, oldstate, msg) {
var s, sb, cad, level;
s = $D('noVNC_status');
@@ -75,8 +125,12 @@
default: level = "warn"; break;
}
if (state === "normal") { cad.disabled = false; }
else { cad.disabled = true; }
if (state === "normal") {
cad.disabled = false;
} else {
cad.disabled = true;
xvpInit(0);
}
if (typeof(msg) !== 'undefined') {
sb.setAttribute("class", "noVNC_status_" + level);
@@ -84,17 +138,42 @@
}
}
window.onload = function () {
function xvpInit(ver) {
var xvpbuttons;
xvpbuttons = $D('noVNC_xvp_buttons');
if (ver >= 1) {
xvpbuttons.style.display = 'inline';
} else {
xvpbuttons.style.display = 'none';
}
}
window.onscriptsload = function () {
var host, port, password, path, token;
$D('sendCtrlAltDelButton').style.display = "inline";
$D('sendCtrlAltDelButton').onclick = sendCtrlAltDel;
$D('xvpShutdownButton').onclick = xvpShutdown;
$D('xvpRebootButton').onclick = xvpReboot;
$D('xvpResetButton').onclick = xvpReset;
WebUtil.init_logging(WebUtil.getQueryVar('logging', 'warn'));
document.title = unescape(WebUtil.getQueryVar('title', 'noVNC'));
// By default, use the host and port of server that served this file
host = WebUtil.getQueryVar('host', window.location.hostname);
port = WebUtil.getQueryVar('port', window.location.port);
// if port == 80 (or 443) then it won't be present and should be
// set manually
if (!port) {
if (window.location.protocol.substring(0,5) == 'https') {
port = 443;
}
else if (window.location.protocol.substring(0,4) == 'http') {
port = 80;
}
}
// If a token variable is passed in, set the parameter in a cookie.
// This is used by nova-novncproxy.
token = WebUtil.getQueryVar('token', null);
@@ -119,7 +198,8 @@
'local_cursor': WebUtil.getQueryVar('cursor', true),
'shared': WebUtil.getQueryVar('shared', true),
'view_only': WebUtil.getQueryVar('view_only', false),
'updateState': updateState,
'onUpdateState': updateState,
'onXvpInit': xvpInit,
'onPasswordRequired': passwordRequired});
rfb.connect(host, port, password, path);
};
@@ -127,4 +207,3 @@
</body>
</html>