]> git.proxmox.com Git - mirror_novnc.git/commitdiff
Merge pull request #699 from CendioOssman/double
authorSamuel Mannehed <samuel@cendio.se>
Tue, 13 Dec 2016 11:42:22 +0000 (12:42 +0100)
committerGitHub <noreply@github.com>
Tue, 13 Dec 2016 11:42:22 +0000 (12:42 +0100)
Display double buffering

30 files changed:
.travis.yml
README.md
app/images/handle_bg.svg
app/locale/de.js [new file with mode: 0644]
app/locale/el.js [new file with mode: 0644]
app/locale/nl.js [new file with mode: 0644]
app/locale/sv.js [new file with mode: 0644]
app/styles/base.css
app/ui.js
app/webutil.js
core/input/keysymdef.js
core/input/util.js
core/rfb.js
core/util.js
package.json
po/Makefile [new file with mode: 0644]
po/de.po [new file with mode: 0644]
po/el.po [new file with mode: 0644]
po/nl.po [new file with mode: 0644]
po/noVNC.pot [new file with mode: 0644]
po/po2js [new file with mode: 0755]
po/sv.po [new file with mode: 0644]
po/xgettext-html [new file with mode: 0755]
tests/run_from_console.js
tests/test.helper.js
tests/test.rfb.js
tests/test.util.js
utils/parse.js
vnc.html
vnc_auto.html

index aa46a3130fb33f5f40e75c0b6f20b54d12234c94..a7fe42330261cf40e4a6281be5d66efb8bfd46f7 100644 (file)
@@ -4,16 +4,14 @@ cache:
   directories:
   - node_modules
 node_js:
-- '0.11.13'
+- '6.1'
 env:
   matrix:
   - TEST_BROWSER_NAME=PhantomJS
-  - TEST_BROWSER_NAME=chrome TEST_BROWSER_OS='Windows 7,Linux'
-  - TEST_BROWSER_NAME=firefox TEST_BROWSER_OS='Windows 7,Linux' TEST_BROWSER_VERSION='30,26'
-  - TEST_BROWSER_NAME='internet explorer' TEST_BROWSER_OS='Windows 7' TEST_BROWSER_VERSION=10
-  - TEST_BROWSER_NAME='internet explorer' TEST_BROWSER_OS='Windows 8.1' TEST_BROWSER_VERSION=11
-  - TEST_BROWSER_NAME=safari TEST_BROWSER_OS='OS X 10.8' TEST_BROWSER_VERSION=6
-  - TEST_BROWSER_NAME=safari TEST_BROWSER_OS='OS X 10.9' TEST_BROWSER_VERSION=7
+  - TEST_BROWSER_NAME=chrome TEST_BROWSER_OS='Windows 10,Linux,OS X 10.11'
+  - TEST_BROWSER_NAME=firefox TEST_BROWSER_OS='Windows 10,Linux,OS X 10.11'
+  - TEST_BROWSER_NAME='internet explorer' TEST_BROWSER_OS='Windows 10'
+  - TEST_BROWSER_NAME=safari TEST_BROWSER_OS='OS X 10.11'
   global:
   - secure: QE5GqGd2hrpQsIgd8dlv3oRUUHqZayomzzQjNXOB81VQi241uz/ru+3GtBZLB5WLZCq/Gj89vbLnR0LN4ixlmPaWv3/WJQGyDGuRD/vMnccVl+rBUP/Hh2zdYwiISIGcrywNAE+KLus/lyt/ahVgzbaRaDSzrM1HaZFT/rndGck=
   - secure: g75sdctEwj0hoLW0Y08Tdv8s5scNzplB6a9EtaJ2vJD9S/bK+AsPqbWesGv1UlrFPCWdbV7Vg61vkmoUjcmb5xhqFIjcM9TlYJoKWeOTsOmnQoSIkIq6gMF1k02+LmKInbPgIzrp3m3jluS1qaOs/EzFpDnJp9hWBiAfXa12Jxk=
index 59a72b103921b9b15b160658e57cda8badc2f8fa..767b76e5cc735073e9fe226d2b5cb77a5028bccf 100644 (file)
--- a/README.md
+++ b/README.md
@@ -4,42 +4,42 @@
 
 ### Description
 
-noVNC is a HTML5 VNC client that runs well in any modern browser
-including mobile browsers (iPhone/iPad and Android).
+noVNC is a HTML5 VNC client that runs well in any modern browser including
+mobile browsers (iOS and Android).
 
-Many companies/projects have integrated noVNC including [Ganeti Web
-Manager](http://code.osuosl.org/projects/ganeti-webmgr),
+Many companies, projects and products have integrated noVNC including
+[Ganeti Web Manager](http://code.osuosl.org/projects/ganeti-webmgr),
 [OpenStack](http://www.openstack.org),
-[OpenNebula](http://opennebula.org/), and
-[LibVNCServer](http://libvncserver.sourceforge.net). See [the Projects
-and Companies wiki
-page](https://github.com/kanaka/noVNC/wiki/ProjectsCompanies-using-noVNC)
+[OpenNebula](http://opennebula.org/),
+[LibVNCServer](http://libvncserver.sourceforge.net), and
+[ThinLinc](https://cendio.com/thinlinc). See
+[the Projects and Companies wiki page](https://github.com/kanaka/noVNC/wiki/Projects-and-companies-using-noVNC)
 for a more complete list with additional info and links.
 
 ### News/help/contact
 
 Notable commits, announcements and news are posted to
-<a href="http://www.twitter.com/noVNC">@noVNC</a>
+<a href="http://www.twitter.com/noVNC">@noVNC</a>.
 
-If you are a noVNC developer/integrator/user (or want to be) please
-join the <a
-href="https://groups.google.com/forum/?fromgroups#!forum/novnc">noVNC
-discussion group</a>
+If you are a noVNC developer/integrator/user (or want to be) please join the
+<a href="https://groups.google.com/forum/?fromgroups#!forum/novnc">
+noVNC discussion group</a>.
 
-Bugs and feature requests can be submitted via [github
-issues](https://github.com/kanaka/noVNC/issues). If you are looking
-for a place to start contributing to noVNC, a good place to start
-would be the issues that are marked as
+Bugs and feature requests can be submitted via
+[github issues](https://github.com/kanaka/noVNC/issues).
+If you are looking for a place to start contributing to noVNC, a good place to
+start would be the issues that are marked as
 ["patchwelcome"](https://github.com/kanaka/noVNC/issues?labels=patchwelcome).
 
-If you want to show appreciation for noVNC you could donate to a great
-non-profits such as: [Compassion
-International](http://www.compassion.com/), [SIL](http://www.sil.org),
-[Habitat for Humanity](http://www.habitat.org), [Electronic Frontier
-Foundation](https://www.eff.org/), [Against Malaria
-Foundation](http://www.againstmalaria.com/), [Nothing But
-Nets](http://www.nothingbutnets.net/), etc. Please tweet <a
-href="http://www.twitter.com/noVNC">@noVNC</a> if you do.
+If you want to show appreciation for noVNC you could donate to a great non-
+profits such as:
+[Compassion International](http://www.compassion.com/),
+[SIL](http://www.sil.org),
+[Habitat for Humanity](http://www.habitat.org),
+[Electronic Frontier Foundation](https://www.eff.org/),
+[Against Malaria Foundation](http://www.againstmalaria.com/),
+[Nothing But Nets](http://www.nothingbutnets.net/), etc.
+Please tweet <a href="http://www.twitter.com/noVNC">@noVNC</a> if you do.
 
 
 ### Features
@@ -59,57 +59,72 @@ href="http://www.twitter.com/noVNC">@noVNC</a> if you do.
 
 Running in Chrome before and after connecting:
 
-<img src="http://kanaka.github.com/noVNC/img/noVNC-5.png" width=400>&nbsp;<img src="http://kanaka.github.com/noVNC/img/noVNC-7.jpg" width=400>
+<img src="http://kanaka.github.com/noVNC/img/noVNC-5.png" width=400>&nbsp;
+<img src="http://kanaka.github.com/noVNC/img/noVNC-7.jpg" width=400>
 
-See more screenshots <a href="http://kanaka.github.com/noVNC/screenshots.html">here</a>.
+See more screenshots
+<a href="http://kanaka.github.com/noVNC/screenshots.html">here</a>.
 
 
 ### Browser Requirements
 
-* HTML5 Canvas (with createImageData): Chrome, Firefox 3.6+, iOS
-  Safari, Opera 11+, Internet Explorer 9+, etc.
+* Chrome 8, Firefox 4, Safari 6, Opera 12, IE 11, Edge 12, etc.
 
-* HTML5 WebSockets and Typed Arrays
+* HTML5 Canvas, WebSockets and Typed Arrays
 
-* Fast Javascript Engine: this is not strictly a requirement, but
-  without a fast Javascript engine, noVNC might be painfully slow.
+* Fast Javascript Engine: this is not strictly a requirement, but without a
+  fast Javascript engine, noVNC might be painfully slow.
 
-* See the more detailed [browser compatibility wiki page](https://github.com/kanaka/noVNC/wiki/Browser-support).
+* See the more detailed
+[browser compatibility wiki page](https://github.com/kanaka/noVNC/wiki/Browser-support).
 
 
 ### Server Requirements
 
-Unless you are using a VNC server with support for WebSockets
-connections (such as
-[x11vnc/libvncserver](http://libvncserver.sourceforge.net/),
+Unless you are using a VNC server with support for WebSockets connections (such
+as [x11vnc/libvncserver](http://libvncserver.sourceforge.net/),
 [QEMU](http://www.qemu.org/), or
-[PocketVNC](http://www.pocketvnc.com/blog/?page_id=866)), you need to
-use a WebSockets to TCP socket proxy. There is a python proxy included
+[PocketVNC](http://www.pocketvnc.com/blog/?page_id=866)), you need to use a
+WebSockets to TCP socket proxy. There is a python proxy included
 ('websockify').
 
 
 ### Quick Start
 
-* Use the launch script to start a mini-webserver and the WebSockets
-  proxy (websockify). The `--vnc` option is used to specify the location of
-  a running VNC server:
+* Use the launch script to start a mini-webserver and the WebSockets proxy
+  (websockify). The `--vnc` option is used to specify the location of a running
+  VNC server:
 
     `./utils/launch.sh --vnc localhost:5901`
 
-* Point your browser to the cut-and-paste URL that is output by the
-  launch script. Enter a password if the VNC server has one
-  configured. Hit the Connect button and enjoy!
+* Point your browser to the cut-and-paste URL that is output by the launch
+  script. Enter a password if the VNC server has one configured. Hit the
+  Connect button and enjoy!
 
 
 ### Other Pages
 
-* [Encrypted Connections](https://github.com/kanaka/websockify/wiki/Encrypted-Connections). How to setup websockify so that you can use encrypted connections from noVNC.
+* [Modules/API](https://github.com/kanaka/noVNC/wiki/Modules-API) - The library
+  modules and their Javascript API.
 
-* [Advanced Usage](https://github.com/kanaka/noVNC/wiki/Advanced-usage). Starting a VNC server, advanced websockify usage, etc.
+* [Integration](https://github.com/kanaka/noVNC/wiki/Integration) - Get noVNC
+  to work in existing projects.
 
-* [Integrating noVNC](https://github.com/kanaka/noVNC/wiki/Integration) into existing projects.
+* [Troubleshooting](https://github.com/kanaka/noVNC/wiki/Troubleshooting) - How
+  to troubleshoot problems.
 
-* [Troubleshooting noVNC](https://github.com/kanaka/noVNC/wiki/Troubleshooting) problems.
+* [Encrypted Connections](https://github.com/kanaka/websockify/wiki/Encrypted-Connections) -
+  Setup websockify so that you can use encrypted connections from noVNC.
+
+* [Advanced Usage](https://github.com/kanaka/noVNC/wiki/Advanced-usage) -
+  Generating an SSL certificate, starting a VNC server, advanced websockify
+  usage, etc.
+
+* [Testing](https://github.com/kanaka/noVNC/wiki/Testing) - Run and write
+  tests.
+
+* [Translations](https://github.com/kanaka/noVNC/wiki/Translations) - Add and
+  modify localization for JavaScript and HTML.
 
 
 ### Authors/Contributors
@@ -121,7 +136,7 @@ use a WebSockets to TCP socket proxy. There is a python proxy included
     * [Solly Ross](https://github.com/DirectXMan12) (Red Hat / OpenStack)
 
 * Notable contributions:
-    * UI and Icons : Chris Gordon
+    * UI and Icons : Pierre Ossman, Chris Gordon
     * Original Logo : Michael Sersen
     * tight encoding : Michael Tinglof (Mercuri.ca)
 
@@ -130,3 +145,5 @@ use a WebSockets to TCP socket proxy. There is a python proxy included
     * base64 : Martijn Pieters (Digital Creations 2), Samuel Sieb (sieb.net)
     * DES : Dave Zimmerman (Widget Workshop), Jef Poskanzer (ACME Labs)
     * Pako : Vitaly Puzrin (https://github.com/nodeca/pako)
+
+* [Contribution guide](https://github.com/kanaka/noVNC/wiki/Contributing)
index b4c4d1319f3a78997c497def069bcb3e03a5288b..7579c42cb78ced2aab10eb96b32b86d480a5c3fe 100644 (file)
@@ -9,9 +9,9 @@
    xmlns="http://www.w3.org/2000/svg"
    xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
    xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
-   width="20"
+   width="15"
    height="50"
-   viewBox="0 0 20 50"
+   viewBox="0 0 15 50"
    id="svg2"
    version="1.1"
    inkscape:version="0.91 r13725"
@@ -29,8 +29,8 @@
      inkscape:pageopacity="0"
      inkscape:pageshadow="2"
      inkscape:zoom="16"
-     inkscape:cx="10.515997"
-     inkscape:cy="22.863737"
+     inkscape:cx="-10.001409"
+     inkscape:cy="24.512566"
      inkscape:document-units="px"
      inkscape:current-layer="layer1"
      showgrid="true"
@@ -63,7 +63,7 @@
         <dc:format>image/svg+xml</dc:format>
         <dc:type
            rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
-        <dc:title />
+        <dc:title></dc:title>
       </cc:Work>
     </rdf:RDF>
   </metadata>
        id="rect4249"
        width="1"
        height="1.0000174"
-       x="14.5"
+       x="9.5"
        y="1008.8622"
        ry="1.7382812e-05" />
     <rect
        ry="1.7382812e-05"
        y="1013.8622"
-       x="14.5"
+       x="9.5"
        height="1.0000174"
        width="1"
        id="rect4255"
@@ -91,7 +91,7 @@
     <rect
        ry="1.7382812e-05"
        y="1008.8622"
-       x="9.5"
+       x="4.5"
        height="1.0000174"
        width="1"
        id="rect4261"
        id="rect4263"
        width="1"
        height="1.0000174"
-       x="9.5"
+       x="4.5"
        y="1013.8622"
        ry="1.7382812e-05" />
     <rect
        ry="1.7382812e-05"
        y="1039.8622"
-       x="14.5"
+       x="9.5"
        height="1.0000174"
        width="1"
        id="rect4265"
        id="rect4267"
        width="1"
        height="1.0000174"
-       x="14.5"
+       x="9.5"
        y="1044.8622"
        ry="1.7382812e-05" />
     <rect
        id="rect4269"
        width="1"
        height="1.0000174"
-       x="9.5"
+       x="4.5"
        y="1039.8622"
        ry="1.7382812e-05" />
     <rect
        ry="1.7382812e-05"
        y="1044.8622"
-       x="9.5"
+       x="4.5"
        height="1.0000174"
        width="1"
        id="rect4271"
        id="rect4273"
        width="1"
        height="1.0000174"
-       x="14.5"
+       x="9.5"
        y="1018.8622"
        ry="1.7382812e-05" />
     <rect
        ry="1.7382812e-05"
        y="1018.8622"
-       x="9.5"
+       x="4.5"
        height="1.0000174"
        width="1"
        id="rect4275"
        id="rect4277"
        width="1"
        height="1.0000174"
-       x="14.5"
+       x="9.5"
        y="1034.8622"
        ry="1.7382812e-05" />
     <rect
        ry="1.7382812e-05"
        y="1034.8622"
-       x="9.5"
+       x="4.5"
        height="1.0000174"
        width="1"
        id="rect4279"
diff --git a/app/locale/de.js b/app/locale/de.js
new file mode 100644 (file)
index 0000000..6819c48
--- /dev/null
@@ -0,0 +1,18 @@
+/*
+ * Translations for de
+ *
+ * This file was autotomatically generated from de.po
+ * DO NOT EDIT!
+ */
+
+Language = {
+    "Connecting...": "Verbunden...",
+    "Connected (encrypted) to ": "Verbunden mit (verschlรผsselt) ",
+    "Connected (unencrypted) to ": "Verbunden mit (unverschlรผsselt) ",
+    "Disconnecting...": "Verbindung trennen...",
+    "Disconnected": "Verbindung zum Server getrennt",
+    "Must set host and port": "Richten Sie Host und Port ein",
+    "Password is required": "Passwort ist erforderlich",
+    "Forcing clipping mode since scrollbars aren't supported by IE in fullscreen": "'Clipping-Modus' aktiviert, Scrollbalken in 'IE-Vollbildmodus' werden nicht unterstรผtzt",
+    "Disconnect timeout": "Timeout beim trennen",
+};
diff --git a/app/locale/el.js b/app/locale/el.js
new file mode 100644 (file)
index 0000000..3ce6c38
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * Translations for el
+ *
+ * This file was autotomatically generated from el.po
+ * DO NOT EDIT!
+ */
+
+Language = {
+    "Connecting...": "ฮฃฯ…ฮฝฮดฮญฮตฯ„ฮฑฮน...",
+    "Connected (encrypted) to ": "ฮฃฯ…ฮฝฮดฮญฮธฮทฮบฮต (ฮบฯฯ…ฯ€ฯ„ฮฟฮณฯฮฑฯ†ฮทฮผฮญฮฝฮฑ) ฮผฮต ฯ„ฮฟ ",
+    "Connected (unencrypted) to ": "ฮฃฯ…ฮฝฮดฮญฮธฮทฮบฮต (ฮผฮท ฮบฯฯ…ฯ€ฯ„ฮฟฮณฯฮฑฯ†ฮทฮผฮญฮฝฮฑ) ฮผฮต ฯ„ฮฟ ",
+    "Disconnecting...": "Aฯ€ฮฟฯƒฯ…ฮฝฮดฮญฮตฯ„ฮฑฮน...",
+    "Disconnected": "ฮ‘ฯ€ฮฟฯƒฯ…ฮฝฮดฮญฮธฮทฮบฮต",
+    "Must set host and port": "ฮ ฯฮญฯ€ฮตฮน ฮฝฮฑ ฮฟฯฮนฯƒฯ„ฮตฮฏ ฯ„ฮฟ ฯŒฮฝฮฟฮผฮฑ ฮบฮฑฮน ฮท ฯ€ฯŒฯฯ„ฮฑ ฯ„ฮฟฯ… ฮดฮนฮฑฮบฮฟฮผฮนฯƒฯ„ฮฎ",
+    "Password is required": "ฮ‘ฯ€ฮฑฮนฯ„ฮตฮฏฯ„ฮฑฮน ฮฟ ฮบฯ‰ฮดฮนฮบฯŒฯ‚ ฯ€ฯฯŒฯƒฮฒฮฑฯƒฮทฯ‚",
+    "Forcing clipping mode since scrollbars aren't supported by IE in fullscreen": "ฮ•ฯ†ฮฑฯฮผฮฟฮณฮฎ ฮปฮตฮนฯ„ฮฟฯ…ฯฮณฮฏฮฑฯ‚ ฮฑฯ€ฮฟฮบฮฟฯ€ฮฎฯ‚ ฮฑฯ†ฮฟฯ ฮดฮตฮฝ ฯ…ฯ€ฮฟฯƒฯ„ฮทฯฮฏฮถฮฟฮฝฯ„ฮฑฮน ฮฟฮน ฮปฯ‰ฯฮฏฮดฮตฯ‚ ฮบฯฮปฮนฯƒฮทฯ‚ ฯƒฮต ฯ€ฮปฮฎฯฮท ฮฟฮธฯŒฮฝฮท ฯƒฯ„ฮฟฮฝ IE",
+    "Disconnect timeout": "ฮ ฮฑฯฮญฮปฮตฯ…ฯƒฮท ฯ‡ฯฮฟฮฝฮนฮบฮฟฯ ฮฟฯฮฏฮฟฯ… ฮฑฯ€ฮฟฯƒฯฮฝฮดฮตฯƒฮทฯ‚",
+    "noVNC encountered an error:": "ฯ„ฮฟ noVNC ฮฑฮฝฯ„ฮนฮผฮตฯ„ฯŽฯ€ฮนฯƒฮต ฮญฮฝฮฑ ฯƒฯ†ฮฌฮปฮผฮฑ",
+    "Hide/Show the control bar": "ฮ‘ฯ€ฯŒฮบฯฯ…ฯˆฮท/ฮ•ฮผฯ†ฮฌฮฝฮนฯƒฮท ฮณฯฮฑฮผฮผฮฎฯ‚ ฮตฮปฮญฮณฯ‡ฮฟฯ…",
+    "Move/Drag Viewport": "ฮœฮตฯ„ฮฑฮบฮฏฮฝฮทฯƒฮท/ฮฃฯฯฯƒฮนฮผฮฟ ฮ˜ฮตฮฑฯ„ฮฟฯ ฯ€ฮตฮดฮฏฮฟฯ…",
+    "viewport drag": "ฯƒฯฯฯƒฮนฮผฮฟ ฮธฮตฮฑฯ„ฮฟฯ ฯ€ฮตฮดฮฏฮฟฯ…",
+    "Active Mouse Button": "ฮ•ฮฝฮตฯฮณฯŒ ฮ ฮปฮฎฮบฯ„ฯฮฟ ฮ ฮฟฮฝฯ„ฮนฮบฮนฮฟฯ",
+    "No mousebutton": "ฮงฯ‰ฯฮฏฯ‚ ฮ ฮปฮฎฮบฯ„ฯฮฟ ฮ ฮฟฮฝฯ„ฮนฮบฮนฮฟฯ",
+    "Left mousebutton": "ฮ‘ฯฮนฯƒฯ„ฮตฯฯŒ ฮ ฮปฮฎฮบฯ„ฯฮฟ ฮ ฮฟฮฝฯ„ฮนฮบฮนฮฟฯ",
+    "Middle mousebutton": "ฮœฮตฯƒฮฑฮฏฮฟ ฮ ฮปฮฎฮบฯ„ฯฮฟ ฮ ฮฟฮฝฯ„ฮนฮบฮนฮฟฯ",
+    "Right mousebutton": "ฮ”ฮตฮพฮฏ ฮ ฮปฮฎฮบฯ„ฯฮฟ ฮ ฮฟฮฝฯ„ฮนฮบฮนฮฟฯ",
+    "Keyboard": "ฮ ฮปฮทฮบฯ„ฯฮฟฮปฯŒฮณฮนฮฟ",
+    "Show Keyboard": "ฮ•ฮผฯ†ฮฌฮฝฮนฯƒฮท ฮ ฮปฮทฮบฯ„ฯฮฟฮปฮฟฮณฮฏฮฟฯ…",
+    "Extra keys": "ฮ•ฯ€ฮนฯ€ฮปฮญฮฟฮฝ ฯ€ฮปฮฎฮบฯ„ฯฮฑ",
+    "Show Extra Keys": "ฮ•ฮผฯ†ฮฌฮฝฮนฯƒฮท ฮ•ฯ€ฮนฯ€ฮปฮญฮฟฮฝ ฮ ฮปฮฎฮบฯ„ฯฯ‰ฮฝ",
+    "Ctrl": "Ctrl",
+    "Toggle Ctrl": "ฮ•ฮฝฮฑฮปฮปฮฑฮณฮฎ Ctrl",
+    "Alt": "Alt",
+    "Toggle Alt": "ฮ•ฮฝฮฑฮปฮปฮฑฮณฮฎ Alt",
+    "Send Tab": "ฮ‘ฯ€ฮฟฯƒฯ„ฮฟฮปฮฎ Tab",
+    "Tab": "Tab",
+    "Esc": "Esc",
+    "Send Escape": "ฮ‘ฯ€ฮฟฯƒฯ„ฮฟฮปฮฎ Escape",
+    "Ctrl+Alt+Del": "Ctrl+Alt+Del",
+    "Send Ctrl-Alt-Del": "ฮ‘ฯ€ฮฟฯƒฯ„ฮฟฮปฮฎ Ctrl-Alt-Del",
+    "Shutdown/Reboot": "ฮšฮปฮตฮฏฯƒฮนฮผฮฟ/ฮ•ฯ€ฮฑฮฝฮตฮบฮบฮฏฮฝฮทฯƒฮท",
+    "Shutdown/Reboot...": "ฮšฮปฮตฮฏฯƒฮนฮผฮฟ/ฮ•ฯ€ฮฑฮฝฮตฮบฮบฮฏฮฝฮทฯƒฮท...",
+    "Power": "ฮ‘ฯ€ฮตฮฝฮตฯฮณฮฟฯ€ฮฟฮฏฮทฯƒฮท",
+    "Shutdown": "ฮšฮปฮตฮฏฯƒฮนฮผฮฟ",
+    "Reboot": "ฮ•ฯ€ฮฑฮฝฮตฮบฮบฮฏฮฝฮทฯƒฮท",
+    "Reset": "ฮ•ฯ€ฮฑฮฝฮฑฯ†ฮฟฯฮฌ",
+    "Clipboard": "ฮ ฯฯŒฯ‡ฮตฮนฯฮฟ",
+    "Clear": "ฮšฮฑฮธฮฌฯฮนฯƒฮผฮฑ",
+    "Fullscreen": "ฮ ฮปฮฎฯฮทฯ‚ ฮŸฮธฯŒฮฝฮท",
+    "Settings": "ฮกฯ…ฮธฮผฮฏฯƒฮตฮนฯ‚",
+    "Encrypt": "ฮšฯฯ…ฯ€ฯ„ฮฟฮณฯฮฌฯ†ฮทฯƒฮท",
+    "True Color": "ฮ ฯฮฑฮณฮผฮฑฯ„ฮนฮบฮฌ ฮงฯฯŽฮผฮฑฯ„ฮฑ",
+    "Local Cursor": "ฮคฮฟฯ€ฮนฮบฯŒฯ‚ ฮ”ฯฮฟฮผฮญฮฑฯ‚",
+    "Clip to Window": "ฮ‘ฯ€ฮฟฮบฮฟฯ€ฮฎ ฯƒฯ„ฮฟ ฯŒฯฮนฮฟ ฯ„ฮฟฯ… ฮ ฮฑฯฮฌฮธฯ…ฯฮฟฯ…",
+    "Shared Mode": "ฮšฮฟฮนฮฝฯŒฯ‡ฯฮทฯƒฯ„ฮท ฮ›ฮตฮนฯ„ฮฟฯ…ฯฮณฮฏฮฑ",
+    "View Only": "ฮœฯŒฮฝฮฟ ฮ˜ฮญฮฑฯƒฮท",
+    "Path:": "ฮ”ฮนฮฑฮดฯฮฟฮผฮฎ:",
+    "Scaling Mode:": "ฮ›ฮตฮนฯ„ฮฟฯ…ฯฮณฮฏฮฑ ฮšฮปฮนฮผฮฌฮบฯ‰ฯƒฮทฯ‚:",
+    "None": "ฮšฮฑฮผฮฏฮฑ",
+    "Local Scaling": "ฮคฮฟฯ€ฮนฮบฮฎ ฮšฮปฮนฮผฮฌฮบฯ‰ฯƒฮท",
+    "Local Downscaling": "ฮคฮฟฯ€ฮนฮบฮฎ ฮฃฯ…ฯฯฮฏฮบฮฝฯ‰ฯƒฮท",
+    "Remote Resizing": "ฮ‘ฯ€ฮฟฮผฮฑฮบฯฯ…ฯƒฮผฮญฮฝฮท ฮ‘ฮปฮปฮฑฮณฮฎ ฮผฮตฮณฮญฮธฮฟฯ…ฯ‚",
+    "Repeater ID:": "Repeater ID:",
+    "Style:": "ฮฃฯ„ฯ…ฮป:",
+    "default": "ฯ€ฯฮฟฮตฯ€ฮนฮปฮตฮณฮผฮญฮฝฮฟ",
+    "Logging:": "ฮšฮฑฯ„ฮฑฮณฯฮฑฯ†ฮฎ:",
+    "Apply": "ฮ•ฯ†ฮฑฯฮผฮฟฮณฮฎ",
+    "Host:": "ฮŒฮฝฮฟฮผฮฑ ฮดฮนฮฑฮบฮฟฮผฮนฯƒฯ„ฮฎ:",
+    "Port:": "ฮ ฯŒฯฯ„ฮฑ ฮดฮนฮฑฮบฮฟฮผฮนฯƒฯ„ฮฎ:",
+    "Password:": "ฮšฯ‰ฮดฮนฮบฯŒฯ‚ ฮ ฯฯŒฯƒฮฒฮฑฯƒฮทฯ‚:",
+    "Token:": "ฮ”ฮนฮฑฮบฯฮนฯ„ฮนฮบฯŒ:",
+    "Send Password": "ฮ‘ฯ€ฮฟฯƒฯ„ฮฟฮปฮฎ ฮšฯ‰ฮดฮนฮบฮฟฯ ฮ ฯฯŒฯƒฮฒฮฑฯƒฮทฯ‚",
+    "Canvas not supported.": "ฮ”ฮตฮฝ ฯ…ฯ€ฮฟฯƒฯ„ฮทฯฮฏฮถฮตฯ„ฮฑฮน ฯ„ฮฟ ฯƒฯ„ฮฟฮนฯ‡ฮตฮฏฮฟ Canvas",
+};
diff --git a/app/locale/nl.js b/app/locale/nl.js
new file mode 100644 (file)
index 0000000..cacab30
--- /dev/null
@@ -0,0 +1,18 @@
+/*
+ * Translations for nl
+ *
+ * This file was autotomatically generated from nl.po
+ * DO NOT EDIT!
+ */
+
+Language = {
+    "Connecting...": "Verbinden...",
+    "Connected (encrypted) to ": "Verbonden (versleuteld) met ",
+    "Connected (unencrypted) to ": "Verbonden (onversleuteld) met ",
+    "Disconnecting...": "Verbinding verbreken...",
+    "Disconnected": "Verbinding verbroken",
+    "Must set host and port": "Host en poort moeten worden ingesteld",
+    "Password is required": "Wachtwoord is vereist",
+    "Forcing clipping mode since scrollbars aren't supported by IE in fullscreen": "''Clipping mode' ingeschakeld, omdat schuifbalken in volledige-scherm-modus in IE niet worden ondersteund",
+    "Disconnect timeout": "Timeout tijdens verbreken van verbinding",
+};
diff --git a/app/locale/sv.js b/app/locale/sv.js
new file mode 100644 (file)
index 0000000..b7f4c1f
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ * Translations for sv
+ *
+ * This file was autotomatically generated from sv.po
+ * DO NOT EDIT!
+ */
+
+Language = {
+    "Connecting...": "Ansluter...",
+    "Connected (encrypted) to ": "Ansluten (krypterat) till ",
+    "Connected (unencrypted) to ": "Ansluten (okrypterat) till ",
+    "Disconnecting...": "Kopplar ner...",
+    "Disconnected": "Frรฅnkopplad",
+    "Must set host and port": "Du mรฅste specifiera en host och port",
+    "Password is required": "Lรถsenord krรคvs",
+    "Forcing clipping mode since scrollbars aren't supported by IE in fullscreen": "Tvingar 'Clipping mode' eftersom skrollning inte stรถdjs av IE i fullskรคrm",
+    "Disconnect timeout": "Det tog fรถr lรฅng tid att koppla ner",
+    "noVNC encountered an error:": "noVNC stรถtte pรฅ ett problem:",
+    "Hide/Show the control bar": "Gรถm/Visa kontrollbaren",
+    "Move/Drag Viewport": "Flytta/Dra Vyn",
+    "viewport drag": "dra vy",
+    "Active Mouse Button": "Aktiv musknapp",
+    "No mousebutton": "Ingen musknapp",
+    "Left mousebutton": "Vรคnster musknapp",
+    "Middle mousebutton": "Mitten-musknapp",
+    "Right mousebutton": "Hรถger musknapp",
+    "Keyboard": "Tangentbord",
+    "Show Keyboard": "Visa Tangentbord",
+    "Extra keys": "Extraknappar",
+    "Show Extra Keys": "Visa Extraknappar",
+    "Ctrl": "Ctrl",
+    "Toggle Ctrl": "Vรคxla Ctrl",
+    "Alt": "Alt",
+    "Toggle Alt": "Vรคxla Alt",
+    "Send Tab": "Skicka Tab",
+    "Tab": "Tab",
+    "Esc": "Esc",
+    "Send Escape": "Skicka Escape",
+    "Ctrl+Alt+Del": "Ctrl+Alt+Del",
+    "Send Ctrl-Alt-Del": "Skicka Ctrl-Alt-Del",
+    "Shutdown/Reboot": "Stรคng av/Boota om",
+    "Shutdown/Reboot...": "Stรคng av/Boota om...",
+    "Power": "Strรถm",
+    "Shutdown": "Stรคng av",
+    "Reboot": "Boota om",
+    "Reset": "ร…terstรคll",
+    "Clipboard": "Urklipp",
+    "Clear": "Rensa",
+    "Fullscreen": "Fullskรคrm",
+    "Settings": "Instรคllningar",
+    "Encrypt": "Kryptera",
+    "True Color": "Fullfรคrg",
+    "Local Cursor": "Lokal Muspekare",
+    "Clip to Window": "Begrรคnsa till Fรถnster",
+    "Shared Mode": "Delat Lรคge",
+    "View Only": "Endast Visning",
+    "Path:": "Sรถkvรคg:",
+    "Scaling Mode:": "Skalningslรคge:",
+    "None": "Ingen",
+    "Local Scaling": "Lokal Skalning",
+    "Local Downscaling": "Lokal Nedskalning",
+    "Remote Resizing": "ร„ndra Storlek",
+    "Repeater ID:": "Repeater-ID:",
+    "Style:": "Stil:",
+    "default": "standard",
+    "Logging:": "Loggning:",
+    "Apply": "Verkstรคll",
+    "Connect": "Anslut",
+    "Disconnect": "Koppla frรฅn",
+    "Connection": "Uppkoppling",
+    "Host:": "Vรคrd:",
+    "Port:": "Port:",
+    "Password:": "Lรถsenord:",
+    "Token:": "Token:",
+    "Send Password": "Skicka Lรถsenord",
+    "Canvas not supported.": "Canvas stรถds ej",
+};
index cb9109868d1801cd79c3ea6ac7afc33cc5e32858..f4ee929dfbbcada668932d5c474bd992413bab96 100644 (file)
@@ -16,6 +16,7 @@ body {
   background-repeat:no-repeat;
   background-position:right bottom;
   height:100%;
+  touch-action: none;
 }
 
 html {
@@ -26,6 +27,37 @@ html {
   display: none;
 }
 
+/* ----------------------------------------
+ * Spinner
+ * ----------------------------------------
+ */
+
+.noVNC_spinner {
+  position: relative;
+}
+.noVNC_spinner, .noVNC_spinner::before, .noVNC_spinner::after {
+  width: 10px;
+  height: 10px;
+  border-radius: 50%;
+  animation: noVNC_spinner 1.0s ease-in-out alternate infinite;
+}
+.noVNC_spinner::before {
+  content: "";
+  position: absolute;
+  left: -20px;
+  animation-delay: -0.2s;
+}
+.noVNC_spinner::after {
+  content: "";
+  position: absolute;
+  left: 20px;
+  animation-delay: 0.2s;
+}
+@keyframes noVNC_spinner {
+  0% { box-shadow: 0 10px 0 white; }
+  100% { box-shadow: 0 30px 0 white; }
+}
+
 /* ----------------------------------------
  * Input Elements
  * ----------------------------------------
@@ -140,6 +172,60 @@ input[type=button]:active, select:active {
   pointer-events: auto;
 }
 
+/* ----------------------------------------
+ * Fallback error
+ * ----------------------------------------
+ */
+
+#noVNC_fallback_error {
+  position: fixed;
+  z-index: 3;
+  left: 50%;
+  transform: translate(-50%, -50px);
+  transition: 0.5s ease-in-out;
+
+  visibility: hidden;
+  opacity: 0;
+
+  top: 60px;
+  padding: 15px;
+  width: auto;
+
+  text-align: center;
+  font-weight: bold;
+  word-wrap: break-word;
+  color: #fff;
+
+  border-radius: 10px;
+  box-shadow: 6px 6px 0px rgba(0, 0, 0, 0.5);
+  background: rgba(200,55,55,0.8);
+}
+#noVNC_fallback_error.noVNC_open {
+  transform: translate(-50%, 0);
+  visibility: visible;
+  opacity: 1;
+}
+
+#noVNC_fallback_errormsg {
+  font-weight: normal;
+}
+
+#noVNC_fallback_error .noVNC_location {
+  font-style: italic;
+  font-size: 0.8em;
+  color: rgba(255, 255, 255, 0.8);
+}
+
+#noVNC_fallback_error .noVNC_stack {
+  padding: 10px;
+  margin: 10px;
+  font-size: 0.8em;
+  text-align: left;
+  white-space: pre;
+  border: 1px solid rgba(0, 0, 0, 0.5);
+  background: rgba(0, 0, 0, 0.2);
+}
+
 /* ----------------------------------------
  * Control Bar
  * ----------------------------------------
@@ -158,37 +244,59 @@ input[type=button]:active, select:active {
 :root.noVNC_connected #noVNC_control_bar_anchor.noVNC_idle {
   opacity: 0.8;
 }
+#noVNC_control_bar_anchor.noVNC_right {
+  left: auto;
+  right: 0;
+}
 
 #noVNC_control_bar {
   position: relative;
-  /* left: calc(-35px - 10px - 5px - 30px), but IE doesn't animate calc */
-  left: -80px;
+  left: -100%;
 
   transition: 0.5s ease-in-out;
 
   background-color: rgb(110, 132, 163);
   border-radius: 0 10px 10px 0;
 
-  /* The extra border is to get a proper shadow */
-  border-color: rgb(110, 132, 163);
-  border-style: solid;
-  border-width: 0 0 0 30px;
 }
 #noVNC_control_bar.noVNC_open {
   box-shadow: 6px 6px 0px rgba(0, 0, 0, 0.5);
+  left: 0;
+}
+#noVNC_control_bar::before {
+  /* This extra element is to get a proper shadow */
+  content: "";
+  position: absolute;
+  z-index: -1;
+  height: 100%;
+  width: 30px;
   left: -30px;
+  transition: box-shadow 0.5s ease-in-out;
+}
+#noVNC_control_bar.noVNC_open::before {
+  box-shadow: 6px 6px 0px rgba(0, 0, 0, 0.5);
+}
+.noVNC_right #noVNC_control_bar {
+  left: 100%;
+  border-radius: 10px 0 0 10px;
+}
+.noVNC_right #noVNC_control_bar.noVNC_open {
+  left: 0;
+}
+.noVNC_right #noVNC_control_bar::before {
+  visibility: hidden;
 }
 
 #noVNC_control_bar_handle {
   position: absolute;
-  right: -15px;
+  left: -15px;
   top: 0;
   transform: translateY(35px);
-  width: 50px;
+  width: calc(100% + 30px);
   height: 50px;
   z-index: -2;
   cursor: pointer;
-  border-radius: 0 5px 5px 0;
+  border-radius: 5px;
   background-color: rgb(83, 99, 122);
   background-image: url("../images/handle_bg.svg");
   background-repeat: no-repeat;
@@ -197,7 +305,7 @@ input[type=button]:active, select:active {
 }
 #noVNC_control_bar_handle:after {
   content: "";
-  transition: 0.5s ease-in-out;
+  transition: transform 0.5s ease-in-out;
   background: url("../images/handle.svg");
   position: absolute;
   top: 22px; /* (50px-6px)/2 */
@@ -211,6 +319,17 @@ input[type=button]:active, select:active {
 :root:not(.noVNC_connected) #noVNC_control_bar_handle {
   display: none;
 }
+.noVNC_right #noVNC_control_bar_handle {
+  background-position: left;
+}
+.noVNC_right #noVNC_control_bar_handle:after {
+  left: 5px;
+  right: 0;
+  transform: translateX(1px) rotate(180deg);
+}
+.noVNC_right #noVNC_control_bar.noVNC_open #noVNC_control_bar_handle:after {
+  transform: none;
+}
 #noVNC_control_bar_handle div {
   position: absolute;
   right: -35px;
@@ -221,6 +340,10 @@ input[type=button]:active, select:active {
 :root:not(.noVNC_touch) #noVNC_control_bar_handle div {
   display: none;
 }
+.noVNC_right #noVNC_control_bar_handle div {
+  left: -35px;
+  right: auto;
+}
 
 #noVNC_control_bar .noVNC_scroll {
   max-height: 100vh; /* Chrome is buggy with 100% */
@@ -228,6 +351,9 @@ input[type=button]:active, select:active {
   overflow-y: auto;
   padding: 0 10px 0 5px;
 }
+.noVNC_right #noVNC_control_bar .noVNC_scroll {
+  padding: 0 5px 0 10px;
+}
 
 /* General button style */
 .noVNC_button {
@@ -293,6 +419,16 @@ input[type=button]:active, select:active {
   opacity: 1;
   transform: translateX(75px);
 }
+.noVNC_right .noVNC_vcenter {
+  left: auto;
+  right: 0;
+}
+.noVNC_right .noVNC_panel {
+  transform: translateX(-25px);
+}
+.noVNC_right .noVNC_panel.noVNC_open {
+  transform: translateX(-75px);
+}
 
 .noVNC_panel hr {
   border: none;
@@ -498,6 +634,34 @@ input[type=button]:active, select:active {
  * ----------------------------------------
  */
 
+/* Transition screen */
+#noVNC_transition {
+  display: none;
+
+  position: fixed;
+  top: 0;
+  left: 0;
+  bottom: 0;
+  right: 0;
+
+  color: white;
+  background: rgba(0, 0, 0, 0.5);
+  z-index: 1000;
+
+  /*display: flex;*/
+  align-items: center;
+  justify-content: center;
+  flex-direction: column;
+}
+:root.noVNC_connecting #noVNC_transition,
+:root.noVNC_disconnecting #noVNC_transition {
+  display: flex;
+}
+#noVNC_transition_text {
+  font-size: 1.5em;
+}
+
+/* Main container */
 #noVNC_container {
   display: table;
   width: 100%;
@@ -527,7 +691,6 @@ input[type=button]:active, select:active {
  * scaling will occur. Canvas size depends on remote VNC
  * settings and noVNC settings. */
 #noVNC_canvas {
-  touch-action: none;
   position: absolute;
   left: 0;
   right: 0;
index 8b889262a554517d041aa9bbc6e995b8c3f454dc..7ca0dc7dcf9256b9215de2bbbe5f6a15a596a2c3 100644 (file)
--- a/app/ui.js
+++ b/app/ui.js
@@ -14,6 +14,7 @@
 /* [module]
  * import Util from "../core/util";
  * import KeyTable from "../core/input/keysym";
+ * import keysyms from "./keysymdef";
  * import RFB from "../core/rfb";
  * import Display from "../core/display";
  * import WebUtil from "./webutil";
@@ -24,6 +25,46 @@ var UI;
 (function () {
     "use strict";
 
+    // Fallback for all uncought errors
+    window.addEventListener('error', function(event) {
+        try {
+            var msg = "";
+
+            msg += "<div>";
+            msg += event.message;
+            msg += "</div>";
+
+            msg += " <div class=\"noVNC_location\">";
+            msg += event.filename;
+            msg += ":" + event.lineno + ":" + event.colno;
+            msg += "</div>";
+
+            if ((event.error !== undefined) &&
+                (event.error.stack !== undefined)) {
+                msg += "<div class=\"noVNC_stack\">";
+                msg += event.error.stack;
+                msg += "</div>";
+            }
+
+            document.getElementById('noVNC_fallback_error')
+                .classList.add("noVNC_open");
+            document.getElementById('noVNC_fallback_errormsg').innerHTML = msg;
+        } catch (exc) {
+            document.write("noVNC encountered an error.");
+        }
+        // Don't return true since this would prevent the error
+        // from being printed to the browser console.
+        return false;
+    });
+
+    // Set up translations
+    var LINGUAS = ["de", "el", "nl", "sv"];
+    Util.Localisation.setup(LINGUAS);
+    if (Util.Localisation.language !== "en") {
+        WebUtil.load_scripts(
+            {'app': ["locale/" + Util.Localisation.language + ".js"]});
+    }
+
     /* [begin skip-as-module] */
     // Load supporting scripts
     WebUtil.load_scripts(
@@ -34,6 +75,8 @@ var UI;
     window.onscriptsload = function () { UI.load(); };
     /* [end skip-as-module] */
 
+    var _ = Util.Localisation.get;
+
     UI = {
 
         connected: false,
@@ -49,7 +92,6 @@ var UI;
         controlbarDrag: false,
         controlbarMouseDownClientY: 0,
         controlbarMouseDownOffsetY: 0,
-        keyboardVisible: false,
 
         isSafari: false,
         rememberedClipSetting: null,
@@ -71,6 +113,9 @@ var UI;
 
             UI.initSettings();
 
+            // Translate the DOM
+            Util.Localisation.translateDOM();
+
             // Adapt the interface for touch screen devices
             if (Util.isTouchDevice) {
                 document.documentElement.classList.add("noVNC_touch");
@@ -81,6 +126,11 @@ var UI;
                 UI.initSetting('clip', false);
             }
 
+            // Restore control bar position
+            if (WebUtil.readSetting('controlbar_pos') === 'right') {
+                UI.toggleControlbarSide();
+            }
+
             // Setup and initialize event handlers
             UI.setupWindowEvents();
             UI.setupFullscreen();
@@ -232,11 +282,16 @@ var UI;
 
             document.getElementById("noVNC_keyboardinput")
                 .addEventListener('input', UI.keyInput);
+            document.getElementById("noVNC_keyboardinput")
+                .addEventListener('focus', UI.onfocusVirtualKeyboard);
             document.getElementById("noVNC_keyboardinput")
                 .addEventListener('blur', UI.onblurVirtualKeyboard);
             document.getElementById("noVNC_keyboardinput")
                 .addEventListener('submit', function () { return false; });
 
+            document.documentElement
+                .addEventListener('mousedown', UI.keepVirtualKeyboard, true);
+
             document.getElementById("noVNC_control_bar")
                 .addEventListener('touchstart', UI.activateControlbar);
             document.getElementById("noVNC_control_bar")
@@ -347,7 +402,7 @@ var UI;
                                   'onDesktopName': UI.updateDesktopName});
                 return true;
             } catch (exc) {
-                var msg = 'Unable to create RFB client -- ' + exc;
+                var msg = "Unable to create RFB client -- " + exc;
                 Util.Error(msg);
                 UI.showStatus(msg, 'error');
                 return false;
@@ -362,25 +417,33 @@ var UI;
 
         updateState: function(rfb, state, oldstate) {
             var msg;
+
+            document.documentElement.classList.remove("noVNC_connecting");
+            document.documentElement.classList.remove("noVNC_connected");
+            document.documentElement.classList.remove("noVNC_disconnecting");
+
             switch (state) {
                 case 'connecting':
-                    UI.showStatus("Connecting");
+                    document.getElementById("noVNC_transition_text").innerHTML = _("Connecting...");
+                    document.documentElement.classList.add("noVNC_connecting");
                     break;
                 case 'connected':
                     UI.connected = true;
+                    document.documentElement.classList.add("noVNC_connected");
                     if (rfb && rfb.get_encrypt()) {
-                        msg = "Connected (encrypted) to " + UI.desktopName;
+                        msg = _("Connected (encrypted) to ") + UI.desktopName;
                     } else {
-                        msg = "Connected (unencrypted) to " + UI.desktopName;
+                        msg = _("Connected (unencrypted) to ") + UI.desktopName;
                     }
                     UI.showStatus(msg);
                     break;
                 case 'disconnecting':
-                    UI.showStatus("Disconnecting");
+                    UI.connected = false;
+                    document.getElementById("noVNC_transition_text").innerHTML = _("Disconnecting...");
+                    document.documentElement.classList.add("noVNC_disconnecting");
                     break;
                 case 'disconnected':
-                    UI.connected = false;
-                    UI.showStatus("Disconnected");
+                    UI.showStatus(_("Disconnected"));
                     break;
                 default:
                     msg = "Invalid UI state";
@@ -412,14 +475,12 @@ var UI;
             document.getElementById('noVNC_setting_repeaterID').disabled = UI.connected;
 
             if (UI.connected) {
-                document.documentElement.classList.add("noVNC_connected");
                 UI.updateViewClip();
                 UI.setMouseButton(1);
 
                 // Hide the controlbar after 2 seconds
                 UI.closeControlbarTimeout = setTimeout(UI.closeControlbar, 2000);
             } else {
-                document.documentElement.classList.remove("noVNC_connected");
                 UI.updateXvpButton(0);
                 UI.keepControlbar();
             }
@@ -537,11 +598,41 @@ var UI;
             }
         },
 
+        toggleControlbarSide: function () {
+            // Temporarily disable animation to avoid weird movement
+            var bar = document.getElementById('noVNC_control_bar');
+            bar.style.transitionDuration = '0s';
+            bar.addEventListener('transitionend', function () { this.style.transitionDuration = ""; });
+
+            var anchor = document.getElementById('noVNC_control_bar_anchor');
+            if (anchor.classList.contains("noVNC_right")) {
+                WebUtil.writeSetting('controlbar_pos', 'left');
+                anchor.classList.remove("noVNC_right");
+            } else {
+                WebUtil.writeSetting('controlbar_pos', 'right');
+                anchor.classList.add("noVNC_right");
+            }
+
+            // Consider this a movement of the handle
+            UI.controlbarDrag = true;
+        },
+
         dragControlbarHandle: function (e) {
             if (!UI.controlbarGrabbed) return;
 
             var ptr = Util.getPointerEvent(e);
 
+            var anchor = document.getElementById('noVNC_control_bar_anchor');
+            if (ptr.clientX < (window.innerWidth * 0.1)) {
+                if (anchor.classList.contains("noVNC_right")) {
+                    UI.toggleControlbarSide();
+                }
+            } else if (ptr.clientX > (window.innerWidth * 0.9)) {
+                if (!anchor.classList.contains("noVNC_right")) {
+                    UI.toggleControlbarSide();
+                }
+            }
+
             if (!UI.controlbarDrag) {
                 // The goal is to trigger on a certain physical width, the
                 // devicePixelRatio brings us a bit closer but is not optimal.
@@ -559,6 +650,8 @@ var UI;
 
             e.preventDefault();
             e.stopPropagation();
+            UI.keepControlbar();
+            UI.activateControlbar();
         },
 
         // Move the handle but don't allow any position outside the bounds
@@ -615,6 +708,8 @@ var UI;
                 UI.toggleControlbar();
                 e.preventDefault();
                 e.stopPropagation();
+                UI.keepControlbar();
+                UI.activateControlbar();
             }
             UI.controlbarGrabbed = false;
         },
@@ -635,6 +730,8 @@ var UI;
             UI.controlbarMouseDownOffsetY = ptr.clientY - bounds.top;
             e.preventDefault();
             e.stopPropagation();
+            UI.keepControlbar();
+            UI.activateControlbar();
         },
 
 /* ------^-------
@@ -967,7 +1064,7 @@ var UI;
             }
 
             if ((!host) || (!port)) {
-                var msg = "Must set host and port";
+                var msg = _("Must set host and port");
                 Util.Error(msg);
                 UI.showStatus(msg, 'error');
                 return;
@@ -1020,7 +1117,7 @@ var UI;
                 }, 100);
 
             if (typeof msg === 'undefined') {
-                msg = "Password is required";
+                msg = _("Password is required");
             }
             Util.Warn(msg);
             UI.showStatus(msg, "warning");
@@ -1208,8 +1305,9 @@ var UI;
                 // The browser is IE and we are in fullscreen mode.
                 // - We need to force clipping while in fullscreen since
                 //   scrollbars doesn't work.
-                var msg = "Forcing clipping mode since scrollbars aren't" +
-                    "supported by IE in fullscreen";
+                var msg = _("Forcing clipping mode since " +
+                            "scrollbars aren't supported " +
+                            "by IE in fullscreen");
                 Util.Debug(msg);
                 UI.showStatus(msg);
                 UI.rememberedClipSetting = UI.getSetting('clip');
@@ -1314,9 +1412,6 @@ var UI;
 
             if (document.activeElement == input) return;
 
-            UI.keyboardVisible = true;
-            document.getElementById('noVNC_keyboard_button')
-                .classList.add("noVNC_selected");
             input.focus();
 
             try {
@@ -1337,31 +1432,44 @@ var UI;
         },
 
         toggleVirtualKeyboard: function () {
-            if (UI.keyboardVisible) {
+            if (document.getElementById('noVNC_keyboard_button')
+                .classList.contains("noVNC_selected")) {
                 UI.hideVirtualKeyboard();
             } else {
                 UI.showVirtualKeyboard();
             }
         },
 
-        onblurVirtualKeyboard: function() {
-            //Weird bug in iOS if you change keyboardVisible
-            //here it does not actually occur so next time
-            //you click keyboard icon it doesnt work.
-            UI.hideKeyboardTimeout = setTimeout(function() {
-                UI.keyboardVisible = false;
-                document.getElementById('noVNC_keyboard_button')
-                       .classList.remove("noVNC_selected");
-            },100);
+        onfocusVirtualKeyboard: function(event) {
+            document.getElementById('noVNC_keyboard_button')
+                .classList.add("noVNC_selected");
         },
 
-        keepKeyboard: function() {
-            clearTimeout(UI.hideKeyboardTimeout);
-            if(UI.keyboardVisible === true) {
-                UI.showVirtualKeyboard();
-            } else if(UI.keyboardVisible === false) {
-                UI.hideVirtualKeyboard();
+        onblurVirtualKeyboard: function(event) {
+            document.getElementById('noVNC_keyboard_button')
+                .classList.remove("noVNC_selected");
+        },
+
+        keepVirtualKeyboard: function(event) {
+            var input = document.getElementById('noVNC_keyboardinput');
+
+            // Only prevent focus change if the virtual keyboard is active
+            if (document.activeElement != input) {
+                return;
+            }
+
+            // Allow clicking on links
+            if (event.target.tagName === "A") {
+                return;
+            }
+
+            // And form elements, except standard noVNC buttons
+            if ((event.target.form !== undefined) &&
+                !event.target.classList.contains("noVNC_button")) {
+                return;
             }
+
+            event.preventDefault();
         },
 
         keyboardinputReset: function() {
@@ -1420,7 +1528,7 @@ var UI;
                 UI.rfb.sendKey(KeyTable.XK_BackSpace);
             }
             for (i = newLen - inputs; i < newLen; i++) {
-                UI.rfb.sendKey(newValue.charCodeAt(i));
+                UI.rfb.sendKey(keysyms.fromUnicode(newValue.charCodeAt(i)).keysym);
             }
 
             // Control the text content length in the keyboardinput element
@@ -1435,7 +1543,7 @@ var UI;
                 // text has been added to the field
                 event.target.blur();
                 // This has to be ran outside of the input handler in order to work
-                setTimeout(UI.keepKeyboard, 0);
+                setTimeout(event.target.focus.bind(event.target), 0);
             } else {
                 UI.lastKeyboardinput = newValue;
             }
@@ -1465,7 +1573,6 @@ var UI;
         },
 
         toggleExtraKeys: function() {
-            UI.keepKeyboard();
             if(document.getElementById('noVNC_modifiers')
                 .classList.contains("noVNC_open")) {
                 UI.closeExtraKeys();
@@ -1475,17 +1582,14 @@ var UI;
         },
 
         sendEsc: function() {
-            UI.keepKeyboard();
             UI.rfb.sendKey(KeyTable.XK_Escape);
         },
 
         sendTab: function() {
-            UI.keepKeyboard();
             UI.rfb.sendKey(KeyTable.XK_Tab);
         },
 
         toggleCtrl: function() {
-            UI.keepKeyboard();
             var btn = document.getElementById('noVNC_toggle_ctrl_button');
             if (btn.classList.contains("noVNC_selected")) {
                 UI.rfb.sendKey(KeyTable.XK_Control_L, false);
@@ -1497,7 +1601,6 @@ var UI;
         },
 
         toggleAlt: function() {
-            UI.keepKeyboard();
             var btn = document.getElementById('noVNC_toggle_alt_button');
             if (btn.classList.contains("noVNC_selected")) {
                 UI.rfb.sendKey(KeyTable.XK_Alt_L, false);
@@ -1509,7 +1612,6 @@ var UI;
         },
 
         sendCtrlAltDel: function() {
-            UI.keepKeyboard();
             UI.rfb.sendCtrlAltDel();
         },
 
index 38bb3967ddbf00774b42a33e2cb10157fc24e90a..90a0a530440fd12b374fae87493466b20000b268 100644 (file)
@@ -371,6 +371,7 @@ WebUtil.releaseCapture = function () {
     }
 };
 
+
 // Dynamically load scripts without using document.write()
 // Reference: http://unixpapa.com/js/dyna.html
 //
index f45d9d9010c5b2224e67c1dd734eb6f9f0b4048b..c4d0ace91a4114fae8859f2189bf99fb704a6948 100644 (file)
@@ -10,7 +10,13 @@ var keysyms = (function(){
 
     function lookup(k) { return k ? {keysym: k, keyname: keynames ? keynames[k] : k} : undefined; }
     return {
-        fromUnicode : function(u) { return lookup(codepoints[u]); },
+        fromUnicode : function(u) {
+            var keysym = codepoints[u];
+            if (keysym === undefined) {
+                keysym = 0x01000000 | u;
+            }
+            return lookup(keysym);
+        },
         lookup : lookup
     };
 })();
index efdccedcda9ef0b6a684c1a432034edc39215181..52b31283f9a0d728fbcfa3e5e264a5e7fef0cb89 100644 (file)
@@ -184,10 +184,7 @@ var KeyboardUtil = {};
             codepoint = evt.keyCode;
         }
         if (codepoint) {
-            var res = keysyms.fromUnicode(substituteCodepoint(codepoint));
-            if (res) {
-                return res;
-            }
+            return keysyms.fromUnicode(substituteCodepoint(codepoint));
         }
         // we could check evt.key here.
         // Legal values are defined in http://www.w3.org/TR/DOM-Level-3-Events/#key-values-list,
index 8a5944f0f345aceeb3406ef39036ca77e0e3e6ab..fdb9560f88c6518628a8e3e7fb17dacb9f884a43 100644 (file)
             this._rfb_init_state = 'ProtocolVersion';
             Util.Debug("Starting VNC handshake");
         } else {
-            this._fail("Got unexpected WebSocket connection");
+            this._fail("Unexpected server connection");
         }
     }.bind(this));
     this._sock.on('close', function (e) {
                 this._updateConnectionState('disconnected');
                 break;
             case 'connecting':
-                this._fail('Failed to connect to server' + msg);
+                this._fail('Failed to connect to server', msg);
+                break;
+            case 'connected':
+                // Handle disconnects that were initiated server-side
+                this._updateConnectionState('disconnecting');
+                this._updateConnectionState('disconnected');
                 break;
             case 'disconnected':
-                Util.Error("Received onclose while disconnected" + msg);
+                this._fail("Unexpected server disconnect",
+                           "Already disconnected: " + msg);
                 break;
             default:
-                this._fail("Server disconnected" + msg);
+                this._fail("Unexpected server disconnect",
+                           "Not in any state yet: " + msg);
                 break;
         }
         this._sock.off('close');
 };
 
 (function() {
+    var _ = Util.Localisation.get;
+
     RFB.prototype = {
         // Public methods
         connect: function (host, port, password, path) {
             this._rfb_path = (path !== undefined) ? path : "";
 
             if (!this._rfb_host || !this._rfb_port) {
-                return this._fail("Must set host and port");
+                return this._fail(
+                    _("Must set host and port"));
             }
 
             this._rfb_init_state = '';
 
         // Send a key press. If 'down' is not specified then send a down key
         // followed by an up key.
-        sendKey: function (code, down) {
+        sendKey: function (keysym, down) {
             if (this._rfb_connection_state !== 'connected' || this._view_only) { return false; }
             if (typeof down !== 'undefined') {
-                Util.Info("Sending key code (" + (down ? "down" : "up") + "): " + code);
-                RFB.messages.keyEvent(this._sock, code, down ? 1 : 0);
+                Util.Info("Sending keysym (" + (down ? "down" : "up") + "): " + keysym);
+                RFB.messages.keyEvent(this._sock, keysym, down ? 1 : 0);
             } else {
-                Util.Info("Sending key code (down + up): " + code);
-                RFB.messages.keyEvent(this._sock, code, 1);
-                RFB.messages.keyEvent(this._sock, code, 0);
+                Util.Info("Sending keysym (down + up): " + keysym);
+                RFB.messages.keyEvent(this._sock, keysym, 1);
+                RFB.messages.keyEvent(this._sock, keysym, 0);
             }
             return true;
         },
         requestDesktopSize: function (width, height) {
             if (this._rfb_connection_state !== 'connected' ||
                 this._view_only) {
-                return;
+                return false;
             }
 
             if (this._supportsSetDesktopSize) {
 
         _connect: function () {
             Util.Debug(">> RFB.connect");
+            this._init_vars();
 
             var uri;
             if (typeof UsingSocketIO !== 'undefined') {
             uri += '://' + this._rfb_host + ':' + this._rfb_port + '/' + this._rfb_path;
             Util.Info("connecting to " + uri);
 
-            this._sock.open(uri, this._wsProtocols);
+            try {
+                // WebSocket.onopen transitions to the RFB init states
+                this._sock.open(uri, this._wsProtocols);
+            } catch (e) {
+                if (e.name === 'SyntaxError') {
+                    this._fail("Invalid host or port value given", e);
+                } else {
+                    this._fail("Error while connecting", e);
+                }
+            }
 
             Util.Debug("<< RFB.connect");
         },
 
+        _disconnect: function () {
+            Util.Debug(">> RFB.disconnect");
+            this._cleanup();
+            this._sock.close();
+            this._print_stats();
+            Util.Debug("<< RFB.disconnect");
+        },
+
         _init_vars: function () {
             // reset state
             this._FBU.rects        = 0;
                 return;
             }
 
-            this._rfb_connection_state = state;
-
-            var smsg = "New state '" + state + "', was '" + oldstate + "'.";
-            Util.Debug(smsg);
-
-            if (this._disconnTimer && state !== 'disconnecting') {
-                Util.Debug("Clearing disconnect timer");
-                clearTimeout(this._disconnTimer);
-                this._disconnTimer = null;
-                this._sock.off('close');  // make sure we don't get a double event
-            }
-
-            this._onUpdateState(this, state, oldstate);
+            // Ensure proper transitions before doing anything
             switch (state) {
                 case 'connected':
                     if (oldstate !== 'connecting') {
                                    "previous connection state: " + oldstate);
                         return;
                     }
+                    break;
 
+                case 'connecting':
+                    if (oldstate !== '') {
+                        Util.Error("Bad transition to connecting state, " +
+                                   "previous connection state: " + oldstate);
+                        return;
+                    }
+                    break;
+
+                case 'disconnecting':
+                    if (oldstate !== 'connected' && oldstate !== 'connecting') {
+                        Util.Error("Bad transition to disconnecting state, " +
+                                   "previous connection state: " + oldstate);
+                        return;
+                    }
+                    break;
+
+                default:
+                    Util.Error("Unknown connection state: " + state);
+                    return;
+            }
+
+            // State change actions
+
+            this._rfb_connection_state = state;
+            this._onUpdateState(this, state, oldstate);
+
+            var smsg = "New state '" + state + "', was '" + oldstate + "'.";
+            Util.Debug(smsg);
+
+            if (this._disconnTimer && state !== 'disconnecting') {
+                Util.Debug("Clearing disconnect timer");
+                clearTimeout(this._disconnTimer);
+                this._disconnTimer = null;
+
+                // make sure we don't get a double event
+                this._sock.off('close');
+            }
+
+            switch (state) {
+                case 'disconnected':
+                    // Call onDisconnected callback after onUpdateState since
+                    // we don't know if the UI only displays the latest message
                     if (this._rfb_disconnect_reason !== "") {
                         this._onDisconnected(this, this._rfb_disconnect_reason);
                     } else {
                     break;
 
                 case 'connecting':
-                    this._init_vars();
-
-                    // WebSocket.onopen transitions to the RFB init states
                     this._connect();
                     break;
 
                 case 'disconnecting':
-                    this._cleanup();
-                    this._sock.close(); // transitions to 'disconnected'
+                    this._disconnect();
 
                     this._disconnTimer = setTimeout(function () {
-                        this._rfb_disconnect_reason = "Disconnect timeout";
+                        this._rfb_disconnect_reason = _("Disconnect timeout");
                         this._updateConnectionState('disconnected');
                     }.bind(this), this._disconnectTimeout * 1000);
-
-                    this._print_stats();
                     break;
-
-                default:
-                    Util.Error("Unknown connection state: " + state);
-                    return;
             }
         },
 
-        _fail: function (msg) {
+        /* Print errors and disconnect
+         *
+         * The optional parameter 'details' is used for information that
+         * should be logged but not sent to the user interface.
+         */
+        _fail: function (msg, details) {
+            var fullmsg = msg;
+            if (typeof details !== 'undefined') {
+                fullmsg = msg + " (" + details + ")";
+            }
             switch (this._rfb_connection_state) {
                 case 'disconnecting':
-                    Util.Error("Error while disconnecting: " + msg);
+                    Util.Error("Failed when disconnecting: " + fullmsg);
                     break;
                 case 'connected':
-                    Util.Error("Error while connected: " + msg);
+                    Util.Error("Failed while connected: " + fullmsg);
                     break;
                 case 'connecting':
-                    Util.Error("Error while connecting: " + msg);
+                    Util.Error("Failed when connecting: " + fullmsg);
                     break;
                 default:
-                    Util.Error("RFB error: " + msg);
+                    Util.Error("RFB failure: " + fullmsg);
                     break;
             }
-            this._rfb_disconnect_reason = msg;
+            this._rfb_disconnect_reason = msg; //This is sent to the UI
+
+            // Transition to disconnected without waiting for socket to close
             this._updateConnectionState('disconnecting');
+            this._updateConnectionState('disconnected');
+
             return false;
         },
 
 
         _negotiate_protocol_version: function () {
             if (this._sock.rQlen() < 12) {
-                return this._fail("Incomplete protocol version");
+                return this._fail("Error while negotiating with server",
+                                  "Incomplete protocol version");
             }
 
             var sversion = this._sock.rQshiftStr(12).substr(4, 7);
                     this._rfb_version = 3.8;
                     break;
                 default:
-                    return this._fail("Invalid server version " + sversion);
+                    return this._fail("Unsupported server",
+                                      "Invalid server version: " + sversion);
             }
 
             if (is_repeater) {
                 if (num_types === 0) {
                     var strlen = this._sock.rQshift32();
                     var reason = this._sock.rQshiftStr(strlen);
-                    return this._fail("Security failure: " + reason);
+                    return this._fail("Error while negotiating with server",
+                                      "Security failure: " + reason);
                 }
 
                 this._rfb_auth_scheme = 0;
                 }
 
                 if (this._rfb_auth_scheme === 0) {
-                    return this._fail("Unsupported security types: " + types);
+                    return this._fail("Unsupported server",
+                                      "Unsupported security types: " + types);
                 }
 
                 this._sock.send([this._rfb_auth_scheme]);
             if (serverSupportedTunnelTypes[0]) {
                 if (serverSupportedTunnelTypes[0].vendor != clientSupportedTunnelTypes[0].vendor ||
                     serverSupportedTunnelTypes[0].signature != clientSupportedTunnelTypes[0].signature) {
-                    return this._fail("Client's tunnel type had the incorrect vendor or signature");
+                    return this._fail("Unsupported server",
+                                      "Client's tunnel type had the incorrect " +
+                                      "vendor or signature");
                 }
                 this._sock.send([0, 0, 0, 0]);  // use NOTUNNEL
                 return false; // wait until we receive the sub auth count to continue
             } else {
-                return this._fail("Server wanted tunnels, but doesn't support the notunnel type");
+                return this._fail("Unsupported server",
+                                  "Server wanted tunnels, but doesn't support " +
+                                  "the notunnel type");
             }
         },
 
                             this._rfb_auth_scheme = 2;
                             return this._init_msg();
                         default:
-                            return this._fail("Unsupported tiny auth scheme: " + authType);
+                            return this._fail("Unsupported server",
+                                              "Unsupported tiny auth scheme: " +
+                                              authType);
                     }
                 }
             }
 
-            return this._fail("No supported sub-auth types!");
+            return this._fail("Unsupported server",
+                              "No supported sub-auth types!");
         },
 
         _negotiate_authentication: function () {
                     if (this._sock.rQwait("auth reason", 4)) { return false; }
                     var strlen = this._sock.rQshift32();
                     var reason = this._sock.rQshiftStr(strlen);
-                    return this._fail("Auth failure: " + reason);
+                    return this._fail("Authentication failure", reason);
 
                 case 1:  // no auth
                     if (this._rfb_version >= 3.8) {
                     return this._negotiate_tight_auth();
 
                 default:
-                    return this._fail("Unsupported auth scheme: " + this._rfb_auth_scheme);
+                    return this._fail("Unsupported server",
+                                      "Unsupported auth scheme: " +
+                                      this._rfb_auth_scheme);
             }
         },
 
                         var length = this._sock.rQshift32();
                         if (this._sock.rQwait("SecurityResult reason", length, 8)) { return false; }
                         var reason = this._sock.rQshiftStr(length);
-                        return this._fail(reason);
+                        return this._fail("Authentication failure", reason);
                     } else {
                         return this._fail("Authentication failure");
                     }
                     return false;
                 case 2:
-                    return this._fail("Too many auth attempts");
+                    return this._fail("Too many authentication attempts");
                 default:
-                    return this._fail("Unknown SecurityResult");
+                    return this._fail("Unsupported server",
+                                      "Unknown SecurityResult");
             }
         },
 
                     return this._negotiate_server_init();
 
                 default:
-                    return this._fail("Unknown init state: " +
+                    return this._fail("Internal error", "Unknown init state: " +
                                       this._rfb_init_state);
             }
         },
              */
 
             if (!(flags & (1<<31))) {
-                return this._fail("Unexpected fence response");
+                return this._fail("Internal error",
+                                  "Unexpected fence response");
             }
 
             // Filter out unsupported flags
                     this._onXvpInit(this._rfb_xvp_ver);
                     break;
                 default:
-                    this._fail("Disconnected: illegal server XVP message " + xvp_msg);
+                    this._fail("Unexpected server message",
+                               "Illegal server XVP message " + xvp_msg);
                     break;
             }
 
                     return this._handle_xvp_msg();
 
                 default:
-                    this._fail("Disconnected: illegal server message type " + msg_type);
+                    this._fail("Unexpected server message", "Type:" + msg_type);
                     Util.Debug("sock.rQslice(0, 30): " + this._sock.rQslice(0, 30));
                     return true;
             }
                          'encodingName': this._encNames[this._FBU.encoding]});
 
                     if (!this._encNames[this._FBU.encoding]) {
-                        this._fail("Disconnected: unsupported encoding " +
+                        this._fail("Unexpected server message",
+                                   "Unsupported encoding " +
                                    this._FBU.encoding);
                         return false;
                     }
                 if (this._sock.rQwait("HEXTILE subencoding", this._FBU.bytes)) { return false; }
                 var subencoding = rQ[rQi];  // Peek
                 if (subencoding > 30) {  // Raw
-                    this._fail("Disconnected: illegal hextile subencoding " + subencoding);
+                    this._fail("Unexpected server message",
+                               "Illegal hextile subencoding: " + subencoding);
                     return false;
                 }
 
 
         display_tight: function (isTightPNG) {
             if (this._fb_depth === 1) {
-                this._fail("Tight protocol handler only implements true color mode");
+                this._fail("Internal error",
+                           "Tight protocol handler only implements " +
+                           "true color mode");
             }
 
             this._FBU.bytes = 1;  // compression-control byte
             else if (ctl === 0x0A)  cmode = "png";
             else if (ctl & 0x04)    cmode = "filter";
             else if (ctl < 0x04)    cmode = "copy";
-            else return this._fail("Illegal tight compression received, ctl: " + ctl);
+            else return this._fail("Unexpected server message",
+                                   "Illegal tight compression received, " +
+                                   "ctl: " + ctl);
 
             if (isTightPNG && (cmode === "filter" || cmode === "copy")) {
-                return this._fail("filter/copy received in tightPNG mode");
+                return this._fail("Unexpected server message",
+                                  "filter/copy received in tightPNG mode");
             }
 
             switch (cmode) {
                     } 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 use if jpeg is enabled
-                        this._fail("Unsupported tight subencoding received, filter: " + filterId);
+                        this._fail("Unexpected server message",
+                                   "Unsupported tight subencoding received, " +
+                                   "filter: " + filterId);
                     }
                     break;
                 case "copy":
index e34f5d8812b6825a85408dae6c8ca5dc9eb7454a..5054ee9efd05c58b60dd2df87149373c2ffc898e 100644 (file)
@@ -366,4 +366,155 @@ Util.Flash = (function () {
     return {version: parseInt(version[0] || 0 + '.' + version[1], 10) || 0, build: parseInt(version[2], 10) || 0};
 }());
 
+
+Util.Localisation = {
+    // Currently configured language
+    language: 'en',
+
+    // Configure suitable language based on user preferences
+    setup: function (supportedLanguages) {
+        var userLanguages;
+
+        Util.Localisation.language = 'en'; // Default: US English
+
+        /*
+         * Navigator.languages only available in Chrome (32+) and FireFox (32+)
+         * Fall back to navigator.language for other browsers
+         */
+        if (typeof window.navigator.languages == 'object') {
+            userLanguages = window.navigator.languages;
+        } else {
+            userLanguages = [navigator.language || navigator.userLanguage];
+        }
+
+        for (var i = 0;i < userLanguages.length;i++) {
+            var userLang = userLanguages[i];
+            userLang = userLang.toLowerCase();
+            userLang = userLang.replace("_", "-");
+            userLang = userLang.split("-");
+
+            // Built-in default?
+            if ((userLang[0] === 'en') &&
+                ((userLang[1] === undefined) || (userLang[1] === 'us'))) {
+                return;
+            }
+
+            // First pass: perfect match
+            for (var j = 0;j < supportedLanguages.length;j++) {
+                var supLang = supportedLanguages[j];
+                supLang = supLang.toLowerCase();
+                supLang = supLang.replace("_", "-");
+                supLang = supLang.split("-");
+
+                if (userLang[0] !== supLang[0])
+                    continue;
+                if (userLang[1] !== supLang[1])
+                    continue;
+
+                Util.Localisation.language = supportedLanguages[j];
+                return;
+            }
+
+            // Second pass: fallback
+            for (var j = 0;j < supportedLanguages.length;j++) {
+                supLang = supportedLanguages[j];
+                supLang = supLang.toLowerCase();
+                supLang = supLang.replace("_", "-");
+                supLang = supLang.split("-");
+
+                if (userLang[0] !== supLang[0])
+                    continue;
+                if (supLang[1] !== undefined)
+                    continue;
+
+                Util.Localisation.language = supportedLanguages[j];
+                return;
+            }
+        }
+    },
+
+    // Retrieve localised text
+    get: function (id) {
+        if (typeof Language !== 'undefined' && Language[id]) {
+            return Language[id];
+        } else {
+            return id;
+        }
+    },
+
+    // Traverses the DOM and translates relevant fields
+    // See https://html.spec.whatwg.org/multipage/dom.html#attr-translate
+    translateDOM: function () {
+        function process(elem, enabled) {
+            function isAnyOf(searchElement, items) {
+                return items.indexOf(searchElement) !== -1;
+            }
+
+            function translateAttribute(elem, attr) {
+                var str = elem.getAttribute(attr);
+                str = Util.Localisation.get(str);
+                elem.setAttribute(attr, str);
+            }
+
+            function translateTextNode(node) {
+                var str = node.data.trim();
+                str = Util.Localisation.get(str);
+                node.data = str;
+            }
+
+            if (elem.hasAttribute("translate")) {
+                if (isAnyOf(elem.getAttribute("translate"), ["", "yes"])) {
+                    enabled = true;
+                } else if (isAnyOf(elem.getAttribute("translate"), ["no"])) {
+                    enabled = false;
+                }
+            }
+
+            if (enabled) {
+                if (elem.hasAttribute("abbr") &&
+                    elem.tagName === "TH") {
+                    translateAttribute(elem, "abbr");
+                }
+                if (elem.hasAttribute("alt") &&
+                    isAnyOf(elem.tagName, ["AREA", "IMG", "INPUT"])) {
+                    translateAttribute(elem, "alt");
+                }
+                if (elem.hasAttribute("download") &&
+                    isAnyOf(elem.tagName, ["A", "AREA"])) {
+                    translateAttribute(elem, "download");
+                }
+                if (elem.hasAttribute("label") &&
+                    isAnyOf(elem.tagName, ["MENUITEM", "MENU", "OPTGROUP",
+                                   "OPTION", "TRACK"])) {
+                    translateAttribute(elem, "label");
+                }
+                // FIXME: Should update "lang"
+                if (elem.hasAttribute("placeholder") &&
+                    isAnyOf(elem.tagName in ["INPUT", "TEXTAREA"])) {
+                    translateAttribute(elem, "placeholder");
+                }
+                if (elem.hasAttribute("title")) {
+                    translateAttribute(elem, "title");
+                }
+                if (elem.hasAttribute("value") &&
+                    elem.tagName === "INPUT" &&
+                    isAnyOf(elem.getAttribute("type"), ["reset", "button"])) {
+                    translateAttribute(elem, "value");
+                }
+            }
+
+            for (var i = 0;i < elem.childNodes.length;i++) {
+                node = elem.childNodes[i];
+                if (node.nodeType === node.ELEMENT_NODE) {
+                    process(node, enabled);
+                } else if (node.nodeType === node.TEXT_NODE && enabled) {
+                    translateTextNode(node);
+                }
+            }
+        }
+
+        process(document.body, true);
+    },
+};
+
 /* [module] export default Util; */
index c169e922131352c8153314711af1f5734d70aaea..52f76f6db98e0a406fc2175c686b1638fa362d5d 100644 (file)
   },
   "homepage": "https://github.com/kanaka/noVNC",
   "devDependencies": {
-    "ansi": "^0.3.0",
+    "ansi": "^0.3.1",
     "babel-plugin-add-module-exports": "^0.2.1",
-    "babel-plugin-transform-es2015-modules-commonjs": "^6.14.0",
+    "babel-plugin-transform-es2015-modules-commonjs": "^6.18.0",
     "babelify": "^7.3.0",
     "browserify": "^13.1.0",
-    "casperjs": "^1.1.0-beta3",
-    "chai": "^2.1.0",
-    "commander": "^2.8.1",
-    "fs-extra": "^0.30.0",
-    "karma": "^0.12.31",
+    "casperjs": "^1.1.3",
+    "chai": "^3.5.0",
+    "commander": "^2.9.0",
+    "fs-extra": "^1.0.0",
+    "jsdom": "*",
+    "karma": "^1.3.0",
     "karma-chai": "^0.1.0",
-    "karma-mocha": "^0.1.10",
-    "karma-mocha-reporter": "^1.0.0",
-    "karma-phantomjs-launcher": "^1.0.0",
-    "karma-sauce-launcher": "^0.2.10",
-    "karma-sinon": "^1.0.4",
+    "karma-mocha": "^1.3.0",
+    "karma-mocha-reporter": "^2.2.0",
+    "karma-phantomjs-launcher": "^1.0.2",
+    "karma-sauce-launcher": "^1.0.0",
+    "karma-sinon": "^1.0.5",
     "karma-sinon-chai-latest": "^0.1.0",
-    "mocha": "^2.1.0",
+    "mocha": "^3.1.2",
+    "node-getopt": "*",
     "open": "^0.0.5",
-    "phantomjs-prebuilt": "^2.1.4",
-    "sinon": "^1.12.2",
-    "sinon-chai": "^2.7.0",
+    "phantomjs-prebuilt": "^2.1.13",
+    "po2json": "*",
+    "sinon": "^1.17.6",
+    "sinon-chai": "^2.8.0",
     "spooky": "^0.2.5",
-    "temp": "^0.8.1",
+    "temp": "^0.8.3",
     "through2": "^2.0.1"
   },
   "dependencies": {
diff --git a/po/Makefile b/po/Makefile
new file mode 100644 (file)
index 0000000..b64b763
--- /dev/null
@@ -0,0 +1,34 @@
+all:
+.PHONY: update-po update-js update-pot
+
+LINGUAS := de el nl sv
+
+VERSION := $(shell grep '"version"' ../package.json | cut -d '"' -f 4)
+
+POFILES := $(addsuffix .po,$(LINGUAS))
+JSFILES := $(addprefix ../app/locale/,$(addsuffix .js,$(LINGUAS)))
+
+update-po: $(POFILES)
+update-js: $(JSFILES)
+
+%.po: noVNC.pot
+       msgmerge --update --lang=$* $@ $<
+../app/locale/%.js: %.po
+       ./po2js $< $@
+
+update-pot:
+       xgettext --output=noVNC.js.pot \
+               --copyright-holder="Various Authors" \
+               --package-name="noVNC" \
+               --package-version="$(VERSION)" \
+               --msgid-bugs-address="novnc@googlegroups.com" \
+               --add-comments=TRANSLATORS: \
+               --sort-by-file \
+               ../app/*.js \
+               ../core/*.js \
+               ../core/input/*.js
+       ./xgettext-html --output=noVNC.html.pot \
+               ../vnc.html
+       msgcat --output-file=noVNC.pot \
+               --sort-by-file noVNC.js.pot noVNC.html.pot
+       rm -f noVNC.js.pot noVNC.html.pot
diff --git a/po/de.po b/po/de.po
new file mode 100644 (file)
index 0000000..5f15d57
--- /dev/null
+++ b/po/de.po
@@ -0,0 +1,58 @@
+# German translations for noVNC package
+# German translation for noVNC.
+# Copyright (C) 2016 Various Authors
+# This file is distributed under the same license as the noVNC package.
+# Loek Janssen <loekjanssen@gmail.com>, 2016.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: noVNC 0.6.1\n"
+"Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
+"POT-Creation-Date: 2016-11-15 08:11+0100\n"
+"PO-Revision-Date: 2016-11-15 07:51+0100\n"
+"Last-Translator: Loek Janssen <loekjanssen@gmail.com>\n"
+"Language-Team: none\n"
+"Language: de\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: ../app/ui.js:402
+msgid "Connecting..."
+msgstr "Verbunden..."
+
+#: ../app/ui.js:409
+msgid "Connected (encrypted) to "
+msgstr "Verbunden mit (verschlรผsselt) "
+
+#: ../app/ui.js:411
+msgid "Connected (unencrypted) to "
+msgstr "Verbunden mit (unverschlรผsselt) "
+
+#: ../app/ui.js:416
+msgid "Disconnecting..."
+msgstr "Verbindung trennen..."
+
+#: ../app/ui.js:421
+msgid "Disconnected"
+msgstr "Verbindung zum Server getrennt"
+
+#: ../app/ui.js:1006 ../core/rfb.js:278
+msgid "Must set host and port"
+msgstr "Richten Sie Host und Port ein"
+
+#: ../app/ui.js:1059
+msgid "Password is required"
+msgstr "Passwort ist erforderlich"
+
+#: ../app/ui.js:1272
+msgid ""
+"Forcing clipping mode since scrollbars aren't supported by IE in fullscreen"
+msgstr ""
+"'Clipping-Modus' aktiviert, Scrollbalken in 'IE-Vollbildmodus' werden nicht "
+"unterstรผtzt"
+
+#: ../core/rfb.js:556
+msgid "Disconnect timeout"
+msgstr "Timeout beim trennen"
diff --git a/po/el.po b/po/el.po
new file mode 100644 (file)
index 0000000..b6a25fd
--- /dev/null
+++ b/po/el.po
@@ -0,0 +1,293 @@
+# Greek translations for noVNC package.
+# Copyright (C) 2016 Various Authors
+# This file is distributed under the same license as the noVNC package.
+# Giannis Kosmas <kosmasgiannis@gmail.com>, 2016.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: noVNC 0.6.1\n"
+"Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
+"POT-Creation-Date: 2016-11-24 08:53+0200\n"
+"PO-Revision-Date: 2016-11-15 07:51+0100\n"
+"Last-Translator: Giannis Kosmas <kosmasgiannis@gmail.com>\n"
+"Language-Team: none\n"
+"Language: el\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: ../app/ui.js:405
+msgid "Connecting..."
+msgstr "ฮฃฯ…ฮฝฮดฮญฮตฯ„ฮฑฮน..."
+
+#: ../app/ui.js:412
+msgid "Connected (encrypted) to "
+msgstr "ฮฃฯ…ฮฝฮดฮญฮธฮทฮบฮต (ฮบฯฯ…ฯ€ฯ„ฮฟฮณฯฮฑฯ†ฮทฮผฮญฮฝฮฑ) ฮผฮต ฯ„ฮฟ "
+
+#: ../app/ui.js:414
+msgid "Connected (unencrypted) to "
+msgstr "ฮฃฯ…ฮฝฮดฮญฮธฮทฮบฮต (ฮผฮท ฮบฯฯ…ฯ€ฯ„ฮฟฮณฯฮฑฯ†ฮทฮผฮญฮฝฮฑ) ฮผฮต ฯ„ฮฟ "
+
+#: ../app/ui.js:419
+msgid "Disconnecting..."
+msgstr "Aฯ€ฮฟฯƒฯ…ฮฝฮดฮญฮตฯ„ฮฑฮน..."
+
+#: ../app/ui.js:424
+msgid "Disconnected"
+msgstr "ฮ‘ฯ€ฮฟฯƒฯ…ฮฝฮดฮญฮธฮทฮบฮต"
+
+#: ../app/ui.js:1009 ../core/rfb.js:278
+msgid "Must set host and port"
+msgstr "ฮ ฯฮญฯ€ฮตฮน ฮฝฮฑ ฮฟฯฮนฯƒฯ„ฮตฮฏ ฯ„ฮฟ ฯŒฮฝฮฟฮผฮฑ ฮบฮฑฮน ฮท ฯ€ฯŒฯฯ„ฮฑ ฯ„ฮฟฯ… ฮดฮนฮฑฮบฮฟฮผฮนฯƒฯ„ฮฎ"
+
+#: ../app/ui.js:1062
+msgid "Password is required"
+msgstr "ฮ‘ฯ€ฮฑฮนฯ„ฮตฮฏฯ„ฮฑฮน ฮฟ ฮบฯ‰ฮดฮนฮบฯŒฯ‚ ฯ€ฯฯŒฯƒฮฒฮฑฯƒฮทฯ‚"
+
+#: ../app/ui.js:1275
+msgid ""
+"Forcing clipping mode since scrollbars aren't supported by IE in fullscreen"
+msgstr ""
+"ฮ•ฯ†ฮฑฯฮผฮฟฮณฮฎ ฮปฮตฮนฯ„ฮฟฯ…ฯฮณฮฏฮฑฯ‚ ฮฑฯ€ฮฟฮบฮฟฯ€ฮฎฯ‚ ฮฑฯ†ฮฟฯ ฮดฮตฮฝ ฯ…ฯ€ฮฟฯƒฯ„ฮทฯฮฏฮถฮฟฮฝฯ„ฮฑฮน ฮฟฮน ฮปฯ‰ฯฮฏฮดฮตฯ‚ ฮบฯฮปฮนฯƒฮทฯ‚ ฯƒฮต "
+"ฯ€ฮปฮฎฯฮท ฮฟฮธฯŒฮฝฮท ฯƒฯ„ฮฟฮฝ IE"
+
+#: ../core/rfb.js:556
+msgid "Disconnect timeout"
+msgstr "ฮ ฮฑฯฮญฮปฮตฯ…ฯƒฮท ฯ‡ฯฮฟฮฝฮนฮบฮฟฯ ฮฟฯฮฏฮฟฯ… ฮฑฯ€ฮฟฯƒฯฮฝฮดฮตฯƒฮทฯ‚"
+
+#: ../vnc.html:70
+msgid "noVNC encountered an error:"
+msgstr "ฯ„ฮฟ noVNC ฮฑฮฝฯ„ฮนฮผฮตฯ„ฯŽฯ€ฮนฯƒฮต ฮญฮฝฮฑ ฯƒฯ†ฮฌฮปฮผฮฑ:"
+
+#: ../vnc.html:78
+msgid "Hide/Show the control bar"
+msgstr "ฮ‘ฯ€ฯŒฮบฯฯ…ฯˆฮท/ฮ•ฮผฯ†ฮฌฮฝฮนฯƒฮท ฮณฯฮฑฮผฮผฮฎฯ‚ ฮตฮปฮญฮณฯ‡ฮฟฯ…"
+
+#: ../vnc.html:85
+msgid "Move/Drag Viewport"
+msgstr "ฮœฮตฯ„ฮฑฮบฮฏฮฝฮทฯƒฮท/ฮฃฯฯฯƒฮนฮผฮฟ ฮ˜ฮตฮฑฯ„ฮฟฯ ฯ€ฮตฮดฮฏฮฟฯ…"
+
+#: ../vnc.html:85
+msgid "viewport drag"
+msgstr "ฯƒฯฯฯƒฮนฮผฮฟ ฮธฮตฮฑฯ„ฮฟฯ ฯ€ฮตฮดฮฏฮฟฯ…"
+
+#: ../vnc.html:91 ../vnc.html:94 ../vnc.html:97 ../vnc.html:100
+msgid "Active Mouse Button"
+msgstr "ฮ•ฮฝฮตฯฮณฯŒ ฮ ฮปฮฎฮบฯ„ฯฮฟ ฮ ฮฟฮฝฯ„ฮนฮบฮนฮฟฯ"
+
+#: ../vnc.html:91
+msgid "No mousebutton"
+msgstr "ฮงฯ‰ฯฮฏฯ‚ ฮ ฮปฮฎฮบฯ„ฯฮฟ ฮ ฮฟฮฝฯ„ฮนฮบฮนฮฟฯ"
+
+#: ../vnc.html:94
+msgid "Left mousebutton"
+msgstr "ฮ‘ฯฮนฯƒฯ„ฮตฯฯŒ ฮ ฮปฮฎฮบฯ„ฯฮฟ ฮ ฮฟฮฝฯ„ฮนฮบฮนฮฟฯ"
+
+#: ../vnc.html:97
+msgid "Middle mousebutton"
+msgstr "ฮœฮตฯƒฮฑฮฏฮฟ ฮ ฮปฮฎฮบฯ„ฯฮฟ ฮ ฮฟฮฝฯ„ฮนฮบฮนฮฟฯ"
+
+#: ../vnc.html:100
+msgid "Right mousebutton"
+msgstr "ฮ”ฮตฮพฮฏ ฮ ฮปฮฎฮบฯ„ฯฮฟ ฮ ฮฟฮฝฯ„ฮนฮบฮนฮฟฯ"
+
+#: ../vnc.html:103
+msgid "Keyboard"
+msgstr "ฮ ฮปฮทฮบฯ„ฯฮฟฮปฯŒฮณฮนฮฟ"
+
+#: ../vnc.html:103
+msgid "Show Keyboard"
+msgstr "ฮ•ฮผฯ†ฮฌฮฝฮนฯƒฮท ฮ ฮปฮทฮบฯ„ฯฮฟฮปฮฟฮณฮฏฮฟฯ…"
+
+#: ../vnc.html:110
+msgid "Extra keys"
+msgstr "ฮ•ฯ€ฮนฯ€ฮปฮญฮฟฮฝ ฯ€ฮปฮฎฮบฯ„ฯฮฑ"
+
+#: ../vnc.html:110
+msgid "Show Extra Keys"
+msgstr "ฮ•ฮผฯ†ฮฌฮฝฮนฯƒฮท ฮ•ฯ€ฮนฯ€ฮปฮญฮฟฮฝ ฮ ฮปฮฎฮบฯ„ฯฯ‰ฮฝ"
+
+#: ../vnc.html:115
+msgid "Ctrl"
+msgstr "Ctrl"
+
+#: ../vnc.html:115
+msgid "Toggle Ctrl"
+msgstr "ฮ•ฮฝฮฑฮปฮปฮฑฮณฮฎ Ctrl"
+
+#: ../vnc.html:118
+msgid "Alt"
+msgstr "Alt"
+
+#: ../vnc.html:118
+msgid "Toggle Alt"
+msgstr "ฮ•ฮฝฮฑฮปฮปฮฑฮณฮฎ Alt"
+
+#: ../vnc.html:121
+msgid "Send Tab"
+msgstr "ฮ‘ฯ€ฮฟฯƒฯ„ฮฟฮปฮฎ Tab"
+
+#: ../vnc.html:121
+msgid "Tab"
+msgstr "Tab"
+
+#: ../vnc.html:124
+msgid "Esc"
+msgstr "Esc"
+
+#: ../vnc.html:124
+msgid "Send Escape"
+msgstr "ฮ‘ฯ€ฮฟฯƒฯ„ฮฟฮปฮฎ Escape"
+
+#: ../vnc.html:127
+msgid "Ctrl+Alt+Del"
+msgstr "Ctrl+Alt+Del"
+
+#: ../vnc.html:127
+msgid "Send Ctrl-Alt-Del"
+msgstr "ฮ‘ฯ€ฮฟฯƒฯ„ฮฟฮปฮฎ Ctrl-Alt-Del"
+
+#: ../vnc.html:135
+msgid "Shutdown/Reboot"
+msgstr "ฮšฮปฮตฮฏฯƒฮนฮผฮฟ/ฮ•ฯ€ฮฑฮฝฮตฮบฮบฮฏฮฝฮทฯƒฮท"
+
+#: ../vnc.html:135
+msgid "Shutdown/Reboot..."
+msgstr "ฮšฮปฮตฮฏฯƒฮนฮผฮฟ/ฮ•ฯ€ฮฑฮฝฮตฮบฮบฮฏฮฝฮทฯƒฮท..."
+
+#: ../vnc.html:141
+msgid "Power"
+msgstr "ฮ‘ฯ€ฮตฮฝฮตฯฮณฮฟฯ€ฮฟฮฏฮทฯƒฮท"
+
+#: ../vnc.html:143
+msgid "Shutdown"
+msgstr "ฮšฮปฮตฮฏฯƒฮนฮผฮฟ"
+
+#: ../vnc.html:144
+msgid "Reboot"
+msgstr "ฮ•ฯ€ฮฑฮฝฮตฮบฮบฮฏฮฝฮทฯƒฮท"
+
+#: ../vnc.html:145
+msgid "Reset"
+msgstr "ฮ•ฯ€ฮฑฮฝฮฑฯ†ฮฟฯฮฌ"
+
+#: ../vnc.html:150 ../vnc.html:156
+msgid "Clipboard"
+msgstr "ฮ ฯฯŒฯ‡ฮตฮนฯฮฟ"
+
+#: ../vnc.html:160
+msgid "Clear"
+msgstr "ฮšฮฑฮธฮฌฯฮนฯƒฮผฮฑ"
+
+#: ../vnc.html:166
+msgid "Fullscreen"
+msgstr "ฮ ฮปฮฎฯฮทฯ‚ ฮŸฮธฯŒฮฝฮท"
+
+#: ../vnc.html:171 ../vnc.html:178
+msgid "Settings"
+msgstr "ฮกฯ…ฮธฮผฮฏฯƒฮตฮนฯ‚"
+
+#: ../vnc.html:181
+msgid "Encrypt"
+msgstr "ฮšฯฯ…ฯ€ฯ„ฮฟฮณฯฮฌฯ†ฮทฯƒฮท"
+
+#: ../vnc.html:184
+msgid "True Color"
+msgstr "ฮ ฯฮฑฮณฮผฮฑฯ„ฮนฮบฮฌ ฮงฯฯŽฮผฮฑฯ„ฮฑ"
+
+#: ../vnc.html:187
+msgid "Local Cursor"
+msgstr "ฮคฮฟฯ€ฮนฮบฯŒฯ‚ ฮ”ฯฮฟฮผฮญฮฑฯ‚"
+
+#: ../vnc.html:190
+msgid "Clip to Window"
+msgstr "ฮ‘ฯ€ฮฟฮบฮฟฯ€ฮฎ ฯƒฯ„ฮฟ ฯŒฯฮนฮฟ ฯ„ฮฟฯ… ฮ ฮฑฯฮฌฮธฯ…ฯฮฟฯ…"
+
+#: ../vnc.html:193
+msgid "Shared Mode"
+msgstr "ฮšฮฟฮนฮฝฯŒฯ‡ฯฮทฯƒฯ„ฮท ฮ›ฮตฮนฯ„ฮฟฯ…ฯฮณฮฏฮฑ"
+
+#: ../vnc.html:196
+msgid "View Only"
+msgstr "ฮœฯŒฮฝฮฟ ฮ˜ฮญฮฑฯƒฮท"
+
+#: ../vnc.html:200
+msgid "Path:"
+msgstr "ฮ”ฮนฮฑฮดฯฮฟฮผฮฎ:"
+
+#: ../vnc.html:204
+msgid "Scaling Mode:"
+msgstr "ฮ›ฮตฮนฯ„ฮฟฯ…ฯฮณฮฏฮฑ ฮšฮปฮนฮผฮฌฮบฯ‰ฯƒฮทฯ‚:"
+
+#: ../vnc.html:206
+msgid "None"
+msgstr "ฮšฮฑฮผฮฏฮฑ"
+
+#: ../vnc.html:207
+msgid "Local Scaling"
+msgstr "ฮคฮฟฯ€ฮนฮบฮฎ ฮšฮปฮนฮผฮฌฮบฯ‰ฯƒฮท"
+
+#: ../vnc.html:208
+msgid "Local Downscaling"
+msgstr "ฮคฮฟฯ€ฮนฮบฮฎ ฮฃฯ…ฯฯฮฏฮบฮฝฯ‰ฯƒฮท"
+
+#: ../vnc.html:209
+msgid "Remote Resizing"
+msgstr "ฮ‘ฯ€ฮฟฮผฮฑฮบฯฯ…ฯƒฮผฮญฮฝฮท ฮ‘ฮปฮปฮฑฮณฮฎ ฮผฮตฮณฮญฮธฮฟฯ…ฯ‚"
+
+#: ../vnc.html:213
+msgid "Repeater ID:"
+msgstr "Repeater ID:"
+
+#: ../vnc.html:219
+msgid "Style:"
+msgstr "ฮฃฯ„ฯ…ฮป:"
+
+#: ../vnc.html:221
+msgid "default"
+msgstr "ฯ€ฯฮฟฮตฯ€ฮนฮปฮตฮณฮผฮญฮฝฮฟ"
+
+#: ../vnc.html:227
+msgid "Logging:"
+msgstr "ฮšฮฑฯ„ฮฑฮณฯฮฑฯ†ฮฎ:"
+
+#: ../vnc.html:234
+msgid "Apply"
+msgstr "ฮ•ฯ†ฮฑฯฮผฮฟฮณฮฎ"
+
+#: ../vnc.html:241 ../vnc.html:271
+msgid "Connect"
+msgstr "ฮฃฯฮฝฮดฮตฯƒฮท"
+
+#: ../vnc.html:244
+msgid "Disconnect"
+msgstr "ฮ‘ฯ€ฮฟฯƒฯฮฝฮดฮตฯƒฮท"
+
+#: ../vnc.html:251
+msgid "Connection"
+msgstr "ฮฃฯฮฝฮดฮตฯƒฮท"
+
+#: ../vnc.html:254
+msgid "Host:"
+msgstr "ฮŒฮฝฮฟฮผฮฑ ฮดฮนฮฑฮบฮฟฮผฮนฯƒฯ„ฮฎ:"
+
+#: ../vnc.html:258
+msgid "Port:"
+msgstr "ฮ ฯŒฯฯ„ฮฑ ฮดฮนฮฑฮบฮฟฮผฮนฯƒฯ„ฮฎ:"
+
+#: ../vnc.html:262 ../vnc.html:290
+msgid "Password:"
+msgstr "ฮšฯ‰ฮดฮนฮบฯŒฯ‚ ฮ ฯฯŒฯƒฮฒฮฑฯƒฮทฯ‚:"
+
+#: ../vnc.html:266
+msgid "Token:"
+msgstr "ฮ”ฮนฮฑฮบฯฮนฯ„ฮนฮบฯŒ:"
+
+#: ../vnc.html:294
+msgid "Send Password"
+msgstr "ฮ‘ฯ€ฮฟฯƒฯ„ฮฟฮปฮฎ ฮšฯ‰ฮดฮนฮบฮฟฯ ฮ ฯฯŒฯƒฮฒฮฑฯƒฮทฯ‚"
+
+#: ../vnc.html:319
+msgid "Canvas not supported."
+msgstr "ฮ”ฮตฮฝ ฯ…ฯ€ฮฟฯƒฯ„ฮทฯฮฏฮถฮตฯ„ฮฑฮน ฯ„ฮฟ ฯƒฯ„ฮฟฮนฯ‡ฮตฮฏฮฟ Canvas"
diff --git a/po/nl.po b/po/nl.po
new file mode 100644 (file)
index 0000000..8a0bf74
--- /dev/null
+++ b/po/nl.po
@@ -0,0 +1,58 @@
+# Dutch translations for noVNC package
+# Nederlandse vertalingen voor het pakket noVNC.
+# Copyright (C) 2016 Various Authors
+# This file is distributed under the same license as the noVNC package.
+# Loek Janssen <loekjanssen@gmail.com>, 2016.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: noVNC 0.6.1\n"
+"Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
+"POT-Creation-Date: 2016-11-15 08:11+0100\n"
+"PO-Revision-Date: 2016-11-15 07:51+0100\n"
+"Last-Translator: Loek Janssen <loekjanssen@gmail.com>\n"
+"Language-Team: none\n"
+"Language: nl\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: ../app/ui.js:402
+msgid "Connecting..."
+msgstr "Verbinden..."
+
+#: ../app/ui.js:409
+msgid "Connected (encrypted) to "
+msgstr "Verbonden (versleuteld) met "
+
+#: ../app/ui.js:411
+msgid "Connected (unencrypted) to "
+msgstr "Verbonden (onversleuteld) met "
+
+#: ../app/ui.js:416
+msgid "Disconnecting..."
+msgstr "Verbinding verbreken..."
+
+#: ../app/ui.js:421
+msgid "Disconnected"
+msgstr "Verbinding verbroken"
+
+#: ../app/ui.js:1006 ../core/rfb.js:278
+msgid "Must set host and port"
+msgstr "Host en poort moeten worden ingesteld"
+
+#: ../app/ui.js:1059
+msgid "Password is required"
+msgstr "Wachtwoord is vereist"
+
+#: ../app/ui.js:1272
+msgid ""
+"Forcing clipping mode since scrollbars aren't supported by IE in fullscreen"
+msgstr ""
+"''Clipping mode' ingeschakeld, omdat schuifbalken in volledige-scherm-modus "
+"in IE niet worden ondersteund"
+
+#: ../core/rfb.js:556
+msgid "Disconnect timeout"
+msgstr "Timeout tijdens verbreken van verbinding"
diff --git a/po/noVNC.pot b/po/noVNC.pot
new file mode 100644 (file)
index 0000000..160a2d4
--- /dev/null
@@ -0,0 +1,291 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR Various Authors
+# This file is distributed under the same license as the noVNC package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: noVNC 0.6.1\n"
+"Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
+"POT-Creation-Date: 2016-11-15 19:32+0100\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../app/ui.js:405
+msgid "Connecting..."
+msgstr ""
+
+#: ../app/ui.js:412
+msgid "Connected (encrypted) to "
+msgstr ""
+
+#: ../app/ui.js:414
+msgid "Connected (unencrypted) to "
+msgstr ""
+
+#: ../app/ui.js:419
+msgid "Disconnecting..."
+msgstr ""
+
+#: ../app/ui.js:424
+msgid "Disconnected"
+msgstr ""
+
+#: ../app/ui.js:1009 ../core/rfb.js:278
+msgid "Must set host and port"
+msgstr ""
+
+#: ../app/ui.js:1062
+msgid "Password is required"
+msgstr ""
+
+#: ../app/ui.js:1275
+msgid ""
+"Forcing clipping mode since scrollbars aren't supported by IE in fullscreen"
+msgstr ""
+
+#: ../core/rfb.js:556
+msgid "Disconnect timeout"
+msgstr ""
+
+#: ../vnc.html:70
+msgid "noVNC encountered an error:"
+msgstr ""
+
+#: ../vnc.html:78
+msgid "Hide/Show the control bar"
+msgstr ""
+
+#: ../vnc.html:85
+msgid "Move/Drag Viewport"
+msgstr ""
+
+#: ../vnc.html:85
+msgid "viewport drag"
+msgstr ""
+
+#: ../vnc.html:91 ../vnc.html:94 ../vnc.html:97 ../vnc.html:100
+msgid "Active Mouse Button"
+msgstr ""
+
+#: ../vnc.html:91
+msgid "No mousebutton"
+msgstr ""
+
+#: ../vnc.html:94
+msgid "Left mousebutton"
+msgstr ""
+
+#: ../vnc.html:97
+msgid "Middle mousebutton"
+msgstr ""
+
+#: ../vnc.html:100
+msgid "Right mousebutton"
+msgstr ""
+
+#: ../vnc.html:103
+msgid "Keyboard"
+msgstr ""
+
+#: ../vnc.html:103
+msgid "Show Keyboard"
+msgstr ""
+
+#: ../vnc.html:110
+msgid "Extra keys"
+msgstr ""
+
+#: ../vnc.html:110
+msgid "Show Extra Keys"
+msgstr ""
+
+#: ../vnc.html:115
+msgid "Ctrl"
+msgstr ""
+
+#: ../vnc.html:115
+msgid "Toggle Ctrl"
+msgstr ""
+
+#: ../vnc.html:118
+msgid "Alt"
+msgstr ""
+
+#: ../vnc.html:118
+msgid "Toggle Alt"
+msgstr ""
+
+#: ../vnc.html:121
+msgid "Send Tab"
+msgstr ""
+
+#: ../vnc.html:121
+msgid "Tab"
+msgstr ""
+
+#: ../vnc.html:124
+msgid "Esc"
+msgstr ""
+
+#: ../vnc.html:124
+msgid "Send Escape"
+msgstr ""
+
+#: ../vnc.html:127
+msgid "Ctrl+Alt+Del"
+msgstr ""
+
+#: ../vnc.html:127
+msgid "Send Ctrl-Alt-Del"
+msgstr ""
+
+#: ../vnc.html:135
+msgid "Shutdown/Reboot"
+msgstr ""
+
+#: ../vnc.html:135
+msgid "Shutdown/Reboot..."
+msgstr ""
+
+#: ../vnc.html:141
+msgid "Power"
+msgstr ""
+
+#: ../vnc.html:143
+msgid "Shutdown"
+msgstr ""
+
+#: ../vnc.html:144
+msgid "Reboot"
+msgstr ""
+
+#: ../vnc.html:145
+msgid "Reset"
+msgstr ""
+
+#: ../vnc.html:150 ../vnc.html:156
+msgid "Clipboard"
+msgstr ""
+
+#: ../vnc.html:160
+msgid "Clear"
+msgstr ""
+
+#: ../vnc.html:166
+msgid "Fullscreen"
+msgstr ""
+
+#: ../vnc.html:171 ../vnc.html:178
+msgid "Settings"
+msgstr ""
+
+#: ../vnc.html:181
+msgid "Encrypt"
+msgstr ""
+
+#: ../vnc.html:184
+msgid "True Color"
+msgstr ""
+
+#: ../vnc.html:187
+msgid "Local Cursor"
+msgstr ""
+
+#: ../vnc.html:190
+msgid "Clip to Window"
+msgstr ""
+
+#: ../vnc.html:193
+msgid "Shared Mode"
+msgstr ""
+
+#: ../vnc.html:196
+msgid "View Only"
+msgstr ""
+
+#: ../vnc.html:200
+msgid "Path:"
+msgstr ""
+
+#: ../vnc.html:204
+msgid "Scaling Mode:"
+msgstr ""
+
+#: ../vnc.html:206
+msgid "None"
+msgstr ""
+
+#: ../vnc.html:207
+msgid "Local Scaling"
+msgstr ""
+
+#: ../vnc.html:208
+msgid "Local Downscaling"
+msgstr ""
+
+#: ../vnc.html:209
+msgid "Remote Resizing"
+msgstr ""
+
+#: ../vnc.html:213
+msgid "Repeater ID:"
+msgstr ""
+
+#: ../vnc.html:219
+msgid "Style:"
+msgstr ""
+
+#: ../vnc.html:221
+msgid "default"
+msgstr ""
+
+#: ../vnc.html:227
+msgid "Logging:"
+msgstr ""
+
+#: ../vnc.html:234
+msgid "Apply"
+msgstr ""
+
+#: ../vnc.html:241 ../vnc.html:271
+msgid "Connect"
+msgstr ""
+
+#: ../vnc.html:244
+msgid "Disconnect"
+msgstr ""
+
+#: ../vnc.html:251
+msgid "Connection"
+msgstr ""
+
+#: ../vnc.html:254
+msgid "Host:"
+msgstr ""
+
+#: ../vnc.html:258
+msgid "Port:"
+msgstr ""
+
+#: ../vnc.html:262 ../vnc.html:290
+msgid "Password:"
+msgstr ""
+
+#: ../vnc.html:266
+msgid "Token:"
+msgstr ""
+
+#: ../vnc.html:294
+msgid "Send Password"
+msgstr ""
+
+#: ../vnc.html:319
+msgid "Canvas not supported."
+msgstr ""
diff --git a/po/po2js b/po/po2js
new file mode 100755 (executable)
index 0000000..972f0b2
--- /dev/null
+++ b/po/po2js
@@ -0,0 +1,56 @@
+#!/usr/bin/env node
+/*
+ * ps2js: gettext .po to noVNC .js converter
+ * Copyright (C) 2016 Pierre Ossman
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+var getopt = require('node-getopt');
+var fs = require('fs');
+var po2json = require("po2json");
+
+opt = getopt.create([
+  ['h' , 'help'                , 'display this help'],
+]).bindHelp().parseSystem();
+
+if (opt.argv.length != 2) {
+  console.error("Incorrect number of arguments given");
+  process.exit(1);
+}
+
+var data = po2json.parseFileSync(opt.argv[0]);
+
+var output =
+"/*\n" +
+" * Translations for " + data[""]["language"] + "\n" +
+" *\n" +
+" * This file was autotomatically generated from " + opt.argv[0] + "\n" +
+" * DO NOT EDIT!\n" +
+" */\n" +
+"\n" +
+"Language = {\n";
+
+for (msgid in data) {
+  if (msgid === "")
+    continue;
+
+  msgstr = data[msgid][1];
+  output += "    " + JSON.stringify(msgid) + ": " +
+            JSON.stringify(msgstr) + ",\n";
+}
+
+output += "};\n";
+
+fs.writeFileSync(opt.argv[1], output);
diff --git a/po/sv.po b/po/sv.po
new file mode 100644 (file)
index 0000000..9df5ad5
--- /dev/null
+++ b/po/sv.po
@@ -0,0 +1,293 @@
+# Swedish translations for noVNC package
+# Svenska รถversรคttningar fรถr paket noVNC.
+# Copyright (C) 2016 Various Authors
+# This file is distributed under the same license as the noVNC package.
+# Samuel Mannehed <samuel@cendio.se>, 2016.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: noVNC 0.6.1\n"
+"Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
+"POT-Creation-Date: 2016-11-15 19:32+0100\n"
+"PO-Revision-Date: 2016-11-15 07:51+0100\n"
+"Last-Translator: Pierre Ossman <pierre@ossman.eu>\n"
+"Language-Team: none\n"
+"Language: sv\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: ../app/ui.js:405
+msgid "Connecting..."
+msgstr "Ansluter..."
+
+#: ../app/ui.js:412
+msgid "Connected (encrypted) to "
+msgstr "Ansluten (krypterat) till "
+
+#: ../app/ui.js:414
+msgid "Connected (unencrypted) to "
+msgstr "Ansluten (okrypterat) till "
+
+#: ../app/ui.js:419
+msgid "Disconnecting..."
+msgstr "Kopplar ner..."
+
+#: ../app/ui.js:424
+msgid "Disconnected"
+msgstr "Frรฅnkopplad"
+
+#: ../app/ui.js:1009 ../core/rfb.js:278
+msgid "Must set host and port"
+msgstr "Du mรฅste specifiera en host och port"
+
+#: ../app/ui.js:1062
+msgid "Password is required"
+msgstr "Lรถsenord krรคvs"
+
+#: ../app/ui.js:1275
+msgid ""
+"Forcing clipping mode since scrollbars aren't supported by IE in fullscreen"
+msgstr ""
+"Tvingar 'Clipping mode' eftersom skrollning inte stรถdjs av IE i fullskรคrm"
+
+#: ../core/rfb.js:556
+msgid "Disconnect timeout"
+msgstr "Det tog fรถr lรฅng tid att koppla ner"
+
+#: ../vnc.html:70
+msgid "noVNC encountered an error:"
+msgstr "noVNC stรถtte pรฅ ett problem:"
+
+#: ../vnc.html:78
+msgid "Hide/Show the control bar"
+msgstr "Gรถm/Visa kontrollbaren"
+
+#: ../vnc.html:85
+msgid "Move/Drag Viewport"
+msgstr "Flytta/Dra Vyn"
+
+#: ../vnc.html:85
+msgid "viewport drag"
+msgstr "dra vy"
+
+#: ../vnc.html:91 ../vnc.html:94 ../vnc.html:97 ../vnc.html:100
+msgid "Active Mouse Button"
+msgstr "Aktiv musknapp"
+
+#: ../vnc.html:91
+msgid "No mousebutton"
+msgstr "Ingen musknapp"
+
+#: ../vnc.html:94
+msgid "Left mousebutton"
+msgstr "Vรคnster musknapp"
+
+#: ../vnc.html:97
+msgid "Middle mousebutton"
+msgstr "Mitten-musknapp"
+
+#: ../vnc.html:100
+msgid "Right mousebutton"
+msgstr "Hรถger musknapp"
+
+#: ../vnc.html:103
+msgid "Keyboard"
+msgstr "Tangentbord"
+
+#: ../vnc.html:103
+msgid "Show Keyboard"
+msgstr "Visa Tangentbord"
+
+#: ../vnc.html:110
+msgid "Extra keys"
+msgstr "Extraknappar"
+
+#: ../vnc.html:110
+msgid "Show Extra Keys"
+msgstr "Visa Extraknappar"
+
+#: ../vnc.html:115
+msgid "Ctrl"
+msgstr "Ctrl"
+
+#: ../vnc.html:115
+msgid "Toggle Ctrl"
+msgstr "Vรคxla Ctrl"
+
+#: ../vnc.html:118
+msgid "Alt"
+msgstr "Alt"
+
+#: ../vnc.html:118
+msgid "Toggle Alt"
+msgstr "Vรคxla Alt"
+
+#: ../vnc.html:121
+msgid "Send Tab"
+msgstr "Skicka Tab"
+
+#: ../vnc.html:121
+msgid "Tab"
+msgstr "Tab"
+
+#: ../vnc.html:124
+msgid "Esc"
+msgstr "Esc"
+
+#: ../vnc.html:124
+msgid "Send Escape"
+msgstr "Skicka Escape"
+
+#: ../vnc.html:127
+msgid "Ctrl+Alt+Del"
+msgstr "Ctrl+Alt+Del"
+
+#: ../vnc.html:127
+msgid "Send Ctrl-Alt-Del"
+msgstr "Skicka Ctrl-Alt-Del"
+
+#: ../vnc.html:135
+msgid "Shutdown/Reboot"
+msgstr "Stรคng av/Boota om"
+
+#: ../vnc.html:135
+msgid "Shutdown/Reboot..."
+msgstr "Stรคng av/Boota om..."
+
+#: ../vnc.html:141
+msgid "Power"
+msgstr "Strรถm"
+
+#: ../vnc.html:143
+msgid "Shutdown"
+msgstr "Stรคng av"
+
+#: ../vnc.html:144
+msgid "Reboot"
+msgstr "Boota om"
+
+#: ../vnc.html:145
+msgid "Reset"
+msgstr "ร…terstรคll"
+
+#: ../vnc.html:150 ../vnc.html:156
+msgid "Clipboard"
+msgstr "Urklipp"
+
+#: ../vnc.html:160
+msgid "Clear"
+msgstr "Rensa"
+
+#: ../vnc.html:166
+msgid "Fullscreen"
+msgstr "Fullskรคrm"
+
+#: ../vnc.html:171 ../vnc.html:178
+msgid "Settings"
+msgstr "Instรคllningar"
+
+#: ../vnc.html:181
+msgid "Encrypt"
+msgstr "Kryptera"
+
+#: ../vnc.html:184
+msgid "True Color"
+msgstr "Fullfรคrg"
+
+#: ../vnc.html:187
+msgid "Local Cursor"
+msgstr "Lokal Muspekare"
+
+#: ../vnc.html:190
+msgid "Clip to Window"
+msgstr "Begrรคnsa till Fรถnster"
+
+#: ../vnc.html:193
+msgid "Shared Mode"
+msgstr "Delat Lรคge"
+
+#: ../vnc.html:196
+msgid "View Only"
+msgstr "Endast Visning"
+
+#: ../vnc.html:200
+msgid "Path:"
+msgstr "Sรถkvรคg:"
+
+#: ../vnc.html:204
+msgid "Scaling Mode:"
+msgstr "Skalningslรคge:"
+
+#: ../vnc.html:206
+msgid "None"
+msgstr "Ingen"
+
+#: ../vnc.html:207
+msgid "Local Scaling"
+msgstr "Lokal Skalning"
+
+#: ../vnc.html:208
+msgid "Local Downscaling"
+msgstr "Lokal Nedskalning"
+
+#: ../vnc.html:209
+msgid "Remote Resizing"
+msgstr "ร„ndra Storlek"
+
+#: ../vnc.html:213
+msgid "Repeater ID:"
+msgstr "Repeater-ID:"
+
+#: ../vnc.html:219
+msgid "Style:"
+msgstr "Stil:"
+
+#: ../vnc.html:221
+msgid "default"
+msgstr "standard"
+
+#: ../vnc.html:227
+msgid "Logging:"
+msgstr "Loggning:"
+
+#: ../vnc.html:234
+msgid "Apply"
+msgstr "Verkstรคll"
+
+#: ../vnc.html:241 ../vnc.html:271
+msgid "Connect"
+msgstr "Anslut"
+
+#: ../vnc.html:244
+msgid "Disconnect"
+msgstr "Koppla frรฅn"
+
+#: ../vnc.html:251
+msgid "Connection"
+msgstr "Uppkoppling"
+
+#: ../vnc.html:254
+msgid "Host:"
+msgstr "Vรคrd:"
+
+#: ../vnc.html:258
+msgid "Port:"
+msgstr "Port:"
+
+#: ../vnc.html:262 ../vnc.html:290
+msgid "Password:"
+msgstr "Lรถsenord:"
+
+#: ../vnc.html:266
+msgid "Token:"
+msgstr "Token:"
+
+#: ../vnc.html:294
+msgid "Send Password"
+msgstr "Skicka Lรถsenord"
+
+#: ../vnc.html:319
+msgid "Canvas not supported."
+msgstr "Canvas stรถds ej"
diff --git a/po/xgettext-html b/po/xgettext-html
new file mode 100755 (executable)
index 0000000..d71822c
--- /dev/null
@@ -0,0 +1,117 @@
+#!/usr/bin/env node
+/*
+ * xgettext-html: HTML gettext parser
+ * Copyright (C) 2016 Pierre Ossman
+ * Licensed under MPL 2.0 (see LICENSE.txt)
+ */
+
+var getopt = require('node-getopt');
+
+var jsdom = require("jsdom");
+var fs = require("fs");
+
+opt = getopt.create([
+    ['o' , 'output=FILE'      , 'write output to specified file'],
+    ['h' , 'help'             , 'display this help'],
+]).bindHelp().parseSystem();
+
+var strings = {};
+
+function addString(str, location) {
+    if (str.length == 0) {
+        return;
+    }
+
+    if (strings[str] === undefined) {
+        strings[str] = {}
+    }
+    strings[str][location] = null;
+}
+
+// See https://html.spec.whatwg.org/multipage/dom.html#attr-translate
+function process(elem, locator, enabled) {
+    function isAnyOf(searchElement, items) {
+        return items.indexOf(searchElement) !== -1;
+    }
+
+    if (elem.hasAttribute("translate")) {
+        if (isAnyOf(elem.getAttribute("translate"), ["", "yes"])) {
+            enabled = true;
+        } else if (isAnyOf(elem.getAttribute("translate"), ["no"])) {
+            enabled = false;
+        }
+    }
+
+    if (enabled) {
+        if (elem.hasAttribute("abbr") &&
+            elem.tagName === "TH") {
+            addString(elem.getAttribute("abbr"), locator(elem));
+        }
+        if (elem.hasAttribute("alt") &&
+            isAnyOf(elem.tagName, ["AREA", "IMG", "INPUT"])) {
+            addString(elem.getAttribute("alt"), locator(elem));
+        }
+        if (elem.hasAttribute("download") &&
+            isAnyOf(elem.tagName, ["A", "AREA"])) {
+            addString(elem.getAttribute("download"), locator(elem));
+        }
+        if (elem.hasAttribute("label") &&
+            isAnyOf(elem.tagName, ["MENUITEM", "MENU", "OPTGROUP",
+                                   "OPTION", "TRACK"])) {
+            addString(elem.getAttribute("label"), locator(elem));
+        }
+        if (elem.hasAttribute("placeholder") &&
+            isAnyOf(elem.tagName in ["INPUT", "TEXTAREA"])) {
+            addString(elem.getAttribute("placeholder"), locator(elem));
+        }
+        if (elem.hasAttribute("title")) {
+            addString(elem.getAttribute("title"), locator(elem));
+        }
+        if (elem.hasAttribute("value") &&
+            elem.tagName === "INPUT" &&
+            isAnyOf(elem.getAttribute("type"), ["reset", "button"])) {
+            addString(elem.getAttribute("value"), locator(elem));
+        }
+    }
+
+    for (var i = 0;i < elem.childNodes.length;i++) {
+        node = elem.childNodes[i];
+        if (node.nodeType === node.ELEMENT_NODE) {
+            process(node, locator, enabled);
+        } else if (node.nodeType === node.TEXT_NODE && enabled) {
+            addString(node.data.trim(), locator(node));
+        }
+    }
+}
+
+for (var i = 0;i < opt.argv.length;i++) {
+    var file;
+
+    fn = opt.argv[i];
+    file = fs.readFileSync(fn, "utf8");
+    doc = jsdom.jsdom(file);
+
+    locator = function (elem) {
+        offset = jsdom.nodeLocation(elem).start;
+        line = file.slice(0, offset).split("\n").length;
+        return fn + ":" + line;
+    };
+
+    process(doc.body, locator, true);
+}
+
+var output = "";
+
+for (str in strings) {
+    output += "#:";
+    for (location in strings[str]) {
+        output += " " + location;
+    }
+    output += "\n";
+
+    output += "msgid " + JSON.stringify(str) + "\n";
+    output += "msgstr \"\"\n";
+    output += "\n";
+}
+
+fs.writeFileSync(opt.options.output, output);
index 1a2b800a01f1e3264ec4b28b4cbda202fc3a4d76..a743d47a26e1db38c40cf87da2d8c316ef253020 100755 (executable)
@@ -99,7 +99,6 @@ if (program.autoInject) {
   template.header += "\n" + template.script_tag(get_path('node_modules/mocha/mocha.js'));
   template.header += "\n" + template.script_tag(get_path('node_modules/sinon/pkg/sinon.js'));
   template.header += "\n" + template.script_tag(get_path('node_modules/sinon-chai/lib/sinon-chai.js'));
-  template.header += "\n" + template.script_tag(get_path('node_modules/sinon-chai/lib/sinon-chai.js'));
   template.header += "\n<script>mocha.setup('bdd');</script>";
 
 
index 4982535c83dc078d639f906d4227f9f559407ea3..4a1c65f362f655a47bae52f30eac897adbc4c7f3 100644 (file)
@@ -38,9 +38,13 @@ describe('Helpers', function() {
         it('should map characters which aren\'t in Latin1 *or* Windows-1252 to keysyms', function() {
             expect(keysyms.fromUnicode('ลต'.charCodeAt())).to.have.property('keysym', 0x1000175);
         });
-        it('should return undefined for unknown codepoints', function() {
-            expect(keysyms.fromUnicode('\n'.charCodeAt())).to.be.undefined;
-            expect(keysyms.fromUnicode('\u1F686'.charCodeAt())).to.be.undefined;
+        it('should map unknown codepoints to the Unicode range', function() {
+            expect(keysyms.fromUnicode('\n'.charCodeAt())).to.have.property('keysym', 0x100000a);
+            expect(keysyms.fromUnicode('\u262D'.charCodeAt())).to.have.property('keysym', 0x100262d);
+        });
+        // This requires very recent versions of most browsers... skipping for now
+        it.skip('should map UCS-4 codepoints to the Unicode range', function() {
+            //expect(keysyms.fromUnicode('\u{1F686}'.codePointAt())).to.have.property('keysym', 0x101f686);
         });
     });
 
index c252c0922527585bdec84629ea9a0aa0543f6fa9..229cfe586bbf5622e2b9a5e74232ac8fa214b403 100644 (file)
@@ -330,7 +330,7 @@ describe('Remote Frame Buffer Protocol Client', function() {
             it('should clear the disconnect timer if the state is not "disconnecting"', function () {
                 var spy = sinon.spy();
                 client._disconnTimer = setTimeout(spy, 50);
-                client._updateConnectionState('connected');
+                client._updateConnectionState('connecting');
                 this.clock.tick(51);
                 expect(spy).to.not.have.been.called;
                 expect(client._disconnTimer).to.be.null;
@@ -338,27 +338,37 @@ describe('Remote Frame Buffer Protocol Client', function() {
 
             it('should call the updateState callback', function () {
                 client.set_onUpdateState(sinon.spy());
-                client._updateConnectionState('a specific state');
+                client._updateConnectionState('connecting');
                 var spy = client.get_onUpdateState();
                 expect(spy).to.have.been.calledOnce;
-                expect(spy.args[0][1]).to.equal('a specific state');
+                expect(spy.args[0][1]).to.equal('connecting');
             });
 
             it('should set the rfb_connection_state', function () {
-                client._updateConnectionState('a specific state');
-                expect(client._rfb_connection_state).to.equal('a specific state');
+                client._rfb_connection_state = 'disconnecting';
+                client._updateConnectionState('disconnected');
+                expect(client._rfb_connection_state).to.equal('disconnected');
             });
 
             it('should not change the state when we are disconnected', function () {
                 client._rfb_connection_state = 'disconnected';
-                client._updateConnectionState('a specific state');
-                expect(client._rfb_connection_state).to.not.equal('a specific state');
+                client._updateConnectionState('connecting');
+                expect(client._rfb_connection_state).to.not.equal('connecting');
             });
 
             it('should ignore state changes to the same state', function () {
                 client.set_onUpdateState(sinon.spy());
-                client._rfb_connection_state = 'a specific state';
-                client._updateConnectionState('a specific state');
+                client._rfb_connection_state = 'connecting';
+                client._updateConnectionState('connecting');
+                var spy = client.get_onUpdateState();
+                expect(spy).to.not.have.been.called;
+            });
+
+            it('should ignore illegal state changes', function () {
+                client.set_onUpdateState(sinon.spy());
+                client._rfb_connection_state = 'connected';
+                client._updateConnectionState('disconnected');
+                expect(client._rfb_connection_state).to.not.equal('disconnected');
                 var spy = client.get_onUpdateState();
                 expect(spy).to.not.have.been.called;
             });
@@ -391,11 +401,19 @@ describe('Remote Frame Buffer Protocol Client', function() {
             });
 
             it('should set disconnect_reason', function () {
+                client._rfb_connection_state = 'connected';
                 client._fail('a reason');
                 expect(client._rfb_disconnect_reason).to.equal('a reason');
             });
 
+            it('should not include details in disconnect_reason', function () {
+                client._rfb_connection_state = 'connected';
+                client._fail('a reason', 'details');
+                expect(client._rfb_disconnect_reason).to.equal('a reason');
+            });
+
             it('should result in disconnect callback with message when reason given', function () {
+                client._rfb_connection_state = 'connected';
                 client.set_onDisconnected(sinon.spy());
                 client._fail('a reason');
                 var spy = client.get_onDisconnected();
@@ -542,7 +560,7 @@ describe('Remote Frame Buffer Protocol Client', function() {
             it('should call the updateState callback before the disconnect callback', function () {
                 client.set_onDisconnected(sinon.spy());
                 client.set_onUpdateState(sinon.spy());
-                client._rfb_connection_state = 'other state';
+                client._rfb_connection_state = 'disconnecting';
                 client._updateConnectionState('disconnected');
                 var updateStateSpy = client.get_onUpdateState();
                 var disconnectSpy = client.get_onDisconnected();
@@ -717,7 +735,8 @@ describe('Remote Frame Buffer Protocol Client', function() {
                 client._sock._websocket._receive_data(failure_data);
 
                 expect(client._fail).to.have.been.calledOnce;
-                expect(client._fail).to.have.been.calledWith('Security failure: whoops');
+                expect(client._fail).to.have.been.calledWith(
+                    'Error while negotiating with server','Security failure: whoops');
             });
 
             it('should transition to the Authentication state and continue on successful negotiation', function () {
@@ -756,7 +775,8 @@ describe('Remote Frame Buffer Protocol Client', function() {
 
                 sinon.spy(client, '_fail');
                 client._sock._websocket._receive_data(new Uint8Array(data));
-                expect(client._fail).to.have.been.calledWith('Auth failure: Whoopsies');
+                expect(client._fail).to.have.been.calledWith(
+                    'Authentication failure', 'Whoopsies');
             });
 
             it('should transition straight to SecurityResult on "no auth" (1) for versions >= 3.8', function () {
@@ -988,7 +1008,8 @@ describe('Remote Frame Buffer Protocol Client', function() {
                 sinon.spy(client, '_fail');
                 var failure_data = [0, 0, 0, 1, 0, 0, 0, 6, 119, 104, 111, 111, 112, 115];
                 client._sock._websocket._receive_data(new Uint8Array(failure_data));
-                expect(client._fail).to.have.been.calledWith('whoops');
+                expect(client._fail).to.have.been.calledWith(
+                    'Authentication failure', 'whoops');
             });
 
             it('should fail on an error code of 1 with a standard message for version < 3.8', function () {
@@ -2062,10 +2083,18 @@ describe('Remote Frame Buffer Protocol Client', function() {
                 expect(client._rfb_connection_state).to.equal('disconnected');
             });
 
-            it('should transition to failed if we get a close event from any non-"disconnection" state', function () {
+            it('should fail if we get a close event while connecting', function () {
                 sinon.spy(client, "_fail");
                 client.connect('host', 8675);
-                client._rfb_connection_state = 'connected';
+                client._rfb_connection_state = 'connecting';
+                client._sock._websocket.close();
+                expect(client._fail).to.have.been.calledOnce;
+            });
+
+            it('should fail if we get a close event while disconnected', function () {
+                sinon.spy(client, "_fail");
+                client.connect('host', 8675);
+                client._rfb_connection_state = 'disconnected';
                 client._sock._websocket.close();
                 expect(client._fail).to.have.been.calledOnce;
             });
index c903c745ae8c57b806021d3763d4dca3f6e8c82e..60cdb5822b41fcdf307004c174754e0f6a373b5b 100644 (file)
@@ -56,6 +56,72 @@ describe('Utils', function() {
         });
     });
 
+    describe('language selection', function () {
+        var origNavigator;
+        beforeEach(function () {
+            // window.navigator is a protected read-only property in many
+            // environments, so we need to redefine it whilst running these
+            // tests.
+            origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
+            if (origNavigator === undefined) {
+                // Object.getOwnPropertyDescriptor() doesn't work
+                // properly in any version of IE
+                this.skip();
+            }
+
+            Object.defineProperty(window, "navigator", {value: {}});
+            if (window.navigator.languages !== undefined) {
+                // Object.defineProperty() doesn't work properly in old
+                // versions of Chrome
+                this.skip();
+            }
+
+            window.navigator.languages = [];
+        });
+        afterEach(function () {
+            Object.defineProperty(window, "navigator", origNavigator);
+        });
+
+        it('should use English by default', function() {
+            expect(Util.Localisation.language).to.equal('en');
+        });
+        it('should use English if no user language matches', function() {
+            window.navigator.languages = ["nl", "de"];
+            Util.Localisation.setup(["es", "fr"]);
+            expect(Util.Localisation.language).to.equal('en');
+        });
+        it('should use the most preferred user language', function() {
+            window.navigator.languages = ["nl", "de", "fr"];
+            Util.Localisation.setup(["es", "fr", "de"]);
+            expect(Util.Localisation.language).to.equal('de');
+        });
+        it('should prefer sub-languages languages', function() {
+            window.navigator.languages = ["pt-BR"];
+            Util.Localisation.setup(["pt", "pt-BR"]);
+            expect(Util.Localisation.language).to.equal('pt-BR');
+        });
+        it('should fall back to language "parents"', function() {
+            window.navigator.languages = ["pt-BR"];
+            Util.Localisation.setup(["fr", "pt", "de"]);
+            expect(Util.Localisation.language).to.equal('pt');
+        });
+        it('should not use specific language when user asks for a generic language', function() {
+            window.navigator.languages = ["pt", "de"];
+            Util.Localisation.setup(["fr", "pt-BR", "de"]);
+            expect(Util.Localisation.language).to.equal('de');
+        });
+        it('should handle underscore as a separator', function() {
+            window.navigator.languages = ["pt-BR"];
+            Util.Localisation.setup(["pt_BR"]);
+            expect(Util.Localisation.language).to.equal('pt_BR');
+        });
+        it('should handle difference in case', function() {
+            window.navigator.languages = ["pt-br"];
+            Util.Localisation.setup(["pt-BR"]);
+            expect(Util.Localisation.language).to.equal('pt-BR');
+        });
+    });
+
     // TODO(directxman12): test the conf_default and conf_defaults methods
     // TODO(directxman12): test decodeUTF8
     // TODO(directxman12): test the event methods (addEvent, removeEvent, stopEvent)
index 02ac66c203a0bd52cfbec0e89b4cb31c9b992555..fd79b12ba576095166deec457fb0c3fe93a18d78 100644 (file)
@@ -87,7 +87,13 @@ var out = "// This file describes mappings from Unicode codepoints to the keysym
 "\n" +
 "    function lookup(k) { return k ? {keysym: k, keyname: keynames ? keynames[k] : k} : undefined; }\n" +
 "    return {\n" +
-"        fromUnicode : function(u) { return lookup(codepoints[u]); },\n" +
+"        fromUnicode : function(u) {\n" +
+"            var keysym = codepoints[u];\n" +
+"            if (keysym === undefined) {\n" +
+"                keysym = 0x01000000 | u;\n" +
+"            }\n" +
+"            return lookup(keysym);\n" +
+"        },\n" +
 "        lookup : lookup\n" +
 "    };\n" +
 "})();\n";
index 6796e10f564044b37a7eb1d010987c481d9aca3b..e46fa62d563b7ca7307f05fa783b7c342b7ea244 100644 (file)
--- a/vnc.html
+++ b/vnc.html
 </head>
 
 <body>
+
+    <div id="noVNC_fallback_error">
+        <div>noVNC encountered an error:</div>
+        <br>
+        <div id="noVNC_fallback_errormsg"></div>
+    </div>
+
     <!-- noVNC Control Bar -->
     <div id="noVNC_control_bar_anchor" class="noVNC_vcenter">
 
@@ -73,7 +80,7 @@
 
             <div class="noVNC_scroll">
 
-            <h1 class="noVNC_logo"><span>no</span><br />VNC</h1>
+            <h1 class="noVNC_logo" translate="no"><span>no</span><br />VNC</h1>
 
             <!-- Drag/Pan the viewport -->
             <input type="image" alt="viewport drag" src="app/images/drag.svg"
     </div>
     </div>
 
+    <!-- Transition Screens -->
+    <div id="noVNC_transition">
+        <div id="noVNC_transition_text"></div>
+        <div class="noVNC_spinner"></div>
+    </div>
+
     <div id="noVNC_container">
-        <h1 id="noVNC_logo" class="noVNC_logo"><span>no</span><br />VNC</h1>
+        <h1 id="noVNC_logo" class="noVNC_logo" translate="no"><span>no</span><br />VNC</h1>
 
         <!-- HTML5 Canvas -->
         <div id="noVNC_screen">
index 48577f55c7e6324542af162192c61b326c8456e0..e86ae5d2fba1f7f69127af0d0b942f1427664d5d 100644 (file)
@@ -81,8 +81,7 @@
         WebUtil.load_scripts({
             'core': ["base64.js", "websock.js", "des.js", "input/keysymdef.js",
                      "input/xtscancodes.js", "input/util.js", "input/devices.js",
-                     "display.js", "inflator.js", "rfb.js", "input/keysym.js"],
-            'app': ["webutil.js"]});
+                     "display.js", "inflator.js", "rfb.js", "input/keysym.js"]});
 
         var rfb;
         var resizeTimeout;
         }
         function updateState(rfb, state, oldstate) {
             var cad = document.getElementById('sendCtrlAltDelButton');
-            var encrypt = (rfb && rfb.get_encrypt()) ? 'encrypted' : 'unencrypted';
             switch (state) {
                 case 'connecting':
                     status("Connecting", "normal");
                     break;
                 case 'connected':
-                    status("Connected (" + encrypt + ") to: " + desktopName, "normal");
+                    if (rfb && rfb.get_encrypt()) {
+                        status("Connected (encrypted) to " +
+                               desktopName, "normal");
+                    } else {
+                        status("Connected (unencrypted) to " +
+                               desktopName, "normal");
+                    }
                     break;
                 case 'disconnecting':
                     status("Disconnecting", "normal");