Compare commits
173 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cba80b6c0b | ||
|
|
850faa25dd | ||
|
|
a108b2bd6b | ||
|
|
b95823d7a8 | ||
|
|
382da7e8f7 | ||
|
|
ba9c118b50 | ||
|
|
531c32f3c9 | ||
|
|
db2f50c76e | ||
|
|
784affe39c | ||
|
|
b486d29d23 | ||
|
|
332f1104a3 | ||
|
|
5a70be1523 | ||
|
|
619552ec5c | ||
|
|
70580abd50 | ||
|
|
f191c35851 | ||
|
|
bd7ed28981 | ||
|
|
b68bd107c1 | ||
|
|
5075273362 | ||
|
|
04268f4c20 | ||
|
|
6f24628fd2 | ||
|
|
debbe44809 | ||
|
|
b2ff0e4051 | ||
|
|
9e7029b76a | ||
|
|
51370799c7 | ||
|
|
0910844896 | ||
|
|
f33ebf810f | ||
|
|
930029b5d2 | ||
|
|
33def928cf | ||
|
|
fc04a93990 | ||
|
|
8d302aa9fe | ||
|
|
2224d917a3 | ||
|
|
664ec43f94 | ||
|
|
6cf742460c | ||
|
|
72981fb981 | ||
|
|
2c5534e2c1 | ||
|
|
5b32540635 | ||
|
|
db3ff7b24a | ||
|
|
5f71e3e73a | ||
|
|
3e04ea4cb0 | ||
|
|
0af823607a | ||
|
|
4d9c0c315e | ||
|
|
9c32935ca2 | ||
|
|
669c336e2f | ||
|
|
35842cf4a6 | ||
|
|
b086270a5a | ||
|
|
f39f06a540 | ||
|
|
58440bc88d | ||
|
|
9f438e2912 | ||
|
|
f794bfcadc | ||
|
|
b6d7831646 | ||
|
|
d212198e30 | ||
|
|
c2843897ac | ||
|
|
c4c4912a7e | ||
|
|
38a3319ca2 | ||
|
|
7e13b8aa2e | ||
|
|
6dca19ae00 | ||
|
|
2659c06c5d | ||
|
|
9b7c7102b2 | ||
|
|
1819087ca0 | ||
|
|
366a61f052 | ||
|
|
6e224cabcf | ||
|
|
8c8fa96133 | ||
|
|
537f2ed97e | ||
|
|
0e23315c41 | ||
|
|
2cde986419 | ||
|
|
d28939810c | ||
|
|
93724b7aa6 | ||
|
|
5d06f040e8 | ||
|
|
ed9afa082a | ||
|
|
9703bd31ad | ||
|
|
9749f25eba | ||
|
|
453b838b24 | ||
|
|
9fdf2a49fd | ||
|
|
3270506bff | ||
|
|
a240f4cf45 | ||
|
|
4647beb0d2 | ||
|
|
d92e806461 | ||
|
|
b75cf0bb84 | ||
|
|
f928efed4e | ||
|
|
36db64d585 | ||
|
|
b8f0430699 | ||
|
|
90e6260d5e | ||
|
|
566d656c16 | ||
|
|
b729085d52 | ||
|
|
a94bcb02f1 | ||
|
|
e28be01dc2 | ||
|
|
7f7c1efac1 | ||
|
|
76df44e6bb | ||
|
|
fd13f3f98d | ||
|
|
269c3cafb0 | ||
|
|
662b5cace6 | ||
|
|
3c01b09fff | ||
|
|
1ce2db9929 | ||
|
|
c987ff718a | ||
|
|
9e295b105c | ||
|
|
37b3a9f8db | ||
|
|
490853e427 | ||
|
|
9ebedc4740 | ||
|
|
82730d9e08 | ||
|
|
3d2756f376 | ||
|
|
6db51bdec0 | ||
|
|
c238c8bddb | ||
|
|
60e1556871 | ||
|
|
e47bd31681 | ||
|
|
951df6b4e2 | ||
|
|
fcd3bc1133 | ||
|
|
f4db9ff212 | ||
|
|
87841886d4 | ||
|
|
cf883f5f05 | ||
|
|
677d75e173 | ||
|
|
cdccc2c433 | ||
|
|
070436a6ed | ||
|
|
69ded4b33e | ||
|
|
e5a8c0eb4a | ||
|
|
29815fb3f0 | ||
|
|
e930ac08aa | ||
|
|
3b969a0077 | ||
|
|
ae717d8f5f | ||
|
|
6c55c3c1be | ||
|
|
c0a0822843 | ||
|
|
08b08d1c47 | ||
|
|
c76cec186a | ||
|
|
5ba3f7cf6d | ||
|
|
468f878a38 | ||
|
|
c50a367063 | ||
|
|
4189f598b9 | ||
|
|
fdb3764f5c | ||
|
|
cb67e805de | ||
|
|
3f83368f42 | ||
|
|
8c7462678f | ||
|
|
6f11390cc4 | ||
|
|
33d390a228 | ||
|
|
937eb350b2 | ||
|
|
3b4ece6bd8 | ||
|
|
35a4fdacbe | ||
|
|
0332779d6a | ||
|
|
f19575b942 | ||
|
|
6a95809da3 | ||
|
|
ac8dab70ef | ||
|
|
243285f7c2 | ||
|
|
b6182216d5 | ||
|
|
e206121bbc | ||
|
|
deceffad00 | ||
|
|
c19909cef1 | ||
|
|
5b7e40638c | ||
|
|
a3673d1af5 | ||
|
|
d5f9cf85c9 | ||
|
|
549f09573f | ||
|
|
94e5bc86fb | ||
|
|
370ac2bd89 | ||
|
|
7041f41981 | ||
|
|
dd6a21257b | ||
|
|
445da0c4ea | ||
|
|
92980824b1 | ||
|
|
7d42b07a32 | ||
|
|
e502b9169f | ||
|
|
9f2d887ca0 | ||
|
|
e6aacc5f08 | ||
|
|
ff935be54a | ||
|
|
4e76162346 | ||
|
|
5fa4f2647b | ||
|
|
81b5889a26 | ||
|
|
514f59258a | ||
|
|
9f79393aa5 | ||
|
|
e49d514236 | ||
|
|
4e1462326c | ||
|
|
b0f1773a92 | ||
|
|
af9f28c010 | ||
|
|
fef0c66868 | ||
|
|
7a5da83ce2 | ||
|
|
97f01387b9 | ||
|
|
012ff83a06 | ||
|
|
a082c63849 |
27
.github/ISSUE_TEMPLATE/bug_report.md
vendored
27
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,20 +1,35 @@
|
|||||||
---
|
---
|
||||||
name: Bug report
|
name: Bug report
|
||||||
about: Create a report to help us improve termux-app
|
about: Create a report to help us improve Termux application
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<!-- Important note: Refusing to provide needed information may result in issue closing. If you are having problems with a package in termux then a bug report should be filed in the termux-packages repo: https://github.com/termux/termux-packages -->
|
<!--
|
||||||
|
IMPORTANT:
|
||||||
|
|
||||||
|
1. Support of Android 5.x - 6.x is finished.
|
||||||
|
2. Fill the template AFTER comments.
|
||||||
|
-->
|
||||||
|
|
||||||
**Problem description**
|
**Problem description**
|
||||||
A clear and concise description of what the problem with the termux app is. You may post screenshots in addition to description.
|
<!--
|
||||||
|
A clear and concise description of what the problem is.
|
||||||
|
You may post screenshots in addition to description.
|
||||||
|
-->
|
||||||
|
|
||||||
**Steps to reproduce**
|
**Steps to reproduce**
|
||||||
Please post all steps that are needed to reproduce the issue.
|
<!--
|
||||||
|
Steps to reproduce the behavior. Please post all necessary
|
||||||
|
commands that are needed to reproduce the issue.
|
||||||
|
-->
|
||||||
|
|
||||||
**Expected behavior**
|
**Expected behavior**
|
||||||
|
<!--
|
||||||
A clear and concise description of what you expected to happen.
|
A clear and concise description of what you expected to happen.
|
||||||
|
-->
|
||||||
|
|
||||||
**Additional information**
|
**Additional information**
|
||||||
Post output of command `termux-info`.
|
|
||||||
If you are rooted or have access to adb then capture a logcat with `logcat -d "*:W"`, from a adb or root shell.
|
* Termux application version:
|
||||||
|
* Android OS version:
|
||||||
|
* Device model:
|
||||||
|
|||||||
14
.github/ISSUE_TEMPLATE/feature_request.md
vendored
14
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,12 +1,22 @@
|
|||||||
---
|
---
|
||||||
name: Feature request
|
name: Feature request
|
||||||
about: Suggest a new feature in termux-app
|
about: Suggest a new feature for Termux application
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
<!--
|
||||||
|
IMPORTANT:
|
||||||
|
|
||||||
|
1. Support of Android 5.x - 6.x is finished.
|
||||||
|
2. Fill the template AFTER comments.
|
||||||
|
-->
|
||||||
|
|
||||||
**Feature description**
|
**Feature description**
|
||||||
|
<!--
|
||||||
Describe the feature and why you want it.
|
Describe the feature and why you want it.
|
||||||
|
-->
|
||||||
|
|
||||||
**Reference implementation**
|
**Reference implementation**
|
||||||
|
|
||||||
Does another app/terminal emulator have this feature?
|
Does another app/terminal emulator have this feature?
|
||||||
Provide links to more background information
|
Provide links to more background information.
|
||||||
|
|||||||
26
.github/workflows/debug_build.yml
vendored
Normal file
26
.github/workflows/debug_build.yml
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
name: Build
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- android-10
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- android-10
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Clone repository
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
- name: Build
|
||||||
|
run: |
|
||||||
|
./gradlew assembleDebug
|
||||||
|
- name: Store generated APK file
|
||||||
|
uses: actions/upload-artifact@v1
|
||||||
|
with:
|
||||||
|
name: termux-app
|
||||||
|
path: ./app/build/outputs/apk/debug/app-debug.apk
|
||||||
19
.github/workflows/gradle-wrapper-validation.yml
vendored
Normal file
19
.github/workflows/gradle-wrapper-validation.yml
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
name: "Validate Gradle Wrapper"
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- android-10
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- android-10
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
validation:
|
||||||
|
name: "Validation"
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: gradle/wrapper-validation-action@v1
|
||||||
21
.github/workflows/run_tests.yml
vendored
Normal file
21
.github/workflows/run_tests.yml
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
name: Unit tests
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- android-10
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- android-10
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
testing:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Clone repository
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
- name: Execute tests
|
||||||
|
run: |
|
||||||
|
./gradlew test
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -7,6 +7,8 @@ build/
|
|||||||
*.apk
|
*.apk
|
||||||
*.so
|
*.so
|
||||||
.externalNativeBuild
|
.externalNativeBuild
|
||||||
|
.cxx
|
||||||
|
*.zip
|
||||||
|
|
||||||
# Crashlytics configuations
|
# Crashlytics configuations
|
||||||
com_crashlytics_export_strings.xml
|
com_crashlytics_export_strings.xml
|
||||||
|
|||||||
34
.travis.yml
34
.travis.yml
@@ -1,34 +0,0 @@
|
|||||||
sudo: false
|
|
||||||
language: android
|
|
||||||
jdk: oraclejdk8
|
|
||||||
|
|
||||||
env:
|
|
||||||
global:
|
|
||||||
# The next declaration is the encrypted COVERITY_SCAN_TOKEN, created
|
|
||||||
# via the "travis encrypt" command using the project repo's public key
|
|
||||||
- secure: "LdajbHNfRlpnqzhX5KY2Vr7KtzU9vXDs1TCNn93J6Dt522f2AaiyUDJvISvz+uslk0WJiS5bB5vGwQmXginxz6Qi6uMgMbjWXulv1vfs6ZviKpUX348DOp1qKPa8WfVNB66F84SwGIfc8cRMAgCFw79l/DFgLErubF8vKo1wZ8Hmvrz//+RJ0BGMa3YRc4VyJhAL0P+0Wc1Q2Im7R9EovAxC5pZXBIMSgr6g5GzLWPisbNLXpMPGsDeYhcenO6XCtCCy+aNxUYM8vcrLDzlVXR5Hy7KEs/MGRTS0Yk13TWUEYa5wBpKelFTszdWYLVn5ANreh/aXRVfHpnW3epotMYguLx1kSvOhWEnc4F+qqv3nle2LpDg9Y9bcLyTTcYnPl9smqEVVjEDu0FoIr1V58xkG4Oc6BPIvLRjlMVU96PXh2HxMLuGsJ/xM+uAFU9oVMbC07xn42Eu5O4NHOHJNOwMWac4/lSKRK8W/7/vWuXj5vhkD9ZsGVpN70UtY5HAfNUGADnTeDblvjgFTNZ2mUN/u0o7Z8ZFURYllZ9YU+Vr2nPf9CAhVBjuwFWx8uRQpAg1aDmc1dVMJijRBeBeU/uWhYqsGp34wkNEl8VGzob4R4QTyI8+T7CndGqKVmbTK/SjqKhjjPpbXIAfOH+JtxvAnNmb8XeQSJ32uK2nexFo="
|
|
||||||
|
|
||||||
android:
|
|
||||||
components:
|
|
||||||
- platform-tools
|
|
||||||
- tools
|
|
||||||
- build-tools-28.0.3
|
|
||||||
- android-28
|
|
||||||
- extra-android-m2repository
|
|
||||||
|
|
||||||
before_install:
|
|
||||||
- yes | sdkmanager "ndk-bundle"
|
|
||||||
- yes | sdkmanager "platforms;android-28"
|
|
||||||
|
|
||||||
script:
|
|
||||||
- ./gradlew testDebugUnitTest
|
|
||||||
|
|
||||||
addons:
|
|
||||||
coverity_scan:
|
|
||||||
project:
|
|
||||||
name: "termux/termux-app"
|
|
||||||
description: "Terminal emulator and Linux environment for Android"
|
|
||||||
notification_email: fredrik@fornwall.net
|
|
||||||
build_command_prepend: "./gradlew clean"
|
|
||||||
build_command: "./gradlew build"
|
|
||||||
branch_pattern: master
|
|
||||||
81
README.md
81
README.md
@@ -1,32 +1,65 @@
|
|||||||
[](https://travis-ci.org/termux/termux-app)
|
# Termux application
|
||||||
|
|
||||||
|
[](https://github.com/termux/termux-app/actions)
|
||||||
|
[](https://github.com/termux/termux-app/actions)
|
||||||
[](https://gitter.im/termux/termux)
|
[](https://gitter.im/termux/termux)
|
||||||
|
|
||||||
|
[Termux](https://termux.com) is an Android terminal application and Linux environment.
|
||||||
|
|
||||||
Termux app
|
- [Termux Reddit community](https://reddit.com/r/termux)
|
||||||
==========
|
- [Termux Wiki](https://wiki.termux.com/wiki/)
|
||||||
|
- [Termux Twitter](http://twitter.com/termux/)
|
||||||
|
|
||||||
[Termux](https://termux.com) is an Android terminal app and Linux environment.
|
Note that this repository is for the app itself (the user interface and the
|
||||||
|
terminal emulation). For the packages installable inside the app, see
|
||||||
|
[termux/termux-packages](https://github.com/termux/termux-packages)
|
||||||
|
|
||||||
* [Termux on Google Play Store](https://play.google.com/store/apps/details?id=com.termux)
|
## Installation
|
||||||
* [Termux on F-Droid](https://f-droid.org/repository/browse/?fdid=com.termux)
|
|
||||||
* [Termux Google+ community](http://termux.com/community/)
|
|
||||||
* [Termux Wiki](https://wiki.termux.com/wiki/)
|
|
||||||
* [Termux Twitter](http://twitter.com/termux/)
|
|
||||||
|
|
||||||
Note that this repository is for the app itself (the user interface and the terminal emulation). For the packages installable inside the app, see [termux/termux-packages](https://github.com/termux/termux-packages)
|
Termux application can be obtained from:
|
||||||
|
|
||||||
Terminal resources
|
- [Google Play](https://play.google.com/store/apps/details?id=com.termux)
|
||||||
==================
|
- [F-Droid](https://f-droid.org/en/packages/com.termux/)
|
||||||
* [XTerm control sequences](http://invisible-island.net/xterm/ctlseqs/ctlseqs.html)
|
- [Kali Nethunter Store](https://store.nethunter.com/en/packages/com.termux/)
|
||||||
* [vt100.net](http://vt100.net/)
|
|
||||||
* [Terminal codes (ANSI and terminfo equivalents)](http://wiki.bash-hackers.org/scripting/terminalcodes)
|
|
||||||
|
|
||||||
Terminal emulators
|
Additionally we provide per-commit debug builds for those who want to try
|
||||||
==================
|
out the latest features or test their pull request. This build can be obtained
|
||||||
* VTE (libvte): Terminal emulator widget for GTK+, mainly used in gnome-terminal. [Source](https://github.com/GNOME/vte), [Open Issues](https://bugzilla.gnome.org/buglist.cgi?quicksearch=product%3A%22vte%22+), and [All (including closed) issues](https://bugzilla.gnome.org/buglist.cgi?bug_status=RESOLVED&bug_status=VERIFIED&chfield=resolution&chfieldfrom=-2000d&chfieldvalue=FIXED&product=vte&resolution=FIXED).
|
from one of the workflow runs listed on [Github Actions](https://github.com/termux/termux-app/actions)
|
||||||
* iTerm 2: OS X terminal application. [Source](https://github.com/gnachman/iTerm2), [Issues](https://gitlab.com/gnachman/iterm2/issues) and [Documentation](http://www.iterm2.com/documentation.html) (which includes [iTerm2 proprietary escape codes](http://www.iterm2.com/documentation-escape-codes.html)).
|
page.
|
||||||
* Konsole: KDE terminal application. [Source](https://projects.kde.org/projects/kde/applications/konsole/repository), in particular [tests](https://projects.kde.org/projects/kde/applications/konsole/repository/revisions/master/show/tests), [Bugs](https://bugs.kde.org/buglist.cgi?bug_severity=critical&bug_severity=grave&bug_severity=major&bug_severity=crash&bug_severity=normal&bug_severity=minor&bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&product=konsole) and [Wishes](https://bugs.kde.org/buglist.cgi?bug_severity=wishlist&bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&product=konsole).
|
|
||||||
* hterm: JavaScript terminal implementation from Chromium. [Source](https://github.com/chromium/hterm), including [tests](https://github.com/chromium/hterm/blob/master/js/hterm_vt_tests.js), and [Google group](https://groups.google.com/a/chromium.org/forum/#!forum/chromium-hterm).
|
Signature keys of all offered builds are different. Before you switch the
|
||||||
* xterm: The grandfather of terminal emulators. [Source](http://invisible-island.net/datafiles/release/xterm.tar.gz).
|
installation source, you will have to uninstall the Termux application and
|
||||||
* Connectbot: Android SSH client. [Source](https://github.com/connectbot/connectbot)
|
all currently installed plugins.
|
||||||
* Android Terminal Emulator: Android terminal app which Termux terminal handling is based on. Inactive. [Source](https://github.com/jackpal/Android-Terminal-Emulator).
|
|
||||||
|
## Terminal resources
|
||||||
|
|
||||||
|
- [XTerm control sequences](http://invisible-island.net/xterm/ctlseqs/ctlseqs.html)
|
||||||
|
- [vt100.net](http://vt100.net/)
|
||||||
|
- [Terminal codes (ANSI and terminfo equivalents)](http://wiki.bash-hackers.org/scripting/terminalcodes)
|
||||||
|
|
||||||
|
## Terminal emulators
|
||||||
|
|
||||||
|
- VTE (libvte): Terminal emulator widget for GTK+, mainly used in gnome-terminal.
|
||||||
|
[Source](https://github.com/GNOME/vte), [Open Issues](https://bugzilla.gnome.org/buglist.cgi?quicksearch=product%3A%22vte%22+),
|
||||||
|
and [All (including closed) issues](https://bugzilla.gnome.org/buglist.cgi?bug_status=RESOLVED&bug_status=VERIFIED&chfield=resolution&chfieldfrom=-2000d&chfieldvalue=FIXED&product=vte&resolution=FIXED).
|
||||||
|
|
||||||
|
- iTerm 2: OS X terminal application. [Source](https://github.com/gnachman/iTerm2),
|
||||||
|
[Issues](https://gitlab.com/gnachman/iterm2/issues) and [Documentation](http://www.iterm2.com/documentation.html)
|
||||||
|
(which includes [iTerm2 proprietary escape codes](http://www.iterm2.com/documentation-escape-codes.html)).
|
||||||
|
|
||||||
|
- Konsole: KDE terminal application. [Source](https://projects.kde.org/projects/kde/applications/konsole/repository),
|
||||||
|
in particular [tests](https://projects.kde.org/projects/kde/applications/konsole/repository/revisions/master/show/tests),
|
||||||
|
[Bugs](https://bugs.kde.org/buglist.cgi?bug_severity=critical&bug_severity=grave&bug_severity=major&bug_severity=crash&bug_severity=normal&bug_severity=minor&bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&product=konsole)
|
||||||
|
and [Wishes](https://bugs.kde.org/buglist.cgi?bug_severity=wishlist&bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&product=konsole).
|
||||||
|
|
||||||
|
- hterm: JavaScript terminal implementation from Chromium. [Source](https://github.com/chromium/hterm),
|
||||||
|
including [tests](https://github.com/chromium/hterm/blob/master/js/hterm_vt_tests.js),
|
||||||
|
and [Google group](https://groups.google.com/a/chromium.org/forum/#!forum/chromium-hterm).
|
||||||
|
|
||||||
|
- xterm: The grandfather of terminal emulators.
|
||||||
|
[Source](http://invisible-island.net/datafiles/release/xterm.tar.gz).
|
||||||
|
|
||||||
|
- Connectbot: Android SSH client. [Source](https://github.com/connectbot/connectbot)
|
||||||
|
|
||||||
|
- Android Terminal Emulator: Android terminal app which Termux terminal handling
|
||||||
|
is based on. Inactive. [Source](https://github.com/jackpal/Android-Terminal-Emulator).
|
||||||
|
|||||||
129
app/build.gradle
129
app/build.gradle
@@ -1,21 +1,44 @@
|
|||||||
apply plugin: 'com.android.application'
|
plugins {
|
||||||
|
id "com.android.application"
|
||||||
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 28
|
compileSdkVersion project.properties.compileSdkVersion.toInteger()
|
||||||
|
ndkVersion project.properties.ndkVersion
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation "androidx.annotation:annotation:1.0.1"
|
implementation "androidx.annotation:annotation:1.1.0"
|
||||||
implementation "androidx.viewpager:viewpager:1.0.0"
|
implementation "androidx.viewpager:viewpager:1.0.0"
|
||||||
implementation "androidx.drawerlayout:drawerlayout:1.0.0"
|
implementation "androidx.drawerlayout:drawerlayout:1.1.0"
|
||||||
implementation project(":terminal-view")
|
implementation project(":terminal-view")
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "com.termux"
|
applicationId "com.termux"
|
||||||
minSdkVersion 21
|
minSdkVersion project.properties.minSdkVersion.toInteger()
|
||||||
targetSdkVersion 28
|
targetSdkVersion project.properties.targetSdkVersion.toInteger()
|
||||||
versionCode 68
|
versionCode 99
|
||||||
versionName "0.68"
|
versionName "0.99"
|
||||||
|
|
||||||
|
externalNativeBuild {
|
||||||
|
ndkBuild {
|
||||||
|
cFlags "-std=c11", "-Wall", "-Wextra", "-Werror", "-Os", "-fno-stack-protector", "-Wl,--gc-sections"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ndk {
|
||||||
|
abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
signingConfigs {
|
||||||
|
debug {
|
||||||
|
storeFile file('dev_keystore.jks')
|
||||||
|
keyAlias 'alias'
|
||||||
|
storePassword 'xrj45yWGLbsO7W0v'
|
||||||
|
keyPassword 'xrj45yWGLbsO7W0v'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
@@ -24,14 +47,102 @@ android {
|
|||||||
shrinkResources true
|
shrinkResources true
|
||||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
debug {
|
||||||
|
signingConfig signingConfigs.debug
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility JavaVersion.VERSION_1_8
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
targetCompatibility JavaVersion.VERSION_1_8
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
}
|
}
|
||||||
|
|
||||||
|
externalNativeBuild {
|
||||||
|
ndkBuild {
|
||||||
|
path "src/main/cpp/Android.mk"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
testOptions {
|
||||||
|
unitTests {
|
||||||
|
includeAndroidResources = true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
testImplementation 'junit:junit:4.12'
|
testImplementation 'junit:junit:4.13'
|
||||||
|
testImplementation 'org.robolectric:robolectric:4.3.1'
|
||||||
|
}
|
||||||
|
|
||||||
|
task versionName {
|
||||||
|
doLast {
|
||||||
|
print android.defaultConfig.versionName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def downloadBootstrap(String arch, String expectedChecksum, int version) {
|
||||||
|
def digest = java.security.MessageDigest.getInstance("SHA-256")
|
||||||
|
|
||||||
|
def localUrl = "src/main/cpp/bootstrap-" + arch + ".zip"
|
||||||
|
def file = new File(projectDir, localUrl)
|
||||||
|
if (file.exists()) {
|
||||||
|
def buffer = new byte[8192]
|
||||||
|
def input = new FileInputStream(file)
|
||||||
|
while (true) {
|
||||||
|
def readBytes = input.read(buffer)
|
||||||
|
if (readBytes < 0) break
|
||||||
|
digest.update(buffer, 0, readBytes)
|
||||||
|
}
|
||||||
|
def checksum = new BigInteger(1, digest.digest()).toString(16)
|
||||||
|
if (checksum == expectedChecksum) {
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
logger.quiet("Deleting old local file with wrong hash: " + localUrl)
|
||||||
|
file.delete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def remoteUrl = "https://bintray.com/termux/bootstrap/download_file?file_path=bootstrap-" + arch + "-v" + version + ".zip"
|
||||||
|
logger.quiet("Downloading " + remoteUrl + " ...")
|
||||||
|
|
||||||
|
file.parentFile.mkdirs()
|
||||||
|
def out = new BufferedOutputStream(new FileOutputStream(file))
|
||||||
|
|
||||||
|
def connection = new URL(remoteUrl).openConnection()
|
||||||
|
connection.setInstanceFollowRedirects(true)
|
||||||
|
def digestStream = new java.security.DigestInputStream(connection.inputStream, digest)
|
||||||
|
out << digestStream
|
||||||
|
out.close()
|
||||||
|
|
||||||
|
def checksum = new BigInteger(1, digest.digest()).toString(16)
|
||||||
|
if (checksum != expectedChecksum) {
|
||||||
|
file.delete()
|
||||||
|
throw new GradleException("Wrong checksum for " + remoteUrl + ": expected: " + expectedChecksum + ", actual: " + checksum)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clean {
|
||||||
|
doLast {
|
||||||
|
def tree = fileTree(new File(projectDir, 'src/main/cpp'))
|
||||||
|
tree.include 'bootstrap-*.zip'
|
||||||
|
tree.each { it.delete() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
task downloadBootstraps(){
|
||||||
|
doLast {
|
||||||
|
def version = 28
|
||||||
|
downloadBootstrap("aarch64", "8f8c8af1d6c50bd0d6ecd5a1df8d5b0c3122a945b6febd0de0308504b4075ed0", version)
|
||||||
|
downloadBootstrap("arm", "179465f403a73fec6534363b11cb38eed3b6b3d8e9d922d8e55dc8a06813240d", version)
|
||||||
|
downloadBootstrap("i686", "2100ebf36befde73b08efaa7e3ccc9c218a8b5e7d69db06e16e364ab8b7dfbd5", version)
|
||||||
|
downloadBootstrap("x86_64", "3f5192f63595426370aa69f102fac07ae00c12154878eddec24e8406e640a67f", version)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
afterEvaluate {
|
||||||
|
android.applicationVariants.all { variant ->
|
||||||
|
variant.javaCompileProvider.get().dependsOn(downloadBootstraps)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
app/dev_keystore.jks
Normal file
BIN
app/dev_keystore.jks
Normal file
Binary file not shown.
@@ -8,16 +8,23 @@
|
|||||||
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
|
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
|
||||||
<uses-feature android:name="android.software.leanback" android:required="false" />
|
<uses-feature android:name="android.software.leanback" android:required="false" />
|
||||||
|
|
||||||
|
<permission android:name="com.termux.permission.RUN_COMMAND"
|
||||||
|
android:label="@string/run_command_permission_label"
|
||||||
|
android:description="@string/run_command_permission_description"
|
||||||
|
android:icon="@drawable/ic_launcher"
|
||||||
|
android:protectionLevel="dangerous" />
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
<uses-permission android:name="android.permission.VIBRATE" />
|
<uses-permission android:name="android.permission.VIBRATE" />
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
|
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:extractNativeLibs="true"
|
android:extractNativeLibs="true"
|
||||||
android:allowBackup="true"
|
android:allowBackup="false"
|
||||||
android:fullBackupContent="@xml/backupscheme"
|
|
||||||
android:icon="@drawable/ic_launcher"
|
android:icon="@drawable/ic_launcher"
|
||||||
android:banner="@drawable/banner"
|
android:banner="@drawable/banner"
|
||||||
android:label="@string/application_name"
|
android:label="@string/application_name"
|
||||||
@@ -30,33 +37,22 @@
|
|||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="com.termux.app.TermuxActivity"
|
android:name="com.termux.app.TermuxActivity"
|
||||||
android:label="@string/application_name"
|
android:label="@string/application_name"
|
||||||
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
|
android:configChanges="orientation|screenSize|smallestScreenSize|density|screenLayout|uiMode|keyboard|keyboardHidden|navigation"
|
||||||
android:launchMode="singleTask"
|
android:launchMode="singleTask"
|
||||||
android:resizeableActivity="true"
|
android:resizeableActivity="true"
|
||||||
android:windowSoftInputMode="adjustResize|stateAlwaysVisible" >
|
android:windowSoftInputMode="adjustResize|stateAlwaysVisible" >
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
<category android:name="android.intent.category.DEFAULT"/>
|
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
||||||
<category android:name="android.intent.category.DEFAULT"/>
|
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
<meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcuts" />
|
<meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcuts" />
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity
|
|
||||||
android:name="com.termux.app.TermuxFailsafeActivity"
|
|
||||||
android:label="@string/app_failsafe_mode" >
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.MAIN" />
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
|
||||||
</intent-filter>
|
|
||||||
</activity>
|
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="com.termux.app.TermuxHelpActivity"
|
android:name="com.termux.app.TermuxHelpActivity"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
@@ -89,6 +85,7 @@
|
|||||||
<action android:name="android.intent.action.VIEW"/>
|
<action android:name="android.intent.action.VIEW"/>
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<data android:mimeType="text/*" />
|
<data android:mimeType="text/*" />
|
||||||
|
<data android:mimeType="application/*log*" />
|
||||||
<data android:mimeType="application/json" />
|
<data android:mimeType="application/json" />
|
||||||
<data android:mimeType="application/*xml*" />
|
<data android:mimeType="application/*xml*" />
|
||||||
<data android:mimeType="application/*latex*" />
|
<data android:mimeType="application/*latex*" />
|
||||||
@@ -123,6 +120,15 @@
|
|||||||
android:name="com.termux.app.TermuxService"
|
android:name="com.termux.app.TermuxService"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name=".app.RunCommandService"
|
||||||
|
android:exported="true"
|
||||||
|
android:permission="com.termux.permission.RUN_COMMAND" >
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="com.termux.RUN_COMMAND" />
|
||||||
|
</intent-filter>
|
||||||
|
</service>
|
||||||
|
|
||||||
<receiver android:name=".app.TermuxOpenReceiver" />
|
<receiver android:name=".app.TermuxOpenReceiver" />
|
||||||
|
|
||||||
<provider android:authorities="com.termux.files"
|
<provider android:authorities="com.termux.files"
|
||||||
@@ -130,8 +136,8 @@
|
|||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:grantUriPermissions="true"
|
android:grantUriPermissions="true"
|
||||||
android:name="com.termux.app.TermuxOpenReceiver$ContentProvider" />
|
android:name="com.termux.app.TermuxOpenReceiver$ContentProvider" />
|
||||||
|
<meta-data android:name="com.sec.android.support.multiwindow" android:value="true" />
|
||||||
<meta-data android:name="com.sec.android.support.multiwindow" android:value="true" />
|
<meta-data android:name="com.samsung.android.multidisplay.keep_process_alive" android:value="true"/>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
5
app/src/main/cpp/Android.mk
Normal file
5
app/src/main/cpp/Android.mk
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
LOCAL_PATH:= $(call my-dir)
|
||||||
|
include $(CLEAR_VARS)
|
||||||
|
LOCAL_MODULE := libtermux-bootstrap
|
||||||
|
LOCAL_SRC_FILES := termux-bootstrap-zip.S termux-bootstrap.c
|
||||||
|
include $(BUILD_SHARED_LIBRARY)
|
||||||
18
app/src/main/cpp/termux-bootstrap-zip.S
Normal file
18
app/src/main/cpp/termux-bootstrap-zip.S
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
.global blob
|
||||||
|
.global blob_size
|
||||||
|
.section .rodata
|
||||||
|
blob:
|
||||||
|
#if defined __i686__
|
||||||
|
.incbin "bootstrap-i686.zip"
|
||||||
|
#elif defined __x86_64__
|
||||||
|
.incbin "bootstrap-x86_64.zip"
|
||||||
|
#elif defined __aarch64__
|
||||||
|
.incbin "bootstrap-aarch64.zip"
|
||||||
|
#elif defined __arm__
|
||||||
|
.incbin "bootstrap-arm.zip"
|
||||||
|
#else
|
||||||
|
# error Unsupported arch
|
||||||
|
#endif
|
||||||
|
1:
|
||||||
|
blob_size:
|
||||||
|
.int 1b - blob
|
||||||
11
app/src/main/cpp/termux-bootstrap.c
Normal file
11
app/src/main/cpp/termux-bootstrap.c
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
#include <jni.h>
|
||||||
|
|
||||||
|
extern jbyte blob[];
|
||||||
|
extern int blob_size;
|
||||||
|
|
||||||
|
JNIEXPORT jbyteArray JNICALL Java_com_termux_app_TermuxInstaller_getZip(JNIEnv *env, __attribute__((__unused__)) jobject This)
|
||||||
|
{
|
||||||
|
jbyteArray ret = (*env)->NewByteArray(env, blob_size);
|
||||||
|
(*env)->SetByteArrayRegion(env, ret, 0, blob_size, blob);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
@@ -1,5 +1,9 @@
|
|||||||
package com.termux.app;
|
package com.termux.app;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
@@ -24,7 +28,11 @@ public final class BackgroundJob {
|
|||||||
|
|
||||||
final Process mProcess;
|
final Process mProcess;
|
||||||
|
|
||||||
public BackgroundJob(String cwd, String fileToExecute, final String[] args, final TermuxService service) {
|
public BackgroundJob(String cwd, String fileToExecute, final String[] args, final TermuxService service){
|
||||||
|
this(cwd, fileToExecute, args, service, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BackgroundJob(String cwd, String fileToExecute, final String[] args, final TermuxService service, PendingIntent pendingIntent) {
|
||||||
String[] env = buildEnvironment(false, cwd);
|
String[] env = buildEnvironment(false, cwd);
|
||||||
if (cwd == null) cwd = TermuxService.HOME_PATH;
|
if (cwd == null) cwd = TermuxService.HOME_PATH;
|
||||||
|
|
||||||
@@ -43,6 +51,28 @@ public final class BackgroundJob {
|
|||||||
|
|
||||||
mProcess = process;
|
mProcess = process;
|
||||||
final int pid = getPid(mProcess);
|
final int pid = getPid(mProcess);
|
||||||
|
final Bundle result = new Bundle();
|
||||||
|
final StringBuilder outResult = new StringBuilder();
|
||||||
|
final StringBuilder errResult = new StringBuilder();
|
||||||
|
|
||||||
|
Thread errThread = new Thread() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
InputStream stderr = mProcess.getErrorStream();
|
||||||
|
BufferedReader reader = new BufferedReader(new InputStreamReader(stderr, StandardCharsets.UTF_8));
|
||||||
|
String line;
|
||||||
|
try {
|
||||||
|
// FIXME: Long lines.
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
errResult.append(line).append('\n');
|
||||||
|
Log.i(LOG_TAG, "[" + pid + "] stderr: " + line);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
// Ignore.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
errThread.start();
|
||||||
|
|
||||||
new Thread() {
|
new Thread() {
|
||||||
@Override
|
@Override
|
||||||
@@ -50,11 +80,13 @@ public final class BackgroundJob {
|
|||||||
Log.i(LOG_TAG, "[" + pid + "] starting: " + processDescription);
|
Log.i(LOG_TAG, "[" + pid + "] starting: " + processDescription);
|
||||||
InputStream stdout = mProcess.getInputStream();
|
InputStream stdout = mProcess.getInputStream();
|
||||||
BufferedReader reader = new BufferedReader(new InputStreamReader(stdout, StandardCharsets.UTF_8));
|
BufferedReader reader = new BufferedReader(new InputStreamReader(stdout, StandardCharsets.UTF_8));
|
||||||
|
|
||||||
String line;
|
String line;
|
||||||
try {
|
try {
|
||||||
// FIXME: Long lines.
|
// FIXME: Long lines.
|
||||||
while ((line = reader.readLine()) != null) {
|
while ((line = reader.readLine()) != null) {
|
||||||
Log.i(LOG_TAG, "[" + pid + "] stdout: " + line);
|
Log.i(LOG_TAG, "[" + pid + "] stdout: " + line);
|
||||||
|
outResult.append(line).append('\n');
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.e(LOG_TAG, "Error reading output", e);
|
Log.e(LOG_TAG, "Error reading output", e);
|
||||||
@@ -68,29 +100,35 @@ public final class BackgroundJob {
|
|||||||
} else {
|
} else {
|
||||||
Log.w(LOG_TAG, "[" + pid + "] exited with code: " + exitCode);
|
Log.w(LOG_TAG, "[" + pid + "] exited with code: " + exitCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
result.putString("stdout", outResult.toString());
|
||||||
|
result.putInt("exitCode", exitCode);
|
||||||
|
|
||||||
|
errThread.join();
|
||||||
|
result.putString("stderr", errResult.toString());
|
||||||
|
|
||||||
|
Intent data = new Intent();
|
||||||
|
data.putExtra("result", result);
|
||||||
|
|
||||||
|
if(pendingIntent != null) {
|
||||||
|
try {
|
||||||
|
pendingIntent.send(service.getApplicationContext(), Activity.RESULT_OK, data);
|
||||||
|
} catch (PendingIntent.CanceledException e) {
|
||||||
|
// The caller doesn't want the result? That's fine, just ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
// Ignore.
|
// Ignore
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.start();
|
}.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void addToEnvIfPresent(List<String> environment, String name) {
|
||||||
new Thread() {
|
String value = System.getenv(name);
|
||||||
@Override
|
if (value != null) {
|
||||||
public void run() {
|
environment.add(name + "=" + value);
|
||||||
InputStream stderr = mProcess.getErrorStream();
|
}
|
||||||
BufferedReader reader = new BufferedReader(new InputStreamReader(stderr, StandardCharsets.UTF_8));
|
|
||||||
String line;
|
|
||||||
try {
|
|
||||||
// FIXME: Long lines.
|
|
||||||
while ((line = reader.readLine()) != null) {
|
|
||||||
Log.i(LOG_TAG, "[" + pid + "] stderr: " + line);
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
// Ignore.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static String[] buildEnvironment(boolean failSafe, String cwd) {
|
static String[] buildEnvironment(boolean failSafe, String cwd) {
|
||||||
@@ -101,20 +139,27 @@ public final class BackgroundJob {
|
|||||||
List<String> environment = new ArrayList<>();
|
List<String> environment = new ArrayList<>();
|
||||||
|
|
||||||
environment.add("TERM=xterm-256color");
|
environment.add("TERM=xterm-256color");
|
||||||
|
environment.add("COLORTERM=truecolor");
|
||||||
environment.add("HOME=" + TermuxService.HOME_PATH);
|
environment.add("HOME=" + TermuxService.HOME_PATH);
|
||||||
environment.add("PREFIX=" + TermuxService.PREFIX_PATH);
|
environment.add("PREFIX=" + TermuxService.PREFIX_PATH);
|
||||||
|
environment.add("BOOTCLASSPATH=" + System.getenv("BOOTCLASSPATH"));
|
||||||
environment.add("ANDROID_ROOT=" + System.getenv("ANDROID_ROOT"));
|
environment.add("ANDROID_ROOT=" + System.getenv("ANDROID_ROOT"));
|
||||||
environment.add("ANDROID_DATA=" + System.getenv("ANDROID_DATA"));
|
environment.add("ANDROID_DATA=" + System.getenv("ANDROID_DATA"));
|
||||||
// EXTERNAL_STORAGE is needed for /system/bin/am to work on at least
|
// EXTERNAL_STORAGE is needed for /system/bin/am to work on at least
|
||||||
// Samsung S7 - see https://plus.google.com/110070148244138185604/posts/gp8Lk3aCGp3.
|
// Samsung S7 - see https://plus.google.com/110070148244138185604/posts/gp8Lk3aCGp3.
|
||||||
environment.add("EXTERNAL_STORAGE=" + System.getenv("EXTERNAL_STORAGE"));
|
environment.add("EXTERNAL_STORAGE=" + System.getenv("EXTERNAL_STORAGE"));
|
||||||
|
|
||||||
|
// These variables are needed if running on Android 10 and higher.
|
||||||
|
addToEnvIfPresent(environment, "ANDROID_ART_ROOT");
|
||||||
|
addToEnvIfPresent(environment, "DEX2OATBOOTCLASSPATH");
|
||||||
|
addToEnvIfPresent(environment, "ANDROID_I18N_ROOT");
|
||||||
|
addToEnvIfPresent(environment, "ANDROID_RUNTIME_ROOT");
|
||||||
|
addToEnvIfPresent(environment, "ANDROID_TZDATA_ROOT");
|
||||||
|
|
||||||
if (failSafe) {
|
if (failSafe) {
|
||||||
// Keep the default path so that system binaries can be used in the failsafe session.
|
// Keep the default path so that system binaries can be used in the failsafe session.
|
||||||
environment.add("PATH= " + System.getenv("PATH"));
|
environment.add("PATH= " + System.getenv("PATH"));
|
||||||
} else {
|
} else {
|
||||||
if (shouldAddLdLibraryPath()) {
|
|
||||||
environment.add("LD_LIBRARY_PATH=" + TermuxService.PREFIX_PATH + "/lib");
|
|
||||||
}
|
|
||||||
environment.add("LANG=en_US.UTF-8");
|
environment.add("LANG=en_US.UTF-8");
|
||||||
environment.add("PATH=" + TermuxService.PREFIX_PATH + "/bin:" + TermuxService.PREFIX_PATH + "/bin/applets");
|
environment.add("PATH=" + TermuxService.PREFIX_PATH + "/bin:" + TermuxService.PREFIX_PATH + "/bin/applets");
|
||||||
environment.add("PWD=" + cwd);
|
environment.add("PWD=" + cwd);
|
||||||
@@ -124,20 +169,6 @@ public final class BackgroundJob {
|
|||||||
return environment.toArray(new String[0]);
|
return environment.toArray(new String[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean shouldAddLdLibraryPath() {
|
|
||||||
try (BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(TermuxService.PREFIX_PATH + "/etc/apt/sources.list")))) {
|
|
||||||
String line;
|
|
||||||
while ((line = in.readLine()) != null) {
|
|
||||||
if (!line.startsWith("#") && line.contains("https://dl.bintray.com/termux/termux-packages-24")) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.e(LOG_TAG, "Error trying to read sources.list", e);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int getPid(Process p) {
|
public static int getPid(Process p) {
|
||||||
try {
|
try {
|
||||||
Field f = p.getClass().getDeclaredField("pid");
|
Field f = p.getClass().getDeclaredField("pid");
|
||||||
|
|||||||
63
app/src/main/java/com/termux/app/BellUtil.java
Normal file
63
app/src/main/java/com/termux/app/BellUtil.java
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
package com.termux.app;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.os.SystemClock;
|
||||||
|
import android.os.Vibrator;
|
||||||
|
|
||||||
|
public class BellUtil {
|
||||||
|
private static BellUtil instance = null;
|
||||||
|
private static final Object lock = new Object();
|
||||||
|
|
||||||
|
public static BellUtil getInstance(Context context) {
|
||||||
|
if (instance == null) {
|
||||||
|
synchronized (lock) {
|
||||||
|
if (instance == null) {
|
||||||
|
instance = new BellUtil((Vibrator) context.getApplicationContext().getSystemService(Context.VIBRATOR_SERVICE));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final long DURATION = 50;
|
||||||
|
private static final long MIN_PAUSE = 3 * DURATION;
|
||||||
|
|
||||||
|
private final Handler handler = new Handler(Looper.getMainLooper());
|
||||||
|
private long lastBell = 0;
|
||||||
|
private final Runnable bellRunnable;
|
||||||
|
|
||||||
|
private BellUtil(final Vibrator vibrator) {
|
||||||
|
bellRunnable = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (vibrator != null) {
|
||||||
|
vibrator.vibrate(DURATION);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void doBell() {
|
||||||
|
long now = now();
|
||||||
|
long timeSinceLastBell = now - lastBell;
|
||||||
|
|
||||||
|
if (timeSinceLastBell < 0) {
|
||||||
|
// there is a next bell pending; don't schedule another one
|
||||||
|
} else if (timeSinceLastBell < MIN_PAUSE) {
|
||||||
|
// there was a bell recently, scheudle the next one
|
||||||
|
handler.postDelayed(bellRunnable, MIN_PAUSE - timeSinceLastBell);
|
||||||
|
lastBell = lastBell + MIN_PAUSE;
|
||||||
|
} else {
|
||||||
|
// the last bell was long ago, do it now
|
||||||
|
bellRunnable.run();
|
||||||
|
lastBell = now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private long now() {
|
||||||
|
return SystemClock.uptimeMillis();
|
||||||
|
}
|
||||||
|
}
|
||||||
340
app/src/main/java/com/termux/app/ExtraKeysInfos.java
Normal file
340
app/src/main/java/com/termux/app/ExtraKeysInfos.java
Normal file
@@ -0,0 +1,340 @@
|
|||||||
|
package com.termux.app;
|
||||||
|
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class ExtraKeysInfos {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Matrix of buttons displayed
|
||||||
|
*/
|
||||||
|
private ExtraKeyButton[][] buttons;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This corresponds to one of the CharMapDisplay below
|
||||||
|
*/
|
||||||
|
private String style = "default";
|
||||||
|
|
||||||
|
public ExtraKeysInfos(String propertiesInfo, String style) throws JSONException {
|
||||||
|
this.style = style;
|
||||||
|
|
||||||
|
// Convert String propertiesInfo to Array of Arrays
|
||||||
|
JSONArray arr = new JSONArray(propertiesInfo);
|
||||||
|
Object[][] matrix = new Object[arr.length()][];
|
||||||
|
for (int i = 0; i < arr.length(); i++) {
|
||||||
|
JSONArray line = arr.getJSONArray(i);
|
||||||
|
matrix[i] = new Object[line.length()];
|
||||||
|
for (int j = 0; j < line.length(); j++) {
|
||||||
|
matrix[i][j] = line.get(j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert matrix to buttons
|
||||||
|
this.buttons = new ExtraKeyButton[matrix.length][];
|
||||||
|
for (int i = 0; i < matrix.length; i++) {
|
||||||
|
this.buttons[i] = new ExtraKeyButton[matrix[i].length];
|
||||||
|
for (int j = 0; j < matrix[i].length; j++) {
|
||||||
|
Object key = matrix[i][j];
|
||||||
|
|
||||||
|
JSONObject jobject = normalizeKeyConfig(key);
|
||||||
|
|
||||||
|
ExtraKeyButton button;
|
||||||
|
|
||||||
|
if(! jobject.has("popup")) {
|
||||||
|
// no popup
|
||||||
|
button = new ExtraKeyButton(getSelectedCharMap(), jobject);
|
||||||
|
} else {
|
||||||
|
// a popup
|
||||||
|
JSONObject popupJobject = normalizeKeyConfig(jobject.get("popup"));
|
||||||
|
ExtraKeyButton popup = new ExtraKeyButton(getSelectedCharMap(), popupJobject);
|
||||||
|
button = new ExtraKeyButton(getSelectedCharMap(), jobject, popup);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.buttons[i][j] = button;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* "hello" -> {"key": "hello"}
|
||||||
|
*/
|
||||||
|
private static JSONObject normalizeKeyConfig(Object key) throws JSONException {
|
||||||
|
JSONObject jobject;
|
||||||
|
if(key instanceof String) {
|
||||||
|
jobject = new JSONObject();
|
||||||
|
jobject.put("key", key);
|
||||||
|
} else if(key instanceof JSONObject) {
|
||||||
|
jobject = (JSONObject) key;
|
||||||
|
} else {
|
||||||
|
throw new JSONException("An key in the extra-key matrix must be a string or an object");
|
||||||
|
}
|
||||||
|
return jobject;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExtraKeyButton[][] getMatrix() {
|
||||||
|
return buttons;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HashMap that implements Python dict.get(key, default) function.
|
||||||
|
* Default java.util .get(key) is then the same as .get(key, null);
|
||||||
|
*/
|
||||||
|
static class CleverMap<K,V> extends HashMap<K,V> {
|
||||||
|
V get(K key, V defaultValue) {
|
||||||
|
if(containsKey(key))
|
||||||
|
return get(key);
|
||||||
|
else
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class CharDisplayMap extends CleverMap<String, String> {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keys are displayed in a natural looking way, like "→" for "RIGHT"
|
||||||
|
*/
|
||||||
|
static final CharDisplayMap classicArrowsDisplay = new CharDisplayMap() {{
|
||||||
|
// classic arrow keys (for ◀ ▶ ▲ ▼ @see arrowVariationDisplay)
|
||||||
|
put("LEFT", "←"); // U+2190 ← LEFTWARDS ARROW
|
||||||
|
put("RIGHT", "→"); // U+2192 → RIGHTWARDS ARROW
|
||||||
|
put("UP", "↑"); // U+2191 ↑ UPWARDS ARROW
|
||||||
|
put("DOWN", "↓"); // U+2193 ↓ DOWNWARDS ARROW
|
||||||
|
}};
|
||||||
|
|
||||||
|
static final CharDisplayMap wellKnownCharactersDisplay = new CharDisplayMap() {{
|
||||||
|
// well known characters // https://en.wikipedia.org/wiki/{Enter_key, Tab_key, Delete_key}
|
||||||
|
put("ENTER", "↲"); // U+21B2 ↲ DOWNWARDS ARROW WITH TIP LEFTWARDS
|
||||||
|
put("TAB", "↹"); // U+21B9 ↹ LEFTWARDS ARROW TO BAR OVER RIGHTWARDS ARROW TO BAR
|
||||||
|
put("BKSP", "⌫"); // U+232B ⌫ ERASE TO THE LEFT sometimes seen and easy to understand
|
||||||
|
put("DEL", "⌦"); // U+2326 ⌦ ERASE TO THE RIGHT not well known but easy to understand
|
||||||
|
put("DRAWER", "☰"); // U+2630 ☰ TRIGRAM FOR HEAVEN not well known but easy to understand
|
||||||
|
put("KEYBOARD", "⌨"); // U+2328 ⌨ KEYBOARD not well known but easy to understand
|
||||||
|
}};
|
||||||
|
|
||||||
|
static final CharDisplayMap lessKnownCharactersDisplay = new CharDisplayMap() {{
|
||||||
|
// https://en.wikipedia.org/wiki/{Home_key, End_key, Page_Up_and_Page_Down_keys}
|
||||||
|
// home key can mean "goto the beginning of line" or "goto first page" depending on context, hence the diagonal
|
||||||
|
put("HOME", "⇱"); // from IEC 9995 // U+21F1 ⇱ NORTH WEST ARROW TO CORNER
|
||||||
|
put("END", "⇲"); // from IEC 9995 // ⇲ // U+21F2 ⇲ SOUTH EAST ARROW TO CORNER
|
||||||
|
put("PGUP", "⇑"); // no ISO character exists, U+21D1 ⇑ UPWARDS DOUBLE ARROW will do the trick
|
||||||
|
put("PGDN", "⇓"); // no ISO character exists, U+21D3 ⇓ DOWNWARDS DOUBLE ARROW will do the trick
|
||||||
|
}};
|
||||||
|
|
||||||
|
static final CharDisplayMap arrowTriangleVariationDisplay = new CharDisplayMap() {{
|
||||||
|
// alternative to classic arrow keys
|
||||||
|
put("LEFT", "◀"); // U+25C0 ◀ BLACK LEFT-POINTING TRIANGLE
|
||||||
|
put("RIGHT", "▶"); // U+25B6 ▶ BLACK RIGHT-POINTING TRIANGLE
|
||||||
|
put("UP", "▲"); // U+25B2 ▲ BLACK UP-POINTING TRIANGLE
|
||||||
|
put("DOWN", "▼"); // U+25BC ▼ BLACK DOWN-POINTING TRIANGLE
|
||||||
|
}};
|
||||||
|
|
||||||
|
static final CharDisplayMap notKnownIsoCharacters = new CharDisplayMap() {{
|
||||||
|
// Control chars that are more clear as text // https://en.wikipedia.org/wiki/{Function_key, Alt_key, Control_key, Esc_key}
|
||||||
|
// put("FN", "FN"); // no ISO character exists
|
||||||
|
put("CTRL", "⎈"); // ISO character "U+2388 ⎈ HELM SYMBOL" is unknown to people and never printed on computers, however "U+25C7 ◇ WHITE DIAMOND" is a nice presentation, and "^" for terminal app and mac is often used
|
||||||
|
put("ALT", "⎇"); // ISO character "U+2387 ⎇ ALTERNATIVE KEY SYMBOL'" is unknown to people and only printed as the Option key "⌥" on Mac computer
|
||||||
|
put("ESC", "⎋"); // ISO character "U+238B ⎋ BROKEN CIRCLE WITH NORTHWEST ARROW" is unknown to people and not often printed on computers
|
||||||
|
}};
|
||||||
|
|
||||||
|
static final CharDisplayMap nicerLookingDisplay = new CharDisplayMap() {{
|
||||||
|
// nicer looking for most cases
|
||||||
|
put("-", "―"); // U+2015 ― HORIZONTAL BAR
|
||||||
|
}};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Multiple maps are available to quickly change
|
||||||
|
* the style of the keys.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Some classic symbols everybody knows
|
||||||
|
*/
|
||||||
|
private static final CharDisplayMap defaultCharDisplay = new CharDisplayMap() {{
|
||||||
|
putAll(classicArrowsDisplay);
|
||||||
|
putAll(wellKnownCharactersDisplay);
|
||||||
|
putAll(nicerLookingDisplay);
|
||||||
|
// all other characters are displayed as themselves
|
||||||
|
}};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Classic symbols and less known symbols
|
||||||
|
*/
|
||||||
|
private static final CharDisplayMap lotsOfArrowsCharDisplay = new CharDisplayMap() {{
|
||||||
|
putAll(classicArrowsDisplay);
|
||||||
|
putAll(wellKnownCharactersDisplay);
|
||||||
|
putAll(lessKnownCharactersDisplay); // NEW
|
||||||
|
putAll(nicerLookingDisplay);
|
||||||
|
}};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Only arrows
|
||||||
|
*/
|
||||||
|
private static final CharDisplayMap arrowsOnlyCharDisplay = new CharDisplayMap() {{
|
||||||
|
putAll(classicArrowsDisplay);
|
||||||
|
// putAll(wellKnownCharactersDisplay); // REMOVED
|
||||||
|
// putAll(lessKnownCharactersDisplay); // REMOVED
|
||||||
|
putAll(nicerLookingDisplay);
|
||||||
|
}};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Full Iso
|
||||||
|
*/
|
||||||
|
private static final CharDisplayMap fullIsoCharDisplay = new CharDisplayMap() {{
|
||||||
|
putAll(classicArrowsDisplay);
|
||||||
|
putAll(wellKnownCharactersDisplay);
|
||||||
|
putAll(lessKnownCharactersDisplay); // NEW
|
||||||
|
putAll(nicerLookingDisplay);
|
||||||
|
putAll(notKnownIsoCharacters); // NEW
|
||||||
|
}};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Some people might call our keys differently
|
||||||
|
*/
|
||||||
|
static private final CharDisplayMap controlCharsAliases = new CharDisplayMap() {{
|
||||||
|
put("ESCAPE", "ESC");
|
||||||
|
put("CONTROL", "CTRL");
|
||||||
|
put("RETURN", "ENTER"); // Technically different keys, but most applications won't see the difference
|
||||||
|
put("FUNCTION", "FN");
|
||||||
|
// no alias for ALT
|
||||||
|
|
||||||
|
// Directions are sometimes written as first and last letter for brevety
|
||||||
|
put("LT", "LEFT");
|
||||||
|
put("RT", "RIGHT");
|
||||||
|
put("DN", "DOWN");
|
||||||
|
// put("UP", "UP"); well, "UP" is already two letters
|
||||||
|
|
||||||
|
put("PAGEUP", "PGUP");
|
||||||
|
put("PAGE_UP", "PGUP");
|
||||||
|
put("PAGE UP", "PGUP");
|
||||||
|
put("PAGE-UP", "PGUP");
|
||||||
|
|
||||||
|
// no alias for HOME
|
||||||
|
// no alias for END
|
||||||
|
|
||||||
|
put("PAGEDOWN", "PGDN");
|
||||||
|
put("PAGE_DOWN", "PGDN");
|
||||||
|
put("PAGE-DOWN", "PGDN");
|
||||||
|
|
||||||
|
put("DELETE", "DEL");
|
||||||
|
put("BACKSPACE", "BKSP");
|
||||||
|
|
||||||
|
// easier for writing in termux.properties
|
||||||
|
put("BACKSLASH", "\\");
|
||||||
|
put("QUOTE", "\"");
|
||||||
|
put("APOSTROPHE", "'");
|
||||||
|
}};
|
||||||
|
|
||||||
|
CharDisplayMap getSelectedCharMap() {
|
||||||
|
switch (style) {
|
||||||
|
case "arrows-only":
|
||||||
|
return arrowsOnlyCharDisplay;
|
||||||
|
case "arrows-all":
|
||||||
|
return lotsOfArrowsCharDisplay;
|
||||||
|
case "all":
|
||||||
|
return fullIsoCharDisplay;
|
||||||
|
case "none":
|
||||||
|
return new CharDisplayMap();
|
||||||
|
default:
|
||||||
|
return defaultCharDisplay;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies the 'controlCharsAliases' mapping to all the strings in *buttons*
|
||||||
|
* Modifies the array, doesn't return a new one.
|
||||||
|
*/
|
||||||
|
public static String replaceAlias(String key) {
|
||||||
|
return controlCharsAliases.get(key, key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ExtraKeyButton {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The key that will be sent to the terminal, either a control character
|
||||||
|
* defined in ExtraKeysView.keyCodesForString (LEFT, RIGHT, PGUP...) or
|
||||||
|
* some text.
|
||||||
|
*/
|
||||||
|
private String key;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the key is a macro, i.e. a sequence of keys separated by space.
|
||||||
|
*/
|
||||||
|
private boolean macro;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The text that will be shown on the button.
|
||||||
|
*/
|
||||||
|
private String display;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The information of the popup (triggered by swipe up).
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
private ExtraKeyButton popup = null;
|
||||||
|
|
||||||
|
public ExtraKeyButton(ExtraKeysInfos.CharDisplayMap charDisplayMap, JSONObject config) throws JSONException {
|
||||||
|
this(charDisplayMap, config, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExtraKeyButton(ExtraKeysInfos.CharDisplayMap charDisplayMap, JSONObject config, ExtraKeyButton popup) throws JSONException {
|
||||||
|
String keyFromConfig = config.optString("key", null);
|
||||||
|
String macroFromConfig = config.optString("macro", null);
|
||||||
|
String[] keys;
|
||||||
|
if (keyFromConfig != null && macroFromConfig != null) {
|
||||||
|
throw new JSONException("Both key and macro can't be set for the same key");
|
||||||
|
} else if (keyFromConfig != null) {
|
||||||
|
keys = new String[]{keyFromConfig};
|
||||||
|
this.macro = false;
|
||||||
|
} else if (macroFromConfig != null) {
|
||||||
|
keys = macroFromConfig.split(" ");
|
||||||
|
this.macro = true;
|
||||||
|
} else {
|
||||||
|
throw new JSONException("All keys have to specify either key or macro");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < keys.length; i++) {
|
||||||
|
keys[i] = ExtraKeysInfos.replaceAlias(keys[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.key = TextUtils.join(" ", keys);
|
||||||
|
|
||||||
|
String displayFromConfig = config.optString("display", null);
|
||||||
|
if (displayFromConfig != null) {
|
||||||
|
this.display = displayFromConfig;
|
||||||
|
} else {
|
||||||
|
this.display = Arrays.stream(keys)
|
||||||
|
.map(key -> charDisplayMap.get(key, key))
|
||||||
|
.collect(Collectors.joining(" "));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.popup = popup;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getKey() {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isMacro() {
|
||||||
|
return macro;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDisplay() {
|
||||||
|
return display;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public ExtraKeyButton getPopup() {
|
||||||
|
return popup;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
package com.termux.app;
|
package com.termux.app;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
import android.provider.Settings;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
|
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
@@ -12,19 +14,22 @@ import java.util.Map;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import android.view.Gravity;
|
||||||
import android.view.HapticFeedbackConstants;
|
import android.view.HapticFeedbackConstants;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import android.view.inputmethod.InputMethodManager;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.GridLayout;
|
import android.widget.GridLayout;
|
||||||
import android.widget.PopupWindow;
|
import android.widget.PopupWindow;
|
||||||
import android.widget.ToggleButton;
|
import android.widget.ToggleButton;
|
||||||
|
|
||||||
import com.termux.R;
|
import com.termux.R;
|
||||||
import com.termux.terminal.TerminalSession;
|
|
||||||
import com.termux.view.TerminalView;
|
import com.termux.view.TerminalView;
|
||||||
|
|
||||||
|
import androidx.drawerlayout.widget.DrawerLayout;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A view showing extra keys (such as Escape, Ctrl, Alt) not normally available on an Android soft
|
* A view showing extra keys (such as Escape, Ctrl, Alt) not normally available on an Android soft
|
||||||
* keyboard.
|
* keyboard.
|
||||||
@@ -34,31 +39,14 @@ public final class ExtraKeysView extends GridLayout {
|
|||||||
private static final int TEXT_COLOR = 0xFFFFFFFF;
|
private static final int TEXT_COLOR = 0xFFFFFFFF;
|
||||||
private static final int BUTTON_COLOR = 0x00000000;
|
private static final int BUTTON_COLOR = 0x00000000;
|
||||||
private static final int INTERESTING_COLOR = 0xFF80DEEA;
|
private static final int INTERESTING_COLOR = 0xFF80DEEA;
|
||||||
private static final int BUTTON_PRESSED_COLOR = 0x7FFFFFFF;
|
private static final int BUTTON_PRESSED_COLOR = 0xFF7F7F7F;
|
||||||
|
|
||||||
public ExtraKeysView(Context context, AttributeSet attrs) {
|
public ExtraKeysView(Context context, AttributeSet attrs) {
|
||||||
super(context, attrs);
|
super(context, attrs);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* HashMap that implements Python dict.get(key, default) function.
|
|
||||||
* Default java.util .get(key) is then the same as .get(key, null);
|
|
||||||
*/
|
|
||||||
static class CleverMap<K,V> extends HashMap<K,V> {
|
|
||||||
V get(K key, V defaultValue) {
|
|
||||||
if(containsKey(key))
|
|
||||||
return get(key);
|
|
||||||
else
|
|
||||||
return defaultValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static class CharDisplayMap extends CleverMap<String, String> {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Keys are displayed in a natural looking way, like "→" for "RIGHT"
|
|
||||||
*/
|
|
||||||
static final Map<String, Integer> keyCodesForString = new HashMap<String, Integer>() {{
|
static final Map<String, Integer> keyCodesForString = new HashMap<String, Integer>() {{
|
||||||
|
put("SPACE", KeyEvent.KEYCODE_SPACE);
|
||||||
put("ESC", KeyEvent.KEYCODE_ESCAPE);
|
put("ESC", KeyEvent.KEYCODE_ESCAPE);
|
||||||
put("TAB", KeyEvent.KEYCODE_TAB);
|
put("TAB", KeyEvent.KEYCODE_TAB);
|
||||||
put("HOME", KeyEvent.KEYCODE_MOVE_HOME);
|
put("HOME", KeyEvent.KEYCODE_MOVE_HOME);
|
||||||
@@ -73,46 +61,92 @@ public final class ExtraKeysView extends GridLayout {
|
|||||||
put("RIGHT", KeyEvent.KEYCODE_DPAD_RIGHT);
|
put("RIGHT", KeyEvent.KEYCODE_DPAD_RIGHT);
|
||||||
put("DOWN", KeyEvent.KEYCODE_DPAD_DOWN);
|
put("DOWN", KeyEvent.KEYCODE_DPAD_DOWN);
|
||||||
put("ENTER", KeyEvent.KEYCODE_ENTER);
|
put("ENTER", KeyEvent.KEYCODE_ENTER);
|
||||||
|
put("F1", KeyEvent.KEYCODE_F1);
|
||||||
|
put("F2", KeyEvent.KEYCODE_F2);
|
||||||
|
put("F3", KeyEvent.KEYCODE_F3);
|
||||||
|
put("F4", KeyEvent.KEYCODE_F4);
|
||||||
|
put("F5", KeyEvent.KEYCODE_F5);
|
||||||
|
put("F6", KeyEvent.KEYCODE_F6);
|
||||||
|
put("F7", KeyEvent.KEYCODE_F7);
|
||||||
|
put("F8", KeyEvent.KEYCODE_F8);
|
||||||
|
put("F9", KeyEvent.KEYCODE_F9);
|
||||||
|
put("F10", KeyEvent.KEYCODE_F10);
|
||||||
|
put("F11", KeyEvent.KEYCODE_F11);
|
||||||
|
put("F12", KeyEvent.KEYCODE_F12);
|
||||||
}};
|
}};
|
||||||
|
|
||||||
static void sendKey(View view, String keyName) {
|
private void sendKey(View view, String keyName, boolean forceCtrlDown, boolean forceLeftAltDown) {
|
||||||
TerminalView terminalView = view.findViewById(R.id.terminal_view);
|
TerminalView terminalView = view.findViewById(R.id.terminal_view);
|
||||||
if (keyCodesForString.containsKey(keyName)) {
|
if ("KEYBOARD".equals(keyName)) {
|
||||||
|
InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||||
|
imm.toggleSoftInput(0, 0);
|
||||||
|
} else if ("DRAWER".equals(keyName)) {
|
||||||
|
DrawerLayout drawer = view.findViewById(R.id.drawer_layout);
|
||||||
|
drawer.openDrawer(Gravity.LEFT);
|
||||||
|
} else if (keyCodesForString.containsKey(keyName)) {
|
||||||
int keyCode = keyCodesForString.get(keyName);
|
int keyCode = keyCodesForString.get(keyName);
|
||||||
terminalView.onKeyDown(keyCode, new KeyEvent(KeyEvent.ACTION_UP, keyCode));
|
int metaState = 0;
|
||||||
// view.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, keyCode));
|
if (forceCtrlDown) {
|
||||||
|
metaState |= KeyEvent.META_CTRL_ON | KeyEvent.META_CTRL_LEFT_ON;
|
||||||
|
}
|
||||||
|
if (forceLeftAltDown) {
|
||||||
|
metaState |= KeyEvent.META_ALT_ON | KeyEvent.META_ALT_LEFT_ON;
|
||||||
|
}
|
||||||
|
KeyEvent keyEvent = new KeyEvent(0, 0, KeyEvent.ACTION_UP, keyCode, 0, metaState);
|
||||||
|
terminalView.onKeyDown(keyCode, keyEvent);
|
||||||
} else {
|
} else {
|
||||||
// not a control char
|
// not a control char
|
||||||
TerminalSession session = terminalView.getCurrentSession();
|
keyName.codePoints().forEach(codePoint -> {
|
||||||
if (session != null && keyName.length() > 0)
|
terminalView.inputCodePoint(codePoint, forceCtrlDown, forceLeftAltDown);
|
||||||
session.write(keyName);
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void sendKey(View view, ExtraKeyButton buttonInfo) {
|
||||||
|
if (buttonInfo.isMacro()) {
|
||||||
|
String[] keys = buttonInfo.getKey().split(" ");
|
||||||
|
boolean ctrlDown = false;
|
||||||
|
boolean altDown = false;
|
||||||
|
for (String key : keys) {
|
||||||
|
if ("CTRL".equals(key)) {
|
||||||
|
ctrlDown = true;
|
||||||
|
} else if ("ALT".equals(key)) {
|
||||||
|
altDown = true;
|
||||||
|
} else {
|
||||||
|
sendKey(view, key, ctrlDown, altDown);
|
||||||
|
ctrlDown = false;
|
||||||
|
altDown = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sendKey(view, buttonInfo.getKey(), false, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public enum SpecialButton {
|
public enum SpecialButton {
|
||||||
CTRL, ALT, FN
|
CTRL, ALT, FN
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class SpecialButtonState {
|
private static class SpecialButtonState {
|
||||||
boolean isOn = false;
|
boolean isOn = false;
|
||||||
ToggleButton button = null;
|
ToggleButton button = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<SpecialButton, SpecialButtonState> specialButtons = new HashMap<SpecialButton, SpecialButtonState>() {{
|
private Map<SpecialButton, SpecialButtonState> specialButtons = new HashMap<SpecialButton, SpecialButtonState>() {{
|
||||||
put(SpecialButton.CTRL, new SpecialButtonState());
|
put(SpecialButton.CTRL, new SpecialButtonState());
|
||||||
put(SpecialButton.ALT, new SpecialButtonState());
|
put(SpecialButton.ALT, new SpecialButtonState());
|
||||||
put(SpecialButton.FN, new SpecialButtonState());
|
put(SpecialButton.FN, new SpecialButtonState());
|
||||||
}};
|
}};
|
||||||
|
|
||||||
private ScheduledExecutorService scheduledExecutor;
|
private ScheduledExecutorService scheduledExecutor;
|
||||||
private PopupWindow popupWindow;
|
private PopupWindow popupWindow;
|
||||||
private int longPressCount;
|
private int longPressCount;
|
||||||
|
|
||||||
public boolean readSpecialButton(SpecialButton name) {
|
public boolean readSpecialButton(SpecialButton name) {
|
||||||
SpecialButtonState state = specialButtons.get(name);
|
SpecialButtonState state = specialButtons.get(name);
|
||||||
if (state == null)
|
if (state == null)
|
||||||
throw new RuntimeException("Must be a valid special button (see source)");
|
throw new RuntimeException("Must be a valid special button (see source)");
|
||||||
|
|
||||||
if (! state.isOn)
|
if (! state.isOn)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@@ -153,145 +187,21 @@ public final class ExtraKeysView extends GridLayout {
|
|||||||
popupWindow.setFocusable(false);
|
popupWindow.setFocusable(false);
|
||||||
popupWindow.showAsDropDown(view, 0, -2 * height);
|
popupWindow.showAsDropDown(view, 0, -2 * height);
|
||||||
}
|
}
|
||||||
|
|
||||||
static final CharDisplayMap classicArrowsDisplay = new CharDisplayMap() {{
|
|
||||||
// classic arrow keys (for ◀ ▶ ▲ ▼ @see arrowVariationDisplay)
|
|
||||||
put("LEFT", "←"); // U+2190 ← LEFTWARDS ARROW
|
|
||||||
put("RIGHT", "→"); // U+2192 → RIGHTWARDS ARROW
|
|
||||||
put("UP", "↑"); // U+2191 ↑ UPWARDS ARROW
|
|
||||||
put("DOWN", "↓"); // U+2193 ↓ DOWNWARDS ARROW
|
|
||||||
}};
|
|
||||||
|
|
||||||
static final CharDisplayMap wellKnownCharactersDisplay = new CharDisplayMap() {{
|
|
||||||
// well known characters // https://en.wikipedia.org/wiki/{Enter_key, Tab_key, Delete_key}
|
|
||||||
put("ENTER", "↲"); // U+21B2 ↲ DOWNWARDS ARROW WITH TIP LEFTWARDS
|
|
||||||
put("TAB", "↹"); // U+21B9 ↹ LEFTWARDS ARROW TO BAR OVER RIGHTWARDS ARROW TO BAR
|
|
||||||
put("BKSP", "⌫"); // U+232B ⌫ ERASE TO THE LEFT sometimes seen and easy to understand
|
|
||||||
put("DEL", "⌦"); // U+2326 ⌦ ERASE TO THE RIGHT not well known but easy to understand
|
|
||||||
}};
|
|
||||||
|
|
||||||
static final CharDisplayMap lessKnownCharactersDisplay = new CharDisplayMap() {{
|
|
||||||
// https://en.wikipedia.org/wiki/{Home_key, End_key, Page_Up_and_Page_Down_keys}
|
|
||||||
// home key can mean "goto the beginning of line" or "goto first page" depending on context, hence the diagonal
|
|
||||||
put("HOME", "⇱"); // from IEC 9995 // U+21F1 ⇱ NORTH WEST ARROW TO CORNER
|
|
||||||
put("END", "⇲"); // from IEC 9995 // ⇲ // U+21F2 ⇲ SOUTH EAST ARROW TO CORNER
|
|
||||||
put("PGUP", "⇑"); // no ISO character exists, U+21D1 ⇑ UPWARDS DOUBLE ARROW will do the trick
|
|
||||||
put("PGDN", "⇓"); // no ISO character exists, U+21D3 ⇓ DOWNWARDS DOUBLE ARROW will do the trick
|
|
||||||
}};
|
|
||||||
|
|
||||||
static final CharDisplayMap arrowTriangleVariationDisplay = new CharDisplayMap() {{
|
|
||||||
// alternative to classic arrow keys
|
|
||||||
put("LEFT", "◀"); // U+25C0 ◀ BLACK LEFT-POINTING TRIANGLE
|
|
||||||
put("RIGHT", "▶"); // U+25B6 ▶ BLACK RIGHT-POINTING TRIANGLE
|
|
||||||
put("UP", "▲"); // U+25B2 ▲ BLACK UP-POINTING TRIANGLE
|
|
||||||
put("DOWN", "▼"); // U+25BC ▼ BLACK DOWN-POINTING TRIANGLE
|
|
||||||
}};
|
|
||||||
|
|
||||||
static final CharDisplayMap notKnownIsoCharacters = new CharDisplayMap() {{
|
|
||||||
// Control chars that are more clear as text // https://en.wikipedia.org/wiki/{Function_key, Alt_key, Control_key, Esc_key}
|
|
||||||
// put("FN", "FN"); // no ISO character exists
|
|
||||||
put("CTRL", "⎈"); // ISO character "U+2388 ⎈ HELM SYMBOL" is unknown to people and never printed on computers, however "U+25C7 ◇ WHITE DIAMOND" is a nice presentation, and "^" for terminal app and mac is often used
|
|
||||||
put("ALT", "⎇"); // ISO character "U+2387 ⎇ ALTERNATIVE KEY SYMBOL'" is unknown to people and only printed as the Option key "⌥" on Mac computer
|
|
||||||
put("ESC", "⎋"); // ISO character "U+238B ⎋ BROKEN CIRCLE WITH NORTHWEST ARROW" is unknown to people and not often printed on computers
|
|
||||||
}};
|
|
||||||
|
|
||||||
static final CharDisplayMap nicerLookingDisplay = new CharDisplayMap() {{
|
|
||||||
// nicer looking for most cases
|
|
||||||
put("-", "―"); // U+2015 ― HORIZONTAL BAR
|
|
||||||
}};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Keys are displayed in a natural looking way, like "→" for "RIGHT" or "↲" for ENTER
|
|
||||||
*/
|
|
||||||
public static final CharDisplayMap defaultCharDisplay = new CharDisplayMap() {{
|
|
||||||
putAll(classicArrowsDisplay);
|
|
||||||
putAll(wellKnownCharactersDisplay);
|
|
||||||
putAll(nicerLookingDisplay);
|
|
||||||
// all other characters are displayed as themselves
|
|
||||||
}};
|
|
||||||
|
|
||||||
public static final CharDisplayMap lotsOfArrowsCharDisplay = new CharDisplayMap() {{
|
|
||||||
putAll(classicArrowsDisplay);
|
|
||||||
putAll(wellKnownCharactersDisplay);
|
|
||||||
putAll(lessKnownCharactersDisplay); // NEW
|
|
||||||
putAll(nicerLookingDisplay);
|
|
||||||
}};
|
|
||||||
|
|
||||||
public static final CharDisplayMap arrowsOnlyCharDisplay = new CharDisplayMap() {{
|
|
||||||
putAll(classicArrowsDisplay);
|
|
||||||
// putAll(wellKnownCharactersDisplay); // REMOVED
|
|
||||||
// putAll(lessKnownCharactersDisplay); // REMOVED
|
|
||||||
putAll(nicerLookingDisplay);
|
|
||||||
}};
|
|
||||||
|
|
||||||
public static final CharDisplayMap fullIsoCharDisplay = new CharDisplayMap() {{
|
|
||||||
putAll(classicArrowsDisplay);
|
|
||||||
putAll(wellKnownCharactersDisplay);
|
|
||||||
putAll(lessKnownCharactersDisplay); // NEW
|
|
||||||
putAll(nicerLookingDisplay);
|
|
||||||
putAll(notKnownIsoCharacters); // NEW
|
|
||||||
}};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Some people might call our keys differently
|
|
||||||
*/
|
|
||||||
static final CharDisplayMap controlCharsAliases = new CharDisplayMap() {{
|
|
||||||
put("ESCAPE", "ESC");
|
|
||||||
put("CONTROL", "CTRL");
|
|
||||||
put("RETURN", "ENTER"); // Technically different keys, but most applications won't see the difference
|
|
||||||
put("FUNCTION", "FN");
|
|
||||||
// no alias for ALT
|
|
||||||
|
|
||||||
// Directions are sometimes written as first and last letter for brevety
|
|
||||||
put("LT", "LEFT");
|
|
||||||
put("RT", "RIGHT");
|
|
||||||
put("DN", "DOWN");
|
|
||||||
// put("UP", "UP"); well, "UP" is already two letters
|
|
||||||
|
|
||||||
put("PAGEUP", "PGUP");
|
|
||||||
put("PAGE_UP", "PGUP");
|
|
||||||
put("PAGE UP", "PGUP");
|
|
||||||
put("PAGE-UP", "PGUP");
|
|
||||||
|
|
||||||
// no alias for HOME
|
|
||||||
// no alias for END
|
|
||||||
|
|
||||||
put("PAGEDOWN", "PGDN");
|
|
||||||
put("PAGE_DOWN", "PGDN");
|
|
||||||
put("PAGE-DOWN", "PGDN");
|
|
||||||
|
|
||||||
put("DELETE", "DEL");
|
|
||||||
put("BACKSPACE", "BKSP");
|
|
||||||
|
|
||||||
// easier for writing in termux.properties
|
|
||||||
put("BACKSLASH", "\\");
|
|
||||||
put("QUOTE", "\"");
|
|
||||||
put("APOSTROPHE", "'");
|
|
||||||
}};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Applies the 'controlCharsAliases' mapping to all the strings in *buttons*
|
|
||||||
* Modifies the array, doesn't return a new one.
|
|
||||||
*/
|
|
||||||
void replaceAliases(String[][] buttons) {
|
|
||||||
for(int i = 0; i < buttons.length; i++)
|
|
||||||
for(int j = 0; j < buttons[i].length; j++)
|
|
||||||
buttons[i][j] = controlCharsAliases.get(buttons[i][j], buttons[i][j]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* General util function to compute the longest column length in a matrix.
|
* General util function to compute the longest column length in a matrix.
|
||||||
*/
|
*/
|
||||||
static int maximumLength(String[][] matrix) {
|
static int maximumLength(Object[][] matrix) {
|
||||||
int m = 0;
|
int m = 0;
|
||||||
for (String[] aMatrix : matrix) m = Math.max(m, aMatrix.length);
|
for (Object[] row : matrix)
|
||||||
|
m = Math.max(m, row.length);
|
||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reload the view given parameters in termux.properties
|
* Reload the view given parameters in termux.properties
|
||||||
*
|
*
|
||||||
* @param buttons matrix of String as defined in termux.properties extrakeys
|
* @param infos matrix as defined in termux.properties extrakeys
|
||||||
* Can Contain The Strings CTRL ALT TAB FN ENTER LEFT RIGHT UP DOWN or normal strings
|
* Can Contain The Strings CTRL ALT TAB FN ENTER LEFT RIGHT UP DOWN or normal strings
|
||||||
* Some aliases are possible like RETURN for ENTER, LT for LEFT and more (@see controlCharsAliases for the whole list).
|
* Some aliases are possible like RETURN for ENTER, LT for LEFT and more (@see controlCharsAliases for the whole list).
|
||||||
* Any string of length > 1 in total Uppercase will print a warning
|
* Any string of length > 1 in total Uppercase will print a warning
|
||||||
@@ -303,49 +213,61 @@ public final class ExtraKeysView extends GridLayout {
|
|||||||
* "−" will input a "−" character
|
* "−" will input a "−" character
|
||||||
* "-_-" will input the string "-_-"
|
* "-_-" will input the string "-_-"
|
||||||
*/
|
*/
|
||||||
void reload(String[][] buttons, CharDisplayMap charDisplayMap) {
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
|
void reload(ExtraKeysInfos infos) {
|
||||||
|
if(infos == null)
|
||||||
|
return;
|
||||||
|
|
||||||
for(SpecialButtonState state : specialButtons.values())
|
for(SpecialButtonState state : specialButtons.values())
|
||||||
state.button = null;
|
state.button = null;
|
||||||
|
|
||||||
removeAllViews();
|
removeAllViews();
|
||||||
|
|
||||||
replaceAliases(buttons); // modifies the array
|
|
||||||
|
|
||||||
final int rows = buttons.length;
|
ExtraKeyButton[][] buttons = infos.getMatrix();
|
||||||
final int cols = maximumLength(buttons);
|
|
||||||
|
|
||||||
setRowCount(rows);
|
setRowCount(buttons.length);
|
||||||
setColumnCount(cols);
|
setColumnCount(maximumLength(buttons));
|
||||||
|
|
||||||
for (int row = 0; row < rows; row++) {
|
for (int row = 0; row < buttons.length; row++) {
|
||||||
for (int col = 0; col < buttons[row].length; col++) {
|
for (int col = 0; col < buttons[row].length; col++) {
|
||||||
final String buttonText = buttons[row][col];
|
final ExtraKeyButton buttonInfo = buttons[row][col];
|
||||||
|
|
||||||
Button button;
|
Button button;
|
||||||
if(Arrays.asList("CTRL", "ALT", "FN").contains(buttonText)) {
|
if(Arrays.asList("CTRL", "ALT", "FN").contains(buttonInfo.getKey())) {
|
||||||
SpecialButtonState state = specialButtons.get(SpecialButton.valueOf(buttonText)); // for valueOf: https://stackoverflow.com/a/604426/1980630
|
SpecialButtonState state = specialButtons.get(SpecialButton.valueOf(buttonInfo.getKey())); // for valueOf: https://stackoverflow.com/a/604426/1980630
|
||||||
state.isOn = true;
|
state.isOn = true;
|
||||||
button = state.button = new ToggleButton(getContext(), null, android.R.attr.buttonBarButtonStyle);
|
button = state.button = new ToggleButton(getContext(), null, android.R.attr.buttonBarButtonStyle);
|
||||||
button.setClickable(true);
|
button.setClickable(true);
|
||||||
} else {
|
} else {
|
||||||
button = new Button(getContext(), null, android.R.attr.buttonBarButtonStyle);
|
button = new Button(getContext(), null, android.R.attr.buttonBarButtonStyle);
|
||||||
}
|
}
|
||||||
|
|
||||||
final String displayedText = charDisplayMap.get(buttonText, buttonText);
|
button.setText(buttonInfo.getDisplay());
|
||||||
button.setText(displayedText);
|
|
||||||
button.setTextColor(TEXT_COLOR);
|
button.setTextColor(TEXT_COLOR);
|
||||||
button.setPadding(0, 0, 0, 0);
|
button.setPadding(0, 0, 0, 0);
|
||||||
|
|
||||||
final Button finalButton = button;
|
final Button finalButton = button;
|
||||||
button.setOnClickListener(v -> {
|
button.setOnClickListener(v -> {
|
||||||
finalButton.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP);
|
if (Settings.System.getInt(getContext().getContentResolver(),
|
||||||
|
Settings.System.HAPTIC_FEEDBACK_ENABLED, 0) != 0) {
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= 28) {
|
||||||
|
finalButton.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP);
|
||||||
|
} else {
|
||||||
|
// Perform haptic feedback only if no total silence mode enabled.
|
||||||
|
if (Settings.Global.getInt(getContext().getContentResolver(), "zen_mode", 0) != 2) {
|
||||||
|
finalButton.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
View root = getRootView();
|
View root = getRootView();
|
||||||
if(Arrays.asList("CTRL", "ALT", "FN").contains(buttonText)) {
|
if (Arrays.asList("CTRL", "ALT", "FN").contains(buttonInfo.getKey())) {
|
||||||
ToggleButton self = (ToggleButton) finalButton;
|
ToggleButton self = (ToggleButton) finalButton;
|
||||||
self.setChecked(self.isChecked());
|
self.setChecked(self.isChecked());
|
||||||
self.setTextColor(self.isChecked() ? INTERESTING_COLOR : TEXT_COLOR);
|
self.setTextColor(self.isChecked() ? INTERESTING_COLOR : TEXT_COLOR);
|
||||||
} else {
|
} else {
|
||||||
sendKey(root, buttonText);
|
sendKey(root, buttonInfo);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -355,22 +277,26 @@ public final class ExtraKeysView extends GridLayout {
|
|||||||
case MotionEvent.ACTION_DOWN:
|
case MotionEvent.ACTION_DOWN:
|
||||||
longPressCount = 0;
|
longPressCount = 0;
|
||||||
v.setBackgroundColor(BUTTON_PRESSED_COLOR);
|
v.setBackgroundColor(BUTTON_PRESSED_COLOR);
|
||||||
if (Arrays.asList("UP", "DOWN", "LEFT", "RIGHT").contains(buttonText)) {
|
if (Arrays.asList("UP", "DOWN", "LEFT", "RIGHT", "BKSP", "DEL").contains(buttonInfo.getKey())) {
|
||||||
|
// autorepeat
|
||||||
scheduledExecutor = Executors.newSingleThreadScheduledExecutor();
|
scheduledExecutor = Executors.newSingleThreadScheduledExecutor();
|
||||||
scheduledExecutor.scheduleWithFixedDelay(() -> {
|
scheduledExecutor.scheduleWithFixedDelay(() -> {
|
||||||
longPressCount++;
|
longPressCount++;
|
||||||
sendKey(root, buttonText);
|
sendKey(root, buttonInfo);
|
||||||
}, 400, 80, TimeUnit.MILLISECONDS);
|
}, 400, 80, TimeUnit.MILLISECONDS);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case MotionEvent.ACTION_MOVE:
|
case MotionEvent.ACTION_MOVE:
|
||||||
// These two keys have a Move-Up button appearing
|
if (buttonInfo.getPopup() != null) {
|
||||||
if (Arrays.asList("/", "-").contains(buttonText)) {
|
|
||||||
if (popupWindow == null && event.getY() < 0) {
|
if (popupWindow == null && event.getY() < 0) {
|
||||||
|
if (scheduledExecutor != null) {
|
||||||
|
scheduledExecutor.shutdownNow();
|
||||||
|
scheduledExecutor = null;
|
||||||
|
}
|
||||||
v.setBackgroundColor(BUTTON_COLOR);
|
v.setBackgroundColor(BUTTON_COLOR);
|
||||||
String text = "-".equals(buttonText) ? "|" : "\\";
|
String extraButtonDisplayedText = buttonInfo.getPopup().getDisplay();
|
||||||
popup(v, text);
|
popup(v, extraButtonDisplayedText);
|
||||||
}
|
}
|
||||||
if (popupWindow != null && event.getY() > 0) {
|
if (popupWindow != null && event.getY() > 0) {
|
||||||
v.setBackgroundColor(BUTTON_PRESSED_COLOR);
|
v.setBackgroundColor(BUTTON_PRESSED_COLOR);
|
||||||
@@ -380,19 +306,27 @@ public final class ExtraKeysView extends GridLayout {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case MotionEvent.ACTION_UP:
|
|
||||||
case MotionEvent.ACTION_CANCEL:
|
case MotionEvent.ACTION_CANCEL:
|
||||||
v.setBackgroundColor(BUTTON_COLOR);
|
v.setBackgroundColor(BUTTON_COLOR);
|
||||||
if (scheduledExecutor != null) {
|
if (scheduledExecutor != null) {
|
||||||
scheduledExecutor.shutdownNow();
|
scheduledExecutor.shutdownNow();
|
||||||
scheduledExecutor = null;
|
scheduledExecutor = null;
|
||||||
}
|
}
|
||||||
if (longPressCount == 0) {
|
return true;
|
||||||
if (popupWindow != null && Arrays.asList("/", "-").contains(buttonText)) {
|
case MotionEvent.ACTION_UP:
|
||||||
|
v.setBackgroundColor(BUTTON_COLOR);
|
||||||
|
if (scheduledExecutor != null) {
|
||||||
|
scheduledExecutor.shutdownNow();
|
||||||
|
scheduledExecutor = null;
|
||||||
|
}
|
||||||
|
if (longPressCount == 0 || popupWindow != null) {
|
||||||
|
if (popupWindow != null) {
|
||||||
popupWindow.setContentView(null);
|
popupWindow.setContentView(null);
|
||||||
popupWindow.dismiss();
|
popupWindow.dismiss();
|
||||||
popupWindow = null;
|
popupWindow = null;
|
||||||
sendKey(root, "-".equals(buttonText) ? "|" : "\\");
|
if (buttonInfo.getPopup() != null) {
|
||||||
|
sendKey(root, buttonInfo.getPopup());
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
v.performClick();
|
v.performClick();
|
||||||
}
|
}
|
||||||
@@ -406,11 +340,7 @@ public final class ExtraKeysView extends GridLayout {
|
|||||||
|
|
||||||
LayoutParams param = new GridLayout.LayoutParams();
|
LayoutParams param = new GridLayout.LayoutParams();
|
||||||
param.width = 0;
|
param.width = 0;
|
||||||
if(Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP) { // special handle api 21
|
param.height = 0;
|
||||||
param.height = (int)(37.5 * getResources().getDisplayMetrics().density + 0.5); // 37.5 equal to R.id.viewpager layout_height / rows in DP
|
|
||||||
} else {
|
|
||||||
param.height = 0;
|
|
||||||
}
|
|
||||||
param.setMargins(0, 0, 0, 0);
|
param.setMargins(0, 0, 0, 0);
|
||||||
param.columnSpec = GridLayout.spec(col, GridLayout.FILL, 1.f);
|
param.columnSpec = GridLayout.spec(col, GridLayout.FILL, 1.f);
|
||||||
param.rowSpec = GridLayout.spec(row, GridLayout.FILL, 1.f);
|
param.rowSpec = GridLayout.spec(row, GridLayout.FILL, 1.f);
|
||||||
|
|||||||
102
app/src/main/java/com/termux/app/RunCommandService.java
Normal file
102
app/src/main/java/com/termux/app/RunCommandService.java
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
package com.termux.app;
|
||||||
|
|
||||||
|
import android.app.Service;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Binder;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When allow-external-apps property is set to "true" in ~/.termux/termux.properties, Termux
|
||||||
|
* is able to process execute intents sent by third-party applications.
|
||||||
|
*
|
||||||
|
* Third-party program must declare com.termux.permission.RUN_COMMAND permission and it should be
|
||||||
|
* granted by user.
|
||||||
|
* Full path of command or script must be given in "RUN_COMMAND_PATH" extra.
|
||||||
|
* The "RUN_COMMAND_ARGUMENTS", "RUN_COMMAND_WORKDIR" and "RUN_COMMAND_BACKGROUND" extras are
|
||||||
|
* optional. The background mode defaults to false.
|
||||||
|
*
|
||||||
|
* Sample code to run command "top" with java:
|
||||||
|
* Intent intent = new Intent();
|
||||||
|
* intent.setClassName("com.termux", "com.termux.app.RunCommandService");
|
||||||
|
* intent.setAction("com.termux.RUN_COMMAND");
|
||||||
|
* intent.putExtra("com.termux.RUN_COMMAND_PATH", "/data/data/com.termux/files/usr/bin/top");
|
||||||
|
* intent.putExtra("com.termux.RUN_COMMAND_ARGUMENTS", new String[]{"-n", "5"});
|
||||||
|
* intent.putExtra("com.termux.RUN_COMMAND_WORKDIR", "/data/data/com.termux/files/home");
|
||||||
|
* intent.putExtra("com.termux.RUN_COMMAND_BACKGROUND", false);
|
||||||
|
* startService(intent);
|
||||||
|
*
|
||||||
|
* Sample code to run command "top" with "am startservice" command:
|
||||||
|
* am startservice --user 0 -n com.termux/com.termux.app.RunCommandService
|
||||||
|
* -a com.termux.RUN_COMMAND
|
||||||
|
* --es com.termux.RUN_COMMAND_PATH '/data/data/com.termux/files/usr/bin/top'
|
||||||
|
* --esa com.termux.RUN_COMMAND_ARGUMENTS '-n,5'
|
||||||
|
* --es com.termux.RUN_COMMAND_WORKDIR '/data/data/com.termux/files/home'
|
||||||
|
* --ez com.termux.RUN_COMMAND_BACKGROUND 'false'
|
||||||
|
*/
|
||||||
|
public class RunCommandService extends Service {
|
||||||
|
|
||||||
|
public static final String RUN_COMMAND_ACTION = "com.termux.RUN_COMMAND";
|
||||||
|
public static final String RUN_COMMAND_PATH = "com.termux.RUN_COMMAND_PATH";
|
||||||
|
public static final String RUN_COMMAND_ARGUMENTS = "com.termux.RUN_COMMAND_ARGUMENTS";
|
||||||
|
public static final String RUN_COMMAND_WORKDIR = "com.termux.RUN_COMMAND_WORKDIR";
|
||||||
|
public static final String RUN_COMMAND_BACKGROUND = "com.termux.RUN_COMMAND_BACKGROUND";
|
||||||
|
|
||||||
|
class LocalBinder extends Binder {
|
||||||
|
public final RunCommandService service = RunCommandService.this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final IBinder mBinder = new RunCommandService.LocalBinder();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IBinder onBind(Intent intent) {
|
||||||
|
return mBinder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||||
|
if (allowExternalApps() && RUN_COMMAND_ACTION.equals(intent.getAction())) {
|
||||||
|
Uri programUri = new Uri.Builder().scheme("com.termux.file").path(intent.getStringExtra(RUN_COMMAND_PATH)).build();
|
||||||
|
|
||||||
|
Intent execIntent = new Intent(TermuxService.ACTION_EXECUTE, programUri);
|
||||||
|
execIntent.setClass(this, TermuxService.class);
|
||||||
|
execIntent.putExtra(TermuxService.EXTRA_ARGUMENTS, intent.getStringArrayExtra(RUN_COMMAND_ARGUMENTS));
|
||||||
|
execIntent.putExtra(TermuxService.EXTRA_CURRENT_WORKING_DIRECTORY, intent.getStringExtra(RUN_COMMAND_WORKDIR));
|
||||||
|
execIntent.putExtra(TermuxService.EXTRA_EXECUTE_IN_BACKGROUND, intent.getBooleanExtra(RUN_COMMAND_BACKGROUND, false));
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
this.startForegroundService(execIntent);
|
||||||
|
} else {
|
||||||
|
this.startService(execIntent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Service.START_NOT_STICKY;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean allowExternalApps() {
|
||||||
|
File propsFile = new File(TermuxService.HOME_PATH + "/.termux/termux.properties");
|
||||||
|
if (!propsFile.exists())
|
||||||
|
propsFile = new File(TermuxService.HOME_PATH + "/.config/termux/termux.properties");
|
||||||
|
|
||||||
|
Properties props = new Properties();
|
||||||
|
try {
|
||||||
|
if (propsFile.isFile() && propsFile.canRead()) {
|
||||||
|
try (FileInputStream in = new FileInputStream(propsFile)) {
|
||||||
|
props.load(new InputStreamReader(in, StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e("termux", "Error loading props", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return props.getProperty("allow-external-apps", "false").equals("true");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,7 +24,6 @@ import android.net.Uri;
|
|||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.Vibrator;
|
|
||||||
import android.text.SpannableString;
|
import android.text.SpannableString;
|
||||||
import android.text.Spanned;
|
import android.text.Spanned;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
@@ -83,6 +82,8 @@ import androidx.viewpager.widget.ViewPager;
|
|||||||
*/
|
*/
|
||||||
public final class TermuxActivity extends Activity implements ServiceConnection {
|
public final class TermuxActivity extends Activity implements ServiceConnection {
|
||||||
|
|
||||||
|
public static final String TERMUX_FAILSAFE_SESSION_ACTION = "com.termux.app.failsafe_session";
|
||||||
|
|
||||||
private static final int CONTEXTMENU_SELECT_URL_ID = 0;
|
private static final int CONTEXTMENU_SELECT_URL_ID = 0;
|
||||||
private static final int CONTEXTMENU_SHARE_TRANSCRIPT_ID = 1;
|
private static final int CONTEXTMENU_SHARE_TRANSCRIPT_ID = 1;
|
||||||
private static final int CONTEXTMENU_PASTE_ID = 3;
|
private static final int CONTEXTMENU_PASTE_ID = 3;
|
||||||
@@ -126,6 +127,8 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
*/
|
*/
|
||||||
boolean mIsVisible;
|
boolean mIsVisible;
|
||||||
|
|
||||||
|
boolean mIsUsingBlackUI;
|
||||||
|
|
||||||
final SoundPool mBellSoundPool = new SoundPool.Builder().setMaxStreams(1).setAudioAttributes(
|
final SoundPool mBellSoundPool = new SoundPool.Builder().setMaxStreams(1).setAudioAttributes(
|
||||||
new AudioAttributes.Builder().setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
|
new AudioAttributes.Builder().setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
|
||||||
.setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION).build()).build();
|
.setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION).build()).build();
|
||||||
@@ -145,7 +148,7 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
mSettings.reloadFromProperties(TermuxActivity.this);
|
mSettings.reloadFromProperties(TermuxActivity.this);
|
||||||
|
|
||||||
if (mExtraKeysView != null) {
|
if (mExtraKeysView != null) {
|
||||||
mExtraKeysView.reload(mSettings.mExtraKeys, ExtraKeysView.defaultCharDisplay);
|
mExtraKeysView.reload(mSettings.mExtraKeys);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -185,28 +188,35 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** For processes to access shared internal storage (/sdcard) we need this permission. */
|
/** For processes to access shared internal storage (/sdcard) we need this permission. */
|
||||||
@TargetApi(Build.VERSION_CODES.M)
|
|
||||||
public boolean ensureStoragePermissionGranted() {
|
public boolean ensureStoragePermissionGranted() {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
|
||||||
if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUESTCODE_PERMISSION_STORAGE);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Always granted before Android 6.0.
|
|
||||||
return true;
|
return true;
|
||||||
|
} else {
|
||||||
|
requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUESTCODE_PERMISSION_STORAGE);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle bundle) {
|
public void onCreate(Bundle bundle) {
|
||||||
|
mSettings = new TermuxPreferences(this);
|
||||||
|
mIsUsingBlackUI = mSettings.isUsingBlackUI();
|
||||||
|
if (mIsUsingBlackUI) {
|
||||||
|
this.setTheme(R.style.Theme_Termux_Black);
|
||||||
|
} else {
|
||||||
|
this.setTheme(R.style.Theme_Termux);
|
||||||
|
}
|
||||||
|
|
||||||
super.onCreate(bundle);
|
super.onCreate(bundle);
|
||||||
|
|
||||||
mSettings = new TermuxPreferences(this);
|
|
||||||
|
|
||||||
setContentView(R.layout.drawer_layout);
|
setContentView(R.layout.drawer_layout);
|
||||||
|
|
||||||
|
if (mIsUsingBlackUI) {
|
||||||
|
findViewById(R.id.left_drawer).setBackgroundColor(
|
||||||
|
getResources().getColor(android.R.color.background_dark)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
mTerminalView = findViewById(R.id.terminal_view);
|
mTerminalView = findViewById(R.id.terminal_view);
|
||||||
mTerminalView.setOnKeyListener(new TermuxViewClient(this));
|
mTerminalView.setOnKeyListener(new TermuxViewClient(this));
|
||||||
|
|
||||||
@@ -219,7 +229,7 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
|
|
||||||
|
|
||||||
ViewGroup.LayoutParams layoutParams = viewPager.getLayoutParams();
|
ViewGroup.LayoutParams layoutParams = viewPager.getLayoutParams();
|
||||||
layoutParams.height = layoutParams.height * mSettings.mExtraKeys.length;
|
layoutParams.height = layoutParams.height * (mSettings.mExtraKeys == null ? 0 : mSettings.mExtraKeys.getMatrix().length);
|
||||||
viewPager.setLayoutParams(layoutParams);
|
viewPager.setLayoutParams(layoutParams);
|
||||||
|
|
||||||
viewPager.setAdapter(new PagerAdapter() {
|
viewPager.setAdapter(new PagerAdapter() {
|
||||||
@@ -240,7 +250,7 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
View layout;
|
View layout;
|
||||||
if (position == 0) {
|
if (position == 0) {
|
||||||
layout = mExtraKeysView = (ExtraKeysView) inflater.inflate(R.layout.extra_keys_main, collection, false);
|
layout = mExtraKeysView = (ExtraKeysView) inflater.inflate(R.layout.extra_keys_main, collection, false);
|
||||||
mExtraKeysView.reload(mSettings.mExtraKeys, ExtraKeysView.defaultCharDisplay);
|
mExtraKeysView.reload(mSettings.mExtraKeys);
|
||||||
} else {
|
} else {
|
||||||
layout = inflater.inflate(R.layout.extra_keys_right, collection, false);
|
layout = inflater.inflate(R.layout.extra_keys_right, collection, false);
|
||||||
final EditText editText = layout.findViewById(R.id.text_input);
|
final EditText editText = layout.findViewById(R.id.text_input);
|
||||||
@@ -400,7 +410,7 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
mBellSoundPool.play(mBellSoundId, 1.f, 1.f, 1, 0, 1.f);
|
mBellSoundPool.play(mBellSoundId, 1.f, 1.f, 1, 0, 1.f);
|
||||||
break;
|
break;
|
||||||
case TermuxPreferences.BELL_VIBRATE:
|
case TermuxPreferences.BELL_VIBRATE:
|
||||||
((Vibrator) getSystemService(VIBRATOR_SERVICE)).vibrate(50);
|
BellUtil.getInstance(TermuxActivity.this).doBell();
|
||||||
break;
|
break;
|
||||||
case TermuxPreferences.BELL_IGNORE:
|
case TermuxPreferences.BELL_IGNORE:
|
||||||
// Ignore the bell character.
|
// Ignore the bell character.
|
||||||
@@ -433,7 +443,11 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
boolean sessionRunning = sessionAtRow.isRunning();
|
boolean sessionRunning = sessionAtRow.isRunning();
|
||||||
|
|
||||||
TextView firstLineView = row.findViewById(R.id.row_line);
|
TextView firstLineView = row.findViewById(R.id.row_line);
|
||||||
|
if (mIsUsingBlackUI) {
|
||||||
|
firstLineView.setBackground(
|
||||||
|
getResources().getDrawable(R.drawable.selected_session_background_black)
|
||||||
|
);
|
||||||
|
}
|
||||||
String name = sessionAtRow.mSessionName;
|
String name = sessionAtRow.mSessionName;
|
||||||
String sessionTitle = sessionAtRow.getTitle();
|
String sessionTitle = sessionAtRow.getTitle();
|
||||||
|
|
||||||
@@ -453,7 +467,8 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
} else {
|
} else {
|
||||||
firstLineView.setPaintFlags(firstLineView.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
|
firstLineView.setPaintFlags(firstLineView.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
|
||||||
}
|
}
|
||||||
int color = sessionRunning || sessionAtRow.getExitStatus() == 0 ? Color.BLACK : Color.RED;
|
int defaultColor = mIsUsingBlackUI ? Color.WHITE : Color.BLACK;
|
||||||
|
int color = sessionRunning || sessionAtRow.getExitStatus() == 0 ? defaultColor : Color.RED;
|
||||||
firstLineView.setTextColor(color);
|
firstLineView.setTextColor(color);
|
||||||
return row;
|
return row;
|
||||||
}
|
}
|
||||||
@@ -478,9 +493,8 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
Bundle bundle = getIntent().getExtras();
|
Bundle bundle = getIntent().getExtras();
|
||||||
boolean launchFailsafe = false;
|
boolean launchFailsafe = false;
|
||||||
if (bundle != null) {
|
if (bundle != null) {
|
||||||
launchFailsafe = bundle.getBoolean(TermuxFailsafeActivity.TERMUX_FAILSAFE_SESSION_ACTION, false);
|
launchFailsafe = bundle.getBoolean(TERMUX_FAILSAFE_SESSION_ACTION, false);
|
||||||
}
|
}
|
||||||
clearTemporaryDirectory();
|
|
||||||
addNewSession(launchFailsafe, null);
|
addNewSession(launchFailsafe, null);
|
||||||
} catch (WindowManager.BadTokenException e) {
|
} catch (WindowManager.BadTokenException e) {
|
||||||
// Activity finished - ignore.
|
// Activity finished - ignore.
|
||||||
@@ -494,8 +508,8 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
Intent i = getIntent();
|
Intent i = getIntent();
|
||||||
if (i != null && Intent.ACTION_RUN.equals(i.getAction())) {
|
if (i != null && Intent.ACTION_RUN.equals(i.getAction())) {
|
||||||
// Android 7.1 app shortcut from res/xml/shortcuts.xml.
|
// Android 7.1 app shortcut from res/xml/shortcuts.xml.
|
||||||
clearTemporaryDirectory();
|
boolean failSafe = i.getBooleanExtra(TERMUX_FAILSAFE_SESSION_ACTION, false);
|
||||||
addNewSession(false, null);
|
addNewSession(failSafe, null);
|
||||||
} else {
|
} else {
|
||||||
switchToSession(getStoredCurrentSessionOrLast());
|
switchToSession(getStoredCurrentSessionOrLast());
|
||||||
}
|
}
|
||||||
@@ -589,8 +603,9 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
new AlertDialog.Builder(this).setTitle(R.string.max_terminals_reached_title).setMessage(R.string.max_terminals_reached_message)
|
new AlertDialog.Builder(this).setTitle(R.string.max_terminals_reached_title).setMessage(R.string.max_terminals_reached_message)
|
||||||
.setPositiveButton(android.R.string.ok, null).show();
|
.setPositiveButton(android.R.string.ok, null).show();
|
||||||
} else {
|
} else {
|
||||||
String executablePath = (failSafe ? "/system/bin/sh" : null);
|
TerminalSession currentSession = getCurrentTermSession();
|
||||||
TerminalSession newSession = mTermService.createTermSession(executablePath, null, null, failSafe);
|
String workingDirectory = (currentSession == null) ? null : currentSession.getCwd();
|
||||||
|
TerminalSession newSession = mTermService.createTermSession(null, null, workingDirectory, failSafe);
|
||||||
if (sessionName != null) {
|
if (sessionName != null) {
|
||||||
newSession.mSessionName = sessionName;
|
newSession.mSessionName = sessionName;
|
||||||
}
|
}
|
||||||
@@ -655,24 +670,94 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
}
|
}
|
||||||
|
|
||||||
static LinkedHashSet<CharSequence> extractUrls(String text) {
|
static LinkedHashSet<CharSequence> extractUrls(String text) {
|
||||||
// Pattern for recognizing a URL, based off RFC 3986
|
|
||||||
// http://stackoverflow.com/questions/5713558/detect-and-extract-url-from-a-string
|
StringBuilder regex_sb = new StringBuilder();
|
||||||
|
|
||||||
|
regex_sb.append("("); // Begin first matching group.
|
||||||
|
regex_sb.append("(?:"); // Begin scheme group.
|
||||||
|
regex_sb.append("dav|"); // The DAV proto.
|
||||||
|
regex_sb.append("dict|"); // The DICT proto.
|
||||||
|
regex_sb.append("dns|"); // The DNS proto.
|
||||||
|
regex_sb.append("file|"); // File path.
|
||||||
|
regex_sb.append("finger|"); // The Finger proto.
|
||||||
|
regex_sb.append("ftp(?:s?)|"); // The FTP proto.
|
||||||
|
regex_sb.append("git|"); // The Git proto.
|
||||||
|
regex_sb.append("gopher|"); // The Gopher proto.
|
||||||
|
regex_sb.append("http(?:s?)|"); // The HTTP proto.
|
||||||
|
regex_sb.append("imap(?:s?)|"); // The IMAP proto.
|
||||||
|
regex_sb.append("irc(?:[6s]?)|"); // The IRC proto.
|
||||||
|
regex_sb.append("ip[fn]s|"); // The IPFS proto.
|
||||||
|
regex_sb.append("ldap(?:s?)|"); // The LDAP proto.
|
||||||
|
regex_sb.append("pop3(?:s?)|"); // The POP3 proto.
|
||||||
|
regex_sb.append("redis(?:s?)|"); // The Redis proto.
|
||||||
|
regex_sb.append("rsync|"); // The Rsync proto.
|
||||||
|
regex_sb.append("rtsp(?:[su]?)|"); // The RTSP proto.
|
||||||
|
regex_sb.append("sftp|"); // The SFTP proto.
|
||||||
|
regex_sb.append("smb(?:s?)|"); // The SAMBA proto.
|
||||||
|
regex_sb.append("smtp(?:s?)|"); // The SMTP proto.
|
||||||
|
regex_sb.append("svn(?:(?:\\+ssh)?)|"); // The Subversion proto.
|
||||||
|
regex_sb.append("tcp|"); // The TCP proto.
|
||||||
|
regex_sb.append("telnet|"); // The Telnet proto.
|
||||||
|
regex_sb.append("tftp|"); // The TFTP proto.
|
||||||
|
regex_sb.append("udp|"); // The UDP proto.
|
||||||
|
regex_sb.append("vnc|"); // The VNC proto.
|
||||||
|
regex_sb.append("ws(?:s?)"); // The Websocket proto.
|
||||||
|
regex_sb.append(")://"); // End scheme group.
|
||||||
|
regex_sb.append(")"); // End first matching group.
|
||||||
|
|
||||||
|
|
||||||
|
// Begin second matching group.
|
||||||
|
regex_sb.append("(");
|
||||||
|
|
||||||
|
// User name and/or password in format 'user:pass@'.
|
||||||
|
regex_sb.append("(?:\\S+(?::\\S*)?@)?");
|
||||||
|
|
||||||
|
// Begin host group.
|
||||||
|
regex_sb.append("(?:");
|
||||||
|
|
||||||
|
// IP address (from http://www.regular-expressions.info/examples.html).
|
||||||
|
regex_sb.append("(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)|");
|
||||||
|
|
||||||
|
// Host name or domain.
|
||||||
|
regex_sb.append("(?:(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)(?:(?:\\.(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)*(?:\\.(?:[a-z\\u00a1-\\uffff]{2,})))?|");
|
||||||
|
|
||||||
|
// Just path. Used in case of 'file://' scheme.
|
||||||
|
regex_sb.append("/(?:(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)");
|
||||||
|
|
||||||
|
// End host group.
|
||||||
|
regex_sb.append(")");
|
||||||
|
|
||||||
|
// Port number.
|
||||||
|
regex_sb.append("(?::\\d{1,5})?");
|
||||||
|
|
||||||
|
// Resource path with optional query string.
|
||||||
|
regex_sb.append("(?:/[a-zA-Z0-9:@%\\-._~!$&()*+,;=?/]*)?");
|
||||||
|
|
||||||
|
// Fragment.
|
||||||
|
regex_sb.append("(?:#[a-zA-Z0-9:@%\\-._~!$&()*+,;=?/]*)?");
|
||||||
|
|
||||||
|
// End second matching group.
|
||||||
|
regex_sb.append(")");
|
||||||
|
|
||||||
final Pattern urlPattern = Pattern.compile(
|
final Pattern urlPattern = Pattern.compile(
|
||||||
"(?:^|[\\W])((ht|f)tp(s?)://|www\\.)" + "(([\\w\\-]+\\.)+?([\\w\\-.~]+/?)*" + "[\\p{Alnum}.,%_=?&#\\-+()\\[\\]*$~@!:/{};']*)",
|
regex_sb.toString(),
|
||||||
Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
|
Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
|
||||||
|
|
||||||
LinkedHashSet<CharSequence> urlSet = new LinkedHashSet<>();
|
LinkedHashSet<CharSequence> urlSet = new LinkedHashSet<>();
|
||||||
Matcher matcher = urlPattern.matcher(text);
|
Matcher matcher = urlPattern.matcher(text);
|
||||||
|
|
||||||
while (matcher.find()) {
|
while (matcher.find()) {
|
||||||
int matchStart = matcher.start(1);
|
int matchStart = matcher.start(1);
|
||||||
int matchEnd = matcher.end();
|
int matchEnd = matcher.end();
|
||||||
String url = text.substring(matchStart, matchEnd);
|
String url = text.substring(matchStart, matchEnd);
|
||||||
urlSet.add(url);
|
urlSet.add(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
return urlSet;
|
return urlSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
void showUrlSelection() {
|
void showUrlSelection() {
|
||||||
String text = getCurrentTermSession().getEmulator().getScreen().getTranscriptText();
|
String text = getCurrentTermSession().getEmulator().getScreen().getTranscriptTextWithFullLinesJoined();
|
||||||
LinkedHashSet<CharSequence> urlSet = extractUrls(text);
|
LinkedHashSet<CharSequence> urlSet = extractUrls(text);
|
||||||
if (urlSet.isEmpty()) {
|
if (urlSet.isEmpty()) {
|
||||||
new AlertDialog.Builder(this).setMessage(R.string.select_url_no_found).show();
|
new AlertDialog.Builder(this).setMessage(R.string.select_url_no_found).show();
|
||||||
@@ -722,7 +807,18 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
if (session != null) {
|
if (session != null) {
|
||||||
Intent intent = new Intent(Intent.ACTION_SEND);
|
Intent intent = new Intent(Intent.ACTION_SEND);
|
||||||
intent.setType("text/plain");
|
intent.setType("text/plain");
|
||||||
intent.putExtra(Intent.EXTRA_TEXT, session.getEmulator().getScreen().getTranscriptText().trim());
|
String transcriptText = session.getEmulator().getScreen().getTranscriptTextWithoutJoinedLines().trim();
|
||||||
|
// See https://github.com/termux/termux-app/issues/1166.
|
||||||
|
final int MAX_LENGTH = 100_000;
|
||||||
|
if (transcriptText.length() > MAX_LENGTH) {
|
||||||
|
int cutOffIndex = transcriptText.length() - MAX_LENGTH;
|
||||||
|
int nextNewlineIndex = transcriptText.indexOf('\n', cutOffIndex);
|
||||||
|
if (nextNewlineIndex != -1 && nextNewlineIndex != transcriptText.length() - 1) {
|
||||||
|
cutOffIndex = nextNewlineIndex + 1;
|
||||||
|
}
|
||||||
|
transcriptText = transcriptText.substring(cutOffIndex).trim();
|
||||||
|
}
|
||||||
|
intent.putExtra(Intent.EXTRA_TEXT, transcriptText);
|
||||||
intent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.share_transcript_title));
|
intent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.share_transcript_title));
|
||||||
startActivity(Intent.createChooser(intent, getString(R.string.share_transcript_chooser_title)));
|
startActivity(Intent.createChooser(intent, getString(R.string.share_transcript_chooser_title)));
|
||||||
}
|
}
|
||||||
@@ -833,18 +929,4 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void clearTemporaryDirectory() {
|
|
||||||
if (mTermService.getSessions().size() == 0 && !mTermService.isWakelockEnabled()) {
|
|
||||||
File termuxTmpDir = new File(TermuxService.PREFIX_PATH + "/tmp");
|
|
||||||
if (termuxTmpDir.exists()) {
|
|
||||||
try {
|
|
||||||
TermuxInstaller.deleteFolder(termuxTmpDir);
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
termuxTmpDir.mkdirs();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
package com.termux.app;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.Bundle;
|
|
||||||
|
|
||||||
public final class TermuxFailsafeActivity extends Activity {
|
|
||||||
|
|
||||||
public static final String TERMUX_FAILSAFE_SESSION_ACTION = "com.termux.app.failsafe_session";
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle bundle) {
|
|
||||||
super.onCreate(bundle);
|
|
||||||
Intent intent = new Intent(TermuxFailsafeActivity.this, TermuxActivity.class);
|
|
||||||
intent.putExtra(TERMUX_FAILSAFE_SESSION_ACTION, true);
|
|
||||||
startActivity(intent);
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,7 +4,6 @@ import android.app.Activity;
|
|||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.app.ProgressDialog;
|
import android.app.ProgressDialog;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Environment;
|
import android.os.Environment;
|
||||||
import android.os.UserManager;
|
import android.os.UserManager;
|
||||||
import android.system.Os;
|
import android.system.Os;
|
||||||
@@ -16,14 +15,12 @@ import com.termux.R;
|
|||||||
import com.termux.terminal.EmulatorDebug;
|
import com.termux.terminal.EmulatorDebug;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.net.MalformedURLException;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.zip.ZipEntry;
|
import java.util.zip.ZipEntry;
|
||||||
import java.util.zip.ZipInputStream;
|
import java.util.zip.ZipInputStream;
|
||||||
@@ -38,7 +35,7 @@ import java.util.zip.ZipInputStream;
|
|||||||
* <p/>
|
* <p/>
|
||||||
* (3) A staging folder, $STAGING_PREFIX, is {@link #deleteFolder(File)} if left over from broken installation below.
|
* (3) A staging folder, $STAGING_PREFIX, is {@link #deleteFolder(File)} if left over from broken installation below.
|
||||||
* <p/>
|
* <p/>
|
||||||
* (4) The architecture is determined and an appropriate bootstrap zip url is determined in {@link #determineZipUrl()}.
|
* (4) The zip file is loaded from a shared library.
|
||||||
* <p/>
|
* <p/>
|
||||||
* (5) The zip, containing entries relative to the $PREFIX, is is downloaded and extracted by a zip input stream
|
* (5) The zip, containing entries relative to the $PREFIX, is is downloaded and extracted by a zip input stream
|
||||||
* continuously encountering zip file entries:
|
* continuously encountering zip file entries:
|
||||||
@@ -82,8 +79,8 @@ final class TermuxInstaller {
|
|||||||
final byte[] buffer = new byte[8096];
|
final byte[] buffer = new byte[8096];
|
||||||
final List<Pair<String, String>> symlinks = new ArrayList<>(50);
|
final List<Pair<String, String>> symlinks = new ArrayList<>(50);
|
||||||
|
|
||||||
final URL zipUrl = determineZipUrl();
|
final byte[] zipBytes = loadZipBytes();
|
||||||
try (ZipInputStream zipInput = new ZipInputStream(zipUrl.openStream())) {
|
try (ZipInputStream zipInput = new ZipInputStream(new ByteArrayInputStream(zipBytes))) {
|
||||||
ZipEntry zipEntry;
|
ZipEntry zipEntry;
|
||||||
while ((zipEntry = zipInput.getNextEntry()) != null) {
|
while ((zipEntry = zipInput.getNextEntry()) != null) {
|
||||||
if (zipEntry.getName().equals("SYMLINKS.txt")) {
|
if (zipEntry.getName().equals("SYMLINKS.txt")) {
|
||||||
@@ -167,34 +164,13 @@ final class TermuxInstaller {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get bootstrap zip url for this systems cpu architecture. */
|
public static byte[] loadZipBytes() {
|
||||||
private static URL determineZipUrl() throws MalformedURLException {
|
// Only load the shared library when necessary to save memory usage.
|
||||||
String archName = determineTermuxArchName();
|
System.loadLibrary("termux-bootstrap");
|
||||||
String url = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
|
return getZip();
|
||||||
? "https://termux.org/bootstrap-" + archName + ".zip"
|
|
||||||
: "https://termux.net/bootstrap/bootstrap-" + archName + ".zip";
|
|
||||||
return new URL(url);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String determineTermuxArchName() {
|
public static native byte[] getZip();
|
||||||
// Note that we cannot use System.getProperty("os.arch") since that may give e.g. "aarch64"
|
|
||||||
// while a 64-bit runtime may not be installed (like on the Samsung Galaxy S5 Neo).
|
|
||||||
// Instead we search through the supported abi:s on the device, see:
|
|
||||||
// http://developer.android.com/ndk/guides/abis.html
|
|
||||||
// Note that we search for abi:s in preferred order (the ordering of the
|
|
||||||
// Build.SUPPORTED_ABIS list) to avoid e.g. installing arm on an x86 system where arm
|
|
||||||
// emulation is available.
|
|
||||||
for (String androidArch : Build.SUPPORTED_ABIS) {
|
|
||||||
switch (androidArch) {
|
|
||||||
case "arm64-v8a": return "aarch64";
|
|
||||||
case "armeabi-v7a": return "arm";
|
|
||||||
case "x86_64": return "x86_64";
|
|
||||||
case "x86": return "i686";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new RuntimeException("Unable to determine arch from Build.SUPPORTED_ABIS = " +
|
|
||||||
Arrays.toString(Build.SUPPORTED_ABIS));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Delete a folder and all its content or throw. Don't follow symlinks. */
|
/** Delete a folder and all its content or throw. Don't follow symlinks. */
|
||||||
static void deleteFolder(File fileOrDirectory) throws IOException {
|
static void deleteFolder(File fileOrDirectory) throws IOException {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.termux.app;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
|
import android.content.res.Configuration;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.TypedValue;
|
import android.util.TypedValue;
|
||||||
@@ -9,6 +10,7 @@ import android.widget.Toast;
|
|||||||
import com.termux.terminal.TerminalSession;
|
import com.termux.terminal.TerminalSession;
|
||||||
import org.json.JSONArray;
|
import org.json.JSONArray;
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
@@ -18,7 +20,10 @@ import java.lang.annotation.Retention;
|
|||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
|
|
||||||
import androidx.annotation.IntDef;
|
import androidx.annotation.IntDef;
|
||||||
@@ -58,6 +63,7 @@ final class TermuxPreferences {
|
|||||||
private static final String CURRENT_SESSION_KEY = "current_session";
|
private static final String CURRENT_SESSION_KEY = "current_session";
|
||||||
private static final String SCREEN_ALWAYS_ON_KEY = "screen_always_on";
|
private static final String SCREEN_ALWAYS_ON_KEY = "screen_always_on";
|
||||||
|
|
||||||
|
private boolean mUseDarkUI;
|
||||||
private boolean mScreenAlwaysOn;
|
private boolean mScreenAlwaysOn;
|
||||||
private int mFontSize;
|
private int mFontSize;
|
||||||
|
|
||||||
@@ -65,9 +71,10 @@ final class TermuxPreferences {
|
|||||||
int mBellBehaviour = BELL_VIBRATE;
|
int mBellBehaviour = BELL_VIBRATE;
|
||||||
|
|
||||||
boolean mBackIsEscape;
|
boolean mBackIsEscape;
|
||||||
|
boolean mDisableVolumeVirtualKeys;
|
||||||
boolean mShowExtraKeys;
|
boolean mShowExtraKeys;
|
||||||
|
|
||||||
String[][] mExtraKeys;
|
ExtraKeysInfos mExtraKeys;
|
||||||
|
|
||||||
final List<KeyboardShortcut> shortcuts = new ArrayList<>();
|
final List<KeyboardShortcut> shortcuts = new ArrayList<>();
|
||||||
|
|
||||||
@@ -126,6 +133,10 @@ final class TermuxPreferences {
|
|||||||
return mScreenAlwaysOn;
|
return mScreenAlwaysOn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean isUsingBlackUI() {
|
||||||
|
return mUseDarkUI;
|
||||||
|
}
|
||||||
|
|
||||||
void setScreenAlwaysOn(Context context, boolean newValue) {
|
void setScreenAlwaysOn(Context context, boolean newValue) {
|
||||||
mScreenAlwaysOn = newValue;
|
mScreenAlwaysOn = newValue;
|
||||||
PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean(SCREEN_ALWAYS_ON_KEY, newValue).apply();
|
PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean(SCREEN_ALWAYS_ON_KEY, newValue).apply();
|
||||||
@@ -143,7 +154,7 @@ final class TermuxPreferences {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
void reloadFromProperties(Context context) {
|
void reloadFromProperties(Context context) {
|
||||||
File propsFile = new File(TermuxService.HOME_PATH + "/.termux/termux.properties");
|
File propsFile = new File(TermuxService.HOME_PATH + "/.termux/termux.properties");
|
||||||
if (!propsFile.exists())
|
if (!propsFile.exists())
|
||||||
@@ -156,8 +167,8 @@ final class TermuxPreferences {
|
|||||||
props.load(new InputStreamReader(in, StandardCharsets.UTF_8));
|
props.load(new InputStreamReader(in, StandardCharsets.UTF_8));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (Exception e) {
|
||||||
Toast.makeText(context, "Could not open properties file termux.properties.", Toast.LENGTH_LONG).show();
|
Toast.makeText(context, "Could not open properties file termux.properties: " + e.getMessage(), Toast.LENGTH_LONG).show();
|
||||||
Log.e("termux", "Error loading props", e);
|
Log.e("termux", "Error loading props", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,24 +184,39 @@ final class TermuxPreferences {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
switch (props.getProperty("use-black-ui", "").toLowerCase()) {
|
||||||
JSONArray arr = new JSONArray(props.getProperty("extra-keys", "[['ESC', 'TAB', 'CTRL', 'ALT', '-', 'DOWN', 'UP']]"));
|
case "true":
|
||||||
|
mUseDarkUI = true;
|
||||||
|
break;
|
||||||
|
case "false":
|
||||||
|
mUseDarkUI = false;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
int nightMode = context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
|
||||||
|
mUseDarkUI = nightMode == Configuration.UI_MODE_NIGHT_YES;
|
||||||
|
}
|
||||||
|
|
||||||
mExtraKeys = new String[arr.length()][];
|
String defaultExtraKeys = "[[ESC, TAB, CTRL, ALT, {key: '-', popup: '|'}, DOWN, UP]]";
|
||||||
for (int i = 0; i < arr.length(); i++) {
|
|
||||||
JSONArray line = arr.getJSONArray(i);
|
try {
|
||||||
mExtraKeys[i] = new String[line.length()];
|
String extrakeyProp = props.getProperty("extra-keys", defaultExtraKeys);
|
||||||
for (int j = 0; j < line.length(); j++) {
|
String extraKeysStyle = props.getProperty("extra-keys-style", "default");
|
||||||
mExtraKeys[i][j] = line.getString(j);
|
mExtraKeys = new ExtraKeysInfos(extrakeyProp, extraKeysStyle);
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (JSONException e) {
|
} catch (JSONException e) {
|
||||||
Toast.makeText(context, "Could not load the extra-keys property from the config: " + e.toString(), Toast.LENGTH_LONG).show();
|
Toast.makeText(context, "Could not load the extra-keys property from the config: " + e.toString(), Toast.LENGTH_LONG).show();
|
||||||
Log.e("termux", "Error loading props", e);
|
Log.e("termux", "Error loading props", e);
|
||||||
mExtraKeys = new String[0][];
|
|
||||||
|
try {
|
||||||
|
mExtraKeys = new ExtraKeysInfos(defaultExtraKeys, "default");
|
||||||
|
} catch (JSONException e2) {
|
||||||
|
e2.printStackTrace();
|
||||||
|
Toast.makeText(context, "Can't create default extra keys", Toast.LENGTH_LONG).show();
|
||||||
|
mExtraKeys = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mBackIsEscape = "escape".equals(props.getProperty("back-key", "back"));
|
mBackIsEscape = "escape".equals(props.getProperty("back-key", "back"));
|
||||||
|
mDisableVolumeVirtualKeys = "volume".equals(props.getProperty("volume-keys", "virtual"));
|
||||||
|
|
||||||
shortcuts.clear();
|
shortcuts.clear();
|
||||||
parseAction("shortcut.create-session", SHORTCUT_ACTION_CREATE_SESSION, props);
|
parseAction("shortcut.create-session", SHORTCUT_ACTION_CREATE_SESSION, props);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import android.app.NotificationChannel;
|
|||||||
import android.app.NotificationManager;
|
import android.app.NotificationManager;
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.app.Service;
|
import android.app.Service;
|
||||||
|
import android.content.ActivityNotFoundException;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
@@ -16,6 +17,7 @@ import android.os.Build;
|
|||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.PowerManager;
|
import android.os.PowerManager;
|
||||||
|
import android.provider.Settings;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.widget.ArrayAdapter;
|
import android.widget.ArrayAdapter;
|
||||||
|
|
||||||
@@ -61,7 +63,7 @@ public final class TermuxService extends Service implements SessionChangedCallba
|
|||||||
public static final String EXTRA_ARGUMENTS = "com.termux.execute.arguments";
|
public static final String EXTRA_ARGUMENTS = "com.termux.execute.arguments";
|
||||||
|
|
||||||
public static final String EXTRA_CURRENT_WORKING_DIRECTORY = "com.termux.execute.cwd";
|
public static final String EXTRA_CURRENT_WORKING_DIRECTORY = "com.termux.execute.cwd";
|
||||||
private static final String EXTRA_EXECUTE_IN_BACKGROUND = "com.termux.execute.background";
|
public static final String EXTRA_EXECUTE_IN_BACKGROUND = "com.termux.execute.background";
|
||||||
|
|
||||||
/** This service is only bound from inside the same process and never uses IPC. */
|
/** This service is only bound from inside the same process and never uses IPC. */
|
||||||
class LocalBinder extends Binder {
|
class LocalBinder extends Binder {
|
||||||
@@ -104,7 +106,7 @@ public final class TermuxService extends Service implements SessionChangedCallba
|
|||||||
} else if (ACTION_LOCK_WAKE.equals(action)) {
|
} else if (ACTION_LOCK_WAKE.equals(action)) {
|
||||||
if (mWakeLock == null) {
|
if (mWakeLock == null) {
|
||||||
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
|
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
|
||||||
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, EmulatorDebug.LOG_TAG);
|
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, EmulatorDebug.LOG_TAG + ":service-wakelock");
|
||||||
mWakeLock.acquire();
|
mWakeLock.acquire();
|
||||||
|
|
||||||
// http://tools.android.com/tech-docs/lint-in-studio-2-3#TOC-WifiManager-Leak
|
// http://tools.android.com/tech-docs/lint-in-studio-2-3#TOC-WifiManager-Leak
|
||||||
@@ -112,6 +114,20 @@ public final class TermuxService extends Service implements SessionChangedCallba
|
|||||||
mWifiLock = wm.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, EmulatorDebug.LOG_TAG);
|
mWifiLock = wm.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, EmulatorDebug.LOG_TAG);
|
||||||
mWifiLock.acquire();
|
mWifiLock.acquire();
|
||||||
|
|
||||||
|
String packageName = getPackageName();
|
||||||
|
if (!pm.isIgnoringBatteryOptimizations(packageName)) {
|
||||||
|
Intent whitelist = new Intent();
|
||||||
|
whitelist.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
|
||||||
|
whitelist.setData(Uri.parse("package:" + packageName));
|
||||||
|
whitelist.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
|
||||||
|
try {
|
||||||
|
startActivity(whitelist);
|
||||||
|
} catch (ActivityNotFoundException e) {
|
||||||
|
Log.e(EmulatorDebug.LOG_TAG, "Failed to call ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
updateNotification();
|
updateNotification();
|
||||||
}
|
}
|
||||||
} else if (ACTION_UNLOCK_WAKE.equals(action)) {
|
} else if (ACTION_UNLOCK_WAKE.equals(action)) {
|
||||||
@@ -132,11 +148,12 @@ public final class TermuxService extends Service implements SessionChangedCallba
|
|||||||
String cwd = intent.getStringExtra(EXTRA_CURRENT_WORKING_DIRECTORY);
|
String cwd = intent.getStringExtra(EXTRA_CURRENT_WORKING_DIRECTORY);
|
||||||
|
|
||||||
if (intent.getBooleanExtra(EXTRA_EXECUTE_IN_BACKGROUND, false)) {
|
if (intent.getBooleanExtra(EXTRA_EXECUTE_IN_BACKGROUND, false)) {
|
||||||
BackgroundJob task = new BackgroundJob(cwd, executablePath, arguments, this);
|
BackgroundJob task = new BackgroundJob(cwd, executablePath, arguments, this, intent.getParcelableExtra("pendingIntent"));
|
||||||
mBackgroundTasks.add(task);
|
mBackgroundTasks.add(task);
|
||||||
updateNotification();
|
updateNotification();
|
||||||
} else {
|
} else {
|
||||||
TerminalSession newSession = createTermSession(executablePath, arguments, cwd, false);
|
boolean failsafe = intent.getBooleanExtra(TermuxActivity.TERMUX_FAILSAFE_SESSION_ACTION, false);
|
||||||
|
TerminalSession newSession = createTermSession(executablePath, arguments, cwd, failsafe);
|
||||||
|
|
||||||
// Transform executable path to session name, e.g. "/bin/do-something.sh" => "do something.sh".
|
// Transform executable path to session name, e.g. "/bin/do-something.sh" => "do something.sh".
|
||||||
if (executablePath != null) {
|
if (executablePath != null) {
|
||||||
@@ -214,7 +231,7 @@ public final class TermuxService extends Service implements SessionChangedCallba
|
|||||||
builder.setShowWhen(false);
|
builder.setShowWhen(false);
|
||||||
|
|
||||||
// Background color for small notification icon:
|
// Background color for small notification icon:
|
||||||
builder.setColor(0xFF000000);
|
builder.setColor(0xFF607D8B);
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
builder.setChannelId(NOTIFICATION_CHANNEL_ID);
|
builder.setChannelId(NOTIFICATION_CHANNEL_ID);
|
||||||
@@ -237,6 +254,18 @@ public final class TermuxService extends Service implements SessionChangedCallba
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
|
File termuxTmpDir = new File(TermuxService.PREFIX_PATH + "/tmp");
|
||||||
|
|
||||||
|
if (termuxTmpDir.exists()) {
|
||||||
|
try {
|
||||||
|
TermuxInstaller.deleteFolder(termuxTmpDir.getCanonicalFile());
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(EmulatorDebug.LOG_TAG, "Error while removing file at " + termuxTmpDir.getAbsolutePath(), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
termuxTmpDir.mkdirs();
|
||||||
|
}
|
||||||
|
|
||||||
if (mWakeLock != null) mWakeLock.release();
|
if (mWakeLock != null) mWakeLock.release();
|
||||||
if (mWifiLock != null) mWifiLock.release();
|
if (mWifiLock != null) mWifiLock.release();
|
||||||
|
|
||||||
@@ -250,14 +279,6 @@ public final class TermuxService extends Service implements SessionChangedCallba
|
|||||||
return mTerminalSessions;
|
return mTerminalSessions;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isWakelockEnabled() {
|
|
||||||
if (mWakeLock == null) {
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
return mWakeLock.isHeld();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TerminalSession createTermSession(String executablePath, String[] arguments, String cwd, boolean failSafe) {
|
TerminalSession createTermSession(String executablePath, String[] arguments, String cwd, boolean failSafe) {
|
||||||
new File(HOME_PATH).mkdirs();
|
new File(HOME_PATH).mkdirs();
|
||||||
|
|
||||||
@@ -267,11 +288,13 @@ public final class TermuxService extends Service implements SessionChangedCallba
|
|||||||
boolean isLoginShell = false;
|
boolean isLoginShell = false;
|
||||||
|
|
||||||
if (executablePath == null) {
|
if (executablePath == null) {
|
||||||
for (String shellBinary : new String[]{"login", "bash", "zsh"}) {
|
if (!failSafe) {
|
||||||
File shellFile = new File(PREFIX_PATH + "/bin/" + shellBinary);
|
for (String shellBinary : new String[]{"login", "bash", "zsh"}) {
|
||||||
if (shellFile.canExecute()) {
|
File shellFile = new File(PREFIX_PATH + "/bin/" + shellBinary);
|
||||||
executablePath = shellFile.getAbsolutePath();
|
if (shellFile.canExecute()) {
|
||||||
break;
|
executablePath = shellFile.getAbsolutePath();
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -264,7 +264,9 @@ public final class TermuxViewClient implements TerminalViewClient {
|
|||||||
/** Handle dedicated volume buttons as virtual keys if applicable. */
|
/** Handle dedicated volume buttons as virtual keys if applicable. */
|
||||||
private boolean handleVirtualKeys(int keyCode, KeyEvent event, boolean down) {
|
private boolean handleVirtualKeys(int keyCode, KeyEvent event, boolean down) {
|
||||||
InputDevice inputDevice = event.getDevice();
|
InputDevice inputDevice = event.getDevice();
|
||||||
if (inputDevice != null && inputDevice.getKeyboardType() == InputDevice.KEYBOARD_TYPE_ALPHABETIC) {
|
if (mActivity.mSettings.mDisableVolumeVirtualKeys) {
|
||||||
|
return false;
|
||||||
|
} else if (inputDevice != null && inputDevice.getKeyboardType() == InputDevice.KEYBOARD_TYPE_ALPHABETIC) {
|
||||||
// Do not steal dedicated buttons from a full external keyboard.
|
// Do not steal dedicated buttons from a full external keyboard.
|
||||||
return false;
|
return false;
|
||||||
} else if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
|
} else if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ public class TermuxDocumentsProvider extends DocumentsProvider {
|
|||||||
row.add(Root.COLUMN_ROOT_ID, getDocIdForFile(BASE_DIR));
|
row.add(Root.COLUMN_ROOT_ID, getDocIdForFile(BASE_DIR));
|
||||||
row.add(Root.COLUMN_DOCUMENT_ID, getDocIdForFile(BASE_DIR));
|
row.add(Root.COLUMN_DOCUMENT_ID, getDocIdForFile(BASE_DIR));
|
||||||
row.add(Root.COLUMN_SUMMARY, null);
|
row.add(Root.COLUMN_SUMMARY, null);
|
||||||
row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_SEARCH);
|
row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_SEARCH | Root.FLAG_SUPPORTS_IS_CHILD);
|
||||||
row.add(Root.COLUMN_TITLE, applicationName);
|
row.add(Root.COLUMN_TITLE, applicationName);
|
||||||
row.add(Root.COLUMN_MIME_TYPES, ALL_MIME_TYPES);
|
row.add(Root.COLUMN_MIME_TYPES, ALL_MIME_TYPES);
|
||||||
row.add(Root.COLUMN_AVAILABLE_BYTES, BASE_DIR.getFreeSpace());
|
row.add(Root.COLUMN_AVAILABLE_BYTES, BASE_DIR.getFreeSpace());
|
||||||
@@ -117,6 +117,29 @@ public class TermuxDocumentsProvider extends DocumentsProvider {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String createDocument(String parentDocumentId, String mimeType, String displayName) throws FileNotFoundException {
|
||||||
|
File newFile = new File(parentDocumentId, displayName);
|
||||||
|
int noConflictId = 2;
|
||||||
|
while (newFile.exists()) {
|
||||||
|
newFile = new File(parentDocumentId, displayName + " (" + noConflictId++ + ")");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
boolean succeeded;
|
||||||
|
if (Document.MIME_TYPE_DIR.equals(mimeType)) {
|
||||||
|
succeeded = newFile.mkdir();
|
||||||
|
} else {
|
||||||
|
succeeded = newFile.createNewFile();
|
||||||
|
}
|
||||||
|
if (!succeeded) {
|
||||||
|
throw new FileNotFoundException("Failed to create document with id " + newFile.getPath());
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new FileNotFoundException("Failed to create document with id " + newFile.getPath());
|
||||||
|
}
|
||||||
|
return newFile.getPath();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void deleteDocument(String documentId) throws FileNotFoundException {
|
public void deleteDocument(String documentId) throws FileNotFoundException {
|
||||||
File file = getFileForDocId(documentId);
|
File file = getFileForDocId(documentId);
|
||||||
@@ -169,6 +192,11 @@ public class TermuxDocumentsProvider extends DocumentsProvider {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isChildDocument(String parentDocumentId, String documentId) {
|
||||||
|
return documentId.startsWith(parentDocumentId);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the document id given a file. This document id must be consistent across time as other
|
* Get the document id given a file. This document id must be consistent across time as other
|
||||||
* applications may save the ID and use it to reference documents later.
|
* applications may save the ID and use it to reference documents later.
|
||||||
@@ -220,10 +248,11 @@ public class TermuxDocumentsProvider extends DocumentsProvider {
|
|||||||
|
|
||||||
int flags = 0;
|
int flags = 0;
|
||||||
if (file.isDirectory()) {
|
if (file.isDirectory()) {
|
||||||
if (file.isDirectory() && file.canWrite()) flags |= Document.FLAG_DIR_SUPPORTS_CREATE;
|
if (file.canWrite()) flags |= Document.FLAG_DIR_SUPPORTS_CREATE;
|
||||||
} else if (file.canWrite()) {
|
} else if (file.canWrite()) {
|
||||||
flags |= Document.FLAG_SUPPORTS_WRITE | Document.FLAG_SUPPORTS_DELETE;
|
flags |= Document.FLAG_SUPPORTS_WRITE;
|
||||||
}
|
}
|
||||||
|
if (file.getParentFile().canWrite()) flags |= Document.FLAG_SUPPORTS_DELETE;
|
||||||
|
|
||||||
final String displayName = file.getName();
|
final String displayName = file.getName();
|
||||||
final String mimeType = getMimeType(file);
|
final String mimeType = getMimeType(file);
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import java.io.FileOutputStream;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
public class TermuxFileReceiverActivity extends Activity {
|
public class TermuxFileReceiverActivity extends Activity {
|
||||||
|
|
||||||
@@ -36,6 +37,11 @@ public class TermuxFileReceiverActivity extends Activity {
|
|||||||
*/
|
*/
|
||||||
boolean mFinishOnDismissNameDialog = true;
|
boolean mFinishOnDismissNameDialog = true;
|
||||||
|
|
||||||
|
static boolean isSharedTextAnUrl(String sharedText) {
|
||||||
|
return Patterns.WEB_URL.matcher(sharedText).matches()
|
||||||
|
|| Pattern.matches("magnet:\\?xt=urn:btih:.*?", sharedText);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onResume() {
|
protected void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
@@ -50,7 +56,7 @@ public class TermuxFileReceiverActivity extends Activity {
|
|||||||
final Uri sharedUri = intent.getParcelableExtra(Intent.EXTRA_STREAM);
|
final Uri sharedUri = intent.getParcelableExtra(Intent.EXTRA_STREAM);
|
||||||
|
|
||||||
if (sharedText != null) {
|
if (sharedText != null) {
|
||||||
if (Patterns.WEB_URL.matcher(sharedText).matches()) {
|
if (isSharedTextAnUrl(sharedText)) {
|
||||||
handleUrlAndFinish(sharedText);
|
handleUrlAndFinish(sharedText);
|
||||||
} else {
|
} else {
|
||||||
String subject = intent.getStringExtra(Intent.EXTRA_SUBJECT);
|
String subject = intent.getStringExtra(Intent.EXTRA_SUBJECT);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
|
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
|
||||||
<solid android:color="#E0E0E0" />
|
<solid android:color="#E0E0E0" />
|
||||||
</shape>
|
</shape>
|
||||||
|
|||||||
4
app/src/main/res/drawable/current_session_black.xml
Normal file
4
app/src/main/res/drawable/current_session_black.xml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
|
||||||
|
<solid android:color="#212325" />
|
||||||
|
</shape>
|
||||||
@@ -1,33 +1,24 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:height="48dp"
|
android:width="24dp"
|
||||||
android:width="48dp"
|
android:height="24dp"
|
||||||
android:viewportWidth="48"
|
android:viewportWidth="24"
|
||||||
android:viewportHeight="48">
|
android:viewportHeight="24">
|
||||||
<!--
|
<!--
|
||||||
https://material.google.com/style/icons.html
|
Updated notification icon compliant with system icons guidelines
|
||||||
|
https://material.io/design/iconography/system-icons.html
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<!-- Screen border. -->
|
<group>
|
||||||
<path android:fillColor="#00000000"
|
<clip-path
|
||||||
android:strokeColor="#FFF"
|
android:pathData="M0,0h24v24h-24z"/>
|
||||||
android:strokeWidth="3"
|
|
||||||
android:pathData="M7,4
|
|
||||||
l34,0
|
|
||||||
q3 0,3 3
|
|
||||||
l0,34
|
|
||||||
q0 3, -3 3
|
|
||||||
l-34,0
|
|
||||||
q-3 0, -3-3
|
|
||||||
l0 -34
|
|
||||||
q0 -3, 3 -3"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- Block cursor. -->
|
<path
|
||||||
<path android:fillColor="#FFF"
|
android:pathData="M5,4H2L8,12L2,20H5L11,12L5,4Z"
|
||||||
android:pathData="M14,14
|
android:fillColor="#ffffff"/>
|
||||||
l5,0
|
|
||||||
l0,10
|
|
||||||
l-5,0"
|
|
||||||
/>
|
|
||||||
|
|
||||||
|
<path
|
||||||
|
android:pathData="M13,18H22V20H13V18Z"
|
||||||
|
android:fillColor="#ffffff"/>
|
||||||
|
|
||||||
|
</group>
|
||||||
</vector>
|
</vector>
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:state_activated="true" android:drawable="@drawable/current_session_black"/>
|
||||||
|
<item android:state_activated="false" android:drawable="@drawable/session_ripple_black"/>
|
||||||
|
</selector>
|
||||||
@@ -4,4 +4,4 @@
|
|||||||
<item>
|
<item>
|
||||||
<color android:color="@android:color/white" />
|
<color android:color="@android:color/white" />
|
||||||
</item>
|
</item>
|
||||||
</ripple>
|
</ripple>
|
||||||
|
|||||||
7
app/src/main/res/drawable/session_ripple_black.xml
Normal file
7
app/src/main/res/drawable/session_ripple_black.xml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:color="@android:color/darker_gray" >
|
||||||
|
<item>
|
||||||
|
<color android:color="@android:color/background_dark" />
|
||||||
|
</item>
|
||||||
|
</ripple>
|
||||||
@@ -2,7 +2,8 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<string name="application_name">Termux</string>
|
<string name="application_name">Termux</string>
|
||||||
<string name="shared_user_label">Termux user</string>
|
<string name="shared_user_label">Termux user</string>
|
||||||
<string name="app_failsafe_mode">Termux (failsafe)</string>
|
<string name="run_command_permission_label">Run commands in Termux environment</string>
|
||||||
|
<string name="run_command_permission_description">execute arbitrary commands within Termux environment</string>
|
||||||
<string name="new_session">New session</string>
|
<string name="new_session">New session</string>
|
||||||
<string name="new_session_failsafe">Failsafe</string>
|
<string name="new_session_failsafe">Failsafe</string>
|
||||||
<string name="toggle_soft_keyboard">Keyboard</string>
|
<string name="toggle_soft_keyboard">Keyboard</string>
|
||||||
@@ -14,7 +15,7 @@
|
|||||||
|
|
||||||
<string name="bootstrap_installer_body">Installing…</string>
|
<string name="bootstrap_installer_body">Installing…</string>
|
||||||
<string name="bootstrap_error_title">Unable to install</string>
|
<string name="bootstrap_error_title">Unable to install</string>
|
||||||
<string name="bootstrap_error_body">Termux was unable to install the bootstrap packages.\n\nCheck your network connection and try again.</string>
|
<string name="bootstrap_error_body">Termux was unable to install the bootstrap packages.</string>
|
||||||
<string name="bootstrap_error_abort">Abort</string>
|
<string name="bootstrap_error_abort">Abort</string>
|
||||||
<string name="bootstrap_error_try_again">Try again</string>
|
<string name="bootstrap_error_try_again">Try again</string>
|
||||||
<string name="bootstrap_error_not_primary_user_message">Termux can only be installed on the primary user account.</string>
|
<string name="bootstrap_error_not_primary_user_message">Termux can only be installed on the primary user account.</string>
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:android="http://schemas.android.com/apk/res/android">
|
<resources xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
<!-- See https://developer.android.com/training/material/theme.html for how to customize the Material theme. -->
|
|
||||||
<!-- NOTE: Cannot use "Light." since it hides the terminal scrollbar on the default black background. -->
|
|
||||||
<style name="Theme.Termux" parent="@android:style/Theme.Material.Light.NoActionBar">
|
<style name="Theme.Termux" parent="@android:style/Theme.Material.Light.NoActionBar">
|
||||||
<item name="android:statusBarColor">#000000</item>
|
<item name="android:statusBarColor">#000000</item>
|
||||||
<item name="android:colorPrimary">#FF000000</item>
|
<item name="android:colorPrimary">#FF000000</item>
|
||||||
@@ -23,9 +21,29 @@
|
|||||||
<item name="android:windowAllowEnterTransitionOverlap">true</item>
|
<item name="android:windowAllowEnterTransitionOverlap">true</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="TermuxAlertDialogStyle" parent="@android:style/Theme.Material.Light.Dialog.Alert">
|
<style name="TermuxAlertDialogStyle" parent="@android:style/Theme.Material.Light.Dialog.Alert">
|
||||||
<!-- Seen in buttons on alert dialog: -->
|
<!-- Seen in buttons on alert dialog: -->
|
||||||
<item name="android:colorAccent">#212121</item>
|
<item name="android:colorAccent">#212121</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<!-- See https://developer.android.com/training/material/theme.html for how to customize the Material theme. -->
|
||||||
|
<!-- NOTE: Cannot use "Light." since it hides the terminal scrollbar on the default black background. -->
|
||||||
|
<style name="Theme.Termux.Black" parent="@android:style/Theme.Material.NoActionBar">
|
||||||
|
<item name="android:statusBarColor">#000000</item>
|
||||||
|
<item name="android:colorPrimary">#FF000000</item>
|
||||||
|
<item name="android:windowBackground">@android:color/black</item>
|
||||||
|
|
||||||
|
<!-- Seen in buttons on left drawer: -->
|
||||||
|
<item name="android:colorAccent">#FDFDFD</item>
|
||||||
|
<!-- Avoid action mode toolbar pushing down terminal content when
|
||||||
|
selecting text on pre-6.0 (non-floating toolbar). -->
|
||||||
|
<item name="android:windowActionModeOverlay">true</item>
|
||||||
|
|
||||||
|
<item name="android:windowTranslucentStatus">true</item>
|
||||||
|
<item name="android:windowTranslucentNavigation">true</item>
|
||||||
|
|
||||||
|
<!-- https://developer.android.com/training/tv/start/start.html#transition-color -->
|
||||||
|
<item name="android:windowAllowReturnTransitionOverlap">true</item>
|
||||||
|
<item name="android:windowAllowEnterTransitionOverlap">true</item>
|
||||||
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<full-backup-content>
|
|
||||||
<!-- See https://developer.android.com/training/backup/autosyncapi.html -->
|
|
||||||
<include domain="file" path="home/backup" />
|
|
||||||
</full-backup-content>
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
<shortcuts xmlns:tools="http://schemas.android.com/tools"
|
<shortcuts xmlns:tools="http://schemas.android.com/tools"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
<shortcut
|
<shortcut
|
||||||
android:shortcutId="new_session"
|
android:shortcutId="new_session"
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
@@ -11,4 +12,19 @@
|
|||||||
android:targetPackage="com.termux"
|
android:targetPackage="com.termux"
|
||||||
android:targetClass="com.termux.app.TermuxActivity"/>
|
android:targetClass="com.termux.app.TermuxActivity"/>
|
||||||
</shortcut>
|
</shortcut>
|
||||||
|
|
||||||
|
<shortcut
|
||||||
|
android:shortcutId="new_failsafe_session"
|
||||||
|
android:enabled="true"
|
||||||
|
android:icon="@drawable/ic_new_session"
|
||||||
|
android:shortcutShortLabel="@string/new_session_failsafe"
|
||||||
|
tools:targetApi="n_mr1">
|
||||||
|
<intent
|
||||||
|
android:action="android.intent.action.RUN"
|
||||||
|
android:targetPackage="com.termux"
|
||||||
|
android:targetClass="com.termux.app.TermuxActivity">
|
||||||
|
<extra android:name="com.termux.app.failsafe_session" android:value="true" />
|
||||||
|
</intent>
|
||||||
|
</shortcut>
|
||||||
|
|
||||||
</shortcuts>
|
</shortcuts>
|
||||||
|
|||||||
@@ -1,25 +1,30 @@
|
|||||||
package com.termux.app;
|
package com.termux.app;
|
||||||
|
|
||||||
import junit.framework.TestCase;
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
|
|
||||||
public class TermuxActivityTest extends TestCase {
|
public class TermuxActivityTest {
|
||||||
|
|
||||||
private void assertUrlsAre(String text, String... urls) {
|
private void assertUrlsAre(String text, String... urls) {
|
||||||
LinkedHashSet<String> expected = new LinkedHashSet<>();
|
LinkedHashSet<String> expected = new LinkedHashSet<>();
|
||||||
Collections.addAll(expected, urls);
|
Collections.addAll(expected, urls);
|
||||||
assertEquals(expected, TermuxActivity.extractUrls(text));
|
Assert.assertEquals(expected, TermuxActivity.extractUrls(text));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testExtractUrls() {
|
@Test
|
||||||
assertUrlsAre("hello http://example.com world", "http://example.com");
|
public void testExtractUrls() {
|
||||||
|
assertUrlsAre("hello http://example.com world", "http://example.com");
|
||||||
|
|
||||||
assertUrlsAre("http://example.com\nhttp://another.com", "http://example.com", "http://another.com");
|
assertUrlsAre("http://example.com\nhttp://another.com", "http://example.com", "http://another.com");
|
||||||
|
|
||||||
assertUrlsAre("hello http://example.com world and http://more.example.com with secure https://more.example.com",
|
assertUrlsAre("hello http://example.com world and http://more.example.com with secure https://more.example.com",
|
||||||
"http://example.com", "http://more.example.com", "https://more.example.com");
|
"http://example.com", "http://more.example.com", "https://more.example.com");
|
||||||
}
|
|
||||||
|
assertUrlsAre("hello https://example.com/#bar https://example.com/foo#bar",
|
||||||
|
"https://example.com/#bar", "https://example.com/foo#bar");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package com.termux.filepicker;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.robolectric.RobolectricTestRunner;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@RunWith(RobolectricTestRunner.class)
|
||||||
|
public class TermuxFileReceiverActivityTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsSharedTextAnUrl() {
|
||||||
|
List<String> validUrls = new ArrayList<>();
|
||||||
|
validUrls.add("http://example.com");
|
||||||
|
validUrls.add("https://example.com");
|
||||||
|
validUrls.add("https://example.com/path/parameter=foo");
|
||||||
|
validUrls.add("magnet:?xt=urn:btih:d540fc48eb12f2833163eed6421d449dd8f1ce1f&dn=Ubuntu+desktop+19.04+%2864bit%29&tr=udp%3A%2F%2Ftracker.openbittorrent.com%3A80&tr=udp%3A%2F%2Ftracker.publicbt.com%3A80&tr=udp%3A%2F%2Ftracker.ccc.de%3A80");
|
||||||
|
for (String url : validUrls) {
|
||||||
|
Assert.assertTrue(TermuxFileReceiverActivity.isSharedTextAnUrl(url));
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> invalidUrls = new ArrayList<>();
|
||||||
|
invalidUrls.add("a test with example.com");
|
||||||
|
for (String url : invalidUrls) {
|
||||||
|
Assert.assertFalse(TermuxFileReceiverActivity.isSharedTextAnUrl(url));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@ buildscript {
|
|||||||
google()
|
google()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:3.3.2'
|
classpath 'com.android.tools.build:gradle:4.0.1'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,3 +13,9 @@
|
|||||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||||
# org.gradle.parallel=true
|
# org.gradle.parallel=true
|
||||||
org.gradle.jvmargs=-Xmx2048M
|
org.gradle.jvmargs=-Xmx2048M
|
||||||
|
android.useAndroidX=true
|
||||||
|
|
||||||
|
minSdkVersion=24
|
||||||
|
targetSdkVersion=28
|
||||||
|
ndkVersion=21.3.6528147
|
||||||
|
compileSdkVersion=28
|
||||||
|
|||||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
3
gradle/wrapper/gradle-wrapper.properties
vendored
3
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,6 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-bin.zip
|
||||||
|
distributionSha256Sum=7873ed5287f47ca03549ab8dcb6dc877ac7f0e3d7b1eb12685161d10080910ac
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
53
gradlew
vendored
53
gradlew
vendored
@@ -1,5 +1,21 @@
|
|||||||
#!/usr/bin/env sh
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
#
|
||||||
|
# Copyright 2015 the original author or authors.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
##
|
##
|
||||||
## Gradle start up script for UN*X
|
## Gradle start up script for UN*X
|
||||||
@@ -28,7 +44,7 @@ APP_NAME="Gradle"
|
|||||||
APP_BASE_NAME=`basename "$0"`
|
APP_BASE_NAME=`basename "$0"`
|
||||||
|
|
||||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
DEFAULT_JVM_OPTS='"-Xmx64m"'
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
MAX_FD="maximum"
|
MAX_FD="maximum"
|
||||||
@@ -66,6 +82,7 @@ esac
|
|||||||
|
|
||||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
# Determine the Java command to use to start the JVM.
|
# Determine the Java command to use to start the JVM.
|
||||||
if [ -n "$JAVA_HOME" ] ; then
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
@@ -109,10 +126,11 @@ if $darwin; then
|
|||||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# For Cygwin, switch paths to Windows format before running java
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
if $cygwin ; then
|
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||||
|
|
||||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||||
|
|
||||||
# We build the pattern for arguments to be converted via cygpath
|
# We build the pattern for arguments to be converted via cygpath
|
||||||
@@ -138,19 +156,19 @@ if $cygwin ; then
|
|||||||
else
|
else
|
||||||
eval `echo args$i`="\"$arg\""
|
eval `echo args$i`="\"$arg\""
|
||||||
fi
|
fi
|
||||||
i=$((i+1))
|
i=`expr $i + 1`
|
||||||
done
|
done
|
||||||
case $i in
|
case $i in
|
||||||
(0) set -- ;;
|
0) set -- ;;
|
||||||
(1) set -- "$args0" ;;
|
1) set -- "$args0" ;;
|
||||||
(2) set -- "$args0" "$args1" ;;
|
2) set -- "$args0" "$args1" ;;
|
||||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
3) set -- "$args0" "$args1" "$args2" ;;
|
||||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -159,14 +177,9 @@ save () {
|
|||||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||||
echo " "
|
echo " "
|
||||||
}
|
}
|
||||||
APP_ARGS=$(save "$@")
|
APP_ARGS=`save "$@"`
|
||||||
|
|
||||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||||
|
|
||||||
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
|
||||||
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
|
||||||
cd "$(dirname "$0")"
|
|
||||||
fi
|
|
||||||
|
|
||||||
exec "$JAVACMD" "$@"
|
exec "$JAVACMD" "$@"
|
||||||
|
|||||||
43
gradlew.bat
vendored
43
gradlew.bat
vendored
@@ -1,3 +1,19 @@
|
|||||||
|
@rem
|
||||||
|
@rem Copyright 2015 the original author or authors.
|
||||||
|
@rem
|
||||||
|
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@rem you may not use this file except in compliance with the License.
|
||||||
|
@rem You may obtain a copy of the License at
|
||||||
|
@rem
|
||||||
|
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
@rem
|
||||||
|
@rem Unless required by applicable law or agreed to in writing, software
|
||||||
|
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@rem See the License for the specific language governing permissions and
|
||||||
|
@rem limitations under the License.
|
||||||
|
@rem
|
||||||
|
|
||||||
@if "%DEBUG%" == "" @echo off
|
@if "%DEBUG%" == "" @echo off
|
||||||
@rem ##########################################################################
|
@rem ##########################################################################
|
||||||
@rem
|
@rem
|
||||||
@@ -13,15 +29,18 @@ if "%DIRNAME%" == "" set DIRNAME=.
|
|||||||
set APP_BASE_NAME=%~n0
|
set APP_BASE_NAME=%~n0
|
||||||
set APP_HOME=%DIRNAME%
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||||
|
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||||
|
|
||||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
set DEFAULT_JVM_OPTS="-Xmx64m"
|
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||||
|
|
||||||
@rem Find java.exe
|
@rem Find java.exe
|
||||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
set JAVA_EXE=java.exe
|
set JAVA_EXE=java.exe
|
||||||
%JAVA_EXE% -version >NUL 2>&1
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
if "%ERRORLEVEL%" == "0" goto init
|
if "%ERRORLEVEL%" == "0" goto execute
|
||||||
|
|
||||||
echo.
|
echo.
|
||||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
@@ -35,7 +54,7 @@ goto fail
|
|||||||
set JAVA_HOME=%JAVA_HOME:"=%
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
if exist "%JAVA_EXE%" goto init
|
if exist "%JAVA_EXE%" goto execute
|
||||||
|
|
||||||
echo.
|
echo.
|
||||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||||
@@ -45,28 +64,14 @@ echo location of your Java installation.
|
|||||||
|
|
||||||
goto fail
|
goto fail
|
||||||
|
|
||||||
:init
|
|
||||||
@rem Get command-line arguments, handling Windows variants
|
|
||||||
|
|
||||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
|
||||||
|
|
||||||
:win9xME_args
|
|
||||||
@rem Slurp the command line arguments.
|
|
||||||
set CMD_LINE_ARGS=
|
|
||||||
set _SKIP=2
|
|
||||||
|
|
||||||
:win9xME_args_slurp
|
|
||||||
if "x%~1" == "x" goto execute
|
|
||||||
|
|
||||||
set CMD_LINE_ARGS=%*
|
|
||||||
|
|
||||||
:execute
|
:execute
|
||||||
@rem Setup the command line
|
@rem Setup the command line
|
||||||
|
|
||||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
@rem Execute Gradle
|
@rem Execute Gradle
|
||||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||||
|
|
||||||
:end
|
:end
|
||||||
@rem End local scope for the variables with windows NT shell
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
|||||||
@@ -17,11 +17,12 @@ ext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 28
|
compileSdkVersion project.properties.compileSdkVersion.toInteger()
|
||||||
|
ndkVersion project.properties.ndkVersion
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 21
|
minSdkVersion project.properties.minSdkVersion.toInteger()
|
||||||
targetSdkVersion 28
|
targetSdkVersion project.properties.targetSdkVersion.toInteger()
|
||||||
|
|
||||||
externalNativeBuild {
|
externalNativeBuild {
|
||||||
ndkBuild {
|
ndkBuild {
|
||||||
@@ -60,7 +61,7 @@ tasks.withType(Test) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
testImplementation 'junit:junit:4.12'
|
testImplementation 'junit:junit:4.13'
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: '../scripts/bintray-publish.gradle'
|
apply from: '../scripts/bintray-publish.gradle'
|
||||||
|
|||||||
@@ -41,7 +41,23 @@ public final class TerminalBuffer {
|
|||||||
return getSelectedText(0, -getActiveTranscriptRows(), mColumns, mScreenRows).trim();
|
return getSelectedText(0, -getActiveTranscriptRows(), mColumns, mScreenRows).trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getTranscriptTextWithoutJoinedLines() {
|
||||||
|
return getSelectedText(0, -getActiveTranscriptRows(), mColumns, mScreenRows, false).trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTranscriptTextWithFullLinesJoined() {
|
||||||
|
return getSelectedText(0, -getActiveTranscriptRows(), mColumns, mScreenRows, true, true).trim();
|
||||||
|
}
|
||||||
|
|
||||||
public String getSelectedText(int selX1, int selY1, int selX2, int selY2) {
|
public String getSelectedText(int selX1, int selY1, int selX2, int selY2) {
|
||||||
|
return getSelectedText(selX1, selY1, selX2, selY2, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSelectedText(int selX1, int selY1, int selX2, int selY2, boolean joinBackLines) {
|
||||||
|
return getSelectedText(selX1, selY1, selX2, selY2, true, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSelectedText(int selX1, int selY1, int selX2, int selY2, boolean joinBackLines, boolean joinFullLines) {
|
||||||
final StringBuilder builder = new StringBuilder();
|
final StringBuilder builder = new StringBuilder();
|
||||||
final int columns = mColumns;
|
final int columns = mColumns;
|
||||||
|
|
||||||
@@ -79,7 +95,9 @@ public final class TerminalBuffer {
|
|||||||
}
|
}
|
||||||
if (lastPrintingCharIndex != -1)
|
if (lastPrintingCharIndex != -1)
|
||||||
builder.append(line, x1Index, lastPrintingCharIndex - x1Index + 1);
|
builder.append(line, x1Index, lastPrintingCharIndex - x1Index + 1);
|
||||||
if (!rowLineWrap && row < selY2 && row < mScreenRows - 1) builder.append('\n');
|
boolean lineFillsWidth = lastPrintingCharIndex == x2Index - 1;
|
||||||
|
if ((!joinBackLines || !rowLineWrap) && (!joinFullLines || !lineFillsWidth)
|
||||||
|
&& row < selY2 && row < mScreenRows - 1) builder.append('\n');
|
||||||
}
|
}
|
||||||
return builder.toString();
|
return builder.toString();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1267,6 +1267,7 @@ public final class TerminalEmulator {
|
|||||||
break;
|
break;
|
||||||
case 'c': // RIS - Reset to Initial State (http://vt100.net/docs/vt510-rm/RIS).
|
case 'c': // RIS - Reset to Initial State (http://vt100.net/docs/vt510-rm/RIS).
|
||||||
reset();
|
reset();
|
||||||
|
mMainBuffer.clearTranscript();
|
||||||
blockClear(0, 0, mColumns, mRows);
|
blockClear(0, 0, mColumns, mRows);
|
||||||
setCursorPosition(0, 0);
|
setCursorPosition(0, 0);
|
||||||
break;
|
break;
|
||||||
@@ -1376,10 +1377,10 @@ public final class TerminalEmulator {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'A': // "CSI${n}A" - Cursor up (CUU) ${n} rows.
|
case 'A': // "CSI${n}A" - Cursor up (CUU) ${n} rows.
|
||||||
setCursorRow(Math.max(mTopMargin, mCursorRow - getArg0(1)));
|
setCursorRow(Math.max(0, mCursorRow - getArg0(1)));
|
||||||
break;
|
break;
|
||||||
case 'B': // "CSI${n}B" - Cursor down (CUD) ${n} rows.
|
case 'B': // "CSI${n}B" - Cursor down (CUD) ${n} rows.
|
||||||
setCursorRow(Math.min(mBottomMargin - 1, mCursorRow + getArg0(1)));
|
setCursorRow(Math.min(mRows - 1, mCursorRow + getArg0(1)));
|
||||||
break;
|
break;
|
||||||
case 'C': // "CSI${n}C" - Cursor forward (CUF).
|
case 'C': // "CSI${n}C" - Cursor forward (CUF).
|
||||||
case 'a': // "CSI${n}a" - Horizontal position relative (HPR). From ISO-6428/ECMA-48.
|
case 'a': // "CSI${n}a" - Horizontal position relative (HPR). From ISO-6428/ECMA-48.
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import android.system.Os;
|
|||||||
import android.system.OsConstants;
|
import android.system.OsConstants;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
import java.io.FileDescriptor;
|
import java.io.FileDescriptor;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
@@ -109,13 +110,13 @@ public final class TerminalSession extends TerminalOutput {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleMessage(Message msg) {
|
public void handleMessage(Message msg) {
|
||||||
if (msg.what == MSG_NEW_INPUT && isRunning()) {
|
int bytesRead = mProcessToTerminalIOQueue.read(mReceiveBuffer, false);
|
||||||
int bytesRead = mProcessToTerminalIOQueue.read(mReceiveBuffer, false);
|
if (bytesRead > 0) {
|
||||||
if (bytesRead > 0) {
|
mEmulator.append(mReceiveBuffer, bytesRead);
|
||||||
mEmulator.append(mReceiveBuffer, bytesRead);
|
notifyScreenUpdate();
|
||||||
notifyScreenUpdate();
|
}
|
||||||
}
|
|
||||||
} else if (msg.what == MSG_PROCESS_EXITED) {
|
if (msg.what == MSG_PROCESS_EXITED) {
|
||||||
int exitCode = (Integer) msg.obj;
|
int exitCode = (Integer) msg.obj;
|
||||||
cleanupResources(exitCode);
|
cleanupResources(exitCode);
|
||||||
mChangeCallback.onSessionFinished(TerminalSession.this);
|
mChangeCallback.onSessionFinished(TerminalSession.this);
|
||||||
@@ -339,4 +340,26 @@ public final class TerminalSession extends TerminalOutput {
|
|||||||
return mShellPid;
|
return mShellPid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns the shell's working directory or null if it was unavailable. */
|
||||||
|
public String getCwd() {
|
||||||
|
if (mShellPid < 1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
final String cwdSymlink = String.format("/proc/%s/cwd/", mShellPid);
|
||||||
|
String outputPath = new File(cwdSymlink).getCanonicalPath();
|
||||||
|
String outputPathWithTrailingSlash = outputPath;
|
||||||
|
if (!outputPath.endsWith("/")) {
|
||||||
|
outputPathWithTrailingSlash += '/';
|
||||||
|
}
|
||||||
|
if (!cwdSymlink.equals(outputPathWithTrailingSlash)) {
|
||||||
|
return outputPath;
|
||||||
|
}
|
||||||
|
} catch (IOException | SecurityException e) {
|
||||||
|
Log.e(EmulatorDebug.LOG_TAG, "Error getting current directory", e);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,212 +4,225 @@ package com.termux.terminal;
|
|||||||
* Implementation of wcwidth(3) for Unicode 9.
|
* Implementation of wcwidth(3) for Unicode 9.
|
||||||
*
|
*
|
||||||
* Implementation from https://github.com/jquast/wcwidth but we return 0 for unprintable characters.
|
* Implementation from https://github.com/jquast/wcwidth but we return 0 for unprintable characters.
|
||||||
|
*
|
||||||
|
* IMPORTANT:
|
||||||
|
* Must be kept in sync with the following:
|
||||||
|
* https://github.com/termux/wcwidth
|
||||||
|
* https://github.com/termux/libandroid-support
|
||||||
|
* https://github.com/termux/termux-packages/tree/master/libandroid-support
|
||||||
*/
|
*/
|
||||||
public final class WcWidth {
|
public final class WcWidth {
|
||||||
|
|
||||||
// From https://github.com/jquast/wcwidth/blob/master/wcwidth/table_zero.py
|
// From https://github.com/jquast/wcwidth/blob/master/wcwidth/table_zero.py
|
||||||
// t commit 0d7de112202cc8b2ebe9232ff4a5c954f19d561a (2016-07-02):
|
// at commit b29897e5a1b403a0e36f7fc991614981cbc42475 (2020-07-14):
|
||||||
private static final int[][] ZERO_WIDTH = {
|
private static final int[][] ZERO_WIDTH = {
|
||||||
{0x0300, 0x036f}, // Combining Grave Accent ..Combining Latin Small Le
|
{0x00300, 0x0036f}, // Combining Grave Accent ..Combining Latin Small Le
|
||||||
{0x0483, 0x0489}, // Combining Cyrillic Titlo..Combining Cyrillic Milli
|
{0x00483, 0x00489}, // Combining Cyrillic Titlo..Combining Cyrillic Milli
|
||||||
{0x0591, 0x05bd}, // Hebrew Accent Etnahta ..Hebrew Point Meteg
|
{0x00591, 0x005bd}, // Hebrew Accent Etnahta ..Hebrew Point Meteg
|
||||||
{0x05bf, 0x05bf}, // Hebrew Point Rafe ..Hebrew Point Rafe
|
{0x005bf, 0x005bf}, // Hebrew Point Rafe ..Hebrew Point Rafe
|
||||||
{0x05c1, 0x05c2}, // Hebrew Point Shin Dot ..Hebrew Point Sin Dot
|
{0x005c1, 0x005c2}, // Hebrew Point Shin Dot ..Hebrew Point Sin Dot
|
||||||
{0x05c4, 0x05c5}, // Hebrew Mark Upper Dot ..Hebrew Mark Lower Dot
|
{0x005c4, 0x005c5}, // Hebrew Mark Upper Dot ..Hebrew Mark Lower Dot
|
||||||
{0x05c7, 0x05c7}, // Hebrew Point Qamats Qata..Hebrew Point Qamats Qata
|
{0x005c7, 0x005c7}, // Hebrew Point Qamats Qata..Hebrew Point Qamats Qata
|
||||||
{0x0610, 0x061a}, // Arabic Sign Sallallahou ..Arabic Small Kasra
|
{0x00610, 0x0061a}, // Arabic Sign Sallallahou ..Arabic Small Kasra
|
||||||
{0x064b, 0x065f}, // Arabic Fathatan ..Arabic Wavy Hamza Below
|
{0x0064b, 0x0065f}, // Arabic Fathatan ..Arabic Wavy Hamza Below
|
||||||
{0x0670, 0x0670}, // Arabic Letter Superscrip..Arabic Letter Superscrip
|
{0x00670, 0x00670}, // Arabic Letter Superscrip..Arabic Letter Superscrip
|
||||||
{0x06d6, 0x06dc}, // Arabic Small High Ligatu..Arabic Small High Seen
|
{0x006d6, 0x006dc}, // Arabic Small High Ligatu..Arabic Small High Seen
|
||||||
{0x06df, 0x06e4}, // Arabic Small High Rounde..Arabic Small High Madda
|
{0x006df, 0x006e4}, // Arabic Small High Rounde..Arabic Small High Madda
|
||||||
{0x06e7, 0x06e8}, // Arabic Small High Yeh ..Arabic Small High Noon
|
{0x006e7, 0x006e8}, // Arabic Small High Yeh ..Arabic Small High Noon
|
||||||
{0x06ea, 0x06ed}, // Arabic Empty Centre Low ..Arabic Small Low Meem
|
{0x006ea, 0x006ed}, // Arabic Empty Centre Low ..Arabic Small Low Meem
|
||||||
{0x0711, 0x0711}, // Syriac Letter Superscrip..Syriac Letter Superscrip
|
{0x00711, 0x00711}, // Syriac Letter Superscrip..Syriac Letter Superscrip
|
||||||
{0x0730, 0x074a}, // Syriac Pthaha Above ..Syriac Barrekh
|
{0x00730, 0x0074a}, // Syriac Pthaha Above ..Syriac Barrekh
|
||||||
{0x07a6, 0x07b0}, // Thaana Abafili ..Thaana Sukun
|
{0x007a6, 0x007b0}, // Thaana Abafili ..Thaana Sukun
|
||||||
{0x07eb, 0x07f3}, // Nko Combining Sh||t High..Nko Combining Double Dot
|
{0x007eb, 0x007f3}, // Nko Combining Short High..Nko Combining Double Dot
|
||||||
{0x0816, 0x0819}, // Samaritan Mark In ..Samaritan Mark Dagesh
|
{0x007fd, 0x007fd}, // Nko Dantayalan ..Nko Dantayalan
|
||||||
{0x081b, 0x0823}, // Samaritan Mark Epentheti..Samaritan Vowel Sign A
|
{0x00816, 0x00819}, // Samaritan Mark In ..Samaritan Mark Dagesh
|
||||||
{0x0825, 0x0827}, // Samaritan Vowel Sign Sho..Samaritan Vowel Sign U
|
{0x0081b, 0x00823}, // Samaritan Mark Epentheti..Samaritan Vowel Sign A
|
||||||
{0x0829, 0x082d}, // Samaritan Vowel Sign Lon..Samaritan Mark Nequdaa
|
{0x00825, 0x00827}, // Samaritan Vowel Sign Sho..Samaritan Vowel Sign U
|
||||||
{0x0859, 0x085b}, // Mandaic Affrication Mark..Mandaic Gemination Mark
|
{0x00829, 0x0082d}, // Samaritan Vowel Sign Lon..Samaritan Mark Nequdaa
|
||||||
{0x08d4, 0x08e1}, // (nil) ..
|
{0x00859, 0x0085b}, // Mandaic Affrication Mark..Mandaic Gemination Mark
|
||||||
{0x08e3, 0x0902}, // Arabic Turned Damma Belo..Devanagari Sign Anusvara
|
{0x008d3, 0x008e1}, // Arabic Small Low Waw ..Arabic Small High Sign S
|
||||||
{0x093a, 0x093a}, // Devanagari Vowel Sign Oe..Devanagari Vowel Sign Oe
|
{0x008e3, 0x00902}, // Arabic Turned Damma Belo..Devanagari Sign Anusvara
|
||||||
{0x093c, 0x093c}, // Devanagari Sign Nukta ..Devanagari Sign Nukta
|
{0x0093a, 0x0093a}, // Devanagari Vowel Sign Oe..Devanagari Vowel Sign Oe
|
||||||
{0x0941, 0x0948}, // Devanagari Vowel Sign U ..Devanagari Vowel Sign Ai
|
{0x0093c, 0x0093c}, // Devanagari Sign Nukta ..Devanagari Sign Nukta
|
||||||
{0x094d, 0x094d}, // Devanagari Sign Virama ..Devanagari Sign Virama
|
{0x00941, 0x00948}, // Devanagari Vowel Sign U ..Devanagari Vowel Sign Ai
|
||||||
{0x0951, 0x0957}, // Devanagari Stress Sign U..Devanagari Vowel Sign Uu
|
{0x0094d, 0x0094d}, // Devanagari Sign Virama ..Devanagari Sign Virama
|
||||||
{0x0962, 0x0963}, // Devanagari Vowel Sign Vo..Devanagari Vowel Sign Vo
|
{0x00951, 0x00957}, // Devanagari Stress Sign U..Devanagari Vowel Sign Uu
|
||||||
{0x0981, 0x0981}, // Bengali Sign Candrabindu..Bengali Sign Candrabindu
|
{0x00962, 0x00963}, // Devanagari Vowel Sign Vo..Devanagari Vowel Sign Vo
|
||||||
{0x09bc, 0x09bc}, // Bengali Sign Nukta ..Bengali Sign Nukta
|
{0x00981, 0x00981}, // Bengali Sign Candrabindu..Bengali Sign Candrabindu
|
||||||
{0x09c1, 0x09c4}, // Bengali Vowel Sign U ..Bengali Vowel Sign Vocal
|
{0x009bc, 0x009bc}, // Bengali Sign Nukta ..Bengali Sign Nukta
|
||||||
{0x09cd, 0x09cd}, // Bengali Sign Virama ..Bengali Sign Virama
|
{0x009c1, 0x009c4}, // Bengali Vowel Sign U ..Bengali Vowel Sign Vocal
|
||||||
{0x09e2, 0x09e3}, // Bengali Vowel Sign Vocal..Bengali Vowel Sign Vocal
|
{0x009cd, 0x009cd}, // Bengali Sign Virama ..Bengali Sign Virama
|
||||||
{0x0a01, 0x0a02}, // Gurmukhi Sign Adak Bindi..Gurmukhi Sign Bindi
|
{0x009e2, 0x009e3}, // Bengali Vowel Sign Vocal..Bengali Vowel Sign Vocal
|
||||||
{0x0a3c, 0x0a3c}, // Gurmukhi Sign Nukta ..Gurmukhi Sign Nukta
|
{0x009fe, 0x009fe}, // Bengali Sandhi Mark ..Bengali Sandhi Mark
|
||||||
{0x0a41, 0x0a42}, // Gurmukhi Vowel Sign U ..Gurmukhi Vowel Sign Uu
|
{0x00a01, 0x00a02}, // Gurmukhi Sign Adak Bindi..Gurmukhi Sign Bindi
|
||||||
{0x0a47, 0x0a48}, // Gurmukhi Vowel Sign Ee ..Gurmukhi Vowel Sign Ai
|
{0x00a3c, 0x00a3c}, // Gurmukhi Sign Nukta ..Gurmukhi Sign Nukta
|
||||||
{0x0a4b, 0x0a4d}, // Gurmukhi Vowel Sign Oo ..Gurmukhi Sign Virama
|
{0x00a41, 0x00a42}, // Gurmukhi Vowel Sign U ..Gurmukhi Vowel Sign Uu
|
||||||
{0x0a51, 0x0a51}, // Gurmukhi Sign Udaat ..Gurmukhi Sign Udaat
|
{0x00a47, 0x00a48}, // Gurmukhi Vowel Sign Ee ..Gurmukhi Vowel Sign Ai
|
||||||
{0x0a70, 0x0a71}, // Gurmukhi Tippi ..Gurmukhi Addak
|
{0x00a4b, 0x00a4d}, // Gurmukhi Vowel Sign Oo ..Gurmukhi Sign Virama
|
||||||
{0x0a75, 0x0a75}, // Gurmukhi Sign Yakash ..Gurmukhi Sign Yakash
|
{0x00a51, 0x00a51}, // Gurmukhi Sign Udaat ..Gurmukhi Sign Udaat
|
||||||
{0x0a81, 0x0a82}, // Gujarati Sign Candrabind..Gujarati Sign Anusvara
|
{0x00a70, 0x00a71}, // Gurmukhi Tippi ..Gurmukhi Addak
|
||||||
{0x0abc, 0x0abc}, // Gujarati Sign Nukta ..Gujarati Sign Nukta
|
{0x00a75, 0x00a75}, // Gurmukhi Sign Yakash ..Gurmukhi Sign Yakash
|
||||||
{0x0ac1, 0x0ac5}, // Gujarati Vowel Sign U ..Gujarati Vowel Sign Cand
|
{0x00a81, 0x00a82}, // Gujarati Sign Candrabind..Gujarati Sign Anusvara
|
||||||
{0x0ac7, 0x0ac8}, // Gujarati Vowel Sign E ..Gujarati Vowel Sign Ai
|
{0x00abc, 0x00abc}, // Gujarati Sign Nukta ..Gujarati Sign Nukta
|
||||||
{0x0acd, 0x0acd}, // Gujarati Sign Virama ..Gujarati Sign Virama
|
{0x00ac1, 0x00ac5}, // Gujarati Vowel Sign U ..Gujarati Vowel Sign Cand
|
||||||
{0x0ae2, 0x0ae3}, // Gujarati Vowel Sign Voca..Gujarati Vowel Sign Voca
|
{0x00ac7, 0x00ac8}, // Gujarati Vowel Sign E ..Gujarati Vowel Sign Ai
|
||||||
{0x0b01, 0x0b01}, // ||iya Sign Candrabindu ..||iya Sign Candrabindu
|
{0x00acd, 0x00acd}, // Gujarati Sign Virama ..Gujarati Sign Virama
|
||||||
{0x0b3c, 0x0b3c}, // ||iya Sign Nukta ..||iya Sign Nukta
|
{0x00ae2, 0x00ae3}, // Gujarati Vowel Sign Voca..Gujarati Vowel Sign Voca
|
||||||
{0x0b3f, 0x0b3f}, // ||iya Vowel Sign I ..||iya Vowel Sign I
|
{0x00afa, 0x00aff}, // Gujarati Sign Sukun ..Gujarati Sign Two-circle
|
||||||
{0x0b41, 0x0b44}, // ||iya Vowel Sign U ..||iya Vowel Sign Vocalic
|
{0x00b01, 0x00b01}, // Oriya Sign Candrabindu ..Oriya Sign Candrabindu
|
||||||
{0x0b4d, 0x0b4d}, // ||iya Sign Virama ..||iya Sign Virama
|
{0x00b3c, 0x00b3c}, // Oriya Sign Nukta ..Oriya Sign Nukta
|
||||||
{0x0b56, 0x0b56}, // ||iya Ai Length Mark ..||iya Ai Length Mark
|
{0x00b3f, 0x00b3f}, // Oriya Vowel Sign I ..Oriya Vowel Sign I
|
||||||
{0x0b62, 0x0b63}, // ||iya Vowel Sign Vocalic..||iya Vowel Sign Vocalic
|
{0x00b41, 0x00b44}, // Oriya Vowel Sign U ..Oriya Vowel Sign Vocalic
|
||||||
{0x0b82, 0x0b82}, // Tamil Sign Anusvara ..Tamil Sign Anusvara
|
{0x00b4d, 0x00b4d}, // Oriya Sign Virama ..Oriya Sign Virama
|
||||||
{0x0bc0, 0x0bc0}, // Tamil Vowel Sign Ii ..Tamil Vowel Sign Ii
|
{0x00b55, 0x00b56}, // (nil) ..Oriya Ai Length Mark
|
||||||
{0x0bcd, 0x0bcd}, // Tamil Sign Virama ..Tamil Sign Virama
|
{0x00b62, 0x00b63}, // Oriya Vowel Sign Vocalic..Oriya Vowel Sign Vocalic
|
||||||
{0x0c00, 0x0c00}, // Telugu Sign Combining Ca..Telugu Sign Combining Ca
|
{0x00b82, 0x00b82}, // Tamil Sign Anusvara ..Tamil Sign Anusvara
|
||||||
{0x0c3e, 0x0c40}, // Telugu Vowel Sign Aa ..Telugu Vowel Sign Ii
|
{0x00bc0, 0x00bc0}, // Tamil Vowel Sign Ii ..Tamil Vowel Sign Ii
|
||||||
{0x0c46, 0x0c48}, // Telugu Vowel Sign E ..Telugu Vowel Sign Ai
|
{0x00bcd, 0x00bcd}, // Tamil Sign Virama ..Tamil Sign Virama
|
||||||
{0x0c4a, 0x0c4d}, // Telugu Vowel Sign O ..Telugu Sign Virama
|
{0x00c00, 0x00c00}, // Telugu Sign Combining Ca..Telugu Sign Combining Ca
|
||||||
{0x0c55, 0x0c56}, // Telugu Length Mark ..Telugu Ai Length Mark
|
{0x00c04, 0x00c04}, // Telugu Sign Combining An..Telugu Sign Combining An
|
||||||
{0x0c62, 0x0c63}, // Telugu Vowel Sign Vocali..Telugu Vowel Sign Vocali
|
{0x00c3e, 0x00c40}, // Telugu Vowel Sign Aa ..Telugu Vowel Sign Ii
|
||||||
{0x0c81, 0x0c81}, // Kannada Sign Candrabindu..Kannada Sign Candrabindu
|
{0x00c46, 0x00c48}, // Telugu Vowel Sign E ..Telugu Vowel Sign Ai
|
||||||
{0x0cbc, 0x0cbc}, // Kannada Sign Nukta ..Kannada Sign Nukta
|
{0x00c4a, 0x00c4d}, // Telugu Vowel Sign O ..Telugu Sign Virama
|
||||||
{0x0cbf, 0x0cbf}, // Kannada Vowel Sign I ..Kannada Vowel Sign I
|
{0x00c55, 0x00c56}, // Telugu Length Mark ..Telugu Ai Length Mark
|
||||||
{0x0cc6, 0x0cc6}, // Kannada Vowel Sign E ..Kannada Vowel Sign E
|
{0x00c62, 0x00c63}, // Telugu Vowel Sign Vocali..Telugu Vowel Sign Vocali
|
||||||
{0x0ccc, 0x0ccd}, // Kannada Vowel Sign Au ..Kannada Sign Virama
|
{0x00c81, 0x00c81}, // Kannada Sign Candrabindu..Kannada Sign Candrabindu
|
||||||
{0x0ce2, 0x0ce3}, // Kannada Vowel Sign Vocal..Kannada Vowel Sign Vocal
|
{0x00cbc, 0x00cbc}, // Kannada Sign Nukta ..Kannada Sign Nukta
|
||||||
{0x0d01, 0x0d01}, // Malayalam Sign Candrabin..Malayalam Sign Candrabin
|
{0x00cbf, 0x00cbf}, // Kannada Vowel Sign I ..Kannada Vowel Sign I
|
||||||
{0x0d41, 0x0d44}, // Malayalam Vowel Sign U ..Malayalam Vowel Sign Voc
|
{0x00cc6, 0x00cc6}, // Kannada Vowel Sign E ..Kannada Vowel Sign E
|
||||||
{0x0d4d, 0x0d4d}, // Malayalam Sign Virama ..Malayalam Sign Virama
|
{0x00ccc, 0x00ccd}, // Kannada Vowel Sign Au ..Kannada Sign Virama
|
||||||
{0x0d62, 0x0d63}, // Malayalam Vowel Sign Voc..Malayalam Vowel Sign Voc
|
{0x00ce2, 0x00ce3}, // Kannada Vowel Sign Vocal..Kannada Vowel Sign Vocal
|
||||||
{0x0dca, 0x0dca}, // Sinhala Sign Al-lakuna ..Sinhala Sign Al-lakuna
|
{0x00d00, 0x00d01}, // Malayalam Sign Combining..Malayalam Sign Candrabin
|
||||||
{0x0dd2, 0x0dd4}, // Sinhala Vowel Sign Ketti..Sinhala Vowel Sign Ketti
|
{0x00d3b, 0x00d3c}, // Malayalam Sign Vertical ..Malayalam Sign Circular
|
||||||
{0x0dd6, 0x0dd6}, // Sinhala Vowel Sign Diga ..Sinhala Vowel Sign Diga
|
{0x00d41, 0x00d44}, // Malayalam Vowel Sign U ..Malayalam Vowel Sign Voc
|
||||||
{0x0e31, 0x0e31}, // Thai Character Mai Han-a..Thai Character Mai Han-a
|
{0x00d4d, 0x00d4d}, // Malayalam Sign Virama ..Malayalam Sign Virama
|
||||||
{0x0e34, 0x0e3a}, // Thai Character Sara I ..Thai Character Phinthu
|
{0x00d62, 0x00d63}, // Malayalam Vowel Sign Voc..Malayalam Vowel Sign Voc
|
||||||
{0x0e47, 0x0e4e}, // Thai Character Maitaikhu..Thai Character Yamakkan
|
{0x00d81, 0x00d81}, // (nil) ..(nil)
|
||||||
{0x0eb1, 0x0eb1}, // Lao Vowel Sign Mai Kan ..Lao Vowel Sign Mai Kan
|
{0x00dca, 0x00dca}, // Sinhala Sign Al-lakuna ..Sinhala Sign Al-lakuna
|
||||||
{0x0eb4, 0x0eb9}, // Lao Vowel Sign I ..Lao Vowel Sign Uu
|
{0x00dd2, 0x00dd4}, // Sinhala Vowel Sign Ketti..Sinhala Vowel Sign Ketti
|
||||||
{0x0ebb, 0x0ebc}, // Lao Vowel Sign Mai Kon ..Lao Semivowel Sign Lo
|
{0x00dd6, 0x00dd6}, // Sinhala Vowel Sign Diga ..Sinhala Vowel Sign Diga
|
||||||
{0x0ec8, 0x0ecd}, // Lao Tone Mai Ek ..Lao Niggahita
|
{0x00e31, 0x00e31}, // Thai Character Mai Han-a..Thai Character Mai Han-a
|
||||||
{0x0f18, 0x0f19}, // Tibetan Astrological Sig..Tibetan Astrological Sig
|
{0x00e34, 0x00e3a}, // Thai Character Sara I ..Thai Character Phinthu
|
||||||
{0x0f35, 0x0f35}, // Tibetan Mark Ngas Bzung ..Tibetan Mark Ngas Bzung
|
{0x00e47, 0x00e4e}, // Thai Character Maitaikhu..Thai Character Yamakkan
|
||||||
{0x0f37, 0x0f37}, // Tibetan Mark Ngas Bzung ..Tibetan Mark Ngas Bzung
|
{0x00eb1, 0x00eb1}, // Lao Vowel Sign Mai Kan ..Lao Vowel Sign Mai Kan
|
||||||
{0x0f39, 0x0f39}, // Tibetan Mark Tsa -phru ..Tibetan Mark Tsa -phru
|
{0x00eb4, 0x00ebc}, // Lao Vowel Sign I ..Lao Semivowel Sign Lo
|
||||||
{0x0f71, 0x0f7e}, // Tibetan Vowel Sign Aa ..Tibetan Sign Rjes Su Nga
|
{0x00ec8, 0x00ecd}, // Lao Tone Mai Ek ..Lao Niggahita
|
||||||
{0x0f80, 0x0f84}, // Tibetan Vowel Sign Rever..Tibetan Mark Halanta
|
{0x00f18, 0x00f19}, // Tibetan Astrological Sig..Tibetan Astrological Sig
|
||||||
{0x0f86, 0x0f87}, // Tibetan Sign Lci Rtags ..Tibetan Sign Yang Rtags
|
{0x00f35, 0x00f35}, // Tibetan Mark Ngas Bzung ..Tibetan Mark Ngas Bzung
|
||||||
{0x0f8d, 0x0f97}, // Tibetan Subjoined Sign L..Tibetan Subjoined Letter
|
{0x00f37, 0x00f37}, // Tibetan Mark Ngas Bzung ..Tibetan Mark Ngas Bzung
|
||||||
{0x0f99, 0x0fbc}, // Tibetan Subjoined Letter..Tibetan Subjoined Letter
|
{0x00f39, 0x00f39}, // Tibetan Mark Tsa -phru ..Tibetan Mark Tsa -phru
|
||||||
{0x0fc6, 0x0fc6}, // Tibetan Symbol Padma Gda..Tibetan Symbol Padma Gda
|
{0x00f71, 0x00f7e}, // Tibetan Vowel Sign Aa ..Tibetan Sign Rjes Su Nga
|
||||||
{0x102d, 0x1030}, // Myanmar Vowel Sign I ..Myanmar Vowel Sign Uu
|
{0x00f80, 0x00f84}, // Tibetan Vowel Sign Rever..Tibetan Mark Halanta
|
||||||
{0x1032, 0x1037}, // Myanmar Vowel Sign Ai ..Myanmar Sign Dot Below
|
{0x00f86, 0x00f87}, // Tibetan Sign Lci Rtags ..Tibetan Sign Yang Rtags
|
||||||
{0x1039, 0x103a}, // Myanmar Sign Virama ..Myanmar Sign Asat
|
{0x00f8d, 0x00f97}, // Tibetan Subjoined Sign L..Tibetan Subjoined Letter
|
||||||
{0x103d, 0x103e}, // Myanmar Consonant Sign M..Myanmar Consonant Sign M
|
{0x00f99, 0x00fbc}, // Tibetan Subjoined Letter..Tibetan Subjoined Letter
|
||||||
{0x1058, 0x1059}, // Myanmar Vowel Sign Vocal..Myanmar Vowel Sign Vocal
|
{0x00fc6, 0x00fc6}, // Tibetan Symbol Padma Gda..Tibetan Symbol Padma Gda
|
||||||
{0x105e, 0x1060}, // Myanmar Consonant Sign M..Myanmar Consonant Sign M
|
{0x0102d, 0x01030}, // Myanmar Vowel Sign I ..Myanmar Vowel Sign Uu
|
||||||
{0x1071, 0x1074}, // Myanmar Vowel Sign Geba ..Myanmar Vowel Sign Kayah
|
{0x01032, 0x01037}, // Myanmar Vowel Sign Ai ..Myanmar Sign Dot Below
|
||||||
{0x1082, 0x1082}, // Myanmar Consonant Sign S..Myanmar Consonant Sign S
|
{0x01039, 0x0103a}, // Myanmar Sign Virama ..Myanmar Sign Asat
|
||||||
{0x1085, 0x1086}, // Myanmar Vowel Sign Shan ..Myanmar Vowel Sign Shan
|
{0x0103d, 0x0103e}, // Myanmar Consonant Sign M..Myanmar Consonant Sign M
|
||||||
{0x108d, 0x108d}, // Myanmar Sign Shan Counci..Myanmar Sign Shan Counci
|
{0x01058, 0x01059}, // Myanmar Vowel Sign Vocal..Myanmar Vowel Sign Vocal
|
||||||
{0x109d, 0x109d}, // Myanmar Vowel Sign Aiton..Myanmar Vowel Sign Aiton
|
{0x0105e, 0x01060}, // Myanmar Consonant Sign M..Myanmar Consonant Sign M
|
||||||
{0x135d, 0x135f}, // Ethiopic Combining Gemin..Ethiopic Combining Gemin
|
{0x01071, 0x01074}, // Myanmar Vowel Sign Geba ..Myanmar Vowel Sign Kayah
|
||||||
{0x1712, 0x1714}, // Tagalog Vowel Sign I ..Tagalog Sign Virama
|
{0x01082, 0x01082}, // Myanmar Consonant Sign S..Myanmar Consonant Sign S
|
||||||
{0x1732, 0x1734}, // Hanunoo Vowel Sign I ..Hanunoo Sign Pamudpod
|
{0x01085, 0x01086}, // Myanmar Vowel Sign Shan ..Myanmar Vowel Sign Shan
|
||||||
{0x1752, 0x1753}, // Buhid Vowel Sign I ..Buhid Vowel Sign U
|
{0x0108d, 0x0108d}, // Myanmar Sign Shan Counci..Myanmar Sign Shan Counci
|
||||||
{0x1772, 0x1773}, // Tagbanwa Vowel Sign I ..Tagbanwa Vowel Sign U
|
{0x0109d, 0x0109d}, // Myanmar Vowel Sign Aiton..Myanmar Vowel Sign Aiton
|
||||||
{0x17b4, 0x17b5}, // Khmer Vowel Inherent Aq ..Khmer Vowel Inherent Aa
|
{0x0135d, 0x0135f}, // Ethiopic Combining Gemin..Ethiopic Combining Gemin
|
||||||
{0x17b7, 0x17bd}, // Khmer Vowel Sign I ..Khmer Vowel Sign Ua
|
{0x01712, 0x01714}, // Tagalog Vowel Sign I ..Tagalog Sign Virama
|
||||||
{0x17c6, 0x17c6}, // Khmer Sign Nikahit ..Khmer Sign Nikahit
|
{0x01732, 0x01734}, // Hanunoo Vowel Sign I ..Hanunoo Sign Pamudpod
|
||||||
{0x17c9, 0x17d3}, // Khmer Sign Muusikatoan ..Khmer Sign Bathamasat
|
{0x01752, 0x01753}, // Buhid Vowel Sign I ..Buhid Vowel Sign U
|
||||||
{0x17dd, 0x17dd}, // Khmer Sign Atthacan ..Khmer Sign Atthacan
|
{0x01772, 0x01773}, // Tagbanwa Vowel Sign I ..Tagbanwa Vowel Sign U
|
||||||
{0x180b, 0x180d}, // Mongolian Free Variation..Mongolian Free Variation
|
{0x017b4, 0x017b5}, // Khmer Vowel Inherent Aq ..Khmer Vowel Inherent Aa
|
||||||
{0x1885, 0x1886}, // Mongolian Letter Ali Gal..Mongolian Letter Ali Gal
|
{0x017b7, 0x017bd}, // Khmer Vowel Sign I ..Khmer Vowel Sign Ua
|
||||||
{0x18a9, 0x18a9}, // Mongolian Letter Ali Gal..Mongolian Letter Ali Gal
|
{0x017c6, 0x017c6}, // Khmer Sign Nikahit ..Khmer Sign Nikahit
|
||||||
{0x1920, 0x1922}, // Limbu Vowel Sign A ..Limbu Vowel Sign U
|
{0x017c9, 0x017d3}, // Khmer Sign Muusikatoan ..Khmer Sign Bathamasat
|
||||||
{0x1927, 0x1928}, // Limbu Vowel Sign E ..Limbu Vowel Sign O
|
{0x017dd, 0x017dd}, // Khmer Sign Atthacan ..Khmer Sign Atthacan
|
||||||
{0x1932, 0x1932}, // Limbu Small Letter Anusv..Limbu Small Letter Anusv
|
{0x0180b, 0x0180d}, // Mongolian Free Variation..Mongolian Free Variation
|
||||||
{0x1939, 0x193b}, // Limbu Sign Mukphreng ..Limbu Sign Sa-i
|
{0x01885, 0x01886}, // Mongolian Letter Ali Gal..Mongolian Letter Ali Gal
|
||||||
{0x1a17, 0x1a18}, // Buginese Vowel Sign I ..Buginese Vowel Sign U
|
{0x018a9, 0x018a9}, // Mongolian Letter Ali Gal..Mongolian Letter Ali Gal
|
||||||
{0x1a1b, 0x1a1b}, // Buginese Vowel Sign Ae ..Buginese Vowel Sign Ae
|
{0x01920, 0x01922}, // Limbu Vowel Sign A ..Limbu Vowel Sign U
|
||||||
{0x1a56, 0x1a56}, // Tai Tham Consonant Sign ..Tai Tham Consonant Sign
|
{0x01927, 0x01928}, // Limbu Vowel Sign E ..Limbu Vowel Sign O
|
||||||
{0x1a58, 0x1a5e}, // Tai Tham Sign Mai Kang L..Tai Tham Consonant Sign
|
{0x01932, 0x01932}, // Limbu Small Letter Anusv..Limbu Small Letter Anusv
|
||||||
{0x1a60, 0x1a60}, // Tai Tham Sign Sakot ..Tai Tham Sign Sakot
|
{0x01939, 0x0193b}, // Limbu Sign Mukphreng ..Limbu Sign Sa-i
|
||||||
{0x1a62, 0x1a62}, // Tai Tham Vowel Sign Mai ..Tai Tham Vowel Sign Mai
|
{0x01a17, 0x01a18}, // Buginese Vowel Sign I ..Buginese Vowel Sign U
|
||||||
{0x1a65, 0x1a6c}, // Tai Tham Vowel Sign I ..Tai Tham Vowel Sign Oa B
|
{0x01a1b, 0x01a1b}, // Buginese Vowel Sign Ae ..Buginese Vowel Sign Ae
|
||||||
{0x1a73, 0x1a7c}, // Tai Tham Vowel Sign Oa A..Tai Tham Sign Khuen-lue
|
{0x01a56, 0x01a56}, // Tai Tham Consonant Sign ..Tai Tham Consonant Sign
|
||||||
{0x1a7f, 0x1a7f}, // Tai Tham Combining Crypt..Tai Tham Combining Crypt
|
{0x01a58, 0x01a5e}, // Tai Tham Sign Mai Kang L..Tai Tham Consonant Sign
|
||||||
{0x1ab0, 0x1abe}, // Combining Doubled Circum..Combining Parentheses Ov
|
{0x01a60, 0x01a60}, // Tai Tham Sign Sakot ..Tai Tham Sign Sakot
|
||||||
{0x1b00, 0x1b03}, // Balinese Sign Ulu Ricem ..Balinese Sign Surang
|
{0x01a62, 0x01a62}, // Tai Tham Vowel Sign Mai ..Tai Tham Vowel Sign Mai
|
||||||
{0x1b34, 0x1b34}, // Balinese Sign Rerekan ..Balinese Sign Rerekan
|
{0x01a65, 0x01a6c}, // Tai Tham Vowel Sign I ..Tai Tham Vowel Sign Oa B
|
||||||
{0x1b36, 0x1b3a}, // Balinese Vowel Sign Ulu ..Balinese Vowel Sign Ra R
|
{0x01a73, 0x01a7c}, // Tai Tham Vowel Sign Oa A..Tai Tham Sign Khuen-lue
|
||||||
{0x1b3c, 0x1b3c}, // Balinese Vowel Sign La L..Balinese Vowel Sign La L
|
{0x01a7f, 0x01a7f}, // Tai Tham Combining Crypt..Tai Tham Combining Crypt
|
||||||
{0x1b42, 0x1b42}, // Balinese Vowel Sign Pepe..Balinese Vowel Sign Pepe
|
{0x01ab0, 0x01ac0}, // Combining Doubled Circum..(nil)
|
||||||
{0x1b6b, 0x1b73}, // Balinese Musical Symbol ..Balinese Musical Symbol
|
{0x01b00, 0x01b03}, // Balinese Sign Ulu Ricem ..Balinese Sign Surang
|
||||||
{0x1b80, 0x1b81}, // Sundanese Sign Panyecek ..Sundanese Sign Panglayar
|
{0x01b34, 0x01b34}, // Balinese Sign Rerekan ..Balinese Sign Rerekan
|
||||||
{0x1ba2, 0x1ba5}, // Sundanese Consonant Sign..Sundanese Vowel Sign Pan
|
{0x01b36, 0x01b3a}, // Balinese Vowel Sign Ulu ..Balinese Vowel Sign Ra R
|
||||||
{0x1ba8, 0x1ba9}, // Sundanese Vowel Sign Pam..Sundanese Vowel Sign Pan
|
{0x01b3c, 0x01b3c}, // Balinese Vowel Sign La L..Balinese Vowel Sign La L
|
||||||
{0x1bab, 0x1bad}, // Sundanese Sign Virama ..Sundanese Consonant Sign
|
{0x01b42, 0x01b42}, // Balinese Vowel Sign Pepe..Balinese Vowel Sign Pepe
|
||||||
{0x1be6, 0x1be6}, // Batak Sign Tompi ..Batak Sign Tompi
|
{0x01b6b, 0x01b73}, // Balinese Musical Symbol ..Balinese Musical Symbol
|
||||||
{0x1be8, 0x1be9}, // Batak Vowel Sign Pakpak ..Batak Vowel Sign Ee
|
{0x01b80, 0x01b81}, // Sundanese Sign Panyecek ..Sundanese Sign Panglayar
|
||||||
{0x1bed, 0x1bed}, // Batak Vowel Sign Karo O ..Batak Vowel Sign Karo O
|
{0x01ba2, 0x01ba5}, // Sundanese Consonant Sign..Sundanese Vowel Sign Pan
|
||||||
{0x1bef, 0x1bf1}, // Batak Vowel Sign U F|| S..Batak Consonant Sign H
|
{0x01ba8, 0x01ba9}, // Sundanese Vowel Sign Pam..Sundanese Vowel Sign Pan
|
||||||
{0x1c2c, 0x1c33}, // Lepcha Vowel Sign E ..Lepcha Consonant Sign T
|
{0x01bab, 0x01bad}, // Sundanese Sign Virama ..Sundanese Consonant Sign
|
||||||
{0x1c36, 0x1c37}, // Lepcha Sign Ran ..Lepcha Sign Nukta
|
{0x01be6, 0x01be6}, // Batak Sign Tompi ..Batak Sign Tompi
|
||||||
{0x1cd0, 0x1cd2}, // Vedic Tone Karshana ..Vedic Tone Prenkha
|
{0x01be8, 0x01be9}, // Batak Vowel Sign Pakpak ..Batak Vowel Sign Ee
|
||||||
{0x1cd4, 0x1ce0}, // Vedic Sign Yajurvedic Mi..Vedic Tone Rigvedic Kash
|
{0x01bed, 0x01bed}, // Batak Vowel Sign Karo O ..Batak Vowel Sign Karo O
|
||||||
{0x1ce2, 0x1ce8}, // Vedic Sign Visarga Svari..Vedic Sign Visarga Anuda
|
{0x01bef, 0x01bf1}, // Batak Vowel Sign U For S..Batak Consonant Sign H
|
||||||
{0x1ced, 0x1ced}, // Vedic Sign Tiryak ..Vedic Sign Tiryak
|
{0x01c2c, 0x01c33}, // Lepcha Vowel Sign E ..Lepcha Consonant Sign T
|
||||||
{0x1cf4, 0x1cf4}, // Vedic Tone Candra Above ..Vedic Tone Candra Above
|
{0x01c36, 0x01c37}, // Lepcha Sign Ran ..Lepcha Sign Nukta
|
||||||
{0x1cf8, 0x1cf9}, // Vedic Tone Ring Above ..Vedic Tone Double Ring A
|
{0x01cd0, 0x01cd2}, // Vedic Tone Karshana ..Vedic Tone Prenkha
|
||||||
{0x1dc0, 0x1df5}, // Combining Dotted Grave A..Combining Up Tack Above
|
{0x01cd4, 0x01ce0}, // Vedic Sign Yajurvedic Mi..Vedic Tone Rigvedic Kash
|
||||||
{0x1dfb, 0x1dff}, // (nil) ..Combining Right Arrowhea
|
{0x01ce2, 0x01ce8}, // Vedic Sign Visarga Svari..Vedic Sign Visarga Anuda
|
||||||
{0x20d0, 0x20f0}, // Combining Left Harpoon A..Combining Asterisk Above
|
{0x01ced, 0x01ced}, // Vedic Sign Tiryak ..Vedic Sign Tiryak
|
||||||
{0x2cef, 0x2cf1}, // Coptic Combining Ni Abov..Coptic Combining Spiritu
|
{0x01cf4, 0x01cf4}, // Vedic Tone Candra Above ..Vedic Tone Candra Above
|
||||||
{0x2d7f, 0x2d7f}, // Tifinagh Consonant Joine..Tifinagh Consonant Joine
|
{0x01cf8, 0x01cf9}, // Vedic Tone Ring Above ..Vedic Tone Double Ring A
|
||||||
{0x2de0, 0x2dff}, // Combining Cyrillic Lette..Combining Cyrillic Lette
|
{0x01dc0, 0x01df9}, // Combining Dotted Grave A..Combining Wide Inverted
|
||||||
{0x302a, 0x302d}, // Ideographic Level Tone M..Ideographic Entering Ton
|
{0x01dfb, 0x01dff}, // Combining Deletion Mark ..Combining Right Arrowhea
|
||||||
{0x3099, 0x309a}, // Combining Katakana-hirag..Combining Katakana-hirag
|
{0x020d0, 0x020f0}, // Combining Left Harpoon A..Combining Asterisk Above
|
||||||
{0xa66f, 0xa672}, // Combining Cyrillic Vzmet..Combining Cyrillic Thous
|
{0x02cef, 0x02cf1}, // Coptic Combining Ni Abov..Coptic Combining Spiritu
|
||||||
{0xa674, 0xa67d}, // Combining Cyrillic Lette..Combining Cyrillic Payer
|
{0x02d7f, 0x02d7f}, // Tifinagh Consonant Joine..Tifinagh Consonant Joine
|
||||||
{0xa69e, 0xa69f}, // Combining Cyrillic Lette..Combining Cyrillic Lette
|
{0x02de0, 0x02dff}, // Combining Cyrillic Lette..Combining Cyrillic Lette
|
||||||
{0xa6f0, 0xa6f1}, // Bamum Combining Mark Koq..Bamum Combining Mark Tuk
|
{0x0302a, 0x0302d}, // Ideographic Level Tone M..Ideographic Entering Ton
|
||||||
{0xa802, 0xa802}, // Syloti Nagri Sign Dvisva..Syloti Nagri Sign Dvisva
|
{0x03099, 0x0309a}, // Combining Katakana-hirag..Combining Katakana-hirag
|
||||||
{0xa806, 0xa806}, // Syloti Nagri Sign Hasant..Syloti Nagri Sign Hasant
|
{0x0a66f, 0x0a672}, // Combining Cyrillic Vzmet..Combining Cyrillic Thous
|
||||||
{0xa80b, 0xa80b}, // Syloti Nagri Sign Anusva..Syloti Nagri Sign Anusva
|
{0x0a674, 0x0a67d}, // Combining Cyrillic Lette..Combining Cyrillic Payer
|
||||||
{0xa825, 0xa826}, // Syloti Nagri Vowel Sign ..Syloti Nagri Vowel Sign
|
{0x0a69e, 0x0a69f}, // Combining Cyrillic Lette..Combining Cyrillic Lette
|
||||||
{0xa8c4, 0xa8c5}, // Saurashtra Sign Virama ..
|
{0x0a6f0, 0x0a6f1}, // Bamum Combining Mark Koq..Bamum Combining Mark Tuk
|
||||||
{0xa8e0, 0xa8f1}, // Combining Devanagari Dig..Combining Devanagari Sig
|
{0x0a802, 0x0a802}, // Syloti Nagri Sign Dvisva..Syloti Nagri Sign Dvisva
|
||||||
{0xa926, 0xa92d}, // Kayah Li Vowel Ue ..Kayah Li Tone Calya Plop
|
{0x0a806, 0x0a806}, // Syloti Nagri Sign Hasant..Syloti Nagri Sign Hasant
|
||||||
{0xa947, 0xa951}, // Rejang Vowel Sign I ..Rejang Consonant Sign R
|
{0x0a80b, 0x0a80b}, // Syloti Nagri Sign Anusva..Syloti Nagri Sign Anusva
|
||||||
{0xa980, 0xa982}, // Javanese Sign Panyangga ..Javanese Sign Layar
|
{0x0a825, 0x0a826}, // Syloti Nagri Vowel Sign ..Syloti Nagri Vowel Sign
|
||||||
{0xa9b3, 0xa9b3}, // Javanese Sign Cecak Telu..Javanese Sign Cecak Telu
|
{0x0a82c, 0x0a82c}, // (nil) ..(nil)
|
||||||
{0xa9b6, 0xa9b9}, // Javanese Vowel Sign Wulu..Javanese Vowel Sign Suku
|
{0x0a8c4, 0x0a8c5}, // Saurashtra Sign Virama ..Saurashtra Sign Candrabi
|
||||||
{0xa9bc, 0xa9bc}, // Javanese Vowel Sign Pepe..Javanese Vowel Sign Pepe
|
{0x0a8e0, 0x0a8f1}, // Combining Devanagari Dig..Combining Devanagari Sig
|
||||||
{0xa9e5, 0xa9e5}, // Myanmar Sign Shan Saw ..Myanmar Sign Shan Saw
|
{0x0a8ff, 0x0a8ff}, // Devanagari Vowel Sign Ay..Devanagari Vowel Sign Ay
|
||||||
{0xaa29, 0xaa2e}, // Cham Vowel Sign Aa ..Cham Vowel Sign Oe
|
{0x0a926, 0x0a92d}, // Kayah Li Vowel Ue ..Kayah Li Tone Calya Plop
|
||||||
{0xaa31, 0xaa32}, // Cham Vowel Sign Au ..Cham Vowel Sign Ue
|
{0x0a947, 0x0a951}, // Rejang Vowel Sign I ..Rejang Consonant Sign R
|
||||||
{0xaa35, 0xaa36}, // Cham Consonant Sign La ..Cham Consonant Sign Wa
|
{0x0a980, 0x0a982}, // Javanese Sign Panyangga ..Javanese Sign Layar
|
||||||
{0xaa43, 0xaa43}, // Cham Consonant Sign Fina..Cham Consonant Sign Fina
|
{0x0a9b3, 0x0a9b3}, // Javanese Sign Cecak Telu..Javanese Sign Cecak Telu
|
||||||
{0xaa4c, 0xaa4c}, // Cham Consonant Sign Fina..Cham Consonant Sign Fina
|
{0x0a9b6, 0x0a9b9}, // Javanese Vowel Sign Wulu..Javanese Vowel Sign Suku
|
||||||
{0xaa7c, 0xaa7c}, // Myanmar Sign Tai Laing T..Myanmar Sign Tai Laing T
|
{0x0a9bc, 0x0a9bd}, // Javanese Vowel Sign Pepe..Javanese Consonant Sign
|
||||||
{0xaab0, 0xaab0}, // Tai Viet Mai Kang ..Tai Viet Mai Kang
|
{0x0a9e5, 0x0a9e5}, // Myanmar Sign Shan Saw ..Myanmar Sign Shan Saw
|
||||||
{0xaab2, 0xaab4}, // Tai Viet Vowel I ..Tai Viet Vowel U
|
{0x0aa29, 0x0aa2e}, // Cham Vowel Sign Aa ..Cham Vowel Sign Oe
|
||||||
{0xaab7, 0xaab8}, // Tai Viet Mai Khit ..Tai Viet Vowel Ia
|
{0x0aa31, 0x0aa32}, // Cham Vowel Sign Au ..Cham Vowel Sign Ue
|
||||||
{0xaabe, 0xaabf}, // Tai Viet Vowel Am ..Tai Viet Tone Mai Ek
|
{0x0aa35, 0x0aa36}, // Cham Consonant Sign La ..Cham Consonant Sign Wa
|
||||||
{0xaac1, 0xaac1}, // Tai Viet Tone Mai Tho ..Tai Viet Tone Mai Tho
|
{0x0aa43, 0x0aa43}, // Cham Consonant Sign Fina..Cham Consonant Sign Fina
|
||||||
{0xaaec, 0xaaed}, // Meetei Mayek Vowel Sign ..Meetei Mayek Vowel Sign
|
{0x0aa4c, 0x0aa4c}, // Cham Consonant Sign Fina..Cham Consonant Sign Fina
|
||||||
{0xaaf6, 0xaaf6}, // Meetei Mayek Virama ..Meetei Mayek Virama
|
{0x0aa7c, 0x0aa7c}, // Myanmar Sign Tai Laing T..Myanmar Sign Tai Laing T
|
||||||
{0xabe5, 0xabe5}, // Meetei Mayek Vowel Sign ..Meetei Mayek Vowel Sign
|
{0x0aab0, 0x0aab0}, // Tai Viet Mai Kang ..Tai Viet Mai Kang
|
||||||
{0xabe8, 0xabe8}, // Meetei Mayek Vowel Sign ..Meetei Mayek Vowel Sign
|
{0x0aab2, 0x0aab4}, // Tai Viet Vowel I ..Tai Viet Vowel U
|
||||||
{0xabed, 0xabed}, // Meetei Mayek Apun Iyek ..Meetei Mayek Apun Iyek
|
{0x0aab7, 0x0aab8}, // Tai Viet Mai Khit ..Tai Viet Vowel Ia
|
||||||
{0xfb1e, 0xfb1e}, // Hebrew Point Judeo-spani..Hebrew Point Judeo-spani
|
{0x0aabe, 0x0aabf}, // Tai Viet Vowel Am ..Tai Viet Tone Mai Ek
|
||||||
{0xfe00, 0xfe0f}, // Variation Select||-1 ..Variation Select||-16
|
{0x0aac1, 0x0aac1}, // Tai Viet Tone Mai Tho ..Tai Viet Tone Mai Tho
|
||||||
{0xfe20, 0xfe2f}, // Combining Ligature Left ..Combining Cyrillic Titlo
|
{0x0aaec, 0x0aaed}, // Meetei Mayek Vowel Sign ..Meetei Mayek Vowel Sign
|
||||||
|
{0x0aaf6, 0x0aaf6}, // Meetei Mayek Virama ..Meetei Mayek Virama
|
||||||
|
{0x0abe5, 0x0abe5}, // Meetei Mayek Vowel Sign ..Meetei Mayek Vowel Sign
|
||||||
|
{0x0abe8, 0x0abe8}, // Meetei Mayek Vowel Sign ..Meetei Mayek Vowel Sign
|
||||||
|
{0x0abed, 0x0abed}, // Meetei Mayek Apun Iyek ..Meetei Mayek Apun Iyek
|
||||||
|
{0x0fb1e, 0x0fb1e}, // Hebrew Point Judeo-spani..Hebrew Point Judeo-spani
|
||||||
|
{0x0fe00, 0x0fe0f}, // Variation Selector-1 ..Variation Selector-16
|
||||||
|
{0x0fe20, 0x0fe2f}, // Combining Ligature Left ..Combining Cyrillic Titlo
|
||||||
{0x101fd, 0x101fd}, // Phaistos Disc Sign Combi..Phaistos Disc Sign Combi
|
{0x101fd, 0x101fd}, // Phaistos Disc Sign Combi..Phaistos Disc Sign Combi
|
||||||
{0x102e0, 0x102e0}, // Coptic Epact Thousands M..Coptic Epact Thousands M
|
{0x102e0, 0x102e0}, // Coptic Epact Thousands M..Coptic Epact Thousands M
|
||||||
{0x10376, 0x1037a}, // Combining Old Permic Let..Combining Old Permic Let
|
{0x10376, 0x1037a}, // Combining Old Permic Let..Combining Old Permic Let
|
||||||
@@ -219,6 +232,9 @@ public final class WcWidth {
|
|||||||
{0x10a38, 0x10a3a}, // Kharoshthi Sign Bar Abov..Kharoshthi Sign Dot Belo
|
{0x10a38, 0x10a3a}, // Kharoshthi Sign Bar Abov..Kharoshthi Sign Dot Belo
|
||||||
{0x10a3f, 0x10a3f}, // Kharoshthi Virama ..Kharoshthi Virama
|
{0x10a3f, 0x10a3f}, // Kharoshthi Virama ..Kharoshthi Virama
|
||||||
{0x10ae5, 0x10ae6}, // Manichaean Abbreviation ..Manichaean Abbreviation
|
{0x10ae5, 0x10ae6}, // Manichaean Abbreviation ..Manichaean Abbreviation
|
||||||
|
{0x10d24, 0x10d27}, // Hanifi Rohingya Sign Har..Hanifi Rohingya Sign Tas
|
||||||
|
{0x10eab, 0x10eac}, // (nil) ..(nil)
|
||||||
|
{0x10f46, 0x10f50}, // Sogdian Combining Dot Be..Sogdian Combining Stroke
|
||||||
{0x11001, 0x11001}, // Brahmi Sign Anusvara ..Brahmi Sign Anusvara
|
{0x11001, 0x11001}, // Brahmi Sign Anusvara ..Brahmi Sign Anusvara
|
||||||
{0x11038, 0x11046}, // Brahmi Vowel Sign Aa ..Brahmi Virama
|
{0x11038, 0x11046}, // Brahmi Vowel Sign Aa ..Brahmi Virama
|
||||||
{0x1107f, 0x11081}, // Brahmi Number Joiner ..Kaithi Sign Anusvara
|
{0x1107f, 0x11081}, // Brahmi Number Joiner ..Kaithi Sign Anusvara
|
||||||
@@ -230,23 +246,25 @@ public final class WcWidth {
|
|||||||
{0x11173, 0x11173}, // Mahajani Sign Nukta ..Mahajani Sign Nukta
|
{0x11173, 0x11173}, // Mahajani Sign Nukta ..Mahajani Sign Nukta
|
||||||
{0x11180, 0x11181}, // Sharada Sign Candrabindu..Sharada Sign Anusvara
|
{0x11180, 0x11181}, // Sharada Sign Candrabindu..Sharada Sign Anusvara
|
||||||
{0x111b6, 0x111be}, // Sharada Vowel Sign U ..Sharada Vowel Sign O
|
{0x111b6, 0x111be}, // Sharada Vowel Sign U ..Sharada Vowel Sign O
|
||||||
{0x111ca, 0x111cc}, // Sharada Sign Nukta ..Sharada Extra Sh||t Vowe
|
{0x111c9, 0x111cc}, // Sharada Sandhi Mark ..Sharada Extra Short Vowe
|
||||||
|
{0x111cf, 0x111cf}, // (nil) ..(nil)
|
||||||
{0x1122f, 0x11231}, // Khojki Vowel Sign U ..Khojki Vowel Sign Ai
|
{0x1122f, 0x11231}, // Khojki Vowel Sign U ..Khojki Vowel Sign Ai
|
||||||
{0x11234, 0x11234}, // Khojki Sign Anusvara ..Khojki Sign Anusvara
|
{0x11234, 0x11234}, // Khojki Sign Anusvara ..Khojki Sign Anusvara
|
||||||
{0x11236, 0x11237}, // Khojki Sign Nukta ..Khojki Sign Shadda
|
{0x11236, 0x11237}, // Khojki Sign Nukta ..Khojki Sign Shadda
|
||||||
{0x1123e, 0x1123e}, // (nil) ..
|
{0x1123e, 0x1123e}, // Khojki Sign Sukun ..Khojki Sign Sukun
|
||||||
{0x112df, 0x112df}, // Khudawadi Sign Anusvara ..Khudawadi Sign Anusvara
|
{0x112df, 0x112df}, // Khudawadi Sign Anusvara ..Khudawadi Sign Anusvara
|
||||||
{0x112e3, 0x112ea}, // Khudawadi Vowel Sign U ..Khudawadi Sign Virama
|
{0x112e3, 0x112ea}, // Khudawadi Vowel Sign U ..Khudawadi Sign Virama
|
||||||
{0x11300, 0x11301}, // Grantha Sign Combining A..Grantha Sign Candrabindu
|
{0x11300, 0x11301}, // Grantha Sign Combining A..Grantha Sign Candrabindu
|
||||||
{0x1133c, 0x1133c}, // Grantha Sign Nukta ..Grantha Sign Nukta
|
{0x1133b, 0x1133c}, // Combining Bindu Below ..Grantha Sign Nukta
|
||||||
{0x11340, 0x11340}, // Grantha Vowel Sign Ii ..Grantha Vowel Sign Ii
|
{0x11340, 0x11340}, // Grantha Vowel Sign Ii ..Grantha Vowel Sign Ii
|
||||||
{0x11366, 0x1136c}, // Combining Grantha Digit ..Combining Grantha Digit
|
{0x11366, 0x1136c}, // Combining Grantha Digit ..Combining Grantha Digit
|
||||||
{0x11370, 0x11374}, // Combining Grantha Letter..Combining Grantha Letter
|
{0x11370, 0x11374}, // Combining Grantha Letter..Combining Grantha Letter
|
||||||
{0x11438, 0x1143f}, // (nil) ..
|
{0x11438, 0x1143f}, // Newa Vowel Sign U ..Newa Vowel Sign Ai
|
||||||
{0x11442, 0x11444}, // (nil) ..
|
{0x11442, 0x11444}, // Newa Sign Virama ..Newa Sign Anusvara
|
||||||
{0x11446, 0x11446}, // (nil) ..
|
{0x11446, 0x11446}, // Newa Sign Nukta ..Newa Sign Nukta
|
||||||
|
{0x1145e, 0x1145e}, // Newa Sandhi Mark ..Newa Sandhi Mark
|
||||||
{0x114b3, 0x114b8}, // Tirhuta Vowel Sign U ..Tirhuta Vowel Sign Vocal
|
{0x114b3, 0x114b8}, // Tirhuta Vowel Sign U ..Tirhuta Vowel Sign Vocal
|
||||||
{0x114ba, 0x114ba}, // Tirhuta Vowel Sign Sh||t..Tirhuta Vowel Sign Sh||t
|
{0x114ba, 0x114ba}, // Tirhuta Vowel Sign Short..Tirhuta Vowel Sign Short
|
||||||
{0x114bf, 0x114c0}, // Tirhuta Sign Candrabindu..Tirhuta Sign Anusvara
|
{0x114bf, 0x114c0}, // Tirhuta Sign Candrabindu..Tirhuta Sign Anusvara
|
||||||
{0x114c2, 0x114c3}, // Tirhuta Sign Virama ..Tirhuta Sign Nukta
|
{0x114c2, 0x114c3}, // Tirhuta Sign Virama ..Tirhuta Sign Nukta
|
||||||
{0x115b2, 0x115b5}, // Siddham Vowel Sign U ..Siddham Vowel Sign Vocal
|
{0x115b2, 0x115b5}, // Siddham Vowel Sign U ..Siddham Vowel Sign Vocal
|
||||||
@@ -263,16 +281,43 @@ public final class WcWidth {
|
|||||||
{0x1171d, 0x1171f}, // Ahom Consonant Sign Medi..Ahom Consonant Sign Medi
|
{0x1171d, 0x1171f}, // Ahom Consonant Sign Medi..Ahom Consonant Sign Medi
|
||||||
{0x11722, 0x11725}, // Ahom Vowel Sign I ..Ahom Vowel Sign Uu
|
{0x11722, 0x11725}, // Ahom Vowel Sign I ..Ahom Vowel Sign Uu
|
||||||
{0x11727, 0x1172b}, // Ahom Vowel Sign Aw ..Ahom Sign Killer
|
{0x11727, 0x1172b}, // Ahom Vowel Sign Aw ..Ahom Sign Killer
|
||||||
{0x11c30, 0x11c36}, // (nil) ..
|
{0x1182f, 0x11837}, // Dogra Vowel Sign U ..Dogra Sign Anusvara
|
||||||
{0x11c38, 0x11c3d}, // (nil) ..
|
{0x11839, 0x1183a}, // Dogra Sign Virama ..Dogra Sign Nukta
|
||||||
{0x11c3f, 0x11c3f}, // (nil) ..
|
{0x1193b, 0x1193c}, // (nil) ..(nil)
|
||||||
{0x11c92, 0x11ca7}, // (nil) ..
|
{0x1193e, 0x1193e}, // (nil) ..(nil)
|
||||||
{0x11caa, 0x11cb0}, // (nil) ..
|
{0x11943, 0x11943}, // (nil) ..(nil)
|
||||||
{0x11cb2, 0x11cb3}, // (nil) ..
|
{0x119d4, 0x119d7}, // Nandinagari Vowel Sign U..Nandinagari Vowel Sign V
|
||||||
{0x11cb5, 0x11cb6}, // (nil) ..
|
{0x119da, 0x119db}, // Nandinagari Vowel Sign E..Nandinagari Vowel Sign A
|
||||||
|
{0x119e0, 0x119e0}, // Nandinagari Sign Virama ..Nandinagari Sign Virama
|
||||||
|
{0x11a01, 0x11a0a}, // Zanabazar Square Vowel S..Zanabazar Square Vowel L
|
||||||
|
{0x11a33, 0x11a38}, // Zanabazar Square Final C..Zanabazar Square Sign An
|
||||||
|
{0x11a3b, 0x11a3e}, // Zanabazar Square Cluster..Zanabazar Square Cluster
|
||||||
|
{0x11a47, 0x11a47}, // Zanabazar Square Subjoin..Zanabazar Square Subjoin
|
||||||
|
{0x11a51, 0x11a56}, // Soyombo Vowel Sign I ..Soyombo Vowel Sign Oe
|
||||||
|
{0x11a59, 0x11a5b}, // Soyombo Vowel Sign Vocal..Soyombo Vowel Length Mar
|
||||||
|
{0x11a8a, 0x11a96}, // Soyombo Final Consonant ..Soyombo Sign Anusvara
|
||||||
|
{0x11a98, 0x11a99}, // Soyombo Gemination Mark ..Soyombo Subjoiner
|
||||||
|
{0x11c30, 0x11c36}, // Bhaiksuki Vowel Sign I ..Bhaiksuki Vowel Sign Voc
|
||||||
|
{0x11c38, 0x11c3d}, // Bhaiksuki Vowel Sign E ..Bhaiksuki Sign Anusvara
|
||||||
|
{0x11c3f, 0x11c3f}, // Bhaiksuki Sign Virama ..Bhaiksuki Sign Virama
|
||||||
|
{0x11c92, 0x11ca7}, // Marchen Subjoined Letter..Marchen Subjoined Letter
|
||||||
|
{0x11caa, 0x11cb0}, // Marchen Subjoined Letter..Marchen Vowel Sign Aa
|
||||||
|
{0x11cb2, 0x11cb3}, // Marchen Vowel Sign U ..Marchen Vowel Sign E
|
||||||
|
{0x11cb5, 0x11cb6}, // Marchen Sign Anusvara ..Marchen Sign Candrabindu
|
||||||
|
{0x11d31, 0x11d36}, // Masaram Gondi Vowel Sign..Masaram Gondi Vowel Sign
|
||||||
|
{0x11d3a, 0x11d3a}, // Masaram Gondi Vowel Sign..Masaram Gondi Vowel Sign
|
||||||
|
{0x11d3c, 0x11d3d}, // Masaram Gondi Vowel Sign..Masaram Gondi Vowel Sign
|
||||||
|
{0x11d3f, 0x11d45}, // Masaram Gondi Vowel Sign..Masaram Gondi Virama
|
||||||
|
{0x11d47, 0x11d47}, // Masaram Gondi Ra-kara ..Masaram Gondi Ra-kara
|
||||||
|
{0x11d90, 0x11d91}, // Gunjala Gondi Vowel Sign..Gunjala Gondi Vowel Sign
|
||||||
|
{0x11d95, 0x11d95}, // Gunjala Gondi Sign Anusv..Gunjala Gondi Sign Anusv
|
||||||
|
{0x11d97, 0x11d97}, // Gunjala Gondi Virama ..Gunjala Gondi Virama
|
||||||
|
{0x11ef3, 0x11ef4}, // Makasar Vowel Sign I ..Makasar Vowel Sign U
|
||||||
{0x16af0, 0x16af4}, // Bassa Vah Combining High..Bassa Vah Combining High
|
{0x16af0, 0x16af4}, // Bassa Vah Combining High..Bassa Vah Combining High
|
||||||
{0x16b30, 0x16b36}, // Pahawh Hmong Mark Cim Tu..Pahawh Hmong Mark Cim Ta
|
{0x16b30, 0x16b36}, // Pahawh Hmong Mark Cim Tu..Pahawh Hmong Mark Cim Ta
|
||||||
|
{0x16f4f, 0x16f4f}, // Miao Sign Consonant Modi..Miao Sign Consonant Modi
|
||||||
{0x16f8f, 0x16f92}, // Miao Tone Right ..Miao Tone Below
|
{0x16f8f, 0x16f92}, // Miao Tone Right ..Miao Tone Below
|
||||||
|
{0x16fe4, 0x16fe4}, // (nil) ..(nil)
|
||||||
{0x1bc9d, 0x1bc9e}, // Duployan Thick Letter Se..Duployan Double Mark
|
{0x1bc9d, 0x1bc9e}, // Duployan Thick Letter Se..Duployan Double Mark
|
||||||
{0x1d167, 0x1d169}, // Musical Symbol Combining..Musical Symbol Combining
|
{0x1d167, 0x1d169}, // Musical Symbol Combining..Musical Symbol Combining
|
||||||
{0x1d17b, 0x1d182}, // Musical Symbol Combining..Musical Symbol Combining
|
{0x1d17b, 0x1d182}, // Musical Symbol Combining..Musical Symbol Combining
|
||||||
@@ -285,97 +330,103 @@ public final class WcWidth {
|
|||||||
{0x1da84, 0x1da84}, // Signwriting Location Hea..Signwriting Location Hea
|
{0x1da84, 0x1da84}, // Signwriting Location Hea..Signwriting Location Hea
|
||||||
{0x1da9b, 0x1da9f}, // Signwriting Fill Modifie..Signwriting Fill Modifie
|
{0x1da9b, 0x1da9f}, // Signwriting Fill Modifie..Signwriting Fill Modifie
|
||||||
{0x1daa1, 0x1daaf}, // Signwriting Rotation Mod..Signwriting Rotation Mod
|
{0x1daa1, 0x1daaf}, // Signwriting Rotation Mod..Signwriting Rotation Mod
|
||||||
{0x1e000, 0x1e006}, // (nil) ..
|
{0x1e000, 0x1e006}, // Combining Glagolitic Let..Combining Glagolitic Let
|
||||||
{0x1e008, 0x1e018}, // (nil) ..
|
{0x1e008, 0x1e018}, // Combining Glagolitic Let..Combining Glagolitic Let
|
||||||
{0x1e01b, 0x1e021}, // (nil) ..
|
{0x1e01b, 0x1e021}, // Combining Glagolitic Let..Combining Glagolitic Let
|
||||||
{0x1e023, 0x1e024}, // (nil) ..
|
{0x1e023, 0x1e024}, // Combining Glagolitic Let..Combining Glagolitic Let
|
||||||
{0x1e026, 0x1e02a}, // (nil) ..
|
{0x1e026, 0x1e02a}, // Combining Glagolitic Let..Combining Glagolitic Let
|
||||||
|
{0x1e130, 0x1e136}, // Nyiakeng Puachue Hmong T..Nyiakeng Puachue Hmong T
|
||||||
|
{0x1e2ec, 0x1e2ef}, // Wancho Tone Tup ..Wancho Tone Koini
|
||||||
{0x1e8d0, 0x1e8d6}, // Mende Kikakui Combining ..Mende Kikakui Combining
|
{0x1e8d0, 0x1e8d6}, // Mende Kikakui Combining ..Mende Kikakui Combining
|
||||||
{0x1e944, 0x1e94a}, // (nil) ..
|
{0x1e944, 0x1e94a}, // Adlam Alif Lengthener ..Adlam Nukta
|
||||||
{0xe0100, 0xe01ef}, // Variation Select||-17 ..Variation Select||-256
|
{0xe0100, 0xe01ef}, // Variation Selector-17 ..Variation Selector-256
|
||||||
};
|
};
|
||||||
|
|
||||||
// https://github.com/jquast/wcwidth/blob/master/wcwidth/table_wide.py
|
// https://github.com/jquast/wcwidth/blob/master/wcwidth/table_wide.py
|
||||||
// at commit 0d7de112202cc8b2ebe9232ff4a5c954f19d561a (2016-07-02):
|
// at commit b29897e5a1b403a0e36f7fc991614981cbc42475 (2020-07-14):
|
||||||
private static final int[][] WIDE_EASTASIAN = {
|
private static final int[][] WIDE_EASTASIAN = {
|
||||||
{0x1100, 0x115f}, // Hangul Choseong Kiyeok ..Hangul Choseong Filler
|
{0x01100, 0x0115f}, // Hangul Choseong Kiyeok ..Hangul Choseong Filler
|
||||||
{0x231a, 0x231b}, // Watch ..Hourglass
|
{0x0231a, 0x0231b}, // Watch ..Hourglass
|
||||||
{0x2329, 0x232a}, // Left-pointing Angle Brac..Right-pointing Angle Bra
|
{0x02329, 0x0232a}, // Left-pointing Angle Brac..Right-pointing Angle Bra
|
||||||
{0x23e9, 0x23ec}, // Black Right-pointing Dou..Black Down-pointing Doub
|
{0x023e9, 0x023ec}, // Black Right-pointing Dou..Black Down-pointing Doub
|
||||||
{0x23f0, 0x23f0}, // Alarm Clock ..Alarm Clock
|
{0x023f0, 0x023f0}, // Alarm Clock ..Alarm Clock
|
||||||
{0x23f3, 0x23f3}, // Hourglass With Flowing S..Hourglass With Flowing S
|
{0x023f3, 0x023f3}, // Hourglass With Flowing S..Hourglass With Flowing S
|
||||||
{0x25fd, 0x25fe}, // White Medium Small Squar..Black Medium Small Squar
|
{0x025fd, 0x025fe}, // White Medium Small Squar..Black Medium Small Squar
|
||||||
{0x2614, 0x2615}, // Umbrella With Rain Drops..Hot Beverage
|
{0x02614, 0x02615}, // Umbrella With Rain Drops..Hot Beverage
|
||||||
{0x2648, 0x2653}, // Aries ..Pisces
|
{0x02648, 0x02653}, // Aries ..Pisces
|
||||||
{0x267f, 0x267f}, // Wheelchair Symbol ..Wheelchair Symbol
|
{0x0267f, 0x0267f}, // Wheelchair Symbol ..Wheelchair Symbol
|
||||||
{0x2693, 0x2693}, // Anch|| ..Anch||
|
{0x02693, 0x02693}, // Anchor ..Anchor
|
||||||
{0x26a1, 0x26a1}, // High Voltage Sign ..High Voltage Sign
|
{0x026a1, 0x026a1}, // High Voltage Sign ..High Voltage Sign
|
||||||
{0x26aa, 0x26ab}, // Medium White Circle ..Medium Black Circle
|
{0x026aa, 0x026ab}, // Medium White Circle ..Medium Black Circle
|
||||||
{0x26bd, 0x26be}, // Soccer Ball ..Baseball
|
{0x026bd, 0x026be}, // Soccer Ball ..Baseball
|
||||||
{0x26c4, 0x26c5}, // Snowman Without Snow ..Sun Behind Cloud
|
{0x026c4, 0x026c5}, // Snowman Without Snow ..Sun Behind Cloud
|
||||||
{0x26ce, 0x26ce}, // Ophiuchus ..Ophiuchus
|
{0x026ce, 0x026ce}, // Ophiuchus ..Ophiuchus
|
||||||
{0x26d4, 0x26d4}, // No Entry ..No Entry
|
{0x026d4, 0x026d4}, // No Entry ..No Entry
|
||||||
{0x26ea, 0x26ea}, // Church ..Church
|
{0x026ea, 0x026ea}, // Church ..Church
|
||||||
{0x26f2, 0x26f3}, // Fountain ..Flag In Hole
|
{0x026f2, 0x026f3}, // Fountain ..Flag In Hole
|
||||||
{0x26f5, 0x26f5}, // Sailboat ..Sailboat
|
{0x026f5, 0x026f5}, // Sailboat ..Sailboat
|
||||||
{0x26fa, 0x26fa}, // Tent ..Tent
|
{0x026fa, 0x026fa}, // Tent ..Tent
|
||||||
{0x26fd, 0x26fd}, // Fuel Pump ..Fuel Pump
|
{0x026fd, 0x026fd}, // Fuel Pump ..Fuel Pump
|
||||||
{0x2705, 0x2705}, // White Heavy Check Mark ..White Heavy Check Mark
|
{0x02705, 0x02705}, // White Heavy Check Mark ..White Heavy Check Mark
|
||||||
{0x270a, 0x270b}, // Raised Fist ..Raised Hand
|
{0x0270a, 0x0270b}, // Raised Fist ..Raised Hand
|
||||||
{0x2728, 0x2728}, // Sparkles ..Sparkles
|
{0x02728, 0x02728}, // Sparkles ..Sparkles
|
||||||
{0x274c, 0x274c}, // Cross Mark ..Cross Mark
|
{0x0274c, 0x0274c}, // Cross Mark ..Cross Mark
|
||||||
{0x274e, 0x274e}, // Negative Squared Cross M..Negative Squared Cross M
|
{0x0274e, 0x0274e}, // Negative Squared Cross M..Negative Squared Cross M
|
||||||
{0x2753, 0x2755}, // Black Question Mark ||na..White Exclamation Mark O
|
{0x02753, 0x02755}, // Black Question Mark Orna..White Exclamation Mark O
|
||||||
{0x2757, 0x2757}, // Heavy Exclamation Mark S..Heavy Exclamation Mark S
|
{0x02757, 0x02757}, // Heavy Exclamation Mark S..Heavy Exclamation Mark S
|
||||||
{0x2795, 0x2797}, // Heavy Plus Sign ..Heavy Division Sign
|
{0x02795, 0x02797}, // Heavy Plus Sign ..Heavy Division Sign
|
||||||
{0x27b0, 0x27b0}, // Curly Loop ..Curly Loop
|
{0x027b0, 0x027b0}, // Curly Loop ..Curly Loop
|
||||||
{0x27bf, 0x27bf}, // Double Curly Loop ..Double Curly Loop
|
{0x027bf, 0x027bf}, // Double Curly Loop ..Double Curly Loop
|
||||||
{0x2b1b, 0x2b1c}, // Black Large Square ..White Large Square
|
{0x02b1b, 0x02b1c}, // Black Large Square ..White Large Square
|
||||||
{0x2b50, 0x2b50}, // White Medium Star ..White Medium Star
|
{0x02b50, 0x02b50}, // White Medium Star ..White Medium Star
|
||||||
{0x2b55, 0x2b55}, // Heavy Large Circle ..Heavy Large Circle
|
{0x02b55, 0x02b55}, // Heavy Large Circle ..Heavy Large Circle
|
||||||
{0x2e80, 0x2e99}, // Cjk Radical Repeat ..Cjk Radical Rap
|
{0x02e80, 0x02e99}, // Cjk Radical Repeat ..Cjk Radical Rap
|
||||||
{0x2e9b, 0x2ef3}, // Cjk Radical Choke ..Cjk Radical C-simplified
|
{0x02e9b, 0x02ef3}, // Cjk Radical Choke ..Cjk Radical C-simplified
|
||||||
{0x2f00, 0x2fd5}, // Kangxi Radical One ..Kangxi Radical Flute
|
{0x02f00, 0x02fd5}, // Kangxi Radical One ..Kangxi Radical Flute
|
||||||
{0x2ff0, 0x2ffb}, // Ideographic Description ..Ideographic Description
|
{0x02ff0, 0x02ffb}, // Ideographic Description ..Ideographic Description
|
||||||
{0x3000, 0x303e}, // Ideographic Space ..Ideographic Variation In
|
{0x03000, 0x0303e}, // Ideographic Space ..Ideographic Variation In
|
||||||
{0x3041, 0x3096}, // Hiragana Letter Small A ..Hiragana Letter Small Ke
|
{0x03041, 0x03096}, // Hiragana Letter Small A ..Hiragana Letter Small Ke
|
||||||
{0x3099, 0x30ff}, // Combining Katakana-hirag..Katakana Digraph Koto
|
{0x03099, 0x030ff}, // Combining Katakana-hirag..Katakana Digraph Koto
|
||||||
{0x3105, 0x312d}, // Bopomofo Letter B ..Bopomofo Letter Ih
|
{0x03105, 0x0312f}, // Bopomofo Letter B ..Bopomofo Letter Nn
|
||||||
{0x3131, 0x318e}, // Hangul Letter Kiyeok ..Hangul Letter Araeae
|
{0x03131, 0x0318e}, // Hangul Letter Kiyeok ..Hangul Letter Araeae
|
||||||
{0x3190, 0x31ba}, // Ideographic Annotation L..Bopomofo Letter Zy
|
{0x03190, 0x031e3}, // Ideographic Annotation L..Cjk Stroke Q
|
||||||
{0x31c0, 0x31e3}, // Cjk Stroke T ..Cjk Stroke Q
|
{0x031f0, 0x0321e}, // Katakana Letter Small Ku..Parenthesized Korean Cha
|
||||||
{0x31f0, 0x321e}, // Katakana Letter Small Ku..Parenthesized K||ean Cha
|
{0x03220, 0x03247}, // Parenthesized Ideograph ..Circled Ideograph Koto
|
||||||
{0x3220, 0x3247}, // Parenthesized Ideograph ..Circled Ideograph Koto
|
{0x03250, 0x04dbf}, // Partnership Sign ..(nil)
|
||||||
{0x3250, 0x32fe}, // Partnership Sign ..Circled Katakana Wo
|
{0x04e00, 0x0a48c}, // Cjk Unified Ideograph-4e..Yi Syllable Yyr
|
||||||
{0x3300, 0x4dbf}, // Square Apaato ..
|
{0x0a490, 0x0a4c6}, // Yi Radical Qot ..Yi Radical Ke
|
||||||
{0x4e00, 0xa48c}, // Cjk Unified Ideograph-4e..Yi Syllable Yyr
|
{0x0a960, 0x0a97c}, // Hangul Choseong Tikeut-m..Hangul Choseong Ssangyeo
|
||||||
{0xa490, 0xa4c6}, // Yi Radical Qot ..Yi Radical Ke
|
{0x0ac00, 0x0d7a3}, // Hangul Syllable Ga ..Hangul Syllable Hih
|
||||||
{0xa960, 0xa97c}, // Hangul Choseong Tikeut-m..Hangul Choseong Ssangyeo
|
{0x0f900, 0x0faff}, // Cjk Compatibility Ideogr..(nil)
|
||||||
{0xac00, 0xd7a3}, // Hangul Syllable Ga ..Hangul Syllable Hih
|
{0x0fe10, 0x0fe19}, // Presentation Form For Ve..Presentation Form For Ve
|
||||||
{0xf900, 0xfaff}, // Cjk Compatibility Ideogr..
|
{0x0fe30, 0x0fe52}, // Presentation Form For Ve..Small Full Stop
|
||||||
{0xfe10, 0xfe19}, // Presentation F||m F|| Ve..Presentation F||m F|| Ve
|
{0x0fe54, 0x0fe66}, // Small Semicolon ..Small Equals Sign
|
||||||
{0xfe30, 0xfe52}, // Presentation F||m F|| Ve..Small Full Stop
|
{0x0fe68, 0x0fe6b}, // Small Reverse Solidus ..Small Commercial At
|
||||||
{0xfe54, 0xfe66}, // Small Semicolon ..Small Equals Sign
|
{0x0ff01, 0x0ff60}, // Fullwidth Exclamation Ma..Fullwidth Right White Pa
|
||||||
{0xfe68, 0xfe6b}, // Small Reverse Solidus ..Small Commercial At
|
{0x0ffe0, 0x0ffe6}, // Fullwidth Cent Sign ..Fullwidth Won Sign
|
||||||
{0xff01, 0xff60}, // Fullwidth Exclamation Ma..Fullwidth Right White Pa
|
{0x16fe0, 0x16fe4}, // Tangut Iteration Mark ..(nil)
|
||||||
{0xffe0, 0xffe6}, // Fullwidth Cent Sign ..Fullwidth Won Sign
|
{0x16ff0, 0x16ff1}, // (nil) ..(nil)
|
||||||
{0x16fe0, 0x16fe0}, // (nil) ..
|
{0x17000, 0x187f7}, // (nil) ..(nil)
|
||||||
{0x17000, 0x187ec}, // (nil) ..
|
{0x18800, 0x18cd5}, // Tangut Component-001 ..(nil)
|
||||||
{0x18800, 0x18af2}, // (nil) ..
|
{0x18d00, 0x18d08}, // (nil) ..(nil)
|
||||||
{0x1b000, 0x1b001}, // Katakana Letter Archaic ..Hiragana Letter Archaic
|
{0x1b000, 0x1b11e}, // Katakana Letter Archaic ..Hentaigana Letter N-mu-m
|
||||||
|
{0x1b150, 0x1b152}, // Hiragana Letter Small Wi..Hiragana Letter Small Wo
|
||||||
|
{0x1b164, 0x1b167}, // Katakana Letter Small Wi..Katakana Letter Small N
|
||||||
|
{0x1b170, 0x1b2fb}, // Nushu Character-1b170 ..Nushu Character-1b2fb
|
||||||
{0x1f004, 0x1f004}, // Mahjong Tile Red Dragon ..Mahjong Tile Red Dragon
|
{0x1f004, 0x1f004}, // Mahjong Tile Red Dragon ..Mahjong Tile Red Dragon
|
||||||
{0x1f0cf, 0x1f0cf}, // Playing Card Black Joker..Playing Card Black Joker
|
{0x1f0cf, 0x1f0cf}, // Playing Card Black Joker..Playing Card Black Joker
|
||||||
{0x1f18e, 0x1f18e}, // Negative Squared Ab ..Negative Squared Ab
|
{0x1f18e, 0x1f18e}, // Negative Squared Ab ..Negative Squared Ab
|
||||||
{0x1f191, 0x1f19a}, // Squared Cl ..Squared Vs
|
{0x1f191, 0x1f19a}, // Squared Cl ..Squared Vs
|
||||||
{0x1f200, 0x1f202}, // Square Hiragana Hoka ..Squared Katakana Sa
|
{0x1f200, 0x1f202}, // Square Hiragana Hoka ..Squared Katakana Sa
|
||||||
{0x1f210, 0x1f23b}, // Squared Cjk Unified Ideo..
|
{0x1f210, 0x1f23b}, // Squared Cjk Unified Ideo..Squared Cjk Unified Ideo
|
||||||
{0x1f240, 0x1f248}, // T||toise Shell Bracketed..T||toise Shell Bracketed
|
{0x1f240, 0x1f248}, // Tortoise Shell Bracketed..Tortoise Shell Bracketed
|
||||||
{0x1f250, 0x1f251}, // Circled Ideograph Advant..Circled Ideograph Accept
|
{0x1f250, 0x1f251}, // Circled Ideograph Advant..Circled Ideograph Accept
|
||||||
|
{0x1f260, 0x1f265}, // Rounded Symbol For Fu ..Rounded Symbol For Cai
|
||||||
{0x1f300, 0x1f320}, // Cyclone ..Shooting Star
|
{0x1f300, 0x1f320}, // Cyclone ..Shooting Star
|
||||||
{0x1f32d, 0x1f335}, // Hot Dog ..Cactus
|
{0x1f32d, 0x1f335}, // Hot Dog ..Cactus
|
||||||
{0x1f337, 0x1f37c}, // Tulip ..Baby Bottle
|
{0x1f337, 0x1f37c}, // Tulip ..Baby Bottle
|
||||||
{0x1f37e, 0x1f393}, // Bottle With Popping C||k..Graduation Cap
|
{0x1f37e, 0x1f393}, // Bottle With Popping Cork..Graduation Cap
|
||||||
{0x1f3a0, 0x1f3ca}, // Carousel H||se ..Swimmer
|
{0x1f3a0, 0x1f3ca}, // Carousel Horse ..Swimmer
|
||||||
{0x1f3cf, 0x1f3d3}, // Cricket Bat And Ball ..Table Tennis Paddle And
|
{0x1f3cf, 0x1f3d3}, // Cricket Bat And Ball ..Table Tennis Paddle And
|
||||||
{0x1f3e0, 0x1f3f0}, // House Building ..European Castle
|
{0x1f3e0, 0x1f3f0}, // House Building ..European Castle
|
||||||
{0x1f3f4, 0x1f3f4}, // Waving Black Flag ..Waving Black Flag
|
{0x1f3f4, 0x1f3f4}, // Waving Black Flag ..Waving Black Flag
|
||||||
@@ -383,27 +434,33 @@ public final class WcWidth {
|
|||||||
{0x1f440, 0x1f440}, // Eyes ..Eyes
|
{0x1f440, 0x1f440}, // Eyes ..Eyes
|
||||||
{0x1f442, 0x1f4fc}, // Ear ..Videocassette
|
{0x1f442, 0x1f4fc}, // Ear ..Videocassette
|
||||||
{0x1f4ff, 0x1f53d}, // Prayer Beads ..Down-pointing Small Red
|
{0x1f4ff, 0x1f53d}, // Prayer Beads ..Down-pointing Small Red
|
||||||
{0x1f54b, 0x1f54e}, // Kaaba ..Men||ah With Nine Branch
|
{0x1f54b, 0x1f54e}, // Kaaba ..Menorah With Nine Branch
|
||||||
{0x1f550, 0x1f567}, // Clock Face One Oclock ..Clock Face Twelve-thirty
|
{0x1f550, 0x1f567}, // Clock Face One Oclock ..Clock Face Twelve-thirty
|
||||||
{0x1f57a, 0x1f57a}, // (nil) ..
|
{0x1f57a, 0x1f57a}, // Man Dancing ..Man Dancing
|
||||||
{0x1f595, 0x1f596}, // Reversed Hand With Middl..Raised Hand With Part Be
|
{0x1f595, 0x1f596}, // Reversed Hand With Middl..Raised Hand With Part Be
|
||||||
{0x1f5a4, 0x1f5a4}, // (nil) ..
|
{0x1f5a4, 0x1f5a4}, // Black Heart ..Black Heart
|
||||||
{0x1f5fb, 0x1f64f}, // Mount Fuji ..Person With Folded Hands
|
{0x1f5fb, 0x1f64f}, // Mount Fuji ..Person With Folded Hands
|
||||||
{0x1f680, 0x1f6c5}, // Rocket ..Left Luggage
|
{0x1f680, 0x1f6c5}, // Rocket ..Left Luggage
|
||||||
{0x1f6cc, 0x1f6cc}, // Sleeping Accommodation ..Sleeping Accommodation
|
{0x1f6cc, 0x1f6cc}, // Sleeping Accommodation ..Sleeping Accommodation
|
||||||
{0x1f6d0, 0x1f6d2}, // Place Of W||ship ..
|
{0x1f6d0, 0x1f6d2}, // Place Of Worship ..Shopping Trolley
|
||||||
|
{0x1f6d5, 0x1f6d7}, // Hindu Temple ..(nil)
|
||||||
{0x1f6eb, 0x1f6ec}, // Airplane Departure ..Airplane Arriving
|
{0x1f6eb, 0x1f6ec}, // Airplane Departure ..Airplane Arriving
|
||||||
{0x1f6f4, 0x1f6f6}, // (nil) ..
|
{0x1f6f4, 0x1f6fc}, // Scooter ..(nil)
|
||||||
{0x1f910, 0x1f91e}, // Zipper-mouth Face ..
|
{0x1f7e0, 0x1f7eb}, // Large Orange Circle ..Large Brown Square
|
||||||
{0x1f920, 0x1f927}, // (nil) ..
|
{0x1f90c, 0x1f93a}, // (nil) ..Fencer
|
||||||
{0x1f930, 0x1f930}, // (nil) ..
|
{0x1f93c, 0x1f945}, // Wrestlers ..Goal Net
|
||||||
{0x1f933, 0x1f93e}, // (nil) ..
|
{0x1f947, 0x1f978}, // First Place Medal ..(nil)
|
||||||
{0x1f940, 0x1f94b}, // (nil) ..
|
{0x1f97a, 0x1f9cb}, // Face With Pleading Eyes ..(nil)
|
||||||
{0x1f950, 0x1f95e}, // (nil) ..
|
{0x1f9cd, 0x1f9ff}, // Standing Person ..Nazar Amulet
|
||||||
{0x1f980, 0x1f991}, // Crab ..
|
{0x1fa70, 0x1fa74}, // Ballet Shoes ..(nil)
|
||||||
{0x1f9c0, 0x1f9c0}, // Cheese Wedge ..Cheese Wedge
|
{0x1fa78, 0x1fa7a}, // Drop Of Blood ..Stethoscope
|
||||||
{0x20000, 0x2fffd}, // Cjk Unified Ideograph-20..
|
{0x1fa80, 0x1fa86}, // Yo-yo ..(nil)
|
||||||
{0x30000, 0x3fffd}, // (nil) ..
|
{0x1fa90, 0x1faa8}, // Ringed Planet ..(nil)
|
||||||
|
{0x1fab0, 0x1fab6}, // (nil) ..(nil)
|
||||||
|
{0x1fac0, 0x1fac2}, // (nil) ..(nil)
|
||||||
|
{0x1fad0, 0x1fad6}, // (nil) ..(nil)
|
||||||
|
{0x20000, 0x2fffd}, // Cjk Unified Ideograph-20..(nil)
|
||||||
|
{0x30000, 0x3fffd}, // (nil) ..(nil)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -133,8 +133,6 @@ public class CursorAndScreenTest extends TerminalTestCase {
|
|||||||
withTerminalSized(3, 3).enterString("ABCDEFG\033[2AH").assertLinesAre("AHC", "DEF", "G ");
|
withTerminalSized(3, 3).enterString("ABCDEFG\033[2AH").assertLinesAre("AHC", "DEF", "G ");
|
||||||
// If an attempt is made to move the cursor above the top margin, the cursor stops at the top margin:
|
// If an attempt is made to move the cursor above the top margin, the cursor stops at the top margin:
|
||||||
withTerminalSized(3, 3).enterString("ABCDEFG\033[44AH").assertLinesAre("AHC", "DEF", "G ");
|
withTerminalSized(3, 3).enterString("ABCDEFG\033[44AH").assertLinesAre("AHC", "DEF", "G ");
|
||||||
// Set top margin and validate that cursor does not go above it:
|
|
||||||
withTerminalSized(3, 3).enterString("\033[2rABCDEFG\033[44AH").assertLinesAre("ABC", "DHF", "G ");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testCursorDown() {
|
public void testCursorDown() {
|
||||||
@@ -143,8 +141,6 @@ public class CursorAndScreenTest extends TerminalTestCase {
|
|||||||
withTerminalSized(3, 3).enterString("AB\033[2BC").assertLinesAre("AB ", " ", " C");
|
withTerminalSized(3, 3).enterString("AB\033[2BC").assertLinesAre("AB ", " ", " C");
|
||||||
// If an attempt is made to move the cursor below the bottom margin, the cursor stops at the bottom margin:
|
// If an attempt is made to move the cursor below the bottom margin, the cursor stops at the bottom margin:
|
||||||
withTerminalSized(3, 3).enterString("AB\033[44BC").assertLinesAre("AB ", " ", " C");
|
withTerminalSized(3, 3).enterString("AB\033[44BC").assertLinesAre("AB ", " ", " C");
|
||||||
// Set bottom margin and validate that cursor does not go above it:
|
|
||||||
withTerminalSized(3, 3).enterString("\033[1;2rAB\033[44BC").assertLinesAre("AB ", " C", " ");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testReportCursorPosition() {
|
public void testReportCursorPosition() {
|
||||||
|
|||||||
@@ -37,4 +37,12 @@ public class ScreenBufferTest extends TerminalTestCase {
|
|||||||
withTerminalSized(5, 3).enterString("ABCDE\r\nFGHIJ").assertLinesAre("ABCDE", "FGHIJ", " ");
|
withTerminalSized(5, 3).enterString("ABCDE\r\nFGHIJ").assertLinesAre("ABCDE", "FGHIJ", " ");
|
||||||
assertEquals("ABCDE\nFG", mTerminal.getSelectedText(0, 0, 1, 1));
|
assertEquals("ABCDE\nFG", mTerminal.getSelectedText(0, 0, 1, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testGetSelectedTextJoinFullLines() {
|
||||||
|
withTerminalSized(5, 3).enterString("ABCDE\r\nFG");
|
||||||
|
assertEquals("ABCDEFG", mTerminal.getScreen().getSelectedText(0, 0, 1, 1, true, true));
|
||||||
|
|
||||||
|
withTerminalSized(5, 3).enterString("ABC\r\nFG");
|
||||||
|
assertEquals("ABC\nFG", mTerminal.getScreen().getSelectedText(0, 0, 1, 1, true, true));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -107,4 +107,24 @@ public class ScrollRegionTest extends TerminalTestCase {
|
|||||||
assertLinesAre("1 ", "2 ", "3 ", "QQ", "YY");
|
assertLinesAre("1 ", "2 ", "3 ", "QQ", "YY");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** See https://github.com/termux/termux-app/issues/1340 */
|
||||||
|
public void testScrollRegionDoesNotLimitCursorMovement() {
|
||||||
|
withTerminalSized(6, 4)
|
||||||
|
.enterString("\033[4;7r\033[3;1Haaa\033[Axxx")
|
||||||
|
.assertLinesAre(
|
||||||
|
" ",
|
||||||
|
" xxx",
|
||||||
|
"aaa ",
|
||||||
|
" "
|
||||||
|
);
|
||||||
|
|
||||||
|
withTerminalSized(6, 4)
|
||||||
|
.enterString("\033[1;3r\033[3;1Haaa\033[Bxxx")
|
||||||
|
.assertLinesAre(
|
||||||
|
" ",
|
||||||
|
" ",
|
||||||
|
"aaa ",
|
||||||
|
" xxx"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,16 +17,16 @@ ext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 28
|
compileSdkVersion project.properties.compileSdkVersion.toInteger()
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation "androidx.annotation:annotation:1.0.1"
|
implementation "androidx.annotation:annotation:1.1.0"
|
||||||
api project(":terminal-emulator")
|
api project(":terminal-emulator")
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 21
|
minSdkVersion project.properties.minSdkVersion.toInteger()
|
||||||
targetSdkVersion 28
|
targetSdkVersion project.properties.targetSdkVersion.toInteger()
|
||||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
testImplementation 'junit:junit:4.12'
|
testImplementation 'junit:junit:4.13'
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: '../scripts/bintray-publish.gradle'
|
apply from: '../scripts/bintray-publish.gradle'
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ public final class TerminalRenderer {
|
|||||||
|
|
||||||
long lastRunStyle = 0;
|
long lastRunStyle = 0;
|
||||||
boolean lastRunInsideCursor = false;
|
boolean lastRunInsideCursor = false;
|
||||||
|
boolean lastRunInsideSelection = false;
|
||||||
int lastRunStartColumn = -1;
|
int lastRunStartColumn = -1;
|
||||||
int lastRunStartIndex = 0;
|
int lastRunStartIndex = 0;
|
||||||
boolean lastRunFontWidthMismatch = false;
|
boolean lastRunFontWidthMismatch = false;
|
||||||
@@ -98,7 +99,8 @@ public final class TerminalRenderer {
|
|||||||
final int charsForCodePoint = charIsHighsurrogate ? 2 : 1;
|
final int charsForCodePoint = charIsHighsurrogate ? 2 : 1;
|
||||||
final int codePoint = charIsHighsurrogate ? Character.toCodePoint(charAtIndex, line[currentCharIndex + 1]) : charAtIndex;
|
final int codePoint = charIsHighsurrogate ? Character.toCodePoint(charAtIndex, line[currentCharIndex + 1]) : charAtIndex;
|
||||||
final int codePointWcWidth = WcWidth.width(codePoint);
|
final int codePointWcWidth = WcWidth.width(codePoint);
|
||||||
final boolean insideCursor = (column >= selx1 && column <= selx2) || (cursorX == column || (codePointWcWidth == 2 && cursorX == column + 1));
|
final boolean insideCursor = (cursorX == column || (codePointWcWidth == 2 && cursorX == column + 1));
|
||||||
|
final boolean insideSelection = column >= selx1 && column <= selx2;
|
||||||
final long style = lineObject.getStyle(column);
|
final long style = lineObject.getStyle(column);
|
||||||
|
|
||||||
// Check if the measured text width for this code point is not the same as that expected by wcwidth().
|
// Check if the measured text width for this code point is not the same as that expected by wcwidth().
|
||||||
@@ -109,7 +111,7 @@ public final class TerminalRenderer {
|
|||||||
currentCharIndex, charsForCodePoint);
|
currentCharIndex, charsForCodePoint);
|
||||||
final boolean fontWidthMismatch = Math.abs(measuredCodePointWidth / mFontWidth - codePointWcWidth) > 0.01;
|
final boolean fontWidthMismatch = Math.abs(measuredCodePointWidth / mFontWidth - codePointWcWidth) > 0.01;
|
||||||
|
|
||||||
if (style != lastRunStyle || insideCursor != lastRunInsideCursor || fontWidthMismatch || lastRunFontWidthMismatch) {
|
if (style != lastRunStyle || insideCursor != lastRunInsideCursor || insideSelection != lastRunInsideSelection || fontWidthMismatch || lastRunFontWidthMismatch) {
|
||||||
if (column == 0) {
|
if (column == 0) {
|
||||||
// Skip first column as there is nothing to draw, just record the current style.
|
// Skip first column as there is nothing to draw, just record the current style.
|
||||||
} else {
|
} else {
|
||||||
@@ -118,11 +120,12 @@ public final class TerminalRenderer {
|
|||||||
int cursorColor = lastRunInsideCursor ? mEmulator.mColors.mCurrentColors[TextStyle.COLOR_INDEX_CURSOR] : 0;
|
int cursorColor = lastRunInsideCursor ? mEmulator.mColors.mCurrentColors[TextStyle.COLOR_INDEX_CURSOR] : 0;
|
||||||
drawTextRun(canvas, line, palette, heightOffset, lastRunStartColumn, columnWidthSinceLastRun,
|
drawTextRun(canvas, line, palette, heightOffset, lastRunStartColumn, columnWidthSinceLastRun,
|
||||||
lastRunStartIndex, charsSinceLastRun, measuredWidthForRun,
|
lastRunStartIndex, charsSinceLastRun, measuredWidthForRun,
|
||||||
cursorColor, cursorShape, lastRunStyle, reverseVideo);
|
cursorColor, cursorShape, lastRunStyle, reverseVideo || lastRunInsideSelection);
|
||||||
}
|
}
|
||||||
measuredWidthForRun = 0.f;
|
measuredWidthForRun = 0.f;
|
||||||
lastRunStyle = style;
|
lastRunStyle = style;
|
||||||
lastRunInsideCursor = insideCursor;
|
lastRunInsideCursor = insideCursor;
|
||||||
|
lastRunInsideSelection = insideSelection;
|
||||||
lastRunStartColumn = column;
|
lastRunStartColumn = column;
|
||||||
lastRunStartIndex = currentCharIndex;
|
lastRunStartIndex = currentCharIndex;
|
||||||
lastRunFontWidthMismatch = fontWidthMismatch;
|
lastRunFontWidthMismatch = fontWidthMismatch;
|
||||||
@@ -141,7 +144,7 @@ public final class TerminalRenderer {
|
|||||||
final int charsSinceLastRun = currentCharIndex - lastRunStartIndex;
|
final int charsSinceLastRun = currentCharIndex - lastRunStartIndex;
|
||||||
int cursorColor = lastRunInsideCursor ? mEmulator.mColors.mCurrentColors[TextStyle.COLOR_INDEX_CURSOR] : 0;
|
int cursorColor = lastRunInsideCursor ? mEmulator.mColors.mCurrentColors[TextStyle.COLOR_INDEX_CURSOR] : 0;
|
||||||
drawTextRun(canvas, line, palette, heightOffset, lastRunStartColumn, columnWidthSinceLastRun, lastRunStartIndex, charsSinceLastRun,
|
drawTextRun(canvas, line, palette, heightOffset, lastRunStartColumn, columnWidthSinceLastRun, lastRunStartIndex, charsSinceLastRun,
|
||||||
measuredWidthForRun, cursorColor, cursorShape, lastRunStyle, reverseVideo);
|
measuredWidthForRun, cursorColor, cursorShape, lastRunStyle, reverseVideo || lastRunInsideSelection);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
Before Width: | Height: | Size: 2.0 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 15 KiB |
@@ -1,4 +1,10 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
|
android:width="48dp"
|
||||||
android:src="@drawable/text_select_handle_left_mtrl_alpha"
|
android:height="24dp"
|
||||||
android:tint="#2196F3" />
|
android:viewportWidth="132"
|
||||||
|
android:viewportHeight="66">
|
||||||
|
<path
|
||||||
|
android:pathData="M52.3,1.6c-5.7,2.1 -12.9,8.6 -16,14.8 -2.2,4.1 -2.8,6.9 -3.1,14.3 -0.6,12.6 1.3,17.8 9.3,25.8 8,8 13.2,9.9 25.8,9.3 11.1,-0.5 17.3,-3.2 23.5,-10.3 6.5,-7.4 7.2,-10.8 7.2,-34.7l0,-20.8 -21.2,0.1c-16.1,-0 -22.3,0.4 -25.5,1.5z"
|
||||||
|
android:fillColor="#2196F3"
|
||||||
|
android:strokeColor="#00000000"/>
|
||||||
|
</vector>
|
||||||
|
|||||||
@@ -1,4 +1,10 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
|
android:width="48dp"
|
||||||
android:src="@drawable/text_select_handle_right_mtrl_alpha"
|
android:height="24dp"
|
||||||
android:tint="#2196F3" />
|
android:viewportWidth="132"
|
||||||
|
android:viewportHeight="66">
|
||||||
|
<path
|
||||||
|
android:pathData="M33,20.8c0,23.9 0.7,27.3 7.2,34.7 6.2,7.1 12.4,9.8 23.5,10.3 12.6,0.6 17.8,-1.3 25.8,-9.3 8,-8 9.9,-13.2 9.3,-25.8 -0.5,-11.1 -3.2,-17.3 -10.3,-23.5 -7.4,-6.5 -10.8,-7.2 -34.7,-7.2l-20.8,-0 0,20.8z"
|
||||||
|
android:fillColor="#2196F3"
|
||||||
|
android:strokeColor="#00000000"/>
|
||||||
|
</vector>
|
||||||
|
|||||||
Reference in New Issue
Block a user