Full Code of novnc/noVNC for AI

master 8e1ebdffba02 cached
182 files
1.5 MB
435.5k tokens
849 symbols
1 requests
Download .txt
Showing preview only (1,585K chars total). Download the full file or copy to clipboard to get everything.
Repository: novnc/noVNC
Branch: master
Commit: 8e1ebdffba02
Files: 182
Total size: 1.5 MB

Directory structure:
gitextract_tymwg30v/

├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.md
│   │   ├── config.yml
│   │   └── feature_request.md
│   └── workflows/
│       ├── deploy.yml
│       ├── lint.yml
│       ├── test.yml
│       └── translate.yml
├── .gitignore
├── .gitmodules
├── AUTHORS
├── LICENSE.txt
├── README.md
├── app/
│   ├── error-handler.js
│   ├── images/
│   │   └── icons/
│   │       └── Makefile
│   ├── locale/
│   │   ├── README
│   │   ├── cs.json
│   │   ├── de.json
│   │   ├── el.json
│   │   ├── es.json
│   │   ├── fr.json
│   │   ├── hu.json
│   │   ├── it.json
│   │   ├── ja.json
│   │   ├── ko.json
│   │   ├── nl.json
│   │   ├── pl.json
│   │   ├── pt_BR.json
│   │   ├── ru.json
│   │   ├── sv.json
│   │   ├── tr.json
│   │   ├── uk.json
│   │   ├── zh_CN.json
│   │   └── zh_TW.json
│   ├── localization.js
│   ├── sounds/
│   │   ├── CREDITS
│   │   └── bell.oga
│   ├── styles/
│   │   ├── base.css
│   │   ├── constants.css
│   │   └── input.css
│   ├── ui.js
│   ├── wakelock.js
│   └── webutil.js
├── core/
│   ├── base64.js
│   ├── clipboard.js
│   ├── crypto/
│   │   ├── aes.js
│   │   ├── bigint.js
│   │   ├── crypto.js
│   │   ├── des.js
│   │   ├── dh.js
│   │   ├── md5.js
│   │   └── rsa.js
│   ├── decoders/
│   │   ├── copyrect.js
│   │   ├── h264.js
│   │   ├── hextile.js
│   │   ├── jpeg.js
│   │   ├── raw.js
│   │   ├── rre.js
│   │   ├── tight.js
│   │   ├── tightpng.js
│   │   ├── zlib.js
│   │   └── zrle.js
│   ├── deflator.js
│   ├── display.js
│   ├── encodings.js
│   ├── inflator.js
│   ├── input/
│   │   ├── domkeytable.js
│   │   ├── fixedkeys.js
│   │   ├── gesturehandler.js
│   │   ├── keyboard.js
│   │   ├── keysym.js
│   │   ├── keysymdef.js
│   │   ├── util.js
│   │   ├── vkeys.js
│   │   └── xtscancodes.js
│   ├── ra2.js
│   ├── rfb.js
│   ├── util/
│   │   ├── browser.js
│   │   ├── cursor.js
│   │   ├── element.js
│   │   ├── events.js
│   │   ├── eventtarget.js
│   │   ├── int.js
│   │   ├── logging.js
│   │   └── strings.js
│   └── websock.js
├── defaults.json
├── docs/
│   ├── API-internal.md
│   ├── API.md
│   ├── EMBEDDING.md
│   ├── LIBRARY.md
│   ├── LICENSE.BSD-2-Clause
│   ├── LICENSE.BSD-3-Clause
│   ├── LICENSE.MPL-2.0
│   ├── LICENSE.OFL-1.1
│   ├── flash_policy.txt
│   ├── links
│   ├── notes
│   ├── novnc_proxy.1
│   └── rfb_notes
├── eslint.config.mjs
├── karma.conf.cjs
├── mandatory.json
├── package.json
├── po/
│   ├── Makefile
│   ├── cs.po
│   ├── de.po
│   ├── el.po
│   ├── es.po
│   ├── fr.po
│   ├── hr.po
│   ├── hu.po
│   ├── it.po
│   ├── ja.po
│   ├── ko.po
│   ├── nl.po
│   ├── noVNC.pot
│   ├── pl.po
│   ├── po2js
│   ├── pt_BR.po
│   ├── ru.po
│   ├── sv.po
│   ├── tr.po
│   ├── uk.po
│   ├── xgettext-html
│   ├── zh_CN.po
│   └── zh_TW.po
├── snap/
│   ├── hooks/
│   │   └── configure
│   ├── local/
│   │   └── svc_wrapper.sh
│   └── snapcraft.yaml
├── tests/
│   ├── assertions.js
│   ├── fake.websocket.js
│   ├── playback-ui.js
│   ├── playback.js
│   ├── test.base64.js
│   ├── test.browser.js
│   ├── test.clipboard.js
│   ├── test.copyrect.js
│   ├── test.deflator.js
│   ├── test.display.js
│   ├── test.gesturehandler.js
│   ├── test.h264.js
│   ├── test.helper.js
│   ├── test.hextile.js
│   ├── test.inflator.js
│   ├── test.int.js
│   ├── test.jpeg.js
│   ├── test.keyboard.js
│   ├── test.localization.js
│   ├── test.raw.js
│   ├── test.rfb.js
│   ├── test.rre.js
│   ├── test.tight.js
│   ├── test.tightpng.js
│   ├── test.util.js
│   ├── test.wakelock.js
│   ├── test.websock.js
│   ├── test.webutil.js
│   ├── test.zlib.js
│   ├── test.zrle.js
│   └── vnc_playback.html
├── utils/
│   ├── README.md
│   ├── b64-to-binary.pl
│   ├── genkeysymdef.js
│   ├── novnc_proxy
│   ├── u2x11
│   └── validate
├── vendor/
│   └── pako/
│       ├── LICENSE
│       ├── README.md
│       └── lib/
│           ├── utils/
│           │   └── common.js
│           └── zlib/
│               ├── adler32.js
│               ├── constants.js
│               ├── crc32.js
│               ├── deflate.js
│               ├── gzheader.js
│               ├── inffast.js
│               ├── inflate.js
│               ├── inftrees.js
│               ├── messages.js
│               ├── trees.js
│               └── zstream.js
├── vnc.html
└── vnc_lite.html

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve

---

**Describe the bug**
A clear and concise description of what the bug is.

**To reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error

**Expected behavior**
A clear and concise description of what you expected to happen.

**Screenshots**
If applicable, add screenshots to help explain your problem.

**Client (please complete the following information):**
 - OS: [e.g. iOS]
 - Browser: [e.g. chrome, safari]
 - Browser version: [e.g. 22]

**Server (please complete the following information):**
 - noVNC version: [e.g. 1.0.0 or git commit id]
 - VNC server: [e.g. QEMU, TigerVNC]
 - WebSocket proxy: [e.g. websockify]

**Additional context**
Add any other context about the problem here.


================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false
contact_links:
  - name: Question or discussion
    url: https://groups.google.com/forum/?fromgroups#!forum/novnc
    about: Ask a question or start a discussion


================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest an idea for this project

---

**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

**Describe the solution you'd like**
A clear and concise description of what you want to happen.

**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.

**Additional context**
Add any other context or screenshots about the feature request here.


================================================
FILE: .github/workflows/deploy.yml
================================================
name: Publish

on:
  push:
  pull_request:
  release:
    types: [published]

jobs:
  npm:
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read
    steps:
      - uses: actions/checkout@v4
      - run: |
          GITREV=$(git rev-parse --short HEAD)
          echo $GITREV
          sed -i "s/^\(.*\"version\".*\)\"\([^\"]\+\)\"\(.*\)\$/\1\"\2-g$GITREV\"\3/" package.json
        if: github.event_name != 'release'
      - uses: actions/setup-node@v4
        with:
          # Node 24 is needed to get npm > 11.5.1, which is a requirement for
          # OIDC auth.
          node-version: 24
          # Needs to be explicitly specified for auth to work
          registry-url: 'https://registry.npmjs.org'
      - run: npm install
      - uses: actions/upload-artifact@v4
        with:
          name: npm
          path: lib
      - run: npm publish --access public
        if: |
          github.repository == 'novnc/noVNC' &&
          github.event_name == 'release' &&
          !github.event.release.prerelease
      - run: npm publish --access public --tag beta
        if: |
          github.repository == 'novnc/noVNC' &&
          github.event_name == 'release' &&
          github.event.release.prerelease
      - run: npm publish --access public --tag dev
        if: |
          github.repository == 'novnc/noVNC' &&
          github.event_name == 'push' &&
          github.event.ref == 'refs/heads/master'
  snap:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: |
          GITREV=$(git rev-parse --short HEAD)
          echo $GITREV
          sed -i "s/^\(.*\"version\".*\)\"\([^\"]\+\)\"\(.*\)\$/\1\"\2-g$GITREV\"\3/" package.json
        if: github.event_name != 'release'
      - run: |
          VERSION=$(grep '"version"' package.json | cut -d '"' -f 4)
          echo $VERSION
          sed -i "s/^version:.*/version: '$VERSION'/" snap/snapcraft.yaml
      - uses: snapcore/action-build@v1
        id: snapcraft
      - uses: actions/upload-artifact@v4
        with:
          name: snap
          path: ${{ steps.snapcraft.outputs.snap }}
      - uses: snapcore/action-publish@v1
        with:
          snap: ${{ steps.snapcraft.outputs.snap }}
          release: stable
        env:
          SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_LOGIN }}
        if: |
          github.repository == 'novnc/noVNC' &&
          github.event_name == 'release' &&
          !github.event.release.prerelease
      - uses: snapcore/action-publish@v1
        with:
          snap: ${{ steps.snapcraft.outputs.snap }}
          release: beta
        env:
          SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_LOGIN }}
        if: |
          github.repository == 'novnc/noVNC' &&
          github.event_name == 'release' &&
          github.event.release.prerelease
      - uses: snapcore/action-publish@v1
        with:
          snap: ${{ steps.snapcraft.outputs.snap }}
          release: edge
        env:
          SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_LOGIN }}
        if: |
          github.repository == 'novnc/noVNC' &&
          github.event_name == 'push' &&
          github.event.ref == 'refs/heads/master'


================================================
FILE: .github/workflows/lint.yml
================================================
name: Lint

on: [push, pull_request]

jobs:
  eslint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
      - run: npm update
      - run: npm run lint
  html:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
      - run: npm update
      - run: git ls-tree --name-only -r HEAD | grep -E "[.](html|css)$" | xargs ./utils/validate


================================================
FILE: .github/workflows/test.yml
================================================
name: Test

on: [push, pull_request]

jobs:
  test:
    strategy:
      matrix:
        os:
          - ubuntu-latest
          - windows-latest
        browser:
          - ChromeHeadless
          - FirefoxHeadless
        include:
          - os: macos-latest
            browser: Safari
          - os: windows-latest
            browser: EdgeHeadless
      fail-fast: false
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
      - run: npm update
      - run: npm run test
        env:
          TEST_BROWSER_NAME: ${{ matrix.browser }}


================================================
FILE: .github/workflows/translate.yml
================================================
name: Translate

on: [push, pull_request]

jobs:
  translate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
      - run: npm update
      - run: sudo apt-get install gettext
      - run: make -C po update-pot
      - run: make -C po update-po
      - run: make -C po update-js


================================================
FILE: .gitignore
================================================
*.pyc
*.o
tests/data_*.js
utils/rebind.so
utils/websockify
/node_modules
/build
/lib
recordings
*.swp
*~
noVNC-*.tgz


================================================
FILE: .gitmodules
================================================


================================================
FILE: AUTHORS
================================================
maintainers:
- Samuel Mannehed for Cendio AB (@samhed)
- Pierre Ossman for Cendio AB (@CendioOssman)
maintainersEmeritus:
- Joel Martin (@kanaka)
- Solly Ross (@directxman12)
- @astrand 
contributors:
# There are a bunch of people that should be here.
# If you want to be on this list, feel free send a PR
# to add yourself.
- jalf <git@jalf.dk>
- NTT corp.


================================================
FILE: LICENSE.txt
================================================
noVNC is Copyright (C) 2022 The noVNC authors
(./AUTHORS)

The noVNC core library files are licensed under the MPL 2.0 (Mozilla
Public License 2.0). The noVNC core library is composed of the
Javascript code necessary for full noVNC operation. This includes (but
is not limited to):

    core/**/*.js
    app/*.js
    test/playback.js

The HTML, CSS, font and images files that included with the noVNC
source distibution (or repository) are not considered part of the
noVNC core library and are licensed under more permissive licenses.
The intent is to allow easy integration of noVNC into existing web
sites and web applications.

The HTML, CSS, font and image files are licensed as follows:

    *.html                     : 2-Clause BSD license

    app/styles/*.css           : 2-Clause BSD license

    app/styles/Orbitron*       : SIL Open Font License 1.1
                                 (Copyright 2009 Matt McInerney)

    app/images/                : Creative Commons Attribution-ShareAlike
                                 http://creativecommons.org/licenses/by-sa/3.0/

Some portions of noVNC are copyright to their individual authors.
Please refer to the individual source files and/or to the noVNC commit
history: https://github.com/novnc/noVNC/commits/master

The are several files and projects that have been incorporated into
the noVNC core library. Here is a list of those files and the original
licenses (all MPL 2.0 compatible):

    core/base64.js          : MPL 2.0

    core/des.js             : Various BSD style licenses

    vendor/pako/            : MIT

Any other files not mentioned above are typically marked with
a copyright/license header at the top of the file. The default noVNC
license is MPL-2.0.

The following license texts are included:

    docs/LICENSE.MPL-2.0
    docs/LICENSE.OFL-1.1
    docs/LICENSE.BSD-3-Clause (New BSD)
    docs/LICENSE.BSD-2-Clause (Simplified BSD / FreeBSD)
    vendor/pako/LICENSE (MIT)

Or alternatively the license texts may be found here:

    http://www.mozilla.org/MPL/2.0/
    http://scripts.sil.org/OFL
    http://en.wikipedia.org/wiki/BSD_licenses
    https://opensource.org/licenses/MIT


================================================
FILE: README.md
================================================
## noVNC: HTML VNC client library and application

[![Test Status](https://github.com/novnc/noVNC/workflows/Test/badge.svg)](https://github.com/novnc/noVNC/actions?query=workflow%3ATest)
[![Lint Status](https://github.com/novnc/noVNC/workflows/Lint/badge.svg)](https://github.com/novnc/noVNC/actions?query=workflow%3ALint)

### Description

noVNC is both a HTML VNC client JavaScript library and an application built on
top of that library. noVNC runs well in any modern browser including mobile
browsers (iOS and Android).

Many companies, projects and products have integrated noVNC including
[OpenStack](http://www.openstack.org),
[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/novnc/noVNC/wiki/Projects-and-companies-using-noVNC)
for a more complete list with additional info and links.

### Table of contents

- [News/help/contact](#newshelpcontact)
- [Features](#features)
- [Screenshots](#screenshots)
- [Browser requirements](#browser-requirements)
- [Server requirements](#server-requirements)
- [Quick start](#quick-start)
- [Installation from snap package](#installation-from-snap-package)
- [Integration and deployment](#integration-and-deployment)
- [Authors/Contributors](#authorscontributors)

### News/help/contact

The project website is found at [novnc.com](http://novnc.com).

If you are a noVNC developer/integrator/user (or want to be) please join the
[noVNC discussion group](https://groups.google.com/forum/?fromgroups#!forum/novnc).

Bugs and feature requests can be submitted via
[github issues](https://github.com/novnc/noVNC/issues). If you have questions
about using noVNC then please first use the
[discussion group](https://groups.google.com/forum/?fromgroups#!forum/novnc).
We also have a [wiki](https://github.com/novnc/noVNC/wiki/) with lots of
helpful information.

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/novnc/noVNC/issues?labels=patchwelcome).
Please check our
[contribution guide](https://github.com/novnc/noVNC/wiki/Contributing) though.

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.


### Features

* Supports all modern browsers including mobile (iOS, Android)
* Supported authentication methods: none, classical VNC, RealVNC's
  RSA-AES, Tight, VeNCrypt Plain, XVP, Apple's Diffie-Hellman,
  UltraVNC's MSLogonII
* Supported VNC encodings: raw, copyrect, rre, hextile, tight, tightPNG,
  ZRLE, JPEG, Zlib, H.264
* Supports scaling, clipping and resizing the desktop
* Supports back & forward mouse buttons
* Local cursor rendering
* Clipboard copy/paste with full Unicode support
* Translations
* Touch gestures for emulating common mouse actions
* Licensed mainly under the [MPL 2.0](http://www.mozilla.org/MPL/2.0/), see
  [the license document](LICENSE.txt) for details

### Screenshots

Running in Firefox before and after connecting:

<img src="http://novnc.com/img/noVNC-1-login.png" width=400>&nbsp;
<img src="http://novnc.com/img/noVNC-3-connected.png" width=400>

See more screenshots
[here](http://novnc.com/screenshots.html).


### Browser requirements

noVNC uses many modern web technologies so a formal requirement list is
not available. However these are the minimum versions we are currently
aware of:

* Chrome 89, Firefox 89, Safari 15, Opera 75, Edge 89


### Server requirements

noVNC follows the standard VNC protocol, but unlike other VNC clients it does
require WebSockets support. Many servers include support (e.g.
[x11vnc/libvncserver](http://libvncserver.sourceforge.net/),
[QEMU](http://www.qemu.org/), and
[MobileVNC](http://www.smartlab.at/mobilevnc/)), but for the others you need to
use a WebSockets to TCP socket proxy. noVNC has a sister project
[websockify](https://github.com/novnc/websockify) that provides a simple such
proxy.


### Quick start

* Use the `novnc_proxy` script to automatically download and start websockify, which
  includes a mini-webserver and the WebSockets proxy. The `--vnc` option is
  used to specify the location of a running VNC server:

    `./utils/novnc_proxy --vnc localhost:5901`
    
* If you don't need to expose the web server to public internet, you can
  bind to localhost:
  
    `./utils/novnc_proxy --vnc localhost:5901 --listen localhost:6081`

* Point your browser to the cut-and-paste URL that is output by the `novnc_proxy`
  script. Hit the Connect button, enter a password if the VNC server has one
  configured, and enjoy!

### Installation from snap package
Running the command below will install the latest release of noVNC from snap:

`sudo snap install novnc`

#### Running noVNC from snap directly

You can run the snap package installed novnc directly with, for example:

`novnc --listen 6081 --vnc localhost:5901 # /snap/bin/novnc if /snap/bin is not in your PATH`

If you want to use certificate files, due to standard snap confinement restrictions you need to have them in the /home/\<user\>/snap/novnc/current/ directory. If your username is jsmith an example command would be:
  
  `novnc --listen 8443 --cert ~jsmith/snap/novnc/current/self.crt --key ~jsmith/snap/novnc/current/self.key --vnc ubuntu.example.com:5901`

#### Running noVNC from snap as a service (daemon)
The snap package also has the capability to run a 'novnc' service which can be
configured to listen on multiple ports connecting to multiple VNC servers 
(effectively a service running multiple instances of novnc).
Instructions (with example values):

List current services (out-of-box this will be blank):

```
sudo snap get novnc services
Key             Value
services.n6080  {...}
services.n6081  {...}
```

Create a new service that listens on port 6082 and connects to the VNC server 
running on port 5902 on localhost:

`sudo snap set novnc services.n6082.listen=6082 services.n6082.vnc=localhost:5902`

(Any services you define with 'snap set' will be automatically started)
Note that the name of the service, 'n6082' in this example, can be anything 
as long as it doesn't start with a number or contain spaces/special characters.

View the configuration of the service just created:

```
sudo snap get novnc services.n6082
Key                    Value
services.n6082.listen  6082
services.n6082.vnc     localhost:5902
```

Disable a service (note that because of a limitation in snap it's currently not
possible to unset config variables, setting them to blank values is the way 
to disable a service):

`sudo snap set novnc services.n6082.listen='' services.n6082.vnc=''`

(Any services you set to blank with 'snap set' like this will be automatically stopped)

Verify that the service is disabled (blank values):

```
sudo snap get novnc services.n6082
Key                    Value
services.n6082.listen  
services.n6082.vnc
```

### Integration and deployment

Please see our other documents for how to integrate noVNC in your own software,
or deploying the noVNC application in production environments:

* [Embedding](docs/EMBEDDING.md) - For the noVNC application
* [Library](docs/LIBRARY.md) - For the noVNC JavaScript library


### Authors/Contributors

See [AUTHORS](AUTHORS) for a (full-ish) list of authors.  If you're not on
that list and you think you should be, feel free to send a PR to fix that.

* Core team:
    * [Samuel Mannehed](https://github.com/samhed) (Cendio)
    * [Pierre Ossman](https://github.com/CendioOssman) (Cendio)

* Previous core contributors:
    * [Joel Martin](https://github.com/kanaka) (Project founder)
    * [Solly Ross](https://github.com/DirectXMan12) (Red Hat / OpenStack)

* Notable contributions:
    * UI and icons : Pierre Ossman, Chris Gordon
    * Original logo : Michael Sersen
    * tight encoding : Michael Tinglof (Mercuri.ca)
    * RealVNC RSA AES authentication : USTC Vlab Team

* Included libraries:
    * 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)

Do you want to be on this list? Check out our
[contribution guide](https://github.com/novnc/noVNC/wiki/Contributing) and
start hacking!


================================================
FILE: app/error-handler.js
================================================
/*
 * noVNC: HTML5 VNC client
 * Copyright (C) 2019 The noVNC authors
 * Licensed under MPL 2.0 (see LICENSE.txt)
 *
 * See README.md for usage and integration instructions.
 */

// Fallback for all uncaught errors
function handleError(event, err) {
    try {
        const msg = document.getElementById('noVNC_fallback_errormsg');

        // Work around Firefox bug:
        // https://bugzilla.mozilla.org/show_bug.cgi?id=1685038
        if (event.message === "ResizeObserver loop completed with undelivered notifications.") {
            return false;
        }

        // Only show the initial error
        if (msg.hasChildNodes()) {
            return false;
        }

        let div = document.createElement("div");
        div.classList.add('noVNC_message');
        div.appendChild(document.createTextNode(event.message));
        msg.appendChild(div);

        if (event.filename) {
            div = document.createElement("div");
            div.className = 'noVNC_location';
            let text = event.filename;
            if (event.lineno !== undefined) {
                text += ":" + event.lineno;
                if (event.colno !== undefined) {
                    text += ":" + event.colno;
                }
            }
            div.appendChild(document.createTextNode(text));
            msg.appendChild(div);
        }

        if (err && err.stack) {
            div = document.createElement("div");
            div.className = 'noVNC_stack';
            div.appendChild(document.createTextNode(err.stack));
            msg.appendChild(div);
        }

        document.getElementById('noVNC_fallback_error')
            .classList.add("noVNC_open");

    } catch (exc) {
        document.write("noVNC encountered an error.");
    }

    // Try to disable keyboard interaction, best effort
    try {
        // Remove focus from the currently focused element in order to
        // prevent keyboard interaction from continuing
        if (document.activeElement) { document.activeElement.blur(); }

        // Don't let any element be focusable when showing the error
        let keyboardFocusable = 'a[href], button, input, textarea, select, details, [tabindex]';
        document.querySelectorAll(keyboardFocusable).forEach((elem) => {
            elem.setAttribute("tabindex", "-1");
        });
    } catch (exc) {
        // Do nothing
    }

    // Don't return true since this would prevent the error
    // from being printed to the browser console.
    return false;
}

window.addEventListener('error', evt => handleError(evt, evt.error));
window.addEventListener('unhandledrejection', evt => handleError(evt.reason, evt.reason));


================================================
FILE: app/images/icons/Makefile
================================================
BROWSER_SIZES := 16 24 32 48 64
#ANDROID_SIZES := 72 96 144 192
# FIXME: The ICO is limited to 8 icons due to a Chrome bug:
#        https://bugs.chromium.org/p/chromium/issues/detail?id=1381393
ANDROID_SIZES := 96 144 192
WEB_ICON_SIZES := $(BROWSER_SIZES) $(ANDROID_SIZES)

#IOS_1X_SIZES := 20 29 40 76 # No such devices exist anymore
IOS_2X_SIZES := 40 58 80 120 152 167
IOS_3X_SIZES := 60 87 120 180
ALL_IOS_SIZES := $(IOS_1X_SIZES) $(IOS_2X_SIZES) $(IOS_3X_SIZES)

ALL_ICONS := \
	$(ALL_IOS_SIZES:%=novnc-ios-%.png) \
	novnc.ico

all: $(ALL_ICONS)

# Our testing shows that the ICO file need to be sorted in largest to
# smallest to get the apporpriate behviour
WEB_ICON_SIZES_REVERSE := $(shell echo $(WEB_ICON_SIZES) | tr ' ' '\n' | sort -nr | tr '\n' ' ')
WEB_BASE_ICONS := $(WEB_ICON_SIZES_REVERSE:%=novnc-%.png)
.INTERMEDIATE: $(WEB_BASE_ICONS)

novnc.ico: $(WEB_BASE_ICONS)
	convert $(WEB_BASE_ICONS) "$@"

# General conversion
novnc-%.png: novnc-icon.svg
	convert -depth 8 -background transparent \
		-size $*x$* "$(lastword $^)" "$@"

# iOS icons use their own SVG
novnc-ios-%.png: novnc-ios-icon.svg
	convert -depth 8 -background transparent \
		-size $*x$* "$(lastword $^)" "$@"

# The smallest sizes are generated using a different SVG
novnc-16.png novnc-24.png novnc-32.png: novnc-icon-sm.svg

clean:
	rm -f *.png


================================================
FILE: app/locale/README
================================================
DO NOT MODIFY THE FILES IN THIS FOLDER, THEY ARE AUTOMATICALLY GENERATED FROM THE PO-FILES.


================================================
FILE: app/locale/cs.json
================================================
{
    "Connecting...": "Připojení...",
    "Disconnecting...": "Odpojení...",
    "Reconnecting...": "Obnova připojení...",
    "Internal error": "Vnitřní chyba",
    "Must set host": "Hostitel musí být nastavení",
    "Connected (encrypted) to ": "Připojení (šifrované) k ",
    "Connected (unencrypted) to ": "Připojení (nešifrované) k ",
    "Something went wrong, connection is closed": "Něco se pokazilo, odpojeno",
    "Failed to connect to server": "Chyba připojení k serveru",
    "Disconnected": "Odpojeno",
    "New connection has been rejected with reason: ": "Nové připojení bylo odmítnuto s odůvodněním: ",
    "New connection has been rejected": "Nové připojení bylo odmítnuto",
    "Password is required": "Je vyžadováno heslo",
    "noVNC encountered an error:": "noVNC narazilo na chybu:",
    "Hide/Show the control bar": "Skrýt/zobrazit ovládací panel",
    "Move/Drag viewport": "Přesunout/přetáhnout výřez",
    "viewport drag": "přesun výřezu",
    "Active Mouse Button": "Aktivní tlačítka myši",
    "No mousebutton": "Žádné",
    "Left mousebutton": "Levé tlačítko myši",
    "Middle mousebutton": "Prostřední tlačítko myši",
    "Right mousebutton": "Pravé tlačítko myši",
    "Keyboard": "Klávesnice",
    "Show keyboard": "Zobrazit klávesnici",
    "Extra keys": "Extra klávesy",
    "Show extra keys": "Zobrazit extra klávesy",
    "Ctrl": "Ctrl",
    "Toggle Ctrl": "Přepnout Ctrl",
    "Alt": "Alt",
    "Toggle Alt": "Přepnout Alt",
    "Send Tab": "Odeslat tabulátor",
    "Tab": "Tab",
    "Esc": "Esc",
    "Send Escape": "Odeslat Esc",
    "Ctrl+Alt+Del": "Ctrl+Alt+Del",
    "Send Ctrl-Alt-Del": "Poslat Ctrl-Alt-Del",
    "Shutdown/Reboot": "Vypnutí/Restart",
    "Shutdown/Reboot...": "Vypnutí/Restart...",
    "Power": "Napájení",
    "Shutdown": "Vypnout",
    "Reboot": "Restart",
    "Reset": "Reset",
    "Clipboard": "Schránka",
    "Clear": "Vymazat",
    "Fullscreen": "Celá obrazovka",
    "Settings": "Nastavení",
    "Shared mode": "Sdílený režim",
    "View only": "Pouze prohlížení",
    "Clip to window": "Přizpůsobit oknu",
    "Scaling mode:": "Přizpůsobení velikosti",
    "None": "Žádné",
    "Local scaling": "Místní",
    "Remote resizing": "Vzdálené",
    "Advanced": "Pokročilé",
    "Repeater ID:": "ID opakovače",
    "WebSocket": "WebSocket",
    "Encrypt": "Šifrování:",
    "Host:": "Hostitel:",
    "Port:": "Port:",
    "Path:": "Cesta",
    "Automatic reconnect": "Automatická obnova připojení",
    "Reconnect delay (ms):": "Zpoždění připojení (ms)",
    "Show dot when no cursor": "Tečka místo chybějícího kurzoru myši",
    "Logging:": "Logování:",
    "Disconnect": "Odpojit",
    "Connect": "Připojit",
    "Password:": "Heslo",
    "Send Password": "Odeslat heslo",
    "Cancel": "Zrušit"
}

================================================
FILE: app/locale/de.json
================================================
{
    "Connecting...": "Verbinden...",
    "Disconnecting...": "Verbindung trennen...",
    "Reconnecting...": "Verbindung wiederherstellen...",
    "Internal error": "Interner Fehler",
    "Must set host": "Richten Sie den Server ein",
    "Connected (encrypted) to ": "Verbunden mit (verschlüsselt) ",
    "Connected (unencrypted) to ": "Verbunden mit (unverschlüsselt) ",
    "Something went wrong, connection is closed": "Etwas lief schief, Verbindung wurde getrennt",
    "Disconnected": "Verbindung zum Server getrennt",
    "New connection has been rejected with reason: ": "Verbindung wurde aus folgendem Grund abgelehnt: ",
    "New connection has been rejected": "Verbindung wurde abgelehnt",
    "Password is required": "Passwort ist erforderlich",
    "noVNC encountered an error:": "Ein Fehler ist aufgetreten:",
    "Hide/Show the control bar": "Kontrollleiste verstecken/anzeigen",
    "Move/Drag viewport": "Ansichtsfenster verschieben/ziehen",
    "viewport drag": "Ansichtsfenster ziehen",
    "Active Mouse Button": "Aktive Maustaste",
    "No mousebutton": "Keine Maustaste",
    "Left mousebutton": "Linke Maustaste",
    "Middle mousebutton": "Mittlere Maustaste",
    "Right mousebutton": "Rechte Maustaste",
    "Keyboard": "Tastatur",
    "Show keyboard": "Tastatur anzeigen",
    "Extra keys": "Zusatztasten",
    "Show extra keys": "Zusatztasten anzeigen",
    "Ctrl": "Strg",
    "Toggle Ctrl": "Strg umschalten",
    "Alt": "Alt",
    "Toggle Alt": "Alt umschalten",
    "Send Tab": "Tab senden",
    "Tab": "Tab",
    "Esc": "Esc",
    "Send Escape": "Escape senden",
    "Ctrl+Alt+Del": "Strg+Alt+Entf",
    "Send Ctrl-Alt-Del": "Strg+Alt+Entf senden",
    "Shutdown/Reboot": "Herunterfahren/Neustarten",
    "Shutdown/Reboot...": "Herunterfahren/Neustarten...",
    "Power": "Energie",
    "Shutdown": "Herunterfahren",
    "Reboot": "Neustarten",
    "Reset": "Zurücksetzen",
    "Clipboard": "Zwischenablage",
    "Clear": "Löschen",
    "Fullscreen": "Vollbild",
    "Settings": "Einstellungen",
    "Shared mode": "Geteilter Modus",
    "View only": "Nur betrachten",
    "Clip to window": "Auf Fenster begrenzen",
    "Scaling mode:": "Skalierungsmodus:",
    "None": "Keiner",
    "Local scaling": "Lokales skalieren",
    "Remote resizing": "Serverseitiges skalieren",
    "Advanced": "Erweitert",
    "Repeater ID:": "Repeater ID:",
    "WebSocket": "WebSocket",
    "Encrypt": "Verschlüsselt",
    "Host:": "Server:",
    "Port:": "Port:",
    "Path:": "Pfad:",
    "Automatic reconnect": "Automatisch wiederverbinden",
    "Reconnect delay (ms):": "Wiederverbindungsverzögerung (ms):",
    "Logging:": "Protokollierung:",
    "Disconnect": "Verbindung trennen",
    "Connect": "Verbinden",
    "Password:": "Passwort:",
    "Cancel": "Abbrechen",
    "Canvas not supported.": "Canvas nicht unterstützt.",
    "Disconnect timeout": "Zeitüberschreitung beim Trennen",
    "Local Downscaling": "Lokales herunterskalieren",
    "Local Cursor": "Lokaler Mauszeiger",
    "Forcing clipping mode since scrollbars aren't supported by IE in fullscreen": "'Clipping-Modus' aktiviert, Scrollbalken in 'IE-Vollbildmodus' werden nicht unterstützt",
    "True Color": "True Color"
}

================================================
FILE: app/locale/el.json
================================================
{
    "HTTPS is required for full functionality": "Το HTTPS είναι απαιτούμενο για πλήρη λειτουργικότητα",
    "Connecting...": "Συνδέεται...",
    "Disconnecting...": "Aποσυνδέεται...",
    "Reconnecting...": "Επανασυνδέεται...",
    "Internal error": "Εσωτερικό σφάλμα",
    "Must set host": "Πρέπει να οριστεί ο διακομιστής",
    "Connected (encrypted) to ": "Συνδέθηκε (κρυπτογραφημένα) με το ",
    "Connected (unencrypted) to ": "Συνδέθηκε (μη κρυπτογραφημένα) με το ",
    "Something went wrong, connection is closed": "Κάτι πήγε στραβά, η σύνδεση διακόπηκε",
    "Failed to connect to server": "Αποτυχία στη σύνδεση με το διακομιστή",
    "Disconnected": "Αποσυνδέθηκε",
    "New connection has been rejected with reason: ": "Η νέα σύνδεση απορρίφθηκε διότι: ",
    "New connection has been rejected": "Η νέα σύνδεση απορρίφθηκε ",
    "Credentials are required": "Απαιτούνται διαπιστευτήρια",
    "noVNC encountered an error:": "το noVNC αντιμετώπισε ένα σφάλμα:",
    "Hide/Show the control bar": "Απόκρυψη/Εμφάνιση γραμμής ελέγχου",
    "Drag": "Σύρσιμο",
    "Move/Drag Viewport": "Μετακίνηση/Σύρσιμο Θεατού πεδίου",
    "Keyboard": "Πληκτρολόγιο",
    "Show Keyboard": "Εμφάνιση Πληκτρολογίου",
    "Extra keys": "Επιπλέον πλήκτρα",
    "Show Extra Keys": "Εμφάνιση Επιπλέον Πλήκτρων",
    "Ctrl": "Ctrl",
    "Toggle Ctrl": "Εναλλαγή Ctrl",
    "Alt": "Alt",
    "Toggle Alt": "Εναλλαγή Alt",
    "Toggle Windows": "Εναλλαγή Παράθυρων",
    "Windows": "Παράθυρα",
    "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": "Πρόχειρο",
    "Edit clipboard content in the textarea below.": "Επεξεργαστείτε το περιεχόμενο του πρόχειρου στην περιοχή κειμένου παρακάτω.",
    "Full Screen": "Πλήρης Οθόνη",
    "Settings": "Ρυθμίσεις",
    "Shared Mode": "Κοινόχρηστη Λειτουργία",
    "View Only": "Μόνο Θέαση",
    "Clip to Window": "Αποκοπή στο όριο του Παράθυρου",
    "Scaling Mode:": "Λειτουργία Κλιμάκωσης:",
    "None": "Καμία",
    "Local Scaling": "Τοπική Κλιμάκωση",
    "Remote Resizing": "Απομακρυσμένη Αλλαγή μεγέθους",
    "Advanced": "Για προχωρημένους",
    "Quality:": "Ποιότητα:",
    "Compression level:": "Επίπεδο συμπίεσης:",
    "Repeater ID:": "Repeater ID:",
    "WebSocket": "WebSocket",
    "Encrypt": "Κρυπτογράφηση",
    "Host:": "Όνομα διακομιστή:",
    "Port:": "Πόρτα διακομιστή:",
    "Path:": "Διαδρομή:",
    "Automatic Reconnect": "Αυτόματη επανασύνδεση",
    "Reconnect Delay (ms):": "Καθυστέρηση επανασύνδεσης (ms):",
    "Show Dot when No Cursor": "Εμφάνιση Τελείας όταν δεν υπάρχει Δρομέας",
    "Logging:": "Καταγραφή:",
    "Version:": "Έκδοση:",
    "Disconnect": "Αποσύνδεση",
    "Connect": "Σύνδεση",
    "Server identity": "Ταυτότητα Διακομιστή",
    "The server has provided the following identifying information:": "Ο διακομιστής παρείχε την ακόλουθη πληροφορία ταυτοποίησης:",
    "Fingerprint:": "Δακτυλικό αποτύπωμα:",
    "Please verify that the information is correct and press \"Approve\". Otherwise press \"Reject\".": "Παρακαλώ επαληθεύσετε ότι η πληροφορία είναι σωστή και πιέστε \"Αποδοχή\". Αλλιώς πιέστε \"Απόρριψη\".",
    "Approve": "Αποδοχή",
    "Reject": "Απόρριψη",
    "Credentials": "Διαπιστευτήρια",
    "Username:": "Κωδικός Χρήστη:",
    "Password:": "Κωδικός Πρόσβασης:",
    "Send Credentials": "Αποστολή Διαπιστευτηρίων",
    "Cancel": "Ακύρωση",
    "Password is required": "Απαιτείται ο κωδικός πρόσβασης",
    "viewport drag": "σύρσιμο θεατού πεδίου",
    "Active Mouse Button": "Ενεργό Πλήκτρο Ποντικιού",
    "No mousebutton": "Χωρίς Πλήκτρο Ποντικιού",
    "Left mousebutton": "Αριστερό Πλήκτρο Ποντικιού",
    "Middle mousebutton": "Μεσαίο Πλήκτρο Ποντικιού",
    "Right mousebutton": "Δεξί Πλήκτρο Ποντικιού",
    "Clear": "Καθάρισμα",
    "Canvas not supported.": "Δεν υποστηρίζεται το στοιχείο Canvas",
    "Disconnect timeout": "Παρέλευση χρονικού ορίου αποσύνδεσης",
    "Local Downscaling": "Τοπική Συρρίκνωση",
    "Local Cursor": "Τοπικός Δρομέας",
    "Forcing clipping mode since scrollbars aren't supported by IE in fullscreen": "Εφαρμογή λειτουργίας αποκοπής αφού δεν υποστηρίζονται οι λωρίδες κύλισης σε πλήρη οθόνη στον IE",
    "True Color": "Πραγματικά Χρώματα",
    "Style:": "Στυλ:",
    "default": "προεπιλεγμένο",
    "Apply": "Εφαρμογή",
    "Connection": "Σύνδεση",
    "Token:": "Διακριτικό:",
    "Send Password": "Αποστολή Κωδικού Πρόσβασης"
}

================================================
FILE: app/locale/es.json
================================================
{
    "Connecting...": "Conectando...",
    "Connected (encrypted) to ": "Conectado (con encriptación) a",
    "Connected (unencrypted) to ": "Conectado (sin encriptación) a",
    "Disconnecting...": "Desconectando...",
    "Disconnected": "Desconectado",
    "Must set host": "Se debe configurar el host",
    "Reconnecting...": "Reconectando...",
    "Password is required": "La contraseña es obligatoria",
    "Disconnect timeout": "Tiempo de desconexión agotado",
    "noVNC encountered an error:": "noVNC ha encontrado un error:",
    "Hide/Show the control bar": "Ocultar/Mostrar la barra de control",
    "Move/Drag viewport": "Mover/Arrastrar la ventana",
    "viewport drag": "Arrastrar la ventana",
    "Active Mouse Button": "Botón activo del ratón",
    "No mousebutton": "Ningún botón del ratón",
    "Left mousebutton": "Botón izquierdo del ratón",
    "Middle mousebutton": "Botón central del ratón",
    "Right mousebutton": "Botón derecho del ratón",
    "Keyboard": "Teclado",
    "Show keyboard": "Mostrar teclado",
    "Extra keys": "Teclas adicionales",
    "Show Extra Keys": "Mostrar Teclas Adicionales",
    "Ctrl": "Ctrl",
    "Toggle Ctrl": "Pulsar/Soltar Ctrl",
    "Alt": "Alt",
    "Toggle Alt": "Pulsar/Soltar Alt",
    "Send Tab": "Enviar Tabulación",
    "Tab": "Tabulación",
    "Esc": "Esc",
    "Send Escape": "Enviar Escape",
    "Ctrl+Alt+Del": "Ctrl+Alt+Del",
    "Send Ctrl-Alt-Del": "Enviar Ctrl+Alt+Del",
    "Shutdown/Reboot": "Apagar/Reiniciar",
    "Shutdown/Reboot...": "Apagar/Reiniciar...",
    "Power": "Encender",
    "Shutdown": "Apagar",
    "Reboot": "Reiniciar",
    "Reset": "Restablecer",
    "Clipboard": "Portapapeles",
    "Clear": "Vaciar",
    "Fullscreen": "Pantalla Completa",
    "Settings": "Configuraciones",
    "Encrypt": "Encriptar",
    "Shared Mode": "Modo Compartido",
    "View only": "Solo visualización",
    "Clip to window": "Recortar al tamaño de la ventana",
    "Scaling mode:": "Modo de escalado:",
    "None": "Ninguno",
    "Local Scaling": "Escalado Local",
    "Local Downscaling": "Reducción de escala local",
    "Remote resizing": "Cambio de tamaño remoto",
    "Advanced": "Avanzado",
    "Local Cursor": "Cursor Local",
    "Repeater ID:": "ID del Repetidor:",
    "WebSocket": "WebSocket",
    "Host:": "Host:",
    "Port:": "Puerto:",
    "Path:": "Ruta:",
    "Automatic reconnect": "Reconexión automática",
    "Reconnect delay (ms):": "Retraso en la reconexión (ms):",
    "Logging:": "Registrando:",
    "Disconnect": "Desconectar",
    "Connect": "Conectar",
    "Password:": "Contraseña:",
    "Cancel": "Cancelar",
    "Canvas not supported.": "Canvas no soportado."
}

================================================
FILE: app/locale/fr.json
================================================
{
    "Running without HTTPS is not recommended, crashes or other issues are likely.": "Lancer sans HTTPS n'est pas recommandé, crashs ou autres problèmes en vue.",
    "Connecting...": "En cours de connexion...",
    "Disconnecting...": "Déconnexion en cours...",
    "Reconnecting...": "Reconnexion en cours...",
    "Internal error": "Erreur interne",
    "Failed to connect to server: ": "Échec de connexion au serveur ",
    "Connected (encrypted) to ": "Connecté (chiffré) à ",
    "Connected (unencrypted) to ": "Connecté (non chiffré) à ",
    "Something went wrong, connection is closed": "Quelque chose s'est mal passé, la connexion a été fermée",
    "Failed to connect to server": "Échec de connexion au serveur",
    "Disconnected": "Déconnecté",
    "New connection has been rejected with reason: ": "Une nouvelle connexion a été rejetée avec motif : ",
    "New connection has been rejected": "Une nouvelle connexion a été rejetée",
    "Credentials are required": "Les identifiants sont requis",
    "noVNC encountered an error:": "noVNC a rencontré une erreur :",
    "Hide/Show the control bar": "Masquer/Afficher la barre de contrôle",
    "Drag": "Faire glisser",
    "Move/Drag viewport": "Déplacer la fenêtre de visualisation",
    "Keyboard": "Clavier",
    "Show keyboard": "Afficher le clavier",
    "Extra keys": "Touches supplémentaires",
    "Show extra keys": "Afficher les touches supplémentaires",
    "Ctrl": "Ctrl",
    "Toggle Ctrl": "Basculer Ctrl",
    "Alt": "Alt",
    "Toggle Alt": "Basculer Alt",
    "Toggle Windows": "Basculer Windows",
    "Windows": "Fenêtre",
    "Send Tab": "Envoyer Tab",
    "Tab": "Tabulation",
    "Esc": "Esc",
    "Send Escape": "Envoyer Escape",
    "Ctrl+Alt+Del": "Ctrl+Alt+Del",
    "Send Ctrl-Alt-Del": "Envoyer Ctrl-Alt-Del",
    "Shutdown/Reboot": "Arrêter/Redémarrer",
    "Shutdown/Reboot...": "Arrêter/Redémarrer...",
    "Power": "Alimentation",
    "Shutdown": "Arrêter",
    "Reboot": "Redémarrer",
    "Reset": "Réinitialiser",
    "Clipboard": "Presse-papiers",
    "Edit clipboard content in the textarea below.": "Editer le contenu du presse-papier dans la zone ci-dessous.",
    "Full screen": "Plein écran",
    "Settings": "Paramètres",
    "Shared mode": "Mode partagé",
    "View only": "Afficher uniquement",
    "Clip to window": "Ajuster à la fenêtre",
    "Scaling mode:": "Mode mise à l'échelle :",
    "None": "Aucun",
    "Local scaling": "Mise à l'échelle locale",
    "Remote resizing": "Redimensionnement à distance",
    "Advanced": "Avancé",
    "Quality:": "Qualité :",
    "Compression level:": "Niveau de compression :",
    "Repeater ID:": "ID Répéteur :",
    "WebSocket": "WebSocket",
    "Encrypt": "Chiffrer",
    "Host:": "Hôte :",
    "Port:": "Port :",
    "Path:": "Chemin :",
    "Automatic reconnect": "Reconnecter automatiquement",
    "Reconnect delay (ms):": "Délai de reconnexion (ms) :",
    "Show dot when no cursor": "Afficher le point lorsqu'il n'y a pas de curseur",
    "Logging:": "Se connecter :",
    "Version:": "Version :",
    "Disconnect": "Déconnecter",
    "Connect": "Connecter",
    "Server identity": "Identité du serveur",
    "The server has provided the following identifying information:": "Le serveur a fourni l'identification suivante :",
    "Fingerprint:": "Empreinte digitale :",
    "Please verify that the information is correct and press \"Approve\". Otherwise press \"Reject\".": "SVP, verifiez que l'information est correcte et pressez \"Accepter\". Sinon pressez \"Refuser\".",
    "Approve": "Accepter",
    "Reject": "Refuser",
    "Credentials": "Envoyer les identifiants",
    "Username:": "Nom d'utilisateur :",
    "Password:": "Mot de passe :",
    "Send credentials": "Envoyer les identifiants",
    "Cancel": "Annuler",
    "Must set host": "Doit définir l'hôte",
    "Clear": "Effacer"
}

================================================
FILE: app/locale/hu.json
================================================
{
    "Running without HTTPS is not recommended, crashes or other issues are likely.": "HTTPS nélkül futtatni nem ajánlott, összeomlások vagy más problémák várhatók.",
    "Connecting...": "Kapcsolódás...",
    "Disconnecting...": "Kapcsolat bontása...",
    "Reconnecting...": "Újrakapcsolódás...",
    "Internal error": "Belső hiba",
    "Failed to connect to server: ": "Nem sikerült csatlakozni a szerverhez: ",
    "Connected (encrypted) to ": "Kapcsolódva (titkosítva) ehhez: ",
    "Connected (unencrypted) to ": "Kapcsolódva (titkosítatlanul) ehhez: ",
    "Something went wrong, connection is closed": "Valami hiba történt, a kapcsolat lezárult",
    "Failed to connect to server": "Nem sikerült csatlakozni a szerverhez",
    "Disconnected": "Kapcsolat bontva",
    "New connection has been rejected with reason: ": "Az új kapcsolat elutasítva, indok: ",
    "New connection has been rejected": "Az új kapcsolat elutasítva",
    "Credentials are required": "Hitelesítő adatok szükségesek",
    "noVNC encountered an error:": "A noVNC hibát észlelt:",
    "Hide/Show the control bar": "Vezérlősáv elrejtése/megjelenítése",
    "Drag": "Húzás",
    "Move/Drag viewport": "Nézet mozgatása/húzása",
    "Keyboard": "Billentyűzet",
    "Show keyboard": "Billentyűzet megjelenítése",
    "Extra keys": "Extra billentyűk",
    "Show extra keys": "Extra billentyűk megjelenítése",
    "Ctrl": "Ctrl",
    "Toggle Ctrl": "Ctrl lenyomása/felengedése",
    "Alt": "Alt",
    "Toggle Alt": "Alt lenyomása/felengedése",
    "Toggle Windows": "Windows lenyomása/felengedése",
    "Windows": "Windows",
    "Send Tab": "Tab küldése",
    "Tab": "Tab",
    "Esc": "Esc",
    "Send Escape": "Escape küldése",
    "Ctrl+Alt+Del": "Ctrl+Alt+Del",
    "Send Ctrl-Alt-Del": "Ctrl-Alt-Del küldése",
    "Shutdown/Reboot": "Leállítás/Újraindítás",
    "Shutdown/Reboot...": "Leállítás/Újraindítás...",
    "Power": "Bekapcsolás",
    "Shutdown": "Leállítás",
    "Reboot": "Újraindítás",
    "Reset": "Reset",
    "Clipboard": "Vágólap",
    "Edit clipboard content in the textarea below.": "Itt tudod módosítani a vágólap tartalmát.",
    "Full screen": "Teljes képernyő",
    "Settings": "Beállítások",
    "Shared mode": "Megosztott mód",
    "View only": "Csak megtekintés",
    "Clip to window": "Ablakhoz igazítás",
    "Scaling mode:": "Méretezési mód:",
    "None": "Nincs",
    "Local scaling": "Helyi méretezés",
    "Remote resizing": "Távoli átméretezés",
    "Advanced": "Speciális",
    "Quality:": "Minőség:",
    "Compression level:": "Tömörítési szint:",
    "Repeater ID:": "Ismétlő azonosító:",
    "WebSocket": "WebSocket",
    "Encrypt": "Titkosítás",
    "Host:": "Hoszt:",
    "Port:": "Port:",
    "Path:": "Útvonal:",
    "Automatic reconnect": "Automatikus újracsatlakozás",
    "Reconnect delay (ms):": "Újracsatlakozás késleltetése (ms):",
    "Show dot when no cursor": "Kurzor hiányában pont mutatása",
    "Logging:": "Naplózás:",
    "Version:": "Verzió:",
    "Disconnect": "Kapcsolat bontása",
    "Connect": "Csatlakozás",
    "Server identity": "Szerver azonosító",
    "The server has provided the following identifying information:": "A szerver a következő azonosító információt adta meg:",
    "Fingerprint:": "Ujjlenyomat:",
    "Please verify that the information is correct and press \"Approve\". Otherwise press \"Reject\".": "Ellenőrizze, hogy az információ helyes-e és nyomja meg a \"Jóváhagyás\" gombot. Ellenkező esetben nyomja meg az \"Elutasítás\" gombot.",
    "Approve": "Jóváhagyás",
    "Reject": "Elutasítás",
    "Credentials": "Hitelesítő adatok",
    "Username:": "Felhasználónév:",
    "Password:": "Jelszó:",
    "Send credentials": "Hitelesítő adatok küldése",
    "Cancel": "Mégse"
}

================================================
FILE: app/locale/it.json
================================================
{
    "Connecting...": "Connessione in corso...",
    "Disconnecting...": "Disconnessione...",
    "Reconnecting...": "Riconnessione...",
    "Internal error": "Errore interno",
    "Must set host": "Devi impostare l'host",
    "Connected (encrypted) to ": "Connesso (crittografato) a ",
    "Connected (unencrypted) to ": "Connesso (non crittografato) a",
    "Something went wrong, connection is closed": "Qualcosa è andato storto, la connessione è stata chiusa",
    "Failed to connect to server": "Impossibile connettersi al server",
    "Disconnected": "Disconnesso",
    "New connection has been rejected with reason: ": "La nuova connessione è stata rifiutata con motivo: ",
    "New connection has been rejected": "La nuova connessione è stata rifiutata",
    "Credentials are required": "Le credenziali sono obbligatorie",
    "noVNC encountered an error:": "noVNC ha riscontrato un errore:",
    "Hide/Show the control bar": "Nascondi/Mostra la barra di controllo",
    "Keyboard": "Tastiera",
    "Show keyboard": "Mostra tastiera",
    "Extra keys": "Tasti Aggiuntivi",
    "Show Extra Keys": "Mostra Tasti Aggiuntivi",
    "Ctrl": "Ctrl",
    "Toggle Ctrl": "Tieni premuto Ctrl",
    "Alt": "Alt",
    "Toggle Alt": "Tieni premuto Alt",
    "Toggle Windows": "Tieni premuto Windows",
    "Windows": "Windows",
    "Send Tab": "Invia Tab",
    "Tab": "Tab",
    "Esc": "Esc",
    "Send Escape": "Invia Esc",
    "Ctrl+Alt+Del": "Ctrl+Alt+Canc",
    "Send Ctrl-Alt-Del": "Invia Ctrl-Alt-Canc",
    "Shutdown/Reboot": "Spegnimento/Riavvio",
    "Shutdown/Reboot...": "Spegnimento/Riavvio...",
    "Power": "Alimentazione",
    "Shutdown": "Spegnimento",
    "Reboot": "Riavvio",
    "Reset": "Reset",
    "Clipboard": "Clipboard",
    "Clear": "Pulisci",
    "Fullscreen": "Schermo intero",
    "Settings": "Impostazioni",
    "Shared mode": "Modalità condivisa",
    "View Only": "Sola Visualizzazione",
    "Scaling mode:": "Modalità di ridimensionamento:",
    "None": "Nessuna",
    "Local Scaling": "Ridimensionamento Locale",
    "Remote Resizing": "Ridimensionamento Remoto",
    "Advanced": "Avanzate",
    "Quality:": "Qualità:",
    "Compression level:": "Livello Compressione:",
    "Repeater ID:": "ID Ripetitore:",
    "WebSocket": "WebSocket",
    "Encrypt": "Crittografa",
    "Host:": "Host:",
    "Port:": "Porta:",
    "Path:": "Percorso:",
    "Automatic Reconnect": "Riconnessione Automatica",
    "Reconnect Delay (ms):": "Ritardo Riconnessione (ms):",
    "Show Dot when No Cursor": "Mostra Punto quando Nessun Cursore",
    "Version:": "Versione:",
    "Disconnect": "Disconnetti",
    "Connect": "Connetti",
    "Username:": "Utente:",
    "Password:": "Password:",
    "Send Credentials": "Invia Credenziale",
    "Cancel": "Annulla"
}

================================================
FILE: app/locale/ja.json
================================================
{
    "Running without HTTPS is not recommended, crashes or other issues are likely.": "HTTPS接続なしで実行することは推奨されません。クラッシュしたりその他の問題が発生したりする可能性があります。",
    "Connecting...": "接続しています...",
    "Disconnecting...": "切断しています...",
    "Reconnecting...": "再接続しています...",
    "Internal error": "内部エラー",
    "Must set host": "ホストを設定する必要があります",
    "Failed to connect to server: ": "サーバーへの接続に失敗しました: ",
    "Connected (encrypted) to ": "接続しました (暗号化済み): ",
    "Connected (unencrypted) to ": "接続しました (暗号化されていません): ",
    "Something went wrong, connection is closed": "問題が発生したため、接続が閉じられました",
    "Failed to connect to server": "サーバーへの接続に失敗しました",
    "Disconnected": "切断しました",
    "New connection has been rejected with reason: ": "新規接続は次の理由で拒否されました: ",
    "New connection has been rejected": "新規接続は拒否されました",
    "Credentials are required": "資格情報が必要です",
    "noVNC encountered an error:": "noVNC でエラーが発生しました:",
    "Hide/Show the control bar": "コントロールバーを隠す/表示する",
    "Drag": "ドラッグ",
    "Move/Drag viewport": "ビューポートを移動/ドラッグ",
    "Keyboard": "キーボード",
    "Show keyboard": "キーボードを表示",
    "Extra keys": "追加キー",
    "Show extra keys": "追加キーを表示",
    "Ctrl": "Ctrl",
    "Toggle Ctrl": "Ctrl キーをトグル",
    "Alt": "Alt",
    "Toggle Alt": "Alt キーをトグル",
    "Toggle Windows": "Windows キーをトグル",
    "Windows": "Windows",
    "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": "クリップボード",
    "Edit clipboard content in the textarea below.": "以下の入力欄からクリップボードの内容を編集できます。",
    "Full screen": "全画面表示",
    "Settings": "設定",
    "Shared mode": "共有モード",
    "View only": "表示専用",
    "Clip to window": "ウィンドウにクリップ",
    "Scaling mode:": "スケーリングモード:",
    "None": "なし",
    "Local scaling": "ローカルでスケーリング",
    "Remote resizing": "リモートでリサイズ",
    "Advanced": "高度",
    "Quality:": "品質:",
    "Compression level:": "圧縮レベル:",
    "Repeater ID:": "リピーター ID:",
    "WebSocket": "WebSocket",
    "Encrypt": "暗号化",
    "Host:": "ホスト:",
    "Port:": "ポート:",
    "Path:": "パス:",
    "Automatic reconnect": "自動再接続",
    "Reconnect delay (ms):": "再接続する遅延 (ミリ秒):",
    "Show dot when no cursor": "カーソルがないときにドットを表示する",
    "Logging:": "ロギング:",
    "Version:": "バージョン:",
    "Disconnect": "切断",
    "Connect": "接続",
    "Server identity": "サーバーの識別情報",
    "The server has provided the following identifying information:": "サーバーは以下の識別情報を提供しています:",
    "Fingerprint:": "フィンガープリント:",
    "Please verify that the information is correct and press \"Approve\". Otherwise press \"Reject\".": "この情報が正しい場合は「承認」を、そうでない場合は「拒否」を押してください。",
    "Approve": "承認",
    "Reject": "拒否",
    "Credentials": "資格情報",
    "Username:": "ユーザー名:",
    "Password:": "パスワード:",
    "Send credentials": "資格情報を送信",
    "Cancel": "キャンセル"
}

================================================
FILE: app/locale/ko.json
================================================
{
    "Connecting...": "연결중...",
    "Disconnecting...": "연결 해제중...",
    "Reconnecting...": "재연결중...",
    "Internal error": "내부 오류",
    "Must set host": "호스트는 설정되어야 합니다.",
    "Connected (encrypted) to ": "다음과 (암호화되어) 연결되었습니다:",
    "Connected (unencrypted) to ": "다음과 (암호화 없이) 연결되었습니다:",
    "Something went wrong, connection is closed": "무언가 잘못되었습니다, 연결이 닫혔습니다.",
    "Failed to connect to server": "서버에 연결하지 못했습니다.",
    "Disconnected": "연결이 해제되었습니다.",
    "New connection has been rejected with reason: ": "새 연결이 다음 이유로 거부되었습니다:",
    "New connection has been rejected": "새 연결이 거부되었습니다.",
    "Password is required": "비밀번호가 필요합니다.",
    "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": "Esc 보내기",
    "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": "설정",
    "Shared mode": "공유 모드",
    "View only": "보기 전용",
    "Clip to window": "창에 클립",
    "Scaling mode:": "스케일링 모드:",
    "None": "없음",
    "Local scaling": "로컬 스케일링",
    "Remote resizing": "원격 크기 조절",
    "Advanced": "고급",
    "Repeater ID:": "중계 ID",
    "WebSocket": "웹소켓",
    "Encrypt": "암호화",
    "Host:": "호스트:",
    "Port:": "포트:",
    "Path:": "위치:",
    "Automatic reconnect": "자동 재연결",
    "Reconnect delay (ms):": "재연결 지연 시간 (ms)",
    "Logging:": "로깅",
    "Disconnect": "연결 해제",
    "Connect": "연결",
    "Password:": "비밀번호:",
    "Send Password": "비밀번호 전송",
    "Cancel": "취소"
}

================================================
FILE: app/locale/nl.json
================================================
{
    "Running without HTTPS is not recommended, crashes or other issues are likely.": "Het is niet aan te raden om zonder HTTPS te werken, crashes of andere problemen zijn dan waarschijnlijk.",
    "Connecting...": "Aan het verbinden…",
    "Disconnecting...": "Bezig om verbinding te verbreken...",
    "Reconnecting...": "Opnieuw verbinding maken...",
    "Internal error": "Interne fout",
    "Failed to connect to server: ": "Verbinding maken met server is mislukt",
    "Connected (encrypted) to ": "Verbonden (versleuteld) met ",
    "Connected (unencrypted) to ": "Verbonden (onversleuteld) met ",
    "Something went wrong, connection is closed": "Er iets fout gelopen, verbinding werd verbroken",
    "Failed to connect to server": "Verbinding maken met server is mislukt",
    "Disconnected": "Verbinding verbroken",
    "New connection has been rejected with reason: ": "Nieuwe verbinding is geweigerd met de volgende reden: ",
    "New connection has been rejected": "Nieuwe verbinding is geweigerd",
    "Credentials are required": "Inloggegevens zijn nodig",
    "noVNC encountered an error:": "noVNC heeft een fout bemerkt:",
    "Hide/Show the control bar": "Verberg/Toon de bedieningsbalk",
    "Drag": "Sleep",
    "Move/Drag viewport": "Verplaats/Versleep Kijkvenster",
    "Keyboard": "Toetsenbord",
    "Show keyboard": "Toon Toetsenbord",
    "Extra keys": "Extra toetsen",
    "Show extra keys": "Toon Extra Toetsen",
    "Ctrl": "Ctrl",
    "Toggle Ctrl": "Ctrl omschakelen",
    "Alt": "Alt",
    "Toggle Alt": "Alt omschakelen",
    "Toggle Windows": "Vensters omschakelen",
    "Windows": "Vensters",
    "Send Tab": "Tab Sturen",
    "Tab": "Tab",
    "Esc": "Esc",
    "Send Escape": "Escape Sturen",
    "Ctrl+Alt+Del": "Ctrl-Alt-Del",
    "Send Ctrl-Alt-Del": "Ctrl-Alt-Del Sturen",
    "Shutdown/Reboot": "Uitschakelen/Herstarten",
    "Shutdown/Reboot...": "Uitschakelen/Herstarten...",
    "Power": "Systeem",
    "Shutdown": "Uitschakelen",
    "Reboot": "Herstarten",
    "Reset": "Resetten",
    "Clipboard": "Klembord",
    "Edit clipboard content in the textarea below.": "Edit de inhoud van het klembord in het tekstveld hieronder",
    "Full screen": "Volledig Scherm",
    "Settings": "Instellingen",
    "Shared mode": "Gedeelde Modus",
    "View only": "Alleen Kijken",
    "Clip to window": "Randen buiten venster afsnijden",
    "Scaling mode:": "Schaalmodus:",
    "None": "Geen",
    "Local scaling": "Lokaal Schalen",
    "Remote resizing": "Op Afstand Formaat Wijzigen",
    "Advanced": "Geavanceerd",
    "Quality:": "Kwaliteit:",
    "Compression level:": "Compressieniveau:",
    "Repeater ID:": "Repeater ID:",
    "WebSocket": "WebSocket",
    "Encrypt": "Versleutelen",
    "Host:": "Host:",
    "Port:": "Poort:",
    "Path:": "Pad:",
    "Automatic reconnect": "Automatisch Opnieuw Verbinden",
    "Reconnect delay (ms):": "Vertraging voor Opnieuw Verbinden (ms):",
    "Show dot when no cursor": "Geef stip weer indien geen cursor",
    "Logging:": "Logmeldingen:",
    "Version:": "Versie:",
    "Disconnect": "Verbinding verbreken",
    "Connect": "Verbinden",
    "Server identity": "Serveridentiteit",
    "The server has provided the following identifying information:": "De server geeft de volgende identificerende informatie:",
    "Fingerprint:": "Vingerafdruk:",
    "Please verify that the information is correct and press \"Approve\". Otherwise press \"Reject\".": "Verifieer dat de informatie is correct en druk “OK”. Druk anders op “Afwijzen”.",
    "Approve": "OK",
    "Reject": "Afwijzen",
    "Credentials": "Inloggegevens",
    "Username:": "Gebruikersnaam:",
    "Password:": "Wachtwoord:",
    "Send credentials": "Stuur inloggegevens",
    "Cancel": "Annuleren",
    "Must set host": "Host moeten worden ingesteld",
    "Password is required": "Wachtwoord is vereist",
    "viewport drag": "kijkvenster slepen",
    "Active Mouse Button": "Actieve Muisknop",
    "No mousebutton": "Geen muisknop",
    "Left mousebutton": "Linker muisknop",
    "Middle mousebutton": "Middelste muisknop",
    "Right mousebutton": "Rechter muisknop",
    "Clear": "Wissen",
    "Send Password": "Verzend Wachtwoord:",
    "Disconnect timeout": "Timeout tijdens verbreken van verbinding",
    "Local Downscaling": "Lokaal Neerschalen",
    "Local Cursor": "Lokale Cursor",
    "Canvas not supported.": "Canvas wordt niet ondersteund.",
    "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"
}

================================================
FILE: app/locale/pl.json
================================================
{
    "Connecting...": "Łączenie...",
    "Disconnecting...": "Rozłączanie...",
    "Reconnecting...": "Łączenie...",
    "Internal error": "Błąd wewnętrzny",
    "Must set host": "Host i port są wymagane",
    "Connected (encrypted) to ": "Połączenie (szyfrowane) z ",
    "Connected (unencrypted) to ": "Połączenie (nieszyfrowane) z ",
    "Something went wrong, connection is closed": "Coś poszło źle, połączenie zostało zamknięte",
    "Disconnected": "Rozłączony",
    "New connection has been rejected with reason: ": "Nowe połączenie zostało odrzucone z powodu: ",
    "New connection has been rejected": "Nowe połączenie zostało odrzucone",
    "Password is required": "Hasło jest wymagane",
    "noVNC encountered an error:": "noVNC napotkało błąd:",
    "Hide/Show the control bar": "Pokaż/Ukryj pasek ustawień",
    "Move/Drag Viewport": "Ruszaj/Przeciągaj Viewport",
    "viewport drag": "przeciągnij viewport",
    "Active Mouse Button": "Aktywny Przycisk Myszy",
    "No mousebutton": "Brak przycisku myszy",
    "Left mousebutton": "Lewy przycisk myszy",
    "Middle mousebutton": "Środkowy przycisk myszy",
    "Right mousebutton": "Prawy przycisk myszy",
    "Keyboard": "Klawiatura",
    "Show keyboard": "Pokaż klawiaturę",
    "Extra keys": "Przyciski dodatkowe",
    "Show extra keys": "Pokaż przyciski dodatkowe",
    "Ctrl": "Ctrl",
    "Toggle Ctrl": "Przełącz Ctrl",
    "Alt": "Alt",
    "Toggle Alt": "Przełącz Alt",
    "Send Tab": "Wyślij Tab",
    "Tab": "Tab",
    "Esc": "Esc",
    "Send Escape": "Wyślij Escape",
    "Ctrl+Alt+Del": "Ctrl+Alt+Del",
    "Send Ctrl-Alt-Del": "Wyślij Ctrl-Alt-Del",
    "Shutdown/Reboot": "Wyłącz/Uruchom ponownie",
    "Shutdown/Reboot...": "Wyłącz/Uruchom ponownie...",
    "Power": "Włączony",
    "Shutdown": "Wyłącz",
    "Reboot": "Uruchom ponownie",
    "Reset": "Resetuj",
    "Clipboard": "Schowek",
    "Clear": "Wyczyść",
    "Fullscreen": "Pełny ekran",
    "Settings": "Ustawienia",
    "Shared Mode": "Tryb Współdzielenia",
    "View Only": "Tylko Podgląd",
    "Clip to Window": "Przytnij do Okna",
    "Scaling Mode:": "Tryb Skalowania:",
    "None": "Brak",
    "Local scaling": "Skalowanie lokalne",
    "Remote resizing": "Skalowanie zdalne",
    "Advanced": "Zaawansowane",
    "Repeater ID:": "ID Repeatera:",
    "WebSocket": "WebSocket",
    "Encrypt": "Szyfrowanie",
    "Host:": "Host:",
    "Port:": "Port:",
    "Path:": "Ścieżka:",
    "Automatic reconnect": "Automatycznie wznawiaj połączenie",
    "Reconnect delay (ms):": "Opóźnienie wznawiania (ms):",
    "Logging:": "Poziom logowania:",
    "Disconnect": "Rozłącz",
    "Connect": "Połącz",
    "Password:": "Hasło:",
    "Cancel": "Anuluj",
    "Canvas not supported.": "Element Canvas nie jest wspierany.",
    "Disconnect timeout": "Timeout rozłączenia",
    "Local Downscaling": "Downscaling lokalny",
    "Local Cursor": "Lokalny kursor",
    "Forcing clipping mode since scrollbars aren't supported by IE in fullscreen": "Wymuszam clipping mode ponieważ paski przewijania nie są wspierane przez IE w trybie pełnoekranowym",
    "True Color": "True Color",
    "Style:": "Styl:",
    "default": "domyślny",
    "Apply": "Zapisz",
    "Connection": "Połączenie",
    "Token:": "Token:",
    "Send Password": "Wyślij Hasło"
}

================================================
FILE: app/locale/pt_BR.json
================================================
{
    "Connecting...": "Conectando...",
    "Disconnecting...": "Desconectando...",
    "Reconnecting...": "Reconectando...",
    "Internal error": "Erro interno",
    "Must set host": "É necessário definir o host",
    "Connected (encrypted) to ": "Conectado (com criptografia) a ",
    "Connected (unencrypted) to ": "Conectado (sem criptografia) a ",
    "Something went wrong, connection is closed": "Algo deu errado. A conexão foi encerrada.",
    "Failed to connect to server": "Falha ao conectar-se ao servidor",
    "Disconnected": "Desconectado",
    "New connection has been rejected with reason: ": "A nova conexão foi rejeitada pelo motivo: ",
    "New connection has been rejected": "A nova conexão foi rejeitada",
    "Credentials are required": "Credenciais são obrigatórias",
    "noVNC encountered an error:": "O noVNC encontrou um erro:",
    "Hide/Show the control bar": "Esconder/mostrar a barra de controles",
    "Drag": "Arrastar",
    "Move/Drag viewport": "Mover/arrastar a janela",
    "Keyboard": "Teclado",
    "Show keyboard": "Mostrar teclado",
    "Extra keys": "Teclas adicionais",
    "Show extra keys": "Mostrar teclas adicionais",
    "Ctrl": "Ctrl",
    "Toggle Ctrl": "Pressionar/soltar Ctrl",
    "Alt": "Alt",
    "Toggle Alt": "Pressionar/soltar Alt",
    "Toggle Windows": "Pressionar/soltar Windows",
    "Windows": "Windows",
    "Send Tab": "Enviar Tab",
    "Tab": "Tab",
    "Esc": "Esc",
    "Send Escape": "Enviar Esc",
    "Ctrl+Alt+Del": "Ctrl+Alt+Del",
    "Send Ctrl-Alt-Del": "Enviar Ctrl-Alt-Del",
    "Shutdown/Reboot": "Desligar/reiniciar",
    "Shutdown/Reboot...": "Desligar/reiniciar...",
    "Power": "Ligar",
    "Shutdown": "Desligar",
    "Reboot": "Reiniciar",
    "Reset": "Reiniciar (forçado)",
    "Clipboard": "Área de transferência",
    "Clear": "Limpar",
    "Fullscreen": "Tela cheia",
    "Settings": "Configurações",
    "Shared mode": "Modo compartilhado",
    "View only": "Apenas visualizar",
    "Clip to window": "Recortar à janela",
    "Scaling mode:": "Modo de dimensionamento:",
    "None": "Nenhum",
    "Local scaling": "Local",
    "Remote resizing": "Remoto",
    "Advanced": "Avançado",
    "Quality:": "Qualidade:",
    "Compression level:": "Nível de compressão:",
    "Repeater ID:": "ID do repetidor:",
    "WebSocket": "WebSocket",
    "Encrypt": "Criptografar",
    "Host:": "Host:",
    "Port:": "Porta:",
    "Path:": "Caminho:",
    "Automatic reconnect": "Reconexão automática",
    "Reconnect delay (ms):": "Atraso da reconexão (ms)",
    "Show dot when no cursor": "Mostrar ponto quando não há cursor",
    "Logging:": "Registros:",
    "Version:": "Versão:",
    "Disconnect": "Desconectar",
    "Connect": "Conectar",
    "Username:": "Nome de usuário:",
    "Password:": "Senha:",
    "Send credentials": "Enviar credenciais",
    "Cancel": "Cancelar"
}

================================================
FILE: app/locale/ru.json
================================================
{
    "Connecting...": "Подключение...",
    "Disconnecting...": "Отключение...",
    "Reconnecting...": "Переподключение...",
    "Internal error": "Внутренняя ошибка",
    "Must set host": "Задайте имя сервера или IP",
    "Connected (encrypted) to ": "Подключено (с шифрованием) к ",
    "Connected (unencrypted) to ": "Подключено (без шифрования) к ",
    "Something went wrong, connection is closed": "Что-то пошло не так, подключение разорвано",
    "Failed to connect to server": "Ошибка подключения к серверу",
    "Disconnected": "Отключено",
    "New connection has been rejected with reason: ": "Новое соединение отклонено по причине: ",
    "New connection has been rejected": "Новое соединение отклонено",
    "Credentials are required": "Требуются учетные данные",
    "noVNC encountered an error:": "Ошибка noVNC: ",
    "Hide/Show the control bar": "Скрыть/Показать контрольную панель",
    "Drag": "Переместить",
    "Move/Drag viewport": "Переместить окно",
    "Keyboard": "Клавиатура",
    "Show keyboard": "Показать клавиатуру",
    "Extra keys": "Дополнительные Кнопки",
    "Show Extra Keys": "Показать Дополнительные Кнопки",
    "Ctrl": "Ctrl",
    "Toggle Ctrl": "Зажать Ctrl",
    "Alt": "Alt",
    "Toggle Alt": "Зажать Alt",
    "Toggle Windows": "Зажать Windows",
    "Windows": "Вкладка",
    "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": "Настройки",
    "Shared mode": "Общий режим",
    "View Only": "Только Просмотр",
    "Clip to window": "В окно",
    "Scaling mode:": "Масштаб:",
    "None": "Нет",
    "Local scaling": "Локальный масштаб",
    "Remote resizing": "Удаленная перенастройка размера",
    "Advanced": "Дополнительно",
    "Quality:": "Качество",
    "Compression level:": "Уровень Сжатия",
    "Repeater ID:": "Идентификатор ID:",
    "WebSocket": "WebSocket",
    "Encrypt": "Шифрование",
    "Host:": "Сервер:",
    "Port:": "Порт:",
    "Path:": "Путь:",
    "Automatic reconnect": "Автоматическое переподключение",
    "Reconnect delay (ms):": "Задержка переподключения (мс):",
    "Show dot when no cursor": "Показать точку вместо курсора",
    "Logging:": "Лог:",
    "Version:": "Версия",
    "Disconnect": "Отключение",
    "Connect": "Подключение",
    "Username:": "Имя Пользователя",
    "Password:": "Пароль:",
    "Send Credentials": "Передача Учетных Данных",
    "Cancel": "Выход"
}

================================================
FILE: app/locale/sv.json
================================================
{
    "Running without HTTPS is not recommended, crashes or other issues are likely.": "Det är ej rekommenderat att köra utan HTTPS, krascher och andra problem är troliga.",
    "Connecting...": "Ansluter...",
    "Disconnecting...": "Kopplar ner...",
    "Reconnecting...": "Återansluter...",
    "Internal error": "Internt fel",
    "Failed to connect to server: ": "Misslyckades att ansluta till servern: ",
    "Connected (encrypted) to ": "Ansluten (krypterat) till ",
    "Connected (unencrypted) to ": "Ansluten (okrypterat) till ",
    "Something went wrong, connection is closed": "Något gick fel, anslutningen avslutades",
    "Failed to connect to server": "Misslyckades att ansluta till servern",
    "Disconnected": "Frånkopplad",
    "New connection has been rejected with reason: ": "Ny anslutning har blivit nekad med följande skäl: ",
    "New connection has been rejected": "Ny anslutning har blivit nekad",
    "Credentials are required": "Användaruppgifter krävs",
    "noVNC encountered an error:": "noVNC stötte på ett problem:",
    "Hide/Show the control bar": "Göm/Visa kontrollbaren",
    "Drag": "Dra",
    "Move/Drag viewport": "Flytta/Dra vyn",
    "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",
    "Toggle Windows": "Växla Windows",
    "Windows": "Windows",
    "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",
    "Edit clipboard content in the textarea below.": "Redigera urklippets innehåll i fältet nedan.",
    "Full screen": "Fullskärm",
    "Settings": "Inställningar",
    "Shared mode": "Delat läge",
    "View only": "Endast visning",
    "Clip to window": "Begränsa till fönster",
    "Scaling mode:": "Skalningsläge:",
    "None": "Ingen",
    "Local scaling": "Lokal skalning",
    "Remote resizing": "Ändra storlek",
    "Advanced": "Avancerat",
    "Quality:": "Kvalitet:",
    "Compression level:": "Kompressionsnivå:",
    "Repeater ID:": "Repeater-ID:",
    "WebSocket": "WebSocket",
    "Encrypt": "Kryptera",
    "Host:": "Värd:",
    "Port:": "Port:",
    "Path:": "Sökväg:",
    "Automatic reconnect": "Automatisk återanslutning",
    "Reconnect delay (ms):": "Fördröjning (ms):",
    "Show dot when no cursor": "Visa prick när ingen muspekare finns",
    "Logging:": "Loggning:",
    "Version:": "Version:",
    "Disconnect": "Koppla från",
    "Connect": "Anslut",
    "Server identity": "Server-identitet",
    "The server has provided the following identifying information:": "Servern har gett följande identifierande information:",
    "Fingerprint:": "Fingeravtryck:",
    "Please verify that the information is correct and press \"Approve\". Otherwise press \"Reject\".": "Kontrollera att informationen är korrekt och tryck sedan \"Godkänn\". Tryck annars \"Neka\".",
    "Approve": "Godkänn",
    "Reject": "Neka",
    "Credentials": "Användaruppgifter",
    "Username:": "Användarnamn:",
    "Password:": "Lösenord:",
    "Send credentials": "Skicka användaruppgifter",
    "Cancel": "Avbryt",
    "Must set host": "Du måste specifiera en värd",
    "HTTPS is required for full functionality": "HTTPS krävs för full funktionalitet",
    "Clear": "Rensa"
}

================================================
FILE: app/locale/tr.json
================================================
{
    "Connecting...": "Bağlanıyor...",
    "Disconnecting...": "Bağlantı kesiliyor...",
    "Reconnecting...": "Yeniden bağlantı kuruluyor...",
    "Internal error": "İç hata",
    "Must set host": "Sunucuyu kur",
    "Connected (encrypted) to ": "Bağlı (şifrelenmiş)",
    "Connected (unencrypted) to ": "Bağlandı (şifrelenmemiş)",
    "Something went wrong, connection is closed": "Bir şeyler ters gitti, bağlantı kesildi",
    "Disconnected": "Bağlantı kesildi",
    "New connection has been rejected with reason: ": "Bağlantı aşağıdaki nedenlerden dolayı reddedildi: ",
    "New connection has been rejected": "Bağlantı reddedildi",
    "Password is required": "Şifre gerekli",
    "noVNC encountered an error:": "Bir hata oluştu:",
    "Hide/Show the control bar": "Denetim masasını Gizle/Göster",
    "Move/Drag Viewport": "Görünümü Taşı/Sürükle",
    "viewport drag": "Görüntü penceresini sürükle",
    "Active Mouse Button": "Aktif Fare Düğmesi",
    "No mousebutton": "Fare düğmesi yok",
    "Left mousebutton": "Farenin sol düğmesi",
    "Middle mousebutton": "Farenin orta düğmesi",
    "Right mousebutton": "Farenin sağ düğmesi",
    "Keyboard": "Klavye",
    "Show Keyboard": "Klavye Düzenini Göster",
    "Extra keys": "Ekstra tuşlar",
    "Show extra keys": "Ekstra tuşları göster",
    "Ctrl": "Ctrl",
    "Toggle Ctrl": "Ctrl Değiştir ",
    "Alt": "Alt",
    "Toggle Alt": "Alt Değiştir",
    "Send Tab": "Sekme Gönder",
    "Tab": "Sekme",
    "Esc": "Esc",
    "Send Escape": "Boşluk Gönder",
    "Ctrl+Alt+Del": "Ctrl + Alt + Del",
    "Send Ctrl-Alt-Del": "Ctrl-Alt-Del Gönder",
    "Shutdown/Reboot": "Kapat/Yeniden Başlat",
    "Shutdown/Reboot...": "Kapat/Yeniden Başlat...",
    "Power": "Güç",
    "Shutdown": "Kapat",
    "Reboot": "Yeniden Başlat",
    "Reset": "Sıfırla",
    "Clipboard": "Pano",
    "Clear": "Temizle",
    "Fullscreen": "Tam Ekran",
    "Settings": "Ayarlar",
    "Shared Mode": "Paylaşım Modu",
    "View Only": "Sadece Görüntüle",
    "Clip to Window": "Pencereye Tıkla",
    "Scaling Mode:": "Ölçekleme Modu:",
    "None": "Bilinmeyen",
    "Local Scaling": "Yerel Ölçeklendirme",
    "Remote Resizing": "Uzaktan Yeniden Boyutlandırma",
    "Advanced": "Gelişmiş",
    "Repeater ID:": "Tekralayıcı ID:",
    "WebSocket": "WebSocket",
    "Encrypt": "Şifrele",
    "Host:": "Ana makine:",
    "Port:": "Port:",
    "Path:": "Yol:",
    "Automatic Reconnect": "Otomatik Yeniden Bağlan",
    "Reconnect Delay (ms):": "Yeniden Bağlanma Süreci (ms):",
    "Logging:": "Giriş yapılıyor:",
    "Disconnect": "Bağlantıyı Kes",
    "Connect": "Bağlan",
    "Password:": "Parola:",
    "Cancel": "Vazgeç",
    "Canvas not supported.": "Tuval desteklenmiyor."
}

================================================
FILE: app/locale/uk.json
================================================
{
    "Running without HTTPS is not recommended, crashes or other issues are likely.": "Робота без HTTPS не рекомендується, ймовірні збої чи інші проблеми.",
    "Connecting...": "З'єднання...",
    "Disconnecting...": "Від'єднання...",
    "Reconnecting...": "Перез'єднання...",
    "Internal error": "Внутрішня помилка",
    "Failed to connect to server: ": "Не вдалося з'єднатися з сервером: ",
    "Connected (encrypted) to ": "З'єднано (з шифруванням) з ",
    "Connected (unencrypted) to ": "З'єднано (без шифрування) з ",
    "Something went wrong, connection is closed": "Щось пішло не так, з'єднання закрито",
    "Failed to connect to server": "Не вдалося з'єднатися з сервером",
    "Disconnected": "Від'єднано",
    "New connection has been rejected with reason: ": "Нове з'єднання відхилено. Причина: ",
    "New connection has been rejected": "Нове з'єднання відхилено",
    "Are you sure you want to disconnect the session?": "Точно від'єднати сеанс?",
    "Credentials are required": "Треба особові дані",
    "noVNC encountered an error:": "Помилка noVNC:",
    "Hide/Show the control bar": "Сховати/показати панель керування",
    "Drag": "Посунути",
    "Move/Drag viewport": "Змістити область огляду",
    "Keyboard": "Клавіатура",
    "Show keyboard": "Показати клавіатуру",
    "Extra keys": "Додаткові клавіші",
    "Show extra keys": "Показати додаткові клавіші",
    "Ctrl": "Ctrl",
    "Toggle Ctrl": "Затиснути Ctrl",
    "Alt": "Alt",
    "Toggle Alt": "Затиснути Alt",
    "Toggle Windows": "Затиснути Windows",
    "Windows": "Windows",
    "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": "Буфер обміну",
    "Edit clipboard content in the textarea below.": "Редагуйте вміст буфера обміну в текстовій зоні внизу.",
    "Full screen": "Повний екран",
    "Settings": "Параметри",
    "Shared mode": "Спільний режим",
    "View only": "Лише перегляд",
    "Clip to window": "До розмірів вікна",
    "Scaling mode:": "Режим масштабування:",
    "None": "Вимкнено",
    "Local scaling": "Локальне масштабування",
    "Remote resizing": "Віддалене масштабування",
    "Advanced": "Додатково",
    "Quality:": "Якість:",
    "Compression level:": "Рівень стиснення:",
    "Repeater ID:": "Ідентифікатор репітера:",
    "WebSocket": "WebSocket",
    "Encrypt": "Шифрування",
    "Host:": "Сервер:",
    "Port:": "Порт:",
    "Path:": "Шлях:",
    "Automatic reconnect": "Автоматичне перез'єднання",
    "Reconnect delay (ms):": "Затримка перез'єднання (мс):",
    "Show dot when no cursor": "Показувати крапку, коли нема курсора",
    "Logging:": "Журнал:",
    "Version:": "Версія:",
    "Disconnect": "Від'єднати",
    "Connect": "З'єднати",
    "Server identity": "Ідентифікація сервера",
    "The server has provided the following identifying information:": "Сервер надає такі ідентифікаційні дані:",
    "Fingerprint:": "Відбиток:",
    "Please verify that the information is correct and press \"Approve\". Otherwise press \"Reject\".": "Перевірте, чи дані коректні, й натисніть «Схвалити». Інакше натисніть «Відхилити».",
    "Approve": "Схвалити",
    "Reject": "Відхилити",
    "Credentials": "Особові дані",
    "Username:": "Користувацьке ім'я:",
    "Password:": "Пароль:",
    "Send credentials": "Надіслати особові дані",
    "Cancel": "Скасувати"
}

================================================
FILE: app/locale/zh_CN.json
================================================
{
    "Running without HTTPS is not recommended, crashes or other issues are likely.": "不建议在没有 HTTPS 的情况下运行,可能会出现崩溃或出现其他问题。",
    "Connecting...": "连接中...",
    "Disconnecting...": "正在断开连接...",
    "Reconnecting...": "重新连接中...",
    "Internal error": "内部错误",
    "Must set host": "必须设置主机",
    "Failed to connect to server: ": "无法连接到服务器:",
    "Connected (encrypted) to ": "已连接(已加密)到",
    "Connected (unencrypted) to ": "已连接(未加密)到",
    "Something went wrong, connection is closed": "出了点问题,连接已关闭",
    "Failed to connect to server": "无法连接到服务器",
    "Disconnected": "已断开连接",
    "New connection has been rejected with reason: ": "新连接被拒绝,原因如下:",
    "New connection has been rejected": "新连接已被拒绝",
    "Credentials are required": "需要凭证",
    "noVNC encountered an error:": "noVNC 遇到一个错误:",
    "Hide/Show the control bar": "显示/隐藏控制栏",
    "Drag": "拖动",
    "Move/Drag viewport": "移动/拖动窗口",
    "Keyboard": "键盘",
    "Show keyboard": "显示键盘",
    "Extra keys": "额外按键",
    "Show extra keys": "显示额外按键",
    "Ctrl": "Ctrl",
    "Toggle Ctrl": "切换 Ctrl",
    "Alt": "Alt",
    "Toggle Alt": "切换 Alt",
    "Toggle Windows": "切换窗口",
    "Windows": "窗口",
    "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": "剪贴板",
    "Edit clipboard content in the textarea below.": "在下面的文本区域中编辑剪贴板内容。",
    "Full screen": "全屏",
    "Settings": "设置",
    "Shared mode": "分享模式",
    "View only": "仅查看",
    "Clip to window": "限制/裁切窗口大小",
    "Scaling mode:": "缩放模式:",
    "None": "无",
    "Local scaling": "本地缩放",
    "Remote resizing": "远程调整大小",
    "Advanced": "高级",
    "Quality:": "品质:",
    "Compression level:": "压缩级别:",
    "Repeater ID:": "中继站 ID",
    "WebSocket": "WebSocket",
    "Encrypt": "加密",
    "Host:": "主机:",
    "Port:": "端口:",
    "Path:": "路径:",
    "Automatic reconnect": "自动重新连接",
    "Reconnect delay (ms):": "重新连接间隔 (ms):",
    "Show dot when no cursor": "无光标时显示点",
    "Logging:": "日志级别:",
    "Version:": "版本:",
    "Disconnect": "断开连接",
    "Connect": "连接",
    "Server identity": "服务器身份",
    "The server has provided the following identifying information:": "服务器提供了以下识别信息:",
    "Fingerprint:": "指纹:",
    "Please verify that the information is correct and press \"Approve\". Otherwise press \"Reject\".": "请核实信息是否正确,并按 “同意”,否则按 “拒绝”。",
    "Approve": "同意",
    "Reject": "拒绝",
    "Credentials": "凭证",
    "Username:": "用户名:",
    "Password:": "密码:",
    "Send credentials": "发送凭证",
    "Cancel": "取消",
    "Password is required": "请提供密码",
    "Disconnect timeout": "超时断开",
    "viewport drag": "窗口拖动",
    "Active Mouse Button": "启动鼠标按键",
    "No mousebutton": "禁用鼠标按键",
    "Left mousebutton": "鼠标左键",
    "Middle mousebutton": "鼠标中键",
    "Right mousebutton": "鼠标右键",
    "Clear": "清除",
    "Local Downscaling": "降低本地尺寸",
    "Local Cursor": "本地光标",
    "Canvas not supported.": "不支持 Canvas。"
}

================================================
FILE: app/locale/zh_TW.json
================================================
{
    "Connecting...": "連線中...",
    "Disconnecting...": "正在中斷連線...",
    "Reconnecting...": "重新連線中...",
    "Internal error": "內部錯誤",
    "Must set host": "請提供主機資訊",
    "Connected (encrypted) to ": "已加密連線到",
    "Connected (unencrypted) to ": "未加密連線到",
    "Something went wrong, connection is closed": "發生錯誤,連線已關閉",
    "Failed to connect to server": "無法連線到伺服器",
    "Disconnected": "連線已中斷",
    "New connection has been rejected with reason: ": "連線被拒絕,原因:",
    "New connection has been rejected": "連線被拒絕",
    "Password is required": "請提供密碼",
    "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": "設定",
    "Shared mode": "分享模式",
    "View only": "僅檢視",
    "Clip to window": "限制/裁切視窗大小",
    "Scaling mode:": "縮放模式:",
    "None": "無",
    "Local scaling": "本機縮放",
    "Remote resizing": "遠端調整大小",
    "Advanced": "進階",
    "Repeater ID:": "中繼站 ID",
    "WebSocket": "WebSocket",
    "Encrypt": "加密",
    "Host:": "主機:",
    "Port:": "連接埠:",
    "Path:": "路徑:",
    "Automatic reconnect": "自動重新連線",
    "Reconnect delay (ms):": "重新連線間隔 (ms):",
    "Logging:": "日誌級別:",
    "Disconnect": "中斷連線",
    "Connect": "連線",
    "Password:": "密碼:",
    "Cancel": "取消"
}

================================================
FILE: app/localization.js
================================================
/*
 * noVNC: HTML5 VNC client
 * Copyright (C) 2018 The noVNC authors
 * Licensed under MPL 2.0 (see LICENSE.txt)
 *
 * See README.md for usage and integration instructions.
 */

/*
 * Localization utilities
 */

export class Localizer {
    constructor() {
        // Currently configured language
        this.language = 'en';

        // Current dictionary of translations
        this._dictionary = undefined;
    }

    // Configure suitable language based on user preferences
    async setup(supportedLanguages, baseURL) {
        this.language = 'en'; // Default: US English
        this._dictionary = undefined;

        this._setupLanguage(supportedLanguages);
        await this._setupDictionary(baseURL);
    }

    _setupLanguage(supportedLanguages) {
        /*
         * Navigator.languages only available in Chrome (32+) and FireFox (32+)
         * Fall back to navigator.language for other browsers
         */
        let userLanguages;
        if (typeof window.navigator.languages == 'object') {
            userLanguages = window.navigator.languages;
        } else {
            userLanguages = [navigator.language || navigator.userLanguage];
        }

        for (let i = 0;i < userLanguages.length;i++) {
            const userLang = userLanguages[i]
                .toLowerCase()
                .replace("_", "-")
                .split("-");

            // First pass: perfect match
            for (let j = 0; j < supportedLanguages.length; j++) {
                const supLang = supportedLanguages[j]
                    .toLowerCase()
                    .replace("_", "-")
                    .split("-");

                if (userLang[0] !== supLang[0]) {
                    continue;
                }
                if (userLang[1] !== supLang[1]) {
                    continue;
                }

                this.language = supportedLanguages[j];
                return;
            }

            // Second pass: English fallback
            if (userLang[0] === 'en') {
                return;
            }

            // Third pass pass: other fallback
            for (let j = 0;j < supportedLanguages.length;j++) {
                const supLang = supportedLanguages[j]
                    .toLowerCase()
                    .replace("_", "-")
                    .split("-");

                if (userLang[0] !== supLang[0]) {
                    continue;
                }
                if (supLang[1] !== undefined) {
                    continue;
                }

                this.language = supportedLanguages[j];
                return;
            }
        }
    }

    async _setupDictionary(baseURL) {
        if (baseURL) {
            if (!baseURL.endsWith("/")) {
                baseURL = baseURL + "/";
            }
        } else {
            baseURL = "";
        }

        if (this.language === "en") {
            return;
        }

        let response = await fetch(baseURL + this.language + ".json");
        if (!response.ok) {
            throw Error("" + response.status + " " + response.statusText);
        }

        this._dictionary = await response.json();
    }

    // Retrieve localised text
    get(id) {
        if (typeof this._dictionary !== 'undefined' &&
            this._dictionary[id]) {
            return this._dictionary[id];
        } else {
            return id;
        }
    }

    // Traverses the DOM and translates relevant fields
    // See https://html.spec.whatwg.org/multipage/dom.html#attr-translate
    translateDOM() {
        const self = this;

        function process(elem, enabled) {
            function isAnyOf(searchElement, items) {
                return items.indexOf(searchElement) !== -1;
            }

            function translateString(str) {
                // We assume surrounding whitespace, and whitespace around line
                // breaks is just for source formatting
                str = str.split("\n").map(s => s.trim()).join(" ").trim();
                return self.get(str);
            }

            function translateAttribute(elem, attr) {
                const str = translateString(elem.getAttribute(attr));
                elem.setAttribute(attr, str);
            }

            function translateTextNode(node) {
                const str = translateString(node.data);
                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, ["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", "submit"])) {
                    translateAttribute(elem, "value");
                }
            }

            for (let i = 0; i < elem.childNodes.length; i++) {
                const 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);
    }
}

export const l10n = new Localizer();
export default l10n.get.bind(l10n);


================================================
FILE: app/sounds/CREDITS
================================================
bell
        Copyright: Dr. Richard Boulanger et al
        URL: http://www.archive.org/details/Berklee44v12
        License: CC-BY Attribution 3.0 Unported


================================================
FILE: app/styles/base.css
================================================
/*
 * noVNC base CSS
 * Copyright (C) 2019 The noVNC authors
 * noVNC is licensed under the MPL 2.0 (see LICENSE.txt)
 * This file is licensed under the 2-Clause BSD license (see LICENSE.txt).
 */

/*
 * Z index layers:
 *
 * 0: Main screen
 * 10: Control bar
 * 50: Transition blocker
 * 60: Connection popups
 * 100: Status bar
 * ...
 * 1000: Javascript crash
 * ...
 * 10000: Max (used for polyfills)
 */

/*
 * State variables (set on :root):
 *
 * noVNC_loading: Page is still loading
 * noVNC_connecting: Connecting to server
 * noVNC_reconnecting: Re-establishing a connection
 * noVNC_connected: Connected to server (most common state)
 * noVNC_disconnecting: Disconnecting from server
 */

:root {
    font-family: sans-serif;
    line-height: 1.6;
}

body {
    margin:0;
    padding:0;
    /*Background image with light grey curve.*/
    background-color:#494949;
    background-repeat:no-repeat;
    background-position:right bottom;
    height:100%;
    touch-action: none;
}

html {
    height:100%;
}

.noVNC_only_touch.noVNC_hidden {
    display: none;
}

.noVNC_disabled {
    color: var(--novnc-grey);
}

/* ----------------------------------------
 * Spinner
 * ----------------------------------------
 */

.noVNC_spinner {
    position: relative;
}
.noVNC_spinner, .noVNC_spinner::before, .noVNC_spinner::after {
    width: 10px;
    height: 10px;
    border-radius: 2px;
    box-shadow: -60px 10px 0 rgba(255, 255, 255, 0);
    animation: noVNC_spinner 1.0s linear infinite;
}
.noVNC_spinner::before {
    content: "";
    position: absolute;
    left: 0px;
    top: 0px;
    animation-delay: -0.1s;
}
.noVNC_spinner::after {
    content: "";
    position: absolute;
    top: 0px;
    left: 0px;
    animation-delay: 0.1s;
}
@keyframes noVNC_spinner {
    0% { box-shadow: -60px 10px 0 rgba(255, 255, 255, 0); width: 20px; }
    25% { box-shadow: 20px 10px 0 rgba(255, 255, 255, 1); width: 10px; }
    50% { box-shadow: 60px 10px 0 rgba(255, 255, 255, 0); width: 10px; }
}

/* ----------------------------------------
 * WebKit centering hacks
 * ----------------------------------------
 */

.noVNC_center {
    /*
     * This is a workaround because webkit misrenders transforms and
     * uses non-integer coordinates, resulting in blurry content.
     * Ideally we'd use "top: 50%; transform: translateY(-50%);" on
     * the objects instead.
     */
    display: flex;
    align-items: center;
    justify-content: center;
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    pointer-events: none;
}
.noVNC_center > * {
    pointer-events: auto;
}
.noVNC_vcenter {
    display: flex !important;
    flex-direction: column;
    justify-content: center;
    position: fixed;
    top: 0;
    left: 0;
    height: 100%;
    margin: 0 !important;
    padding: 0 !important;
    pointer-events: none;
}
.noVNC_vcenter > * {
    pointer-events: auto;
}

/* ----------------------------------------
 * Layering
 * ----------------------------------------
 */

.noVNC_connect_layer {
    z-index: 60;
}

/* ----------------------------------------
 * Fallback error
 * ----------------------------------------
 */

#noVNC_fallback_error {
    z-index: 1000;
    visibility: hidden;
    /* Put a dark background in front of everything but the error,
       and don't let mouse events pass through */
    background: rgba(0, 0, 0, 0.8);
    pointer-events: all;
}
#noVNC_fallback_error.noVNC_open {
    visibility: visible;
}

#noVNC_fallback_error > div {
    max-width: calc(100vw - 30px - 30px);
    max-height: calc(100vh - 30px - 30px);
    overflow: auto;

    padding: 15px;

    transition: 0.5s ease-in-out;

    transform: translateY(-50px);
    opacity: 0;

    text-align: center;
    font-weight: bold;
    color: #fff;

    border-radius: 12px;
    box-shadow: 6px 6px 0px rgba(0, 0, 0, 0.5);
    background: rgba(200,55,55,0.8);
}
#noVNC_fallback_error.noVNC_open > div {
    transform: translateY(0);
    opacity: 1;
}

#noVNC_fallback_errormsg {
    font-weight: normal;
}

#noVNC_fallback_errormsg .noVNC_message {
    display: inline-block;
    text-align: left;
    font-family: monospace;
    white-space: pre-wrap;
}

#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;
    font-family: monospace;
    white-space: pre;
    border: 1px solid rgba(0, 0, 0, 0.5);
    background: rgba(0, 0, 0, 0.2);
    overflow: auto;
}

/* ----------------------------------------
 * Control bar
 * ----------------------------------------
 */

#noVNC_control_bar_anchor {
    /* The anchor is needed to get z-stacking to work */
    position: fixed;
    z-index: 10;

    transition: 0.5s ease-in-out;

    /* Edge misrenders animations wihthout this */
    transform: translateX(0);
}
: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: -100%;

    transition: 0.5s ease-in-out;

    background-color: var(--novnc-blue);
    border-radius: 0 12px 12px 0;

    user-select: none;
    -webkit-user-select: none;
    -webkit-touch-callout: none; /* Disable iOS image long-press popup */
}
#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: 12px 0 0 12px;
}
.noVNC_right #noVNC_control_bar.noVNC_open {
    left: 0;
}
.noVNC_right #noVNC_control_bar::before {
    visibility: hidden;
}

#noVNC_control_bar_handle {
    position: absolute;
    left: -15px;
    top: 0;
    transform: translateY(35px);
    width: calc(100% + 30px);
    height: 50px;
    z-index: -1;
    cursor: pointer;
    border-radius: 6px;
    background-color: var(--novnc-darkblue);
    background-image: url("../images/handle_bg.svg");
    background-repeat: no-repeat;
    background-position: right;
    box-shadow: 3px 3px 0px rgba(0, 0, 0, 0.5);
}
#noVNC_control_bar_handle:after {
    content: "";
    transition: transform 0.5s ease-in-out;
    background: url("../images/handle.svg");
    position: absolute;
    top: 22px; /* (50px-6px)/2 */
    right: 5px;
    width: 5px;
    height: 6px;
}
#noVNC_control_bar.noVNC_open #noVNC_control_bar_handle:after {
    transform: translateX(1px) rotate(180deg);
}
: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;
}
/* Larger touch area for the handle, used when a touch screen is available */
#noVNC_control_bar_handle div {
    position: absolute;
    right: -35px;
    top: 0;
    width: 50px;
    height: 100%;
    display: none;
}
@media (any-pointer: coarse) {
    #noVNC_control_bar_handle div {
        display: initial;
    }
}
.noVNC_right #noVNC_control_bar_handle div {
    left: -35px;
    right: auto;
}

#noVNC_control_bar > .noVNC_scroll {
    max-height: 100vh; /* Chrome is buggy with 100% */
    overflow-x: hidden;
    overflow-y: auto;
    padding: 0 10px;
}

#noVNC_control_bar > .noVNC_scroll > * {
    display: block;
    margin: 10px auto;
}

/* Control bar hint */
#noVNC_hint_anchor {
    position: fixed;
    right: -50px;
    left: auto;
}
#noVNC_control_bar_anchor.noVNC_right + #noVNC_hint_anchor {
    left: -50px;
    right: auto;
}
#noVNC_control_bar_hint {
    position: relative;
    transform: scale(0);
    width: 100px;
    height: 50%;
    max-height: 600px;

    visibility: hidden;
    opacity: 0;
    transition: 0.2s ease-in-out;
    background: transparent;
    box-shadow: 0 0 10px black, inset 0 0 10px 10px var(--novnc-darkblue);
    border-radius: 12px;
    transition-delay: 0s;
}
#noVNC_control_bar_hint.noVNC_active {
    visibility: visible;
    opacity: 1;
    transition-delay: 0.2s;
    transform: scale(1);
}
#noVNC_control_bar_hint.noVNC_notransition {
    transition: none !important;
}

/* Control bar buttons */
#noVNC_control_bar .noVNC_button {
    min-width: unset;
    padding: 4px 4px;
    vertical-align: middle;
    border:1px solid rgba(255, 255, 255, 0.2);
    border-radius: 6px;
    background-color: transparent;
}
#noVNC_control_bar .noVNC_button.noVNC_selected {
    border-color: rgba(0, 0, 0, 0.8);
    background-color: rgba(0, 0, 0, 0.5);
}
#noVNC_control_bar .noVNC_button.noVNC_hidden {
    display: none !important;
}

/* Panels */
.noVNC_panel {
    transform: translateX(25px);

    transition: 0.5s ease-in-out;

    box-sizing: border-box; /* so max-width don't have to care about padding */
    max-width: calc(100vw - 75px - 25px); /* minus left and right margins */
    max-height: 100vh; /* Chrome is buggy with 100% */
    overflow-x: hidden;
    overflow-y: auto;

    visibility: hidden;
    opacity: 0;

    padding: 15px;

    background: #fff;
    border-radius: 12px;
    color: #000;
    border: 2px solid #E0E0E0;
    box-shadow: 6px 6px 0px rgba(0, 0, 0, 0.5);
}
.noVNC_panel.noVNC_open {
    visibility: visible;
    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 > * {
    display: block;
    margin: 10px auto;
}
.noVNC_panel > *:first-child {
    margin-top: 0 !important;
}
.noVNC_panel > *:last-child {
    margin-bottom: 0 !important;
}

.noVNC_panel hr {
    border: none;
    border-top: 1px solid var(--novnc-lightgrey);
    width: 100%; /* <hr> inside a flexbox will otherwise be 0px wide */
}

.noVNC_panel label {
    display: block;
    white-space: nowrap;
    margin: 5px;
}
@media (max-width: 540px) {
    /* Allow wrapping on small screens */
    .noVNC_panel label {
        white-space: unset;
    }
}

.noVNC_panel li {
    margin: 5px;
}

.noVNC_panel .noVNC_heading {
    background-color: var(--novnc-blue);
    border-radius: 6px;
    padding: 5px 8px;
    /* Compensate for padding in image */
    padding-right: 11px;
    display: flex;
    align-items: center;
    gap: 6px;
    color: white;
    font-size: 20px;
    font-weight: bold;
    white-space: nowrap;
}
.noVNC_panel .noVNC_heading img {
    vertical-align: bottom;
}

.noVNC_panel form {
    display: flex;
    flex-direction: column;
    gap: 12px
}

.noVNC_panel .button_row {
    margin-top: 10px;
    display: flex;
    gap: 10px;
    justify-content: space-between;
}
.noVNC_panel .button_row *:only-child {
    margin-left: auto; /* Align single buttons to the right */
}

/* Expanders */
.noVNC_expander {
    cursor: pointer;
}
.noVNC_expander::before {
    content: url("../images/expander.svg");
    display: inline-block;
    margin-right: 5px;
    transition: 0.2s ease-in-out;
}
.noVNC_expander.noVNC_open::before {
    transform: rotateZ(90deg);
}
.noVNC_expander ~ * {
    margin: 5px;
    margin-left: 10px;
    padding: 5px;
    background: rgba(0, 0, 0, 0.04);
    border-radius: 6px;
}
.noVNC_expander:not(.noVNC_open) ~ * {
    display: none;
}

/* Control bar content */

#noVNC_control_bar .noVNC_logo {
    font-size: 13px;
}

.noVNC_logo + hr {
    /* Remove all but top border */
    border: none;
    border-top: 1px solid rgba(255, 255, 255, 0.2);
}

:root:not(.noVNC_connected) #noVNC_view_drag_button {
    display: none;
}

/* noVNC Touch Device only buttons */
:root:not(.noVNC_connected) #noVNC_mobile_buttons {
    display: none;
}
@media not all and (any-pointer: coarse) {
    /* FIXME: The button for the virtual keyboard is the only button in this
              group of "mobile buttons". It is bad to assume that no touch
              devices have physical keyboards available. Hopefully we can get
              a media query for this:
              https://github.com/w3c/csswg-drafts/issues/3871 */
    :root.noVNC_connected #noVNC_mobile_buttons {
        display: none;
    }
}

/* Extra manual keys */
:root:not(.noVNC_connected) #noVNC_toggle_extra_keys_button {
    display: none;
}

#noVNC_modifiers {
    background-color: var(--novnc-darkgrey);
    border: none;
    padding: 10px;
}

/* Shutdown/Reboot */
:root:not(.noVNC_connected) #noVNC_power_button {
    display: none;
}
#noVNC_power {
}
#noVNC_power_buttons {
    display: none;
}

#noVNC_power input[type=button] {
    width: 100%;
}

/* Clipboard */
:root:not(.noVNC_connected) #noVNC_clipboard_button {
    display: none;
}
#noVNC_clipboard_text {
    width: 360px;
    min-width: 150px;
    height: 160px;
    min-height: 70px;

    box-sizing: border-box;
    max-width: 100%;
    /* minus approximate height of title, height of subtitle, and margin */
    max-height: calc(100vh - 10em - 25px);
}

/* Settings */
#noVNC_settings {
}
#noVNC_settings ul {
    list-style: none;
    padding: 0px;
}
#noVNC_settings button,
#noVNC_settings select,
#noVNC_settings textarea,
#noVNC_settings input:not([type=checkbox]):not([type=radio]) {
    margin-left: 6px;
    /* Prevent inputs in settings from being too wide */
    max-width: calc(100% - 6px - var(--input-xpadding) * 2);
}

#noVNC_setting_port {
    width: 80px;
}
#noVNC_setting_path {
    width: 100px;
}

/* Version */

.noVNC_version_wrapper {
    font-size: small;
}

.noVNC_version {
    margin-left: 1rem;
}

/* Connection controls */
:root:not(.noVNC_connected) #noVNC_disconnect_button {
    display: none;
}

/* ----------------------------------------
 * Status dialog
 * ----------------------------------------
 */

#noVNC_status {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    z-index: 100;
    transform: translateY(-100%);

    cursor: pointer;

    transition: 0.5s ease-in-out;

    visibility: hidden;
    opacity: 0;

    padding: 5px;

    display: flex;
    flex-direction: row;
    justify-content: center;
    align-content: center;

    line-height: 1.6;
    word-wrap: break-word;
    color: #fff;

    border-bottom: 1px solid rgba(0, 0, 0, 0.9);
}
#noVNC_status.noVNC_open {
    transform: translateY(0);
    visibility: visible;
    opacity: 1;
}

#noVNC_status::before {
    content: "";
    display: inline-block;
    width: 25px;
    height: 25px;
    margin-right: 5px;
}

#noVNC_status.noVNC_status_normal {
    background: rgba(128,128,128,0.9);
}
#noVNC_status.noVNC_status_normal::before {
    content: url("../images/info.svg") " ";
}
#noVNC_status.noVNC_status_error {
    background: rgba(200,55,55,0.9);
}
#noVNC_status.noVNC_status_error::before {
    content: url("../images/error.svg") " ";
}
#noVNC_status.noVNC_status_warn {
    background: rgba(180,180,30,0.9);
}
#noVNC_status.noVNC_status_warn::before {
    content: url("../images/warning.svg") " ";
}

/* ----------------------------------------
 * Connect dialog
 * ----------------------------------------
 */

#noVNC_connect_dlg {
    transition: 0.5s ease-in-out;

    transform: scale(0, 0);
    visibility: hidden;
    opacity: 0;
}
#noVNC_connect_dlg.noVNC_open {
    transform: scale(1, 1);
    visibility: visible;
    opacity: 1;
}
#noVNC_connect_dlg .noVNC_logo {
    transition: 0.5s ease-in-out;
    padding: 10px;
    margin-bottom: 10px;

    font-size: 80px;
    text-align: center;

    border-radius: 6px;
}
@media (max-width: 440px) {
    #noVNC_connect_dlg {
        max-width: calc(100vw - 100px);
    }
    #noVNC_connect_dlg .noVNC_logo {
        font-size: calc(25vw - 30px);
    }
}
#noVNC_connect_dlg div {
    padding: 18px;

    background-color: var(--novnc-darkgrey);
    border-radius: 12px;
    text-align: center;
    font-size: 20px;

    box-shadow: 6px 6px 0px rgba(0, 0, 0, 0.5);
}
#noVNC_connect_button {
    width: 100%;
    padding: 6px 30px;
    cursor: pointer;
    border-color: transparent;
    border-radius: 12px;
    background-color: var(--novnc-blue);
    color: white;

    display: flex;
    justify-content: center;
    place-items: center;
    gap: 4px;
}

#noVNC_connect_button img {
    vertical-align: bottom;
    height: 1.3em;
}

/* ----------------------------------------
 * Server verification dialog
 * ----------------------------------------
 */

#noVNC_verify_server_dlg {
    position: relative;

    transform: translateY(-50px);
}
#noVNC_verify_server_dlg.noVNC_open {
    transform: translateY(0);
}
#noVNC_fingerprint_block {
    margin: 10px;
}

/* ----------------------------------------
 * Password dialog
 * ----------------------------------------
 */

#noVNC_credentials_dlg {
    position: relative;

    transform: translateY(-50px);
}
#noVNC_credentials_dlg.noVNC_open {
    transform: translateY(0);
}
#noVNC_username_block.noVNC_hidden,
#noVNC_password_block.noVNC_hidden {
    display: none;
}


/* ----------------------------------------
 * Main area
 * ----------------------------------------
 */

/* Transition screen */
#noVNC_transition {
    transition: 0.5s ease-in-out;

    display: flex;
    opacity: 0;
    visibility: hidden;

    position: fixed;
    top: 0;
    left: 0;
    bottom: 0;
    right: 0;

    color: white;
    background: rgba(0, 0, 0, 0.5);
    z-index: 50;

    /*display: flex;*/
    align-items: center;
    justify-content: center;
    flex-direction: column;
}
:root.noVNC_loading #noVNC_transition,
:root.noVNC_connecting #noVNC_transition,
:root.noVNC_disconnecting #noVNC_transition,
:root.noVNC_reconnecting #noVNC_transition {
    opacity: 1;
    visibility: visible;
}
:root:not(.noVNC_reconnecting) #noVNC_cancel_reconnect_button {
    display: none;
}
#noVNC_transition_text {
    font-size: 1.5em;
}

/* Main container */
#noVNC_container {
    width: 100%;
    height: 100%;
    background-color: #313131;
    border-bottom-right-radius: 800px 600px;
    /*border-top-left-radius: 800px 600px;*/

    /* If selection isn't disabled, long-pressing stuff in the sidebar
       can accidentally select the container or the canvas. This can
       happen when attempting to move the handle. */
    user-select: none;
    -webkit-user-select: none;
}

#noVNC_keyboardinput {
    width: 1px;
    height: 1px;
    background-color: #fff;
    color: #fff;
    border: 0;
    position: absolute;
    left: -40px;
    z-index: -1;
    ime-mode: disabled;
}

/*Default noVNC logo.*/
/* From: http://fonts.googleapis.com/css?family=Orbitron:700 */
@font-face {
    font-family: 'Orbitron';
    font-style: normal;
    font-weight: 700;
    src: local('?'), url('Orbitron700.woff') format('woff'),
                     url('Orbitron700.ttf') format('truetype');
}

.noVNC_logo {
    color: var(--novnc-yellow);
    font-family: 'Orbitron', 'OrbitronTTF', sans-serif;
    line-height: 0.9;
    text-shadow: 0.1em 0.1em 0 black;
}
.noVNC_logo span{
    color: var(--novnc-green);
}

#noVNC_bell {
    display: none;
}

/* ----------------------------------------
 * Media sizing
 * ----------------------------------------
 */

@media screen and (max-width: 640px){
    #noVNC_logo {
        font-size: 150px;
    }
}

@media screen and (min-width: 321px) and (max-width: 480px) {
    #noVNC_logo {
        font-size: 110px;
    }
}

@media screen and (max-width: 320px) {
    #noVNC_logo {
        font-size: 90px;
    }
}


================================================
FILE: app/styles/constants.css
================================================
/*
 * noVNC general CSS constant variables
 * Copyright (C) 2025 The noVNC authors
 * noVNC is licensed under the MPL 2.0 (see LICENSE.txt)
 * This file is licensed under the 2-Clause BSD license (see LICENSE.txt).
 */

/* ---------- COLORS ----------- */

:root {
    --novnc-grey: rgb(128, 128, 128);
    --novnc-lightgrey: rgb(192, 192, 192);
    --novnc-darkgrey: rgb(92, 92, 92);

    /* Transparent to make button colors adapt to the background */
    --novnc-buttongrey: rgba(192, 192, 192, 0.5);

    --novnc-blue: rgb(110, 132, 163);
    --novnc-lightblue: rgb(74, 144, 217);
    --novnc-darkblue: rgb(83, 99, 122);

    --novnc-green: rgb(0, 128, 0);
    --novnc-yellow: rgb(255, 255, 0);
}

/* ------ MISC PROPERTIES ------ */

:root {
    --input-xpadding: 1em;
}


================================================
FILE: app/styles/input.css
================================================
/*
 * noVNC general input element CSS
 * Copyright (C) 2025 The noVNC authors
 * noVNC is licensed under the MPL 2.0 (see LICENSE.txt)
 * This file is licensed under the 2-Clause BSD license (see LICENSE.txt).
 */

/* ------- SHARED BETWEEN INPUT ELEMENTS -------- */

input,
textarea,
button,
select,
input::file-selector-button {
    padding: 0.5em var(--input-xpadding);
    border-radius: 6px;
    appearance: none;
    text-overflow: ellipsis;

    /* Respect standard font settings */
    font: inherit;
    line-height: 1.6;
}
input:disabled,
textarea:disabled,
button:disabled,
select:disabled,
label[disabled] {
    opacity: 0.4;
}

input:focus-visible,
textarea:focus-visible,
button:focus-visible,
select:focus-visible,
input:focus-visible::file-selector-button {
    outline: 2px solid var(--novnc-lightblue);
    outline-offset: 1px;
}

/* ------- TEXT INPUT -------- */

input:not([type]),
input[type=date],
input[type=datetime-local],
input[type=email],
input[type=month],
input[type=number],
input[type=password],
input[type=search],
input[type=tel],
input[type=text],
input[type=time],
input[type=url],
input[type=week],
textarea {
    border: 1px solid var(--novnc-lightgrey);
    /* Account for borders on text inputs, buttons dont have borders */
    padding: calc(0.5em - 1px) var(--input-xpadding);
}
input:not([type]):focus-visible,
input[type=date]:focus-visible,
input[type=datetime-local]:focus-visible,
input[type=email]:focus-visible,
input[type=month]:focus-visible,
input[type=number]:focus-visible,
input[type=password]:focus-visible,
input[type=search]:focus-visible,
input[type=tel]:focus-visible,
input[type=text]:focus-visible,
input[type=time]:focus-visible,
input[type=url]:focus-visible,
input[type=week]:focus-visible,
textarea:focus-visible {
    outline-offset: -1px;
}

textarea {
    margin: unset; /* Remove Firefox's built in margin */
    /* Prevent layout from shifting when scrollbars show */
    scrollbar-gutter: stable;
    /* Make textareas show at minimum one line. This does not work when
       using box-sizing border-box, in which case, vertical padding and
       border width needs to be taken into account. */
    min-height: 1lh;
    vertical-align: baseline; /* Firefox gives "text-bottom" by default */
}

/* ------- NUMBER PICKERS ------- */

/* We can't style the number spinner buttons:
   https://github.com/w3c/csswg-drafts/issues/8777 */
input[type=number]::-webkit-inner-spin-button,
input[type=number]::-webkit-outer-spin-button {
    /* Get rid of increase/decrease buttons in WebKit */
    appearance: none;
}
input[type=number] {
    /* Get rid of increase/decrease buttons in Firefox */
    appearance: textfield;
}

/* ------- BUTTON ACTIVATIONS -------- */

/* A color overlay that depends on the activation level. The level can then be
   set for different states on an element, for example hover and click on a
   <button>. */
input, button, select, option,
input::file-selector-button,
.button-activations {
    --button-activation-level: 0;
    /* Note that CSS variables aren't functions, beware when inheriting */
    --button-activation-alpha: calc(0.08 * var(--button-activation-level));
    /* FIXME: We want the image() function instead of the linear-gradient()
              function below. But it's not supported in the browsers yet. */
    --button-activation-overlay:
        linear-gradient(rgba(0, 0, 0, var(--button-activation-alpha))
        100%, transparent);
    --button-activation-overlay-light:
        linear-gradient(rgba(255, 255, 255, calc(0.23 * var(--button-activation-level)))
        100%, transparent);
}
.button-activations {
    background-image: var(--button-activation-overlay);

    /* Disable Chrome's touch tap highlight to avoid conflicts with overlay */
    -webkit-tap-highlight-color: transparent;
}
/* When we want the light overlay on activations instead.
   This is best used on elements with darker backgrounds. */
.button-activations.light-overlay {
    background-image: var(--button-activation-overlay-light);
    /* Can't use the normal blend mode since that gives washed out colors. */
    /* FIXME: For elements with these activation overlays we'd like only
              the luminosity to change. The proprty "background-blend-mode" set
              to "luminosity" sounds good, but it doesn't work as intended,
              see: https://bugzilla.mozilla.org/show_bug.cgi?id=1806417 */
    background-blend-mode: overlay;
}

input:hover, button:hover, select:hover, option:hover,
input::file-selector-button:hover,
.button-activations:hover {
    --button-activation-level: 1;
}
/* Unfortunately we have to disable the :hover effect on touch devices,
   otherwise the style lingers after tapping the button. */
@media (any-pointer: coarse) {
    input:hover, button:hover, select:hover, option:hover,
    input::file-selector-button:hover,
    .button-activations:hover {
        --button-activation-level: 0;
    }
}
input:active, button:active, select:active, option:active,
input::file-selector-button:active,
.button-activations:active {
    --button-activation-level: 2;
}
input:disabled, button:disabled, select:disabled, select:disabled option,
input:disabled::file-selector-button,
.button-activations:disabled {
    --button-activation-level: 0;
}

/* ------- BUTTONS -------- */

input[type=button],
input[type=color],
input[type=image],
input[type=reset],
input[type=submit],
input::file-selector-button,
button,
select {
    min-width: 8em;
    border: none;
    color: black;
    font-weight: bold;
    background-color: var(--novnc-buttongrey);
    background-image: var(--button-activation-overlay);
    cursor: pointer;
    /* Disable Chrome's touch tap highlight */
    -webkit-tap-highlight-color: transparent;
}
input[type=button]:disabled,
input[type=color]:disabled,
input[type=image]:disabled,
input[type=reset]:disabled,
input[type=submit]:disabled,
input:disabled::file-selector-button,
button:disabled,
select:disabled {
    /* See Firefox bug:
       https://bugzilla.mozilla.org/show_bug.cgi?id=1798304 */
    cursor: default;
}

input[type=button],
input[type=color],
input[type=reset],
input[type=submit] {
    /* Workaround for text-overflow bugs in Firefox and Chromium:
        https://bugzilla.mozilla.org/show_bug.cgi?id=1800077
        https://bugs.chromium.org/p/chromium/issues/detail?id=1383144 */
    overflow: clip;
}

/* ------- COLOR PICKERS ------- */

input[type=color] {
    min-width: unset;
    box-sizing: content-box;
    width: 1.4em;
    height: 1.4em;
}
input[type=color]::-webkit-color-swatch-wrapper {
    padding: 0;
}
/* -webkit-color-swatch & -moz-color-swatch cant be in a selector list:
   https://bugs.chromium.org/p/chromium/issues/detail?id=1154623 */
input[type=color]::-webkit-color-swatch {
    border: none;
    border-radius: 6px;
}
input[type=color]::-moz-color-swatch {
    border: none;
    border-radius: 6px;
}

/* -- SHARED BETWEEN CHECKBOXES, RADIOBUTTONS AND THE TOGGLE CLASS -- */

input[type=radio],
input[type=checkbox] {
    display: inline-flex;
    justify-content: center;
    align-items: center;
    background-color: var(--novnc-buttongrey);
    background-image: var(--button-activation-overlay);
    /* Disable Chrome's touch tap highlight to avoid conflicts with overlay */
    -webkit-tap-highlight-color: transparent;
    width: 16px;
    --checkradio-height: 16px;
    height: var(--checkradio-height);
    padding: 0;
    margin: 0 6px 0 0;
    /* Don't have transitions for outline in order to be consistent
       with other elements */
    transition: all 0.2s, outline-color 0s, outline-offset 0s;

    /* A transparent outline in order to work around a graphical clipping issue
       in WebKit. See bug: https://bugs.webkit.org/show_bug.cgi?id=256003 */
    outline: 1px solid transparent;
    position: relative; /* Since ::before & ::after are absolute positioned */

    /* We want to align with the middle of capital letters, this requires
       a workaround. The default behavior is to align the bottom of the element
       on top of the text baseline, this is too far up.
       We want to push the element down half the difference in height between
       it and a capital X. In our font, the height of a capital "X" is 0.698em.
     */
    vertical-align: calc(0px - (var(--checkradio-height) - 0.698em) / 2);
    /* FIXME: Could write 1cap instead of 0.698em, but it's only supported in
              Firefox as of 2023 */
    /* FIXME: We probably want to use round() here, see bug 8148 */
}
input[type=radio]:focus-visible,
input[type=checkbox]:focus-visible {
    outline-color: var(--novnc-lightblue);
}
input[type=checkbox]::before,
input[type=checkbox]:not(.toggle)::after,
input[type=radio]::before,
input[type=radio]::after {
    content: "";
    display: block; /* width & height doesn't work on inline elements */
    transition: inherit;
    /* Let's prevent the pseudo-elements from taking up layout space so that
       the ::before and ::after pseudo-elements can be in the same place. This
       is also required for vertical-align: baseline to work like we want it to
       on radio/checkboxes. If the pseudo-elements take up layout space, the
       baseline of text inside them will be used instead. */
    position: absolute;
}
input[type=checkbox]:not(.toggle)::after,
input[type=radio]::after {
    width: 10px;
    height: 2px;
    background-color: transparent;
    border-radius: 2px;
}

/* ------- CHECKBOXES ------- */

input[type=checkbox]:not(.toggle) {
    border-radius: 4px;
}
input[type=checkbox]:not(.toggle):checked,
input[type=checkbox]:not(.toggle):indeterminate {
    background-color: var(--novnc-blue);
    background-image: var(--button-activation-overlay-light);
    background-blend-mode: overlay;
}
input[type=checkbox]:not(.toggle)::before {
    width: 25%;
    height: 55%;
    border-style: solid;
    border-color: transparent;
    border-width: 0 2px 2px 0;
    border-radius: 1px;
    transform: translateY(-1px) rotate(35deg);
}
input[type=checkbox]:not(.toggle):checked::before {
    border-color: white;
}
input[type=checkbox]:not(.toggle):indeterminate::after {
    background-color: white;
}

/* ------- RADIO BUTTONS ------- */

input[type=radio] {
    border-radius: 50%;
    border: 1px solid transparent; /* To ensure a smooth transition */
}
input[type=radio]:checked {
    border: 4px solid var(--novnc-blue);
    background-color: white;
    /* button-activation-overlay should be removed from the radio
       element to not interfere with button-activation-overlay-light
       that is set on the ::before element. */
    background-image: none;
}
input[type=radio]::before {
    width: inherit;
    height: inherit;
    border-radius: inherit;
    /* We can achieve the highlight overlay effect on border colors by
       setting button-activation-overlay-light on an element that stays
       on top (z-axis) of the element with a border. */
    background-image: var(--button-activation-overlay-light);
    mix-blend-mode: overlay;
    opacity: 0;
}
input[type=radio]:checked::before {
    opacity: 1;
}
input[type=radio]:indeterminate::after {
    background-color: black;
}

/* ------- TOGGLE SWITCHES ------- */

/* These are meant to be used instead of checkboxes in some cases. If all of
   the following critera are true you should use a toggle switch:

    * The choice is a simple ON/OFF or ENABLE/DISABLE
    * The choice doesn't give the feeling of "I agree" or "I confirm"
    * There are not multiple related & grouped options
 */

input[type=checkbox].toggle {
    display: inline-block;
    --checkradio-height: 18px; /* Height value used in calc, see above */
    width: 31px;
    cursor: pointer;
    user-select: none;
    -webkit-user-select: none;
    border-radius: 9px;
}
input[type=checkbox].toggle:disabled {
    cursor: default;
}
input[type=checkbox].toggle:indeterminate {
    background-color: var(--novnc-buttongrey);
    background-image: var(--button-activation-overlay);
}
input[type=checkbox].toggle:checked {
    background-color: var(--novnc-blue);
    background-image: var(--button-activation-overlay-light);
    background-blend-mode: overlay;
}
input[type=checkbox].toggle::before {
    --circle-diameter: 10px;
    --circle-offset: 4px;
    width: var(--circle-diameter);
    height: var(--circle-diameter);
    top: var(--circle-offset);
    left: var(--circle-offset);
    background: white;
    border-radius: 6px;
}
input[type=checkbox].toggle:checked::before {
    left: calc(100% - var(--circle-offset) - var(--circle-diameter));
}
input[type=checkbox].toggle:indeterminate::before {
    left: calc(50% - var(--circle-diameter) / 2);
}

/* ------- RANGE SLIDERS ------- */

input[type=range] {
    border: unset;
    border-radius: 8px;
    height: 15px;
    padding: 0;
    background: transparent;
    /* Needed to get properly rounded corners on -moz-range-progress
       when the thumb is all the way to the right. Without overflow
       hidden, the pointy edges of the progress track shows to the
       right of the thumb. */
    overflow: hidden;
}
@supports selector(::-webkit-slider-thumb) {
    input[type=range] {
        /* Needs a fixed width to match clip-path */
        width: 125px;
        /* overflow: hidden is not ideal for hiding the left part of the box
           shadow of -webkit-slider-thumb since it doesn't match the smaller
           border-radius of the progress track. The below clip-path has two
           circular sides to make the ends of the track have correctly rounded
           corners. The clip path shape looks something like this:

                  +-------------------------------+
              /---|                               |---\
             |                                         |
              \---|                               |---/
                  +-------------------------------+

           The larger middle part of the clip path is made to have room for the
           thumb. By using margins on the track, we prevent the thumb from
           touching the ends of the track.
         */
        clip-path: path(' \
         M 4.5 3 \
         L 4.5 0 \
         L 120.5 0 \
         L 120.5 3 \
         A 1 1 0 0 1 120.5 12 \
         L 120.5 15 \
         L 4.5 15 \
         L 4.5 12 \
         A 1 1 0 0 1 4.5 3 \
        ');
    }
}
input[type=range]:hover {
    cursor: grab;
}
input[type=range]:active {
    cursor: grabbing;
}
input[type=range]:disabled {
    cursor: default;
}
input[type=range]:focus-visible {
    clip-path: none; /* Otherwise it hides the outline */
}
/* -webkit-slider.. & -moz-range.. cant be in selector lists:
   https://bugs.chromium.org/p/chromium/issues/detail?id=1154623 */
input[type=range]::-webkit-slider-runnable-track {
    background-color: var(--novnc-buttongrey);
    height: 7px;
    border-radius: 4px;
    margin: 0 3px;
}
input[type=range]::-moz-range-track {
    background-color: var(--novnc-buttongrey);
    height: 7px;
    border-radius: 4px;
}
input[type=range]::-moz-range-progress {
    background-color: var(--novnc-blue);
    height: 9px;
    /* Needs rounded corners only on the left side. Otherwise the rounding of
       the progress track starts before the thumb, when the thumb is close to
       the left edge. */
    border-radius: 5px 0 0 5px;
}
input[type=range]::-webkit-slider-thumb {
    appearance: none;
    width: 15px;
    height: 15px;
    border-radius: 50%;
    background-color: white;
    background-image: var(--button-activation-overlay);
    /* Disable Chrome's touch tap highlight to avoid conflicts with overlay */
    -webkit-tap-highlight-color: transparent;
    border: 3px solid var(--novnc-blue);
    margin-top: -4px; /* (track height / 2) - (thumb height /2) */

    /* Since there is no way to style the left part of the range track in
       webkit, we add a large shadow (1000px wide) to the left of the thumb and
       then crop it with a clip-path shaped like this:
                              ___
        +-------------------/     \
        |      progress     |Thumb|
        +-------------------\ ___ /

        The large left part of the shadow is clipped by another clip-path on on
        the main range input element. */
    /* FIXME: We can remove the box shadow workaround when this is standardized:
              https://github.com/w3c/csswg-drafts/issues/4410 */

    box-shadow: calc(-100vw - 8px) 0 0 100vw var(--novnc-blue);
    clip-path: path(' \
     M -1000 3 \
     L 3 3 \
     L 15 7.5 \
     A 1 1 0 0 1 0 7.5 \
     A 1 1 0 0 1 15 7.5 \
     L 3 12 \
     L -1000 12 Z \
    ');
}
input[type=range]::-moz-range-thumb {
    appearance: none;
    width: 15px;
    height: 15px;
    border-radius: 50%;
    box-sizing: border-box;
    background-color: white;
    background-image: var(--button-activation-overlay);
    border: 3px solid var(--novnc-blue);
    margin-top: -7px;
}

/* ------- FILE CHOOSERS ------- */

input[type=file] {
    background-image: none;
    border: none;
}
input::file-selector-button {
    margin-right: 6px;
}
input[type=file]:focus-visible {
    outline: none; /* We outline the button instead of the entire element */
}

/* ------- SELECT BUTTONS ------- */

select {
    --select-arrow: url('data:image/svg+xml;utf8, \
        <svg width="11" height="6" version="1.1" viewBox="0 0 11 6" \
             xmlns="http://www.w3.org/2000/svg"> \
            <path d="m10.5.5-5 5-5-5" fill="none" \
                  stroke="black" stroke-width="1.5" \
                  stroke-linecap="round" stroke-linejoin="round"/> \
        </svg>');

    /* FIXME: A bug in Firefox, requires a workaround for the background:
              https://bugzilla.mozilla.org/show_bug.cgi?id=1810958 */
    /* The dropdown list will show the select element's background above and
       below the options in Firefox. We want the entire dropdown to be white. */
    background-color: white;
    /* However, we don't want the select element to actually show a white
       background, so let's place a gradient above it with the color we want. */
    --grey-background: linear-gradient(var(--novnc-buttongrey) 100%,
                                       transparent);
    background-image:
        var(--select-arrow),
        var(--button-activation-overlay),
        var(--grey-background);
    background-position: calc(100% - var(--input-xpadding)), left top, left top;
    background-repeat: no-repeat;
    padding-right: calc(2*var(--input-xpadding) + 11px);
    overflow: auto;
}
/* FIXME: :active isn't set when the <select> is opened in Firefox:
          https://bugzilla.mozilla.org/show_bug.cgi?id=1805406 */
select:active {
    /* Rotated arrow */
    background-image: url('data:image/svg+xml;utf8, \
        <svg width="11" height="6" version="1.1" viewBox="0 0 11 6" \
             xmlns="http://www.w3.org/2000/svg" transform="rotate(180)"> \
            <path d="m10.5.5-5 5-5-5" fill="none" \
                  stroke="black" stroke-width="1.5" \
                  stroke-linecap="round" stroke-linejoin="round"/> \
        </svg>'),
        var(--button-activation-overlay),
        var(--grey-background);
}
select:disabled {
    background-image:
        var(--select-arrow),
        var(--grey-background);
}
/* Note that styling for <option> doesn't work in all browsers
   since its often drawn directly by the OS. We are generally very
   limited in what we can change here. */
option {
    /* Prevent Chrome from inheriting background-color from the <select> */
    background-color: white;
    color: black;
    font-weight: normal;
    background-image: var(--button-activation-overlay);
}
option:checked {
    background-color: var(--novnc-lightgrey);
}
/* Change the look when the <select> isn't used as a dropdown. When "size"
   or "multiple" are set, these elements behaves more like lists. */
select[size]:not([size="1"]), select[multiple] {
    background-color: white;
    background-image: unset; /* Don't show the arrow and other gradients */
    border: 1px solid var(--novnc-lightgrey);
    padding: 0;
    font-weight: normal; /* Without this, options get bold font in WebKit. */

    /* As an exception to the "list"-look, multi-selects in Chrome on Android,
       and Safari on iOS, are unfortunately designed to be shown as a single
       line. We can mitigate this inconsistency by at least fixing the height
       here. By setting a min-height that matches other input elements, it
       doesn't look too much out of place:
         (1px border * 2) + (6.5px padding * 2) + 24px line-height = 39px */
    min-height: 39px;
}
select[size]:not([size="1"]):focus-visible,
select[multiple]:focus-visible {
    /* Text input style focus-visible highlight */
    outline-offset: -1px;
}
select[size]:not([size="1"]) option, select[multiple] option {
    overflow: hidden;
    text-overflow: ellipsis;
    padding: 4px var(--input-xpadding);
}


================================================
FILE: app/ui.js
================================================
/*
 * noVNC: HTML5 VNC client
 * Copyright (C) 2019 The noVNC authors
 * Licensed under MPL 2.0 (see LICENSE.txt)
 *
 * See README.md for usage and integration instructions.
 */

import * as Log from '../core/util/logging.js';
import _, { l10n } from './localization.js';
import { isTouchDevice, isMac, isIOS, isAndroid, isChromeOS, isSafari,
         hasScrollbarGutter, dragThreshold, browserAsyncClipboardSupport }
    from '../core/util/browser.js';
import { setCapture, getPointerEvent } from '../core/util/events.js';
import KeyTable from "../core/input/keysym.js";
import keysyms from "../core/input/keysymdef.js";
import Keyboard from "../core/input/keyboard.js";
import RFB from "../core/rfb.js";
import WakeLockManager from './wakelock.js';
import * as WebUtil from "./webutil.js";

const PAGE_TITLE = "noVNC";

const LINGUAS = ["cs", "de", "el", "es", "fr", "hr", "hu", "it", "ja", "ko", "nl", "pl", "pt_BR", "ru", "sv", "tr", "uk", "zh_CN", "zh_TW"];

const UI = {

    customSettings: {},

    connected: false,
    desktopName: "",

    statusTimeout: null,
    hideKeyboardTimeout: null,
    idleControlbarTimeout: null,
    closeControlbarTimeout: null,

    controlbarGrabbed: false,
    controlbarDrag: false,
    controlbarMouseDownClientY: 0,
    controlbarMouseDownOffsetY: 0,

    lastKeyboardinput: null,
    defaultKeyboardinputLen: 100,

    inhibitReconnect: true,
    reconnectCallback: null,
    reconnectPassword: null,

    wakeLockManager: new WakeLockManager(),

    async start(options={}) {
        UI.customSettings = options.settings || {};
        if (UI.customSettings.defaults === undefined) {
            UI.customSettings.defaults = {};
        }
        if (UI.customSettings.mandatory === undefined) {
            UI.customSettings.mandatory = {};
        }

        // Set up translations
        try {
            await l10n.setup(LINGUAS, "app/locale/");
        } catch (err) {
            Log.Error("Failed to load translations: " + err);
        }

        // Initialize setting storage
        await WebUtil.initSettings();

        // Wait for the page to load
        if (document.readyState !== "interactive" && document.readyState !== "complete") {
            await new Promise((resolve, reject) => {
                document.addEventListener('DOMContentLoaded', resolve);
            });
        }

        UI.initSettings();

        // Translate the DOM
        l10n.translateDOM();

        // We rely on modern APIs which might not be available in an
        // insecure context
        if (!window.isSecureContext) {
            // FIXME: This gets hidden when connecting
            UI.showStatus(_("Running without HTTPS is not recommended, crashes or other issues are likely."), 'error');
        }

        // Try to fetch version number
        try {
            let response = await fetch('./package.json');
            if (!response.ok) {
                throw Error("" + response.status + " " + response.statusText);
            }

            let packageInfo = await response.json();
            Array.from(document.getElementsByClassName('noVNC_version')).forEach(el => el.innerText = packageInfo.version);
        } catch (err) {
            Log.Error("Couldn't fetch package.json: " + err);
            Array.from(document.getElementsByClassName('noVNC_version_wrapper'))
                .concat(Array.from(document.getElementsByClassName('noVNC_version_separator')))
                .forEach(el => el.style.display = 'none');
        }

        // Adapt the interface for touch screen devices
        if (isTouchDevice) {
            // Remove the address bar
            setTimeout(() => window.scrollTo(0, 1), 100);
        }

        // Restore control bar position
        if (WebUtil.readSetting('controlbar_pos') === 'right') {
            UI.toggleControlbarSide();
        }

        UI.initFullscreen();

        // Setup event handlers
        UI.addControlbarHandlers();
        UI.addTouchSpecificHandlers();
        UI.addExtraKeysHandlers();
        UI.addMachineHandlers();
        UI.addConnectionControlHandlers();
        UI.addClipboardHandlers();
        UI.addSettingsHandlers();
        document.getElementById("noVNC_status")
            .addEventListener('click', UI.hideStatus);

        // Bootstrap fallback input handler
        UI.keyboardinputReset();

        UI.openControlbar();

        UI.updateVisualState('init');

        document.documentElement.classList.remove("noVNC_loading");

        let autoconnect = UI.getSetting('autoconnect');
        if (autoconnect === 'true' || autoconnect == '1') {
            UI.connect();
        } else {
            // Show the connect panel on first load unless autoconnecting
            UI.openConnectPanel();
        }
    },

    initFullscreen() {
        // Only show the button if fullscreen is properly supported
        // * Safari doesn't support alphanumerical input while in fullscreen
        if (!isSafari() &&
            (document.documentElement.requestFullscreen ||
             document.documentElement.mozRequestFullScreen ||
             document.documentElement.webkitRequestFullscreen ||
             document.body.msRequestFullscreen)) {
            document.getElementById('noVNC_fullscreen_button')
                .classList.remove("noVNC_hidden");
            UI.addFullscreenHandlers();
        }
    },

    initSettings() {
        // Logging selection dropdown
        const llevels = ['error', 'warn', 'info', 'debug'];
        for (let i = 0; i < llevels.length; i += 1) {
            UI.addOption(document.getElementById('noVNC_setting_logging'), llevels[i], llevels[i]);
        }

        // Settings with immediate effects
        UI.initSetting('logging', 'warn');
        UI.updateLogging();

        UI.setupSettingLabels();

        /* Populate the controls if defaults are provided in the URL */
        UI.initSetting('host', '');
        UI.initSetting('port', 0);
        UI.initSetting('encrypt', (window.location.protocol === "https:"));
        UI.initSetting('password');
        UI.initSetting('autoconnect', false);
        UI.initSetting('view_clip', false);
        UI.initSetting('resize', 'off');
        UI.initSetting('quality', 6);
        UI.initSetting('compression', 2);
        UI.initSetting('shared', true);
        UI.initSetting('bell', 'on');
        UI.initSetting('view_only', false);
        UI.initSetting('show_dot', false);
        UI.initSetting('path', 'websockify');
        UI.initSetting('repeaterID', '');
        UI.initSetting('reconnect', false);
        UI.initSetting('reconnect_delay', 5000);
        UI.initSetting('keep_device_awake', false);
    },
    // Adds a link to the label elements on the corresponding input elements
    setupSettingLabels() {
        const labels = document.getElementsByTagName('LABEL');
        for (let i = 0; i < labels.length; i++) {
            const htmlFor = labels[i].htmlFor;
            if (htmlFor != '') {
                const elem = document.getElementById(htmlFor);
                if (elem) elem.label = labels[i];
            } else {
                // If 'for' isn't set, use the first input element child
                const children = labels[i].children;
                for (let j = 0; j < children.length; j++) {
                    if (children[j].form !== undefined) {
                        children[j].label = labels[i];
                        break;
                    }
                }
            }
        }
    },

/* ------^-------
*     /INIT
* ==============
* EVENT HANDLERS
* ------v------*/

    addControlbarHandlers() {
        document.getElementById("noVNC_control_bar")
            .addEventListener('mousemove', UI.activateControlbar);
        document.getElementById("noVNC_control_bar")
            .addEventListener('mouseup', UI.activateControlbar);
        document.getElementById("noVNC_control_bar")
            .addEventListener('mousedown', UI.activateControlbar);
        document.getElementById("noVNC_control_bar")
            .addEventListener('keydown', UI.activateControlbar);

        document.getElementById("noVNC_control_bar")
            .addEventListener('mousedown', UI.keepControlbar);
        document.getElementById("noVNC_control_bar")
            .addEventListener('keydown', UI.keepControlbar);

        document.getElementById("noVNC_view_drag_button")
            .addEventListener('click', UI.toggleViewDrag);

        document.getElementById("noVNC_control_bar_handle")
            .addEventListener('mousedown', UI.controlbarHandleMouseDown);
        document.getElementById("noVNC_control_bar_handle")
            .addEventListener('mouseup', UI.controlbarHandleMouseUp);
        document.getElementById("noVNC_control_bar_handle")
            .addEventListener('mousemove', UI.dragControlbarHandle);
        // resize events aren't available for elements
        window.addEventListener('resize', UI.updateControlbarHandle);

        const exps = document.getElementsByClassName("noVNC_expander");
        for (let i = 0;i < exps.length;i++) {
            exps[i].addEventListener('click', UI.toggleExpander);
        }
    },

    addTouchSpecificHandlers() {
        document.getElementById("noVNC_keyboard_button")
            .addEventListener('click', UI.toggleVirtualKeyboard);

        UI.touchKeyboard = new Keyboard(document.getElementById('noVNC_keyboardinput'));
        UI.touchKeyboard.onkeyevent = UI.keyEvent;
        UI.touchKeyboard.grab();
        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', () => false);

        document.documentElement
            .addEventListener('mousedown', UI.keepVirtualKeyboard, true);

        document.getElementById("noVNC_control_bar")
            .addEventListener('touchstart', UI.activateControlbar);
        document.getElementById("noVNC_control_bar")
            .addEventListener('touchmove', UI.activateControlbar);
        document.getElementById("noVNC_control_bar")
            .addEventListener('touchend', UI.activateControlbar);
        document.getElementById("noVNC_control_bar")
            .addEventListener('input', UI.activateControlbar);

        document.getElementById("noVNC_control_bar")
            .addEventListener('touchstart', UI.keepControlbar);
        document.getElementById("noVNC_control_bar")
            .addEventListener('input', UI.keepControlbar);

        document.getElementById("noVNC_control_bar_handle")
            .addEventListener('touchstart', UI.controlbarHandleMouseDown);
        document.getElementById("noVNC_control_bar_handle")
            .addEventListener('touchend', UI.controlbarHandleMouseUp);
        document.getElementById("noVNC_control_bar_handle")
            .addEventListener('touchmove', UI.dragControlbarHandle);
    },

    addExtraKeysHandlers() {
        document.getElementById("noVNC_toggle_extra_keys_button")
            .addEventListener('click', UI.toggleExtraKeys);
        document.getElementById("noVNC_toggle_ctrl_button")
            .addEventListener('click', UI.toggleCtrl);
        document.getElementById("noVNC_toggle_windows_button")
            .addEventListener('click', UI.toggleWindows);
        document.getElementById("noVNC_toggle_alt_button")
            .addEventListener('click', UI.toggleAlt);
        document.getElementById("noVNC_send_tab_button")
            .addEventListener('click', UI.sendTab);
        document.getElementById("noVNC_send_esc_button")
            .addEventListener('click', UI.sendEsc);
        document.getElementById("noVNC_send_ctrl_alt_del_button")
            .addEventListener('click', UI.sendCtrlAltDel);
    },

    addMachineHandlers() {
        document.getElementById("noVNC_shutdown_button")
            .addEventListener('click', () => UI.rfb.machineShutdown());
        document.getElementById("noVNC_reboot_button")
            .addEventListener('click', () => UI.rfb.machineReboot());
        document.getElementById("noVNC_reset_button")
            .addEventListener('click', () => UI.rfb.machineReset());
        document.getElementById("noVNC_power_button")
            .addEventListener('click', UI.togglePowerPanel);
    },

    addConnectionControlHandlers() {
        document.getElementById("noVNC_disconnect_button")
            .addEventListener('click', UI.disconnect);
        document.getElementById("noVNC_connect_button")
            .addEventListener('click', UI.connect);
        document.getElementById("noVNC_cancel_reconnect_button")
            .addEventListener('click', UI.cancelReconnect);

        document.getElementById("noVNC_approve_server_button")
            .addEventListener('click', UI.approveServer);
        document.getElementById("noVNC_reject_server_button")
            .addEventListener('click', UI.rejectServer);
        document.getElementById("noVNC_credentials_button")
            .addEventListener('click', UI.setCredentials);
    },

    addClipboardHandlers() {
        document.getElementById("noVNC_clipboard_button")
            .addEventListener('click', UI.toggleClipboardPanel);
        document.getElementById("noVNC_clipboard_text")
            .addEventListener('change', UI.clipboardSend);
    },

    // Add a call to save settings when the element changes,
    // unless the optional parameter changeFunc is used instead.
    addSettingChangeHandler(name, changeFunc) {
        const settingElem = document.getElementById("noVNC_setting_" + name);
        if (changeFunc === undefined) {
            changeFunc = () => UI.saveSetting(name);
        }
        settingElem.addEventListener('change', changeFunc);
    },

    addSettingsHandlers() {
        document.getElementById("noVNC_settings_button")
            .addEventListener('click', UI.toggleSettingsPanel);

        UI.addSettingChangeHandler('encrypt');
        UI.addSettingChangeHandler('resize');
        UI.addSettingChangeHandler('resize', UI.applyResizeMode);
        UI.addSettingChangeHandler('resize', UI.updateViewClip);
        UI.addSettingChangeHandler('quality');
        UI.addSettingChangeHandler('quality', UI.updateQuality);
        UI.addSettingChangeHandler('compression');
        UI.addSettingChangeHandler('compression', UI.updateCompression);
        UI.addSettingChangeHandler('view_clip');
        UI.addSettingChangeHandler('view_clip', UI.updateViewClip);
        UI.addSettingChangeHandler('shared');
        UI.addSettingChangeHandler('view_only');
        UI.addSettingChangeHandler('view_only', UI.updateViewOnly);
        UI.addSettingChangeHandler('show_dot');
        UI.addSettingChangeHandler('show_dot', UI.updateShowDotCursor);
        UI.addSettingChangeHandler('keep_device_awake');
        UI.addSettingChangeHandler('keep_device_awake', UI.updateRequestWakelock);
        UI.addSettingChangeHandler('host');
        UI.addSettingChangeHandler('port');
        UI.addSettingChangeHandler('path');
        UI.addSettingChangeHandler('repeaterID');
        UI.addSettingChangeHandler('logging');
        UI.addSettingChangeHandler('logging', UI.updateLogging);
        UI.addSettingChangeHandler('reconnect');
        UI.addSettingChangeHandler('reconnect_delay');
    },

    addFullscreenHandlers() {
        document.getElementById("noVNC_fullscreen_button")
            .addEventListener('click', UI.toggleFullscreen);

        window.addEventListener('fullscreenchange', UI.updateFullscreenButton);
        window.addEventListener('mozfullscreenchange', UI.updateFullscreenButton);
        window.addEventListener('webkitfullscreenchange', UI.updateFullscreenButton);
        window.addEventListener('msfullscreenchange', UI.updateFullscreenButton);
    },

/* ------^-------
 * /EVENT HANDLERS
 * ==============
 *     VISUAL
 * ------v------*/

    // Disable/enable controls depending on connection state
    updateVisualState(state) {

        document.documentElement.classList.remove("noVNC_connecting");
        document.documentElement.classList.remove("noVNC_connected");
        document.documentElement.classList.remove("noVNC_disconnecting");
        document.documentElement.classList.remove("noVNC_reconnecting");

        const transitionElem = document.getElementById("noVNC_transition_text");
        switch (state) {
            case 'init':
                break;
            case 'connecting':
                transitionElem.textContent = _("Connecting...");
                document.documentElement.classList.add("noVNC_connecting");
                break;
            case 'connected':
                document.documentElement.classList.add("noVNC_connected");
                break;
            case 'disconnecting':
                transitionElem.textContent = _("Disconnecting...");
                document.documentElement.classList.add("noVNC_disconnecting");
                break;
            case 'disconnected':
                break;
            case 'reconnecting':
                transitionElem.textContent = _("Reconnecting...");
                document.documentElement.classList.add("noVNC_reconnecting");
                break;
            default:
                Log.Error("Invalid visual state: " + state);
                UI.showStatus(_("Internal error"), 'error');
                return;
        }

        if (UI.connected) {
            UI.updateViewClip();

            UI.disableSetting('encrypt');
            UI.disableSetting('shared');
            UI.disableSetting('host');
            UI.disableSetting('port');
            UI.disableSetting('path');
            UI.disableSetting('repeaterID');

            // Hide the controlbar after 2 seconds
            UI.closeControlbarTimeout = setTimeout(UI.closeControlbar, 2000);
        } else {
            UI.enableSetting('encrypt');
            UI.enableSetting('shared');
            UI.enableSetting('host');
            UI.enableSetting('port');
            UI.enableSetting('path');
            UI.enableSetting('repeaterID');
            UI.updatePowerButton();
            UI.keepControlbar();
        }

        // State change closes dialogs as they may not be relevant
        // anymore
        UI.closeAllPanels();
        document.getElementById('noVNC_verify_server_dlg')
            .classList.remove('noVNC_open');
        document.getElementById('noVNC_credentials_dlg')
            .classList.remove('noVNC_open');
    },

    showStatus(text, statusType, time) {
        const statusElem = document.getElementById('noVNC_status');

        if (typeof statusType === 'undefined') {
            statusType = 'normal';
        }

        // Don't overwrite more severe visible statuses and never
        // errors. Only shows the first error.
        if (statusElem.classList.contains("noVNC_open")) {
            if (statusElem.classList.contains("noVNC_status_error")) {
                return;
            }
            if (statusElem.classList.contains("noVNC_status_warn") &&
                statusType === 'normal') {
                return;
            }
        }

        clearTimeout(UI.statusTimeout);

        switch (statusType) {
            case 'error':
                statusElem.classList.remove("noVNC_status_warn");
                statusElem.classList.remove("noVNC_status_normal");
                statusElem.classList.add("noVNC_status_error");
                break;
            case 'warning':
            case 'warn':
                statusElem.classList.remove("noVNC_status_error");
                statusElem.classList.remove("noVNC_status_normal");
                statusElem.classList.add("noVNC_status_warn");
                break;
            case 'normal':
            case 'info':
            default:
                statusElem.classList.remove("noVNC_status_error");
                statusElem.classList.remove("noVNC_status_warn");
                statusElem.classList.add("noVNC_status_normal");
                break;
        }

        statusElem.textContent = text;
        statusElem.classList.add("noVNC_open");

        // If no time was specified, show the status for 1.5 seconds
        if (typeof time === 'undefined') {
            time = 1500;
        }

        // Error messages do not timeout
        if (statusType !== 'error') {
            UI.statusTimeout = window.setTimeout(UI.hideStatus, time);
        }
    },

    hideStatus() {
        clearTimeout(UI.statusTimeout);
        document.getElementById('noVNC_status').classList.remove("noVNC_open");
    },

    activateControlbar(event) {
        clearTimeout(UI.idleControlbarTimeout);
        // We manipulate the anchor instead of the actual control
        // bar in order to avoid creating new a stacking group
        document.getElementById('noVNC_control_bar_anchor')
            .classList.remove("noVNC_idle");
        UI.idleControlbarTimeout = window.setTimeout(UI.idleControlbar, 2000);
    },

    idleControlbar() {
        // Don't fade if a child of the control bar has focus
        if (document.getElementById('noVNC_control_bar')
            .contains(document.activeElement) && document.hasFocus()) {
            UI.activateControlbar();
            return;
        }

        document.getElementById('noVNC_control_bar_anchor')
            .classList.add("noVNC_idle");
    },

    keepControlbar() {
        clearTimeout(UI.closeControlbarTimeout);
    },

    openControlbar() {
        document.getElementById('noVNC_control_bar')
            .classList.add("noVNC_open");
    },

    closeControlbar() {
        UI.closeAllPanels();
        document.getElementById('noVNC_control_bar')
            .classList.remove("noVNC_open");
        UI.rfb.focus();
    },

    toggleControlbar() {
        if (document.getElementById('noVNC_control_bar')
            .classList.contains("noVNC_open")) {
            UI.closeControlbar();
        } else {
            UI.openControlbar();
        }
    },

    toggleControlbarSide() {
        // Temporarily disable animation, if bar is displayed, to avoid weird
        // movement. The transitionend-event will not fire when display=none.
        const bar = document.getElementById('noVNC_control_bar');
        const barDisplayStyle = window.getComputedStyle(bar).display;
        if (barDisplayStyle !== 'none') {
            bar.style.transitionDuration = '0s';
            bar.addEventListener('transitionend', () => bar.style.transitionDuration = '');
        }

        const 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;

        // The user has "followed" hint, let's hide it until the next drag
        UI.showControlbarHint(false, false);
    },

    showControlbarHint(show, animate=true) {
        const hint = document.getElementById('noVNC_control_bar_hint');

        if (animate) {
            hint.classList.remove("noVNC_notransition");
        } else {
            hint.classList.add("noVNC_notransition");
        }

        if (show) {
            hint.classList.add("noVNC_active");
        } else {
            hint.classList.remove("noVNC_active");
        }
    },

    dragControlbarHandle(e) {
        if (!UI.controlbarGrabbed) return;

        const ptr = getPointerEvent(e);

        const 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) {
            const dragDistance = Math.abs(ptr.clientY - UI.controlbarMouseDownClientY);

            if (dragDistance < dragThreshold) return;

            UI.controlbarDrag = true;
        }

        const eventY = ptr.clientY - UI.controlbarMouseDownOffsetY;

        UI.moveControlbarHandle(eventY);

        e.preventDefault();
        e.stopPropagation();
        UI.keepControlbar();
        UI.activateControlbar();
    },

    // Move the handle but don't allow any position outside the bounds
    moveControlbarHandle(viewportRelativeY) {
        const handle = document.getElementById("noVNC_control_bar_handle");
        const handleHeight = handle.getBoundingClientRect().height;
        const controlbarBounds = document.getElementById("noVNC_control_bar")
            .getBoundingClientRect();
        const margin = 10;

        // These heights need to be non-zero for the below logic to work
        if (handleHeight === 0 || controlbarBounds.height === 0) {
            return;
        }

        let newY = viewportRelativeY;

        // Check if the coordinates are outside the control bar
        if (newY < controlbarBounds.top + margin) {
            // Force coordinates to be below the top of the control bar
            newY = controlbarBounds.top + margin;

        } else if (newY > controlbarBounds.top +
                   controlbarBounds.height - handleHeight - margin) {
            // Force coordinates to be above the bottom of the control bar
            newY = controlbarBounds.top +
                controlbarBounds.height - handleHeight - margin;
        }

        // Corner case: control bar too small for stable position
        if (controlbarBounds.height < (handleHeight + margin * 2)) {
            newY = controlbarBounds.top +
                (controlbarBounds.height - handleHeight) / 2;
        }

        // The transform needs coordinates that are relative to the parent
        const parentRelativeY = newY - controlbarBounds.top;
        handle.style.transform = "translateY(" + parentRelativeY + "px)";
    },

    updateControlbarHandle() {
        // Since the control bar is fixed on the viewport and not the page,
        // the move function expects coordinates relative the the viewport.
        const handle = document.getElementById("noVNC_control_bar_handle");
        const handleBounds = handle.getBoundingClientRect();
        UI.moveControlbarHandle(handleBounds.top);
    },

    controlbarHandleMouseUp(e) {
        if ((e.type == "mouseup") && (e.button != 0)) return;

        // mouseup and mousedown on the same place toggles the controlbar
        if (UI.controlbarGrabbed && !UI.controlbarDrag) {
            UI.toggleControlbar();
            e.preventDefault();
            e.stopPropagation();
            UI.keepControlbar();
            UI.activateControlbar();
        }
        UI.controlbarGrabbed = false;
        UI.showControlbarHint(false);
    },

    controlbarHandleMouseDown(e) {
        if ((e.type == "mousedown") && (e.button != 0)) return;

        const ptr = getPointerEvent(e);

        const handle = document.getElementById("noVNC_control_bar_handle");
        const bounds = handle.getBoundingClientRect();

        // Touch events have implicit capture
        if (e.type === "mousedown") {
            setCapture(handle);
        }

        UI.controlbarGrabbed = true;
        UI.controlbarDrag = false;

        UI.showControlbarHint(true);

        UI.controlbarMouseDownClientY = ptr.clientY;
        UI.controlbarMouseDownOffsetY = ptr.clientY - bounds.top;
        e.preventDefault();
        e.stopPropagation();
        UI.keepControlbar();
        UI.activateControlbar();
    },

    toggleExpander(e) {
        if (this.classList.contains("noVNC_open")) {
            this.classList.remove("noVNC_open");
        } else {
            this.classList.add("noVNC_open");
        }
    },

/* ------^-------
 *    /VISUAL
 * ==============
 *    SETTINGS
 * ------v------*/

    // Initial page load read/initialization of settings
    initSetting(name, defVal) {
        // Has the user overridden the default value?
        if (name in UI.customSettings.defaults) {
            defVal = UI.customSettings.defaults[name];
        }
        // Check Query string followed by cookie
        let val = WebUtil.getConfigVar(name);
        if (val === null) {
            val = WebUtil.readSetting(name, defVal);
        }
        WebUtil.setSetting(name, val);
        UI.updateSetting(name);
        // Has the user forced a value?
        if (name in UI.customSettings.mandatory) {
            val = UI.customSettings.mandatory[name];
            UI.forceSetting(name, val);
        }
        return val;
    },

    // Set the new value, update and disable form control setting
    forceSetting(name, val) {
        WebUtil.setSetting(name, val);
        UI.updateSetting(name);
        UI.disableSetting(name);
    },

    // Update cookie and form control setting. If value is not set, then
    // updates from control to current cookie setting.
    updateSetting(name) {

        // Update the settings control
        let value = UI.getSetting(name);

        const ctrl = document.getElementById('noVNC_setting_' + name);
        if (ctrl === null) {
            return;
        }

        if (ctrl.type === 'checkbox') {
            ctrl.checked = value;
        } else if (typeof ctrl.options !== 'undefined') {
            for (let i = 0; i < ctrl.options.length; i += 1) {
                if (ctrl.options[i].value === value) {
                    ctrl.selectedIndex = i;
                    break;
                }
            }
        } else {
            ctrl.value = value;
        }
    },

    // Save control setting to cookie
    saveSetting(name) {
        const ctrl = document.getElementById('noVNC_setting_' + name);
        let val;
        if (ctrl.type === 'checkbox') {
            val = ctrl.checked;
        } else if (typeof ctrl.options !== 'undefined') {
            val = ctrl.options[ctrl.selectedIndex].value;
        } else {
            val = ctrl.value;
        }
        WebUtil.writeSetting(name, val);
        //Log.Debug("Setting saved '" + name + "=" + val + "'");
        return val;
    },

    // Read form control compatible setting from cookie
    getSetting(name) {
        const ctrl = document.getElementById('noVNC_setting_' + name);
        let val = WebUtil.readSetting(name);
        if (typeof val !== 'undefined' && val !== null &&
            ctrl !== null && ctrl.type === 'checkbox') {
            if (val.toString().toLowerCase() in {'0': 1, 'no': 1, 'false': 1}) {
                val = false;
            } else {
                val = true;
            }
        }
        return val;
    },

    // These helpers compensate for the lack of parent-selectors and
    // previous-sibling-selectors in CSS which are needed when we want to
    // disable the labels that belong to disabled input elements.
    disableSetting(name) {
        const ctrl = document.getElementById('noVNC_setting_' + name);
        if (ctrl !== null) {
            ctrl.disabled = true;
            if (ctrl.label !== undefined) {
                ctrl.label.classList.add('noVNC_disabled');
            }
        }
    },

    enableSetting(name) {
        const ctrl = document.getElementById('noVNC_setting_' + name);
        if (ctrl !== null) {
            ctrl.disabled = false;
            if (ctrl.label !== undefined) {
                ctrl.label.classList.remove('noVNC_disabled');
            }
        }
    },

/* ------^-------
 *   /SETTINGS
 * ==============
 *    PANELS
 * ------v------*/

    closeAllPanels() {
        UI.closeSettingsPanel();
        UI.closePowerPanel();
        UI.closeClipboardPanel();
        UI.closeExtraKeys();
    },

/* ------^-------
 *   /PANELS
 * ==============
 * SETTINGS (panel)
 * ------v------*/

    openSettingsPanel() {
        UI.closeAllPanels();
        UI.openControlbar();

        // Refresh UI elements from saved cookies
        UI.updateSetting('encrypt');
        UI.updateSetting('view_clip');
        UI.updateSetting('resize');
        UI.updateSetting('quality');
        UI.updateSetting('compression');
        UI.updateSetting('shared');
        UI.updateSetting('view_only');
        UI.updateSetting('path');
        UI.updateSetting('repeaterID');
        UI.updateSetting('logging');
        UI.updateSetting('reconnect');
        UI.updateSetting('reconnect_delay');

        document.getElementById('noVNC_settings')
            .classList.add("noVNC_open");
        document.getElementById('noVNC_settings_button')
            .classList.add("noVNC_selected");
    },

    closeSettingsPanel() {
        document.getElementById('noVNC_settings')
            .classList.remove("noVNC_open");
        document.getElementById('noVNC_settings_button')
            .classList.remove("noVNC_selected");
    },

    toggleSettingsPanel() {
        if (document.getElementById('noVNC_settings')
            .classList.contains("noVNC_open")) {
            UI.closeSettingsPanel();
        } else {
            UI.openSettingsPanel();
        }
    },

/* ------^-------
 *   /SETTINGS
 * ==============
 *     POWER
 * ------v------*/

    openPowerPanel() {
        UI.closeAllPanels();
        UI.openControlbar();

        document.getElementById('noVNC_power')
            .classList.add("noVNC_open");
        document.getElementById('noVNC_power_button')
            .classList.add("noVNC_selected");
    },

    closePowerPanel() {
        document.getElementById('noVNC_power')
            .classList.remove("noVNC_open");
        document.getElementById('noVNC_power_button')
            .classList.remove("noVNC_selected");
    },

    togglePowerPanel() {
        if (document.getElementById('noVNC_power')
            .classList.contains("noVNC_open")) {
            UI.closePowerPanel();
        } else {
            UI.openPowerPanel();
        }
    },

    // Disable/enable power button
    updatePowerButton() {
        if (UI.connected &&
            UI.rfb.capabilities.power &&
            !UI.rfb.viewOnly) {
            document.getElementById('noVNC_power_button')
                .classList.remove("noVNC_hidden");
        } else {
            document.getElementById('noVNC_power_button')
                .classList.add("noVNC_hidden");
            // Close power panel if open
            UI.closePowerPanel();
        }
    },

/* ------^-------
 *    /POWER
 * ==============
 *   CLIPBOARD
 * ------v------*/

    openClipboardPanel() {
        UI.closeAllPanels();
        UI.openControlbar();

        document.getElementById('noVNC_clipboard')
            .classList.add("noVNC_open");
        document.getElementById('noVNC_clipboard_button')
            .classList.add("noVNC_selected");
    },

    closeClipboardPanel() {
        document.getElementById('noVNC_clipboard')
            .classList.remove("noVNC_open");
        document.getElementById('noVNC_clipboard_button')
            .classList.remove("noVNC_selected");
    },

    toggleClipboardPanel() {
        if (document.getElementById('noVNC_clipboard')
            .classList.contains("noVNC_open")) {
            UI.closeClipboardPanel();
        } else {
            UI.openClipboardPanel();
        }
    },

    clipboardReceive(e) {
        Log.Debug(">> UI.clipboardReceive: " + e.detail.text.substr(0, 40) + "...");
        document.getElementById('noVNC_clipboard_text').value = e.detail.text;
        Log.Debug("<< UI.clipboardReceive");
    },

    clipboardSend() {
        const text = document.getElementById('noVNC_clipboard_text').value;
        Log.Debug(">> UI.clipboardSend: " + text.substr(0, 40) + "...");
        UI.rfb.clipboardPasteFrom(text);
        Log.Debug("<< UI.clipboardSend");
    },

/* ------^-------
 *  /CLIPBOARD
 * ==============
 *  CONNECTION
 * ------v------*/

    openConnectPanel() {
        document.getElementById('noVNC_connect_dlg')
            .classList.add("noVNC_open");
    },

    closeConnectPanel() {
        document.getElementById('noVNC_connect_dlg')
            .classList.remove("noVNC_open");
    },

    connect(event, password) {

        // Ignore when rfb already exists
        if (typeof UI.rfb !== 'undefined') {
            return;
        }

        const host = UI.getSetting('host');
        const port = UI.getSetting('port');
        const path = UI.getSetting('path');

        if (typeof password === 'undefined') {
            password = UI.getSetting('password');
            UI.reconnectPassword = password;
        }

        if (password === null) {
            password = undefined;
        }

        UI.hideStatus();

        UI.closeConnectPanel();

        UI.updateVisualState('connecting');

        let url;

        if (host) {
            url = new URL("https://" + host);

            url.protocol = UI.getSetting('encrypt') ? 'wss:' : 'ws:';
            if (port) {
                url.port = port;
            }

            // "./" is needed to force URL() to interpret the path-variable as
            // a path and not as an URL. This is relevant if for example path
            // starts with more than one "/", in which case it would be
            // interpreted as a host name instead.
            url = new URL("./" + path, url);
        } else {
            // Current (May 2024) browsers support relative WebSocket
            // URLs natively, but we need to support older browsers for
            // some time.
            url = new URL(path, location.href);
            url.protocol = (window.location.protocol === "https:") ? 'wss:' : 'ws:';
        }

        if (UI.getSetting('keep_device_awake')) {
            UI.wakeLockManager.acquire();
        }

        try {
            UI.rfb = new RFB(document.getElementById('noVNC_container'),
                             url.href,
                             { shared: UI.getSetting('shared'),
                               repeaterID: UI.getSetting('repeaterID'),
                               credentials: { password: password } });
        } catch (exc) {
            Log.Error("Failed to connect to server: " + exc);
            UI.updateVisualState('disconnected');
            UI.showStatus(_("Failed to connect to server: ") + exc, 'error');
            return;
        }

        UI.rfb.addEventListener("connect", UI.connectFinished);
        UI.rfb.addEventListener("disconnect", UI.disconnectFinished);
        UI.rfb.addEventListener("serververification", UI.serverVerify);
        UI.rfb.addEventListener("credentialsrequired", UI.credentials);
        UI.rfb.addEventListener("securityfailure", UI.securityFailed);
        UI.rfb.addEventListener("clippingviewport", UI.updateViewDrag);
        UI.rfb.addEventListener("capabilities", UI.updatePowerButton);
        UI.rfb.addEventListener("clipboard", UI.clipboardReceive);
        UI.rfb.addEventListener("bell", UI.bell);
        UI.rfb.addEventListener("desktopname", UI.updateDesktopName);
        UI.rfb.clipViewport = UI.getSetting('view_clip');
        UI.rfb.scaleViewport = UI.getSetting('resize') === 'scale';
        UI.rfb.resizeSession = UI.getSetting('resize') === 'remote';
        UI.rfb.qualityLevel = parseInt(UI.getSetting('quality'));
        UI.rfb.compressionLevel = parseInt(UI.getSetting('compression'));
        UI.rfb.showDotCursor = UI.getSetting('show_dot');

        UI.updateViewOnly(); // requires UI.rfb
        UI.updateClipboard();
    },

    disconnect() {
        UI.rfb.disconnect();

        UI.connected = false;

        // Disable automatic reconnecting
        UI.inhibitReconnect = true;

        UI.updateVisualState('disconnecting');

        // Don't display the connection settings until we're actually disconnected
    },

    reconnect() {
        UI.reconnectCallback = null;

        // if reconnect has been disabled in the meantime, do nothing.
        if (UI.inhibitReconnect) {
            return;
        }

        UI.connect(null, UI.reconnectPassword);
    },

    cancelReconnect() {
        if (UI.reconnectCallback !== null) {
            clearTimeout(UI.reconnectCallback);
            UI.reconnectCallback = null;
        }

        UI.updateVisualState('disconnected');

        UI.openControlbar();
        UI.openConnectPanel();
    },

    connectFinished(e) {
        UI.connected = true;
        UI.inhibitReconnect = false;

        let msg;
        if (UI.getSetting('encrypt')) {
            msg = _("Connected (encrypted) to ") + UI.desktopName;
        } else {
            msg = _("Connected (unencrypted) to ") + UI.desktopName;
        }
        UI.showStatus(msg);
        UI.updateVisualState('connected');

        UI.updateBeforeUnload();

        // Do this last because it can only be used on rendered elements
        UI.rfb.focus();
    },

    disconnectFinished(e) {
        const wasConnected = UI.connected;

        // This variable is ideally set when disconnection starts, but
        // when the disconnection isn't clean or if it is initiated by
        // the server, we need to do it here as well since
        // UI.disconnect() won't be used in those cases.
        UI.connected = false;

        UI.rfb = undefined;
        UI.wakeLockManager.release();

        if (!e.detail.clean) {
            UI.updateVisualState('disconnected');
            if (wasConnected) {
                UI.showStatus(_("Something went wrong, connection is closed"),
                              'error');
            } else {
                UI.showStatus(_("Failed to connect to server"), 'error');
            }
        }
        // If reconnecting is allowed process it now
        if (UI.getSetting('reconnect', false) === true && !UI.inhibitReconnect) {
            UI.updateVisualState('reconnecting');

            const delay = parseInt(UI.getSetting('reconnect_delay'));
            UI.reconnectCallback = setTimeout(UI.reconnect, delay);
            return;
        } else {
            UI.updateVisualState('disconnected');
            UI.showStatus(_("Disconnected"), 'normal');
        }

        UI.updateBeforeUnload();

        document.title = PAGE_TITLE;

        UI.openControlbar();
        UI.openConnectPanel();
    },

    securityFailed(e) {
        let msg;
        // On security failures we might get a string with a reason
        // directly from the server. Note that we can't control if
        // this string is translated or not.
        if ('reason' in e.detail) {
            msg = _("New connection has been rejected with reason: ") +
                e.detail.reason;
        } else {
            msg = _("New connection has been rejected");
        }
        UI.showStatus(msg, 'error');
    },

    handleBeforeUnload(e) {
        // Trigger a "Leave site?" warning prompt before closing the
        // page. Modern browsers (Oct 2025) accept either (or both)
        // preventDefault() or a nonempty returnValue, though the latter is
        // considered legacy. The custom string is ignored by modern browsers,
        // which display a native message, but older browsers will show it.
        e.preventDefault();
        e.returnValue = _("Are you sure you want to disconnect the session?");
    },

    updateBeforeUnload() {
        // Remove first to avoid adding duplicates
        window.removeEventListener("beforeunload", UI.handleBeforeUnload);
        if (!UI.rfb?.viewOnly && UI.connected) {
            window.addEventListener("beforeunload", UI.handleBeforeUnload);
        }
    },

/* ------^-------
 *  /CONNECTION
 * ==============
 * SERVER VERIFY
 * ------v------*/

    async serverVerify(e) {
        const type = e.detail.type;
        if (type === 'RSA') {
            const publickey = e.detail.publickey;
            let fingerprint = await window.crypto.subtle.digest("SHA-1", publickey);
            // The same fingerprint format as RealVNC
            fingerprint = Array.from(new Uint8Array(fingerprint).slice(0, 8)).map(
                x => x.toString(16).padStart(2, '0')).join('-');
            document.getElementById('noVNC_verify_server_dlg').classList.add('noVNC_open');
            document.getElementById('noVNC_fingerprint').innerHTML = fingerprint;
        }
    },

    approveServer(e) {
        e.preventDefault();
        document.getElementById('noVNC_verify_server_dlg').classList.remove('noVNC_open');
        UI.rfb.approveServer();
    },

    rejectServer(e) {
        e.preventDefault();
        document.getElementById('noVNC_verify_server_dlg').classList.remove('noVNC_open');
        UI.disconnect();
    },

/* ------^-------
 * /SERVER VERIFY
 * ==============
 *   PASSWORD
 * ------v------*/

    credentials(e) {
        // FIXME: handle more types

        document.getElementById("noVNC_username_block").classList.remove("noVNC_hidden");
        document.getElementById("noVNC_password_block").classList.remove("noVNC_hidden");

        let inputFocus = "none";
        if (e.detail.types.indexOf("username") === -1) {
            document.getElementById("noVNC_username_block").classList.add("noVNC_hidden");
        } else {
            inputFocus = inputFocus === "none" ? "noVNC_username_input" : inputFocus;
        }
        if (e.detail.types.indexOf("password") === -1) {
            document.getElementById("noVNC_password_block").classList.add("noVNC_hidden");
        } else {
            inputFocus = inputFocus === "none" ? "noVNC_password_input" : inputFocus;
        }
        document.getElementById('noVNC_credentials_dlg')
            .classList.add('noVNC_open');

        setTimeout(() => document
            .getElementById(inputFocus).focus(), 100);

        Log.Warn("Server asked for credentials");
        UI.showStatus(_("Credentials are required"), "warning");
    },

    setCredentials(e) {
        // Prevent actually submitting the form
        e.preventDefault();

        let inputElemUsername = document.getElementById('noVNC_username_input');
        const username = inputElemUsername.value;

        let inputElemPassword = document.getElementById('noVNC_password_input');
        const password = inputElemPassword.value;
        // Clear the input after reading the password
        inputElemPassword.value = "";

        UI.rfb.sendCredentials({ username: username, password: password });
        UI.reconnectPassword = password;
        document.getElementById('noVNC_credentials_dlg')
            .classList.remove('noVNC_open');
    },

/* ------^-------
 *  /PASSWORD
 * ==============
 *   FULLSCREEN
 * ------v------*/

    toggleFullscreen() {
        if (document.fullscreenElement || // alternative standard method
            document.mozFullScreenElement || // currently working methods
            document.webkitFullscreenElement ||
            document.msFullscreenElement) {
            if (document.exitFullscreen) {
                document.exitFullscreen();
            } else if (document.mozCancelFullScreen) {
                document.mozCancelFullScreen();
            } else if (document.webkitExitFullscreen) {
                document.webkitExitFullscreen();
            } else if (document.msExitFullscreen) {
                document.msExitFullscreen();
            }
        } else {
            if (document.documentElement.requestFullscreen) {
                document.documentElement.requestFullscreen();
            } else if (document.documentElement.mozRequestFullScreen) {
                document.documentElement.mozRequestFullScreen();
            } else if (document.documentElement.webkitRequestFullscreen) {
                document.documentElement.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
            } else if (document.body.msRequestFullscreen) {
                document.body.msRequestFullscreen();
            }
        }
        UI.updateFullscreenButton();
    },

    updateFullscreenButton() {
        if (document.fullscreenElement || // alternative standard method
            document.mozFullScreenElement || // currently working methods
            document.webkitFullscreenElement ||
            document.msFullscreenElement ) {
            document.getElementById('noVNC_fullscreen_button')
                .classList.add("noVNC_selected");
        } else {
            document.getElementById('noVNC_fullscreen_button')
                .classList.remove("noVNC_selected");
        }
    },

/* ------^-------
 *  /FULLSCREEN
 * ==============
 *     RESIZE
 * ------v------*/

    // Apply remote resizing or local scaling
    applyResizeMode() {
        if (!UI.rfb) return;

        UI.rfb.scaleViewport = UI.getSetting('resize') === 'scale';
        UI.rfb.resizeSession = UI.getSetting('resize') === 'remote';
    },

/* ------^-------
 *    /RESIZE
 * ==============
 * VIEW CLIPPING
 * ------v------*/

    // Update viewport clipping property for the connection. The normal
    // case is to get the value from the setting. There are special cases
    // for when the viewport is scaled or when a touch device is used.
    updateViewClip() {
        if (!UI.rfb) return;

        const scaling = UI.getSetting('resize') === 'scale';

        // Some platforms have overlay scrollbars that are difficult
        // to use in our case, which means we have to force panning
        // FIXME: Working scrollbars can still be annoying to use with
        //        touch, so we should ideally be able to have both
        //        panning and scrollbars at the same time

        let brokenScrollbars = false;

        if (!hasScrollbarGutter) {
            if (isIOS() || isAndroid() || isMac() || isChromeOS()) {
                brokenScrollbars = true;
            }
        }

        if (scaling) {
            // Can't be clipping if viewport is scaled to fit
            UI.forceSetting('view_clip', false);
            UI.rfb.clipViewport  = false;
        } else if (brokenScrollbars) {
            UI.forceSetting('view_clip', true);
            UI.rfb.clipViewport = true;
        } else {
            UI.enableSetting('view_clip');
            UI.rfb.clipViewport = UI.getSetting('view_clip');
        }

        // Changing the viewport may change the state of
        // the dragging button
        UI.updateViewDrag();
    },

/* ------^-------
 * /VIEW CLIPPING
 * ==============
 *    VIEWDRAG
 * ------v------*/

    toggleViewDrag() {
        if (!UI.rfb) return;

        UI.rfb.dragViewport = !UI.rfb.dragViewport;
        UI.updateViewDrag();
    },

    updateViewDrag() {
        if (!UI.connected) return;

        const viewDragButton = document.getElementById('noVNC_view_drag_button');

        if ((!UI.rfb.clipViewport || !UI.rfb.clippingViewport) &&
            UI.rfb.dragViewport) {
            // We are no longer clipping the viewport. Make sure
            // viewport drag isn't active when it can't be used.
            UI.rfb.dragViewport = false;
        }

        if (UI.rfb.dragViewport) {
            viewDragButton.classList.add("noVNC_selected");
        } else {
            viewDragButton.classList.remove("noVNC_selected");
        }

        if (UI.rfb.clipViewport) {
            viewDragButton.classList.remove("noVNC_hidden");
        } else {
            viewDragButton.classList.add("noVNC_hidden");
        }

        viewDragButton.disabled = !UI.rfb.clippingViewport;
    },

/* ------^-------
 *   /VIEWDRAG
 * ==============
 *    QUALITY
 * ------v------*/

    updateQuality() {
        if (!UI.rfb) return;

        UI.rfb.qualityLevel = parseInt(UI.getSetting('quality'));
    },

/* ------^-------
 *   /QUALITY
 * ==============
 *  COMPRESSION
 * ------v------*/

    updateCompression() {
        if (!UI.rfb) return;

        UI.rfb.compressionLevel = parseInt(UI.getSetting('compression'));
    },

/* ------^-------
 *  /COMPRESSION
 * ==============
 *    KEYBOARD
 * ------v------*/

    showVirtualKeyboard() {
        if (!isTouchDevice) return;

        const input = document.getElementById('noVNC_keyboardinput');

        if (document.activeElement == input) return;

        input.focus();

        try {
            const l = input.value.length;
            // Move the caret to the end
            input.setSelectionRange(l, l);
        } catch (err) {
            // setSelectionRange is undefined in Google Chrome
        }
    },

    hideVirtualKeyboard() {
        if (!isTouchDevice) return;

        const input = document.getElementById('noVNC_keyboardinput');

        if (document.activeElement != input) return;

        input.blur();
    },

    toggleVirtualKeyboard() {
        if (document.getElementById('noVNC_keyboard_button')
            .classList.contains("noVNC_selected")) {
            UI.hideVirtualKeyboard();
        } else {
            UI.showVirtualKeyboard();
        }
    },

    onfocusVirtualKeyboard(event) {
        document.getElementById('noVNC_keyboard_button')
            .classList.add("noVNC_selected");
        if (UI.rfb) {
            UI.rfb.focusOnClick = false;
        }
    },

    onblurVirtualKeyboard(event) {
        document.getElementById('noVNC_keyboard_button')
            .classList.remove("noVNC_selected");
        if (UI.rfb) {
            UI.rfb.focusOnClick = true;
        }
    },

    keepVirtualKeyboard(event) {
        const input = document.getElementById('noVNC_keyboardinput');

        // Only prevent focus change if the virtual keyboard is active
        if (document.activeElement != input) {
            return;
        }

        // Only allow focus to move to other elements that need
        // focus to function properly
        if (event.target.form !== undefined) {
            switch (event.target.type) {
                case 'text':
                case 'email':
                case 'search':
                case 'password':
                case 'tel':
                case 'url':
                case 'textarea':
                case 'select-one':
                case 'select-multiple':
                    return;
            }
        }

        event.preventDefault();
    },

    keyboardinputReset() {
        const kbi = document.getElementById('noVNC_keyboardinput');
        kbi.value = new Array(UI.defaultKeyboardinputLen).join("_");
        UI.lastKeyboardinput = kbi.value;
    },

    keyEvent(keysym, code, down) {
        if (!UI.rfb) return;

        UI.rfb.sendKey(keysym, code, down);
    },

    // When normal keyboard events are left uncought, use the input events from
    // the keyboardinput element instead and generate the corresponding key events.
    // This code is required since some browsers on Android are inconsistent in
    // sending keyCodes in the normal keyboard events when using on screen keyboards.
    keyInput(event) {

        if (!UI.rfb) return;

        const newValue = event.target.value;

        if (!UI.lastKeyboardinput) {
            UI.keyboardinputReset();
        }
        const oldValue = UI.lastKeyboardinput;

        let newLen;
        try {
            // Try to check caret position since whitespace at the end
            // will not be considered by value.length in some browsers
            newLen = Math.max(event.target.selectionStart, newValue.length);
        } catch (err) {
            // selectionStart is undefined in Google Chrome
            newLen = newValue.length;
        }
        const oldLen = oldValue.length;

        let inputs = newLen - oldLen;
        let backspaces = inputs < 0 ? -inputs : 0;

        // Compare the old string with the new to account for
        // text-corrections or other input that modify existing text
        for (let i = 0; i < Math.min(oldLen, newLen); i++) {
            if (newValue.charAt(i) != oldValue.charAt(i)) {
                inputs = newLen - i;
                backspaces = oldLen - i;
                break;
            }
        }

        // Send the key events
        for (let i = 0; i < backspaces; i++) {
            UI.rfb.sendKey(KeyTable.XK_BackSpace, "Backspace");
        }
        for (let i = newLen - inputs; i < newLen; i++) {
            UI.rfb.sendKey(keysyms.lookup(newValue.charCodeAt(i)));
        }

        // Control the text content length in the keyboardinput element
        if (newLen > 2 * UI.defaultKeyboardinputLen) {
            UI.keyboardinputReset();
        } else if (newLen < 1) {
            // There always have to be some text in the keyboardinput
            // element with which backspace can interact.
            UI.keyboardinputReset();
            // This sometimes causes the keyboard to disappear for a second
            // but it is required for the android keyboard to recognize that
            // 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(event.target.focus.bind(event.target), 0);
        } else {
            UI.lastKeyboardinput = newValue;
        }
    },

/* ------^-------
 *   /KEYBOARD
 * ==============
 *   EXTRA KEYS
 * ------v------*/

    openExtraKeys() {
        UI.closeAllPanels();
        UI.openControlbar();

        document.getElementById('noVNC_modifiers')
            .classList.add("noVNC_open");
        document.getElementById('noVNC_toggle_extra_keys_button')
            .classList.add("noVNC_selected");
    },

    closeExtraKeys() {
        document.getElementById('noVNC_modifiers')
            .classList.remove("noVNC_open");
        document.getElementById('noVNC_toggle_extra_keys_button')
            .classList.remove("noVNC_selected");
    },

    toggleExtraKeys() {
        if (document.getElementById('noVNC_modifiers')
            .classList.contains("noVNC_open")) {
            UI.closeExtraKeys();
        } else  {
            UI.openExtraKeys();
        }
    },

    sendEsc() {
        UI.sendKey(KeyTable.XK_Escape, "Escape");
    },

    sendTab() {
        UI.sendKey(KeyTable.XK_Tab, "Tab");
    },

    toggleCtrl() {
        const btn = document.getElementById('noVNC_toggle_ctrl_button');
        if (btn.classList.contains("noVNC_selected")) {
            UI.sendKey(KeyTable.XK_Control_L, "ControlLeft", false);
            btn.classList.remove("noVNC_selected");
        } else {
            UI.sendKey(KeyTable.XK_Control_L, "ControlLeft", true);
            btn.classList.add("noVNC_selected");
        }
    },

    toggleWindows() {
        const btn = document.getElementById('noVNC_toggle_windows_button');
        if (btn.classList.contains("noVNC_selected")) {
            UI.sendKey(KeyTable.XK_Super_L, "MetaLeft", false);
            btn.classList.remove("noVNC_selected");
        } else {
            UI.sendKey(KeyTable.XK_Super_L, "MetaLeft", true);
            btn.classList.add("noVNC_selected");
        }
    },

    toggleAlt() {
        const btn = document.getElementById('noVNC_toggle_alt_button');
        if (btn.classList.contains("noVNC_selected")) {
            UI.sendKey(KeyTable.XK_Alt_L, "AltLeft", false);
            btn.classList.remove("noVNC_selected");
        } else {
            UI.sendKey(KeyTable.XK_Alt_L, "AltLeft", true);
            btn.classList.add("noVNC_selected");
        }
    },

    sendCtrlAltDel() {
        UI.rfb.sendCtrlAltDel();
        // See below
        UI.rfb.focus();
        UI.idleControlbar();
    },

    sendKey(keysym, code, down) {
        UI.rfb.sendKey(keysym, code, down);

        // Move focus to the screen in order to be able to use the
        // keyboard right after these extra keys.
        // The exception is when a virtual keyboard is used, because
        // if we focus the screen the virtual keyboard would be closed.
        // In this case we focus our special virtual keyboard input
        // element instead.
        if (document.getElementById('noVNC_keyboard_button')
            .classList.contains("noVNC_selected")) {
            document.getElementById('noVNC_keyboardinput').focus();
        } else {
            UI.rfb.focus();
        }
        // fade out the controlbar to highlight that
        // the focus has been moved to the screen
        UI.idleControlbar();
    },

/* ------^-------
 *   /EXTRA KEYS
 * ==============
 *     MISC
 * ------v------*/

    updateViewOnly() {
        if (!UI.rfb) return;
        UI.rfb.viewOnly = UI.getSetting('view_only');

        UI.updateBeforeUnload();

        // Hide input related buttons in view only mode
        if (UI.rfb.viewOnly) {
            document.getElementById('noVNC_keyboard_button')
                .classList.add('noVNC_hidden');
            document.getElementById('noVNC_toggle_extra_keys_button')
                .classList.add('noVNC_hidden');
            document.getElementById('noVNC_clipboard_button')
                .classList.add('noVNC_hidden');
        } else {
            document.getElementById('noVNC_keyboard_button')
                .classList.remove('noVNC_hidden');
            document.getElementById('noVNC_toggle_extra_keys_button')
                .classList.remove('noVNC_hidden');
            document.getElementById('noVNC_clipboard_button')
                .classList.remove('noVNC_hidden');
        }
    },

    updateClipboard() {
        browserAsyncClipboardSupport()
            .then((support) => {
                if (support === 'unsupported') {
                    // Use fallback clipboard panel
                    return;
                }
                if (support === 'denied' || support === 'available') {
                    UI.closeClipboardPanel();
                    document.getElementById('noVNC_clipboard_button')
                        .classList.add('noVNC_hidden');
                    document.getElementById('noVNC_clipboard_button')
                        .removeEventListener('click', UI.toggleClipboardPanel);
                    document.getElementById('noVNC_clipboard_text')
                        .removeEventListener('change', UI.clipboardSend);
                    if (UI.rfb) {
                        UI.rfb.removeEventListener('clipboard', UI.clipboardReceive);
                    }
                }
            })
            .catch(() => {
                // Treat as unsupported
            });
    },

    updateShowDotCursor() {
        if (!UI.rfb) return;
        UI.rfb.showDotCursor = UI.getSetting('show_dot');
    },

    updateLogging() {
        WebUtil.initLogging(UI.getSetting('logging'));
    },

    updateDesktopName(e) {
        UI.desktopName = e.detail.name;
        // Display the desktop name in the document title
        document.title = e.detail.name + " - " + PAGE_TITLE;
    },

    updateRequestWakelock() {
        if (!UI.rfb) return;
        if (UI.getSetting('keep_device_awake')) {
            UI.wakeLockManager.acquire();
        } else {
            UI.wakeLockManager.release();
        }
    },


    bell(e) {
        if (UI.getSetting('bell') === 'on') {
            const promise = document.getElementById('noVNC_bell').play();
            // The standards disagree on the return value here
            if (promise) {
                promise.catch((e) => {
                    if (e.name === "NotAllowedError") {
                        // Ignore when the browser doesn't let us play audio.
                        // It is common that the browsers require audio to be
                        // initiated from a user action.
                    } else {
                        Log.Error("Unable to play bell: " + e);
                    }
                });
            }
        }
    },

    //Helper to add options to dropdown.
    addOption(selectbox, text, value) {
        const optn = document.createElement("OPTION");
        optn.text = text;
        optn.value = value;
        selectbox.options.add(optn);
    },

/* ------^-------
 *    /MISC
 * ==============
 */
};

export default UI;


================================================
FILE: app/wakelock.js
================================================
/*
 * noVNC: HTML5 VNC client
 * Copyright (C) 2025 The noVNC authors
 * Licensed under MPL 2.0 or any later version (see LICENSE.txt)
 *
 * Wrapper around the `navigator.wakeLock` api that handles reacquiring the
 * lock on visiblility changes.
 *
 * The `acquire` and `release` methods may be called any number of times. The
 * most recent call dictates the desired end-state (if `acquire` was most
 * recently called, then we will try to acquire and hold the wake lock).
 */

import * as Log from '../core/util/logging.js';

const _STATES = {
    /* No wake lock.
     *
     * Can transition to:
     *  - AWAITING_VISIBLE: `acquire` called when document is hidden.
     *  - ACQUIRING: `acquire` called.
     *  - ERROR: `acquired` called when the api is not available.
     */
    RELEASED: 'released',
    /* Wake lock requested, waiting for browser.
     *
     * Can transition to:
     *  - ACQUIRED: success
     *  - ACQUIRING_WANT_RELEASE: `release` called while waiting
     *  - ERROR
     */
    ACQUIRING: 'acquiring',
    /* Wake lock requested, release called, still waiting for browser.
     *
     * Can transition to:
     *  - ACQUIRING: `acquire` called (but promise has not resolved yet)
     *  - RELEASED: success
     */
    ACQUIRING_WANT_RELEASE: 'releasing',
    /* Wake lock held.
     *
     * Can transition to:
     *  - AWAITING_VISIBLE: wakelock lost due to visibility change
     *  - RELEASED: success
     */
    ACQUIRED: 'acquired',
    /* Caller wants wakelock, but we can not get it due to visibility.
     *
     * Can transition to:
     *  - ACQUIRING: document is now visible, attempting to get wakelock.
     *  - RELEASED: when release is called.
     */
    AWAITING_VISIBLE: 'awaiting_visible',
    /* An error has occurred.
     *
     * Can transition to:
     *  - RELEASED: will happen immediately.
     */
    ERROR: 'error',
};

class TestOnlyWakeLockManagerStateChangeEvent extends Event {
    constructor(oldState, newState) {
        super("testOnlyStateChange");
        this.oldState = oldState;
        this.newState = newState;
    }
}

export default class WakeLockManager extends EventTarget {
    constructor() {
        super();

        this._state = _STATES.RELEASED;
        this._wakelock = null;

        this._eventHandlers = {
            wakelockAcquired: this._wakelockAcquired.bind(this),
            wakelockReleased: this._wakelockReleased.bind(this),
            documentVisibilityChange: this._documentVisibilityChange.bind(this),
        };
    }

    acquire() {
        switch (this._state) {
            case _STATES.ACQUIRING_WANT_RELEASE:
                // We are currently waiting to acquire the wakelock. While
                // waiting, `release()` was called. By transitioning back to
                // ACQUIRING, we will keep the lock after we receive it.
                this._transitionTo(_STATES.ACQUIRING);
                break;
            case _STATES.AWAITING_
Download .txt
gitextract_tymwg30v/

├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.md
│   │   ├── config.yml
│   │   └── feature_request.md
│   └── workflows/
│       ├── deploy.yml
│       ├── lint.yml
│       ├── test.yml
│       └── translate.yml
├── .gitignore
├── .gitmodules
├── AUTHORS
├── LICENSE.txt
├── README.md
├── app/
│   ├── error-handler.js
│   ├── images/
│   │   └── icons/
│   │       └── Makefile
│   ├── locale/
│   │   ├── README
│   │   ├── cs.json
│   │   ├── de.json
│   │   ├── el.json
│   │   ├── es.json
│   │   ├── fr.json
│   │   ├── hu.json
│   │   ├── it.json
│   │   ├── ja.json
│   │   ├── ko.json
│   │   ├── nl.json
│   │   ├── pl.json
│   │   ├── pt_BR.json
│   │   ├── ru.json
│   │   ├── sv.json
│   │   ├── tr.json
│   │   ├── uk.json
│   │   ├── zh_CN.json
│   │   └── zh_TW.json
│   ├── localization.js
│   ├── sounds/
│   │   ├── CREDITS
│   │   └── bell.oga
│   ├── styles/
│   │   ├── base.css
│   │   ├── constants.css
│   │   └── input.css
│   ├── ui.js
│   ├── wakelock.js
│   └── webutil.js
├── core/
│   ├── base64.js
│   ├── clipboard.js
│   ├── crypto/
│   │   ├── aes.js
│   │   ├── bigint.js
│   │   ├── crypto.js
│   │   ├── des.js
│   │   ├── dh.js
│   │   ├── md5.js
│   │   └── rsa.js
│   ├── decoders/
│   │   ├── copyrect.js
│   │   ├── h264.js
│   │   ├── hextile.js
│   │   ├── jpeg.js
│   │   ├── raw.js
│   │   ├── rre.js
│   │   ├── tight.js
│   │   ├── tightpng.js
│   │   ├── zlib.js
│   │   └── zrle.js
│   ├── deflator.js
│   ├── display.js
│   ├── encodings.js
│   ├── inflator.js
│   ├── input/
│   │   ├── domkeytable.js
│   │   ├── fixedkeys.js
│   │   ├── gesturehandler.js
│   │   ├── keyboard.js
│   │   ├── keysym.js
│   │   ├── keysymdef.js
│   │   ├── util.js
│   │   ├── vkeys.js
│   │   └── xtscancodes.js
│   ├── ra2.js
│   ├── rfb.js
│   ├── util/
│   │   ├── browser.js
│   │   ├── cursor.js
│   │   ├── element.js
│   │   ├── events.js
│   │   ├── eventtarget.js
│   │   ├── int.js
│   │   ├── logging.js
│   │   └── strings.js
│   └── websock.js
├── defaults.json
├── docs/
│   ├── API-internal.md
│   ├── API.md
│   ├── EMBEDDING.md
│   ├── LIBRARY.md
│   ├── LICENSE.BSD-2-Clause
│   ├── LICENSE.BSD-3-Clause
│   ├── LICENSE.MPL-2.0
│   ├── LICENSE.OFL-1.1
│   ├── flash_policy.txt
│   ├── links
│   ├── notes
│   ├── novnc_proxy.1
│   └── rfb_notes
├── eslint.config.mjs
├── karma.conf.cjs
├── mandatory.json
├── package.json
├── po/
│   ├── Makefile
│   ├── cs.po
│   ├── de.po
│   ├── el.po
│   ├── es.po
│   ├── fr.po
│   ├── hr.po
│   ├── hu.po
│   ├── it.po
│   ├── ja.po
│   ├── ko.po
│   ├── nl.po
│   ├── noVNC.pot
│   ├── pl.po
│   ├── po2js
│   ├── pt_BR.po
│   ├── ru.po
│   ├── sv.po
│   ├── tr.po
│   ├── uk.po
│   ├── xgettext-html
│   ├── zh_CN.po
│   └── zh_TW.po
├── snap/
│   ├── hooks/
│   │   └── configure
│   ├── local/
│   │   └── svc_wrapper.sh
│   └── snapcraft.yaml
├── tests/
│   ├── assertions.js
│   ├── fake.websocket.js
│   ├── playback-ui.js
│   ├── playback.js
│   ├── test.base64.js
│   ├── test.browser.js
│   ├── test.clipboard.js
│   ├── test.copyrect.js
│   ├── test.deflator.js
│   ├── test.display.js
│   ├── test.gesturehandler.js
│   ├── test.h264.js
│   ├── test.helper.js
│   ├── test.hextile.js
│   ├── test.inflator.js
│   ├── test.int.js
│   ├── test.jpeg.js
│   ├── test.keyboard.js
│   ├── test.localization.js
│   ├── test.raw.js
│   ├── test.rfb.js
│   ├── test.rre.js
│   ├── test.tight.js
│   ├── test.tightpng.js
│   ├── test.util.js
│   ├── test.wakelock.js
│   ├── test.websock.js
│   ├── test.webutil.js
│   ├── test.zlib.js
│   ├── test.zrle.js
│   └── vnc_playback.html
├── utils/
│   ├── README.md
│   ├── b64-to-binary.pl
│   ├── genkeysymdef.js
│   ├── novnc_proxy
│   ├── u2x11
│   └── validate
├── vendor/
│   └── pako/
│       ├── LICENSE
│       ├── README.md
│       └── lib/
│           ├── utils/
│           │   └── common.js
│           └── zlib/
│               ├── adler32.js
│               ├── constants.js
│               ├── crc32.js
│               ├── deflate.js
│               ├── gzheader.js
│               ├── inffast.js
│               ├── inflate.js
│               ├── inftrees.js
│               ├── messages.js
│               ├── trees.js
│               └── zstream.js
├── vnc.html
└── vnc_lite.html
Download .txt
SYMBOL INDEX (849 symbols across 79 files)

FILE: app/error-handler.js
  function handleError (line 10) | function handleError(event, err) {

FILE: app/localization.js
  class Localizer (line 13) | class Localizer {
    method constructor (line 14) | constructor() {
    method setup (line 23) | async setup(supportedLanguages, baseURL) {
    method _setupLanguage (line 31) | _setupLanguage(supportedLanguages) {
    method _setupDictionary (line 92) | async _setupDictionary(baseURL) {
    method get (line 114) | get(id) {
    method translateDOM (line 125) | translateDOM() {

FILE: app/ui.js
  constant PAGE_TITLE (line 22) | const PAGE_TITLE = "noVNC";
  constant LINGUAS (line 24) | const LINGUAS = ["cs", "de", "el", "es", "fr", "hr", "hu", "it", "ja", "...
  method start (line 52) | async start(options={}) {
  method initFullscreen (line 148) | initFullscreen() {
  method initSettings (line 162) | initSettings() {
  method setupSettingLabels (line 196) | setupSettingLabels() {
  method addControlbarHandlers (line 222) | addControlbarHandlers() {
  method addTouchSpecificHandlers (line 255) | addTouchSpecificHandlers() {
  method addExtraKeysHandlers (line 296) | addExtraKeysHandlers() {
  method addMachineHandlers (line 313) | addMachineHandlers() {
  method addConnectionControlHandlers (line 324) | addConnectionControlHandlers() {
  method addClipboardHandlers (line 340) | addClipboardHandlers() {
  method addSettingChangeHandler (line 349) | addSettingChangeHandler(name, changeFunc) {
  method addSettingsHandlers (line 357) | addSettingsHandlers() {
  method addFullscreenHandlers (line 388) | addFullscreenHandlers() {
  method updateVisualState (line 405) | updateVisualState(state) {
  method showStatus (line 471) | showStatus(text, statusType, time) {
  method hideStatus (line 527) | hideStatus() {
  method activateControlbar (line 532) | activateControlbar(event) {
  method idleControlbar (line 541) | idleControlbar() {
  method keepControlbar (line 553) | keepControlbar() {
  method openControlbar (line 557) | openControlbar() {
  method closeControlbar (line 562) | closeControlbar() {
  method toggleControlbar (line 569) | toggleControlbar() {
  method toggleControlbarSide (line 578) | toggleControlbarSide() {
  method showControlbarHint (line 604) | showControlbarHint(show, animate=true) {
  method dragControlbarHandle (line 620) | dragControlbarHandle(e) {
  method moveControlbarHandle (line 655) | moveControlbarHandle(viewportRelativeY) {
  method updateControlbarHandle (line 692) | updateControlbarHandle() {
  method controlbarHandleMouseUp (line 700) | controlbarHandleMouseUp(e) {
  method controlbarHandleMouseDown (line 715) | controlbarHandleMouseDown(e) {
  method toggleExpander (line 741) | toggleExpander(e) {
  method initSetting (line 756) | initSetting(name, defVal) {
  method forceSetting (line 777) | forceSetting(name, val) {
  method updateSetting (line 785) | updateSetting(name) {
  method saveSetting (line 810) | saveSetting(name) {
  method getSetting (line 826) | getSetting(name) {
  method disableSetting (line 843) | disableSetting(name) {
  method enableSetting (line 853) | enableSetting(name) {
  method closeAllPanels (line 869) | closeAllPanels() {
  method openSettingsPanel (line 882) | openSettingsPanel() {
  method closeSettingsPanel (line 906) | closeSettingsPanel() {
  method toggleSettingsPanel (line 913) | toggleSettingsPanel() {
  method openPowerPanel (line 928) | openPowerPanel() {
  method closePowerPanel (line 938) | closePowerPanel() {
  method togglePowerPanel (line 945) | togglePowerPanel() {
  method updatePowerButton (line 955) | updatePowerButton() {
  method openClipboardPanel (line 975) | openClipboardPanel() {
  method closeClipboardPanel (line 985) | closeClipboardPanel() {
  method toggleClipboardPanel (line 992) | toggleClipboardPanel() {
  method clipboardReceive (line 1001) | clipboardReceive(e) {
  method clipboardSend (line 1007) | clipboardSend() {
  method openConnectPanel (line 1020) | openConnectPanel() {
  method closeConnectPanel (line 1025) | closeConnectPanel() {
  method connect (line 1030) | connect(event, password) {
  method disconnect (line 1117) | disconnect() {
  method reconnect (line 1130) | reconnect() {
  method cancelReconnect (line 1141) | cancelReconnect() {
  method connectFinished (line 1153) | connectFinished(e) {
  method disconnectFinished (line 1172) | disconnectFinished(e) {
  method securityFailed (line 1213) | securityFailed(e) {
  method handleBeforeUnload (line 1227) | handleBeforeUnload(e) {
  method updateBeforeUnload (line 1237) | updateBeforeUnload() {
  method serverVerify (line 1251) | async serverVerify(e) {
  method approveServer (line 1264) | approveServer(e) {
  method rejectServer (line 1270) | rejectServer(e) {
  method credentials (line 1282) | credentials(e) {
  method setCredentials (line 1309) | setCredentials(e) {
  method toggleFullscreen (line 1333) | toggleFullscreen() {
  method updateFullscreenButton (line 1361) | updateFullscreenButton() {
  method applyResizeMode (line 1381) | applyResizeMode() {
  method updateViewClip (line 1397) | updateViewClip() {
  method toggleViewDrag (line 1439) | toggleViewDrag() {
  method updateViewDrag (line 1446) | updateViewDrag() {
  method updateQuality (line 1479) | updateQuality() {
  method updateCompression (line 1491) | updateCompression() {
  method showVirtualKeyboard (line 1503) | showVirtualKeyboard() {
  method hideVirtualKeyboard (line 1521) | hideVirtualKeyboard() {
  method toggleVirtualKeyboard (line 1531) | toggleVirtualKeyboard() {
  method onfocusVirtualKeyboard (line 1540) | onfocusVirtualKeyboard(event) {
  method onblurVirtualKeyboard (line 1548) | onblurVirtualKeyboard(event) {
  method keepVirtualKeyboard (line 1556) | keepVirtualKeyboard(event) {
  method keyboardinputReset (line 1584) | keyboardinputReset() {
  method keyEvent (line 1590) | keyEvent(keysym, code, down) {
  method keyInput (line 1600) | keyInput(event) {
  method openExtraKeys (line 1667) | openExtraKeys() {
  method closeExtraKeys (line 1677) | closeExtraKeys() {
  method toggleExtraKeys (line 1684) | toggleExtraKeys() {
  method sendEsc (line 1693) | sendEsc() {
  method sendTab (line 1697) | sendTab() {
  method toggleCtrl (line 1701) | toggleCtrl() {
  method toggleWindows (line 1712) | toggleWindows() {
  method toggleAlt (line 1723) | toggleAlt() {
  method sendCtrlAltDel (line 1734) | sendCtrlAltDel() {
  method sendKey (line 1741) | sendKey(keysym, code, down) {
  method updateViewOnly (line 1767) | updateViewOnly() {
  method updateClipboard (line 1791) | updateClipboard() {
  method updateShowDotCursor (line 1816) | updateShowDotCursor() {
  method updateLogging (line 1821) | updateLogging() {
  method updateDesktopName (line 1825) | updateDesktopName(e) {
  method updateRequestWakelock (line 1831) | updateRequestWakelock() {
  method bell (line 1841) | bell(e) {
  method addOption (line 1860) | addOption(selectbox, text, value) {

FILE: app/wakelock.js
  class TestOnlyWakeLockManagerStateChangeEvent (line 62) | class TestOnlyWakeLockManagerStateChangeEvent extends Event {
    method constructor (line 63) | constructor(oldState, newState) {
  class WakeLockManager (line 70) | class WakeLockManager extends EventTarget {
    method constructor (line 71) | constructor() {
    method acquire (line 84) | acquire() {
    method release (line 110) | release() {
    method _transitionTo (line 139) | _transitionTo(newState) {
    method _awaitVisible (line 146) | _awaitVisible() {
    method _acquireWakelockNow (line 151) | _acquireWakelockNow() {
    method _wakelockAcquired (line 169) | _wakelockAcquired(wakelock) {
    method _wakelockReleased (line 182) | _wakelockReleased(event) {
    method _documentVisibilityChange (line 192) | _documentVisibilityChange(event) {

FILE: app/webutil.js
  function initLogging (line 12) | function initLogging(level) {
  function getQueryVar (line 32) | function getQueryVar(name, defVal) {
  function getHashVar (line 46) | function getHashVar(name, defVal) {
  function getConfigVar (line 61) | function getConfigVar(name, defVal) {
  function createCookie (line 77) | function createCookie(name, value, days) {
  function readCookie (line 97) | function readCookie(name, defaultValue) {
  function eraseCookie (line 115) | function eraseCookie(name) {
  function initSettings (line 126) | function initSettings() {
  function setSetting (line 137) | function setSetting(name, value) {
  function writeSetting (line 142) | function writeSetting(name, value) {
  function readSetting (line 153) | function readSetting(name, defaultValue) {
  function eraseSetting (line 173) | function eraseSetting(name) {
  function logOnce (line 189) | function logOnce(msg, level = "warn") {
  function localStorageGet (line 210) | function localStorageGet(name) {
  function localStorageSet (line 225) | function localStorageSet(name, value) {
  function localStorageRemove (line 238) | function localStorageRemove(name) {

FILE: core/base64.js
  method encode (line 14) | encode(data) {
  method decode (line 59) | decode(data, offset = 0) {

FILE: core/clipboard.js
  class AsyncClipboard (line 10) | class AsyncClipboard {
    method constructor (line 11) | constructor(target) {
    method _ensureAvailable (line 27) | async _ensureAvailable() {
    method _handleFocus (line 38) | async _handleFocus(event) {
    method writeClipboard (line 50) | writeClipboard(text) {
    method grab (line 58) | grab() {
    method ungrab (line 68) | ungrab() {

FILE: core/crypto/aes.js
  class AESECBCipher (line 1) | class AESECBCipher {
    method constructor (line 2) | constructor() {
    method algorithm (line 6) | get algorithm() {
    method importKey (line 10) | static async importKey(key, _algorithm, extractable, keyUsages) {
    method _importKey (line 16) | async _importKey(key, extractable, keyUsages) {
    method encrypt (line 21) | async encrypt(_algorithm, plaintext) {
  class AESEAXCipher (line 38) | class AESEAXCipher {
    method constructor (line 39) | constructor() {
    method algorithm (line 49) | get algorithm() {
    method _encryptBlock (line 53) | async _encryptBlock(block) {
    method _initCMAC (line 61) | async _initCMAC() {
    method _encryptCTR (line 77) | async _encryptCTR(data, counter) {
    method _decryptCTR (line 86) | async _decryptCTR(data, counter) {
    method _computeCMAC (line 95) | async _computeCMAC(data, prefixBlock) {
    method importKey (line 125) | static async importKey(key, _algorithm, _extractable, _keyUsages) {
    method _importKey (line 131) | async _importKey(key) {
    method encrypt (line 140) | async encrypt(algorithm, message) {
    method decrypt (line 156) | async decrypt(algorithm, data) {

FILE: core/crypto/bigint.js
  function modPow (line 1) | function modPow(b, e, m) {
  function bigIntToU8Array (line 14) | function bigIntToU8Array(bigint, padLength=0) {
  function u8ArrayToBigInt (line 28) | function u8ArrayToBigInt(arr) {

FILE: core/crypto/crypto.js
  class LegacyCrypto (line 9) | class LegacyCrypto {
    method constructor (line 10) | constructor() {
    method encrypt (line 22) | encrypt(algorithm, key, data) {
    method decrypt (line 32) | decrypt(algorithm, key, data) {
    method importKey (line 42) | importKey(format, keyData, algorithm, extractable, keyUsages) {
    method generateKey (line 53) | generateKey(algorithm, extractable, keyUsages) {
    method exportKey (line 61) | exportKey(format, key) {
    method digest (line 71) | digest(algorithm, data) {
    method deriveBits (line 79) | deriveBits(algorithm, key, length) {

FILE: core/crypto/des.js
  constant PC2 (line 81) | const PC2 = [13,16,10,23, 0, 4, 2,27,14, 5,20, 9,22,18,11, 3,
  constant SP1 (line 89) | const SP1 = [c|e,z|z,a|z,c|f,c|d,a|f,z|d,a|z,z|e,c|e,c|f,z|e,b|f,c|d,b|z...
  constant SP2 (line 94) | const SP2 = [c|f,b|e,z|e,a|f,a|z,z|d,c|d,b|f,b|d,c|f,c|e,b|z,b|e,a|z,z|d...
  constant SP3 (line 99) | const SP3 = [z|f,c|e,z|z,c|d,b|e,z|z,a|f,b|e,a|d,b|d,b|d,a|z,c|f,a|d,c|z...
  constant SP4 (line 104) | const SP4 = [c|d,a|f,a|f,z|e,c|e,b|f,b|d,a|d,z|z,c|z,c|z,c|f,z|f,z|z,b|e...
  constant SP5 (line 109) | const SP5 = [z|d,a|f,a|e,c|d,z|e,z|d,b|z,a|e,b|f,z|e,a|d,b|f,c|d,c|e,z|f...
  constant SP6 (line 114) | const SP6 = [b|d,c|z,z|e,c|f,c|z,z|d,c|f,a|z,b|e,a|f,a|z,b|d,a|d,b|e,b|z...
  constant SP7 (line 119) | const SP7 = [a|z,c|d,b|f,z|z,z|e,b|f,a|f,c|e,c|f,a|z,z|z,b|d,z|d,b|z,c|d...
  constant SP8 (line 124) | const SP8 = [b|f,z|e,a|z,c|f,b|z,b|f,z|d,b|z,a|d,c|z,c|f,a|e,c|e,a|f,z|e...
  class DES (line 131) | class DES {
    method constructor (line 132) | constructor(password) {
    method enc8 (line 182) | enc8(text) {
  class DESECBCipher (line 263) | class DESECBCipher {
    method constructor (line 264) | constructor() {
    method algorithm (line 268) | get algorithm() {
    method importKey (line 272) | static importKey(key, _algorithm, _extractable, _keyUsages) {
    method _importKey (line 278) | _importKey(key, _extractable, _keyUsages) {
    method encrypt (line 282) | encrypt(_algorithm, plaintext) {
  class DESCBCCipher (line 295) | class DESCBCCipher {
    method constructor (line 296) | constructor() {
    method algorithm (line 300) | get algorithm() {
    method importKey (line 304) | static importKey(key, _algorithm, _extractable, _keyUsages) {
    method _importKey (line 310) | _importKey(key) {
    method encrypt (line 314) | encrypt(algorithm, plaintext) {

FILE: core/crypto/dh.js
  class DHPublicKey (line 3) | class DHPublicKey {
    method constructor (line 4) | constructor(key) {
    method algorithm (line 8) | get algorithm() {
    method exportKey (line 12) | exportKey() {
  class DHCipher (line 17) | class DHCipher {
    method constructor (line 18) | constructor() {
    method algorithm (line 26) | get algorithm() {
    method generateKey (line 30) | static generateKey(algorithm, _extractable) {
    method _generateKey (line 36) | _generateKey(algorithm) {
    method deriveBits (line 48) | deriveBits(algorithm, length) {

FILE: core/crypto/md5.js
  function MD5 (line 13) | async function MD5(d) {
  function M (line 21) | function M(d) {
  function X (line 29) | function X(d) {
  function V (line 36) | function V(d) {
  function Y (line 42) | function Y(d, g) {
  function cmn (line 55) | function cmn(d, g, m, f, r, i) {
  function ff (line 59) | function ff(d, g, m, f, r, i, n) {
  function gg (line 63) | function gg(d, g, m, f, r, i, n) {
  function hh (line 67) | function hh(d, g, m, f, r, i, n) {
  function ii (line 71) | function ii(d, g, m, f, r, i, n) {
  function add (line 75) | function add(d, g) {
  function rol (line 80) | function rol(d, g) {

FILE: core/crypto/rsa.js
  class RSACipher (line 4) | class RSACipher {
    method constructor (line 5) | constructor() {
    method algorithm (line 17) | get algorithm() {
    method _base64urlDecode (line 21) | _base64urlDecode(data) {
    method _padArray (line 27) | _padArray(arr, length) {
    method generateKey (line 33) | static async generateKey(algorithm, extractable, _keyUsages) {
    method _generateKey (line 39) | async _generateKey(algorithm, extractable) {
    method importKey (line 60) | static async importKey(key, _algorithm, extractable, keyUsages) {
    method _importKey (line 69) | async _importKey(key, extractable) {
    method encrypt (line 86) | async encrypt(_algorithm, message) {
    method decrypt (line 104) | async decrypt(_algorithm, message) {
    method exportKey (line 126) | async exportKey() {

FILE: core/decoders/copyrect.js
  class CopyRectDecoder (line 10) | class CopyRectDecoder {
    method decodeRect (line 11) | decodeRect(x, y, width, height, sock, display, depth) {

FILE: core/decoders/h264.js
  class H264Parser (line 12) | class H264Parser {
    method constructor (line 13) | constructor(data) {
    method _getStartSequenceLen (line 21) | _getStartSequenceLen(index) {
    method _indexOfNextNalUnit (line 32) | _indexOfNextNalUnit(index) {
    method _parseSps (line 42) | _parseSps(index) {
    method _parseNalUnit (line 48) | _parseNalUnit(index) {
    method parse (line 74) | parse() {
  class H264Context (line 112) | class H264Context {
    method constructor (line 113) | constructor(width, height) {
    method _handleFrame (line 124) | _handleFrame(frame) {
    method _handleError (line 144) | _handleError(e) {
    method _configureDecoder (line 148) | _configureDecoder(profileIdc, constraintSet, levelIdc) {
    method _preparePendingFrame (line 167) | _preparePendingFrame(timestamp) {
    method decode (line 184) | decode(payload) {
  class H264Decoder (line 241) | class H264Decoder {
    method constructor (line 242) | constructor() {
    method _contextId (line 247) | _contextId(x, y, width, height) {
    method _findOldestContextId (line 251) | _findOldestContextId() {
    method _createContext (line 263) | _createContext(x, y, width, height) {
    method _getContext (line 274) | _getContext(x, y, width, height) {
    method _resetContext (line 279) | _resetContext(x, y, width, height) {
    method _resetAllContexts (line 283) | _resetAllContexts() {
    method decodeRect (line 287) | decodeRect(x, y, width, height, sock, display, depth) {

FILE: core/decoders/hextile.js
  class HextileDecoder (line 12) | class HextileDecoder {
    method constructor (line 13) | constructor() {
    method decodeRect (line 19) | decodeRect(x, y, width, height, sock, display, depth) {
    method _startTile (line 135) | _startTile(x, y, width, height, color) {
    method _subTile (line 155) | _subTile(x, y, w, h, color) {
    method _finishTile (line 176) | _finishTile(display) {

FILE: core/decoders/jpeg.js
  class JPEGDecoder (line 10) | class JPEGDecoder {
    method constructor (line 11) | constructor() {
    method decodeRect (line 20) | decodeRect(x, y, width, height, sock, display, depth) {
    method _readSegment (line 89) | _readSegment(sock) {

FILE: core/decoders/raw.js
  class RawDecoder (line 10) | class RawDecoder {
    method constructor (line 11) | constructor() {
    method decodeRect (line 15) | decodeRect(x, y, width, height, sock, display, depth) {

FILE: core/decoders/rre.js
  class RREDecoder (line 10) | class RREDecoder {
    method constructor (line 11) | constructor() {
    method decodeRect (line 15) | decodeRect(x, y, width, height, sock, display, depth) {

FILE: core/decoders/tight.js
  class TightDecoder (line 14) | class TightDecoder {
    method constructor (line 15) | constructor() {
    method decodeRect (line 28) | decodeRect(x, y, width, height, sock, display, depth) {
    method _fillRect (line 74) | _fillRect(x, y, width, height, sock, display, depth) {
    method _jpegRect (line 85) | _jpegRect(x, y, width, height, sock, display, depth) {
    method _pngRect (line 96) | _pngRect(x, y, width, height, sock, display, depth) {
    method _basicRect (line 100) | _basicRect(ctl, x, y, width, height, sock, display, depth) {
    method _copyFilter (line 143) | _copyFilter(streamId, x, y, width, height, sock, display, depth) {
    method _paletteFilter (line 181) | _paletteFilter(streamId, x, y, width, height, sock, display, depth) {
    method _monoRect (line 239) | _monoRect(x, y, width, height, data, palette, display) {
    method _paletteRect (line 272) | _paletteRect(x, y, width, height, data, palette, display) {
    method _gradientFilter (line 287) | _gradientFilter(streamId, x, y, width, height, sock, display, depth) {
    method _readData (line 357) | _readData(sock) {
    method _getScratchBuffer (line 387) | _getScratchBuffer(size) {

FILE: core/decoders/tightpng.js
  class TightPNGDecoder (line 12) | class TightPNGDecoder extends TightDecoder {
    method _pngRect (line 13) | _pngRect(x, y, width, height, sock, display, depth) {
    method _basicRect (line 24) | _basicRect(ctl, x, y, width, height, sock, display, depth) {

FILE: core/decoders/zlib.js
  class ZlibDecoder (line 12) | class ZlibDecoder {
    method constructor (line 13) | constructor() {
    method decodeRect (line 18) | decodeRect(x, y, width, height, sock, display, depth) {

FILE: core/decoders/zrle.js
  constant ZRLE_TILE_WIDTH (line 12) | const ZRLE_TILE_WIDTH = 64;
  constant ZRLE_TILE_HEIGHT (line 13) | const ZRLE_TILE_HEIGHT = 64;
  class ZRLEDecoder (line 15) | class ZRLEDecoder {
    method constructor (line 16) | constructor() {
    method decodeRect (line 24) | decodeRect(x, y, width, height, sock, display, depth) {
    method _getBitsPerPixelInPalette (line 73) | _getBitsPerPixelInPalette(paletteSize) {
    method _readPixels (line 83) | _readPixels(pixels) {
    method _decodePaletteTile (line 95) | _decodePaletteTile(paletteSize, tileSize, tilew, tileh) {
    method _decodeRLETile (line 127) | _decodeRLETile(tileSize) {
    method _decodeRLEPaletteTile (line 144) | _decodeRLEPaletteTile(paletteSize, tileSize) {
    method _readRLELength (line 176) | _readRLELength() {

FILE: core/deflator.js
  class Deflator (line 13) | class Deflator {
    method constructor (line 14) | constructor() {
    method deflate (line 22) | deflate(inData) {

FILE: core/display.js
  class Display (line 13) | class Display {
    method constructor (line 14) | constructor(target) {
    method scale (line 68) | get scale() { return this._scale; }
    method scale (line 69) | set scale(scale) {
    method clipViewport (line 73) | get clipViewport() { return this._clipViewport; }
    method clipViewport (line 74) | set clipViewport(viewport) {
    method width (line 82) | get width() {
    method height (line 86) | get height() {
    method viewportChangePos (line 92) | viewportChangePos(deltaX, deltaY) {
    method viewportChangeSize (line 134) | viewportChangeSize(width, height) {
    method absX (line 175) | absX(x) {
    method absY (line 182) | absY(y) {
    method resize (line 189) | resize(width, height) {
    method getImageData (line 223) | getImageData() {
    method toDataURL (line 227) | toDataURL(type, encoderOptions) {
    method toBlob (line 231) | toBlob(callback, type, quality) {
    method _damage (line 236) | _damage(x, y, w, h) {
    method flip (line 253) | flip(fromQueue) {
    method pending (line 299) | pending() {
    method flush (line 303) | flush() {
    method fillRect (line 316) | fillRect(x, y, width, height, color, fromQueue) {
    method copyImage (line 333) | copyImage(oldX, oldY, newX, newY, w, h, fromQueue) {
    method imageRect (line 364) | imageRect(x, y, width, height, mime, arr) {
    method videoFrame (line 383) | videoFrame(x, y, width, height, frame) {
    method blitImage (line 394) | blitImage(x, y, width, height, arr, offset, fromQueue) {
    method drawImage (line 420) | drawImage(img, ...args) {
    method autoscale (line 432) | autoscale(containerWidth, containerHeight) {
    method _rescale (line 456) | _rescale(factor) {
    method _setFillColor (line 474) | _setFillColor(color) {
    method _renderQPush (line 482) | _renderQPush(action) {
    method _resumeRenderQ (line 491) | _resumeRenderQ() {
    method _scanRenderQ (line 498) | _scanRenderQ() {

FILE: core/encodings.js
  function encodingName (line 40) | function encodingName(num) {

FILE: core/inflator.js
  class Inflate (line 12) | class Inflate {
    method constructor (line 13) | constructor() {
    method setInput (line 21) | setInput(data) {
    method inflate (line 36) | inflate(expected) {
    method reset (line 62) | reset() {

FILE: core/input/domkeytable.js
  function addStandard (line 18) | function addStandard(key, standard) {
  function addLeftRight (line 24) | function addLeftRight(key, left, right) {
  function addNumpad (line 31) | function addNumpad(key, standard, numpad) {

FILE: core/input/gesturehandler.js
  constant GH_NOGESTURE (line 10) | const GH_NOGESTURE = 0;
  constant GH_ONETAP (line 11) | const GH_ONETAP    = 1;
  constant GH_TWOTAP (line 12) | const GH_TWOTAP    = 2;
  constant GH_THREETAP (line 13) | const GH_THREETAP  = 4;
  constant GH_DRAG (line 14) | const GH_DRAG      = 8;
  constant GH_LONGPRESS (line 15) | const GH_LONGPRESS = 16;
  constant GH_TWODRAG (line 16) | const GH_TWODRAG   = 32;
  constant GH_PINCH (line 17) | const GH_PINCH     = 64;
  constant GH_INITSTATE (line 19) | const GH_INITSTATE = 127;
  constant GH_MOVE_THRESHOLD (line 21) | const GH_MOVE_THRESHOLD = 50;
  constant GH_ANGLE_THRESHOLD (line 22) | const GH_ANGLE_THRESHOLD = 90;
  constant GH_MULTITOUCH_TIMEOUT (line 25) | const GH_MULTITOUCH_TIMEOUT = 250;
  constant GH_TAP_TIMEOUT (line 28) | const GH_TAP_TIMEOUT = 1000;
  constant GH_LONGPRESS_TIMEOUT (line 31) | const GH_LONGPRESS_TIMEOUT = 1000;
  constant GH_TWOTOUCH_TIMEOUT (line 34) | const GH_TWOTOUCH_TIMEOUT = 50;
  class GestureHandler (line 36) | class GestureHandler {
    method constructor (line 37) | constructor() {
    method attach (line 54) | attach(target) {
    method detach (line 68) | detach() {
    method _eventHandler (line 87) | _eventHandler(e) {
    method _touchStart (line 112) | _touchStart(id, x, y) {
    method _touchMove (line 167) | _touchMove(id, x, y) {
    method _touchEnd (line 252) | _touchEnd(id, x, y) {
    method _hasDetectedGesture (line 352) | _hasDetectedGesture() {
    method _startLongpressTimeout (line 373) | _startLongpressTimeout() {
    method _stopLongpressTimeout (line 379) | _stopLongpressTimeout() {
    method _longpressTimeout (line 384) | _longpressTimeout() {
    method _startTwoTouchTimeout (line 393) | _startTwoTouchTimeout() {
    method _stopTwoTouchTimeout (line 399) | _stopTwoTouchTimeout() {
    method _isTwoTouchTimeoutRunning (line 404) | _isTwoTouchTimeoutRunning() {
    method _twoTouchTimeout (line 408) | _twoTouchTimeout() {
    method _pushEvent (line 435) | _pushEvent(type) {
    method _stateToGesture (line 488) | _stateToGesture(state) {
    method _getPosition (line 509) | _getPosition() {
    method _getAverageMovement (line 530) | _getAverageMovement() {
    method _getAverageDistance (line 548) | _getAverageDistance() {

FILE: core/input/keyboard.js
  class Keyboard (line 17) | class Keyboard {
    method constructor (line 18) | constructor(target) {
    method _sendKeyEvent (line 39) | _sendKeyEvent(keysym, code, down, numlock = null, capslock = null) {
    method _getKeyCode (line 56) | _getKeyCode(e) {
    method _handleKeyDown (line 87) | _handleKeyDown(e) {
    method _handleKeyUp (line 214) | _handleKeyUp(e) {
    method _interruptAltGrSequence (line 248) | _interruptAltGrSequence() {
    method _allKeysUp (line 256) | _allKeysUp() {
    method grab (line 270) | grab() {
    method ungrab (line 282) | ungrab() {

FILE: core/input/keysymdef.js
  method lookup (line 673) | lookup(u) {

FILE: core/input/util.js
  function getKeycode (line 9) | function getKeycode(evt) {
  function getKey (line 68) | function getKey(evt) {
  function getKeysym (line 111) | function getKeysym(evt) {

FILE: core/ra2.js
  class RA2Cipher (line 5) | class RA2Cipher {
    method constructor (line 6) | constructor() {
    method setKey (line 11) | async setKey(key) {
    method makeMessage (line 16) | async makeMessage(message) {
    method receiveMessage (line 30) | async receiveMessage(length, encrypted) {
  class RSAAESAuthenticationState (line 42) | class RSAAESAuthenticationState extends EventTargetMixin {
    method constructor (line 43) | constructor(sock, getCredentials) {
    method _waitSockAsync (line 56) | _waitSockAsync(len) {
    method _waitApproveKeyAsync (line 74) | _waitApproveKeyAsync() {
    method _waitCredentialsAsync (line 81) | _waitCredentialsAsync(subtype) {
    method checkInternalEvents (line 107) | checkInternalEvents() {
    method approveServer (line 116) | approveServer() {
    method disconnect (line 123) | disconnect() {
    method negotiateRA2neAuthAsync (line 138) | async negotiateRA2neAuthAsync() {
    method hasStarted (line 305) | get hasStarted() {
    method hasStarted (line 309) | set hasStarted(s) {

FILE: core/rfb.js
  constant DISCONNECT_TIMEOUT (line 43) | const DISCONNECT_TIMEOUT = 3;
  constant DEFAULT_BACKGROUND (line 44) | const DEFAULT_BACKGROUND = 'rgb(40, 40, 40)';
  constant MOUSE_MOVE_DELAY (line 47) | const MOUSE_MOVE_DELAY = 17;
  constant WHEEL_STEP (line 50) | const WHEEL_STEP = 50;
  constant WHEEL_LINE_HEIGHT (line 51) | const WHEEL_LINE_HEIGHT = 19;
  constant GESTURE_ZOOMSENS (line 54) | const GESTURE_ZOOMSENS = 75;
  constant GESTURE_SCRLSENS (line 55) | const GESTURE_SCRLSENS = 50;
  constant DOUBLE_TAP_TIMEOUT (line 56) | const DOUBLE_TAP_TIMEOUT = 1000;
  constant DOUBLE_TAP_THRESHOLD (line 57) | const DOUBLE_TAP_THRESHOLD = 50;
  class RFB (line 91) | class RFB extends EventTargetMixin {
    method constructor (line 92) | constructor(target, urlOrChannel, options) {
    method viewOnly (line 315) | get viewOnly() { return this._viewOnly; }
    method viewOnly (line 316) | set viewOnly(viewOnly) {
    method capabilities (line 331) | get capabilities() { return this._capabilities; }
    method clippingViewport (line 333) | get clippingViewport() { return this._clippingViewport; }
    method _setClippingViewport (line 334) | _setClippingViewport(on) {
    method touchButton (line 343) | get touchButton() { return 0; }
    method touchButton (line 344) | set touchButton(button) { Log.Warn("Using old API!"); }
    method clipViewport (line 346) | get clipViewport() { return this._clipViewport; }
    method clipViewport (line 347) | set clipViewport(viewport) {
    method scaleViewport (line 352) | get scaleViewport() { return this._scaleViewport; }
    method scaleViewport (line 353) | set scaleViewport(scale) {
    method resizeSession (line 366) | get resizeSession() { return this._resizeSession; }
    method resizeSession (line 367) | set resizeSession(resize) {
    method showDotCursor (line 374) | get showDotCursor() { return this._showDotCursor; }
    method showDotCursor (line 375) | set showDotCursor(show) {
    method background (line 380) | get background() { return this._screen.style.background; }
    method background (line 381) | set background(cssValue) { this._screen.style.background = cssValue; }
    method qualityLevel (line 383) | get qualityLevel() {
    method qualityLevel (line 386) | set qualityLevel(qualityLevel) {
    method compressionLevel (line 403) | get compressionLevel() {
    method compressionLevel (line 406) | set compressionLevel(compressionLevel) {
    method disconnect (line 425) | disconnect() {
    method approveServer (line 435) | approveServer() {
    method sendCredentials (line 441) | sendCredentials(creds) {
    method sendCtrlAltDel (line 446) | sendCtrlAltDel() {
    method machineShutdown (line 458) | machineShutdown() {
    method machineReboot (line 462) | machineReboot() {
    method machineReset (line 466) | machineReset() {
    method sendKey (line 472) | sendKey(keysym, code, down) {
    method focus (line 499) | focus(options) {
    method blur (line 503) | blur() {
    method clipboardPasteFrom (line 507) | clipboardPasteFrom(text) {
    method getImageData (line 543) | getImageData() {
    method toDataURL (line 547) | toDataURL(type, encoderOptions) {
    method toBlob (line 551) | toBlob(callback, type, quality) {
    method _connect (line 557) | _connect() {
    method _disconnect (line 615) | _disconnect() {
    method _socketOpen (line 648) | _socketOpen() {
    method _socketClose (line 659) | _socketClose(e) {
    method _socketError (line 696) | _socketError(e) {
    method _focusCanvas (line 700) | _focusCanvas(event) {
    method _setDesktopName (line 708) | _setDesktopName(name) {
    method _saveExpectedClientSize (line 715) | _saveExpectedClientSize() {
    method _currentClientSize (line 720) | _currentClientSize() {
    method _clientHasExpectedSize (line 724) | _clientHasExpectedSize() {
    method _handleResize (line 731) | _handleResize() {
    method _updateClip (line 751) | _updateClip() {
    method _updateScale (line 783) | _updateScale() {
    method _requestRemoteResize (line 795) | _requestRemoteResize() {
    method _screenSize (line 838) | _screenSize() {
    method _fixScrollbars (line 843) | _fixScrollbars() {
    method _updateConnectionState (line 863) | _updateConnectionState(state) {
    method _fail (line 962) | _fail(details) {
    method _setCapability (line 986) | _setCapability(cap, val) {
    method _handleMessage (line 992) | _handleMessage() {
    method _handleKeyEvent (line 1028) | _handleKeyEvent(keysym, code, down, numlock, capslock) {
    method _convertButtonMask (line 1060) | static _convertButtonMask(buttons) {
    method _handleMouse (line 1090) | _handleMouse(ev) {
    method _handleMouseButton (line 1179) | _handleMouseButton(x, y, bmask) {
    method _handleMouseMove (line 1187) | _handleMouseMove(x, y) {
    method _handleDelayedMouseMove (line 1206) | _handleDelayedMouseMove() {
    method _sendMouse (line 1213) | _sendMouse(x, y, mask) {
    method _handleWheel (line 1233) | _handleWheel(ev) {
    method _fakeMouseMove (line 1290) | _fakeMouseMove(ev, elementX, elementY) {
    method _handleTapEvent (line 1295) | _handleTapEvent(ev, bmask) {
    method _handleGesture (line 1326) | _handleGesture(ev) {
    method _flushMouseMoveTimer (line 1493) | _flushMouseMoveTimer(x, y) {
    method _negotiateProtocolVersion (line 1503) | _negotiateProtocolVersion() {
    method _isSupportedSecurityType (line 1556) | _isSupportedSecurityType(type) {
    method _negotiateSecurity (line 1572) | _negotiateSecurity() {
    method _handleSecurityReason (line 1623) | _handleSecurityReason() {
    method _negotiateXvpAuth (line 1655) | _negotiateXvpAuth() {
    method _negotiateVeNCryptAuth (line 1678) | _negotiateVeNCryptAuth() {
    method _negotiatePlainAuth (line 1760) | _negotiatePlainAuth() {
    method _negotiateStdVNCAuth (line 1782) | _negotiateStdVNCAuth() {
    method _negotiateARDAuth (line 1801) | _negotiateARDAuth() {
    method _negotiateARDAuthAsync (line 1842) | async _negotiateARDAuthAsync(keyLength, serverPublicKey, clientKey) {
    method _negotiateTightUnixAuth (line 1871) | _negotiateTightUnixAuth() {
    method _negotiateTightTunnels (line 1890) | _negotiateTightTunnels(numTunnels) {
    method _negotiateTightAuth (line 1932) | _negotiateTightAuth() {
    method _handleRSAAESCredentialsRequired (line 1998) | _handleRSAAESCredentialsRequired(event) {
    method _handleRSAAESServerVerification (line 2002) | _handleRSAAESServerVerification(event) {
    method _negotiateRA2neAuth (line 2006) | _negotiateRA2neAuth() {
    method _negotiateMSLogonIIAuth (line 2036) | _negotiateMSLogonIIAuth() {
    method _negotiateAuthentication (line 2079) | _negotiateAuthentication() {
    method _handleSecurityResult (line 2122) | _handleSecurityResult() {
    method _negotiateServerInit (line 2147) | _negotiateServerInit() {
    method _sendEncodings (line 2238) | _sendEncodings() {
    method _initMsg (line 2290) | _initMsg() {
    method _resumeAuthentication (line 2324) | _resumeAuthentication() {
    method _handleSetColourMapMsg (line 2330) | _handleSetColourMapMsg() {
    method _writeClipboard (line 2336) | _writeClipboard(text) {
    method _handleServerCutText (line 2345) | _handleServerCutText() {
    method _handleServerFenceMsg (line 2509) | _handleServerFenceMsg() {
    method _handleXvpMsg (line 2551) | _handleXvpMsg() {
    method _normalMsg (line 2574) | _normalMsg() {
    method _framebufferUpdate (line 2632) | _framebufferUpdate() {
    method _handleRect (line 2681) | _handleRect() {
    method _handleVMwareCursor (line 2719) | _handleVMwareCursor() {
    method _handleCursor (line 2835) | _handleCursor() {
    method _handleDesktopName (line 2872) | _handleDesktopName() {
    method _handleLedEvent (line 2891) | _handleLedEvent() {
    method _handleExtendedDesktopSize (line 2906) | _handleExtendedDesktopSize() {
    method _handleDataRect (line 2991) | _handleDataRect() {
    method _updateContinuousUpdates (line 3010) | _updateContinuousUpdates() {
    method _resize (line 3018) | _resize(width, height) {
    method _xvpOp (line 3034) | _xvpOp(ver, op) {
    method _updateCursor (line 3040) | _updateCursor(rgba, hotx, hoty, w, h) {
    method _shouldShowDotCursor (line 3048) | _shouldShowDotCursor() {
    method _refreshCursor (line 3070) | _refreshCursor() {
    method genDES (line 3082) | static genDES(password, challenge) {
  method keyEvent (line 3092) | keyEvent(sock, keysym, down) {
  method QEMUExtendedKeyEvent (line 3103) | QEMUExtendedKeyEvent(sock, keysym, down, keycode) {
  method pointerEvent (line 3127) | pointerEvent(sock, x, y, mask) {
  method extendedPointerEvent (line 3142) | extendedPointerEvent(sock, x, y, mask) {
  method _buildExtendedClipboardFlags (line 3164) | _buildExtendedClipboardFlags(actions, formats) {
  method extendedClipboardProvide (line 3185) | extendedClipboardProvide(sock, formats, inData) {
  method extendedClipboardNotify (line 3223) | extendedClipboardNotify(sock, formats) {
  method extendedClipboardRequest (line 3229) | extendedClipboardRequest(sock, formats) {
  method extendedClipboardCaps (line 3235) | extendedClipboardCaps(sock, actions, formats) {
  method clientCutText (line 3258) | clientCutText(sock, data, extended = false) {
  method setDesktopSize (line 3277) | setDesktopSize(sock, width, height, id, flags) {
  method clientFence (line 3300) | clientFence(sock, flags, payload) {
  method enableContinuousUpdates (line 3315) | enableContinuousUpdates(sock, enable, x, y, width, height) {
  method pixelFormat (line 3328) | pixelFormat(sock, depth, trueColor) {
  method clientEncodings (line 3367) | clientEncodings(sock, encodings) {
  method fbUpdateRequest (line 3380) | fbUpdateRequest(sock, incremental, x, y, w, h) {
  method xvpOp (line 3396) | xvpOp(sock, ver, op) {

FILE: core/util/browser.js
  function browserAsyncClipboardSupport (line 23) | async function browserAsyncClipboardSupport() {
  function _checkWebCodecsH264DecodeSupport (line 109) | async function _checkWebCodecsH264DecodeSupport() {
  function isMac (line 196) | function isMac() {
  function isWindows (line 200) | function isWindows() {
  function isIOS (line 204) | function isIOS() {
  function isAndroid (line 210) | function isAndroid() {
  function isChromeOS (line 215) | function isChromeOS() {
  function isSafari (line 222) | function isSafari() {
  function isFirefox (line 229) | function isFirefox() {
  function isChrome (line 234) | function isChrome() {
  function isChromium (line 241) | function isChromium() {
  function isOpera (line 245) | function isOpera() {
  function isEdge (line 249) | function isEdge() {
  function isGecko (line 255) | function isGecko() {
  function isWebKit (line 259) | function isWebKit() {
  function isBlink (line 264) | function isBlink() {

FILE: core/util/cursor.js
  class Cursor (line 11) | class Cursor {
    method constructor (line 12) | constructor() {
    method attach (line 40) | attach(target) {
    method detach (line 60) | detach() {
    method change (line 80) | change(rgba, hotx, hoty, w, h) {
    method clear (line 108) | clear() {
    method move (line 120) | move(clientX, clientY) {
    method _handleMouseOver (line 139) | _handleMouseOver(event) {
    method _handleMouseLeave (line 146) | _handleMouseLeave(event) {
    method _handleMouseMove (line 151) | _handleMouseMove(event) {
    method _handleMouseUp (line 160) | _handleMouseUp(event) {
    method _showCursor (line 191) | _showCursor() {
    method _hideCursor (line 197) | _hideCursor() {
    method _shouldShowCursor (line 206) | _shouldShowCursor(target) {
    method _updateVisibility (line 227) | _updateVisibility(target) {
    method _updatePosition (line 240) | _updatePosition() {
    method _captureIsActive (line 245) | _captureIsActive() {

FILE: core/util/element.js
  function clientToElement (line 13) | function clientToElement(x, y, elem) {

FILE: core/util/events.js
  function getPointerEvent (line 13) | function getPointerEvent(e) {
  function stopEvent (line 17) | function stopEvent(e) {
  function _captureProxy (line 26) | function _captureProxy(e) {
  function _capturedElemChanged (line 56) | function _capturedElemChanged() {
  function setCapture (line 63) | function setCapture(target) {
  function releaseCapture (line 110) | function releaseCapture() {

FILE: core/util/eventtarget.js
  class EventTargetMixin (line 9) | class EventTargetMixin {
    method constructor (line 10) | constructor() {
    method addEventListener (line 14) | addEventListener(type, callback) {
    method removeEventListener (line 21) | removeEventListener(type, callback) {
    method dispatchEvent (line 27) | dispatchEvent(event) {

FILE: core/util/int.js
  function toUnsigned32bit (line 9) | function toUnsigned32bit(toConvert) {
  function toSigned32bit (line 13) | function toSigned32bit(toConvert) {

FILE: core/util/logging.js
  function initLogging (line 20) | function initLogging(level) {
  function getLogging (line 49) | function getLogging() {

FILE: core/util/strings.js
  function decodeUTF8 (line 10) | function decodeUTF8(utf8string, allowLatin1=false) {
  function encodeUTF8 (line 26) | function encodeUTF8(DOMString) {

FILE: core/websock.js
  constant MAX_RQ_GROW_SIZE (line 20) | const MAX_RQ_GROW_SIZE = 40 * 1024 * 1024;
  class Websock (line 50) | class Websock {
    method constructor (line 51) | constructor() {
    method readyState (line 75) | get readyState() {
    method rQpeek8 (line 98) | rQpeek8() {
    method rQskipBytes (line 102) | rQskipBytes(bytes) {
    method rQshift8 (line 106) | rQshift8() {
    method rQshift16 (line 110) | rQshift16() {
    method rQshift32 (line 114) | rQshift32() {
    method _rQshift (line 119) | _rQshift(bytes) {
    method rQlen (line 127) | rQlen() {
    method rQshiftStr (line 131) | rQshiftStr(len) {
    method rQshiftBytes (line 141) | rQshiftBytes(len, copy=true) {
    method rQshiftTo (line 150) | rQshiftTo(target, len) {
    method rQpeekBytes (line 156) | rQpeekBytes(len, copy=true) {
    method rQwait (line 167) | rQwait(msg, num, goback) {
    method sQpush8 (line 182) | sQpush8(num) {
    method sQpush16 (line 187) | sQpush16(num) {
    method sQpush32 (line 193) | sQpush32(num) {
    method sQpushString (line 201) | sQpushString(str) {
    method sQpushBytes (line 206) | sQpushBytes(bytes) {
    method flush (line 221) | flush() {
    method _sQensureSpace (line 228) | _sQensureSpace(bytes) {
    method off (line 235) | off(evt) {
    method on (line 239) | on(evt, handler) {
    method _allocateBuffers (line 243) | _allocateBuffers() {
    method init (line 248) | init() {
    method open (line 254) | open(uri, protocols) {
    method attach (line 258) | attach(rawChannel) {
    method close (line 297) | close() {
    method _expandCompactRQ (line 316) | _expandCompactRQ(minFit) {
    method _recvMessage (line 349) | _recvMessage(e) {

FILE: karma.conf.cjs
  function SafariBrowser (line 4) | function SafariBrowser(id, baseBrowserDecorator, args) {

FILE: tests/assertions.js
  function _equal (line 12) | function _equal(a, b) {

FILE: tests/fake.websocket.js
  class FakeWebSocket (line 3) | class FakeWebSocket {
    method constructor (line 4) | constructor(uri, protocols) {
    method close (line 27) | close(code, reason) {
    method send (line 34) | send(data) {
    method _getSentData (line 53) | _getSentData() {
    method _open (line 59) | _open() {
    method _receiveData (line 66) | _receiveData(data) {

FILE: tests/playback-ui.js
  function message (line 9) | function message(str) {
  function loadFile (line 15) | function loadFile() {
  function enableUI (line 33) | function enableUI() {
  class IterationPlayer (line 103) | class IterationPlayer {
    method constructor (line 104) | constructor(iterations, frames) {
    method start (line 121) | start(realtime) {
    method _nextIteration (line 130) | _nextIteration() {
    method _finish (line 145) | _finish() {
    method _iterationFinish (line 156) | _iterationFinish(duration) {
    method _disconnected (line 166) | _disconnected(clean, frame) {
  function start (line 180) | function start() {

FILE: tests/playback.js
  class FakeWebSocket (line 45) | class FakeWebSocket {
    method constructor (line 46) | constructor() {
    method send (line 56) | send() {
    method close (line 59) | close() {
  class RecordingPlayer (line 63) | class RecordingPlayer {
    method constructor (line 64) | constructor(frames, disconnected) {
    method run (line 82) | run(realtime, trafficManagement) {
    method _queueNextPacket (line 103) | _queueNextPacket() {
    method _doPacket (line 131) | _doPacket() {
    method _finish (line 149) | _finish() {
    method _handleDisconnect (line 161) | _handleDisconnect(evt) {
    method _handleCredentials (line 166) | _handleCredentials(evt) {

FILE: tests/test.clipboard.js
  function stubClipboardPermissions (line 29) | function stubClipboardPermissions(state) {
  function nextTick (line 38) | function nextTick() {

FILE: tests/test.copyrect.js
  function testDecodeRect (line 8) | function testDecodeRect(decoder, x, y, width, height, data, display, dep...

FILE: tests/test.deflator.js
  function _inflator (line 5) | function _inflator(compText, expected) {

FILE: tests/test.display.js
  function makeImageCanvas (line 14) | function makeImageCanvas(inputData, width, height) {
  function makeImagePng (line 24) | function makeImagePng(inputData, width, height) {

FILE: tests/test.gesturehandler.js
  class DummyTarget (line 5) | class DummyTarget extends EventTargetMixin {
  function touchStart (line 41) | function touchStart(id, x, y) {
  function touchMove (line 54) | function touchMove(id, x, y) {
  function touchEnd (line 67) | function touchEnd(id) {

FILE: tests/test.h264.js
  function createSolidColorFrameBuffer (line 33) | function createSolidColorFrameBuffer(color, width, height) {
  function makeMessageHeader (line 52) | function makeMessageHeader(length, resetContext, resetAllContexts) {
  function wrapRectData (line 77) | function wrapRectData(data, resetContext, resetAllContexts) {
  function testDecodeRect (line 82) | function testDecodeRect(decoder, x, y, width, height, data, display, dep...
  function almost (line 106) | function almost(a, b) {

FILE: tests/test.hextile.js
  function testDecodeRect (line 8) | function testDecodeRect(decoder, x, y, width, height, data, display, dep...
  function push32 (line 32) | function push32(arr, num) {

FILE: tests/test.inflator.js
  function _deflator (line 5) | function _deflator(data) {

FILE: tests/test.jpeg.js
  function testDecodeRect (line 8) | function testDecodeRect(decoder, x, y, width, height, data, display, dep...
  function almost (line 147) | function almost(a, b) {
  function almost (line 282) | function almost(a, b) {

FILE: tests/test.keyboard.js
  function keyevent (line 8) | function keyevent(typeArg, KeyboardEventInit) {

FILE: tests/test.raw.js
  function testDecodeRect (line 8) | function testDecodeRect(decoder, x, y, width, height, data, display, dep...

FILE: tests/test.rfb.js
  function push8 (line 13) | function push8(arr, num) {
  function push16 (line 18) | function push16(arr, num) {
  function push32 (line 24) | function push32(arr, num) {
  function pushString (line 32) | function pushString(arr, string) {
  function deflateWithSize (line 39) | function deflateWithSize(data) {
  class FakeResizeObserver (line 79) | class FakeResizeObserver {
    method constructor (line 80) | constructor(handler) {
    method disconnect (line 84) | disconnect() {}
    method observe (line 85) | observe(target, options) {}
    method unobserve (line 86) | unobserve(target) {}
  function makeRFB (line 150) | function makeRFB(url, options) {
  function elementToClient (line 161) | function elementToClient(x, y, client) {
  function sendMouseMoveEvent (line 179) | function sendMouseMoveEvent(x, y, buttons, client) {
  function sendMouseButtonEvent (line 192) | function sendMouseButtonEvent(x, y, down, buttons, client) {
  function gestureStart (line 205) | function gestureStart(gestureType, x, y, client,
  function gestureMove (line 217) | function gestureMove(gestureType, x, y, client,
  function gestureEnd (line 229) | function gestureEnd(gestureType, x, y, client) {
  function sendFbuMsg (line 236) | function sendFbuMsg(rectInfo, rectData, client, rectCnt) {
  function sendExtendedDesktopSize (line 260) | function sendExtendedDesktopSize(client, reason, result, width, height, ...
  function sendVer (line 1395) | function sendVer(ver, client) {
  function sendSecurity (line 1405) | function sendSecurity(type, cl) {
  function fakeGetRandomValues (line 1652) | function fakeGetRandomValues(arr) {
  function fakeGeneratekey (line 1694) | async function fakeGeneratekey() {
  function fakeGetRandomValues (line 2098) | function fakeGetRandomValues(arr) {
  function fakeGetRandomValues (line 2190) | function fakeGetRandomValues(arr) {
  function sendNumStrPairs (line 2327) | function sendNumStrPairs(pairs, client) {
  function sendServerInit (line 2659) | function sendServerInit(opts, client) {
  function makeScreenData (line 2901) | function makeScreenData(nrOfScreens) {
  function sendLedStateUpdate (line 3304) | function sendLedStateUpdate(state) {
  function sendWheelEvent (line 4179) | function sendWheelEvent(x, y, dx, dy, mode=0, buttons=0) {

FILE: tests/test.rre.js
  function testDecodeRect (line 8) | function testDecodeRect(decoder, x, y, width, height, data, display, dep...
  function push16 (line 32) | function push16(arr, num) {
  function push32 (line 37) | function push32(arr, num) {

FILE: tests/test.tight.js
  function testDecodeRect (line 8) | function testDecodeRect(decoder, x, y, width, height, data, display, dep...
  function almost (line 472) | function almost(a, b) {

FILE: tests/test.tightpng.js
  function testDecodeRect (line 8) | function testDecodeRect(decoder, x, y, width, height, data, display, dep...
  function almost (line 135) | function almost(a, b) {

FILE: tests/test.wakelock.js
  class FakeWakeLockSentinal (line 5) | class FakeWakeLockSentinal extends EventTarget {
    method constructor (line 6) | constructor() {
    method release (line 11) | async release() {
  function waitForStateTransition (line 20) | function waitForStateTransition(wakelockManager, newState) {

FILE: tests/test.webutil.js
  method get (line 172) | get(cb) { cb(settings); }
  method set (line 173) | set() {}
  method remove (line 174) | remove() {}

FILE: tests/test.zlib.js
  function testDecodeRect (line 8) | function testDecodeRect(decoder, x, y, width, height, data, display, dep...

FILE: tests/test.zrle.js
  function testDecodeRect (line 8) | function testDecodeRect(decoder, x, y, width, height, data, display, dep...

FILE: utils/genkeysymdef.js
  function toHex (line 80) | function toHex(num) {

FILE: vendor/pako/lib/utils/common.js
  function shrinkBuf (line 2) | function shrinkBuf (buf, size) {
  function arraySet (line 10) | function arraySet (dest, src, src_offs, len, dest_offs) {
  function flattenChunks (line 22) | function flattenChunks (chunks) {

FILE: vendor/pako/lib/zlib/adler32.js
  function adler32 (line 5) | function adler32(adler, buf, len, pos) {

FILE: vendor/pako/lib/zlib/crc32.js
  function makeTable (line 7) | function makeTable() {
  function crc32 (line 25) | function crc32(crc, buf, len, pos) {

FILE: vendor/pako/lib/zlib/deflate.js
  constant Z_NO_FLUSH (line 12) | const Z_NO_FLUSH      = 0;
  constant Z_PARTIAL_FLUSH (line 13) | const Z_PARTIAL_FLUSH = 1;
  constant Z_FULL_FLUSH (line 15) | const Z_FULL_FLUSH    = 3;
  constant Z_FINISH (line 16) | const Z_FINISH        = 4;
  constant Z_BLOCK (line 17) | const Z_BLOCK         = 5;
  constant Z_OK (line 24) | const Z_OK            = 0;
  constant Z_STREAM_END (line 25) | const Z_STREAM_END    = 1;
  constant Z_STREAM_ERROR (line 28) | const Z_STREAM_ERROR  = -2;
  constant Z_DATA_ERROR (line 29) | const Z_DATA_ERROR    = -3;
  constant Z_BUF_ERROR (line 31) | const Z_BUF_ERROR     = -5;
  constant Z_DEFAULT_COMPRESSION (line 39) | const Z_DEFAULT_COMPRESSION = -1;
  constant Z_FILTERED (line 42) | const Z_FILTERED            = 1;
  constant Z_HUFFMAN_ONLY (line 43) | const Z_HUFFMAN_ONLY        = 2;
  constant Z_RLE (line 44) | const Z_RLE                 = 3;
  constant Z_FIXED (line 45) | const Z_FIXED               = 4;
  constant Z_DEFAULT_STRATEGY (line 46) | const Z_DEFAULT_STRATEGY    = 0;
  constant Z_UNKNOWN (line 52) | const Z_UNKNOWN             = 2;
  constant Z_DEFLATED (line 56) | const Z_DEFLATED  = 8;
  function err (line 104) | function err(strm, errorCode) {
  function rank (line 109) | function rank(f) {
  function zero (line 113) | function zero(buf) { var len = buf.length; while (--len >= 0) { buf[len]...
  function flush_pending (line 122) | function flush_pending(strm) {
  function flush_block_only (line 144) | function flush_block_only(s, last) {
  function put_byte (line 151) | function put_byte(s, b) {
  function putShortMSB (line 161) | function putShortMSB(s, b) {
  function read_buf (line 176) | function read_buf(strm, buf, start, size) {
  function longest_match (line 210) | function longest_match(s, cur_match) {
  function fill_window (line 323) | function fill_window(s) {
  function deflate_stored (line 479) | function deflate_stored(s, flush) {
  function deflate_fast (line 577) | function deflate_fast(s, flush) {
  function deflate_slow (line 705) | function deflate_slow(s, flush) {
  function deflate_rle (line 867) | function deflate_rle(s, flush) {
  function deflate_huff (line 962) | function deflate_huff(s, flush) {
  function Config (line 1019) | function Config(good_length, max_lazy, nice_length, max_chain, func) {
  function lm_init (line 1048) | function lm_init(s) {
  function DeflateState (line 1071) | function DeflateState() {
  function deflateResetKeep (line 1260) | function deflateResetKeep(strm) {
  function deflateReset (line 1289) | function deflateReset(strm) {
  function deflateSetHeader (line 1298) | function deflateSetHeader(strm, head) {
  function deflateInit2 (line 1306) | function deflateInit2(strm, level, method, windowBits, memLevel, strateg...
  function deflateInit (line 1384) | function deflateInit(strm, level) {
  function deflate (line 1389) | function deflate(strm, flush) {
  function deflateEnd (line 1723) | function deflateEnd(strm) {
  function deflateSetDictionary (line 1752) | function deflateSetDictionary(strm, dictionary) {

FILE: vendor/pako/lib/zlib/gzheader.js
  function GZheader (line 1) | function GZheader() {

FILE: vendor/pako/lib/zlib/inffast.js
  function inflate_fast (line 40) | function inflate_fast(strm, start) {

FILE: vendor/pako/lib/zlib/inflate.js
  constant Z_FINISH (line 20) | const Z_FINISH        = 4;
  constant Z_BLOCK (line 21) | const Z_BLOCK         = 5;
  constant Z_TREES (line 22) | const Z_TREES         = 6;
  constant Z_OK (line 28) | const Z_OK            = 0;
  constant Z_STREAM_END (line 29) | const Z_STREAM_END    = 1;
  constant Z_NEED_DICT (line 30) | const Z_NEED_DICT     = 2;
  constant Z_STREAM_ERROR (line 32) | const Z_STREAM_ERROR  = -2;
  constant Z_DATA_ERROR (line 33) | const Z_DATA_ERROR    = -3;
  constant Z_MEM_ERROR (line 34) | const Z_MEM_ERROR     = -4;
  constant Z_BUF_ERROR (line 35) | const Z_BUF_ERROR     = -5;
  constant Z_DEFLATED (line 39) | const Z_DEFLATED  = 8;
  function zswap32 (line 92) | function zswap32(q) {
  function InflateState (line 100) | function InflateState() {
  function inflateResetKeep (line 158) | function inflateResetKeep(strm) {
  function inflateReset (line 185) | function inflateReset(strm) {
  function inflateReset2 (line 197) | function inflateReset2(strm, windowBits) {
  function inflateInit2 (line 231) | function inflateInit2(strm, windowBits) {
  function inflateInit (line 251) | function inflateInit(strm) {
  function fixedtables (line 270) | function fixedtables(state) {
  function updatewindow (line 318) | function updatewindow(strm, src, end, copy) {
  function inflate (line 360) | function inflate(strm, flush) {
  function inflateEnd (line 1452) | function inflateEnd(strm) {
  function inflateGetHeader (line 1466) | function inflateGetHeader(strm, head) {
  function inflateSetDictionary (line 1480) | function inflateSetDictionary(strm, dictionary) {

FILE: vendor/pako/lib/zlib/inftrees.js
  function inflate_table (line 34) | function inflate_table(type, lens, lens_index, codes, table, table_index...

FILE: vendor/pako/lib/zlib/trees.js
  function zero (line 22) | function zero(buf) { var len = buf.length; while (--len >= 0) { buf[len]...
  function StaticTreeDesc (line 145) | function StaticTreeDesc(static_tree, extra_bits, extra_base, elems, max_...
  function TreeDesc (line 163) | function TreeDesc(dyn_tree, stat_desc) {
  function d_code (line 171) | function d_code(dist) {
  function put_short (line 180) | function put_short(s, w) {
  function send_bits (line 192) | function send_bits(s, value, length) {
  function send_code (line 205) | function send_code(s, c, tree) {
  function bi_reverse (line 215) | function bi_reverse(code, len) {
  function bi_flush (line 229) | function bi_flush(s) {
  function gen_bitlen (line 253) | function gen_bitlen(s, desc)
  function gen_codes (line 350) | function gen_codes(tree, max_code, bl_count)
  function tr_static_init (line 388) | function tr_static_init() {
  function init_block (line 492) | function init_block(s) {
  function bi_windup (line 509) | function bi_windup(s)
  function copy_block (line 525) | function copy_block(s, buf, len, header)
  function smaller (line 548) | function smaller(tree, n, m, depth) {
  function pqdownheap (line 561) | function pqdownheap(s, tree, k)
  function compress_block (line 594) | function compress_block(s, ltree, dtree)
  function build_tree (line 654) | function build_tree(s, desc)
  function scan_tree (line 750) | function scan_tree(s, tree, max_code)
  function send_tree (line 816) | function send_tree(s, tree, max_code)
  function build_bl_tree (line 887) | function build_bl_tree(s) {
  function send_all_trees (line 923) | function send_all_trees(s, lcodes, dcodes, blcodes)
  function detect_data_type (line 963) | function detect_data_type(s) {
  function _tr_init (line 1001) | function _tr_init(s)
  function _tr_stored_block (line 1024) | function _tr_stored_block(s, buf, stored_len, last)
  function _tr_align (line 1039) | function _tr_align(s) {
  function _tr_flush_block (line 1050) | function _tr_flush_block(s, buf, stored_len, last)
  function _tr_tally (line 1137) | function _tr_tally(s, dist, lc)

FILE: vendor/pako/lib/zlib/zstream.js
  function ZStream (line 1) | function ZStream() {
Condensed preview — 182 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,632K chars).
[
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "chars": 819,
    "preview": "---\nname: Bug report\nabout: Create a report to help us improve\n\n---\n\n**Describe the bug**\nA clear and concise descriptio"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "chars": 190,
    "preview": "blank_issues_enabled: false\ncontact_links:\n  - name: Question or discussion\n    url: https://groups.google.com/forum/?fr"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "chars": 560,
    "preview": "---\nname: Feature request\nabout: Suggest an idea for this project\n\n---\n\n**Is your feature request related to a problem? "
  },
  {
    "path": ".github/workflows/deploy.yml",
    "chars": 3229,
    "preview": "name: Publish\n\non:\n  push:\n  pull_request:\n  release:\n    types: [published]\n\njobs:\n  npm:\n    runs-on: ubuntu-latest\n  "
  },
  {
    "path": ".github/workflows/lint.yml",
    "chars": 449,
    "preview": "name: Lint\n\non: [push, pull_request]\n\njobs:\n  eslint:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checko"
  },
  {
    "path": ".github/workflows/test.yml",
    "chars": 604,
    "preview": "name: Test\n\non: [push, pull_request]\n\njobs:\n  test:\n    strategy:\n      matrix:\n        os:\n          - ubuntu-latest\n  "
  },
  {
    "path": ".github/workflows/translate.yml",
    "chars": 339,
    "preview": "name: Translate\n\non: [push, pull_request]\n\njobs:\n  translate:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: action"
  },
  {
    "path": ".gitignore",
    "chars": 117,
    "preview": "*.pyc\n*.o\ntests/data_*.js\nutils/rebind.so\nutils/websockify\n/node_modules\n/build\n/lib\nrecordings\n*.swp\n*~\nnoVNC-*.tgz\n"
  },
  {
    "path": ".gitmodules",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "AUTHORS",
    "chars": 358,
    "preview": "maintainers:\n- Samuel Mannehed for Cendio AB (@samhed)\n- Pierre Ossman for Cendio AB (@CendioOssman)\nmaintainersEmeritus"
  },
  {
    "path": "LICENSE.txt",
    "chars": 2163,
    "preview": "noVNC is Copyright (C) 2022 The noVNC authors\n(./AUTHORS)\n\nThe noVNC core library files are licensed under the MPL 2.0 ("
  },
  {
    "path": "README.md",
    "chars": 8650,
    "preview": "## noVNC: HTML VNC client library and application\n\n[![Test Status](https://github.com/novnc/noVNC/workflows/Test/badge.s"
  },
  {
    "path": "app/error-handler.js",
    "chars": 2675,
    "preview": "/*\n * noVNC: HTML5 VNC client\n * Copyright (C) 2019 The noVNC authors\n * Licensed under MPL 2.0 (see LICENSE.txt)\n *\n * "
  },
  {
    "path": "app/images/icons/Makefile",
    "chars": 1331,
    "preview": "BROWSER_SIZES := 16 24 32 48 64\n#ANDROID_SIZES := 72 96 144 192\n# FIXME: The ICO is limited to 8 icons due to a Chrome b"
  },
  {
    "path": "app/locale/README",
    "chars": 92,
    "preview": "DO NOT MODIFY THE FILES IN THIS FOLDER, THEY ARE AUTOMATICALLY GENERATED FROM THE PO-FILES.\n"
  },
  {
    "path": "app/locale/cs.json",
    "chars": 2764,
    "preview": "{\n    \"Connecting...\": \"Připojení...\",\n    \"Disconnecting...\": \"Odpojení...\",\n    \"Reconnecting...\": \"Obnova připojení.."
  },
  {
    "path": "app/locale/de.json",
    "chars": 3210,
    "preview": "{\n    \"Connecting...\": \"Verbinden...\",\n    \"Disconnecting...\": \"Verbindung trennen...\",\n    \"Reconnecting...\": \"Verbindu"
  },
  {
    "path": "app/locale/el.json",
    "chars": 4724,
    "preview": "{\n    \"HTTPS is required for full functionality\": \"Το HTTPS είναι απαιτούμενο για πλήρη λειτουργικότητα\",\n    \"Connectin"
  },
  {
    "path": "app/locale/es.json",
    "chars": 2666,
    "preview": "{\n    \"Connecting...\": \"Conectando...\",\n    \"Connected (encrypted) to \": \"Conectado (con encriptación) a\",\n    \"Connecte"
  },
  {
    "path": "app/locale/fr.json",
    "chars": 3851,
    "preview": "{\n    \"Running without HTTPS is not recommended, crashes or other issues are likely.\": \"Lancer sans HTTPS n'est pas reco"
  },
  {
    "path": "app/locale/hu.json",
    "chars": 3731,
    "preview": "{\n    \"Running without HTTPS is not recommended, crashes or other issues are likely.\": \"HTTPS nélkül futtatni nem ajánlo"
  },
  {
    "path": "app/locale/it.json",
    "chars": 2770,
    "preview": "{\n    \"Connecting...\": \"Connessione in corso...\",\n    \"Disconnecting...\": \"Disconnessione...\",\n    \"Reconnecting...\": \"R"
  },
  {
    "path": "app/locale/ja.json",
    "chars": 2975,
    "preview": "{\n    \"Running without HTTPS is not recommended, crashes or other issues are likely.\": \"HTTPS接続なしで実行することは推奨されません。クラッシュした"
  },
  {
    "path": "app/locale/ko.json",
    "chars": 2245,
    "preview": "{\n    \"Connecting...\": \"연결중...\",\n    \"Disconnecting...\": \"연결 해제중...\",\n    \"Reconnecting...\": \"재연결중...\",\n    \"Internal er"
  },
  {
    "path": "app/locale/nl.json",
    "chars": 4591,
    "preview": "{\n    \"Running without HTTPS is not recommended, crashes or other issues are likely.\": \"Het is niet aan te raden om zond"
  },
  {
    "path": "app/locale/pl.json",
    "chars": 3276,
    "preview": "{\n    \"Connecting...\": \"Łączenie...\",\n    \"Disconnecting...\": \"Rozłączanie...\",\n    \"Reconnecting...\": \"Łączenie...\",\n  "
  },
  {
    "path": "app/locale/pt_BR.json",
    "chars": 2856,
    "preview": "{\n    \"Connecting...\": \"Conectando...\",\n    \"Disconnecting...\": \"Desconectando...\",\n    \"Reconnecting...\": \"Reconectando"
  },
  {
    "path": "app/locale/ru.json",
    "chars": 2836,
    "preview": "{\n    \"Connecting...\": \"Подключение...\",\n    \"Disconnecting...\": \"Отключение...\",\n    \"Reconnecting...\": \"Переподключени"
  },
  {
    "path": "app/locale/sv.json",
    "chars": 3676,
    "preview": "{\n    \"Running without HTTPS is not recommended, crashes or other issues are likely.\": \"Det är ej rekommenderat att köra"
  },
  {
    "path": "app/locale/tr.json",
    "chars": 2705,
    "preview": "{\n    \"Connecting...\": \"Bağlanıyor...\",\n    \"Disconnecting...\": \"Bağlantı kesiliyor...\",\n    \"Reconnecting...\": \"Yeniden"
  },
  {
    "path": "app/locale/uk.json",
    "chars": 3663,
    "preview": "{\n    \"Running without HTTPS is not recommended, crashes or other issues are likely.\": \"Робота без HTTPS не рекомендуєть"
  },
  {
    "path": "app/locale/zh_CN.json",
    "chars": 3097,
    "preview": "{\n    \"Running without HTTPS is not recommended, crashes or other issues are likely.\": \"不建议在没有 HTTPS 的情况下运行,可能会出现崩溃或出现其他"
  },
  {
    "path": "app/locale/zh_TW.json",
    "chars": 2070,
    "preview": "{\n    \"Connecting...\": \"連線中...\",\n    \"Disconnecting...\": \"正在中斷連線...\",\n    \"Reconnecting...\": \"重新連線中...\",\n    \"Internal e"
  },
  {
    "path": "app/localization.js",
    "chars": 6677,
    "preview": "/*\n * noVNC: HTML5 VNC client\n * Copyright (C) 2018 The noVNC authors\n * Licensed under MPL 2.0 (see LICENSE.txt)\n *\n * "
  },
  {
    "path": "app/sounds/CREDITS",
    "chars": 157,
    "preview": "bell\n        Copyright: Dr. Richard Boulanger et al\n        URL: http://www.archive.org/details/Berklee44v12\n        Lic"
  },
  {
    "path": "app/styles/base.css",
    "chars": 19823,
    "preview": "/*\n * noVNC base CSS\n * Copyright (C) 2019 The noVNC authors\n * noVNC is licensed under the MPL 2.0 (see LICENSE.txt)\n *"
  },
  {
    "path": "app/styles/constants.css",
    "chars": 776,
    "preview": "/*\n * noVNC general CSS constant variables\n * Copyright (C) 2025 The noVNC authors\n * noVNC is licensed under the MPL 2."
  },
  {
    "path": "app/styles/input.css",
    "chars": 20948,
    "preview": "/*\n * noVNC general input element CSS\n * Copyright (C) 2025 The noVNC authors\n * noVNC is licensed under the MPL 2.0 (se"
  },
  {
    "path": "app/ui.js",
    "chars": 64306,
    "preview": "/*\n * noVNC: HTML5 VNC client\n * Copyright (C) 2019 The noVNC authors\n * Licensed under MPL 2.0 (see LICENSE.txt)\n *\n * "
  },
  {
    "path": "app/wakelock.js",
    "chars": 7186,
    "preview": "/*\n * noVNC: HTML5 VNC client\n * Copyright (C) 2025 The noVNC authors\n * Licensed under MPL 2.0 or any later version (se"
  },
  {
    "path": "app/webutil.js",
    "chars": 6662,
    "preview": "/*\n * noVNC: HTML5 VNC client\n * Copyright (C) 2019 The noVNC authors\n * Licensed under MPL 2.0 (see LICENSE.txt)\n *\n * "
  },
  {
    "path": "core/base64.js",
    "chars": 4182,
    "preview": "/* This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not "
  },
  {
    "path": "core/clipboard.js",
    "chars": 1954,
    "preview": "/*\n * noVNC: HTML5 VNC client\n * Copyright (c) 2025 The noVNC authors\n * Licensed under MPL 2.0 or any later version (se"
  },
  {
    "path": "core/crypto/aes.js",
    "chars": 5965,
    "preview": "export class AESECBCipher {\n    constructor() {\n        this._key = null;\n    }\n\n    get algorithm() {\n        return { "
  },
  {
    "path": "core/crypto/bigint.js",
    "chars": 825,
    "preview": "export function modPow(b, e, m) {\n    let r = 1n;\n    b = b % m;\n    while (e > 0n) {\n        if ((e & 1n) === 1n) {\n   "
  },
  {
    "path": "core/crypto/crypto.js",
    "chars": 3042,
    "preview": "import { AESECBCipher, AESEAXCipher } from \"./aes.js\";\nimport { DESCBCCipher, DESECBCipher } from \"./des.js\";\nimport { R"
  },
  {
    "path": "core/crypto/des.js",
    "chars": 12704,
    "preview": "/*\n * Ported from Flashlight VNC ActionScript implementation:\n *     http://www.wizhelp.com/flashlight-vnc/\n *\n * Full a"
  },
  {
    "path": "core/crypto/dh.js",
    "chars": 1612,
    "preview": "import { modPow, bigIntToU8Array, u8ArrayToBigInt } from \"./bigint.js\";\n\nclass DHPublicKey {\n    constructor(key) {\n    "
  },
  {
    "path": "core/crypto/md5.js",
    "chars": 4686,
    "preview": "/*\n * noVNC: HTML5 VNC client\n * Copyright (C) 2021 The noVNC authors\n * Licensed under MPL 2.0 (see LICENSE.txt)\n *\n * "
  },
  {
    "path": "core/crypto/rsa.js",
    "chars": 4493,
    "preview": "import Base64 from \"../base64.js\";\nimport { modPow, bigIntToU8Array, u8ArrayToBigInt } from \"./bigint.js\";\n\nexport class"
  },
  {
    "path": "core/decoders/copyrect.js",
    "chars": 616,
    "preview": "/*\n * noVNC: HTML5 VNC client\n * Copyright (C) 2019 The noVNC authors\n * Licensed under MPL 2.0 (see LICENSE.txt)\n *\n * "
  },
  {
    "path": "core/decoders/h264.js",
    "chars": 9322,
    "preview": "/*\n * noVNC: HTML5 VNC client\n * Copyright (C) 2024 The noVNC authors\n * Licensed under MPL 2.0 (see LICENSE.txt)\n *\n * "
  },
  {
    "path": "core/decoders/hextile.js",
    "chars": 6060,
    "preview": "/*\n * noVNC: HTML5 VNC client\n * Copyright (C) 2019 The noVNC authors\n * Licensed under MPL 2.0 (see LICENSE.txt)\n *\n * "
  },
  {
    "path": "core/decoders/jpeg.js",
    "chars": 4557,
    "preview": "/*\n * noVNC: HTML5 VNC client\n * Copyright (C) 2019 The noVNC authors\n * Licensed under MPL 2.0 (see LICENSE.txt)\n *\n * "
  },
  {
    "path": "core/decoders/raw.js",
    "chars": 1622,
    "preview": "/*\n * noVNC: HTML5 VNC client\n * Copyright (C) 2019 The noVNC authors\n * Licensed under MPL 2.0 (see LICENSE.txt)\n *\n * "
  },
  {
    "path": "core/decoders/rre.js",
    "chars": 1114,
    "preview": "/*\n * noVNC: HTML5 VNC client\n * Copyright (C) 2019 The noVNC authors\n * Licensed under MPL 2.0 (see LICENSE.txt)\n *\n * "
  },
  {
    "path": "core/decoders/tight.js",
    "chars": 11982,
    "preview": "/*\n * noVNC: HTML5 VNC client\n * Copyright (C) 2019 The noVNC authors\n * (c) 2012 Michael Tinglof, Joe Balaz, Les Piech "
  },
  {
    "path": "core/decoders/tightpng.js",
    "chars": 687,
    "preview": "/*\n * noVNC: HTML5 VNC client\n * Copyright (C) 2019 The noVNC authors\n * Licensed under MPL 2.0 (see LICENSE.txt)\n *\n * "
  },
  {
    "path": "core/decoders/zlib.js",
    "chars": 1211,
    "preview": "/*\n * noVNC: HTML5 VNC client\n * Copyright (C) 2024 The noVNC authors\n * Licensed under MPL 2.0 (see LICENSE.txt)\n *\n * "
  },
  {
    "path": "core/decoders/zrle.js",
    "chars": 6480,
    "preview": "/*\n * noVNC: HTML5 VNC client\n * Copyright (C) 2021 The noVNC authors\n * Licensed under MPL 2.0 (see LICENSE.txt)\n *\n * "
  },
  {
    "path": "core/deflator.js",
    "chars": 2589,
    "preview": "/*\n * noVNC: HTML5 VNC client\n * Copyright (C) 2020 The noVNC authors\n * Licensed under MPL 2.0 (see LICENSE.txt)\n *\n * "
  },
  {
    "path": "core/display.js",
    "chars": 18387,
    "preview": "/*\n * noVNC: HTML5 VNC client\n * Copyright (C) 2019 The noVNC authors\n * Licensed under MPL 2.0 (see LICENSE.txt)\n *\n * "
  },
  {
    "path": "core/encodings.js",
    "chars": 1819,
    "preview": "/*\n * noVNC: HTML5 VNC client\n * Copyright (C) 2019 The noVNC authors\n * Licensed under MPL 2.0 (see LICENSE.txt)\n *\n * "
  },
  {
    "path": "core/inflator.js",
    "chars": 1913,
    "preview": "/*\n * noVNC: HTML5 VNC client\n * Copyright (C) 2020 The noVNC authors\n * Licensed under MPL 2.0 (see LICENSE.txt)\n *\n * "
  },
  {
    "path": "core/input/domkeytable.js",
    "chars": 11478,
    "preview": "/*\n * noVNC: HTML5 VNC client\n * Copyright (C) 2018 The noVNC authors\n * Licensed under MPL 2.0 or any later version (se"
  },
  {
    "path": "core/input/fixedkeys.js",
    "chars": 3804,
    "preview": "/*\n * noVNC: HTML5 VNC client\n * Copyright (C) 2018 The noVNC authors\n * Licensed under MPL 2.0 or any later version (se"
  },
  {
    "path": "core/input/gesturehandler.js",
    "chars": 18260,
    "preview": "/*\n * noVNC: HTML5 VNC client\n * Copyright (C) 2020 The noVNC authors\n * Licensed under MPL 2.0 (see LICENSE.txt)\n *\n * "
  },
  {
    "path": "core/input/keyboard.js",
    "chars": 10849,
    "preview": "/*\n * noVNC: HTML5 VNC client\n * Copyright (C) 2019 The noVNC authors\n * Licensed under MPL 2.0 or any later version (se"
  },
  {
    "path": "core/input/keysym.js",
    "chars": 34609,
    "preview": "/* eslint-disable key-spacing */\n\nexport default {\n    XK_VoidSymbol:                  0xffffff, /* Void symbol */\n\n    "
  },
  {
    "path": "core/input/keysymdef.js",
    "chars": 25374,
    "preview": "/*\n * Mapping from Unicode codepoints to X11/RFB keysyms\n *\n * This file was automatically generated from keysymdef.h\n *"
  },
  {
    "path": "core/input/util.js",
    "chars": 6069,
    "preview": "import KeyTable from \"./keysym.js\";\nimport keysyms from \"./keysymdef.js\";\nimport vkeys from \"./vkeys.js\";\nimport fixedke"
  },
  {
    "path": "core/input/vkeys.js",
    "chars": 2507,
    "preview": "/*\n * noVNC: HTML5 VNC client\n * Copyright (C) 2018 The noVNC authors\n * Licensed under MPL 2.0 or any later version (se"
  },
  {
    "path": "core/input/xtscancodes.js",
    "chars": 14400,
    "preview": "/*\n * This file is auto-generated from keymaps.csv\n * Database checksum sha256(76d68c10e97d37fe2ea459e210125ae41796253fb"
  },
  {
    "path": "core/ra2.js",
    "chars": 12474,
    "preview": "import { encodeUTF8 } from './util/strings.js';\nimport EventTargetMixin from './util/eventtarget.js';\nimport legacyCrypt"
  },
  {
    "path": "core/rfb.js",
    "chars": 122792,
    "preview": "/*\n * noVNC: HTML5 VNC client\n * Copyright (C) 2020 The noVNC authors\n * Licensed under MPL 2.0 (see LICENSE.txt)\n *\n * "
  },
  {
    "path": "core/util/browser.js",
    "chars": 9166,
    "preview": "/*\n * noVNC: HTML5 VNC client\n * Copyright (C) 2019 The noVNC authors\n * Licensed under MPL 2.0 (see LICENSE.txt)\n *\n * "
  },
  {
    "path": "core/util/cursor.js",
    "chars": 8347,
    "preview": "/*\n * noVNC: HTML5 VNC client\n * Copyright (C) 2019 The noVNC authors\n * Licensed under MPL 2.0 or any later version (se"
  },
  {
    "path": "core/util/element.js",
    "chars": 729,
    "preview": "/*\n * noVNC: HTML5 VNC client\n * Copyright (C) 2020 The noVNC authors\n * Licensed under MPL 2.0 (see LICENSE.txt)\n *\n * "
  },
  {
    "path": "core/util/events.js",
    "chars": 4327,
    "preview": "/*\n * noVNC: HTML5 VNC client\n * Copyright (C) 2018 The noVNC authors\n * Licensed under MPL 2.0 (see LICENSE.txt)\n *\n * "
  },
  {
    "path": "core/util/eventtarget.js",
    "chars": 896,
    "preview": "/*\n * noVNC: HTML5 VNC client\n * Copyright (C) 2019 The noVNC authors\n * Licensed under MPL 2.0 (see LICENSE.txt)\n *\n * "
  },
  {
    "path": "core/util/int.js",
    "chars": 326,
    "preview": "/*\n * noVNC: HTML5 VNC client\n * Copyright (C) 2020 The noVNC authors\n * Licensed under MPL 2.0 (see LICENSE.txt)\n *\n * "
  },
  {
    "path": "core/util/logging.js",
    "chars": 1351,
    "preview": "/*\n * noVNC: HTML5 VNC client\n * Copyright (C) 2019 The noVNC authors\n * Licensed under MPL 2.0 (see LICENSE.txt)\n *\n * "
  },
  {
    "path": "core/util/strings.js",
    "chars": 745,
    "preview": "/*\n * noVNC: HTML5 VNC client\n * Copyright (C) 2019 The noVNC authors\n * Licensed under MPL 2.0 (see LICENSE.txt)\n *\n * "
  },
  {
    "path": "core/websock.js",
    "chars": 11258,
    "preview": "/*\n * Websock: high-performance buffering wrapper\n * Copyright (C) 2019 The noVNC authors\n * Licensed under MPL 2.0 (see"
  },
  {
    "path": "defaults.json",
    "chars": 3,
    "preview": "{}\n"
  },
  {
    "path": "docs/API-internal.md",
    "chars": 4579,
    "preview": "# 1. Internal modules\n\nThe noVNC client is composed of several internal modules that handle\nrendering, input, networking"
  },
  {
    "path": "docs/API.md",
    "chars": 15849,
    "preview": "# noVNC API\n\nThe interface of the noVNC client consists of a single RFB object that\nis instantiated once per connection."
  },
  {
    "path": "docs/EMBEDDING.md",
    "chars": 4474,
    "preview": "# Embedding and deploying noVNC application\n\nThis document describes how to embed and deploy the noVNC application, whic"
  },
  {
    "path": "docs/LIBRARY.md",
    "chars": 1093,
    "preview": "# Using the noVNC JavaScript library\n\nThis document describes how to make use of the noVNC JavaScript library for\nintegr"
  },
  {
    "path": "docs/LICENSE.BSD-2-Clause",
    "chars": 1304,
    "preview": "Copyright (c) <year>, <copyright holder>\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with o"
  },
  {
    "path": "docs/LICENSE.BSD-3-Clause",
    "chars": 1507,
    "preview": "Copyright (c) <year>, <copyright holder>\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with o"
  },
  {
    "path": "docs/LICENSE.MPL-2.0",
    "chars": 16726,
    "preview": "Mozilla Public License Version 2.0\n==================================\n\n1. Definitions\n--------------\n\n1.1. \"Contributor\""
  },
  {
    "path": "docs/LICENSE.OFL-1.1",
    "chars": 4302,
    "preview": "This Font Software is licensed under the SIL Open Font License, Version 1.1.\nThis license is copied below, and is also a"
  },
  {
    "path": "docs/flash_policy.txt",
    "chars": 197,
    "preview": "Manual setup:\n\nDATA=\"echo \\'<cross-domain-policy><allow-access-from domain=\\\\\\\"*\\\\\\\" to-ports=\\\\\\\"*\\\\\\\" /></cross-domain"
  },
  {
    "path": "docs/links",
    "chars": 2268,
    "preview": "New tight PNG protocol:\n    http://wiki.qemu.org/VNC_Tight_PNG\n    http://xf.iksaif.net/blog/index.php?post/2010/06/14/Q"
  },
  {
    "path": "docs/notes",
    "chars": 146,
    "preview": "Rebuilding inflator.js\n\n- Download pako from npm\n- Install browserify using npm\n- browserify core/inflator.mod.js -o cor"
  },
  {
    "path": "docs/novnc_proxy.1",
    "chars": 1451,
    "preview": ".TH novnc_proxy 1  \"June 25, 2020\" \"version 1.2.0\" \"USER COMMANDS\"\n\n.SH NAME\nnovnc_proxy - noVNC proxy server\n.SH SYNOPS"
  },
  {
    "path": "docs/rfb_notes",
    "chars": 3473,
    "preview": "5.1.1 ProtocolVersion: 12, 12 bytes\n\n    - Sent by server, max supported\n        12 ascii - \"RFB 003.008\\n\"\n    - Respon"
  },
  {
    "path": "eslint.config.mjs",
    "chars": 3797,
    "preview": "import globals from \"globals\";\nimport { defineConfig } from \"eslint/config\";\nimport js from \"@eslint/js\";\n\nexport defaul"
  },
  {
    "path": "karma.conf.cjs",
    "chars": 2737,
    "preview": "// Karma configuration\n\n// The Safari launcher is broken, so construct our own\nfunction SafariBrowser(id, baseBrowserDec"
  },
  {
    "path": "mandatory.json",
    "chars": 3,
    "preview": "{}\n"
  },
  {
    "path": "package.json",
    "chars": 1764,
    "preview": "{\n  \"name\": \"@novnc/novnc\",\n  \"version\": \"1.6.0\",\n  \"description\": \"An HTML5 VNC client\",\n  \"type\": \"module\",\n  \"files\":"
  },
  {
    "path": "po/Makefile",
    "chars": 967,
    "preview": "all:\n.PHONY: update-po update-js update-pot\n.PHONY: FORCE\n\nLINGUAS := cs de el es fr hr hu it ja ko nl pl pt_BR ru sv tr"
  },
  {
    "path": "po/cs.po",
    "chars": 5366,
    "preview": "# Czech translations for noVNC package.\n# Copyright (C) 2018 The noVNC authors\n# This file is distributed under the same"
  },
  {
    "path": "po/de.po",
    "chars": 5889,
    "preview": "# German translations for noVNC package\n# German translation for noVNC.\n# Copyright (C) 2018 The noVNC authors\n# This fi"
  },
  {
    "path": "po/el.po",
    "chars": 7841,
    "preview": "# Greek translations for noVNC package.\n# Copyright (C) 2018 The noVNC authors\n# This file is distributed under the same"
  },
  {
    "path": "po/es.po",
    "chars": 5309,
    "preview": "# Spanish translations for noVNC package\n# Traducciones al español para el paquete noVNC.\n# Copyright (C) 2018 The noVNC"
  },
  {
    "path": "po/fr.po",
    "chars": 6806,
    "preview": "# French translations for noVNC package\n# Traductions françaises du paquet noVNC.\n# Copyright (C) 2021 The noVNC authors"
  },
  {
    "path": "po/hr.po",
    "chars": 6501,
    "preview": "# Croatian translations for noVNC package\n# Hrvatski prijevod za noVNC paket\n# Copyright (C) 2025 The noVNC authors\n# Th"
  },
  {
    "path": "po/hu.po",
    "chars": 6501,
    "preview": "# Hungarian translations for noVNC package.\n# Copyright (C) 2025 The noVNC authors\n# This file is distributed under the "
  },
  {
    "path": "po/it.po",
    "chars": 5491,
    "preview": "# Italian translations for noVNC\n# Traduzione italiana di noVNC\n# Copyright (C) 2022 The noVNC authors\n# This file is di"
  },
  {
    "path": "po/ja.po",
    "chars": 5847,
    "preview": "# Japanese translations for noVNC package\n# noVNC パッケージに対する日訳\n# Copyright (C) 2019-2024 The noVNC authors\n# This file is"
  },
  {
    "path": "po/ko.po",
    "chars": 4768,
    "preview": "# SOME DESCRIPTIVE TITLE.\n# Copyright (C) 2018 The noVNC authors\n# This file is distributed under the same license as th"
  },
  {
    "path": "po/nl.po",
    "chars": 7854,
    "preview": "# Dutch translations for noVNC package\n# Nederlandse vertalingen voor het pakket noVNC.\n# Copyright (C) 2018 The noVNC a"
  },
  {
    "path": "po/noVNC.pot",
    "chars": 5032,
    "preview": "# SOME DESCRIPTIVE TITLE.\n# Copyright (C) YEAR The noVNC authors\n# This file is distributed under the same license as th"
  },
  {
    "path": "po/pl.po",
    "chars": 6099,
    "preview": "# Polish translations for noVNC package.\n# Copyright (C) 2018 The noVNC authors\n# This file is distributed under the sam"
  },
  {
    "path": "po/po2js",
    "chars": 1400,
    "preview": "#!/usr/bin/env node\n/*\n * ps2js: gettext .po to noVNC .js converter\n * Copyright (C) 2018 The noVNC authors\n *\n * This p"
  },
  {
    "path": "po/pt_BR.po",
    "chars": 5450,
    "preview": "# Portuguese translations for noVNC package.\n# Copyright (C) 2021 The noVNC authors\n# This file is distributed under the"
  },
  {
    "path": "po/ru.po",
    "chars": 5582,
    "preview": "# Russian translations for noVNC package\n# Русский перевод для пакета noVNC.\n# Copyright (C) 2019 Dmitriy Shweew\n# This "
  },
  {
    "path": "po/sv.po",
    "chars": 7108,
    "preview": "# Swedish translations for noVNC package\r\n# Svenska översättningar för paketet noVNC.\r\n# Copyright (C) 2025 The noVNC au"
  },
  {
    "path": "po/tr.po",
    "chars": 5344,
    "preview": "# Turkish translations for noVNC package\n# Turkish translation for noVNC.\n# Copyright (C) 2018 The noVNC authors\n# This "
  },
  {
    "path": "po/uk.po",
    "chars": 6582,
    "preview": "# Ukrainian translation of noVNC.\n# Copyright (C) 2025 The noVNC authors\n# This file is distributed under the same licen"
  },
  {
    "path": "po/xgettext-html",
    "chars": 3681,
    "preview": "#!/usr/bin/env node\n/*\n * xgettext-html: HTML gettext parser\n * Copyright (C) 2018 The noVNC authors\n * Licensed under M"
  },
  {
    "path": "po/zh_CN.po",
    "chars": 6069,
    "preview": "# Simplified Chinese translations for noVNC package.\n# Copyright (C) 2020 The noVNC authors\n# This file is distributed u"
  },
  {
    "path": "po/zh_TW.po",
    "chars": 4641,
    "preview": "# Traditional Chinese translations for noVNC package.\n# Copyright (C) 2018 The noVNC authors\n# This file is distributed "
  },
  {
    "path": "snap/hooks/configure",
    "chars": 45,
    "preview": "#!/bin/sh -e\n\nsnapctl restart novnc.novncsvc\n"
  },
  {
    "path": "snap/local/svc_wrapper.sh",
    "chars": 1203,
    "preview": "#!/bin/bash\n\n# `snapctl get services` returns a JSON array, example:\n#{\n#\"n6801\": {\n#   \"listen\": 6801,\n#   \"vnc\": \"loca"
  },
  {
    "path": "snap/snapcraft.yaml",
    "chars": 1428,
    "preview": "name: novnc\nbase: core22 # the base snap is the execution environment for this snap\nversion: git\nsummary: Open Source VN"
  },
  {
    "path": "tests/assertions.js",
    "chars": 3931,
    "preview": "import * as chai from '../node_modules/chai/index.js';\nimport sinon from '../node_modules/sinon/pkg/sinon-esm.js';\nimpor"
  },
  {
    "path": "tests/fake.websocket.js",
    "chars": 2895,
    "preview": "import Base64 from '../core/base64.js';\n\nexport default class FakeWebSocket {\n    constructor(uri, protocols) {\n        "
  },
  {
    "path": "tests/playback-ui.js",
    "chars": 6508,
    "preview": "/* global VNC_frame_data, VNC_frame_encoding */\n\nimport * as WebUtil from '../app/webutil.js';\nimport RecordingPlayer fr"
  },
  {
    "path": "tests/playback.js",
    "chars": 4634,
    "preview": "/*\n * noVNC: HTML5 VNC client\n * Copyright (C) 2018 The noVNC authors\n * Licensed under MPL 2.0 (see LICENSE.txt)\n */\n\ni"
  },
  {
    "path": "tests/test.base64.js",
    "chars": 1234,
    "preview": "import Base64 from '../core/base64.js';\n\ndescribe('Base64 tools', function () {\n    \"use strict\";\n\n    const BIN_ARR = n"
  },
  {
    "path": "tests/test.browser.js",
    "chars": 11407,
    "preview": "import { isMac, isWindows, isIOS, isAndroid, isChromeOS,\n         isSafari, isFirefox, isChrome, isChromium, isOpera, is"
  },
  {
    "path": "tests/test.clipboard.js",
    "chars": 3550,
    "preview": "import AsyncClipboard from '../core/clipboard.js';\n\ndescribe('Async Clipboard', function () {\n    \"use strict\";\n\n    let"
  },
  {
    "path": "tests/test.copyrect.js",
    "chars": 3301,
    "preview": "import Websock from '../core/websock.js';\nimport Display from '../core/display.js';\n\nimport CopyRectDecoder from '../cor"
  },
  {
    "path": "tests/test.deflator.js",
    "chars": 2385,
    "preview": "import { inflateInit, inflate } from \"../vendor/pako/lib/zlib/inflate.js\";\nimport ZStream from \"../vendor/pako/lib/zlib/"
  },
  {
    "path": "tests/test.display.js",
    "chars": 16221,
    "preview": "import Base64 from '../core/base64.js';\nimport Display from '../core/display.js';\n\ndescribe('Display/Canvas helper', fun"
  },
  {
    "path": "tests/test.gesturehandler.js",
    "chars": 35932,
    "preview": "import EventTargetMixin from '../core/util/eventtarget.js';\n\nimport GestureHandler from '../core/input/gesturehandler.js"
  },
  {
    "path": "tests/test.h264.js",
    "chars": 8349,
    "preview": "import Websock from '../core/websock.js';\nimport Display from '../core/display.js';\n\nimport { H264Parser } from '../core"
  },
  {
    "path": "tests/test.helper.js",
    "chars": 12072,
    "preview": "import keysyms from '../core/input/keysymdef.js';\nimport * as KeyboardUtil from \"../core/input/util.js\";\n\ndescribe('Help"
  },
  {
    "path": "tests/test.hextile.js",
    "chars": 9197,
    "preview": "import Websock from '../core/websock.js';\nimport Display from '../core/display.js';\n\nimport HextileDecoder from '../core"
  },
  {
    "path": "tests/test.inflator.js",
    "chars": 3272,
    "preview": "import { deflateInit, deflate, Z_FULL_FLUSH } from \"../vendor/pako/lib/zlib/deflate.js\";\nimport ZStream from \"../vendor/"
  },
  {
    "path": "tests/test.int.js",
    "chars": 426,
    "preview": "import { toUnsigned32bit, toSigned32bit } from '../core/util/int.js';\n\ndescribe('Integer casting', function () {\n    it("
  },
  {
    "path": "tests/test.jpeg.js",
    "chars": 14364,
    "preview": "import Websock from '../core/websock.js';\nimport Display from '../core/display.js';\n\nimport JPEGDecoder from '../core/de"
  },
  {
    "path": "tests/test.keyboard.js",
    "chars": 29905,
    "preview": "import Keyboard from '../core/input/keyboard.js';\n\ndescribe('Key event handling', function () {\n    \"use strict\";\n\n    /"
  },
  {
    "path": "tests/test.localization.js",
    "chars": 6180,
    "preview": "import _, { Localizer, l10n } from '../app/localization.js';\n\ndescribe('Localization', function () {\n    \"use strict\";\n\n"
  },
  {
    "path": "tests/test.raw.js",
    "chars": 6194,
    "preview": "import Websock from '../core/websock.js';\nimport Display from '../core/display.js';\n\nimport RawDecoder from '../core/dec"
  },
  {
    "path": "tests/test.rfb.js",
    "chars": 263259,
    "preview": "import RFB from '../core/rfb.js';\nimport Websock from '../core/websock.js';\nimport ZStream from \"../vendor/pako/lib/zlib"
  },
  {
    "path": "tests/test.rre.js",
    "chars": 3831,
    "preview": "import Websock from '../core/websock.js';\nimport Display from '../core/display.js';\n\nimport RREDecoder from '../core/dec"
  },
  {
    "path": "tests/test.tight.js",
    "chars": 22725,
    "preview": "import Websock from '../core/websock.js';\nimport Display from '../core/display.js';\n\nimport TightDecoder from '../core/d"
  },
  {
    "path": "tests/test.tightpng.js",
    "chars": 6485,
    "preview": "import Websock from '../core/websock.js';\nimport Display from '../core/display.js';\n\nimport TightPngDecoder from '../cor"
  },
  {
    "path": "tests/test.util.js",
    "chars": 3302,
    "preview": "/* eslint-disable no-console */\nimport * as Log from '../core/util/logging.js';\nimport { encodeUTF8, decodeUTF8 } from '"
  },
  {
    "path": "tests/test.wakelock.js",
    "chars": 6626,
    "preview": "/* jshint expr: true */\n\nimport WakeLockManager from '../app/wakelock.js';\n\nclass FakeWakeLockSentinal extends EventTarg"
  },
  {
    "path": "tests/test.websock.js",
    "chars": 25136,
    "preview": "import Websock from '../core/websock.js';\nimport FakeWebSocket from './fake.websocket.js';\n\ndescribe('Websock', function"
  },
  {
    "path": "tests/test.webutil.js",
    "chars": 10501,
    "preview": "/* jshint expr: true */\n\nimport * as WebUtil from '../app/webutil.js';\n\ndescribe('WebUtil', function () {\n    \"use stric"
  },
  {
    "path": "tests/test.zlib.js",
    "chars": 3057,
    "preview": "import Websock from '../core/websock.js';\nimport Display from '../core/display.js';\n\nimport ZlibDecoder from '../core/de"
  },
  {
    "path": "tests/test.zrle.js",
    "chars": 6315,
    "preview": "import Websock from '../core/websock.js';\nimport Display from '../core/display.js';\n\nimport ZRLEDecoder from '../core/de"
  },
  {
    "path": "tests/vnc_playback.html",
    "chars": 673,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n    <head>\n        <title>VNC playback</title>\n        <script type=\"module\" src=\"./pla"
  },
  {
    "path": "utils/README.md",
    "chars": 538,
    "preview": "## WebSockets Proxy/Bridge\n\nWebsockify has been forked out into its own project. `novnc_proxy` will\nautomatically downlo"
  },
  {
    "path": "utils/b64-to-binary.pl",
    "chars": 396,
    "preview": "#!/usr/bin/env perl\nuse MIME::Base64;\n\nfor (<>) {\n    unless (/^'([{}])(\\d+)\\1(.+?)',$/) {\n        print;\n        next;\n"
  },
  {
    "path": "utils/genkeysymdef.js",
    "chars": 3155,
    "preview": "#!/usr/bin/env node\n/*\n * genkeysymdef: X11 keysymdef.h to JavaScript converter\n * Copyright (C) 2018 The noVNC authors\n"
  },
  {
    "path": "utils/novnc_proxy",
    "chars": 7583,
    "preview": "#!/usr/bin/env bash\n\n# Copyright (C) 2018 The noVNC authors\n# Licensed under MPL 2.0 or any later version (see LICENSE.t"
  },
  {
    "path": "utils/u2x11",
    "chars": 911,
    "preview": "#!/usr/bin/env bash\n#\n# Convert \"U+...\" commented entries in /usr/include/X11/keysymdef.h\n# into JavaScript for use by n"
  },
  {
    "path": "utils/validate",
    "chars": 2285,
    "preview": "#!/bin/bash\n\nset -e\n\nRET=0\n\nOUT=`mktemp`\n\nfor fn in \"$@\"; do\n\techo \"Validating $fn...\"\n\techo\n\n\tcase $fn in\n\t\t*.html)\n\t\t\t"
  },
  {
    "path": "vendor/pako/LICENSE",
    "chars": 1084,
    "preview": "(The MIT License)\n\nCopyright (C) 2014-2016 by Vitaly Puzrin\n\nPermission is hereby granted, free of charge, to any person"
  },
  {
    "path": "vendor/pako/README.md",
    "chars": 301,
    "preview": "This is an ES6-modules-compatible version of\nhttps://github.com/nodeca/pako, based on pako version 1.0.3.\n\nIt's more-or-"
  },
  {
    "path": "vendor/pako/lib/utils/common.js",
    "chars": 1062,
    "preview": "// reduce buffer size, avoiding mem copy\nexport function shrinkBuf (buf, size) {\n  if (buf.length === size) { return buf"
  },
  {
    "path": "vendor/pako/lib/zlib/adler32.js",
    "chars": 666,
    "preview": "// Note: adler32 takes 12% for level 0 and 2% for level 6.\n// It doesn't worth to make additional optimizationa as in or"
  },
  {
    "path": "vendor/pako/lib/zlib/constants.js",
    "chars": 1334,
    "preview": "export default {\n\n  /* Allowed flush values; see deflate() and inflate() below for details */\n  Z_NO_FLUSH:         0,\n "
  },
  {
    "path": "vendor/pako/lib/zlib/crc32.js",
    "chars": 764,
    "preview": "// Note: we can't get significant speed boost here.\n// So write code to minimize size - no pregenerated tables\n// and ar"
  },
  {
    "path": "vendor/pako/lib/zlib/deflate.js",
    "chars": 60286,
    "preview": "import * as utils from \"../utils/common.js\";\nimport * as trees from \"./trees.js\";\nimport adler32 from \"./adler32.js\";\nim"
  },
  {
    "path": "vendor/pako/lib/zlib/gzheader.js",
    "chars": 1251,
    "preview": "export default function GZheader() {\n  /* true if compressed data believed to be text */\n  this.text       = 0;\n  /* mod"
  },
  {
    "path": "vendor/pako/lib/zlib/inffast.js",
    "chars": 11690,
    "preview": "// See state defs from inflate.js\nvar BAD = 30;       /* got a data error -- remain here until reset */\nvar TYPE = 12;  "
  },
  {
    "path": "vendor/pako/lib/zlib/inflate.js",
    "chars": 47281,
    "preview": "import * as utils from \"../utils/common.js\";\nimport adler32 from \"./adler32.js\";\nimport crc32 from \"./crc32.js\";\nimport "
  },
  {
    "path": "vendor/pako/lib/zlib/inftrees.js",
    "chars": 11527,
    "preview": "import * as utils from \"../utils/common.js\";\n\nvar MAXBITS = 15;\nvar ENOUGH_LENS = 852;\nvar ENOUGH_DISTS = 592;\n//var ENO"
  },
  {
    "path": "vendor/pako/lib/zlib/messages.js",
    "chars": 560,
    "preview": "export default {\n  2:      'need dictionary',     /* Z_NEED_DICT       2  */\n  1:      'stream end',          /* Z_STREA"
  },
  {
    "path": "vendor/pako/lib/zlib/trees.js",
    "chars": 38767,
    "preview": "import * as utils from \"../utils/common.js\";\n\n/* Public constants ======================================================"
  },
  {
    "path": "vendor/pako/lib/zlib/zstream.js",
    "chars": 823,
    "preview": "export default function ZStream() {\n  /* next input byte */\n  this.input = null; // JS specific, because we have no poin"
  },
  {
    "path": "vnc.html",
    "chars": 18192,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\" class=\"noVNC_loading\">\n<head>\n\n    <!--\n    noVNC example: simple example using default "
  },
  {
    "path": "vnc_lite.html",
    "chars": 5781,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n\n    <!--\n    noVNC example: lightweight example using minimal UI and features\n\n"
  }
]

// ... and 1 more files (download for full content)

About this extraction

This page contains the full source code of the novnc/noVNC GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 182 files (1.5 MB), approximately 435.5k tokens, and a symbol index with 849 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!