Compare commits

..

82 Commits
v0.62 ... v0.66

Author SHA1 Message Date
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
55 changed files with 838 additions and 965 deletions

View File

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

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

18
.gitignore vendored
View File

@@ -20,21 +20,8 @@ local.properties
# Signing files # Signing files
.signing/ .signing/
# User-specific configurations # Intellij
.idea/libraries/ .idea/
.idea/workspace.xml
.idea/tasks.xml
.idea/.name
.idea/compiler.xml
.idea/copyright/profiles_settings.xml
.idea/encodings.xml
.idea/misc.xml
.idea/modules.xml
.idea/scopes/scope_settings.xml
.idea/vcs.xml
.idea/dictionaries/
.idea/caches/
.idea/codeStyles/
*.iml *.iml
# OS-specific files # OS-specific files
@@ -43,5 +30,6 @@ local.properties
._* ._*
.Spotlight-V100 .Spotlight-V100
.Trashes .Trashes
.swp
ehthumbs.db ehthumbs.db
Thumbs.db Thumbs.db

View File

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

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,14 +12,13 @@ android:
components: components:
- platform-tools - platform-tools
- tools - tools
- build-tools-27.0.3 - build-tools-28.0.3
- android-27 - android-28
- extra-android-m2repository - extra-android-m2repository
before_install: before_install:
- git clone https://github.com/urho3d/android-ndk.git $HOME/android-ndk - yes | sdkmanager "ndk-bundle"
- export ANDROID_NDK_HOME=$HOME/android-ndk - yes | sdkmanager "platforms;android-28"
- yes | sdkmanager "platforms;android-27"
script: script:
- ./gradlew testDebugUnitTest - ./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) [![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) [![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](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 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 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 Google+ community](http://termux.com/community/)
* [Termux Help](http://termux.com/help/)
* [Termux Twitter](http://twitter.com/termux/)
* [Termux Wiki](https://wiki.termux.com/wiki/) * [Termux Wiki](https://wiki.termux.com/wiki/)
* [Termux Twitter](http://twitter.com/termux/)
Note that this repository is for the app itself (the user interface and the terminal emulation). For the packages installable inside the app, see [termux/termux-packages](https://github.com/termux/termux-packages) 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,20 +1,21 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
android { android {
compileSdkVersion 27 compileSdkVersion 28
dependencies { dependencies {
implementation 'com.android.support:support-annotations:27.1.1' implementation "androidx.annotation:annotation:1.0.1"
implementation "com.android.support:support-core-ui:27.1.1" implementation "androidx.viewpager:viewpager:1.0.0"
implementation "androidx.drawerlayout:drawerlayout:1.0.0"
implementation project(":terminal-view") implementation project(":terminal-view")
} }
defaultConfig { defaultConfig {
applicationId "com.termux" applicationId "com.termux"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 27 targetSdkVersion 28
versionCode 62 versionCode 66
versionName "0.62" versionName "0.66"
} }
buildTypes { buildTypes {
@@ -24,6 +25,11 @@ android {
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
} }
} }
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
} }
dependencies { dependencies {

View File

@@ -12,13 +12,13 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.VIBRATE" /> <uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application <application
android:extractNativeLibs="true" android:extractNativeLibs="true"
android:allowBackup="true" android:allowBackup="true"
android:fullBackupContent="@xml/backupscheme" android:fullBackupContent="@xml/backupscheme"
android:icon="@mipmap/ic_launcher" android:icon="@drawable/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:banner="@drawable/banner" android:banner="@drawable/banner"
android:label="@string/application_name" android:label="@string/application_name"
android:theme="@style/Theme.Termux" android:theme="@style/Theme.Termux"

View File

@@ -186,7 +186,7 @@ public final class BackgroundJob {
if (interpreter != null) result.add(interpreter); if (interpreter != null) result.add(interpreter);
result.add(fileToExecute); result.add(fileToExecute);
if (args != null) Collections.addAll(result, args); if (args != null) Collections.addAll(result, args);
return result.toArray(new String[result.size()]); return result.toArray(new String[0]);
} }
} }

View File

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

View File

@@ -1,13 +1,17 @@
package com.termux.app; package com.termux.app;
import android.content.Context; import android.content.Context;
import android.os.Build;
import android.util.AttributeSet; import android.util.AttributeSet;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import android.view.Gravity; import java.util.Map;
import java.util.HashMap;
import java.util.Arrays;
import android.view.HapticFeedbackConstants; import android.view.HapticFeedbackConstants;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.MotionEvent; import android.view.MotionEvent;
@@ -28,101 +32,99 @@ import com.termux.view.TerminalView;
public final class ExtraKeysView extends GridLayout { public final class ExtraKeysView extends GridLayout {
private static final int TEXT_COLOR = 0xFFFFFFFF; private static final int TEXT_COLOR = 0xFFFFFFFF;
private static final int BUTTON_COLOR = 0xFF000000; private static final int BUTTON_COLOR = 0x00000000;
private static final int BUTTON_PRESSED_COLOR = 0xFF888888; private static final int INTERESTING_COLOR = 0xFF80DEEA;
private static final int BUTTON_PRESSED_COLOR = 0x7FFFFFFF;
public ExtraKeysView(Context context, AttributeSet attrs) { public ExtraKeysView(Context context, AttributeSet attrs) {
super(context, 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);
}};
static void sendKey(View view, String keyName) { 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 "HOME":
keyCode = KeyEvent.KEYCODE_MOVE_HOME;
break;
case "END":
keyCode = KeyEvent.KEYCODE_MOVE_END;
break;
case "PGUP":
keyCode = KeyEvent.KEYCODE_PAGE_UP;
break;
case "PGDN":
keyCode = KeyEvent.KEYCODE_PAGE_DOWN;
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;
}
TerminalView terminalView = view.findViewById(R.id.terminal_view); TerminalView terminalView = view.findViewById(R.id.terminal_view);
if (keyCode > 0) { if (keyCodesForString.containsKey(keyName)) {
int keyCode = keyCodesForString.get(keyName);
terminalView.onKeyDown(keyCode, new KeyEvent(KeyEvent.ACTION_UP, keyCode)); terminalView.onKeyDown(keyCode, new KeyEvent(KeyEvent.ACTION_UP, keyCode));
// view.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, keyCode)); // view.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, keyCode));
} else { } else {
// not a control char
TerminalSession session = terminalView.getCurrentSession(); TerminalSession session = terminalView.getCurrentSession();
if (session != null) session.write(chars); if (session != null)
session.write(keyName);
} }
} }
private ToggleButton controlButton; public enum SpecialButton {
private ToggleButton altButton; CTRL, ALT, FN
private ToggleButton fnButton; }
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 ScheduledExecutorService scheduledExecutor;
private PopupWindow popupWindow; private PopupWindow popupWindow;
private int longPressCount; private int longPressCount;
public boolean readControlButton() { public boolean readSpecialButton(SpecialButton name) {
if (controlButton.isPressed()) return true; SpecialButtonState state = specialButtons.get(name);
boolean result = controlButton.isChecked(); if (state == null)
if (result) { throw new RuntimeException("Must be a valid special button (see source)");
controlButton.setChecked(false);
controlButton.setTextColor(TEXT_COLOR);
}
return result;
}
public boolean readAltButton() { if (! state.isOn)
if (altButton.isPressed()) return true; return false;
boolean result = altButton.isChecked();
if (result) {
altButton.setChecked(false);
altButton.setTextColor(TEXT_COLOR);
}
return result;
}
public boolean readFnButton() { if (state.button.isPressed())
if (fnButton.isPressed()) return true; return true;
boolean result = fnButton.isChecked();
if (result) { if (! state.button.isChecked())
fnButton.setChecked(false); return false;
fnButton.setTextColor(TEXT_COLOR);
} state.button.setChecked(false);
return result; state.button.setTextColor(TEXT_COLOR);
return true;
} }
void popup(View view, String text) { void popup(View view, String text) {
@@ -148,92 +150,222 @@ public final class ExtraKeysView extends GridLayout {
popupWindow.showAsDropDown(view, 0, -2 * height); popupWindow.showAsDropDown(view, 0, -2 * height);
} }
void reload() { static final CharDisplayMap classicArrowsDisplay = new CharDisplayMap() {{
altButton = controlButton = null; // classic arrow keys (for ◀ ▶ ▲ ▼ @see arrowVariationDisplay)
removeAllViews(); put("LEFT", ""); // U+2190 ← LEFTWARDS ARROW
put("RIGHT", ""); // U+2192 → RIGHTWARDS ARROW
put("UP", ""); // U+2191 ↑ UPWARDS ARROW
put("DOWN", ""); // U+2193 ↓ DOWNWARDS ARROW
}};
String[][] buttons = { static final CharDisplayMap wellKnownCharactersDisplay = new CharDisplayMap() {{
{"ESC", "/", "", "HOME", "", "END", "PGUP"}, // well known characters // https://en.wikipedia.org/wiki/{Enter_key, Tab_key, Delete_key}
{"TAB", "CTRL", "ALT", "", "", "", "PGDN"} 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
}};
final int rows = buttons.length; static final CharDisplayMap lessKnownCharactersDisplay = new CharDisplayMap() {{
final int[] cols = {buttons[0].length, buttons[1].length}; // 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
}};
setRowCount(rows); static final CharDisplayMap arrowTriangleVariationDisplay = new CharDisplayMap() {{
setColumnCount(cols[0]); // 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
}};
for (int row = 0; row < rows; row++) { static final CharDisplayMap notKnownIsoCharacters = new CharDisplayMap() {{
for (int col = 0; col < cols[row]; col++) { // Control chars that are more clear as text // https://en.wikipedia.org/wiki/{Function_key, Alt_key, Control_key, Esc_key}
final String buttonText = buttons[row][col]; // put("FN", "FN"); // no ISO character exists
Button button; 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
switch (buttonText) { put("ALT", ""); // ISO character "U+2387 ⎇ ALTERNATIVE KEY SYMBOL'" is unknown to people and only printed as the Option key "⌥" on Mac computer
case "CTRL": put("ESC", ""); // ISO character "U+238B ⎋ BROKEN CIRCLE WITH NORTHWEST ARROW" is unknown to people and not often printed on computers
button = controlButton = new ToggleButton(getContext(), null, android.R.attr.buttonBarButtonStyle); }};
button.setClickable(true);
break; static final CharDisplayMap nicerLookingDisplay = new CharDisplayMap() {{
case "ALT": // nicer looking for most cases
button = altButton = new ToggleButton(getContext(), null, android.R.attr.buttonBarButtonStyle); put("-", ""); // U+2015 ― HORIZONTAL BAR
button.setClickable(true); }};
break;
case "FN": /**
button = fnButton = new ToggleButton(getContext(), null, android.R.attr.buttonBarButtonStyle); * Keys are displayed in a natural looking way, like "→" for "RIGHT" or "↲" for ENTER
button.setClickable(true); */
break; public static final CharDisplayMap defaultCharDisplay = new CharDisplayMap() {{
default: putAll(classicArrowsDisplay);
button = new Button(getContext(), null, android.R.attr.buttonBarButtonStyle); putAll(wellKnownCharactersDisplay);
break; 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]);
} }
button.setText(buttonText); /**
* 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();
replaceAliases(buttons); // modifies the array
final int rows = buttons.length;
final int cols = maximumLength(buttons);
setRowCount(rows);
setColumnCount(cols);
for (int row = 0; row < rows; row++) {
for (int col = 0; col < buttons[row].length; col++) {
final String buttonText = buttons[row][col];
Button button;
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);
}
final String displayedText = charDisplayMap.get(buttonText, buttonText);
button.setText(displayedText);
button.setTextColor(TEXT_COLOR); button.setTextColor(TEXT_COLOR);
button.setPadding(0, 0, 0, 0); button.setPadding(0, 0, 0, 0);
final Button finalButton = button; final Button finalButton = button;
button.setOnClickListener(new OnClickListener() { button.setOnClickListener(v -> {
@Override
public void onClick(View v) {
finalButton.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP); finalButton.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP);
View root = getRootView(); View root = getRootView();
switch (buttonText) { if(Arrays.asList("CTRL", "ALT", "FN").contains(buttonText)) {
case "CTRL":
case "ALT":
case "FN":
ToggleButton self = (ToggleButton) finalButton; ToggleButton self = (ToggleButton) finalButton;
self.setChecked(self.isChecked()); self.setChecked(self.isChecked());
self.setTextColor(self.isChecked() ? 0xFF80DEEA : TEXT_COLOR); self.setTextColor(self.isChecked() ? INTERESTING_COLOR : TEXT_COLOR);
break; } else {
default:
sendKey(root, buttonText); sendKey(root, buttonText);
break;
}
} }
}); });
button.setOnTouchListener(new OnTouchListener() { button.setOnTouchListener((v, event) -> {
@Override
public boolean onTouch(View v, MotionEvent event) {
final View root = getRootView(); final View root = getRootView();
switch (event.getAction()) { switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_DOWN:
longPressCount = 0; longPressCount = 0;
v.setBackgroundColor(BUTTON_PRESSED_COLOR); v.setBackgroundColor(BUTTON_PRESSED_COLOR);
if ("↑↓←→".contains(buttonText)) { if (Arrays.asList("UP", "DOWN", "LEFT", "RIGHT").contains(buttonText)) {
scheduledExecutor = Executors.newSingleThreadScheduledExecutor(); scheduledExecutor = Executors.newSingleThreadScheduledExecutor();
scheduledExecutor.scheduleWithFixedDelay(new Runnable() { scheduledExecutor.scheduleWithFixedDelay(() -> {
@Override
public void run() {
longPressCount++; longPressCount++;
sendKey(root, buttonText); sendKey(root, buttonText);
}
}, 400, 80, TimeUnit.MILLISECONDS); }, 400, 80, TimeUnit.MILLISECONDS);
} }
return true; return true;
case MotionEvent.ACTION_MOVE: case MotionEvent.ACTION_MOVE:
if ("―/".contains(buttonText)) { // These two keys have a Move-Up button appearing
if (Arrays.asList("/", "-").contains(buttonText)) {
if (popupWindow == null && event.getY() < 0) { if (popupWindow == null && event.getY() < 0) {
v.setBackgroundColor(BUTTON_COLOR); v.setBackgroundColor(BUTTON_COLOR);
String text = "".equals(buttonText) ? "|" : "\\"; String text = "-".equals(buttonText) ? "|" : "\\";
popup(v, text); popup(v, text);
} }
if (popupWindow != null && event.getY() > 0) { if (popupWindow != null && event.getY() > 0) {
@@ -243,6 +375,7 @@ public final class ExtraKeysView extends GridLayout {
} }
} }
return true; return true;
case MotionEvent.ACTION_UP: case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_CANCEL:
v.setBackgroundColor(BUTTON_COLOR); v.setBackgroundColor(BUTTON_COLOR);
@@ -251,27 +384,30 @@ public final class ExtraKeysView extends GridLayout {
scheduledExecutor = null; scheduledExecutor = null;
} }
if (longPressCount == 0) { if (longPressCount == 0) {
if (popupWindow != null && "/".contains(buttonText)) { if (popupWindow != null && Arrays.asList("/", "-").contains(buttonText)) {
popupWindow.setContentView(null); popupWindow.setContentView(null);
popupWindow.dismiss(); popupWindow.dismiss();
popupWindow = null; popupWindow = null;
sendKey(root, "".equals(buttonText) ? "|" : "\\"); sendKey(root, "-".equals(buttonText) ? "|" : "\\");
} else { } else {
v.performClick(); v.performClick();
} }
} }
return true; return true;
default: default:
return true; return true;
} }
}
}); });
LayoutParams param = new GridLayout.LayoutParams(); LayoutParams param = new GridLayout.LayoutParams();
param.width = param.height = 0; 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.setMargins(0, 0, 0, 0);
param.setGravity(Gravity.LEFT);
param.columnSpec = GridLayout.spec(col, GridLayout.FILL, 1.f); param.columnSpec = GridLayout.spec(col, GridLayout.FILL, 1.f);
param.rowSpec = GridLayout.spec(row, GridLayout.FILL, 1.f); param.rowSpec = GridLayout.spec(row, GridLayout.FILL, 1.f);
button.setLayoutParams(param); button.setLayoutParams(param);

View File

@@ -11,8 +11,6 @@ import android.content.ClipData;
import android.content.ClipboardManager; import android.content.ClipboardManager;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnShowListener;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.ServiceConnection; import android.content.ServiceConnection;
@@ -27,11 +25,6 @@ import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.IBinder; import android.os.IBinder;
import android.os.Vibrator; import android.os.Vibrator;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v4.widget.DrawerLayout;
import android.text.SpannableString; import android.text.SpannableString;
import android.text.Spanned; import android.text.Spanned;
import android.text.TextUtils; import android.text.TextUtils;
@@ -40,19 +33,13 @@ import android.util.Log;
import android.view.ContextMenu; import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo; import android.view.ContextMenu.ContextMenuInfo;
import android.view.Gravity; import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.WindowManager; import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.ArrayAdapter; import android.widget.ArrayAdapter;
import android.widget.EditText; import android.widget.EditText;
import android.widget.ListView; import android.widget.ListView;
@@ -78,6 +65,12 @@ import java.util.Properties;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.viewpager.widget.PagerAdapter;
import androidx.viewpager.widget.ViewPager;
/** /**
* A terminal emulator activity. * A terminal emulator activity.
* <p/> * <p/>
@@ -97,6 +90,7 @@ public final class TermuxActivity extends Activity implements ServiceConnection
private static final int CONTEXTMENU_RESET_TERMINAL_ID = 5; private static final int CONTEXTMENU_RESET_TERMINAL_ID = 5;
private static final int CONTEXTMENU_STYLING_ID = 6; private static final int CONTEXTMENU_STYLING_ID = 6;
private static final int CONTEXTMENU_HELP_ID = 8; private static final int CONTEXTMENU_HELP_ID = 8;
private static final int CONTEXTMENU_TOGGLE_KEEP_SCREEN_ON = 9;
private static final int MAX_SESSIONS = 8; private static final int MAX_SESSIONS = 8;
@@ -149,6 +143,10 @@ public final class TermuxActivity extends Activity implements ServiceConnection
} }
checkForFontAndColors(); checkForFontAndColors();
mSettings.reloadFromProperties(TermuxActivity.this); mSettings.reloadFromProperties(TermuxActivity.this);
if (mExtraKeysView != null) {
mExtraKeysView.reload(mSettings.mExtraKeys, ExtraKeysView.defaultCharDisplay);
}
} }
} }
}; };
@@ -213,10 +211,16 @@ public final class TermuxActivity extends Activity implements ServiceConnection
mTerminalView.setOnKeyListener(new TermuxViewClient(this)); mTerminalView.setOnKeyListener(new TermuxViewClient(this));
mTerminalView.setTextSize(mSettings.getFontSize()); mTerminalView.setTextSize(mSettings.getFontSize());
mTerminalView.setKeepScreenOn(mSettings.isScreenAlwaysOn());
mTerminalView.requestFocus(); mTerminalView.requestFocus();
final ViewPager viewPager = findViewById(R.id.viewpager); final ViewPager viewPager = findViewById(R.id.viewpager);
if (mSettings.isShowExtraKeys()) viewPager.setVisibility(View.VISIBLE); if (mSettings.mShowExtraKeys) viewPager.setVisibility(View.VISIBLE);
ViewGroup.LayoutParams layoutParams = viewPager.getLayoutParams();
layoutParams.height = layoutParams.height * mSettings.mExtraKeys.length;
viewPager.setLayoutParams(layoutParams);
viewPager.setAdapter(new PagerAdapter() { viewPager.setAdapter(new PagerAdapter() {
@Override @Override
@@ -236,12 +240,11 @@ public final class TermuxActivity extends Activity implements ServiceConnection
View layout; View layout;
if (position == 0) { if (position == 0) {
layout = mExtraKeysView = (ExtraKeysView) inflater.inflate(R.layout.extra_keys_main, collection, false); layout = mExtraKeysView = (ExtraKeysView) inflater.inflate(R.layout.extra_keys_main, collection, false);
mExtraKeysView.reload(mSettings.mExtraKeys, ExtraKeysView.defaultCharDisplay);
} else { } else {
layout = inflater.inflate(R.layout.extra_keys_right, collection, false); layout = inflater.inflate(R.layout.extra_keys_right, collection, false);
final EditText editText = layout.findViewById(R.id.text_input); final EditText editText = layout.findViewById(R.id.text_input);
editText.setOnEditorActionListener(new TextView.OnEditorActionListener() { editText.setOnEditorActionListener((v, actionId, event) -> {
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
TerminalSession session = getCurrentTermSession(); TerminalSession session = getCurrentTermSession();
if (session != null) { if (session != null) {
if (session.isRunning()) { if (session.isRunning()) {
@@ -254,7 +257,6 @@ public final class TermuxActivity extends Activity implements ServiceConnection
editText.setText(""); editText.setText("");
} }
return true; return true;
}
}); });
} }
collection.addView(layout); collection.addView(layout);
@@ -280,47 +282,23 @@ public final class TermuxActivity extends Activity implements ServiceConnection
}); });
View newSessionButton = findViewById(R.id.new_session_button); View newSessionButton = findViewById(R.id.new_session_button);
newSessionButton.setOnClickListener(new OnClickListener() { newSessionButton.setOnClickListener(v -> addNewSession(false, null));
@Override newSessionButton.setOnLongClickListener(v -> {
public void onClick(View v) {
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, DialogUtils.textInput(TermuxActivity.this, R.string.session_new_named_title, null, R.string.session_new_named_positive_button,
new DialogUtils.TextSetListener() { text -> addNewSession(false, text), R.string.new_session_failsafe, text -> addNewSession(true, text)
@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); , -1, null, null);
return true; return true;
}
}); });
findViewById(R.id.toggle_keyboard_button).setOnClickListener(new OnClickListener() { findViewById(R.id.toggle_keyboard_button).setOnClickListener(v -> {
@Override
public void onClick(View v) {
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.toggleSoftInput(InputMethodManager.SHOW_IMPLICIT, 0); imm.toggleSoftInput(InputMethodManager.SHOW_IMPLICIT, 0);
getDrawer().closeDrawers(); getDrawer().closeDrawers();
}
}); });
findViewById(R.id.toggle_keyboard_button).setOnLongClickListener(new OnLongClickListener() { findViewById(R.id.toggle_keyboard_button).setOnLongClickListener(v -> {
@Override
public boolean onLongClick(View v) {
toggleShowExtraKeys(); toggleShowExtraKeys();
return true; return true;
}
}); });
registerForContextMenu(mTerminalView); registerForContextMenu(mTerminalView);
@@ -388,6 +366,11 @@ public final class TermuxActivity extends Activity implements ServiceConnection
if (indexOfSession >= 0) if (indexOfSession >= 0)
showToast(toToastTitle(finishedSession) + " - exited", true); showToast(toToastTitle(finishedSession) + " - exited", true);
} }
if (mTermService.getSessions().size() > 1) {
removeFinishedSession(finishedSession);
}
mListViewAdapter.notifyDataSetChanged(); mListViewAdapter.notifyDataSetChanged();
} }
@@ -466,35 +449,26 @@ public final class TermuxActivity extends Activity implements ServiceConnection
} }
}; };
listView.setAdapter(mListViewAdapter); listView.setAdapter(mListViewAdapter);
listView.setOnItemClickListener(new OnItemClickListener() { listView.setOnItemClickListener((parent, view, position, id) -> {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
TerminalSession clickedSession = mListViewAdapter.getItem(position); TerminalSession clickedSession = mListViewAdapter.getItem(position);
switchToSession(clickedSession); switchToSession(clickedSession);
getDrawer().closeDrawers(); getDrawer().closeDrawers();
}
}); });
listView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() { listView.setOnItemLongClickListener((parent, view, position, id) -> {
@Override
public boolean onItemLongClick(AdapterView<?> parent, View view, final int position, long id) {
final TerminalSession selectedSession = mListViewAdapter.getItem(position); final TerminalSession selectedSession = mListViewAdapter.getItem(position);
renameSession(selectedSession); renameSession(selectedSession);
return true; return true;
}
}); });
if (mTermService.getSessions().isEmpty()) { if (mTermService.getSessions().isEmpty()) {
if (mIsVisible) { if (mIsVisible) {
TermuxInstaller.setupIfNeeded(TermuxActivity.this, new Runnable() { TermuxInstaller.setupIfNeeded(TermuxActivity.this, () -> {
@Override
public void run() {
if (mTermService == null) return; // Activity might have been destroyed. if (mTermService == null) return; // Activity might have been destroyed.
try { try {
addNewSession(false, null); addNewSession(false, null);
} catch (WindowManager.BadTokenException e) { } catch (WindowManager.BadTokenException e) {
// Activity finished - ignore. // Activity finished - ignore.
} }
}
}); });
} else { } else {
// The service connected while not in foreground - just bail out. // The service connected while not in foreground - just bail out.
@@ -524,12 +498,9 @@ public final class TermuxActivity extends Activity implements ServiceConnection
@SuppressLint("InflateParams") @SuppressLint("InflateParams")
void renameSession(final TerminalSession sessionToRename) { void renameSession(final TerminalSession sessionToRename) {
DialogUtils.textInput(this, R.string.session_rename_title, sessionToRename.mSessionName, R.string.session_rename_positive_button, new DialogUtils.TextSetListener() { DialogUtils.textInput(this, R.string.session_rename_title, sessionToRename.mSessionName, R.string.session_rename_positive_button, text -> {
@Override
public void onTextSet(String text) {
sessionToRename.mSessionName = text; sessionToRename.mSessionName = text;
mListViewAdapter.notifyDataSetChanged(); mListViewAdapter.notifyDataSetChanged();
}
}, -1, null, -1, null, null); }, -1, null, -1, null, null);
} }
@@ -601,6 +572,18 @@ public final class TermuxActivity extends Activity implements ServiceConnection
new AlertDialog.Builder(this).setTitle(R.string.max_terminals_reached_title).setMessage(R.string.max_terminals_reached_message) new AlertDialog.Builder(this).setTitle(R.string.max_terminals_reached_title).setMessage(R.string.max_terminals_reached_message)
.setPositiveButton(android.R.string.ok, null).show(); .setPositiveButton(android.R.string.ok, null).show();
} else { } else {
if (mTermService.getSessions().size() == 0 && !mTermService.isWakelockEnabled()) {
File termuxTmpDir = new File(TermuxService.PREFIX_PATH + "/tmp");
if (termuxTmpDir.exists()) {
try {
TermuxInstaller.deleteFolder(termuxTmpDir);
} catch (Exception e) {
e.printStackTrace();
}
termuxTmpDir.mkdirs();
}
}
String executablePath = (failSafe ? "/system/bin/sh" : null); String executablePath = (failSafe ? "/system/bin/sh" : null);
TerminalSession newSession = mTermService.createTermSession(executablePath, null, null, failSafe); TerminalSession newSession = mTermService.createTermSession(executablePath, null, null, failSafe);
if (sessionName != null) { if (sessionName != null) {
@@ -655,6 +638,7 @@ public final class TermuxActivity extends Activity implements ServiceConnection
menu.add(Menu.NONE, CONTEXTMENU_RESET_TERMINAL_ID, Menu.NONE, R.string.reset_terminal); menu.add(Menu.NONE, CONTEXTMENU_RESET_TERMINAL_ID, Menu.NONE, R.string.reset_terminal);
menu.add(Menu.NONE, CONTEXTMENU_KILL_PROCESS_ID, Menu.NONE, getResources().getString(R.string.kill_process, getCurrentTermSession().getPid())).setEnabled(currentSession.isRunning()); menu.add(Menu.NONE, CONTEXTMENU_KILL_PROCESS_ID, Menu.NONE, getResources().getString(R.string.kill_process, getCurrentTermSession().getPid())).setEnabled(currentSession.isRunning());
menu.add(Menu.NONE, CONTEXTMENU_STYLING_ID, Menu.NONE, R.string.style_terminal); menu.add(Menu.NONE, CONTEXTMENU_STYLING_ID, Menu.NONE, R.string.style_terminal);
menu.add(Menu.NONE, CONTEXTMENU_TOGGLE_KEEP_SCREEN_ON, Menu.NONE, R.string.toggle_keep_screen_on).setCheckable(true).setChecked(mSettings.isScreenAlwaysOn());
menu.add(Menu.NONE, CONTEXTMENU_HELP_ID, Menu.NONE, R.string.help); menu.add(Menu.NONE, CONTEXTMENU_HELP_ID, Menu.NONE, R.string.help);
} }
@@ -669,7 +653,7 @@ public final class TermuxActivity extends Activity implements ServiceConnection
// Pattern for recognizing a URL, based off RFC 3986 // Pattern for recognizing a URL, based off RFC 3986
// http://stackoverflow.com/questions/5713558/detect-and-extract-url-from-a-string // http://stackoverflow.com/questions/5713558/detect-and-extract-url-from-a-string
final Pattern urlPattern = Pattern.compile( 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); Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
LinkedHashSet<CharSequence> urlSet = new LinkedHashSet<>(); LinkedHashSet<CharSequence> urlSet = new LinkedHashSet<>();
Matcher matcher = urlPattern.matcher(text); Matcher matcher = urlPattern.matcher(text);
@@ -690,28 +674,21 @@ public final class TermuxActivity extends Activity implements ServiceConnection
return; return;
} }
final CharSequence[] urls = urlSet.toArray(new CharSequence[urlSet.size()]); final CharSequence[] urls = urlSet.toArray(new CharSequence[0]);
Collections.reverse(Arrays.asList(urls)); // Latest first. Collections.reverse(Arrays.asList(urls)); // Latest first.
// Click to copy url to clipboard: // Click to copy url to clipboard:
final AlertDialog dialog = new AlertDialog.Builder(TermuxActivity.this).setItems(urls, new DialogInterface.OnClickListener() { final AlertDialog dialog = new AlertDialog.Builder(TermuxActivity.this).setItems(urls, (di, which) -> {
@Override
public void onClick(DialogInterface di, int which) {
String url = (String) urls[which]; String url = (String) urls[which];
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
clipboard.setPrimaryClip(new ClipData(null, new String[]{"text/plain"}, new ClipData.Item(url))); 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(); Toast.makeText(TermuxActivity.this, R.string.select_url_copied_to_clipboard, Toast.LENGTH_LONG).show();
}
}).setTitle(R.string.select_url_dialog_title).create(); }).setTitle(R.string.select_url_dialog_title).create();
// Long press to open URL: // Long press to open URL:
dialog.setOnShowListener(new OnShowListener() { dialog.setOnShowListener(di -> {
@Override
public void onShow(DialogInterface di) {
ListView lv = dialog.getListView(); // this is a ListView with your "buds" in it ListView lv = dialog.getListView(); // this is a ListView with your "buds" in it
lv.setOnItemLongClickListener(new OnItemLongClickListener() { lv.setOnItemLongClickListener((parent, view, position, id) -> {
@Override
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
dialog.dismiss(); dialog.dismiss();
String url = (String) urls[position]; String url = (String) urls[position];
Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
@@ -722,9 +699,7 @@ public final class TermuxActivity extends Activity implements ServiceConnection
startActivity(Intent.createChooser(i, null)); startActivity(Intent.createChooser(i, null));
} }
return true; return true;
}
}); });
}
}); });
dialog.show(); dialog.show();
@@ -754,12 +729,9 @@ public final class TermuxActivity extends Activity implements ServiceConnection
final AlertDialog.Builder b = new AlertDialog.Builder(this); final AlertDialog.Builder b = new AlertDialog.Builder(this);
b.setIcon(android.R.drawable.ic_dialog_alert); b.setIcon(android.R.drawable.ic_dialog_alert);
b.setMessage(R.string.confirm_kill_process); b.setMessage(R.string.confirm_kill_process);
b.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { b.setPositiveButton(android.R.string.yes, (dialog, id) -> {
@Override
public void onClick(DialogInterface dialog, int id) {
dialog.dismiss(); dialog.dismiss();
getCurrentTermSession().finishIfRunning(); getCurrentTermSession().finishIfRunning();
}
}); });
b.setNegativeButton(android.R.string.no, null); b.setNegativeButton(android.R.string.no, null);
b.show(); b.show();
@@ -780,18 +752,23 @@ public final class TermuxActivity extends Activity implements ServiceConnection
// The startActivity() call is not documented to throw IllegalArgumentException. // The startActivity() call is not documented to throw IllegalArgumentException.
// However, crash reporting shows that it sometimes does, so catch it here. // However, crash reporting shows that it sometimes does, so catch it here.
new AlertDialog.Builder(this).setMessage(R.string.styling_not_installed) new AlertDialog.Builder(this).setMessage(R.string.styling_not_installed)
.setPositiveButton(R.string.styling_install, new DialogInterface.OnClickListener() { .setPositiveButton(R.string.styling_install, (dialog, which) -> startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://play.google.com/store/apps/details?id=com.termux.styling")))).setNegativeButton(android.R.string.cancel, null).show();
@Override
public void onClick(DialogInterface dialog, int which) {
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://play.google.com/store/apps/details?id=com.termux.styling")));
}
}).setNegativeButton(android.R.string.cancel, null).show();
} }
return true; return true;
} }
case CONTEXTMENU_HELP_ID: case CONTEXTMENU_HELP_ID:
startActivity(new Intent(this, TermuxHelpActivity.class)); startActivity(new Intent(this, TermuxHelpActivity.class));
return true; return true;
case CONTEXTMENU_TOGGLE_KEEP_SCREEN_ON: {
if(mTerminalView.getKeepScreenOn()) {
mTerminalView.setKeepScreenOn(false);
mSettings.setScreenAlwaysOn(this, false);
} else {
mTerminalView.setKeepScreenOn(true);
mSettings.setScreenAlwaysOn(this, true);
}
return true;
}
default: default:
return super.onContextItemSelected(item); return super.onContextItemSelected(item);
} }

View File

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

View File

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

View File

@@ -3,28 +3,49 @@ package com.termux.app;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.support.annotation.IntDef;
import android.util.Log; import android.util.Log;
import android.util.TypedValue; import android.util.TypedValue;
import android.widget.Toast; import android.widget.Toast;
import com.termux.terminal.TerminalSession; import com.termux.terminal.TerminalSession;
import org.json.JSONArray;
import org.json.JSONException;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Properties; import java.util.Properties;
import androidx.annotation.IntDef;
final class TermuxPreferences { final class TermuxPreferences {
@IntDef({BELL_VIBRATE, BELL_BEEP, BELL_IGNORE}) @IntDef({BELL_VIBRATE, BELL_BEEP, BELL_IGNORE})
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
public @interface AsciiBellBehaviour { @interface AsciiBellBehaviour {
} }
final static class KeyboardShortcut {
KeyboardShortcut(int codePoint, int shortcutAction) {
this.codePoint = codePoint;
this.shortcutAction = shortcutAction;
}
final int codePoint;
final int shortcutAction;
}
static final int SHORTCUT_ACTION_CREATE_SESSION = 1;
static final int SHORTCUT_ACTION_NEXT_SESSION = 2;
static final int SHORTCUT_ACTION_PREVIOUS_SESSION = 3;
static final int SHORTCUT_ACTION_RENAME_SESSION = 4;
static final int BELL_VIBRATE = 1; static final int BELL_VIBRATE = 1;
static final int BELL_BEEP = 2; static final int BELL_BEEP = 2;
static final int BELL_IGNORE = 3; static final int BELL_IGNORE = 3;
@@ -35,7 +56,9 @@ final class TermuxPreferences {
private static final String SHOW_EXTRA_KEYS_KEY = "show_extra_keys"; private static final String SHOW_EXTRA_KEYS_KEY = "show_extra_keys";
private static final String FONTSIZE_KEY = "fontsize"; private static final String FONTSIZE_KEY = "fontsize";
private static final String CURRENT_SESSION_KEY = "current_session"; private static final String CURRENT_SESSION_KEY = "current_session";
private static final String SCREEN_ALWAYS_ON_KEY = "screen_always_on";
private boolean mScreenAlwaysOn;
private int mFontSize; private int mFontSize;
@AsciiBellBehaviour @AsciiBellBehaviour
@@ -44,6 +67,17 @@ final class TermuxPreferences {
boolean mBackIsEscape; boolean mBackIsEscape;
boolean mShowExtraKeys; 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) { TermuxPreferences(Context context) {
reloadFromProperties(context); reloadFromProperties(context);
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
@@ -54,7 +88,8 @@ final class TermuxPreferences {
// to prevent invisible text due to zoom be mistake: // to prevent invisible text due to zoom be mistake:
MIN_FONTSIZE = (int) (4f * dipInPixels); MIN_FONTSIZE = (int) (4f * dipInPixels);
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 // http://www.google.com/design/spec/style/typography.html#typography-line-height
int defaultFontSize = Math.round(12 * dipInPixels); int defaultFontSize = Math.round(12 * dipInPixels);
@@ -66,11 +101,7 @@ final class TermuxPreferences {
} catch (NumberFormatException | ClassCastException e) { } catch (NumberFormatException | ClassCastException e) {
mFontSize = defaultFontSize; mFontSize = defaultFontSize;
} }
mFontSize = Math.max(MIN_FONTSIZE, Math.min(mFontSize, MAX_FONTSIZE)); mFontSize = clamp(mFontSize, MIN_FONTSIZE, MAX_FONTSIZE);
}
boolean isShowExtraKeys() {
return mShowExtraKeys;
} }
boolean toggleShowExtraKeys(Context context) { boolean toggleShowExtraKeys(Context context) {
@@ -91,6 +122,15 @@ final class TermuxPreferences {
prefs.edit().putString(FONTSIZE_KEY, Integer.toString(mFontSize)).apply(); prefs.edit().putString(FONTSIZE_KEY, Integer.toString(mFontSize)).apply();
} }
boolean isScreenAlwaysOn() {
return mScreenAlwaysOn;
}
void setScreenAlwaysOn(Context context, boolean newValue) {
mScreenAlwaysOn = newValue;
PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean(SCREEN_ALWAYS_ON_KEY, newValue).apply();
}
static void storeCurrentSession(Context context, TerminalSession session) { static void storeCurrentSession(Context context, TerminalSession session) {
PreferenceManager.getDefaultSharedPreferences(context).edit().putString(TermuxPreferences.CURRENT_SESSION_KEY, session.mHandle).apply(); PreferenceManager.getDefaultSharedPreferences(context).edit().putString(TermuxPreferences.CURRENT_SESSION_KEY, session.mHandle).apply();
} }
@@ -104,18 +144,22 @@ final class TermuxPreferences {
return null; return null;
} }
public void reloadFromProperties(Context context) { void reloadFromProperties(Context context) {
try {
File propsFile = new File(TermuxService.HOME_PATH + "/.termux/termux.properties"); File propsFile = new File(TermuxService.HOME_PATH + "/.termux/termux.properties");
if (!propsFile.exists()) if (!propsFile.exists())
propsFile = new File(TermuxService.HOME_PATH + "/.config/termux/termux.properties"); propsFile = new File(TermuxService.HOME_PATH + "/.config/termux/termux.properties");
Properties props = new Properties(); Properties props = new Properties();
try {
if (propsFile.isFile() && propsFile.canRead()) { if (propsFile.isFile() && propsFile.canRead()) {
try (FileInputStream in = new FileInputStream(propsFile)) { try (FileInputStream in = new FileInputStream(propsFile)) {
props.load(in); props.load(new InputStreamReader(in, StandardCharsets.UTF_8));
} }
} }
} catch (IOException e) {
Toast.makeText(context, "Could not open the propertiey file termux.properties.", Toast.LENGTH_LONG).show();
Log.e("termux", "Error loading props", e);
}
switch (props.getProperty("bell-character", "vibrate")) { switch (props.getProperty("bell-character", "vibrate")) {
case "beep": case "beep":
@@ -129,6 +173,23 @@ final class TermuxPreferences {
break; break;
} }
try {
JSONArray arr = new JSONArray(props.getProperty("extra-keys", "[['ESC', 'TAB', 'CTRL', 'ALT', '-', 'DOWN', 'UP']]"));
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")); mBackIsEscape = "escape".equals(props.getProperty("back-key", "back"));
shortcuts.clear(); shortcuts.clear();
@@ -136,29 +197,7 @@ final class TermuxPreferences {
parseAction("shortcut.next-session", SHORTCUT_ACTION_NEXT_SESSION, props); parseAction("shortcut.next-session", SHORTCUT_ACTION_NEXT_SESSION, props);
parseAction("shortcut.previous-session", SHORTCUT_ACTION_PREVIOUS_SESSION, props); parseAction("shortcut.previous-session", SHORTCUT_ACTION_PREVIOUS_SESSION, props);
parseAction("shortcut.rename-session", SHORTCUT_ACTION_RENAME_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();
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;
}
final int codePoint;
final int shortcutAction;
}
final List<KeyboardShortcut> shortcuts = new ArrayList<>();
private void parseAction(String name, int shortcutAction, Properties props) { private void parseAction(String name, int shortcutAction, Properties props) {
String value = props.getProperty(name); String value = props.getProperty(name);

View File

@@ -250,6 +250,14 @@ public final class TermuxService extends Service implements SessionChangedCallba
return mTerminalSessions; return mTerminalSessions;
} }
public boolean isWakelockEnabled() {
if (mWakeLock == null) {
return false;
} else {
return mWakeLock.isHeld();
}
}
TerminalSession createTermSession(String executablePath, String[] arguments, String cwd, boolean failSafe) { TerminalSession createTermSession(String executablePath, String[] arguments, String cwd, boolean failSafe) {
new File(HOME_PATH).mkdirs(); new File(HOME_PATH).mkdirs();
@@ -335,12 +343,9 @@ public final class TermuxService extends Service implements SessionChangedCallba
} }
public void onBackgroundJobExited(final BackgroundJob task) { public void onBackgroundJobExited(final BackgroundJob task) {
mHandler.post(new Runnable() { mHandler.post(() -> {
@Override
public void run() {
mBackgroundTasks.remove(task); mBackgroundTasks.remove(task);
updateNotification(); updateNotification();
}
}); });
} }

View File

@@ -2,7 +2,6 @@ package com.termux.app;
import android.content.Context; import android.content.Context;
import android.media.AudioManager; import android.media.AudioManager;
import android.support.v4.widget.DrawerLayout;
import android.view.Gravity; import android.view.Gravity;
import android.view.InputDevice; import android.view.InputDevice;
import android.view.KeyEvent; import android.view.KeyEvent;
@@ -16,6 +15,8 @@ import com.termux.view.TerminalViewClient;
import java.util.List; import java.util.List;
import androidx.drawerlayout.widget.DrawerLayout;
public final class TermuxViewClient implements TerminalViewClient { public final class TermuxViewClient implements TerminalViewClient {
final TermuxActivity mActivity; final TermuxActivity mActivity;
@@ -112,12 +113,12 @@ public final class TermuxViewClient implements TerminalViewClient {
@Override @Override
public boolean readControlKey() { public boolean readControlKey() {
return (mActivity.mExtraKeysView != null && mActivity.mExtraKeysView.readControlButton()) || mVirtualControlKeyDown; return (mActivity.mExtraKeysView != null && mActivity.mExtraKeysView.readSpecialButton(ExtraKeysView.SpecialButton.CTRL)) || mVirtualControlKeyDown;
} }
@Override @Override
public boolean readAltKey() { public boolean readAltKey() {
return (mActivity.mExtraKeysView != null && mActivity.mExtraKeysView.readAltButton()); return (mActivity.mExtraKeysView != null && mActivity.mExtraKeysView.readSpecialButton(ExtraKeysView.SpecialButton.ALT));
} }
@Override @Override
@@ -209,6 +210,7 @@ public final class TermuxViewClient implements TerminalViewClient {
// Writing mode: // Writing mode:
case 'q': case 'q':
case 'k':
mActivity.toggleShowExtraKeys(); mActivity.toggleShowExtraKeys();
break; break;
} }

View File

@@ -75,7 +75,7 @@ public class TermuxDocumentsProvider extends DocumentsProvider {
row.add(Root.COLUMN_TITLE, applicationName); row.add(Root.COLUMN_TITLE, applicationName);
row.add(Root.COLUMN_MIME_TYPES, ALL_MIME_TYPES); row.add(Root.COLUMN_MIME_TYPES, ALL_MIME_TYPES);
row.add(Root.COLUMN_AVAILABLE_BYTES, BASE_DIR.getFreeSpace()); row.add(Root.COLUMN_AVAILABLE_BYTES, BASE_DIR.getFreeSpace());
row.add(Root.COLUMN_ICON, R.mipmap.ic_launcher); row.add(Root.COLUMN_ICON, R.drawable.ic_launcher);
return result; return result;
} }
@@ -236,7 +236,7 @@ public class TermuxDocumentsProvider extends DocumentsProvider {
row.add(Document.COLUMN_MIME_TYPE, mimeType); row.add(Document.COLUMN_MIME_TYPE, mimeType);
row.add(Document.COLUMN_LAST_MODIFIED, file.lastModified()); row.add(Document.COLUMN_LAST_MODIFIED, file.lastModified());
row.add(Document.COLUMN_FLAGS, flags); row.add(Document.COLUMN_FLAGS, flags);
row.add(Document.COLUMN_ICON, R.mipmap.ic_launcher); row.add(Document.COLUMN_ICON, R.drawable.ic_launcher);
} }
} }

View File

@@ -2,7 +2,6 @@ package com.termux.filepicker;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.database.Cursor; import android.database.Cursor;
import android.net.Uri; import android.net.Uri;
@@ -83,17 +82,7 @@ public class TermuxFileReceiverActivity extends Activity {
void showErrorDialogAndQuit(String message) { void showErrorDialogAndQuit(String message) {
mFinishOnDismissNameDialog = false; mFinishOnDismissNameDialog = false;
new AlertDialog.Builder(this).setMessage(message).setOnDismissListener(new DialogInterface.OnDismissListener() { new AlertDialog.Builder(this).setMessage(message).setOnDismissListener(dialog -> finish()).setPositiveButton(android.R.string.ok, (dialog, which) -> finish()).show();
@Override
public void onDismiss(DialogInterface dialog) {
finish();
}
}).setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
finish();
}
}).show();
} }
void handleContentUri(final Uri uri, String subjectFromIntent) { void handleContentUri(final Uri uri, String subjectFromIntent) {
@@ -119,9 +108,7 @@ public class TermuxFileReceiverActivity extends Activity {
} }
void promptNameAndSave(final InputStream in, final String attachmentFileName) { void promptNameAndSave(final InputStream in, final String attachmentFileName) {
DialogUtils.textInput(this, R.string.file_received_title, attachmentFileName, R.string.file_received_edit_button, new DialogUtils.TextSetListener() { DialogUtils.textInput(this, R.string.file_received_title, attachmentFileName, R.string.file_received_edit_button, text -> {
@Override
public void onTextSet(String text) {
File outFile = saveStreamWithName(in, text); File outFile = saveStreamWithName(in, text);
if (outFile == null) return; if (outFile == null) return;
@@ -143,11 +130,8 @@ public class TermuxFileReceiverActivity extends Activity {
executeIntent.putExtra(TermuxService.EXTRA_ARGUMENTS, new String[]{outFile.getAbsolutePath()}); executeIntent.putExtra(TermuxService.EXTRA_ARGUMENTS, new String[]{outFile.getAbsolutePath()});
startService(executeIntent); startService(executeIntent);
finish(); finish();
}
}, },
R.string.file_received_open_folder_button, new DialogUtils.TextSetListener() { R.string.file_received_open_folder_button, text -> {
@Override
public void onTextSet(String text) {
if (saveStreamWithName(in, text) == null) return; if (saveStreamWithName(in, text) == null) return;
Intent executeIntent = new Intent(TermuxService.ACTION_EXECUTE); Intent executeIntent = new Intent(TermuxService.ACTION_EXECUTE);
@@ -155,18 +139,9 @@ public class TermuxFileReceiverActivity extends Activity {
executeIntent.setClass(TermuxFileReceiverActivity.this, TermuxService.class); executeIntent.setClass(TermuxFileReceiverActivity.this, TermuxService.class);
startService(executeIntent); startService(executeIntent);
finish(); finish();
}
}, },
android.R.string.cancel, new DialogUtils.TextSetListener() { android.R.string.cancel, text -> finish(), dialog -> {
@Override
public void onTextSet(final String text) {
finish();
}
}, new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
if (mFinishOnDismissNameDialog) finish(); 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

@@ -4,7 +4,7 @@
android:orientation="vertical" android:orientation="vertical"
android:fitsSystemWindows="true"> android:fitsSystemWindows="true">
<android.support.v4.widget.DrawerLayout <androidx.drawerlayout.widget.DrawerLayout
android:id="@+id/drawer_layout" android:id="@+id/drawer_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_alignParentTop="true" android:layout_alignParentTop="true"
@@ -15,6 +15,8 @@
android:id="@+id/terminal_view" android:id="@+id/terminal_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_marginRight="3dp"
android:layout_marginLeft="3dp"
android:focusableInTouchMode="true" android:focusableInTouchMode="true"
android:scrollbarThumbVertical="@drawable/terminal_scroll_shape" android:scrollbarThumbVertical="@drawable/terminal_scroll_shape"
android:scrollbars="vertical" /> android:scrollbars="vertical" />
@@ -64,13 +66,13 @@
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
</android.support.v4.widget.DrawerLayout> </androidx.drawerlayout.widget.DrawerLayout>
<android.support.v4.view.ViewPager <androidx.viewpager.widget.ViewPager
android:id="@+id/viewpager" android:id="@+id/viewpager"
android:visibility="gone" android:visibility="gone"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="75dp" android:layout_height="37.5dp"
android:background="@android:drawable/screen_background_dark_transparent" android:background="@android:drawable/screen_background_dark_transparent"
android:layout_alignParentBottom="true" /> android:layout_alignParentBottom="true" />
</RelativeLayout> </RelativeLayout>

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

@@ -9,6 +9,7 @@
<string name="style_terminal">Style</string> <string name="style_terminal">Style</string>
<string name="share_transcript_title">Terminal transcript</string> <string name="share_transcript_title">Terminal transcript</string>
<string name="help">Help</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_installer_body">Installing…</string>
<string name="bootstrap_error_title">Unable to install</string> <string name="bootstrap_error_title">Unable to install</string>

View File

@@ -1,20 +1,13 @@
#!/bin/sh #!/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 for APP in api boot styling tasker widget; do
APPDIR=../../termux-$APP APPDIR=../../termux-$APP
if [ -d $APPDIR ]; then for file in ic_foreground ic_launcher; do
APP_FOLDER=$APPDIR/app/src/main/res/mipmap-$DENSITY cp ../app/src/main/res/drawable/$file.xml \
mkdir -p $APP_FOLDER $APPDIR/app/src/main/res/drawable/$file.xml
cp $PNG $APP_FOLDER/$FILE.png
fi
done
done done
cp ../app/src/main/res/drawable-anydpi-v26/ic_launcher.xml \
$APPDIR/app/src/main/res/drawable-anydpi-v26/$file.xml
done done

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,7 +4,7 @@ buildscript {
google() google()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:3.1.3' classpath 'com.android.tools.build:gradle:3.3.0'
} }
} }

Binary file not shown.

View File

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

View File

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

View File

@@ -57,7 +57,7 @@ static int create_subprocess(JNIEnv* env,
tcsetattr(ptm, TCSANOW, &tios); tcsetattr(ptm, TCSANOW, &tios);
/** Set initial winsize. */ /** 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); ioctl(ptm, TIOCSWINSZ, &sz);
pid_t pid = fork(); 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) 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); 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; int status;
waitpid(pid, &status, 0); waitpid(pid, &status, 0);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -15,6 +15,7 @@ import android.text.InputType;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.Log; import android.util.Log;
import android.view.accessibility.AccessibilityManager;
import android.view.ActionMode; import android.view.ActionMode;
import android.view.HapticFeedbackConstants; import android.view.HapticFeedbackConstants;
import android.view.InputDevice; 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. */ /** If non-zero, this is the last unicode code point received if that was a combining character. */
int mCombiningAccent; int mCombiningAccent;
private boolean mAccessibilityEnabled;
public TerminalView(Context context, AttributeSet attributes) { // NO_UCD (unused code) public TerminalView(Context context, AttributeSet attributes) { // NO_UCD (unused code)
super(context, attributes); super(context, attributes);
mGestureRecognizer = new GestureAndScaleRecognizer(context, new GestureAndScaleRecognizer.Listener() { mGestureRecognizer = new GestureAndScaleRecognizer(context, new GestureAndScaleRecognizer.Listener() {
@@ -197,6 +200,8 @@ public final class TerminalView extends View {
} }
}); });
mScroller = new Scroller(context); mScroller = new Scroller(context);
AccessibilityManager am = (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
mAccessibilityEnabled = am.isEnabled();
} }
/** /**
@@ -384,6 +389,7 @@ public final class TerminalView extends View {
mEmulator.clearScrollCounter(); mEmulator.clearScrollCounter();
invalidate(); invalidate();
if (mAccessibilityEnabled) setContentDescription(getText());
} }
/** /**
@@ -594,7 +600,7 @@ public final class TerminalView extends View {
if (controlDownFromEvent) keyMod |= KeyHandler.KEYMOD_CTRL; if (controlDownFromEvent) keyMod |= KeyHandler.KEYMOD_CTRL;
if (event.isAltPressed()) keyMod |= KeyHandler.KEYMOD_ALT; if (event.isAltPressed()) keyMod |= KeyHandler.KEYMOD_ALT;
if (event.isShiftPressed()) keyMod |= KeyHandler.KEYMOD_SHIFT; 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"); if (LOG_KEY_EVENTS) Log.i(EmulatorDebug.LOG_TAG, "handleKeyCode() took key event");
return true; return true;
} }
@@ -613,7 +619,7 @@ public final class TerminalView extends View {
if (LOG_KEY_EVENTS) if (LOG_KEY_EVENTS)
Log.i(EmulatorDebug.LOG_TAG, "KeyEvent#getUnicodeChar(" + effectiveMetaState + ") returned: " + result); Log.i(EmulatorDebug.LOG_TAG, "KeyEvent#getUnicodeChar(" + effectiveMetaState + ") returned: " + result);
if (result == 0) { if (result == 0) {
return true; return false;
} }
int oldCombiningAccent = mCombiningAccent; int oldCombiningAccent = mCombiningAccent;
@@ -915,4 +921,8 @@ public final class TerminalView extends View {
return mTermSession; return mTermSession;
} }
private CharSequence getText() {
return mEmulator.getScreen().getSelectedText(0, mTopRow, mEmulator.mColumns, mTopRow +mEmulator.mRows);
}
} }