Compare commits

...

57 Commits
v0.18 ... v0.23

Author SHA1 Message Date
Fredrik Fornwall
c3aa9d9662 Updated storage handling
Let the user run termux-setup-storage, which will ensure that storage
permission has been granted and setup $HOME/storage/ folder with
symlinks to storage folders.
2015-12-28 01:37:00 +01:00
Fredrik Fornwall
47634ca237 Update AS configuration 2015-12-28 01:17:40 +01:00
Fredrik Fornwall
d9ec1bf40b Add .idea/dictionaries/ to .gitignore 2015-12-28 00:41:21 +01:00
Fredrik Fornwall
bc1b742a36 Remove bundled help in favour of online help 2015-12-28 00:40:51 +01:00
Fredrik Fornwall
86e2945069 Make it possible to reload settings at runtime 2015-12-27 15:35:03 +01:00
Fredrik Fornwall
961e06379b Avoid prompting for storage permission for now 2015-12-27 13:46:20 +01:00
Fredrik Fornwall
468185efb3 Transition to https://termux.net for bootstrap
The initial bootstrap zip was previously downloaded from
http://apt.termux.com, which lacked security and was not behind a CDN.

By moving to https://termux.net we improve security (as it's https)
and reliability (as it's using a CDN).

Fixes https://github.com/termux/termux-packages/issues/89.
2015-12-27 08:17:29 +01:00
Fredrik Fornwall
e006e36dd0 Do not eat escape key events in onKeyPreIme()
The original reason for intercepting the escape key in onKeyPreIme()
was to prevent the escape key as being treated as the Back key before
reaching onKeyDown().

This seems no long necessary, and may mess up handling the ...+1
combination for escape on the Google Pixel C keyboard (see #27).
2015-12-25 21:10:48 +01:00
Fredrik Fornwall
dd38965c46 Use $PREFIX/storage for symlinks. Rix null check. 2015-12-24 09:29:14 +01:00
Fredrik Fornwall
79d56b778d Update gradle to 2.10 2015-12-23 01:44:51 +01:00
Fredrik Fornwall
2326c52199 Implement support for termux.properties
Also some symlink-to-storage improvements and experimenting with
requesting read storage permission.
2015-12-23 01:43:41 +01:00
Fredrik Fornwall
f907684ef2 Read support for customizing through properties
By using the file $HOME/.config/termux/termux.properties it will
be possible to configure the behaviour of:

- The bell character.
- Tapping the terminal.
- The back key.
2015-12-23 01:39:49 +01:00
Fredrik Fornwall
9d37461ac7 Fix lint warnings 2015-12-23 01:12:47 +01:00
Fredrik Fornwall
4de0f98fa4 Fix lint warning 2015-12-23 01:08:23 +01:00
Fredrik Fornwall
f153a72592 TerminalView: Make theming work for non-activities 2015-12-20 14:37:00 +01:00
Fredrik Fornwall
b0f4efb0bc Bump version to 0.23 2015-12-20 14:26:08 +01:00
Fredrik Fornwall
5c03c2d77e Change theme for file picker 2015-12-20 14:25:07 +01:00
Fredrik Fornwall
bce65f7db1 Setup $HOME/storage symlink for external symlink
At startup Termux now checks if there is external storage available,
and creates a private area on the external storage if one exists as
well as creating a symlink to the private are at $HOME/storage.
2015-12-12 03:02:06 +01:00
Fredrik Fornwall
e18579164f Bump version number to 0.22 2015-12-12 00:10:43 +01:00
Fredrik Fornwall
16273a1981 Fix bug with scrolling down and top scroll margin
The Termux implementation of the ${CSI}${N}T escape sequence to scroll
down N lines (SD - Pan Up) did not take the top margin into account
when figuring out where to place the scrolled rows.

Fixes #28.
2015-12-12 00:07:55 +01:00
Fredrik Fornwall
ce82979e2b Update idea codeStyleSettings.xml 2015-12-05 20:16:20 +01:00
Fredrik Fornwall
625aeab398 Reformat code 2015-11-30 00:39:24 +01:00
Fredrik Fornwall
bad6712338 Improve motion event handling after long press
When mouse reporting is enabled, do not send mouse events on up
after a long press, since that causes e.g. the cursor to move in
vim when lifting the finger after long pressing for the menu.
2015-11-29 10:56:31 +01:00
Fredrik Fornwall
b54c7909bd Fix crash with wide character in last column
Ignore wide character outputs instead of crashing when the cursor
is in the last column with autowrap disabled.
2015-11-29 10:04:50 +01:00
Fredrik Fornwall
4ccc703fcf Remove dead code 2015-11-29 10:03:38 +01:00
Fredrik Fornwall
7348820caf Add screen wraparound test 2015-11-29 09:36:06 +01:00
Fredrik Fornwall
525985b1f2 Remove some dead code 2015-11-29 08:56:56 +01:00
Fredrik Fornwall
ab3852d2e4 Add missing null guard for no current session 2015-11-29 08:55:13 +01:00
Fredrik Fornwall
9928073e48 Fix bitwise operation issue 2015-11-29 02:20:37 +01:00
Fredrik Fornwall
3091da64bc Update travis config 2015-11-29 02:00:55 +01:00
Fredrik Fornwall
74dca95101 Fix inspect code warnings 2015-11-29 01:55:41 +01:00
Fredrik Fornwall
2a6a3b76b7 Update travis config 2015-11-29 01:45:11 +01:00
Fredrik Fornwall
0e4ea95d74 Revert to gradle-bin to check travis build failure 2015-11-29 01:27:13 +01:00
Fredrik Fornwall
7389dbb56f Fix two Android Studio inspect code warnings 2015-11-29 01:12:27 +01:00
Fredrik Fornwall
d982c71efe Url-regexp: Remove redundant escapes and add test 2015-11-29 01:08:18 +01:00
Fredrik Fornwall
1b36c684d6 Add a customized auto backup
Starting with Android 6.0 the system may automatically backup app
data when a users installs an app on a new device or reinstalls an
app on one. After this commit this only affects the $HOME/backup/
folder, so that the user may choose what to backup.

See https://developer.android.com/training/backup/autosyncapi.html
2015-11-29 00:55:05 +01:00
Fredrik Fornwall
a6a83b1fcd Tweak file picker layout 2015-11-29 00:42:47 +01:00
Fredrik Fornwall
d6f01bfe9a Update gradle config and add Support Annotations 2015-11-29 00:25:51 +01:00
Fredrik Fornwall
271dd7dcee Specify Android Studio "Inspect Code" profile 2015-11-29 00:23:18 +01:00
Fredrik Fornwall
95fbb810e2 Code simplifications 2015-11-29 00:20:45 +01:00
Fredrik Fornwall
1aa439311b Remove some warnings 2015-11-29 00:17:50 +01:00
Fredrik Fornwall
12ddaccaf7 Extract hardcoded string to resource 2015-11-29 00:09:25 +01:00
Fredrik Fornwall
09fe7e5941 Use the gradle source distribution for the wrapper 2015-11-29 00:08:07 +01:00
Fredrik Fornwall
36cc010a87 Show bold text in bright colors
Fixes 17. Could be made an option in the future if necessary.
2015-11-24 17:31:48 +01:00
Fredrik Fornwall
b1aaf5abe5 Add *.apk to .gitignore 2015-11-24 17:31:32 +01:00
Fredrik Fornwall
9b3dc57447 Remove junk from app/build.gradle 2015-11-20 23:29:44 +01:00
Fredrik Fornwall
f7ce206212 Bump version to 0.20 2015-11-20 00:40:52 +01:00
Fredrik Fornwall
0deacd8fc6 Show "-press Enter to close" at session exit
This makes it more clear how to close the session after finishing.
Fixes #15.
2015-11-20 00:38:58 +01:00
Fredrik Fornwall
65cfcffa6f Update to gradle 2.9 2015-11-17 21:53:40 +01:00
Fredrik Fornwall
463b927813 Bump version to 0.19 2015-11-13 00:22:18 +01:00
Fredrik Fornwall
09ecd14764 Fix crash when using some unicode characters
We should never try to look at characters in a row after those
actually used.
2015-11-13 00:14:35 +01:00
Fredrik Fornwall
89912be500 Fix crash on installation if activity destroyed
If the activity had been destroyed waiting for the installation to
finish the code would crash trying to display welcome dialogs (on
success) or error dialogs (on error).
2015-11-13 00:10:21 +01:00
Fredrik Fornwall
3b4e3b0e42 Update to Android Gradle Plugin version 1.5.0 2015-11-12 22:16:09 +01:00
Fredrik Fornwall
7a726c035c Clarify Apache License version (fixes #10) 2015-11-07 23:09:52 +01:00
Fredrik Fornwall
430a98e9ad Merge pull request #11 from RyDroid/minor
Minor changes in README and adding an EditorConfig file
2015-11-07 23:06:50 +01:00
Nicola Spanti (RyDroid)
01de6b4d18 Minor changes in README and adding an EditorConfig file 2015-11-07 19:39:13 +01:00
Fredrik Fornwall
1652c1dcf3 Update gradle wrapper to 2.8 2015-11-06 00:05:32 +01:00
43 changed files with 772 additions and 448 deletions

14
.editorconfig Normal file
View File

@@ -0,0 +1,14 @@
# Copying and distribution of this file, with or without modification,
# are permitted in any medium without royalty provided this notice is
# preserved. This file is offered as-is, without any warranty.
# EditorConfig
# http://EditorConfig.org
# top-most EditorConfig file
root = true
[*]
end_of_line = lf
insert_final_newline = true
charset = utf-8

2
.gitignore vendored
View File

@@ -4,6 +4,7 @@
# Built application files
build/
*.apk
# Crashlytics configuations
com_crashlytics_export_strings.xml
@@ -29,6 +30,7 @@ local.properties
.idea/modules.xml
.idea/scopes/scope_settings.xml
.idea/vcs.xml
.idea/dictionaries/
*.iml
# OS-specific files

229
.idea/codeStyleSettings.xml generated Normal file
View File

@@ -0,0 +1,229 @@
<?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>Namespace:</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:.*</NAME>
<XML_NAMESPACE>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="TermuxCodeStyle" />
</component>
</project>

1
.idea/gradle.xml generated
View File

@@ -3,6 +3,7 @@
<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="gradleJvm" value="1.8" />

View File

@@ -0,0 +1,15 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="AndroidLintGoogleAppIndexingWarning" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="EmptyStatementBody" enabled="true" level="WARNING" enabled_by_default="true">
<option name="m_reportEmptyBlocks" value="true" />
<option name="commentsAreContent" value="true" />
</inspection_tool>
<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>
</profile>
</component>

View File

@@ -0,0 +1,7 @@
<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

@@ -11,9 +11,9 @@ android:
components:
- platform-tools
- tools
- build-tools-23.0.1
- build-tools-23.0.2
- android-23
- sys-img-x86-android-23
- extra-android-m2repository
script:
- ./gradlew testDebugUnitTest

View File

@@ -5,7 +5,8 @@ Termux app
Termux is an Android terminal app and Linux environment.
* [Termux on Google Play](http://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.com](http://termux.com)
* [Termux Help](http://termux.com/help/)
* [Termux app on GitHub](https://github.com/termux/termux-app)
@@ -14,7 +15,7 @@ Termux is an Android terminal app and Linux environment.
License
=======
Released under the GPLv3 license. Contains code from `Terminal Emulator for Android` which is released under the Apache License.
Released under [the GPLv3 license](https://www.gnu.org/licenses/gpl.html). Contains code from `Terminal Emulator for Android` which is released under [the Apache License 2.0](https://www.apache.org/licenses/).
Building JNI libraries
======================
@@ -29,9 +30,9 @@ Terminal resources
Terminal emulators
==================
* VTE (libvte): Terminal emulator widget for GTK+, mainly used in gnome-terminal. [Source](https://github.com/GNOME/vte), [Open Issues](https://bugzilla.gnome.org/buglist.cgi?quicksearch=product%3A%22vte%22+), and [All (including closed) issues](https://bugzilla.gnome.org/buglist.cgi?bug_status=RESOLVED&bug_status=VERIFIED&chfield=resolution&chfieldfrom=-2000d&chfieldvalue=FIXED&product=vte&resolution=FIXED).
* iTerm 2: Mac terminal application. [Source](https://github.com/gnachman/iTerm2), [Issues](https://gitlab.com/gnachman/iterm2/issues) and [Documentation](http://www.iterm2.com/documentation.html) (which includes [iTerm2 proprietary escape codes](http://www.iterm2.com/documentation-escape-codes.html)).
* iTerm 2: OS X terminal application. [Source](https://github.com/gnachman/iTerm2), [Issues](https://gitlab.com/gnachman/iterm2/issues) and [Documentation](http://www.iterm2.com/documentation.html) (which includes [iTerm2 proprietary escape codes](http://www.iterm2.com/documentation-escape-codes.html)).
* Konsole: KDE terminal application. [Source](https://projects.kde.org/projects/kde/applications/konsole/repository), in particular [tests](https://projects.kde.org/projects/kde/applications/konsole/repository/revisions/master/show/tests), [Bugs](https://bugs.kde.org/buglist.cgi?bug_severity=critical&bug_severity=grave&bug_severity=major&bug_severity=crash&bug_severity=normal&bug_severity=minor&bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&product=konsole) and [Wishes](https://bugs.kde.org/buglist.cgi?bug_severity=wishlist&bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&product=konsole).
* hterm: Javascript terminal implementation from chromium. [Source](https://github.com/chromium/hterm), including [tests](https://github.com/chromium/hterm/blob/master/js/hterm_vt_tests.js), and [google group](https://groups.google.com/a/chromium.org/forum/#!forum/chromium-hterm).
* hterm: JavaScript terminal implementation from Chromium. [Source](https://github.com/chromium/hterm), including [tests](https://github.com/chromium/hterm/blob/master/js/hterm_vt_tests.js), and [Google group](https://groups.google.com/a/chromium.org/forum/#!forum/chromium-hterm).
* xterm: The grandfather of terminal emulators. [Source](http://invisible-island.net/datafiles/release/xterm.tar.gz).
* Connectbot: Android SSH client. [Source](https://github.com/connectbot/connectbot)
* Android Terminal Emulator: Android terminal app which Termux terminal handling is based on. Inactive. [Source](https://github.com/jackpal/Android-Terminal-Emulator).

View File

@@ -2,7 +2,11 @@ apply plugin: 'com.android.application'
android {
compileSdkVersion 23
buildToolsVersion "23.0.1"
buildToolsVersion "23.0.2"
dependencies {
compile 'com.android.support:support-annotations:23.1.1'
}
sourceSets {
main {
@@ -13,32 +17,15 @@ android {
defaultConfig {
applicationId "com.termux"
minSdkVersion 21
targetSdkVersion 22
versionCode 18
versionName "0.18"
}
signingConfigs {
release {
if (System.getenv("TRAVIS")) {
storeFile rootProject.file('travis.keystore')
storePassword 'abcdef'
keyAlias 'travis'
keyPassword 'abcdef'
} else {
storeFile new File(TERMUX_KEYSTORE_FILE)
storePassword TERMUX_KEYSTORE_PASSWORD
keyAlias TERMUX_KEYSTORE_ALIAS
keyPassword TERMUX_KEYSTORE_PASSWORD
}
}
targetSdkVersion 23
versionCode 23
versionName "0.23"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release
}
}
}

View File

@@ -14,6 +14,7 @@
<application
android:allowBackup="true"
android:fullBackupContent="@xml/backupscheme"
android:icon="@drawable/ic_launcher"
android:banner="@drawable/banner"
android:label="@string/application_name"
@@ -38,12 +39,14 @@
<activity
android:name="com.termux.app.TermuxHelpActivity"
android:exported="false"
android:label="@string/application_help" />
android:theme="@android:style/Theme.Material.Light.DarkActionBar"
android:parentActivityName=".app.TermuxActivity"
android:label="@string/application_name" />
<activity
android:name="com.termux.filepicker.TermuxFilePickerActivity"
android:label="@string/application_name"
android:theme="@android:style/Theme.Material"
android:theme="@android:style/Theme.Material.Light.DarkActionBar"
android:noHistory="true">
<intent-filter>
<!--

View File

@@ -1,229 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, user-scalable=no" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>Termux Help</title>
<style>
html { font-family: 'sans-serif-light', sans-serif; height: 100%; margin: auto; padding: 0; color: black; background-color: white; }
.page { max-width: 820px; margin: auto; padding: 0 1em; }
body { margin-left: auto; margin-right: auto; margin-top: 0; padding: 0; width: 100%; }
p { font-size: 16px; line-height: 1.3em; }
ul.index { padding-left: 0; }
.index li { list-style-type: none; line-height: 1.8em; }
dt { margin-left: 1em; list-style-type: bullet; }
a, a:visited { color: #0000EE }
</style>
</head>
<body>
<div class="page help">
<h1 id="index">Termux Help</h1>
<ul class="index">
<li><a href="#introduction">Introduction</a></li>
<li><a href="#user_interface">User interface</a></li>
<li><a href="#touch_keyboard">Using a touch keyboard</a></li>
<li><a href="#hardware_keyboard">Using a hardware keyboard</a></li>
<li><a href="#package_management">Package management</a></li>
<li><a href="#text_editing">Text editing</a></li>
<li><a href="#using_ssh">Using SSH</a></li>
<li><a href="#interactive_shells">Interactive shells</a></li>
<li><a href="#termux_android">Termux and Android</a></li>
<li><a href="#add_on_api">Add-on: API</a></li>
<li><a href="#add_on_float">Add-on: Float</a></li>
<li><a href="#add_on_styling">Add-on: Styling</a></li>
<li><a href="#add_on_widget">Add-on: Widget</a></li>
<li><a href="#source_and_licenses">Source and licenses</a>
</ul>
<h2 id="introduction">Introduction</h2>
<p>Termux is a terminal emulator for Android combined with a collection of packages for command line software. This help
explains both the terminal interface and the packaging tool available from inside the terminal.</p>
<p>Want to ask a question, report a bug or have an idea for a new package or feature?
Visit the <a href="https://plus.google.com/communities/101692629528551299417">Google+ Termux Community</a>!</p>
<h2 id="user_interface">User interface</h2>
<p>At launch Termux shows a terminal interface, whose text size can be adjusted by pinch zooming or double tapping
and pulling the content towards or from you.</p>
<p>Besides the terminal (with keyboard shortcuts explained below) there are three additional interface elements available:
A <strong>context menu</strong>, <strong>navigation drawer</strong>
and <strong>notification</strong>.</p>
<p>The <strong>context menu</strong> can be shown by long pressing anywhere on the terminal. It provides menu entries for:</p>
<ul>
<li>Selecting and pasting text.</li>
<li>Sharing text from the terminal to other apps (e.g. email or SMS)</li>
<li>Resetting the terminal if it gets stuck.</li>
<li>Switching the terminal to full-screen.</li>
<li>Hangup (exiting the current terminal session).</li>
<li>Styling the terminal by selecting a font and a color scheme.</li>
<li>Showing this help page.</li>
</ul>
<p>The <strong>navigation drawer</strong> is revealed by swiping from the left part of the screen. It has three
elements:</p>
<ul>
<li>A list of sessions. Clicking on a session shows it in the terminal while long pressing allows you to specify a session title.</li>
<li>A button to toggle visibility of the touch keyboard.</li>
<li>A button to create new terminal sessions (long press for creating a named session or a fail-safe one).</li>
</ul>
<p>The <strong>notification</strong>, available when a terminal session is running, is available by pulling down the notification menu.
Pressing the notification leads to the most current terminal session. The notification may also be expanded
(by pinch-zooming or performing a single-finger glide) to expose three actions:</p>
<ul>
<li>Exiting all running terminal sessions.</li>
<li>Use a wake lock to avoid entering sleep mode.</li>
<li>Use a high performance wifi lock to maximize wifi performance.</li>
</ul>
<p>With a wake or wifi lock held the notification and Termux background processes will be available even if no terminal
session is running, which allows server and other background processes to run more reliably.</p>
<h2 id="touch_keyboard">Using a touch keyboard</h2>
<p>Using the Ctrl key is necessary for working with a terminal - but most touch keyboards
does not include one. For that purpose Termux uses the <em>Volume down</em> button to emulate
the Ctrl key. For example, pressing <em>Volume down+L</em> on a touch keyboard sends the same input as
pressing <em>Ctrl+L</em> on a hardware keyboard. The result of using Ctrl in combination
with a key depends on which program is used, but for many command line tools the following
shortcuts works:</p>
<ul>
<li>Ctrl+A → Move cursor to the beginning of line.</li>
<li>Ctrl+C → Abort (send SIGINT to) current process.</li>
<li>Ctrl+D → Logout of a terminal session.</li>
<li>Ctrl+E → Move cursor to the end of line.</li>
<li>Ctrl+K → Delete from cursor to the end of line.</li>
<li>Ctrl+L → Clear the terminal.</li>
<li>Ctrl+Z → Suspend (send SIGTSTP to) current process.</li>
</ul>
<p>The <em>Volume up</em> key also serves as a special key to produce certain input:</p>
<ul>
<li>Volume Up+L → | (the pipe character).</li>
<li>Volume Up+E → Escape key.</li>
<li>Volume Up+T → Tab key.</li>
<li>Volume Up+1 → F1 (and Volume Up+2 → F2, etc).</li>
<li>Volume Up+B → Alt+B, back a word when using readline.</li>
<li>Volume Up+F → Alt+F, forward a word when using readline.</li>
<li>Volume Up+W → Up arrow key.</li>
<li>Volume Up+A → Left arrow key.</li>
<li>Volume Up+S → Down arrow key.</li>
<li>Volume Up+D → Right arrow key.</li>
</ul>
<h2 id="hardware_keyboard">Using a hardware keyboard</h2>
<p>The following shortcuts are available when using Termux with a hardware (e.g. bluetooth) keyboard by combining them with <em>Ctrl+Shift</em>:</p>
<ul>
<li>'C' → Create new session</li>
<li>'R' → Rename current session</li>
<li>Down arrow (or 'N') → Next session</li>
<li>Up arrow (or 'P') → Previous session</li>
<li>Right arrow → Open drawer</li>
<li>Left arrow → Close drawer</li>
<li>'F' → Toggle full screen</li>
<li>'M' → Show menu</li>
<li>'V' → Paste</li>
<li>+/- → Adjust text size</li>
<li>1-9 → Go to numbered session</li>
</ul>
<h2 id="package_management">Package management</h2>
<p>A minimal base system consisting of the Apt package manager and the busybox collection of system utilities
is installed when first starting Termux. Additional packages are available using the apt command:</p>
<dl>
<dt>apt update</dt><dd>Updates the list of available packages. This commands needs to be run initially directly after installation
and regularly afterwards to receive updates.</dd>
<dt>apt search &lt;query&gt;</dt><dd>Search among available packages.</dd>
<dt>apt install &lt;package&gt;</dt><dd>Install a new package.</dd>
<dt>apt upgrade</dt><dd>Upgrade outdated packages. For Apt to know about newer packages you will need to update the package index, so you will normally want to run <em>apt update</em> before upgrading.</dd>
<dt>apt show &lt;package&gt;</dt><dd>Show information about a package.</dd>
<dt>apt list</dt><dd>List all available packages.</dd>
<dt>apt list --installed</dt><dd>List all installed packages.</dd>
<dt>apt remove &lt;package&gt;</dt><dd>Remove an installed package.</dd>
</dl>
<p>Apt as a package manager uses a package format named <em>dpkg</em>. Normally direct use of dpkg is not necessary, but the
following two commands may be of use:</p>
<dl>
<dt>dpkg -L &lt;package&gt;</dt>
<dd>List installed files of a package.</dd>
<dt>dpkg --verify</dt>
<dd>Verify the integrity of installed packages.</dd>
</dl>
<p>View the apt manual page (execute <em>apt install man</em> to install a man page viewer first) for more information.</p>
<h2 id="text_editing">Text editing</h2>
<p>By default the busybox version of <em>vi</em> is available. This is a barebone and somewhat unfriendly editor -
install <a href="http://www.nano-editor.org/dist/v2.2/nano.html">nano</a> for a more straight-forward editor and
<a href="http://vimdoc.sourceforge.net/htmldoc/usr_toc.html">vim</a> for a more powerful one.</p>
<h2 id="using_ssh">Using SSH</h2>
<p>By installing the <strong>openssh</strong> package (by executing <em>apt install openssh</em>) you may SSH into remote systems,
optionally putting private keys or configuration under $HOME/.ssh/.</p>
<p>If you wish to use an SSH agent to avoid entering passwords, the Termux openssh package provides
a wrapper script named <strong>ssha</strong> (note the 'a' at the end) for ssh which:</p>
<ol>
<li>Starts the ssh agent if necessary (or connect to it if already running).</li>
<li>Runs <strong>ssh-add</strong> if necessary.</li>
<li>Runs <strong>ssh</strong> with the provided arguments.</li>
</ol>
<p>This means that the agent will prompt for a key password at first run, but remember the authorization for subsequent ones.</p>
<h2 id="interactive_shells">Interactive shells</h2>
<p>The base system that is installed when first starting Termux uses the <em>bash</em> shell while zsh is available as
an installable alternative:</p>
<ul>
<li>bash - the default shell on most Linux distributions, with resources such as
<a href="http://www.tldp.org/LDP/Bash-Beginners-Guide/html/">Bash Guide for Beginners</a>,
the <a href="https://www.gnu.org/software/bash/manual/bash.html">Bash Reference Manual</a>
or the <a href="http://www.tldp.org/LDP/abs/html/">Advanced Bash-Scripting Guide</a> available.</li>
<li>zsh - a powerful shell with information available at
<a href="http://zsh.sourceforge.net/Guide/zshguide.html">A User's Guide to the Z-Shell</a>, the
<a href="http://zsh.sourceforge.net/Doc/Release/zsh_toc.html">Z Shell Manual</a> or
<a href="http://www.rayninfo.co.uk/tips/zshtips.html">ZSH Tips by ZZapper</a>.
After installing zsh through <em>apt install zsh</em>, execute <em>chsh -s zsh</em> to set it as the default login shell when starting Termux
(and change back with <em>chsh -s bash</em> if necessary).</li>
</ul>
<h2 id="termux_android">Termux and Android</h2>
<p>Termux is designed to cope with the restrictions of running as an ordinary Android app without requiring root, which
leads to several differences between Termux and a traditional desktop system. The file system layout is drastically different:</p>
<ul>
<li>Common folders such as /bin, /usr/, /var and /etc does not exist.</li>
<li>The Android system provides a basic non-standard file system hierarchy, where e.g. /system/bin contains some system binaries.</li>
<li>The user folder $HOME is inside the private file area exposed to Termux as an ordinary Android app.
Uninstalling Termux will cause this file area to be wiped - so save important files outside this area such as in /sdcard
or use a version control system such as <em>git</em>.</li>
<li>Termux installs its packages in a folder exposed through the $PREFIX environment variable (with e.g. binaries in $PREFIX/bin,
and configuration in $PREFIX/etc).</li>
<li>Shared libraries are installed in $PREFIX/lib, which are available from binaries due to Termux setting the $LD_LIBRARY_PATH
environment variable. These may clash with Android system binaries in /system/bin, which may force LD_LIBRARY_PATH to be
cleared before running system binaries.</li>
</ul>
<p>Besides the file system being different, Termux is running as a single-user system without root - each Android app is running as
its own Linux user, so running commands inside Termux may not interfere with other installed applications.</p>
<p>Running as non-root implies that ports below 1024 cannot be bound to. Many packages have been configured to have compatible
default values - the ftpd, httpd, and sshd servers default to 8021, 8080 and 8022, respectively.</p>
<h2 id="add_on_api">Add-on: API</h2>
<p>The API add-on exposes Android system functionality such as SMS messages, GPS location or the Text-to-speech functionality through command line tools.</p>
<ul><li><a href="http://play.google.com/store/apps/details?id=com.termux.api">See more and install from Google Play</a></li></ul>
<h2 id="add_on_float">Add-on: Float</h2>
<p>The Float add-on consists of a floating terminal window visible while running other apps.</p>
<ul><li><a href="http://play.google.com/store/apps/details?id=com.termux.window">See more and install from Google Play</a></li></ul>
<h2 id="add_on_styling">Add-on: Styling</h2>
<p>The Styling add-on provides color schemes and fonts to beabeautify and customize the appearance of the Termux terminal.</p>
<ul><li><a href="http://play.google.com/store/apps/details?id=com.termux.styling">See more and install from Google Play</a></li></ul>
<h2 id="add_on_widget">Add-on: Widget</h2>
<p>The Widget add-on brings a widget to your homescreen, providing links to run scripts in your $HOME/.shortcuts/ folder.</p>
<ul><li><a href="http://play.google.com/store/apps/details?id=com.termux.widget">See more and install from Google Play</a></li></ul>
<h2 id="source_and_licenses">Source and licenses</h2>
<p>Termux uses terminal emulation code from <a href="https://github.com/jackpal/Android-Terminal-Emulator">Terminal Emulator for Android</a>
which is under the <a href="https://raw.githubusercontent.com/jackpal/Android-Terminal-Emulator/master/NOTICE">Apache License, Version 2.0</a>.
Packages available through Termux are distributed under their respective licenses with scripts and patches used to build them
<a href="https://github.com/termux/termux-packages">available on github</a>.</p>
</div>
</body>
</html>

View File

@@ -1,19 +1,8 @@
package com.termux.app;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.termux.R;
import com.termux.drawer.DrawerLayout;
import com.termux.terminal.TerminalSession;
import com.termux.terminal.TerminalSession.SessionChangedCallback;
import com.termux.view.TerminalKeyListener;
import com.termux.view.TerminalView;
import android.Manifest;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.ActivityNotFoundException;
@@ -27,14 +16,20 @@ import android.content.DialogInterface.OnShowListener;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Typeface;
import android.media.AudioAttributes;
import android.media.SoundPool;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Vibrator;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.TextUtils;
@@ -52,6 +47,7 @@ import android.view.View.OnClickListener;
import android.view.View.OnKeyListener;
import android.view.View.OnLongClickListener;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
@@ -61,6 +57,19 @@ import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import com.termux.R;
import com.termux.drawer.DrawerLayout;
import com.termux.terminal.TerminalSession;
import com.termux.terminal.TerminalSession.SessionChangedCallback;
import com.termux.view.TerminalKeyListener;
import com.termux.view.TerminalView;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* A terminal emulator activity.
*
@@ -83,10 +92,12 @@ public final class TermuxActivity extends Activity implements ServiceConnection
private static final int MAX_SESSIONS = 8;
private static final int REQUESTCODE_PERMISSION_STORAGE = 1234;
private static final String RELOAD_STYLE_ACTION = "com.termux.app.reload_style";
/** The main view of the activity showing the terminal. */
TerminalView mTerminalView;
/** The main view of the activity showing the terminal. Initialized in onCreate(). */
@SuppressWarnings("NullableProblems") @NonNull TerminalView mTerminalView;
final FullScreenHelper mFullScreenHelper = new FullScreenHelper(this);
@@ -111,17 +122,43 @@ public final class TermuxActivity extends Activity implements ServiceConnection
*/
boolean mIsVisible;
private SoundPool mBellSoundPool = new SoundPool.Builder().setMaxStreams(1).setAudioAttributes(
new AudioAttributes.Builder().setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION).build()).build();
private int mBellSoundId;
private final BroadcastReceiver mBroadcastReceiever = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (mIsVisible) {
String whatToReload = intent.getStringExtra(RELOAD_STYLE_ACTION);
if ("storage".equals(whatToReload)) {
if (ensureStoragePermissionGranted()) TermuxInstaller.setupStorageSymlinks(TermuxActivity.this);
return;
}
if (whatToReload == null || "colors".equals(whatToReload)) mTerminalView.checkForColors();
if (whatToReload == null || "font".equals(whatToReload)) mTerminalView.checkForTypeface();
if (whatToReload == null || "settings".equals(whatToReload)) mSettings.reloadFromProperties(TermuxActivity.this);
}
}
};
/** For processes to access shared internal storage (/sdcard) we need this permission. */
@TargetApi(Build.VERSION_CODES.M)
public boolean ensureStoragePermissionGranted() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (checkSelfPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
return true;
} else {
requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUESTCODE_PERMISSION_STORAGE);
return false;
}
} else {
// Always granted before Android 6.0.
return true;
}
}
@Override
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
@@ -142,6 +179,7 @@ public final class TermuxActivity extends Activity implements ServiceConnection
if (event.getAction() != KeyEvent.ACTION_DOWN) return false;
final TerminalSession currentSession = getCurrentTermSession();
if (currentSession == null) return false;
if (keyCode == KeyEvent.KEYCODE_ENTER && !currentSession.isRunning()) {
// Return pressed with finished session - remove it.
@@ -225,11 +263,22 @@ public final class TermuxActivity extends Activity implements ServiceConnection
@Override
public void onSingleTapUp(MotionEvent e) {
// Toggle keyboard visibility if tapping with a finger:
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.toggleSoftInput(InputMethodManager.SHOW_IMPLICIT, 0);
switch (mSettings.mTapBehaviour) {
case TermuxPreferences.TAP_TOGGLE_KEYBOARD:
// Toggle keyboard visibility if tapping with a finger:
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.toggleSoftInput(InputMethodManager.SHOW_IMPLICIT, 0);
break;
case TermuxPreferences.TAP_SHOW_MENU:
mTerminalView.showContextMenu();
break;
}
}
@Override
public boolean shouldBackButtonBeMappedToEscape() {
return mSettings.mBackIsEscape;
}
});
findViewById(R.id.new_session_button).setOnClickListener(new OnClickListener() {
@@ -289,6 +338,8 @@ public final class TermuxActivity extends Activity implements ServiceConnection
mTerminalView.checkForTypeface();
mTerminalView.checkForColors();
mBellSoundId = mBellSoundPool.load(this, R.raw.bell, 1);
}
/**
@@ -345,7 +396,17 @@ public final class TermuxActivity extends Activity implements ServiceConnection
@Override
public void onBell(TerminalSession session) {
if (mIsVisible) ((Vibrator) getSystemService(VIBRATOR_SERVICE)).vibrate(50);
if (mIsVisible) {
switch (mSettings.mBellBehaviour) {
case TermuxPreferences.BELL_BEEP:
mBellSoundPool.play(mBellSoundId, 1.f, 1.f, 1, 0, 1.f);
break;
case TermuxPreferences.BELL_VIBRATE:
((Vibrator) getSystemService(VIBRATOR_SERVICE)).vibrate(50);
break;
}
}
}
};
@@ -414,18 +475,22 @@ public final class TermuxActivity extends Activity implements ServiceConnection
TermuxInstaller.setupIfNeeded(TermuxActivity.this, new Runnable() {
@Override
public void run() {
if (TermuxPreferences.isShowWelcomeDialog(TermuxActivity.this)) {
new AlertDialog.Builder(TermuxActivity.this).setTitle(R.string.welcome_dialog_title).setMessage(R.string.welcome_dialog_body)
.setCancelable(false).setPositiveButton(android.R.string.ok, null)
.setNegativeButton(R.string.welcome_dialog_dont_show_again_button, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
TermuxPreferences.disableWelcomeDialog(TermuxActivity.this);
dialog.dismiss();
}
}).show();
try {
if (TermuxPreferences.isShowWelcomeDialog(TermuxActivity.this)) {
new AlertDialog.Builder(TermuxActivity.this).setTitle(R.string.welcome_dialog_title).setMessage(R.string.welcome_dialog_body)
.setCancelable(false).setPositiveButton(android.R.string.ok, null)
.setNegativeButton(R.string.welcome_dialog_dont_show_again_button, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
TermuxPreferences.disableWelcomeDialog(TermuxActivity.this);
dialog.dismiss();
}
}).show();
}
addNewSession(false, null);
} catch (WindowManager.BadTokenException e) {
// Activity finished - ignore.
}
addNewSession(false, null);
}
});
} else {
@@ -456,7 +521,7 @@ public final class TermuxActivity extends Activity implements ServiceConnection
}
}
TerminalSession getCurrentTermSession() {
@Nullable TerminalSession getCurrentTermSession() {
return mTerminalView.getCurrentSession();
}
@@ -575,12 +640,11 @@ public final class TermuxActivity extends Activity implements ServiceConnection
return false;
}
void showUrlSelection() {
String text = getCurrentTermSession().getEmulator().getScreen().getTranscriptText();
static LinkedHashSet<CharSequence> extractUrls(String text) {
// Pattern for recognizing a URL, based off RFC 3986
// http://stackoverflow.com/questions/5713558/detect-and-extract-url-from-a-string
final Pattern urlPattern = Pattern.compile(
"(?:^|[\\W])((ht|f)tp(s?):\\/\\/|www\\.)" + "(([\\w\\-]+\\.){1,}?([\\w\\-.~]+\\/?)*" + "[\\p{Alnum}.,%_=?&#\\-+()\\[\\]\\*$~@!:/{};']*)",
"(?:^|[\\W])((ht|f)tp(s?)://|www\\.)" + "(([\\w\\-]+\\.)+?([\\w\\-.~]+/?)*" + "[\\p{Alnum}.,%_=?&#\\-+()\\[\\]\\*$~@!:/{};']*)",
Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
LinkedHashSet<CharSequence> urlSet = new LinkedHashSet<>();
Matcher matcher = urlPattern.matcher(text);
@@ -590,7 +654,12 @@ public final class TermuxActivity extends Activity implements ServiceConnection
String url = text.substring(matchStart, matchEnd);
urlSet.add(url);
}
return urlSet;
}
void showUrlSelection() {
String text = getCurrentTermSession().getEmulator().getScreen().getTranscriptText();
LinkedHashSet<CharSequence> urlSet = extractUrls(text);
if (urlSet.isEmpty()) {
new AlertDialog.Builder(this).setMessage(R.string.select_url_no_found).show();
return;
@@ -713,6 +782,13 @@ public final class TermuxActivity extends Activity implements ServiceConnection
}
}
@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
if (requestCode == REQUESTCODE_PERMISSION_STORAGE && grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
TermuxInstaller.setupStorageSymlinks(this);
}
}
void toggleImmersive() {
boolean newValue = !mSettings.isFullScreen();
mSettings.setFullScreen(this, newValue);

View File

@@ -5,33 +5,63 @@ import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.ViewGroup;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
/** Basic embedded browser for viewing the bundled help page. */
/** Basic embedded browser for viewing help pages. */
public final class TermuxHelpActivity extends Activity {
private WebView mWebView;
private ProgressBar mProgressBar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final RelativeLayout progressLayout = new RelativeLayout(this);
RelativeLayout.LayoutParams lParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
lParams.addRule(RelativeLayout.CENTER_IN_PARENT);
mProgressBar = new ProgressBar(this);
mProgressBar.setIndeterminate(true);
mProgressBar.setLayoutParams(lParams);
progressLayout.addView(mProgressBar);
mWebView = new WebView(this);
setContentView(mWebView);
WebSettings settings = mWebView.getSettings();
settings.setCacheMode(WebSettings.LOAD_NO_CACHE);
settings.setAppCacheEnabled(false);
setContentView(progressLayout);
mWebView.clearCache(true);
mWebView.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (url.startsWith("https://termux.com")) {
// Inline help.
setContentView(progressLayout);
return false;
}
try {
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
} catch (ActivityNotFoundException e) {
// TODO: Android TV does not have a system browser - but needs better method of getting back
// than navigating deep here.
// Android TV does not have a system browser.
setContentView(progressLayout);
return false;
}
return true;
}
@Override
public void onPageFinished(WebView view, String url) {
setContentView(mWebView);
}
});
mWebView.loadUrl("file:///android_asset/help.html");
mWebView.loadUrl("https://termux.com/help.html");
}
@Override

View File

@@ -1,5 +1,21 @@
package com.termux.app;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.DialogInterface.OnDismissListener;
import android.os.Environment;
import android.system.Os;
import android.util.Log;
import android.util.Pair;
import android.view.WindowManager;
import com.termux.R;
import com.termux.terminal.EmulatorDebug;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
@@ -11,20 +27,6 @@ import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.DialogInterface.OnDismissListener;
import android.system.Os;
import android.util.Log;
import android.util.Pair;
import com.termux.R;
import com.termux.terminal.EmulatorDebug;
/**
* Install the Termux bootstrap packages if necessary by following the below steps:
*
@@ -138,27 +140,35 @@ final class TermuxInstaller {
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
new AlertDialog.Builder(activity).setTitle(R.string.bootstrap_error_title).setMessage(R.string.bootstrap_error_body)
.setNegativeButton(R.string.bootstrap_error_abort, new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
activity.finish();
}
}).setPositiveButton(R.string.bootstrap_error_try_again, new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
TermuxInstaller.setupIfNeeded(activity, whenDone);
}
}).show();
try {
new AlertDialog.Builder(activity).setTitle(R.string.bootstrap_error_title).setMessage(R.string.bootstrap_error_body)
.setNegativeButton(R.string.bootstrap_error_abort, new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
activity.finish();
}
}).setPositiveButton(R.string.bootstrap_error_try_again, new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
TermuxInstaller.setupIfNeeded(activity, whenDone);
}
}).show();
} catch (WindowManager.BadTokenException e) {
// Activity already dismissed - ignore.
}
}
});
} finally {
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
progress.dismiss();
try {
progress.dismiss();
} catch (RuntimeException e) {
// Activity already dismissed - ignore.
}
}
});
}
@@ -175,7 +185,7 @@ final class TermuxInstaller {
} else if (arch.equals("x86_64")) {
arch = "i686";
}
return new URL("http://apt.termux.com/bootstrap/bootstrap-" + arch + ".zip");
return new URL("https://termux.net/bootstrap/bootstrap-" + arch + ".zip");
}
/** Delete a folder and all its content or throw. */
@@ -191,4 +201,51 @@ final class TermuxInstaller {
}
}
public static void setupStorageSymlinks(final Context context) {
final String LOG_TAG = "termux-storage";
new Thread() {
public void run() {
try {
File storageDir = new File(TermuxService.HOME_PATH, "storage");
if (storageDir.exists() && !storageDir.delete()) {
Log.e(LOG_TAG, "Could not delete old $HOME/storage");
return;
}
if (!storageDir.mkdirs()) {
Log.e(LOG_TAG, "Unable to mkdirs() for $HOME/storage");
return;
}
File sharedDir = Environment.getExternalStorageDirectory();
Os.symlink(sharedDir.getAbsolutePath(), new File(storageDir, "shared").getAbsolutePath());
File downloadsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
Os.symlink(downloadsDir.getAbsolutePath(), new File(storageDir, "downloads").getAbsolutePath());
File dcimDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM);
Os.symlink(dcimDir.getAbsolutePath(), new File(storageDir, "dcim").getAbsolutePath());
File picturesDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
Os.symlink(picturesDir.getAbsolutePath(), new File(storageDir, "pictures").getAbsolutePath());
File musicDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC);
Os.symlink(musicDir.getAbsolutePath(), new File(storageDir, "music").getAbsolutePath());
File moviesDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES);
Os.symlink(moviesDir.getAbsolutePath(), new File(storageDir, "movies").getAbsolutePath());
final File[] dirs = context.getExternalFilesDirs(null);
if (dirs != null && dirs.length >= 2) {
final File externalDir = dirs[1];
Os.symlink(externalDir.getAbsolutePath(), new File(storageDir, "external").getAbsolutePath());
}
} catch (Exception e) {
Log.e(LOG_TAG, "Error setting up link", e);
}
}
}.start();
}
}

View File

@@ -5,12 +5,38 @@ import com.termux.terminal.TerminalSession;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.support.annotation.IntDef;
import android.util.Log;
import android.util.TypedValue;
import android.widget.Toast;
import java.io.File;
import java.io.FileInputStream;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Properties;
final class TermuxPreferences {
@IntDef({BELL_VIBRATE, BELL_BEEP, BELL_IGNORE})
@Retention(RetentionPolicy.SOURCE)
public @interface AsciiBellBehaviour {}
static final int BELL_VIBRATE = 1;
static final int BELL_BEEP = 2;
static final int BELL_IGNORE = 3;
@IntDef({TAP_TOGGLE_KEYBOARD, TAP_SHOW_MENU, TAP_IGNORE})
@Retention(RetentionPolicy.SOURCE)
public @interface TapTerminalBehaviour {}
static final int TAP_TOGGLE_KEYBOARD = 1;
static final int TAP_SHOW_MENU = 2;
static final int TAP_IGNORE = 3;
private final int MIN_FONTSIZE;
private static final int MAX_FONTSIZE = 256;
private static final String FULLSCREEN_KEY = "fullscreen";
private static final String FONTSIZE_KEY = "fontsize";
private static final String CURRENT_SESSION_KEY = "current_session";
@@ -19,7 +45,16 @@ final class TermuxPreferences {
private boolean mFullScreen;
private int mFontSize;
@AsciiBellBehaviour
int mBellBehaviour = BELL_VIBRATE;
@TapTerminalBehaviour
int mTapBehaviour = TAP_TOGGLE_KEYBOARD;
boolean mBackIsEscape = true;
TermuxPreferences(Context context) {
reloadFromProperties(context);
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
float dipInPixels = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, context.getResources().getDisplayMetrics());
@@ -86,4 +121,49 @@ final class TermuxPreferences {
PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean(SHOW_WELCOME_DIALOG_KEY, false).apply();
}
public void reloadFromProperties(Context context) {
try {
File propsFile = new File(TermuxService.HOME_PATH + "/.config/termux/termux.properties");
if (propsFile.isFile() && propsFile.canRead()) {
Properties props = new Properties();
try (FileInputStream in = new FileInputStream(propsFile)) {
props.load(in);
}
switch (props.getProperty("bell-character", "vibrate")) {
case "beep":
mBellBehaviour = BELL_BEEP;
break;
case "ignore":
mBellBehaviour = BELL_IGNORE;
break;
default: // "vibrate".
mBellBehaviour = BELL_VIBRATE;
break;
}
switch (props.getProperty("tap-screen", "toggle-keyboard")) {
case "show-menu":
mTapBehaviour = TAP_SHOW_MENU;
break;
case "ignore":
mTapBehaviour = TAP_IGNORE;
break;
default: // "toggle-keyboard".
mTapBehaviour = TAP_TOGGLE_KEYBOARD;
break;
}
mBackIsEscape = !"back".equals(props.getProperty("back-key", "escape"));
} else {
mBellBehaviour = BELL_VIBRATE;
mTapBehaviour = TAP_TOGGLE_KEYBOARD;
mBackIsEscape = true;
}
} catch (Exception e) {
Toast.makeText(context, "Error loading properties: " + e.getMessage(), Toast.LENGTH_LONG).show();
Log.e("termux", "Error loading props", e);
}
}
}

View File

@@ -138,11 +138,11 @@ public class DrawerLayout extends ViewGroup {
private final ChildAccessibilityDelegate mChildAccessibilityDelegate = new ChildAccessibilityDelegate();
private int mMinDrawerMargin;
private final int mMinDrawerMargin;
private int mScrimColor = DEFAULT_SCRIM_COLOR;
private float mScrimOpacity;
private Paint mScrimPaint = new Paint();
private final Paint mScrimPaint = new Paint();
private final ViewDragHelper mLeftDragger;
private final ViewDragHelper mRightDragger;
@@ -768,16 +768,10 @@ public class DrawerLayout extends ViewGroup {
// or pick a magic number from thin air otherwise.
// TODO Better communication with tools of this bogus state.
// It will crash on a real device.
if (widthMode == MeasureSpec.AT_MOST) {
widthMode = MeasureSpec.EXACTLY;
} else if (widthMode == MeasureSpec.UNSPECIFIED) {
widthMode = MeasureSpec.EXACTLY;
if (widthMode == MeasureSpec.UNSPECIFIED) {
widthSize = 300;
}
if (heightMode == MeasureSpec.AT_MOST) {
heightMode = MeasureSpec.EXACTLY;
} else if (heightMode == MeasureSpec.UNSPECIFIED) {
heightMode = MeasureSpec.EXACTLY;
if (heightMode == MeasureSpec.UNSPECIFIED) {
heightSize = 300;
}
} else {
@@ -934,10 +928,7 @@ public class DrawerLayout extends ViewGroup {
private static boolean hasOpaqueBackground(View v) {
final Drawable bg = v.getBackground();
if (bg != null) {
return bg.getOpacity() == PixelFormat.OPAQUE;
}
return false;
return bg != null && bg.getOpacity() == PixelFormat.OPAQUE;
}
/**
@@ -1114,7 +1105,6 @@ public class DrawerLayout extends ViewGroup {
mRightDragger.processTouchEvent(ev);
final int action = ev.getAction();
boolean wantTouchEvents = true;
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: {
@@ -1154,7 +1144,7 @@ public class DrawerLayout extends ViewGroup {
}
}
return wantTouchEvents;
return true;
}
@Override
@@ -1317,10 +1307,7 @@ public class DrawerLayout extends ViewGroup {
*/
public boolean isDrawerOpen(int drawerGravity) {
final View drawerView = findDrawerWithGravity(drawerGravity);
if (drawerView != null) {
return isDrawerOpen(drawerView);
}
return false;
return drawerView != null && isDrawerOpen(drawerView);
}
/**
@@ -1350,10 +1337,7 @@ public class DrawerLayout extends ViewGroup {
*/
public boolean isDrawerVisible(int drawerGravity) {
final View drawerView = findDrawerWithGravity(drawerGravity);
if (drawerView != null) {
return isDrawerVisible(drawerView);
}
return false;
return drawerView != null && isDrawerVisible(drawerView);
}
private boolean hasPeekingDrawer() {
@@ -1776,10 +1760,7 @@ public class DrawerLayout extends ViewGroup {
@Override
public boolean onRequestSendAccessibilityEvent(ViewGroup host, View child, AccessibilityEvent event) {
if (CAN_HIDE_DESCENDANTS || includeChildForAccessibility(child)) {
return super.onRequestSendAccessibilityEvent(host, child, event);
}
return false;
return (CAN_HIDE_DESCENDANTS || includeChildForAccessibility(child)) && super.onRequestSendAccessibilityEvent(host, child, event);
}
}
@@ -1796,4 +1777,4 @@ public class DrawerLayout extends ViewGroup {
}
}
}
}
}

View File

@@ -57,7 +57,7 @@ public class ViewDragHelper {
/**
* Edge flag indicating that the left edge should be affected.
*/
public static final int EDGE_LEFT = 1 << 0;
public static final int EDGE_LEFT = 1 /*1 << 0*/;
/**
* Edge flag indicating that the right edge should be affected.
@@ -82,7 +82,7 @@ public class ViewDragHelper {
/**
* Indicates that a check should occur along the horizontal axis
*/
public static final int DIRECTION_HORIZONTAL = 1 << 0;
public static final int DIRECTION_HORIZONTAL = 1 /*1 << 0*/;
/**
* Indicates that a check should occur along the vertical axis
@@ -1485,10 +1485,7 @@ public class ViewDragHelper {
* @return true if the supplied view is under the given point, false otherwise
*/
public boolean isViewUnder(View view, int x, int y) {
if (view == null) {
return false;
}
return x >= view.getLeft() && x < view.getRight() && y >= view.getTop() && y < view.getBottom();
return view != null && x >= view.getLeft() && x < view.getRight() && y >= view.getTop() && y < view.getBottom();
}
/**
@@ -1522,4 +1519,4 @@ public class ViewDragHelper {
return result;
}
}
}

View File

@@ -36,17 +36,12 @@ public class TermuxFilePickerActivity extends ListActivity {
super.onCreate(savedInstanceState);
setContentView(R.layout.file_picker);
mAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, mFileNames);
mAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, mFileNames);
enterDirectory(new File(TERMUX_HOME));
setListAdapter(mAdapter);
}
@Override
protected void onResume() {
super.onResume();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();

View File

@@ -6,6 +6,7 @@ import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.support.annotation.NonNull;
import java.io.File;
import java.io.FileNotFoundException;
@@ -18,32 +19,32 @@ public class TermuxFilePickerProvider extends ContentProvider {
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
public Cursor query(@NonNull Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
return null;
}
@Override
public String getType(Uri uri) {
public String getType(@NonNull Uri uri) {
return null;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
public Uri insert(@NonNull Uri uri, ContentValues values) {
return null;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
public int delete(@NonNull Uri uri, String selection, String[] selectionArgs) {
return 0;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
public int update(@NonNull Uri uri, ContentValues values, String selection, String[] selectionArgs) {
return 0;
}
@Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) throws FileNotFoundException {
File file = new File(uri.getPath());
return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
}

View File

@@ -33,8 +33,7 @@ public final class TerminalEmulator {
private static final boolean LOG_ESCAPE_SEQUENCES = false;
public static final int MOUSE_LEFT_BUTTON = 0;
public static final int MOUSE_MIDDLE_BUTTON = 1;
public static final int MOUSE_RIGHT_BUTTON = 2;
/** Mouse moving while having left mouse button pressed. */
public static final int MOUSE_LEFT_BUTTON_MOVED = 32;
public static final int MOUSE_WHEELUP_BUTTON = 64;
@@ -1444,7 +1443,7 @@ public final class TerminalEmulator {
final int linesToScrollArg = getArg0(1);
final int linesBetweenTopAndBottomMargins = mBottomMargin - mTopMargin;
final int linesToScroll = Math.min(linesBetweenTopAndBottomMargins, linesToScrollArg);
mScreen.blockCopy(0, mTopMargin, mColumns, linesBetweenTopAndBottomMargins - linesToScroll, 0, linesToScroll);
mScreen.blockCopy(0, mTopMargin, mColumns, linesBetweenTopAndBottomMargins - linesToScroll, 0, mTopMargin + linesToScroll);
blockClear(0, mTopMargin, mColumns, linesToScroll);
} else {
// "${CSI}${func};${startx};${starty};${firstrow};${lastrow}T" - initiate highlight mouse tracking.
@@ -2156,15 +2155,22 @@ public final class TerminalEmulator {
final boolean autoWrap = isDecsetInternalBitSet(DECSET_BIT_AUTOWRAP);
final int displayWidth = WcWidth.width(codePoint);
final boolean cursorInLastColumn = mCursorCol == mRightMargin - 1;
if (autoWrap && (mCursorCol == mRightMargin - 1 && ((mAboutToAutoWrap && displayWidth == 1) || displayWidth == 2))) {
mScreen.setLineWrap(mCursorRow);
mCursorCol = mLeftMargin;
if (mCursorRow + 1 < mBottomMargin) {
mCursorRow++;
} else {
scrollDownOneLine();
if (autoWrap) {
if (cursorInLastColumn && ((mAboutToAutoWrap && displayWidth == 1) || displayWidth == 2)) {
mScreen.setLineWrap(mCursorRow);
mCursorCol = mLeftMargin;
if (mCursorRow + 1 < mBottomMargin) {
mCursorRow++;
} else {
scrollDownOneLine();
}
}
} else if (cursorInLastColumn && displayWidth == 2) {
// The behaviour when a wide character is output with cursor in the last column when
// autowrap is disabled is not obvious - it's ignored here.
return;
}
if (mInsertMode && displayWidth > 0) {

View File

@@ -119,7 +119,7 @@ public final class TerminalSession extends TerminalOutput {
// Negated signal.
exitDescription += " with signal " + (-exitCode);
}
exitDescription += "]";
exitDescription += " - press Enter to close]";
byte[] bytesToWrite = exitDescription.getBytes(StandardCharsets.UTF_8);
mEmulator.append(bytesToWrite, bytesToWrite.length);

View File

@@ -6,7 +6,7 @@ import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
/** A combination of {@link GestureDetector} and {@link ScaleGestureDetector}. */
public class GestureAndScaleRecognizer {
public final class GestureAndScaleRecognizer {
public interface Listener {
boolean onSingleTapUp(MotionEvent e);
@@ -29,6 +29,7 @@ public class GestureAndScaleRecognizer {
private final GestureDetector mGestureDetector;
private final ScaleGestureDetector mScaleDetector;
final Listener mListener;
boolean isAfterLongPress;
public GestureAndScaleRecognizer(Context context, Listener listener) {
mListener = listener;
@@ -52,6 +53,7 @@ public class GestureAndScaleRecognizer {
@Override
public void onLongPress(MotionEvent e) {
mListener.onLongPress(e);
isAfterLongPress = true;
}
}, null, true /* ignoreMultitouch */);
@@ -88,8 +90,17 @@ public class GestureAndScaleRecognizer {
public void onTouchEvent(MotionEvent event) {
mGestureDetector.onTouchEvent(event);
mScaleDetector.onTouchEvent(event);
if (event.getAction() == MotionEvent.ACTION_UP) {
mListener.onUp(event);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
isAfterLongPress = false;
break;
case MotionEvent.ACTION_UP:
if (!isAfterLongPress) {
// This behaviour is desired when in e.g. vim with mouse events, where we do not
// want to move the cursor when lifting finger after a long press.
mListener.onUp(event);
}
break;
}
}
@@ -97,4 +108,4 @@ public class GestureAndScaleRecognizer {
return mScaleDetector.isInProgress();
}
}
}

View File

@@ -6,6 +6,8 @@ import android.view.ScaleGestureDetector;
/**
* Input and scale listener which may be set on a {@link TerminalView} through
* {@link TerminalView#setOnKeyListener(TerminalKeyListener)}.
*
* TODO: Rename to TerminalViewClient.
*/
public interface TerminalKeyListener {
@@ -17,4 +19,6 @@ public interface TerminalKeyListener {
/** On a single tap on the terminal if terminal mouse reporting not enabled. */
void onSingleTapUp(MotionEvent e);
boolean shouldBackButtonBeMappedToEscape();
}

View File

@@ -80,6 +80,7 @@ final class TerminalRenderer {
TerminalRow lineObject = screen.allocateFullLineIfNecessary(screen.externalToInternalRow(row));
final char[] line = lineObject.mText;
final int charsUsedInLine = lineObject.getSpaceUsed();
int lastRunStyle = 0;
boolean lastRunInsideCursor = false;
@@ -125,7 +126,7 @@ final class TerminalRenderer {
measuredWidthForRun += measuredCodePointWidth;
column += codePointWcWidth;
currentCharIndex += charsForCodePoint;
while (WcWidth.width(line, currentCharIndex) <= 0) {
while (currentCharIndex < charsUsedInLine && WcWidth.width(line, currentCharIndex) <= 0) {
// Eat combining chars so that they are treated as part of the last non-combining code point,
// instead of e.g. being considered inside the cursor in the next run.
currentCharIndex += Character.isHighSurrogate(line[currentCharIndex]) ? 2 : 1;
@@ -204,7 +205,10 @@ final class TerminalRenderer {
final boolean strikeThrough = (effect & TextStyle.CHARACTER_ATTRIBUTE_STRIKETHROUGH) != 0;
final boolean dim = (effect & TextStyle.CHARACTER_ATTRIBUTE_DIM) != 0;
int foreColorARGB = palette[foreColor];
// Let bold have bright colors if applicable (one of the first 8):
final int actualForeColor = foreColor + (bold && foreColor < 8 ? 8 : 0);
int foreColorARGB = palette[actualForeColor];
if (dim) {
int red = (0xFF & (foreColorARGB >> 16));
int green = (0xFF & (foreColorARGB >> 8));

View File

@@ -12,7 +12,6 @@ import com.termux.terminal.TerminalEmulator;
import com.termux.terminal.TerminalSession;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Typeface;
@@ -492,9 +491,8 @@ public final class TerminalView extends View {
@Override
public boolean onKeyPreIme(int keyCode, KeyEvent event) {
if (LOG_KEY_EVENTS) Log.i(EmulatorDebug.LOG_TAG, "onKeyPreIme(keyCode=" + keyCode + ", event=" + event + ")");
if (keyCode == KeyEvent.KEYCODE_ESCAPE || keyCode == KeyEvent.KEYCODE_BACK) {
// Handle the escape key ourselves to avoid the system from treating it as back key
// and e.g. close keyboard.
if (keyCode == KeyEvent.KEYCODE_BACK && mOnKeyListener.shouldBackButtonBeMappedToEscape()) {
// Intercept back button to treat it as escape:
switch (event.getAction()) {
case KeyEvent.ACTION_DOWN:
return onKeyDown(keyCode, event);
@@ -518,7 +516,7 @@ public final class TerminalView extends View {
if (handleVirtualKeys(keyCode, event, true)) {
invalidate();
return true;
} else if (event.isSystem() && keyCode != KeyEvent.KEYCODE_BACK) {
} else if (event.isSystem() && (!mOnKeyListener.shouldBackButtonBeMappedToEscape() || keyCode != KeyEvent.KEYCODE_BACK)) {
return super.onKeyDown(keyCode, event);
}
@@ -717,10 +715,10 @@ public final class TerminalView extends View {
@Override
public void run() {
try {
File fontFile = new File(getContext().getFilesDir().getPath() + "/home/.termux/font.ttf");
File fontFile = new File("/data/data/com.termux/files/home/.termux/font.ttf");
final Typeface newTypeface = fontFile.exists() ? Typeface.createFromFile(fontFile) : Typeface.MONOSPACE;
if (newTypeface != mRenderer.mTypeface) {
((Activity) getContext()).runOnUiThread(new Runnable() {
post(new Runnable() {
@Override
public void run() {
try {
@@ -745,14 +743,14 @@ public final class TerminalView extends View {
@Override
public void run() {
try {
File colorsFile = new File(getContext().getFilesDir().getPath() + "/home/.termux/colors.properties");
File colorsFile = new File("/data/data/com.termux/files/home/.termux/colors.properties");
final Properties props = colorsFile.isFile() ? new Properties() : null;
if (props != null) {
try (InputStream in = new FileInputStream(colorsFile)) {
props.load(in);
}
}
((Activity) getContext()).runOnUiThread(new Runnable() {
post(new Runnable() {
@Override
public void run() {
try {

View File

@@ -9,12 +9,13 @@
<ListView android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:drawSelectorOnTop="false"/>
<TextView android:id="@android:id/empty"
android:gravity="center"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="$HOME is empty!"/>
android:text="@string/empty_folder"/>
</LinearLayout>

Binary file not shown.

View File

@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="application_name">Termux</string>
<string name="application_help">Termux help</string>
<string name="shared_user_label">Termux user</string>
<string name="new_session">New session</string>
<string name="new_session_normal_unnamed">Normal - unnamed</string>
@@ -56,4 +55,6 @@
<string name="notification_action_wakelock">Wake</string>
<string name="notification_action_wifilock">Wifi</string>
<string name="empty_folder">Empty folder.</string>
</resources>

View File

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

View File

@@ -0,0 +1,25 @@
package com.termux.app;
import junit.framework.TestCase;
import java.util.Collections;
import java.util.LinkedHashSet;
public class TermuxActivityTest extends TestCase {
private void assertUrlsAre(String text, String... urls) {
LinkedHashSet<String> expected = new LinkedHashSet<>();
Collections.addAll(expected, urls);
assertEquals(expected, TermuxActivity.extractUrls(text));
}
public void testExtractUrls() {
assertUrlsAre("hello http://example.com world", "http://example.com");
assertUrlsAre("http://example.com\nhttp://another.com", "http://example.com", "http://another.com");
assertUrlsAre("hello http://example.com world and http://more.example.com with secure https://more.example.com",
"http://example.com", "http://more.example.com", "https://more.example.com");
}
}

View File

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

View File

@@ -4,13 +4,13 @@ package com.termux.terminal;
* <pre>
* "CSI ? Pm h", DEC Private Mode Set (DECSET)
* </pre>
*
* <p/>
* and
*
* <p/>
* <pre>
* "CSI ? Pm l", DEC Private Mode Reset (DECRST)
* </pre>
*
* <p/>
* controls various aspects of the terminal
*/
public class DecSetTest extends TerminalTestCase {
@@ -59,4 +59,15 @@ public class DecSetTest extends TerminalTestCase {
assertEquals("Terminal reset() should disable bracketed paste mode", "a", mOutput.getOutputAndClear());
}
/** DECSET 7, DECAWM, controls wraparound mode. */
public void testWrapAroundMode() {
// Default with wraparound:
withTerminalSized(3, 3).enterString("abcd").assertLinesAre("abc", "d ", " ");
// With wraparound disabled:
withTerminalSized(3, 3).enterString("\033[?7labcd").assertLinesAre("abd", " ", " ");
enterString("efg").assertLinesAre("abg", " ", " ");
// Re-enabling wraparound:
enterString("\033[?7hhij").assertLinesAre("abh", "ij ", " ");
}
}

View File

@@ -1,6 +1,7 @@
package com.termux.terminal;
import android.view.KeyEvent;
import junit.framework.TestCase;
public class KeyHandlerTest extends TestCase {

View File

@@ -1,11 +1,11 @@
package com.termux.terminal;
import android.util.Base64;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import android.util.Base64;
/** "ESC ]" is the Operating System Command. */
public class OperatingSystemControlTest extends TerminalTestCase {
@@ -15,7 +15,7 @@ public class OperatingSystemControlTest extends TerminalTestCase {
withTerminalSized(10, 10);
enterString("\033]0;Hello, world\007");
assertEquals("Hello, world", mTerminal.getTitle());
expectedTitleChanges.add(new ChangedTitle((String) null, "Hello, world"));
expectedTitleChanges.add(new ChangedTitle(null, "Hello, world"));
assertEquals(expectedTitleChanges, mOutput.titleChanges);
enterString("\033]0;Goodbye, world\007");

View File

@@ -2,7 +2,7 @@ package com.termux.terminal;
/**
* ${CSI}${top};${bottom}r" - set Scrolling Region [top;bottom] (default = full size of window) (DECSTBM).
*
* <p/>
* "DECSTBM moves the cursor to column 1, line 1 of the page" (http://www.vt100.net/docs/vt510-rm/DECSTBM).
*/
public class ScrollRegionTest extends TerminalTestCase {
@@ -94,4 +94,8 @@ public class ScrollRegionTest extends TerminalTestCase {
withTerminalSized(3, 3).enterString("\033[?69h\033[0;2sABCD\0339").assertLinesAre("B ", "D ", " ");
}
public void testScrollDownWithScrollRegion() {
withTerminalSized(2, 5).enterString("1\r\n2\r\n3\r\n4\r\n5").assertLinesAre("1 ", "2 ", "3 ", "4 ", "5 ");
enterString("\033[3r").enterString("\033[2T").assertLinesAre("1 ", "2 ", " ", " ", "3 ");
}
}

View File

@@ -1,10 +1,10 @@
package com.termux.terminal;
import junit.framework.TestCase;
import java.util.Arrays;
import java.util.Random;
import junit.framework.TestCase;
public class TerminalRowTest extends TestCase {
/** The properties of these code points are validated in {@link #testStaticConstants()}. */
@@ -96,7 +96,7 @@ public class TerminalRowTest extends TestCase {
assertEquals(80, row.getSpaceUsed());
assertColumnCharIndicesStartsWith(0, 1, 2, 3);
char[] someChars = new char[] { 'a', 'c', 'e', '4', '5', '6', '7', '8' };
char[] someChars = new char[]{'a', 'c', 'e', '4', '5', '6', '7', '8'};
char[] rawLine = new char[80];
Arrays.fill(rawLine, ' ');
@@ -373,13 +373,13 @@ public class TerminalRowTest extends TestCase {
assertEquals(0, WcWidth.width(0x009F));
assertEquals(1, Character.charCount(0x009F));
int[] points = new int[] { 0xC2541, 'a', '8', 0x73EE, 0x009F, 0x881F, 0x8324, 0xD4C9, 0xFFFD, 'B', 0x009B, 0x61C9, 'Z' };
int[] points = new int[]{0xC2541, 'a', '8', 0x73EE, 0x009F, 0x881F, 0x8324, 0xD4C9, 0xFFFD, 'B', 0x009B, 0x61C9, 'Z'};
// int[] expected = new int[] { TerminalEmulator.UNICODE_REPLACEMENT_CHAR, 'a', '8', 0x73EE, 0x009F, 0x881F, 0x8324, 0xD4C9, 0xFFFD,
// 'B', 0x009B, 0x61C9, 'Z' };
int currentColumn = 0;
for (int i = 0; i < points.length; i++) {
row.setChar(currentColumn, points[i], 0);
currentColumn += WcWidth.width(points[i]);
for (int point : points) {
row.setChar(currentColumn, point, 0);
currentColumn += WcWidth.width(point);
}
// assertLineStartsWith(points);
// assertEquals(Character.highSurrogate(0xC2541), line.mText[0]);

View File

@@ -1,5 +1,8 @@
package com.termux.terminal;
import junit.framework.AssertionFailedError;
import junit.framework.TestCase;
import java.io.ByteArrayOutputStream;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
@@ -9,16 +12,10 @@ import java.util.List;
import java.util.Objects;
import java.util.Set;
import junit.framework.AssertionFailedError;
import junit.framework.TestCase;
import com.termux.terminal.TerminalEmulator;
import com.termux.terminal.TerminalOutput;
public abstract class TerminalTestCase extends TestCase {
public static class MockTerminalOutput extends TerminalOutput {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
public final List<ChangedTitle> titleChanges = new ArrayList<>();
public final List<String> clipboardPuts = new ArrayList<>();
public int bellsRung = 0;
@@ -57,7 +54,7 @@ public abstract class TerminalTestCase extends TestCase {
public TerminalEmulator mTerminal;
public MockTerminalOutput mOutput;
public static class ChangedTitle {
public static final class ChangedTitle {
final String oldTitle;
final String newTitle;
@@ -68,6 +65,7 @@ public abstract class TerminalTestCase extends TestCase {
@Override
public boolean equals(Object o) {
if (!(o instanceof ChangedTitle)) return false;
ChangedTitle other = (ChangedTitle) o;
return Objects.equals(oldTitle, other.oldTitle) && Objects.equals(newTitle, other.newTitle);
}
@@ -115,8 +113,8 @@ public abstract class TerminalTestCase extends TestCase {
}
}
private static class LineWrapper {
TerminalRow mLine;
private static final class LineWrapper {
final TerminalRow mLine;
public LineWrapper(TerminalRow line) {
mLine = line;
@@ -129,7 +127,7 @@ public abstract class TerminalTestCase extends TestCase {
@Override
public boolean equals(Object o) {
return ((LineWrapper) o).mLine == mLine;
return o instanceof LineWrapper && ((LineWrapper) o).mLine == mLine;
}
}

View File

@@ -4,10 +4,10 @@ import junit.framework.TestCase;
public class TextStyleTest extends TestCase {
private static final int[] ALL_EFFECTS = new int[] { 0, TextStyle.CHARACTER_ATTRIBUTE_BOLD, TextStyle.CHARACTER_ATTRIBUTE_ITALIC,
private static final int[] ALL_EFFECTS = new int[]{0, TextStyle.CHARACTER_ATTRIBUTE_BOLD, TextStyle.CHARACTER_ATTRIBUTE_ITALIC,
TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE, TextStyle.CHARACTER_ATTRIBUTE_BLINK, TextStyle.CHARACTER_ATTRIBUTE_INVERSE,
TextStyle.CHARACTER_ATTRIBUTE_INVISIBLE, TextStyle.CHARACTER_ATTRIBUTE_STRIKETHROUGH, TextStyle.CHARACTER_ATTRIBUTE_PROTECTED,
TextStyle.CHARACTER_ATTRIBUTE_DIM };
TextStyle.CHARACTER_ATTRIBUTE_DIM};
public void testEncodingSingle() {
for (int fx : ALL_EFFECTS) {
@@ -34,7 +34,7 @@ public class TextStyleTest extends TestCase {
public void testEncodingStrikeThrough() {
int encoded = TextStyle.encode(TextStyle.COLOR_INDEX_FOREGROUND, TextStyle.COLOR_INDEX_BACKGROUND,
TextStyle.CHARACTER_ATTRIBUTE_STRIKETHROUGH);
assertTrue((TextStyle.decodeEffect(encoded) | TextStyle.CHARACTER_ATTRIBUTE_STRIKETHROUGH) != 0);
assertTrue((TextStyle.decodeEffect(encoded) & TextStyle.CHARACTER_ATTRIBUTE_STRIKETHROUGH) != 0);
}
public void testEncodingProtected() {

View File

@@ -10,14 +10,14 @@ public class UnicodeInputTest extends TerminalTestCase {
// continue with valid successor bytes (see Table 3-7), it must not consume the successor bytes as part of the ill-formed
// subsequence whenever those successor bytes themselves constitute part of a well-formed UTF-8 code unit subsequence."
withTerminalSized(5, 5);
mTerminal.append(new byte[] { (byte) 0b11101111, (byte) 'a' }, 2);
mTerminal.append(new byte[]{(byte) 0b11101111, (byte) 'a'}, 2);
assertLineIs(0, ((char) TerminalEmulator.UNICODE_REPLACEMENT_CHAR) + "a ");
}
public void testUnassignedCodePoint() throws UnsupportedEncodingException {
withTerminalSized(3, 3);
// UTF-8 for U+C2541, an unassigned code point:
byte[] b = new byte[] { (byte) 0xf3, (byte) 0x82, (byte) 0x95, (byte) 0x81 };
byte[] b = new byte[]{(byte) 0xf3, (byte) 0x82, (byte) 0x95, (byte) 0x81};
mTerminal.append(b, b.length);
enterString("Y");
assertEquals(1, Character.charCount(TerminalEmulator.UNICODE_REPLACEMENT_CHAR));
@@ -26,10 +26,10 @@ public class UnicodeInputTest extends TerminalTestCase {
public void testStuff() {
withTerminalSized(80, 24);
byte[] b = new byte[] { (byte) 0xf3, (byte) 0x82, (byte) 0x95, (byte) 0x81, (byte) 0x61, (byte) 0x38, (byte) 0xe7, (byte) 0x8f,
byte[] b = new byte[]{(byte) 0xf3, (byte) 0x82, (byte) 0x95, (byte) 0x81, (byte) 0x61, (byte) 0x38, (byte) 0xe7, (byte) 0x8f,
(byte) 0xae, (byte) 0xc2, (byte) 0x9f, (byte) 0xe8, (byte) 0xa0, (byte) 0x9f, (byte) 0xe8, (byte) 0x8c, (byte) 0xa4,
(byte) 0xed, (byte) 0x93, (byte) 0x89, (byte) 0xef, (byte) 0xbf, (byte) 0xbd, (byte) 0x42, (byte) 0xc2, (byte) 0x9b,
(byte) 0xe6, (byte) 0x87, (byte) 0x89, (byte) 0x5a };
(byte) 0xe6, (byte) 0x87, (byte) 0x89, (byte) 0x5a};
mTerminal.append(b, b.length);
}
@@ -80,8 +80,16 @@ public class UnicodeInputTest extends TerminalTestCase {
public void testOverlongUtf8Encoding() throws Exception {
// U+0020 should be encoded as 0x20, 0xc0 0xa0 is an overlong encoding
// so should be replaced with the replacement char U+FFFD.
withTerminalSized(5, 5).mTerminal.append(new byte[] { (byte) 0xc0, (byte) 0xa0, 'Y' }, 3);
withTerminalSized(5, 5).mTerminal.append(new byte[]{(byte) 0xc0, (byte) 0xa0, 'Y'}, 3);
assertLineIs(0, "\uFFFDY ");
}
public void testWideCharacterWithoutWrapping() throws Exception {
// With wraparound disabled. The behaviour when a wide character is output with cursor in
// the last column when autowrap is disabled is not obvious, but we expect the wide
// character to be ignored here.
withTerminalSized(3, 3).enterString("\033[?7l").enterString("枝枝枝").assertLinesAre("", " ", " ");
enterString("a枝").assertLinesAre("枝a", " ", " ");
}
}

View File

@@ -5,7 +5,7 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.3.1'
classpath 'com.android.tools.build:gradle:1.5.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files

Binary file not shown.

View File

@@ -1,6 +1,6 @@
#Thu Oct 22 22:50:58 CEST 2015
#Wed Dec 23 01:44:47 CET 2015
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.7-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-bin.zip

4
gradlew vendored
View File

@@ -56,9 +56,9 @@ while [ -h "$PRG" ] ; do
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >&-
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >&-
cd "$SAVED" >/dev/null
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar