Compare commits
205 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
48fab33b79 | ||
|
|
8d7a67645b | ||
|
|
186b49d429 | ||
|
|
40a2775f52 | ||
|
|
f802d6001d | ||
|
|
50f66a12da | ||
|
|
d8e6fd21d1 | ||
|
|
0964d83572 | ||
|
|
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 | ||
|
|
743b067cae | ||
|
|
23333c074a | ||
|
|
f11644fa51 | ||
|
|
212be59fca | ||
|
|
e3a1f8224f | ||
|
|
4f40d5a26a | ||
|
|
df92896eef | ||
|
|
4c93cb42f1 | ||
|
|
34afb9de43 | ||
|
|
b6ea29d260 | ||
|
|
289d58a2f0 | ||
|
|
0501ce924b | ||
|
|
d12256f5e5 | ||
|
|
fcbc036f92 | ||
|
|
70d5839334 | ||
|
|
9c19540759 | ||
|
|
9fe0e49473 | ||
|
|
357b17e972 | ||
|
|
6334470f81 | ||
|
|
b8cdd59c68 | ||
|
|
6cf36fffd7 | ||
|
|
f10ecd4db5 | ||
|
|
26457e8443 | ||
|
|
6702846c7c | ||
|
|
0c6180bbb1 | ||
|
|
fcf07f6a19 | ||
|
|
e272e3b3b2 | ||
|
|
cdf0e72145 | ||
|
|
70245eb78c | ||
|
|
ee7631dfac | ||
|
|
5ecf5d12d1 | ||
|
|
0c8cd90f4e | ||
|
|
e1ea68913f | ||
|
|
dde854eba7 | ||
|
|
07a4607c04 | ||
|
|
883be37b98 | ||
|
|
d939d3d927 | ||
|
|
a0fa51eb92 | ||
|
|
755513bb33 | ||
|
|
8ad7a6669c | ||
|
|
60f7aada9e | ||
|
|
6aa0492434 | ||
|
|
019aa44837 | ||
|
|
8d3d5e147f | ||
|
|
44197b90e2 | ||
|
|
794c7ee333 | ||
|
|
0457ddbc69 | ||
|
|
283792af5e | ||
|
|
be7cfa603a | ||
|
|
3480bf7346 | ||
|
|
8314a2756c | ||
|
|
4de82d9fe0 | ||
|
|
d658e16801 | ||
|
|
26dcd5af88 | ||
|
|
8056013082 | ||
|
|
8e90545c4b | ||
|
|
426ddbacbd | ||
|
|
7e1f8a551f | ||
|
|
e169af0447 | ||
|
|
a2cb3fafee | ||
|
|
166710f14a | ||
|
|
c1a9b7726f | ||
|
|
afb339e9d8 | ||
|
|
64c23f498f | ||
|
|
1dc92b2a12 | ||
|
|
990a957383 | ||
|
|
7bb64d724c | ||
|
|
4609dd71c6 | ||
|
|
8d00f22d4c | ||
|
|
5532421ab2 | ||
|
|
d2b27978e2 | ||
|
|
30b05e9ab2 | ||
|
|
c350318c77 |
1
.gitignore
vendored
@@ -6,6 +6,7 @@
|
||||
build/
|
||||
*.apk
|
||||
*.so
|
||||
.externalNativeBuild
|
||||
|
||||
# Crashlytics configuations
|
||||
com_crashlytics_export_strings.xml
|
||||
|
||||
9
.idea/gradle.xml
generated
@@ -10,14 +10,11 @@
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
<option value="$PROJECT_DIR$/app" />
|
||||
<option value="$PROJECT_DIR$/terminal-emulator" />
|
||||
<option value="$PROJECT_DIR$/terminal-view" />
|
||||
</set>
|
||||
</option>
|
||||
<option name="myModules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
<option value="$PROJECT_DIR$/app" />
|
||||
</set>
|
||||
</option>
|
||||
<option name="resolveModulePerSourceSet" value="false" />
|
||||
</GradleProjectSettings>
|
||||
</option>
|
||||
</component>
|
||||
|
||||
15
.travis.yml
@@ -6,16 +6,21 @@ env:
|
||||
global:
|
||||
# The next declaration is the encrypted COVERITY_SCAN_TOKEN, created
|
||||
# via the "travis encrypt" command using the project repo's public key
|
||||
- secure: "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:
|
||||
components:
|
||||
- platform-tools
|
||||
- tools
|
||||
- build-tools-24.0.1
|
||||
- android-24
|
||||
- build-tools-27.0.2
|
||||
- android-27
|
||||
- extra-android-m2repository
|
||||
|
||||
before_install:
|
||||
- git clone https://github.com/urho3d/android-ndk.git $HOME/android-ndk
|
||||
- export ANDROID_NDK_HOME=$HOME/android-ndk
|
||||
- yes | sdkmanager "platforms;android-27"
|
||||
|
||||
script:
|
||||
- ./gradlew testDebugUnitTest
|
||||
|
||||
@@ -26,5 +31,5 @@ addons:
|
||||
description: "Terminal emulator and Linux environment for Android"
|
||||
notification_email: fredrik@fornwall.net
|
||||
build_command_prepend: "./gradlew clean"
|
||||
build_command: "./gradlew assemble"
|
||||
branch_pattern: coverity_scan
|
||||
build_command: "./gradlew build"
|
||||
branch_pattern: master
|
||||
|
||||
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/).
|
||||
19
README.md
@@ -3,24 +3,17 @@ Termux app
|
||||
[](https://travis-ci.org/termux/termux-app)
|
||||
[](https://gitter.im/termux/termux)
|
||||
|
||||
|
||||
Termux is an Android terminal app and Linux environment.
|
||||
[Termux](https://termux.com) is an Android terminal app and Linux environment.
|
||||
|
||||
* [Termux on Google Play Store](https://play.google.com/store/apps/details?id=com.termux)
|
||||
* [Termux on F-Droid](https://f-droid.org/repository/browse/?fdid=com.termux)
|
||||
* [termux.com](http://termux.com)
|
||||
* [Termux Help](http://termux.com/help/)
|
||||
* [Termux app on GitHub](https://github.com/termux/termux-app)
|
||||
* [Termux packages on GitHub](https://github.com/termux/termux-packages)
|
||||
* [Termux Facebook](https://facebook.com/termux/)
|
||||
* [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/)
|
||||
|
||||
License
|
||||
=======
|
||||
Released under [the GPLv3 license](https://www.gnu.org/licenses/gpl.html). Contains code from `Terminal Emulator for Android` which is released under [the Apache License 2.0](https://www.apache.org/licenses/).
|
||||
|
||||
Building JNI libraries
|
||||
======================
|
||||
Execute the `build-jnilibs.sh` script to build the required JNI libraries.
|
||||
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)
|
||||
|
||||
Terminal resources
|
||||
==================
|
||||
|
||||
@@ -1,26 +1,21 @@
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion 24
|
||||
buildToolsVersion "24.0.1"
|
||||
compileSdkVersion 27
|
||||
buildToolsVersion "27.0.2"
|
||||
|
||||
dependencies {
|
||||
compile 'com.android.support:support-annotations:24.1.1'
|
||||
compile "com.android.support:support-v4:24.1.1"
|
||||
implementation 'com.android.support:support-annotations:27.0.2'
|
||||
implementation "com.android.support:support-core-ui:27.0.2"
|
||||
implementation project(":terminal-view")
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.termux"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 24
|
||||
versionCode 38
|
||||
versionName "0.38"
|
||||
|
||||
ndk {
|
||||
moduleName "libtermux"
|
||||
abiFilters "armeabi-v7a", "arm64-v8a", "x86", "x86_64"
|
||||
cFlags "-std=c11 -Wall -Wextra -Os -fno-stack-protector -nostdlib -Wl,--gc-sections"
|
||||
}
|
||||
targetSdkVersion 27
|
||||
versionCode 60
|
||||
versionName "0.60"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
@@ -33,5 +28,5 @@ android {
|
||||
}
|
||||
|
||||
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
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# Add any project specific keep options here:
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
-renamesourcefileattribute SourceFile
|
||||
-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
@@ -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"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="com.termux"
|
||||
android:installLocation="internalOnly"
|
||||
android:sharedUserId="com.termux"
|
||||
@@ -13,18 +14,25 @@
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
|
||||
<application
|
||||
android:extractNativeLibs="true"
|
||||
android:allowBackup="true"
|
||||
android:fullBackupContent="@xml/backupscheme"
|
||||
android:icon="@drawable/ic_launcher"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:banner="@drawable/banner"
|
||||
android:label="@string/application_name"
|
||||
android:theme="@style/Theme.Termux"
|
||||
android:supportsRtl="false" >
|
||||
|
||||
<!-- This (or rather, value 2.1 or higher) is needed to make the Samsung Galaxy S8
|
||||
mark the app with "This app is optimized to run in full screen." -->
|
||||
<meta-data android:name="android.max_aspect" android:value="10.0" />
|
||||
|
||||
<activity
|
||||
android:name="com.termux.app.TermuxActivity"
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
|
||||
android:launchMode="singleTask"
|
||||
android:resizeableActivity="true"
|
||||
android:windowSoftInputMode="adjustResize|stateAlwaysVisible" >
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
@@ -34,6 +42,7 @@
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
||||
</intent-filter>
|
||||
<meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcuts" />
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
@@ -41,6 +50,7 @@
|
||||
android:exported="false"
|
||||
android:theme="@android:style/Theme.Material.Light.DarkActionBar"
|
||||
android:parentActivityName=".app.TermuxActivity"
|
||||
android:resizeableActivity="true"
|
||||
android:label="@string/application_name" />
|
||||
|
||||
<activity
|
||||
@@ -48,6 +58,7 @@
|
||||
android:label="@string/application_name"
|
||||
android:taskAffinity="com.termux.filereceiver"
|
||||
android:excludeFromRecents="true"
|
||||
android:resizeableActivity="true"
|
||||
android:noHistory="true">
|
||||
<!-- Accept multiple file types when sending. -->
|
||||
<intent-filter>
|
||||
@@ -62,7 +73,7 @@
|
||||
<data android:mimeType="video/*" />
|
||||
</intent-filter>
|
||||
<!-- Be more restrictive for viewing files, restricting ourselves to text files. -->
|
||||
<intent-filter>
|
||||
<intent-filter tools:ignore="AppLinkUrlError">
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="text/*" />
|
||||
@@ -73,6 +84,18 @@
|
||||
</intent-filter>
|
||||
</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
|
||||
android:name=".filepicker.TermuxDocumentsProvider"
|
||||
android:authorities="com.termux.documents"
|
||||
@@ -88,6 +111,15 @@
|
||||
android:name="com.termux.app.TermuxService"
|
||||
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>
|
||||
|
||||
</manifest>
|
||||
|
||||
@@ -4,58 +4,71 @@ import android.util.Log;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.lang.reflect.Field;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A background job launched by Termux.
|
||||
*/
|
||||
public final class BackgroundJob {
|
||||
|
||||
private static final String LOG_TAG = "termux-background";
|
||||
private static final String LOG_TAG = "termux-task";
|
||||
|
||||
final Process mProcess;
|
||||
|
||||
public BackgroundJob(File cwd, File fileToExecute, String[] args) throws IOException {
|
||||
String[] env = buildEnvironment(false, cwd.getAbsolutePath());
|
||||
public BackgroundJob(String cwd, String fileToExecute, final String[] args, final TermuxService service) {
|
||||
String[] env = buildEnvironment(false, cwd);
|
||||
if (cwd == null) cwd = TermuxService.HOME_PATH;
|
||||
|
||||
String[] progArray = new String[args.length + 1];
|
||||
final String[] progArray = setupProcessArgs(fileToExecute, args);
|
||||
final String processDescription = Arrays.toString(progArray);
|
||||
|
||||
mProcess = Runtime.getRuntime().exec(progArray, env, cwd);
|
||||
|
||||
new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
while (true) {
|
||||
try {
|
||||
int exitCode = mProcess.waitFor();
|
||||
if (exitCode == 0) {
|
||||
Log.i(LOG_TAG, "exited normally");
|
||||
return;
|
||||
} else {
|
||||
Log.i(LOG_TAG, "exited with exit code: " + exitCode);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
// Ignore.
|
||||
}
|
||||
}
|
||||
}
|
||||
}.start();
|
||||
Process process;
|
||||
try {
|
||||
process = Runtime.getRuntime().exec(progArray, env, new File(cwd));
|
||||
} catch (IOException e) {
|
||||
mProcess = null;
|
||||
// TODO: Visible error message?
|
||||
Log.e(LOG_TAG, "Failed running background job: " + processDescription, e);
|
||||
return;
|
||||
}
|
||||
|
||||
mProcess = process;
|
||||
final int pid = getPid(mProcess);
|
||||
|
||||
new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
Log.i(LOG_TAG, "[" + pid + "] starting: " + processDescription);
|
||||
InputStream stdout = mProcess.getInputStream();
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(stdout, StandardCharsets.UTF_8));
|
||||
String line;
|
||||
try {
|
||||
// FIXME: Long lines.
|
||||
while ((line = reader.readLine()) != null) {
|
||||
Log.i(LOG_TAG, line);
|
||||
Log.i(LOG_TAG, "[" + pid + "] stdout: " + line);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.e(LOG_TAG, "Error reading output", e);
|
||||
}
|
||||
|
||||
try {
|
||||
int exitCode = mProcess.waitFor();
|
||||
service.onBackgroundJobExited(BackgroundJob.this);
|
||||
if (exitCode == 0) {
|
||||
Log.i(LOG_TAG, "[" + pid + "] exited normally");
|
||||
} else {
|
||||
Log.w(LOG_TAG, "[" + pid + "] exited with code: " + exitCode);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
// Ignore.
|
||||
}
|
||||
}
|
||||
@@ -71,7 +84,7 @@ public final class BackgroundJob {
|
||||
try {
|
||||
// FIXME: Long lines.
|
||||
while ((line = reader.readLine()) != null) {
|
||||
Log.e(LOG_TAG, line);
|
||||
Log.i(LOG_TAG, "[" + pid + "] stderr: " + line);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// Ignore.
|
||||
@@ -80,7 +93,7 @@ public final class BackgroundJob {
|
||||
};
|
||||
}
|
||||
|
||||
public String[] buildEnvironment(boolean failSafe, String cwd) {
|
||||
public static String[] buildEnvironment(boolean failSafe, String cwd) {
|
||||
new File(TermuxService.HOME_PATH).mkdirs();
|
||||
|
||||
if (cwd == null) cwd = TermuxService.HOME_PATH;
|
||||
@@ -93,7 +106,6 @@ public final class BackgroundJob {
|
||||
// EXTERNAL_STORAGE is needed for /system/bin/am to work on at least
|
||||
// Samsung S7 - see https://plus.google.com/110070148244138185604/posts/gp8Lk3aCGp3.
|
||||
final String externalStorageEnv = "EXTERNAL_STORAGE=" + System.getenv("EXTERNAL_STORAGE");
|
||||
String[] env;
|
||||
if (failSafe) {
|
||||
// Keep the default path so that system binaries can be used in the failsafe session.
|
||||
final String pathEnv = "PATH=" + System.getenv("PATH");
|
||||
@@ -104,9 +116,78 @@ public final class BackgroundJob {
|
||||
final String langEnv = "LANG=en_US.UTF-8";
|
||||
final String pathEnv = "PATH=" + TermuxService.PREFIX_PATH + "/bin:" + TermuxService.PREFIX_PATH + "/bin/applets";
|
||||
final String pwdEnv = "PWD=" + cwd;
|
||||
final String tmpdirEnv = "TMPDIR=" + TermuxService.PREFIX_PATH + "/tmp";
|
||||
|
||||
return new String[]{termEnv, homeEnv, prefixEnv, ps1Env, ldEnv, langEnv, pathEnv, pwdEnv, androidRootEnv, androidDataEnv, externalStorageEnv};
|
||||
return new String[]{termEnv, homeEnv, prefixEnv, ps1Env, ldEnv, langEnv, pathEnv, pwdEnv, androidRootEnv, androidDataEnv, externalStorageEnv, tmpdirEnv};
|
||||
}
|
||||
}
|
||||
|
||||
public static int getPid(Process p) {
|
||||
try {
|
||||
Field f = p.getClass().getDeclaredField("pid");
|
||||
f.setAccessible(true);
|
||||
try {
|
||||
return f.getInt(p);
|
||||
} finally {
|
||||
f.setAccessible(false);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
static String[] setupProcessArgs(String fileToExecute, String[] args) {
|
||||
// The file to execute may either be:
|
||||
// - An elf file, in which we execute it directly.
|
||||
// - A script file without shebang, which we execute with our standard shell $PREFIX/bin/sh instead of the
|
||||
// system /system/bin/sh. The system shell may vary and may not work at all due to LD_LIBRARY_PATH.
|
||||
// - A file with shebang, which we try to handle with e.g. /bin/foo -> $PREFIX/bin/foo.
|
||||
String interpreter = null;
|
||||
try {
|
||||
File file = new File(fileToExecute);
|
||||
try (FileInputStream in = new FileInputStream(file)) {
|
||||
byte[] buffer = new byte[256];
|
||||
int bytesRead = in.read(buffer);
|
||||
if (bytesRead > 4) {
|
||||
if (buffer[0] == 0x7F && buffer[1] == 'E' && buffer[2] == 'L' && buffer[3] == 'F') {
|
||||
// Elf file, do nothing.
|
||||
} else if (buffer[0] == '#' && buffer[1] == '!') {
|
||||
// Try to parse shebang.
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (int i = 2; i < bytesRead; i++) {
|
||||
char c = (char) buffer[i];
|
||||
if (c == ' ' || c == '\n') {
|
||||
if (builder.length() == 0) {
|
||||
// Skip whitespace after shebang.
|
||||
} else {
|
||||
// End of shebang.
|
||||
String executable = builder.toString();
|
||||
if (executable.startsWith("/usr") || executable.startsWith("/bin")) {
|
||||
String[] parts = executable.split("/");
|
||||
String binary = parts[parts.length - 1];
|
||||
interpreter = TermuxService.PREFIX_PATH + "/bin/" + binary;
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
builder.append(c);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No shebang and no ELF, use standard shell.
|
||||
interpreter = TermuxService.PREFIX_PATH + "/bin/sh";
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// Ignore.
|
||||
}
|
||||
|
||||
List<String> result = new ArrayList<>();
|
||||
if (interpreter != null) result.add(interpreter);
|
||||
result.add(fileToExecute);
|
||||
if (args != null) Collections.addAll(result, args);
|
||||
return result.toArray(new String[result.size()]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.termux.app;
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.Gravity;
|
||||
import android.view.HapticFeedbackConstants;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
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_UP, keyCode));
|
||||
} else {
|
||||
TerminalView terminalView = (TerminalView) view.findViewById(R.id.terminal_view);
|
||||
TerminalView terminalView = view.findViewById(R.id.terminal_view);
|
||||
TerminalSession session = terminalView.getCurrentSession();
|
||||
if (session != null) session.write(chars);
|
||||
}
|
||||
@@ -144,6 +145,7 @@ public final class ExtraKeysView extends GridLayout {
|
||||
button.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
finalButton.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP);
|
||||
View root = getRootView();
|
||||
switch (buttonText) {
|
||||
case "CTRL":
|
||||
@@ -165,8 +167,8 @@ public final class ExtraKeysView extends GridLayout {
|
||||
param.rightMargin = param.topMargin = 0;
|
||||
param.setGravity(Gravity.LEFT);
|
||||
float weight = "▲▼◀▶".contains(buttonText) ? 0.7f : 1.f;
|
||||
param.columnSpec = GridLayout.spec(col, weight);
|
||||
param.rowSpec = GridLayout.spec(row, 1.f);
|
||||
param.columnSpec = GridLayout.spec(col, GridLayout.FILL, weight);
|
||||
param.rowSpec = GridLayout.spec(row, GridLayout.FILL, 1.f);
|
||||
button.setLayoutParams(param);
|
||||
|
||||
addView(button);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -73,6 +73,7 @@ import java.io.InputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
@@ -95,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_RESET_TERMINAL_ID = 5;
|
||||
private static final int CONTEXTMENU_STYLING_ID = 6;
|
||||
private static final int CONTEXTMENU_TOGGLE_FULLSCREEN_ID = 7;
|
||||
private static final int CONTEXTMENU_HELP_ID = 8;
|
||||
|
||||
private static final int MAX_SESSIONS = 8;
|
||||
@@ -111,8 +111,6 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
||||
|
||||
ExtraKeysView mExtraKeysView;
|
||||
|
||||
final FullScreenHelper mFullScreenHelper = new FullScreenHelper(this);
|
||||
|
||||
TermuxPreferences mSettings;
|
||||
|
||||
/**
|
||||
@@ -157,7 +155,6 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
||||
|
||||
void checkForFontAndColors() {
|
||||
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 colorsFile = new File("/data/data/com.termux/files/home/.termux/colors.properties");
|
||||
|
||||
@@ -212,14 +209,13 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
||||
mSettings = new TermuxPreferences(this);
|
||||
|
||||
setContentView(R.layout.drawer_layout);
|
||||
mTerminalView = (TerminalView) findViewById(R.id.terminal_view);
|
||||
mTerminalView.setOnKeyListener(new TermuxKeyListener(this));
|
||||
mTerminalView = findViewById(R.id.terminal_view);
|
||||
mTerminalView.setOnKeyListener(new TermuxViewClient(this));
|
||||
|
||||
mTerminalView.setTextSize(mSettings.getFontSize());
|
||||
mFullScreenHelper.setImmersive(mSettings.isFullScreen());
|
||||
mTerminalView.requestFocus();
|
||||
|
||||
final ViewPager viewPager = (ViewPager) findViewById(R.id.viewpager);
|
||||
final ViewPager viewPager = findViewById(R.id.viewpager);
|
||||
if (mSettings.isShowExtraKeys()) viewPager.setVisibility(View.VISIBLE);
|
||||
|
||||
viewPager.setAdapter(new PagerAdapter() {
|
||||
@@ -229,26 +225,29 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isViewFromObject(View view, Object object) {
|
||||
public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
|
||||
return view == object;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Object instantiateItem(ViewGroup collection, int position) {
|
||||
public Object instantiateItem(@NonNull ViewGroup collection, int position) {
|
||||
LayoutInflater inflater = LayoutInflater.from(TermuxActivity.this);
|
||||
View layout;
|
||||
if (position == 0) {
|
||||
layout = mExtraKeysView = (ExtraKeysView) inflater.inflate(R.layout.extra_keys_main, collection, false);
|
||||
} else {
|
||||
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() {
|
||||
@Override
|
||||
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
||||
TerminalSession session = getCurrentTermSession();
|
||||
if (session != null) {
|
||||
if (session.isRunning()) {
|
||||
session.write(editText.getText().toString() + "\n");
|
||||
String textToSend = editText.getText().toString();
|
||||
if (textToSend.length() == 0) textToSend = "\n";
|
||||
session.write(textToSend);
|
||||
} else {
|
||||
removeFinishedSession(session);
|
||||
}
|
||||
@@ -263,7 +262,7 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroyItem(ViewGroup collection, int position, Object view) {
|
||||
public void destroyItem(@NonNull ViewGroup collection, int position, @NonNull Object view) {
|
||||
collection.removeView((View) view);
|
||||
}
|
||||
});
|
||||
@@ -274,7 +273,7 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
||||
if (position == 0) {
|
||||
mTerminalView.requestFocus();
|
||||
} else {
|
||||
final EditText editText = (EditText) viewPager.findViewById(R.id.text_input);
|
||||
final EditText editText = viewPager.findViewById(R.id.text_input);
|
||||
if (editText != null) editText.requestFocus();
|
||||
}
|
||||
}
|
||||
@@ -338,7 +337,7 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
||||
}
|
||||
|
||||
void toggleShowExtraKeys() {
|
||||
final ViewPager viewPager = (ViewPager) findViewById(R.id.viewpager);
|
||||
final ViewPager viewPager = findViewById(R.id.viewpager);
|
||||
final boolean showNow = mSettings.toggleShowExtraKeys(TermuxActivity.this);
|
||||
viewPager.setVisibility(showNow ? View.VISIBLE : View.GONE);
|
||||
if (showNow && viewPager.getCurrentItem() == 1) {
|
||||
@@ -395,27 +394,26 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
||||
@Override
|
||||
public void onClipboardText(TerminalSession session, String text) {
|
||||
if (!mIsVisible) return;
|
||||
showToast("Clipboard:\n\"" + text + "\"", false);
|
||||
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
clipboard.setPrimaryClip(new ClipData(null, new String[]{"text/plain"}, new ClipData.Item(text)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBell(TerminalSession session) {
|
||||
if (mIsVisible) {
|
||||
switch (mSettings.mBellBehaviour) {
|
||||
case TermuxPreferences.BELL_BEEP:
|
||||
mBellSoundPool.play(mBellSoundId, 1.f, 1.f, 1, 0, 1.f);
|
||||
break;
|
||||
case TermuxPreferences.BELL_VIBRATE:
|
||||
((Vibrator) getSystemService(VIBRATOR_SERVICE)).vibrate(50);
|
||||
break;
|
||||
case TermuxPreferences.BELL_IGNORE:
|
||||
// Ignore the bell character.
|
||||
break;
|
||||
}
|
||||
if (!mIsVisible) return;
|
||||
|
||||
switch (mSettings.mBellBehaviour) {
|
||||
case TermuxPreferences.BELL_BEEP:
|
||||
mBellSoundPool.play(mBellSoundId, 1.f, 1.f, 1, 0, 1.f);
|
||||
break;
|
||||
case TermuxPreferences.BELL_VIBRATE:
|
||||
((Vibrator) getSystemService(VIBRATOR_SERVICE)).vibrate(50);
|
||||
break;
|
||||
case TermuxPreferences.BELL_IGNORE:
|
||||
// Ignore the bell character.
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -424,13 +422,14 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
||||
}
|
||||
};
|
||||
|
||||
ListView listView = (ListView) findViewById(R.id.left_drawer_list);
|
||||
ListView listView = findViewById(R.id.left_drawer_list);
|
||||
mListViewAdapter = new ArrayAdapter<TerminalSession>(getApplicationContext(), R.layout.line_in_drawer, mTermService.getSessions()) {
|
||||
final StyleSpan boldSpan = new StyleSpan(Typeface.BOLD);
|
||||
final StyleSpan italicSpan = new StyleSpan(Typeface.ITALIC);
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
public View getView(int position, View convertView, @NonNull ViewGroup parent) {
|
||||
View row = convertView;
|
||||
if (row == null) {
|
||||
LayoutInflater inflater = getLayoutInflater();
|
||||
@@ -440,7 +439,7 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
||||
TerminalSession sessionAtRow = getItem(position);
|
||||
boolean sessionRunning = sessionAtRow.isRunning();
|
||||
|
||||
TextView firstLineView = (TextView) row.findViewById(R.id.row_line);
|
||||
TextView firstLineView = row.findViewById(R.id.row_line);
|
||||
|
||||
String name = sessionAtRow.mSessionName;
|
||||
String sessionTitle = sessionAtRow.getTitle();
|
||||
@@ -491,17 +490,6 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
||||
public void run() {
|
||||
if (mTermService == null) return; // Activity might have been destroyed.
|
||||
try {
|
||||
if (TermuxPreferences.isShowWelcomeDialog(TermuxActivity.this)) {
|
||||
new AlertDialog.Builder(TermuxActivity.this).setTitle(R.string.welcome_dialog_title).setMessage(R.string.welcome_dialog_body)
|
||||
.setCancelable(false).setPositiveButton(android.R.string.ok, null)
|
||||
.setNegativeButton(R.string.welcome_dialog_dont_show_again_button, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
TermuxPreferences.disableWelcomeDialog(TermuxActivity.this);
|
||||
dialog.dismiss();
|
||||
}
|
||||
}).show();
|
||||
}
|
||||
addNewSession(false, null);
|
||||
} catch (WindowManager.BadTokenException e) {
|
||||
// Activity finished - ignore.
|
||||
@@ -513,7 +501,13 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
||||
finish();
|
||||
}
|
||||
} else {
|
||||
switchToSession(getStoredCurrentSessionOrLast());
|
||||
Intent i = getIntent();
|
||||
if (i != null && Intent.ACTION_RUN.equals(i.getAction())) {
|
||||
// Android 7.1 app shortcut from res/xml/shortcuts.xml.
|
||||
addNewSession(false, null);
|
||||
} else {
|
||||
switchToSession(getStoredCurrentSessionOrLast());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -534,16 +528,15 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
||||
@Override
|
||||
public void onTextSet(String text) {
|
||||
sessionToRename.mSessionName = text;
|
||||
mListViewAdapter.notifyDataSetChanged();
|
||||
}
|
||||
}, -1, null, -1, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName name) {
|
||||
if (mTermService != null) {
|
||||
// Respect being stopped from the TermuxService notification action.
|
||||
finish();
|
||||
}
|
||||
// Respect being stopped from the TermuxService notification action.
|
||||
finish();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -647,7 +640,7 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
||||
final int indexOfSession = mTermService.getSessions().indexOf(session);
|
||||
showToast(toToastTitle(session), false);
|
||||
mListViewAdapter.notifyDataSetChanged();
|
||||
final ListView lv = ((ListView) findViewById(R.id.left_drawer_list));
|
||||
final ListView lv = findViewById(R.id.left_drawer_list);
|
||||
lv.setItemChecked(indexOfSession, true);
|
||||
lv.smoothScrollToPosition(indexOfSession);
|
||||
}
|
||||
@@ -661,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_RESET_TERMINAL_ID, Menu.NONE, R.string.reset_terminal);
|
||||
menu.add(Menu.NONE, CONTEXTMENU_KILL_PROCESS_ID, Menu.NONE, getResources().getString(R.string.kill_process, getCurrentTermSession().getPid())).setEnabled(currentSession.isRunning());
|
||||
menu.add(Menu.NONE, CONTEXTMENU_TOGGLE_FULLSCREEN_ID, Menu.NONE, R.string.toggle_fullscreen).setCheckable(true).setChecked(mSettings.isFullScreen());
|
||||
menu.add(Menu.NONE, CONTEXTMENU_STYLING_ID, Menu.NONE, R.string.style_terminal);
|
||||
menu.add(Menu.NONE, CONTEXTMENU_HELP_ID, Menu.NONE, R.string.help);
|
||||
}
|
||||
@@ -795,11 +787,8 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
||||
}
|
||||
}).setNegativeButton(android.R.string.cancel, null).show();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
case CONTEXTMENU_TOGGLE_FULLSCREEN_ID:
|
||||
toggleImmersive();
|
||||
return true;
|
||||
}
|
||||
case CONTEXTMENU_HELP_ID:
|
||||
startActivity(new Intent(this, TermuxHelpActivity.class));
|
||||
return true;
|
||||
@@ -815,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) {
|
||||
mSettings.changeFontSize(this, increase);
|
||||
mTerminalView.setTextSize(mSettings.getFontSize());
|
||||
@@ -839,9 +822,8 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
||||
public TerminalSession getStoredCurrentSessionOrLast() {
|
||||
TerminalSession stored = TermuxPreferences.getCurrentSession(this);
|
||||
if (stored != null) return stored;
|
||||
int numberOfSessions = mTermService.getSessions().size();
|
||||
if (numberOfSessions == 0) return null;
|
||||
return mTermService.getSessions().get(numberOfSessions - 1);
|
||||
List<TerminalSession> sessions = mTermService.getSessions();
|
||||
return sessions.isEmpty() ? null : sessions.get(sessions.size() - 1);
|
||||
}
|
||||
|
||||
/** Show a toast and dismiss the last one if still visible. */
|
||||
|
||||
@@ -39,7 +39,7 @@ public final class TermuxHelpActivity extends Activity {
|
||||
mWebView.setWebViewClient(new WebViewClient() {
|
||||
@Override
|
||||
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
||||
if (url.startsWith("https://termux.com")) {
|
||||
if (url.startsWith("https://wiki.termux.com")) {
|
||||
// Inline help.
|
||||
setContentView(progressLayout);
|
||||
return false;
|
||||
@@ -60,7 +60,7 @@ public final class TermuxHelpActivity extends Activity {
|
||||
setContentView(mWebView);
|
||||
}
|
||||
});
|
||||
mWebView.loadUrl("https://termux.com/help.html");
|
||||
mWebView.loadUrl("https://wiki.termux.com/wiki/Main_Page");
|
||||
}
|
||||
|
||||
@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()}.
|
||||
* <p/>
|
||||
* (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/>
|
||||
* (5.1) If the zip entry encountered is SYMLINKS.txt, go through it and remember all symlinks to setup.
|
||||
* <p/>
|
||||
@@ -184,25 +184,28 @@ final class TermuxInstaller {
|
||||
|
||||
/** Get bootstrap zip url for this systems cpu architecture. */
|
||||
static URL determineZipUrl() throws MalformedURLException {
|
||||
String termuxArch = null;
|
||||
String archName = determineTermuxArchName();
|
||||
return new URL("https://termux.net/bootstrap/bootstrap-" + archName + ".zip");
|
||||
}
|
||||
|
||||
private static String determineTermuxArchName() {
|
||||
// Note that we cannot use System.getProperty("os.arch") since that may give e.g. "aarch64"
|
||||
// while a 64-bit runtime may not be installed (like on the Samsung Galaxy S5 Neo).
|
||||
// Instead we search through the supported abi:s on the device, see:
|
||||
// http://developer.android.com/ndk/guides/abis.html
|
||||
// Note that we search for abi:s in preferred order, and want to avoid installing arm on
|
||||
// an x86 system where arm emulation is available.
|
||||
final String[] androidArchNames = {"arm64-v8a", "x86_64", "x86", "armeabi-v7a"};
|
||||
final String[] termuxArchNames = {"aarch64", "x86_64", "i686", "arm"};
|
||||
|
||||
final List<String> supportedArches = Arrays.asList(Build.SUPPORTED_ABIS);
|
||||
for (int i = 0; i < termuxArchNames.length; i++) {
|
||||
if (supportedArches.contains(androidArchNames[i])) {
|
||||
termuxArch = termuxArchNames[i];
|
||||
break;
|
||||
// Note that we search for abi:s in preferred order (the ordering of the
|
||||
// Build.SUPPORTED_ABIS list) to avoid e.g. installing arm on an x86 system where arm
|
||||
// emulation is available.
|
||||
for (String androidArch : Build.SUPPORTED_ABIS) {
|
||||
switch (androidArch) {
|
||||
case "arm64-v8a": return "aarch64";
|
||||
case "armeabi-v7a": return "arm";
|
||||
case "x86_64": return "x86_64";
|
||||
case "x86": return "i686";
|
||||
}
|
||||
}
|
||||
|
||||
return new URL("https://termux.net/bootstrap/bootstrap-" + termuxArch + ".zip");
|
||||
throw new RuntimeException("Unable to determine arch from Build.SUPPORTED_ABIS = " +
|
||||
Arrays.toString(Build.SUPPORTED_ABIS));
|
||||
}
|
||||
|
||||
/** Delete a folder and all its content or throw. */
|
||||
@@ -254,9 +257,13 @@ final class TermuxInstaller {
|
||||
Os.symlink(moviesDir.getAbsolutePath(), new File(storageDir, "movies").getAbsolutePath());
|
||||
|
||||
final File[] dirs = context.getExternalFilesDirs(null);
|
||||
if (dirs != null && dirs.length >= 2) {
|
||||
final File externalDir = dirs[1];
|
||||
Os.symlink(externalDir.getAbsolutePath(), new File(storageDir, "external").getAbsolutePath());
|
||||
if (dirs != null && dirs.length > 1) {
|
||||
for (int i = 1; i < dirs.length; i++) {
|
||||
File dir = dirs[i];
|
||||
if (dir == null) continue;
|
||||
String symlinkName = "external-" + i;
|
||||
Os.symlink(dir.getAbsolutePath(), new File(storageDir, symlinkName).getAbsolutePath());
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(LOG_TAG, "Error setting up link", e);
|
||||
|
||||
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 static final int MAX_FONTSIZE = 256;
|
||||
|
||||
private static final String FULLSCREEN_KEY = "fullscreen";
|
||||
private static final String SHOW_EXTRA_KEYS_KEY = "show_extra_keys";
|
||||
private static final String FONTSIZE_KEY = "fontsize";
|
||||
private static final String CURRENT_SESSION_KEY = "current_session";
|
||||
private static final String SHOW_WELCOME_DIALOG_KEY = "intro_dialog";
|
||||
|
||||
private boolean mFullScreen;
|
||||
private int mFontSize;
|
||||
|
||||
@AsciiBellBehaviour
|
||||
@@ -57,7 +54,6 @@ final class TermuxPreferences {
|
||||
// to prevent invisible text due to zoom be mistake:
|
||||
MIN_FONTSIZE = (int) (4f * dipInPixels);
|
||||
|
||||
mFullScreen = prefs.getBoolean(FULLSCREEN_KEY, false);
|
||||
mShowExtraKeys = prefs.getBoolean(SHOW_EXTRA_KEYS_KEY, false);
|
||||
|
||||
// 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));
|
||||
}
|
||||
|
||||
boolean isFullScreen() {
|
||||
return mFullScreen;
|
||||
}
|
||||
|
||||
void setFullScreen(Context context, boolean newValue) {
|
||||
mFullScreen = newValue;
|
||||
PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean(FULLSCREEN_KEY, newValue).apply();
|
||||
}
|
||||
|
||||
boolean isShowExtraKeys() {
|
||||
return mShowExtraKeys;
|
||||
}
|
||||
@@ -105,7 +92,7 @@ final class TermuxPreferences {
|
||||
}
|
||||
|
||||
static void storeCurrentSession(Context context, TerminalSession session) {
|
||||
PreferenceManager.getDefaultSharedPreferences(context).edit().putString(TermuxPreferences.CURRENT_SESSION_KEY, session.mHandle).commit();
|
||||
PreferenceManager.getDefaultSharedPreferences(context).edit().putString(TermuxPreferences.CURRENT_SESSION_KEY, session.mHandle).apply();
|
||||
}
|
||||
|
||||
static TerminalSession getCurrentSession(TermuxActivity context) {
|
||||
@@ -117,14 +104,6 @@ final class TermuxPreferences {
|
||||
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) {
|
||||
try {
|
||||
File propsFile = new File(TermuxService.HOME_PATH + "/.termux/termux.properties");
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.termux.app;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
@@ -11,6 +12,8 @@ import android.content.res.Resources;
|
||||
import android.net.Uri;
|
||||
import android.net.wifi.WifiManager;
|
||||
import android.os.Binder;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.PowerManager;
|
||||
import android.util.Log;
|
||||
@@ -22,7 +25,6 @@ import com.termux.terminal.TerminalSession;
|
||||
import com.termux.terminal.TerminalSession.SessionChangedCallback;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -40,6 +42,8 @@ import java.util.List;
|
||||
*/
|
||||
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. */
|
||||
@SuppressLint("SdCardPath")
|
||||
public static final String FILES_PATH = "/data/data/com.termux/files";
|
||||
@@ -48,18 +52,16 @@ public final class TermuxService extends Service implements SessionChangedCallba
|
||||
|
||||
private static final int NOTIFICATION_ID = 1337;
|
||||
|
||||
/** Intent action to stop the service. */
|
||||
private static final String ACTION_STOP_SERVICE = "com.termux.service_stop";
|
||||
/** Intent action to toggle the wake lock, {@link #mWakeLock}, which this service may hold. */
|
||||
private static final String ACTION_LOCK_WAKE = "com.termux.service_toggle_wake_lock";
|
||||
/** Intent action to toggle the wifi lock, {@link #mWifiLock}, which this service may hold. */
|
||||
private static final String ACTION_LOCK_WIFI = "com.termux.service_toggle_wifi_lock";
|
||||
private static final String ACTION_LOCK_WAKE = "com.termux.service_wake_lock";
|
||||
private static final String ACTION_UNLOCK_WAKE = "com.termux.service_wake_unlock";
|
||||
/** Intent action to launch a new terminal session. Executed from TermuxWidgetProvider. */
|
||||
public static final String ACTION_EXECUTE = "com.termux.service_execute";
|
||||
|
||||
public static final String EXTRA_ARGUMENTS = "com.termux.execute.arguments";
|
||||
|
||||
public static final String EXTRA_CURRENT_WORKING_DIRECTORY = "com.termux.execute.cwd";
|
||||
private static final String EXTRA_EXECUTE_IN_BACKGROUND = "com.termux.execute.background";
|
||||
|
||||
/** This service is only bound from inside the same process and never uses IPC. */
|
||||
class LocalBinder extends Binder {
|
||||
@@ -68,6 +70,8 @@ public final class TermuxService extends Service implements SessionChangedCallba
|
||||
|
||||
private final IBinder mBinder = new LocalBinder();
|
||||
|
||||
private final Handler mHandler = new Handler();
|
||||
|
||||
/**
|
||||
* The terminal sessions which this service manages.
|
||||
* <p/>
|
||||
@@ -76,9 +80,12 @@ public final class TermuxService extends Service implements SessionChangedCallba
|
||||
*/
|
||||
final List<TerminalSession> mTerminalSessions = new ArrayList<>();
|
||||
|
||||
final List<BackgroundJob> mBackgroundTasks = new ArrayList<>();
|
||||
|
||||
/** Note that the service may often outlive the activity, so need to clear this reference. */
|
||||
SessionChangedCallback mSessionChangeCallback;
|
||||
|
||||
/** The wake lock and wifi lock are always acquired and released together. */
|
||||
private PowerManager.WakeLock mWakeLock;
|
||||
private WifiManager.WifiLock mWifiLock;
|
||||
|
||||
@@ -99,41 +106,52 @@ public final class TermuxService extends Service implements SessionChangedCallba
|
||||
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
|
||||
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, EmulatorDebug.LOG_TAG);
|
||||
mWakeLock.acquire();
|
||||
} else {
|
||||
mWakeLock.release();
|
||||
mWakeLock = null;
|
||||
}
|
||||
updateNotification();
|
||||
} else if (ACTION_LOCK_WIFI.equals(action)) {
|
||||
if (mWifiLock == null) {
|
||||
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.acquire();
|
||||
} else {
|
||||
|
||||
updateNotification();
|
||||
}
|
||||
} else if (ACTION_UNLOCK_WAKE.equals(action)) {
|
||||
if (mWakeLock != null) {
|
||||
mWakeLock.release();
|
||||
mWakeLock = null;
|
||||
|
||||
mWifiLock.release();
|
||||
mWifiLock = null;
|
||||
|
||||
updateNotification();
|
||||
}
|
||||
updateNotification();
|
||||
} else if (ACTION_EXECUTE.equals(action)) {
|
||||
Uri executableUri = intent.getData();
|
||||
String executablePath = (executableUri == null ? null : executableUri.getPath());
|
||||
|
||||
String[] arguments = (executableUri == null ? null : intent.getStringArrayExtra(EXTRA_ARGUMENTS));
|
||||
String cwd = intent.getStringExtra(EXTRA_CURRENT_WORKING_DIRECTORY);
|
||||
TerminalSession newSession = createTermSession(executablePath, arguments, cwd, false);
|
||||
|
||||
// Transform executable path to session name, e.g. "/bin/do-something.sh" => "do something.sh".
|
||||
if (executablePath != null) {
|
||||
int lastSlash = executablePath.lastIndexOf('/');
|
||||
String name = (lastSlash == -1) ? executablePath : executablePath.substring(lastSlash + 1);
|
||||
name = name.replace('-', ' ');
|
||||
newSession.mSessionName = name;
|
||||
if (intent.getBooleanExtra(EXTRA_EXECUTE_IN_BACKGROUND, false)) {
|
||||
BackgroundJob task = new BackgroundJob(cwd, executablePath, arguments, this);
|
||||
mBackgroundTasks.add(task);
|
||||
updateNotification();
|
||||
} else {
|
||||
TerminalSession newSession = createTermSession(executablePath, arguments, cwd, false);
|
||||
|
||||
// Transform executable path to session name, e.g. "/bin/do-something.sh" => "do something.sh".
|
||||
if (executablePath != null) {
|
||||
int lastSlash = executablePath.lastIndexOf('/');
|
||||
String name = (lastSlash == -1) ? executablePath : executablePath.substring(lastSlash + 1);
|
||||
name = name.replace('-', ' ');
|
||||
newSession.mSessionName = name;
|
||||
}
|
||||
|
||||
// Make the newly created session the current one to be displayed:
|
||||
TermuxPreferences.storeCurrentSession(this, newSession);
|
||||
|
||||
// Launch the main Termux app, which will now show the current session:
|
||||
startActivity(new Intent(this, TermuxActivity.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
|
||||
}
|
||||
|
||||
// Make the newly created session the current one to be displayed:
|
||||
TermuxPreferences.storeCurrentSession(this, newSession);
|
||||
|
||||
// Launch the main Termux app, which will now show to current session:
|
||||
startActivity(new Intent(this, TermuxActivity.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
|
||||
} else if (action != null) {
|
||||
Log.e(EmulatorDebug.LOG_TAG, "Unknown TermuxService action: '" + action + "'");
|
||||
}
|
||||
@@ -150,13 +168,14 @@ public final class TermuxService extends Service implements SessionChangedCallba
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
setupNotificationChannel();
|
||||
startForeground(NOTIFICATION_ID, buildNotification());
|
||||
}
|
||||
|
||||
/** Update the shown foreground service notification after making any changes that affect it. */
|
||||
private void updateNotification() {
|
||||
if (mWakeLock == null && mWifiLock == null && getSessions().isEmpty()) {
|
||||
// Exit if we are updating after the user disabled all locks with no sessions.
|
||||
void updateNotification() {
|
||||
if (mWakeLock == null && mTerminalSessions.isEmpty() && mBackgroundTasks.isEmpty()) {
|
||||
// Exit if we are updating after the user disabled all locks with no sessions or tasks running.
|
||||
stopSelf();
|
||||
} else {
|
||||
((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).notify(NOTIFICATION_ID, buildNotification());
|
||||
@@ -171,18 +190,15 @@ public final class TermuxService extends Service implements SessionChangedCallba
|
||||
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notifyIntent, 0);
|
||||
|
||||
int sessionCount = mTerminalSessions.size();
|
||||
String contentText = sessionCount + " terminal session" + (sessionCount == 1 ? "" : "s");
|
||||
|
||||
boolean wakeLockHeld = mWakeLock != null;
|
||||
boolean wifiLockHeld = mWifiLock != null;
|
||||
if (wakeLockHeld && wifiLockHeld) {
|
||||
contentText += " (wake&wifi lock held)";
|
||||
} else if (wakeLockHeld) {
|
||||
contentText += " (wake lock held)";
|
||||
} else if (wifiLockHeld) {
|
||||
contentText += " (wifi lock held)";
|
||||
int taskCount = mBackgroundTasks.size();
|
||||
String contentText = sessionCount + " session" + (sessionCount == 1 ? "" : "s");
|
||||
if (taskCount > 0) {
|
||||
contentText += ", " + taskCount + " task" + (taskCount == 1 ? "" : "s");
|
||||
}
|
||||
|
||||
final boolean wakeLockHeld = mWakeLock != null;
|
||||
if (wakeLockHeld) contentText += " (wake lock held)";
|
||||
|
||||
Notification.Builder builder = new Notification.Builder(this);
|
||||
builder.setContentTitle(getText(R.string.application_name));
|
||||
builder.setContentText(contentText);
|
||||
@@ -191,8 +207,8 @@ public final class TermuxService extends Service implements SessionChangedCallba
|
||||
builder.setOngoing(true);
|
||||
|
||||
// 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:
|
||||
builder.setPriority((wakeLockHeld || wifiLockHeld) ? Notification.PRIORITY_HIGH : Notification.PRIORITY_MIN);
|
||||
// otherwise use a low priority
|
||||
builder.setPriority((wakeLockHeld) ? Notification.PRIORITY_HIGH : Notification.PRIORITY_LOW);
|
||||
|
||||
// No need to show a timestamp:
|
||||
builder.setShowWhen(false);
|
||||
@@ -200,17 +216,21 @@ public final class TermuxService extends Service implements SessionChangedCallba
|
||||
// Background color for small notification icon:
|
||||
builder.setColor(0xFF000000);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
builder.setChannelId(NOTIFICATION_CHANNEL_ID);
|
||||
}
|
||||
|
||||
Resources res = getResources();
|
||||
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));
|
||||
|
||||
Intent toggleWakeLockIntent = new Intent(this, TermuxService.class).setAction(ACTION_LOCK_WAKE);
|
||||
builder.addAction(android.R.drawable.ic_lock_lock, res.getString(R.string.notification_action_wakelock),
|
||||
PendingIntent.getService(this, 0, toggleWakeLockIntent, 0));
|
||||
|
||||
Intent toggleWifiLockIntent = new Intent(this, TermuxService.class).setAction(ACTION_LOCK_WIFI);
|
||||
builder.addAction(android.R.drawable.ic_lock_lock, res.getString(R.string.notification_action_wifilock),
|
||||
PendingIntent.getService(this, 0, toggleWifiLockIntent, 0));
|
||||
String newWakeAction = wakeLockHeld ? ACTION_UNLOCK_WAKE : ACTION_LOCK_WAKE;
|
||||
Intent toggleWakeLockIntent = new Intent(this, TermuxService.class).setAction(newWakeAction);
|
||||
String actionTitle = res.getString(wakeLockHeld ?
|
||||
R.string.notification_action_wake_unlock :
|
||||
R.string.notification_action_wake_lock);
|
||||
int actionIcon = wakeLockHeld ? android.R.drawable.ic_lock_idle_lock : android.R.drawable.ic_lock_lock;
|
||||
builder.addAction(actionIcon, actionTitle, PendingIntent.getService(this, 0, toggleWakeLockIntent, 0));
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
@@ -224,7 +244,6 @@ public final class TermuxService extends Service implements SessionChangedCallba
|
||||
|
||||
for (int i = 0; i < mTerminalSessions.size(); i++)
|
||||
mTerminalSessions.get(i).finishIfRunning();
|
||||
mTerminalSessions.clear();
|
||||
}
|
||||
|
||||
public List<TerminalSession> getSessions() {
|
||||
@@ -236,53 +255,15 @@ public final class TermuxService extends Service implements SessionChangedCallba
|
||||
|
||||
if (cwd == null) cwd = HOME_PATH;
|
||||
|
||||
final String termEnv = "TERM=xterm-256color";
|
||||
final String homeEnv = "HOME=" + HOME_PATH;
|
||||
final String prefixEnv = "PREFIX=" + PREFIX_PATH;
|
||||
final String androidRootEnv = "ANDROID_ROOT=" + System.getenv("ANDROID_ROOT");
|
||||
final String androidDataEnv = "ANDROID_DATA=" + System.getenv("ANDROID_DATA");
|
||||
// EXTERNAL_STORAGE is needed for /system/bin/am to work on at least
|
||||
// Samsung S7 - see https://plus.google.com/110070148244138185604/posts/gp8Lk3aCGp3.
|
||||
final String externalStorageEnv = "EXTERNAL_STORAGE=" + System.getenv("EXTERNAL_STORAGE");
|
||||
String[] env;
|
||||
if (failSafe) {
|
||||
// Keep the default path so that system binaries can be used in the failsafe session.
|
||||
final String pathEnv = "PATH=" + System.getenv("PATH");
|
||||
env = new String[]{termEnv, homeEnv, prefixEnv, androidRootEnv, androidDataEnv, pathEnv, externalStorageEnv};
|
||||
} else {
|
||||
final String ps1Env = "PS1=$ ";
|
||||
final String ldEnv = "LD_LIBRARY_PATH=" + PREFIX_PATH + "/lib";
|
||||
final String langEnv = "LANG=en_US.UTF-8";
|
||||
final String pathEnv = "PATH=" + PREFIX_PATH + "/bin:" + PREFIX_PATH + "/bin/applets";
|
||||
final String pwdEnv = "PWD=" + cwd;
|
||||
String[] env = BackgroundJob.buildEnvironment(failSafe, cwd);
|
||||
boolean isLoginShell = false;
|
||||
|
||||
env = new String[]{termEnv, homeEnv, prefixEnv, ps1Env, ldEnv, langEnv, pathEnv, pwdEnv, androidRootEnv, androidDataEnv, externalStorageEnv};
|
||||
}
|
||||
|
||||
String shellName;
|
||||
if (executablePath == null) {
|
||||
File shell = new File(HOME_PATH, ".termux/shell");
|
||||
if (shell.exists()) {
|
||||
try {
|
||||
File canonicalFile = shell.getCanonicalFile();
|
||||
if (canonicalFile.isFile() && canonicalFile.canExecute()) {
|
||||
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;
|
||||
}
|
||||
for (String shellBinary : new String[]{"login", "bash", "zsh"}) {
|
||||
File shellFile = new File(PREFIX_PATH + "/bin/" + shellBinary);
|
||||
if (shellFile.canExecute()) {
|
||||
executablePath = shellFile.getAbsolutePath();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -290,23 +271,18 @@ public final class TermuxService extends Service implements SessionChangedCallba
|
||||
// Fall back to system shell as last resort:
|
||||
executablePath = "/system/bin/sh";
|
||||
}
|
||||
|
||||
String[] parts = executablePath.split("/");
|
||||
shellName = "-" + parts[parts.length - 1];
|
||||
} else {
|
||||
int lastSlashIndex = executablePath.lastIndexOf('/');
|
||||
shellName = lastSlashIndex == -1 ? executablePath : executablePath.substring(lastSlashIndex + 1);
|
||||
isLoginShell = true;
|
||||
}
|
||||
|
||||
String[] args;
|
||||
if (arguments == null) {
|
||||
args = new String[]{shellName};
|
||||
} else {
|
||||
args = new String[arguments.length + 1];
|
||||
args[0] = shellName;
|
||||
String[] processArgs = BackgroundJob.setupProcessArgs(executablePath, arguments);
|
||||
executablePath = processArgs[0];
|
||||
int lastSlashIndex = executablePath.lastIndexOf('/');
|
||||
String processName = (isLoginShell ? "-" : "") +
|
||||
(lastSlashIndex == -1 ? executablePath : executablePath.substring(lastSlashIndex + 1));
|
||||
|
||||
System.arraycopy(arguments, 0, args, 1, arguments.length);
|
||||
}
|
||||
String[] args = new String[processArgs.length];
|
||||
args[0] = processName;
|
||||
if (processArgs.length > 1) System.arraycopy(processArgs, 1, args, 1, processArgs.length - 1);
|
||||
|
||||
TerminalSession session = new TerminalSession(executablePath, cwd, args, env, this);
|
||||
mTerminalSessions.add(session);
|
||||
@@ -358,4 +334,26 @@ public final class TermuxService extends Service implements SessionChangedCallba
|
||||
if (mSessionChangeCallback != null) mSessionChangeCallback.onColorsChanged(session);
|
||||
}
|
||||
|
||||
public void onBackgroundJobExited(final BackgroundJob task) {
|
||||
mHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mBackgroundTasks.remove(task);
|
||||
updateNotification();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package com.termux.app;
|
||||
import android.content.Context;
|
||||
import android.media.AudioManager;
|
||||
import android.support.v4.widget.DrawerLayout;
|
||||
import android.util.Log;
|
||||
import android.view.Gravity;
|
||||
import android.view.InputDevice;
|
||||
import android.view.KeyEvent;
|
||||
@@ -13,18 +12,18 @@ import android.view.inputmethod.InputMethodManager;
|
||||
import com.termux.terminal.KeyHandler;
|
||||
import com.termux.terminal.TerminalEmulator;
|
||||
import com.termux.terminal.TerminalSession;
|
||||
import com.termux.view.TerminalKeyListener;
|
||||
import com.termux.view.TerminalViewClient;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public final class TermuxKeyListener implements TerminalKeyListener {
|
||||
public final class TermuxViewClient implements TerminalViewClient {
|
||||
|
||||
final TermuxActivity mActivity;
|
||||
|
||||
/** Keeping track of the special keys acting as Ctrl and Fn for the soft keyboard and other hardware keys. */
|
||||
boolean mVirtualControlKeyDown, mVirtualFnKeyDown;
|
||||
|
||||
public TermuxKeyListener(TermuxActivity activity) {
|
||||
public TermuxViewClient(TermuxActivity activity) {
|
||||
this.mActivity = activity;
|
||||
}
|
||||
|
||||
@@ -62,7 +61,7 @@ public final class TermuxKeyListener implements TerminalKeyListener {
|
||||
if (keyCode == KeyEvent.KEYCODE_ENTER && !currentSession.isRunning()) {
|
||||
mActivity.removeFinishedSession(currentSession);
|
||||
return true;
|
||||
} else if (e.isCtrlPressed() && e.isShiftPressed()) {
|
||||
} else if (e.isCtrlPressed() && e.isAltPressed()) {
|
||||
// Get the unmodified code point:
|
||||
int unicodeChar = e.getUnicodeChar(0);
|
||||
|
||||
@@ -74,8 +73,6 @@ public final class TermuxKeyListener implements TerminalKeyListener {
|
||||
mActivity.getDrawer().openDrawer(Gravity.LEFT);
|
||||
} else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
|
||||
mActivity.getDrawer().closeDrawers();
|
||||
} else if (unicodeChar == 'f'/* full screen */) {
|
||||
mActivity.toggleImmersive();
|
||||
} else if (unicodeChar == 'k'/* keyboard */) {
|
||||
InputMethodManager imm = (InputMethodManager) mActivity.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
|
||||
@@ -161,7 +158,7 @@ public final class TermuxKeyListener implements TerminalKeyListener {
|
||||
resultingKeyCode = KeyEvent.KEYCODE_INSERT;
|
||||
break;
|
||||
case 'h':
|
||||
resultingKeyCode = KeyEvent.KEYCODE_MOVE_HOME;
|
||||
resultingCodePoint = '~';
|
||||
break;
|
||||
|
||||
// Special characters to input.
|
||||
@@ -257,6 +254,11 @@ public final class TermuxKeyListener implements TerminalKeyListener {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onLongPress(MotionEvent event) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Handle dedicated volume buttons as virtual keys if applicable. */
|
||||
private boolean handleVirtualKeys(int keyCode, KeyEvent event, boolean down) {
|
||||
InputDevice inputDevice = event.getDevice();
|
||||
@@ -75,7 +75,7 @@ public class TermuxDocumentsProvider extends DocumentsProvider {
|
||||
row.add(Root.COLUMN_TITLE, applicationName);
|
||||
row.add(Root.COLUMN_MIME_TYPES, ALL_MIME_TYPES);
|
||||
row.add(Root.COLUMN_AVAILABLE_BYTES, BASE_DIR.getFreeSpace());
|
||||
row.add(Root.COLUMN_ICON, R.drawable.ic_launcher);
|
||||
row.add(Root.COLUMN_ICON, R.mipmap.ic_launcher);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -236,7 +236,7 @@ public class TermuxDocumentsProvider extends DocumentsProvider {
|
||||
row.add(Document.COLUMN_MIME_TYPE, mimeType);
|
||||
row.add(Document.COLUMN_LAST_MODIFIED, file.lastModified());
|
||||
row.add(Document.COLUMN_FLAGS, flags);
|
||||
row.add(Document.COLUMN_ICON, R.drawable.ic_launcher);
|
||||
row.add(Document.COLUMN_ICON, R.mipmap.ic_launcher);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
package com.termux.terminal;
|
||||
|
||||
/**
|
||||
* Encodes effects, foreground and background colors into a 32 bit integer, which are stored for each cell in a terminal
|
||||
* row in {@link TerminalRow#mStyle}.
|
||||
* <p/>
|
||||
* The foreground and background colors take 9 bits each, leaving (32-9-9)=14 bits for effect flags. Using 9 for now
|
||||
* (the different CHARACTER_ATTRIBUTE_* bits).
|
||||
*/
|
||||
public final class TextStyle {
|
||||
|
||||
public final static int CHARACTER_ATTRIBUTE_BOLD = 1;
|
||||
public final static int CHARACTER_ATTRIBUTE_ITALIC = 1 << 1;
|
||||
public final static int CHARACTER_ATTRIBUTE_UNDERLINE = 1 << 2;
|
||||
public final static int CHARACTER_ATTRIBUTE_BLINK = 1 << 3;
|
||||
public final static int CHARACTER_ATTRIBUTE_INVERSE = 1 << 4;
|
||||
public final static int CHARACTER_ATTRIBUTE_INVISIBLE = 1 << 5;
|
||||
public final static int CHARACTER_ATTRIBUTE_STRIKETHROUGH = 1 << 6;
|
||||
/**
|
||||
* The selective erase control functions (DECSED and DECSEL) can only erase characters defined as erasable.
|
||||
* <p/>
|
||||
* 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.
|
||||
*/
|
||||
public final static int CHARACTER_ATTRIBUTE_PROTECTED = 1 << 7;
|
||||
/** Dim colors. Also known as faint or half intensity. */
|
||||
public final static int CHARACTER_ATTRIBUTE_DIM = 1 << 8;
|
||||
|
||||
public final static int COLOR_INDEX_FOREGROUND = 256;
|
||||
public final static int COLOR_INDEX_BACKGROUND = 257;
|
||||
public final static int COLOR_INDEX_CURSOR = 258;
|
||||
|
||||
/** The 256 standard color entries and the three special (foreground, background and cursor) ones. */
|
||||
public final static int NUM_INDEXED_COLORS = 259;
|
||||
|
||||
/** Normal foreground and background colors and no effects. */
|
||||
final static int NORMAL = encode(COLOR_INDEX_FOREGROUND, COLOR_INDEX_BACKGROUND, 0);
|
||||
|
||||
static int encode(int foreColor, int backColor, int effect) {
|
||||
return ((effect & 0b111111111) << 18) | ((foreColor & 0b111111111) << 9) | (backColor & 0b111111111);
|
||||
}
|
||||
|
||||
public static int decodeForeColor(int encodedColor) {
|
||||
return (encodedColor >> 9) & 0b111111111;
|
||||
}
|
||||
|
||||
public static int decodeBackColor(int encodedColor) {
|
||||
return encodedColor & 0b111111111;
|
||||
}
|
||||
|
||||
public static int decodeEffect(int encodedColor) {
|
||||
return (encodedColor >> 18) & 0b111111111;
|
||||
}
|
||||
|
||||
}
|
||||
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 695 B |
|
Before Width: | Height: | Size: 786 B |
|
Before Width: | Height: | Size: 597 B |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 779 B |
|
Before Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 983 B |
|
Before Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 3.3 KiB |
17
app/src/main/res/drawable/ic_new_session.xml
Normal file
@@ -0,0 +1,17 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
|
||||
<path
|
||||
android:fillColor="#FFF"
|
||||
android:pathData="M 12, 12
|
||||
m -10.5, 0
|
||||
a 10.5,10.5 0 1,0 21,0
|
||||
a 10.5,10.5 0 1,0 -21,0"/>
|
||||
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM17,13h-4v4h-2v-4L7,13v-2h4L11,7h2v4h4v2z"/>
|
||||
</vector>
|
||||
33
app/src/main/res/drawable/ic_service_notification.xml
Normal file
@@ -0,0 +1,33 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="48dp"
|
||||
android:width="48dp"
|
||||
android:viewportWidth="48"
|
||||
android:viewportHeight="48">
|
||||
<!--
|
||||
https://material.google.com/style/icons.html
|
||||
-->
|
||||
|
||||
<!-- Screen border. -->
|
||||
<path android:fillColor="#00000000"
|
||||
android:strokeColor="#FFF"
|
||||
android:strokeWidth="3"
|
||||
android:pathData="M7,4
|
||||
l34,0
|
||||
q3 0,3 3
|
||||
l0,34
|
||||
q0 3, -3 3
|
||||
l-34,0
|
||||
q-3 0, -3-3
|
||||
l0 -34
|
||||
q0 -3, 3 -3"
|
||||
/>
|
||||
|
||||
<!-- Block cursor. -->
|
||||
<path android:fillColor="#FFF"
|
||||
android:pathData="M14,14
|
||||
l5,0
|
||||
l0,10
|
||||
l-5,0"
|
||||
/>
|
||||
|
||||
</vector>
|
||||
@@ -1,7 +1,8 @@
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
android:orientation="vertical"
|
||||
android:fitsSystemWindows="true">
|
||||
|
||||
<android.support.v4.widget.DrawerLayout
|
||||
android:id="@+id/drawer_layout"
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
android:imeOptions="actionSend|flagNoFullscreen"
|
||||
android:maxLines="1"
|
||||
android:inputType="text"
|
||||
android:singleLine="true"
|
||||
android:textColor="@android:color/white"
|
||||
android:paddingTop="0dp"
|
||||
android:textCursorDrawable="@null"
|
||||
|
||||
BIN
app/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 338 B |
BIN
app/src/main/res/mipmap-hdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 203 B |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 331 B |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 505 B |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 691 B |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
@@ -7,14 +7,9 @@
|
||||
<string name="toggle_soft_keyboard">Keyboard</string>
|
||||
<string name="reset_terminal">Reset</string>
|
||||
<string name="style_terminal">Style</string>
|
||||
<string name="toggle_fullscreen">Fullscreen</string>
|
||||
<string name="share_transcript_title">Terminal transcript</string>
|
||||
<string name="help">Help</string>
|
||||
|
||||
<string name="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_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>
|
||||
@@ -34,10 +29,6 @@
|
||||
<string name="select_url_copied_to_clipboard">URL copied to clipboard</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="confirm_kill_process">Really kill this session?</string>
|
||||
|
||||
@@ -50,8 +41,8 @@
|
||||
<string name="styling_install">Install</string>
|
||||
|
||||
<string name="notification_action_exit">Exit</string>
|
||||
<string name="notification_action_wakelock">Wake</string>
|
||||
<string name="notification_action_wifilock">Wifi</string>
|
||||
<string name="notification_action_wake_lock">Acquire wakelock</string>
|
||||
<string name="notification_action_wake_unlock">Release wakelock</string>
|
||||
|
||||
<string name="file_received_title">Save file in ~/downloads/</string>
|
||||
<string name="file_received_edit_button">Edit</string>
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
<!-- 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">
|
||||
<item name="android:statusBarColor">#000000</item>
|
||||
<item name="android:colorPrimary">#FF000000</item>
|
||||
<item name="android:windowBackground">@android:color/black</item>
|
||||
|
||||
<!-- Seen in buttons on left drawer: -->
|
||||
@@ -13,6 +14,13 @@
|
||||
<!-- Avoid action mode toolbar pushing down terminal content when
|
||||
selecting text on pre-6.0 (non-floating toolbar). -->
|
||||
<item name="android:windowActionModeOverlay">true</item>
|
||||
|
||||
<item name="android:windowTranslucentStatus">true</item>
|
||||
<item name="android:windowTranslucentNavigation">true</item>
|
||||
|
||||
<!-- https://developer.android.com/training/tv/start/start.html#transition-color -->
|
||||
<item name="android:windowAllowReturnTransitionOverlap">true</item>
|
||||
<item name="android:windowAllowEnterTransitionOverlap">true</item>
|
||||
</style>
|
||||
|
||||
<style name="TermuxAlertDialogStyle" parent="@android:style/Theme.Material.Light.Dialog.Alert">
|
||||
|
||||
14
app/src/main/res/xml/shortcuts.xml
Normal file
@@ -0,0 +1,14 @@
|
||||
<shortcuts xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<shortcut
|
||||
android:shortcutId="new_session"
|
||||
android:enabled="true"
|
||||
android:icon="@drawable/ic_new_session"
|
||||
android:shortcutShortLabel="@string/new_session"
|
||||
tools:targetApi="n_mr1">
|
||||
<intent
|
||||
android:action="android.intent.action.RUN"
|
||||
android:targetPackage="com.termux"
|
||||
android:targetClass="com.termux.app.TermuxActivity"/>
|
||||
</shortcut>
|
||||
</shortcuts>
|
||||
20
art/copy-to-other-apps.sh
Executable file
@@ -0,0 +1,20 @@
|
||||
#!/bin/sh
|
||||
|
||||
for DENSITY in mdpi hdpi xhdpi xxhdpi xxxhdpi; do
|
||||
FOLDER=../app/src/main/res/mipmap-$DENSITY
|
||||
|
||||
for FILE in ic_launcher ic_launcher_round; do
|
||||
PNG=$FOLDER/$FILE.png
|
||||
|
||||
# Update other apps:
|
||||
for APP in api boot styling tasker widget; do
|
||||
APPDIR=../../termux-$APP
|
||||
if [ -d $APPDIR ]; then
|
||||
APP_FOLDER=$APPDIR/app/src/main/res/mipmap-$DENSITY
|
||||
mkdir -p $APP_FOLDER
|
||||
cp $PNG $APP_FOLDER/$FILE.png
|
||||
fi
|
||||
done
|
||||
done
|
||||
|
||||
done
|
||||
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
|
||||
20
art/generate-launcher-images.sh
Executable file
@@ -0,0 +1,20 @@
|
||||
#!/bin/sh
|
||||
|
||||
for DENSITY in mdpi hdpi xhdpi xxhdpi xxxhdpi; do
|
||||
case $DENSITY in
|
||||
mdpi) SIZE=48;;
|
||||
hdpi) SIZE=72;;
|
||||
xhdpi) SIZE=96;;
|
||||
xxhdpi) SIZE=144;;
|
||||
xxxhdpi) SIZE=192;;
|
||||
esac
|
||||
|
||||
FOLDER=../app/src/main/res/mipmap-$DENSITY
|
||||
mkdir -p $FOLDER
|
||||
|
||||
for FILE in ic_launcher ic_launcher_round; do
|
||||
PNG=$FOLDER/$FILE.png
|
||||
rsvg-convert -w $SIZE -h $SIZE $FILE.svg > $PNG
|
||||
zopflipng -y $PNG $PNG
|
||||
done
|
||||
done
|
||||
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
|
||||
26
art/ic_launcher.svg
Normal file
@@ -0,0 +1,26 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48">
|
||||
|
||||
<!-- Screen and border. -->
|
||||
<path fill="#000"
|
||||
stroke="#BFCBCD"
|
||||
stroke-width="2"
|
||||
d="M9,6
|
||||
l30,0
|
||||
q3 0,3 3
|
||||
l0,30
|
||||
q0 3, -3 3
|
||||
l-30,0
|
||||
q-3 0, -3-3
|
||||
l0 -30
|
||||
q0 -3, 3 -3"
|
||||
/>
|
||||
|
||||
<!-- Block cursor. -->
|
||||
<path fill="#FFF"
|
||||
d="M14,14
|
||||
l5,0
|
||||
l0,10
|
||||
l-5,0"
|
||||
/>
|
||||
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 512 B |
21
art/ic_launcher_round.svg
Normal file
@@ -0,0 +1,21 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48">
|
||||
|
||||
<!-- Screen and border. -->
|
||||
<path fill="#000"
|
||||
stroke="#BFCBCD"
|
||||
stroke-width="2"
|
||||
d="M 24, 24
|
||||
m -21, 0
|
||||
a 21,21 0 1,0 42,0
|
||||
a 21,21 0 1,0 -42,0"
|
||||
/>
|
||||
|
||||
<!-- Block cursor. -->
|
||||
<path fill="#FFF"
|
||||
d="M15,15
|
||||
l5,0
|
||||
l0,10
|
||||
l-5,0"
|
||||
/>
|
||||
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 429 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 |
11
build.gradle
@@ -1,20 +1,19 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
jcenter()
|
||||
google()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:2.1.2'
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
classpath 'com.android.tools.build:gradle:3.0.1'
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
jcenter()
|
||||
maven {
|
||||
url "https://maven.google.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,4 @@
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
#Fri May 13 01:11:09 CEST 2016
|
||||
org.gradle.jvmargs=-Xmx2048M
|
||||
android.useDeprecatedNdk=true
|
||||
|
||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
5
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,5 @@
|
||||
#Sat Jul 23 17:08:29 CEST 2016
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.3.1-bin.zip
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
26
gradlew
vendored
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
#!/usr/bin/env sh
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
@@ -33,11 +33,11 @@ DEFAULT_JVM_OPTS=""
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn ( ) {
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die ( ) {
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
@@ -154,11 +154,19 @@ if $cygwin ; then
|
||||
esac
|
||||
fi
|
||||
|
||||
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
|
||||
function splitJvmOpts() {
|
||||
JVM_OPTS=("$@")
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
|
||||
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
|
||||
APP_ARGS=$(save "$@")
|
||||
|
||||
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||
cd "$(dirname "$0")"
|
||||
fi
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
|
||||
84
gradlew.bat
vendored
Normal file
@@ -0,0 +1,84 @@
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windows variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
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>
|
||||
@@ -26,9 +26,9 @@ import static android.view.KeyEvent.KEYCODE_F7;
|
||||
import static android.view.KeyEvent.KEYCODE_F8;
|
||||
import static android.view.KeyEvent.KEYCODE_F9;
|
||||
import static android.view.KeyEvent.KEYCODE_FORWARD_DEL;
|
||||
import static android.view.KeyEvent.KEYCODE_HOME;
|
||||
import static android.view.KeyEvent.KEYCODE_INSERT;
|
||||
import static android.view.KeyEvent.KEYCODE_MOVE_END;
|
||||
import static android.view.KeyEvent.KEYCODE_MOVE_HOME;
|
||||
import static android.view.KeyEvent.KEYCODE_NUMPAD_0;
|
||||
import static android.view.KeyEvent.KEYCODE_NUMPAD_1;
|
||||
import static android.view.KeyEvent.KEYCODE_NUMPAD_2;
|
||||
@@ -66,7 +66,7 @@ public final class KeyHandler {
|
||||
// terminfo: http://pubs.opengroup.org/onlinepubs/7990989799/xcurses/terminfo.html
|
||||
// termcap: http://man7.org/linux/man-pages/man5/termcap.5.html
|
||||
TERMCAP_TO_KEYCODE.put("%i", KEYMOD_SHIFT | KEYCODE_DPAD_RIGHT);
|
||||
TERMCAP_TO_KEYCODE.put("#2", KEYMOD_SHIFT | KEYCODE_HOME); // Shifted home
|
||||
TERMCAP_TO_KEYCODE.put("#2", KEYMOD_SHIFT | KEYCODE_MOVE_HOME); // Shifted home
|
||||
TERMCAP_TO_KEYCODE.put("#4", KEYMOD_SHIFT | KEYCODE_DPAD_LEFT);
|
||||
TERMCAP_TO_KEYCODE.put("*7", KEYMOD_SHIFT | KEYCODE_MOVE_END); // Shifted end key
|
||||
|
||||
@@ -98,7 +98,7 @@ public final class KeyHandler {
|
||||
TERMCAP_TO_KEYCODE.put("kb", KEYCODE_DEL); // backspace key
|
||||
|
||||
TERMCAP_TO_KEYCODE.put("kd", KEYCODE_DPAD_DOWN); // terminfo=kcud1, down-arrow key
|
||||
TERMCAP_TO_KEYCODE.put("kh", KEYCODE_HOME);
|
||||
TERMCAP_TO_KEYCODE.put("kh", KEYCODE_MOVE_HOME);
|
||||
TERMCAP_TO_KEYCODE.put("kl", KEYCODE_DPAD_LEFT);
|
||||
TERMCAP_TO_KEYCODE.put("kr", KEYCODE_DPAD_RIGHT);
|
||||
|
||||
@@ -107,7 +107,7 @@ public final class KeyHandler {
|
||||
// t_K3 <kPageUp> keypad page-up key
|
||||
// t_K4 <kEnd> keypad end key
|
||||
// t_K5 <kPageDown> keypad page-down key
|
||||
TERMCAP_TO_KEYCODE.put("K1", KEYCODE_HOME);
|
||||
TERMCAP_TO_KEYCODE.put("K1", KEYCODE_MOVE_HOME);
|
||||
TERMCAP_TO_KEYCODE.put("K3", KEYCODE_PAGE_UP);
|
||||
TERMCAP_TO_KEYCODE.put("K4", KEYCODE_MOVE_END);
|
||||
TERMCAP_TO_KEYCODE.put("K5", KEYCODE_PAGE_DOWN);
|
||||
@@ -162,7 +162,9 @@ public final class KeyHandler {
|
||||
case KEYCODE_DPAD_LEFT:
|
||||
return (keyMode == 0) ? (cursorApp ? "\033OD" : "\033[D") : transformForModifiers("\033[1", keyMode, 'D');
|
||||
|
||||
case KEYCODE_HOME:
|
||||
case KEYCODE_MOVE_HOME:
|
||||
// Note that KEYCODE_HOME is handled by the system and never delivered to applications.
|
||||
// On a Logitech k810 keyboard KEYCODE_MOVE_HOME is sent by FN+LeftArrow.
|
||||
return (keyMode == 0) ? (cursorApp ? "\033OH" : "\033[H") : transformForModifiers("\033[1", keyMode, 'H');
|
||||
case KEYCODE_MOVE_END:
|
||||
return (keyMode == 0) ? (cursorApp ? "\033OF" : "\033[F") : transformForModifiers("\033[1", keyMode, 'F');
|
||||
@@ -217,9 +219,6 @@ public final class KeyHandler {
|
||||
case KEYCODE_FORWARD_DEL:
|
||||
return transformForModifiers("\033[3", keyMode, '~');
|
||||
|
||||
case KEYCODE_NUMPAD_DOT:
|
||||
return keypadApplication ? "\033On" : "\033[3~";
|
||||
|
||||
case KEYCODE_PAGE_UP:
|
||||
return "\033[5~";
|
||||
case KEYCODE_PAGE_DOWN:
|
||||
@@ -249,12 +248,14 @@ public final class KeyHandler {
|
||||
return keypadApplication ? transformForModifiers("\033O", keyMode, 'k') : "+";
|
||||
case KEYCODE_NUMPAD_COMMA:
|
||||
return ",";
|
||||
case KEYCODE_NUMPAD_DOT:
|
||||
return keypadApplication ? "\033On" : ".";
|
||||
case KEYCODE_NUMPAD_SUBTRACT:
|
||||
return keypadApplication ? transformForModifiers("\033O", keyMode, 'm') : "-";
|
||||
case KEYCODE_NUMPAD_DIVIDE:
|
||||
return keypadApplication ? transformForModifiers("\033O", keyMode, 'o') : "/";
|
||||
case KEYCODE_NUMPAD_0:
|
||||
return keypadApplication ? transformForModifiers("\033O", keyMode, 'p') : "1";
|
||||
return keypadApplication ? transformForModifiers("\033O", keyMode, 'p') : "0";
|
||||
case KEYCODE_NUMPAD_1:
|
||||
return keypadApplication ? transformForModifiers("\033O", keyMode, 'q') : "1";
|
||||
case KEYCODE_NUMPAD_2:
|
||||
@@ -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
|
||||
* history.
|
||||
* <p/>
|
||||
* <p>
|
||||
* See {@link #externalToInternalRow(int)} for how to map from logical screen rows to array indices.
|
||||
*/
|
||||
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.
|
||||
* <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>
|
||||
* [ ... ] [ ... ]
|
||||
* [ -mActiveTranscriptRows ] [ mScreenFirstRow - mActiveTranscriptRows ]
|
||||
* [ ... ] [ ... ]
|
||||
* [ 0 (visible screen starts here) ] <-----> [ mScreenFirstRow ]
|
||||
* [ ... ] [ ... ]
|
||||
* [ mScreenRows-1 ] [ mScreenFirstRow + mScreenRows-1 ]
|
||||
* - External coordinate system: -mActiveTranscriptRows to mScreenRows-1, with the screen being 0..mScreenRows-1.
|
||||
* - 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).
|
||||
*
|
||||
* External ↔ Internal:
|
||||
*
|
||||
* [ ... ] [ ... ]
|
||||
* [ -mActiveTranscriptRows ] [ mScreenFirstRow - mActiveTranscriptRows ]
|
||||
* [ ... ] [ ... ]
|
||||
* [ 0 (visible screen starts here) ] ↔ [ mScreenFirstRow ]
|
||||
* [ ... ] [ ... ]
|
||||
* [ mScreenRows-1 ] [ mScreenFirstRow + mScreenRows-1 ]
|
||||
* </pre>
|
||||
*
|
||||
* @param externalRow a row in the external coordinate system.
|
||||
@@ -140,7 +138,7 @@ public final class TerminalBuffer {
|
||||
* @param newRows The number of rows the screen should have.
|
||||
* @param cursor An int[2] containing the (column, row) cursor location.
|
||||
*/
|
||||
public void resize(int newColumns, int newRows, int newTotalRows, int[] cursor, int currentStyle, boolean altScreen) {
|
||||
public void resize(int newColumns, int newRows, int newTotalRows, int[] cursor, long currentStyle, boolean altScreen) {
|
||||
// newRows > mTotalRows should not normally happen since mTotalRows is TRANSCRIPT_ROWS (10000):
|
||||
if (newColumns == mColumns && newRows <= mTotalRows) {
|
||||
// Fast resize where just the rows changed.
|
||||
@@ -237,7 +235,7 @@ public final class TerminalBuffer {
|
||||
}
|
||||
|
||||
int currentOldCol = 0;
|
||||
int styleAtCol = 0;
|
||||
long styleAtCol = 0;
|
||||
for (int i = 0; i < lastNonSpaceIndex; i++) {
|
||||
// Note that looping over java character, not cells.
|
||||
char c = oldLine.mText[i];
|
||||
@@ -321,7 +319,7 @@ public final class TerminalBuffer {
|
||||
* @param bottomMargin One line after the last line that is scrolled.
|
||||
* @param style the style for the newly exposed line.
|
||||
*/
|
||||
public void scrollDownOneLine(int topMargin, int bottomMargin, int style) {
|
||||
public void scrollDownOneLine(int topMargin, int bottomMargin, long style) {
|
||||
if (topMargin > bottomMargin - 1 || topMargin < 0 || bottomMargin > mScreenRows)
|
||||
throw new IllegalArgumentException("topMargin=" + topMargin + ", bottomMargin=" + bottomMargin + ", mScreenRows=" + mScreenRows);
|
||||
|
||||
@@ -374,7 +372,7 @@ public final class TerminalBuffer {
|
||||
* InvalidParemeterException will be thrown. Typically this is called with a "val" argument of 32 to clear a block
|
||||
* of characters.
|
||||
*/
|
||||
public void blockSet(int sx, int sy, int w, int h, int val, int style) {
|
||||
public void blockSet(int sx, int sy, int w, int h, int val, long style) {
|
||||
if (sx < 0 || sx + w > mColumns || sy < 0 || sy + h > mScreenRows) {
|
||||
throw new IllegalArgumentException(
|
||||
"Illegal arguments! blockSet(" + sx + ", " + sy + ", " + w + ", " + h + ", " + val + ", " + mColumns + ", " + mScreenRows + ")");
|
||||
@@ -388,14 +386,14 @@ public final class TerminalBuffer {
|
||||
return (mLines[row] == null) ? (mLines[row] = new TerminalRow(mColumns, 0)) : mLines[row];
|
||||
}
|
||||
|
||||
public void setChar(int column, int row, int codePoint, int style) {
|
||||
public void setChar(int column, int row, int codePoint, long style) {
|
||||
if (row >= mScreenRows || column >= mColumns)
|
||||
throw new IllegalArgumentException("row=" + row + ", column=" + column + ", mScreenRows=" + mScreenRows + ", mColumns=" + mColumns);
|
||||
row = externalToInternalRow(row);
|
||||
allocateFullLineIfNecessary(row).setChar(column, codePoint, style);
|
||||
}
|
||||
|
||||
public int getStyleAt(int externalRow, int column) {
|
||||
public long getStyleAt(int externalRow, int column) {
|
||||
return allocateFullLineIfNecessary(externalToInternalRow(externalRow)).getStyle(column);
|
||||
}
|
||||
|
||||
@@ -407,7 +405,7 @@ public final class TerminalBuffer {
|
||||
int startOfLine = (rectangular || y == top) ? left : leftMargin;
|
||||
int endOfLine = (rectangular || y + 1 == bottom) ? right : rightMargin;
|
||||
for (int x = startOfLine; x < endOfLine; x++) {
|
||||
int currentStyle = line.getStyle(x);
|
||||
long currentStyle = line.getStyle(x);
|
||||
int foreColor = TextStyle.decodeForeColor(currentStyle);
|
||||
int backColor = TextStyle.decodeBackColor(currentStyle);
|
||||
int effect = TextStyle.decodeEffect(currentStyle);
|
||||
@@ -57,7 +57,7 @@ public final class TerminalColorScheme {
|
||||
0xff808080, 0xff8a8a8a, 0xff949494, 0xff9e9e9e, 0xffa8a8a8, 0xffb2b2b2, 0xffbcbcbc, 0xffc6c6c6, 0xffd0d0d0, 0xffdadada, 0xffe4e4e4, 0xffeeeeee,
|
||||
|
||||
// COLOR_INDEX_DEFAULT_FOREGROUND, COLOR_INDEX_DEFAULT_BACKGROUND and COLOR_INDEX_DEFAULT_CURSOR:
|
||||
0xffffffff, 0xff000000, 0xffffffff};
|
||||
0xffffffff, 0xff000000, 0xffA9AAA9};
|
||||
|
||||
public final int[] mDefaultColors = new int[TextStyle.NUM_INDEXED_COLORS];
|
||||
|
||||
@@ -12,7 +12,7 @@ import java.util.Stack;
|
||||
/**
|
||||
* Renders text into a screen. Contains all the terminal-specific knowledge and state. Emulates a subset of the X Window
|
||||
* System xterm terminal, which in turn is an emulator for a subset of the Digital Equipment Corporation vt100 terminal.
|
||||
* <p/>
|
||||
* <p>
|
||||
* References:
|
||||
* <ul>
|
||||
* <li>http://invisible-island.net/xterm/ctlseqs/ctlseqs.html</li>
|
||||
@@ -56,8 +56,6 @@ public final class TerminalEmulator {
|
||||
private static final int ESC_SELECT_LEFT_PAREN = 3;
|
||||
/** Escape processing: Have seen ESC and a character-set-select ) char */
|
||||
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). */
|
||||
private static final int ESC_CSI = 6;
|
||||
/** Escape processing: ESC [ ? */
|
||||
@@ -145,7 +143,7 @@ public final class TerminalEmulator {
|
||||
/**
|
||||
* The alternate screen buffer, exactly as large as the display and contains no additional saved lines (so that when
|
||||
* the alternate screen buffer is active, you cannot scroll back to view saved lines).
|
||||
* <p/>
|
||||
* <p>
|
||||
* See http://www.xfree86.org/current/ctlseqs.html#The%20Alternate%20Screen%20Buffer
|
||||
*/
|
||||
final TerminalBuffer mAltBuffer;
|
||||
@@ -206,10 +204,15 @@ public final class TerminalEmulator {
|
||||
*/
|
||||
private boolean mAboutToAutoWrap;
|
||||
|
||||
/** Foreground and background color indices, 0..255. */
|
||||
/**
|
||||
* Current foreground and background colors. Can either be a color index in [0,259] or a truecolor (24-bit) value.
|
||||
* For a 24-bit value the top byte (0xff000000) is set.
|
||||
*
|
||||
* @see TextStyle
|
||||
*/
|
||||
int mForeColor, mBackColor;
|
||||
|
||||
/** Current TextStyle effect */
|
||||
/** Current {@link TextStyle} effect. */
|
||||
private int mEffect;
|
||||
|
||||
/**
|
||||
@@ -220,6 +223,7 @@ public final class TerminalEmulator {
|
||||
|
||||
private byte mUtf8ToFollow, mUtf8Index;
|
||||
private final byte[] mUtf8InputBuffer = new byte[4];
|
||||
private int mLastEmittedCodePoint = -1;
|
||||
|
||||
public final TerminalColors mColors = new TerminalColors();
|
||||
|
||||
@@ -297,6 +301,11 @@ public final class TerminalEmulator {
|
||||
* @param mouseButton one of the MOUSE_* constants of this class.
|
||||
*/
|
||||
public void sendMouseEvent(int mouseButton, int column, int row, boolean pressed) {
|
||||
if (column < 1) column = 1;
|
||||
if (column > mColumns) column = mColumns;
|
||||
if (row < 1) row = 1;
|
||||
if (row > mRows) row = mRows;
|
||||
|
||||
if (mouseButton == MOUSE_LEFT_BUTTON_MOVED && !isDecsetInternalBitSet(DECSET_BIT_MOUSE_TRACKING_BUTTON_EVENT)) {
|
||||
// Do not send tracking.
|
||||
} else if (isDecsetInternalBitSet(DECSET_BIT_MOUSE_PROTOCOL_SGR)) {
|
||||
@@ -304,7 +313,7 @@ public final class TerminalEmulator {
|
||||
} else {
|
||||
mouseButton = pressed ? mouseButton : 3; // 3 for release of all buttons.
|
||||
// Clip to screen, and clip to the limits of 8-bit data.
|
||||
boolean out_of_bounds = column < 1 || row < 1 || column > mColumns || row > mRows || column > 255 - 32 || row > 255 - 32;
|
||||
boolean out_of_bounds = column > 255 - 32 || row > 255 - 32;
|
||||
if (!out_of_bounds) {
|
||||
byte[] data = {'\033', '[', 'M', (byte) (32 + mouseButton), (byte) (32 + column), (byte) (32 + row)};
|
||||
mSession.write(data, 0, data.length);
|
||||
@@ -416,10 +425,11 @@ public final class TerminalEmulator {
|
||||
mUtf8Index = mUtf8ToFollow = 0;
|
||||
|
||||
if (codePoint >= 0x80 && codePoint <= 0x9F) {
|
||||
// Sequence decoded to a C1 control character which is the same as escape followed by
|
||||
// ((code & 0x7F) + 0x40).
|
||||
processCodePoint(/* escape (hexadecimal=0x1B, octal=033): */27);
|
||||
processCodePoint((codePoint & 0x7F) + 0x40);
|
||||
// Sequence decoded to a C1 control character which we ignore. They are
|
||||
// not used nowadays and increases the risk of messing up the terminal state
|
||||
// on binary input. XTerm does not allow them in utf-8:
|
||||
// "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 {
|
||||
switch (Character.getType(codePoint)) {
|
||||
case Character.UNASSIGNED:
|
||||
@@ -611,7 +621,7 @@ public final class TerminalEmulator {
|
||||
int left = Math.min(getArg(argIndex++, 1, true) + effectiveLeftMargin, effectiveRightMargin + 1);
|
||||
int bottom = Math.min(getArg(argIndex++, mRows, true) + effectiveTopMargin, effectiveBottomMargin);
|
||||
int right = Math.min(getArg(argIndex, mColumns, true) + effectiveLeftMargin, effectiveRightMargin);
|
||||
int style = getStyle();
|
||||
long style = getStyle();
|
||||
for (int row = top - 1; row < bottom; row++)
|
||||
for (int col = left - 1; col < right; col++)
|
||||
if (!selective || (TextStyle.decodeEffect(mScreen.getStyleAt(row, col)) & TextStyle.CHARACTER_ATTRIBUTE_PROTECTED) == 0)
|
||||
@@ -623,12 +633,13 @@ public final class TerminalEmulator {
|
||||
case 't': // "${CSI}${TOP}${LEFT}${BOTTOM}${RIGHT}${ATTRIBUTES}$t"
|
||||
// Reverse attributes in rectangular area (DECRARA - http://www.vt100.net/docs/vt510-rm/DECRARA).
|
||||
boolean reverse = b == 't';
|
||||
// FIXME: "coordinates of the rectangular area are affected by the setting of origin mode (DECOM)".s
|
||||
// FIXME: "coordinates of the rectangular area are affected by the setting of origin mode (DECOM)".
|
||||
int top = Math.min(getArg(0, 1, true) - 1, effectiveBottomMargin) + effectiveTopMargin;
|
||||
int left = Math.min(getArg(1, 1, true) - 1, effectiveRightMargin) + effectiveLeftMargin;
|
||||
int bottom = Math.min(getArg(2, mRows, true) + 1, effectiveBottomMargin - 1) + effectiveTopMargin;
|
||||
int right = Math.min(getArg(3, mColumns, true) + 1, effectiveRightMargin - 1) + effectiveLeftMargin;
|
||||
if (mArgIndex >= 4) {
|
||||
if (mArgIndex >= mArgs.length) mArgIndex = mArgs.length - 1;
|
||||
for (int i = 4; i <= mArgIndex; i++) {
|
||||
int bits = 0;
|
||||
boolean setOrClear = true; // True if setting, false if clearing.
|
||||
@@ -717,7 +728,6 @@ public final class TerminalEmulator {
|
||||
}
|
||||
break;
|
||||
case ESC_PERCENT:
|
||||
Log.i(EmulatorDebug.LOG_TAG, "Ignoring character set sequence 'ESC % " + (char) b + "'");
|
||||
break;
|
||||
case ESC_OSC:
|
||||
doOsc(b);
|
||||
@@ -953,7 +963,7 @@ public final class TerminalEmulator {
|
||||
unknownSequence(b);
|
||||
break;
|
||||
}
|
||||
int style = getStyle();
|
||||
long style = getStyle();
|
||||
for (int row = startRow; row < endRow; row++) {
|
||||
for (int col = startCol; col < endCol; col++) {
|
||||
if ((TextStyle.decodeEffect(mScreen.getStyleAt(row, col)) & TextStyle.CHARACTER_ATTRIBUTE_PROTECTED) == 0)
|
||||
@@ -963,6 +973,7 @@ public final class TerminalEmulator {
|
||||
break;
|
||||
case 'h':
|
||||
case 'l':
|
||||
if (mArgIndex >= mArgs.length) mArgIndex = mArgs.length - 1;
|
||||
for (int i = 0; i <= mArgIndex; i++)
|
||||
doDecSetOrReset(b == 'h', mArgs[i]);
|
||||
break;
|
||||
@@ -979,6 +990,7 @@ public final class TerminalEmulator {
|
||||
break;
|
||||
case 'r':
|
||||
case 's':
|
||||
if (mArgIndex >= mArgs.length) mArgIndex = mArgs.length - 1;
|
||||
for (int i = 0; i <= mArgIndex; i++) {
|
||||
int externalBit = mArgs[i];
|
||||
int internalBit = mapDecSetBitToInternalBit(externalBit);
|
||||
@@ -1185,12 +1197,20 @@ public final class TerminalEmulator {
|
||||
}
|
||||
|
||||
private void doLinefeed() {
|
||||
boolean belowScrollingRegion = mCursorRow >= mBottomMargin;
|
||||
int newCursorRow = mCursorRow + 1;
|
||||
if (newCursorRow >= mBottomMargin) {
|
||||
scrollDownOneLine();
|
||||
newCursorRow = mBottomMargin - 1;
|
||||
if (belowScrollingRegion) {
|
||||
// Move down (but not scroll) as long as we are above the last row.
|
||||
if (mCursorRow != mRows - 1) {
|
||||
setCursorRow(newCursorRow);
|
||||
}
|
||||
} else {
|
||||
if (newCursorRow == mBottomMargin) {
|
||||
scrollDownOneLine();
|
||||
newCursorRow = mBottomMargin - 1;
|
||||
}
|
||||
setCursorRow(newCursorRow);
|
||||
}
|
||||
setCursorRow(newCursorRow);
|
||||
}
|
||||
|
||||
private void continueSequence(int state) {
|
||||
@@ -1305,6 +1325,8 @@ public final class TerminalEmulator {
|
||||
state.mSavedCursorRow = mCursorRow;
|
||||
state.mSavedCursorCol = mCursorCol;
|
||||
state.mSavedEffect = mEffect;
|
||||
state.mSavedForeColor = mForeColor;
|
||||
state.mSavedBackColor = mBackColor;
|
||||
state.mSavedDecFlags = mCurrentDecSetFlags;
|
||||
state.mUseLineDrawingG0 = mUseLineDrawingG0;
|
||||
state.mUseLineDrawingG1 = mUseLineDrawingG1;
|
||||
@@ -1316,6 +1338,8 @@ public final class TerminalEmulator {
|
||||
SavedScreenState state = (mScreen == mMainBuffer) ? mSavedStateMain : mSavedStateAlt;
|
||||
setCursorRowCol(state.mSavedCursorRow, state.mSavedCursorCol);
|
||||
mEffect = state.mSavedEffect;
|
||||
mForeColor = state.mSavedForeColor;
|
||||
mBackColor = state.mSavedBackColor;
|
||||
int mask = (DECSET_BIT_AUTOWRAP | DECSET_BIT_ORIGIN_MODE);
|
||||
mCurrentDecSetFlags = (mCurrentDecSetFlags & ~mask) | (state.mSavedDecFlags & mask);
|
||||
mUseLineDrawingG0 = state.mUseLineDrawingG0;
|
||||
@@ -1499,6 +1523,11 @@ public final class TerminalEmulator {
|
||||
case '`': // Horizontal position absolute (HPA - http://www.vt100.net/docs/vt510-rm/HPA).
|
||||
setCursorColRespectingOriginMode(getArg0(1) - 1);
|
||||
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.
|
||||
// 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".
|
||||
@@ -1555,7 +1584,7 @@ public final class TerminalEmulator {
|
||||
break;
|
||||
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 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
|
||||
@@ -1564,6 +1593,7 @@ public final class TerminalEmulator {
|
||||
// Also require that top + 2 <= bottom.
|
||||
mTopMargin = Math.max(0, Math.min(getArg0(1) - 1, mRows - 2));
|
||||
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.
|
||||
setCursorPosition(0, 0);
|
||||
}
|
||||
@@ -1637,6 +1667,7 @@ public final class TerminalEmulator {
|
||||
|
||||
/** Select Graphic Rendition (SGR) - see http://en.wikipedia.org/wiki/ANSI_escape_code#graphics. */
|
||||
private void selectGraphicRendition() {
|
||||
if (mArgIndex >= mArgs.length) mArgIndex = mArgs.length - 1;
|
||||
for (int i = 0; i <= mArgIndex; i++) {
|
||||
int code = mArgs[i];
|
||||
if (code < 0) {
|
||||
@@ -1687,43 +1718,42 @@ public final class TerminalEmulator {
|
||||
} else if (code >= 30 && code <= 37) {
|
||||
mForeColor = code - 30;
|
||||
} else if (code == 38 || code == 48) {
|
||||
// ISO-8613-3 controls to set foreground (38) or background (48) colors.
|
||||
// P_s = (38|48) ; 2 ; P_r ; P_g ; P_b => Set to RGB value in range (0-255).
|
||||
// P_s = (38|48) ; 5 ; P_s => Set to indexed color.
|
||||
if (i + 2 <= mArgIndex) {
|
||||
int color = -1;
|
||||
int firstArg = mArgs[i + 1];
|
||||
if (firstArg == 2) {
|
||||
if (i + 4 > mArgIndex) {
|
||||
Log.w(EmulatorDebug.LOG_TAG, "Too few CSI" + code + ";2 RGB arguments");
|
||||
} else {
|
||||
int red = mArgs[i + 2], green = mArgs[i + 3], blue = mArgs[i + 4];
|
||||
if (red < 0 || green < 0 || blue < 0 || red > 255 || green > 255 || blue > 255) {
|
||||
finishSequenceAndLogError("Invalid RGB: " + red + "," + green + "," + blue);
|
||||
} else {
|
||||
// TODO: Implement 24 bit color.
|
||||
finishSequenceAndLogError("Unimplemented RGB: " + red + "," + green + "," + blue);
|
||||
}
|
||||
i += 4; // "2;P_r;P_g;P_r"
|
||||
}
|
||||
} else if (firstArg == 5) {
|
||||
color = mArgs[i + 2];
|
||||
i += 2; // "5;P_s"
|
||||
// Extended set foreground(38)/background (48) color.
|
||||
// This is followed by either "2;$R;$G;$B" to set a 24-bit color or
|
||||
// "5;$INDEX" to set an indexed color.
|
||||
if (i + 2 > mArgIndex) continue;
|
||||
int firstArg = mArgs[i + 1];
|
||||
if (firstArg == 2) {
|
||||
if (i + 4 > mArgIndex) {
|
||||
Log.w(EmulatorDebug.LOG_TAG, "Too few CSI" + code + ";2 RGB arguments");
|
||||
} else {
|
||||
finishSequenceAndLogError("Invalid ISO-8613-3 SGR first argument: " + firstArg);
|
||||
}
|
||||
if (i != -1) {
|
||||
if (color >= 0 && color < TextStyle.NUM_INDEXED_COLORS) {
|
||||
if (code == 38) {
|
||||
mForeColor = color;
|
||||
} else {
|
||||
mBackColor = color;
|
||||
}
|
||||
int red = mArgs[i + 2], green = mArgs[i + 3], blue = mArgs[i + 4];
|
||||
if (red < 0 || green < 0 || blue < 0 || red > 255 || green > 255 || blue > 255) {
|
||||
finishSequenceAndLogError("Invalid RGB: " + red + "," + green + "," + blue);
|
||||
} else {
|
||||
if (LOG_ESCAPE_SEQUENCES)
|
||||
Log.w(EmulatorDebug.LOG_TAG, "Invalid color index: " + color);
|
||||
int argbColor = 0xff000000 | (red << 16) | (green << 8) | blue;
|
||||
if (code == 38) {
|
||||
mForeColor = argbColor;
|
||||
} else {
|
||||
mBackColor = argbColor;
|
||||
}
|
||||
}
|
||||
i += 4; // "2;P_r;P_g;P_r"
|
||||
}
|
||||
} else if (firstArg == 5) {
|
||||
int color = mArgs[i + 2];
|
||||
i += 2; // "5;P_s"
|
||||
if (color >= 0 && color < TextStyle.NUM_INDEXED_COLORS) {
|
||||
if (code == 38) {
|
||||
mForeColor = color;
|
||||
} else {
|
||||
mBackColor = color;
|
||||
}
|
||||
} else {
|
||||
if (LOG_ESCAPE_SEQUENCES) Log.w(EmulatorDebug.LOG_TAG, "Invalid color index: " + color);
|
||||
}
|
||||
} else {
|
||||
finishSequenceAndLogError("Invalid ISO-8613-3 SGR first argument: " + firstArg);
|
||||
}
|
||||
} else if (code == 39) { // Set default foreground color.
|
||||
mForeColor = TextStyle.COLOR_INDEX_FOREGROUND;
|
||||
@@ -1924,7 +1954,7 @@ public final class TerminalEmulator {
|
||||
mScreen.blockSet(sx, sy, w, h, ' ', getStyle());
|
||||
}
|
||||
|
||||
private int getStyle() {
|
||||
private long getStyle() {
|
||||
return TextStyle.encode(mForeColor, mBackColor, mEffect);
|
||||
}
|
||||
|
||||
@@ -2048,6 +2078,7 @@ public final class TerminalEmulator {
|
||||
buf.append(", escapeState=");
|
||||
buf.append(mEscapeState);
|
||||
boolean firstArg = true;
|
||||
if (mArgIndex >= mArgs.length) mArgIndex = mArgs.length - 1;
|
||||
for (int i = 0; i <= mArgIndex; i++) {
|
||||
int value = mArgs[i];
|
||||
if (value >= 0) {
|
||||
@@ -2080,6 +2111,7 @@ public final class TerminalEmulator {
|
||||
* @param codePoint The code point of the character to display
|
||||
*/
|
||||
private void emitCodePoint(int codePoint) {
|
||||
mLastEmittedCodePoint = codePoint;
|
||||
if (mUseLineDrawingUsesG0 ? mUseLineDrawingG0 : mUseLineDrawingG1) {
|
||||
// http://www.vt100.net/docs/vt102-ug/table5-15.html.
|
||||
switch (codePoint) {
|
||||
@@ -2262,8 +2294,8 @@ public final class TerminalEmulator {
|
||||
mBottomMargin = mRows;
|
||||
mRightMargin = mColumns;
|
||||
mAboutToAutoWrap = false;
|
||||
mForeColor = TextStyle.COLOR_INDEX_FOREGROUND;
|
||||
mBackColor = TextStyle.COLOR_INDEX_BACKGROUND;
|
||||
mForeColor = mSavedStateMain.mSavedForeColor = mSavedStateAlt.mSavedForeColor = TextStyle.COLOR_INDEX_FOREGROUND;
|
||||
mBackColor = mSavedStateMain.mSavedBackColor = mSavedStateAlt.mSavedBackColor = TextStyle.COLOR_INDEX_BACKGROUND;
|
||||
setDefaultTabStops();
|
||||
|
||||
mUseLineDrawingG0 = mUseLineDrawingG1 = false;
|
||||
@@ -2317,7 +2349,7 @@ public final class TerminalEmulator {
|
||||
static final class SavedScreenState {
|
||||
/** Saved state of the cursor position, Used to implement the save/restore cursor position escape sequences. */
|
||||
int mSavedCursorRow, mSavedCursorCol;
|
||||
int mSavedEffect;
|
||||
int mSavedEffect, mSavedForeColor, mSavedBackColor;
|
||||
int mSavedDecFlags;
|
||||
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.
|
||||
* <p/>
|
||||
* <p>
|
||||
* The text in the row is stored in a char[] array, {@link #mText}, for quick access during rendering.
|
||||
*/
|
||||
public final class TerminalRow {
|
||||
@@ -20,13 +20,13 @@ public final class TerminalRow {
|
||||
/** If this row has been line wrapped due to text output at the end of line. */
|
||||
boolean mLineWrap;
|
||||
/** The style bits of each cell in the row. See {@link TextStyle}. */
|
||||
final int[] mStyle;
|
||||
final long[] mStyle;
|
||||
|
||||
/** Construct a blank row (containing only whitespace, ' ') with a specified style. */
|
||||
public TerminalRow(int columns, int style) {
|
||||
public TerminalRow(int columns, long style) {
|
||||
mColumns = columns;
|
||||
mText = new char[(int) (SPARE_CAPACITY_FACTOR * columns)];
|
||||
mStyle = new int[columns];
|
||||
mStyle = new long[columns];
|
||||
clear(style);
|
||||
}
|
||||
|
||||
@@ -112,14 +112,14 @@ public final class TerminalRow {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void clear(int style) {
|
||||
public void clear(long style) {
|
||||
Arrays.fill(mText, ' ');
|
||||
Arrays.fill(mStyle, style);
|
||||
mSpaceUsed = (short) mColumns;
|
||||
}
|
||||
|
||||
// https://github.com/steven676/Android-Terminal-Emulator/commit/9a47042620bec87617f0b4f5d50568535668fe26
|
||||
public void setChar(int columnToSet, int codePoint, int style) {
|
||||
public void setChar(int columnToSet, int codePoint, long style) {
|
||||
mStyle[columnToSet] = style;
|
||||
|
||||
final int newCodePointDisplayWidth = WcWidth.width(codePoint);
|
||||
@@ -225,7 +225,7 @@ public final class TerminalRow {
|
||||
return true;
|
||||
}
|
||||
|
||||
public final int getStyle(int column) {
|
||||
public final long getStyle(int column) {
|
||||
return mStyle[column];
|
||||
}
|
||||
|
||||
@@ -19,13 +19,13 @@ import java.util.UUID;
|
||||
|
||||
/**
|
||||
* 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
|
||||
* {@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.
|
||||
* <p/>
|
||||
* <p>
|
||||
* 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!
|
||||
*/
|
||||
public final class TerminalSession extends TerminalOutput {
|
||||
@@ -123,12 +123,12 @@ public final class TerminalSession extends TerminalOutput {
|
||||
String exitDescription = "\r\n[Process completed";
|
||||
if (exitCode > 0) {
|
||||
// Non-zero process exit.
|
||||
exitDescription += " with code " + exitCode;
|
||||
exitDescription += " (code " + exitCode + ")";
|
||||
} else if (exitCode < 0) {
|
||||
// Negated signal.
|
||||
exitDescription += " with signal " + (-exitCode);
|
||||
exitDescription += " (signal " + (-exitCode) + ")";
|
||||
}
|
||||
exitDescription += " - press Enter to close]";
|
||||
exitDescription += " - press Enter]";
|
||||
|
||||
byte[] bytesToWrite = exitDescription.getBytes(StandardCharsets.UTF_8);
|
||||
mEmulator.append(bytesToWrite, bytesToWrite.length);
|
||||
@@ -173,7 +173,7 @@ public final class TerminalSession extends TerminalOutput {
|
||||
* @param rows The number of rows in the terminal window.
|
||||
*/
|
||||
public void initializeEmulator(int columns, int rows) {
|
||||
mEmulator = new TerminalEmulator(this, columns, rows, /* transcript= */5000);
|
||||
mEmulator = new TerminalEmulator(this, columns, rows, /* transcript= */2000);
|
||||
|
||||
int[] processId = new int[1];
|
||||
mTerminalFileDescriptor = JNI.createSubprocess(mShellPath, mCwd, mArgs, mEnv, processId, rows, columns);
|
||||
@@ -0,0 +1,90 @@
|
||||
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
|
||||
* row in {@link TerminalRow#mStyle}.
|
||||
* </p>
|
||||
* <p>
|
||||
* The bit layout is:
|
||||
* </p>
|
||||
* - 16 flags (11 currently used).
|
||||
* - 24 for foreground color (only 9 first bits if a color index).
|
||||
* - 24 for background color (only 9 first bits if a color index).
|
||||
*/
|
||||
public final class TextStyle {
|
||||
|
||||
public final static int CHARACTER_ATTRIBUTE_BOLD = 1;
|
||||
public final static int CHARACTER_ATTRIBUTE_ITALIC = 1 << 1;
|
||||
public final static int CHARACTER_ATTRIBUTE_UNDERLINE = 1 << 2;
|
||||
public final static int CHARACTER_ATTRIBUTE_BLINK = 1 << 3;
|
||||
public final static int CHARACTER_ATTRIBUTE_INVERSE = 1 << 4;
|
||||
public final static int CHARACTER_ATTRIBUTE_INVISIBLE = 1 << 5;
|
||||
public final static int CHARACTER_ATTRIBUTE_STRIKETHROUGH = 1 << 6;
|
||||
/**
|
||||
* The selective erase control functions (DECSED and DECSEL) can only erase characters defined as erasable.
|
||||
* <p>
|
||||
* 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.
|
||||
* </p>
|
||||
*/
|
||||
public final static int CHARACTER_ATTRIBUTE_PROTECTED = 1 << 7;
|
||||
/** Dim colors. Also known as faint or half intensity. */
|
||||
public final static int CHARACTER_ATTRIBUTE_DIM = 1 << 8;
|
||||
/** If true (24-bit) color is used for the cell for foreground. */
|
||||
private final static int CHARACTER_ATTRIBUTE_TRUECOLOR_FOREGROUND = 1 << 9;
|
||||
/** If true (24-bit) color is used for the cell for foreground. */
|
||||
private final static int CHARACTER_ATTRIBUTE_TRUECOLOR_BACKGROUND= 1 << 10;
|
||||
|
||||
public final static int COLOR_INDEX_FOREGROUND = 256;
|
||||
public final static int COLOR_INDEX_BACKGROUND = 257;
|
||||
public final static int COLOR_INDEX_CURSOR = 258;
|
||||
|
||||
/** The 256 standard color entries and the three special (foreground, background and cursor) ones. */
|
||||
public final static int NUM_INDEXED_COLORS = 259;
|
||||
|
||||
/** Normal foreground and background colors and no effects. */
|
||||
final static long NORMAL = encode(COLOR_INDEX_FOREGROUND, COLOR_INDEX_BACKGROUND, 0);
|
||||
|
||||
static long encode(int foreColor, int backColor, int effect) {
|
||||
long result = effect & 0b111111111;
|
||||
if ((0xff000000 & foreColor) == 0xff000000) {
|
||||
// 24-bit color.
|
||||
result |= CHARACTER_ATTRIBUTE_TRUECOLOR_FOREGROUND | ((foreColor & 0x00ffffffL) << 40L);
|
||||
} else {
|
||||
// Indexed color.
|
||||
result |= (foreColor & 0b111111111L) << 40;
|
||||
}
|
||||
if ((0xff000000 & backColor) == 0xff000000) {
|
||||
// 24-bit color.
|
||||
result |= CHARACTER_ATTRIBUTE_TRUECOLOR_BACKGROUND | ((backColor & 0x00ffffffL) << 16L);
|
||||
} else {
|
||||
// Indexed color.
|
||||
result |= (backColor & 0b111111111L) << 16L;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static int decodeForeColor(long style) {
|
||||
if ((style & CHARACTER_ATTRIBUTE_TRUECOLOR_FOREGROUND) == 0) {
|
||||
return (int) ((style >>> 40) & 0b111111111L);
|
||||
} else {
|
||||
return 0xff000000 | (int) ((style >>> 40) & 0x00ffffffL);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static int decodeBackColor(long style) {
|
||||
if ((style & CHARACTER_ATTRIBUTE_TRUECOLOR_BACKGROUND) == 0) {
|
||||
return (int) ((style >>> 16) & 0b111111111L);
|
||||
} else {
|
||||
return 0xff000000 | (int) ((style >>> 16) & 0x00ffffffL);
|
||||
}
|
||||
}
|
||||
|
||||
public static int decodeEffect(long style) {
|
||||
return (int) (style & 0b11111111111);
|
||||
}
|
||||
|
||||
}
|
||||
5
terminal-emulator/src/main/jni/Android.mk
Normal file
@@ -0,0 +1,5 @@
|
||||
LOCAL_PATH:= $(call my-dir)
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE:= libtermux
|
||||
LOCAL_SRC_FILES:= termux.c
|
||||
include $(BUILD_SHARED_LIBRARY)
|
||||
@@ -29,4 +29,21 @@ public class ControlSequenceIntroducerTest extends TerminalTestCase {
|
||||
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", " ");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,7 +18,7 @@ public class CursorAndScreenTest extends TerminalTestCase {
|
||||
assertLinesAre("ABCDE", "FGHIJ", "KLMNO", "PQRST", "UVWXY");
|
||||
for (int row = 0; row < 5; row++) {
|
||||
for (int col = 0; col < 5; col++) {
|
||||
int s = getStyleAt(row, col);
|
||||
long s = getStyleAt(row, col);
|
||||
Assert.assertEquals(col, TextStyle.decodeForeColor(s));
|
||||
Assert.assertEquals(row, TextStyle.decodeBackColor(s));
|
||||
}
|
||||
@@ -28,7 +28,7 @@ public class CursorAndScreenTest extends TerminalTestCase {
|
||||
assertLinesAre("KLMNO", "PQRST", "UVWXY", " ", " ");
|
||||
for (int row = 0; row < 3; row++) {
|
||||
for (int col = 0; col < 5; col++) {
|
||||
int s = getStyleAt(row, col);
|
||||
long s = getStyleAt(row, col);
|
||||
Assert.assertEquals(col, TextStyle.decodeForeColor(s));
|
||||
Assert.assertEquals(row + 2, TextStyle.decodeBackColor(s));
|
||||
}
|
||||
@@ -43,7 +43,7 @@ public class CursorAndScreenTest extends TerminalTestCase {
|
||||
for (int col = 0; col < 5; col++) {
|
||||
int wantedForeground = (row == 1 || row == 2) ? 98 : col;
|
||||
int wantedBackground = (row == 1 || row == 2) ? 99 : (row == 0 ? 2 : row);
|
||||
int s = getStyleAt(row, col);
|
||||
long s = getStyleAt(row, col);
|
||||
Assert.assertEquals(wantedForeground, TextStyle.decodeForeColor(s));
|
||||
Assert.assertEquals(wantedBackground, TextStyle.decodeBackColor(s));
|
||||
}
|
||||
@@ -227,4 +227,44 @@ public class CursorAndScreenTest extends TerminalTestCase {
|
||||
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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -141,10 +141,10 @@ public class KeyHandlerTest extends TestCase {
|
||||
assertKeysEquals("\033[1;6D", KeyHandler.getCode(KeyEvent.KEYCODE_DPAD_LEFT, mod, false, false));
|
||||
|
||||
// Home/end keys:
|
||||
assertKeysEquals("\033[H", KeyHandler.getCode(KeyEvent.KEYCODE_HOME, 0, false, false));
|
||||
assertKeysEquals("\033[H", KeyHandler.getCode(KeyEvent.KEYCODE_MOVE_HOME, 0, false, false));
|
||||
assertKeysEquals("\033[F", KeyHandler.getCode(KeyEvent.KEYCODE_MOVE_END, 0, false, false));
|
||||
// ... shifted:
|
||||
assertKeysEquals("\033[1;2H", KeyHandler.getCode(KeyEvent.KEYCODE_HOME, KeyHandler.KEYMOD_SHIFT, false, false));
|
||||
assertKeysEquals("\033[1;2H", KeyHandler.getCode(KeyEvent.KEYCODE_MOVE_HOME, KeyHandler.KEYMOD_SHIFT, false, false));
|
||||
assertKeysEquals("\033[1;2F", KeyHandler.getCode(KeyEvent.KEYCODE_MOVE_END, KeyHandler.KEYMOD_SHIFT, false, false));
|
||||
|
||||
// Function keys F1-F12:
|
||||
@@ -173,5 +173,19 @@ public class KeyHandlerTest extends TestCase {
|
||||
assertKeysEquals("\033[21;2~", KeyHandler.getCode(KeyEvent.KEYCODE_F10, KeyHandler.KEYMOD_SHIFT, false, false));
|
||||
assertKeysEquals("\033[23;2~", KeyHandler.getCode(KeyEvent.KEYCODE_F11, KeyHandler.KEYMOD_SHIFT, false, false));
|
||||
assertKeysEquals("\033[24;2~", KeyHandler.getCode(KeyEvent.KEYCODE_F12, KeyHandler.KEYMOD_SHIFT, false, false));
|
||||
}
|
||||
|
||||
assertKeysEquals("0", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_0, 0, false, false));
|
||||
assertKeysEquals("1", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_1, 0, false, false));
|
||||
assertKeysEquals("2", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_2, 0, false, false));
|
||||
assertKeysEquals("3", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_3, 0, false, false));
|
||||
assertKeysEquals("4", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_4, 0, false, false));
|
||||
assertKeysEquals("5", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_5, 0, false, false));
|
||||
assertKeysEquals("6", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_6, 0, false, false));
|
||||
assertKeysEquals("7", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_7, 0, false, false));
|
||||
assertKeysEquals("8", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_8, 0, false, false));
|
||||
assertKeysEquals("9", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_9, 0, false, false));
|
||||
assertKeysEquals(",", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_COMMA, 0, false, false));
|
||||
assertKeysEquals(".", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_DOT, 0, false, false));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -93,7 +93,7 @@ public class ResizeTest extends TerminalTestCase {
|
||||
enterString("\033[2J");
|
||||
for (int r = 0; r < rows; r++) {
|
||||
for (int c = 0; c < cols; c++) {
|
||||
int style = getStyleAt(r, c);
|
||||
long style = getStyleAt(r, c);
|
||||
assertEquals(119, TextStyle.decodeForeColor(style));
|
||||
assertEquals(129, TextStyle.decodeBackColor(style));
|
||||
}
|
||||
@@ -105,7 +105,7 @@ public class ResizeTest extends TerminalTestCase {
|
||||
// After resize, screen should still be same color:
|
||||
for (int r = 0; r < rows - 2; r++) {
|
||||
for (int c = 0; c < cols; c++) {
|
||||
int style = getStyleAt(r, c);
|
||||
long style = getStyleAt(r, c);
|
||||
assertEquals(119, TextStyle.decodeForeColor(style));
|
||||
assertEquals(129, TextStyle.decodeBackColor(style));
|
||||
}
|
||||
@@ -116,7 +116,7 @@ public class ResizeTest extends TerminalTestCase {
|
||||
resize(cols, rows);
|
||||
for (int r = 0; r < rows; r++) {
|
||||
for (int c = 0; c < cols; c++) {
|
||||
int style = getStyleAt(r, c);
|
||||
long style = getStyleAt(r, c);
|
||||
assertEquals(119, TextStyle.decodeForeColor(style));
|
||||
assertEquals("wrong at row=" + r, r >= 3 ? 200 : 129, TextStyle.decodeBackColor(style));
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.termux.terminal;
|
||||
|
||||
public class ScreenBufferTest extends TerminalTest {
|
||||
public class ScreenBufferTest extends TerminalTestCase {
|
||||
|
||||
public void testBasics() {
|
||||
TerminalBuffer screen = new TerminalBuffer(5, 3, 3);
|
||||
@@ -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 ");
|
||||
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
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
row = new TerminalRow(COLUMNS, TextStyle.NORMAL);
|
||||
}
|
||||
|
||||
@@ -55,6 +55,14 @@ public class TerminalTest extends TerminalTestCase {
|
||||
assertEquals("\033[<0;3;4M", mOutput.getOutputAndClear());
|
||||
mTerminal.sendMouseEvent(TerminalEmulator.MOUSE_LEFT_BUTTON, 3, 4, false);
|
||||
assertEquals("\033[<0;3;4m", mOutput.getOutputAndClear());
|
||||
|
||||
// When the client says that a click is outside (which could happen when pixels are outside
|
||||
// the terminal area, see https://github.com/termux/termux-app/issues/501) the terminal
|
||||
// sends a click at the edge.
|
||||
mTerminal.sendMouseEvent(TerminalEmulator.MOUSE_LEFT_BUTTON, 0, 0, true);
|
||||
assertEquals("\033[<0;1;1M", mOutput.getOutputAndClear());
|
||||
mTerminal.sendMouseEvent(TerminalEmulator.MOUSE_LEFT_BUTTON, 11, 11, false);
|
||||
assertEquals("\033[<0;10;10m", mOutput.getOutputAndClear());
|
||||
}
|
||||
|
||||
public void testNormalization() throws UnsupportedEncodingException {
|
||||
@@ -147,12 +155,11 @@ public class TerminalTest extends TerminalTestCase {
|
||||
enterString("\033[38;5;119m");
|
||||
assertEquals(119, mTerminal.mForeColor);
|
||||
assertEquals(TextStyle.COLOR_INDEX_BACKGROUND, mTerminal.mBackColor);
|
||||
|
||||
enterString("\033[48;5;129m");
|
||||
assertEquals(119, mTerminal.mForeColor);
|
||||
assertEquals(129, mTerminal.mBackColor);
|
||||
|
||||
// Invalid parameter:
|
||||
// Invalid parameter:
|
||||
enterString("\033[48;8;129m");
|
||||
assertEquals(119, mTerminal.mForeColor);
|
||||
assertEquals(129, mTerminal.mBackColor);
|
||||
@@ -161,7 +168,31 @@ public class TerminalTest extends TerminalTestCase {
|
||||
enterString("\033[38;5;178;48;5;179;m");
|
||||
assertEquals(178, mTerminal.mForeColor);
|
||||
assertEquals(179, mTerminal.mBackColor);
|
||||
}
|
||||
|
||||
// 24 bit colors:
|
||||
enterString(("\033[0m")); // Reset fg and bg colors.
|
||||
enterString("\033[38;2;255;127;2m");
|
||||
int expectedForeground = 0xff000000 | (255 << 16) | (127 << 8) | 2;
|
||||
assertEquals(expectedForeground, mTerminal.mForeColor);
|
||||
assertEquals(TextStyle.COLOR_INDEX_BACKGROUND, mTerminal.mBackColor);
|
||||
enterString("\033[48;2;1;2;254m");
|
||||
int expectedBackground = 0xff000000 | (1 << 16) | (2 << 8) | 254;
|
||||
assertEquals(expectedForeground, mTerminal.mForeColor);
|
||||
assertEquals(expectedBackground, mTerminal.mBackColor);
|
||||
|
||||
// 24 bit colors, set fg and bg at once:
|
||||
enterString(("\033[0m")); // Reset fg and bg colors.
|
||||
assertEquals(TextStyle.COLOR_INDEX_FOREGROUND, mTerminal.mForeColor);
|
||||
assertEquals(TextStyle.COLOR_INDEX_BACKGROUND, mTerminal.mBackColor);
|
||||
enterString("\033[38;2;255;127;2;48;2;1;2;254m");
|
||||
assertEquals(expectedForeground, mTerminal.mForeColor);
|
||||
assertEquals(expectedBackground, mTerminal.mBackColor);
|
||||
|
||||
// 24 bit colors, invalid input:
|
||||
enterString("\033[38;2;300;127;2;48;2;1;300;254m");
|
||||
assertEquals(expectedForeground, mTerminal.mForeColor);
|
||||
assertEquals(expectedBackground, mTerminal.mBackColor);
|
||||
}
|
||||
|
||||
public void testBackgroundColorErase() {
|
||||
final int rows = 3;
|
||||
@@ -169,7 +200,7 @@ public class TerminalTest extends TerminalTestCase {
|
||||
withTerminalSized(cols, rows);
|
||||
for (int r = 0; r < rows; r++) {
|
||||
for (int c = 0; c < cols; c++) {
|
||||
int style = getStyleAt(r, c);
|
||||
long style = getStyleAt(r, c);
|
||||
assertEquals(TextStyle.COLOR_INDEX_FOREGROUND, TextStyle.decodeForeColor(style));
|
||||
assertEquals(TextStyle.COLOR_INDEX_BACKGROUND, TextStyle.decodeBackColor(style));
|
||||
}
|
||||
@@ -182,7 +213,7 @@ public class TerminalTest extends TerminalTestCase {
|
||||
enterString("\033[2J");
|
||||
for (int r = 0; r < rows; r++) {
|
||||
for (int c = 0; c < cols; c++) {
|
||||
int style = getStyleAt(r, c);
|
||||
long style = getStyleAt(r, c);
|
||||
assertEquals(119, TextStyle.decodeForeColor(style));
|
||||
assertEquals(129, TextStyle.decodeBackColor(style));
|
||||
}
|
||||
@@ -193,7 +224,7 @@ public class TerminalTest extends TerminalTestCase {
|
||||
enterString("\033[2L");
|
||||
for (int r = 0; r < rows; r++) {
|
||||
for (int c = 0; c < cols; c++) {
|
||||
int style = getStyleAt(r, c);
|
||||
long style = getStyleAt(r, c);
|
||||
assertEquals((r == 0 || r == 1) ? 139 : 129, TextStyle.decodeBackColor(style));
|
||||
}
|
||||
}
|
||||
@@ -103,6 +103,7 @@ public abstract class TerminalTestCase extends TestCase {
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
mOutput = new MockTerminalOutput();
|
||||
}
|
||||
|
||||
@@ -240,7 +241,7 @@ public abstract class TerminalTestCase extends TestCase {
|
||||
}
|
||||
|
||||
/** For testing only. Encoded style according to {@link TextStyle}. */
|
||||
public int getStyleAt(int externalRow, int column) {
|
||||
public long getStyleAt(int externalRow, int column) {
|
||||
return mTerminal.getScreen().getStyleAt(externalRow, column);
|
||||
}
|
||||
|
||||
@@ -296,7 +297,7 @@ public abstract class TerminalTestCase extends TestCase {
|
||||
}
|
||||
|
||||
public void assertForegroundColorAt(int externalRow, int column, int color) {
|
||||
int style = mTerminal.getScreen().mLines[mTerminal.getScreen().externalToInternalRow(externalRow)].getStyle(column);
|
||||
long style = mTerminal.getScreen().mLines[mTerminal.getScreen().externalToInternalRow(externalRow)].getStyle(column);
|
||||
assertEquals(color, TextStyle.decodeForeColor(style));
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ public class TextStyleTest extends TestCase {
|
||||
for (int fx : ALL_EFFECTS) {
|
||||
for (int fg = 0; fg < TextStyle.NUM_INDEXED_COLORS; fg++) {
|
||||
for (int bg = 0; bg < TextStyle.NUM_INDEXED_COLORS; bg++) {
|
||||
int encoded = TextStyle.encode(fg, bg, fx);
|
||||
long encoded = TextStyle.encode(fg, bg, fx);
|
||||
assertEquals(fg, TextStyle.decodeForeColor(encoded));
|
||||
assertEquals(bg, TextStyle.decodeBackColor(encoded));
|
||||
assertEquals(fx, TextStyle.decodeEffect(encoded));
|
||||
@@ -22,7 +22,23 @@ public class TextStyleTest extends TestCase {
|
||||
}
|
||||
}
|
||||
|
||||
public void testEncodingCombinations() {
|
||||
public void testEncoding24Bit() {
|
||||
int[] values = {255, 240, 127, 1, 0};
|
||||
for (int red : values) {
|
||||
for (int green : values) {
|
||||
for (int blue : values) {
|
||||
int argb = 0xFF000000 | (red << 16) | (green << 8) | blue;
|
||||
long encoded = TextStyle.encode(argb, 0, 0);
|
||||
assertEquals(argb, TextStyle.decodeForeColor(encoded));
|
||||
encoded = TextStyle.encode(0, argb, 0);
|
||||
assertEquals(argb, TextStyle.decodeBackColor(encoded));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void testEncodingCombinations() {
|
||||
for (int f1 : ALL_EFFECTS) {
|
||||
for (int f2 : ALL_EFFECTS) {
|
||||
int combined = f1 | f2;
|
||||
@@ -32,13 +48,13 @@ public class TextStyleTest extends TestCase {
|
||||
}
|
||||
|
||||
public void testEncodingStrikeThrough() {
|
||||
int encoded = TextStyle.encode(TextStyle.COLOR_INDEX_FOREGROUND, TextStyle.COLOR_INDEX_BACKGROUND,
|
||||
long encoded = TextStyle.encode(TextStyle.COLOR_INDEX_FOREGROUND, TextStyle.COLOR_INDEX_BACKGROUND,
|
||||
TextStyle.CHARACTER_ATTRIBUTE_STRIKETHROUGH);
|
||||
assertTrue((TextStyle.decodeEffect(encoded) & TextStyle.CHARACTER_ATTRIBUTE_STRIKETHROUGH) != 0);
|
||||
}
|
||||
|
||||
public void testEncodingProtected() {
|
||||
int encoded = TextStyle.encode(TextStyle.COLOR_INDEX_FOREGROUND, TextStyle.COLOR_INDEX_BACKGROUND,
|
||||
long encoded = TextStyle.encode(TextStyle.COLOR_INDEX_FOREGROUND, TextStyle.COLOR_INDEX_BACKGROUND,
|
||||
TextStyle.CHARACTER_ATTRIBUTE_STRIKETHROUGH);
|
||||
assertTrue((TextStyle.decodeEffect(encoded) & TextStyle.CHARACTER_ATTRIBUTE_PROTECTED) == 0);
|
||||
encoded = TextStyle.encode(TextStyle.COLOR_INDEX_FOREGROUND, TextStyle.COLOR_INDEX_BACKGROUND,
|
||||