Compare commits
124 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cee0466dd7 | ||
|
|
de4f334e24 | ||
|
|
ab205ae05b | ||
|
|
b29b24f507 | ||
|
|
b3472e9e62 | ||
|
|
ab59e08959 | ||
|
|
8b566485e8 | ||
|
|
231c02a0c7 | ||
|
|
65f30e77ba | ||
|
|
686677ae45 | ||
|
|
85037a75a6 | ||
|
|
6025afc2c0 | ||
|
|
a4e4f76775 | ||
|
|
02af113dda | ||
|
|
fc4ef838bf | ||
|
|
86bd3ca21b | ||
|
|
367398bafb | ||
|
|
dd502e55f8 | ||
|
|
798125ef7a | ||
|
|
5126f06e06 | ||
|
|
0bdbf314ef | ||
|
|
2deb9899bd | ||
|
|
694ccc38c4 | ||
|
|
c949940374 | ||
|
|
d09f70de1e | ||
|
|
ac338ce2c5 | ||
|
|
092a83a688 | ||
|
|
3533b13de8 | ||
|
|
d68a0f05be | ||
|
|
9e5293a08e | ||
|
|
3f04a0a0d5 | ||
|
|
e558f824c5 | ||
|
|
8ff72ddd8b | ||
|
|
3e05356a69 | ||
|
|
82673d3f82 | ||
|
|
f2757fdb76 | ||
|
|
81fb669d0e | ||
|
|
4a45b1b617 | ||
|
|
62b08b3cf1 | ||
|
|
1e83ea3151 | ||
|
|
9c42fdb3d6 | ||
|
|
42e3163e92 | ||
|
|
68912139f6 | ||
|
|
edc92bbcbb | ||
|
|
d7520642de | ||
|
|
adae111d5c | ||
|
|
dc9272790b | ||
|
|
05ea2ea238 | ||
|
|
ad293562bc | ||
|
|
f9a565d1e0 | ||
|
|
5b3909cb73 | ||
|
|
7913e765d5 | ||
|
|
ed3a3269d8 | ||
|
|
dc3994d2cf | ||
|
|
c972377bd1 | ||
|
|
d4be782c03 | ||
|
|
c29909726c | ||
|
|
45bac89298 | ||
|
|
c06770c353 | ||
|
|
52a627efc8 | ||
|
|
2e9383720c | ||
|
|
58f9f1be71 | ||
|
|
058441dda6 | ||
|
|
888802a519 | ||
|
|
1a09b6d2a6 | ||
|
|
9d3f7734a1 | ||
|
|
61f766b59f | ||
|
|
3f08376881 | ||
|
|
cf06e70429 | ||
|
|
0714e435cb | ||
|
|
c26315185f | ||
|
|
1be5139253 | ||
|
|
adc43c40c5 | ||
|
|
779b1ca1f8 | ||
|
|
e375f99b0c | ||
|
|
0ac55cd77a | ||
|
|
4aefa5049b | ||
|
|
5b14124258 | ||
|
|
1a9c38374c | ||
|
|
eefd504f08 | ||
|
|
19f838e3d1 | ||
|
|
63adb2b132 | ||
|
|
7d3a988d2f | ||
|
|
5cd705d6d1 | ||
|
|
af8b269b51 | ||
|
|
8c220eaaea | ||
|
|
0142e1ed0e | ||
|
|
ac0a349ed9 | ||
|
|
548b0916b6 | ||
|
|
009de5a3ee | ||
|
|
41d0d60017 | ||
|
|
12ac0fa73c | ||
|
|
835dfc0276 | ||
|
|
f60316835f | ||
|
|
f74a091be6 | ||
|
|
dd6cb5221d | ||
|
|
cab6df5c0e | ||
|
|
f6f0809558 | ||
|
|
11ed7e45d8 | ||
|
|
ed1874db05 | ||
|
|
cb60803a80 | ||
|
|
fc92a27cb2 | ||
|
|
29e62e608f | ||
|
|
8a7f93d722 | ||
|
|
420683fe65 | ||
|
|
a3256ed551 | ||
|
|
57add98e3c | ||
|
|
6e5c04e04f | ||
|
|
528a05ef61 | ||
|
|
cb2f892dc5 | ||
|
|
8b6e8d7fdd | ||
|
|
d0eeaa9fc3 | ||
|
|
b793913481 | ||
|
|
d48b438c40 | ||
|
|
11afe895e1 | ||
|
|
bc96f71a2d | ||
|
|
87dfded5e6 | ||
|
|
7c0ae4cb54 | ||
|
|
b917acbbfa | ||
|
|
7d9d6fb797 | ||
|
|
74040dd37f | ||
|
|
e94f06d0f7 | ||
|
|
af21b6dc3e | ||
|
|
e0e8257f1c |
2
.idea/gradle.xml
generated
@@ -10,6 +10,8 @@
|
|||||||
<set>
|
<set>
|
||||||
<option value="$PROJECT_DIR$" />
|
<option value="$PROJECT_DIR$" />
|
||||||
<option value="$PROJECT_DIR$/app" />
|
<option value="$PROJECT_DIR$/app" />
|
||||||
|
<option value="$PROJECT_DIR$/terminal-emulator" />
|
||||||
|
<option value="$PROJECT_DIR$/terminal-view" />
|
||||||
</set>
|
</set>
|
||||||
</option>
|
</option>
|
||||||
<option name="resolveModulePerSourceSet" value="false" />
|
<option name="resolveModulePerSourceSet" value="false" />
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
sudo: true
|
sudo: false
|
||||||
language: android
|
language: android
|
||||||
jdk: oraclejdk8
|
jdk: oraclejdk8
|
||||||
|
|
||||||
@@ -6,20 +6,19 @@ env:
|
|||||||
global:
|
global:
|
||||||
# The next declaration is the encrypted COVERITY_SCAN_TOKEN, created
|
# The next declaration is the encrypted COVERITY_SCAN_TOKEN, created
|
||||||
# via the "travis encrypt" command using the project repo's public key
|
# via the "travis encrypt" command using the project repo's public key
|
||||||
- secure: "ACnFJxw0VusS2lnGXL+epP/CNJmftWS39YcPdgN2EurWw5ZfXSo7vi+zpMB+11IBS3LQyLFFUambi2N9L4lbReZkHVkoVcZFGZlwbXNTAeqT8CABPTcuOyEOZU4bJwqeYU87ztYipENMLNECaZrgWx5odbWLKnSJQw7Zkb4ArCstfXfYk9u8q49ThRxQyGwHW2xKp1an5aa+3Y6IY+ywsSHw6AvXbyFH078Kolxy86caagczcfmKcMi15QYzwAvFggUphvsO3M5PHJMQXuaNlQxDcQRGUEXsK8aZE0dPH5PB97SFjDALZqI7NEpjZAk5htWjX48ssW064LDbjcBg/ZLgDd8R8uhA159NVZgvcnP2czCn6pmggx1sW5MBmcj7i+bJS2ejaMO+KoovWlVvsch742H5QR6rQaNkjDZRsGVLYvJaR1gBLs898UoT1hcHWoqLVR22r2VFo7OWWCRfNRvZuZDR2HIrYRdFvn8P3nWVMkvXwgsOlxWG5sN+yQqW+6lZS7hivsFhtYs4CkRdoZIan3Qvi/CkY8Lg+ESkZ3IJ0NnId8qOWH+8Xl1sqZ7xlsWTd1sYYHlpvkdvqw1HNLP22EpwwKW5Kb5zBEd/qs3o1OO0Tqa0MR6JpgGdHHRk1iZ25+qTfRVP06vO2RXsgAx4SZfO7DyB0QZn8tGNMMI="
|
- secure: "LdajbHNfRlpnqzhX5KY2Vr7KtzU9vXDs1TCNn93J6Dt522f2AaiyUDJvISvz+uslk0WJiS5bB5vGwQmXginxz6Qi6uMgMbjWXulv1vfs6ZviKpUX348DOp1qKPa8WfVNB66F84SwGIfc8cRMAgCFw79l/DFgLErubF8vKo1wZ8Hmvrz//+RJ0BGMa3YRc4VyJhAL0P+0Wc1Q2Im7R9EovAxC5pZXBIMSgr6g5GzLWPisbNLXpMPGsDeYhcenO6XCtCCy+aNxUYM8vcrLDzlVXR5Hy7KEs/MGRTS0Yk13TWUEYa5wBpKelFTszdWYLVn5ANreh/aXRVfHpnW3epotMYguLx1kSvOhWEnc4F+qqv3nle2LpDg9Y9bcLyTTcYnPl9smqEVVjEDu0FoIr1V58xkG4Oc6BPIvLRjlMVU96PXh2HxMLuGsJ/xM+uAFU9oVMbC07xn42Eu5O4NHOHJNOwMWac4/lSKRK8W/7/vWuXj5vhkD9ZsGVpN70UtY5HAfNUGADnTeDblvjgFTNZ2mUN/u0o7Z8ZFURYllZ9YU+Vr2nPf9CAhVBjuwFWx8uRQpAg1aDmc1dVMJijRBeBeU/uWhYqsGp34wkNEl8VGzob4R4QTyI8+T7CndGqKVmbTK/SjqKhjjPpbXIAfOH+JtxvAnNmb8XeQSJ32uK2nexFo="
|
||||||
|
|
||||||
android:
|
android:
|
||||||
components:
|
components:
|
||||||
- platform-tools
|
- platform-tools
|
||||||
- tools
|
- tools
|
||||||
- build-tools-25.0.2
|
- build-tools-27.0.2
|
||||||
- android-25
|
- android-27
|
||||||
- extra-android-m2repository
|
- extra-android-m2repository
|
||||||
|
|
||||||
before_install:
|
before_install:
|
||||||
- git clone https://github.com/urho3d/android-ndk.git $HOME/android-ndk
|
- git clone https://github.com/urho3d/android-ndk.git $HOME/android-ndk
|
||||||
- export ANDROID_NDK_HOME=$HOME/android-ndk
|
- export ANDROID_NDK_HOME=$HOME/android-ndk
|
||||||
- echo -n | openssl s_client -connect scan.coverity.com:443 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' | sudo tee -a /etc/ssl/certs/ca-certificates.crt
|
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- ./gradlew testDebugUnitTest
|
- ./gradlew testDebugUnitTest
|
||||||
|
|||||||
3
LICENSE.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
Released under [the GPLv3 license](https://www.gnu.org/licenses/gpl.html).
|
||||||
|
|
||||||
|
Contains code from `Terminal Emulator for Android` by which is released under [the Apache License 2.0](https://www.apache.org/licenses/).
|
||||||
@@ -7,15 +7,14 @@ Termux app
|
|||||||
|
|
||||||
* [Termux on Google Play Store](https://play.google.com/store/apps/details?id=com.termux)
|
* [Termux on Google Play Store](https://play.google.com/store/apps/details?id=com.termux)
|
||||||
* [Termux on F-Droid](https://f-droid.org/repository/browse/?fdid=com.termux)
|
* [Termux on F-Droid](https://f-droid.org/repository/browse/?fdid=com.termux)
|
||||||
* [Termux Help](http://termux.com/help/)
|
* [Termux Facebook](https://facebook.com/termux/)
|
||||||
* [Termux Google+ community](http://termux.com/community/)
|
* [Termux Google+ community](http://termux.com/community/)
|
||||||
|
* [Termux Help](http://termux.com/help/)
|
||||||
|
* [Termux Twitter](http://twitter.com/termux/)
|
||||||
|
* [Termux Wiki](https://wiki.termux.com/wiki/)
|
||||||
|
|
||||||
Note that this repository is for the app itself (the user interface and the terminal emulation). For the packages installable inside the app, see [termux/termux-packages](https://github.com/termux/termux-packages)
|
Note that this repository is for the app itself (the user interface and the terminal emulation). For the packages installable inside the app, see [termux/termux-packages](https://github.com/termux/termux-packages)
|
||||||
|
|
||||||
License
|
|
||||||
=======
|
|
||||||
Released under [the GPLv3 license](https://www.gnu.org/licenses/gpl.html). Contains code from `Terminal Emulator for Android` which is released under [the Apache License 2.0](https://www.apache.org/licenses/).
|
|
||||||
|
|
||||||
Terminal resources
|
Terminal resources
|
||||||
==================
|
==================
|
||||||
* [XTerm control sequences](http://invisible-island.net/xterm/ctlseqs/ctlseqs.html)
|
* [XTerm control sequences](http://invisible-island.net/xterm/ctlseqs/ctlseqs.html)
|
||||||
|
|||||||
@@ -1,30 +1,21 @@
|
|||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 25
|
compileSdkVersion 27
|
||||||
buildToolsVersion "25.0.2"
|
buildToolsVersion "27.0.2"
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile 'com.android.support:support-annotations:25.0.1'
|
implementation 'com.android.support:support-annotations:27.0.1'
|
||||||
compile "com.android.support:support-v4:25.0.1"
|
implementation "com.android.support:support-core-ui:27.0.1"
|
||||||
|
implementation project(":terminal-view")
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "com.termux"
|
applicationId "com.termux"
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 25
|
targetSdkVersion 27
|
||||||
versionCode 45
|
versionCode 57
|
||||||
versionName "0.45"
|
versionName "0.57"
|
||||||
|
|
||||||
externalNativeBuild {
|
|
||||||
ndkBuild {
|
|
||||||
cFlags "-std=c11", "-Wall", "-Wextra", "-Werror", "-Os", "-fno-stack-protector", "-Wl,--gc-sections"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ndk {
|
|
||||||
abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
@@ -34,14 +25,8 @@ android {
|
|||||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
externalNativeBuild {
|
|
||||||
ndkBuild {
|
|
||||||
path "src/main/jni/Android.mk"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
testCompile 'junit:junit:4.12'
|
testImplementation 'junit:junit:4.12'
|
||||||
}
|
}
|
||||||
|
|||||||
10
app/proguard-rules.pro
vendored
@@ -7,11 +7,5 @@
|
|||||||
# For more details, see
|
# For more details, see
|
||||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|
||||||
# Add any project specific keep options here:
|
-renamesourcefileattribute SourceFile
|
||||||
|
-keepattributes SourceFile,LineNumberTable
|
||||||
# If your project uses WebView with JS, uncomment the following
|
|
||||||
# and specify the fully qualified class name to the JavaScript interface
|
|
||||||
# class:
|
|
||||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
|
||||||
# public *;
|
|
||||||
#}
|
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
package com.termux;
|
|
||||||
|
|
||||||
import android.app.Application;
|
|
||||||
import android.test.ApplicationTestCase;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
|
|
||||||
*/
|
|
||||||
public class ApplicationTest extends ApplicationTestCase<Application> {
|
|
||||||
public ApplicationTest() {
|
|
||||||
super(Application.class);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
package="com.termux"
|
package="com.termux"
|
||||||
android:installLocation="internalOnly"
|
android:installLocation="internalOnly"
|
||||||
android:sharedUserId="com.termux"
|
android:sharedUserId="com.termux"
|
||||||
@@ -13,6 +14,7 @@
|
|||||||
<uses-permission android:name="android.permission.VIBRATE" />
|
<uses-permission android:name="android.permission.VIBRATE" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
|
android:extractNativeLibs="true"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:fullBackupContent="@xml/backupscheme"
|
android:fullBackupContent="@xml/backupscheme"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
@@ -22,6 +24,10 @@
|
|||||||
android:theme="@style/Theme.Termux"
|
android:theme="@style/Theme.Termux"
|
||||||
android:supportsRtl="false" >
|
android:supportsRtl="false" >
|
||||||
|
|
||||||
|
<!-- This (or rather, value 2.1 or higher) is needed to make the Samsung Galaxy S8
|
||||||
|
mark the app with "This app is optimized to run in full screen." -->
|
||||||
|
<meta-data android:name="android.max_aspect" android:value="10.0" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="com.termux.app.TermuxActivity"
|
android:name="com.termux.app.TermuxActivity"
|
||||||
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
|
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
|
||||||
@@ -64,7 +70,7 @@
|
|||||||
<data android:mimeType="video/*" />
|
<data android:mimeType="video/*" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
<!-- Be more restrictive for viewing files, restricting ourselves to text files. -->
|
<!-- Be more restrictive for viewing files, restricting ourselves to text files. -->
|
||||||
<intent-filter>
|
<intent-filter tools:ignore="AppLinkUrlError">
|
||||||
<action android:name="android.intent.action.VIEW"/>
|
<action android:name="android.intent.action.VIEW"/>
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<data android:mimeType="text/*" />
|
<data android:mimeType="text/*" />
|
||||||
@@ -75,6 +81,18 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<activity-alias
|
||||||
|
android:name=".HomeActivity"
|
||||||
|
android:targetActivity="com.termux.app.TermuxActivity">
|
||||||
|
|
||||||
|
<!-- Launch activity automatically on boot on Android Things devices -->
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
|
<category android:name="android.intent.category.IOT_LAUNCHER"/>
|
||||||
|
<category android:name="android.intent.category.DEFAULT"/>
|
||||||
|
</intent-filter>
|
||||||
|
</activity-alias>
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name=".filepicker.TermuxDocumentsProvider"
|
android:name=".filepicker.TermuxDocumentsProvider"
|
||||||
android:authorities="com.termux.documents"
|
android:authorities="com.termux.documents"
|
||||||
@@ -90,6 +108,15 @@
|
|||||||
android:name="com.termux.app.TermuxService"
|
android:name="com.termux.app.TermuxService"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
|
<receiver android:name=".app.TermuxOpenReceiver" />
|
||||||
|
|
||||||
|
<provider android:authorities="com.termux.files"
|
||||||
|
android:readPermission="android.permission.permRead"
|
||||||
|
android:exported="true"
|
||||||
|
android:grantUriPermissions="true"
|
||||||
|
android:name="com.termux.app.TermuxOpenReceiver$ContentProvider" />
|
||||||
|
|
||||||
|
<meta-data android:name="com.sec.android.support.multiwindow" android:value="true" />
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import java.lang.reflect.Field;
|
|||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -105,7 +106,6 @@ public final class BackgroundJob {
|
|||||||
// 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");
|
final String externalStorageEnv = "EXTERNAL_STORAGE=" + System.getenv("EXTERNAL_STORAGE");
|
||||||
String[] env;
|
|
||||||
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");
|
final String pathEnv = "PATH=" + System.getenv("PATH");
|
||||||
@@ -116,8 +116,9 @@ public final class BackgroundJob {
|
|||||||
final String langEnv = "LANG=en_US.UTF-8";
|
final String langEnv = "LANG=en_US.UTF-8";
|
||||||
final String pathEnv = "PATH=" + TermuxService.PREFIX_PATH + "/bin:" + TermuxService.PREFIX_PATH + "/bin/applets";
|
final String pathEnv = "PATH=" + TermuxService.PREFIX_PATH + "/bin:" + TermuxService.PREFIX_PATH + "/bin/applets";
|
||||||
final String pwdEnv = "PWD=" + cwd;
|
final String pwdEnv = "PWD=" + cwd;
|
||||||
|
final String tmpdirEnv = "TMPDIR=" + TermuxService.PREFIX_PATH + "/tmp";
|
||||||
|
|
||||||
return new String[]{termEnv, homeEnv, prefixEnv, ps1Env, ldEnv, langEnv, pathEnv, pwdEnv, androidRootEnv, androidDataEnv, externalStorageEnv};
|
return new String[]{termEnv, homeEnv, prefixEnv, ps1Env, ldEnv, langEnv, pathEnv, pwdEnv, androidRootEnv, androidDataEnv, externalStorageEnv, tmpdirEnv};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,7 +159,6 @@ public final class BackgroundJob {
|
|||||||
if (c == ' ' || c == '\n') {
|
if (c == ' ' || c == '\n') {
|
||||||
if (builder.length() == 0) {
|
if (builder.length() == 0) {
|
||||||
// Skip whitespace after shebang.
|
// Skip whitespace after shebang.
|
||||||
continue;
|
|
||||||
} else {
|
} else {
|
||||||
// End of shebang.
|
// End of shebang.
|
||||||
String executable = builder.toString();
|
String executable = builder.toString();
|
||||||
@@ -186,9 +186,7 @@ public final class BackgroundJob {
|
|||||||
List<String> result = new ArrayList<>();
|
List<String> result = new ArrayList<>();
|
||||||
if (interpreter != null) result.add(interpreter);
|
if (interpreter != null) result.add(interpreter);
|
||||||
result.add(fileToExecute);
|
result.add(fileToExecute);
|
||||||
if (args != null) {
|
if (args != null) Collections.addAll(result, args);
|
||||||
for (String arg : args) result.add(arg);
|
|
||||||
}
|
|
||||||
return result.toArray(new String[result.size()]);
|
return result.toArray(new String[result.size()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.termux.app;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.view.Gravity;
|
import android.view.Gravity;
|
||||||
|
import android.view.HapticFeedbackConstants;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
@@ -60,7 +61,7 @@ public final class ExtraKeysView extends GridLayout {
|
|||||||
view.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, keyCode));
|
view.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, keyCode));
|
||||||
view.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, keyCode));
|
view.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, keyCode));
|
||||||
} else {
|
} else {
|
||||||
TerminalView terminalView = (TerminalView) view.findViewById(R.id.terminal_view);
|
TerminalView terminalView = view.findViewById(R.id.terminal_view);
|
||||||
TerminalSession session = terminalView.getCurrentSession();
|
TerminalSession session = terminalView.getCurrentSession();
|
||||||
if (session != null) session.write(chars);
|
if (session != null) session.write(chars);
|
||||||
}
|
}
|
||||||
@@ -144,6 +145,7 @@ public final class ExtraKeysView extends GridLayout {
|
|||||||
button.setOnClickListener(new OnClickListener() {
|
button.setOnClickListener(new OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
|
finalButton.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP);
|
||||||
View root = getRootView();
|
View root = getRootView();
|
||||||
switch (buttonText) {
|
switch (buttonText) {
|
||||||
case "CTRL":
|
case "CTRL":
|
||||||
|
|||||||
@@ -1,67 +0,0 @@
|
|||||||
package com.termux.app;
|
|
||||||
|
|
||||||
import android.graphics.Color;
|
|
||||||
import android.graphics.drawable.ColorDrawable;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.view.View;
|
|
||||||
|
|
||||||
import com.termux.R;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility to manage full screen immersive mode.
|
|
||||||
* <p/>
|
|
||||||
* See https://code.google.com/p/android/issues/detail?id=5497
|
|
||||||
*/
|
|
||||||
final class FullScreenHelper {
|
|
||||||
|
|
||||||
private boolean mEnabled = false;
|
|
||||||
final TermuxActivity mActivity;
|
|
||||||
|
|
||||||
public FullScreenHelper(TermuxActivity activity) {
|
|
||||||
this.mActivity = activity;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setImmersive(boolean enabled) {
|
|
||||||
if (enabled == mEnabled) return;
|
|
||||||
mEnabled = enabled;
|
|
||||||
|
|
||||||
View decorView = mActivity.getWindow().getDecorView();
|
|
||||||
|
|
||||||
if (enabled) {
|
|
||||||
decorView.setOnSystemUiVisibilityChangeListener
|
|
||||||
(new View.OnSystemUiVisibilityChangeListener() {
|
|
||||||
@Override
|
|
||||||
public void onSystemUiVisibilityChange(int visibility) {
|
|
||||||
if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) {
|
|
||||||
if (mActivity.mSettings.isShowExtraKeys()) {
|
|
||||||
mActivity.findViewById(R.id.viewpager).setVisibility(View.VISIBLE);
|
|
||||||
}
|
|
||||||
setImmersiveMode();
|
|
||||||
} else {
|
|
||||||
mActivity.findViewById(R.id.viewpager).setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
setImmersiveMode();
|
|
||||||
} else {
|
|
||||||
decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
|
|
||||||
decorView.setOnSystemUiVisibilityChangeListener(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isColorLight(int color) {
|
|
||||||
double darkness = 1 - (0.299 * Color.red(color) + 0.587 * Color.green(color) + 0.114 * Color.blue(color)) / 255;
|
|
||||||
return darkness < 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setImmersiveMode() {
|
|
||||||
int flags = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
|
||||||
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
|
|
||||||
| View.SYSTEM_UI_FLAG_FULLSCREEN;
|
|
||||||
int color = ((ColorDrawable) mActivity.getWindow().getDecorView().getBackground()).getColor();
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && isColorLight(color))
|
|
||||||
flags |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
|
|
||||||
mActivity.getWindow().getDecorView().setSystemUiVisibility(flags);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -49,8 +49,6 @@ import android.view.View.OnClickListener;
|
|||||||
import android.view.View.OnLongClickListener;
|
import android.view.View.OnLongClickListener;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
import android.view.animation.Animation;
|
|
||||||
import android.view.animation.AnimationUtils;
|
|
||||||
import android.view.inputmethod.InputMethodManager;
|
import android.view.inputmethod.InputMethodManager;
|
||||||
import android.widget.AdapterView;
|
import android.widget.AdapterView;
|
||||||
import android.widget.AdapterView.OnItemClickListener;
|
import android.widget.AdapterView.OnItemClickListener;
|
||||||
@@ -75,6 +73,7 @@ import java.io.InputStream;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
@@ -97,7 +96,6 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
private static final int CONTEXTMENU_KILL_PROCESS_ID = 4;
|
private static final int CONTEXTMENU_KILL_PROCESS_ID = 4;
|
||||||
private static final int CONTEXTMENU_RESET_TERMINAL_ID = 5;
|
private static final int CONTEXTMENU_RESET_TERMINAL_ID = 5;
|
||||||
private static final int CONTEXTMENU_STYLING_ID = 6;
|
private static final int CONTEXTMENU_STYLING_ID = 6;
|
||||||
private static final int CONTEXTMENU_TOGGLE_FULLSCREEN_ID = 7;
|
|
||||||
private static final int CONTEXTMENU_HELP_ID = 8;
|
private static final int CONTEXTMENU_HELP_ID = 8;
|
||||||
|
|
||||||
private static final int MAX_SESSIONS = 8;
|
private static final int MAX_SESSIONS = 8;
|
||||||
@@ -113,8 +111,6 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
|
|
||||||
ExtraKeysView mExtraKeysView;
|
ExtraKeysView mExtraKeysView;
|
||||||
|
|
||||||
final FullScreenHelper mFullScreenHelper = new FullScreenHelper(this);
|
|
||||||
|
|
||||||
TermuxPreferences mSettings;
|
TermuxPreferences mSettings;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -141,8 +137,6 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
.setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION).build()).build();
|
.setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION).build()).build();
|
||||||
int mBellSoundId;
|
int mBellSoundId;
|
||||||
|
|
||||||
Animation mOnBellAnimation;
|
|
||||||
|
|
||||||
private final BroadcastReceiver mBroadcastReceiever = new BroadcastReceiver() {
|
private final BroadcastReceiver mBroadcastReceiever = new BroadcastReceiver() {
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(Context context, Intent intent) {
|
public void onReceive(Context context, Intent intent) {
|
||||||
@@ -161,7 +155,6 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
|
|
||||||
void checkForFontAndColors() {
|
void checkForFontAndColors() {
|
||||||
try {
|
try {
|
||||||
// Hard-coded paths since this file is used also in Termux:Float.
|
|
||||||
@SuppressLint("SdCardPath") File fontFile = new File("/data/data/com.termux/files/home/.termux/font.ttf");
|
@SuppressLint("SdCardPath") File fontFile = new File("/data/data/com.termux/files/home/.termux/font.ttf");
|
||||||
@SuppressLint("SdCardPath") File colorsFile = new File("/data/data/com.termux/files/home/.termux/colors.properties");
|
@SuppressLint("SdCardPath") File colorsFile = new File("/data/data/com.termux/files/home/.termux/colors.properties");
|
||||||
|
|
||||||
@@ -213,19 +206,16 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
public void onCreate(Bundle bundle) {
|
public void onCreate(Bundle bundle) {
|
||||||
super.onCreate(bundle);
|
super.onCreate(bundle);
|
||||||
|
|
||||||
mOnBellAnimation = AnimationUtils.loadAnimation(this, R.anim.on_bell);
|
|
||||||
|
|
||||||
mSettings = new TermuxPreferences(this);
|
mSettings = new TermuxPreferences(this);
|
||||||
|
|
||||||
setContentView(R.layout.drawer_layout);
|
setContentView(R.layout.drawer_layout);
|
||||||
mTerminalView = (TerminalView) findViewById(R.id.terminal_view);
|
mTerminalView = findViewById(R.id.terminal_view);
|
||||||
mTerminalView.setOnKeyListener(new TermuxKeyListener(this));
|
mTerminalView.setOnKeyListener(new TermuxViewClient(this));
|
||||||
|
|
||||||
mTerminalView.setTextSize(mSettings.getFontSize());
|
mTerminalView.setTextSize(mSettings.getFontSize());
|
||||||
mFullScreenHelper.setImmersive(mSettings.isFullScreen());
|
|
||||||
mTerminalView.requestFocus();
|
mTerminalView.requestFocus();
|
||||||
|
|
||||||
final ViewPager viewPager = (ViewPager) findViewById(R.id.viewpager);
|
final ViewPager viewPager = findViewById(R.id.viewpager);
|
||||||
if (mSettings.isShowExtraKeys()) viewPager.setVisibility(View.VISIBLE);
|
if (mSettings.isShowExtraKeys()) viewPager.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
viewPager.setAdapter(new PagerAdapter() {
|
viewPager.setAdapter(new PagerAdapter() {
|
||||||
@@ -235,26 +225,29 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isViewFromObject(View view, Object object) {
|
public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
|
||||||
return view == object;
|
return view == object;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public Object instantiateItem(ViewGroup collection, int position) {
|
public Object instantiateItem(@NonNull ViewGroup collection, int position) {
|
||||||
LayoutInflater inflater = LayoutInflater.from(TermuxActivity.this);
|
LayoutInflater inflater = LayoutInflater.from(TermuxActivity.this);
|
||||||
View layout;
|
View layout;
|
||||||
if (position == 0) {
|
if (position == 0) {
|
||||||
layout = mExtraKeysView = (ExtraKeysView) inflater.inflate(R.layout.extra_keys_main, collection, false);
|
layout = mExtraKeysView = (ExtraKeysView) inflater.inflate(R.layout.extra_keys_main, collection, false);
|
||||||
} else {
|
} else {
|
||||||
layout = inflater.inflate(R.layout.extra_keys_right, collection, false);
|
layout = inflater.inflate(R.layout.extra_keys_right, collection, false);
|
||||||
final EditText editText = (EditText) layout.findViewById(R.id.text_input);
|
final EditText editText = layout.findViewById(R.id.text_input);
|
||||||
editText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
|
editText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
|
||||||
@Override
|
@Override
|
||||||
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
||||||
TerminalSession session = getCurrentTermSession();
|
TerminalSession session = getCurrentTermSession();
|
||||||
if (session != null) {
|
if (session != null) {
|
||||||
if (session.isRunning()) {
|
if (session.isRunning()) {
|
||||||
session.write(editText.getText().toString() + "\n");
|
String textToSend = editText.getText().toString();
|
||||||
|
if (textToSend.length() == 0) textToSend = "\n";
|
||||||
|
session.write(textToSend);
|
||||||
} else {
|
} else {
|
||||||
removeFinishedSession(session);
|
removeFinishedSession(session);
|
||||||
}
|
}
|
||||||
@@ -269,7 +262,7 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void destroyItem(ViewGroup collection, int position, Object view) {
|
public void destroyItem(@NonNull ViewGroup collection, int position, @NonNull Object view) {
|
||||||
collection.removeView((View) view);
|
collection.removeView((View) view);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -280,7 +273,7 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
if (position == 0) {
|
if (position == 0) {
|
||||||
mTerminalView.requestFocus();
|
mTerminalView.requestFocus();
|
||||||
} else {
|
} else {
|
||||||
final EditText editText = (EditText) viewPager.findViewById(R.id.text_input);
|
final EditText editText = viewPager.findViewById(R.id.text_input);
|
||||||
if (editText != null) editText.requestFocus();
|
if (editText != null) editText.requestFocus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -344,7 +337,7 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
}
|
}
|
||||||
|
|
||||||
void toggleShowExtraKeys() {
|
void toggleShowExtraKeys() {
|
||||||
final ViewPager viewPager = (ViewPager) findViewById(R.id.viewpager);
|
final ViewPager viewPager = findViewById(R.id.viewpager);
|
||||||
final boolean showNow = mSettings.toggleShowExtraKeys(TermuxActivity.this);
|
final boolean showNow = mSettings.toggleShowExtraKeys(TermuxActivity.this);
|
||||||
viewPager.setVisibility(showNow ? View.VISIBLE : View.GONE);
|
viewPager.setVisibility(showNow ? View.VISIBLE : View.GONE);
|
||||||
if (showNow && viewPager.getCurrentItem() == 1) {
|
if (showNow && viewPager.getCurrentItem() == 1) {
|
||||||
@@ -401,7 +394,6 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
@Override
|
@Override
|
||||||
public void onClipboardText(TerminalSession session, String text) {
|
public void onClipboardText(TerminalSession session, String text) {
|
||||||
if (!mIsVisible) return;
|
if (!mIsVisible) return;
|
||||||
showToast("Clipboard:\n\"" + text + "\"", false);
|
|
||||||
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
|
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
|
||||||
clipboard.setPrimaryClip(new ClipData(null, new String[]{"text/plain"}, new ClipData.Item(text)));
|
clipboard.setPrimaryClip(new ClipData(null, new String[]{"text/plain"}, new ClipData.Item(text)));
|
||||||
}
|
}
|
||||||
@@ -410,8 +402,6 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
public void onBell(TerminalSession session) {
|
public void onBell(TerminalSession session) {
|
||||||
if (!mIsVisible) return;
|
if (!mIsVisible) return;
|
||||||
|
|
||||||
mTerminalView.startAnimation(mOnBellAnimation);
|
|
||||||
|
|
||||||
switch (mSettings.mBellBehaviour) {
|
switch (mSettings.mBellBehaviour) {
|
||||||
case TermuxPreferences.BELL_BEEP:
|
case TermuxPreferences.BELL_BEEP:
|
||||||
mBellSoundPool.play(mBellSoundId, 1.f, 1.f, 1, 0, 1.f);
|
mBellSoundPool.play(mBellSoundId, 1.f, 1.f, 1, 0, 1.f);
|
||||||
@@ -432,7 +422,7 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ListView listView = (ListView) findViewById(R.id.left_drawer_list);
|
ListView listView = findViewById(R.id.left_drawer_list);
|
||||||
mListViewAdapter = new ArrayAdapter<TerminalSession>(getApplicationContext(), R.layout.line_in_drawer, mTermService.getSessions()) {
|
mListViewAdapter = new ArrayAdapter<TerminalSession>(getApplicationContext(), R.layout.line_in_drawer, mTermService.getSessions()) {
|
||||||
final StyleSpan boldSpan = new StyleSpan(Typeface.BOLD);
|
final StyleSpan boldSpan = new StyleSpan(Typeface.BOLD);
|
||||||
final StyleSpan italicSpan = new StyleSpan(Typeface.ITALIC);
|
final StyleSpan italicSpan = new StyleSpan(Typeface.ITALIC);
|
||||||
@@ -449,7 +439,7 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
TerminalSession sessionAtRow = getItem(position);
|
TerminalSession sessionAtRow = getItem(position);
|
||||||
boolean sessionRunning = sessionAtRow.isRunning();
|
boolean sessionRunning = sessionAtRow.isRunning();
|
||||||
|
|
||||||
TextView firstLineView = (TextView) row.findViewById(R.id.row_line);
|
TextView firstLineView = row.findViewById(R.id.row_line);
|
||||||
|
|
||||||
String name = sessionAtRow.mSessionName;
|
String name = sessionAtRow.mSessionName;
|
||||||
String sessionTitle = sessionAtRow.getTitle();
|
String sessionTitle = sessionAtRow.getTitle();
|
||||||
@@ -500,17 +490,6 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
public void run() {
|
public void run() {
|
||||||
if (mTermService == null) return; // Activity might have been destroyed.
|
if (mTermService == null) return; // Activity might have been destroyed.
|
||||||
try {
|
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);
|
addNewSession(false, null);
|
||||||
} catch (WindowManager.BadTokenException e) {
|
} catch (WindowManager.BadTokenException e) {
|
||||||
// Activity finished - ignore.
|
// Activity finished - ignore.
|
||||||
@@ -556,10 +535,8 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onServiceDisconnected(ComponentName name) {
|
public void onServiceDisconnected(ComponentName name) {
|
||||||
if (mTermService != null) {
|
// Respect being stopped from the TermuxService notification action.
|
||||||
// Respect being stopped from the TermuxService notification action.
|
finish();
|
||||||
finish();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@@ -663,7 +640,7 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
final int indexOfSession = mTermService.getSessions().indexOf(session);
|
final int indexOfSession = mTermService.getSessions().indexOf(session);
|
||||||
showToast(toToastTitle(session), false);
|
showToast(toToastTitle(session), false);
|
||||||
mListViewAdapter.notifyDataSetChanged();
|
mListViewAdapter.notifyDataSetChanged();
|
||||||
final ListView lv = ((ListView) findViewById(R.id.left_drawer_list));
|
final ListView lv = findViewById(R.id.left_drawer_list);
|
||||||
lv.setItemChecked(indexOfSession, true);
|
lv.setItemChecked(indexOfSession, true);
|
||||||
lv.smoothScrollToPosition(indexOfSession);
|
lv.smoothScrollToPosition(indexOfSession);
|
||||||
}
|
}
|
||||||
@@ -677,7 +654,6 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
menu.add(Menu.NONE, CONTEXTMENU_SHARE_TRANSCRIPT_ID, Menu.NONE, R.string.select_all_and_share);
|
menu.add(Menu.NONE, CONTEXTMENU_SHARE_TRANSCRIPT_ID, Menu.NONE, R.string.select_all_and_share);
|
||||||
menu.add(Menu.NONE, CONTEXTMENU_RESET_TERMINAL_ID, Menu.NONE, R.string.reset_terminal);
|
menu.add(Menu.NONE, CONTEXTMENU_RESET_TERMINAL_ID, Menu.NONE, R.string.reset_terminal);
|
||||||
menu.add(Menu.NONE, CONTEXTMENU_KILL_PROCESS_ID, Menu.NONE, getResources().getString(R.string.kill_process, getCurrentTermSession().getPid())).setEnabled(currentSession.isRunning());
|
menu.add(Menu.NONE, CONTEXTMENU_KILL_PROCESS_ID, Menu.NONE, getResources().getString(R.string.kill_process, getCurrentTermSession().getPid())).setEnabled(currentSession.isRunning());
|
||||||
menu.add(Menu.NONE, CONTEXTMENU_TOGGLE_FULLSCREEN_ID, Menu.NONE, R.string.toggle_fullscreen).setCheckable(true).setChecked(mSettings.isFullScreen());
|
|
||||||
menu.add(Menu.NONE, CONTEXTMENU_STYLING_ID, Menu.NONE, R.string.style_terminal);
|
menu.add(Menu.NONE, CONTEXTMENU_STYLING_ID, Menu.NONE, R.string.style_terminal);
|
||||||
menu.add(Menu.NONE, CONTEXTMENU_HELP_ID, Menu.NONE, R.string.help);
|
menu.add(Menu.NONE, CONTEXTMENU_HELP_ID, Menu.NONE, R.string.help);
|
||||||
}
|
}
|
||||||
@@ -811,11 +787,8 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
}
|
}
|
||||||
}).setNegativeButton(android.R.string.cancel, null).show();
|
}).setNegativeButton(android.R.string.cancel, null).show();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return true;
|
|
||||||
case CONTEXTMENU_TOGGLE_FULLSCREEN_ID:
|
|
||||||
toggleImmersive();
|
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
case CONTEXTMENU_HELP_ID:
|
case CONTEXTMENU_HELP_ID:
|
||||||
startActivity(new Intent(this, TermuxHelpActivity.class));
|
startActivity(new Intent(this, TermuxHelpActivity.class));
|
||||||
return true;
|
return true;
|
||||||
@@ -831,12 +804,6 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void toggleImmersive() {
|
|
||||||
boolean newValue = !mSettings.isFullScreen();
|
|
||||||
mSettings.setFullScreen(this, newValue);
|
|
||||||
mFullScreenHelper.setImmersive(newValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
void changeFontSize(boolean increase) {
|
void changeFontSize(boolean increase) {
|
||||||
mSettings.changeFontSize(this, increase);
|
mSettings.changeFontSize(this, increase);
|
||||||
mTerminalView.setTextSize(mSettings.getFontSize());
|
mTerminalView.setTextSize(mSettings.getFontSize());
|
||||||
@@ -855,9 +822,8 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
public TerminalSession getStoredCurrentSessionOrLast() {
|
public TerminalSession getStoredCurrentSessionOrLast() {
|
||||||
TerminalSession stored = TermuxPreferences.getCurrentSession(this);
|
TerminalSession stored = TermuxPreferences.getCurrentSession(this);
|
||||||
if (stored != null) return stored;
|
if (stored != null) return stored;
|
||||||
int numberOfSessions = mTermService.getSessions().size();
|
List<TerminalSession> sessions = mTermService.getSessions();
|
||||||
if (numberOfSessions == 0) return null;
|
return sessions.isEmpty() ? null : sessions.get(sessions.size() - 1);
|
||||||
return mTermService.getSessions().get(numberOfSessions - 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Show a toast and dismiss the last one if still visible. */
|
/** Show a toast and dismiss the last one if still visible. */
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ public final class TermuxHelpActivity extends Activity {
|
|||||||
mWebView.setWebViewClient(new WebViewClient() {
|
mWebView.setWebViewClient(new WebViewClient() {
|
||||||
@Override
|
@Override
|
||||||
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
||||||
if (url.startsWith("https://termux.com")) {
|
if (url.startsWith("https://wiki.termux.com")) {
|
||||||
// Inline help.
|
// Inline help.
|
||||||
setContentView(progressLayout);
|
setContentView(progressLayout);
|
||||||
return false;
|
return false;
|
||||||
@@ -60,7 +60,7 @@ public final class TermuxHelpActivity extends Activity {
|
|||||||
setContentView(mWebView);
|
setContentView(mWebView);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
mWebView.loadUrl("https://termux.com/help.html");
|
mWebView.loadUrl("https://wiki.termux.com/wiki/Main_Page");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ import java.util.zip.ZipInputStream;
|
|||||||
* (4) The architecture is determined and an appropriate bootstrap zip url is determined in {@link #determineZipUrl()}.
|
* (4) The architecture is determined and an appropriate bootstrap zip url is determined in {@link #determineZipUrl()}.
|
||||||
* <p/>
|
* <p/>
|
||||||
* (5) The zip, containing entries relative to the $PREFIX, is is downloaded and extracted by a zip input stream
|
* (5) The zip, containing entries relative to the $PREFIX, is is downloaded and extracted by a zip input stream
|
||||||
* continously encountering zip file entries:
|
* continuously encountering zip file entries:
|
||||||
* <p/>
|
* <p/>
|
||||||
* (5.1) If the zip entry encountered is SYMLINKS.txt, go through it and remember all symlinks to setup.
|
* (5.1) If the zip entry encountered is SYMLINKS.txt, go through it and remember all symlinks to setup.
|
||||||
* <p/>
|
* <p/>
|
||||||
|
|||||||
190
app/src/main/java/com/termux/app/TermuxOpenReceiver.java
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
package com.termux.app;
|
||||||
|
|
||||||
|
import android.content.ActivityNotFoundException;
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.ContentValues;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.database.MatrixCursor;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Environment;
|
||||||
|
import android.os.ParcelFileDescriptor;
|
||||||
|
import android.provider.MediaStore;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.webkit.MimeTypeMap;
|
||||||
|
|
||||||
|
import com.termux.terminal.EmulatorDebug;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class TermuxOpenReceiver extends BroadcastReceiver {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
final Uri data = intent.getData();
|
||||||
|
if (data == null) {
|
||||||
|
Log.e(EmulatorDebug.LOG_TAG, "termux-open: Called without intent data");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final String filePath = data.getPath();
|
||||||
|
final String contentTypeExtra = intent.getStringExtra("content-type");
|
||||||
|
final boolean useChooser = intent.getBooleanExtra("chooser", false);
|
||||||
|
final String intentAction = intent.getAction() == null ? Intent.ACTION_VIEW : intent.getAction();
|
||||||
|
switch (intentAction) {
|
||||||
|
case Intent.ACTION_SEND:
|
||||||
|
case Intent.ACTION_VIEW:
|
||||||
|
// Ok.
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Log.e(EmulatorDebug.LOG_TAG, "Invalid action '" + intentAction + "', using 'view'");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
final boolean isExternalUrl = data.getScheme() != null && !data.getScheme().equals("file");
|
||||||
|
if (isExternalUrl) {
|
||||||
|
Intent urlIntent = new Intent(intentAction, data);
|
||||||
|
if (intentAction.equals(Intent.ACTION_SEND)) {
|
||||||
|
urlIntent.putExtra(Intent.EXTRA_TEXT, data.toString());
|
||||||
|
urlIntent.setData(null);
|
||||||
|
} else if (contentTypeExtra != null) {
|
||||||
|
urlIntent.setDataAndType(data, contentTypeExtra);
|
||||||
|
}
|
||||||
|
urlIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
try {
|
||||||
|
context.startActivity(urlIntent);
|
||||||
|
} catch (ActivityNotFoundException e) {
|
||||||
|
Log.e(EmulatorDebug.LOG_TAG, "termux-open: No app handles the url " + data);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final File fileToShare = new File(filePath);
|
||||||
|
if (!(fileToShare.isFile() && fileToShare.canRead())) {
|
||||||
|
Log.e(EmulatorDebug.LOG_TAG, "termux-open: Not a readable file: '" + fileToShare.getAbsolutePath() + "'");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Intent sendIntent = new Intent();
|
||||||
|
sendIntent.setAction(intentAction);
|
||||||
|
sendIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||||
|
|
||||||
|
String contentTypeToUse;
|
||||||
|
if (contentTypeExtra == null) {
|
||||||
|
String fileName = fileToShare.getName();
|
||||||
|
int lastDotIndex = fileName.lastIndexOf('.');
|
||||||
|
String fileExtension = fileName.substring(lastDotIndex + 1, fileName.length());
|
||||||
|
MimeTypeMap mimeTypes = MimeTypeMap.getSingleton();
|
||||||
|
// Lower casing makes it work with e.g. "JPG":
|
||||||
|
contentTypeToUse = mimeTypes.getMimeTypeFromExtension(fileExtension.toLowerCase());
|
||||||
|
if (contentTypeToUse == null) contentTypeToUse = "application/octet-stream";
|
||||||
|
} else {
|
||||||
|
contentTypeToUse = contentTypeExtra;
|
||||||
|
}
|
||||||
|
|
||||||
|
Uri uriToShare = Uri.withAppendedPath(Uri.parse("content://com.termux.files/"), filePath);
|
||||||
|
|
||||||
|
if (Intent.ACTION_SEND.equals(intentAction)) {
|
||||||
|
sendIntent.putExtra(Intent.EXTRA_STREAM, uriToShare);
|
||||||
|
sendIntent.setType(contentTypeToUse);
|
||||||
|
} else {
|
||||||
|
sendIntent.setDataAndType(uriToShare, contentTypeToUse);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (useChooser) {
|
||||||
|
sendIntent = Intent.createChooser(sendIntent, null).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
context.startActivity(sendIntent);
|
||||||
|
} catch (ActivityNotFoundException e) {
|
||||||
|
Log.e(EmulatorDebug.LOG_TAG, "termux-open: No app handles the url " + data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ContentProvider extends android.content.ContentProvider {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCreate() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Cursor query(@NonNull Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
|
||||||
|
File file = new File(uri.getPath());
|
||||||
|
|
||||||
|
if (projection == null) {
|
||||||
|
projection = new String[]{
|
||||||
|
MediaStore.MediaColumns.DISPLAY_NAME,
|
||||||
|
MediaStore.MediaColumns.SIZE,
|
||||||
|
MediaStore.MediaColumns._ID
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Object[] row = new Object[projection.length];
|
||||||
|
for (int i = 0; i < projection.length; i++) {
|
||||||
|
String column = projection[i];
|
||||||
|
Object value;
|
||||||
|
switch (column) {
|
||||||
|
case MediaStore.MediaColumns.DISPLAY_NAME:
|
||||||
|
value = file.getName();
|
||||||
|
break;
|
||||||
|
case MediaStore.MediaColumns.SIZE:
|
||||||
|
value = (int) file.length();
|
||||||
|
break;
|
||||||
|
case MediaStore.MediaColumns._ID:
|
||||||
|
value = 1;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
value = null;
|
||||||
|
}
|
||||||
|
row[i] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
MatrixCursor cursor = new MatrixCursor(projection);
|
||||||
|
cursor.addRow(row);
|
||||||
|
return cursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getType(@NonNull Uri uri) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Uri insert(@NonNull Uri uri, ContentValues values) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int delete(@NonNull Uri uri, String selection, String[] selectionArgs) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int update(@NonNull Uri uri, ContentValues values, String selection, String[] selectionArgs) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) throws FileNotFoundException {
|
||||||
|
File file = new File(uri.getPath());
|
||||||
|
try {
|
||||||
|
String path = file.getCanonicalPath();
|
||||||
|
String storagePath = Environment.getExternalStorageDirectory().getCanonicalPath();
|
||||||
|
// See https://support.google.com/faqs/answer/7496913:
|
||||||
|
if (!(path.startsWith(TermuxService.FILES_PATH) || path.startsWith(storagePath))) {
|
||||||
|
throw new IllegalArgumentException("Invalid path: " + path);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IllegalArgumentException(e);
|
||||||
|
}
|
||||||
|
return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -32,13 +32,10 @@ final class TermuxPreferences {
|
|||||||
private final int MIN_FONTSIZE;
|
private final int MIN_FONTSIZE;
|
||||||
private static final int MAX_FONTSIZE = 256;
|
private static final int MAX_FONTSIZE = 256;
|
||||||
|
|
||||||
private static final String FULLSCREEN_KEY = "fullscreen";
|
|
||||||
private static final String SHOW_EXTRA_KEYS_KEY = "show_extra_keys";
|
private static final String SHOW_EXTRA_KEYS_KEY = "show_extra_keys";
|
||||||
private static final String FONTSIZE_KEY = "fontsize";
|
private static final String FONTSIZE_KEY = "fontsize";
|
||||||
private static final String CURRENT_SESSION_KEY = "current_session";
|
private static final String CURRENT_SESSION_KEY = "current_session";
|
||||||
private static final String SHOW_WELCOME_DIALOG_KEY = "intro_dialog";
|
|
||||||
|
|
||||||
private boolean mFullScreen;
|
|
||||||
private int mFontSize;
|
private int mFontSize;
|
||||||
|
|
||||||
@AsciiBellBehaviour
|
@AsciiBellBehaviour
|
||||||
@@ -57,7 +54,6 @@ final class TermuxPreferences {
|
|||||||
// to prevent invisible text due to zoom be mistake:
|
// to prevent invisible text due to zoom be mistake:
|
||||||
MIN_FONTSIZE = (int) (4f * dipInPixels);
|
MIN_FONTSIZE = (int) (4f * dipInPixels);
|
||||||
|
|
||||||
mFullScreen = prefs.getBoolean(FULLSCREEN_KEY, false);
|
|
||||||
mShowExtraKeys = prefs.getBoolean(SHOW_EXTRA_KEYS_KEY, false);
|
mShowExtraKeys = prefs.getBoolean(SHOW_EXTRA_KEYS_KEY, false);
|
||||||
|
|
||||||
// http://www.google.com/design/spec/style/typography.html#typography-line-height
|
// http://www.google.com/design/spec/style/typography.html#typography-line-height
|
||||||
@@ -73,15 +69,6 @@ final class TermuxPreferences {
|
|||||||
mFontSize = Math.max(MIN_FONTSIZE, Math.min(mFontSize, MAX_FONTSIZE));
|
mFontSize = Math.max(MIN_FONTSIZE, Math.min(mFontSize, MAX_FONTSIZE));
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isFullScreen() {
|
|
||||||
return mFullScreen;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setFullScreen(Context context, boolean newValue) {
|
|
||||||
mFullScreen = newValue;
|
|
||||||
PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean(FULLSCREEN_KEY, newValue).apply();
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean isShowExtraKeys() {
|
boolean isShowExtraKeys() {
|
||||||
return mShowExtraKeys;
|
return mShowExtraKeys;
|
||||||
}
|
}
|
||||||
@@ -117,14 +104,6 @@ final class TermuxPreferences {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isShowWelcomeDialog(Context context) {
|
|
||||||
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SHOW_WELCOME_DIALOG_KEY, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void disableWelcomeDialog(Context context) {
|
|
||||||
PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean(SHOW_WELCOME_DIALOG_KEY, false).apply();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void reloadFromProperties(Context context) {
|
public void reloadFromProperties(Context context) {
|
||||||
try {
|
try {
|
||||||
File propsFile = new File(TermuxService.HOME_PATH + "/.termux/termux.properties");
|
File propsFile = new File(TermuxService.HOME_PATH + "/.termux/termux.properties");
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.termux.app;
|
|||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.app.Notification;
|
import android.app.Notification;
|
||||||
|
import android.app.NotificationChannel;
|
||||||
import android.app.NotificationManager;
|
import android.app.NotificationManager;
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.app.Service;
|
import android.app.Service;
|
||||||
@@ -11,6 +12,7 @@ import android.content.res.Resources;
|
|||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.net.wifi.WifiManager;
|
import android.net.wifi.WifiManager;
|
||||||
import android.os.Binder;
|
import android.os.Binder;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.PowerManager;
|
import android.os.PowerManager;
|
||||||
@@ -23,7 +25,6 @@ import com.termux.terminal.TerminalSession;
|
|||||||
import com.termux.terminal.TerminalSession.SessionChangedCallback;
|
import com.termux.terminal.TerminalSession.SessionChangedCallback;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -41,6 +42,8 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
public final class TermuxService extends Service implements SessionChangedCallback {
|
public final class TermuxService extends Service implements SessionChangedCallback {
|
||||||
|
|
||||||
|
private static final String NOTIFICATION_CHANNEL_ID = "termux_notification_channel";
|
||||||
|
|
||||||
/** Note that this is a symlink on the Android M preview. */
|
/** Note that this is a symlink on the Android M preview. */
|
||||||
@SuppressLint("SdCardPath")
|
@SuppressLint("SdCardPath")
|
||||||
public static final String FILES_PATH = "/data/data/com.termux/files";
|
public static final String FILES_PATH = "/data/data/com.termux/files";
|
||||||
@@ -104,7 +107,8 @@ public final class TermuxService extends Service implements SessionChangedCallba
|
|||||||
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, EmulatorDebug.LOG_TAG);
|
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, EmulatorDebug.LOG_TAG);
|
||||||
mWakeLock.acquire();
|
mWakeLock.acquire();
|
||||||
|
|
||||||
WifiManager wm = (WifiManager) getSystemService(Context.WIFI_SERVICE);
|
// http://tools.android.com/tech-docs/lint-in-studio-2-3#TOC-WifiManager-Leak
|
||||||
|
WifiManager wm = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
|
||||||
mWifiLock = wm.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, EmulatorDebug.LOG_TAG);
|
mWifiLock = wm.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, EmulatorDebug.LOG_TAG);
|
||||||
mWifiLock.acquire();
|
mWifiLock.acquire();
|
||||||
|
|
||||||
@@ -164,11 +168,12 @@ public final class TermuxService extends Service implements SessionChangedCallba
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
|
setupNotificationChannel();
|
||||||
startForeground(NOTIFICATION_ID, buildNotification());
|
startForeground(NOTIFICATION_ID, buildNotification());
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Update the shown foreground service notification after making any changes that affect it. */
|
/** Update the shown foreground service notification after making any changes that affect it. */
|
||||||
private void updateNotification() {
|
void updateNotification() {
|
||||||
if (mWakeLock == null && mTerminalSessions.isEmpty() && mBackgroundTasks.isEmpty()) {
|
if (mWakeLock == null && mTerminalSessions.isEmpty() && mBackgroundTasks.isEmpty()) {
|
||||||
// Exit if we are updating after the user disabled all locks with no sessions or tasks running.
|
// Exit if we are updating after the user disabled all locks with no sessions or tasks running.
|
||||||
stopSelf();
|
stopSelf();
|
||||||
@@ -202,8 +207,8 @@ public final class TermuxService extends Service implements SessionChangedCallba
|
|||||||
builder.setOngoing(true);
|
builder.setOngoing(true);
|
||||||
|
|
||||||
// If holding a wake or wifi lock consider the notification of high priority since it's using power,
|
// If holding a wake or wifi lock consider the notification of high priority since it's using power,
|
||||||
// otherwise use a minimal priority since this is just a background service notification:
|
// otherwise use a low priority
|
||||||
builder.setPriority((wakeLockHeld) ? Notification.PRIORITY_HIGH : Notification.PRIORITY_MIN);
|
builder.setPriority((wakeLockHeld) ? Notification.PRIORITY_HIGH : Notification.PRIORITY_LOW);
|
||||||
|
|
||||||
// No need to show a timestamp:
|
// No need to show a timestamp:
|
||||||
builder.setShowWhen(false);
|
builder.setShowWhen(false);
|
||||||
@@ -211,6 +216,10 @@ public final class TermuxService extends Service implements SessionChangedCallba
|
|||||||
// Background color for small notification icon:
|
// Background color for small notification icon:
|
||||||
builder.setColor(0xFF000000);
|
builder.setColor(0xFF000000);
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
builder.setChannelId(NOTIFICATION_CHANNEL_ID);
|
||||||
|
}
|
||||||
|
|
||||||
Resources res = getResources();
|
Resources res = getResources();
|
||||||
Intent exitIntent = new Intent(this, TermuxService.class).setAction(ACTION_STOP_SERVICE);
|
Intent exitIntent = new Intent(this, TermuxService.class).setAction(ACTION_STOP_SERVICE);
|
||||||
builder.addAction(android.R.drawable.ic_delete, res.getString(R.string.notification_action_exit), PendingIntent.getService(this, 0, exitIntent, 0));
|
builder.addAction(android.R.drawable.ic_delete, res.getString(R.string.notification_action_exit), PendingIntent.getService(this, 0, exitIntent, 0));
|
||||||
@@ -235,7 +244,6 @@ public final class TermuxService extends Service implements SessionChangedCallba
|
|||||||
|
|
||||||
for (int i = 0; i < mTerminalSessions.size(); i++)
|
for (int i = 0; i < mTerminalSessions.size(); i++)
|
||||||
mTerminalSessions.get(i).finishIfRunning();
|
mTerminalSessions.get(i).finishIfRunning();
|
||||||
mTerminalSessions.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<TerminalSession> getSessions() {
|
public List<TerminalSession> getSessions() {
|
||||||
@@ -251,28 +259,11 @@ public final class TermuxService extends Service implements SessionChangedCallba
|
|||||||
boolean isLoginShell = false;
|
boolean isLoginShell = false;
|
||||||
|
|
||||||
if (executablePath == null) {
|
if (executablePath == null) {
|
||||||
File shell = new File(HOME_PATH, ".termux/shell");
|
for (String shellBinary : new String[]{"login", "bash", "zsh"}) {
|
||||||
if (shell.exists()) {
|
File shellFile = new File(PREFIX_PATH + "/bin/" + shellBinary);
|
||||||
try {
|
if (shellFile.canExecute()) {
|
||||||
File canonicalFile = shell.getCanonicalFile();
|
executablePath = shellFile.getAbsolutePath();
|
||||||
if (canonicalFile.isFile() && canonicalFile.canExecute()) {
|
break;
|
||||||
executablePath = canonicalFile.getName().equals("busybox") ? (PREFIX_PATH + "/bin/ash") : canonicalFile.getAbsolutePath();
|
|
||||||
} else {
|
|
||||||
Log.w(EmulatorDebug.LOG_TAG, "$HOME/.termux/shell points to non-executable shell: " + canonicalFile.getAbsolutePath());
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.e(EmulatorDebug.LOG_TAG, "Error checking $HOME/.termux/shell", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (executablePath == null) {
|
|
||||||
// Try bash, zsh and ash in that order:
|
|
||||||
for (String shellBinary : new String[]{"bash", "zsh", "ash"}) {
|
|
||||||
File shellFile = new File(PREFIX_PATH + "/bin/" + shellBinary);
|
|
||||||
if (shellFile.canExecute()) {
|
|
||||||
executablePath = shellFile.getAbsolutePath();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -352,4 +343,17 @@ public final class TermuxService extends Service implements SessionChangedCallba
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setupNotificationChannel() {
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return;
|
||||||
|
|
||||||
|
String channelName = "Termux";
|
||||||
|
String channelDescription = "Notifications from Termux";
|
||||||
|
int importance = NotificationManager.IMPORTANCE_LOW;
|
||||||
|
|
||||||
|
NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, channelName,importance);
|
||||||
|
channel.setDescription(channelDescription);
|
||||||
|
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||||
|
manager.createNotificationChannel(channel);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,18 +12,18 @@ import android.view.inputmethod.InputMethodManager;
|
|||||||
import com.termux.terminal.KeyHandler;
|
import com.termux.terminal.KeyHandler;
|
||||||
import com.termux.terminal.TerminalEmulator;
|
import com.termux.terminal.TerminalEmulator;
|
||||||
import com.termux.terminal.TerminalSession;
|
import com.termux.terminal.TerminalSession;
|
||||||
import com.termux.view.TerminalKeyListener;
|
import com.termux.view.TerminalViewClient;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public final class TermuxKeyListener implements TerminalKeyListener {
|
public final class TermuxViewClient implements TerminalViewClient {
|
||||||
|
|
||||||
final TermuxActivity mActivity;
|
final TermuxActivity mActivity;
|
||||||
|
|
||||||
/** Keeping track of the special keys acting as Ctrl and Fn for the soft keyboard and other hardware keys. */
|
/** Keeping track of the special keys acting as Ctrl and Fn for the soft keyboard and other hardware keys. */
|
||||||
boolean mVirtualControlKeyDown, mVirtualFnKeyDown;
|
boolean mVirtualControlKeyDown, mVirtualFnKeyDown;
|
||||||
|
|
||||||
public TermuxKeyListener(TermuxActivity activity) {
|
public TermuxViewClient(TermuxActivity activity) {
|
||||||
this.mActivity = activity;
|
this.mActivity = activity;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,8 +73,6 @@ public final class TermuxKeyListener implements TerminalKeyListener {
|
|||||||
mActivity.getDrawer().openDrawer(Gravity.LEFT);
|
mActivity.getDrawer().openDrawer(Gravity.LEFT);
|
||||||
} else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
|
} else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
|
||||||
mActivity.getDrawer().closeDrawers();
|
mActivity.getDrawer().closeDrawers();
|
||||||
} else if (unicodeChar == 'f'/* full screen */) {
|
|
||||||
mActivity.toggleImmersive();
|
|
||||||
} else if (unicodeChar == 'k'/* keyboard */) {
|
} else if (unicodeChar == 'k'/* keyboard */) {
|
||||||
InputMethodManager imm = (InputMethodManager) mActivity.getSystemService(Context.INPUT_METHOD_SERVICE);
|
InputMethodManager imm = (InputMethodManager) mActivity.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||||
imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
|
imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
|
||||||
@@ -256,6 +254,11 @@ public final class TermuxKeyListener implements TerminalKeyListener {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onLongPress(MotionEvent event) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/** Handle dedicated volume buttons as virtual keys if applicable. */
|
/** Handle dedicated volume buttons as virtual keys if applicable. */
|
||||||
private boolean handleVirtualKeys(int keyCode, KeyEvent event, boolean down) {
|
private boolean handleVirtualKeys(int keyCode, KeyEvent event, boolean down) {
|
||||||
InputDevice inputDevice = event.getDevice();
|
InputDevice inputDevice = event.getDevice();
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<translate android:duration="30"
|
|
||||||
android:fromXDelta="-1%"
|
|
||||||
android:repeatCount="1"
|
|
||||||
android:repeatMode="reverse"
|
|
||||||
android:toXDelta="1%"/>
|
|
||||||
</set>
|
|
||||||
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 3.3 KiB |
@@ -24,7 +24,7 @@
|
|||||||
|
|
||||||
<!-- Block cursor. -->
|
<!-- Block cursor. -->
|
||||||
<path android:fillColor="#000"
|
<path android:fillColor="#000"
|
||||||
android:pathData="M12,12
|
android:pathData="M14,14
|
||||||
l5,0
|
l5,0
|
||||||
l0,10
|
l0,10
|
||||||
l-5,0"
|
l-5,0"
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical"
|
||||||
|
android:fitsSystemWindows="true">
|
||||||
|
|
||||||
<android.support.v4.widget.DrawerLayout
|
<android.support.v4.widget.DrawerLayout
|
||||||
android:id="@+id/drawer_layout"
|
android:id="@+id/drawer_layout"
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 343 B After Width: | Height: | Size: 338 B |
|
Before Width: | Height: | Size: 204 B After Width: | Height: | Size: 203 B |
|
Before Width: | Height: | Size: 324 B After Width: | Height: | Size: 331 B |
|
Before Width: | Height: | Size: 510 B After Width: | Height: | Size: 505 B |
|
Before Width: | Height: | Size: 685 B After Width: | Height: | Size: 691 B |
@@ -7,14 +7,9 @@
|
|||||||
<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="toggle_fullscreen">Fullscreen</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="welcome_dialog_title">Welcome to Termux</string>
|
|
||||||
<string name="welcome_dialog_body">Long press and select <i>More…</i> to show a menu where <i>Help</i> is available.\n\nExecute <b>apt update</b> to update the packages list before installing packages.</string>
|
|
||||||
<string name="welcome_dialog_dont_show_again_button">Do not show again</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>
|
||||||
@@ -34,10 +29,6 @@
|
|||||||
<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="paste_text">Paste</string>
|
|
||||||
<string name="copy_text">Copy</string>
|
|
||||||
<string name="text_selection_more">More…</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>
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
<!-- NOTE: Cannot use "Light." since it hides the terminal scrollbar on the default black background. -->
|
<!-- NOTE: Cannot use "Light." since it hides the terminal scrollbar on the default black background. -->
|
||||||
<style name="Theme.Termux" parent="@android:style/Theme.Material.Light.NoActionBar">
|
<style name="Theme.Termux" parent="@android:style/Theme.Material.Light.NoActionBar">
|
||||||
<item name="android:statusBarColor">#000000</item>
|
<item name="android:statusBarColor">#000000</item>
|
||||||
|
<item name="android:colorPrimary">#FF000000</item>
|
||||||
<item name="android:windowBackground">@android:color/black</item>
|
<item name="android:windowBackground">@android:color/black</item>
|
||||||
|
|
||||||
<!-- Seen in buttons on left drawer: -->
|
<!-- Seen in buttons on left drawer: -->
|
||||||
@@ -13,6 +14,13 @@
|
|||||||
<!-- Avoid action mode toolbar pushing down terminal content when
|
<!-- Avoid action mode toolbar pushing down terminal content when
|
||||||
selecting text on pre-6.0 (non-floating toolbar). -->
|
selecting text on pre-6.0 (non-floating toolbar). -->
|
||||||
<item name="android:windowActionModeOverlay">true</item>
|
<item name="android:windowActionModeOverlay">true</item>
|
||||||
|
|
||||||
|
<item name="android:windowTranslucentStatus">true</item>
|
||||||
|
<item name="android:windowTranslucentNavigation">true</item>
|
||||||
|
|
||||||
|
<!-- https://developer.android.com/training/tv/start/start.html#transition-color -->
|
||||||
|
<item name="android:windowAllowReturnTransitionOverlap">true</item>
|
||||||
|
<item name="android:windowAllowEnterTransitionOverlap">true</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="TermuxAlertDialogStyle" parent="@android:style/Theme.Material.Light.Dialog.Alert">
|
<style name="TermuxAlertDialogStyle" parent="@android:style/Theme.Material.Light.Dialog.Alert">
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
|
<shortcuts xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<shortcut
|
<shortcut
|
||||||
android:shortcutId="new_session"
|
android:shortcutId="new_session"
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
android:icon="@drawable/ic_new_session"
|
android:icon="@drawable/ic_new_session"
|
||||||
android:shortcutShortLabel="@string/new_session">
|
android:shortcutShortLabel="@string/new_session"
|
||||||
|
tools:targetApi="n_mr1">
|
||||||
<intent
|
<intent
|
||||||
android:action="android.intent.action.RUN"
|
android:action="android.intent.action.RUN"
|
||||||
android:targetPackage="com.termux"
|
android:targetPackage="com.termux"
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ for DENSITY in mdpi hdpi xhdpi xxhdpi xxxhdpi; do
|
|||||||
PNG=$FOLDER/$FILE.png
|
PNG=$FOLDER/$FILE.png
|
||||||
|
|
||||||
# Update other apps:
|
# Update other apps:
|
||||||
for APP in tasker widget api; do
|
for APP in api boot styling tasker widget; do
|
||||||
APPDIR=../../termux-$APP
|
APPDIR=../../termux-$APP
|
||||||
if [ -d $APPDIR ]; then
|
if [ -d $APPDIR ]; then
|
||||||
APP_FOLDER=$APPDIR/app/src/main/res/mipmap-$DENSITY
|
APP_FOLDER=$APPDIR/app/src/main/res/mipmap-$DENSITY
|
||||||
|
|||||||
30
art/feature-graphic.svg
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
|
||||||
|
<!--
|
||||||
|
This is a feature graphic:
|
||||||
|
https://support.google.com/googleplay/android-developer/answer/1078870
|
||||||
|
- 1024px by 500px, no alpha
|
||||||
|
- Don't include any copy or important visual information near the borders of the asset,
|
||||||
|
specifically near the bottom third of the frame.
|
||||||
|
- Try to center align any logo/copy information in the vertical and horizontal center of the frame.
|
||||||
|
- If adding text, use large font sizes.
|
||||||
|
- Your graphic may be displayed alone without the app icon.
|
||||||
|
-->
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg"
|
||||||
|
version="1.1"
|
||||||
|
viewBox="0 0 1024 500">
|
||||||
|
|
||||||
|
<rect fill="#0" width="100%" height="100%" />
|
||||||
|
|
||||||
|
<text id="shell_prompt"
|
||||||
|
x="130"
|
||||||
|
y="330"
|
||||||
|
style="fill: #ffffff; font-size: 124px; font-family: Menlo;">
|
||||||
|
<!--
|
||||||
|
<tspan>$</tspan>
|
||||||
|
<tspan x="290">Termux</tspan>
|
||||||
|
<tspan x="734">█</tspan>
|
||||||
|
-->
|
||||||
|
<tspan>$ Termux █</tspan>
|
||||||
|
</text>
|
||||||
|
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 962 B |
5
art/generate-feature-graphic.sh
Executable file
@@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
echo "Generating feature graphics to ~/termux-icons/termux-feature-graphic.png..."
|
||||||
|
mkdir -p ~/termux-icons/
|
||||||
|
rsvg-convert feature-graphic.svg > ~/termux-icons/feature-graphic.png
|
||||||
9
art/generate-tv-banner.sh
Executable file
@@ -0,0 +1,9 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
echo "Generating feature graphics to ~/termux-icons/termux-feature-graphic.png..."
|
||||||
|
mkdir -p ~/termux-icons/
|
||||||
|
|
||||||
|
# The Android TV banner on google play (1280x720) has same aspect ratio
|
||||||
|
# as the banner in the app (320x180).
|
||||||
|
rsvg-convert -w 1280 -h 720 tv-banner.svg > ~/termux-icons/tv-banner.png
|
||||||
|
rsvg-convert -w 320 -h 180 tv-banner.svg > ../app/src/main/res/drawable/banner.png
|
||||||
@@ -4,20 +4,20 @@
|
|||||||
<path fill="#000"
|
<path fill="#000"
|
||||||
stroke="#BFCBCD"
|
stroke="#BFCBCD"
|
||||||
stroke-width="2"
|
stroke-width="2"
|
||||||
d="M7,4
|
d="M9,6
|
||||||
l34,0
|
l30,0
|
||||||
q3 0,3 3
|
q3 0,3 3
|
||||||
l0,34
|
l0,30
|
||||||
q0 3, -3 3
|
q0 3, -3 3
|
||||||
l-34,0
|
l-30,0
|
||||||
q-3 0, -3-3
|
q-3 0, -3-3
|
||||||
l0 -34
|
l0 -30
|
||||||
q0 -3, 3 -3"
|
q0 -3, 3 -3"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Block cursor. -->
|
<!-- Block cursor. -->
|
||||||
<path fill="#FFF"
|
<path fill="#FFF"
|
||||||
d="M12,12
|
d="M14,14
|
||||||
l5,0
|
l5,0
|
||||||
l0,10
|
l0,10
|
||||||
l-5,0"
|
l-5,0"
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 512 B After Width: | Height: | Size: 512 B |
23
art/tv-banner.svg
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
|
||||||
|
<!--
|
||||||
|
This is a tv banner graphic:
|
||||||
|
https://developer.android.com/design/tv/patterns.html#banner
|
||||||
|
- Size: 320 x 180 px, xhdpi resource in app
|
||||||
|
- Size: 1280 x 720 in google play.
|
||||||
|
- Text must be included in the image. If your app is available in more
|
||||||
|
than one language, you must provide versions of the banner image for each supported language.
|
||||||
|
-->
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg"
|
||||||
|
version="1.1"
|
||||||
|
viewBox="0 0 1280 720">
|
||||||
|
|
||||||
|
<rect fill="#0" width="100%" height="100%" />
|
||||||
|
|
||||||
|
<text id="shell_prompt"
|
||||||
|
x="200"
|
||||||
|
y="410"
|
||||||
|
style="fill: #ffffff; font-size: 210px; font-family: Menlo, Monospace;">
|
||||||
|
<tspan>Termux ▌</tspan>
|
||||||
|
</text>
|
||||||
|
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 736 B |
@@ -1,17 +1,19 @@
|
|||||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
repositories {
|
repositories {
|
||||||
jcenter()
|
jcenter()
|
||||||
|
google()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:2.2.3'
|
classpath 'com.android.tools.build:gradle:3.0.1'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
repositories {
|
repositories {
|
||||||
jcenter()
|
jcenter()
|
||||||
|
maven {
|
||||||
|
url "https://maven.google.com"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
5
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,5 @@
|
|||||||
#Sun Dec 04 17:26:05 CET 2016
|
distributionUrl=https\://services.gradle.org/distributions/gradle-4.3.1-bin.zip
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-3.2.1-bin.zip
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
|||||||
6
gradlew
vendored
@@ -33,11 +33,11 @@ DEFAULT_JVM_OPTS=""
|
|||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
MAX_FD="maximum"
|
MAX_FD="maximum"
|
||||||
|
|
||||||
warn ( ) {
|
warn () {
|
||||||
echo "$*"
|
echo "$*"
|
||||||
}
|
}
|
||||||
|
|
||||||
die ( ) {
|
die () {
|
||||||
echo
|
echo
|
||||||
echo "$*"
|
echo "$*"
|
||||||
echo
|
echo
|
||||||
@@ -155,7 +155,7 @@ if $cygwin ; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Escape application args
|
# Escape application args
|
||||||
save ( ) {
|
save () {
|
||||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||||
echo " "
|
echo " "
|
||||||
}
|
}
|
||||||
|
|||||||
77
scripts/bintray-publish.gradle
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
// Start https://raw.githubusercontent.com/nuuneoi/JCenter/master/installv1.gradle
|
||||||
|
group = publishedGroupId // Maven Group ID for the artifact
|
||||||
|
install {
|
||||||
|
repositories.mavenInstaller {
|
||||||
|
pom {
|
||||||
|
project {
|
||||||
|
packaging 'aar'
|
||||||
|
groupId publishedGroupId
|
||||||
|
artifactId artifact
|
||||||
|
|
||||||
|
name libraryName
|
||||||
|
description libraryDescription
|
||||||
|
url siteUrl
|
||||||
|
|
||||||
|
licenses {
|
||||||
|
license {
|
||||||
|
name 'GNU General Public License version 3'
|
||||||
|
url 'https://opensource.org/licenses/gpl-3.0.html'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
developers {
|
||||||
|
developer {
|
||||||
|
id 'fornwall'
|
||||||
|
name 'Fredrik Fornwall'
|
||||||
|
email 'fredrik@fornwall.net'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
scm {
|
||||||
|
connection gitUrl
|
||||||
|
developerConnection gitUrl
|
||||||
|
url siteUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// End https://raw.githubusercontent.com/nuuneoi/JCenter/master/installv1.gradle
|
||||||
|
|
||||||
|
// Start https://raw.githubusercontent.com/nuuneoi/JCenter/master/bintrayv1.gradle
|
||||||
|
apply plugin: 'com.jfrog.bintray'
|
||||||
|
|
||||||
|
version = libraryVersion
|
||||||
|
|
||||||
|
task sourcesJar(type: Jar) {
|
||||||
|
classifier = 'sources'
|
||||||
|
from android.sourceSets.main.java.srcDirs
|
||||||
|
}
|
||||||
|
|
||||||
|
artifacts {
|
||||||
|
archives sourcesJar
|
||||||
|
}
|
||||||
|
|
||||||
|
bintray {
|
||||||
|
user = System.getenv('BINTRAY_USER')
|
||||||
|
key = System.getenv('BINTRAY_API_KEY')
|
||||||
|
|
||||||
|
configurations = ['archives']
|
||||||
|
pkg {
|
||||||
|
repo = 'maven'
|
||||||
|
name = bintrayName
|
||||||
|
userOrg = 'termux'
|
||||||
|
desc = libraryDescription
|
||||||
|
websiteUrl = siteUrl
|
||||||
|
vcsUrl = gitUrl
|
||||||
|
licenses = ['GPL-3.0']
|
||||||
|
publish = true
|
||||||
|
publicDownloadNumbers = true
|
||||||
|
version {
|
||||||
|
desc = libraryDescription
|
||||||
|
gpg {
|
||||||
|
sign = false //Determines whether to GPG sign the files. The default is false
|
||||||
|
// passphrase = properties.getProperty("bintray.gpg.password")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1 +1 @@
|
|||||||
include ':app'
|
include ':app', ':terminal-emulator', ':terminal-view'
|
||||||
|
|||||||
62
terminal-emulator/build.gradle
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
plugins {
|
||||||
|
id "com.jfrog.bintray" version "1.7.3"
|
||||||
|
id "com.github.dcendents.android-maven" version "2.0"
|
||||||
|
}
|
||||||
|
|
||||||
|
apply plugin: 'com.android.library'
|
||||||
|
|
||||||
|
ext {
|
||||||
|
bintrayName = 'terminal-emulator'
|
||||||
|
publishedGroupId = 'com.termux'
|
||||||
|
libraryName = 'TerminalEmulator'
|
||||||
|
artifact = 'terminal-emulator'
|
||||||
|
libraryDescription = 'The terminal emulator used in Termux'
|
||||||
|
siteUrl = 'https://github.com/termux/termux'
|
||||||
|
gitUrl = 'https://github.com/termux/termux.git'
|
||||||
|
libraryVersion = '0.52'
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion 27
|
||||||
|
buildToolsVersion "27.0.2"
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
minSdkVersion 21
|
||||||
|
targetSdkVersion 27
|
||||||
|
|
||||||
|
externalNativeBuild {
|
||||||
|
ndkBuild {
|
||||||
|
cFlags "-std=c11", "-Wall", "-Wextra", "-Werror", "-Os", "-fno-stack-protector", "-Wl,--gc-sections"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ndk {
|
||||||
|
abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
minifyEnabled false
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
externalNativeBuild {
|
||||||
|
ndkBuild {
|
||||||
|
path "src/main/jni/Android.mk"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.withType(Test) {
|
||||||
|
testLogging {
|
||||||
|
events "started", "passed", "skipped", "failed"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
testImplementation 'junit:junit:4.12'
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: '../scripts/bintray-publish.gradle'
|
||||||
25
terminal-emulator/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Add project specific ProGuard rules here.
|
||||||
|
# By default, the flags in this file are appended to flags specified
|
||||||
|
# in /Users/fornwall/lib/android-sdk/tools/proguard/proguard-android.txt
|
||||||
|
# You can edit the include path and order by changing the proguardFiles
|
||||||
|
# directive in build.gradle.
|
||||||
|
#
|
||||||
|
# For more details, see
|
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|
||||||
|
# Add any project specific keep options here:
|
||||||
|
|
||||||
|
# If your project uses WebView with JS, uncomment the following
|
||||||
|
# and specify the fully qualified class name to the JavaScript interface
|
||||||
|
# class:
|
||||||
|
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||||
|
# public *;
|
||||||
|
#}
|
||||||
|
|
||||||
|
# Uncomment this to preserve the line number information for
|
||||||
|
# debugging stack traces.
|
||||||
|
#-keepattributes SourceFile,LineNumberTable
|
||||||
|
|
||||||
|
# If you keep the line number information, uncomment this to
|
||||||
|
# hide the original source file name.
|
||||||
|
#-renamesourcefileattribute SourceFile
|
||||||
2
terminal-emulator/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
<manifest package="com.termux.terminal">
|
||||||
|
</manifest>
|
||||||
@@ -3,7 +3,7 @@ package com.termux.terminal;
|
|||||||
/**
|
/**
|
||||||
* 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.
|
||||||
* <p/>
|
* <p>
|
||||||
* See {@link #externalToInternalRow(int)} for how to map from logical screen rows to array indices.
|
* See {@link #externalToInternalRow(int)} for how to map from logical screen rows to array indices.
|
||||||
*/
|
*/
|
||||||
public final class TerminalBuffer {
|
public final class TerminalBuffer {
|
||||||
@@ -92,22 +92,20 @@ public final class TerminalBuffer {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert a row value from the public external coordinate system to our internal private coordinate system.
|
* Convert a row value from the public external coordinate system to our internal private coordinate system.
|
||||||
* <p/>
|
*
|
||||||
* <ul>
|
|
||||||
* <li>External coordinate system: -mActiveTranscriptRows to mScreenRows-1, with the screen being 0..mScreenRows-1.
|
|
||||||
* <li>Internal coordinate system: the mScreenRows lines starting at mScreenFirstRow comprise the screen, while the
|
|
||||||
* mActiveTranscriptRows lines ending at mScreenFirstRow-1 form the transcript (as a circular buffer).
|
|
||||||
* </ul>
|
|
||||||
* <p/>
|
|
||||||
* External <---> Internal:
|
|
||||||
* <p/>
|
|
||||||
* <pre>
|
* <pre>
|
||||||
* [ ... ] [ ... ]
|
* - External coordinate system: -mActiveTranscriptRows to mScreenRows-1, with the screen being 0..mScreenRows-1.
|
||||||
* [ -mActiveTranscriptRows ] [ mScreenFirstRow - mActiveTranscriptRows ]
|
* - Internal coordinate system: the mScreenRows lines starting at mScreenFirstRow comprise the screen, while the
|
||||||
* [ ... ] [ ... ]
|
* mActiveTranscriptRows lines ending at mScreenFirstRow-1 form the transcript (as a circular buffer).
|
||||||
* [ 0 (visible screen starts here) ] <-----> [ mScreenFirstRow ]
|
*
|
||||||
* [ ... ] [ ... ]
|
* External ↔ Internal:
|
||||||
* [ mScreenRows-1 ] [ mScreenFirstRow + mScreenRows-1 ]
|
*
|
||||||
|
* [ ... ] [ ... ]
|
||||||
|
* [ -mActiveTranscriptRows ] [ mScreenFirstRow - mActiveTranscriptRows ]
|
||||||
|
* [ ... ] [ ... ]
|
||||||
|
* [ 0 (visible screen starts here) ] ↔ [ mScreenFirstRow ]
|
||||||
|
* [ ... ] [ ... ]
|
||||||
|
* [ mScreenRows-1 ] [ mScreenFirstRow + mScreenRows-1 ]
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
* @param externalRow a row in the external coordinate system.
|
* @param externalRow a row in the external coordinate system.
|
||||||
@@ -56,8 +56,6 @@ public final class TerminalEmulator {
|
|||||||
private static final int ESC_SELECT_LEFT_PAREN = 3;
|
private static final int ESC_SELECT_LEFT_PAREN = 3;
|
||||||
/** Escape processing: Have seen ESC and a character-set-select ) char */
|
/** Escape processing: Have seen ESC and a character-set-select ) char */
|
||||||
private static final int ESC_SELECT_RIGHT_PAREN = 4;
|
private static final int ESC_SELECT_RIGHT_PAREN = 4;
|
||||||
/** Escape processing: Have seen ESC and a character-set-select + char */
|
|
||||||
// private static final int ESC_SELECT_PLUS = 5;
|
|
||||||
/** Escape processing: "ESC [" or CSI (Control Sequence Introducer). */
|
/** Escape processing: "ESC [" or CSI (Control Sequence Introducer). */
|
||||||
private static final int ESC_CSI = 6;
|
private static final int ESC_CSI = 6;
|
||||||
/** Escape processing: ESC [ ? */
|
/** Escape processing: ESC [ ? */
|
||||||
@@ -225,6 +223,7 @@ public final class TerminalEmulator {
|
|||||||
|
|
||||||
private byte mUtf8ToFollow, mUtf8Index;
|
private byte mUtf8ToFollow, mUtf8Index;
|
||||||
private final byte[] mUtf8InputBuffer = new byte[4];
|
private final byte[] mUtf8InputBuffer = new byte[4];
|
||||||
|
private int mLastEmittedCodePoint = -1;
|
||||||
|
|
||||||
public final TerminalColors mColors = new TerminalColors();
|
public final TerminalColors mColors = new TerminalColors();
|
||||||
|
|
||||||
@@ -421,10 +420,11 @@ public final class TerminalEmulator {
|
|||||||
mUtf8Index = mUtf8ToFollow = 0;
|
mUtf8Index = mUtf8ToFollow = 0;
|
||||||
|
|
||||||
if (codePoint >= 0x80 && codePoint <= 0x9F) {
|
if (codePoint >= 0x80 && codePoint <= 0x9F) {
|
||||||
// Sequence decoded to a C1 control character which is the same as escape followed by
|
// Sequence decoded to a C1 control character which we ignore. They are
|
||||||
// ((code & 0x7F) + 0x40).
|
// not used nowadays and increases the risk of messing up the terminal state
|
||||||
processCodePoint(/* escape (hexadecimal=0x1B, octal=033): */27);
|
// on binary input. XTerm does not allow them in utf-8:
|
||||||
processCodePoint((codePoint & 0x7F) + 0x40);
|
// "It is not possible to use a C1 control obtained from decoding the
|
||||||
|
// UTF-8 text" - http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
|
||||||
} else {
|
} else {
|
||||||
switch (Character.getType(codePoint)) {
|
switch (Character.getType(codePoint)) {
|
||||||
case Character.UNASSIGNED:
|
case Character.UNASSIGNED:
|
||||||
@@ -634,6 +634,7 @@ public final class TerminalEmulator {
|
|||||||
int bottom = Math.min(getArg(2, mRows, true) + 1, effectiveBottomMargin - 1) + effectiveTopMargin;
|
int bottom = Math.min(getArg(2, mRows, true) + 1, effectiveBottomMargin - 1) + effectiveTopMargin;
|
||||||
int right = Math.min(getArg(3, mColumns, true) + 1, effectiveRightMargin - 1) + effectiveLeftMargin;
|
int right = Math.min(getArg(3, mColumns, true) + 1, effectiveRightMargin - 1) + effectiveLeftMargin;
|
||||||
if (mArgIndex >= 4) {
|
if (mArgIndex >= 4) {
|
||||||
|
if (mArgIndex >= mArgs.length) mArgIndex = mArgs.length - 1;
|
||||||
for (int i = 4; i <= mArgIndex; i++) {
|
for (int i = 4; i <= mArgIndex; i++) {
|
||||||
int bits = 0;
|
int bits = 0;
|
||||||
boolean setOrClear = true; // True if setting, false if clearing.
|
boolean setOrClear = true; // True if setting, false if clearing.
|
||||||
@@ -967,6 +968,7 @@ public final class TerminalEmulator {
|
|||||||
break;
|
break;
|
||||||
case 'h':
|
case 'h':
|
||||||
case 'l':
|
case 'l':
|
||||||
|
if (mArgIndex >= mArgs.length) mArgIndex = mArgs.length - 1;
|
||||||
for (int i = 0; i <= mArgIndex; i++)
|
for (int i = 0; i <= mArgIndex; i++)
|
||||||
doDecSetOrReset(b == 'h', mArgs[i]);
|
doDecSetOrReset(b == 'h', mArgs[i]);
|
||||||
break;
|
break;
|
||||||
@@ -983,6 +985,7 @@ public final class TerminalEmulator {
|
|||||||
break;
|
break;
|
||||||
case 'r':
|
case 'r':
|
||||||
case 's':
|
case 's':
|
||||||
|
if (mArgIndex >= mArgs.length) mArgIndex = mArgs.length - 1;
|
||||||
for (int i = 0; i <= mArgIndex; i++) {
|
for (int i = 0; i <= mArgIndex; i++) {
|
||||||
int externalBit = mArgs[i];
|
int externalBit = mArgs[i];
|
||||||
int internalBit = mapDecSetBitToInternalBit(externalBit);
|
int internalBit = mapDecSetBitToInternalBit(externalBit);
|
||||||
@@ -1189,12 +1192,20 @@ public final class TerminalEmulator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void doLinefeed() {
|
private void doLinefeed() {
|
||||||
|
boolean belowScrollingRegion = mCursorRow >= mBottomMargin;
|
||||||
int newCursorRow = mCursorRow + 1;
|
int newCursorRow = mCursorRow + 1;
|
||||||
if (newCursorRow >= mBottomMargin) {
|
if (belowScrollingRegion) {
|
||||||
scrollDownOneLine();
|
// Move down (but not scroll) as long as we are above the last row.
|
||||||
newCursorRow = mBottomMargin - 1;
|
if (mCursorRow != mRows - 1) {
|
||||||
|
setCursorRow(newCursorRow);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (newCursorRow == mBottomMargin) {
|
||||||
|
scrollDownOneLine();
|
||||||
|
newCursorRow = mBottomMargin - 1;
|
||||||
|
}
|
||||||
|
setCursorRow(newCursorRow);
|
||||||
}
|
}
|
||||||
setCursorRow(newCursorRow);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void continueSequence(int state) {
|
private void continueSequence(int state) {
|
||||||
@@ -1309,6 +1320,8 @@ public final class TerminalEmulator {
|
|||||||
state.mSavedCursorRow = mCursorRow;
|
state.mSavedCursorRow = mCursorRow;
|
||||||
state.mSavedCursorCol = mCursorCol;
|
state.mSavedCursorCol = mCursorCol;
|
||||||
state.mSavedEffect = mEffect;
|
state.mSavedEffect = mEffect;
|
||||||
|
state.mSavedForeColor = mForeColor;
|
||||||
|
state.mSavedBackColor = mBackColor;
|
||||||
state.mSavedDecFlags = mCurrentDecSetFlags;
|
state.mSavedDecFlags = mCurrentDecSetFlags;
|
||||||
state.mUseLineDrawingG0 = mUseLineDrawingG0;
|
state.mUseLineDrawingG0 = mUseLineDrawingG0;
|
||||||
state.mUseLineDrawingG1 = mUseLineDrawingG1;
|
state.mUseLineDrawingG1 = mUseLineDrawingG1;
|
||||||
@@ -1320,6 +1333,8 @@ public final class TerminalEmulator {
|
|||||||
SavedScreenState state = (mScreen == mMainBuffer) ? mSavedStateMain : mSavedStateAlt;
|
SavedScreenState state = (mScreen == mMainBuffer) ? mSavedStateMain : mSavedStateAlt;
|
||||||
setCursorRowCol(state.mSavedCursorRow, state.mSavedCursorCol);
|
setCursorRowCol(state.mSavedCursorRow, state.mSavedCursorCol);
|
||||||
mEffect = state.mSavedEffect;
|
mEffect = state.mSavedEffect;
|
||||||
|
mForeColor = state.mSavedForeColor;
|
||||||
|
mBackColor = state.mSavedBackColor;
|
||||||
int mask = (DECSET_BIT_AUTOWRAP | DECSET_BIT_ORIGIN_MODE);
|
int mask = (DECSET_BIT_AUTOWRAP | DECSET_BIT_ORIGIN_MODE);
|
||||||
mCurrentDecSetFlags = (mCurrentDecSetFlags & ~mask) | (state.mSavedDecFlags & mask);
|
mCurrentDecSetFlags = (mCurrentDecSetFlags & ~mask) | (state.mSavedDecFlags & mask);
|
||||||
mUseLineDrawingG0 = state.mUseLineDrawingG0;
|
mUseLineDrawingG0 = state.mUseLineDrawingG0;
|
||||||
@@ -1503,6 +1518,11 @@ public final class TerminalEmulator {
|
|||||||
case '`': // Horizontal position absolute (HPA - http://www.vt100.net/docs/vt510-rm/HPA).
|
case '`': // Horizontal position absolute (HPA - http://www.vt100.net/docs/vt510-rm/HPA).
|
||||||
setCursorColRespectingOriginMode(getArg0(1) - 1);
|
setCursorColRespectingOriginMode(getArg0(1) - 1);
|
||||||
break;
|
break;
|
||||||
|
case 'b': // Repeat the preceding graphic character Ps times (REP).
|
||||||
|
if (mLastEmittedCodePoint == -1) break;
|
||||||
|
final int numRepeat = getArg0(1);
|
||||||
|
for (int i = 0; i < numRepeat; i++) emitCodePoint(mLastEmittedCodePoint);
|
||||||
|
break;
|
||||||
case 'c': // Primary Device Attributes (http://www.vt100.net/docs/vt510-rm/DA1) if argument is missing or zero.
|
case 'c': // Primary Device Attributes (http://www.vt100.net/docs/vt510-rm/DA1) if argument is missing or zero.
|
||||||
// The important part that may still be used by some (tmux stores this value but does not currently use it)
|
// The important part that may still be used by some (tmux stores this value but does not currently use it)
|
||||||
// is the first response parameter identifying the terminal service class, where we send 64 for "vt420".
|
// is the first response parameter identifying the terminal service class, where we send 64 for "vt420".
|
||||||
@@ -1559,7 +1579,7 @@ public final class TerminalEmulator {
|
|||||||
break;
|
break;
|
||||||
case 'r': // "CSI${top};${bottom}r" - set top and bottom Margins (DECSTBM).
|
case 'r': // "CSI${top};${bottom}r" - set top and bottom Margins (DECSTBM).
|
||||||
{
|
{
|
||||||
// http://www.vt100.net/docs/vt510-rm/DECSTBM
|
// https://vt100.net/docs/vt510-rm/DECSTBM.html
|
||||||
// The top margin defaults to 1, the bottom margin defaults to mRows.
|
// The top margin defaults to 1, the bottom margin defaults to mRows.
|
||||||
// The escape sequence numbers top 1..23, but we number top 0..22.
|
// The escape sequence numbers top 1..23, but we number top 0..22.
|
||||||
// The escape sequence numbers bottom 2..24, and so do we (because we use a zero based numbering
|
// The escape sequence numbers bottom 2..24, and so do we (because we use a zero based numbering
|
||||||
@@ -1568,6 +1588,7 @@ public final class TerminalEmulator {
|
|||||||
// Also require that top + 2 <= bottom.
|
// Also require that top + 2 <= bottom.
|
||||||
mTopMargin = Math.max(0, Math.min(getArg0(1) - 1, mRows - 2));
|
mTopMargin = Math.max(0, Math.min(getArg0(1) - 1, mRows - 2));
|
||||||
mBottomMargin = Math.max(mTopMargin + 2, Math.min(getArg1(mRows), mRows));
|
mBottomMargin = Math.max(mTopMargin + 2, Math.min(getArg1(mRows), mRows));
|
||||||
|
|
||||||
// DECSTBM moves the cursor to column 1, line 1 of the page respecting origin mode.
|
// DECSTBM moves the cursor to column 1, line 1 of the page respecting origin mode.
|
||||||
setCursorPosition(0, 0);
|
setCursorPosition(0, 0);
|
||||||
}
|
}
|
||||||
@@ -1641,6 +1662,7 @@ public final class TerminalEmulator {
|
|||||||
|
|
||||||
/** Select Graphic Rendition (SGR) - see http://en.wikipedia.org/wiki/ANSI_escape_code#graphics. */
|
/** Select Graphic Rendition (SGR) - see http://en.wikipedia.org/wiki/ANSI_escape_code#graphics. */
|
||||||
private void selectGraphicRendition() {
|
private void selectGraphicRendition() {
|
||||||
|
if (mArgIndex >= mArgs.length) mArgIndex = mArgs.length - 1;
|
||||||
for (int i = 0; i <= mArgIndex; i++) {
|
for (int i = 0; i <= mArgIndex; i++) {
|
||||||
int code = mArgs[i];
|
int code = mArgs[i];
|
||||||
if (code < 0) {
|
if (code < 0) {
|
||||||
@@ -2051,6 +2073,7 @@ public final class TerminalEmulator {
|
|||||||
buf.append(", escapeState=");
|
buf.append(", escapeState=");
|
||||||
buf.append(mEscapeState);
|
buf.append(mEscapeState);
|
||||||
boolean firstArg = true;
|
boolean firstArg = true;
|
||||||
|
if (mArgIndex >= mArgs.length) mArgIndex = mArgs.length - 1;
|
||||||
for (int i = 0; i <= mArgIndex; i++) {
|
for (int i = 0; i <= mArgIndex; i++) {
|
||||||
int value = mArgs[i];
|
int value = mArgs[i];
|
||||||
if (value >= 0) {
|
if (value >= 0) {
|
||||||
@@ -2083,6 +2106,7 @@ public final class TerminalEmulator {
|
|||||||
* @param codePoint The code point of the character to display
|
* @param codePoint The code point of the character to display
|
||||||
*/
|
*/
|
||||||
private void emitCodePoint(int codePoint) {
|
private void emitCodePoint(int codePoint) {
|
||||||
|
mLastEmittedCodePoint = codePoint;
|
||||||
if (mUseLineDrawingUsesG0 ? mUseLineDrawingG0 : mUseLineDrawingG1) {
|
if (mUseLineDrawingUsesG0 ? mUseLineDrawingG0 : mUseLineDrawingG1) {
|
||||||
// http://www.vt100.net/docs/vt102-ug/table5-15.html.
|
// http://www.vt100.net/docs/vt102-ug/table5-15.html.
|
||||||
switch (codePoint) {
|
switch (codePoint) {
|
||||||
@@ -2265,8 +2289,8 @@ public final class TerminalEmulator {
|
|||||||
mBottomMargin = mRows;
|
mBottomMargin = mRows;
|
||||||
mRightMargin = mColumns;
|
mRightMargin = mColumns;
|
||||||
mAboutToAutoWrap = false;
|
mAboutToAutoWrap = false;
|
||||||
mForeColor = TextStyle.COLOR_INDEX_FOREGROUND;
|
mForeColor = mSavedStateMain.mSavedForeColor = mSavedStateAlt.mSavedForeColor = TextStyle.COLOR_INDEX_FOREGROUND;
|
||||||
mBackColor = TextStyle.COLOR_INDEX_BACKGROUND;
|
mBackColor = mSavedStateMain.mSavedBackColor = mSavedStateAlt.mSavedBackColor = TextStyle.COLOR_INDEX_BACKGROUND;
|
||||||
setDefaultTabStops();
|
setDefaultTabStops();
|
||||||
|
|
||||||
mUseLineDrawingG0 = mUseLineDrawingG1 = false;
|
mUseLineDrawingG0 = mUseLineDrawingG1 = false;
|
||||||
@@ -2320,7 +2344,7 @@ public final class TerminalEmulator {
|
|||||||
static final class SavedScreenState {
|
static final class SavedScreenState {
|
||||||
/** Saved state of the cursor position, Used to implement the save/restore cursor position escape sequences. */
|
/** Saved state of the cursor position, Used to implement the save/restore cursor position escape sequences. */
|
||||||
int mSavedCursorRow, mSavedCursorCol;
|
int mSavedCursorRow, mSavedCursorCol;
|
||||||
int mSavedEffect;
|
int mSavedEffect, mSavedForeColor, mSavedBackColor;
|
||||||
int mSavedDecFlags;
|
int mSavedDecFlags;
|
||||||
boolean mUseLineDrawingG0, mUseLineDrawingG1, mUseLineDrawingUsesG0 = true;
|
boolean mUseLineDrawingG0, mUseLineDrawingG1, mUseLineDrawingUsesG0 = true;
|
||||||
}
|
}
|
||||||
@@ -4,7 +4,7 @@ import java.util.Arrays;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* A row in a terminal, composed of a fixed number of cells.
|
* A row in a terminal, composed of a fixed number of cells.
|
||||||
* <p/>
|
* <p>
|
||||||
* The text in the row is stored in a char[] array, {@link #mText}, for quick access during rendering.
|
* The text in the row is stored in a char[] array, {@link #mText}, for quick access during rendering.
|
||||||
*/
|
*/
|
||||||
public final class TerminalRow {
|
public final class TerminalRow {
|
||||||
@@ -19,13 +19,13 @@ import java.util.UUID;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* A terminal session, consisting of a process coupled to a terminal interface.
|
* A terminal session, consisting of a process coupled to a terminal interface.
|
||||||
* <p/>
|
* <p>
|
||||||
* The subprocess will be executed by the constructor, and when the size is made known by a call to
|
* The subprocess will be executed by the constructor, and when the size is made known by a call to
|
||||||
* {@link #updateSize(int, int)} terminal emulation will begin and threads will be spawned to handle the subprocess I/O.
|
* {@link #updateSize(int, int)} terminal emulation will begin and threads will be spawned to handle the subprocess I/O.
|
||||||
* All terminal emulation and callback methods will be performed on the main thread.
|
* All terminal emulation and callback methods will be performed on the main thread.
|
||||||
* <p/>
|
* <p>
|
||||||
* The child process may be exited forcefully by using the {@link #finishIfRunning()} method.
|
* The child process may be exited forcefully by using the {@link #finishIfRunning()} method.
|
||||||
* <p/>
|
* <p>
|
||||||
* NOTE: The terminal session may outlive the EmulatorView, so be careful with callbacks!
|
* NOTE: The terminal session may outlive the EmulatorView, so be careful with callbacks!
|
||||||
*/
|
*/
|
||||||
public final class TerminalSession extends TerminalOutput {
|
public final class TerminalSession extends TerminalOutput {
|
||||||
@@ -123,12 +123,12 @@ public final class TerminalSession extends TerminalOutput {
|
|||||||
String exitDescription = "\r\n[Process completed";
|
String exitDescription = "\r\n[Process completed";
|
||||||
if (exitCode > 0) {
|
if (exitCode > 0) {
|
||||||
// Non-zero process exit.
|
// Non-zero process exit.
|
||||||
exitDescription += " with code " + exitCode;
|
exitDescription += " (code " + exitCode + ")";
|
||||||
} else if (exitCode < 0) {
|
} else if (exitCode < 0) {
|
||||||
// Negated signal.
|
// Negated signal.
|
||||||
exitDescription += " with signal " + (-exitCode);
|
exitDescription += " (signal " + (-exitCode) + ")";
|
||||||
}
|
}
|
||||||
exitDescription += " - press Enter to close]";
|
exitDescription += " - press Enter]";
|
||||||
|
|
||||||
byte[] bytesToWrite = exitDescription.getBytes(StandardCharsets.UTF_8);
|
byte[] bytesToWrite = exitDescription.getBytes(StandardCharsets.UTF_8);
|
||||||
mEmulator.append(bytesToWrite, bytesToWrite.length);
|
mEmulator.append(bytesToWrite, bytesToWrite.length);
|
||||||
@@ -1,10 +1,13 @@
|
|||||||
package com.termux.terminal;
|
package com.termux.terminal;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* <p>
|
||||||
* Encodes effects, foreground and background colors into a 64 bit long, which are stored for each cell in a terminal
|
* Encodes effects, foreground and background colors into a 64 bit long, which are stored for each cell in a terminal
|
||||||
* row in {@link TerminalRow#mStyle}.
|
* row in {@link TerminalRow#mStyle}.
|
||||||
* <p/>
|
* </p>
|
||||||
|
* <p>
|
||||||
* The bit layout is:
|
* The bit layout is:
|
||||||
|
* </p>
|
||||||
* - 16 flags (11 currently used).
|
* - 16 flags (11 currently used).
|
||||||
* - 24 for foreground color (only 9 first bits if a color index).
|
* - 24 for foreground color (only 9 first bits if a color index).
|
||||||
* - 24 for background color (only 9 first bits if a color index).
|
* - 24 for background color (only 9 first bits if a color index).
|
||||||
@@ -20,9 +23,10 @@ public final class TextStyle {
|
|||||||
public final static int CHARACTER_ATTRIBUTE_STRIKETHROUGH = 1 << 6;
|
public final static int CHARACTER_ATTRIBUTE_STRIKETHROUGH = 1 << 6;
|
||||||
/**
|
/**
|
||||||
* The selective erase control functions (DECSED and DECSEL) can only erase characters defined as erasable.
|
* The selective erase control functions (DECSED and DECSEL) can only erase characters defined as erasable.
|
||||||
* <p/>
|
* <p>
|
||||||
* This bit is set if DECSCA (Select Character Protection Attribute) has been used to define the characters that
|
* This bit is set if DECSCA (Select Character Protection Attribute) has been used to define the characters that
|
||||||
* come after it as erasable from the screen.
|
* come after it as erasable from the screen.
|
||||||
|
* </p>
|
||||||
*/
|
*/
|
||||||
public final static int CHARACTER_ATTRIBUTE_PROTECTED = 1 << 7;
|
public final static int CHARACTER_ATTRIBUTE_PROTECTED = 1 << 7;
|
||||||
/** Dim colors. Also known as faint or half intensity. */
|
/** Dim colors. Also known as faint or half intensity. */
|
||||||
@@ -29,4 +29,21 @@ public class ControlSequenceIntroducerTest extends TerminalTestCase {
|
|||||||
withTerminalSized(13, 2).enterString("abcdefghijkl\b\b\b\b\b\033[20X").assertLinesAre("abcdefg ", " ");
|
withTerminalSized(13, 2).enterString("abcdefghijkl\b\b\b\b\b\033[20X").assertLinesAre("abcdefg ", " ");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** CSI Pm m Set SGR parameter(s) from semicolon-separated list Pm. */
|
||||||
|
public void testCsiSGRParameters() {
|
||||||
|
// Set more parameters (19) than supported (16). Additional parameters should be silently consumed.
|
||||||
|
withTerminalSized(3, 2).enterString("\033[0;38;2;255;255;255;48;2;0;0;0;1;2;3;4;5;7;8;9mabc").assertLinesAre("abc", " ");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** CSI Ps b Repeat the preceding graphic character Ps times (REP). */
|
||||||
|
public void testRepeat() {
|
||||||
|
withTerminalSized(3, 2).enterString("a\033[b").assertLinesAre("aa ", " ");
|
||||||
|
withTerminalSized(3, 2).enterString("a\033[2b").assertLinesAre("aaa", " ");
|
||||||
|
// When no char has been output we ignore REP:
|
||||||
|
withTerminalSized(3, 2).enterString("\033[b").assertLinesAre(" ", " ");
|
||||||
|
// This shows that REP outputs the last emitted code point and not the one relative to the
|
||||||
|
// current cursor position:
|
||||||
|
withTerminalSized(5, 2).enterString("abcde\033[2G\033[2b\n").assertLinesAre("aeede", " ");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -227,4 +227,44 @@ public class CursorAndScreenTest extends TerminalTestCase {
|
|||||||
withTerminalSized(3, 3).enterString("\b\b\b\bhi").assertLinesAre("hi ", " ", " ");
|
withTerminalSized(3, 3).enterString("\b\b\b\bhi").assertLinesAre("hi ", " ", " ");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testCursorSaveRestoreLocation() {
|
||||||
|
// DEC save/restore
|
||||||
|
withTerminalSized(4, 2).enterString("t\0337est\r\nme\0338ry ").assertLinesAre("try ", "me ");
|
||||||
|
// ANSI.SYS save/restore
|
||||||
|
withTerminalSized(4, 2).enterString("t\033[sest\r\nme\033[ury ").assertLinesAre("try ", "me ");
|
||||||
|
// Alternate screen enter/exit
|
||||||
|
withTerminalSized(4, 2).enterString("t\033[?1049h\033[Hest\r\nme").assertLinesAre("est ", "me ").enterString("\033[?1049lry").assertLinesAre("try ", " ");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testCursorSaveRestoreTextStyle() {
|
||||||
|
long s;
|
||||||
|
|
||||||
|
// DEC save/restore
|
||||||
|
withTerminalSized(4, 2).enterString("\033[31;42;4m..\0337\033[36;47;24m\0338..");
|
||||||
|
s = getStyleAt(0, 3);
|
||||||
|
Assert.assertEquals(1, TextStyle.decodeForeColor(s));
|
||||||
|
Assert.assertEquals(2, TextStyle.decodeBackColor(s));
|
||||||
|
Assert.assertEquals(TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE, TextStyle.decodeEffect(s));
|
||||||
|
|
||||||
|
// ANSI.SYS save/restore
|
||||||
|
withTerminalSized(4, 2).enterString("\033[31;42;4m..\033[s\033[36;47;24m\033[u..");
|
||||||
|
s = getStyleAt(0, 3);
|
||||||
|
Assert.assertEquals(1, TextStyle.decodeForeColor(s));
|
||||||
|
Assert.assertEquals(2, TextStyle.decodeBackColor(s));
|
||||||
|
Assert.assertEquals(TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE, TextStyle.decodeEffect(s));
|
||||||
|
|
||||||
|
// Alternate screen enter/exit
|
||||||
|
withTerminalSized(4, 2);
|
||||||
|
enterString("\033[31;42;4m..\033[?1049h\033[H\033[36;47;24m.");
|
||||||
|
s = getStyleAt(0, 0);
|
||||||
|
Assert.assertEquals(6, TextStyle.decodeForeColor(s));
|
||||||
|
Assert.assertEquals(7, TextStyle.decodeBackColor(s));
|
||||||
|
Assert.assertEquals(0, TextStyle.decodeEffect(s));
|
||||||
|
enterString("\033[?1049l..");
|
||||||
|
s = getStyleAt(0, 3);
|
||||||
|
Assert.assertEquals(1, TextStyle.decodeForeColor(s));
|
||||||
|
Assert.assertEquals(2, TextStyle.decodeBackColor(s));
|
||||||
|
Assert.assertEquals(TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE, TextStyle.decodeEffect(s));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -98,4 +98,13 @@ public class ScrollRegionTest extends TerminalTestCase {
|
|||||||
withTerminalSized(2, 5).enterString("1\r\n2\r\n3\r\n4\r\n5").assertLinesAre("1 ", "2 ", "3 ", "4 ", "5 ");
|
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 ");
|
enterString("\033[3r").enterString("\033[2T").assertLinesAre("1 ", "2 ", " ", " ", "3 ");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testScrollDownBelowScrollRegion() {
|
||||||
|
withTerminalSized(2, 5).enterString("1\r\n2\r\n3\r\n4\r\n5").assertLinesAre("1 ", "2 ", "3 ", "4 ", "5 ");
|
||||||
|
enterString("\033[1;3r"); // DECSTBM margins.
|
||||||
|
enterString("\033[4;1H"); // Place cursor just below bottom margin.
|
||||||
|
enterString("QQ\r\nRR\r\n\r\n\r\nYY");
|
||||||
|
assertLinesAre("1 ", "2 ", "3 ", "QQ", "YY");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -27,6 +27,7 @@ public class TerminalRowTest extends TestCase {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void setUp() throws Exception {
|
protected void setUp() throws Exception {
|
||||||
|
super.setUp();
|
||||||
row = new TerminalRow(COLUMNS, TextStyle.NORMAL);
|
row = new TerminalRow(COLUMNS, TextStyle.NORMAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,6 +103,7 @@ public abstract class TerminalTestCase extends TestCase {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void setUp() throws Exception {
|
protected void setUp() throws Exception {
|
||||||
|
super.setUp();
|
||||||
mOutput = new MockTerminalOutput();
|
mOutput = new MockTerminalOutput();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -19,6 +19,7 @@ public class WcWidthTest extends TestCase {
|
|||||||
assertWidthIs(1, 'å');
|
assertWidthIs(1, 'å');
|
||||||
assertWidthIs(1, 'ä');
|
assertWidthIs(1, 'ä');
|
||||||
assertWidthIs(1, 'ö');
|
assertWidthIs(1, 'ö');
|
||||||
|
assertWidthIs(1, 0x23F2);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testSomeWide() {
|
public void testSomeWide() {
|
||||||
@@ -45,6 +46,7 @@ public class WcWidthTest extends TestCase {
|
|||||||
public void testCombining() {
|
public void testCombining() {
|
||||||
assertWidthIs(0, 0x0302);
|
assertWidthIs(0, 0x0302);
|
||||||
assertWidthIs(0, 0x0308);
|
assertWidthIs(0, 0x0308);
|
||||||
|
assertWidthIs(0, 0xFE0F);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testWordJoiner() {
|
public void testWordJoiner() {
|
||||||
46
terminal-view/build.gradle
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
plugins {
|
||||||
|
id "com.jfrog.bintray" version "1.7.3"
|
||||||
|
id "com.github.dcendents.android-maven" version "2.0"
|
||||||
|
}
|
||||||
|
|
||||||
|
apply plugin: 'com.android.library'
|
||||||
|
|
||||||
|
ext {
|
||||||
|
bintrayName = 'terminal-view'
|
||||||
|
publishedGroupId = 'com.termux'
|
||||||
|
libraryName = 'TerminalView'
|
||||||
|
artifact = 'terminal-view'
|
||||||
|
libraryDescription = 'The terminal view used in Termux'
|
||||||
|
siteUrl = 'https://github.com/termux/termux'
|
||||||
|
gitUrl = 'https://github.com/termux/termux.git'
|
||||||
|
libraryVersion = '0.50'
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion 27
|
||||||
|
buildToolsVersion "27.0.2"
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation 'com.android.support:support-annotations:27.0.0'
|
||||||
|
api project(":terminal-emulator")
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
minSdkVersion 21
|
||||||
|
targetSdkVersion 27
|
||||||
|
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
minifyEnabled false
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
testImplementation 'junit:junit:4.12'
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: '../scripts/bintray-publish.gradle'
|
||||||
25
terminal-view/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Add project specific ProGuard rules here.
|
||||||
|
# By default, the flags in this file are appended to flags specified
|
||||||
|
# in /Users/fornwall/lib/android-sdk/tools/proguard/proguard-android.txt
|
||||||
|
# You can edit the include path and order by changing the proguardFiles
|
||||||
|
# directive in build.gradle.
|
||||||
|
#
|
||||||
|
# For more details, see
|
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|
||||||
|
# Add any project specific keep options here:
|
||||||
|
|
||||||
|
# If your project uses WebView with JS, uncomment the following
|
||||||
|
# and specify the fully qualified class name to the JavaScript interface
|
||||||
|
# class:
|
||||||
|
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||||
|
# public *;
|
||||||
|
#}
|
||||||
|
|
||||||
|
# Uncomment this to preserve the line number information for
|
||||||
|
# debugging stack traces.
|
||||||
|
#-keepattributes SourceFile,LineNumberTable
|
||||||
|
|
||||||
|
# If you keep the line number information, uncomment this to
|
||||||
|
# hide the original source file name.
|
||||||
|
#-renamesourcefileattribute SourceFile
|
||||||
2
terminal-view/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
<manifest package="com.termux.view">
|
||||||
|
</manifest>
|
||||||
@@ -6,7 +6,7 @@ import android.view.MotionEvent;
|
|||||||
import android.view.ScaleGestureDetector;
|
import android.view.ScaleGestureDetector;
|
||||||
|
|
||||||
/** A combination of {@link GestureDetector} and {@link ScaleGestureDetector}. */
|
/** A combination of {@link GestureDetector} and {@link ScaleGestureDetector}. */
|
||||||
public final class GestureAndScaleRecognizer {
|
final class GestureAndScaleRecognizer {
|
||||||
|
|
||||||
public interface Listener {
|
public interface Listener {
|
||||||
boolean onSingleTapUp(MotionEvent e);
|
boolean onSingleTapUp(MotionEvent e);
|
||||||
@@ -85,6 +85,7 @@ public final class GestureAndScaleRecognizer {
|
|||||||
return mListener.onScale(detector.getFocusX(), detector.getFocusY(), detector.getScaleFactor());
|
return mListener.onScale(detector.getFocusX(), detector.getFocusY(), detector.getScaleFactor());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
mScaleDetector.setQuickScaleEnabled(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onTouchEvent(MotionEvent event) {
|
public void onTouchEvent(MotionEvent event) {
|
||||||
@@ -29,7 +29,6 @@ import android.view.inputmethod.EditorInfo;
|
|||||||
import android.view.inputmethod.InputConnection;
|
import android.view.inputmethod.InputConnection;
|
||||||
import android.widget.Scroller;
|
import android.widget.Scroller;
|
||||||
|
|
||||||
import com.termux.R;
|
|
||||||
import com.termux.terminal.EmulatorDebug;
|
import com.termux.terminal.EmulatorDebug;
|
||||||
import com.termux.terminal.KeyHandler;
|
import com.termux.terminal.KeyHandler;
|
||||||
import com.termux.terminal.TerminalBuffer;
|
import com.termux.terminal.TerminalBuffer;
|
||||||
@@ -49,7 +48,7 @@ public final class TerminalView extends View {
|
|||||||
|
|
||||||
TerminalRenderer mRenderer;
|
TerminalRenderer mRenderer;
|
||||||
|
|
||||||
TerminalKeyListener mOnKeyListener;
|
TerminalViewClient mClient;
|
||||||
|
|
||||||
/** The top row of text to display. Ranges from -activeTranscriptRows to 0. */
|
/** The top row of text to display. Ranges from -activeTranscriptRows to 0. */
|
||||||
int mTopRow;
|
int mTopRow;
|
||||||
@@ -106,7 +105,7 @@ public final class TerminalView extends View {
|
|||||||
requestFocus();
|
requestFocus();
|
||||||
if (!mEmulator.isMouseTrackingActive()) {
|
if (!mEmulator.isMouseTrackingActive()) {
|
||||||
if (!e.isFromSource(InputDevice.SOURCE_MOUSE)) {
|
if (!e.isFromSource(InputDevice.SOURCE_MOUSE)) {
|
||||||
mOnKeyListener.onSingleTapUp(e);
|
mClient.onSingleTapUp(e);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -136,7 +135,7 @@ public final class TerminalView extends View {
|
|||||||
public boolean onScale(float focusX, float focusY, float scale) {
|
public boolean onScale(float focusX, float focusY, float scale) {
|
||||||
if (mEmulator == null || mIsSelectingText) return true;
|
if (mEmulator == null || mIsSelectingText) return true;
|
||||||
mScaleFactor *= scale;
|
mScaleFactor *= scale;
|
||||||
mScaleFactor = mOnKeyListener.onScale(mScaleFactor);
|
mScaleFactor = mClient.onScale(mScaleFactor);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,7 +188,9 @@ public final class TerminalView extends View {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onLongPress(MotionEvent e) {
|
public void onLongPress(MotionEvent e) {
|
||||||
if (!mGestureRecognizer.isInProgress() && !mIsSelectingText) {
|
if (mGestureRecognizer.isInProgress()) return;
|
||||||
|
if (mClient.onLongPress(e)) return;
|
||||||
|
if (!mIsSelectingText) {
|
||||||
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
|
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
|
||||||
toggleSelectingText(e);
|
toggleSelectingText(e);
|
||||||
}
|
}
|
||||||
@@ -202,8 +203,8 @@ public final class TerminalView extends View {
|
|||||||
* @param onKeyListener Listener for all kinds of key events, both hardware and IME (which makes it different from that
|
* @param onKeyListener Listener for all kinds of key events, both hardware and IME (which makes it different from that
|
||||||
* available with {@link View#setOnKeyListener(OnKeyListener)}.
|
* available with {@link View#setOnKeyListener(OnKeyListener)}.
|
||||||
*/
|
*/
|
||||||
public void setOnKeyListener(TerminalKeyListener onKeyListener) {
|
public void setOnKeyListener(TerminalViewClient onKeyListener) {
|
||||||
this.mOnKeyListener = onKeyListener;
|
this.mClient = onKeyListener;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -238,9 +239,9 @@ public final class TerminalView extends View {
|
|||||||
// https://github.com/termux/termux-app/issues/137 (japanese chars and TYPE_NULL).
|
// https://github.com/termux/termux-app/issues/137 (japanese chars and TYPE_NULL).
|
||||||
outAttrs.inputType = InputType.TYPE_NULL;
|
outAttrs.inputType = InputType.TYPE_NULL;
|
||||||
|
|
||||||
outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_FULLSCREEN |
|
// Note that IME_ACTION_NONE cannot be used as that makes it impossible to input newlines using the on-screen
|
||||||
EditorInfo.IME_FLAG_NO_ENTER_ACTION |
|
// keyboard on Android TV (see https://github.com/termux/termux-app/issues/221).
|
||||||
EditorInfo.IME_ACTION_NONE;
|
outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_FULLSCREEN;
|
||||||
|
|
||||||
return new BaseInputConnection(this, true) {
|
return new BaseInputConnection(this, true) {
|
||||||
|
|
||||||
@@ -298,6 +299,14 @@ public final class TerminalView extends View {
|
|||||||
|
|
||||||
boolean ctrlHeld = false;
|
boolean ctrlHeld = false;
|
||||||
if (codePoint <= 31 && codePoint != 27) {
|
if (codePoint <= 31 && codePoint != 27) {
|
||||||
|
if (codePoint == '\n') {
|
||||||
|
// The AOSP keyboard and descendants seems to send \n as text when the enter key is pressed,
|
||||||
|
// instead of a key event like most other keyboard apps. A terminal expects \r for the enter
|
||||||
|
// key (although when icrnl is enabled this doesn't make a difference - run 'stty -icrnl' to
|
||||||
|
// check the behaviour).
|
||||||
|
codePoint = '\r';
|
||||||
|
}
|
||||||
|
|
||||||
// E.g. penti keyboard for ctrl input.
|
// E.g. penti keyboard for ctrl input.
|
||||||
ctrlHeld = true;
|
ctrlHeld = true;
|
||||||
switch (codePoint) {
|
switch (codePoint) {
|
||||||
@@ -547,7 +556,7 @@ public final class TerminalView extends View {
|
|||||||
if (mIsSelectingText) {
|
if (mIsSelectingText) {
|
||||||
toggleSelectingText(null);
|
toggleSelectingText(null);
|
||||||
return true;
|
return true;
|
||||||
} else if (mOnKeyListener.shouldBackButtonBeMappedToEscape()) {
|
} else if (mClient.shouldBackButtonBeMappedToEscape()) {
|
||||||
// Intercept back button to treat it as escape:
|
// Intercept back button to treat it as escape:
|
||||||
switch (event.getAction()) {
|
switch (event.getAction()) {
|
||||||
case KeyEvent.ACTION_DOWN:
|
case KeyEvent.ACTION_DOWN:
|
||||||
@@ -566,10 +575,10 @@ public final class TerminalView extends View {
|
|||||||
Log.i(EmulatorDebug.LOG_TAG, "onKeyDown(keyCode=" + keyCode + ", isSystem()=" + event.isSystem() + ", event=" + event + ")");
|
Log.i(EmulatorDebug.LOG_TAG, "onKeyDown(keyCode=" + keyCode + ", isSystem()=" + event.isSystem() + ", event=" + event + ")");
|
||||||
if (mEmulator == null) return true;
|
if (mEmulator == null) return true;
|
||||||
|
|
||||||
if (mOnKeyListener.onKeyDown(keyCode, event, mTermSession)) {
|
if (mClient.onKeyDown(keyCode, event, mTermSession)) {
|
||||||
invalidate();
|
invalidate();
|
||||||
return true;
|
return true;
|
||||||
} else if (event.isSystem() && (!mOnKeyListener.shouldBackButtonBeMappedToEscape() || keyCode != KeyEvent.KEYCODE_BACK)) {
|
} else if (event.isSystem() && (!mClient.shouldBackButtonBeMappedToEscape() || keyCode != KeyEvent.KEYCODE_BACK)) {
|
||||||
return super.onKeyDown(keyCode, event);
|
return super.onKeyDown(keyCode, event);
|
||||||
} else if (event.getAction() == KeyEvent.ACTION_MULTIPLE && keyCode == KeyEvent.KEYCODE_UNKNOWN) {
|
} else if (event.getAction() == KeyEvent.ACTION_MULTIPLE && keyCode == KeyEvent.KEYCODE_UNKNOWN) {
|
||||||
mTermSession.write(event.getCharacters());
|
mTermSession.write(event.getCharacters());
|
||||||
@@ -633,10 +642,12 @@ public final class TerminalView extends View {
|
|||||||
+ leftAltDownFromEvent + ")");
|
+ leftAltDownFromEvent + ")");
|
||||||
}
|
}
|
||||||
|
|
||||||
final boolean controlDown = controlDownFromEvent || mOnKeyListener.readControlKey();
|
if (mTermSession == null) return;
|
||||||
final boolean altDown = leftAltDownFromEvent || mOnKeyListener.readAltKey();
|
|
||||||
|
|
||||||
if (mOnKeyListener.onCodePoint(codePoint, controlDown, mTermSession)) return;
|
final boolean controlDown = controlDownFromEvent || mClient.readControlKey();
|
||||||
|
final boolean altDown = leftAltDownFromEvent || mClient.readAltKey();
|
||||||
|
|
||||||
|
if (mClient.onCodePoint(codePoint, controlDown, mTermSession)) return;
|
||||||
|
|
||||||
if (controlDown) {
|
if (controlDown) {
|
||||||
if (codePoint >= 'a' && codePoint <= 'z') {
|
if (codePoint >= 'a' && codePoint <= 'z') {
|
||||||
@@ -705,7 +716,7 @@ public final class TerminalView extends View {
|
|||||||
Log.i(EmulatorDebug.LOG_TAG, "onKeyUp(keyCode=" + keyCode + ", event=" + event + ")");
|
Log.i(EmulatorDebug.LOG_TAG, "onKeyUp(keyCode=" + keyCode + ", event=" + event + ")");
|
||||||
if (mEmulator == null) return true;
|
if (mEmulator == null) return true;
|
||||||
|
|
||||||
if (mOnKeyListener.onKeyUp(keyCode, event)) {
|
if (mClient.onKeyUp(keyCode, event)) {
|
||||||
invalidate();
|
invalidate();
|
||||||
return true;
|
return true;
|
||||||
} else if (event.isSystem()) {
|
} else if (event.isSystem()) {
|
||||||
@@ -773,7 +784,7 @@ public final class TerminalView extends View {
|
|||||||
@TargetApi(23)
|
@TargetApi(23)
|
||||||
public void toggleSelectingText(MotionEvent ev) {
|
public void toggleSelectingText(MotionEvent ev) {
|
||||||
mIsSelectingText = !mIsSelectingText;
|
mIsSelectingText = !mIsSelectingText;
|
||||||
mOnKeyListener.copyModeChanged(mIsSelectingText);
|
mClient.copyModeChanged(mIsSelectingText);
|
||||||
|
|
||||||
if (mIsSelectingText) {
|
if (mIsSelectingText) {
|
||||||
if (mLeftSelectionHandle == null) {
|
if (mLeftSelectionHandle == null) {
|
||||||
@@ -825,6 +836,10 @@ public final class TerminalView extends View {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
|
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
|
||||||
|
if (!mIsSelectingText) {
|
||||||
|
// Fix issue where the dialog is pressed while being dismissed.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
switch (item.getItemId()) {
|
switch (item.getItemId()) {
|
||||||
case 1:
|
case 1:
|
||||||
String selectedText = mEmulator.getSelectedText(mSelX1, mSelY1, mSelX2, mSelY2).trim();
|
String selectedText = mEmulator.getSelectedText(mSelX1, mSelY1, mSelX2, mSelY2).trim();
|
||||||
@@ -8,16 +8,19 @@ import com.termux.terminal.TerminalSession;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Input and scale listener which may be set on a {@link TerminalView} through
|
* Input and scale listener which may be set on a {@link TerminalView} through
|
||||||
* {@link TerminalView#setOnKeyListener(TerminalKeyListener)}.
|
* {@link TerminalView#setOnKeyListener(TerminalViewClient)}.
|
||||||
* <p/>
|
* <p/>
|
||||||
* TODO: Rename to TerminalViewClient.
|
|
||||||
*/
|
*/
|
||||||
public interface TerminalKeyListener {
|
public interface TerminalViewClient {
|
||||||
|
|
||||||
/** Callback function on scale events according to {@link ScaleGestureDetector#getScaleFactor()}. */
|
/**
|
||||||
|
* Callback function on scale events according to {@link ScaleGestureDetector#getScaleFactor()}.
|
||||||
|
*/
|
||||||
float onScale(float scale);
|
float onScale(float scale);
|
||||||
|
|
||||||
/** On a single tap on the terminal if terminal mouse reporting not enabled. */
|
/**
|
||||||
|
* On a single tap on the terminal if terminal mouse reporting not enabled.
|
||||||
|
*/
|
||||||
void onSingleTapUp(MotionEvent e);
|
void onSingleTapUp(MotionEvent e);
|
||||||
|
|
||||||
boolean shouldBackButtonBeMappedToEscape();
|
boolean shouldBackButtonBeMappedToEscape();
|
||||||
@@ -34,4 +37,6 @@ public interface TerminalKeyListener {
|
|||||||
|
|
||||||
boolean onCodePoint(int codePoint, boolean ctrlDown, TerminalSession session);
|
boolean onCodePoint(int codePoint, boolean ctrlDown, TerminalSession session);
|
||||||
|
|
||||||
|
boolean onLongPress(MotionEvent event);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
5
terminal-view/src/main/res/values/strings.xml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<resources>
|
||||||
|
<string name="paste_text">Paste</string>
|
||||||
|
<string name="copy_text">Copy</string>
|
||||||
|
<string name="text_selection_more">More…</string>
|
||||||
|
</resources>
|
||||||