Compare commits
242 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4080f41cc7 | ||
|
|
b4e2c99d46 | ||
|
|
c5923201a4 | ||
|
|
bafd21bb39 | ||
|
|
0f20fab02c | ||
|
|
c5ae5bb06a | ||
|
|
bbd46a763c | ||
|
|
dc145d65f8 | ||
|
|
ce2d1c0d88 | ||
|
|
f2f7f963e6 | ||
|
|
2e53ef038e | ||
|
|
8c82f43dce | ||
|
|
3a16f461e7 | ||
|
|
594a5dfe25 | ||
|
|
d5e007dbb3 | ||
|
|
f4335d3824 | ||
|
|
90b6f93697 | ||
|
|
17c4a45212 | ||
|
|
6d9ffb6922 | ||
|
|
8472fce8ba | ||
|
|
69d954a583 | ||
|
|
ec1087d56f | ||
|
|
be6a73d862 | ||
|
|
80c81b274d | ||
|
|
cd9dbac548 | ||
|
|
0c837796f0 | ||
|
|
3417e37e8d | ||
|
|
31ba36e0fa | ||
|
|
c444f1fd28 | ||
|
|
9f36ed06b8 | ||
|
|
c2ab5bcd50 | ||
|
|
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 | ||
|
|
c9b49cef58 | ||
|
|
f9c642c672 | ||
|
|
c0a5e5f57a | ||
|
|
dfdc9b37e1 | ||
|
|
dfb22e6050 | ||
|
|
b95d84fe13 |
3
.gitignore
vendored
@@ -6,6 +6,7 @@
|
|||||||
build/
|
build/
|
||||||
*.apk
|
*.apk
|
||||||
*.so
|
*.so
|
||||||
|
.externalNativeBuild
|
||||||
|
|
||||||
# Crashlytics configuations
|
# Crashlytics configuations
|
||||||
com_crashlytics_export_strings.xml
|
com_crashlytics_export_strings.xml
|
||||||
@@ -32,6 +33,8 @@ local.properties
|
|||||||
.idea/scopes/scope_settings.xml
|
.idea/scopes/scope_settings.xml
|
||||||
.idea/vcs.xml
|
.idea/vcs.xml
|
||||||
.idea/dictionaries/
|
.idea/dictionaries/
|
||||||
|
.idea/caches/
|
||||||
|
.idea/codeStyles/
|
||||||
*.iml
|
*.iml
|
||||||
|
|
||||||
# OS-specific files
|
# OS-specific files
|
||||||
|
|||||||
9
.idea/gradle.xml
generated
@@ -10,14 +10,11 @@
|
|||||||
<set>
|
<set>
|
||||||
<option value="$PROJECT_DIR$" />
|
<option value="$PROJECT_DIR$" />
|
||||||
<option value="$PROJECT_DIR$/app" />
|
<option value="$PROJECT_DIR$/app" />
|
||||||
|
<option value="$PROJECT_DIR$/terminal-emulator" />
|
||||||
|
<option value="$PROJECT_DIR$/terminal-view" />
|
||||||
</set>
|
</set>
|
||||||
</option>
|
</option>
|
||||||
<option name="myModules">
|
<option name="resolveModulePerSourceSet" value="false" />
|
||||||
<set>
|
|
||||||
<option value="$PROJECT_DIR$" />
|
|
||||||
<option value="$PROJECT_DIR$/app" />
|
|
||||||
</set>
|
|
||||||
</option>
|
|
||||||
</GradleProjectSettings>
|
</GradleProjectSettings>
|
||||||
</option>
|
</option>
|
||||||
</component>
|
</component>
|
||||||
|
|||||||
15
.travis.yml
@@ -6,16 +6,21 @@ env:
|
|||||||
global:
|
global:
|
||||||
# The next declaration is the encrypted COVERITY_SCAN_TOKEN, created
|
# The next declaration is the encrypted COVERITY_SCAN_TOKEN, created
|
||||||
# via the "travis encrypt" command using the project repo's public key
|
# via the "travis encrypt" command using the project repo's public key
|
||||||
- secure: "ACnFJxw0VusS2lnGXL+epP/CNJmftWS39YcPdgN2EurWw5ZfXSo7vi+zpMB+11IBS3LQyLFFUambi2N9L4lbReZkHVkoVcZFGZlwbXNTAeqT8CABPTcuOyEOZU4bJwqeYU87ztYipENMLNECaZrgWx5odbWLKnSJQw7Zkb4ArCstfXfYk9u8q49ThRxQyGwHW2xKp1an5aa+3Y6IY+ywsSHw6AvXbyFH078Kolxy86caagczcfmKcMi15QYzwAvFggUphvsO3M5PHJMQXuaNlQxDcQRGUEXsK8aZE0dPH5PB97SFjDALZqI7NEpjZAk5htWjX48ssW064LDbjcBg/ZLgDd8R8uhA159NVZgvcnP2czCn6pmggx1sW5MBmcj7i+bJS2ejaMO+KoovWlVvsch742H5QR6rQaNkjDZRsGVLYvJaR1gBLs898UoT1hcHWoqLVR22r2VFo7OWWCRfNRvZuZDR2HIrYRdFvn8P3nWVMkvXwgsOlxWG5sN+yQqW+6lZS7hivsFhtYs4CkRdoZIan3Qvi/CkY8Lg+ESkZ3IJ0NnId8qOWH+8Xl1sqZ7xlsWTd1sYYHlpvkdvqw1HNLP22EpwwKW5Kb5zBEd/qs3o1OO0Tqa0MR6JpgGdHHRk1iZ25+qTfRVP06vO2RXsgAx4SZfO7DyB0QZn8tGNMMI="
|
- secure: "LdajbHNfRlpnqzhX5KY2Vr7KtzU9vXDs1TCNn93J6Dt522f2AaiyUDJvISvz+uslk0WJiS5bB5vGwQmXginxz6Qi6uMgMbjWXulv1vfs6ZviKpUX348DOp1qKPa8WfVNB66F84SwGIfc8cRMAgCFw79l/DFgLErubF8vKo1wZ8Hmvrz//+RJ0BGMa3YRc4VyJhAL0P+0Wc1Q2Im7R9EovAxC5pZXBIMSgr6g5GzLWPisbNLXpMPGsDeYhcenO6XCtCCy+aNxUYM8vcrLDzlVXR5Hy7KEs/MGRTS0Yk13TWUEYa5wBpKelFTszdWYLVn5ANreh/aXRVfHpnW3epotMYguLx1kSvOhWEnc4F+qqv3nle2LpDg9Y9bcLyTTcYnPl9smqEVVjEDu0FoIr1V58xkG4Oc6BPIvLRjlMVU96PXh2HxMLuGsJ/xM+uAFU9oVMbC07xn42Eu5O4NHOHJNOwMWac4/lSKRK8W/7/vWuXj5vhkD9ZsGVpN70UtY5HAfNUGADnTeDblvjgFTNZ2mUN/u0o7Z8ZFURYllZ9YU+Vr2nPf9CAhVBjuwFWx8uRQpAg1aDmc1dVMJijRBeBeU/uWhYqsGp34wkNEl8VGzob4R4QTyI8+T7CndGqKVmbTK/SjqKhjjPpbXIAfOH+JtxvAnNmb8XeQSJ32uK2nexFo="
|
||||||
|
|
||||||
android:
|
android:
|
||||||
components:
|
components:
|
||||||
- platform-tools
|
- platform-tools
|
||||||
- tools
|
- tools
|
||||||
- build-tools-24.0.1
|
- build-tools-27.0.3
|
||||||
- android-24
|
- android-27
|
||||||
- extra-android-m2repository
|
- 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:
|
script:
|
||||||
- ./gradlew testDebugUnitTest
|
- ./gradlew testDebugUnitTest
|
||||||
|
|
||||||
@@ -26,5 +31,5 @@ addons:
|
|||||||
description: "Terminal emulator and Linux environment for Android"
|
description: "Terminal emulator and Linux environment for Android"
|
||||||
notification_email: fredrik@fornwall.net
|
notification_email: fredrik@fornwall.net
|
||||||
build_command_prepend: "./gradlew clean"
|
build_command_prepend: "./gradlew clean"
|
||||||
build_command: "./gradlew assemble"
|
build_command: "./gradlew build"
|
||||||
branch_pattern: coverity_scan
|
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://travis-ci.org/termux/termux-app)
|
||||||
[](https://gitter.im/termux/termux)
|
[](https://gitter.im/termux/termux)
|
||||||
|
|
||||||
|
[Termux](https://termux.com) is an Android terminal app and Linux environment.
|
||||||
Termux is an Android terminal app and Linux environment.
|
|
||||||
|
|
||||||
* [Termux on Google Play Store](https://play.google.com/store/apps/details?id=com.termux)
|
* [Termux on Google Play Store](https://play.google.com/store/apps/details?id=com.termux)
|
||||||
* [Termux on F-Droid](https://f-droid.org/repository/browse/?fdid=com.termux)
|
* [Termux on F-Droid](https://f-droid.org/repository/browse/?fdid=com.termux)
|
||||||
* [termux.com](http://termux.com)
|
* [Termux Facebook](https://facebook.com/termux/)
|
||||||
* [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 Google+ community](http://termux.com/community/)
|
* [Termux Google+ community](http://termux.com/community/)
|
||||||
|
* [Termux Help](http://termux.com/help/)
|
||||||
|
* [Termux Twitter](http://twitter.com/termux/)
|
||||||
|
* [Termux Wiki](https://wiki.termux.com/wiki/)
|
||||||
|
|
||||||
License
|
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)
|
||||||
=======
|
|
||||||
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.
|
|
||||||
|
|
||||||
Terminal resources
|
Terminal resources
|
||||||
==================
|
==================
|
||||||
|
|||||||
@@ -1,26 +1,20 @@
|
|||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 24
|
compileSdkVersion 27
|
||||||
buildToolsVersion "24.0.1"
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile 'com.android.support:support-annotations:24.1.1'
|
implementation 'com.android.support:support-annotations:27.1.1'
|
||||||
compile "com.android.support:support-v4:24.1.1"
|
implementation "com.android.support:support-core-ui:27.1.1"
|
||||||
|
implementation project(":terminal-view")
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "com.termux"
|
applicationId "com.termux"
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 24
|
targetSdkVersion 27
|
||||||
versionCode 37
|
versionCode 62
|
||||||
versionName "0.37"
|
versionName "0.62"
|
||||||
|
|
||||||
ndk {
|
|
||||||
moduleName "libtermux"
|
|
||||||
abiFilters "armeabi-v7a", "arm64-v8a", "x86", "x86_64"
|
|
||||||
cFlags "-std=c11 -Wall -Wextra -Os -fno-stack-protector -nostdlib -Wl,--gc-sections"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
@@ -33,5 +27,5 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
testCompile 'junit:junit:4.12'
|
testImplementation 'junit:junit:4.12'
|
||||||
}
|
}
|
||||||
|
|||||||
10
app/proguard-rules.pro
vendored
@@ -7,11 +7,5 @@
|
|||||||
# For more details, see
|
# For more details, see
|
||||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|
||||||
# Add any project specific keep options here:
|
-renamesourcefileattribute SourceFile
|
||||||
|
-keepattributes SourceFile,LineNumberTable
|
||||||
# If your project uses WebView with JS, uncomment the following
|
|
||||||
# and specify the fully qualified class name to the JavaScript interface
|
|
||||||
# class:
|
|
||||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
|
||||||
# public *;
|
|
||||||
#}
|
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
package com.termux;
|
|
||||||
|
|
||||||
import android.app.Application;
|
|
||||||
import android.test.ApplicationTestCase;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
|
|
||||||
*/
|
|
||||||
public class ApplicationTest extends ApplicationTestCase<Application> {
|
|
||||||
public ApplicationTest() {
|
|
||||||
super(Application.class);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
package="com.termux"
|
package="com.termux"
|
||||||
android:installLocation="internalOnly"
|
android:installLocation="internalOnly"
|
||||||
android:sharedUserId="com.termux"
|
android:sharedUserId="com.termux"
|
||||||
@@ -13,18 +14,25 @@
|
|||||||
<uses-permission android:name="android.permission.VIBRATE" />
|
<uses-permission android:name="android.permission.VIBRATE" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
|
android:extractNativeLibs="true"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:fullBackupContent="@xml/backupscheme"
|
android:fullBackupContent="@xml/backupscheme"
|
||||||
android:icon="@drawable/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:banner="@drawable/banner"
|
android:banner="@drawable/banner"
|
||||||
android:label="@string/application_name"
|
android:label="@string/application_name"
|
||||||
android:theme="@style/Theme.Termux"
|
android:theme="@style/Theme.Termux"
|
||||||
android:supportsRtl="false" >
|
android:supportsRtl="false" >
|
||||||
|
|
||||||
|
<!-- This (or rather, value 2.1 or higher) is needed to make the Samsung Galaxy S8
|
||||||
|
mark the app with "This app is optimized to run in full screen." -->
|
||||||
|
<meta-data android:name="android.max_aspect" android:value="10.0" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="com.termux.app.TermuxActivity"
|
android:name="com.termux.app.TermuxActivity"
|
||||||
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
|
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
|
||||||
android:launchMode="singleTask"
|
android:launchMode="singleTask"
|
||||||
|
android:resizeableActivity="true"
|
||||||
android:windowSoftInputMode="adjustResize|stateAlwaysVisible" >
|
android:windowSoftInputMode="adjustResize|stateAlwaysVisible" >
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
@@ -34,6 +42,7 @@
|
|||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
<meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcuts" />
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
@@ -41,6 +50,7 @@
|
|||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:theme="@android:style/Theme.Material.Light.DarkActionBar"
|
android:theme="@android:style/Theme.Material.Light.DarkActionBar"
|
||||||
android:parentActivityName=".app.TermuxActivity"
|
android:parentActivityName=".app.TermuxActivity"
|
||||||
|
android:resizeableActivity="true"
|
||||||
android:label="@string/application_name" />
|
android:label="@string/application_name" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
@@ -48,6 +58,7 @@
|
|||||||
android:label="@string/application_name"
|
android:label="@string/application_name"
|
||||||
android:taskAffinity="com.termux.filereceiver"
|
android:taskAffinity="com.termux.filereceiver"
|
||||||
android:excludeFromRecents="true"
|
android:excludeFromRecents="true"
|
||||||
|
android:resizeableActivity="true"
|
||||||
android:noHistory="true">
|
android:noHistory="true">
|
||||||
<!-- Accept multiple file types when sending. -->
|
<!-- Accept multiple file types when sending. -->
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
@@ -62,7 +73,7 @@
|
|||||||
<data android:mimeType="video/*" />
|
<data android:mimeType="video/*" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
<!-- Be more restrictive for viewing files, restricting ourselves to text files. -->
|
<!-- Be more restrictive for viewing files, restricting ourselves to text files. -->
|
||||||
<intent-filter>
|
<intent-filter tools:ignore="AppLinkUrlError">
|
||||||
<action android:name="android.intent.action.VIEW"/>
|
<action android:name="android.intent.action.VIEW"/>
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<data android:mimeType="text/*" />
|
<data android:mimeType="text/*" />
|
||||||
@@ -73,6 +84,18 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<activity-alias
|
||||||
|
android:name=".HomeActivity"
|
||||||
|
android:targetActivity="com.termux.app.TermuxActivity">
|
||||||
|
|
||||||
|
<!-- Launch activity automatically on boot on Android Things devices -->
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
|
<category android:name="android.intent.category.IOT_LAUNCHER"/>
|
||||||
|
<category android:name="android.intent.category.DEFAULT"/>
|
||||||
|
</intent-filter>
|
||||||
|
</activity-alias>
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name=".filepicker.TermuxDocumentsProvider"
|
android:name=".filepicker.TermuxDocumentsProvider"
|
||||||
android:authorities="com.termux.documents"
|
android:authorities="com.termux.documents"
|
||||||
@@ -88,6 +111,15 @@
|
|||||||
android:name="com.termux.app.TermuxService"
|
android:name="com.termux.app.TermuxService"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
|
<receiver android:name=".app.TermuxOpenReceiver" />
|
||||||
|
|
||||||
|
<provider android:authorities="com.termux.files"
|
||||||
|
android:readPermission="android.permission.permRead"
|
||||||
|
android:exported="true"
|
||||||
|
android:grantUriPermissions="true"
|
||||||
|
android:name="com.termux.app.TermuxOpenReceiver$ContentProvider" />
|
||||||
|
|
||||||
|
<meta-data android:name="com.sec.android.support.multiwindow" android:value="true" />
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
@@ -4,58 +4,71 @@ import android.util.Log;
|
|||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
import java.nio.charset.StandardCharsets;
|
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.
|
* A background job launched by Termux.
|
||||||
*/
|
*/
|
||||||
public final class BackgroundJob {
|
public final class BackgroundJob {
|
||||||
|
|
||||||
private static final String LOG_TAG = "termux-background";
|
private static final String LOG_TAG = "termux-task";
|
||||||
|
|
||||||
final Process mProcess;
|
final Process mProcess;
|
||||||
|
|
||||||
public BackgroundJob(File cwd, File fileToExecute, String[] args) throws IOException {
|
public BackgroundJob(String cwd, String fileToExecute, final String[] args, final TermuxService service) {
|
||||||
String[] env = buildEnvironment(false, cwd.getAbsolutePath());
|
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);
|
Process process;
|
||||||
|
try {
|
||||||
new Thread() {
|
process = Runtime.getRuntime().exec(progArray, env, new File(cwd));
|
||||||
@Override
|
} catch (IOException e) {
|
||||||
public void run() {
|
mProcess = null;
|
||||||
while (true) {
|
// TODO: Visible error message?
|
||||||
try {
|
Log.e(LOG_TAG, "Failed running background job: " + processDescription, e);
|
||||||
int exitCode = mProcess.waitFor();
|
return;
|
||||||
if (exitCode == 0) {
|
}
|
||||||
Log.i(LOG_TAG, "exited normally");
|
|
||||||
return;
|
mProcess = process;
|
||||||
} else {
|
final int pid = getPid(mProcess);
|
||||||
Log.i(LOG_TAG, "exited with exit code: " + exitCode);
|
|
||||||
}
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
// Ignore.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.start();
|
|
||||||
|
|
||||||
new Thread() {
|
new Thread() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
Log.i(LOG_TAG, "[" + pid + "] starting: " + processDescription);
|
||||||
InputStream stdout = mProcess.getInputStream();
|
InputStream stdout = mProcess.getInputStream();
|
||||||
BufferedReader reader = new BufferedReader(new InputStreamReader(stdout, StandardCharsets.UTF_8));
|
BufferedReader reader = new BufferedReader(new InputStreamReader(stdout, StandardCharsets.UTF_8));
|
||||||
String line;
|
String line;
|
||||||
try {
|
try {
|
||||||
// FIXME: Long lines.
|
// FIXME: Long lines.
|
||||||
while ((line = reader.readLine()) != null) {
|
while ((line = reader.readLine()) != null) {
|
||||||
Log.i(LOG_TAG, line);
|
Log.i(LOG_TAG, "[" + pid + "] stdout: " + line);
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} 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.
|
// Ignore.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,7 +84,7 @@ public final class BackgroundJob {
|
|||||||
try {
|
try {
|
||||||
// FIXME: Long lines.
|
// FIXME: Long lines.
|
||||||
while ((line = reader.readLine()) != null) {
|
while ((line = reader.readLine()) != null) {
|
||||||
Log.e(LOG_TAG, line);
|
Log.i(LOG_TAG, "[" + pid + "] stderr: " + line);
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
// Ignore.
|
// 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();
|
new File(TermuxService.HOME_PATH).mkdirs();
|
||||||
|
|
||||||
if (cwd == null) cwd = TermuxService.HOME_PATH;
|
if (cwd == null) cwd = TermuxService.HOME_PATH;
|
||||||
@@ -93,20 +106,87 @@ public final class BackgroundJob {
|
|||||||
// EXTERNAL_STORAGE is needed for /system/bin/am to work on at least
|
// EXTERNAL_STORAGE is needed for /system/bin/am to work on at least
|
||||||
// Samsung S7 - see https://plus.google.com/110070148244138185604/posts/gp8Lk3aCGp3.
|
// Samsung S7 - see https://plus.google.com/110070148244138185604/posts/gp8Lk3aCGp3.
|
||||||
final String externalStorageEnv = "EXTERNAL_STORAGE=" + System.getenv("EXTERNAL_STORAGE");
|
final String externalStorageEnv = "EXTERNAL_STORAGE=" + System.getenv("EXTERNAL_STORAGE");
|
||||||
String[] env;
|
|
||||||
if (failSafe) {
|
if (failSafe) {
|
||||||
// Keep the default path so that system binaries can be used in the failsafe session.
|
// Keep the default path so that system binaries can be used in the failsafe session.
|
||||||
final String pathEnv = "PATH=" + System.getenv("PATH");
|
final String pathEnv = "PATH=" + System.getenv("PATH");
|
||||||
return new String[]{termEnv, homeEnv, prefixEnv, androidRootEnv, androidDataEnv, pathEnv, externalStorageEnv};
|
return new String[]{termEnv, homeEnv, prefixEnv, androidRootEnv, androidDataEnv, pathEnv, externalStorageEnv};
|
||||||
} else {
|
} else {
|
||||||
final String ps1Env = "PS1=$ ";
|
|
||||||
final String ldEnv = "LD_LIBRARY_PATH=" + TermuxService.PREFIX_PATH + "/lib";
|
final String ldEnv = "LD_LIBRARY_PATH=" + TermuxService.PREFIX_PATH + "/lib";
|
||||||
final String langEnv = "LANG=en_US.UTF-8";
|
final String langEnv = "LANG=en_US.UTF-8";
|
||||||
final String pathEnv = "PATH=" + TermuxService.PREFIX_PATH + "/bin:" + TermuxService.PREFIX_PATH + "/bin/applets";
|
final String pathEnv = "PATH=" + TermuxService.PREFIX_PATH + "/bin:" + TermuxService.PREFIX_PATH + "/bin/applets";
|
||||||
final String pwdEnv = "PWD=" + cwd;
|
final String pwdEnv = "PWD=" + cwd;
|
||||||
|
final String tmpdirEnv = "TMPDIR=" + TermuxService.PREFIX_PATH + "/tmp";
|
||||||
|
|
||||||
return new String[]{termEnv, homeEnv, prefixEnv, ps1Env, ldEnv, langEnv, pathEnv, pwdEnv, androidRootEnv, androidDataEnv, externalStorageEnv};
|
return new String[]{termEnv, homeEnv, prefixEnv, 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()]);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,8 +11,6 @@ import android.widget.EditText;
|
|||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import android.R;
|
|
||||||
|
|
||||||
public final class DialogUtils {
|
public final class DialogUtils {
|
||||||
|
|
||||||
public interface TextSetListener {
|
public interface TextSetListener {
|
||||||
@@ -72,7 +70,7 @@ public final class DialogUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (onNegative == null) {
|
if (onNegative == null) {
|
||||||
builder.setNegativeButton(R.string.cancel, null);
|
builder.setNegativeButton(android.R.string.cancel, null);
|
||||||
} else {
|
} else {
|
||||||
builder.setNegativeButton(negativeButtonText, new DialogInterface.OnClickListener() {
|
builder.setNegativeButton(negativeButtonText, new DialogInterface.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -2,11 +2,19 @@ package com.termux.app;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
|
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
|
||||||
import android.view.Gravity;
|
import android.view.Gravity;
|
||||||
|
import android.view.HapticFeedbackConstants;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
|
import android.view.MotionEvent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.GridLayout;
|
import android.widget.GridLayout;
|
||||||
|
import android.widget.PopupWindow;
|
||||||
import android.widget.ToggleButton;
|
import android.widget.ToggleButton;
|
||||||
|
|
||||||
import com.termux.R;
|
import com.termux.R;
|
||||||
@@ -20,6 +28,8 @@ import com.termux.view.TerminalView;
|
|||||||
public final class ExtraKeysView extends GridLayout {
|
public final class ExtraKeysView extends GridLayout {
|
||||||
|
|
||||||
private static final int TEXT_COLOR = 0xFFFFFFFF;
|
private static final int TEXT_COLOR = 0xFFFFFFFF;
|
||||||
|
private static final int BUTTON_COLOR = 0xFF000000;
|
||||||
|
private static final int BUTTON_PRESSED_COLOR = 0xFF888888;
|
||||||
|
|
||||||
public ExtraKeysView(Context context, AttributeSet attrs) {
|
public ExtraKeysView(Context context, AttributeSet attrs) {
|
||||||
super(context, attrs);
|
super(context, attrs);
|
||||||
@@ -37,16 +47,28 @@ public final class ExtraKeysView extends GridLayout {
|
|||||||
case "TAB":
|
case "TAB":
|
||||||
keyCode = KeyEvent.KEYCODE_TAB;
|
keyCode = KeyEvent.KEYCODE_TAB;
|
||||||
break;
|
break;
|
||||||
case "▲":
|
case "HOME":
|
||||||
|
keyCode = KeyEvent.KEYCODE_MOVE_HOME;
|
||||||
|
break;
|
||||||
|
case "END":
|
||||||
|
keyCode = KeyEvent.KEYCODE_MOVE_END;
|
||||||
|
break;
|
||||||
|
case "PGUP":
|
||||||
|
keyCode = KeyEvent.KEYCODE_PAGE_UP;
|
||||||
|
break;
|
||||||
|
case "PGDN":
|
||||||
|
keyCode = KeyEvent.KEYCODE_PAGE_DOWN;
|
||||||
|
break;
|
||||||
|
case "↑":
|
||||||
keyCode = KeyEvent.KEYCODE_DPAD_UP;
|
keyCode = KeyEvent.KEYCODE_DPAD_UP;
|
||||||
break;
|
break;
|
||||||
case "◀":
|
case "←":
|
||||||
keyCode = KeyEvent.KEYCODE_DPAD_LEFT;
|
keyCode = KeyEvent.KEYCODE_DPAD_LEFT;
|
||||||
break;
|
break;
|
||||||
case "▶":
|
case "→":
|
||||||
keyCode = KeyEvent.KEYCODE_DPAD_RIGHT;
|
keyCode = KeyEvent.KEYCODE_DPAD_RIGHT;
|
||||||
break;
|
break;
|
||||||
case "▼":
|
case "↓":
|
||||||
keyCode = KeyEvent.KEYCODE_DPAD_DOWN;
|
keyCode = KeyEvent.KEYCODE_DPAD_DOWN;
|
||||||
break;
|
break;
|
||||||
case "―":
|
case "―":
|
||||||
@@ -56,11 +78,11 @@ public final class ExtraKeysView extends GridLayout {
|
|||||||
chars = keyName;
|
chars = keyName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TerminalView terminalView = view.findViewById(R.id.terminal_view);
|
||||||
if (keyCode > 0) {
|
if (keyCode > 0) {
|
||||||
view.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, keyCode));
|
terminalView.onKeyDown(keyCode, new KeyEvent(KeyEvent.ACTION_UP, keyCode));
|
||||||
view.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, keyCode));
|
// view.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, keyCode));
|
||||||
} else {
|
} else {
|
||||||
TerminalView terminalView = (TerminalView) view.findViewById(R.id.terminal_view);
|
|
||||||
TerminalSession session = terminalView.getCurrentSession();
|
TerminalSession session = terminalView.getCurrentSession();
|
||||||
if (session != null) session.write(chars);
|
if (session != null) session.write(chars);
|
||||||
}
|
}
|
||||||
@@ -69,6 +91,9 @@ public final class ExtraKeysView extends GridLayout {
|
|||||||
private ToggleButton controlButton;
|
private ToggleButton controlButton;
|
||||||
private ToggleButton altButton;
|
private ToggleButton altButton;
|
||||||
private ToggleButton fnButton;
|
private ToggleButton fnButton;
|
||||||
|
private ScheduledExecutorService scheduledExecutor;
|
||||||
|
private PopupWindow popupWindow;
|
||||||
|
private int longPressCount;
|
||||||
|
|
||||||
public boolean readControlButton() {
|
public boolean readControlButton() {
|
||||||
if (controlButton.isPressed()) return true;
|
if (controlButton.isPressed()) return true;
|
||||||
@@ -100,24 +125,47 @@ public final class ExtraKeysView extends GridLayout {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void popup(View view, String text) {
|
||||||
|
int width = view.getMeasuredWidth();
|
||||||
|
int height = view.getMeasuredHeight();
|
||||||
|
Button button = new Button(getContext(), null, android.R.attr.buttonBarButtonStyle);
|
||||||
|
button.setText(text);
|
||||||
|
button.setTextColor(TEXT_COLOR);
|
||||||
|
button.setPadding(0, 0, 0, 0);
|
||||||
|
button.setMinHeight(0);
|
||||||
|
button.setMinWidth(0);
|
||||||
|
button.setMinimumWidth(0);
|
||||||
|
button.setMinimumHeight(0);
|
||||||
|
button.setWidth(width);
|
||||||
|
button.setHeight(height);
|
||||||
|
button.setBackgroundColor(BUTTON_PRESSED_COLOR);
|
||||||
|
popupWindow = new PopupWindow(this);
|
||||||
|
popupWindow.setWidth(LayoutParams.WRAP_CONTENT);
|
||||||
|
popupWindow.setHeight(LayoutParams.WRAP_CONTENT);
|
||||||
|
popupWindow.setContentView(button);
|
||||||
|
popupWindow.setOutsideTouchable(true);
|
||||||
|
popupWindow.setFocusable(false);
|
||||||
|
popupWindow.showAsDropDown(view, 0, -2 * height);
|
||||||
|
}
|
||||||
|
|
||||||
void reload() {
|
void reload() {
|
||||||
altButton = controlButton = null;
|
altButton = controlButton = null;
|
||||||
removeAllViews();
|
removeAllViews();
|
||||||
|
|
||||||
String[][] buttons = {
|
String[][] buttons = {
|
||||||
{"ESC", "CTRL", "ALT", "TAB", "―", "/", "|"}
|
{"ESC", "/", "―", "HOME", "↑", "END", "PGUP"},
|
||||||
|
{"TAB", "CTRL", "ALT", "←", "↓", "→", "PGDN"}
|
||||||
};
|
};
|
||||||
|
|
||||||
final int rows = buttons.length;
|
final int rows = buttons.length;
|
||||||
final int cols = buttons[0].length;
|
final int[] cols = {buttons[0].length, buttons[1].length};
|
||||||
|
|
||||||
setRowCount(rows);
|
setRowCount(rows);
|
||||||
setColumnCount(cols);
|
setColumnCount(cols[0]);
|
||||||
|
|
||||||
for (int row = 0; row < rows; row++) {
|
for (int row = 0; row < rows; row++) {
|
||||||
for (int col = 0; col < cols; col++) {
|
for (int col = 0; col < cols[row]; col++) {
|
||||||
final String buttonText = buttons[row][col];
|
final String buttonText = buttons[row][col];
|
||||||
|
|
||||||
Button button;
|
Button button;
|
||||||
switch (buttonText) {
|
switch (buttonText) {
|
||||||
case "CTRL":
|
case "CTRL":
|
||||||
@@ -139,11 +187,13 @@ public final class ExtraKeysView extends GridLayout {
|
|||||||
|
|
||||||
button.setText(buttonText);
|
button.setText(buttonText);
|
||||||
button.setTextColor(TEXT_COLOR);
|
button.setTextColor(TEXT_COLOR);
|
||||||
|
button.setPadding(0, 0, 0, 0);
|
||||||
|
|
||||||
final Button finalButton = button;
|
final Button finalButton = button;
|
||||||
button.setOnClickListener(new OnClickListener() {
|
button.setOnClickListener(new OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
|
finalButton.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP);
|
||||||
View root = getRootView();
|
View root = getRootView();
|
||||||
switch (buttonText) {
|
switch (buttonText) {
|
||||||
case "CTRL":
|
case "CTRL":
|
||||||
@@ -160,13 +210,70 @@ public final class ExtraKeysView extends GridLayout {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
GridLayout.LayoutParams param = new GridLayout.LayoutParams();
|
button.setOnTouchListener(new OnTouchListener() {
|
||||||
param.height = param.width = 0;
|
@Override
|
||||||
param.rightMargin = param.topMargin = 0;
|
public boolean onTouch(View v, MotionEvent event) {
|
||||||
|
final View root = getRootView();
|
||||||
|
switch (event.getAction()) {
|
||||||
|
case MotionEvent.ACTION_DOWN:
|
||||||
|
longPressCount = 0;
|
||||||
|
v.setBackgroundColor(BUTTON_PRESSED_COLOR);
|
||||||
|
if ("↑↓←→".contains(buttonText)) {
|
||||||
|
scheduledExecutor = Executors.newSingleThreadScheduledExecutor();
|
||||||
|
scheduledExecutor.scheduleWithFixedDelay(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
longPressCount++;
|
||||||
|
sendKey(root, buttonText);
|
||||||
|
}
|
||||||
|
}, 400, 80, TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
case MotionEvent.ACTION_MOVE:
|
||||||
|
if ("―/".contains(buttonText)) {
|
||||||
|
if (popupWindow == null && event.getY() < 0) {
|
||||||
|
v.setBackgroundColor(BUTTON_COLOR);
|
||||||
|
String text = "―".equals(buttonText) ? "|" : "\\";
|
||||||
|
popup(v, text);
|
||||||
|
}
|
||||||
|
if (popupWindow != null && event.getY() > 0) {
|
||||||
|
v.setBackgroundColor(BUTTON_PRESSED_COLOR);
|
||||||
|
popupWindow.dismiss();
|
||||||
|
popupWindow = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
case MotionEvent.ACTION_UP:
|
||||||
|
case MotionEvent.ACTION_CANCEL:
|
||||||
|
v.setBackgroundColor(BUTTON_COLOR);
|
||||||
|
if (scheduledExecutor != null) {
|
||||||
|
scheduledExecutor.shutdownNow();
|
||||||
|
scheduledExecutor = null;
|
||||||
|
}
|
||||||
|
if (longPressCount == 0) {
|
||||||
|
if (popupWindow != null && "―/".contains(buttonText)) {
|
||||||
|
popupWindow.setContentView(null);
|
||||||
|
popupWindow.dismiss();
|
||||||
|
popupWindow = null;
|
||||||
|
sendKey(root, "―".equals(buttonText) ? "|" : "\\");
|
||||||
|
} else {
|
||||||
|
v.performClick();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
LayoutParams param = new GridLayout.LayoutParams();
|
||||||
|
param.width = param.height = 0;
|
||||||
|
param.setMargins(0, 0, 0, 0);
|
||||||
param.setGravity(Gravity.LEFT);
|
param.setGravity(Gravity.LEFT);
|
||||||
float weight = "▲▼◀▶".contains(buttonText) ? 0.7f : 1.f;
|
param.columnSpec = GridLayout.spec(col, GridLayout.FILL, 1.f);
|
||||||
param.columnSpec = GridLayout.spec(col, weight);
|
param.rowSpec = GridLayout.spec(row, GridLayout.FILL, 1.f);
|
||||||
param.rowSpec = GridLayout.spec(row, 1.f);
|
|
||||||
button.setLayoutParams(param);
|
button.setLayoutParams(param);
|
||||||
|
|
||||||
addView(button);
|
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.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
@@ -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_KILL_PROCESS_ID = 4;
|
||||||
private static final int CONTEXTMENU_RESET_TERMINAL_ID = 5;
|
private static final int CONTEXTMENU_RESET_TERMINAL_ID = 5;
|
||||||
private static final int CONTEXTMENU_STYLING_ID = 6;
|
private static final int CONTEXTMENU_STYLING_ID = 6;
|
||||||
private static final int CONTEXTMENU_TOGGLE_FULLSCREEN_ID = 7;
|
|
||||||
private static final int CONTEXTMENU_HELP_ID = 8;
|
private static final int CONTEXTMENU_HELP_ID = 8;
|
||||||
|
|
||||||
private static final int MAX_SESSIONS = 8;
|
private static final int MAX_SESSIONS = 8;
|
||||||
@@ -111,8 +111,6 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
|
|
||||||
ExtraKeysView mExtraKeysView;
|
ExtraKeysView mExtraKeysView;
|
||||||
|
|
||||||
final FullScreenHelper mFullScreenHelper = new FullScreenHelper(this);
|
|
||||||
|
|
||||||
TermuxPreferences mSettings;
|
TermuxPreferences mSettings;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -157,7 +155,6 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
|
|
||||||
void checkForFontAndColors() {
|
void checkForFontAndColors() {
|
||||||
try {
|
try {
|
||||||
// Hard-coded paths since this file is used also in Termux:Float.
|
|
||||||
@SuppressLint("SdCardPath") File fontFile = new File("/data/data/com.termux/files/home/.termux/font.ttf");
|
@SuppressLint("SdCardPath") File fontFile = new File("/data/data/com.termux/files/home/.termux/font.ttf");
|
||||||
@SuppressLint("SdCardPath") File colorsFile = new File("/data/data/com.termux/files/home/.termux/colors.properties");
|
@SuppressLint("SdCardPath") File colorsFile = new File("/data/data/com.termux/files/home/.termux/colors.properties");
|
||||||
|
|
||||||
@@ -212,14 +209,13 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
mSettings = new TermuxPreferences(this);
|
mSettings = new TermuxPreferences(this);
|
||||||
|
|
||||||
setContentView(R.layout.drawer_layout);
|
setContentView(R.layout.drawer_layout);
|
||||||
mTerminalView = (TerminalView) findViewById(R.id.terminal_view);
|
mTerminalView = findViewById(R.id.terminal_view);
|
||||||
mTerminalView.setOnKeyListener(new TermuxKeyListener(this));
|
mTerminalView.setOnKeyListener(new TermuxViewClient(this));
|
||||||
|
|
||||||
mTerminalView.setTextSize(mSettings.getFontSize());
|
mTerminalView.setTextSize(mSettings.getFontSize());
|
||||||
mFullScreenHelper.setImmersive(mSettings.isFullScreen());
|
|
||||||
mTerminalView.requestFocus();
|
mTerminalView.requestFocus();
|
||||||
|
|
||||||
final ViewPager viewPager = (ViewPager) findViewById(R.id.viewpager);
|
final ViewPager viewPager = findViewById(R.id.viewpager);
|
||||||
if (mSettings.isShowExtraKeys()) viewPager.setVisibility(View.VISIBLE);
|
if (mSettings.isShowExtraKeys()) viewPager.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
viewPager.setAdapter(new PagerAdapter() {
|
viewPager.setAdapter(new PagerAdapter() {
|
||||||
@@ -229,25 +225,34 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isViewFromObject(View view, Object object) {
|
public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
|
||||||
return view == object;
|
return view == object;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public Object instantiateItem(ViewGroup collection, int position) {
|
public Object instantiateItem(@NonNull ViewGroup collection, int position) {
|
||||||
LayoutInflater inflater = LayoutInflater.from(TermuxActivity.this);
|
LayoutInflater inflater = LayoutInflater.from(TermuxActivity.this);
|
||||||
View layout;
|
View layout;
|
||||||
if (position == 0) {
|
if (position == 0) {
|
||||||
layout = mExtraKeysView = (ExtraKeysView) inflater.inflate(R.layout.extra_keys_main, collection, false);
|
layout = mExtraKeysView = (ExtraKeysView) inflater.inflate(R.layout.extra_keys_main, collection, false);
|
||||||
} else {
|
} else {
|
||||||
layout = inflater.inflate(R.layout.extra_keys_right, collection, false);
|
layout = inflater.inflate(R.layout.extra_keys_right, collection, false);
|
||||||
final EditText editText = (EditText) layout.findViewById(R.id.text_input);
|
final EditText editText = layout.findViewById(R.id.text_input);
|
||||||
editText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
|
editText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
|
||||||
@Override
|
@Override
|
||||||
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
||||||
String s = editText.getText().toString() + "\n";
|
TerminalSession session = getCurrentTermSession();
|
||||||
getCurrentTermSession().write(s);
|
if (session != null) {
|
||||||
editText.setText("");
|
if (session.isRunning()) {
|
||||||
|
String textToSend = editText.getText().toString();
|
||||||
|
if (textToSend.length() == 0) textToSend = "\n";
|
||||||
|
session.write(textToSend);
|
||||||
|
} else {
|
||||||
|
removeFinishedSession(session);
|
||||||
|
}
|
||||||
|
editText.setText("");
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -257,7 +262,7 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void destroyItem(ViewGroup collection, int position, Object view) {
|
public void destroyItem(@NonNull ViewGroup collection, int position, @NonNull Object view) {
|
||||||
collection.removeView((View) view);
|
collection.removeView((View) view);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -268,7 +273,7 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
if (position == 0) {
|
if (position == 0) {
|
||||||
mTerminalView.requestFocus();
|
mTerminalView.requestFocus();
|
||||||
} else {
|
} else {
|
||||||
final EditText editText = (EditText) viewPager.findViewById(R.id.text_input);
|
final EditText editText = viewPager.findViewById(R.id.text_input);
|
||||||
if (editText != null) editText.requestFocus();
|
if (editText != null) editText.requestFocus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -332,7 +337,7 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
}
|
}
|
||||||
|
|
||||||
void toggleShowExtraKeys() {
|
void toggleShowExtraKeys() {
|
||||||
final ViewPager viewPager = (ViewPager) findViewById(R.id.viewpager);
|
final ViewPager viewPager = findViewById(R.id.viewpager);
|
||||||
final boolean showNow = mSettings.toggleShowExtraKeys(TermuxActivity.this);
|
final boolean showNow = mSettings.toggleShowExtraKeys(TermuxActivity.this);
|
||||||
viewPager.setVisibility(showNow ? View.VISIBLE : View.GONE);
|
viewPager.setVisibility(showNow ? View.VISIBLE : View.GONE);
|
||||||
if (showNow && viewPager.getCurrentItem() == 1) {
|
if (showNow && viewPager.getCurrentItem() == 1) {
|
||||||
@@ -389,27 +394,26 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
@Override
|
@Override
|
||||||
public void onClipboardText(TerminalSession session, String text) {
|
public void onClipboardText(TerminalSession session, String text) {
|
||||||
if (!mIsVisible) return;
|
if (!mIsVisible) return;
|
||||||
showToast("Clipboard:\n\"" + text + "\"", false);
|
|
||||||
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
|
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
|
||||||
clipboard.setPrimaryClip(new ClipData(null, new String[]{"text/plain"}, new ClipData.Item(text)));
|
clipboard.setPrimaryClip(new ClipData(null, new String[]{"text/plain"}, new ClipData.Item(text)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBell(TerminalSession session) {
|
public void onBell(TerminalSession session) {
|
||||||
if (mIsVisible) {
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
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
|
@Override
|
||||||
@@ -418,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()) {
|
mListViewAdapter = new ArrayAdapter<TerminalSession>(getApplicationContext(), R.layout.line_in_drawer, mTermService.getSessions()) {
|
||||||
final StyleSpan boldSpan = new StyleSpan(Typeface.BOLD);
|
final StyleSpan boldSpan = new StyleSpan(Typeface.BOLD);
|
||||||
final StyleSpan italicSpan = new StyleSpan(Typeface.ITALIC);
|
final StyleSpan italicSpan = new StyleSpan(Typeface.ITALIC);
|
||||||
|
|
||||||
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public View getView(int position, View convertView, ViewGroup parent) {
|
public View getView(int position, View convertView, @NonNull ViewGroup parent) {
|
||||||
View row = convertView;
|
View row = convertView;
|
||||||
if (row == null) {
|
if (row == null) {
|
||||||
LayoutInflater inflater = getLayoutInflater();
|
LayoutInflater inflater = getLayoutInflater();
|
||||||
@@ -434,7 +439,7 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
TerminalSession sessionAtRow = getItem(position);
|
TerminalSession sessionAtRow = getItem(position);
|
||||||
boolean sessionRunning = sessionAtRow.isRunning();
|
boolean sessionRunning = sessionAtRow.isRunning();
|
||||||
|
|
||||||
TextView firstLineView = (TextView) row.findViewById(R.id.row_line);
|
TextView firstLineView = row.findViewById(R.id.row_line);
|
||||||
|
|
||||||
String name = sessionAtRow.mSessionName;
|
String name = sessionAtRow.mSessionName;
|
||||||
String sessionTitle = sessionAtRow.getTitle();
|
String sessionTitle = sessionAtRow.getTitle();
|
||||||
@@ -485,17 +490,6 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
public void run() {
|
public void run() {
|
||||||
if (mTermService == null) return; // Activity might have been destroyed.
|
if (mTermService == null) return; // Activity might have been destroyed.
|
||||||
try {
|
try {
|
||||||
if (TermuxPreferences.isShowWelcomeDialog(TermuxActivity.this)) {
|
|
||||||
new AlertDialog.Builder(TermuxActivity.this).setTitle(R.string.welcome_dialog_title).setMessage(R.string.welcome_dialog_body)
|
|
||||||
.setCancelable(false).setPositiveButton(android.R.string.ok, null)
|
|
||||||
.setNegativeButton(R.string.welcome_dialog_dont_show_again_button, new DialogInterface.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
TermuxPreferences.disableWelcomeDialog(TermuxActivity.this);
|
|
||||||
dialog.dismiss();
|
|
||||||
}
|
|
||||||
}).show();
|
|
||||||
}
|
|
||||||
addNewSession(false, null);
|
addNewSession(false, null);
|
||||||
} catch (WindowManager.BadTokenException e) {
|
} catch (WindowManager.BadTokenException e) {
|
||||||
// Activity finished - ignore.
|
// Activity finished - ignore.
|
||||||
@@ -507,7 +501,13 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
} else {
|
} 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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -528,16 +528,15 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
@Override
|
@Override
|
||||||
public void onTextSet(String text) {
|
public void onTextSet(String text) {
|
||||||
sessionToRename.mSessionName = text;
|
sessionToRename.mSessionName = text;
|
||||||
|
mListViewAdapter.notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
}, -1, null, -1, null, null);
|
}, -1, null, -1, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onServiceDisconnected(ComponentName name) {
|
public void onServiceDisconnected(ComponentName name) {
|
||||||
if (mTermService != null) {
|
// Respect being stopped from the TermuxService notification action.
|
||||||
// Respect being stopped from the TermuxService notification action.
|
finish();
|
||||||
finish();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@@ -641,7 +640,7 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
final int indexOfSession = mTermService.getSessions().indexOf(session);
|
final int indexOfSession = mTermService.getSessions().indexOf(session);
|
||||||
showToast(toToastTitle(session), false);
|
showToast(toToastTitle(session), false);
|
||||||
mListViewAdapter.notifyDataSetChanged();
|
mListViewAdapter.notifyDataSetChanged();
|
||||||
final ListView lv = ((ListView) findViewById(R.id.left_drawer_list));
|
final ListView lv = findViewById(R.id.left_drawer_list);
|
||||||
lv.setItemChecked(indexOfSession, true);
|
lv.setItemChecked(indexOfSession, true);
|
||||||
lv.smoothScrollToPosition(indexOfSession);
|
lv.smoothScrollToPosition(indexOfSession);
|
||||||
}
|
}
|
||||||
@@ -655,7 +654,6 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
menu.add(Menu.NONE, CONTEXTMENU_SHARE_TRANSCRIPT_ID, Menu.NONE, R.string.select_all_and_share);
|
menu.add(Menu.NONE, CONTEXTMENU_SHARE_TRANSCRIPT_ID, Menu.NONE, R.string.select_all_and_share);
|
||||||
menu.add(Menu.NONE, CONTEXTMENU_RESET_TERMINAL_ID, Menu.NONE, R.string.reset_terminal);
|
menu.add(Menu.NONE, CONTEXTMENU_RESET_TERMINAL_ID, Menu.NONE, R.string.reset_terminal);
|
||||||
menu.add(Menu.NONE, CONTEXTMENU_KILL_PROCESS_ID, Menu.NONE, getResources().getString(R.string.kill_process, getCurrentTermSession().getPid())).setEnabled(currentSession.isRunning());
|
menu.add(Menu.NONE, CONTEXTMENU_KILL_PROCESS_ID, Menu.NONE, getResources().getString(R.string.kill_process, getCurrentTermSession().getPid())).setEnabled(currentSession.isRunning());
|
||||||
menu.add(Menu.NONE, CONTEXTMENU_TOGGLE_FULLSCREEN_ID, Menu.NONE, R.string.toggle_fullscreen).setCheckable(true).setChecked(mSettings.isFullScreen());
|
|
||||||
menu.add(Menu.NONE, CONTEXTMENU_STYLING_ID, Menu.NONE, R.string.style_terminal);
|
menu.add(Menu.NONE, CONTEXTMENU_STYLING_ID, Menu.NONE, R.string.style_terminal);
|
||||||
menu.add(Menu.NONE, CONTEXTMENU_HELP_ID, Menu.NONE, R.string.help);
|
menu.add(Menu.NONE, CONTEXTMENU_HELP_ID, Menu.NONE, R.string.help);
|
||||||
}
|
}
|
||||||
@@ -789,11 +787,8 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
}
|
}
|
||||||
}).setNegativeButton(android.R.string.cancel, null).show();
|
}).setNegativeButton(android.R.string.cancel, null).show();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return true;
|
|
||||||
case CONTEXTMENU_TOGGLE_FULLSCREEN_ID:
|
|
||||||
toggleImmersive();
|
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
case CONTEXTMENU_HELP_ID:
|
case CONTEXTMENU_HELP_ID:
|
||||||
startActivity(new Intent(this, TermuxHelpActivity.class));
|
startActivity(new Intent(this, TermuxHelpActivity.class));
|
||||||
return true;
|
return true;
|
||||||
@@ -809,12 +804,6 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void toggleImmersive() {
|
|
||||||
boolean newValue = !mSettings.isFullScreen();
|
|
||||||
mSettings.setFullScreen(this, newValue);
|
|
||||||
mFullScreenHelper.setImmersive(newValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
void changeFontSize(boolean increase) {
|
void changeFontSize(boolean increase) {
|
||||||
mSettings.changeFontSize(this, increase);
|
mSettings.changeFontSize(this, increase);
|
||||||
mTerminalView.setTextSize(mSettings.getFontSize());
|
mTerminalView.setTextSize(mSettings.getFontSize());
|
||||||
@@ -833,9 +822,8 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
public TerminalSession getStoredCurrentSessionOrLast() {
|
public TerminalSession getStoredCurrentSessionOrLast() {
|
||||||
TerminalSession stored = TermuxPreferences.getCurrentSession(this);
|
TerminalSession stored = TermuxPreferences.getCurrentSession(this);
|
||||||
if (stored != null) return stored;
|
if (stored != null) return stored;
|
||||||
int numberOfSessions = mTermService.getSessions().size();
|
List<TerminalSession> sessions = mTermService.getSessions();
|
||||||
if (numberOfSessions == 0) return null;
|
return sessions.isEmpty() ? null : sessions.get(sessions.size() - 1);
|
||||||
return mTermService.getSessions().get(numberOfSessions - 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Show a toast and dismiss the last one if still visible. */
|
/** Show a toast and dismiss the last one if still visible. */
|
||||||
@@ -846,4 +834,21 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
mLastToast.show();
|
mLastToast.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void removeFinishedSession(TerminalSession finishedSession) {
|
||||||
|
// Return pressed with finished session - remove it.
|
||||||
|
TermuxService service = mTermService;
|
||||||
|
|
||||||
|
int index = service.removeTermSession(finishedSession);
|
||||||
|
mListViewAdapter.notifyDataSetChanged();
|
||||||
|
if (mTermService.getSessions().isEmpty()) {
|
||||||
|
// There are no sessions to show, so finish the activity.
|
||||||
|
finish();
|
||||||
|
} else {
|
||||||
|
if (index >= service.getSessions().size()) {
|
||||||
|
index = service.getSessions().size() - 1;
|
||||||
|
}
|
||||||
|
switchToSession(service.getSessions().get(index));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ public final class TermuxHelpActivity extends Activity {
|
|||||||
mWebView.setWebViewClient(new WebViewClient() {
|
mWebView.setWebViewClient(new WebViewClient() {
|
||||||
@Override
|
@Override
|
||||||
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
||||||
if (url.startsWith("https://termux.com")) {
|
if (url.startsWith("https://wiki.termux.com")) {
|
||||||
// Inline help.
|
// Inline help.
|
||||||
setContentView(progressLayout);
|
setContentView(progressLayout);
|
||||||
return false;
|
return false;
|
||||||
@@ -60,7 +60,7 @@ public final class TermuxHelpActivity extends Activity {
|
|||||||
setContentView(mWebView);
|
setContentView(mWebView);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
mWebView.loadUrl("https://termux.com/help.html");
|
mWebView.loadUrl("https://wiki.termux.com/wiki/Main_Page");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ import java.util.zip.ZipInputStream;
|
|||||||
* (4) The architecture is determined and an appropriate bootstrap zip url is determined in {@link #determineZipUrl()}.
|
* (4) The architecture is determined and an appropriate bootstrap zip url is determined in {@link #determineZipUrl()}.
|
||||||
* <p/>
|
* <p/>
|
||||||
* (5) The zip, containing entries relative to the $PREFIX, is is downloaded and extracted by a zip input stream
|
* (5) The zip, containing entries relative to the $PREFIX, is is downloaded and extracted by a zip input stream
|
||||||
* continously encountering zip file entries:
|
* continuously encountering zip file entries:
|
||||||
* <p/>
|
* <p/>
|
||||||
* (5.1) If the zip entry encountered is SYMLINKS.txt, go through it and remember all symlinks to setup.
|
* (5.1) If the zip entry encountered is SYMLINKS.txt, go through it and remember all symlinks to setup.
|
||||||
* <p/>
|
* <p/>
|
||||||
@@ -184,25 +184,28 @@ final class TermuxInstaller {
|
|||||||
|
|
||||||
/** Get bootstrap zip url for this systems cpu architecture. */
|
/** Get bootstrap zip url for this systems cpu architecture. */
|
||||||
static URL determineZipUrl() throws MalformedURLException {
|
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"
|
// 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).
|
// 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:
|
// Instead we search through the supported abi:s on the device, see:
|
||||||
// http://developer.android.com/ndk/guides/abis.html
|
// 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
|
// Note that we search for abi:s in preferred order (the ordering of the
|
||||||
// an x86 system where arm emulation is available.
|
// Build.SUPPORTED_ABIS list) to avoid e.g. installing arm on an x86 system where arm
|
||||||
final String[] androidArchNames = {"arm64-v8a", "x86_64", "x86", "armeabi-v7a"};
|
// emulation is available.
|
||||||
final String[] termuxArchNames = {"aarch64", "x86_64", "i686", "arm"};
|
for (String androidArch : Build.SUPPORTED_ABIS) {
|
||||||
|
switch (androidArch) {
|
||||||
final List<String> supportedArches = Arrays.asList(Build.SUPPORTED_ABIS);
|
case "arm64-v8a": return "aarch64";
|
||||||
for (int i = 0; i < termuxArchNames.length; i++) {
|
case "armeabi-v7a": return "arm";
|
||||||
if (supportedArches.contains(androidArchNames[i])) {
|
case "x86_64": return "x86_64";
|
||||||
termuxArch = termuxArchNames[i];
|
case "x86": return "i686";
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
throw new RuntimeException("Unable to determine arch from Build.SUPPORTED_ABIS = " +
|
||||||
return new URL("https://termux.net/bootstrap/bootstrap-" + termuxArch + ".zip");
|
Arrays.toString(Build.SUPPORTED_ABIS));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Delete a folder and all its content or throw. */
|
/** Delete a folder and all its content or throw. */
|
||||||
@@ -225,9 +228,13 @@ final class TermuxInstaller {
|
|||||||
try {
|
try {
|
||||||
File storageDir = new File(TermuxService.HOME_PATH, "storage");
|
File storageDir = new File(TermuxService.HOME_PATH, "storage");
|
||||||
|
|
||||||
if (storageDir.exists() && !storageDir.delete()) {
|
if (storageDir.exists()) {
|
||||||
Log.e(LOG_TAG, "Could not delete old $HOME/storage");
|
try {
|
||||||
return;
|
deleteFolder(storageDir);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(LOG_TAG, "Could not delete old $HOME/storage, " + e.getMessage());
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!storageDir.mkdirs()) {
|
if (!storageDir.mkdirs()) {
|
||||||
@@ -254,9 +261,13 @@ final class TermuxInstaller {
|
|||||||
Os.symlink(moviesDir.getAbsolutePath(), new File(storageDir, "movies").getAbsolutePath());
|
Os.symlink(moviesDir.getAbsolutePath(), new File(storageDir, "movies").getAbsolutePath());
|
||||||
|
|
||||||
final File[] dirs = context.getExternalFilesDirs(null);
|
final File[] dirs = context.getExternalFilesDirs(null);
|
||||||
if (dirs != null && dirs.length >= 2) {
|
if (dirs != null && dirs.length > 1) {
|
||||||
final File externalDir = dirs[1];
|
for (int i = 1; i < dirs.length; i++) {
|
||||||
Os.symlink(externalDir.getAbsolutePath(), new File(storageDir, "external").getAbsolutePath());
|
File dir = dirs[i];
|
||||||
|
if (dir == null) continue;
|
||||||
|
String symlinkName = "external-" + i;
|
||||||
|
Os.symlink(dir.getAbsolutePath(), new File(storageDir, symlinkName).getAbsolutePath());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.e(LOG_TAG, "Error setting up link", 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 final int MIN_FONTSIZE;
|
||||||
private static final int MAX_FONTSIZE = 256;
|
private static final int MAX_FONTSIZE = 256;
|
||||||
|
|
||||||
private static final String FULLSCREEN_KEY = "fullscreen";
|
|
||||||
private static final String SHOW_EXTRA_KEYS_KEY = "show_extra_keys";
|
private static final String SHOW_EXTRA_KEYS_KEY = "show_extra_keys";
|
||||||
private static final String FONTSIZE_KEY = "fontsize";
|
private static final String FONTSIZE_KEY = "fontsize";
|
||||||
private static final String CURRENT_SESSION_KEY = "current_session";
|
private static final String CURRENT_SESSION_KEY = "current_session";
|
||||||
private static final String SHOW_WELCOME_DIALOG_KEY = "intro_dialog";
|
|
||||||
|
|
||||||
private boolean mFullScreen;
|
|
||||||
private int mFontSize;
|
private int mFontSize;
|
||||||
|
|
||||||
@AsciiBellBehaviour
|
@AsciiBellBehaviour
|
||||||
@@ -57,7 +54,6 @@ final class TermuxPreferences {
|
|||||||
// to prevent invisible text due to zoom be mistake:
|
// to prevent invisible text due to zoom be mistake:
|
||||||
MIN_FONTSIZE = (int) (4f * dipInPixels);
|
MIN_FONTSIZE = (int) (4f * dipInPixels);
|
||||||
|
|
||||||
mFullScreen = prefs.getBoolean(FULLSCREEN_KEY, false);
|
|
||||||
mShowExtraKeys = prefs.getBoolean(SHOW_EXTRA_KEYS_KEY, false);
|
mShowExtraKeys = prefs.getBoolean(SHOW_EXTRA_KEYS_KEY, false);
|
||||||
|
|
||||||
// http://www.google.com/design/spec/style/typography.html#typography-line-height
|
// http://www.google.com/design/spec/style/typography.html#typography-line-height
|
||||||
@@ -73,15 +69,6 @@ final class TermuxPreferences {
|
|||||||
mFontSize = Math.max(MIN_FONTSIZE, Math.min(mFontSize, MAX_FONTSIZE));
|
mFontSize = Math.max(MIN_FONTSIZE, Math.min(mFontSize, MAX_FONTSIZE));
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isFullScreen() {
|
|
||||||
return mFullScreen;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setFullScreen(Context context, boolean newValue) {
|
|
||||||
mFullScreen = newValue;
|
|
||||||
PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean(FULLSCREEN_KEY, newValue).apply();
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean isShowExtraKeys() {
|
boolean isShowExtraKeys() {
|
||||||
return mShowExtraKeys;
|
return mShowExtraKeys;
|
||||||
}
|
}
|
||||||
@@ -105,7 +92,7 @@ final class TermuxPreferences {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void storeCurrentSession(Context context, TerminalSession session) {
|
static void storeCurrentSession(Context context, TerminalSession session) {
|
||||||
PreferenceManager.getDefaultSharedPreferences(context).edit().putString(TermuxPreferences.CURRENT_SESSION_KEY, session.mHandle).commit();
|
PreferenceManager.getDefaultSharedPreferences(context).edit().putString(TermuxPreferences.CURRENT_SESSION_KEY, session.mHandle).apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
static TerminalSession getCurrentSession(TermuxActivity context) {
|
static TerminalSession getCurrentSession(TermuxActivity context) {
|
||||||
@@ -117,14 +104,6 @@ final class TermuxPreferences {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isShowWelcomeDialog(Context context) {
|
|
||||||
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SHOW_WELCOME_DIALOG_KEY, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void disableWelcomeDialog(Context context) {
|
|
||||||
PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean(SHOW_WELCOME_DIALOG_KEY, false).apply();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void reloadFromProperties(Context context) {
|
public void reloadFromProperties(Context context) {
|
||||||
try {
|
try {
|
||||||
File propsFile = new File(TermuxService.HOME_PATH + "/.termux/termux.properties");
|
File propsFile = new File(TermuxService.HOME_PATH + "/.termux/termux.properties");
|
||||||
@@ -184,9 +163,9 @@ final class TermuxPreferences {
|
|||||||
private void parseAction(String name, int shortcutAction, Properties props) {
|
private void parseAction(String name, int shortcutAction, Properties props) {
|
||||||
String value = props.getProperty(name);
|
String value = props.getProperty(name);
|
||||||
if (value == null) return;
|
if (value == null) return;
|
||||||
String[] parts = value.trim().split("\\+");
|
String[] parts = value.toLowerCase().trim().split("\\+");
|
||||||
String input = parts.length == 2 ? parts[1].trim() : null;
|
String input = parts.length == 2 ? parts[1].trim() : null;
|
||||||
if (!(parts.length == 2 && parts[0].trim().equalsIgnoreCase("ctrl")) || input.isEmpty() || input.length() > 2) {
|
if (!(parts.length == 2 && parts[0].trim().equals("ctrl")) || input.isEmpty() || input.length() > 2) {
|
||||||
Log.e("termux", "Keyboard shortcut '" + name + "' is not Ctrl+<something>");
|
Log.e("termux", "Keyboard shortcut '" + name + "' is not Ctrl+<something>");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.termux.app;
|
|||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.app.Notification;
|
import android.app.Notification;
|
||||||
|
import android.app.NotificationChannel;
|
||||||
import android.app.NotificationManager;
|
import android.app.NotificationManager;
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.app.Service;
|
import android.app.Service;
|
||||||
@@ -11,6 +12,8 @@ import android.content.res.Resources;
|
|||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.net.wifi.WifiManager;
|
import android.net.wifi.WifiManager;
|
||||||
import android.os.Binder;
|
import android.os.Binder;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Handler;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.PowerManager;
|
import android.os.PowerManager;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
@@ -22,7 +25,6 @@ import com.termux.terminal.TerminalSession;
|
|||||||
import com.termux.terminal.TerminalSession.SessionChangedCallback;
|
import com.termux.terminal.TerminalSession.SessionChangedCallback;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -40,6 +42,8 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
public final class TermuxService extends Service implements SessionChangedCallback {
|
public final class TermuxService extends Service implements SessionChangedCallback {
|
||||||
|
|
||||||
|
private static final String NOTIFICATION_CHANNEL_ID = "termux_notification_channel";
|
||||||
|
|
||||||
/** Note that this is a symlink on the Android M preview. */
|
/** Note that this is a symlink on the Android M preview. */
|
||||||
@SuppressLint("SdCardPath")
|
@SuppressLint("SdCardPath")
|
||||||
public static final String FILES_PATH = "/data/data/com.termux/files";
|
public static final String FILES_PATH = "/data/data/com.termux/files";
|
||||||
@@ -48,18 +52,16 @@ public final class TermuxService extends Service implements SessionChangedCallba
|
|||||||
|
|
||||||
private static final int NOTIFICATION_ID = 1337;
|
private static final int NOTIFICATION_ID = 1337;
|
||||||
|
|
||||||
/** Intent action to stop the service. */
|
|
||||||
private static final String ACTION_STOP_SERVICE = "com.termux.service_stop";
|
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_wake_lock";
|
||||||
private static final String ACTION_LOCK_WAKE = "com.termux.service_toggle_wake_lock";
|
private static final String ACTION_UNLOCK_WAKE = "com.termux.service_wake_unlock";
|
||||||
/** 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";
|
|
||||||
/** Intent action to launch a new terminal session. Executed from TermuxWidgetProvider. */
|
/** 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 ACTION_EXECUTE = "com.termux.service_execute";
|
||||||
|
|
||||||
public static final String EXTRA_ARGUMENTS = "com.termux.execute.arguments";
|
public static final String EXTRA_ARGUMENTS = "com.termux.execute.arguments";
|
||||||
|
|
||||||
public static final String EXTRA_CURRENT_WORKING_DIRECTORY = "com.termux.execute.cwd";
|
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. */
|
/** This service is only bound from inside the same process and never uses IPC. */
|
||||||
class LocalBinder extends Binder {
|
class LocalBinder extends Binder {
|
||||||
@@ -68,6 +70,8 @@ public final class TermuxService extends Service implements SessionChangedCallba
|
|||||||
|
|
||||||
private final IBinder mBinder = new LocalBinder();
|
private final IBinder mBinder = new LocalBinder();
|
||||||
|
|
||||||
|
private final Handler mHandler = new Handler();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The terminal sessions which this service manages.
|
* The terminal sessions which this service manages.
|
||||||
* <p/>
|
* <p/>
|
||||||
@@ -76,9 +80,12 @@ public final class TermuxService extends Service implements SessionChangedCallba
|
|||||||
*/
|
*/
|
||||||
final List<TerminalSession> mTerminalSessions = new ArrayList<>();
|
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. */
|
/** Note that the service may often outlive the activity, so need to clear this reference. */
|
||||||
SessionChangedCallback mSessionChangeCallback;
|
SessionChangedCallback mSessionChangeCallback;
|
||||||
|
|
||||||
|
/** The wake lock and wifi lock are always acquired and released together. */
|
||||||
private PowerManager.WakeLock mWakeLock;
|
private PowerManager.WakeLock mWakeLock;
|
||||||
private WifiManager.WifiLock mWifiLock;
|
private WifiManager.WifiLock mWifiLock;
|
||||||
|
|
||||||
@@ -99,41 +106,52 @@ public final class TermuxService extends Service implements SessionChangedCallba
|
|||||||
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
|
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
|
||||||
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, EmulatorDebug.LOG_TAG);
|
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, EmulatorDebug.LOG_TAG);
|
||||||
mWakeLock.acquire();
|
mWakeLock.acquire();
|
||||||
} else {
|
|
||||||
mWakeLock.release();
|
// http://tools.android.com/tech-docs/lint-in-studio-2-3#TOC-WifiManager-Leak
|
||||||
mWakeLock = null;
|
WifiManager wm = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
|
||||||
}
|
|
||||||
updateNotification();
|
|
||||||
} else if (ACTION_LOCK_WIFI.equals(action)) {
|
|
||||||
if (mWifiLock == null) {
|
|
||||||
WifiManager wm = (WifiManager) getSystemService(Context.WIFI_SERVICE);
|
|
||||||
mWifiLock = wm.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, EmulatorDebug.LOG_TAG);
|
mWifiLock = wm.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, EmulatorDebug.LOG_TAG);
|
||||||
mWifiLock.acquire();
|
mWifiLock.acquire();
|
||||||
} else {
|
|
||||||
|
updateNotification();
|
||||||
|
}
|
||||||
|
} else if (ACTION_UNLOCK_WAKE.equals(action)) {
|
||||||
|
if (mWakeLock != null) {
|
||||||
|
mWakeLock.release();
|
||||||
|
mWakeLock = null;
|
||||||
|
|
||||||
mWifiLock.release();
|
mWifiLock.release();
|
||||||
mWifiLock = null;
|
mWifiLock = null;
|
||||||
|
|
||||||
|
updateNotification();
|
||||||
}
|
}
|
||||||
updateNotification();
|
|
||||||
} else if (ACTION_EXECUTE.equals(action)) {
|
} else if (ACTION_EXECUTE.equals(action)) {
|
||||||
Uri executableUri = intent.getData();
|
Uri executableUri = intent.getData();
|
||||||
String executablePath = (executableUri == null ? null : executableUri.getPath());
|
String executablePath = (executableUri == null ? null : executableUri.getPath());
|
||||||
|
|
||||||
String[] arguments = (executableUri == null ? null : intent.getStringArrayExtra(EXTRA_ARGUMENTS));
|
String[] arguments = (executableUri == null ? null : intent.getStringArrayExtra(EXTRA_ARGUMENTS));
|
||||||
String cwd = intent.getStringExtra(EXTRA_CURRENT_WORKING_DIRECTORY);
|
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 (intent.getBooleanExtra(EXTRA_EXECUTE_IN_BACKGROUND, false)) {
|
||||||
if (executablePath != null) {
|
BackgroundJob task = new BackgroundJob(cwd, executablePath, arguments, this);
|
||||||
int lastSlash = executablePath.lastIndexOf('/');
|
mBackgroundTasks.add(task);
|
||||||
String name = (lastSlash == -1) ? executablePath : executablePath.substring(lastSlash + 1);
|
updateNotification();
|
||||||
name = name.replace('-', ' ');
|
} else {
|
||||||
newSession.mSessionName = name;
|
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) {
|
} else if (action != null) {
|
||||||
Log.e(EmulatorDebug.LOG_TAG, "Unknown TermuxService action: '" + action + "'");
|
Log.e(EmulatorDebug.LOG_TAG, "Unknown TermuxService action: '" + action + "'");
|
||||||
}
|
}
|
||||||
@@ -150,13 +168,14 @@ public final class TermuxService extends Service implements SessionChangedCallba
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
|
setupNotificationChannel();
|
||||||
startForeground(NOTIFICATION_ID, buildNotification());
|
startForeground(NOTIFICATION_ID, buildNotification());
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Update the shown foreground service notification after making any changes that affect it. */
|
/** Update the shown foreground service notification after making any changes that affect it. */
|
||||||
private void updateNotification() {
|
void updateNotification() {
|
||||||
if (mWakeLock == null && mWifiLock == null && getSessions().isEmpty()) {
|
if (mWakeLock == null && mTerminalSessions.isEmpty() && mBackgroundTasks.isEmpty()) {
|
||||||
// Exit if we are updating after the user disabled all locks with no sessions.
|
// Exit if we are updating after the user disabled all locks with no sessions or tasks running.
|
||||||
stopSelf();
|
stopSelf();
|
||||||
} else {
|
} else {
|
||||||
((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).notify(NOTIFICATION_ID, buildNotification());
|
((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);
|
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notifyIntent, 0);
|
||||||
|
|
||||||
int sessionCount = mTerminalSessions.size();
|
int sessionCount = mTerminalSessions.size();
|
||||||
String contentText = sessionCount + " terminal session" + (sessionCount == 1 ? "" : "s");
|
int taskCount = mBackgroundTasks.size();
|
||||||
|
String contentText = sessionCount + " session" + (sessionCount == 1 ? "" : "s");
|
||||||
boolean wakeLockHeld = mWakeLock != null;
|
if (taskCount > 0) {
|
||||||
boolean wifiLockHeld = mWifiLock != null;
|
contentText += ", " + taskCount + " task" + (taskCount == 1 ? "" : "s");
|
||||||
if (wakeLockHeld && wifiLockHeld) {
|
|
||||||
contentText += " (wake&wifi lock held)";
|
|
||||||
} else if (wakeLockHeld) {
|
|
||||||
contentText += " (wake lock held)";
|
|
||||||
} else if (wifiLockHeld) {
|
|
||||||
contentText += " (wifi lock held)";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final boolean wakeLockHeld = mWakeLock != null;
|
||||||
|
if (wakeLockHeld) contentText += " (wake lock held)";
|
||||||
|
|
||||||
Notification.Builder builder = new Notification.Builder(this);
|
Notification.Builder builder = new Notification.Builder(this);
|
||||||
builder.setContentTitle(getText(R.string.application_name));
|
builder.setContentTitle(getText(R.string.application_name));
|
||||||
builder.setContentText(contentText);
|
builder.setContentText(contentText);
|
||||||
@@ -191,8 +207,8 @@ public final class TermuxService extends Service implements SessionChangedCallba
|
|||||||
builder.setOngoing(true);
|
builder.setOngoing(true);
|
||||||
|
|
||||||
// If holding a wake or wifi lock consider the notification of high priority since it's using power,
|
// If holding a wake or wifi lock consider the notification of high priority since it's using power,
|
||||||
// otherwise use a minimal priority since this is just a background service notification:
|
// otherwise use a low priority
|
||||||
builder.setPriority((wakeLockHeld || wifiLockHeld) ? Notification.PRIORITY_HIGH : Notification.PRIORITY_MIN);
|
builder.setPriority((wakeLockHeld) ? Notification.PRIORITY_HIGH : Notification.PRIORITY_LOW);
|
||||||
|
|
||||||
// No need to show a timestamp:
|
// No need to show a timestamp:
|
||||||
builder.setShowWhen(false);
|
builder.setShowWhen(false);
|
||||||
@@ -200,17 +216,21 @@ public final class TermuxService extends Service implements SessionChangedCallba
|
|||||||
// Background color for small notification icon:
|
// Background color for small notification icon:
|
||||||
builder.setColor(0xFF000000);
|
builder.setColor(0xFF000000);
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
builder.setChannelId(NOTIFICATION_CHANNEL_ID);
|
||||||
|
}
|
||||||
|
|
||||||
Resources res = getResources();
|
Resources res = getResources();
|
||||||
Intent exitIntent = new Intent(this, TermuxService.class).setAction(ACTION_STOP_SERVICE);
|
Intent exitIntent = new Intent(this, TermuxService.class).setAction(ACTION_STOP_SERVICE);
|
||||||
builder.addAction(android.R.drawable.ic_delete, res.getString(R.string.notification_action_exit), PendingIntent.getService(this, 0, exitIntent, 0));
|
builder.addAction(android.R.drawable.ic_delete, res.getString(R.string.notification_action_exit), PendingIntent.getService(this, 0, exitIntent, 0));
|
||||||
|
|
||||||
Intent toggleWakeLockIntent = new Intent(this, TermuxService.class).setAction(ACTION_LOCK_WAKE);
|
String newWakeAction = wakeLockHeld ? ACTION_UNLOCK_WAKE : ACTION_LOCK_WAKE;
|
||||||
builder.addAction(android.R.drawable.ic_lock_lock, res.getString(R.string.notification_action_wakelock),
|
Intent toggleWakeLockIntent = new Intent(this, TermuxService.class).setAction(newWakeAction);
|
||||||
PendingIntent.getService(this, 0, toggleWakeLockIntent, 0));
|
String actionTitle = res.getString(wakeLockHeld ?
|
||||||
|
R.string.notification_action_wake_unlock :
|
||||||
Intent toggleWifiLockIntent = new Intent(this, TermuxService.class).setAction(ACTION_LOCK_WIFI);
|
R.string.notification_action_wake_lock);
|
||||||
builder.addAction(android.R.drawable.ic_lock_lock, res.getString(R.string.notification_action_wifilock),
|
int actionIcon = wakeLockHeld ? android.R.drawable.ic_lock_idle_lock : android.R.drawable.ic_lock_lock;
|
||||||
PendingIntent.getService(this, 0, toggleWifiLockIntent, 0));
|
builder.addAction(actionIcon, actionTitle, PendingIntent.getService(this, 0, toggleWakeLockIntent, 0));
|
||||||
|
|
||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
@@ -224,7 +244,6 @@ public final class TermuxService extends Service implements SessionChangedCallba
|
|||||||
|
|
||||||
for (int i = 0; i < mTerminalSessions.size(); i++)
|
for (int i = 0; i < mTerminalSessions.size(); i++)
|
||||||
mTerminalSessions.get(i).finishIfRunning();
|
mTerminalSessions.get(i).finishIfRunning();
|
||||||
mTerminalSessions.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<TerminalSession> getSessions() {
|
public List<TerminalSession> getSessions() {
|
||||||
@@ -236,53 +255,15 @@ public final class TermuxService extends Service implements SessionChangedCallba
|
|||||||
|
|
||||||
if (cwd == null) cwd = HOME_PATH;
|
if (cwd == null) cwd = HOME_PATH;
|
||||||
|
|
||||||
final String termEnv = "TERM=xterm-256color";
|
String[] env = BackgroundJob.buildEnvironment(failSafe, cwd);
|
||||||
final String homeEnv = "HOME=" + HOME_PATH;
|
boolean isLoginShell = false;
|
||||||
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;
|
|
||||||
|
|
||||||
env = new String[]{termEnv, homeEnv, prefixEnv, ps1Env, ldEnv, langEnv, pathEnv, pwdEnv, androidRootEnv, androidDataEnv, externalStorageEnv};
|
|
||||||
}
|
|
||||||
|
|
||||||
String shellName;
|
|
||||||
if (executablePath == null) {
|
if (executablePath == null) {
|
||||||
File shell = new File(HOME_PATH, ".termux/shell");
|
for (String shellBinary : new String[]{"login", "bash", "zsh"}) {
|
||||||
if (shell.exists()) {
|
File shellFile = new File(PREFIX_PATH + "/bin/" + shellBinary);
|
||||||
try {
|
if (shellFile.canExecute()) {
|
||||||
File canonicalFile = shell.getCanonicalFile();
|
executablePath = shellFile.getAbsolutePath();
|
||||||
if (canonicalFile.isFile() && canonicalFile.canExecute()) {
|
break;
|
||||||
executablePath = canonicalFile.getName().equals("busybox") ? (PREFIX_PATH + "/bin/ash") : canonicalFile.getAbsolutePath();
|
|
||||||
} else {
|
|
||||||
Log.w(EmulatorDebug.LOG_TAG, "$HOME/.termux/shell points to non-executable shell: " + canonicalFile.getAbsolutePath());
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.e(EmulatorDebug.LOG_TAG, "Error checking $HOME/.termux/shell", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (executablePath == null) {
|
|
||||||
// Try bash, zsh and ash in that order:
|
|
||||||
for (String shellBinary : new String[]{"bash", "zsh", "ash"}) {
|
|
||||||
File shellFile = new File(PREFIX_PATH + "/bin/" + shellBinary);
|
|
||||||
if (shellFile.canExecute()) {
|
|
||||||
executablePath = shellFile.getAbsolutePath();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -290,23 +271,18 @@ public final class TermuxService extends Service implements SessionChangedCallba
|
|||||||
// Fall back to system shell as last resort:
|
// Fall back to system shell as last resort:
|
||||||
executablePath = "/system/bin/sh";
|
executablePath = "/system/bin/sh";
|
||||||
}
|
}
|
||||||
|
isLoginShell = true;
|
||||||
String[] parts = executablePath.split("/");
|
|
||||||
shellName = "-" + parts[parts.length - 1];
|
|
||||||
} else {
|
|
||||||
int lastSlashIndex = executablePath.lastIndexOf('/');
|
|
||||||
shellName = lastSlashIndex == -1 ? executablePath : executablePath.substring(lastSlashIndex + 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String[] args;
|
String[] processArgs = BackgroundJob.setupProcessArgs(executablePath, arguments);
|
||||||
if (arguments == null) {
|
executablePath = processArgs[0];
|
||||||
args = new String[]{shellName};
|
int lastSlashIndex = executablePath.lastIndexOf('/');
|
||||||
} else {
|
String processName = (isLoginShell ? "-" : "") +
|
||||||
args = new String[arguments.length + 1];
|
(lastSlashIndex == -1 ? executablePath : executablePath.substring(lastSlashIndex + 1));
|
||||||
args[0] = shellName;
|
|
||||||
|
|
||||||
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);
|
TerminalSession session = new TerminalSession(executablePath, cwd, args, env, this);
|
||||||
mTerminalSessions.add(session);
|
mTerminalSessions.add(session);
|
||||||
@@ -358,4 +334,26 @@ public final class TermuxService extends Service implements SessionChangedCallba
|
|||||||
if (mSessionChangeCallback != null) mSessionChangeCallback.onColorsChanged(session);
|
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.content.Context;
|
||||||
import android.media.AudioManager;
|
import android.media.AudioManager;
|
||||||
import android.support.v4.widget.DrawerLayout;
|
import android.support.v4.widget.DrawerLayout;
|
||||||
import android.util.Log;
|
|
||||||
import android.view.Gravity;
|
import android.view.Gravity;
|
||||||
import android.view.InputDevice;
|
import android.view.InputDevice;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
@@ -13,18 +12,18 @@ import android.view.inputmethod.InputMethodManager;
|
|||||||
import com.termux.terminal.KeyHandler;
|
import com.termux.terminal.KeyHandler;
|
||||||
import com.termux.terminal.TerminalEmulator;
|
import com.termux.terminal.TerminalEmulator;
|
||||||
import com.termux.terminal.TerminalSession;
|
import com.termux.terminal.TerminalSession;
|
||||||
import com.termux.view.TerminalKeyListener;
|
import com.termux.view.TerminalViewClient;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public final class TermuxKeyListener implements TerminalKeyListener {
|
public final class TermuxViewClient implements TerminalViewClient {
|
||||||
|
|
||||||
final TermuxActivity mActivity;
|
final TermuxActivity mActivity;
|
||||||
|
|
||||||
/** Keeping track of the special keys acting as Ctrl and Fn for the soft keyboard and other hardware keys. */
|
/** Keeping track of the special keys acting as Ctrl and Fn for the soft keyboard and other hardware keys. */
|
||||||
boolean mVirtualControlKeyDown, mVirtualFnKeyDown;
|
boolean mVirtualControlKeyDown, mVirtualFnKeyDown;
|
||||||
|
|
||||||
public TermuxKeyListener(TermuxActivity activity) {
|
public TermuxViewClient(TermuxActivity activity) {
|
||||||
this.mActivity = activity;
|
this.mActivity = activity;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,33 +54,14 @@ public final class TermuxKeyListener implements TerminalKeyListener {
|
|||||||
mActivity.getDrawer().setDrawerLockMode(copyMode ? DrawerLayout.LOCK_MODE_LOCKED_CLOSED : DrawerLayout.LOCK_MODE_UNLOCKED);
|
mActivity.getDrawer().setDrawerLockMode(copyMode ? DrawerLayout.LOCK_MODE_LOCKED_CLOSED : DrawerLayout.LOCK_MODE_UNLOCKED);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void returnOnFinishedSession(TerminalSession currentSession) {
|
|
||||||
// Return pressed with finished session - remove it.
|
|
||||||
currentSession.finishIfRunning();
|
|
||||||
|
|
||||||
TermuxService service = mActivity.mTermService;
|
|
||||||
|
|
||||||
int index = service.removeTermSession(currentSession);
|
|
||||||
mActivity.mListViewAdapter.notifyDataSetChanged();
|
|
||||||
if (mActivity.mTermService.getSessions().isEmpty()) {
|
|
||||||
// There are no sessions to show, so finish the activity.
|
|
||||||
mActivity.finish();
|
|
||||||
} else {
|
|
||||||
if (index >= service.getSessions().size()) {
|
|
||||||
index = service.getSessions().size() - 1;
|
|
||||||
}
|
|
||||||
mActivity.switchToSession(service.getSessions().get(index));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onKeyDown(int keyCode, KeyEvent e, TerminalSession currentSession) {
|
public boolean onKeyDown(int keyCode, KeyEvent e, TerminalSession currentSession) {
|
||||||
if (handleVirtualKeys(keyCode, e, true)) return true;
|
if (handleVirtualKeys(keyCode, e, true)) return true;
|
||||||
|
|
||||||
if (keyCode == KeyEvent.KEYCODE_ENTER && !currentSession.isRunning()) {
|
if (keyCode == KeyEvent.KEYCODE_ENTER && !currentSession.isRunning()) {
|
||||||
returnOnFinishedSession(currentSession);
|
mActivity.removeFinishedSession(currentSession);
|
||||||
return true;
|
return true;
|
||||||
} else if (e.isCtrlPressed() && e.isShiftPressed()) {
|
} else if (e.isCtrlPressed() && e.isAltPressed()) {
|
||||||
// Get the unmodified code point:
|
// Get the unmodified code point:
|
||||||
int unicodeChar = e.getUnicodeChar(0);
|
int unicodeChar = e.getUnicodeChar(0);
|
||||||
|
|
||||||
@@ -93,8 +73,6 @@ public final class TermuxKeyListener implements TerminalKeyListener {
|
|||||||
mActivity.getDrawer().openDrawer(Gravity.LEFT);
|
mActivity.getDrawer().openDrawer(Gravity.LEFT);
|
||||||
} else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
|
} else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
|
||||||
mActivity.getDrawer().closeDrawers();
|
mActivity.getDrawer().closeDrawers();
|
||||||
} else if (unicodeChar == 'f'/* full screen */) {
|
|
||||||
mActivity.toggleImmersive();
|
|
||||||
} else if (unicodeChar == 'k'/* keyboard */) {
|
} else if (unicodeChar == 'k'/* keyboard */) {
|
||||||
InputMethodManager imm = (InputMethodManager) mActivity.getSystemService(Context.INPUT_METHOD_SERVICE);
|
InputMethodManager imm = (InputMethodManager) mActivity.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||||
imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
|
imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
|
||||||
@@ -180,7 +158,7 @@ public final class TermuxKeyListener implements TerminalKeyListener {
|
|||||||
resultingKeyCode = KeyEvent.KEYCODE_INSERT;
|
resultingKeyCode = KeyEvent.KEYCODE_INSERT;
|
||||||
break;
|
break;
|
||||||
case 'h':
|
case 'h':
|
||||||
resultingKeyCode = KeyEvent.KEYCODE_MOVE_HOME;
|
resultingCodePoint = '~';
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Special characters to input.
|
// Special characters to input.
|
||||||
@@ -244,15 +222,16 @@ public final class TermuxKeyListener implements TerminalKeyListener {
|
|||||||
return true;
|
return true;
|
||||||
} else if (ctrlDown) {
|
} else if (ctrlDown) {
|
||||||
if (codePoint == 106 /* Ctrl+j or \n */ && !session.isRunning()) {
|
if (codePoint == 106 /* Ctrl+j or \n */ && !session.isRunning()) {
|
||||||
returnOnFinishedSession(session);
|
mActivity.removeFinishedSession(session);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<TermuxPreferences.KeyboardShortcut> shortcuts = mActivity.mSettings.shortcuts;
|
List<TermuxPreferences.KeyboardShortcut> shortcuts = mActivity.mSettings.shortcuts;
|
||||||
if (!shortcuts.isEmpty()) {
|
if (!shortcuts.isEmpty()) {
|
||||||
|
int codePointLowerCase = Character.toLowerCase(codePoint);
|
||||||
for (int i = shortcuts.size() - 1; i >= 0; i--) {
|
for (int i = shortcuts.size() - 1; i >= 0; i--) {
|
||||||
TermuxPreferences.KeyboardShortcut shortcut = shortcuts.get(i);
|
TermuxPreferences.KeyboardShortcut shortcut = shortcuts.get(i);
|
||||||
if (codePoint == shortcut.codePoint) {
|
if (codePointLowerCase == shortcut.codePoint) {
|
||||||
switch (shortcut.shortcutAction) {
|
switch (shortcut.shortcutAction) {
|
||||||
case TermuxPreferences.SHORTCUT_ACTION_CREATE_SESSION:
|
case TermuxPreferences.SHORTCUT_ACTION_CREATE_SESSION:
|
||||||
mActivity.addNewSession(false, null);
|
mActivity.addNewSession(false, null);
|
||||||
@@ -275,6 +254,11 @@ public final class TermuxKeyListener implements TerminalKeyListener {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onLongPress(MotionEvent event) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/** Handle dedicated volume buttons as virtual keys if applicable. */
|
/** Handle dedicated volume buttons as virtual keys if applicable. */
|
||||||
private boolean handleVirtualKeys(int keyCode, KeyEvent event, boolean down) {
|
private boolean handleVirtualKeys(int keyCode, KeyEvent event, boolean down) {
|
||||||
InputDevice inputDevice = event.getDevice();
|
InputDevice inputDevice = event.getDevice();
|
||||||
@@ -75,7 +75,7 @@ public class TermuxDocumentsProvider extends DocumentsProvider {
|
|||||||
row.add(Root.COLUMN_TITLE, applicationName);
|
row.add(Root.COLUMN_TITLE, applicationName);
|
||||||
row.add(Root.COLUMN_MIME_TYPES, ALL_MIME_TYPES);
|
row.add(Root.COLUMN_MIME_TYPES, ALL_MIME_TYPES);
|
||||||
row.add(Root.COLUMN_AVAILABLE_BYTES, BASE_DIR.getFreeSpace());
|
row.add(Root.COLUMN_AVAILABLE_BYTES, BASE_DIR.getFreeSpace());
|
||||||
row.add(Root.COLUMN_ICON, R.drawable.ic_launcher);
|
row.add(Root.COLUMN_ICON, R.mipmap.ic_launcher);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,7 +195,7 @@ public class TermuxDocumentsProvider extends DocumentsProvider {
|
|||||||
final String name = file.getName();
|
final String name = file.getName();
|
||||||
final int lastDot = name.lastIndexOf('.');
|
final int lastDot = name.lastIndexOf('.');
|
||||||
if (lastDot >= 0) {
|
if (lastDot >= 0) {
|
||||||
final String extension = name.substring(lastDot + 1);
|
final String extension = name.substring(lastDot + 1).toLowerCase();
|
||||||
final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
|
final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
|
||||||
if (mime != null) return mime;
|
if (mime != null) return mime;
|
||||||
}
|
}
|
||||||
@@ -236,7 +236,7 @@ public class TermuxDocumentsProvider extends DocumentsProvider {
|
|||||||
row.add(Document.COLUMN_MIME_TYPE, mimeType);
|
row.add(Document.COLUMN_MIME_TYPE, mimeType);
|
||||||
row.add(Document.COLUMN_LAST_MODIFIED, file.lastModified());
|
row.add(Document.COLUMN_LAST_MODIFIED, file.lastModified());
|
||||||
row.add(Document.COLUMN_FLAGS, flags);
|
row.add(Document.COLUMN_FLAGS, flags);
|
||||||
row.add(Document.COLUMN_ICON, R.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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,108 +0,0 @@
|
|||||||
package com.termux.terminal;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* wcwidth() implementation from http://git.musl-libc.org/cgit/musl/tree/src/ctype
|
|
||||||
* <p/>
|
|
||||||
* Modified to return 0 instead of -1.
|
|
||||||
*/
|
|
||||||
public final class WcWidth {
|
|
||||||
|
|
||||||
private static final short table[] = {16, 16, 16, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 16, 16, 32, 16, 16, 16, 33, 34, 35, 36, 37, 38,
|
|
||||||
39, 16, 16, 40, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 41, 42, 16, 16, 43, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
|
|
||||||
16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
|
|
||||||
16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
|
|
||||||
16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 44, 16, 45, 46, 47, 48, 16, 16, 16, 16, 16,
|
|
||||||
16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
|
|
||||||
16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
|
|
||||||
49, 16, 16, 50, 51, 16, 52, 16, 16, 16, 16, 16, 16, 16, 16, 53, 16, 16, 16, 16, 16, 54, 55, 16, 16, 16, 16, 56, 16, 16, 16, 16, 16, 16, 16, 16, 16,
|
|
||||||
16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
|
|
||||||
16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
|
|
||||||
16, 16, 16, 16, 16, 57, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
|
|
||||||
16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
|
|
||||||
16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 58, 59, 16, 16, 16, 16, 16, 16,
|
|
||||||
16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
|
|
||||||
16, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255,
|
|
||||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
|
||||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
248, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 254, 255, 255, 255, 255, 191, 182, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 31, 0, 255, 7, 0, 0, 0, 0, 0, 248, 255, 255, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 191, 159, 61, 0, 0, 0, 128, 2, 0, 0, 0,
|
|
||||||
255, 255, 255, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 255, 1, 0, 0, 0, 0, 0, 0, 248, 15, 0, 0, 0, 192, 251, 239, 62, 0, 0, 0, 0, 0, 14, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 240, 255, 255, 127, 7, 0, 0, 0, 0, 0, 0, 20, 254, 33, 254, 0, 12, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 16, 30, 32, 0,
|
|
||||||
0, 12, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 16, 134, 57, 2, 0, 0, 0, 35, 0, 6, 0, 0, 0, 0, 0, 0, 16, 190, 33, 0, 0, 12, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 144,
|
|
||||||
30, 32, 64, 0, 12, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 1, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 193, 61, 96, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 144, 64, 48, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 30, 32, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 92, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 242, 7, 128, 127, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 242, 27, 0, 63, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 160, 2, 0, 0, 0, 0, 0, 0, 254,
|
|
||||||
127, 223, 224, 255, 254, 255, 255, 255, 31, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 253, 102, 0, 0, 0, 195, 1, 0, 30, 0, 100, 32, 0, 32, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 28, 0, 0, 0,
|
|
||||||
28, 0, 0, 0, 12, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 176, 63, 64, 254, 15, 32, 0, 0, 0, 0, 0, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 135, 1, 4, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
128, 1, 0, 0, 0, 0, 0, 0, 64, 127, 229, 31, 248, 159, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 0, 0, 208, 23, 4, 0, 0, 0, 0,
|
|
||||||
248, 15, 0, 3, 0, 0, 0, 60, 11, 0, 0, 0, 0, 0, 0, 64, 163, 3, 0, 0, 0, 0, 0, 0, 240, 207, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
247, 255, 253, 33, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 127, 0, 0, 240, 0, 248, 0, 0,
|
|
||||||
0, 124, 0, 0, 0, 0, 0, 0, 31, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255,
|
|
||||||
255, 0, 0, 0, 0, 0, 60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128,
|
|
||||||
247, 63, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 68, 8, 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0,
|
|
||||||
255, 255, 3, 0, 0, 0, 0, 0, 192, 63, 0, 0, 128, 255, 3, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 200, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 126, 102,
|
|
||||||
0, 8, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 157, 193, 2, 0, 0, 0, 0, 48, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 32, 33, 0, 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0,
|
|
||||||
127, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 110, 240, 0,
|
|
||||||
0, 0, 0, 0, 135, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 255, 127, 0, 0, 0, 0, 0, 0, 0, 3, 0,
|
|
||||||
0, 0, 0, 0, 120, 38, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 128, 239, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 192, 127, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 191, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 128, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 3, 248, 255, 231, 15, 0, 0, 0, 60, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 28, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,};
|
|
||||||
|
|
||||||
private static final short wtable[] = {16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 18, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
|
|
||||||
16, 16, 16, 16, 16, 16, 19, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 20, 21, 22, 23, 24, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
|
|
||||||
17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 25, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
|
|
||||||
17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
|
|
||||||
17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 26, 16, 16, 16, 16, 27, 16, 16, 17, 17, 17, 17, 17,
|
|
||||||
17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
|
|
||||||
17, 28, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 17, 17,
|
|
||||||
16, 16, 16, 29, 30, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
|
|
||||||
16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
|
|
||||||
16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
|
|
||||||
16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
|
|
||||||
16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 31, 16, 16, 16,
|
|
||||||
16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
|
|
||||||
16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 32, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
|
|
||||||
16, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255,
|
|
||||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
|
||||||
255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 252, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 251, 255, 255, 255, 255, 255, 255,
|
|
||||||
255, 255, 255, 255, 15, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
|
||||||
255, 255, 63, 0, 0, 0, 255, 15, 255, 255, 255, 255, 255, 255, 255, 127, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 127, 254, 255, 255, 255,
|
|
||||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 224, 255, 255, 255, 255, 63, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 127, 255, 255,
|
|
||||||
255, 255, 255, 7, 255, 255, 255, 255, 15, 0, 255, 255, 255, 255, 255, 127, 255, 255, 255, 255, 255, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
|
||||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 127, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
|
||||||
255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
|
||||||
255, 31, 255, 255, 255, 255, 255, 255, 127, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 15, 0, 255, 255, 127, 248,
|
|
||||||
255, 255, 255, 255, 255, 15, 0, 0, 255, 3, 0, 0, 255, 255, 255, 255, 247, 255, 127, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 254,
|
|
||||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 127, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 255, 255, 255, 255, 255, 7, 255, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0};
|
|
||||||
|
|
||||||
/** Return the terminal display width of a code point: 0, 1 or 2. */
|
|
||||||
public static int width(int wc) {
|
|
||||||
if (wc < 0xff) return (wc + 1 & 0x7f) >= 0x21 ? 1 : (wc != 0) ? 0 : 0;
|
|
||||||
if ((wc & 0xfffeffff) < 0xfffe) {
|
|
||||||
if (((table[table[wc >> 8] * 32 + ((wc & 255) >> 3)] >> (wc & 7)) & 1) != 0) return 0;
|
|
||||||
if (((wtable[wtable[wc >> 8] * 32 + ((wc & 255) >> 3)] >> (wc & 7)) & 1) != 0) return 2;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if ((wc & 0xfffe) == 0xfffe) return 0;
|
|
||||||
if (wc - 0x20000 < 0x20000) return 2;
|
|
||||||
if (wc == 0xe0001 || wc - 0xe0020 < 0x5f || wc - 0xe0100 < 0xef) return 0;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** The width at an index position in a java char array. */
|
|
||||||
public static int width(char[] chars, int index) {
|
|
||||||
char c = chars[index];
|
|
||||||
return Character.isHighSurrogate(c) ? width(Character.toCodePoint(c, chars[index + 1])) : width(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
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"
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical"
|
||||||
|
android:fitsSystemWindows="true">
|
||||||
|
|
||||||
<android.support.v4.widget.DrawerLayout
|
<android.support.v4.widget.DrawerLayout
|
||||||
android:id="@+id/drawer_layout"
|
android:id="@+id/drawer_layout"
|
||||||
@@ -69,7 +70,7 @@
|
|||||||
android:id="@+id/viewpager"
|
android:id="@+id/viewpager"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="40dp"
|
android:layout_height="75dp"
|
||||||
android:background="@android:drawable/screen_background_dark_transparent"
|
android:background="@android:drawable/screen_background_dark_transparent"
|
||||||
android:layout_alignParentBottom="true" />
|
android:layout_alignParentBottom="true" />
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
android:imeOptions="actionSend|flagNoFullscreen"
|
android:imeOptions="actionSend|flagNoFullscreen"
|
||||||
android:maxLines="1"
|
android:maxLines="1"
|
||||||
android:inputType="text"
|
android:inputType="text"
|
||||||
android:singleLine="true"
|
|
||||||
android:textColor="@android:color/white"
|
android:textColor="@android:color/white"
|
||||||
android:paddingTop="0dp"
|
android:paddingTop="0dp"
|
||||||
android:textCursorDrawable="@null"
|
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="toggle_soft_keyboard">Keyboard</string>
|
||||||
<string name="reset_terminal">Reset</string>
|
<string name="reset_terminal">Reset</string>
|
||||||
<string name="style_terminal">Style</string>
|
<string name="style_terminal">Style</string>
|
||||||
<string name="toggle_fullscreen">Fullscreen</string>
|
|
||||||
<string name="share_transcript_title">Terminal transcript</string>
|
<string name="share_transcript_title">Terminal transcript</string>
|
||||||
<string name="help">Help</string>
|
<string name="help">Help</string>
|
||||||
|
|
||||||
<string name="welcome_dialog_title">Welcome to Termux</string>
|
|
||||||
<string name="welcome_dialog_body">Long press and select <i>More…</i> to show a menu where <i>Help</i> is available.\n\nExecute <b>apt update</b> to update the packages list before installing packages.</string>
|
|
||||||
<string name="welcome_dialog_dont_show_again_button">Do not show again</string>
|
|
||||||
|
|
||||||
<string name="bootstrap_installer_body">Installing…</string>
|
<string name="bootstrap_installer_body">Installing…</string>
|
||||||
<string name="bootstrap_error_title">Unable to install</string>
|
<string name="bootstrap_error_title">Unable to install</string>
|
||||||
<string name="bootstrap_error_body">Termux was unable to install the bootstrap packages.\n\nCheck your network connection and try again.</string>
|
<string name="bootstrap_error_body">Termux was unable to install the bootstrap packages.\n\nCheck your network connection and try again.</string>
|
||||||
@@ -34,10 +29,6 @@
|
|||||||
<string name="select_url_copied_to_clipboard">URL copied to clipboard</string>
|
<string name="select_url_copied_to_clipboard">URL copied to clipboard</string>
|
||||||
<string name="share_transcript_chooser_title">Send text to:</string>
|
<string name="share_transcript_chooser_title">Send text to:</string>
|
||||||
|
|
||||||
<string name="paste_text">Paste</string>
|
|
||||||
<string name="copy_text">Copy</string>
|
|
||||||
<string name="text_selection_more">More…</string>
|
|
||||||
|
|
||||||
<string name="kill_process">Kill process (%d)</string>
|
<string name="kill_process">Kill process (%d)</string>
|
||||||
<string name="confirm_kill_process">Really kill this session?</string>
|
<string name="confirm_kill_process">Really kill this session?</string>
|
||||||
|
|
||||||
@@ -50,8 +41,8 @@
|
|||||||
<string name="styling_install">Install</string>
|
<string name="styling_install">Install</string>
|
||||||
|
|
||||||
<string name="notification_action_exit">Exit</string>
|
<string name="notification_action_exit">Exit</string>
|
||||||
<string name="notification_action_wakelock">Wake</string>
|
<string name="notification_action_wake_lock">Acquire wakelock</string>
|
||||||
<string name="notification_action_wifilock">Wifi</string>
|
<string name="notification_action_wake_unlock">Release wakelock</string>
|
||||||
|
|
||||||
<string name="file_received_title">Save file in ~/downloads/</string>
|
<string name="file_received_title">Save file in ~/downloads/</string>
|
||||||
<string name="file_received_edit_button">Edit</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. -->
|
<!-- NOTE: Cannot use "Light." since it hides the terminal scrollbar on the default black background. -->
|
||||||
<style name="Theme.Termux" parent="@android:style/Theme.Material.Light.NoActionBar">
|
<style name="Theme.Termux" parent="@android:style/Theme.Material.Light.NoActionBar">
|
||||||
<item name="android:statusBarColor">#000000</item>
|
<item name="android:statusBarColor">#000000</item>
|
||||||
|
<item name="android:colorPrimary">#FF000000</item>
|
||||||
<item name="android:windowBackground">@android:color/black</item>
|
<item name="android:windowBackground">@android:color/black</item>
|
||||||
|
|
||||||
<!-- Seen in buttons on left drawer: -->
|
<!-- Seen in buttons on left drawer: -->
|
||||||
@@ -13,6 +14,13 @@
|
|||||||
<!-- Avoid action mode toolbar pushing down terminal content when
|
<!-- Avoid action mode toolbar pushing down terminal content when
|
||||||
selecting text on pre-6.0 (non-floating toolbar). -->
|
selecting text on pre-6.0 (non-floating toolbar). -->
|
||||||
<item name="android:windowActionModeOverlay">true</item>
|
<item name="android:windowActionModeOverlay">true</item>
|
||||||
|
|
||||||
|
<item name="android:windowTranslucentStatus">true</item>
|
||||||
|
<item name="android:windowTranslucentNavigation">true</item>
|
||||||
|
|
||||||
|
<!-- https://developer.android.com/training/tv/start/start.html#transition-color -->
|
||||||
|
<item name="android:windowAllowReturnTransitionOverlap">true</item>
|
||||||
|
<item name="android:windowAllowEnterTransitionOverlap">true</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="TermuxAlertDialogStyle" parent="@android:style/Theme.Material.Light.Dialog.Alert">
|
<style name="TermuxAlertDialogStyle" parent="@android:style/Theme.Material.Light.Dialog.Alert">
|
||||||
|
|||||||
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 |
@@ -1,19 +1,16 @@
|
|||||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
repositories {
|
repositories {
|
||||||
jcenter()
|
jcenter()
|
||||||
|
google()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:2.1.2'
|
classpath 'com.android.tools.build:gradle:3.1.3'
|
||||||
|
|
||||||
// NOTE: Do not place your application dependencies here; they belong
|
|
||||||
// in the individual module build.gradle files
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
repositories {
|
repositories {
|
||||||
|
google()
|
||||||
jcenter()
|
jcenter()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,4 @@
|
|||||||
# This option should only be used with decoupled projects. More details, visit
|
# 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
|
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||||
# org.gradle.parallel=true
|
# org.gradle.parallel=true
|
||||||
#Fri May 13 01:11:09 CEST 2016
|
|
||||||
org.gradle.jvmargs=-Xmx2048M
|
org.gradle.jvmargs=-Xmx2048M
|
||||||
android.useDeprecatedNdk=true
|
|
||||||
|
|||||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
3
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,5 @@
|
|||||||
#Sat Jul 23 17:08:29 CEST 2016
|
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-4.7-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-bin.zip
|
|
||||||
|
|||||||
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.
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
MAX_FD="maximum"
|
MAX_FD="maximum"
|
||||||
|
|
||||||
warn ( ) {
|
warn () {
|
||||||
echo "$*"
|
echo "$*"
|
||||||
}
|
}
|
||||||
|
|
||||||
die ( ) {
|
die () {
|
||||||
echo
|
echo
|
||||||
echo "$*"
|
echo "$*"
|
||||||
echo
|
echo
|
||||||
@@ -154,11 +154,19 @@ if $cygwin ; then
|
|||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
|
# Escape application args
|
||||||
function splitJvmOpts() {
|
save () {
|
||||||
JVM_OPTS=("$@")
|
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||||
|
echo " "
|
||||||
}
|
}
|
||||||
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
|
APP_ARGS=$(save "$@")
|
||||||
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
|
|
||||||
|
|
||||||
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'
|
||||||
|
|||||||
61
terminal-emulator/build.gradle
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
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_F8;
|
||||||
import static android.view.KeyEvent.KEYCODE_F9;
|
import static android.view.KeyEvent.KEYCODE_F9;
|
||||||
import static android.view.KeyEvent.KEYCODE_FORWARD_DEL;
|
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_INSERT;
|
||||||
import static android.view.KeyEvent.KEYCODE_MOVE_END;
|
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_0;
|
||||||
import static android.view.KeyEvent.KEYCODE_NUMPAD_1;
|
import static android.view.KeyEvent.KEYCODE_NUMPAD_1;
|
||||||
import static android.view.KeyEvent.KEYCODE_NUMPAD_2;
|
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
|
// terminfo: http://pubs.opengroup.org/onlinepubs/7990989799/xcurses/terminfo.html
|
||||||
// termcap: http://man7.org/linux/man-pages/man5/termcap.5.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("%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("#4", KEYMOD_SHIFT | KEYCODE_DPAD_LEFT);
|
||||||
TERMCAP_TO_KEYCODE.put("*7", KEYMOD_SHIFT | KEYCODE_MOVE_END); // Shifted end key
|
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("kb", KEYCODE_DEL); // backspace key
|
||||||
|
|
||||||
TERMCAP_TO_KEYCODE.put("kd", KEYCODE_DPAD_DOWN); // terminfo=kcud1, down-arrow 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("kl", KEYCODE_DPAD_LEFT);
|
||||||
TERMCAP_TO_KEYCODE.put("kr", KEYCODE_DPAD_RIGHT);
|
TERMCAP_TO_KEYCODE.put("kr", KEYCODE_DPAD_RIGHT);
|
||||||
|
|
||||||
@@ -107,7 +107,7 @@ public final class KeyHandler {
|
|||||||
// t_K3 <kPageUp> keypad page-up key
|
// t_K3 <kPageUp> keypad page-up key
|
||||||
// t_K4 <kEnd> keypad end key
|
// t_K4 <kEnd> keypad end key
|
||||||
// t_K5 <kPageDown> keypad page-down 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("K3", KEYCODE_PAGE_UP);
|
||||||
TERMCAP_TO_KEYCODE.put("K4", KEYCODE_MOVE_END);
|
TERMCAP_TO_KEYCODE.put("K4", KEYCODE_MOVE_END);
|
||||||
TERMCAP_TO_KEYCODE.put("K5", KEYCODE_PAGE_DOWN);
|
TERMCAP_TO_KEYCODE.put("K5", KEYCODE_PAGE_DOWN);
|
||||||
@@ -162,7 +162,9 @@ public final class KeyHandler {
|
|||||||
case KEYCODE_DPAD_LEFT:
|
case KEYCODE_DPAD_LEFT:
|
||||||
return (keyMode == 0) ? (cursorApp ? "\033OD" : "\033[D") : transformForModifiers("\033[1", keyMode, 'D');
|
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');
|
return (keyMode == 0) ? (cursorApp ? "\033OH" : "\033[H") : transformForModifiers("\033[1", keyMode, 'H');
|
||||||
case KEYCODE_MOVE_END:
|
case KEYCODE_MOVE_END:
|
||||||
return (keyMode == 0) ? (cursorApp ? "\033OF" : "\033[F") : transformForModifiers("\033[1", keyMode, 'F');
|
return (keyMode == 0) ? (cursorApp ? "\033OF" : "\033[F") : transformForModifiers("\033[1", keyMode, 'F');
|
||||||
@@ -217,9 +219,6 @@ public final class KeyHandler {
|
|||||||
case KEYCODE_FORWARD_DEL:
|
case KEYCODE_FORWARD_DEL:
|
||||||
return transformForModifiers("\033[3", keyMode, '~');
|
return transformForModifiers("\033[3", keyMode, '~');
|
||||||
|
|
||||||
case KEYCODE_NUMPAD_DOT:
|
|
||||||
return keypadApplication ? "\033On" : "\033[3~";
|
|
||||||
|
|
||||||
case KEYCODE_PAGE_UP:
|
case KEYCODE_PAGE_UP:
|
||||||
return "\033[5~";
|
return "\033[5~";
|
||||||
case KEYCODE_PAGE_DOWN:
|
case KEYCODE_PAGE_DOWN:
|
||||||
@@ -249,12 +248,14 @@ public final class KeyHandler {
|
|||||||
return keypadApplication ? transformForModifiers("\033O", keyMode, 'k') : "+";
|
return keypadApplication ? transformForModifiers("\033O", keyMode, 'k') : "+";
|
||||||
case KEYCODE_NUMPAD_COMMA:
|
case KEYCODE_NUMPAD_COMMA:
|
||||||
return ",";
|
return ",";
|
||||||
|
case KEYCODE_NUMPAD_DOT:
|
||||||
|
return keypadApplication ? "\033On" : ".";
|
||||||
case KEYCODE_NUMPAD_SUBTRACT:
|
case KEYCODE_NUMPAD_SUBTRACT:
|
||||||
return keypadApplication ? transformForModifiers("\033O", keyMode, 'm') : "-";
|
return keypadApplication ? transformForModifiers("\033O", keyMode, 'm') : "-";
|
||||||
case KEYCODE_NUMPAD_DIVIDE:
|
case KEYCODE_NUMPAD_DIVIDE:
|
||||||
return keypadApplication ? transformForModifiers("\033O", keyMode, 'o') : "/";
|
return keypadApplication ? transformForModifiers("\033O", keyMode, 'o') : "/";
|
||||||
case KEYCODE_NUMPAD_0:
|
case KEYCODE_NUMPAD_0:
|
||||||
return keypadApplication ? transformForModifiers("\033O", keyMode, 'p') : "1";
|
return keypadApplication ? transformForModifiers("\033O", keyMode, 'p') : "0";
|
||||||
case KEYCODE_NUMPAD_1:
|
case KEYCODE_NUMPAD_1:
|
||||||
return keypadApplication ? transformForModifiers("\033O", keyMode, 'q') : "1";
|
return keypadApplication ? transformForModifiers("\033O", keyMode, 'q') : "1";
|
||||||
case KEYCODE_NUMPAD_2:
|
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
|
* A circular buffer of {@link TerminalRow}:s which keeps notes about what is visible on a logical screen and the scroll
|
||||||
* history.
|
* history.
|
||||||
* <p/>
|
* <p>
|
||||||
* See {@link #externalToInternalRow(int)} for how to map from logical screen rows to array indices.
|
* See {@link #externalToInternalRow(int)} for how to map from logical screen rows to array indices.
|
||||||
*/
|
*/
|
||||||
public final class TerminalBuffer {
|
public final class TerminalBuffer {
|
||||||
@@ -92,22 +92,20 @@ public final class TerminalBuffer {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert a row value from the public external coordinate system to our internal private coordinate system.
|
* Convert a row value from the public external coordinate system to our internal private coordinate system.
|
||||||
* <p/>
|
*
|
||||||
* <ul>
|
|
||||||
* <li>External coordinate system: -mActiveTranscriptRows to mScreenRows-1, with the screen being 0..mScreenRows-1.
|
|
||||||
* <li>Internal coordinate system: the mScreenRows lines starting at mScreenFirstRow comprise the screen, while the
|
|
||||||
* mActiveTranscriptRows lines ending at mScreenFirstRow-1 form the transcript (as a circular buffer).
|
|
||||||
* </ul>
|
|
||||||
* <p/>
|
|
||||||
* External <---> Internal:
|
|
||||||
* <p/>
|
|
||||||
* <pre>
|
* <pre>
|
||||||
* [ ... ] [ ... ]
|
* - External coordinate system: -mActiveTranscriptRows to mScreenRows-1, with the screen being 0..mScreenRows-1.
|
||||||
* [ -mActiveTranscriptRows ] [ mScreenFirstRow - mActiveTranscriptRows ]
|
* - Internal coordinate system: the mScreenRows lines starting at mScreenFirstRow comprise the screen, while the
|
||||||
* [ ... ] [ ... ]
|
* mActiveTranscriptRows lines ending at mScreenFirstRow-1 form the transcript (as a circular buffer).
|
||||||
* [ 0 (visible screen starts here) ] <-----> [ mScreenFirstRow ]
|
*
|
||||||
* [ ... ] [ ... ]
|
* External ↔ Internal:
|
||||||
* [ mScreenRows-1 ] [ mScreenFirstRow + mScreenRows-1 ]
|
*
|
||||||
|
* [ ... ] [ ... ]
|
||||||
|
* [ -mActiveTranscriptRows ] [ mScreenFirstRow - mActiveTranscriptRows ]
|
||||||
|
* [ ... ] [ ... ]
|
||||||
|
* [ 0 (visible screen starts here) ] ↔ [ mScreenFirstRow ]
|
||||||
|
* [ ... ] [ ... ]
|
||||||
|
* [ mScreenRows-1 ] [ mScreenFirstRow + mScreenRows-1 ]
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
* @param externalRow a row in the external coordinate system.
|
* @param externalRow a row in the external coordinate system.
|
||||||
@@ -140,7 +138,7 @@ public final class TerminalBuffer {
|
|||||||
* @param newRows The number of rows the screen should have.
|
* @param newRows The number of rows the screen should have.
|
||||||
* @param cursor An int[2] containing the (column, row) cursor location.
|
* @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):
|
// newRows > mTotalRows should not normally happen since mTotalRows is TRANSCRIPT_ROWS (10000):
|
||||||
if (newColumns == mColumns && newRows <= mTotalRows) {
|
if (newColumns == mColumns && newRows <= mTotalRows) {
|
||||||
// Fast resize where just the rows changed.
|
// Fast resize where just the rows changed.
|
||||||
@@ -237,7 +235,7 @@ public final class TerminalBuffer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int currentOldCol = 0;
|
int currentOldCol = 0;
|
||||||
int styleAtCol = 0;
|
long styleAtCol = 0;
|
||||||
for (int i = 0; i < lastNonSpaceIndex; i++) {
|
for (int i = 0; i < lastNonSpaceIndex; i++) {
|
||||||
// Note that looping over java character, not cells.
|
// Note that looping over java character, not cells.
|
||||||
char c = oldLine.mText[i];
|
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 bottomMargin One line after the last line that is scrolled.
|
||||||
* @param style the style for the newly exposed line.
|
* @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)
|
if (topMargin > bottomMargin - 1 || topMargin < 0 || bottomMargin > mScreenRows)
|
||||||
throw new IllegalArgumentException("topMargin=" + topMargin + ", bottomMargin=" + bottomMargin + ", mScreenRows=" + 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
|
* InvalidParemeterException will be thrown. Typically this is called with a "val" argument of 32 to clear a block
|
||||||
* of characters.
|
* 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) {
|
if (sx < 0 || sx + w > mColumns || sy < 0 || sy + h > mScreenRows) {
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
"Illegal arguments! blockSet(" + sx + ", " + sy + ", " + w + ", " + h + ", " + val + ", " + mColumns + ", " + mScreenRows + ")");
|
"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];
|
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)
|
if (row >= mScreenRows || column >= mColumns)
|
||||||
throw new IllegalArgumentException("row=" + row + ", column=" + column + ", mScreenRows=" + mScreenRows + ", mColumns=" + mColumns);
|
throw new IllegalArgumentException("row=" + row + ", column=" + column + ", mScreenRows=" + mScreenRows + ", mColumns=" + mColumns);
|
||||||
row = externalToInternalRow(row);
|
row = externalToInternalRow(row);
|
||||||
allocateFullLineIfNecessary(row).setChar(column, codePoint, style);
|
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);
|
return allocateFullLineIfNecessary(externalToInternalRow(externalRow)).getStyle(column);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -407,7 +405,7 @@ public final class TerminalBuffer {
|
|||||||
int startOfLine = (rectangular || y == top) ? left : leftMargin;
|
int startOfLine = (rectangular || y == top) ? left : leftMargin;
|
||||||
int endOfLine = (rectangular || y + 1 == bottom) ? right : rightMargin;
|
int endOfLine = (rectangular || y + 1 == bottom) ? right : rightMargin;
|
||||||
for (int x = startOfLine; x < endOfLine; x++) {
|
for (int x = startOfLine; x < endOfLine; x++) {
|
||||||
int currentStyle = line.getStyle(x);
|
long currentStyle = line.getStyle(x);
|
||||||
int foreColor = TextStyle.decodeForeColor(currentStyle);
|
int foreColor = TextStyle.decodeForeColor(currentStyle);
|
||||||
int backColor = TextStyle.decodeBackColor(currentStyle);
|
int backColor = TextStyle.decodeBackColor(currentStyle);
|
||||||
int effect = TextStyle.decodeEffect(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,
|
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:
|
// 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];
|
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
|
* 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.
|
* System xterm terminal, which in turn is an emulator for a subset of the Digital Equipment Corporation vt100 terminal.
|
||||||
* <p/>
|
* <p>
|
||||||
* References:
|
* References:
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>http://invisible-island.net/xterm/ctlseqs/ctlseqs.html</li>
|
* <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;
|
private static final int ESC_SELECT_LEFT_PAREN = 3;
|
||||||
/** Escape processing: Have seen ESC and a character-set-select ) char */
|
/** Escape processing: Have seen ESC and a character-set-select ) char */
|
||||||
private static final int ESC_SELECT_RIGHT_PAREN = 4;
|
private static final int ESC_SELECT_RIGHT_PAREN = 4;
|
||||||
/** Escape processing: Have seen ESC and a character-set-select + char */
|
|
||||||
// private static final int ESC_SELECT_PLUS = 5;
|
|
||||||
/** Escape processing: "ESC [" or CSI (Control Sequence Introducer). */
|
/** Escape processing: "ESC [" or CSI (Control Sequence Introducer). */
|
||||||
private static final int ESC_CSI = 6;
|
private static final int ESC_CSI = 6;
|
||||||
/** Escape processing: ESC [ ? */
|
/** Escape processing: ESC [ ? */
|
||||||
@@ -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, 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).
|
* 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
|
* See http://www.xfree86.org/current/ctlseqs.html#The%20Alternate%20Screen%20Buffer
|
||||||
*/
|
*/
|
||||||
final TerminalBuffer mAltBuffer;
|
final TerminalBuffer mAltBuffer;
|
||||||
@@ -206,10 +204,15 @@ public final class TerminalEmulator {
|
|||||||
*/
|
*/
|
||||||
private boolean mAboutToAutoWrap;
|
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;
|
int mForeColor, mBackColor;
|
||||||
|
|
||||||
/** Current TextStyle effect */
|
/** Current {@link TextStyle} effect. */
|
||||||
private int mEffect;
|
private int mEffect;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -220,6 +223,7 @@ public final class TerminalEmulator {
|
|||||||
|
|
||||||
private byte mUtf8ToFollow, mUtf8Index;
|
private byte mUtf8ToFollow, mUtf8Index;
|
||||||
private final byte[] mUtf8InputBuffer = new byte[4];
|
private final byte[] mUtf8InputBuffer = new byte[4];
|
||||||
|
private int mLastEmittedCodePoint = -1;
|
||||||
|
|
||||||
public final TerminalColors mColors = new TerminalColors();
|
public final TerminalColors mColors = new TerminalColors();
|
||||||
|
|
||||||
@@ -297,6 +301,11 @@ public final class TerminalEmulator {
|
|||||||
* @param mouseButton one of the MOUSE_* constants of this class.
|
* @param mouseButton one of the MOUSE_* constants of this class.
|
||||||
*/
|
*/
|
||||||
public void sendMouseEvent(int mouseButton, int column, int row, boolean pressed) {
|
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)) {
|
if (mouseButton == MOUSE_LEFT_BUTTON_MOVED && !isDecsetInternalBitSet(DECSET_BIT_MOUSE_TRACKING_BUTTON_EVENT)) {
|
||||||
// Do not send tracking.
|
// Do not send tracking.
|
||||||
} else if (isDecsetInternalBitSet(DECSET_BIT_MOUSE_PROTOCOL_SGR)) {
|
} else if (isDecsetInternalBitSet(DECSET_BIT_MOUSE_PROTOCOL_SGR)) {
|
||||||
@@ -304,7 +313,7 @@ public final class TerminalEmulator {
|
|||||||
} else {
|
} else {
|
||||||
mouseButton = pressed ? mouseButton : 3; // 3 for release of all buttons.
|
mouseButton = pressed ? mouseButton : 3; // 3 for release of all buttons.
|
||||||
// Clip to screen, and clip to the limits of 8-bit data.
|
// 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) {
|
if (!out_of_bounds) {
|
||||||
byte[] data = {'\033', '[', 'M', (byte) (32 + mouseButton), (byte) (32 + column), (byte) (32 + row)};
|
byte[] data = {'\033', '[', 'M', (byte) (32 + mouseButton), (byte) (32 + column), (byte) (32 + row)};
|
||||||
mSession.write(data, 0, data.length);
|
mSession.write(data, 0, data.length);
|
||||||
@@ -416,10 +425,11 @@ public final class TerminalEmulator {
|
|||||||
mUtf8Index = mUtf8ToFollow = 0;
|
mUtf8Index = mUtf8ToFollow = 0;
|
||||||
|
|
||||||
if (codePoint >= 0x80 && codePoint <= 0x9F) {
|
if (codePoint >= 0x80 && codePoint <= 0x9F) {
|
||||||
// Sequence decoded to a C1 control character which is the same as escape followed by
|
// Sequence decoded to a C1 control character which we ignore. They are
|
||||||
// ((code & 0x7F) + 0x40).
|
// not used nowadays and increases the risk of messing up the terminal state
|
||||||
processCodePoint(/* escape (hexadecimal=0x1B, octal=033): */27);
|
// on binary input. XTerm does not allow them in utf-8:
|
||||||
processCodePoint((codePoint & 0x7F) + 0x40);
|
// "It is not possible to use a C1 control obtained from decoding the
|
||||||
|
// UTF-8 text" - http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
|
||||||
} else {
|
} else {
|
||||||
switch (Character.getType(codePoint)) {
|
switch (Character.getType(codePoint)) {
|
||||||
case Character.UNASSIGNED:
|
case Character.UNASSIGNED:
|
||||||
@@ -611,7 +621,7 @@ public final class TerminalEmulator {
|
|||||||
int left = Math.min(getArg(argIndex++, 1, true) + effectiveLeftMargin, effectiveRightMargin + 1);
|
int left = Math.min(getArg(argIndex++, 1, true) + effectiveLeftMargin, effectiveRightMargin + 1);
|
||||||
int bottom = Math.min(getArg(argIndex++, mRows, true) + effectiveTopMargin, effectiveBottomMargin);
|
int bottom = Math.min(getArg(argIndex++, mRows, true) + effectiveTopMargin, effectiveBottomMargin);
|
||||||
int right = Math.min(getArg(argIndex, mColumns, true) + effectiveLeftMargin, effectiveRightMargin);
|
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 row = top - 1; row < bottom; row++)
|
||||||
for (int col = left - 1; col < right; col++)
|
for (int col = left - 1; col < right; col++)
|
||||||
if (!selective || (TextStyle.decodeEffect(mScreen.getStyleAt(row, col)) & TextStyle.CHARACTER_ATTRIBUTE_PROTECTED) == 0)
|
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"
|
case 't': // "${CSI}${TOP}${LEFT}${BOTTOM}${RIGHT}${ATTRIBUTES}$t"
|
||||||
// Reverse attributes in rectangular area (DECRARA - http://www.vt100.net/docs/vt510-rm/DECRARA).
|
// Reverse attributes in rectangular area (DECRARA - http://www.vt100.net/docs/vt510-rm/DECRARA).
|
||||||
boolean reverse = b == 't';
|
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 top = Math.min(getArg(0, 1, true) - 1, effectiveBottomMargin) + effectiveTopMargin;
|
||||||
int left = Math.min(getArg(1, 1, true) - 1, effectiveRightMargin) + effectiveLeftMargin;
|
int left = Math.min(getArg(1, 1, true) - 1, effectiveRightMargin) + effectiveLeftMargin;
|
||||||
int bottom = Math.min(getArg(2, mRows, true) + 1, effectiveBottomMargin - 1) + effectiveTopMargin;
|
int bottom = Math.min(getArg(2, mRows, true) + 1, effectiveBottomMargin - 1) + effectiveTopMargin;
|
||||||
int right = Math.min(getArg(3, mColumns, true) + 1, effectiveRightMargin - 1) + effectiveLeftMargin;
|
int right = Math.min(getArg(3, mColumns, true) + 1, effectiveRightMargin - 1) + effectiveLeftMargin;
|
||||||
if (mArgIndex >= 4) {
|
if (mArgIndex >= 4) {
|
||||||
|
if (mArgIndex >= mArgs.length) mArgIndex = mArgs.length - 1;
|
||||||
for (int i = 4; i <= mArgIndex; i++) {
|
for (int i = 4; i <= mArgIndex; i++) {
|
||||||
int bits = 0;
|
int bits = 0;
|
||||||
boolean setOrClear = true; // True if setting, false if clearing.
|
boolean setOrClear = true; // True if setting, false if clearing.
|
||||||
@@ -717,7 +728,6 @@ public final class TerminalEmulator {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case ESC_PERCENT:
|
case ESC_PERCENT:
|
||||||
Log.i(EmulatorDebug.LOG_TAG, "Ignoring character set sequence 'ESC % " + (char) b + "'");
|
|
||||||
break;
|
break;
|
||||||
case ESC_OSC:
|
case ESC_OSC:
|
||||||
doOsc(b);
|
doOsc(b);
|
||||||
@@ -953,7 +963,7 @@ public final class TerminalEmulator {
|
|||||||
unknownSequence(b);
|
unknownSequence(b);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
int style = getStyle();
|
long style = getStyle();
|
||||||
for (int row = startRow; row < endRow; row++) {
|
for (int row = startRow; row < endRow; row++) {
|
||||||
for (int col = startCol; col < endCol; col++) {
|
for (int col = startCol; col < endCol; col++) {
|
||||||
if ((TextStyle.decodeEffect(mScreen.getStyleAt(row, col)) & TextStyle.CHARACTER_ATTRIBUTE_PROTECTED) == 0)
|
if ((TextStyle.decodeEffect(mScreen.getStyleAt(row, col)) & TextStyle.CHARACTER_ATTRIBUTE_PROTECTED) == 0)
|
||||||
@@ -963,6 +973,7 @@ public final class TerminalEmulator {
|
|||||||
break;
|
break;
|
||||||
case 'h':
|
case 'h':
|
||||||
case 'l':
|
case 'l':
|
||||||
|
if (mArgIndex >= mArgs.length) mArgIndex = mArgs.length - 1;
|
||||||
for (int i = 0; i <= mArgIndex; i++)
|
for (int i = 0; i <= mArgIndex; i++)
|
||||||
doDecSetOrReset(b == 'h', mArgs[i]);
|
doDecSetOrReset(b == 'h', mArgs[i]);
|
||||||
break;
|
break;
|
||||||
@@ -979,6 +990,7 @@ public final class TerminalEmulator {
|
|||||||
break;
|
break;
|
||||||
case 'r':
|
case 'r':
|
||||||
case 's':
|
case 's':
|
||||||
|
if (mArgIndex >= mArgs.length) mArgIndex = mArgs.length - 1;
|
||||||
for (int i = 0; i <= mArgIndex; i++) {
|
for (int i = 0; i <= mArgIndex; i++) {
|
||||||
int externalBit = mArgs[i];
|
int externalBit = mArgs[i];
|
||||||
int internalBit = mapDecSetBitToInternalBit(externalBit);
|
int internalBit = mapDecSetBitToInternalBit(externalBit);
|
||||||
@@ -1185,12 +1197,20 @@ public final class TerminalEmulator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void doLinefeed() {
|
private void doLinefeed() {
|
||||||
|
boolean belowScrollingRegion = mCursorRow >= mBottomMargin;
|
||||||
int newCursorRow = mCursorRow + 1;
|
int newCursorRow = mCursorRow + 1;
|
||||||
if (newCursorRow >= mBottomMargin) {
|
if (belowScrollingRegion) {
|
||||||
scrollDownOneLine();
|
// Move down (but not scroll) as long as we are above the last row.
|
||||||
newCursorRow = mBottomMargin - 1;
|
if (mCursorRow != mRows - 1) {
|
||||||
|
setCursorRow(newCursorRow);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (newCursorRow == mBottomMargin) {
|
||||||
|
scrollDownOneLine();
|
||||||
|
newCursorRow = mBottomMargin - 1;
|
||||||
|
}
|
||||||
|
setCursorRow(newCursorRow);
|
||||||
}
|
}
|
||||||
setCursorRow(newCursorRow);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void continueSequence(int state) {
|
private void continueSequence(int state) {
|
||||||
@@ -1305,6 +1325,8 @@ public final class TerminalEmulator {
|
|||||||
state.mSavedCursorRow = mCursorRow;
|
state.mSavedCursorRow = mCursorRow;
|
||||||
state.mSavedCursorCol = mCursorCol;
|
state.mSavedCursorCol = mCursorCol;
|
||||||
state.mSavedEffect = mEffect;
|
state.mSavedEffect = mEffect;
|
||||||
|
state.mSavedForeColor = mForeColor;
|
||||||
|
state.mSavedBackColor = mBackColor;
|
||||||
state.mSavedDecFlags = mCurrentDecSetFlags;
|
state.mSavedDecFlags = mCurrentDecSetFlags;
|
||||||
state.mUseLineDrawingG0 = mUseLineDrawingG0;
|
state.mUseLineDrawingG0 = mUseLineDrawingG0;
|
||||||
state.mUseLineDrawingG1 = mUseLineDrawingG1;
|
state.mUseLineDrawingG1 = mUseLineDrawingG1;
|
||||||
@@ -1316,6 +1338,8 @@ public final class TerminalEmulator {
|
|||||||
SavedScreenState state = (mScreen == mMainBuffer) ? mSavedStateMain : mSavedStateAlt;
|
SavedScreenState state = (mScreen == mMainBuffer) ? mSavedStateMain : mSavedStateAlt;
|
||||||
setCursorRowCol(state.mSavedCursorRow, state.mSavedCursorCol);
|
setCursorRowCol(state.mSavedCursorRow, state.mSavedCursorCol);
|
||||||
mEffect = state.mSavedEffect;
|
mEffect = state.mSavedEffect;
|
||||||
|
mForeColor = state.mSavedForeColor;
|
||||||
|
mBackColor = state.mSavedBackColor;
|
||||||
int mask = (DECSET_BIT_AUTOWRAP | DECSET_BIT_ORIGIN_MODE);
|
int mask = (DECSET_BIT_AUTOWRAP | DECSET_BIT_ORIGIN_MODE);
|
||||||
mCurrentDecSetFlags = (mCurrentDecSetFlags & ~mask) | (state.mSavedDecFlags & mask);
|
mCurrentDecSetFlags = (mCurrentDecSetFlags & ~mask) | (state.mSavedDecFlags & mask);
|
||||||
mUseLineDrawingG0 = state.mUseLineDrawingG0;
|
mUseLineDrawingG0 = state.mUseLineDrawingG0;
|
||||||
@@ -1499,6 +1523,11 @@ public final class TerminalEmulator {
|
|||||||
case '`': // Horizontal position absolute (HPA - http://www.vt100.net/docs/vt510-rm/HPA).
|
case '`': // Horizontal position absolute (HPA - http://www.vt100.net/docs/vt510-rm/HPA).
|
||||||
setCursorColRespectingOriginMode(getArg0(1) - 1);
|
setCursorColRespectingOriginMode(getArg0(1) - 1);
|
||||||
break;
|
break;
|
||||||
|
case 'b': // Repeat the preceding graphic character Ps times (REP).
|
||||||
|
if (mLastEmittedCodePoint == -1) break;
|
||||||
|
final int numRepeat = getArg0(1);
|
||||||
|
for (int i = 0; i < numRepeat; i++) emitCodePoint(mLastEmittedCodePoint);
|
||||||
|
break;
|
||||||
case 'c': // Primary Device Attributes (http://www.vt100.net/docs/vt510-rm/DA1) if argument is missing or zero.
|
case 'c': // Primary Device Attributes (http://www.vt100.net/docs/vt510-rm/DA1) if argument is missing or zero.
|
||||||
// The important part that may still be used by some (tmux stores this value but does not currently use it)
|
// The important part that may still be used by some (tmux stores this value but does not currently use it)
|
||||||
// is the first response parameter identifying the terminal service class, where we send 64 for "vt420".
|
// is the first response parameter identifying the terminal service class, where we send 64 for "vt420".
|
||||||
@@ -1555,7 +1584,7 @@ public final class TerminalEmulator {
|
|||||||
break;
|
break;
|
||||||
case 'r': // "CSI${top};${bottom}r" - set top and bottom Margins (DECSTBM).
|
case 'r': // "CSI${top};${bottom}r" - set top and bottom Margins (DECSTBM).
|
||||||
{
|
{
|
||||||
// http://www.vt100.net/docs/vt510-rm/DECSTBM
|
// https://vt100.net/docs/vt510-rm/DECSTBM.html
|
||||||
// The top margin defaults to 1, the bottom margin defaults to mRows.
|
// The top margin defaults to 1, the bottom margin defaults to mRows.
|
||||||
// The escape sequence numbers top 1..23, but we number top 0..22.
|
// The escape sequence numbers top 1..23, but we number top 0..22.
|
||||||
// The escape sequence numbers bottom 2..24, and so do we (because we use a zero based numbering
|
// The escape sequence numbers bottom 2..24, and so do we (because we use a zero based numbering
|
||||||
@@ -1564,6 +1593,7 @@ public final class TerminalEmulator {
|
|||||||
// Also require that top + 2 <= bottom.
|
// Also require that top + 2 <= bottom.
|
||||||
mTopMargin = Math.max(0, Math.min(getArg0(1) - 1, mRows - 2));
|
mTopMargin = Math.max(0, Math.min(getArg0(1) - 1, mRows - 2));
|
||||||
mBottomMargin = Math.max(mTopMargin + 2, Math.min(getArg1(mRows), mRows));
|
mBottomMargin = Math.max(mTopMargin + 2, Math.min(getArg1(mRows), mRows));
|
||||||
|
|
||||||
// DECSTBM moves the cursor to column 1, line 1 of the page respecting origin mode.
|
// DECSTBM moves the cursor to column 1, line 1 of the page respecting origin mode.
|
||||||
setCursorPosition(0, 0);
|
setCursorPosition(0, 0);
|
||||||
}
|
}
|
||||||
@@ -1637,6 +1667,7 @@ public final class TerminalEmulator {
|
|||||||
|
|
||||||
/** Select Graphic Rendition (SGR) - see http://en.wikipedia.org/wiki/ANSI_escape_code#graphics. */
|
/** Select Graphic Rendition (SGR) - see http://en.wikipedia.org/wiki/ANSI_escape_code#graphics. */
|
||||||
private void selectGraphicRendition() {
|
private void selectGraphicRendition() {
|
||||||
|
if (mArgIndex >= mArgs.length) mArgIndex = mArgs.length - 1;
|
||||||
for (int i = 0; i <= mArgIndex; i++) {
|
for (int i = 0; i <= mArgIndex; i++) {
|
||||||
int code = mArgs[i];
|
int code = mArgs[i];
|
||||||
if (code < 0) {
|
if (code < 0) {
|
||||||
@@ -1687,43 +1718,42 @@ public final class TerminalEmulator {
|
|||||||
} else if (code >= 30 && code <= 37) {
|
} else if (code >= 30 && code <= 37) {
|
||||||
mForeColor = code - 30;
|
mForeColor = code - 30;
|
||||||
} else if (code == 38 || code == 48) {
|
} else if (code == 38 || code == 48) {
|
||||||
// ISO-8613-3 controls to set foreground (38) or background (48) colors.
|
// Extended set foreground(38)/background (48) color.
|
||||||
// P_s = (38|48) ; 2 ; P_r ; P_g ; P_b => Set to RGB value in range (0-255).
|
// This is followed by either "2;$R;$G;$B" to set a 24-bit color or
|
||||||
// P_s = (38|48) ; 5 ; P_s => Set to indexed color.
|
// "5;$INDEX" to set an indexed color.
|
||||||
if (i + 2 <= mArgIndex) {
|
if (i + 2 > mArgIndex) continue;
|
||||||
int color = -1;
|
int firstArg = mArgs[i + 1];
|
||||||
int firstArg = mArgs[i + 1];
|
if (firstArg == 2) {
|
||||||
if (firstArg == 2) {
|
if (i + 4 > mArgIndex) {
|
||||||
if (i + 4 > mArgIndex) {
|
Log.w(EmulatorDebug.LOG_TAG, "Too few CSI" + code + ";2 RGB arguments");
|
||||||
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"
|
|
||||||
} else {
|
} else {
|
||||||
finishSequenceAndLogError("Invalid ISO-8613-3 SGR first argument: " + firstArg);
|
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) {
|
||||||
if (i != -1) {
|
finishSequenceAndLogError("Invalid RGB: " + red + "," + green + "," + blue);
|
||||||
if (color >= 0 && color < TextStyle.NUM_INDEXED_COLORS) {
|
|
||||||
if (code == 38) {
|
|
||||||
mForeColor = color;
|
|
||||||
} else {
|
|
||||||
mBackColor = color;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if (LOG_ESCAPE_SEQUENCES)
|
int argbColor = 0xff000000 | (red << 16) | (green << 8) | blue;
|
||||||
Log.w(EmulatorDebug.LOG_TAG, "Invalid color index: " + color);
|
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.
|
} else if (code == 39) { // Set default foreground color.
|
||||||
mForeColor = TextStyle.COLOR_INDEX_FOREGROUND;
|
mForeColor = TextStyle.COLOR_INDEX_FOREGROUND;
|
||||||
@@ -1924,7 +1954,7 @@ public final class TerminalEmulator {
|
|||||||
mScreen.blockSet(sx, sy, w, h, ' ', getStyle());
|
mScreen.blockSet(sx, sy, w, h, ' ', getStyle());
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getStyle() {
|
private long getStyle() {
|
||||||
return TextStyle.encode(mForeColor, mBackColor, mEffect);
|
return TextStyle.encode(mForeColor, mBackColor, mEffect);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2048,6 +2078,7 @@ public final class TerminalEmulator {
|
|||||||
buf.append(", escapeState=");
|
buf.append(", escapeState=");
|
||||||
buf.append(mEscapeState);
|
buf.append(mEscapeState);
|
||||||
boolean firstArg = true;
|
boolean firstArg = true;
|
||||||
|
if (mArgIndex >= mArgs.length) mArgIndex = mArgs.length - 1;
|
||||||
for (int i = 0; i <= mArgIndex; i++) {
|
for (int i = 0; i <= mArgIndex; i++) {
|
||||||
int value = mArgs[i];
|
int value = mArgs[i];
|
||||||
if (value >= 0) {
|
if (value >= 0) {
|
||||||
@@ -2080,6 +2111,7 @@ public final class TerminalEmulator {
|
|||||||
* @param codePoint The code point of the character to display
|
* @param codePoint The code point of the character to display
|
||||||
*/
|
*/
|
||||||
private void emitCodePoint(int codePoint) {
|
private void emitCodePoint(int codePoint) {
|
||||||
|
mLastEmittedCodePoint = codePoint;
|
||||||
if (mUseLineDrawingUsesG0 ? mUseLineDrawingG0 : mUseLineDrawingG1) {
|
if (mUseLineDrawingUsesG0 ? mUseLineDrawingG0 : mUseLineDrawingG1) {
|
||||||
// http://www.vt100.net/docs/vt102-ug/table5-15.html.
|
// http://www.vt100.net/docs/vt102-ug/table5-15.html.
|
||||||
switch (codePoint) {
|
switch (codePoint) {
|
||||||
@@ -2262,8 +2294,8 @@ public final class TerminalEmulator {
|
|||||||
mBottomMargin = mRows;
|
mBottomMargin = mRows;
|
||||||
mRightMargin = mColumns;
|
mRightMargin = mColumns;
|
||||||
mAboutToAutoWrap = false;
|
mAboutToAutoWrap = false;
|
||||||
mForeColor = TextStyle.COLOR_INDEX_FOREGROUND;
|
mForeColor = mSavedStateMain.mSavedForeColor = mSavedStateAlt.mSavedForeColor = TextStyle.COLOR_INDEX_FOREGROUND;
|
||||||
mBackColor = TextStyle.COLOR_INDEX_BACKGROUND;
|
mBackColor = mSavedStateMain.mSavedBackColor = mSavedStateAlt.mSavedBackColor = TextStyle.COLOR_INDEX_BACKGROUND;
|
||||||
setDefaultTabStops();
|
setDefaultTabStops();
|
||||||
|
|
||||||
mUseLineDrawingG0 = mUseLineDrawingG1 = false;
|
mUseLineDrawingG0 = mUseLineDrawingG1 = false;
|
||||||
@@ -2306,6 +2338,9 @@ public final class TerminalEmulator {
|
|||||||
public void paste(String text) {
|
public void paste(String text) {
|
||||||
// First: Always remove escape key and C1 control characters [0x80,0x9F]:
|
// First: Always remove escape key and C1 control characters [0x80,0x9F]:
|
||||||
text = text.replaceAll("(\u001B|[\u0080-\u009F])", "");
|
text = text.replaceAll("(\u001B|[\u0080-\u009F])", "");
|
||||||
|
// Second: Replace all newlines (\n) or CRLF (\r\n) with carriage returns (\r).
|
||||||
|
text = text.replaceAll("\r?\n", "\r");
|
||||||
|
|
||||||
// Then: Implement bracketed paste mode if enabled:
|
// Then: Implement bracketed paste mode if enabled:
|
||||||
boolean bracketed = isDecsetInternalBitSet(DECSET_BIT_BRACKETED_PASTE_MODE);
|
boolean bracketed = isDecsetInternalBitSet(DECSET_BIT_BRACKETED_PASTE_MODE);
|
||||||
if (bracketed) mSession.write("\033[200~");
|
if (bracketed) mSession.write("\033[200~");
|
||||||
@@ -2317,7 +2352,7 @@ public final class TerminalEmulator {
|
|||||||
static final class SavedScreenState {
|
static final class SavedScreenState {
|
||||||
/** Saved state of the cursor position, Used to implement the save/restore cursor position escape sequences. */
|
/** Saved state of the cursor position, Used to implement the save/restore cursor position escape sequences. */
|
||||||
int mSavedCursorRow, mSavedCursorCol;
|
int mSavedCursorRow, mSavedCursorCol;
|
||||||
int mSavedEffect;
|
int mSavedEffect, mSavedForeColor, mSavedBackColor;
|
||||||
int mSavedDecFlags;
|
int mSavedDecFlags;
|
||||||
boolean mUseLineDrawingG0, mUseLineDrawingG1, mUseLineDrawingUsesG0 = true;
|
boolean mUseLineDrawingG0, mUseLineDrawingG1, mUseLineDrawingUsesG0 = true;
|
||||||
}
|
}
|
||||||
@@ -4,7 +4,7 @@ import java.util.Arrays;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* A row in a terminal, composed of a fixed number of cells.
|
* A row in a terminal, composed of a fixed number of cells.
|
||||||
* <p/>
|
* <p>
|
||||||
* The text in the row is stored in a char[] array, {@link #mText}, for quick access during rendering.
|
* The text in the row is stored in a char[] array, {@link #mText}, for quick access during rendering.
|
||||||
*/
|
*/
|
||||||
public final class TerminalRow {
|
public final class TerminalRow {
|
||||||
@@ -20,18 +20,21 @@ public final class TerminalRow {
|
|||||||
/** If this row has been line wrapped due to text output at the end of line. */
|
/** If this row has been line wrapped due to text output at the end of line. */
|
||||||
boolean mLineWrap;
|
boolean mLineWrap;
|
||||||
/** The style bits of each cell in the row. See {@link TextStyle}. */
|
/** The style bits of each cell in the row. See {@link TextStyle}. */
|
||||||
final int[] mStyle;
|
final long[] mStyle;
|
||||||
|
/** If this row might contain chars with width != 1, used for deactivating fast path */
|
||||||
|
boolean mHasNonOneWidthOrSurrogateChars;
|
||||||
|
|
||||||
/** Construct a blank row (containing only whitespace, ' ') with a specified style. */
|
/** 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;
|
mColumns = columns;
|
||||||
mText = new char[(int) (SPARE_CAPACITY_FACTOR * columns)];
|
mText = new char[(int) (SPARE_CAPACITY_FACTOR * columns)];
|
||||||
mStyle = new int[columns];
|
mStyle = new long[columns];
|
||||||
clear(style);
|
clear(style);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** NOTE: The sourceX2 is exclusive. */
|
/** NOTE: The sourceX2 is exclusive. */
|
||||||
public void copyInterval(TerminalRow line, int sourceX1, int sourceX2, int destinationX) {
|
public void copyInterval(TerminalRow line, int sourceX1, int sourceX2, int destinationX) {
|
||||||
|
mHasNonOneWidthOrSurrogateChars |= line.mHasNonOneWidthOrSurrogateChars;
|
||||||
final int x1 = line.findStartOfColumn(sourceX1);
|
final int x1 = line.findStartOfColumn(sourceX1);
|
||||||
final int x2 = line.findStartOfColumn(sourceX2);
|
final int x2 = line.findStartOfColumn(sourceX2);
|
||||||
boolean startingFromSecondHalfOfWideChar = (sourceX1 > 0 && line.wideDisplayCharacterStartingAt(sourceX1 - 1));
|
boolean startingFromSecondHalfOfWideChar = (sourceX1 > 0 && line.wideDisplayCharacterStartingAt(sourceX1 - 1));
|
||||||
@@ -112,17 +115,29 @@ public final class TerminalRow {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clear(int style) {
|
public void clear(long style) {
|
||||||
Arrays.fill(mText, ' ');
|
Arrays.fill(mText, ' ');
|
||||||
Arrays.fill(mStyle, style);
|
Arrays.fill(mStyle, style);
|
||||||
mSpaceUsed = (short) mColumns;
|
mSpaceUsed = (short) mColumns;
|
||||||
|
mHasNonOneWidthOrSurrogateChars = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://github.com/steven676/Android-Terminal-Emulator/commit/9a47042620bec87617f0b4f5d50568535668fe26
|
// 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;
|
mStyle[columnToSet] = style;
|
||||||
|
|
||||||
final int newCodePointDisplayWidth = WcWidth.width(codePoint);
|
final int newCodePointDisplayWidth = WcWidth.width(codePoint);
|
||||||
|
|
||||||
|
// Fast path when we don't have any chars with width != 1
|
||||||
|
if (!mHasNonOneWidthOrSurrogateChars) {
|
||||||
|
if (codePoint >= Character.MIN_SUPPLEMENTARY_CODE_POINT || newCodePointDisplayWidth != 1) {
|
||||||
|
mHasNonOneWidthOrSurrogateChars = true;
|
||||||
|
} else {
|
||||||
|
mText[columnToSet] = (char) codePoint;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final boolean newIsCombining = newCodePointDisplayWidth <= 0;
|
final boolean newIsCombining = newCodePointDisplayWidth <= 0;
|
||||||
|
|
||||||
boolean wasExtraColForWideChar = (columnToSet > 0) && wideDisplayCharacterStartingAt(columnToSet - 1);
|
boolean wasExtraColForWideChar = (columnToSet > 0) && wideDisplayCharacterStartingAt(columnToSet - 1);
|
||||||
@@ -225,7 +240,7 @@ public final class TerminalRow {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public final int getStyle(int column) {
|
public final long getStyle(int column) {
|
||||||
return mStyle[column];
|
return mStyle[column];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -19,13 +19,13 @@ import java.util.UUID;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* A terminal session, consisting of a process coupled to a terminal interface.
|
* A terminal session, consisting of a process coupled to a terminal interface.
|
||||||
* <p/>
|
* <p>
|
||||||
* The subprocess will be executed by the constructor, and when the size is made known by a call to
|
* The subprocess will be executed by the constructor, and when the size is made known by a call to
|
||||||
* {@link #updateSize(int, int)} terminal emulation will begin and threads will be spawned to handle the subprocess I/O.
|
* {@link #updateSize(int, int)} terminal emulation will begin and threads will be spawned to handle the subprocess I/O.
|
||||||
* All terminal emulation and callback methods will be performed on the main thread.
|
* All terminal emulation and callback methods will be performed on the main thread.
|
||||||
* <p/>
|
* <p>
|
||||||
* The child process may be exited forcefully by using the {@link #finishIfRunning()} method.
|
* The child process may be exited forcefully by using the {@link #finishIfRunning()} method.
|
||||||
* <p/>
|
* <p>
|
||||||
* NOTE: The terminal session may outlive the EmulatorView, so be careful with callbacks!
|
* NOTE: The terminal session may outlive the EmulatorView, so be careful with callbacks!
|
||||||
*/
|
*/
|
||||||
public final class TerminalSession extends TerminalOutput {
|
public final class TerminalSession extends TerminalOutput {
|
||||||
@@ -123,12 +123,12 @@ public final class TerminalSession extends TerminalOutput {
|
|||||||
String exitDescription = "\r\n[Process completed";
|
String exitDescription = "\r\n[Process completed";
|
||||||
if (exitCode > 0) {
|
if (exitCode > 0) {
|
||||||
// Non-zero process exit.
|
// Non-zero process exit.
|
||||||
exitDescription += " with code " + exitCode;
|
exitDescription += " (code " + exitCode + ")";
|
||||||
} else if (exitCode < 0) {
|
} else if (exitCode < 0) {
|
||||||
// Negated signal.
|
// Negated signal.
|
||||||
exitDescription += " with signal " + (-exitCode);
|
exitDescription += " (signal " + (-exitCode) + ")";
|
||||||
}
|
}
|
||||||
exitDescription += " - press Enter to close]";
|
exitDescription += " - press Enter]";
|
||||||
|
|
||||||
byte[] bytesToWrite = exitDescription.getBytes(StandardCharsets.UTF_8);
|
byte[] bytesToWrite = exitDescription.getBytes(StandardCharsets.UTF_8);
|
||||||
mEmulator.append(bytesToWrite, bytesToWrite.length);
|
mEmulator.append(bytesToWrite, bytesToWrite.length);
|
||||||
@@ -173,7 +173,7 @@ public final class TerminalSession extends TerminalOutput {
|
|||||||
* @param rows The number of rows in the terminal window.
|
* @param rows The number of rows in the terminal window.
|
||||||
*/
|
*/
|
||||||
public void initializeEmulator(int columns, int rows) {
|
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];
|
int[] processId = new int[1];
|
||||||
mTerminalFileDescriptor = JNI.createSubprocess(mShellPath, mCwd, mArgs, mEnv, processId, rows, columns);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
458
terminal-emulator/src/main/java/com/termux/terminal/WcWidth.java
Normal file
@@ -0,0 +1,458 @@
|
|||||||
|
package com.termux.terminal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of wcwidth(3) for Unicode 9.
|
||||||
|
*
|
||||||
|
* Implementation from https://github.com/jquast/wcwidth but we return 0 for unprintable characters.
|
||||||
|
*/
|
||||||
|
public final class WcWidth {
|
||||||
|
|
||||||
|
// From https://github.com/jquast/wcwidth/blob/master/wcwidth/table_zero.py
|
||||||
|
// t commit 0d7de112202cc8b2ebe9232ff4a5c954f19d561a (2016-07-02):
|
||||||
|
private static final int[][] ZERO_WIDTH = {
|
||||||
|
{0x0300, 0x036f}, // Combining Grave Accent ..Combining Latin Small Le
|
||||||
|
{0x0483, 0x0489}, // Combining Cyrillic Titlo..Combining Cyrillic Milli
|
||||||
|
{0x0591, 0x05bd}, // Hebrew Accent Etnahta ..Hebrew Point Meteg
|
||||||
|
{0x05bf, 0x05bf}, // Hebrew Point Rafe ..Hebrew Point Rafe
|
||||||
|
{0x05c1, 0x05c2}, // Hebrew Point Shin Dot ..Hebrew Point Sin Dot
|
||||||
|
{0x05c4, 0x05c5}, // Hebrew Mark Upper Dot ..Hebrew Mark Lower Dot
|
||||||
|
{0x05c7, 0x05c7}, // Hebrew Point Qamats Qata..Hebrew Point Qamats Qata
|
||||||
|
{0x0610, 0x061a}, // Arabic Sign Sallallahou ..Arabic Small Kasra
|
||||||
|
{0x064b, 0x065f}, // Arabic Fathatan ..Arabic Wavy Hamza Below
|
||||||
|
{0x0670, 0x0670}, // Arabic Letter Superscrip..Arabic Letter Superscrip
|
||||||
|
{0x06d6, 0x06dc}, // Arabic Small High Ligatu..Arabic Small High Seen
|
||||||
|
{0x06df, 0x06e4}, // Arabic Small High Rounde..Arabic Small High Madda
|
||||||
|
{0x06e7, 0x06e8}, // Arabic Small High Yeh ..Arabic Small High Noon
|
||||||
|
{0x06ea, 0x06ed}, // Arabic Empty Centre Low ..Arabic Small Low Meem
|
||||||
|
{0x0711, 0x0711}, // Syriac Letter Superscrip..Syriac Letter Superscrip
|
||||||
|
{0x0730, 0x074a}, // Syriac Pthaha Above ..Syriac Barrekh
|
||||||
|
{0x07a6, 0x07b0}, // Thaana Abafili ..Thaana Sukun
|
||||||
|
{0x07eb, 0x07f3}, // Nko Combining Sh||t High..Nko Combining Double Dot
|
||||||
|
{0x0816, 0x0819}, // Samaritan Mark In ..Samaritan Mark Dagesh
|
||||||
|
{0x081b, 0x0823}, // Samaritan Mark Epentheti..Samaritan Vowel Sign A
|
||||||
|
{0x0825, 0x0827}, // Samaritan Vowel Sign Sho..Samaritan Vowel Sign U
|
||||||
|
{0x0829, 0x082d}, // Samaritan Vowel Sign Lon..Samaritan Mark Nequdaa
|
||||||
|
{0x0859, 0x085b}, // Mandaic Affrication Mark..Mandaic Gemination Mark
|
||||||
|
{0x08d4, 0x08e1}, // (nil) ..
|
||||||
|
{0x08e3, 0x0902}, // Arabic Turned Damma Belo..Devanagari Sign Anusvara
|
||||||
|
{0x093a, 0x093a}, // Devanagari Vowel Sign Oe..Devanagari Vowel Sign Oe
|
||||||
|
{0x093c, 0x093c}, // Devanagari Sign Nukta ..Devanagari Sign Nukta
|
||||||
|
{0x0941, 0x0948}, // Devanagari Vowel Sign U ..Devanagari Vowel Sign Ai
|
||||||
|
{0x094d, 0x094d}, // Devanagari Sign Virama ..Devanagari Sign Virama
|
||||||
|
{0x0951, 0x0957}, // Devanagari Stress Sign U..Devanagari Vowel Sign Uu
|
||||||
|
{0x0962, 0x0963}, // Devanagari Vowel Sign Vo..Devanagari Vowel Sign Vo
|
||||||
|
{0x0981, 0x0981}, // Bengali Sign Candrabindu..Bengali Sign Candrabindu
|
||||||
|
{0x09bc, 0x09bc}, // Bengali Sign Nukta ..Bengali Sign Nukta
|
||||||
|
{0x09c1, 0x09c4}, // Bengali Vowel Sign U ..Bengali Vowel Sign Vocal
|
||||||
|
{0x09cd, 0x09cd}, // Bengali Sign Virama ..Bengali Sign Virama
|
||||||
|
{0x09e2, 0x09e3}, // Bengali Vowel Sign Vocal..Bengali Vowel Sign Vocal
|
||||||
|
{0x0a01, 0x0a02}, // Gurmukhi Sign Adak Bindi..Gurmukhi Sign Bindi
|
||||||
|
{0x0a3c, 0x0a3c}, // Gurmukhi Sign Nukta ..Gurmukhi Sign Nukta
|
||||||
|
{0x0a41, 0x0a42}, // Gurmukhi Vowel Sign U ..Gurmukhi Vowel Sign Uu
|
||||||
|
{0x0a47, 0x0a48}, // Gurmukhi Vowel Sign Ee ..Gurmukhi Vowel Sign Ai
|
||||||
|
{0x0a4b, 0x0a4d}, // Gurmukhi Vowel Sign Oo ..Gurmukhi Sign Virama
|
||||||
|
{0x0a51, 0x0a51}, // Gurmukhi Sign Udaat ..Gurmukhi Sign Udaat
|
||||||
|
{0x0a70, 0x0a71}, // Gurmukhi Tippi ..Gurmukhi Addak
|
||||||
|
{0x0a75, 0x0a75}, // Gurmukhi Sign Yakash ..Gurmukhi Sign Yakash
|
||||||
|
{0x0a81, 0x0a82}, // Gujarati Sign Candrabind..Gujarati Sign Anusvara
|
||||||
|
{0x0abc, 0x0abc}, // Gujarati Sign Nukta ..Gujarati Sign Nukta
|
||||||
|
{0x0ac1, 0x0ac5}, // Gujarati Vowel Sign U ..Gujarati Vowel Sign Cand
|
||||||
|
{0x0ac7, 0x0ac8}, // Gujarati Vowel Sign E ..Gujarati Vowel Sign Ai
|
||||||
|
{0x0acd, 0x0acd}, // Gujarati Sign Virama ..Gujarati Sign Virama
|
||||||
|
{0x0ae2, 0x0ae3}, // Gujarati Vowel Sign Voca..Gujarati Vowel Sign Voca
|
||||||
|
{0x0b01, 0x0b01}, // ||iya Sign Candrabindu ..||iya Sign Candrabindu
|
||||||
|
{0x0b3c, 0x0b3c}, // ||iya Sign Nukta ..||iya Sign Nukta
|
||||||
|
{0x0b3f, 0x0b3f}, // ||iya Vowel Sign I ..||iya Vowel Sign I
|
||||||
|
{0x0b41, 0x0b44}, // ||iya Vowel Sign U ..||iya Vowel Sign Vocalic
|
||||||
|
{0x0b4d, 0x0b4d}, // ||iya Sign Virama ..||iya Sign Virama
|
||||||
|
{0x0b56, 0x0b56}, // ||iya Ai Length Mark ..||iya Ai Length Mark
|
||||||
|
{0x0b62, 0x0b63}, // ||iya Vowel Sign Vocalic..||iya Vowel Sign Vocalic
|
||||||
|
{0x0b82, 0x0b82}, // Tamil Sign Anusvara ..Tamil Sign Anusvara
|
||||||
|
{0x0bc0, 0x0bc0}, // Tamil Vowel Sign Ii ..Tamil Vowel Sign Ii
|
||||||
|
{0x0bcd, 0x0bcd}, // Tamil Sign Virama ..Tamil Sign Virama
|
||||||
|
{0x0c00, 0x0c00}, // Telugu Sign Combining Ca..Telugu Sign Combining Ca
|
||||||
|
{0x0c3e, 0x0c40}, // Telugu Vowel Sign Aa ..Telugu Vowel Sign Ii
|
||||||
|
{0x0c46, 0x0c48}, // Telugu Vowel Sign E ..Telugu Vowel Sign Ai
|
||||||
|
{0x0c4a, 0x0c4d}, // Telugu Vowel Sign O ..Telugu Sign Virama
|
||||||
|
{0x0c55, 0x0c56}, // Telugu Length Mark ..Telugu Ai Length Mark
|
||||||
|
{0x0c62, 0x0c63}, // Telugu Vowel Sign Vocali..Telugu Vowel Sign Vocali
|
||||||
|
{0x0c81, 0x0c81}, // Kannada Sign Candrabindu..Kannada Sign Candrabindu
|
||||||
|
{0x0cbc, 0x0cbc}, // Kannada Sign Nukta ..Kannada Sign Nukta
|
||||||
|
{0x0cbf, 0x0cbf}, // Kannada Vowel Sign I ..Kannada Vowel Sign I
|
||||||
|
{0x0cc6, 0x0cc6}, // Kannada Vowel Sign E ..Kannada Vowel Sign E
|
||||||
|
{0x0ccc, 0x0ccd}, // Kannada Vowel Sign Au ..Kannada Sign Virama
|
||||||
|
{0x0ce2, 0x0ce3}, // Kannada Vowel Sign Vocal..Kannada Vowel Sign Vocal
|
||||||
|
{0x0d01, 0x0d01}, // Malayalam Sign Candrabin..Malayalam Sign Candrabin
|
||||||
|
{0x0d41, 0x0d44}, // Malayalam Vowel Sign U ..Malayalam Vowel Sign Voc
|
||||||
|
{0x0d4d, 0x0d4d}, // Malayalam Sign Virama ..Malayalam Sign Virama
|
||||||
|
{0x0d62, 0x0d63}, // Malayalam Vowel Sign Voc..Malayalam Vowel Sign Voc
|
||||||
|
{0x0dca, 0x0dca}, // Sinhala Sign Al-lakuna ..Sinhala Sign Al-lakuna
|
||||||
|
{0x0dd2, 0x0dd4}, // Sinhala Vowel Sign Ketti..Sinhala Vowel Sign Ketti
|
||||||
|
{0x0dd6, 0x0dd6}, // Sinhala Vowel Sign Diga ..Sinhala Vowel Sign Diga
|
||||||
|
{0x0e31, 0x0e31}, // Thai Character Mai Han-a..Thai Character Mai Han-a
|
||||||
|
{0x0e34, 0x0e3a}, // Thai Character Sara I ..Thai Character Phinthu
|
||||||
|
{0x0e47, 0x0e4e}, // Thai Character Maitaikhu..Thai Character Yamakkan
|
||||||
|
{0x0eb1, 0x0eb1}, // Lao Vowel Sign Mai Kan ..Lao Vowel Sign Mai Kan
|
||||||
|
{0x0eb4, 0x0eb9}, // Lao Vowel Sign I ..Lao Vowel Sign Uu
|
||||||
|
{0x0ebb, 0x0ebc}, // Lao Vowel Sign Mai Kon ..Lao Semivowel Sign Lo
|
||||||
|
{0x0ec8, 0x0ecd}, // Lao Tone Mai Ek ..Lao Niggahita
|
||||||
|
{0x0f18, 0x0f19}, // Tibetan Astrological Sig..Tibetan Astrological Sig
|
||||||
|
{0x0f35, 0x0f35}, // Tibetan Mark Ngas Bzung ..Tibetan Mark Ngas Bzung
|
||||||
|
{0x0f37, 0x0f37}, // Tibetan Mark Ngas Bzung ..Tibetan Mark Ngas Bzung
|
||||||
|
{0x0f39, 0x0f39}, // Tibetan Mark Tsa -phru ..Tibetan Mark Tsa -phru
|
||||||
|
{0x0f71, 0x0f7e}, // Tibetan Vowel Sign Aa ..Tibetan Sign Rjes Su Nga
|
||||||
|
{0x0f80, 0x0f84}, // Tibetan Vowel Sign Rever..Tibetan Mark Halanta
|
||||||
|
{0x0f86, 0x0f87}, // Tibetan Sign Lci Rtags ..Tibetan Sign Yang Rtags
|
||||||
|
{0x0f8d, 0x0f97}, // Tibetan Subjoined Sign L..Tibetan Subjoined Letter
|
||||||
|
{0x0f99, 0x0fbc}, // Tibetan Subjoined Letter..Tibetan Subjoined Letter
|
||||||
|
{0x0fc6, 0x0fc6}, // Tibetan Symbol Padma Gda..Tibetan Symbol Padma Gda
|
||||||
|
{0x102d, 0x1030}, // Myanmar Vowel Sign I ..Myanmar Vowel Sign Uu
|
||||||
|
{0x1032, 0x1037}, // Myanmar Vowel Sign Ai ..Myanmar Sign Dot Below
|
||||||
|
{0x1039, 0x103a}, // Myanmar Sign Virama ..Myanmar Sign Asat
|
||||||
|
{0x103d, 0x103e}, // Myanmar Consonant Sign M..Myanmar Consonant Sign M
|
||||||
|
{0x1058, 0x1059}, // Myanmar Vowel Sign Vocal..Myanmar Vowel Sign Vocal
|
||||||
|
{0x105e, 0x1060}, // Myanmar Consonant Sign M..Myanmar Consonant Sign M
|
||||||
|
{0x1071, 0x1074}, // Myanmar Vowel Sign Geba ..Myanmar Vowel Sign Kayah
|
||||||
|
{0x1082, 0x1082}, // Myanmar Consonant Sign S..Myanmar Consonant Sign S
|
||||||
|
{0x1085, 0x1086}, // Myanmar Vowel Sign Shan ..Myanmar Vowel Sign Shan
|
||||||
|
{0x108d, 0x108d}, // Myanmar Sign Shan Counci..Myanmar Sign Shan Counci
|
||||||
|
{0x109d, 0x109d}, // Myanmar Vowel Sign Aiton..Myanmar Vowel Sign Aiton
|
||||||
|
{0x135d, 0x135f}, // Ethiopic Combining Gemin..Ethiopic Combining Gemin
|
||||||
|
{0x1712, 0x1714}, // Tagalog Vowel Sign I ..Tagalog Sign Virama
|
||||||
|
{0x1732, 0x1734}, // Hanunoo Vowel Sign I ..Hanunoo Sign Pamudpod
|
||||||
|
{0x1752, 0x1753}, // Buhid Vowel Sign I ..Buhid Vowel Sign U
|
||||||
|
{0x1772, 0x1773}, // Tagbanwa Vowel Sign I ..Tagbanwa Vowel Sign U
|
||||||
|
{0x17b4, 0x17b5}, // Khmer Vowel Inherent Aq ..Khmer Vowel Inherent Aa
|
||||||
|
{0x17b7, 0x17bd}, // Khmer Vowel Sign I ..Khmer Vowel Sign Ua
|
||||||
|
{0x17c6, 0x17c6}, // Khmer Sign Nikahit ..Khmer Sign Nikahit
|
||||||
|
{0x17c9, 0x17d3}, // Khmer Sign Muusikatoan ..Khmer Sign Bathamasat
|
||||||
|
{0x17dd, 0x17dd}, // Khmer Sign Atthacan ..Khmer Sign Atthacan
|
||||||
|
{0x180b, 0x180d}, // Mongolian Free Variation..Mongolian Free Variation
|
||||||
|
{0x1885, 0x1886}, // Mongolian Letter Ali Gal..Mongolian Letter Ali Gal
|
||||||
|
{0x18a9, 0x18a9}, // Mongolian Letter Ali Gal..Mongolian Letter Ali Gal
|
||||||
|
{0x1920, 0x1922}, // Limbu Vowel Sign A ..Limbu Vowel Sign U
|
||||||
|
{0x1927, 0x1928}, // Limbu Vowel Sign E ..Limbu Vowel Sign O
|
||||||
|
{0x1932, 0x1932}, // Limbu Small Letter Anusv..Limbu Small Letter Anusv
|
||||||
|
{0x1939, 0x193b}, // Limbu Sign Mukphreng ..Limbu Sign Sa-i
|
||||||
|
{0x1a17, 0x1a18}, // Buginese Vowel Sign I ..Buginese Vowel Sign U
|
||||||
|
{0x1a1b, 0x1a1b}, // Buginese Vowel Sign Ae ..Buginese Vowel Sign Ae
|
||||||
|
{0x1a56, 0x1a56}, // Tai Tham Consonant Sign ..Tai Tham Consonant Sign
|
||||||
|
{0x1a58, 0x1a5e}, // Tai Tham Sign Mai Kang L..Tai Tham Consonant Sign
|
||||||
|
{0x1a60, 0x1a60}, // Tai Tham Sign Sakot ..Tai Tham Sign Sakot
|
||||||
|
{0x1a62, 0x1a62}, // Tai Tham Vowel Sign Mai ..Tai Tham Vowel Sign Mai
|
||||||
|
{0x1a65, 0x1a6c}, // Tai Tham Vowel Sign I ..Tai Tham Vowel Sign Oa B
|
||||||
|
{0x1a73, 0x1a7c}, // Tai Tham Vowel Sign Oa A..Tai Tham Sign Khuen-lue
|
||||||
|
{0x1a7f, 0x1a7f}, // Tai Tham Combining Crypt..Tai Tham Combining Crypt
|
||||||
|
{0x1ab0, 0x1abe}, // Combining Doubled Circum..Combining Parentheses Ov
|
||||||
|
{0x1b00, 0x1b03}, // Balinese Sign Ulu Ricem ..Balinese Sign Surang
|
||||||
|
{0x1b34, 0x1b34}, // Balinese Sign Rerekan ..Balinese Sign Rerekan
|
||||||
|
{0x1b36, 0x1b3a}, // Balinese Vowel Sign Ulu ..Balinese Vowel Sign Ra R
|
||||||
|
{0x1b3c, 0x1b3c}, // Balinese Vowel Sign La L..Balinese Vowel Sign La L
|
||||||
|
{0x1b42, 0x1b42}, // Balinese Vowel Sign Pepe..Balinese Vowel Sign Pepe
|
||||||
|
{0x1b6b, 0x1b73}, // Balinese Musical Symbol ..Balinese Musical Symbol
|
||||||
|
{0x1b80, 0x1b81}, // Sundanese Sign Panyecek ..Sundanese Sign Panglayar
|
||||||
|
{0x1ba2, 0x1ba5}, // Sundanese Consonant Sign..Sundanese Vowel Sign Pan
|
||||||
|
{0x1ba8, 0x1ba9}, // Sundanese Vowel Sign Pam..Sundanese Vowel Sign Pan
|
||||||
|
{0x1bab, 0x1bad}, // Sundanese Sign Virama ..Sundanese Consonant Sign
|
||||||
|
{0x1be6, 0x1be6}, // Batak Sign Tompi ..Batak Sign Tompi
|
||||||
|
{0x1be8, 0x1be9}, // Batak Vowel Sign Pakpak ..Batak Vowel Sign Ee
|
||||||
|
{0x1bed, 0x1bed}, // Batak Vowel Sign Karo O ..Batak Vowel Sign Karo O
|
||||||
|
{0x1bef, 0x1bf1}, // Batak Vowel Sign U F|| S..Batak Consonant Sign H
|
||||||
|
{0x1c2c, 0x1c33}, // Lepcha Vowel Sign E ..Lepcha Consonant Sign T
|
||||||
|
{0x1c36, 0x1c37}, // Lepcha Sign Ran ..Lepcha Sign Nukta
|
||||||
|
{0x1cd0, 0x1cd2}, // Vedic Tone Karshana ..Vedic Tone Prenkha
|
||||||
|
{0x1cd4, 0x1ce0}, // Vedic Sign Yajurvedic Mi..Vedic Tone Rigvedic Kash
|
||||||
|
{0x1ce2, 0x1ce8}, // Vedic Sign Visarga Svari..Vedic Sign Visarga Anuda
|
||||||
|
{0x1ced, 0x1ced}, // Vedic Sign Tiryak ..Vedic Sign Tiryak
|
||||||
|
{0x1cf4, 0x1cf4}, // Vedic Tone Candra Above ..Vedic Tone Candra Above
|
||||||
|
{0x1cf8, 0x1cf9}, // Vedic Tone Ring Above ..Vedic Tone Double Ring A
|
||||||
|
{0x1dc0, 0x1df5}, // Combining Dotted Grave A..Combining Up Tack Above
|
||||||
|
{0x1dfb, 0x1dff}, // (nil) ..Combining Right Arrowhea
|
||||||
|
{0x20d0, 0x20f0}, // Combining Left Harpoon A..Combining Asterisk Above
|
||||||
|
{0x2cef, 0x2cf1}, // Coptic Combining Ni Abov..Coptic Combining Spiritu
|
||||||
|
{0x2d7f, 0x2d7f}, // Tifinagh Consonant Joine..Tifinagh Consonant Joine
|
||||||
|
{0x2de0, 0x2dff}, // Combining Cyrillic Lette..Combining Cyrillic Lette
|
||||||
|
{0x302a, 0x302d}, // Ideographic Level Tone M..Ideographic Entering Ton
|
||||||
|
{0x3099, 0x309a}, // Combining Katakana-hirag..Combining Katakana-hirag
|
||||||
|
{0xa66f, 0xa672}, // Combining Cyrillic Vzmet..Combining Cyrillic Thous
|
||||||
|
{0xa674, 0xa67d}, // Combining Cyrillic Lette..Combining Cyrillic Payer
|
||||||
|
{0xa69e, 0xa69f}, // Combining Cyrillic Lette..Combining Cyrillic Lette
|
||||||
|
{0xa6f0, 0xa6f1}, // Bamum Combining Mark Koq..Bamum Combining Mark Tuk
|
||||||
|
{0xa802, 0xa802}, // Syloti Nagri Sign Dvisva..Syloti Nagri Sign Dvisva
|
||||||
|
{0xa806, 0xa806}, // Syloti Nagri Sign Hasant..Syloti Nagri Sign Hasant
|
||||||
|
{0xa80b, 0xa80b}, // Syloti Nagri Sign Anusva..Syloti Nagri Sign Anusva
|
||||||
|
{0xa825, 0xa826}, // Syloti Nagri Vowel Sign ..Syloti Nagri Vowel Sign
|
||||||
|
{0xa8c4, 0xa8c5}, // Saurashtra Sign Virama ..
|
||||||
|
{0xa8e0, 0xa8f1}, // Combining Devanagari Dig..Combining Devanagari Sig
|
||||||
|
{0xa926, 0xa92d}, // Kayah Li Vowel Ue ..Kayah Li Tone Calya Plop
|
||||||
|
{0xa947, 0xa951}, // Rejang Vowel Sign I ..Rejang Consonant Sign R
|
||||||
|
{0xa980, 0xa982}, // Javanese Sign Panyangga ..Javanese Sign Layar
|
||||||
|
{0xa9b3, 0xa9b3}, // Javanese Sign Cecak Telu..Javanese Sign Cecak Telu
|
||||||
|
{0xa9b6, 0xa9b9}, // Javanese Vowel Sign Wulu..Javanese Vowel Sign Suku
|
||||||
|
{0xa9bc, 0xa9bc}, // Javanese Vowel Sign Pepe..Javanese Vowel Sign Pepe
|
||||||
|
{0xa9e5, 0xa9e5}, // Myanmar Sign Shan Saw ..Myanmar Sign Shan Saw
|
||||||
|
{0xaa29, 0xaa2e}, // Cham Vowel Sign Aa ..Cham Vowel Sign Oe
|
||||||
|
{0xaa31, 0xaa32}, // Cham Vowel Sign Au ..Cham Vowel Sign Ue
|
||||||
|
{0xaa35, 0xaa36}, // Cham Consonant Sign La ..Cham Consonant Sign Wa
|
||||||
|
{0xaa43, 0xaa43}, // Cham Consonant Sign Fina..Cham Consonant Sign Fina
|
||||||
|
{0xaa4c, 0xaa4c}, // Cham Consonant Sign Fina..Cham Consonant Sign Fina
|
||||||
|
{0xaa7c, 0xaa7c}, // Myanmar Sign Tai Laing T..Myanmar Sign Tai Laing T
|
||||||
|
{0xaab0, 0xaab0}, // Tai Viet Mai Kang ..Tai Viet Mai Kang
|
||||||
|
{0xaab2, 0xaab4}, // Tai Viet Vowel I ..Tai Viet Vowel U
|
||||||
|
{0xaab7, 0xaab8}, // Tai Viet Mai Khit ..Tai Viet Vowel Ia
|
||||||
|
{0xaabe, 0xaabf}, // Tai Viet Vowel Am ..Tai Viet Tone Mai Ek
|
||||||
|
{0xaac1, 0xaac1}, // Tai Viet Tone Mai Tho ..Tai Viet Tone Mai Tho
|
||||||
|
{0xaaec, 0xaaed}, // Meetei Mayek Vowel Sign ..Meetei Mayek Vowel Sign
|
||||||
|
{0xaaf6, 0xaaf6}, // Meetei Mayek Virama ..Meetei Mayek Virama
|
||||||
|
{0xabe5, 0xabe5}, // Meetei Mayek Vowel Sign ..Meetei Mayek Vowel Sign
|
||||||
|
{0xabe8, 0xabe8}, // Meetei Mayek Vowel Sign ..Meetei Mayek Vowel Sign
|
||||||
|
{0xabed, 0xabed}, // Meetei Mayek Apun Iyek ..Meetei Mayek Apun Iyek
|
||||||
|
{0xfb1e, 0xfb1e}, // Hebrew Point Judeo-spani..Hebrew Point Judeo-spani
|
||||||
|
{0xfe00, 0xfe0f}, // Variation Select||-1 ..Variation Select||-16
|
||||||
|
{0xfe20, 0xfe2f}, // Combining Ligature Left ..Combining Cyrillic Titlo
|
||||||
|
{0x101fd, 0x101fd}, // Phaistos Disc Sign Combi..Phaistos Disc Sign Combi
|
||||||
|
{0x102e0, 0x102e0}, // Coptic Epact Thousands M..Coptic Epact Thousands M
|
||||||
|
{0x10376, 0x1037a}, // Combining Old Permic Let..Combining Old Permic Let
|
||||||
|
{0x10a01, 0x10a03}, // Kharoshthi Vowel Sign I ..Kharoshthi Vowel Sign Vo
|
||||||
|
{0x10a05, 0x10a06}, // Kharoshthi Vowel Sign E ..Kharoshthi Vowel Sign O
|
||||||
|
{0x10a0c, 0x10a0f}, // Kharoshthi Vowel Length ..Kharoshthi Sign Visarga
|
||||||
|
{0x10a38, 0x10a3a}, // Kharoshthi Sign Bar Abov..Kharoshthi Sign Dot Belo
|
||||||
|
{0x10a3f, 0x10a3f}, // Kharoshthi Virama ..Kharoshthi Virama
|
||||||
|
{0x10ae5, 0x10ae6}, // Manichaean Abbreviation ..Manichaean Abbreviation
|
||||||
|
{0x11001, 0x11001}, // Brahmi Sign Anusvara ..Brahmi Sign Anusvara
|
||||||
|
{0x11038, 0x11046}, // Brahmi Vowel Sign Aa ..Brahmi Virama
|
||||||
|
{0x1107f, 0x11081}, // Brahmi Number Joiner ..Kaithi Sign Anusvara
|
||||||
|
{0x110b3, 0x110b6}, // Kaithi Vowel Sign U ..Kaithi Vowel Sign Ai
|
||||||
|
{0x110b9, 0x110ba}, // Kaithi Sign Virama ..Kaithi Sign Nukta
|
||||||
|
{0x11100, 0x11102}, // Chakma Sign Candrabindu ..Chakma Sign Visarga
|
||||||
|
{0x11127, 0x1112b}, // Chakma Vowel Sign A ..Chakma Vowel Sign Uu
|
||||||
|
{0x1112d, 0x11134}, // Chakma Vowel Sign Ai ..Chakma Maayyaa
|
||||||
|
{0x11173, 0x11173}, // Mahajani Sign Nukta ..Mahajani Sign Nukta
|
||||||
|
{0x11180, 0x11181}, // Sharada Sign Candrabindu..Sharada Sign Anusvara
|
||||||
|
{0x111b6, 0x111be}, // Sharada Vowel Sign U ..Sharada Vowel Sign O
|
||||||
|
{0x111ca, 0x111cc}, // Sharada Sign Nukta ..Sharada Extra Sh||t Vowe
|
||||||
|
{0x1122f, 0x11231}, // Khojki Vowel Sign U ..Khojki Vowel Sign Ai
|
||||||
|
{0x11234, 0x11234}, // Khojki Sign Anusvara ..Khojki Sign Anusvara
|
||||||
|
{0x11236, 0x11237}, // Khojki Sign Nukta ..Khojki Sign Shadda
|
||||||
|
{0x1123e, 0x1123e}, // (nil) ..
|
||||||
|
{0x112df, 0x112df}, // Khudawadi Sign Anusvara ..Khudawadi Sign Anusvara
|
||||||
|
{0x112e3, 0x112ea}, // Khudawadi Vowel Sign U ..Khudawadi Sign Virama
|
||||||
|
{0x11300, 0x11301}, // Grantha Sign Combining A..Grantha Sign Candrabindu
|
||||||
|
{0x1133c, 0x1133c}, // Grantha Sign Nukta ..Grantha Sign Nukta
|
||||||
|
{0x11340, 0x11340}, // Grantha Vowel Sign Ii ..Grantha Vowel Sign Ii
|
||||||
|
{0x11366, 0x1136c}, // Combining Grantha Digit ..Combining Grantha Digit
|
||||||
|
{0x11370, 0x11374}, // Combining Grantha Letter..Combining Grantha Letter
|
||||||
|
{0x11438, 0x1143f}, // (nil) ..
|
||||||
|
{0x11442, 0x11444}, // (nil) ..
|
||||||
|
{0x11446, 0x11446}, // (nil) ..
|
||||||
|
{0x114b3, 0x114b8}, // Tirhuta Vowel Sign U ..Tirhuta Vowel Sign Vocal
|
||||||
|
{0x114ba, 0x114ba}, // Tirhuta Vowel Sign Sh||t..Tirhuta Vowel Sign Sh||t
|
||||||
|
{0x114bf, 0x114c0}, // Tirhuta Sign Candrabindu..Tirhuta Sign Anusvara
|
||||||
|
{0x114c2, 0x114c3}, // Tirhuta Sign Virama ..Tirhuta Sign Nukta
|
||||||
|
{0x115b2, 0x115b5}, // Siddham Vowel Sign U ..Siddham Vowel Sign Vocal
|
||||||
|
{0x115bc, 0x115bd}, // Siddham Sign Candrabindu..Siddham Sign Anusvara
|
||||||
|
{0x115bf, 0x115c0}, // Siddham Sign Virama ..Siddham Sign Nukta
|
||||||
|
{0x115dc, 0x115dd}, // Siddham Vowel Sign Alter..Siddham Vowel Sign Alter
|
||||||
|
{0x11633, 0x1163a}, // Modi Vowel Sign U ..Modi Vowel Sign Ai
|
||||||
|
{0x1163d, 0x1163d}, // Modi Sign Anusvara ..Modi Sign Anusvara
|
||||||
|
{0x1163f, 0x11640}, // Modi Sign Virama ..Modi Sign Ardhacandra
|
||||||
|
{0x116ab, 0x116ab}, // Takri Sign Anusvara ..Takri Sign Anusvara
|
||||||
|
{0x116ad, 0x116ad}, // Takri Vowel Sign Aa ..Takri Vowel Sign Aa
|
||||||
|
{0x116b0, 0x116b5}, // Takri Vowel Sign U ..Takri Vowel Sign Au
|
||||||
|
{0x116b7, 0x116b7}, // Takri Sign Nukta ..Takri Sign Nukta
|
||||||
|
{0x1171d, 0x1171f}, // Ahom Consonant Sign Medi..Ahom Consonant Sign Medi
|
||||||
|
{0x11722, 0x11725}, // Ahom Vowel Sign I ..Ahom Vowel Sign Uu
|
||||||
|
{0x11727, 0x1172b}, // Ahom Vowel Sign Aw ..Ahom Sign Killer
|
||||||
|
{0x11c30, 0x11c36}, // (nil) ..
|
||||||
|
{0x11c38, 0x11c3d}, // (nil) ..
|
||||||
|
{0x11c3f, 0x11c3f}, // (nil) ..
|
||||||
|
{0x11c92, 0x11ca7}, // (nil) ..
|
||||||
|
{0x11caa, 0x11cb0}, // (nil) ..
|
||||||
|
{0x11cb2, 0x11cb3}, // (nil) ..
|
||||||
|
{0x11cb5, 0x11cb6}, // (nil) ..
|
||||||
|
{0x16af0, 0x16af4}, // Bassa Vah Combining High..Bassa Vah Combining High
|
||||||
|
{0x16b30, 0x16b36}, // Pahawh Hmong Mark Cim Tu..Pahawh Hmong Mark Cim Ta
|
||||||
|
{0x16f8f, 0x16f92}, // Miao Tone Right ..Miao Tone Below
|
||||||
|
{0x1bc9d, 0x1bc9e}, // Duployan Thick Letter Se..Duployan Double Mark
|
||||||
|
{0x1d167, 0x1d169}, // Musical Symbol Combining..Musical Symbol Combining
|
||||||
|
{0x1d17b, 0x1d182}, // Musical Symbol Combining..Musical Symbol Combining
|
||||||
|
{0x1d185, 0x1d18b}, // Musical Symbol Combining..Musical Symbol Combining
|
||||||
|
{0x1d1aa, 0x1d1ad}, // Musical Symbol Combining..Musical Symbol Combining
|
||||||
|
{0x1d242, 0x1d244}, // Combining Greek Musical ..Combining Greek Musical
|
||||||
|
{0x1da00, 0x1da36}, // Signwriting Head Rim ..Signwriting Air Sucking
|
||||||
|
{0x1da3b, 0x1da6c}, // Signwriting Mouth Closed..Signwriting Excitement
|
||||||
|
{0x1da75, 0x1da75}, // Signwriting Upper Body T..Signwriting Upper Body T
|
||||||
|
{0x1da84, 0x1da84}, // Signwriting Location Hea..Signwriting Location Hea
|
||||||
|
{0x1da9b, 0x1da9f}, // Signwriting Fill Modifie..Signwriting Fill Modifie
|
||||||
|
{0x1daa1, 0x1daaf}, // Signwriting Rotation Mod..Signwriting Rotation Mod
|
||||||
|
{0x1e000, 0x1e006}, // (nil) ..
|
||||||
|
{0x1e008, 0x1e018}, // (nil) ..
|
||||||
|
{0x1e01b, 0x1e021}, // (nil) ..
|
||||||
|
{0x1e023, 0x1e024}, // (nil) ..
|
||||||
|
{0x1e026, 0x1e02a}, // (nil) ..
|
||||||
|
{0x1e8d0, 0x1e8d6}, // Mende Kikakui Combining ..Mende Kikakui Combining
|
||||||
|
{0x1e944, 0x1e94a}, // (nil) ..
|
||||||
|
{0xe0100, 0xe01ef}, // Variation Select||-17 ..Variation Select||-256
|
||||||
|
};
|
||||||
|
|
||||||
|
// https://github.com/jquast/wcwidth/blob/master/wcwidth/table_wide.py
|
||||||
|
// at commit 0d7de112202cc8b2ebe9232ff4a5c954f19d561a (2016-07-02):
|
||||||
|
private static final int[][] WIDE_EASTASIAN = {
|
||||||
|
{0x1100, 0x115f}, // Hangul Choseong Kiyeok ..Hangul Choseong Filler
|
||||||
|
{0x231a, 0x231b}, // Watch ..Hourglass
|
||||||
|
{0x2329, 0x232a}, // Left-pointing Angle Brac..Right-pointing Angle Bra
|
||||||
|
{0x23e9, 0x23ec}, // Black Right-pointing Dou..Black Down-pointing Doub
|
||||||
|
{0x23f0, 0x23f0}, // Alarm Clock ..Alarm Clock
|
||||||
|
{0x23f3, 0x23f3}, // Hourglass With Flowing S..Hourglass With Flowing S
|
||||||
|
{0x25fd, 0x25fe}, // White Medium Small Squar..Black Medium Small Squar
|
||||||
|
{0x2614, 0x2615}, // Umbrella With Rain Drops..Hot Beverage
|
||||||
|
{0x2648, 0x2653}, // Aries ..Pisces
|
||||||
|
{0x267f, 0x267f}, // Wheelchair Symbol ..Wheelchair Symbol
|
||||||
|
{0x2693, 0x2693}, // Anch|| ..Anch||
|
||||||
|
{0x26a1, 0x26a1}, // High Voltage Sign ..High Voltage Sign
|
||||||
|
{0x26aa, 0x26ab}, // Medium White Circle ..Medium Black Circle
|
||||||
|
{0x26bd, 0x26be}, // Soccer Ball ..Baseball
|
||||||
|
{0x26c4, 0x26c5}, // Snowman Without Snow ..Sun Behind Cloud
|
||||||
|
{0x26ce, 0x26ce}, // Ophiuchus ..Ophiuchus
|
||||||
|
{0x26d4, 0x26d4}, // No Entry ..No Entry
|
||||||
|
{0x26ea, 0x26ea}, // Church ..Church
|
||||||
|
{0x26f2, 0x26f3}, // Fountain ..Flag In Hole
|
||||||
|
{0x26f5, 0x26f5}, // Sailboat ..Sailboat
|
||||||
|
{0x26fa, 0x26fa}, // Tent ..Tent
|
||||||
|
{0x26fd, 0x26fd}, // Fuel Pump ..Fuel Pump
|
||||||
|
{0x2705, 0x2705}, // White Heavy Check Mark ..White Heavy Check Mark
|
||||||
|
{0x270a, 0x270b}, // Raised Fist ..Raised Hand
|
||||||
|
{0x2728, 0x2728}, // Sparkles ..Sparkles
|
||||||
|
{0x274c, 0x274c}, // Cross Mark ..Cross Mark
|
||||||
|
{0x274e, 0x274e}, // Negative Squared Cross M..Negative Squared Cross M
|
||||||
|
{0x2753, 0x2755}, // Black Question Mark ||na..White Exclamation Mark O
|
||||||
|
{0x2757, 0x2757}, // Heavy Exclamation Mark S..Heavy Exclamation Mark S
|
||||||
|
{0x2795, 0x2797}, // Heavy Plus Sign ..Heavy Division Sign
|
||||||
|
{0x27b0, 0x27b0}, // Curly Loop ..Curly Loop
|
||||||
|
{0x27bf, 0x27bf}, // Double Curly Loop ..Double Curly Loop
|
||||||
|
{0x2b1b, 0x2b1c}, // Black Large Square ..White Large Square
|
||||||
|
{0x2b50, 0x2b50}, // White Medium Star ..White Medium Star
|
||||||
|
{0x2b55, 0x2b55}, // Heavy Large Circle ..Heavy Large Circle
|
||||||
|
{0x2e80, 0x2e99}, // Cjk Radical Repeat ..Cjk Radical Rap
|
||||||
|
{0x2e9b, 0x2ef3}, // Cjk Radical Choke ..Cjk Radical C-simplified
|
||||||
|
{0x2f00, 0x2fd5}, // Kangxi Radical One ..Kangxi Radical Flute
|
||||||
|
{0x2ff0, 0x2ffb}, // Ideographic Description ..Ideographic Description
|
||||||
|
{0x3000, 0x303e}, // Ideographic Space ..Ideographic Variation In
|
||||||
|
{0x3041, 0x3096}, // Hiragana Letter Small A ..Hiragana Letter Small Ke
|
||||||
|
{0x3099, 0x30ff}, // Combining Katakana-hirag..Katakana Digraph Koto
|
||||||
|
{0x3105, 0x312d}, // Bopomofo Letter B ..Bopomofo Letter Ih
|
||||||
|
{0x3131, 0x318e}, // Hangul Letter Kiyeok ..Hangul Letter Araeae
|
||||||
|
{0x3190, 0x31ba}, // Ideographic Annotation L..Bopomofo Letter Zy
|
||||||
|
{0x31c0, 0x31e3}, // Cjk Stroke T ..Cjk Stroke Q
|
||||||
|
{0x31f0, 0x321e}, // Katakana Letter Small Ku..Parenthesized K||ean Cha
|
||||||
|
{0x3220, 0x3247}, // Parenthesized Ideograph ..Circled Ideograph Koto
|
||||||
|
{0x3250, 0x32fe}, // Partnership Sign ..Circled Katakana Wo
|
||||||
|
{0x3300, 0x4dbf}, // Square Apaato ..
|
||||||
|
{0x4e00, 0xa48c}, // Cjk Unified Ideograph-4e..Yi Syllable Yyr
|
||||||
|
{0xa490, 0xa4c6}, // Yi Radical Qot ..Yi Radical Ke
|
||||||
|
{0xa960, 0xa97c}, // Hangul Choseong Tikeut-m..Hangul Choseong Ssangyeo
|
||||||
|
{0xac00, 0xd7a3}, // Hangul Syllable Ga ..Hangul Syllable Hih
|
||||||
|
{0xf900, 0xfaff}, // Cjk Compatibility Ideogr..
|
||||||
|
{0xfe10, 0xfe19}, // Presentation F||m F|| Ve..Presentation F||m F|| Ve
|
||||||
|
{0xfe30, 0xfe52}, // Presentation F||m F|| Ve..Small Full Stop
|
||||||
|
{0xfe54, 0xfe66}, // Small Semicolon ..Small Equals Sign
|
||||||
|
{0xfe68, 0xfe6b}, // Small Reverse Solidus ..Small Commercial At
|
||||||
|
{0xff01, 0xff60}, // Fullwidth Exclamation Ma..Fullwidth Right White Pa
|
||||||
|
{0xffe0, 0xffe6}, // Fullwidth Cent Sign ..Fullwidth Won Sign
|
||||||
|
{0x16fe0, 0x16fe0}, // (nil) ..
|
||||||
|
{0x17000, 0x187ec}, // (nil) ..
|
||||||
|
{0x18800, 0x18af2}, // (nil) ..
|
||||||
|
{0x1b000, 0x1b001}, // Katakana Letter Archaic ..Hiragana Letter Archaic
|
||||||
|
{0x1f004, 0x1f004}, // Mahjong Tile Red Dragon ..Mahjong Tile Red Dragon
|
||||||
|
{0x1f0cf, 0x1f0cf}, // Playing Card Black Joker..Playing Card Black Joker
|
||||||
|
{0x1f18e, 0x1f18e}, // Negative Squared Ab ..Negative Squared Ab
|
||||||
|
{0x1f191, 0x1f19a}, // Squared Cl ..Squared Vs
|
||||||
|
{0x1f200, 0x1f202}, // Square Hiragana Hoka ..Squared Katakana Sa
|
||||||
|
{0x1f210, 0x1f23b}, // Squared Cjk Unified Ideo..
|
||||||
|
{0x1f240, 0x1f248}, // T||toise Shell Bracketed..T||toise Shell Bracketed
|
||||||
|
{0x1f250, 0x1f251}, // Circled Ideograph Advant..Circled Ideograph Accept
|
||||||
|
{0x1f300, 0x1f320}, // Cyclone ..Shooting Star
|
||||||
|
{0x1f32d, 0x1f335}, // Hot Dog ..Cactus
|
||||||
|
{0x1f337, 0x1f37c}, // Tulip ..Baby Bottle
|
||||||
|
{0x1f37e, 0x1f393}, // Bottle With Popping C||k..Graduation Cap
|
||||||
|
{0x1f3a0, 0x1f3ca}, // Carousel H||se ..Swimmer
|
||||||
|
{0x1f3cf, 0x1f3d3}, // Cricket Bat And Ball ..Table Tennis Paddle And
|
||||||
|
{0x1f3e0, 0x1f3f0}, // House Building ..European Castle
|
||||||
|
{0x1f3f4, 0x1f3f4}, // Waving Black Flag ..Waving Black Flag
|
||||||
|
{0x1f3f8, 0x1f43e}, // Badminton Racquet And Sh..Paw Prints
|
||||||
|
{0x1f440, 0x1f440}, // Eyes ..Eyes
|
||||||
|
{0x1f442, 0x1f4fc}, // Ear ..Videocassette
|
||||||
|
{0x1f4ff, 0x1f53d}, // Prayer Beads ..Down-pointing Small Red
|
||||||
|
{0x1f54b, 0x1f54e}, // Kaaba ..Men||ah With Nine Branch
|
||||||
|
{0x1f550, 0x1f567}, // Clock Face One Oclock ..Clock Face Twelve-thirty
|
||||||
|
{0x1f57a, 0x1f57a}, // (nil) ..
|
||||||
|
{0x1f595, 0x1f596}, // Reversed Hand With Middl..Raised Hand With Part Be
|
||||||
|
{0x1f5a4, 0x1f5a4}, // (nil) ..
|
||||||
|
{0x1f5fb, 0x1f64f}, // Mount Fuji ..Person With Folded Hands
|
||||||
|
{0x1f680, 0x1f6c5}, // Rocket ..Left Luggage
|
||||||
|
{0x1f6cc, 0x1f6cc}, // Sleeping Accommodation ..Sleeping Accommodation
|
||||||
|
{0x1f6d0, 0x1f6d2}, // Place Of W||ship ..
|
||||||
|
{0x1f6eb, 0x1f6ec}, // Airplane Departure ..Airplane Arriving
|
||||||
|
{0x1f6f4, 0x1f6f6}, // (nil) ..
|
||||||
|
{0x1f910, 0x1f91e}, // Zipper-mouth Face ..
|
||||||
|
{0x1f920, 0x1f927}, // (nil) ..
|
||||||
|
{0x1f930, 0x1f930}, // (nil) ..
|
||||||
|
{0x1f933, 0x1f93e}, // (nil) ..
|
||||||
|
{0x1f940, 0x1f94b}, // (nil) ..
|
||||||
|
{0x1f950, 0x1f95e}, // (nil) ..
|
||||||
|
{0x1f980, 0x1f991}, // Crab ..
|
||||||
|
{0x1f9c0, 0x1f9c0}, // Cheese Wedge ..Cheese Wedge
|
||||||
|
{0x20000, 0x2fffd}, // Cjk Unified Ideograph-20..
|
||||||
|
{0x30000, 0x3fffd}, // (nil) ..
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
private static boolean intable(int[][] table, int c) {
|
||||||
|
// First quick check f|| Latin1 etc. characters.
|
||||||
|
if (c < table[0][0]) return false;
|
||||||
|
|
||||||
|
// Binary search in table.
|
||||||
|
int bot = 0;
|
||||||
|
int top = table.length - 1; // (int)(size / sizeof(struct interval) - 1);
|
||||||
|
while (top >= bot) {
|
||||||
|
int mid = (bot + top) / 2;
|
||||||
|
if (table[mid][1] < c) {
|
||||||
|
bot = mid + 1;
|
||||||
|
} else if (table[mid][0] > c) {
|
||||||
|
top = mid - 1;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Return the terminal display width of a code point: 0, 1 || 2. */
|
||||||
|
public static int width(int ucs) {
|
||||||
|
if (ucs == 0 ||
|
||||||
|
ucs == 0x034F ||
|
||||||
|
(0x200B <= ucs && ucs <= 0x200F) ||
|
||||||
|
ucs == 0x2028 ||
|
||||||
|
ucs == 0x2029 ||
|
||||||
|
(0x202A <= ucs && ucs <= 0x202E) ||
|
||||||
|
(0x2060 <= ucs && ucs <= 0x2063)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// C0/C1 control characters
|
||||||
|
// Termux change: Return 0 instead of -1.
|
||||||
|
if (ucs < 32 || (0x07F <= ucs && ucs < 0x0A0)) return 0;
|
||||||
|
|
||||||
|
// combining characters with zero width
|
||||||
|
if (intable(ZERO_WIDTH, ucs)) return 0;
|
||||||
|
|
||||||
|
return intable(WIDE_EASTASIAN, ucs) ? 2 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The width at an index position in a java char array. */
|
||||||
|
public static int width(char[] chars, int index) {
|
||||||
|
char c = chars[index];
|
||||||
|
return Character.isHighSurrogate(c) ? width(Character.toCodePoint(c, chars[index + 1])) : width(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
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 ", " ");
|
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");
|
assertLinesAre("ABCDE", "FGHIJ", "KLMNO", "PQRST", "UVWXY");
|
||||||
for (int row = 0; row < 5; row++) {
|
for (int row = 0; row < 5; row++) {
|
||||||
for (int col = 0; col < 5; col++) {
|
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(col, TextStyle.decodeForeColor(s));
|
||||||
Assert.assertEquals(row, TextStyle.decodeBackColor(s));
|
Assert.assertEquals(row, TextStyle.decodeBackColor(s));
|
||||||
}
|
}
|
||||||
@@ -28,7 +28,7 @@ public class CursorAndScreenTest extends TerminalTestCase {
|
|||||||
assertLinesAre("KLMNO", "PQRST", "UVWXY", " ", " ");
|
assertLinesAre("KLMNO", "PQRST", "UVWXY", " ", " ");
|
||||||
for (int row = 0; row < 3; row++) {
|
for (int row = 0; row < 3; row++) {
|
||||||
for (int col = 0; col < 5; col++) {
|
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(col, TextStyle.decodeForeColor(s));
|
||||||
Assert.assertEquals(row + 2, TextStyle.decodeBackColor(s));
|
Assert.assertEquals(row + 2, TextStyle.decodeBackColor(s));
|
||||||
}
|
}
|
||||||
@@ -43,7 +43,7 @@ public class CursorAndScreenTest extends TerminalTestCase {
|
|||||||
for (int col = 0; col < 5; col++) {
|
for (int col = 0; col < 5; col++) {
|
||||||
int wantedForeground = (row == 1 || row == 2) ? 98 : col;
|
int wantedForeground = (row == 1 || row == 2) ? 98 : col;
|
||||||
int wantedBackground = (row == 1 || row == 2) ? 99 : (row == 0 ? 2 : row);
|
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(wantedForeground, TextStyle.decodeForeColor(s));
|
||||||
Assert.assertEquals(wantedBackground, TextStyle.decodeBackColor(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 ", " ", " ");
|
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));
|
assertKeysEquals("\033[1;6D", KeyHandler.getCode(KeyEvent.KEYCODE_DPAD_LEFT, mod, false, false));
|
||||||
|
|
||||||
// Home/end keys:
|
// 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));
|
assertKeysEquals("\033[F", KeyHandler.getCode(KeyEvent.KEYCODE_MOVE_END, 0, false, false));
|
||||||
// ... shifted:
|
// ... 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));
|
assertKeysEquals("\033[1;2F", KeyHandler.getCode(KeyEvent.KEYCODE_MOVE_END, KeyHandler.KEYMOD_SHIFT, false, false));
|
||||||
|
|
||||||
// Function keys F1-F12:
|
// 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[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[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("\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");
|
enterString("\033[2J");
|
||||||
for (int r = 0; r < rows; r++) {
|
for (int r = 0; r < rows; r++) {
|
||||||
for (int c = 0; c < cols; c++) {
|
for (int c = 0; c < cols; c++) {
|
||||||
int style = getStyleAt(r, c);
|
long style = getStyleAt(r, c);
|
||||||
assertEquals(119, TextStyle.decodeForeColor(style));
|
assertEquals(119, TextStyle.decodeForeColor(style));
|
||||||
assertEquals(129, TextStyle.decodeBackColor(style));
|
assertEquals(129, TextStyle.decodeBackColor(style));
|
||||||
}
|
}
|
||||||
@@ -105,7 +105,7 @@ public class ResizeTest extends TerminalTestCase {
|
|||||||
// After resize, screen should still be same color:
|
// After resize, screen should still be same color:
|
||||||
for (int r = 0; r < rows - 2; r++) {
|
for (int r = 0; r < rows - 2; r++) {
|
||||||
for (int c = 0; c < cols; c++) {
|
for (int c = 0; c < cols; c++) {
|
||||||
int style = getStyleAt(r, c);
|
long style = getStyleAt(r, c);
|
||||||
assertEquals(119, TextStyle.decodeForeColor(style));
|
assertEquals(119, TextStyle.decodeForeColor(style));
|
||||||
assertEquals(129, TextStyle.decodeBackColor(style));
|
assertEquals(129, TextStyle.decodeBackColor(style));
|
||||||
}
|
}
|
||||||
@@ -116,7 +116,7 @@ public class ResizeTest extends TerminalTestCase {
|
|||||||
resize(cols, rows);
|
resize(cols, rows);
|
||||||
for (int r = 0; r < rows; r++) {
|
for (int r = 0; r < rows; r++) {
|
||||||
for (int c = 0; c < cols; c++) {
|
for (int c = 0; c < cols; c++) {
|
||||||
int style = getStyleAt(r, c);
|
long style = getStyleAt(r, c);
|
||||||
assertEquals(119, TextStyle.decodeForeColor(style));
|
assertEquals(119, TextStyle.decodeForeColor(style));
|
||||||
assertEquals("wrong at row=" + r, r >= 3 ? 200 : 129, TextStyle.decodeBackColor(style));
|
assertEquals("wrong at row=" + r, r >= 3 ? 200 : 129, TextStyle.decodeBackColor(style));
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
package com.termux.terminal;
|
package com.termux.terminal;
|
||||||
|
|
||||||
public class ScreenBufferTest extends TerminalTest {
|
public class ScreenBufferTest extends TerminalTestCase {
|
||||||
|
|
||||||
public void testBasics() {
|
public void testBasics() {
|
||||||
TerminalBuffer screen = new TerminalBuffer(5, 3, 3);
|
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 ");
|
withTerminalSized(2, 5).enterString("1\r\n2\r\n3\r\n4\r\n5").assertLinesAre("1 ", "2 ", "3 ", "4 ", "5 ");
|
||||||
enterString("\033[3r").enterString("\033[2T").assertLinesAre("1 ", "2 ", " ", " ", "3 ");
|
enterString("\033[3r").enterString("\033[2T").assertLinesAre("1 ", "2 ", " ", " ", "3 ");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testScrollDownBelowScrollRegion() {
|
||||||
|
withTerminalSized(2, 5).enterString("1\r\n2\r\n3\r\n4\r\n5").assertLinesAre("1 ", "2 ", "3 ", "4 ", "5 ");
|
||||||
|
enterString("\033[1;3r"); // DECSTBM margins.
|
||||||
|
enterString("\033[4;1H"); // Place cursor just below bottom margin.
|
||||||
|
enterString("QQ\r\nRR\r\n\r\n\r\nYY");
|
||||||
|
assertLinesAre("1 ", "2 ", "3 ", "QQ", "YY");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -27,6 +27,7 @@ public class TerminalRowTest extends TestCase {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void setUp() throws Exception {
|
protected void setUp() throws Exception {
|
||||||
|
super.setUp();
|
||||||
row = new TerminalRow(COLUMNS, TextStyle.NORMAL);
|
row = new TerminalRow(COLUMNS, TextStyle.NORMAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,6 +55,14 @@ public class TerminalTest extends TerminalTestCase {
|
|||||||
assertEquals("\033[<0;3;4M", mOutput.getOutputAndClear());
|
assertEquals("\033[<0;3;4M", mOutput.getOutputAndClear());
|
||||||
mTerminal.sendMouseEvent(TerminalEmulator.MOUSE_LEFT_BUTTON, 3, 4, false);
|
mTerminal.sendMouseEvent(TerminalEmulator.MOUSE_LEFT_BUTTON, 3, 4, false);
|
||||||
assertEquals("\033[<0;3;4m", mOutput.getOutputAndClear());
|
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 {
|
public void testNormalization() throws UnsupportedEncodingException {
|
||||||
@@ -147,12 +155,11 @@ public class TerminalTest extends TerminalTestCase {
|
|||||||
enterString("\033[38;5;119m");
|
enterString("\033[38;5;119m");
|
||||||
assertEquals(119, mTerminal.mForeColor);
|
assertEquals(119, mTerminal.mForeColor);
|
||||||
assertEquals(TextStyle.COLOR_INDEX_BACKGROUND, mTerminal.mBackColor);
|
assertEquals(TextStyle.COLOR_INDEX_BACKGROUND, mTerminal.mBackColor);
|
||||||
|
|
||||||
enterString("\033[48;5;129m");
|
enterString("\033[48;5;129m");
|
||||||
assertEquals(119, mTerminal.mForeColor);
|
assertEquals(119, mTerminal.mForeColor);
|
||||||
assertEquals(129, mTerminal.mBackColor);
|
assertEquals(129, mTerminal.mBackColor);
|
||||||
|
|
||||||
// Invalid parameter:
|
// Invalid parameter:
|
||||||
enterString("\033[48;8;129m");
|
enterString("\033[48;8;129m");
|
||||||
assertEquals(119, mTerminal.mForeColor);
|
assertEquals(119, mTerminal.mForeColor);
|
||||||
assertEquals(129, mTerminal.mBackColor);
|
assertEquals(129, mTerminal.mBackColor);
|
||||||
@@ -161,7 +168,31 @@ public class TerminalTest extends TerminalTestCase {
|
|||||||
enterString("\033[38;5;178;48;5;179;m");
|
enterString("\033[38;5;178;48;5;179;m");
|
||||||
assertEquals(178, mTerminal.mForeColor);
|
assertEquals(178, mTerminal.mForeColor);
|
||||||
assertEquals(179, mTerminal.mBackColor);
|
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() {
|
public void testBackgroundColorErase() {
|
||||||
final int rows = 3;
|
final int rows = 3;
|
||||||
@@ -169,7 +200,7 @@ public class TerminalTest extends TerminalTestCase {
|
|||||||
withTerminalSized(cols, rows);
|
withTerminalSized(cols, rows);
|
||||||
for (int r = 0; r < rows; r++) {
|
for (int r = 0; r < rows; r++) {
|
||||||
for (int c = 0; c < cols; c++) {
|
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_FOREGROUND, TextStyle.decodeForeColor(style));
|
||||||
assertEquals(TextStyle.COLOR_INDEX_BACKGROUND, TextStyle.decodeBackColor(style));
|
assertEquals(TextStyle.COLOR_INDEX_BACKGROUND, TextStyle.decodeBackColor(style));
|
||||||
}
|
}
|
||||||
@@ -182,7 +213,7 @@ public class TerminalTest extends TerminalTestCase {
|
|||||||
enterString("\033[2J");
|
enterString("\033[2J");
|
||||||
for (int r = 0; r < rows; r++) {
|
for (int r = 0; r < rows; r++) {
|
||||||
for (int c = 0; c < cols; c++) {
|
for (int c = 0; c < cols; c++) {
|
||||||
int style = getStyleAt(r, c);
|
long style = getStyleAt(r, c);
|
||||||
assertEquals(119, TextStyle.decodeForeColor(style));
|
assertEquals(119, TextStyle.decodeForeColor(style));
|
||||||
assertEquals(129, TextStyle.decodeBackColor(style));
|
assertEquals(129, TextStyle.decodeBackColor(style));
|
||||||
}
|
}
|
||||||
@@ -193,7 +224,7 @@ public class TerminalTest extends TerminalTestCase {
|
|||||||
enterString("\033[2L");
|
enterString("\033[2L");
|
||||||
for (int r = 0; r < rows; r++) {
|
for (int r = 0; r < rows; r++) {
|
||||||
for (int c = 0; c < cols; c++) {
|
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));
|
assertEquals((r == 0 || r == 1) ? 139 : 129, TextStyle.decodeBackColor(style));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -103,6 +103,7 @@ public abstract class TerminalTestCase extends TestCase {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void setUp() throws Exception {
|
protected void setUp() throws Exception {
|
||||||
|
super.setUp();
|
||||||
mOutput = new MockTerminalOutput();
|
mOutput = new MockTerminalOutput();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,7 +241,7 @@ public abstract class TerminalTestCase extends TestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** For testing only. Encoded style according to {@link TextStyle}. */
|
/** 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);
|
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) {
|
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));
|
assertEquals(color, TextStyle.decodeForeColor(style));
|
||||||
}
|
}
|
||||||
|
|
||||||