diff --git a/.gitignore b/.gitignore index b72714bb..676d6bda 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ !/qiming-claude-code-acp-ts/ !/qiming-dev-inject/ !/qiming-mcp-proxy/ +!/qiming-noVNC/ !/qimingclaw/ !/qimingcode/ diff --git a/qiming-noVNC/.gitattributes b/qiming-noVNC/.gitattributes new file mode 100644 index 00000000..6814a11c --- /dev/null +++ b/qiming-noVNC/.gitattributes @@ -0,0 +1 @@ +app/vendor/opus-decoder.min.js -text diff --git a/qiming-noVNC/.github/ISSUE_TEMPLATE/bug_report.md b/qiming-noVNC/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..1b377d56 --- /dev/null +++ b/qiming-noVNC/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,34 @@ +--- +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. diff --git a/qiming-noVNC/.github/ISSUE_TEMPLATE/config.yml b/qiming-noVNC/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..cbd35aa7 --- /dev/null +++ b/qiming-noVNC/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +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 diff --git a/qiming-noVNC/.github/ISSUE_TEMPLATE/feature_request.md b/qiming-noVNC/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..066b2d92 --- /dev/null +++ b/qiming-noVNC/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,17 @@ +--- +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. diff --git a/qiming-noVNC/.github/workflows/deploy.yml b/qiming-noVNC/.github/workflows/deploy.yml new file mode 100644 index 00000000..a11d3d0a --- /dev/null +++ b/qiming-noVNC/.github/workflows/deploy.yml @@ -0,0 +1,97 @@ +name: Publish + +on: + push: + pull_request: + release: + types: [published] + +jobs: + npm: + 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' + - uses: actions/setup-node@v4 + with: + # 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 + env: + NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} + if: | + github.repository == 'novnc/noVNC' && + github.event_name == 'release' && + !github.event.release.prerelease + - run: npm publish --access public --tag beta + env: + NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} + if: | + github.repository == 'novnc/noVNC' && + github.event_name == 'release' && + github.event.release.prerelease + - run: npm publish --access public --tag dev + env: + NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} + 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' diff --git a/qiming-noVNC/.github/workflows/lint.yml b/qiming-noVNC/.github/workflows/lint.yml new file mode 100644 index 00000000..540bb990 --- /dev/null +++ b/qiming-noVNC/.github/workflows/lint.yml @@ -0,0 +1,19 @@ +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 diff --git a/qiming-noVNC/.github/workflows/test.yml b/qiming-noVNC/.github/workflows/test.yml new file mode 100644 index 00000000..b72195b5 --- /dev/null +++ b/qiming-noVNC/.github/workflows/test.yml @@ -0,0 +1,28 @@ +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 }} diff --git a/qiming-noVNC/.github/workflows/translate.yml b/qiming-noVNC/.github/workflows/translate.yml new file mode 100644 index 00000000..a4da9cbf --- /dev/null +++ b/qiming-noVNC/.github/workflows/translate.yml @@ -0,0 +1,15 @@ +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 diff --git a/qiming-noVNC/.gitignore b/qiming-noVNC/.gitignore new file mode 100644 index 00000000..61ae4234 --- /dev/null +++ b/qiming-noVNC/.gitignore @@ -0,0 +1,14 @@ +*.pyc +*.o +tests/data_*.js +utils/rebind.so +utils/websockify +/node_modules +/build +/lib +recordings +*.swp +*~ +noVNC-*.tgz +.DS_Store +test-page/ \ No newline at end of file diff --git a/qiming-noVNC/.gitmodules b/qiming-noVNC/.gitmodules new file mode 100644 index 00000000..e69de29b diff --git a/qiming-noVNC/AUTHORS b/qiming-noVNC/AUTHORS new file mode 100644 index 00000000..e8fb1240 --- /dev/null +++ b/qiming-noVNC/AUTHORS @@ -0,0 +1,13 @@ +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 +- NTT corp. diff --git a/qiming-noVNC/LICENSE.txt b/qiming-noVNC/LICENSE.txt new file mode 100644 index 00000000..0581c11e --- /dev/null +++ b/qiming-noVNC/LICENSE.txt @@ -0,0 +1,62 @@ +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 diff --git a/qiming-noVNC/LOCAL_DEV.md b/qiming-noVNC/LOCAL_DEV.md new file mode 100644 index 00000000..836b55c4 --- /dev/null +++ b/qiming-noVNC/LOCAL_DEV.md @@ -0,0 +1,73 @@ +# 本地开发指南 + +> ⚠️ **注意**: 本文档及 `scripts/` 目录下的脚本仅用于**开发调试**,不影响原始业务逻辑。 + +## 启动开发服务器 + +```bash +npm run dev +# 或 +pnpm dev +``` + +服务器将在 `http://localhost:8080` 启动。 + +## 启动虚拟桌面服务 + +```bash +# 使用 npm 运行(推荐) +npm run start:desktop + +# 或直接运行脚本 +./scripts/start_desktop.sh + +# 自定义提示词 +npm run start:desktop -- "你好,帮我完成一个任务" + +# 自定义配置 +API_HOST=192.168.1.34 API_PORT=8086 USER_ID=my_user npm run start:desktop +``` + +## 测试地址 + +### 连接到内网 VNC 服务器 + +``` +http://localhost:8080/vnc.html?host=192.168.1.34&port=8088&path=computer/vnc/user_123/666/websockify&encrypt=0&resize=scale&autoconnect=true +``` + +### URL 参数说明 + +| 参数 | 说明 | 示例值 | +|------|------|--------| +| `host` | VNC WebSocket 服务器地址 | `192.168.1.34` | +| `port` | WebSocket 端口 | `8088` | +| `path` | WebSocket 路径 | `computer/vnc/user_123/666/websockify` | +| `encrypt` | 是否加密 (0=ws://, 1=wss://) | `0` | +| `resize` | 缩放模式 (`off`, `scale`, `remote`) | `scale` | +| `autoconnect` | 是否自动连接 | `true` | + +### 完整 WebSocket 地址 + +上述配置对应的 WebSocket 连接地址为: + +``` +ws://192.168.1.34:8088/computer/vnc/user_123/666/websockify +``` + +## 配置默认值 + +可以通过修改 `defaults.json` 来设置默认连接参数: + +```json +{ + "host": "192.168.1.34", + "port": 8088, + "path": "computer/vnc/user_123/666/websockify", + "encrypt": false, + "resize": "scale", + "autoconnect": true +} +``` + +> **注意**: URL 参数会覆盖 `defaults.json` 中的配置。 diff --git a/qiming-noVNC/README.md b/qiming-noVNC/README.md new file mode 100644 index 00000000..3c831be6 --- /dev/null +++ b/qiming-noVNC/README.md @@ -0,0 +1,228 @@ +## 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: + +  + + +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/\/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! diff --git a/qiming-noVNC/app/audio.js b/qiming-noVNC/app/audio.js new file mode 100644 index 00000000..ac8d742b --- /dev/null +++ b/qiming-noVNC/app/audio.js @@ -0,0 +1,392 @@ +/* + * noVNC: HTML5 VNC client + * Audio streaming module for remote desktop audio playback + * + * 音频流模块 - 用于播放远程桌面的音频输出 + * + * 功能: + * - 通过 WebSocket 接收 Opus 编码的音频数据 + * - 使用 Web Audio API 解码并播放音频 + * - 支持音量控制和连接状态管理 + */ + +import * as Log from '../core/util/logging.js'; + +// 音频管理器类 +class AudioManager { + constructor() { + // WebSocket 连接 + this.ws = null; + this.connected = false; + + // Web Audio API 组件 + this.audioContext = null; + this.gainNode = null; + this.decoder = null; + + // 播放调度 + this.nextPlayTime = 0; + + // 配置 + this.volume = 0.8; // 默认音量 80% + this.sampleRate = 48000; + this.channels = 2; + + // 重试配置 + this.maxRetries = 5; // 最大重试次数 + this.retryCount = 0; // 当前重试次数 + this.retryDelay = 2000; // 重试间隔 (ms) + this.retryTimer = null; // 重试定时器 + this.lastWsUrl = null; // 上次连接的URL(用于重试) + this.autoRetryEnabled = true; // 是否启用自动重试 + + // 回调函数 + this.onStatusChange = null; + this.onMaxRetriesReached = null; // 达到最大重试次数的回调 + + // OpusDecoder 加载状态 + this.opusDecoderReady = false; + this._checkOpusDecoder(); + } + + /** + * 检查 OpusDecoder 是否已加载 + * @private + */ + _checkOpusDecoder() { + // opus-decoder 库使用 UMD 模式,导出为 window["opus-decoder"].OpusDecoder + const opusDecoderLib = window["opus-decoder"]; + if (opusDecoderLib && opusDecoderLib.OpusDecoder) { + window.OpusDecoder = opusDecoderLib.OpusDecoder; + this.opusDecoderReady = true; + Log.Info('[Audio] OpusDecoder 加载成功'); + } else { + Log.Warn('[Audio] OpusDecoder 未加载,音频功能可能无法使用'); + this.opusDecoderReady = false; + } + } + + /** + * 构建音频 WebSocket URL + * @param {string} baseUrl - 基础 URL (如 http://127.0.0.1:8088) + * @param {string} projectId - 项目 ID + * @param {string} userId - 用户 ID (可选,调试模式使用) + * @param {boolean} debugMode - 是否调试模式 + * @returns {string} WebSocket URL + */ + buildWsUrl(baseUrl, projectId, userId = null, debugMode = false) { + // 将 http/https 转换为 ws/wss + const wsUrl = baseUrl + .replace(/^http/, 'ws') + .replace(/\/+$/, ''); + + // 调试模式:包含 user_id + // 正式模式:不包含 user_id + if (debugMode && userId) { + return `ws://192.168.1.34:8088/computer/audio/${userId}/${projectId}/ws`; + } else { + return `${wsUrl}/computer/audio/${projectId}/ws`; + } + } + + /** + * 连接音频流 + * @param {string} wsUrl - WebSocket URL + * @returns {Promise} + */ + async connect(wsUrl) { + if (this.connected) { + Log.Warn('[Audio] 已经连接,请先断开'); + return; + } + + // 检查 OpusDecoder + if (!this.opusDecoderReady) { + this._checkOpusDecoder(); + if (!this.opusDecoderReady) { + Log.Error('[Audio] OpusDecoder 未加载,无法启动音频'); + this._updateStatus('error', 'OpusDecoder 未加载'); + return; + } + } + + try { + this._updateStatus('connecting', '音频连接中...'); + Log.Info('[Audio] 连接音频流: ' + wsUrl); + + // 初始化 Web Audio API + await this._initAudioContext(); + + // 保存URL用于重试 + this.lastWsUrl = wsUrl; + + // 连接 WebSocket + this.ws = new WebSocket(wsUrl); + this.ws.binaryType = 'arraybuffer'; + + this.ws.onopen = () => { + Log.Info('[Audio] WebSocket 已连接'); + this.connected = true; + this.retryCount = 0; // 连接成功,重置重试计数 + this._updateStatus('connected', '音频已连接'); + }; + + this.ws.onmessage = async (event) => { + try { + const data = new Uint8Array(event.data); + // 检查协议头 (0x01 表示 Opus 音频) + if (data[0] === 0x01) { + const opusData = data.slice(1); + await this._playAudioChunk(opusData); + } + } catch (err) { + Log.Error('[Audio] 处理音频数据失败: ' + err); + } + }; + + this.ws.onerror = (err) => { + Log.Error('[Audio] WebSocket 错误: ' + err); + this._updateStatus('error', '音频连接错误'); + }; + + this.ws.onclose = () => { + Log.Info('[Audio] WebSocket 已关闭'); + this.connected = false; + this._cleanup(); + + // 自动重试逻辑 + if (this.autoRetryEnabled && this.retryCount < this.maxRetries) { + this.retryCount++; + Log.Info('[Audio] 将在 ' + (this.retryDelay / 1000) + ' 秒后重试 (' + this.retryCount + '/' + this.maxRetries + ')'); + this._updateStatus('connecting', '重试中 (' + this.retryCount + '/' + this.maxRetries + ')'); + + this.retryTimer = setTimeout(() => { + if (this.lastWsUrl && this.autoRetryEnabled) { + this.connect(this.lastWsUrl); + } + }, this.retryDelay); + } else if (this.retryCount >= this.maxRetries) { + Log.Warn('[Audio] 已达到最大重试次数 (' + this.maxRetries + ')'); + this._updateStatus('error', '连接失败,请手动重试'); + if (this.onMaxRetriesReached) { + this.onMaxRetriesReached(); + } + } else { + this._updateStatus('disconnected', '音频已断开'); + } + }; + + } catch (err) { + Log.Error('[Audio] 连接失败: ' + err); + this._updateStatus('error', '音频启动失败'); + throw err; + } + } + + /** + * 断开音频流 + */ + disconnect() { + // 禁用自动重试 + this.autoRetryEnabled = false; + + // 清除重试定时器 + if (this.retryTimer) { + clearTimeout(this.retryTimer); + this.retryTimer = null; + } + + if (this.ws) { + this.ws.close(); + this.ws = null; + } + this.connected = false; + this.retryCount = 0; + this._cleanup(); + this._updateStatus('disconnected', '音频已停止'); + Log.Info('[Audio] 音频流已断开'); + } + + /** + * 设置音量 + * @param {number} volume - 音量值 (0-1) + */ + setVolume(volume) { + this.volume = Math.max(0, Math.min(1, volume)); + if (this.gainNode) { + this.gainNode.gain.value = this.volume; + } + Log.Debug('[Audio] 音量设置为: ' + Math.round(this.volume * 100) + '%'); + } + + /** + * 获取当前音量 + * @returns {number} 音量值 (0-1) + */ + getVolume() { + return this.volume; + } + + /** + * 初始化 Web Audio API + * @private + */ + async _initAudioContext() { + if (!this.audioContext) { + this.audioContext = new (window.AudioContext || window.webkitAudioContext)({ + sampleRate: this.sampleRate, + latencyHint: 'interactive' + }); + + this.gainNode = this.audioContext.createGain(); + this.gainNode.gain.value = this.volume; + this.gainNode.connect(this.audioContext.destination); + + this.nextPlayTime = this.audioContext.currentTime + 0.1; + } + + // 恢复 AudioContext(浏览器安全策略) + if (this.audioContext.state === 'suspended') { + try { + await this.audioContext.resume(); + Log.Info('[Audio] AudioContext 已恢复'); + } catch (e) { + Log.Warn('[Audio] AudioContext 自动恢复失败,等待用户交互: ' + e); + } + } + } + + /** + * 尝试恢复 AudioContext + */ + async resume() { + if (this.audioContext && this.audioContext.state === 'suspended') { + try { + await this.audioContext.resume(); + Log.Info('[Audio] AudioContext 已恢复'); + } catch (e) { + Log.Error('[Audio] 恢复失败: ' + e); + } + } + } + + + /** + * 播放音频数据块 + * @param {Uint8Array} opusData - Opus 编码的音频数据 + * @private + */ + async _playAudioChunk(opusData) { + try { + // 初始化或复用 Opus 解码器 + if (!this.decoder) { + Log.Debug('[Audio] 初始化 OpusDecoder...'); + this.decoder = new window.OpusDecoder({ + sampleRate: this.sampleRate, + channels: this.channels + }); + await this.decoder.ready; + Log.Debug('[Audio] OpusDecoder 初始化完成'); + } + + // 解码 Opus 数据 + const decoded = this.decoder.decodeFrame(opusData); + const channels = decoded.channelData; + const samplesDecoded = decoded.samplesDecoded; + + if (!channels || channels.length < 2) { + Log.Warn('[Audio] 输出声道数据无效'); + return; + } + + const leftChannelData = channels[0]; + const rightChannelData = channels[1]; + + // 创建 AudioBuffer + const audioBuffer = this.audioContext.createBuffer( + 2, samplesDecoded, this.sampleRate + ); + + // 填充声道数据 + audioBuffer.getChannelData(0).set(leftChannelData); + audioBuffer.getChannelData(1).set(rightChannelData); + + // 调度播放 + const source = this.audioContext.createBufferSource(); + source.buffer = audioBuffer; + source.connect(this.gainNode); + + // 计算播放时间 + const currentTime = this.audioContext.currentTime; + if (this.nextPlayTime < currentTime) { + this.nextPlayTime = currentTime + 0.05; // 50ms 缓冲 + } + + source.start(this.nextPlayTime); + + // 计算下一个播放时间点 + const duration = samplesDecoded / this.sampleRate; + this.nextPlayTime += duration; + + } catch (err) { + Log.Error('[Audio] 解码或播放失败: ' + err); + // 解码失败时重置解码器 + if (this.decoder) { + try { + this.decoder.free(); + } catch (e) { + // 忽略释放错误 + } + this.decoder = null; + } + } + } + + /** + * 清理资源 + * @private + */ + _cleanup() { + // 重置播放调度 + this.nextPlayTime = 0; + + // 释放解码器 + if (this.decoder) { + try { + this.decoder.free(); + } catch (e) { + // 忽略释放错误 + } + this.decoder = null; + } + } + + /** + * 更新状态并触发回调 + * @param {string} status - 状态 (connecting/connected/disconnected/error) + * @param {string} message - 状态消息 + * @private + */ + _updateStatus(status, message) { + if (this.onStatusChange) { + this.onStatusChange(status, message); + } + } + + /** + * 销毁音频管理器 + */ + destroy() { + this.disconnect(); + if (this.audioContext) { + this.audioContext.close(); + this.audioContext = null; + } + this.gainNode = null; + } +} + +// 导出单例 +const audioManager = new AudioManager(); +export default audioManager; + diff --git a/qiming-noVNC/app/clipboard.js b/qiming-noVNC/app/clipboard.js new file mode 100644 index 00000000..edff3dfe --- /dev/null +++ b/qiming-noVNC/app/clipboard.js @@ -0,0 +1,252 @@ +/* + * noVNC: HTML5 VNC client + * Clipboard management module + */ + +import * as Log from '../core/util/logging.js'; +import { l10n } from './localization.js'; + +const _ = l10n.get.bind(l10n); + +class ClipboardManager { + constructor() { + this.rfb = null; + this.autoSync = false; + this.permissionGranted = false; + this.lastLocalText = null; + this.syncFocusHandler = null; + + // Callbacks for UI updates + this.onStatusChange = null; // (status, message) => void + } + + setRFB(rfb) { + this.rfb = rfb; + } + + /** + * Initialize clipboard state based on permissions + * 根据权限状态初始化剪贴板自动同步 + * + * 注意:默认开启自动同步,因为: + * 1. 这是更好的用户体验 + * 2. Clipboard API 在用户手势(如点击、焦点切换)时才会真正读取 + * 3. 如果权限被拒绝,会在实际操作时处理 + */ + async init() { + Log.Info('[Clipboard] Initializing...'); + + // 检查 Clipboard API 是否可用 + if (!navigator.clipboard) { + Log.Warn('[Clipboard] Clipboard API not supported'); + this._updateStatus('error', _('Clipboard API not supported')); + return; + } + + // 尝试查询权限状态(某些浏览器可能不支持) + let permissionState = 'unknown'; + + if (navigator.permissions && navigator.permissions.query) { + try { + // 注意:某些浏览器不支持 "clipboard-read" 权限查询 + const result = await navigator.permissions.query({ name: "clipboard-read" }); + permissionState = result.state; + Log.Info('[Clipboard] Permission query result: ' + permissionState); + } catch (err) { + // 权限查询不支持(如 Firefox),这是正常的 + Log.Debug('[Clipboard] Permission query not supported: ' + err.message); + permissionState = 'unknown'; + } + } + + // 根据权限状态决定行为 + if (permissionState === 'denied') { + // 权限明确被拒绝 + Log.Info('[Clipboard] Permission denied, disabling auto sync'); + this.autoSync = false; + this._updateStatus('inactive', ''); + } else { + // granted / prompt / unknown 都默认开启 + // 实际权限检查会在用户手势时进行 + Log.Info('[Clipboard] Enabling auto sync (permission: ' + permissionState + ')'); + this.autoSync = true; + this._startSync(false); // 不立即执行,等待用户手势 + this._updateStatus('active', _('Auto sync enabled')); + } + } + + /** + * Toggle auto-sync state + * @param {boolean} enable + * @returns {Promise} resulting state + */ + async toggleAutoSync(enable) { + if (enable) { + const hasPermission = await this._requestPermission(); + if (!hasPermission) { + this._updateStatus('error', _('Clipboard permission denied')); + return false; + } + + this.autoSync = true; + this._startSync(); + this._updateStatus('active', _('Auto sync enabled')); + Log.Info('[Clipboard] Auto sync enabled'); + } else { + this.autoSync = false; + this._stopSync(); + this._updateStatus('inactive', ''); + Log.Info('[Clipboard] Auto sync disabled'); + } + return this.autoSync; + } + + /** + * Receive text from remote and sync to local if enabled + * @param {string} text + */ + handleRemoteText(text) { + if (this.autoSync) { + this.writeLocal(text); + } + } + + /** + * Read from local clipboard manually + */ + async readLocal() { + try { + if (!navigator.clipboard || !navigator.clipboard.readText) { + this._updateStatus('error', _('Clipboard API not supported')); + return null; + } + + const text = await navigator.clipboard.readText(); + this.lastLocalText = text; + Log.Debug('[Clipboard] Read from local: ' + text.substr(0, 40) + '...'); + + this._updateStatus('success', _('Read from local clipboard')); + this._resetStatus(); + + return text; + } catch (err) { + Log.Warn('[Clipboard] Failed to read local: ' + err.message); + this._updateStatus('error', _('Failed to read clipboard')); + return null; + } + } + + /** + * Write to local clipboard + * @param {string} text + */ + async writeLocal(text) { + try { + if (!navigator.clipboard || !navigator.clipboard.writeText) { + this._updateStatus('error', _('Clipboard API not supported')); + return; + } + + await navigator.clipboard.writeText(text); + this.lastLocalText = text; + Log.Debug('[Clipboard] Wrote to local: ' + text.substr(0, 40) + '...'); + + this._updateStatus('success', _('Copied to local clipboard')); + this._resetStatus(); + } catch (err) { + Log.Warn('[Clipboard] Failed to write local: ' + err.message); + this._updateStatus('error', _('Failed to write clipboard')); + } + } + + /** + * Send text to remote + * @param {string} text + */ + sendToRemote(text) { + if (this.rfb) { + this.rfb.clipboardPasteFrom(text); + Log.Debug('[Clipboard] Sent to remote: ' + text.substr(0, 40) + '...'); + } + } + + // Private methods + + async _requestPermission() { + try { + if (navigator.clipboard && navigator.clipboard.readText) { + await navigator.clipboard.readText(); + this.permissionGranted = true; + return true; + } + return false; + } catch (err) { + if (err.name === 'NotAllowedError') { + return false; + } + // Other errors (like empty clipboard) usually mean we have permission + return true; + } + } + + _startSync(runImmediately = true) { + this.syncFocusHandler = async () => { + if (!this.autoSync || !this.rfb) return; + + try { + // Focus event is a user gesture, so readText should work/prompt + const text = await navigator.clipboard.readText(); + if (text && text !== this.lastLocalText) { + this.lastLocalText = text; + this.sendToRemote(text); + } + } catch (err) { + // If it fails (e.g. denied), we might want to turn off autoSync? + // For now, just log and maybe show status + Log.Warn('[Clipboard] Auto-sync read failed: ' + err); + if (err.name === 'NotAllowedError') { + this._updateStatus('error', _('Clipboard permission denied')); + this.autoSync = false; // Disable if denied + // We might want to notify UI to uncheck box + // But we don't have a direct way to sync back to checkbox unless we use callback + // Current arch: init sets checkbox. toggle sets checkbox. + // If we change state here, checkbox might desync. + // We can use onStatusChange to signal "error"? Or add onStateChange? + // For now, keep simple. + } + } + }; + + window.addEventListener('focus', this.syncFocusHandler); + if (runImmediately) { + this.syncFocusHandler(); // Initial sync + } + } + + _stopSync() { + if (this.syncFocusHandler) { + window.removeEventListener('focus', this.syncFocusHandler); + this.syncFocusHandler = null; + } + this.lastLocalText = null; + } + + _updateStatus(status, message) { + if (this.onStatusChange) { + this.onStatusChange(status, message); + } + } + + _resetStatus() { + setTimeout(() => { + if (this.autoSync) { + this._updateStatus('active', _('Auto sync enabled')); + } else { + this._updateStatus('inactive', ''); + } + }, 2000); + } +} + +const clipboardManager = new ClipboardManager(); +export default clipboardManager; diff --git a/qiming-noVNC/app/error-handler.js b/qiming-noVNC/app/error-handler.js new file mode 100644 index 00000000..5f6ffb67 --- /dev/null +++ b/qiming-noVNC/app/error-handler.js @@ -0,0 +1,79 @@ +/* + * 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 uncought 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)); diff --git a/qiming-noVNC/app/images/alt.svg b/qiming-noVNC/app/images/alt.svg new file mode 100644 index 00000000..e5bb4612 --- /dev/null +++ b/qiming-noVNC/app/images/alt.svg @@ -0,0 +1,92 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/qiming-noVNC/app/images/audio-muted.png b/qiming-noVNC/app/images/audio-muted.png new file mode 100644 index 00000000..b87bc8f9 Binary files /dev/null and b/qiming-noVNC/app/images/audio-muted.png differ diff --git a/qiming-noVNC/app/images/audio-muted.svg b/qiming-noVNC/app/images/audio-muted.svg new file mode 100644 index 00000000..bca86c33 --- /dev/null +++ b/qiming-noVNC/app/images/audio-muted.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/qiming-noVNC/app/images/audio-on.png b/qiming-noVNC/app/images/audio-on.png new file mode 100644 index 00000000..b82edb76 Binary files /dev/null and b/qiming-noVNC/app/images/audio-on.png differ diff --git a/qiming-noVNC/app/images/audio-on.svg b/qiming-noVNC/app/images/audio-on.svg new file mode 100644 index 00000000..30730b5f --- /dev/null +++ b/qiming-noVNC/app/images/audio-on.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/qiming-noVNC/app/images/audio.svg b/qiming-noVNC/app/images/audio.svg new file mode 100644 index 00000000..d6a77b56 --- /dev/null +++ b/qiming-noVNC/app/images/audio.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/qiming-noVNC/app/images/bg.jpg b/qiming-noVNC/app/images/bg.jpg new file mode 100644 index 00000000..921a7e39 Binary files /dev/null and b/qiming-noVNC/app/images/bg.jpg differ diff --git a/qiming-noVNC/app/images/clipboard.svg b/qiming-noVNC/app/images/clipboard.svg new file mode 100644 index 00000000..79af2752 --- /dev/null +++ b/qiming-noVNC/app/images/clipboard.svg @@ -0,0 +1,106 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/qiming-noVNC/app/images/connect.svg b/qiming-noVNC/app/images/connect.svg new file mode 100644 index 00000000..56cde414 --- /dev/null +++ b/qiming-noVNC/app/images/connect.svg @@ -0,0 +1,96 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/qiming-noVNC/app/images/ctrl.svg b/qiming-noVNC/app/images/ctrl.svg new file mode 100644 index 00000000..856e9395 --- /dev/null +++ b/qiming-noVNC/app/images/ctrl.svg @@ -0,0 +1,96 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/qiming-noVNC/app/images/ctrlaltdel.svg b/qiming-noVNC/app/images/ctrlaltdel.svg new file mode 100644 index 00000000..d7744ea3 --- /dev/null +++ b/qiming-noVNC/app/images/ctrlaltdel.svg @@ -0,0 +1,100 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/qiming-noVNC/app/images/disconnect.svg b/qiming-noVNC/app/images/disconnect.svg new file mode 100644 index 00000000..6be7d187 --- /dev/null +++ b/qiming-noVNC/app/images/disconnect.svg @@ -0,0 +1,94 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/qiming-noVNC/app/images/drag.svg b/qiming-noVNC/app/images/drag.svg new file mode 100644 index 00000000..139caf94 --- /dev/null +++ b/qiming-noVNC/app/images/drag.svg @@ -0,0 +1,76 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/qiming-noVNC/app/images/error.svg b/qiming-noVNC/app/images/error.svg new file mode 100644 index 00000000..8356d3f1 --- /dev/null +++ b/qiming-noVNC/app/images/error.svg @@ -0,0 +1,81 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/qiming-noVNC/app/images/esc.svg b/qiming-noVNC/app/images/esc.svg new file mode 100644 index 00000000..830152b5 --- /dev/null +++ b/qiming-noVNC/app/images/esc.svg @@ -0,0 +1,92 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/qiming-noVNC/app/images/expander.svg b/qiming-noVNC/app/images/expander.svg new file mode 100644 index 00000000..e1635358 --- /dev/null +++ b/qiming-noVNC/app/images/expander.svg @@ -0,0 +1,69 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/qiming-noVNC/app/images/fullscreen.svg b/qiming-noVNC/app/images/fullscreen.svg new file mode 100644 index 00000000..29bd05da --- /dev/null +++ b/qiming-noVNC/app/images/fullscreen.svg @@ -0,0 +1,93 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/qiming-noVNC/app/images/handle.svg b/qiming-noVNC/app/images/handle.svg new file mode 100644 index 00000000..4a7a126f --- /dev/null +++ b/qiming-noVNC/app/images/handle.svg @@ -0,0 +1,82 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/qiming-noVNC/app/images/handle_bg.svg b/qiming-noVNC/app/images/handle_bg.svg new file mode 100644 index 00000000..7579c42c --- /dev/null +++ b/qiming-noVNC/app/images/handle_bg.svg @@ -0,0 +1,172 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + diff --git a/qiming-noVNC/app/images/icons/Makefile b/qiming-noVNC/app/images/icons/Makefile new file mode 100644 index 00000000..03eaed07 --- /dev/null +++ b/qiming-noVNC/app/images/icons/Makefile @@ -0,0 +1,42 @@ +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 diff --git a/qiming-noVNC/app/images/icons/fav.ico b/qiming-noVNC/app/images/icons/fav.ico new file mode 100644 index 00000000..cfed03e3 Binary files /dev/null and b/qiming-noVNC/app/images/icons/fav.ico differ diff --git a/qiming-noVNC/app/images/icons/novnc-icon-sm.svg b/qiming-noVNC/app/images/icons/novnc-icon-sm.svg new file mode 100644 index 00000000..aa1c6f18 --- /dev/null +++ b/qiming-noVNC/app/images/icons/novnc-icon-sm.svg @@ -0,0 +1,163 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/qiming-noVNC/app/images/icons/novnc-icon.svg b/qiming-noVNC/app/images/icons/novnc-icon.svg new file mode 100644 index 00000000..1efff912 --- /dev/null +++ b/qiming-noVNC/app/images/icons/novnc-icon.svg @@ -0,0 +1,163 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/qiming-noVNC/app/images/icons/novnc-ios-120.png b/qiming-noVNC/app/images/icons/novnc-ios-120.png new file mode 100644 index 00000000..8da7bab3 Binary files /dev/null and b/qiming-noVNC/app/images/icons/novnc-ios-120.png differ diff --git a/qiming-noVNC/app/images/icons/novnc-ios-152.png b/qiming-noVNC/app/images/icons/novnc-ios-152.png new file mode 100644 index 00000000..60b2bcef Binary files /dev/null and b/qiming-noVNC/app/images/icons/novnc-ios-152.png differ diff --git a/qiming-noVNC/app/images/icons/novnc-ios-167.png b/qiming-noVNC/app/images/icons/novnc-ios-167.png new file mode 100644 index 00000000..98fade2e Binary files /dev/null and b/qiming-noVNC/app/images/icons/novnc-ios-167.png differ diff --git a/qiming-noVNC/app/images/icons/novnc-ios-180.png b/qiming-noVNC/app/images/icons/novnc-ios-180.png new file mode 100644 index 00000000..5d24df70 Binary files /dev/null and b/qiming-noVNC/app/images/icons/novnc-ios-180.png differ diff --git a/qiming-noVNC/app/images/icons/novnc-ios-40.png b/qiming-noVNC/app/images/icons/novnc-ios-40.png new file mode 100644 index 00000000..cf14894d Binary files /dev/null and b/qiming-noVNC/app/images/icons/novnc-ios-40.png differ diff --git a/qiming-noVNC/app/images/icons/novnc-ios-58.png b/qiming-noVNC/app/images/icons/novnc-ios-58.png new file mode 100644 index 00000000..f6dfbebd Binary files /dev/null and b/qiming-noVNC/app/images/icons/novnc-ios-58.png differ diff --git a/qiming-noVNC/app/images/icons/novnc-ios-60.png b/qiming-noVNC/app/images/icons/novnc-ios-60.png new file mode 100644 index 00000000..8cda2953 Binary files /dev/null and b/qiming-noVNC/app/images/icons/novnc-ios-60.png differ diff --git a/qiming-noVNC/app/images/icons/novnc-ios-80.png b/qiming-noVNC/app/images/icons/novnc-ios-80.png new file mode 100644 index 00000000..6c417c47 Binary files /dev/null and b/qiming-noVNC/app/images/icons/novnc-ios-80.png differ diff --git a/qiming-noVNC/app/images/icons/novnc-ios-87.png b/qiming-noVNC/app/images/icons/novnc-ios-87.png new file mode 100644 index 00000000..4377d874 Binary files /dev/null and b/qiming-noVNC/app/images/icons/novnc-ios-87.png differ diff --git a/qiming-noVNC/app/images/icons/novnc-ios-icon.svg b/qiming-noVNC/app/images/icons/novnc-ios-icon.svg new file mode 100644 index 00000000..009452ac --- /dev/null +++ b/qiming-noVNC/app/images/icons/novnc-ios-icon.svg @@ -0,0 +1,183 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/qiming-noVNC/app/images/ime.svg b/qiming-noVNC/app/images/ime.svg new file mode 100644 index 00000000..46a8fb1c --- /dev/null +++ b/qiming-noVNC/app/images/ime.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/qiming-noVNC/app/images/info.svg b/qiming-noVNC/app/images/info.svg new file mode 100644 index 00000000..557b772f --- /dev/null +++ b/qiming-noVNC/app/images/info.svg @@ -0,0 +1,81 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/qiming-noVNC/app/images/keyboard.svg b/qiming-noVNC/app/images/keyboard.svg new file mode 100644 index 00000000..137b350a --- /dev/null +++ b/qiming-noVNC/app/images/keyboard.svg @@ -0,0 +1,88 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/qiming-noVNC/app/images/power.svg b/qiming-noVNC/app/images/power.svg new file mode 100644 index 00000000..4925d3e8 --- /dev/null +++ b/qiming-noVNC/app/images/power.svg @@ -0,0 +1,87 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/qiming-noVNC/app/images/settings.svg b/qiming-noVNC/app/images/settings.svg new file mode 100644 index 00000000..dbb2e80a --- /dev/null +++ b/qiming-noVNC/app/images/settings.svg @@ -0,0 +1,76 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/qiming-noVNC/app/images/tab.svg b/qiming-noVNC/app/images/tab.svg new file mode 100644 index 00000000..1ccb3229 --- /dev/null +++ b/qiming-noVNC/app/images/tab.svg @@ -0,0 +1,86 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/qiming-noVNC/app/images/toggleextrakeys.svg b/qiming-noVNC/app/images/toggleextrakeys.svg new file mode 100644 index 00000000..b578c0d4 --- /dev/null +++ b/qiming-noVNC/app/images/toggleextrakeys.svg @@ -0,0 +1,90 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/qiming-noVNC/app/images/warning.svg b/qiming-noVNC/app/images/warning.svg new file mode 100644 index 00000000..7114f9b1 --- /dev/null +++ b/qiming-noVNC/app/images/warning.svg @@ -0,0 +1,81 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/qiming-noVNC/app/images/windows.svg b/qiming-noVNC/app/images/windows.svg new file mode 100644 index 00000000..ad5eec36 --- /dev/null +++ b/qiming-noVNC/app/images/windows.svg @@ -0,0 +1,65 @@ + + + +image/svg+xml + + + \ No newline at end of file diff --git a/qiming-noVNC/app/ime.js b/qiming-noVNC/app/ime.js new file mode 100644 index 00000000..3240c664 --- /dev/null +++ b/qiming-noVNC/app/ime.js @@ -0,0 +1,296 @@ +/* + * noVNC: HTML5 VNC client + * IME (Input Method Editor) passthrough module + * + * 输入法透传模块 - 用于将本地输入法的输入传送到远程桌面 + * + * 功能: + * - 通过 WebSocket 连接 IME 服务 + * - 监听输入法事件(compositionend) + * - 将输入的文本发送到远程桌面 + */ + +import * as Log from '../core/util/logging.js'; + +// 输入法管理器类 +class IMEManager { + constructor() { + // WebSocket 连接 + this.ws = null; + this.connected = false; + + // 隐藏输入框元素 + this.inputElement = null; + + // 回调函数 + this.onStatusChange = null; + + // 绑定事件处理函数(用于移除监听器) + this._boundCompositionEnd = this._handleCompositionEnd.bind(this); + this._boundInput = this._handleInput.bind(this); + } + + /** + * 构建 IME WebSocket URL + * @param {string} baseUrl - 基础 URL (如 http://127.0.0.1:8088) + * @param {string} projectId - 项目 ID + * @param {string} userId - 用户 ID (可选,调试模式使用) + * @param {boolean} debugMode - 是否调试模式 + * @returns {string} WebSocket URL + */ + buildWsUrl(baseUrl, projectId, userId = null, debugMode = false) { + // 将 http/https 转换为 ws/wss + const wsUrl = baseUrl + .replace(/^http/, 'ws') + .replace(/\/+$/, ''); + + // 调试模式:包含 user_id + // 正式模式:不包含 user_id + if (debugMode && userId) { + return `ws://192.168.1.34:8088/computer/ime/${userId}/${projectId}/connect`; + } else { + return `${wsUrl}/computer/ime/${projectId}/connect`; + } + } + + /** + * 连接 IME 服务 + * @param {string} wsUrl - WebSocket URL + * @returns {Promise} + */ + async connect(wsUrl) { + if (this.connected) { + Log.Warn('[IME] 已经连接,请先断开'); + return; + } + + try { + this._updateStatus('connecting', 'IME 连接中...'); + Log.Info('[IME] 连接 IME 服务: ' + wsUrl); + + // 创建隐藏输入框 + this._createInputElement(); + + // 连接 WebSocket + this.ws = new WebSocket(wsUrl); + + this.ws.onopen = () => { + Log.Info('[IME] WebSocket 已连接'); + this.connected = true; + this._updateStatus('connected', 'IME 已连接'); + + // 启用输入监听 + this._enableInputListeners(); + }; + + this.ws.onmessage = (event) => { + try { + const response = JSON.parse(event.data); + if (response.status === 'ok') { + Log.Debug('[IME] 文本发送成功'); + } else { + Log.Warn('[IME] 服务器响应: ' + response.message); + } + } catch (err) { + Log.Error('[IME] 响应解析失败: ' + err); + } + }; + + this.ws.onerror = (err) => { + Log.Error('[IME] WebSocket 错误: ' + err); + this._updateStatus('error', 'IME 连接错误'); + }; + + this.ws.onclose = () => { + Log.Info('[IME] WebSocket 已关闭'); + this.connected = false; + this._updateStatus('disconnected', 'IME 已断开'); + this._disableInputListeners(); + }; + + } catch (err) { + Log.Error('[IME] 连接失败: ' + err); + this._updateStatus('error', 'IME 启动失败'); + throw err; + } + } + + /** + * 断开 IME 服务 + */ + disconnect() { + if (this.ws) { + this.ws.close(); + this.ws = null; + } + this.connected = false; + this._disableInputListeners(); + this._updateStatus('disconnected', 'IME 已停止'); + Log.Info('[IME] IME 服务已断开'); + } + + /** + * 发送文本到远程桌面 + * @param {string} text - 要发送的文本 + */ + sendText(text) { + if (!this.connected || !this.ws) { + Log.Warn('[IME] 未连接,无法发送文本'); + return; + } + + if (!text || text.length === 0) { + return; + } + + const message = JSON.stringify({ + type: 'text', + text: text, + method: 'xdotool' // 使用 xdotool 输入方法 + }); + + this.ws.send(message); + Log.Debug('[IME] 发送文本: ' + text); + } + + /** + * 激活输入(聚焦到隐藏输入框) + * 当用户点击 VNC 画面时调用 + */ + activateInput() { + if (this.inputElement && this.connected) { + this.inputElement.focus(); + } + } + + /** + * 创建隐藏输入框 + * @private + */ + _createInputElement() { + if (this.inputElement) { + return; + } + + // 创建隐藏的输入框,用于捕获输入法事件 + this.inputElement = document.createElement('input'); + this.inputElement.type = 'text'; + this.inputElement.id = 'noVNC_ime_input'; + this.inputElement.autocomplete = 'off'; + this.inputElement.autocapitalize = 'off'; + this.inputElement.spellcheck = false; + + // 设置样式 - 隐藏但可聚焦 + this.inputElement.style.cssText = ` + position: fixed; + top: -9999px; + left: -9999px; + opacity: 0; + pointer-events: none; + width: 1px; + height: 1px; + `; + + document.body.appendChild(this.inputElement); + Log.Debug('[IME] 隐藏输入框已创建'); + } + + /** + * 启用输入监听器 + * @private + */ + _enableInputListeners() { + if (!this.inputElement) { + return; + } + + // 监听输入法完成事件(如选择中文候选词) + this.inputElement.addEventListener('compositionend', this._boundCompositionEnd); + + // 监听直接输入事件(非输入法输入) + this.inputElement.addEventListener('input', this._boundInput); + + Log.Debug('[IME] 输入监听器已启用'); + } + + /** + * 禁用输入监听器 + * @private + */ + _disableInputListeners() { + if (!this.inputElement) { + return; + } + + this.inputElement.removeEventListener('compositionend', this._boundCompositionEnd); + this.inputElement.removeEventListener('input', this._boundInput); + + Log.Debug('[IME] 输入监听器已禁用'); + } + + /** + * 处理输入法完成事件 + * @param {CompositionEvent} event + * @private + */ + _handleCompositionEnd(event) { + const text = event.data; + if (text && text.length > 0) { + Log.Debug('[IME] 输入法完成: ' + text); + this.sendText(text); + + // 清空输入框 + if (this.inputElement) { + this.inputElement.value = ''; + } + } + } + + /** + * 处理直接输入事件 + * @param {InputEvent} event + * @private + */ + _handleInput(event) { + // 如果正在进行输入法输入,忽略 + if (event.isComposing) { + return; + } + + const text = event.target.value; + if (text && text.length > 0) { + Log.Debug('[IME] 直接输入: ' + text); + this.sendText(text); + event.target.value = ''; + } + } + + /** + * 更新状态并触发回调 + * @param {string} status - 状态 (connecting/connected/disconnected/error) + * @param {string} message - 状态消息 + * @private + */ + _updateStatus(status, message) { + if (this.onStatusChange) { + this.onStatusChange(status, message); + } + } + + /** + * 销毁 IME 管理器 + */ + destroy() { + this.disconnect(); + + if (this.inputElement && this.inputElement.parentNode) { + this.inputElement.parentNode.removeChild(this.inputElement); + this.inputElement = null; + } + } +} + +// 导出单例 +const imeManager = new IMEManager(); +export default imeManager; + diff --git a/qiming-noVNC/app/locale/README b/qiming-noVNC/app/locale/README new file mode 100644 index 00000000..ca4f548b --- /dev/null +++ b/qiming-noVNC/app/locale/README @@ -0,0 +1 @@ +DO NOT MODIFY THE FILES IN THIS FOLDER, THEY ARE AUTOMATICALLY GENERATED FROM THE PO-FILES. diff --git a/qiming-noVNC/app/locale/cs.json b/qiming-noVNC/app/locale/cs.json new file mode 100644 index 00000000..dd31e6c1 --- /dev/null +++ b/qiming-noVNC/app/locale/cs.json @@ -0,0 +1,71 @@ +{ + "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" +} \ No newline at end of file diff --git a/qiming-noVNC/app/locale/de.json b/qiming-noVNC/app/locale/de.json new file mode 100644 index 00000000..fa154637 --- /dev/null +++ b/qiming-noVNC/app/locale/de.json @@ -0,0 +1,74 @@ +{ + "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" +} \ No newline at end of file diff --git a/qiming-noVNC/app/locale/el.json b/qiming-noVNC/app/locale/el.json new file mode 100644 index 00000000..57d67316 --- /dev/null +++ b/qiming-noVNC/app/locale/el.json @@ -0,0 +1,100 @@ +{ + "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": "Αποστολή Κωδικού Πρόσβασης" +} \ No newline at end of file diff --git a/qiming-noVNC/app/locale/es.json b/qiming-noVNC/app/locale/es.json new file mode 100644 index 00000000..bb088243 --- /dev/null +++ b/qiming-noVNC/app/locale/es.json @@ -0,0 +1,68 @@ +{ + "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." +} \ No newline at end of file diff --git a/qiming-noVNC/app/locale/fr.json b/qiming-noVNC/app/locale/fr.json new file mode 100644 index 00000000..c8607ceb --- /dev/null +++ b/qiming-noVNC/app/locale/fr.json @@ -0,0 +1,82 @@ +{ + "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" +} \ No newline at end of file diff --git a/qiming-noVNC/app/locale/it.json b/qiming-noVNC/app/locale/it.json new file mode 100644 index 00000000..28e6f721 --- /dev/null +++ b/qiming-noVNC/app/locale/it.json @@ -0,0 +1,68 @@ +{ + "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" +} \ No newline at end of file diff --git a/qiming-noVNC/app/locale/ja.json b/qiming-noVNC/app/locale/ja.json new file mode 100644 index 00000000..4fc9b8a9 --- /dev/null +++ b/qiming-noVNC/app/locale/ja.json @@ -0,0 +1,81 @@ +{ + "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": "キャンセル" +} \ No newline at end of file diff --git a/qiming-noVNC/app/locale/ko.json b/qiming-noVNC/app/locale/ko.json new file mode 100644 index 00000000..47b0805c --- /dev/null +++ b/qiming-noVNC/app/locale/ko.json @@ -0,0 +1,70 @@ +{ + "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": "취소" +} \ No newline at end of file diff --git a/qiming-noVNC/app/locale/nl.json b/qiming-noVNC/app/locale/nl.json new file mode 100644 index 00000000..61f2df3c --- /dev/null +++ b/qiming-noVNC/app/locale/nl.json @@ -0,0 +1,95 @@ +{ + "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" +} \ No newline at end of file diff --git a/qiming-noVNC/app/locale/pl.json b/qiming-noVNC/app/locale/pl.json new file mode 100644 index 00000000..87c16905 --- /dev/null +++ b/qiming-noVNC/app/locale/pl.json @@ -0,0 +1,80 @@ +{ + "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" +} \ No newline at end of file diff --git a/qiming-noVNC/app/locale/pt_BR.json b/qiming-noVNC/app/locale/pt_BR.json new file mode 100644 index 00000000..224b86ef --- /dev/null +++ b/qiming-noVNC/app/locale/pt_BR.json @@ -0,0 +1,72 @@ +{ + "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": "Mostar 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" +} \ No newline at end of file diff --git a/qiming-noVNC/app/locale/ru.json b/qiming-noVNC/app/locale/ru.json new file mode 100644 index 00000000..bd1bb534 --- /dev/null +++ b/qiming-noVNC/app/locale/ru.json @@ -0,0 +1,72 @@ +{ + "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": "Выход" +} \ No newline at end of file diff --git a/qiming-noVNC/app/locale/sv.json b/qiming-noVNC/app/locale/sv.json new file mode 100644 index 00000000..67f6675a --- /dev/null +++ b/qiming-noVNC/app/locale/sv.json @@ -0,0 +1,83 @@ +{ + "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" +} \ No newline at end of file diff --git a/qiming-noVNC/app/locale/tr.json b/qiming-noVNC/app/locale/tr.json new file mode 100644 index 00000000..90f81624 --- /dev/null +++ b/qiming-noVNC/app/locale/tr.json @@ -0,0 +1,69 @@ +{ + "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." +} \ No newline at end of file diff --git a/qiming-noVNC/app/locale/zh_CN.json b/qiming-noVNC/app/locale/zh_CN.json new file mode 100644 index 00000000..671be6d3 --- /dev/null +++ b/qiming-noVNC/app/locale/zh_CN.json @@ -0,0 +1,107 @@ +{ + "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": "剪贴板", + "Auto sync clipboard": "自动同步剪贴板", + "Auto sync enabled": "自动同步已开启", + "Clipboard permission denied": "剪贴板权限被拒绝", + "Clipboard API not supported": "浏览器不支持剪贴板 API", + "Read from local clipboard": "已读取本地剪贴板", + "Copied to local clipboard": "已复制到本地剪贴板", + "Failed to read clipboard": "读取剪贴板失败", + "Failed to write clipboard": "写入剪贴板失败", + "Read Local": "读取本地", + "Send Remote": "发送远程", + "Copy Local": "复制本地", + "Read from local clipboard": "从本地剪贴板读取", + "Send to remote clipboard": "发送到远程剪贴板", + "Copy to local 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。" +} \ No newline at end of file diff --git a/qiming-noVNC/app/locale/zh_TW.json b/qiming-noVNC/app/locale/zh_TW.json new file mode 100644 index 00000000..9d292a31 --- /dev/null +++ b/qiming-noVNC/app/locale/zh_TW.json @@ -0,0 +1,69 @@ +{ + "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": "取消" +} \ No newline at end of file diff --git a/qiming-noVNC/app/localization.js b/qiming-noVNC/app/localization.js new file mode 100644 index 00000000..c8257fdb --- /dev/null +++ b/qiming-noVNC/app/localization.js @@ -0,0 +1,206 @@ +/* + * 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); diff --git a/qiming-noVNC/app/sounds/CREDITS b/qiming-noVNC/app/sounds/CREDITS new file mode 100644 index 00000000..ec1fb556 --- /dev/null +++ b/qiming-noVNC/app/sounds/CREDITS @@ -0,0 +1,4 @@ +bell + Copyright: Dr. Richard Boulanger et al + URL: http://www.archive.org/details/Berklee44v12 + License: CC-BY Attribution 3.0 Unported diff --git a/qiming-noVNC/app/sounds/bell.mp3 b/qiming-noVNC/app/sounds/bell.mp3 new file mode 100644 index 00000000..fdbf149a Binary files /dev/null and b/qiming-noVNC/app/sounds/bell.mp3 differ diff --git a/qiming-noVNC/app/sounds/bell.oga b/qiming-noVNC/app/sounds/bell.oga new file mode 100644 index 00000000..144d2b36 Binary files /dev/null and b/qiming-noVNC/app/sounds/bell.oga differ diff --git a/qiming-noVNC/app/styles/Orbitron700.ttf b/qiming-noVNC/app/styles/Orbitron700.ttf new file mode 100644 index 00000000..e28729dc Binary files /dev/null and b/qiming-noVNC/app/styles/Orbitron700.ttf differ diff --git a/qiming-noVNC/app/styles/Orbitron700.woff b/qiming-noVNC/app/styles/Orbitron700.woff new file mode 100644 index 00000000..61db630c Binary files /dev/null and b/qiming-noVNC/app/styles/Orbitron700.woff differ diff --git a/qiming-noVNC/app/styles/base.css b/qiming-noVNC/app/styles/base.css new file mode 100644 index 00000000..9edc7a63 --- /dev/null +++ b/qiming-noVNC/app/styles/base.css @@ -0,0 +1,1296 @@ +/* + * 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: #ffffff; + 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; + z-index: 5; +} + +.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%; + /*
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 button, +.noVNC_panel select, +.noVNC_panel textarea, +.noVNC_panel input:not([type=checkbox]):not([type=radio]) { + margin-left: 6px; + /* Prevent inputs in panels from being too wide */ + max-width: calc(100% - 6px - var(--input-xpadding) * 2); +} + +.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_controls { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 10px; + padding: 8px 0; + border-bottom: 1px solid rgba(255, 255, 255, 0.1); +} + +/* 自动同步开关标签 */ +.noVNC_clipboard_sync_label { + display: flex; + align-items: center; + gap: 8px; + cursor: pointer; + font-size: 13px; +} + +.noVNC_clipboard_sync_label span { + user-select: none; +} + +/* 同步状态显示 */ +.noVNC_clipboard_sync_status { + display: none; + align-items: center; + gap: 6px; + font-size: 12px; + color: rgba(255, 255, 255, 0.7); +} + +/* 状态指示点 */ +.noVNC_sync_status_dot { + width: 8px; + height: 8px; + border-radius: 50%; + background-color: #888; + transition: background-color 0.3s ease; +} + +.noVNC_sync_status_dot.active { + background-color: #4CAF50; + box-shadow: 0 0 6px rgba(76, 175, 80, 0.6); +} + +.noVNC_sync_status_dot.inactive { + background-color: #888; +} + +.noVNC_sync_status_dot.success { + background-color: #2196F3; + box-shadow: 0 0 6px rgba(33, 150, 243, 0.6); +} + +.noVNC_sync_status_dot.error { + background-color: #F44336; + box-shadow: 0 0 6px rgba(244, 67, 54, 0.6); +} + +/* 快捷按钮容器 */ +.noVNC_clipboard_buttons { + display: flex; + gap: 8px; + margin-bottom: 10px; +} + +/* 快捷按钮样式 */ +.noVNC_clipboard_btn { + flex: 1; + padding: 8px 12px; + font-size: 12px; + border: none; + border-radius: 4px; + background-color: var(--novnc-buttongrey); + color: white; + cursor: pointer; + transition: background-color 0.2s ease, transform 0.1s ease; + white-space: nowrap; +} + +.noVNC_clipboard_btn:hover { + background-color: var(--novnc-blue); +} + +.noVNC_clipboard_btn:active { + transform: scale(0.98); +} + +.noVNC_clipboard_btn:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +#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_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; + z-index: 10; +} + +#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: #333; + background: rgba(255, 255, 255, 0.8); + 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 { + position: relative; + width: 100%; + height: 100%; + background-color: #ffffff; + display: flex; + align-items: center; + justify-content: center; + /* 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; +} + +/* Background image with opacity */ +#noVNC_container::before { + content: ""; + position: absolute; + top: -20px; + left: -20px; + width: calc(100% + 40px); + height: calc(100% + 40px); + background-image: url("../images/bg.jpg"); + background-size: cover; + background-position: center; + background-repeat: no-repeat; + opacity: 0.6; + filter: blur(8px); + -webkit-filter: blur(8px); + z-index: 0; + pointer-events: none; + transform: scale(1.5); + -webkit-transform: scale(1.5); +} + +/* Background placeholder - shows before VNC connection */ +#noVNC_background_placeholder { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 100%; + aspect-ratio: 1920 / 1080; + background-image: url("../images/bg.jpg"); + background-size: 100% 100%; + background-position: center; + background-repeat: no-repeat; + z-index: 1; +} + +/* Hide placeholder when connected */ +:root.noVNC_connected #noVNC_background_placeholder { + display: 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; + } +} + +/* ---------------------------------------- + * Audio Panel (音频面板) + * ---------------------------------------- + */ + +#noVNC_audio_button.noVNC_audio_active { + background-color: var(--novnc-green); +} + +#noVNC_audio { + min-width: 180px; +} + +.noVNC_audio_controls { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 10px; +} + +.noVNC_audio_toggle_btn { + padding: 8px 16px; + border: none; + border-radius: 6px; + background: var(--novnc-blue); + color: white; + font-size: 14px; + cursor: pointer; + transition: all 0.2s ease; + width: 100%; +} + +.noVNC_audio_toggle_btn:hover { + background: var(--novnc-darkblue); +} + +.noVNC_audio_toggle_btn.active { + background: var(--novnc-green); +} + +.noVNC_audio_controls label { + font-size: 12px; + white-space: nowrap; +} + +.noVNC_audio_controls input[type="range"] { + flex: 1; + min-width: 80px; + height: 4px; + -webkit-appearance: none; + appearance: none; + background: var(--novnc-light-grey); + border-radius: 2px; + outline: none; +} + +.noVNC_audio_controls input[type="range"]::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 14px; + height: 14px; + border-radius: 50%; + background: var(--novnc-green); + cursor: pointer; +} + +.noVNC_audio_controls input[type="range"]::-moz-range-thumb { + width: 14px; + height: 14px; + border-radius: 50%; + background: var(--novnc-green); + cursor: pointer; + border: none; +} + +#noVNC_audio_volume_value { + font-size: 12px; + min-width: 35px; + text-align: right; +} + +.noVNC_audio_status, +.noVNC_ime_status { + display: flex; + align-items: center; + gap: 8px; + padding: 6px 10px; + background: var(--novnc-light-grey); + border-radius: 4px; + font-size: 12px; +} + +.noVNC_status_dot { + width: 8px; + height: 8px; + border-radius: 50%; + background: var(--novnc-red); +} + +.noVNC_audio_status.connected .noVNC_status_dot, +.noVNC_ime_status.connected .noVNC_status_dot { + background: var(--novnc-green); +} + +.noVNC_audio_status.connecting .noVNC_status_dot, +.noVNC_ime_status.connecting .noVNC_status_dot { + background: var(--novnc-yellow); + animation: noVNC_status_blink 1s ease-in-out infinite; +} + +@keyframes noVNC_status_blink { + + 0%, + 100% { + opacity: 1; + } + + 50% { + opacity: 0.4; + } +} + +/* ---------------------------------------- + * IME Panel (输入法面板) + * ---------------------------------------- + */ + +#noVNC_ime_button.noVNC_ime_active { + background-color: var(--novnc-green); +} + +#noVNC_ime { + min-width: 150px; +} \ No newline at end of file diff --git a/qiming-noVNC/app/styles/constants.css b/qiming-noVNC/app/styles/constants.css new file mode 100644 index 00000000..1123a3ef --- /dev/null +++ b/qiming-noVNC/app/styles/constants.css @@ -0,0 +1,30 @@ +/* + * 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; +} diff --git a/qiming-noVNC/app/styles/input.css b/qiming-noVNC/app/styles/input.css new file mode 100644 index 00000000..8273d70a --- /dev/null +++ b/qiming-noVNC/app/styles/input.css @@ -0,0 +1,628 @@ +/* + * 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 + + +
+ + + 80% +
+
+ + 未连接 +
+ + + + +
+ +
+ + + +
+
+ + + + + + +
+
+ + + +
+
+
+ Power +
+ + + +
+
+ + + + + + + + +
+
+
+ Clipboard +
+ +
+ +
+ + +
+
+ +
+ + + +
+

+ Edit clipboard content in the textarea below. +

+ +
+
+ + + + + + +
+
+
+ Settings +
+
    +
  • + +
  • +
  • + +
  • +
  • +
    +
  • +
  • + +
  • +
  • + + +
  • +
  • +
    +
  • +
  • +
    Advanced
    +
    +
      +
    • + + +
    • +
    • + + +
    • +
    • +
      +
    • +
    • + + +
    • +
    • +
      WebSocket
      +
      +
        +
      • + +
      • +
      • + + +
      • +
      • + + +
      • +
      • + + +
      • +
      +
      +
    • +
    • +
      +
    • +
    • + +
    • +
    • + + +
    • +
    • +
      +
    • +
    • + +
    • +
    • +
      +
    • + +
    • + +
    • +
    +
    +
  • +
  • +
    +
  • +
  • + Version: + +
  • +
+
+
+ + + + + + + + + +
+
+
+
+ + +
+ + +
+
+
+ +
+
+
+ + +
+
+
+
+ Server identity +
+
+ The server has provided the following identifying information: +
+
+ Fingerprint: + +
+
+ Please verify that the information is correct and press + "Approve". Otherwise press "Reject". +
+
+ + +
+
+
+
+ + +
+
+
+
+ Credentials +
+
+ + +
+
+ + +
+
+ +
+
+
+
+ + +
+
+
+ +
+
+
+ + +
+ +
+ + +
+ + + + + \ No newline at end of file diff --git a/qiming-noVNC/vnc_lite.html b/qiming-noVNC/vnc_lite.html new file mode 100644 index 00000000..79d48146 --- /dev/null +++ b/qiming-noVNC/vnc_lite.html @@ -0,0 +1,180 @@ + + + + + + noVNC + + + + + + + +
+
Loading
+
Send CtrlAltDel
+
+
+ +
+ +