140 Commits

Author SHA1 Message Date
Solly Ross 5230ab6764 Release 0.6.0
This bumps the release version to 0.6.0
2016-07-01 15:42:14 -04:00
Solly Ross 67685d0700 Fix missing mistyped setTimeout handler in UI
This commit fixes a mistyped setTimeout handler that would result
in the desired function not getting called (due to a missing set
of parentheses).  It also removes some uncessary anonymous functions,
and just passes the function objects directly to setTimeout.
2016-07-01 15:42:14 -04:00
Joel Martin 15e733f533 Clarify that utils/launch.sh is MPL-2.0
Also, note in the top-level license file that the default noVNC
license for files that are not explicitly marked or mentioned in
the LICENSE.txt file are by default MPL-2.0 licensed.
2016-06-13 10:22:43 -05:00
samhed b2cdd55859 Proper error handling for tight filters
Don't throw an exception when we encounter an unsupported tight
subencoding.
2016-06-10 17:15:42 +02:00
samhed 3daa86cbae Update vnc_auto after the screen namechange
Commit 553864e858 changed the name of
noVNC_screen to noVNC_container, vnc_auto.html was not updated
accordingly. Fixes #621
2016-06-08 16:02:37 +02:00
samhed 5fd3f88e50 Merge branch 'ui-cleanup' of https://github.com/kanaka/noVNC 2016-06-03 10:46:05 +02:00
Samuel ae11605141 Split the setDesktopSize function (#618)
In order to follow the surrounding coding-standards, the
setDesktopSize client message is split from the public function which
now is called requestDesktopSize().
2016-06-02 22:37:52 +02:00
Samuel Mannehed a1dbbcc1a7 Consistent closing tags for void elements 2016-05-27 16:14:25 +02:00
Samuel Mannehed 3f2c25a60f Use a consistent naming convention for elements
* Element names we use "_" as word-delimiter, not "-"
* Element names use less camel-case
* Element names end with the type
* Element names always start with noVNC_
2016-05-27 16:14:25 +02:00
Samuel Mannehed 682fd02be6 Change the names of settings-elements
This change was made in order to make it easier to distinguish
settings-elements from other elements. One example of the problem
that was solved is the two elements "noVNC_clip" and
"noVNC_clipboard" where the first is the setting for clipping mode.
That element was now renamed to "noVNC_setting_clip".
2016-05-27 16:12:32 +02:00
samhed a20a898765 Timeouts should always use anonymous functions
While you CAN pass functions in strings, it is not the correct way
of doing it. The browsers are just being nice.
2016-05-27 16:12:31 +02:00
samhed 4d26f58e0d Change name of clipboard functions
There were a high risk of confusion between clipboard functions and
clipping functions.
2016-05-27 16:11:47 +02:00
samhed 95dd60011c Group together related functions and add dividers
Dividers between the different parts of the UI has been added in
order to easier get an overview.
2016-05-12 16:56:50 +02:00
samhed afcf031a68 Send the xvp keys from anonymous functions 2016-05-12 16:56:50 +02:00
samhed fdf21468d3 Renamed and moved keyboard and mouse functions 2016-05-12 16:56:50 +02:00
samhed 9e45354efc Renamed and moved updateXvpButton 2016-05-12 16:56:50 +02:00
samhed ab81ddf5d4 Move the connect and disconnect functions 2016-05-12 16:56:50 +02:00
samhed e00698fef8 Clarify comments and variable names for viewDrag 2016-05-12 16:56:50 +02:00
samhed cd611a5326 Renamed showExtraKeys to toggleExtraKeys 2016-05-12 16:56:50 +02:00
samhed 4e471b5b55 Moved and split the popupStatus function
Now if popupStatus is called twice it will refresh the duration of
the popup. If you want to wait 1.5 seconds and close the popup you
can click the popup itself.
2016-05-12 16:56:50 +02:00
samhed 45c70c9e23 Moved and changed order of the setting-functions 2016-05-12 16:56:50 +02:00
samhed 0bd2cbacaa Remove whitespace from function definitions 2016-05-12 16:56:50 +02:00
samhed 29475d7728 Moved UpdateState and UpdateVisualState 2016-05-12 16:56:49 +02:00
samhed 777cb7a0c5 Cleanup for the resize related functions
Renamed functions, added clarifying comments and moved the resize
related functions closer to the other viewport functions.
2016-05-12 16:56:49 +02:00
samhed 553864e858 Switch names between the container and the screen
The noVNC_container now contains the logo and the screen. While the
noVNC_screen in turn contains the canvas.
2016-05-12 16:56:49 +02:00
samhed f52105bc88 Add fallback value for devicePixelRatio
In IE 10 for example, devicePixelRatio doesn't exist which caused the
code to fail by setting the thresholds to zero.
2016-05-12 16:43:19 +02:00
Samuel Mannehed 057cfc7cb4 Add missing parenthesis (#600) 2016-04-30 04:26:30 +02:00
Samuel Mannehed 12ae8b3d50 Respect the threshold in the viewdrag test (#600) 2016-04-30 04:08:34 +02:00
Samuel Mannehed 3120c9b5ec Merge branch 'SirCmpwn-fix-send-on-disconnect'
Updated patch to use the readyState constant even in the tests.
2016-04-30 02:34:20 +02:00
Drew DeVault cf0623fffa Fix failing test
It only makes sense to send data on a websocket if the readyState is
equal to 1.
2016-04-30 02:28:45 +02:00
Drew DeVault f6a29ddeeb Fix occasional error with sending while readyState !== 1
Under certain conditions, noVNC would attempt to flush the web socket
while it was disconnected, before the disconnected state was picked up.
This casues noVNC to crash ungracefully and the parent window is not
notified - leading to no chance at recovery without a page refresh.
2016-04-30 02:26:17 +02:00
samhed 32df3fdbe1 Add a threshold for viewport dragging (#600) 2016-04-28 17:41:48 +02:00
Jonathan Bennett 27e77d468f Adds tap-to-click in viewport drag mode (#600) 2016-04-28 13:46:40 +02:00
samhed f620259bc5 Force clipping mode in Safari on all platforms 2016-04-25 16:38:23 +02:00
samhed 529c64e103 Re-arrange global UI variables 2016-04-25 16:35:49 +02:00
Solly Ross 8b0a0f6637 Merge pull request #602 from oneru/typo-fix
Adds missed line from PR 597
2016-04-13 15:02:48 -04:00
Jonathan Bennett 3c9558444c Adds missed line from PR 597 2016-04-13 12:53:37 -05:00
Solly Ross bf0ce0ba3c Merge pull request #597 from oneru/master
Handle missing leading slash in elem.pathname
2016-04-12 17:32:43 -04:00
Jonathan Bennett c8294760b1 Handle missing leading slash in elem.pathname
IE11 with compat mode turned off has been observed displaying this behavior.  This commit checks for and corrects this broken behavior.

Fixes #591
2016-04-12 11:00:28 -05:00
Solly Ross b403cb92fb Merge pull request #582 from kanaka/bug/render-queue-corruption
Make sure to copy arrays when using render queue
2016-02-24 16:52:55 -05:00
Solly Ross 7bc383e8b6 Make sure to copy arrays when using render queue
This commit ensures that input arrays are copied to new storage when
they are pushed onto the render queue.  This ensures that they are
not overwritten before they are eventually used.

Fixes #571
2016-02-24 16:29:34 -05:00
Solly Ross 670dbddb54 Merge pull request #568 from kanaka/refactor/remove-refs-to-jsunzip
Cleanup/fixup unused code

Remove jsunzip, remove unused vars, fix vnc_perf.html
2016-01-06 16:01:50 -05:00
Solly Ross 317042f8db Merge branch 'feature/token-in-url'
Injects token into the path element if it's passed to
the noVNC page.

Closes #537
Fixes #536
2016-01-06 15:57:04 -05:00
Miguel Xavier Penha Neto c55f05f619 Pass token into the path variable
If a token is already present in the path, the new variable
is ignored.  In order to properly manipulate the path,
a new method, `WebUtil.injectParamIfMissing` was introduced.

Fixes #536

[@directxman12: fix up path manipulation logic]
2016-01-06 15:56:28 -05:00
Solly Ross 28646d978f Merge pull request #547 from pigshell/hashargs
Add hash fragment as an optional method to supply config variables.

Closes #544
2016-01-06 14:22:24 -05:00
Solly Ross 4ee55e0126 Fixed vnc_perf.html
This commit updates vnc_perf.html so that it works with the current
version of noVNC.  It also introduces a utility to convert noVNC session
recordings recorded in base64-mode to binary-mode recordings, since noVNC
no longer supports base64-mode.

Fixes #479.
2016-01-06 13:32:05 -05:00
Solly Ross 464944844f Remove unused variables in the TIGHT server init
This commit removes some unused variables in the TIGHT server init
section of the server init handler, replacing them with documenting
comments and calls to rQskipBytes.

Fixes #440.
2016-01-06 11:18:47 -05:00
Solly Ross 0252c7f766 Remove remaining references to jsunzip.js
This commit removes remaining references to jsunzip.js,
which is no longer actually used in noVNC.

Closes #436.
2016-01-06 11:10:26 -05:00
Solly Ross 7e4475fa92 Merge pull request #565 from kanaka/bug/dynamic-rq-resize
Resize Receive Queue to Fit Incoming Messages
2016-01-05 14:28:01 -05:00
Solly Ross 40037b6a29 On-Demand Dynamic Receive Queue Resizing
This commit causes the receive queue to dynamically
resize to fit incoming messages.

Fixes #557
2015-12-22 16:05:33 -05:00
Solly Ross c8f14d175b Disable copyWithin Use in Websock.js
the `copyWithin` function of typed arrays has performance issues
in some versions of Chromium, and doesn't doesn't have enough
of a performance impact to justify leaving it enabled.
2015-12-22 13:51:30 -05:00
Joel Martin b2a813dc73 Merge pull request #555 from maditnerd/patch-1
Small typo in launch.sh
2015-11-23 22:32:31 -05:00
Sarrailh Rémi 54e835eeac Small typo in launch.sh 2015-11-23 13:08:05 +01:00
Ganesh Varadarajan 494b407a0a Add hash fragment as an optional method to supply config variables.
Any config variable like host, port, password, token may be
specified either in the query string (like now), or in the URL hash
fragment. In case a given variable is present in both, the value in the
fragment takes precedence. Supplying variables in the fragment avoids
leaking them to the web server hosting the noVNC viewer HTML.
2015-10-23 18:54:08 +05:30
Solly 6a90803feb Merge pull request #543 from DvdGiessen/fix/vnc_sendpassword_auth
Fixes VNC standard auth using onPasswordRequired callback
2015-10-16 11:08:04 -04:00
Daniël van de Giessen 3e3df4dbb1 Added return to correctly wait for password input before proceeding. Fixes #542 2015-10-16 16:46:10 +02:00
Solly a0e7ab43dc Merge pull request #532 from kanaka/bug/pako-issue
Make sure Pako always has enough room
2015-09-23 11:29:43 -04:00
Solly Ross c802d93189 Make sure Pako always has enough room
Previously, we used a fixed chunkSize of 100KiB for Pako's output
buffer.  Using a hardcoded size caused issues, since Pako would assume
we wanted to use multiple chunks, and we didn't deal with this.  Now,
`Inflator#inflate()` takes a new `expected` argument, which indicates
the expected output size.  If this is bigger than the current chunkSize,
Inflator allocates a new output buffer that's big enough to hold the
output.

Fixes #531
2015-09-23 09:53:00 -04:00
samhed 045d922406 Moved resizeTimout to the UI object, no reason to have it outside anymore.. 2015-09-22 14:59:44 +02:00
Solly 40b35fa20d Merge pull request #524 from kanaka/bug/tight-buffer-overread
Fix buffer over-reads in handle_tight
2015-08-27 18:46:26 -04:00
Solly Ross 89bdc8ce48 Fix buffer over-reads in handle_tight
For performance reasons, the `handle_tight` function skips the
use of the receive queue API and uses the raw receive queue directly.
Because of the way that typed array receive queue gets reused, this
introduced the potential for buffer over-reads.

To address this, a new function, `rQwhole`, was introduced.
`rQwhole` simply returns a new view into the receive queue that
starts at 0 and ends at the current recorded end of the queue.

`handle_tight` now makes use of this function.

Fixes #522
2015-08-26 14:31:23 -04:00
Solly Ross a369a80c24 Fix bug in non-true-color code
There was a bug caused by 38781d931e
which prevented color map look-ups sent by rfb.js from working properly,
since display.js expected a single-item array, and rfb.js sent just them
item value itself (a number) instead.  This fixes that, and tweaks the
corresponding test to match that behavior.
2015-08-24 19:34:30 -04:00
Solly Ross bb180145c6 Tests: Fixed bug in displayed assertion
This `displayed` assertion had a bug that was causing it to not
actually check anything (it was using obj.length instead of
data_cl.length).  This fixes that.
2015-08-24 19:30:44 -04:00
Solly Ross 340290fa1d UI: Fix typo preventing reconnect after password
There was a typo in ui.js preventing the use of the connect button
after a password was entered.
2015-08-24 16:41:00 -04:00
Solly abf2b09ea7 Merge pull request #488 from kanaka/feature/more-perf-improvements
Performance Improvements
2015-08-14 11:56:57 -04:00
samhed a825582196 Only work with integers when panning to avoid getting a blurry image.
Also disable image-smoothing to avoid bugs seen on Android which were
also causing a blurry image while panning.
2015-08-14 17:02:00 +02:00
Solly Ross f00193e08f Skip unnecessary render queue object creation
This commit skips object creation for the render queue when not
needed.  Instead of pushing an object onto the queue, and then
immediately running the result, you call the function directly.
Then, if the render queue is not empty, an object is created and
pushed onto the queue.  Otherwise, the functionality is just run
directly.
2015-08-06 14:47:03 -04:00
Solly Ross b0b5fc55e1 Fix multi-line assertion messages in test runner
This commit prevents multi-line error messages from being
truncated in the local test runner ('tests/run_from_console.js').
2015-08-06 14:47:03 -04:00
Solly Ross 07f514d887 Allow the use of the PhantomJS remote debugger
This commit adds the '--debugger <port>' option, which triggers
the PhantomJS remote debugger.  The initial output of the terminal
when running the debugger gives more information on how to use
it.
2015-08-06 14:47:03 -04:00
Solly Ross 9ff86fb718 Use Typed Arrays for the send queue
This commit converts the send queue to use typed arrays, and converts
message creation functions in 'rfb.js' to create messages directly into
the socket's send queue.  This commit also removes the separate mouse array,
which is no longer needed.
2015-08-06 14:47:03 -04:00
Solly Ross d1800d0960 Avoid Creating Small Objects Frequently
Creating lots of small objects frequently can drastically decrease
performance.  This commit introduces three fixes which avoid this:

- Use a preallocated palette and indexed-to-rgb destination Typed Array
  (the destination typed array is currently allocated at `4 * width *
  height`).

- Inline `getTightCLength`, which returned a two-item array.

- Pass RGBX data directly in a Typed Array to the Display, which
  avoids an extra loop, and only creates a new Typed Array View,
  instead of a whole new ArrayBuffer.
2015-08-06 14:47:03 -04:00
Solly Ross 38781d931e Use Typed Arrays for the Websock receive queue
**This commit removes Base64 (and Flash) support**

This commit converts websock.js to used Typed Arrays for the
receive queue (and tweaks rfb.js to ensure that it continues
to function, since only Firefox implements
`%TypedArray%.prototype.slice`).  Base64 support was removed
to simplify code paths, and pave the way for using Typed Arrays
for the send queue as well.

This provides two advantages: first, we allocate a buffer ahead
of time, meaning the browser doesn't have to do any work dynamically
increasing the receive queue size.  Secondly, we are now able to pass
around Typed Array Views (e.g. `Uint8Array`), which are lightweight, and
don't involve copying.

The downside is that we initially allocate more memory -- we currently
start out with 4 MiB, and then automatically double when it looks like
the amount unused is getting to small.

The commit also explicitly adds a check to the compacting logic that
avoids calling the copy functions if `_rQlen === _rQi`.
2015-08-06 14:47:03 -04:00
Solly Ross 6c883653a2 Remove unecessarily nested loop
This commit removes a nested loop in indexedToRGB, converting it into a
plain loop without multiplication.
2015-08-06 14:47:03 -04:00
Solly Ross 6940936ffc WIP: Switch to Pako for zlib
This commit introduces an alternate implementation of the zlib
decompressor based on Pako (https://github.com/nodeca/pako).
2015-08-06 14:47:03 -04:00
Solly Ross 0442e153a1 Fix RFB.js JSHint Errors
This fixes a couple of JSHint errors in RFB.js caused
by using `==` instead of `===`.
2015-08-06 14:47:03 -04:00
Solly Ross efed2eeafd Fix up vnc_playback.html and playback.js
This commit fixes vnc_playback.html and playback.js
so that they work with the current version of noVNC.
2015-08-06 14:47:03 -04:00
Solly Ross bc4414f5b1 CI: Switch to Container-Based Travis
This commit switches the Travis tests over to Travis's new
container-based infrastructure.  It also tells Travis to
cache the node_modules directory for faster setup.
2015-08-06 14:43:02 -04:00
Samuel 8f3c0f6b9b Merge pull request #499 from samhed/fullscreen
Add option to toggle fullscreen mode
2015-07-01 13:16:53 +02:00
samhed a6357e8276 * Hide the fullscreen toggle button on Safari since alphanumerical
keyboard input doesn't work in fullscreen.
* Force clipping mode in Internet Explorer while in fullscreen since
  scrollbars doesn't work in fullscreen.
2015-06-25 22:37:05 +02:00
samhed 6e296bfa8a Adapt display to be able to handle a changing clipping-setting while connected 2015-06-25 22:37:05 +02:00
samhed 7d1dc09ad0 Fixes #498 - Add the ability to toggle fullscreen mode 2015-06-25 22:37:05 +02:00
samhed b098afc234 Fix error from a previous commit, there is no data type called 'text'.. 2015-06-25 22:34:44 +02:00
samhed 74f2ac968d Renamed popupStatusPanel to popupStatus and removed the global variable
which kept track on if it was open or not (it automatically closes after
1.5 seconds anyway).
2015-06-25 17:04:55 +02:00
samhed 30bfff81d9 Clarified enableDisableClip (now called enableDisableViewClip) and
moved it to where the other clipping functions are.
2015-06-25 15:29:23 +02:00
samhed 4f19e5c697 Allow the popupStatusPanel to show any text but close it on a 1.5 second timer. 2015-06-25 15:22:53 +02:00
samhed 31ddaa1c7a Clarify code with regards to the viewport drag functionality
* Fixes #502 so that the viewport drag functionality can't get stuck
2015-06-24 16:20:03 +02:00
samhed 29a0e6a8a3 Always show the viewport drag button on touch devices to keep the GUI from
"jumping around". Enable/disable the button instead of show/hide on these
devices.
2015-06-16 22:41:29 +02:00
Solly 539955ff27 Merge pull request #497 from MOZGIII/MOZGIII-patch-vnc-auto-fix
Fixed incorrect UI usage and minor updateState params errors in vnc_auto.html
2015-06-12 14:34:34 -04:00
MOZGIII 2bcfd58667 Fixed incorrect UI usage and minor updateState params errors in vnc_auto.html 2015-06-12 20:41:41 +03:00
samhed 1138bdd4b7 Reverting most of commit cb3e4de. The issue was not related to using keyboardinputReset.. the issue was infact a typo which was introduced in 3b8ec46. I must be too tired.. 2015-05-19 13:34:50 +02:00
samhed cb3e4deb73 Stop using keyboardinputReset at the start of keyInput
* This fixes backspace when using the on-screen keyboard on Android
2015-05-19 12:44:30 +02:00
Samuel 613f05eea0 Update LICENSE.txt
Removed old no longer existing vnc.js and added playback.js
2015-05-05 13:13:17 +02:00
Samuel 8af9b6910c Merge pull request #483 from fabian-z/master
Make noVNC compatible with Content Security Policies
2015-05-04 12:17:30 +02:00
Fabian Zaremba 48d26b2d47 Move #keyboardinput declarations to base.css/ui.js 2015-05-04 12:11:30 +02:00
samhed 8ce27ddb4b Re-fixes #428 which was broken by commit 58ded70
* Disable local cursor when the browser doesn't support data uri
2015-04-29 14:54:28 +02:00
Solly f9ffb21b6b Merge pull request #415 from jimdigriz/fixes
slip in PATH for 'npm test' as not everyone has karma
2015-04-07 14:29:46 -04:00
Solly cd1ab146bb Merge pull request #474 from kanaka/bug/throw-error-from-constructor
Throw exceptions from RFB constructor
2015-03-27 13:23:32 -04:00
Solly 245dc8669b Merge pull request #475 from kanaka/bug/follow-symlinks-for-here
Follow symbolic links in launch.sh
2015-03-27 11:54:39 -04:00
Solly Ross d9fc1c7be4 Throw exceptions from RFB constructor
Previously, if an error was thrown from the Display constructor
in the RFB constructor, we would attempt to use `RFB#updateState`
to handle this.  However, `RFB#updateState` attempts to close
the WebSocket connection, which doesn't exist at this point.

In the constructor, it's probably just better to raise an exception
instead (making sure to clean up anything relevant).

Fixes #460
2015-03-26 17:10:24 -04:00
Solly Ross 58ded70d15 Create RFB object on connect
In e543525faa, we switched to creating
a new RFB object on disconnect.  This caused issues, however, since
any errors were only displayed briefly before the new "loaded" text
was displayed instead.

Now, we create the RFB object on connect.  This essentially removes
the usefulness of the "loaded" state, but prevents the aforementioned
problem.

To facilitate this, the code which does detection of cursor URI support
was moved from this Display constructor (which now calls the new
function) into its own function, `Util.browserSupportsCursorURIs()`.

Fixes #467
2015-03-26 17:09:05 -04:00
Solly Ross 2ace90e6d5 Follow symbolic links in launch.sh
Previously, in launch.sh, `$HERE` was the directory of `$0`.
However, if `$0` was actually a symlink, `$HERE` would be
wherever the symlink was, which could cause issues (for
example, the script wouldn't be able to local `$WEB` or
`$WEBSOCKIFY` properly).

Now, `$HERE` looks at whatever `$0` points at instead.

Closes #447.
2015-03-26 16:57:17 -04:00
samhed 16b3ef77d1 Make getCanvasLimit more efficient by only calling getPosition once. 2015-03-11 07:29:30 +01:00
Solly cefc9a9177 Merge pull request #464 from kanaka/bug/firefoxresize
Fix resize in Firefox on Android
2015-03-10 17:13:21 -04:00
samhed fdedbafb1d * Don't check specific html elements from the display code (Fixes #446)
* Renamed and reworked fbuClip to clippingDisplay
* Added tests for clippingDisplay
* Use the a noVNC_container which covers the entire page to get the full size
  (Fixes #463)
* Added maxWidth and maxHeight to the canvas which can limit the viewport size
* Only show either the canvas or the logo, hide one when the other is shown
* Always center the canvas (previously it was only centered when not clipping)
* Removed iOS specific "position-fixed" fixes and start calling setBarPosition
  on every resize
* Removed the noVNC_screen_pad
2015-03-09 14:30:56 +01:00
samhed 798340b98d * Change name of ext_desktop_size to the proper ExtendedDesktopSize
* Added better error handling in ExtendedDesktopSize
* Added helper function to share code with DesktopSize
* Update test.rfb.js to only check for error handling if we were the
  ones requesting the resize
2015-03-05 14:56:47 +01:00
samhed 3b8ec46fd2 Make the touch-keyboard code more robust through verifying that global variables are set before use. 2015-03-05 09:54:56 +01:00
Samuel 205d1a11ce Merge pull request #451 from kanaka/feature/scaling
Introduce Local Autoscaling
2015-03-05 09:52:10 +01:00
Solly Ross 72747869a7 Support local scaling
This commit adds two new addition scaling options.  Both options do
local scaling.  The first "Local Scaling", does both upscaling and
downscaling.  The second option, "Local Downscaling", only downscales.

This is based on work by @mightypenguin (with an additional bug
reported by @glazik12).
2015-03-03 13:15:49 -05:00
Solly Ross 8b46c0deb0 Update UI to allow for different scaling modes
This commit updates the UI to allow for different
scaling modes.  The "resize" option was changed to
be a dropdown with the following options: "None" (nothing),
"Remote Resizing" (SetDesktopSize).
2015-02-27 17:13:51 -05:00
Solly Ross 7e161007ab Launch Sauce Connect through Travis
This commit makes Travis launch sauce connect, instead of
using karma to start Sauce Connect
2015-02-27 17:13:33 -05:00
Solly 18c34c4766 Merge pull request #461 from kanaka/bug/459-incorrect-position-with-scrollbars
Make Util.getPosition be relative to page
2015-02-27 14:11:46 -05:00
Solly Ross 7e54fb93dd Make Util.getPosition be relative to page
Commit 5108c4635c caused a regression
in the case where scrolling is used -- getPosition return position
relative to the viewport, while getEventPosition expected a position
relative to the page.

As per
https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect,
the fix for this is simply to add the `pageXOffset` and `pageYOffset` to
the output of `getBoundingClientRect()`.

Fixes #459.
2015-02-27 13:37:13 -05:00
Solly Ross 20d3fb6665 Increase Karma-Sauce Connector Timeout
This commit increases the browserNoActivityTimeout to 100s
(from the default of 10s) in an attempt to alliviate the
timeout issues that we are seeing.
2015-02-27 13:23:09 -05:00
Solly Ross 88224c3fa7 Update dependencies to the latest versions
This commit updates the test dependencies to the latest versions.
2015-02-27 12:42:43 -05:00
Samuel 4043d8bb12 Merge pull request #458 from kanaka/bug/disconnect
Fix disconnect/reconnect issues
2015-02-26 12:41:46 +01:00
Solly Ross e543525faa Fix disconnect/reconnect issues
Commit 155d78b399 prevented reconnections
from working properly.  This fixes that by creating a new RFB object
after disconnecting or failing.

Furthermore, this ensures that a new connection cannot be opened util
we've actually disconnected (either by timer or by receiving a `close`
event).

Closes #452
2015-02-25 11:01:18 -05:00
Solly Ross 2ec29db752 Remove 'debian' directory
This commit removes the old debian packaging information, which is
out of date.  People who wish to create Debain packages for noVNC
are better off using the package information from the actual Debian
or Ubuntu packages.

Closes #453
2015-02-20 17:34:59 -05:00
Solly Ross fe8a4dc9d8 Remove last bits of websockify cruft
Issue #449 pointed out that there were some files that were
missed in 6f5148648b.  This
fixes that.

Closes #449
Closes #450
2015-02-17 23:15:51 -05:00
Solly Ross 9db6a90677 Make sure websockify is cloned to the correct dir
When `utils/launch.sh` clones websockify, it can be cloned
into the incorrect directory, depending on how `utils/launch.sh`
is run.  This commit ensures that websockify is always cloned into
`utils/websockify`.
2015-02-17 17:54:21 -05:00
Solly Ross 5cd6de495f Actually remove the "websockify" file
Somehow, `utils/websockify` itself manage to sneak back in to
6f5148648b.  This actually removes
it.
2015-02-17 17:45:57 -05:00
Solly 985c433867 Merge pull request #448 from kanaka/websockify-as-submodule
Remove local copies of websockify
2015-02-17 17:35:57 -05:00
Solly 5108c4635c Merge pull request #432 from nbibler/getPositionUpdate
Replace custom getPosition algorithms with getBoundingClientRect.
2015-02-17 15:59:27 -05:00
Solly Ross dedf9affbf Merge branch 'bug/fix-input-html'
Closes #434
2015-02-17 13:36:56 -05:00
Jesper Dam (jalfd) 1188993c0f Update tests/input.html to include keysym.js instead of keysymdef.js 2015-02-17 13:36:22 -05:00
Solly Ross 4e534a804e Update package.json to specify version 0.5.1
The current noVNC release is 0.5.1, but package.json still
says version 0.5.0.  This fixes that.

Fixes #441
2015-02-16 17:13:00 -05:00
Solly Ross 6f5148648b Remove local copies of websockify
This commit removes local copies of websockify.
Instead `utils/launch.sh` performs the following logic:

If `utils/websockify` exists, use `utils/websockify/run` (if the latter
does not exist, or is not executable, fail, since this is probably a
mistake).

Otherwise, check to see if websockify is installed somewhere (try
`which websockify`).  If it is, use that.  Otherwise, clone
websockify from github, and tell git to ignore that directory.

Packaged versions of noVNC should simply list websockify as a
requirement.  The debian packaging has been updated to reflect
this.

Closes #433
2015-02-16 17:09:58 -05:00
Solly f675e03ccc Merge pull request #444 from samhed/extdesktop
Support ExtendedDesktopSize and SetDesktopSize
2015-02-10 11:31:37 -05:00
samhed 4dec490aae Support automatic resize [Part 4/4]: unit tests
* Added new tests for the setDesktopSize encoding
* Added new tests for the ExtendedDesktopSize encoding
2015-02-10 17:06:55 +01:00
samhed f8b399d7df Support automatic resize [Part 3/4]: ui.js
* Added a resize request (setDesktopSize) triggered when connecting and by
  changes to the browser window's size.
* Hid the view-drag-hand when the display area is the same or smaller than the
  remote session size.
* Added a setting for the automatic resize feature.
* Updated vnc.html and vnc_auto.html to reflect the changes to the UI.
2015-02-10 17:05:58 +01:00
Samuel 757653c007 Merge pull request #443 from fzylogic/master
keysym.js needs inclusion in debian's install list
2015-02-07 03:13:41 +01:00
Jeremy Hanmer a2158362d9 keysym.js needs inclusion in debian's install list 2015-02-06 14:24:47 -08:00
samhed b0ec6509f1 Support automatic resize [Part 2/4]: rfb.js
* Support sending the setDesktopSize encoding (client -> server)
* Support recieving the ExtendedDesktopSize encoding (server <- client)
2015-02-06 17:06:48 +01:00
samhed 636be753b2 Support automatic resize [Part 1/4]: display.js
* Split viewportChange into two functions, one for changing size and the other for changing position.
* Modified viewport code to be capable of changing to a bigger size in the context of a
  client-initiated resize.
* Made clearer distinctions between when viewport-clipping or not.
* Added public function for telling when viewport-clipping.
* Updated tests that were using viewportChange.
2015-02-06 16:54:14 +01:00
samhed 5b7598ac6b Use our own event registration function for keyboardinputReset. 2015-02-06 15:53:10 +01:00
Solly a437079307 Merge pull request #427 from jswanner/register-listeners
Unregister event listeners from websock.
2015-02-04 11:09:37 -05:00
samhed e9f55ea0f8 Fixes issue #435 - added missing comma in keysym.js 2015-02-03 13:04:16 +01:00
Nathaniel Bibler 455f8f3fd0 Replace custom getPosition algorithms with getBoundingClientRect. 2015-01-26 11:46:44 -05:00
samhed b804b3e458 Fixes #428 - hides the local cursor when using the server-side cursor. 2015-01-15 16:27:18 +01:00
Jacob Swanner 155d78b399 Unregister event listeners from websock.
Prevents possible memory and event notification leaks when tearing down
connection and reestablishing a new one.
2015-01-08 15:25:55 -05:00
Alexander Clouter dc4b6301c8 slip in PATH for 'npm test' as not everyone has karma
So people can follow the 'unit tests' instructions, we need
to make sure PATH includes the karma bin directory otherwise
we see the following:
----
aclouter@stevemcqueen:/usr/src/aten-ikvm/noVNC$ npm test

> noVNC@0.5.0 test /usr/src/aten-ikvm/noVNC
> karma start karma.conf.js

sh: 1: karma: not found
npm ERR! Test failed.  See above for more details.
npm ERR! not ok code 0
----
2014-12-04 21:18:16 +00:00
65 changed files with 5278 additions and 4622 deletions
+1
View File
@@ -2,4 +2,5 @@
*.o
tests/data_*.js
utils/rebind.so
utils/websockify
node_modules
-3
View File
@@ -1,3 +0,0 @@
[submodule "include/web-socket-js-project"]
path = include/web-socket-js-project
url = https://github.com/gimite/web-socket-js.git
+6
View File
@@ -1,4 +1,8 @@
language: node_js
sudo: false
cache:
directories:
- node_modules
node_js:
- '0.11.13'
env:
@@ -14,3 +18,5 @@ env:
- 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
addons:
sauce_connect: true
+9 -7
View File
@@ -9,13 +9,12 @@ is not limited to):
include/des.js
include/display.js
include/input.js
include/jsunzip.js
include/keysym.js
include/logo.js
include/playback.js
include/rfb.js
include/ui.js
include/util.js
include/vnc.js
include/websock.js
include/webutil.js
@@ -49,16 +48,18 @@ licenses (all MPL 2.0 compatible):
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
utils/websockify
utils/websocket.py : LGPL 3
utils/inflator.partial.js
include/inflator.js : MIT (for pako)
Any other files not mentioned above are typically marked with
a copyright/license header at the top of the file. The default noVNC
license is MPL-2.0.
The following license texts are included:
@@ -70,6 +71,7 @@ The following license texts are included:
docs/LICENSE.BSD-2-Clause (Simplified BSD / FreeBSD)
docs/LICENSE.zlib
docs/LICENSE.Apache-2.0
docs/LICENSE.pako
Or alternatively the license texts may be found here:
+2 -8
View File
@@ -69,11 +69,7 @@ See more screenshots <a href="http://kanaka.github.com/noVNC/screenshots.html">h
* HTML5 Canvas (with createImageData): Chrome, Firefox 3.6+, iOS
Safari, Opera 11+, Internet Explorer 9+, etc.
* HTML5 WebSockets: For browsers that do not have builtin
WebSockets support, the project includes
<a href="http://github.com/gimite/web-socket-js">web-socket-js</a>,
a WebSockets emulator using Adobe Flash. iOS 4.2+ has built-in
WebSocket support.
* HTML5 WebSockets and Typed Arrays
* Fast Javascript Engine: this is not strictly a requirement, but
without a fast Javascript engine, noVNC might be painfully slow.
@@ -130,9 +126,7 @@ use a WebSockets to TCP socket proxy. There is a python proxy included
* tight encoding : Michael Tinglof (Mercuri.ca)
* Included libraries:
* web-socket-js : Hiroshi Ichikawa (github.com/gimite/web-socket-js)
* as3crypto : Henri Torgemane (code.google.com/p/as3crypto)
* base64 : Martijn Pieters (Digital Creations 2), Samuel Sieb (sieb.net)
* jsunzip : Erik Moller (github.com/operasoftware/jsunzip),
* tinflate : Joergen Ibsen (ibsensoftware.com)
* DES : Dave Zimmerman (Widget Workshop), Jef Poskanzer (ACME Labs)
* Pako : Vitaly Puzrin (https://github.com/nodeca/pako)
-35
View File
@@ -1,35 +0,0 @@
novnc (0.4) maverick; urgency=low
* Clarify permissive licenses of HTML, CSS, images.
* Use render queue and requestAnimationFrame
* UltraVNC repeater support
-- Joel Martin <github@martintribe.org> Fri, 14 Sep 2012 05:00:00 -0600
novnc (0.3) maverick; urgency=low
* add tight encoding support
* release pressed key when focus lost (fixes locked Alt key)
* Support Apple Remote Desktop
* Add nova/openstack proxy wrapper
* Better connection close handling/reporting
-- Joel Martin <github@martintribe.org> Fri, 11 May 2012 03:00:00 -0600
novnc (0.2) maverick; urgency=low
* Mobile device support with viewport clipping
* Much better styling that also works on mobile devices well
* Update websockify to support latest WebSocket protocol HyBi 13
(i.e. support IETF 6455)
* Better support in websockify for python 2.4 through 3.2
* Support VMWare ESX and Intel AMT KVM
* View only mode
-- Joel Martin <github@martintribe.org> Tue, 05 Jul 2011 01:00:00 -0600
novnc (0.1) maverick; urgency=low
* First upstream release
-- Joel Martin <github@martintribe.org> Tue, 05 Jul 2011 01:00:00 -0600
-1
View File
@@ -1 +0,0 @@
7
-13
View File
@@ -1,13 +0,0 @@
Source: novnc
Section: web
Priority: optional
Maintainer: Joel Martin <github@martintribe.org>
Build-Depends: debhelper (>= 7.0.0~)
Standards-Version: 3.8.3
Homepage: https://github.com/kanaka/noVNC/
Package: novnc
Architecture: any
Depends: ${shlibs:Depends}, ${misc:Depends}, python (>= 2.4)
Description: HTML5 VNC client
VNC client using HTML5 (WebSockets, Canvas) with encryption (wss://) support.
-37
View File
@@ -1,37 +0,0 @@
Upstream Project: https://github.com/kanaka/noVNC/
--------------------- Original LICENSE.txt ---------------------------
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 MPL 2.0 (Mozilla Public License) with the
following exceptions:
*.html, *.css : 2-Clause BSD license
include/Orbitron* : SIL Open Font License 1.1
(Copyright 2009 Matt McInerney)
images/ : Creative Commons Attribution-ShareAlike
http://creativecommons.org/licenses/by-sa/3.0/
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 MPL-2.0 license text may be found here:
http://www.mozilla.org/MPL/2.0/
-30
View File
@@ -1,30 +0,0 @@
vnc.html /usr/share/novnc
vnc_auto.html /usr/share/novnc
README.md /usr/share/doc/novnc
LICENSE.txt /usr/share/doc/novnc
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/rebind.c /usr/share/novnc/utils
utils/rebind.so /usr/share/novnc/utils
images /usr/share/novnc
images/favicon.ico /usr/share/novnc
include/base64.js /usr/share/novnc/include
include/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
include/blue.css /usr/share/novnc/include
include/black.css /usr/share/novnc/include
include/playback.js /usr/share/novnc/include
include/rfb.js /usr/share/novnc/include
include/ui.js /usr/share/novnc/include
include/util.js /usr/share/novnc/include
include/websock.js /usr/share/novnc/include
include/webutil.js /usr/share/novnc/include
include/jsunzip.js /usr/share/novnc/include
include/web-socket-js/* /usr/share/novnc/include/web-socket-js
-14
View File
@@ -1,14 +0,0 @@
#!/usr/bin/make -f
# Uncomment this to turn on verbose mode.
#export DH_VERBOSE=1
clean:
make -C utils clean
dh clean
build:
make -C utils rebind.so
%:
dh ${@}
+21
View File
@@ -0,0 +1,21 @@
(The MIT License)
Copyright (C) 2014 by Vitaly Puzrin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+1 -1
View File
@@ -1 +1 @@
0.4
0.6
+4 -16
View File
@@ -1,17 +1,5 @@
Some implementation notes:
Rebuilding inflator.js
There is an included flash object (web-socket-js) that is used to
emulate websocket support on browsers without websocket support
(currently only Chrome has WebSocket support).
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 websockify is base64 encoding/decoding what is sent to/from the
browser.
Building web-socket-js emulator:
cd include/web-socket-js/flash-src
mxmlc -static-link-runtime-shared-libraries WebSocketMain.as
- Download pako from npm
- Install browserify using npm
- browserify utils/inflator.partial.js -o include/inflator.js -s inflator
-23
View File
@@ -1,23 +0,0 @@
noVNC packaging steps for Debian/Ubuntu:
- Update the noVNC version in docs/VERSION and add a new entry for the
version in debian/changelog
- Rename the novnc source directory to match the form "novnc-VERSION".
- In the novnc source directory, run the packaging command:
debuild -I -uc -us
- The -I option ignores the .git directory when generating the
source tarball.
- the -uc and -us may be omitted in order to create a signed
package.
- Alternatively, use pbuilder instead of debuild in order to build
for other distributions and to guarantee a sanitized package.
- Verify the package and then commit the changes to docs/VERSION and
debian/changelog.
- Upload the new package file(s).
+1 -1
View File
@@ -1,4 +1,4 @@
- Update and commit docs/VERSION and debian/changelog
- Update and commit docs/VERSION
- Create version tag and tarball from tag
WVER=0.3
git tag v${WVER}
Binary file not shown.

After

Width:  |  Height:  |  Size: 851 B

Before

Width:  |  Height:  |  Size: 735 B

After

Width:  |  Height:  |  Size: 735 B

+53 -38
View File
@@ -1,7 +1,7 @@
/*
* noVNC base CSS
* Copyright (C) 2012 Joel Martin
* Copyright (C) 2013 Samuel Mannehed for Cendio AB
* Copyright (C) 2016 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).
*/
@@ -30,18 +30,18 @@ html {
padding-bottom:8px;
}
#noVNC_host {
#noVNC_setting_host {
width:150px;
}
#noVNC_port {
#noVNC_setting_port {
width: 80px;
}
#noVNC_password {
#noVNC_setting_password {
width: 150px;
}
#noVNC_encrypt {
#noVNC_setting_encrypt {
}
#noVNC_path {
#noVNC_setting_path {
width: 100px;
}
#noVNC_connect_button {
@@ -56,7 +56,10 @@ html {
#noVNC_view_drag_button {
display: none;
}
#sendCtrlAltDelButton {
#noVNC_sendCtrlAltDel_button {
display: none;
}
#noVNC_fullscreen_button {
display: none;
}
#noVNC_xvp_buttons {
@@ -74,13 +77,13 @@ html {
position: relative;
}
.noVNC-buttons-left {
.noVNC_buttons_left {
float: left;
z-index: 1;
position: relative;
}
.noVNC-buttons-right {
.noVNC_buttons_right {
float:right;
right: 0px;
z-index: 2;
@@ -106,19 +109,11 @@ html {
padding: 0px;
}
#noVNC_apply {
#noVNC_settings_apply {
float:right;
}
/* Do not set width/height for VNC_screen or VNC_canvas or incorrect
* scaling will occur. Canvas resizes to remote VNC settings */
#noVNC_screen_pad {
margin: 0px;
padding: 0px;
height: 36px;
}
#noVNC_screen {
text-align: center;
#noVNC_container {
display: table;
width:100%;
height:100%;
@@ -127,13 +122,28 @@ html {
/*border-top-left-radius: 800px 600px;*/
}
#noVNC_container, #noVNC_canvas {
#noVNC_screen {
display: none;
position: absolute;
margin: 0px;
padding: 0px;
bottom: 0px;
top: 36px; /* the height of the control bar */
left: 0px;
right: 0px;
width: auto;
height: auto;
}
/* Do not set width/height for VNC_canvas or incorrect
* scaling will occur. Canvas size depends on remote VNC
* settings and noVNC settings. */
#noVNC_canvas {
left: 0px;
position: absolute;
left: 0;
right: 0;
margin-left: auto;
margin-right: auto;
}
#VNC_clipboard_clear_button {
@@ -182,7 +192,7 @@ html {
border-radius:10px;
}
#noVNC_popup_status_panel {
#noVNC_popup_status {
display:none;
position: fixed;
z-index: 1;
@@ -223,7 +233,7 @@ html {
right:85px;
}
#keyboardinput {
#noVNC_keyboardinput {
width:1px;
height:1px;
background-color:#fff;
@@ -232,6 +242,7 @@ html {
position: relative;
left: -40px;
z-index: -1;
ime-mode: disabled;
}
/*
@@ -267,7 +278,7 @@ html {
}
/* Control bar */
#noVNC-control-bar {
#noVNC_control_bar {
position:fixed;
display:block;
@@ -313,6 +324,10 @@ html {
/*box-shadow:inset 0.4px 0.4px 0.4px #000000;*/
}
.noVNC_status_button:disabled {
opacity: 0.4;
}
/*Settings Bubble*/
.triangle-right {
@@ -412,11 +427,11 @@ html {
font-size: 180px;
}
.noVNC-buttons-left {
.noVNC_buttons_left {
padding-left: 10px;
}
.noVNC-buttons-right {
.noVNC_buttons_right {
padding-right: 10px;
}
@@ -427,11 +442,11 @@ html {
margin-left: 0px;
}
#showExtraKeysButton { display: none; }
#toggleCtrlButton { display: inline; }
#toggleAltButton { display: inline; }
#sendTabButton { display: inline; }
#sendEscButton { display: inline; }
#noVNC_toggleExtraKeys_button { display: none; }
#noVNC_toggleCtrl_button { display: inline; }
#noVNC_toggleAlt_button { display: inline; }
#noVNC_sendTab_button { display: inline; }
#noVNC_sendEsc_button { display: inline; }
/* left-align the status text on lower resolutions */
@media screen and (max-width: 800px){
@@ -454,35 +469,35 @@ html {
.noVNC_status_button {
font-size: 10px;
}
.noVNC-buttons-left {
.noVNC_buttons_left {
padding-left: 0px;
}
.noVNC-buttons-right {
.noVNC_buttons_right {
padding-right: 0px;
}
/* collapse the extra keys on lower resolutions */
#showExtraKeysButton {
#noVNC_toggleExtraKeys_button {
display: inline;
}
#toggleCtrlButton {
#noVNC_toggleCtrl_button {
display: none;
position: absolute;
top: 30px;
left: 0px;
}
#toggleAltButton {
#noVNC_toggleAlt_button {
display: none;
position: absolute;
top: 65px;
left: 0px;
}
#sendTabButton {
#noVNC_sendTab_button {
display: none;
position: absolute;
top: 100px;
left: 0px;
}
#sendEscButton {
#noVNC_sendEsc_button {
display: none;
position: absolute;
top: 135px;
+1 -1
View File
@@ -6,7 +6,7 @@
* This file is licensed under the 2-Clause BSD license (see LICENSE.txt).
*/
#keyboardinput {
#noVNC_keyboardinput {
background-color:#000;
}
+1 -1
View File
@@ -58,7 +58,7 @@
color:#fff;
}
#keyboardinput {
#noVNC_keyboardinput {
background-color:#04073d;
}
+283 -121
View File
@@ -1,6 +1,7 @@
/*
* noVNC: HTML5 VNC client
* Copyright (C) 2012 Joel Martin
* Copyright (C) 2015 Samuel Mannehed for Cendio AB
* Licensed under MPL 2.0 (see LICENSE.txt)
*
* See README.md for usage and integration instructions.
@@ -14,6 +15,14 @@ var Display;
(function () {
"use strict";
var SUPPORTS_IMAGEDATA_CONSTRUCTOR = false;
try {
new ImageData(new Uint8ClampedArray(1), 1, 1);
SUPPORTS_IMAGEDATA_CONSTRUCTOR = true;
} catch (ex) {
// ignore failure
}
Display = function (defaults) {
this._drawCtx = null;
this._c_forceCanvas = false;
@@ -24,6 +33,10 @@ var Display;
this._fb_width = 0;
this._fb_height = 0;
// the size limit of the viewport (start disabled)
this._maxWidth = 0;
this._maxHeight = 0;
// the visible "physical canvas" viewport
this._viewportLoc = { 'x': 0, 'y': 0, 'w': 0, 'h': 0 };
this._cleanRect = { 'x1': 0, 'y1': 0, 'x2': -1, 'y2': -1 };
@@ -81,28 +94,9 @@ var Display;
}
// Determine browser support for setting the cursor via data URI scheme
var curDat = [];
for (var i = 0; i < 8 * 8 * 4; i++) {
curDat.push(255);
}
try {
var curSave = this._target.style.cursor;
Display.changeCursor(this._target, curDat, curDat, 2, 2, 8, 8);
if (this._target.style.cursor) {
if (this._cursor_uri === null || this._cursor_uri === undefined) {
this._cursor_uri = true;
}
Util.Info("Data URI scheme cursor supported");
} else {
if (this._cursor_uri === null || this._cursor_uri === undefined) {
this._cursor_uri = false;
}
Util.Warn("Data URI scheme cursor not supported");
}
this._target.style.cursor = curSave;
} catch (exc) {
Util.Error("Data URI scheme cursor test exception: " + exc);
this._cursor_uri = false;
if (this._cursor_uri || this._cursor_uri === null ||
this._cursor_uri === undefined) {
this._cursor_uri = Util.browserSupportsCursorURIs();
}
Util.Debug("<< Display.constructor");
@@ -110,54 +104,14 @@ var Display;
Display.prototype = {
// Public methods
viewportChange: function (deltaX, deltaY, width, height) {
viewportChangePos: function (deltaX, deltaY) {
var vp = this._viewportLoc;
var cr = this._cleanRect;
var canvas = this._target;
deltaX = Math.floor(deltaX);
deltaY = Math.floor(deltaY);
if (!this._viewport) {
Util.Debug("Setting viewport to full display region");
deltaX = -vp.w; // clamped later of out of bounds
deltaY = -vp.h;
width = this._fb_width;
height = this._fb_height;
}
if (typeof(deltaX) === "undefined") { deltaX = 0; }
if (typeof(deltaY) === "undefined") { deltaY = 0; }
if (typeof(width) === "undefined") { width = vp.w; }
if (typeof(height) === "undefined") { height = vp.h; }
// Size change
if (width > this._fb_width) { width = this._fb_width; }
if (height > this._fb_height) { height = this._fb_height; }
if (vp.w !== width || vp.h !== height) {
// Change width
if (width < vp.w && cr.x2 > vp.x + width - 1) {
cr.x2 = vp.x + width - 1;
}
vp.w = width;
// Change height
if (height < vp.h && cr.y2 > vp.y + height - 1) {
cr.y2 = vp.y + height - 1;
}
vp.h = height;
var saveImg = null;
if (vp.w > 0 && vp.h > 0 && canvas.width > 0 && canvas.height > 0) {
var img_width = canvas.width < vp.w ? canvas.width : vp.w;
var img_height = canvas.height < vp.h ? canvas.height : vp.h;
saveImg = this._drawCtx.getImageData(0, 0, img_width, img_height);
}
canvas.width = vp.w;
canvas.height = vp.h;
if (saveImg) {
this._drawCtx.putImageData(saveImg, 0, 0);
}
}
var vx2 = vp.x + vp.w - 1;
@@ -190,6 +144,7 @@ var Display;
vy2 += deltaY;
// Update the clean rectangle
var cr = this._cleanRect;
if (vp.x > cr.x1) {
cr.x1 = vp.x;
}
@@ -225,20 +180,99 @@ var Display;
h = deltaY;
}
// Copy the valid part of the viewport to the shifted location
var saveStyle = this._drawCtx.fillStyle;
var canvas = this._target;
this._drawCtx.fillStyle = "rgb(255,255,255)";
// Due to this bug among others [1] we need to disable the image-smoothing to
// avoid getting a blur effect when panning.
//
// 1. https://bugzilla.mozilla.org/show_bug.cgi?id=1194719
//
// We need to set these every time since all properties are reset
// when the the size is changed
if (this._drawCtx.mozImageSmoothingEnabled) {
this._drawCtx.mozImageSmoothingEnabled = false;
} else if (this._drawCtx.webkitImageSmoothingEnabled) {
this._drawCtx.webkitImageSmoothingEnabled = false;
} else if (this._drawCtx.msImageSmoothingEnabled) {
this._drawCtx.msImageSmoothingEnabled = false;
} else if (this._drawCtx.imageSmoothingEnabled) {
this._drawCtx.imageSmoothingEnabled = false;
}
// Copy the valid part of the viewport to the shifted location
this._drawCtx.drawImage(canvas, 0, 0, vp.w, vp.h, -deltaX, -deltaY, vp.w, vp.h);
if (deltaX !== 0) {
this._drawCtx.drawImage(canvas, 0, 0, vp.w, vp.h, -deltaX, 0, vp.w, vp.h);
this._drawCtx.fillRect(x1, 0, w, vp.h);
}
if (deltaY !== 0) {
this._drawCtx.drawImage(canvas, 0, 0, vp.w, vp.h, 0, -deltaY, vp.w, vp.h);
this._drawCtx.fillRect(0, y1, vp.w, h);
}
this._drawCtx.fillStyle = saveStyle;
},
viewportChangeSize: function(width, height) {
if (typeof(width) === "undefined" || typeof(height) === "undefined") {
Util.Debug("Setting viewport to full display region");
width = this._fb_width;
height = this._fb_height;
}
var vp = this._viewportLoc;
if (vp.w !== width || vp.h !== height) {
if (this._viewport) {
if (this._maxWidth !== 0 && width > this._maxWidth) {
width = this._maxWidth;
}
if (this._maxHeight !== 0 && height > this._maxHeight) {
height = this._maxHeight;
}
}
var cr = this._cleanRect;
if (width < vp.w && cr.x2 > vp.x + width - 1) {
cr.x2 = vp.x + width - 1;
}
if (height < vp.h && cr.y2 > vp.y + height - 1) {
cr.y2 = vp.y + height - 1;
}
vp.w = width;
vp.h = height;
var canvas = this._target;
if (canvas.width !== width || canvas.height !== height) {
// We have to save the canvas data since changing the size will clear it
var saveImg = null;
if (vp.w > 0 && vp.h > 0 && canvas.width > 0 && canvas.height > 0) {
var img_width = canvas.width < vp.w ? canvas.width : vp.w;
var img_height = canvas.height < vp.h ? canvas.height : vp.h;
saveImg = this._drawCtx.getImageData(0, 0, img_width, img_height);
}
if (canvas.width !== width) {
canvas.width = width;
canvas.style.width = width + 'px';
}
if (canvas.height !== height) {
canvas.height = height;
canvas.style.height = height + 'px';
}
if (saveImg) {
this._drawCtx.putImageData(saveImg, 0, 0);
}
}
}
},
// Return a map of clean and dirty areas of the viewport and reset the
// tracking of clean and dirty areas
//
@@ -304,7 +338,7 @@ var Display;
this._rescale(this._scale);
this.viewportChange();
this.viewportChangeSize();
},
clear: function () {
@@ -325,18 +359,41 @@ var Display;
this._renderQ = [];
},
fillRect: function (x, y, width, height, color) {
this._setFillColor(color);
this._drawCtx.fillRect(x - this._viewportLoc.x, y - this._viewportLoc.y, width, height);
fillRect: function (x, y, width, height, color, from_queue) {
if (this._renderQ.length !== 0 && !from_queue) {
this.renderQ_push({
'type': 'fill',
'x': x,
'y': y,
'width': width,
'height': height,
'color': color
});
} else {
this._setFillColor(color);
this._drawCtx.fillRect(x - this._viewportLoc.x, y - this._viewportLoc.y, width, height);
}
},
copyImage: function (old_x, old_y, new_x, new_y, w, h) {
var x1 = old_x - this._viewportLoc.x;
var y1 = old_y - this._viewportLoc.y;
var x2 = new_x - this._viewportLoc.x;
var y2 = new_y - this._viewportLoc.y;
copyImage: function (old_x, old_y, new_x, new_y, w, h, from_queue) {
if (this._renderQ.length !== 0 && !from_queue) {
this.renderQ_push({
'type': 'copy',
'old_x': old_x,
'old_y': old_y,
'x': new_x,
'y': new_y,
'width': w,
'height': h,
});
} else {
var x1 = old_x - this._viewportLoc.x;
var y1 = old_y - this._viewportLoc.y;
var x2 = new_x - this._viewportLoc.x;
var y2 = new_y - this._viewportLoc.y;
this._drawCtx.drawImage(this._target, x1, y1, w, h, x2, y2, w, h);
this._drawCtx.drawImage(this._target, x1, y1, w, h, x2, y2, w, h);
}
},
// start updating a tile
@@ -368,7 +425,7 @@ var Display;
data[i + 3] = 255;
}
} else {
this.fillRect(x, y, width, height, color);
this.fillRect(x, y, width, height, color, true);
}
},
@@ -399,7 +456,7 @@ var Display;
}
}
} else {
this.fillRect(this._tile_x + x, this._tile_y + y, w, h, color);
this.fillRect(this._tile_x + x, this._tile_y + y, w, h, color, true);
}
},
@@ -412,16 +469,44 @@ var Display;
// else: No-op -- already done by setSubTile
},
blitImage: function (x, y, width, height, arr, offset) {
if (this._true_color) {
blitImage: function (x, y, width, height, arr, offset, from_queue) {
if (this._renderQ.length !== 0 && !from_queue) {
// NB(directxman12): it's technically more performant here to use preallocated arrays,
// but it's a lot of extra work for not a lot of payoff -- if we're using the render queue,
// this probably isn't getting called *nearly* as much
var new_arr = new Uint8Array(width * height * 4);
new_arr.set(new Uint8Array(arr.buffer, 0, new_arr.length));
this.renderQ_push({
'type': 'blit',
'data': new_arr,
'x': x,
'y': y,
'width': width,
'height': height,
});
} else if (this._true_color) {
this._bgrxImageData(x, y, this._viewportLoc.x, this._viewportLoc.y, width, height, arr, offset);
} else {
this._cmapImageData(x, y, this._viewportLoc.x, this._viewportLoc.y, width, height, arr, offset);
}
},
blitRgbImage: function (x, y , width, height, arr, offset) {
if (this._true_color) {
blitRgbImage: function (x, y , width, height, arr, offset, from_queue) {
if (this._renderQ.length !== 0 && !from_queue) {
// NB(directxman12): it's technically more performant here to use preallocated arrays,
// but it's a lot of extra work for not a lot of payoff -- if we're using the render queue,
// this probably isn't getting called *nearly* as much
var new_arr = new Uint8Array(width * height * 4);
new_arr.set(new Uint8Array(arr.buffer, 0, new_arr.length));
this.renderQ_push({
'type': 'blitRgb',
'data': new_arr,
'x': x,
'y': y,
'width': width,
'height': height,
});
} else if (this._true_color) {
this._rgbImageData(x, y, this._viewportLoc.x, this._viewportLoc.y, width, height, arr, offset);
} else {
// probably wrong?
@@ -429,6 +514,26 @@ var Display;
}
},
blitRgbxImage: function (x, y, width, height, arr, offset, from_queue) {
if (this._renderQ.length !== 0 && !from_queue) {
// NB(directxman12): it's technically more performant here to use preallocated arrays,
// but it's a lot of extra work for not a lot of payoff -- if we're using the render queue,
// this probably isn't getting called *nearly* as much
var new_arr = new Uint8Array(width * height * 4);
new_arr.set(new Uint8Array(arr.buffer, 0, new_arr.length));
this.renderQ_push({
'type': 'blitRgbx',
'data': new_arr,
'x': x,
'y': y,
'width': width,
'height': height,
});
} else {
this._rgbxImageData(x, y, this._viewportLoc.x, this._viewportLoc.y, width, height, arr, offset);
}
},
blitStringImage: function (str, x, y) {
var img = new Image();
img.onload = function () {
@@ -470,6 +575,24 @@ var Display;
this._target.style.cursor = "default";
},
disableLocalCursor: function () {
this._target.style.cursor = "none";
},
clippingDisplay: function () {
var vp = this._viewportLoc;
var fbClip = this._fb_width > vp.w || this._fb_height > vp.h;
var limitedVp = this._maxWidth !== 0 && this._maxHeight !== 0;
var clipping = false;
if (limitedVp) {
clipping = vp.w > this._maxWidth || vp.h > this._maxHeight;
}
return fbClip || (limitedVp && clipping);
},
// Overridden getters/setters
get_context: function () {
return this._drawCtx;
@@ -480,51 +603,73 @@ var Display;
},
set_width: function (w) {
this.resize(w, this._fb_height);
this._fb_width = w;
},
get_width: function () {
return this._fb_width;
},
set_height: function (h) {
this.resize(this._fb_width, h);
this._fb_height = h;
},
get_height: function () {
return this._fb_height;
},
autoscale: function (containerWidth, containerHeight, downscaleOnly) {
var targetAspectRatio = containerWidth / containerHeight;
var fbAspectRatio = this._fb_width / this._fb_height;
var scaleRatio;
if (fbAspectRatio >= targetAspectRatio) {
scaleRatio = containerWidth / this._fb_width;
} else {
scaleRatio = containerHeight / this._fb_height;
}
var targetW, targetH;
if (scaleRatio > 1.0 && downscaleOnly) {
targetW = this._fb_width;
targetH = this._fb_height;
scaleRatio = 1.0;
} else if (fbAspectRatio >= targetAspectRatio) {
targetW = containerWidth;
targetH = Math.round(containerWidth / fbAspectRatio);
} else {
targetW = Math.round(containerHeight * fbAspectRatio);
targetH = containerHeight;
}
// NB(directxman12): If you set the width directly, or set the
// style width to a number, the canvas is cleared.
// However, if you set the style width to a string
// ('NNNpx'), the canvas is scaled without clearing.
this._target.style.width = targetW + 'px';
this._target.style.height = targetH + 'px';
this._scale = scaleRatio;
return scaleRatio; // so that the mouse, etc scale can be set
},
// Private Methods
_rescale: function (factor) {
var canvas = this._target;
var properties = ['transform', 'WebkitTransform', 'MozTransform'];
var transform_prop;
while ((transform_prop = properties.shift())) {
if (typeof canvas.style[transform_prop] !== 'undefined') {
break;
}
}
if (transform_prop === null) {
Util.Debug("No scaling support");
return;
}
if (typeof(factor) === "undefined") {
factor = this._scale;
} else if (factor > 1.0) {
factor = 1.0;
} else if (factor < 0.1) {
factor = 0.1;
}
if (this._scale === factor) {
return;
}
this._scale = factor;
var x = canvas.width - (canvas.width * factor);
var y = canvas.height - (canvas.height * factor);
canvas.style[transform_prop] = 'scale(' + this._scale + ') translate(-' + x + 'px, -' + y + 'px)';
var w;
var h;
if (this._viewport &&
this._maxWidth !== 0 && this._maxHeight !== 0) {
w = Math.min(this._fb_width, this._maxWidth);
h = Math.min(this._fb_height, this._maxHeight);
} else {
w = this._fb_width;
h = this._fb_height;
}
this._target.style.width = Math.round(factor * w) + 'px';
this._target.style.height = Math.round(factor * h) + 'px';
},
_setFillColor: function (color) {
@@ -532,7 +677,7 @@ var Display;
if (this._true_color) {
bgr = color;
} else {
bgr = this._colourMap[color[0]];
bgr = this._colourMap[color];
}
var newStyle = 'rgb(' + bgr[2] + ',' + bgr[1] + ',' + bgr[0] + ')';
@@ -566,6 +711,18 @@ var Display;
this._drawCtx.putImageData(img, x - vx, y - vy);
},
_rgbxImageData: function (x, y, vx, vy, width, height, arr, offset) {
// NB(directxman12): arr must be an Type Array view
var img;
if (SUPPORTS_IMAGEDATA_CONSTRUCTOR) {
img = new ImageData(new Uint8ClampedArray(arr.buffer, arr.byteOffset, width * height * 4), width, height);
} else {
img = this._drawCtx.createImageData(width, height);
img.data.set(new Uint8ClampedArray(arr.buffer, arr.byteOffset, width * height * 4));
}
this._drawCtx.putImageData(img, x - vx, y - vy);
},
_cmapImageData: function (x, y, vx, vy, width, height, arr, offset) {
var img = this._drawCtx.createImageData(width, height);
var data = img.data;
@@ -586,16 +743,19 @@ var Display;
var a = this._renderQ[0];
switch (a.type) {
case 'copy':
this.copyImage(a.old_x, a.old_y, a.x, a.y, a.width, a.height);
this.copyImage(a.old_x, a.old_y, a.x, a.y, a.width, a.height, true);
break;
case 'fill':
this.fillRect(a.x, a.y, a.width, a.height, a.color);
this.fillRect(a.x, a.y, a.width, a.height, a.color, true);
break;
case 'blit':
this.blitImage(a.x, a.y, a.width, a.height, a.data, 0);
this.blitImage(a.x, a.y, a.width, a.height, a.data, 0, true);
break;
case 'blitRgb':
this.blitRgbImage(a.x, a.y, a.width, a.height, a.data, 0);
this.blitRgbImage(a.x, a.y, a.width, a.height, a.data, 0, true);
break;
case 'blitRgbx':
this.blitRgbxImage(a.x, a.y, a.width, a.height, a.data, 0, true);
break;
case 'img':
if (a.img.complete) {
@@ -626,9 +786,11 @@ var Display;
['true_color', 'rw', 'bool'], // Use true-color pixel data
['colourMap', 'rw', 'arr'], // Colour map array (when not true-color)
['scale', 'rw', 'float'], // Display area scale factor 0.0 - 1.0
['viewport', 'rw', 'bool'], // Use a viewport set with viewportChange()
['viewport', 'rw', 'bool'], // Use viewport clipping
['width', 'rw', 'int'], // Display area width
['height', 'rw', 'int'], // Display area height
['maxWidth', 'rw', 'int'], // Viewport max width (0 if disabled)
['maxHeight', 'rw', 'int'], // Viewport max height (0 if disabled)
['render_mode', 'ro', 'str'], // Canvas rendering mode (read-only)
+2418
View File
File diff suppressed because it is too large Load Diff
+3 -2
View File
@@ -212,7 +212,7 @@ var Keyboard, Mouse;
// Touch device
// When two touches occur within 500 ms of each other and are
// closer than 20 pixels together a double click is triggered.
// close enough together a double click is triggered.
if (down == 1) {
if (this._doubleClickTimer === null) {
this._lastTouchPos = pos;
@@ -229,7 +229,8 @@ var Keyboard, Mouse;
// The goal is to trigger on a certain physical width, the
// devicePixelRatio brings us a bit closer but is not optimal.
if (d < 20 * window.devicePixelRatio) {
var threshold = 20 * (window.devicePixelRatio || 1);
if (d < threshold) {
pos = this._lastTouchPos;
}
}
-676
View File
@@ -1,676 +0,0 @@
/*
* JSUnzip
*
* Copyright (c) 2011 by Erik Moller
* All Rights Reserved
*
* This software is provided 'as-is', without any express
* or implied warranty. In no event will the authors be
* held liable for any damages arising from the use of
* this software.
*
* Permission is granted to anyone to use this software
* for any purpose, including commercial applications,
* and to alter it and redistribute it freely, subject to
* the following restrictions:
*
* 1. The origin of this software must not be
* misrepresented; you must not claim that you
* wrote the original software. If you use this
* software in a product, an acknowledgment in
* the product documentation would be appreciated
* but is not required.
*
* 2. Altered source versions must be plainly marked
* as such, and must not be misrepresented as
* being the original software.
*
* 3. This notice may not be removed or altered from
* any source distribution.
*/
var tinf;
function JSUnzip() {
this.getInt = function(offset, size) {
switch (size) {
case 4:
return (this.data.charCodeAt(offset + 3) & 0xff) << 24 |
(this.data.charCodeAt(offset + 2) & 0xff) << 16 |
(this.data.charCodeAt(offset + 1) & 0xff) << 8 |
(this.data.charCodeAt(offset + 0) & 0xff);
break;
case 2:
return (this.data.charCodeAt(offset + 1) & 0xff) << 8 |
(this.data.charCodeAt(offset + 0) & 0xff);
break;
default:
return this.data.charCodeAt(offset) & 0xff;
break;
}
};
this.getDOSDate = function(dosdate, dostime) {
var day = dosdate & 0x1f;
var month = ((dosdate >> 5) & 0xf) - 1;
var year = 1980 + ((dosdate >> 9) & 0x7f)
var second = (dostime & 0x1f) * 2;
var minute = (dostime >> 5) & 0x3f;
hour = (dostime >> 11) & 0x1f;
return new Date(year, month, day, hour, minute, second);
}
this.open = function(data) {
this.data = data;
this.files = [];
if (this.data.length < 22)
return { 'status' : false, 'error' : 'Invalid data' };
var endOfCentralDirectory = this.data.length - 22;
while (endOfCentralDirectory >= 0 && this.getInt(endOfCentralDirectory, 4) != 0x06054b50)
--endOfCentralDirectory;
if (endOfCentralDirectory < 0)
return { 'status' : false, 'error' : 'Invalid data' };
if (this.getInt(endOfCentralDirectory + 4, 2) != 0 || this.getInt(endOfCentralDirectory + 6, 2) != 0)
return { 'status' : false, 'error' : 'No multidisk support' };
var entriesInThisDisk = this.getInt(endOfCentralDirectory + 8, 2);
var centralDirectoryOffset = this.getInt(endOfCentralDirectory + 16, 4);
var globalCommentLength = this.getInt(endOfCentralDirectory + 20, 2);
this.comment = this.data.slice(endOfCentralDirectory + 22, endOfCentralDirectory + 22 + globalCommentLength);
var fileOffset = centralDirectoryOffset;
for (var i = 0; i < entriesInThisDisk; ++i) {
if (this.getInt(fileOffset + 0, 4) != 0x02014b50)
return { 'status' : false, 'error' : 'Invalid data' };
if (this.getInt(fileOffset + 6, 2) > 20)
return { 'status' : false, 'error' : 'Unsupported version' };
if (this.getInt(fileOffset + 8, 2) & 1)
return { 'status' : false, 'error' : 'Encryption not implemented' };
var compressionMethod = this.getInt(fileOffset + 10, 2);
if (compressionMethod != 0 && compressionMethod != 8)
return { 'status' : false, 'error' : 'Unsupported compression method' };
var lastModFileTime = this.getInt(fileOffset + 12, 2);
var lastModFileDate = this.getInt(fileOffset + 14, 2);
var lastModifiedDate = this.getDOSDate(lastModFileDate, lastModFileTime);
var crc = this.getInt(fileOffset + 16, 4);
// TODO: crc
var compressedSize = this.getInt(fileOffset + 20, 4);
var uncompressedSize = this.getInt(fileOffset + 24, 4);
var fileNameLength = this.getInt(fileOffset + 28, 2);
var extraFieldLength = this.getInt(fileOffset + 30, 2);
var fileCommentLength = this.getInt(fileOffset + 32, 2);
var relativeOffsetOfLocalHeader = this.getInt(fileOffset + 42, 4);
var fileName = this.data.slice(fileOffset + 46, fileOffset + 46 + fileNameLength);
var fileComment = this.data.slice(fileOffset + 46 + fileNameLength + extraFieldLength, fileOffset + 46 + fileNameLength + extraFieldLength + fileCommentLength);
if (this.getInt(relativeOffsetOfLocalHeader + 0, 4) != 0x04034b50)
return { 'status' : false, 'error' : 'Invalid data' };
var localFileNameLength = this.getInt(relativeOffsetOfLocalHeader + 26, 2);
var localExtraFieldLength = this.getInt(relativeOffsetOfLocalHeader + 28, 2);
var localFileContent = relativeOffsetOfLocalHeader + 30 + localFileNameLength + localExtraFieldLength;
this.files[fileName] =
{
'fileComment' : fileComment,
'compressionMethod' : compressionMethod,
'compressedSize' : compressedSize,
'uncompressedSize' : uncompressedSize,
'localFileContent' : localFileContent,
'lastModifiedDate' : lastModifiedDate
};
fileOffset += 46 + fileNameLength + extraFieldLength + fileCommentLength;
}
return { 'status' : true }
};
this.read = function(fileName) {
var fileInfo = this.files[fileName];
if (fileInfo) {
if (fileInfo.compressionMethod == 8) {
if (!tinf) {
tinf = new TINF();
tinf.init();
}
var result = tinf.uncompress(this.data, fileInfo.localFileContent);
if (result.status == tinf.OK)
return { 'status' : true, 'data' : result.data };
else
return { 'status' : false, 'error' : result.error };
} else {
return { 'status' : true, 'data' : this.data.slice(fileInfo.localFileContent, fileInfo.localFileContent + fileInfo.uncompressedSize) };
}
}
return { 'status' : false, 'error' : "File '" + fileName + "' doesn't exist in zip" };
};
};
/*
* tinflate - tiny inflate
*
* Copyright (c) 2003 by Joergen Ibsen / Jibz
* All Rights Reserved
*
* http://www.ibsensoftware.com/
*
* This software is provided 'as-is', without any express
* or implied warranty. In no event will the authors be
* held liable for any damages arising from the use of
* this software.
*
* Permission is granted to anyone to use this software
* for any purpose, including commercial applications,
* and to alter it and redistribute it freely, subject to
* the following restrictions:
*
* 1. The origin of this software must not be
* misrepresented; you must not claim that you
* wrote the original software. If you use this
* software in a product, an acknowledgment in
* the product documentation would be appreciated
* but is not required.
*
* 2. Altered source versions must be plainly marked
* as such, and must not be misrepresented as
* being the original software.
*
* 3. This notice may not be removed or altered from
* any source distribution.
*/
/*
* tinflate javascript port by Erik Moller in May 2011.
* emoller@opera.com
*
* read_bits() patched by mike@imidio.com to allow
* reading more then 8 bits (needed in some zlib streams)
*/
"use strict";
function TINF() {
this.OK = 0;
this.DATA_ERROR = (-3);
this.WINDOW_SIZE = 32768;
/* ------------------------------ *
* -- internal data structures -- *
* ------------------------------ */
this.TREE = function() {
this.table = new Array(16); /* table of code length counts */
this.trans = new Array(288); /* code -> symbol translation table */
};
this.DATA = function(that) {
this.source = '';
this.sourceIndex = 0;
this.tag = 0;
this.bitcount = 0;
this.dest = [];
this.history = [];
this.ltree = new that.TREE(); /* dynamic length/symbol tree */
this.dtree = new that.TREE(); /* dynamic distance tree */
};
/* --------------------------------------------------- *
* -- uninitialized global data (static structures) -- *
* --------------------------------------------------- */
this.sltree = new this.TREE(); /* fixed length/symbol tree */
this.sdtree = new this.TREE(); /* fixed distance tree */
/* extra bits and base tables for length codes */
this.length_bits = new Array(30);
this.length_base = new Array(30);
/* extra bits and base tables for distance codes */
this.dist_bits = new Array(30);
this.dist_base = new Array(30);
/* special ordering of code length codes */
this.clcidx = [
16, 17, 18, 0, 8, 7, 9, 6,
10, 5, 11, 4, 12, 3, 13, 2,
14, 1, 15
];
/* ----------------------- *
* -- utility functions -- *
* ----------------------- */
/* build extra bits and base tables */
this.build_bits_base = function(bits, base, delta, first)
{
var i, sum;
/* build bits table */
for (i = 0; i < delta; ++i) bits[i] = 0;
for (i = 0; i < 30 - delta; ++i) bits[i + delta] = Math.floor(i / delta);
/* build base table */
for (sum = first, i = 0; i < 30; ++i)
{
base[i] = sum;
sum += 1 << bits[i];
}
}
/* build the fixed huffman trees */
this.build_fixed_trees = function(lt, dt)
{
var i;
/* build fixed length tree */
for (i = 0; i < 7; ++i) lt.table[i] = 0;
lt.table[7] = 24;
lt.table[8] = 152;
lt.table[9] = 112;
for (i = 0; i < 24; ++i) lt.trans[i] = 256 + i;
for (i = 0; i < 144; ++i) lt.trans[24 + i] = i;
for (i = 0; i < 8; ++i) lt.trans[24 + 144 + i] = 280 + i;
for (i = 0; i < 112; ++i) lt.trans[24 + 144 + 8 + i] = 144 + i;
/* build fixed distance tree */
for (i = 0; i < 5; ++i) dt.table[i] = 0;
dt.table[5] = 32;
for (i = 0; i < 32; ++i) dt.trans[i] = i;
}
/* given an array of code lengths, build a tree */
this.build_tree = function(t, lengths, loffset, num)
{
var offs = new Array(16);
var i, sum;
/* clear code length count table */
for (i = 0; i < 16; ++i) t.table[i] = 0;
/* scan symbol lengths, and sum code length counts */
for (i = 0; i < num; ++i) t.table[lengths[loffset + i]]++;
t.table[0] = 0;
/* compute offset table for distribution sort */
for (sum = 0, i = 0; i < 16; ++i)
{
offs[i] = sum;
sum += t.table[i];
}
/* create code->symbol translation table (symbols sorted by code) */
for (i = 0; i < num; ++i)
{
if (lengths[loffset + i]) t.trans[offs[lengths[loffset + i]]++] = i;
}
}
/* ---------------------- *
* -- decode functions -- *
* ---------------------- */
/* get one bit from source stream */
this.getbit = function(d)
{
var bit;
/* check if tag is empty */
if (!d.bitcount--)
{
/* load next tag */
d.tag = d.source[d.sourceIndex++] & 0xff;
d.bitcount = 7;
}
/* shift bit out of tag */
bit = d.tag & 0x01;
d.tag >>= 1;
return bit;
}
/* read a num bit value from a stream and add base */
function read_bits_direct(source, bitcount, tag, idx, num)
{
var val = 0;
while (bitcount < 24) {
tag = tag | (source[idx++] & 0xff) << bitcount;
bitcount += 8;
}
val = tag & (0xffff >> (16 - num));
tag >>= num;
bitcount -= num;
return [bitcount, tag, idx, val];
}
this.read_bits = function(d, num, base)
{
if (!num)
return base;
var ret = read_bits_direct(d.source, d.bitcount, d.tag, d.sourceIndex, num);
d.bitcount = ret[0];
d.tag = ret[1];
d.sourceIndex = ret[2];
return ret[3] + base;
}
/* given a data stream and a tree, decode a symbol */
this.decode_symbol = function(d, t)
{
while (d.bitcount < 16) {
d.tag = d.tag | (d.source[d.sourceIndex++] & 0xff) << d.bitcount;
d.bitcount += 8;
}
var sum = 0, cur = 0, len = 0;
do {
cur = 2 * cur + ((d.tag & (1 << len)) >> len);
++len;
sum += t.table[len];
cur -= t.table[len];
} while (cur >= 0);
d.tag >>= len;
d.bitcount -= len;
return t.trans[sum + cur];
}
/* given a data stream, decode dynamic trees from it */
this.decode_trees = function(d, lt, dt)
{
var code_tree = new this.TREE();
var lengths = new Array(288+32);
var hlit, hdist, hclen;
var i, num, length;
/* get 5 bits HLIT (257-286) */
hlit = this.read_bits(d, 5, 257);
/* get 5 bits HDIST (1-32) */
hdist = this.read_bits(d, 5, 1);
/* get 4 bits HCLEN (4-19) */
hclen = this.read_bits(d, 4, 4);
for (i = 0; i < 19; ++i) lengths[i] = 0;
/* read code lengths for code length alphabet */
for (i = 0; i < hclen; ++i)
{
/* get 3 bits code length (0-7) */
var clen = this.read_bits(d, 3, 0);
lengths[this.clcidx[i]] = clen;
}
/* build code length tree */
this.build_tree(code_tree, lengths, 0, 19);
/* decode code lengths for the dynamic trees */
for (num = 0; num < hlit + hdist; )
{
var sym = this.decode_symbol(d, code_tree);
switch (sym)
{
case 16:
/* copy previous code length 3-6 times (read 2 bits) */
{
var prev = lengths[num - 1];
for (length = this.read_bits(d, 2, 3); length; --length)
{
lengths[num++] = prev;
}
}
break;
case 17:
/* repeat code length 0 for 3-10 times (read 3 bits) */
for (length = this.read_bits(d, 3, 3); length; --length)
{
lengths[num++] = 0;
}
break;
case 18:
/* repeat code length 0 for 11-138 times (read 7 bits) */
for (length = this.read_bits(d, 7, 11); length; --length)
{
lengths[num++] = 0;
}
break;
default:
/* values 0-15 represent the actual code lengths */
lengths[num++] = sym;
break;
}
}
/* build dynamic trees */
this.build_tree(lt, lengths, 0, hlit);
this.build_tree(dt, lengths, hlit, hdist);
}
/* ----------------------------- *
* -- block inflate functions -- *
* ----------------------------- */
/* given a stream and two trees, inflate a block of data */
this.inflate_block_data = function(d, lt, dt)
{
// js optimization.
var ddest = d.dest;
var ddestlength = ddest.length;
while (1)
{
var sym = this.decode_symbol(d, lt);
/* check for end of block */
if (sym == 256)
{
return this.OK;
}
if (sym < 256)
{
ddest[ddestlength++] = sym; // ? String.fromCharCode(sym);
d.history.push(sym);
} else {
var length, dist, offs;
var i;
sym -= 257;
/* possibly get more bits from length code */
length = this.read_bits(d, this.length_bits[sym], this.length_base[sym]);
dist = this.decode_symbol(d, dt);
/* possibly get more bits from distance code */
offs = d.history.length - this.read_bits(d, this.dist_bits[dist], this.dist_base[dist]);
if (offs < 0)
throw ("Invalid zlib offset " + offs);
/* copy match */
for (i = offs; i < offs + length; ++i) {
//ddest[ddestlength++] = ddest[i];
ddest[ddestlength++] = d.history[i];
d.history.push(d.history[i]);
}
}
}
}
/* inflate an uncompressed block of data */
this.inflate_uncompressed_block = function(d)
{
var length, invlength;
var i;
if (d.bitcount > 7) {
var overflow = Math.floor(d.bitcount / 8);
d.sourceIndex -= overflow;
d.bitcount = 0;
d.tag = 0;
}
/* get length */
length = d.source[d.sourceIndex+1];
length = 256*length + d.source[d.sourceIndex];
/* get one's complement of length */
invlength = d.source[d.sourceIndex+3];
invlength = 256*invlength + d.source[d.sourceIndex+2];
/* check length */
if (length != (~invlength & 0x0000ffff)) return this.DATA_ERROR;
d.sourceIndex += 4;
/* copy block */
for (i = length; i; --i) {
d.history.push(d.source[d.sourceIndex]);
d.dest[d.dest.length] = d.source[d.sourceIndex++];
}
/* make sure we start next block on a byte boundary */
d.bitcount = 0;
return this.OK;
}
/* inflate a block of data compressed with fixed huffman trees */
this.inflate_fixed_block = function(d)
{
/* decode block using fixed trees */
return this.inflate_block_data(d, this.sltree, this.sdtree);
}
/* inflate a block of data compressed with dynamic huffman trees */
this.inflate_dynamic_block = function(d)
{
/* decode trees from stream */
this.decode_trees(d, d.ltree, d.dtree);
/* decode block using decoded trees */
return this.inflate_block_data(d, d.ltree, d.dtree);
}
/* ---------------------- *
* -- public functions -- *
* ---------------------- */
/* initialize global (static) data */
this.init = function()
{
/* build fixed huffman trees */
this.build_fixed_trees(this.sltree, this.sdtree);
/* build extra bits and base tables */
this.build_bits_base(this.length_bits, this.length_base, 4, 3);
this.build_bits_base(this.dist_bits, this.dist_base, 2, 1);
/* fix a special case */
this.length_bits[28] = 0;
this.length_base[28] = 258;
this.reset();
}
this.reset = function()
{
this.d = new this.DATA(this);
delete this.header;
}
/* inflate stream from source to dest */
this.uncompress = function(source, offset)
{
var d = this.d;
var bfinal;
/* initialise data */
d.source = source;
d.sourceIndex = offset;
d.bitcount = 0;
d.dest = [];
// Skip zlib header at start of stream
if (typeof this.header == 'undefined') {
this.header = this.read_bits(d, 16, 0);
/* byte 0: 0x78, 7 = 32k window size, 8 = deflate */
/* byte 1: check bits for header and other flags */
}
var blocks = 0;
do {
var btype;
var res;
/* read final block flag */
bfinal = this.getbit(d);
/* read block type (2 bits) */
btype = this.read_bits(d, 2, 0);
/* decompress block */
switch (btype)
{
case 0:
/* decompress uncompressed block */
res = this.inflate_uncompressed_block(d);
break;
case 1:
/* decompress block with fixed huffman trees */
res = this.inflate_fixed_block(d);
break;
case 2:
/* decompress block with dynamic huffman trees */
res = this.inflate_dynamic_block(d);
break;
default:
return { 'status' : this.DATA_ERROR };
}
if (res != this.OK) return { 'status' : this.DATA_ERROR };
blocks++;
} while (!bfinal && d.sourceIndex < d.source.length);
d.history = d.history.slice(-this.WINDOW_SIZE);
return { 'status' : this.OK, 'data' : d.dest };
}
};
+1 -1
View File
@@ -58,7 +58,7 @@ XK_KP_Up = 0xff97,
XK_KP_Right = 0xff98,
XK_KP_Down = 0xff99,
XK_KP_Prior = 0xff9a,
XK_KP_Page_Up = 0xff9a
XK_KP_Page_Up = 0xff9a,
XK_KP_Next = 0xff9b,
XK_KP_Page_Down = 0xff9b,
XK_KP_End = 0xff9c,
+24 -6
View File
@@ -12,21 +12,39 @@ var rfb, mode, test_state, frame_idx, frame_length,
iteration, iterations, istart_time,
// Pre-declarations for jslint
send_array, next_iteration, queue_next_packet, do_packet;
send_array, next_iteration, queue_next_packet, do_packet, enable_test_mode;
// Override send_array
send_array = function (arr) {
// Stub out send_array
};
enable_test_mode = function () {
rfb._sock._mode = VNC_frame_encoding;
rfb._sock.send = send_array;
rfb._sock.close = function () {};
rfb._sock.flush = function () {};
rfb._checkEvents = function () {};
rfb.connect = function (host, port, password, path) {
this._rfb_host = host;
this._rfb_port = port;
this._rfb_password = (password !== undefined) ? password : "";
this._rfb_path = (path !== undefined) ? path : "";
this._sock.init('binary', 'ws');
this._updateState('ProtocolVersion', "Starting VNC handshake");
};
};
next_iteration = function () {
rfb = new RFB({'target': $D('VNC_canvas'),
'onUpdateState': updateState});
enable_test_mode();
if (iteration === 0) {
frame_length = VNC_frame_data.length;
test_state = 'running';
} else {
rfb.disconnect();
}
if (test_state !== 'running') { return; }
iteration += 1;
@@ -91,9 +109,9 @@ do_packet = function () {
for (var i = 0; i < frame.length - start; i++) {
u8[i] = frame.charCodeAt(start + i);
}
rfb.recv_message({'data' : u8});
rfb._sock._recv_message({'data' : u8});
} else {
rfb.recv_message({'data' : frame.slice(start)});
rfb._sock._recv_message({'data' : frame.slice(start)});
}
frame_idx += 1;
+535 -269
View File
File diff suppressed because it is too large Load Diff
+802 -494
View File
File diff suppressed because it is too large Load Diff
+33 -67
View File
@@ -433,72 +433,15 @@ Util.load_scripts = function (files) {
};
// 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 () {
Util.getPosition = function(obj) {
"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};
};
})();
// NB(sross): the Mozilla developer reference seems to indicate that
// getBoundingClientRect includes border and padding, so the canvas
// style should NOT include either.
var objPosition = obj.getBoundingClientRect();
return {'x': objPosition.left + window.pageXOffset, 'y': objPosition.top + window.pageYOffset,
'width': objPosition.width, 'height': objPosition.height};
};
// Get mouse event position in DOM element
@@ -523,8 +466,8 @@ Util.getEventPosition = function (e, obj, scale) {
}
var realx = docX - pos.x;
var realy = docY - pos.y;
var x = Math.max(Math.min(realx, obj.width - 1), 0);
var y = Math.max(Math.min(realy, obj.height - 1), 0);
var x = Math.max(Math.min(realx, pos.width - 1), 0);
var y = Math.max(Math.min(realy, pos.height - 1), 0);
return {'x': x / scale, 'y': y / scale, 'realx': realx / scale, 'realy': realy / scale};
};
@@ -565,6 +508,29 @@ Util.stopEvent = function (e) {
else { e.returnValue = false; }
};
Util._cursor_uris_supported = null;
Util.browserSupportsCursorURIs = function () {
if (Util._cursor_uris_supported === null) {
try {
var target = document.createElement('canvas');
target.style.cursor = 'url("data:image/x-icon;base64,AAACAAEACAgAAAIAAgA4AQAAFgAAACgAAAAIAAAAEAAAAAEAIAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAAAAAAAAAAAAAAAA==") 2 2, default';
if (target.style.cursor) {
Util.Info("Data URI scheme cursor supported");
Util._cursor_uris_supported = true;
} else {
Util.Warn("Data URI scheme cursor not supported");
Util._cursor_uris_supported = false;
}
} catch (exc) {
Util.Error("Data URI scheme cursor test exception: " + exc);
Util._cursor_uris_supported = false;
}
}
return Util._cursor_uris_supported;
};
// Set browser engine versions. Based on mootools.
Util.Features = {xpath: !!(document.evaluate), air: !!(window.runtime), query: !!(document.querySelector)};
-109
View File
@@ -1,109 +0,0 @@
* How to try
Assuming you have Web server (e.g. Apache) running at http://example.com/ .
- Download web_socket.rb from:
http://github.com/gimite/web-socket-ruby/tree/master
- Run sample Web Socket server (echo server) in example.com with: (#1)
$ ruby web-socket-ruby/samples/echo_server.rb example.com 10081
- If your server already provides socket policy file at port 843, modify the file to allow access to port 10081. Otherwise you can skip this step. See below for details.
- Publish the web-socket-js directory with your Web server (e.g. put it in ~/public_html).
- Change ws://localhost:10081 to ws://example.com:10081 in sample.html.
- Open sample.html in your browser.
- After "onopen" is shown, input something, click [Send] and confirm echo back.
#1: First argument of echo_server.rb means that it accepts Web Socket connection from HTML pages in example.com.
* Troubleshooting
If it doesn't work, try these:
1. Try Chrome and Firefox 3.x.
- It doesn't work on Chrome:
-- It's likely an issue of your code or the server. Debug your code as usual e.g. using console.log.
- It works on Chrome but it doesn't work on Firefox:
-- It's likely an issue of web-socket-js specific configuration (e.g. 3 and 4 below).
- It works on both Chrome and Firefox, but it doesn't work on your browser:
-- Check "Supported environment" section below. Your browser may not be supported by web-socket-js.
2. Add this line before your code:
WEB_SOCKET_DEBUG = true;
and use Developer Tools (Chrome/Safari) or Firebug (Firefox) to see if console.log outputs any errors.
3. Make sure you do NOT open your HTML page as local file e.g. file:///.../sample.html. web-socket-js doesn't work on local file. Open it via Web server e.g. http:///.../sample.html.
4. If you are NOT using web-socket-ruby as your WebSocket server, you need to place Flash socket policy file on your server. See "Flash socket policy file" section below for details.
5. Check if sample.html bundled with web-socket-js works.
6. Make sure the port used for WebSocket (10081 in example above) is not blocked by your server/client's firewall.
7. Install debugger version of Flash Player available here to see Flash errors:
http://www.adobe.com/support/flashplayer/downloads.html
* Supported environments
It should work on:
- Google Chrome 4 or later (just uses native implementation)
- Firefox 3.x, Internet Explorer 8 + Flash Player 9 or later
It may or may not work on other browsers such as Safari, Opera or IE 6. Patch for these browsers are appreciated, but I will not work on fixing issues specific to these browsers by myself.
* Flash socket policy file
This implementation uses Flash's socket, which means that your server must provide Flash socket policy file to declare the server accepts connections from Flash.
If you use web-socket-ruby available at
http://github.com/gimite/web-socket-ruby/tree/master
, you don't need anything special, because web-socket-ruby handles Flash socket policy file request. But if you already provide socket policy file at port 843, you need to modify the file to allow access to Web Socket port, because it precedes what web-socket-ruby provides.
If you use other Web Socket server implementation, you need to provide socket policy file yourself. See
http://www.lightsphere.com/dev/articles/flash_socket_policy.html
for details and sample script to run socket policy file server. node.js implementation is available here:
http://github.com/LearnBoost/Socket.IO-node/blob/master/lib/socket.io/transports/flashsocket.js
Actually, it's still better to provide socket policy file at port 843 even if you use web-socket-ruby. Flash always try to connect to port 843 first, so providing the file at port 843 makes startup faster.
* Cookie considerations
Cookie is sent if Web Socket host is the same as the origin of JavaScript. Otherwise it is not sent, because I don't know way to send right Cookie (which is Cookie of the host of Web Socket, I heard).
Note that it's technically possible that client sends arbitrary string as Cookie and any other headers (by modifying this library for example) once you place Flash socket policy file in your server. So don't trust Cookie and other headers if you allow connection from untrusted origin.
* Proxy considerations
The WebSocket spec (http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol) specifies instructions for User Agents to support proxied connections by implementing the HTTP CONNECT method.
The AS3 Socket class doesn't implement this mechanism, which renders it useless for the scenarios where the user trying to open a socket is behind a proxy.
The class RFC2817Socket (by Christian Cantrell) effectively lets us implement this, as long as the proxy settings are known and provided by the interface that instantiates the WebSocket. As such, if you want to support proxied conncetions, you'll have to supply this information to the WebSocket constructor when Flash is being used. One way to go about it would be to ask the user for proxy settings information if the initial connection fails.
* How to host HTML file and SWF file in different domains
By default, HTML file and SWF file must be in the same domain. You can follow steps below to allow hosting them in different domain.
WARNING: If you use the method below, HTML files in ANY domains can send arbitrary TCP data to your WebSocket server, regardless of configuration in Flash socket policy file. Arbitrary TCP data means that they can even fake request headers including Origin and Cookie.
- Unzip WebSocketMainInsecure.zip to extract WebSocketMainInsecure.swf.
- Put WebSocketMainInsecure.swf on your server, instead of WebSocketMain.swf.
- In JavaScript, set WEB_SOCKET_SWF_LOCATION to URL of your WebSocketMainInsecure.swf.
* How to build WebSocketMain.swf
Install Flex 4 SDK:
http://opensource.adobe.com/wiki/display/flexsdk/Download+Flex+4
$ cd flash-src
$ ./build.sh
* License
New BSD License.
Binary file not shown.
File diff suppressed because one or more lines are too long
-391
View File
@@ -1,391 +0,0 @@
// Copyright: Hiroshi Ichikawa <http://gimite.net/en/>
// License: New BSD License
// Reference: http://dev.w3.org/html5/websockets/
// Reference: http://tools.ietf.org/html/rfc6455
(function() {
if (window.WEB_SOCKET_FORCE_FLASH) {
// Keeps going.
} else if (window.WebSocket) {
return;
} else if (window.MozWebSocket) {
// Firefox.
window.WebSocket = MozWebSocket;
return;
}
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:") {
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://...");
}
/**
* Our own implementation of WebSocket class using Flash.
* @param {string} url
* @param {array or string} protocols
* @param {string} proxyHost
* @param {int} proxyPort
* @param {string} 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.
self.__createTask = setTimeout(function() {
WebSocket.__addTask(function() {
self.__createTask = null;
WebSocket.__flash.create(
self.__id, url, protocols, proxyHost || null, proxyPort || 0, headers || null);
});
}, 0);
};
/**
* Send data to the web socket.
* @param {string} data The data to send to the socket.
* @return {boolean} True for success, false for failure.
*/
WebSocket.prototype.send = function(data) {
if (this.readyState == WebSocket.CONNECTING) {
throw "INVALID_STATE_ERR: Web Socket connection has not been established";
}
// We use encodeURIComponent() here, because FABridge doesn't work if
// the argument includes some characters. We don't use escape() here
// because of this:
// https://developer.mozilla.org/en/Core_JavaScript_1.5_Guide/Functions#escape_and_unescape_Functions
// But it looks decodeURIComponent(encodeURIComponent(s)) doesn't
// preserve all Unicode characters either e.g. "\uffff" in Firefox.
// Note by wtritch: Hopefully this will not be necessary using ExternalInterface. Will require
// additional testing.
var result = WebSocket.__flash.send(this.__id, encodeURIComponent(data));
if (result < 0) { // success
return true;
} else {
this.bufferedAmount += result;
return false;
}
};
/**
* 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;
}
this.readyState = WebSocket.CLOSING;
WebSocket.__flash.close(this.__id);
};
/**
* Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>}
*
* @param {string} type
* @param {function} listener
* @param {boolean} useCapture
* @return void
*/
WebSocket.prototype.addEventListener = function(type, listener, useCapture) {
if (!(type in this.__events)) {
this.__events[type] = [];
}
this.__events[type].push(listener);
};
/**
* Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>}
*
* @param {string} type
* @param {function} listener
* @param {boolean} useCapture
* @return void
*/
WebSocket.prototype.removeEventListener = function(type, listener, useCapture) {
if (!(type in this.__events)) return;
var events = this.__events[type];
for (var i = events.length - 1; i >= 0; --i) {
if (events[i] === listener) {
events.splice(i, 1);
break;
}
}
};
/**
* Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>}
*
* @param {Event} event
* @return void
*/
WebSocket.prototype.dispatchEvent = function(event) {
var events = this.__events[event.type] || [];
for (var i = 0; i < events.length; ++i) {
events[i](event);
}
var handler = this["on" + event.type];
if (handler) handler.apply(this, [event]);
};
/**
* Handles an event from Flash.
* @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") {
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);
} else {
throw "unknown event type: " + flashEvent.type;
}
this.dispatchEvent(jsEvent);
};
WebSocket.prototype.__createSimpleEvent = function(type) {
if (document.createEvent && window.Event) {
var event = document.createEvent("Event");
event.initEvent(type, false, false);
return event;
} else {
return {type: type, bubbles: false, cancelable: false};
}
};
WebSocket.prototype.__createMessageEvent = function(type, data) {
if (document.createEvent && window.MessageEvent && !window.opera) {
var event = document.createEvent("MessageEvent");
event.initMessageEvent("message", false, false, data, null, null, window, null);
return event;
} else {
// IE and Opera, the latter one truncates the data parameter after any 0x00 bytes.
return {type: type, data: data, bubbles: false, cancelable: false};
}
};
/**
* Define the WebSocket readyState enumeration.
*/
WebSocket.CONNECTING = 0;
WebSocket.OPEN = 1;
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 = [];
WebSocket.__nextId = 0;
/**
* Load a new flash security policy file.
* @param {string} url
*/
WebSocket.loadFlashPolicyFile = function(url){
WebSocket.__addTask(function() {
WebSocket.__flash.loadManualPolicyFile(url);
});
};
/**
* Loads WebSocketMain.swf and creates WebSocketMain object in Flash.
*/
WebSocket.__initialize = function() {
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) {
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
// Flash from loading at least in IE. So we move it out of the screen at (-100, -100).
// But this even doesn't work with Flash Lite (e.g. in Droid Incredible). So with Flash
// Lite, we put it at (0, 0). This shows 1x1 box visible at left-top corner but this is
// the best we can do as far as we know now.
container.style.position = "absolute";
if (WebSocket.__isFlashLite()) {
container.style.left = "0px";
container.style.top = "0px";
} else {
container.style.left = "-100px";
container.style.top = "-100px";
}
var holder = document.createElement("div");
holder.id = "webSocketFlash";
container.appendChild(holder);
document.body.appendChild(container);
// See this article for hasPriority:
// http://help.adobe.com/en_US/as3/mobile/WS4bebcd66a74275c36cfb8137124318eebc6-7ffd.html
swfobject.embedSWF(
WEB_SOCKET_SWF_LOCATION,
"webSocketFlash",
"1" /* width */,
"1" /* height */,
"10.0.0" /* SWF version */,
null,
null,
{hasPriority: true, swliveconnect : true, allowScriptAccess: "always"},
null,
function(e) {
if (!e.success) {
logger.error("[WebSocket] swfobject.embedSWF failed");
}
}
);
};
/**
* Called by Flash to notify JS that it's fully loaded and ready
* for communication.
*/
WebSocket.__onFlashInitialized = function() {
// We need to set a timeout here to avoid round-trip calls
// to flash during the initialization process.
setTimeout(function() {
WebSocket.__flash = document.getElementById("webSocketFlash");
WebSocket.__flash.setCallerUrl(location.href);
WebSocket.__flash.setDebug(!!window.WEB_SOCKET_DEBUG);
for (var i = 0; i < WebSocket.__tasks.length; ++i) {
WebSocket.__tasks[i]();
}
WebSocket.__tasks = [];
}, 0);
};
/**
* Called by Flash to notify WebSockets events are fired.
*/
WebSocket.__onFlashEvent = function() {
setTimeout(function() {
try {
// Gets events using receiveEvents() instead of getting it from event object
// of Flash event. This is to make sure to keep message order.
// It seems sometimes Flash events don't arrive in the same order as they are sent.
var events = WebSocket.__flash.receiveEvents();
for (var i = 0; i < events.length; ++i) {
WebSocket.__instances[events[i].webSocketId].__handleEvent(events[i]);
}
} catch (e) {
logger.error(e);
}
}, 0);
return true;
};
// Called by Flash.
WebSocket.__log = function(message) {
logger.log(decodeURIComponent(message));
};
// Called by Flash.
WebSocket.__error = function(message) {
logger.error(decodeURIComponent(message));
};
WebSocket.__addTask = function(task) {
if (WebSocket.__flash) {
task();
} else {
WebSocket.__tasks.push(task);
}
};
/**
* Test if the browser is running flash lite.
* @return {boolean} True if flash lite is running, false otherwise.
*/
WebSocket.__isFlashLite = function() {
if (!window.navigator || !window.navigator.mimeTypes) {
return false;
}
var mimeType = window.navigator.mimeTypes["application/x-shockwave-flash"];
if (!mimeType || !mimeType.enabledPlugin || !mimeType.enabledPlugin.filename) {
return false;
}
return mimeType.enabledPlugin.filename.match(/flashlite/i) ? true : false;
};
if (!window.WEB_SOCKET_DISABLE_AUTO_INITIALIZATION) {
// NOTE:
// This fires immediately if web_socket.js is dynamically loaded after
// the document is loaded.
swfobject.addDomLoadEvent(function() {
WebSocket.__initialize();
});
}
})();
+138 -82
View File
@@ -15,7 +15,7 @@
*/
/*jslint browser: true, bitwise: true */
/*global Util, Base64 */
/*global Util*/
// Load Flash WebSocket emulator if needed
@@ -34,29 +34,26 @@ if (window.WebSocket && !window.WEB_SOCKET_FORCE_FLASH) {
/* no builtin WebSocket so load web_socket.js */
Websock_native = false;
(function () {
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();
}
Util.load_scripts(["web-socket-js/swfobject.js",
"web-socket-js/web_socket.js"]);
})();
}
function Websock() {
"use strict";
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
this._mode = 'base64'; // Current WebSocket mode: 'binary', 'base64'
this._rQi = 0; // Receive queue index
this._rQlen = 0; // Next write position in the receive queue
this._rQbufferSize = 1024 * 1024 * 4; // Receive queue buffer size (4 MiB)
this._rQmax = this._rQbufferSize / 8;
// called in init: this._rQ = new Uint8Array(this._rQbufferSize);
this._rQ = null; // Receive queue
this._sQbufferSize = 1024 * 10; // 10 KiB
// called in init: this._sQ = new Uint8Array(this._sQbufferSize);
this._sQlen = 0;
this._sQ = null; // Send queue
this._mode = 'binary'; // Current WebSocket mode: 'binary', 'base64'
this.maxBufferedAmount = 200;
this._eventHandlers = {
@@ -69,6 +66,28 @@ function Websock() {
(function () {
"use strict";
// this has performance issues in some versions Chromium, and
// doesn't gain a tremendous amount of performance increase in Firefox
// at the moment. It may be valuable to turn it on in the future.
var ENABLE_COPYWITHIN = false;
var MAX_RQ_GROW_SIZE = 40 * 1024 * 1024; // 40 MiB
var typedArrayToString = (function () {
// This is only for PhantomJS, which doesn't like apply-ing
// with Typed Arrays
try {
var arr = new Uint8Array([1, 2, 3]);
String.fromCharCode.apply(null, arr);
return function (a) { return String.fromCharCode.apply(null, a); };
} catch (ex) {
return function (a) {
return String.fromCharCode.apply(
null, Array.prototype.slice.call(a));
};
}
})();
Websock.prototype = {
// Getters and Setters
get_sQ: function () {
@@ -89,7 +108,7 @@ function Websock() {
// Receive Queue
rQlen: function () {
return this._rQ.length - this._rQi;
return this._rQlen - this._rQi;
},
rQpeek8: function () {
@@ -108,15 +127,7 @@ function Websock() {
this._rQi += num;
},
rQunshift8: function (num) {
if (this._rQi === 0) {
this._rQ.unshift(num);
} else {
this._rQi--;
this._rQ[this._rQi] = num;
}
},
// TODO(directxman12): test performance with these vs a DataView
rQshift16: function () {
return (this._rQ[this._rQi++] << 8) +
this._rQ[this._rQi++];
@@ -131,22 +142,33 @@ function Websock() {
rQshiftStr: function (len) {
if (typeof(len) === 'undefined') { len = this.rQlen(); }
var arr = this._rQ.slice(this._rQi, this._rQi + len);
var arr = new Uint8Array(this._rQ.buffer, this._rQi, len);
this._rQi += len;
return String.fromCharCode.apply(null, arr);
return typedArrayToString(arr);
},
rQshiftBytes: function (len) {
if (typeof(len) === 'undefined') { len = this.rQlen(); }
this._rQi += len;
return this._rQ.slice(this._rQi - len, this._rQi);
return new Uint8Array(this._rQ.buffer, this._rQi - len, len);
},
rQshiftTo: function (target, len) {
if (len === undefined) { len = this.rQlen(); }
// TODO: make this just use set with views when using a ArrayBuffer to store the rQ
target.set(new Uint8Array(this._rQ.buffer, this._rQi, len));
this._rQi += len;
},
rQwhole: function () {
return new Uint8Array(this._rQ.buffer, 0, this._rQlen);
},
rQslice: function (start, end) {
if (end) {
return this._rQ.slice(this._rQi + start, this._rQi + end);
return new Uint8Array(this._rQ.buffer, this._rQi + start, end - start);
} else {
return this._rQ.slice(this._rQi + start);
return new Uint8Array(this._rQ.buffer, this._rQi + start, this._rQlen - this._rQi - start);
}
},
@@ -154,7 +176,7 @@ function Websock() {
// 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
var rQlen = this._rQlen - this._rQi; // Skip rQlen() function call
if (rQlen < num) {
if (goback) {
if (this._rQi < goback) {
@@ -175,9 +197,9 @@ function Websock() {
}
if (this._websocket.bufferedAmount < this.maxBufferedAmount) {
if (this._sQ.length > 0) {
if (this._sQlen > 0 && this._websocket.readyState === WebSocket.OPEN) {
this._websocket.send(this._encode_message());
this._sQ = [];
this._sQlen = 0;
}
return true;
@@ -189,8 +211,9 @@ function Websock() {
},
send: function (arr) {
this._sQ = this._sQ.concat(arr);
return this.flush();
this._sQ.set(arr, this._sQlen);
this._sQlen += arr.length;
return this.flush();
},
send_string: function (str) {
@@ -200,14 +223,22 @@ function Websock() {
},
// Event Handlers
off: function (evt) {
this._eventHandlers[evt] = function () {};
},
on: function (evt, handler) {
this._eventHandlers[evt] = handler;
},
_allocate_buffers: function () {
this._rQ = new Uint8Array(this._rQbufferSize);
this._sQ = new Uint8Array(this._sQbufferSize);
},
init: function (protocols, ws_schema) {
this._rQ = [];
this._allocate_buffers();
this._rQi = 0;
this._sQ = [];
this._websocket = null;
// Check for full typed array support
@@ -234,35 +265,21 @@ function Websock() {
// Default protocols if not specified
if (typeof(protocols) === "undefined") {
if (wsbt) {
protocols = ['binary', 'base64'];
} else {
protocols = 'base64';
}
protocols = 'binary';
}
if (Array.isArray(protocols) && protocols.indexOf('binary') > -1) {
protocols = 'binary';
}
if (!wsbt) {
if (protocols === 'binary') {
throw new Error('WebSocket binary sub-protocol requested but not supported');
}
throw new Error("noVNC no longer supports base64 WebSockets. " +
"Please use a browser which supports binary WebSockets.");
}
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.");
}
}
if (protocols != 'binary') {
throw new Error("noVNC no longer supports base64 WebSockets. Please " +
"use the binary subprotocol instead.");
}
return protocols;
@@ -285,9 +302,16 @@ function Websock() {
this._mode = this._websocket.protocol;
Util.Info("Server choose sub-protocol: " + this._websocket.protocol);
} else {
this._mode = 'base64';
this._mode = 'binary';
Util.Error('Server select no sub-protocol!: ' + this._websocket.protocol);
}
if (this._mode != 'binary') {
throw new Error("noVNC no longer supports base64 WebSockets. Please " +
"use the binary subprotocol instead.");
}
this._eventHandlers.open();
Util.Debug("<< WebSock.onopen");
}).bind(this);
@@ -317,26 +341,56 @@ function Websock() {
// 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);
// Put in a binary arraybuffer
// according to the spec, you can send ArrayBufferViews with the send method
return new Uint8Array(this._sQ.buffer, 0, this._sQlen);
},
_expand_compact_rQ: function (min_fit) {
var resizeNeeded = min_fit || this._rQlen - this._rQi > this._rQbufferSize / 2;
if (resizeNeeded) {
if (!min_fit) {
// just double the size if we need to do compaction
this._rQbufferSize *= 2;
} else {
// otherwise, make sure we satisy rQlen - rQi + min_fit < rQbufferSize / 8
this._rQbufferSize = (this._rQlen - this._rQi + min_fit) * 8;
}
}
// we don't want to grow unboundedly
if (this._rQbufferSize > MAX_RQ_GROW_SIZE) {
this._rQbufferSize = MAX_RQ_GROW_SIZE;
if (this._rQbufferSize - this._rQlen - this._rQi < min_fit) {
throw new Exception("Receive Queue buffer exceeded " + MAX_RQ_GROW_SIZE + " bytes, and the new message could not fit");
}
}
if (resizeNeeded) {
var old_rQbuffer = this._rQ.buffer;
this._rQmax = this._rQbufferSize / 8;
this._rQ = new Uint8Array(this._rQbufferSize);
this._rQ.set(new Uint8Array(old_rQbuffer, this._rQi));
} else {
if (ENABLE_COPYWITHIN) {
this._rQ.copyWithin(0, this._rQi);
} else {
this._rQ.set(new Uint8Array(this._rQ.buffer, this._rQi));
}
}
this._rQlen = this._rQlen - this._rQi;
this._rQi = 0;
},
_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));
// push arraybuffer values onto the end
var u8 = new Uint8Array(data);
if (u8.length > this._rQbufferSize - this._rQlen) {
this._expand_compact_rQ(u8.length);
}
this._rQ.set(u8, this._rQlen);
this._rQlen += u8.length;
},
_recv_message: function (e) {
@@ -345,9 +399,11 @@ function Websock() {
if (this.rQlen() > 0) {
this._eventHandlers.message();
// Compact the receive queue
if (this._rQ.length > this._rQmax) {
this._rQ = this._rQ.slice(this._rQi);
if (this._rQlen == this._rQi) {
this._rQlen = 0;
this._rQi = 0;
} else if (this._rQlen > this._rQmax) {
this._expand_compact_rQ();
}
} else {
Util.Debug("Ignoring empty message");
+53
View File
@@ -90,6 +90,29 @@ WebUtil.getQueryVar = function (name, defVal) {
}
};
// Read a hash fragment variable
WebUtil.getHashVar = function (name, defVal) {
"use strict";
var re = new RegExp('.*[&#]' + name + '=([^&]*)'),
match = document.location.hash.match(re);
if (typeof defVal === 'undefined') { defVal = null; }
if (match) {
return decodeURIComponent(match[1]);
} else {
return defVal;
}
};
// Read a variable from the fragment or the query string
// Fragment takes precedence
WebUtil.getConfigVar = function (name, defVal) {
"use strict";
var val = WebUtil.getHashVar(name);
if (val === null) {
val = WebUtil.getQueryVar(name, defVal);
}
return val;
};
/*
* Cookie handling. Dervied from: http://www.quirksmode.org/js/cookies.html
@@ -237,3 +260,33 @@ WebUtil.selectStylesheet = function (sheet) {
}
return sheet;
};
WebUtil.injectParamIfMissing = function (path, param, value) {
// force pretend that we're dealing with a relative path
// (assume that we wanted an extra if we pass one in)
path = "/" + path;
var elem = document.createElement('a');
elem.href = path;
var param_eq = encodeURIComponent(param) + "=";
var query;
if (elem.search) {
query = elem.search.slice(1).split('&');
} else {
query = [];
}
if (!query.some(function (v) { return v.startsWith(param_eq); })) {
query.push(param_eq + encodeURIComponent(value));
elem.search = "?" + query.join("&");
}
// some browsers (e.g. IE11) may occasionally omit the leading slash
// in the elem.pathname string. Handle that case gracefully.
if (elem.pathname.charAt(0) == "/") {
return elem.pathname.slice(1) + elem.search + elem.hash;
} else {
return elem.pathname + elem.search + elem.hash;
}
};
+6 -3
View File
@@ -119,9 +119,9 @@ module.exports = function(config) {
'include/input.js',
'include/websock.js',
'include/rfb.js',
'include/jsunzip.js',
'include/des.js',
'include/display.js',
'include/inflator.js',
'tests/test.*.js'
],
@@ -178,14 +178,17 @@ module.exports = function(config) {
// 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
captureTimeout: 240000,
// similarly to above
browserNoActivityTimeout: 100000,
};
if (useSauce) {
my_conf.captureTimeout = 0; // use SL timeout
my_conf.sauceLabs = {
testName: 'noVNC Tests (all)',
startConnect: true,
startConnect: false,
tunnelIdentifier: process.env.TRAVIS_JOB_NUMBER
};
}
+13 -13
View File
@@ -1,6 +1,6 @@
{
"name": "noVNC",
"version": "0.5.0",
"version": "0.5.1",
"description": "An HTML5 VNC client",
"main": "karma.conf.js",
"directories": {
@@ -8,7 +8,7 @@
"test": "tests"
},
"scripts": {
"test": "karma start karma.conf.js"
"test": "PATH=$PATH:node_modules/karma/bin karma start karma.conf.js"
},
"repository": {
"type": "git",
@@ -28,22 +28,22 @@
"devDependencies": {
"ansi": "^0.3.0",
"casperjs": "^1.1.0-beta3",
"chai": "^1.10.0",
"commander": "^2.5.0",
"karma": "^0.12.25",
"chai": "^2.1.0",
"commander": "^2.6.0",
"karma": "^0.12.31",
"karma-chai": "^0.1.0",
"karma-mocha": "^0.1.9",
"karma-mocha-reporter": "^0.3.1",
"karma-mocha": "^0.1.10",
"karma-mocha-reporter": "^1.0.0",
"karma-phantomjs-launcher": "^0.1.4",
"karma-sauce-launcher": "^0.2.10",
"karma-sinon": "^1.0.3",
"karma-sinon": "^1.0.4",
"karma-sinon-chai-latest": "^0.1.0",
"mocha": "^2.0.1",
"mocha": "^2.1.0",
"open": "^0.0.5",
"phantom": "^0.7.0",
"phantomjs": "^1.9.12",
"sinon": "^1.12.1",
"sinon-chai": "^2.6.0",
"phantom": "^0.7.2",
"phantomjs": "^1.9.15",
"sinon": "^1.12.2",
"sinon-chai": "^2.7.0",
"spooky": "^0.2.5",
"temp": "^0.8.1"
}
+78 -4
View File
@@ -5,7 +5,22 @@ chai.use(function (_chai, utils) {
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),
var same = true;
var len = data_cl.length;
if (len != target_data.length) {
same = false;
} else {
for (var i = 0; i < len; i++) {
if (data[i] != target_data[i]) {
same = false;
break;
}
}
}
if (!same) {
console.log("expected data: %o, actual data: %o", target_data, data);
}
this.assert(same,
"expected #{this} to have displayed the image #{exp}, but instead it displayed #{act}",
"expected #{this} not to have displayed the image #{act}",
target_data,
@@ -14,11 +29,70 @@ chai.use(function (_chai, utils) {
_chai.Assertion.addMethod('sent', function (target_data) {
var obj = this._obj;
obj.inspect = function () {
var res = { _websocket: obj._websocket, rQi: obj._rQi, _rQ: new Uint8Array(obj._rQ.buffer, 0, obj._rQlen),
_sQ: new Uint8Array(obj._sQ.buffer, 0, obj._sQlen) };
res.prototype = obj;
return res;
};
var data = obj._websocket._get_sent_data();
this.assert(utils.eql(data, target_data),
var same = true;
for (var i = 0; i < obj.length; i++) {
if (data[i] != target_data[i]) {
same = false;
break;
}
}
if (!same) {
console.log("expected data: %o, actual data: %o", target_data, data);
}
this.assert(same,
"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);
Array.prototype.slice.call(target_data),
Array.prototype.slice.call(data));
});
_chai.Assertion.addProperty('array', function () {
utils.flag(this, 'array', true);
});
_chai.Assertion.overwriteMethod('equal', function (_super) {
return function assertArrayEqual(target) {
if (utils.flag(this, 'array')) {
var obj = this._obj;
var i;
var same = true;
if (utils.flag(this, 'deep')) {
for (i = 0; i < obj.length; i++) {
if (!utils.eql(obj[i], target[i])) {
same = false;
break;
}
}
this.assert(same,
"expected #{this} to have elements deeply equal to #{exp}",
"expected #{this} not to have elements deeply equal to #{exp}",
Array.prototype.slice.call(target));
} else {
for (i = 0; i < obj.length; i++) {
if (obj[i] != target[i]) {
same = false;
break;
}
}
this.assert(same,
"expected #{this} to have elements equal to #{exp}",
"expected #{this} not to have elements equal to #{exp}",
Array.prototype.slice.call(target));
}
} else {
_super.apply(this, arguments);
}
};
});
});
+2 -7
View File
@@ -51,14 +51,9 @@ var FakeWebSocket;
},
_get_sent_data: function () {
var arr = [];
for (var i = 0; i < this.bufferedAmount; i++) {
arr[i] = this._send_queue[i];
}
var res = new Uint8Array(this._send_queue.buffer, 0, this.bufferedAmount);
this.bufferedAmount = 0;
return arr;
return res;
},
_open: function (data) {
+1
View File
@@ -26,6 +26,7 @@
<script src="../include/util.js"></script>
<script src="../include/webutil.js"></script>
<script src="../include/base64.js"></script>
<script src="../include/keysym.js"></script>
<script src="../include/keysymdef.js"></script>
<script src="../include/keyboard.js"></script>
<script src="../include/input.js"></script>
+13 -1
View File
@@ -15,7 +15,19 @@ var casper_opts = {
}
};
var provide_emitter = function(file_paths) {
var provide_emitter = function(file_paths, debug_port) {
if (debug_port) {
casper_opts.child['remote-debugger-port'] = debug_port;
var debug_url = ('https://localhost:' + debug_port +
'/webkit/inspector/inspector.html?page=');
console.info('[remote-debugger] Navigate to ' + debug_url + '1 and ' +
'run `__run();` in the console to continue loading.' +
'\n[remote-debugger] Navigate to ' + debug_url + '2 to ' +
'view the actual page source.\n' +
'[remote-debugger] Use the `debugger;` statement to ' +
'trigger an initial breakpoint.');
}
var spooky = new Spooky(casper_opts, function(err) {
if (err) {
if (err.stack) console.warn(err.stack);
+21 -2
View File
@@ -20,6 +20,7 @@ program
.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')
.option('--debugger <port>', 'Enable the remote debugger for CasperJS')
.parse(process.argv);
if (program.tests.length === 0) {
@@ -202,7 +203,7 @@ if (!program.outputHtml && !program.generateHtml) {
.write("\n");
//console.log("Running tests %s using provider %s", program.tests.join(', '), prov.name);
var provider = prov.provide_emitter(file_paths);
var provider = prov.provide_emitter(file_paths, program.debugger);
provider.on('test_ready', function(test_json) {
console.log('');
@@ -249,6 +250,24 @@ if (!program.outputHtml && !program.generateHtml) {
console.log('');
if (test_json.num_fails > 0 || program.printAll) {
var extract_error_lines = function (err) {
// the split is to avoid a weird thing where in PhantomJS where we get a stack trace too
var err_lines = err.split('\n');
if (err_lines.length == 1) {
return err_lines[0];
} else {
var ind;
for (ind = 0; ind < err_lines.length; ind++) {
var at_ind = err_lines[ind].trim().indexOf('at ');
if (at_ind === 0) {
break;
}
}
return err_lines.slice(0, ind).join('\n');
}
};
var traverse_tree = function(indentation, node) {
if (node.type == 'suite') {
if (!node.has_subfailures && !program.printAll) return;
@@ -280,7 +299,7 @@ if (!program.outputHtml && !program.generateHtml) {
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
console.log(' '+extract_error_lines(node.error));
cursor.reset();
console.log('');
}
+132 -24
View File
@@ -28,35 +28,29 @@ describe('Display/Canvas Helper', function () {
describe('checking for cursor uri support', function () {
beforeEach(function () {
this._old_change_cursor = Display.changeCursor;
this._old_browser_supports_cursor_uris = Util.browserSupportsCursorURIs;
});
it('should disable cursor URIs if there is no support', function () {
Display.changeCursor = function(target) {
target.style.cursor = undefined;
};
Util.browserSupportsCursorURIs = function () { return false; };
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';
};
Util.browserSupportsCursorURIs = function () { return true; };
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';
};
Util.browserSupportsCursorURIs = function () { return false; };
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;
Util.browserSupportsCursorURIs = this._old_browser_supports_cursor_uris;
});
});
@@ -65,13 +59,15 @@ describe('Display/Canvas Helper', function () {
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.viewportChangeSize(3, 3);
display.viewportChangePos(1, 1);
display.getCleanDirtyReset();
});
it('should take viewport location into consideration when drawing images', function () {
display.resize(4, 4);
display.viewportChange(0, 0, 2, 2);
display.set_width(4);
display.set_height(4);
display.viewportChangeSize(2, 2);
display.drawImage(make_image_canvas(basic_data), 1, 1);
var expected = new Uint8Array(16);
@@ -82,7 +78,7 @@ describe('Display/Canvas Helper', function () {
});
it('should redraw the left side when shifted left', function () {
display.viewportChange(-1, 0, 3, 3);
display.viewportChangePos(-1, 0);
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);
@@ -90,7 +86,7 @@ describe('Display/Canvas Helper', function () {
});
it('should redraw the right side when shifted right', function () {
display.viewportChange(1, 0, 3, 3);
display.viewportChangePos(1, 0);
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);
@@ -98,7 +94,7 @@ describe('Display/Canvas Helper', function () {
});
it('should redraw the top part when shifted up', function () {
display.viewportChange(0, -1, 3, 3);
display.viewportChangePos(0, -1);
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);
@@ -106,7 +102,7 @@ describe('Display/Canvas Helper', function () {
});
it('should redraw the bottom part when shifted down', function () {
display.viewportChange(0, 1, 3, 3);
display.viewportChangePos(0, 1);
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);
@@ -114,7 +110,7 @@ describe('Display/Canvas Helper', function () {
});
it('should reset the entire viewport to being clean after calculating the clean/dirty boxes', function () {
display.viewportChange(0, 1, 3, 3);
display.viewportChangePos(0, 1);
var cdr1 = display.getCleanDirtyReset();
var cdr2 = display.getCleanDirtyReset();
expect(cdr1).to.not.deep.equal(cdr2);
@@ -132,6 +128,40 @@ describe('Display/Canvas Helper', function () {
});
});
describe('clipping', function () {
var display;
beforeEach(function () {
display = new Display({ target: document.createElement('canvas'), prefer_js: false, viewport: true });
display.resize(4, 3);
});
it('should report true when no max-size and framebuffer > viewport', function () {
display.viewportChangeSize(2,2);
var clipping = display.clippingDisplay();
expect(clipping).to.be.true;
});
it('should report false when no max-size and framebuffer = viewport', function () {
var clipping = display.clippingDisplay();
expect(clipping).to.be.false;
});
it('should report true when viewport > max-size and framebuffer > viewport', function () {
display.viewportChangeSize(2,2);
display.set_maxWidth(1);
display.set_maxHeight(2);
var clipping = display.clippingDisplay();
expect(clipping).to.be.true;
});
it('should report true when viewport > max-size and framebuffer = viewport', function () {
display.set_maxWidth(1);
display.set_maxHeight(2);
var clipping = display.clippingDisplay();
expect(clipping).to.be.true;
});
});
describe('resizing', function () {
var display;
beforeEach(function () {
@@ -146,9 +176,87 @@ describe('Display/Canvas Helper', function () {
});
it('should update the viewport dimensions', function () {
sinon.spy(display, 'viewportChange');
sinon.spy(display, 'viewportChangeSize');
display.resize(2, 2);
expect(display.viewportChange).to.have.been.calledOnce;
expect(display.viewportChangeSize).to.have.been.calledOnce;
});
});
describe('rescaling', function () {
var display;
var canvas;
beforeEach(function () {
display = new Display({ target: document.createElement('canvas'), prefer_js: false, viewport: true });
display.resize(4, 3);
canvas = display.get_target();
document.body.appendChild(canvas);
});
afterEach(function () {
document.body.removeChild(canvas);
});
it('should not change the bitmap size of the canvas', function () {
display.set_scale(0.5);
expect(canvas.width).to.equal(4);
expect(canvas.height).to.equal(3);
});
it('should change the effective rendered size of the canvas', function () {
display.set_scale(0.5);
expect(canvas.clientWidth).to.equal(2);
expect(canvas.clientHeight).to.equal(2);
});
});
describe('autoscaling', function () {
var display;
var canvas;
beforeEach(function () {
display = new Display({ target: document.createElement('canvas'), prefer_js: false, viewport: true });
display.resize(4, 3);
canvas = display.get_target();
document.body.appendChild(canvas);
});
afterEach(function () {
document.body.removeChild(canvas);
});
it('should preserve aspect ratio while autoscaling', function () {
display.autoscale(16, 9);
expect(canvas.clientWidth / canvas.clientHeight).to.equal(4 / 3);
});
it('should use width to determine scale when the current aspect ratio is wider than the target', function () {
expect(display.autoscale(9, 16)).to.equal(9 / 4);
expect(canvas.clientWidth).to.equal(9);
expect(canvas.clientHeight).to.equal(7); // round 9 / (4 / 3)
});
it('should use height to determine scale when the current aspect ratio is taller than the target', function () {
expect(display.autoscale(16, 9)).to.equal(3); // 9 / 3
expect(canvas.clientWidth).to.equal(12); // 16 * (4 / 3)
expect(canvas.clientHeight).to.equal(9);
});
it('should not change the bitmap size of the canvas', function () {
display.autoscale(16, 9);
expect(canvas.width).to.equal(4);
expect(canvas.height).to.equal(3);
});
it('should not upscale when downscaleOnly is true', function () {
expect(display.autoscale(2, 2, true)).to.equal(0.5);
expect(canvas.clientWidth).to.equal(2);
expect(canvas.clientHeight).to.equal(2);
expect(display.autoscale(16, 9, true)).to.equal(1.0);
expect(canvas.clientWidth).to.equal(4);
expect(canvas.clientHeight).to.equal(3);
});
});
@@ -245,9 +353,9 @@ describe('Display/Canvas Helper', function () {
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]);
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);
});
+304 -91
View File
@@ -1,4 +1,4 @@
// requires local modules: util, base64, websock, rfb, keyboard, keysym, keysymdef, input, jsunzip, des, display
// requires local modules: util, websock, rfb, keyboard, keysym, keysymdef, input, inflator, des, display
// requires test modules: fake.websocket, assertions
/* jshint expr: true */
var assert = chai.assert;
@@ -18,6 +18,27 @@ describe('Remote Frame Buffer Protocol Client', function() {
before(FakeWebSocket.replace);
after(FakeWebSocket.restore);
before(function () {
this.clock = sinon.useFakeTimers();
// Use a single set of buffers instead of reallocating to
// speed up tests
var sock = new Websock();
var _sQ = new Uint8Array(sock._sQbufferSize);
var rQ = new Uint8Array(sock._rQbufferSize);
Websock.prototype._old_allocate_buffers = Websock.prototype._allocate_buffers;
Websock.prototype._allocate_buffers = function () {
this._sQ = _sQ;
this._rQ = rQ;
};
});
after(function () {
Websock.prototype._allocate_buffers = Websock.prototype._old_allocate_buffers;
this.clock.restore();
});
describe('Public API Basic Behavior', function () {
var client;
beforeEach(function () {
@@ -62,6 +83,24 @@ describe('Remote Frame Buffer Protocol Client', function() {
expect(client._updateState).to.have.been.calledOnce;
expect(client._updateState).to.have.been.calledWith('disconnect');
});
it('should unregister error event handler', function () {
sinon.spy(client._sock, 'off');
client.disconnect();
expect(client._sock.off).to.have.been.calledWith('error');
});
it('should unregister message event handler', function () {
sinon.spy(client._sock, 'off');
client.disconnect();
expect(client._sock.off).to.have.been.calledWith('message');
});
it('should unregister open event handler', function () {
sinon.spy(client._sock, 'off');
client.disconnect();
expect(client._sock.off).to.have.been.calledWith('open');
});
});
describe('#sendPassword', function () {
@@ -87,34 +126,34 @@ describe('Remote Frame Buffer Protocol Client', function() {
client._sock = new Websock();
client._sock.open('ws://', 'binary');
client._sock._websocket._open();
sinon.spy(client._sock, 'send');
sinon.spy(client._sock, 'flush');
client._rfb_state = "normal";
client._view_only = false;
});
it('should sent ctrl[down]-alt[down]-del[down] then del[up]-alt[up]-ctrl[up]', function () {
var expected = [];
expected = expected.concat(RFB.messages.keyEvent(0xFFE3, 1));
expected = expected.concat(RFB.messages.keyEvent(0xFFE9, 1));
expected = expected.concat(RFB.messages.keyEvent(0xFFFF, 1));
expected = expected.concat(RFB.messages.keyEvent(0xFFFF, 0));
expected = expected.concat(RFB.messages.keyEvent(0xFFE9, 0));
expected = expected.concat(RFB.messages.keyEvent(0xFFE3, 0));
var expected = {_sQ: new Uint8Array(48), _sQlen: 0};
RFB.messages.keyEvent(expected, 0xFFE3, 1);
RFB.messages.keyEvent(expected, 0xFFE9, 1);
RFB.messages.keyEvent(expected, 0xFFFF, 1);
RFB.messages.keyEvent(expected, 0xFFFF, 0);
RFB.messages.keyEvent(expected, 0xFFE9, 0);
RFB.messages.keyEvent(expected, 0xFFE3, 0);
client.sendCtrlAltDel();
expect(client._sock).to.have.sent(expected);
expect(client._sock).to.have.sent(expected._sQ);
});
it('should not send the keys if we are not in a normal state', function () {
client._rfb_state = "broken";
client.sendCtrlAltDel();
expect(client._sock.send).to.not.have.been.called;
expect(client._sock.flush).to.not.have.been.called;
});
it('should not send the keys if we are set as view_only', function () {
client._view_only = true;
client.sendCtrlAltDel();
expect(client._sock.send).to.not.have.been.called;
expect(client._sock.flush).to.not.have.been.called;
});
});
@@ -123,34 +162,36 @@ describe('Remote Frame Buffer Protocol Client', function() {
client._sock = new Websock();
client._sock.open('ws://', 'binary');
client._sock._websocket._open();
sinon.spy(client._sock, 'send');
sinon.spy(client._sock, 'flush');
client._rfb_state = "normal";
client._view_only = false;
});
it('should send a single key with the given code and state (down = true)', function () {
var expected = RFB.messages.keyEvent(123, 1);
var expected = {_sQ: new Uint8Array(8), _sQlen: 0};
RFB.messages.keyEvent(expected, 123, 1);
client.sendKey(123, true);
expect(client._sock).to.have.sent(expected);
expect(client._sock).to.have.sent(expected._sQ);
});
it('should send both a down and up event if the state is not specified', function () {
var expected = RFB.messages.keyEvent(123, 1);
expected = expected.concat(RFB.messages.keyEvent(123, 0));
var expected = {_sQ: new Uint8Array(16), _sQlen: 0};
RFB.messages.keyEvent(expected, 123, 1);
RFB.messages.keyEvent(expected, 123, 0);
client.sendKey(123);
expect(client._sock).to.have.sent(expected);
expect(client._sock).to.have.sent(expected._sQ);
});
it('should not send the key if we are not in a normal state', function () {
client._rfb_state = "broken";
client.sendKey(123);
expect(client._sock.send).to.not.have.been.called;
expect(client._sock.flush).to.not.have.been.called;
});
it('should not send the key if we are set as view_only', function () {
client._view_only = true;
client.sendKey(123);
expect(client._sock.send).to.not.have.been.called;
expect(client._sock.flush).to.not.have.been.called;
});
});
@@ -159,21 +200,64 @@ describe('Remote Frame Buffer Protocol Client', function() {
client._sock = new Websock();
client._sock.open('ws://', 'binary');
client._sock._websocket._open();
sinon.spy(client._sock, 'send');
sinon.spy(client._sock, 'flush');
client._rfb_state = "normal";
client._view_only = false;
});
it('should send the given text in a paste event', function () {
var expected = RFB.messages.clientCutText('abc');
var expected = {_sQ: new Uint8Array(11), _sQlen: 0};
RFB.messages.clientCutText(expected, 'abc');
client.clipboardPasteFrom('abc');
expect(client._sock).to.have.sent(expected);
expect(client._sock).to.have.sent(expected._sQ);
});
it('should not send the text if we are not in a normal state', function () {
client._rfb_state = "broken";
client.clipboardPasteFrom('abc');
expect(client._sock.send).to.not.have.been.called;
expect(client._sock.flush).to.not.have.been.called;
});
});
describe("#requestDesktopSize", function () {
beforeEach(function() {
client._sock = new Websock();
client._sock.open('ws://', 'binary');
client._sock._websocket._open();
sinon.spy(client._sock, 'flush');
client._rfb_state = "normal";
client._view_only = false;
client._supportsSetDesktopSize = true;
});
it('should send the request with the given width and height', function () {
var expected = [251];
expected.push8(0); // padding
expected.push16(1); // width
expected.push16(2); // height
expected.push8(1); // number-of-screens
expected.push8(0); // padding before screen array
expected.push32(0); // id
expected.push16(0); // x-position
expected.push16(0); // y-position
expected.push16(1); // width
expected.push16(2); // height
expected.push32(0); // flags
client.requestDesktopSize(1, 2);
expect(client._sock).to.have.sent(new Uint8Array(expected));
});
it('should not send the request if the client has not recieved a ExtendedDesktopSize rectangle', function () {
client._supportsSetDesktopSize = false;
client.requestDesktopSize(1,2);
expect(client._sock.flush).to.not.have.been.called;
});
it('should not send the request if we are not in a normal state', function () {
client._rfb_state = "broken";
client.requestDesktopSize(1,2);
expect(client._sock.flush).to.not.have.been.called;
});
});
@@ -182,7 +266,7 @@ describe('Remote Frame Buffer Protocol Client', function() {
client._sock = new Websock();
client._sock.open('ws://', 'binary');
client._sock._websocket._open();
sinon.spy(client._sock, 'send');
sinon.spy(client._sock, 'flush');
client._rfb_state = "normal";
client._view_only = false;
client._rfb_xvp_ver = 1;
@@ -190,27 +274,27 @@ describe('Remote Frame Buffer Protocol Client', function() {
it('should send the shutdown signal on #xvpShutdown', function () {
client.xvpShutdown();
expect(client._sock).to.have.sent([0xFA, 0x00, 0x01, 0x02]);
expect(client._sock).to.have.sent(new Uint8Array([0xFA, 0x00, 0x01, 0x02]));
});
it('should send the reboot signal on #xvpReboot', function () {
client.xvpReboot();
expect(client._sock).to.have.sent([0xFA, 0x00, 0x01, 0x03]);
expect(client._sock).to.have.sent(new Uint8Array([0xFA, 0x00, 0x01, 0x03]));
});
it('should send the reset signal on #xvpReset', function () {
client.xvpReset();
expect(client._sock).to.have.sent([0xFA, 0x00, 0x01, 0x04]);
expect(client._sock).to.have.sent(new Uint8Array([0xFA, 0x00, 0x01, 0x04]));
});
it('should support sending arbitrary XVP operations via #xvpOp', function () {
client.xvpOp(1, 7);
expect(client._sock).to.have.sent([0xFA, 0x00, 0x01, 0x07]);
expect(client._sock).to.have.sent(new Uint8Array([0xFA, 0x00, 0x01, 0x07]));
});
it('should not send XVP operations with higher versions than we support', function () {
expect(client.xvpOp(2, 7)).to.be.false;
expect(client._sock.send).to.not.have.been.called;
expect(client._sock.flush).to.not.have.been.called;
});
});
});
@@ -423,7 +507,7 @@ describe('Remote Frame Buffer Protocol Client', function() {
expect(client._rfb_version).to.equal(0);
var sent_data = client._sock._websocket._get_sent_data();
expect(sent_data.slice(0, 5)).to.deep.equal([1, 2, 3, 4, 5]);
expect(new Uint8Array(sent_data.buffer, 0, 5)).to.array.equal(new Uint8Array([1, 2, 3, 4, 5]));
});
it('should interpret version 003.003 as version 3.3', function () {
@@ -480,7 +564,7 @@ describe('Remote Frame Buffer Protocol Client', function() {
send_ver('000.000', client);
expect(client._rfb_version).to.equal(0);
var sent_data = client._sock._websocket._get_sent_data();
expect(sent_data.slice(0, 5)).to.deep.equal([1, 2, 3, 4, 5]);
expect(new Uint8Array(sent_data.buffer, 0, 5)).to.array.equal(new Uint8Array([1, 2, 3, 4, 5]));
expect(sent_data).to.have.length(250);
send_ver('003.008', client);
@@ -503,7 +587,7 @@ describe('Remote Frame Buffer Protocol Client', function() {
expected[i] = expected_str.charCodeAt(i);
}
expect(client._sock).to.have.sent(expected);
expect(client._sock).to.have.sent(new Uint8Array(expected));
});
it('should transition to the Security state on successful negotiation', function () {
@@ -536,7 +620,7 @@ describe('Remote Frame Buffer Protocol Client', function() {
var auth_schemes = [2, 1, 2];
client._sock._websocket._receive_data(auth_schemes);
expect(client._rfb_auth_scheme).to.equal(2);
expect(client._sock).to.have.sent([2]);
expect(client._sock).to.have.sent(new Uint8Array([2]));
});
it('should fail if there are no supported schemes for versions >= 3.7', function () {
@@ -642,7 +726,7 @@ describe('Remote Frame Buffer Protocol Client', function() {
client._sock._websocket._receive_data(new Uint8Array(challenge));
var des_pass = RFB.genDES('passwd', challenge);
expect(client._sock).to.have.sent(des_pass);
expect(client._sock).to.have.sent(new Uint8Array(des_pass));
});
it('should transition to SecurityResult immediately after sending the password', function () {
@@ -699,7 +783,7 @@ describe('Remote Frame Buffer Protocol Client', function() {
var expected = [22, 4, 6]; // auth selection, len user, len target
for (var i = 0; i < 10; i++) { expected[i+3] = 'usertarget'.charCodeAt(i); }
expect(client._sock).to.have.sent(expected);
expect(client._sock).to.have.sent(new Uint8Array(expected));
});
});
@@ -747,14 +831,14 @@ describe('Remote Frame Buffer Protocol Client', function() {
it('should choose the notunnel tunnel type', function () {
send_num_str_pairs([[0, 'TGHT', 'NOTUNNEL'], [123, 'OTHR', 'SOMETHNG']], client);
expect(client._sock).to.have.sent([0, 0, 0, 0]);
expect(client._sock).to.have.sent(new Uint8Array([0, 0, 0, 0]));
});
it('should continue to sub-auth negotiation after tunnel negotiation', function () {
send_num_str_pairs([[0, 'TGHT', 'NOTUNNEL']], client);
client._sock._websocket._get_sent_data(); // skip the tunnel choice here
send_num_str_pairs([[1, 'STDV', 'NOAUTH__']], client);
expect(client._sock).to.have.sent([0, 0, 0, 1]);
expect(client._sock).to.have.sent(new Uint8Array([0, 0, 0, 1]));
expect(client._rfb_state).to.equal('SecurityResult');
});
@@ -770,7 +854,7 @@ describe('Remote Frame Buffer Protocol Client', function() {
it('should accept the "no auth" auth type and transition to SecurityResult', function () {
client._rfb_tightvnc = true;
send_num_str_pairs([[1, 'STDV', 'NOAUTH__']], client);
expect(client._sock).to.have.sent([0, 0, 0, 1]);
expect(client._sock).to.have.sent(new Uint8Array([0, 0, 0, 1]));
expect(client._rfb_state).to.equal('SecurityResult');
});
@@ -778,7 +862,7 @@ describe('Remote Frame Buffer Protocol Client', function() {
client._rfb_tightvnc = true;
client._negotiate_std_vnc_auth = sinon.spy();
send_num_str_pairs([[2, 'STDV', 'VNCAUTH__']], client);
expect(client._sock).to.have.sent([0, 0, 0, 2]);
expect(client._sock).to.have.sent(new Uint8Array([0, 0, 0, 2]));
expect(client._negotiate_std_vnc_auth).to.have.been.calledOnce;
expect(client._rfb_auth_scheme).to.equal(2);
});
@@ -842,13 +926,13 @@ describe('Remote Frame Buffer Protocol Client', function() {
it('should send 1 if we are in shared mode', function () {
client.set_shared(true);
client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 0]));
expect(client._sock).to.have.sent([1]);
expect(client._sock).to.have.sent(new Uint8Array([1]));
});
it('should send 0 if we are not in shared mode', function () {
client.set_shared(false);
client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 0]));
expect(client._sock).to.have.sent([0]);
expect(client._sock).to.have.sent(new Uint8Array([0]));
});
});
@@ -985,21 +1069,16 @@ describe('Remote Frame Buffer Protocol Client', function() {
it('should reply with the pixel format, client encodings, and initial update request', function () {
client.set_true_color(true);
client.set_local_cursor(false);
var expected = RFB.messages.pixelFormat(4, 3, true);
expected = expected.concat(RFB.messages.clientEncodings(client._encodings, false, true));
// we skip the cursor encoding
var expected = {_sQ: new Uint8Array(34 + 4 * (client._encodings.length - 1)), _sQlen: 0};
RFB.messages.pixelFormat(expected, 4, 3, true);
RFB.messages.clientEncodings(expected, client._encodings, false, true);
var expected_cdr = { cleanBox: { x: 0, y: 0, w: 0, h: 0 },
dirtyBoxes: [ { x: 0, y: 0, w: 27, h: 32 } ] };
expected = expected.concat(RFB.messages.fbUpdateRequests(expected_cdr, 27, 32));
RFB.messages.fbUpdateRequests(expected, expected_cdr, 27, 32);
send_server_init({ width: 27, height: 32 }, client);
expect(client._sock).to.have.sent(expected);
});
it('should check for sending mouse events', function () {
// be lazy with our checking so we don't have to check through the whole sent buffer
sinon.spy(client, '_checkEvents');
send_server_init({}, client);
expect(client._checkEvents).to.have.been.calledOnce;
expect(client._sock).to.have.sent(expected._sQ);
});
it('should transition to the "normal" state', function () {
@@ -1082,14 +1161,15 @@ describe('Remote Frame Buffer Protocol Client', function() {
}
it('should send an update request if there is sufficient data', function () {
var expected_msg = {_sQ: new Uint8Array(10), _sQlen: 0};
var expected_cdr = { cleanBox: { x: 0, y: 0, w: 0, h: 0 },
dirtyBoxes: [ { x: 0, y: 0, w: 240, h: 20 } ] };
var expected_msg = RFB.messages.fbUpdateRequests(expected_cdr, 240, 20);
RFB.messages.fbUpdateRequests(expected_msg, expected_cdr, 240, 20);
client._framebufferUpdate = function () { return true; };
client._sock._websocket._receive_data(new Uint8Array([0]));
expect(client._sock).to.have.sent(expected_msg);
expect(client._sock).to.have.sent(expected_msg._sQ);
});
it('should not send an update request if we need more data', function () {
@@ -1098,9 +1178,10 @@ describe('Remote Frame Buffer Protocol Client', function() {
});
it('should resume receiving an update if we previously did not have enough data', function () {
var expected_msg = {_sQ: new Uint8Array(10), _sQlen: 0};
var expected_cdr = { cleanBox: { x: 0, y: 0, w: 0, h: 0 },
dirtyBoxes: [ { x: 0, y: 0, w: 240, h: 20 } ] };
var expected_msg = RFB.messages.fbUpdateRequests(expected_cdr, 240, 20);
RFB.messages.fbUpdateRequests(expected_msg, expected_cdr, 240, 20);
// just enough to set FBU.rects
client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 3]));
@@ -1109,7 +1190,7 @@ describe('Remote Frame Buffer Protocol Client', function() {
client._framebufferUpdate = function () { return true; }; // we magically have enough data
// 247 should *not* be used as the message type here
client._sock._websocket._receive_data(new Uint8Array([247]));
expect(client._sock).to.have.sent(expected_msg);
expect(client._sock).to.have.sent(expected_msg._sQ);
});
it('should parse out information from a header before any actual data comes in', function () {
@@ -1425,6 +1506,122 @@ describe('Remote Frame Buffer Protocol Client', function() {
expect(client._display.resize).to.have.been.calledWith(20, 50);
});
describe('the ExtendedDesktopSize pseudo-encoding handler', function () {
var client;
beforeEach(function () {
client = make_rfb();
client.connect('host', 8675);
client._sock._websocket._open();
client._rfb_state = 'normal';
client._fb_name = 'some device';
client._supportsSetDesktopSize = false;
// a really small frame
client._fb_width = 4;
client._fb_height = 4;
client._display._fb_width = 4;
client._display._fb_height = 4;
client._display._viewportLoc.w = 4;
client._display._viewportLoc.h = 4;
client._fb_Bpp = 4;
sinon.spy(client._display, 'resize');
client.set_onFBResize(sinon.spy());
});
function make_screen_data (nr_of_screens) {
var data = [];
data.push8(nr_of_screens); // number-of-screens
data.push8(0); // padding
data.push16(0); // padding
for (var i=0; i<nr_of_screens; i += 1) {
data.push32(0); // id
data.push16(0); // x-position
data.push16(0); // y-position
data.push16(20); // width
data.push16(50); // height
data.push32(0); // flags
}
return data;
}
it('should handle a resize requested by this client', function () {
var reason_for_change = 1; // requested by this client
var status_code = 0; // No error
send_fbu_msg([{ x: reason_for_change, y: status_code,
width: 20, height: 50, encoding: -308 }],
make_screen_data(1), client);
expect(client._supportsSetDesktopSize).to.be.true;
expect(client._fb_width).to.equal(20);
expect(client._fb_height).to.equal(50);
expect(client._display.resize).to.have.been.calledOnce;
expect(client._display.resize).to.have.been.calledWith(20, 50);
var spy = client.get_onFBResize();
expect(spy).to.have.been.calledOnce;
expect(spy).to.have.been.calledWith(sinon.match.any, 20, 50);
});
it('should handle a resize requested by another client', function () {
var reason_for_change = 2; // requested by another client
var status_code = 0; // No error
send_fbu_msg([{ x: reason_for_change, y: status_code,
width: 20, height: 50, encoding: -308 }],
make_screen_data(1), client);
expect(client._supportsSetDesktopSize).to.be.true;
expect(client._fb_width).to.equal(20);
expect(client._fb_height).to.equal(50);
expect(client._display.resize).to.have.been.calledOnce;
expect(client._display.resize).to.have.been.calledWith(20, 50);
var spy = client.get_onFBResize();
expect(spy).to.have.been.calledOnce;
expect(spy).to.have.been.calledWith(sinon.match.any, 20, 50);
});
it('should be able to recieve requests which contain data for multiple screens', function () {
var reason_for_change = 2; // requested by another client
var status_code = 0; // No error
send_fbu_msg([{ x: reason_for_change, y: status_code,
width: 60, height: 50, encoding: -308 }],
make_screen_data(3), client);
expect(client._supportsSetDesktopSize).to.be.true;
expect(client._fb_width).to.equal(60);
expect(client._fb_height).to.equal(50);
expect(client._display.resize).to.have.been.calledOnce;
expect(client._display.resize).to.have.been.calledWith(60, 50);
var spy = client.get_onFBResize();
expect(spy).to.have.been.calledOnce;
expect(spy).to.have.been.calledWith(sinon.match.any, 60, 50);
});
it('should not handle a failed request', function () {
var reason_for_change = 1; // requested by this client
var status_code = 1; // Resize is administratively prohibited
send_fbu_msg([{ x: reason_for_change, y: status_code,
width: 20, height: 50, encoding: -308 }],
make_screen_data(1), client);
expect(client._fb_width).to.equal(4);
expect(client._fb_height).to.equal(4);
expect(client._display.resize).to.not.have.been.called;
var spy = client.get_onFBResize();
expect(spy).to.not.have.been.called;
});
});
it.skip('should handle the Cursor pseudo-encoding', function () {
// TODO(directxman12): test
});
@@ -1515,58 +1712,61 @@ describe('Remote Frame Buffer Protocol Client', function() {
var client;
beforeEach(function () {
client = make_rfb();
client._sock.send = sinon.spy();
client._sock = new Websock();
client._sock.open('ws://', 'binary');
client._sock._websocket._open();
sinon.spy(client._sock, 'flush');
client._rfb_state = 'normal';
});
it('should not send button messages in view-only mode', function () {
client._view_only = true;
client._mouse._onMouseButton(0, 0, 1, 0x001);
expect(client._sock.send).to.not.have.been.called;
expect(client._sock.flush).to.not.have.been.called;
});
it('should not send movement messages in view-only mode', function () {
client._view_only = true;
client._mouse._onMouseMove(0, 0);
expect(client._sock.send).to.not.have.been.called;
expect(client._sock.flush).to.not.have.been.called;
});
it('should send a pointer event on mouse button presses', function () {
client._mouse._onMouseButton(10, 12, 1, 0x001);
expect(client._sock.send).to.have.been.calledOnce;
var pointer_msg = RFB.messages.pointerEvent(10, 12, 0x001);
expect(client._sock.send).to.have.been.calledWith(pointer_msg);
var pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0};
RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x001);
expect(client._sock).to.have.sent(pointer_msg._sQ);
});
it('should send a mask of 1 on mousedown', function () {
client._mouse._onMouseButton(10, 12, 1, 0x001);
expect(client._sock.send).to.have.been.calledOnce;
var pointer_msg = RFB.messages.pointerEvent(10, 12, 0x001);
expect(client._sock.send).to.have.been.calledWith(pointer_msg);
var pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0};
RFB.messages.pointerEvent(pointer_msg, 0, 10, 12, 0x001);
expect(client._sock).to.have.sent(pointer_msg._sQ);
});
it('should send a mask of 0 on mouseup', function () {
client._mouse_buttonMask = 0x001;
client._mouse._onMouseButton(10, 12, 0, 0x001);
expect(client._sock.send).to.have.been.calledOnce;
var pointer_msg = RFB.messages.pointerEvent(10, 12, 0x000);
expect(client._sock.send).to.have.been.calledWith(pointer_msg);
var pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0};
RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x000);
expect(client._sock).to.have.sent(pointer_msg._sQ);
});
it('should send a pointer event on mouse movement', function () {
client._mouse._onMouseMove(10, 12);
expect(client._sock.send).to.have.been.calledOnce;
var pointer_msg = RFB.messages.pointerEvent(10, 12, 0);
expect(client._sock.send).to.have.been.calledWith(pointer_msg);
var pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0};
RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x000);
expect(client._sock).to.have.sent(pointer_msg._sQ);
});
it('should set the button mask so that future mouse movements use it', function () {
client._mouse._onMouseButton(10, 12, 1, 0x010);
client._sock.send = sinon.spy();
client._mouse._onMouseMove(13, 9);
expect(client._sock.send).to.have.been.calledOnce;
var pointer_msg = RFB.messages.pointerEvent(13, 9, 0x010);
expect(client._sock.send).to.have.been.calledWith(pointer_msg);
var pointer_msg = {_sQ: new Uint8Array(12), _sQlen: 0};
RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x010);
RFB.messages.pointerEvent(pointer_msg, 13, 9, 0x010);
expect(client._sock).to.have.sent(pointer_msg._sQ);
});
// NB(directxman12): we don't need to test not sending messages in
@@ -1575,15 +1775,15 @@ describe('Remote Frame Buffer Protocol Client', function() {
it('should not send movement messages when viewport dragging', function () {
client._viewportDragging = true;
client._display.viewportChange = sinon.spy();
client._display.viewportChangePos = sinon.spy();
client._mouse._onMouseMove(13, 9);
expect(client._sock.send).to.not.have.been.called;
expect(client._sock.flush).to.not.have.been.called;
});
it('should not send button messages when initiating viewport dragging', function () {
client._viewportDrag = true;
client._mouse._onMouseButton(13, 9, 0x001);
expect(client._sock.send).to.not.have.been.called;
expect(client._sock.flush).to.not.have.been.called;
});
it('should be initiate viewport dragging on a button down event, if enabled', function () {
@@ -1603,15 +1803,17 @@ describe('Remote Frame Buffer Protocol Client', function() {
it('if enabled, viewportDragging should occur on mouse movement while a button is down', function () {
client._viewportDrag = true;
client._viewportDragging = true;
client._viewportDragPos = { x: 13, y: 9 };
client._display.viewportChange = sinon.spy();
client._viewportHasMoved = false;
client._viewportDragPos = { x: 23, y: 9 };
client._display.viewportChangePos = sinon.spy();
client._mouse._onMouseMove(10, 4);
expect(client._viewportDragging).to.be.true;
expect(client._viewportHasMoved).to.be.true;
expect(client._viewportDragPos).to.deep.equal({ x: 10, y: 4 });
expect(client._display.viewportChange).to.have.been.calledOnce;
expect(client._display.viewportChange).to.have.been.calledWith(3, 5);
expect(client._display.viewportChangePos).to.have.been.calledOnce;
expect(client._display.viewportChangePos).to.have.been.calledWith(13, 5);
});
});
@@ -1619,20 +1821,23 @@ describe('Remote Frame Buffer Protocol Client', function() {
var client;
beforeEach(function () {
client = make_rfb();
client._sock.send = sinon.spy();
client._sock = new Websock();
client._sock.open('ws://', 'binary');
client._sock._websocket._open();
sinon.spy(client._sock, 'flush');
});
it('should send a key message on a key press', function () {
client._keyboard._onKeyPress(1234, 1);
expect(client._sock.send).to.have.been.calledOnce;
var key_msg = RFB.messages.keyEvent(1234, 1);
expect(client._sock.send).to.have.been.calledWith(key_msg);
var key_msg = {_sQ: new Uint8Array(8), _sQlen: 0};
RFB.messages.keyEvent(key_msg, 1234, 1);
expect(client._sock).to.have.sent(key_msg._sQ);
});
it('should not send messages in view-only mode', function () {
client._view_only = true;
client._keyboard._onKeyPress(1234, 1);
expect(client._sock.send).to.not.have.been.called;
expect(client._sock.flush).to.not.have.been.called;
});
});
@@ -1650,7 +1855,7 @@ describe('Remote Frame Buffer Protocol Client', function() {
client.connect('host', 8675);
client._rfb_state = 'normal';
client._normal_msg = sinon.spy();
client._sock._websocket._receive_data(Base64.encode([]));
client._sock._websocket._receive_data(new Uint8Array([]));
expect(client._normal_msg).to.not.have.been.called;
});
@@ -1658,7 +1863,7 @@ describe('Remote Frame Buffer Protocol Client', function() {
client.connect('host', 8675);
client._rfb_state = 'normal';
client._normal_msg = sinon.spy();
client._sock._websocket._receive_data(Base64.encode([1, 2, 3]));
client._sock._websocket._receive_data(new Uint8Array([1, 2, 3]));
expect(client._normal_msg).to.have.been.calledOnce;
});
@@ -1666,7 +1871,7 @@ describe('Remote Frame Buffer Protocol Client', function() {
client.connect('host', 8675);
client._rfb_state = 'ProtocolVersion';
client._init_msg = sinon.spy();
client._sock._websocket._receive_data(Base64.encode([1, 2, 3]));
client._sock._websocket._receive_data(new Uint8Array([1, 2, 3]));
expect(client._init_msg).to.have.been.calledOnce;
});
@@ -1710,6 +1915,14 @@ describe('Remote Frame Buffer Protocol Client', function() {
expect(client._rfb_state).to.equal('failed');
});
it('should unregister close event handler', function () {
sinon.spy(client._sock, 'off');
client.connect('host', 8675);
client._rfb_state = 'disconnect';
client._sock._websocket.close();
expect(client._sock.off).to.have.been.calledWith('close');
});
// error events do nothing
});
});
+62 -85
View File
@@ -1,5 +1,5 @@
// requires local modules: websock, base64, util
// requires test modules: fake.websocket
// requires local modules: websock, util
// requires test modules: fake.websocket, assertions
/* jshint expr: true */
var assert = chai.assert;
var expect = chai.expect;
@@ -9,13 +9,14 @@ describe('Websock', function() {
describe('Queue methods', function () {
var sock;
var RQ_TEMPLATE = [0, 1, 2, 3, 4, 5, 6, 7];
var RQ_TEMPLATE = new Uint8Array([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]);
}
// skip init
sock._allocate_buffers();
sock._rQ.set(RQ_TEMPLATE);
sock._rQlen = RQ_TEMPLATE.length;
});
describe('rQlen', function () {
it('should return the length of the receive queue', function () {
@@ -49,14 +50,6 @@ describe('Websock', function() {
});
});
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();
@@ -84,7 +77,7 @@ describe('Websock', function() {
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(shifted).to.equal(String.fromCharCode.apply(null, Array.prototype.slice.call(new Uint8Array(RQ_TEMPLATE.buffer, bef_rQi, 3))));
expect(sock.rQlen()).to.equal(bef_len - 3);
});
@@ -99,8 +92,8 @@ describe('Websock', 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(shifted).to.be.an.instanceof(Uint8Array);
expect(shifted).to.array.equal(new Uint8Array(RQ_TEMPLATE.buffer, bef_rQi, 3));
expect(sock.rQlen()).to.equal(bef_len - 3);
});
@@ -123,19 +116,19 @@ describe('Websock', function() {
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));
expect(sl).to.be.an.instanceof(Uint8Array);
expect(sl).to.array.equal(new Uint8Array(RQ_TEMPLATE.buffer, 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));
expect(sl).to.array.equal(new Uint8Array(RQ_TEMPLATE.buffer, 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));
expect(sock.rQslice(0, 2)).to.array.equal(new Uint8Array(RQ_TEMPLATE.buffer, 1, 2));
});
});
@@ -180,7 +173,9 @@ describe('Websock', function() {
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];
sock._websocket.readyState = WebSocket.OPEN
sock._sQ = new Uint8Array([1, 2, 3]);
sock._sQlen = 3;
var encoded = sock._encode_message();
sock.flush();
@@ -196,7 +191,7 @@ describe('Websock', function() {
});
it('should not call send if we do not have anything queued up', function () {
sock._sQ = [];
sock._sQlen = 0;
sock.maxBufferedAmount = 10;
sock._websocket.bufferedAmount = 8;
@@ -222,7 +217,7 @@ describe('Websock', function() {
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]);
expect(new Uint8Array(sq.buffer, sock._sQlen - 3, 3)).to.array.equal(new Uint8Array([1, 2, 3]));
});
it('should call flush', function () {
@@ -257,6 +252,8 @@ describe('Websock', function() {
WebSocket.CONNECTING = old_WS.CONNECTING;
WebSocket.CLOSING = old_WS.CLOSING;
WebSocket.CLOSED = old_WS.CLOSED;
WebSocket.prototype.binaryType = 'arraybuffer';
});
describe('opening', function () {
@@ -265,22 +262,14 @@ describe('Websock', function() {
});
it('should open the actual websocket', function () {
sock.open('ws://localhost:8675', 'base64');
expect(WebSocket).to.have.been.calledWith('ws://localhost:8675', 'base64');
sock.open('ws://localhost:8675', 'binary');
expect(WebSocket).to.have.been.calledWith('ws://localhost:8675', 'binary');
});
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 specify a protocol besides binary', function () {
expect(function () { sock.open('ws:///', 'base64'); }).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')?
});
@@ -340,16 +329,15 @@ describe('Websock', function() {
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 fail if a protocol besides binary is requested', function () {
sock._websocket.protocol = 'base64';
expect(sock._websocket.onopen).to.throw(Error);
});
it('should assume base64 if no protocol was available on opening', function () {
it('should assume binary if no protocol was available on opening', function () {
sock._websocket.protocol = null;
sock._websocket.onopen();
expect(sock._mode).to.equal('base64');
expect(sock._mode).to.equal('binary');
});
it('should call the open event handler on opening', function () {
@@ -377,13 +365,7 @@ describe('Websock', 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');
sock._allocate_buffers();
});
it('should support adding binary Uint8Array data to the receive queue', function () {
@@ -395,16 +377,16 @@ describe('Websock', function() {
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';
var msg = { data: new Uint8Array([1, 2, 3]).buffer };
sock._mode = 'binary';
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';
var msg = { data: new Uint8Array([]).buffer };
sock._mode = 'binary';
sock._recv_message(msg);
expect(sock._eventHandlers.message).not.to.have.been.called;
});
@@ -412,21 +394,36 @@ describe('Websock', function() {
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._rQ = new Uint8Array([0, 1, 2, 3, 4, 5, 0, 0, 0, 0]);
sock._rQlen = 6;
sock.set_rQi(6);
sock._rQmax = 3;
var msg = { data: Base64.encode([1, 2, 3]) };
sock._mode = 'base64';
var msg = { data: new Uint8Array([1, 2, 3]).buffer };
sock._mode = 'binary';
sock._recv_message(msg);
expect(sock._rQ.length).to.equal(3);
expect(sock._rQlen).to.equal(3);
expect(sock.get_rQi()).to.equal(0);
});
it('should automatically resize the receive queue if the incoming message is too large', function () {
sock._rQ = new Uint8Array(20);
sock._rQlen = 0;
sock.set_rQi(0);
sock._rQbufferSize = 20;
sock._rQmax = 2;
var msg = { data: new Uint8Array(30).buffer };
sock._mode = 'binary';
sock._recv_message(msg);
expect(sock._rQlen).to.equal(30);
expect(sock.get_rQi()).to.equal(0);
expect(sock._rQ.length).to.equal(240); // keep the invariant that rQbufferSize / 8 >= rQlen
});
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';
var msg = { data: new Uint8Array([1, 2, 3]).buffer };
sock._mode = 'binary';
sock._recv_message(msg);
expect(sock._eventHandlers.error).to.have.been.calledOnce;
});
@@ -444,37 +441,17 @@ describe('Websock', function() {
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 only send the send queue up to the send queue length', function () {
sock._sQ = new Uint8Array([1, 2, 3, 4, 5]);
sock._sQlen = 3;
var res = sock._encode_message();
expect(res).to.array.equal(new Uint8Array([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]);
expect(sock._websocket._get_sent_data()).to.array.equal(new Uint8Array([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]);
});
});
});
});
+4 -5
View File
@@ -97,7 +97,7 @@
deltaY = lastPos.y - y; // drag frame buffer
lastPos = {'x': x, 'y': y};
display.viewportChange(deltaX, deltaY);
display.viewportChangePos(deltaX, deltaY);
return;
}
@@ -166,14 +166,13 @@
var p = $D('canvas').parentNode;
message("doResize1: [" + (p.offsetWidth - padW) +
"," + (p.offsetHeight - padH) + "]");
display.viewportChange(0, 0,
p.offsetWidth - padW, p.offsetHeight - padH);
display.viewportChangeSize(p.offsetWidth - padW, p.offsetHeight - padH);
/*
var pos, new_w, new_h;pos
pos = Util.getPosition($D('canvas').parentNode);
new_w = window.innerWidth - pos.x;
new_h = window.innerHeight - pos.y;
display.viewportChange(0, 0, new_w, new_h);
display.viewportChangeSize(new_w, new_h);
*/
}
@@ -194,7 +193,7 @@
Util.addEvent(window, 'resize', doResize);
// Shrink viewport for first resize call so that the
// scrollbars are disabled
display.viewportChange(0, 0, 10, 10);
display.viewportChangeSize(10, 10);
setTimeout(doResize, 1);
setInterval(dirtyRedraw, 50);
+14 -10
View File
@@ -38,17 +38,23 @@
<script type="text/javascript">
var INCLUDE_URI= "../include/";
// TODO: Data file should override
var VNC_frame_encoding = "base64";
var VNC_frame_encoding = "binary";
</script>
<script src="../include/util.js"></script>
<script src="../include/playback.js"></script>
<script src="../data/multi.js"></script>
<script src="../include/webutil.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 fname = WebUtil.getQueryVar('data', null);
if (fname) {
msg("Loading " + fname);
// Load supporting scripts
Util.load_scripts(["base64.js", "websock.js", "des.js", "keysym.js",
"keysymdef.js", "keyboard.js", "input.js", "display.js",
"rfb.js", "playback.js", "inflator.js", fname]);
} else {
msg("Must specifiy data=FOO.js in query string.");
}
var start_time, VNC_frame_data, pass, passes, encIdx,
encOrder = ['raw', 'rre', 'hextile', 'tightpng', 'copyrect'],
@@ -201,9 +207,7 @@
enc = encOrder[i];
dbgmsg(" " + enc + ": " + VNC_frame_data_multi[enc].length);
}
rfb = new RFB({'target': $D('VNC_canvas'),
'onUpdateState': updateState});
rfb.testMode(send_array, VNC_frame_encoding);
$D('startButton').disabled = false;
}
</script>
</html>
+6 -7
View File
@@ -40,7 +40,7 @@
<script type="text/javascript">
var INCLUDE_URI= "../include/";
// TODO: Data file should override
var VNC_frame_encoding = "base64";
var VNC_frame_encoding = "binary";
</script>
<script src="../include/util.js"></script>
<script src="../include/webutil.js"></script>
@@ -59,9 +59,9 @@
if (fname) {
message("Loading " + fname);
// Load supporting scripts
Util.load_scripts(["base64.js", "websock.js", "des.js",
Util.load_scripts(["base64.js", "websock.js", "des.js", "keysym.js",
"keysymdef.js", "keyboard.js", "input.js", "display.js",
"jsunzip.js", "rfb.js", "playback.js", fname]);
"rfb.js", "playback.js", "inflator.js", fname]);
} else {
message("Must specify data=FOO in query string.");
@@ -75,7 +75,6 @@
test_state = 'failed';
break;
case 'loaded':
$D('startButton').disabled = false;
break;
}
if (typeof msg !== 'undefined') {
@@ -99,7 +98,8 @@
mode = 'realtime';
}
recv_message = rfb.testMode(send_array, VNC_frame_encoding);
//recv_message = rfb.testMode(send_array, VNC_frame_encoding);
next_iteration();
}
@@ -130,9 +130,8 @@
}
if (fname) {
message("VNC_frame_data.length: " + VNC_frame_data.length);
rfb = new RFB({'target': $D('VNC_canvas'),
'onUpdateState': updateState});
}
$D('startButton').disabled = false;
}
</script>
</html>
-11
View File
@@ -1,11 +0,0 @@
TARGETS=rebind.so
CFLAGS += -fPIC
all: $(TARGETS)
rebind.so: rebind.o
$(CC) $(LDFLAGS) $^ -shared -fPIC -ldl -o $@
clean:
rm -f rebind.o rebind.so
+4
View File
@@ -1,5 +1,9 @@
## WebSockets Proxy/Bridge
Websockify has been forked out into its own project. `launch.sh` wil
automatically download it here if it is not already present and not
installed as system-wide.
For more detailed description and usage information please refer to
the [websockify README](https://github.com/kanaka/websockify/blob/master/README.md).
+17
View File
@@ -0,0 +1,17 @@
#!/usr/bin/env perl
use MIME::Base64;
for (<>) {
unless (/^'([{}])(\d+)\1(.+?)',$/) {
print;
next;
}
my ($dir, $amt, $b64) = ($1, $2, $3);
my $decoded = MIME::Base64::decode($b64) or die "Could not base64-decode line `$_`";
my $decoded_escaped = join "", map { "\\x$_" } unpack("(H2)*", $decoded);
print "'${dir}${amt}${dir}${decoded_escaped}',\n";
}
+40
View File
@@ -0,0 +1,40 @@
var zlib = require('../node_modules/pako/lib/zlib/inflate.js');
var ZStream = require('../node_modules/pako/lib/zlib/zstream.js');
var Inflate = function () {
this.strm = new ZStream();
this.chunkSize = 1024 * 10 * 10;
this.strm.output = new Uint8Array(this.chunkSize);
this.windowBits = 5;
zlib.inflateInit(this.strm, this.windowBits);
};
Inflate.prototype = {
inflate: function (data, flush, expected) {
this.strm.input = data;
this.strm.avail_in = this.strm.input.length;
this.strm.next_in = 0;
this.strm.next_out = 0;
// resize our output buffer if it's too small
// (we could just use multiple chunks, but that would cause an extra
// allocation each time to flatten the chunks)
if (expected > this.chunkSize) {
this.chunkSize = expected;
this.strm.output = new Uint8Array(this.chunkSize);
}
this.strm.avail_out = this.chunkSize;
zlib.inflate(this.strm, flush);
return new Uint8Array(this.strm.output.buffer, 0, this.strm.next_out);
},
reset: function () {
zlib.inflateReset(this.strm);
}
};
module.exports = {Inflate: Inflate};
+39 -3
View File
@@ -1,5 +1,9 @@
#!/usr/bin/env bash
# Copyright 2016 Joel Martin
# Copyright 2016 Solly Ross
# Licensed under MPL 2.0 or any later version (see LICENSE.txt)
usage() {
if [ "$*" ]; then
echo "$*"
@@ -9,7 +13,7 @@ usage() {
echo
echo "Starts the WebSockets proxy and a mini-webserver and "
echo "provides a cut-and-paste URL to go to."
echo
echo
echo " --listen PORT Port for proxy/webserver to listen on"
echo " Default: 6080"
echo " --vnc VNC_HOST:PORT VNC server host:port proxy target"
@@ -22,7 +26,8 @@ usage() {
}
NAME="$(basename $0)"
HERE="$(cd "$(dirname "$0")" && pwd)"
REAL_NAME="$(readlink -f $0)"
HERE="$(cd "$(dirname "$REAL_NAME")" && pwd)"
PORT="6080"
VNC_DEST="localhost:5900"
CERT=""
@@ -101,8 +106,39 @@ else
echo "Warning: could not find self.pem"
fi
# try to find websockify (prefer local, try global, then download local)
if [[ -e ${HERE}/websockify ]]; then
WEBSOCKIFY=${HERE}/websockify/run
if [[ ! -x $WEBSOCKIFY ]]; then
echo "The path ${HERE}/websockify exists, but $WEBSOCKIFY either does not exist or is not executable."
echo "If you intended to use an installed websockify package, please remove ${HERE}/websockify."
exit 1
fi
echo "Using local websockify at $WEBSOCKIFY"
else
WEBSOCKIFY=$(which websockify 2>/dev/null)
if [[ $? -ne 0 ]]; then
echo "No installed websockify, attempting to clone websockify..."
WEBSOCKIFY=${HERE}/websockify/run
git clone https://github.com/kanaka/websockify ${HERE}/websockify
if [[ ! -e $WEBSOCKIFY ]]; then
echo "Unable to locate ${HERE}/websockify/run after downloading"
exit 1
fi
echo "Using local websockify at $WEBSOCKIFY"
else
echo "Using installed websockify at $WEBSOCKIFY"
fi
fi
echo "Starting webserver and WebSockets proxy on port ${PORT}"
${HERE}/websockify --web ${WEB} ${CERT:+--cert ${CERT}} ${PORT} ${VNC_DEST} &
#${HERE}/websockify --web ${WEB} ${CERT:+--cert ${CERT}} ${PORT} ${VNC_DEST} &
${WEBSOCKIFY} --web ${WEB} ${CERT:+--cert ${CERT}} ${PORT} ${VNC_DEST} &
proxy_pid="$!"
sleep 1
if ! ps -p ${proxy_pid} >/dev/null; then
-152
View File
@@ -1,152 +0,0 @@
#!/usr/bin/env python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright (c) 2012 Openstack, LLC.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#!/usr/bin/env python
'''
Websocket proxy that is compatible with Openstack Nova.
Leverages websockify by Joel Martin
'''
import Cookie
from oslo.config import cfg
import socket
import sys
import websockify
from nova import config
from nova import context
from nova import utils
from nova.openstack.common import rpc
opts = [
cfg.BoolOpt('record',
default=False,
help='Record sessions to FILE.[session_number]'),
cfg.BoolOpt('daemon',
default=False,
help='Become a daemon (background process)'),
cfg.BoolOpt('ssl_only',
default=False,
help='Disallow non-encrypted connections'),
cfg.BoolOpt('source_is_ipv6',
default=False,
help='Source is ipv6'),
cfg.StrOpt('cert',
default='self.pem',
help='SSL certificate file'),
cfg.StrOpt('key',
default=None,
help='SSL key file (if separate from cert)'),
cfg.StrOpt('web',
default='.',
help='Run webserver on same port. Serve files from DIR.'),
cfg.StrOpt('novncproxy_host',
default='0.0.0.0',
help='Host on which to listen for incoming requests'),
cfg.IntOpt('novncproxy_port',
default=6080,
help='Port on which to listen for incoming requests'),
]
CONF = cfg.CONF
CONF.register_cli_opts(opts)
# As of nova commit 0b11668e64450039dc071a4a123abd02206f865f we must
# manually register the rpc library
if hasattr(rpc, 'register_opts'):
rpc.register_opts(CONF)
class NovaWebSocketProxy(websockify.WebSocketProxy):
def __init__(self, *args, **kwargs):
websockify.WebSocketProxy.__init__(self, *args, **kwargs)
def new_client(self):
"""
Called after a new WebSocket connection has been established.
"""
cookie = Cookie.SimpleCookie()
cookie.load(self.headers.getheader('cookie'))
token = cookie['token'].value
ctxt = context.get_admin_context()
connect_info = rpc.call(ctxt, 'consoleauth',
{'method': 'check_token',
'args': {'token': token}})
if not connect_info:
raise Exception("Invalid Token")
host = connect_info['host']
port = int(connect_info['port'])
# Connect to the target
self.msg("connecting to: %s:%s" % (
host, port))
tsock = self.socket(host, port,
connect=True)
# Handshake as necessary
if connect_info.get('internal_access_path'):
tsock.send("CONNECT %s HTTP/1.1\r\n\r\n" %
connect_info['internal_access_path'])
while True:
data = tsock.recv(4096, socket.MSG_PEEK)
if data.find("\r\n\r\n") != -1:
if not data.split("\r\n")[0].find("200"):
raise Exception("Invalid Connection Info")
tsock.recv(len(data))
break
if self.verbose and not self.daemon:
print(self.traffic_legend)
# Start proxying
try:
self.do_proxy(tsock)
except:
if tsock:
tsock.shutdown(socket.SHUT_RDWR)
tsock.close()
self.vmsg("%s:%s: Target closed" % (host, port))
raise
if __name__ == '__main__':
if CONF.ssl_only and not os.path.exists(CONF.cert):
parser.error("SSL only and %s not found" % CONF.cert)
# Setup flags
config.parse_args(sys.argv)
# Create and start the NovaWebSockets proxy
server = NovaWebSocketProxy(listen_host=CONF.novncproxy_host,
listen_port=CONF.novncproxy_port,
source_is_ipv6=CONF.source_is_ipv6,
verbose=CONF.verbose,
cert=CONF.cert,
key=CONF.key,
ssl_only=CONF.ssl_only,
daemon=CONF.daemon,
record=CONF.record,
web=CONF.web,
target_host='ignore',
target_port='ignore',
wrap_mode='exit',
wrap_cmd=None)
server.start_server()
-18
View File
@@ -1,18 +0,0 @@
#!/usr/bin/env bash
usage() {
echo "Usage: $(basename $0) OLD_PORT NEW_PORT COMMAND_LINE"
echo
echo "Launch COMMAND_LINE, but intercept system calls to bind"
echo "to OLD_PORT and instead bind them to localhost:NEW_PORT"
exit 2
}
# Parameter defaults
mydir=$(readlink -f $(dirname ${0}))
export REBIND_PORT_OLD="${1}"; shift
export REBIND_PORT_NEW="${1}"; shift
LD_PRELOAD=${mydir}/rebind.so "${@}"
-94
View File
@@ -1,94 +0,0 @@
/*
* rebind: Intercept bind calls and bind to a different port
* Copyright 2010 Joel Martin
* 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 bridge/proxy (such as websockify) to run on the old port and
* translate traffic to/from the new port.
*
* Usage:
* LD_PRELOAD=./rebind.so \
* REBIND_PORT_OLD=23 \
* REBIND_PORT_NEW=2023 \
* program
*/
//#define DO_DEBUG 1
#include <stdio.h>
#include <stdlib.h>
#define __USE_GNU 1 // Pull in RTLD_NEXT
#include <dlfcn.h>
#include <string.h>
#include <netinet/in.h>
#if defined(DO_DEBUG)
#define DEBUG(...) \
fprintf(stderr, "wswrapper: "); \
fprintf(stderr, __VA_ARGS__);
#else
#define DEBUG(...)
#endif
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
{
static void * (*func)();
int do_move = 0;
struct sockaddr_in * addr_in = (struct sockaddr_in *)addr;
struct sockaddr_in addr_tmp;
socklen_t addrlen_tmp;
char * PORT_OLD, * PORT_NEW, * end1, * end2;
int ret, oldport, newport, askport = htons(addr_in->sin_port);
uint32_t askaddr = htons(addr_in->sin_addr.s_addr);
if (!func) func = (void *(*)()) dlsym(RTLD_NEXT, "bind");
DEBUG(">> bind(%d, _, %d), askaddr %d, askport %d\n",
sockfd, addrlen, askaddr, askport);
/* Determine if we should move this socket */
if (addr_in->sin_family == AF_INET) {
// TODO: support IPv6
PORT_OLD = getenv("REBIND_OLD_PORT");
PORT_NEW = getenv("REBIND_NEW_PORT");
if (PORT_OLD && (*PORT_OLD != '\0') &&
PORT_NEW && (*PORT_NEW != '\0')) {
oldport = strtol(PORT_OLD, &end1, 10);
newport = strtol(PORT_NEW, &end2, 10);
if (oldport && (*end1 == '\0') &&
newport && (*end2 == '\0') &&
(oldport == askport)) {
do_move = 1;
}
}
}
if (! do_move) {
/* Just pass everything right through to the real bind */
ret = (int) func(sockfd, addr, addrlen);
DEBUG("<< bind(%d, _, %d) ret %d\n", sockfd, addrlen, ret);
return ret;
}
DEBUG("binding fd %d on localhost:%d instead of 0x%x:%d\n",
sockfd, newport, ntohl(addr_in->sin_addr.s_addr), oldport);
/* Use a temporary location for the new address information */
addrlen_tmp = sizeof(addr_tmp);
memcpy(&addr_tmp, addr, addrlen_tmp);
/* Bind to other port on the loopback instead */
addr_tmp.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
addr_tmp.sin_port = htons(newport);
ret = (int) func(sockfd, &addr_tmp, addrlen_tmp);
DEBUG("<< bind(%d, _, %d) ret %d\n", sockfd, addrlen, ret);
return ret;
}
-55
View File
@@ -1,55 +0,0 @@
#!/usr/bin/env python
'''
A super simple HTTP/HTTPS webserver for python. Automatically detect
You can make a cert/key with openssl using:
openssl req -new -x509 -days 365 -nodes -out self.pem -keyout self.pem
as taken from http://docs.python.org/dev/library/ssl.html#certificates
'''
import traceback, sys
import socket
import ssl
#import http.server as server # python 3.X
import SimpleHTTPServer as server # python 2.X
def do_request(connstream, from_addr):
x = object()
server.SimpleHTTPRequestHandler(connstream, from_addr, x)
connstream.close()
def serve():
bindsocket = socket.socket()
bindsocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
#bindsocket.bind(('localhost', PORT))
bindsocket.bind(('', PORT))
bindsocket.listen(5)
print("serving on port", PORT)
while True:
try:
newsocket, from_addr = bindsocket.accept()
peek = newsocket.recv(1024, socket.MSG_PEEK)
if peek.startswith("\x16"):
connstream = ssl.wrap_socket(
newsocket,
server_side=True,
certfile='self.pem',
ssl_version=ssl.PROTOCOL_TLSv1)
else:
connstream = newsocket
do_request(connstream, from_addr)
except Exception:
traceback.print_exc()
try:
PORT = int(sys.argv[1])
except:
print "%s port" % sys.argv[0]
sys.exit(2)
serve()
-1030
View File
File diff suppressed because it is too large Load Diff
-471
View File
@@ -1,471 +0,0 @@
#!/usr/bin/env python
'''
A WebSocket to TCP socket proxy with support for "wss://" encryption.
Copyright 2011 Joel Martin
Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3)
You can make a cert/key with openssl using:
openssl req -new -x509 -days 365 -nodes -out self.pem -keyout self.pem
as taken from http://docs.python.org/dev/library/ssl.html#certificates
'''
import signal, socket, optparse, time, os, sys, subprocess, logging
try: from socketserver import ForkingMixIn
except: from SocketServer import ForkingMixIn
try: from http.server import HTTPServer
except: from BaseHTTPServer import HTTPServer
from select import select
import websocket
try:
from urllib.parse import parse_qs, urlparse
except:
from cgi import parse_qs
from urlparse import urlparse
class ProxyRequestHandler(websocket.WebSocketRequestHandler):
traffic_legend = """
Traffic Legend:
} - Client receive
}. - Client receive partial
{ - Target receive
> - Target send
>. - Target send partial
< - Client send
<. - Client send partial
"""
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', None)
self.target_port = kwargs.pop('target_port', None)
self.wrap_cmd = kwargs.pop('wrap_cmd', None)
self.wrap_mode = kwargs.pop('wrap_mode', None)
self.unix_target = kwargs.pop('unix_target', None)
self.ssl_target = kwargs.pop('ssl_target', None)
self.target_cfg = kwargs.pop('target_cfg', None)
# Last 3 timestamps command was run
self.wrap_times = [0, 0, 0]
if self.wrap_cmd:
wsdir = os.path.dirname(sys.argv[0])
rebinder_path = [os.path.join(wsdir, "..", "lib"),
os.path.join(wsdir, "..", "lib", "websockify"),
wsdir]
self.rebinder = None
for rdir in rebinder_path:
rpath = os.path.join(rdir, "rebind.so")
if os.path.exists(rpath):
self.rebinder = rpath
break
if not self.rebinder:
raise Exception("rebind.so not found, perhaps you need to run make")
self.rebinder = os.path.abspath(self.rebinder)
self.target_host = "127.0.0.1" # Loopback
# Find a free high port
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('', 0))
self.target_port = sock.getsockname()[1]
sock.close()
os.environ.update({
"LD_PRELOAD": self.rebinder,
"REBIND_OLD_PORT": str(kwargs['listen_port']),
"REBIND_NEW_PORT": str(self.target_port)})
websocket.WebSocketServer.__init__(self, RequestHandlerClass, *args, **kwargs)
def run_wrap_cmd(self):
self.msg("Starting '%s'", " ".join(self.wrap_cmd))
self.wrap_times.append(time.time())
self.wrap_times.pop(0)
self.cmd = subprocess.Popen(
self.wrap_cmd, env=os.environ, preexec_fn=_subprocess_setup)
self.spawn_message = True
def started(self):
"""
Called after Websockets server startup (i.e. after daemonize)
"""
# Need to call wrapped command after daemonization so we can
# know when the wrapped command exits
if self.wrap_cmd:
dst_string = "'%s' (port %s)" % (" ".join(self.wrap_cmd), self.target_port)
elif self.unix_target:
dst_string = self.unix_target
else:
dst_string = "%s:%s" % (self.target_host, self.target_port)
if self.target_cfg:
msg = " - proxying from %s:%s to targets in %s" % (
self.listen_host, self.listen_port, self.target_cfg)
else:
msg = " - proxying from %s:%s to %s" % (
self.listen_host, self.listen_port, dst_string)
if self.ssl_target:
msg += " (using SSL)"
self.msg("%s", msg)
if self.wrap_cmd:
self.run_wrap_cmd()
def poll(self):
# If we are wrapping a command, check it's status
if self.wrap_cmd and self.cmd:
ret = self.cmd.poll()
if ret != None:
self.vmsg("Wrapped command exited (or daemon). Returned %s" % ret)
self.cmd = None
if self.wrap_cmd and self.cmd == None:
# Response to wrapped command being gone
if self.wrap_mode == "ignore":
pass
elif self.wrap_mode == "exit":
sys.exit(ret)
elif self.wrap_mode == "respawn":
now = time.time()
avg = sum(self.wrap_times)/len(self.wrap_times)
if (now - avg) < 10:
# 3 times in the last 10 seconds
if self.spawn_message:
self.warn("Command respawning too fast")
self.spawn_message = False
else:
self.run_wrap_cmd()
def _subprocess_setup():
# Python installs a SIGPIPE handler by default. This is usually not what
# non-Python successfulbprocesses expect.
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
def logger_init():
logger = logging.getLogger(WebSocketProxy.log_prefix)
logger.propagate = False
logger.setLevel(logging.INFO)
h = logging.StreamHandler()
h.setLevel(logging.DEBUG)
h.setFormatter(logging.Formatter("%(message)s"))
logger.addHandler(h)
def websockify_init():
logger_init()
usage = "\n %prog [options]"
usage += " [source_addr:]source_port [target_addr:target_port]"
usage += "\n %prog [options]"
usage += " [source_addr:]source_port -- WRAP_COMMAND_LINE"
parser = optparse.OptionParser(usage=usage)
parser.add_option("--verbose", "-v", action="store_true",
help="verbose messages")
parser.add_option("--traffic", action="store_true",
help="per frame traffic")
parser.add_option("--record",
help="record sessions to FILE.[session_number]", metavar="FILE")
parser.add_option("--daemon", "-D",
dest="daemon", action="store_true",
help="become a daemon (background process)")
parser.add_option("--run-once", action="store_true",
help="handle a single WebSocket connection and exit")
parser.add_option("--timeout", type=int, default=0,
help="after TIMEOUT seconds exit when not connected")
parser.add_option("--idle-timeout", type=int, default=0,
help="server exits after TIMEOUT seconds if there are no "
"active connections")
parser.add_option("--cert", default="self.pem",
help="SSL certificate file")
parser.add_option("--key", default=None,
help="SSL key file (if separate from cert)")
parser.add_option("--ssl-only", action="store_true",
help="disallow non-encrypted 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 and not (opts.target_cfg or opts.unix_target):
parser.error("Too few arguments")
if sys.argv.count('--'):
opts.wrap_cmd = args[1:]
else:
opts.wrap_cmd = None
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 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
libserver = opts.libserver
del opts.libserver
if libserver:
# Use standard Python SocketServer framework
server = LibProxyServer(**opts.__dict__)
server.serve_forever()
else:
# Use internal service framework
server = WebSocketProxy(**opts.__dict__)
server.start_server()
class LibProxyServer(ForkingMixIn, HTTPServer):
"""
Just like WebSocketProxy, but uses standard Python SocketServer
framework.
"""
def __init__(self, RequestHandlerClass=ProxyRequestHandler, **kwargs):
# Save off proxy specific options
self.target_host = kwargs.pop('target_host', None)
self.target_port = kwargs.pop('target_port', None)
self.wrap_cmd = kwargs.pop('wrap_cmd', None)
self.wrap_mode = kwargs.pop('wrap_mode', None)
self.unix_target = kwargs.pop('unix_target', None)
self.ssl_target = kwargs.pop('ssl_target', None)
self.target_cfg = kwargs.pop('target_cfg', None)
self.daemon = False
self.target_cfg = None
# Server configuration
listen_host = kwargs.pop('listen_host', '')
listen_port = kwargs.pop('listen_port', None)
web = kwargs.pop('web', '')
# Configuration affecting base request handler
self.only_upgrade = not web
self.verbose = kwargs.pop('verbose', False)
record = kwargs.pop('record', '')
if record:
self.record = os.path.abspath(record)
self.run_once = kwargs.pop('run_once', False)
self.handler_id = 0
for arg in kwargs.keys():
print("warning: option %s ignored when using --libserver" % arg)
if web:
os.chdir(web)
HTTPServer.__init__(self, (listen_host, listen_port),
RequestHandlerClass)
def process_request(self, request, client_address):
"""Override process_request to implement a counter"""
self.handler_id += 1
ForkingMixIn.process_request(self, request, client_address)
if __name__ == '__main__':
websockify_init()
-1
View File
@@ -1 +0,0 @@
websockify
-1
View File
@@ -1 +0,0 @@
websockify
+71 -59
View File
@@ -5,29 +5,31 @@
<!--
noVNC example: simple example using default UI
Copyright (C) 2012 Joel Martin
Copyright (C) 2013 Samuel Mannehed for Cendio AB
Copyright (C) 2016 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
or the fragment:
http://example.com/#host=HOST&port=PORT&encrypt=1&true_color=1
-->
<title>noVNC</title>
<meta charset="utf-8">
<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">
<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="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" href="images/screen_57x57.png" />
<!--
<link rel="apple-touch-icon-precomposed" href="images/screen_57x57.png" />
-->
@@ -46,68 +48,70 @@
</head>
<body>
<div id="noVNC-control-bar">
<div id="noVNC_control_bar" class="noVNC_status_normal">
<!--noVNC Mobile Device only Buttons-->
<div class="noVNC-buttons-left">
<div class="noVNC_buttons_left">
<input type="image" alt="viewport drag" src="images/drag.png"
id="noVNC_view_drag_button" class="noVNC_status_button"
title="Move/Drag Viewport">
title="Move/Drag Viewport" />
<div id="noVNC_mobile_buttons">
<input type="image" alt="No mousebutton" src="images/mouse_none.png"
id="noVNC_mouse_button0" class="noVNC_status_button">
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">
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">
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">
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"/>
id="noVNC_keyboard_button" class="noVNC_status_button"
value="Keyboard" title="Show Keyboard" />
<!-- Note that Google Chrome on Android doesn't respect any of these,
html attributes which attempt to disable text suggestions on the
on-screen keyboard. Let's hope Chrome implements the ime-mode
style for example -->
<textarea id="keyboardinput" autocapitalize="off"
<textarea id="noVNC_keyboardinput" autocapitalize="off"
autocorrect="off" autocomplete="off" spellcheck="false"
mozactionhint="Enter" onsubmit="return false;"
style="ime-mode: disabled;"></textarea>
mozactionhint="Enter"></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="Extra keys" src="images/toggleextrakeys.png"
id="noVNC_toggleExtraKeys_button" class="noVNC_status_button" />
<input type="image" alt="Ctrl" src="images/ctrl.png"
id="toggleCtrlButton" class="noVNC_status_button">
id="noVNC_toggleCtrl_button" class="noVNC_status_button" />
<input type="image" alt="Alt" src="images/alt.png"
id="toggleAltButton" class="noVNC_status_button">
id="noVNC_toggleAlt_button" class="noVNC_status_button" />
<input type="image" alt="Tab" src="images/tab.png"
id="sendTabButton" class="noVNC_status_button">
id="noVNC_sendTab_button" class="noVNC_status_button" />
<input type="image" alt="Esc" src="images/esc.png"
id="sendEscButton" class="noVNC_status_button">
id="noVNC_sendEsc_button" class="noVNC_status_button" />
</div>
</div>
</div>
<div id="noVNC_status">Loading</div>
<div id="noVNC_status"></div>
<!--noVNC Buttons-->
<div class="noVNC-buttons-right">
<div class="noVNC_buttons_right">
<input type="image" alt="Ctrl+Alt+Del" src="images/ctrlaltdel.png"
id="sendCtrlAltDelButton" class="noVNC_status_button"
id="noVNC_sendCtrlAltDel_button" 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"
id="noVNC_toggleXvp_button" class="noVNC_status_button"
title="Shutdown/Reboot..." />
<input type="image" alt="Clipboard" src="images/clipboard.png"
id="clipboardButton" class="noVNC_status_button"
id="noVNC_clipboard_button" class="noVNC_status_button"
title="Clipboard" />
<input type="image" alt="Fullscreen" src="images/fullscreen.png"
id="noVNC_fullscreen_button" class="noVNC_status_button"
title="Fullscreen" />
<input type="image" alt="Settings" src="images/settings.png"
id="settingsButton" class="noVNC_status_button"
id="noVNC_settings_button" class="noVNC_status_button"
title="Settings" />
<input type="image" alt="Connect" src="images/connect.png"
id="connectButton" class="noVNC_status_button"
id="noVNC_connectPanel_button" class="noVNC_status_button"
title="Connect" />
<input type="image" alt="Disconnect" src="images/disconnect.png"
id="disconnectButton" class="noVNC_status_button"
id="noVNC_disconnect_button" class="noVNC_status_button"
title="Disconnect" />
</div>
@@ -124,11 +128,11 @@
and <a href="http://kanaka.github.com/noVNC">website</a>
for more information.
<br />
<input id="descriptionButton" type="button" value="Close">
<input id="noVNC_description_button" type="button" value="Close" />
</div>
<!-- Popup Status Panel -->
<div id="noVNC_popup_status_panel" class="">
<!-- Popup Status -->
<div id="noVNC_popup_status" class="">
</div>
<!-- Clipboard Panel -->
@@ -137,15 +141,15 @@
</textarea>
<br />
<input id="noVNC_clipboard_clear_button" type="button"
value="Clear">
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" />
<input type="button" id="noVNC_xvpShutdown_button" value="Shutdown" />
<input type="button" id="noVNC_xvpReboot_button" value="Reboot" />
<input type="button" id="noVNC_xvpReset_button" value="Reset" />
</span>
</div>
@@ -153,29 +157,38 @@
<div id="noVNC_settings" class="triangle-right top">
<span id="noVNC_settings_menu">
<ul>
<li><input id="noVNC_encrypt" type="checkbox"> Encrypt</li>
<li><input id="noVNC_true_color" type="checkbox" checked> True Color</li>
<li><input id="noVNC_cursor" type="checkbox"> Local Cursor</li>
<li><input id="noVNC_clip" type="checkbox"> Clip to Window</li>
<li><input id="noVNC_shared" type="checkbox"> Shared Mode</li>
<li><input id="noVNC_view_only" type="checkbox"> View Only</li>
<li><input id="noVNC_path" type="input" value="websockify"> Path</li>
<li><input id="noVNC_repeaterID" type="input" value=""> Repeater ID</li>
<li><input id="noVNC_setting_encrypt" type="checkbox" /> Encrypt</li>
<li><input id="noVNC_setting_true_color" type="checkbox" checked /> True Color</li>
<li><input id="noVNC_setting_cursor" type="checkbox" /> Local Cursor</li>
<li><input id="noVNC_setting_clip" type="checkbox" /> Clip to Window</li>
<li><input id="noVNC_setting_shared" type="checkbox" /> Shared Mode</li>
<li><input id="noVNC_setting_view_only" type="checkbox" /> View Only</li>
<hr>
<li><input id="noVNC_setting_path" type="input" value="websockify" /> Path</li>
<li><label>
<select id="noVNC_setting_resize" name="vncResize">
<option value="off">None</option>
<option value="scale">Local Scaling</option>
<option value="downscale">Local Downscaling</option>
<option value="remote">Remote Resizing</option>
</select> Scaling Mode</label>
</li>
<li><input id="noVNC_setting_repeaterID" type="input" value="" /> Repeater ID</li>
<hr>
<!-- Stylesheet selection dropdown -->
<li><label><strong>Style: </strong>
<select id="noVNC_stylesheet" name="vncStyle">
<select id="noVNC_setting_stylesheet" name="vncStyle">
<option value="default">default</option>
</select></label>
</li>
<!-- Logging selection dropdown -->
<li><label><strong>Logging: </strong>
<select id="noVNC_logging" name="vncLogging">
<select id="noVNC_setting_logging" name="vncLogging">
</select></label>
</li>
<hr>
<li><input type="button" id="noVNC_apply" value="Apply"></li>
<li><input type="button" id="noVNC_settings_apply" value="Apply" /></li>
</ul>
</span>
</div>
@@ -183,24 +196,23 @@
<!-- Connection Panel -->
<div id="noVNC_controls" class="triangle-right top">
<ul>
<li><label><strong>Host: </strong><input id="noVNC_host" /></label></li>
<li><label><strong>Port: </strong><input id="noVNC_port" /></label></li>
<li><label><strong>Password: </strong><input id="noVNC_password" type="password" /></label></li>
<li><input id="noVNC_connect_button" type="button" value="Connect"></li>
<li><label><strong>Host: </strong><input id="noVNC_setting_host" /></label></li>
<li><label><strong>Port: </strong><input id="noVNC_setting_port" /></label></li>
<li><label><strong>Password: </strong><input id="noVNC_setting_password" type="password" /></label></li>
<li><label><strong>Token: </strong><input id="noVNC_setting_token" /></label></li>
<li><input id="noVNC_connect_button" type="button" value="Connect" /></li>
</ul>
</div>
</div> <!-- End of noVNC-control-bar -->
</div> <!-- End of noVNC_control_bar -->
<div id="noVNC_screen">
<div id="noVNC_screen_pad"></div>
<div id="noVNC_container">
<h1 id="noVNC_logo"><span>no</span><br />VNC</h1>
<!-- HTML5 Canvas -->
<div id="noVNC_container">
<canvas id="noVNC_canvas" width="640px" height="20px">
<div id="noVNC_screen">
<canvas id="noVNC_canvas" width="0" height="0">
Canvas not supported.
</canvas>
</div>
+61 -23
View File
@@ -11,6 +11,8 @@
Connect parameters are provided in query string:
http://example.com/?host=HOST&port=PORT&encrypt=1&true_color=1
or the fragment:
http://example.com/#host=HOST&port=PORT&encrypt=1&true_color=1
-->
<title>noVNC</title>
@@ -44,7 +46,7 @@
</head>
<body style="margin: 0px;">
<div id="noVNC_screen">
<div id="noVNC_container">
<div id="noVNC_status_bar" class="noVNC_status_bar" style="margin-top: 0px;">
<table border=0 width="100%"><tr>
<td><div id="noVNC_status" style="position: relative; height: auto;">
@@ -77,10 +79,26 @@
// 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", "keysym.js"]);
"inflator.js", "rfb.js", "keysym.js"]);
var rfb;
var resizeTimeout;
function UIresize() {
if (WebUtil.getConfigVar('resize', false)) {
var innerW = window.innerWidth;
var innerH = window.innerHeight;
var controlbarH = $D('noVNC_status_bar').offsetHeight;
var padding = 5;
if (innerW !== undefined && innerH !== undefined)
rfb.requestDesktopSize(innerW, innerH - controlbarH - padding);
}
}
function FBUComplete(rfb, fbu) {
UIresize();
rfb.set_onFBUComplete(function() { });
}
function passwordRequired(rfb) {
var msg;
msg = '<form onsubmit="return setPassword();"';
@@ -138,6 +156,16 @@
}
}
window.onresize = function () {
// When the window has been resized, wait until the size remains
// the same for 0.5 seconds before sending the request for changing
// the resolution of the session
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(function(){
UIresize();
}, 500);
};
function xvpInit(ver) {
var xvpbuttons;
xvpbuttons = $D('noVNC_xvp_buttons');
@@ -157,11 +185,11 @@
$D('xvpRebootButton').onclick = xvpReboot;
$D('xvpResetButton').onclick = xvpReset;
WebUtil.init_logging(WebUtil.getQueryVar('logging', 'warn'));
document.title = unescape(WebUtil.getQueryVar('title', 'noVNC'));
WebUtil.init_logging(WebUtil.getConfigVar('logging', 'warn'));
document.title = unescape(WebUtil.getConfigVar('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);
host = WebUtil.getConfigVar('host', window.location.hostname);
port = WebUtil.getConfigVar('port', window.location.port);
// if port == 80 (or 443) then it won't be present and should be
// set manually
@@ -174,33 +202,43 @@
}
}
password = WebUtil.getConfigVar('password', '');
path = WebUtil.getConfigVar('path', 'websockify');
// If a token variable is passed in, set the parameter in a cookie.
// This is used by nova-novncproxy.
token = WebUtil.getQueryVar('token', null);
token = WebUtil.getConfigVar('token', null);
if (token) {
// if token is already present in the path we should use it
path = WebUtil.injectParamIfMissing(path, "token", token);
WebUtil.createCookie('token', token, 1)
}
password = WebUtil.getQueryVar('password', '');
path = WebUtil.getQueryVar('path', 'websockify');
if ((!host) || (!port)) {
updateState('failed',
"Must specify host and port in URL");
updateState(null, 'fatal', null, 'Must specify host and port in URL');
return;
}
rfb = new RFB({'target': $D('noVNC_canvas'),
'encrypt': WebUtil.getQueryVar('encrypt',
(window.location.protocol === "https:")),
'repeaterID': WebUtil.getQueryVar('repeaterID', ''),
'true_color': WebUtil.getQueryVar('true_color', true),
'local_cursor': WebUtil.getQueryVar('cursor', true),
'shared': WebUtil.getQueryVar('shared', true),
'view_only': WebUtil.getQueryVar('view_only', false),
'onUpdateState': updateState,
'onXvpInit': xvpInit,
'onPasswordRequired': passwordRequired});
try {
rfb = new RFB({'target': $D('noVNC_canvas'),
'encrypt': WebUtil.getConfigVar('encrypt',
(window.location.protocol === "https:")),
'repeaterID': WebUtil.getConfigVar('repeaterID', ''),
'true_color': WebUtil.getConfigVar('true_color', true),
'local_cursor': WebUtil.getConfigVar('cursor', true),
'shared': WebUtil.getConfigVar('shared', true),
'view_only': WebUtil.getConfigVar('view_only', false),
'onUpdateState': updateState,
'onXvpInit': xvpInit,
'onPasswordRequired': passwordRequired,
'onFBUComplete': FBUComplete});
} catch (exc) {
updateState(null, 'fatal', null, 'Unable to create RFB client -- ' + exc);
return; // don't continue trying to connect
}
rfb.connect(host, port, password, path);
};
</script>