Compare commits

...

482 Commits

Author SHA1 Message Date
Leonid Pliushch
a56cba6843 version 0.105 2021-01-06 14:31:56 +02:00
Leonid Pliushch
9228982632 remove restrictions from viewable file types
Fixes https://github.com/termux/termux-app/issues/1872 and
similar issues.
2021-01-06 14:28:28 +02:00
Leonid Pliushch
38114784f1 update bootstrap archives 2021-01-06 14:23:15 +02:00
Henrik Grimler
b805f1486c Merge pull request #1869 from termux/ctrlspace
Add workaround property to fix ctrl+space on devices where this does not work
2021-01-04 22:41:56 +01:00
Henrik Grimler
7d31b7f480 terminal-emulator: tests: change spaces to tabs for consistency 2021-01-02 09:12:14 +01:00
Henrik Grimler
a0298285e3 terminal-emulator: tests: avoid error about methods not being mocked
unitTests.returnDefaultValues = true fixes it.
More info: http://g.co/androidstudio/not-mocked
2021-01-02 09:12:14 +01:00
Lucy Phipps
538a1d5cdf F-Droid URL for Termux:Styling 2021-01-01 23:48:34 +02:00
Henrik Grimler
f1e973f0d2 terminal-view: add "ctrl-space-workaround" property
Makes it possible to run ctrl+space with hardware keyboards on
devices/ROMs where it otherwise is broken. On devices where it already
works this workaround breaks ctrl+space though.

Where to add this fix was investigated and found by @5bodnar.
2021-01-01 19:20:16 +01:00
Henrik Grimler
b467b68f7b terminal-view: mv code to get properties to its own function 2021-01-01 19:20:11 +01:00
Henrik Grimler
b895cbbb1e app: set importantForAutofill="no" for extra keys input field
Fixes warning "extra_keys_right.xml:2: Missing autofillHints attribute".
2021-01-01 18:13:37 +01:00
Henrik Grimler
fc30eba247 terminal-view: silence warning from use of SHOW_AS_ACTION_ALWAYS
```
Prefer "SHOW_AS_ACTION_IF_ROOM" instead of "SHOW_AS_ACTION_ALWAYS"
```
2021-01-01 18:13:36 +01:00
Henrik Grimler
b1d4c0c7fe app: disable test for "ProtectedPermissions"
Needed for tests to pass after READ_LOGS and WRITE_SECURE_SETTINGS
were added to AndroidManifest.xml.
2021-01-01 18:13:36 +01:00
Henrik Grimler
7fe5bd32c8 Update ndk to r22 2021-01-01 18:12:53 +01:00
Henrik Grimler
43bbef9a11 Update plugins and gradle to latest versions 2021-01-01 18:12:53 +01:00
Henrik Grimler
eaea0f74a5 Fix github links for terminal-{emulator,view} jcenter packages 2021-01-01 18:12:53 +01:00
Leonid Pliushch
cb13a5a531 version 0.104 2020-12-29 13:45:35 +02:00
Kevin LeBlanc
d267843e36 Don't exclude hidden files from document provider (#1220)
Credit to @johnmellor for requesting the document provider in the
first place via #79, mentioning this limitation in a comment on
that issue, and creating a commit like this one to address it.
2020-12-29 13:42:36 +02:00
Leonid Pliushch
5ca67dd885 update bootstrap archives 2020-12-29 13:40:25 +02:00
Leonid Pliushch
6b0d531758 Revert "update readme" 2020-12-17 20:27:46 +02:00
Leonid Pliushch
1b3283bd69 update bootstrap archives 2020-12-11 20:07:40 +02:00
Leonid Pliushch
2820f6a7b8 update readme 2020-12-11 17:36:14 +02:00
Leonid Pliushch
8925ae67bb update readme 2020-12-11 17:35:28 +02:00
Lucy Phipps
4066c5df42 don't add $PREFIX/bin/applets to $PATH
busybox doesn't use that folder anymore, and is deprecated anyway
2020-11-24 23:16:17 +02:00
Leonid Pliushch
20aac6aa72 copy FUNDING.yml from termux-packages repo 2020-11-18 03:04:38 +02:00
Leonid Pliushch
e1f799f9a1 version 0.103 2020-11-17 20:40:40 +02:00
Leonid Pliushch
4479de4b9b update bootstrap archives 2020-11-17 20:35:55 +02:00
Leonid Pliushch
3a8f53a54c add permission WRITE_SECURE_SETTINGS
https://github.com/termux/termux-api/issues/211
2020-11-17 20:07:33 +02:00
Leonid Pliushch
2f04a6186b add READ_LOGS permission
* https://github.com/termux/termux-app/issues/873
* https://github.com/termux/termux-app/issues/1821
2020-11-17 20:03:10 +02:00
Leonid Pliushch
ad64dd7c3d update bootstrap archives 2020-10-12 20:39:28 +03:00
Fredrik Fornwall
dfc4595ec5 version 0.102 2020-10-02 10:30:18 +02:00
Fabian Henneke
8c80efb904 Add an "Autofill password" context menu action 2020-10-02 10:21:34 +02:00
Leonid Pliushch
564079c7e9 update .gitattributes 2020-10-01 01:08:39 +03:00
Leonid Pliushch
5b6fd9b88c version 0.101 2020-09-28 03:30:21 +03:00
Leonid Pliushch
63bfe95848 update bootstrap archives again
Fixes faulty dpkg https://github.com/termux/termux-packages/issues/5858.
2020-09-28 03:28:49 +03:00
Leonid Pliushch
af95a99854 version 0.100 2020-09-28 00:59:52 +03:00
Leonid Pliushch
25523ae224 fix bootstrap archive checksum for arm
Due to leading zero issues, SHA-256 checksum for ARM variant will
have 63 characters instead of correct 64...
2020-09-28 00:52:44 +03:00
Leonid Pliushch
665adb6895 update bootstrap archives 2020-09-28 00:46:38 +03:00
Henrik Grimler
eb7fda28df Merge pull request #1764 from agnostic-apollo/termux-run-command-crash-and-foreground-patch
Fix RunCommandService crash and foreground issue
2020-09-27 21:02:56 +02:00
agnostic-apollo
7063a3a9da Add @override annotation to onStartCommand() function of RunCommandService. 2020-09-18 22:25:05 +05:00
agnostic-apollo
6e02b99ff6 Update RunCommandService docs. 2020-09-18 22:22:35 +05:00
agnostic-apollo
9aae665bfc Fix issue where termux session does not come to foreground automatically in android >= 10 unless user manually clicks termux notification for "RUN_COMMAND" intents and "Termux:Tasker" plugin actions that have background mode "false" because of new restrictions of starting activities from background. This is done by adding "android.permission.SYSTEM_ALERT_WINDOW" permission in AndroidManifest.xml so that the user may optionally grant "Draw Over Apps" permission to termux to fix the issue. 2020-09-18 22:21:58 +05:00
agnostic-apollo
52ce6cc94b Add support for "$PREFIX/" and "~/" prefix in "RUN_COMMAND" intent extras for paths. 2020-09-18 22:21:31 +05:00
agnostic-apollo
f91168eff4 Fix issue where termux crashes occasionally in android >= 8 because "startForeground()" function is not being called before running "startForegroundService()" in RunCommandService. 2020-09-18 22:20:23 +05:00
Henrik Grimler
8faa5b2151 TerminalEmulator: fix bug in DECRQM handling
Reported in https://github.com/termux/termux-app/issues/1752
2020-09-12 21:27:24 +02:00
Leonid Pliushch
216cc10f3c Revert "add gradle distribution sha256"
Was initially added to try fix F-Droid builds but appears missing
distribution sha256 is not a cause of the issue.

F-Droid maintains own set of Gradle SHA-256 checksums.

This reverts commit cba80b6c0b.
2020-09-12 19:06:54 +00:00
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
Fredrik Fornwall
cdccc2c433 Do not limit cursor movement to scroll region
The scrolling region set by DECSTBM should not affect the
Cursor Down (CUD) and Cursor Up (CUU) escape sequences.

Fixes #1340.
2019-11-10 22:05:06 +01:00
Fredrik Fornwall
070436a6ed Bump version to 0.82 2019-11-10 19:26:10 +01:00
Fredrik Fornwall
69ded4b33e Update Android gradle plug-in 2019-11-10 19:26:01 +01:00
Fredrik Fornwall
e5a8c0eb4a Cleanup after requiring Android 7.0 2019-11-03 22:39:21 +01:00
Fredrik Fornwall
29815fb3f0 Fix gradle deprecation warning 2019-11-03 22:35:52 +01:00
Fredrik Fornwall
e930ac08aa Bump version to 0.78 2019-11-03 19:18:31 +01:00
Fredrik Fornwall
3b969a0077 Bump version to 0.77 2019-10-28 01:54:50 +01:00
Leonid Plyushch
ae717d8f5f do not allow to backup data by Google
Why:

 * During backup process Termux is being killed in most cases.
 * Backup data is limited to 25 MB.
 * Backup may not be performed/updated in certain cases.
2019-10-27 20:33:12 +01:00
Leonid Plyushch
6c55c3c1be bootstraps: update to v18 2019-10-20 22:06:18 +03:00
Fredrik Fornwall
c0a0822843 Do not set LD_LIBRARY_PATH by default
See https://github.com/termux/termux-app/issues/1286
2019-10-20 20:48:10 +02:00
Fredrik Fornwall
08b08d1c47 Bump version to 0.76 2019-10-20 20:16:48 +02:00
Fredrik Fornwall
c76cec186a Include bootstrap zips as shared libraries 2019-10-20 20:15:50 +02:00
Leon Omelan
5ba3f7cf6d Made Black UI an option to configure 2019-10-20 20:05:48 +02:00
Leon Omelan
468f878a38 Unified UI colors across the app. Dark sidebar and dark app theme for
dark Alert Dialogs
2019-10-20 20:05:48 +02:00
Fredrik Fornwall
c50a367063 Add .cxx folder to gitignore 2019-10-13 20:48:24 +02:00
Leonid Plyushch
4189f598b9 add permission ACCESS_NETWORK_STATE
Seems to be required by some Android TV devices.
2019-10-13 20:05:55 +02:00
mao
fdb3764f5c Optimize handle view 2019-10-11 07:37:57 +08:00
Leonid Plyushch
cb67e805de update readme 2019-10-07 15:32:21 +03:00
Leonid Plyushch
3f83368f42 CI: update .cirrus.yml 2019-10-07 15:01:22 +03:00
Leonid Plyushch
8c7462678f update readme 2019-10-07 00:21:57 +03:00
Leonid Plyushch
6f11390cc4 update readme 2019-10-07 00:20:33 +03:00
Leonid Plyushch
33d390a228 CI: development builds are now signed with publically-shared key
This commit adds keystore which is shared with community. People freely can
use it for creating own Termux app and plugin builds. However, builds signed
with such keystore cannot be proven as official.

Signed-off-by: Leonid Plyushch <leonid.plyushch@gmail.com>
2019-10-06 23:24:15 +03: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
Leonid Plyushch
0332779d6a update readme 2019-10-04 23:50:46 +03:00
Leonid Plyushch
f19575b942 readme: update status badge url 2019-10-04 02:16:22 +03:00
Leonid Plyushch
6a95809da3 CI: add test keystore 2019-10-04 02:08:44 +03:00
Leonid Plyushch
ac8dab70ef CI: add initial configuration for Cirrus 2019-10-04 01:59:49 +03:00
Leonid Plyushch
243285f7c2 CI: remove Travis setup 2019-10-04 01:59:49 +03:00
Fredrik Fornwall
b6182216d5 Update android gradle plug-in 2019-10-02 21:37:23 +02:00
Fredrik Fornwall
e206121bbc Bump version to 0.75 2019-09-08 21:43:52 +02:00
Fredrik Fornwall
deceffad00 Update gradle configuration 2019-09-08 21:43:07 +02:00
Leonid Plyushch
c19909cef1 improve url matching regex
Now it support complex URLs and some other schemes beyond just http/ftp.
2019-09-08 20:10:26 +02:00
Leonid Plyushch
5b7e40638c handle possible ActivityNotFoundException when requesting to disable battery optimizations
Related issue: https://github.com/termux/termux-app/issues/1224
2019-09-08 20:10:15 +02:00
Leonid Plyushch
a3673d1af5 extra keys: follow DnD mode and haptic feedback Android settings
Do not vibrate when:

 * Do not disturb mode is on.

 * Haptic feedback is disabled in Android settings.
2019-09-08 20:08:16 +02:00
Fredrik Fornwall
d5f9cf85c9 Bump version to 0.74 2019-08-18 22:11:21 +02:00
hungphd
549f09573f Change method name 'with' to 'getInstance' 2019-08-18 20:30:34 +02:00
Daniil Gentili
94e5bc86fb Ignore battery optimization settings to prevent suspension 2019-08-18 20:23:00 +02:00
Fredrik Fornwall
370ac2bd89 Avoid joining lines for shared transcript (#1166) 2019-08-04 19:42:36 +02:00
Fredrik Fornwall
7041f41981 Bump version to 0.73 2019-08-04 17:03:41 +02:00
Fredrik Fornwall
dd6a21257b Update the Android Gradle plugin 2019-08-04 16:44:58 +02:00
Fredrik Fornwall
445da0c4ea Fix problem with sharing large terminal transcript
Fixes #1166.
2019-07-14 20:26:16 +02:00
Sacha Chua
92980824b1 Add function keys F1 to F12 as keyboard options 2019-07-04 23:27:13 +02:00
Michal Bednarski
7d42b07a32 Also export ANDROID_TZDATA_ROOT
Since Android Q Beta 2 ANDROID_TZDATA_ROOT is required
in addition to ANDROID_RUNTIME_ROOT in order to run
`am` and `dalvikvm`
2019-07-04 23:26:55 +02:00
Fredrik Fornwall
e502b9169f Respect failsafe sessions in ACTION_EXECUTE 2019-06-09 20:00:17 +02:00
Leonid Plyushch
9f2d887ca0 minor fix for log message while removing tmpdir 2019-06-09 14:31:09 +03:00
Fredrik Fornwall
e6aacc5f08 Update Android Gradle plug-in 2019-05-27 14:07:50 +02:00
Fredrik Fornwall
ff935be54a Add versionName gradle task 2019-05-27 00:07:41 +02:00
Fredrik Fornwall
4e76162346 Bump version to 0.72 2019-05-27 00:03:42 +02:00
Leonid Plyushch
5fa4f2647b set BOOTCLASSPATH environment variable
Required on at least Android 5. Otherwise utilities that use dalvikvm may fail.

Issue happens on Nexus 7 with official 5.0/5.1 firmware and possibly other
AOSP ROMs.
2019-05-26 20:05:03 +02:00
Fredrik Fornwall
81b5889a26 Bump version to 0.71 2019-05-21 21:52:39 +02:00
Leonid Plyushch
514f59258a let $PREFIX/tmp be cleaned when TermuxService is going to be stopped 2019-05-21 21:52:14 +02:00
Leonid Plyushch
9f79393aa5 Revert "clean /tmp directory on cold start"
This reverts commit beb8a004e6.
2019-05-21 21:52:14 +02:00
Leonid Plyushch
e49d514236 Revert "sessions: do not clear TMPDIR if application was not started"
This reverts commit bd45837d93.
2019-05-21 21:52:14 +02:00
Leonid Plyushch
4e1462326c remove intent category "default"
It was needed only when Termux had separate icon for failsafe activity.
2019-05-21 21:52:14 +02:00
Fredrik Fornwall
b0f1773a92 Bump version to 0.70 2019-05-20 13:32:35 +02:00
Fredrik Fornwall
af9f28c010 Remove the failsafe activity
The failsafe activity were infrequently used while confusing users.

Replace it with an app shortcut on Android 7.1+ and a separate app on
earlier versions.
2019-05-20 13:32:03 +02:00
Nishith Khanna
fef0c66868 Tweak notification icon color to make it work with dark notifcations
As the notification icon and text is set to black, ROMs like Samsung OneUI and themes that change notifications to have a dark background will have visibility issues with Termux notifications. This commit sets a neutral color which will be visible on both white and dark backgrounds.
2019-05-20 13:27:58 +02:00
Fredrik Fornwall
7a5da83ce2 Bump version to 0.69 2019-05-12 00:18:02 +02:00
Fredrik Fornwall
97f01387b9 Update Android Gradle plugin 2019-05-12 00:17:16 +02:00
Jonas L
012ff83a06 Add limit for the bell (vibration)
This fixes https://github.com/termux/termux-app/issues/442
2019-05-12 00:13:54 +02:00
Michal Bednarski
a082c63849 Make am work on Android Q 2019-05-12 00:10:27 +02:00
Fredrik Fornwall
8c27084086 Bump version to 0.68 2019-04-22 00:54:27 +02:00
Fredrik Fornwall
f4fb45cbd6 Add the label attribute on TermuxActivity
Should fix the F-Droid naming of the app, see
https://github.com/termux/termux-app/issues/1107
2019-04-21 20:19:34 +02:00
Leonid Plyushch
0605fc4843 reformat res/values/strings.xml 2019-04-18 20:22:40 +03:00
Leonid Plyushch
3bbd61f9d7 extra keys view: fix crash in some cases
AndroidRuntime: FATAL EXCEPTION: main
AndroidRuntime: Process: com.termux, PID: 15799
AndroidRuntime: java.lang.NullPointerException: Attempt to invoke virtual method 'boolean android.widget.ToggleButton.isPressed()' on a null object reference
AndroidRuntime:        at com.termux.app.ExtraKeysView.readSpecialButton(ExtraKeysView.java:119)
AndroidRuntime:        at com.termux.app.TermuxViewClient.readControlKey(TermuxViewClient.java:116)
AndroidRuntime:        at com.termux.view.TerminalView.inputCodePoint(TerminalView.java:655)
AndroidRuntime:        at com.termux.view.TerminalView$2.sendTextToTerminal(TerminalView.java:336)
AndroidRuntime:        at com.termux.view.TerminalView$2.commitText(TerminalView.java:273)
AndroidRuntime:        at com.android.internal.view.IInputConnectionWrapper.executeMessage(IInputConnectionWrapper.java:341)
AndroidRuntime:        at com.android.internal.view.IInputConnectionWrapper$MyHandler.handleMessage(IInputConnectionWrapper.java:85)
AndroidRuntime:        at android.os.Handler.dispatchMessage(Handler.java:105)
AndroidRuntime:        at android.os.Looper.loop(Looper.java:164)
AndroidRuntime:        at android.app.ActivityThread.main(ActivityThread.java:6944)
AndroidRuntime:        at java.lang.reflect.Method.invoke(Native Method)
AndroidRuntime:        at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:327)
AndroidRuntime:        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1374)
2019-04-17 03:13:32 +03:00
Fredrik Fornwall
42fc0ea1cd Bump version to 0.67 2019-04-14 20:55:52 +02:00
Fredrik Fornwall
6218d08037 Use https://termux.org/bootstrap-${ARCH}.zip
This will redirect to the desired version of the bootstrap zip in
bintray.
2019-04-08 19:06:57 +02:00
Leonid Plyushch
10fe238ddb termux service: make sure that styling is always applied whenever session starts 2019-04-07 20:53:38 +02:00
Leonid Plyushch
fe41cd486f sessions: failsafe session is now accessible via separate launcher icon
Also enables session autoclosing so no more "annoying" messages
about "process completed - press enter". There autoclosing will be
performed on exit codes '0' and '130'.

On Android TV devices old behaviour will be used - auto close enabled for all
sessions when amount of running sessions >1.
2019-04-07 20:51:25 +02:00
Fredrik Fornwall
bda80547ad Do not export LD_LIBRARY_PATH if bintray is used 2019-04-03 23:20:08 +02:00
Fredrik Fornwall
70a786613d Install from bintray on Android 7 and later 2019-04-03 23:02:31 +02:00
Fredrik Fornwall
e4220a7ab1 Update the Android gradle plugin 2019-04-03 23:02:31 +02:00
Leonid Plyushch
bd45837d93 sessions: do not clear TMPDIR if application was not started 2019-02-24 22:01:31 +02:00
Leonid Plyushch
ddf5341b86 native text input: make text selection visible 2019-02-24 22:01:22 +02:00
Yuvraj Saxena
85a48a79a3 termux-app: Fix a typo in TermuxPreferences 2019-02-19 20:22:26 +02:00
Tom Yan
e3512c957d fix termux/termux-app#995 on the java side 2019-02-13 22:50:29 +01:00
Fredrik Fornwall
330301899a Send \r instead of \n from native input text view (fixes #1020) 2019-02-09 23:14:44 +01:00
iamahuman
2a36b915cb Implement CSI 3 J - Clear transcript 2019-02-05 23:27:25 +01:00
Leonid Plyushch
c986a46fb9 extra keys: fix crash when clicking on "empty" (undefined) key 2019-02-05 23:26:08 +01:00
Fredrik Fornwall
ed8bd4b569 Update gradle wrapper 2019-02-03 22:38:55 +01:00
Fredrik Fornwall
7f4df8328c Toggle the extra keys view with VolumeUp+K
Using K as a shortcut for toggling extra Keyboard probably makes more
sense than VolumeUp+Q, although we support both for now.
2019-01-21 00:52:24 +01:00
Fredrik Fornwall
ad1ce585d9 Bump version to 0.66 2019-01-21 00:37:39 +01:00
Fredrik Fornwall
158908a3bf Keep help context menu item as last item 2019-01-21 00:34:02 +01:00
Fredrik Fornwall
d8f066dec4 Minor restructuring of TermuxPreferences 2019-01-21 00:33:03 +01:00
Leonid Plyushch
743225ab6a feature: allow to keep screen on 2019-01-21 00:24:49 +01:00
Fredrik Fornwall
b38ae24908 Make termux-reload-settings reload the extra keys directly 2019-01-21 00:12:07 +01:00
Fredrik Fornwall
70c1bddae0 Use constant for utf-8 encoding 2019-01-20 23:58:04 +01:00
Fredrik Fornwall
4df285013e Use constant for utf-8 encoding 2019-01-20 23:50:49 +01:00
Fredrik Fornwall
1e4fa8c045 Update the android gradle plugin 2019-01-20 23:49:03 +01:00
Fredrik Fornwall
31f2dc35bc Fix Android Studio lint warning 2019-01-20 23:48:23 +01:00
Henrik Grimler
fbb048ce56 Add issue templates
Hopefully gives less package bug reports opened in termux-app
2019-01-20 23:46:20 +01:00
Fredrik Fornwall
8c3a30027e Migrate to AndroidX 2019-01-11 21:19:06 +01:00
Fredrik Fornwall
a8c270f3c1 Use size in icon name in big icon 2019-01-11 00:36:47 +01:00
Trygve Aaberge
829414ac19 Reduce the height of viewpager/ExtraKeysView to previous height
Commit f0eeb47 introduced configurable extra keys and thus changed the
height of the viewpager to be multipled by the number of rows of extra
keys. However, the extra keys had two rows at the time, so the initial
height should have been changed to half of what it was now that it is
multiplied. This was not done, so the viewpager was twice the height it
should be after that change.
2019-01-11 00:32:22 +01:00
Fredrik Fornwall
8e0b8623bc Update art/copy-to-other-apps.sh 2019-01-10 23:58:18 +01:00
Fredrik Fornwall
e2bda2bb52 Add art/generate-big-icon.sh script 2018-11-23 02:22:38 +01:00
Fredrik Fornwall
8c7b1a60d2 Minor icon tweaks 2018-11-23 02:12:57 +01:00
Fredrik Fornwall
cb0710bb83 Use a solid color as background for adaptive icon 2018-11-17 23:34:14 +01:00
Fredrik Fornwall
b54c8276b4 Add .swp to gitignore 2018-11-17 23:26:26 +01:00
Fredrik Fornwall
7056945f5d Move to vector icon also on pre-26 2018-11-17 23:19:22 +01:00
Fredrik Fornwall
063ef02f2b Let the installer create directories when necessary
By creating directories when necessary before trying to install files we
depend on less details in how the bootstrap zip is constructed.
2018-11-17 22:50:27 +01:00
Fredrik Fornwall
6e6e4fd212 Use vector drawables for the launcher icon 2018-11-14 00:21:59 +01:00
Fredrik Fornwall
406438e391 Fix Android Studio lint warnings 2018-11-10 23:16:54 +01:00
Fredrik Fornwall
d1977479bd Update the Android Gradle plugin 2018-11-10 23:16:27 +01:00
Fredrik Fornwall
b4563023f6 Add casts in termux.c to silence lint warning 2018-11-08 22:31:06 +01:00
Fredrik Fornwall
095ed8b54f Use jint as return value from native function
This silences an error from the Android Studio lint.
2018-11-08 22:28:37 +01:00
Markus Gräb
af445f9618 Improve config parsing 2018-10-25 23:31:38 +02:00
Fredrik Fornwall
82f977fbf1 Remove unused function 2018-10-07 20:08:41 +02:00
Fredrik Fornwall
4b52cfbb93 Drop the round launcher icon
It's better to focus on the adaptive icon for the future. See #830.
2018-10-02 21:54:03 +02:00
Fredrik Fornwall
b61da23be7 Enable java 8 2018-09-29 00:49:05 +02:00
Fredrik Fornwall
35df3f72c9 Revert to java 8 for travis build 2018-09-29 00:32:43 +02:00
Fredrik Fornwall
d584502fef Update .travis.yml 2018-09-29 00:27:25 +02:00
Fredrik Fornwall
a691333c5e Update the gradle wrapper 2018-09-29 00:25:24 +02:00
Fredrik Fornwall
e053cf82ca Update the Android Gradle plugin 2018-09-29 00:22:44 +02:00
Fredrik Fornwall
076176a5f6 Add whole of .idea/ to .gitignore 2018-09-29 00:22:22 +02:00
Fredrik Fornwall
08c72f9a65 Remove .idea/ folder 2018-09-29 00:17:25 +02:00
Fredrik Fornwall
7593ddde38 Update README.md 2018-09-23 16:57:58 +02:00
Matthew Klein
6dce32aece Use 2 spaces for yaml files (.travis.yml) 2018-08-30 13:51:24 +02:00
Matthew Klein
5ff801c914 Use sdkmanager to download ndk-bundle 2018-08-29 13:54:09 +02:00
Matthew Klein
221046732b Update travis to android 28
This bumps build-tools from 27.0.3 to 28.0.2.
2018-08-29 13:54:09 +02:00
Fredrik Fornwall
07d6c9bd5e Remove unused import 2018-08-28 02:51:11 +02:00
Fredrik Fornwall
a9eddce672 compileSdkVersion = targetSdkVersion = 28 2018-08-28 02:50:52 +02:00
Leonid Plyushch
26c95f1397 reduce padding to 3dp (5 dp seems too many) 2018-08-28 02:39:41 +02:00
Leonid Plyushch
4eca639212 add some padding for TerminalView
Should fix https://github.com/termux/termux-app/issues/516
2018-08-28 02:39:41 +02:00
Leonid Plyushch
beb8a004e6 clean /tmp directory on cold start 2018-08-28 02:37:03 +02:00
Robert Vanden Eynde
3693e3c1b6 Default extra-keys has TAB after ESC and UP/DOWN arrow keys 2018-08-28 02:34:36 +02:00
Robert Vanden Eynde
99e8ffcf90 Add aliases for BACKSLASH QUOTE and APOSTROPHE 2018-08-28 02:34:36 +02:00
Henrik Grimler
0807600a2d ExtraKeys: use ' instead of \" in default key string 2018-08-28 02:34:36 +02:00
Robert Vanden Eynde
f74293e8fb Use extra-keys spelling and correct default setting 2018-08-28 02:34:36 +02:00
Henrik Grimler
b99d092305 ExtraKeys: Prevent app crash if user specifies different row lengths 2018-08-28 02:34:36 +02:00
Robert Vanden Eynde
af7515247b Fix refactoring, Ctrl, Alt, Fn keys work again 2018-08-28 02:34:36 +02:00
Henrik Grimler
ec77be00dc state -> SpecialButtonState state 2018-08-28 02:34:36 +02:00
Henrik Grimler
a854960476 Declare buttonText final String, dont't change value inside reload 2018-08-28 02:34:36 +02:00
Henrik Grimler
9db8948f23 Fix typos and build errors 2018-08-28 02:34:36 +02:00
Robert Vanden Eynde
c24167f6a5 Use utf-8 for config, Activate defaultCharDisplay, Fix Typo 2018-08-28 02:34:36 +02:00
Robert Vanden Eynde
49c051c8b7 Fix Typo (KeyEvent.KEYCODE_DEL and others) 2018-08-28 02:34:36 +02:00
Robert Vanden Eynde
55efdb2f56 Refactor Ctrl, Alt, Fn code 2018-08-28 02:34:36 +02:00
Robert Vanden Eynde
d03e420e75 Use LEFT instead of arrow keys in config, and include arrows characters for special keys (will be easily changed in the properties after) 2018-08-28 02:34:36 +02:00
Henrik Grimler
06968a9295 Extrakeys: fix typo 2018-08-28 02:34:36 +02:00
Henrik Grimler
244248b1a0 Extrakeys: make ― the same as - in some more places 2018-08-28 02:34:36 +02:00
Henrik Grimler
7187ed6950 Extrakeys: fix rebase error 2018-08-28 02:34:36 +02:00
Henrik Grimler
b3eabd9bad ExtraKeys: fix so app doesn't crash if ctrl/alt aren't in extrakeys
Otherwise we get:
AndroidRuntime: java.lang.NullPointerException: Attempt to invoke virtual method 'boolean android.widget.CompoundButton.isChecked()' on a null object reference
AndroidRuntime:        at com.termux.app.ExtraKeysView.b(SourceFile:128)
2018-08-28 02:34:36 +02:00
Henrik Grimler
b51dd4f558 Extrakeys: show extra key row per default
Might help new users.
2018-08-28 02:34:36 +02:00
Henrik Grimler
f88b9c4629 ExtraKeys: add possibility for insert, delete, enter and - 2018-08-28 02:34:36 +02:00
neverwin
f0eeb4781b add support of configurable extra keys 2018-08-28 02:34:36 +02:00
Fredrik Fornwall
57a3a9b111 Request the FOREGROUND_SERVICE permission
From https://developer.android.com/guide/components/services:

"Apps that target Android 9 (API level 28) or higher and use foreground
services must request the FOREGROUND_SERVICE permission. This is a
normal permission, so the system automatically grants it to the
requesting app.

If an app that targets API level 28 or higher attempts to create a
foreground service without requesting FOREGROUND_SERVICE, the system
throws a SecurityException."
2018-08-26 01:07:33 +02:00
Fredrik Fornwall
8df73a3006 Set targetSdkVersion 28 2018-08-14 01:07:51 +02:00
Leonid Plyushch
963663e0cd close sessions without waiting for user input except the last one
An attempt to deal with these issues:

 * https://github.com/termux/termux-app/issues/627
 * https://github.com/termux/termux-app/issues/56
2018-08-11 23:09:16 +02:00
Lokesh Krishna
68a83ccf37 replaces foreground and background layers with nord color variants 2018-08-06 21:53:30 +02:00
Lokesh Krishna
db13ea02b6 Adds foreground and background layers 2018-08-02 00:41:18 +02:00
Lokesh Krishna
61a44dbfa8 Adds alternative drawable resource 2018-08-02 00:41:18 +02:00
Fredrik Fornwall
aaa92279ca Bump version to 0.65 2018-08-02 00:38:21 +02:00
Ico Doornekamp
365f9723cc - in onKeyDown() ignore keys when the Fn key is pressed
- in onKeyDown() return false when the key is not handled.

The above two changes fix the handling of Fn-key combo's on devices with
a physical keyboard, allowing the android system defined fallbacks from
`/system/usr/keychars/Generic.kcm` to be properly handled.

Fixes #731.

Original diagnosis and fix by Konehaltia.
2018-08-02 00:36:44 +02:00
Fredrik Fornwall
fdae272214 Bump version to 0.64 2018-07-02 00:05:04 +02:00
Leonid Plyushch
b7864d6ac2 deleteFolder(): check if passed argument is a symlink
Prevents possible data loss when user replaced directory '~/storage' with
a symlink.
2018-07-01 18:00:54 +02:00
Peter Vágner
d1f0c76db3 TerminalView: only use accessibility features when accessibility is
enabled when starting the view
2018-06-29 12:31:15 +02:00
Peter Vágner
5652624fc2 Call setContentDescription in onScreenUpdated rather than in onDraw.
That will be much less expensive.
2018-06-29 12:31:15 +02:00
Peter Vágner
35a9101f84 terminalview: add contentDescription to the view so accessibility
services can get the text currently being shown.
2018-06-29 12:31:15 +02:00
David xu
2b6a10712b fix button background and row height bug in api 21 2018-06-29 12:30:02 +02:00
Matthew Klein
c80e126b8f Add a .gitattributes file 2018-06-28 11:57:12 +02:00
Fredrik Fornwall
89048274dd Bump version to 0.63 2018-06-25 14:08:50 +02:00
Leonid Plyushch
d3b4d35b2a deleteFolder(): don't treat symlinks as directory 2018-06-25 14:08:28 +02:00
Fredrik Fornwall
4080f41cc7 Bump version to 0.62 2018-06-24 01:56:43 +02:00
Tom Yan
b4e2c99d46 Stop exporting PS1
PS1 is not supposed to be an env var and should be shell-specific.
We will set it appropriately with the init files of the shells.
2018-06-22 01:11:51 +02:00
Quasic
c5923201a4 fix indent with vim on termux
web interface would have worked, if I had deleted the tabs, first
This vim is adding tabs instead of matching the previous line's
indentation. Looking into fixing that now...
2018-06-22 00:33:35 +02:00
Quasic
bafd21bb39 fmt indentation 2018-06-22 00:33:35 +02:00
Quasic
0f20fab02c I think this is it 2018-06-22 00:33:35 +02:00
Quasic
c5ae5bb06a oops, should have local Java 2018-06-22 00:33:35 +02:00
Quasic
bbd46a763c Fix for #572 using recursive delete
Also see #578
2018-06-22 00:33:35 +02:00
David xu
dc145d65f8 minify long press interval 2018-06-22 00:29:51 +02:00
David xu
ce2d1c0d88 restore terminalview 2018-06-22 00:29:51 +02:00
David xu
f2f7f963e6 feat for - ― 2018-06-22 00:29:51 +02:00
David xu
2e53ef038e fix popup bug 2018-06-22 00:29:51 +02:00
xqdoo00o
8c82f43dce delete unnecss code 2018-06-22 00:29:51 +02:00
david
3a16f461e7 add symbol popup 2018-06-22 00:29:51 +02:00
xqdoo00o
594a5dfe25 comment unnecss code 2018-06-22 00:29:51 +02:00
david
d5e007dbb3 feat button color 2018-06-22 00:29:51 +02:00
david
f4335d3824 add some functional keys 2018-06-22 00:29:51 +02:00
Michał Bednarski
90b6f93697 Add fast path for TerminalRow.setChar
termux/termux-app#603
2018-06-22 00:21:31 +02:00
Fredrik Fornwall
17c4a45212 Bump gradle dependencies 2018-06-15 00:22:52 +02:00
Fredrik Fornwall
6d9ffb6922 Bump version to 0.61 2018-06-15 00:21:27 +02:00
Fredrik Fornwall
8472fce8ba Use lower when looking up mime type from extension
Fixes #721.
2018-06-15 00:19:47 +02:00
Fredrik Fornwall
69d954a583 Update gradle configuration 2018-06-14 23:58:41 +02:00
easyaspi314 (Devin)
ec1087d56f Replace CRLF with CR as well.
This should replace both \r\n and \n with \r now.

"\r?\n" matches 0 or 1 \r and one \n, which should capture both
escape sequences.
2018-06-13 22:45:37 +02:00
easyaspi314 (Devin)
be6a73d862 Replace \n with \r when pasting (Fixes #678)
Termux will now properly send \r to the terminal instead of \n when pasting
multiline strings.

This fixes cat not repeating back lines and nano accidentally justifying
text (because \n maps to ^J), as well as other potential issues.

This matches the behavior of other terminals, such as iTerm2 which explicitly
does it here:
https://github.com/gnachman/iTerm2/blob/f8a5930/sources/iTermPasteHelper.m#L113

Signed-off-by: easyaspi314 (Devin) <easyaspi314@users.noreply.github.com>
2018-06-13 22:45:37 +02:00
l-jonas
80c81b274d Update build.gradle 2018-06-11 12:26:35 +02:00
Jonas L
cd9dbac548 Add Google Repository
The Google-Repository is required for compilation because some dependencies were removed from jcenter()
2018-06-11 12:26:35 +02:00
Fredrik Fornwall
0c837796f0 Update Android plugin for Gradle 2018-06-11 12:23:52 +02:00
Fredrik Fornwall
3417e37e8d Update gitignore for Android Studio 3.1 2018-04-02 01:38:47 +02:00
Fredrik Fornwall
31ba36e0fa Remove explicit buildToolsVersion
https://developer.android.com/studio/releases/gradle-plugin.html#behavior_changes
2018-04-02 00:58:02 +02:00
Fredrik Fornwall
c444f1fd28 Update gradle configuration 2018-04-02 00:40:16 +02:00
RangerMauve
9f36ed06b8 Make TerminalRenderer public
I want to use the renderer with a custom canvas without having to render to an Android View which requires a context and all sorts of stuff.

Can't currently do that because the renderer is package-private
2018-03-10 22:55:10 +01:00
Fredrik Fornwall
c2ab5bcd50 Bump some dependency versions 2018-02-06 23:30:28 +01:00
Fredrik Fornwall
48fab33b79 Bump version to 0.60 2018-01-26 01:00:37 +01:00
Marcel Dopita
8d7a67645b Fix notification icon color
Icons used in notifications/status bar are monochrome and should use only white color and transparency. It is displayed fine on Android emulators but it's shown in black on Samsung Galaxy S7 running Android 7.0.

After the change, status bar icon is white and the small icon in notification still uses the black color set via setColor(). Termux doesn't support devices running Android older than 5.0 but using white color in vector drawables would be also necessary to generate proper (white) bitmap icons.
2018-01-01 23:17:22 +01:00
Fredrik Fornwall
186b49d429 Work around travis API 27 SDK checksum mismatch
See https://github.com/travis-ci/travis-ci/issues/8874.
2018-01-01 23:16:43 +01:00
Fredrik Fornwall
40a2775f52 Bump version to 0.59 2017-12-09 00:59:19 +01:00
Fredrik Fornwall
f802d6001d Do not send mouse clicks out of range (fixes #501) 2017-12-09 00:58:31 +01:00
Fredrik Fornwall
50f66a12da Bump version to 0.58 2017-12-08 23:32:44 +01:00
Fredrik Fornwall
d8e6fd21d1 Set android:resizeableActivity="true"
Setting android:resizeableActivity="true" should AFAIK not be needed
as https://developer.android.com/guide/topics/ui/multi-window.html
states that:

  If your app targets API level 24, but you do not specify a value
  for this attribute, the attribute's value defaults to true.

However, on a Galaxy S8 running Android 7.0 this attribute is needed
in order to have resizeable windows when in Samsung Dex mode.

Fixes #309.
2017-12-08 23:30:45 +01:00
Fredrik Fornwall
0964d83572 Bump com.android.support dependencies 2017-12-07 10:25:02 +01:00
Fredrik Fornwall
cee0466dd7 Bump version to 0.57 2017-12-07 01:48:51 +01:00
Fredrik Fornwall
de4f334e24 Remove the fullscreen setting
Trying to use fullscreen doesn't work well in a multi-windowed world
and makes using translucent navigation and status bars more complicated.
2017-12-07 01:46:16 +01:00
Fredrik Fornwall
ab205ae05b Update build tools in travis 2017-12-07 01:45:30 +01:00
Fredrik Fornwall
b29b24f507 Add android.max_aspect=10.0 metadata
This is needed to mark the app with

	"This app is optimized to run in full screen"

when run on a Samsung Galaxy S8.
2017-12-07 01:39:43 +01:00
Fredrik Fornwall
b3472e9e62 Disable the "quick scale" gesture
Disable the default android behaviour where a double tap followed by a
swipe is interpreted as a zoom gesture. Most people seem not to know
about it and hit it by mistake, see #495 for an example.
2017-12-07 01:36:29 +01:00
Fredrik Fornwall
ab59e08959 Update build tools 2017-12-07 01:12:39 +01:00
Fredrik Fornwall
8b566485e8 Do not clear the sessions on ACTION_STOP_SERVICE
There is no point doing so and it may cause problems with the list
adapter.
2017-11-26 00:56:13 +01:00
Fredrik Fornwall
231c02a0c7 Check for null mTermSession in inputCodePoint() 2017-11-26 00:50:13 +01:00
Fredrik Fornwall
65f30e77ba Add tools:ignore="AppLinkUrlError" 2017-11-25 22:59:03 +01:00
Fredrik Fornwall
686677ae45 Update android support dependencies 2017-11-25 22:58:46 +01:00
Fredrik Fornwall
85037a75a6 Remove redundant cast 2017-11-25 22:58:23 +01:00
Fredrik Fornwall
6025afc2c0 Update the Android Plugin for Gradle to 3.0.1 2017-11-24 23:11:55 +01:00
Fredrik Fornwall
a4e4f76775 Remove some Android Studio warnings 2017-11-22 01:27:14 +01:00
Fredrik Fornwall
02af113dda Bump app version to 0.56 2017-11-19 20:54:16 +01:00
Fredrik Fornwall
fc4ef838bf Configure proguard to keep line numbers 2017-11-19 20:53:56 +01:00
Fredrik Fornwall
86bd3ca21b Bump app version to 0.54 2017-11-19 15:27:17 +01:00
Fredrik Fornwall
367398bafb Update gradle configuration 2017-11-19 15:27:00 +01:00
Lauri Tirkkonen
dd502e55f8 set default notification priority to low
since we're a foreground service, in oreo we already get a higher
priority; see
https://developer.android.com/reference/android/app/NotificationManager.html#IMPORTANCE_MIN
2017-11-12 23:44:14 +01:00
Fredrik Fornwall
798125ef7a Remove outdated comment 2017-11-12 23:06:28 +01:00
Fredrik Fornwall
5126f06e06 Update .travis.yml to latest setup 2017-11-12 22:30:09 +01:00
Fredrik Fornwall
0bdbf314ef Update gradle configuration 2017-11-07 04:25:48 +01:00
Fredrik Fornwall
2deb9899bd Update buildToolsVersion to 27.0.1 2017-11-07 04:13:26 +01:00
Fredrik Fornwall
694ccc38c4 Remove WakefulBroadcastReceiver usage 2017-11-04 23:38:49 +01:00
Fredrik Fornwall
c949940374 Update support deps 2017-11-02 01:56:42 +01:00
Fredrik Fornwall
d09f70de1e Use a notification channel on Android O 2017-11-01 21:15:41 +01:00
Fredrik Fornwall
ac338ce2c5 Update gradle to 4.3 2017-10-31 19:07:00 +01:00
Fredrik Fornwall
092a83a688 Validate file path in TermuxOpenReceiver 2017-10-28 16:19:58 +02:00
Fredrik Fornwall
3533b13de8 Get rid of two android studio warnings 2017-10-28 11:05:43 +02:00
Fredrik Fornwall
d68a0f05be Update gradle configuration 2017-10-28 11:05:43 +02:00
Fredrik Fornwall
9e5293a08e Do not scroll when below bottom margin 2017-10-28 11:05:43 +02:00
Fredrik Fornwall
3f04a0a0d5 Bump build tools version in .travis.yml 2017-10-28 11:05:43 +02:00
Fredrik Fornwall
e558f824c5 Update gradle configuration 2017-10-28 11:05:43 +02:00
Auxilus
8ff72ddd8b replace help url with wiki url
HELP page wiki changed to https://wiki.termux.com/wiki/Main_Page as it now has more info than http://termux.com/help.html
2017-09-26 18:14:19 +02:00
Fredrik Fornwall
3e05356a69 Merge pull request #421 from sdrausty/master
Added two important links to README.md
2017-09-20 22:37:49 +02:00
Fredrik Fornwall
82673d3f82 Update gradle configuration 2017-09-17 11:47:12 +02:00
Fredrik Fornwall
f2757fdb76 Merge pull request #424 from EdwardBetts/patch-1
correct spelling mistake
2017-09-15 00:21:47 +02:00
Edward Betts
81fb669d0e correct spelling mistake 2017-09-13 16:16:08 +01:00
S D Rausty
4a45b1b617 Commit on 20170911 at 13:23:22 and at 1505150602 in Unix time. 2017-09-11 13:23:22 -04:00
S D Rausty
62b08b3cf1 Merge pull request #1 from termux/master
Hello
2017-09-10 13:24:32 -04:00
Fredrik Fornwall
1e83ea3151 Merge pull request #412 from Auxilus/patch-2
Added wiki link
2017-09-06 16:26:54 +02:00
Auxilus
9c42fdb3d6 Added wiki link 2017-09-05 10:43:30 +05:30
Fredrik Fornwall
42e3163e92 Update gradle to 4.1 2017-08-24 20:49:43 +02:00
Fredrik Fornwall
68912139f6 Additional REP test 2017-08-23 23:29:59 +02:00
Fredrik Fornwall
edc92bbcbb Start supporting the REP escape sequence 2017-08-13 16:14:31 +02:00
Fredrik Fornwall
d7520642de Update gradle 2017-07-29 03:05:48 +02:00
Fredrik Fornwall
adae111d5c Update gradle config 2017-07-09 21:07:18 +02:00
Fredrik Fornwall
dc9272790b Merge pull request #301 from kzlin129/android-things-auto-launch
[Android-things] Launch TermuxActivity automatically on boot
2017-06-26 02:04:03 +02:00
Fredrik Fornwall
05ea2ea238 Bump version to 0.53 2017-06-26 01:30:08 +02:00
Fredrik Fornwall
ad293562bc Fix possible crash in the copy/paste/more dialog 2017-06-26 01:29:05 +02:00
Fredrik Fornwall
f9a565d1e0 Ignore C1 control codes
C1 control codes are not used nowadays and just risks messing up
the terminal when they are used by accident.
2017-06-26 01:28:18 +02:00
Will Lin
5b3909cb73 Update AndroidManifest.xml with expanded comment 2017-06-17 15:39:23 +08:00
Fredrik Fornwall
7913e765d5 Bump version to 0.52 2017-06-13 16:44:33 +02:00
Fredrik Fornwall
ed3a3269d8 Merge pull request #339 from bumper314/master
Add Multi Window support for Samsung devices
2017-06-13 10:13:26 +02:00
Steven Audette
dc3994d2cf Add Multi Window support for Samsung devices
A single meta-data entry is enough to make Termux "Multi Window" aware on supported Samsung devices.
Tested and confirmed working on Samsung Note Pro 12.2 running Android 5.1.1.

This addition should not interfer with Android 6+'s split screen feature.
Tested and confirmed split screen functions as normal on a Nexus 6 running Android 7.0.

This addition should go unnoticed on non-Smasung Android 5 devices.
Tested and confirmed no functionality visible on Nexus 5 running Android 5.1.
2017-06-12 01:47:27 -06:00
Fredrik Fornwall
c972377bd1 Update gradle config 2017-06-11 22:47:59 +02:00
Fredrik Fornwall
d4be782c03 Bump version to 0.51 2017-06-05 22:49:11 +02:00
Fredrik Fornwall
c29909726c Bump terminal-emulator libraryVersion to 0.50 2017-06-05 22:45:44 +02:00
Fredrik Fornwall
45bac89298 Remove unused xmlns:android declaration 2017-06-05 22:45:15 +02:00
Fredrik Fornwall
c06770c353 Merge pull request #330 from whydoubt/cursor_preserve_color
Make cursor save/restore affect color attributes
2017-06-05 22:35:39 +02:00
Fredrik Fornwall
52a627efc8 Merge pull request #331 from whydoubt/fix_parameter_fc
Fix FC from too many control code parameters
2017-06-05 22:35:32 +02:00
Jeff Smith
2e9383720c Make cursor save/restore affect color attributes
SGR attributes are stored in three variables: mEffect, mForeColor, and
mBackColor.  Saving/restoring the cursor only preserves mEffect.

Change the cursor save/restore methods to additionally preserve
mForeColor and mBackColor.  This affects both 'explit' saving/restoring
the cursor and switching to/from the alternate screen buffer.
2017-06-03 17:45:36 -05:00
Jeff Smith
58f9f1be71 Add tests for cursor save/restore 2017-06-03 17:45:36 -05:00
Jeff Smith
058441dda6 Fix FC from too many control code parameters
When the number of parameters in a CSI control code exceeds the size of
the mArgs array, the code may attempt to read past the end of the array,
resulting in a force close of the app.

Stop the index from moving past the last element of the mArgs array.
2017-06-03 00:15:09 -05:00
Jeff Smith
888802a519 Add test for CSI followed by many parameters 2017-06-03 00:14:20 -05:00
Fredrik Fornwall
1a09b6d2a6 Change android:extractNativeLibs to false
We're trying this to see if that fixes an F-Droid build issue.

See #319.
2017-05-18 13:25:41 +02:00
Fredrik Fornwall
9d3f7734a1 Update android build tools 2017-05-16 11:11:57 +02:00
Fredrik Fornwall
61f766b59f Bump build tools in .travis.yml 2017-05-16 00:21:15 +02:00
Fredrik Fornwall
3f08376881 Bump version to 0.49 2017-05-15 23:42:55 +02:00
Fredrik Fornwall
cf06e70429 Update gradle configuration 2017-05-15 23:42:30 +02:00
Fredrik Fornwall
0714e435cb Do not show a Toast on clipboard text
Fixes https://github.com/termux/termux-app/issues/149.
2017-05-15 23:42:13 +02:00
Fredrik Fornwall
c26315185f Export TMPDIR to $PREFIX/tmp
Fixes https://github.com/termux/termux-packages/issues/1010.
Fixes https://github.com/termux/termux-app/issues/306.
2017-05-15 23:40:55 +02:00
Will Lin
1be5139253 [Android-things] Launch TermuxActivity automatically on boot 2017-04-18 20:13:47 +08:00
Fredrik Fornwall
adc43c40c5 Perform haptic feedback on extra keys
Fixes #269.
2017-04-16 12:18:13 +02:00
Fredrik Fornwall
779b1ca1f8 Update the Android gradle plugin to 2.3.1 2017-04-05 09:10:54 +02:00
Fredrik Fornwall
e375f99b0c Remove float/ module from .idea/gradle.xml 2017-04-05 09:09:48 +02:00
Fredrik Fornwall
0ac55cd77a Remove float/
The Termux:Float app has a new home at:

https://github.com/termux/termux-float
2017-04-04 23:39:05 +02:00
Fredrik Fornwall
4aefa5049b Add gradle setup to publish to jcenter 2017-04-04 23:31:00 +02:00
Fredrik Fornwall
5b14124258 Make GestureAndScaleRecognizer non-public 2017-04-04 20:56:19 +02:00
Fredrik Fornwall
1a9c38374c Fix some invalid html in javadoc 2017-04-02 15:11:56 +02:00
Fredrik Fornwall
eefd504f08 Use getLocationOnScreen() to calculate position
This avoids problem when the window has been prevented from scrolling
outside of the screen, or when the touch keyboard has made it move.
2017-04-02 14:46:47 +02:00
Fredrik Fornwall
19f838e3d1 Show window if launched when hidden 2017-04-02 14:46:00 +02:00
Fredrik Fornwall
63adb2b132 Rename TerminalKeyListener to TerminalViewClient 2017-04-02 14:25:34 +02:00
Fredrik Fornwall
7d3a988d2f Restructure library modules
terminal/ -> terminal-emulator/
view/ -> terminal-view/
2017-04-02 14:21:36 +02:00
Fredrik Fornwall
5cd705d6d1 Reformat all code in float/, getting rid of tabs 2017-04-02 14:10:26 +02:00
Fredrik Fornwall
af8b269b51 Work on Termux:Float input handling 2017-04-02 14:09:43 +02:00
Fredrik Fornwall
8c220eaaea Do not call long press listener when scaling 2017-04-02 14:07:19 +02:00
Fredrik Fornwall
0142e1ed0e Update Termux:Float launcher icon 2017-04-02 07:33:06 +02:00
Fredrik Fornwall
ac0a349ed9 Remove versionCode&versionName from libraries 2017-04-02 07:12:03 +02:00
Fredrik Fornwall
548b0916b6 Clean up {terminal,view}/build.gradle 2017-04-02 06:58:59 +02:00
Fredrik Fornwall
009de5a3ee Split up into modules and add float module
Split the app/ module into three modules

terminal/ - Terminal emulator library module.
view/ - Terminal view library module (depending on terminal/).
app/ - The main Termux app (depending on view/).

Also add the

float/ - The Termux:Float app (depending on view/).
2017-04-01 19:06:02 +02:00
Fredrik Fornwall
41d0d60017 Respect content type termux-open for url:s 2017-03-28 23:52:52 +02:00
Fredrik Fornwall
12ac0fa73c Add android:extractNativeLibs="false" to manifest 2017-03-12 20:55:58 +01:00
Fredrik Fornwall
835dfc0276 Avoid synthetic accessor method 2017-03-06 01:53:09 +01:00
Fredrik Fornwall
f60316835f Fix some android studio lint warnings 2017-03-06 01:47:08 +01:00
Fredrik Fornwall
f74a091be6 Remove useless continue statement 2017-03-06 01:44:16 +01:00
Fredrik Fornwall
dd6cb5221d Work around Android < 7.0 wifi manager leak
http://tools.android.com/tech-docs/lint-in-studio-2-3#TOC-WifiManager-Leak
2017-03-06 01:42:33 +01:00
Fredrik Fornwall
cab6df5c0e Update gradle config 2017-03-06 01:41:12 +01:00
Fredrik Fornwall
f6f0809558 Bump version to 0.48 2017-03-02 00:32:09 +01:00
Fredrik Fornwall
11ed7e45d8 Fix crash when opening URL:s in Android < 7.0
In versions of Android earlier than 7.0 the FLAG_ACTIVITY_NEW_TASK
is needed when starting an activity from a non-activity context.

This was removed in Android 7.0 (possibly by mistake), see
https://code.google.com/p/android/issues/detail?id=226647.

Fixes #802
2017-03-02 00:29:17 +01:00
Fredrik Fornwall
ed1874db05 Bump version to 0.47 2017-02-28 00:47:16 +01:00
Fredrik Fornwall
cb60803a80 Tweak launcher size 2017-02-28 00:46:21 +01:00
Fredrik Fornwall
fc92a27cb2 Extended keyboard: allow inline input
Ff the buffer is empty, send a newline, otherwise send the content of
the buffer with the newline stripped. This way <Enter><Enter> means
"insert the buffer content and send a newline", while a single <Enter>
means just "insert the buffer content". Fixes #261.
2017-02-28 00:42:36 +01:00
Fredrik Fornwall
29e62e608f Use standard actions 2017-02-28 00:42:07 +01:00
Fredrik Fornwall
8a7f93d722 Launch $PREFIX/bin/login by default
The login command takes care of both showing the etc/motd and
launching the login shell.
2017-02-28 00:41:20 +01:00
Fredrik Fornwall
420683fe65 Add built-in broadcast receiver to open files 2017-02-27 22:31:11 +01:00
Fredrik Fornwall
a3256ed551 Travis testing 2017-02-05 22:25:37 +01:00
Fredrik Fornwall
57add98e3c More attempts to get coverity scan working 2017-02-05 21:44:47 +01:00
Fredrik Fornwall
6e5c04e04f Remove the travis certificate workaround 2017-02-05 18:24:59 +01:00
Fredrik Fornwall
528a05ef61 Remove the welcome dialog
Instead of a modal dialog to be dismissed without any way to recall
it, the initial information will be shown in the terminal itself.
2017-02-04 11:31:04 +01:00
Fredrik Fornwall
cb2f892dc5 Adjust text placement on feature graphics 2016-12-30 03:23:53 +01:00
Fredrik Fornwall
8b6e8d7fdd Bump version to 0.46 2016-12-30 02:43:40 +01:00
Fredrik Fornwall
d0eeaa9fc3 Call completeWakefulIntent() in Termux service 2016-12-30 02:42:41 +01:00
Fredrik Fornwall
b793913481 Style for android tv launcher transition 2016-12-30 02:39:26 +01:00
Fredrik Fornwall
d48b438c40 Test wcwidth on two more code points 2016-12-30 02:39:01 +01:00
Fredrik Fornwall
11afe895e1 Try with a bit shorter process completed messages 2016-12-30 02:38:41 +01:00
Fredrik Fornwall
bc96f71a2d Update the tv banner a bit 2016-12-30 02:34:43 +01:00
Fredrik Fornwall
87dfded5e6 Remove unused on_bell.xml file 2016-12-30 01:54:31 +01:00
Fredrik Fornwall
7c0ae4cb54 Remove the bell shake animation
Fixes https://github.com/termux/termux-packages/issues/628
Fixes https://github.com/termux/termux-app/issues/222
2016-12-30 01:53:07 +01:00
Fredrik Fornwall
b917acbbfa Do not use IME_ACTION_NONE in the inputconnection
Using IME_ACTION_NONE prevents enter key to be used with the stock
Android TV keyboard.

Fixes #221.
2016-12-30 01:42:47 +01:00
Fredrik Fornwall
7d9d6fb797 Translate \n to \r when receiving text from an IME
Fixes issue with fzf and return using the stock cyanogenmod
keyboard reported by @mklein994 on gitter.
2016-12-30 00:45:11 +01:00
Fredrik Fornwall
74040dd37f Add feature and tv banner to art/ 2016-12-29 22:29:37 +01:00
Fredrik Fornwall
e94f06d0f7 Silence lint warnings in shortcuts.xml 2016-12-28 02:45:14 +01:00
Fredrik Fornwall
af21b6dc3e Remove unused variable. Use Collections.addAll() 2016-12-28 01:19:57 +01:00
Fredrik Fornwall
e0e8257f1c Update build dependencies 2016-12-28 01:07:12 +01:00
139 changed files with 5417 additions and 2948 deletions

View File

@@ -14,3 +14,7 @@ insert_final_newline = true
charset = utf-8 charset = utf-8
indent_style = space indent_style = space
indent_size = 4 indent_size = 4
[*.y{a,}ml]
indent_size = 2
indent_style = space

5
.gitattributes vendored Normal file
View File

@@ -0,0 +1,5 @@
* text=auto
*.bat eol=crlf
*.gradle eol=lf
*.mk eol=lf
*.sh eol=lf

2
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,2 @@
patreon: termux
custom: https://paypal.me/fornwall

35
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,35 @@
---
name: Bug report
about: Create a report to help us improve Termux application
---
<!--
IMPORTANT:
1. Support of Android 5.x - 6.x is finished.
2. Fill the template AFTER comments.
-->
**Problem 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 the behavior. Please post all necessary
commands that are needed to reproduce the issue.
-->
**Expected behavior**
<!--
A clear and concise description of what you expected to happen.
-->
**Additional information**
* Termux application version:
* Android OS version:
* Device model:

View File

@@ -0,0 +1,22 @@
---
name: Feature request
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**
<!--
Describe the feature and why you want it.
-->
**Reference implementation**
Does another app/terminal emulator have this feature?
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

18
.gitignore vendored
View File

@@ -7,6 +7,8 @@ build/
*.apk *.apk
*.so *.so
.externalNativeBuild .externalNativeBuild
.cxx
*.zip
# Crashlytics configuations # Crashlytics configuations
com_crashlytics_export_strings.xml com_crashlytics_export_strings.xml
@@ -20,19 +22,8 @@ local.properties
# Signing files # Signing files
.signing/ .signing/
# User-specific configurations # Intellij
.idea/libraries/ .idea/
.idea/workspace.xml
.idea/tasks.xml
.idea/.name
.idea/compiler.xml
.idea/copyright/profiles_settings.xml
.idea/encodings.xml
.idea/misc.xml
.idea/modules.xml
.idea/scopes/scope_settings.xml
.idea/vcs.xml
.idea/dictionaries/
*.iml *.iml
# OS-specific files # OS-specific files
@@ -41,5 +32,6 @@ local.properties
._* ._*
.Spotlight-V100 .Spotlight-V100
.Trashes .Trashes
.swp
ehthumbs.db ehthumbs.db
Thumbs.db Thumbs.db

View File

@@ -1,229 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectCodeStyleSettingsManager">
<option name="PER_PROJECT_SETTINGS">
<value>
<option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" />
<option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" />
<option name="PACKAGES_TO_USE_IMPORT_ON_DEMAND">
<value />
</option>
<option name="IMPORT_LAYOUT_TABLE">
<value>
<package name="android" withSubpackages="true" static="false" />
<emptyLine />
<package name="com" withSubpackages="true" static="false" />
<emptyLine />
<package name="junit" withSubpackages="true" static="false" />
<emptyLine />
<package name="net" withSubpackages="true" static="false" />
<emptyLine />
<package name="org" withSubpackages="true" static="false" />
<emptyLine />
<package name="java" withSubpackages="true" static="false" />
<emptyLine />
<package name="javax" withSubpackages="true" static="false" />
<emptyLine />
<package name="" withSubpackages="true" static="false" />
<emptyLine />
<package name="" withSubpackages="true" static="true" />
<emptyLine />
</value>
</option>
<option name="RIGHT_MARGIN" value="100" />
<AndroidXmlCodeStyleSettings>
<option name="USE_CUSTOM_SETTINGS" value="true" />
</AndroidXmlCodeStyleSettings>
<Objective-C-extensions>
<option name="GENERATE_INSTANCE_VARIABLES_FOR_PROPERTIES" value="ASK" />
<option name="RELEASE_STYLE" value="IVAR" />
<option name="TYPE_QUALIFIERS_PLACEMENT" value="BEFORE" />
<file>
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Import" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Macro" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Typedef" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Enum" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Constant" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Global" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Struct" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="FunctionPredecl" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Function" />
</file>
<class>
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Property" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Synthesize" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InitMethod" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="StaticMethod" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InstanceMethod" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="DeallocMethod" />
</class>
<extensions>
<pair source="cpp" header="h" />
<pair source="c" header="h" />
</extensions>
</Objective-C-extensions>
<XML>
<option name="XML_LEGACY_SETTINGS_IMPORTED" value="true" />
</XML>
<codeStyleSettings language="XML">
<option name="FORCE_REARRANGE_MODE" value="1" />
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
</indentOptions>
<arrangement>
<rules>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:android</NAME>
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:.*</NAME>
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:id</NAME>
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:name</NAME>
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>name</NAME>
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>style</NAME>
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:layout_width</NAME>
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:layout_height</NAME>
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:layout_.*</NAME>
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:width</NAME>
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:height</NAME>
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_NAMESPACE>.*</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
</rules>
</arrangement>
</codeStyleSettings>
</value>
</option>
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default (1)" />
</component>
</project>

19
.idea/gradle.xml generated
View File

@@ -1,19 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="disableWrapperSourceDistributionNotification" value="true" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
<option name="resolveModulePerSourceSet" value="false" />
</GradleProjectSettings>
</option>
</component>
</project>

View File

@@ -1,71 +0,0 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="AndroidLintLogConditional" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AndroidLintNegativeMargin" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AssignmentUsedAsCondition" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="DeprecatedAPI" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="DuplicateSwitchCase" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="EmptyStatementBody" enabled="false" level="WARNING" enabled_by_default="false">
<option name="m_reportEmptyBlocks" value="true" />
</inspection_tool>
<inspection_tool class="EndlessLoop" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="EqualityInConditionalOperator" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="Finalize" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoreTrivialFinalizers" value="true" />
</inspection_tool>
<inspection_tool class="FinalizeNotProtected" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="FormatSpecifiers" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="FunctionImplicitDeclarationInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="HidesUpperScope" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="HidingNonVirtualFunction" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="ImplicitIntegerAndEnumConversion" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="ImplicitPointerAndIntegerConversion" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="IncompatibleEnums" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="IncompatibleInitializers" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="IncompatiblePointers" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="InstanceofChain" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoreInstanceofOnLibraryClasses" value="false" />
</inspection_tool>
<inspection_tool class="KRUnspecifiedParameters" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="LocalValueEscapesScope" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="LoggerInitializedWithForeignClass" enabled="false" level="WARNING" enabled_by_default="false">
<option name="loggerClassName" value="org.apache.log4j.Logger,org.slf4j.LoggerFactory,org.apache.commons.logging.LogFactory,java.util.logging.Logger" />
<option name="loggerFactoryMethodName" value="getLogger,getLogger,getLog,getLogger" />
</inspection_tool>
<inspection_tool class="MissingReturn" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="MissingSwitchCase" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="NotImplementedFunctions" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="NotInitializedVariable" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="NotSuperclass" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="OCDFAInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="OCLoopDoesntUseConditionVariableInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="OCSimplifyInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="OCUnusedGlobalDeclarationInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="OCUnusedMacroInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="OCUnusedStructInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="OCUnusedTemplateParameterInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="OnDemandImport" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="PrivateMemberAccessBetweenOuterAndInnerClass" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ResourceNotFoundInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SamePackageImport" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="SignednessMismatch" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
<option name="processCode" value="true" />
<option name="processLiterals" value="true" />
<option name="processComments" value="true" />
</inspection_tool>
<inspection_tool class="UnnecessaryFullyQualifiedName" enabled="true" level="WARNING" enabled_by_default="true">
<option name="m_ignoreJavadoc" value="false" />
</inspection_tool>
<inspection_tool class="UnreachableCode" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="UnusedExpressionResult" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="UnusedImportStatement" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="UnusedLocalVariable" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="UnusedLocalization" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="UnusedParameter" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="UnusedValue" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="ValueMayNotFitIntoReceiver" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="VariableNotUsedInsideIf" enabled="true" level="WARNING" enabled_by_default="true" />
</profile>
</component>

View File

@@ -1,7 +0,0 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="PROJECT_PROFILE" value="Project Default" />
<option name="USE_PROJECT_PROFILE" value="true" />
<version value="1.0" />
</settings>
</component>

View File

@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
</set>
</option>
</component>
</project>

View File

@@ -1,35 +0,0 @@
sudo: true
language: android
jdk: oraclejdk8
env:
global:
# The next declaration is the encrypted COVERITY_SCAN_TOKEN, created
# via the "travis encrypt" command using the project repo's public key
- secure: "ACnFJxw0VusS2lnGXL+epP/CNJmftWS39YcPdgN2EurWw5ZfXSo7vi+zpMB+11IBS3LQyLFFUambi2N9L4lbReZkHVkoVcZFGZlwbXNTAeqT8CABPTcuOyEOZU4bJwqeYU87ztYipENMLNECaZrgWx5odbWLKnSJQw7Zkb4ArCstfXfYk9u8q49ThRxQyGwHW2xKp1an5aa+3Y6IY+ywsSHw6AvXbyFH078Kolxy86caagczcfmKcMi15QYzwAvFggUphvsO3M5PHJMQXuaNlQxDcQRGUEXsK8aZE0dPH5PB97SFjDALZqI7NEpjZAk5htWjX48ssW064LDbjcBg/ZLgDd8R8uhA159NVZgvcnP2czCn6pmggx1sW5MBmcj7i+bJS2ejaMO+KoovWlVvsch742H5QR6rQaNkjDZRsGVLYvJaR1gBLs898UoT1hcHWoqLVR22r2VFo7OWWCRfNRvZuZDR2HIrYRdFvn8P3nWVMkvXwgsOlxWG5sN+yQqW+6lZS7hivsFhtYs4CkRdoZIan3Qvi/CkY8Lg+ESkZ3IJ0NnId8qOWH+8Xl1sqZ7xlsWTd1sYYHlpvkdvqw1HNLP22EpwwKW5Kb5zBEd/qs3o1OO0Tqa0MR6JpgGdHHRk1iZ25+qTfRVP06vO2RXsgAx4SZfO7DyB0QZn8tGNMMI="
android:
components:
- platform-tools
- tools
- build-tools-25.0.2
- android-25
- extra-android-m2repository
before_install:
- git clone https://github.com/urho3d/android-ndk.git $HOME/android-ndk
- export ANDROID_NDK_HOME=$HOME/android-ndk
- echo -n | openssl s_client -connect scan.coverity.com:443 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' | sudo tee -a /etc/ssl/certs/ca-certificates.crt
script:
- ./gradlew testDebugUnitTest
addons:
coverity_scan:
project:
name: "termux/termux-app"
description: "Terminal emulator and Linux environment for Android"
notification_email: fredrik@fornwall.net
build_command_prepend: "./gradlew clean"
build_command: "./gradlew build"
branch_pattern: master

3
LICENSE.md Normal file
View File

@@ -0,0 +1,3 @@
Released under [the GPLv3 license](https://www.gnu.org/licenses/gpl.html).
Contains code from `Terminal Emulator for Android` by which is released under [the Apache License 2.0](https://www.apache.org/licenses/).

View File

@@ -1,33 +1,65 @@
Termux app # Termux application
==========
[![Travis build status](https://travis-ci.org/termux/termux-app.svg?branch=master)](https://travis-ci.org/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 app and Linux environment. [Termux](https://termux.com) is an Android terminal application and Linux environment.
* [Termux on Google Play Store](https://play.google.com/store/apps/details?id=com.termux) - [Termux Reddit community](https://reddit.com/r/termux)
* [Termux on F-Droid](https://f-droid.org/repository/browse/?fdid=com.termux) - [Termux Wiki](https://wiki.termux.com/wiki/)
* [Termux Help](http://termux.com/help/) - [Termux Twitter](http://twitter.com/termux/)
* [Termux Google+ community](http://termux.com/community/)
Note that this repository is for the app itself (the user interface and the terminal emulation). For the packages installable inside the app, see [termux/termux-packages](https://github.com/termux/termux-packages) Note that this repository is for the app itself (the user interface and the
terminal emulation). For the packages installable inside the app, see
[termux/termux-packages](https://github.com/termux/termux-packages)
License ## Installation
=======
Released under [the GPLv3 license](https://www.gnu.org/licenses/gpl.html). Contains code from `Terminal Emulator for Android` which is released under [the Apache License 2.0](https://www.apache.org/licenses/).
Terminal resources Termux application can be obtained from:
==================
* [XTerm control sequences](http://invisible-island.net/xterm/ctlseqs/ctlseqs.html)
* [vt100.net](http://vt100.net/)
* [Terminal codes (ANSI and terminfo equivalents)](http://wiki.bash-hackers.org/scripting/terminalcodes)
Terminal emulators - [Google Play](https://play.google.com/store/apps/details?id=com.termux)
================== - [F-Droid](https://f-droid.org/en/packages/com.termux/)
* VTE (libvte): Terminal emulator widget for GTK+, mainly used in gnome-terminal. [Source](https://github.com/GNOME/vte), [Open Issues](https://bugzilla.gnome.org/buglist.cgi?quicksearch=product%3A%22vte%22+), and [All (including closed) issues](https://bugzilla.gnome.org/buglist.cgi?bug_status=RESOLVED&bug_status=VERIFIED&chfield=resolution&chfieldfrom=-2000d&chfieldvalue=FIXED&product=vte&resolution=FIXED). - [Kali Nethunter Store](https://store.nethunter.com/en/packages/com.termux/)
* iTerm 2: OS X terminal application. [Source](https://github.com/gnachman/iTerm2), [Issues](https://gitlab.com/gnachman/iterm2/issues) and [Documentation](http://www.iterm2.com/documentation.html) (which includes [iTerm2 proprietary escape codes](http://www.iterm2.com/documentation-escape-codes.html)).
* Konsole: KDE terminal application. [Source](https://projects.kde.org/projects/kde/applications/konsole/repository), in particular [tests](https://projects.kde.org/projects/kde/applications/konsole/repository/revisions/master/show/tests), [Bugs](https://bugs.kde.org/buglist.cgi?bug_severity=critical&bug_severity=grave&bug_severity=major&bug_severity=crash&bug_severity=normal&bug_severity=minor&bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&product=konsole) and [Wishes](https://bugs.kde.org/buglist.cgi?bug_severity=wishlist&bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&product=konsole). Additionally we provide per-commit debug builds for those who want to try
* hterm: JavaScript terminal implementation from Chromium. [Source](https://github.com/chromium/hterm), including [tests](https://github.com/chromium/hterm/blob/master/js/hterm_vt_tests.js), and [Google group](https://groups.google.com/a/chromium.org/forum/#!forum/chromium-hterm). out the latest features or test their pull request. This build can be obtained
* xterm: The grandfather of terminal emulators. [Source](http://invisible-island.net/datafiles/release/xterm.tar.gz). from one of the workflow runs listed on [Github Actions](https://github.com/termux/termux-app/actions)
* Connectbot: Android SSH client. [Source](https://github.com/connectbot/connectbot) page.
* Android Terminal Emulator: Android terminal app which Termux terminal handling is based on. Inactive. [Source](https://github.com/jackpal/Android-Terminal-Emulator).
Signature keys of all offered builds are different. Before you switch the
installation source, you will have to uninstall the Termux application and
all currently installed plugins.
## Terminal resources
- [XTerm control sequences](http://invisible-island.net/xterm/ctlseqs/ctlseqs.html)
- [vt100.net](http://vt100.net/)
- [Terminal codes (ANSI and terminfo equivalents)](http://wiki.bash-hackers.org/scripting/terminalcodes)
## Terminal emulators
- VTE (libvte): Terminal emulator widget for GTK+, mainly used in gnome-terminal.
[Source](https://github.com/GNOME/vte), [Open Issues](https://bugzilla.gnome.org/buglist.cgi?quicksearch=product%3A%22vte%22+),
and [All (including closed) issues](https://bugzilla.gnome.org/buglist.cgi?bug_status=RESOLVED&bug_status=VERIFIED&chfield=resolution&chfieldfrom=-2000d&chfieldvalue=FIXED&product=vte&resolution=FIXED).
- iTerm 2: OS X terminal application. [Source](https://github.com/gnachman/iTerm2),
[Issues](https://gitlab.com/gnachman/iterm2/issues) and [Documentation](http://www.iterm2.com/documentation.html)
(which includes [iTerm2 proprietary escape codes](http://www.iterm2.com/documentation-escape-codes.html)).
- Konsole: KDE terminal application. [Source](https://projects.kde.org/projects/kde/applications/konsole/repository),
in particular [tests](https://projects.kde.org/projects/kde/applications/konsole/repository/revisions/master/show/tests),
[Bugs](https://bugs.kde.org/buglist.cgi?bug_severity=critical&bug_severity=grave&bug_severity=major&bug_severity=crash&bug_severity=normal&bug_severity=minor&bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&product=konsole)
and [Wishes](https://bugs.kde.org/buglist.cgi?bug_severity=wishlist&bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&product=konsole).
- hterm: JavaScript terminal implementation from Chromium. [Source](https://github.com/chromium/hterm),
including [tests](https://github.com/chromium/hterm/blob/master/js/hterm_vt_tests.js),
and [Google group](https://groups.google.com/a/chromium.org/forum/#!forum/chromium-hterm).
- xterm: The grandfather of terminal emulators.
[Source](http://invisible-island.net/datafiles/release/xterm.tar.gz).
- Connectbot: Android SSH client. [Source](https://github.com/connectbot/connectbot)
- Android Terminal Emulator: Android terminal app which Termux terminal handling
is based on. Inactive. [Source](https://github.com/jackpal/Android-Terminal-Emulator).

View File

@@ -1,20 +1,24 @@
apply plugin: 'com.android.application' plugins {
id "com.android.application"
}
android { android {
compileSdkVersion 25 compileSdkVersion project.properties.compileSdkVersion.toInteger()
buildToolsVersion "25.0.2" ndkVersion project.properties.ndkVersion
dependencies { dependencies {
compile 'com.android.support:support-annotations:25.0.1' implementation "androidx.annotation:annotation:1.1.0"
compile "com.android.support:support-v4:25.0.1" implementation "androidx.viewpager:viewpager:1.0.0"
implementation "androidx.drawerlayout:drawerlayout:1.1.1"
implementation project(":terminal-view")
} }
defaultConfig { defaultConfig {
applicationId "com.termux" applicationId "com.termux"
minSdkVersion 21 minSdkVersion project.properties.minSdkVersion.toInteger()
targetSdkVersion 25 targetSdkVersion project.properties.targetSdkVersion.toInteger()
versionCode 45 versionCode 105
versionName "0.45" versionName "0.105"
externalNativeBuild { externalNativeBuild {
ndkBuild { ndkBuild {
@@ -25,6 +29,16 @@ android {
ndk { ndk {
abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a' abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'
} }
}
signingConfigs {
debug {
storeFile file('dev_keystore.jks')
keyAlias 'alias'
storePassword 'xrj45yWGLbsO7W0v'
keyPassword 'xrj45yWGLbsO7W0v'
}
} }
buildTypes { buildTypes {
@@ -33,15 +47,106 @@ android {
shrinkResources true shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
} }
debug {
signingConfig signingConfigs.debug
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
} }
externalNativeBuild { externalNativeBuild {
ndkBuild { ndkBuild {
path "src/main/jni/Android.mk" path "src/main/cpp/Android.mk"
}
}
lintOptions {
disable 'ProtectedPermissions'
}
testOptions {
unitTests {
includeAndroidResources = true
} }
} }
} }
dependencies { dependencies {
testCompile 'junit:junit:4.12' testImplementation 'junit:junit:4.13.1'
testImplementation 'org.robolectric:robolectric:4.4'
}
task versionName {
doLast {
print android.defaultConfig.versionName
}
}
def downloadBootstrap(String arch, String expectedChecksum, int version) {
def digest = java.security.MessageDigest.getInstance("SHA-256")
def localUrl = "src/main/cpp/bootstrap-" + arch + ".zip"
def file = new File(projectDir, localUrl)
if (file.exists()) {
def buffer = new byte[8192]
def input = new FileInputStream(file)
while (true) {
def readBytes = input.read(buffer)
if (readBytes < 0) break
digest.update(buffer, 0, readBytes)
}
def checksum = new BigInteger(1, digest.digest()).toString(16)
if (checksum == expectedChecksum) {
return
} else {
logger.quiet("Deleting old local file with wrong hash: " + localUrl)
file.delete()
}
}
def remoteUrl = "https://bintray.com/termux/bootstrap/download_file?file_path=bootstrap-" + arch + "-v" + version + ".zip"
logger.quiet("Downloading " + remoteUrl + " ...")
file.parentFile.mkdirs()
def out = new BufferedOutputStream(new FileOutputStream(file))
def connection = new URL(remoteUrl).openConnection()
connection.setInstanceFollowRedirects(true)
def digestStream = new java.security.DigestInputStream(connection.inputStream, digest)
out << digestStream
out.close()
def checksum = new BigInteger(1, digest.digest()).toString(16)
if (checksum != expectedChecksum) {
file.delete()
throw new GradleException("Wrong checksum for " + remoteUrl + ": expected: " + expectedChecksum + ", actual: " + checksum)
}
}
clean {
doLast {
def tree = fileTree(new File(projectDir, 'src/main/cpp'))
tree.include 'bootstrap-*.zip'
tree.each { it.delete() }
}
}
task downloadBootstraps(){
doLast {
def version = 35
downloadBootstrap("aarch64", "6707cc641cde13dc2f24e819cd8ca3f1a9a003577523c25beff72690588594f5", version)
downloadBootstrap("arm", "eadc9afb52900dc744fdb39ed0c3dbb87ad8ce6190b27946467df7aeab353fa7", version)
downloadBootstrap("i686", "b674ef43c8388dd19e0d25b024b5792c8532ee0ddcc49f90c0716042724fa905", version)
downloadBootstrap("x86_64", "86043eb76efededbdf5644686a899f4d762703fe18e23fb906a2482d593d7549", version)
}
}
afterEvaluate {
android.applicationVariants.all { variant ->
variant.javaCompileProvider.get().dependsOn(downloadBootstraps)
}
} }

BIN
app/dev_keystore.jks Normal file

Binary file not shown.

View File

@@ -7,11 +7,5 @@
# For more details, see # For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html # http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here: -renamesourcefileattribute SourceFile
-keepattributes SourceFile,LineNumberTable
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

View File

@@ -1,13 +0,0 @@
package com.termux;
import android.app.Application;
import android.test.ApplicationTestCase;
/**
* <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
*/
public class ApplicationTest extends ApplicationTestCase<Application> {
public ApplicationTest() {
super(Application.class);
}
}

View File

@@ -1,4 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.termux" package="com.termux"
android:installLocation="internalOnly" android:installLocation="internalOnly"
android:sharedUserId="com.termux" android:sharedUserId="com.termux"
@@ -7,26 +8,43 @@
<uses-feature android:name="android.hardware.touchscreen" android:required="false" /> <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
<uses-feature android:name="android.software.leanback" android:required="false" /> <uses-feature android:name="android.software.leanback" android:required="false" />
<permission android:name="com.termux.permission.RUN_COMMAND"
android:label="@string/run_command_permission_label"
android:description="@string/run_command_permission_description"
android:icon="@drawable/ic_launcher"
android:protectionLevel="dangerous" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.VIBRATE" /> <uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.READ_LOGS" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
<application <application
android:allowBackup="true" android:extractNativeLibs="true"
android:fullBackupContent="@xml/backupscheme" android:allowBackup="false"
android:icon="@mipmap/ic_launcher" android:icon="@drawable/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:banner="@drawable/banner" android:banner="@drawable/banner"
android:label="@string/application_name" android:label="@string/application_name"
android:theme="@style/Theme.Termux" android:theme="@style/Theme.Termux"
android:supportsRtl="false" > android:supportsRtl="false" >
<!-- This (or rather, value 2.1 or higher) is needed to make the Samsung Galaxy S8
mark the app with "This app is optimized to run in full screen." -->
<meta-data android:name="android.max_aspect" android:value="10.0" />
<activity <activity
android:name="com.termux.app.TermuxActivity" android:name="com.termux.app.TermuxActivity"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize" android:label="@string/application_name"
android:configChanges="orientation|screenSize|smallestScreenSize|density|screenLayout|uiMode|keyboard|keyboardHidden|navigation"
android:launchMode="singleTask" android:launchMode="singleTask"
android:windowSoftInputMode="adjustResize|stateAlwaysVisible" > android:resizeableActivity="true"
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" />
@@ -43,6 +61,7 @@
android:exported="false" android:exported="false"
android:theme="@android:style/Theme.Material.Light.DarkActionBar" android:theme="@android:style/Theme.Material.Light.DarkActionBar"
android:parentActivityName=".app.TermuxActivity" android:parentActivityName=".app.TermuxActivity"
android:resizeableActivity="true"
android:label="@string/application_name" /> android:label="@string/application_name" />
<activity <activity
@@ -50,6 +69,7 @@
android:label="@string/application_name" android:label="@string/application_name"
android:taskAffinity="com.termux.filereceiver" android:taskAffinity="com.termux.filereceiver"
android:excludeFromRecents="true" android:excludeFromRecents="true"
android:resizeableActivity="true"
android:noHistory="true"> android:noHistory="true">
<!-- Accept multiple file types when sending. --> <!-- Accept multiple file types when sending. -->
<intent-filter> <intent-filter>
@@ -63,18 +83,30 @@
<data android:mimeType="text/*" /> <data android:mimeType="text/*" />
<data android:mimeType="video/*" /> <data android:mimeType="video/*" />
</intent-filter> </intent-filter>
<!-- Be more restrictive for viewing files, restricting ourselves to text files. --> <!-- Accept multiple file types to let Termux be usable as generic file viewer. -->
<intent-filter> <intent-filter tools:ignore="AppLinkUrlError">
<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="application/*" />
<data android:mimeType="audio/*" />
<data android:mimeType="image/*" />
<data android:mimeType="text/*" /> <data android:mimeType="text/*" />
<data android:mimeType="application/json" /> <data android:mimeType="video/*" />
<data android:mimeType="application/*xml*" />
<data android:mimeType="application/*latex*" />
<data android:mimeType="application/javascript" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity-alias
android:name=".HomeActivity"
android:targetActivity="com.termux.app.TermuxActivity">
<!-- Launch activity automatically on boot on Android Things devices -->
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.IOT_LAUNCHER"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity-alias>
<provider <provider
android:name=".filepicker.TermuxDocumentsProvider" android:name=".filepicker.TermuxDocumentsProvider"
android:authorities="com.termux.documents" android:authorities="com.termux.documents"
@@ -90,6 +122,24 @@
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" />
<provider android:authorities="com.termux.files"
android:readPermission="android.permission.permRead"
android:exported="true"
android:grantUriPermissions="true"
android:name="com.termux.app.TermuxOpenReceiver$ContentProvider" />
<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

@@ -0,0 +1,5 @@
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := libtermux-bootstrap
LOCAL_SRC_FILES := termux-bootstrap-zip.S termux-bootstrap.c
include $(BUILD_SHARED_LIBRARY)

View File

@@ -0,0 +1,18 @@
.global blob
.global blob_size
.section .rodata
blob:
#if defined __i686__
.incbin "bootstrap-i686.zip"
#elif defined __x86_64__
.incbin "bootstrap-x86_64.zip"
#elif defined __aarch64__
.incbin "bootstrap-aarch64.zip"
#elif defined __arm__
.incbin "bootstrap-arm.zip"
#else
# error Unsupported arch
#endif
1:
blob_size:
.int 1b - blob

View File

@@ -0,0 +1,11 @@
#include <jni.h>
extern jbyte blob[];
extern int blob_size;
JNIEXPORT jbyteArray JNICALL Java_com_termux_app_TermuxInstaller_getZip(JNIEnv *env, __attribute__((__unused__)) jobject This)
{
jbyteArray ret = (*env)->NewByteArray(env, blob_size);
(*env)->SetByteArrayRegion(env, ret, 0, blob_size, blob);
return ret;
}

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;
@@ -12,6 +16,7 @@ import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.List; import java.util.List;
/** /**
@@ -23,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;
@@ -42,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
@@ -49,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);
@@ -67,58 +100,73 @@ 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.
}
}
};
} }
public static String[] buildEnvironment(boolean failSafe, String cwd) { private static void addToEnvIfPresent(List<String> environment, String name) {
String value = System.getenv(name);
if (value != null) {
environment.add(name + "=" + value);
}
}
static String[] buildEnvironment(boolean failSafe, String cwd) {
new File(TermuxService.HOME_PATH).mkdirs(); new File(TermuxService.HOME_PATH).mkdirs();
if (cwd == null) cwd = TermuxService.HOME_PATH; if (cwd == null) cwd = TermuxService.HOME_PATH;
final String termEnv = "TERM=xterm-256color"; List<String> environment = new ArrayList<>();
final String homeEnv = "HOME=" + TermuxService.HOME_PATH;
final String prefixEnv = "PREFIX=" + TermuxService.PREFIX_PATH; environment.add("TERM=xterm-256color");
final String androidRootEnv = "ANDROID_ROOT=" + System.getenv("ANDROID_ROOT"); environment.add("COLORTERM=truecolor");
final String androidDataEnv = "ANDROID_DATA=" + System.getenv("ANDROID_DATA"); environment.add("HOME=" + TermuxService.HOME_PATH);
environment.add("PREFIX=" + TermuxService.PREFIX_PATH);
environment.add("BOOTCLASSPATH=" + System.getenv("BOOTCLASSPATH"));
environment.add("ANDROID_ROOT=" + System.getenv("ANDROID_ROOT"));
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.
final String externalStorageEnv = "EXTERNAL_STORAGE=" + System.getenv("EXTERNAL_STORAGE"); environment.add("EXTERNAL_STORAGE=" + System.getenv("EXTERNAL_STORAGE"));
String[] env;
// These variables are needed if running on Android 10 and higher.
addToEnvIfPresent(environment, "ANDROID_ART_ROOT");
addToEnvIfPresent(environment, "DEX2OATBOOTCLASSPATH");
addToEnvIfPresent(environment, "ANDROID_I18N_ROOT");
addToEnvIfPresent(environment, "ANDROID_RUNTIME_ROOT");
addToEnvIfPresent(environment, "ANDROID_TZDATA_ROOT");
if (failSafe) { if (failSafe) {
// Keep the default path so that system binaries can be used in the failsafe session. // Keep the default path so that system binaries can be used in the failsafe session.
final String pathEnv = "PATH=" + System.getenv("PATH"); environment.add("PATH= " + System.getenv("PATH"));
return new String[]{termEnv, homeEnv, prefixEnv, androidRootEnv, androidDataEnv, pathEnv, externalStorageEnv};
} else { } else {
final String ps1Env = "PS1=$ "; environment.add("LANG=en_US.UTF-8");
final String ldEnv = "LD_LIBRARY_PATH=" + TermuxService.PREFIX_PATH + "/lib"; environment.add("PATH=" + TermuxService.PREFIX_PATH + "/bin");
final String langEnv = "LANG=en_US.UTF-8"; environment.add("PWD=" + cwd);
final String pathEnv = "PATH=" + TermuxService.PREFIX_PATH + "/bin:" + TermuxService.PREFIX_PATH + "/bin/applets"; environment.add("TMPDIR=" + TermuxService.PREFIX_PATH + "/tmp");
final String pwdEnv = "PWD=" + cwd;
return new String[]{termEnv, homeEnv, prefixEnv, ps1Env, ldEnv, langEnv, pathEnv, pwdEnv, androidRootEnv, androidDataEnv, externalStorageEnv};
} }
return environment.toArray(new String[0]);
} }
public static int getPid(Process p) { public static int getPid(Process p) {
@@ -158,7 +206,6 @@ public final class BackgroundJob {
if (c == ' ' || c == '\n') { if (c == ' ' || c == '\n') {
if (builder.length() == 0) { if (builder.length() == 0) {
// Skip whitespace after shebang. // Skip whitespace after shebang.
continue;
} else { } else {
// End of shebang. // End of shebang.
String executable = builder.toString(); String executable = builder.toString();
@@ -186,10 +233,8 @@ public final class BackgroundJob {
List<String> result = new ArrayList<>(); List<String> result = new ArrayList<>();
if (interpreter != null) result.add(interpreter); if (interpreter != null) result.add(interpreter);
result.add(fileToExecute); result.add(fileToExecute);
if (args != null) { if (args != null) Collections.addAll(result, args);
for (String arg : args) result.add(arg); return result.toArray(new String[0]);
}
return result.toArray(new String[result.size()]);
} }
} }

View File

@@ -0,0 +1,63 @@
package com.termux.app;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock;
import android.os.Vibrator;
public class BellUtil {
private static BellUtil instance = null;
private static final Object lock = new Object();
public static BellUtil getInstance(Context context) {
if (instance == null) {
synchronized (lock) {
if (instance == null) {
instance = new BellUtil((Vibrator) context.getApplicationContext().getSystemService(Context.VIBRATOR_SERVICE));
}
}
}
return instance;
}
private static final long DURATION = 50;
private static final long MIN_PAUSE = 3 * DURATION;
private final Handler handler = new Handler(Looper.getMainLooper());
private long lastBell = 0;
private final Runnable bellRunnable;
private BellUtil(final Vibrator vibrator) {
bellRunnable = new Runnable() {
@Override
public void run() {
if (vibrator != null) {
vibrator.vibrate(DURATION);
}
}
};
}
public synchronized void doBell() {
long now = now();
long timeSinceLastBell = now - lastBell;
if (timeSinceLastBell < 0) {
// there is a next bell pending; don't schedule another one
} else if (timeSinceLastBell < MIN_PAUSE) {
// there was a bell recently, scheudle the next one
handler.postDelayed(bellRunnable, MIN_PAUSE - timeSinceLastBell);
lastBell = lastBell + MIN_PAUSE;
} else {
// the last bell was long ago, do it now
bellRunnable.run();
lastBell = now;
}
}
private long now() {
return SystemClock.uptimeMillis();
}
}

View File

@@ -9,7 +9,6 @@ import android.view.KeyEvent;
import android.view.ViewGroup.LayoutParams; import android.view.ViewGroup.LayoutParams;
import android.widget.EditText; import android.widget.EditText;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.TextView;
public final class DialogUtils { public final class DialogUtils {
@@ -31,13 +30,10 @@ public final class DialogUtils {
final AlertDialog[] dialogHolder = new AlertDialog[1]; final AlertDialog[] dialogHolder = new AlertDialog[1];
input.setImeActionLabel(activity.getResources().getString(positiveButtonText), KeyEvent.KEYCODE_ENTER); input.setImeActionLabel(activity.getResources().getString(positiveButtonText), KeyEvent.KEYCODE_ENTER);
input.setOnEditorActionListener(new TextView.OnEditorActionListener() { input.setOnEditorActionListener((v, actionId, event) -> {
@Override onPositive.onTextSet(input.getText().toString());
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { dialogHolder[0].dismiss();
onPositive.onTextSet(input.getText().toString()); return true;
dialogHolder[0].dismiss();
return true;
}
}); });
float dipInPixels = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, activity.getResources().getDisplayMetrics()); float dipInPixels = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, activity.getResources().getDisplayMetrics());
@@ -53,31 +49,16 @@ public final class DialogUtils {
AlertDialog.Builder builder = new AlertDialog.Builder(activity) AlertDialog.Builder builder = new AlertDialog.Builder(activity)
.setTitle(titleText).setView(layout) .setTitle(titleText).setView(layout)
.setPositiveButton(positiveButtonText, new DialogInterface.OnClickListener() { .setPositiveButton(positiveButtonText, (d, whichButton) -> onPositive.onTextSet(input.getText().toString()));
@Override
public void onClick(DialogInterface d, int whichButton) {
onPositive.onTextSet(input.getText().toString());
}
});
if (onNeutral != null) { if (onNeutral != null) {
builder.setNeutralButton(neutralButtonText, new DialogInterface.OnClickListener() { builder.setNeutralButton(neutralButtonText, (dialog, which) -> onNeutral.onTextSet(input.getText().toString()));
@Override
public void onClick(DialogInterface dialog, int which) {
onNeutral.onTextSet(input.getText().toString());
}
});
} }
if (onNegative == null) { if (onNegative == null) {
builder.setNegativeButton(android.R.string.cancel, null); builder.setNegativeButton(android.R.string.cancel, null);
} else { } else {
builder.setNegativeButton(negativeButtonText, new DialogInterface.OnClickListener() { builder.setNegativeButton(negativeButtonText, (dialog, which) -> onNegative.onTextSet(input.getText().toString()));
@Override
public void onClick(DialogInterface dialog, int which) {
onNegative.onTextSet(input.getText().toString());
}
});
} }
if (onDismiss != null) builder.setOnDismissListener(onDismiss); if (onDismiss != null) builder.setOnDismissListener(onDismiss);

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,18 +1,35 @@
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.provider.Settings;
import android.util.AttributeSet; import android.util.AttributeSet;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.ScheduledExecutorService;
import java.util.Map;
import java.util.HashMap;
import java.util.Arrays;
import android.view.Gravity; import android.view.Gravity;
import android.view.HapticFeedbackConstants;
import android.view.KeyEvent; import android.view.KeyEvent;
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.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.
@@ -20,152 +37,312 @@ import com.termux.view.TerminalView;
public final class ExtraKeysView extends GridLayout { 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 INTERESTING_COLOR = 0xFF80DEEA;
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);
reload();
} }
static void sendKey(View view, String keyName) { static final Map<String, Integer> keyCodesForString = new HashMap<String, Integer>() {{
int keyCode = 0; put("SPACE", KeyEvent.KEYCODE_SPACE);
String chars = null; put("ESC", KeyEvent.KEYCODE_ESCAPE);
switch (keyName) { put("TAB", KeyEvent.KEYCODE_TAB);
case "ESC": put("HOME", KeyEvent.KEYCODE_MOVE_HOME);
keyCode = KeyEvent.KEYCODE_ESCAPE; put("END", KeyEvent.KEYCODE_MOVE_END);
break; put("PGUP", KeyEvent.KEYCODE_PAGE_UP);
case "TAB": put("PGDN", KeyEvent.KEYCODE_PAGE_DOWN);
keyCode = KeyEvent.KEYCODE_TAB; put("INS", KeyEvent.KEYCODE_INSERT);
break; put("DEL", KeyEvent.KEYCODE_FORWARD_DEL);
case "": put("BKSP", KeyEvent.KEYCODE_DEL);
keyCode = KeyEvent.KEYCODE_DPAD_UP; put("UP", KeyEvent.KEYCODE_DPAD_UP);
break; put("LEFT", KeyEvent.KEYCODE_DPAD_LEFT);
case "": put("RIGHT", KeyEvent.KEYCODE_DPAD_RIGHT);
keyCode = KeyEvent.KEYCODE_DPAD_LEFT; put("DOWN", KeyEvent.KEYCODE_DPAD_DOWN);
break; put("ENTER", KeyEvent.KEYCODE_ENTER);
case "": put("F1", KeyEvent.KEYCODE_F1);
keyCode = KeyEvent.KEYCODE_DPAD_RIGHT; put("F2", KeyEvent.KEYCODE_F2);
break; put("F3", KeyEvent.KEYCODE_F3);
case "": put("F4", KeyEvent.KEYCODE_F4);
keyCode = KeyEvent.KEYCODE_DPAD_DOWN; put("F5", KeyEvent.KEYCODE_F5);
break; put("F6", KeyEvent.KEYCODE_F6);
case "": put("F7", KeyEvent.KEYCODE_F7);
chars = "-"; put("F8", KeyEvent.KEYCODE_F8);
break; put("F9", KeyEvent.KEYCODE_F9);
default: put("F10", KeyEvent.KEYCODE_F10);
chars = keyName; put("F11", KeyEvent.KEYCODE_F11);
} put("F12", KeyEvent.KEYCODE_F12);
}};
if (keyCode > 0) { private void sendKey(View view, String keyName, boolean forceCtrlDown, boolean forceLeftAltDown) {
view.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, keyCode)); TerminalView terminalView = view.findViewById(R.id.terminal_view);
view.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, keyCode)); 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 metaState = 0;
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 {
TerminalView terminalView = (TerminalView) view.findViewById(R.id.terminal_view); // not a control char
TerminalSession session = terminalView.getCurrentSession(); keyName.codePoints().forEach(codePoint -> {
if (session != null) session.write(chars); terminalView.inputCodePoint(codePoint, forceCtrlDown, forceLeftAltDown);
});
} }
} }
private ToggleButton controlButton; private void sendKey(View view, ExtraKeyButton buttonInfo) {
private ToggleButton altButton; if (buttonInfo.isMacro()) {
private ToggleButton fnButton; String[] keys = buttonInfo.getKey().split(" ");
boolean ctrlDown = false;
public boolean readControlButton() { boolean altDown = false;
if (controlButton.isPressed()) return true; for (String key : keys) {
boolean result = controlButton.isChecked(); if ("CTRL".equals(key)) {
if (result) { ctrlDown = true;
controlButton.setChecked(false); } else if ("ALT".equals(key)) {
controlButton.setTextColor(TEXT_COLOR); altDown = true;
} else {
sendKey(view, key, ctrlDown, altDown);
ctrlDown = false;
altDown = false;
}
}
} else {
sendKey(view, buttonInfo.getKey(), false, false);
} }
return result;
} }
public boolean readAltButton() { public enum SpecialButton {
if (altButton.isPressed()) return true; CTRL, ALT, FN
boolean result = altButton.isChecked();
if (result) {
altButton.setChecked(false);
altButton.setTextColor(TEXT_COLOR);
}
return result;
} }
public boolean readFnButton() { private static class SpecialButtonState {
if (fnButton.isPressed()) return true; boolean isOn = false;
boolean result = fnButton.isChecked(); ToggleButton button = null;
if (result) {
fnButton.setChecked(false);
fnButton.setTextColor(TEXT_COLOR);
}
return result;
} }
void reload() { private Map<SpecialButton, SpecialButtonState> specialButtons = new HashMap<SpecialButton, SpecialButtonState>() {{
altButton = controlButton = null; put(SpecialButton.CTRL, new SpecialButtonState());
put(SpecialButton.ALT, new SpecialButtonState());
put(SpecialButton.FN, new SpecialButtonState());
}};
private ScheduledExecutorService scheduledExecutor;
private PopupWindow popupWindow;
private int longPressCount;
public boolean readSpecialButton(SpecialButton name) {
SpecialButtonState state = specialButtons.get(name);
if (state == null)
throw new RuntimeException("Must be a valid special button (see source)");
if (! state.isOn)
return false;
if (state.button == null) {
return false;
}
if (state.button.isPressed())
return true;
if (! state.button.isChecked())
return false;
state.button.setChecked(false);
state.button.setTextColor(TEXT_COLOR);
return true;
}
void popup(View view, String text) {
int width = view.getMeasuredWidth();
int height = view.getMeasuredHeight();
Button button = new Button(getContext(), null, android.R.attr.buttonBarButtonStyle);
button.setText(text);
button.setTextColor(TEXT_COLOR);
button.setPadding(0, 0, 0, 0);
button.setMinHeight(0);
button.setMinWidth(0);
button.setMinimumWidth(0);
button.setMinimumHeight(0);
button.setWidth(width);
button.setHeight(height);
button.setBackgroundColor(BUTTON_PRESSED_COLOR);
popupWindow = new PopupWindow(this);
popupWindow.setWidth(LayoutParams.WRAP_CONTENT);
popupWindow.setHeight(LayoutParams.WRAP_CONTENT);
popupWindow.setContentView(button);
popupWindow.setOutsideTouchable(true);
popupWindow.setFocusable(false);
popupWindow.showAsDropDown(view, 0, -2 * height);
}
/**
* General util function to compute the longest column length in a matrix.
*/
static int maximumLength(Object[][] matrix) {
int m = 0;
for (Object[] row : matrix)
m = Math.max(m, row.length);
return m;
}
/**
* Reload the view given parameters in termux.properties
*
* @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
* 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
*
* Examples:
* "ENTER" will trigger the ENTER keycode
* "LEFT" will trigger the LEFT keycode and be displayed as "←"
* "→" will input a "→" character
* "" will input a "" character
* "-_-" will input the string "-_-"
*/
@SuppressLint("ClickableViewAccessibility")
void reload(ExtraKeysInfos infos) {
if(infos == null)
return;
for(SpecialButtonState state : specialButtons.values())
state.button = null;
removeAllViews(); removeAllViews();
String[][] buttons = { ExtraKeyButton[][] buttons = infos.getMatrix();
{"ESC", "CTRL", "ALT", "TAB", "", "/", "|"}
};
final int rows = buttons.length; setRowCount(buttons.length);
final int cols = buttons[0].length; setColumnCount(maximumLength(buttons));
setRowCount(rows); for (int row = 0; row < buttons.length; row++) {
setColumnCount(cols); for (int col = 0; col < buttons[row].length; col++) {
final ExtraKeyButton buttonInfo = buttons[row][col];
for (int row = 0; row < rows; row++) {
for (int col = 0; col < cols; col++) {
final String buttonText = buttons[row][col];
Button button; Button button;
switch (buttonText) { if(Arrays.asList("CTRL", "ALT", "FN").contains(buttonInfo.getKey())) {
case "CTRL": SpecialButtonState state = specialButtons.get(SpecialButton.valueOf(buttonInfo.getKey())); // for valueOf: https://stackoverflow.com/a/604426/1980630
button = controlButton = new ToggleButton(getContext(), null, android.R.attr.buttonBarButtonStyle); state.isOn = true;
button.setClickable(true); button = state.button = new ToggleButton(getContext(), null, android.R.attr.buttonBarButtonStyle);
break; button.setClickable(true);
case "ALT": } else {
button = altButton = new ToggleButton(getContext(), null, android.R.attr.buttonBarButtonStyle); button = new Button(getContext(), null, android.R.attr.buttonBarButtonStyle);
button.setClickable(true);
break;
case "FN":
button = fnButton = new ToggleButton(getContext(), null, android.R.attr.buttonBarButtonStyle);
button.setClickable(true);
break;
default:
button = new Button(getContext(), null, android.R.attr.buttonBarButtonStyle);
break;
} }
button.setText(buttonText); button.setText(buttonInfo.getDisplay());
button.setTextColor(TEXT_COLOR); button.setTextColor(TEXT_COLOR);
button.setPadding(0, 0, 0, 0);
final Button finalButton = button; final Button finalButton = button;
button.setOnClickListener(new OnClickListener() { button.setOnClickListener(v -> {
@Override if (Settings.System.getInt(getContext().getContentResolver(),
public void onClick(View v) { Settings.System.HAPTIC_FEEDBACK_ENABLED, 0) != 0) {
View root = getRootView();
switch (buttonText) { if (Build.VERSION.SDK_INT >= 28) {
case "CTRL": finalButton.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP);
case "ALT": } else {
case "FN": // Perform haptic feedback only if no total silence mode enabled.
ToggleButton self = (ToggleButton) finalButton; if (Settings.Global.getInt(getContext().getContentResolver(), "zen_mode", 0) != 2) {
self.setChecked(self.isChecked()); finalButton.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP);
self.setTextColor(self.isChecked() ? 0xFF80DEEA : TEXT_COLOR); }
break;
default:
sendKey(root, buttonText);
break;
} }
} }
View root = getRootView();
if (Arrays.asList("CTRL", "ALT", "FN").contains(buttonInfo.getKey())) {
ToggleButton self = (ToggleButton) finalButton;
self.setChecked(self.isChecked());
self.setTextColor(self.isChecked() ? INTERESTING_COLOR : TEXT_COLOR);
} else {
sendKey(root, buttonInfo);
}
}); });
GridLayout.LayoutParams param = new GridLayout.LayoutParams(); button.setOnTouchListener((v, event) -> {
param.height = param.width = 0; final View root = getRootView();
param.rightMargin = param.topMargin = 0; switch (event.getAction()) {
param.setGravity(Gravity.LEFT); case MotionEvent.ACTION_DOWN:
float weight = "▲▼◀▶".contains(buttonText) ? 0.7f : 1.f; longPressCount = 0;
param.columnSpec = GridLayout.spec(col, GridLayout.FILL, weight); v.setBackgroundColor(BUTTON_PRESSED_COLOR);
if (Arrays.asList("UP", "DOWN", "LEFT", "RIGHT", "BKSP", "DEL").contains(buttonInfo.getKey())) {
// autorepeat
scheduledExecutor = Executors.newSingleThreadScheduledExecutor();
scheduledExecutor.scheduleWithFixedDelay(() -> {
longPressCount++;
sendKey(root, buttonInfo);
}, 400, 80, TimeUnit.MILLISECONDS);
}
return true;
case MotionEvent.ACTION_MOVE:
if (buttonInfo.getPopup() != null) {
if (popupWindow == null && event.getY() < 0) {
if (scheduledExecutor != null) {
scheduledExecutor.shutdownNow();
scheduledExecutor = null;
}
v.setBackgroundColor(BUTTON_COLOR);
String extraButtonDisplayedText = buttonInfo.getPopup().getDisplay();
popup(v, extraButtonDisplayedText);
}
if (popupWindow != null && event.getY() > 0) {
v.setBackgroundColor(BUTTON_PRESSED_COLOR);
popupWindow.dismiss();
popupWindow = null;
}
}
return true;
case MotionEvent.ACTION_CANCEL:
v.setBackgroundColor(BUTTON_COLOR);
if (scheduledExecutor != null) {
scheduledExecutor.shutdownNow();
scheduledExecutor = null;
}
return true;
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.dismiss();
popupWindow = null;
if (buttonInfo.getPopup() != null) {
sendKey(root, buttonInfo.getPopup());
}
} else {
v.performClick();
}
}
return true;
default:
return true;
}
});
LayoutParams param = new GridLayout.LayoutParams();
param.width = 0;
param.height = 0;
param.setMargins(0, 0, 0, 0);
param.columnSpec = GridLayout.spec(col, GridLayout.FILL, 1.f);
param.rowSpec = GridLayout.spec(row, GridLayout.FILL, 1.f); param.rowSpec = GridLayout.spec(row, GridLayout.FILL, 1.f);
button.setLayoutParams(param); button.setLayoutParams(param);

View File

@@ -1,67 +0,0 @@
package com.termux.app;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.Build;
import android.view.View;
import com.termux.R;
/**
* Utility to manage full screen immersive mode.
* <p/>
* See https://code.google.com/p/android/issues/detail?id=5497
*/
final class FullScreenHelper {
private boolean mEnabled = false;
final TermuxActivity mActivity;
public FullScreenHelper(TermuxActivity activity) {
this.mActivity = activity;
}
public void setImmersive(boolean enabled) {
if (enabled == mEnabled) return;
mEnabled = enabled;
View decorView = mActivity.getWindow().getDecorView();
if (enabled) {
decorView.setOnSystemUiVisibilityChangeListener
(new View.OnSystemUiVisibilityChangeListener() {
@Override
public void onSystemUiVisibilityChange(int visibility) {
if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) {
if (mActivity.mSettings.isShowExtraKeys()) {
mActivity.findViewById(R.id.viewpager).setVisibility(View.VISIBLE);
}
setImmersiveMode();
} else {
mActivity.findViewById(R.id.viewpager).setVisibility(View.GONE);
}
}
});
setImmersiveMode();
} else {
decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
decorView.setOnSystemUiVisibilityChangeListener(null);
}
}
private static boolean isColorLight(int color) {
double darkness = 1 - (0.299 * Color.red(color) + 0.587 * Color.green(color) + 0.114 * Color.blue(color)) / 255;
return darkness < 0.5;
}
void setImmersiveMode() {
int flags = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
| View.SYSTEM_UI_FLAG_FULLSCREEN;
int color = ((ColorDrawable) mActivity.getWindow().getDecorView().getBackground()).getColor();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && isColorLight(color))
flags |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
mActivity.getWindow().getDecorView().setSystemUiVisibility(flags);
}
}

View File

@@ -0,0 +1,189 @@
package com.termux.app;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.Service;
import android.content.Context;
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 com.termux.R;
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.
*
* Absolute 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 workdir defaults to termux home. The background mode defaults to "false".
* The command path and workdir can optionally be prefixed with "$PREFIX/" or "~/" if an absolute
* path is not to be given.
*
* To automatically bring to foreground and start termux commands that were started with
* background mode "false" in android >= 10 without user having to click the notification manually,
* requires termux to be granted draw over apps permission due to new restrictions
* of starting activities from the background, this also applies to Termux:Tasker plugin.
*
* To reduce the chance of termux being killed by android even further due to violation of not
* being able to call startForeground() within ~5s of service start in android >= 8, the user
* may disable battery optimizations for termux.
*
* 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";
private static final String NOTIFICATION_CHANNEL_ID = "termux_run_command_notification_channel";
private static final int NOTIFICATION_ID = 1338;
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;
}
@Override
public void onCreate() {
runStartForeground();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// Run again in case service is already started and onCreate() is not called
runStartForeground();
if (allowExternalApps() && RUN_COMMAND_ACTION.equals(intent.getAction())) {
Uri programUri = new Uri.Builder().scheme("com.termux.file").path(parsePath(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, parsePath(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);
}
}
runStopForeground();
return Service.START_NOT_STICKY;
}
private void runStartForeground() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
setupNotificationChannel();
startForeground(NOTIFICATION_ID, buildNotification());
}
}
private void runStopForeground() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
stopForeground(true);
}
}
private Notification buildNotification() {
Notification.Builder builder = new Notification.Builder(this);
builder.setContentTitle(getText(R.string.application_name) + " Run Command");
builder.setSmallIcon(R.drawable.ic_service_notification);
// Use a low priority:
builder.setPriority(Notification.PRIORITY_LOW);
// No need to show a timestamp:
builder.setShowWhen(false);
// Background color for small notification icon:
builder.setColor(0xFF607D8B);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
builder.setChannelId(NOTIFICATION_CHANNEL_ID);
}
return builder.build();
}
private void setupNotificationChannel() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return;
String channelName = "Termux Run Command";
int importance = NotificationManager.IMPORTANCE_LOW;
NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, channelName, importance);
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
manager.createNotificationChannel(channel);
}
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");
}
/** Replace "$PREFIX/" or "~/" prefix with termux absolute paths */
private String parsePath(String path) {
if(path != null && !path.isEmpty()) {
path = path.replaceAll("^\\$PREFIX\\/", TermuxService.PREFIX_PATH + "/");
path = path.replaceAll("^~\\/", TermuxService.HOME_PATH + "/");
}
return path;
}
}

View File

@@ -11,8 +11,6 @@ import android.content.ClipData;
import android.content.ClipboardManager; import android.content.ClipboardManager;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnShowListener;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.ServiceConnection; import android.content.ServiceConnection;
@@ -26,12 +24,6 @@ import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.IBinder; import android.os.IBinder;
import android.os.Vibrator;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v4.widget.DrawerLayout;
import android.text.SpannableString; import android.text.SpannableString;
import android.text.Spanned; import android.text.Spanned;
import android.text.TextUtils; import android.text.TextUtils;
@@ -40,21 +32,14 @@ import android.util.Log;
import android.view.ContextMenu; import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo; import android.view.ContextMenu.ContextMenuInfo;
import android.view.Gravity; import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.WindowManager; import android.view.WindowManager;
import android.view.animation.Animation; import android.view.autofill.AutofillManager;
import android.view.animation.AnimationUtils;
import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.ArrayAdapter; import android.widget.ArrayAdapter;
import android.widget.EditText; import android.widget.EditText;
import android.widget.ListView; import android.widget.ListView;
@@ -75,10 +60,17 @@ import java.io.InputStream;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List;
import java.util.Properties; import java.util.Properties;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.viewpager.widget.PagerAdapter;
import androidx.viewpager.widget.ViewPager;
/** /**
* A terminal emulator activity. * A terminal emulator activity.
* <p/> * <p/>
@@ -91,14 +83,17 @@ import java.util.regex.Pattern;
*/ */
public final class TermuxActivity extends Activity implements ServiceConnection { public final class TermuxActivity extends Activity implements ServiceConnection {
public static final String TERMUX_FAILSAFE_SESSION_ACTION = "com.termux.app.failsafe_session";
private static final int CONTEXTMENU_SELECT_URL_ID = 0; private static final int CONTEXTMENU_SELECT_URL_ID = 0;
private static final int CONTEXTMENU_SHARE_TRANSCRIPT_ID = 1; private static final int CONTEXTMENU_SHARE_TRANSCRIPT_ID = 1;
private static final int CONTEXTMENU_PASTE_ID = 3; private static final int CONTEXTMENU_PASTE_ID = 3;
private static final int CONTEXTMENU_KILL_PROCESS_ID = 4; private static final int CONTEXTMENU_KILL_PROCESS_ID = 4;
private static final int CONTEXTMENU_RESET_TERMINAL_ID = 5; private static final int CONTEXTMENU_RESET_TERMINAL_ID = 5;
private static final int CONTEXTMENU_STYLING_ID = 6; private static final int CONTEXTMENU_STYLING_ID = 6;
private static final int CONTEXTMENU_TOGGLE_FULLSCREEN_ID = 7;
private static final int CONTEXTMENU_HELP_ID = 8; private static final int CONTEXTMENU_HELP_ID = 8;
private static final int CONTEXTMENU_TOGGLE_KEEP_SCREEN_ON = 9;
private static final int CONTEXTMENU_AUTOFILL_ID = 10;
private static final int MAX_SESSIONS = 8; private static final int MAX_SESSIONS = 8;
@@ -113,8 +108,6 @@ public final class TermuxActivity extends Activity implements ServiceConnection
ExtraKeysView mExtraKeysView; ExtraKeysView mExtraKeysView;
final FullScreenHelper mFullScreenHelper = new FullScreenHelper(this);
TermuxPreferences mSettings; TermuxPreferences mSettings;
/** /**
@@ -136,13 +129,13 @@ public final class TermuxActivity extends Activity implements ServiceConnection
*/ */
boolean mIsVisible; boolean mIsVisible;
boolean mIsUsingBlackUI;
final SoundPool mBellSoundPool = new SoundPool.Builder().setMaxStreams(1).setAudioAttributes( final SoundPool mBellSoundPool = new SoundPool.Builder().setMaxStreams(1).setAudioAttributes(
new AudioAttributes.Builder().setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) new AudioAttributes.Builder().setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION).build()).build(); .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION).build()).build();
int mBellSoundId; int mBellSoundId;
Animation mOnBellAnimation;
private final BroadcastReceiver mBroadcastReceiever = new BroadcastReceiver() { private final BroadcastReceiver mBroadcastReceiever = new BroadcastReceiver() {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
@@ -155,13 +148,16 @@ public final class TermuxActivity extends Activity implements ServiceConnection
} }
checkForFontAndColors(); checkForFontAndColors();
mSettings.reloadFromProperties(TermuxActivity.this); mSettings.reloadFromProperties(TermuxActivity.this);
if (mExtraKeysView != null) {
mExtraKeysView.reload(mSettings.mExtraKeys);
}
} }
} }
}; };
void checkForFontAndColors() { void checkForFontAndColors() {
try { try {
// Hard-coded paths since this file is used also in Termux:Float.
@SuppressLint("SdCardPath") File fontFile = new File("/data/data/com.termux/files/home/.termux/font.ttf"); @SuppressLint("SdCardPath") File fontFile = new File("/data/data/com.termux/files/home/.termux/font.ttf");
@SuppressLint("SdCardPath") File colorsFile = new File("/data/data/com.termux/files/home/.termux/colors.properties"); @SuppressLint("SdCardPath") File colorsFile = new File("/data/data/com.termux/files/home/.termux/colors.properties");
@@ -194,39 +190,49 @@ public final class TermuxActivity extends Activity implements ServiceConnection
} }
/** For processes to access shared internal storage (/sdcard) we need this permission. */ /** For processes to access shared internal storage (/sdcard) we need this permission. */
@TargetApi(Build.VERSION_CODES.M)
public boolean ensureStoragePermissionGranted() { public boolean ensureStoragePermissionGranted() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
return true;
} else {
requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUESTCODE_PERMISSION_STORAGE);
return false;
}
} else {
// Always granted before Android 6.0.
return true; return true;
} else {
requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUESTCODE_PERMISSION_STORAGE);
return false;
} }
} }
@Override @Override
public void onCreate(Bundle bundle) { public void onCreate(Bundle bundle) {
mSettings = new TermuxPreferences(this);
mIsUsingBlackUI = mSettings.isUsingBlackUI();
if (mIsUsingBlackUI) {
this.setTheme(R.style.Theme_Termux_Black);
} else {
this.setTheme(R.style.Theme_Termux);
}
super.onCreate(bundle); super.onCreate(bundle);
mOnBellAnimation = AnimationUtils.loadAnimation(this, R.anim.on_bell);
mSettings = new TermuxPreferences(this);
setContentView(R.layout.drawer_layout); setContentView(R.layout.drawer_layout);
mTerminalView = (TerminalView) findViewById(R.id.terminal_view);
mTerminalView.setOnKeyListener(new TermuxKeyListener(this)); if (mIsUsingBlackUI) {
findViewById(R.id.left_drawer).setBackgroundColor(
getResources().getColor(android.R.color.background_dark)
);
}
mTerminalView = findViewById(R.id.terminal_view);
mTerminalView.setOnKeyListener(new TermuxViewClient(this));
mTerminalView.setTextSize(mSettings.getFontSize()); mTerminalView.setTextSize(mSettings.getFontSize());
mFullScreenHelper.setImmersive(mSettings.isFullScreen()); mTerminalView.setKeepScreenOn(mSettings.isScreenAlwaysOn());
mTerminalView.requestFocus(); mTerminalView.requestFocus();
final ViewPager viewPager = (ViewPager) findViewById(R.id.viewpager); final ViewPager viewPager = findViewById(R.id.viewpager);
if (mSettings.isShowExtraKeys()) viewPager.setVisibility(View.VISIBLE); if (mSettings.mShowExtraKeys) viewPager.setVisibility(View.VISIBLE);
ViewGroup.LayoutParams layoutParams = viewPager.getLayoutParams();
layoutParams.height = layoutParams.height * (mSettings.mExtraKeys == null ? 0 : mSettings.mExtraKeys.getMatrix().length);
viewPager.setLayoutParams(layoutParams);
viewPager.setAdapter(new PagerAdapter() { viewPager.setAdapter(new PagerAdapter() {
@Override @Override
@@ -235,33 +241,34 @@ public final class TermuxActivity extends Activity implements ServiceConnection
} }
@Override @Override
public boolean isViewFromObject(View view, Object object) { public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
return view == object; return view == object;
} }
@NonNull
@Override @Override
public Object instantiateItem(ViewGroup collection, int position) { public Object instantiateItem(@NonNull ViewGroup collection, int position) {
LayoutInflater inflater = LayoutInflater.from(TermuxActivity.this); LayoutInflater inflater = LayoutInflater.from(TermuxActivity.this);
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);
} 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 = (EditText) layout.findViewById(R.id.text_input); final EditText editText = layout.findViewById(R.id.text_input);
editText.setOnEditorActionListener(new TextView.OnEditorActionListener() { editText.setOnEditorActionListener((v, actionId, event) -> {
@Override TerminalSession session = getCurrentTermSession();
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { if (session != null) {
TerminalSession session = getCurrentTermSession(); if (session.isRunning()) {
if (session != null) { String textToSend = editText.getText().toString();
if (session.isRunning()) { if (textToSend.length() == 0) textToSend = "\r";
session.write(editText.getText().toString() + "\n"); session.write(textToSend);
} else { } else {
removeFinishedSession(session); removeFinishedSession(session);
}
editText.setText("");
} }
return true; editText.setText("");
} }
return true;
}); });
} }
collection.addView(layout); collection.addView(layout);
@@ -269,7 +276,7 @@ public final class TermuxActivity extends Activity implements ServiceConnection
} }
@Override @Override
public void destroyItem(ViewGroup collection, int position, Object view) { public void destroyItem(@NonNull ViewGroup collection, int position, @NonNull Object view) {
collection.removeView((View) view); collection.removeView((View) view);
} }
}); });
@@ -280,54 +287,30 @@ public final class TermuxActivity extends Activity implements ServiceConnection
if (position == 0) { if (position == 0) {
mTerminalView.requestFocus(); mTerminalView.requestFocus();
} else { } else {
final EditText editText = (EditText) viewPager.findViewById(R.id.text_input); final EditText editText = viewPager.findViewById(R.id.text_input);
if (editText != null) editText.requestFocus(); if (editText != null) editText.requestFocus();
} }
} }
}); });
View newSessionButton = findViewById(R.id.new_session_button); View newSessionButton = findViewById(R.id.new_session_button);
newSessionButton.setOnClickListener(new OnClickListener() { newSessionButton.setOnClickListener(v -> addNewSession(false, null));
@Override newSessionButton.setOnLongClickListener(v -> {
public void onClick(View v) { DialogUtils.textInput(TermuxActivity.this, R.string.session_new_named_title, null, R.string.session_new_named_positive_button,
addNewSession(false, null); text -> addNewSession(false, text), R.string.new_session_failsafe, text -> addNewSession(true, text)
} , -1, null, null);
}); return true;
newSessionButton.setOnLongClickListener(new OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
DialogUtils.textInput(TermuxActivity.this, R.string.session_new_named_title, null, R.string.session_new_named_positive_button,
new DialogUtils.TextSetListener() {
@Override
public void onTextSet(String text) {
addNewSession(false, text);
}
}, R.string.new_session_failsafe, new DialogUtils.TextSetListener() {
@Override
public void onTextSet(String text) {
addNewSession(true, text);
}
}
, -1, null, null);
return true;
}
}); });
findViewById(R.id.toggle_keyboard_button).setOnClickListener(new OnClickListener() { findViewById(R.id.toggle_keyboard_button).setOnClickListener(v -> {
@Override InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
public void onClick(View v) { imm.toggleSoftInput(InputMethodManager.SHOW_IMPLICIT, 0);
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); getDrawer().closeDrawers();
imm.toggleSoftInput(InputMethodManager.SHOW_IMPLICIT, 0);
getDrawer().closeDrawers();
}
}); });
findViewById(R.id.toggle_keyboard_button).setOnLongClickListener(new OnLongClickListener() { findViewById(R.id.toggle_keyboard_button).setOnLongClickListener(v -> {
@Override toggleShowExtraKeys();
public boolean onLongClick(View v) { return true;
toggleShowExtraKeys();
return true;
}
}); });
registerForContextMenu(mTerminalView); registerForContextMenu(mTerminalView);
@@ -344,7 +327,7 @@ public final class TermuxActivity extends Activity implements ServiceConnection
} }
void toggleShowExtraKeys() { void toggleShowExtraKeys() {
final ViewPager viewPager = (ViewPager) findViewById(R.id.viewpager); final ViewPager viewPager = findViewById(R.id.viewpager);
final boolean showNow = mSettings.toggleShowExtraKeys(TermuxActivity.this); final boolean showNow = mSettings.toggleShowExtraKeys(TermuxActivity.this);
viewPager.setVisibility(showNow ? View.VISIBLE : View.GONE); viewPager.setVisibility(showNow ? View.VISIBLE : View.GONE);
if (showNow && viewPager.getCurrentItem() == 1) { if (showNow && viewPager.getCurrentItem() == 1) {
@@ -395,13 +378,27 @@ public final class TermuxActivity extends Activity implements ServiceConnection
if (indexOfSession >= 0) if (indexOfSession >= 0)
showToast(toToastTitle(finishedSession) + " - exited", true); showToast(toToastTitle(finishedSession) + " - exited", true);
} }
if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
// On Android TV devices we need to use older behaviour because we may
// not be able to have multiple launcher icons.
if (mTermService.getSessions().size() > 1) {
removeFinishedSession(finishedSession);
}
} else {
// Once we have a separate launcher icon for the failsafe session, it
// should be safe to auto-close session on exit code '0' or '130'.
if (finishedSession.getExitStatus() == 0 || finishedSession.getExitStatus() == 130) {
removeFinishedSession(finishedSession);
}
}
mListViewAdapter.notifyDataSetChanged(); mListViewAdapter.notifyDataSetChanged();
} }
@Override @Override
public void onClipboardText(TerminalSession session, String text) { public void onClipboardText(TerminalSession session, String text) {
if (!mIsVisible) return; if (!mIsVisible) return;
showToast("Clipboard:\n\"" + text + "\"", false);
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
clipboard.setPrimaryClip(new ClipData(null, new String[]{"text/plain"}, new ClipData.Item(text))); clipboard.setPrimaryClip(new ClipData(null, new String[]{"text/plain"}, new ClipData.Item(text)));
} }
@@ -410,14 +407,12 @@ public final class TermuxActivity extends Activity implements ServiceConnection
public void onBell(TerminalSession session) { public void onBell(TerminalSession session) {
if (!mIsVisible) return; if (!mIsVisible) return;
mTerminalView.startAnimation(mOnBellAnimation);
switch (mSettings.mBellBehaviour) { switch (mSettings.mBellBehaviour) {
case TermuxPreferences.BELL_BEEP: case TermuxPreferences.BELL_BEEP:
mBellSoundPool.play(mBellSoundId, 1.f, 1.f, 1, 0, 1.f); mBellSoundPool.play(mBellSoundId, 1.f, 1.f, 1, 0, 1.f);
break; break;
case TermuxPreferences.BELL_VIBRATE: case TermuxPreferences.BELL_VIBRATE:
((Vibrator) getSystemService(VIBRATOR_SERVICE)).vibrate(50); BellUtil.getInstance(TermuxActivity.this).doBell();
break; break;
case TermuxPreferences.BELL_IGNORE: case TermuxPreferences.BELL_IGNORE:
// Ignore the bell character. // Ignore the bell character.
@@ -432,7 +427,7 @@ public final class TermuxActivity extends Activity implements ServiceConnection
} }
}; };
ListView listView = (ListView) findViewById(R.id.left_drawer_list); ListView listView = findViewById(R.id.left_drawer_list);
mListViewAdapter = new ArrayAdapter<TerminalSession>(getApplicationContext(), R.layout.line_in_drawer, mTermService.getSessions()) { mListViewAdapter = new ArrayAdapter<TerminalSession>(getApplicationContext(), R.layout.line_in_drawer, mTermService.getSessions()) {
final StyleSpan boldSpan = new StyleSpan(Typeface.BOLD); final StyleSpan boldSpan = new StyleSpan(Typeface.BOLD);
final StyleSpan italicSpan = new StyleSpan(Typeface.ITALIC); final StyleSpan italicSpan = new StyleSpan(Typeface.ITALIC);
@@ -449,8 +444,12 @@ public final class TermuxActivity extends Activity implements ServiceConnection
TerminalSession sessionAtRow = getItem(position); TerminalSession sessionAtRow = getItem(position);
boolean sessionRunning = sessionAtRow.isRunning(); boolean sessionRunning = sessionAtRow.isRunning();
TextView firstLineView = (TextView) row.findViewById(R.id.row_line); TextView firstLineView = row.findViewById(R.id.row_line);
if (mIsUsingBlackUI) {
firstLineView.setBackground(
getResources().getDrawable(R.drawable.selected_session_background_black)
);
}
String name = sessionAtRow.mSessionName; String name = sessionAtRow.mSessionName;
String sessionTitle = sessionAtRow.getTitle(); String sessionTitle = sessionAtRow.getTitle();
@@ -470,51 +469,37 @@ public final class TermuxActivity extends Activity implements ServiceConnection
} else { } else {
firstLineView.setPaintFlags(firstLineView.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG); firstLineView.setPaintFlags(firstLineView.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
} }
int color = sessionRunning || sessionAtRow.getExitStatus() == 0 ? Color.BLACK : Color.RED; int defaultColor = mIsUsingBlackUI ? Color.WHITE : Color.BLACK;
int color = sessionRunning || sessionAtRow.getExitStatus() == 0 ? defaultColor : Color.RED;
firstLineView.setTextColor(color); firstLineView.setTextColor(color);
return row; return row;
} }
}; };
listView.setAdapter(mListViewAdapter); listView.setAdapter(mListViewAdapter);
listView.setOnItemClickListener(new OnItemClickListener() { listView.setOnItemClickListener((parent, view, position, id) -> {
@Override TerminalSession clickedSession = mListViewAdapter.getItem(position);
public void onItemClick(AdapterView<?> parent, View view, int position, long id) { switchToSession(clickedSession);
TerminalSession clickedSession = mListViewAdapter.getItem(position); getDrawer().closeDrawers();
switchToSession(clickedSession);
getDrawer().closeDrawers();
}
}); });
listView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() { listView.setOnItemLongClickListener((parent, view, position, id) -> {
@Override final TerminalSession selectedSession = mListViewAdapter.getItem(position);
public boolean onItemLongClick(AdapterView<?> parent, View view, final int position, long id) { renameSession(selectedSession);
final TerminalSession selectedSession = mListViewAdapter.getItem(position); return true;
renameSession(selectedSession);
return true;
}
}); });
if (mTermService.getSessions().isEmpty()) { if (mTermService.getSessions().isEmpty()) {
if (mIsVisible) { if (mIsVisible) {
TermuxInstaller.setupIfNeeded(TermuxActivity.this, new Runnable() { TermuxInstaller.setupIfNeeded(TermuxActivity.this, () -> {
@Override if (mTermService == null) return; // Activity might have been destroyed.
public void run() { try {
if (mTermService == null) return; // Activity might have been destroyed. Bundle bundle = getIntent().getExtras();
try { boolean launchFailsafe = false;
if (TermuxPreferences.isShowWelcomeDialog(TermuxActivity.this)) { if (bundle != null) {
new AlertDialog.Builder(TermuxActivity.this).setTitle(R.string.welcome_dialog_title).setMessage(R.string.welcome_dialog_body) launchFailsafe = bundle.getBoolean(TERMUX_FAILSAFE_SESSION_ACTION, false);
.setCancelable(false).setPositiveButton(android.R.string.ok, null)
.setNegativeButton(R.string.welcome_dialog_dont_show_again_button, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
TermuxPreferences.disableWelcomeDialog(TermuxActivity.this);
dialog.dismiss();
}
}).show();
}
addNewSession(false, null);
} catch (WindowManager.BadTokenException e) {
// Activity finished - ignore.
} }
addNewSession(launchFailsafe, null);
} catch (WindowManager.BadTokenException e) {
// Activity finished - ignore.
} }
}); });
} else { } else {
@@ -525,7 +510,8 @@ public final class TermuxActivity extends Activity implements ServiceConnection
Intent i = getIntent(); Intent i = getIntent();
if (i != null && Intent.ACTION_RUN.equals(i.getAction())) { if (i != null && Intent.ACTION_RUN.equals(i.getAction())) {
// Android 7.1 app shortcut from res/xml/shortcuts.xml. // Android 7.1 app shortcut from res/xml/shortcuts.xml.
addNewSession(false, null); boolean failSafe = i.getBooleanExtra(TERMUX_FAILSAFE_SESSION_ACTION, false);
addNewSession(failSafe, null);
} else { } else {
switchToSession(getStoredCurrentSessionOrLast()); switchToSession(getStoredCurrentSessionOrLast());
} }
@@ -545,21 +531,16 @@ public final class TermuxActivity extends Activity implements ServiceConnection
@SuppressLint("InflateParams") @SuppressLint("InflateParams")
void renameSession(final TerminalSession sessionToRename) { void renameSession(final TerminalSession sessionToRename) {
DialogUtils.textInput(this, R.string.session_rename_title, sessionToRename.mSessionName, R.string.session_rename_positive_button, new DialogUtils.TextSetListener() { DialogUtils.textInput(this, R.string.session_rename_title, sessionToRename.mSessionName, R.string.session_rename_positive_button, text -> {
@Override sessionToRename.mSessionName = text;
public void onTextSet(String text) { mListViewAdapter.notifyDataSetChanged();
sessionToRename.mSessionName = text;
mListViewAdapter.notifyDataSetChanged();
}
}, -1, null, -1, null, null); }, -1, null, -1, null, null);
} }
@Override @Override
public void onServiceDisconnected(ComponentName name) { public void onServiceDisconnected(ComponentName name) {
if (mTermService != null) { // Respect being stopped from the TermuxService notification action.
// Respect being stopped from the TermuxService notification action. finish();
finish();
}
} }
@Nullable @Nullable
@@ -624,8 +605,9 @@ public final class TermuxActivity extends Activity implements ServiceConnection
new AlertDialog.Builder(this).setTitle(R.string.max_terminals_reached_title).setMessage(R.string.max_terminals_reached_message) new AlertDialog.Builder(this).setTitle(R.string.max_terminals_reached_title).setMessage(R.string.max_terminals_reached_message)
.setPositiveButton(android.R.string.ok, null).show(); .setPositiveButton(android.R.string.ok, null).show();
} else { } else {
String executablePath = (failSafe ? "/system/bin/sh" : null); TerminalSession currentSession = getCurrentTermSession();
TerminalSession newSession = mTermService.createTermSession(executablePath, null, null, failSafe); String workingDirectory = (currentSession == null) ? null : currentSession.getCwd();
TerminalSession newSession = mTermService.createTermSession(null, null, workingDirectory, failSafe);
if (sessionName != null) { if (sessionName != null) {
newSession.mSessionName = sessionName; newSession.mSessionName = sessionName;
} }
@@ -663,7 +645,7 @@ public final class TermuxActivity extends Activity implements ServiceConnection
final int indexOfSession = mTermService.getSessions().indexOf(session); final int indexOfSession = mTermService.getSessions().indexOf(session);
showToast(toToastTitle(session), false); showToast(toToastTitle(session), false);
mListViewAdapter.notifyDataSetChanged(); mListViewAdapter.notifyDataSetChanged();
final ListView lv = ((ListView) findViewById(R.id.left_drawer_list)); final ListView lv = findViewById(R.id.left_drawer_list);
lv.setItemChecked(indexOfSession, true); lv.setItemChecked(indexOfSession, true);
lv.smoothScrollToPosition(indexOfSession); lv.smoothScrollToPosition(indexOfSession);
} }
@@ -675,10 +657,16 @@ public final class TermuxActivity extends Activity implements ServiceConnection
menu.add(Menu.NONE, CONTEXTMENU_SELECT_URL_ID, Menu.NONE, R.string.select_url); menu.add(Menu.NONE, CONTEXTMENU_SELECT_URL_ID, Menu.NONE, R.string.select_url);
menu.add(Menu.NONE, CONTEXTMENU_SHARE_TRANSCRIPT_ID, Menu.NONE, R.string.select_all_and_share); menu.add(Menu.NONE, CONTEXTMENU_SHARE_TRANSCRIPT_ID, Menu.NONE, R.string.select_all_and_share);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
AutofillManager autofillManager = getSystemService(AutofillManager.class);
if (autofillManager != null && autofillManager.isEnabled()) {
menu.add(Menu.NONE, CONTEXTMENU_AUTOFILL_ID, Menu.NONE, R.string.autofill_password);
}
}
menu.add(Menu.NONE, CONTEXTMENU_RESET_TERMINAL_ID, Menu.NONE, R.string.reset_terminal); menu.add(Menu.NONE, CONTEXTMENU_RESET_TERMINAL_ID, Menu.NONE, R.string.reset_terminal);
menu.add(Menu.NONE, CONTEXTMENU_KILL_PROCESS_ID, Menu.NONE, getResources().getString(R.string.kill_process, getCurrentTermSession().getPid())).setEnabled(currentSession.isRunning()); menu.add(Menu.NONE, CONTEXTMENU_KILL_PROCESS_ID, Menu.NONE, getResources().getString(R.string.kill_process, getCurrentTermSession().getPid())).setEnabled(currentSession.isRunning());
menu.add(Menu.NONE, CONTEXTMENU_TOGGLE_FULLSCREEN_ID, Menu.NONE, R.string.toggle_fullscreen).setCheckable(true).setChecked(mSettings.isFullScreen());
menu.add(Menu.NONE, CONTEXTMENU_STYLING_ID, Menu.NONE, R.string.style_terminal); menu.add(Menu.NONE, CONTEXTMENU_STYLING_ID, Menu.NONE, R.string.style_terminal);
menu.add(Menu.NONE, CONTEXTMENU_TOGGLE_KEEP_SCREEN_ON, Menu.NONE, R.string.toggle_keep_screen_on).setCheckable(true).setChecked(mSettings.isScreenAlwaysOn());
menu.add(Menu.NONE, CONTEXTMENU_HELP_ID, Menu.NONE, R.string.help); menu.add(Menu.NONE, CONTEXTMENU_HELP_ID, Menu.NONE, R.string.help);
} }
@@ -690,65 +678,126 @@ public final class TermuxActivity extends Activity implements ServiceConnection
} }
static LinkedHashSet<CharSequence> extractUrls(String text) { static LinkedHashSet<CharSequence> extractUrls(String text) {
// Pattern for recognizing a URL, based off RFC 3986
// http://stackoverflow.com/questions/5713558/detect-and-extract-url-from-a-string StringBuilder regex_sb = new StringBuilder();
regex_sb.append("("); // Begin first matching group.
regex_sb.append("(?:"); // Begin scheme group.
regex_sb.append("dav|"); // The DAV proto.
regex_sb.append("dict|"); // The DICT proto.
regex_sb.append("dns|"); // The DNS proto.
regex_sb.append("file|"); // File path.
regex_sb.append("finger|"); // The Finger proto.
regex_sb.append("ftp(?:s?)|"); // The FTP proto.
regex_sb.append("git|"); // The Git proto.
regex_sb.append("gopher|"); // The Gopher proto.
regex_sb.append("http(?:s?)|"); // The HTTP proto.
regex_sb.append("imap(?:s?)|"); // The IMAP proto.
regex_sb.append("irc(?:[6s]?)|"); // The IRC proto.
regex_sb.append("ip[fn]s|"); // The IPFS proto.
regex_sb.append("ldap(?:s?)|"); // The LDAP proto.
regex_sb.append("pop3(?:s?)|"); // The POP3 proto.
regex_sb.append("redis(?:s?)|"); // The Redis proto.
regex_sb.append("rsync|"); // The Rsync proto.
regex_sb.append("rtsp(?:[su]?)|"); // The RTSP proto.
regex_sb.append("sftp|"); // The SFTP proto.
regex_sb.append("smb(?:s?)|"); // The SAMBA proto.
regex_sb.append("smtp(?:s?)|"); // The SMTP proto.
regex_sb.append("svn(?:(?:\\+ssh)?)|"); // The Subversion proto.
regex_sb.append("tcp|"); // The TCP proto.
regex_sb.append("telnet|"); // The Telnet proto.
regex_sb.append("tftp|"); // The TFTP proto.
regex_sb.append("udp|"); // The UDP proto.
regex_sb.append("vnc|"); // The VNC proto.
regex_sb.append("ws(?:s?)"); // The Websocket proto.
regex_sb.append(")://"); // End scheme group.
regex_sb.append(")"); // End first matching group.
// Begin second matching group.
regex_sb.append("(");
// User name and/or password in format 'user:pass@'.
regex_sb.append("(?:\\S+(?::\\S*)?@)?");
// Begin host group.
regex_sb.append("(?:");
// IP address (from http://www.regular-expressions.info/examples.html).
regex_sb.append("(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)|");
// Host name or domain.
regex_sb.append("(?:(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)(?:(?:\\.(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)*(?:\\.(?:[a-z\\u00a1-\\uffff]{2,})))?|");
// Just path. Used in case of 'file://' scheme.
regex_sb.append("/(?:(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)");
// End host group.
regex_sb.append(")");
// Port number.
regex_sb.append("(?::\\d{1,5})?");
// Resource path with optional query string.
regex_sb.append("(?:/[a-zA-Z0-9:@%\\-._~!$&()*+,;=?/]*)?");
// Fragment.
regex_sb.append("(?:#[a-zA-Z0-9:@%\\-._~!$&()*+,;=?/]*)?");
// End second matching group.
regex_sb.append(")");
final Pattern urlPattern = Pattern.compile( final Pattern urlPattern = Pattern.compile(
"(?:^|[\\W])((ht|f)tp(s?)://|www\\.)" + "(([\\w\\-]+\\.)+?([\\w\\-.~]+/?)*" + "[\\p{Alnum}.,%_=?&#\\-+()\\[\\]\\*$~@!:/{};']*)", regex_sb.toString(),
Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
LinkedHashSet<CharSequence> urlSet = new LinkedHashSet<>(); LinkedHashSet<CharSequence> urlSet = new LinkedHashSet<>();
Matcher matcher = urlPattern.matcher(text); Matcher matcher = urlPattern.matcher(text);
while (matcher.find()) { while (matcher.find()) {
int matchStart = matcher.start(1); int matchStart = matcher.start(1);
int matchEnd = matcher.end(); int matchEnd = matcher.end();
String url = text.substring(matchStart, matchEnd); String url = text.substring(matchStart, matchEnd);
urlSet.add(url); urlSet.add(url);
} }
return urlSet; return urlSet;
} }
void showUrlSelection() { void showUrlSelection() {
String text = getCurrentTermSession().getEmulator().getScreen().getTranscriptText(); String text = getCurrentTermSession().getEmulator().getScreen().getTranscriptTextWithFullLinesJoined();
LinkedHashSet<CharSequence> urlSet = extractUrls(text); LinkedHashSet<CharSequence> urlSet = extractUrls(text);
if (urlSet.isEmpty()) { if (urlSet.isEmpty()) {
new AlertDialog.Builder(this).setMessage(R.string.select_url_no_found).show(); new AlertDialog.Builder(this).setMessage(R.string.select_url_no_found).show();
return; return;
} }
final CharSequence[] urls = urlSet.toArray(new CharSequence[urlSet.size()]); final CharSequence[] urls = urlSet.toArray(new CharSequence[0]);
Collections.reverse(Arrays.asList(urls)); // Latest first. Collections.reverse(Arrays.asList(urls)); // Latest first.
// Click to copy url to clipboard: // Click to copy url to clipboard:
final AlertDialog dialog = new AlertDialog.Builder(TermuxActivity.this).setItems(urls, new DialogInterface.OnClickListener() { final AlertDialog dialog = new AlertDialog.Builder(TermuxActivity.this).setItems(urls, (di, which) -> {
@Override String url = (String) urls[which];
public void onClick(DialogInterface di, int which) { ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
String url = (String) urls[which]; clipboard.setPrimaryClip(new ClipData(null, new String[]{"text/plain"}, new ClipData.Item(url)));
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); Toast.makeText(TermuxActivity.this, R.string.select_url_copied_to_clipboard, Toast.LENGTH_LONG).show();
clipboard.setPrimaryClip(new ClipData(null, new String[]{"text/plain"}, new ClipData.Item(url)));
Toast.makeText(TermuxActivity.this, R.string.select_url_copied_to_clipboard, Toast.LENGTH_LONG).show();
}
}).setTitle(R.string.select_url_dialog_title).create(); }).setTitle(R.string.select_url_dialog_title).create();
// Long press to open URL: // Long press to open URL:
dialog.setOnShowListener(new OnShowListener() { dialog.setOnShowListener(di -> {
@Override ListView lv = dialog.getListView(); // this is a ListView with your "buds" in it
public void onShow(DialogInterface di) { lv.setOnItemLongClickListener((parent, view, position, id) -> {
ListView lv = dialog.getListView(); // this is a ListView with your "buds" in it dialog.dismiss();
lv.setOnItemLongClickListener(new OnItemLongClickListener() { String url = (String) urls[position];
@Override Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { try {
dialog.dismiss(); startActivity(i, null);
String url = (String) urls[position]; } catch (ActivityNotFoundException e) {
Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); // If no applications match, Android displays a system message.
try { startActivity(Intent.createChooser(i, null));
startActivity(i, null); }
} catch (ActivityNotFoundException e) { return true;
// If no applications match, Android displays a system message. });
startActivity(Intent.createChooser(i, null));
}
return true;
}
});
}
}); });
dialog.show(); dialog.show();
@@ -766,7 +815,18 @@ public final class TermuxActivity extends Activity implements ServiceConnection
if (session != null) { if (session != null) {
Intent intent = new Intent(Intent.ACTION_SEND); Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("text/plain"); intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_TEXT, session.getEmulator().getScreen().getTranscriptText().trim()); String transcriptText = session.getEmulator().getScreen().getTranscriptTextWithoutJoinedLines().trim();
// See https://github.com/termux/termux-app/issues/1166.
final int MAX_LENGTH = 100_000;
if (transcriptText.length() > MAX_LENGTH) {
int cutOffIndex = transcriptText.length() - MAX_LENGTH;
int nextNewlineIndex = transcriptText.indexOf('\n', cutOffIndex);
if (nextNewlineIndex != -1 && nextNewlineIndex != transcriptText.length() - 1) {
cutOffIndex = nextNewlineIndex + 1;
}
transcriptText = transcriptText.substring(cutOffIndex).trim();
}
intent.putExtra(Intent.EXTRA_TEXT, transcriptText);
intent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.share_transcript_title)); intent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.share_transcript_title));
startActivity(Intent.createChooser(intent, getString(R.string.share_transcript_chooser_title))); startActivity(Intent.createChooser(intent, getString(R.string.share_transcript_chooser_title)));
} }
@@ -778,12 +838,9 @@ public final class TermuxActivity extends Activity implements ServiceConnection
final AlertDialog.Builder b = new AlertDialog.Builder(this); final AlertDialog.Builder b = new AlertDialog.Builder(this);
b.setIcon(android.R.drawable.ic_dialog_alert); b.setIcon(android.R.drawable.ic_dialog_alert);
b.setMessage(R.string.confirm_kill_process); b.setMessage(R.string.confirm_kill_process);
b.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { b.setPositiveButton(android.R.string.yes, (dialog, id) -> {
@Override dialog.dismiss();
public void onClick(DialogInterface dialog, int id) { getCurrentTermSession().finishIfRunning();
dialog.dismiss();
getCurrentTermSession().finishIfRunning();
}
}); });
b.setNegativeButton(android.R.string.no, null); b.setNegativeButton(android.R.string.no, null);
b.show(); b.show();
@@ -804,21 +861,31 @@ public final class TermuxActivity extends Activity implements ServiceConnection
// The startActivity() call is not documented to throw IllegalArgumentException. // The startActivity() call is not documented to throw IllegalArgumentException.
// However, crash reporting shows that it sometimes does, so catch it here. // However, crash reporting shows that it sometimes does, so catch it here.
new AlertDialog.Builder(this).setMessage(R.string.styling_not_installed) new AlertDialog.Builder(this).setMessage(R.string.styling_not_installed)
.setPositiveButton(R.string.styling_install, new DialogInterface.OnClickListener() { .setPositiveButton(R.string.styling_install, (dialog, which) -> startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://f-droid.org/en/packages/com.termux.styling/")))).setNegativeButton(android.R.string.cancel, null).show();
@Override
public void onClick(DialogInterface dialog, int which) {
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://play.google.com/store/apps/details?id=com.termux.styling")));
}
}).setNegativeButton(android.R.string.cancel, null).show();
} }
}
return true;
case CONTEXTMENU_TOGGLE_FULLSCREEN_ID:
toggleImmersive();
return true; return true;
}
case CONTEXTMENU_HELP_ID: case CONTEXTMENU_HELP_ID:
startActivity(new Intent(this, TermuxHelpActivity.class)); startActivity(new Intent(this, TermuxHelpActivity.class));
return true; return true;
case CONTEXTMENU_TOGGLE_KEEP_SCREEN_ON: {
if(mTerminalView.getKeepScreenOn()) {
mTerminalView.setKeepScreenOn(false);
mSettings.setScreenAlwaysOn(this, false);
} else {
mTerminalView.setKeepScreenOn(true);
mSettings.setScreenAlwaysOn(this, true);
}
return true;
}
case CONTEXTMENU_AUTOFILL_ID: {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
AutofillManager autofillManager = getSystemService(AutofillManager.class);
if (autofillManager != null && autofillManager.isEnabled()) {
autofillManager.requestAutofill(mTerminalView);
}
}
}
default: default:
return super.onContextItemSelected(item); return super.onContextItemSelected(item);
} }
@@ -831,12 +898,6 @@ public final class TermuxActivity extends Activity implements ServiceConnection
} }
} }
void toggleImmersive() {
boolean newValue = !mSettings.isFullScreen();
mSettings.setFullScreen(this, newValue);
mFullScreenHelper.setImmersive(newValue);
}
void changeFontSize(boolean increase) { void changeFontSize(boolean increase) {
mSettings.changeFontSize(this, increase); mSettings.changeFontSize(this, increase);
mTerminalView.setTextSize(mSettings.getFontSize()); mTerminalView.setTextSize(mSettings.getFontSize());
@@ -855,9 +916,8 @@ public final class TermuxActivity extends Activity implements ServiceConnection
public TerminalSession getStoredCurrentSessionOrLast() { public TerminalSession getStoredCurrentSessionOrLast() {
TerminalSession stored = TermuxPreferences.getCurrentSession(this); TerminalSession stored = TermuxPreferences.getCurrentSession(this);
if (stored != null) return stored; if (stored != null) return stored;
int numberOfSessions = mTermService.getSessions().size(); List<TerminalSession> sessions = mTermService.getSessions();
if (numberOfSessions == 0) return null; return sessions.isEmpty() ? null : sessions.get(sessions.size() - 1);
return mTermService.getSessions().get(numberOfSessions - 1);
} }
/** Show a toast and dismiss the last one if still visible. */ /** Show a toast and dismiss the last one if still visible. */

View File

@@ -39,7 +39,7 @@ public final class TermuxHelpActivity extends Activity {
mWebView.setWebViewClient(new WebViewClient() { mWebView.setWebViewClient(new WebViewClient() {
@Override @Override
public boolean shouldOverrideUrlLoading(WebView view, String url) { public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (url.startsWith("https://termux.com")) { if (url.startsWith("https://wiki.termux.com")) {
// Inline help. // Inline help.
setContentView(progressLayout); setContentView(progressLayout);
return false; return false;
@@ -60,7 +60,7 @@ public final class TermuxHelpActivity extends Activity {
setContentView(mWebView); setContentView(mWebView);
} }
}); });
mWebView.loadUrl("https://termux.com/help.html"); mWebView.loadUrl("https://wiki.termux.com/wiki/Main_Page");
} }
@Override @Override

View File

@@ -4,10 +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.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.DialogInterface.OnDismissListener;
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;
@@ -19,13 +15,12 @@ import com.termux.R;
import com.termux.terminal.EmulatorDebug; import com.termux.terminal.EmulatorDebug;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException;
import java.io.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;
@@ -40,10 +35,10 @@ import java.util.zip.ZipInputStream;
* <p/> * <p/>
* (3) A staging folder, $STAGING_PREFIX, is {@link #deleteFolder(File)} if left over from broken installation below. * (3) A staging folder, $STAGING_PREFIX, is {@link #deleteFolder(File)} if left over from broken installation below.
* <p/> * <p/>
* (4) The architecture is determined and an appropriate bootstrap zip url is determined in {@link #determineZipUrl()}. * (4) The zip file is loaded from a shared library.
* <p/> * <p/>
* (5) The zip, containing entries relative to the $PREFIX, is is downloaded and extracted by a zip input stream * (5) The zip, containing entries relative to the $PREFIX, is is downloaded and extracted by a zip input stream
* continously encountering zip file entries: * continuously encountering zip file entries:
* <p/> * <p/>
* (5.1) If the zip entry encountered is SYMLINKS.txt, go through it and remember all symlinks to setup. * (5.1) If the zip entry encountered is SYMLINKS.txt, go through it and remember all symlinks to setup.
* <p/> * <p/>
@@ -59,12 +54,7 @@ final class TermuxInstaller {
boolean isPrimaryUser = um.getSerialNumberForUser(android.os.Process.myUserHandle()) == 0; boolean isPrimaryUser = um.getSerialNumberForUser(android.os.Process.myUserHandle()) == 0;
if (!isPrimaryUser) { if (!isPrimaryUser) {
new AlertDialog.Builder(activity).setTitle(R.string.bootstrap_error_title).setMessage(R.string.bootstrap_error_not_primary_user_message) new AlertDialog.Builder(activity).setTitle(R.string.bootstrap_error_title).setMessage(R.string.bootstrap_error_not_primary_user_message)
.setOnDismissListener(new OnDismissListener() { .setOnDismissListener(dialog -> System.exit(0)).setPositiveButton(android.R.string.ok, null).show();
@Override
public void onDismiss(DialogInterface dialog) {
System.exit(0);
}
}).setPositiveButton(android.R.string.ok, null).show();
return; return;
} }
@@ -89,8 +79,8 @@ final class TermuxInstaller {
final byte[] buffer = new byte[8096]; final byte[] buffer = new byte[8096];
final List<Pair<String, String>> symlinks = new ArrayList<>(50); final List<Pair<String, String>> symlinks = new ArrayList<>(50);
final URL zipUrl = determineZipUrl(); final byte[] zipBytes = loadZipBytes();
try (ZipInputStream zipInput = new ZipInputStream(zipUrl.openStream())) { try (ZipInputStream zipInput = new ZipInputStream(new ByteArrayInputStream(zipBytes))) {
ZipEntry zipEntry; ZipEntry zipEntry;
while ((zipEntry = zipInput.getNextEntry()) != null) { while ((zipEntry = zipInput.getNextEntry()) != null) {
if (zipEntry.getName().equals("SYMLINKS.txt")) { if (zipEntry.getName().equals("SYMLINKS.txt")) {
@@ -103,14 +93,17 @@ final class TermuxInstaller {
String oldPath = parts[0]; String oldPath = parts[0];
String newPath = STAGING_PREFIX_PATH + "/" + parts[1]; String newPath = STAGING_PREFIX_PATH + "/" + parts[1];
symlinks.add(Pair.create(oldPath, newPath)); symlinks.add(Pair.create(oldPath, newPath));
ensureDirectoryExists(new File(newPath).getParentFile());
} }
} else { } else {
String zipEntryName = zipEntry.getName(); String zipEntryName = zipEntry.getName();
File targetFile = new File(STAGING_PREFIX_PATH, zipEntryName); File targetFile = new File(STAGING_PREFIX_PATH, zipEntryName);
if (zipEntry.isDirectory()) { boolean isDirectory = zipEntry.isDirectory();
if (!targetFile.mkdirs())
throw new RuntimeException("Failed to create directory: " + targetFile.getAbsolutePath()); ensureDirectoryExists(isDirectory ? targetFile : targetFile.getParentFile());
} else {
if (!isDirectory) {
try (FileOutputStream outStream = new FileOutputStream(targetFile)) { try (FileOutputStream outStream = new FileOutputStream(targetFile)) {
int readBytes; int readBytes;
while ((readBytes = zipInput.read(buffer)) != -1) while ((readBytes = zipInput.read(buffer)) != -1)
@@ -135,46 +128,29 @@ final class TermuxInstaller {
throw new RuntimeException("Unable to rename staging folder"); throw new RuntimeException("Unable to rename staging folder");
} }
activity.runOnUiThread(new Runnable() { activity.runOnUiThread(whenDone);
@Override
public void run() {
whenDone.run();
}
});
} catch (final Exception e) { } catch (final Exception e) {
Log.e(EmulatorDebug.LOG_TAG, "Bootstrap error", e); Log.e(EmulatorDebug.LOG_TAG, "Bootstrap error", e);
activity.runOnUiThread(new Runnable() { activity.runOnUiThread(() -> {
@Override try {
public void run() { new AlertDialog.Builder(activity).setTitle(R.string.bootstrap_error_title).setMessage(R.string.bootstrap_error_body)
try { .setNegativeButton(R.string.bootstrap_error_abort, (dialog, which) -> {
new AlertDialog.Builder(activity).setTitle(R.string.bootstrap_error_title).setMessage(R.string.bootstrap_error_body) dialog.dismiss();
.setNegativeButton(R.string.bootstrap_error_abort, new OnClickListener() { activity.finish();
@Override }).setPositiveButton(R.string.bootstrap_error_try_again, (dialog, which) -> {
public void onClick(DialogInterface dialog, int which) { dialog.dismiss();
dialog.dismiss(); TermuxInstaller.setupIfNeeded(activity, whenDone);
activity.finish();
}
}).setPositiveButton(R.string.bootstrap_error_try_again, new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
TermuxInstaller.setupIfNeeded(activity, whenDone);
}
}).show(); }).show();
} catch (WindowManager.BadTokenException e) { } catch (WindowManager.BadTokenException e1) {
// Activity already dismissed - ignore. // Activity already dismissed - ignore.
}
} }
}); });
} finally { } finally {
activity.runOnUiThread(new Runnable() { activity.runOnUiThread(() -> {
@Override try {
public void run() { progress.dismiss();
try { } catch (RuntimeException e) {
progress.dismiss(); // Activity already dismissed - ignore.
} catch (RuntimeException e) {
// Activity already dismissed - ignore.
}
} }
}); });
} }
@@ -182,55 +158,51 @@ final class TermuxInstaller {
}.start(); }.start();
} }
/** Get bootstrap zip url for this systems cpu architecture. */ private static void ensureDirectoryExists(File directory) {
static URL determineZipUrl() throws MalformedURLException { if (!directory.isDirectory() && !directory.mkdirs()) {
String archName = determineTermuxArchName(); throw new RuntimeException("Unable to create directory: " + directory.getAbsolutePath());
return new URL("https://termux.net/bootstrap/bootstrap-" + archName + ".zip"); }
} }
private static String determineTermuxArchName() { public static byte[] loadZipBytes() {
// Note that we cannot use System.getProperty("os.arch") since that may give e.g. "aarch64" // Only load the shared library when necessary to save memory usage.
// while a 64-bit runtime may not be installed (like on the Samsung Galaxy S5 Neo). System.loadLibrary("termux-bootstrap");
// Instead we search through the supported abi:s on the device, see: return getZip();
// http://developer.android.com/ndk/guides/abis.html
// Note that we search for abi:s in preferred order (the ordering of the
// Build.SUPPORTED_ABIS list) to avoid e.g. installing arm on an x86 system where arm
// emulation is available.
for (String androidArch : Build.SUPPORTED_ABIS) {
switch (androidArch) {
case "arm64-v8a": return "aarch64";
case "armeabi-v7a": return "arm";
case "x86_64": return "x86_64";
case "x86": return "i686";
}
}
throw new RuntimeException("Unable to determine arch from Build.SUPPORTED_ABIS = " +
Arrays.toString(Build.SUPPORTED_ABIS));
} }
/** Delete a folder and all its content or throw. */ public static native byte[] getZip();
static void deleteFolder(File fileOrDirectory) {
File[] children = fileOrDirectory.listFiles(); /** Delete a folder and all its content or throw. Don't follow symlinks. */
if (children != null) { static void deleteFolder(File fileOrDirectory) throws IOException {
for (File child : children) { if (fileOrDirectory.getCanonicalPath().equals(fileOrDirectory.getAbsolutePath()) && fileOrDirectory.isDirectory()) {
deleteFolder(child); File[] children = fileOrDirectory.listFiles();
if (children != null) {
for (File child : children) {
deleteFolder(child);
}
} }
} }
if (!fileOrDirectory.delete()) { if (!fileOrDirectory.delete()) {
throw new RuntimeException("Unable to delete " + (fileOrDirectory.isDirectory() ? "directory " : "file ") + fileOrDirectory.getAbsolutePath()); throw new RuntimeException("Unable to delete " + (fileOrDirectory.isDirectory() ? "directory " : "file ") + fileOrDirectory.getAbsolutePath());
} }
} }
public static void setupStorageSymlinks(final Context context) { static void setupStorageSymlinks(final Context context) {
final String LOG_TAG = "termux-storage"; final String LOG_TAG = "termux-storage";
new Thread() { new Thread() {
public void run() { public void run() {
try { try {
File storageDir = new File(TermuxService.HOME_PATH, "storage"); File storageDir = new File(TermuxService.HOME_PATH, "storage");
if (storageDir.exists() && !storageDir.delete()) { if (storageDir.exists()) {
Log.e(LOG_TAG, "Could not delete old $HOME/storage"); try {
return; deleteFolder(storageDir);
} catch (IOException e) {
Log.e(LOG_TAG, "Could not delete old $HOME/storage, " + e.getMessage());
return;
}
} }
if (!storageDir.mkdirs()) { if (!storageDir.mkdirs()) {

View File

@@ -0,0 +1,191 @@
package com.termux.app;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.net.Uri;
import android.os.Environment;
import android.os.ParcelFileDescriptor;
import android.provider.MediaStore;
import android.util.Log;
import android.webkit.MimeTypeMap;
import com.termux.terminal.EmulatorDebug;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import androidx.annotation.NonNull;
public class TermuxOpenReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
final Uri data = intent.getData();
if (data == null) {
Log.e(EmulatorDebug.LOG_TAG, "termux-open: Called without intent data");
return;
}
final String filePath = data.getPath();
final String contentTypeExtra = intent.getStringExtra("content-type");
final boolean useChooser = intent.getBooleanExtra("chooser", false);
final String intentAction = intent.getAction() == null ? Intent.ACTION_VIEW : intent.getAction();
switch (intentAction) {
case Intent.ACTION_SEND:
case Intent.ACTION_VIEW:
// Ok.
break;
default:
Log.e(EmulatorDebug.LOG_TAG, "Invalid action '" + intentAction + "', using 'view'");
break;
}
final boolean isExternalUrl = data.getScheme() != null && !data.getScheme().equals("file");
if (isExternalUrl) {
Intent urlIntent = new Intent(intentAction, data);
if (intentAction.equals(Intent.ACTION_SEND)) {
urlIntent.putExtra(Intent.EXTRA_TEXT, data.toString());
urlIntent.setData(null);
} else if (contentTypeExtra != null) {
urlIntent.setDataAndType(data, contentTypeExtra);
}
urlIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
context.startActivity(urlIntent);
} catch (ActivityNotFoundException e) {
Log.e(EmulatorDebug.LOG_TAG, "termux-open: No app handles the url " + data);
}
return;
}
final File fileToShare = new File(filePath);
if (!(fileToShare.isFile() && fileToShare.canRead())) {
Log.e(EmulatorDebug.LOG_TAG, "termux-open: Not a readable file: '" + fileToShare.getAbsolutePath() + "'");
return;
}
Intent sendIntent = new Intent();
sendIntent.setAction(intentAction);
sendIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_GRANT_READ_URI_PERMISSION);
String contentTypeToUse;
if (contentTypeExtra == null) {
String fileName = fileToShare.getName();
int lastDotIndex = fileName.lastIndexOf('.');
String fileExtension = fileName.substring(lastDotIndex + 1);
MimeTypeMap mimeTypes = MimeTypeMap.getSingleton();
// Lower casing makes it work with e.g. "JPG":
contentTypeToUse = mimeTypes.getMimeTypeFromExtension(fileExtension.toLowerCase());
if (contentTypeToUse == null) contentTypeToUse = "application/octet-stream";
} else {
contentTypeToUse = contentTypeExtra;
}
Uri uriToShare = Uri.parse("content://com.termux.files" + fileToShare.getAbsolutePath());
if (Intent.ACTION_SEND.equals(intentAction)) {
sendIntent.putExtra(Intent.EXTRA_STREAM, uriToShare);
sendIntent.setType(contentTypeToUse);
} else {
sendIntent.setDataAndType(uriToShare, contentTypeToUse);
}
if (useChooser) {
sendIntent = Intent.createChooser(sendIntent, null).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
try {
context.startActivity(sendIntent);
} catch (ActivityNotFoundException e) {
Log.e(EmulatorDebug.LOG_TAG, "termux-open: No app handles the url " + data);
}
}
public static class ContentProvider extends android.content.ContentProvider {
@Override
public boolean onCreate() {
return true;
}
@Override
public Cursor query(@NonNull Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
File file = new File(uri.getPath());
if (projection == null) {
projection = new String[]{
MediaStore.MediaColumns.DISPLAY_NAME,
MediaStore.MediaColumns.SIZE,
MediaStore.MediaColumns._ID
};
}
Object[] row = new Object[projection.length];
for (int i = 0; i < projection.length; i++) {
String column = projection[i];
Object value;
switch (column) {
case MediaStore.MediaColumns.DISPLAY_NAME:
value = file.getName();
break;
case MediaStore.MediaColumns.SIZE:
value = (int) file.length();
break;
case MediaStore.MediaColumns._ID:
value = 1;
break;
default:
value = null;
}
row[i] = value;
}
MatrixCursor cursor = new MatrixCursor(projection);
cursor.addRow(row);
return cursor;
}
@Override
public String getType(@NonNull Uri uri) {
return null;
}
@Override
public Uri insert(@NonNull Uri uri, ContentValues values) {
return null;
}
@Override
public int delete(@NonNull Uri uri, String selection, String[] selectionArgs) {
return 0;
}
@Override
public int update(@NonNull Uri uri, ContentValues values, String selection, String[] selectionArgs) {
return 0;
}
@Override
public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) throws FileNotFoundException {
File file = new File(uri.getPath());
try {
String path = file.getCanonicalPath();
String storagePath = Environment.getExternalStorageDirectory().getCanonicalPath();
// See https://support.google.com/faqs/answer/7496913:
if (!(path.startsWith(TermuxService.FILES_PATH) || path.startsWith(storagePath))) {
throw new IllegalArgumentException("Invalid path: " + path);
}
} catch (IOException e) {
throw new IllegalArgumentException(e);
}
return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
}
}
}

View File

@@ -2,29 +2,55 @@ 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.support.annotation.IntDef;
import android.util.Log; import android.util.Log;
import android.util.TypedValue; import android.util.TypedValue;
import android.widget.Toast; import android.widget.Toast;
import com.termux.terminal.TerminalSession; import com.termux.terminal.TerminalSession;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
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;
final class TermuxPreferences { final class TermuxPreferences {
@IntDef({BELL_VIBRATE, BELL_BEEP, BELL_IGNORE}) @IntDef({BELL_VIBRATE, BELL_BEEP, BELL_IGNORE})
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
public @interface AsciiBellBehaviour { @interface AsciiBellBehaviour {
} }
final static class KeyboardShortcut {
KeyboardShortcut(int codePoint, int shortcutAction) {
this.codePoint = codePoint;
this.shortcutAction = shortcutAction;
}
final int codePoint;
final int shortcutAction;
}
static final int SHORTCUT_ACTION_CREATE_SESSION = 1;
static final int SHORTCUT_ACTION_NEXT_SESSION = 2;
static final int SHORTCUT_ACTION_PREVIOUS_SESSION = 3;
static final int SHORTCUT_ACTION_RENAME_SESSION = 4;
static final int BELL_VIBRATE = 1; static final int BELL_VIBRATE = 1;
static final int BELL_BEEP = 2; static final int BELL_BEEP = 2;
static final int BELL_IGNORE = 3; static final int BELL_IGNORE = 3;
@@ -32,21 +58,33 @@ final class TermuxPreferences {
private final int MIN_FONTSIZE; private final int MIN_FONTSIZE;
private static final int MAX_FONTSIZE = 256; private static final int MAX_FONTSIZE = 256;
private static final String FULLSCREEN_KEY = "fullscreen";
private static final String SHOW_EXTRA_KEYS_KEY = "show_extra_keys"; private static final String SHOW_EXTRA_KEYS_KEY = "show_extra_keys";
private static final String FONTSIZE_KEY = "fontsize"; private static final String FONTSIZE_KEY = "fontsize";
private static final String CURRENT_SESSION_KEY = "current_session"; private static final String CURRENT_SESSION_KEY = "current_session";
private static final String SHOW_WELCOME_DIALOG_KEY = "intro_dialog"; private static final String SCREEN_ALWAYS_ON_KEY = "screen_always_on";
private boolean mFullScreen; private boolean mUseDarkUI;
private boolean mScreenAlwaysOn;
private int mFontSize; private int mFontSize;
@AsciiBellBehaviour @AsciiBellBehaviour
int mBellBehaviour = BELL_VIBRATE; int mBellBehaviour = BELL_VIBRATE;
boolean mBackIsEscape; boolean mBackIsEscape;
boolean mDisableVolumeVirtualKeys;
boolean mShowExtraKeys; boolean mShowExtraKeys;
ExtraKeysInfos mExtraKeys;
final List<KeyboardShortcut> shortcuts = new ArrayList<>();
/**
* If value is not in the range [min, max], set it to either min or max.
*/
static int clamp(int value, int min, int max) {
return Math.min(Math.max(value, min), max);
}
TermuxPreferences(Context context) { TermuxPreferences(Context context) {
reloadFromProperties(context); reloadFromProperties(context);
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
@@ -57,8 +95,8 @@ final class TermuxPreferences {
// to prevent invisible text due to zoom be mistake: // to prevent invisible text due to zoom be mistake:
MIN_FONTSIZE = (int) (4f * dipInPixels); MIN_FONTSIZE = (int) (4f * dipInPixels);
mFullScreen = prefs.getBoolean(FULLSCREEN_KEY, false); mShowExtraKeys = prefs.getBoolean(SHOW_EXTRA_KEYS_KEY, true);
mShowExtraKeys = prefs.getBoolean(SHOW_EXTRA_KEYS_KEY, false); mScreenAlwaysOn = prefs.getBoolean(SCREEN_ALWAYS_ON_KEY, false);
// http://www.google.com/design/spec/style/typography.html#typography-line-height // http://www.google.com/design/spec/style/typography.html#typography-line-height
int defaultFontSize = Math.round(12 * dipInPixels); int defaultFontSize = Math.round(12 * dipInPixels);
@@ -70,20 +108,7 @@ final class TermuxPreferences {
} catch (NumberFormatException | ClassCastException e) { } catch (NumberFormatException | ClassCastException e) {
mFontSize = defaultFontSize; mFontSize = defaultFontSize;
} }
mFontSize = Math.max(MIN_FONTSIZE, Math.min(mFontSize, MAX_FONTSIZE)); mFontSize = clamp(mFontSize, MIN_FONTSIZE, MAX_FONTSIZE);
}
boolean isFullScreen() {
return mFullScreen;
}
void setFullScreen(Context context, boolean newValue) {
mFullScreen = newValue;
PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean(FULLSCREEN_KEY, newValue).apply();
}
boolean isShowExtraKeys() {
return mShowExtraKeys;
} }
boolean toggleShowExtraKeys(Context context) { boolean toggleShowExtraKeys(Context context) {
@@ -104,6 +129,19 @@ final class TermuxPreferences {
prefs.edit().putString(FONTSIZE_KEY, Integer.toString(mFontSize)).apply(); prefs.edit().putString(FONTSIZE_KEY, Integer.toString(mFontSize)).apply();
} }
boolean isScreenAlwaysOn() {
return mScreenAlwaysOn;
}
boolean isUsingBlackUI() {
return mUseDarkUI;
}
void setScreenAlwaysOn(Context context, boolean newValue) {
mScreenAlwaysOn = newValue;
PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean(SCREEN_ALWAYS_ON_KEY, newValue).apply();
}
static void storeCurrentSession(Context context, TerminalSession session) { static void storeCurrentSession(Context context, TerminalSession session) {
PreferenceManager.getDefaultSharedPreferences(context).edit().putString(TermuxPreferences.CURRENT_SESSION_KEY, session.mHandle).apply(); PreferenceManager.getDefaultSharedPreferences(context).edit().putString(TermuxPreferences.CURRENT_SESSION_KEY, session.mHandle).apply();
} }
@@ -117,69 +155,75 @@ final class TermuxPreferences {
return null; return null;
} }
public static boolean isShowWelcomeDialog(Context context) { void reloadFromProperties(Context context) {
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SHOW_WELCOME_DIALOG_KEY, true); File propsFile = new File(TermuxService.HOME_PATH + "/.termux/termux.properties");
} if (!propsFile.exists())
propsFile = new File(TermuxService.HOME_PATH + "/.config/termux/termux.properties");
public static void disableWelcomeDialog(Context context) { Properties props = new Properties();
PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean(SHOW_WELCOME_DIALOG_KEY, false).apply();
}
public void reloadFromProperties(Context context) {
try { try {
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();
if (propsFile.isFile() && propsFile.canRead()) { if (propsFile.isFile() && propsFile.canRead()) {
try (FileInputStream in = new FileInputStream(propsFile)) { try (FileInputStream in = new FileInputStream(propsFile)) {
props.load(in); props.load(new InputStreamReader(in, StandardCharsets.UTF_8));
} }
} }
switch (props.getProperty("bell-character", "vibrate")) {
case "beep":
mBellBehaviour = BELL_BEEP;
break;
case "ignore":
mBellBehaviour = BELL_IGNORE;
break;
default: // "vibrate".
mBellBehaviour = BELL_VIBRATE;
break;
}
mBackIsEscape = "escape".equals(props.getProperty("back-key", "back"));
shortcuts.clear();
parseAction("shortcut.create-session", SHORTCUT_ACTION_CREATE_SESSION, props);
parseAction("shortcut.next-session", SHORTCUT_ACTION_NEXT_SESSION, props);
parseAction("shortcut.previous-session", SHORTCUT_ACTION_PREVIOUS_SESSION, props);
parseAction("shortcut.rename-session", SHORTCUT_ACTION_RENAME_SESSION, props);
} catch (Exception e) { } catch (Exception e) {
Toast.makeText(context, "Error loading properties: " + e.getMessage(), 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);
} }
}
public static final int SHORTCUT_ACTION_CREATE_SESSION = 1; switch (props.getProperty("bell-character", "vibrate")) {
public static final int SHORTCUT_ACTION_NEXT_SESSION = 2; case "beep":
public static final int SHORTCUT_ACTION_PREVIOUS_SESSION = 3; mBellBehaviour = BELL_BEEP;
public static final int SHORTCUT_ACTION_RENAME_SESSION = 4; break;
case "ignore":
public final static class KeyboardShortcut { mBellBehaviour = BELL_IGNORE;
break;
public KeyboardShortcut(int codePoint, int shortcutAction) { default: // "vibrate".
this.codePoint = codePoint; mBellBehaviour = BELL_VIBRATE;
this.shortcutAction = shortcutAction; break;
} }
final int codePoint; switch (props.getProperty("use-black-ui", "").toLowerCase()) {
final int shortcutAction; 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;
}
final List<KeyboardShortcut> shortcuts = new ArrayList<>(); String defaultExtraKeys = "[[ESC, TAB, CTRL, ALT, {key: '-', popup: '|'}, DOWN, UP]]";
try {
String extrakeyProp = props.getProperty("extra-keys", defaultExtraKeys);
String extraKeysStyle = props.getProperty("extra-keys-style", "default");
mExtraKeys = new ExtraKeysInfos(extrakeyProp, extraKeysStyle);
} catch (JSONException e) {
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);
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"));
mDisableVolumeVirtualKeys = "volume".equals(props.getProperty("volume-keys", "virtual"));
shortcuts.clear();
parseAction("shortcut.create-session", SHORTCUT_ACTION_CREATE_SESSION, props);
parseAction("shortcut.next-session", SHORTCUT_ACTION_NEXT_SESSION, props);
parseAction("shortcut.previous-session", SHORTCUT_ACTION_PREVIOUS_SESSION, props);
parseAction("shortcut.rename-session", SHORTCUT_ACTION_RENAME_SESSION, props);
}
private void parseAction(String name, int shortcutAction, Properties props) { private void parseAction(String name, int shortcutAction, Properties props) {
String value = props.getProperty(name); String value = props.getProperty(name);

View File

@@ -2,18 +2,22 @@ package com.termux.app;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.Notification; import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.app.Service; import android.app.Service;
import android.content.ActivityNotFoundException;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.res.Resources; import android.content.res.Resources;
import android.net.Uri; import android.net.Uri;
import android.net.wifi.WifiManager; import android.net.wifi.WifiManager;
import android.os.Binder; import android.os.Binder;
import android.os.Build;
import android.os.Handler; import android.os.Handler;
import android.os.IBinder; import android.os.IBinder;
import android.os.PowerManager; import android.os.PowerManager;
import android.provider.Settings;
import android.util.Log; import android.util.Log;
import android.widget.ArrayAdapter; import android.widget.ArrayAdapter;
@@ -23,7 +27,6 @@ import com.termux.terminal.TerminalSession;
import com.termux.terminal.TerminalSession.SessionChangedCallback; import com.termux.terminal.TerminalSession.SessionChangedCallback;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -41,6 +44,8 @@ import java.util.List;
*/ */
public final class TermuxService extends Service implements SessionChangedCallback { public final class TermuxService extends Service implements SessionChangedCallback {
private static final String NOTIFICATION_CHANNEL_ID = "termux_notification_channel";
/** Note that this is a symlink on the Android M preview. */ /** Note that this is a symlink on the Android M preview. */
@SuppressLint("SdCardPath") @SuppressLint("SdCardPath")
public static final String FILES_PATH = "/data/data/com.termux/files"; public static final String FILES_PATH = "/data/data/com.termux/files";
@@ -58,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 {
@@ -101,13 +106,28 @@ 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();
WifiManager wm = (WifiManager) getSystemService(Context.WIFI_SERVICE); // http://tools.android.com/tech-docs/lint-in-studio-2-3#TOC-WifiManager-Leak
WifiManager wm = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
mWifiLock = wm.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, EmulatorDebug.LOG_TAG); mWifiLock = wm.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, EmulatorDebug.LOG_TAG);
mWifiLock.acquire(); mWifiLock.acquire();
String packageName = getPackageName();
if (!pm.isIgnoringBatteryOptimizations(packageName)) {
Intent whitelist = new Intent();
whitelist.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
whitelist.setData(Uri.parse("package:" + packageName));
whitelist.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
startActivity(whitelist);
} catch (ActivityNotFoundException e) {
Log.e(EmulatorDebug.LOG_TAG, "Failed to call ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS", e);
}
}
updateNotification(); updateNotification();
} }
} else if (ACTION_UNLOCK_WAKE.equals(action)) { } else if (ACTION_UNLOCK_WAKE.equals(action)) {
@@ -128,11 +148,12 @@ public final class TermuxService extends Service implements SessionChangedCallba
String cwd = intent.getStringExtra(EXTRA_CURRENT_WORKING_DIRECTORY); String cwd = intent.getStringExtra(EXTRA_CURRENT_WORKING_DIRECTORY);
if (intent.getBooleanExtra(EXTRA_EXECUTE_IN_BACKGROUND, false)) { if (intent.getBooleanExtra(EXTRA_EXECUTE_IN_BACKGROUND, false)) {
BackgroundJob task = new BackgroundJob(cwd, executablePath, arguments, this); BackgroundJob task = new BackgroundJob(cwd, executablePath, arguments, this, intent.getParcelableExtra("pendingIntent"));
mBackgroundTasks.add(task); mBackgroundTasks.add(task);
updateNotification(); updateNotification();
} else { } else {
TerminalSession newSession = createTermSession(executablePath, arguments, cwd, false); boolean failsafe = intent.getBooleanExtra(TermuxActivity.TERMUX_FAILSAFE_SESSION_ACTION, false);
TerminalSession newSession = createTermSession(executablePath, arguments, cwd, failsafe);
// Transform executable path to session name, e.g. "/bin/do-something.sh" => "do something.sh". // Transform executable path to session name, e.g. "/bin/do-something.sh" => "do something.sh".
if (executablePath != null) { if (executablePath != null) {
@@ -164,11 +185,12 @@ public final class TermuxService extends Service implements SessionChangedCallba
@Override @Override
public void onCreate() { public void onCreate() {
setupNotificationChannel();
startForeground(NOTIFICATION_ID, buildNotification()); startForeground(NOTIFICATION_ID, buildNotification());
} }
/** Update the shown foreground service notification after making any changes that affect it. */ /** Update the shown foreground service notification after making any changes that affect it. */
private void updateNotification() { void updateNotification() {
if (mWakeLock == null && mTerminalSessions.isEmpty() && mBackgroundTasks.isEmpty()) { if (mWakeLock == null && mTerminalSessions.isEmpty() && mBackgroundTasks.isEmpty()) {
// Exit if we are updating after the user disabled all locks with no sessions or tasks running. // Exit if we are updating after the user disabled all locks with no sessions or tasks running.
stopSelf(); stopSelf();
@@ -202,14 +224,18 @@ public final class TermuxService extends Service implements SessionChangedCallba
builder.setOngoing(true); builder.setOngoing(true);
// If holding a wake or wifi lock consider the notification of high priority since it's using power, // If holding a wake or wifi lock consider the notification of high priority since it's using power,
// otherwise use a minimal priority since this is just a background service notification: // otherwise use a low priority
builder.setPriority((wakeLockHeld) ? Notification.PRIORITY_HIGH : Notification.PRIORITY_MIN); builder.setPriority((wakeLockHeld) ? Notification.PRIORITY_HIGH : Notification.PRIORITY_LOW);
// No need to show a timestamp: // No need to show a timestamp:
builder.setShowWhen(false); builder.setShowWhen(false);
// Background color for small notification icon: // Background color for small notification icon:
builder.setColor(0xFF000000); builder.setColor(0xFF607D8B);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
builder.setChannelId(NOTIFICATION_CHANNEL_ID);
}
Resources res = getResources(); Resources res = getResources();
Intent exitIntent = new Intent(this, TermuxService.class).setAction(ACTION_STOP_SERVICE); Intent exitIntent = new Intent(this, TermuxService.class).setAction(ACTION_STOP_SERVICE);
@@ -228,6 +254,18 @@ public final class TermuxService extends Service implements SessionChangedCallba
@Override @Override
public void onDestroy() { public void onDestroy() {
File termuxTmpDir = new File(TermuxService.PREFIX_PATH + "/tmp");
if (termuxTmpDir.exists()) {
try {
TermuxInstaller.deleteFolder(termuxTmpDir.getCanonicalFile());
} catch (Exception e) {
Log.e(EmulatorDebug.LOG_TAG, "Error while removing file at " + termuxTmpDir.getAbsolutePath(), e);
}
termuxTmpDir.mkdirs();
}
if (mWakeLock != null) mWakeLock.release(); if (mWakeLock != null) mWakeLock.release();
if (mWifiLock != null) mWifiLock.release(); if (mWifiLock != null) mWifiLock.release();
@@ -235,7 +273,6 @@ public final class TermuxService extends Service implements SessionChangedCallba
for (int i = 0; i < mTerminalSessions.size(); i++) for (int i = 0; i < mTerminalSessions.size(); i++)
mTerminalSessions.get(i).finishIfRunning(); mTerminalSessions.get(i).finishIfRunning();
mTerminalSessions.clear();
} }
public List<TerminalSession> getSessions() { public List<TerminalSession> getSessions() {
@@ -251,23 +288,8 @@ public final class TermuxService extends Service implements SessionChangedCallba
boolean isLoginShell = false; boolean isLoginShell = false;
if (executablePath == null) { if (executablePath == null) {
File shell = new File(HOME_PATH, ".termux/shell"); if (!failSafe) {
if (shell.exists()) { for (String shellBinary : new String[]{"login", "bash", "zsh"}) {
try {
File canonicalFile = shell.getCanonicalFile();
if (canonicalFile.isFile() && canonicalFile.canExecute()) {
executablePath = canonicalFile.getName().equals("busybox") ? (PREFIX_PATH + "/bin/ash") : canonicalFile.getAbsolutePath();
} else {
Log.w(EmulatorDebug.LOG_TAG, "$HOME/.termux/shell points to non-executable shell: " + canonicalFile.getAbsolutePath());
}
} catch (IOException e) {
Log.e(EmulatorDebug.LOG_TAG, "Error checking $HOME/.termux/shell", e);
}
}
if (executablePath == null) {
// Try bash, zsh and ash in that order:
for (String shellBinary : new String[]{"bash", "zsh", "ash"}) {
File shellFile = new File(PREFIX_PATH + "/bin/" + shellBinary); File shellFile = new File(PREFIX_PATH + "/bin/" + shellBinary);
if (shellFile.canExecute()) { if (shellFile.canExecute()) {
executablePath = shellFile.getAbsolutePath(); executablePath = shellFile.getAbsolutePath();
@@ -296,6 +318,12 @@ public final class TermuxService extends Service implements SessionChangedCallba
TerminalSession session = new TerminalSession(executablePath, cwd, args, env, this); TerminalSession session = new TerminalSession(executablePath, cwd, args, env, this);
mTerminalSessions.add(session); mTerminalSessions.add(session);
updateNotification(); updateNotification();
// Make sure that terminal styling is always applied.
Intent stylingIntent = new Intent("com.termux.app.reload_style");
stylingIntent.putExtra("com.termux.app.reload_style", "styling");
sendBroadcast(stylingIntent);
return session; return session;
} }
@@ -344,12 +372,22 @@ public final class TermuxService extends Service implements SessionChangedCallba
} }
public void onBackgroundJobExited(final BackgroundJob task) { public void onBackgroundJobExited(final BackgroundJob task) {
mHandler.post(new Runnable() { mHandler.post(() -> {
@Override mBackgroundTasks.remove(task);
public void run() { updateNotification();
mBackgroundTasks.remove(task);
updateNotification();
}
}); });
} }
private void setupNotificationChannel() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return;
String channelName = "Termux";
String channelDescription = "Notifications from Termux";
int importance = NotificationManager.IMPORTANCE_LOW;
NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, channelName,importance);
channel.setDescription(channelDescription);
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
manager.createNotificationChannel(channel);
}
} }

View File

@@ -2,7 +2,6 @@ package com.termux.app;
import android.content.Context; import android.content.Context;
import android.media.AudioManager; import android.media.AudioManager;
import android.support.v4.widget.DrawerLayout;
import android.view.Gravity; import android.view.Gravity;
import android.view.InputDevice; import android.view.InputDevice;
import android.view.KeyEvent; import android.view.KeyEvent;
@@ -12,18 +11,20 @@ import android.view.inputmethod.InputMethodManager;
import com.termux.terminal.KeyHandler; import com.termux.terminal.KeyHandler;
import com.termux.terminal.TerminalEmulator; import com.termux.terminal.TerminalEmulator;
import com.termux.terminal.TerminalSession; import com.termux.terminal.TerminalSession;
import com.termux.view.TerminalKeyListener; import com.termux.view.TerminalViewClient;
import java.util.List; import java.util.List;
public final class TermuxKeyListener implements TerminalKeyListener { import androidx.drawerlayout.widget.DrawerLayout;
public final class TermuxViewClient implements TerminalViewClient {
final TermuxActivity mActivity; final TermuxActivity mActivity;
/** Keeping track of the special keys acting as Ctrl and Fn for the soft keyboard and other hardware keys. */ /** Keeping track of the special keys acting as Ctrl and Fn for the soft keyboard and other hardware keys. */
boolean mVirtualControlKeyDown, mVirtualFnKeyDown; boolean mVirtualControlKeyDown, mVirtualFnKeyDown;
public TermuxKeyListener(TermuxActivity activity) { public TermuxViewClient(TermuxActivity activity) {
this.mActivity = activity; this.mActivity = activity;
} }
@@ -73,8 +74,6 @@ public final class TermuxKeyListener implements TerminalKeyListener {
mActivity.getDrawer().openDrawer(Gravity.LEFT); mActivity.getDrawer().openDrawer(Gravity.LEFT);
} else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) { } else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
mActivity.getDrawer().closeDrawers(); mActivity.getDrawer().closeDrawers();
} else if (unicodeChar == 'f'/* full screen */) {
mActivity.toggleImmersive();
} else if (unicodeChar == 'k'/* keyboard */) { } else if (unicodeChar == 'k'/* keyboard */) {
InputMethodManager imm = (InputMethodManager) mActivity.getSystemService(Context.INPUT_METHOD_SERVICE); InputMethodManager imm = (InputMethodManager) mActivity.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0); imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
@@ -114,12 +113,12 @@ public final class TermuxKeyListener implements TerminalKeyListener {
@Override @Override
public boolean readControlKey() { public boolean readControlKey() {
return (mActivity.mExtraKeysView != null && mActivity.mExtraKeysView.readControlButton()) || mVirtualControlKeyDown; return (mActivity.mExtraKeysView != null && mActivity.mExtraKeysView.readSpecialButton(ExtraKeysView.SpecialButton.CTRL)) || mVirtualControlKeyDown;
} }
@Override @Override
public boolean readAltKey() { public boolean readAltKey() {
return (mActivity.mExtraKeysView != null && mActivity.mExtraKeysView.readAltButton()); return (mActivity.mExtraKeysView != null && mActivity.mExtraKeysView.readSpecialButton(ExtraKeysView.SpecialButton.ALT));
} }
@Override @Override
@@ -211,6 +210,7 @@ public final class TermuxKeyListener implements TerminalKeyListener {
// Writing mode: // Writing mode:
case 'q': case 'q':
case 'k':
mActivity.toggleShowExtraKeys(); mActivity.toggleShowExtraKeys();
break; break;
} }
@@ -256,10 +256,17 @@ public final class TermuxKeyListener implements TerminalKeyListener {
return false; return false;
} }
@Override
public boolean onLongPress(MotionEvent event) {
return false;
}
/** 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,11 +71,11 @@ 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());
row.add(Root.COLUMN_ICON, R.mipmap.ic_launcher); row.add(Root.COLUMN_ICON, R.drawable.ic_launcher);
return result; return result;
} }
@@ -91,9 +91,7 @@ public class TermuxDocumentsProvider extends DocumentsProvider {
final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION); final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
final File parent = getFileForDocId(parentDocumentId); final File parent = getFileForDocId(parentDocumentId);
for (File file : parent.listFiles()) { for (File file : parent.listFiles()) {
if (!file.getName().startsWith(".")) { includeFile(result, null, file);
includeFile(result, null, file);
}
} }
return result; return result;
} }
@@ -117,6 +115,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);
@@ -154,8 +175,7 @@ public class TermuxDocumentsProvider extends DocumentsProvider {
} catch (IOException e) { } catch (IOException e) {
isInsideHome = true; isInsideHome = true;
} }
final boolean isHidden = file.getName().startsWith("."); if (isInsideHome) {
if (isInsideHome && !isHidden) {
if (file.isDirectory()) { if (file.isDirectory()) {
Collections.addAll(pending, file.listFiles()); Collections.addAll(pending, file.listFiles());
} else { } else {
@@ -169,6 +189,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.
@@ -195,7 +220,7 @@ public class TermuxDocumentsProvider extends DocumentsProvider {
final String name = file.getName(); final String name = file.getName();
final int lastDot = name.lastIndexOf('.'); final int lastDot = name.lastIndexOf('.');
if (lastDot >= 0) { if (lastDot >= 0) {
final String extension = name.substring(lastDot + 1); final String extension = name.substring(lastDot + 1).toLowerCase();
final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
if (mime != null) return mime; if (mime != null) return mime;
} }
@@ -220,10 +245,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);
@@ -236,7 +262,7 @@ public class TermuxDocumentsProvider extends DocumentsProvider {
row.add(Document.COLUMN_MIME_TYPE, mimeType); row.add(Document.COLUMN_MIME_TYPE, mimeType);
row.add(Document.COLUMN_LAST_MODIFIED, file.lastModified()); row.add(Document.COLUMN_LAST_MODIFIED, file.lastModified());
row.add(Document.COLUMN_FLAGS, flags); row.add(Document.COLUMN_FLAGS, flags);
row.add(Document.COLUMN_ICON, R.mipmap.ic_launcher); row.add(Document.COLUMN_ICON, R.drawable.ic_launcher);
} }
} }

View File

@@ -2,7 +2,6 @@ package com.termux.filepicker;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.database.Cursor; import android.database.Cursor;
import android.net.Uri; import android.net.Uri;
@@ -22,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 {
@@ -37,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();
@@ -51,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);
@@ -83,17 +88,7 @@ public class TermuxFileReceiverActivity extends Activity {
void showErrorDialogAndQuit(String message) { void showErrorDialogAndQuit(String message) {
mFinishOnDismissNameDialog = false; mFinishOnDismissNameDialog = false;
new AlertDialog.Builder(this).setMessage(message).setOnDismissListener(new DialogInterface.OnDismissListener() { new AlertDialog.Builder(this).setMessage(message).setOnDismissListener(dialog -> finish()).setPositiveButton(android.R.string.ok, (dialog, which) -> finish()).show();
@Override
public void onDismiss(DialogInterface dialog) {
finish();
}
}).setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
finish();
}
}).show();
} }
void handleContentUri(final Uri uri, String subjectFromIntent) { void handleContentUri(final Uri uri, String subjectFromIntent) {
@@ -119,54 +114,40 @@ public class TermuxFileReceiverActivity extends Activity {
} }
void promptNameAndSave(final InputStream in, final String attachmentFileName) { void promptNameAndSave(final InputStream in, final String attachmentFileName) {
DialogUtils.textInput(this, R.string.file_received_title, attachmentFileName, R.string.file_received_edit_button, new DialogUtils.TextSetListener() { DialogUtils.textInput(this, R.string.file_received_title, attachmentFileName, R.string.file_received_edit_button, text -> {
@Override File outFile = saveStreamWithName(in, text);
public void onTextSet(String text) { if (outFile == null) return;
File outFile = saveStreamWithName(in, text);
if (outFile == null) return;
final File editorProgramFile = new File(EDITOR_PROGRAM); final File editorProgramFile = new File(EDITOR_PROGRAM);
if (!editorProgramFile.isFile()) { if (!editorProgramFile.isFile()) {
showErrorDialogAndQuit("The following file does not exist:\n$HOME/bin/termux-file-editor\n\n" showErrorDialogAndQuit("The following file does not exist:\n$HOME/bin/termux-file-editor\n\n"
+ "Create this file as a script or a symlink - it will be called with the received file as only argument."); + "Create this file as a script or a symlink - it will be called with the received file as only argument.");
return; return;
} }
// Do this for the user if necessary: // Do this for the user if necessary:
//noinspection ResultOfMethodCallIgnored //noinspection ResultOfMethodCallIgnored
editorProgramFile.setExecutable(true); editorProgramFile.setExecutable(true);
final Uri scriptUri = new Uri.Builder().scheme("file").path(EDITOR_PROGRAM).build(); final Uri scriptUri = new Uri.Builder().scheme("file").path(EDITOR_PROGRAM).build();
Intent executeIntent = new Intent(TermuxService.ACTION_EXECUTE, scriptUri); Intent executeIntent = new Intent(TermuxService.ACTION_EXECUTE, scriptUri);
executeIntent.setClass(TermuxFileReceiverActivity.this, TermuxService.class); executeIntent.setClass(TermuxFileReceiverActivity.this, TermuxService.class);
executeIntent.putExtra(TermuxService.EXTRA_ARGUMENTS, new String[]{outFile.getAbsolutePath()}); executeIntent.putExtra(TermuxService.EXTRA_ARGUMENTS, new String[]{outFile.getAbsolutePath()});
startService(executeIntent); startService(executeIntent);
finish(); finish();
} },
R.string.file_received_open_folder_button, text -> {
if (saveStreamWithName(in, text) == null) return;
Intent executeIntent = new Intent(TermuxService.ACTION_EXECUTE);
executeIntent.putExtra(TermuxService.EXTRA_CURRENT_WORKING_DIRECTORY, TERMUX_RECEIVEDIR);
executeIntent.setClass(TermuxFileReceiverActivity.this, TermuxService.class);
startService(executeIntent);
finish();
}, },
R.string.file_received_open_folder_button, new DialogUtils.TextSetListener() { android.R.string.cancel, text -> finish(), dialog -> {
@Override if (mFinishOnDismissNameDialog) finish();
public void onTextSet(String text) {
if (saveStreamWithName(in, text) == null) return;
Intent executeIntent = new Intent(TermuxService.ACTION_EXECUTE);
executeIntent.putExtra(TermuxService.EXTRA_CURRENT_WORKING_DIRECTORY, TERMUX_RECEIVEDIR);
executeIntent.setClass(TermuxFileReceiverActivity.this, TermuxService.class);
startService(executeIntent);
finish();
}
},
android.R.string.cancel, new DialogUtils.TextSetListener() {
@Override
public void onTextSet(final String text) {
finish();
}
}, new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
if (mFinishOnDismissNameDialog) finish();
}
}); });
} }

View File

@@ -1,458 +0,0 @@
package com.termux.terminal;
/**
* Implementation of wcwidth(3) for Unicode 9.
*
* Implementation from https://github.com/jquast/wcwidth but we return 0 for unprintable characters.
*/
public final class WcWidth {
// From https://github.com/jquast/wcwidth/blob/master/wcwidth/table_zero.py
// t commit 0d7de112202cc8b2ebe9232ff4a5c954f19d561a (2016-07-02):
private static final int[][] ZERO_WIDTH = {
{0x0300, 0x036f}, // Combining Grave Accent ..Combining Latin Small Le
{0x0483, 0x0489}, // Combining Cyrillic Titlo..Combining Cyrillic Milli
{0x0591, 0x05bd}, // Hebrew Accent Etnahta ..Hebrew Point Meteg
{0x05bf, 0x05bf}, // Hebrew Point Rafe ..Hebrew Point Rafe
{0x05c1, 0x05c2}, // Hebrew Point Shin Dot ..Hebrew Point Sin Dot
{0x05c4, 0x05c5}, // Hebrew Mark Upper Dot ..Hebrew Mark Lower Dot
{0x05c7, 0x05c7}, // Hebrew Point Qamats Qata..Hebrew Point Qamats Qata
{0x0610, 0x061a}, // Arabic Sign Sallallahou ..Arabic Small Kasra
{0x064b, 0x065f}, // Arabic Fathatan ..Arabic Wavy Hamza Below
{0x0670, 0x0670}, // Arabic Letter Superscrip..Arabic Letter Superscrip
{0x06d6, 0x06dc}, // Arabic Small High Ligatu..Arabic Small High Seen
{0x06df, 0x06e4}, // Arabic Small High Rounde..Arabic Small High Madda
{0x06e7, 0x06e8}, // Arabic Small High Yeh ..Arabic Small High Noon
{0x06ea, 0x06ed}, // Arabic Empty Centre Low ..Arabic Small Low Meem
{0x0711, 0x0711}, // Syriac Letter Superscrip..Syriac Letter Superscrip
{0x0730, 0x074a}, // Syriac Pthaha Above ..Syriac Barrekh
{0x07a6, 0x07b0}, // Thaana Abafili ..Thaana Sukun
{0x07eb, 0x07f3}, // Nko Combining Sh||t High..Nko Combining Double Dot
{0x0816, 0x0819}, // Samaritan Mark In ..Samaritan Mark Dagesh
{0x081b, 0x0823}, // Samaritan Mark Epentheti..Samaritan Vowel Sign A
{0x0825, 0x0827}, // Samaritan Vowel Sign Sho..Samaritan Vowel Sign U
{0x0829, 0x082d}, // Samaritan Vowel Sign Lon..Samaritan Mark Nequdaa
{0x0859, 0x085b}, // Mandaic Affrication Mark..Mandaic Gemination Mark
{0x08d4, 0x08e1}, // (nil) ..
{0x08e3, 0x0902}, // Arabic Turned Damma Belo..Devanagari Sign Anusvara
{0x093a, 0x093a}, // Devanagari Vowel Sign Oe..Devanagari Vowel Sign Oe
{0x093c, 0x093c}, // Devanagari Sign Nukta ..Devanagari Sign Nukta
{0x0941, 0x0948}, // Devanagari Vowel Sign U ..Devanagari Vowel Sign Ai
{0x094d, 0x094d}, // Devanagari Sign Virama ..Devanagari Sign Virama
{0x0951, 0x0957}, // Devanagari Stress Sign U..Devanagari Vowel Sign Uu
{0x0962, 0x0963}, // Devanagari Vowel Sign Vo..Devanagari Vowel Sign Vo
{0x0981, 0x0981}, // Bengali Sign Candrabindu..Bengali Sign Candrabindu
{0x09bc, 0x09bc}, // Bengali Sign Nukta ..Bengali Sign Nukta
{0x09c1, 0x09c4}, // Bengali Vowel Sign U ..Bengali Vowel Sign Vocal
{0x09cd, 0x09cd}, // Bengali Sign Virama ..Bengali Sign Virama
{0x09e2, 0x09e3}, // Bengali Vowel Sign Vocal..Bengali Vowel Sign Vocal
{0x0a01, 0x0a02}, // Gurmukhi Sign Adak Bindi..Gurmukhi Sign Bindi
{0x0a3c, 0x0a3c}, // Gurmukhi Sign Nukta ..Gurmukhi Sign Nukta
{0x0a41, 0x0a42}, // Gurmukhi Vowel Sign U ..Gurmukhi Vowel Sign Uu
{0x0a47, 0x0a48}, // Gurmukhi Vowel Sign Ee ..Gurmukhi Vowel Sign Ai
{0x0a4b, 0x0a4d}, // Gurmukhi Vowel Sign Oo ..Gurmukhi Sign Virama
{0x0a51, 0x0a51}, // Gurmukhi Sign Udaat ..Gurmukhi Sign Udaat
{0x0a70, 0x0a71}, // Gurmukhi Tippi ..Gurmukhi Addak
{0x0a75, 0x0a75}, // Gurmukhi Sign Yakash ..Gurmukhi Sign Yakash
{0x0a81, 0x0a82}, // Gujarati Sign Candrabind..Gujarati Sign Anusvara
{0x0abc, 0x0abc}, // Gujarati Sign Nukta ..Gujarati Sign Nukta
{0x0ac1, 0x0ac5}, // Gujarati Vowel Sign U ..Gujarati Vowel Sign Cand
{0x0ac7, 0x0ac8}, // Gujarati Vowel Sign E ..Gujarati Vowel Sign Ai
{0x0acd, 0x0acd}, // Gujarati Sign Virama ..Gujarati Sign Virama
{0x0ae2, 0x0ae3}, // Gujarati Vowel Sign Voca..Gujarati Vowel Sign Voca
{0x0b01, 0x0b01}, // ||iya Sign Candrabindu ..||iya Sign Candrabindu
{0x0b3c, 0x0b3c}, // ||iya Sign Nukta ..||iya Sign Nukta
{0x0b3f, 0x0b3f}, // ||iya Vowel Sign I ..||iya Vowel Sign I
{0x0b41, 0x0b44}, // ||iya Vowel Sign U ..||iya Vowel Sign Vocalic
{0x0b4d, 0x0b4d}, // ||iya Sign Virama ..||iya Sign Virama
{0x0b56, 0x0b56}, // ||iya Ai Length Mark ..||iya Ai Length Mark
{0x0b62, 0x0b63}, // ||iya Vowel Sign Vocalic..||iya Vowel Sign Vocalic
{0x0b82, 0x0b82}, // Tamil Sign Anusvara ..Tamil Sign Anusvara
{0x0bc0, 0x0bc0}, // Tamil Vowel Sign Ii ..Tamil Vowel Sign Ii
{0x0bcd, 0x0bcd}, // Tamil Sign Virama ..Tamil Sign Virama
{0x0c00, 0x0c00}, // Telugu Sign Combining Ca..Telugu Sign Combining Ca
{0x0c3e, 0x0c40}, // Telugu Vowel Sign Aa ..Telugu Vowel Sign Ii
{0x0c46, 0x0c48}, // Telugu Vowel Sign E ..Telugu Vowel Sign Ai
{0x0c4a, 0x0c4d}, // Telugu Vowel Sign O ..Telugu Sign Virama
{0x0c55, 0x0c56}, // Telugu Length Mark ..Telugu Ai Length Mark
{0x0c62, 0x0c63}, // Telugu Vowel Sign Vocali..Telugu Vowel Sign Vocali
{0x0c81, 0x0c81}, // Kannada Sign Candrabindu..Kannada Sign Candrabindu
{0x0cbc, 0x0cbc}, // Kannada Sign Nukta ..Kannada Sign Nukta
{0x0cbf, 0x0cbf}, // Kannada Vowel Sign I ..Kannada Vowel Sign I
{0x0cc6, 0x0cc6}, // Kannada Vowel Sign E ..Kannada Vowel Sign E
{0x0ccc, 0x0ccd}, // Kannada Vowel Sign Au ..Kannada Sign Virama
{0x0ce2, 0x0ce3}, // Kannada Vowel Sign Vocal..Kannada Vowel Sign Vocal
{0x0d01, 0x0d01}, // Malayalam Sign Candrabin..Malayalam Sign Candrabin
{0x0d41, 0x0d44}, // Malayalam Vowel Sign U ..Malayalam Vowel Sign Voc
{0x0d4d, 0x0d4d}, // Malayalam Sign Virama ..Malayalam Sign Virama
{0x0d62, 0x0d63}, // Malayalam Vowel Sign Voc..Malayalam Vowel Sign Voc
{0x0dca, 0x0dca}, // Sinhala Sign Al-lakuna ..Sinhala Sign Al-lakuna
{0x0dd2, 0x0dd4}, // Sinhala Vowel Sign Ketti..Sinhala Vowel Sign Ketti
{0x0dd6, 0x0dd6}, // Sinhala Vowel Sign Diga ..Sinhala Vowel Sign Diga
{0x0e31, 0x0e31}, // Thai Character Mai Han-a..Thai Character Mai Han-a
{0x0e34, 0x0e3a}, // Thai Character Sara I ..Thai Character Phinthu
{0x0e47, 0x0e4e}, // Thai Character Maitaikhu..Thai Character Yamakkan
{0x0eb1, 0x0eb1}, // Lao Vowel Sign Mai Kan ..Lao Vowel Sign Mai Kan
{0x0eb4, 0x0eb9}, // Lao Vowel Sign I ..Lao Vowel Sign Uu
{0x0ebb, 0x0ebc}, // Lao Vowel Sign Mai Kon ..Lao Semivowel Sign Lo
{0x0ec8, 0x0ecd}, // Lao Tone Mai Ek ..Lao Niggahita
{0x0f18, 0x0f19}, // Tibetan Astrological Sig..Tibetan Astrological Sig
{0x0f35, 0x0f35}, // Tibetan Mark Ngas Bzung ..Tibetan Mark Ngas Bzung
{0x0f37, 0x0f37}, // Tibetan Mark Ngas Bzung ..Tibetan Mark Ngas Bzung
{0x0f39, 0x0f39}, // Tibetan Mark Tsa -phru ..Tibetan Mark Tsa -phru
{0x0f71, 0x0f7e}, // Tibetan Vowel Sign Aa ..Tibetan Sign Rjes Su Nga
{0x0f80, 0x0f84}, // Tibetan Vowel Sign Rever..Tibetan Mark Halanta
{0x0f86, 0x0f87}, // Tibetan Sign Lci Rtags ..Tibetan Sign Yang Rtags
{0x0f8d, 0x0f97}, // Tibetan Subjoined Sign L..Tibetan Subjoined Letter
{0x0f99, 0x0fbc}, // Tibetan Subjoined Letter..Tibetan Subjoined Letter
{0x0fc6, 0x0fc6}, // Tibetan Symbol Padma Gda..Tibetan Symbol Padma Gda
{0x102d, 0x1030}, // Myanmar Vowel Sign I ..Myanmar Vowel Sign Uu
{0x1032, 0x1037}, // Myanmar Vowel Sign Ai ..Myanmar Sign Dot Below
{0x1039, 0x103a}, // Myanmar Sign Virama ..Myanmar Sign Asat
{0x103d, 0x103e}, // Myanmar Consonant Sign M..Myanmar Consonant Sign M
{0x1058, 0x1059}, // Myanmar Vowel Sign Vocal..Myanmar Vowel Sign Vocal
{0x105e, 0x1060}, // Myanmar Consonant Sign M..Myanmar Consonant Sign M
{0x1071, 0x1074}, // Myanmar Vowel Sign Geba ..Myanmar Vowel Sign Kayah
{0x1082, 0x1082}, // Myanmar Consonant Sign S..Myanmar Consonant Sign S
{0x1085, 0x1086}, // Myanmar Vowel Sign Shan ..Myanmar Vowel Sign Shan
{0x108d, 0x108d}, // Myanmar Sign Shan Counci..Myanmar Sign Shan Counci
{0x109d, 0x109d}, // Myanmar Vowel Sign Aiton..Myanmar Vowel Sign Aiton
{0x135d, 0x135f}, // Ethiopic Combining Gemin..Ethiopic Combining Gemin
{0x1712, 0x1714}, // Tagalog Vowel Sign I ..Tagalog Sign Virama
{0x1732, 0x1734}, // Hanunoo Vowel Sign I ..Hanunoo Sign Pamudpod
{0x1752, 0x1753}, // Buhid Vowel Sign I ..Buhid Vowel Sign U
{0x1772, 0x1773}, // Tagbanwa Vowel Sign I ..Tagbanwa Vowel Sign U
{0x17b4, 0x17b5}, // Khmer Vowel Inherent Aq ..Khmer Vowel Inherent Aa
{0x17b7, 0x17bd}, // Khmer Vowel Sign I ..Khmer Vowel Sign Ua
{0x17c6, 0x17c6}, // Khmer Sign Nikahit ..Khmer Sign Nikahit
{0x17c9, 0x17d3}, // Khmer Sign Muusikatoan ..Khmer Sign Bathamasat
{0x17dd, 0x17dd}, // Khmer Sign Atthacan ..Khmer Sign Atthacan
{0x180b, 0x180d}, // Mongolian Free Variation..Mongolian Free Variation
{0x1885, 0x1886}, // Mongolian Letter Ali Gal..Mongolian Letter Ali Gal
{0x18a9, 0x18a9}, // Mongolian Letter Ali Gal..Mongolian Letter Ali Gal
{0x1920, 0x1922}, // Limbu Vowel Sign A ..Limbu Vowel Sign U
{0x1927, 0x1928}, // Limbu Vowel Sign E ..Limbu Vowel Sign O
{0x1932, 0x1932}, // Limbu Small Letter Anusv..Limbu Small Letter Anusv
{0x1939, 0x193b}, // Limbu Sign Mukphreng ..Limbu Sign Sa-i
{0x1a17, 0x1a18}, // Buginese Vowel Sign I ..Buginese Vowel Sign U
{0x1a1b, 0x1a1b}, // Buginese Vowel Sign Ae ..Buginese Vowel Sign Ae
{0x1a56, 0x1a56}, // Tai Tham Consonant Sign ..Tai Tham Consonant Sign
{0x1a58, 0x1a5e}, // Tai Tham Sign Mai Kang L..Tai Tham Consonant Sign
{0x1a60, 0x1a60}, // Tai Tham Sign Sakot ..Tai Tham Sign Sakot
{0x1a62, 0x1a62}, // Tai Tham Vowel Sign Mai ..Tai Tham Vowel Sign Mai
{0x1a65, 0x1a6c}, // Tai Tham Vowel Sign I ..Tai Tham Vowel Sign Oa B
{0x1a73, 0x1a7c}, // Tai Tham Vowel Sign Oa A..Tai Tham Sign Khuen-lue
{0x1a7f, 0x1a7f}, // Tai Tham Combining Crypt..Tai Tham Combining Crypt
{0x1ab0, 0x1abe}, // Combining Doubled Circum..Combining Parentheses Ov
{0x1b00, 0x1b03}, // Balinese Sign Ulu Ricem ..Balinese Sign Surang
{0x1b34, 0x1b34}, // Balinese Sign Rerekan ..Balinese Sign Rerekan
{0x1b36, 0x1b3a}, // Balinese Vowel Sign Ulu ..Balinese Vowel Sign Ra R
{0x1b3c, 0x1b3c}, // Balinese Vowel Sign La L..Balinese Vowel Sign La L
{0x1b42, 0x1b42}, // Balinese Vowel Sign Pepe..Balinese Vowel Sign Pepe
{0x1b6b, 0x1b73}, // Balinese Musical Symbol ..Balinese Musical Symbol
{0x1b80, 0x1b81}, // Sundanese Sign Panyecek ..Sundanese Sign Panglayar
{0x1ba2, 0x1ba5}, // Sundanese Consonant Sign..Sundanese Vowel Sign Pan
{0x1ba8, 0x1ba9}, // Sundanese Vowel Sign Pam..Sundanese Vowel Sign Pan
{0x1bab, 0x1bad}, // Sundanese Sign Virama ..Sundanese Consonant Sign
{0x1be6, 0x1be6}, // Batak Sign Tompi ..Batak Sign Tompi
{0x1be8, 0x1be9}, // Batak Vowel Sign Pakpak ..Batak Vowel Sign Ee
{0x1bed, 0x1bed}, // Batak Vowel Sign Karo O ..Batak Vowel Sign Karo O
{0x1bef, 0x1bf1}, // Batak Vowel Sign U F|| S..Batak Consonant Sign H
{0x1c2c, 0x1c33}, // Lepcha Vowel Sign E ..Lepcha Consonant Sign T
{0x1c36, 0x1c37}, // Lepcha Sign Ran ..Lepcha Sign Nukta
{0x1cd0, 0x1cd2}, // Vedic Tone Karshana ..Vedic Tone Prenkha
{0x1cd4, 0x1ce0}, // Vedic Sign Yajurvedic Mi..Vedic Tone Rigvedic Kash
{0x1ce2, 0x1ce8}, // Vedic Sign Visarga Svari..Vedic Sign Visarga Anuda
{0x1ced, 0x1ced}, // Vedic Sign Tiryak ..Vedic Sign Tiryak
{0x1cf4, 0x1cf4}, // Vedic Tone Candra Above ..Vedic Tone Candra Above
{0x1cf8, 0x1cf9}, // Vedic Tone Ring Above ..Vedic Tone Double Ring A
{0x1dc0, 0x1df5}, // Combining Dotted Grave A..Combining Up Tack Above
{0x1dfb, 0x1dff}, // (nil) ..Combining Right Arrowhea
{0x20d0, 0x20f0}, // Combining Left Harpoon A..Combining Asterisk Above
{0x2cef, 0x2cf1}, // Coptic Combining Ni Abov..Coptic Combining Spiritu
{0x2d7f, 0x2d7f}, // Tifinagh Consonant Joine..Tifinagh Consonant Joine
{0x2de0, 0x2dff}, // Combining Cyrillic Lette..Combining Cyrillic Lette
{0x302a, 0x302d}, // Ideographic Level Tone M..Ideographic Entering Ton
{0x3099, 0x309a}, // Combining Katakana-hirag..Combining Katakana-hirag
{0xa66f, 0xa672}, // Combining Cyrillic Vzmet..Combining Cyrillic Thous
{0xa674, 0xa67d}, // Combining Cyrillic Lette..Combining Cyrillic Payer
{0xa69e, 0xa69f}, // Combining Cyrillic Lette..Combining Cyrillic Lette
{0xa6f0, 0xa6f1}, // Bamum Combining Mark Koq..Bamum Combining Mark Tuk
{0xa802, 0xa802}, // Syloti Nagri Sign Dvisva..Syloti Nagri Sign Dvisva
{0xa806, 0xa806}, // Syloti Nagri Sign Hasant..Syloti Nagri Sign Hasant
{0xa80b, 0xa80b}, // Syloti Nagri Sign Anusva..Syloti Nagri Sign Anusva
{0xa825, 0xa826}, // Syloti Nagri Vowel Sign ..Syloti Nagri Vowel Sign
{0xa8c4, 0xa8c5}, // Saurashtra Sign Virama ..
{0xa8e0, 0xa8f1}, // Combining Devanagari Dig..Combining Devanagari Sig
{0xa926, 0xa92d}, // Kayah Li Vowel Ue ..Kayah Li Tone Calya Plop
{0xa947, 0xa951}, // Rejang Vowel Sign I ..Rejang Consonant Sign R
{0xa980, 0xa982}, // Javanese Sign Panyangga ..Javanese Sign Layar
{0xa9b3, 0xa9b3}, // Javanese Sign Cecak Telu..Javanese Sign Cecak Telu
{0xa9b6, 0xa9b9}, // Javanese Vowel Sign Wulu..Javanese Vowel Sign Suku
{0xa9bc, 0xa9bc}, // Javanese Vowel Sign Pepe..Javanese Vowel Sign Pepe
{0xa9e5, 0xa9e5}, // Myanmar Sign Shan Saw ..Myanmar Sign Shan Saw
{0xaa29, 0xaa2e}, // Cham Vowel Sign Aa ..Cham Vowel Sign Oe
{0xaa31, 0xaa32}, // Cham Vowel Sign Au ..Cham Vowel Sign Ue
{0xaa35, 0xaa36}, // Cham Consonant Sign La ..Cham Consonant Sign Wa
{0xaa43, 0xaa43}, // Cham Consonant Sign Fina..Cham Consonant Sign Fina
{0xaa4c, 0xaa4c}, // Cham Consonant Sign Fina..Cham Consonant Sign Fina
{0xaa7c, 0xaa7c}, // Myanmar Sign Tai Laing T..Myanmar Sign Tai Laing T
{0xaab0, 0xaab0}, // Tai Viet Mai Kang ..Tai Viet Mai Kang
{0xaab2, 0xaab4}, // Tai Viet Vowel I ..Tai Viet Vowel U
{0xaab7, 0xaab8}, // Tai Viet Mai Khit ..Tai Viet Vowel Ia
{0xaabe, 0xaabf}, // Tai Viet Vowel Am ..Tai Viet Tone Mai Ek
{0xaac1, 0xaac1}, // Tai Viet Tone Mai Tho ..Tai Viet Tone Mai Tho
{0xaaec, 0xaaed}, // Meetei Mayek Vowel Sign ..Meetei Mayek Vowel Sign
{0xaaf6, 0xaaf6}, // Meetei Mayek Virama ..Meetei Mayek Virama
{0xabe5, 0xabe5}, // Meetei Mayek Vowel Sign ..Meetei Mayek Vowel Sign
{0xabe8, 0xabe8}, // Meetei Mayek Vowel Sign ..Meetei Mayek Vowel Sign
{0xabed, 0xabed}, // Meetei Mayek Apun Iyek ..Meetei Mayek Apun Iyek
{0xfb1e, 0xfb1e}, // Hebrew Point Judeo-spani..Hebrew Point Judeo-spani
{0xfe00, 0xfe0f}, // Variation Select||-1 ..Variation Select||-16
{0xfe20, 0xfe2f}, // Combining Ligature Left ..Combining Cyrillic Titlo
{0x101fd, 0x101fd}, // Phaistos Disc Sign Combi..Phaistos Disc Sign Combi
{0x102e0, 0x102e0}, // Coptic Epact Thousands M..Coptic Epact Thousands M
{0x10376, 0x1037a}, // Combining Old Permic Let..Combining Old Permic Let
{0x10a01, 0x10a03}, // Kharoshthi Vowel Sign I ..Kharoshthi Vowel Sign Vo
{0x10a05, 0x10a06}, // Kharoshthi Vowel Sign E ..Kharoshthi Vowel Sign O
{0x10a0c, 0x10a0f}, // Kharoshthi Vowel Length ..Kharoshthi Sign Visarga
{0x10a38, 0x10a3a}, // Kharoshthi Sign Bar Abov..Kharoshthi Sign Dot Belo
{0x10a3f, 0x10a3f}, // Kharoshthi Virama ..Kharoshthi Virama
{0x10ae5, 0x10ae6}, // Manichaean Abbreviation ..Manichaean Abbreviation
{0x11001, 0x11001}, // Brahmi Sign Anusvara ..Brahmi Sign Anusvara
{0x11038, 0x11046}, // Brahmi Vowel Sign Aa ..Brahmi Virama
{0x1107f, 0x11081}, // Brahmi Number Joiner ..Kaithi Sign Anusvara
{0x110b3, 0x110b6}, // Kaithi Vowel Sign U ..Kaithi Vowel Sign Ai
{0x110b9, 0x110ba}, // Kaithi Sign Virama ..Kaithi Sign Nukta
{0x11100, 0x11102}, // Chakma Sign Candrabindu ..Chakma Sign Visarga
{0x11127, 0x1112b}, // Chakma Vowel Sign A ..Chakma Vowel Sign Uu
{0x1112d, 0x11134}, // Chakma Vowel Sign Ai ..Chakma Maayyaa
{0x11173, 0x11173}, // Mahajani Sign Nukta ..Mahajani Sign Nukta
{0x11180, 0x11181}, // Sharada Sign Candrabindu..Sharada Sign Anusvara
{0x111b6, 0x111be}, // Sharada Vowel Sign U ..Sharada Vowel Sign O
{0x111ca, 0x111cc}, // Sharada Sign Nukta ..Sharada Extra Sh||t Vowe
{0x1122f, 0x11231}, // Khojki Vowel Sign U ..Khojki Vowel Sign Ai
{0x11234, 0x11234}, // Khojki Sign Anusvara ..Khojki Sign Anusvara
{0x11236, 0x11237}, // Khojki Sign Nukta ..Khojki Sign Shadda
{0x1123e, 0x1123e}, // (nil) ..
{0x112df, 0x112df}, // Khudawadi Sign Anusvara ..Khudawadi Sign Anusvara
{0x112e3, 0x112ea}, // Khudawadi Vowel Sign U ..Khudawadi Sign Virama
{0x11300, 0x11301}, // Grantha Sign Combining A..Grantha Sign Candrabindu
{0x1133c, 0x1133c}, // Grantha Sign Nukta ..Grantha Sign Nukta
{0x11340, 0x11340}, // Grantha Vowel Sign Ii ..Grantha Vowel Sign Ii
{0x11366, 0x1136c}, // Combining Grantha Digit ..Combining Grantha Digit
{0x11370, 0x11374}, // Combining Grantha Letter..Combining Grantha Letter
{0x11438, 0x1143f}, // (nil) ..
{0x11442, 0x11444}, // (nil) ..
{0x11446, 0x11446}, // (nil) ..
{0x114b3, 0x114b8}, // Tirhuta Vowel Sign U ..Tirhuta Vowel Sign Vocal
{0x114ba, 0x114ba}, // Tirhuta Vowel Sign Sh||t..Tirhuta Vowel Sign Sh||t
{0x114bf, 0x114c0}, // Tirhuta Sign Candrabindu..Tirhuta Sign Anusvara
{0x114c2, 0x114c3}, // Tirhuta Sign Virama ..Tirhuta Sign Nukta
{0x115b2, 0x115b5}, // Siddham Vowel Sign U ..Siddham Vowel Sign Vocal
{0x115bc, 0x115bd}, // Siddham Sign Candrabindu..Siddham Sign Anusvara
{0x115bf, 0x115c0}, // Siddham Sign Virama ..Siddham Sign Nukta
{0x115dc, 0x115dd}, // Siddham Vowel Sign Alter..Siddham Vowel Sign Alter
{0x11633, 0x1163a}, // Modi Vowel Sign U ..Modi Vowel Sign Ai
{0x1163d, 0x1163d}, // Modi Sign Anusvara ..Modi Sign Anusvara
{0x1163f, 0x11640}, // Modi Sign Virama ..Modi Sign Ardhacandra
{0x116ab, 0x116ab}, // Takri Sign Anusvara ..Takri Sign Anusvara
{0x116ad, 0x116ad}, // Takri Vowel Sign Aa ..Takri Vowel Sign Aa
{0x116b0, 0x116b5}, // Takri Vowel Sign U ..Takri Vowel Sign Au
{0x116b7, 0x116b7}, // Takri Sign Nukta ..Takri Sign Nukta
{0x1171d, 0x1171f}, // Ahom Consonant Sign Medi..Ahom Consonant Sign Medi
{0x11722, 0x11725}, // Ahom Vowel Sign I ..Ahom Vowel Sign Uu
{0x11727, 0x1172b}, // Ahom Vowel Sign Aw ..Ahom Sign Killer
{0x11c30, 0x11c36}, // (nil) ..
{0x11c38, 0x11c3d}, // (nil) ..
{0x11c3f, 0x11c3f}, // (nil) ..
{0x11c92, 0x11ca7}, // (nil) ..
{0x11caa, 0x11cb0}, // (nil) ..
{0x11cb2, 0x11cb3}, // (nil) ..
{0x11cb5, 0x11cb6}, // (nil) ..
{0x16af0, 0x16af4}, // Bassa Vah Combining High..Bassa Vah Combining High
{0x16b30, 0x16b36}, // Pahawh Hmong Mark Cim Tu..Pahawh Hmong Mark Cim Ta
{0x16f8f, 0x16f92}, // Miao Tone Right ..Miao Tone Below
{0x1bc9d, 0x1bc9e}, // Duployan Thick Letter Se..Duployan Double Mark
{0x1d167, 0x1d169}, // Musical Symbol Combining..Musical Symbol Combining
{0x1d17b, 0x1d182}, // Musical Symbol Combining..Musical Symbol Combining
{0x1d185, 0x1d18b}, // Musical Symbol Combining..Musical Symbol Combining
{0x1d1aa, 0x1d1ad}, // Musical Symbol Combining..Musical Symbol Combining
{0x1d242, 0x1d244}, // Combining Greek Musical ..Combining Greek Musical
{0x1da00, 0x1da36}, // Signwriting Head Rim ..Signwriting Air Sucking
{0x1da3b, 0x1da6c}, // Signwriting Mouth Closed..Signwriting Excitement
{0x1da75, 0x1da75}, // Signwriting Upper Body T..Signwriting Upper Body T
{0x1da84, 0x1da84}, // Signwriting Location Hea..Signwriting Location Hea
{0x1da9b, 0x1da9f}, // Signwriting Fill Modifie..Signwriting Fill Modifie
{0x1daa1, 0x1daaf}, // Signwriting Rotation Mod..Signwriting Rotation Mod
{0x1e000, 0x1e006}, // (nil) ..
{0x1e008, 0x1e018}, // (nil) ..
{0x1e01b, 0x1e021}, // (nil) ..
{0x1e023, 0x1e024}, // (nil) ..
{0x1e026, 0x1e02a}, // (nil) ..
{0x1e8d0, 0x1e8d6}, // Mende Kikakui Combining ..Mende Kikakui Combining
{0x1e944, 0x1e94a}, // (nil) ..
{0xe0100, 0xe01ef}, // Variation Select||-17 ..Variation Select||-256
};
// https://github.com/jquast/wcwidth/blob/master/wcwidth/table_wide.py
// at commit 0d7de112202cc8b2ebe9232ff4a5c954f19d561a (2016-07-02):
private static final int[][] WIDE_EASTASIAN = {
{0x1100, 0x115f}, // Hangul Choseong Kiyeok ..Hangul Choseong Filler
{0x231a, 0x231b}, // Watch ..Hourglass
{0x2329, 0x232a}, // Left-pointing Angle Brac..Right-pointing Angle Bra
{0x23e9, 0x23ec}, // Black Right-pointing Dou..Black Down-pointing Doub
{0x23f0, 0x23f0}, // Alarm Clock ..Alarm Clock
{0x23f3, 0x23f3}, // Hourglass With Flowing S..Hourglass With Flowing S
{0x25fd, 0x25fe}, // White Medium Small Squar..Black Medium Small Squar
{0x2614, 0x2615}, // Umbrella With Rain Drops..Hot Beverage
{0x2648, 0x2653}, // Aries ..Pisces
{0x267f, 0x267f}, // Wheelchair Symbol ..Wheelchair Symbol
{0x2693, 0x2693}, // Anch|| ..Anch||
{0x26a1, 0x26a1}, // High Voltage Sign ..High Voltage Sign
{0x26aa, 0x26ab}, // Medium White Circle ..Medium Black Circle
{0x26bd, 0x26be}, // Soccer Ball ..Baseball
{0x26c4, 0x26c5}, // Snowman Without Snow ..Sun Behind Cloud
{0x26ce, 0x26ce}, // Ophiuchus ..Ophiuchus
{0x26d4, 0x26d4}, // No Entry ..No Entry
{0x26ea, 0x26ea}, // Church ..Church
{0x26f2, 0x26f3}, // Fountain ..Flag In Hole
{0x26f5, 0x26f5}, // Sailboat ..Sailboat
{0x26fa, 0x26fa}, // Tent ..Tent
{0x26fd, 0x26fd}, // Fuel Pump ..Fuel Pump
{0x2705, 0x2705}, // White Heavy Check Mark ..White Heavy Check Mark
{0x270a, 0x270b}, // Raised Fist ..Raised Hand
{0x2728, 0x2728}, // Sparkles ..Sparkles
{0x274c, 0x274c}, // Cross Mark ..Cross Mark
{0x274e, 0x274e}, // Negative Squared Cross M..Negative Squared Cross M
{0x2753, 0x2755}, // Black Question Mark ||na..White Exclamation Mark O
{0x2757, 0x2757}, // Heavy Exclamation Mark S..Heavy Exclamation Mark S
{0x2795, 0x2797}, // Heavy Plus Sign ..Heavy Division Sign
{0x27b0, 0x27b0}, // Curly Loop ..Curly Loop
{0x27bf, 0x27bf}, // Double Curly Loop ..Double Curly Loop
{0x2b1b, 0x2b1c}, // Black Large Square ..White Large Square
{0x2b50, 0x2b50}, // White Medium Star ..White Medium Star
{0x2b55, 0x2b55}, // Heavy Large Circle ..Heavy Large Circle
{0x2e80, 0x2e99}, // Cjk Radical Repeat ..Cjk Radical Rap
{0x2e9b, 0x2ef3}, // Cjk Radical Choke ..Cjk Radical C-simplified
{0x2f00, 0x2fd5}, // Kangxi Radical One ..Kangxi Radical Flute
{0x2ff0, 0x2ffb}, // Ideographic Description ..Ideographic Description
{0x3000, 0x303e}, // Ideographic Space ..Ideographic Variation In
{0x3041, 0x3096}, // Hiragana Letter Small A ..Hiragana Letter Small Ke
{0x3099, 0x30ff}, // Combining Katakana-hirag..Katakana Digraph Koto
{0x3105, 0x312d}, // Bopomofo Letter B ..Bopomofo Letter Ih
{0x3131, 0x318e}, // Hangul Letter Kiyeok ..Hangul Letter Araeae
{0x3190, 0x31ba}, // Ideographic Annotation L..Bopomofo Letter Zy
{0x31c0, 0x31e3}, // Cjk Stroke T ..Cjk Stroke Q
{0x31f0, 0x321e}, // Katakana Letter Small Ku..Parenthesized K||ean Cha
{0x3220, 0x3247}, // Parenthesized Ideograph ..Circled Ideograph Koto
{0x3250, 0x32fe}, // Partnership Sign ..Circled Katakana Wo
{0x3300, 0x4dbf}, // Square Apaato ..
{0x4e00, 0xa48c}, // Cjk Unified Ideograph-4e..Yi Syllable Yyr
{0xa490, 0xa4c6}, // Yi Radical Qot ..Yi Radical Ke
{0xa960, 0xa97c}, // Hangul Choseong Tikeut-m..Hangul Choseong Ssangyeo
{0xac00, 0xd7a3}, // Hangul Syllable Ga ..Hangul Syllable Hih
{0xf900, 0xfaff}, // Cjk Compatibility Ideogr..
{0xfe10, 0xfe19}, // Presentation F||m F|| Ve..Presentation F||m F|| Ve
{0xfe30, 0xfe52}, // Presentation F||m F|| Ve..Small Full Stop
{0xfe54, 0xfe66}, // Small Semicolon ..Small Equals Sign
{0xfe68, 0xfe6b}, // Small Reverse Solidus ..Small Commercial At
{0xff01, 0xff60}, // Fullwidth Exclamation Ma..Fullwidth Right White Pa
{0xffe0, 0xffe6}, // Fullwidth Cent Sign ..Fullwidth Won Sign
{0x16fe0, 0x16fe0}, // (nil) ..
{0x17000, 0x187ec}, // (nil) ..
{0x18800, 0x18af2}, // (nil) ..
{0x1b000, 0x1b001}, // Katakana Letter Archaic ..Hiragana Letter Archaic
{0x1f004, 0x1f004}, // Mahjong Tile Red Dragon ..Mahjong Tile Red Dragon
{0x1f0cf, 0x1f0cf}, // Playing Card Black Joker..Playing Card Black Joker
{0x1f18e, 0x1f18e}, // Negative Squared Ab ..Negative Squared Ab
{0x1f191, 0x1f19a}, // Squared Cl ..Squared Vs
{0x1f200, 0x1f202}, // Square Hiragana Hoka ..Squared Katakana Sa
{0x1f210, 0x1f23b}, // Squared Cjk Unified Ideo..
{0x1f240, 0x1f248}, // T||toise Shell Bracketed..T||toise Shell Bracketed
{0x1f250, 0x1f251}, // Circled Ideograph Advant..Circled Ideograph Accept
{0x1f300, 0x1f320}, // Cyclone ..Shooting Star
{0x1f32d, 0x1f335}, // Hot Dog ..Cactus
{0x1f337, 0x1f37c}, // Tulip ..Baby Bottle
{0x1f37e, 0x1f393}, // Bottle With Popping C||k..Graduation Cap
{0x1f3a0, 0x1f3ca}, // Carousel H||se ..Swimmer
{0x1f3cf, 0x1f3d3}, // Cricket Bat And Ball ..Table Tennis Paddle And
{0x1f3e0, 0x1f3f0}, // House Building ..European Castle
{0x1f3f4, 0x1f3f4}, // Waving Black Flag ..Waving Black Flag
{0x1f3f8, 0x1f43e}, // Badminton Racquet And Sh..Paw Prints
{0x1f440, 0x1f440}, // Eyes ..Eyes
{0x1f442, 0x1f4fc}, // Ear ..Videocassette
{0x1f4ff, 0x1f53d}, // Prayer Beads ..Down-pointing Small Red
{0x1f54b, 0x1f54e}, // Kaaba ..Men||ah With Nine Branch
{0x1f550, 0x1f567}, // Clock Face One Oclock ..Clock Face Twelve-thirty
{0x1f57a, 0x1f57a}, // (nil) ..
{0x1f595, 0x1f596}, // Reversed Hand With Middl..Raised Hand With Part Be
{0x1f5a4, 0x1f5a4}, // (nil) ..
{0x1f5fb, 0x1f64f}, // Mount Fuji ..Person With Folded Hands
{0x1f680, 0x1f6c5}, // Rocket ..Left Luggage
{0x1f6cc, 0x1f6cc}, // Sleeping Accommodation ..Sleeping Accommodation
{0x1f6d0, 0x1f6d2}, // Place Of W||ship ..
{0x1f6eb, 0x1f6ec}, // Airplane Departure ..Airplane Arriving
{0x1f6f4, 0x1f6f6}, // (nil) ..
{0x1f910, 0x1f91e}, // Zipper-mouth Face ..
{0x1f920, 0x1f927}, // (nil) ..
{0x1f930, 0x1f930}, // (nil) ..
{0x1f933, 0x1f93e}, // (nil) ..
{0x1f940, 0x1f94b}, // (nil) ..
{0x1f950, 0x1f95e}, // (nil) ..
{0x1f980, 0x1f991}, // Crab ..
{0x1f9c0, 0x1f9c0}, // Cheese Wedge ..Cheese Wedge
{0x20000, 0x2fffd}, // Cjk Unified Ideograph-20..
{0x30000, 0x3fffd}, // (nil) ..
};
private static boolean intable(int[][] table, int c) {
// First quick check f|| Latin1 etc. characters.
if (c < table[0][0]) return false;
// Binary search in table.
int bot = 0;
int top = table.length - 1; // (int)(size / sizeof(struct interval) - 1);
while (top >= bot) {
int mid = (bot + top) / 2;
if (table[mid][1] < c) {
bot = mid + 1;
} else if (table[mid][0] > c) {
top = mid - 1;
} else {
return true;
}
}
return false;
}
/** Return the terminal display width of a code point: 0, 1 || 2. */
public static int width(int ucs) {
if (ucs == 0 ||
ucs == 0x034F ||
(0x200B <= ucs && ucs <= 0x200F) ||
ucs == 0x2028 ||
ucs == 0x2029 ||
(0x202A <= ucs && ucs <= 0x202E) ||
(0x2060 <= ucs && ucs <= 0x2063)) {
return 0;
}
// C0/C1 control characters
// Termux change: Return 0 instead of -1.
if (ucs < 32 || (0x07F <= ucs && ucs < 0x0A0)) return 0;
// combining characters with zero width
if (intable(ZERO_WIDTH, ucs)) return 0;
return intable(WIDE_EASTASIAN, ucs) ? 2 : 1;
}
/** The width at an index position in a java char array. */
public static int width(char[] chars, int index) {
char c = chars[index];
return Character.isHighSurrogate(c) ? width(Character.toCodePoint(c, chars[index + 1])) : width(c);
}
}

View File

@@ -1,903 +0,0 @@
package com.termux.view;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.graphics.drawable.BitmapDrawable;
import android.os.Build;
import android.text.Editable;
import android.text.InputType;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.view.ActionMode;
import android.view.HapticFeedbackConstants;
import android.view.InputDevice;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.inputmethod.BaseInputConnection;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.widget.Scroller;
import com.termux.R;
import com.termux.terminal.EmulatorDebug;
import com.termux.terminal.KeyHandler;
import com.termux.terminal.TerminalBuffer;
import com.termux.terminal.TerminalEmulator;
import com.termux.terminal.TerminalSession;
/** View displaying and interacting with a {@link TerminalSession}. */
public final class TerminalView extends View {
/** Log view key and IME events. */
private static final boolean LOG_KEY_EVENTS = false;
/** The currently displayed terminal session, whose emulator is {@link #mEmulator}. */
TerminalSession mTermSession;
/** Our terminal emulator whose session is {@link #mTermSession}. */
TerminalEmulator mEmulator;
TerminalRenderer mRenderer;
TerminalKeyListener mOnKeyListener;
/** The top row of text to display. Ranges from -activeTranscriptRows to 0. */
int mTopRow;
boolean mIsSelectingText = false, mIsDraggingLeftSelection, mInitialTextSelection;
int mSelX1 = -1, mSelX2 = -1, mSelY1 = -1, mSelY2 = -1;
float mSelectionDownX, mSelectionDownY;
private ActionMode mActionMode;
private BitmapDrawable mLeftSelectionHandle, mRightSelectionHandle;
float mScaleFactor = 1.f;
final GestureAndScaleRecognizer mGestureRecognizer;
/** Keep track of where mouse touch event started which we report as mouse scroll. */
private int mMouseScrollStartX = -1, mMouseScrollStartY = -1;
/** Keep track of the time when a touch event leading to sending mouse scroll events started. */
private long mMouseStartDownTime = -1;
final Scroller mScroller;
/** What was left in from scrolling movement. */
float mScrollRemainder;
/** If non-zero, this is the last unicode code point received if that was a combining character. */
int mCombiningAccent;
public TerminalView(Context context, AttributeSet attributes) { // NO_UCD (unused code)
super(context, attributes);
mGestureRecognizer = new GestureAndScaleRecognizer(context, new GestureAndScaleRecognizer.Listener() {
boolean scrolledWithFinger;
@Override
public boolean onUp(MotionEvent e) {
mScrollRemainder = 0.0f;
if (mEmulator != null && mEmulator.isMouseTrackingActive() && !mIsSelectingText && !scrolledWithFinger) {
// Quick event processing when mouse tracking is active - do not wait for check of double tapping
// for zooming.
sendMouseEventCode(e, TerminalEmulator.MOUSE_LEFT_BUTTON, true);
sendMouseEventCode(e, TerminalEmulator.MOUSE_LEFT_BUTTON, false);
return true;
}
scrolledWithFinger = false;
return false;
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
if (mEmulator == null) return true;
if (mIsSelectingText) {
toggleSelectingText(null);
return true;
}
requestFocus();
if (!mEmulator.isMouseTrackingActive()) {
if (!e.isFromSource(InputDevice.SOURCE_MOUSE)) {
mOnKeyListener.onSingleTapUp(e);
return true;
}
}
return false;
}
@Override
public boolean onScroll(MotionEvent e, float distanceX, float distanceY) {
if (mEmulator == null || mIsSelectingText) return true;
if (mEmulator.isMouseTrackingActive() && e.isFromSource(InputDevice.SOURCE_MOUSE)) {
// If moving with mouse pointer while pressing button, report that instead of scroll.
// This means that we never report moving with button press-events for touch input,
// since we cannot just start sending these events without a starting press event,
// which we do not do for touch input, only mouse in onTouchEvent().
sendMouseEventCode(e, TerminalEmulator.MOUSE_LEFT_BUTTON_MOVED, true);
} else {
scrolledWithFinger = true;
distanceY += mScrollRemainder;
int deltaRows = (int) (distanceY / mRenderer.mFontLineSpacing);
mScrollRemainder = distanceY - deltaRows * mRenderer.mFontLineSpacing;
doScroll(e, deltaRows);
}
return true;
}
@Override
public boolean onScale(float focusX, float focusY, float scale) {
if (mEmulator == null || mIsSelectingText) return true;
mScaleFactor *= scale;
mScaleFactor = mOnKeyListener.onScale(mScaleFactor);
return true;
}
@Override
public boolean onFling(final MotionEvent e2, float velocityX, float velocityY) {
if (mEmulator == null || mIsSelectingText) return true;
// Do not start scrolling until last fling has been taken care of:
if (!mScroller.isFinished()) return true;
final boolean mouseTrackingAtStartOfFling = mEmulator.isMouseTrackingActive();
float SCALE = 0.25f;
if (mouseTrackingAtStartOfFling) {
mScroller.fling(0, 0, 0, -(int) (velocityY * SCALE), 0, 0, -mEmulator.mRows / 2, mEmulator.mRows / 2);
} else {
mScroller.fling(0, mTopRow, 0, -(int) (velocityY * SCALE), 0, 0, -mEmulator.getScreen().getActiveTranscriptRows(), 0);
}
post(new Runnable() {
private int mLastY = 0;
@Override
public void run() {
if (mouseTrackingAtStartOfFling != mEmulator.isMouseTrackingActive()) {
mScroller.abortAnimation();
return;
}
if (mScroller.isFinished()) return;
boolean more = mScroller.computeScrollOffset();
int newY = mScroller.getCurrY();
int diff = mouseTrackingAtStartOfFling ? (newY - mLastY) : (newY - mTopRow);
doScroll(e2, diff);
mLastY = newY;
if (more) post(this);
}
});
return true;
}
@Override
public boolean onDown(float x, float y) {
return false;
}
@Override
public boolean onDoubleTap(MotionEvent e) {
// Do not treat is as a single confirmed tap - it may be followed by zoom.
return false;
}
@Override
public void onLongPress(MotionEvent e) {
if (!mGestureRecognizer.isInProgress() && !mIsSelectingText) {
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
toggleSelectingText(e);
}
}
});
mScroller = new Scroller(context);
}
/**
* @param onKeyListener Listener for all kinds of key events, both hardware and IME (which makes it different from that
* available with {@link View#setOnKeyListener(OnKeyListener)}.
*/
public void setOnKeyListener(TerminalKeyListener onKeyListener) {
this.mOnKeyListener = onKeyListener;
}
/**
* Attach a {@link TerminalSession} to this view.
*
* @param session The {@link TerminalSession} this view will be displaying.
*/
public boolean attachSession(TerminalSession session) {
if (session == mTermSession) return false;
mTopRow = 0;
mTermSession = session;
mEmulator = null;
mCombiningAccent = 0;
updateSize();
// Wait with enabling the scrollbar until we have a terminal to get scroll position from.
setVerticalScrollBarEnabled(true);
return true;
}
@Override
public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
// Using InputType.NULL is the most correct input type and avoids issues with other hacks.
//
// Previous keyboard issues:
// https://github.com/termux/termux-packages/issues/25
// https://github.com/termux/termux-app/issues/87.
// https://github.com/termux/termux-app/issues/126.
// https://github.com/termux/termux-app/issues/137 (japanese chars and TYPE_NULL).
outAttrs.inputType = InputType.TYPE_NULL;
outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_FULLSCREEN |
EditorInfo.IME_FLAG_NO_ENTER_ACTION |
EditorInfo.IME_ACTION_NONE;
return new BaseInputConnection(this, true) {
@Override
public boolean finishComposingText() {
if (LOG_KEY_EVENTS) Log.i(EmulatorDebug.LOG_TAG, "IME: finishComposingText()");
super.finishComposingText();
sendTextToTerminal(getEditable());
getEditable().clear();
return true;
}
@Override
public boolean commitText(CharSequence text, int newCursorPosition) {
if (LOG_KEY_EVENTS) {
Log.i(EmulatorDebug.LOG_TAG, "IME: commitText(\"" + text + "\", " + newCursorPosition + ")");
}
super.commitText(text, newCursorPosition);
if (mEmulator == null) return true;
Editable content = getEditable();
sendTextToTerminal(content);
content.clear();
return true;
}
@Override
public boolean deleteSurroundingText(int leftLength, int rightLength) {
if (LOG_KEY_EVENTS) {
Log.i(EmulatorDebug.LOG_TAG, "IME: deleteSurroundingText(" + leftLength + ", " + rightLength + ")");
}
// The stock Samsung keyboard with 'Auto check spelling' enabled sends leftLength > 1.
KeyEvent deleteKey = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL);
for (int i = 0; i < leftLength; i++) sendKeyEvent(deleteKey);
return super.deleteSurroundingText(leftLength, rightLength);
}
void sendTextToTerminal(CharSequence text) {
final int textLengthInChars = text.length();
for (int i = 0; i < textLengthInChars; i++) {
char firstChar = text.charAt(i);
int codePoint;
if (Character.isHighSurrogate(firstChar)) {
if (++i < textLengthInChars) {
codePoint = Character.toCodePoint(firstChar, text.charAt(i));
} else {
// At end of string, with no low surrogate following the high:
codePoint = TerminalEmulator.UNICODE_REPLACEMENT_CHAR;
}
} else {
codePoint = firstChar;
}
boolean ctrlHeld = false;
if (codePoint <= 31 && codePoint != 27) {
// E.g. penti keyboard for ctrl input.
ctrlHeld = true;
switch (codePoint) {
case 31:
codePoint = '_';
break;
case 30:
codePoint = '^';
break;
case 29:
codePoint = ']';
break;
case 28:
codePoint = '\\';
break;
default:
codePoint += 96;
break;
}
}
inputCodePoint(codePoint, ctrlHeld, false);
}
}
};
}
@Override
protected int computeVerticalScrollRange() {
return mEmulator == null ? 1 : mEmulator.getScreen().getActiveRows();
}
@Override
protected int computeVerticalScrollExtent() {
return mEmulator == null ? 1 : mEmulator.mRows;
}
@Override
protected int computeVerticalScrollOffset() {
return mEmulator == null ? 1 : mEmulator.getScreen().getActiveRows() + mTopRow - mEmulator.mRows;
}
public void onScreenUpdated() {
if (mEmulator == null) return;
boolean skipScrolling = false;
if (mIsSelectingText) {
// Do not scroll when selecting text.
int rowsInHistory = mEmulator.getScreen().getActiveTranscriptRows();
int rowShift = mEmulator.getScrollCounter();
if (-mTopRow + rowShift > rowsInHistory) {
// .. unless we're hitting the end of history transcript, in which
// case we abort text selection and scroll to end.
toggleSelectingText(null);
} else {
skipScrolling = true;
mTopRow -= rowShift;
mSelY1 -= rowShift;
mSelY2 -= rowShift;
}
}
if (!skipScrolling && mTopRow != 0) {
// Scroll down if not already there.
if (mTopRow < -3) {
// Awaken scroll bars only if scrolling a noticeable amount
// - we do not want visible scroll bars during normal typing
// of one row at a time.
awakenScrollBars();
}
mTopRow = 0;
}
mEmulator.clearScrollCounter();
invalidate();
}
/**
* Sets the text size, which in turn sets the number of rows and columns.
*
* @param textSize the new font size, in density-independent pixels.
*/
public void setTextSize(int textSize) {
mRenderer = new TerminalRenderer(textSize, mRenderer == null ? Typeface.MONOSPACE : mRenderer.mTypeface);
updateSize();
}
public void setTypeface(Typeface newTypeface) {
mRenderer = new TerminalRenderer(mRenderer.mTextSize, newTypeface);
updateSize();
invalidate();
}
@Override
public boolean onCheckIsTextEditor() {
return true;
}
@Override
public boolean isOpaque() {
return true;
}
/** Send a single mouse event code to the terminal. */
void sendMouseEventCode(MotionEvent e, int button, boolean pressed) {
int x = (int) (e.getX() / mRenderer.mFontWidth) + 1;
int y = (int) ((e.getY() - mRenderer.mFontLineSpacingAndAscent) / mRenderer.mFontLineSpacing) + 1;
if (pressed && (button == TerminalEmulator.MOUSE_WHEELDOWN_BUTTON || button == TerminalEmulator.MOUSE_WHEELUP_BUTTON)) {
if (mMouseStartDownTime == e.getDownTime()) {
x = mMouseScrollStartX;
y = mMouseScrollStartY;
} else {
mMouseStartDownTime = e.getDownTime();
mMouseScrollStartX = x;
mMouseScrollStartY = y;
}
}
mEmulator.sendMouseEvent(button, x, y, pressed);
}
/** Perform a scroll, either from dragging the screen or by scrolling a mouse wheel. */
void doScroll(MotionEvent event, int rowsDown) {
boolean up = rowsDown < 0;
int amount = Math.abs(rowsDown);
for (int i = 0; i < amount; i++) {
if (mEmulator.isMouseTrackingActive()) {
sendMouseEventCode(event, up ? TerminalEmulator.MOUSE_WHEELUP_BUTTON : TerminalEmulator.MOUSE_WHEELDOWN_BUTTON, true);
} else if (mEmulator.isAlternateBufferActive()) {
// Send up and down key events for scrolling, which is what some terminals do to make scroll work in
// e.g. less, which shifts to the alt screen without mouse handling.
handleKeyCode(up ? KeyEvent.KEYCODE_DPAD_UP : KeyEvent.KEYCODE_DPAD_DOWN, 0);
} else {
mTopRow = Math.min(0, Math.max(-(mEmulator.getScreen().getActiveTranscriptRows()), mTopRow + (up ? -1 : 1)));
if (!awakenScrollBars()) invalidate();
}
}
}
/** Overriding {@link View#onGenericMotionEvent(MotionEvent)}. */
@Override
public boolean onGenericMotionEvent(MotionEvent event) {
if (mEmulator != null && event.isFromSource(InputDevice.SOURCE_MOUSE) && event.getAction() == MotionEvent.ACTION_SCROLL) {
// Handle mouse wheel scrolling.
boolean up = event.getAxisValue(MotionEvent.AXIS_VSCROLL) > 0.0f;
doScroll(event, up ? -3 : 3);
return true;
}
return false;
}
@SuppressLint("ClickableViewAccessibility")
@Override
@TargetApi(23)
public boolean onTouchEvent(MotionEvent ev) {
if (mEmulator == null) return true;
final int action = ev.getAction();
if (mIsSelectingText) {
int cy = (int) (ev.getY() / mRenderer.mFontLineSpacing) + mTopRow;
int cx = (int) (ev.getX() / mRenderer.mFontWidth);
switch (action) {
case MotionEvent.ACTION_UP:
mInitialTextSelection = false;
break;
case MotionEvent.ACTION_DOWN:
int distanceFromSel1 = Math.abs(cx - mSelX1) + Math.abs(cy - mSelY1);
int distanceFromSel2 = Math.abs(cx - mSelX2) + Math.abs(cy - mSelY2);
mIsDraggingLeftSelection = distanceFromSel1 <= distanceFromSel2;
mSelectionDownX = ev.getX();
mSelectionDownY = ev.getY();
break;
case MotionEvent.ACTION_MOVE:
if (mInitialTextSelection) break;
float deltaX = ev.getX() - mSelectionDownX;
float deltaY = ev.getY() - mSelectionDownY;
int deltaCols = (int) Math.ceil(deltaX / mRenderer.mFontWidth);
int deltaRows = (int) Math.ceil(deltaY / mRenderer.mFontLineSpacing);
mSelectionDownX += deltaCols * mRenderer.mFontWidth;
mSelectionDownY += deltaRows * mRenderer.mFontLineSpacing;
if (mIsDraggingLeftSelection) {
mSelX1 += deltaCols;
mSelY1 += deltaRows;
} else {
mSelX2 += deltaCols;
mSelY2 += deltaRows;
}
mSelX1 = Math.min(mEmulator.mColumns, Math.max(0, mSelX1));
mSelX2 = Math.min(mEmulator.mColumns, Math.max(0, mSelX2));
if (mSelY1 == mSelY2 && mSelX1 > mSelX2 || mSelY1 > mSelY2) {
// Switch handles.
mIsDraggingLeftSelection = !mIsDraggingLeftSelection;
int tmpX1 = mSelX1, tmpY1 = mSelY1;
mSelX1 = mSelX2;
mSelY1 = mSelY2;
mSelX2 = tmpX1;
mSelY2 = tmpY1;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
mActionMode.invalidateContentRect();
invalidate();
break;
default:
break;
}
mGestureRecognizer.onTouchEvent(ev);
return true;
} else if (ev.isFromSource(InputDevice.SOURCE_MOUSE)) {
if (ev.isButtonPressed(MotionEvent.BUTTON_SECONDARY)) {
if (action == MotionEvent.ACTION_DOWN) showContextMenu();
return true;
} else if (ev.isButtonPressed(MotionEvent.BUTTON_TERTIARY)) {
ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clipData = clipboard.getPrimaryClip();
if (clipData != null) {
CharSequence paste = clipData.getItemAt(0).coerceToText(getContext());
if (!TextUtils.isEmpty(paste)) mEmulator.paste(paste.toString());
}
} else if (mEmulator.isMouseTrackingActive()) { // BUTTON_PRIMARY.
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_UP:
sendMouseEventCode(ev, TerminalEmulator.MOUSE_LEFT_BUTTON, ev.getAction() == MotionEvent.ACTION_DOWN);
break;
case MotionEvent.ACTION_MOVE:
sendMouseEventCode(ev, TerminalEmulator.MOUSE_LEFT_BUTTON_MOVED, true);
break;
}
return true;
}
}
mGestureRecognizer.onTouchEvent(ev);
return true;
}
@Override
public boolean onKeyPreIme(int keyCode, KeyEvent event) {
if (LOG_KEY_EVENTS)
Log.i(EmulatorDebug.LOG_TAG, "onKeyPreIme(keyCode=" + keyCode + ", event=" + event + ")");
if (keyCode == KeyEvent.KEYCODE_BACK) {
if (mIsSelectingText) {
toggleSelectingText(null);
return true;
} else if (mOnKeyListener.shouldBackButtonBeMappedToEscape()) {
// Intercept back button to treat it as escape:
switch (event.getAction()) {
case KeyEvent.ACTION_DOWN:
return onKeyDown(keyCode, event);
case KeyEvent.ACTION_UP:
return onKeyUp(keyCode, event);
}
}
}
return super.onKeyPreIme(keyCode, event);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (LOG_KEY_EVENTS)
Log.i(EmulatorDebug.LOG_TAG, "onKeyDown(keyCode=" + keyCode + ", isSystem()=" + event.isSystem() + ", event=" + event + ")");
if (mEmulator == null) return true;
if (mOnKeyListener.onKeyDown(keyCode, event, mTermSession)) {
invalidate();
return true;
} else if (event.isSystem() && (!mOnKeyListener.shouldBackButtonBeMappedToEscape() || keyCode != KeyEvent.KEYCODE_BACK)) {
return super.onKeyDown(keyCode, event);
} else if (event.getAction() == KeyEvent.ACTION_MULTIPLE && keyCode == KeyEvent.KEYCODE_UNKNOWN) {
mTermSession.write(event.getCharacters());
return true;
}
final int metaState = event.getMetaState();
final boolean controlDownFromEvent = event.isCtrlPressed();
final boolean leftAltDownFromEvent = (metaState & KeyEvent.META_ALT_LEFT_ON) != 0;
final boolean rightAltDownFromEvent = (metaState & KeyEvent.META_ALT_RIGHT_ON) != 0;
int keyMod = 0;
if (controlDownFromEvent) keyMod |= KeyHandler.KEYMOD_CTRL;
if (event.isAltPressed()) keyMod |= KeyHandler.KEYMOD_ALT;
if (event.isShiftPressed()) keyMod |= KeyHandler.KEYMOD_SHIFT;
if (handleKeyCode(keyCode, keyMod)) {
if (LOG_KEY_EVENTS) Log.i(EmulatorDebug.LOG_TAG, "handleKeyCode() took key event");
return true;
}
// Clear Ctrl since we handle that ourselves:
int bitsToClear = KeyEvent.META_CTRL_MASK;
if (rightAltDownFromEvent) {
// Let right Alt/Alt Gr be used to compose characters.
} else {
// Use left alt to send to terminal (e.g. Left Alt+B to jump back a word), so remove:
bitsToClear |= KeyEvent.META_ALT_ON | KeyEvent.META_ALT_LEFT_ON;
}
int effectiveMetaState = event.getMetaState() & ~bitsToClear;
int result = event.getUnicodeChar(effectiveMetaState);
if (LOG_KEY_EVENTS)
Log.i(EmulatorDebug.LOG_TAG, "KeyEvent#getUnicodeChar(" + effectiveMetaState + ") returned: " + result);
if (result == 0) {
return true;
}
int oldCombiningAccent = mCombiningAccent;
if ((result & KeyCharacterMap.COMBINING_ACCENT) != 0) {
// If entered combining accent previously, write it out:
if (mCombiningAccent != 0)
inputCodePoint(mCombiningAccent, controlDownFromEvent, leftAltDownFromEvent);
mCombiningAccent = result & KeyCharacterMap.COMBINING_ACCENT_MASK;
} else {
if (mCombiningAccent != 0) {
int combinedChar = KeyCharacterMap.getDeadChar(mCombiningAccent, result);
if (combinedChar > 0) result = combinedChar;
mCombiningAccent = 0;
}
inputCodePoint(result, controlDownFromEvent, leftAltDownFromEvent);
}
if (mCombiningAccent != oldCombiningAccent) invalidate();
return true;
}
void inputCodePoint(int codePoint, boolean controlDownFromEvent, boolean leftAltDownFromEvent) {
if (LOG_KEY_EVENTS) {
Log.i(EmulatorDebug.LOG_TAG, "inputCodePoint(codePoint=" + codePoint + ", controlDownFromEvent=" + controlDownFromEvent + ", leftAltDownFromEvent="
+ leftAltDownFromEvent + ")");
}
final boolean controlDown = controlDownFromEvent || mOnKeyListener.readControlKey();
final boolean altDown = leftAltDownFromEvent || mOnKeyListener.readAltKey();
if (mOnKeyListener.onCodePoint(codePoint, controlDown, mTermSession)) return;
if (controlDown) {
if (codePoint >= 'a' && codePoint <= 'z') {
codePoint = codePoint - 'a' + 1;
} else if (codePoint >= 'A' && codePoint <= 'Z') {
codePoint = codePoint - 'A' + 1;
} else if (codePoint == ' ' || codePoint == '2') {
codePoint = 0;
} else if (codePoint == '[' || codePoint == '3') {
codePoint = 27; // ^[ (Esc)
} else if (codePoint == '\\' || codePoint == '4') {
codePoint = 28;
} else if (codePoint == ']' || codePoint == '5') {
codePoint = 29;
} else if (codePoint == '^' || codePoint == '6') {
codePoint = 30; // control-^
} else if (codePoint == '_' || codePoint == '7' || codePoint == '/') {
// "Ctrl-/ sends 0x1f which is equivalent of Ctrl-_ since the days of VT102"
// - http://apple.stackexchange.com/questions/24261/how-do-i-send-c-that-is-control-slash-to-the-terminal
codePoint = 31;
} else if (codePoint == '8') {
codePoint = 127; // DEL
}
}
if (codePoint > -1) {
// Work around bluetooth keyboards sending funny unicode characters instead
// of the more normal ones from ASCII that terminal programs expect - the
// desire to input the original characters should be low.
switch (codePoint) {
case 0x02DC: // SMALL TILDE.
codePoint = 0x007E; // TILDE (~).
break;
case 0x02CB: // MODIFIER LETTER GRAVE ACCENT.
codePoint = 0x0060; // GRAVE ACCENT (`).
break;
case 0x02C6: // MODIFIER LETTER CIRCUMFLEX ACCENT.
codePoint = 0x005E; // CIRCUMFLEX ACCENT (^).
break;
}
// If left alt, send escape before the code point to make e.g. Alt+B and Alt+F work in readline:
mTermSession.writeCodePoint(altDown, codePoint);
}
}
/** Input the specified keyCode if applicable and return if the input was consumed. */
public boolean handleKeyCode(int keyCode, int keyMod) {
TerminalEmulator term = mTermSession.getEmulator();
String code = KeyHandler.getCode(keyCode, keyMod, term.isCursorKeysApplicationMode(), term.isKeypadApplicationMode());
if (code == null) return false;
mTermSession.write(code);
return true;
}
/**
* Called when a key is released in the view.
*
* @param keyCode The keycode of the key which was released.
* @param event A {@link KeyEvent} describing the event.
* @return Whether the event was handled.
*/
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (LOG_KEY_EVENTS)
Log.i(EmulatorDebug.LOG_TAG, "onKeyUp(keyCode=" + keyCode + ", event=" + event + ")");
if (mEmulator == null) return true;
if (mOnKeyListener.onKeyUp(keyCode, event)) {
invalidate();
return true;
} else if (event.isSystem()) {
// Let system key events through.
return super.onKeyUp(keyCode, event);
}
return true;
}
/**
* This is called during layout when the size of this view has changed. If you were just added to the view
* hierarchy, you're called with the old values of 0.
*/
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
updateSize();
}
/** Check if the terminal size in rows and columns should be updated. */
public void updateSize() {
int viewWidth = getWidth();
int viewHeight = getHeight();
if (viewWidth == 0 || viewHeight == 0 || mTermSession == null) return;
// Set to 80 and 24 if you want to enable vttest.
int newColumns = Math.max(4, (int) (viewWidth / mRenderer.mFontWidth));
int newRows = Math.max(4, (viewHeight - mRenderer.mFontLineSpacingAndAscent) / mRenderer.mFontLineSpacing);
if (mEmulator == null || (newColumns != mEmulator.mColumns || newRows != mEmulator.mRows)) {
mTermSession.updateSize(newColumns, newRows);
mEmulator = mTermSession.getEmulator();
mTopRow = 0;
scrollTo(0, 0);
invalidate();
}
}
@Override
protected void onDraw(Canvas canvas) {
if (mEmulator == null) {
canvas.drawColor(0XFF000000);
} else {
mRenderer.render(mEmulator, canvas, mTopRow, mSelY1, mSelY2, mSelX1, mSelX2);
if (mIsSelectingText) {
final int gripHandleWidth = mLeftSelectionHandle.getIntrinsicWidth();
final int gripHandleMargin = gripHandleWidth / 4; // See the png.
int right = Math.round((mSelX1) * mRenderer.mFontWidth) + gripHandleMargin;
int top = (mSelY1 + 1 - mTopRow) * mRenderer.mFontLineSpacing + mRenderer.mFontLineSpacingAndAscent;
mLeftSelectionHandle.setBounds(right - gripHandleWidth, top, right, top + mLeftSelectionHandle.getIntrinsicHeight());
mLeftSelectionHandle.draw(canvas);
int left = Math.round((mSelX2 + 1) * mRenderer.mFontWidth) - gripHandleMargin;
top = (mSelY2 + 1 - mTopRow) * mRenderer.mFontLineSpacing + mRenderer.mFontLineSpacingAndAscent;
mRightSelectionHandle.setBounds(left, top, left + gripHandleWidth, top + mRightSelectionHandle.getIntrinsicHeight());
mRightSelectionHandle.draw(canvas);
}
}
}
/** Toggle text selection mode in the view. */
@TargetApi(23)
public void toggleSelectingText(MotionEvent ev) {
mIsSelectingText = !mIsSelectingText;
mOnKeyListener.copyModeChanged(mIsSelectingText);
if (mIsSelectingText) {
if (mLeftSelectionHandle == null) {
mLeftSelectionHandle = (BitmapDrawable) getContext().getDrawable(R.drawable.text_select_handle_left_material);
mRightSelectionHandle = (BitmapDrawable) getContext().getDrawable(R.drawable.text_select_handle_right_material);
}
int cx = (int) (ev.getX() / mRenderer.mFontWidth);
final boolean eventFromMouse = ev.isFromSource(InputDevice.SOURCE_MOUSE);
// Offset for finger:
final int SELECT_TEXT_OFFSET_Y = eventFromMouse ? 0 : -40;
int cy = (int) ((ev.getY() + SELECT_TEXT_OFFSET_Y) / mRenderer.mFontLineSpacing) + mTopRow;
mSelX1 = mSelX2 = cx;
mSelY1 = mSelY2 = cy;
TerminalBuffer screen = mEmulator.getScreen();
if (!" ".equals(screen.getSelectedText(mSelX1, mSelY1, mSelX1, mSelY1))) {
// Selecting something other than whitespace. Expand to word.
while (mSelX1 > 0 && !"".equals(screen.getSelectedText(mSelX1 - 1, mSelY1, mSelX1 - 1, mSelY1))) {
mSelX1--;
}
while (mSelX2 < mEmulator.mColumns - 1 && !"".equals(screen.getSelectedText(mSelX2 + 1, mSelY1, mSelX2 + 1, mSelY1))) {
mSelX2++;
}
}
mInitialTextSelection = true;
mIsDraggingLeftSelection = true;
mSelectionDownX = ev.getX();
mSelectionDownY = ev.getY();
final ActionMode.Callback callback = new ActionMode.Callback() {
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
int show = MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT;
ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
menu.add(Menu.NONE, 1, Menu.NONE, R.string.copy_text).setShowAsAction(show);
menu.add(Menu.NONE, 2, Menu.NONE, R.string.paste_text).setEnabled(clipboard.hasPrimaryClip()).setShowAsAction(show);
menu.add(Menu.NONE, 3, Menu.NONE, R.string.text_selection_more);
return true;
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
return false;
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
switch (item.getItemId()) {
case 1:
String selectedText = mEmulator.getSelectedText(mSelX1, mSelY1, mSelX2, mSelY2).trim();
mTermSession.clipboardText(selectedText);
break;
case 2:
ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clipData = clipboard.getPrimaryClip();
if (clipData != null) {
CharSequence paste = clipData.getItemAt(0).coerceToText(getContext());
if (!TextUtils.isEmpty(paste)) mEmulator.paste(paste.toString());
}
break;
case 3:
showContextMenu();
break;
}
toggleSelectingText(null);
return true;
}
@Override
public void onDestroyActionMode(ActionMode mode) {
}
};
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
mActionMode = startActionMode(new ActionMode.Callback2() {
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
return callback.onCreateActionMode(mode, menu);
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
return false;
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
return callback.onActionItemClicked(mode, item);
}
@Override
public void onDestroyActionMode(ActionMode mode) {
// Ignore.
}
@Override
public void onGetContentRect(ActionMode mode, View view, Rect outRect) {
int x1 = Math.round(mSelX1 * mRenderer.mFontWidth);
int x2 = Math.round(mSelX2 * mRenderer.mFontWidth);
int y1 = Math.round((mSelY1 - mTopRow) * mRenderer.mFontLineSpacing);
int y2 = Math.round((mSelY2 + 1 - mTopRow) * mRenderer.mFontLineSpacing);
outRect.set(Math.min(x1, x2), y1, Math.max(x1, x2), y2);
}
}, ActionMode.TYPE_FLOATING);
} else {
mActionMode = startActionMode(callback);
}
invalidate();
} else {
mActionMode.finish();
mSelX1 = mSelY1 = mSelX2 = mSelY2 = -1;
invalidate();
}
}
public TerminalSession getCurrentSession() {
return mTermSession;
}
}

View File

@@ -1,7 +0,0 @@
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate android:duration="30"
android:fromXDelta="-1%"
android:repeatCount="1"
android:repeatMode="reverse"
android:toXDelta="1%"/>
</set>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@android:color/black"/>
<foreground android:drawable="@drawable/ic_foreground"/>
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<solid android:color="#E0E0E0" /> <solid android:color="#E0E0E0" />
</shape> </shape>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<solid android:color="#212325" />
</shape>

View File

@@ -0,0 +1,28 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="108dp"
android:width="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<!-- Keep in sync with non-adaptive ic_launcher.xml -->
<path
android:fillColor="#FFFFFF"
android:pathData="M34,38
h6
l12,16
l-12,16
h-6
l12,-16
"
/>
<path
android:fillColor="#FFFFFF"
android:pathData="M56,66
h18
v4
h-18
"
/>
</vector>

View File

@@ -0,0 +1,34 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="108dp"
android:width="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#000000"
android:pathData="M18,54
A36,36 0 1,1 90,54
A36,36 0 1,1 18,54 Z" />
<!-- Keep in sync with adaptive ic_foreground.xml: -->
<path
android:fillColor="#FFFFFF"
android:pathData="M34,38
h6
l12,16
l-12,16
h-6
l12,-16
"
/>
<path
android:fillColor="#FFFFFF"
android:pathData="M56,66
h18
v4
h-18
"
/>
</vector>

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="#000" 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="#000" android:pathData="M5,4H2L8,12L2,20H5L11,12L5,4Z"
android:pathData="M12,12 android:fillColor="#ffffff"/>
l5,0
l0,10
l-5,0"
/>
<path
android:pathData="M13,18H22V20H13V18Z"
android:fillColor="#ffffff"/>
</group>
</vector> </vector>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8" ?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_activated="true" android:drawable="@drawable/current_session_black"/>
<item android:state_activated="false" android:drawable="@drawable/session_ripple_black"/>
</selector>

View File

@@ -4,4 +4,4 @@
<item> <item>
<color android:color="@android:color/white" /> <color android:color="@android:color/white" />
</item> </item>
</ripple> </ripple>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@android:color/darker_gray" >
<item>
<color android:color="@android:color/background_dark" />
</item>
</ripple>

View File

@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@drawable/text_select_handle_left_mtrl_alpha"
android:tint="#2196F3" />

View File

@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@drawable/text_select_handle_right_mtrl_alpha"
android:tint="#2196F3" />

View File

@@ -1,9 +1,10 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical"> android:orientation="vertical"
android:fitsSystemWindows="true">
<android.support.v4.widget.DrawerLayout <androidx.drawerlayout.widget.DrawerLayout
android:id="@+id/drawer_layout" android:id="@+id/drawer_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_alignParentTop="true" android:layout_alignParentTop="true"
@@ -14,9 +15,13 @@
android:id="@+id/terminal_view" android:id="@+id/terminal_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_marginRight="3dp"
android:layout_marginLeft="3dp"
android:focusableInTouchMode="true" android:focusableInTouchMode="true"
android:scrollbarThumbVertical="@drawable/terminal_scroll_shape" android:scrollbarThumbVertical="@drawable/terminal_scroll_shape"
android:scrollbars="vertical" /> android:scrollbars="vertical"
android:importantForAutofill="no"
android:autofillHints="password" />
<LinearLayout <LinearLayout
android:id="@+id/left_drawer" android:id="@+id/left_drawer"
@@ -63,13 +68,13 @@
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
</android.support.v4.widget.DrawerLayout> </androidx.drawerlayout.widget.DrawerLayout>
<android.support.v4.view.ViewPager <androidx.viewpager.widget.ViewPager
android:id="@+id/viewpager" android:id="@+id/viewpager"
android:visibility="gone" android:visibility="gone"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="40dp" android:layout_height="37.5dp"
android:background="@android:drawable/screen_background_dark_transparent" android:background="@android:drawable/screen_background_dark_transparent"
android:layout_alignParentBottom="true" /> android:layout_alignParentBottom="true" />
</RelativeLayout> </RelativeLayout>

View File

@@ -7,7 +7,9 @@
android:imeOptions="actionSend|flagNoFullscreen" android:imeOptions="actionSend|flagNoFullscreen"
android:maxLines="1" android:maxLines="1"
android:inputType="text" android:inputType="text"
android:importantForAutofill="no"
android:textColor="@android:color/white" android:textColor="@android:color/white"
android:textColorHighlight="@android:color/darker_gray"
android:paddingTop="0dp" android:paddingTop="0dp"
android:textCursorDrawable="@null" android:textCursorDrawable="@null"
android:paddingBottom="0dp" android:paddingBottom="0dp"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 343 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 204 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 324 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 510 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 685 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

View File

@@ -1,60 +1,54 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-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="new_session">New session</string> <string name="run_command_permission_label">Run commands in Termux environment</string>
<string name="new_session_failsafe">Failsafe</string> <string name="run_command_permission_description">execute arbitrary commands within Termux environment</string>
<string name="toggle_soft_keyboard">Keyboard</string> <string name="new_session">New session</string>
<string name="reset_terminal">Reset</string> <string name="new_session_failsafe">Failsafe</string>
<string name="style_terminal">Style</string> <string name="toggle_soft_keyboard">Keyboard</string>
<string name="toggle_fullscreen">Fullscreen</string> <string name="reset_terminal">Reset</string>
<string name="share_transcript_title">Terminal transcript</string> <string name="style_terminal">Style</string>
<string name="help">Help</string> <string name="share_transcript_title">Terminal transcript</string>
<string name="help">Help</string>
<string name="toggle_keep_screen_on">Keep screen on</string>
<string name="autofill_password">Autofill password</string>
<string name="welcome_dialog_title">Welcome to Termux</string> <string name="bootstrap_installer_body">Installing…</string>
<string name="welcome_dialog_body">Long press and select <i>More…</i> to show a menu where <i>Help</i> is available.\n\nExecute <b>apt update</b> to update the packages list before installing packages.</string> <string name="bootstrap_error_title">Unable to install</string>
<string name="welcome_dialog_dont_show_again_button">Do not show 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_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_installer_body">Installing…</string> <string name="max_terminals_reached_title">Max terminals reached</string>
<string name="bootstrap_error_title">Unable to install</string> <string name="max_terminals_reached_message">Close down existing ones before creating new.</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_abort">Abort</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="max_terminals_reached_title">Max terminals reached</string> <string name="reset_toast_notification">Terminal reset.</string>
<string name="max_terminals_reached_message">Close down existing ones before creating new.</string>
<string name="reset_toast_notification">Terminal reset.</string> <string name="select_url">Select URL</string>
<string name="select_url_dialog_title">Click URL to copy or long press to open</string>
<string name="select_all_and_share">Share transcript</string>
<string name="select_url_no_found">No URL found in the terminal.</string>
<string name="select_url_copied_to_clipboard">URL copied to clipboard</string>
<string name="share_transcript_chooser_title">Send text to:</string>
<string name="select_url">Select URL</string> <string name="kill_process">Kill process (%d)</string>
<string name="select_url_dialog_title">Click URL to copy or long press to open</string> <string name="confirm_kill_process">Really kill this session?</string>
<string name="select_all_and_share">Share transcript</string>
<string name="select_url_no_found">No URL found in the terminal.</string>
<string name="select_url_copied_to_clipboard">URL copied to clipboard</string>
<string name="share_transcript_chooser_title">Send text to:</string>
<string name="paste_text">Paste</string> <string name="session_rename_title">Set session name</string>
<string name="copy_text">Copy</string> <string name="session_rename_positive_button">Set</string>
<string name="text_selection_more">More…</string> <string name="session_new_named_title">New named session</string>
<string name="session_new_named_positive_button">Create</string>
<string name="kill_process">Kill process (%d)</string> <string name="styling_not_installed">The Termux:Style add-on is not installed.</string>
<string name="confirm_kill_process">Really kill this session?</string> <string name="styling_install">Install</string>
<string name="session_rename_title">Set session name</string> <string name="notification_action_exit">Exit</string>
<string name="session_rename_positive_button">Set</string> <string name="notification_action_wake_lock">Acquire wakelock</string>
<string name="session_new_named_title">New named session</string> <string name="notification_action_wake_unlock">Release wakelock</string>
<string name="session_new_named_positive_button">Create</string>
<string name="styling_not_installed">The Termux:Style add-on is not installed.</string>
<string name="styling_install">Install</string>
<string name="notification_action_exit">Exit</string>
<string name="notification_action_wake_lock">Acquire wakelock</string>
<string name="notification_action_wake_unlock">Release wakelock</string>
<string name="file_received_title">Save file in ~/downloads/</string>
<string name="file_received_edit_button">Edit</string>
<string name="file_received_open_folder_button">Open folder</string>
<string name="file_received_title">Save file in ~/downloads/</string>
<string name="file_received_edit_button">Edit</string>
<string name="file_received_open_folder_button">Open folder</string>
</resources> </resources>

View File

@@ -1,10 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"> <resources xmlns:android="http://schemas.android.com/apk/res/android">
<!-- See https://developer.android.com/training/material/theme.html for how to customize the Material theme. -->
<!-- NOTE: Cannot use "Light." since it hides the terminal scrollbar on the default black background. -->
<style name="Theme.Termux" parent="@android:style/Theme.Material.Light.NoActionBar"> <style name="Theme.Termux" parent="@android:style/Theme.Material.Light.NoActionBar">
<item name="android:statusBarColor">#000000</item> <item name="android:statusBarColor">#000000</item>
<item name="android:colorPrimary">#FF000000</item>
<item name="android:windowBackground">@android:color/black</item> <item name="android:windowBackground">@android:color/black</item>
<!-- Seen in buttons on left drawer: --> <!-- Seen in buttons on left drawer: -->
@@ -13,11 +12,38 @@
<!-- Avoid action mode toolbar pushing down terminal content when <!-- Avoid action mode toolbar pushing down terminal content when
selecting text on pre-6.0 (non-floating toolbar). --> selecting text on pre-6.0 (non-floating toolbar). -->
<item name="android:windowActionModeOverlay">true</item> <item name="android:windowActionModeOverlay">true</item>
<item name="android:windowTranslucentStatus">true</item>
<item name="android:windowTranslucentNavigation">true</item>
<!-- https://developer.android.com/training/tv/start/start.html#transition-color -->
<item name="android:windowAllowReturnTransitionOverlap">true</item>
<item name="android:windowAllowEnterTransitionOverlap">true</item>
</style> </style>
<style name="TermuxAlertDialogStyle" parent="@android:style/Theme.Material.Light.Dialog.Alert"> <style name="TermuxAlertDialogStyle" parent="@android:style/Theme.Material.Light.Dialog.Alert">
<!-- Seen in buttons on alert dialog: --> <!-- Seen in buttons on alert dialog: -->
<item name="android:colorAccent">#212121</item> <item name="android:colorAccent">#212121</item>
</style> </style>
<!-- See https://developer.android.com/training/material/theme.html for how to customize the Material theme. -->
<!-- NOTE: Cannot use "Light." since it hides the terminal scrollbar on the default black background. -->
<style name="Theme.Termux.Black" parent="@android:style/Theme.Material.NoActionBar">
<item name="android:statusBarColor">#000000</item>
<item name="android:colorPrimary">#FF000000</item>
<item name="android:windowBackground">@android:color/black</item>
<!-- Seen in buttons on left drawer: -->
<item name="android:colorAccent">#FDFDFD</item>
<!-- Avoid action mode toolbar pushing down terminal content when
selecting text on pre-6.0 (non-floating toolbar). -->
<item name="android:windowActionModeOverlay">true</item>
<item name="android:windowTranslucentStatus">true</item>
<item name="android:windowTranslucentNavigation">true</item>
<!-- https://developer.android.com/training/tv/start/start.html#transition-color -->
<item name="android:windowAllowReturnTransitionOverlap">true</item>
<item name="android:windowAllowEnterTransitionOverlap">true</item>
</style>
</resources> </resources>

View File

@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<full-backup-content>
<!-- See https://developer.android.com/training/backup/autosyncapi.html -->
<include domain="file" path="home/backup" />
</full-backup-content>

View File

@@ -1,12 +1,30 @@
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android"> <shortcuts xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android">
<shortcut <shortcut
android:shortcutId="new_session" android:shortcutId="new_session"
android:enabled="true" android:enabled="true"
android:icon="@drawable/ic_new_session" android:icon="@drawable/ic_new_session"
android:shortcutShortLabel="@string/new_session"> android:shortcutShortLabel="@string/new_session"
tools:targetApi="n_mr1">
<intent <intent
android:action="android.intent.action.RUN" android:action="android.intent.action.RUN"
android:targetPackage="com.termux" android:targetPackage="com.termux"
android:targetClass="com.termux.app.TermuxActivity"/> android:targetClass="com.termux.app.TermuxActivity"/>
</shortcut> </shortcut>
<shortcut
android:shortcutId="new_failsafe_session"
android:enabled="true"
android:icon="@drawable/ic_new_session"
android:shortcutShortLabel="@string/new_session_failsafe"
tools:targetApi="n_mr1">
<intent
android:action="android.intent.action.RUN"
android:targetPackage="com.termux"
android:targetClass="com.termux.app.TermuxActivity">
<extra android:name="com.termux.app.failsafe_session" android:value="true" />
</intent>
</shortcut>
</shortcuts> </shortcuts>

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

@@ -1,20 +1,13 @@
#!/bin/sh #!/bin/sh
set -e -u
for DENSITY in mdpi hdpi xhdpi xxhdpi xxxhdpi; do for APP in api boot styling tasker widget; do
FOLDER=../app/src/main/res/mipmap-$DENSITY APPDIR=../../termux-$APP
for file in ic_foreground ic_launcher; do
for FILE in ic_launcher ic_launcher_round; do cp ../app/src/main/res/drawable/$file.xml \
PNG=$FOLDER/$FILE.png $APPDIR/app/src/main/res/drawable/$file.xml
# Update other apps:
for APP in tasker widget api; do
APPDIR=../../termux-$APP
if [ -d $APPDIR ]; then
APP_FOLDER=$APPDIR/app/src/main/res/mipmap-$DENSITY
mkdir -p $APP_FOLDER
cp $PNG $APP_FOLDER/$FILE.png
fi
done
done done
cp ../app/src/main/res/drawable-anydpi-v26/ic_launcher.xml \
$APPDIR/app/src/main/res/drawable-anydpi-v26/$file.xml
done done

30
art/feature-graphic.svg Normal file
View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<!--
This is a feature graphic:
https://support.google.com/googleplay/android-developer/answer/1078870
- 1024px by 500px, no alpha
- Don't include any copy or important visual information near the borders of the asset,
specifically near the bottom third of the frame.
- Try to center align any logo/copy information in the vertical and horizontal center of the frame.
- If adding text, use large font sizes.
- Your graphic may be displayed alone without the app icon.
-->
<svg xmlns="http://www.w3.org/2000/svg"
version="1.1"
viewBox="0 0 1024 500">
<rect fill="#0" width="100%" height="100%" />
<text id="shell_prompt"
x="130"
y="330"
style="fill: #ffffff; font-size: 124px; font-family: Menlo;">
<!--
<tspan>$</tspan>
<tspan x="290">Termux</tspan>
<tspan x="734">█</tspan>
-->
<tspan>$ Termux █</tspan>
</text>
</svg>

After

Width:  |  Height:  |  Size: 962 B

23
art/generate-big-icon.sh Executable file
View File

@@ -0,0 +1,23 @@
#!/bin/sh
set -e -u
echo "Generating ~/termux-icons/ic_launcher.png..."
mkdir -p ~/termux-icons/
vector2svg ../app/src/main/res/drawable/ic_launcher.xml ~/termux-icons/ic_launcher.svg
sed -i "" 's/viewBox="0 0 108 108"/viewBox="18 18 72 72"/' ~/termux-icons/ic_launcher.svg
SIZE=512
rsvg-convert \
-w $SIZE \
-h $SIZE \
-o ~/termux-icons/ic_launcher_$SIZE.png \
~/termux-icons/ic_launcher.svg
rsvg-convert \
-b black \
-w $SIZE \
-h $SIZE \
-o ~/termux-icons/ic_launcher_square_$SIZE.png \
~/termux-icons/ic_launcher.svg

View File

@@ -0,0 +1,5 @@
#!/bin/bash
echo "Generating feature graphics to ~/termux-icons/termux-feature-graphic.png..."
mkdir -p ~/termux-icons/
rsvg-convert feature-graphic.svg > ~/termux-icons/feature-graphic.png

9
art/generate-tv-banner.sh Executable file
View File

@@ -0,0 +1,9 @@
#!/bin/bash
echo "Generating feature graphics to ~/termux-icons/termux-feature-graphic.png..."
mkdir -p ~/termux-icons/
# The Android TV banner on google play (1280x720) has same aspect ratio
# as the banner in the app (320x180).
rsvg-convert -w 1280 -h 720 tv-banner.svg > ~/termux-icons/tv-banner.png
rsvg-convert -w 320 -h 180 tv-banner.svg > ../app/src/main/res/drawable/banner.png

View File

@@ -4,20 +4,20 @@
<path fill="#000" <path fill="#000"
stroke="#BFCBCD" stroke="#BFCBCD"
stroke-width="2" stroke-width="2"
d="M7,4 d="M9,6
l34,0 l30,0
q3 0,3 3 q3 0,3 3
l0,34 l0,30
q0 3, -3 3 q0 3, -3 3
l-34,0 l-30,0
q-3 0, -3-3 q-3 0, -3-3
l0 -34 l0 -30
q0 -3, 3 -3" q0 -3, 3 -3"
/> />
<!-- Block cursor. --> <!-- Block cursor. -->
<path fill="#FFF" <path fill="#FFF"
d="M12,12 d="M14,14
l5,0 l5,0
l0,10 l0,10
l-5,0" l-5,0"

Before

Width:  |  Height:  |  Size: 512 B

After

Width:  |  Height:  |  Size: 512 B

View File

@@ -1,21 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48">
<!-- Screen and border. -->
<path fill="#000"
stroke="#BFCBCD"
stroke-width="2"
d="M 24, 24
m -21, 0
a 21,21 0 1,0 42,0
a 21,21 0 1,0 -42,0"
/>
<!-- Block cursor. -->
<path fill="#FFF"
d="M15,15
l5,0
l0,10
l-5,0"
/>
</svg>

Before

Width:  |  Height:  |  Size: 429 B

23
art/tv-banner.svg Normal file
View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<!--
This is a tv banner graphic:
https://developer.android.com/design/tv/patterns.html#banner
- Size: 320 x 180 px, xhdpi resource in app
- Size: 1280 x 720 in google play.
- Text must be included in the image. If your app is available in more
than one language, you must provide versions of the banner image for each supported language.
-->
<svg xmlns="http://www.w3.org/2000/svg"
version="1.1"
viewBox="0 0 1280 720">
<rect fill="#0" width="100%" height="100%" />
<text id="shell_prompt"
x="200"
y="410"
style="fill: #ffffff; font-size: 210px; font-family: Menlo, Monospace;">
<tspan>Termux ▌</tspan>
</text>
</svg>

After

Width:  |  Height:  |  Size: 736 B

View File

@@ -1,16 +1,16 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript { buildscript {
repositories { repositories {
jcenter() jcenter()
google()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:2.2.3' classpath 'com.android.tools.build:gradle:4.1.1'
} }
} }
allprojects { allprojects {
repositories { repositories {
google()
jcenter() jcenter()
} }
} }

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=22.0.7026061
compileSdkVersion=28

Binary file not shown.

View File

@@ -1,6 +1,5 @@
#Sun Dec 04 17:26:05 CET 2016
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-3.2.1-bin.zip

59
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,16 +44,16 @@ 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="" 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"
warn ( ) { warn () {
echo "$*" echo "$*"
} }
die ( ) { die () {
echo echo
echo "$*" echo "$*"
echo echo
@@ -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,35 +156,30 @@ 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
# Escape application args # Escape application args
save ( ) { 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= 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

@@ -0,0 +1,77 @@
// Start https://raw.githubusercontent.com/nuuneoi/JCenter/master/installv1.gradle
group = publishedGroupId // Maven Group ID for the artifact
install {
repositories.mavenInstaller {
pom {
project {
packaging 'aar'
groupId publishedGroupId
artifactId artifact
name libraryName
description libraryDescription
url siteUrl
licenses {
license {
name 'GNU General Public License version 3'
url 'https://opensource.org/licenses/gpl-3.0.html'
}
}
developers {
developer {
id 'fornwall'
name 'Fredrik Fornwall'
email 'fredrik@fornwall.net'
}
}
scm {
connection gitUrl
developerConnection gitUrl
url siteUrl
}
}
}
}
}
// End https://raw.githubusercontent.com/nuuneoi/JCenter/master/installv1.gradle
// Start https://raw.githubusercontent.com/nuuneoi/JCenter/master/bintrayv1.gradle
apply plugin: 'com.jfrog.bintray'
version = libraryVersion
task sourcesJar(type: Jar) {
classifier = 'sources'
from android.sourceSets.main.java.srcDirs
}
artifacts {
archives sourcesJar
}
bintray {
user = System.getenv('BINTRAY_USER')
key = System.getenv('BINTRAY_API_KEY')
configurations = ['archives']
pkg {
repo = 'maven'
name = bintrayName
userOrg = 'termux'
desc = libraryDescription
websiteUrl = siteUrl
vcsUrl = gitUrl
licenses = ['GPL-3.0']
publish = true
publicDownloadNumbers = true
version {
desc = libraryDescription
gpg {
sign = false //Determines whether to GPG sign the files. The default is false
// passphrase = properties.getProperty("bintray.gpg.password")
}
}
}
}

View File

@@ -1 +1 @@
include ':app' include ':app', ':terminal-emulator', ':terminal-view'

View File

@@ -0,0 +1,71 @@
plugins {
id "com.jfrog.bintray" version "1.8.5"
id "com.github.dcendents.android-maven" version "2.1"
}
apply plugin: 'com.android.library'
ext {
bintrayName = 'terminal-emulator'
publishedGroupId = 'com.termux'
libraryName = 'TerminalEmulator'
artifact = 'terminal-emulator'
libraryDescription = 'The terminal emulator used in Termux'
siteUrl = 'https://github.com/termux/termux-app'
gitUrl = 'https://github.com/termux/termux-app.git'
libraryVersion = '0.52'
}
android {
compileSdkVersion project.properties.compileSdkVersion.toInteger()
ndkVersion project.properties.ndkVersion
defaultConfig {
minSdkVersion project.properties.minSdkVersion.toInteger()
targetSdkVersion project.properties.targetSdkVersion.toInteger()
externalNativeBuild {
ndkBuild {
cFlags "-std=c11", "-Wall", "-Wextra", "-Werror", "-Os", "-fno-stack-protector", "-Wl,--gc-sections"
}
}
ndk {
abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
externalNativeBuild {
ndkBuild {
path "src/main/jni/Android.mk"
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
testOptions {
unitTests.returnDefaultValues = true
}
}
tasks.withType(Test) {
testLogging {
events "started", "passed", "skipped", "failed"
}
}
dependencies {
testImplementation 'junit:junit:4.13.1'
}
apply from: '../scripts/bintray-publish.gradle'

25
terminal-emulator/proguard-rules.pro vendored Normal file
View File

@@ -0,0 +1,25 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Users/fornwall/lib/android-sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@@ -0,0 +1,2 @@
<manifest package="com.termux.terminal">
</manifest>

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