Compare commits

..

74 Commits
v0.41 ... v0.46

Author SHA1 Message Date
Fredrik Fornwall
cb2f892dc5 Adjust text placement on feature graphics 2016-12-30 03:23:53 +01:00
Fredrik Fornwall
8b6e8d7fdd Bump version to 0.46 2016-12-30 02:43:40 +01:00
Fredrik Fornwall
d0eeaa9fc3 Call completeWakefulIntent() in Termux service 2016-12-30 02:42:41 +01:00
Fredrik Fornwall
b793913481 Style for android tv launcher transition 2016-12-30 02:39:26 +01:00
Fredrik Fornwall
d48b438c40 Test wcwidth on two more code points 2016-12-30 02:39:01 +01:00
Fredrik Fornwall
11afe895e1 Try with a bit shorter process completed messages 2016-12-30 02:38:41 +01:00
Fredrik Fornwall
bc96f71a2d Update the tv banner a bit 2016-12-30 02:34:43 +01:00
Fredrik Fornwall
87dfded5e6 Remove unused on_bell.xml file 2016-12-30 01:54:31 +01:00
Fredrik Fornwall
7c0ae4cb54 Remove the bell shake animation
Fixes https://github.com/termux/termux-packages/issues/628
Fixes https://github.com/termux/termux-app/issues/222
2016-12-30 01:53:07 +01:00
Fredrik Fornwall
b917acbbfa Do not use IME_ACTION_NONE in the inputconnection
Using IME_ACTION_NONE prevents enter key to be used with the stock
Android TV keyboard.

Fixes #221.
2016-12-30 01:42:47 +01:00
Fredrik Fornwall
7d9d6fb797 Translate \n to \r when receiving text from an IME
Fixes issue with fzf and return using the stock cyanogenmod
keyboard reported by @mklein994 on gitter.
2016-12-30 00:45:11 +01:00
Fredrik Fornwall
74040dd37f Add feature and tv banner to art/ 2016-12-29 22:29:37 +01:00
Fredrik Fornwall
e94f06d0f7 Silence lint warnings in shortcuts.xml 2016-12-28 02:45:14 +01:00
Fredrik Fornwall
af21b6dc3e Remove unused variable. Use Collections.addAll() 2016-12-28 01:19:57 +01:00
Fredrik Fornwall
e0e8257f1c Update build dependencies 2016-12-28 01:07:12 +01:00
Fredrik Fornwall
743b067cae Bump version to 0.45 2016-12-27 10:43:05 +01:00
Fredrik Fornwall
23333c074a Fix NPE regression in version 0.44 2016-12-27 10:42:41 +01:00
Fredrik Fornwall
f11644fa51 travis: Update build tools 2016-12-26 05:18:45 +01:00
Fredrik Fornwall
212be59fca gradle: Update build tools 2016-12-26 04:48:28 +01:00
Fredrik Fornwall
e3a1f8224f Add art/copy-to-other-apps.sh script 2016-12-26 04:45:00 +01:00
Fredrik Fornwall
4f40d5a26a Bump version to 0.44 2016-12-26 04:07:44 +01:00
Fredrik Fornwall
df92896eef Add Android 7.1 launcher shortcut for new session 2016-12-26 04:07:03 +01:00
Fredrik Fornwall
4c93cb42f1 Use constant for intent extra key 2016-12-26 00:57:09 +01:00
Fredrik Fornwall
34afb9de43 Give up on using vector drawables for launch icons
Instead use svg files in art/ and a script to build the png files.
2016-12-24 01:05:33 +01:00
Fredrik Fornwall
b6ea29d260 Remove duplicate updateNotification() calls 2016-12-24 00:25:24 +01:00
Fredrik Fornwall
289d58a2f0 Try it with launcher icons in drawable-anydpi 2016-12-10 22:51:11 -05:00
Fredrik Fornwall
0501ce924b gradle: Update android gradle plugin 2016-12-07 20:26:09 -05:00
Fredrik Fornwall
d12256f5e5 Add flags to imeOptions
Add IME_FLAG_NO_ENTER_ACTION and IME_ACTION_NONE. I haven't
encountered any issue without them, but specifying them is correct.
2016-12-04 17:32:40 +01:00
Fredrik Fornwall
fcbc036f92 Update gradle to 3.2.1 2016-12-04 17:26:26 +01:00
Fredrik Fornwall
70d5839334 Bump version to 0.43 for preview testing 2016-12-04 04:38:35 +01:00
Fredrik Fornwall
9c19540759 Keep track of background tasks
Also merge wake lock and wifi lock into one, and make it exposed to
termux-wake-lock and termux-wake-unlock commands.
2016-12-04 04:37:13 +01:00
Fredrik Fornwall
9fe0e49473 Render cursor color&shapes (underline and ibeam)
Allow the cursor to be colored by the theme, and support rendering
underline and ibeam cursor styles.
2016-12-04 01:06:50 +01:00
Fredrik Fornwall
357b17e972 Improve setup of symlinks to external storage
The context.getExternalFilesDirs(null) call may return several
elements, and some of them may be null.
2016-12-04 01:04:19 +01:00
Fredrik Fornwall
6334470f81 Try to handle Samsung keyboard better
The stock Samsung keyboard with 'Auto check spelling' enabled may
send multiple backspaces. Note that this auto-correction of
spelling will not work good in general with a terminal, so should
be disabled (or another keyboard used) when using Termux.
2016-11-28 00:26:37 +01:00
Fredrik Fornwall
b8cdd59c68 Use gradle build, not assembly, for coverity 2016-11-25 03:55:07 +01:00
Fredrik Fornwall
6cf36fffd7 Fix typo on coverity setup 2016-11-25 03:43:16 +01:00
Fredrik Fornwall
f10ecd4db5 Try to fix coverity scans in travis 2016-11-25 03:17:50 +01:00
Fredrik Fornwall
26457e8443 Tweak fillcolor in icons for easier svg generation 2016-11-25 02:14:54 +01:00
Fredrik Fornwall
6702846c7c Update build tools version in travis 2016-11-24 23:58:43 +01:00
Fredrik Fornwall
0c6180bbb1 Update gradle deps 2016-11-24 23:26:27 +01:00
Fredrik Fornwall
fcf07f6a19 Coverity: Specify "branch_pattern: master" 2016-11-24 20:15:31 +01:00
Fredrik Fornwall
e272e3b3b2 Update README a bit 2016-11-24 20:05:28 +01:00
Fredrik Fornwall
cdf0e72145 Enable coverity scan for all branches 2016-11-24 19:36:38 +01:00
Fredrik Fornwall
70245eb78c Use BackgroundJob.setupProcessArgs for sessions 2016-11-23 01:55:29 +01:00
Fredrik Fornwall
ee7631dfac Add a round icon for android-25 2016-11-23 01:38:17 +01:00
Fredrik Fornwall
5ecf5d12d1 Tweak the icon somewhat 2016-11-23 01:37:58 +01:00
Fredrik Fornwall
0c8cd90f4e Use $PREFIX/bin/sh for script file without shebang
Also try to handle #!(/usr)/bin/foo shebangs.
2016-11-20 16:43:27 +01:00
Fredrik Fornwall
e1ea68913f Use to a vector notification icon 2016-11-19 22:33:45 +01:00
Fredrik Fornwall
dde854eba7 Switch to a vector icon 2016-11-19 22:18:26 +01:00
Fredrik Fornwall
07a4607c04 Merge pull request #193 from friederbluemle/update-gradle-wrapper
Update Gradle wrapper to 3.2
2016-11-19 21:04:10 +01:00
Fredrik Fornwall
883be37b98 Add a shake animation on a terminal bell 2016-11-19 15:41:24 +01:00
Frieder Bluemle
d939d3d927 Update Gradle wrapper to 3.2 2016-11-18 18:01:00 +08:00
Fredrik Fornwall
a0fa51eb92 Start work on background jobs 2016-10-26 02:26:44 +02:00
Fredrik Fornwall
755513bb33 Update gradle setup 2016-10-20 01:00:28 +02:00
Fredrik Fornwall
8ad7a6669c Switch a commit() to apply() for shared prefs 2016-10-16 00:13:35 +02:00
Fredrik Fornwall
60f7aada9e Remove logging at info level 2016-10-16 00:13:04 +02:00
Fredrik Fornwall
6aa0492434 Add @NonNull annotation 2016-10-16 00:12:42 +02:00
Fredrik Fornwall
019aa44837 Update Android Gradle Plugin 2016-10-10 22:23:39 +02:00
Fredrik Fornwall
8d3d5e147f Remove outdated JNI building instructions 2016-09-29 01:13:45 +02:00
Fredrik Fornwall
44197b90e2 Update .travis.yml 2016-09-29 00:34:12 +02:00
Fredrik Fornwall
794c7ee333 Remove deprecated android:singleLine attribute 2016-09-27 00:30:05 +02:00
Fredrik Fornwall
0457ddbc69 Update build.gradle with latest versions 2016-09-27 00:29:47 +02:00
Fredrik Fornwall
283792af5e Refresh listview after changing session name 2016-09-27 00:29:08 +02:00
Fredrik Fornwall
be7cfa603a Fix gradle syntax for Android Studio 2016-09-25 01:15:49 +02:00
Fredrik Fornwall
3480bf7346 Switch from cmake to ndk-build
We switch from cmake to ndk-build to make it easier for builders
in not requiring an additional tool installed. The JNI build is
so simple so we don't really need much of a build tool anyway.
2016-09-24 20:22:10 +02:00
Fredrik Fornwall
8314a2756c Setup NDK in travis 2016-09-23 08:57:40 +02:00
Fredrik Fornwall
4de82d9fe0 Update gradle to 3.1 2016-09-21 22:34:44 +02:00
Fredrik Fornwall
d658e16801 Use the new cmake support to build JNI code 2016-09-21 22:30:20 +02:00
Fredrik Fornwall
26dcd5af88 Update gradle and Android Studio 2016-09-19 23:26:49 +02:00
Fredrik Fornwall
8056013082 Bump version to 0.42 2016-09-16 23:18:51 +02:00
Fredrik Fornwall
8e90545c4b Remove comment from build.gradle 2016-09-16 23:18:34 +02:00
Fredrik Fornwall
426ddbacbd Remove useless casts 2016-09-16 23:17:47 +02:00
Fredrik Fornwall
7e1f8a551f Change VolumeUp+H to generate ~
Using VolumeUp+H to generate a tilde (~) is more useful than
sending the home key.

Fixes #151.
2016-09-16 23:15:27 +02:00
Fredrik Fornwall
e169af0447 Change shortcuts from Ctrl+Shift to Ctrl+Alt
This works on more language layouts and devices.

Fixes #145.
2016-09-16 23:12:56 +02:00
62 changed files with 667 additions and 242 deletions

1
.gitignore vendored
View File

@@ -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

7
.idea/gradle.xml generated
View File

@@ -12,12 +12,7 @@
<option value="$PROJECT_DIR$/app" /> <option value="$PROJECT_DIR$/app" />
</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>

View File

@@ -1,4 +1,4 @@
sudo: false sudo: true
language: android language: android
jdk: oraclejdk8 jdk: oraclejdk8
@@ -12,10 +12,15 @@ android:
components: components:
- platform-tools - platform-tools
- tools - tools
- build-tools-24.0.1 - build-tools-25.0.2
- android-24 - android-25
- 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
- echo -n | openssl s_client -connect scan.coverity.com:443 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' | sudo tee -a /etc/ssl/certs/ca-certificates.crt
script: script:
- ./gradlew testDebugUnitTest - ./gradlew testDebugUnitTest
@@ -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

View File

@@ -3,25 +3,19 @@ Termux app
[![Travis build status](https://travis-ci.org/termux/termux-app.svg?branch=master)](https://travis-ci.org/termux/termux-app) [![Travis build status](https://travis-ci.org/termux/termux-app.svg?branch=master)](https://travis-ci.org/termux/termux-app)
[![Join the chat at https://gitter.im/termux/termux](https://badges.gitter.im/termux/termux.svg)](https://gitter.im/termux/termux) [![Join the chat at https://gitter.im/termux/termux](https://badges.gitter.im/termux/termux.svg)](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 Help](http://termux.com/help/) * [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/)
Note that this repository is for the app itself (the user interface and the terminal emulation). For the packages installable inside the app, see [termux/termux-packages](https://github.com/termux/termux-packages)
License License
======= =======
Released under [the GPLv3 license](https://www.gnu.org/licenses/gpl.html). Contains code from `Terminal Emulator for Android` which is released under [the Apache License 2.0](https://www.apache.org/licenses/). 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
================== ==================
* [XTerm control sequences](http://invisible-island.net/xterm/ctlseqs/ctlseqs.html) * [XTerm control sequences](http://invisible-island.net/xterm/ctlseqs/ctlseqs.html)

View File

@@ -1,25 +1,29 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
android { android {
compileSdkVersion 24 compileSdkVersion 25
buildToolsVersion "24.0.1" buildToolsVersion "25.0.2"
dependencies { dependencies {
compile 'com.android.support:support-annotations:24.2.0' compile 'com.android.support:support-annotations:25.1.0'
compile "com.android.support:support-v4:24.2.0" compile "com.android.support:support-v4:25.1.0"
} }
defaultConfig { defaultConfig {
applicationId "com.termux" applicationId "com.termux"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 24 targetSdkVersion 25
versionCode 41 versionCode 46
versionName "0.41" versionName "0.46"
externalNativeBuild {
ndkBuild {
cFlags "-std=c11", "-Wall", "-Wextra", "-Werror", "-Os", "-fno-stack-protector", "-Wl,--gc-sections"
}
}
ndk { ndk {
moduleName "libtermux" abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'
abiFilters "armeabi-v7a", "arm64-v8a", "x86", "x86_64"
cFlags "-std=c11 -Wall -Wextra -Os -fno-stack-protector -nostdlib -Wl,--gc-sections"
} }
} }
@@ -30,6 +34,12 @@ android {
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
} }
} }
externalNativeBuild {
ndkBuild {
path "src/main/jni/Android.mk"
}
}
} }
dependencies { dependencies {

View File

@@ -15,7 +15,8 @@
<application <application
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"
@@ -34,6 +35,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

View File

@@ -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,7 +106,6 @@ public final class BackgroundJob {
// EXTERNAL_STORAGE is needed for /system/bin/am to work on at least // EXTERNAL_STORAGE is needed for /system/bin/am to work on at least
// Samsung S7 - see https://plus.google.com/110070148244138185604/posts/gp8Lk3aCGp3. // Samsung S7 - see https://plus.google.com/110070148244138185604/posts/gp8Lk3aCGp3.
final String externalStorageEnv = "EXTERNAL_STORAGE=" + System.getenv("EXTERNAL_STORAGE"); final String externalStorageEnv = "EXTERNAL_STORAGE=" + System.getenv("EXTERNAL_STORAGE");
String[] env;
if (failSafe) { if (failSafe) {
// Keep the default path so that system binaries can be used in the failsafe session. // Keep the default path so that system binaries can be used in the failsafe session.
final String pathEnv = "PATH=" + System.getenv("PATH"); final String pathEnv = "PATH=" + System.getenv("PATH");
@@ -109,4 +121,73 @@ public final class BackgroundJob {
} }
} }
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.
continue;
} 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()]);
}
} }

View File

@@ -49,6 +49,8 @@ import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener; import android.view.View.OnLongClickListener;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.WindowManager; import android.view.WindowManager;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView; import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener; import android.widget.AdapterView.OnItemClickListener;
@@ -402,20 +404,20 @@ public final class TermuxActivity extends Activity implements ServiceConnection
@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
@@ -429,8 +431,9 @@ public final class TermuxActivity extends Activity implements ServiceConnection
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();
@@ -513,7 +516,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());
}
} }
} }
@@ -534,6 +543,7 @@ 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);
} }

View File

@@ -257,9 +257,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);

View File

@@ -61,7 +61,7 @@ public final class TermuxKeyListener implements TerminalKeyListener {
if (keyCode == KeyEvent.KEYCODE_ENTER && !currentSession.isRunning()) { if (keyCode == KeyEvent.KEYCODE_ENTER && !currentSession.isRunning()) {
mActivity.removeFinishedSession(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);
@@ -160,7 +160,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.

View File

@@ -105,7 +105,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) {

View File

@@ -11,8 +11,10 @@ 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.Handler;
import android.os.IBinder; import android.os.IBinder;
import android.os.PowerManager; import android.os.PowerManager;
import android.support.v4.content.WakefulBroadcastReceiver;
import android.util.Log; import android.util.Log;
import android.widget.ArrayAdapter; import android.widget.ArrayAdapter;
@@ -48,18 +50,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 +68,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 +78,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,45 +104,60 @@ 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();
mWakeLock = null;
}
updateNotification();
} else if (ACTION_LOCK_WIFI.equals(action)) {
if (mWifiLock == null) {
WifiManager wm = (WifiManager) getSystemService(Context.WIFI_SERVICE); 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 + "'");
} }
if ((flags & START_FLAG_REDELIVERY) == 0) {
// Service is started by WBR, not restarted by system, so release the WakeLock from WBR.
WakefulBroadcastReceiver.completeWakefulIntent(intent);
}
// If this service really do get killed, there is no point restarting it automatically - let the user do on next // If this service really do get killed, there is no point restarting it automatically - let the user do on next
// start of {@link Term): // start of {@link Term):
return Service.START_NOT_STICKY; return Service.START_NOT_STICKY;
@@ -155,8 +175,8 @@ public final class TermuxService extends Service implements SessionChangedCallba
/** 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() { private 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 +191,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);
@@ -192,7 +209,7 @@ public final class TermuxService extends Service implements SessionChangedCallba
// 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 minimal priority since this is just a background service notification:
builder.setPriority((wakeLockHeld || wifiLockHeld) ? Notification.PRIORITY_HIGH : Notification.PRIORITY_MIN); builder.setPriority((wakeLockHeld) ? Notification.PRIORITY_HIGH : Notification.PRIORITY_MIN);
// No need to show a timestamp: // No need to show a timestamp:
builder.setShowWhen(false); builder.setShowWhen(false);
@@ -204,13 +221,13 @@ public final class TermuxService extends Service implements SessionChangedCallba
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();
} }
@@ -236,30 +253,9 @@ 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"); File shell = new File(HOME_PATH, ".termux/shell");
if (shell.exists()) { if (shell.exists()) {
@@ -290,23 +286,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 +349,13 @@ 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();
}
});
}
} }

View File

@@ -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;
} }
@@ -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);
} }
} }

View File

@@ -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];

View File

@@ -722,7 +722,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);

View File

@@ -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);

View File

@@ -46,17 +46,17 @@ public final class TextStyle {
long result = effect & 0b111111111; long result = effect & 0b111111111;
if ((0xff000000 & foreColor) == 0xff000000) { if ((0xff000000 & foreColor) == 0xff000000) {
// 24-bit color. // 24-bit color.
result |= CHARACTER_ATTRIBUTE_TRUECOLOR_FOREGROUND | (((long) foreColor & 0x00ffffffL) << 40L); result |= CHARACTER_ATTRIBUTE_TRUECOLOR_FOREGROUND | ((foreColor & 0x00ffffffL) << 40L);
} else { } else {
// Indexed color. // Indexed color.
result |= (((long) foreColor) & 0b111111111L) << 40; result |= (foreColor & 0b111111111L) << 40;
} }
if ((0xff000000 & backColor) == 0xff000000) { if ((0xff000000 & backColor) == 0xff000000) {
// 24-bit color. // 24-bit color.
result |= CHARACTER_ATTRIBUTE_TRUECOLOR_BACKGROUND | (((long) backColor & 0x00ffffffL) << 16L); result |= CHARACTER_ATTRIBUTE_TRUECOLOR_BACKGROUND | ((backColor & 0x00ffffffL) << 16L);
} else { } else {
// Indexed color. // Indexed color.
result |= (((long) backColor) & 0b111111111L) << 16L; result |= (backColor & 0b111111111L) << 16L;
} }
return result; return result;

View File

@@ -54,7 +54,8 @@ final class TerminalRenderer {
} }
/** Render the terminal to a canvas with at a specified row scroll, and an optional rectangular selection. */ /** Render the terminal to a canvas with at a specified row scroll, and an optional rectangular selection. */
public final void render(TerminalEmulator mEmulator, Canvas canvas, int topRow, int selectionY1, int selectionY2, int selectionX1, int selectionX2) { public final void render(TerminalEmulator mEmulator, Canvas canvas, int topRow,
int selectionY1, int selectionY2, int selectionX1, int selectionX2) {
final boolean reverseVideo = mEmulator.isReverseVideo(); final boolean reverseVideo = mEmulator.isReverseVideo();
final int endRow = topRow + mEmulator.mRows; final int endRow = topRow + mEmulator.mRows;
final int columns = mEmulator.mColumns; final int columns = mEmulator.mColumns;
@@ -63,6 +64,7 @@ final class TerminalRenderer {
final boolean cursorVisible = mEmulator.isShowingCursor(); final boolean cursorVisible = mEmulator.isShowingCursor();
final TerminalBuffer screen = mEmulator.getScreen(); final TerminalBuffer screen = mEmulator.getScreen();
final int[] palette = mEmulator.mColors.mCurrentColors; final int[] palette = mEmulator.mColors.mCurrentColors;
final int cursorShape = mEmulator.getCursorStyle();
if (reverseVideo) if (reverseVideo)
canvas.drawColor(palette[TextStyle.COLOR_INDEX_FOREGROUND], PorterDuff.Mode.SRC); canvas.drawColor(palette[TextStyle.COLOR_INDEX_FOREGROUND], PorterDuff.Mode.SRC);
@@ -113,8 +115,10 @@ final class TerminalRenderer {
} else { } else {
final int columnWidthSinceLastRun = column - lastRunStartColumn; final int columnWidthSinceLastRun = column - lastRunStartColumn;
final int charsSinceLastRun = currentCharIndex - lastRunStartIndex; final int charsSinceLastRun = currentCharIndex - lastRunStartIndex;
drawTextRun(canvas, line, palette, heightOffset, lastRunStartColumn, columnWidthSinceLastRun, lastRunStartIndex, charsSinceLastRun, int cursorColor = lastRunInsideCursor ? mEmulator.mColors.mCurrentColors[TextStyle.COLOR_INDEX_CURSOR] : 0;
measuredWidthForRun, lastRunInsideCursor, lastRunStyle, reverseVideo); drawTextRun(canvas, line, palette, heightOffset, lastRunStartColumn, columnWidthSinceLastRun,
lastRunStartIndex, charsSinceLastRun, measuredWidthForRun,
cursorColor, cursorShape, lastRunStyle, reverseVideo);
} }
measuredWidthForRun = 0.f; measuredWidthForRun = 0.f;
lastRunStyle = style; lastRunStyle = style;
@@ -135,37 +139,42 @@ final class TerminalRenderer {
final int columnWidthSinceLastRun = columns - lastRunStartColumn; final int columnWidthSinceLastRun = columns - lastRunStartColumn;
final int charsSinceLastRun = currentCharIndex - lastRunStartIndex; final int charsSinceLastRun = currentCharIndex - lastRunStartIndex;
int cursorColor = lastRunInsideCursor ? mEmulator.mColors.mCurrentColors[TextStyle.COLOR_INDEX_CURSOR] : 0;
drawTextRun(canvas, line, palette, heightOffset, lastRunStartColumn, columnWidthSinceLastRun, lastRunStartIndex, charsSinceLastRun, drawTextRun(canvas, line, palette, heightOffset, lastRunStartColumn, columnWidthSinceLastRun, lastRunStartIndex, charsSinceLastRun,
measuredWidthForRun, lastRunInsideCursor, lastRunStyle, reverseVideo); measuredWidthForRun, cursorColor, cursorShape, lastRunStyle, reverseVideo);
} }
} }
/** private void drawTextRun(Canvas canvas, char[] text, int[] palette, float y, int startColumn, int runWidthColumns,
* @param canvas the canvas to render on int startCharIndex, int runWidthChars, float mes, int cursor, int cursorStyle,
* @param palette the color palette to look up colors from textStyle long textStyle, boolean reverseVideo) {
* @param y height offset into the canvas where to render the line: line * {@link #mFontLineSpacing}
* @param startColumn the run offset in columns
* @param runWidthColumns the run width in columns - this is computed from wcwidth() and may not be what the font measures to
* @param text the java char array to render text from
* @param startCharIndex index into the text array where to start
* @param runWidthChars number of java characters from the text array to render
* @param cursor true if rendering a cursor or selection
* @param textStyle the background, foreground and effect encoded using {@link TextStyle}
* @param reverseVideo if the screen is rendered with the global reverse video flag set
*/
private void drawTextRun(Canvas canvas, char[] text, int[] palette, float y, int startColumn, int runWidthColumns, int startCharIndex, int runWidthChars,
float mes, boolean cursor, long textStyle, boolean reverseVideo) {
int foreColor = TextStyle.decodeForeColor(textStyle); int foreColor = TextStyle.decodeForeColor(textStyle);
final int effect = TextStyle.decodeEffect(textStyle);
int backColor = TextStyle.decodeBackColor(textStyle); int backColor = TextStyle.decodeBackColor(textStyle);
final boolean bold = (effect & (TextStyle.CHARACTER_ATTRIBUTE_BOLD | TextStyle.CHARACTER_ATTRIBUTE_BLINK)) != 0;
final boolean underline = (effect & TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE) != 0;
final boolean italic = (effect & TextStyle.CHARACTER_ATTRIBUTE_ITALIC) != 0;
final boolean strikeThrough = (effect & TextStyle.CHARACTER_ATTRIBUTE_STRIKETHROUGH) != 0;
final boolean dim = (effect & TextStyle.CHARACTER_ATTRIBUTE_DIM) != 0;
int foreColorIndex = -1;
if ((foreColor & 0xff000000) != 0xff000000) { if ((foreColor & 0xff000000) != 0xff000000) {
foreColorIndex = foreColor; // Let bold have bright colors if applicable (one of the first 8):
if (bold && foreColor >= 0 && foreColor < 8) foreColor += 8;
foreColor = palette[foreColor]; foreColor = palette[foreColor];
} }
if ((backColor & 0xff000000) != 0xff000000) backColor = palette[backColor];
final int effect = TextStyle.decodeEffect(textStyle); if ((backColor & 0xff000000) != 0xff000000) {
backColor = palette[backColor];
}
// Reverse video here if _one and only one_ of the reverse flags are set:
final boolean reverseVideoHere = reverseVideo ^ (effect & (TextStyle.CHARACTER_ATTRIBUTE_INVERSE)) != 0;
if (reverseVideoHere) {
int tmp = foreColor;
foreColor = backColor;
backColor = tmp;
}
float left = startColumn * mFontWidth; float left = startColumn * mFontWidth;
float right = left + runWidthColumns * mFontWidth; float right = left + runWidthColumns * mFontWidth;
@@ -179,32 +188,21 @@ final class TerminalRenderer {
savedMatrix = true; savedMatrix = true;
} }
// Reverse video here if _one and only one_ of the reverse flags are set:
boolean reverseVideoHere = reverseVideo ^ (effect & (TextStyle.CHARACTER_ATTRIBUTE_INVERSE)) != 0;
// Switch if _one and only one_ of reverse video and cursor is set:
if (reverseVideoHere ^ cursor) {
int tmp = foreColor;
foreColor = backColor;
backColor = tmp;
}
if (backColor != palette[TextStyle.COLOR_INDEX_BACKGROUND]) { if (backColor != palette[TextStyle.COLOR_INDEX_BACKGROUND]) {
// Only draw non-default background. // Only draw non-default background.
mTextPaint.setColor(backColor); mTextPaint.setColor(backColor);
canvas.drawRect(left, y - mFontLineSpacingAndAscent + mFontAscent, right, y, mTextPaint); canvas.drawRect(left, y - mFontLineSpacingAndAscent + mFontAscent, right, y, mTextPaint);
} }
if (cursor != 0) {
mTextPaint.setColor(cursor);
float cursorHeight = mFontLineSpacingAndAscent - mFontAscent;
if (cursorStyle == TerminalEmulator.CURSOR_STYLE_UNDERLINE) cursorHeight /= 4.;
else if (cursorStyle == TerminalEmulator.CURSOR_STYLE_BAR) right -= ((right - left) * 3) / 4.;
canvas.drawRect(left, y - cursorHeight, right, y, mTextPaint);
}
if ((effect & TextStyle.CHARACTER_ATTRIBUTE_INVISIBLE) == 0) { if ((effect & TextStyle.CHARACTER_ATTRIBUTE_INVISIBLE) == 0) {
// Treat blink as bold:
final boolean bold = (effect & (TextStyle.CHARACTER_ATTRIBUTE_BOLD | TextStyle.CHARACTER_ATTRIBUTE_BLINK)) != 0;
final boolean underline = (effect & TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE) != 0;
final boolean italic = (effect & TextStyle.CHARACTER_ATTRIBUTE_ITALIC) != 0;
final boolean strikeThrough = (effect & TextStyle.CHARACTER_ATTRIBUTE_STRIKETHROUGH) != 0;
final boolean dim = (effect & TextStyle.CHARACTER_ATTRIBUTE_DIM) != 0;
// Let bold have bright colors if applicable (one of the first 8):
if (bold && foreColorIndex >= 0 && foreColorIndex < 8) foreColor = palette[foreColorIndex + 8];
if (dim) { if (dim) {
int red = (0xFF & (foreColor >> 16)); int red = (0xFF & (foreColor >> 16));
int green = (0xFF & (foreColor >> 8)); int green = (0xFF & (foreColor >> 8));

View File

@@ -238,8 +238,9 @@ public final class TerminalView extends View {
// https://github.com/termux/termux-app/issues/137 (japanese chars and TYPE_NULL). // https://github.com/termux/termux-app/issues/137 (japanese chars and TYPE_NULL).
outAttrs.inputType = InputType.TYPE_NULL; outAttrs.inputType = InputType.TYPE_NULL;
// Let part of the application show behind when in landscape: // Note that IME_ACTION_NONE cannot be used as that makes it impossible to input newlines using the on-screen
outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_FULLSCREEN; // keyboard on Android TV (see https://github.com/termux/termux-app/issues/221).
outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_FULLSCREEN;
return new BaseInputConnection(this, true) { return new BaseInputConnection(this, true) {
@@ -270,16 +271,15 @@ public final class TerminalView extends View {
@Override @Override
public boolean deleteSurroundingText(int leftLength, int rightLength) { public boolean deleteSurroundingText(int leftLength, int rightLength) {
if (LOG_KEY_EVENTS) if (LOG_KEY_EVENTS) {
Log.i(EmulatorDebug.LOG_TAG, "IME: deleteSurroundingText(" + leftLength + ", " + rightLength + ")"); Log.i(EmulatorDebug.LOG_TAG, "IME: deleteSurroundingText(" + leftLength + ", " + rightLength + ")");
// If leftLength=2 it may be due to a UTF-16 surrogate pair. So we cannot send }
// multiple key events for that. Let's just hope that keyboards don't use // The stock Samsung keyboard with 'Auto check spelling' enabled sends leftLength > 1.
// leftLength > 1 for other purposes (such as holding down backspace for repeat). KeyEvent deleteKey = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL);
sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL)); for (int i = 0; i < leftLength; i++) sendKeyEvent(deleteKey);
return super.deleteSurroundingText(leftLength, rightLength); return super.deleteSurroundingText(leftLength, rightLength);
} }
void sendTextToTerminal(CharSequence text) { void sendTextToTerminal(CharSequence text) {
final int textLengthInChars = text.length(); final int textLengthInChars = text.length();
for (int i = 0; i < textLengthInChars; i++) { for (int i = 0; i < textLengthInChars; i++) {
@@ -298,6 +298,14 @@ public final class TerminalView extends View {
boolean ctrlHeld = false; boolean ctrlHeld = false;
if (codePoint <= 31 && codePoint != 27) { if (codePoint <= 31 && codePoint != 27) {
if (codePoint == '\n') {
// The AOSP keyboard and descendants seems to send \n as text when the enter key is pressed,
// instead of a key event like most other keyboard apps. A terminal expects \r for the enter
// key (although when icrnl is enabled this doesn't make a difference - run 'stty -icrnl' to
// check the behaviour).
codePoint = '\r';
}
// E.g. penti keyboard for ctrl input. // E.g. penti keyboard for ctrl input.
ctrlHeld = true; ctrlHeld = true;
switch (codePoint) { switch (codePoint) {

View 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)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 695 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 786 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 597 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 779 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 983 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

View 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>

View 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="#000"
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="#000"
android:pathData="M12,12
l5,0
l0,10
l-5,0"
/>
</vector>

View File

@@ -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"

Binary file not shown.

After

Width:  |  Height:  |  Size: 343 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 324 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 510 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 685 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

@@ -50,8 +50,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>

View File

@@ -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,10 @@
<!-- 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>
<!-- 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">

View 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>

View File

@@ -19,6 +19,7 @@ public class WcWidthTest extends TestCase {
assertWidthIs(1, 'å'); assertWidthIs(1, 'å');
assertWidthIs(1, 'ä'); assertWidthIs(1, 'ä');
assertWidthIs(1, 'ö'); assertWidthIs(1, 'ö');
assertWidthIs(1, 0x23F2);
} }
public void testSomeWide() { public void testSomeWide() {
@@ -45,6 +46,7 @@ public class WcWidthTest extends TestCase {
public void testCombining() { public void testCombining() {
assertWidthIs(0, 0x0302); assertWidthIs(0, 0x0302);
assertWidthIs(0, 0x0308); assertWidthIs(0, 0x0308);
assertWidthIs(0, 0xFE0F);
} }
public void testWordJoiner() { public void testWordJoiner() {

20
art/copy-to-other-apps.sh Executable file
View 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

31
art/feature-graphic.svg Normal file
View File

@@ -0,0 +1,31 @@
<?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:svg="http://www.w3.org/2000/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: 1006 B

View 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
View 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
View 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
View 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="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 fill="#FFF"
d="M12,12
l5,0
l0,10
l-5,0"
/>
</svg>

After

Width:  |  Height:  |  Size: 512 B

21
art/ic_launcher_round.svg Normal file
View 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

24
art/tv-banner.svg Normal file
View File

@@ -0,0 +1,24 @@
<?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:svg="http://www.w3.org/2000/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: 780 B

View File

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

View File

@@ -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

Binary file not shown.

View File

@@ -1,6 +1,6 @@
#Sat Jul 23 17:08:29 CEST 2016 #Sun Dec 04 17:26:05 CET 2016
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
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 distributionUrl=https\://services.gradle.org/distributions/gradle-3.2.1-bin.zip

22
gradlew vendored
View File

@@ -1,4 +1,4 @@
#!/usr/bin/env bash #!/usr/bin/env sh
############################################################################## ##############################################################################
## ##
@@ -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
View 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