Compare commits

..

72 Commits

Author SHA1 Message Date
hstyi
aee34415a7 release: 2.0.0-beta.11 2025-08-08 13:41:31 +08:00
hstyi
e4e70cc72c chore: osx dist 2025-08-08 13:37:27 +08:00
hstyi
49779fe8f2 feat: tab order settings 2025-08-08 10:02:16 +08:00
hstyi
969ddc3662 feat: add tab index 2025-08-07 23:47:52 +08:00
hstyi
de9b418c75 feat: editor support maximization 2025-08-07 16:44:31 +08:00
hstyi
f8588745cd fix: compression not working & popup not closing 2025-08-07 16:44:19 +08:00
hstyi
7c0cbab187 chore: improve subversion 2025-08-07 13:17:31 +08:00
hstyi
176fa64de0 chore: improve osx pack 2025-08-07 11:29:13 +08:00
hstyi
495ab69195 chore: improve ssh loading 2025-08-07 11:28:59 +08:00
hstyi
93c28242fb chore: App Store 2025-08-06 19:56:06 +08:00
hstyi
57662f717b chore: README 2025-08-06 18:56:38 +08:00
dependabot[bot]
3669bd1f88 chore(deps): bump com.github.hstyi:geolite2
Bumps com.github.hstyi:geolite2 from v1.0-202507280101 to v1.0-202508040102.

---
updated-dependencies:
- dependency-name: com.github.hstyi:geolite2
  dependency-version: v1.0-202508040102
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-05 21:43:25 +08:00
dependabot[bot]
00e695b7d5 chore(deps): bump org.commonmark:commonmark from 0.25.0 to 0.25.1
Bumps [org.commonmark:commonmark](https://github.com/commonmark/commonmark-java) from 0.25.0 to 0.25.1.
- [Release notes](https://github.com/commonmark/commonmark-java/releases)
- [Changelog](https://github.com/commonmark/commonmark-java/blob/main/CHANGELOG.md)
- [Commits](https://github.com/commonmark/commonmark-java/compare/commonmark-parent-0.25.0...commonmark-parent-0.25.1)

---
updated-dependencies:
- dependency-name: org.commonmark:commonmark
  dependency-version: 0.25.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-04 12:13:31 +08:00
dependabot[bot]
02c92e6019 chore(deps): bump commons-net:commons-net from 3.11.1 to 3.12.0
Bumps [commons-net:commons-net](https://github.com/apache/commons-net) from 3.11.1 to 3.12.0.
- [Changelog](https://github.com/apache/commons-net/blob/master/RELEASE-NOTES.txt)
- [Commits](https://github.com/apache/commons-net/compare/rel/commons-net-3.11.1...rel/commons-net-3.12.0)

---
updated-dependencies:
- dependency-name: commons-net:commons-net
  dependency-version: 3.12.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-04 12:13:09 +08:00
hstyi
8ba74f0846 fix: terminal exception caused by reconnection 2025-08-01 18:10:34 +08:00
hstyi
79ed6d3858 feat: support right click copy and paste 2025-08-01 17:39:19 +08:00
hstyi
8a66606275 chore: match case tooltip 2025-08-01 11:44:24 +08:00
dependabot[bot]
3ebdf73fbf chore(deps): bump exposed from 1.0.0-beta-4 to 1.0.0-beta-5
Bumps `exposed` from 1.0.0-beta-4 to 1.0.0-beta-5.

Updates `org.jetbrains.exposed:exposed-core` from 1.0.0-beta-4 to 1.0.0-beta-5
- [Release notes](https://github.com/JetBrains/Exposed/releases)
- [Changelog](https://github.com/JetBrains/Exposed/blob/main/CHANGELOG.md)
- [Commits](https://github.com/JetBrains/Exposed/commits)

Updates `org.jetbrains.exposed:exposed-crypt` from 1.0.0-beta-4 to 1.0.0-beta-5
- [Release notes](https://github.com/JetBrains/Exposed/releases)
- [Changelog](https://github.com/JetBrains/Exposed/blob/main/CHANGELOG.md)
- [Commits](https://github.com/JetBrains/Exposed/commits)

Updates `org.jetbrains.exposed:exposed-jdbc` from 1.0.0-beta-4 to 1.0.0-beta-5
- [Release notes](https://github.com/JetBrains/Exposed/releases)
- [Changelog](https://github.com/JetBrains/Exposed/blob/main/CHANGELOG.md)
- [Commits](https://github.com/JetBrains/Exposed/commits)

Updates `org.jetbrains.exposed:exposed-migration` from 1.0.0-beta-4 to 1.0.0-beta-5
- [Release notes](https://github.com/JetBrains/Exposed/releases)
- [Changelog](https://github.com/JetBrains/Exposed/blob/main/CHANGELOG.md)
- [Commits](https://github.com/JetBrains/Exposed/commits)

---
updated-dependencies:
- dependency-name: org.jetbrains.exposed:exposed-core
  dependency-version: 1.0.0-beta-5
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.jetbrains.exposed:exposed-crypt
  dependency-version: 1.0.0-beta-5
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.jetbrains.exposed:exposed-jdbc
  dependency-version: 1.0.0-beta-5
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.jetbrains.exposed:exposed-migration
  dependency-version: 1.0.0-beta-5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-01 11:09:44 +08:00
hstyi
d249e5da5a fix: XTerm Set Scrolling Region 2025-07-31 16:57:33 +08:00
hstyi
7243e933e6 fix: aura selection text color 2025-07-31 16:22:32 +08:00
hstyi
f92e43ee41 release: 2.0.0-beta.10 2025-07-31 10:33:37 +08:00
dependabot[bot]
f6243e33da chore(deps): bump com.github.hstyi:geolite2
Bumps com.github.hstyi:geolite2 from v1.0-202507070058 to v1.0-202507280101.

---
updated-dependencies:
- dependency-name: com.github.hstyi:geolite2
  dependency-version: v1.0-202507280101
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-31 10:28:32 +08:00
hstyi
72f334d572 chore: import CSV with password support 2025-07-31 10:28:17 +08:00
dependabot[bot]
68cbb10dec chore(deps): bump org.apache.commons:commons-csv from 1.14.0 to 1.14.1
Bumps [org.apache.commons:commons-csv](https://github.com/apache/commons-csv) from 1.14.0 to 1.14.1.
- [Changelog](https://github.com/apache/commons-csv/blob/master/RELEASE-NOTES.txt)
- [Commits](https://github.com/apache/commons-csv/compare/rel/commons-csv-1.14.0...rel/commons-csv-1.14.1)

---
updated-dependencies:
- dependency-name: org.apache.commons:commons-csv
  dependency-version: 1.14.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-31 10:26:16 +08:00
dependabot[bot]
45f5c4ee91 chore(deps): bump com.qcloud:cos_api from 5.6.247 to 5.6.249
Bumps [com.qcloud:cos_api](https://github.com/tencentyun/cos-java-sdk-v5) from 5.6.247 to 5.6.249.
- [Release notes](https://github.com/tencentyun/cos-java-sdk-v5/releases)
- [Changelog](https://github.com/tencentyun/cos-java-sdk-v5/blob/master/CHANGELOG.md)
- [Commits](https://github.com/tencentyun/cos-java-sdk-v5/commits)

---
updated-dependencies:
- dependency-name: com.qcloud:cos_api
  dependency-version: 5.6.249
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-30 12:29:12 +08:00
dependabot[bot]
48c511613e chore(deps): bump org.apache.commons:commons-compress
Bumps [org.apache.commons:commons-compress](https://github.com/apache/commons-compress) from 1.27.1 to 1.28.0.
- [Changelog](https://github.com/apache/commons-compress/blob/master/RELEASE-NOTES.txt)
- [Commits](https://github.com/apache/commons-compress/compare/rel/commons-compress-1.27.1...rel/commons-compress-1.28.0)

---
updated-dependencies:
- dependency-name: org.apache.commons:commons-compress
  dependency-version: 1.28.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-30 12:29:01 +08:00
hstyi
c94063d459 fix: SSH timeout is always 30 seconds 2025-07-29 17:57:14 +08:00
hstyi
c26aafd831 chore: heartbeat interval 60 seconds 2025-07-29 17:31:00 +08:00
hstyi
a5638329e7 feat: support transfer double-click behavior 2025-07-29 09:23:31 +08:00
hstyi
8323f8eb5d fix: missing placeholder 2025-07-28 19:46:26 +08:00
hstyi
35199ed094 chore: RDP encrypt password on Windows 2025-07-28 10:02:02 +08:00
dependabot[bot]
b5d53cf416 chore(deps): bump org.xerial:sqlite-jdbc from 3.50.2.0 to 3.50.3.0
Bumps [org.xerial:sqlite-jdbc](https://github.com/xerial/sqlite-jdbc) from 3.50.2.0 to 3.50.3.0.
- [Release notes](https://github.com/xerial/sqlite-jdbc/releases)
- [Changelog](https://github.com/xerial/sqlite-jdbc/blob/master/CHANGELOG)
- [Commits](https://github.com/xerial/sqlite-jdbc/compare/3.50.2.0...3.50.3.0)

---
updated-dependencies:
- dependency-name: org.xerial:sqlite-jdbc
  dependency-version: 3.50.3.0
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-27 07:10:19 +08:00
hstyi
39e26a6e3d fix: keyword background color 2025-07-26 07:38:18 +08:00
dependabot[bot]
15cb06af0f chore(deps): bump commons-codec:commons-codec from 1.18.0 to 1.19.0
Bumps [commons-codec:commons-codec](https://github.com/apache/commons-codec) from 1.18.0 to 1.19.0.
- [Changelog](https://github.com/apache/commons-codec/blob/master/RELEASE-NOTES.txt)
- [Commits](https://github.com/apache/commons-codec/compare/rel/commons-codec-1.18.0...rel/commons-codec-1.19.0)

---
updated-dependencies:
- dependency-name: commons-codec:commons-codec
  dependency-version: 1.19.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-25 20:06:59 +08:00
dependabot[bot]
1e0bbb5a00 chore(deps): bump org.apache.commons:commons-text from 1.13.1 to 1.14.0
Bumps [org.apache.commons:commons-text](https://github.com/apache/commons-text) from 1.13.1 to 1.14.0.
- [Changelog](https://github.com/apache/commons-text/blob/master/RELEASE-NOTES.txt)
- [Commits](https://github.com/apache/commons-text/compare/rel/commons-text-1.13.1...rel/commons-text-1.14.0)

---
updated-dependencies:
- dependency-name: org.apache.commons:commons-text
  dependency-version: 1.14.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-25 20:06:48 +08:00
hstyi
fb6fdbc14c chore: sync plugin 0.0.4 2025-07-23 09:45:30 +08:00
dependabot[bot]
96df53ce40 chore(deps): bump commons-io:commons-io from 2.19.0 to 2.20.0
Bumps [commons-io:commons-io](https://github.com/apache/commons-io) from 2.19.0 to 2.20.0.
- [Changelog](https://github.com/apache/commons-io/blob/master/RELEASE-NOTES.txt)
- [Commits](https://github.com/apache/commons-io/compare/rel/commons-io-2.19.0...rel/commons-io-2.20.0)

---
updated-dependencies:
- dependency-name: commons-io:commons-io
  dependency-version: 2.20.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-21 10:47:18 +08:00
hstyi
42f86dc3a3 chore: linux appimagetool 2025-07-21 10:47:07 +08:00
hstyi
32b11c6063 fix: macOS not opening local terminal 2025-07-21 10:47:07 +08:00
hstyi
b2f43ba439 chore: improve upgrade 2025-07-21 09:58:37 +08:00
hstyi
7d9cfc79cf release: 2.0.0-beta.9 2025-07-20 13:55:38 +08:00
hstyi
5624a44104 chore: vnc clipboard charset 2025-07-20 13:46:59 +08:00
hstyi
2aaa6371ab feat: support VNC protocol 2025-07-20 13:46:59 +08:00
hstyi
fdc1b0c07d chore: compatible with multiple keyboard layouts 2025-07-20 10:14:51 +08:00
hstyi
8ec7e416ea fix: key shortcut not working 2025-07-19 21:27:57 +08:00
hstyi
ae7730fb35 feat: keyword highlight set 2025-07-18 21:47:57 +08:00
hstyi
f99e3e9147 chore: title uses the selected tab 2025-07-18 17:09:56 +08:00
hstyi
5fcda04544 fix: reconnection causing cloned sessions to close unexpectedly 2025-07-18 13:29:25 +08:00
hstyi
aca0fbb1e3 chore: disable keyword highlighting when AlternateScreenBuffer 2025-07-18 10:20:12 +08:00
hstyi
4f8ffbf225 fix: osx notary 2025-07-17 11:28:17 +08:00
hstyi
8b08edf38f chore: osx.yml 2025-07-17 11:22:09 +08:00
hstyi
e3d43111e8 fix: memory leaks 2025-07-17 11:13:31 +08:00
hstyi
6bb9a33a04 feat: terminal preview 2025-07-17 10:22:14 +08:00
hstyi
ca64880a01 chore: windows inno setup 2025-07-17 10:10:06 +08:00
hstyi
c8d70e2a5a chore: Windows MSIX 2025-07-16 14:34:17 +08:00
hstyi
cee7eb8928 fix: tooltip not showing 2025-07-15 13:51:41 +08:00
dependabot[bot]
abc5f203d0 chore(deps): bump org.jetbrains.pty4j:pty4j from 0.13.6 to 0.13.10
Bumps [org.jetbrains.pty4j:pty4j](https://github.com/JetBrains/pty4j) from 0.13.6 to 0.13.10.
- [Commits](https://github.com/JetBrains/pty4j/commits)

---
updated-dependencies:
- dependency-name: org.jetbrains.pty4j:pty4j
  dependency-version: 0.13.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-15 13:51:05 +08:00
hstyi
c3fc8f8e12 chore: improve extension 2025-07-15 10:31:46 +08:00
hstyi
afd4f8baa9 fix: alt modifier echo problem 2025-07-15 10:12:59 +08:00
hstyi
b5ca8b988c chore: support plugin incompatibility 2025-07-15 09:34:38 +08:00
hstyi
89b22ba984 feat: support modifying log format 2025-07-14 16:45:50 +08:00
hstyi
f329ef60df release: 2.0.0-beta.8 2025-07-14 14:20:44 +08:00
hstyi
8acfdb8bca feat: transfer supports copy and paste 2025-07-14 13:50:12 +08:00
hstyi
a7aec52f2a fix: password text field status 2025-07-14 11:56:59 +08:00
hstyi
7f1317a9a7 chore: improve terminal options 2025-07-14 11:11:20 +08:00
hstyi
a8a1fea91b feat: support focus mode 2025-07-14 11:02:40 +08:00
hstyi
675ad4608a chore: improve keymap refresh 2025-07-11 14:57:24 +08:00
hstyi
72ba3757e2 chore: discussion group 2025-07-11 11:24:29 +08:00
hstyi
c58e84d2ae chore: windows action 2025-07-11 10:05:38 +08:00
hstyi
18a7a5059b feat: keyword highlight support import and export 2025-07-11 09:20:24 +08:00
hstyi
f0102b6f13 fix: windows tray icon size 2025-07-10 16:13:50 +08:00
hstyi
0cf8eb3c17 chore: README 2025-07-10 12:14:33 +08:00
291 changed files with 19195 additions and 1675 deletions

View File

@@ -3,7 +3,8 @@ name: Linux
on: [ push, pull_request ]
env:
DOCKER_NAME: hstyi/jbr:21.0.7b1038.58
JBR_MAJOR: 21.0.7
JBR_PATCH: b1038.58
jobs:
build:
@@ -14,7 +15,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-depth: 1
- uses: actions/cache@v4
with:
@@ -25,6 +26,10 @@ jobs:
restore-keys: |
${{ runner.os }}-${{ runner.arch }}-gradlexyz-
- name: Set dynamic DOCKER_NAME
run: |
echo "DOCKER_NAME=hstyi/jbr:${{ env.JBR_MAJOR }}${{ env.JBR_PATCH }}" >> $GITHUB_ENV
- name: Create docker-run.sh helper script
shell: bash
run: |
@@ -54,11 +59,23 @@ jobs:
shell: bash
run: sudo chmod -R 777 ~/.gradle
- name: Upload artifact
- name: Upload targz artifact
uses: actions/upload-artifact@v4
with:
name: termora-linux-${{ runner.arch }}
name: termora-linux-targz-${{ runner.arch }}
path: |
build/distributions/*.tar.gz
- name: Upload AppImage artifact
uses: actions/upload-artifact@v4
with:
name: termora-linux-AppImage-${{ runner.arch }}
path: |
build/distributions/*.AppImage
- name: Upload deb artifact
uses: actions/upload-artifact@v4
with:
name: termora-linux-deb-${{ runner.arch }}
path: |
build/distributions/*.deb

View File

@@ -3,11 +3,13 @@ name: macOS
on: [ push, pull_request ]
env:
TERMORA_MAC_SIGN: "${{ startsWith(github.event.head_commit.message, 'release: ') && github.repository == 'TermoraDev/termora' }}"
TERMORA_MAC_SIGN: "${{ github.repository == 'TermoraDev/termora' }}"
TERMORA_MAC_SIGN_USER_NAME: ${{ secrets.TERMORA_MAC_SIGN_USER_NAME }}
# 只有发布版本时才需要公证
TERMORA_MAC_NOTARY: "${{ startsWith(github.event.head_commit.message, 'release: ') && github.repository == 'TermoraDev/termora' }}"
TERMORA_MAC_NOTARY_KEYCHAIN_PROFILE: ${{ secrets.TERMORA_MAC_NOTARY_KEYCHAIN_PROFILE }}
JBR_MAJOR: 21.0.7
JBR_PATCH: b1038.58
jobs:
build:
@@ -18,10 +20,10 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-depth: 1
- name: Install the Apple certificate
if: "startsWith(github.event.head_commit.message, 'release: ') && github.repository == 'TermoraDev/termora' && env.BUILD_CERTIFICATE_BASE64 != ''"
if: ${{ fromJSON(env.TERMORA_MAC_SIGN) && env.BUILD_CERTIFICATE_BASE64 != '' }}
env:
BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}
P12_PASSWORD: ${{ secrets.P12_PASSWORD }}
@@ -44,7 +46,7 @@ jobs:
security list-keychain -d user -s $KEYCHAIN_PATH
- name: Setup the Notary information
if: "startsWith(github.event.head_commit.message, 'release: ') && github.repository == 'TermoraDev/termora' && env.APPLE_ID != ''"
if: ${{ fromJSON(env.TERMORA_MAC_NOTARY) && env.APPLE_ID != '' }}
env:
APPLE_ID: ${{ secrets.APPLE_ID }}
TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
@@ -60,7 +62,7 @@ jobs:
else
ARCH="x64"
fi
wget -q -O $RUNNER_TEMP/java_package.tar.gz https://cache-redirector.jetbrains.com/intellij-jbr/jbrsdk-21.0.7-osx-$ARCH-b1034.51.tar.gz
wget -q -O $RUNNER_TEMP/java_package.tar.gz https://cache-redirector.jetbrains.com/intellij-jbr/jbrsdk-${{ env.JBR_MAJOR }}-osx-$ARCH-${{ env.JBR_PATCH }}.tar.gz
# install jdk
- name: Installing Java
@@ -79,6 +81,10 @@ jobs:
restore-keys: |
${{ runner.os }}-${{ runner.arch }}-gradle-
- name: Install create-dmg
shell: bash
run: brew install create-dmg
- name: Compile
shell: bash
run: ./gradlew :check-license && ./gradlew classes -x test
@@ -91,10 +97,9 @@ jobs:
shell: bash
run: ./gradlew :jpackage && ./gradlew :dist
- name: Upload artifact
- name: Upload dmg artifact
uses: actions/upload-artifact@v4
with:
name: termora-osx-${{ runner.arch }}
name: termora-osx-dmg-${{ runner.arch }}
path: |
build/distributions/*.zip
build/distributions/*.dmg

View File

@@ -1,55 +0,0 @@
name: Windows x86-64
on: [ push, pull_request ]
jobs:
build:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install zip
run: |
$system32 = [System.Environment]::GetEnvironmentVariable("WINDIR") + "\System32"
Invoke-WebRequest -Uri "http://stahlworks.com/dev/zip.exe" -OutFile "$system32\zip.exe"
Invoke-WebRequest -Uri "http://stahlworks.com/dev/unzip.exe" -OutFile "$system32\unzip.exe"
- name: Install 7z
uses: milliewalky/setup-7-zip@v2
- name: Installing Java
run: |
curl -s --output ${{ runner.temp }}\java_package.zip -L https://cache-redirector.jetbrains.com/intellij-jbr/jbrsdk-21.0.7-windows-x64-b1034.51.zip
unzip -q ${{ runner.temp }}\java_package.zip -d ${{ runner.temp }}\jbr
echo "JAVA_HOME=${{ runner.temp }}\jbr\jbrsdk-21.0.7-windows-x64-b1034.51" >> $env:GITHUB_ENV
- uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-${{ runner.arch }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-${{ runner.arch }}-gradle-
- name: Compile
run: .\gradlew :check-license && .\gradlew classes -x test
- name: JLink
run: .\gradlew :jar :copy-dependencies :plugins:migration:build :jlink
- name: Package
run: .\gradlew :jpackage && .\gradlew :dist
- name: Stop Gradle
run: .\gradlew.bat --stop
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: termora-windows-x86-64
path: |
build/distributions/*.zip
build/distributions/*.exe

116
.github/workflows/windows.yml vendored Normal file
View File

@@ -0,0 +1,116 @@
name: Windows
on: [ push, pull_request ]
env:
JBR_MAJOR: 21.0.7
JBR_PATCH: b1038.58
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ windows-11-arm, windows-2022 ]
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Setup MSbuild
uses: microsoft/setup-msbuild@v2
- name: Set architecture
id: set-arch
run: |
if ($env:PROCESSOR_ARCHITECTURE -eq "ARM64") {
echo "ARCH=aarch64" >> $env:GITHUB_ENV
} else {
echo "ARCH=x64" >> $env:GITHUB_ENV
}
- name: Find MakeAppx
shell: pwsh
run: |
$installedRootsKey = "HKLM:\SOFTWARE\Microsoft\Windows Kits\Installed Roots"
$kitsRoot = (Get-ItemProperty $installedRootsKey).KitsRoot10
$versions = Get-ChildItem -Path $installedRootsKey | Select-Object -ExpandProperty PSChildName
$maxVersion = $versions | ForEach-Object { [version]$_ } | Sort-Object -Descending | Select-Object -First 1
$arch = if ($env:ARCH -eq "aarch64") { "arm64" } else { "x64" }
$makeAppXPath = Join-Path -Path $kitsRoot -ChildPath "bin\$maxVersion\$arch\makeappx.exe"
Write-Output "MakeAppx.exe path: $makeAppXPath"
if (Test-Path $makeAppXPath) {
"MAKEAPPX_PATH=$makeAppXPath" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append
} else {
Write-Output "MakeAppx.exe not found!"
exit 1
}
- name: Install zip
run: |
$system32 = [System.Environment]::GetEnvironmentVariable("WINDIR") + "\System32"
Invoke-WebRequest -Uri "http://stahlworks.com/dev/zip.exe" -OutFile "$system32\zip.exe"
Invoke-WebRequest -Uri "http://stahlworks.com/dev/unzip.exe" -OutFile "$system32\unzip.exe"
- name: Install 7z
uses: milliewalky/setup-7-zip@v2
- name: Installing Java
run: |
$zipPath = "${{ runner.temp }}\java_package.zip"
$extractDir = "${{ runner.temp }}\jbr"
$url = "https://cache-redirector.jetbrains.com/intellij-jbr/jbrsdk-${{ env.JBR_MAJOR }}-windows-${{ env.ARCH }}-${{ env.JBR_PATCH }}.zip"
curl -s --output $zipPath -L $url
unzip -q $zipPath -d $extractDir
$jbrDir = Get-ChildItem $extractDir | Select-Object -First 1
echo "JAVA_HOME=$($jbrDir.FullName)" >> $env:GITHUB_ENV
- uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-${{ runner.arch }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-${{ runner.arch }}-gradle-
- name: Compile
run: .\gradlew :check-license && .\gradlew classes -x test
- name: JLink
run: .\gradlew :jar :copy-dependencies :plugins:migration:build :jlink
- name: Package
run: .\gradlew :jpackage && .\gradlew :dist
- name: MSIX
env:
TERMORA_TYPE: appx
run: |
.\gradlew --stop
.\gradlew :dist
- name: Stop Gradle
run: .\gradlew.bat --stop
- name: Upload zip artifact
uses: actions/upload-artifact@v4
with:
name: termora-windows-zip-${{ runner.arch }}
path: |
build/distributions/*.zip
- name: Upload exe artifact
uses: actions/upload-artifact@v4
with:
name: termora-windows-exe-${{ runner.arch }}
path: |
build/distributions/*.exe
- name: Upload msix artifact
uses: actions/upload-artifact@v4
with:
name: termora-windows-msix-${{ runner.arch }}
path: |
build/distributions/*.msix

View File

@@ -82,6 +82,7 @@ Termora is developed using [**Kotlin/JVM**](https://kotlinlang.org/) and partial
- 🧾 [Latest Release](https://github.com/TermoraDev/termora/releases/latest)
- 🍺 **Homebrew**: `brew install --cask termora`
- 🔨 **WinGet**: `winget install termora`
- <img src="https://apps.microsoft.com/assets/icons/logo-16x16.png" alt="microsoft logo"/> <b>Microsoft Store</b>: <a href="https://apps.microsoft.com/store/detail/9NRZBHG43SB9?cid=DevShareMCLPCS">Visit Termora in the Microsoft Store</a>
@@ -90,8 +91,6 @@ Termora is developed using [**Kotlin/JVM**](https://kotlinlang.org/) and partial
We recommend using the [JetBrainsRuntime](https://github.com/JetBrains/JetBrainsRuntime) JDK for development.
- Run locally: `./gradlew :run`
- Build for current OS: `./gradlew :dist`
## 📄 License

View File

@@ -80,6 +80,7 @@ Termora 使用 [**Kotlin/JVM**](https://kotlinlang.org/) 开发,支持(正
- 🧾 [Latest release](https://github.com/TermoraDev/termora/releases/latest)
- 🍺 **Homebrew**`brew install --cask termora`
- 🪟 **WinGet**`winget install termora`
- <img src="https://apps.microsoft.com/assets/icons/logo-16x16.png" alt="microsoft logo"/> <b>Microsoft Store</b>: <a href="https://apps.microsoft.com/store/detail/9NRZBHG43SB9?cid=DevShareMCLPCS">Termora</a>
@@ -88,8 +89,6 @@ Termora 使用 [**Kotlin/JVM**](https://kotlinlang.org/) 开发,支持(正
建议使用 [JetBrainsRuntime](https://github.com/JetBrains/JetBrainsRuntime) JDK 运行环境。
- 本地运行:`./gradlew :run`
- 构建当前系统安装包:`./gradlew :dist`
## 📄 授权协议

View File

@@ -1 +1 @@
2.0.0-beta.7
2.0.0-beta.11

View File

@@ -1,3 +1,4 @@
import org.apache.tools.ant.filters.ReplaceTokens
import org.gradle.internal.jvm.Jvm
import org.gradle.kotlin.dsl.support.uppercaseFirstChar
import org.gradle.nativeplatform.platform.internal.ArchitectureInternal
@@ -28,7 +29,10 @@ version = rootProject.projectDir.resolve("VERSION").readText().trim()
val os: OperatingSystem = DefaultNativePlatform.getCurrentOperatingSystem()
val arch: ArchitectureInternal = DefaultNativePlatform.getCurrentArchitecture()
val appVersion = project.version.toString().split("-")[0]
val makeAppx = if (os.isWindows) StringUtils.defaultString(System.getenv("MAKEAPPX_PATH")) else StringUtils.EMPTY
val isDeb = os.isLinux && System.getenv("TERMORA_TYPE") == "deb"
val isAppx = os.isWindows && makeAppx.isNotBlank() && System.getenv("TERMORA_TYPE") == "appx"
val isBeta = project.version.toString().contains("beta", ignoreCase = true)
// macOS 签名信息
val macOSSignUsername = System.getenv("TERMORA_MAC_SIGN_USER_NAME") ?: StringUtils.EMPTY
@@ -169,102 +173,148 @@ publishing {
}
}
tasks.processResources {
val betaVersion = project.version.toString().substringAfterLast('.')
filesMatching("**/AppxManifest.xml") {
filter<ReplaceTokens>(
"tokens" to mapOf(
"version" to appVersion,
"betaVersion" to if (isBeta) betaVersion else "0",
"architecture" to if (arch.isArm64) "arm64" else "x64",
"projectDir" to project.projectDir.absolutePath,
)
)
}
}
tasks.test {
useJUnitPlatform()
}
@Suppress("CascadeIf")
tasks.register<Copy>("copy-dependencies") {
val dir = layout.buildDirectory.dir("libs")
from(configurations.runtimeClasspath).into(dir)
val jna = libs.jna.asProvider().get()
val pty4j = libs.pty4j.get()
val flatlaf = libs.flatlaf.get()
val jSerialComm = libs.jSerialComm.get()
val restart4j = libs.restart4j.get()
val sqlite = libs.sqlite.get()
val archName = if (arch.isArm) "aarch64" else "x86_64"
val dylib = dir.get().dir("dylib").asFile
// 对 JNA 和 PTY4J 的本地库提取
// 提取出来是为了单独签名,不然无法通过公证
if (os.isMacOsX) {
doLast {
val archName = if (arch.isArm) "aarch64" else "x86_64"
val dylib = dir.get().dir("dylib").asFile
for (file in dir.get().asFile.listFiles() ?: emptyArray()) {
if ("${jna.name}-${jna.version}" == file.nameWithoutExtension) {
val targetDir = File(dylib, jna.name)
FileUtils.forceMkdir(targetDir)
doLast {
for (file in dir.get().asFile.listFiles() ?: emptyArray()) {
if ("${jna.name}-${jna.version}" == file.nameWithoutExtension) {
val targetDir = File(dylib, jna.name)
FileUtils.forceMkdir(targetDir)
if (os.isWindows) {
// @formatter:off
exec { commandLine("unzip","-j","-o", file.absolutePath, "com/sun/jna/win32-${arch.name}/*", "-d", targetDir.absolutePath) }
// @formatter:on
} else if (os.isLinux) {
// @formatter:off
exec { commandLine("unzip","-j","-o", file.absolutePath, "com/sun/jna/linux-${arch.name}/*", "-d", targetDir.absolutePath) }
// @formatter:on
} else if (os.isMacOsX) {
// @formatter:off
exec { commandLine("unzip","-j","-o", file.absolutePath, "com/sun/jna/darwin-${arch.name}/*", "-d", targetDir.absolutePath) }
// @formatter:on
// 删除所有二进制类库
exec { commandLine("zip", "-d", file.absolutePath, "com/sun/jna/darwin-*") }
exec { commandLine("zip", "-d", file.absolutePath, "com/sun/jna/win32-*") }
exec { commandLine("zip", "-d", file.absolutePath, "com/sun/jna/sunos-*") }
exec { commandLine("zip", "-d", file.absolutePath, "com/sun/jna/openbsd-*") }
exec { commandLine("zip", "-d", file.absolutePath, "com/sun/jna/linux-*") }
exec { commandLine("zip", "-d", file.absolutePath, "com/sun/jna/freebsd-*") }
exec { commandLine("zip", "-d", file.absolutePath, "com/sun/jna/dragonflybsd-*") }
exec { commandLine("zip", "-d", file.absolutePath, "com/sun/jna/aix-*") }
} else if ("${pty4j.name}-${pty4j.version}" == file.nameWithoutExtension) {
val targetDir = FileUtils.getFile(dylib, pty4j.name, "darwin")
FileUtils.forceMkdir(targetDir)
}
exec { commandLine("zip", "-d", file.absolutePath, "com/sun/jna/win32-*") }
exec { commandLine("zip", "-d", file.absolutePath, "com/sun/jna/linux-*") }
exec { commandLine("zip", "-d", file.absolutePath, "com/sun/jna/darwin-*") }
exec { commandLine("zip", "-d", file.absolutePath, "com/sun/jna/sunos-*") }
exec { commandLine("zip", "-d", file.absolutePath, "com/sun/jna/openbsd-*") }
exec { commandLine("zip", "-d", file.absolutePath, "com/sun/jna/freebsd-*") }
exec { commandLine("zip", "-d", file.absolutePath, "com/sun/jna/dragonflybsd-*") }
exec { commandLine("zip", "-d", file.absolutePath, "com/sun/jna/aix-*") }
} else if ("${pty4j.name}-${pty4j.version}" == file.nameWithoutExtension) {
val osName = if (os.isWindows) "win32" else if (os.isMacOsX) "darwin" else "linux"
val targetDir = FileUtils.getFile(dylib, pty4j.name, osName)
FileUtils.forceMkdir(targetDir)
val myArchName = if (arch.isArm) "aarch64" else "x86-64"
if (os.isWindows) {
// @formatter:off
exec { commandLine("unzip", "-j" , "-o", file.absolutePath, "resources/*win/${myArchName}/*", "-d", targetDir.absolutePath) }
// @formatter:on
} else if (os.isLinux) {
// @formatter:off
exec { commandLine("unzip", "-j" , "-o", file.absolutePath, "resources/*linux/${myArchName}/*", "-d", targetDir.absolutePath) }
// @formatter:on
} else if (os.isMacOsX) {
// @formatter:off
exec { commandLine("unzip", "-j" , "-o", file.absolutePath, "resources/com/pty4j/native/darwin*", "-d", targetDir.absolutePath) }
// @formatter:on
// 删除所有二进制类库
exec { commandLine("zip", "-d", file.absolutePath, "resources/*") }
} else if ("${jSerialComm.name}-${jSerialComm.version}" == file.nameWithoutExtension) {
val targetDir = FileUtils.getFile(dylib, jSerialComm.name, "OSX", archName)
FileUtils.forceMkdir(targetDir)
}
exec { commandLine("zip", "-d", file.absolutePath, "resources/*") }
} else if ("${restart4j.name}-${restart4j.version}" == file.nameWithoutExtension) {
val targetDir = FileUtils.getFile(dylib, restart4j.name)
FileUtils.forceMkdir(targetDir)
if (os.isWindows) {
// @formatter:off
exec { commandLine("unzip", "-j" , "-o", file.absolutePath, "OSX/${archName}/*", "-d", targetDir.absolutePath) }
exec { commandLine("unzip", "-j" , "-o", file.absolutePath, "win32/${archName}/*", "-d", targetDir.absolutePath) }
// @formatter:on
// 删除所有二进制类库
exec { commandLine("zip", "-d", file.absolutePath, "Android/*") }
exec { commandLine("zip", "-d", file.absolutePath, "FreeBSD/*") }
exec { commandLine("zip", "-d", file.absolutePath, "Linux/*") }
exec { commandLine("zip", "-d", file.absolutePath, "OpenBSD/*") }
exec { commandLine("zip", "-d", file.absolutePath, "OSX/*") }
exec { commandLine("zip", "-d", file.absolutePath, "Solaris/*") }
exec { commandLine("zip", "-d", file.absolutePath, "Windows/*") }
} else if ("${restart4j.name}-${restart4j.version}" == file.nameWithoutExtension) {
val targetDir = FileUtils.getFile(dylib, restart4j.name)
FileUtils.forceMkdir(targetDir)
} else if (os.isLinux) {
// @formatter:off
exec { commandLine("unzip", "-j" , "-o", file.absolutePath, "linux/${archName}/*", "-d", targetDir.absolutePath) }
// @formatter:on
} else if (os.isMacOsX) {
// @formatter:off
exec { commandLine("unzip", "-j" , "-o", file.absolutePath, "darwin/${archName}/*", "-d", targetDir.absolutePath) }
// @formatter:on
// 删除所有二进制类库
exec { commandLine("zip", "-d", file.absolutePath, "win32/*") }
exec { commandLine("zip", "-d", file.absolutePath, "darwin/*") }
exec { commandLine("zip", "-d", file.absolutePath, "linux/*") }
// 设置可执行权限
for (e in FileUtils.listFiles(
targetDir,
FileFilterUtils.trueFileFilter(),
FileFilterUtils.falseFileFilter()
)) {
e.setExecutable(true)
}
} else if ("${sqlite.name}-${sqlite.version}" == file.nameWithoutExtension) {
val targetDir = FileUtils.getFile(dylib, sqlite.name)
FileUtils.forceMkdir(targetDir)
}
// 设置可执行权限
for (e in FileUtils.listFiles(
targetDir,
FileFilterUtils.trueFileFilter(),
FileFilterUtils.falseFileFilter()
)) e.setExecutable(true)
exec { commandLine("zip", "-d", file.absolutePath, "win32/*") }
exec { commandLine("zip", "-d", file.absolutePath, "darwin/*") }
exec { commandLine("zip", "-d", file.absolutePath, "linux/*") }
} else if ("${sqlite.name}-${sqlite.version}" == file.nameWithoutExtension) {
val targetDir = FileUtils.getFile(dylib, sqlite.name)
FileUtils.forceMkdir(targetDir)
if (os.isWindows) {
// @formatter:off
exec { commandLine("unzip", "-j" , "-o", file.absolutePath, "org/sqlite/native/Windows/${archName}/*", "-d", targetDir.absolutePath) }
// @formatter:on
} else if (os.isLinux) {
// @formatter:off
exec { commandLine("unzip", "-j" , "-o", file.absolutePath, "org/sqlite/native/Linux/${archName}/*", "-d", targetDir.absolutePath) }
// @formatter:on
} else if (os.isMacOsX) {
// @formatter:off
exec { commandLine("unzip", "-j" , "-o", file.absolutePath, "org/sqlite/native/Mac/${archName}/*", "-d", targetDir.absolutePath) }
// @formatter:on
// 删除所有二进制类库
exec { commandLine("zip", "-d", file.absolutePath, "org/sqlite/native/*") }
} else if ("${flatlaf.name}-${flatlaf.version}" == file.nameWithoutExtension) {
val targetDir = FileUtils.getFile(dylib, flatlaf.name)
FileUtils.forceMkdir(targetDir)
val isArm = arch.isArm
}
exec { commandLine("zip", "-d", file.absolutePath, "org/sqlite/native/*") }
} else if ("${flatlaf.name}-${flatlaf.version}" == file.nameWithoutExtension) {
val targetDir = FileUtils.getFile(dylib, flatlaf.name)
FileUtils.forceMkdir(targetDir)
val isArm = arch.isArm
if (os.isWindows) {
// @formatter:off
exec { commandLine("unzip", "-j" , "-o", file.absolutePath, "com/formdev/flatlaf/natives/*windows*${if (isArm) "arm64" else "x86_64"}*", "-d", targetDir.absolutePath) }
// @formatter:on
} else if (os.isLinux) {
// @formatter:off
exec { commandLine("unzip", "-j" , "-o", file.absolutePath, "com/formdev/flatlaf/natives/*linux*${if (isArm) "arm64" else "x86_64"}*", "-d", targetDir.absolutePath) }
// @formatter:on
} else if (os.isMacOsX) {
// @formatter:off
exec { commandLine("unzip", "-j" , "-o", file.absolutePath, "com/formdev/flatlaf/natives/*macos*${if (isArm) "arm" else "x86"}*", "-d", targetDir.absolutePath) }
// @formatter:on
exec { commandLine("zip", "-d", file.absolutePath, "com/formdev/flatlaf/natives/*") }
}
exec { commandLine("zip", "-d", file.absolutePath, "com/formdev/flatlaf/natives/*") }
}
}
// 对二进制签名
// 对二进制签名
if (os.isMacOsX) {
Files.walk(dylib.toPath()).use { paths ->
for (path in paths) {
if (Files.isRegularFile(path)) {
@@ -273,116 +323,8 @@ tasks.register<Copy>("copy-dependencies") {
}
}
}
} else if (os.isLinux || os.isWindows) { // 缩减安装包
doLast {
for (file in dir.get().asFile.listFiles() ?: emptyArray()) {
if ("${jna.name}-${jna.version}" == file.nameWithoutExtension) {
exec { commandLine("zip", "-d", file.absolutePath, "com/sun/jna/darwin-*") }
exec { commandLine("zip", "-d", file.absolutePath, "com/sun/jna/sunos-*") }
exec { commandLine("zip", "-d", file.absolutePath, "com/sun/jna/openbsd-*") }
exec { commandLine("zip", "-d", file.absolutePath, "com/sun/jna/freebsd-*") }
exec { commandLine("zip", "-d", file.absolutePath, "com/sun/jna/dragonflybsd-*") }
exec { commandLine("zip", "-d", file.absolutePath, "com/sun/jna/aix-*") }
if (os.isWindows) {
exec { commandLine("zip", "-d", file.absolutePath, "com/sun/jna/linux-*") }
if (arch.isArm) {
exec { commandLine("zip", "-d", file.absolutePath, "com/sun/jna/win32-x86*") }
} else {
exec { commandLine("zip", "-d", file.absolutePath, "com/sun/jna/win32-aarch64/*") }
exec { commandLine("zip", "-d", file.absolutePath, "com/sun/jna/win32-x86/*") }
}
} else if (os.isLinux) {
exec { commandLine("zip", "-d", file.absolutePath, "com/sun/jna/win32-*") }
}
} else if ("${pty4j.name}-${pty4j.version}" == file.nameWithoutExtension) {
exec { commandLine("zip", "-d", file.absolutePath, "resources/*darwin*") }
exec { commandLine("zip", "-d", file.absolutePath, "resources/*freebsd*") }
if (os.isWindows) {
exec { commandLine("zip", "-d", file.absolutePath, "resources/*linux*") }
if (arch.isArm) {
exec { commandLine("zip", "-d", file.absolutePath, "resources/*win/x86/*") }
exec { commandLine("zip", "-d", file.absolutePath, "resources/*win/x86-64*") }
} else {
exec { commandLine("zip", "-d", file.absolutePath, "resources/*win/x86/*") }
exec { commandLine("zip", "-d", file.absolutePath, "resources/*win/aarch64/*") }
}
} else if (os.isLinux) {
exec { commandLine("zip", "-d", file.absolutePath, "resources/*win*") }
}
} else if ("${jSerialComm.name}-${jSerialComm.version}" == file.nameWithoutExtension) {
exec { commandLine("zip", "-d", file.absolutePath, "Android/*") }
exec { commandLine("zip", "-d", file.absolutePath, "FreeBSD/*") }
exec { commandLine("zip", "-d", file.absolutePath, "OpenBSD/*") }
exec { commandLine("zip", "-d", file.absolutePath, "OSX/*") }
exec { commandLine("zip", "-d", file.absolutePath, "Solaris/*") }
if (os.isWindows) {
exec { commandLine("zip", "-d", file.absolutePath, "Linux/*") }
} else if (os.isLinux) {
exec { commandLine("zip", "-d", file.absolutePath, "Windows/*") }
}
} else if ("${restart4j.name}-${restart4j.version}" == file.nameWithoutExtension) {
exec { commandLine("zip", "-d", file.absolutePath, "darwin/*") }
if (os.isWindows) {
exec { commandLine("zip", "-d", file.absolutePath, "linux/*") }
if (arch.isArm) {
exec { commandLine("zip", "-d", file.absolutePath, "win32/x86_64/*") }
} else {
exec { commandLine("zip", "-d", file.absolutePath, "win32/aarch64/*") }
}
} else if (os.isLinux) {
exec { commandLine("zip", "-d", file.absolutePath, "win32/*") }
if (arch.isArm) {
exec { commandLine("zip", "-d", file.absolutePath, "linux/x86_64/*") }
} else {
exec { commandLine("zip", "-d", file.absolutePath, "linux/aarch64/*") }
}
}
} else if ("${sqlite.name}-${sqlite.version}" == file.nameWithoutExtension) {
exec { commandLine("zip", "-d", file.absolutePath, "org/sqlite/native/Linux-*") }
exec { commandLine("zip", "-d", file.absolutePath, "org/sqlite/native/FreeBSD/*") }
exec { commandLine("zip", "-d", file.absolutePath, "org/sqlite/native/Mac/*") }
if (os.isWindows) {
exec { commandLine("zip", "-d", file.absolutePath, "org/sqlite/native/Linux/*") }
exec { commandLine("zip", "-d", file.absolutePath, "org/sqlite/native/Windows/armv7/*") }
exec { commandLine("zip", "-d", file.absolutePath, "org/sqlite/native/Windows/x86/*") }
if (arch.isArm) {
exec { commandLine("zip", "-d", file.absolutePath, "org/sqlite/native/Windows/x86_64/*") }
} else {
exec { commandLine("zip", "-d", file.absolutePath, "org/sqlite/native/Windows/aarch64/*") }
}
} else if (os.isLinux) {
exec { commandLine("zip", "-d", file.absolutePath, "org/sqlite/native/Windows/*") }
exec { commandLine("zip", "-d", file.absolutePath, "org/sqlite/native/Linux/arm*") }
exec { commandLine("zip", "-d", file.absolutePath, "org/sqlite/native/Linux/ppc64/*") }
exec { commandLine("zip", "-d", file.absolutePath, "org/sqlite/native/Linux/riscv64/*") }
exec { commandLine("zip", "-d", file.absolutePath, "org/sqlite/native/Linux/x86/*") }
if (arch.isArm) {
exec { commandLine("zip", "-d", file.absolutePath, "org/sqlite/native/Linux/x86_64/*") }
} else {
exec { commandLine("zip", "-d", file.absolutePath, "org/sqlite/native/Linux/aarch64/*") }
}
}
} else if ("${flatlaf.name}-${flatlaf.version}" == file.nameWithoutExtension) {
exec { commandLine("zip", "-d", file.absolutePath, "com/formdev/flatlaf/natives/*macos*") }
if (os.isWindows) {
exec { commandLine("zip", "-d", file.absolutePath, "com/formdev/flatlaf/natives/*linux*") }
if (arch.isArm) {
exec { commandLine("zip", "-d", file.absolutePath, "com/formdev/flatlaf/natives/*x86*") }
} else {
exec { commandLine("zip", "-d", file.absolutePath, "com/formdev/flatlaf/natives/*x86.dll") }
}
} else if (os.isLinux) {
exec { commandLine("zip", "-d", file.absolutePath, "com/formdev/flatlaf/natives/*windows*") }
if (arch.isArm) {
exec { commandLine("zip", "-d", file.absolutePath, "com/formdev/flatlaf/natives/*x86*") }
} else {
exec { commandLine("zip", "-d", file.absolutePath, "com/formdev/flatlaf/natives/*arm*") }
}
}
}
}
}
}
}
tasks.register<Exec>("jlink") {
@@ -480,10 +422,6 @@ tasks.register<Exec>("jpackage") {
}
if (os.isWindows) {
arguments.add("--win-dir-chooser")
arguments.add("--win-shortcut")
arguments.add("--win-shortcut-prompt")
arguments.addAll(listOf("--win-upgrade-uuid", "E1D93CAD-5BF8-442E-93BA-6E90DE601E4C"))
arguments.addAll(listOf("--icon", "${projectDir.absolutePath}/src/main/resources/icons/termora.ico"))
}
@@ -496,7 +434,7 @@ tasks.register<Exec>("jpackage") {
if (os.isMacOsX) {
arguments.add("dmg")
} else if (os.isWindows) {
arguments.add("msi")
arguments.add("app-image")
} else if (os.isLinux) {
arguments.add(if (isDeb) "deb" else "app-image")
if (isDeb) {
@@ -507,7 +445,7 @@ tasks.register<Exec>("jpackage") {
throw UnsupportedOperationException()
}
if (os.isMacOsX && macOSSign) {
if (macOSSign) {
arguments.add("--mac-sign")
arguments.add("--mac-signing-key-user-name")
arguments.add(macOSSignUsername)
@@ -568,10 +506,31 @@ tasks.register("check-license") {
* 创建 zip、msi
*/
fun packOnWindows(distributionDir: Directory, finalFilenameWithoutExtension: String, projectName: String) {
val dir = layout.buildDirectory.dir("jpackage/images/win-msi.image/").get().asFile
val dir = layout.buildDirectory.dir("distributions").get().asFile
val cfg = FileUtils.getFile(dir, projectName, "app", "${projectName}.cfg")
val configText = cfg.readText()
// appx
if (isAppx) {
cfg.writeText(StringBuilder(configText).appendLine("java-options=-Djpackage.app-layout=appx").toString())
val appxManifest = FileUtils.getFile(dir, projectName, "AppxManifest.xml")
layout.buildDirectory.file("resources/main/AppxManifest.xml").get().asFile
.renameTo(appxManifest)
val icons = setOf("termora.png", "termora_44x44.png", "termora_150x150.png")
for (file in projectDir.resolve("src/main/resources/icons/").listFiles()) {
if (icons.contains(file.name)) {
val p = appxManifest.parentFile.resolve("icons/${file.name}")
FileUtils.forceMkdirParent(p)
file.copyTo(p, true)
}
}
exec {
commandLine(makeAppx, "pack", "/d", projectName, "/p", "${finalFilenameWithoutExtension}.msix")
workingDir = dir
}
return
}
// zip
cfg.writeText(StringBuilder(configText).appendLine("java-options=-Djpackage.app-layout=zip").toString())
exec {
@@ -593,21 +552,22 @@ fun packOnWindows(distributionDir: Directory, finalFilenameWithoutExtension: Str
"/DMyAppVersion=${appVersion}",
"/DMyOutputDir=${distributionDir.asFile.absolutePath}",
"/DMySetupIconFile=${FileUtils.getFile(projectDir, "src", "main", "resources", "icons", "termora.ico")}",
"/DMySourceDir=${layout.buildDirectory.dir("jpackage/images/win-msi.image/${projectName}").get().asFile}",
"/DMyWizardSmallImageFile=${
FileUtils.getFile(
projectDir,
"src",
"main",
"resources",
"icons",
"termora_128x128.bmp"
)
}",
"/DMySourceDir=${FileUtils.getFile(dir, projectName).absolutePath}",
"/F${finalFilenameWithoutExtension}",
FileUtils.getFile(projectDir, "src", "main", "resources", "termora.iss")
)
}
// msi
exec {
commandLine(
"cmd", "/c", "move",
"${projectName}-${appVersion}.msi",
"${finalFilenameWithoutExtension}.msi"
)
workingDir = distributionDir.asFile
}
}
/**
@@ -711,7 +671,7 @@ fun packOnLinux(distributionDir: Directory, finalFilenameWithoutExtension: Strin
commandLine(
"wget",
"-O", appimagetool.absolutePath,
"https://github.com/AppImage/AppImageKit/releases/download/13/appimagetool-${if (arch.isArm) "aarch64" else "x86_64"}.AppImage"
"https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-${if (arch.isArm) "aarch64" else "x86_64"}.AppImage"
)
workingDir = distributionDir.asFile
}

View File

@@ -1,17 +1,17 @@
[versions]
kotlin = "2.2.0"
slf4j = "2.0.17"
pty4j = "0.13.6"
pty4j = "0.13.10"
tinylog = "2.7.0"
kotlinx-coroutines = "1.10.2"
flatlaf = "3.6.1-SNAPSHOT"
flatlaf = "3.6.1"
kotlinx-serialization-json = "1.9.0"
commons-codec = "1.18.0"
commons-codec = "1.19.0"
commons-lang3 = "3.18.0"
commons-csv = "1.14.0"
commons-net = "3.11.1"
commons-text = "1.13.1"
commons-compress = "1.27.1"
commons-csv = "1.14.1"
commons-net = "3.12.0"
commons-text = "1.14.0"
commons-compress = "1.28.0"
commons-vfs2 = "2.10.0"
swingx = "1.6.5-1"
jgoodies-forms = "1.9.0"
@@ -20,7 +20,7 @@ oshi = "6.8.1"
versioncompare = "1.4.1"
jna = "5.17.0"
jSystemThemeDetector = "3.9.1"
commons-io = "2.19.0"
commons-io = "2.20.0"
jbr-api = "17.1.10.1"
hutool = "5.8.39"
jsch = "2.27.2"
@@ -28,7 +28,7 @@ okhttp = "5.1.0"
sshj = "0.39.0"
sshd-core = "2.15.0"
jgit = "7.2.0.202503040940-r"
commonmark = "0.25.0"
commonmark = "0.25.1"
jnafilechooser = "1.1.2"
xodus = "2.0.1"
bip39 = "1.0.9"
@@ -41,9 +41,9 @@ jSerialComm = "2.11.2"
ini4j = "0.5.5-2"
restart4j = "0.0.1"
eddsa = "0.3.0"
exposed = "1.0.0-beta-4"
exposed = "1.0.0-beta-5"
h2 = "2.3.232"
sqlite = "3.50.2.0"
sqlite = "3.50.3.0"
jug = "5.1.0"
semver4j = "6.0.0"
jsvg = "2.0.0"

View File

@@ -5,6 +5,7 @@ The files in this catalogue are for public access only. Specific descriptions ar
- You may view and study the contents of these files;
- You may NOT use them for any commercial purpose;
- You may NOT modify, copy, distribute, republish, or use them to create derivative works;
- Written permission must be obtained from the author for any use beyond personal viewing.
- Written permission must be obtained from the author for any use beyond personal viewing;
- If you submit a Pull Request that modifies, supplements, or adds to the files in this directory or its subdirectories, unless otherwise agreed in writing, you agree that the copyright of your contribution is owned by hstyi and may be used and managed under the current license terms.
All rights reserved.

View File

@@ -2,13 +2,13 @@ plugins {
alias(libs.plugins.kotlin.jvm)
}
project.version = "0.0.3"
project.version = "0.0.4"
dependencies {
testImplementation(kotlin("test"))
implementation("com.qcloud:cos_api:5.6.247")
implementation("com.qcloud:cos_api:5.6.249")
compileOnly(project(":"))
}

View File

@@ -4,7 +4,7 @@ plugins {
project.version = "0.0.6"
project.version = "0.0.7"
dependencies {

View File

@@ -1,94 +0,0 @@
package app.termora.plugins.editor
import app.termora.DialogWrapper
import app.termora.Disposable
import app.termora.Disposer
import app.termora.OptionPane
import java.awt.Dimension
import java.awt.Window
import java.awt.event.WindowAdapter
import java.awt.event.WindowEvent
import java.io.File
import java.nio.file.Path
import java.util.concurrent.atomic.AtomicBoolean
import javax.swing.JComponent
import javax.swing.JOptionPane
import javax.swing.UIManager
import kotlin.io.path.absolutePathString
import kotlin.io.path.name
class EditorDialog(file: Path, owner: Window, private val myDisposable: Disposable) : DialogWrapper(null) {
private val filename = file.name
private val filepath = File(file.absolutePathString())
private val editorPanel = EditorPanel(this, filepath)
private val disposed = AtomicBoolean()
init {
size = Dimension(UIManager.getInt("Dialog.width"), UIManager.getInt("Dialog.height"))
isModal = false
controlsVisible = true
isResizable = true
title = filename
iconImages = owner.iconImages
escapeDispose = false
defaultCloseOperation = DO_NOTHING_ON_CLOSE
initEvents()
setLocationRelativeTo(owner)
init()
}
private fun initEvents() {
addWindowListener(object : WindowAdapter() {
override fun windowClosing(e: WindowEvent?) {
if (disposed.compareAndSet(false, true)) {
doCancelAction()
}
}
})
Disposer.register(myDisposable, object : Disposable {
override fun dispose() {
if (disposed.compareAndSet(false, true)) {
doCancelAction()
}
}
})
Disposer.register(disposable, object : Disposable {
override fun dispose() {
if (disposed.compareAndSet(false, true)) {
Disposer.dispose(myDisposable)
}
}
})
}
override fun doCancelAction() {
if (editorPanel.changes()) {
if (OptionPane.showConfirmDialog(
this,
"文件尚未保存,你确定要退出吗?",
optionType = JOptionPane.OK_CANCEL_OPTION,
) != JOptionPane.OK_OPTION
) {
return
}
}
super.doCancelAction()
}
override fun createCenterPanel(): JComponent {
return editorPanel
}
override fun createSouthPanel(): JComponent? {
return null
}
}

View File

@@ -0,0 +1,91 @@
package app.termora.plugins.editor
import app.termora.Disposable
import app.termora.Disposer
import app.termora.EnableManager
import app.termora.OptionPane
import java.awt.BorderLayout
import java.awt.Dimension
import java.awt.Window
import java.awt.event.WindowAdapter
import java.awt.event.WindowEvent
import java.io.File
import java.nio.file.Path
import java.util.concurrent.atomic.AtomicBoolean
import javax.swing.JFrame
import javax.swing.JOptionPane
import javax.swing.UIManager
import kotlin.io.path.absolutePathString
import kotlin.io.path.name
import kotlin.math.max
class EditorFrame(private val file: Path, private val owner: Window, private val disposable: Disposable) : JFrame() {
private val enableManager get() = EnableManager.getInstance()
private val disposed = AtomicBoolean()
private val filepath = File(file.absolutePathString())
private val frame get() = this
private val editorPanel = EditorPanel(this, filepath)
init {
initView()
initEvent()
}
private fun initEvent() {
Disposer.register(disposable, object : Disposable {
override fun dispose() {
if (disposed.compareAndSet(false, true)) frame.dispose()
}
})
addWindowListener(object : WindowAdapter() {
override fun windowClosed(e: WindowEvent) {
if (disposed.compareAndSet(false, true)) Disposer.dispose(disposable)
enableManager.setFlag("Plugins.editor.dialog.width", width)
enableManager.setFlag("Plugins.editor.dialog.height", height)
enableManager.setFlag("Plugins.editor.dialog.extendedState", extendedState)
}
override fun windowClosing(e: WindowEvent?) {
if (editorPanel.changes()) {
if (OptionPane.showConfirmDialog(
frame,
EditorI18n.getString("termora.plugins.editor.not-save"),
optionType = JOptionPane.OK_CANCEL_OPTION,
) == JOptionPane.OK_OPTION
) {
frame.dispose()
}
} else {
frame.dispose()
}
}
})
}
private fun initView() {
size = Dimension(UIManager.getInt("Dialog.width"), UIManager.getInt("Dialog.height"))
val state = enableManager.getFlag("Plugins.editor.dialog.extendedState", 0)
if ((state and MAXIMIZED_BOTH) == MAXIMIZED_BOTH) {
frame.setLocationRelativeTo(null)
frame.extendedState = state
} else {
val mySize = size
mySize.width = max(enableManager.getFlag("Plugins.editor.dialog.width", mySize.width), mySize.width)
mySize.height = max(enableManager.getFlag("Plugins.editor.dialog.height", mySize.height), mySize.height)
size = mySize
setLocationRelativeTo(owner)
}
title = file.name
iconImages = owner.iconImages
defaultCloseOperation = DO_NOTHING_ON_CLOSE
rootPane.contentPane.layout = BorderLayout()
rootPane.contentPane.add(editorPanel, BorderLayout.CENTER)
}
}

View File

@@ -0,0 +1,13 @@
package app.termora.plugins.editor
import app.termora.NamedI18n
import org.slf4j.Logger
import org.slf4j.LoggerFactory
object EditorI18n : NamedI18n("i18n/messages") {
private val log = LoggerFactory.getLogger(EditorI18n::class.java)
override fun getLogger(): Logger {
return log
}
}

View File

@@ -35,7 +35,7 @@ import javax.swing.event.DocumentEvent
import kotlin.math.max
import kotlin.math.min
class EditorPanel(private val window: JDialog, private val file: File) : JPanel(BorderLayout()) {
class EditorPanel(private val window: JFrame, private val file: File) : JPanel(BorderLayout()) {
companion object {
private val log = LoggerFactory.getLogger(EditorPanel::class.java)

View File

@@ -14,7 +14,7 @@ class MyTransportEditFileExtension private constructor() : TransportEditFileExte
override fun edit(owner: Window, path: Path): Disposable {
val disposable = Disposer.newDisposable()
SwingUtilities.invokeLater { EditorDialog(path, owner, disposable).isVisible = true }
SwingUtilities.invokeLater { EditorFrame(path, owner, disposable).isVisible = true }
return disposable
}
}

View File

@@ -0,0 +1,2 @@
termora.plugins.editor.not-save=The file has not been saved. Are you sure you want to exit?

View File

@@ -0,0 +1 @@
termora.plugins.editor.not-save=Файл не сохранён. Вы уверены, что хотите выйти?

View File

@@ -0,0 +1 @@
termora.plugins.editor.not-save=文件尚未保存,你确定要退出吗?

View File

@@ -0,0 +1 @@
termora.plugins.editor.not-save=檔案尚未儲存,你確定要退出嗎?

View File

@@ -2,7 +2,7 @@ plugins {
alias(libs.plugins.kotlin.jvm)
}
project.version = "0.0.1"
project.version = "0.0.2"
dependencies {
testImplementation(kotlin("test"))

View File

@@ -14,6 +14,7 @@ import java.awt.Component
import java.awt.KeyboardFocusManager
import java.awt.event.ComponentAdapter
import java.awt.event.ComponentEvent
import java.awt.event.ItemEvent
import java.nio.charset.Charset
import javax.swing.*
@@ -246,6 +247,12 @@ class FTPHostOptionsPane : OptionsPane() {
removeComponentListener(this)
}
})
authenticationTypeComboBox.addItemListener {
if (it.stateChange == ItemEvent.SELECTED) {
passwordTextField.isEnabled = authenticationTypeComboBox.selectedItem == AuthenticationType.Password
}
}
}
override fun getIcon(isSelected: Boolean): Icon {
@@ -378,7 +385,7 @@ class FTPHostOptionsPane : OptionsPane() {
}
}
enum class PassiveMode {
enum class PassiveMode {
Local,
Remote,
}

View File

@@ -2,14 +2,14 @@ plugins {
alias(libs.plugins.kotlin.jvm)
}
project.version = "0.0.7"
project.version = "0.0.8"
dependencies {
testImplementation(kotlin("test"))
compileOnly(project(":"))
implementation("com.maxmind.geoip2:geoip2:4.3.1")
// https://github.com/hstyi/geolite2
implementation("com.github.hstyi:geolite2:v1.0-202507070058")
implementation("com.github.hstyi:geolite2:v1.0-202508040102")
}
apply(from = "$rootDir/plugins/common.gradle.kts")

View File

@@ -3,7 +3,7 @@ plugins {
}
project.version = "0.0.3"
project.version = "0.0.4"
dependencies {

View File

@@ -187,8 +187,6 @@ class MigrationApplicationRunnerExtension private constructor() : ApplicationRun
// 重启
TermoraRestarter.getInstance().scheduleRestart(null, ask = false)
// 退出程序
Disposer.dispose(TermoraFrameManager.getInstance())
}

View File

@@ -4,7 +4,7 @@ plugins {
project.version = "0.0.1"
project.version = "0.0.5"
dependencies {

View File

@@ -1,7 +1,11 @@
package app.termora.plugins.serial
import app.termora.*
import app.termora.account.AccountOwner
import app.termora.highlight.KeywordHighlight
import app.termora.plugin.internal.AltKeyModifier
import app.termora.plugin.internal.BasicGeneralOption
import app.termora.plugin.internal.BasicTerminalOption
import com.fazecast.jSerialComm.SerialPort
import com.formdev.flatlaf.FlatClientProperties
import com.jgoodies.forms.builder.FormBuilder
@@ -15,12 +19,16 @@ import java.awt.BorderLayout
import java.awt.Component
import java.awt.event.ComponentAdapter
import java.awt.event.ComponentEvent
import java.nio.charset.Charset
import javax.swing.*
class SerialHostOptionsPane : OptionsPane() {
class SerialHostOptionsPane(private val accountOwner: AccountOwner) : OptionsPane() {
private val generalOption = BasicGeneralOption()
private val terminalOption = TerminalOption()
private val terminalOption = BasicTerminalOption().apply {
showCharsetComboBox = true
showStartupCommandTextField = true
accountOwner = this@SerialHostOptionsPane.accountOwner
init()
}
private val serialCommOption = SerialCommOption()
init {
@@ -48,6 +56,12 @@ class SerialHostOptionsPane : OptionsPane() {
encoding = terminalOption.charsetComboBox.selectedItem as String,
startupCommand = terminalOption.startupCommandTextField.text,
serialComm = serialComm,
extras = mutableMapOf(
"altModifier" to (terminalOption.altModifierComboBox.selectedItem?.toString()
?: AltKeyModifier.EightBit.name),
"keywordHighlightSetId" to ((terminalOption.highlightSetComboBox.selectedItem as? KeywordHighlight)?.id
?: "-1"),
)
)
return Host(
@@ -76,6 +90,20 @@ class SerialHostOptionsPane : OptionsPane() {
serialCommOption.stopBitsComboBox.selectedItem = serialComm.stopBits
serialCommOption.flowControlComboBox.selectedItem = serialComm.flowControl
val altModifier = host.options.extras["altModifier"] ?: AltKeyModifier.EightBit.name
terminalOption.altModifierComboBox.selectedItem = runCatching { AltKeyModifier.valueOf(altModifier) }
.getOrNull() ?: AltKeyModifier.EightBit
val keywordHighlightSetId = host.options.extras["keywordHighlightSetId"]
for (i in 0 until terminalOption.highlightSetComboBox.itemCount) {
val item = terminalOption.highlightSetComboBox.getItemAt(i)
if (item.id == keywordHighlightSetId) {
terminalOption.highlightSetComboBox.selectedItem = item
break
}
}
}
fun validateFields(): Boolean {
@@ -128,67 +156,6 @@ class SerialHostOptionsPane : OptionsPane() {
}
protected inner class TerminalOption : JPanel(BorderLayout()), Option {
val charsetComboBox = JComboBox<String>()
val startupCommandTextField = OutlineTextField()
init {
initView()
initEvents()
}
private fun initView() {
add(getCenterComponent(), BorderLayout.CENTER)
for (e in Charset.availableCharsets()) {
charsetComboBox.addItem(e.key)
}
charsetComboBox.selectedItem = "UTF-8"
}
private fun initEvents() {
}
override fun getIcon(isSelected: Boolean): Icon {
return Icons.terminal
}
override fun getTitle(): String {
return I18n.getString("termora.new-host.terminal")
}
override fun getJComponent(): JComponent {
return this
}
private fun getCenterComponent(): JComponent {
val layout = FormLayout(
"left:pref, $FORM_MARGIN, default:grow",
"pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref, $FORM_MARGIN, pref"
)
var rows = 1
val step = 2
val panel = FormBuilder.create().layout(layout)
.add("${I18n.getString("termora.new-host.terminal.encoding")}:").xy(1, rows)
.add(charsetComboBox).xy(3, rows).apply { rows += step }
.add("${I18n.getString("termora.new-host.terminal.startup-commands")}:").xy(1, rows)
.add(startupCommandTextField).xy(3, rows).apply { rows += step }
.apply { rows += step }
.build()
return panel
}
}
protected inner class SerialCommOption : JPanel(BorderLayout()), Option {
val serialPortComboBox = OutlineComboBox<String>()
val baudRateComboBox = OutlineComboBox<Int>()

View File

@@ -2,11 +2,12 @@ package app.termora.plugins.serial
import app.termora.Disposer
import app.termora.Host
import app.termora.account.AccountOwner
import app.termora.protocol.ProtocolHostPanel
import java.awt.BorderLayout
class SerialProtocolHostPanel : ProtocolHostPanel() {
private val pane = SerialHostOptionsPane()
class SerialProtocolHostPanel(accountOwner: AccountOwner) : ProtocolHostPanel() {
private val pane = SerialHostOptionsPane(accountOwner)
init {
initView()

View File

@@ -16,7 +16,7 @@ internal class SerialProtocolHostPanelExtension private constructor() : Protocol
}
override fun createProtocolHostPanel(accountOwner: AccountOwner): ProtocolHostPanel {
return SerialProtocolHostPanel()
return SerialProtocolHostPanel(accountOwner)
}
override fun ordered(): Long {

View File

@@ -1,9 +1,6 @@
package app.termora.plugins.serial
import app.termora.Host
import app.termora.Icons
import app.termora.PtyHostTerminalTab
import app.termora.WindowScope
import app.termora.*
import app.termora.terminal.PtyConnector
import org.apache.commons.io.Charsets
import java.nio.charset.StandardCharsets
@@ -11,6 +8,8 @@ import javax.swing.Icon
class SerialTerminalTab(windowScope: WindowScope, host: Host) :
PtyHostTerminalTab(windowScope, host) {
override suspend fun openPtyConnector(): PtyConnector {
val serialPort = Serials.openPort(host)
return SerialPortPtyConnector(
@@ -19,6 +18,10 @@ class SerialTerminalTab(windowScope: WindowScope, host: Host) :
)
}
override fun createReconnectTerminalTab(): TerminalTab {
return SerialTerminalTab(windowScope, host)
}
override fun getIcon(): Icon {
return Icons.plugin
}

View File

@@ -3,7 +3,7 @@ plugins {
}
project.version = "0.0.3"
project.version = "0.0.4"
dependencies {

View File

@@ -0,0 +1,17 @@
plugins {
alias(libs.plugins.kotlin.jvm)
}
project.version = "0.0.1"
dependencies {
testImplementation(kotlin("test"))
testImplementation(project(":"))
implementation(files("${project.projectDir}/libs/trilead-ssh2-build217-jenkins-8.jar"))
compileOnly(project(":"))
}
apply(from = "$rootDir/plugins/common.gradle.kts")

Binary file not shown.

View File

@@ -0,0 +1,40 @@
// Copyright (C) 2010 - 2014 GlavSoft LLC.
// All rights reserved.
//
// -----------------------------------------------------------------------
// This file is part of the TightVNC software. Please visit our Web site:
//
// http://www.tightvnc.com/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// -----------------------------------------------------------------------
//
package com.glavsoft.core;
/**
* @author dime at tightvnc.com
*/
public class SettingsChangedEvent {
private final Object source;
public SettingsChangedEvent(Object source) {
this.source = source;
}
public Object getSource() {
return source;
}
}

View File

@@ -0,0 +1,144 @@
// Copyright (C) 2010 - 2014 GlavSoft LLC.
// All rights reserved.
//
// -----------------------------------------------------------------------
// This file is part of the TightVNC software. Please visit our Web site:
//
// http://www.tightvnc.com/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// -----------------------------------------------------------------------
//
package com.glavsoft.drawing;
import com.glavsoft.exceptions.TransportException;
import com.glavsoft.rfb.encoding.PixelFormat;
import com.glavsoft.transport.Transport;
public class ColorDecoder {
protected byte redShift;
protected byte greenShift;
protected byte blueShift;
public short redMax;
public short greenMax;
public short blueMax;
public final int bytesPerPixel;
public final int bytesPerCPixel;
public final int bytesPerPixelTight;
private final byte[] buff;
private int startShift;
private int startShiftCompact;
private int addShiftItem;
private final boolean isTightSpecific;
public ColorDecoder(PixelFormat pf) {
redShift = pf.redShift;
greenShift = pf.greenShift;
blueShift = pf.blueShift;
redMax = pf.redMax;
greenMax = pf.greenMax;
blueMax = pf.blueMax;
bytesPerPixel = pf.bitsPerPixel / 8;
final long significant = redMax << redShift | greenMax << greenShift | blueMax << blueShift;
bytesPerCPixel = pf.depth <= 24 // as in RFB
// || 32 == pf.depth) // UltraVNC use this... :(
&& 32 == pf.bitsPerPixel
&& ((significant & 0x00ff000000L) == 0 || (significant & 0x000000ffL) == 0)
? 3
: bytesPerPixel;
bytesPerPixelTight = 24 == pf.depth && 32 == pf.bitsPerPixel ? 3 : bytesPerPixel;
buff = new byte[bytesPerPixel];
if (0 == pf.bigEndianFlag) {
startShift = 0;
startShiftCompact = 0;
addShiftItem = 8;
} else {
startShift = pf.bitsPerPixel - 8;
startShiftCompact = Math.max(0, pf.depth - 8);
addShiftItem = -8;
}
isTightSpecific = 4==bytesPerPixel && 3==bytesPerPixelTight &&
255 == redMax && 255 == greenMax && 255 == blueMax;
}
protected int readColor(Transport transport) throws TransportException {
return getColor(transport.readBytes(buff, 0, bytesPerPixel), 0);
}
protected int readCompactColor(Transport transport) throws TransportException {
return getCompactColor(transport.readBytes(buff, 0, bytesPerCPixel), 0);
}
protected int readTightColor(Transport transport) throws TransportException {
return getTightColor(transport.readBytes(buff, 0, bytesPerPixelTight), 0);
}
/**
* Convert rfb encoded pixel color into 0x00rrggbb int value.
* @param rawColor - bytes are ordered in right sequence (little/big endian transformations already done
* @return 0x00rrggbb
*/
protected int convertColor(int rawColor) {
return 255 * (rawColor >> redShift & redMax) / redMax << 16 |
255 * (rawColor >> greenShift & greenMax) / greenMax << 8 |
255 * (rawColor >> blueShift & blueMax) / blueMax;
}
public void fillRawComponents(byte[] comp, byte[] bytes, int offset) {
int rawColor = getRawTightColor(bytes, offset);
comp[0] = (byte) (rawColor >> redShift & redMax);
comp[1] = (byte) (rawColor >> greenShift & greenMax);
comp[2] = (byte) (rawColor >> blueShift & blueMax);
}
public int getTightColor(byte[] bytes, int offset) {
return convertColor(getRawTightColor(bytes, offset));
}
private int getRawTightColor(byte[] bytes, int offset) {
if (isTightSpecific)
return (bytes[offset++] & 0xff)<<16 |
(bytes[offset++] & 0xff)<<8 |
bytes[offset] & 0xff;
else
return getRawColor(bytes, offset);
}
protected int getColor(byte[] bytes, int offset) {
return convertColor(getRawColor(bytes, offset));
}
private int getRawColor(byte[] bytes, int offset) {
int shift = startShift;
int item = addShiftItem;
int rawColor = (bytes[offset++] & 0xff)<<shift;
for (int i=1; i<bytesPerPixel; ++i) {
rawColor |= (bytes[offset++] & 0xff)<<(shift+=item);
}
return rawColor;
}
protected int getCompactColor(byte[] bytes, int offset) {
int shift = startShiftCompact;
int item = addShiftItem;
int rawColor = (bytes[offset++] & 0xff)<<shift;
for (int i=1; i< bytesPerCPixel; ++i) {
rawColor |= (bytes[offset++] & 0xff)<<(shift+=item);
}
return convertColor(rawColor);
}
}

View File

@@ -0,0 +1,340 @@
// Copyright (C) 2010 - 2014 GlavSoft LLC.
// All rights reserved.
//
// -----------------------------------------------------------------------
// This file is part of the TightVNC software. Please visit our Web site:
//
// http://www.tightvnc.com/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// -----------------------------------------------------------------------
//
package com.glavsoft.drawing;
import com.glavsoft.exceptions.TransportException;
import com.glavsoft.rfb.encoding.PixelFormat;
import com.glavsoft.rfb.encoding.decoder.FramebufferUpdateRectangle;
import com.glavsoft.transport.Transport;
import java.util.Arrays;
import java.util.concurrent.locks.ReentrantLock;
/**
* Render bitmap data
*
* @author dime @ tightvnc.com
*/
public abstract class Renderer {
protected final ReentrantLock lock = new ReentrantLock();
public abstract void drawJpegImage(byte[] bytes, int offset,
int jpegBufferLength, FramebufferUpdateRectangle rect);
protected int width;
protected int height;
protected int[] pixels;
protected SoftCursor cursor;
protected ColorDecoder colorDecoder;
protected void init(int width, int height, PixelFormat pixelFormat) {
this.width = width;
this.height = height;
initColorDecoder(pixelFormat);
pixels = new int[width * height];
Arrays.fill(pixels, 0);
}
public void initColorDecoder(PixelFormat pixelFormat) {
lock.lock();
colorDecoder = new ColorDecoder(pixelFormat);
lock.unlock();
}
/**
* Draw byte array bitmap data
*
* @param bytes bitmap data
* @param x bitmap x position
* @param y bitmap y position
* @param width bitmap width
* @param height bitmap height
*/
public void drawBytes(byte[] bytes, int x, int y, int width, int height) {
int i = 0;
lock.lock();
for (int ly = y; ly < y + height; ++ly) {
int end = ly * this.width + x + width;
for (int pixelsOffset = ly * this.width + x; pixelsOffset < end; ++pixelsOffset) {
pixels[pixelsOffset] = getPixelColor(bytes, i);
i += colorDecoder.bytesPerPixel;
}
}
lock.unlock();
}
/**
* Draw byte array bitmap data (for ZRLE)
*/
public int drawCompactBytes(byte[] bytes, int offset, int x, int y, int width, int height) {
int i = offset;
lock.lock();
for (int ly = y; ly < y + height; ++ly) {
int end = ly * this.width + x + width;
for (int pixelsOffset = ly * this.width + x; pixelsOffset < end; ++pixelsOffset) {
pixels[pixelsOffset] = getCompactPixelColor(bytes, i);
i += colorDecoder.bytesPerCPixel;
}
}
lock.unlock();
return i - offset;
}
/**
* Draw int (colors) array bitmap data (for ZRLE)
*/
public void drawColoredBitmap(int[] colors, int x, int y, int width, int height) {
int i = 0;
lock.lock();
for (int ly = y; ly < y + height; ++ly) {
int end = ly * this.width + x + width;
for (int pixelsOffset = ly * this.width + x; pixelsOffset < end; ++pixelsOffset) {
pixels[pixelsOffset] = colors[i++];
}
}
lock.unlock();
}
/**
* Draw byte array bitmap data (for Tight)
*/
public int drawTightBytes(byte[] bytes, int offset, int x, int y, int width, int height) {
int i = offset;
lock.lock();
for (int ly = y; ly < y + height; ++ly) {
int end = ly * this.width + x + width;
for (int pixelsOffset = ly * this.width + x; pixelsOffset < end; ++pixelsOffset) {
pixels[pixelsOffset] = colorDecoder.getTightColor(bytes, i);
i += colorDecoder.bytesPerPixelTight;
}
}
lock.unlock();
return i - offset;
}
/**
* Draw byte array bitmap data (from array with plain RGB color components. Assumed: rrrrrrrr gggggggg bbbbbbbb)
*/
public void drawUncaliberedRGBLine(byte[] bytes, int x, int y, int width) {
int end = y * this.width + x + width;
lock.lock();
for (int i = 3, pixelsOffset = y * this.width + x; pixelsOffset < end; ++pixelsOffset) {
pixels[pixelsOffset] =
// (0xff & bytes[i++]) << 16 |
// (0xff & bytes[i++]) << 8 |
// 0xff & bytes[i++];
(0xff & 255 * (colorDecoder.redMax & bytes[i++]) / colorDecoder.redMax) << 16 |
(0xff & 255 * (colorDecoder.greenMax & bytes[i++]) / colorDecoder.greenMax) << 8 |
0xff & 255 * (colorDecoder.blueMax & bytes[i++]) / colorDecoder.blueMax;
}
lock.unlock();
}
/**
* Draw paletted byte array bitmap data
*
* @param buffer bitmap data
* @param rect bitmap location and dimensions
* @param palette colour palette
* @param paletteSize number of colors in palette
*/
public void drawBytesWithPalette(byte[] buffer, FramebufferUpdateRectangle rect, int[] palette, int paletteSize) {
lock.lock();
// 2 colors
if (2 == paletteSize) {
int dx, dy, n;
int i = rect.y * this.width + rect.x;
int rowBytes = (rect.width + 7) / 8;
byte b;
for (dy = 0; dy < rect.height; dy++) {
for (dx = 0; dx < rect.width / 8; dx++) {
b = buffer[dy * rowBytes + dx];
for (n = 7; n >= 0; n--) {
pixels[i++] = palette[b >> n & 1];
}
}
for (n = 7; n >= 8 - rect.width % 8; n--) {
pixels[i++] = palette[buffer[dy * rowBytes + dx] >> n & 1];
}
i += this.width - rect.width;
}
} else {
// 3..255 colors (assuming bytesPixel == 4).
int i = 0;
for (int ly = rect.y; ly < rect.y + rect.height; ++ly) {
for (int lx = rect.x; lx < rect.x + rect.width; ++lx) {
int pixelsOffset = ly * this.width + lx;
pixels[pixelsOffset] = palette[buffer[i++] & 0xFF];
}
}
}
lock.unlock();
}
/**
* Copy rectangle region from one position to another. Regions may be overlapped.
*
* @param srcX source rectangle x position
* @param srcY source rectangle y position
* @param dstRect destination rectangle
*/
public void copyRect(int srcX, int srcY, FramebufferUpdateRectangle dstRect) {
int startSrcY, endSrcY, dstY, deltaY;
if (srcY > dstRect.y) {
startSrcY = srcY;
endSrcY = srcY + dstRect.height;
dstY = dstRect.y;
deltaY = +1;
} else {
startSrcY = srcY + dstRect.height - 1;
endSrcY = srcY - 1;
dstY = dstRect.y + dstRect.height - 1;
deltaY = -1;
}
lock.lock();
for (int y = startSrcY; y != endSrcY; y += deltaY) {
System.arraycopy(pixels, y * width + srcX,
pixels, dstY * width + dstRect.x, dstRect.width);
dstY += deltaY;
}
lock.unlock();
}
/**
* Fill rectangle region with specified colour
*
* @param color colour to fill with
* @param rect rectangle region positions and dimensions
*/
public void fillRect(int color, FramebufferUpdateRectangle rect) {
fillRect(color, rect.x, rect.y, rect.width, rect.height);
}
/**
* Fill rectangle region with specified colour
*
* @param color colour to fill with
* @param x rectangle x position
* @param y rectangle y position
* @param width rectangle width
* @param height rectangle height
*/
public void fillRect(int color, int x, int y, int width, int height) {
lock.lock();
int sy = y * this.width + x;
int ey = sy + height * this.width;
for (int i = sy; i < ey; i += this.width) {
Arrays.fill(pixels, i, i + width, color);
}
lock.unlock();
}
/**
* Reads color bytes (PIXEL) from transport, returns int combined RGB
* value consisting of the red component in bits 16-23, the green component
* in bits 8-15, and the blue component in bits 0-7. May be used directly for
* creation awt.Color object
*/
public int readPixelColor(Transport transport) throws TransportException {
return colorDecoder.readColor(transport);
}
public int readTightPixelColor(Transport transport) throws TransportException {
return colorDecoder.readTightColor(transport);
}
public ColorDecoder getColorDecoder() {
return colorDecoder;
}
public int getCompactPixelColor(byte[] bytes, int offset) {
return colorDecoder.getCompactColor(bytes, offset);
}
public int getPixelColor(byte[] bytes, int offset) {
return colorDecoder.getColor(bytes, offset);
}
public int getBytesPerPixel() {
return colorDecoder.bytesPerPixel;
}
public int getBytesPerCPixel() {
return colorDecoder.bytesPerCPixel;
}
public int getBytesPerPixelTight() {
return colorDecoder.bytesPerPixelTight;
}
public void fillColorBitmapWithColor(int[] bitmapData, int decodedOffset, int rlength, int color) {
while (rlength-- > 0) {
bitmapData[decodedOffset++] = color;
}
}
/**
* Width of rendered image
*
* @return width
*/
public int getWidth() {
return width;
}
/**
* Height of rendered image
*
* @return height
*/
public int getHeight() {
return height;
}
/**
* Read and decode cursor image
*
* @param rect new cursor hot point position and cursor dimensions
* @throws TransportException
*/
public void createCursor(int[] cursorPixels, FramebufferUpdateRectangle rect)
throws TransportException {
synchronized (cursor.getLock()) {
cursor.createCursor(cursorPixels, rect.x, rect.y, rect.width, rect.height);
}
}
/**
* Read and decode new cursor position
*
* @param rect cursor position
*/
public void decodeCursorPosition(FramebufferUpdateRectangle rect) {
synchronized (cursor.getLock()) {
cursor.updatePosition(rect.x, rect.y);
}
}
}

View File

@@ -0,0 +1,91 @@
// Copyright (C) 2010 - 2014 GlavSoft LLC.
// All rights reserved.
//
// -----------------------------------------------------------------------
// This file is part of the TightVNC software. Please visit our Web site:
//
// http://www.tightvnc.com/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// -----------------------------------------------------------------------
//
package com.glavsoft.drawing;
/**
* Abstract class for operations with soft cursor positions, dimensions and
* hot point position.
*/
public abstract class SoftCursor {
protected int hotX, hotY;
protected int x, y;
public int width, height;
public int rX, rY;
public int oldRX, oldRY;
public int oldWidth, oldHeight;
private final Object lock = new Object();
public SoftCursor(int hotX, int hotY, int width, int height) {
this.hotX = hotX;
this.hotY = hotY;
oldWidth = this.width = width;
oldHeight = this.height = height;
oldRX = rX = 0;
oldRY = rY = 0;
}
/**
* Update cursor position
*
* @param newX
* @param newY
*/
public void updatePosition(int newX, int newY) {
oldRX = rX; oldRY = rY;
oldWidth = width; oldHeight = height;
x = newX; y = newY;
rX = x - hotX; rY = y - hotY;
}
/**
* Set new cursor dimensions and hot point position
*
* @param hotX
* @param hotY
* @param width
* @param height
*/
public void setNewDimensions(int hotX, int hotY, int width, int height) {
this.hotX = hotX;
this.hotY = hotY;
oldWidth = this.width;
oldHeight = this.height;
oldRX = rX; oldRY = rY;
rX = x - hotX; rY = y - hotY;
this.width = width;
this.height = height;
}
public void createCursor(int[] cursorPixels, int hotX, int hotY, int width, int height) {
createNewCursorImage(cursorPixels, hotX, hotY, width, height);
setNewDimensions(hotX, hotY, width, height);
}
protected abstract void createNewCursorImage(int[] cursorPixels, int hotX, int hotY, int width, int height);
public Object getLock() {
return lock;
}
}

View File

@@ -0,0 +1,45 @@
// Copyright (C) 2010 - 2014 GlavSoft LLC.
// All rights reserved.
//
// -----------------------------------------------------------------------
// This file is part of the TightVNC software. Please visit our Web site:
//
// http://www.tightvnc.com/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// -----------------------------------------------------------------------
//
package com.glavsoft.exceptions;
/**
* Throws when authentication was wrong
*/
@SuppressWarnings("serial")
public class AuthenticationFailedException extends ProtocolException {
private String reason;
public AuthenticationFailedException(String message) {
super(message);
}
public AuthenticationFailedException(String message, String reason) {
super(message);
this.reason = reason;
}
public String getReason() {
return reason;
}
}

View File

@@ -0,0 +1,36 @@
// Copyright (C) 2010 - 2014 GlavSoft LLC.
// All rights reserved.
//
// -----------------------------------------------------------------------
// This file is part of the TightVNC software. Please visit our Web site:
//
// http://www.tightvnc.com/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// -----------------------------------------------------------------------
//
package com.glavsoft.exceptions;
/**
* Throwed when connection closed (EOF)
*/
@SuppressWarnings("serial")
public class ClosedConnectionException extends TransportException {
public ClosedConnectionException(Throwable exception) {
super(exception);
}
}

View File

@@ -0,0 +1,39 @@
// Copyright (C) 2010 - 2014 GlavSoft LLC.
// All rights reserved.
//
// -----------------------------------------------------------------------
// This file is part of the TightVNC software. Please visit our Web site:
//
// http://www.tightvnc.com/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// -----------------------------------------------------------------------
//
package com.glavsoft.exceptions;
@SuppressWarnings("serial")
public class CommonException extends Exception {
public CommonException(Throwable exception) {
super(exception);
}
public CommonException(String message, Throwable exception) {
super(message, exception);
}
public CommonException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,33 @@
// Copyright (C) 2010 - 2014 GlavSoft LLC.
// All rights reserved.
//
// -----------------------------------------------------------------------
// This file is part of the TightVNC software. Please visit our Web site:
//
// http://www.tightvnc.com/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// -----------------------------------------------------------------------
//
package com.glavsoft.exceptions;
@SuppressWarnings("serial")
public class CouldNotConnectException extends TransportException {
public CouldNotConnectException(Throwable exception) {
super(exception);
}
}

View File

@@ -0,0 +1,34 @@
// Copyright (C) 2010 - 2014 GlavSoft LLC.
// All rights reserved.
//
// -----------------------------------------------------------------------
// This file is part of the TightVNC software. Please visit our Web site:
//
// http://www.tightvnc.com/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// -----------------------------------------------------------------------
//
package com.glavsoft.exceptions;
/**
* Throws when problem with DES (or smth else) cryptosystem occured
*/
@SuppressWarnings("serial")
public class CryptoException extends FatalException {
public CryptoException(String message, Throwable exception) {
super(message, exception);
}
}

View File

@@ -0,0 +1,34 @@
// Copyright (C) 2010 - 2014 GlavSoft LLC.
// All rights reserved.
//
// -----------------------------------------------------------------------
// This file is part of the TightVNC software. Please visit our Web site:
//
// http://www.tightvnc.com/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// -----------------------------------------------------------------------
//
package com.glavsoft.exceptions;
/**
* Trhows when further normal program execution unavailable
*/
@SuppressWarnings("serial")
public class FatalException extends CommonException {
public FatalException(String message, Throwable e) {
super(message, e);
}
}

View File

@@ -0,0 +1,31 @@
// Copyright (C) 2010 - 2014 GlavSoft LLC.
// All rights reserved.
//
// -----------------------------------------------------------------------
// This file is part of the TightVNC software. Please visit our Web site:
//
// http://www.tightvnc.com/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// -----------------------------------------------------------------------
//
package com.glavsoft.exceptions;
@SuppressWarnings("serial")
public class ProtocolException extends CommonException {
public ProtocolException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,39 @@
// Copyright (C) 2010 - 2014 GlavSoft LLC.
// All rights reserved.
//
// -----------------------------------------------------------------------
// This file is part of the TightVNC software. Please visit our Web site:
//
// http://www.tightvnc.com/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// -----------------------------------------------------------------------
//
package com.glavsoft.exceptions;
@SuppressWarnings("serial")
public class TransportException extends CommonException {
public TransportException(String message, Throwable exception) {
super(message, exception);
}
public TransportException(Throwable exception) {
super(exception);
}
public TransportException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,32 @@
// Copyright (C) 2010 - 2014 GlavSoft LLC.
// All rights reserved.
//
// -----------------------------------------------------------------------
// This file is part of the TightVNC software. Please visit our Web site:
//
// http://www.tightvnc.com/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// -----------------------------------------------------------------------
//
package com.glavsoft.exceptions;
@SuppressWarnings("serial")
public class UnsupportedProtocolVersionException extends ProtocolException {
public UnsupportedProtocolVersionException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,32 @@
// Copyright (C) 2010 - 2014 GlavSoft LLC.
// All rights reserved.
//
// -----------------------------------------------------------------------
// This file is part of the TightVNC software. Please visit our Web site:
//
// http://www.tightvnc.com/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// -----------------------------------------------------------------------
//
package com.glavsoft.exceptions;
@SuppressWarnings("serial")
public class UnsupportedSecurityTypeException extends ProtocolException {
public UnsupportedSecurityTypeException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,58 @@
// Copyright (C) 2010 - 2014 GlavSoft LLC.
// All rights reserved.
//
// -----------------------------------------------------------------------
// This file is part of the TightVNC software. Please visit our Web site:
//
// http://www.tightvnc.com/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// -----------------------------------------------------------------------
//
package com.glavsoft.rfb;
/**
* Interface for handling clipboard texts
*/
public interface ClipboardController extends IChangeSettingsListener {
void updateSystemClipboard(byte[] bytes);
/**
* Get text clipboard contens when needed send to remote, or null vise versa
* Implement this method such a way in swing context, because swing's clipboard
* update listener invoked only on DataFlavor changes not content changes.
* Implement as returned null on systems where clipboard listeners work correctly.
*
* @return clipboad string contents if it is changed from last method call
* or null when clipboard contains non text object or clipboard contents didn't changed
*/
String getRenewedClipboardText();
/**
* Returns clipboard text content previously retrieved frim system clipboard by
* updateSavedClippoardContent()
*
* @return clipboard text content
*/
String getClipboardText();
/**
* Enable/disable clipboard transfer
*
* @param enable
*/
void setEnabled(boolean enable);
}

View File

@@ -0,0 +1,33 @@
// Copyright (C) 2010 - 2014 GlavSoft LLC.
// All rights reserved.
//
// -----------------------------------------------------------------------
// This file is part of the TightVNC software. Please visit our Web site:
//
// http://www.tightvnc.com/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// -----------------------------------------------------------------------
//
package com.glavsoft.rfb;
import com.glavsoft.core.SettingsChangedEvent;
/**
* @author dime at tightvnc.com
*/
public interface IChangeSettingsListener {
void settingsChanged(SettingsChangedEvent event);
}

View File

@@ -0,0 +1,41 @@
// Copyright (C) 2010 - 2014 GlavSoft LLC.
// All rights reserved.
//
// -----------------------------------------------------------------------
// This file is part of the TightVNC software. Please visit our Web site:
//
// http://www.tightvnc.com/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// -----------------------------------------------------------------------
//
package com.glavsoft.rfb;
import com.glavsoft.drawing.Renderer;
import com.glavsoft.rfb.encoding.PixelFormat;
import com.glavsoft.rfb.encoding.decoder.FramebufferUpdateRectangle;
import com.glavsoft.transport.Transport;
/**
* Interface for sending repaint event from worker thread to GUI thread
*/
public interface IRepaintController extends IChangeSettingsListener {
void repaintBitmap(FramebufferUpdateRectangle rect);
void repaintBitmap(int x, int y, int width, int height);
void repaintCursor();
void updateCursorPosition(short x, short y);
Renderer createRenderer(Transport transport, int width, int height, PixelFormat pixelFormat);
void setPixelFormat(PixelFormat pixelFormat);
}

View File

@@ -0,0 +1,28 @@
// Copyright (C) 2010 - 2014 GlavSoft LLC.
// All rights reserved.
//
// -----------------------------------------------------------------------
// This file is part of the TightVNC software. Please visit our Web site:
//
// http://www.tightvnc.com/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// -----------------------------------------------------------------------
//
package com.glavsoft.rfb;
public interface IRequestString {
String getResult();
}

View File

@@ -0,0 +1,38 @@
// Copyright (C) 2010 - 2014 GlavSoft LLC.
// All rights reserved.
//
// -----------------------------------------------------------------------
// This file is part of the TightVNC software. Please visit our Web site:
//
// http://www.tightvnc.com/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// -----------------------------------------------------------------------
//
package com.glavsoft.rfb;
/**
* Fires Rfb session [start]-stop events
*/
public interface IRfbSessionListener {
/**
* Fired after rfb session closed (threads stopped, other resources fried).
* Note: this event may be fired from unpredictable thread.
* @param reason reason to show why session closed
*/
void rfbSessionStopped(String reason);
}

View File

@@ -0,0 +1,135 @@
// Copyright (C) 2010 - 2014 GlavSoft LLC.
// All rights reserved.
//
// -----------------------------------------------------------------------
// This file is part of the TightVNC software. Please visit our Web site:
//
// http://www.tightvnc.com/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// -----------------------------------------------------------------------
//
package com.glavsoft.rfb;
import com.glavsoft.exceptions.TransportException;
import com.glavsoft.transport.Transport;
/**
* Structure used to describe protocol options such as tunneling methods,
* authentication schemes and message types (protocol versions 3.7t, 3.8t).
* typedef struct _rfbCapabilityInfo {
* CARD32 code; // numeric identifier
* CARD8 vendorSignature[4]; // vendor identification
* CARD8 nameSignature[8]; // abbreviated option name
* } rfbCapabilityInfo;
*/
public class RfbCapabilityInfo {
/*
* Vendors known by TightVNC: standard VNC/RealVNC, TridiaVNC, and TightVNC.
* #define rfbStandardVendor "STDV"
* #define rfbTridiaVncVendor "TRDV"
* #define rfbTightVncVendor "TGHT"
*/
public static final String VENDOR_STANDARD = "STDV";
public static final String VENDOR_TRIADA = "TRDV";
public static final String VENDOR_TIGHT = "TGHT";
public static final String TUNNELING_NO_TUNNEL = "NOTUNNEL";
public static final String AUTHENTICATION_NO_AUTH = "NOAUTH__";
public static final String AUTHENTICATION_VNC_AUTH ="VNCAUTH_";
public static final String ENCODING_COPYRECT = "COPYRECT";
public static final String ENCODING_HEXTILE = "HEXTILE_";
public static final String ENCODING_ZLIB = "ZLIB____";
public static final String ENCODING_ZRLE = "ZRLE____";
public static final String ENCODING_RRE = "RRE_____";
public static final String ENCODING_TIGHT = "TIGHT___";
// "Pseudo" encoding types
public static final String ENCODING_RICH_CURSOR = "RCHCURSR";
public static final String ENCODING_CURSOR_POS = "POINTPOS";
public static final String ENCODING_DESKTOP_SIZE = "NEWFBSIZ";
private int code;
private String vendorSignature;
private String nameSignature;
private boolean enable;
public RfbCapabilityInfo(int code, String vendorSignature, String nameSignature) {
this.code = code;
this.vendorSignature = vendorSignature;
this.nameSignature = nameSignature;
enable = true;
}
public RfbCapabilityInfo() {
this(0, "", "");
}
public RfbCapabilityInfo readFrom(Transport transport) throws TransportException {
code = transport.readInt32();
vendorSignature = transport.readString(4);
nameSignature = transport.readString(8);
enable = true;
return this;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
RfbCapabilityInfo that = (RfbCapabilityInfo) o;
return code == that.code &&
nameSignature.equals(that.nameSignature) &&
vendorSignature.equals(that.vendorSignature);
}
@Override
public int hashCode() {
int result = code;
result = 31 * result + vendorSignature.hashCode();
result = 31 * result + nameSignature.hashCode();
return result;
}
public void setEnable(boolean enable) {
this.enable = enable;
}
public int getCode() {
return code;
}
public String getVendorSignature() {
return vendorSignature;
}
public String getNameSignature() {
return nameSignature;
}
public boolean isEnabled() {
return enable;
}
@Override
public String toString() {
return "RfbCapabilityInfo{" +
"code=" + code +
", vendorSignature='" + vendorSignature + '\'' +
", nameSignature='" + nameSignature + '\'' +
'}';
}
}

View File

@@ -0,0 +1,66 @@
// Copyright (C) 2010 - 2014 GlavSoft LLC.
// All rights reserved.
//
// -----------------------------------------------------------------------
// This file is part of the TightVNC software. Please visit our Web site:
//
// http://www.tightvnc.com/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// -----------------------------------------------------------------------
//
package com.glavsoft.rfb.client;
import com.glavsoft.exceptions.TransportException;
import com.glavsoft.transport.Transport;
import java.nio.charset.Charset;
import java.util.Arrays;
import static com.glavsoft.utils.Strings.getBytesWithCharset;
/**
* ClientCutText
* The client has new ISO 8859-1 (Latin-1) text in its cut buffer. Ends of lines are repre-
* sented by the linefeed / newline character (value 10) alone. No carriage-return (value
* 13) is needed. There is currently no way to transfer text outside the Latin-1 character
* set.
* 1 - U8 - 6
* 3 - - padding
* 4 - U32 - length
* length - U8 array - text
*/
public class ClientCutTextMessage implements ClientToServerMessage {
private final byte [] bytes;
public ClientCutTextMessage(String str, Charset charset) {
final byte[] b = charset != null? getBytesWithCharset(str, charset): str.getBytes();
this.bytes = Arrays.copyOf(b, b.length);
}
@Override
public void send(Transport transport) throws TransportException {
transport.writeByte(ClientMessageType.CLIENT_CUT_TEXT.id)
.zero(3) // padding
.writeInt32(bytes.length)
.write(bytes)
.flush();
}
@Override
public String toString() {
return "ClientCutTextMessage: [length: " + bytes.length +", text: ...]";
}
}

View File

@@ -0,0 +1,66 @@
// Copyright (C) 2010 - 2014 GlavSoft LLC.
// All rights reserved.
//
// -----------------------------------------------------------------------
// This file is part of the TightVNC software. Please visit our Web site:
//
// http://www.tightvnc.com/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// -----------------------------------------------------------------------
//
package com.glavsoft.rfb.client;
/**
* @author dime at glavsoft.com
*/
public enum ClientMessageType {
SET_PIXEL_FORMAT(0),
SET_ENCODINGS(2),
FRAMEBUFFER_UPDATE_REQUEST(3),
KEY_EVENT(4),
POINTER_EVENT(5),
CLIENT_CUT_TEXT(6),
VIDEO_RECTANGLE_SELECTION(151),
VIDEO_FREEZE(152);
final public int id;
ClientMessageType(int id) {
this.id = id;
}
private static final ClientMessageType [] standardTypes =
{SET_PIXEL_FORMAT, SET_ENCODINGS, FRAMEBUFFER_UPDATE_REQUEST, KEY_EVENT, POINTER_EVENT, CLIENT_CUT_TEXT};
public static boolean isStandardType(ClientMessageType type) {
for (final ClientMessageType it : standardTypes) {
if (it == type) {
return true;
}
}
return false;
}
public static ClientMessageType byId(int id) {
for (ClientMessageType type : values()) {
if (type.id == id)
return type;
}
throw new IllegalArgumentException("Unsupported client message type: " + id);
}
}

View File

@@ -0,0 +1,31 @@
// Copyright (C) 2010 - 2014 GlavSoft LLC.
// All rights reserved.
//
// -----------------------------------------------------------------------
// This file is part of the TightVNC software. Please visit our Web site:
//
// http://www.tightvnc.com/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// -----------------------------------------------------------------------
//
package com.glavsoft.rfb.client;
import com.glavsoft.exceptions.TransportException;
import com.glavsoft.transport.Transport;
public interface ClientToServerMessage {
void send(Transport transport) throws TransportException;
}

View File

@@ -0,0 +1,64 @@
// Copyright (C) 2010 - 2014 GlavSoft LLC.
// All rights reserved.
//
// -----------------------------------------------------------------------
// This file is part of the TightVNC software. Please visit our Web site:
//
// http://www.tightvnc.com/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// -----------------------------------------------------------------------
//
package com.glavsoft.rfb.client;
import com.glavsoft.exceptions.TransportException;
import com.glavsoft.transport.Transport;
public class FramebufferUpdateRequestMessage implements ClientToServerMessage {
private final boolean incremental;
private final int height;
private final int width;
private final int y;
private final int x;
public FramebufferUpdateRequestMessage(int x, int y, int width,
int height, boolean incremental) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.incremental = incremental;
}
@Override
public void send(Transport transport) throws TransportException {
transport.writeByte(ClientMessageType.FRAMEBUFFER_UPDATE_REQUEST.id)
.writeByte(incremental ? 1 : 0)
.writeInt16(x)
.writeInt16(y)
.writeInt16(width)
.writeInt16(height)
.flush();
}
@Override
public String toString() {
return "FramebufferUpdateRequestMessage: [x: " + x + " y: " + y
+ " width: " + width + " height: " + height +
" incremental: " + incremental + "]";
}
}

View File

@@ -0,0 +1,65 @@
// Copyright (C) 2010 - 2014 GlavSoft LLC.
// All rights reserved.
//
// -----------------------------------------------------------------------
// This file is part of the TightVNC software. Please visit our Web site:
//
// http://www.tightvnc.com/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// -----------------------------------------------------------------------
//
package com.glavsoft.rfb.client;
import com.glavsoft.exceptions.TransportException;
import com.glavsoft.transport.Transport;
/**
* A key press or release. Down-flag is non-zero (true) if the key is now pressed, zero
* (false) if it is now released. The key itself is specified using the "keysym" values
* defined by the X Window System.
* 1 - U8 - message-type
* 1 - U8 - down-flag
* 2 - - - padding
* 4 - U32 - key
* For most ordinary keys, the "keysym" is the same as the corresponding ASCII value.
* For full details, see The Xlib Reference Manual, published by O'Reilly &amp; Associates,
* or see the header file &lt;X11/keysymdef.h&gt; from any X Window System installation.
*/
public class KeyEventMessage implements ClientToServerMessage {
private final int key;
private final boolean downFlag;
public KeyEventMessage(int key, boolean downFlag) {
this.downFlag = downFlag;
this.key = key;
}
@Override
public void send(Transport transport) throws TransportException {
transport.writeByte(ClientMessageType.KEY_EVENT.id)
.writeByte(downFlag ? 1 : 0)
.zero(2) // padding
.write(key)
.flush();
}
@Override
public String toString() {
return "[KeyEventMessage: [down-flag: "+downFlag + ", key: " + key +"("+Integer.toHexString(key)+")]";
}
}

View File

@@ -0,0 +1,55 @@
// Copyright (C) 2010 - 2014 GlavSoft LLC.
// All rights reserved.
//
// -----------------------------------------------------------------------
// This file is part of the TightVNC software. Please visit our Web site:
//
// http://www.tightvnc.com/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// -----------------------------------------------------------------------
//
package com.glavsoft.rfb.client;
import com.glavsoft.exceptions.TransportException;
import com.glavsoft.transport.Transport;
public class PointerEventMessage implements ClientToServerMessage {
private final byte buttonMask;
private final short x;
private final short y;
public PointerEventMessage(byte buttonMask, short x, short y) {
this.buttonMask = buttonMask;
this.x = x;
this.y = y;
}
@Override
public void send(Transport transport) throws TransportException {
transport.writeByte(ClientMessageType.POINTER_EVENT.id)
.writeByte(buttonMask)
.writeInt16(x)
.writeInt16(y)
.flush();
}
@Override
public String toString() {
return "PointerEventMessage: [x: "+ x +", y: "+ y + ", button-mask: " +
buttonMask +"]";
}
}

View File

@@ -0,0 +1,60 @@
// Copyright (C) 2010 - 2014 GlavSoft LLC.
// All rights reserved.
//
// -----------------------------------------------------------------------
// This file is part of the TightVNC software. Please visit our Web site:
//
// http://www.tightvnc.com/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// -----------------------------------------------------------------------
//
package com.glavsoft.rfb.client;
import com.glavsoft.exceptions.TransportException;
import com.glavsoft.rfb.encoding.EncodingType;
import com.glavsoft.transport.Transport;
import java.util.Set;
public class SetEncodingsMessage implements ClientToServerMessage {
private final Set<EncodingType> encodings;
public SetEncodingsMessage(Set<EncodingType> set) {
this.encodings = set;
}
@Override
public void send(Transport transport) throws TransportException {
transport.writeByte(ClientMessageType.SET_ENCODINGS.id)
.zero(1) // padding byte
.writeInt16(encodings.size());
for (EncodingType enc : encodings) {
transport.writeInt32(enc.getId());
}
transport.flush();
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("SetEncodingsMessage: [encodings: ");
for (EncodingType enc : encodings) {
sb.append(enc.name()).append(',');
}
sb.setLength(sb.length()-1);
return sb.append(']').toString();
}
}

View File

@@ -0,0 +1,45 @@
// Copyright (C) 2010 - 2014 GlavSoft LLC.
// All rights reserved.
//
// -----------------------------------------------------------------------
// This file is part of the TightVNC software. Please visit our Web site:
//
// http://www.tightvnc.com/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// -----------------------------------------------------------------------
//
package com.glavsoft.rfb.client;
import com.glavsoft.exceptions.TransportException;
import com.glavsoft.rfb.encoding.PixelFormat;
import com.glavsoft.transport.Transport;
public class SetPixelFormatMessage implements ClientToServerMessage {
private final PixelFormat pixelFormat;
public SetPixelFormatMessage(PixelFormat pixelFormat) {
this.pixelFormat = pixelFormat;
}
@Override
public void send(Transport transport) throws TransportException {
transport.writeByte(ClientMessageType.SET_PIXEL_FORMAT.id)
.zero(3);
pixelFormat.send(transport);
transport.flush();
}
}

View File

@@ -0,0 +1,46 @@
// Copyright (C) 2010 - 2014 GlavSoft LLC.
// All rights reserved.
//
// -----------------------------------------------------------------------
// This file is part of the TightVNC software. Please visit our Web site:
//
// http://www.tightvnc.com/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// -----------------------------------------------------------------------
//
package com.glavsoft.rfb.client;
import com.glavsoft.exceptions.TransportException;
import com.glavsoft.transport.Transport;
/**
* @author dime at glavsoft.com
*/
public class VideoFreezeMessage implements ClientToServerMessage {
private boolean freeze;
public VideoFreezeMessage(boolean freeze) {
this.freeze = freeze;
}
@Override
public void send(Transport transport) throws TransportException {
transport.writeByte(ClientMessageType.VIDEO_FREEZE.id)
.writeByte(freeze ? 1 : 0)
.flush();
}
}

View File

@@ -0,0 +1,56 @@
// Copyright (C) 2010 - 2014 GlavSoft LLC.
// All rights reserved.
//
// -----------------------------------------------------------------------
// This file is part of the TightVNC software. Please visit our Web site:
//
// http://www.tightvnc.com/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// -----------------------------------------------------------------------
//
package com.glavsoft.rfb.client;
import com.glavsoft.exceptions.TransportException;
import com.glavsoft.transport.Transport;
/**
* @author dime at glavsoft.com
*/
public class VideoRectangleSelectionMessage implements ClientToServerMessage {
private final int x;
private final int y;
private final int width;
private final int height;
public VideoRectangleSelectionMessage(int x, int y, int width, int height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
@Override
public void send(Transport transport) throws TransportException {
transport.writeByte(ClientMessageType.VIDEO_RECTANGLE_SELECTION.id)
.zero(1) // padding
.writeInt16(x)
.writeInt16(y)
.writeInt16(width)
.writeInt16(height)
.flush();
}
}

View File

@@ -0,0 +1,156 @@
// Copyright (C) 2010 - 2014 GlavSoft LLC.
// All rights reserved.
//
// -----------------------------------------------------------------------
// This file is part of the TightVNC software. Please visit our Web site:
//
// http://www.tightvnc.com/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// -----------------------------------------------------------------------
//
package com.glavsoft.rfb.encoding;
import com.glavsoft.rfb.encoding.decoder.CopyRectDecoder;
import com.glavsoft.rfb.encoding.decoder.CursorPosDecoder;
import com.glavsoft.rfb.encoding.decoder.Decoder;
import com.glavsoft.rfb.encoding.decoder.DesctopSizeDecoder;
import com.glavsoft.rfb.encoding.decoder.FakeDecoder;
import com.glavsoft.rfb.encoding.decoder.HextileDecoder;
import com.glavsoft.rfb.encoding.decoder.RREDecoder;
import com.glavsoft.rfb.encoding.decoder.RawDecoder;
import com.glavsoft.rfb.encoding.decoder.RichCursorDecoder;
import com.glavsoft.rfb.encoding.decoder.TightDecoder;
import com.glavsoft.rfb.encoding.decoder.ZRLEDecoder;
import com.glavsoft.rfb.encoding.decoder.ZlibDecoder;
import java.util.LinkedHashSet;
/**
* Encoding types
*/
public enum EncodingType {
/**
* Desktop data representes as raw bytes stream
*/
RAW_ENCODING(0, "Raw", RawDecoder.class),
/**
* Specfies encodings which allow to copy part of image in client's
* framebuffer from one place to another.
*/
COPY_RECT(1, "CopyRect", CopyRectDecoder.class),
RRE(2, "RRE", RREDecoder.class),
/**
* Hextile encoding, uses palettes, filling and raw subencoding
*/
HEXTILE(5, "Hextile", HextileDecoder.class),
/**
* This encoding is like raw but previously all data compressed with zlib.
*/
ZLIB(6, "ZLib", ZlibDecoder.class),
/**
* Tight Encoding for slow connection. It is uses raw data, palettes, filling
* and jpeg subencodings
*/
TIGHT(7, "Tight", TightDecoder.class),
//ZlibHex(8),
/**
* ZRLE Encoding is like Hextile but previously all data compressed with zlib.
*/
ZRLE(16, "ZRLE", ZRLEDecoder.class),
/**
* Rich Cursor pseudo encoding which allows to transfer cursor shape
* with transparency
*/
RICH_CURSOR(0xFFFFFF11, "RichCursor", RichCursorDecoder.class),
/**
* Desktop Size Pseudo encoding allows to notificate client about
* remote screen resolution changed.
*/
DESKTOP_SIZE(0xFFFFFF21, "DesctopSize", DesctopSizeDecoder.class),
/**
* Cusros position encoding allows to transfer remote cursor position to
* client side.
*/
CURSOR_POS(0xFFFFFF18, "CursorPos", CursorPosDecoder.class),
COMPRESS_LEVEL_0(0xFFFFFF00 + 0, "CompressionLevel0", FakeDecoder.class),
COMPRESS_LEVEL_1(0xFFFFFF00 + 1, "CompressionLevel1", null),
COMPRESS_LEVEL_2(0xFFFFFF00 + 2, "CompressionLevel2", null),
COMPRESS_LEVEL_3(0xFFFFFF00 + 3, "CompressionLevel3", null),
COMPRESS_LEVEL_4(0xFFFFFF00 + 4, "CompressionLevel4", null),
COMPRESS_LEVEL_5(0xFFFFFF00 + 5, "CompressionLevel5", null),
COMPRESS_LEVEL_6(0xFFFFFF00 + 6, "CompressionLevel6", null),
COMPRESS_LEVEL_7(0xFFFFFF00 + 7, "CompressionLevel7", null),
COMPRESS_LEVEL_8(0xFFFFFF00 + 8, "CompressionLevel8", null),
COMPRESS_LEVEL_9(0xFFFFFF00 + 9, "CompressionLevel9", null),
JPEG_QUALITY_LEVEL_0(0xFFFFFFE0 + 0, "JpegQualityLevel0", FakeDecoder.class),
JPEG_QUALITY_LEVEL_1(0xFFFFFFE0 + 1, "JpegQualityLevel1", null),
JPEG_QUALITY_LEVEL_2(0xFFFFFFE0 + 2, "JpegQualityLevel2", null),
JPEG_QUALITY_LEVEL_3(0xFFFFFFE0 + 3, "JpegQualityLevel3", null),
JPEG_QUALITY_LEVEL_4(0xFFFFFFE0 + 4, "JpegQualityLevel4", null),
JPEG_QUALITY_LEVEL_5(0xFFFFFFE0 + 5, "JpegQualityLevel5", null),
JPEG_QUALITY_LEVEL_6(0xFFFFFFE0 + 6, "JpegQualityLevel6", null),
JPEG_QUALITY_LEVEL_7(0xFFFFFFE0 + 7, "JpegQualityLevel7", null),
JPEG_QUALITY_LEVEL_8(0xFFFFFFE0 + 8, "JpegQualityLevel8", null),
JPEG_QUALITY_LEVEL_9(0xFFFFFFE0 + 9, "JpegQualityLevel9", null);
private final int id;
private final String name;
public final Class<? extends Decoder> klass;
private EncodingType(int id, String name, Class<? extends Decoder> klass) {
this.id = id;
this.name = name;
this.klass = klass;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public static final LinkedHashSet<EncodingType> ordinaryEncodings = new LinkedHashSet<EncodingType>();
static {
ordinaryEncodings.add(ZRLE);
ordinaryEncodings.add(TIGHT);
ordinaryEncodings.add(ZLIB);
ordinaryEncodings.add(HEXTILE);
ordinaryEncodings.add(RRE);
ordinaryEncodings.add(COPY_RECT);
ordinaryEncodings.add(RAW_ENCODING);
}
public static final LinkedHashSet<EncodingType> pseudoEncodings = new LinkedHashSet<EncodingType>();
static {
pseudoEncodings.add(RICH_CURSOR);
pseudoEncodings.add(CURSOR_POS);
pseudoEncodings.add(DESKTOP_SIZE);
}
public static EncodingType byId(int id) {
// TODO needs to speedup with hash usage?
for (EncodingType type : values()) {
if (type.getId() == id)
return type;
}
throw new IllegalArgumentException("Unsupported encoding code: " + id);
}
}

View File

@@ -0,0 +1,185 @@
// Copyright (C) 2010 - 2014 GlavSoft LLC.
// All rights reserved.
//
// -----------------------------------------------------------------------
// This file is part of the TightVNC software. Please visit our Web site:
//
// http://www.tightvnc.com/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// -----------------------------------------------------------------------
//
package com.glavsoft.rfb.encoding;
import com.glavsoft.exceptions.TransportException;
import com.glavsoft.transport.Transport;
/**
* Pixel Format:
* 1 - U8 - bits-per-pixel
* 1 - U8 - depth
* 1 - U8 - big-endian-flag
* 1 - U8 - true-color-flag
* 2 - U16 - red-max
* 2 - U16 - green-max
* 2 - U16 - blue-max
* 1 - U8 - red-shift
* 1 - U8 - green-shift
* 1 - U8 - blue-shift
* 3 - - padding
*/
public class PixelFormat {
public byte bitsPerPixel;
public byte depth;
public byte bigEndianFlag;
public byte trueColourFlag;
public short redMax;
public short greenMax;
public short blueMax;
public byte redShift;
public byte greenShift;
public byte blueShift;
public void fill(Transport transport) throws TransportException {
bitsPerPixel = transport.readByte();
depth = transport.readByte();
bigEndianFlag = transport.readByte();
trueColourFlag = transport.readByte();
redMax = transport.readInt16();
greenMax = transport.readInt16();
blueMax = transport.readInt16();
redShift = transport.readByte();
greenShift = transport.readByte();
blueShift = transport.readByte();
transport.readBytes(3); // skip padding bytes
}
public void send(Transport transport) throws TransportException {
transport.write(bitsPerPixel)
.write(depth)
.write(bigEndianFlag)
.write(trueColourFlag)
.write(redMax)
.write(greenMax)
.write(blueMax)
.write(redShift)
.write(greenShift)
.write(blueShift)
.writeInt16(0) // padding bytes
.writeByte(0); // padding bytes
}
public static PixelFormat create24bitColorDepthPixelFormat(int bigEndianFlag) {
final PixelFormat pixelFormat = new PixelFormat();
pixelFormat.bigEndianFlag = (byte) bigEndianFlag;
pixelFormat.bitsPerPixel = 32;
pixelFormat.blueMax = 255;
pixelFormat.blueShift = 0;
pixelFormat.greenMax = 255;
pixelFormat.greenShift = 8;
pixelFormat.redMax = 255;
pixelFormat.redShift = 16;
pixelFormat.depth = 24;
pixelFormat.trueColourFlag = 1;
return pixelFormat;
}
/**
* specifies 65536 colors, 5bit per Red, 6bit per Green, 5bit per Blue
*/
public static PixelFormat create16bitColorDepthPixelFormat(int bigEndianFlag) {
final PixelFormat pixelFormat = new PixelFormat();
pixelFormat.bigEndianFlag = (byte) bigEndianFlag;
pixelFormat.bitsPerPixel = 16;
pixelFormat.blueMax = 31;
pixelFormat.blueShift = 0;
pixelFormat.greenMax = 63;
pixelFormat.greenShift = 5;
pixelFormat.redMax = 31;
pixelFormat.redShift = 11;
pixelFormat.depth = 16;
pixelFormat.trueColourFlag = 1;
return pixelFormat;
}
/**
* specifies 256 colors, 2bit per Blue, 3bit per Green &amp; Red
*/
public static PixelFormat create8bitColorDepthBGRPixelFormat(int bigEndianFlag) {
final PixelFormat pixelFormat = new PixelFormat();
pixelFormat.bigEndianFlag = (byte) bigEndianFlag;
pixelFormat.bitsPerPixel = 8;
pixelFormat.redMax = 7;
pixelFormat.redShift = 0;
pixelFormat.greenMax = 7;
pixelFormat.greenShift = 3;
pixelFormat.blueMax = 3;
pixelFormat.blueShift = 6;
pixelFormat.depth = 8;
pixelFormat.trueColourFlag = 1;
return pixelFormat;
}
/**
* specifies 64 colors, 2bit per Red, Green &amp; Blue
*/
public static PixelFormat create6bitColorDepthPixelFormat(int bigEndianFlag) {
final PixelFormat pixelFormat = new PixelFormat();
pixelFormat.bigEndianFlag = (byte) bigEndianFlag;
pixelFormat.bitsPerPixel = 8;
pixelFormat.blueMax = 3;
pixelFormat.blueShift = 0;
pixelFormat.greenMax = 3;
pixelFormat.greenShift = 2;
pixelFormat.redMax = 3;
pixelFormat.redShift = 4;
pixelFormat.depth = 6;
pixelFormat.trueColourFlag = 1;
return pixelFormat;
}
/**
* specifies 8 colors, 1bit per Red, Green &amp; Blue
*/
public static PixelFormat create3bitColorDepthPixelFormat(int bigEndianFlag) {
final PixelFormat pixelFormat = new PixelFormat();
pixelFormat.bigEndianFlag = (byte) bigEndianFlag;
pixelFormat.bitsPerPixel = 8;
pixelFormat.blueMax = 1;
pixelFormat.blueShift = 0;
pixelFormat.greenMax = 1;
pixelFormat.greenShift = 1;
pixelFormat.redMax = 1;
pixelFormat.redShift = 2;
pixelFormat.depth = 3;
pixelFormat.trueColourFlag = 1;
return pixelFormat;
}
@Override
public String toString() {
return "PixelFormat: [bits-per-pixel: " + String.valueOf(0xff & bitsPerPixel) +
", depth: " + String.valueOf(0xff & depth) +
", big-endian-flag: " + String.valueOf(0xff & bigEndianFlag) +
", true-color-flag: " + String.valueOf(0xff & trueColourFlag) +
", red-max: " + String.valueOf(0xffff & redMax) +
", green-max: " + String.valueOf(0xffff & greenMax) +
", blue-max: " + String.valueOf(0xffff & blueMax) +
", red-shift: " + String.valueOf(0xff & redShift) +
", green-shift: " + String.valueOf(0xff & greenShift) +
", blue-shift: " + String.valueOf(0xff & blueShift) +
"]";
}
}

View File

@@ -0,0 +1,77 @@
// Copyright (C) 2010 - 2014 GlavSoft LLC.
// All rights reserved.
//
// -----------------------------------------------------------------------
// This file is part of the TightVNC software. Please visit our Web site:
//
// http://www.tightvnc.com/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// -----------------------------------------------------------------------
//
package com.glavsoft.rfb.encoding;
import com.glavsoft.exceptions.TransportException;
import com.glavsoft.transport.Transport;
/**
* Struct filled from the ServerInit message
* 2 - U16 - framebuffer-width
* 2 - U16 - framebuffer-height
* 16 - PixelFormat - server-pixel-format
* 4 - U32 - name-length
* name-length - U8 array - name-string
*/
public class ServerInitMessage {
protected String name;
protected int framebufferWidth;
protected int framebufferHeight;
protected PixelFormat pixelFormat;
public ServerInitMessage readFrom(Transport transport) throws TransportException {
framebufferWidth = transport.readUInt16();
framebufferHeight = transport.readUInt16();
pixelFormat = new PixelFormat();
pixelFormat.fill(transport);
name = transport.readString();
return this;
}
public int getFramebufferWidth() {
return framebufferWidth;
}
public int getFramebufferHeight() {
return framebufferHeight;
}
public PixelFormat getPixelFormat() {
return pixelFormat;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "ServerInitMessage{" +
"name='" + name + '\'' +
", framebufferWidth=" + framebufferWidth +
", framebufferHeight=" + framebufferHeight +
", pixelFormat=" + pixelFormat +
'}';
}
}

View File

@@ -0,0 +1,65 @@
// Copyright (C) 2010 - 2014 GlavSoft LLC.
// All rights reserved.
//
// -----------------------------------------------------------------------
// This file is part of the TightVNC software. Please visit our Web site:
//
// http://www.tightvnc.com/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// -----------------------------------------------------------------------
//
package com.glavsoft.rfb.encoding.decoder;
/**
* Resizeable to needed length byte buffer
* Singleton for share among decoders.
*/
public class ByteBuffer {
private static ThreadLocal<ByteBuffer> threadLocal = new ThreadLocal<ByteBuffer>() {
@Override
protected ByteBuffer initialValue() {
return new ByteBuffer();
}
};
private byte [] buffer = new byte[0];
private ByteBuffer() { /*empty*/ }
public static ByteBuffer getInstance() {
return threadLocal.get();
}
public static void removeInstance() {
threadLocal.remove();
}
/**
* Checks for buffer capacity is enougth ( &lt; length) and enlarge it if not
*/
public void correctBufferCapacity(int length) {
// procondition: buffer != null
assert (buffer != null);
if (buffer.length < length) {
buffer = new byte[length];
}
}
public byte[] getBuffer(int length) {
correctBufferCapacity(length);
return buffer;
}
}

View File

@@ -0,0 +1,41 @@
// Copyright (C) 2010 - 2014 GlavSoft LLC.
// All rights reserved.
//
// -----------------------------------------------------------------------
// This file is part of the TightVNC software. Please visit our Web site:
//
// http://www.tightvnc.com/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// -----------------------------------------------------------------------
//
package com.glavsoft.rfb.encoding.decoder;
import com.glavsoft.drawing.Renderer;
import com.glavsoft.exceptions.TransportException;
import com.glavsoft.transport.Transport;
public class CopyRectDecoder extends Decoder {
@Override
public void decode(Transport transport, Renderer renderer,
FramebufferUpdateRectangle rect) throws TransportException {
int srcX = transport.readUInt16();
int srcY = transport.readUInt16();
if (rect.width == 0 || rect.height == 0) return;
renderer.copyRect(srcX, srcY, rect);
}
}

View File

@@ -0,0 +1,39 @@
// Copyright (C) 2010 - 2014 GlavSoft LLC.
// All rights reserved.
//
// -----------------------------------------------------------------------
// This file is part of the TightVNC software. Please visit our Web site:
//
// http://www.tightvnc.com/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// -----------------------------------------------------------------------
//
package com.glavsoft.rfb.encoding.decoder;
import com.glavsoft.drawing.Renderer;
import com.glavsoft.exceptions.TransportException;
import com.glavsoft.transport.Transport;
/**
* @author dime at glavsoft.com
*/
public class CursorPosDecoder extends Decoder {
@Override
public void decode(Transport transport, Renderer renderer, FramebufferUpdateRectangle rect) throws TransportException {
renderer.decodeCursorPosition(rect);
}
}

View File

@@ -0,0 +1,43 @@
// Copyright (C) 2010 - 2014 GlavSoft LLC.
// All rights reserved.
//
// -----------------------------------------------------------------------
// This file is part of the TightVNC software. Please visit our Web site:
//
// http://www.tightvnc.com/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// -----------------------------------------------------------------------
//
package com.glavsoft.rfb.encoding.decoder;
import com.glavsoft.drawing.Renderer;
import com.glavsoft.exceptions.TransportException;
import com.glavsoft.transport.Transport;
public abstract class Decoder {
/**
* Decode rectangle data.
*/
abstract public void decode(Transport transport, Renderer renderer,
FramebufferUpdateRectangle rect) throws TransportException;
/**
* Reset decoder when needed. Ex. reset ZLib stream inflaters for Z* and Tight decoders.
*/
public void reset() { /*empty*/ }
}

View File

@@ -0,0 +1,39 @@
// Copyright (C) 2010 - 2014 GlavSoft LLC.
// All rights reserved.
//
// -----------------------------------------------------------------------
// This file is part of the TightVNC software. Please visit our Web site:
//
// http://www.tightvnc.com/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// -----------------------------------------------------------------------
//
package com.glavsoft.rfb.encoding.decoder;
import com.glavsoft.drawing.Renderer;
import com.glavsoft.exceptions.TransportException;
import com.glavsoft.transport.Transport;
/**
* @author dime at glavsoft.com
*/
public class DesctopSizeDecoder extends Decoder {
@Override
public void decode(Transport transport, Renderer renderer, FramebufferUpdateRectangle rect) throws TransportException {
// do nothing, all real job will be done in ReceiverTask.framebufferUpdateMessage()
}
}

View File

@@ -0,0 +1,39 @@
// Copyright (C) 2010 - 2014 GlavSoft LLC.
// All rights reserved.
//
// -----------------------------------------------------------------------
// This file is part of the TightVNC software. Please visit our Web site:
//
// http://www.tightvnc.com/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// -----------------------------------------------------------------------
//
package com.glavsoft.rfb.encoding.decoder;
import com.glavsoft.drawing.Renderer;
import com.glavsoft.exceptions.TransportException;
import com.glavsoft.transport.Transport;
/**
* @author dime at glavsoft.com
*/
public class FakeDecoder extends Decoder {
@Override
public void decode(Transport transport, Renderer renderer, FramebufferUpdateRectangle rect) throws TransportException {
throw new IllegalStateException("Improper use of fake decoder");
}
}

View File

@@ -0,0 +1,76 @@
// Copyright (C) 2010 - 2014 GlavSoft LLC.
// All rights reserved.
//
// -----------------------------------------------------------------------
// This file is part of the TightVNC software. Please visit our Web site:
//
// http://www.tightvnc.com/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// -----------------------------------------------------------------------
//
package com.glavsoft.rfb.encoding.decoder;
import com.glavsoft.exceptions.TransportException;
import com.glavsoft.rfb.encoding.EncodingType;
import com.glavsoft.transport.Transport;
/**
* Header for framebuffer-update-rectangle header server message
* 2 - U16 - x-position
* 2 - U16 - y-position
* 2 - U16 - width
* 2 - U16 - height
* 4 - S32 - encoding-type
* and then follows the pixel data in the specified encoding
*/
public class FramebufferUpdateRectangle {
public int x;
public int y;
public int width;
public int height;
private EncodingType encodingType;
public FramebufferUpdateRectangle() {
// nop
}
public FramebufferUpdateRectangle(int x, int y, int w, int h) {
this.x = x; this.y = y;
width = w; height = h;
}
public void fill(Transport transport) throws TransportException {
x = transport.readUInt16();
y = transport.readUInt16();
width = transport.readUInt16();
height = transport.readUInt16();
int encoding = transport.readInt32();
encodingType = EncodingType.byId(encoding);
}
public EncodingType getEncodingType() {
return encodingType;
}
@Override
public String toString() {
return "FramebufferUpdateRect: [x: " + x + ", y: " + y +
", width: " + width + ", height: " + height +
", encodingType: " + encodingType +
"]";
}
}

View File

@@ -0,0 +1,103 @@
// Copyright (C) 2010 - 2014 GlavSoft LLC.
// All rights reserved.
//
// -----------------------------------------------------------------------
// This file is part of the TightVNC software. Please visit our Web site:
//
// http://www.tightvnc.com/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// -----------------------------------------------------------------------
//
package com.glavsoft.rfb.encoding.decoder;
import com.glavsoft.drawing.Renderer;
import com.glavsoft.exceptions.TransportException;
import com.glavsoft.transport.Transport;
public class HextileDecoder extends Decoder {
private static final int DEFAULT_TILE_SIZE = 16;
private static final int RAW_MASK = 1;
private static final int BACKGROUND_SPECIFIED_MASK = 2;
private static final int FOREGROUND_SPECIFIED_MASK = 4;
private static final int ANY_SUBRECTS_MASK = 8;
private static final int SUBRECTS_COLOURED_MASK = 16;
private static final int FG_COLOR_INDEX = 0;
private static final int BG_COLOR_INDEX = 1;
@Override
public void decode(Transport transport, Renderer renderer,
FramebufferUpdateRectangle rect) throws TransportException {
if (rect.width == 0 || rect.height == 0) return;
int[] colors = new int[] {-1, -1};
int maxX = rect.x + rect.width;
int maxY = rect.y + rect.height;
for (int tileY = rect.y; tileY < maxY;
tileY += DEFAULT_TILE_SIZE) {
int tileHeight = Math.min(maxY - tileY, DEFAULT_TILE_SIZE);
for (int tileX = rect.x; tileX < maxX;
tileX += DEFAULT_TILE_SIZE) {
int tileWidth = Math.min(maxX - tileX, DEFAULT_TILE_SIZE);
decodeTile(transport, renderer, colors, tileX,
tileY, tileWidth, tileHeight);
}
}
}
private void decodeTile(Transport transport,
Renderer renderer, int[] colors,
int tileX, int tileY, int tileWidth, int tileHeight)
throws TransportException {
int subencoding = transport.readUInt8();
if ((subencoding & RAW_MASK) != 0) {
RawDecoder.getInstance().decode(transport, renderer,
tileX, tileY, tileWidth, tileHeight);
return;
}
if ((subencoding & BACKGROUND_SPECIFIED_MASK) != 0) {
colors[BG_COLOR_INDEX] = renderer.readPixelColor(transport);
}
renderer.fillRect(colors[BG_COLOR_INDEX],
tileX, tileY, tileWidth, tileHeight);
if ((subencoding & FOREGROUND_SPECIFIED_MASK) != 0) {
colors[FG_COLOR_INDEX] = renderer.readPixelColor(transport);
}
if ((subencoding & ANY_SUBRECTS_MASK) == 0)
return;
int numberOfSubrectangles = transport.readUInt8();
boolean colorSpecified = (subencoding & SUBRECTS_COLOURED_MASK) != 0;
for (int i = 0; i < numberOfSubrectangles; ++i) {
int color = colorSpecified ? renderer.readPixelColor(transport) : colors[FG_COLOR_INDEX];
colors[FG_COLOR_INDEX] = color;
byte dimensions = transport.readByte(); // bits 7-4 for x, bits 3-0 for y
int subtileX = dimensions >> 4 & 0x0f;
int subtileY = dimensions & 0x0f;
dimensions = transport.readByte(); // bits 7-4 for w, bits 3-0 for h
int subtileWidth = 1 + (dimensions >> 4 & 0x0f);
int subtileHeight = 1 + (dimensions & 0x0f);
renderer.fillRect(color,
tileX + subtileX, tileY + subtileY,
subtileWidth, subtileHeight);
}
}
}

View File

@@ -0,0 +1,49 @@
// Copyright (C) 2010 - 2014 GlavSoft LLC.
// All rights reserved.
//
// -----------------------------------------------------------------------
// This file is part of the TightVNC software. Please visit our Web site:
//
// http://www.tightvnc.com/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// -----------------------------------------------------------------------
//
package com.glavsoft.rfb.encoding.decoder;
import com.glavsoft.drawing.Renderer;
import com.glavsoft.exceptions.TransportException;
import com.glavsoft.transport.Transport;
public class RREDecoder extends Decoder {
@Override
public void decode(Transport transport, Renderer renderer,
FramebufferUpdateRectangle rect) throws TransportException {
int numOfSubrectangles = transport.readInt32();
int color = renderer.readPixelColor(transport);
renderer.fillRect(color, rect);
for (int i = 0; i < numOfSubrectangles; ++i) {
color = renderer.readPixelColor(transport);
int x = transport.readUInt16();
int y = transport.readUInt16();
int width = transport.readUInt16();
int height = transport.readUInt16();
renderer.fillRect(color, rect.x + x, rect.y + y, width, height);
}
}
}

View File

@@ -0,0 +1,51 @@
// Copyright (C) 2010 - 2014 GlavSoft LLC.
// All rights reserved.
//
// -----------------------------------------------------------------------
// This file is part of the TightVNC software. Please visit our Web site:
//
// http://www.tightvnc.com/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// -----------------------------------------------------------------------
//
package com.glavsoft.rfb.encoding.decoder;
import com.glavsoft.drawing.Renderer;
import com.glavsoft.exceptions.TransportException;
import com.glavsoft.transport.Transport;
public class RawDecoder extends Decoder {
private static RawDecoder instance = new RawDecoder();
public static RawDecoder getInstance() {
return instance;
}
private RawDecoder() { /*empty*/ }
@Override
public void decode(Transport transport,
Renderer renderer, FramebufferUpdateRectangle rect) throws TransportException {
decode(transport, renderer, rect.x, rect.y, rect.width, rect.height);
}
public void decode(Transport transport, Renderer renderer, int x, int y,
int width, int height) throws TransportException {
int length = width * height * renderer.getBytesPerPixel();
byte [] bytes = ByteBuffer.getInstance().getBuffer(length);
transport.readBytes(bytes, 0, length);
renderer.drawBytes(bytes, x, y, width, height);
}
}

View File

@@ -0,0 +1,73 @@
// Copyright (C) 2010 - 2014 GlavSoft LLC.
// All rights reserved.
//
// -----------------------------------------------------------------------
// This file is part of the TightVNC software. Please visit our Web site:
//
// http://www.tightvnc.com/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// -----------------------------------------------------------------------
//
package com.glavsoft.rfb.encoding.decoder;
import com.glavsoft.drawing.Renderer;
import com.glavsoft.exceptions.TransportException;
import com.glavsoft.transport.Transport;
/**
* Decoder for RichCursor pseudo encoding
*/
public class RichCursorDecoder extends Decoder {
@Override
public void decode(Transport transport, Renderer renderer,
FramebufferUpdateRectangle rect) throws TransportException {
int bytesPerPixel = renderer.getBytesPerPixel();
int length = rect.width * rect.height * bytesPerPixel;
if (0 == length)
return;
byte[] buffer = ByteBuffer.getInstance().getBuffer(length);
transport.readBytes(buffer, 0, length);
StringBuilder sb = new StringBuilder(" ");
for (int i=0; i<length; ++i) {
sb.append(Integer.toHexString(buffer[i]&0xff)).append(" ");
}
int scanLine = (rect.width + 7) / 8;
byte[] bitmask = new byte[scanLine * rect.height];
transport.readBytes(bitmask, 0, bitmask.length);
sb = new StringBuilder(" ");
for (byte aBitmask : bitmask) {
sb.append(Integer.toHexString(aBitmask & 0xff)).append(" ");
}
int[] cursorPixels = new int[rect.width * rect.height];
for (int y = 0; y < rect.height; ++y) {
for (int x = 0; x < rect.width; ++x) {
int offset = y * rect.width + x;
cursorPixels[offset] = isBitSet(bitmask[y * scanLine + x / 8], x % 8) ?
0xFF000000 | renderer.getPixelColor(buffer, offset * bytesPerPixel) :
0; // transparent
}
}
renderer.createCursor(cursorPixels, rect);
}
private boolean isBitSet(byte aByte, int index) {
return (aByte & 1 << 7 - index) > 0;
}
}

View File

@@ -0,0 +1,291 @@
// Copyright (C) 2010 - 2014 GlavSoft LLC.
// All rights reserved.
//
// -----------------------------------------------------------------------
// This file is part of the TightVNC software. Please visit our Web site:
//
// http://www.tightvnc.com/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// -----------------------------------------------------------------------
//
package com.glavsoft.rfb.encoding.decoder;
import com.glavsoft.drawing.ColorDecoder;
import com.glavsoft.drawing.Renderer;
import com.glavsoft.exceptions.TransportException;
import com.glavsoft.transport.Transport;
import java.util.logging.Logger;
import java.util.zip.DataFormatException;
import java.util.zip.Inflater;
/**
* Tight protocol extention decoder
*/
public class TightDecoder extends Decoder {
private static Logger logger = Logger.getLogger("com.glavsoft.rfb.encoding.decoder");
private static final int FILL_TYPE = 0x08;
private static final int JPEG_TYPE = 0x09;
private static final int FILTER_ID_MASK = 0x40;
private static final int STREAM_ID_MASK = 0x30;
private static final int BASIC_FILTER = 0x00;
private static final int PALETTE_FILTER = 0x01;
private static final int GRADIENT_FILTER = 0x02;
private static final int MIN_SIZE_TO_COMPRESS = 12;
static final int DECODERS_NUM = 4;
Inflater[] decoders;
private int decoderId;
private int[] palette;
public TightDecoder() {
reset();
}
@Override
public void decode(Transport transport, Renderer renderer,
FramebufferUpdateRectangle rect) throws TransportException {
int bytesPerPixel = renderer.getBytesPerPixelTight();
/**
* bits
* 7 - FILL or JPEG type
* 6 - filter presence flag
* 5, 4 - decoder to use when Basic type (bit 7 not set)
* or
* 4 - JPEG type when set bit 7
* 3 - reset decoder #3
* 2 - reset decoder #2
* 1 - reset decoder #1
* 0 - reset decoder #0
*/
int compControl = transport.readUInt8();
resetDecoders(compControl);
int compType = compControl >> 4 & 0x0F;
switch (compType) {
case FILL_TYPE:
int color = renderer.readTightPixelColor(transport);
renderer.fillRect(color, rect);
break;
case JPEG_TYPE:
assert 3 == bytesPerPixel : "Tight doesn't support JPEG subencoding while depth not equal to 24bpp is used";
processJpegType(transport, renderer, rect);
break;
default:
assert compType <= JPEG_TYPE : "Compression control byte is incorrect!";
processBasicType(compControl, transport, renderer, rect);
}
}
private void processBasicType(int compControl, Transport transport,
Renderer renderer, FramebufferUpdateRectangle rect) throws TransportException {
decoderId = (compControl & STREAM_ID_MASK) >> 4;
int filterId = 0;
if ((compControl & FILTER_ID_MASK) > 0) { // filter byte presence
filterId = transport.readUInt8();
}
int bytesPerCPixel = renderer.getBytesPerPixelTight();
int lengthCurrentbpp = bytesPerCPixel * rect.width * rect.height;
byte [] buffer;
switch (filterId) {
case BASIC_FILTER:
buffer = readTightData(lengthCurrentbpp, transport);
renderer.drawTightBytes(buffer, 0, rect.x, rect.y, rect.width, rect.height);
break;
case PALETTE_FILTER:
int paletteSize = transport.readUInt8() + 1;
completePalette(paletteSize, transport, renderer);
int dataLength = paletteSize == 2 ?
rect.height * ((rect.width + 7) / 8) :
rect.width * rect.height;
buffer = readTightData(dataLength, transport);
renderer.drawBytesWithPalette(buffer, rect, palette, paletteSize);
break;
case GRADIENT_FILTER:
/*
* The "gradient" filter pre-processes pixel data with a simple algorithm
* which converts each color component to a difference between a "predicted"
* intensity and the actual intensity. Such a technique does not affect
* uncompressed data size, but helps to compress photo-like images better.
* Pseudo-code for converting intensities to differences is the following:
*
* P[i,j] := V[i-1,j] + V[i,j-1] - V[i-1,j-1];
* if (P[i,j] < 0) then P[i,j] := 0;
* if (P[i,j] > MAX) then P[i,j] := MAX;
* D[i,j] := V[i,j] - P[i,j];
*
* Here V[i,j] is the intensity of a color component for a pixel at
* coordinates (i,j). MAX is the maximum value of intensity for a color
* component.*/
buffer = readTightData(bytesPerCPixel * rect.width * rect.height, transport);
byte [][] opRows = new byte[2][rect.width * 3 + 3];
int opRowIndex = 0;
byte [] components = new byte[3];
int pixelOffset = 0;
ColorDecoder colorDecoder = renderer.getColorDecoder();
for (int i = 0; i < rect.height; ++i) {
// exchange thisRow and prevRow:
byte [] thisRow = opRows[opRowIndex];
byte [] prevRow = opRows[opRowIndex = (opRowIndex + 1) % 2];
for (int j = 3; j < rect.width * 3 + 3; j += 3) {
colorDecoder.fillRawComponents(components, buffer, pixelOffset);
pixelOffset += bytesPerCPixel;
int
d = (0xff & prevRow[j + 0]) + // "upper" pixel (from prev row)
(0xff & thisRow[j + 0 - 3]) - // prev pixel
(0xff & prevRow[j + 0 - 3]); // "diagonal" prev pixel
thisRow[j + 0] = (byte) (components[0] + (d < 0 ? 0 : d > colorDecoder.redMax ? colorDecoder.redMax: d) & colorDecoder.redMax);
d = (0xff & prevRow[j + 1]) +
(0xff & thisRow[j + 1 - 3]) -
(0xff & prevRow[j + 1 - 3]);
thisRow[j + 1] = (byte) (components[1] + (d < 0 ? 0 : d > colorDecoder.greenMax ? colorDecoder.greenMax: d) & colorDecoder.greenMax);
d = (0xff & prevRow[j + 2]) +
(0xff & thisRow[j + 2 - 3]) -
(0xff & prevRow[j + 2 - 3]);
thisRow[j + 2] = (byte) (components[2] + (d < 0 ? 0 : d > colorDecoder.blueMax ? colorDecoder.blueMax: d) & colorDecoder.blueMax);
}
renderer.drawUncaliberedRGBLine(thisRow, rect.x, rect.y + i, rect.width);
}
break;
default:
break;
}
}
/**
* Complete palette from transport
*/
private void completePalette(int paletteSize, Transport transport, Renderer renderer) throws TransportException {
/**
* When bytesPerPixel == 1 && paletteSize == 2 read 2 bytes of palette
* When bytesPerPixel == 1 && paletteSize != 2 - error
* When bytesPerPixel == 3 (4) read (paletteSize * 3) bytes of palette
* so use renderer.readPixelColor
*/
if (null == palette) palette = new int[256];
for (int i = 0; i < paletteSize; ++i) {
palette[i] = renderer.readTightPixelColor(transport);
}
}
/**
* Reads compressed (expected length >= MIN_SIZE_TO_COMPRESS) or
* uncompressed data. When compressed decompresses it.
*
* @param expectedLength expected data length in bytes
* @param transport data source
* @return result data
* @throws TransportException
*/
private byte[] readTightData(int expectedLength, Transport transport) throws TransportException {
if (expectedLength < MIN_SIZE_TO_COMPRESS) {
byte [] buffer = ByteBuffer.getInstance().getBuffer(expectedLength);
transport.readBytes(buffer, 0, expectedLength);
return buffer;
} else
return readCompressedData(expectedLength, transport);
}
/**
* Reads compressed data length, then read compressed data into rawBuffer
* and decompress data with expected length == length
*
* Note: returned data contains not only decompressed data but raw data at array tail
* which need to be ignored. Use only first expectedLength bytes.
*
* @param expectedLength expected data length
* @param transport data source
* @return decompressed data (length == expectedLength) / + followed raw data (ignore, please)
* @throws TransportException
*/
private byte[] readCompressedData(int expectedLength, Transport transport) throws TransportException {
int rawDataLength = readCompactSize(transport);
byte [] buffer = ByteBuffer.getInstance().getBuffer(expectedLength + rawDataLength);
// read compressed (raw) data behind space allocated for decompressed data
transport.readBytes(buffer, expectedLength, rawDataLength);
if (null == decoders[decoderId]) {
decoders[decoderId] = new Inflater();
}
Inflater decoder = decoders[decoderId];
decoder.setInput(buffer, expectedLength, rawDataLength);
try {
decoder.inflate(buffer, 0, expectedLength);
} catch (DataFormatException e) {
logger.throwing("TightDecoder", "readCompressedData", e);
throw new TransportException("cannot inflate tight compressed data", e);
}
return buffer;
}
private void processJpegType(Transport transport, Renderer renderer,
FramebufferUpdateRectangle rect) throws TransportException {
int jpegBufferLength = readCompactSize(transport);
byte [] bytes = ByteBuffer.getInstance().getBuffer(jpegBufferLength);
transport.readBytes(bytes, 0, jpegBufferLength);
renderer.drawJpegImage(bytes, 0, jpegBufferLength, rect);
}
/**
* Read an integer from transport in compact representation (from 1 to 3 bytes).
* Highest bit of read byte set to 1 means next byte contains data.
* Lower 7 bit of each byte contains significant data. Max bytes = 3.
* Less significant bytes first order.
*
* @param transport data source
* @return int value
* @throws TransportException
*/
private int readCompactSize(Transport transport) throws TransportException {
int b = transport.readUInt8();
int size = b & 0x7F;
if ((b & 0x80) != 0) {
b = transport.readUInt8();
size += (b & 0x7F) << 7;
if ((b & 0x80) != 0) {
size += transport.readUInt8() << 14;
}
}
return size;
}
/**
* Flush (reset) zlib decoders when bits 3, 2, 1, 0 of compControl is set
* @param compControl control flags
*/
private void resetDecoders(int compControl) {
for (int i=0; i < DECODERS_NUM; ++i) {
if ((compControl & 1) != 0 && decoders[i] != null) {
decoders[i].reset();
}
compControl >>= 1;
}
}
@Override
public void reset() {
decoders = new Inflater[DECODERS_NUM];
}
}

View File

@@ -0,0 +1,166 @@
// Copyright (C) 2010 - 2014 GlavSoft LLC.
// All rights reserved.
//
// -----------------------------------------------------------------------
// This file is part of the TightVNC software. Please visit our Web site:
//
// http://www.tightvnc.com/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// -----------------------------------------------------------------------
//
package com.glavsoft.rfb.encoding.decoder;
import com.glavsoft.drawing.Renderer;
import com.glavsoft.exceptions.TransportException;
import com.glavsoft.transport.Transport;
public class ZRLEDecoder extends ZlibDecoder {
private static final int MAX_TILE_SIZE = 64;
private int[] decodedBitmap;
private int[] palette;
@Override
public void decode(Transport transport, Renderer renderer,
FramebufferUpdateRectangle rect) throws TransportException {
int zippedLength = (int) transport.readUInt32();
if (0 == zippedLength) return;
int length = rect.width * rect.height * renderer.getBytesPerCPixel()
+ (rect.width / MAX_TILE_SIZE + 1) * (rect.height / MAX_TILE_SIZE + 1);
byte[] bytes = unzip(transport, zippedLength, length);
int offset = zippedLength;
int maxX = rect.x + rect.width;
int maxY = rect.y + rect.height;
if (null == palette) {
palette = new int [128];
}
if (null == decodedBitmap) {
decodedBitmap = new int[MAX_TILE_SIZE * MAX_TILE_SIZE];
}
for (int tileY = rect.y; tileY < maxY; tileY += MAX_TILE_SIZE) {
int tileHeight = Math.min(maxY - tileY, MAX_TILE_SIZE);
for (int tileX = rect.x; tileX < maxX; tileX += MAX_TILE_SIZE) {
int tileWidth = Math.min(maxX - tileX, MAX_TILE_SIZE);
int subencoding = bytes[offset++] & 0x0ff;
// 128 -plain RLE, 130-255 - Palette RLE
boolean isRle = (subencoding & 128) != 0;
// 2 to 16 for raw packed palette data, 130 to 255 for Palette RLE (subencoding - 128)
int paletteSize = subencoding & 127;
offset += readPalette(bytes, offset, renderer, paletteSize);
if (1 == subencoding) { // A solid tile consisting of a single colour
renderer.fillRect(palette[0], tileX, tileY, tileWidth, tileHeight);
continue;
}
if (isRle) {
if (0 == paletteSize) { // subencoding == 128 (or paletteSize == 0) - Plain RLE
offset += decodePlainRle(bytes, offset, renderer, tileX, tileY, tileWidth, tileHeight);
} else {
offset += decodePaletteRle(bytes, offset, renderer, tileX, tileY, tileWidth, tileHeight);
}
} else {
if (0 == paletteSize) { // subencoding == 0 (or paletteSize == 0) - raw CPIXEL data
offset += decodeRaw(bytes, offset, renderer, tileX, tileY, tileWidth, tileHeight);
} else {
offset += decodePacked(bytes, offset, renderer, paletteSize, tileX, tileY, tileWidth, tileHeight);
}
}
}
}
}
private int decodePlainRle(byte[] bytes, int offset, Renderer renderer,
int tileX, int tileY, int tileWidth, int tileHeight) {
int bytesPerCPixel = renderer.getBytesPerCPixel();
int decodedOffset = 0;
int decodedEnd = tileWidth * tileHeight;
int index = offset;
while (decodedOffset < decodedEnd) {
int color = renderer.getCompactPixelColor(bytes, index);
index += bytesPerCPixel;
int rlength = 1;
do {
rlength += bytes[index] & 0x0ff;
} while ((bytes[index++] & 0x0ff) == 255);
assert rlength <= decodedEnd - decodedOffset;
renderer.fillColorBitmapWithColor(decodedBitmap, decodedOffset, rlength, color);
decodedOffset += rlength;
}
renderer.drawColoredBitmap(decodedBitmap, tileX, tileY, tileWidth, tileHeight);
return index - offset;
}
private int decodePaletteRle(byte[] bytes, int offset, Renderer renderer,
int tileX, int tileY, int tileWidth, int tileHeight) {
int decodedOffset = 0;
int decodedEnd = tileWidth * tileHeight;
int index = offset;
while (decodedOffset < decodedEnd) {
int colorIndex = bytes[index++];
int color = palette[colorIndex & 127];
int rlength = 1;
if ((colorIndex & 128) != 0) {
do {
rlength += bytes[index] & 0x0ff;
} while (bytes[index++] == (byte) 255);
}
assert rlength <= decodedEnd - decodedOffset;
renderer.fillColorBitmapWithColor(decodedBitmap, decodedOffset, rlength, color);
decodedOffset += rlength;
}
renderer.drawColoredBitmap(decodedBitmap, tileX, tileY, tileWidth, tileHeight);
return index - offset;
}
private int decodePacked(byte[] bytes, int offset, Renderer renderer,
int paletteSize, int tileX, int tileY, int tileWidth, int tileHeight) {
int bitsPerPalletedPixel = paletteSize > 16 ? 8 : paletteSize > 4 ? 4 : paletteSize > 2 ? 2 : 1;
int packedOffset = offset;
int decodedOffset = 0;
for (int i = 0; i < tileHeight; ++i) {
int decodedRowEnd = decodedOffset + tileWidth;
int byteProcessed = 0;
int bitsRemain = 0;
while (decodedOffset < decodedRowEnd) {
if (bitsRemain == 0) {
byteProcessed = bytes[packedOffset++];
bitsRemain = 8;
}
bitsRemain -= bitsPerPalletedPixel;
int index = byteProcessed >> bitsRemain & (1 << bitsPerPalletedPixel) - 1 & 127;
int color = palette[index];
renderer.fillColorBitmapWithColor(decodedBitmap, decodedOffset, 1, color);
++decodedOffset;
}
}
renderer.drawColoredBitmap(decodedBitmap, tileX, tileY, tileWidth, tileHeight);
return packedOffset - offset;
}
private int decodeRaw(byte[] bytes, int offset, Renderer renderer,
int tileX, int tileY, int tileWidth, int tileHeight) throws TransportException {
return renderer.drawCompactBytes(bytes, offset, tileX, tileY, tileWidth, tileHeight);
}
private int readPalette(byte[] bytes, int offset, Renderer renderer, int paletteSize) {
final int bytesPerCPixel = renderer.getBytesPerCPixel();
for (int i=0; i<paletteSize; ++i) {
palette[i] = renderer.getCompactPixelColor(bytes, offset + i* bytesPerCPixel);
}
return paletteSize * bytesPerCPixel;
}
}

View File

@@ -0,0 +1,71 @@
// Copyright (C) 2010 - 2014 GlavSoft LLC.
// All rights reserved.
//
// -----------------------------------------------------------------------
// This file is part of the TightVNC software. Please visit our Web site:
//
// http://www.tightvnc.com/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// -----------------------------------------------------------------------
//
package com.glavsoft.rfb.encoding.decoder;
import com.glavsoft.drawing.Renderer;
import com.glavsoft.exceptions.TransportException;
import com.glavsoft.transport.Transport;
import java.io.ByteArrayInputStream;
import java.util.zip.DataFormatException;
import java.util.zip.Inflater;
public class ZlibDecoder extends Decoder {
private Inflater decoder;
@Override
public void decode(Transport transport, Renderer renderer,
FramebufferUpdateRectangle rect) throws TransportException {
int zippedLength = (int) transport.readUInt32();
if (0 == zippedLength) return;
int length = rect.width * rect.height * renderer.getBytesPerPixel();
byte[] bytes = unzip(transport, zippedLength, length);
Transport unzippedReader =
new Transport(
new ByteArrayInputStream(bytes, zippedLength, length));
RawDecoder.getInstance().decode(unzippedReader, renderer, rect);
}
protected byte[] unzip(Transport transport, int zippedLength, int length)
throws TransportException {
byte [] bytes = ByteBuffer.getInstance().getBuffer(zippedLength + length);
transport.readBytes(bytes, 0, zippedLength);
if (null == decoder) {
decoder = new Inflater();
}
decoder.setInput(bytes, 0, zippedLength);
try {
decoder.inflate(bytes, zippedLength, length);
} catch (DataFormatException e) {
throw new TransportException("cannot inflate Zlib data", e);
}
return bytes;
}
@Override
public void reset() {
decoder = null;
}
}

View File

@@ -0,0 +1,33 @@
// Copyright (C) 2010 - 2014 GlavSoft LLC.
// All rights reserved.
//
// -----------------------------------------------------------------------
// This file is part of the TightVNC software. Please visit our Web site:
//
// http://www.tightvnc.com/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// -----------------------------------------------------------------------
//
package com.glavsoft.rfb.protocol;
/**
* How to track local mouse cursor.
*/
public enum LocalPointer {
ON,
OFF,
HIDE,
}

View File

@@ -0,0 +1,60 @@
// Copyright (C) 2010 - 2014 GlavSoft LLC.
// All rights reserved.
//
// -----------------------------------------------------------------------
// This file is part of the TightVNC software. Please visit our Web site:
//
// http://www.tightvnc.com/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// -----------------------------------------------------------------------
//
package com.glavsoft.rfb.protocol;
import com.glavsoft.rfb.client.ClientToServerMessage;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
/**
* @author dime at tightvnc.com
*/
public class MessageQueue {
private final BlockingQueue<ClientToServerMessage> queue;
public MessageQueue() {
queue = new LinkedBlockingQueue<ClientToServerMessage>();
}
public void put(ClientToServerMessage message) {
if ( ! queue.offer(message)) {
Logger.getLogger(getClass().getName()).severe("Cannot put message into message queue. Skip: " + message);
}
}
/**
* Retrieves and removes the head of this queue, waiting if necessary until an element becomes available.
* Retrieves and removes the head of this queue, waiting up to the certain wait time if necessary for
* an element to become available.
* @return the head of this queue, or null if the specified waiting time elapses before an element is available
* @throws InterruptedException - if interrupted while waiting
*/
public ClientToServerMessage get() throws InterruptedException {
return queue.poll(1, TimeUnit.SECONDS);
}
}

View File

@@ -0,0 +1,473 @@
// Copyright (C) 2010 - 2014 GlavSoft LLC.
// All rights reserved.
//
// -----------------------------------------------------------------------
// This file is part of the TightVNC software. Please visit our Web site:
//
// http://www.tightvnc.com/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// -----------------------------------------------------------------------
//
package com.glavsoft.rfb.protocol;
import com.glavsoft.core.SettingsChangedEvent;
import com.glavsoft.exceptions.AuthenticationFailedException;
import com.glavsoft.exceptions.FatalException;
import com.glavsoft.exceptions.TransportException;
import com.glavsoft.exceptions.UnsupportedProtocolVersionException;
import com.glavsoft.exceptions.UnsupportedSecurityTypeException;
import com.glavsoft.rfb.ClipboardController;
import com.glavsoft.rfb.IChangeSettingsListener;
import com.glavsoft.rfb.IRepaintController;
import com.glavsoft.rfb.IRequestString;
import com.glavsoft.rfb.IRfbSessionListener;
import com.glavsoft.rfb.RfbCapabilityInfo;
import com.glavsoft.rfb.client.ClientMessageType;
import com.glavsoft.rfb.client.ClientToServerMessage;
import com.glavsoft.rfb.client.FramebufferUpdateRequestMessage;
import com.glavsoft.rfb.client.SetEncodingsMessage;
import com.glavsoft.rfb.client.SetPixelFormatMessage;
import com.glavsoft.rfb.encoding.EncodingType;
import com.glavsoft.rfb.encoding.PixelFormat;
import com.glavsoft.rfb.encoding.decoder.ByteBuffer;
import com.glavsoft.rfb.encoding.decoder.CopyRectDecoder;
import com.glavsoft.rfb.encoding.decoder.CursorPosDecoder;
import com.glavsoft.rfb.encoding.decoder.Decoder;
import com.glavsoft.rfb.encoding.decoder.DesctopSizeDecoder;
import com.glavsoft.rfb.encoding.decoder.HextileDecoder;
import com.glavsoft.rfb.encoding.decoder.RREDecoder;
import com.glavsoft.rfb.encoding.decoder.RawDecoder;
import com.glavsoft.rfb.encoding.decoder.RichCursorDecoder;
import com.glavsoft.rfb.encoding.decoder.TightDecoder;
import com.glavsoft.rfb.encoding.decoder.ZRLEDecoder;
import com.glavsoft.rfb.encoding.decoder.ZlibDecoder;
import com.glavsoft.rfb.protocol.handlers.Handshaker;
import com.glavsoft.rfb.protocol.tunnel.TunnelType;
import com.glavsoft.transport.BaudrateMeter;
import com.glavsoft.transport.Transport;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
public class Protocol implements IChangeSettingsListener {
private final ProtocolContext context;
private final Logger logger;
private final IRequestString passwordRetriever;
private MessageQueue messageQueue;
private SenderTask senderTask;
private ReceiverTask receiverTask;
private IRfbSessionListener rfbSessionListener;
private IRepaintController repaintController;
private Thread senderThread;
private Thread receiverThread;
private PixelFormat serverPixelFormat;
private final Map<EncodingType, Decoder> decoders = new LinkedHashMap<EncodingType, Decoder>();
private final Set<ClientMessageType> clientMessageTypes = new HashSet<ClientMessageType>();
private boolean inCleanUp = false;
private boolean isMac;
private BaudrateMeter baudrateMeter;
private IRequestString connectionIdRetriever;
public Protocol(Transport transport, IRequestString passwordRetriever, ProtocolSettings settings) {
context = new ProtocolContext();
context.transport = transport;
this.passwordRetriever = passwordRetriever;
logger = Logger.getLogger(getClass().getName());
context.settings = settings;
decoders.put(EncodingType.RAW_ENCODING, RawDecoder.getInstance());
}
public void handshake() throws UnsupportedProtocolVersionException, UnsupportedSecurityTypeException,
AuthenticationFailedException, TransportException, FatalException {
context.transport = new Handshaker(this).handshake(getTransport());
messageQueue = new MessageQueue(); // TODO Why here?
}
public IRequestString getPasswordRetriever() {
return passwordRetriever;
}
/**
* Following the server initialisation message it's up to the client to send
* whichever protocol messages it wants. Typically it will send a
* SetPixelFormat message and a SetEncodings message, followed by a
* FramebufferUpdateRequest. From then on the server will send
* FramebufferUpdate messages in response to the client's
* FramebufferUpdateRequest messages. The client should send
* FramebufferUpdateRequest messages with incremental set to true when it has
* finished processing one FramebufferUpdate and is ready to process another.
* With a fast client, the rate at which FramebufferUpdateRequests are sent
* should be regulated to avoid hogging the network.
*/
public void startNormalHandling(IRfbSessionListener rfbSessionListener,
IRepaintController repaintController, ClipboardController clipboardController) {
this.rfbSessionListener = rfbSessionListener;
this.repaintController = repaintController;
// if (settings.getColorDepth() == 0) {
// settings.setColorDepth(pixelFormat.depth); // the same the server sent when not initialized yet
// }
correctServerPixelFormat();
context.setPixelFormat(createPixelFormat(context.settings));
sendMessage(new SetPixelFormatMessage(context.pixelFormat));
logger.fine("sent: " + context.pixelFormat);
sendSupportedEncodingsMessage(context.settings);
context.settings.addListener(Protocol.this); // to support pixel format (color depth), and encodings changes
context.settings.addListener(repaintController);
sendRefreshMessage();
senderTask = new SenderTask(messageQueue, context.transport, Protocol.this);
senderThread = new Thread(senderTask, "RfbSenderTask");
senderThread.start();
resetDecoders();
receiverTask = new ReceiverTask(
context.transport, repaintController,
clipboardController,
Protocol.this, baudrateMeter);
receiverThread = new Thread(receiverTask, "RfbReceiverTask");
receiverThread.start();
}
private void correctServerPixelFormat() {
// correct true color flag
if (0 == serverPixelFormat.trueColourFlag) {
//we don't support color maps, so always set true color flag up
//and select closest convenient value for bpp/depth
int depth = serverPixelFormat.depth;
if (0 == depth) depth = serverPixelFormat.bitsPerPixel;
if (0 == depth) depth = 24;
if (depth <= 3) serverPixelFormat = PixelFormat.create3bitColorDepthPixelFormat(serverPixelFormat.bigEndianFlag);
else if (depth <= 6) serverPixelFormat = PixelFormat.create6bitColorDepthPixelFormat(serverPixelFormat.bigEndianFlag);
else if (depth <= 8) serverPixelFormat = PixelFormat.create8bitColorDepthBGRPixelFormat(serverPixelFormat.bigEndianFlag);
else if (depth <= 16) serverPixelFormat = PixelFormat.create16bitColorDepthPixelFormat(serverPixelFormat.bigEndianFlag);
else serverPixelFormat = PixelFormat.create24bitColorDepthPixelFormat(serverPixelFormat.bigEndianFlag);
}
// correct .depth to use actual depth 24 instead of incorrect 32, used by ex. UltraVNC server, that cause
// protocol incompatibility in ZRLE encoding
final long significant = serverPixelFormat.redMax << serverPixelFormat.redShift |
serverPixelFormat.greenMax << serverPixelFormat.greenShift |
serverPixelFormat.blueMax << serverPixelFormat.blueShift;
if (32 == serverPixelFormat.bitsPerPixel &&
((significant & 0x00ff000000L) == 0 || (significant & 0x000000ffL) == 0) &&
32 == serverPixelFormat.depth) {
serverPixelFormat.depth = 24;
}
}
public void sendMessage(ClientToServerMessage message) {
messageQueue.put(message);
}
public void sendSupportedEncodingsMessage(ProtocolSettings settings) {
final LinkedHashSet<EncodingType> encodings = new LinkedHashSet<EncodingType>();
final EncodingType preferredEncoding = settings.getPreferredEncoding();
if (preferredEncoding != EncodingType.RAW_ENCODING) {
encodings.add(preferredEncoding); // preferred first
}
for (final EncodingType e : decoders.keySet()) {
if (e == preferredEncoding) continue;
switch (e) {
case RAW_ENCODING: break;
case COMPRESS_LEVEL_0 :
final int compressionLevel = settings.getCompressionLevel();
if (compressionLevel > 0 && compressionLevel < 10) {
encodings.add(EncodingType.byId(EncodingType.COMPRESS_LEVEL_0.getId() + compressionLevel));
}
break;
case JPEG_QUALITY_LEVEL_0 :
final int jpegQuality = settings.getJpegQuality();
final int colorDepth = settings.getColorDepth();
if (jpegQuality > 0 && jpegQuality < 10 &&
(colorDepth == ProtocolSettings.COLOR_DEPTH_24 ||
colorDepth == ProtocolSettings.COLOR_DEPTH_SERVER_SETTINGS)) {
encodings.add(EncodingType.byId(EncodingType.JPEG_QUALITY_LEVEL_0.getId() + jpegQuality));
}
break;
case COPY_RECT:
if (settings.isAllowCopyRect()) {
encodings.add(EncodingType.COPY_RECT);
}
break;
case RICH_CURSOR:
if (settings.getMouseCursorTrack() == LocalPointer.HIDE ||
settings.getMouseCursorTrack() == LocalPointer.ON) {
encodings.add(EncodingType.RICH_CURSOR);
}
break;
case CURSOR_POS:
if (settings.getMouseCursorTrack() == LocalPointer.HIDE ||
settings.getMouseCursorTrack() == LocalPointer.ON) {
encodings.add(EncodingType.CURSOR_POS);
}
break;
default:
encodings.add(e);
}
}
SetEncodingsMessage encodingsMessage = new SetEncodingsMessage(encodings);
sendMessage(encodingsMessage);
logger.fine("sent: " + encodingsMessage.toString());
}
/**
* create pixel format by bpp
*/
private PixelFormat createPixelFormat(ProtocolSettings settings) {
int serverBigEndianFlag = serverPixelFormat.bigEndianFlag;
switch (settings.getColorDepth()) {
case ProtocolSettings.COLOR_DEPTH_24:
return PixelFormat.create24bitColorDepthPixelFormat(serverBigEndianFlag);
case ProtocolSettings.COLOR_DEPTH_16:
return PixelFormat.create16bitColorDepthPixelFormat(serverBigEndianFlag);
case ProtocolSettings.COLOR_DEPTH_8:
return hackForMacOsXScreenSharingServer(PixelFormat.create8bitColorDepthBGRPixelFormat(serverBigEndianFlag));
case ProtocolSettings.COLOR_DEPTH_6:
return hackForMacOsXScreenSharingServer(PixelFormat.create6bitColorDepthPixelFormat(serverBigEndianFlag));
case ProtocolSettings.COLOR_DEPTH_3:
return hackForMacOsXScreenSharingServer(PixelFormat.create3bitColorDepthPixelFormat(serverBigEndianFlag));
case ProtocolSettings.COLOR_DEPTH_SERVER_SETTINGS:
return serverPixelFormat;
default:
// unsupported bpp, use default
return PixelFormat.create24bitColorDepthPixelFormat(serverBigEndianFlag);
}
}
private PixelFormat hackForMacOsXScreenSharingServer(PixelFormat pixelFormat) {
if (isMac) {
pixelFormat.bitsPerPixel = pixelFormat.depth = 16;
}
return pixelFormat;
}
@Override
public void settingsChanged(SettingsChangedEvent e) {
ProtocolSettings settings = (ProtocolSettings) e.getSource();
if (settings.isChangedEncodings()) {
sendSupportedEncodingsMessage(settings);
}
if (settings.isChangedColorDepth() && receiverTask != null) {
receiverTask.queueUpdatePixelFormat(createPixelFormat(settings));
}
}
public void sendRefreshMessage() {
sendMessage(new FramebufferUpdateRequestMessage(0, 0, context.fbWidth, context.fbHeight, false));
logger.fine("sent: full FB Refresh");
}
public void sendFbUpdateMessage() {
sendMessage(receiverTask.fullscreenFbUpdateIncrementalRequest);
}
public void cleanUpSession(String message) {
cleanUpSession();
rfbSessionListener.rfbSessionStopped(message);
}
public void cleanUpSession() {
synchronized (this) {
if (inCleanUp) return;
inCleanUp = true;
}
if (senderTask != null && senderThread.isAlive()) { senderThread.interrupt(); }
if (receiverTask != null && receiverThread.isAlive()) { receiverThread.interrupt(); }
if (senderTask != null) {
try {
senderThread.join(1000);
} catch (InterruptedException e) {
// nop
}
senderTask = null;
}
if (receiverTask != null) {
try {
receiverThread.join(1000);
} catch (InterruptedException e) {
// nop
}
receiverTask = null;
}
synchronized (this) {
inCleanUp = false;
}
ByteBuffer.removeInstance();
}
public void setServerPixelFormat(PixelFormat serverPixelFormat) {
this.serverPixelFormat = serverPixelFormat;
}
public ProtocolSettings getSettings() {
return context.getSettings();
}
public Transport getTransport() {
return context.getTransport();
}
public int getFbWidth() {
return context.getFbWidth();
}
public void setFbWidth(int frameBufferWidth) {
context.setFbWidth(frameBufferWidth);
}
public int getFbHeight() {
return context.getFbHeight();
}
public void setFbHeight(int frameBufferHeight) {
context.setFbHeight(frameBufferHeight);
}
public PixelFormat getPixelFormat() {
return context.getPixelFormat();
}
public void setPixelFormat(PixelFormat pixelFormat) {
context.setPixelFormat(pixelFormat);
if (repaintController != null) {
repaintController.setPixelFormat(pixelFormat);
}
}
public void setRemoteDesktopName(String name) {
context.setRemoteDesktopName(name);
}
public String getRemoteDesktopName() {
return context.getRemoteDesktopName();
}
public void setTight(boolean isTight) {
context.setTight(isTight);
}
public boolean isTight() {
return context.isTight();
}
public void setProtocolVersion(Handshaker.ProtocolVersion protocolVersion) {
context.setProtocolVersion(protocolVersion);
}
public Handshaker.ProtocolVersion getProtocolVersion() {
return context.getProtocolVersion();
}
public void registerRfbEncodings() {
decoders.put(EncodingType.TIGHT, new TightDecoder());
decoders.put(EncodingType.HEXTILE, new HextileDecoder());
decoders.put(EncodingType.ZRLE, new ZRLEDecoder());
decoders.put(EncodingType.ZLIB, new ZlibDecoder());
decoders.put(EncodingType.RRE, new RREDecoder());
decoders.put(EncodingType.COPY_RECT, new CopyRectDecoder());
decoders.put(EncodingType.RICH_CURSOR, new RichCursorDecoder());
decoders.put(EncodingType.DESKTOP_SIZE, new DesctopSizeDecoder());
decoders.put(EncodingType.CURSOR_POS, new CursorPosDecoder());
}
public void resetDecoders() {
for (Decoder decoder : decoders.values()) {
if (decoder != null) {
decoder.reset();
}
}
}
public Decoder getDecoderByType(EncodingType type) {
return decoders.get(type);
}
public void registerEncoding(RfbCapabilityInfo capInfo) {
try {
final EncodingType encodingType = EncodingType.byId(capInfo.getCode());
if ( ! decoders.containsKey(encodingType)) {
final Decoder decoder = encodingType.klass.newInstance();
if (decoder != null) {
decoders.put(encodingType, decoder);
logger.finer("Register encoding: " + encodingType);
}
}
} catch (IllegalArgumentException e) {
logger.finer(e.getMessage());
} catch (InstantiationException e) {
logger.warning(e.getMessage());
} catch (IllegalAccessException e) {
logger.warning(e.getMessage());
}
}
public void registerClientMessageType(RfbCapabilityInfo capInfo) {
try {
final ClientMessageType clientMessageType = ClientMessageType.byId(capInfo.getCode());
clientMessageTypes.add(clientMessageType);
logger.finer("Register client message type: " + clientMessageType);
} catch (IllegalArgumentException e) {
logger.finer(e.getMessage());
}
}
/**
* Check whether server is supported for given client-to-server message
*
* @param type client-to-server message type to check for
* @return true when supported
*/
public boolean isSupported(ClientMessageType type) {
return clientMessageTypes.contains(type) || ClientMessageType.isStandardType(type );
}
public void setTunnelType(TunnelType tunnelType) {
context.setTunnelType(tunnelType);
}
public TunnelType getTunnelType() {
return context.getTunnelType();
}
public void setMac(boolean isMac) {
this.isMac = isMac;
}
public void setBaudrateMeter(BaudrateMeter baudrateMeter) {
this.baudrateMeter = baudrateMeter;
}
public int kBPS() {
return baudrateMeter == null ? -1 : baudrateMeter.kBPS();
}
public boolean isMac() {
return isMac;
}
public void setConnectionIdRetriever(IRequestString connectionIdRetriever) {
this.connectionIdRetriever = connectionIdRetriever;
}
public IRequestString getConnectionIdRetriever() {
return connectionIdRetriever;
}
}

View File

@@ -0,0 +1,105 @@
// Copyright (C) 2010 - 2014 GlavSoft LLC.
// All rights reserved.
//
// -----------------------------------------------------------------------
// This file is part of the TightVNC software. Please visit our Web site:
//
// http://www.tightvnc.com/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// -----------------------------------------------------------------------
//
package com.glavsoft.rfb.protocol;
import com.glavsoft.rfb.encoding.PixelFormat;
import com.glavsoft.rfb.protocol.handlers.Handshaker;
import com.glavsoft.rfb.protocol.tunnel.TunnelType;
import com.glavsoft.transport.Transport;
public class ProtocolContext {
int fbWidth;
int fbHeight;
PixelFormat pixelFormat;
Transport transport;
String remoteDesktopName;
boolean isTight;
Handshaker.ProtocolVersion protocolVersion;
ProtocolSettings settings;
private TunnelType tunnelType;
public PixelFormat getPixelFormat() {
return pixelFormat;
}
public void setPixelFormat(PixelFormat pixelFormat) {
this.pixelFormat = pixelFormat;
}
public String getRemoteDesktopName() {
return remoteDesktopName;
}
public void setRemoteDesktopName(String name) {
remoteDesktopName = name;
}
public int getFbWidth() {
return fbWidth;
}
public void setFbWidth(int fbWidth) {
this.fbWidth = fbWidth;
}
public int getFbHeight() {
return fbHeight;
}
public void setFbHeight(int fbHeight) {
this.fbHeight = fbHeight;
}
public ProtocolSettings getSettings() {
return settings;
}
public Transport getTransport() {
return transport;
}
public void setTight(boolean isTight) {
this.isTight = isTight;
}
public boolean isTight() {
return isTight;
}
public void setProtocolVersion(Handshaker.ProtocolVersion protocolVersion) {
this.protocolVersion = protocolVersion;
}
public Handshaker.ProtocolVersion getProtocolVersion() {
return protocolVersion;
}
public void setTunnelType(TunnelType tunnelType) {
this.tunnelType = tunnelType;
}
public TunnelType getTunnelType() {
return tunnelType;
}
}

View File

@@ -0,0 +1,343 @@
// Copyright (C) 2010 - 2014 GlavSoft LLC.
// All rights reserved.
//
// -----------------------------------------------------------------------
// This file is part of the TightVNC software. Please visit our Web site:
//
// http://www.tightvnc.com/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// -----------------------------------------------------------------------
//
package com.glavsoft.rfb.protocol;
import com.glavsoft.core.SettingsChangedEvent;
import com.glavsoft.rfb.IChangeSettingsListener;
import com.glavsoft.rfb.encoding.EncodingType;
import com.glavsoft.rfb.protocol.tunnel.TunnelType;
import java.io.Serializable;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* Protocol Settings class
*/
public class ProtocolSettings implements Serializable {
private static final long serialVersionUID = 1L;
private static final EncodingType DEFAULT_PREFERRED_ENCODING = EncodingType.TIGHT;
public static final int DEFAULT_JPEG_QUALITY = 6;
private static final int DEFAULT_COMPRESSION_LEVEL = -6;
// color depth constants
public static final int COLOR_DEPTH_32 = 32;
public static final int COLOR_DEPTH_24 = 24;
public static final int COLOR_DEPTH_16 = 16;
public static final int COLOR_DEPTH_8 = 8;
public static final int COLOR_DEPTH_6 = 6;
public static final int COLOR_DEPTH_3 = 3;
public static final int COLOR_DEPTH_SERVER_SETTINGS = 0;
private static final int DEFAULT_COLOR_DEPTH = COLOR_DEPTH_24;
public static final int CHANGED_VIEW_ONLY = 1; // 1 << 0;
public static final int CHANGED_ENCODINGS = 1 << 1;
public static final int CHANGED_ALLOW_COPY_RECT = 1 << 2;
public static final int CHANGED_SHOW_REMOTE_CURSOR = 1 << 3;
public static final int CHANGED_MOUSE_CURSOR_TRACK = 1 << 4;
public static final int CHANGED_COMPRESSION_LEVEL = 1 << 5;
public static final int CHANGED_JPEG_QUALITY = 1 << 6;
public static final int CHANGED_ALLOW_CLIPBOARD_TRANSFER = 1 << 7;
public static final int CHANGED_CONVERT_TO_ASCII = 1 << 8;
public static final int CHANGED_COLOR_DEPTH = 1 << 9;
public static final int CHANGED_SHARED = 1 << 10;
private static final int MIN_COMPRESSION_LEVEL = 1;
private static final int MAX_COMPRESSION_LEVEL = 9;
private static final int MIN_JPEG_QUALITY = 1;
private static final int MAX_JPEG_QUALITY = 9;
private transient int changedSettingsMask;
private boolean sharedFlag;
private boolean viewOnly;
private EncodingType preferredEncoding;
private boolean allowCopyRect;
private boolean showRemoteCursor;
private LocalPointer mouseCursorTrack;
private int compressionLevel;
private int jpegQuality;
private boolean allowClipboardTransfer;
private boolean convertToAscii;
private int colorDepth;
private transient final List<IChangeSettingsListener> listeners;
private transient String remoteCharsetName;
private TunnelType tunnelType;
public static ProtocolSettings getDefaultSettings() {
return new ProtocolSettings();
}
private ProtocolSettings() {
sharedFlag = true;
viewOnly = false;
showRemoteCursor = true;
mouseCursorTrack = LocalPointer.ON;
preferredEncoding = DEFAULT_PREFERRED_ENCODING;
allowCopyRect = true;
compressionLevel = DEFAULT_COMPRESSION_LEVEL;
jpegQuality = DEFAULT_JPEG_QUALITY;
convertToAscii = false;
allowClipboardTransfer = true;
colorDepth = COLOR_DEPTH_SERVER_SETTINGS;
listeners = new CopyOnWriteArrayList<IChangeSettingsListener>();
changedSettingsMask = 0;
}
public ProtocolSettings(ProtocolSettings s) {
this();
copyDataFrom(s);
changedSettingsMask = s.changedSettingsMask;
}
public void copyDataFrom(ProtocolSettings s) {
copyDataFrom(s, 0);
}
public void copyDataFrom(ProtocolSettings s, int mask) {
if (null == s) return;
if ((mask & CHANGED_SHARED) == 0) setSharedFlag(s.sharedFlag);
if ((mask & CHANGED_VIEW_ONLY) == 0) setViewOnly(s.viewOnly);
if ((mask & CHANGED_ALLOW_COPY_RECT) == 0) setAllowCopyRect(s.allowCopyRect);
if ((mask & CHANGED_SHOW_REMOTE_CURSOR) == 0) setShowRemoteCursor(s.showRemoteCursor);
if ((mask & CHANGED_ALLOW_CLIPBOARD_TRANSFER) == 0) setAllowClipboardTransfer(s.allowClipboardTransfer);
if ((mask & CHANGED_MOUSE_CURSOR_TRACK) == 0) setMouseCursorTrack(s.mouseCursorTrack);
if ((mask & CHANGED_COMPRESSION_LEVEL) == 0) setCompressionLevel(s.compressionLevel);
if ((mask & CHANGED_JPEG_QUALITY) == 0) setJpegQuality(s.jpegQuality);
if ((mask & CHANGED_CONVERT_TO_ASCII) == 0) setConvertToAscii(s.convertToAscii);
if ((mask & CHANGED_COLOR_DEPTH) == 0) setColorDepth(s.colorDepth);
if ((mask & CHANGED_ENCODINGS) == 0) setPreferredEncoding(s.preferredEncoding);
}
public void addListener(IChangeSettingsListener listener) {
listeners.add(listener);
}
public byte getSharedFlag() {
return (byte) (sharedFlag ? 1 : 0);
}
public boolean isShared() {
return sharedFlag;
}
public void setSharedFlag(boolean sharedFlag) {
if (this.sharedFlag != sharedFlag) {
this.sharedFlag = sharedFlag;
changedSettingsMask |= CHANGED_SHARED;
}
}
public boolean isViewOnly() {
return viewOnly;
}
public void setViewOnly(boolean viewOnly) {
if (this.viewOnly != viewOnly) {
this.viewOnly = viewOnly;
changedSettingsMask |= CHANGED_VIEW_ONLY;
}
}
public int getColorDepth() {
return colorDepth;
}
/**
* Set depth only in 3, 6, 8, 16, 32. When depth is wrong, it resets to {@link #DEFAULT_COLOR_DEPTH}
*/
public void setColorDepth(int depth) {
if (colorDepth != depth) {
changedSettingsMask |= CHANGED_COLOR_DEPTH | CHANGED_ENCODINGS;
switch (depth) {
case COLOR_DEPTH_32:
colorDepth = COLOR_DEPTH_24;
break;
case COLOR_DEPTH_24:
case COLOR_DEPTH_16:
case COLOR_DEPTH_8:
case COLOR_DEPTH_6:
case COLOR_DEPTH_3:
case COLOR_DEPTH_SERVER_SETTINGS:
colorDepth = depth;
break;
default:
colorDepth = DEFAULT_COLOR_DEPTH;
}
}
}
public void fireListeners() {
if (null == listeners) return;
final SettingsChangedEvent event = new SettingsChangedEvent(new ProtocolSettings(this));
changedSettingsMask = 0;
for (IChangeSettingsListener listener : listeners) {
listener.settingsChanged(event);
}
}
public static boolean isRfbSettingsChangedFired(SettingsChangedEvent event) {
return event.getSource() instanceof ProtocolSettings;
}
public void setPreferredEncoding(EncodingType preferredEncoding) {
if (this.preferredEncoding != preferredEncoding) {
this.preferredEncoding = preferredEncoding;
changedSettingsMask |= CHANGED_ENCODINGS;
}
}
public EncodingType getPreferredEncoding() {
return preferredEncoding;
}
public void setAllowCopyRect(boolean allowCopyRect) {
if (this.allowCopyRect != allowCopyRect) {
this.allowCopyRect = allowCopyRect;
changedSettingsMask |= CHANGED_ALLOW_COPY_RECT | CHANGED_ENCODINGS;
}
}
public boolean isAllowCopyRect() {
return allowCopyRect;
}
private void setShowRemoteCursor(boolean showRemoteCursor) {
if (this.showRemoteCursor != showRemoteCursor) {
this.showRemoteCursor = showRemoteCursor;
changedSettingsMask |= CHANGED_SHOW_REMOTE_CURSOR | CHANGED_ENCODINGS;
}
}
public boolean isShowRemoteCursor() {
return showRemoteCursor;
}
public void setMouseCursorTrack(LocalPointer mouseCursorTrack) {
if (this.mouseCursorTrack != mouseCursorTrack) {
this.mouseCursorTrack = mouseCursorTrack;
changedSettingsMask |= CHANGED_MOUSE_CURSOR_TRACK | CHANGED_ENCODINGS;
setShowRemoteCursor(LocalPointer.ON == mouseCursorTrack);
}
}
public LocalPointer getMouseCursorTrack() {
return mouseCursorTrack;
}
public int setCompressionLevel(int compressionLevel) {
if (compressionLevel >= MIN_COMPRESSION_LEVEL && compressionLevel <= MAX_COMPRESSION_LEVEL &&
this.compressionLevel != compressionLevel) {
this.compressionLevel = compressionLevel;
changedSettingsMask |= CHANGED_COMPRESSION_LEVEL | CHANGED_ENCODINGS;
}
return this.compressionLevel;
}
public int getCompressionLevel() {
return compressionLevel;
}
public int setJpegQuality(int jpegQuality) {
if (jpegQuality >= MIN_JPEG_QUALITY && jpegQuality <= MAX_JPEG_QUALITY &&
this.jpegQuality != jpegQuality) {
this.jpegQuality = jpegQuality;
changedSettingsMask |= CHANGED_JPEG_QUALITY | CHANGED_ENCODINGS;
}
return this.jpegQuality;
}
public int getJpegQuality() {
return jpegQuality;
}
public void setAllowClipboardTransfer(boolean enable) {
if (this.allowClipboardTransfer != enable) {
this.allowClipboardTransfer = enable;
changedSettingsMask |= CHANGED_ALLOW_CLIPBOARD_TRANSFER;
}
}
public boolean isAllowClipboardTransfer() {
return allowClipboardTransfer;
}
public boolean isConvertToAscii() {
return convertToAscii;
}
public void setConvertToAscii(boolean convertToAscii) {
if (this.convertToAscii != convertToAscii) {
this.convertToAscii = convertToAscii;
changedSettingsMask |= CHANGED_CONVERT_TO_ASCII;
}
}
public boolean isChangedEncodings() {
return (changedSettingsMask & CHANGED_ENCODINGS) == CHANGED_ENCODINGS;
}
public boolean isChangedColorDepth() {
return (changedSettingsMask & CHANGED_COLOR_DEPTH) == CHANGED_COLOR_DEPTH;
}
public void setRemoteCharsetName(String remoteCharsetName) {
this.remoteCharsetName = remoteCharsetName;
}
public String getRemoteCharsetName() {
return remoteCharsetName;
}
@Override
public String toString() {
return "ProtocolSettings{" +
"sharedFlag=" + sharedFlag +
", viewOnly=" + viewOnly +
", preferredEncoding=" + preferredEncoding +
", allowCopyRect=" + allowCopyRect +
", showRemoteCursor=" + showRemoteCursor +
", mouseCursorTrack=" + mouseCursorTrack +
", compressionLevel=" + compressionLevel +
", jpegQuality=" + jpegQuality +
", allowClipboardTransfer=" + allowClipboardTransfer +
", convertToAscii=" + convertToAscii +
", colorDepth=" + colorDepth +
'}';
}
public TunnelType getTunnelType() {
return tunnelType;
}
public void setTunnelType(TunnelType tunnelType) {
this.tunnelType = tunnelType;
}
}

View File

@@ -0,0 +1,204 @@
// Copyright (C) 2010 - 2014 GlavSoft LLC.
// All rights reserved.
//
// -----------------------------------------------------------------------
// This file is part of the TightVNC software. Please visit our Web site:
//
// http://www.tightvnc.com/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// -----------------------------------------------------------------------
//
package com.glavsoft.rfb.protocol;
import com.glavsoft.drawing.Renderer;
import com.glavsoft.exceptions.CommonException;
import com.glavsoft.exceptions.ProtocolException;
import com.glavsoft.exceptions.TransportException;
import com.glavsoft.rfb.ClipboardController;
import com.glavsoft.rfb.IRepaintController;
import com.glavsoft.rfb.client.FramebufferUpdateRequestMessage;
import com.glavsoft.rfb.client.SetPixelFormatMessage;
import com.glavsoft.rfb.encoding.EncodingType;
import com.glavsoft.rfb.encoding.PixelFormat;
import com.glavsoft.rfb.encoding.decoder.Decoder;
import com.glavsoft.rfb.encoding.decoder.FramebufferUpdateRectangle;
import com.glavsoft.transport.BaudrateMeter;
import com.glavsoft.transport.Transport;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.logging.Logger;
public class ReceiverTask implements Runnable {
private static final byte FRAMEBUFFER_UPDATE = 0;
private static final byte SET_COLOR_MAP_ENTRIES = 1;
private static final byte BELL = 2;
private static final byte SERVER_CUT_TEXT = 3;
private static Logger logger = Logger.getLogger("com.glavsoft.rfb.protocol.ReceiverTask");
private final Transport transport;
private Renderer renderer;
private final IRepaintController repaintController;
private final ClipboardController clipboardController;
protected FramebufferUpdateRequestMessage fullscreenFbUpdateIncrementalRequest;
private final Protocol protocol;
private BaudrateMeter baudrateMeter;
private PixelFormat pixelFormat;
private volatile boolean needSendPixelFormat;
public ReceiverTask(Transport transport,
IRepaintController repaintController, ClipboardController clipboardController,
Protocol protocol, BaudrateMeter baudrateMeter) {
this.transport = transport;
this.repaintController = repaintController;
this.clipboardController = clipboardController;
this.protocol = protocol;
this.baudrateMeter = baudrateMeter;
renderer = repaintController.createRenderer(transport, protocol.getFbWidth(), protocol.getFbHeight(),
protocol.getPixelFormat());
fullscreenFbUpdateIncrementalRequest =
new FramebufferUpdateRequestMessage(0, 0, protocol.getFbWidth(), protocol.getFbHeight(), true);
}
@Override
public void run() {
try {
while ( ! Thread.currentThread().isInterrupted()) {
byte messageId = transport.readByte();
switch (messageId) {
case FRAMEBUFFER_UPDATE:
// logger.fine("Server message: FramebufferUpdate (0)");
framebufferUpdateMessage();
break;
case SET_COLOR_MAP_ENTRIES:
logger.severe("Server message SetColorMapEntries is not implemented. Skip.");
setColorMapEntries();
break;
case BELL:
logger.fine("Server message: Bell");
System.out.print("\0007");
System.out.flush();
break;
case SERVER_CUT_TEXT:
logger.fine("Server message: CutText (3)");
serverCutText();
break;
default:
logger.severe("Unsupported server message. Id = " + messageId);
}
}
} catch (TransportException e) {
logger.severe("Close session: " + e.getMessage());
protocol.cleanUpSession("Connection closed.");
} catch (ProtocolException e) {
logger.severe(e.getMessage());
protocol.cleanUpSession(e.getMessage() + "\nConnection closed.");
} catch (CommonException e) {
logger.severe(e.getMessage());
protocol.cleanUpSession("Connection closed..");
} catch (Throwable te) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
te.printStackTrace(pw);
protocol.cleanUpSession(te.getMessage() + "\n" + sw.toString());
}
Logger.getLogger(getClass().getName()).finer("Receiver task stopped");
}
private void setColorMapEntries() throws TransportException {
transport.readByte(); // padding
transport.readUInt16(); // first color index
int length = transport.readUInt16();
while (length-- > 0) {
transport.readUInt16(); // R
transport.readUInt16(); // G
transport.readUInt16(); // B
}
}
private void serverCutText() throws TransportException, IOException {
transport.readByte(); // padding
transport.readInt16(); // padding
long length = transport.readInt32();
if (0 == length) return;
if (length > Integer.MAX_VALUE) {
clipboardController.updateSystemClipboard(transport.readBytes(Integer.MAX_VALUE));
clipboardController.updateSystemClipboard(transport.readBytes((int) (length - Integer.MAX_VALUE)));
} else {
clipboardController.updateSystemClipboard(transport.readBytes((int) length));
}
}
public void framebufferUpdateMessage() throws CommonException {
transport.skip(1); // padding
int numberOfRectangles = transport.readUInt16();
while (numberOfRectangles-- > 0) {
FramebufferUpdateRectangle rect = new FramebufferUpdateRectangle();
rect.fill(transport);
Decoder decoder = protocol.getDecoderByType(rect.getEncodingType());
// logger.finer(rect.toString() + (0 == numberOfRectangles ? "\n---" : ""));
if (decoder != null) {
try {
if (baudrateMeter != null) baudrateMeter.startMeasuringCycle();
decoder.decode(transport, renderer, rect);
} finally {
if (baudrateMeter != null) baudrateMeter.stopMeasuringCycle();
}
if (EncodingType.RICH_CURSOR == rect.getEncodingType() ||
EncodingType.CURSOR_POS == rect.getEncodingType()) {
repaintController.repaintCursor();
} else if (rect.getEncodingType() == EncodingType.DESKTOP_SIZE) {
synchronized (this) {
fullscreenFbUpdateIncrementalRequest =
new FramebufferUpdateRequestMessage(0, 0, rect.width, rect.height, true);
}
renderer = repaintController.createRenderer(transport, rect.width, rect.height,
protocol.getPixelFormat());
protocol.sendMessage(new FramebufferUpdateRequestMessage(0, 0, rect.width, rect.height, false));
return;
} else {
repaintController.repaintBitmap(rect);
}
} else {
throw new CommonException("Unprocessed encoding: " + rect.toString());
}
}
if (needSendPixelFormat) {
synchronized (this) {
if (needSendPixelFormat) {
needSendPixelFormat = false;
protocol.setPixelFormat(pixelFormat);
protocol.sendMessage(new SetPixelFormatMessage(pixelFormat));
logger.fine("sent: " + pixelFormat);
protocol.sendRefreshMessage();
logger.fine("sent: nonincremental fb update");
}
}
} else {
protocol.sendMessage(fullscreenFbUpdateIncrementalRequest);
}
}
public synchronized void queueUpdatePixelFormat(PixelFormat pf) {
pixelFormat = pf;
needSendPixelFormat = true;
// protocol.sendMessage(new FramebufferUpdateRequestMessage(0, 0, 1, 1, false));
}
}

View File

@@ -0,0 +1,79 @@
// Copyright (C) 2010 - 2014 GlavSoft LLC.
// All rights reserved.
//
// -----------------------------------------------------------------------
// This file is part of the TightVNC software. Please visit our Web site:
//
// http://www.tightvnc.com/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// -----------------------------------------------------------------------
//
package com.glavsoft.rfb.protocol;
import com.glavsoft.exceptions.TransportException;
import com.glavsoft.rfb.client.ClientToServerMessage;
import com.glavsoft.transport.Transport;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.logging.Logger;
public class SenderTask implements Runnable {
private final MessageQueue queue;
private final Transport transport;
private final Protocol protocol;
/**
* Create sender task
* Task runs as thread, receive messages from queue and sends them to transport.
* When no messages appears in queue longer than timeout period, sends FramebufferUpdate
* request
* @param messageQueue queue to poll messages
* @param transport transport to send messages out
* @param protocol session lifecircle support
*/
public SenderTask(MessageQueue messageQueue, Transport transport, Protocol protocol) {
this.queue = messageQueue;
this.transport = transport;
this.protocol = protocol;
}
@Override
public void run() {
ClientToServerMessage message;
try {
while ( ! Thread.currentThread().isInterrupted()) {
message = queue.get();
if (message != null) {
message.send(transport);
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (TransportException e) {
Logger.getLogger(getClass().getName()).severe("Close session: " + e.getMessage());
protocol.cleanUpSession("Connection closed");
} catch (Throwable te) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
te.printStackTrace(pw);
protocol.cleanUpSession(te.getMessage() + "\n" + sw.toString());
}
Logger.getLogger(getClass().getName()).finer("Sender task stopped");
}
}

View File

@@ -0,0 +1,117 @@
// Copyright (C) 2010 - 2014 GlavSoft LLC.
// All rights reserved.
//
// -----------------------------------------------------------------------
// This file is part of the TightVNC software. Please visit our Web site:
//
// http://www.tightvnc.com/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// -----------------------------------------------------------------------
//
package com.glavsoft.rfb.protocol.auth;
import com.glavsoft.exceptions.AuthenticationFailedException;
import com.glavsoft.exceptions.ClosedConnectionException;
import com.glavsoft.exceptions.FatalException;
import com.glavsoft.exceptions.TransportException;
import com.glavsoft.exceptions.UnsupportedSecurityTypeException;
import com.glavsoft.rfb.encoding.ServerInitMessage;
import com.glavsoft.rfb.protocol.Protocol;
import com.glavsoft.transport.Transport;
import java.util.logging.Logger;
public abstract class AuthHandler {
private static final int AUTH_RESULT_OK = 0;
// private static final int AUTH_RESULT_FAILED = 1;
private Logger logger;
/**
* Not thread safe, no need to be thread safe
*/
protected Logger logger() {
if (null == logger) {
logger = Logger.getLogger(getClass().getName());
}
return logger;
}
/**
* Authenticate using appropriate auth scheme
*
* @param transport transport for i/o
* @param protocol rfb protocol object
* @return transport for future i/o using
*/
public abstract Transport authenticate(Transport transport, Protocol protocol)
throws TransportException, FatalException, UnsupportedSecurityTypeException;
public abstract SecurityType getType();
public int getId() {
return getType().getId();
}
public String getName() {
return getType().name();
}
/**
* Check Security Result received from server
* May be:
* * 0 - OK
* * 1 - Failed
*
* Do not check on NoneAuthentication
*/
public void checkSecurityResult(Transport transport) throws TransportException,
AuthenticationFailedException {
final int securityResult = transport.readInt32();
logger().fine("Security result: " + securityResult + (AUTH_RESULT_OK == securityResult ? " (OK)" : " (Failed)"));
if (securityResult != AUTH_RESULT_OK) {
try {
String reason = transport.readString();
logger().fine("Security result reason: " + reason);
throw new AuthenticationFailedException(reason);
} catch (ClosedConnectionException e) {
// protocol version 3.3 and 3.7 does not send reason string,
// but silently closes the connection
throw new AuthenticationFailedException("Authentication failed");
}
}
}
public void initProcedure(Transport transport, Protocol protocol) throws TransportException {
sendClientInitMessage(transport, protocol.getSettings().getSharedFlag());
ServerInitMessage serverInitMessage = readServerInitMessage(transport);
completeContextData(serverInitMessage, protocol);
protocol.registerRfbEncodings();
}
protected ServerInitMessage readServerInitMessage(Transport transport) throws TransportException {
final ServerInitMessage serverInitMessage = new ServerInitMessage().readFrom(transport);
logger().fine("Read: " + serverInitMessage);
return serverInitMessage;
}
protected void sendClientInitMessage(Transport transport, byte sharedFlag) throws TransportException {
logger().fine("Sent client-init-message: " + sharedFlag);
transport.writeByte(sharedFlag).flush();
}
protected void completeContextData(ServerInitMessage serverInitMessage, Protocol protocol) {
protocol.setServerPixelFormat(serverInitMessage.getPixelFormat());
protocol.setFbWidth(serverInitMessage.getFramebufferWidth());
protocol.setFbHeight(serverInitMessage.getFramebufferHeight());
protocol.setRemoteDesktopName(serverInitMessage.getName());
}
}

View File

@@ -0,0 +1,42 @@
// Copyright (C) 2010 - 2014 GlavSoft LLC.
// All rights reserved.
//
// -----------------------------------------------------------------------
// This file is part of the TightVNC software. Please visit our Web site:
//
// http://www.tightvnc.com/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// -----------------------------------------------------------------------
//
package com.glavsoft.rfb.protocol.auth;
import com.glavsoft.exceptions.TransportException;
import com.glavsoft.rfb.protocol.Protocol;
import com.glavsoft.transport.Transport;
public class NoneAuthentication extends AuthHandler {
@Override
public Transport authenticate(Transport transport, Protocol protocol) throws TransportException {
return transport;
}
@Override
public SecurityType getType() {
return SecurityType.NONE_AUTHENTICATION;
}
}

View File

@@ -0,0 +1,49 @@
// Copyright (C) 2010 - 2014 GlavSoft LLC.
// All rights reserved.
//
// -----------------------------------------------------------------------
// This file is part of the TightVNC software. Please visit our Web site:
//
// http://www.tightvnc.com/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// -----------------------------------------------------------------------
//
package com.glavsoft.rfb.protocol.auth;
/**
* Security types that implemented
*/
public enum SecurityType {
NONE_AUTHENTICATION(1),
VNC_AUTHENTICATION(2),
// int RA2_AUTHENTICATION = 5;
// int RA2NE_AUTHENTICATION = 6;
TIGHT_AUTHENTICATION(16),
TIGHT2_AUTHENTICATION(116);
// int ULTRA_AUTHENTICATION = 17;
// int TLS_AUTHENTICATION = 18;
// int VENCRYPT_AUTHENTICATION = 19;
private int id;
private SecurityType(int id) {
this.id = id;
}
public int getId() {
return id;
}
}

View File

@@ -0,0 +1,268 @@
// Copyright (C) 2010 - 2014 GlavSoft LLC.
// All rights reserved.
//
// -----------------------------------------------------------------------
// This file is part of the TightVNC software. Please visit our Web site:
//
// http://www.tightvnc.com/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// -----------------------------------------------------------------------
//
package com.glavsoft.rfb.protocol.auth;
import com.glavsoft.exceptions.FatalException;
import com.glavsoft.exceptions.TransportException;
import com.glavsoft.exceptions.UnsupportedSecurityTypeException;
import com.glavsoft.rfb.RfbCapabilityInfo;
import com.glavsoft.rfb.encoding.ServerInitMessage;
import com.glavsoft.rfb.protocol.Protocol;
import com.glavsoft.rfb.protocol.tunnel.TunnelHandler;
import com.glavsoft.rfb.protocol.tunnel.TunnelType;
import com.glavsoft.transport.Transport;
import com.glavsoft.utils.Strings;
import java.util.HashMap;
import java.util.Map;
/**
*
*/
public class TightAuthentication extends AuthHandler {
private final Map<Integer, AuthHandler> registeredAuthHandlers = new HashMap<Integer, AuthHandler>();
private final Map<Integer, TunnelHandler> registeredTunnelHandlers = new HashMap<Integer, TunnelHandler>();
public TightAuthentication() {
}
public void registerTunnelingHandler(TunnelHandler handler) {
registeredTunnelHandlers.put(handler.getId(), handler);
}
public void registerAuthHandler(AuthHandler handler) {
registeredAuthHandlers.put(handler.getId(), handler);
}
@Override
public SecurityType getType() {
return SecurityType.TIGHT_AUTHENTICATION;
}
@Override
public Transport authenticate(Transport transport, Protocol protocol)
throws TransportException, FatalException, UnsupportedSecurityTypeException {
transport = tunnelingNegotiation(transport, protocol);
authorizationNegotiation(transport, protocol);
protocol.setTight(true);
return transport;
}
@Override
public void initProcedure(Transport transport, Protocol protocol) throws TransportException {
capabilitiesNegotiation(transport, protocol);
protocol.registerRfbEncodings();
}
/**
* Capabilities negotiation consists of server-to-client message, where server introduces its capabilities,
* and client-to-server message, which introduces only those client capabilities which are supported by server
* and encodings supported by server.
*
* This data immediately follows the server initialisation message.
*
* typedef struct _rfbInteractionCapsMsg {
* CARD16 nServerMessageTypes;
* CARD16 nClientMessageTypes;
* CARD16 nEncodingTypes;
* CARD16 pad;><------><------>// reserved, must be 0
* // followed by nServerMessageTypes * rfbCapabilityInfo structures
* // followed by nClientMessageTypes * rfbCapabilityInfo structures
* } rfbInteractionCapsMsg;
* #define sz_rfbInteractionCapsMsg 8
*
* nServerMessageTypes | UINT16 | Number of server message types server announces.
* nClientMessageTypes | UINT16 | Number of client message types server announces.
* nEncodingTypes | UINT16 | Number of encoding types server announces.
* ServerMessageTypes | RFBCAPABILITY x nServerMessageTypes | Server side messages which server supports.
* ClientMessageTypes | RFBCAPABILITY x nClientMessageTypes | Client side messages which server supports.
* Encodings | RFBCAPABILITY x nEncodingTypes | Encoding types which server supports.
*
* Client replies with message in exactly the same format, listing only those capabilities which are supported
* both by server and the client.
* Once all three initialization stages are successfully finished, client and server switch to normal protocol flow.
*
*/
void capabilitiesNegotiation(Transport transport, Protocol protocol) throws TransportException {
sendClientInitMessage(transport, protocol.getSettings().getSharedFlag());
ServerInitMessage serverInitMessage = readServerInitMessage(transport);
int nServerMessageTypes = transport.readUInt16();
int nClientMessageTypes = transport.readUInt16();
int nEncodingTypes = transport.readUInt16();
transport.readUInt16(); //padding
logger().fine("nServerMessageTypes: " + nServerMessageTypes + ", nClientMessageTypes: " + nClientMessageTypes +
", nEncodingTypes: " + nEncodingTypes);
registerServerMessagesTypes(transport, protocol, nServerMessageTypes);
registerClientMessagesTypes(transport, protocol, nClientMessageTypes);
registerEncodings(transport, protocol, nEncodingTypes);
completeContextData(serverInitMessage, protocol);
}
private void registerServerMessagesTypes(Transport transport, Protocol protocol, int count) throws TransportException {
while (count-- > 0) {
RfbCapabilityInfo capInfoReceived = new RfbCapabilityInfo().readFrom(transport);
logger().fine("Server message type: " + capInfoReceived.toString());
}
}
private void registerClientMessagesTypes(Transport transport, Protocol protocol, int count) throws TransportException {
while (count-- > 0) {
RfbCapabilityInfo capInfoReceived = new RfbCapabilityInfo().readFrom(transport);
logger().fine("Client message type: " + capInfoReceived.toString());
protocol.registerClientMessageType(capInfoReceived);
}
}
private void registerEncodings(Transport transport, Protocol protocol, int count) throws TransportException {
while (count-- > 0) {
RfbCapabilityInfo capInfoReceived = new RfbCapabilityInfo().readFrom(transport);
logger().fine("Encoding: " + capInfoReceived.toString());
protocol.registerEncoding(capInfoReceived);
}
}
/**
* Negotiation of Tunneling Capabilities (protocol versions 3.7t, 3.8t)
*
* If the chosen security type is rfbSecTypeTight, the server sends a list of
* supported tunneling methods ("tunneling" refers to any additional layer of
* data transformation, such as encryption or external compression.)
*
* nTunnelTypes specifies the number of following rfbCapabilityInfo structures
* that list all supported tunneling methods in the order of preference.
*
* NOTE: If nTunnelTypes is 0, that tells the client that no tunneling can be
* used, and the client should not send a response requesting a tunneling
* method.
*
* typedef struct _rfbTunnelingCapsMsg {
* CARD32 nTunnelTypes;
* //followed by nTunnelTypes * rfbCapabilityInfo structures
* } rfbTunnelingCapsMsg;
* #define sz_rfbTunnelingCapsMsg 4
* ----------------------------------------------------------------------------
* Tunneling Method Request (protocol versions 3.7t, 3.8t)
*
* If the list of tunneling capabilities sent by the server was not empty, the
* client should reply with a 32-bit code specifying a particular tunneling
* method. The following code should be used for no tunneling.
*
* #define rfbNoTunneling 0
* #define sig_rfbNoTunneling "NOTUNNEL"
*/
Transport tunnelingNegotiation(Transport transport, Protocol protocol)
throws TransportException {
Transport newTransport = transport;
int tunnelsCount;
tunnelsCount = (int) transport.readUInt32();
logger().fine("Tunneling capabilities: " + tunnelsCount);
int [] tunnelCodes = new int[tunnelsCount];
if (tunnelsCount > 0) {
for (int i = 0; i < tunnelsCount; ++i) {
RfbCapabilityInfo rfbCapabilityInfo = new RfbCapabilityInfo().readFrom(transport);
tunnelCodes[i] = rfbCapabilityInfo.getCode();
logger().fine(rfbCapabilityInfo.toString());
}
int selectedTunnelCode;
if (tunnelsCount > 0) {
for (int i = 0; i < tunnelsCount; ++i) {
final TunnelHandler tunnelHandler = registeredTunnelHandlers.get(tunnelCodes[i]);
if (tunnelHandler != null) {
selectedTunnelCode = tunnelCodes[i];
transport.writeInt32(selectedTunnelCode).flush();
logger().fine("Accepted tunneling type: " + selectedTunnelCode);
newTransport = tunnelHandler.createTunnel(transport);
logger().fine("Tunnel created: " + TunnelType.byCode(selectedTunnelCode));
protocol.setTunnelType(TunnelType.byCode(selectedTunnelCode));
break;
}
}
}
}
if (protocol.getTunnelType() == null) {
protocol.setTunnelType(TunnelType.NOTUNNEL);
if (tunnelsCount > 0) {
transport.writeInt32(TunnelType.NOTUNNEL.code).flush();
}
logger().fine("Accepted tunneling type: " + TunnelType.NOTUNNEL);
}
return newTransport;
}
/**
* Negotiation of Authentication Capabilities (protocol versions 3.7t, 3.8t)
*
* After setting up tunneling, the server sends a list of supported
* authentication schemes.
*
* nAuthTypes specifies the number of following rfbCapabilityInfo structures
* that list all supported authentication schemes in the order of preference.
*
* NOTE: If nAuthTypes is 0, that tells the client that no authentication is
* necessary, and the client should not send a response requesting an
* authentication scheme.
*
* typedef struct _rfbAuthenticationCapsMsg {
* CARD32 nAuthTypes;
* // followed by nAuthTypes * rfbCapabilityInfo structures
* } rfbAuthenticationCapsMsg;
* #define sz_rfbAuthenticationCapsMsg 4
*
*/
void authorizationNegotiation(Transport transport, Protocol protocol)
throws UnsupportedSecurityTypeException, TransportException, FatalException {
int authCount;
authCount = transport.readInt32();
logger().fine("Auth capabilities: " + authCount);
byte[] cap = new byte[authCount];
for (int i = 0; i < authCount; ++i) {
RfbCapabilityInfo rfbCapabilityInfo = new RfbCapabilityInfo().readFrom(transport);
cap[i] = (byte) rfbCapabilityInfo.getCode();
logger().fine(rfbCapabilityInfo.toString());
}
AuthHandler authHandler = null;
if (authCount > 0) {
for (int i = 0; i < authCount; ++i) {
authHandler = registeredAuthHandlers.get((int) cap[i]);
if (authHandler != null) {
//sending back RFB capability code
transport.writeInt32(authHandler.getId()).flush();
break;
}
}
} else {
authHandler = registeredAuthHandlers.get(SecurityType.NONE_AUTHENTICATION.getId());
}
if (null == authHandler) {
throw new UnsupportedSecurityTypeException("Server auth types: " + Strings.toString(cap) +
", supported auth types: " + registeredAuthHandlers.values());
}
logger().fine("Auth capability accepted: " + authHandler.getName());
authHandler.authenticate(transport, protocol);
}
}

View File

@@ -0,0 +1,103 @@
// Copyright (C) 2010 - 2014 GlavSoft LLC.
// All rights reserved.
//
// -----------------------------------------------------------------------
// This file is part of the TightVNC software. Please visit our Web site:
//
// http://www.tightvnc.com/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// -----------------------------------------------------------------------
//
package com.glavsoft.rfb.protocol.auth;
import com.glavsoft.exceptions.CryptoException;
import com.glavsoft.exceptions.FatalException;
import com.glavsoft.exceptions.TransportException;
import com.glavsoft.rfb.protocol.Protocol;
import com.glavsoft.transport.Transport;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import static com.glavsoft.utils.Strings.getBytesWithCharset;
public class VncAuthentication extends AuthHandler {
@Override
public SecurityType getType() {
return SecurityType.VNC_AUTHENTICATION;
}
@Override
public Transport authenticate(Transport transport, Protocol protocol)
throws TransportException, FatalException {
byte [] challenge = transport.readBytes(16);
String password = protocol.getPasswordRetriever().getResult();
if (null == password) password = "";
byte [] key = new byte[8];
System.arraycopy(getBytesWithCharset(password, Transport.ISO_8859_1), 0, key, 0, Math.min(key.length, getBytesWithCharset(password, Transport.ISO_8859_1).length));
transport.write(encrypt(challenge, key)).flush();
return transport;
}
/**
* Encrypt challenge by key using DES
* @return encrypted bytes
* @throws CryptoException on problem with DES algorithm support or smth about
*/
public byte[] encrypt(byte[] challenge, byte[] key) throws CryptoException {
try {
DESKeySpec desKeySpec = new DESKeySpec(mirrorBits(key));
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
SecretKey secretKey = keyFactory.generateSecret(desKeySpec);
Cipher desCipher = Cipher.getInstance("DES/ECB/NoPadding");
desCipher.init(Cipher.ENCRYPT_MODE, secretKey);
return desCipher.doFinal(challenge);
} catch (NoSuchAlgorithmException e) {
throw new CryptoException("Cannot encrypt challenge", e);
} catch (NoSuchPaddingException e) {
throw new CryptoException("Cannot encrypt challenge", e);
} catch (IllegalBlockSizeException e) {
throw new CryptoException("Cannot encrypt challenge", e);
} catch (BadPaddingException e) {
throw new CryptoException("Cannot encrypt challenge", e);
} catch (InvalidKeyException e) {
throw new CryptoException("Cannot encrypt challenge", e);
} catch (InvalidKeySpecException e) {
throw new CryptoException("Cannot encrypt challenge", e);
}
}
private byte[] mirrorBits(byte[] k) {
byte[] key = new byte[8];
for (int i = 0; i < 8; i++) {
byte s = k[i];
s = (byte) (((s >> 1) & 0x55) | ((s << 1) & 0xaa));
s = (byte) (((s >> 2) & 0x33) | ((s << 2) & 0xcc));
s = (byte) (((s >> 4) & 0x0f) | ((s << 4) & 0xf0));
key[i] = s;
}
return key;
}
}

View File

@@ -0,0 +1,345 @@
// Copyright (C) 2010 - 2014 GlavSoft LLC.
// All rights reserved.
//
// -----------------------------------------------------------------------
// This file is part of the TightVNC software. Please visit our Web site:
//
// http://www.tightvnc.com/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// -----------------------------------------------------------------------
//
package com.glavsoft.rfb.protocol.handlers;
import com.glavsoft.exceptions.AuthenticationFailedException;
import com.glavsoft.exceptions.FatalException;
import com.glavsoft.exceptions.TransportException;
import com.glavsoft.exceptions.UnsupportedProtocolVersionException;
import com.glavsoft.exceptions.UnsupportedSecurityTypeException;
import com.glavsoft.rfb.IRequestString;
import com.glavsoft.rfb.protocol.Protocol;
import com.glavsoft.rfb.protocol.auth.AuthHandler;
import com.glavsoft.rfb.protocol.auth.NoneAuthentication;
import com.glavsoft.rfb.protocol.auth.SecurityType;
import com.glavsoft.rfb.protocol.auth.TightAuthentication;
import com.glavsoft.rfb.protocol.auth.VncAuthentication;
import com.glavsoft.rfb.protocol.tunnel.SslTunnel;
import com.glavsoft.rfb.protocol.tunnel.TunnelType;
import com.glavsoft.transport.Transport;
import com.glavsoft.utils.Strings;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author dime at glavsoft.com
*/
public class Handshaker {
private static final int PROTOCOL_STRING_LENGTH = 12;
private static final String RFB_PROTOCOL_STRING_REGEXP = "^RFB (\\d\\d\\d).(\\d\\d\\d)\n$";
private static final String DISPATCHER_PROTOCOL_STRING = "TCPDISPATCH\n";
private static final int MIN_SUPPORTED_VERSION_MAJOR = 3;
private static final int MIN_SUPPORTED_VERSION_MINOR = 3;
private static final int MAX_SUPPORTED_VERSION_MAJOR = 3;
private static final int MAX_SUPPORTED_VERSION_MINOR = 8;
protected static final int DISPATCHER_PROTOCOL_VERSION = 3;
protected static final int KEEP_ALIVE_BYTE = 0;
protected static final int START_BYTE = 1;
private Protocol protocol;
private Logger logger;
private final Map<Integer, AuthHandler> registeredAuthHandlers = new HashMap<Integer, AuthHandler>();
public Handshaker(Protocol protocol) {
this.protocol = protocol;
logger = Logger.getLogger(getClass().getName());
registerAuthHandler(SecurityType.NONE_AUTHENTICATION.getId(), new NoneAuthentication());
registerAuthHandler(SecurityType.VNC_AUTHENTICATION.getId(), new VncAuthentication());
final TightAuthentication tightAuthentication = new TightAuthentication();
tightAuthentication.registerAuthHandler(new NoneAuthentication());
tightAuthentication.registerAuthHandler(new VncAuthentication());
if (protocol.getSettings().getTunnelType() != TunnelType.NOTUNNEL &&
SslTunnel.isTransportAvailable()) {
tightAuthentication.registerTunnelingHandler(new SslTunnel());
registerAuthHandler(SecurityType.TIGHT2_AUTHENTICATION.getId(), tightAuthentication);
}
registerAuthHandler(SecurityType.TIGHT_AUTHENTICATION.getId(), tightAuthentication);
}
public Transport handshake(Transport transport) throws TransportException, UnsupportedProtocolVersionException, AuthenticationFailedException, FatalException, UnsupportedSecurityTypeException {
String protocolString = transport.readString(PROTOCOL_STRING_LENGTH);
if (isDispatcherConnection(protocolString)) {
handshakeToDispatcher(transport);
protocolString = transport.readString(PROTOCOL_STRING_LENGTH);
}
ProtocolVersion ver = matchProtocolVersion(protocolString);
transport.write(Strings.getBytesWithCharset("RFB 00" + ver.major + ".00" + ver.minor + "\n", Transport.ISO_8859_1)).flush();
protocol.setProtocolVersion(ver);
logger.info("Set protocol version to: " + ver);
transport = auth(transport, ver);
return transport;
}
/**
* Make dispatcher connection
*
* Dispatcher protocol v.3: '<-' means receive from dispatcher, '->' means send to dispatcher
* <- "TCPDISPATCH\n" &mdash; already received at this point
* <- UInt8 numSupportedVersions value
* <- numSupportedVersions UInt8 values of supported version num
* -> UInt8 value of version accepted
* -> UInt8 remoteHostRole value (0 == RFB Server or 1 == RFB Client/viewer)
* -> UInt32 connId
* <- UInt32 connId (when 0 == connId, then dispatcher generates unique random connId value
* and sends it to clients, else it doesn't send the one)
* -> UInt8 secret keyword string length
* -> String (byte array of ASCII characters) - secret keyword
* -> UInt8 dispatcher name string length (may equals to 0)
* -> String (byte array of ASCII characters) - dispatcher name
* <- UInt8 dispatcher name string length
* <- String (byte array of ASCII characters) - dispatcher name
* <- 0 'keep alive byte' or non zero 'start byte' (1)
* On keep alive byte immediately answer with the same byte, and wain for next byte,
* on start byte go to ordinary rfb negotiation.
*
* @param transport
*
* @throws TransportException when some io error happens
* @throws UnsupportedProtocolVersionException when protocol doesn't match
* @throws AuthenticationFailedException when connectionId provided by user is wrong
*/
private void handshakeToDispatcher(Transport transport) throws TransportException, UnsupportedProtocolVersionException, AuthenticationFailedException {
int numSupportedVersions = transport.readUInt8(); // receive num of supported version followed (u8)
List<Integer> remoteVersions = new ArrayList<Integer>(numSupportedVersions);
for (int i = 0; i < numSupportedVersions; ++i) {
remoteVersions.add(transport.readUInt8()); // receive supported protocol versions (numSupportedVersions x u8)
}
logger.fine("Dispatcher protocol versions: " + Arrays.toString(remoteVersions.toArray()));
if (!remoteVersions.contains(DISPATCHER_PROTOCOL_VERSION)) {
throw new UnsupportedProtocolVersionException("Dispatcher unsupported protocol versions");
}
transport.writeByte(DISPATCHER_PROTOCOL_VERSION); // send protocol version we use (u8)
transport.writeByte(1).flush(); // send we are the viewer (u8)
long connectionId = 0;
IRequestString connIdRetriever = protocol.getConnectionIdRetriever();
if (null == connIdRetriever) throw new IllegalStateException("ConnectionIdRetriever is null");
String sId = connIdRetriever.getResult();
if (Strings.isTrimmedEmpty(sId)) throw new AuthenticationFailedException("ConnectionId is empty");
try {
connectionId = Long.parseLong(sId);
} catch (NumberFormatException nfe) {
throw new AuthenticationFailedException("Wrong ConnectionId");
}
if ( 0 == connectionId) {
throw new AuthenticationFailedException("ConnectionId have not be equals to zero");
}
transport.writeUInt32(connectionId).flush(); // send connectionId (u32)
transport.writeByte(0); // send UInt8 secret keyword string length. 0 - for none
// send String (byte array of ASCII characters) - secret keyword.
// Skip if none
transport.writeByte(0).flush(); // send UInt8 dispatcher name string length (may equals to 0)
// send -> String (byte array of ASCII characters) - dispatcher name.
// Skip if none
//logger.fine("Sent: version3, viewer, connectionId: " + connectionId + " secret:0, token: 0");
int tokenLength = transport.readUInt8(); // receive UInt8 token length
// receive byte array - dispatcher token
byte [] token = transport.readBytes(tokenLength);
//logger.fine("token: #" + tokenLength + " " + (tokenLength>0?token[0]:"") +(tokenLength>1?token[1]:"")+(tokenLength>2?token[2]:""));
// receive 0 'keep alive byte' or non zero 'start byte' (1)
// on keep alive byte send the same to remote
// on start byte go to starting rfb connection
int b;
do {
b = transport.readByte();
if (KEEP_ALIVE_BYTE == b) {
logger.finer("keep-alive");
transport.writeByte(KEEP_ALIVE_BYTE).flush();
}
} while (b != START_BYTE);
logger.info("Dispatcher handshake completed");
}
/**
* When first 12 bytes sent by server is "TCPDISPATCH\n" this is dispatcher connection
*
* @param protocolString string with first 12 bytes sent by server
* @return true when we connects to dispatcher, not remote rfb server
*/
private boolean isDispatcherConnection(String protocolString) {
final boolean dispatcherDetected = DISPATCHER_PROTOCOL_STRING.equals(protocolString);
if (dispatcherDetected) {
logger.info("Dispatcher connection detected");
}
return dispatcherDetected;
}
/**
* Take first 12 bytes sent by server and match rfb protocol version.
* RFB protocol version string is "RFB MMM.mmm\n". Where MMM is major
* protocol version and mmm is minor one.
*
* Side effect: set protocol.isMac when MacOs at other side is detected
*
* @param protocolString string with first 12 bytes sent by server
* @return version of protocol will be used
*/
private ProtocolVersion matchProtocolVersion(String protocolString) throws UnsupportedProtocolVersionException {
logger.info("Server protocol string: " + protocolString.substring(0, protocolString.length() - 1));
Pattern pattern = Pattern.compile(RFB_PROTOCOL_STRING_REGEXP);
final Matcher matcher = pattern.matcher(protocolString);
if ( ! matcher.matches())
throw new UnsupportedProtocolVersionException(
"Unsupported protocol version: " + protocolString);
int major = Integer.parseInt(matcher.group(1));
int minor = Integer.parseInt(matcher.group(2));
ProtocolVersion ver;
boolean isMac = false;
if (889 == minor) {
isMac = true;
}
if (major < MIN_SUPPORTED_VERSION_MAJOR ||
MIN_SUPPORTED_VERSION_MAJOR == major && minor < MIN_SUPPORTED_VERSION_MINOR)
throw new UnsupportedProtocolVersionException(
"Unsupported protocol version: " + major + "." + minor);
if (major > MAX_SUPPORTED_VERSION_MAJOR) {
// major = MAX_SUPPORTED_VERSION_MAJOR;
minor = MAX_SUPPORTED_VERSION_MINOR;
}
if (minor >= MIN_SUPPORTED_VERSION_MINOR && minor < 7) {
ver = ProtocolVersion.PROTOCOL_VERSION_3_3;
} else if (7 == minor) {
ver = ProtocolVersion.PROTOCOL_VERSION_3_7;
} else if (minor >= MAX_SUPPORTED_VERSION_MINOR) {
ver = ProtocolVersion.PROTOCOL_VERSION_3_8;
} else
throw new UnsupportedProtocolVersionException("Unsupported protocol version: " + protocolString);
protocol.setMac(isMac);
return ver;
}
private Transport auth(Transport transport, ProtocolVersion ver) throws UnsupportedSecurityTypeException, TransportException, FatalException, AuthenticationFailedException {
AuthHandler handler;
switch (ver) {
case PROTOCOL_VERSION_3_3:
handler = auth33(transport);
break;
case PROTOCOL_VERSION_3_7:
handler = auth37_38(transport);
break;
case PROTOCOL_VERSION_3_8:
handler = auth37_38(transport);
break;
default:
throw new IllegalStateException();
}
transport = handler.authenticate(transport, protocol);
if (ver == ProtocolVersion.PROTOCOL_VERSION_3_8 ||
handler.getType() != SecurityType.NONE_AUTHENTICATION) {
handler.checkSecurityResult(transport);
}
handler.initProcedure(transport, protocol);
return transport;
}
private AuthHandler auth33(Transport transport) throws TransportException, UnsupportedSecurityTypeException {
int type = transport.readInt32();
logger.info("Type received: " + type);
if (0 == type)
throw new UnsupportedSecurityTypeException(transport.readString());
AuthHandler handler = registeredAuthHandlers.get(selectAuthHandlerId((byte) (0xff & type)));
return handler;
}
private AuthHandler auth37_38(Transport transport) throws TransportException, UnsupportedSecurityTypeException {
int secTypesNum = transport.readUInt8();
if (0 == secTypesNum)
throw new UnsupportedSecurityTypeException(transport.readString());
byte[] secTypes = transport.readBytes(secTypesNum);
logger.info("Security Types received (" + secTypesNum + "): " + Strings.toString(secTypes));
final int typeIdAccepted = selectAuthHandlerId(secTypes);
final AuthHandler authHandler = registeredAuthHandlers.get(typeIdAccepted);
transport.writeByte(typeIdAccepted).flush();
return authHandler;
}
private int selectAuthHandlerId(byte... secTypes)
throws UnsupportedSecurityTypeException, TransportException {
AuthHandler handler;
// Tight2 Authentication very first
for (byte type : secTypes) {
if (SecurityType.TIGHT2_AUTHENTICATION.getId() == (0xff & type)) {
handler = registeredAuthHandlers.get(SecurityType.TIGHT2_AUTHENTICATION.getId());
if (handler != null) {
logger.info("Security Type accepted: " + SecurityType.TIGHT2_AUTHENTICATION.name());
return SecurityType.TIGHT2_AUTHENTICATION.getId();
}
}
}
// Tight Authentication first
for (byte type : secTypes) {
if (SecurityType.TIGHT_AUTHENTICATION.getId() == (0xff & type)) {
handler = registeredAuthHandlers.get(SecurityType.TIGHT_AUTHENTICATION.getId());
if (handler != null) {
logger.info("Security Type accepted: " + SecurityType.TIGHT_AUTHENTICATION.name());
return SecurityType.TIGHT_AUTHENTICATION.getId();
}
}
}
for (byte type : secTypes) {
handler = registeredAuthHandlers.get(0xff & type);
if (handler != null) {
logger.info("Security Type accepted: " + handler.getType());
return handler.getType().getId();
}
}
throw new UnsupportedSecurityTypeException(
"No security types supported. Server sent '"
+ Strings.toString(secTypes)
+ "' security types, but we do not support any of their.");
}
private void registerAuthHandler(int id, AuthHandler handler) {
registeredAuthHandlers.put(id, handler);
}
public static enum ProtocolVersion {
PROTOCOL_VERSION_3_3(3, 3),
PROTOCOL_VERSION_3_7(3, 7),
PROTOCOL_VERSION_3_8(3, 8);
public final int minor;
public final int major;
ProtocolVersion(int major, int minor) {
this.major = major;
this.minor = minor;
}
@Override
public String toString() {
return String.valueOf(major) + "." + String.valueOf(minor);
}
}
}

View File

@@ -0,0 +1,121 @@
// Copyright (C) 2010 - 2014 GlavSoft LLC.
// All rights reserved.
//
// -----------------------------------------------------------------------
// This file is part of the TightVNC software. Please visit our Web site:
//
// http://www.tightvnc.com/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// -----------------------------------------------------------------------
//
package com.glavsoft.rfb.protocol.tunnel;
import com.glavsoft.exceptions.TransportException;
import com.glavsoft.transport.Transport;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
/**
* @author dime at glavsoft.com
*/
public class SslTunnel implements TunnelHandler {
/**
* Requested protocol (algorithm) name.
* May be one of the follows:
* SSL Supports some version of SSL; may support other versions
* SSLv2 Supports SSL version 2 or later; may support other versions
* SSLv3 Supports SSL version 3; may support other versions
* TLS Supports some version of TLS; may support other versions
* TLSv1 Supports RFC 2246: TLS version 1.0 ; may support other versions
* TLSv1.1 Supports RFC 4346: TLS version 1.1 ; may support other versions
* TLSv1.2 Supports RFC 5246: TLS version 1.2 ; may support other versions
*
* Using TLSv1.1 because earlier versions have vulnerabilities.
*/
private static final String PROTOCOL = "TLSv1.1";
private static final String SSL_TRANSPORT = "com.glavsoft.transport.SslTransport";
public SslTunnel() {
}
@Override
public int getId() {
return TunnelType.SSL.code;
}
@Override
public Transport createTunnel(Transport transport) throws TransportException {
try {
SSLContext sslContext = SSLContext.getInstance(PROTOCOL);
sslContext.init(null, getTrustAllCertsManager(), null);
SSLEngine engine = sslContext.createSSLEngine();
engine.setUseClientMode(true);
@SuppressWarnings("unchecked")
final Class<Transport> sslTransportClass = (Class<Transport>) Class.forName(SSL_TRANSPORT);
final Constructor<Transport> constructor = sslTransportClass.getConstructor(Transport.class, SSLEngine.class);
return constructor.newInstance(transport, engine);
} catch (NoSuchAlgorithmException e) {
throw new TransportException("Cannot create SSL/TLS tunnel", e);
} catch (KeyManagementException e) {
throw new TransportException("Cannot create SSL/TLS tunnel", e);
} catch (ClassNotFoundException e) {
throw new TransportException("Cannot create SSL/TLS tunnel, SSL transport plugin unavailable", e);
} catch (NoSuchMethodException e) {
throw new TransportException("Cannot create SSL/TLS tunnel, SSL transport plugin unavailable", e);
} catch (InvocationTargetException e) {
throw new TransportException("Cannot create SSL/TLS tunnel, SSL transport plugin unavailable", e);
} catch (InstantiationException e) {
throw new TransportException("Cannot create SSL/TLS tunnel, SSL transport plugin unavailable", e);
} catch (IllegalAccessException e) {
throw new TransportException("Cannot create SSL/TLS tunnel, SSL transport plugin unavailable", e);
}
}
private TrustManager[] getTrustAllCertsManager() {
return new TrustManager[]{
new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(
java.security.cert.X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(
java.security.cert.X509Certificate[] certs, String authType) {
}
}
};
}
public static boolean isTransportAvailable() {
try {
Class.forName("com.glavsoft.transport.SslTransport");
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
}

View File

@@ -0,0 +1,36 @@
// Copyright (C) 2010 - 2014 GlavSoft LLC.
// All rights reserved.
//
// -----------------------------------------------------------------------
// This file is part of the TightVNC software. Please visit our Web site:
//
// http://www.tightvnc.com/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// -----------------------------------------------------------------------
//
package com.glavsoft.rfb.protocol.tunnel;
import com.glavsoft.exceptions.TransportException;
import com.glavsoft.transport.Transport;
/**
* @author dime at glavsoft.com
*/
public interface TunnelHandler {
public int getId();
Transport createTunnel(Transport transport) throws TransportException;
}

View File

@@ -0,0 +1,55 @@
// Copyright (C) 2010 - 2014 GlavSoft LLC.
// All rights reserved.
//
// -----------------------------------------------------------------------
// This file is part of the TightVNC software. Please visit our Web site:
//
// http://www.tightvnc.com/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// -----------------------------------------------------------------------
//
package com.glavsoft.rfb.protocol.tunnel;
import com.glavsoft.rfb.RfbCapabilityInfo;
/**
* @author dime at tightvnc.com
*/
public enum TunnelType {
NOTUNNEL(0, RfbCapabilityInfo.VENDOR_STANDARD, "NOTUNNEL", ""),
SSL(2, RfbCapabilityInfo.VENDOR_TIGHT, "SSL_____", "SSL/TLS");
public final int code;
public final String vendor;
public final String name;
public final String hrName;
TunnelType(int code, String vendor, String name, String humanReadableName) {
this.code = code;
this.vendor = vendor;
this.name = name;
hrName = humanReadableName;
}
public static TunnelType byCode(int code) {
for (TunnelType type : values()) {
if (type.code == code) return type;
}
return null;
}
}

View File

@@ -0,0 +1,65 @@
// Copyright (C) 2010 - 2014 GlavSoft LLC.
// All rights reserved.
//
// -----------------------------------------------------------------------
// This file is part of the TightVNC software. Please visit our Web site:
//
// http://www.tightvnc.com/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// -----------------------------------------------------------------------
//
package com.glavsoft.transport;
/**
* @author dime at tightvnc.com
*/
public class BaudrateMeter {
public static final int MIN_BPS = 10000;
private static final int n = 5;
private static final double ALPHA = 2. / (n + 1);
private double ema = 0;
private boolean measure = false;
private long start;
private long bytes;
public void count(int bytes) {
if (measure) this.bytes += bytes;
}
public int kBPS() {
return (int) (ema / 1000);
}
public void startMeasuringCycle() {
measure = true;
start = System.currentTimeMillis();
}
public void stopMeasuringCycle() {
measure = false;
long ms = System.currentTimeMillis() - start;
if (0 == ms || bytes < 100) return; // skip with too small portion of data
double bps = bytes * 8. / (ms / 1000.);
// double bpms = bytes * 8. / ms;
if (bps < MIN_BPS) { // limit lower value
bps = MIN_BPS;
}
// exponential moving-average smoothing
ema = ALPHA * bps + (1. - ALPHA) * (0. == ema ? bps : ema);
bytes = 0;
}
}

View File

@@ -0,0 +1,349 @@
// Copyright (C) 2010 - 2014 GlavSoft LLC.
// All rights reserved.
//
// -----------------------------------------------------------------------
// This file is part of the TightVNC software. Please visit our Web site:
//
// http://www.tightvnc.com/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// -----------------------------------------------------------------------
//
package com.glavsoft.transport;
import com.glavsoft.exceptions.ClosedConnectionException;
import com.glavsoft.exceptions.TransportException;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
/**
* @author dime
*/
public class Transport {
public final static Charset ISO_8859_1 = StandardCharsets.ISO_8859_1;
public final static Charset UTF8 = StandardCharsets.UTF_8;
DataInputStream is;
DataOutputStream os;
InputStream origIs;
OutputStream origOs;
private BaudrateMeter baudrateMeter;
public Transport(Socket socket) throws IOException {
this(new BufferedInputStream(socket.getInputStream()), new BufferedOutputStream(socket.getOutputStream()));
}
public Transport(InputStream is) {
this(is, null);
}
public Transport(OutputStream os) {
this(null, os);
}
public Transport(InputStream is, OutputStream os) {
init(is, os);
}
void init(InputStream is, OutputStream os) {
origIs = is;
this.is = is != null ? new DataInputStream(is) : null;
origOs = os;
this.os = os != null ? new DataOutputStream(os) : null;
}
public Transport() {
this(null, null);
}
void release() {
origIs = is = null;
origOs = os = null;
}
public byte readByte() throws TransportException {
try {
if (baudrateMeter != null) baudrateMeter.count(1);
return is.readByte();
} catch (EOFException e) {
throw new ClosedConnectionException(e);
} catch (IOException e) {
throw new TransportException("Cannot read byte", e);
}
}
public void setBaudrateMeter(BaudrateMeter baudrateMeter) {
this.baudrateMeter = baudrateMeter;
}
public int readUInt8() throws TransportException {
return readByte() & 0x0ff;
}
public int readUInt16() throws TransportException {
return readInt16() & 0x0ffff;
}
public short readInt16() throws TransportException {
try {
if (baudrateMeter != null) baudrateMeter.count(2);
return is.readShort();
} catch (EOFException e) {
throw new ClosedConnectionException(e);
} catch (IOException e) {
throw new TransportException("Cannot read int16", e);
}
}
public long readUInt32() throws TransportException {
return readInt32() & 0xffffffffL;
}
public int readInt32() throws TransportException {
try {
if (baudrateMeter != null) baudrateMeter.count(4);
return is.readInt();
} catch (EOFException e) {
throw new ClosedConnectionException(e);
} catch (IOException e) {
throw new TransportException("Cannot read int32", e);
}
}
public long readInt64() throws TransportException {
try {
if (baudrateMeter != null) baudrateMeter.count(8);
return is.readLong();
} catch (EOFException e) {
throw new ClosedConnectionException(e);
} catch (IOException e) {
throw new TransportException("Cannot read int32", e);
}
}
/**
* Read string by it length.
* Use this method only when sure no character accept ASCII will be read.
* Use readBytes and character encoding conversion instead.
*
* @return String read
*/
public String readString(int length) throws TransportException {
// return new String(readBytes(length), ISO_8859_1);
return stringWithBytesAndCharset(readBytes(length));
}
/**
* Read 32-bit string length and then string themself by it length
* Use this method only when sure no character accept ASCII will be read.
* Use readBytes and character encoding conversion instead or {@link #readUtf8String} method
* when utf-8 encoding needed.
*
* @return String read
* @throws TransportException
*/
public String readString() throws TransportException {
// unset most significant (sign) bit 'cause InputStream#readFully reads
// [int] length bytes from stream. Change when really need read string more
// than 2147483647 bytes length
int length = readInt32() & Integer.MAX_VALUE;
return readString(length);
}
/**
* Read 32-bit string length and then string themself by it length
* Assume UTF-8 character encoding used
*
* @return String read
* @throws TransportException
*/
public String readUtf8String() throws TransportException {
// unset most significant (sign) bit 'cause InputStream#readFully reads
// [int] length bytes from stream. Change when really need read string more
// than 2147483647 bytes length
int length = readInt32() & Integer.MAX_VALUE;
return new String(readBytes(length), UTF8);
}
/**
* Read @code{length} byte array
* Create byte array with length of @code{length}, read @code{length} bytes and return the array
*
* @param length
* @return byte array which contains the data read
* @throws TransportException
*/
public byte[] readBytes(int length) throws TransportException {
try {
return is.readNBytes(length);
} catch (IOException e) {
throw new TransportException(e.getMessage(), e);
}
}
public byte[] readBytes(byte[] b, int offset, int length) throws TransportException {
try {
is.readFully(b, offset, length);
if (baudrateMeter != null) {
baudrateMeter.count(length);
}
return b;
} catch (EOFException e) {
throw new ClosedConnectionException(e);
} catch (IOException e) {
throw new TransportException("Cannot read " + length + " bytes array", e);
}
}
public void skip(int length) throws TransportException {
try {
int rest = length;
do {
rest -= is.skipBytes(rest);
} while (rest > 0);
if (baudrateMeter != null) {
baudrateMeter.count(length);
}
} catch (EOFException e) {
throw new ClosedConnectionException(e);
} catch (IOException e) {
throw new TransportException("Cannot skip " + length + " bytes", e);
}
}
private void checkForOutputInit() throws TransportException {
if (null == os) throw new TransportException("Uninitialized writer");
}
public Transport flush() throws TransportException {
checkForOutputInit();
try {
os.flush();
} catch (IOException e) {
throw new TransportException("Cannot flush output stream", e);
}
return this;
}
public Transport writeByte(int b) throws TransportException {
return write((byte) (b & 0xff));
}
public Transport write(byte b) throws TransportException {
checkForOutputInit();
try {
os.writeByte(b);
} catch (IOException e) {
throw new TransportException("Cannot write byte", e);
}
return this;
}
public Transport writeInt16(int sh) throws TransportException {
return write((short) (sh & 0xffff));
}
public Transport write(short sh) throws TransportException {
checkForOutputInit();
try {
os.writeShort(sh);
} catch (IOException e) {
throw new TransportException("Cannot write short", e);
}
return this;
}
public Transport writeInt32(int i) throws TransportException {
return write(i);
}
public Transport writeUInt32(long i) throws TransportException {
return write((int) i & 0xffffffff);
}
public Transport writeInt64(long i) throws TransportException {
checkForOutputInit();
try {
os.writeLong(i);
} catch (IOException e) {
throw new TransportException("Cannot write long", e);
}
return this;
}
public Transport write(int i) throws TransportException {
checkForOutputInit();
try {
os.writeInt(i);
} catch (IOException e) {
throw new TransportException("Cannot write int", e);
}
return this;
}
public Transport write(byte[] b) throws TransportException {
return write(b, 0, b.length);
}
public Transport write(byte[] b, int length) throws TransportException {
return write(b, 0, length);
}
public Transport write(byte[] b, int offset, int length) throws TransportException {
checkForOutputInit();
try {
os.write(b, offset, length <= b.length ? length : b.length);
} catch (IOException e) {
throw new TransportException("Cannot write " + length + " bytes", e);
}
return this;
}
public void setOutputStreamTo(OutputStream os) {
this.os = new DataOutputStream(os);
}
public Transport zero(int count) throws TransportException {
while (count-- > 0) {
writeByte(0);
}
return this;
}
private String stringWithBytesAndCharset(byte[] bytes) {
String result;
try {
result = new String(bytes, ISO_8859_1);
} catch (NoSuchMethodError error) {
try {
result = new String(bytes, ISO_8859_1.name());
} catch (UnsupportedEncodingException e) {
result = null;
}
}
return result;
}
}

View File

@@ -0,0 +1,904 @@
// Copyright (C) 2010 - 2014 GlavSoft LLC.
// All rights reserved.
//
// -----------------------------------------------------------------------
// This file is part of the TightVNC software. Please visit our Web site:
//
// http://www.tightvnc.com/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// -----------------------------------------------------------------------
//
package com.glavsoft.utils;
import java.util.HashMap;
import java.util.Map;
/**
* @author dime at tightvnc.com
*/
public class Keymap {
public static final int K_F1 = 0xffbe;
public static final int K_F2 = 0xffbf;
public static final int K_F3 = 0xffc0;
public static final int K_F4 = 0xffc1;
public static final int K_F5 = 0xffc2;
public static final int K_F6 = 0xffc3;
public static final int K_F7 = 0xffc4;
public static final int K_F8 = 0xffc5;
public static final int K_F9 = 0xffc6;
public static final int K_F10 = 0xffc7;
public static final int K_F11 = 0xffc8;
public static final int K_F12 = 0xffc9;
public static final int K_INSERT = 0xff63;
public static final int K_DELETE = 0xffff;
public static final int K_HOME = 0xff50;
public static final int K_END = 0xff57;
public static final int K_PAGE_DOWN = 0xff56;
public static final int K_PAGE_UP = 0xff55;
public static final int K_DOWN = 0xff54;
public static final int K_RIGHT = 0xff53;
public static final int K_UP = 0xff52;
public static final int K_LEFT = 0xff51;
public static final int K_ESCAPE = 0xff1b;
public static final int K_ENTER = 0xff0d;
public static final int K_TAB = 0xff09;
public static final int K_BACK_SPACE = 0xff08;
public static final int K_ALT_LEFT = 0xffe9;
public static final int K_META_LEFT = 0xffe7;
public static final int K_SHIFT_LEFT = 0xffe1;
public static final int K_CTRL_LEFT = 0xffe3;
public static final int K_SUPER_LEFT = 0xffeb;
public static final int K_HYPER_LEFT = 0xffed;
public static final int K_KP_SPACE = 0xFF80;
public static final int K_KP_TAB = 0xFF89;
public static final int K_KP_ENTER = 0xFF8D;
public static final int K_KP_F1 = 0xFF91;
public static final int K_KP_F2 = 0xFF92;
public static final int K_KP_F3 = 0xFF93;
public static final int K_KP_F4 = 0xFF94;
public static final int K_KP_HOME = 0xFF95;
public static final int K_KP_LEFT = 0xFF96;
public static final int K_KP_UP = 0xFF97;
public static final int K_KP_RIGHT = 0xFF98;
public static final int K_KP_DOWN = 0xFF99;
public static final int K_KP_PRIOR = 0xFF9A;
public static final int K_KP_PAGE_UP = 0xFF9A;
public static final int K_KP_NEXT = 0xFF9B;
public static final int K_KP_PAGE_DOWN = 0xFF9B;
public static final int K_KP_END = 0xFF9C;
public static final int K_KP_BEGIN = 0xFF9D;
public static final int K_KP_INSERT = 0xFF9E;
public static final int K_KP_DELETE = 0xFF9F;
public static final int K_KP_EQUAL = 0xFFBD;
public static final int K_KP_MULTIPLY = 0xFFAA;
public static final int K_KP_ADD = 0xFFAB;
public static final int K_KP_SEPARATOR = 0xFFAC; /* separator, often comma */
public static final int K_KP_SUBTRACT = 0xFFAD;
public static final int K_KP_DECIMAL = 0xFFAE;
public static final int K_KP_DIVIDE = 0xFFAF;
public static final int K_KP_0 = 0xFFB0;
public static final int K_KP_1 = 0xFFB1;
public static final int K_KP_2 = 0xFFB2;
public static final int K_KP_3 = 0xFFB3;
public static final int K_KP_4 = 0xFFB4;
public static final int K_KP_5 = 0xFFB5;
public static final int K_KP_6 = 0xFFB6;
public static final int K_KP_7 = 0xFFB7;
public static final int K_KP_8 = 0xFFB8;
public static final int K_KP_9 = 0xFFB9;
private static Map<Integer, Integer> keyMap = toMap(new int[][] {
// X Unicode
{ 0x01a1, 0x0104 }, /* Aogonek LATIN CAPITAL LETTER A WITH OGONEK */
{ 0x01a2, 0x02d8 }, /* breve BREVE */
{ 0x01a3, 0x0141 }, /* Lstroke LATIN CAPITAL LETTER L WITH STROKE */
{ 0x01a5, 0x013d }, /* Lcaron LATIN CAPITAL LETTER L WITH CARON */
{ 0x01a6, 0x015a }, /* Sacute LATIN CAPITAL LETTER S WITH ACUTE */
{ 0x01a9, 0x0160 }, /* Scaron LATIN CAPITAL LETTER S WITH CARON */
{ 0x01aa, 0x015e }, /* Scedilla LATIN CAPITAL LETTER S WITH CEDILLA */
{ 0x01ab, 0x0164 }, /* Tcaron LATIN CAPITAL LETTER T WITH CARON */
{ 0x01ac, 0x0179 }, /* Zacute LATIN CAPITAL LETTER Z WITH ACUTE */
{ 0x01ae, 0x017d }, /* Zcaron LATIN CAPITAL LETTER Z WITH CARON */
{ 0x01af, 0x017b }, /* Zabovedot LATIN CAPITAL LETTER Z WITH DOT ABOVE */
{ 0x01b1, 0x0105 }, /* aogonek LATIN SMALL LETTER A WITH OGONEK */
{ 0x01b2, 0x02db }, /* ogonek OGONEK */
{ 0x01b3, 0x0142 }, /* lstroke LATIN SMALL LETTER L WITH STROKE */
{ 0x01b5, 0x013e }, /* lcaron LATIN SMALL LETTER L WITH CARON */
{ 0x01b6, 0x015b }, /* sacute LATIN SMALL LETTER S WITH ACUTE */
{ 0x01b7, 0x02c7 }, /* caron CARON */
{ 0x01b9, 0x0161 }, /* scaron LATIN SMALL LETTER S WITH CARON */
{ 0x01ba, 0x015f }, /* scedilla LATIN SMALL LETTER S WITH CEDILLA */
{ 0x01bb, 0x0165 }, /* tcaron LATIN SMALL LETTER T WITH CARON */
{ 0x01bc, 0x017a }, /* zacute LATIN SMALL LETTER Z WITH ACUTE */
{ 0x01bd, 0x02dd }, /* doubleacute DOUBLE ACUTE ACCENT */
{ 0x01be, 0x017e }, /* zcaron LATIN SMALL LETTER Z WITH CARON */
{ 0x01bf, 0x017c }, /* zabovedot LATIN SMALL LETTER Z WITH DOT ABOVE */
{ 0x01c0, 0x0154 }, /* Racute LATIN CAPITAL LETTER R WITH ACUTE */
{ 0x01c3, 0x0102 }, /* Abreve LATIN CAPITAL LETTER A WITH BREVE */
{ 0x01c5, 0x0139 }, /* Lacute LATIN CAPITAL LETTER L WITH ACUTE */
{ 0x01c6, 0x0106 }, /* Cacute LATIN CAPITAL LETTER C WITH ACUTE */
{ 0x01c8, 0x010c }, /* Ccaron LATIN CAPITAL LETTER C WITH CARON */
{ 0x01ca, 0x0118 }, /* Eogonek LATIN CAPITAL LETTER E WITH OGONEK */
{ 0x01cc, 0x011a }, /* Ecaron LATIN CAPITAL LETTER E WITH CARON */
{ 0x01cf, 0x010e }, /* Dcaron LATIN CAPITAL LETTER D WITH CARON */
{ 0x01d0, 0x0110 }, /* Dstroke LATIN CAPITAL LETTER D WITH STROKE */
{ 0x01d1, 0x0143 }, /* Nacute LATIN CAPITAL LETTER N WITH ACUTE */
{ 0x01d2, 0x0147 }, /* Ncaron LATIN CAPITAL LETTER N WITH CARON */
{ 0x01d5, 0x0150 }, /* Odoubleacute LATIN CAPITAL LETTER O WITH DOUBLE ACUTE */
{ 0x01d8, 0x0158 }, /* Rcaron LATIN CAPITAL LETTER R WITH CARON */
{ 0x01d9, 0x016e }, /* Uring LATIN CAPITAL LETTER U WITH RING ABOVE */
{ 0x01db, 0x0170 }, /* Udoubleacute LATIN CAPITAL LETTER U WITH DOUBLE ACUTE */
{ 0x01de, 0x0162 }, /* Tcedilla LATIN CAPITAL LETTER T WITH CEDILLA */
{ 0x01e0, 0x0155 }, /* racute LATIN SMALL LETTER R WITH ACUTE */
{ 0x01e3, 0x0103 }, /* abreve LATIN SMALL LETTER A WITH BREVE */
{ 0x01e5, 0x013a }, /* lacute LATIN SMALL LETTER L WITH ACUTE */
{ 0x01e6, 0x0107 }, /* cacute LATIN SMALL LETTER C WITH ACUTE */
{ 0x01e8, 0x010d }, /* ccaron LATIN SMALL LETTER C WITH CARON */
{ 0x01ea, 0x0119 }, /* eogonek LATIN SMALL LETTER E WITH OGONEK */
{ 0x01ec, 0x011b }, /* ecaron LATIN SMALL LETTER E WITH CARON */
{ 0x01ef, 0x010f }, /* dcaron LATIN SMALL LETTER D WITH CARON */
{ 0x01f0, 0x0111 }, /* dstroke LATIN SMALL LETTER D WITH STROKE */
{ 0x01f1, 0x0144 }, /* nacute LATIN SMALL LETTER N WITH ACUTE */
{ 0x01f2, 0x0148 }, /* ncaron LATIN SMALL LETTER N WITH CARON */
{ 0x01f5, 0x0151 }, /* odoubleacute LATIN SMALL LETTER O WITH DOUBLE ACUTE */
{ 0x01f8, 0x0159 }, /* rcaron LATIN SMALL LETTER R WITH CARON */
{ 0x01f9, 0x016f }, /* uring LATIN SMALL LETTER U WITH RING ABOVE */
{ 0x01fb, 0x0171 }, /* udoubleacute LATIN SMALL LETTER U WITH DOUBLE ACUTE */
{ 0x01fe, 0x0163 }, /* tcedilla LATIN SMALL LETTER T WITH CEDILLA */
{ 0x01ff, 0x02d9 }, /* abovedot DOT ABOVE */
{ 0x02a1, 0x0126 }, /* Hstroke LATIN CAPITAL LETTER H WITH STROKE */
{ 0x02a6, 0x0124 }, /* Hcircumflex LATIN CAPITAL LETTER H WITH CIRCUMFLEX */
{ 0x02a9, 0x0130 }, /* Iabovedot LATIN CAPITAL LETTER I WITH DOT ABOVE */
{ 0x02ab, 0x011e }, /* Gbreve LATIN CAPITAL LETTER G WITH BREVE */
{ 0x02ac, 0x0134 }, /* Jcircumflex LATIN CAPITAL LETTER J WITH CIRCUMFLEX */
{ 0x02b1, 0x0127 }, /* hstroke LATIN SMALL LETTER H WITH STROKE */
{ 0x02b6, 0x0125 }, /* hcircumflex LATIN SMALL LETTER H WITH CIRCUMFLEX */
{ 0x02b9, 0x0131 }, /* idotless LATIN SMALL LETTER DOTLESS I */
{ 0x02bb, 0x011f }, /* gbreve LATIN SMALL LETTER G WITH BREVE */
{ 0x02bc, 0x0135 }, /* jcircumflex LATIN SMALL LETTER J WITH CIRCUMFLEX */
{ 0x02c5, 0x010a }, /* Cabovedot LATIN CAPITAL LETTER C WITH DOT ABOVE */
{ 0x02c6, 0x0108 }, /* Ccircumflex LATIN CAPITAL LETTER C WITH CIRCUMFLEX */
{ 0x02d5, 0x0120 }, /* Gabovedot LATIN CAPITAL LETTER G WITH DOT ABOVE */
{ 0x02d8, 0x011c }, /* Gcircumflex LATIN CAPITAL LETTER G WITH CIRCUMFLEX */
{ 0x02dd, 0x016c }, /* Ubreve LATIN CAPITAL LETTER U WITH BREVE */
{ 0x02de, 0x015c }, /* Scircumflex LATIN CAPITAL LETTER S WITH CIRCUMFLEX */
{ 0x02e5, 0x010b }, /* cabovedot LATIN SMALL LETTER C WITH DOT ABOVE */
{ 0x02e6, 0x0109 }, /* ccircumflex LATIN SMALL LETTER C WITH CIRCUMFLEX */
{ 0x02f5, 0x0121 }, /* gabovedot LATIN SMALL LETTER G WITH DOT ABOVE */
{ 0x02f8, 0x011d }, /* gcircumflex LATIN SMALL LETTER G WITH CIRCUMFLEX */
{ 0x02fd, 0x016d }, /* ubreve LATIN SMALL LETTER U WITH BREVE */
{ 0x02fe, 0x015d }, /* scircumflex LATIN SMALL LETTER S WITH CIRCUMFLEX */
{ 0x03a2, 0x0138 }, /* kra LATIN SMALL LETTER KRA */
{ 0x03a3, 0x0156 }, /* Rcedilla LATIN CAPITAL LETTER R WITH CEDILLA */
{ 0x03a5, 0x0128 }, /* Itilde LATIN CAPITAL LETTER I WITH TILDE */
{ 0x03a6, 0x013b }, /* Lcedilla LATIN CAPITAL LETTER L WITH CEDILLA */
{ 0x03aa, 0x0112 }, /* Emacron LATIN CAPITAL LETTER E WITH MACRON */
{ 0x03ab, 0x0122 }, /* Gcedilla LATIN CAPITAL LETTER G WITH CEDILLA */
{ 0x03ac, 0x0166 }, /* Tslash LATIN CAPITAL LETTER T WITH STROKE */
{ 0x03b3, 0x0157 }, /* rcedilla LATIN SMALL LETTER R WITH CEDILLA */
{ 0x03b5, 0x0129 }, /* itilde LATIN SMALL LETTER I WITH TILDE */
{ 0x03b6, 0x013c }, /* lcedilla LATIN SMALL LETTER L WITH CEDILLA */
{ 0x03ba, 0x0113 }, /* emacron LATIN SMALL LETTER E WITH MACRON */
{ 0x03bb, 0x0123 }, /* gcedilla LATIN SMALL LETTER G WITH CEDILLA */
{ 0x03bc, 0x0167 }, /* tslash LATIN SMALL LETTER T WITH STROKE */
{ 0x03bd, 0x014a }, /* ENG LATIN CAPITAL LETTER ENG */
{ 0x03bf, 0x014b }, /* eng LATIN SMALL LETTER ENG */
{ 0x03c0, 0x0100 }, /* Amacron LATIN CAPITAL LETTER A WITH MACRON */
{ 0x03c7, 0x012e }, /* Iogonek LATIN CAPITAL LETTER I WITH OGONEK */
{ 0x03cc, 0x0116 }, /* Eabovedot LATIN CAPITAL LETTER E WITH DOT ABOVE */
{ 0x03cf, 0x012a }, /* Imacron LATIN CAPITAL LETTER I WITH MACRON */
{ 0x03d1, 0x0145 }, /* Ncedilla LATIN CAPITAL LETTER N WITH CEDILLA */
{ 0x03d2, 0x014c }, /* Omacron LATIN CAPITAL LETTER O WITH MACRON */
{ 0x03d3, 0x0136 }, /* Kcedilla LATIN CAPITAL LETTER K WITH CEDILLA */
{ 0x03d9, 0x0172 }, /* Uogonek LATIN CAPITAL LETTER U WITH OGONEK */
{ 0x03dd, 0x0168 }, /* Utilde LATIN CAPITAL LETTER U WITH TILDE */
{ 0x03de, 0x016a }, /* Umacron LATIN CAPITAL LETTER U WITH MACRON */
{ 0x03e0, 0x0101 }, /* amacron LATIN SMALL LETTER A WITH MACRON */
{ 0x03e7, 0x012f }, /* iogonek LATIN SMALL LETTER I WITH OGONEK */
{ 0x03ec, 0x0117 }, /* eabovedot LATIN SMALL LETTER E WITH DOT ABOVE */
{ 0x03ef, 0x012b }, /* imacron LATIN SMALL LETTER I WITH MACRON */
{ 0x03f1, 0x0146 }, /* ncedilla LATIN SMALL LETTER N WITH CEDILLA */
{ 0x03f2, 0x014d }, /* omacron LATIN SMALL LETTER O WITH MACRON */
{ 0x03f3, 0x0137 }, /* kcedilla LATIN SMALL LETTER K WITH CEDILLA */
{ 0x03f9, 0x0173 }, /* uogonek LATIN SMALL LETTER U WITH OGONEK */
{ 0x03fd, 0x0169 }, /* utilde LATIN SMALL LETTER U WITH TILDE */
{ 0x03fe, 0x016b }, /* umacron LATIN SMALL LETTER U WITH MACRON */
{ 0x047e, 0x203e }, /* overline OVERLINE */
{ 0x04a1, 0x3002 }, /* kana_fullstop IDEOGRAPHIC FULL STOP */
{ 0x04a2, 0x300c }, /* kana_openingbracket LEFT CORNER BRACKET */
{ 0x04a3, 0x300d }, /* kana_closingbracket RIGHT CORNER BRACKET */
{ 0x04a4, 0x3001 }, /* kana_comma IDEOGRAPHIC COMMA */
{ 0x04a5, 0x30fb }, /* kana_conjunctive KATAKANA MIDDLE DOT */
{ 0x04a6, 0x30f2 }, /* kana_WO KATAKANA LETTER WO */
{ 0x04a7, 0x30a1 }, /* kana_a KATAKANA LETTER SMALL A */
{ 0x04a8, 0x30a3 }, /* kana_i KATAKANA LETTER SMALL I */
{ 0x04a9, 0x30a5 }, /* kana_u KATAKANA LETTER SMALL U */
{ 0x04aa, 0x30a7 }, /* kana_e KATAKANA LETTER SMALL E */
{ 0x04ab, 0x30a9 }, /* kana_o KATAKANA LETTER SMALL O */
{ 0x04ac, 0x30e3 }, /* kana_ya KATAKANA LETTER SMALL YA */
{ 0x04ad, 0x30e5 }, /* kana_yu KATAKANA LETTER SMALL YU */
{ 0x04ae, 0x30e7 }, /* kana_yo KATAKANA LETTER SMALL YO */
{ 0x04af, 0x30c3 }, /* kana_tsu KATAKANA LETTER SMALL TU */
{ 0x04b0, 0x30fc }, /* prolongedsound KATAKANA-HIRAGANA PROLONGED SOUND MARK */
{ 0x04b1, 0x30a2 }, /* kana_A KATAKANA LETTER A */
{ 0x04b2, 0x30a4 }, /* kana_I KATAKANA LETTER I */
{ 0x04b3, 0x30a6 }, /* kana_U KATAKANA LETTER U */
{ 0x04b4, 0x30a8 }, /* kana_E KATAKANA LETTER E */
{ 0x04b5, 0x30aa }, /* kana_O KATAKANA LETTER O */
{ 0x04b6, 0x30ab }, /* kana_KA KATAKANA LETTER KA */
{ 0x04b7, 0x30ad }, /* kana_KI KATAKANA LETTER KI */
{ 0x04b8, 0x30af }, /* kana_KU KATAKANA LETTER KU */
{ 0x04b9, 0x30b1 }, /* kana_KE KATAKANA LETTER KE */
{ 0x04ba, 0x30b3 }, /* kana_KO KATAKANA LETTER KO */
{ 0x04bb, 0x30b5 }, /* kana_SA KATAKANA LETTER SA */
{ 0x04bc, 0x30b7 }, /* kana_SHI KATAKANA LETTER SI */
{ 0x04bd, 0x30b9 }, /* kana_SU KATAKANA LETTER SU */
{ 0x04be, 0x30bb }, /* kana_SE KATAKANA LETTER SE */
{ 0x04bf, 0x30bd }, /* kana_SO KATAKANA LETTER SO */
{ 0x04c0, 0x30bf }, /* kana_TA KATAKANA LETTER TA */
{ 0x04c1, 0x30c1 }, /* kana_CHI KATAKANA LETTER TI */
{ 0x04c2, 0x30c4 }, /* kana_TSU KATAKANA LETTER TU */
{ 0x04c3, 0x30c6 }, /* kana_TE KATAKANA LETTER TE */
{ 0x04c4, 0x30c8 }, /* kana_TO KATAKANA LETTER TO */
{ 0x04c5, 0x30ca }, /* kana_NA KATAKANA LETTER NA */
{ 0x04c6, 0x30cb }, /* kana_NI KATAKANA LETTER NI */
{ 0x04c7, 0x30cc }, /* kana_NU KATAKANA LETTER NU */
{ 0x04c8, 0x30cd }, /* kana_NE KATAKANA LETTER NE */
{ 0x04c9, 0x30ce }, /* kana_NO KATAKANA LETTER NO */
{ 0x04ca, 0x30cf }, /* kana_HA KATAKANA LETTER HA */
{ 0x04cb, 0x30d2 }, /* kana_HI KATAKANA LETTER HI */
{ 0x04cc, 0x30d5 }, /* kana_FU KATAKANA LETTER HU */
{ 0x04cd, 0x30d8 }, /* kana_HE KATAKANA LETTER HE */
{ 0x04ce, 0x30db }, /* kana_HO KATAKANA LETTER HO */
{ 0x04cf, 0x30de }, /* kana_MA KATAKANA LETTER MA */
{ 0x04d0, 0x30df }, /* kana_MI KATAKANA LETTER MI */
{ 0x04d1, 0x30e0 }, /* kana_MU KATAKANA LETTER MU */
{ 0x04d2, 0x30e1 }, /* kana_ME KATAKANA LETTER ME */
{ 0x04d3, 0x30e2 }, /* kana_MO KATAKANA LETTER MO */
{ 0x04d4, 0x30e4 }, /* kana_YA KATAKANA LETTER YA */
{ 0x04d5, 0x30e6 }, /* kana_YU KATAKANA LETTER YU */
{ 0x04d6, 0x30e8 }, /* kana_YO KATAKANA LETTER YO */
{ 0x04d7, 0x30e9 }, /* kana_RA KATAKANA LETTER RA */
{ 0x04d8, 0x30ea }, /* kana_RI KATAKANA LETTER RI */
{ 0x04d9, 0x30eb }, /* kana_RU KATAKANA LETTER RU */
{ 0x04da, 0x30ec }, /* kana_RE KATAKANA LETTER RE */
{ 0x04db, 0x30ed }, /* kana_RO KATAKANA LETTER RO */
{ 0x04dc, 0x30ef }, /* kana_WA KATAKANA LETTER WA */
{ 0x04dd, 0x30f3 }, /* kana_N KATAKANA LETTER N */
{ 0x04de, 0x309b }, /* voicedsound KATAKANA-HIRAGANA VOICED SOUND MARK */
{ 0x04df, 0x309c }, /* semivoicedsound KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK */
{ 0x05ac, 0x060c }, /* Arabic_comma ARABIC COMMA */
{ 0x05bb, 0x061b }, /* Arabic_semicolon ARABIC SEMICOLON */
{ 0x05bf, 0x061f }, /* Arabic_question_mark ARABIC QUESTION MARK */
{ 0x05c1, 0x0621 }, /* Arabic_hamza ARABIC LETTER HAMZA */
{ 0x05c2, 0x0622 }, /* Arabic_maddaonalef ARABIC LETTER ALEF WITH MADDA ABOVE */
{ 0x05c3, 0x0623 }, /* Arabic_hamzaonalef ARABIC LETTER ALEF WITH HAMZA ABOVE */
{ 0x05c4, 0x0624 }, /* Arabic_hamzaonwaw ARABIC LETTER WAW WITH HAMZA ABOVE */
{ 0x05c5, 0x0625 }, /* Arabic_hamzaunderalef ARABIC LETTER ALEF WITH HAMZA BELOW */
{ 0x05c6, 0x0626 }, /* Arabic_hamzaonyeh ARABIC LETTER YEH WITH HAMZA ABOVE */
{ 0x05c7, 0x0627 }, /* Arabic_alef ARABIC LETTER ALEF */
{ 0x05c8, 0x0628 }, /* Arabic_beh ARABIC LETTER BEH */
{ 0x05c9, 0x0629 }, /* Arabic_tehmarbuta ARABIC LETTER TEH MARBUTA */
{ 0x05ca, 0x062a }, /* Arabic_teh ARABIC LETTER TEH */
{ 0x05cb, 0x062b }, /* Arabic_theh ARABIC LETTER THEH */
{ 0x05cc, 0x062c }, /* Arabic_jeem ARABIC LETTER JEEM */
{ 0x05cd, 0x062d }, /* Arabic_hah ARABIC LETTER HAH */
{ 0x05ce, 0x062e }, /* Arabic_khah ARABIC LETTER KHAH */
{ 0x05cf, 0x062f }, /* Arabic_dal ARABIC LETTER DAL */
{ 0x05d0, 0x0630 }, /* Arabic_thal ARABIC LETTER THAL */
{ 0x05d1, 0x0631 }, /* Arabic_ra ARABIC LETTER REH */
{ 0x05d2, 0x0632 }, /* Arabic_zain ARABIC LETTER ZAIN */
{ 0x05d3, 0x0633 }, /* Arabic_seen ARABIC LETTER SEEN */
{ 0x05d4, 0x0634 }, /* Arabic_sheen ARABIC LETTER SHEEN */
{ 0x05d5, 0x0635 }, /* Arabic_sad ARABIC LETTER SAD */
{ 0x05d6, 0x0636 }, /* Arabic_dad ARABIC LETTER DAD */
{ 0x05d7, 0x0637 }, /* Arabic_tah ARABIC LETTER TAH */
{ 0x05d8, 0x0638 }, /* Arabic_zah ARABIC LETTER ZAH */
{ 0x05d9, 0x0639 }, /* Arabic_ain ARABIC LETTER AIN */
{ 0x05da, 0x063a }, /* Arabic_ghain ARABIC LETTER GHAIN */
{ 0x05e0, 0x0640 }, /* Arabic_tatweel ARABIC TATWEEL */
{ 0x05e1, 0x0641 }, /* Arabic_feh ARABIC LETTER FEH */
{ 0x05e2, 0x0642 }, /* Arabic_qaf ARABIC LETTER QAF */
{ 0x05e3, 0x0643 }, /* Arabic_kaf ARABIC LETTER KAF */
{ 0x05e4, 0x0644 }, /* Arabic_lam ARABIC LETTER LAM */
{ 0x05e5, 0x0645 }, /* Arabic_meem ARABIC LETTER MEEM */
{ 0x05e6, 0x0646 }, /* Arabic_noon ARABIC LETTER NOON */
{ 0x05e7, 0x0647 }, /* Arabic_ha ARABIC LETTER HEH */
{ 0x05e8, 0x0648 }, /* Arabic_waw ARABIC LETTER WAW */
{ 0x05e9, 0x0649 }, /* Arabic_alefmaksura ARABIC LETTER ALEF MAKSURA */
{ 0x05ea, 0x064a }, /* Arabic_yeh ARABIC LETTER YEH */
{ 0x05eb, 0x064b }, /* Arabic_fathatan ARABIC FATHATAN */
{ 0x05ec, 0x064c }, /* Arabic_dammatan ARABIC DAMMATAN */
{ 0x05ed, 0x064d }, /* Arabic_kasratan ARABIC KASRATAN */
{ 0x05ee, 0x064e }, /* Arabic_fatha ARABIC FATHA */
{ 0x05ef, 0x064f }, /* Arabic_damma ARABIC DAMMA */
{ 0x05f0, 0x0650 }, /* Arabic_kasra ARABIC KASRA */
{ 0x05f1, 0x0651 }, /* Arabic_shadda ARABIC SHADDA */
{ 0x05f2, 0x0652 }, /* Arabic_sukun ARABIC SUKUN */
{ 0x06a1, 0x0452 }, /* Serbian_dje CYRILLIC SMALL LETTER DJE */
{ 0x06a2, 0x0453 }, /* Macedonia_gje CYRILLIC SMALL LETTER GJE */
{ 0x06a3, 0x0451 }, /* Cyrillic_io CYRILLIC SMALL LETTER IO */
{ 0x06a4, 0x0454 }, /* Ukrainian_ie CYRILLIC SMALL LETTER UKRAINIAN IE */
{ 0x06a5, 0x0455 }, /* Macedonia_dse CYRILLIC SMALL LETTER DZE */
{ 0x06a6, 0x0456 }, /* Ukrainian_i CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I */
{ 0x06a7, 0x0457 }, /* Ukrainian_yi CYRILLIC SMALL LETTER YI */
{ 0x06a8, 0x0458 }, /* Cyrillic_je CYRILLIC SMALL LETTER JE */
{ 0x06a9, 0x0459 }, /* Cyrillic_lje CYRILLIC SMALL LETTER LJE */
{ 0x06aa, 0x045a }, /* Cyrillic_nje CYRILLIC SMALL LETTER NJE */
{ 0x06ab, 0x045b }, /* Serbian_tshe CYRILLIC SMALL LETTER TSHE */
{ 0x06ac, 0x045c }, /* Macedonia_kje CYRILLIC SMALL LETTER KJE */
{ 0x06ae, 0x045e }, /* Byelorussian_shortu CYRILLIC SMALL LETTER SHORT U */
{ 0x06af, 0x045f }, /* Cyrillic_dzhe CYRILLIC SMALL LETTER DZHE */
{ 0x06b0, 0x2116 }, /* numerosign NUMERO SIGN */
{ 0x06b1, 0x0402 }, /* Serbian_DJE CYRILLIC CAPITAL LETTER DJE */
{ 0x06b2, 0x0403 }, /* Macedonia_GJE CYRILLIC CAPITAL LETTER GJE */
{ 0x06b3, 0x0401 }, /* Cyrillic_IO CYRILLIC CAPITAL LETTER IO */
{ 0x06b4, 0x0404 }, /* Ukrainian_IE CYRILLIC CAPITAL LETTER UKRAINIAN IE */
{ 0x06b5, 0x0405 }, /* Macedonia_DSE CYRILLIC CAPITAL LETTER DZE */
{ 0x06b6, 0x0406 }, /* Ukrainian_I CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I */
{ 0x06b7, 0x0407 }, /* Ukrainian_YI CYRILLIC CAPITAL LETTER YI */
{ 0x06b8, 0x0408 }, /* Cyrillic_JE CYRILLIC CAPITAL LETTER JE */
{ 0x06b9, 0x0409 }, /* Cyrillic_LJE CYRILLIC CAPITAL LETTER LJE */
{ 0x06ba, 0x040a }, /* Cyrillic_NJE CYRILLIC CAPITAL LETTER NJE */
{ 0x06bb, 0x040b }, /* Serbian_TSHE CYRILLIC CAPITAL LETTER TSHE */
{ 0x06bc, 0x040c }, /* Macedonia_KJE CYRILLIC CAPITAL LETTER KJE */
{ 0x06be, 0x040e }, /* Byelorussian_SHORTU CYRILLIC CAPITAL LETTER SHORT U */
{ 0x06bf, 0x040f }, /* Cyrillic_DZHE CYRILLIC CAPITAL LETTER DZHE */
{ 0x06c0, 0x044e }, /* Cyrillic_yu CYRILLIC SMALL LETTER YU */
{ 0x06c1, 0x0430 }, /* Cyrillic_a CYRILLIC SMALL LETTER A */
{ 0x06c2, 0x0431 }, /* Cyrillic_be CYRILLIC SMALL LETTER BE */
{ 0x06c3, 0x0446 }, /* Cyrillic_tse CYRILLIC SMALL LETTER TSE */
{ 0x06c4, 0x0434 }, /* Cyrillic_de CYRILLIC SMALL LETTER DE */
{ 0x06c5, 0x0435 }, /* Cyrillic_ie CYRILLIC SMALL LETTER IE */
{ 0x06c6, 0x0444 }, /* Cyrillic_ef CYRILLIC SMALL LETTER EF */
{ 0x06c7, 0x0433 }, /* Cyrillic_ghe CYRILLIC SMALL LETTER GHE */
{ 0x06c8, 0x0445 }, /* Cyrillic_ha CYRILLIC SMALL LETTER HA */
{ 0x06c9, 0x0438 }, /* Cyrillic_i CYRILLIC SMALL LETTER I */
{ 0x06ca, 0x0439 }, /* Cyrillic_shorti CYRILLIC SMALL LETTER SHORT I */
{ 0x06cb, 0x043a }, /* Cyrillic_ka CYRILLIC SMALL LETTER KA */
{ 0x06cc, 0x043b }, /* Cyrillic_el CYRILLIC SMALL LETTER EL */
{ 0x06cd, 0x043c }, /* Cyrillic_em CYRILLIC SMALL LETTER EM */
{ 0x06ce, 0x043d }, /* Cyrillic_en CYRILLIC SMALL LETTER EN */
{ 0x06cf, 0x043e }, /* Cyrillic_o CYRILLIC SMALL LETTER O */
{ 0x06d0, 0x043f }, /* Cyrillic_pe CYRILLIC SMALL LETTER PE */
{ 0x06d1, 0x044f }, /* Cyrillic_ya CYRILLIC SMALL LETTER YA */
{ 0x06d2, 0x0440 }, /* Cyrillic_er CYRILLIC SMALL LETTER ER */
{ 0x06d3, 0x0441 }, /* Cyrillic_es CYRILLIC SMALL LETTER ES */
{ 0x06d4, 0x0442 }, /* Cyrillic_te CYRILLIC SMALL LETTER TE */
{ 0x06d5, 0x0443 }, /* Cyrillic_u CYRILLIC SMALL LETTER U */
{ 0x06d6, 0x0436 }, /* Cyrillic_zhe CYRILLIC SMALL LETTER ZHE */
{ 0x06d7, 0x0432 }, /* Cyrillic_ve CYRILLIC SMALL LETTER VE */
{ 0x06d8, 0x044c }, /* Cyrillic_softsign CYRILLIC SMALL LETTER SOFT SIGN */
{ 0x06d9, 0x044b }, /* Cyrillic_yeru CYRILLIC SMALL LETTER YERU */
{ 0x06da, 0x0437 }, /* Cyrillic_ze CYRILLIC SMALL LETTER ZE */
{ 0x06db, 0x0448 }, /* Cyrillic_sha CYRILLIC SMALL LETTER SHA */
{ 0x06dc, 0x044d }, /* Cyrillic_e CYRILLIC SMALL LETTER E */
{ 0x06dd, 0x0449 }, /* Cyrillic_shcha CYRILLIC SMALL LETTER SHCHA */
{ 0x06de, 0x0447 }, /* Cyrillic_che CYRILLIC SMALL LETTER CHE */
{ 0x06df, 0x044a }, /* Cyrillic_hardsign CYRILLIC SMALL LETTER HARD SIGN */
{ 0x06e0, 0x042e }, /* Cyrillic_YU CYRILLIC CAPITAL LETTER YU */
{ 0x06e1, 0x0410 }, /* Cyrillic_A CYRILLIC CAPITAL LETTER A */
{ 0x06e2, 0x0411 }, /* Cyrillic_BE CYRILLIC CAPITAL LETTER BE */
{ 0x06e3, 0x0426 }, /* Cyrillic_TSE CYRILLIC CAPITAL LETTER TSE */
{ 0x06e4, 0x0414 }, /* Cyrillic_DE CYRILLIC CAPITAL LETTER DE */
{ 0x06e5, 0x0415 }, /* Cyrillic_IE CYRILLIC CAPITAL LETTER IE */
{ 0x06e6, 0x0424 }, /* Cyrillic_EF CYRILLIC CAPITAL LETTER EF */
{ 0x06e7, 0x0413 }, /* Cyrillic_GHE CYRILLIC CAPITAL LETTER GHE */
{ 0x06e8, 0x0425 }, /* Cyrillic_HA CYRILLIC CAPITAL LETTER HA */
{ 0x06e9, 0x0418 }, /* Cyrillic_I CYRILLIC CAPITAL LETTER I */
{ 0x06ea, 0x0419 }, /* Cyrillic_SHORTI CYRILLIC CAPITAL LETTER SHORT I */
{ 0x06eb, 0x041a }, /* Cyrillic_KA CYRILLIC CAPITAL LETTER KA */
{ 0x06ec, 0x041b }, /* Cyrillic_EL CYRILLIC CAPITAL LETTER EL */
{ 0x06ed, 0x041c }, /* Cyrillic_EM CYRILLIC CAPITAL LETTER EM */
{ 0x06ee, 0x041d }, /* Cyrillic_EN CYRILLIC CAPITAL LETTER EN */
{ 0x06ef, 0x041e }, /* Cyrillic_O CYRILLIC CAPITAL LETTER O */
{ 0x06f0, 0x041f }, /* Cyrillic_PE CYRILLIC CAPITAL LETTER PE */
{ 0x06f1, 0x042f }, /* Cyrillic_YA CYRILLIC CAPITAL LETTER YA */
{ 0x06f2, 0x0420 }, /* Cyrillic_ER CYRILLIC CAPITAL LETTER ER */
{ 0x06f3, 0x0421 }, /* Cyrillic_ES CYRILLIC CAPITAL LETTER ES */
{ 0x06f4, 0x0422 }, /* Cyrillic_TE CYRILLIC CAPITAL LETTER TE */
{ 0x06f5, 0x0423 }, /* Cyrillic_U CYRILLIC CAPITAL LETTER U */
{ 0x06f6, 0x0416 }, /* Cyrillic_ZHE CYRILLIC CAPITAL LETTER ZHE */
{ 0x06f7, 0x0412 }, /* Cyrillic_VE CYRILLIC CAPITAL LETTER VE */
{ 0x06f8, 0x042c }, /* Cyrillic_SOFTSIGN CYRILLIC CAPITAL LETTER SOFT SIGN */
{ 0x06f9, 0x042b }, /* Cyrillic_YERU CYRILLIC CAPITAL LETTER YERU */
{ 0x06fa, 0x0417 }, /* Cyrillic_ZE CYRILLIC CAPITAL LETTER ZE */
{ 0x06fb, 0x0428 }, /* Cyrillic_SHA CYRILLIC CAPITAL LETTER SHA */
{ 0x06fc, 0x042d }, /* Cyrillic_E CYRILLIC CAPITAL LETTER E */
{ 0x06fd, 0x0429 }, /* Cyrillic_SHCHA CYRILLIC CAPITAL LETTER SHCHA */
{ 0x06fe, 0x0427 }, /* Cyrillic_CHE CYRILLIC CAPITAL LETTER CHE */
{ 0x06ff, 0x042a }, /* Cyrillic_HARDSIGN CYRILLIC CAPITAL LETTER HARD SIGN */
{ 0x07a1, 0x0386 }, /* Greek_ALPHAaccent GREEK CAPITAL LETTER ALPHA WITH TONOS */
{ 0x07a2, 0x0388 }, /* Greek_EPSILONaccent GREEK CAPITAL LETTER EPSILON WITH TONOS */
{ 0x07a3, 0x0389 }, /* Greek_ETAaccent GREEK CAPITAL LETTER ETA WITH TONOS */
{ 0x07a4, 0x038a }, /* Greek_IOTAaccent GREEK CAPITAL LETTER IOTA WITH TONOS */
{ 0x07a5, 0x03aa }, /* Greek_IOTAdiaeresis GREEK CAPITAL LETTER IOTA WITH DIALYTIKA */
{ 0x07a7, 0x038c }, /* Greek_OMICRONaccent GREEK CAPITAL LETTER OMICRON WITH TONOS */
{ 0x07a8, 0x038e }, /* Greek_UPSILONaccent GREEK CAPITAL LETTER UPSILON WITH TONOS */
{ 0x07a9, 0x03ab }, /* Greek_UPSILONdieresis GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA */
{ 0x07ab, 0x038f }, /* Greek_OMEGAaccent GREEK CAPITAL LETTER OMEGA WITH TONOS */
{ 0x07ae, 0x0385 }, /* Greek_accentdieresis GREEK DIALYTIKA TONOS */
{ 0x07af, 0x2015 }, /* Greek_horizbar HORIZONTAL BAR */
{ 0x07b1, 0x03ac }, /* Greek_alphaaccent GREEK SMALL LETTER ALPHA WITH TONOS */
{ 0x07b2, 0x03ad }, /* Greek_epsilonaccent GREEK SMALL LETTER EPSILON WITH TONOS */
{ 0x07b3, 0x03ae }, /* Greek_etaaccent GREEK SMALL LETTER ETA WITH TONOS */
{ 0x07b4, 0x03af }, /* Greek_iotaaccent GREEK SMALL LETTER IOTA WITH TONOS */
{ 0x07b5, 0x03ca }, /* Greek_iotadieresis GREEK SMALL LETTER IOTA WITH DIALYTIKA */
{ 0x07b6, 0x0390 }, /* Greek_iotaaccentdieresis GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS */
{ 0x07b7, 0x03cc }, /* Greek_omicronaccent GREEK SMALL LETTER OMICRON WITH TONOS */
{ 0x07b8, 0x03cd }, /* Greek_upsilonaccent GREEK SMALL LETTER UPSILON WITH TONOS */
{ 0x07b9, 0x03cb }, /* Greek_upsilondieresis GREEK SMALL LETTER UPSILON WITH DIALYTIKA */
{ 0x07ba, 0x03b0 }, /* Greek_upsilonaccentdieresis GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS */
{ 0x07bb, 0x03ce }, /* Greek_omegaaccent GREEK SMALL LETTER OMEGA WITH TONOS */
{ 0x07c1, 0x0391 }, /* Greek_ALPHA GREEK CAPITAL LETTER ALPHA */
{ 0x07c2, 0x0392 }, /* Greek_BETA GREEK CAPITAL LETTER BETA */
{ 0x07c3, 0x0393 }, /* Greek_GAMMA GREEK CAPITAL LETTER GAMMA */
{ 0x07c4, 0x0394 }, /* Greek_DELTA GREEK CAPITAL LETTER DELTA */
{ 0x07c5, 0x0395 }, /* Greek_EPSILON GREEK CAPITAL LETTER EPSILON */
{ 0x07c6, 0x0396 }, /* Greek_ZETA GREEK CAPITAL LETTER ZETA */
{ 0x07c7, 0x0397 }, /* Greek_ETA GREEK CAPITAL LETTER ETA */
{ 0x07c8, 0x0398 }, /* Greek_THETA GREEK CAPITAL LETTER THETA */
{ 0x07c9, 0x0399 }, /* Greek_IOTA GREEK CAPITAL LETTER IOTA */
{ 0x07ca, 0x039a }, /* Greek_KAPPA GREEK CAPITAL LETTER KAPPA */
{ 0x07cb, 0x039b }, /* Greek_LAMBDA GREEK CAPITAL LETTER LAMDA */
{ 0x07cc, 0x039c }, /* Greek_MU GREEK CAPITAL LETTER MU */
{ 0x07cd, 0x039d }, /* Greek_NU GREEK CAPITAL LETTER NU */
{ 0x07ce, 0x039e }, /* Greek_XI GREEK CAPITAL LETTER XI */
{ 0x07cf, 0x039f }, /* Greek_OMICRON GREEK CAPITAL LETTER OMICRON */
{ 0x07d0, 0x03a0 }, /* Greek_PI GREEK CAPITAL LETTER PI */
{ 0x07d1, 0x03a1 }, /* Greek_RHO GREEK CAPITAL LETTER RHO */
{ 0x07d2, 0x03a3 }, /* Greek_SIGMA GREEK CAPITAL LETTER SIGMA */
{ 0x07d4, 0x03a4 }, /* Greek_TAU GREEK CAPITAL LETTER TAU */
{ 0x07d5, 0x03a5 }, /* Greek_UPSILON GREEK CAPITAL LETTER UPSILON */
{ 0x07d6, 0x03a6 }, /* Greek_PHI GREEK CAPITAL LETTER PHI */
{ 0x07d7, 0x03a7 }, /* Greek_CHI GREEK CAPITAL LETTER CHI */
{ 0x07d8, 0x03a8 }, /* Greek_PSI GREEK CAPITAL LETTER PSI */
{ 0x07d9, 0x03a9 }, /* Greek_OMEGA GREEK CAPITAL LETTER OMEGA */
{ 0x07e1, 0x03b1 }, /* Greek_alpha GREEK SMALL LETTER ALPHA */
{ 0x07e2, 0x03b2 }, /* Greek_beta GREEK SMALL LETTER BETA */
{ 0x07e3, 0x03b3 }, /* Greek_gamma GREEK SMALL LETTER GAMMA */
{ 0x07e4, 0x03b4 }, /* Greek_delta GREEK SMALL LETTER DELTA */
{ 0x07e5, 0x03b5 }, /* Greek_epsilon GREEK SMALL LETTER EPSILON */
{ 0x07e6, 0x03b6 }, /* Greek_zeta GREEK SMALL LETTER ZETA */
{ 0x07e7, 0x03b7 }, /* Greek_eta GREEK SMALL LETTER ETA */
{ 0x07e8, 0x03b8 }, /* Greek_theta GREEK SMALL LETTER THETA */
{ 0x07e9, 0x03b9 }, /* Greek_iota GREEK SMALL LETTER IOTA */
{ 0x07ea, 0x03ba }, /* Greek_kappa GREEK SMALL LETTER KAPPA */
{ 0x07eb, 0x03bb }, /* Greek_lambda GREEK SMALL LETTER LAMDA */
{ 0x07ec, 0x03bc }, /* Greek_mu GREEK SMALL LETTER MU */
{ 0x07ed, 0x03bd }, /* Greek_nu GREEK SMALL LETTER NU */
{ 0x07ee, 0x03be }, /* Greek_xi GREEK SMALL LETTER XI */
{ 0x07ef, 0x03bf }, /* Greek_omicron GREEK SMALL LETTER OMICRON */
{ 0x07f0, 0x03c0 }, /* Greek_pi GREEK SMALL LETTER PI */
{ 0x07f1, 0x03c1 }, /* Greek_rho GREEK SMALL LETTER RHO */
{ 0x07f2, 0x03c3 }, /* Greek_sigma GREEK SMALL LETTER SIGMA */
{ 0x07f3, 0x03c2 }, /* Greek_finalsmallsigma GREEK SMALL LETTER FINAL SIGMA */
{ 0x07f4, 0x03c4 }, /* Greek_tau GREEK SMALL LETTER TAU */
{ 0x07f5, 0x03c5 }, /* Greek_upsilon GREEK SMALL LETTER UPSILON */
{ 0x07f6, 0x03c6 }, /* Greek_phi GREEK SMALL LETTER PHI */
{ 0x07f7, 0x03c7 }, /* Greek_chi GREEK SMALL LETTER CHI */
{ 0x07f8, 0x03c8 }, /* Greek_psi GREEK SMALL LETTER PSI */
{ 0x07f9, 0x03c9 }, /* Greek_omega GREEK SMALL LETTER OMEGA */
{ 0x08a1, 0x23b7 }, /* leftradical ??? */
{ 0x08a2, 0x250c }, /* topleftradical BOX DRAWINGS LIGHT DOWN AND RIGHT */
{ 0x08a3, 0x2500 }, /* horizconnector BOX DRAWINGS LIGHT HORIZONTAL */
{ 0x08a4, 0x2320 }, /* topintegral TOP HALF INTEGRAL */
{ 0x08a5, 0x2321 }, /* botintegral BOTTOM HALF INTEGRAL */
{ 0x08a6, 0x2502 }, /* vertconnector BOX DRAWINGS LIGHT VERTICAL */
{ 0x08a7, 0x23a1 }, /* topleftsqbracket ??? */
{ 0x08a8, 0x23a3 }, /* botleftsqbracket ??? */
{ 0x08a9, 0x23a4 }, /* toprightsqbracket ??? */
{ 0x08aa, 0x23a6 }, /* botrightsqbracket ??? */
{ 0x08ab, 0x239b }, /* topleftparens ??? */
{ 0x08ac, 0x239d }, /* botleftparens ??? */
{ 0x08ad, 0x239e }, /* toprightparens ??? */
{ 0x08ae, 0x23a0 }, /* botrightparens ??? */
{ 0x08af, 0x23a8 }, /* leftmiddlecurlybrace ??? */
{ 0x08b0, 0x23ac }, /* rightmiddlecurlybrace ??? */
/* 0x08b1 topleftsummation ??? */
/* 0x08b2 botleftsummation ??? */
/* 0x08b3 topvertsummationconnector ??? */
/* 0x08b4 botvertsummationconnector ??? */
/* 0x08b5 toprightsummation ??? */
/* 0x08b6 botrightsummation ??? */
/* 0x08b7 rightmiddlesummation ??? */
{ 0x08bc, 0x2264 }, /* lessthanequal LESS-THAN OR EQUAL TO */
{ 0x08bd, 0x2260 }, /* notequal NOT EQUAL TO */
{ 0x08be, 0x2265 }, /* greaterthanequal GREATER-THAN OR EQUAL TO */
{ 0x08bf, 0x222b }, /* integral INTEGRAL */
{ 0x08c0, 0x2234 }, /* therefore THEREFORE */
{ 0x08c1, 0x221d }, /* variation PROPORTIONAL TO */
{ 0x08c2, 0x221e }, /* infinity INFINITY */
{ 0x08c5, 0x2207 }, /* nabla NABLA */
{ 0x08c8, 0x223c }, /* approximate TILDE OPERATOR */
{ 0x08c9, 0x2243 }, /* similarequal ASYMPTOTICALLY EQUAL TO */
{ 0x08cd, 0x21d4 }, /* ifonlyif LEFT RIGHT DOUBLE ARROW */
{ 0x08ce, 0x21d2 }, /* implies RIGHTWARDS DOUBLE ARROW */
{ 0x08cf, 0x2261 }, /* identical IDENTICAL TO */
{ 0x08d6, 0x221a }, /* radical SQUARE ROOT */
{ 0x08da, 0x2282 }, /* includedin SUBSET OF */
{ 0x08db, 0x2283 }, /* includes SUPERSET OF */
{ 0x08dc, 0x2229 }, /* intersection INTERSECTION */
{ 0x08dd, 0x222a }, /* union UNION */
{ 0x08de, 0x2227 }, /* logicaland LOGICAL AND */
{ 0x08df, 0x2228 }, /* logicalor LOGICAL OR */
{ 0x08ef, 0x2202 }, /* partialderivative PARTIAL DIFFERENTIAL */
{ 0x08f6, 0x0192 }, /* function LATIN SMALL LETTER F WITH HOOK */
{ 0x08fb, 0x2190 }, /* leftarrow LEFTWARDS ARROW */
{ 0x08fc, 0x2191 }, /* uparrow UPWARDS ARROW */
{ 0x08fd, 0x2192 }, /* rightarrow RIGHTWARDS ARROW */
{ 0x08fe, 0x2193 }, /* downarrow DOWNWARDS ARROW */
/* 0x09df blank ??? */
{ 0x09e0, 0x25c6 }, /* soliddiamond BLACK DIAMOND */
{ 0x09e1, 0x2592 }, /* checkerboard MEDIUM SHADE */
{ 0x09e2, 0x2409 }, /* ht SYMBOL FOR HORIZONTAL TABULATION */
{ 0x09e3, 0x240c }, /* ff SYMBOL FOR FORM FEED */
{ 0x09e4, 0x240d }, /* cr SYMBOL FOR CARRIAGE RETURN */
{ 0x09e5, 0x240a }, /* lf SYMBOL FOR LINE FEED */
{ 0x09e8, 0x2424 }, /* nl SYMBOL FOR NEWLINE */
{ 0x09e9, 0x240b }, /* vt SYMBOL FOR VERTICAL TABULATION */
{ 0x09ea, 0x2518 }, /* lowrightcorner BOX DRAWINGS LIGHT UP AND LEFT */
{ 0x09eb, 0x2510 }, /* uprightcorner BOX DRAWINGS LIGHT DOWN AND LEFT */
{ 0x09ec, 0x250c }, /* upleftcorner BOX DRAWINGS LIGHT DOWN AND RIGHT */
{ 0x09ed, 0x2514 }, /* lowleftcorner BOX DRAWINGS LIGHT UP AND RIGHT */
{ 0x09ee, 0x253c }, /* crossinglines BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL */
{ 0x09ef, 0x23ba }, /* horizlinescan1 HORIZONTAL SCAN LINE-1 (Unicode 3.2 draft) */
{ 0x09f0, 0x23bb }, /* horizlinescan3 HORIZONTAL SCAN LINE-3 (Unicode 3.2 draft) */
{ 0x09f1, 0x2500 }, /* horizlinescan5 BOX DRAWINGS LIGHT HORIZONTAL */
{ 0x09f2, 0x23bc }, /* horizlinescan7 HORIZONTAL SCAN LINE-7 (Unicode 3.2 draft) */
{ 0x09f3, 0x23bd }, /* horizlinescan9 HORIZONTAL SCAN LINE-9 (Unicode 3.2 draft) */
{ 0x09f4, 0x251c }, /* leftt BOX DRAWINGS LIGHT VERTICAL AND RIGHT */
{ 0x09f5, 0x2524 }, /* rightt BOX DRAWINGS LIGHT VERTICAL AND LEFT */
{ 0x09f6, 0x2534 }, /* bott BOX DRAWINGS LIGHT UP AND HORIZONTAL */
{ 0x09f7, 0x252c }, /* topt BOX DRAWINGS LIGHT DOWN AND HORIZONTAL */
{ 0x09f8, 0x2502 }, /* vertbar BOX DRAWINGS LIGHT VERTICAL */
{ 0x0aa1, 0x2003 }, /* emspace EM SPACE */
{ 0x0aa2, 0x2002 }, /* enspace EN SPACE */
{ 0x0aa3, 0x2004 }, /* em3space THREE-PER-EM SPACE */
{ 0x0aa4, 0x2005 }, /* em4space FOUR-PER-EM SPACE */
{ 0x0aa5, 0x2007 }, /* digitspace FIGURE SPACE */
{ 0x0aa6, 0x2008 }, /* punctspace PUNCTUATION SPACE */
{ 0x0aa7, 0x2009 }, /* thinspace THIN SPACE */
{ 0x0aa8, 0x200a }, /* hairspace HAIR SPACE */
{ 0x0aa9, 0x2014 }, /* emdash EM DASH */
{ 0x0aaa, 0x2013 }, /* endash EN DASH */
/* 0x0aac signifblank ??? */
{ 0x0aae, 0x2026 }, /* ellipsis HORIZONTAL ELLIPSIS */
{ 0x0aaf, 0x2025 }, /* doubbaselinedot TWO DOT LEADER */
{ 0x0ab0, 0x2153 }, /* onethird VULGAR FRACTION ONE THIRD */
{ 0x0ab1, 0x2154 }, /* twothirds VULGAR FRACTION TWO THIRDS */
{ 0x0ab2, 0x2155 }, /* onefifth VULGAR FRACTION ONE FIFTH */
{ 0x0ab3, 0x2156 }, /* twofifths VULGAR FRACTION TWO FIFTHS */
{ 0x0ab4, 0x2157 }, /* threefifths VULGAR FRACTION THREE FIFTHS */
{ 0x0ab5, 0x2158 }, /* fourfifths VULGAR FRACTION FOUR FIFTHS */
{ 0x0ab6, 0x2159 }, /* onesixth VULGAR FRACTION ONE SIXTH */
{ 0x0ab7, 0x215a }, /* fivesixths VULGAR FRACTION FIVE SIXTHS */
{ 0x0ab8, 0x2105 }, /* careof CARE OF */
{ 0x0abb, 0x2012 }, /* figdash FIGURE DASH */
{ 0x0abc, 0x2329 }, /* leftanglebracket LEFT-POINTING ANGLE BRACKET */
/* 0x0abd decimalpoint ??? */
{ 0x0abe, 0x232a }, /* rightanglebracket RIGHT-POINTING ANGLE BRACKET */
/* 0x0abf marker ??? */
{ 0x0ac3, 0x215b }, /* oneeighth VULGAR FRACTION ONE EIGHTH */
{ 0x0ac4, 0x215c }, /* threeeighths VULGAR FRACTION THREE EIGHTHS */
{ 0x0ac5, 0x215d }, /* fiveeighths VULGAR FRACTION FIVE EIGHTHS */
{ 0x0ac6, 0x215e }, /* seveneighths VULGAR FRACTION SEVEN EIGHTHS */
{ 0x0ac9, 0x2122 }, /* trademark TRADE MARK SIGN */
{ 0x0aca, 0x2613 }, /* signaturemark SALTIRE */
/* 0x0acb trademarkincircle ??? */
{ 0x0acc, 0x25c1 }, /* leftopentriangle WHITE LEFT-POINTING TRIANGLE */
{ 0x0acd, 0x25b7 }, /* rightopentriangle WHITE RIGHT-POINTING TRIANGLE */
{ 0x0ace, 0x25cb }, /* emopencircle WHITE CIRCLE */
{ 0x0acf, 0x25af }, /* emopenrectangle WHITE VERTICAL RECTANGLE */
{ 0x0ad0, 0x2018 }, /* leftsinglequotemark LEFT SINGLE QUOTATION MARK */
{ 0x0ad1, 0x2019 }, /* rightsinglequotemark RIGHT SINGLE QUOTATION MARK */
{ 0x0ad2, 0x201c }, /* leftdoublequotemark LEFT DOUBLE QUOTATION MARK */
{ 0x0ad3, 0x201d }, /* rightdoublequotemark RIGHT DOUBLE QUOTATION MARK */
{ 0x0ad4, 0x211e }, /* prescription PRESCRIPTION TAKE */
{ 0x0ad6, 0x2032 }, /* minutes PRIME */
{ 0x0ad7, 0x2033 }, /* seconds DOUBLE PRIME */
{ 0x0ad9, 0x271d }, /* latincross LATIN CROSS */
/* 0x0ada hexagram ??? */
{ 0x0adb, 0x25ac }, /* filledrectbullet BLACK RECTANGLE */
{ 0x0adc, 0x25c0 }, /* filledlefttribullet BLACK LEFT-POINTING TRIANGLE */
{ 0x0add, 0x25b6 }, /* filledrighttribullet BLACK RIGHT-POINTING TRIANGLE */
{ 0x0ade, 0x25cf }, /* emfilledcircle BLACK CIRCLE */
{ 0x0adf, 0x25ae }, /* emfilledrect BLACK VERTICAL RECTANGLE */
{ 0x0ae0, 0x25e6 }, /* enopencircbullet WHITE BULLET */
{ 0x0ae1, 0x25ab }, /* enopensquarebullet WHITE SMALL SQUARE */
{ 0x0ae2, 0x25ad }, /* openrectbullet WHITE RECTANGLE */
{ 0x0ae3, 0x25b3 }, /* opentribulletup WHITE UP-POINTING TRIANGLE */
{ 0x0ae4, 0x25bd }, /* opentribulletdown WHITE DOWN-POINTING TRIANGLE */
{ 0x0ae5, 0x2606 }, /* openstar WHITE STAR */
{ 0x0ae6, 0x2022 }, /* enfilledcircbullet BULLET */
{ 0x0ae7, 0x25aa }, /* enfilledsqbullet BLACK SMALL SQUARE */
{ 0x0ae8, 0x25b2 }, /* filledtribulletup BLACK UP-POINTING TRIANGLE */
{ 0x0ae9, 0x25bc }, /* filledtribulletdown BLACK DOWN-POINTING TRIANGLE */
{ 0x0aea, 0x261c }, /* leftpointer WHITE LEFT POINTING INDEX */
{ 0x0aeb, 0x261e }, /* rightpointer WHITE RIGHT POINTING INDEX */
{ 0x0aec, 0x2663 }, /* club BLACK CLUB SUIT */
{ 0x0aed, 0x2666 }, /* diamond BLACK DIAMOND SUIT */
{ 0x0aee, 0x2665 }, /* heart BLACK HEART SUIT */
{ 0x0af0, 0x2720 }, /* maltesecross MALTESE CROSS */
{ 0x0af1, 0x2020 }, /* dagger DAGGER */
{ 0x0af2, 0x2021 }, /* doubledagger DOUBLE DAGGER */
{ 0x0af3, 0x2713 }, /* checkmark CHECK MARK */
{ 0x0af4, 0x2717 }, /* ballotcross BALLOT X */
{ 0x0af5, 0x266f }, /* musicalsharp MUSIC SHARP SIGN */
{ 0x0af6, 0x266d }, /* musicalflat MUSIC FLAT SIGN */
{ 0x0af7, 0x2642 }, /* malesymbol MALE SIGN */
{ 0x0af8, 0x2640 }, /* femalesymbol FEMALE SIGN */
{ 0x0af9, 0x260e }, /* telephone BLACK TELEPHONE */
{ 0x0afa, 0x2315 }, /* telephonerecorder TELEPHONE RECORDER */
{ 0x0afb, 0x2117 }, /* phonographcopyright SOUND RECORDING COPYRIGHT */
{ 0x0afc, 0x2038 }, /* caret CARET */
{ 0x0afd, 0x201a }, /* singlelowquotemark SINGLE LOW-9 QUOTATION MARK */
{ 0x0afe, 0x201e }, /* doublelowquotemark DOUBLE LOW-9 QUOTATION MARK */
/* 0x0aff cursor ??? */
{ 0x0ba3, 0x003c }, /* leftcaret LESS-THAN SIGN */
{ 0x0ba6, 0x003e }, /* rightcaret GREATER-THAN SIGN */
{ 0x0ba8, 0x2228 }, /* downcaret LOGICAL OR */
{ 0x0ba9, 0x2227 }, /* upcaret LOGICAL AND */
{ 0x0bc0, 0x00af }, /* overbar MACRON */
{ 0x0bc2, 0x22a5 }, /* downtack UP TACK */
{ 0x0bc3, 0x2229 }, /* upshoe INTERSECTION */
{ 0x0bc4, 0x230a }, /* downstile LEFT FLOOR */
{ 0x0bc6, 0x005f }, /* underbar LOW LINE */
{ 0x0bca, 0x2218 }, /* jot RING OPERATOR */
{ 0x0bcc, 0x2395 }, /* quad APL FUNCTIONAL SYMBOL QUAD */
{ 0x0bce, 0x22a4 }, /* uptack DOWN TACK */
{ 0x0bcf, 0x25cb }, /* circle WHITE CIRCLE */
{ 0x0bd3, 0x2308 }, /* upstile LEFT CEILING */
{ 0x0bd6, 0x222a }, /* downshoe UNION */
{ 0x0bd8, 0x2283 }, /* rightshoe SUPERSET OF */
{ 0x0bda, 0x2282 }, /* leftshoe SUBSET OF */
{ 0x0bdc, 0x22a2 }, /* lefttack RIGHT TACK */
{ 0x0bfc, 0x22a3 }, /* righttack LEFT TACK */
{ 0x0cdf, 0x2017 }, /* hebrew_doublelowline DOUBLE LOW LINE */
{ 0x0ce0, 0x05d0 }, /* hebrew_aleph HEBREW LETTER ALEF */
{ 0x0ce1, 0x05d1 }, /* hebrew_bet HEBREW LETTER BET */
{ 0x0ce2, 0x05d2 }, /* hebrew_gimel HEBREW LETTER GIMEL */
{ 0x0ce3, 0x05d3 }, /* hebrew_dalet HEBREW LETTER DALET */
{ 0x0ce4, 0x05d4 }, /* hebrew_he HEBREW LETTER HE */
{ 0x0ce5, 0x05d5 }, /* hebrew_waw HEBREW LETTER VAV */
{ 0x0ce6, 0x05d6 }, /* hebrew_zain HEBREW LETTER ZAYIN */
{ 0x0ce7, 0x05d7 }, /* hebrew_chet HEBREW LETTER HET */
{ 0x0ce8, 0x05d8 }, /* hebrew_tet HEBREW LETTER TET */
{ 0x0ce9, 0x05d9 }, /* hebrew_yod HEBREW LETTER YOD */
{ 0x0cea, 0x05da }, /* hebrew_finalkaph HEBREW LETTER FINAL KAF */
{ 0x0ceb, 0x05db }, /* hebrew_kaph HEBREW LETTER KAF */
{ 0x0cec, 0x05dc }, /* hebrew_lamed HEBREW LETTER LAMED */
{ 0x0ced, 0x05dd }, /* hebrew_finalmem HEBREW LETTER FINAL MEM */
{ 0x0cee, 0x05de }, /* hebrew_mem HEBREW LETTER MEM */
{ 0x0cef, 0x05df }, /* hebrew_finalnun HEBREW LETTER FINAL NUN */
{ 0x0cf0, 0x05e0 }, /* hebrew_nun HEBREW LETTER NUN */
{ 0x0cf1, 0x05e1 }, /* hebrew_samech HEBREW LETTER SAMEKH */
{ 0x0cf2, 0x05e2 }, /* hebrew_ayin HEBREW LETTER AYIN */
{ 0x0cf3, 0x05e3 }, /* hebrew_finalpe HEBREW LETTER FINAL PE */
{ 0x0cf4, 0x05e4 }, /* hebrew_pe HEBREW LETTER PE */
{ 0x0cf5, 0x05e5 }, /* hebrew_finalzade HEBREW LETTER FINAL TSADI */
{ 0x0cf6, 0x05e6 }, /* hebrew_zade HEBREW LETTER TSADI */
{ 0x0cf7, 0x05e7 }, /* hebrew_qoph HEBREW LETTER QOF */
{ 0x0cf8, 0x05e8 }, /* hebrew_resh HEBREW LETTER RESH */
{ 0x0cf9, 0x05e9 }, /* hebrew_shin HEBREW LETTER SHIN */
{ 0x0cfa, 0x05ea }, /* hebrew_taw HEBREW LETTER TAV */
{ 0x0da1, 0x0e01 }, /* Thai_kokai THAI CHARACTER KO KAI */
{ 0x0da2, 0x0e02 }, /* Thai_khokhai THAI CHARACTER KHO KHAI */
{ 0x0da3, 0x0e03 }, /* Thai_khokhuat THAI CHARACTER KHO KHUAT */
{ 0x0da4, 0x0e04 }, /* Thai_khokhwai THAI CHARACTER KHO KHWAI */
{ 0x0da5, 0x0e05 }, /* Thai_khokhon THAI CHARACTER KHO KHON */
{ 0x0da6, 0x0e06 }, /* Thai_khorakhang THAI CHARACTER KHO RAKHANG */
{ 0x0da7, 0x0e07 }, /* Thai_ngongu THAI CHARACTER NGO NGU */
{ 0x0da8, 0x0e08 }, /* Thai_chochan THAI CHARACTER CHO CHAN */
{ 0x0da9, 0x0e09 }, /* Thai_choching THAI CHARACTER CHO CHING */
{ 0x0daa, 0x0e0a }, /* Thai_chochang THAI CHARACTER CHO CHANG */
{ 0x0dab, 0x0e0b }, /* Thai_soso THAI CHARACTER SO SO */
{ 0x0dac, 0x0e0c }, /* Thai_chochoe THAI CHARACTER CHO CHOE */
{ 0x0dad, 0x0e0d }, /* Thai_yoying THAI CHARACTER YO YING */
{ 0x0dae, 0x0e0e }, /* Thai_dochada THAI CHARACTER DO CHADA */
{ 0x0daf, 0x0e0f }, /* Thai_topatak THAI CHARACTER TO PATAK */
{ 0x0db0, 0x0e10 }, /* Thai_thothan THAI CHARACTER THO THAN */
{ 0x0db1, 0x0e11 }, /* Thai_thonangmontho THAI CHARACTER THO NANGMONTHO */
{ 0x0db2, 0x0e12 }, /* Thai_thophuthao THAI CHARACTER THO PHUTHAO */
{ 0x0db3, 0x0e13 }, /* Thai_nonen THAI CHARACTER NO NEN */
{ 0x0db4, 0x0e14 }, /* Thai_dodek THAI CHARACTER DO DEK */
{ 0x0db5, 0x0e15 }, /* Thai_totao THAI CHARACTER TO TAO */
{ 0x0db6, 0x0e16 }, /* Thai_thothung THAI CHARACTER THO THUNG */
{ 0x0db7, 0x0e17 }, /* Thai_thothahan THAI CHARACTER THO THAHAN */
{ 0x0db8, 0x0e18 }, /* Thai_thothong THAI CHARACTER THO THONG */
{ 0x0db9, 0x0e19 }, /* Thai_nonu THAI CHARACTER NO NU */
{ 0x0dba, 0x0e1a }, /* Thai_bobaimai THAI CHARACTER BO BAIMAI */
{ 0x0dbb, 0x0e1b }, /* Thai_popla THAI CHARACTER PO PLA */
{ 0x0dbc, 0x0e1c }, /* Thai_phophung THAI CHARACTER PHO PHUNG */
{ 0x0dbd, 0x0e1d }, /* Thai_fofa THAI CHARACTER FO FA */
{ 0x0dbe, 0x0e1e }, /* Thai_phophan THAI CHARACTER PHO PHAN */
{ 0x0dbf, 0x0e1f }, /* Thai_fofan THAI CHARACTER FO FAN */
{ 0x0dc0, 0x0e20 }, /* Thai_phosamphao THAI CHARACTER PHO SAMPHAO */
{ 0x0dc1, 0x0e21 }, /* Thai_moma THAI CHARACTER MO MA */
{ 0x0dc2, 0x0e22 }, /* Thai_yoyak THAI CHARACTER YO YAK */
{ 0x0dc3, 0x0e23 }, /* Thai_rorua THAI CHARACTER RO RUA */
{ 0x0dc4, 0x0e24 }, /* Thai_ru THAI CHARACTER RU */
{ 0x0dc5, 0x0e25 }, /* Thai_loling THAI CHARACTER LO LING */
{ 0x0dc6, 0x0e26 }, /* Thai_lu THAI CHARACTER LU */
{ 0x0dc7, 0x0e27 }, /* Thai_wowaen THAI CHARACTER WO WAEN */
{ 0x0dc8, 0x0e28 }, /* Thai_sosala THAI CHARACTER SO SALA */
{ 0x0dc9, 0x0e29 }, /* Thai_sorusi THAI CHARACTER SO RUSI */
{ 0x0dca, 0x0e2a }, /* Thai_sosua THAI CHARACTER SO SUA */
{ 0x0dcb, 0x0e2b }, /* Thai_hohip THAI CHARACTER HO HIP */
{ 0x0dcc, 0x0e2c }, /* Thai_lochula THAI CHARACTER LO CHULA */
{ 0x0dcd, 0x0e2d }, /* Thai_oang THAI CHARACTER O ANG */
{ 0x0dce, 0x0e2e }, /* Thai_honokhuk THAI CHARACTER HO NOKHUK */
{ 0x0dcf, 0x0e2f }, /* Thai_paiyannoi THAI CHARACTER PAIYANNOI */
{ 0x0dd0, 0x0e30 }, /* Thai_saraa THAI CHARACTER SARA A */
{ 0x0dd1, 0x0e31 }, /* Thai_maihanakat THAI CHARACTER MAI HAN-AKAT */
{ 0x0dd2, 0x0e32 }, /* Thai_saraaa THAI CHARACTER SARA AA */
{ 0x0dd3, 0x0e33 }, /* Thai_saraam THAI CHARACTER SARA AM */
{ 0x0dd4, 0x0e34 }, /* Thai_sarai THAI CHARACTER SARA I */
{ 0x0dd5, 0x0e35 }, /* Thai_saraii THAI CHARACTER SARA II */
{ 0x0dd6, 0x0e36 }, /* Thai_saraue THAI CHARACTER SARA UE */
{ 0x0dd7, 0x0e37 }, /* Thai_sarauee THAI CHARACTER SARA UEE */
{ 0x0dd8, 0x0e38 }, /* Thai_sarau THAI CHARACTER SARA U */
{ 0x0dd9, 0x0e39 }, /* Thai_sarauu THAI CHARACTER SARA UU */
{ 0x0dda, 0x0e3a }, /* Thai_phinthu THAI CHARACTER PHINTHU */
/* 0x0dde Thai_maihanakat_maitho ??? */
{ 0x0ddf, 0x0e3f }, /* Thai_baht THAI CURRENCY SYMBOL BAHT */
{ 0x0de0, 0x0e40 }, /* Thai_sarae THAI CHARACTER SARA E */
{ 0x0de1, 0x0e41 }, /* Thai_saraae THAI CHARACTER SARA AE */
{ 0x0de2, 0x0e42 }, /* Thai_sarao THAI CHARACTER SARA O */
{ 0x0de3, 0x0e43 }, /* Thai_saraaimaimuan THAI CHARACTER SARA AI MAIMUAN */
{ 0x0de4, 0x0e44 }, /* Thai_saraaimaimalai THAI CHARACTER SARA AI MAIMALAI */
{ 0x0de5, 0x0e45 }, /* Thai_lakkhangyao THAI CHARACTER LAKKHANGYAO */
{ 0x0de6, 0x0e46 }, /* Thai_maiyamok THAI CHARACTER MAIYAMOK */
{ 0x0de7, 0x0e47 }, /* Thai_maitaikhu THAI CHARACTER MAITAIKHU */
{ 0x0de8, 0x0e48 }, /* Thai_maiek THAI CHARACTER MAI EK */
{ 0x0de9, 0x0e49 }, /* Thai_maitho THAI CHARACTER MAI THO */
{ 0x0dea, 0x0e4a }, /* Thai_maitri THAI CHARACTER MAI TRI */
{ 0x0deb, 0x0e4b }, /* Thai_maichattawa THAI CHARACTER MAI CHATTAWA */
{ 0x0dec, 0x0e4c }, /* Thai_thanthakhat THAI CHARACTER THANTHAKHAT */
{ 0x0ded, 0x0e4d }, /* Thai_nikhahit THAI CHARACTER NIKHAHIT */
{ 0x0df0, 0x0e50 }, /* Thai_leksun THAI DIGIT ZERO */
{ 0x0df1, 0x0e51 }, /* Thai_leknung THAI DIGIT ONE */
{ 0x0df2, 0x0e52 }, /* Thai_leksong THAI DIGIT TWO */
{ 0x0df3, 0x0e53 }, /* Thai_leksam THAI DIGIT THREE */
{ 0x0df4, 0x0e54 }, /* Thai_leksi THAI DIGIT FOUR */
{ 0x0df5, 0x0e55 }, /* Thai_lekha THAI DIGIT FIVE */
{ 0x0df6, 0x0e56 }, /* Thai_lekhok THAI DIGIT SIX */
{ 0x0df7, 0x0e57 }, /* Thai_lekchet THAI DIGIT SEVEN */
{ 0x0df8, 0x0e58 }, /* Thai_lekpaet THAI DIGIT EIGHT */
{ 0x0df9, 0x0e59 }, /* Thai_lekkao THAI DIGIT NINE */
{ 0x0ea1, 0x3131 }, /* Hangul_Kiyeog HANGUL LETTER KIYEOK */
{ 0x0ea2, 0x3132 }, /* Hangul_SsangKiyeog HANGUL LETTER SSANGKIYEOK */
{ 0x0ea3, 0x3133 }, /* Hangul_KiyeogSios HANGUL LETTER KIYEOK-SIOS */
{ 0x0ea4, 0x3134 }, /* Hangul_Nieun HANGUL LETTER NIEUN */
{ 0x0ea5, 0x3135 }, /* Hangul_NieunJieuj HANGUL LETTER NIEUN-CIEUC */
{ 0x0ea6, 0x3136 }, /* Hangul_NieunHieuh HANGUL LETTER NIEUN-HIEUH */
{ 0x0ea7, 0x3137 }, /* Hangul_Dikeud HANGUL LETTER TIKEUT */
{ 0x0ea8, 0x3138 }, /* Hangul_SsangDikeud HANGUL LETTER SSANGTIKEUT */
{ 0x0ea9, 0x3139 }, /* Hangul_Rieul HANGUL LETTER RIEUL */
{ 0x0eaa, 0x313a }, /* Hangul_RieulKiyeog HANGUL LETTER RIEUL-KIYEOK */
{ 0x0eab, 0x313b }, /* Hangul_RieulMieum HANGUL LETTER RIEUL-MIEUM */
{ 0x0eac, 0x313c }, /* Hangul_RieulPieub HANGUL LETTER RIEUL-PIEUP */
{ 0x0ead, 0x313d }, /* Hangul_RieulSios HANGUL LETTER RIEUL-SIOS */
{ 0x0eae, 0x313e }, /* Hangul_RieulTieut HANGUL LETTER RIEUL-THIEUTH */
{ 0x0eaf, 0x313f }, /* Hangul_RieulPhieuf HANGUL LETTER RIEUL-PHIEUPH */
{ 0x0eb0, 0x3140 }, /* Hangul_RieulHieuh HANGUL LETTER RIEUL-HIEUH */
{ 0x0eb1, 0x3141 }, /* Hangul_Mieum HANGUL LETTER MIEUM */
{ 0x0eb2, 0x3142 }, /* Hangul_Pieub HANGUL LETTER PIEUP */
{ 0x0eb3, 0x3143 }, /* Hangul_SsangPieub HANGUL LETTER SSANGPIEUP */
{ 0x0eb4, 0x3144 }, /* Hangul_PieubSios HANGUL LETTER PIEUP-SIOS */
{ 0x0eb5, 0x3145 }, /* Hangul_Sios HANGUL LETTER SIOS */
{ 0x0eb6, 0x3146 }, /* Hangul_SsangSios HANGUL LETTER SSANGSIOS */
{ 0x0eb7, 0x3147 }, /* Hangul_Ieung HANGUL LETTER IEUNG */
{ 0x0eb8, 0x3148 }, /* Hangul_Jieuj HANGUL LETTER CIEUC */
{ 0x0eb9, 0x3149 }, /* Hangul_SsangJieuj HANGUL LETTER SSANGCIEUC */
{ 0x0eba, 0x314a }, /* Hangul_Cieuc HANGUL LETTER CHIEUCH */
{ 0x0ebb, 0x314b }, /* Hangul_Khieuq HANGUL LETTER KHIEUKH */
{ 0x0ebc, 0x314c }, /* Hangul_Tieut HANGUL LETTER THIEUTH */
{ 0x0ebd, 0x314d }, /* Hangul_Phieuf HANGUL LETTER PHIEUPH */
{ 0x0ebe, 0x314e }, /* Hangul_Hieuh HANGUL LETTER HIEUH */
{ 0x0ebf, 0x314f }, /* Hangul_A HANGUL LETTER A */
{ 0x0ec0, 0x3150 }, /* Hangul_AE HANGUL LETTER AE */
{ 0x0ec1, 0x3151 }, /* Hangul_YA HANGUL LETTER YA */
{ 0x0ec2, 0x3152 }, /* Hangul_YAE HANGUL LETTER YAE */
{ 0x0ec3, 0x3153 }, /* Hangul_EO HANGUL LETTER EO */
{ 0x0ec4, 0x3154 }, /* Hangul_E HANGUL LETTER E */
{ 0x0ec5, 0x3155 }, /* Hangul_YEO HANGUL LETTER YEO */
{ 0x0ec6, 0x3156 }, /* Hangul_YE HANGUL LETTER YE */
{ 0x0ec7, 0x3157 }, /* Hangul_O HANGUL LETTER O */
{ 0x0ec8, 0x3158 }, /* Hangul_WA HANGUL LETTER WA */
{ 0x0ec9, 0x3159 }, /* Hangul_WAE HANGUL LETTER WAE */
{ 0x0eca, 0x315a }, /* Hangul_OE HANGUL LETTER OE */
{ 0x0ecb, 0x315b }, /* Hangul_YO HANGUL LETTER YO */
{ 0x0ecc, 0x315c }, /* Hangul_U HANGUL LETTER U */
{ 0x0ecd, 0x315d }, /* Hangul_WEO HANGUL LETTER WEO */
{ 0x0ece, 0x315e }, /* Hangul_WE HANGUL LETTER WE */
{ 0x0ecf, 0x315f }, /* Hangul_WI HANGUL LETTER WI */
{ 0x0ed0, 0x3160 }, /* Hangul_YU HANGUL LETTER YU */
{ 0x0ed1, 0x3161 }, /* Hangul_EU HANGUL LETTER EU */
{ 0x0ed2, 0x3162 }, /* Hangul_YI HANGUL LETTER YI */
{ 0x0ed3, 0x3163 }, /* Hangul_I HANGUL LETTER I */
{ 0x0ed4, 0x11a8 }, /* Hangul_J_Kiyeog HANGUL JONGSEONG KIYEOK */
{ 0x0ed5, 0x11a9 }, /* Hangul_J_SsangKiyeog HANGUL JONGSEONG SSANGKIYEOK */
{ 0x0ed6, 0x11aa }, /* Hangul_J_KiyeogSios HANGUL JONGSEONG KIYEOK-SIOS */
{ 0x0ed7, 0x11ab }, /* Hangul_J_Nieun HANGUL JONGSEONG NIEUN */
{ 0x0ed8, 0x11ac }, /* Hangul_J_NieunJieuj HANGUL JONGSEONG NIEUN-CIEUC */
{ 0x0ed9, 0x11ad }, /* Hangul_J_NieunHieuh HANGUL JONGSEONG NIEUN-HIEUH */
{ 0x0eda, 0x11ae }, /* Hangul_J_Dikeud HANGUL JONGSEONG TIKEUT */
{ 0x0edb, 0x11af }, /* Hangul_J_Rieul HANGUL JONGSEONG RIEUL */
{ 0x0edc, 0x11b0 }, /* Hangul_J_RieulKiyeog HANGUL JONGSEONG RIEUL-KIYEOK */
{ 0x0edd, 0x11b1 }, /* Hangul_J_RieulMieum HANGUL JONGSEONG RIEUL-MIEUM */
{ 0x0ede, 0x11b2 }, /* Hangul_J_RieulPieub HANGUL JONGSEONG RIEUL-PIEUP */
{ 0x0edf, 0x11b3 }, /* Hangul_J_RieulSios HANGUL JONGSEONG RIEUL-SIOS */
{ 0x0ee0, 0x11b4 }, /* Hangul_J_RieulTieut HANGUL JONGSEONG RIEUL-THIEUTH */
{ 0x0ee1, 0x11b5 }, /* Hangul_J_RieulPhieuf HANGUL JONGSEONG RIEUL-PHIEUPH */
{ 0x0ee2, 0x11b6 }, /* Hangul_J_RieulHieuh HANGUL JONGSEONG RIEUL-HIEUH */
{ 0x0ee3, 0x11b7 }, /* Hangul_J_Mieum HANGUL JONGSEONG MIEUM */
{ 0x0ee4, 0x11b8 }, /* Hangul_J_Pieub HANGUL JONGSEONG PIEUP */
{ 0x0ee5, 0x11b9 }, /* Hangul_J_PieubSios HANGUL JONGSEONG PIEUP-SIOS */
{ 0x0ee6, 0x11ba }, /* Hangul_J_Sios HANGUL JONGSEONG SIOS */
{ 0x0ee7, 0x11bb }, /* Hangul_J_SsangSios HANGUL JONGSEONG SSANGSIOS */
{ 0x0ee8, 0x11bc }, /* Hangul_J_Ieung HANGUL JONGSEONG IEUNG */
{ 0x0ee9, 0x11bd }, /* Hangul_J_Jieuj HANGUL JONGSEONG CIEUC */
{ 0x0eea, 0x11be }, /* Hangul_J_Cieuc HANGUL JONGSEONG CHIEUCH */
{ 0x0eeb, 0x11bf }, /* Hangul_J_Khieuq HANGUL JONGSEONG KHIEUKH */
{ 0x0eec, 0x11c0 }, /* Hangul_J_Tieut HANGUL JONGSEONG THIEUTH */
{ 0x0eed, 0x11c1 }, /* Hangul_J_Phieuf HANGUL JONGSEONG PHIEUPH */
{ 0x0eee, 0x11c2 }, /* Hangul_J_Hieuh HANGUL JONGSEONG HIEUH */
{ 0x0eef, 0x316d }, /* Hangul_RieulYeorinHieuh HANGUL LETTER RIEUL-YEORINHIEUH */
{ 0x0ef0, 0x3171 }, /* Hangul_SunkyeongeumMieum HANGUL LETTER KAPYEOUNMIEUM */
{ 0x0ef1, 0x3178 }, /* Hangul_SunkyeongeumPieub HANGUL LETTER KAPYEOUNPIEUP */
{ 0x0ef2, 0x317f }, /* Hangul_PanSios HANGUL LETTER PANSIOS */
{ 0x0ef3, 0x3181 }, /* Hangul_KkogjiDalrinIeung HANGUL LETTER YESIEUNG */
{ 0x0ef4, 0x3184 }, /* Hangul_SunkyeongeumPhieuf HANGUL LETTER KAPYEOUNPHIEUPH */
{ 0x0ef5, 0x3186 }, /* Hangul_YeorinHieuh HANGUL LETTER YEORINHIEUH */
{ 0x0ef6, 0x318d }, /* Hangul_AraeA HANGUL LETTER ARAEA */
{ 0x0ef7, 0x318e }, /* Hangul_AraeAE HANGUL LETTER ARAEAE */
{ 0x0ef8, 0x11eb }, /* Hangul_J_PanSios HANGUL JONGSEONG PANSIOS */
{ 0x0ef9, 0x11f0 }, /* Hangul_J_KkogjiDalrinIeung HANGUL JONGSEONG YESIEUNG */
{ 0x0efa, 0x11f9 }, /* Hangul_J_YeorinHieuh HANGUL JONGSEONG YEORINHIEUH */
{ 0x0eff, 0x20a9 }, /* Korean_Won WON SIGN */
{ 0x13a4, 0x20ac }, /* Euro EURO SIGN */
{ 0x13bc, 0x0152 }, /* OE LATIN CAPITAL LIGATURE OE */
{ 0x13bd, 0x0153 }, /* oe LATIN SMALL LIGATURE OE */
{ 0x13be, 0x0178 }, /* Ydiaeresis LATIN CAPITAL LETTER Y WITH DIAERESIS */
{ 0x20ac, 0x20ac }, /* EuroSign EURO SIGN */
});
private static Map<Integer, Integer> toMap(int[][] keys) {
Map<Integer, Integer> keyMap = new HashMap<Integer, Integer>();
for (int[] km: keys) {
keyMap.put(km[1], km[0]);
}
return keyMap;
}
public static int unicode2keysym(int ch) {
if (ch >= 32 && ch <= 126 || ch >= 160 && ch <= 255)
return ch;
Integer converted = keyMap.get(ch);
return converted != null ?
converted :
// No variants has been found for the unicode symbol then pass as unicode
// with the special flag.
// Note this method is valid only for 0x100-0x10ffff unicode value range.
ch | 0x01000000;
}
}

Some files were not shown because too many files have changed in this diff Show More