160 Commits

Author SHA1 Message Date
Joel Martin ceec05260e noVNC version 0.4 2012-09-14 17:00:56 -05:00
Joel Martin cbb55df039 README: simplify projects/companies. News/help/contact section.
Instead of continuing to maintain the full list of project/companies
that use noVNC in multiple places (README, wiki, web page) just link
to the wiki page.

Link to noVNC discussion group page. Link to issues page. Link to
Amazon wishlist and non-profits for appreciation.
2012-08-28 17:45:48 -05:00
Joel Martin af6e9e2408 Merge pull request #185 from dark/master
Fix grep pattern when matching netstat output
2012-08-28 06:59:52 -07:00
Marco Leogrande cf068be2d8 Fix grep pattern when searching for listening sockets
The current grep pattern matches also port numbers that match only
partially the given $PORT number; e.g., if $PORT is 6080, 60800 will
match as well.

While TCP listening sockets in the 60000-65535 range are rare, they
need to be handled as well. The problem is also present if the user
selects a shorter PORT value with the --listen command line argument.

By adding a space, the pattern is fixed.
2012-08-27 22:19:51 -07:00
Joel Martin fc003a13e7 Revert "Pull in latest websockify."
This reverts commit 4dd1bb1ecb.
2012-08-15 13:48:03 -05:00
Joel Martin ca6b3f4cb1 Revert "Fix tight decoding when using binary/non-base64 connection."
This reverts commit 14717eb468.
2012-08-15 13:47:37 -05:00
Joel Martin 14717eb468 Fix tight decoding when using binary/non-base64 connection. 2012-08-15 12:45:53 -05:00
Joel Martin 4dd1bb1ecb Pull in latest websockify.
Pull in version 376872d99.

Several changes including:
- binary/typed array support in websock.js
- unix socket support
- multiple target support via config file(s)
- prefer IPv6 option
2012-08-14 15:35:48 -05:00
Joel Martin af1527b276 timers: fix over repeated connects.
With tight we can't ever use FBU.rects being 0 as an indication of
a full frame having been drawn.
2012-06-28 10:29:12 -05:00
Joel Martin e5d5a7d3fd websock.js: comment out debug options. 2012-06-25 21:42:56 -05:00
Joel Martin d58f8b5144 License clarification: HTML,CSS,images,fonts under permissive licenses.
Clarify in LICENSE.txt that the noVNC core library is the part that is
LGPLv3 licensed. The HTML, CSS, images and fonts are separate from the
core library and can be modified and distributed with the noVNC core
but under their own license conditions.

HTML and CSS: 2-Clause BSD
Fonts: SIL OFL 1.1
Images: CC BY SA 3.0

In other words, you can modify the layout and appearance of of noVNC
to integrate with an existing or new web site or application without
having to publish the source for those modifications under the LGPLv3.
However, use of and modification of the noVNC core library (i.e. the
core Javascript that makes up noVNC) must still be according to the
LGPLv3.

Chris Gordon was the other contributor to the HTML, CSS, and images
included with noVNC and gave permission for this license clarification
on June 23, 2012.
2012-06-24 16:29:44 -05:00
Joel Martin 18232af2a6 Merge branch 'master' of github.com:kanaka/noVNC 2012-06-23 14:23:50 -05:00
Joel Martin b50f340674 UltraVNC repeaterID cleanup.
https://github.com/kanaka/noVNC/issues/103
https://github.com/kanaka/noVNC/pull/170
2012-06-23 14:22:47 -05:00
Joel Martin 2090f1af67 Merge remote branch 'kassah/master' 2012-06-23 14:16:49 -05:00
Joel Martin 9450f132c6 remove docs/TODO: see issues marked as "feature".
https://github.com/kanaka/noVNC/issues?labels=feature
2012-06-23 14:15:41 -05:00
William Lightning 9ebc84f2e9 Update vnc_auto.html to use config option for repeaterID 2012-06-22 22:34:20 -07:00
William Lightning a997c5fd94 Make repeaterID show up in config options on vnc.html 2012-06-22 22:30:57 -07:00
William Lightning 12acb663eb Change RepeaterID to be a config option 2012-06-22 22:30:05 -07:00
Joel Martin d5575ef6f3 Merge pull request #172 from soheilhassasyeganeh/fix_novarpc
Fixes nova rpc package.
2012-06-22 15:41:43 -07:00
Soheil Hassas Yeganeh 90f18a8a96 Fixes nova rpc package. 2012-06-21 21:38:52 -04:00
Joel Martin bee36506e1 Fix forcing of web-socket-js even when native available. 2012-06-08 10:42:26 -05:00
Joel Martin cb3ad1b5b5 Merge pull request #168 from soheilhassasyeganeh/master
Fix nova flag API change
2012-06-08 08:26:41 -07:00
Soheil Hassas Yeganeh b1d2225eee Merging with upstream tree. 2012-06-08 11:12:22 -04:00
William Lightning 8d0c7fb5ac Add ultravnc_repeater.pl support to rfb.js and vnc_auto.html 2012-06-06 23:10:13 -07:00
Soheil Hassas Yeganeh 9e2351f5eb Switch to nova's new flag API. 2012-06-06 21:26:25 -03:00
Joel Martin ab44059b8b README: Add PocketVNC. 2012-05-24 07:09:47 -05:00
Joel Martin a04087e44c Merge pull request #166 from mnaser/patch-1
Remove old flagfile support
2012-05-18 09:44:54 -07:00
Mohammed Naser 9f54c23629 Remove old flagfile support
default_flagfile has been removed from the nova trunk in favour of .ini files (https://github.com/openstack/nova/commit/7e3e9b8e9cea4f1bf78d127ffb915b79c854fdbe)
2012-05-17 13:48:42 -03:00
Joel Martin 72a5596e50 rfb: Use the render queue for copyrect.
This will keep copyrect rendering actions in order with tight and tightPNG
rendering actions (otherwise you can get visual image corruption when
they are mixed together).

Warning:

RAW, RRE and HEXTILE still use immediate render commands so there is
still the risk of out-of-order rendering if RAW, RRE, and HEXTILE are
mixed with tight and tightPNG. Copyrect will work with either because
the renderQ_push function will render copyrects immediately if they
are the only thing being pushed on the queue.
2012-05-17 11:24:58 -05:00
Joel Martin 34d8b844ae Move render queue processing to Display and use requestAnimationFrame
The imgQ code in RFB should be a generic rendering queue system in
Display.

The reason for the render queue in the first place is that images
loaded from raw data URI strings aren't immediately ready to display
so we have to wait for them to complete 'loading'. However, when data
URI images are mixed with other types of rendering actions then things
can get out of order. This is the reason for the rendering queue.

Currently this only keeps display actions for tight and tightPNG
related actions in order (because they use a mix of fills, raw pixel
data and data URI images).
2012-05-17 11:13:08 -05:00
Joel Martin a0726a4b56 rfb: debug output cleanup. 2012-05-17 11:07:07 -05:00
Joel Martin 57044a3c0a README: Link to websockify encryption page. 2012-05-16 18:31:04 -05:00
Joel Martin dc6e501f20 Fail the connection on unexpected tightPNG cmode.
This related to https://github.com/kanaka/noVNC/issues/145

The real fix is to QEMU so that this doesn't happen which was
submitted as a patch to the mailinglist right before this.
2012-05-16 18:07:18 -05:00
Joel Martin e472e6ce3a docs/release.txt: --all and --tags are incompatible 2012-05-11 14:48:34 -05:00
Joel Martin 17bb6ca38e docs/release.txt: correct tag instructions 2012-05-11 14:45:53 -05:00
Joel Martin df4fa13b89 noVNC version 0.3
- Also add docs/release.txt instruction file.
2012-05-11 14:42:05 -05:00
Joel Martin 53dfab7fb7 Pull from websockify. Fix close code. 2012-05-10 22:10:01 -05:00
Joel Martin bc28395abf Add/use display.drawImage which is viewport relative.
Fixes https://github.com/kanaka/noVNC/issues/163

When using an encoding with raw images (tight, tightPNG) we need to
draw those image relative to the viewport so that clipping works when
the viewport isn't at 0, 0.
2012-05-10 18:16:27 -05:00
Joel Martin a117514183 Merge pull request #162 from toddfreese/master
RealVNC 4.6 Mac Support.
2012-05-10 15:31:14 -07:00
Todd Freese 58ad83878c Added support for RealVNC Mac. 2012-05-10 12:29:36 -05:00
Joel Martin 02d1f19b5f Pull from websockify.
Fix python 2.4 CClose exception handling.
2012-05-10 08:06:43 -05:00
Joel Martin 4910600b71 Merge pull request #158 from cloudbuilders/fix_rpc_backend
Fix rpc initialization issue
2012-04-30 15:16:24 -07:00
Anthony Young ca78183171 Use explicit check for rpc.register_opts 2012-04-30 14:07:31 -07:00
Anthony Young 901e565503 Update to fix trunk nova issue with rpc library
* rpc flags now must be manually initialized.
2012-04-30 13:34:57 -07:00
Joel Martin 483157f87f Comment out duplicates in unicodeTable.
Unfortunately the values for those duplicate keys are not the same and
I'm not sure which ones are more correct. However, for now, I've
commented out the second occurrence.

This data is generated from /usr/include/X11/keysymdef.h using the
utils/u2x11 script.
2012-04-26 08:18:27 -05:00
Joel Martin dcf1994d3e Don't treat onerror as a close.
The close event will also fire so trying to fail the connection twice
is unhelpful and hiding status information.
2012-04-25 20:13:25 -05:00
Joel Martin c390011099 Update websockify to pull in close code/reason fixes. 2012-04-25 13:46:30 -05:00
Joel Martin f8380ff939 Add code/reason to close event messages. Remove useless object print in error event. 2012-04-13 11:25:04 -05:00
Joel Martin f736c3316a Merge pull request #149 from ohadlevy/patch-1
Update README.md to add Foreman project
2012-04-10 07:07:35 -07:00
Ohad Levy cbd3435a07 Update README.md 2012-04-10 16:58:27 +03:00
Joel Martin bd96e91932 Use page host:port as default for WebSocket host/port. 2012-04-03 16:58:21 -05:00
Anthony Young 0139b2562c Simplify nova-novncproxy related logic.
*  Don't clobber default host/port
2012-04-03 10:34:24 -07:00
Anthony Young 4c75210a4d Add nova-novncproxy
* Adds the nova-novncproxy binary, to provide support for vnc + OpensStack nova
 * Adds the ability to pass an auth token in via url, which is subsequently
   passed back to the proxy as a cookie.
2012-04-03 10:17:47 -07:00
Joel Martin f84504bc63 Support Apple Remote Desktop.
https://github.com/kanaka/noVNC/issues/58
2012-03-23 12:21:23 -05:00
Joel Martin c76b3e4b26 Add jsunzip.js to debian install list.
Thanks for catching this: https://github.com/abligh
2012-03-16 09:23:19 -07:00
Joel Martin 6671c7624d Release down/pressed keys when window loses focus.
May window managers have a keyboard shortcut that switch away from the
current desktop (e.g. desktop switcher). Unfortunately, when this
happens, the meta/control keys that are used with the shortcut will
send a down event to the browser, but the up event will never be sent
because the browser no longer has focus at the point when the up event
happens. This can cause weird stuck key issues for VNC clients (not
just noVNC). To get around this, we try and detect when the browser
loses focus and release any keys that are on the keyDownList.

As an aside, if you run into this situation (in noVNC or another VNC
client), you can unstick the state by pressing and releasing the Ctrl,
Shift, Alt, etc.

Addresses: https://github.com/kanaka/noVNC/pull/135
2012-03-14 11:10:06 -05:00
Joel Martin ce86f5c954 Squelch noisy debug logs. 2012-03-13 20:24:24 -05:00
Joel Martin c0c20581f5 Merge tight/tightPNG routine.
Mostly duplicate code so merge it and wrap with closures that pass in
the isTightPNG parameter.

Still detect and error if copy/filter when tightPNG.
2012-03-12 15:34:56 -07:00
Joel Martin f2a495c944 Use standard citing format for contributors. 2012-03-12 00:34:53 -05:00
Joel Martin b8dd87c757 Flesh out authors/contributors and licenses. 2012-03-11 14:39:23 -05:00
Joel Martin d065cad99e General code formatting and cleanliness. 2012-03-10 20:32:01 -06:00
Joel Martin a09a75e8f2 Stats: count pixels instead of just FBU.rects.
With last_rect, the rects count can be high until a last_rect
pseudo-encoding is received which messes with the timing stats. So
count up the number of pixels rendered and show timing after the pixel
count reaches the width*height of the screen.
2012-03-10 19:56:19 -06:00
Joel Martin 4cd0070a1c Cleanup vnc.js includes. 2012-03-10 19:54:56 -06:00
Joel Martin 35d7574b09 Merge commit 'd38db74abd0efa34f7297dc19bf603b7f765e0f5'
Conflicts:
	README.md
2012-03-10 19:52:11 -06:00
Mike Tinglof d38db74abd add some documentation; default to existing websocket transport 2012-03-09 11:02:18 -05:00
Joel Martin 0c4f4b598c Don't swallow SSL EOF errors. 2012-02-20 16:33:08 -06:00
Joel Martin c6f3919cb0 Better base64 illegal character output. 2012-02-20 15:48:39 -06:00
Joel Martin 3b302463f8 Correction: Version 0.2
noVNC version 0.2
2012-02-20 12:37:14 -06:00
Joel Martin 23ff806774 Version 0.1
noVNC version 0.1
2012-02-20 12:29:56 -06:00
Joel Martin 94bcf652b3 Add images subdirectory to deb build. 2012-02-20 12:28:17 -06:00
Joel Martin 0ce93900ae Add message panel when hosted at kanaka.github.com.
Add a helpful popup/panel message for when noVNC is used at
kanaka.github.com (e.g. where novnc.com redirects).
2012-02-15 14:12:27 -06:00
Joel Martin 9e6e6662c7 Set $D on window directly.
Addresses: https://github.com/kanaka/websockify/issues/29
2012-02-14 17:23:14 -06:00
Joel Martin 32f135d730 Add array map function for IE9. 2012-02-14 07:53:39 -06:00
Joel Martin 1af3e54bef Make WebSocket encrypt setting default to page protocol.
I.e. if the page is https:// then the WebSocket encrypt setting will
default to wss:// (TLS encryption).

Note that since noVNC settings are saved in cookies, this will only
affect first load. If you have already loaded the page, then the
encrypt setting will be whatever you last set it to.
2012-02-02 09:29:02 -06:00
Joel Martin 6a02f88e4d Default path to 'websockify' in include/ui.js 2012-01-31 13:37:20 -06:00
Mike Tinglof 9b75bcaada add tight zlib stream reset; add error if tight encoding is used w/o true color 2012-01-31 00:15:56 -05:00
Mike Tinglof a14b8fae2a comment out per-decompress checksum and logging 2012-01-30 02:26:36 -05:00
Mike Tinglof 2cedf48397 add last rect special encoding; fix tight fill subencoding color handling; fix mono indexed rect handling 2012-01-30 02:19:18 -05:00
Mike Tinglof c514fd5e1c don't need to copy palette data until we have all data for rect; change a few comments 2012-01-29 02:10:25 -05:00
Mike Tinglof 5ca5e2d8cd implement tight indexed rectangle; remove some debug code 2012-01-29 01:55:41 -05:00
Mike Tinglof 6fbc37489f fix handling of min compression size 2012-01-28 02:56:19 -05:00
Mike Tinglof b0ac240f31 re-enable history buffer (used as sliding window for decompress) 2012-01-28 01:56:55 -05:00
Joel Martin 523cc4d6ab Default path 'websockify' for Clojure Websockify.
The Jetty WebSocket server code used by the Clojure version of
websockify doesn't support WebSocket and Web at the same path so pass
in a default value for the path so that we can support the Clojure
version websockify.
2012-01-27 16:03:20 -06:00
Mike Tinglof 0fa6748c52 fix issue with parsing distance of more then 8 bits; convert to just supporting arrays for buffers 2012-01-26 14:35:36 -05:00
Mike Tinglof a820f1267a added rgb image drawing, some zlib changes (huffman coding working, but lz77 not so much) 2012-01-25 18:09:55 -05:00
Mike Tinglof c577ca2305 keep zlib history so we can decode as a stream 2012-01-24 21:18:29 -05:00
Mike T de84e09854 basic framing for tight is working (decode not complete) 2012-01-24 13:56:33 -05:00
Mike 682f33a790 add javascript zlib 2012-01-24 13:49:49 -05:00
Joel Martin e79917c3db Merge remote branch 'origin/issue-70'
Conflicts:
	include/display.js
	include/rfb.js

This merges in the fix for https://github.com/kanaka/noVNC/issues/70

This changes noVNC to use the preferred color ordering that most VNC
server prefer and that VMWare VNC requires. It's possible this may
break some VNC servers out there in which case we might have to do
something a bit more subtle such as having alternate render functions
for little and big endian color ordering.
2012-01-12 17:17:11 -06:00
Joel Martin a9a7f0e18c Fix Intel AMT KVM: disable true color.
Issue: https://github.com/kanaka/noVNC/issues/118
Reporter @maxnet also found and suggested the fix.

Probably could be more intelligent/generic by keying off the depth
sent by the server, but this will do for now.
2012-01-12 12:52:09 -06:00
Joel Martin ff4bfcb773 Linting.
Switched to using: http://www.jshint.com/
2012-01-12 12:38:55 -06:00
Joel Martin f66bcda31b Add websockify.py symlink for Windows. 2012-01-05 11:55:56 -06:00
Joel Martin 86ebeef709 Merge branch 'master' of github.com:kanaka/noVNC 2011-12-15 15:34:30 -06:00
Joel Martin f2d856767d websockify: better multi-python version support.
Pull from websockify: 008a5118e728.

Should address issue https://github.com/kanaka/noVNC/issues/107

- Also add ability to force use of web-socket-js using
  window.WEB_SOCKET_FORCE_FLASH

- in websock.js, for rQshift*, assume length is the full length if not
  specified.
2011-12-15 15:26:16 -06:00
Joel Martin 2fa565b375 Fix python3 wss (TLS/SSL) connection detection.
Compare the first byte of the handshake against numbers (character
code) as well as string characters.
2011-12-15 14:45:55 -06:00
Joel Martin b688a909b0 Show close code and reason.
Also add commented out web-socket-js debug variable in
include/websock.js
2011-12-15 13:59:35 -06:00
Joel Martin 06a9ef0c6c Add View Only mode setting.
Resolve issue: https://github.com/kanaka/noVNC/pull/101

Based on proposal from @mightpenguin:
Matthew Balman <emperor@mightypenguin.org>

If view_only option is set then do not send mouse and keyboard events.
This is not a secure/enforced way to make a client view only. To
enforce view only at the server, most VNC servers support setting
a view only password.
2011-12-01 09:05:16 -06:00
Joel Martin c3a172b98c Typo in Opera 11.60 fix. 2011-11-19 17:59:06 -06:00
Joel Martin aa67056785 Fix issue #102 - Opera 11.60 doesn't work.
Opera presto engine version detection is broken. For now, just return
true or false. That should cover most cases.
2011-11-19 17:37:18 -06:00
Joel Martin 91b0dc3915 LibVNCServer capitalization. 2011-11-17 10:53:31 -06:00
Joel Martin 4f545a5ba6 Add libvncserver as project including noVNC.
libvncserver now includes noVNC as an alternative to the Java web
client.
2011-11-17 10:49:34 -06:00
Joel Martin 83ed7b8c25 Add Brightbox to user list. 2011-11-07 14:26:44 -06:00
Joel Martin 1c92807a8e debian/ - Update CSS file names.
Thanks Alex Bligh for the catch.
2011-10-31 12:20:46 -05:00
Joel Martin bc71506553 README: feature section and minor rearranging. 2011-10-29 16:11:37 -05:00
Joel Martin 2e7d81ce24 Add Intel MeshCentral. 2011-10-28 10:00:03 -05:00
Joel Martin fa4607dbf5 Add Amahi as a project using noVNC. 2011-10-28 09:49:59 -05:00
Joel Martin a538be4350 Update description. Remove Sentry reference. 2011-10-08 13:00:42 -05:00
Joel Martin a367e84a92 Update requirements. Note WS added to libvncserver. 2011-10-08 12:57:57 -05:00
Joel Martin 1e50871599 websockify --run-once, --timeout, numpy fallback
Pull websockify 724aa3a.

- Use array module for unmasking HyBi when no numpy module is
    available.

- Detect client close properly when using python 3.

- Print request URL path is specified.

- New option --run-once will exit after handling a single WebSocket
  connection (but not ater flash policy or normal web requests).

- New option --timeout TIME will stop listening for new connections
  after exit after TIME seconds (the master process shuts down).
  Existing WebSocket connections will continue but once all
  connections are closed all processes will terminate.
2011-09-29 16:12:19 -05:00
Joel Martin 65db2d8243 Fix README screenshot src. 2011-09-29 14:33:25 -05:00
Joel Martin b29e06f36e Update README screenshots. 2011-09-29 14:32:54 -05:00
Joel Martin f4bce78307 Re-organize CSS. Remove broken IE filter statement. 2011-09-29 12:09:32 -05:00
Joel Martin 5299db1a00 Formatting. Image renames/cleanup.
- Remove the images using the old font.

- Simplify the naming of the new control bar icon images.

- Change keyboard input type to 'email'. 'url' type doesn't have
  a space bar.

- Some clarifications to main LICENSE.txt file.
2011-09-29 09:32:09 -05:00
Chris Gordon 938daad1c9 Mobile icons.
- CSS highlighting of buttons when selected.
- Keyboard button tweaked to allow show/hide toggle of keyboard.

Signed-off-by: Chris Gordon <snorkeyg@gmail.com>
2011-09-29 09:05:15 -05:00
Joel Martin 1709ee3ab7 LICENSE.txt, docs/TODO: bring up to date.
- Add Orbitron info to LICENSE.txt

- Update TODO.
2011-09-28 11:39:36 -05:00
Joel Martin 479bfa9964 Add local copy of Orbitron font.
Instead of using Google Font API, use local copy of Orbitron for speed
and also in case Internet connection is flaky or unavailable.

More info about Orbitron font here:
http://www.google.com/webfonts/specimen/Orbitron

Orbitron font is:
Copyright (c) 2009, Matt McInerney <matt@pixelspread.com>

Licensed under SIL Open Font License 1.1
see docs/LICENSE.OFL-1.1 or http://scripts.sil.org/OFL
2011-09-27 16:53:09 -05:00
Joel Martin a7f5589946 vnc_auto.html: sync with mobile changes.
Also, add path option settable with query string.
2011-09-27 12:49:50 -05:00
Joel Martin 6209639f2c Add WebSocket URL path to UI settings. 2011-09-27 12:36:06 -05:00
Joel Martin 7cd6118ce5 Move viewport functions.
Move viewportChange(), getCleanDirtyReset(), absX() and absY() to the
public API section. Also add some function comments.
2011-09-26 17:09:29 -05:00
Joel Martin 490d471c53 Remove support for non-HTML5 browsers.
Display API change:
    - getTile -> startTile (no longer returns a tile)
    - setSubTile -> subTile (drop img/tile first parameter)
    - putTile -> finishTile (no longer takes img/tile paramter)

The Display tile logic uses canvas image data directly and
caches/reuses a 16x16 imageData tile (for other sizes, the tile is
create for each call). This gives a 30% speedup on Chrome
13 (and no significant change for Firefox 3.6/4.0).

Other:

- Remove rgbxImageFill and cmapImageFill routines.

- Simplify constructor tests and just error if createImageData is not
  supported by canvas instead of .

- Remove webkit canvas bug workaround that effects Chrome 7. Chrome
  7 usage share is now less than 0.5 percent and the workaround is
  ugly. Drop the function wrapping in the constructor and the canvas
  flush() routine.

- Remove support for getImageData (Opera 11+ now required)

Update browser support list:

    - Chrome 8+ (really any except 7)
    - Firefox 3.6+
    - Safari 4+
    - Opera 11+
    - IE9+
    - iOS 4.2+
2011-09-26 11:28:24 -05:00
Joel Martin 859fc7f18f include/ui.js: remove console.log debug. 2011-09-26 11:16:16 -05:00
Joel Martin 1310606305 Make tests/vnc_perf.html work again. 2011-09-25 22:02:13 -05:00
Joel Martin a7db50597c Fixing iOS clipping, keyboard issues.
- Make sure that on iOS the clipping setting is always forced to be
  enabled.
- Hide the showKeyboard button unless connected.
- Use the URL text entry method and disable autocorrect and
  autocapitalize in the show keyboard input box.
2011-09-23 00:51:55 -05:00
Joel Martin c506a48154 Only show move/drag button when clipping. Cleanup.
Cleanup:

- remove unused changeViewportMeta function from include/ui.js
- remove some debug output and debug CSS.
- rename panel toggle functions and put them in same location in the
  code.
- refactor some code from updateState to updateVisualState routine
  (renamed from updateSettingsState).
2011-09-23 00:04:01 -05:00
Joel Martin a5df24b488 Viewport clip/drag for mobile/touchscreen devices.
API changes (forward compatible):

- Display: add 'viewport' conf option to turn on and off viewport
  mode.
- RFB: add 'viewportDrag' option to enable/disable viewport dragging
  mode.

Other:

- Add clip mode setting to default UI. For touch devices, clipping is
  forced on.
- Use CSS media queries to adjust visual elements based on screen
  size. Especially disconnected logo size/position and button text size.
- Catch page unload while connected and give a confirm dialog.
- Change mouse button selector to a single button that changes between
  ' ', 'L', 'M', 'R' when clicked (empty means mouse is just being
  moved and doesn't send clicks).
- include/ui.js:setViewClip() routine sets the clipping of the
  viewport to the current size of the viewport area (if clipping is
  enabled).
- include/ui.js:setViewDrag() toggles/enables/disables viewport
  dragging mode.
- Add several images for the UI and for Apple devices:
    - images/clipboard.png: clipboard menu icon
    - images/connect.png: connect menu icon
    - images/disconnect.png: disconnect button icon
    - images/keyboard.png: show keyboard button
    - images/move.png: viewport drag/move toggle button
    - images/settings.png: settings menu icon
    - images/screen_320x460.png: iOS app/desktop link start image
    - images/screen_57x57.png: iOS app icon
    - images/screen_700x700.png: full size noVNC image
2011-09-22 10:36:23 -05:00
Joel Martin e0b23efe95 Fix fbUpdateRequest undefined parameter logic. 2011-09-14 10:42:15 -05:00
Joel Martin 608e0f52ee Some CSS style and DOM id/class cleanup. 2011-09-14 09:24:30 -05:00
Joel Martin 7e5f81f255 Merge branch 'master' into mobile 2011-09-13 16:53:04 -05:00
Joel Martin 832c744578 Framebuffer requests based on clean/dirty areas.
New routine fbUpdateRequests that builds the update request messages
based on the result of display.getCleanDirtyReset().

- Also, fix fbUpdateRequest to properly accept x,y,xw,yw parameters.
2011-09-13 14:42:38 -05:00
Joel Martin b70ce07706 Fix window/document sizing in firefox.
Another firefox issue is that height: 100% is calculated as 100% of
the containing element even when the containing element is the window.
This means that the size of any sibling element shifts the window size
down by that much and causes the vertical scroll bars to appear. This
doesn't happen in Chrome.

- So instead, put a pad element inside the noVNC_screen element that
  is the size of the control bar. This is hidden by the control bar,
  however, it causes things to be sized correctly.

- Also, rename noVNC_defaultScreen to noVNC_logo.

- Clean some style specification out of the HTML.
2011-09-13 13:14:11 -05:00
Joel Martin 1c3df652de Firefox offset bug: use margin instead of padding.
For some reason, the position calculation is broken in firefox when
a DOM object in the ancestry change uses padding. So use margin to
shift the view area down.
2011-09-13 12:17:44 -05:00
Joel Martin 9192cbe39c Merge branch 'master' into mobile
Conflicts:
	include/rfb.js
2011-09-13 10:14:53 -05:00
Joel Martin 54e7cbdf8f Viewport handling in include/display.js
Part of mobile device support:
https://github.com/kanaka/noVNC/issues/48

The Display object is redefined as a larger display region with
an equal or smaller visible viewport. The size of the full display
region is set/changed using resize(). The viewport is set/changed
using viewportChange().

All exposed routines that draw on the display now take coordinates
that are absolute (relative to the full display region). For example,
the result of fillRect(100, 100, 10, 10, [255,0,0]) will appear in the
canvas at (0,0) if the viewport is set to (100,100).

Details:

- Move the generic part of the viewport code from tests/viewport.html
  into include/display.

- Add two new routines to the Display interface:

    - viewportChange(deltaX, deltaY, width, height)
        - This adjusts the position of the visible viewport and/or the
          size of the viewport.

        - deltaX and deltaY specify how the position of the viewport
          should be shifted. The position of the viewport is clamped
          to the full region size (i.e. cannot outside the display
          region).

        - The clean and dirty regions of the display are updated based
          on calls to this routine. For example, if the viewport width
          is increased, then there is now a dirty box on the right
          side of the viewport. Another example, if the viewport is
          shifted down and to the left over the display region, there
          are now two dirty boxes: one on the left side and one
          on the bottom of the viewport.

    - getCleanDirtyReset()
        - This returns an object with the clean box and a list of
          dirty boxes (that need to be redrawn).

            {'cleanBox':
                {'x': x, 'y': y, 'w': w, 'h': h},
             'dirtyBoxes':
                [{'x': x, 'y': y, 'w': w, 'h': h}, ...]
             }

        - The coordinates in the clean and dirty boxes are absolute
          coordinates (relative to the full display region) but they
          are clipped to the visible viewport.

        - Calling this function also resets the clean rectangle to be
          the whole viewport (i.e. nothing visible needs to be redrawn
          dirty) so the caller of this routine is responsible for
          redrawing any
2011-09-13 09:54:44 -05:00
Joel Martin afecb83988 Pull websockify: python3 numpy buffer/string fix.
Pull from websockify 1c39c7f1f001.
2011-09-10 16:03:39 -05:00
Joel Martin 6ea8bece9b Fix setBarPosotion runtime errors. 2011-09-08 10:10:46 -05:00
Joel Martin 01a9eee991 HTML, CSS and Javascript indent/formatting.
- 4 space indenting for HTML and Javascript.
- 2 space indenting for CSS
- Use hanging start curly for conditional blocks
2011-09-08 10:00:09 -05:00
Joel Martin 0911173a89 Disable static canvas logo. 2011-09-08 09:29:05 -05:00
Joel Martin 42e04907ff Merge remote branch 'kanaka/master' into mobile 2011-09-08 08:50:18 -05:00
Joel Martin d29c54c476 Remove unused include/plain.css. 2011-09-08 08:41:50 -05:00
Chris Gordon 89a76c785c New theme. File: include/blue.css 2011-09-08 08:38:36 -05:00
Chris Gordon a7eb596d89 Updated to use CSS3 gradients instead of images.
Fixed display bug with keyboard textbox.
2011-09-08 08:36:42 -05:00
Chris Gordon 8e0f008841 Fixed bugs with connection panel, and moved functions into UI class. 2011-09-08 08:35:27 -05:00
Chris Gordon c1e8b30877 iOS keyboard bug fix. 2011-09-08 08:32:31 -05:00
Chris Gordon 2c6b8a481e Bug fix with mobile buttons. 2011-09-08 08:31:25 -05:00
Chris Gordon c327865b4b Add base.css. Bar position formatting. 2011-09-08 08:29:49 -05:00
Chris Gordon 53fc7392bb Modifed UI. Move HTML from ui.js to vnc.html. 2011-09-08 08:26:30 -05:00
Chris Gordon 1734b5e465 Helper function for viewport meta changes.
changeViewportMeta() now does viewport changes.
2011-09-08 08:21:46 -05:00
Chris Gordon 7825b9ee9f Added support for native iOS and Android keyboards.
Tested with Safari on iPad for iOS and Asus Transformer for Android.
2011-09-08 08:20:38 -05:00
Joel Martin 1150d0f70f Only output debug colourMap table once. 2011-09-06 17:46:57 -05:00
Joel Martin 7b10dc8a48 utils/websocket.py: add HyBi 13 support.
Pull from websockify 6e26306.
2011-08-31 18:06:58 -05:00
Joel Martin 94568bc08d utils/websocket.py: silence partial unmask debug.
Pull from websockify ada02f2.
2011-08-31 15:33:30 -05:00
Joel Martin fa74a6e60c websocket.py: fix payload length calculation.
Pull from websockify 0da91c7.
2011-08-31 15:08:03 -05:00
Joel Martin 48f26d799f websockify: pull HyBi fixes.
Pull 86af0b614d and 7b496ce5b from websockify.

    Fix HyBi support on 64-bit systems.

    https://github.com/kanaka/noVNC/issues/76

    - cleanup/update TODO.
    - remove explicit check for ctypes module for HyBi.

    Clarify that we support HyBi 07-10.

    HyBi 07 reports version 7 in the handshake.
    HyBi 08-10 report version 8 in the handshake.

    Remove version 9 since that is not yet actually defined.
2011-08-24 13:26:28 -05:00
Joel Martin ec40268e55 Working viewport test.
Tested on iOS (iPhone and iPad).

The viewport is correctly clipped to the screen/browser size and
resizing works correctly.

This uses the CSS3 Flexible Box Layout model.
2011-08-22 11:21:55 -05:00
Joel Martin 46c621175c Fix websockify with python2.4
https://github.com/kanaka/noVNC/issues/72

Pull from websockify 2e00f9643.
2011-08-10 17:54:15 -05:00
Joel Martin 676fbbb662 README: Zentyal (Ebox) and SlapOS to projects list. 2011-08-04 10:50:16 -05:00
Joel Martin 4245363cf4 Add a viewport example/test.
Tested with an iPad 2.

This example shows a 400x200 viewport of an 800x400 display.

It tries to be intelligent about how much it redraws. It copies what
it can, and then when the user releases the mouse, it redraws the
"dirty" areas that were newly revealed.
2011-08-03 18:18:41 -05:00
Joel Martin ce3bdbccc2 Catch Mozilla prefixing of WebSocket.
Pull 475cfae from websockify.

In Firefox 7 Mozilla has added Websockets support but prefixed the
constructor with "Moz".
2011-08-02 08:02:51 -05:00
Joel Martin ad3f762409 Touch events and mouse button selectors.
First crack at supporting touch screen for devices like Android and
iOS tablets. Part of https://github.com/kanaka/noVNC/issues/48.

This change detects touch screen support and uses the touchstart,
touchmove, touchend events in place of the normal mouse events.

In order to support middle and right mouse clicks, if the device is
a touch device, then three toggle buttons are added to the UI
representing the left, middle and right mouse buttons. These select
which mouse button will be sent when the screen is touched. All the
buttons can be toggled off, in which case then the touch events only
move the mouse cursor rather than sending a mouse down and mouse up
for touchstart and touchend events respectively. This allows fairly
full control with the mouse on touch screens.
2011-07-23 21:24:59 -05:00
Joel Martin ac99a1f791 Change default PixelFormat. Fix canvas test.
Instead of R,G,B (red-shift of 0, green-shift of 8, and blue-shift
of 16), use the default ordering of B,G,R (red-shift of 16, green-shift of 8, and blue-shift
of 0) that tightvncserver uses (and that VMWare's VNC server seems to
require). Also, warn in the console if the server does not default to
the new format.

Fix the tests/canvas.html test. This is a general fix with regards to
the rename/refactor of canvas.js into display.js and not specific to
the color re-ordering.
2011-07-18 12:17:47 -05:00
59 changed files with 4057 additions and 1686 deletions
+61 -13
View File
@@ -1,30 +1,78 @@
noVNC is Copyright (C) 2011 Joel Martin <github@martintribe.org> noVNC is Copyright (C) 2011 Joel Martin <github@martintribe.org>
The noVNC core library is licensed under the LGPLv3 (GNU Lesser
General Public License). The noVNC core library is composed of the
Javascript code necessary for full noVNC operation. This includes (but
is not limited to):
include/base64.js
include/des.js
include/display.js
include/input.js
include/jsunzip.js
include/logo.js
include/rfb.js
include/ui.js
include/util.js
include/vnc.js
include/websock.js
include/webutil.js
The HTML, CSS, font and images files that included with the noVNC
source distibution (or repository) are not considered part of the
noVNC core library and are licensed under more permissive licenses.
The intent is to allow easy integration of noVNC into existing web
sites and web applications.
The HTML, CSS, font and image files are licensed as follows:
*.html : 2-Clause BSD license
include/*.css : 2-Clause BSD license
include/Orbitron* : SIL Open Font License 1.1
(Copyright 2009 Matt McInerney)
images/ : Creative Commons Attribution-ShareAlike
http://creativecommons.org/licenses/by-sa/3.0/
In addition the following file, which is part of the noVNC core
library, may be licensed under either the LGPL-2, LGPL-3 or MPL 2.0
when it used separately from the noVNC core library.
include/input.js : LGPL-2 or any later version
Some portions of noVNC are copyright to their individual authors. Some portions of noVNC are copyright to their individual authors.
Please refer to the individual source files and/or to the noVNC commit Please refer to the individual source files and/or to the noVNC commit
history: https://github.com/kanaka/noVNC/commits/master history: https://github.com/kanaka/noVNC/commits/master
noVNC is licensed under the LGPL (GNU Lesser General Public License) The are several files and projects that have been incorporated into
version 3 with the following exceptions: the noVNC core library. Here is a list of those files and the original
licenses (all LGPL-3 compatible):
include/input.js : LGPL-2 or any later version include/base64.js : MPL 1.1, GPL-2 or LGPL-2.1
include/base64.js : Dual GPL-2 or LGPL-2.1 include/des.js : Various BSD style licenses
incluee/des.js : Various BSD style licenses
include/web-socket-js/ : New BSD license. Source code at include/jsunzip.js : zlib/libpng license
include/web-socket-js/ : New BSD license (3-clause). Source code at
http://github.com/gimite/web-socket-js http://github.com/gimite/web-socket-js
images/ : Creative Commons Attribution-ShareAlike The following license texts are included:
http://creativecommons.org/licenses/by-sa/3.0/
Uses of the work must be attributed
to the noVNC project.
The LGPL-3 license text is included at:
docs/LICENSE.LGPL-3 and docs/LICENSE.LGPL-3 and
docs/LICENSE.GPL-3 docs/LICENSE.GPL-3
docs/LICENSE.OFL-1.1
docs/LICENSE.BSD-3-Clause (New BSD)
docs/LICENSE.BSD-2-Clause (Simplified BSD / FreeBSD)
docs/LICENSE.zlib
docs/LICENSE.MPL-2.0
Or alternatively the license texts may be found here:
Or alternatively the LGPL-3 license text may be found here:
http://www.gnu.org/licenses/lgpl.html and http://www.gnu.org/licenses/lgpl.html and
http://www.gnu.org/licenses/gpl.html http://www.gnu.org/licenses/gpl.html
http://scripts.sil.org/OFL
http://www.mozilla.org/MPL/1.1/
http://www.mozilla.org/MPL/2.0/
+73 -36
View File
@@ -3,69 +3,89 @@
### Description ### Description
noVNC is a VNC client implemented using HTML5 technologies, noVNC is a HTML5 VNC client that runs well in any modern browser
specifically Canvas and WebSockets (supports 'wss://' encryption). including mobile browsers (iPhone/iPad and Android).
noVNC is licensed under the
[LGPLv3](http://www.gnu.org/licenses/lgpl.html).
Special thanks to [Sentry Data Systems](http://www.sentryds.com) for More than 16 companies/projects have integrated noVNC into their
sponsoring ongoing development of this project (and for employing me). products including [Ganeti Web
Manager](http://code.osuosl.org/projects/ganeti-webmgr),
[OpenStack](http://www.openstack.org), and
[OpenNebula](http://opennebula.org/). See [the Projects and Companies
wiki page](https://github.com/kanaka/noVNC/wiki/ProjectsCompanies-using-noVNC)
for more complete list.
There are many companies/projects that have integrated noVNC into ### News/help/contact
their products including: [Sentry Data Systems](http://www.sentryds.com), [Ganeti Web Manager](http://code.osuosl.org/projects/ganeti-webmgr), [Archipel](http://archipelproject.org), [openQRM](http://www.openqrm.com/), [OpenNode](http://www.opennodecloud.com/), [OpenStack](http://www.openstack.org), [Broadway (HTML5 GDK/GTK+ backend)](http://blogs.gnome.org/alexl/2011/03/15/gtk-html-backend-update/), [OpenNebula](http://opennebula.org/) and [CloudSigma](http://www.cloudsigma.com/). See [this wiki page](https://github.com/kanaka/noVNC/wiki/ProjectsCompanies-using-noVNC) for more info and links.
Notable commits, announcements and news are posted to Notable commits, announcements and news are posted to
@<a href="http://www.twitter.com/noVNC">noVNC</a> @<a href="http://www.twitter.com/noVNC">noVNC</a>
If you are a noVNC developer/integrator/user (or want to be) please
join the <a
href="https://groups.google.com/forum/?fromgroups#!forum/novnc">noVNC
discussion group</a>
Bugs and feature requests can be submitted via [github
issues](https://github.com/kanaka/noVNC/issues). If you are looking
for a place to start contributing to noVNC, a good place to start
would be the issues that I have marked as
["patchwelcome"](https://github.com/kanaka/noVNC/issues?labels=patchwelcome).
If you want to show appreciation for noVNC you could buy something off
my [Amazon wishlist](http://www.amazon.com/registry/wishlist/XTXFXK39IA8C/?reveal=unpurchased&sort=priority&layout=compact) or you could donate to a great non-profits such as: [Compassion
International](http://www.compassion.com/), [SIL](http://www.sil.org),
[Habitat for Humanity](http://www.habitat.org), [Electronic Frontier
Foundation](https://www.eff.org/), [Against Malaria
Foundation](http://www.againstmalaria.com/), [Nothing But
Nets](http://www.nothingbutnets.net/), etc.
### Features
* Supports all modern browsers including mobile (iOS, Android)
* Supported VNC encodings: raw, copyrect, rre, hextile, tight, tightPNG
* WebSocket SSL/TLS encryption (i.e. "wss://") support
* 24-bit true color and 8 bit colour mapped
* Supports desktop resize notification/pseudo-encoding
* Local or remote cursor
* Clipboard copy/paste
* Clipping or scolling modes for large remote screens
* Easy site integration and theming (3 example themes included)
* Licensed under the [LGPLv3](http://www.gnu.org/licenses/lgpl.html)
### Screenshots ### Screenshots
Running in Chrome before and after connecting: Running in Chrome before and after connecting:
<img src="http://kanaka.github.com/noVNC/img/noVNC-1.jpg" width=400>&nbsp;<img src="http://kanaka.github.com/noVNC/img/noVNC-2.jpg" width=400> <img src="http://kanaka.github.com/noVNC/img/noVNC-5.png" width=400>&nbsp;<img src="http://kanaka.github.com/noVNC/img/noVNC-7.jpg" width=400>
See more screenshots <a href="http://kanaka.github.com/noVNC/screenshots.html">here</a>. See more screenshots <a href="http://kanaka.github.com/noVNC/screenshots.html">here</a>.
### Browser Requirements ### Browser Requirements
* HTML5 Canvas: Except for Internet Explorer, most * HTML5 Canvas (with createImageData): Chrome, Firefox 3.6+, iOS
browsers have had Canvas support for quite some time. Internet Safari, Opera 11+, Internet Explorer 9+, etc.
Explorer 9 will have Canvas support (finally).
* HTML5 WebSockets: For browsers that do not have builtin * HTML5 WebSockets: For browsers that do not have builtin
WebSockets support, the project includes WebSockets support, the project includes
<a href="http://github.com/gimite/web-socket-js">web-socket-js</a>, <a href="http://github.com/gimite/web-socket-js">web-socket-js</a>,
a WebSockets emulator using Adobe Flash. a WebSockets emulator using Adobe Flash. iOS 4.2+ has built-in
WebSocket support.
* Fast Javascript Engine: noVNC avoids using new Javascript * Fast Javascript Engine: this is not strictly a requirement, but
functionality so it will run on older browsers, but decode and without a fast Javascript engine, noVNC might be painfully slow.
rendering happen in Javascript, so a slow Javascript engine will
mean noVNC is painfully slow.
* I maintain a more detailed list of browser compatibility <a * I maintain a more detailed browser compatibility list <a
href="https://github.com/kanaka/noVNC/wiki/Browser-support">here</a>. href="https://github.com/kanaka/noVNC/wiki/Browser-support">here</a>.
### Server Requirements ### Server Requirements
Unless you are using a VNC server with support for WebSockets Unless you are using a VNC server with support for WebSockets
connections (only my [fork of libvncserver](http://github.com/kanaka/libvncserver) connections (such as [x11vnc/libvncserver](http://libvncserver.sourceforge.net/) or
currently), you need to use a WebSockets to TCP socket proxy. There is [PocketVNC](http://www.pocketvnc.com/blog/?page_id=866)),
a python proxy included ('websockify'). One advantage of using the you need to use a WebSockets to TCP socket proxy. There is
proxy is that it has builtin support for SSL/TLS encryption (i.e. a python proxy included ('websockify').
"wss://").
There a few reasons why a proxy is required:
1. WebSockets is not a pure socket protocol. There is an initial HTTP
like handshake to allow easy hand-off by web servers and allow
some origin policy exchange. Also, each WebSockets frame begins
with 0 ('\x00') and ends with 255 ('\xff').
2. Javascript itself does not have the ability to handle pure byte
arrays. The python proxy encodes the data as base64 so that the
Javascript client can decode the data as an integer array.
### Quick Start ### Quick Start
@@ -83,11 +103,28 @@ There a few reasons why a proxy is required:
### Other Pages ### Other Pages
* [Advanced Usage](https://github.com/kanaka/noVNC/wiki/Advanced-usage). Generating an SSL * [Encrypted Connections](https://github.com/kanaka/websockify/wiki/Encrypted-Connections). How to setup websockify so that you can use encrypted connections from noVNC.
certificate, starting a VNC server, advanced websockify usage, etc.
* [Advanced Usage](https://github.com/kanaka/noVNC/wiki/Advanced-usage). Starting a VNC server, advanced websockify usage, etc.
* [Integrating noVNC](https://github.com/kanaka/noVNC/wiki/Integration) into existing projects. * [Integrating noVNC](https://github.com/kanaka/noVNC/wiki/Integration) into existing projects.
* [Troubleshooting noVNC](https://github.com/kanaka/noVNC/wiki/Troubleshooting) problems. * [Troubleshooting noVNC](https://github.com/kanaka/noVNC/wiki/Troubleshooting) problems.
### Authors/Contributors
* noVNC : Joel Martin (github.com/kanaka)
* UI and Icons : Chris Gordon
* Original Logo : Michael Sersen
* tight encoding : Michael Tinglof (Mercuri.ca)
* Included libraries:
* web-socket-js : Hiroshi Ichikawa (github.com/gimite/web-socket-js)
* as3crypto : Henri Torgemane (code.google.com/p/as3crypto)
* base64 : Martijn Pieters (Digital Creations 2), Samuel Sieb (sieb.net)
* jsunzip : Erik Moller (github.com/operasoftware/jsunzip),
* tinflate : Joergen Ibsen (ibsensoftware.com)
* DES : Dave Zimmerman (Widget Workshop), Jef Poskanzer (ACME Labs)
+30
View File
@@ -1,3 +1,33 @@
novnc (0.4) maverick; urgency=low
* Clarify permissive licenses of HTML, CSS, images.
* Use render queue and requestAnimationFrame
* UltraVNC repeater support
-- Joel Martin <github@martintribe.org> Fri, 14 Sep 2012 05:00:00 -0600
novnc (0.3) maverick; urgency=low
* add tight encoding support
* release pressed key when focus lost (fixes locked Alt key)
* Support Apple Remote Desktop
* Add nova/openstack proxy wrapper
* Better connection close handling/reporting
-- Joel Martin <github@martintribe.org> Fri, 11 May 2012 03:00:00 -0600
novnc (0.2) maverick; urgency=low
* Mobile device support with viewport clipping
* Much better styling that also works on mobile devices well
* Update websockify to support latest WebSocket protocol HyBi 13
(i.e. support IETF 6455)
* Better support in websockify for python 2.4 through 3.2
* Support VMWare ESX and Intel AMT KVM
* View only mode
-- Joel Martin <github@martintribe.org> Tue, 05 Jul 2011 01:00:00 -0600
novnc (0.1) maverick; urgency=low novnc (0.1) maverick; urgency=low
* First upstream release * First upstream release
+5 -2
View File
@@ -9,14 +9,16 @@ utils/websockify /usr/share/novnc/utils
utils/wsproxy.py /usr/share/novnc/utils utils/wsproxy.py /usr/share/novnc/utils
utils/rebind.c /usr/share/novnc/utils utils/rebind.c /usr/share/novnc/utils
utils/rebind.so /usr/share/novnc/utils utils/rebind.so /usr/share/novnc/utils
images /usr/share/novnc
images/favicon.ico /usr/share/novnc images/favicon.ico /usr/share/novnc
include/base64.js /usr/share/novnc/include include/base64.js /usr/share/novnc/include
include/black.css /usr/share/novnc/include
include/des.js /usr/share/novnc/include include/des.js /usr/share/novnc/include
include/display.js /usr/share/novnc/include include/display.js /usr/share/novnc/include
include/input.js /usr/share/novnc/include include/input.js /usr/share/novnc/include
include/logo.js /usr/share/novnc/include include/logo.js /usr/share/novnc/include
include/plain.css /usr/share/novnc/include include/base.css /usr/share/novnc/include
include/blue.css /usr/share/novnc/include
include/black.css /usr/share/novnc/include
include/playback.js /usr/share/novnc/include include/playback.js /usr/share/novnc/include
include/rfb.js /usr/share/novnc/include include/rfb.js /usr/share/novnc/include
include/ui.js /usr/share/novnc/include include/ui.js /usr/share/novnc/include
@@ -24,4 +26,5 @@ include/util.js /usr/share/novnc/include
include/vnc.js /usr/share/novnc/include include/vnc.js /usr/share/novnc/include
include/websock.js /usr/share/novnc/include include/websock.js /usr/share/novnc/include
include/webutil.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 include/web-socket-js/* /usr/share/novnc/include/web-socket-js
+22
View File
@@ -0,0 +1,22 @@
Copyright (c) <year>, <copyright holder>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+24
View File
@@ -0,0 +1,24 @@
Copyright (c) <year>, <copyright holder>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the <organization> nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+373
View File
@@ -0,0 +1,373 @@
Mozilla Public License Version 2.0
==================================
1. Definitions
--------------
1.1. "Contributor"
means each individual or legal entity that creates, contributes to
the creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used
by a Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached
the notice in Exhibit A, the Executable Form of such Source Code
Form, and Modifications of such Source Code Form, in each case
including portions thereof.
1.5. "Incompatible With Secondary Licenses"
means
(a) that the initial Contributor has attached the notice described
in Exhibit B to the Covered Software; or
(b) that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the
terms of a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and
all of the rights conveyed by this License.
1.10. "Modifications"
means any of the following:
(a) any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered
Software; or
(b) any new file in Source Code Form that contains any Covered
Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the
License, by the making, using, selling, offering for sale, having
made, import, or transfer of either its Contributions or its
Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General
Public License, Version 3.0, or any later versions of those
licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that
controls, is controlled by, or is under common control with You. For
purposes of this definition, "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of more than
fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity.
2. License Grants and Conditions
--------------------------------
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
(a) under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
(b) under Patent Claims of such Contributor to make, use, sell, offer
for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
(a) for any code that a Contributor has removed from Covered Software;
or
(b) for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
(c) under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.
3. Responsibilities
-------------------
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
(a) such Covered Software must also be made available in Source Code
Form, as described in Section 3.1, and You must inform recipients of
the Executable Form how they can obtain a copy of such Source Code
Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and
(b) You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------
If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.
5. Termination
--------------
5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.
************************************************************************
* *
* 6. Disclaimer of Warranty *
* ------------------------- *
* *
* Covered Software is provided under this License on an "as is" *
* basis, without warranty of any kind, either expressed, implied, or *
* statutory, including, without limitation, warranties that the *
* Covered Software is free of defects, merchantable, fit for a *
* particular purpose or non-infringing. The entire risk as to the *
* quality and performance of the Covered Software is with You. *
* Should any Covered Software prove defective in any respect, You *
* (not any Contributor) assume the cost of any necessary servicing, *
* repair, or correction. This disclaimer of warranty constitutes an *
* essential part of this License. No use of any Covered Software is *
* authorized under this License except under this disclaimer. *
* *
************************************************************************
************************************************************************
* *
* 7. Limitation of Liability *
* -------------------------- *
* *
* Under no circumstances and under no legal theory, whether tort *
* (including negligence), contract, or otherwise, shall any *
* Contributor, or anyone who distributes Covered Software as *
* permitted above, be liable to You for any direct, indirect, *
* special, incidental, or consequential damages of any character *
* including, without limitation, damages for lost profits, loss of *
* goodwill, work stoppage, computer failure or malfunction, or any *
* and all other commercial damages or losses, even if such party *
* shall have been informed of the possibility of such damages. This *
* limitation of liability shall not apply to liability for death or *
* personal injury resulting from such party's negligence to the *
* extent applicable law prohibits such limitation. Some *
* jurisdictions do not allow the exclusion or limitation of *
* incidental or consequential damages, so this exclusion and *
* limitation may not apply to You. *
* *
************************************************************************
8. Litigation
-------------
Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.
9. Miscellaneous
----------------
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.
10. Versions of the License
---------------------------
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
-------------------------------------------
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.
+91
View File
@@ -0,0 +1,91 @@
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
+27
View File
@@ -0,0 +1,27 @@
Copyright (c) <year>, <copyright holder>
All rights reserved.
This software is provided 'as-is', without any express
or implied warranty. In no event will the authors be
held liable for any damages arising from the use of
this software.
Permission is granted to anyone to use this software
for any purpose, including commercial applications,
and to alter it and redistribute it freely, subject to
the following restrictions:
1. The origin of this software must not be
misrepresented; you must not claim that you
wrote the original software. If you use this
software in a product, an acknowledgment in
the product documentation would be appreciated
but is not required.
2. Altered source versions must be plainly marked
as such, and must not be misrepresented as
being the original software.
3. This notice may not be removed or altered from
any source distribution.
-53
View File
@@ -1,53 +0,0 @@
Short Term:
- Test normal arrays vs TypedArrays vs imageData arrays.
- Blog post about it.
- VNC performance and regression playback suite.
- WebSockets
- expand latency test
- add absolute timers (every 500 packets)
- try 1 ms delay
- stop at 4000 packets
- small and large packets test
- JavaScript
- just base64 decode
- everything except Canvas
- Full test
- Without WebSockets
- With replay from python tester
- add higher-resolution multi test
- websockify test with echo and playback functionality
- choosen by client test page on connect
- Keyboard layout/internationalization support
- convert keyCode into proper charCode
- IE 9 improvements.
- https://github.com/gimite/web-socket-js/issues#issue/41
- try window.atob instead of decode for better performance
- Status bar menu/buttons:
- Explanatory hover text over buttons
- Configuration menu:
- Tunable: speed vs. bandwidth selection
- Tunable: CPU use versus latency.
- Scaling
- Keyboard menu:
- Ctrl Lock, Alt Lock, SysRq Lock
- Highlight menu icon when keys are locked
- Clipboard button -> popup:
- text, clear and send buttons
Medium Term:
- Viewport support
- Touchscreen testing/support.
+1 -1
View File
@@ -1 +1 @@
0.1 0.4
+8
View File
@@ -0,0 +1,8 @@
- Update and commit docs/VERSION and debian/changelog
- Create version tag and tarball from tag
WVER=0.3
git tag v${WVER}
git push origin master v${WVER}
git archive --format=tar --prefix=novnc-${WVER}/ v${WVER} > novnc-${WVER}.tar
gzip novnc-${WVER}.tar
- Upload tarball to repo
-687
View File
@@ -1,687 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="512"
height="512"
id="svg2"
sodipodi:version="0.32"
inkscape:version="0.47pre4 r22446"
version="1.0"
sodipodi:docname="noVNC_Logo_13.svg"
inkscape:output_extension="org.inkscape.output.svg.inkscape"
inkscape:export-filename="/home/joelm/Downloads/noVNC_Logo12.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90"
style="display:inline">
<defs
id="defs4">
<linearGradient
id="linearGradient7584">
<stop
id="stop7586"
offset="0"
style="stop-color:#171717;stop-opacity:1;" />
<stop
id="stop7588"
offset="1"
style="stop-color:#000000;stop-opacity:1;" />
</linearGradient>
<linearGradient
id="linearGradient5965">
<stop
style="stop-color:#030303;stop-opacity:1;"
offset="0"
id="stop5967" />
<stop
id="stop5973"
offset="0.5"
style="stop-color:#090909;stop-opacity:1;" />
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="1"
id="stop5969" />
</linearGradient>
<linearGradient
id="linearGradient5932">
<stop
style="stop-color:#000000;stop-opacity:0.87450981;"
offset="0"
id="stop5934" />
<stop
style="stop-color:#000000;stop-opacity:0;"
offset="1"
id="stop5936" />
</linearGradient>
<linearGradient
id="linearGradient5333">
<stop
style="stop-color:#ffffff;stop-opacity:0.05;"
offset="0"
id="stop5335" />
<stop
style="stop-color:#4c4c4c;stop-opacity:0;"
offset="1"
id="stop5337" />
</linearGradient>
<linearGradient
id="linearGradient4694">
<stop
style="stop-color:#ffffff;stop-opacity:0.03529412;"
offset="0"
id="stop4696" />
<stop
style="stop-color:#4c4c4c;stop-opacity:0;"
offset="1"
id="stop4698" />
</linearGradient>
<linearGradient
id="linearGradient6989">
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop6991" />
<stop
style="stop-color:#0f0f0f;stop-opacity:1;"
offset="1"
id="stop6993" />
</linearGradient>
<linearGradient
id="linearGradient6790">
<stop
style="stop-color:#434343;stop-opacity:1;"
offset="0"
id="stop6792" />
<stop
style="stop-color:#000000;stop-opacity:0;"
offset="1"
id="stop6794" />
</linearGradient>
<linearGradient
id="linearGradient6736">
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop6738" />
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="1"
id="stop6740" />
</linearGradient>
<linearGradient
id="linearGradient6720">
<stop
style="stop-color:#303030;stop-opacity:1;"
offset="0"
id="stop6722" />
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="1"
id="stop6724" />
</linearGradient>
<linearGradient
id="linearGradient6702">
<stop
id="stop4688"
offset="0"
style="stop-color:#000000;stop-opacity:0;" />
<stop
id="stop6714"
offset="1"
style="stop-color:#ffffff;stop-opacity:0;" />
</linearGradient>
<linearGradient
id="linearGradient6662">
<stop
style="stop-color:#010000;stop-opacity:1;"
offset="0"
id="stop6664" />
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="1"
id="stop6666" />
</linearGradient>
<marker
inkscape:stockid="Arrow1Send"
orient="auto"
refY="0"
refX="0"
id="Arrow1Send"
style="overflow:visible">
<path
id="path4730"
d="M 0,0 5,-5 -12.5,0 5,5 0,0 z"
style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;marker-start:none"
transform="matrix(-0.2,0,0,-0.2,-1.2,0)" />
</marker>
<marker
inkscape:stockid="Arrow1Mstart"
orient="auto"
refY="0"
refX="0"
id="Arrow1Mstart"
style="overflow:visible">
<path
id="path4721"
d="M 0,0 5,-5 -12.5,0 5,5 0,0 z"
style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;marker-start:none"
transform="matrix(0.4,0,0,0.4,4,0)" />
</marker>
<marker
inkscape:stockid="Arrow1Lstart"
orient="auto"
refY="0"
refX="0"
id="Arrow1Lstart"
style="overflow:visible">
<path
id="path4715"
d="M 0,0 5,-5 -12.5,0 5,5 0,0 z"
style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;marker-start:none"
transform="matrix(0.8,0,0,0.8,10,0)" />
</marker>
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="-24.105461 : 505.9325 : 1"
inkscape:vp_y="6.1230318e-14 : 1000 : 0"
inkscape:vp_z="719.98902 : 505.9325 : 1"
inkscape:persp3d-origin="347.94178 : 330.5388 : 1"
id="perspective10" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient6720"
id="linearGradient6742"
x1="341.66687"
y1="-253.40732"
x2="470.19839"
y2="-253.40732"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.874106,0,0,1.0453135,43.679874,11.482781)" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient6790"
id="linearGradient6796"
x1="-32.526917"
y1="294.91821"
x2="653.36664"
y2="294.91821"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient7584"
id="linearGradient6987"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.6520109,0,0,1.5543006,133.44336,136.96388)"
x1="341.66687"
y1="-253.40732"
x2="470.19839"
y2="-253.40732" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient6702"
id="linearGradient4686"
gradientUnits="userSpaceOnUse"
x1="159.54247"
y1="443.13766"
x2="430.90103"
y2="443.13766" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient4694"
id="linearGradient4700"
x1="229.03033"
y1="461.81128"
x2="393.28391"
y2="461.81128"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient4694"
id="linearGradient4966"
gradientUnits="userSpaceOnUse"
x1="229.03033"
y1="461.81128"
x2="393.28391"
y2="461.81128"
spreadMethod="reflect" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient5333"
id="linearGradient5339"
x1="271.28635"
y1="294.12686"
x2="490.23206"
y2="294.12686"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient5932"
id="linearGradient5938"
x1="-245.06445"
y1="7.705513"
x2="-62.328537"
y2="7.705513"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(2.5245666,0,0,2.6282551,644.92825,162.55033)" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient5965"
id="linearGradient5971"
x1="159.54247"
y1="443.13766"
x2="430.90103"
y2="443.13766"
gradientUnits="userSpaceOnUse"
spreadMethod="pad" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient6720"
id="radialGradient7582"
cx="295.22174"
cy="443.13766"
fx="295.22174"
fy="443.13766"
r="135.67928"
gradientTransform="matrix(1,0,0,0.1356784,0,383.01346)"
gradientUnits="userSpaceOnUse" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient6720"
id="radialGradient7611"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1,0,0,0.1356784,0,383.01346)"
cx="295.22174"
cy="443.13766"
fx="295.22174"
fy="443.13766"
r="135.67928" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient4694"
id="linearGradient7613"
gradientUnits="userSpaceOnUse"
spreadMethod="reflect"
x1="229.03033"
y1="461.81128"
x2="393.28391"
y2="461.81128" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient5333"
id="linearGradient2913"
gradientUnits="userSpaceOnUse"
x1="271.28635"
y1="294.12686"
x2="490.23206"
y2="294.12686"
gradientTransform="matrix(1.0217389,0,0,1.0105488,-8.1719712,-6.8195787)" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient5932"
id="linearGradient2916"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(2.5891218,0,0,2.7050059,653.14704,159.9845)"
x1="-245.06445"
y1="7.705513"
x2="-62.328537"
y2="7.705513" />
<inkscape:perspective
id="perspective2920"
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
inkscape:vp_z="1 : 0.5 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 0.5 : 1"
sodipodi:type="inkscape:persp3d" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
gridtolerance="10000"
guidetolerance="10"
objecttolerance="10"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="1"
inkscape:cx="198.11269"
inkscape:cy="276.37194"
inkscape:document-units="px"
inkscape:current-layer="svg2"
showgrid="false"
inkscape:window-width="1680"
inkscape:window-height="1026"
inkscape:window-x="0"
inkscape:window-y="0"
showborder="false"
inkscape:window-maximized="1" />
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="Screen"
style="display:inline">
<rect
style="fill:#303030;fill-opacity:1;stroke:none;display:inline"
id="rect6684"
width="473.59906"
height="321.92017"
x="18.594318"
y="19.852146"
inkscape:export-filename="/home/joelm/Downloads/noVNC_screen.png"
inkscape:export-xdpi="121.61401"
inkscape:export-ydpi="121.61401" />
</g>
<g
inkscape:groupmode="layer"
id="layer15"
inkscape:label="Favicon_Box"
style="display:none">
<rect
style="opacity:0;fill:#e6e6e6;fill-opacity:1;fill-rule:nonzero;stroke:none;display:inline"
id="rect3899"
width="342.5874"
height="342.62888"
x="86.710419"
y="14.318709"
inkscape:export-filename="/home/joelm/Downloads/noVNC_favicon.png"
inkscape:export-xdpi="4.1999998"
inkscape:export-ydpi="4.1999998" />
<rect
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;display:inline"
id="rect3899-5"
width="342.5874"
height="342.62888"
x="86.710419"
y="14.31871"
inkscape:export-filename="/home/joelm/Downloads/noVNC_favicon.png"
inkscape:export-xdpi="4.1999998"
inkscape:export-ydpi="4.1999998" />
</g>
<g
inkscape:groupmode="layer"
id="layer6"
inkscape:label="Base"
style="display:inline">
<path
sodipodi:type="arc"
style="fill:url(#linearGradient5971);fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path6944"
sodipodi:cx="295.22174"
sodipodi:cy="443.13766"
sodipodi:rx="135.67928"
sodipodi:ry="18.408747"
d="m 430.90102,443.13766 a 135.67928,18.408747 0 1 1 -271.35855,0 135.67928,18.408747 0 1 1 271.35855,0 z"
transform="matrix(0.01302703,-0.3700754,8.066322,-0.0237182,-3322.837,581.39212)" />
<path
sodipodi:type="arc"
style="fill:url(#linearGradient4686);fill-opacity:1;fill-rule:nonzero;stroke:none;display:inline"
id="path4684"
sodipodi:cx="295.22174"
sodipodi:cy="443.13766"
sodipodi:rx="135.67928"
sodipodi:ry="18.408747"
d="m 430.90102,443.13766 a 135.67928,18.408747 0 1 1 -271.35855,0 135.67928,18.408747 0 1 1 271.35855,0 z"
transform="matrix(0.01219366,-0.2777519,7.5503003,-0.01780117,-2984.4223,654.88677)" />
</g>
<g
inkscape:groupmode="layer"
id="layer14"
inkscape:label="BaseHighlight"
style="display:inline">
<path
style="fill:none;stroke:#0f0626;stroke-width:3.4000001;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:0.9254902;stroke-dasharray:none;marker-mid:none"
d="m 113.22547,451.27721 c -6.48144,25.47482 27.77284,36.00307 33.46034,37.48107 75.48368,19.61581 142.61203,18.18957 201.82073,2.0728 19.20235,-5.22693 38.29352,-12.81118 45.54545,-22.87742 8.91645,-12.37672 -0.92272,-23.26137 -0.92272,-23.26137"
id="path5997"
sodipodi:nodetypes="csssc" />
</g>
<g
inkscape:groupmode="layer"
id="layer10"
inkscape:label="BaseShadow_1"
style="display:inline">
<g
id="g7591"
transform="translate(0,-2.1213203)">
<path
transform="matrix(0.01219366,-0.2777519,7.5503003,-0.01780117,-3095.3084,545.23419)"
d="m 430.90102,443.13766 a 135.67928,18.408747 0 1 1 -271.35855,0 135.67928,18.408747 0 1 1 271.35855,0 z"
sodipodi:ry="18.408747"
sodipodi:rx="135.67928"
sodipodi:cy="443.13766"
sodipodi:cx="295.22174"
id="path6716"
style="fill:url(#radialGradient7611);fill-opacity:1;fill-rule:nonzero;stroke:none;display:inline"
sodipodi:type="arc" />
<path
sodipodi:nodetypes="cscc"
id="path4964"
d="m 316.59702,421.17478 c 0,0 58.23544,7.94555 73.90264,26.394 24.79157,29.19259 -118.30811,67.5501 -242.96933,33.90089 213.50314,-1.39087 169.09283,-60.31728 169.09283,-60.31728"
style="fill:url(#linearGradient7613);fill-opacity:1;fill-rule:evenodd;stroke:none;display:inline" />
</g>
</g>
<g
inkscape:groupmode="layer"
id="layer5"
inkscape:label="Support"
style="display:inline">
<rect
style="fill:#000000;fill-opacity:0.98453603;fill-rule:nonzero;stroke:url(#linearGradient6987);stroke-width:30;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;display:inline"
id="rect6686"
width="72.335358"
height="30.264278"
x="361.94818"
y="-272.03949"
transform="matrix(0,1,-1,0,0,0)" />
</g>
<g
inkscape:groupmode="layer"
id="layer13"
inkscape:label="ScreenShade"
style="display:inline">
<rect
style="fill:url(#linearGradient2916);fill-opacity:1;fill-rule:nonzero;stroke:none"
id="rect5930"
width="473.12555"
height="321.87091"
x="18.64533"
y="19.892525"
inkscape:export-filename="/home/joelm/Downloads/noVNC_screen.png"
inkscape:export-xdpi="121.61401"
inkscape:export-ydpi="121.61401" />
<path
style="fill:url(#linearGradient2913);fill-opacity:1;fill-rule:evenodd;stroke:none;display:inline"
d="m 492.23643,341.65103 -222.74382,-0.21858 c 0,0 42.71861,-8.28882 90.56766,-25.15824 28.31396,-9.98224 57.59439,-24.1182 83.13673,-38.1986 45.98594,-25.35007 49.03937,-38.90668 49.03937,-38.90668 l 6e-5,102.4821 z"
id="path5331"
sodipodi:nodetypes="ccsscc"
inkscape:export-filename="/home/joelm/Downloads/noVNC_screen.png"
inkscape:export-xdpi="121.61401"
inkscape:export-ydpi="121.61401" />
</g>
<g
inkscape:groupmode="layer"
id="layer9"
inkscape:label="noVNC_Black"
style="display:inline">
<path
sodipodi:nodetypes="cccc"
style="fill:none;stroke:#000000;stroke-width:22.07362747;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline"
d="m 416.69139,186.74564 c -77.95059,0 -77.95059,0 -77.95059,0 l 0,73.23957 80.00192,0"
id="path5134"
inkscape:export-filename="/home/joelm/Downloads/noVNC_screen.png"
inkscape:export-xdpi="121.61401"
inkscape:export-ydpi="121.61401" />
<path
sodipodi:nodetypes="cccc"
style="fill:none;stroke:#000000;stroke-width:22.07362747;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline"
d="m 221.52501,259.96552 0,-74.30876 75.89924,74.67652 0,-73.6827"
id="path5132"
inkscape:export-filename="/home/joelm/Downloads/noVNC_screen.png"
inkscape:export-xdpi="121.61401"
inkscape:export-ydpi="121.61401" />
<g
style="fill:none;stroke:#000000;stroke-width:25.32735443;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline"
id="g5128"
transform="matrix(1.0637273,0,0,0.7140646,-87.846677,11.859872)"
inkscape:export-filename="/home/joelm/Downloads/noVNC_screen.png"
inkscape:export-xdpi="121.61401"
inkscape:export-ydpi="121.61401">
<path
sodipodi:nodetypes="cccc"
id="path5130"
d="m 181.27307,243.94727 0,42.42561 69.52825,61.54333 0,-103.59063"
style="fill:none;stroke:#000000;stroke-width:25.32735443;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
</g>
<path
sodipodi:nodetypes="cccc"
style="fill:none;stroke:#000000;stroke-width:21.4314537;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline"
d="m 106.33831,159.40077 0,-72.146954 73.69116,72.504004 0,-71.539108"
id="path5136"
inkscape:export-filename="/home/joelm/Downloads/noVNC_screen.png"
inkscape:export-xdpi="121.61401"
inkscape:export-ydpi="121.61401" />
<path
style="fill:none;stroke:#000000;stroke-width:21.4314537;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline"
d="m 219.23536,92.259872 0,71.123858 77.68903,0 -2.0092,-71.123858 c -75.6828,0 -75.67983,0 -75.67983,0 z"
id="path5138"
inkscape:export-filename="/home/joelm/Downloads/noVNC_screen.png"
inkscape:export-xdpi="121.61401"
inkscape:export-ydpi="121.61401" />
</g>
<g
inkscape:groupmode="layer"
id="layer4"
inkscape:label="noVNC"
style="display:inline">
<g
style="fill:none;display:inline"
id="g5142"
transform="matrix(1.0637273,0,0,0.7140646,-87.924897,11.633929)"
inkscape:export-filename="/home/joelm/Downloads/noVNC_favicon.png"
inkscape:export-xdpi="4.1999998"
inkscape:export-ydpi="4.1999998">
<path
sodipodi:nodetypes="cccc"
id="path5144"
d="m 181.27307,243.94727 0,42.42561 69.52825,61.54333 0,-103.59063"
style="fill:none;stroke:#f2eb41;stroke-width:19.83890915;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
</g>
<path
sodipodi:nodetypes="cccc"
style="fill:none;stroke:#f2eb41;stroke-width:17.29026794;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline"
d="m 221.44679,259.73958 0,-74.30877 75.89925,74.67651 0,-73.68269"
id="path5146"
inkscape:export-filename="/home/joelm/Downloads/noVNC_favicon.png"
inkscape:export-xdpi="4.1999998"
inkscape:export-ydpi="4.1999998" />
<path
sodipodi:nodetypes="cccc"
style="fill:none;stroke:#f2eb41;stroke-width:17.29026794;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline"
d="m 416.61319,186.51969 c -77.95061,0 -77.95061,0 -77.95061,0 l 0,73.23958 80.00193,0"
id="path5148"
inkscape:export-filename="/home/joelm/Downloads/noVNC_favicon.png"
inkscape:export-xdpi="4.1999998"
inkscape:export-ydpi="4.1999998" />
<path
sodipodi:nodetypes="cccc"
style="fill:none;stroke:#00df00;stroke-width:16.78725243;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline"
d="m 106.26238,159.1814 0,-72.146955 73.69115,72.503995 0,-71.539084"
id="path5150"
inkscape:export-filename="/home/joelm/Downloads/noVNC_favicon.png"
inkscape:export-xdpi="4.1999998"
inkscape:export-ydpi="4.1999998" />
<path
style="fill:none;stroke:#00df00;stroke-width:16.78725243;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline"
d="m 219.15941,92.040508 0,71.123862 77.68904,0 -2.00919,-71.123862 c -75.68283,0 -75.67985,0 -75.67985,0 z"
id="path5152"
inkscape:export-filename="/home/joelm/Downloads/noVNC_favicon.png"
inkscape:export-xdpi="4.1999998"
inkscape:export-ydpi="4.1999998" />
</g>
<g
inkscape:groupmode="layer"
id="layer7"
inkscape:label="noVNC_Save"
style="display:none">
<g
style="display:inline"
id="g3903"
transform="matrix(0.9560363,0,0,0.9560363,-6.710991,-337.27245)">
<g
transform="matrix(1.0081199,0,0,1.0081199,-11.487266,-121.24134)"
id="g3905"
style="fill:none">
<path
style="fill:none;stroke:#00ff00;stroke-width:19.83890915;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
d="m 83.455645,199.50003 101.674295,0"
id="path3907"
sodipodi:nodetypes="cc" />
<path
style="fill:none;stroke:#00ff00;stroke-width:19.83890915;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
d="m 81.687109,243.94727 99.585961,0 0,42.42561 79.06591,61.54333 0,-103.59063"
id="path3909"
sodipodi:nodetypes="ccccc" />
<path
style="fill:none;stroke:#00ff00;stroke-width:19.83890915;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
d="M 87.323416,283.46826 175.10772,160.81511"
id="path3911"
sodipodi:nodetypes="cc" />
</g>
<path
id="path3913"
d="m 289.84756,229.03542 0,-104.90947 71.93153,105.42866 0,-104.02559"
style="fill:none;stroke:#00ff00;stroke-width:20;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
sodipodi:nodetypes="cccc" />
<path
id="path3915"
d="m 472.59255,125.66322 c -73.87564,0 -73.87564,0 -73.87564,0 l 0,103.4 75.81973,0"
style="fill:none;stroke:#00ff00;stroke-width:20;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
sodipodi:nodetypes="cccc" />
</g>
</g>
<g
inkscape:groupmode="layer"
id="layer3"
inkscape:label="Bezel"
style="display:inline">
<rect
style="fill:none;stroke:#040404;stroke-width:24.66699982;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;display:inline"
id="rect6660"
width="485.7326"
height="337.33023"
x="14.179501"
y="14.179514" />
</g>
<g
inkscape:groupmode="layer"
id="layer8"
inkscape:label="BezelHighlight"
style="display:inline">
<rect
style="fill:none;stroke:#100626;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:0.89803922;stroke-dasharray:none;stroke-dashoffset:0;display:inline"
id="rect3888"
width="485.7326"
height="337.33023"
x="13.819346"
y="14.179423" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 501 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 404 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 317 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 963 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 511 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 517 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 497 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 513 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.
Binary file not shown.
+405
View File
@@ -0,0 +1,405 @@
/*
* noVNC base CSS
* Copyright (C) 2012 Joel Martin
* noVNC is licensed under the LGPL-3 (see LICENSE.txt)
* This file is licensed under the 2-Clause BSD license (see LICENSE.txt).
*/
body {
margin:0;
padding:0;
font-family: Helvetica;
/*Background image with light grey curve.*/
background-color:#494949;
background-repeat:no-repeat;
background-position:right bottom;
height:100%;
}
html {
height:100%;
}
#noVNC_controls ul {
list-style: none;
margin: 0px;
padding: 0px;
}
#noVNC_controls li {
padding-bottom:8px;
}
#noVNC_host {
width:150px;
}
#noVNC_port {
width: 80px;
}
#noVNC_password {
width: 150px;
}
#noVNC_encrypt {
}
#noVNC_connectTimeout {
width: 30px;
}
#noVNC_path {
width: 100px;
}
#noVNC_connect_button {
width: 110px;
float:right;
}
#noVNC_view_drag_button {
display: none;
}
#sendCtrlAltDelButton {
display: none;
}
#noVNC_mobile_buttons {
display: none;
}
.noVNC-buttons-left {
float: left;
padding-left:10px;
padding-top:4px;
}
.noVNC-buttons-right {
float:right;
right: 0px;
padding-right:10px;
padding-top:4px;
}
#noVNC_status_bar {
margin-top: 0px;
padding: 0px;
}
#noVNC_status_bar div {
font-size: 12px;
padding-top: 4px;
width:100%;
}
#noVNC_status {
height:20px;
text-align: center;
}
#noVNC_settings_menu {
margin: 3px;
text-align: left;
}
#noVNC_settings_menu ul {
list-style: none;
margin: 0px;
padding: 0px;
}
#noVNC_apply {
float:right;
}
.noVNC_status_normal {
background: #eee;
}
.noVNC_status_error {
background: #f44;
}
.noVNC_status_warn {
background: #ff4;
}
/* Do not set width/height for VNC_screen or VNC_canvas or incorrect
* scaling will occur. Canvas resizes to remote VNC settings */
#noVNC_screen_pad {
margin: 0px;
padding: 0px;
height: 44px;
}
#noVNC_screen {
text-align: center;
display: table;
width:100%;
height:100%;
background-color:#313131;
border-bottom-right-radius: 800px 600px;
/*border-top-left-radius: 800px 600px;*/
}
#noVNC_container, #noVNC_canvas {
margin: 0px;
padding: 0px;
}
#noVNC_canvas {
left: 0px;
}
#VNC_clipboard_clear_button {
float:right;
}
#VNC_clipboard_text {
font-size: 11px;
}
#noVNC_clipboard_clear_button {
float:right;
}
/*Bubble contents divs*/
#noVNC_settings {
display:none;
margin-top:77px;
right:20px;
position:fixed;
}
#noVNC_controls {
display:none;
margin-top:77px;
right:12px;
position:fixed;
}
#noVNC_controls.top:after {
right:15px;
}
#noVNC_description {
display:none;
position:fixed;
margin-top:77px;
right:20px;
left:20px;
padding:15px;
color:#000;
background:#eee; /* default background for browsers without gradient support */
border:2px solid #E0E0E0;
-webkit-border-radius:10px;
-moz-border-radius:10px;
border-radius:10px;
}
#noVNC_clipboard {
display:none;
margin-top:77px;
right:30px;
position:fixed;
}
#noVNC_clipboard.top:after {
right:85px;
}
#keyboardinput {
width:1px;
height:1px;
background-color:#fff;
color:#fff;
border:0;
position: relative;
left: -40px;
z-index: -1;
}
.noVNC_status_warn {
background-color:yellow;
}
/*
* Advanced Styling
*/
/* Control bar */
#noVNC-control-bar {
position:fixed;
background: #b2bdcd; /* Old browsers */
background: -moz-linear-gradient(top, #b2bdcd 0%, #899cb3 49%, #7e93af 51%, #6e84a3 100%); /* FF3.6+ */
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#b2bdcd), color-stop(49%,#899cb3), color-stop(51%,#7e93af), color-stop(100%,#6e84a3)); /* Chrome,Safari4+ */
background: -webkit-linear-gradient(top, #b2bdcd 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* Chrome10+,Safari5.1+ */
background: -o-linear-gradient(top, #b2bdcd 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* Opera11.10+ */
background: -ms-linear-gradient(top, #b2bdcd 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* IE10+ */
background: linear-gradient(top, #b2bdcd 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* W3C */
display:block;
height:44px;
left:0;
top:0;
width:100%;
z-index:200;
}
.noVNC_status_button {
padding: 4px 4px;
vertical-align: middle;
border:1px solid #869dbc;
-webkit-border-radius: 6px;
-moz-border-radius: 6px;
border-radius: 6px;
background: #b2bdcd; /* Old browsers */
background: -moz-linear-gradient(top, #b2bdcd 0%, #899cb3 49%, #7e93af 51%, #6e84a3 100%); /* FF3.6+ */
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#b2bdcd), color-stop(49%,#899cb3), color-stop(51%,#7e93af), color-stop(100%,#6e84a3)); /* Chrome,Safari4+ */
background: -webkit-linear-gradient(top, #b2bdcd 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* Chrome10+,Safari5.1+ */
background: -o-linear-gradient(top, #b2bdcd 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* Opera11.10+ */
background: -ms-linear-gradient(top, #b2bdcd 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* IE10+ */
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#b2bdcd', endColorstr='#6e84a3',GradientType=0 ); /* IE6-9 */
background: linear-gradient(top, #b2bdcd 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* W3C */
/*box-shadow:inset 0.4px 0.4px 0.4px #000000;*/
}
.noVNC_status_button_selected {
padding: 4px 4px;
vertical-align: middle;
border:1px solid #4366a9;
-webkit-border-radius: 6px;
-moz-border-radius: 6px;
background: #779ced; /* Old browsers */
background: -moz-linear-gradient(top, #779ced 0%, #3970e0 49%, #2160dd 51%, #2463df 100%); /* FF3.6+ */
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#779ced), color-stop(49%,#3970e0), color-stop(51%,#2160dd), color-stop(100%,#2463df)); /* Chrome,Safari4+ */
background: -webkit-linear-gradient(top, #779ced 0%,#3970e0 49%,#2160dd 51%,#2463df 100%); /* Chrome10+,Safari5.1+ */
background: -o-linear-gradient(top, #779ced 0%,#3970e0 49%,#2160dd 51%,#2463df 100%); /* Opera11.10+ */
background: -ms-linear-gradient(top, #779ced 0%,#3970e0 49%,#2160dd 51%,#2463df 100%); /* IE10+ */
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#779ced', endColorstr='#2463df',GradientType=0 ); /* IE6-9 */
background: linear-gradient(top, #779ced 0%,#3970e0 49%,#2160dd 51%,#2463df 100%); /* W3C */
/*box-shadow:inset 0.4px 0.4px 0.4px #000000;*/
}
/*Settings Bubble*/
.triangle-right {
position:relative;
padding:15px;
margin:1em 0 3em;
color:#fff;
background:#fff; /* default background for browsers without gradient support */
/* css3 */
/*background:-webkit-gradient(linear, 0 0, 0 100%, from(#2e88c4), to(#075698));
background:-moz-linear-gradient(#2e88c4, #075698);
background:-o-linear-gradient(#2e88c4, #075698);
background:linear-gradient(#2e88c4, #075698);*/
-webkit-border-radius:10px;
-moz-border-radius:10px;
border-radius:10px;
color:#000;
border:2px solid #E0E0E0;
}
.triangle-right.top:after {
border-color: transparent #E0E0E0;
border-width: 20px 20px 0 0;
bottom: auto;
left: auto;
right: 50px;
top: -20px;
}
.triangle-right:after {
content:"";
position:absolute;
bottom:-20px; /* value = - border-top-width - border-bottom-width */
left:50px; /* controls horizontal position */
border-width:20px 0 0 20px; /* vary these values to change the angle of the vertex */
border-style:solid;
border-color:#E0E0E0 transparent;
/* reduce the damage in FF3.0 */
display:block;
width:0;
}
.triangle-right.top:after {
top:-40px; /* value = - border-top-width - border-bottom-width */
right:50px; /* controls horizontal position */
bottom:auto;
left:auto;
border-width:40px 40px 0 0; /* vary these values to change the angle of the vertex */
border-color:transparent #E0E0E0;
}
/*Default noVNC logo.*/
/* From: http://fonts.googleapis.com/css?family=Orbitron:700 */
@font-face {
font-family: 'Orbitron';
font-style: normal;
font-weight: 700;
src: local('?'), url('Orbitron700.woff') format('woff'),
url('Orbitron700.ttf') format('truetype');
}
#noVNC_logo {
margin-top: 170px;
margin-left: 10px;
color:yellow;
text-align:left;
font-family: 'Orbitron', 'OrbitronTTF', sans-serif;
line-height:90%;
text-shadow:
5px 5px 0 #000,
-1px -1px 0 #000,
1px -1px 0 #000,
-1px 1px 0 #000,
1px 1px 0 #000;
}
#noVNC_logo span{
color:green;
}
/* ----------------------------------------
* Media sizing
* ----------------------------------------
*/
.noVNC_status_button {
font-size: 12px;
}
#noVNC_clipboard_text {
width: 500px;
}
#noVNC_logo {
font-size: 180px;
}
@media screen and (min-width: 481px) and (max-width: 640px) {
.noVNC_status_button {
font-size: 10px;
}
#noVNC_clipboard_text {
width: 410px;
}
#noVNC_logo {
font-size: 150px;
}
}
@media screen and (min-width: 321px) and (max-width: 480px) {
.noVNC_status_button {
font-size: 10px;
}
#noVNC_clipboard_text {
width: 250px;
}
#noVNC_logo {
font-size: 110px;
}
}
@media screen and (max-width: 320px) {
.noVNC_status_button {
font-size: 9px;
}
#noVNC_clipboard_text {
width: 220px;
}
#noVNC_logo {
font-size: 90px;
}
}
+1 -1
View File
@@ -116,7 +116,7 @@ decode: function (data, offset) {
padding = (data.charAt(i) === pad); padding = (data.charAt(i) === pad);
// Skip illegal characters and whitespace // Skip illegal characters and whitespace
if (c === -1) { if (c === -1) {
console.error("Illegal character '" + data.charCodeAt(i) + "'"); console.error("Illegal character code " + data.charCodeAt(i) + " at position " + i);
continue; continue;
} }
+43 -115
View File
@@ -1,124 +1,52 @@
body { /*
margin: 0px; * noVNC base CSS
font-size: 13px; * Copyright (C) 2012 Joel Martin
color: #111; * noVNC is licensed under the LGPL-3 (see LICENSE.txt)
font-family: "Helvetica"; * This file is licensed under the 2-Clause BSD license (see LICENSE.txt).
*/
#keyboardinput {
background-color:#000;
} }
#VNC_controls { #noVNC-control-bar {
background: #111; background: #4c4c4c; /* Old browsers */
line-height: 1em; background: -moz-linear-gradient(top, #4c4c4c 0%, #2c2c2c 50%, #000000 51%, #131313 100%); /* FF3.6+ */
color: #FFF; background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#4c4c4c), color-stop(50%,#2c2c2c), color-stop(51%,#000000), color-stop(100%,#131313)); /* Chrome,Safari4+ */
overflow: hidden; background: -webkit-linear-gradient(top, #4c4c4c 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* Chrome10+,Safari5.1+ */
padding: 4px 24px; background: -o-linear-gradient(top, #4c4c4c 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* Opera11.10+ */
background: -ms-linear-gradient(top, #4c4c4c 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* IE10+ */
background: linear-gradient(top, #4c4c4c 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* W3C */
} }
#VNC_controls ul { .triangle-right {
list-style:none; border:2px solid #fff;
list-style-position: outside; background:#000;
margin: 0px; color:#fff;
padding: 0px;
}
#VNC_controls li {
margin-right: 15px;
padding: 2px 0px;
float: left;
}
#VNC_controls li input[type=text],
#VNC_controls li input[type=password] {
border: 2px solid #333;
} }
#VNC_host { .noVNC_status_button {
width: 100px; font-size: 12px;
} vertical-align: middle;
#VNC_port { border:1px solid #4c4c4c;
width: 50px;
} background: #4c4c4c; /* Old browsers */
#VNC_password { background: -moz-linear-gradient(top, #4c4c4c 0%, #2c2c2c 50%, #000000 51%, #131313 100%); /* FF3.6+ */
width: 80px; background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#4c4c4c), color-stop(50%,#2c2c2c), color-stop(51%,#000000), color-stop(100%,#131313)); /* Chrome,Safari4+ */
} background: -webkit-linear-gradient(top, #4c4c4c 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* Chrome10+,Safari5.1+ */
#VNC_encrypt { background: -o-linear-gradient(top, #4c4c4c 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* Opera11.10+ */
} background: -ms-linear-gradient(top, #4c4c4c 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* IE10+ */
#VNC_connect_button { filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#4c4c4c', endColorstr='#131313',GradientType=0 ); /* IE6-9 */
width: 100px; background: linear-gradient(top, #4c4c4c 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* W3C */
} }
#VNC_status_bar td { .noVNC_status_button_selected {
padding: 0px; background: #9dd53a; /* Old browsers */
margin: 0px; background: -moz-linear-gradient(top, #9dd53a 0%, #a1d54f 50%, #80c217 51%, #7cbc0a 100%); /* FF3.6+ */
} background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#9dd53a), color-stop(50%,#a1d54f), color-stop(51%,#80c217), color-stop(100%,#7cbc0a)); /* Chrome,Safari4+ */
#VNC_status_bar div { background: -webkit-linear-gradient(top, #9dd53a 0%,#a1d54f 50%,#80c217 51%,#7cbc0a 100%); /* Chrome10+,Safari5.1+ */
font-size: 12px; background: -o-linear-gradient(top, #9dd53a 0%,#a1d54f 50%,#80c217 51%,#7cbc0a 100%); /* Opera11.10+ */
font-weight: bold; background: -ms-linear-gradient(top, #9dd53a 0%,#a1d54f 50%,#80c217 51%,#7cbc0a 100%); /* IE10+ */
text-align: center; filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#9dd53a', endColorstr='#7cbc0a',GradientType=0 ); /* IE6-9 */
margin: 0px; background: linear-gradient(top, #9dd53a 0%,#a1d54f 50%,#80c217 51%,#7cbc0a 100%); /* W3C */
padding: 1em;
}
.VNC_status_button {
font-size: 10px;
margin: 0px;
padding: 0px;
}
#VNC_status {
text-align: center;
}
#VNC_settings_menu {
display: none;
position: absolute;
width: 13em;
border: 1px solid #888;
color: #111;
font-weight: normal;
background-color: #f0f2f6;
padding: 5px; margin: 3px;
z-index: 100; opacity: 1;
text-align: left; white-space: normal;
}
#VNC_settings_menu ul {
list-style: none;
margin: 0px;
padding: 0px;
}
.VNC_buttons_right {
text-align: right;
}
.VNC_buttons_left {
text-align: left;
}
.VNC_status_normal {
background: #111;
color: #fff;
}
.VNC_status_error {
background: #111;
color: #f44;
}
.VNC_status_warn {
background: #111;
color: #ff4;
}
#VNC_screen {
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
border-radius: 10px;
background: #111;
padding: 20px;
margin: 0 auto;
color: #FFF;
margin-top: 20px;
text-align: center;
/* This causes the width of the outer div(#screen) honor the size of the inner (#vnc) div */
display: table;
table-layout: auto;
}
#VNC_canvas {
background: #111;
margin: 0 auto;
}
#VNC_clipboard {
display: none;
} }
+33
View File
@@ -0,0 +1,33 @@
/*
* noVNC base CSS
* Copyright (C) 2012 Joel Martin
* noVNC is licensed under the LGPL-3 (see LICENSE.txt)
* This file is licensed under the 2-Clause BSD license (see LICENSE.txt).
*/
#noVNC-control-bar {
background-color:#04073d;
background-image: -webkit-gradient(
linear,
left bottom,
left top,
color-stop(0.54, rgb(10,15,79)),
color-stop(0.5, rgb(4,7,61))
);
background-image: -moz-linear-gradient(
center bottom,
rgb(10,15,79) 54%,
rgb(4,7,61) 50%
);
}
.triangle-right {
border:2px solid #fff;
background:#04073d;
color:#fff;
}
#keyboardinput {
background-color:#04073d;
}
+377 -188
View File
@@ -1,6 +1,6 @@
/* /*
* noVNC: HTML5 VNC client * noVNC: HTML5 VNC client
* Copyright (C) 2011 Joel Martin * Copyright (C) 2012 Joel Martin
* Licensed under LGPL-3 (see LICENSE.txt) * Licensed under LGPL-3 (see LICENSE.txt)
* *
* See README.md for usage and integration instructions. * See README.md for usage and integration instructions.
@@ -19,19 +19,26 @@ var that = {}, // Public API methods
c_ctx = null, c_ctx = null,
c_forceCanvas = false, c_forceCanvas = false,
c_imageData, c_rgbxImage, c_cmapImage, // Queued drawing actions for in-order rendering
renderQ = [],
// Predefine function variables (jslint) // Predefine function variables (jslint)
imageDataCreate, imageDataGet, rgbxImageData, cmapImageData, imageDataGet, rgbImageData, bgrxImageData, cmapImageData,
rgbxImageFill, cmapImageFill, setFillColor, rescale, flush, setFillColor, rescale, scan_renderQ,
c_width = 0, // The full frame buffer (logical canvas) size
c_height = 0, fb_width = 0,
fb_height = 0,
// The visible "physical canvas" viewport
viewport = {'x': 0, 'y': 0, 'w' : 0, 'h' : 0 },
cleanRect = {'x1': 0, 'y1': 0, 'x2': -1, 'y2': -1},
c_prevStyle = "", c_prevStyle = "",
tile = null,
tile16x16 = null,
tile_x = 0,
tile_y = 0;
c_webkit_bug = false,
c_flush_timer = null;
// Configuration attributes // Configuration attributes
Util.conf_defaults(conf, that, defaults, [ Util.conf_defaults(conf, that, defaults, [
@@ -41,6 +48,7 @@ Util.conf_defaults(conf, that, defaults, [
['true_color', 'rw', 'bool', true, 'Use true-color pixel data'], ['true_color', 'rw', 'bool', true, 'Use true-color pixel data'],
['colourMap', 'rw', 'arr', [], 'Colour map array (when not true-color)'], ['colourMap', 'rw', 'arr', [], 'Colour map array (when not true-color)'],
['scale', 'rw', 'float', 1.0, 'Display area scale factor 0.0 - 1.0'], ['scale', 'rw', 'float', 1.0, 'Display area scale factor 0.0 - 1.0'],
['viewport', 'rw', 'bool', false, 'Use a viewport set with viewportChange()'],
['width', 'rw', 'int', null, 'Display area width'], ['width', 'rw', 'int', null, 'Display area width'],
['height', 'rw', 'int', null, 'Display area height'], ['height', 'rw', 'int', null, 'Display area height'],
@@ -55,20 +63,11 @@ that.get_context = function () { return c_ctx; };
that.set_scale = function(scale) { rescale(scale); }; that.set_scale = function(scale) { rescale(scale); };
that.set_width = function (val) { that.resize(val, c_height); }; that.set_width = function (val) { that.resize(val, fb_height); };
that.get_width = function() { return c_width; }; that.get_width = function() { return fb_width; };
that.set_height = function (val) { that.resize(c_width, val); }; that.set_height = function (val) { that.resize(fb_width, val); };
that.get_height = function() { return c_height; }; that.get_height = function() { return fb_height; };
that.set_prefer_js = function(val) {
if (val && c_forceCanvas) {
Util.Warn("Preferring Javascript to Canvas ops is not supported");
return false;
}
conf.prefer_js = val;
return true;
};
@@ -80,7 +79,7 @@ that.set_prefer_js = function(val) {
function constructor() { function constructor() {
Util.Debug(">> Display.constructor"); Util.Debug(">> Display.constructor");
var c, func, imgTest, tval, i, curDat, curSave, var c, func, i, curDat, curSave,
has_imageData = false, UE = Util.Engine; has_imageData = false, UE = Util.Engine;
if (! conf.target) { throw("target must be set"); } if (! conf.target) { throw("target must be set"); }
@@ -103,70 +102,19 @@ function constructor() {
that.clear(); that.clear();
/* // Check canvas features
* Determine browser Canvas feature support if ('createImageData' in c_ctx) {
* and select fastest rendering methods conf.render_mode = "canvas rendering";
*/
tval = 0;
try {
imgTest = c_ctx.getImageData(0, 0, 1,1);
imgTest.data[0] = 123;
imgTest.data[3] = 255;
c_ctx.putImageData(imgTest, 0, 0);
tval = c_ctx.getImageData(0, 0, 1, 1).data[0];
if (tval === 123) {
has_imageData = true;
}
} catch (exc1) {}
if (has_imageData) {
Util.Info("Canvas supports imageData");
c_forceCanvas = false;
if (c_ctx.createImageData) {
// If it's there, it's faster
Util.Info("Using Canvas createImageData");
conf.render_mode = "createImageData rendering";
c_imageData = imageDataCreate;
} else if (c_ctx.getImageData) {
// I think this is mostly just Opera
Util.Info("Using Canvas getImageData");
conf.render_mode = "getImageData rendering";
c_imageData = imageDataGet;
}
Util.Info("Prefering javascript operations");
if (conf.prefer_js === null) {
conf.prefer_js = true;
}
c_rgbxImage = rgbxImageData;
c_cmapImage = cmapImageData;
} else { } else {
Util.Warn("Canvas lacks imageData, using fillRect (slow)"); throw("Canvas does not support createImageData");
conf.render_mode = "fillRect rendering (slow)"; }
c_forceCanvas = true; if (conf.prefer_js === null) {
conf.prefer_js = false; Util.Info("Prefering javascript operations");
c_rgbxImage = rgbxImageFill; conf.prefer_js = true;
c_cmapImage = cmapImageFill;
} }
if (UE.webkit && UE.webkit >= 534.7 && UE.webkit <= 534.9) { // Initialize cached tile imageData
// Workaround WebKit canvas rendering bug #46319 tile16x16 = c_ctx.createImageData(16, 16);
conf.render_mode += ", webkit bug workaround";
Util.Debug("Working around WebKit bug #46319");
c_webkit_bug = true;
for (func in {"fillRect":1, "copyImage":1, "rgbxImage":1,
"cmapImage":1, "blitStringImage":1}) {
that[func] = (function() {
var myfunc = that[func]; // Save original function
//Util.Debug("Wrapping " + func);
return function() {
myfunc.apply(this, arguments);
if (!c_flush_timer) {
c_flush_timer = setTimeout(flush, 100);
}
};
}());
}
}
/* /*
* Determine browser support for setting the cursor via data URI * Determine browser support for setting the cursor via data URI
@@ -217,7 +165,10 @@ rescale = function(factor) {
return; return;
} }
if (factor > 1.0) {
if (typeof(factor) === "undefined") {
factor = conf.scale;
} else if (factor > 1.0) {
factor = 1.0; factor = 1.0;
} else if (factor < 0.1) { } else if (factor < 0.1) {
factor = 0.1; factor = 0.1;
@@ -234,26 +185,14 @@ rescale = function(factor) {
c.style[tp] = "scale(" + conf.scale + ") translate(-" + x + "px, -" + y + "px)"; c.style[tp] = "scale(" + conf.scale + ") translate(-" + x + "px, -" + y + "px)";
}; };
// Force canvas redraw (for webkit bug #46319 workaround)
flush = function() {
var old_val;
//Util.Debug(">> flush");
old_val = conf.target.style.marginRight;
conf.target.style.marginRight = "1px";
c_flush_timer = null;
setTimeout(function () {
conf.target.style.marginRight = old_val;
}, 1);
};
setFillColor = function(color) { setFillColor = function(color) {
var rgb, newStyle; var bgr, newStyle;
if (conf.true_color) { if (conf.true_color) {
rgb = color; bgr = color;
} else { } else {
rgb = conf.colourMap[color[0]]; bgr = conf.colourMap[color[0]];
} }
newStyle = "rgb(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + ")"; newStyle = "rgb(" + bgr[2] + "," + bgr[1] + "," + bgr[0] + ")";
if (newStyle !== c_prevStyle) { if (newStyle !== c_prevStyle) {
c_ctx.fillStyle = newStyle; c_ctx.fillStyle = newStyle;
c_prevStyle = newStyle; c_prevStyle = newStyle;
@@ -265,18 +204,205 @@ setFillColor = function(color) {
// Public API interface functions // Public API interface functions
// //
that.resize = function(width, height) { // Shift and/or resize the visible viewport
var c = conf.target; that.viewportChange = function(deltaX, deltaY, width, height) {
var c = conf.target, v = viewport, cr = cleanRect,
saveImg = null, saveStyle, x1, y1, vx2, vy2, w, h;
if (!conf.viewport) {
Util.Debug("Setting viewport to full display region");
deltaX = -v.w; // Clamped later if out of bounds
deltaY = -v.h; // Clamped later if out of bounds
width = fb_width;
height = fb_height;
}
if (typeof(deltaX) === "undefined") { deltaX = 0; }
if (typeof(deltaY) === "undefined") { deltaY = 0; }
if (typeof(width) === "undefined") { width = v.w; }
if (typeof(height) === "undefined") { height = v.h; }
// Size change
if (width > fb_width) { width = fb_width; }
if (height > fb_height) { height = fb_height; }
if ((v.w !== width) || (v.h !== height)) {
// Change width
if ((width < v.w) && (cr.x2 > v.x + width -1)) {
cr.x2 = v.x + width - 1;
}
v.w = width;
// Change height
if ((height < v.h) && (cr.y2 > v.y + height -1)) {
cr.y2 = v.y + height - 1;
}
v.h = height;
if (v.w > 0 && v.h > 0 && c.width > 0 && c.height > 0) {
saveImg = c_ctx.getImageData(0, 0,
(c.width < v.w) ? c.width : v.w,
(c.height < v.h) ? c.height : v.h);
}
c.width = v.w;
c.height = v.h;
if (saveImg) {
c_ctx.putImageData(saveImg, 0, 0);
}
}
vx2 = v.x + v.w - 1;
vy2 = v.y + v.h - 1;
// Position change
if ((deltaX < 0) && ((v.x + deltaX) < 0)) {
deltaX = - v.x;
}
if ((vx2 + deltaX) >= fb_width) {
deltaX -= ((vx2 + deltaX) - fb_width + 1);
}
if ((v.y + deltaY) < 0) {
deltaY = - v.y;
}
if ((vy2 + deltaY) >= fb_height) {
deltaY -= ((vy2 + deltaY) - fb_height + 1);
}
if ((deltaX === 0) && (deltaY === 0)) {
//Util.Debug("skipping viewport change");
return;
}
Util.Debug("viewportChange deltaX: " + deltaX + ", deltaY: " + deltaY);
v.x += deltaX;
vx2 += deltaX;
v.y += deltaY;
vy2 += deltaY;
// Update the clean rectangle
if (v.x > cr.x1) {
cr.x1 = v.x;
}
if (vx2 < cr.x2) {
cr.x2 = vx2;
}
if (v.y > cr.y1) {
cr.y1 = v.y;
}
if (vy2 < cr.y2) {
cr.y2 = vy2;
}
if (deltaX < 0) {
// Shift viewport left, redraw left section
x1 = 0;
w = - deltaX;
} else {
// Shift viewport right, redraw right section
x1 = v.w - deltaX;
w = deltaX;
}
if (deltaY < 0) {
// Shift viewport up, redraw top section
y1 = 0;
h = - deltaY;
} else {
// Shift viewport down, redraw bottom section
y1 = v.h - deltaY;
h = deltaY;
}
// Copy the valid part of the viewport to the shifted location
saveStyle = c_ctx.fillStyle;
c_ctx.fillStyle = "rgb(255,255,255)";
if (deltaX !== 0) {
//that.copyImage(0, 0, -deltaX, 0, v.w, v.h);
//that.fillRect(x1, 0, w, v.h, [255,255,255]);
c_ctx.drawImage(c, 0, 0, v.w, v.h, -deltaX, 0, v.w, v.h);
c_ctx.fillRect(x1, 0, w, v.h);
}
if (deltaY !== 0) {
//that.copyImage(0, 0, 0, -deltaY, v.w, v.h);
//that.fillRect(0, y1, v.w, h, [255,255,255]);
c_ctx.drawImage(c, 0, 0, v.w, v.h, 0, -deltaY, v.w, v.h);
c_ctx.fillRect(0, y1, v.w, h);
}
c_ctx.fillStyle = saveStyle;
};
// Return a map of clean and dirty areas of the viewport and reset the
// tracking of clean and dirty areas.
//
// Returns: {'cleanBox': {'x': x, 'y': y, 'w': w, 'h': h},
// 'dirtyBoxes': [{'x': x, 'y': y, 'w': w, 'h': h}, ...]}
that.getCleanDirtyReset = function() {
var v = viewport, c = cleanRect, cleanBox, dirtyBoxes = [],
vx2 = v.x + v.w - 1, vy2 = v.y + v.h - 1;
// Copy the cleanRect
cleanBox = {'x': c.x1, 'y': c.y1,
'w': c.x2 - c.x1 + 1, 'h': c.y2 - c.y1 + 1};
if ((c.x1 >= c.x2) || (c.y1 >= c.y2)) {
// Whole viewport is dirty
dirtyBoxes.push({'x': v.x, 'y': v.y, 'w': v.w, 'h': v.h});
} else {
// Redraw dirty regions
if (v.x < c.x1) {
// left side dirty region
dirtyBoxes.push({'x': v.x, 'y': v.y,
'w': c.x1 - v.x + 1, 'h': v.h});
}
if (vx2 > c.x2) {
// right side dirty region
dirtyBoxes.push({'x': c.x2 + 1, 'y': v.y,
'w': vx2 - c.x2, 'h': v.h});
}
if (v.y < c.y1) {
// top/middle dirty region
dirtyBoxes.push({'x': c.x1, 'y': v.y,
'w': c.x2 - c.x1 + 1, 'h': c.y1 - v.y});
}
if (vy2 > c.y2) {
// bottom/middle dirty region
dirtyBoxes.push({'x': c.x1, 'y': c.y2 + 1,
'w': c.x2 - c.x1 + 1, 'h': vy2 - c.y2});
}
}
// Reset the cleanRect to the whole viewport
cleanRect = {'x1': v.x, 'y1': v.y,
'x2': v.x + v.w - 1, 'y2': v.y + v.h - 1};
return {'cleanBox': cleanBox, 'dirtyBoxes': dirtyBoxes};
};
// Translate viewport coordinates to absolute coordinates
that.absX = function(x) {
return x + viewport.x;
};
that.absY = function(y) {
return y + viewport.y;
};
that.resize = function(width, height) {
c_prevStyle = ""; c_prevStyle = "";
c.width = width; fb_width = width;
c.height = height; fb_height = height;
c_width = c.offsetWidth;
c_height = c.offsetHeight;
rescale(conf.scale); rescale(conf.scale);
that.viewportChange();
}; };
that.clear = function() { that.clear = function() {
@@ -286,67 +412,72 @@ that.clear = function() {
that.blitStringImage(conf.logo.data, 0, 0); that.blitStringImage(conf.logo.data, 0, 0);
} else { } else {
that.resize(640, 20); that.resize(640, 20);
c_ctx.clearRect(0, 0, c_width, c_height); c_ctx.clearRect(0, 0, viewport.w, viewport.h);
} }
renderQ = [];
// No benefit over default ("source-over") in Chrome and firefox // No benefit over default ("source-over") in Chrome and firefox
//c_ctx.globalCompositeOperation = "copy"; //c_ctx.globalCompositeOperation = "copy";
}; };
that.fillRect = function(x, y, width, height, color) { that.fillRect = function(x, y, width, height, color) {
setFillColor(color); setFillColor(color);
c_ctx.fillRect(x, y, width, height); c_ctx.fillRect(x - viewport.x, y - viewport.y, width, height);
}; };
that.copyImage = function(old_x, old_y, new_x, new_y, width, height) { that.copyImage = function(old_x, old_y, new_x, new_y, w, h) {
c_ctx.drawImage(conf.target, old_x, old_y, width, height, var x1 = old_x - viewport.x, y1 = old_y - viewport.y,
new_x, new_y, width, height); x2 = new_x - viewport.x, y2 = new_y - viewport.y;
c_ctx.drawImage(conf.target, x1, y1, w, h, x2, y2, w, h);
}; };
/*
* Tile rendering functions optimized for rendering engines. // Start updating a tile
* that.startTile = function(x, y, width, height, color) {
* - In Chrome/webkit, Javascript image data array manipulations are var data, bgr, red, green, blue, i;
* faster than direct Canvas fillStyle, fillRect rendering. In tile_x = x;
* gecko, Javascript array handling is much slower. tile_y = y;
*/ if ((width === 16) && (height === 16)) {
that.getTile = function(x, y, width, height, color) { tile = tile16x16;
var img, data = [], rgb, red, green, blue, i; } else {
img = {'x': x, 'y': y, 'width': width, 'height': height, tile = c_ctx.createImageData(width, height);
'data': data}; }
data = tile.data;
if (conf.prefer_js) { if (conf.prefer_js) {
if (conf.true_color) { if (conf.true_color) {
rgb = color; bgr = color;
} else { } else {
rgb = conf.colourMap[color[0]]; bgr = conf.colourMap[color[0]];
} }
red = rgb[0]; red = bgr[2];
green = rgb[1]; green = bgr[1];
blue = rgb[2]; blue = bgr[0];
for (i = 0; i < (width * height * 4); i+=4) { for (i = 0; i < (width * height * 4); i+=4) {
data[i ] = red; data[i ] = red;
data[i + 1] = green; data[i + 1] = green;
data[i + 2] = blue; data[i + 2] = blue;
data[i + 3] = 255;
} }
} else { } else {
that.fillRect(x, y, width, height, color); that.fillRect(x, y, width, height, color);
} }
return img;
}; };
that.setSubTile = function(img, x, y, w, h, color) { // Update sub-rectangle of the current tile
var data, p, rgb, red, green, blue, width, j, i, xend, yend; that.subTile = function(x, y, w, h, color) {
var data, p, bgr, red, green, blue, width, j, i, xend, yend;
if (conf.prefer_js) { if (conf.prefer_js) {
data = img.data; data = tile.data;
width = img.width; width = tile.width;
if (conf.true_color) { if (conf.true_color) {
rgb = color; bgr = color;
} else { } else {
rgb = conf.colourMap[color[0]]; bgr = conf.colourMap[color[0]];
} }
red = rgb[0]; red = bgr[2];
green = rgb[1]; green = bgr[1];
blue = rgb[2]; blue = bgr[0];
xend = x + w; xend = x + w;
yend = y + h; yend = y + h;
for (j = y; j < yend; j += 1) { for (j = y; j < yend; j += 1) {
@@ -355,96 +486,154 @@ that.setSubTile = function(img, x, y, w, h, color) {
data[p ] = red; data[p ] = red;
data[p + 1] = green; data[p + 1] = green;
data[p + 2] = blue; data[p + 2] = blue;
data[p + 3] = 255;
} }
} }
} else { } else {
that.fillRect(img.x + x, img.y + y, w, h, color); that.fillRect(tile_x + x, tile_y + y, w, h, color);
} }
}; };
that.putTile = function(img) { // Draw the current tile to the screen
that.finishTile = function() {
if (conf.prefer_js) { if (conf.prefer_js) {
c_rgbxImage(img.x, img.y, img.width, img.height, img.data, 0); c_ctx.putImageData(tile, tile_x - viewport.x, tile_y - viewport.y);
} }
// else: No-op, under gecko already done by setSubTile // else: No-op, if not prefer_js then already done by setSubTile
}; };
imageDataGet = function(width, height) { rgbImageData = function(x, y, width, height, arr, offset) {
return c_ctx.getImageData(0, 0, width, height); var img, i, j, data, v = viewport;
}; /*
imageDataCreate = function(width, height) { if ((x - v.x >= v.w) || (y - v.y >= v.h) ||
return c_ctx.createImageData(width, height); (x - v.x + width < 0) || (y - v.y + height < 0)) {
}; // Skipping because outside of viewport
return;
rgbxImageData = function(x, y, width, height, arr, offset) { }
var img, i, j, data; */
img = c_imageData(width, height); img = c_ctx.createImageData(width, height);
data = img.data; data = img.data;
for (i=0, j=offset; i < (width * height * 4); i=i+4, j=j+4) { for (i=0, j=offset; i < (width * height * 4); i=i+4, j=j+3) {
data[i ] = arr[j ]; data[i ] = arr[j ];
data[i + 1] = arr[j + 1]; data[i + 1] = arr[j + 1];
data[i + 2] = arr[j + 2]; data[i + 2] = arr[j + 2];
data[i + 3] = 255; // Set Alpha data[i + 3] = 255; // Set Alpha
} }
c_ctx.putImageData(img, x, y); c_ctx.putImageData(img, x - v.x, y - v.y);
}; };
// really slow fallback if we don't have imageData bgrxImageData = function(x, y, width, height, arr, offset) {
rgbxImageFill = function(x, y, width, height, arr, offset) { var img, i, j, data, v = viewport;
var i, j, sx = 0, sy = 0; /*
for (i=0, j=offset; i < (width * height); i+=1, j+=4) { if ((x - v.x >= v.w) || (y - v.y >= v.h) ||
that.fillRect(x+sx, y+sy, 1, 1, [arr[j], arr[j+1], arr[j+2]]); (x - v.x + width < 0) || (y - v.y + height < 0)) {
sx += 1; // Skipping because outside of viewport
if ((sx % width) === 0) { return;
sx = 0;
sy += 1;
}
} }
*/
img = c_ctx.createImageData(width, height);
data = img.data;
for (i=0, j=offset; i < (width * height * 4); i=i+4, j=j+4) {
data[i ] = arr[j + 2];
data[i + 1] = arr[j + 1];
data[i + 2] = arr[j ];
data[i + 3] = 255; // Set Alpha
}
c_ctx.putImageData(img, x - v.x, y - v.y);
}; };
cmapImageData = function(x, y, width, height, arr, offset) { cmapImageData = function(x, y, width, height, arr, offset) {
var img, i, j, data, rgb, cmap; var img, i, j, data, bgr, cmap;
img = c_imageData(width, height); img = c_ctx.createImageData(width, height);
data = img.data; data = img.data;
cmap = conf.colourMap; cmap = conf.colourMap;
for (i=0, j=offset; i < (width * height * 4); i+=4, j+=1) { for (i=0, j=offset; i < (width * height * 4); i+=4, j+=1) {
rgb = cmap[arr[j]]; bgr = cmap[arr[j]];
data[i ] = rgb[0]; data[i ] = bgr[2];
data[i + 1] = rgb[1]; data[i + 1] = bgr[1];
data[i + 2] = rgb[2]; data[i + 2] = bgr[0];
data[i + 3] = 255; // Set Alpha data[i + 3] = 255; // Set Alpha
} }
c_ctx.putImageData(img, x, y); c_ctx.putImageData(img, x - viewport.x, y - viewport.y);
}; };
cmapImageFill = function(x, y, width, height, arr, offset) {
var i, j, sx = 0, sy = 0, cmap;
cmap = conf.colourMap;
for (i=0, j=offset; i < (width * height); i+=1, j+=1) {
that.fillRect(x+sx, y+sy, 1, 1, [arr[j]]);
sx += 1;
if ((sx % width) === 0) {
sx = 0;
sy += 1;
}
}
};
that.blitImage = function(x, y, width, height, arr, offset) { that.blitImage = function(x, y, width, height, arr, offset) {
if (conf.true_color) { if (conf.true_color) {
c_rgbxImage(x, y, width, height, arr, offset); bgrxImageData(x, y, width, height, arr, offset);
} else { } else {
c_cmapImage(x, y, width, height, arr, offset); cmapImageData(x, y, width, height, arr, offset);
}
};
that.blitRgbImage = function(x, y, width, height, arr, offset) {
if (conf.true_color) {
rgbImageData(x, y, width, height, arr, offset);
} else {
// prolly wrong...
cmapImageData(x, y, width, height, arr, offset);
} }
}; };
that.blitStringImage = function(str, x, y) { that.blitStringImage = function(str, x, y) {
var img = new Image(); var img = new Image();
img.onload = function () { c_ctx.drawImage(img, x, y); }; img.onload = function () {
c_ctx.drawImage(img, x - viewport.x, y - viewport.y);
};
img.src = str; img.src = str;
}; };
// Wrap ctx.drawImage but relative to viewport
that.drawImage = function(img, x, y) {
c_ctx.drawImage(img, x - viewport.x, y - viewport.y);
};
that.renderQ_push = function(action) {
renderQ.push(action);
if (renderQ.length === 1) {
// If this can be rendered immediately it will be, otherwise
// the scanner will start polling the queue (every
// requestAnimationFrame interval)
scan_renderQ();
}
};
scan_renderQ = function() {
var a, ready = true;
while (ready && renderQ.length > 0) {
a = renderQ[0];
switch (a.type) {
case 'copy':
that.copyImage(a.old_x, a.old_y, a.x, a.y, a.width, a.height);
break;
case 'fill':
that.fillRect(a.x, a.y, a.width, a.height, a.color);
break;
case 'blit':
that.blitImage(a.x, a.y, a.width, a.height, a.data, 0);
break;
case 'blitRgb':
that.blitRgbImage(a.x, a.y, a.width, a.height, a.data, 0);
break;
case 'img':
if (a.img.complete) {
that.drawImage(a.img, a.x, a.y);
} else {
// We need to wait for this image to 'load'
// to keep things in-order
ready = false;
}
break;
}
if (ready) {
a = renderQ.shift();
}
}
if (renderQ.length > 0) {
requestAnimFrame(scan_renderQ);
}
};
that.changeCursor = function(pixels, mask, hotx, hoty, w, h) { that.changeCursor = function(pixels, mask, hotx, hoty, w, h) {
if (conf.cursor_uri === false) { if (conf.cursor_uri === false) {
Util.Warn("changeCursor called but no cursor data URI support"); Util.Warn("changeCursor called but no cursor data URI support");
+63 -17
View File
@@ -412,6 +412,26 @@ function onKeyUp(e) {
return false; return false;
} }
function allKeysUp() {
Util.Debug(">> Keyboard.allKeysUp");
if (keyDownList.length > 0) {
Util.Info("Releasing pressed/down keys");
}
var i, keysym, fevt = null;
for (i = keyDownList.length-1; i >= 0; i--) {
fevt = keyDownList.splice(i, 1)[0];
keysym = fevt.keysym;
if (conf.onKeyPress && (keysym > 0)) {
Util.Debug("allKeysUp, keysym: " + keysym +
" (keyCode: " + fevt.keyCode +
", which: " + fevt.which + ")");
conf.onKeyPress(keysym, 0, fevt);
}
}
Util.Debug("<< Keyboard.allKeysUp");
return;
}
// //
// Public API interface functions // Public API interface functions
// //
@@ -424,6 +444,9 @@ that.grab = function() {
Util.addEvent(c, 'keyup', onKeyUp); Util.addEvent(c, 'keyup', onKeyUp);
Util.addEvent(c, 'keypress', onKeyPress); Util.addEvent(c, 'keypress', onKeyPress);
// Release (key up) if window loses focus
Util.addEvent(window, 'blur', allKeysUp);
//Util.Debug("<< Keyboard.grab"); //Util.Debug("<< Keyboard.grab");
}; };
@@ -434,6 +457,10 @@ that.ungrab = function() {
Util.removeEvent(c, 'keydown', onKeyDown); Util.removeEvent(c, 'keydown', onKeyDown);
Util.removeEvent(c, 'keyup', onKeyUp); Util.removeEvent(c, 'keyup', onKeyUp);
Util.removeEvent(c, 'keypress', onKeyPress); Util.removeEvent(c, 'keypress', onKeyPress);
Util.removeEvent(window, 'blur', allKeysUp);
// Release (key up) all keys that are in a down state
allKeysUp();
//Util.Debug(">> Keyboard.ungrab"); //Util.Debug(">> Keyboard.ungrab");
}; };
@@ -460,7 +487,8 @@ Util.conf_defaults(conf, that, defaults, [
['scale', 'rw', 'float', 1.0, 'Viewport scale factor 0.0 - 1.0'], ['scale', 'rw', 'float', 1.0, 'Viewport scale factor 0.0 - 1.0'],
['onMouseButton', 'rw', 'func', null, 'Handler for mouse button click/release'], ['onMouseButton', 'rw', 'func', null, 'Handler for mouse button click/release'],
['onMouseMove', 'rw', 'func', null, 'Handler for mouse movement'] ['onMouseMove', 'rw', 'func', null, 'Handler for mouse movement'],
['touchButton', 'rw', 'int', 1, 'Button mask (1, 2, 4) for touch devices (0 means ignore clicks)']
]); ]);
@@ -475,7 +503,11 @@ function onMouseButton(e, down) {
} }
evt = (e ? e : window.event); evt = (e ? e : window.event);
pos = Util.getEventPosition(e, conf.target, conf.scale); pos = Util.getEventPosition(e, conf.target, conf.scale);
if (evt.which) { if (e.touches || e.changedTouches) {
// Touch device
bmask = conf.touchButton;
// If bmask is set
} else if (evt.which) {
/* everything except IE */ /* everything except IE */
bmask = 1 << evt.button; bmask = 1 << evt.button;
} else { } else {
@@ -486,7 +518,7 @@ function onMouseButton(e, down) {
} }
//Util.Debug("mouse " + pos.x + "," + pos.y + " down: " + down + //Util.Debug("mouse " + pos.x + "," + pos.y + " down: " + down +
// " bmask: " + bmask + "(evt.button: " + evt.button + ")"); // " bmask: " + bmask + "(evt.button: " + evt.button + ")");
if (conf.onMouseButton) { if (bmask > 0 && conf.onMouseButton) {
Util.Debug("onMouseButton " + (down ? "down" : "up") + Util.Debug("onMouseButton " + (down ? "down" : "up") +
", x: " + pos.x + ", y: " + pos.y + ", bmask: " + bmask); ", x: " + pos.x + ", y: " + pos.y + ", bmask: " + bmask);
conf.onMouseButton(pos.x, pos.y, down, bmask); conf.onMouseButton(pos.x, pos.y, down, bmask);
@@ -536,6 +568,8 @@ function onMouseMove(e) {
if (conf.onMouseMove) { if (conf.onMouseMove) {
conf.onMouseMove(pos.x, pos.y); conf.onMouseMove(pos.x, pos.y);
} }
Util.stopEvent(e);
return false;
} }
function onMouseDisable(e) { function onMouseDisable(e) {
@@ -565,11 +599,17 @@ that.grab = function() {
//Util.Debug(">> Mouse.grab"); //Util.Debug(">> Mouse.grab");
var c = conf.target; var c = conf.target;
Util.addEvent(c, 'mousedown', onMouseDown); if ('ontouchstart' in document.documentElement) {
Util.addEvent(c, 'mouseup', onMouseUp); Util.addEvent(c, 'touchstart', onMouseDown);
Util.addEvent(c, 'mousemove', onMouseMove); Util.addEvent(c, 'touchend', onMouseUp);
Util.addEvent(c, (Util.Engine.gecko) ? 'DOMMouseScroll' : 'mousewheel', Util.addEvent(c, 'touchmove', onMouseMove);
onMouseWheel); } else {
Util.addEvent(c, 'mousedown', onMouseDown);
Util.addEvent(c, 'mouseup', onMouseUp);
Util.addEvent(c, 'mousemove', onMouseMove);
Util.addEvent(c, (Util.Engine.gecko) ? 'DOMMouseScroll' : 'mousewheel',
onMouseWheel);
}
/* Work around right and middle click browser behaviors */ /* Work around right and middle click browser behaviors */
Util.addEvent(document, 'click', onMouseDisable); Util.addEvent(document, 'click', onMouseDisable);
@@ -582,11 +622,17 @@ that.ungrab = function() {
//Util.Debug(">> Mouse.ungrab"); //Util.Debug(">> Mouse.ungrab");
var c = conf.target; var c = conf.target;
Util.removeEvent(c, 'mousedown', onMouseDown); if ('ontouchstart' in document.documentElement) {
Util.removeEvent(c, 'mouseup', onMouseUp); Util.removeEvent(c, 'touchstart', onMouseDown);
Util.removeEvent(c, 'mousemove', onMouseMove); Util.removeEvent(c, 'touchend', onMouseUp);
Util.removeEvent(c, (Util.Engine.gecko) ? 'DOMMouseScroll' : 'mousewheel', Util.removeEvent(c, 'touchmove', onMouseMove);
onMouseWheel); } else {
Util.removeEvent(c, 'mousedown', onMouseDown);
Util.removeEvent(c, 'mouseup', onMouseUp);
Util.removeEvent(c, 'mousemove', onMouseMove);
Util.removeEvent(c, (Util.Engine.gecko) ? 'DOMMouseScroll' : 'mousewheel',
onMouseWheel);
}
/* Work around right and middle click browser behaviors */ /* Work around right and middle click browser behaviors */
Util.removeEvent(document, 'click', onMouseDisable); Util.removeEvent(document, 'click', onMouseDisable);
@@ -1121,14 +1167,14 @@ unicodeTable = {
0x21D4 : 0x08cd, 0x21D4 : 0x08cd,
0x21D2 : 0x08ce, 0x21D2 : 0x08ce,
0x2261 : 0x08cf, 0x2261 : 0x08cf,
0x221A : 0x08d6, //0x221A : 0x08d6,
0x2282 : 0x08da, 0x2282 : 0x08da,
0x2283 : 0x08db, 0x2283 : 0x08db,
0x2229 : 0x08dc, 0x2229 : 0x08dc,
0x222A : 0x08dd, 0x222A : 0x08dd,
0x2227 : 0x08de, 0x2227 : 0x08de,
0x2228 : 0x08df, 0x2228 : 0x08df,
0x2202 : 0x08ef, //0x2202 : 0x08ef,
0x0192 : 0x08f6, 0x0192 : 0x08f6,
0x2190 : 0x08fb, 0x2190 : 0x08fb,
0x2191 : 0x08fc, 0x2191 : 0x08fc,
@@ -1463,7 +1509,7 @@ unicodeTable = {
0x012D : 0x100012d, 0x012D : 0x100012d,
0x01B6 : 0x10001b6, 0x01B6 : 0x10001b6,
0x01E7 : 0x10001e7, 0x01E7 : 0x10001e7,
0x01D2 : 0x10001d2, //0x01D2 : 0x10001d2,
0x0275 : 0x1000275, 0x0275 : 0x1000275,
0x018F : 0x100018f, 0x018F : 0x100018f,
0x0259 : 0x1000259, 0x0259 : 0x1000259,
@@ -1833,7 +1879,7 @@ unicodeTable = {
0x28e0 : 0x10028e0, 0x28e0 : 0x10028e0,
0x28e1 : 0x10028e1, 0x28e1 : 0x10028e1,
0x28e2 : 0x10028e2, 0x28e2 : 0x10028e2,
0x28e3 : 0x10028e3, 0x28e3 : 0x10028e3,
0x28e4 : 0x10028e4, 0x28e4 : 0x10028e4,
0x28e5 : 0x10028e5, 0x28e5 : 0x10028e5,
0x28e6 : 0x10028e6, 0x28e6 : 0x10028e6,
+668
View File
@@ -0,0 +1,668 @@
/*
* 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 */
this.read_bits = function(d, num, base)
{
if (!num)
return base;
var val = 0;
while (d.bitcount < 24) {
d.tag = d.tag | (d.source[d.sourceIndex++] & 0xff) << d.bitcount;
d.bitcount += 8;
}
val = d.tag & (0xffff >> (16 - num));
d.tag >>= num;
d.bitcount -= num;
return val + base;
}
/* 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 };
}
};
-97
View File
@@ -1,97 +0,0 @@
#VNC_controls {
overflow: hidden;
}
#VNC_controls ul {
list-style: none;
margin: 0px;
padding: 0px;
}
#VNC_controls li {
float: left;
margin-right: 15px;
}
#VNC_host {
width: 100px;
}
#VNC_port {
width: 50px;
}
#VNC_password {
width: 80px;
}
#VNC_encrypt {
}
#VNC_connectTimeout {
width: 30px;
}
#VNC_connect_button {
width: 110px;
}
#VNC_status_bar td {
margin-top: 15px;
padding: 0px;
margin: 0px;
}
#VNC_status_bar div {
font-size: 12px;
margin: 0px;
padding: 0px;
}
.VNC_status_button {
font-size: 10px;
margin: 0px;
padding: 0px;
}
#VNC_status {
text-align: center;
}
#VNC_settings_menu {
display: none;
position: absolute;
width: 12em;
border: 1px solid #888;
background-color: #f0f2f6;
padding: 5px; margin: 3px;
z-index: 100; opacity: 1;
text-align: left; white-space: normal;
}
#VNC_settings_menu ul {
list-style: none;
margin: 0px;
padding: 0px;
}
.VNC_buttons_right {
text-align: right;
}
.VNC_buttons_left {
text-align: left;
}
.VNC_status_normal {
background: #eee;
}
.VNC_status_error {
background: #f44;
}
.VNC_status_warn {
background: #ff4;
}
/* Do not set width/height for VNC_screen or VNC_canvas or incorrect
* scaling will occur. Canvas resizes to remote VNC settings */
#VNC_screen {
text-align: center;
display: table;
}
#VNC_canvas {
background: #eee;
}
#VNC_clipboard_clear_button {
}
#VNC_clipboard_text {
font-size: 9px;
}
+1 -1
View File
@@ -1,6 +1,6 @@
/* /*
* noVNC: HTML5 VNC client * noVNC: HTML5 VNC client
* Copyright (C) 2011 Joel Martin * Copyright (C) 2012 Joel Martin
* Licensed under LGPL-3 (see LICENSE.LGPL-3) * Licensed under LGPL-3 (see LICENSE.LGPL-3)
*/ */
+406 -120
View File
@@ -1,13 +1,16 @@
/* /*
* noVNC: HTML5 VNC client * noVNC: HTML5 VNC client
* Copyright (C) 2011 Joel Martin * Copyright (C) 2012 Joel Martin
* Licensed under LGPL-3 (see LICENSE.txt) * Licensed under LGPL-3 (see LICENSE.txt)
* *
* See README.md for usage and integration instructions. * See README.md for usage and integration instructions.
*
* TIGHT decoder portion:
* (c) 2012 Michael Tinglof, Joe Balaz, Les Piech (Mercuri.ca)
*/ */
/*jslint white: false, browser: true, bitwise: false, plusplus: false */ /*jslint white: false, browser: true, bitwise: false, plusplus: false */
/*global window, Util, Display, Keyboard, Mouse, Websock, Websock_native, Base64, DES, noVNC_logo */ /*global window, Util, Display, Keyboard, Mouse, Websock, Websock_native, Base64, DES */
function RFB(defaults) { function RFB(defaults) {
@@ -20,10 +23,10 @@ var that = {}, // Public API methods
init_vars, updateState, fail, handle_message, init_vars, updateState, fail, handle_message,
init_msg, normal_msg, framebufferUpdate, print_stats, init_msg, normal_msg, framebufferUpdate, print_stats,
pixelFormat, clientEncodings, fbUpdateRequest, pixelFormat, clientEncodings, fbUpdateRequest, fbUpdateRequests,
keyEvent, pointerEvent, clientCutText, keyEvent, pointerEvent, clientCutText,
extract_data_uri, scan_tight_imgQ, getTightCLength, extract_data_uri,
keyPress, mouseButton, mouseMove, keyPress, mouseButton, mouseMove,
checkEvents, // Overridable for testing checkEvents, // Overridable for testing
@@ -35,7 +38,7 @@ var that = {}, // Public API methods
rfb_host = '', rfb_host = '',
rfb_port = 5900, rfb_port = 5900,
rfb_password = '', rfb_password = '',
rfb_uri = '', rfb_path = '',
rfb_state = 'disconnected', rfb_state = 'disconnected',
rfb_version = 0, rfb_version = 0,
@@ -46,6 +49,7 @@ var that = {}, // Public API methods
// In preference order // In preference order
encodings = [ encodings = [
['COPYRECT', 0x01 ], ['COPYRECT', 0x01 ],
['TIGHT', 0x07 ],
['TIGHT_PNG', -260 ], ['TIGHT_PNG', -260 ],
['HEXTILE', 0x05 ], ['HEXTILE', 0x05 ],
['RRE', 0x02 ], ['RRE', 0x02 ],
@@ -54,10 +58,12 @@ var that = {}, // Public API methods
['Cursor', -239 ], ['Cursor', -239 ],
// Psuedo-encoding settings // Psuedo-encoding settings
['JPEG_quality_lo', -32 ], //['JPEG_quality_lo', -32 ],
['JPEG_quality_med', -26 ],
//['JPEG_quality_hi', -23 ], //['JPEG_quality_hi', -23 ],
['compress_lo', -255 ] //['compress_lo', -255 ],
//['compress_hi', -247 ] ['compress_hi', -247 ],
['last_rect', -224 ]
], ],
encHandlers = {}, encHandlers = {},
@@ -87,7 +93,7 @@ var that = {}, // Public API methods
encoding : 0, encoding : 0,
subencoding : -1, subencoding : -1,
background : null, background : null,
imgQ : [] // TIGHT_PNG image queue zlibs : [] // TIGHT zlib streams
}, },
fb_Bpp = 4, fb_Bpp = 4,
@@ -96,7 +102,6 @@ var that = {}, // Public API methods
fb_height = 0, fb_height = 0,
fb_name = "", fb_name = "",
scan_imgQ_rate = 40, // 25 times per second or so
last_req_time = 0, last_req_time = 0,
rre_chunk_sz = 100, rre_chunk_sz = 100,
@@ -109,7 +114,8 @@ var that = {}, // Public API methods
fbu_rt_start : 0, fbu_rt_start : 0,
fbu_rt_total : 0, fbu_rt_total : 0,
fbu_rt_cnt : 0 fbu_rt_cnt : 0,
pixels : 0
}, },
test_mode = false, test_mode = false,
@@ -118,7 +124,9 @@ var that = {}, // Public API methods
/* Mouse state */ /* Mouse state */
mouse_buttonMask = 0, mouse_buttonMask = 0,
mouse_arr = []; mouse_arr = [],
viewportDragging = false,
viewportDragPos = {};
// Configuration attributes // Configuration attributes
Util.conf_defaults(conf, that, defaults, [ Util.conf_defaults(conf, that, defaults, [
@@ -129,10 +137,16 @@ Util.conf_defaults(conf, that, defaults, [
['true_color', 'rw', 'bool', true, 'Request true color pixel data'], ['true_color', 'rw', 'bool', true, 'Request true color pixel data'],
['local_cursor', 'rw', 'bool', false, 'Request locally rendered cursor'], ['local_cursor', 'rw', 'bool', false, 'Request locally rendered cursor'],
['shared', 'rw', 'bool', true, 'Request shared mode'], ['shared', 'rw', 'bool', true, 'Request shared mode'],
['view_only', 'rw', 'bool', false, 'Disable client mouse/keyboard'],
['connectTimeout', 'rw', 'int', def_con_timeout, 'Time (s) to wait for connection'], ['connectTimeout', 'rw', 'int', def_con_timeout, 'Time (s) to wait for connection'],
['disconnectTimeout', 'rw', 'int', 3, 'Time (s) to wait for disconnection'], ['disconnectTimeout', 'rw', 'int', 3, 'Time (s) to wait for disconnection'],
// UltraVNC repeater ID to connect to
['repeaterID', 'rw', 'str', '', 'RepeaterID to connect to'],
['viewportDrag', 'rw', 'bool', false, 'Move the viewport on mouse drags'],
['check_rate', 'rw', 'int', 217, 'Timing (ms) of send/receive check'], ['check_rate', 'rw', 'int', 217, 'Timing (ms) of send/receive check'],
['fbu_req_rate', 'rw', 'int', 1413, 'Timing (ms) of frameBufferUpdate requests'], ['fbu_req_rate', 'rw', 'int', 1413, 'Timing (ms) of frameBufferUpdate requests'],
@@ -211,10 +225,6 @@ function constructor() {
rmode = display.get_render_mode(); rmode = display.get_render_mode();
if (typeof noVNC_logo !== 'undefined') {
display.set_logo(noVNC_logo);
}
ws = new Websock(); ws = new Websock();
ws.on('message', handle_message); ws.on('message', handle_message);
ws.on('open', function() { ws.on('open', function() {
@@ -224,19 +234,29 @@ function constructor() {
fail("Got unexpected WebSockets connection"); fail("Got unexpected WebSockets connection");
} }
}); });
ws.on('close', function() { ws.on('close', function(e) {
Util.Warn("WebSocket on-close event");
var msg = "";
if (e.code) {
msg = " (code: " + e.code;
if (e.reason) {
msg += ", reason: " + e.reason;
}
msg += ")";
}
if (rfb_state === 'disconnect') { if (rfb_state === 'disconnect') {
updateState('disconnected', 'VNC disconnected'); updateState('disconnected', 'VNC disconnected' + msg);
} else if (rfb_state === 'ProtocolVersion') { } else if (rfb_state === 'ProtocolVersion') {
fail('Failed to connect to server'); fail('Failed to connect to server' + msg);
} else if (rfb_state in {'failed':1, 'disconnected':1}) { } else if (rfb_state in {'failed':1, 'disconnected':1}) {
Util.Error("Received onclose while disconnected"); Util.Error("Received onclose while disconnected" + msg);
} else { } else {
fail('Server disconnected'); fail('Server disconnected' + msg);
} }
}); });
ws.on('error', function(e) { ws.on('error', function(e) {
fail("WebSock error: " + e); Util.Warn("WebSocket on-error event");
//fail("WebSock reported an error");
}); });
@@ -266,14 +286,18 @@ function constructor() {
function connect() { function connect() {
Util.Debug(">> RFB.connect"); Util.Debug(">> RFB.connect");
var uri;
var uri = "";
if (conf.encrypt) { if (typeof UsingSocketIO !== "undefined") {
uri = "wss://"; uri = "http://" + rfb_host + ":" + rfb_port + "/" + rfb_path;
} else { } else {
uri = "ws://"; if (conf.encrypt) {
uri = "wss://";
} else {
uri = "ws://";
}
uri += rfb_host + ":" + rfb_port + "/" + rfb_path;
} }
uri += rfb_host + ":" + rfb_port + "/" + rfb_uri;
Util.Info("connecting to " + uri); Util.Info("connecting to " + uri);
ws.open(uri); ws.open(uri);
@@ -291,7 +315,7 @@ init_vars = function() {
FBU.subrects = 0; // RRE and HEXTILE FBU.subrects = 0; // RRE and HEXTILE
FBU.lines = 0; // RAW FBU.lines = 0; // RAW
FBU.tiles = 0; // HEXTILE FBU.tiles = 0; // HEXTILE
FBU.imgQ = []; // TIGHT_PNG image queue FBU.zlibs = []; // TIGHT zlib encoders
mouse_buttonMask = 0; mouse_buttonMask = 0;
mouse_arr = []; mouse_arr = [];
@@ -299,6 +323,12 @@ init_vars = function() {
for (i=0; i < encodings.length; i+=1) { for (i=0; i < encodings.length; i+=1) {
encStats[encodings[i][1]][0] = 0; encStats[encodings[i][1]][0] = 0;
} }
for (i=0; i < 4; i++) {
//FBU.zlibs[i] = new InflateStream();
FBU.zlibs[i] = new TINF();
FBU.zlibs[i].init();
}
}; };
// Print statistics // Print statistics
@@ -396,16 +426,16 @@ updateState = function(state, statusMsg) {
func = Util.Warn; func = Util.Warn;
} }
cmsg = typeof(statusMsg) !== 'undefined' ? (" Msg: " + statusMsg) : "";
func("New state '" + state + "', was '" + oldstate + "'." + cmsg);
if ((oldstate === 'failed') && (state === 'disconnected')) { if ((oldstate === 'failed') && (state === 'disconnected')) {
// Do disconnect action, but stay in failed state. // Do disconnect action, but stay in failed state
rfb_state = 'failed'; rfb_state = 'failed';
} else { } else {
rfb_state = state; rfb_state = state;
} }
cmsg = typeof(statusMsg) !== 'undefined' ? (" Msg: " + statusMsg) : "";
func("New state '" + rfb_state + "', was '" + oldstate + "'." + cmsg);
if (connTimer && (rfb_state !== 'connect')) { if (connTimer && (rfb_state !== 'connect')) {
Util.Debug("Clearing connect timer"); Util.Debug("Clearing connect timer");
clearInterval(connTimer); clearInterval(connTimer);
@@ -535,10 +565,10 @@ function genDES(password, challenge) {
function flushClient() { function flushClient() {
if (mouse_arr.length > 0) { if (mouse_arr.length > 0) {
//send(mouse_arr.concat(fbUpdateRequest(1))); //send(mouse_arr.concat(fbUpdateRequests()));
ws.send(mouse_arr); ws.send(mouse_arr);
setTimeout(function() { setTimeout(function() {
ws.send(fbUpdateRequest(1)); ws.send(fbUpdateRequests());
}, 50); }, 50);
mouse_arr = []; mouse_arr = [];
@@ -551,12 +581,12 @@ function flushClient() {
// overridable for testing // overridable for testing
checkEvents = function() { checkEvents = function() {
var now; var now;
if (rfb_state === 'normal') { if (rfb_state === 'normal' && !viewportDragging) {
if (! flushClient()) { if (! flushClient()) {
now = new Date().getTime(); now = new Date().getTime();
if (now > last_req_time + conf.fbu_req_rate) { if (now > last_req_time + conf.fbu_req_rate) {
last_req_time = now; last_req_time = now;
ws.send(fbUpdateRequest(1)); ws.send(fbUpdateRequests());
} }
} }
} }
@@ -565,8 +595,11 @@ checkEvents = function() {
keyPress = function(keysym, down) { keyPress = function(keysym, down) {
var arr; var arr;
if (conf.view_only) { return; } // View only, skip keyboard events
arr = keyEvent(keysym, down); arr = keyEvent(keysym, down);
arr = arr.concat(fbUpdateRequest(1)); arr = arr.concat(fbUpdateRequests());
ws.send(arr); ws.send(arr);
}; };
@@ -576,13 +609,48 @@ mouseButton = function(x, y, down, bmask) {
} else { } else {
mouse_buttonMask ^= bmask; mouse_buttonMask ^= bmask;
} }
mouse_arr = mouse_arr.concat( pointerEvent(x, y) );
if (conf.viewportDrag) {
if (down && !viewportDragging) {
viewportDragging = true;
viewportDragPos = {'x': x, 'y': y};
// Skip sending mouse events
return;
} else {
viewportDragging = false;
ws.send(fbUpdateRequests()); // Force immediate redraw
}
}
if (conf.view_only) { return; } // View only, skip mouse events
mouse_arr = mouse_arr.concat(
pointerEvent(display.absX(x), display.absY(y)) );
flushClient(); flushClient();
}; };
mouseMove = function(x, y) { mouseMove = function(x, y) {
//Util.Debug('>> mouseMove ' + x + "," + y); //Util.Debug('>> mouseMove ' + x + "," + y);
mouse_arr = mouse_arr.concat( pointerEvent(x, y) ); var deltaX, deltaY;
if (viewportDragging) {
//deltaX = x - viewportDragPos.x; // drag viewport
deltaX = viewportDragPos.x - x; // drag frame buffer
//deltaY = y - viewportDragPos.y; // drag viewport
deltaY = viewportDragPos.y - y; // drag frame buffer
viewportDragPos = {'x': x, 'y': y};
display.viewportChange(deltaX, deltaY);
// Skip sending mouse events
return;
}
if (conf.view_only) { return; } // View only, skip mouse events
mouse_arr = mouse_arr.concat(
pointerEvent(display.absX(x), display.absY(y)) );
}; };
@@ -594,10 +662,10 @@ mouseMove = function(x, y) {
init_msg = function() { init_msg = function() {
//Util.Debug(">> init_msg [rfb_state '" + rfb_state + "']"); //Util.Debug(">> init_msg [rfb_state '" + rfb_state + "']");
var strlen, reason, length, sversion, cversion, var strlen, reason, length, sversion, cversion, repeaterID,
i, types, num_types, challenge, response, bpp, depth, i, types, num_types, challenge, response, bpp, depth,
big_endian, red_max, green_max, blue_max, red_shift, big_endian, red_max, green_max, blue_max, red_shift,
green_shift, blue_shift, true_color, name_length; green_shift, blue_shift, true_color, name_length, is_repeater;
//Util.Debug("ws.rQ (" + ws.rQlen() + ") " + ws.rQslice(0)); //Util.Debug("ws.rQ (" + ws.rQlen() + ") " + ws.rQslice(0));
switch (rfb_state) { switch (rfb_state) {
@@ -608,14 +676,27 @@ init_msg = function() {
} }
sversion = ws.rQshiftStr(12).substr(4,7); sversion = ws.rQshiftStr(12).substr(4,7);
Util.Info("Server ProtocolVersion: " + sversion); Util.Info("Server ProtocolVersion: " + sversion);
is_repeater = 0;
switch (sversion) { switch (sversion) {
case "000.000": is_repeater = 1; break; // UltraVNC repeater
case "003.003": rfb_version = 3.3; break; case "003.003": rfb_version = 3.3; break;
case "003.006": rfb_version = 3.3; break; // UltraVNC case "003.006": rfb_version = 3.3; break; // UltraVNC
case "003.889": rfb_version = 3.3; break; // Apple Remote Desktop
case "003.007": rfb_version = 3.7; break; case "003.007": rfb_version = 3.7; break;
case "003.008": rfb_version = 3.8; break; case "003.008": rfb_version = 3.8; break;
case "004.000": rfb_version = 3.8; break; // Intel AMT KVM
case "004.001": rfb_version = 3.8; break; // RealVNC 4.6
default: default:
return fail("Invalid server version " + sversion); return fail("Invalid server version " + sversion);
} }
if (is_repeater) {
repeaterID = conf.repeaterID;
while (repeaterID.length < 250) {
repeaterID += "\0";
}
ws.send_string(repeaterID);
break;
}
if (rfb_version > rfb_max_version) { if (rfb_version > rfb_max_version) {
rfb_version = rfb_max_version; rfb_version = rfb_max_version;
} }
@@ -776,9 +857,25 @@ init_msg = function() {
", green_shift: " + green_shift + ", green_shift: " + green_shift +
", blue_shift: " + blue_shift); ", blue_shift: " + blue_shift);
if (big_endian !== 0) {
Util.Warn("Server native endian is not little endian");
}
if (red_shift !== 16) {
Util.Warn("Server native red-shift is not 16");
}
if (blue_shift !== 0) {
Util.Warn("Server native blue-shift is not 0");
}
/* Connection name/title */ /* Connection name/title */
name_length = ws.rQshift32(); name_length = ws.rQshift32();
fb_name = ws.rQshiftStr(name_length); fb_name = ws.rQshiftStr(name_length);
if (conf.true_color && fb_name === "Intel(r) AMT KVM")
{
Util.Warn("Intel AMT KVM only support 8/16 bit depths. Disabling true color");
conf.true_color = false;
}
display.set_true_color(conf.true_color); display.set_true_color(conf.true_color);
display.resize(fb_width, fb_height); display.resize(fb_width, fb_height);
@@ -795,13 +892,13 @@ init_msg = function() {
response = pixelFormat(); response = pixelFormat();
response = response.concat(clientEncodings()); response = response.concat(clientEncodings());
response = response.concat(fbUpdateRequest(0)); response = response.concat(fbUpdateRequests());
timing.fbu_rt_start = (new Date()).getTime(); timing.fbu_rt_start = (new Date()).getTime();
timing.pixels = 0;
ws.send(response); ws.send(response);
/* Start pushing/polling */ /* Start pushing/polling */
setTimeout(checkEvents, conf.check_rate); setTimeout(checkEvents, conf.check_rate);
setTimeout(scan_tight_imgQ, scan_imgQ_rate);
if (conf.encrypt) { if (conf.encrypt) {
updateState('normal', "Connected (encrypted) to: " + fb_name); updateState('normal', "Connected (encrypted) to: " + fb_name);
@@ -835,6 +932,8 @@ normal_msg = function() {
ws.rQshift8(); // Padding ws.rQshift8(); // Padding
first_colour = ws.rQshift16(); // First colour first_colour = ws.rQshift16(); // First colour
num_colours = ws.rQshift16(); num_colours = ws.rQshift16();
if (ws.rQwait("SetColourMapEntries", num_colours*6, 6)) { return false; }
for (c=0; c < num_colours; c+=1) { for (c=0; c < num_colours; c+=1) {
red = ws.rQshift16(); red = ws.rQshift16();
//Util.Debug("red before: " + red); //Util.Debug("red before: " + red);
@@ -842,9 +941,9 @@ normal_msg = function() {
//Util.Debug("red after: " + red); //Util.Debug("red after: " + red);
green = parseInt(ws.rQshift16() / 256, 10); green = parseInt(ws.rQshift16() / 256, 10);
blue = parseInt(ws.rQshift16() / 256, 10); blue = parseInt(ws.rQshift16() / 256, 10);
Util.Debug("*** colourMap: " + display.get_colourMap()); display.set_colourMap([blue, green, red], first_colour + c);
display.set_colourMap([red, green, blue], first_colour + c);
} }
Util.Debug("colourMap: " + display.get_colourMap());
Util.Info("Registered " + num_colours + " colourMap entries"); Util.Info("Registered " + num_colours + " colourMap entries");
//Util.Debug("colourMap: " + display.get_colourMap()); //Util.Debug("colourMap: " + display.get_colourMap());
break; break;
@@ -943,9 +1042,10 @@ framebufferUpdate = function() {
if (ret) { if (ret) {
encStats[FBU.encoding][0] += 1; encStats[FBU.encoding][0] += 1;
encStats[FBU.encoding][1] += 1; encStats[FBU.encoding][1] += 1;
timing.pixels += FBU.width * FBU.height;
} }
if (FBU.rects === 0) { if (timing.pixels >= (fb_width * fb_height)) {
if (((FBU.width === fb_width) && if (((FBU.width === fb_width) &&
(FBU.height === fb_height)) || (FBU.height === fb_height)) ||
(timing.fbu_rt_start > 0)) { (timing.fbu_rt_start > 0)) {
@@ -1023,9 +1123,14 @@ encHandlers.COPYRECT = function display_copy_rect() {
var old_x, old_y; var old_x, old_y;
if (ws.rQwait("COPYRECT", 4)) { return false; } if (ws.rQwait("COPYRECT", 4)) { return false; }
old_x = ws.rQshift16(); display.renderQ_push({
old_y = ws.rQshift16(); 'type': 'copy',
display.copyImage(old_x, old_y, FBU.x, FBU.y, FBU.width, FBU.height); 'old_x': ws.rQshift16(),
'old_y': ws.rQshift16(),
'x': FBU.x,
'y': FBU.y,
'width': FBU.width,
'height': FBU.height});
FBU.rects -= 1; FBU.rects -= 1;
FBU.bytes = 0; FBU.bytes = 0;
return true; return true;
@@ -1066,7 +1171,7 @@ encHandlers.RRE = function display_rre() {
encHandlers.HEXTILE = function display_hextile() { encHandlers.HEXTILE = function display_hextile() {
//Util.Debug(">> display_hextile"); //Util.Debug(">> display_hextile");
var subencoding, subrects, tile, color, cur_tile, var subencoding, subrects, color, cur_tile,
tile_x, x, w, tile_y, y, h, xy, s, sx, sy, wh, sw, sh, tile_x, x, w, tile_y, y, h, xy, s, sx, sy, wh, sw, sh,
rQ = ws.get_rQ(), rQi = ws.get_rQi(); rQ = ws.get_rQ(), rQi = ws.get_rQi();
@@ -1155,7 +1260,7 @@ encHandlers.HEXTILE = function display_hextile() {
rQi += fb_Bpp; rQi += fb_Bpp;
} }
tile = display.getTile(x, y, w, h, FBU.background); display.startTile(x, y, w, h, FBU.background);
if (FBU.subencoding & 0x08) { // AnySubrects if (FBU.subencoding & 0x08) { // AnySubrects
subrects = rQ[rQi]; subrects = rQ[rQi];
rQi += 1; rQi += 1;
@@ -1176,10 +1281,10 @@ encHandlers.HEXTILE = function display_hextile() {
sw = (wh >> 4) + 1; sw = (wh >> 4) + 1;
sh = (wh & 0x0f) + 1; sh = (wh & 0x0f) + 1;
display.setSubTile(tile, sx, sy, sw, sh, color); display.subTile(sx, sy, sw, sh, color);
} }
} }
display.putTile(tile); display.finishTile();
} }
ws.set_rQi(rQi); ws.set_rQi(rQi);
FBU.lastsubencoding = FBU.subencoding; FBU.lastsubencoding = FBU.subencoding;
@@ -1196,42 +1301,197 @@ encHandlers.HEXTILE = function display_hextile() {
}; };
encHandlers.TIGHT_PNG = function display_tight_png() { // Get 'compact length' header and data size
//Util.Debug(">> display_tight_png"); getTightCLength = function (arr) {
var ctl, cmode, clength, getCLength, color, img; var header = 1, data = 0;
//Util.Debug(" FBU.rects: " + FBU.rects); data += arr[0] & 0x7f;
//Util.Debug(" starting ws.rQslice(0,20): " + ws.rQslice(0,20) + " (" + ws.rQlen() + ")"); if (arr[0] & 0x80) {
header += 1;
data += (arr[1] & 0x7f) << 7;
if (arr[1] & 0x80) {
header += 1;
data += arr[2] << 14;
}
}
return [header, data];
};
function display_tight(isTightPNG) {
//Util.Debug(">> display_tight");
if (fb_depth === 1) {
fail("Tight protocol handler only implements true color mode");
}
var ctl, cmode, clength, color, img, data;
var filterId = -1, resetStreams = 0, streamId = -1;
var rQ = ws.get_rQ(), rQi = ws.get_rQi();
FBU.bytes = 1; // compression-control byte FBU.bytes = 1; // compression-control byte
if (ws.rQwait("TIGHT compression-control", FBU.bytes)) { return false; } if (ws.rQwait("TIGHT compression-control", FBU.bytes)) { return false; }
// Get 'compact length' header and data size var checksum = function(data) {
getCLength = function (arr) { var sum=0, i;
var header = 1, data = 0; for (i=0; i<data.length;i++) {
data += arr[0] & 0x7f; sum += data[i];
if (arr[0] & 0x80) { if (sum > 65536) sum -= 65536;
header += 1; }
data += (arr[1] & 0x7f) << 7; return sum;
if (arr[1] & 0x80) { }
header += 1;
data += arr[2] << 14; var decompress = function(data) {
for (var i=0; i<4; i++) {
if ((resetStreams >> i) & 1) {
FBU.zlibs[i].reset();
Util.Info("Reset zlib stream " + i);
} }
} }
return [header, data]; var uncompressed = FBU.zlibs[streamId].uncompress(data, 0);
}; if (uncompressed.status !== 0) {
Util.Error("Invalid data in zlib stream");
}
//Util.Warn("Decompressed " + data.length + " to " +
// uncompressed.data.length + " checksums " +
// checksum(data) + ":" + checksum(uncompressed.data));
return uncompressed.data;
}
var handlePalette = function() {
var numColors = rQ[rQi + 2] + 1;
var paletteSize = numColors * fb_depth;
FBU.bytes += paletteSize;
if (ws.rQwait("TIGHT palette " + cmode, FBU.bytes)) { return false; }
var bpp = (numColors <= 2) ? 1 : 8;
var rowSize = Math.floor((FBU.width * bpp + 7) / 8);
var raw = false;
if (rowSize * FBU.height < 12) {
raw = true;
clength = [0, rowSize * FBU.height];
} else {
clength = getTightCLength(ws.rQslice(3 + paletteSize,
3 + paletteSize + 3));
}
FBU.bytes += clength[0] + clength[1];
if (ws.rQwait("TIGHT " + cmode, FBU.bytes)) { return false; }
// Shift ctl, filter id, num colors, palette entries, and clength off
ws.rQshiftBytes(3);
var palette = ws.rQshiftBytes(paletteSize);
ws.rQshiftBytes(clength[0]);
if (raw) {
data = ws.rQshiftBytes(clength[1]);
} else {
data = decompress(ws.rQshiftBytes(clength[1]));
}
// Convert indexed (palette based) image data to RGB
// TODO: reduce number of calculations inside loop
var dest = [];
var x, y, b, w, w1, dp, sp;
if (numColors === 2) {
w = Math.floor((FBU.width + 7) / 8);
w1 = Math.floor(FBU.width / 8);
for (y = 0; y < FBU.height; y++) {
for (x = 0; x < w1; x++) {
for (b = 7; b >= 0; b--) {
dp = (y*FBU.width + x*8 + 7-b) * 3;
sp = (data[y*w + x] >> b & 1) * 3;
dest[dp ] = palette[sp ];
dest[dp+1] = palette[sp+1];
dest[dp+2] = palette[sp+2];
}
}
for (b = 7; b >= 8 - FBU.width % 8; b--) {
dp = (y*FBU.width + x*8 + 7-b) * 3;
sp = (data[y*w + x] >> b & 1) * 3;
dest[dp ] = palette[sp ];
dest[dp+1] = palette[sp+1];
dest[dp+2] = palette[sp+2];
}
}
} else {
for (y = 0; y < FBU.height; y++) {
for (x = 0; x < FBU.width; x++) {
dp = (y*FBU.width + x) * 3;
sp = data[y*FBU.width + x] * 3;
dest[dp ] = palette[sp ];
dest[dp+1] = palette[sp+1];
dest[dp+2] = palette[sp+2];
}
}
}
display.renderQ_push({
'type': 'blitRgb',
'data': dest,
'x': FBU.x,
'y': FBU.y,
'width': FBU.width,
'height': FBU.height});
return true;
}
var handleCopy = function() {
var raw = false;
var uncompressedSize = FBU.width * FBU.height * fb_depth;
if (uncompressedSize < 12) {
raw = true;
clength = [0, uncompressedSize];
} else {
clength = getTightCLength(ws.rQslice(1, 4));
}
FBU.bytes = 1 + clength[0] + clength[1];
if (ws.rQwait("TIGHT " + cmode, FBU.bytes)) { return false; }
// Shift ctl, clength off
ws.rQshiftBytes(1 + clength[0]);
if (raw) {
data = ws.rQshiftBytes(clength[1]);
} else {
data = decompress(ws.rQshiftBytes(clength[1]));
}
display.renderQ_push({
'type': 'blitRgb',
'data': data,
'x': FBU.x,
'y': FBU.y,
'width': FBU.width,
'height': FBU.height});
return true;
}
ctl = ws.rQpeek8(); ctl = ws.rQpeek8();
switch (ctl >> 4) {
case 0x08: cmode = "fill"; break; // Keep tight reset bits
case 0x09: cmode = "jpeg"; break; resetStreams = ctl & 0xF;
case 0x0A: cmode = "png"; break;
default: throw("Illegal basic compression received, ctl: " + ctl); // Figure out filter
ctl = ctl >> 4;
streamId = ctl & 0x3;
if (ctl === 0x08) cmode = "fill";
else if (ctl === 0x09) cmode = "jpeg";
else if (ctl === 0x0A) cmode = "png";
else if (ctl & 0x04) cmode = "filter";
else if (ctl < 0x04) cmode = "copy";
else return fail("Illegal tight compression received, ctl: " + ctl);
if (isTightPNG && (cmode === "filter" || cmode === "copy")) {
return fail("filter/copy received in tightPNG mode");
} }
switch (cmode) { switch (cmode) {
// fill uses fb_depth because TPIXELs drop the padding byte // fill uses fb_depth because TPIXELs drop the padding byte
case "fill": FBU.bytes += fb_depth; break; // TPIXEL case "fill": FBU.bytes += fb_depth; break; // TPIXEL
case "jpeg": FBU.bytes += 3; break; // max clength case "jpeg": FBU.bytes += 3; break; // max clength
case "png": FBU.bytes += 3; break; // max clength case "png": FBU.bytes += 3; break; // max clength
case "filter": FBU.bytes += 2; break; // filter id + num colors if palette
case "copy": break;
} }
if (ws.rQwait("TIGHT " + cmode, FBU.bytes)) { return false; } if (ws.rQwait("TIGHT " + cmode, FBU.bytes)) { return false; }
@@ -1244,42 +1504,55 @@ encHandlers.TIGHT_PNG = function display_tight_png() {
case "fill": case "fill":
ws.rQshift8(); // shift off ctl ws.rQshift8(); // shift off ctl
color = ws.rQshiftBytes(fb_depth); color = ws.rQshiftBytes(fb_depth);
FBU.imgQ.push({ display.renderQ_push({
'type': 'fill', 'type': 'fill',
'img': {'complete': true},
'x': FBU.x, 'x': FBU.x,
'y': FBU.y, 'y': FBU.y,
'width': FBU.width, 'width': FBU.width,
'height': FBU.height, 'height': FBU.height,
'color': color}); 'color': [color[2], color[1], color[0]] });
break; break;
case "jpeg":
case "png": case "png":
clength = getCLength(ws.rQslice(1, 4)); case "jpeg":
clength = getTightCLength(ws.rQslice(1, 4));
FBU.bytes = 1 + clength[0] + clength[1]; // ctl + clength size + jpeg-data FBU.bytes = 1 + clength[0] + clength[1]; // ctl + clength size + jpeg-data
if (ws.rQwait("TIGHT " + cmode, FBU.bytes)) { return false; } if (ws.rQwait("TIGHT " + cmode, FBU.bytes)) { return false; }
// We have everything, render it // We have everything, render it
//Util.Debug(" png, ws.rQlen(): " + ws.rQlen() + ", clength[0]: " + clength[0] + ", clength[1]: " + clength[1]); //Util.Debug(" jpeg, ws.rQlen(): " + ws.rQlen() + ", clength[0]: " +
// clength[0] + ", clength[1]: " + clength[1]);
ws.rQshiftBytes(1 + clength[0]); // shift off ctl + compact length ws.rQshiftBytes(1 + clength[0]); // shift off ctl + compact length
img = new Image(); img = new Image();
//img.onload = scan_tight_imgQ; img.src = "data:image/" + cmode +
FBU.imgQ.push({ extract_data_uri(ws.rQshiftBytes(clength[1]));
display.renderQ_push({
'type': 'img', 'type': 'img',
'img': img, 'img': img,
'x': FBU.x, 'x': FBU.x,
'y': FBU.y}); 'y': FBU.y});
img.src = "data:image/" + cmode +
extract_data_uri(ws.rQshiftBytes(clength[1]));
img = null; img = null;
break; break;
case "filter":
filterId = rQ[rQi + 1];
if (filterId === 1) {
if (!handlePalette()) { return false; }
} else {
// Filter 0, Copy could be valid here, but servers don't send it as an explicit filter
// Filter 2, Gradient is valid but not used if jpeg is enabled
throw("Unsupported tight subencoding received, filter: " + filterId);
}
break;
case "copy":
if (!handleCopy()) { return false; }
break;
} }
FBU.bytes = 0; FBU.bytes = 0;
FBU.rects -= 1; FBU.rects -= 1;
//Util.Debug(" ending ws.rQslice(0,20): " + ws.rQslice(0,20) + " (" + ws.rQlen() + ")"); //Util.Debug(" ending ws.rQslice(0,20): " + ws.rQslice(0,20) + " (" + ws.rQlen() + ")");
//Util.Debug("<< display_tight_png"); //Util.Debug("<< display_tight_png");
return true; return true;
}; }
extract_data_uri = function(arr) { extract_data_uri = function(arr) {
//var i, stra = []; //var i, stra = [];
@@ -1290,21 +1563,14 @@ extract_data_uri = function(arr) {
return ";base64," + Base64.encode(arr); return ";base64," + Base64.encode(arr);
}; };
scan_tight_imgQ = function() { encHandlers.TIGHT = function () { return display_tight(false); };
var data, imgQ, ctx; encHandlers.TIGHT_PNG = function () { return display_tight(true); };
ctx = display.get_context();
if (rfb_state === 'normal') { encHandlers.last_rect = function last_rect() {
imgQ = FBU.imgQ; //Util.Debug(">> last_rect");
while ((imgQ.length > 0) && (imgQ[0].img.complete)) { FBU.rects = 0;
data = imgQ.shift(); //Util.Debug("<< last_rect");
if (data['type'] === 'fill') { return true;
display.fillRect(data.x, data.y, data.width, data.height, data.color);
} else {
ctx.drawImage(data.img, data.x, data.y);
}
}
setTimeout(scan_tight_imgQ, scan_imgQ_rate);
}
}; };
encHandlers.DesktopSize = function set_desktopsize() { encHandlers.DesktopSize = function set_desktopsize() {
@@ -1314,7 +1580,7 @@ encHandlers.DesktopSize = function set_desktopsize() {
display.resize(fb_width, fb_height); display.resize(fb_width, fb_height);
timing.fbu_rt_start = (new Date()).getTime(); timing.fbu_rt_start = (new Date()).getTime();
// Send a new non-incremental request // Send a new non-incremental request
ws.send(fbUpdateRequest(0)); ws.send(fbUpdateRequests());
FBU.bytes = 0; FBU.bytes = 0;
FBU.rects -= 1; FBU.rects -= 1;
@@ -1325,7 +1591,7 @@ encHandlers.DesktopSize = function set_desktopsize() {
encHandlers.Cursor = function set_cursor() { encHandlers.Cursor = function set_cursor() {
var x, y, w, h, pixelslength, masklength; var x, y, w, h, pixelslength, masklength;
//Util.Debug(">> set_cursor"); Util.Debug(">> set_cursor");
x = FBU.x; // hotspot-x x = FBU.x; // hotspot-x
y = FBU.y; // hotspot-y y = FBU.y; // hotspot-y
w = FBU.width; w = FBU.width;
@@ -1346,7 +1612,7 @@ encHandlers.Cursor = function set_cursor() {
FBU.bytes = 0; FBU.bytes = 0;
FBU.rects -= 1; FBU.rects -= 1;
//Util.Debug("<< set_cursor"); Util.Debug("<< set_cursor");
return true; return true;
}; };
@@ -1378,9 +1644,9 @@ pixelFormat = function() {
arr.push16(255); // red-max arr.push16(255); // red-max
arr.push16(255); // green-max arr.push16(255); // green-max
arr.push16(255); // blue-max arr.push16(255); // blue-max
arr.push8(0); // red-shift arr.push8(16); // red-shift
arr.push8(8); // green-shift arr.push8(8); // green-shift
arr.push8(16); // blue-shift arr.push8(0); // blue-shift
arr.push8(0); // padding arr.push8(0); // padding
arr.push8(0); // padding arr.push8(0); // padding
@@ -1416,10 +1682,10 @@ clientEncodings = function() {
fbUpdateRequest = function(incremental, x, y, xw, yw) { fbUpdateRequest = function(incremental, x, y, xw, yw) {
//Util.Debug(">> fbUpdateRequest"); //Util.Debug(">> fbUpdateRequest");
if (!x) { x = 0; } if (typeof(x) === "undefined") { x = 0; }
if (!y) { y = 0; } if (typeof(y) === "undefined") { y = 0; }
if (!xw) { xw = fb_width; } if (typeof(xw) === "undefined") { xw = fb_width; }
if (!yw) { yw = fb_height; } if (typeof(yw) === "undefined") { yw = fb_height; }
var arr; var arr;
arr = [3]; // msg-type arr = [3]; // msg-type
arr.push8(incremental); arr.push8(incremental);
@@ -1431,6 +1697,26 @@ fbUpdateRequest = function(incremental, x, y, xw, yw) {
return arr; return arr;
}; };
// Based on clean/dirty areas, generate requests to send
fbUpdateRequests = function() {
var cleanDirty = display.getCleanDirtyReset(),
arr = [], i, cb, db;
cb = cleanDirty.cleanBox;
if (cb.w > 0 && cb.h > 0) {
// Request incremental for clean box
arr = arr.concat(fbUpdateRequest(1, cb.x, cb.y, cb.w, cb.h));
}
for (i = 0; i < cleanDirty.dirtyBoxes.length; i++) {
db = cleanDirty.dirtyBoxes[i];
// Force all (non-incremental for dirty box
arr = arr.concat(fbUpdateRequest(0, db.x, db.y, db.w, db.h));
}
return arr;
};
keyEvent = function(keysym, down) { keyEvent = function(keysym, down) {
//Util.Debug(">> keyEvent, keysym: " + keysym + ", down: " + down); //Util.Debug(">> keyEvent, keysym: " + keysym + ", down: " + down);
var arr; var arr;
@@ -1476,13 +1762,13 @@ clientCutText = function(text) {
// Public API interface functions // Public API interface functions
// //
that.connect = function(host, port, password, uri) { that.connect = function(host, port, password, path) {
//Util.Debug(">> connect"); //Util.Debug(">> connect");
rfb_host = host; rfb_host = host;
rfb_port = port; rfb_port = port;
rfb_password = (password !== undefined) ? password : ""; rfb_password = (password !== undefined) ? password : "";
rfb_uri = (uri !== undefined) ? uri : ""; rfb_path = (path !== undefined) ? path : "";
if ((!rfb_host) || (!rfb_port)) { if ((!rfb_host) || (!rfb_port)) {
return fail("Must set host and port"); return fail("Must set host and port");
@@ -1506,7 +1792,7 @@ that.sendPassword = function(passwd) {
}; };
that.sendCtrlAltDel = function() { that.sendCtrlAltDel = function() {
if (rfb_state !== "normal") { return false; } if (rfb_state !== "normal" || conf.view_only) { return false; }
Util.Info("Sending Ctrl-Alt-Del"); Util.Info("Sending Ctrl-Alt-Del");
var arr = []; var arr = [];
arr = arr.concat(keyEvent(0xFFE3, 1)); // Control arr = arr.concat(keyEvent(0xFFE3, 1)); // Control
@@ -1515,14 +1801,14 @@ that.sendCtrlAltDel = function() {
arr = arr.concat(keyEvent(0xFFFF, 0)); // Delete arr = arr.concat(keyEvent(0xFFFF, 0)); // Delete
arr = arr.concat(keyEvent(0xFFE9, 0)); // Alt arr = arr.concat(keyEvent(0xFFE9, 0)); // Alt
arr = arr.concat(keyEvent(0xFFE3, 0)); // Control arr = arr.concat(keyEvent(0xFFE3, 0)); // Control
arr = arr.concat(fbUpdateRequest(1)); arr = arr.concat(fbUpdateRequests());
ws.send(arr); ws.send(arr);
}; };
// Send a key press. If 'down' is not specified then send a down key // Send a key press. If 'down' is not specified then send a down key
// followed by an up key. // followed by an up key.
that.sendKey = function(code, down) { that.sendKey = function(code, down) {
if (rfb_state !== "normal") { return false; } if (rfb_state !== "normal" || conf.view_only) { return false; }
var arr = []; var arr = [];
if (typeof down !== 'undefined') { if (typeof down !== 'undefined') {
Util.Info("Sending key code (" + (down ? "down" : "up") + "): " + code); Util.Info("Sending key code (" + (down ? "down" : "up") + "): " + code);
@@ -1532,7 +1818,7 @@ that.sendKey = function(code, down) {
arr = arr.concat(keyEvent(code, 1)); arr = arr.concat(keyEvent(code, 1));
arr = arr.concat(keyEvent(code, 0)); arr = arr.concat(keyEvent(code, 0));
} }
arr = arr.concat(fbUpdateRequest(1)); arr = arr.concat(fbUpdateRequests());
ws.send(arr); ws.send(arr);
}; };
+410 -174
View File
@@ -1,6 +1,6 @@
/* /*
* noVNC: HTML5 VNC client * noVNC: HTML5 VNC client
* Copyright (C) 2011 Joel Martin * Copyright (C) 2012 Joel Martin
* Licensed under LGPL-3 (see LICENSE.txt) * Licensed under LGPL-3 (see LICENSE.txt)
* *
* See README.md for usage and integration instructions. * See README.md for usage and integration instructions.
@@ -12,150 +12,110 @@
var UI = { var UI = {
rfb_state : 'loaded',
settingsOpen : false, settingsOpen : false,
connSettingsOpen : false,
clipboardOpen: false,
keyboardVisible: false,
// Render default UI and initialize settings menu // Render default UI and initialize settings menu
load: function(target) { load: function() {
var html = '', i, sheet, sheets, llevels; var html = '', i, sheet, sheets, llevels;
/* Populate the 'target' DOM element with default UI */
if (!target) {
target = $D('vnc');
} else if (typeof target === 'string') {
target = $D(target);
}
if ((!document.createElement('canvas').getContext) &&
window.ActiveXObject) {
// Suggest Chrome frame for Internet Explorer users
html += '<center><div style="text-align: left; width: 400px">';
html += ' You are using a version of Internet Explorer ';
html += ' that does not have HTML5 Canvas support. ';
html += ' To use noVNC you must use a browser with HTML5 ';
html += ' Canvas support or install ';
html += ' <a href="http://google.com/chromeframe" target="cframe">';
html += ' Google Chrome Frame.</a>';
html += '</div></center>';
target.innerHTML = html;
return;
}
html += '<div id="VNC_controls">';
html += ' <ul>';
html += ' <li>Host: <input id="VNC_host"></li>';
html += ' <li>Port: <input id="VNC_port"></li>';
html += ' <li>Password: <input id="VNC_password"';
html += ' type="password"></li>';
html += ' <li><input id="VNC_connect_button" type="button"';
html += ' value="Loading" disabled></li>';
html += ' </ul>';
html += '</div>';
html += '<div id="VNC_screen">';
html += ' <div id="VNC_status_bar" class="VNC_status_bar" style="margin-top: 0px;">';
html += ' <table border=0 width=100%><tr>';
html += ' <td><div id="VNC_status">Loading</div></td>';
html += ' <td width=1%><div class="VNC_buttons_right">';
html += ' <input type=button class="VNC_status_button" value="Settings"';
html += ' id="menuButton"';
html += ' onclick="UI.clickSettingsMenu();">';
html += ' <span id="VNC_settings_menu"';
html += ' onmouseover="UI.displayBlur();"';
html += ' onmouseout="UI.displayFocus();">';
html += ' <ul>';
html += ' <li><input id="VNC_encrypt"';
html += ' type="checkbox"> Encrypt</li>';
html += ' <li><input id="VNC_true_color"';
html += ' type="checkbox" checked> True Color</li>';
html += ' <li><input id="VNC_cursor"';
html += ' type="checkbox"> Local Cursor</li>';
html += ' <li><input id="VNC_shared"';
html += ' type="checkbox"> Shared Mode</li>';
html += ' <li><input id="VNC_connectTimeout"';
html += ' type="input"> Connect Timeout (s)</li>';
html += ' <hr>';
// Stylesheet selection dropdown // Stylesheet selection dropdown
html += ' <li><select id="VNC_stylesheet" name="vncStyle">';
html += ' <option value="default">default</option>';
sheet = WebUtil.selectStylesheet(); sheet = WebUtil.selectStylesheet();
sheets = WebUtil.getStylesheets(); sheets = WebUtil.getStylesheets();
for (i = 0; i < sheets.length; i += 1) { for (i = 0; i < sheets.length; i += 1) {
html += '<option value="' + sheets[i].title + '">' + sheets[i].title + '</option>'; UI.addOption($D('noVNC_stylesheet'),sheets[i].title, sheets[i].title);
} }
html += ' </select> Style</li>';
// Logging selection dropdown // Logging selection dropdown
html += ' <li><select id="VNC_logging" name="vncLogging">';
llevels = ['error', 'warn', 'info', 'debug']; llevels = ['error', 'warn', 'info', 'debug'];
for (i = 0; i < llevels.length; i += 1) { for (i = 0; i < llevels.length; i += 1) {
html += '<option value="' + llevels[i] + '">' + llevels[i] + '</option>'; UI.addOption($D('noVNC_logging'),llevels[i], llevels[i]);
} }
html += ' </select> Logging</li>';
html += ' <hr>';
html += ' <li><input type="button" id="VNC_apply" value="Apply"';
html += ' onclick="UI.settingsApply()"></li>';
html += ' </ul>';
html += ' </span></div></td>';
html += ' <td width=1%><div class="VNC_buttons_right">';
html += ' <input type=button class="VNC_status_button" value="Send CtrlAltDel"';
html += ' id="sendCtrlAltDelButton"';
html += ' onclick="UI.sendCtrlAltDel();"></div></td>';
html += ' </tr></table>';
html += ' </div>';
html += ' <canvas id="VNC_canvas" width="640px" height="20px">';
html += ' Canvas not supported.';
html += ' </canvas>';
html += '</div>';
html += '<br><br>';
html += '<div id="VNC_clipboard">';
html += ' VNC Clipboard:';
html += ' <input id="VNC_clipboard_clear_button"';
html += ' type="button" value="Clear"';
html += ' onclick="UI.clipClear();">';
html += ' <br>';
html += ' <textarea id="VNC_clipboard_text" cols=80 rows=5';
html += ' onfocus="UI.displayBlur();"';
html += ' onblur="UI.displayFocus();"';
html += ' onchange="UI.clipSend();"></textarea>';
html += '</div>';
target.innerHTML = html;
// Settings with immediate effects // Settings with immediate effects
UI.initSetting('logging', 'warn'); UI.initSetting('logging', 'warn');
WebUtil.init_logging(UI.getSetting('logging')); WebUtil.init_logging(UI.getSetting('logging'));
UI.initSetting('stylesheet', 'default');
WebUtil.selectStylesheet(null); // call twice to get around webkit bug UI.initSetting('stylesheet', 'default');
WebUtil.selectStylesheet(null);
// call twice to get around webkit bug
WebUtil.selectStylesheet(UI.getSetting('stylesheet')); WebUtil.selectStylesheet(UI.getSetting('stylesheet'));
/* Populate the controls if defaults are provided in the URL */ /* Populate the controls if defaults are provided in the URL */
UI.initSetting('host', ''); UI.initSetting('host', window.location.hostname);
UI.initSetting('port', ''); UI.initSetting('port', window.location.port);
UI.initSetting('password', ''); UI.initSetting('password', '');
UI.initSetting('encrypt', false); UI.initSetting('encrypt', (window.location.protocol === "https:"));
UI.initSetting('true_color', true); UI.initSetting('true_color', true);
UI.initSetting('cursor', false); UI.initSetting('cursor', false);
UI.initSetting('shared', true); UI.initSetting('shared', true);
UI.initSetting('view_only', false);
UI.initSetting('connectTimeout', 2); UI.initSetting('connectTimeout', 2);
UI.initSetting('path', 'websockify');
UI.initSetting('repeaterID', '');
UI.rfb = RFB({'target': $D('VNC_canvas'), UI.rfb = RFB({'target': $D('noVNC_canvas'),
'onUpdateState': UI.updateState, 'onUpdateState': UI.updateState,
'onClipboard': UI.clipReceive}); 'onClipboard': UI.clipReceive});
UI.updateVisualState();
// Unfocus clipboard when over the VNC area // Unfocus clipboard when over the VNC area
$D('VNC_screen').onmousemove = function () { //$D('VNC_screen').onmousemove = function () {
var keyboard = UI.rfb.get_keyboard(); // var keyboard = UI.rfb.get_keyboard();
if ((! keyboard) || (! keyboard.get_focused())) { // if ((! keyboard) || (! keyboard.get_focused())) {
$D('VNC_clipboard_text').blur(); // $D('VNC_clipboard_text').blur();
} // }
}; // };
// Show mouse selector buttons on touch screen devices
if ('ontouchstart' in document.documentElement) {
// Show mobile buttons
$D('noVNC_mobile_buttons').style.display = "inline";
UI.setMouseButton();
// Remove the address bar
setTimeout(function() { window.scrollTo(0, 1); }, 100);
UI.forceSetting('clip', true);
$D('noVNC_clip').disabled = true;
} else {
UI.initSetting('clip', false);
}
//iOS Safari does not support CSS position:fixed.
//This detects iOS devices and enables javascript workaround.
if ((navigator.userAgent.match(/iPhone/i)) ||
(navigator.userAgent.match(/iPod/i)) ||
(navigator.userAgent.match(/iPad/i))) {
//UI.setOnscroll();
//UI.setResize();
}
$D('noVNC_host').focus();
UI.setViewClip();
Util.addEvent(window, 'resize', UI.setViewClip);
Util.addEvent(window, 'beforeunload', function () {
if (UI.rfb_state === 'normal') {
return "You are currently connected.";
}
} );
// Show description by default when hosted at for kanaka.github.com
if (location.host === "kanaka.github.com") {
// Open the description dialog
$D('noVNC_description').style.display = "block";
} else {
// Open the connect panel on first load
UI.toggleConnectPanel();
}
}, },
// Read form control compatible setting from cookie // Read form control compatible setting from cookie
getSetting: function(name) { getSetting: function(name) {
var val, ctrl = $D('VNC_' + name); var val, ctrl = $D('noVNC_' + name);
val = WebUtil.readCookie(name); val = WebUtil.readCookie(name);
if (ctrl.type === 'checkbox') { if (ctrl.type === 'checkbox') {
if (val.toLowerCase() in {'0':1, 'no':1, 'false':1}) { if (val.toLowerCase() in {'0':1, 'no':1, 'false':1}) {
@@ -170,7 +130,8 @@ getSetting: function(name) {
// Update cookie and form control setting. If value is not set, then // Update cookie and form control setting. If value is not set, then
// updates from control to current cookie setting. // updates from control to current cookie setting.
updateSetting: function(name, value) { updateSetting: function(name, value) {
var i, ctrl = $D('VNC_' + name);
var i, ctrl = $D('noVNC_' + name);
// Save the cookie for this session // Save the cookie for this session
if (typeof value !== 'undefined') { if (typeof value !== 'undefined') {
WebUtil.createCookie(name, value); WebUtil.createCookie(name, value);
@@ -178,8 +139,10 @@ updateSetting: function(name, value) {
// Update the settings control // Update the settings control
value = UI.getSetting(name); value = UI.getSetting(name);
if (ctrl.type === 'checkbox') { if (ctrl.type === 'checkbox') {
ctrl.checked = value; ctrl.checked = value;
} else if (typeof ctrl.options !== 'undefined') { } else if (typeof ctrl.options !== 'undefined') {
for (i = 0; i < ctrl.options.length; i += 1) { for (i = 0; i < ctrl.options.length; i += 1) {
if (ctrl.options[i].value === value) { if (ctrl.options[i].value === value) {
@@ -188,13 +151,18 @@ updateSetting: function(name, value) {
} }
} }
} else { } else {
/*Weird IE9 error leads to 'null' appearring
in textboxes instead of ''.*/
if (value === null) {
value = "";
}
ctrl.value = value; ctrl.value = value;
} }
}, },
// Save control setting to cookie // Save control setting to cookie
saveSetting: function(name) { saveSetting: function(name) {
var val, ctrl = $D('VNC_' + name); var val, ctrl = $D('noVNC_' + name);
if (ctrl.type === 'checkbox') { if (ctrl.type === 'checkbox') {
val = ctrl.checked; val = ctrl.checked;
} else if (typeof ctrl.options !== 'undefined') { } else if (typeof ctrl.options !== 'undefined') {
@@ -217,18 +185,77 @@ initSetting: function(name, defVal) {
val = WebUtil.readCookie(name, defVal); val = WebUtil.readCookie(name, defVal);
} }
UI.updateSetting(name, val); UI.updateSetting(name, val);
//Util.Debug("Setting '" + name + "' initialized to '" + val + "'"); //Util.Debug("Setting '" + name + "' initialized to '" + val + "'");
return val;
},
// Force a setting to be a certain value
forceSetting: function(name, val) {
UI.updateSetting(name, val);
return val; return val;
}, },
// Show the clipboard panel
toggleClipboardPanel: function() {
// Close the description panel
$D('noVNC_description').style.display = "none";
//Close settings if open
if (UI.settingsOpen === true) {
UI.settingsApply();
UI.closeSettingsMenu();
}
//Close connection settings if open
if (UI.connSettingsOpen === true) {
UI.toggleConnectPanel();
}
//Toggle Clipboard Panel
if (UI.clipboardOpen === true) {
$D('noVNC_clipboard').style.display = "none";
$D('clipboardButton').className = "noVNC_status_button";
UI.clipboardOpen = false;
} else {
$D('noVNC_clipboard').style.display = "block";
$D('clipboardButton').className = "noVNC_status_button_selected";
UI.clipboardOpen = true;
}
},
// Show the connection settings panel/menu
toggleConnectPanel: function() {
// Close the description panel
$D('noVNC_description').style.display = "none";
//Close connection settings if open
if (UI.settingsOpen === true) {
UI.settingsApply();
UI.closeSettingsMenu();
$D('connectButton').className = "noVNC_status_button";
}
if (UI.clipboardOpen === true) {
UI.toggleClipboardPanel();
}
//Toggle Connection Panel
if (UI.connSettingsOpen === true) {
$D('noVNC_controls').style.display = "none";
$D('connectButton').className = "noVNC_status_button";
UI.connSettingsOpen = false;
} else {
$D('noVNC_controls').style.display = "block";
$D('connectButton').className = "noVNC_status_button_selected";
UI.connSettingsOpen = true;
$D('noVNC_host').focus();
}
},
// Toggle the settings menu: // Toggle the settings menu:
// On open, settings are refreshed from saved cookies. // On open, settings are refreshed from saved cookies.
// On close, settings are applied // On close, settings are applied
clickSettingsMenu: function() { toggleSettingsPanel: function() {
// Close the description panel
$D('noVNC_description').style.display = "none";
if (UI.settingsOpen) { if (UI.settingsOpen) {
UI.settingsApply(); UI.settingsApply();
UI.closeSettingsMenu(); UI.closeSettingsMenu();
} else { } else {
UI.updateSetting('encrypt'); UI.updateSetting('encrypt');
@@ -237,10 +264,14 @@ clickSettingsMenu: function() {
UI.updateSetting('cursor'); UI.updateSetting('cursor');
} else { } else {
UI.updateSetting('cursor', false); UI.updateSetting('cursor', false);
$D('VNC_cursor').disabled = true; $D('noVNC_cursor').disabled = true;
} }
UI.updateSetting('clip');
UI.updateSetting('shared'); UI.updateSetting('shared');
UI.updateSetting('view_only');
UI.updateSetting('connectTimeout'); UI.updateSetting('connectTimeout');
UI.updateSetting('path');
UI.updateSetting('repeaterID');
UI.updateSetting('stylesheet'); UI.updateSetting('stylesheet');
UI.updateSetting('logging'); UI.updateSetting('logging');
@@ -250,32 +281,27 @@ clickSettingsMenu: function() {
// Open menu // Open menu
openSettingsMenu: function() { openSettingsMenu: function() {
$D('VNC_settings_menu').style.display = "block"; // Close the description panel
$D('noVNC_description').style.display = "none";
if (UI.clipboardOpen === true) {
UI.toggleClipboardPanel();
}
//Close connection settings if open
if (UI.connSettingsOpen === true) {
UI.toggleConnectPanel();
}
$D('noVNC_settings').style.display = "block";
$D('settingsButton').className = "noVNC_status_button_selected";
UI.settingsOpen = true; UI.settingsOpen = true;
}, },
// Close menu (without applying settings) // Close menu (without applying settings)
closeSettingsMenu: function() { closeSettingsMenu: function() {
$D('VNC_settings_menu').style.display = "none"; $D('noVNC_settings').style.display = "none";
$D('settingsButton').className = "noVNC_status_button";
UI.settingsOpen = false; UI.settingsOpen = false;
}, },
// Disable/enable controls depending on connection state
settingsDisabled: function(disabled, rfb) {
//Util.Debug(">> settingsDisabled");
$D('VNC_encrypt').disabled = disabled;
$D('VNC_true_color').disabled = disabled;
if (rfb && rfb.get_display() && rfb.get_display().get_cursor_uri()) {
$D('VNC_cursor').disabled = disabled;
} else {
UI.updateSetting('cursor', false);
$D('VNC_cursor').disabled = true;
}
$D('VNC_shared').disabled = disabled;
$D('VNC_connectTimeout').disabled = disabled;
//Util.Debug("<< settingsDisabled");
},
// Save/apply settings when 'Apply' button is pressed // Save/apply settings when 'Apply' button is pressed
settingsApply: function() { settingsApply: function() {
//Util.Debug(">> settingsApply"); //Util.Debug(">> settingsApply");
@@ -284,22 +310,32 @@ settingsApply: function() {
if (UI.rfb.get_display().get_cursor_uri()) { if (UI.rfb.get_display().get_cursor_uri()) {
UI.saveSetting('cursor'); UI.saveSetting('cursor');
} }
UI.saveSetting('clip');
UI.saveSetting('shared'); UI.saveSetting('shared');
UI.saveSetting('view_only');
UI.saveSetting('connectTimeout'); UI.saveSetting('connectTimeout');
UI.saveSetting('path');
UI.saveSetting('repeaterID');
UI.saveSetting('stylesheet'); UI.saveSetting('stylesheet');
UI.saveSetting('logging'); UI.saveSetting('logging');
// Settings with immediate (non-connected related) effect // Settings with immediate (non-connected related) effect
WebUtil.selectStylesheet(UI.getSetting('stylesheet')); WebUtil.selectStylesheet(UI.getSetting('stylesheet'));
WebUtil.init_logging(UI.getSetting('logging')); WebUtil.init_logging(UI.getSetting('logging'));
UI.setViewClip();
UI.setViewDrag(UI.rfb.get_viewportDrag());
//Util.Debug("<< settingsApply"); //Util.Debug("<< settingsApply");
}, },
setPassword: function() { setPassword: function() {
UI.rfb.sendPassword($D('VNC_password').value); UI.rfb.sendPassword($D('noVNC_password').value);
//Reset connect button.
$D('noVNC_connect_button').value = "Connect";
$D('noVNC_connect_button').onclick = UI.Connect;
//Hide connection panel.
UI.toggleConnectPanel();
return false; return false;
}, },
@@ -307,52 +343,63 @@ sendCtrlAltDel: function() {
UI.rfb.sendCtrlAltDel(); UI.rfb.sendCtrlAltDel();
}, },
setMouseButton: function(num) {
var b, blist = [0, 1,2,4], button;
if (typeof num === 'undefined') {
// Disable mouse buttons
num = -1;
}
if (UI.rfb) {
UI.rfb.get_mouse().set_touchButton(num);
}
for (b = 0; b < blist.length; b++) {
button = $D('noVNC_mouse_button' + blist[b]);
if (blist[b] === num) {
button.style.display = "";
} else {
button.style.display = "none";
/*
button.style.backgroundColor = "black";
button.style.color = "lightgray";
button.style.backgroundColor = "";
button.style.color = "";
*/
}
}
},
updateState: function(rfb, state, oldstate, msg) { updateState: function(rfb, state, oldstate, msg) {
var s, sb, c, cad, klass; var s, sb, c, d, cad, vd, klass;
s = $D('VNC_status'); UI.rfb_state = state;
sb = $D('VNC_status_bar'); s = $D('noVNC_status');
c = $D('VNC_connect_button'); sb = $D('noVNC_status_bar');
cad = $D('sendCtrlAltDelButton');
switch (state) { switch (state) {
case 'failed': case 'failed':
case 'fatal': case 'fatal':
c.disabled = true; klass = "noVNC_status_error";
cad.disabled = true;
UI.settingsDisabled(true, rfb);
klass = "VNC_status_error";
break; break;
case 'normal': case 'normal':
c.value = "Disconnect"; klass = "noVNC_status_normal";
c.onclick = UI.disconnect;
c.disabled = false;
cad.disabled = false;
UI.settingsDisabled(true, rfb);
klass = "VNC_status_normal";
break; break;
case 'disconnected': case 'disconnected':
$D('noVNC_logo').style.display = "block";
// Fall through
case 'loaded': case 'loaded':
c.value = "Connect"; klass = "noVNC_status_normal";
c.onclick = UI.connect;
c.disabled = false;
cad.disabled = true;
UI.settingsDisabled(false, rfb);
klass = "VNC_status_normal";
break; break;
case 'password': case 'password':
c.value = "Send Password"; UI.toggleConnectPanel();
c.onclick = UI.setPassword;
c.disabled = false; $D('noVNC_connect_button').value = "Send Password";
cad.disabled = true; $D('noVNC_connect_button').onclick = UI.setPassword;
UI.settingsDisabled(true, rfb); $D('noVNC_password').focus();
klass = "VNC_status_warn";
klass = "noVNC_status_warn";
break; break;
default: default:
c.disabled = true; klass = "noVNC_status_warn";
cad.disabled = true;
UI.settingsDisabled(true, rfb);
klass = "VNC_status_warn";
break; break;
} }
@@ -362,23 +409,80 @@ updateState: function(rfb, state, oldstate, msg) {
s.innerHTML = msg; s.innerHTML = msg;
} }
UI.updateVisualState();
}, },
// Disable/enable controls depending on connection state
updateVisualState: function() {
var connected = UI.rfb_state === 'normal' ? true : false;
//Util.Debug(">> updateVisualState");
$D('noVNC_encrypt').disabled = connected;
$D('noVNC_true_color').disabled = connected;
if (UI.rfb && UI.rfb.get_display() &&
UI.rfb.get_display().get_cursor_uri()) {
$D('noVNC_cursor').disabled = connected;
} else {
UI.updateSetting('cursor', false);
$D('noVNC_cursor').disabled = true;
}
$D('noVNC_shared').disabled = connected;
$D('noVNC_view_only').disabled = connected;
$D('noVNC_connectTimeout').disabled = connected;
$D('noVNC_path').disabled = connected;
$D('noVNC_repeaterID').disabled = connected;
if (connected) {
UI.setViewClip();
UI.setMouseButton(1);
$D('clipboardButton').style.display = "inline";
$D('showKeyboard').style.display = "inline";
$D('sendCtrlAltDelButton').style.display = "inline";
} else {
UI.setMouseButton();
$D('clipboardButton').style.display = "none";
$D('showKeyboard').style.display = "none";
$D('sendCtrlAltDelButton').style.display = "none";
}
// State change disables viewport dragging.
// It is enabled (toggled) by direct click on the button
UI.setViewDrag(false);
switch (UI.rfb_state) {
case 'fatal':
case 'failed':
case 'loaded':
case 'disconnected':
$D('connectButton').style.display = "";
$D('disconnectButton').style.display = "none";
break;
default:
$D('connectButton').style.display = "none";
$D('disconnectButton').style.display = "";
break;
}
//Util.Debug("<< updateVisualState");
},
clipReceive: function(rfb, text) { clipReceive: function(rfb, text) {
Util.Debug(">> UI.clipReceive: " + text.substr(0,40) + "..."); Util.Debug(">> UI.clipReceive: " + text.substr(0,40) + "...");
$D('VNC_clipboard_text').value = text; $D('noVNC_clipboard_text').value = text;
Util.Debug("<< UI.clipReceive"); Util.Debug("<< UI.clipReceive");
}, },
connect: function() { connect: function() {
var host, port, password; var host, port, password, path;
UI.closeSettingsMenu(); UI.closeSettingsMenu();
UI.toggleConnectPanel();
host = $D('VNC_host').value; host = $D('noVNC_host').value;
port = $D('VNC_port').value; port = $D('noVNC_port').value;
password = $D('VNC_password').value; password = $D('noVNC_password').value;
path = $D('noVNC_path').value;
if ((!host) || (!port)) { if ((!host) || (!port)) {
throw("Must set host and port"); throw("Must set host and port");
} }
@@ -387,15 +491,24 @@ connect: function() {
UI.rfb.set_true_color(UI.getSetting('true_color')); UI.rfb.set_true_color(UI.getSetting('true_color'));
UI.rfb.set_local_cursor(UI.getSetting('cursor')); UI.rfb.set_local_cursor(UI.getSetting('cursor'));
UI.rfb.set_shared(UI.getSetting('shared')); UI.rfb.set_shared(UI.getSetting('shared'));
UI.rfb.set_view_only(UI.getSetting('view_only'));
UI.rfb.set_connectTimeout(UI.getSetting('connectTimeout')); UI.rfb.set_connectTimeout(UI.getSetting('connectTimeout'));
UI.rfb.set_repeaterID(UI.getSetting('repeaterID'));
UI.rfb.connect(host, port, password); UI.rfb.connect(host, port, password, path);
//Close dialog.
setTimeout(UI.setBarPosition, 100);
$D('noVNC_logo').style.display = "none";
}, },
disconnect: function() { disconnect: function() {
UI.closeSettingsMenu(); UI.closeSettingsMenu();
UI.rfb.disconnect(); UI.rfb.disconnect();
$D('noVNC_logo').style.display = "block";
UI.connSettingsOpen = false;
UI.toggleConnectPanel();
}, },
displayBlur: function() { displayBlur: function() {
@@ -409,15 +522,138 @@ displayFocus: function() {
}, },
clipClear: function() { clipClear: function() {
$D('VNC_clipboard_text').value = ""; $D('noVNC_clipboard_text').value = "";
UI.rfb.clipboardPasteFrom(""); UI.rfb.clipboardPasteFrom("");
}, },
clipSend: function() { clipSend: function() {
var text = $D('VNC_clipboard_text').value; var text = $D('noVNC_clipboard_text').value;
Util.Debug(">> UI.clipSend: " + text.substr(0,40) + "..."); Util.Debug(">> UI.clipSend: " + text.substr(0,40) + "...");
UI.rfb.clipboardPasteFrom(text); UI.rfb.clipboardPasteFrom(text);
Util.Debug("<< UI.clipSend"); Util.Debug("<< UI.clipSend");
},
// Enable/disable and configure viewport clipping
setViewClip: function(clip) {
var display, cur_clip, pos, new_w, new_h;
if (UI.rfb) {
display = UI.rfb.get_display();
} else {
return;
}
cur_clip = display.get_viewport();
if (typeof(clip) !== 'boolean') {
// Use current setting
clip = UI.getSetting('clip');
}
if (clip && !cur_clip) {
// Turn clipping on
UI.updateSetting('clip', true);
} else if (!clip && cur_clip) {
// Turn clipping off
UI.updateSetting('clip', false);
display.set_viewport(false);
$D('noVNC_canvas').style.position = 'static';
display.viewportChange();
}
if (UI.getSetting('clip')) {
// If clipping, update clipping settings
$D('noVNC_canvas').style.position = 'absolute';
pos = Util.getPosition($D('noVNC_canvas'));
new_w = window.innerWidth - pos.x;
new_h = window.innerHeight - pos.y;
display.set_viewport(true);
display.viewportChange(0, 0, new_w, new_h);
}
},
// Toggle/set/unset the viewport drag/move button
setViewDrag: function(drag) {
var vmb = $D('noVNC_view_drag_button');
if (!UI.rfb) { return; }
if (UI.rfb_state === 'normal' &&
UI.rfb.get_display().get_viewport()) {
vmb.style.display = "inline";
} else {
vmb.style.display = "none";
}
if (typeof(drag) === "undefined") {
// If not specified, then toggle
drag = !UI.rfb.get_viewportDrag();
}
if (drag) {
vmb.className = "noVNC_status_button_selected";
UI.rfb.set_viewportDrag(true);
} else {
vmb.className = "noVNC_status_button";
UI.rfb.set_viewportDrag(false);
}
},
// On touch devices, show the OS keyboard
showKeyboard: function() {
if(UI.keyboardVisible === false) {
$D('keyboardinput').focus();
UI.keyboardVisible = true;
$D('showKeyboard').className = "noVNC_status_button_selected";
} else if(UI.keyboardVisible === true) {
$D('keyboardinput').blur();
$D('showKeyboard').className = "noVNC_status_button";
UI.keyboardVisible = false;
}
},
keyInputBlur: function() {
$D('showKeyboard').className = "noVNC_status_button";
//Weird bug in iOS if you change keyboardVisible
//here it does not actually occur so next time
//you click keyboard icon it doesnt work.
setTimeout(function() { UI.setKeyboard(); },100);
},
setKeyboard: function() {
UI.keyboardVisible = false;
},
// iOS < Version 5 does not support position fixed. Javascript workaround:
setOnscroll: function() {
window.onscroll = function() {
UI.setBarPosition();
};
},
setResize: function () {
window.onResize = function() {
UI.setBarPosition();
};
},
//Helper to add options to dropdown.
addOption: function(selectbox,text,value )
{
var optn = document.createElement("OPTION");
optn.text = text;
optn.value = value;
selectbox.options.add(optn);
},
setBarPosition: function() {
$D('noVNC-control-bar').style.top = (window.pageYOffset) + 'px';
$D('noVNC_mobile_buttons').style.left = (window.pageXOffset) + 'px';
var vncwidth = $D('noVNC_screen').style.offsetWidth;
$D('noVNC-control-bar').style.width = vncwidth + 'px';
} }
}; };
+49 -4
View File
@@ -1,6 +1,6 @@
/* /*
* noVNC: HTML5 VNC client * noVNC: HTML5 VNC client
* Copyright (C) 2011 Joel Martin * Copyright (C) 2012 Joel Martin
* Licensed under LGPL-3 (see LICENSE.txt) * Licensed under LGPL-3 (see LICENSE.txt)
* *
* See README.md for usage and integration instructions. * See README.md for usage and integration instructions.
@@ -33,6 +33,45 @@ Array.prototype.push32 = function (num) {
(num ) & 0xFF ); (num ) & 0xFF );
}; };
// IE does not support map (even in IE9)
//This prototype is provided by the Mozilla foundation and
//is distributed under the MIT license.
//http://www.ibiblio.org/pub/Linux/LICENSES/mit.license
if (!Array.prototype.map)
{
Array.prototype.map = function(fun /*, thisp*/)
{
var len = this.length;
if (typeof fun != "function")
throw new TypeError();
var res = new Array(len);
var thisp = arguments[1];
for (var i = 0; i < len; i++)
{
if (i in this)
res[i] = fun.call(thisp, this[i], i, this);
}
return res;
};
}
//
// requestAnimationFrame shim with setTimeout fallback
//
window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(callback){
window.setTimeout(callback, 1000 / 60);
};
})();
/* /*
* ------------------------------------------------------ * ------------------------------------------------------
* Namespaced in Util * Namespaced in Util
@@ -107,6 +146,8 @@ Util.conf_default = function(cfg, api, defaults, v, mode, type, defval, desc) {
} }
} else if (type in {'integer':1, 'int':1}) { } else if (type in {'integer':1, 'int':1}) {
val = parseInt(val, 10); val = parseInt(val, 10);
} else if (type === 'str') {
val = String(val);
} else if (type === 'func') { } else if (type === 'func') {
if (!val) { if (!val) {
val = function () {}; val = function () {};
@@ -159,7 +200,7 @@ Util.conf_defaults = function(cfg, api, defaults, arr) {
Util.conf_default(cfg, api, defaults, arr[i][0], arr[i][1], Util.conf_default(cfg, api, defaults, arr[i][0], arr[i][1],
arr[i][2], arr[i][3], arr[i][4]); arr[i][2], arr[i][3], arr[i][4]);
} }
} };
/* /*
@@ -184,6 +225,7 @@ Util.getEventPosition = function (e, obj, scale) {
var evt, docX, docY, pos; var evt, docX, docY, pos;
//if (!e) evt = window.event; //if (!e) evt = window.event;
evt = (e ? e : window.event); evt = (e ? e : window.event);
evt = (evt.changedTouches ? evt.changedTouches[0] : evt.touches ? evt.touches[0] : evt);
if (evt.pageX || evt.pageY) { if (evt.pageX || evt.pageY) {
docX = evt.pageX; docX = evt.pageX;
docY = evt.pageY; docY = evt.pageY;
@@ -239,8 +281,11 @@ Util.stopEvent = function(e) {
Util.Features = {xpath: !!(document.evaluate), air: !!(window.runtime), query: !!(document.querySelector)}; Util.Features = {xpath: !!(document.evaluate), air: !!(window.runtime), query: !!(document.querySelector)};
Util.Engine = { Util.Engine = {
'presto': (function() { // Version detection break in Opera 11.60 (errors on arguments.callee.caller reference)
return (!window.opera) ? false : ((arguments.callee.caller) ? 960 : ((document.getElementsByClassName) ? 950 : 925)); }()), //'presto': (function() {
// return (!window.opera) ? false : ((arguments.callee.caller) ? 960 : ((document.getElementsByClassName) ? 950 : 925)); }()),
'presto': (function() { return (!window.opera) ? false : true; }()),
'trident': (function() { 'trident': (function() {
return (!window.ActiveXObject) ? false : ((window.XMLHttpRequest) ? ((document.querySelectorAll) ? 6 : 5) : 4); }()), return (!window.ActiveXObject) ? false : ((window.XMLHttpRequest) ? ((document.querySelectorAll) ? 6 : 5) : 4); }()),
'webkit': (function() { 'webkit': (function() {
+2 -2
View File
@@ -1,6 +1,6 @@
/* /*
* noVNC: HTML5 VNC client * noVNC: HTML5 VNC client
* Copyright (C) 2011 Joel Martin * Copyright (C) 2012 Joel Martin
* Licensed under LGPL-3 (see LICENSE.txt) * Licensed under LGPL-3 (see LICENSE.txt)
* *
* See README.md for usage and integration instructions. * See README.md for usage and integration instructions.
@@ -30,13 +30,13 @@ function get_INCLUDE_URI() {
extra += start + "util.js" + end; extra += start + "util.js" + end;
extra += start + "webutil.js" + end; extra += start + "webutil.js" + end;
extra += start + "logo.js" + end;
extra += start + "base64.js" + end; extra += start + "base64.js" + end;
extra += start + "websock.js" + end; extra += start + "websock.js" + end;
extra += start + "des.js" + end; extra += start + "des.js" + end;
extra += start + "input.js" + end; extra += start + "input.js" + end;
extra += start + "display.js" + end; extra += start + "display.js" + end;
extra += start + "rfb.js" + end; extra += start + "rfb.js" + end;
extra += start + "jsunzip.js" + end;
document.write(extra); document.write(extra);
}()); }());
+1 -1
View File
@@ -5,7 +5,7 @@
(function() { (function() {
if (window.WebSocket) return; if (window.WebSocket && !window.WEB_SOCKET_FORCE_FLASH) return;
var console = window.console; var console = window.console;
if (!console || !console.log || !console.error) { if (!console || !console.log || !console.error) {
+21 -5
View File
@@ -1,6 +1,6 @@
/* /*
* Websock: high-performance binary WebSockets * Websock: high-performance binary WebSockets
* Copyright (C) 2011 Joel Martin * Copyright (C) 2012 Joel Martin
* Licensed under LGPL-3 (see LICENSE.txt) * Licensed under LGPL-3 (see LICENSE.txt)
* *
* Websock is similar to the standard WebSocket object but Websock * Websock is similar to the standard WebSocket object but Websock
@@ -14,13 +14,25 @@
* read binary data off of the receive queue. * read binary data off of the receive queue.
*/ */
/*jslint browser: true, bitwise: false, plusplus: false */
/*global Util, Base64 */
// Load Flash WebSocket emulator if needed // Load Flash WebSocket emulator if needed
if (window.WebSocket) { // To force WebSocket emulator even when native WebSocket available
//window.WEB_SOCKET_FORCE_FLASH = true;
// To enable WebSocket emulator debug:
//window.WEB_SOCKET_DEBUG=1;
if (window.WebSocket && !window.WEB_SOCKET_FORCE_FLASH) {
Websock_native = true; Websock_native = true;
} else if (window.MozWebSocket && !window.WEB_SOCKET_FORCE_FLASH) {
Websock_native = true;
window.WebSocket = window.MozWebSocket;
} else { } else {
/* no builtin WebSocket so load web_socket.js */ /* no builtin WebSocket so load web_socket.js */
Websock_native = false; Websock_native = false;
(function () { (function () {
function get_INCLUDE_URI() { function get_INCLUDE_URI() {
@@ -31,11 +43,11 @@ if (window.WebSocket) {
var start = "<script src='" + get_INCLUDE_URI(), var start = "<script src='" + get_INCLUDE_URI(),
end = "'><\/script>", extra = ""; end = "'><\/script>", extra = "";
WEB_SOCKET_SWF_LOCATION = get_INCLUDE_URI() + window.WEB_SOCKET_SWF_LOCATION = get_INCLUDE_URI() +
"web-socket-js/WebSocketMain.swf"; "web-socket-js/WebSocketMain.swf";
if (Util.Engine.trident) { if (Util.Engine.trident) {
Util.Debug("Forcing uncached load of WebSocketMain.swf"); Util.Debug("Forcing uncached load of WebSocketMain.swf");
WEB_SOCKET_SWF_LOCATION += "?" + Math.random(); window.WEB_SOCKET_SWF_LOCATION += "?" + Math.random();
} }
extra += start + "web-socket-js/swfobject.js" + end; extra += start + "web-socket-js/swfobject.js" + end;
extra += start + "web-socket-js/web_socket.js" + end; extra += start + "web-socket-js/web_socket.js" + end;
@@ -80,7 +92,7 @@ function get_rQi() {
} }
function set_rQi(val) { function set_rQi(val) {
rQi = val; rQi = val;
}; }
function rQlen() { function rQlen() {
return rQ.length - rQi; return rQ.length - rQi;
@@ -112,6 +124,7 @@ function rQshift32() {
(rQ[rQi++] ); (rQ[rQi++] );
} }
function rQshiftStr(len) { function rQshiftStr(len) {
if (typeof(len) === 'undefined') { len = rQlen(); }
var arr = rQ.slice(rQi, rQi + len); var arr = rQ.slice(rQi, rQi + len);
rQi += len; rQi += len;
return arr.map(function (num) { return arr.map(function (num) {
@@ -119,6 +132,7 @@ function rQshiftStr(len) {
} }
function rQshiftBytes(len) { function rQshiftBytes(len) {
if (typeof(len) === 'undefined') { len = rQlen(); }
rQi += len; rQi += len;
return rQ.slice(rQi-len, rQi); return rQ.slice(rQi-len, rQi);
} }
@@ -268,6 +282,8 @@ function open(uri) {
Util.Debug(">> WebSock.onopen"); Util.Debug(">> WebSock.onopen");
if (websocket.protocol) { if (websocket.protocol) {
Util.Info("Server chose sub-protocol: " + websocket.protocol); Util.Info("Server chose sub-protocol: " + websocket.protocol);
} else {
Util.Error("Server select no sub-protocol!: " + websocket.protocol);
} }
eventHandlers.open(); eventHandlers.open();
Util.Debug("<< WebSock.onopen"); Util.Debug("<< WebSock.onopen");
+5 -5
View File
@@ -1,6 +1,6 @@
/* /*
* noVNC: HTML5 VNC client * noVNC: HTML5 VNC client
* Copyright (C) 2011 Joel Martin * Copyright (C) 2012 Joel Martin
* Licensed under LGPL-3 (see LICENSE.txt) * Licensed under LGPL-3 (see LICENSE.txt)
* *
* See README.md for usage and integration instructions. * See README.md for usage and integration instructions.
@@ -8,7 +8,7 @@
"use strict"; "use strict";
/*jslint bitwise: false, white: false */ /*jslint bitwise: false, white: false */
/*global window, document */ /*global Util, window, document */
// Globals defined here // Globals defined here
var WebUtil = {}, $D; var WebUtil = {}, $D;
@@ -17,7 +17,7 @@ var WebUtil = {}, $D;
* Simple DOM selector by ID * Simple DOM selector by ID
*/ */
if (!window.$D) { if (!window.$D) {
$D = function (id) { window.$D = function (id) {
if (document.getElementById) { if (document.getElementById) {
return document.getElementById(id); return document.getElementById(id);
} else if (document.all) { } else if (document.all) {
@@ -42,8 +42,8 @@ WebUtil.init_logging = function() {
/logging=([A-Za-z0-9\._\-]*)/) || /logging=([A-Za-z0-9\._\-]*)/) ||
['', Util._log_level])[1]; ['', Util._log_level])[1];
Util.init_logging() Util.init_logging();
} };
WebUtil.init_logging(); WebUtil.init_logging();
+22 -22
View File
@@ -9,7 +9,7 @@
<script src="../include/util.js"></script> <script src="../include/util.js"></script>
<script src="../include/webutil.js"></script> <script src="../include/webutil.js"></script>
<script src="../include/base64.js"></script> <script src="../include/base64.js"></script>
<script src="../include/canvas.js"></script> <script src="../include/display.js"></script>
<script src="face.png.js"></script> <script src="face.png.js"></script>
</head> </head>
<body> <body>
@@ -36,7 +36,7 @@
<script> <script>
var msg_cnt = 0; var msg_cnt = 0;
var start_width = 300, start_height = 100; var display, start_width = 300, start_height = 100;
var iterations; var iterations;
function message(str) { function message(str) {
@@ -48,12 +48,12 @@
} }
function test_functions () { function test_functions () {
var img, x, y, w, h, ctx = canvas.getContext(); var img, x, y, w, h, ctx = display.get_context();
w = canvas.get_width(); w = display.get_width();
h = canvas.get_height(); h = display.get_height();
canvas.fillRect(0, 0, w, h, [240,240,240]); display.fillRect(0, 0, w, h, [240,240,240]);
canvas.blitStringImage("data:image/png;base64," + face64, 150, 10); display.blitStringImage("data:image/png;base64," + face64, 150, 10);
var himg = new Image(); var himg = new Image();
himg.onload = function () { himg.onload = function () {
@@ -70,14 +70,14 @@
data[(y*50 + x)*4 + 3] = 255; data[(y*50 + x)*4 + 3] = 255;
} }
} }
canvas.blitImage(30, 10, 50, 50, data, 0); display.blitImage(30, 10, 50, 50, data, 0);
img = canvas.getTile(5,5,16,16,[0,128,128]); img = display.getTile(5,5,16,16,[0,128,128]);
canvas.putTile(img); display.putTile(img);
img = canvas.getTile(90,15,16,16,[0,0,0]); img = display.getTile(90,15,16,16,[0,0,0]);
canvas.setSubTile(img, 0,0,16,16,[128,128,0]); display.setSubTile(img, 0,0,16,16,[128,128,0]);
canvas.putTile(img); display.putTile(img);
} }
function begin () { function begin () {
@@ -90,7 +90,7 @@
function start_delayed () { function start_delayed () {
var ret; var ret;
ret = canvas.set_prefer_js(true); ret = display.set_prefer_js(true);
if (ret) { if (ret) {
message("Running test: prefer Javascript ops"); message("Running test: prefer Javascript ops");
var time1 = run_test(); var time1 = run_test();
@@ -100,14 +100,14 @@
message("Could not run: prefer Javascript ops"); message("Could not run: prefer Javascript ops");
} }
canvas.set_prefer_js(false); display.set_prefer_js(false);
message("Running test: prefer Canvas ops"); message("Running test: prefer Canvas ops");
var time2 = run_test(); var time2 = run_test();
message("prefer Canvas ops: " + time2 + "ms total, " + message("prefer Canvas ops: " + time2 + "ms total, " +
(time2 / iterations) + "ms per frame"); (time2 / iterations) + "ms per frame");
if (Util.get_logging() !== 'debug') { if (Util.get_logging() !== 'debug') {
canvas.resize(start_width, start_height, true); display.resize(start_width, start_height, true);
test_functions(); test_functions();
} }
$D('startButton').disabled = false; $D('startButton').disabled = false;
@@ -118,7 +118,7 @@
var width, height; var width, height;
width = $D('width').value; width = $D('width').value;
height = $D('height').value; height = $D('height').value;
canvas.resize(width, height); display.resize(width, height);
var color, start_time = (new Date()).getTime(), w, h; var color, start_time = (new Date()).getTime(), w, h;
for (var i=0; i < iterations; i++) { for (var i=0; i < iterations; i++) {
color = [128, 128, (255 / iterations) * i, 0]; color = [128, 128, (255 / iterations) * i, 0];
@@ -126,9 +126,9 @@
for (var y=0; y < height; y = y + 16) { for (var y=0; y < height; y = y + 16) {
w = Math.min(16, width - x); w = Math.min(16, width - x);
h = Math.min(16, height - y); h = Math.min(16, height - y);
var tile = canvas.getTile(x, y, w, h, color); var tile = display.getTile(x, y, w, h, color);
canvas.setSubTile(tile, 0, 0, w, h, color); display.setSubTile(tile, 0, 0, w, h, color);
canvas.putTile(tile); display.putTile(tile);
} }
} }
} }
@@ -139,8 +139,8 @@
window.onload = function() { window.onload = function() {
message("in onload"); message("in onload");
$D('iterations').value = 10; $D('iterations').value = 10;
canvas = new Canvas({'target' : $D('canvas')}); display = new Display({'target' : $D('canvas')});
canvas.resize(start_width, start_height, true); display.resize(start_width, start_height, true);
message("Canvas initialized"); message("Canvas initialized");
test_functions(); test_functions();
} }
+51 -10
View File
@@ -4,7 +4,11 @@
<body> <body>
<br><br> <br><br>
Canvas:<br> Canvas:
<span id="button-selection" style="display: none;">
<input id="button1" type="button" value="L"><input id="button2" type="button" value="M"><input id="button4" type="button" value="R">
</span>
<br>
<canvas id="canvas" width="640" height="20" <canvas id="canvas" width="640" height="20"
style="border-style: dotted; border-width: 1px;"> style="border-style: dotted; border-width: 1px;">
Canvas not supported. Canvas not supported.
@@ -23,11 +27,11 @@
<script src="../include/webutil.js"></script> <script src="../include/webutil.js"></script>
<script src="../include/base64.js"></script> <script src="../include/base64.js"></script>
<script src="../include/input.js"></script> <script src="../include/input.js"></script>
<script src="../include/canvas.js"></script> <script src="../include/display.js"></script>
<script> <script>
var msg_cnt = 0; var msg_cnt = 0, iterations,
var width = 400, height = 200; width = 400, height = 200,
var iterations; canvas, keyboard, mouse;
var newline = "\n"; var newline = "\n";
if (Util.Engine.trident) { if (Util.Engine.trident) {
@@ -61,17 +65,54 @@
message(msg); message(msg);
} }
function selectButton(num) {
var b, blist = [1,2,4];
if (typeof num === 'undefined') {
// Show the default
num = mouse.get_touchButton();
} else if (num === mouse.get_touchButton()) {
// Set all buttons off (no clicks)
mouse.set_touchButton(0);
num = 0;
} else {
// Turn on one button
mouse.set_touchButton(num);
}
for (b = 0; b < blist.length; b++) {
if (blist[b] === num) {
$D('button' + blist[b]).style.backgroundColor = "black";
$D('button' + blist[b]).style.color = "lightgray";
} else {
$D('button' + blist[b]).style.backgroundColor = "";
$D('button' + blist[b]).style.color = "";
}
}
}
window.onload = function() { window.onload = function() {
var canvas = new Canvas({'target' : $D('canvas')}); canvas = new Display({'target' : $D('canvas')});
keyboard = new Keyboard({'target': document, keyboard = new Keyboard({'target': document,
'keyPress': keyPress}); 'onKeyPress': keyPress});
mouse = new Mouse({'target': $D('canvas'), mouse = new Mouse({'target': $D('canvas'),
'mouseButton': mouseButton, 'onMouseButton': mouseButton,
'mouseMove': mouseMove}); 'onMouseMove': mouseMove});
canvas.resize(width, height, true); canvas.resize(width, height, true);
keyboard.grab(); keyboard.grab();
mouse.grab(); mouse.grab();
message("Canvas initialized"); message("Display initialized");
if ('ontouchstart' in document.documentElement) {
message("Touch device detected");
$D('button-selection').style.display = "inline";
$D('button1').onclick = function(){ selectButton(1) };
$D('button2').onclick = function(){ selectButton(2) };
$D('button4').onclick = function(){ selectButton(4) };
selectButton();
}
} }
</script> </script>
</html> </html>
+43
View File
@@ -0,0 +1,43 @@
html,body {
margin: 0px;
padding: 0px;
width: 100%;
height: 100%;
}
.flex-layout {
width: 100%;
height: 100%;
display: box;
display: -webkit-box;
display: -moz-box;
display: -ms-box;
box-orient: vertical;
-webkit-box-orient: vertical;
-moz-box-orient: vertical;
-ms-box-orient: vertical;
box-align: stretch;
-webkit-box-align: stretch;
-moz-box-align: stretch;
-ms-box-align: stretch;
}
.flex-box {
box-flex: 1;
-webkit-box-flex: 1;
-moz-box-flex: 1;
-ms-box-flex: 1;
}
.container {
margin: 0px;
padding: 0px;
}
.canvas {
position: absolute;
border-style: dotted;
border-width: 1px;
}
+202
View File
@@ -0,0 +1,202 @@
<!DOCTYPE html>
<html>
<head><title>Viewport Test</title>
<link rel="stylesheet" href="viewport.css">
<!--
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
-->
<meta name=viewport content="width=device-width, initial-scale=1.0, user-scalable=no">
</head>
<body>
<div class="flex-layout">
<div>
Canvas:
<input id="move-selector" type="button" value="Move"
onclick="toggleMove();">
<br>
</div>
<div class="container flex-box">
<canvas id="canvas" class="canvas">
Canvas not supported.
</canvas>
<br>
</div>
<div>
<br>
Results:<br>
<textarea id="messages" style="font-size: 9;" cols=80 rows=8></textarea>
</div>
</div>
</body>
<!--
<script type='text/javascript'
src='http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js'></script>
-->
<script src="../include/util.js"></script>
<script src="../include/webutil.js"></script>
<script src="../include/base64.js"></script>
<script src="../include/input.js"></script>
<script src="../include/display.js"></script>
<script>
var msg_cnt = 0, iterations,
penDown = false, doMove = false,
inMove = false, lastPos = {},
padW = 0, padH = 0,
display, ctx, keyboard, mouse;
var newline = "\n";
if (Util.Engine.trident) {
var newline = "<br>\n";
}
function message(str) {
console.log(str);
cell = $D('messages');
cell.innerHTML += msg_cnt + ": " + str + newline;
cell.scrollTop = cell.scrollHeight;
msg_cnt++;
}
function mouseButton(x, y, down, bmask) {
//msg = 'mouse x,y: ' + x + ',' + y + ' down: ' + down;
//msg += ' bmask: ' + bmask;
//message(msg);
if (doMove) {
if (down && !inMove) {
inMove = true;
lastPos = {'x': x, 'y': y};
} else if (!down && inMove) {
inMove = false;
//dirtyRedraw();
}
return;
}
if (down && ! penDown) {
penDown = true;
ctx.beginPath();
ctx.moveTo(x, y);
} else if (!down && penDown) {
penDown = false;
ctx.closePath();
}
}
function mouseMove(x, y) {
var deltaX, deltaY;
if (inMove) {
//deltaX = x - lastPos.x; // drag viewport
deltaX = lastPos.x - x; // drag frame buffer
//deltaY = y - lastPos.y; // drag viewport
deltaY = lastPos.y - y; // drag frame buffer
lastPos = {'x': x, 'y': y};
display.viewportChange(deltaX, deltaY);
return;
}
if (penDown) {
ctx.lineTo(x, y);
ctx.stroke();
}
}
function dirtyRedraw() {
if (inMove) {
// Wait for user to stop moving viewport
return;
}
var d = display.getCleanDirtyReset();
for (i = 0; i < d.dirtyBoxes.length; i++) {
//showBox(d.dirtyBoxes[i], "dirty[" + i + "]: ");
drawArea(d.dirtyBoxes[i]);
}
}
function drawArea(b) {
var data = [], pixel, x, y;
//message("draw "+b.x+","+b.y+" ("+b.w+","+b.h+")");
for (var i = 0; i < b.w; i++) {
x = b.x + i;
for (var j = 0; j < b.h; j++) {
y = b.y + j;
pixel = (j * b.w * 4 + i * 4);
data[pixel + 0] = ((x * y) / 13) % 256;
data[pixel + 1] = ((x * y) + 392) % 256;
data[pixel + 2] = ((x + y) + 256) % 256;
data[pixel + 3] = 255;
}
}
//message("i: " + i + ", j: " + j + ", pixel: " + pixel);
display.blitImage(b.x, b.y, b.w, b.h, data, 0);
}
function toggleMove() {
if (doMove) {
doMove = false;
$D('move-selector').style.backgroundColor = "";
$D('move-selector').style.color = "";
} else {
doMove = true;
$D('move-selector').style.backgroundColor = "black";
$D('move-selector').style.color = "lightgray";
}
}
function detectPad() {
var c = $D('canvas'), p = c.parentNode;
c.width = 10;
c.height = 10;
padW = c.offsetWidth - 10;
padH = c.offsetHeight - 10;
message("padW: " + padW + ", padH: " + padH);
}
function doResize() {
var p = $D('canvas').parentNode;
message("doResize1: [" + (p.offsetWidth - padW) +
"," + (p.offsetHeight - padH) + "]");
display.viewportChange(0, 0,
p.offsetWidth - padW, p.offsetHeight - padH);
/*
var pos, new_w, new_h;pos
pos = Util.getPosition($D('canvas').parentNode);
new_w = window.innerWidth - pos.x;
new_h = window.innerHeight - pos.y;
display.viewportChange(0, 0, new_w, new_h);
*/
}
window.onload = function() {
detectPad();
display = new Display({'target': $D('canvas')});
display.resize(1600, 1024);
display.set_viewport(true);
ctx = display.get_context();
mouse = new Mouse({'target': $D('canvas'),
'onMouseButton': mouseButton,
'onMouseMove': mouseMove});
mouse.grab();
Util.addEvent(window, 'resize', doResize);
// Shrink viewport for first resize call so that the
// scrollbars are disabled
display.viewportChange(0, 0, 10, 10);
setTimeout(doResize, 1);
setInterval(dirtyRedraw, 50);
message("Display initialized");
};
</script>
</html>
+5 -3
View File
@@ -2,7 +2,7 @@
<html> <html>
<head> <head>
<title>VNC Performance Benchmark</title> <title>VNC Performance Benchmark</title>
<link rel="stylesheet" href="../include/plain.css"> <link rel="stylesheet" href="../include/base.css">
</head> </head>
<body> <body>
@@ -41,7 +41,7 @@
</script> </script>
<script src="../include/vnc.js"></script> <script src="../include/vnc.js"></script>
<script src="../include/playback.js"></script> <script src="../include/playback.js"></script>
<script src="data/multi.js"></script> <script src="../data/multi.js"></script>
<script> <script>
var start_time, VNC_frame_data, pass, passes, encIdx, var start_time, VNC_frame_data, pass, passes, encIdx,
@@ -126,7 +126,9 @@
pass += 1; pass += 1;
if (pass > passes) { if (pass > passes) {
// We are finished // We are finished
rfb.get_canvas().stop(); // Shut-off event interception // Shut-off event interception
rfb.get_mouse().ungrab();
rfb.get_keyboard().ungrab();
$D('startButton').disabled = false; $D('startButton').disabled = false;
$D('startButton').value = "Start"; $D('startButton').value = "Start";
finish_passes(); finish_passes();
+1 -1
View File
@@ -60,7 +60,7 @@ done
which netstat >/dev/null 2>&1 \ which netstat >/dev/null 2>&1 \
|| die "Must have netstat installed" || die "Must have netstat installed"
netstat -ltn | grep -qs "${PORT}.*LISTEN" \ netstat -ltn | grep -qs "${PORT} .*LISTEN" \
&& die "Port ${PORT} in use. Try --listen PORT" && die "Port ${PORT} in use. Try --listen PORT"
trap "cleanup" TERM QUIT INT EXIT trap "cleanup" TERM QUIT INT EXIT
+152
View File
@@ -0,0 +1,152 @@
#!/usr/bin/env python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright (c) 2012 Openstack, LLC.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#!/usr/bin/env python
'''
Websocket proxy that is compatible with Openstack Nova.
Leverages wsproxy.py by Joel Martin
'''
import Cookie
import socket
import sys
import wsproxy
from nova import context
from nova import flags
from nova import utils
from nova.openstack.common import cfg
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'),
]
FLAGS = flags.FLAGS
FLAGS.register_cli_opts(opts)
# As of nova commit 0b11668e64450039dc071a4a123abd02206f865f we must
# manually register the rpc library
if hasattr(rpc, 'register_opts'):
rpc.register_opts(FLAGS)
class NovaWebSocketProxy(wsproxy.WebSocketProxy):
def __init__(self, *args, **kwargs):
wsproxy.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 FLAGS.ssl_only and not os.path.exists(FLAGS.cert):
parser.error("SSL only and %s not found" % FLAGS.cert)
# Setup flags
flags.parse_args(sys.argv)
# Create and start the NovaWebSockets proxy
server = NovaWebSocketProxy(listen_host=FLAGS.novncproxy_host,
listen_port=FLAGS.novncproxy_port,
source_is_ipv6=FLAGS.source_is_ipv6,
verbose=FLAGS.verbose,
cert=FLAGS.cert,
key=FLAGS.key,
ssl_only=FLAGS.ssl_only,
daemon=FLAGS.daemon,
record=FLAGS.record,
web=FLAGS.web,
target_host='ignore',
target_port='ignore',
wrap_mode='exit',
wrap_cmd=None)
server.start_server()
+137 -77
View File
@@ -8,7 +8,7 @@ Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3)
Supports following protocol versions: Supports following protocol versions:
- http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75 - http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75
- http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76 - http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76
- http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-07 - http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10
You can make a cert/key with openssl using: You can make a cert/key with openssl using:
openssl req -new -x509 -days 365 -nodes -out self.pem -keyout self.pem openssl req -new -x509 -days 365 -nodes -out self.pem -keyout self.pem
@@ -16,47 +16,55 @@ as taken from http://docs.python.org/dev/library/ssl.html#certificates
''' '''
import os, sys, time, errno, signal, socket, struct, traceback, select import os, sys, time, errno, signal, socket, traceback, select
import array, struct
from cgi import parse_qsl from cgi import parse_qsl
from base64 import b64encode, b64decode from base64 import b64encode, b64decode
# Imports that vary by python version # Imports that vary by python version
# python 3.0 differences
if sys.hexversion > 0x3000000: if sys.hexversion > 0x3000000:
# python >= 3.0
from io import StringIO
from http.server import SimpleHTTPRequestHandler
from urllib.parse import urlsplit
b2s = lambda buf: buf.decode('latin_1') b2s = lambda buf: buf.decode('latin_1')
s2b = lambda s: s.encode('latin_1') s2b = lambda s: s.encode('latin_1')
s2a = lambda s: s
else: else:
# python 2.X b2s = lambda buf: buf # No-op
from cStringIO import StringIO s2b = lambda s: s # No-op
from SimpleHTTPServer import SimpleHTTPRequestHandler s2a = lambda s: [ord(c) for c in s]
from urlparse import urlsplit try: from io import StringIO
# No-ops except: from cStringIO import StringIO
b2s = lambda buf: buf try: from http.server import SimpleHTTPRequestHandler
s2b = lambda s: s except: from SimpleHTTPServer import SimpleHTTPRequestHandler
try: from urllib.parse import urlsplit
except: from urlparse import urlsplit
if sys.hexversion >= 0x2060000: # python 2.6 differences
# python >= 2.6 try: from hashlib import md5, sha1
from multiprocessing import Process except: from md5 import md5; from sha import sha as sha1
from hashlib import md5, sha1
else: # python 2.5 differences
# python < 2.6 try:
Process = None from struct import pack, unpack_from
from md5 import md5 except:
from sha import sha as sha1 from struct import pack
def unpack_from(fmt, buf, offset=0):
slice = buffer(buf, offset, struct.calcsize(fmt))
return struct.unpack(fmt, slice)
# Degraded functionality if these imports are missing # Degraded functionality if these imports are missing
for mod, sup in [('numpy', 'HyBi protocol'), for mod, sup in [('numpy', 'HyBi protocol'), ('ssl', 'TLS/SSL/wss'),
('ctypes', 'HyBi protocol'), ('ssl', 'TLS/SSL/wss'), ('multiprocessing', 'Multi-Processing'),
('resource', 'daemonizing')]: ('resource', 'daemonizing')]:
try: try:
globals()[mod] = __import__(mod) globals()[mod] = __import__(mod)
except ImportError: except ImportError:
globals()[mod] = None globals()[mod] = None
print("WARNING: no '%s' module, %s support disabled" % ( print("WARNING: no '%s' module, %s is slower or disabled" % (
mod, sup)) mod, sup))
if multiprocessing and sys.platform == 'win32':
# make sockets pickle-able/inheritable
import multiprocessing.reduction
class WebSocketServer(object): class WebSocketServer(object):
@@ -84,12 +92,18 @@ Sec-WebSocket-Accept: %s\r
policy_response = """<cross-domain-policy><allow-access-from domain="*" to-ports="*" /></cross-domain-policy>\n""" policy_response = """<cross-domain-policy><allow-access-from domain="*" to-ports="*" /></cross-domain-policy>\n"""
# An exception before the WebSocket connection was established
class EClose(Exception): class EClose(Exception):
pass pass
# An exception while the WebSocket client was connected
class CClose(Exception):
pass
def __init__(self, listen_host='', listen_port=None, source_is_ipv6=False, def __init__(self, listen_host='', listen_port=None, source_is_ipv6=False,
verbose=False, cert='', key='', ssl_only=None, verbose=False, cert='', key='', ssl_only=None,
daemon=False, record='', web=''): daemon=False, record='', web='',
run_once=False, timeout=0):
# settings # settings
self.verbose = verbose self.verbose = verbose
@@ -97,6 +111,11 @@ Sec-WebSocket-Accept: %s\r
self.listen_port = listen_port self.listen_port = listen_port
self.ssl_only = ssl_only self.ssl_only = ssl_only
self.daemon = daemon self.daemon = daemon
self.run_once = run_once
self.timeout = timeout
self.launch_time = time.time()
self.ws_connection = False
self.handler_id = 1 self.handler_id = 1
# Make paths settings absolute # Make paths settings absolute
@@ -208,6 +227,38 @@ Sec-WebSocket-Accept: %s\r
os.dup2(os.open(os.devnull, os.O_RDWR), sys.stdout.fileno()) os.dup2(os.open(os.devnull, os.O_RDWR), sys.stdout.fileno())
os.dup2(os.open(os.devnull, os.O_RDWR), sys.stderr.fileno()) os.dup2(os.open(os.devnull, os.O_RDWR), sys.stderr.fileno())
@staticmethod
def unmask(buf, f):
pstart = f['hlen'] + 4
pend = pstart + f['length']
if numpy:
b = c = s2b('')
if f['length'] >= 4:
mask = numpy.frombuffer(buf, dtype=numpy.dtype('<u4'),
offset=f['hlen'], count=1)
data = numpy.frombuffer(buf, dtype=numpy.dtype('<u4'),
offset=pstart, count=int(f['length'] / 4))
#b = numpy.bitwise_xor(data, mask).data
b = numpy.bitwise_xor(data, mask).tostring()
if f['length'] % 4:
#print("Partial unmask")
mask = numpy.frombuffer(buf, dtype=numpy.dtype('B'),
offset=f['hlen'], count=(f['length'] % 4))
data = numpy.frombuffer(buf, dtype=numpy.dtype('B'),
offset=pend - (f['length'] % 4),
count=(f['length'] % 4))
c = numpy.bitwise_xor(data, mask).tostring()
return b + c
else:
# Slower fallback
data = array.array('B')
mask = s2a(f['mask'])
data.fromstring(buf[pstart:pend])
for i in range(len(data)):
data[i] ^= mask[i % 4]
return data.tostring()
@staticmethod @staticmethod
def encode_hybi(buf, opcode, base64=False): def encode_hybi(buf, opcode, base64=False):
""" Encode a HyBi style WebSocket frame. """ Encode a HyBi style WebSocket frame.
@@ -225,11 +276,11 @@ Sec-WebSocket-Accept: %s\r
b1 = 0x80 | (opcode & 0x0f) # FIN + opcode b1 = 0x80 | (opcode & 0x0f) # FIN + opcode
payload_len = len(buf) payload_len = len(buf)
if payload_len <= 125: if payload_len <= 125:
header = struct.pack('>BB', b1, payload_len) header = pack('>BB', b1, payload_len)
elif payload_len > 125 and payload_len <= 65536: elif payload_len > 125 and payload_len < 65536:
header = struct.pack('>BBH', b1, 126, payload_len) header = pack('>BBH', b1, 126, payload_len)
elif payload_len >= 65536: elif payload_len >= 65536:
header = struct.pack('>BBQ', b1, 127, payload_len) header = pack('>BBQ', b1, 127, payload_len)
#print("Encoded: %s" % repr(header + buf)) #print("Encoded: %s" % repr(header + buf))
@@ -257,8 +308,8 @@ Sec-WebSocket-Accept: %s\r
'length' : 0, 'length' : 0,
'payload' : None, 'payload' : None,
'left' : 0, 'left' : 0,
'close_code' : None, 'close_code' : 1000,
'close_reason' : None} 'close_reason' : ''}
blen = len(buf) blen = len(buf)
f['left'] = blen f['left'] = blen
@@ -266,7 +317,7 @@ Sec-WebSocket-Accept: %s\r
if blen < f['hlen']: if blen < f['hlen']:
return f # Incomplete frame header return f # Incomplete frame header
b1, b2 = struct.unpack_from(">BB", buf) b1, b2 = unpack_from(">BB", buf)
f['opcode'] = b1 & 0x0f f['opcode'] = b1 & 0x0f
f['fin'] = (b1 & 0x80) >> 7 f['fin'] = (b1 & 0x80) >> 7
has_mask = (b2 & 0x80) >> 7 has_mask = (b2 & 0x80) >> 7
@@ -277,12 +328,12 @@ Sec-WebSocket-Accept: %s\r
f['hlen'] = 4 f['hlen'] = 4
if blen < f['hlen']: if blen < f['hlen']:
return f # Incomplete frame header return f # Incomplete frame header
(f['length'],) = struct.unpack_from('>xxH', buf) (f['length'],) = unpack_from('>xxH', buf)
elif f['length'] == 127: elif f['length'] == 127:
f['hlen'] = 10 f['hlen'] = 10
if blen < f['hlen']: if blen < f['hlen']:
return f # Incomplete frame header return f # Incomplete frame header
(f['length'],) = struct.unpack_from('>xxQ', buf) (f['length'],) = unpack_from('>xxQ', buf)
full_len = f['hlen'] + has_mask * 4 + f['length'] full_len = f['hlen'] + has_mask * 4 + f['length']
@@ -296,24 +347,7 @@ Sec-WebSocket-Accept: %s\r
if has_mask: if has_mask:
# unmask payload # unmask payload
f['mask'] = buf[f['hlen']:f['hlen']+4] f['mask'] = buf[f['hlen']:f['hlen']+4]
b = c = '' f['payload'] = WebSocketServer.unmask(buf, f)
if f['length'] >= 4:
mask = numpy.frombuffer(buf, dtype=numpy.dtype('<L4'),
offset=f['hlen'], count=1)
data = numpy.frombuffer(buf, dtype=numpy.dtype('<L4'),
offset=f['hlen'] + 4, count=int(f['length'] / 4))
#b = numpy.bitwise_xor(data, mask).data
b = numpy.bitwise_xor(data, mask).tostring()
if f['length'] % 4:
print("Partial unmask")
mask = numpy.frombuffer(buf, dtype=numpy.dtype('B'),
offset=f['hlen'], count=(f['length'] % 4))
data = numpy.frombuffer(buf, dtype=numpy.dtype('B'),
offset=full_len - (f['length'] % 4),
count=(f['length'] % 4))
c = numpy.bitwise_xor(data, mask).tostring()
f['payload'] = b + c
else: else:
print("Unmasked frame: %s" % repr(buf)) print("Unmasked frame: %s" % repr(buf))
f['payload'] = buf[(f['hlen'] + has_mask * 4):full_len] f['payload'] = buf[(f['hlen'] + has_mask * 4):full_len]
@@ -328,7 +362,7 @@ Sec-WebSocket-Accept: %s\r
if f['opcode'] == 0x08: if f['opcode'] == 0x08:
if f['length'] >= 2: if f['length'] >= 2:
f['close_code'] = struct.unpack_from(">H", f['payload']) f['close_code'] = unpack_from(">H", f['payload'])[0]
if f['length'] > 3: if f['length'] > 3:
f['close_reason'] = f['payload'][2:] f['close_reason'] = f['payload'][2:]
@@ -358,7 +392,7 @@ Sec-WebSocket-Accept: %s\r
num1 = int("".join([c for c in key1 if c.isdigit()])) / spaces1 num1 = int("".join([c for c in key1 if c.isdigit()])) / spaces1
num2 = int("".join([c for c in key2 if c.isdigit()])) / spaces2 num2 = int("".join([c for c in key2 if c.isdigit()])) / spaces2
return b2s(md5(struct.pack('>II8s', return b2s(md5(pack('>II8s',
int(num1), int(num2), key3)).digest()) int(num1), int(num2), key3)).digest())
# #
@@ -441,7 +475,7 @@ Sec-WebSocket-Accept: %s\r
buf = self.client.recv(self.buffer_size) buf = self.client.recv(self.buffer_size)
if len(buf) == 0: if len(buf) == 0:
closed = "Client closed abruptly" closed = {'code': 1000, 'reason': "Client closed abruptly"}
return bufs, closed return bufs, closed
if self.recv_part: if self.recv_part:
@@ -463,17 +497,17 @@ Sec-WebSocket-Accept: %s\r
break break
else: else:
if frame['opcode'] == 0x8: # connection close if frame['opcode'] == 0x8: # connection close
closed = "Client closed, reason: %s - %s" % ( closed = {'code': frame['close_code'],
frame['close_code'], 'reason': frame['close_reason']}
frame['close_reason'])
break break
else: else:
if buf[0:2] == '\xff\x00': if buf[0:2] == s2b('\xff\x00'):
closed = "Client sent orderly close frame" closed = {'code': 1000,
'reason': "Client sent orderly close frame"}
break break
elif buf[0:2] == '\x00\xff': elif buf[0:2] == s2b('\x00\xff'):
buf = buf[2:] buf = buf[2:]
continue # No-op continue # No-op
@@ -503,13 +537,11 @@ Sec-WebSocket-Accept: %s\r
return bufs, closed return bufs, closed
def send_close(self, code=None, reason=''): def send_close(self, code=1000, reason=''):
""" Send a WebSocket orderly close frame. """ """ Send a WebSocket orderly close frame. """
if self.version.startswith("hybi"): if self.version.startswith("hybi"):
msg = s2b('') msg = pack(">H%ds" % len(reason), code, reason)
if code != None:
msg = struct.pack(">H%ds" % (len(reason)), code)
buf, h, t = self.encode_hybi(msg, opcode=0x08, base64=False) buf, h, t = self.encode_hybi(msg, opcode=0x08, base64=False)
self.client.send(buf) self.client.send(buf)
@@ -557,7 +589,7 @@ Sec-WebSocket-Accept: %s\r
sock.send(s2b(self.policy_response)) sock.send(s2b(self.policy_response))
raise self.EClose("Sending flash policy response") raise self.EClose("Sending flash policy response")
elif handshake[0] in ("\x16", "\x80"): elif handshake[0] in ("\x16", "\x80", 22, 128):
# SSL wrap the connection # SSL wrap the connection
if not ssl: if not ssl:
raise self.EClose("SSL connection but no 'ssl' module") raise self.EClose("SSL connection but no 'ssl' module")
@@ -574,7 +606,10 @@ Sec-WebSocket-Accept: %s\r
except ssl.SSLError: except ssl.SSLError:
_, x, _ = sys.exc_info() _, x, _ = sys.exc_info()
if x.args[0] == ssl.SSL_ERROR_EOF: if x.args[0] == ssl.SSL_ERROR_EOF:
raise self.EClose("") if len(x.args) > 1:
raise self.EClose(x.args[1])
else:
raise self.EClose("Got SSL_ERROR_EOF")
else: else:
raise raise
@@ -612,11 +647,11 @@ Sec-WebSocket-Accept: %s\r
if ver: if ver:
# HyBi/IETF version of the protocol # HyBi/IETF version of the protocol
if sys.hexversion < 0x2060000 or not numpy: # HyBi-07 report version 7
raise self.EClose("Python >= 2.6 and numpy module is required for HyBi-07 or greater") # HyBi-08 - HyBi-12 report version 8
# HyBi-13 reports version 13
if ver in ['7', '8', '9']: if ver in ['7', '8', '13']:
self.version = "hybi-0" + ver self.version = "hybi-%02d" % int(ver)
else: else:
raise self.EClose('Unsupported protocol version %s' % ver) raise self.EClose('Unsupported protocol version %s' % ver)
@@ -633,7 +668,7 @@ Sec-WebSocket-Accept: %s\r
# Generate the hash value for the accept header # Generate the hash value for the accept header
accept = b64encode(sha1(s2b(key + self.GUID)).digest()) accept = b64encode(sha1(s2b(key + self.GUID)).digest())
response = self.server_handshake_hybi % accept response = self.server_handshake_hybi % b2s(accept)
if self.base64: if self.base64:
response += "Sec-WebSocket-Protocol: base64\r\n" response += "Sec-WebSocket-Protocol: base64\r\n"
else: else:
@@ -667,6 +702,9 @@ Sec-WebSocket-Accept: %s\r
self.msg("%s: %s WebSocket connection" % (address[0], stype)) self.msg("%s: %s WebSocket connection" % (address[0], stype))
self.msg("%s: Version %s, base64: '%s'" % (address[0], self.msg("%s: Version %s, base64: '%s'" % (address[0],
self.version, self.base64)) self.version, self.base64))
if self.path != '/':
self.msg("%s: Path: '%s'" % (address[0], self.path))
# Send server WebSockets handshake response # Send server WebSockets handshake response
#self.msg("sending response [%s]" % response) #self.msg("sending response [%s]" % response)
@@ -725,7 +763,13 @@ Sec-WebSocket-Accept: %s\r
self.rec = open(fname, 'w+') self.rec = open(fname, 'w+')
self.rec.write("var VNC_frame_data = [\n") self.rec.write("var VNC_frame_data = [\n")
self.ws_connection = True
self.new_client() self.new_client()
except self.CClose:
# Close the client
_, exc, _ = sys.exc_info()
if self.client:
self.send_close(exc.args[0], exc.args[1])
except self.EClose: except self.EClose:
_, exc, _ = sys.exc_info() _, exc, _ = sys.exc_info()
# Connection was not a WebSockets connection # Connection was not a WebSockets connection
@@ -742,6 +786,8 @@ Sec-WebSocket-Accept: %s\r
self.rec.close() self.rec.close()
if self.client and self.client != startsock: if self.client and self.client != startsock:
# Close the SSL wrapped socket
# Original socket closed by caller
self.client.close() self.client.close()
def new_client(self): def new_client(self):
@@ -764,7 +810,7 @@ Sec-WebSocket-Accept: %s\r
# Allow override of SIGINT # Allow override of SIGINT
signal.signal(signal.SIGINT, self.do_SIGINT) signal.signal(signal.SIGINT, self.do_SIGINT)
if not Process: if not multiprocessing:
# os.fork() (python 2.4) child reaper # os.fork() (python 2.4) child reaper
signal.signal(signal.SIGCHLD, self.fallback_SIGCHLD) signal.signal(signal.SIGCHLD, self.fallback_SIGCHLD)
@@ -775,6 +821,12 @@ Sec-WebSocket-Accept: %s\r
startsock = None startsock = None
pid = err = 0 pid = err = 0
time_elapsed = time.time() - self.launch_time
if self.timeout and time_elapsed > self.timeout:
self.msg('listener exit due to --timeout %s'
% self.timeout)
break
try: try:
self.poll() self.poll()
@@ -797,9 +849,17 @@ Sec-WebSocket-Accept: %s\r
else: else:
raise raise
if Process: if self.run_once:
# Run in same process if run_once
self.top_new_client(startsock, address)
if self.ws_connection :
self.msg('%s: exiting due to --run-once'
% address[0])
break
elif multiprocessing:
self.vmsg('%s: new handler Process' % address[0]) self.vmsg('%s: new handler Process' % address[0])
p = Process(target=self.top_new_client, p = multiprocessing.Process(
target=self.top_new_client,
args=(startsock, address)) args=(startsock, address))
p.start() p.start()
# child will not return # child will not return
+13 -6
View File
@@ -190,7 +190,8 @@ Traffic Legend:
if target in ins: if target in ins:
# Receive target data, encode it and queue for client # Receive target data, encode it and queue for client
buf = target.recv(self.buffer_size) buf = target.recv(self.buffer_size)
if len(buf) == 0: raise self.EClose("Target closed") if len(buf) == 0:
raise self.CClose(1000, "Target closed")
cqueue.append(buf) cqueue.append(buf)
self.traffic("{") self.traffic("{")
@@ -210,10 +211,9 @@ Traffic Legend:
if closed: if closed:
# TODO: What about blocking on client socket? # TODO: What about blocking on client socket?
self.send_close() raise self.CClose(closed['code'], closed['reason'])
raise self.EClose(closed)
if __name__ == '__main__': def websockify_init():
usage = "\n %prog [options]" usage = "\n %prog [options]"
usage += " [source_addr:]source_port target_addr:target_port" usage += " [source_addr:]source_port target_addr:target_port"
usage += "\n %prog [options]" usage += "\n %prog [options]"
@@ -226,6 +226,10 @@ if __name__ == '__main__':
parser.add_option("--daemon", "-D", parser.add_option("--daemon", "-D",
dest="daemon", action="store_true", dest="daemon", action="store_true",
help="become a daemon (background process)") 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("--cert", default="self.pem", parser.add_option("--cert", default="self.pem",
help="SSL certificate file") help="SSL certificate file")
parser.add_option("--key", default=None, parser.add_option("--key", default=None,
@@ -255,7 +259,7 @@ if __name__ == '__main__':
# Parse host:port and convert ports to numbers # Parse host:port and convert ports to numbers
if args[0].count(':') > 0: if args[0].count(':') > 0:
opts.listen_host, sep, opts.listen_port = args[0].rpartition(':') opts.listen_host, opts.listen_port = args[0].rsplit(':', 1)
else: else:
opts.listen_host, opts.listen_port = '', args[0] opts.listen_host, opts.listen_port = '', args[0]
@@ -267,7 +271,7 @@ if __name__ == '__main__':
opts.target_port = None opts.target_port = None
else: else:
if args[1].count(':') > 0: if args[1].count(':') > 0:
opts.target_host, sep, opts.target_port = args[1].rpartition(':') opts.target_host, opts.target_port = args[1].rsplit(':', 1)
else: else:
parser.error("Error parsing target") parser.error("Error parsing target")
try: opts.target_port = int(opts.target_port) try: opts.target_port = int(opts.target_port)
@@ -276,3 +280,6 @@ if __name__ == '__main__':
# Create and start the WebSockets proxy # Create and start the WebSockets proxy
server = WebSocketProxy(**opts.__dict__) server = WebSocketProxy(**opts.__dict__)
server.start_server() server.start_server()
if __name__ == '__main__':
websockify_init()
+1
View File
@@ -0,0 +1 @@
websockify
+193 -24
View File
@@ -1,31 +1,200 @@
<!DOCTYPE html> <!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.1//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile11.dtd">
<html> <html>
<!-- <head>
<!--
noVNC example: simple example using default UI noVNC example: simple example using default UI
Copyright (C) 2011 Joel Martin Copyright (C) 2012 Joel Martin
Licensed under LGPL-3 (see LICENSE.txt) noVNC is licensed under the LGPL-3 (see LICENSE.txt)
This file is licensed under the 2-Clause BSD license (see LICENSE.txt).
--> -->
<head> <title>noVNC</title>
<title>noVNC</title>
<meta http-equiv="X-UA-Compatible" content="chrome=1">
<link rel="stylesheet" href="include/plain.css">
<link rel="alternate stylesheet" href="include/black.css" TITLE="Black">
<!--
<script type='text/javascript'
src='http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js'></script>
-->
<script src="include/vnc.js"></script>
<script src="include/ui.js"></script>
</head>
<body> <meta charset="utf-8">
<div id='vnc'>Loading</div>
<script> <!-- Always force latest IE rendering engine (even in intranet) & Chrome Frame
window.onload = function () { Remove this if you use the .htaccess -->
UI.load('vnc'); <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
};
</script>
</body> <!-- Apple iOS Safari settings -->
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta names="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<!-- App Start Icon -->
<link rel="apple-touch-startup-image" href="images/screen_320x460.png" />
<!-- For iOS devices set the icon to use if user bookmarks app on their homescreen -->
<link rel="apple-touch-icon" href="images/screen_57x57.png">
<!--
<link rel="apple-touch-icon-precomposed" href="images/screen_57x57.png" />
-->
<!-- Stylesheets -->
<link rel="stylesheet" href="include/base.css" />
<link rel="alternate stylesheet" href="include/black.css" TITLE="Black" />
<link rel="alternate stylesheet" href="include/blue.css" TITLE="Blue" />
<!--
<script type='text/javascript'
src='http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js'></script>
-->
<script src="include/vnc.js"></script>
<script src="include/ui.js"></script>
</head>
<body>
<div id="noVNC-control-bar">
<!--noVNC Mobile Device only Buttons-->
<div class="noVNC-buttons-left">
<input type="image" src="images/drag.png"
id="noVNC_view_drag_button" class="noVNC_status_button"
title="Move/Drag Viewport"
onclick="UI.setViewDrag();">
<div id="noVNC_mobile_buttons">
<input type="image" src="images/mouse_none.png"
id="noVNC_mouse_button0" class="noVNC_status_button"
onclick="UI.setMouseButton(1);">
<input type="image" src="images/mouse_left.png"
id="noVNC_mouse_button1" class="noVNC_status_button"
onclick="UI.setMouseButton(2);">
<input type="image" src="images/mouse_middle.png"
id="noVNC_mouse_button2" class="noVNC_status_button"
onclick="UI.setMouseButton(4);">
<input type="image" src="images/mouse_right.png"
id="noVNC_mouse_button4" class="noVNC_status_button"
onclick="UI.setMouseButton(0);">
<input type="image" src="images/keyboard.png"
id="showKeyboard" class="noVNC_status_button"
value="Keyboard" title="Show Keyboard"
onclick="UI.showKeyboard()"/>
<input type="email"
autocapitalize="off" autocorrect="off"
id="keyboardinput" class="noVNC_status_button"
onKeyDown="onKeyDown(event);" onblur="UI.keyInputBlur();"/>
</div>
</div>
<!--noVNC Buttons-->
<div class="noVNC-buttons-right">
<input type="image" src="images/ctrlaltdel.png"
id="sendCtrlAltDelButton" class="noVNC_status_button"
title="Send Ctrl-Alt-Del"
onclick="UI.sendCtrlAltDel();" />
<input type="image" src="images/clipboard.png"
id="clipboardButton" class="noVNC_status_button"
title="Clipboard"
onclick="UI.toggleClipboardPanel();" />
<input type="image" src="images/settings.png"
id="settingsButton" class="noVNC_status_button"
title="Settings"
onclick="UI.toggleSettingsPanel();" />
<input type="image" src="images/connect.png"
id="connectButton" class="noVNC_status_button"
title="Connect"
onclick="UI.toggleConnectPanel()" />
<input type="image" src="images/disconnect.png"
id="disconnectButton" class="noVNC_status_button"
title="Disconnect"
onclick="UI.disconnect()" />
</div>
<!-- Description Panel -->
<!-- Shown by default when hosted at for kanaka.github.com -->
<div id="noVNC_description" style="display:none;" class="">
noVNC is a browser based VNC client implemented using HTML5 Canvas
and WebSockets. You will either need a VNC server with WebSockets
support (such as <a href="http://libvncserver.sourceforge.net/">libvncserver</a>)
or you will need to use
<a href="https://github.com/kanaka/websockify">websockify</a>
to bridge between your browser and VNC server. See the noVNC
<a href="https://github.com/kanaka/noVNC">README</a>
and <a href="http://kanaka.github.com/noVNC">website</a>
for more information.
<br />
<input type="button" value="Close"
onclick="UI.toggleConnectPanel();">
</div>
<!-- Clipboard Panel -->
<div id="noVNC_clipboard" class="triangle-right top">
<textarea id="noVNC_clipboard_text" rows=5
onfocus="UI.displayBlur();" onblur="UI.displayFocus();"
onchange="UI.clipSend();">
</textarea>
<br />
<input id="noVNC_clipboard_clear_button" type="button"
value="Clear" onclick="UI.clipClear();">
</div>
<!-- Settings Panel -->
<div id="noVNC_settings" class="triangle-right top">
<span id="noVNC_settings_menu" onmouseover="UI.displayBlur();"
onmouseout="UI.displayFocus();">
<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_connectTimeout" type="input"> Connect Timeout (s)</li>
<li><input id="noVNC_path" type="input" value="websockify"> Path</li>
<li><input id="noVNC_repeaterID" type="input" value=""> Repeater ID</li>
<hr>
<!-- Stylesheet selection dropdown -->
<li><label><strong>Style: </strong>
<select id="noVNC_stylesheet" name="vncStyle">
<option value="default">default</option>
</select></label>
</li>
<!-- Logging selection dropdown -->
<li><label><strong>Logging: </strong>
<select id="noVNC_logging" name="vncLogging">
</select></label>
</li>
<hr>
<li><input type="button" id="noVNC_apply" value="Apply"
onclick="UI.settingsApply()"></li>
</ul>
</span>
</div>
<!-- Connection Panel -->
<div id="noVNC_controls" class="triangle-right top">
<ul>
<li><label><strong>Host: </strong><input id="noVNC_host" /></label></li>
<li><label><strong>Port: </strong><input id="noVNC_port" /></label></li>
<li><label><strong>Password: </strong><input id="noVNC_password" type="password" /></label></li>
<li><input id="noVNC_connect_button" type="button" value="Connect" onclick="UI.connect();"></li>
</ul>
</div>
</div> <!-- End of noVNC-control-bar -->
<div id="noVNC_screen">
<div id="noVNC_screen_pad"></div>
<div id="noVNC_status_bar" class="noVNC_status_bar">
<div id="noVNC_status">Loading</div>
</div>
<h1 id="noVNC_logo"><span>no</span><br />VNC</h1>
<!-- HTML5 Canvas -->
<div id="noVNC_container">
<canvas id="noVNC_canvas" width="640px" height="20px">
Canvas not supported.
</canvas>
</div>
</div>
<script>
window.onload = UI.load;
</script>
</body>
</html> </html>
+37 -21
View File
@@ -1,9 +1,10 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<!-- <!--
noVNC Example: Automatically connect on page load. noVNC example: simple example using default UI
Copyright (C) 2011 Joel Martin Copyright (C) 2012 Joel Martin
Licensed under LGPL-3 (see LICENSE.txt) noVNC is licensed under the LGPL-3 (see LICENSE.txt)
This file is licensed under the 2-Clause BSD license (see LICENSE.txt).
Connect parameters are provided in query string: Connect parameters are provided in query string:
http://example.com/?host=HOST&port=PORT&encrypt=1&true_color=1 http://example.com/?host=HOST&port=PORT&encrypt=1&true_color=1
@@ -11,7 +12,7 @@
<head> <head>
<title>noVNC</title> <title>noVNC</title>
<meta http-equiv="X-UA-Compatible" content="chrome=1"> <meta http-equiv="X-UA-Compatible" content="chrome=1">
<link rel="stylesheet" href="include/plain.css" title="plain"> <link rel="stylesheet" href="include/base.css" title="plain">
<!-- <!--
<script type='text/javascript' <script type='text/javascript'
src='http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js'></script> src='http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js'></script>
@@ -20,17 +21,17 @@
</head> </head>
<body style="margin: 0px;"> <body style="margin: 0px;">
<div id="VNC_screen"> <div id="noVNC_screen">
<div id="VNC_status_bar" class="VNC_status_bar" style="margin-top: 0px;"> <div id="noVNC_status_bar" class="noVNC_status_bar" style="margin-top: 0px;">
<table border=0 width="100%"><tr> <table border=0 width="100%"><tr>
<td><div id="VNC_status">Loading</div></td> <td><div id="noVNC_status">Loading</div></td>
<td width="1%"><div id="VNC_buttons"> <td width="1%"><div id="noVNC_buttons">
<input type=button value="Send CtrlAltDel" <input type=button value="Send CtrlAltDel"
id="sendCtrlAltDelButton"> id="sendCtrlAltDelButton">
</div></td> </div></td>
</tr></table> </tr></table>
</div> </div>
<canvas id="VNC_canvas" width="640px" height="20px"> <canvas id="noVNC_canvas" width="640px" height="20px">
Canvas not supported. Canvas not supported.
</canvas> </canvas>
</div> </div>
@@ -47,10 +48,10 @@
msg = '<form onsubmit="return setPassword();"'; msg = '<form onsubmit="return setPassword();"';
msg += ' style="margin-bottom: 0px">'; msg += ' style="margin-bottom: 0px">';
msg += 'Password Required: '; msg += 'Password Required: ';
msg += '<input type=password size=10 id="password_input" class="VNC_status">'; msg += '<input type=password size=10 id="password_input" class="noVNC_status">';
msg += '<\/form>'; msg += '<\/form>';
$D('VNC_status_bar').setAttribute("class", "VNC_status_warn"); $D('noVNC_status_bar').setAttribute("class", "noVNC_status_warn");
$D('VNC_status').innerHTML = msg; $D('noVNC_status').innerHTML = msg;
} }
function setPassword() { function setPassword() {
rfb.sendPassword($D('password_input').value); rfb.sendPassword($D('password_input').value);
@@ -62,8 +63,8 @@
} }
function updateState(rfb, state, oldstate, msg) { function updateState(rfb, state, oldstate, msg) {
var s, sb, cad, level; var s, sb, cad, level;
s = $D('VNC_status'); s = $D('noVNC_status');
sb = $D('VNC_status_bar'); sb = $D('noVNC_status_bar');
cad = $D('sendCtrlAltDelButton'); cad = $D('sendCtrlAltDelButton');
switch (state) { switch (state) {
case 'failed': level = "error"; break; case 'failed': level = "error"; break;
@@ -78,34 +79,49 @@
else { cad.disabled = true; } else { cad.disabled = true; }
if (typeof(msg) !== 'undefined') { if (typeof(msg) !== 'undefined') {
sb.setAttribute("class", "VNC_status_" + level); sb.setAttribute("class", "noVNC_status_" + level);
s.innerHTML = msg; s.innerHTML = msg;
} }
} }
window.onload = function () { window.onload = function () {
var host, port, password; var host, port, password, path, token;
$D('sendCtrlAltDelButton').style.display = "inline";
$D('sendCtrlAltDelButton').onclick = sendCtrlAltDel; $D('sendCtrlAltDelButton').onclick = sendCtrlAltDel;
document.title = unescape(WebUtil.getQueryVar('title', 'noVNC')); document.title = unescape(WebUtil.getQueryVar('title', 'noVNC'));
host = WebUtil.getQueryVar('host', null); // By default, use the host and port of server that served this file
port = WebUtil.getQueryVar('port', null); host = WebUtil.getQueryVar('host', window.location.hostname);
port = WebUtil.getQueryVar('port', window.location.port);
// If a token variable is passed in, set the parameter in a cookie.
// This is used by nova-novncproxy.
token = WebUtil.getQueryVar('token', null);
if (token) {
WebUtil.createCookie('token', token, 1)
}
password = WebUtil.getQueryVar('password', ''); password = WebUtil.getQueryVar('password', '');
path = WebUtil.getQueryVar('path', 'websockify');
if ((!host) || (!port)) { if ((!host) || (!port)) {
updateState('failed', updateState('failed',
"Must specify host and port in URL"); "Must specify host and port in URL");
return; return;
} }
rfb = new RFB({'target': $D('VNC_canvas'), rfb = new RFB({'target': $D('noVNC_canvas'),
'encrypt': WebUtil.getQueryVar('encrypt', false), 'encrypt': WebUtil.getQueryVar('encrypt',
(window.location.protocol === "https:")),
'repeaterID': WebUtil.getQueryVar('repeaterID', ''),
'true_color': WebUtil.getQueryVar('true_color', true), 'true_color': WebUtil.getQueryVar('true_color', true),
'local_cursor': WebUtil.getQueryVar('cursor', true), 'local_cursor': WebUtil.getQueryVar('cursor', true),
'shared': WebUtil.getQueryVar('shared', true), 'shared': WebUtil.getQueryVar('shared', true),
'view_only': WebUtil.getQueryVar('view_only', false),
'updateState': updateState, 'updateState': updateState,
'onPasswordRequired': passwordRequired}); 'onPasswordRequired': passwordRequired});
rfb.connect(host, port, password); rfb.connect(host, port, password, path);
}; };
</script> </script>