Compare commits
125 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| df4fa13b89 | |||
| 53dfab7fb7 | |||
| bc28395abf | |||
| a117514183 | |||
| 58ad83878c | |||
| 02d1f19b5f | |||
| 4910600b71 | |||
| ca78183171 | |||
| 901e565503 | |||
| 483157f87f | |||
| dcf1994d3e | |||
| c390011099 | |||
| f8380ff939 | |||
| f736c3316a | |||
| cbd3435a07 | |||
| bd96e91932 | |||
| 0139b2562c | |||
| 4c75210a4d | |||
| f84504bc63 | |||
| c76b3e4b26 | |||
| 6671c7624d | |||
| ce86f5c954 | |||
| c0c20581f5 | |||
| f2a495c944 | |||
| b8dd87c757 | |||
| d065cad99e | |||
| a09a75e8f2 | |||
| 4cd0070a1c | |||
| 35d7574b09 | |||
| d38db74abd | |||
| 0c4f4b598c | |||
| c6f3919cb0 | |||
| 3b302463f8 | |||
| 23ff806774 | |||
| 94bcf652b3 | |||
| 0ce93900ae | |||
| 9e6e6662c7 | |||
| 32f135d730 | |||
| 1af3e54bef | |||
| 6a02f88e4d | |||
| 9b75bcaada | |||
| a14b8fae2a | |||
| 2cedf48397 | |||
| c514fd5e1c | |||
| 5ca5e2d8cd | |||
| 6fbc37489f | |||
| b0ac240f31 | |||
| 523cc4d6ab | |||
| 0fa6748c52 | |||
| a820f1267a | |||
| c577ca2305 | |||
| de84e09854 | |||
| 682f33a790 | |||
| e79917c3db | |||
| a9a7f0e18c | |||
| ff4bfcb773 | |||
| f66bcda31b | |||
| 86ebeef709 | |||
| f2d856767d | |||
| 2fa565b375 | |||
| b688a909b0 | |||
| 06a9ef0c6c | |||
| c3a172b98c | |||
| aa67056785 | |||
| 91b0dc3915 | |||
| 4f545a5ba6 | |||
| 83ed7b8c25 | |||
| 1c92807a8e | |||
| bc71506553 | |||
| 2e7d81ce24 | |||
| fa4607dbf5 | |||
| a538be4350 | |||
| a367e84a92 | |||
| 1e50871599 | |||
| 65db2d8243 | |||
| b29e06f36e | |||
| f4bce78307 | |||
| 5299db1a00 | |||
| 938daad1c9 | |||
| 1709ee3ab7 | |||
| 479bfa9964 | |||
| a7f5589946 | |||
| 6209639f2c | |||
| 7cd6118ce5 | |||
| 490d471c53 | |||
| 859fc7f18f | |||
| 1310606305 | |||
| a7db50597c | |||
| c506a48154 | |||
| a5df24b488 | |||
| e0b23efe95 | |||
| 608e0f52ee | |||
| 7e5f81f255 | |||
| 832c744578 | |||
| b70ce07706 | |||
| 1c3df652de | |||
| 9192cbe39c | |||
| 54e7cbdf8f | |||
| afecb83988 | |||
| 6ea8bece9b | |||
| 01a9eee991 | |||
| 0911173a89 | |||
| 42e04907ff | |||
| d29c54c476 | |||
| 89a76c785c | |||
| a7eb596d89 | |||
| 8e0f008841 | |||
| c1e8b30877 | |||
| 2c6b8a481e | |||
| c327865b4b | |||
| 53fc7392bb | |||
| 1734b5e465 | |||
| 7825b9ee9f | |||
| 1150d0f70f | |||
| 7b10dc8a48 | |||
| 94568bc08d | |||
| fa74a6e60c | |||
| 48f26d799f | |||
| ec40268e55 | |||
| 46c621175c | |||
| 676fbbb662 | |||
| 4245363cf4 | |||
| ce3bdbccc2 | |||
| ad3f762409 | |||
| ac99a1f791 |
@@ -5,26 +5,31 @@ Please refer to the individual source files and/or to the noVNC commit
|
||||
history: https://github.com/kanaka/noVNC/commits/master
|
||||
|
||||
noVNC is licensed under the LGPL (GNU Lesser General Public License)
|
||||
version 3 with the following exceptions:
|
||||
version 3 with the following exceptions (all LGPL-3 compatible):
|
||||
|
||||
include/input.js : LGPL-2 or any later version
|
||||
|
||||
include/base64.js : Dual GPL-2 or LGPL-2.1
|
||||
|
||||
incluee/des.js : Various BSD style licenses
|
||||
include/des.js : Various BSD style licenses
|
||||
|
||||
include/jsunzip.js : zlib/libpng license
|
||||
|
||||
include/web-socket-js/ : New BSD license. Source code at
|
||||
http://github.com/gimite/web-socket-js
|
||||
|
||||
include/Orbitron* : SIL Open Font License 1.1
|
||||
(Copyright 2009 Matt McInerney)
|
||||
|
||||
images/ : Creative Commons Attribution-ShareAlike
|
||||
http://creativecommons.org/licenses/by-sa/3.0/
|
||||
Uses of the work must be attributed
|
||||
to the noVNC project.
|
||||
|
||||
The LGPL-3 license text is included at:
|
||||
The license texts are included at:
|
||||
docs/LICENSE.LGPL-3 and
|
||||
docs/LICENSE.GPL-3
|
||||
docs/LICENSE.OFL-1.1
|
||||
|
||||
Or alternatively the LGPL-3 license text may be found here:
|
||||
Or alternatively the license texts may be found here:
|
||||
http://www.gnu.org/licenses/lgpl.html and
|
||||
http://www.gnu.org/licenses/gpl.html
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
@@ -3,69 +3,62 @@
|
||||
|
||||
### Description
|
||||
|
||||
noVNC is a VNC client implemented using HTML5 technologies,
|
||||
specifically Canvas and WebSockets (supports 'wss://' encryption).
|
||||
noVNC is licensed under the
|
||||
[LGPLv3](http://www.gnu.org/licenses/lgpl.html).
|
||||
|
||||
Special thanks to [Sentry Data Systems](http://www.sentryds.com) for
|
||||
sponsoring ongoing development of this project (and for employing me).
|
||||
|
||||
There are many companies/projects that have integrated noVNC into
|
||||
their products including: [Sentry Data Systems](http://www.sentryds.com), [Ganeti Web Manager](http://code.osuosl.org/projects/ganeti-webmgr), [Archipel](http://archipelproject.org), [openQRM](http://www.openqrm.com/), [OpenNode](http://www.opennodecloud.com/), [OpenStack](http://www.openstack.org), [Broadway (HTML5 GDK/GTK+ backend)](http://blogs.gnome.org/alexl/2011/03/15/gtk-html-backend-update/), [OpenNebula](http://opennebula.org/) and [CloudSigma](http://www.cloudsigma.com/). See [this wiki page](https://github.com/kanaka/noVNC/wiki/ProjectsCompanies-using-noVNC) for more info and links.
|
||||
noVNC is a HTML5 VNC client that runs well in any modern browser
|
||||
including mobile browsers (iPhone/iPad and Android).
|
||||
|
||||
Notable commits, announcements and news are posted to
|
||||
@<a href="http://www.twitter.com/noVNC">noVNC</a>
|
||||
|
||||
There are many companies/projects that have integrated noVNC into
|
||||
their products including: [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/), [CloudSigma](http://www.cloudsigma.com/), [Zentyal (formerly eBox)](http://www.zentyal.org/), [SlapOS](http://www.slapos.org), [Intel MeshCentral](https://meshcentral.com), [Amahi](http://amahi.org), [Brightbox](http://brightbox.com/), [Foreman](http://theforeman.org) and [LibVNCServer](http://libvncserver.sourceforge.net). See [this wiki page](https://github.com/kanaka/noVNC/wiki/ProjectsCompanies-using-noVNC) for more info and links.
|
||||
|
||||
|
||||
### 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
|
||||
|
||||
Running in Chrome before and after connecting:
|
||||
|
||||
<img src="http://kanaka.github.com/noVNC/img/noVNC-1.jpg" width=400> <img src="http://kanaka.github.com/noVNC/img/noVNC-2.jpg" width=400>
|
||||
<img src="http://kanaka.github.com/noVNC/img/noVNC-5.png" width=400> <img src="http://kanaka.github.com/noVNC/img/noVNC-7.jpg" width=400>
|
||||
|
||||
See more screenshots <a href="http://kanaka.github.com/noVNC/screenshots.html">here</a>.
|
||||
|
||||
|
||||
### Browser Requirements
|
||||
|
||||
* HTML5 Canvas: Except for Internet Explorer, most
|
||||
browsers have had Canvas support for quite some time. Internet
|
||||
Explorer 9 will have Canvas support (finally).
|
||||
* HTML5 Canvas (with createImageData): Chrome, Firefox 3.6+, iOS
|
||||
Safari, Opera 11+, Internet Explorer 9+, etc.
|
||||
|
||||
* HTML5 WebSockets: For browsers that do not have builtin
|
||||
WebSockets support, the project includes
|
||||
<a href="http://github.com/gimite/web-socket-js">web-socket-js</a>,
|
||||
a WebSockets emulator using Adobe Flash.
|
||||
a WebSockets emulator using Adobe Flash. iOS 4.2+ has built-in
|
||||
WebSocket support.
|
||||
|
||||
* Fast Javascript Engine: noVNC avoids using new Javascript
|
||||
functionality so it will run on older browsers, but decode and
|
||||
rendering happen in Javascript, so a slow Javascript engine will
|
||||
mean noVNC is painfully slow.
|
||||
* Fast Javascript Engine: this is not strictly a requirement, but
|
||||
without a fast Javascript engine, noVNC might be painfully slow.
|
||||
|
||||
* I maintain a more detailed list of browser compatibility <a
|
||||
* I maintain a more detailed browser compatibility list <a
|
||||
href="https://github.com/kanaka/noVNC/wiki/Browser-support">here</a>.
|
||||
|
||||
|
||||
### Server Requirements
|
||||
|
||||
Unless you are using a VNC server with support for WebSockets
|
||||
connections (only my [fork of libvncserver](http://github.com/kanaka/libvncserver)
|
||||
currently), you need to use a WebSockets to TCP socket proxy. There is
|
||||
a python proxy included ('websockify'). One advantage of using the
|
||||
proxy is that it has builtin support for SSL/TLS encryption (i.e.
|
||||
"wss://").
|
||||
|
||||
There a few reasons why a proxy is required:
|
||||
|
||||
1. WebSockets is not a pure socket protocol. There is an initial HTTP
|
||||
like handshake to allow easy hand-off by web servers and allow
|
||||
some origin policy exchange. Also, each WebSockets frame begins
|
||||
with 0 ('\x00') and ends with 255 ('\xff').
|
||||
|
||||
2. Javascript itself does not have the ability to handle pure byte
|
||||
arrays. The python proxy encodes the data as base64 so that the
|
||||
Javascript client can decode the data as an integer array.
|
||||
connections (such as [x11vnc/libvncserver](http://libvncserver.sourceforge.net/)),
|
||||
you need to use a WebSockets to TCP socket proxy. There is
|
||||
a python proxy included ('websockify').
|
||||
|
||||
|
||||
### Quick Start
|
||||
@@ -91,3 +84,19 @@ There a few reasons why a proxy is required:
|
||||
* [Troubleshooting noVNC](https://github.com/kanaka/noVNC/wiki/Troubleshooting) problems.
|
||||
|
||||
|
||||
### Authors/Contributors
|
||||
|
||||
* noVNC : Joel Martin (github.com/kanaka)
|
||||
* New 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)
|
||||
|
||||
|
||||
|
||||
@@ -1,3 +1,25 @@
|
||||
novnc (0.3) maverick; urgency=low
|
||||
|
||||
* add tight encoding support
|
||||
* release pressed key when focus lost (fixes locked Alt key)
|
||||
* Support Apple Remote Desktop
|
||||
* Add nova/openstack proxy wrapper
|
||||
* Better connection close handling/reporting
|
||||
|
||||
-- Joel Martin <github@martintribe.org> Fri, 11 May 2012 03:00:00 -0600
|
||||
|
||||
novnc (0.2) maverick; urgency=low
|
||||
|
||||
* Mobile device support with viewport clipping
|
||||
* Much better styling that also works on mobile devices well
|
||||
* Update websockify to support latest WebSocket protocol HyBi 13
|
||||
(i.e. support IETF 6455)
|
||||
* Better support in websockify for python 2.4 through 3.2
|
||||
* Support VMWare ESX and Intel AMT KVM
|
||||
* View only mode
|
||||
|
||||
-- Joel Martin <github@martintribe.org> Tue, 05 Jul 2011 01:00:00 -0600
|
||||
|
||||
novnc (0.1) maverick; urgency=low
|
||||
|
||||
* First upstream release
|
||||
|
||||
@@ -9,14 +9,16 @@ utils/websockify /usr/share/novnc/utils
|
||||
utils/wsproxy.py /usr/share/novnc/utils
|
||||
utils/rebind.c /usr/share/novnc/utils
|
||||
utils/rebind.so /usr/share/novnc/utils
|
||||
images /usr/share/novnc
|
||||
images/favicon.ico /usr/share/novnc
|
||||
include/base64.js /usr/share/novnc/include
|
||||
include/black.css /usr/share/novnc/include
|
||||
include/des.js /usr/share/novnc/include
|
||||
include/display.js /usr/share/novnc/include
|
||||
include/input.js /usr/share/novnc/include
|
||||
include/logo.js /usr/share/novnc/include
|
||||
include/plain.css /usr/share/novnc/include
|
||||
include/base.css /usr/share/novnc/include
|
||||
include/blue.css /usr/share/novnc/include
|
||||
include/black.css /usr/share/novnc/include
|
||||
include/playback.js /usr/share/novnc/include
|
||||
include/rfb.js /usr/share/novnc/include
|
||||
include/ui.js /usr/share/novnc/include
|
||||
@@ -24,4 +26,5 @@ include/util.js /usr/share/novnc/include
|
||||
include/vnc.js /usr/share/novnc/include
|
||||
include/websock.js /usr/share/novnc/include
|
||||
include/webutil.js /usr/share/novnc/include
|
||||
include/jsunzip.js /usr/share/novnc/include
|
||||
include/web-socket-js/* /usr/share/novnc/include/web-socket-js
|
||||
|
||||
@@ -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.
|
||||
@@ -1,53 +1,19 @@
|
||||
Short Term:
|
||||
- Use requestAnimationFrame for frameBufferUpdate polling
|
||||
|
||||
- Test normal arrays vs TypedArrays vs imageData arrays.
|
||||
- Blog post about it.
|
||||
- Use WebSocket binary API when available.
|
||||
|
||||
- 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
|
||||
- Scaling support
|
||||
|
||||
- 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
|
||||
- Keyboard menu/panel:
|
||||
- Move CtrlAltDel button here
|
||||
- direct text entry with Send/Paste button
|
||||
- Ctrl Once/Lock, Alt Once/Lock, SysRq Once/Lock
|
||||
- Highlight menu icon when keys are locked
|
||||
|
||||
- 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.
|
||||
- zlib/tight support (even if slow)
|
||||
|
||||
@@ -1 +1 @@
|
||||
0.1
|
||||
0.3
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
- Update and commit docs/VERSION and debian/changelog
|
||||
- Create version tag and tarball from tag
|
||||
WVER=0.3
|
||||
git tag ${WVER}
|
||||
git archive --format=tar --prefix=novnc-${WVER}/ v${WVER} > novnc-${WVER}.tar
|
||||
gzip novnc-${WVER}.tar
|
||||
- Upload tarball to repo
|
||||
@@ -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 |
|
After Width: | Height: | Size: 501 B |
|
After Width: | Height: | Size: 404 B |
|
After Width: | Height: | Size: 317 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 963 B |
|
Before Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 511 B |
|
After Width: | Height: | Size: 517 B |
|
After Width: | Height: | Size: 497 B |
|
After Width: | Height: | Size: 513 B |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
@@ -0,0 +1,398 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -116,7 +116,7 @@ decode: function (data, offset) {
|
||||
padding = (data.charAt(i) === pad);
|
||||
// Skip illegal characters and whitespace
|
||||
if (c === -1) {
|
||||
console.error("Illegal character '" + data.charCodeAt(i) + "'");
|
||||
console.error("Illegal character code " + data.charCodeAt(i) + " at position " + i);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,124 +1,45 @@
|
||||
body {
|
||||
margin: 0px;
|
||||
font-size: 13px;
|
||||
color: #111;
|
||||
font-family: "Helvetica";
|
||||
#keyboardinput {
|
||||
background-color:#000;
|
||||
}
|
||||
|
||||
#VNC_controls {
|
||||
background: #111;
|
||||
line-height: 1em;
|
||||
color: #FFF;
|
||||
overflow: hidden;
|
||||
padding: 4px 24px;
|
||||
#noVNC-control-bar {
|
||||
background: #4c4c4c; /* Old browsers */
|
||||
background: -moz-linear-gradient(top, #4c4c4c 0%, #2c2c2c 50%, #000000 51%, #131313 100%); /* FF3.6+ */
|
||||
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#4c4c4c), color-stop(50%,#2c2c2c), color-stop(51%,#000000), color-stop(100%,#131313)); /* Chrome,Safari4+ */
|
||||
background: -webkit-linear-gradient(top, #4c4c4c 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* Chrome10+,Safari5.1+ */
|
||||
background: -o-linear-gradient(top, #4c4c4c 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* Opera11.10+ */
|
||||
background: -ms-linear-gradient(top, #4c4c4c 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* IE10+ */
|
||||
background: linear-gradient(top, #4c4c4c 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* W3C */
|
||||
}
|
||||
|
||||
#VNC_controls ul {
|
||||
list-style:none;
|
||||
list-style-position: outside;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
#VNC_controls li {
|
||||
margin-right: 15px;
|
||||
padding: 2px 0px;
|
||||
float: left;
|
||||
}
|
||||
#VNC_controls li input[type=text],
|
||||
#VNC_controls li input[type=password] {
|
||||
border: 2px solid #333;
|
||||
.triangle-right {
|
||||
border:2px solid #fff;
|
||||
background:#000;
|
||||
color:#fff;
|
||||
}
|
||||
|
||||
#VNC_host {
|
||||
width: 100px;
|
||||
}
|
||||
#VNC_port {
|
||||
width: 50px;
|
||||
}
|
||||
#VNC_password {
|
||||
width: 80px;
|
||||
}
|
||||
#VNC_encrypt {
|
||||
}
|
||||
#VNC_connect_button {
|
||||
width: 100px;
|
||||
.noVNC_status_button {
|
||||
font-size: 12px;
|
||||
vertical-align: middle;
|
||||
border:1px solid #4c4c4c;
|
||||
|
||||
background: #4c4c4c; /* Old browsers */
|
||||
background: -moz-linear-gradient(top, #4c4c4c 0%, #2c2c2c 50%, #000000 51%, #131313 100%); /* FF3.6+ */
|
||||
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#4c4c4c), color-stop(50%,#2c2c2c), color-stop(51%,#000000), color-stop(100%,#131313)); /* Chrome,Safari4+ */
|
||||
background: -webkit-linear-gradient(top, #4c4c4c 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* Chrome10+,Safari5.1+ */
|
||||
background: -o-linear-gradient(top, #4c4c4c 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* Opera11.10+ */
|
||||
background: -ms-linear-gradient(top, #4c4c4c 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* IE10+ */
|
||||
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#4c4c4c', endColorstr='#131313',GradientType=0 ); /* IE6-9 */
|
||||
background: linear-gradient(top, #4c4c4c 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* W3C */
|
||||
}
|
||||
|
||||
#VNC_status_bar td {
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
}
|
||||
#VNC_status_bar div {
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
margin: 0px;
|
||||
padding: 1em;
|
||||
}
|
||||
.VNC_status_button {
|
||||
font-size: 10px;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
#VNC_status {
|
||||
text-align: center;
|
||||
}
|
||||
#VNC_settings_menu {
|
||||
display: none;
|
||||
position: absolute;
|
||||
width: 13em;
|
||||
border: 1px solid #888;
|
||||
color: #111;
|
||||
font-weight: normal;
|
||||
background-color: #f0f2f6;
|
||||
padding: 5px; margin: 3px;
|
||||
z-index: 100; opacity: 1;
|
||||
text-align: left; white-space: normal;
|
||||
}
|
||||
#VNC_settings_menu ul {
|
||||
list-style: none;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.VNC_buttons_right {
|
||||
text-align: right;
|
||||
}
|
||||
.VNC_buttons_left {
|
||||
text-align: left;
|
||||
}
|
||||
.VNC_status_normal {
|
||||
background: #111;
|
||||
color: #fff;
|
||||
}
|
||||
.VNC_status_error {
|
||||
background: #111;
|
||||
color: #f44;
|
||||
}
|
||||
.VNC_status_warn {
|
||||
background: #111;
|
||||
color: #ff4;
|
||||
}
|
||||
|
||||
#VNC_screen {
|
||||
-webkit-border-radius: 10px;
|
||||
-moz-border-radius: 10px;
|
||||
border-radius: 10px;
|
||||
background: #111;
|
||||
padding: 20px;
|
||||
margin: 0 auto;
|
||||
color: #FFF;
|
||||
margin-top: 20px;
|
||||
text-align: center;
|
||||
|
||||
/* This causes the width of the outer div(#screen) honor the size of the inner (#vnc) div */
|
||||
display: table;
|
||||
table-layout: auto;
|
||||
}
|
||||
#VNC_canvas {
|
||||
background: #111;
|
||||
margin: 0 auto;
|
||||
}
|
||||
#VNC_clipboard {
|
||||
display: none;
|
||||
.noVNC_status_button_selected {
|
||||
background: #9dd53a; /* Old browsers */
|
||||
background: -moz-linear-gradient(top, #9dd53a 0%, #a1d54f 50%, #80c217 51%, #7cbc0a 100%); /* FF3.6+ */
|
||||
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#9dd53a), color-stop(50%,#a1d54f), color-stop(51%,#80c217), color-stop(100%,#7cbc0a)); /* Chrome,Safari4+ */
|
||||
background: -webkit-linear-gradient(top, #9dd53a 0%,#a1d54f 50%,#80c217 51%,#7cbc0a 100%); /* Chrome10+,Safari5.1+ */
|
||||
background: -o-linear-gradient(top, #9dd53a 0%,#a1d54f 50%,#80c217 51%,#7cbc0a 100%); /* Opera11.10+ */
|
||||
background: -ms-linear-gradient(top, #9dd53a 0%,#a1d54f 50%,#80c217 51%,#7cbc0a 100%); /* IE10+ */
|
||||
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#9dd53a', endColorstr='#7cbc0a',GradientType=0 ); /* IE6-9 */
|
||||
background: linear-gradient(top, #9dd53a 0%,#a1d54f 50%,#80c217 51%,#7cbc0a 100%); /* W3C */
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
|
||||
#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;
|
||||
}
|
||||
|
||||
@@ -19,19 +19,23 @@ var that = {}, // Public API methods
|
||||
c_ctx = null,
|
||||
c_forceCanvas = false,
|
||||
|
||||
c_imageData, c_rgbxImage, c_cmapImage,
|
||||
|
||||
// Predefine function variables (jslint)
|
||||
imageDataCreate, imageDataGet, rgbxImageData, cmapImageData,
|
||||
rgbxImageFill, cmapImageFill, setFillColor, rescale, flush,
|
||||
imageDataGet, rgbImageData, bgrxImageData, cmapImageData,
|
||||
setFillColor, rescale,
|
||||
|
||||
c_width = 0,
|
||||
c_height = 0,
|
||||
// The full frame buffer (logical canvas) size
|
||||
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 = "",
|
||||
tile = null,
|
||||
tile16x16 = null,
|
||||
tile_x = 0,
|
||||
tile_y = 0;
|
||||
|
||||
c_webkit_bug = false,
|
||||
c_flush_timer = null;
|
||||
|
||||
// Configuration attributes
|
||||
Util.conf_defaults(conf, that, defaults, [
|
||||
@@ -41,6 +45,7 @@ Util.conf_defaults(conf, that, defaults, [
|
||||
['true_color', 'rw', 'bool', true, 'Use true-color pixel data'],
|
||||
['colourMap', 'rw', 'arr', [], 'Colour map array (when not true-color)'],
|
||||
['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'],
|
||||
['height', 'rw', 'int', null, 'Display area height'],
|
||||
|
||||
@@ -55,20 +60,11 @@ that.get_context = function () { return c_ctx; };
|
||||
|
||||
that.set_scale = function(scale) { rescale(scale); };
|
||||
|
||||
that.set_width = function (val) { that.resize(val, c_height); };
|
||||
that.get_width = function() { return c_width; };
|
||||
that.set_width = function (val) { that.resize(val, fb_height); };
|
||||
that.get_width = function() { return fb_width; };
|
||||
|
||||
that.set_height = function (val) { that.resize(c_width, val); };
|
||||
that.get_height = function() { return c_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;
|
||||
};
|
||||
that.set_height = function (val) { that.resize(fb_width, val); };
|
||||
that.get_height = function() { return fb_height; };
|
||||
|
||||
|
||||
|
||||
@@ -80,7 +76,7 @@ that.set_prefer_js = function(val) {
|
||||
function 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;
|
||||
|
||||
if (! conf.target) { throw("target must be set"); }
|
||||
@@ -103,70 +99,19 @@ function constructor() {
|
||||
|
||||
that.clear();
|
||||
|
||||
/*
|
||||
* Determine browser Canvas feature support
|
||||
* and select fastest rendering methods
|
||||
*/
|
||||
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;
|
||||
// Check canvas features
|
||||
if ('createImageData' in c_ctx) {
|
||||
conf.render_mode = "canvas rendering";
|
||||
} else {
|
||||
Util.Warn("Canvas lacks imageData, using fillRect (slow)");
|
||||
conf.render_mode = "fillRect rendering (slow)";
|
||||
c_forceCanvas = true;
|
||||
conf.prefer_js = false;
|
||||
c_rgbxImage = rgbxImageFill;
|
||||
c_cmapImage = cmapImageFill;
|
||||
throw("Canvas does not support createImageData");
|
||||
}
|
||||
if (conf.prefer_js === null) {
|
||||
Util.Info("Prefering javascript operations");
|
||||
conf.prefer_js = true;
|
||||
}
|
||||
|
||||
if (UE.webkit && UE.webkit >= 534.7 && UE.webkit <= 534.9) {
|
||||
// Workaround WebKit canvas rendering bug #46319
|
||||
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);
|
||||
}
|
||||
};
|
||||
}());
|
||||
}
|
||||
}
|
||||
// Initialize cached tile imageData
|
||||
tile16x16 = c_ctx.createImageData(16, 16);
|
||||
|
||||
/*
|
||||
* Determine browser support for setting the cursor via data URI
|
||||
@@ -217,7 +162,10 @@ rescale = function(factor) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (factor > 1.0) {
|
||||
|
||||
if (typeof(factor) === "undefined") {
|
||||
factor = conf.scale;
|
||||
} else if (factor > 1.0) {
|
||||
factor = 1.0;
|
||||
} else if (factor < 0.1) {
|
||||
factor = 0.1;
|
||||
@@ -234,26 +182,14 @@ rescale = function(factor) {
|
||||
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) {
|
||||
var rgb, newStyle;
|
||||
var bgr, newStyle;
|
||||
if (conf.true_color) {
|
||||
rgb = color;
|
||||
bgr = color;
|
||||
} 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) {
|
||||
c_ctx.fillStyle = newStyle;
|
||||
c_prevStyle = newStyle;
|
||||
@@ -265,18 +201,205 @@ setFillColor = function(color) {
|
||||
// Public API interface functions
|
||||
//
|
||||
|
||||
that.resize = function(width, height) {
|
||||
var c = conf.target;
|
||||
// Shift and/or resize the visible viewport
|
||||
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.width = width;
|
||||
c.height = height;
|
||||
|
||||
c_width = c.offsetWidth;
|
||||
c_height = c.offsetHeight;
|
||||
fb_width = width;
|
||||
fb_height = height;
|
||||
|
||||
rescale(conf.scale);
|
||||
that.viewportChange();
|
||||
};
|
||||
|
||||
that.clear = function() {
|
||||
@@ -286,7 +409,7 @@ that.clear = function() {
|
||||
that.blitStringImage(conf.logo.data, 0, 0);
|
||||
} else {
|
||||
that.resize(640, 20);
|
||||
c_ctx.clearRect(0, 0, c_width, c_height);
|
||||
c_ctx.clearRect(0, 0, viewport.w, viewport.h);
|
||||
}
|
||||
|
||||
// No benefit over default ("source-over") in Chrome and firefox
|
||||
@@ -295,58 +418,61 @@ that.clear = function() {
|
||||
|
||||
that.fillRect = function(x, y, width, height, 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) {
|
||||
c_ctx.drawImage(conf.target, old_x, old_y, width, height,
|
||||
new_x, new_y, width, height);
|
||||
that.copyImage = function(old_x, old_y, new_x, new_y, w, h) {
|
||||
var x1 = old_x - viewport.x, y1 = old_y - viewport.y,
|
||||
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.
|
||||
*
|
||||
* - In Chrome/webkit, Javascript image data array manipulations are
|
||||
* faster than direct Canvas fillStyle, fillRect rendering. In
|
||||
* gecko, Javascript array handling is much slower.
|
||||
*/
|
||||
that.getTile = function(x, y, width, height, color) {
|
||||
var img, data = [], rgb, red, green, blue, i;
|
||||
img = {'x': x, 'y': y, 'width': width, 'height': height,
|
||||
'data': data};
|
||||
|
||||
// Start updating a tile
|
||||
that.startTile = function(x, y, width, height, color) {
|
||||
var data, bgr, red, green, blue, i;
|
||||
tile_x = x;
|
||||
tile_y = y;
|
||||
if ((width === 16) && (height === 16)) {
|
||||
tile = tile16x16;
|
||||
} else {
|
||||
tile = c_ctx.createImageData(width, height);
|
||||
}
|
||||
data = tile.data;
|
||||
if (conf.prefer_js) {
|
||||
if (conf.true_color) {
|
||||
rgb = color;
|
||||
bgr = color;
|
||||
} else {
|
||||
rgb = conf.colourMap[color[0]];
|
||||
bgr = conf.colourMap[color[0]];
|
||||
}
|
||||
red = rgb[0];
|
||||
green = rgb[1];
|
||||
blue = rgb[2];
|
||||
red = bgr[2];
|
||||
green = bgr[1];
|
||||
blue = bgr[0];
|
||||
for (i = 0; i < (width * height * 4); i+=4) {
|
||||
data[i ] = red;
|
||||
data[i + 1] = green;
|
||||
data[i + 2] = blue;
|
||||
data[i + 3] = 255;
|
||||
}
|
||||
} else {
|
||||
that.fillRect(x, y, width, height, color);
|
||||
}
|
||||
return img;
|
||||
};
|
||||
|
||||
that.setSubTile = function(img, x, y, w, h, color) {
|
||||
var data, p, rgb, red, green, blue, width, j, i, xend, yend;
|
||||
// Update sub-rectangle of the current tile
|
||||
that.subTile = function(x, y, w, h, color) {
|
||||
var data, p, bgr, red, green, blue, width, j, i, xend, yend;
|
||||
if (conf.prefer_js) {
|
||||
data = img.data;
|
||||
width = img.width;
|
||||
data = tile.data;
|
||||
width = tile.width;
|
||||
if (conf.true_color) {
|
||||
rgb = color;
|
||||
bgr = color;
|
||||
} else {
|
||||
rgb = conf.colourMap[color[0]];
|
||||
bgr = conf.colourMap[color[0]];
|
||||
}
|
||||
red = rgb[0];
|
||||
green = rgb[1];
|
||||
blue = rgb[2];
|
||||
red = bgr[2];
|
||||
green = bgr[1];
|
||||
blue = bgr[0];
|
||||
xend = x + w;
|
||||
yend = y + h;
|
||||
for (j = y; j < yend; j += 1) {
|
||||
@@ -355,96 +481,108 @@ that.setSubTile = function(img, x, y, w, h, color) {
|
||||
data[p ] = red;
|
||||
data[p + 1] = green;
|
||||
data[p + 2] = blue;
|
||||
data[p + 3] = 255;
|
||||
}
|
||||
}
|
||||
} 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) {
|
||||
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) {
|
||||
return c_ctx.getImageData(0, 0, width, height);
|
||||
};
|
||||
imageDataCreate = function(width, height) {
|
||||
return c_ctx.createImageData(width, height);
|
||||
};
|
||||
|
||||
rgbxImageData = function(x, y, width, height, arr, offset) {
|
||||
var img, i, j, data;
|
||||
img = c_imageData(width, height);
|
||||
rgbImageData = function(x, y, width, height, arr, offset) {
|
||||
var img, i, j, data, v = viewport;
|
||||
/*
|
||||
if ((x - v.x >= v.w) || (y - v.y >= v.h) ||
|
||||
(x - v.x + width < 0) || (y - v.y + height < 0)) {
|
||||
// Skipping because outside of viewport
|
||||
return;
|
||||
}
|
||||
*/
|
||||
img = c_ctx.createImageData(width, height);
|
||||
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 + 1] = arr[j + 1];
|
||||
data[i + 2] = arr[j + 2];
|
||||
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
|
||||
rgbxImageFill = function(x, y, width, height, arr, offset) {
|
||||
var i, j, sx = 0, sy = 0;
|
||||
for (i=0, j=offset; i < (width * height); i+=1, j+=4) {
|
||||
that.fillRect(x+sx, y+sy, 1, 1, [arr[j], arr[j+1], arr[j+2]]);
|
||||
sx += 1;
|
||||
if ((sx % width) === 0) {
|
||||
sx = 0;
|
||||
sy += 1;
|
||||
}
|
||||
bgrxImageData = function(x, y, width, height, arr, offset) {
|
||||
var img, i, j, data, v = viewport;
|
||||
/*
|
||||
if ((x - v.x >= v.w) || (y - v.y >= v.h) ||
|
||||
(x - v.x + width < 0) || (y - v.y + height < 0)) {
|
||||
// Skipping because outside of viewport
|
||||
return;
|
||||
}
|
||||
*/
|
||||
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) {
|
||||
var img, i, j, data, rgb, cmap;
|
||||
img = c_imageData(width, height);
|
||||
var img, i, j, data, bgr, cmap;
|
||||
img = c_ctx.createImageData(width, height);
|
||||
data = img.data;
|
||||
cmap = conf.colourMap;
|
||||
for (i=0, j=offset; i < (width * height * 4); i+=4, j+=1) {
|
||||
rgb = cmap[arr[j]];
|
||||
data[i ] = rgb[0];
|
||||
data[i + 1] = rgb[1];
|
||||
data[i + 2] = rgb[2];
|
||||
bgr = cmap[arr[j]];
|
||||
data[i ] = bgr[2];
|
||||
data[i + 1] = bgr[1];
|
||||
data[i + 2] = bgr[0];
|
||||
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) {
|
||||
if (conf.true_color) {
|
||||
c_rgbxImage(x, y, width, height, arr, offset);
|
||||
bgrxImageData(x, y, width, height, arr, offset);
|
||||
} 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) {
|
||||
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;
|
||||
};
|
||||
|
||||
// Wrap ctx.drawImage but relative to viewport
|
||||
that.drawImage = function(img, x, y) {
|
||||
c_ctx.drawImage(img, x - viewport.x, y - viewport.y);
|
||||
};
|
||||
|
||||
|
||||
that.changeCursor = function(pixels, mask, hotx, hoty, w, h) {
|
||||
if (conf.cursor_uri === false) {
|
||||
Util.Warn("changeCursor called but no cursor data URI support");
|
||||
|
||||
@@ -412,6 +412,26 @@ function onKeyUp(e) {
|
||||
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
|
||||
//
|
||||
@@ -424,6 +444,9 @@ that.grab = function() {
|
||||
Util.addEvent(c, 'keyup', onKeyUp);
|
||||
Util.addEvent(c, 'keypress', onKeyPress);
|
||||
|
||||
// Release (key up) if window loses focus
|
||||
Util.addEvent(window, 'blur', allKeysUp);
|
||||
|
||||
//Util.Debug("<< Keyboard.grab");
|
||||
};
|
||||
|
||||
@@ -434,6 +457,10 @@ that.ungrab = function() {
|
||||
Util.removeEvent(c, 'keydown', onKeyDown);
|
||||
Util.removeEvent(c, 'keyup', onKeyUp);
|
||||
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");
|
||||
};
|
||||
@@ -460,7 +487,8 @@ Util.conf_defaults(conf, that, defaults, [
|
||||
['scale', 'rw', 'float', 1.0, 'Viewport scale factor 0.0 - 1.0'],
|
||||
|
||||
['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);
|
||||
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 */
|
||||
bmask = 1 << evt.button;
|
||||
} else {
|
||||
@@ -486,7 +518,7 @@ function onMouseButton(e, down) {
|
||||
}
|
||||
//Util.Debug("mouse " + pos.x + "," + pos.y + " down: " + down +
|
||||
// " bmask: " + bmask + "(evt.button: " + evt.button + ")");
|
||||
if (conf.onMouseButton) {
|
||||
if (bmask > 0 && conf.onMouseButton) {
|
||||
Util.Debug("onMouseButton " + (down ? "down" : "up") +
|
||||
", x: " + pos.x + ", y: " + pos.y + ", bmask: " + bmask);
|
||||
conf.onMouseButton(pos.x, pos.y, down, bmask);
|
||||
@@ -536,6 +568,8 @@ function onMouseMove(e) {
|
||||
if (conf.onMouseMove) {
|
||||
conf.onMouseMove(pos.x, pos.y);
|
||||
}
|
||||
Util.stopEvent(e);
|
||||
return false;
|
||||
}
|
||||
|
||||
function onMouseDisable(e) {
|
||||
@@ -565,11 +599,17 @@ that.grab = function() {
|
||||
//Util.Debug(">> Mouse.grab");
|
||||
var c = conf.target;
|
||||
|
||||
Util.addEvent(c, 'mousedown', onMouseDown);
|
||||
Util.addEvent(c, 'mouseup', onMouseUp);
|
||||
Util.addEvent(c, 'mousemove', onMouseMove);
|
||||
Util.addEvent(c, (Util.Engine.gecko) ? 'DOMMouseScroll' : 'mousewheel',
|
||||
onMouseWheel);
|
||||
if ('ontouchstart' in document.documentElement) {
|
||||
Util.addEvent(c, 'touchstart', onMouseDown);
|
||||
Util.addEvent(c, 'touchend', onMouseUp);
|
||||
Util.addEvent(c, 'touchmove', onMouseMove);
|
||||
} 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 */
|
||||
Util.addEvent(document, 'click', onMouseDisable);
|
||||
@@ -582,11 +622,17 @@ that.ungrab = function() {
|
||||
//Util.Debug(">> Mouse.ungrab");
|
||||
var c = conf.target;
|
||||
|
||||
Util.removeEvent(c, 'mousedown', onMouseDown);
|
||||
Util.removeEvent(c, 'mouseup', onMouseUp);
|
||||
Util.removeEvent(c, 'mousemove', onMouseMove);
|
||||
Util.removeEvent(c, (Util.Engine.gecko) ? 'DOMMouseScroll' : 'mousewheel',
|
||||
onMouseWheel);
|
||||
if ('ontouchstart' in document.documentElement) {
|
||||
Util.removeEvent(c, 'touchstart', onMouseDown);
|
||||
Util.removeEvent(c, 'touchend', onMouseUp);
|
||||
Util.removeEvent(c, 'touchmove', onMouseMove);
|
||||
} 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 */
|
||||
Util.removeEvent(document, 'click', onMouseDisable);
|
||||
@@ -1121,14 +1167,14 @@ unicodeTable = {
|
||||
0x21D4 : 0x08cd,
|
||||
0x21D2 : 0x08ce,
|
||||
0x2261 : 0x08cf,
|
||||
0x221A : 0x08d6,
|
||||
//0x221A : 0x08d6,
|
||||
0x2282 : 0x08da,
|
||||
0x2283 : 0x08db,
|
||||
0x2229 : 0x08dc,
|
||||
0x222A : 0x08dd,
|
||||
0x2227 : 0x08de,
|
||||
0x2228 : 0x08df,
|
||||
0x2202 : 0x08ef,
|
||||
//0x2202 : 0x08ef,
|
||||
0x0192 : 0x08f6,
|
||||
0x2190 : 0x08fb,
|
||||
0x2191 : 0x08fc,
|
||||
@@ -1463,7 +1509,7 @@ unicodeTable = {
|
||||
0x012D : 0x100012d,
|
||||
0x01B6 : 0x10001b6,
|
||||
0x01E7 : 0x10001e7,
|
||||
0x01D2 : 0x10001d2,
|
||||
//0x01D2 : 0x10001d2,
|
||||
0x0275 : 0x1000275,
|
||||
0x018F : 0x100018f,
|
||||
0x0259 : 0x1000259,
|
||||
@@ -1833,7 +1879,7 @@ unicodeTable = {
|
||||
0x28e0 : 0x10028e0,
|
||||
0x28e1 : 0x10028e1,
|
||||
0x28e2 : 0x10028e2,
|
||||
0x28e3 : 0x10028e3,
|
||||
0x28e3 : 0x10028e3,
|
||||
0x28e4 : 0x10028e4,
|
||||
0x28e5 : 0x10028e5,
|
||||
0x28e6 : 0x10028e6,
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -4,10 +4,13 @@
|
||||
* Licensed under LGPL-3 (see LICENSE.txt)
|
||||
*
|
||||
* 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 */
|
||||
/*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) {
|
||||
@@ -20,10 +23,10 @@ var that = {}, // Public API methods
|
||||
init_vars, updateState, fail, handle_message,
|
||||
init_msg, normal_msg, framebufferUpdate, print_stats,
|
||||
|
||||
pixelFormat, clientEncodings, fbUpdateRequest,
|
||||
pixelFormat, clientEncodings, fbUpdateRequest, fbUpdateRequests,
|
||||
keyEvent, pointerEvent, clientCutText,
|
||||
|
||||
extract_data_uri, scan_tight_imgQ,
|
||||
getTightCLength, extract_data_uri, scan_tight_imgQ,
|
||||
keyPress, mouseButton, mouseMove,
|
||||
|
||||
checkEvents, // Overridable for testing
|
||||
@@ -35,7 +38,7 @@ var that = {}, // Public API methods
|
||||
rfb_host = '',
|
||||
rfb_port = 5900,
|
||||
rfb_password = '',
|
||||
rfb_uri = '',
|
||||
rfb_path = '',
|
||||
|
||||
rfb_state = 'disconnected',
|
||||
rfb_version = 0,
|
||||
@@ -46,6 +49,7 @@ var that = {}, // Public API methods
|
||||
// In preference order
|
||||
encodings = [
|
||||
['COPYRECT', 0x01 ],
|
||||
['TIGHT', 0x07 ],
|
||||
['TIGHT_PNG', -260 ],
|
||||
['HEXTILE', 0x05 ],
|
||||
['RRE', 0x02 ],
|
||||
@@ -54,10 +58,12 @@ var that = {}, // Public API methods
|
||||
['Cursor', -239 ],
|
||||
|
||||
// Psuedo-encoding settings
|
||||
['JPEG_quality_lo', -32 ],
|
||||
//['JPEG_quality_lo', -32 ],
|
||||
['JPEG_quality_med', -26 ],
|
||||
//['JPEG_quality_hi', -23 ],
|
||||
['compress_lo', -255 ]
|
||||
//['compress_hi', -247 ]
|
||||
//['compress_lo', -255 ],
|
||||
['compress_hi', -247 ],
|
||||
['last_rect', -224 ]
|
||||
],
|
||||
|
||||
encHandlers = {},
|
||||
@@ -87,7 +93,8 @@ var that = {}, // Public API methods
|
||||
encoding : 0,
|
||||
subencoding : -1,
|
||||
background : null,
|
||||
imgQ : [] // TIGHT_PNG image queue
|
||||
imgQ : [], // TIGHT_PNG image queue
|
||||
zlibs : [] // TIGHT zlib streams
|
||||
},
|
||||
|
||||
fb_Bpp = 4,
|
||||
@@ -109,7 +116,8 @@ var that = {}, // Public API methods
|
||||
|
||||
fbu_rt_start : 0,
|
||||
fbu_rt_total : 0,
|
||||
fbu_rt_cnt : 0
|
||||
fbu_rt_cnt : 0,
|
||||
pixels : 0
|
||||
},
|
||||
|
||||
test_mode = false,
|
||||
@@ -118,7 +126,9 @@ var that = {}, // Public API methods
|
||||
|
||||
/* Mouse state */
|
||||
mouse_buttonMask = 0,
|
||||
mouse_arr = [];
|
||||
mouse_arr = [],
|
||||
viewportDragging = false,
|
||||
viewportDragPos = {};
|
||||
|
||||
// Configuration attributes
|
||||
Util.conf_defaults(conf, that, defaults, [
|
||||
@@ -129,10 +139,13 @@ Util.conf_defaults(conf, that, defaults, [
|
||||
['true_color', 'rw', 'bool', true, 'Request true color pixel data'],
|
||||
['local_cursor', 'rw', 'bool', false, 'Request locally rendered cursor'],
|
||||
['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'],
|
||||
['disconnectTimeout', 'rw', 'int', 3, 'Time (s) to wait for disconnection'],
|
||||
|
||||
['viewportDrag', 'rw', 'bool', false, 'Move the viewport on mouse drags'],
|
||||
|
||||
['check_rate', 'rw', 'int', 217, 'Timing (ms) of send/receive check'],
|
||||
['fbu_req_rate', 'rw', 'int', 1413, 'Timing (ms) of frameBufferUpdate requests'],
|
||||
|
||||
@@ -211,10 +224,6 @@ function constructor() {
|
||||
|
||||
rmode = display.get_render_mode();
|
||||
|
||||
if (typeof noVNC_logo !== 'undefined') {
|
||||
display.set_logo(noVNC_logo);
|
||||
}
|
||||
|
||||
ws = new Websock();
|
||||
ws.on('message', handle_message);
|
||||
ws.on('open', function() {
|
||||
@@ -224,19 +233,29 @@ function constructor() {
|
||||
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') {
|
||||
updateState('disconnected', 'VNC disconnected');
|
||||
updateState('disconnected', 'VNC disconnected' + msg);
|
||||
} 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}) {
|
||||
Util.Error("Received onclose while disconnected");
|
||||
Util.Error("Received onclose while disconnected" + msg);
|
||||
} else {
|
||||
fail('Server disconnected');
|
||||
fail('Server disconnected' + msg);
|
||||
}
|
||||
});
|
||||
ws.on('error', function(e) {
|
||||
fail("WebSock error: " + e);
|
||||
Util.Warn("WebSocket on-error event");
|
||||
//fail("WebSock reported an error");
|
||||
});
|
||||
|
||||
|
||||
@@ -266,14 +285,18 @@ function constructor() {
|
||||
|
||||
function connect() {
|
||||
Util.Debug(">> RFB.connect");
|
||||
|
||||
var uri = "";
|
||||
if (conf.encrypt) {
|
||||
uri = "wss://";
|
||||
var uri;
|
||||
|
||||
if (typeof UsingSocketIO !== "undefined") {
|
||||
uri = "http://" + rfb_host + ":" + rfb_port + "/" + rfb_path;
|
||||
} 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);
|
||||
ws.open(uri);
|
||||
|
||||
@@ -292,6 +315,7 @@ init_vars = function() {
|
||||
FBU.lines = 0; // RAW
|
||||
FBU.tiles = 0; // HEXTILE
|
||||
FBU.imgQ = []; // TIGHT_PNG image queue
|
||||
FBU.zlibs = []; // TIGHT zlib encoders
|
||||
mouse_buttonMask = 0;
|
||||
mouse_arr = [];
|
||||
|
||||
@@ -299,6 +323,12 @@ init_vars = function() {
|
||||
for (i=0; i < encodings.length; i+=1) {
|
||||
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
|
||||
@@ -396,16 +426,16 @@ updateState = function(state, statusMsg) {
|
||||
func = Util.Warn;
|
||||
}
|
||||
|
||||
cmsg = typeof(statusMsg) !== 'undefined' ? (" Msg: " + statusMsg) : "";
|
||||
func("New state '" + state + "', was '" + oldstate + "'." + cmsg);
|
||||
|
||||
if ((oldstate === 'failed') && (state === 'disconnected')) {
|
||||
// Do disconnect action, but stay in failed state.
|
||||
// Do disconnect action, but stay in failed state
|
||||
rfb_state = 'failed';
|
||||
} else {
|
||||
rfb_state = state;
|
||||
}
|
||||
|
||||
cmsg = typeof(statusMsg) !== 'undefined' ? (" Msg: " + statusMsg) : "";
|
||||
func("New state '" + rfb_state + "', was '" + oldstate + "'." + cmsg);
|
||||
|
||||
if (connTimer && (rfb_state !== 'connect')) {
|
||||
Util.Debug("Clearing connect timer");
|
||||
clearInterval(connTimer);
|
||||
@@ -535,10 +565,10 @@ function genDES(password, challenge) {
|
||||
|
||||
function flushClient() {
|
||||
if (mouse_arr.length > 0) {
|
||||
//send(mouse_arr.concat(fbUpdateRequest(1)));
|
||||
//send(mouse_arr.concat(fbUpdateRequests()));
|
||||
ws.send(mouse_arr);
|
||||
setTimeout(function() {
|
||||
ws.send(fbUpdateRequest(1));
|
||||
ws.send(fbUpdateRequests());
|
||||
}, 50);
|
||||
|
||||
mouse_arr = [];
|
||||
@@ -551,12 +581,12 @@ function flushClient() {
|
||||
// overridable for testing
|
||||
checkEvents = function() {
|
||||
var now;
|
||||
if (rfb_state === 'normal') {
|
||||
if (rfb_state === 'normal' && !viewportDragging) {
|
||||
if (! flushClient()) {
|
||||
now = new Date().getTime();
|
||||
if (now > last_req_time + conf.fbu_req_rate) {
|
||||
last_req_time = now;
|
||||
ws.send(fbUpdateRequest(1));
|
||||
ws.send(fbUpdateRequests());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -565,8 +595,11 @@ checkEvents = function() {
|
||||
|
||||
keyPress = function(keysym, down) {
|
||||
var arr;
|
||||
|
||||
if (conf.view_only) { return; } // View only, skip keyboard events
|
||||
|
||||
arr = keyEvent(keysym, down);
|
||||
arr = arr.concat(fbUpdateRequest(1));
|
||||
arr = arr.concat(fbUpdateRequests());
|
||||
ws.send(arr);
|
||||
};
|
||||
|
||||
@@ -576,13 +609,48 @@ mouseButton = function(x, y, down, bmask) {
|
||||
} else {
|
||||
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();
|
||||
};
|
||||
|
||||
mouseMove = function(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)) );
|
||||
};
|
||||
|
||||
|
||||
@@ -611,8 +679,11 @@ init_msg = function() {
|
||||
switch (sversion) {
|
||||
case "003.003": rfb_version = 3.3; break;
|
||||
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.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:
|
||||
return fail("Invalid server version " + sversion);
|
||||
}
|
||||
@@ -776,9 +847,25 @@ init_msg = function() {
|
||||
", green_shift: " + green_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 */
|
||||
name_length = ws.rQshift32();
|
||||
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.resize(fb_width, fb_height);
|
||||
@@ -795,7 +882,7 @@ init_msg = function() {
|
||||
|
||||
response = pixelFormat();
|
||||
response = response.concat(clientEncodings());
|
||||
response = response.concat(fbUpdateRequest(0));
|
||||
response = response.concat(fbUpdateRequests());
|
||||
timing.fbu_rt_start = (new Date()).getTime();
|
||||
ws.send(response);
|
||||
|
||||
@@ -835,6 +922,8 @@ normal_msg = function() {
|
||||
ws.rQshift8(); // Padding
|
||||
first_colour = ws.rQshift16(); // First colour
|
||||
num_colours = ws.rQshift16();
|
||||
if (ws.rQwait("SetColourMapEntries", num_colours*6, 6)) { return false; }
|
||||
|
||||
for (c=0; c < num_colours; c+=1) {
|
||||
red = ws.rQshift16();
|
||||
//Util.Debug("red before: " + red);
|
||||
@@ -842,9 +931,9 @@ normal_msg = function() {
|
||||
//Util.Debug("red after: " + red);
|
||||
green = parseInt(ws.rQshift16() / 256, 10);
|
||||
blue = parseInt(ws.rQshift16() / 256, 10);
|
||||
Util.Debug("*** colourMap: " + display.get_colourMap());
|
||||
display.set_colourMap([red, green, blue], first_colour + c);
|
||||
display.set_colourMap([blue, green, red], first_colour + c);
|
||||
}
|
||||
Util.Debug("colourMap: " + display.get_colourMap());
|
||||
Util.Info("Registered " + num_colours + " colourMap entries");
|
||||
//Util.Debug("colourMap: " + display.get_colourMap());
|
||||
break;
|
||||
@@ -943,9 +1032,10 @@ framebufferUpdate = function() {
|
||||
if (ret) {
|
||||
encStats[FBU.encoding][0] += 1;
|
||||
encStats[FBU.encoding][1] += 1;
|
||||
timing.pixels += FBU.width * FBU.height;
|
||||
}
|
||||
|
||||
if (FBU.rects === 0) {
|
||||
if (FBU.rects === 0 || (timing.pixels >= (fb_width * fb_height))) {
|
||||
if (((FBU.width === fb_width) &&
|
||||
(FBU.height === fb_height)) ||
|
||||
(timing.fbu_rt_start > 0)) {
|
||||
@@ -1066,7 +1156,7 @@ encHandlers.RRE = function display_rre() {
|
||||
|
||||
encHandlers.HEXTILE = function 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,
|
||||
rQ = ws.get_rQ(), rQi = ws.get_rQi();
|
||||
|
||||
@@ -1155,7 +1245,7 @@ encHandlers.HEXTILE = function display_hextile() {
|
||||
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
|
||||
subrects = rQ[rQi];
|
||||
rQi += 1;
|
||||
@@ -1176,10 +1266,10 @@ encHandlers.HEXTILE = function display_hextile() {
|
||||
sw = (wh >> 4) + 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);
|
||||
FBU.lastsubencoding = FBU.subencoding;
|
||||
@@ -1196,42 +1286,197 @@ encHandlers.HEXTILE = function display_hextile() {
|
||||
};
|
||||
|
||||
|
||||
encHandlers.TIGHT_PNG = function display_tight_png() {
|
||||
//Util.Debug(">> display_tight_png");
|
||||
var ctl, cmode, clength, getCLength, color, img;
|
||||
//Util.Debug(" FBU.rects: " + FBU.rects);
|
||||
//Util.Debug(" starting ws.rQslice(0,20): " + ws.rQslice(0,20) + " (" + ws.rQlen() + ")");
|
||||
// Get 'compact length' header and data size
|
||||
getTightCLength = function (arr) {
|
||||
var header = 1, data = 0;
|
||||
data += arr[0] & 0x7f;
|
||||
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
|
||||
if (ws.rQwait("TIGHT compression-control", FBU.bytes)) { return false; }
|
||||
|
||||
// Get 'compact length' header and data size
|
||||
getCLength = function (arr) {
|
||||
var header = 1, data = 0;
|
||||
data += arr[0] & 0x7f;
|
||||
if (arr[0] & 0x80) {
|
||||
header += 1;
|
||||
data += (arr[1] & 0x7f) << 7;
|
||||
if (arr[1] & 0x80) {
|
||||
header += 1;
|
||||
data += arr[2] << 14;
|
||||
var checksum = function(data) {
|
||||
var sum=0, i;
|
||||
for (i=0; i<data.length;i++) {
|
||||
sum += data[i];
|
||||
if (sum > 65536) sum -= 65536;
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FBU.imgQ.push({
|
||||
'type': 'rgb',
|
||||
'img': {'complete': true, '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]));
|
||||
}
|
||||
|
||||
FBU.imgQ.push({
|
||||
'type': 'rgb',
|
||||
'img': {'complete': true, 'data': data},
|
||||
'x': FBU.x,
|
||||
'y': FBU.y,
|
||||
'width': FBU.width,
|
||||
'height': FBU.height});
|
||||
return true;
|
||||
}
|
||||
|
||||
ctl = ws.rQpeek8();
|
||||
switch (ctl >> 4) {
|
||||
case 0x08: cmode = "fill"; break;
|
||||
case 0x09: cmode = "jpeg"; break;
|
||||
case 0x0A: cmode = "png"; break;
|
||||
default: throw("Illegal basic compression received, ctl: " + ctl);
|
||||
|
||||
// Keep tight reset bits
|
||||
resetStreams = ctl & 0xF;
|
||||
|
||||
// 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 throw("Illegal tight compression received, ctl: " + ctl);
|
||||
|
||||
if (isTightPNG && (cmode === "filter" || cmode === "copy")) {
|
||||
throw("filter/copy received in tightPNG mode");
|
||||
}
|
||||
|
||||
switch (cmode) {
|
||||
// fill uses fb_depth because TPIXELs drop the padding byte
|
||||
case "fill": FBU.bytes += fb_depth; break; // TPIXEL
|
||||
case "jpeg": FBU.bytes += 3; break; // max clength
|
||||
case "png": FBU.bytes += 3; break; // max clength
|
||||
case "fill": FBU.bytes += fb_depth; break; // TPIXEL
|
||||
case "jpeg": 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; }
|
||||
@@ -1251,16 +1496,17 @@ encHandlers.TIGHT_PNG = function display_tight_png() {
|
||||
'y': FBU.y,
|
||||
'width': FBU.width,
|
||||
'height': FBU.height,
|
||||
'color': color});
|
||||
'color': [color[2], color[1], color[0]] });
|
||||
break;
|
||||
case "jpeg":
|
||||
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
|
||||
if (ws.rQwait("TIGHT " + cmode, FBU.bytes)) { return false; }
|
||||
|
||||
// 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
|
||||
img = new Image();
|
||||
//img.onload = scan_tight_imgQ;
|
||||
@@ -1273,13 +1519,27 @@ encHandlers.TIGHT_PNG = function display_tight_png() {
|
||||
extract_data_uri(ws.rQshiftBytes(clength[1]));
|
||||
img = null;
|
||||
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.rects -= 1;
|
||||
//Util.Debug(" ending ws.rQslice(0,20): " + ws.rQslice(0,20) + " (" + ws.rQlen() + ")");
|
||||
//Util.Debug("<< display_tight_png");
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
extract_data_uri = function(arr) {
|
||||
//var i, stra = [];
|
||||
@@ -1297,16 +1557,28 @@ scan_tight_imgQ = function() {
|
||||
imgQ = FBU.imgQ;
|
||||
while ((imgQ.length > 0) && (imgQ[0].img.complete)) {
|
||||
data = imgQ.shift();
|
||||
if (data['type'] === 'fill') {
|
||||
if (data.type === 'fill') {
|
||||
display.fillRect(data.x, data.y, data.width, data.height, data.color);
|
||||
} else if (data.type === 'rgb') {
|
||||
display.blitRgbImage(data.x, data.y, data.width, data.height, data.img.data, 0);
|
||||
} else {
|
||||
ctx.drawImage(data.img, data.x, data.y);
|
||||
display.drawImage(data.img, data.x, data.y);
|
||||
}
|
||||
}
|
||||
setTimeout(scan_tight_imgQ, scan_imgQ_rate);
|
||||
}
|
||||
};
|
||||
|
||||
encHandlers.TIGHT = function () { return display_tight(false); };
|
||||
encHandlers.TIGHT_PNG = function () { return display_tight(true); };
|
||||
|
||||
encHandlers.last_rect = function last_rect() {
|
||||
Util.Debug(">> set_desktopsize");
|
||||
FBU.rects = 0;
|
||||
Util.Debug("<< set_desktopsize");
|
||||
return true;
|
||||
};
|
||||
|
||||
encHandlers.DesktopSize = function set_desktopsize() {
|
||||
Util.Debug(">> set_desktopsize");
|
||||
fb_width = FBU.width;
|
||||
@@ -1314,7 +1586,7 @@ encHandlers.DesktopSize = function set_desktopsize() {
|
||||
display.resize(fb_width, fb_height);
|
||||
timing.fbu_rt_start = (new Date()).getTime();
|
||||
// Send a new non-incremental request
|
||||
ws.send(fbUpdateRequest(0));
|
||||
ws.send(fbUpdateRequests());
|
||||
|
||||
FBU.bytes = 0;
|
||||
FBU.rects -= 1;
|
||||
@@ -1378,9 +1650,9 @@ pixelFormat = function() {
|
||||
arr.push16(255); // red-max
|
||||
arr.push16(255); // green-max
|
||||
arr.push16(255); // blue-max
|
||||
arr.push8(0); // red-shift
|
||||
arr.push8(16); // red-shift
|
||||
arr.push8(8); // green-shift
|
||||
arr.push8(16); // blue-shift
|
||||
arr.push8(0); // blue-shift
|
||||
|
||||
arr.push8(0); // padding
|
||||
arr.push8(0); // padding
|
||||
@@ -1416,10 +1688,10 @@ clientEncodings = function() {
|
||||
|
||||
fbUpdateRequest = function(incremental, x, y, xw, yw) {
|
||||
//Util.Debug(">> fbUpdateRequest");
|
||||
if (!x) { x = 0; }
|
||||
if (!y) { y = 0; }
|
||||
if (!xw) { xw = fb_width; }
|
||||
if (!yw) { yw = fb_height; }
|
||||
if (typeof(x) === "undefined") { x = 0; }
|
||||
if (typeof(y) === "undefined") { y = 0; }
|
||||
if (typeof(xw) === "undefined") { xw = fb_width; }
|
||||
if (typeof(yw) === "undefined") { yw = fb_height; }
|
||||
var arr;
|
||||
arr = [3]; // msg-type
|
||||
arr.push8(incremental);
|
||||
@@ -1431,6 +1703,26 @@ fbUpdateRequest = function(incremental, x, y, xw, yw) {
|
||||
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) {
|
||||
//Util.Debug(">> keyEvent, keysym: " + keysym + ", down: " + down);
|
||||
var arr;
|
||||
@@ -1476,13 +1768,13 @@ clientCutText = function(text) {
|
||||
// Public API interface functions
|
||||
//
|
||||
|
||||
that.connect = function(host, port, password, uri) {
|
||||
that.connect = function(host, port, password, path) {
|
||||
//Util.Debug(">> connect");
|
||||
|
||||
rfb_host = host;
|
||||
rfb_port = port;
|
||||
rfb_password = (password !== undefined) ? password : "";
|
||||
rfb_uri = (uri !== undefined) ? uri : "";
|
||||
rfb_path = (path !== undefined) ? path : "";
|
||||
|
||||
if ((!rfb_host) || (!rfb_port)) {
|
||||
return fail("Must set host and port");
|
||||
@@ -1506,7 +1798,7 @@ that.sendPassword = function(passwd) {
|
||||
};
|
||||
|
||||
that.sendCtrlAltDel = function() {
|
||||
if (rfb_state !== "normal") { return false; }
|
||||
if (rfb_state !== "normal" || conf.view_only) { return false; }
|
||||
Util.Info("Sending Ctrl-Alt-Del");
|
||||
var arr = [];
|
||||
arr = arr.concat(keyEvent(0xFFE3, 1)); // Control
|
||||
@@ -1515,14 +1807,14 @@ that.sendCtrlAltDel = function() {
|
||||
arr = arr.concat(keyEvent(0xFFFF, 0)); // Delete
|
||||
arr = arr.concat(keyEvent(0xFFE9, 0)); // Alt
|
||||
arr = arr.concat(keyEvent(0xFFE3, 0)); // Control
|
||||
arr = arr.concat(fbUpdateRequest(1));
|
||||
arr = arr.concat(fbUpdateRequests());
|
||||
ws.send(arr);
|
||||
};
|
||||
|
||||
// Send a key press. If 'down' is not specified then send a down key
|
||||
// followed by an up key.
|
||||
that.sendKey = function(code, down) {
|
||||
if (rfb_state !== "normal") { return false; }
|
||||
if (rfb_state !== "normal" || conf.view_only) { return false; }
|
||||
var arr = [];
|
||||
if (typeof down !== 'undefined') {
|
||||
Util.Info("Sending key code (" + (down ? "down" : "up") + "): " + code);
|
||||
@@ -1532,7 +1824,7 @@ that.sendKey = function(code, down) {
|
||||
arr = arr.concat(keyEvent(code, 1));
|
||||
arr = arr.concat(keyEvent(code, 0));
|
||||
}
|
||||
arr = arr.concat(fbUpdateRequest(1));
|
||||
arr = arr.concat(fbUpdateRequests());
|
||||
ws.send(arr);
|
||||
};
|
||||
|
||||
|
||||
@@ -12,150 +12,109 @@
|
||||
|
||||
var UI = {
|
||||
|
||||
rfb_state : 'loaded',
|
||||
settingsOpen : false,
|
||||
connSettingsOpen : false,
|
||||
clipboardOpen: false,
|
||||
keyboardVisible: false,
|
||||
|
||||
// Render default UI and initialize settings menu
|
||||
load: function(target) {
|
||||
load: function() {
|
||||
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
|
||||
html += ' <li><select id="VNC_stylesheet" name="vncStyle">';
|
||||
html += ' <option value="default">default</option>';
|
||||
sheet = WebUtil.selectStylesheet();
|
||||
sheets = WebUtil.getStylesheets();
|
||||
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
|
||||
html += ' <li><select id="VNC_logging" name="vncLogging">';
|
||||
llevels = ['error', 'warn', 'info', 'debug'];
|
||||
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
|
||||
UI.initSetting('logging', 'warn');
|
||||
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'));
|
||||
|
||||
/* Populate the controls if defaults are provided in the URL */
|
||||
UI.initSetting('host', '');
|
||||
UI.initSetting('port', '');
|
||||
UI.initSetting('host', window.location.hostname);
|
||||
UI.initSetting('port', window.location.port);
|
||||
UI.initSetting('password', '');
|
||||
UI.initSetting('encrypt', false);
|
||||
UI.initSetting('encrypt', (window.location.protocol === "https:"));
|
||||
UI.initSetting('true_color', true);
|
||||
UI.initSetting('cursor', false);
|
||||
UI.initSetting('shared', true);
|
||||
UI.initSetting('view_only', false);
|
||||
UI.initSetting('connectTimeout', 2);
|
||||
UI.initSetting('path', 'websockify');
|
||||
|
||||
UI.rfb = RFB({'target': $D('VNC_canvas'),
|
||||
UI.rfb = RFB({'target': $D('noVNC_canvas'),
|
||||
'onUpdateState': UI.updateState,
|
||||
'onClipboard': UI.clipReceive});
|
||||
UI.updateVisualState();
|
||||
|
||||
// Unfocus clipboard when over the VNC area
|
||||
$D('VNC_screen').onmousemove = function () {
|
||||
var keyboard = UI.rfb.get_keyboard();
|
||||
if ((! keyboard) || (! keyboard.get_focused())) {
|
||||
$D('VNC_clipboard_text').blur();
|
||||
}
|
||||
};
|
||||
//$D('VNC_screen').onmousemove = function () {
|
||||
// var keyboard = UI.rfb.get_keyboard();
|
||||
// if ((! keyboard) || (! keyboard.get_focused())) {
|
||||
// $D('VNC_clipboard_text').blur();
|
||||
// }
|
||||
// };
|
||||
|
||||
// Show mouse selector buttons on touch screen devices
|
||||
if ('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
|
||||
getSetting: function(name) {
|
||||
var val, ctrl = $D('VNC_' + name);
|
||||
var val, ctrl = $D('noVNC_' + name);
|
||||
val = WebUtil.readCookie(name);
|
||||
if (ctrl.type === 'checkbox') {
|
||||
if (val.toLowerCase() in {'0':1, 'no':1, 'false':1}) {
|
||||
@@ -170,7 +129,8 @@ getSetting: function(name) {
|
||||
// Update cookie and form control setting. If value is not set, then
|
||||
// updates from control to current cookie setting.
|
||||
updateSetting: function(name, value) {
|
||||
var i, ctrl = $D('VNC_' + name);
|
||||
|
||||
var i, ctrl = $D('noVNC_' + name);
|
||||
// Save the cookie for this session
|
||||
if (typeof value !== 'undefined') {
|
||||
WebUtil.createCookie(name, value);
|
||||
@@ -178,8 +138,10 @@ updateSetting: function(name, value) {
|
||||
|
||||
// Update the settings control
|
||||
value = UI.getSetting(name);
|
||||
|
||||
if (ctrl.type === 'checkbox') {
|
||||
ctrl.checked = value;
|
||||
|
||||
} else if (typeof ctrl.options !== 'undefined') {
|
||||
for (i = 0; i < ctrl.options.length; i += 1) {
|
||||
if (ctrl.options[i].value === value) {
|
||||
@@ -188,13 +150,18 @@ updateSetting: function(name, value) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/*Weird IE9 error leads to 'null' appearring
|
||||
in textboxes instead of ''.*/
|
||||
if (value === null) {
|
||||
value = "";
|
||||
}
|
||||
ctrl.value = value;
|
||||
}
|
||||
},
|
||||
|
||||
// Save control setting to cookie
|
||||
saveSetting: function(name) {
|
||||
var val, ctrl = $D('VNC_' + name);
|
||||
var val, ctrl = $D('noVNC_' + name);
|
||||
if (ctrl.type === 'checkbox') {
|
||||
val = ctrl.checked;
|
||||
} else if (typeof ctrl.options !== 'undefined') {
|
||||
@@ -217,18 +184,77 @@ initSetting: function(name, defVal) {
|
||||
val = WebUtil.readCookie(name, defVal);
|
||||
}
|
||||
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;
|
||||
},
|
||||
|
||||
|
||||
// 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:
|
||||
// On open, settings are refreshed from saved cookies.
|
||||
// On close, settings are applied
|
||||
clickSettingsMenu: function() {
|
||||
toggleSettingsPanel: function() {
|
||||
// Close the description panel
|
||||
$D('noVNC_description').style.display = "none";
|
||||
if (UI.settingsOpen) {
|
||||
UI.settingsApply();
|
||||
|
||||
UI.closeSettingsMenu();
|
||||
} else {
|
||||
UI.updateSetting('encrypt');
|
||||
@@ -237,10 +263,13 @@ clickSettingsMenu: function() {
|
||||
UI.updateSetting('cursor');
|
||||
} else {
|
||||
UI.updateSetting('cursor', false);
|
||||
$D('VNC_cursor').disabled = true;
|
||||
$D('noVNC_cursor').disabled = true;
|
||||
}
|
||||
UI.updateSetting('clip');
|
||||
UI.updateSetting('shared');
|
||||
UI.updateSetting('view_only');
|
||||
UI.updateSetting('connectTimeout');
|
||||
UI.updateSetting('path');
|
||||
UI.updateSetting('stylesheet');
|
||||
UI.updateSetting('logging');
|
||||
|
||||
@@ -250,32 +279,27 @@ clickSettingsMenu: function() {
|
||||
|
||||
// Open menu
|
||||
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;
|
||||
},
|
||||
|
||||
// Close menu (without applying settings)
|
||||
closeSettingsMenu: function() {
|
||||
$D('VNC_settings_menu').style.display = "none";
|
||||
$D('noVNC_settings').style.display = "none";
|
||||
$D('settingsButton').className = "noVNC_status_button";
|
||||
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
|
||||
settingsApply: function() {
|
||||
//Util.Debug(">> settingsApply");
|
||||
@@ -284,22 +308,31 @@ settingsApply: function() {
|
||||
if (UI.rfb.get_display().get_cursor_uri()) {
|
||||
UI.saveSetting('cursor');
|
||||
}
|
||||
UI.saveSetting('clip');
|
||||
UI.saveSetting('shared');
|
||||
UI.saveSetting('view_only');
|
||||
UI.saveSetting('connectTimeout');
|
||||
UI.saveSetting('path');
|
||||
UI.saveSetting('stylesheet');
|
||||
UI.saveSetting('logging');
|
||||
|
||||
// Settings with immediate (non-connected related) effect
|
||||
WebUtil.selectStylesheet(UI.getSetting('stylesheet'));
|
||||
WebUtil.init_logging(UI.getSetting('logging'));
|
||||
|
||||
UI.setViewClip();
|
||||
UI.setViewDrag(UI.rfb.get_viewportDrag());
|
||||
//Util.Debug("<< settingsApply");
|
||||
},
|
||||
|
||||
|
||||
|
||||
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;
|
||||
},
|
||||
|
||||
@@ -307,52 +340,63 @@ sendCtrlAltDel: function() {
|
||||
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) {
|
||||
var s, sb, c, cad, klass;
|
||||
s = $D('VNC_status');
|
||||
sb = $D('VNC_status_bar');
|
||||
c = $D('VNC_connect_button');
|
||||
cad = $D('sendCtrlAltDelButton');
|
||||
var s, sb, c, d, cad, vd, klass;
|
||||
UI.rfb_state = state;
|
||||
s = $D('noVNC_status');
|
||||
sb = $D('noVNC_status_bar');
|
||||
switch (state) {
|
||||
case 'failed':
|
||||
case 'fatal':
|
||||
c.disabled = true;
|
||||
cad.disabled = true;
|
||||
UI.settingsDisabled(true, rfb);
|
||||
klass = "VNC_status_error";
|
||||
klass = "noVNC_status_error";
|
||||
break;
|
||||
case 'normal':
|
||||
c.value = "Disconnect";
|
||||
c.onclick = UI.disconnect;
|
||||
c.disabled = false;
|
||||
cad.disabled = false;
|
||||
UI.settingsDisabled(true, rfb);
|
||||
klass = "VNC_status_normal";
|
||||
klass = "noVNC_status_normal";
|
||||
break;
|
||||
case 'disconnected':
|
||||
$D('noVNC_logo').style.display = "block";
|
||||
// Fall through
|
||||
case 'loaded':
|
||||
c.value = "Connect";
|
||||
c.onclick = UI.connect;
|
||||
|
||||
c.disabled = false;
|
||||
cad.disabled = true;
|
||||
UI.settingsDisabled(false, rfb);
|
||||
klass = "VNC_status_normal";
|
||||
klass = "noVNC_status_normal";
|
||||
break;
|
||||
case 'password':
|
||||
c.value = "Send Password";
|
||||
c.onclick = UI.setPassword;
|
||||
UI.toggleConnectPanel();
|
||||
|
||||
c.disabled = false;
|
||||
cad.disabled = true;
|
||||
UI.settingsDisabled(true, rfb);
|
||||
klass = "VNC_status_warn";
|
||||
$D('noVNC_connect_button').value = "Send Password";
|
||||
$D('noVNC_connect_button').onclick = UI.setPassword;
|
||||
$D('noVNC_password').focus();
|
||||
|
||||
klass = "noVNC_status_warn";
|
||||
break;
|
||||
default:
|
||||
c.disabled = true;
|
||||
cad.disabled = true;
|
||||
UI.settingsDisabled(true, rfb);
|
||||
klass = "VNC_status_warn";
|
||||
klass = "noVNC_status_warn";
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -362,23 +406,79 @@ updateState: function(rfb, state, oldstate, 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;
|
||||
|
||||
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) {
|
||||
Util.Debug(">> UI.clipReceive: " + text.substr(0,40) + "...");
|
||||
$D('VNC_clipboard_text').value = text;
|
||||
$D('noVNC_clipboard_text').value = text;
|
||||
Util.Debug("<< UI.clipReceive");
|
||||
},
|
||||
|
||||
|
||||
connect: function() {
|
||||
var host, port, password;
|
||||
var host, port, password, path;
|
||||
|
||||
UI.closeSettingsMenu();
|
||||
UI.toggleConnectPanel();
|
||||
|
||||
host = $D('VNC_host').value;
|
||||
port = $D('VNC_port').value;
|
||||
password = $D('VNC_password').value;
|
||||
host = $D('noVNC_host').value;
|
||||
port = $D('noVNC_port').value;
|
||||
password = $D('noVNC_password').value;
|
||||
path = $D('noVNC_path').value;
|
||||
if ((!host) || (!port)) {
|
||||
throw("Must set host and port");
|
||||
}
|
||||
@@ -387,15 +487,22 @@ connect: function() {
|
||||
UI.rfb.set_true_color(UI.getSetting('true_color'));
|
||||
UI.rfb.set_local_cursor(UI.getSetting('cursor'));
|
||||
UI.rfb.set_shared(UI.getSetting('shared'));
|
||||
UI.rfb.set_view_only(UI.getSetting('view_only'));
|
||||
UI.rfb.set_connectTimeout(UI.getSetting('connectTimeout'));
|
||||
|
||||
UI.rfb.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() {
|
||||
UI.closeSettingsMenu();
|
||||
|
||||
UI.rfb.disconnect();
|
||||
|
||||
$D('noVNC_logo').style.display = "block";
|
||||
UI.connSettingsOpen = false;
|
||||
UI.toggleConnectPanel();
|
||||
},
|
||||
|
||||
displayBlur: function() {
|
||||
@@ -409,15 +516,138 @@ displayFocus: function() {
|
||||
},
|
||||
|
||||
clipClear: function() {
|
||||
$D('VNC_clipboard_text').value = "";
|
||||
$D('noVNC_clipboard_text').value = "";
|
||||
UI.rfb.clipboardPasteFrom("");
|
||||
},
|
||||
|
||||
clipSend: function() {
|
||||
var text = $D('VNC_clipboard_text').value;
|
||||
var text = $D('noVNC_clipboard_text').value;
|
||||
Util.Debug(">> UI.clipSend: " + text.substr(0,40) + "...");
|
||||
UI.rfb.clipboardPasteFrom(text);
|
||||
Util.Debug("<< UI.clipSend");
|
||||
},
|
||||
|
||||
|
||||
// Enable/disable and configure viewport clipping
|
||||
setViewClip: function(clip) {
|
||||
var display, cur_clip, pos, new_w, new_h;
|
||||
|
||||
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';
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -33,6 +33,30 @@ Array.prototype.push32 = function (num) {
|
||||
(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;
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* ------------------------------------------------------
|
||||
* Namespaced in Util
|
||||
@@ -159,7 +183,7 @@ Util.conf_defaults = function(cfg, api, defaults, arr) {
|
||||
Util.conf_default(cfg, api, defaults, arr[i][0], arr[i][1],
|
||||
arr[i][2], arr[i][3], arr[i][4]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
@@ -184,6 +208,7 @@ Util.getEventPosition = function (e, obj, scale) {
|
||||
var evt, docX, docY, pos;
|
||||
//if (!e) evt = window.event;
|
||||
evt = (e ? e : window.event);
|
||||
evt = (evt.changedTouches ? evt.changedTouches[0] : evt.touches ? evt.touches[0] : evt);
|
||||
if (evt.pageX || evt.pageY) {
|
||||
docX = evt.pageX;
|
||||
docY = evt.pageY;
|
||||
@@ -239,8 +264,11 @@ Util.stopEvent = function(e) {
|
||||
Util.Features = {xpath: !!(document.evaluate), air: !!(window.runtime), query: !!(document.querySelector)};
|
||||
|
||||
Util.Engine = {
|
||||
'presto': (function() {
|
||||
return (!window.opera) ? false : ((arguments.callee.caller) ? 960 : ((document.getElementsByClassName) ? 950 : 925)); }()),
|
||||
// Version detection break in Opera 11.60 (errors on arguments.callee.caller reference)
|
||||
//'presto': (function() {
|
||||
// return (!window.opera) ? false : ((arguments.callee.caller) ? 960 : ((document.getElementsByClassName) ? 950 : 925)); }()),
|
||||
'presto': (function() { return (!window.opera) ? false : true; }()),
|
||||
|
||||
'trident': (function() {
|
||||
return (!window.ActiveXObject) ? false : ((window.XMLHttpRequest) ? ((document.querySelectorAll) ? 6 : 5) : 4); }()),
|
||||
'webkit': (function() {
|
||||
|
||||
@@ -30,13 +30,13 @@ function get_INCLUDE_URI() {
|
||||
|
||||
extra += start + "util.js" + end;
|
||||
extra += start + "webutil.js" + end;
|
||||
extra += start + "logo.js" + end;
|
||||
extra += start + "base64.js" + end;
|
||||
extra += start + "websock.js" + end;
|
||||
extra += start + "des.js" + end;
|
||||
extra += start + "input.js" + end;
|
||||
extra += start + "display.js" + end;
|
||||
extra += start + "rfb.js" + end;
|
||||
extra += start + "jsunzip.js" + end;
|
||||
|
||||
document.write(extra);
|
||||
}());
|
||||
|
||||
@@ -14,13 +14,23 @@
|
||||
* read binary data off of the receive queue.
|
||||
*/
|
||||
|
||||
/*jslint browser: true, bitwise: false, plusplus: false */
|
||||
/*global Util, Base64 */
|
||||
|
||||
|
||||
// Load Flash WebSocket emulator if needed
|
||||
|
||||
if (window.WebSocket) {
|
||||
if (window.WebSocket && !window.WEB_SOCKET_FORCE_FLASH) {
|
||||
Websock_native = true;
|
||||
} else if (window.MozWebSocket && !window.WEB_SOCKET_FORCE_FLASH) {
|
||||
Websock_native = true;
|
||||
window.WebSocket = window.MozWebSocket;
|
||||
} else {
|
||||
/* no builtin WebSocket so load web_socket.js */
|
||||
|
||||
// To enable debug:
|
||||
// window.WEB_SOCKET_DEBUG=1;
|
||||
|
||||
Websock_native = false;
|
||||
(function () {
|
||||
function get_INCLUDE_URI() {
|
||||
@@ -31,11 +41,11 @@ if (window.WebSocket) {
|
||||
var start = "<script src='" + get_INCLUDE_URI(),
|
||||
end = "'><\/script>", extra = "";
|
||||
|
||||
WEB_SOCKET_SWF_LOCATION = get_INCLUDE_URI() +
|
||||
window.WEB_SOCKET_SWF_LOCATION = get_INCLUDE_URI() +
|
||||
"web-socket-js/WebSocketMain.swf";
|
||||
if (Util.Engine.trident) {
|
||||
Util.Debug("Forcing uncached load of WebSocketMain.swf");
|
||||
WEB_SOCKET_SWF_LOCATION += "?" + Math.random();
|
||||
window.WEB_SOCKET_SWF_LOCATION += "?" + Math.random();
|
||||
}
|
||||
extra += start + "web-socket-js/swfobject.js" + end;
|
||||
extra += start + "web-socket-js/web_socket.js" + end;
|
||||
@@ -80,7 +90,7 @@ function get_rQi() {
|
||||
}
|
||||
function set_rQi(val) {
|
||||
rQi = val;
|
||||
};
|
||||
}
|
||||
|
||||
function rQlen() {
|
||||
return rQ.length - rQi;
|
||||
@@ -112,6 +122,7 @@ function rQshift32() {
|
||||
(rQ[rQi++] );
|
||||
}
|
||||
function rQshiftStr(len) {
|
||||
if (typeof(len) === 'undefined') { len = rQlen(); }
|
||||
var arr = rQ.slice(rQi, rQi + len);
|
||||
rQi += len;
|
||||
return arr.map(function (num) {
|
||||
@@ -119,6 +130,7 @@ function rQshiftStr(len) {
|
||||
|
||||
}
|
||||
function rQshiftBytes(len) {
|
||||
if (typeof(len) === 'undefined') { len = rQlen(); }
|
||||
rQi += len;
|
||||
return rQ.slice(rQi-len, rQi);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
"use strict";
|
||||
/*jslint bitwise: false, white: false */
|
||||
/*global window, document */
|
||||
/*global Util, window, document */
|
||||
|
||||
// Globals defined here
|
||||
var WebUtil = {}, $D;
|
||||
@@ -17,7 +17,7 @@ var WebUtil = {}, $D;
|
||||
* Simple DOM selector by ID
|
||||
*/
|
||||
if (!window.$D) {
|
||||
$D = function (id) {
|
||||
window.$D = function (id) {
|
||||
if (document.getElementById) {
|
||||
return document.getElementById(id);
|
||||
} else if (document.all) {
|
||||
@@ -42,8 +42,8 @@ WebUtil.init_logging = function() {
|
||||
/logging=([A-Za-z0-9\._\-]*)/) ||
|
||||
['', Util._log_level])[1];
|
||||
|
||||
Util.init_logging()
|
||||
}
|
||||
Util.init_logging();
|
||||
};
|
||||
WebUtil.init_logging();
|
||||
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<script src="../include/util.js"></script>
|
||||
<script src="../include/webutil.js"></script>
|
||||
<script src="../include/base64.js"></script>
|
||||
<script src="../include/canvas.js"></script>
|
||||
<script src="../include/display.js"></script>
|
||||
<script src="face.png.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
@@ -36,7 +36,7 @@
|
||||
|
||||
<script>
|
||||
var msg_cnt = 0;
|
||||
var start_width = 300, start_height = 100;
|
||||
var display, start_width = 300, start_height = 100;
|
||||
var iterations;
|
||||
|
||||
function message(str) {
|
||||
@@ -48,12 +48,12 @@
|
||||
}
|
||||
|
||||
function test_functions () {
|
||||
var img, x, y, w, h, ctx = canvas.getContext();
|
||||
w = canvas.get_width();
|
||||
h = canvas.get_height();
|
||||
canvas.fillRect(0, 0, w, h, [240,240,240]);
|
||||
var img, x, y, w, h, ctx = display.get_context();
|
||||
w = display.get_width();
|
||||
h = display.get_height();
|
||||
display.fillRect(0, 0, w, h, [240,240,240]);
|
||||
|
||||
canvas.blitStringImage("data:image/png;base64," + face64, 150, 10);
|
||||
display.blitStringImage("data:image/png;base64," + face64, 150, 10);
|
||||
|
||||
var himg = new Image();
|
||||
himg.onload = function () {
|
||||
@@ -70,14 +70,14 @@
|
||||
data[(y*50 + x)*4 + 3] = 255;
|
||||
}
|
||||
}
|
||||
canvas.blitImage(30, 10, 50, 50, data, 0);
|
||||
display.blitImage(30, 10, 50, 50, data, 0);
|
||||
|
||||
img = canvas.getTile(5,5,16,16,[0,128,128]);
|
||||
canvas.putTile(img);
|
||||
img = display.getTile(5,5,16,16,[0,128,128]);
|
||||
display.putTile(img);
|
||||
|
||||
img = canvas.getTile(90,15,16,16,[0,0,0]);
|
||||
canvas.setSubTile(img, 0,0,16,16,[128,128,0]);
|
||||
canvas.putTile(img);
|
||||
img = display.getTile(90,15,16,16,[0,0,0]);
|
||||
display.setSubTile(img, 0,0,16,16,[128,128,0]);
|
||||
display.putTile(img);
|
||||
}
|
||||
|
||||
function begin () {
|
||||
@@ -90,7 +90,7 @@
|
||||
function start_delayed () {
|
||||
var ret;
|
||||
|
||||
ret = canvas.set_prefer_js(true);
|
||||
ret = display.set_prefer_js(true);
|
||||
if (ret) {
|
||||
message("Running test: prefer Javascript ops");
|
||||
var time1 = run_test();
|
||||
@@ -100,14 +100,14 @@
|
||||
message("Could not run: prefer Javascript ops");
|
||||
}
|
||||
|
||||
canvas.set_prefer_js(false);
|
||||
display.set_prefer_js(false);
|
||||
message("Running test: prefer Canvas ops");
|
||||
var time2 = run_test();
|
||||
message("prefer Canvas ops: " + time2 + "ms total, " +
|
||||
(time2 / iterations) + "ms per frame");
|
||||
|
||||
if (Util.get_logging() !== 'debug') {
|
||||
canvas.resize(start_width, start_height, true);
|
||||
display.resize(start_width, start_height, true);
|
||||
test_functions();
|
||||
}
|
||||
$D('startButton').disabled = false;
|
||||
@@ -118,7 +118,7 @@
|
||||
var width, height;
|
||||
width = $D('width').value;
|
||||
height = $D('height').value;
|
||||
canvas.resize(width, height);
|
||||
display.resize(width, height);
|
||||
var color, start_time = (new Date()).getTime(), w, h;
|
||||
for (var i=0; i < iterations; i++) {
|
||||
color = [128, 128, (255 / iterations) * i, 0];
|
||||
@@ -126,9 +126,9 @@
|
||||
for (var y=0; y < height; y = y + 16) {
|
||||
w = Math.min(16, width - x);
|
||||
h = Math.min(16, height - y);
|
||||
var tile = canvas.getTile(x, y, w, h, color);
|
||||
canvas.setSubTile(tile, 0, 0, w, h, color);
|
||||
canvas.putTile(tile);
|
||||
var tile = display.getTile(x, y, w, h, color);
|
||||
display.setSubTile(tile, 0, 0, w, h, color);
|
||||
display.putTile(tile);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -139,8 +139,8 @@
|
||||
window.onload = function() {
|
||||
message("in onload");
|
||||
$D('iterations').value = 10;
|
||||
canvas = new Canvas({'target' : $D('canvas')});
|
||||
canvas.resize(start_width, start_height, true);
|
||||
display = new Display({'target' : $D('canvas')});
|
||||
display.resize(start_width, start_height, true);
|
||||
message("Canvas initialized");
|
||||
test_functions();
|
||||
}
|
||||
|
||||
@@ -4,7 +4,11 @@
|
||||
<body>
|
||||
<br><br>
|
||||
|
||||
Canvas:<br>
|
||||
Canvas:
|
||||
<span id="button-selection" style="display: none;">
|
||||
<input id="button1" type="button" value="L"><input id="button2" type="button" value="M"><input id="button4" type="button" value="R">
|
||||
</span>
|
||||
<br>
|
||||
<canvas id="canvas" width="640" height="20"
|
||||
style="border-style: dotted; border-width: 1px;">
|
||||
Canvas not supported.
|
||||
@@ -23,11 +27,11 @@
|
||||
<script src="../include/webutil.js"></script>
|
||||
<script src="../include/base64.js"></script>
|
||||
<script src="../include/input.js"></script>
|
||||
<script src="../include/canvas.js"></script>
|
||||
<script src="../include/display.js"></script>
|
||||
<script>
|
||||
var msg_cnt = 0;
|
||||
var width = 400, height = 200;
|
||||
var iterations;
|
||||
var msg_cnt = 0, iterations,
|
||||
width = 400, height = 200,
|
||||
canvas, keyboard, mouse;
|
||||
|
||||
var newline = "\n";
|
||||
if (Util.Engine.trident) {
|
||||
@@ -61,17 +65,54 @@
|
||||
message(msg);
|
||||
}
|
||||
|
||||
function selectButton(num) {
|
||||
var b, blist = [1,2,4];
|
||||
|
||||
if (typeof num === 'undefined') {
|
||||
// Show the default
|
||||
num = mouse.get_touchButton();
|
||||
} else if (num === mouse.get_touchButton()) {
|
||||
// Set all buttons off (no clicks)
|
||||
mouse.set_touchButton(0);
|
||||
num = 0;
|
||||
} else {
|
||||
// Turn on one button
|
||||
mouse.set_touchButton(num);
|
||||
}
|
||||
|
||||
for (b = 0; b < blist.length; b++) {
|
||||
if (blist[b] === num) {
|
||||
$D('button' + blist[b]).style.backgroundColor = "black";
|
||||
$D('button' + blist[b]).style.color = "lightgray";
|
||||
} else {
|
||||
$D('button' + blist[b]).style.backgroundColor = "";
|
||||
$D('button' + blist[b]).style.color = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.onload = function() {
|
||||
var canvas = new Canvas({'target' : $D('canvas')});
|
||||
canvas = new Display({'target' : $D('canvas')});
|
||||
keyboard = new Keyboard({'target': document,
|
||||
'keyPress': keyPress});
|
||||
'onKeyPress': keyPress});
|
||||
mouse = new Mouse({'target': $D('canvas'),
|
||||
'mouseButton': mouseButton,
|
||||
'mouseMove': mouseMove});
|
||||
'onMouseButton': mouseButton,
|
||||
'onMouseMove': mouseMove});
|
||||
|
||||
canvas.resize(width, height, true);
|
||||
keyboard.grab();
|
||||
mouse.grab();
|
||||
message("Canvas initialized");
|
||||
message("Display initialized");
|
||||
|
||||
if ('ontouchstart' in document.documentElement) {
|
||||
message("Touch device detected");
|
||||
$D('button-selection').style.display = "inline";
|
||||
$D('button1').onclick = function(){ selectButton(1) };
|
||||
$D('button2').onclick = function(){ selectButton(2) };
|
||||
$D('button4').onclick = function(){ selectButton(4) };
|
||||
selectButton();
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
</html>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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>
|
||||
@@ -2,7 +2,7 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>VNC Performance Benchmark</title>
|
||||
<link rel="stylesheet" href="../include/plain.css">
|
||||
<link rel="stylesheet" href="../include/base.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
</script>
|
||||
<script src="../include/vnc.js"></script>
|
||||
<script src="../include/playback.js"></script>
|
||||
<script src="data/multi.js"></script>
|
||||
<script src="../data/multi.js"></script>
|
||||
|
||||
<script>
|
||||
var start_time, VNC_frame_data, pass, passes, encIdx,
|
||||
@@ -126,7 +126,9 @@
|
||||
pass += 1;
|
||||
if (pass > passes) {
|
||||
// We are finished
|
||||
rfb.get_canvas().stop(); // Shut-off event interception
|
||||
// Shut-off event interception
|
||||
rfb.get_mouse().ungrab();
|
||||
rfb.get_keyboard().ungrab();
|
||||
$D('startButton').disabled = false;
|
||||
$D('startButton').value = "Start";
|
||||
finish_passes();
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
#!/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 rpc
|
||||
from nova import utils
|
||||
from nova.openstack.common import cfg
|
||||
|
||||
|
||||
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
|
||||
utils.default_flagfile()
|
||||
FLAGS(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()
|
||||
@@ -8,7 +8,7 @@ Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3)
|
||||
Supports following protocol versions:
|
||||
- http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75
|
||||
- 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:
|
||||
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 base64 import b64encode, b64decode
|
||||
|
||||
# Imports that vary by python version
|
||||
|
||||
# python 3.0 differences
|
||||
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')
|
||||
s2b = lambda s: s.encode('latin_1')
|
||||
s2a = lambda s: s
|
||||
else:
|
||||
# python 2.X
|
||||
from cStringIO import StringIO
|
||||
from SimpleHTTPServer import SimpleHTTPRequestHandler
|
||||
from urlparse import urlsplit
|
||||
# No-ops
|
||||
b2s = lambda buf: buf
|
||||
s2b = lambda s: s
|
||||
b2s = lambda buf: buf # No-op
|
||||
s2b = lambda s: s # No-op
|
||||
s2a = lambda s: [ord(c) for c in s]
|
||||
try: from io import StringIO
|
||||
except: from cStringIO import StringIO
|
||||
try: from http.server import SimpleHTTPRequestHandler
|
||||
except: from SimpleHTTPServer import SimpleHTTPRequestHandler
|
||||
try: from urllib.parse import urlsplit
|
||||
except: from urlparse import urlsplit
|
||||
|
||||
if sys.hexversion >= 0x2060000:
|
||||
# python >= 2.6
|
||||
from multiprocessing import Process
|
||||
from hashlib import md5, sha1
|
||||
else:
|
||||
# python < 2.6
|
||||
Process = None
|
||||
from md5 import md5
|
||||
from sha import sha as sha1
|
||||
# python 2.6 differences
|
||||
try: from hashlib import md5, sha1
|
||||
except: from md5 import md5; from sha import sha as sha1
|
||||
|
||||
# python 2.5 differences
|
||||
try:
|
||||
from struct import pack, unpack_from
|
||||
except:
|
||||
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
|
||||
for mod, sup in [('numpy', 'HyBi protocol'),
|
||||
('ctypes', 'HyBi protocol'), ('ssl', 'TLS/SSL/wss'),
|
||||
for mod, sup in [('numpy', 'HyBi protocol'), ('ssl', 'TLS/SSL/wss'),
|
||||
('multiprocessing', 'Multi-Processing'),
|
||||
('resource', 'daemonizing')]:
|
||||
try:
|
||||
globals()[mod] = __import__(mod)
|
||||
except ImportError:
|
||||
globals()[mod] = None
|
||||
print("WARNING: no '%s' module, %s support disabled" % (
|
||||
print("WARNING: no '%s' module, %s is slower or disabled" % (
|
||||
mod, sup))
|
||||
if multiprocessing and sys.platform == 'win32':
|
||||
# make sockets pickle-able/inheritable
|
||||
import multiprocessing.reduction
|
||||
|
||||
|
||||
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"""
|
||||
|
||||
# An exception before the WebSocket connection was established
|
||||
class EClose(Exception):
|
||||
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,
|
||||
verbose=False, cert='', key='', ssl_only=None,
|
||||
daemon=False, record='', web=''):
|
||||
daemon=False, record='', web='',
|
||||
run_once=False, timeout=0):
|
||||
|
||||
# settings
|
||||
self.verbose = verbose
|
||||
@@ -97,6 +111,11 @@ Sec-WebSocket-Accept: %s\r
|
||||
self.listen_port = listen_port
|
||||
self.ssl_only = ssl_only
|
||||
self.daemon = daemon
|
||||
self.run_once = run_once
|
||||
self.timeout = timeout
|
||||
|
||||
self.launch_time = time.time()
|
||||
self.ws_connection = False
|
||||
self.handler_id = 1
|
||||
|
||||
# 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.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
|
||||
def encode_hybi(buf, opcode, base64=False):
|
||||
""" Encode a HyBi style WebSocket frame.
|
||||
@@ -225,11 +276,11 @@ Sec-WebSocket-Accept: %s\r
|
||||
b1 = 0x80 | (opcode & 0x0f) # FIN + opcode
|
||||
payload_len = len(buf)
|
||||
if payload_len <= 125:
|
||||
header = struct.pack('>BB', b1, payload_len)
|
||||
elif payload_len > 125 and payload_len <= 65536:
|
||||
header = struct.pack('>BBH', b1, 126, payload_len)
|
||||
header = pack('>BB', b1, payload_len)
|
||||
elif payload_len > 125 and payload_len < 65536:
|
||||
header = pack('>BBH', b1, 126, payload_len)
|
||||
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))
|
||||
|
||||
@@ -257,8 +308,8 @@ Sec-WebSocket-Accept: %s\r
|
||||
'length' : 0,
|
||||
'payload' : None,
|
||||
'left' : 0,
|
||||
'close_code' : None,
|
||||
'close_reason' : None}
|
||||
'close_code' : 1000,
|
||||
'close_reason' : ''}
|
||||
|
||||
blen = len(buf)
|
||||
f['left'] = blen
|
||||
@@ -266,7 +317,7 @@ Sec-WebSocket-Accept: %s\r
|
||||
if blen < f['hlen']:
|
||||
return f # Incomplete frame header
|
||||
|
||||
b1, b2 = struct.unpack_from(">BB", buf)
|
||||
b1, b2 = unpack_from(">BB", buf)
|
||||
f['opcode'] = b1 & 0x0f
|
||||
f['fin'] = (b1 & 0x80) >> 7
|
||||
has_mask = (b2 & 0x80) >> 7
|
||||
@@ -277,12 +328,12 @@ Sec-WebSocket-Accept: %s\r
|
||||
f['hlen'] = 4
|
||||
if blen < f['hlen']:
|
||||
return f # Incomplete frame header
|
||||
(f['length'],) = struct.unpack_from('>xxH', buf)
|
||||
(f['length'],) = unpack_from('>xxH', buf)
|
||||
elif f['length'] == 127:
|
||||
f['hlen'] = 10
|
||||
if blen < f['hlen']:
|
||||
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']
|
||||
|
||||
@@ -296,24 +347,7 @@ Sec-WebSocket-Accept: %s\r
|
||||
if has_mask:
|
||||
# unmask payload
|
||||
f['mask'] = buf[f['hlen']:f['hlen']+4]
|
||||
b = c = ''
|
||||
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
|
||||
f['payload'] = WebSocketServer.unmask(buf, f)
|
||||
else:
|
||||
print("Unmasked frame: %s" % repr(buf))
|
||||
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['length'] >= 2:
|
||||
f['close_code'] = struct.unpack_from(">H", f['payload'])
|
||||
f['close_code'] = unpack_from(">H", f['payload'])[0]
|
||||
if f['length'] > 3:
|
||||
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
|
||||
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())
|
||||
|
||||
#
|
||||
@@ -441,7 +475,7 @@ Sec-WebSocket-Accept: %s\r
|
||||
|
||||
buf = self.client.recv(self.buffer_size)
|
||||
if len(buf) == 0:
|
||||
closed = "Client closed abruptly"
|
||||
closed = {'code': 1000, 'reason': "Client closed abruptly"}
|
||||
return bufs, closed
|
||||
|
||||
if self.recv_part:
|
||||
@@ -463,17 +497,17 @@ Sec-WebSocket-Accept: %s\r
|
||||
break
|
||||
else:
|
||||
if frame['opcode'] == 0x8: # connection close
|
||||
closed = "Client closed, reason: %s - %s" % (
|
||||
frame['close_code'],
|
||||
frame['close_reason'])
|
||||
closed = {'code': frame['close_code'],
|
||||
'reason': frame['close_reason']}
|
||||
break
|
||||
|
||||
else:
|
||||
if buf[0:2] == '\xff\x00':
|
||||
closed = "Client sent orderly close frame"
|
||||
if buf[0:2] == s2b('\xff\x00'):
|
||||
closed = {'code': 1000,
|
||||
'reason': "Client sent orderly close frame"}
|
||||
break
|
||||
|
||||
elif buf[0:2] == '\x00\xff':
|
||||
elif buf[0:2] == s2b('\x00\xff'):
|
||||
buf = buf[2:]
|
||||
continue # No-op
|
||||
|
||||
@@ -503,13 +537,11 @@ Sec-WebSocket-Accept: %s\r
|
||||
|
||||
return bufs, closed
|
||||
|
||||
def send_close(self, code=None, reason=''):
|
||||
def send_close(self, code=1000, reason=''):
|
||||
""" Send a WebSocket orderly close frame. """
|
||||
|
||||
if self.version.startswith("hybi"):
|
||||
msg = s2b('')
|
||||
if code != None:
|
||||
msg = struct.pack(">H%ds" % (len(reason)), code)
|
||||
msg = pack(">H%ds" % len(reason), code, reason)
|
||||
|
||||
buf, h, t = self.encode_hybi(msg, opcode=0x08, base64=False)
|
||||
self.client.send(buf)
|
||||
@@ -557,7 +589,7 @@ Sec-WebSocket-Accept: %s\r
|
||||
sock.send(s2b(self.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
|
||||
if not ssl:
|
||||
raise self.EClose("SSL connection but no 'ssl' module")
|
||||
@@ -574,7 +606,10 @@ Sec-WebSocket-Accept: %s\r
|
||||
except ssl.SSLError:
|
||||
_, x, _ = sys.exc_info()
|
||||
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:
|
||||
raise
|
||||
|
||||
@@ -612,11 +647,11 @@ Sec-WebSocket-Accept: %s\r
|
||||
if ver:
|
||||
# HyBi/IETF version of the protocol
|
||||
|
||||
if sys.hexversion < 0x2060000 or not numpy:
|
||||
raise self.EClose("Python >= 2.6 and numpy module is required for HyBi-07 or greater")
|
||||
|
||||
if ver in ['7', '8', '9']:
|
||||
self.version = "hybi-0" + ver
|
||||
# HyBi-07 report version 7
|
||||
# HyBi-08 - HyBi-12 report version 8
|
||||
# HyBi-13 reports version 13
|
||||
if ver in ['7', '8', '13']:
|
||||
self.version = "hybi-%02d" % int(ver)
|
||||
else:
|
||||
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
|
||||
accept = b64encode(sha1(s2b(key + self.GUID)).digest())
|
||||
|
||||
response = self.server_handshake_hybi % accept
|
||||
response = self.server_handshake_hybi % b2s(accept)
|
||||
if self.base64:
|
||||
response += "Sec-WebSocket-Protocol: base64\r\n"
|
||||
else:
|
||||
@@ -667,6 +702,9 @@ Sec-WebSocket-Accept: %s\r
|
||||
self.msg("%s: %s WebSocket connection" % (address[0], stype))
|
||||
self.msg("%s: Version %s, base64: '%s'" % (address[0],
|
||||
self.version, self.base64))
|
||||
if self.path != '/':
|
||||
self.msg("%s: Path: '%s'" % (address[0], self.path))
|
||||
|
||||
|
||||
# Send server WebSockets handshake response
|
||||
#self.msg("sending response [%s]" % response)
|
||||
@@ -725,7 +763,13 @@ Sec-WebSocket-Accept: %s\r
|
||||
self.rec = open(fname, 'w+')
|
||||
self.rec.write("var VNC_frame_data = [\n")
|
||||
|
||||
self.ws_connection = True
|
||||
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:
|
||||
_, exc, _ = sys.exc_info()
|
||||
# Connection was not a WebSockets connection
|
||||
@@ -742,6 +786,8 @@ Sec-WebSocket-Accept: %s\r
|
||||
self.rec.close()
|
||||
|
||||
if self.client and self.client != startsock:
|
||||
# Close the SSL wrapped socket
|
||||
# Original socket closed by caller
|
||||
self.client.close()
|
||||
|
||||
def new_client(self):
|
||||
@@ -764,7 +810,7 @@ Sec-WebSocket-Accept: %s\r
|
||||
|
||||
# Allow override of SIGINT
|
||||
signal.signal(signal.SIGINT, self.do_SIGINT)
|
||||
if not Process:
|
||||
if not multiprocessing:
|
||||
# os.fork() (python 2.4) child reaper
|
||||
signal.signal(signal.SIGCHLD, self.fallback_SIGCHLD)
|
||||
|
||||
@@ -775,6 +821,12 @@ Sec-WebSocket-Accept: %s\r
|
||||
startsock = None
|
||||
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:
|
||||
self.poll()
|
||||
|
||||
@@ -797,9 +849,17 @@ Sec-WebSocket-Accept: %s\r
|
||||
else:
|
||||
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])
|
||||
p = Process(target=self.top_new_client,
|
||||
p = multiprocessing.Process(
|
||||
target=self.top_new_client,
|
||||
args=(startsock, address))
|
||||
p.start()
|
||||
# child will not return
|
||||
|
||||
@@ -190,7 +190,8 @@ Traffic Legend:
|
||||
if target in ins:
|
||||
# Receive target data, encode it and queue for client
|
||||
buf = target.recv(self.buffer_size)
|
||||
if len(buf) == 0: raise self.EClose("Target closed")
|
||||
if len(buf) == 0:
|
||||
raise self.CClose(1000, "Target closed")
|
||||
|
||||
cqueue.append(buf)
|
||||
self.traffic("{")
|
||||
@@ -210,10 +211,9 @@ Traffic Legend:
|
||||
|
||||
if closed:
|
||||
# TODO: What about blocking on client socket?
|
||||
self.send_close()
|
||||
raise self.EClose(closed)
|
||||
raise self.CClose(closed['code'], closed['reason'])
|
||||
|
||||
if __name__ == '__main__':
|
||||
def websockify_init():
|
||||
usage = "\n %prog [options]"
|
||||
usage += " [source_addr:]source_port target_addr:target_port"
|
||||
usage += "\n %prog [options]"
|
||||
@@ -226,6 +226,10 @@ if __name__ == '__main__':
|
||||
parser.add_option("--daemon", "-D",
|
||||
dest="daemon", action="store_true",
|
||||
help="become a daemon (background process)")
|
||||
parser.add_option("--run-once", action="store_true",
|
||||
help="handle a single WebSocket connection and exit")
|
||||
parser.add_option("--timeout", type=int, default=0,
|
||||
help="after TIMEOUT seconds exit when not connected")
|
||||
parser.add_option("--cert", default="self.pem",
|
||||
help="SSL certificate file")
|
||||
parser.add_option("--key", default=None,
|
||||
@@ -255,7 +259,7 @@ if __name__ == '__main__':
|
||||
|
||||
# Parse host:port and convert ports to numbers
|
||||
if args[0].count(':') > 0:
|
||||
opts.listen_host, sep, opts.listen_port = args[0].rpartition(':')
|
||||
opts.listen_host, opts.listen_port = args[0].rsplit(':', 1)
|
||||
else:
|
||||
opts.listen_host, opts.listen_port = '', args[0]
|
||||
|
||||
@@ -267,7 +271,7 @@ if __name__ == '__main__':
|
||||
opts.target_port = None
|
||||
else:
|
||||
if args[1].count(':') > 0:
|
||||
opts.target_host, sep, opts.target_port = args[1].rpartition(':')
|
||||
opts.target_host, opts.target_port = args[1].rsplit(':', 1)
|
||||
else:
|
||||
parser.error("Error parsing target")
|
||||
try: opts.target_port = int(opts.target_port)
|
||||
@@ -276,3 +280,6 @@ if __name__ == '__main__':
|
||||
# Create and start the WebSockets proxy
|
||||
server = WebSocketProxy(**opts.__dict__)
|
||||
server.start_server()
|
||||
|
||||
if __name__ == '__main__':
|
||||
websockify_init()
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
websockify
|
||||
@@ -1,31 +1,198 @@
|
||||
<!DOCTYPE html>
|
||||
<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.1//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile11.dtd">
|
||||
<html>
|
||||
<!--
|
||||
<head>
|
||||
|
||||
<!--
|
||||
noVNC example: simple example using default UI
|
||||
Copyright (C) 2011 Joel Martin
|
||||
Licensed under LGPL-3 (see LICENSE.txt)
|
||||
-->
|
||||
<head>
|
||||
<title>noVNC</title>
|
||||
<meta http-equiv="X-UA-Compatible" content="chrome=1">
|
||||
<link rel="stylesheet" href="include/plain.css">
|
||||
<link rel="alternate stylesheet" href="include/black.css" TITLE="Black">
|
||||
<!--
|
||||
<script type='text/javascript'
|
||||
src='http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js'></script>
|
||||
-->
|
||||
<script src="include/vnc.js"></script>
|
||||
<script src="include/ui.js"></script>
|
||||
</head>
|
||||
<title>noVNC</title>
|
||||
|
||||
<body>
|
||||
<div id='vnc'>Loading</div>
|
||||
<meta charset="utf-8">
|
||||
|
||||
<script>
|
||||
window.onload = function () {
|
||||
UI.load('vnc');
|
||||
};
|
||||
</script>
|
||||
<!-- Always force latest IE rendering engine (even in intranet) & Chrome Frame
|
||||
Remove this if you use the .htaccess -->
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
|
||||
</body>
|
||||
<!-- Apple iOS Safari settings -->
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta 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>
|
||||
<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>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<head>
|
||||
<title>noVNC</title>
|
||||
<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'
|
||||
src='http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js'></script>
|
||||
@@ -20,17 +20,17 @@
|
||||
</head>
|
||||
|
||||
<body style="margin: 0px;">
|
||||
<div id="VNC_screen">
|
||||
<div id="VNC_status_bar" class="VNC_status_bar" style="margin-top: 0px;">
|
||||
<div id="noVNC_screen">
|
||||
<div id="noVNC_status_bar" class="noVNC_status_bar" style="margin-top: 0px;">
|
||||
<table border=0 width="100%"><tr>
|
||||
<td><div id="VNC_status">Loading</div></td>
|
||||
<td width="1%"><div id="VNC_buttons">
|
||||
<td><div id="noVNC_status">Loading</div></td>
|
||||
<td width="1%"><div id="noVNC_buttons">
|
||||
<input type=button value="Send CtrlAltDel"
|
||||
id="sendCtrlAltDelButton">
|
||||
</div></td>
|
||||
</tr></table>
|
||||
</div>
|
||||
<canvas id="VNC_canvas" width="640px" height="20px">
|
||||
<canvas id="noVNC_canvas" width="640px" height="20px">
|
||||
Canvas not supported.
|
||||
</canvas>
|
||||
</div>
|
||||
@@ -47,10 +47,10 @@
|
||||
msg = '<form onsubmit="return setPassword();"';
|
||||
msg += ' style="margin-bottom: 0px">';
|
||||
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>';
|
||||
$D('VNC_status_bar').setAttribute("class", "VNC_status_warn");
|
||||
$D('VNC_status').innerHTML = msg;
|
||||
$D('noVNC_status_bar').setAttribute("class", "noVNC_status_warn");
|
||||
$D('noVNC_status').innerHTML = msg;
|
||||
}
|
||||
function setPassword() {
|
||||
rfb.sendPassword($D('password_input').value);
|
||||
@@ -62,8 +62,8 @@
|
||||
}
|
||||
function updateState(rfb, state, oldstate, msg) {
|
||||
var s, sb, cad, level;
|
||||
s = $D('VNC_status');
|
||||
sb = $D('VNC_status_bar');
|
||||
s = $D('noVNC_status');
|
||||
sb = $D('noVNC_status_bar');
|
||||
cad = $D('sendCtrlAltDelButton');
|
||||
switch (state) {
|
||||
case 'failed': level = "error"; break;
|
||||
@@ -78,34 +78,47 @@
|
||||
else { cad.disabled = true; }
|
||||
|
||||
if (typeof(msg) !== 'undefined') {
|
||||
sb.setAttribute("class", "VNC_status_" + level);
|
||||
sb.setAttribute("class", "noVNC_status_" + level);
|
||||
s.innerHTML = msg;
|
||||
}
|
||||
}
|
||||
|
||||
window.onload = function () {
|
||||
var host, port, password;
|
||||
var host, port, password, path, token;
|
||||
|
||||
$D('sendCtrlAltDelButton').style.display = "inline";
|
||||
$D('sendCtrlAltDelButton').onclick = sendCtrlAltDel;
|
||||
|
||||
document.title = unescape(WebUtil.getQueryVar('title', 'noVNC'));
|
||||
host = WebUtil.getQueryVar('host', null);
|
||||
port = WebUtil.getQueryVar('port', null);
|
||||
// By default, use the host and port of server that served this file
|
||||
host = WebUtil.getQueryVar('host', window.location.hostname);
|
||||
port = WebUtil.getQueryVar('port', window.location.port);
|
||||
|
||||
// If 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', '');
|
||||
path = WebUtil.getQueryVar('path', 'websockify');
|
||||
if ((!host) || (!port)) {
|
||||
updateState('failed',
|
||||
"Must specify host and port in URL");
|
||||
return;
|
||||
}
|
||||
|
||||
rfb = new RFB({'target': $D('VNC_canvas'),
|
||||
'encrypt': WebUtil.getQueryVar('encrypt', false),
|
||||
rfb = new RFB({'target': $D('noVNC_canvas'),
|
||||
'encrypt': WebUtil.getQueryVar('encrypt',
|
||||
(window.location.protocol === "https:")),
|
||||
'true_color': WebUtil.getQueryVar('true_color', true),
|
||||
'local_cursor': WebUtil.getQueryVar('cursor', true),
|
||||
'shared': WebUtil.getQueryVar('shared', true),
|
||||
'view_only': WebUtil.getQueryVar('view_only', false),
|
||||
'updateState': updateState,
|
||||
'onPasswordRequired': passwordRequired});
|
||||
rfb.connect(host, port, password);
|
||||
rfb.connect(host, port, password, path);
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||