Compare commits
78 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cdccc2c433 | ||
|
|
070436a6ed | ||
|
|
69ded4b33e | ||
|
|
e5a8c0eb4a | ||
|
|
29815fb3f0 | ||
|
|
e930ac08aa | ||
|
|
3b969a0077 | ||
|
|
ae717d8f5f | ||
|
|
6c55c3c1be | ||
|
|
c0a0822843 | ||
|
|
08b08d1c47 | ||
|
|
c76cec186a | ||
|
|
5ba3f7cf6d | ||
|
|
468f878a38 | ||
|
|
c50a367063 | ||
|
|
4189f598b9 | ||
|
|
cb67e805de | ||
|
|
3f83368f42 | ||
|
|
8c7462678f | ||
|
|
6f11390cc4 | ||
|
|
33d390a228 | ||
|
|
0332779d6a | ||
|
|
f19575b942 | ||
|
|
6a95809da3 | ||
|
|
ac8dab70ef | ||
|
|
243285f7c2 | ||
|
|
b6182216d5 | ||
|
|
e206121bbc | ||
|
|
deceffad00 | ||
|
|
c19909cef1 | ||
|
|
5b7e40638c | ||
|
|
a3673d1af5 | ||
|
|
d5f9cf85c9 | ||
|
|
549f09573f | ||
|
|
94e5bc86fb | ||
|
|
370ac2bd89 | ||
|
|
7041f41981 | ||
|
|
dd6a21257b | ||
|
|
445da0c4ea | ||
|
|
92980824b1 | ||
|
|
7d42b07a32 | ||
|
|
e502b9169f | ||
|
|
9f2d887ca0 | ||
|
|
e6aacc5f08 | ||
|
|
ff935be54a | ||
|
|
4e76162346 | ||
|
|
5fa4f2647b | ||
|
|
81b5889a26 | ||
|
|
514f59258a | ||
|
|
9f79393aa5 | ||
|
|
e49d514236 | ||
|
|
4e1462326c | ||
|
|
b0f1773a92 | ||
|
|
af9f28c010 | ||
|
|
fef0c66868 | ||
|
|
7a5da83ce2 | ||
|
|
97f01387b9 | ||
|
|
012ff83a06 | ||
|
|
a082c63849 | ||
|
|
8c27084086 | ||
|
|
f4fb45cbd6 | ||
|
|
0605fc4843 | ||
|
|
3bbd61f9d7 | ||
|
|
42fc0ea1cd | ||
|
|
6218d08037 | ||
|
|
10fe238ddb | ||
|
|
fe41cd486f | ||
|
|
bda80547ad | ||
|
|
70a786613d | ||
|
|
e4220a7ab1 | ||
|
|
bd45837d93 | ||
|
|
ddf5341b86 | ||
|
|
85a48a79a3 | ||
|
|
e3512c957d | ||
|
|
330301899a | ||
|
|
2a36b915cb | ||
|
|
c986a46fb9 | ||
|
|
ed8bd4b569 |
20
.cirrus.yml
Normal file
20
.cirrus.yml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
container:
|
||||||
|
image: cirrusci/android-sdk:28
|
||||||
|
cpu: 2
|
||||||
|
memory: 8G
|
||||||
|
|
||||||
|
task:
|
||||||
|
name: tests
|
||||||
|
script: ./gradlew test
|
||||||
|
|
||||||
|
task:
|
||||||
|
name: debug-build
|
||||||
|
|
||||||
|
depends_on:
|
||||||
|
- tests
|
||||||
|
|
||||||
|
script: |
|
||||||
|
./gradlew assembleDebug
|
||||||
|
|
||||||
|
output_artifacts:
|
||||||
|
path: "./app/build/outputs/apk/debug/*.apk"
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -7,6 +7,8 @@ build/
|
|||||||
*.apk
|
*.apk
|
||||||
*.so
|
*.so
|
||||||
.externalNativeBuild
|
.externalNativeBuild
|
||||||
|
.cxx
|
||||||
|
*.zip
|
||||||
|
|
||||||
# Crashlytics configuations
|
# Crashlytics configuations
|
||||||
com_crashlytics_export_strings.xml
|
com_crashlytics_export_strings.xml
|
||||||
|
|||||||
34
.travis.yml
34
.travis.yml
@@ -1,34 +0,0 @@
|
|||||||
sudo: false
|
|
||||||
language: android
|
|
||||||
jdk: oraclejdk8
|
|
||||||
|
|
||||||
env:
|
|
||||||
global:
|
|
||||||
# The next declaration is the encrypted COVERITY_SCAN_TOKEN, created
|
|
||||||
# via the "travis encrypt" command using the project repo's public key
|
|
||||||
- secure: "LdajbHNfRlpnqzhX5KY2Vr7KtzU9vXDs1TCNn93J6Dt522f2AaiyUDJvISvz+uslk0WJiS5bB5vGwQmXginxz6Qi6uMgMbjWXulv1vfs6ZviKpUX348DOp1qKPa8WfVNB66F84SwGIfc8cRMAgCFw79l/DFgLErubF8vKo1wZ8Hmvrz//+RJ0BGMa3YRc4VyJhAL0P+0Wc1Q2Im7R9EovAxC5pZXBIMSgr6g5GzLWPisbNLXpMPGsDeYhcenO6XCtCCy+aNxUYM8vcrLDzlVXR5Hy7KEs/MGRTS0Yk13TWUEYa5wBpKelFTszdWYLVn5ANreh/aXRVfHpnW3epotMYguLx1kSvOhWEnc4F+qqv3nle2LpDg9Y9bcLyTTcYnPl9smqEVVjEDu0FoIr1V58xkG4Oc6BPIvLRjlMVU96PXh2HxMLuGsJ/xM+uAFU9oVMbC07xn42Eu5O4NHOHJNOwMWac4/lSKRK8W/7/vWuXj5vhkD9ZsGVpN70UtY5HAfNUGADnTeDblvjgFTNZ2mUN/u0o7Z8ZFURYllZ9YU+Vr2nPf9CAhVBjuwFWx8uRQpAg1aDmc1dVMJijRBeBeU/uWhYqsGp34wkNEl8VGzob4R4QTyI8+T7CndGqKVmbTK/SjqKhjjPpbXIAfOH+JtxvAnNmb8XeQSJ32uK2nexFo="
|
|
||||||
|
|
||||||
android:
|
|
||||||
components:
|
|
||||||
- platform-tools
|
|
||||||
- tools
|
|
||||||
- build-tools-28.0.3
|
|
||||||
- android-28
|
|
||||||
- extra-android-m2repository
|
|
||||||
|
|
||||||
before_install:
|
|
||||||
- yes | sdkmanager "ndk-bundle"
|
|
||||||
- yes | sdkmanager "platforms;android-28"
|
|
||||||
|
|
||||||
script:
|
|
||||||
- ./gradlew testDebugUnitTest
|
|
||||||
|
|
||||||
addons:
|
|
||||||
coverity_scan:
|
|
||||||
project:
|
|
||||||
name: "termux/termux-app"
|
|
||||||
description: "Terminal emulator and Linux environment for Android"
|
|
||||||
notification_email: fredrik@fornwall.net
|
|
||||||
build_command_prepend: "./gradlew clean"
|
|
||||||
build_command: "./gradlew build"
|
|
||||||
branch_pattern: master
|
|
||||||
79
README.md
79
README.md
@@ -1,32 +1,63 @@
|
|||||||
[](https://travis-ci.org/termux/termux-app)
|
# Termux application
|
||||||
|
|
||||||
|
[](https://cirrus-ci.com/termux/termux-app)
|
||||||
[](https://gitter.im/termux/termux)
|
[](https://gitter.im/termux/termux)
|
||||||
|
|
||||||
|
[Termux](https://termux.com) is an Android terminal application and Linux environment.
|
||||||
|
|
||||||
Termux app
|
- [Termux Reddit community](https://reddit.com/r/termux)
|
||||||
==========
|
- [Termux Wiki](https://wiki.termux.com/wiki/)
|
||||||
|
- [Termux Twitter](http://twitter.com/termux/)
|
||||||
|
|
||||||
[Termux](https://termux.com) is an Android terminal app and Linux environment.
|
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)
|
||||||
|
|
||||||
* [Termux on Google Play Store](https://play.google.com/store/apps/details?id=com.termux)
|
## Installation
|
||||||
* [Termux on F-Droid](https://f-droid.org/repository/browse/?fdid=com.termux)
|
|
||||||
* [Termux Google+ community](http://termux.com/community/)
|
|
||||||
* [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)
|
Termux:Widget application can be obtained from:
|
||||||
|
|
||||||
Terminal resources
|
- [Google Play](https://play.google.com/store/apps/details?id=com.termux)
|
||||||
==================
|
- [F-Droid](https://f-droid.org/en/packages/com.termux/)
|
||||||
* [XTerm control sequences](http://invisible-island.net/xterm/ctlseqs/ctlseqs.html)
|
- [Kali Nethunter Store](https://store.nethunter.com/en/packages/com.termux/)
|
||||||
* [vt100.net](http://vt100.net/)
|
|
||||||
* [Terminal codes (ANSI and terminfo equivalents)](http://wiki.bash-hackers.org/scripting/terminalcodes)
|
|
||||||
|
|
||||||
Terminal emulators
|
Additionally we offer development builds for those who want to try out latest
|
||||||
==================
|
features ready to be included in future versions. Such build can be obtained
|
||||||
* 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).
|
directly from [Cirrus CI artifacts](https://api.cirrus-ci.com/v1/artifact/github/termux/termux-app/debug-build/output/app/build/outputs/apk/debug/app-debug.apk).
|
||||||
* 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).
|
Signature keys of all offered builds are different. Before you switch the
|
||||||
* 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).
|
installation source, you will have to uninstall the Termux application and
|
||||||
* xterm: The grandfather of terminal emulators. [Source](http://invisible-island.net/datafiles/release/xterm.tar.gz).
|
all currently installed plugins.
|
||||||
* 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).
|
## Terminal resources
|
||||||
|
|
||||||
|
- [XTerm control sequences](http://invisible-island.net/xterm/ctlseqs/ctlseqs.html)
|
||||||
|
- [vt100.net](http://vt100.net/)
|
||||||
|
- [Terminal codes (ANSI and terminfo equivalents)](http://wiki.bash-hackers.org/scripting/terminalcodes)
|
||||||
|
|
||||||
|
## Terminal emulators
|
||||||
|
|
||||||
|
- VTE (libvte): Terminal emulator widget for GTK+, mainly used in gnome-terminal.
|
||||||
|
[Source](https://github.com/GNOME/vte), [Open Issues](https://bugzilla.gnome.org/buglist.cgi?quicksearch=product%3A%22vte%22+),
|
||||||
|
and [All (including closed) issues](https://bugzilla.gnome.org/buglist.cgi?bug_status=RESOLVED&bug_status=VERIFIED&chfield=resolution&chfieldfrom=-2000d&chfieldvalue=FIXED&product=vte&resolution=FIXED).
|
||||||
|
|
||||||
|
- iTerm 2: OS X terminal application. [Source](https://github.com/gnachman/iTerm2),
|
||||||
|
[Issues](https://gitlab.com/gnachman/iterm2/issues) and [Documentation](http://www.iterm2.com/documentation.html)
|
||||||
|
(which includes [iTerm2 proprietary escape codes](http://www.iterm2.com/documentation-escape-codes.html)).
|
||||||
|
|
||||||
|
- Konsole: KDE terminal application. [Source](https://projects.kde.org/projects/kde/applications/konsole/repository),
|
||||||
|
in particular [tests](https://projects.kde.org/projects/kde/applications/konsole/repository/revisions/master/show/tests),
|
||||||
|
[Bugs](https://bugs.kde.org/buglist.cgi?bug_severity=critical&bug_severity=grave&bug_severity=major&bug_severity=crash&bug_severity=normal&bug_severity=minor&bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&product=konsole)
|
||||||
|
and [Wishes](https://bugs.kde.org/buglist.cgi?bug_severity=wishlist&bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&product=konsole).
|
||||||
|
|
||||||
|
- hterm: JavaScript terminal implementation from Chromium. [Source](https://github.com/chromium/hterm),
|
||||||
|
including [tests](https://github.com/chromium/hterm/blob/master/js/hterm_vt_tests.js),
|
||||||
|
and [Google group](https://groups.google.com/a/chromium.org/forum/#!forum/chromium-hterm).
|
||||||
|
|
||||||
|
- xterm: The grandfather of terminal emulators.
|
||||||
|
[Source](http://invisible-island.net/datafiles/release/xterm.tar.gz).
|
||||||
|
|
||||||
|
- Connectbot: Android SSH client. [Source](https://github.com/connectbot/connectbot)
|
||||||
|
|
||||||
|
- Android Terminal Emulator: Android terminal app which Termux terminal handling
|
||||||
|
is based on. Inactive. [Source](https://github.com/jackpal/Android-Terminal-Emulator).
|
||||||
|
|||||||
112
app/build.gradle
112
app/build.gradle
@@ -1,4 +1,6 @@
|
|||||||
apply plugin: 'com.android.application'
|
plugins {
|
||||||
|
id "com.android.application"
|
||||||
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 28
|
compileSdkVersion 28
|
||||||
@@ -12,10 +14,30 @@ android {
|
|||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "com.termux"
|
applicationId "com.termux"
|
||||||
minSdkVersion 21
|
minSdkVersion 24
|
||||||
targetSdkVersion 28
|
targetSdkVersion 28
|
||||||
versionCode 66
|
versionCode 82
|
||||||
versionName "0.66"
|
versionName "0.82"
|
||||||
|
|
||||||
|
externalNativeBuild {
|
||||||
|
ndkBuild {
|
||||||
|
cFlags "-std=c11", "-Wall", "-Wextra", "-Werror", "-Os", "-fno-stack-protector", "-Wl,--gc-sections"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ndk {
|
||||||
|
abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
signingConfigs {
|
||||||
|
debug {
|
||||||
|
storeFile file('dev_keystore.jks')
|
||||||
|
keyAlias 'alias'
|
||||||
|
storePassword 'xrj45yWGLbsO7W0v'
|
||||||
|
keyPassword 'xrj45yWGLbsO7W0v'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
@@ -24,14 +46,96 @@ android {
|
|||||||
shrinkResources true
|
shrinkResources true
|
||||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
debug {
|
||||||
|
signingConfig signingConfigs.debug
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility JavaVersion.VERSION_1_8
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
targetCompatibility JavaVersion.VERSION_1_8
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
}
|
}
|
||||||
|
|
||||||
|
externalNativeBuild {
|
||||||
|
ndkBuild {
|
||||||
|
path "src/main/cpp/Android.mk"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
testImplementation 'junit:junit:4.12'
|
testImplementation 'junit:junit:4.12'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
task versionName {
|
||||||
|
doLast {
|
||||||
|
print android.defaultConfig.versionName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def downloadBootstrap(String arch, String expectedChecksum, int version) {
|
||||||
|
def digest = java.security.MessageDigest.getInstance("SHA-256")
|
||||||
|
|
||||||
|
def localUrl = "src/main/cpp/bootstrap-" + arch + ".zip"
|
||||||
|
def file = new File(projectDir, localUrl)
|
||||||
|
if (file.exists()) {
|
||||||
|
def buffer = new byte[8192]
|
||||||
|
def input = new FileInputStream(file)
|
||||||
|
while (true) {
|
||||||
|
def readBytes = input.read(buffer)
|
||||||
|
if (readBytes < 0) break
|
||||||
|
digest.update(buffer, 0, readBytes)
|
||||||
|
}
|
||||||
|
def checksum = new BigInteger(1, digest.digest()).toString(16)
|
||||||
|
if (checksum == expectedChecksum) {
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
logger.quiet("Deleting old local file with wrong hash: " + localUrl)
|
||||||
|
file.delete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def remoteUrl = "https://bintray.com/termux/bootstrap/download_file?file_path=bootstrap-" + arch + "-v" + version + ".zip"
|
||||||
|
logger.quiet("Downloading " + remoteUrl + " ...")
|
||||||
|
|
||||||
|
file.parentFile.mkdirs()
|
||||||
|
def out = new BufferedOutputStream(new FileOutputStream(file))
|
||||||
|
|
||||||
|
def connection = new URL(remoteUrl).openConnection()
|
||||||
|
connection.setInstanceFollowRedirects(true)
|
||||||
|
def digestStream = new java.security.DigestInputStream(connection.inputStream, digest)
|
||||||
|
out << digestStream
|
||||||
|
out.close()
|
||||||
|
|
||||||
|
def checksum = new BigInteger(1, digest.digest()).toString(16)
|
||||||
|
if (checksum != expectedChecksum) {
|
||||||
|
file.delete()
|
||||||
|
throw new GradleException("Wrong checksum for " + remoteUrl + ": expected: " + expectedChecksum + ", actual: " + checksum)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clean {
|
||||||
|
doLast {
|
||||||
|
def tree = fileTree(new File(projectDir, 'src/main/cpp'))
|
||||||
|
tree.include 'bootstrap-*.zip'
|
||||||
|
tree.each { it.delete() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
task downloadBootstraps(){
|
||||||
|
doLast {
|
||||||
|
def version = 18
|
||||||
|
downloadBootstrap("aarch64", "1a4c08a696d452b58f69102428239ec0c07521c0ca9f48b23ef70ae0e5e3d4f8", version)
|
||||||
|
downloadBootstrap("arm", "bff11f2c7e9c1055a22fc5f20bb7507b75f6034e0f5d591ec6725b3407981b85", version)
|
||||||
|
downloadBootstrap("i686", "6fb93020db2807337d82a1537e24612400cacbd10cf4bccaeb0714d51e653da1", version)
|
||||||
|
downloadBootstrap("x86_64", "a6067e5decc486dcad190c1ed9e15366c798e5e7d9b9b9ee6b4b8231290524c3", version)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
afterEvaluate {
|
||||||
|
android.applicationVariants.all { variant ->
|
||||||
|
variant.javaCompileProvider.get().dependsOn(downloadBootstraps)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
BIN
app/dev_keystore.jks
Normal file
BIN
app/dev_keystore.jks
Normal file
Binary file not shown.
@@ -8,16 +8,17 @@
|
|||||||
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
|
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
|
||||||
<uses-feature android:name="android.software.leanback" android:required="false" />
|
<uses-feature android:name="android.software.leanback" android:required="false" />
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
<uses-permission android:name="android.permission.VIBRATE" />
|
<uses-permission android:name="android.permission.VIBRATE" />
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
|
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:extractNativeLibs="true"
|
android:extractNativeLibs="true"
|
||||||
android:allowBackup="true"
|
android:allowBackup="false"
|
||||||
android:fullBackupContent="@xml/backupscheme"
|
|
||||||
android:icon="@drawable/ic_launcher"
|
android:icon="@drawable/ic_launcher"
|
||||||
android:banner="@drawable/banner"
|
android:banner="@drawable/banner"
|
||||||
android:label="@string/application_name"
|
android:label="@string/application_name"
|
||||||
@@ -30,6 +31,7 @@
|
|||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="com.termux.app.TermuxActivity"
|
android:name="com.termux.app.TermuxActivity"
|
||||||
|
android:label="@string/application_name"
|
||||||
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
|
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
|
||||||
android:launchMode="singleTask"
|
android:launchMode="singleTask"
|
||||||
android:resizeableActivity="true"
|
android:resizeableActivity="true"
|
||||||
|
|||||||
5
app/src/main/cpp/Android.mk
Normal file
5
app/src/main/cpp/Android.mk
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
LOCAL_PATH:= $(call my-dir)
|
||||||
|
include $(CLEAR_VARS)
|
||||||
|
LOCAL_MODULE := libtermux-bootstrap
|
||||||
|
LOCAL_SRC_FILES := termux-bootstrap-zip.S termux-bootstrap.c
|
||||||
|
include $(BUILD_SHARED_LIBRARY)
|
||||||
18
app/src/main/cpp/termux-bootstrap-zip.S
Normal file
18
app/src/main/cpp/termux-bootstrap-zip.S
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
.global blob
|
||||||
|
.global blob_size
|
||||||
|
.section .rodata
|
||||||
|
blob:
|
||||||
|
#if defined __i686__
|
||||||
|
.incbin "bootstrap-i686.zip"
|
||||||
|
#elif defined __x86_64__
|
||||||
|
.incbin "bootstrap-x86_64.zip"
|
||||||
|
#elif defined __aarch64__
|
||||||
|
.incbin "bootstrap-aarch64.zip"
|
||||||
|
#elif defined __arm__
|
||||||
|
.incbin "bootstrap-arm.zip"
|
||||||
|
#else
|
||||||
|
# error Unsupported arch
|
||||||
|
#endif
|
||||||
|
1:
|
||||||
|
blob_size:
|
||||||
|
.int 1b - blob
|
||||||
11
app/src/main/cpp/termux-bootstrap.c
Normal file
11
app/src/main/cpp/termux-bootstrap.c
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
#include <jni.h>
|
||||||
|
|
||||||
|
extern jbyte blob[];
|
||||||
|
extern int blob_size;
|
||||||
|
|
||||||
|
JNIEXPORT jbyteArray JNICALL Java_com_termux_app_TermuxInstaller_getZip(JNIEnv *env, __attribute__((__unused__)) jobject This)
|
||||||
|
{
|
||||||
|
jbyteArray ret = (*env)->NewByteArray(env, blob_size);
|
||||||
|
(*env)->SetByteArrayRegion(env, ret, 0, blob_size, blob);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
@@ -93,32 +93,60 @@ public final class BackgroundJob {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String[] buildEnvironment(boolean failSafe, String cwd) {
|
private static void addToEnvIfPresent(List<String> environment, String name) {
|
||||||
|
String value = System.getenv(name);
|
||||||
|
if (value != null) {
|
||||||
|
environment.add(name + "=" + value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static String[] buildEnvironment(boolean failSafe, String cwd) {
|
||||||
new File(TermuxService.HOME_PATH).mkdirs();
|
new File(TermuxService.HOME_PATH).mkdirs();
|
||||||
|
|
||||||
if (cwd == null) cwd = TermuxService.HOME_PATH;
|
if (cwd == null) cwd = TermuxService.HOME_PATH;
|
||||||
|
|
||||||
final String termEnv = "TERM=xterm-256color";
|
List<String> environment = new ArrayList<>();
|
||||||
final String homeEnv = "HOME=" + TermuxService.HOME_PATH;
|
|
||||||
final String prefixEnv = "PREFIX=" + TermuxService.PREFIX_PATH;
|
environment.add("TERM=xterm-256color");
|
||||||
final String androidRootEnv = "ANDROID_ROOT=" + System.getenv("ANDROID_ROOT");
|
environment.add("HOME=" + TermuxService.HOME_PATH);
|
||||||
final String androidDataEnv = "ANDROID_DATA=" + System.getenv("ANDROID_DATA");
|
environment.add("PREFIX=" + TermuxService.PREFIX_PATH);
|
||||||
|
environment.add("BOOTCLASSPATH" + System.getenv("BOOTCLASSPATH"));
|
||||||
|
environment.add("ANDROID_ROOT=" + System.getenv("ANDROID_ROOT"));
|
||||||
|
environment.add("ANDROID_DATA=" + System.getenv("ANDROID_DATA"));
|
||||||
// EXTERNAL_STORAGE is needed for /system/bin/am to work on at least
|
// EXTERNAL_STORAGE is needed for /system/bin/am to work on at least
|
||||||
// Samsung S7 - see https://plus.google.com/110070148244138185604/posts/gp8Lk3aCGp3.
|
// Samsung S7 - see https://plus.google.com/110070148244138185604/posts/gp8Lk3aCGp3.
|
||||||
final String externalStorageEnv = "EXTERNAL_STORAGE=" + System.getenv("EXTERNAL_STORAGE");
|
environment.add("EXTERNAL_STORAGE=" + System.getenv("EXTERNAL_STORAGE"));
|
||||||
|
// ANDROID_RUNTIME_ROOT and ANDROID_TZDATA_ROOT are required for `am` to run on Android Q
|
||||||
|
addToEnvIfPresent(environment, "ANDROID_RUNTIME_ROOT");
|
||||||
|
addToEnvIfPresent(environment, "ANDROID_TZDATA_ROOT");
|
||||||
if (failSafe) {
|
if (failSafe) {
|
||||||
// Keep the default path so that system binaries can be used in the failsafe session.
|
// Keep the default path so that system binaries can be used in the failsafe session.
|
||||||
final String pathEnv = "PATH=" + System.getenv("PATH");
|
environment.add("PATH= " + System.getenv("PATH"));
|
||||||
return new String[]{termEnv, homeEnv, prefixEnv, androidRootEnv, androidDataEnv, pathEnv, externalStorageEnv};
|
|
||||||
} else {
|
} else {
|
||||||
final String ldEnv = "LD_LIBRARY_PATH=" + TermuxService.PREFIX_PATH + "/lib";
|
if (shouldAddLdLibraryPath()) {
|
||||||
final String langEnv = "LANG=en_US.UTF-8";
|
environment.add("LD_LIBRARY_PATH=" + TermuxService.PREFIX_PATH + "/lib");
|
||||||
final String pathEnv = "PATH=" + TermuxService.PREFIX_PATH + "/bin:" + TermuxService.PREFIX_PATH + "/bin/applets";
|
}
|
||||||
final String pwdEnv = "PWD=" + cwd;
|
environment.add("LANG=en_US.UTF-8");
|
||||||
final String tmpdirEnv = "TMPDIR=" + TermuxService.PREFIX_PATH + "/tmp";
|
environment.add("PATH=" + TermuxService.PREFIX_PATH + "/bin:" + TermuxService.PREFIX_PATH + "/bin/applets");
|
||||||
|
environment.add("PWD=" + cwd);
|
||||||
return new String[]{termEnv, homeEnv, prefixEnv, ldEnv, langEnv, pathEnv, pwdEnv, androidRootEnv, androidDataEnv, externalStorageEnv, tmpdirEnv};
|
environment.add("TMPDIR=" + TermuxService.PREFIX_PATH + "/tmp");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return environment.toArray(new String[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean shouldAddLdLibraryPath() {
|
||||||
|
try (BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(TermuxService.PREFIX_PATH + "/etc/apt/sources.list")))) {
|
||||||
|
String line;
|
||||||
|
while ((line = in.readLine()) != null) {
|
||||||
|
if (!line.startsWith("#") && line.contains("//termux.net stable")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(LOG_TAG, "Error trying to read sources.list", e);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int getPid(Process p) {
|
public static int getPid(Process p) {
|
||||||
|
|||||||
63
app/src/main/java/com/termux/app/BellUtil.java
Normal file
63
app/src/main/java/com/termux/app/BellUtil.java
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
package com.termux.app;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.os.SystemClock;
|
||||||
|
import android.os.Vibrator;
|
||||||
|
|
||||||
|
public class BellUtil {
|
||||||
|
private static BellUtil instance = null;
|
||||||
|
private static final Object lock = new Object();
|
||||||
|
|
||||||
|
public static BellUtil getInstance(Context context) {
|
||||||
|
if (instance == null) {
|
||||||
|
synchronized (lock) {
|
||||||
|
if (instance == null) {
|
||||||
|
instance = new BellUtil((Vibrator) context.getApplicationContext().getSystemService(Context.VIBRATOR_SERVICE));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final long DURATION = 50;
|
||||||
|
private static final long MIN_PAUSE = 3 * DURATION;
|
||||||
|
|
||||||
|
private final Handler handler = new Handler(Looper.getMainLooper());
|
||||||
|
private long lastBell = 0;
|
||||||
|
private final Runnable bellRunnable;
|
||||||
|
|
||||||
|
private BellUtil(final Vibrator vibrator) {
|
||||||
|
bellRunnable = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (vibrator != null) {
|
||||||
|
vibrator.vibrate(DURATION);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void doBell() {
|
||||||
|
long now = now();
|
||||||
|
long timeSinceLastBell = now - lastBell;
|
||||||
|
|
||||||
|
if (timeSinceLastBell < 0) {
|
||||||
|
// there is a next bell pending; don't schedule another one
|
||||||
|
} else if (timeSinceLastBell < MIN_PAUSE) {
|
||||||
|
// there was a bell recently, scheudle the next one
|
||||||
|
handler.postDelayed(bellRunnable, MIN_PAUSE - timeSinceLastBell);
|
||||||
|
lastBell = lastBell + MIN_PAUSE;
|
||||||
|
} else {
|
||||||
|
// the last bell was long ago, do it now
|
||||||
|
bellRunnable.run();
|
||||||
|
lastBell = now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private long now() {
|
||||||
|
return SystemClock.uptimeMillis();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ package com.termux.app;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
import android.provider.Settings;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
|
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
@@ -73,6 +74,18 @@ public final class ExtraKeysView extends GridLayout {
|
|||||||
put("RIGHT", KeyEvent.KEYCODE_DPAD_RIGHT);
|
put("RIGHT", KeyEvent.KEYCODE_DPAD_RIGHT);
|
||||||
put("DOWN", KeyEvent.KEYCODE_DPAD_DOWN);
|
put("DOWN", KeyEvent.KEYCODE_DPAD_DOWN);
|
||||||
put("ENTER", KeyEvent.KEYCODE_ENTER);
|
put("ENTER", KeyEvent.KEYCODE_ENTER);
|
||||||
|
put("F1", KeyEvent.KEYCODE_F1);
|
||||||
|
put("F2", KeyEvent.KEYCODE_F2);
|
||||||
|
put("F3", KeyEvent.KEYCODE_F3);
|
||||||
|
put("F4", KeyEvent.KEYCODE_F4);
|
||||||
|
put("F5", KeyEvent.KEYCODE_F5);
|
||||||
|
put("F6", KeyEvent.KEYCODE_F6);
|
||||||
|
put("F7", KeyEvent.KEYCODE_F7);
|
||||||
|
put("F8", KeyEvent.KEYCODE_F8);
|
||||||
|
put("F9", KeyEvent.KEYCODE_F9);
|
||||||
|
put("F10", KeyEvent.KEYCODE_F10);
|
||||||
|
put("F11", KeyEvent.KEYCODE_F11);
|
||||||
|
put("F12", KeyEvent.KEYCODE_F12);
|
||||||
}};
|
}};
|
||||||
|
|
||||||
static void sendKey(View view, String keyName) {
|
static void sendKey(View view, String keyName) {
|
||||||
@@ -84,7 +97,7 @@ public final class ExtraKeysView extends GridLayout {
|
|||||||
} else {
|
} else {
|
||||||
// not a control char
|
// not a control char
|
||||||
TerminalSession session = terminalView.getCurrentSession();
|
TerminalSession session = terminalView.getCurrentSession();
|
||||||
if (session != null)
|
if (session != null && keyName.length() > 0)
|
||||||
session.write(keyName);
|
session.write(keyName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -115,10 +128,14 @@ public final class ExtraKeysView extends GridLayout {
|
|||||||
|
|
||||||
if (! state.isOn)
|
if (! state.isOn)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
if (state.button == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (state.button.isPressed())
|
if (state.button.isPressed())
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (! state.button.isChecked())
|
if (! state.button.isChecked())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@@ -334,7 +351,15 @@ public final class ExtraKeysView extends GridLayout {
|
|||||||
|
|
||||||
final Button finalButton = button;
|
final Button finalButton = button;
|
||||||
button.setOnClickListener(v -> {
|
button.setOnClickListener(v -> {
|
||||||
finalButton.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP);
|
if (Settings.System.getInt(getContext().getContentResolver(),
|
||||||
|
Settings.System.HAPTIC_FEEDBACK_ENABLED, 0) != 0) {
|
||||||
|
|
||||||
|
// Depending on DnD settings, value can be >1 but 0 means "disabled".
|
||||||
|
if (Settings.Global.getInt(getContext().getContentResolver(), "zen_mode", 0) < 1) {
|
||||||
|
finalButton.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
View root = getRootView();
|
View root = getRootView();
|
||||||
if(Arrays.asList("CTRL", "ALT", "FN").contains(buttonText)) {
|
if(Arrays.asList("CTRL", "ALT", "FN").contains(buttonText)) {
|
||||||
ToggleButton self = (ToggleButton) finalButton;
|
ToggleButton self = (ToggleButton) finalButton;
|
||||||
@@ -402,11 +427,7 @@ public final class ExtraKeysView extends GridLayout {
|
|||||||
|
|
||||||
LayoutParams param = new GridLayout.LayoutParams();
|
LayoutParams param = new GridLayout.LayoutParams();
|
||||||
param.width = 0;
|
param.width = 0;
|
||||||
if(Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP) { // special handle api 21
|
param.height = 0;
|
||||||
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.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);
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ import android.net.Uri;
|
|||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.Vibrator;
|
|
||||||
import android.text.SpannableString;
|
import android.text.SpannableString;
|
||||||
import android.text.Spanned;
|
import android.text.Spanned;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
@@ -83,6 +82,8 @@ import androidx.viewpager.widget.ViewPager;
|
|||||||
*/
|
*/
|
||||||
public final class TermuxActivity extends Activity implements ServiceConnection {
|
public final class TermuxActivity extends Activity implements ServiceConnection {
|
||||||
|
|
||||||
|
public static final String TERMUX_FAILSAFE_SESSION_ACTION = "com.termux.app.failsafe_session";
|
||||||
|
|
||||||
private static final int CONTEXTMENU_SELECT_URL_ID = 0;
|
private static final int CONTEXTMENU_SELECT_URL_ID = 0;
|
||||||
private static final int CONTEXTMENU_SHARE_TRANSCRIPT_ID = 1;
|
private static final int CONTEXTMENU_SHARE_TRANSCRIPT_ID = 1;
|
||||||
private static final int CONTEXTMENU_PASTE_ID = 3;
|
private static final int CONTEXTMENU_PASTE_ID = 3;
|
||||||
@@ -126,6 +127,8 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
*/
|
*/
|
||||||
boolean mIsVisible;
|
boolean mIsVisible;
|
||||||
|
|
||||||
|
boolean mIsUsingBlackUI;
|
||||||
|
|
||||||
final SoundPool mBellSoundPool = new SoundPool.Builder().setMaxStreams(1).setAudioAttributes(
|
final SoundPool mBellSoundPool = new SoundPool.Builder().setMaxStreams(1).setAudioAttributes(
|
||||||
new AudioAttributes.Builder().setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
|
new AudioAttributes.Builder().setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
|
||||||
.setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION).build()).build();
|
.setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION).build()).build();
|
||||||
@@ -185,28 +188,35 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** For processes to access shared internal storage (/sdcard) we need this permission. */
|
/** For processes to access shared internal storage (/sdcard) we need this permission. */
|
||||||
@TargetApi(Build.VERSION_CODES.M)
|
|
||||||
public boolean ensureStoragePermissionGranted() {
|
public boolean ensureStoragePermissionGranted() {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
|
||||||
if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUESTCODE_PERMISSION_STORAGE);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Always granted before Android 6.0.
|
|
||||||
return true;
|
return true;
|
||||||
|
} else {
|
||||||
|
requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUESTCODE_PERMISSION_STORAGE);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle bundle) {
|
public void onCreate(Bundle bundle) {
|
||||||
|
mSettings = new TermuxPreferences(this);
|
||||||
|
mIsUsingBlackUI = mSettings.isUsingBlackUI();
|
||||||
|
if (mIsUsingBlackUI) {
|
||||||
|
this.setTheme(R.style.Theme_Termux_Black);
|
||||||
|
} else {
|
||||||
|
this.setTheme(R.style.Theme_Termux);
|
||||||
|
}
|
||||||
|
|
||||||
super.onCreate(bundle);
|
super.onCreate(bundle);
|
||||||
|
|
||||||
mSettings = new TermuxPreferences(this);
|
|
||||||
|
|
||||||
setContentView(R.layout.drawer_layout);
|
setContentView(R.layout.drawer_layout);
|
||||||
|
|
||||||
|
if (mIsUsingBlackUI) {
|
||||||
|
findViewById(R.id.left_drawer).setBackgroundColor(
|
||||||
|
getResources().getColor(android.R.color.background_dark)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
mTerminalView = findViewById(R.id.terminal_view);
|
mTerminalView = findViewById(R.id.terminal_view);
|
||||||
mTerminalView.setOnKeyListener(new TermuxViewClient(this));
|
mTerminalView.setOnKeyListener(new TermuxViewClient(this));
|
||||||
|
|
||||||
@@ -216,8 +226,8 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
|
|
||||||
final ViewPager viewPager = findViewById(R.id.viewpager);
|
final ViewPager viewPager = findViewById(R.id.viewpager);
|
||||||
if (mSettings.mShowExtraKeys) viewPager.setVisibility(View.VISIBLE);
|
if (mSettings.mShowExtraKeys) viewPager.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
|
|
||||||
ViewGroup.LayoutParams layoutParams = viewPager.getLayoutParams();
|
ViewGroup.LayoutParams layoutParams = viewPager.getLayoutParams();
|
||||||
layoutParams.height = layoutParams.height * mSettings.mExtraKeys.length;
|
layoutParams.height = layoutParams.height * mSettings.mExtraKeys.length;
|
||||||
viewPager.setLayoutParams(layoutParams);
|
viewPager.setLayoutParams(layoutParams);
|
||||||
@@ -249,7 +259,7 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
if (session != null) {
|
if (session != null) {
|
||||||
if (session.isRunning()) {
|
if (session.isRunning()) {
|
||||||
String textToSend = editText.getText().toString();
|
String textToSend = editText.getText().toString();
|
||||||
if (textToSend.length() == 0) textToSend = "\n";
|
if (textToSend.length() == 0) textToSend = "\r";
|
||||||
session.write(textToSend);
|
session.write(textToSend);
|
||||||
} else {
|
} else {
|
||||||
removeFinishedSession(session);
|
removeFinishedSession(session);
|
||||||
@@ -367,8 +377,18 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
showToast(toToastTitle(finishedSession) + " - exited", true);
|
showToast(toToastTitle(finishedSession) + " - exited", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mTermService.getSessions().size() > 1) {
|
if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
|
||||||
removeFinishedSession(finishedSession);
|
// On Android TV devices we need to use older behaviour because we may
|
||||||
|
// not be able to have multiple launcher icons.
|
||||||
|
if (mTermService.getSessions().size() > 1) {
|
||||||
|
removeFinishedSession(finishedSession);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Once we have a separate launcher icon for the failsafe session, it
|
||||||
|
// should be safe to auto-close session on exit code '0' or '130'.
|
||||||
|
if (finishedSession.getExitStatus() == 0 || finishedSession.getExitStatus() == 130) {
|
||||||
|
removeFinishedSession(finishedSession);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mListViewAdapter.notifyDataSetChanged();
|
mListViewAdapter.notifyDataSetChanged();
|
||||||
@@ -390,7 +410,7 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
mBellSoundPool.play(mBellSoundId, 1.f, 1.f, 1, 0, 1.f);
|
mBellSoundPool.play(mBellSoundId, 1.f, 1.f, 1, 0, 1.f);
|
||||||
break;
|
break;
|
||||||
case TermuxPreferences.BELL_VIBRATE:
|
case TermuxPreferences.BELL_VIBRATE:
|
||||||
((Vibrator) getSystemService(VIBRATOR_SERVICE)).vibrate(50);
|
BellUtil.getInstance(TermuxActivity.this).doBell();
|
||||||
break;
|
break;
|
||||||
case TermuxPreferences.BELL_IGNORE:
|
case TermuxPreferences.BELL_IGNORE:
|
||||||
// Ignore the bell character.
|
// Ignore the bell character.
|
||||||
@@ -423,7 +443,11 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
boolean sessionRunning = sessionAtRow.isRunning();
|
boolean sessionRunning = sessionAtRow.isRunning();
|
||||||
|
|
||||||
TextView firstLineView = row.findViewById(R.id.row_line);
|
TextView firstLineView = row.findViewById(R.id.row_line);
|
||||||
|
if (mIsUsingBlackUI) {
|
||||||
|
firstLineView.setBackground(
|
||||||
|
getResources().getDrawable(R.drawable.selected_session_background_black)
|
||||||
|
);
|
||||||
|
}
|
||||||
String name = sessionAtRow.mSessionName;
|
String name = sessionAtRow.mSessionName;
|
||||||
String sessionTitle = sessionAtRow.getTitle();
|
String sessionTitle = sessionAtRow.getTitle();
|
||||||
|
|
||||||
@@ -443,7 +467,8 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
} else {
|
} else {
|
||||||
firstLineView.setPaintFlags(firstLineView.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
|
firstLineView.setPaintFlags(firstLineView.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
|
||||||
}
|
}
|
||||||
int color = sessionRunning || sessionAtRow.getExitStatus() == 0 ? Color.BLACK : Color.RED;
|
int defaultColor = mIsUsingBlackUI ? Color.WHITE : Color.BLACK;
|
||||||
|
int color = sessionRunning || sessionAtRow.getExitStatus() == 0 ? defaultColor : Color.RED;
|
||||||
firstLineView.setTextColor(color);
|
firstLineView.setTextColor(color);
|
||||||
return row;
|
return row;
|
||||||
}
|
}
|
||||||
@@ -465,7 +490,12 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
TermuxInstaller.setupIfNeeded(TermuxActivity.this, () -> {
|
TermuxInstaller.setupIfNeeded(TermuxActivity.this, () -> {
|
||||||
if (mTermService == null) return; // Activity might have been destroyed.
|
if (mTermService == null) return; // Activity might have been destroyed.
|
||||||
try {
|
try {
|
||||||
addNewSession(false, null);
|
Bundle bundle = getIntent().getExtras();
|
||||||
|
boolean launchFailsafe = false;
|
||||||
|
if (bundle != null) {
|
||||||
|
launchFailsafe = bundle.getBoolean(TERMUX_FAILSAFE_SESSION_ACTION, false);
|
||||||
|
}
|
||||||
|
addNewSession(launchFailsafe, null);
|
||||||
} catch (WindowManager.BadTokenException e) {
|
} catch (WindowManager.BadTokenException e) {
|
||||||
// Activity finished - ignore.
|
// Activity finished - ignore.
|
||||||
}
|
}
|
||||||
@@ -478,7 +508,8 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
Intent i = getIntent();
|
Intent i = getIntent();
|
||||||
if (i != null && Intent.ACTION_RUN.equals(i.getAction())) {
|
if (i != null && Intent.ACTION_RUN.equals(i.getAction())) {
|
||||||
// Android 7.1 app shortcut from res/xml/shortcuts.xml.
|
// Android 7.1 app shortcut from res/xml/shortcuts.xml.
|
||||||
addNewSession(false, null);
|
boolean failSafe = i.getBooleanExtra(TERMUX_FAILSAFE_SESSION_ACTION, false);
|
||||||
|
addNewSession(failSafe, null);
|
||||||
} else {
|
} else {
|
||||||
switchToSession(getStoredCurrentSessionOrLast());
|
switchToSession(getStoredCurrentSessionOrLast());
|
||||||
}
|
}
|
||||||
@@ -572,20 +603,7 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
new AlertDialog.Builder(this).setTitle(R.string.max_terminals_reached_title).setMessage(R.string.max_terminals_reached_message)
|
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()) {
|
TerminalSession newSession = mTermService.createTermSession(null, null, null, failSafe);
|
||||||
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);
|
|
||||||
TerminalSession newSession = mTermService.createTermSession(executablePath, null, null, failSafe);
|
|
||||||
if (sessionName != null) {
|
if (sessionName != null) {
|
||||||
newSession.mSessionName = sessionName;
|
newSession.mSessionName = sessionName;
|
||||||
}
|
}
|
||||||
@@ -650,19 +668,86 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
}
|
}
|
||||||
|
|
||||||
static LinkedHashSet<CharSequence> extractUrls(String text) {
|
static LinkedHashSet<CharSequence> extractUrls(String text) {
|
||||||
// Pattern for recognizing a URL, based off RFC 3986
|
|
||||||
// http://stackoverflow.com/questions/5713558/detect-and-extract-url-from-a-string
|
StringBuilder regex_sb = new StringBuilder();
|
||||||
|
|
||||||
|
regex_sb.append("("); // Begin first matching group.
|
||||||
|
regex_sb.append("(?:"); // Begin scheme group.
|
||||||
|
regex_sb.append("dav|"); // The DAV proto.
|
||||||
|
regex_sb.append("dict|"); // The DICT proto.
|
||||||
|
regex_sb.append("dns|"); // The DNS proto.
|
||||||
|
regex_sb.append("file|"); // File path.
|
||||||
|
regex_sb.append("finger|"); // The Finger proto.
|
||||||
|
regex_sb.append("ftp(?:s?)|"); // The FTP proto.
|
||||||
|
regex_sb.append("git|"); // The Git proto.
|
||||||
|
regex_sb.append("gopher|"); // The Gopher proto.
|
||||||
|
regex_sb.append("http(?:s?)|"); // The HTTP proto.
|
||||||
|
regex_sb.append("imap(?:s?)|"); // The IMAP proto.
|
||||||
|
regex_sb.append("irc(?:[6s]?)|"); // The IRC proto.
|
||||||
|
regex_sb.append("ip[fn]s|"); // The IPFS proto.
|
||||||
|
regex_sb.append("ldap(?:s?)|"); // The LDAP proto.
|
||||||
|
regex_sb.append("pop3(?:s?)|"); // The POP3 proto.
|
||||||
|
regex_sb.append("redis(?:s?)|"); // The Redis proto.
|
||||||
|
regex_sb.append("rsync|"); // The Rsync proto.
|
||||||
|
regex_sb.append("rtsp(?:[su]?)|"); // The RTSP proto.
|
||||||
|
regex_sb.append("sftp|"); // The SFTP proto.
|
||||||
|
regex_sb.append("smb(?:s?)|"); // The SAMBA proto.
|
||||||
|
regex_sb.append("smtp(?:s?)|"); // The SMTP proto.
|
||||||
|
regex_sb.append("svn(?:(?:\\+ssh)?)|"); // The Subversion proto.
|
||||||
|
regex_sb.append("tcp|"); // The TCP proto.
|
||||||
|
regex_sb.append("telnet|"); // The Telnet proto.
|
||||||
|
regex_sb.append("tftp|"); // The TFTP proto.
|
||||||
|
regex_sb.append("udp|"); // The UDP proto.
|
||||||
|
regex_sb.append("vnc|"); // The VNC proto.
|
||||||
|
regex_sb.append("ws(?:s?)"); // The Websocket proto.
|
||||||
|
regex_sb.append(")://"); // End scheme group.
|
||||||
|
regex_sb.append(")"); // End first matching group.
|
||||||
|
|
||||||
|
|
||||||
|
// Begin second matching group.
|
||||||
|
regex_sb.append("(");
|
||||||
|
|
||||||
|
// User name and/or password in format 'user:pass@'.
|
||||||
|
regex_sb.append("(?:\\S+(?::\\S*)?@)?");
|
||||||
|
|
||||||
|
// Begin host group.
|
||||||
|
regex_sb.append("(?:");
|
||||||
|
|
||||||
|
// IP address (from http://www.regular-expressions.info/examples.html).
|
||||||
|
regex_sb.append("(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)|");
|
||||||
|
|
||||||
|
// Host name or domain.
|
||||||
|
regex_sb.append("(?:(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)(?:(?:\\.(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)*(?:\\.(?:[a-z\\u00a1-\\uffff]{2,})))?|");
|
||||||
|
|
||||||
|
// Just path. Used in case of 'file://' scheme.
|
||||||
|
regex_sb.append("/(?:(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)");
|
||||||
|
|
||||||
|
// End host group.
|
||||||
|
regex_sb.append(")");
|
||||||
|
|
||||||
|
// Port number.
|
||||||
|
regex_sb.append("(?::\\d{1,5})?");
|
||||||
|
|
||||||
|
// Resource path with optional query string.
|
||||||
|
regex_sb.append("(?:/[a-zA-Z0-9:@%\\-._~!$&()*+,;=?/]*)?");
|
||||||
|
|
||||||
|
// End second matching group.
|
||||||
|
regex_sb.append(")");
|
||||||
|
|
||||||
final Pattern urlPattern = Pattern.compile(
|
final Pattern urlPattern = Pattern.compile(
|
||||||
"(?:^|[\\W])((ht|f)tp(s?)://|www\\.)" + "(([\\w\\-]+\\.)+?([\\w\\-.~]+/?)*" + "[\\p{Alnum}.,%_=?&#\\-+()\\[\\]*$~@!:/{};']*)",
|
regex_sb.toString(),
|
||||||
Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
|
Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
|
||||||
|
|
||||||
LinkedHashSet<CharSequence> urlSet = new LinkedHashSet<>();
|
LinkedHashSet<CharSequence> urlSet = new LinkedHashSet<>();
|
||||||
Matcher matcher = urlPattern.matcher(text);
|
Matcher matcher = urlPattern.matcher(text);
|
||||||
|
|
||||||
while (matcher.find()) {
|
while (matcher.find()) {
|
||||||
int matchStart = matcher.start(1);
|
int matchStart = matcher.start(1);
|
||||||
int matchEnd = matcher.end();
|
int matchEnd = matcher.end();
|
||||||
String url = text.substring(matchStart, matchEnd);
|
String url = text.substring(matchStart, matchEnd);
|
||||||
urlSet.add(url);
|
urlSet.add(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
return urlSet;
|
return urlSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -717,7 +802,18 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
if (session != null) {
|
if (session != null) {
|
||||||
Intent intent = new Intent(Intent.ACTION_SEND);
|
Intent intent = new Intent(Intent.ACTION_SEND);
|
||||||
intent.setType("text/plain");
|
intent.setType("text/plain");
|
||||||
intent.putExtra(Intent.EXTRA_TEXT, session.getEmulator().getScreen().getTranscriptText().trim());
|
String transcriptText = session.getEmulator().getScreen().getTranscriptTextWithoutJoinedLines().trim();
|
||||||
|
// See https://github.com/termux/termux-app/issues/1166.
|
||||||
|
final int MAX_LENGTH = 100_000;
|
||||||
|
if (transcriptText.length() > MAX_LENGTH) {
|
||||||
|
int cutOffIndex = transcriptText.length() - MAX_LENGTH;
|
||||||
|
int nextNewlineIndex = transcriptText.indexOf('\n', cutOffIndex);
|
||||||
|
if (nextNewlineIndex != -1 && nextNewlineIndex != transcriptText.length() - 1) {
|
||||||
|
cutOffIndex = nextNewlineIndex + 1;
|
||||||
|
}
|
||||||
|
transcriptText = transcriptText.substring(cutOffIndex).trim();
|
||||||
|
}
|
||||||
|
intent.putExtra(Intent.EXTRA_TEXT, transcriptText);
|
||||||
intent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.share_transcript_title));
|
intent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.share_transcript_title));
|
||||||
startActivity(Intent.createChooser(intent, getString(R.string.share_transcript_chooser_title)));
|
startActivity(Intent.createChooser(intent, getString(R.string.share_transcript_chooser_title)));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import com.termux.R;
|
|||||||
import com.termux.terminal.EmulatorDebug;
|
import com.termux.terminal.EmulatorDebug;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -38,7 +39,7 @@ import java.util.zip.ZipInputStream;
|
|||||||
* <p/>
|
* <p/>
|
||||||
* (3) A staging folder, $STAGING_PREFIX, is {@link #deleteFolder(File)} if left over from broken installation below.
|
* (3) A staging folder, $STAGING_PREFIX, is {@link #deleteFolder(File)} if left over from broken installation below.
|
||||||
* <p/>
|
* <p/>
|
||||||
* (4) The architecture is determined and an appropriate bootstrap zip url is determined in {@link #determineZipUrl()}.
|
* (4) The zip file is loaded from a shared library.
|
||||||
* <p/>
|
* <p/>
|
||||||
* (5) The zip, containing entries relative to the $PREFIX, is is downloaded and extracted by a zip input stream
|
* (5) The zip, containing entries relative to the $PREFIX, is is downloaded and extracted by a zip input stream
|
||||||
* continuously encountering zip file entries:
|
* continuously encountering zip file entries:
|
||||||
@@ -82,8 +83,8 @@ final class TermuxInstaller {
|
|||||||
final byte[] buffer = new byte[8096];
|
final byte[] buffer = new byte[8096];
|
||||||
final List<Pair<String, String>> symlinks = new ArrayList<>(50);
|
final List<Pair<String, String>> symlinks = new ArrayList<>(50);
|
||||||
|
|
||||||
final URL zipUrl = determineZipUrl();
|
final byte[] zipBytes = loadZipBytes();
|
||||||
try (ZipInputStream zipInput = new ZipInputStream(zipUrl.openStream())) {
|
try (ZipInputStream zipInput = new ZipInputStream(new ByteArrayInputStream(zipBytes))) {
|
||||||
ZipEntry zipEntry;
|
ZipEntry zipEntry;
|
||||||
while ((zipEntry = zipInput.getNextEntry()) != null) {
|
while ((zipEntry = zipInput.getNextEntry()) != null) {
|
||||||
if (zipEntry.getName().equals("SYMLINKS.txt")) {
|
if (zipEntry.getName().equals("SYMLINKS.txt")) {
|
||||||
@@ -167,31 +168,13 @@ final class TermuxInstaller {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get bootstrap zip url for this systems cpu architecture. */
|
public static byte[] loadZipBytes() {
|
||||||
private static URL determineZipUrl() throws MalformedURLException {
|
// Only load the shared library when necessary to save memory usage.
|
||||||
String archName = determineTermuxArchName();
|
System.loadLibrary("termux-bootstrap");
|
||||||
return new URL("https://termux.net/bootstrap/bootstrap-" + archName + ".zip");
|
return getZip();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String determineTermuxArchName() {
|
public static native byte[] getZip();
|
||||||
// Note that we cannot use System.getProperty("os.arch") since that may give e.g. "aarch64"
|
|
||||||
// while a 64-bit runtime may not be installed (like on the Samsung Galaxy S5 Neo).
|
|
||||||
// Instead we search through the supported abi:s on the device, see:
|
|
||||||
// http://developer.android.com/ndk/guides/abis.html
|
|
||||||
// Note that we search for abi:s in preferred order (the ordering of the
|
|
||||||
// Build.SUPPORTED_ABIS list) to avoid e.g. installing arm on an x86 system where arm
|
|
||||||
// emulation is available.
|
|
||||||
for (String androidArch : Build.SUPPORTED_ABIS) {
|
|
||||||
switch (androidArch) {
|
|
||||||
case "arm64-v8a": return "aarch64";
|
|
||||||
case "armeabi-v7a": return "arm";
|
|
||||||
case "x86_64": return "x86_64";
|
|
||||||
case "x86": return "i686";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new RuntimeException("Unable to determine arch from Build.SUPPORTED_ABIS = " +
|
|
||||||
Arrays.toString(Build.SUPPORTED_ABIS));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Delete a folder and all its content or throw. Don't follow symlinks. */
|
/** Delete a folder and all its content or throw. Don't follow symlinks. */
|
||||||
static void deleteFolder(File fileOrDirectory) throws IOException {
|
static void deleteFolder(File fileOrDirectory) throws IOException {
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ public class TermuxOpenReceiver extends BroadcastReceiver {
|
|||||||
contentTypeToUse = contentTypeExtra;
|
contentTypeToUse = contentTypeExtra;
|
||||||
}
|
}
|
||||||
|
|
||||||
Uri uriToShare = Uri.withAppendedPath(Uri.parse("content://com.termux.files/"), filePath);
|
Uri uriToShare = Uri.parse("content://com.termux.files" + fileToShare.getAbsolutePath());
|
||||||
|
|
||||||
if (Intent.ACTION_SEND.equals(intentAction)) {
|
if (Intent.ACTION_SEND.equals(intentAction)) {
|
||||||
sendIntent.putExtra(Intent.EXTRA_STREAM, uriToShare);
|
sendIntent.putExtra(Intent.EXTRA_STREAM, uriToShare);
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ final class TermuxPreferences {
|
|||||||
private static final String CURRENT_SESSION_KEY = "current_session";
|
private static final String CURRENT_SESSION_KEY = "current_session";
|
||||||
private static final String SCREEN_ALWAYS_ON_KEY = "screen_always_on";
|
private static final String SCREEN_ALWAYS_ON_KEY = "screen_always_on";
|
||||||
|
|
||||||
|
private String mUseDarkUI;
|
||||||
private boolean mScreenAlwaysOn;
|
private boolean mScreenAlwaysOn;
|
||||||
private int mFontSize;
|
private int mFontSize;
|
||||||
|
|
||||||
@@ -126,6 +127,10 @@ final class TermuxPreferences {
|
|||||||
return mScreenAlwaysOn;
|
return mScreenAlwaysOn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean isUsingBlackUI() {
|
||||||
|
return mUseDarkUI.toLowerCase().equals("true");
|
||||||
|
}
|
||||||
|
|
||||||
void setScreenAlwaysOn(Context context, boolean newValue) {
|
void setScreenAlwaysOn(Context context, boolean newValue) {
|
||||||
mScreenAlwaysOn = newValue;
|
mScreenAlwaysOn = newValue;
|
||||||
PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean(SCREEN_ALWAYS_ON_KEY, newValue).apply();
|
PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean(SCREEN_ALWAYS_ON_KEY, newValue).apply();
|
||||||
@@ -157,7 +162,7 @@ final class TermuxPreferences {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Toast.makeText(context, "Could not open the propertiey file termux.properties.", Toast.LENGTH_LONG).show();
|
Toast.makeText(context, "Could not open properties file termux.properties.", Toast.LENGTH_LONG).show();
|
||||||
Log.e("termux", "Error loading props", e);
|
Log.e("termux", "Error loading props", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,6 +178,8 @@ final class TermuxPreferences {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mUseDarkUI = props.getProperty("use-black-ui", "false");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
JSONArray arr = new JSONArray(props.getProperty("extra-keys", "[['ESC', 'TAB', 'CTRL', 'ALT', '-', 'DOWN', 'UP']]"));
|
JSONArray arr = new JSONArray(props.getProperty("extra-keys", "[['ESC', 'TAB', 'CTRL', 'ALT', '-', 'DOWN', 'UP']]"));
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import android.app.NotificationChannel;
|
|||||||
import android.app.NotificationManager;
|
import android.app.NotificationManager;
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.app.Service;
|
import android.app.Service;
|
||||||
|
import android.content.ActivityNotFoundException;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
@@ -16,6 +17,7 @@ import android.os.Build;
|
|||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.PowerManager;
|
import android.os.PowerManager;
|
||||||
|
import android.provider.Settings;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.widget.ArrayAdapter;
|
import android.widget.ArrayAdapter;
|
||||||
|
|
||||||
@@ -112,6 +114,20 @@ public final class TermuxService extends Service implements SessionChangedCallba
|
|||||||
mWifiLock = wm.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, EmulatorDebug.LOG_TAG);
|
mWifiLock = wm.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, EmulatorDebug.LOG_TAG);
|
||||||
mWifiLock.acquire();
|
mWifiLock.acquire();
|
||||||
|
|
||||||
|
String packageName = getPackageName();
|
||||||
|
if (!pm.isIgnoringBatteryOptimizations(packageName)) {
|
||||||
|
Intent whitelist = new Intent();
|
||||||
|
whitelist.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
|
||||||
|
whitelist.setData(Uri.parse("package:" + packageName));
|
||||||
|
whitelist.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
|
||||||
|
try {
|
||||||
|
startActivity(whitelist);
|
||||||
|
} catch (ActivityNotFoundException e) {
|
||||||
|
Log.e(EmulatorDebug.LOG_TAG, "Failed to call ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
updateNotification();
|
updateNotification();
|
||||||
}
|
}
|
||||||
} else if (ACTION_UNLOCK_WAKE.equals(action)) {
|
} else if (ACTION_UNLOCK_WAKE.equals(action)) {
|
||||||
@@ -136,7 +152,8 @@ public final class TermuxService extends Service implements SessionChangedCallba
|
|||||||
mBackgroundTasks.add(task);
|
mBackgroundTasks.add(task);
|
||||||
updateNotification();
|
updateNotification();
|
||||||
} else {
|
} else {
|
||||||
TerminalSession newSession = createTermSession(executablePath, arguments, cwd, false);
|
boolean failsafe = intent.getBooleanExtra(TermuxActivity.TERMUX_FAILSAFE_SESSION_ACTION, false);
|
||||||
|
TerminalSession newSession = createTermSession(executablePath, arguments, cwd, failsafe);
|
||||||
|
|
||||||
// Transform executable path to session name, e.g. "/bin/do-something.sh" => "do something.sh".
|
// Transform executable path to session name, e.g. "/bin/do-something.sh" => "do something.sh".
|
||||||
if (executablePath != null) {
|
if (executablePath != null) {
|
||||||
@@ -214,7 +231,7 @@ public final class TermuxService extends Service implements SessionChangedCallba
|
|||||||
builder.setShowWhen(false);
|
builder.setShowWhen(false);
|
||||||
|
|
||||||
// Background color for small notification icon:
|
// Background color for small notification icon:
|
||||||
builder.setColor(0xFF000000);
|
builder.setColor(0xFF607D8B);
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
builder.setChannelId(NOTIFICATION_CHANNEL_ID);
|
builder.setChannelId(NOTIFICATION_CHANNEL_ID);
|
||||||
@@ -237,6 +254,18 @@ public final class TermuxService extends Service implements SessionChangedCallba
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
|
File termuxTmpDir = new File(TermuxService.PREFIX_PATH + "/tmp");
|
||||||
|
|
||||||
|
if (termuxTmpDir.exists()) {
|
||||||
|
try {
|
||||||
|
TermuxInstaller.deleteFolder(termuxTmpDir.getCanonicalFile());
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(EmulatorDebug.LOG_TAG, "Error while removing file at " + termuxTmpDir.getAbsolutePath(), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
termuxTmpDir.mkdirs();
|
||||||
|
}
|
||||||
|
|
||||||
if (mWakeLock != null) mWakeLock.release();
|
if (mWakeLock != null) mWakeLock.release();
|
||||||
if (mWifiLock != null) mWifiLock.release();
|
if (mWifiLock != null) mWifiLock.release();
|
||||||
|
|
||||||
@@ -250,14 +279,6 @@ 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();
|
||||||
|
|
||||||
@@ -267,11 +288,13 @@ public final class TermuxService extends Service implements SessionChangedCallba
|
|||||||
boolean isLoginShell = false;
|
boolean isLoginShell = false;
|
||||||
|
|
||||||
if (executablePath == null) {
|
if (executablePath == null) {
|
||||||
for (String shellBinary : new String[]{"login", "bash", "zsh"}) {
|
if (!failSafe) {
|
||||||
File shellFile = new File(PREFIX_PATH + "/bin/" + shellBinary);
|
for (String shellBinary : new String[]{"login", "bash", "zsh"}) {
|
||||||
if (shellFile.canExecute()) {
|
File shellFile = new File(PREFIX_PATH + "/bin/" + shellBinary);
|
||||||
executablePath = shellFile.getAbsolutePath();
|
if (shellFile.canExecute()) {
|
||||||
break;
|
executablePath = shellFile.getAbsolutePath();
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -295,6 +318,12 @@ public final class TermuxService extends Service implements SessionChangedCallba
|
|||||||
TerminalSession session = new TerminalSession(executablePath, cwd, args, env, this);
|
TerminalSession session = new TerminalSession(executablePath, cwd, args, env, this);
|
||||||
mTerminalSessions.add(session);
|
mTerminalSessions.add(session);
|
||||||
updateNotification();
|
updateNotification();
|
||||||
|
|
||||||
|
// Make sure that terminal styling is always applied.
|
||||||
|
Intent stylingIntent = new Intent("com.termux.app.reload_style");
|
||||||
|
stylingIntent.putExtra("com.termux.app.reload_style", "styling");
|
||||||
|
sendBroadcast(stylingIntent);
|
||||||
|
|
||||||
return session;
|
return session;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
|
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
|
||||||
<solid android:color="#E0E0E0" />
|
<solid android:color="#E0E0E0" />
|
||||||
</shape>
|
</shape>
|
||||||
|
|||||||
4
app/src/main/res/drawable/current_session_black.xml
Normal file
4
app/src/main/res/drawable/current_session_black.xml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
|
||||||
|
<solid android:color="#212325" />
|
||||||
|
</shape>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:state_activated="true" android:drawable="@drawable/current_session_black"/>
|
||||||
|
<item android:state_activated="false" android:drawable="@drawable/session_ripple_black"/>
|
||||||
|
</selector>
|
||||||
@@ -4,4 +4,4 @@
|
|||||||
<item>
|
<item>
|
||||||
<color android:color="@android:color/white" />
|
<color android:color="@android:color/white" />
|
||||||
</item>
|
</item>
|
||||||
</ripple>
|
</ripple>
|
||||||
|
|||||||
7
app/src/main/res/drawable/session_ripple_black.xml
Normal file
7
app/src/main/res/drawable/session_ripple_black.xml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:color="@android:color/darker_gray" >
|
||||||
|
<item>
|
||||||
|
<color android:color="@android:color/background_dark" />
|
||||||
|
</item>
|
||||||
|
</ripple>
|
||||||
@@ -8,6 +8,7 @@
|
|||||||
android:maxLines="1"
|
android:maxLines="1"
|
||||||
android:inputType="text"
|
android:inputType="text"
|
||||||
android:textColor="@android:color/white"
|
android:textColor="@android:color/white"
|
||||||
|
android:textColorHighlight="@android:color/darker_gray"
|
||||||
android:paddingTop="0dp"
|
android:paddingTop="0dp"
|
||||||
android:textCursorDrawable="@null"
|
android:textCursorDrawable="@null"
|
||||||
android:paddingBottom="0dp"
|
android:paddingBottom="0dp"
|
||||||
|
|||||||
@@ -1,52 +1,51 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="application_name">Termux</string>
|
<string name="application_name">Termux</string>
|
||||||
<string name="shared_user_label">Termux user</string>
|
<string name="shared_user_label">Termux user</string>
|
||||||
<string name="new_session">New session</string>
|
<string name="new_session">New session</string>
|
||||||
<string name="new_session_failsafe">Failsafe</string>
|
<string name="new_session_failsafe">Failsafe</string>
|
||||||
<string name="toggle_soft_keyboard">Keyboard</string>
|
<string name="toggle_soft_keyboard">Keyboard</string>
|
||||||
<string name="reset_terminal">Reset</string>
|
<string name="reset_terminal">Reset</string>
|
||||||
<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="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>
|
||||||
<string name="bootstrap_error_body">Termux was unable to install the bootstrap packages.\n\nCheck your network connection and try again.</string>
|
<string name="bootstrap_error_body">Termux was unable to install the bootstrap packages.\n\nCheck your network connection and try again.</string>
|
||||||
<string name="bootstrap_error_abort">Abort</string>
|
<string name="bootstrap_error_abort">Abort</string>
|
||||||
<string name="bootstrap_error_try_again">Try again</string>
|
<string name="bootstrap_error_try_again">Try again</string>
|
||||||
<string name="bootstrap_error_not_primary_user_message">Termux can only be installed on the primary user account.</string>
|
<string name="bootstrap_error_not_primary_user_message">Termux can only be installed on the primary user account.</string>
|
||||||
|
|
||||||
<string name="max_terminals_reached_title">Max terminals reached</string>
|
<string name="max_terminals_reached_title">Max terminals reached</string>
|
||||||
<string name="max_terminals_reached_message">Close down existing ones before creating new.</string>
|
<string name="max_terminals_reached_message">Close down existing ones before creating new.</string>
|
||||||
|
|
||||||
<string name="reset_toast_notification">Terminal reset.</string>
|
<string name="reset_toast_notification">Terminal reset.</string>
|
||||||
|
|
||||||
<string name="select_url">Select URL</string>
|
<string name="select_url">Select URL</string>
|
||||||
<string name="select_url_dialog_title">Click URL to copy or long press to open</string>
|
<string name="select_url_dialog_title">Click URL to copy or long press to open</string>
|
||||||
<string name="select_all_and_share">Share transcript</string>
|
<string name="select_all_and_share">Share transcript</string>
|
||||||
<string name="select_url_no_found">No URL found in the terminal.</string>
|
<string name="select_url_no_found">No URL found in the terminal.</string>
|
||||||
<string name="select_url_copied_to_clipboard">URL copied to clipboard</string>
|
<string name="select_url_copied_to_clipboard">URL copied to clipboard</string>
|
||||||
<string name="share_transcript_chooser_title">Send text to:</string>
|
<string name="share_transcript_chooser_title">Send text to:</string>
|
||||||
|
|
||||||
<string name="kill_process">Kill process (%d)</string>
|
<string name="kill_process">Kill process (%d)</string>
|
||||||
<string name="confirm_kill_process">Really kill this session?</string>
|
<string name="confirm_kill_process">Really kill this session?</string>
|
||||||
|
|
||||||
<string name="session_rename_title">Set session name</string>
|
<string name="session_rename_title">Set session name</string>
|
||||||
<string name="session_rename_positive_button">Set</string>
|
<string name="session_rename_positive_button">Set</string>
|
||||||
<string name="session_new_named_title">New named session</string>
|
<string name="session_new_named_title">New named session</string>
|
||||||
<string name="session_new_named_positive_button">Create</string>
|
<string name="session_new_named_positive_button">Create</string>
|
||||||
|
|
||||||
<string name="styling_not_installed">The Termux:Style add-on is not installed.</string>
|
<string name="styling_not_installed">The Termux:Style add-on is not installed.</string>
|
||||||
<string name="styling_install">Install</string>
|
<string name="styling_install">Install</string>
|
||||||
|
|
||||||
<string name="notification_action_exit">Exit</string>
|
<string name="notification_action_exit">Exit</string>
|
||||||
<string name="notification_action_wake_lock">Acquire wakelock</string>
|
<string name="notification_action_wake_lock">Acquire wakelock</string>
|
||||||
<string name="notification_action_wake_unlock">Release wakelock</string>
|
<string name="notification_action_wake_unlock">Release wakelock</string>
|
||||||
|
|
||||||
<string name="file_received_title">Save file in ~/downloads/</string>
|
|
||||||
<string name="file_received_edit_button">Edit</string>
|
|
||||||
<string name="file_received_open_folder_button">Open folder</string>
|
|
||||||
|
|
||||||
|
<string name="file_received_title">Save file in ~/downloads/</string>
|
||||||
|
<string name="file_received_edit_button">Edit</string>
|
||||||
|
<string name="file_received_open_folder_button">Open folder</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:android="http://schemas.android.com/apk/res/android">
|
<resources xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
<!-- See https://developer.android.com/training/material/theme.html for how to customize the Material theme. -->
|
|
||||||
<!-- NOTE: Cannot use "Light." since it hides the terminal scrollbar on the default black background. -->
|
|
||||||
<style name="Theme.Termux" parent="@android:style/Theme.Material.Light.NoActionBar">
|
<style name="Theme.Termux" parent="@android:style/Theme.Material.Light.NoActionBar">
|
||||||
<item name="android:statusBarColor">#000000</item>
|
<item name="android:statusBarColor">#000000</item>
|
||||||
<item name="android:colorPrimary">#FF000000</item>
|
<item name="android:colorPrimary">#FF000000</item>
|
||||||
@@ -23,9 +21,29 @@
|
|||||||
<item name="android:windowAllowEnterTransitionOverlap">true</item>
|
<item name="android:windowAllowEnterTransitionOverlap">true</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="TermuxAlertDialogStyle" parent="@android:style/Theme.Material.Light.Dialog.Alert">
|
<style name="TermuxAlertDialogStyle" parent="@android:style/Theme.Material.Light.Dialog.Alert">
|
||||||
<!-- Seen in buttons on alert dialog: -->
|
<!-- Seen in buttons on alert dialog: -->
|
||||||
<item name="android:colorAccent">#212121</item>
|
<item name="android:colorAccent">#212121</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<!-- See https://developer.android.com/training/material/theme.html for how to customize the Material theme. -->
|
||||||
|
<!-- NOTE: Cannot use "Light." since it hides the terminal scrollbar on the default black background. -->
|
||||||
|
<style name="Theme.Termux.Black" parent="@android:style/Theme.Material.NoActionBar">
|
||||||
|
<item name="android:statusBarColor">#000000</item>
|
||||||
|
<item name="android:colorPrimary">#FF000000</item>
|
||||||
|
<item name="android:windowBackground">@android:color/black</item>
|
||||||
|
|
||||||
|
<!-- Seen in buttons on left drawer: -->
|
||||||
|
<item name="android:colorAccent">#FDFDFD</item>
|
||||||
|
<!-- Avoid action mode toolbar pushing down terminal content when
|
||||||
|
selecting text on pre-6.0 (non-floating toolbar). -->
|
||||||
|
<item name="android:windowActionModeOverlay">true</item>
|
||||||
|
|
||||||
|
<item name="android:windowTranslucentStatus">true</item>
|
||||||
|
<item name="android:windowTranslucentNavigation">true</item>
|
||||||
|
|
||||||
|
<!-- https://developer.android.com/training/tv/start/start.html#transition-color -->
|
||||||
|
<item name="android:windowAllowReturnTransitionOverlap">true</item>
|
||||||
|
<item name="android:windowAllowEnterTransitionOverlap">true</item>
|
||||||
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<full-backup-content>
|
|
||||||
<!-- See https://developer.android.com/training/backup/autosyncapi.html -->
|
|
||||||
<include domain="file" path="home/backup" />
|
|
||||||
</full-backup-content>
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
<shortcuts xmlns:tools="http://schemas.android.com/tools"
|
<shortcuts xmlns:tools="http://schemas.android.com/tools"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
<shortcut
|
<shortcut
|
||||||
android:shortcutId="new_session"
|
android:shortcutId="new_session"
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
@@ -11,4 +12,19 @@
|
|||||||
android:targetPackage="com.termux"
|
android:targetPackage="com.termux"
|
||||||
android:targetClass="com.termux.app.TermuxActivity"/>
|
android:targetClass="com.termux.app.TermuxActivity"/>
|
||||||
</shortcut>
|
</shortcut>
|
||||||
|
|
||||||
|
<shortcut
|
||||||
|
android:shortcutId="new_failsafe_session"
|
||||||
|
android:enabled="true"
|
||||||
|
android:icon="@drawable/ic_new_session"
|
||||||
|
android:shortcutShortLabel="@string/new_session_failsafe"
|
||||||
|
tools:targetApi="n_mr1">
|
||||||
|
<intent
|
||||||
|
android:action="android.intent.action.RUN"
|
||||||
|
android:targetPackage="com.termux"
|
||||||
|
android:targetClass="com.termux.app.TermuxActivity">
|
||||||
|
<extra android:name="com.termux.app.failsafe_session" android:value="true" />
|
||||||
|
</intent>
|
||||||
|
</shortcut>
|
||||||
|
|
||||||
</shortcuts>
|
</shortcuts>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ buildscript {
|
|||||||
google()
|
google()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:3.3.0'
|
classpath 'com.android.tools.build:gradle:3.5.2'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
3
gradle/wrapper/gradle-wrapper.properties
vendored
3
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,6 @@
|
|||||||
|
#Sun Aug 25 01:57:11 CEST 2019
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-bin.zip
|
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
|
||||||
|
|||||||
2
gradlew
vendored
2
gradlew
vendored
@@ -28,7 +28,7 @@ APP_NAME="Gradle"
|
|||||||
APP_BASE_NAME=`basename "$0"`
|
APP_BASE_NAME=`basename "$0"`
|
||||||
|
|
||||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
DEFAULT_JVM_OPTS=""
|
DEFAULT_JVM_OPTS='"-Xmx64m"'
|
||||||
|
|
||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
MAX_FD="maximum"
|
MAX_FD="maximum"
|
||||||
|
|||||||
2
gradlew.bat
vendored
2
gradlew.bat
vendored
@@ -14,7 +14,7 @@ set APP_BASE_NAME=%~n0
|
|||||||
set APP_HOME=%DIRNAME%
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
set DEFAULT_JVM_OPTS=
|
set DEFAULT_JVM_OPTS="-Xmx64m"
|
||||||
|
|
||||||
@rem Find java.exe
|
@rem Find java.exe
|
||||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package com.termux.terminal;
|
package com.termux.terminal;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A circular buffer of {@link TerminalRow}:s which keeps notes about what is visible on a logical screen and the scroll
|
* A circular buffer of {@link TerminalRow}:s which keeps notes about what is visible on a logical screen and the scroll
|
||||||
* history.
|
* history.
|
||||||
@@ -39,7 +41,15 @@ public final class TerminalBuffer {
|
|||||||
return getSelectedText(0, -getActiveTranscriptRows(), mColumns, mScreenRows).trim();
|
return getSelectedText(0, -getActiveTranscriptRows(), mColumns, mScreenRows).trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getTranscriptTextWithoutJoinedLines() {
|
||||||
|
return getSelectedText(0, -getActiveTranscriptRows(), mColumns, mScreenRows, false).trim();
|
||||||
|
}
|
||||||
|
|
||||||
public String getSelectedText(int selX1, int selY1, int selX2, int selY2) {
|
public String getSelectedText(int selX1, int selY1, int selX2, int selY2) {
|
||||||
|
return getSelectedText(selX1, selY1, selX2, selY2, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSelectedText(int selX1, int selY1, int selX2, int selY2, boolean joinBackLines) {
|
||||||
final StringBuilder builder = new StringBuilder();
|
final StringBuilder builder = new StringBuilder();
|
||||||
final int columns = mColumns;
|
final int columns = mColumns;
|
||||||
|
|
||||||
@@ -77,7 +87,8 @@ public final class TerminalBuffer {
|
|||||||
}
|
}
|
||||||
if (lastPrintingCharIndex != -1)
|
if (lastPrintingCharIndex != -1)
|
||||||
builder.append(line, x1Index, lastPrintingCharIndex - x1Index + 1);
|
builder.append(line, x1Index, lastPrintingCharIndex - x1Index + 1);
|
||||||
if (!rowLineWrap && row < selY2 && row < mScreenRows - 1) builder.append('\n');
|
if ((!joinBackLines || !rowLineWrap)
|
||||||
|
&& row < selY2 && row < mScreenRows - 1) builder.append('\n');
|
||||||
}
|
}
|
||||||
return builder.toString();
|
return builder.toString();
|
||||||
}
|
}
|
||||||
@@ -422,4 +433,14 @@ public final class TerminalBuffer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void clearTranscript() {
|
||||||
|
if (mScreenFirstRow < mActiveTranscriptRows) {
|
||||||
|
Arrays.fill(mLines, mTotalRows + mScreenFirstRow - mActiveTranscriptRows, mTotalRows, null);
|
||||||
|
Arrays.fill(mLines, 0, mScreenFirstRow, null);
|
||||||
|
} else {
|
||||||
|
Arrays.fill(mLines, mScreenFirstRow - mActiveTranscriptRows, mScreenFirstRow, null);
|
||||||
|
}
|
||||||
|
mActiveTranscriptRows = 0;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1376,10 +1376,10 @@ public final class TerminalEmulator {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'A': // "CSI${n}A" - Cursor up (CUU) ${n} rows.
|
case 'A': // "CSI${n}A" - Cursor up (CUU) ${n} rows.
|
||||||
setCursorRow(Math.max(mTopMargin, mCursorRow - getArg0(1)));
|
setCursorRow(Math.max(0, mCursorRow - getArg0(1)));
|
||||||
break;
|
break;
|
||||||
case 'B': // "CSI${n}B" - Cursor down (CUD) ${n} rows.
|
case 'B': // "CSI${n}B" - Cursor down (CUD) ${n} rows.
|
||||||
setCursorRow(Math.min(mBottomMargin - 1, mCursorRow + getArg0(1)));
|
setCursorRow(Math.min(mRows - 1, mCursorRow + getArg0(1)));
|
||||||
break;
|
break;
|
||||||
case 'C': // "CSI${n}C" - Cursor forward (CUF).
|
case 'C': // "CSI${n}C" - Cursor forward (CUF).
|
||||||
case 'a': // "CSI${n}a" - Horizontal position relative (HPR). From ISO-6428/ECMA-48.
|
case 'a': // "CSI${n}a" - Horizontal position relative (HPR). From ISO-6428/ECMA-48.
|
||||||
@@ -1404,7 +1404,7 @@ public final class TerminalEmulator {
|
|||||||
case 'I': // Cursor Horizontal Forward Tabulation (CHT). Move the active position n tabs forward.
|
case 'I': // Cursor Horizontal Forward Tabulation (CHT). Move the active position n tabs forward.
|
||||||
setCursorCol(nextTabStop(getArg0(1)));
|
setCursorCol(nextTabStop(getArg0(1)));
|
||||||
break;
|
break;
|
||||||
case 'J': // "${CSI}${0,1,2}J" - Erase in Display (ED)
|
case 'J': // "${CSI}${0,1,2,3}J" - Erase in Display (ED)
|
||||||
// ED ignores the scrolling margins.
|
// ED ignores the scrolling margins.
|
||||||
switch (getArg0(0)) {
|
switch (getArg0(0)) {
|
||||||
case 0: // Erase from the active position to the end of the screen, inclusive (default).
|
case 0: // Erase from the active position to the end of the screen, inclusive (default).
|
||||||
@@ -1419,6 +1419,9 @@ public final class TerminalEmulator {
|
|||||||
// move..
|
// move..
|
||||||
blockClear(0, 0, mColumns, mRows);
|
blockClear(0, 0, mColumns, mRows);
|
||||||
break;
|
break;
|
||||||
|
case 3: // Delete all lines saved in the scrollback buffer (xterm etc)
|
||||||
|
mMainBuffer.clearTranscript();
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
unknownSequence(b);
|
unknownSequence(b);
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -46,4 +46,20 @@ public class ControlSequenceIntroducerTest extends TerminalTestCase {
|
|||||||
withTerminalSized(5, 2).enterString("abcde\033[2G\033[2b\n").assertLinesAre("aeede", " ");
|
withTerminalSized(5, 2).enterString("abcde\033[2G\033[2b\n").assertLinesAre("aeede", " ");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** CSI 3 J Clear scrollback (xterm, libvte; non-standard). */
|
||||||
|
public void testCsi3J() {
|
||||||
|
withTerminalSized(3, 2).enterString("a\r\nb\r\nc\r\nd");
|
||||||
|
assertEquals("a\nb\nc\nd", mTerminal.getScreen().getTranscriptText());
|
||||||
|
enterString("\033[3J");
|
||||||
|
assertEquals("c\nd", mTerminal.getScreen().getTranscriptText());
|
||||||
|
|
||||||
|
withTerminalSized(3, 2).enterString("Lorem_ipsum");
|
||||||
|
assertEquals("Lorem_ipsum", mTerminal.getScreen().getTranscriptText());
|
||||||
|
enterString("\033[3J");
|
||||||
|
assertEquals("ipsum", mTerminal.getScreen().getTranscriptText());
|
||||||
|
|
||||||
|
withTerminalSized(3, 2).enterString("w\r\nx\r\ny\r\nz\033[?1049h\033[3J\033[?1049l");
|
||||||
|
assertEquals("y\nz", mTerminal.getScreen().getTranscriptText());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -107,4 +107,24 @@ public class ScrollRegionTest extends TerminalTestCase {
|
|||||||
assertLinesAre("1 ", "2 ", "3 ", "QQ", "YY");
|
assertLinesAre("1 ", "2 ", "3 ", "QQ", "YY");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** See https://github.com/termux/termux-app/issues/1340 */
|
||||||
|
public void testScrollRegionDoesNotLimitCursorMovement() {
|
||||||
|
withTerminalSized(6, 4)
|
||||||
|
.enterString("\033[4;7r\033[3;1Haaa\033[Axxx")
|
||||||
|
.assertLinesAre(
|
||||||
|
" ",
|
||||||
|
" xxx",
|
||||||
|
"aaa ",
|
||||||
|
" "
|
||||||
|
);
|
||||||
|
|
||||||
|
withTerminalSized(6, 4)
|
||||||
|
.enterString("\033[1;3r\033[3;1Haaa\033[Bxxx")
|
||||||
|
.assertLinesAre(
|
||||||
|
" ",
|
||||||
|
" ",
|
||||||
|
"aaa ",
|
||||||
|
" xxx"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -358,10 +358,12 @@ public final class TerminalView extends View {
|
|||||||
public void onScreenUpdated() {
|
public void onScreenUpdated() {
|
||||||
if (mEmulator == null) return;
|
if (mEmulator == null) return;
|
||||||
|
|
||||||
|
int rowsInHistory = mEmulator.getScreen().getActiveTranscriptRows();
|
||||||
|
if (mTopRow < -rowsInHistory) mTopRow = -rowsInHistory;
|
||||||
|
|
||||||
boolean skipScrolling = false;
|
boolean skipScrolling = false;
|
||||||
if (mIsSelectingText) {
|
if (mIsSelectingText) {
|
||||||
// Do not scroll when selecting text.
|
// Do not scroll when selecting text.
|
||||||
int rowsInHistory = mEmulator.getScreen().getActiveTranscriptRows();
|
|
||||||
int rowShift = mEmulator.getScrollCounter();
|
int rowShift = mEmulator.getScrollCounter();
|
||||||
if (-mTopRow + rowShift > rowsInHistory) {
|
if (-mTopRow + rowShift > rowsInHistory) {
|
||||||
// .. unless we're hitting the end of history transcript, in which
|
// .. unless we're hitting the end of history transcript, in which
|
||||||
@@ -516,8 +518,7 @@ public final class TerminalView extends View {
|
|||||||
mSelY2 = tmpY1;
|
mSelY2 = tmpY1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
|
mActionMode.invalidateContentRect();
|
||||||
mActionMode.invalidateContentRect();
|
|
||||||
invalidate();
|
invalidate();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -873,41 +874,36 @@ public final class TerminalView extends View {
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
mActionMode = startActionMode(new ActionMode.Callback2() {
|
||||||
mActionMode = startActionMode(new ActionMode.Callback2() {
|
@Override
|
||||||
@Override
|
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
|
||||||
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
|
return callback.onCreateActionMode(mode, menu);
|
||||||
return callback.onCreateActionMode(mode, menu);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
|
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
|
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
|
||||||
return callback.onActionItemClicked(mode, item);
|
return callback.onActionItemClicked(mode, item);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyActionMode(ActionMode mode) {
|
public void onDestroyActionMode(ActionMode mode) {
|
||||||
// Ignore.
|
// Ignore.
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onGetContentRect(ActionMode mode, View view, Rect outRect) {
|
|
||||||
int x1 = Math.round(mSelX1 * mRenderer.mFontWidth);
|
|
||||||
int x2 = Math.round(mSelX2 * mRenderer.mFontWidth);
|
|
||||||
int y1 = Math.round((mSelY1 - mTopRow) * mRenderer.mFontLineSpacing);
|
|
||||||
int y2 = Math.round((mSelY2 + 1 - mTopRow) * mRenderer.mFontLineSpacing);
|
|
||||||
outRect.set(Math.min(x1, x2), y1, Math.max(x1, x2), y2);
|
|
||||||
}
|
|
||||||
}, ActionMode.TYPE_FLOATING);
|
|
||||||
} else {
|
|
||||||
mActionMode = startActionMode(callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onGetContentRect(ActionMode mode, View view, Rect outRect) {
|
||||||
|
int x1 = Math.round(mSelX1 * mRenderer.mFontWidth);
|
||||||
|
int x2 = Math.round(mSelX2 * mRenderer.mFontWidth);
|
||||||
|
int y1 = Math.round((mSelY1 - mTopRow) * mRenderer.mFontLineSpacing);
|
||||||
|
int y2 = Math.round((mSelY2 + 1 - mTopRow) * mRenderer.mFontLineSpacing);
|
||||||
|
outRect.set(Math.min(x1, x2), y1, Math.max(x1, x2), y2);
|
||||||
|
}
|
||||||
|
}, ActionMode.TYPE_FLOATING);
|
||||||
|
|
||||||
invalidate();
|
invalidate();
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user