Compare commits

..

114 Commits
v0.82 ... v0.99

Author SHA1 Message Date
Leonid Pliushch
cba80b6c0b add gradle distribution sha256 2020-09-01 03:38:53 +03:00
Leonid Pliushch
850faa25dd version 0.99 2020-08-26 20:51:05 +03:00
Leonid Pliushch
a108b2bd6b gradle 6.6.1 2020-08-26 20:40:29 +03:00
Leonid Pliushch
b95823d7a8 update bootstrap archives 2020-08-26 20:27:47 +03:00
Leonid Pliushch
382da7e8f7 terminal view: provide a workaroud for issue with some keyboards
Issue https://github.com/termux/termux-app/issues/686.

Note that there can be a better workaround which I don't know...
2020-08-26 20:04:22 +03:00
blackcat-917
ba9c118b50 readme: fix a copy-paste typo (#1720) 2020-08-22 00:17:45 +03:00
Leonid Pliushch
531c32f3c9 CI: do builds for android-10 branch 2020-08-16 22:56:55 +03:00
Leonid Pliushch
db2f50c76e extra keys: use TextUtils.join instead of String.join
String.join() is available only on Android API 26+ but our current
minimal is 24.

See https://github.com/termux/termux-app/issues/1670.
2020-08-14 15:08:32 +03:00
Leonid Pliushch
784affe39c linter: fix wakelock log tag
See https://github.com/termux/termux-app/issues/1670.
2020-08-14 15:03:51 +03:00
Leonid Pliushch
b486d29d23 fix RUN_COMMAND permission description
See https://github.com/termux/termux-app/issues/1713.
2020-08-14 14:50:34 +03:00
Fredrik Fornwall
332f1104a3 Update drawerlayout library 2020-08-09 00:07:35 +02:00
Leonid Pliushch
5a70be1523 terminal emulator: flush remaining process output data when terminating session 2020-08-08 02:22:47 +03:00
Leonid Pliushch
619552ec5c version 0.98 (v0.97 is non-release, so updating from 0.96 to 0.98) 2020-08-07 15:39:44 +03:00
Leonid Pliushch
70580abd50 update bootstrap archives 2020-08-07 15:39:11 +03:00
Henrik Grimler
f191c35851 Merge pull request #1693 from landfillbaby/patch-1
update WcWidth.java, add note about c version
2020-08-07 13:39:33 +02:00
lucy phipps
bd7ed28981 update WcWidth.java, add note about c version 2020-08-07 07:38:06 +01:00
hannesa2
b68bd107c1 Gradle 6.5.1 (#1686) 2020-08-04 15:37:18 +03:00
hannesa2
5075273362 Android Studio 4.0.1 (#1687) 2020-08-04 15:36:41 +03:00
cn
04268f4c20 move sdk version configs to gradle.properties (#1685) 2020-08-04 15:36:14 +03:00
Leonid Pliushch
6f24628fd2 version 0.96 2020-07-30 23:56:27 +03:00
Leonid Pliushch
debbe44809 update bootstrap archives 2020-07-30 23:47:26 +03:00
Agnostic Apollo
b2ff0e4051 Changed static string "EXTRA_EXECUTE_IN_BACKGROUND" access to public 2020-07-30 21:39:41 +03:00
Agnostic Apollo
9e7029b76a Receive "RUN_COMMAND_ARGUMENTS" extra for "RUN_COMMAND_ACTION" intent
as a string array extra instead of a string extra since TermuxService expects it that way.

Added "RUN_COMMAND_BACKGROUND" boolean extra so that Termux session can be started in background
when running a command.

Updated usage docs.

Check #1029 for details.
2020-07-30 21:39:41 +03:00
Egor Zhdanov
51370799c7 update notification icon 2020-07-14 12:36:56 +03:00
Danny Lin
0910844896 Invert selected text instead of highlighting
Highlighting text in the terminal often makes it hard to read, which
can be problematic for users who want to adjust or review selections
before copying them. For example, the default theme makes white and
green text hard to read on its light gray selection background, and
there are plenty of other themes where the choice of text and cursor
colors would hinder selection readability.

To fix this issue and make selected text more legible in nearly all
combinations of colors, invert selected text instead of highlighting it.
This is more common among terminal emulators anyway:
    Invert:    xterm, fbcon, kitty, Konsole, Alacritty, Tilix,
               gnome-terminal (7)
    Highlight: Termux, Terminal.app, iTerm2, Windows Terminal (4)
2020-07-12 17:54:47 +03:00
Danny Lin
f33ebf810f Fix selection rendering with alternate cursor styles
There is currently a bug where selection rendering is broken if the
active cursor shape is anything other than the default solid box.
Selected text is normally highlighted by effectively rendering a cursor
over all of the characters in the selection region, but if the cursor is
a bar, the resulting selection highlight is too narrow to cover the full
width of the selection. Similarly, if the cursor is an underline, all of
the selected text will be underlined instead of highlighted.

To fix this issue, treat selections different from cursors in the
rendering logic and force the renderer to always use the block cursor
style for rendering selections. That way, we get correct behavior
regardless of what the current cursor shape is.
2020-07-04 15:31:45 +03:00
Felix C. Stegerman
930029b5d2 export COLORTERM=truecolor 2020-06-22 02:07:20 +03:00
Leonid Pliushch
33def928cf add DEX2OATBOOTCLASSPATH environment variable 2020-06-19 22:25:51 +03:00
Leonid Pliushch
fc04a93990 get rid of Android 5 legacy stuff
We don't need LD_LIBRARY_PATH as of Android 7.0.
2020-06-19 22:18:28 +03:00
Leonid Pliushch
8d302aa9fe fix am on Android R 2020-06-19 22:18:21 +03:00
Leonid Pliushch
2224d917a3 update ndk version 2020-06-17 14:08:58 +03:00
Leonid Pliushch
664ec43f94 version 0.95 2020-06-09 23:08:23 +03:00
Leonid Pliushch
6cf742460c extra keys: make popup & pressed button no longer transparent
Fixes visual issue when popup overlaps text of upper row.
2020-06-09 22:54:57 +03:00
Leonid Pliushch
72981fb981 Use vector drawables for text selection handle
https://github.com/termux/termux-app/issues/1036
2020-06-09 21:47:11 +03:00
Leonid Pliushch
2c5534e2c1 RunCommandService: update information about usage 2020-06-09 16:07:42 +03:00
Leonid Pliushch
5b32540635 minor refactoring: RunCommand => RunCommandService 2020-06-09 15:48:56 +03:00
Leonid Pliushch
db3ff7b24a Provide a service for executing commands by third-party applications
Re-implementation of https://github.com/termux/termux-app/pull/1029.

If Termux has property "allow-external-apps" set to "true", a third-party
program will be able to send intents for executing custom commands
within Termux environment.

Third-party program must declare permission "com.termux.permission.RUN_COMMAND".
2020-06-09 15:36:08 +03:00
Trygve Aaberge
5f71e3e73a Join lines that fills the width when selecting urls
Some terminal applications, like mutt and weechat, prints a newline at
the end of each line even if text is wrapped. This causes urls which are
wrapped to not be selectable in full.

By ignoring newlines when the text fills the entire width of the screen,
those urls can be selected. Many other terminal emulators do this as
well.

A drawback of this is that if a url happens to fill the width of the
screen, the url selection will include the first word of the next line,
but this doesn't happen that often so I think it's an okay tradeoff.

Fixes #313
2020-06-09 13:41:36 +03:00
Josh Home
3e04ea4cb0 Add Log file to view intent #1497 2020-06-09 12:31:59 +03:00
Trygve Aaberge
0af823607a Improvements to extra keys (#1479)
* Make popup keys for extra keys row configurable

This makes the keys you get when swiping up on a key configurable. You
can configure such a key by using an array of strings instead of a
single string in the row. The first entry will be the normal key and the
second will be the extra key.

This is a slightly breaking change, as people that have configured
custom extra keys with "-" or "/" will have to change the config to keep
the popup keys. The default config will remain the same in terms of
functionality, i.e. it includes the same popup key for "-".

* Make popup keys interact well with long press keys

This stops the repeat action when the popup is shown, and makes sure the
popup is closed when you release even if there has been some repeat
actions.

* Support configuring the style of the extra keys

This adds a setting for choosing between the different ways to render
key names that were already present in ExtraKeysView.

The available setting values are "arrows-only", "arrows-all", "all",
"none" and "default". Other values will fallback to "default".

Can be used as a workaround for #1410

* Support using modifier keys with letter keys in extra keys

This allows you to use the modifier keys on the extra keys rows, e.g.
ctrl, together with another button on the extra keys rows, as long as
that button is a normal letter and not a special key. Support for
special keys will come in the next commit.

* Support using modifier keys with special keys in extra keys

This allows you to use the modifier keys on the extra keys rows together
with a special key on the extra keys rows, e.g. CTRL+LEFT.

Fixes #745, fixes most of #895 and possibly #154

* Support mapping extra keys to other actions

This adds a setting called extra-keys-map which allows you to map a key
on the extra keys rows to another action. The value is a json object
where the key is the button text as configured in extra-keys and the
value is the action. Multiple actions can be used, but if they are
special characters (like ESC or LEFT) they have to be separated from the
other characters with a space on each side. If you want an actual space
character, use SPACE.

For example if you want to add a key to go to the next active channel in
weechat, you can use this:
    extra-keys-map = {"weechat next": "ESC a"}
And then add "weechat next" to extra-keys. The name can of course be
whatever you want.

Or if you want the button for the UP arrow to show ⇧ instead of ↑, you
can use this:
    extra-keys-map = {"⇧": "UP"}
And put "⇧" in extra-keys instead of "UP".

Modifier keys (ctrl, alt and shift) can't be used in this map yet.
Support for ctrl and alt will come in the next commit.

I think this fixes #1186

* Support CTRL and ALT in extra keys map

This allows you to use CTRL and ALT in extra-keys-map.

For example if you want a button to exit the terminal, you can use this:

    extra-keys-map = {"exit": "CTRL d"}

And add "exit" to extra-keys.

* Support a KEYBOARD button in extra keys

This toggles showing the keyboard input method.

* Support specifying macro keys in the extra-keys option

Instead of specifying macros in the separate extra-keys-map option by
matching the key name in the two options, you can now use "macro"
instead of "key" in extra-keys, and it will be a macro, i.e. a sequence
of multiple keys separated by space.

* Remove option extra-keys-map

Now that you can specify macro in extra-keys, there is no point in
having this separate option. Instead of specifying the value to display
as key, and the macro to perform in extra-keys-map, you would now
specify the value to display in the display property and the macro to
perform in the macro property.

* Lookup display text when creating ExtraKeyButton

This will make it easier to support key aliases for macros in the next
commit.

* Add support for a key to open the drawer

Fixes (I think) #1325
2020-06-09 12:17:07 +03:00
Trygve Aaberge
4d9c0c315e Update notification icon to match launcher icon
This is the launcher icon with a circle around it. I added the circle
because the icon has a transparent background, so it looks a bit weird
with just the >_.
2020-06-09 12:11:49 +03:00
Leonid Pliushch
9c32935ca2 fix ndk version for terminal-emulator module 2020-06-09 12:08:13 +03:00
Leonid Pliushch
669c336e2f update ndk version 2020-06-09 12:02:41 +03:00
Trygve Aaberge
35842cf4a6 Set orientation of HandleView in show (#1477)
* Place long press menu above selection

Previously, the long press menu would cover the first line of the
selection.

* Flip selection handle at different positions depending on drag direction

When the selection handle changes direction, the selection jumps to the
new point of the handle. When the handle changes direction at the same
place when you come from the left as from the right, that makes it
impossible to select the characters which are at the position where it
changes direction.

With this change the handle remains pointing towards the edge further
into the line when you drag it from the edge and against the center.

* Set orientation of HandleView when showing it

When you hold down on a word that starts or ends at the edge of the
screen, the handle will appear outside of the screen. This happens
because the orientation was only switched when the handle is dragged, so
when it is shown it just used the same orientation as it had for the
last selection.

Relates to #334, but not sure if it fixes it completely.
2020-06-09 11:59:59 +03:00
Trygve Aaberge
b086270a5a Support auto detection of dark theme
By default it uses the system setting. If use-black-ui is set to either
true or false, that overrides it.

Fixes #1351 properly, fixes #1354
2020-06-09 11:59:07 +03:00
Leonid Pliushch
f39f06a540 update bootstraps again
* Reduced archive size by using nano as preinstalled text editor instead of vim.
* Updated command-not-found and termux-tools.
2020-06-07 22:36:14 +03:00
Leonid Pliushch
58440bc88d update bootstraps again
Added few more packages to bootstraps which are replacements to
some busybox applets.
2020-06-07 03:56:51 +03:00
Leonid Pliushch
9f438e2912 update bootstraps 2020-06-06 22:42:29 +03:00
Leonid Pliushch
f794bfcadc Handle all exceptions when loading termux.properties (#1590)
* Catch all exceptions which can occur.
* Print short description in toast message about occurred exception.
2020-06-06 21:20:05 +03:00
hannesa2
b6d7831646 Fix CI build and see result in pull request (#1565) 2020-05-24 02:05:24 +02:00
Leonid Pliushch
d212198e30 update bootstraps 2020-05-24 02:44:06 +03:00
Fredrik Fornwall
c2843897ac Fix build by specifying ndkVersion 2020-05-24 01:27:01 +02:00
Fredrik Fornwall
c4c4912a7e Avoid specifying ndk version 2020-05-24 01:22:33 +02:00
Hannes Achleitner
38a3319ca2 show result in pull request 2020-05-24 01:08:01 +02:00
Hannes Achleitner
7e13b8aa2e Verify Gradle 2020-05-24 01:08:01 +02:00
Frieder Bluemle
6dca19ae00 Update Android Gradle plugin to 3.6.3 2020-05-24 01:06:15 +02:00
Leonid Plyushch
2659c06c5d Remove irrelevant message from bootstrap installation error
Bootstraps are no longer downloaded from the Internet.
2020-04-30 14:34:35 +03:00
Fredrik Fornwall
9b7c7102b2 Bump version to 0.94
Some checks failed
Build / build (push) Has been cancelled
Unit tests / testing (push) Has been cancelled
2020-03-24 23:25:49 +01:00
Daniel Lublin
1819087ca0 Add tests for URL fragment 2020-03-24 23:13:14 +01:00
Daniel Lublin
366a61f052 Grab fragment when extracting URLs for selection 2020-03-24 23:13:14 +01:00
Leonid Plyushch
6e224cabcf set ndk version in build.gradle
Fixing CI builds.
2020-03-21 11:31:52 +02:00
Leonid Plyushch
8c8fa96133 update bootstraps 2020-03-21 10:35:17 +02:00
Fredrik Fornwall
537f2ed97e Update gradle wrapper 2020-03-20 14:42:43 +01:00
Fredrik Fornwall
0e23315c41 Add android.useAndroidX=true to gradle.properties 2020-03-20 10:44:24 +01:00
Fredrik Fornwall
2cde986419 Update Android gradle plug-in 2020-03-16 13:58:34 +01:00
Fredrik Fornwall
d28939810c Update Robolectric from 4.3 to 4.3.1 2020-02-21 14:02:53 +01:00
Fredrik Fornwall
93724b7aa6 Bump version to 0.92
Some checks failed
Build / build (push) Has been cancelled
Unit tests / testing (push) Has been cancelled
2020-02-09 22:59:33 +01:00
x0b
5d06f040e8 Fix deletable flags in DocumentProvider
A file is deletable if the _parent_ is writable, not the file itself.
2020-02-09 22:57:27 +01:00
x0b
ed9afa082a Fix #1350: Support createDocument(...) 2020-02-09 22:57:27 +01:00
x0b
9703bd31ad Fix #1424: Support ACTION_OPEN_DOCUMENT_TREE 2020-02-09 22:57:27 +01:00
Leonid Plyushch
9749f25eba update bootstrap archives
v19 had a problem with metadata which broke the package manager.
2020-02-09 12:34:11 +02:00
Leonid Plyushch
453b838b24 update bootstrap archives 2020-02-08 20:33:34 +02:00
Leonid Plyushch
9fdf2a49fd update readme 2020-02-07 15:48:32 +02:00
Leonid Plyushch
3270506bff update issue templates 2020-02-07 15:05:16 +02:00
Leonid Plyushch
a240f4cf45 CI: fix job name in run_tests.yml 2020-02-07 14:02:34 +02:00
Leonid Plyushch
4647beb0d2 update readme 2020-02-07 13:57:08 +02:00
Leonid Plyushch
d92e806461 CI: update workflow labels 2020-02-07 13:55:45 +02:00
Leonid Plyushch
b75cf0bb84 CI: add configuration for running terminal emulator unit tests 2020-02-07 13:50:12 +02:00
Leonid Plyushch
f928efed4e CI: switch to Github Actions 2020-02-07 13:47:22 +02:00
Fredrik Fornwall
36db64d585 Bump version to 0.90 2020-02-02 03:36:52 +01:00
Jean Schurger
b8f0430699 Prevent kill by Samsung Dex on plug/unplug. 2020-02-02 03:36:13 +01:00
Edontin
90e6260d5e Allow the user to disable virtual key emulation.
Use volume-keys=volume within termux.properties to disable.
2020-01-06 10:21:32 +01:00
Fredrik Fornwall
566d656c16 Avoid trailing slash in CWD (fixes #1413) 2020-01-05 19:14:52 +01:00
Fredrik Fornwall
b729085d52 Bump version to 0.88 2020-01-05 19:14:35 +01:00
Fredrik Fornwall
a94bcb02f1 Bump version to 0.86 2020-01-05 02:02:05 +01:00
Fredrik Fornwall
e28be01dc2 Create new terminal sessions with directory of active session
This mimics the behaviour of most tabbed terminal emulators.

Fixes #1009.
2020-01-05 02:00:25 +01:00
Archenoth
7f7c1efac1 Add PendingIntent in TermuxService to return data from execution
This commit adds an optional final argument to the BackgroundJob
constructor for a PendingIntent to return the results of its
execution to, and also attempts to pass an optional pendingIntent to
it from the Service start intent
2020-01-05 01:07:11 +01:00
Archenoth
76df44e6bb Start stderr BackgroundJob Thread 2020-01-05 01:07:11 +01:00
Fredrik Fornwall
fd13f3f98d Handle magnet links with termux-url-opener
Fixes #1339 and #1382.
2020-01-05 01:02:47 +01:00
Leonid Plyushch
269c3cafb0 properly set bootclasspath environment variable 2020-01-05 00:38:49 +01:00
Fredrik Fornwall
662b5cace6 Update gradle wrapper 2020-01-01 22:57:52 +01:00
Fredrik Fornwall
3c01b09fff Bump version to 0.85 2019-12-29 18:53:00 +01:00
Fredrik Fornwall
1ce2db9929 Merge branch 'maoabc-master' 2019-12-29 18:50:34 +01:00
Fredrik Fornwall
c987ff718a Update Android Gradle plug-in 2019-12-29 18:49:41 +01:00
Leonid Plyushch
9e295b105c terminal emulator: clear scrollback buffer when resetting to initial state
Fixes utility 'reset' not being able to clear scrollback buffer.
2019-12-29 18:49:41 +01:00
Fredrik Fornwall
37b3a9f8db Update Android Gradle plug-in 2019-12-29 18:49:10 +01:00
Leonid Plyushch
490853e427 terminal emulator: clear scrollback buffer when resetting to initial state
Fixes utility 'reset' not being able to clear scrollback buffer.
2019-12-24 00:32:40 +01:00
mao
9ebedc4740 Merge remote-tracking branch 'remotes/origin/master' 2019-12-19 00:43:45 +08:00
mao
82730d9e08 confict 2019-12-19 00:34:14 +08:00
mao
3d2756f376 Optimize handleView move. 2019-12-18 23:50:23 +08:00
Leonid Plyushch
6db51bdec0 remove SDK_INT conditionals
We are targting on API 24, so conditionals for Android 5/6 compatibillity
are useless now.
2019-12-18 14:06:06 +02:00
mao
c238c8bddb Optimize handle view 2019-12-18 14:00:58 +02:00
mao
60e1556871 Stop selection mode on enter 2019-12-18 14:00:57 +02:00
mao
e47bd31681 Selection mode fling 2019-12-18 14:00:57 +02:00
mao
951df6b4e2 Add selection mode cursor controller 2019-12-18 14:00:32 +02:00
Leonid Plyushch
fcd3bc1133 update gradle wrapper 2019-12-02 17:19:17 +02:00
Fredrik Fornwall
f4db9ff212 Bump version to 0.84 2019-11-24 19:10:10 +01:00
Leonid Plyushch
87841886d4 extra keys: improve handling of DnD mode
Do not disturb mode is now handled only for SDKs pre-28. Extra keys will
not vibrate only when total silence mode is used.
2019-11-24 19:08:32 +01:00
Leonid Plyushch
cf883f5f05 extra keys: handle actions UP & CANCEL separately
Related issue: https://github.com/termux/termux-app/issues/905
2019-11-24 19:08:32 +01:00
Fredrik Fornwall
677d75e173 Remove outdated tests
Remove tests that asserted that Cursor Down (CUD) and Cursor Up (CUU)
escape sequences were affected by the scrolling region set by DECSTBM.

This was incorrect and recently fixed:
https://github.com/termux/termux-app/issues/1340
2019-11-24 19:07:34 +01:00
mao
fdb3764f5c Optimize handle view 2019-10-11 07:37:57 +08:00
mao
937eb350b2 Stop selection mode on enter 2019-10-05 18:54:15 +08:00
mao
3b4ece6bd8 Selection mode fling 2019-10-05 18:30:54 +08:00
mao
35a4fdacbe Add selection mode cursor controller 2019-10-05 18:05:42 +08:00
44 changed files with 2177 additions and 866 deletions

View File

@@ -1,20 +0,0 @@
container:
image: cirrusci/android-sdk:28
cpu: 2
memory: 8G
task:
name: tests
script: ./gradlew test
task:
name: debug-build
depends_on:
- tests
script: |
./gradlew assembleDebug
output_artifacts:
path: "./app/build/outputs/apk/debug/*.apk"

View File

@@ -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:

View File

@@ -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
View 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

View 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
View 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

View File

@@ -1,6 +1,7 @@
# Termux application # Termux application
[![Build status](https://api.cirrus-ci.com/github/termux/termux-app.svg?branch=master)](https://cirrus-ci.com/termux/termux-app) [![Build status](https://github.com/termux/termux-app/workflows/Build/badge.svg)](https://github.com/termux/termux-app/actions)
[![Testing status](https://github.com/termux/termux-app/workflows/Unit%20tests/badge.svg)](https://github.com/termux/termux-app/actions)
[![Join the chat at https://gitter.im/termux/termux](https://badges.gitter.im/termux/termux.svg)](https://gitter.im/termux/termux) [![Join the chat at https://gitter.im/termux/termux](https://badges.gitter.im/termux/termux.svg)](https://gitter.im/termux/termux)
[Termux](https://termux.com) is an Android terminal application and Linux environment. [Termux](https://termux.com) is an Android terminal application and Linux environment.
@@ -15,15 +16,16 @@ terminal emulation). For the packages installable inside the app, see
## Installation ## Installation
Termux:Widget application can be obtained from: Termux application can be obtained from:
- [Google Play](https://play.google.com/store/apps/details?id=com.termux) - [Google Play](https://play.google.com/store/apps/details?id=com.termux)
- [F-Droid](https://f-droid.org/en/packages/com.termux/) - [F-Droid](https://f-droid.org/en/packages/com.termux/)
- [Kali Nethunter Store](https://store.nethunter.com/en/packages/com.termux/) - [Kali Nethunter Store](https://store.nethunter.com/en/packages/com.termux/)
Additionally we offer development builds for those who want to try out latest Additionally we provide per-commit debug builds for those who want to try
features ready to be included in future versions. Such build can be obtained out the latest features or test their pull request. This build can be obtained
directly from [Cirrus CI artifacts](https://api.cirrus-ci.com/v1/artifact/github/termux/termux-app/debug-build/output/app/build/outputs/apk/debug/app-debug.apk). from one of the workflow runs listed on [Github Actions](https://github.com/termux/termux-app/actions)
page.
Signature keys of all offered builds are different. Before you switch the Signature keys of all offered builds are different. Before you switch the
installation source, you will have to uninstall the Termux application and installation source, you will have to uninstall the Termux application and

View File

@@ -3,21 +3,22 @@ plugins {
} }
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 24 minSdkVersion project.properties.minSdkVersion.toInteger()
targetSdkVersion 28 targetSdkVersion project.properties.targetSdkVersion.toInteger()
versionCode 82 versionCode 99
versionName "0.82" versionName "0.99"
externalNativeBuild { externalNativeBuild {
ndkBuild { ndkBuild {
@@ -62,10 +63,17 @@ android {
path "src/main/cpp/Android.mk" 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 { task versionName {
@@ -125,11 +133,11 @@ clean {
task downloadBootstraps(){ task downloadBootstraps(){
doLast { doLast {
def version = 18 def version = 28
downloadBootstrap("aarch64", "1a4c08a696d452b58f69102428239ec0c07521c0ca9f48b23ef70ae0e5e3d4f8", version) downloadBootstrap("aarch64", "8f8c8af1d6c50bd0d6ecd5a1df8d5b0c3122a945b6febd0de0308504b4075ed0", version)
downloadBootstrap("arm", "bff11f2c7e9c1055a22fc5f20bb7507b75f6034e0f5d591ec6725b3407981b85", version) downloadBootstrap("arm", "179465f403a73fec6534363b11cb38eed3b6b3d8e9d922d8e55dc8a06813240d", version)
downloadBootstrap("i686", "6fb93020db2807337d82a1537e24612400cacbd10cf4bccaeb0714d51e653da1", version) downloadBootstrap("i686", "2100ebf36befde73b08efaa7e3ccc9c218a8b5e7d69db06e16e364ab8b7dfbd5", version)
downloadBootstrap("x86_64", "a6067e5decc486dcad190c1ed9e15366c798e5e7d9b9b9ee6b4b8231290524c3", version) downloadBootstrap("x86_64", "3f5192f63595426370aa69f102fac07ae00c12154878eddec24e8406e640a67f", version)
} }
} }
@@ -138,4 +146,3 @@ afterEvaluate {
variant.javaCompileProvider.get().dependsOn(downloadBootstraps) variant.javaCompileProvider.get().dependsOn(downloadBootstraps)
} }
} }

View File

@@ -8,6 +8,12 @@
<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.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" />
@@ -31,11 +37,11 @@
<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" />
@@ -79,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*" />
@@ -113,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"
@@ -120,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>

View File

@@ -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,28 @@ 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();
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) {
Log.i(LOG_TAG, "[" + pid + "] stderr: " + line);
}
} catch (IOException e) {
// Ignore.
}
}
};
} }
private static void addToEnvIfPresent(List<String> environment, String name) { private static void addToEnvIfPresent(List<String> environment, String name) {
@@ -108,24 +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("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"));
// ANDROID_RUNTIME_ROOT and ANDROID_TZDATA_ROOT are required for `am` to run on Android Q
// 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_RUNTIME_ROOT");
addToEnvIfPresent(environment, "ANDROID_TZDATA_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);
@@ -135,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("//termux.net stable")) {
return true;
}
}
} catch (IOException e) {
Log.e(LOG_TAG, "Error trying to read sources.list", e);
}
return false;
}
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");

View 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;
}
}

View File

@@ -1,5 +1,6 @@
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.provider.Settings;
@@ -13,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.
@@ -35,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);
@@ -87,45 +74,79 @@ public final class ExtraKeysView extends GridLayout {
put("F11", KeyEvent.KEYCODE_F11); put("F11", KeyEvent.KEYCODE_F11);
put("F12", KeyEvent.KEYCODE_F12); 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;
@@ -166,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
@@ -316,36 +213,36 @@ 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);
@@ -354,19 +251,23 @@ public final class ExtraKeysView extends GridLayout {
if (Settings.System.getInt(getContext().getContentResolver(), if (Settings.System.getInt(getContext().getContentResolver(),
Settings.System.HAPTIC_FEEDBACK_ENABLED, 0) != 0) { Settings.System.HAPTIC_FEEDBACK_ENABLED, 0) != 0) {
// Depending on DnD settings, value can be >1 but 0 means "disabled". if (Build.VERSION.SDK_INT >= 28) {
if (Settings.Global.getInt(getContext().getContentResolver(), "zen_mode", 0) < 1) {
finalButton.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP); 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);
} }
}); });
@@ -376,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);
@@ -401,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();
} }

View 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");
}
}

View File

@@ -148,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);
} }
} }
} }
@@ -229,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() {
@@ -250,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);
@@ -603,7 +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 {
TerminalSession newSession = mTermService.createTermSession(null, null, null, failSafe); TerminalSession currentSession = getCurrentTermSession();
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;
} }
@@ -731,6 +733,9 @@ public final class TermuxActivity extends Activity implements ServiceConnection
// Resource path with optional query string. // Resource path with optional query string.
regex_sb.append("(?:/[a-zA-Z0-9:@%\\-._~!$&()*+,;=?/]*)?"); regex_sb.append("(?:/[a-zA-Z0-9:@%\\-._~!$&()*+,;=?/]*)?");
// Fragment.
regex_sb.append("(?:#[a-zA-Z0-9:@%\\-._~!$&()*+,;=?/]*)?");
// End second matching group. // End second matching group.
regex_sb.append(")"); regex_sb.append(")");
@@ -752,7 +757,7 @@ public final class TermuxActivity extends Activity implements ServiceConnection
} }
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();

View File

@@ -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;
@@ -21,10 +20,7 @@ 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;

View File

@@ -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,7 +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 String mUseDarkUI; private boolean mUseDarkUI;
private boolean mScreenAlwaysOn; private boolean mScreenAlwaysOn;
private int mFontSize; private int mFontSize;
@@ -66,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<>();
@@ -128,7 +134,7 @@ final class TermuxPreferences {
} }
boolean isUsingBlackUI() { boolean isUsingBlackUI() {
return mUseDarkUI.toLowerCase().equals("true"); return mUseDarkUI;
} }
void setScreenAlwaysOn(Context context, boolean newValue) { void setScreenAlwaysOn(Context context, boolean newValue) {
@@ -148,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())
@@ -161,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);
} }
@@ -178,26 +184,39 @@ final class TermuxPreferences {
break; break;
} }
mUseDarkUI = props.getProperty("use-black-ui", "false"); switch (props.getProperty("use-black-ui", "").toLowerCase()) {
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;
}
String defaultExtraKeys = "[[ESC, TAB, CTRL, ALT, {key: '-', popup: '|'}, DOWN, UP]]";
try { try {
JSONArray arr = new JSONArray(props.getProperty("extra-keys", "[['ESC', 'TAB', 'CTRL', 'ALT', '-', 'DOWN', 'UP']]")); String extrakeyProp = props.getProperty("extra-keys", defaultExtraKeys);
String extraKeysStyle = props.getProperty("extra-keys-style", "default");
mExtraKeys = new String[arr.length()][]; mExtraKeys = new ExtraKeysInfos(extrakeyProp, extraKeysStyle);
for (int i = 0; i < arr.length(); i++) {
JSONArray line = arr.getJSONArray(i);
mExtraKeys[i] = new String[line.length()];
for (int j = 0; j < line.length(); j++) {
mExtraKeys[i][j] = line.getString(j);
}
}
} 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);

View File

@@ -63,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 {
@@ -106,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
@@ -148,7 +148,7 @@ 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 {

View File

@@ -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) {

View File

@@ -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);

View 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);

View File

@@ -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>

View File

@@ -2,6 +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="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>
@@ -13,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>

View File

@@ -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");
}
} }

View File

@@ -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));
}
}
}

View File

@@ -4,7 +4,7 @@ buildscript {
google() google()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:3.5.2' classpath 'com.android.tools.build:gradle:4.0.1'
} }
} }

View File

@@ -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

Binary file not shown.

View File

@@ -1,6 +1,6 @@
#Sun Aug 25 01:57:11 CEST 2019
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
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
distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip

53
gradlew vendored
View File

@@ -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
View File

@@ -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

View File

@@ -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'

View File

@@ -45,11 +45,19 @@ public final class TerminalBuffer {
return getSelectedText(0, -getActiveTranscriptRows(), mColumns, mScreenRows, false).trim(); 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); return getSelectedText(selX1, selY1, selX2, selY2, true);
} }
public String getSelectedText(int selX1, int selY1, int selX2, int selY2, boolean joinBackLines) { 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;
@@ -87,7 +95,8 @@ 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 ((!joinBackLines || !rowLineWrap) boolean lineFillsWidth = lastPrintingCharIndex == x2Index - 1;
if ((!joinBackLines || !rowLineWrap) && (!joinFullLines || !lineFillsWidth)
&& row < selY2 && row < mScreenRows - 1) builder.append('\n'); && row < selY2 && row < mScreenRows - 1) builder.append('\n');
} }
return builder.toString(); return builder.toString();

View File

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

View File

@@ -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;
}
} }

View File

@@ -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)
}; };

View File

@@ -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() {

View File

@@ -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));
}
} }

View File

@@ -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'

View File

@@ -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

View File

@@ -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>

View File

@@ -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>