Compare commits

...

182 Commits
v0.55 ... v0.74

Author SHA1 Message Date
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
71 changed files with 1247 additions and 1110 deletions

View File

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

3
.gitattributes vendored Normal file
View File

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

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

@@ -0,0 +1,20 @@
---
name: Bug report
about: Create a report to help us improve termux-app
---
<!-- Important note: Refusing to provide needed information may result in issue closing. If you are having problems with a package in termux then a bug report should be filed in the termux-packages repo: https://github.com/termux/termux-packages -->
**Problem description**
A clear and concise description of what the problem with the termux app is. You may post screenshots in addition to description.
**Steps to reproduce**
Please post all steps that are needed to reproduce the issue.
**Expected behavior**
A clear and concise description of what you expected to happen.
**Additional information**
Post output of command `termux-info`.
If you are rooted or have access to adb then capture a logcat with `logcat -d "*:W"`, from a adb or root shell.

View File

@@ -0,0 +1,12 @@
---
name: Feature request
about: Suggest a new feature in termux-app
---
**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

16
.gitignore vendored
View File

@@ -20,19 +20,8 @@ local.properties
# Signing files
.signing/
# User-specific configurations
.idea/libraries/
.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/
# Intellij
.idea/
*.iml
# OS-specific files
@@ -41,5 +30,6 @@ local.properties
._*
.Spotlight-V100
.Trashes
.swp
ehthumbs.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>

21
.idea/gradle.xml generated
View File

@@ -1,21 +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" />
<option value="$PROJECT_DIR$/terminal-emulator" />
<option value="$PROJECT_DIR$/terminal-view" />
</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

@@ -12,13 +12,13 @@ android:
components:
- platform-tools
- tools
- build-tools-27.0.1
- android-27
- build-tools-28.0.3
- android-28
- extra-android-m2repository
before_install:
- git clone https://github.com/urho3d/android-ndk.git $HOME/android-ndk
- export ANDROID_NDK_HOME=$HOME/android-ndk
- yes | sdkmanager "ndk-bundle"
- yes | sdkmanager "platforms;android-28"
script:
- ./gradlew testDebugUnitTest

View File

@@ -1,17 +1,17 @@
Termux app
==========
[![Travis build status](https://travis-ci.org/termux/termux-app.svg?branch=master)](https://travis-ci.org/termux/termux-app)
[![Join the chat at https://gitter.im/termux/termux](https://badges.gitter.im/termux/termux.svg)](https://gitter.im/termux/termux)
Termux app
==========
[Termux](https://termux.com) is an Android terminal app and Linux environment.
* [Termux on Google Play Store](https://play.google.com/store/apps/details?id=com.termux)
* [Termux on F-Droid](https://f-droid.org/repository/browse/?fdid=com.termux)
* [Termux Facebook](https://facebook.com/termux/)
* [Termux Google+ community](http://termux.com/community/)
* [Termux Help](http://termux.com/help/)
* [Termux Wiki](https://wiki.termux.com/wiki/)
* [Termux Twitter](http://twitter.com/termux/)
* [Termux Wiki](https://wiki.termux.com/wiki/)
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)

View File

@@ -1,21 +1,21 @@
apply plugin: 'com.android.application'
android {
compileSdkVersion 27
buildToolsVersion "27.0.1"
compileSdkVersion 28
dependencies {
implementation 'com.android.support:support-annotations:27.0.0'
implementation "com.android.support:support-core-ui:27.0.0"
implementation "androidx.annotation:annotation:1.0.1"
implementation "androidx.viewpager:viewpager:1.0.0"
implementation "androidx.drawerlayout:drawerlayout:1.0.0"
implementation project(":terminal-view")
}
defaultConfig {
applicationId "com.termux"
minSdkVersion 21
targetSdkVersion 27
versionCode 55
versionName "0.55"
targetSdkVersion 28
versionCode 74
versionName "0.74"
}
buildTypes {
@@ -25,8 +25,19 @@ android {
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
testImplementation 'junit:junit:4.12'
}
task versionName {
doLast {
print android.defaultConfig.versionName
}
}

View File

@@ -7,11 +7,5 @@
# 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 *;
#}
-renamesourcefileattribute SourceFile
-keepattributes SourceFile,LineNumberTable

View File

@@ -1,4 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.termux"
android:installLocation="internalOnly"
android:sharedUserId="com.termux"
@@ -11,22 +12,29 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<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"/>
<application
android:extractNativeLibs="true"
android:allowBackup="true"
android:fullBackupContent="@xml/backupscheme"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:icon="@drawable/ic_launcher"
android:banner="@drawable/banner"
android:label="@string/application_name"
android:theme="@style/Theme.Termux"
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
android:name="com.termux.app.TermuxActivity"
android:label="@string/application_name"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
android:launchMode="singleTask"
android:resizeableActivity="true"
android:windowSoftInputMode="adjustResize|stateAlwaysVisible" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -44,6 +52,7 @@
android:exported="false"
android:theme="@android:style/Theme.Material.Light.DarkActionBar"
android:parentActivityName=".app.TermuxActivity"
android:resizeableActivity="true"
android:label="@string/application_name" />
<activity
@@ -51,6 +60,7 @@
android:label="@string/application_name"
android:taskAffinity="com.termux.filereceiver"
android:excludeFromRecents="true"
android:resizeableActivity="true"
android:noHistory="true">
<!-- Accept multiple file types when sending. -->
<intent-filter>
@@ -65,7 +75,7 @@
<data android:mimeType="video/*" />
</intent-filter>
<!-- Be more restrictive for viewing files, restricting ourselves to text files. -->
<intent-filter>
<intent-filter tools:ignore="AppLinkUrlError">
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/*" />

View File

@@ -93,33 +93,60 @@ public final class BackgroundJob {
};
}
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();
if (cwd == null) cwd = TermuxService.HOME_PATH;
final String termEnv = "TERM=xterm-256color";
final String homeEnv = "HOME=" + TermuxService.HOME_PATH;
final String prefixEnv = "PREFIX=" + TermuxService.PREFIX_PATH;
final String androidRootEnv = "ANDROID_ROOT=" + System.getenv("ANDROID_ROOT");
final String androidDataEnv = "ANDROID_DATA=" + System.getenv("ANDROID_DATA");
List<String> environment = new ArrayList<>();
environment.add("TERM=xterm-256color");
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
// 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"));
// ANDROID_RUNTIME_ROOT and ANDROID_TZDATA_ROOT are required for `am` to run on Android Q
addToEnvIfPresent(environment, "ANDROID_RUNTIME_ROOT");
addToEnvIfPresent(environment, "ANDROID_TZDATA_ROOT");
if (failSafe) {
// Keep the default path so that system binaries can be used in the failsafe session.
final String pathEnv = "PATH=" + System.getenv("PATH");
return new String[]{termEnv, homeEnv, prefixEnv, androidRootEnv, androidDataEnv, pathEnv, externalStorageEnv};
environment.add("PATH= " + System.getenv("PATH"));
} else {
final String ps1Env = "PS1=$ ";
final String ldEnv = "LD_LIBRARY_PATH=" + TermuxService.PREFIX_PATH + "/lib";
final String langEnv = "LANG=en_US.UTF-8";
final String pathEnv = "PATH=" + TermuxService.PREFIX_PATH + "/bin:" + TermuxService.PREFIX_PATH + "/bin/applets";
final String pwdEnv = "PWD=" + cwd;
final String tmpdirEnv = "TMPDIR=" + TermuxService.PREFIX_PATH + "/tmp";
return new String[]{termEnv, homeEnv, prefixEnv, ps1Env, ldEnv, langEnv, pathEnv, pwdEnv, androidRootEnv, androidDataEnv, externalStorageEnv, tmpdirEnv};
if (shouldAddLdLibraryPath()) {
environment.add("LD_LIBRARY_PATH=" + TermuxService.PREFIX_PATH + "/lib");
}
environment.add("LANG=en_US.UTF-8");
environment.add("PATH=" + TermuxService.PREFIX_PATH + "/bin:" + TermuxService.PREFIX_PATH + "/bin/applets");
environment.add("PWD=" + cwd);
environment.add("TMPDIR=" + TermuxService.PREFIX_PATH + "/tmp");
}
return environment.toArray(new String[0]);
}
private static boolean shouldAddLdLibraryPath() {
try (BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(TermuxService.PREFIX_PATH + "/etc/apt/sources.list")))) {
String line;
while ((line = in.readLine()) != null) {
if (!line.startsWith("#") && line.contains("https://dl.bintray.com/termux/termux-packages-24")) {
return false;
}
}
} catch (IOException e) {
Log.e(LOG_TAG, "Error trying to read sources.list", e);
}
return true;
}
public static int getPid(Process p) {
@@ -187,7 +214,7 @@ public final class BackgroundJob {
if (interpreter != null) result.add(interpreter);
result.add(fileToExecute);
if (args != null) Collections.addAll(result, args);
return result.toArray(new String[result.size()]);
return result.toArray(new String[0]);
}
}

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

View File

@@ -1,13 +1,24 @@
package com.termux.app;
import android.content.Context;
import android.os.Build;
import android.util.AttributeSet;
import android.view.Gravity;
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.HapticFeedbackConstants;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
import android.widget.GridLayout;
import android.widget.PopupWindow;
import android.widget.ToggleButton;
import com.termux.R;
@@ -21,153 +32,399 @@ import com.termux.view.TerminalView;
public final class ExtraKeysView extends GridLayout {
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 = 0x7FFFFFFF;
public ExtraKeysView(Context context, AttributeSet attrs) {
super(context, attrs);
reload();
}
/**
* HashMap that implements Python dict.get(key, default) function.
* Default java.util .get(key) is then the same as .get(key, null);
*/
static class CleverMap<K,V> extends HashMap<K,V> {
V get(K key, V defaultValue) {
if(containsKey(key))
return get(key);
else
return defaultValue;
}
}
static class CharDisplayMap extends CleverMap<String, String> {}
/**
* Keys are displayed in a natural looking way, like "→" for "RIGHT"
*/
static final Map<String, Integer> keyCodesForString = new HashMap<String, Integer>() {{
put("ESC", KeyEvent.KEYCODE_ESCAPE);
put("TAB", KeyEvent.KEYCODE_TAB);
put("HOME", KeyEvent.KEYCODE_MOVE_HOME);
put("END", KeyEvent.KEYCODE_MOVE_END);
put("PGUP", KeyEvent.KEYCODE_PAGE_UP);
put("PGDN", KeyEvent.KEYCODE_PAGE_DOWN);
put("INS", KeyEvent.KEYCODE_INSERT);
put("DEL", KeyEvent.KEYCODE_FORWARD_DEL);
put("BKSP", KeyEvent.KEYCODE_DEL);
put("UP", KeyEvent.KEYCODE_DPAD_UP);
put("LEFT", KeyEvent.KEYCODE_DPAD_LEFT);
put("RIGHT", KeyEvent.KEYCODE_DPAD_RIGHT);
put("DOWN", KeyEvent.KEYCODE_DPAD_DOWN);
put("ENTER", KeyEvent.KEYCODE_ENTER);
put("F1", KeyEvent.KEYCODE_F1);
put("F2", KeyEvent.KEYCODE_F2);
put("F3", KeyEvent.KEYCODE_F3);
put("F4", KeyEvent.KEYCODE_F4);
put("F5", KeyEvent.KEYCODE_F5);
put("F6", KeyEvent.KEYCODE_F6);
put("F7", KeyEvent.KEYCODE_F7);
put("F8", KeyEvent.KEYCODE_F8);
put("F9", KeyEvent.KEYCODE_F9);
put("F10", KeyEvent.KEYCODE_F10);
put("F11", KeyEvent.KEYCODE_F11);
put("F12", KeyEvent.KEYCODE_F12);
}};
static void sendKey(View view, String keyName) {
int keyCode = 0;
String chars = null;
switch (keyName) {
case "ESC":
keyCode = KeyEvent.KEYCODE_ESCAPE;
break;
case "TAB":
keyCode = KeyEvent.KEYCODE_TAB;
break;
case "":
keyCode = KeyEvent.KEYCODE_DPAD_UP;
break;
case "":
keyCode = KeyEvent.KEYCODE_DPAD_LEFT;
break;
case "":
keyCode = KeyEvent.KEYCODE_DPAD_RIGHT;
break;
case "":
keyCode = KeyEvent.KEYCODE_DPAD_DOWN;
break;
case "":
chars = "-";
break;
default:
chars = keyName;
}
if (keyCode > 0) {
view.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, keyCode));
view.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, keyCode));
TerminalView terminalView = view.findViewById(R.id.terminal_view);
if (keyCodesForString.containsKey(keyName)) {
int keyCode = keyCodesForString.get(keyName);
terminalView.onKeyDown(keyCode, new KeyEvent(KeyEvent.ACTION_UP, keyCode));
// view.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, keyCode));
} else {
TerminalView terminalView = (TerminalView) view.findViewById(R.id.terminal_view);
// not a control char
TerminalSession session = terminalView.getCurrentSession();
if (session != null) session.write(chars);
if (session != null && keyName.length() > 0)
session.write(keyName);
}
}
public enum SpecialButton {
CTRL, ALT, FN
}
private static class SpecialButtonState {
boolean isOn = false;
ToggleButton button = null;
}
private Map<SpecialButton, SpecialButtonState> specialButtons = new HashMap<SpecialButton, SpecialButtonState>() {{
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;
private ToggleButton controlButton;
private ToggleButton altButton;
private ToggleButton fnButton;
public boolean readControlButton() {
if (controlButton.isPressed()) return true;
boolean result = controlButton.isChecked();
if (result) {
controlButton.setChecked(false);
controlButton.setTextColor(TEXT_COLOR);
if (state.button == null) {
return false;
}
return result;
if (state.button.isPressed())
return true;
if (! state.button.isChecked())
return false;
state.button.setChecked(false);
state.button.setTextColor(TEXT_COLOR);
return true;
}
public boolean readAltButton() {
if (altButton.isPressed()) return true;
boolean result = altButton.isChecked();
if (result) {
altButton.setChecked(false);
altButton.setTextColor(TEXT_COLOR);
}
return result;
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);
}
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
}};
public boolean readFnButton() {
if (fnButton.isPressed()) return true;
boolean result = fnButton.isChecked();
if (result) {
fnButton.setChecked(false);
fnButton.setTextColor(TEXT_COLOR);
}
return result;
static final CharDisplayMap wellKnownCharactersDisplay = new CharDisplayMap() {{
// well known characters // https://en.wikipedia.org/wiki/{Enter_key, Tab_key, Delete_key}
put("ENTER", ""); // U+21B2 ↲ DOWNWARDS ARROW WITH TIP LEFTWARDS
put("TAB", ""); // U+21B9 ↹ LEFTWARDS ARROW TO BAR OVER RIGHTWARDS ARROW TO BAR
put("BKSP", ""); // U+232B ⌫ ERASE TO THE LEFT sometimes seen and easy to understand
put("DEL", ""); // U+2326 ⌦ ERASE TO THE RIGHT not well known but easy to understand
}};
static final CharDisplayMap lessKnownCharactersDisplay = new CharDisplayMap() {{
// https://en.wikipedia.org/wiki/{Home_key, End_key, Page_Up_and_Page_Down_keys}
// home key can mean "goto the beginning of line" or "goto first page" depending on context, hence the diagonal
put("HOME", ""); // from IEC 9995 // U+21F1 ⇱ NORTH WEST ARROW TO CORNER
put("END", ""); // from IEC 9995 // ⇲ // U+21F2 ⇲ SOUTH EAST ARROW TO CORNER
put("PGUP", ""); // no ISO character exists, U+21D1 ⇑ UPWARDS DOUBLE ARROW will do the trick
put("PGDN", ""); // no ISO character exists, U+21D3 ⇓ DOWNWARDS DOUBLE ARROW will do the trick
}};
static final CharDisplayMap arrowTriangleVariationDisplay = new CharDisplayMap() {{
// alternative to classic arrow keys
put("LEFT", ""); // U+25C0 ◀ BLACK LEFT-POINTING TRIANGLE
put("RIGHT", ""); // U+25B6 ▶ BLACK RIGHT-POINTING TRIANGLE
put("UP", ""); // U+25B2 ▲ BLACK UP-POINTING TRIANGLE
put("DOWN", ""); // U+25BC ▼ BLACK DOWN-POINTING TRIANGLE
}};
static final CharDisplayMap notKnownIsoCharacters = new CharDisplayMap() {{
// Control chars that are more clear as text // https://en.wikipedia.org/wiki/{Function_key, Alt_key, Control_key, Esc_key}
// put("FN", "FN"); // no ISO character exists
put("CTRL", ""); // ISO character "U+2388 ⎈ HELM SYMBOL" is unknown to people and never printed on computers, however "U+25C7 ◇ WHITE DIAMOND" is a nice presentation, and "^" for terminal app and mac is often used
put("ALT", ""); // ISO character "U+2387 ⎇ ALTERNATIVE KEY SYMBOL'" is unknown to people and only printed as the Option key "⌥" on Mac computer
put("ESC", ""); // ISO character "U+238B ⎋ BROKEN CIRCLE WITH NORTHWEST ARROW" is unknown to people and not often printed on computers
}};
static final CharDisplayMap nicerLookingDisplay = new CharDisplayMap() {{
// nicer looking for most cases
put("-", ""); // U+2015 ― HORIZONTAL BAR
}};
/**
* Keys are displayed in a natural looking way, like "→" for "RIGHT" or "↲" for ENTER
*/
public static final CharDisplayMap defaultCharDisplay = new CharDisplayMap() {{
putAll(classicArrowsDisplay);
putAll(wellKnownCharactersDisplay);
putAll(nicerLookingDisplay);
// all other characters are displayed as themselves
}};
public static final CharDisplayMap lotsOfArrowsCharDisplay = new CharDisplayMap() {{
putAll(classicArrowsDisplay);
putAll(wellKnownCharactersDisplay);
putAll(lessKnownCharactersDisplay); // NEW
putAll(nicerLookingDisplay);
}};
public static final CharDisplayMap arrowsOnlyCharDisplay = new CharDisplayMap() {{
putAll(classicArrowsDisplay);
// putAll(wellKnownCharactersDisplay); // REMOVED
// putAll(lessKnownCharactersDisplay); // REMOVED
putAll(nicerLookingDisplay);
}};
public static final CharDisplayMap fullIsoCharDisplay = new CharDisplayMap() {{
putAll(classicArrowsDisplay);
putAll(wellKnownCharactersDisplay);
putAll(lessKnownCharactersDisplay); // NEW
putAll(nicerLookingDisplay);
putAll(notKnownIsoCharacters); // NEW
}};
/**
* Some people might call our keys differently
*/
static final CharDisplayMap controlCharsAliases = new CharDisplayMap() {{
put("ESCAPE", "ESC");
put("CONTROL", "CTRL");
put("RETURN", "ENTER"); // Technically different keys, but most applications won't see the difference
put("FUNCTION", "FN");
// no alias for ALT
// Directions are sometimes written as first and last letter for brevety
put("LT", "LEFT");
put("RT", "RIGHT");
put("DN", "DOWN");
// put("UP", "UP"); well, "UP" is already two letters
put("PAGEUP", "PGUP");
put("PAGE_UP", "PGUP");
put("PAGE UP", "PGUP");
put("PAGE-UP", "PGUP");
// no alias for HOME
// no alias for END
put("PAGEDOWN", "PGDN");
put("PAGE_DOWN", "PGDN");
put("PAGE-DOWN", "PGDN");
put("DELETE", "DEL");
put("BACKSPACE", "BKSP");
// easier for writing in termux.properties
put("BACKSLASH", "\\");
put("QUOTE", "\"");
put("APOSTROPHE", "'");
}};
/**
* Applies the 'controlCharsAliases' mapping to all the strings in *buttons*
* Modifies the array, doesn't return a new one.
*/
void replaceAliases(String[][] buttons) {
for(int i = 0; i < buttons.length; i++)
for(int j = 0; j < buttons[i].length; j++)
buttons[i][j] = controlCharsAliases.get(buttons[i][j], buttons[i][j]);
}
void reload() {
altButton = controlButton = null;
/**
* General util function to compute the longest column length in a matrix.
*/
static int maximumLength(String[][] matrix) {
int m = 0;
for (String[] aMatrix : matrix) m = Math.max(m, aMatrix.length);
return m;
}
/**
* Reload the view given parameters in termux.properties
*
* @param buttons matrix of String 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 "-_-"
*/
void reload(String[][] buttons, CharDisplayMap charDisplayMap) {
for(SpecialButtonState state : specialButtons.values())
state.button = null;
removeAllViews();
String[][] buttons = {
{"ESC", "CTRL", "ALT", "TAB", "", "/", "|"}
};
replaceAliases(buttons); // modifies the array
final int rows = buttons.length;
final int cols = buttons[0].length;
final int cols = maximumLength(buttons);
setRowCount(rows);
setColumnCount(cols);
for (int row = 0; row < rows; row++) {
for (int col = 0; col < cols; col++) {
for (int col = 0; col < buttons[row].length; col++) {
final String buttonText = buttons[row][col];
Button button;
switch (buttonText) {
case "CTRL":
button = controlButton = new ToggleButton(getContext(), null, android.R.attr.buttonBarButtonStyle);
button.setClickable(true);
break;
case "ALT":
button = altButton = new ToggleButton(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;
if(Arrays.asList("CTRL", "ALT", "FN").contains(buttonText)) {
SpecialButtonState state = specialButtons.get(SpecialButton.valueOf(buttonText)); // for valueOf: https://stackoverflow.com/a/604426/1980630
state.isOn = true;
button = state.button = new ToggleButton(getContext(), null, android.R.attr.buttonBarButtonStyle);
button.setClickable(true);
} else {
button = new Button(getContext(), null, android.R.attr.buttonBarButtonStyle);
}
button.setText(buttonText);
final String displayedText = charDisplayMap.get(buttonText, buttonText);
button.setText(displayedText);
button.setTextColor(TEXT_COLOR);
button.setPadding(0, 0, 0, 0);
final Button finalButton = button;
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
finalButton.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP);
View root = getRootView();
switch (buttonText) {
case "CTRL":
case "ALT":
case "FN":
ToggleButton self = (ToggleButton) finalButton;
self.setChecked(self.isChecked());
self.setTextColor(self.isChecked() ? 0xFF80DEEA : TEXT_COLOR);
break;
default:
sendKey(root, buttonText);
break;
}
button.setOnClickListener(v -> {
finalButton.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP);
View root = getRootView();
if(Arrays.asList("CTRL", "ALT", "FN").contains(buttonText)) {
ToggleButton self = (ToggleButton) finalButton;
self.setChecked(self.isChecked());
self.setTextColor(self.isChecked() ? INTERESTING_COLOR : TEXT_COLOR);
} else {
sendKey(root, buttonText);
}
});
GridLayout.LayoutParams param = new GridLayout.LayoutParams();
param.height = param.width = 0;
param.rightMargin = param.topMargin = 0;
param.setGravity(Gravity.LEFT);
float weight = "▲▼◀▶".contains(buttonText) ? 0.7f : 1.f;
param.columnSpec = GridLayout.spec(col, GridLayout.FILL, weight);
button.setOnTouchListener((v, event) -> {
final View root = getRootView();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
longPressCount = 0;
v.setBackgroundColor(BUTTON_PRESSED_COLOR);
if (Arrays.asList("UP", "DOWN", "LEFT", "RIGHT").contains(buttonText)) {
scheduledExecutor = Executors.newSingleThreadScheduledExecutor();
scheduledExecutor.scheduleWithFixedDelay(() -> {
longPressCount++;
sendKey(root, buttonText);
}, 400, 80, TimeUnit.MILLISECONDS);
}
return true;
case MotionEvent.ACTION_MOVE:
// These two keys have a Move-Up button appearing
if (Arrays.asList("/", "-").contains(buttonText)) {
if (popupWindow == null && event.getY() < 0) {
v.setBackgroundColor(BUTTON_COLOR);
String text = "-".equals(buttonText) ? "|" : "\\";
popup(v, text);
}
if (popupWindow != null && event.getY() > 0) {
v.setBackgroundColor(BUTTON_PRESSED_COLOR);
popupWindow.dismiss();
popupWindow = null;
}
}
return true;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
v.setBackgroundColor(BUTTON_COLOR);
if (scheduledExecutor != null) {
scheduledExecutor.shutdownNow();
scheduledExecutor = null;
}
if (longPressCount == 0) {
if (popupWindow != null && Arrays.asList("/", "-").contains(buttonText)) {
popupWindow.setContentView(null);
popupWindow.dismiss();
popupWindow = null;
sendKey(root, "-".equals(buttonText) ? "|" : "\\");
} else {
v.performClick();
}
}
return true;
default:
return true;
}
});
LayoutParams param = new GridLayout.LayoutParams();
param.width = 0;
if(Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP) { // special handle api 21
param.height = (int)(37.5 * getResources().getDisplayMetrics().density + 0.5); // 37.5 equal to R.id.viewpager layout_height / rows in DP
} else {
param.height = 0;
}
param.setMargins(0, 0, 0, 0);
param.columnSpec = GridLayout.spec(col, GridLayout.FILL, 1.f);
param.rowSpec = GridLayout.spec(row, GridLayout.FILL, 1.f);
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

@@ -11,8 +11,6 @@ import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnShowListener;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
@@ -26,12 +24,6 @@ import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
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.Spanned;
import android.text.TextUtils;
@@ -40,19 +32,13 @@ import android.util.Log;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
import android.view.ViewGroup;
import android.view.WindowManager;
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.EditText;
import android.widget.ListView;
@@ -73,10 +59,17 @@ import java.io.InputStream;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Properties;
import java.util.regex.Matcher;
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.
* <p/>
@@ -89,14 +82,16 @@ import java.util.regex.Pattern;
*/
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_SHARE_TRANSCRIPT_ID = 1;
private static final int CONTEXTMENU_PASTE_ID = 3;
private static final int CONTEXTMENU_KILL_PROCESS_ID = 4;
private static final int CONTEXTMENU_RESET_TERMINAL_ID = 5;
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_TOGGLE_KEEP_SCREEN_ON = 9;
private static final int MAX_SESSIONS = 8;
@@ -111,8 +106,6 @@ public final class TermuxActivity extends Activity implements ServiceConnection
ExtraKeysView mExtraKeysView;
final FullScreenHelper mFullScreenHelper = new FullScreenHelper(this);
TermuxPreferences mSettings;
/**
@@ -151,6 +144,10 @@ public final class TermuxActivity extends Activity implements ServiceConnection
}
checkForFontAndColors();
mSettings.reloadFromProperties(TermuxActivity.this);
if (mExtraKeysView != null) {
mExtraKeysView.reload(mSettings.mExtraKeys, ExtraKeysView.defaultCharDisplay);
}
}
}
};
@@ -211,15 +208,20 @@ public final class TermuxActivity extends Activity implements ServiceConnection
mSettings = new TermuxPreferences(this);
setContentView(R.layout.drawer_layout);
mTerminalView = (TerminalView) findViewById(R.id.terminal_view);
mTerminalView = findViewById(R.id.terminal_view);
mTerminalView.setOnKeyListener(new TermuxViewClient(this));
mTerminalView.setTextSize(mSettings.getFontSize());
mFullScreenHelper.setImmersive(mSettings.isFullScreen());
mTerminalView.setKeepScreenOn(mSettings.isScreenAlwaysOn());
mTerminalView.requestFocus();
final ViewPager viewPager = (ViewPager) findViewById(R.id.viewpager);
if (mSettings.isShowExtraKeys()) viewPager.setVisibility(View.VISIBLE);
final ViewPager viewPager = findViewById(R.id.viewpager);
if (mSettings.mShowExtraKeys) viewPager.setVisibility(View.VISIBLE);
ViewGroup.LayoutParams layoutParams = viewPager.getLayoutParams();
layoutParams.height = layoutParams.height * mSettings.mExtraKeys.length;
viewPager.setLayoutParams(layoutParams);
viewPager.setAdapter(new PagerAdapter() {
@Override
@@ -228,35 +230,34 @@ public final class TermuxActivity extends Activity implements ServiceConnection
}
@Override
public boolean isViewFromObject(View view, Object object) {
public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
return view == object;
}
@NonNull
@Override
public Object instantiateItem(ViewGroup collection, int position) {
public Object instantiateItem(@NonNull ViewGroup collection, int position) {
LayoutInflater inflater = LayoutInflater.from(TermuxActivity.this);
View layout;
if (position == 0) {
layout = mExtraKeysView = (ExtraKeysView) inflater.inflate(R.layout.extra_keys_main, collection, false);
mExtraKeysView.reload(mSettings.mExtraKeys, ExtraKeysView.defaultCharDisplay);
} else {
layout = inflater.inflate(R.layout.extra_keys_right, collection, false);
final EditText editText = (EditText) layout.findViewById(R.id.text_input);
editText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
TerminalSession session = getCurrentTermSession();
if (session != null) {
if (session.isRunning()) {
String textToSend = editText.getText().toString();
if (textToSend.length() == 0) textToSend = "\n";
session.write(textToSend);
} else {
removeFinishedSession(session);
}
editText.setText("");
final EditText editText = layout.findViewById(R.id.text_input);
editText.setOnEditorActionListener((v, actionId, event) -> {
TerminalSession session = getCurrentTermSession();
if (session != null) {
if (session.isRunning()) {
String textToSend = editText.getText().toString();
if (textToSend.length() == 0) textToSend = "\r";
session.write(textToSend);
} else {
removeFinishedSession(session);
}
return true;
editText.setText("");
}
return true;
});
}
collection.addView(layout);
@@ -264,7 +265,7 @@ public final class TermuxActivity extends Activity implements ServiceConnection
}
@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);
}
});
@@ -275,54 +276,30 @@ public final class TermuxActivity extends Activity implements ServiceConnection
if (position == 0) {
mTerminalView.requestFocus();
} 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();
}
}
});
View newSessionButton = findViewById(R.id.new_session_button);
newSessionButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
addNewSession(false, null);
}
});
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;
}
newSessionButton.setOnClickListener(v -> addNewSession(false, null));
newSessionButton.setOnLongClickListener(v -> {
DialogUtils.textInput(TermuxActivity.this, R.string.session_new_named_title, null, R.string.session_new_named_positive_button,
text -> addNewSession(false, text), R.string.new_session_failsafe, text -> addNewSession(true, text)
, -1, null, null);
return true;
});
findViewById(R.id.toggle_keyboard_button).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.toggleSoftInput(InputMethodManager.SHOW_IMPLICIT, 0);
getDrawer().closeDrawers();
}
findViewById(R.id.toggle_keyboard_button).setOnClickListener(v -> {
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.toggleSoftInput(InputMethodManager.SHOW_IMPLICIT, 0);
getDrawer().closeDrawers();
});
findViewById(R.id.toggle_keyboard_button).setOnLongClickListener(new OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
toggleShowExtraKeys();
return true;
}
findViewById(R.id.toggle_keyboard_button).setOnLongClickListener(v -> {
toggleShowExtraKeys();
return true;
});
registerForContextMenu(mTerminalView);
@@ -339,7 +316,7 @@ public final class TermuxActivity extends Activity implements ServiceConnection
}
void toggleShowExtraKeys() {
final ViewPager viewPager = (ViewPager) findViewById(R.id.viewpager);
final ViewPager viewPager = findViewById(R.id.viewpager);
final boolean showNow = mSettings.toggleShowExtraKeys(TermuxActivity.this);
viewPager.setVisibility(showNow ? View.VISIBLE : View.GONE);
if (showNow && viewPager.getCurrentItem() == 1) {
@@ -390,6 +367,21 @@ public final class TermuxActivity extends Activity implements ServiceConnection
if (indexOfSession >= 0)
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();
}
@@ -409,7 +401,7 @@ public final class TermuxActivity extends Activity implements ServiceConnection
mBellSoundPool.play(mBellSoundId, 1.f, 1.f, 1, 0, 1.f);
break;
case TermuxPreferences.BELL_VIBRATE:
((Vibrator) getSystemService(VIBRATOR_SERVICE)).vibrate(50);
BellUtil.getInstance(TermuxActivity.this).doBell();
break;
case TermuxPreferences.BELL_IGNORE:
// Ignore the bell character.
@@ -424,7 +416,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()) {
final StyleSpan boldSpan = new StyleSpan(Typeface.BOLD);
final StyleSpan italicSpan = new StyleSpan(Typeface.ITALIC);
@@ -441,7 +433,7 @@ public final class TermuxActivity extends Activity implements ServiceConnection
TerminalSession sessionAtRow = getItem(position);
boolean sessionRunning = sessionAtRow.isRunning();
TextView firstLineView = (TextView) row.findViewById(R.id.row_line);
TextView firstLineView = row.findViewById(R.id.row_line);
String name = sessionAtRow.mSessionName;
String sessionTitle = sessionAtRow.getTitle();
@@ -468,34 +460,30 @@ public final class TermuxActivity extends Activity implements ServiceConnection
}
};
listView.setAdapter(mListViewAdapter);
listView.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
TerminalSession clickedSession = mListViewAdapter.getItem(position);
switchToSession(clickedSession);
getDrawer().closeDrawers();
}
listView.setOnItemClickListener((parent, view, position, id) -> {
TerminalSession clickedSession = mListViewAdapter.getItem(position);
switchToSession(clickedSession);
getDrawer().closeDrawers();
});
listView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
@Override
public boolean onItemLongClick(AdapterView<?> parent, View view, final int position, long id) {
final TerminalSession selectedSession = mListViewAdapter.getItem(position);
renameSession(selectedSession);
return true;
}
listView.setOnItemLongClickListener((parent, view, position, id) -> {
final TerminalSession selectedSession = mListViewAdapter.getItem(position);
renameSession(selectedSession);
return true;
});
if (mTermService.getSessions().isEmpty()) {
if (mIsVisible) {
TermuxInstaller.setupIfNeeded(TermuxActivity.this, new Runnable() {
@Override
public void run() {
if (mTermService == null) return; // Activity might have been destroyed.
try {
addNewSession(false, null);
} catch (WindowManager.BadTokenException e) {
// Activity finished - ignore.
TermuxInstaller.setupIfNeeded(TermuxActivity.this, () -> {
if (mTermService == null) return; // Activity might have been destroyed.
try {
Bundle bundle = getIntent().getExtras();
boolean launchFailsafe = false;
if (bundle != null) {
launchFailsafe = bundle.getBoolean(TERMUX_FAILSAFE_SESSION_ACTION, false);
}
addNewSession(launchFailsafe, null);
} catch (WindowManager.BadTokenException e) {
// Activity finished - ignore.
}
});
} else {
@@ -506,7 +494,8 @@ public final class TermuxActivity extends Activity implements ServiceConnection
Intent i = getIntent();
if (i != null && Intent.ACTION_RUN.equals(i.getAction())) {
// 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 {
switchToSession(getStoredCurrentSessionOrLast());
}
@@ -526,21 +515,16 @@ public final class TermuxActivity extends Activity implements ServiceConnection
@SuppressLint("InflateParams")
void renameSession(final TerminalSession sessionToRename) {
DialogUtils.textInput(this, R.string.session_rename_title, sessionToRename.mSessionName, R.string.session_rename_positive_button, new DialogUtils.TextSetListener() {
@Override
public void onTextSet(String text) {
sessionToRename.mSessionName = text;
mListViewAdapter.notifyDataSetChanged();
}
DialogUtils.textInput(this, R.string.session_rename_title, sessionToRename.mSessionName, R.string.session_rename_positive_button, text -> {
sessionToRename.mSessionName = text;
mListViewAdapter.notifyDataSetChanged();
}, -1, null, -1, null, null);
}
@Override
public void onServiceDisconnected(ComponentName name) {
if (mTermService != null) {
// Respect being stopped from the TermuxService notification action.
finish();
}
// Respect being stopped from the TermuxService notification action.
finish();
}
@Nullable
@@ -605,8 +589,7 @@ 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)
.setPositiveButton(android.R.string.ok, null).show();
} else {
String executablePath = (failSafe ? "/system/bin/sh" : null);
TerminalSession newSession = mTermService.createTermSession(executablePath, null, null, failSafe);
TerminalSession newSession = mTermService.createTermSession(null, null, null, failSafe);
if (sessionName != null) {
newSession.mSessionName = sessionName;
}
@@ -644,7 +627,7 @@ public final class TermuxActivity extends Activity implements ServiceConnection
final int indexOfSession = mTermService.getSessions().indexOf(session);
showToast(toToastTitle(session), false);
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.smoothScrollToPosition(indexOfSession);
}
@@ -658,8 +641,8 @@ public final class TermuxActivity extends Activity implements ServiceConnection
menu.add(Menu.NONE, CONTEXTMENU_SHARE_TRANSCRIPT_ID, Menu.NONE, R.string.select_all_and_share);
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_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_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);
}
@@ -674,7 +657,7 @@ public final class TermuxActivity extends Activity implements ServiceConnection
// Pattern for recognizing a URL, based off RFC 3986
// http://stackoverflow.com/questions/5713558/detect-and-extract-url-from-a-string
final Pattern urlPattern = Pattern.compile(
"(?:^|[\\W])((ht|f)tp(s?)://|www\\.)" + "(([\\w\\-]+\\.)+?([\\w\\-.~]+/?)*" + "[\\p{Alnum}.,%_=?&#\\-+()\\[\\]\\*$~@!:/{};']*)",
"(?:^|[\\W])((ht|f)tp(s?)://|www\\.)" + "(([\\w\\-]+\\.)+?([\\w\\-.~]+/?)*" + "[\\p{Alnum}.,%_=?&#\\-+()\\[\\]*$~@!:/{};']*)",
Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
LinkedHashSet<CharSequence> urlSet = new LinkedHashSet<>();
Matcher matcher = urlPattern.matcher(text);
@@ -695,41 +678,32 @@ public final class TermuxActivity extends Activity implements ServiceConnection
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.
// Click to copy url to clipboard:
final AlertDialog dialog = new AlertDialog.Builder(TermuxActivity.this).setItems(urls, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface di, int which) {
String url = (String) urls[which];
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
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();
}
final AlertDialog dialog = new AlertDialog.Builder(TermuxActivity.this).setItems(urls, (di, which) -> {
String url = (String) urls[which];
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
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();
// Long press to open URL:
dialog.setOnShowListener(new OnShowListener() {
@Override
public void onShow(DialogInterface di) {
ListView lv = dialog.getListView(); // this is a ListView with your "buds" in it
lv.setOnItemLongClickListener(new OnItemLongClickListener() {
@Override
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
dialog.dismiss();
String url = (String) urls[position];
Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
try {
startActivity(i, null);
} catch (ActivityNotFoundException e) {
// If no applications match, Android displays a system message.
startActivity(Intent.createChooser(i, null));
}
return true;
}
});
}
dialog.setOnShowListener(di -> {
ListView lv = dialog.getListView(); // this is a ListView with your "buds" in it
lv.setOnItemLongClickListener((parent, view, position, id) -> {
dialog.dismiss();
String url = (String) urls[position];
Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
try {
startActivity(i, null);
} catch (ActivityNotFoundException e) {
// If no applications match, Android displays a system message.
startActivity(Intent.createChooser(i, null));
}
return true;
});
});
dialog.show();
@@ -747,7 +721,18 @@ public final class TermuxActivity extends Activity implements ServiceConnection
if (session != null) {
Intent intent = new Intent(Intent.ACTION_SEND);
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));
startActivity(Intent.createChooser(intent, getString(R.string.share_transcript_chooser_title)));
}
@@ -759,12 +744,9 @@ public final class TermuxActivity extends Activity implements ServiceConnection
final AlertDialog.Builder b = new AlertDialog.Builder(this);
b.setIcon(android.R.drawable.ic_dialog_alert);
b.setMessage(R.string.confirm_kill_process);
b.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
dialog.dismiss();
getCurrentTermSession().finishIfRunning();
}
b.setPositiveButton(android.R.string.yes, (dialog, id) -> {
dialog.dismiss();
getCurrentTermSession().finishIfRunning();
});
b.setNegativeButton(android.R.string.no, null);
b.show();
@@ -785,21 +767,23 @@ public final class TermuxActivity extends Activity implements ServiceConnection
// The startActivity() call is not documented to throw IllegalArgumentException.
// However, crash reporting shows that it sometimes does, so catch it here.
new AlertDialog.Builder(this).setMessage(R.string.styling_not_installed)
.setPositiveButton(R.string.styling_install, new DialogInterface.OnClickListener() {
@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();
.setPositiveButton(R.string.styling_install, (dialog, 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;
}
case CONTEXTMENU_HELP_ID:
startActivity(new Intent(this, TermuxHelpActivity.class));
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;
}
default:
return super.onContextItemSelected(item);
}
@@ -812,12 +796,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) {
mSettings.changeFontSize(this, increase);
mTerminalView.setTextSize(mSettings.getFontSize());
@@ -836,9 +814,8 @@ public final class TermuxActivity extends Activity implements ServiceConnection
public TerminalSession getStoredCurrentSessionOrLast() {
TerminalSession stored = TermuxPreferences.getCurrentSession(this);
if (stored != null) return stored;
int numberOfSessions = mTermService.getSessions().size();
if (numberOfSessions == 0) return null;
return mTermService.getSessions().get(numberOfSessions - 1);
List<TerminalSession> sessions = mTermService.getSessions();
return sessions.isEmpty() ? null : sessions.get(sessions.size() - 1);
}
/** Show a toast and dismiss the last one if still visible. */

View File

@@ -4,9 +4,6 @@ import android.app.Activity;
import android.app.AlertDialog;
import android.app.ProgressDialog;
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.UserManager;
@@ -21,6 +18,7 @@ import com.termux.terminal.EmulatorDebug;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
@@ -59,12 +57,7 @@ final class TermuxInstaller {
boolean isPrimaryUser = um.getSerialNumberForUser(android.os.Process.myUserHandle()) == 0;
if (!isPrimaryUser) {
new AlertDialog.Builder(activity).setTitle(R.string.bootstrap_error_title).setMessage(R.string.bootstrap_error_not_primary_user_message)
.setOnDismissListener(new OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
System.exit(0);
}
}).setPositiveButton(android.R.string.ok, null).show();
.setOnDismissListener(dialog -> System.exit(0)).setPositiveButton(android.R.string.ok, null).show();
return;
}
@@ -103,14 +96,17 @@ final class TermuxInstaller {
String oldPath = parts[0];
String newPath = STAGING_PREFIX_PATH + "/" + parts[1];
symlinks.add(Pair.create(oldPath, newPath));
ensureDirectoryExists(new File(newPath).getParentFile());
}
} else {
String zipEntryName = zipEntry.getName();
File targetFile = new File(STAGING_PREFIX_PATH, zipEntryName);
if (zipEntry.isDirectory()) {
if (!targetFile.mkdirs())
throw new RuntimeException("Failed to create directory: " + targetFile.getAbsolutePath());
} else {
boolean isDirectory = zipEntry.isDirectory();
ensureDirectoryExists(isDirectory ? targetFile : targetFile.getParentFile());
if (!isDirectory) {
try (FileOutputStream outStream = new FileOutputStream(targetFile)) {
int readBytes;
while ((readBytes = zipInput.read(buffer)) != -1)
@@ -135,46 +131,29 @@ final class TermuxInstaller {
throw new RuntimeException("Unable to rename staging folder");
}
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
whenDone.run();
}
});
activity.runOnUiThread(whenDone);
} catch (final Exception e) {
Log.e(EmulatorDebug.LOG_TAG, "Bootstrap error", e);
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
try {
new AlertDialog.Builder(activity).setTitle(R.string.bootstrap_error_title).setMessage(R.string.bootstrap_error_body)
.setNegativeButton(R.string.bootstrap_error_abort, new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
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);
}
activity.runOnUiThread(() -> {
try {
new AlertDialog.Builder(activity).setTitle(R.string.bootstrap_error_title).setMessage(R.string.bootstrap_error_body)
.setNegativeButton(R.string.bootstrap_error_abort, (dialog, which) -> {
dialog.dismiss();
activity.finish();
}).setPositiveButton(R.string.bootstrap_error_try_again, (dialog, which) -> {
dialog.dismiss();
TermuxInstaller.setupIfNeeded(activity, whenDone);
}).show();
} catch (WindowManager.BadTokenException e) {
// Activity already dismissed - ignore.
}
} catch (WindowManager.BadTokenException e1) {
// Activity already dismissed - ignore.
}
});
} finally {
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
try {
progress.dismiss();
} catch (RuntimeException e) {
// Activity already dismissed - ignore.
}
activity.runOnUiThread(() -> {
try {
progress.dismiss();
} catch (RuntimeException e) {
// Activity already dismissed - ignore.
}
});
}
@@ -182,10 +161,19 @@ final class TermuxInstaller {
}.start();
}
private static void ensureDirectoryExists(File directory) {
if (!directory.isDirectory() && !directory.mkdirs()) {
throw new RuntimeException("Unable to create directory: " + directory.getAbsolutePath());
}
}
/** Get bootstrap zip url for this systems cpu architecture. */
static URL determineZipUrl() throws MalformedURLException {
private static URL determineZipUrl() throws MalformedURLException {
String archName = determineTermuxArchName();
return new URL("https://termux.net/bootstrap/bootstrap-" + archName + ".zip");
String url = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
? "https://termux.org/bootstrap-" + archName + ".zip"
: "https://termux.net/bootstrap/bootstrap-" + archName + ".zip";
return new URL(url);
}
private static String determineTermuxArchName() {
@@ -208,29 +196,37 @@ final class TermuxInstaller {
Arrays.toString(Build.SUPPORTED_ABIS));
}
/** Delete a folder and all its content or throw. */
static void deleteFolder(File fileOrDirectory) {
File[] children = fileOrDirectory.listFiles();
if (children != null) {
for (File child : children) {
deleteFolder(child);
/** Delete a folder and all its content or throw. Don't follow symlinks. */
static void deleteFolder(File fileOrDirectory) throws IOException {
if (fileOrDirectory.getCanonicalPath().equals(fileOrDirectory.getAbsolutePath()) && fileOrDirectory.isDirectory()) {
File[] children = fileOrDirectory.listFiles();
if (children != null) {
for (File child : children) {
deleteFolder(child);
}
}
}
if (!fileOrDirectory.delete()) {
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";
new Thread() {
public void run() {
try {
File storageDir = new File(TermuxService.HOME_PATH, "storage");
if (storageDir.exists() && !storageDir.delete()) {
Log.e(LOG_TAG, "Could not delete old $HOME/storage");
return;
if (storageDir.exists()) {
try {
deleteFolder(storageDir);
} catch (IOException e) {
Log.e(LOG_TAG, "Could not delete old $HOME/storage, " + e.getMessage());
return;
}
}
if (!storageDir.mkdirs()) {

View File

@@ -11,7 +11,6 @@ import android.net.Uri;
import android.os.Environment;
import android.os.ParcelFileDescriptor;
import android.provider.MediaStore;
import android.support.annotation.NonNull;
import android.util.Log;
import android.webkit.MimeTypeMap;
@@ -21,6 +20,8 @@ import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import androidx.annotation.NonNull;
public class TermuxOpenReceiver extends BroadcastReceiver {
@Override
@@ -77,7 +78,7 @@ public class TermuxOpenReceiver extends BroadcastReceiver {
if (contentTypeExtra == null) {
String fileName = fileToShare.getName();
int lastDotIndex = fileName.lastIndexOf('.');
String fileExtension = fileName.substring(lastDotIndex + 1, fileName.length());
String fileExtension = fileName.substring(lastDotIndex + 1);
MimeTypeMap mimeTypes = MimeTypeMap.getSingleton();
// Lower casing makes it work with e.g. "JPG":
contentTypeToUse = mimeTypes.getMimeTypeFromExtension(fileExtension.toLowerCase());
@@ -86,7 +87,7 @@ public class TermuxOpenReceiver extends BroadcastReceiver {
contentTypeToUse = contentTypeExtra;
}
Uri uriToShare = Uri.withAppendedPath(Uri.parse("content://com.termux.files/"), filePath);
Uri uriToShare = Uri.parse("content://com.termux.files" + fileToShare.getAbsolutePath());
if (Intent.ACTION_SEND.equals(intentAction)) {
sendIntent.putExtra(Intent.EXTRA_STREAM, uriToShare);

View File

@@ -3,28 +3,49 @@ package com.termux.app;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.support.annotation.IntDef;
import android.util.Log;
import android.util.TypedValue;
import android.widget.Toast;
import com.termux.terminal.TerminalSession;
import org.json.JSONArray;
import org.json.JSONException;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import androidx.annotation.IntDef;
final class TermuxPreferences {
@IntDef({BELL_VIBRATE, BELL_BEEP, BELL_IGNORE})
@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_BEEP = 2;
static final int BELL_IGNORE = 3;
@@ -32,12 +53,12 @@ final class TermuxPreferences {
private final int MIN_FONTSIZE;
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 FONTSIZE_KEY = "fontsize";
private static final String CURRENT_SESSION_KEY = "current_session";
private static final String SCREEN_ALWAYS_ON_KEY = "screen_always_on";
private boolean mFullScreen;
private boolean mScreenAlwaysOn;
private int mFontSize;
@AsciiBellBehaviour
@@ -46,6 +67,17 @@ final class TermuxPreferences {
boolean mBackIsEscape;
boolean mShowExtraKeys;
String[][] 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) {
reloadFromProperties(context);
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
@@ -56,8 +88,8 @@ final class TermuxPreferences {
// to prevent invisible text due to zoom be mistake:
MIN_FONTSIZE = (int) (4f * dipInPixels);
mFullScreen = prefs.getBoolean(FULLSCREEN_KEY, false);
mShowExtraKeys = prefs.getBoolean(SHOW_EXTRA_KEYS_KEY, false);
mShowExtraKeys = prefs.getBoolean(SHOW_EXTRA_KEYS_KEY, true);
mScreenAlwaysOn = prefs.getBoolean(SCREEN_ALWAYS_ON_KEY, false);
// http://www.google.com/design/spec/style/typography.html#typography-line-height
int defaultFontSize = Math.round(12 * dipInPixels);
@@ -69,20 +101,7 @@ final class TermuxPreferences {
} catch (NumberFormatException | ClassCastException e) {
mFontSize = defaultFontSize;
}
mFontSize = Math.max(MIN_FONTSIZE, Math.min(mFontSize, 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;
mFontSize = clamp(mFontSize, MIN_FONTSIZE, MAX_FONTSIZE);
}
boolean toggleShowExtraKeys(Context context) {
@@ -103,6 +122,15 @@ final class TermuxPreferences {
prefs.edit().putString(FONTSIZE_KEY, Integer.toString(mFontSize)).apply();
}
boolean isScreenAlwaysOn() {
return mScreenAlwaysOn;
}
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) {
PreferenceManager.getDefaultSharedPreferences(context).edit().putString(TermuxPreferences.CURRENT_SESSION_KEY, session.mHandle).apply();
}
@@ -115,62 +143,61 @@ final class TermuxPreferences {
}
return null;
}
void reloadFromProperties(Context context) {
File propsFile = new File(TermuxService.HOME_PATH + "/.termux/termux.properties");
if (!propsFile.exists())
propsFile = new File(TermuxService.HOME_PATH + "/.config/termux/termux.properties");
public void reloadFromProperties(Context context) {
Properties props = new Properties();
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()) {
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) {
Toast.makeText(context, "Error loading properties: " + e.getMessage(), Toast.LENGTH_LONG).show();
} catch (IOException e) {
Toast.makeText(context, "Could not open properties file termux.properties.", Toast.LENGTH_LONG).show();
Log.e("termux", "Error loading props", e);
}
}
public static final int SHORTCUT_ACTION_CREATE_SESSION = 1;
public static final int SHORTCUT_ACTION_NEXT_SESSION = 2;
public static final int SHORTCUT_ACTION_PREVIOUS_SESSION = 3;
public static final int SHORTCUT_ACTION_RENAME_SESSION = 4;
public final static class KeyboardShortcut {
public KeyboardShortcut(int codePoint, int shortcutAction) {
this.codePoint = codePoint;
this.shortcutAction = shortcutAction;
switch (props.getProperty("bell-character", "vibrate")) {
case "beep":
mBellBehaviour = BELL_BEEP;
break;
case "ignore":
mBellBehaviour = BELL_IGNORE;
break;
default: // "vibrate".
mBellBehaviour = BELL_VIBRATE;
break;
}
final int codePoint;
final int shortcutAction;
}
try {
JSONArray arr = new JSONArray(props.getProperty("extra-keys", "[['ESC', 'TAB', 'CTRL', 'ALT', '-', 'DOWN', 'UP']]"));
final List<KeyboardShortcut> shortcuts = new ArrayList<>();
mExtraKeys = new String[arr.length()][];
for (int i = 0; i < arr.length(); i++) {
JSONArray line = arr.getJSONArray(i);
mExtraKeys[i] = new String[line.length()];
for (int j = 0; j < line.length(); j++) {
mExtraKeys[i][j] = line.getString(j);
}
}
} catch (JSONException e) {
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);
mExtraKeys = new String[0][];
}
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);
}
private void parseAction(String name, int shortcutAction, Properties props) {
String value = props.getProperty(name);

View File

@@ -16,6 +16,7 @@ import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.PowerManager;
import android.provider.Settings;
import android.util.Log;
import android.widget.ArrayAdapter;
@@ -112,6 +113,17 @@ public final class TermuxService extends Service implements SessionChangedCallba
mWifiLock = wm.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, EmulatorDebug.LOG_TAG);
mWifiLock.acquire();
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
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);
startActivity(whitelist);
}
}
updateNotification();
}
} else if (ACTION_UNLOCK_WAKE.equals(action)) {
@@ -136,7 +148,8 @@ public final class TermuxService extends Service implements SessionChangedCallba
mBackgroundTasks.add(task);
updateNotification();
} 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".
if (executablePath != null) {
@@ -214,7 +227,7 @@ public final class TermuxService extends Service implements SessionChangedCallba
builder.setShowWhen(false);
// 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);
@@ -237,6 +250,18 @@ public final class TermuxService extends Service implements SessionChangedCallba
@Override
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 (mWifiLock != null) mWifiLock.release();
@@ -244,7 +269,6 @@ public final class TermuxService extends Service implements SessionChangedCallba
for (int i = 0; i < mTerminalSessions.size(); i++)
mTerminalSessions.get(i).finishIfRunning();
mTerminalSessions.clear();
}
public List<TerminalSession> getSessions() {
@@ -260,11 +284,13 @@ public final class TermuxService extends Service implements SessionChangedCallba
boolean isLoginShell = false;
if (executablePath == null) {
for (String shellBinary : new String[]{"login", "bash", "zsh"}) {
File shellFile = new File(PREFIX_PATH + "/bin/" + shellBinary);
if (shellFile.canExecute()) {
executablePath = shellFile.getAbsolutePath();
break;
if (!failSafe) {
for (String shellBinary : new String[]{"login", "bash", "zsh"}) {
File shellFile = new File(PREFIX_PATH + "/bin/" + shellBinary);
if (shellFile.canExecute()) {
executablePath = shellFile.getAbsolutePath();
break;
}
}
}
@@ -288,6 +314,12 @@ public final class TermuxService extends Service implements SessionChangedCallba
TerminalSession session = new TerminalSession(executablePath, cwd, args, env, this);
mTerminalSessions.add(session);
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;
}
@@ -336,12 +368,9 @@ public final class TermuxService extends Service implements SessionChangedCallba
}
public void onBackgroundJobExited(final BackgroundJob task) {
mHandler.post(new Runnable() {
@Override
public void run() {
mBackgroundTasks.remove(task);
updateNotification();
}
mHandler.post(() -> {
mBackgroundTasks.remove(task);
updateNotification();
});
}

View File

@@ -2,7 +2,6 @@ package com.termux.app;
import android.content.Context;
import android.media.AudioManager;
import android.support.v4.widget.DrawerLayout;
import android.view.Gravity;
import android.view.InputDevice;
import android.view.KeyEvent;
@@ -16,6 +15,8 @@ import com.termux.view.TerminalViewClient;
import java.util.List;
import androidx.drawerlayout.widget.DrawerLayout;
public final class TermuxViewClient implements TerminalViewClient {
final TermuxActivity mActivity;
@@ -73,8 +74,6 @@ public final class TermuxViewClient implements TerminalViewClient {
mActivity.getDrawer().openDrawer(Gravity.LEFT);
} else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
mActivity.getDrawer().closeDrawers();
} else if (unicodeChar == 'f'/* full screen */) {
mActivity.toggleImmersive();
} else if (unicodeChar == 'k'/* keyboard */) {
InputMethodManager imm = (InputMethodManager) mActivity.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
@@ -114,12 +113,12 @@ public final class TermuxViewClient implements TerminalViewClient {
@Override
public boolean readControlKey() {
return (mActivity.mExtraKeysView != null && mActivity.mExtraKeysView.readControlButton()) || mVirtualControlKeyDown;
return (mActivity.mExtraKeysView != null && mActivity.mExtraKeysView.readSpecialButton(ExtraKeysView.SpecialButton.CTRL)) || mVirtualControlKeyDown;
}
@Override
public boolean readAltKey() {
return (mActivity.mExtraKeysView != null && mActivity.mExtraKeysView.readAltButton());
return (mActivity.mExtraKeysView != null && mActivity.mExtraKeysView.readSpecialButton(ExtraKeysView.SpecialButton.ALT));
}
@Override
@@ -211,6 +210,7 @@ public final class TermuxViewClient implements TerminalViewClient {
// Writing mode:
case 'q':
case 'k':
mActivity.toggleShowExtraKeys();
break;
}

View File

@@ -75,7 +75,7 @@ public class TermuxDocumentsProvider extends DocumentsProvider {
row.add(Root.COLUMN_TITLE, applicationName);
row.add(Root.COLUMN_MIME_TYPES, ALL_MIME_TYPES);
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;
}
@@ -195,7 +195,7 @@ public class TermuxDocumentsProvider extends DocumentsProvider {
final String name = file.getName();
final int lastDot = name.lastIndexOf('.');
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);
if (mime != null) return mime;
}
@@ -236,7 +236,7 @@ public class TermuxDocumentsProvider extends DocumentsProvider {
row.add(Document.COLUMN_MIME_TYPE, mimeType);
row.add(Document.COLUMN_LAST_MODIFIED, file.lastModified());
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.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
@@ -83,17 +82,7 @@ public class TermuxFileReceiverActivity extends Activity {
void showErrorDialogAndQuit(String message) {
mFinishOnDismissNameDialog = false;
new AlertDialog.Builder(this).setMessage(message).setOnDismissListener(new DialogInterface.OnDismissListener() {
@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();
new AlertDialog.Builder(this).setMessage(message).setOnDismissListener(dialog -> finish()).setPositiveButton(android.R.string.ok, (dialog, which) -> finish()).show();
}
void handleContentUri(final Uri uri, String subjectFromIntent) {
@@ -119,54 +108,40 @@ public class TermuxFileReceiverActivity extends Activity {
}
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() {
@Override
public void onTextSet(String text) {
File outFile = saveStreamWithName(in, text);
if (outFile == null) return;
DialogUtils.textInput(this, R.string.file_received_title, attachmentFileName, R.string.file_received_edit_button, text -> {
File outFile = saveStreamWithName(in, text);
if (outFile == null) return;
final File editorProgramFile = new File(EDITOR_PROGRAM);
if (!editorProgramFile.isFile()) {
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.");
return;
}
final File editorProgramFile = new File(EDITOR_PROGRAM);
if (!editorProgramFile.isFile()) {
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.");
return;
}
// Do this for the user if necessary:
//noinspection ResultOfMethodCallIgnored
editorProgramFile.setExecutable(true);
// Do this for the user if necessary:
//noinspection ResultOfMethodCallIgnored
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);
executeIntent.setClass(TermuxFileReceiverActivity.this, TermuxService.class);
executeIntent.putExtra(TermuxService.EXTRA_ARGUMENTS, new String[]{outFile.getAbsolutePath()});
startService(executeIntent);
finish();
}
Intent executeIntent = new Intent(TermuxService.ACTION_EXECUTE, scriptUri);
executeIntent.setClass(TermuxFileReceiverActivity.this, TermuxService.class);
executeIntent.putExtra(TermuxService.EXTRA_ARGUMENTS, new String[]{outFile.getAbsolutePath()});
startService(executeIntent);
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() {
@Override
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();
}
android.R.string.cancel, text -> finish(), dialog -> {
if (mFinishOnDismissNameDialog) finish();
});
}

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>

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

@@ -9,7 +9,7 @@
<!-- Screen border. -->
<path android:fillColor="#00000000"
android:strokeColor="#000"
android:strokeColor="#FFF"
android:strokeWidth="3"
android:pathData="M7,4
l34,0
@@ -23,7 +23,7 @@
/>
<!-- Block cursor. -->
<path android:fillColor="#000"
<path android:fillColor="#FFF"
android:pathData="M14,14
l5,0
l0,10

View File

@@ -1,9 +1,10 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="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:layout_width="match_parent"
android:layout_alignParentTop="true"
@@ -14,6 +15,8 @@
android:id="@+id/terminal_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginRight="3dp"
android:layout_marginLeft="3dp"
android:focusableInTouchMode="true"
android:scrollbarThumbVertical="@drawable/terminal_scroll_shape"
android:scrollbars="vertical" />
@@ -63,13 +66,13 @@
</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:visibility="gone"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_height="37.5dp"
android:background="@android:drawable/screen_background_dark_transparent"
android:layout_alignParentBottom="true" />
</RelativeLayout>

View File

@@ -8,6 +8,7 @@
android:maxLines="1"
android:inputType="text"
android:textColor="@android:color/white"
android:textColorHighlight="@android:color/darker_gray"
android:paddingTop="0dp"
android:textCursorDrawable="@null"
android:paddingBottom="0dp"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 338 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 203 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 331 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 505 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 691 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

View File

@@ -1,52 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="application_name">Termux</string>
<string name="shared_user_label">Termux user</string>
<string name="new_session">New session</string>
<string name="new_session_failsafe">Failsafe</string>
<string name="toggle_soft_keyboard">Keyboard</string>
<string name="reset_terminal">Reset</string>
<string name="style_terminal">Style</string>
<string name="toggle_fullscreen">Fullscreen</string>
<string name="share_transcript_title">Terminal transcript</string>
<string name="help">Help</string>
<string name="application_name">Termux</string>
<string name="shared_user_label">Termux user</string>
<string name="new_session">New session</string>
<string name="new_session_failsafe">Failsafe</string>
<string name="toggle_soft_keyboard">Keyboard</string>
<string name="reset_terminal">Reset</string>
<string name="style_terminal">Style</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="bootstrap_installer_body">Installing…</string>
<string name="bootstrap_error_title">Unable to install</string>
<string name="bootstrap_error_body">Termux was unable to install the bootstrap packages.\n\nCheck your network connection and try again.</string>
<string name="bootstrap_error_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="bootstrap_error_title">Unable to install</string>
<string name="bootstrap_error_body">Termux was unable to install the bootstrap packages.\n\nCheck your network connection and try again.</string>
<string name="bootstrap_error_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="max_terminals_reached_message">Close down existing ones before creating new.</string>
<string name="max_terminals_reached_title">Max terminals reached</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="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="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="kill_process">Kill process (%d)</string>
<string name="confirm_kill_process">Really kill this session?</string>
<string name="kill_process">Kill process (%d)</string>
<string name="confirm_kill_process">Really kill this session?</string>
<string name="session_rename_title">Set session name</string>
<string name="session_rename_positive_button">Set</string>
<string name="session_new_named_title">New named session</string>
<string name="session_new_named_positive_button">Create</string>
<string name="session_rename_title">Set session name</string>
<string name="session_rename_positive_button">Set</string>
<string name="session_new_named_title">New named session</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="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="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>
</resources>

View File

@@ -15,6 +15,9 @@
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>

View File

@@ -1,5 +1,6 @@
<shortcuts xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android">
<shortcut
android:shortcutId="new_session"
android:enabled="true"
@@ -11,4 +12,19 @@
android:targetPackage="com.termux"
android:targetClass="com.termux.app.TermuxActivity"/>
</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>

View File

@@ -1,20 +1,13 @@
#!/bin/sh
set -e -u
for DENSITY in mdpi hdpi xhdpi xxhdpi xxxhdpi; do
FOLDER=../app/src/main/res/mipmap-$DENSITY
for FILE in ic_launcher ic_launcher_round; do
PNG=$FOLDER/$FILE.png
# Update other apps:
for APP in api boot styling tasker widget; 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
for APP in api boot styling tasker widget; do
APPDIR=../../termux-$APP
for file in ic_foreground ic_launcher; do
cp ../app/src/main/res/drawable/$file.xml \
$APPDIR/app/src/main/res/drawable/$file.xml
done
cp ../app/src/main/res/drawable-anydpi-v26/ic_launcher.xml \
$APPDIR/app/src/main/res/drawable-anydpi-v26/$file.xml
done

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

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

View File

@@ -4,16 +4,14 @@ buildscript {
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.0.0'
classpath 'com.android.tools.build:gradle:3.4.2'
}
}
allprojects {
repositories {
google()
jcenter()
maven {
url "https://maven.google.com"
}
}
}

Binary file not shown.

View File

@@ -1,5 +1,5 @@
distributionUrl=https\://services.gradle.org/distributions/gradle-4.3.1-bin.zip
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

2
gradlew vendored
View File

@@ -28,7 +28,7 @@ APP_NAME="Gradle"
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.
DEFAULT_JVM_OPTS=""
DEFAULT_JVM_OPTS='"-Xmx64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"

2
gradlew.bat vendored
View File

@@ -14,7 +14,7 @@ set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@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"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome

View File

@@ -17,12 +17,11 @@ ext {
}
android {
compileSdkVersion 27
buildToolsVersion "27.0.1"
compileSdkVersion 28
defaultConfig {
minSdkVersion 21
targetSdkVersion 27
targetSdkVersion 28
externalNativeBuild {
ndkBuild {
@@ -47,6 +46,11 @@ android {
path "src/main/jni/Android.mk"
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
tasks.withType(Test) {

View File

@@ -1,5 +1,7 @@
package com.termux.terminal;
import java.util.Arrays;
/**
* A circular buffer of {@link TerminalRow}:s which keeps notes about what is visible on a logical screen and the scroll
* history.
@@ -39,7 +41,15 @@ public final class TerminalBuffer {
return getSelectedText(0, -getActiveTranscriptRows(), mColumns, mScreenRows).trim();
}
public String getTranscriptTextWithoutJoinedLines() {
return getSelectedText(0, -getActiveTranscriptRows(), mColumns, mScreenRows, false).trim();
}
public String getSelectedText(int selX1, int selY1, int selX2, int selY2) {
return getSelectedText(selX1, selY1, selX2, selY2, true);
}
public String getSelectedText(int selX1, int selY1, int selX2, int selY2, boolean joinBackLines) {
final StringBuilder builder = new StringBuilder();
final int columns = mColumns;
@@ -77,7 +87,8 @@ public final class TerminalBuffer {
}
if (lastPrintingCharIndex != -1)
builder.append(line, x1Index, lastPrintingCharIndex - x1Index + 1);
if (!rowLineWrap && row < selY2 && row < mScreenRows - 1) builder.append('\n');
if ((!joinBackLines || !rowLineWrap)
&& row < selY2 && row < mScreenRows - 1) builder.append('\n');
}
return builder.toString();
}
@@ -422,4 +433,14 @@ public final class TerminalBuffer {
}
}
public void clearTranscript() {
if (mScreenFirstRow < mActiveTranscriptRows) {
Arrays.fill(mLines, mTotalRows + mScreenFirstRow - mActiveTranscriptRows, mTotalRows, null);
Arrays.fill(mLines, 0, mScreenFirstRow, null);
} else {
Arrays.fill(mLines, mScreenFirstRow - mActiveTranscriptRows, mScreenFirstRow, null);
}
mActiveTranscriptRows = 0;
}
}

View File

@@ -301,6 +301,11 @@ public final class TerminalEmulator {
* @param mouseButton one of the MOUSE_* constants of this class.
*/
public void sendMouseEvent(int mouseButton, int column, int row, boolean pressed) {
if (column < 1) column = 1;
if (column > mColumns) column = mColumns;
if (row < 1) row = 1;
if (row > mRows) row = mRows;
if (mouseButton == MOUSE_LEFT_BUTTON_MOVED && !isDecsetInternalBitSet(DECSET_BIT_MOUSE_TRACKING_BUTTON_EVENT)) {
// Do not send tracking.
} else if (isDecsetInternalBitSet(DECSET_BIT_MOUSE_PROTOCOL_SGR)) {
@@ -308,7 +313,7 @@ public final class TerminalEmulator {
} else {
mouseButton = pressed ? mouseButton : 3; // 3 for release of all buttons.
// Clip to screen, and clip to the limits of 8-bit data.
boolean out_of_bounds = column < 1 || row < 1 || column > mColumns || row > mRows || column > 255 - 32 || row > 255 - 32;
boolean out_of_bounds = column > 255 - 32 || row > 255 - 32;
if (!out_of_bounds) {
byte[] data = {'\033', '[', 'M', (byte) (32 + mouseButton), (byte) (32 + column), (byte) (32 + row)};
mSession.write(data, 0, data.length);
@@ -1399,7 +1404,7 @@ public final class TerminalEmulator {
case 'I': // Cursor Horizontal Forward Tabulation (CHT). Move the active position n tabs forward.
setCursorCol(nextTabStop(getArg0(1)));
break;
case 'J': // "${CSI}${0,1,2}J" - Erase in Display (ED)
case 'J': // "${CSI}${0,1,2,3}J" - Erase in Display (ED)
// ED ignores the scrolling margins.
switch (getArg0(0)) {
case 0: // Erase from the active position to the end of the screen, inclusive (default).
@@ -1414,6 +1419,9 @@ public final class TerminalEmulator {
// move..
blockClear(0, 0, mColumns, mRows);
break;
case 3: // Delete all lines saved in the scrollback buffer (xterm etc)
mMainBuffer.clearTranscript();
break;
default:
unknownSequence(b);
return;
@@ -2333,6 +2341,9 @@ public final class TerminalEmulator {
public void paste(String text) {
// First: Always remove escape key and C1 control characters [0x80,0x9F]:
text = text.replaceAll("(\u001B|[\u0080-\u009F])", "");
// Second: Replace all newlines (\n) or CRLF (\r\n) with carriage returns (\r).
text = text.replaceAll("\r?\n", "\r");
// Then: Implement bracketed paste mode if enabled:
boolean bracketed = isDecsetInternalBitSet(DECSET_BIT_BRACKETED_PASTE_MODE);
if (bracketed) mSession.write("\033[200~");

View File

@@ -21,6 +21,8 @@ public final class TerminalRow {
boolean mLineWrap;
/** The style bits of each cell in the row. See {@link TextStyle}. */
final long[] mStyle;
/** If this row might contain chars with width != 1, used for deactivating fast path */
boolean mHasNonOneWidthOrSurrogateChars;
/** Construct a blank row (containing only whitespace, ' ') with a specified style. */
public TerminalRow(int columns, long style) {
@@ -32,6 +34,7 @@ public final class TerminalRow {
/** NOTE: The sourceX2 is exclusive. */
public void copyInterval(TerminalRow line, int sourceX1, int sourceX2, int destinationX) {
mHasNonOneWidthOrSurrogateChars |= line.mHasNonOneWidthOrSurrogateChars;
final int x1 = line.findStartOfColumn(sourceX1);
final int x2 = line.findStartOfColumn(sourceX2);
boolean startingFromSecondHalfOfWideChar = (sourceX1 > 0 && line.wideDisplayCharacterStartingAt(sourceX1 - 1));
@@ -116,6 +119,7 @@ public final class TerminalRow {
Arrays.fill(mText, ' ');
Arrays.fill(mStyle, style);
mSpaceUsed = (short) mColumns;
mHasNonOneWidthOrSurrogateChars = false;
}
// https://github.com/steven676/Android-Terminal-Emulator/commit/9a47042620bec87617f0b4f5d50568535668fe26
@@ -123,6 +127,17 @@ public final class TerminalRow {
mStyle[columnToSet] = style;
final int newCodePointDisplayWidth = WcWidth.width(codePoint);
// Fast path when we don't have any chars with width != 1
if (!mHasNonOneWidthOrSurrogateChars) {
if (codePoint >= Character.MIN_SUPPLEMENTARY_CODE_POINT || newCodePointDisplayWidth != 1) {
mHasNonOneWidthOrSurrogateChars = true;
} else {
mText[columnToSet] = (char) codePoint;
return;
}
}
final boolean newIsCombining = newCodePointDisplayWidth <= 0;
boolean wasExtraColForWideChar = (columnToSet > 0) && wideDisplayCharacterStartingAt(columnToSet - 1);

View File

@@ -57,7 +57,7 @@ static int create_subprocess(JNIEnv* env,
tcsetattr(ptm, TCSANOW, &tios);
/** Set initial winsize. */
struct winsize sz = { .ws_row = rows, .ws_col = columns };
struct winsize sz = { .ws_row = (unsigned short) rows, .ws_col = (unsigned short) columns };
ioctl(ptm, TIOCSWINSZ, &sz);
pid_t pid = fork();
@@ -180,7 +180,7 @@ JNIEXPORT jint JNICALL Java_com_termux_terminal_JNI_createSubprocess(
JNIEXPORT void JNICALL Java_com_termux_terminal_JNI_setPtyWindowSize(JNIEnv* TERMUX_UNUSED(env), jclass TERMUX_UNUSED(clazz), jint fd, jint rows, jint cols)
{
struct winsize sz = { .ws_row = rows, .ws_col = cols };
struct winsize sz = { .ws_row = (unsigned short) rows, .ws_col = (unsigned short) cols };
ioctl(fd, TIOCSWINSZ, &sz);
}
@@ -194,7 +194,7 @@ JNIEXPORT void JNICALL Java_com_termux_terminal_JNI_setPtyUTF8Mode(JNIEnv* TERMU
}
}
JNIEXPORT int JNICALL Java_com_termux_terminal_JNI_waitFor(JNIEnv* TERMUX_UNUSED(env), jclass TERMUX_UNUSED(clazz), jint pid)
JNIEXPORT jint JNICALL Java_com_termux_terminal_JNI_waitFor(JNIEnv* TERMUX_UNUSED(env), jclass TERMUX_UNUSED(clazz), jint pid)
{
int status;
waitpid(pid, &status, 0);

View File

@@ -17,13 +17,13 @@ public class ByteQueueTest extends TestCase {
public void testCompleteWrites() throws Exception {
ByteQueue q = new ByteQueue(10);
assertEquals(true, q.write(new byte[]{1, 2, 3}, 0, 3));
assertTrue(q.write(new byte[]{1, 2, 3}, 0, 3));
byte[] arr = new byte[10];
assertEquals(3, q.read(arr, true));
assertArrayEquals(new byte[]{1, 2, 3}, new byte[]{arr[0], arr[1], arr[2]});
assertEquals(true, q.write(new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, 0, 10));
assertTrue(q.write(new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, 0, 10));
assertEquals(10, q.read(arr, true));
assertArrayEquals(new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, arr);
}
@@ -43,7 +43,7 @@ public class ByteQueueTest extends TestCase {
public void testWriteNotesClosing() throws Exception {
ByteQueue q = new ByteQueue(10);
q.close();
assertEquals(false, q.write(new byte[]{1, 2, 3}, 0, 3));
assertFalse(q.write(new byte[]{1, 2, 3}, 0, 3));
}
public void testReadNonBlocking() throws Exception {

View File

@@ -46,4 +46,20 @@ public class ControlSequenceIntroducerTest extends TerminalTestCase {
withTerminalSized(5, 2).enterString("abcde\033[2G\033[2b\n").assertLinesAre("aeede", " ");
}
/** CSI 3 J Clear scrollback (xterm, libvte; non-standard). */
public void testCsi3J() {
withTerminalSized(3, 2).enterString("a\r\nb\r\nc\r\nd");
assertEquals("a\nb\nc\nd", mTerminal.getScreen().getTranscriptText());
enterString("\033[3J");
assertEquals("c\nd", mTerminal.getScreen().getTranscriptText());
withTerminalSized(3, 2).enterString("Lorem_ipsum");
assertEquals("Lorem_ipsum", mTerminal.getScreen().getTranscriptText());
enterString("\033[3J");
assertEquals("ipsum", mTerminal.getScreen().getTranscriptText());
withTerminalSized(3, 2).enterString("w\r\nx\r\ny\r\nz\033[?1049h\033[3J\033[?1049l");
assertEquals("y\nz", mTerminal.getScreen().getTranscriptText());
}
}

View File

@@ -1,6 +1,6 @@
package com.termux.terminal;
import junit.framework.Assert;
import org.junit.Assert;
public class CursorAndScreenTest extends TerminalTestCase {

View File

@@ -55,6 +55,14 @@ public class TerminalTest extends TerminalTestCase {
assertEquals("\033[<0;3;4M", mOutput.getOutputAndClear());
mTerminal.sendMouseEvent(TerminalEmulator.MOUSE_LEFT_BUTTON, 3, 4, false);
assertEquals("\033[<0;3;4m", mOutput.getOutputAndClear());
// When the client says that a click is outside (which could happen when pixels are outside
// the terminal area, see https://github.com/termux/termux-app/issues/501) the terminal
// sends a click at the edge.
mTerminal.sendMouseEvent(TerminalEmulator.MOUSE_LEFT_BUTTON, 0, 0, true);
assertEquals("\033[<0;1;1M", mOutput.getOutputAndClear());
mTerminal.sendMouseEvent(TerminalEmulator.MOUSE_LEFT_BUTTON, 11, 11, false);
assertEquals("\033[<0;10;10m", mOutput.getOutputAndClear());
}
public void testNormalization() throws UnsupportedEncodingException {

View File

@@ -4,7 +4,6 @@ import junit.framework.AssertionFailedError;
import junit.framework.TestCase;
import java.io.ByteArrayOutputStream;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashSet;
@@ -27,14 +26,10 @@ public abstract class TerminalTestCase extends TestCase {
}
public String getOutputAndClear() {
try {
String result = new String(baos.toByteArray(), "UTF-8");
baos.reset();
return result;
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
String result = new String(baos.toByteArray(), StandardCharsets.UTF_8);
baos.reset();
return result;
}
@Override
public void titleChanged(String oldTitle, String newTitle) {

View File

@@ -56,7 +56,7 @@ public class TextStyleTest extends TestCase {
public void testEncodingProtected() {
long encoded = TextStyle.encode(TextStyle.COLOR_INDEX_FOREGROUND, TextStyle.COLOR_INDEX_BACKGROUND,
TextStyle.CHARACTER_ATTRIBUTE_STRIKETHROUGH);
assertTrue((TextStyle.decodeEffect(encoded) & TextStyle.CHARACTER_ATTRIBUTE_PROTECTED) == 0);
assertEquals(0, (TextStyle.decodeEffect(encoded) & TextStyle.CHARACTER_ATTRIBUTE_PROTECTED));
encoded = TextStyle.encode(TextStyle.COLOR_INDEX_FOREGROUND, TextStyle.COLOR_INDEX_BACKGROUND,
TextStyle.CHARACTER_ATTRIBUTE_STRIKETHROUGH | TextStyle.CHARACTER_ATTRIBUTE_PROTECTED);
assertTrue((TextStyle.decodeEffect(encoded) & TextStyle.CHARACTER_ATTRIBUTE_PROTECTED) != 0);

View File

@@ -58,10 +58,6 @@ public class WcWidthTest extends TestCase {
assertWidthIs(0, 0x2060);
}
public void testWatch() {
}
public void testSofthyphen() {
// http://osdir.com/ml/internationalization.linux/2003-05/msg00006.html:
// "Existing implementation practice in terminals is that the SOFT HYPHEN is

View File

@@ -17,17 +17,16 @@ ext {
}
android {
compileSdkVersion 27
buildToolsVersion "27.0.1"
compileSdkVersion 28
dependencies {
implementation 'com.android.support:support-annotations:27.0.0'
implementation "androidx.annotation:annotation:1.0.1"
api project(":terminal-emulator")
}
defaultConfig {
minSdkVersion 21
targetSdkVersion 27
targetSdkVersion 28
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
@@ -37,6 +36,11 @@ android {
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {

View File

@@ -85,6 +85,7 @@ final class GestureAndScaleRecognizer {
return mListener.onScale(detector.getFocusX(), detector.getFocusY(), detector.getScaleFactor());
}
});
mScaleDetector.setQuickScaleEnabled(false);
}
public void onTouchEvent(MotionEvent event) {

View File

@@ -16,7 +16,7 @@ import com.termux.terminal.WcWidth;
* <p/>
* Saves font metrics, so needs to be recreated each time the typeface or font size changes.
*/
final class TerminalRenderer {
public final class TerminalRenderer {
final int mTextSize;
final Typeface mTypeface;

View File

@@ -15,6 +15,7 @@ import android.text.InputType;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.view.accessibility.AccessibilityManager;
import android.view.ActionMode;
import android.view.HapticFeedbackConstants;
import android.view.InputDevice;
@@ -75,6 +76,8 @@ public final class TerminalView extends View {
/** If non-zero, this is the last unicode code point received if that was a combining character. */
int mCombiningAccent;
private boolean mAccessibilityEnabled;
public TerminalView(Context context, AttributeSet attributes) { // NO_UCD (unused code)
super(context, attributes);
mGestureRecognizer = new GestureAndScaleRecognizer(context, new GestureAndScaleRecognizer.Listener() {
@@ -197,6 +200,8 @@ public final class TerminalView extends View {
}
});
mScroller = new Scroller(context);
AccessibilityManager am = (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
mAccessibilityEnabled = am.isEnabled();
}
/**
@@ -353,10 +358,12 @@ public final class TerminalView extends View {
public void onScreenUpdated() {
if (mEmulator == null) return;
int rowsInHistory = mEmulator.getScreen().getActiveTranscriptRows();
if (mTopRow < -rowsInHistory) mTopRow = -rowsInHistory;
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
@@ -384,6 +391,7 @@ public final class TerminalView extends View {
mEmulator.clearScrollCounter();
invalidate();
if (mAccessibilityEnabled) setContentDescription(getText());
}
/**
@@ -594,7 +602,7 @@ public final class TerminalView extends View {
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 (!event.isFunctionPressed() && handleKeyCode(keyCode, keyMod)) {
if (LOG_KEY_EVENTS) Log.i(EmulatorDebug.LOG_TAG, "handleKeyCode() took key event");
return true;
}
@@ -613,7 +621,7 @@ public final class TerminalView extends View {
if (LOG_KEY_EVENTS)
Log.i(EmulatorDebug.LOG_TAG, "KeyEvent#getUnicodeChar(" + effectiveMetaState + ") returned: " + result);
if (result == 0) {
return true;
return false;
}
int oldCombiningAccent = mCombiningAccent;
@@ -642,6 +650,8 @@ public final class TerminalView extends View {
+ leftAltDownFromEvent + ")");
}
if (mTermSession == null) return;
final boolean controlDown = controlDownFromEvent || mClient.readControlKey();
final boolean altDown = leftAltDownFromEvent || mClient.readAltKey();
@@ -913,4 +923,8 @@ public final class TerminalView extends View {
return mTermSession;
}
private CharSequence getText() {
return mEmulator.getScreen().getSelectedText(0, mTopRow, mEmulator.mColumns, mTopRow +mEmulator.mRows);
}
}