Compare commits

..

61 Commits

Author SHA1 Message Date
Leonid Pliushch
92570bee06 version 0.106 2021-01-17 19:45:19 +02:00
Leonid Pliushch
05bb399893 terminal-view: fix array indexing 2021-01-17 19:40:29 +02:00
Henrik Grimler
831aa69da8 Merge pull request #726 from dkramer95/OpenBroadcast
Sends broadcast on app open to notify addon termux receivers
2021-01-06 13:48:15 +01:00
Leonid Pliushch
a56cba6843 version 0.105 2021-01-06 14:31:56 +02:00
Leonid Pliushch
9228982632 remove restrictions from viewable file types
Fixes https://github.com/termux/termux-app/issues/1872 and
similar issues.
2021-01-06 14:28:28 +02:00
Leonid Pliushch
38114784f1 update bootstrap archives 2021-01-06 14:23:15 +02:00
Henrik Grimler
b805f1486c Merge pull request #1869 from termux/ctrlspace
Add workaround property to fix ctrl+space on devices where this does not work
2021-01-04 22:41:56 +01:00
Henrik Grimler
7d31b7f480 terminal-emulator: tests: change spaces to tabs for consistency 2021-01-02 09:12:14 +01:00
Henrik Grimler
a0298285e3 terminal-emulator: tests: avoid error about methods not being mocked
unitTests.returnDefaultValues = true fixes it.
More info: http://g.co/androidstudio/not-mocked
2021-01-02 09:12:14 +01:00
Lucy Phipps
538a1d5cdf F-Droid URL for Termux:Styling 2021-01-01 23:48:34 +02:00
Henrik Grimler
f1e973f0d2 terminal-view: add "ctrl-space-workaround" property
Makes it possible to run ctrl+space with hardware keyboards on
devices/ROMs where it otherwise is broken. On devices where it already
works this workaround breaks ctrl+space though.

Where to add this fix was investigated and found by @5bodnar.
2021-01-01 19:20:16 +01:00
Henrik Grimler
b467b68f7b terminal-view: mv code to get properties to its own function 2021-01-01 19:20:11 +01:00
Henrik Grimler
b895cbbb1e app: set importantForAutofill="no" for extra keys input field
Fixes warning "extra_keys_right.xml:2: Missing autofillHints attribute".
2021-01-01 18:13:37 +01:00
Henrik Grimler
fc30eba247 terminal-view: silence warning from use of SHOW_AS_ACTION_ALWAYS
```
Prefer "SHOW_AS_ACTION_IF_ROOM" instead of "SHOW_AS_ACTION_ALWAYS"
```
2021-01-01 18:13:36 +01:00
Henrik Grimler
b1d4c0c7fe app: disable test for "ProtectedPermissions"
Needed for tests to pass after READ_LOGS and WRITE_SECURE_SETTINGS
were added to AndroidManifest.xml.
2021-01-01 18:13:36 +01:00
Henrik Grimler
7fe5bd32c8 Update ndk to r22 2021-01-01 18:12:53 +01:00
Henrik Grimler
43bbef9a11 Update plugins and gradle to latest versions 2021-01-01 18:12:53 +01:00
Henrik Grimler
eaea0f74a5 Fix github links for terminal-{emulator,view} jcenter packages 2021-01-01 18:12:53 +01:00
Leonid Pliushch
cb13a5a531 version 0.104 2020-12-29 13:45:35 +02:00
Kevin LeBlanc
d267843e36 Don't exclude hidden files from document provider (#1220)
Credit to @johnmellor for requesting the document provider in the
first place via #79, mentioning this limitation in a comment on
that issue, and creating a commit like this one to address it.
2020-12-29 13:42:36 +02:00
Leonid Pliushch
5ca67dd885 update bootstrap archives 2020-12-29 13:40:25 +02:00
Leonid Pliushch
6b0d531758 Revert "update readme" 2020-12-17 20:27:46 +02:00
Leonid Pliushch
1b3283bd69 update bootstrap archives 2020-12-11 20:07:40 +02:00
Leonid Pliushch
2820f6a7b8 update readme 2020-12-11 17:36:14 +02:00
Leonid Pliushch
8925ae67bb update readme 2020-12-11 17:35:28 +02:00
Lucy Phipps
4066c5df42 don't add $PREFIX/bin/applets to $PATH
busybox doesn't use that folder anymore, and is deprecated anyway
2020-11-24 23:16:17 +02:00
Leonid Pliushch
20aac6aa72 copy FUNDING.yml from termux-packages repo 2020-11-18 03:04:38 +02:00
Leonid Pliushch
e1f799f9a1 version 0.103 2020-11-17 20:40:40 +02:00
Leonid Pliushch
4479de4b9b update bootstrap archives 2020-11-17 20:35:55 +02:00
Leonid Pliushch
3a8f53a54c add permission WRITE_SECURE_SETTINGS
https://github.com/termux/termux-api/issues/211
2020-11-17 20:07:33 +02:00
Leonid Pliushch
2f04a6186b add READ_LOGS permission
* https://github.com/termux/termux-app/issues/873
* https://github.com/termux/termux-app/issues/1821
2020-11-17 20:03:10 +02:00
Leonid Pliushch
ad64dd7c3d update bootstrap archives 2020-10-12 20:39:28 +03:00
Fredrik Fornwall
dfc4595ec5 version 0.102 2020-10-02 10:30:18 +02:00
Fabian Henneke
8c80efb904 Add an "Autofill password" context menu action 2020-10-02 10:21:34 +02:00
Leonid Pliushch
564079c7e9 update .gitattributes 2020-10-01 01:08:39 +03:00
Leonid Pliushch
5b6fd9b88c version 0.101 2020-09-28 03:30:21 +03:00
Leonid Pliushch
63bfe95848 update bootstrap archives again
Fixes faulty dpkg https://github.com/termux/termux-packages/issues/5858.
2020-09-28 03:28:49 +03:00
Leonid Pliushch
af95a99854 version 0.100 2020-09-28 00:59:52 +03:00
Leonid Pliushch
25523ae224 fix bootstrap archive checksum for arm
Due to leading zero issues, SHA-256 checksum for ARM variant will
have 63 characters instead of correct 64...
2020-09-28 00:52:44 +03:00
Leonid Pliushch
665adb6895 update bootstrap archives 2020-09-28 00:46:38 +03:00
Henrik Grimler
eb7fda28df Merge pull request #1764 from agnostic-apollo/termux-run-command-crash-and-foreground-patch
Fix RunCommandService crash and foreground issue
2020-09-27 21:02:56 +02:00
agnostic-apollo
7063a3a9da Add @override annotation to onStartCommand() function of RunCommandService. 2020-09-18 22:25:05 +05:00
agnostic-apollo
6e02b99ff6 Update RunCommandService docs. 2020-09-18 22:22:35 +05:00
agnostic-apollo
9aae665bfc Fix issue where termux session does not come to foreground automatically in android >= 10 unless user manually clicks termux notification for "RUN_COMMAND" intents and "Termux:Tasker" plugin actions that have background mode "false" because of new restrictions of starting activities from background. This is done by adding "android.permission.SYSTEM_ALERT_WINDOW" permission in AndroidManifest.xml so that the user may optionally grant "Draw Over Apps" permission to termux to fix the issue. 2020-09-18 22:21:58 +05:00
agnostic-apollo
52ce6cc94b Add support for "$PREFIX/" and "~/" prefix in "RUN_COMMAND" intent extras for paths. 2020-09-18 22:21:31 +05:00
agnostic-apollo
f91168eff4 Fix issue where termux crashes occasionally in android >= 8 because "startForeground()" function is not being called before running "startForegroundService()" in RunCommandService. 2020-09-18 22:20:23 +05:00
Henrik Grimler
8faa5b2151 TerminalEmulator: fix bug in DECRQM handling
Reported in https://github.com/termux/termux-app/issues/1752
2020-09-12 21:27:24 +02:00
Leonid Pliushch
216cc10f3c Revert "add gradle distribution sha256"
Was initially added to try fix F-Droid builds but appears missing
distribution sha256 is not a cause of the issue.

F-Droid maintains own set of Gradle SHA-256 checksums.

This reverts commit cba80b6c0b.
2020-09-12 19:06:54 +00:00
Leonid Pliushch
cba80b6c0b add gradle distribution sha256 2020-09-01 03:38:53 +03:00
Leonid Pliushch
850faa25dd version 0.99 2020-08-26 20:51:05 +03:00
Leonid Pliushch
a108b2bd6b gradle 6.6.1 2020-08-26 20:40:29 +03:00
Leonid Pliushch
b95823d7a8 update bootstrap archives 2020-08-26 20:27:47 +03:00
Leonid Pliushch
382da7e8f7 terminal view: provide a workaroud for issue with some keyboards
Issue https://github.com/termux/termux-app/issues/686.

Note that there can be a better workaround which I don't know...
2020-08-26 20:04:22 +03:00
blackcat-917
ba9c118b50 readme: fix a copy-paste typo (#1720) 2020-08-22 00:17:45 +03:00
Leonid Pliushch
531c32f3c9 CI: do builds for android-10 branch 2020-08-16 22:56:55 +03:00
Leonid Pliushch
db2f50c76e extra keys: use TextUtils.join instead of String.join
String.join() is available only on Android API 26+ but our current
minimal is 24.

See https://github.com/termux/termux-app/issues/1670.
2020-08-14 15:08:32 +03:00
Leonid Pliushch
784affe39c linter: fix wakelock log tag
See https://github.com/termux/termux-app/issues/1670.
2020-08-14 15:03:51 +03:00
Leonid Pliushch
b486d29d23 fix RUN_COMMAND permission description
See https://github.com/termux/termux-app/issues/1713.
2020-08-14 14:50:34 +03:00
Fredrik Fornwall
332f1104a3 Update drawerlayout library 2020-08-09 00:07:35 +02:00
Leonid Pliushch
5a70be1523 terminal emulator: flush remaining process output data when terminating session 2020-08-08 02:22:47 +03:00
David Kramer
3bb2849a88 Sends broadcast on app open to notify addon termux receivers 2018-06-18 13:06:34 -06:00
38 changed files with 445 additions and 238 deletions

8
.gitattributes vendored
View File

@@ -1,3 +1,5 @@
* text=auto * text=auto
*.bat eol=crlf *.bat eol=crlf
*.sh eol=lf *.gradle eol=lf
*.mk eol=lf
*.sh eol=lf

2
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,2 @@
patreon: termux
custom: https://paypal.me/fornwall

View File

@@ -4,9 +4,11 @@ on:
push: push:
branches: branches:
- master - master
- android-10
pull_request: pull_request:
branches: branches:
- master - master
- android-10
jobs: jobs:
build: build:

View File

@@ -4,9 +4,11 @@ on:
push: push:
branches: branches:
- master - master
- android-10
pull_request: pull_request:
branches: branches:
- master - master
- android-10
jobs: jobs:
validation: validation:

View File

@@ -4,9 +4,11 @@ on:
push: push:
branches: branches:
- master - master
- android-10
pull_request: pull_request:
branches: branches:
- master - master
- android-10
jobs: jobs:
testing: testing:

View File

@@ -16,7 +16,7 @@ terminal emulation). For the packages installable inside the app, see
## Installation ## Installation
Termux:Widget application can be obtained from: Termux application can be obtained from:
- [Google Play](https://play.google.com/store/apps/details?id=com.termux) - [Google Play](https://play.google.com/store/apps/details?id=com.termux)
- [F-Droid](https://f-droid.org/en/packages/com.termux/) - [F-Droid](https://f-droid.org/en/packages/com.termux/)

View File

@@ -9,7 +9,7 @@ android {
dependencies { dependencies {
implementation "androidx.annotation:annotation:1.1.0" implementation "androidx.annotation:annotation:1.1.0"
implementation "androidx.viewpager:viewpager:1.0.0" implementation "androidx.viewpager:viewpager:1.0.0"
implementation "androidx.drawerlayout:drawerlayout:1.0.0" implementation "androidx.drawerlayout:drawerlayout:1.1.1"
implementation project(":terminal-view") implementation project(":terminal-view")
} }
@@ -17,8 +17,8 @@ android {
applicationId "com.termux" applicationId "com.termux"
minSdkVersion project.properties.minSdkVersion.toInteger() minSdkVersion project.properties.minSdkVersion.toInteger()
targetSdkVersion project.properties.targetSdkVersion.toInteger() targetSdkVersion project.properties.targetSdkVersion.toInteger()
versionCode 98 versionCode 106
versionName "0.98" versionName "0.106"
externalNativeBuild { externalNativeBuild {
ndkBuild { ndkBuild {
@@ -64,6 +64,10 @@ android {
} }
} }
lintOptions {
disable 'ProtectedPermissions'
}
testOptions { testOptions {
unitTests { unitTests {
includeAndroidResources = true includeAndroidResources = true
@@ -72,8 +76,8 @@ android {
} }
dependencies { dependencies {
testImplementation 'junit:junit:4.13' testImplementation 'junit:junit:4.13.1'
testImplementation 'org.robolectric:robolectric:4.3.1' testImplementation 'org.robolectric:robolectric:4.4'
} }
task versionName { task versionName {
@@ -133,11 +137,11 @@ clean {
task downloadBootstraps(){ task downloadBootstraps(){
doLast { doLast {
def version = 27 def version = 35
downloadBootstrap("aarch64", "517fb3aa215f7b96961f9377822d7f1b5e86c831efb4ab096ed65d0b1cdf02e9", version) downloadBootstrap("aarch64", "6707cc641cde13dc2f24e819cd8ca3f1a9a003577523c25beff72690588594f5", version)
downloadBootstrap("arm", "94d17183afdd017cf8ab885b9103a370b16bec1d3cb641884511d545ee009b90", version) downloadBootstrap("arm", "eadc9afb52900dc744fdb39ed0c3dbb87ad8ce6190b27946467df7aeab353fa7", version)
downloadBootstrap("i686", "7f27723d2f0afbe7e90f203b3ca2e80871a8dfa08b136229476aa5e7ba3e988f", version) downloadBootstrap("i686", "b674ef43c8388dd19e0d25b024b5792c8532ee0ddcc49f90c0716042724fa905", version)
downloadBootstrap("x86_64", "b19b2721bae5fb3a3fb0754c49611ce4721221e1e7997e7fd98940776ad88c3d", version) downloadBootstrap("x86_64", "86043eb76efededbdf5644686a899f4d762703fe18e23fb906a2482d593d7549", version)
} }
} }

View File

@@ -21,6 +21,9 @@
<uses-permission android:name="android.permission.VIBRATE" /> <uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/> <uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.READ_LOGS" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
<application <application
android:extractNativeLibs="true" android:extractNativeLibs="true"
@@ -80,16 +83,15 @@
<data android:mimeType="text/*" /> <data android:mimeType="text/*" />
<data android:mimeType="video/*" /> <data android:mimeType="video/*" />
</intent-filter> </intent-filter>
<!-- Be more restrictive for viewing files, restricting ourselves to text files. --> <!-- Accept multiple file types to let Termux be usable as generic file viewer. -->
<intent-filter tools:ignore="AppLinkUrlError"> <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="application/*" />
<data android:mimeType="audio/*" />
<data android:mimeType="image/*" />
<data android:mimeType="text/*" /> <data android:mimeType="text/*" />
<data android:mimeType="application/*log*" /> <data android:mimeType="video/*" />
<data android:mimeType="application/json" />
<data android:mimeType="application/*xml*" />
<data android:mimeType="application/*latex*" />
<data android:mimeType="application/javascript" />
</intent-filter> </intent-filter>
</activity> </activity>

View File

@@ -161,7 +161,7 @@ public final class BackgroundJob {
environment.add("PATH= " + System.getenv("PATH")); environment.add("PATH= " + System.getenv("PATH"));
} else { } else {
environment.add("LANG=en_US.UTF-8"); environment.add("LANG=en_US.UTF-8");
environment.add("PATH=" + TermuxService.PREFIX_PATH + "/bin:" + TermuxService.PREFIX_PATH + "/bin/applets"); environment.add("PATH=" + TermuxService.PREFIX_PATH + "/bin");
environment.add("PWD=" + cwd); environment.add("PWD=" + cwd);
environment.add("TMPDIR=" + TermuxService.PREFIX_PATH + "/tmp"); environment.add("TMPDIR=" + TermuxService.PREFIX_PATH + "/tmp");
} }

View File

@@ -1,5 +1,7 @@
package com.termux.app; package com.termux.app;
import android.text.TextUtils;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import org.json.JSONArray; import org.json.JSONArray;
@@ -305,7 +307,7 @@ class ExtraKeyButton {
keys[i] = ExtraKeysInfos.replaceAlias(keys[i]); keys[i] = ExtraKeysInfos.replaceAlias(keys[i]);
} }
this.key = String.join(" ", keys); this.key = TextUtils.join(" ", keys);
String displayFromConfig = config.optString("display", null); String displayFromConfig = config.optString("display", null);
if (displayFromConfig != null) { if (displayFromConfig != null) {

View File

@@ -1,6 +1,10 @@
package com.termux.app; package com.termux.app;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.Service; import android.app.Service;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.Binder; import android.os.Binder;
@@ -8,6 +12,8 @@ import android.os.Build;
import android.os.IBinder; import android.os.IBinder;
import android.util.Log; import android.util.Log;
import com.termux.R;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
@@ -20,9 +26,21 @@ import java.util.Properties;
* *
* Third-party program must declare com.termux.permission.RUN_COMMAND permission and it should be * Third-party program must declare com.termux.permission.RUN_COMMAND permission and it should be
* granted by user. * granted by user.
* Full path of command or script must be given in "RUN_COMMAND_PATH" extra. *
* Absolute path of command or script must be given in "RUN_COMMAND_PATH" extra.
* The "RUN_COMMAND_ARGUMENTS", "RUN_COMMAND_WORKDIR" and "RUN_COMMAND_BACKGROUND" extras are * The "RUN_COMMAND_ARGUMENTS", "RUN_COMMAND_WORKDIR" and "RUN_COMMAND_BACKGROUND" extras are
* optional. The background mode defaults to false. * optional. The workdir defaults to termux home. The background mode defaults to "false".
* The command path and workdir can optionally be prefixed with "$PREFIX/" or "~/" if an absolute
* path is not to be given.
*
* To automatically bring to foreground and start termux commands that were started with
* background mode "false" in android >= 10 without user having to click the notification manually,
* requires termux to be granted draw over apps permission due to new restrictions
* of starting activities from the background, this also applies to Termux:Tasker plugin.
*
* To reduce the chance of termux being killed by android even further due to violation of not
* being able to call startForeground() within ~5s of service start in android >= 8, the user
* may disable battery optimizations for termux.
* *
* Sample code to run command "top" with java: * Sample code to run command "top" with java:
* Intent intent = new Intent(); * Intent intent = new Intent();
@@ -50,6 +68,9 @@ public class RunCommandService extends Service {
public static final String RUN_COMMAND_WORKDIR = "com.termux.RUN_COMMAND_WORKDIR"; public static final String RUN_COMMAND_WORKDIR = "com.termux.RUN_COMMAND_WORKDIR";
public static final String RUN_COMMAND_BACKGROUND = "com.termux.RUN_COMMAND_BACKGROUND"; public static final String RUN_COMMAND_BACKGROUND = "com.termux.RUN_COMMAND_BACKGROUND";
private static final String NOTIFICATION_CHANNEL_ID = "termux_run_command_notification_channel";
private static final int NOTIFICATION_ID = 1338;
class LocalBinder extends Binder { class LocalBinder extends Binder {
public final RunCommandService service = RunCommandService.this; public final RunCommandService service = RunCommandService.this;
} }
@@ -61,14 +82,23 @@ public class RunCommandService extends Service {
return mBinder; return mBinder;
} }
@Override
public void onCreate() {
runStartForeground();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) { public int onStartCommand(Intent intent, int flags, int startId) {
// Run again in case service is already started and onCreate() is not called
runStartForeground();
if (allowExternalApps() && RUN_COMMAND_ACTION.equals(intent.getAction())) { if (allowExternalApps() && RUN_COMMAND_ACTION.equals(intent.getAction())) {
Uri programUri = new Uri.Builder().scheme("com.termux.file").path(intent.getStringExtra(RUN_COMMAND_PATH)).build(); Uri programUri = new Uri.Builder().scheme("com.termux.file").path(parsePath(intent.getStringExtra(RUN_COMMAND_PATH))).build();
Intent execIntent = new Intent(TermuxService.ACTION_EXECUTE, programUri); Intent execIntent = new Intent(TermuxService.ACTION_EXECUTE, programUri);
execIntent.setClass(this, TermuxService.class); execIntent.setClass(this, TermuxService.class);
execIntent.putExtra(TermuxService.EXTRA_ARGUMENTS, intent.getStringArrayExtra(RUN_COMMAND_ARGUMENTS)); execIntent.putExtra(TermuxService.EXTRA_ARGUMENTS, intent.getStringArrayExtra(RUN_COMMAND_ARGUMENTS));
execIntent.putExtra(TermuxService.EXTRA_CURRENT_WORKING_DIRECTORY, intent.getStringExtra(RUN_COMMAND_WORKDIR)); execIntent.putExtra(TermuxService.EXTRA_CURRENT_WORKING_DIRECTORY, parsePath(intent.getStringExtra(RUN_COMMAND_WORKDIR)));
execIntent.putExtra(TermuxService.EXTRA_EXECUTE_IN_BACKGROUND, intent.getBooleanExtra(RUN_COMMAND_BACKGROUND, false)); execIntent.putExtra(TermuxService.EXTRA_EXECUTE_IN_BACKGROUND, intent.getBooleanExtra(RUN_COMMAND_BACKGROUND, false));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
@@ -78,9 +108,56 @@ public class RunCommandService extends Service {
} }
} }
runStopForeground();
return Service.START_NOT_STICKY; return Service.START_NOT_STICKY;
} }
private void runStartForeground() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
setupNotificationChannel();
startForeground(NOTIFICATION_ID, buildNotification());
}
}
private void runStopForeground() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
stopForeground(true);
}
}
private Notification buildNotification() {
Notification.Builder builder = new Notification.Builder(this);
builder.setContentTitle(getText(R.string.application_name) + " Run Command");
builder.setSmallIcon(R.drawable.ic_service_notification);
// Use a low priority:
builder.setPriority(Notification.PRIORITY_LOW);
// No need to show a timestamp:
builder.setShowWhen(false);
// Background color for small notification icon:
builder.setColor(0xFF607D8B);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
builder.setChannelId(NOTIFICATION_CHANNEL_ID);
}
return builder.build();
}
private void setupNotificationChannel() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return;
String channelName = "Termux Run Command";
int importance = NotificationManager.IMPORTANCE_LOW;
NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, channelName, importance);
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
manager.createNotificationChannel(channel);
}
private boolean allowExternalApps() { private boolean allowExternalApps() {
File propsFile = new File(TermuxService.HOME_PATH + "/.termux/termux.properties"); File propsFile = new File(TermuxService.HOME_PATH + "/.termux/termux.properties");
if (!propsFile.exists()) if (!propsFile.exists())
@@ -99,4 +176,14 @@ public class RunCommandService extends Service {
return props.getProperty("allow-external-apps", "false").equals("true"); return props.getProperty("allow-external-apps", "false").equals("true");
} }
/** Replace "$PREFIX/" or "~/" prefix with termux absolute paths */
private String parsePath(String path) {
if(path != null && !path.isEmpty()) {
path = path.replaceAll("^\\$PREFIX\\/", TermuxService.PREFIX_PATH + "/");
path = path.replaceAll("^~\\/", TermuxService.HOME_PATH + "/");
}
return path;
}
} }

View File

@@ -15,6 +15,7 @@ import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.ServiceConnection; import android.content.ServiceConnection;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.Paint; import android.graphics.Paint;
import android.graphics.Typeface; import android.graphics.Typeface;
@@ -38,6 +39,7 @@ import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.WindowManager; import android.view.WindowManager;
import android.view.autofill.AutofillManager;
import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodManager;
import android.widget.ArrayAdapter; import android.widget.ArrayAdapter;
import android.widget.EditText; import android.widget.EditText;
@@ -92,6 +94,7 @@ public final class TermuxActivity extends Activity implements ServiceConnection
private static final int CONTEXTMENU_STYLING_ID = 6; private static final int CONTEXTMENU_STYLING_ID = 6;
private static final int CONTEXTMENU_HELP_ID = 8; private static final int CONTEXTMENU_HELP_ID = 8;
private static final int CONTEXTMENU_TOGGLE_KEEP_SCREEN_ON = 9; private static final int CONTEXTMENU_TOGGLE_KEEP_SCREEN_ON = 9;
private static final int CONTEXTMENU_AUTOFILL_ID = 10;
private static final int MAX_SESSIONS = 8; private static final int MAX_SESSIONS = 8;
@@ -99,6 +102,8 @@ public final class TermuxActivity extends Activity implements ServiceConnection
private static final String RELOAD_STYLE_ACTION = "com.termux.app.reload_style"; private static final String RELOAD_STYLE_ACTION = "com.termux.app.reload_style";
private static final String BROADCAST_TERMUX_OPENED = "com.termux.app.OPENED";
/** The main view of the activity showing the terminal. Initialized in onCreate(). */ /** The main view of the activity showing the terminal. Initialized in onCreate(). */
@SuppressWarnings("NullableProblems") @SuppressWarnings("NullableProblems")
@NonNull @NonNull
@@ -322,6 +327,26 @@ public final class TermuxActivity extends Activity implements ServiceConnection
checkForFontAndColors(); checkForFontAndColors();
mBellSoundId = mBellSoundPool.load(this, R.raw.bell, 1); mBellSoundId = mBellSoundPool.load(this, R.raw.bell, 1);
sendOpenedBroadcast();
}
/**
* Send a broadcast notifying Termux app has been opened
*/
void sendOpenedBroadcast() {
Intent broadcast = new Intent(BROADCAST_TERMUX_OPENED);
List<ResolveInfo> matches = getPackageManager().queryBroadcastReceivers(broadcast, 0);
// send broadcast to registered Termux receivers
// this technique is needed to work around broadcast changes that Oreo introduced
for (ResolveInfo info : matches) {
Intent explicitBroadcast = new Intent(broadcast);
ComponentName cname = new ComponentName(info.activityInfo.applicationInfo.packageName,
info.activityInfo.name);
explicitBroadcast.setComponent(cname);
sendBroadcast(explicitBroadcast);
}
} }
void toggleShowExtraKeys() { void toggleShowExtraKeys() {
@@ -655,6 +680,12 @@ public final class TermuxActivity extends Activity implements ServiceConnection
menu.add(Menu.NONE, CONTEXTMENU_SELECT_URL_ID, Menu.NONE, R.string.select_url); menu.add(Menu.NONE, CONTEXTMENU_SELECT_URL_ID, Menu.NONE, R.string.select_url);
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);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
AutofillManager autofillManager = getSystemService(AutofillManager.class);
if (autofillManager != null && autofillManager.isEnabled()) {
menu.add(Menu.NONE, CONTEXTMENU_AUTOFILL_ID, Menu.NONE, R.string.autofill_password);
}
}
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_STYLING_ID, Menu.NONE, R.string.style_terminal); menu.add(Menu.NONE, CONTEXTMENU_STYLING_ID, Menu.NONE, R.string.style_terminal);
@@ -853,7 +884,7 @@ public final class TermuxActivity extends Activity implements ServiceConnection
// The startActivity() call is not documented to throw IllegalArgumentException. // The startActivity() call is not documented to throw IllegalArgumentException.
// However, crash reporting shows that it sometimes does, so catch it here. // However, crash reporting shows that it sometimes does, so catch it here.
new AlertDialog.Builder(this).setMessage(R.string.styling_not_installed) new AlertDialog.Builder(this).setMessage(R.string.styling_not_installed)
.setPositiveButton(R.string.styling_install, (dialog, which) -> startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://play.google.com/store/apps/details?id=com.termux.styling")))).setNegativeButton(android.R.string.cancel, null).show(); .setPositiveButton(R.string.styling_install, (dialog, which) -> startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://f-droid.org/en/packages/com.termux.styling/")))).setNegativeButton(android.R.string.cancel, null).show();
} }
return true; return true;
} }
@@ -870,6 +901,14 @@ public final class TermuxActivity extends Activity implements ServiceConnection
} }
return true; return true;
} }
case CONTEXTMENU_AUTOFILL_ID: {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
AutofillManager autofillManager = getSystemService(AutofillManager.class);
if (autofillManager != null && autofillManager.isEnabled()) {
autofillManager.requestAutofill(mTerminalView);
}
}
}
default: default:
return super.onContextItemSelected(item); return super.onContextItemSelected(item);
} }

View File

@@ -106,7 +106,7 @@ public final class TermuxService extends Service implements SessionChangedCallba
} else if (ACTION_LOCK_WAKE.equals(action)) { } else if (ACTION_LOCK_WAKE.equals(action)) {
if (mWakeLock == null) { if (mWakeLock == null) {
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 + ":service-wakelock");
mWakeLock.acquire(); mWakeLock.acquire();
// http://tools.android.com/tech-docs/lint-in-studio-2-3#TOC-WifiManager-Leak // http://tools.android.com/tech-docs/lint-in-studio-2-3#TOC-WifiManager-Leak

View File

@@ -91,9 +91,7 @@ public class TermuxDocumentsProvider extends DocumentsProvider {
final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION); final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
final File parent = getFileForDocId(parentDocumentId); final File parent = getFileForDocId(parentDocumentId);
for (File file : parent.listFiles()) { for (File file : parent.listFiles()) {
if (!file.getName().startsWith(".")) { includeFile(result, null, file);
includeFile(result, null, file);
}
} }
return result; return result;
} }
@@ -177,8 +175,7 @@ public class TermuxDocumentsProvider extends DocumentsProvider {
} catch (IOException e) { } catch (IOException e) {
isInsideHome = true; isInsideHome = true;
} }
final boolean isHidden = file.getName().startsWith("."); if (isInsideHome) {
if (isInsideHome && !isHidden) {
if (file.isDirectory()) { if (file.isDirectory()) {
Collections.addAll(pending, file.listFiles()); Collections.addAll(pending, file.listFiles());
} else { } else {

View File

@@ -19,7 +19,9 @@
android:layout_marginLeft="3dp" android:layout_marginLeft="3dp"
android:focusableInTouchMode="true" android:focusableInTouchMode="true"
android:scrollbarThumbVertical="@drawable/terminal_scroll_shape" android:scrollbarThumbVertical="@drawable/terminal_scroll_shape"
android:scrollbars="vertical" /> android:scrollbars="vertical"
android:importantForAutofill="no"
android:autofillHints="password" />
<LinearLayout <LinearLayout
android:id="@+id/left_drawer" android:id="@+id/left_drawer"

View File

@@ -7,6 +7,7 @@
android:imeOptions="actionSend|flagNoFullscreen" android:imeOptions="actionSend|flagNoFullscreen"
android:maxLines="1" android:maxLines="1"
android:inputType="text" android:inputType="text"
android:importantForAutofill="no"
android:textColor="@android:color/white" android:textColor="@android:color/white"
android:textColorHighlight="@android:color/darker_gray" android:textColorHighlight="@android:color/darker_gray"
android:paddingTop="0dp" android:paddingTop="0dp"

View File

@@ -3,7 +3,7 @@
<string name="application_name">Termux</string> <string name="application_name">Termux</string>
<string name="shared_user_label">Termux user</string> <string name="shared_user_label">Termux user</string>
<string name="run_command_permission_label">Run commands in Termux environment</string> <string name="run_command_permission_label">Run commands in Termux environment</string>
<string name="run_command_permission_description">Allow third-party applications to execute arbitrary commands within Termux environment.</string> <string name="run_command_permission_description">execute arbitrary commands within Termux environment</string>
<string name="new_session">New session</string> <string name="new_session">New session</string>
<string name="new_session_failsafe">Failsafe</string> <string name="new_session_failsafe">Failsafe</string>
<string name="toggle_soft_keyboard">Keyboard</string> <string name="toggle_soft_keyboard">Keyboard</string>
@@ -12,6 +12,7 @@
<string name="share_transcript_title">Terminal transcript</string> <string name="share_transcript_title">Terminal transcript</string>
<string name="help">Help</string> <string name="help">Help</string>
<string name="toggle_keep_screen_on">Keep screen on</string> <string name="toggle_keep_screen_on">Keep screen on</string>
<string name="autofill_password">Autofill password</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>

View File

@@ -4,7 +4,7 @@ buildscript {
google() google()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:4.0.1' classpath 'com.android.tools.build:gradle:4.1.1'
} }
} }

View File

@@ -17,5 +17,5 @@ android.useAndroidX=true
minSdkVersion=24 minSdkVersion=24
targetSdkVersion=28 targetSdkVersion=28
ndkVersion=21.3.6528147 ndkVersion=22.0.7026061
compileSdkVersion=28 compileSdkVersion=28

Binary file not shown.

View File

@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5.1-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

21
gradlew.bat vendored
View File

@@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1 %JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init if "%ERRORLEVEL%" == "0" goto execute
echo. echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@@ -54,7 +54,7 @@ goto fail
set JAVA_HOME=%JAVA_HOME:"=% set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init if exist "%JAVA_EXE%" goto execute
echo. echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
@@ -64,21 +64,6 @@ echo location of your Java installation.
goto fail 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 :execute
@rem Setup the command line @rem Setup the command line
@@ -86,7 +71,7 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle @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% "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end :end
@rem End local scope for the variables with windows NT shell @rem End local scope for the variables with windows NT shell

View File

@@ -1,6 +1,6 @@
plugins { plugins {
id "com.jfrog.bintray" version "1.7.3" id "com.jfrog.bintray" version "1.8.5"
id "com.github.dcendents.android-maven" version "2.0" id "com.github.dcendents.android-maven" version "2.1"
} }
apply plugin: 'com.android.library' apply plugin: 'com.android.library'
@@ -11,8 +11,8 @@ ext {
libraryName = 'TerminalEmulator' libraryName = 'TerminalEmulator'
artifact = 'terminal-emulator' artifact = 'terminal-emulator'
libraryDescription = 'The terminal emulator used in Termux' libraryDescription = 'The terminal emulator used in Termux'
siteUrl = 'https://github.com/termux/termux' siteUrl = 'https://github.com/termux/termux-app'
gitUrl = 'https://github.com/termux/termux.git' gitUrl = 'https://github.com/termux/termux-app.git'
libraryVersion = '0.52' libraryVersion = '0.52'
} }
@@ -52,6 +52,10 @@ android {
sourceCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8
} }
testOptions {
unitTests.returnDefaultValues = true
}
} }
tasks.withType(Test) { tasks.withType(Test) {
@@ -61,7 +65,7 @@ tasks.withType(Test) {
} }
dependencies { dependencies {
testImplementation 'junit:junit:4.13' testImplementation 'junit:junit:4.13.1'
} }
apply from: '../scripts/bintray-publish.gradle' apply from: '../scripts/bintray-publish.gradle'

View File

@@ -748,7 +748,7 @@ public final class TerminalEmulator {
value = (mScreen == mAltBuffer) ? 1 : 2; value = (mScreen == mAltBuffer) ? 1 : 2;
} else { } else {
int internalBit = mapDecSetBitToInternalBit(mode); int internalBit = mapDecSetBitToInternalBit(mode);
if (internalBit == -1) { if (internalBit != -1) {
value = isDecsetInternalBitSet(internalBit) ? 1 : 2; // 1=set, 2=reset. value = isDecsetInternalBitSet(internalBit) ? 1 : 2; // 1=set, 2=reset.
} else { } else {
Log.e(EmulatorDebug.LOG_TAG, "Got DECRQM for unrecognized private DEC mode=" + mode); Log.e(EmulatorDebug.LOG_TAG, "Got DECRQM for unrecognized private DEC mode=" + mode);

View File

@@ -110,13 +110,13 @@ public final class TerminalSession extends TerminalOutput {
@Override @Override
public void handleMessage(Message msg) { public void handleMessage(Message msg) {
if (msg.what == MSG_NEW_INPUT && isRunning()) { int bytesRead = mProcessToTerminalIOQueue.read(mReceiveBuffer, false);
int bytesRead = mProcessToTerminalIOQueue.read(mReceiveBuffer, false); if (bytesRead > 0) {
if (bytesRead > 0) { mEmulator.append(mReceiveBuffer, bytesRead);
mEmulator.append(mReceiveBuffer, bytesRead); notifyScreenUpdate();
notifyScreenUpdate(); }
}
} else if (msg.what == MSG_PROCESS_EXITED) { if (msg.what == MSG_PROCESS_EXITED) {
int exitCode = (Integer) msg.obj; int exitCode = (Integer) msg.obj;
cleanupResources(exitCode); cleanupResources(exitCode);
mChangeCallback.onSessionFinished(TerminalSession.this); mChangeCallback.onSessionFinished(TerminalSession.this);

View File

@@ -17,13 +17,13 @@ public class ByteQueueTest extends TestCase {
public void testCompleteWrites() throws Exception { public void testCompleteWrites() throws Exception {
ByteQueue q = new ByteQueue(10); ByteQueue q = new ByteQueue(10);
assertTrue(q.write(new byte[]{1, 2, 3}, 0, 3)); assertTrue(q.write(new byte[]{1, 2, 3}, 0, 3));
byte[] arr = new byte[10]; byte[] arr = new byte[10];
assertEquals(3, q.read(arr, true)); assertEquals(3, q.read(arr, true));
assertArrayEquals(new byte[]{1, 2, 3}, new byte[]{arr[0], arr[1], arr[2]}); assertArrayEquals(new byte[]{1, 2, 3}, new byte[]{arr[0], arr[1], arr[2]});
assertTrue(q.write(new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, 0, 10)); assertTrue(q.write(new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, 0, 10));
assertEquals(10, q.read(arr, true)); assertEquals(10, q.read(arr, true));
assertArrayEquals(new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, arr); assertArrayEquals(new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, arr);
} }
@@ -43,7 +43,7 @@ public class ByteQueueTest extends TestCase {
public void testWriteNotesClosing() throws Exception { public void testWriteNotesClosing() throws Exception {
ByteQueue q = new ByteQueue(10); ByteQueue q = new ByteQueue(10);
q.close(); q.close();
assertFalse(q.write(new byte[]{1, 2, 3}, 0, 3)); assertFalse(q.write(new byte[]{1, 2, 3}, 0, 3));
} }
public void testReadNonBlocking() throws Exception { public void testReadNonBlocking() throws Exception {

View File

@@ -35,31 +35,31 @@ public class ControlSequenceIntroducerTest extends TerminalTestCase {
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", " "); 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). */ /** CSI Ps b Repeat the preceding graphic character Ps times (REP). */
public void testRepeat() { public void testRepeat() {
withTerminalSized(3, 2).enterString("a\033[b").assertLinesAre("aa ", " "); withTerminalSized(3, 2).enterString("a\033[b").assertLinesAre("aa ", " ");
withTerminalSized(3, 2).enterString("a\033[2b").assertLinesAre("aaa", " "); withTerminalSized(3, 2).enterString("a\033[2b").assertLinesAre("aaa", " ");
// When no char has been output we ignore REP: // When no char has been output we ignore REP:
withTerminalSized(3, 2).enterString("\033[b").assertLinesAre(" ", " "); withTerminalSized(3, 2).enterString("\033[b").assertLinesAre(" ", " ");
// This shows that REP outputs the last emitted code point and not the one relative to the // This shows that REP outputs the last emitted code point and not the one relative to the
// current cursor position: // current cursor position:
withTerminalSized(5, 2).enterString("abcde\033[2G\033[2b\n").assertLinesAre("aeede", " "); withTerminalSized(5, 2).enterString("abcde\033[2G\033[2b\n").assertLinesAre("aeede", " ");
} }
/** CSI 3 J Clear scrollback (xterm, libvte; non-standard). */ /** CSI 3 J Clear scrollback (xterm, libvte; non-standard). */
public void testCsi3J() { public void testCsi3J() {
withTerminalSized(3, 2).enterString("a\r\nb\r\nc\r\nd"); withTerminalSized(3, 2).enterString("a\r\nb\r\nc\r\nd");
assertEquals("a\nb\nc\nd", mTerminal.getScreen().getTranscriptText()); assertEquals("a\nb\nc\nd", mTerminal.getScreen().getTranscriptText());
enterString("\033[3J"); enterString("\033[3J");
assertEquals("c\nd", mTerminal.getScreen().getTranscriptText()); assertEquals("c\nd", mTerminal.getScreen().getTranscriptText());
withTerminalSized(3, 2).enterString("Lorem_ipsum"); withTerminalSized(3, 2).enterString("Lorem_ipsum");
assertEquals("Lorem_ipsum", mTerminal.getScreen().getTranscriptText()); assertEquals("Lorem_ipsum", mTerminal.getScreen().getTranscriptText());
enterString("\033[3J"); enterString("\033[3J");
assertEquals("ipsum", mTerminal.getScreen().getTranscriptText()); assertEquals("ipsum", mTerminal.getScreen().getTranscriptText());
withTerminalSized(3, 2).enterString("w\r\nx\r\ny\r\nz\033[?1049h\033[3J\033[?1049l"); withTerminalSized(3, 2).enterString("w\r\nx\r\ny\r\nz\033[?1049h\033[3J\033[?1049l");
assertEquals("y\nz", mTerminal.getScreen().getTranscriptText()); assertEquals("y\nz", mTerminal.getScreen().getTranscriptText());
} }
} }

View File

@@ -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++) {
long 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));
} }
@@ -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);
long 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));
} }
@@ -159,11 +159,11 @@ public class CursorAndScreenTest extends TerminalTestCase {
} }
} }
/** /**
* See comments on horizontal tab handling in TerminalEmulator.java. * See comments on horizontal tab handling in TerminalEmulator.java.
* * <p/>
* We do not want to color already written cells when tabbing over them. * We do not want to color already written cells when tabbing over them.
*/ */
public void DISABLED_testHorizontalTabColorsBackground() { public void DISABLED_testHorizontalTabColorsBackground() {
withTerminalSized(10, 3).enterString("\033[48;5;15m").enterString("\t"); withTerminalSized(10, 3).enterString("\033[48;5;15m").enterString("\t");
assertCursorAt(0, 8); assertCursorAt(0, 8);
@@ -214,14 +214,14 @@ public class CursorAndScreenTest extends TerminalTestCase {
" -"); " -");
} }
public void testBackspaceAcrossWrappedLines() { public void testBackspaceAcrossWrappedLines() {
// Backspace should not go to previous line if not auto-wrapped: // Backspace should not go to previous line if not auto-wrapped:
withTerminalSized(3, 3).enterString("hi\r\n\b\byou").assertLinesAre("hi ", "you", " "); withTerminalSized(3, 3).enterString("hi\r\n\b\byou").assertLinesAre("hi ", "you", " ");
// Backspace should go to previous line if auto-wrapped: // Backspace should go to previous line if auto-wrapped:
withTerminalSized(3, 3).enterString("hi y").assertLinesAre("hi ", "y ", " ").enterString("\b\b#").assertLinesAre("hi#", "y ", " "); withTerminalSized(3, 3).enterString("hi y").assertLinesAre("hi ", "y ", " ").enterString("\b\b#").assertLinesAre("hi#", "y ", " ");
// Initial backspace should do nothing: // Initial backspace should do nothing:
withTerminalSized(3, 3).enterString("\b\b\b\bhi").assertLinesAre("hi ", " ", " "); withTerminalSized(3, 3).enterString("\b\b\b\bhi").assertLinesAre("hi ", " ", " ");
} }
public void testCursorSaveRestoreLocation() { public void testCursorSaveRestoreLocation() {
// DEC save/restore // DEC save/restore

View File

@@ -174,18 +174,18 @@ public class KeyHandlerTest extends TestCase {
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("0", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_0, 0, false, false));
assertKeysEquals("1", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_1, 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("2", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_2, 0, false, false));
assertKeysEquals("3", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_3, 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("4", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_4, 0, false, false));
assertKeysEquals("5", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_5, 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("6", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_6, 0, false, false));
assertKeysEquals("7", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_7, 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("8", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_8, 0, false, false));
assertKeysEquals("9", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_9, 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_COMMA, 0, false, false));
assertKeysEquals(".", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_DOT, 0, false, false)); assertKeysEquals(".", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_DOT, 0, false, false));
} }
} }

View File

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

View File

@@ -107,24 +107,24 @@ public class ScrollRegionTest extends TerminalTestCase {
assertLinesAre("1 ", "2 ", "3 ", "QQ", "YY"); assertLinesAre("1 ", "2 ", "3 ", "QQ", "YY");
} }
/** See https://github.com/termux/termux-app/issues/1340 */ /** See https://github.com/termux/termux-app/issues/1340 */
public void testScrollRegionDoesNotLimitCursorMovement() { public void testScrollRegionDoesNotLimitCursorMovement() {
withTerminalSized(6, 4) withTerminalSized(6, 4)
.enterString("\033[4;7r\033[3;1Haaa\033[Axxx") .enterString("\033[4;7r\033[3;1Haaa\033[Axxx")
.assertLinesAre( .assertLinesAre(
" ", " ",
" xxx", " xxx",
"aaa ", "aaa ",
" " " "
); );
withTerminalSized(6, 4) withTerminalSized(6, 4)
.enterString("\033[1;3r\033[3;1Haaa\033[Bxxx") .enterString("\033[1;3r\033[3;1Haaa\033[Bxxx")
.assertLinesAre( .assertLinesAre(
" ", " ",
" ", " ",
"aaa ", "aaa ",
" xxx" " xxx"
); );
} }
} }

View File

@@ -159,7 +159,7 @@ public class TerminalTest extends TerminalTestCase {
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);
@@ -169,30 +169,30 @@ public class TerminalTest extends TerminalTestCase {
assertEquals(178, mTerminal.mForeColor); assertEquals(178, mTerminal.mForeColor);
assertEquals(179, mTerminal.mBackColor); assertEquals(179, mTerminal.mBackColor);
// 24 bit colors: // 24 bit colors:
enterString(("\033[0m")); // Reset fg and bg colors. enterString(("\033[0m")); // Reset fg and bg colors.
enterString("\033[38;2;255;127;2m"); enterString("\033[38;2;255;127;2m");
int expectedForeground = 0xff000000 | (255 << 16) | (127 << 8) | 2; int expectedForeground = 0xff000000 | (255 << 16) | (127 << 8) | 2;
assertEquals(expectedForeground, mTerminal.mForeColor); assertEquals(expectedForeground, mTerminal.mForeColor);
assertEquals(TextStyle.COLOR_INDEX_BACKGROUND, mTerminal.mBackColor); assertEquals(TextStyle.COLOR_INDEX_BACKGROUND, mTerminal.mBackColor);
enterString("\033[48;2;1;2;254m"); enterString("\033[48;2;1;2;254m");
int expectedBackground = 0xff000000 | (1 << 16) | (2 << 8) | 254; int expectedBackground = 0xff000000 | (1 << 16) | (2 << 8) | 254;
assertEquals(expectedForeground, mTerminal.mForeColor); assertEquals(expectedForeground, mTerminal.mForeColor);
assertEquals(expectedBackground, mTerminal.mBackColor); assertEquals(expectedBackground, mTerminal.mBackColor);
// 24 bit colors, set fg and bg at once: // 24 bit colors, set fg and bg at once:
enterString(("\033[0m")); // Reset fg and bg colors. enterString(("\033[0m")); // Reset fg and bg colors.
assertEquals(TextStyle.COLOR_INDEX_FOREGROUND, mTerminal.mForeColor); assertEquals(TextStyle.COLOR_INDEX_FOREGROUND, mTerminal.mForeColor);
assertEquals(TextStyle.COLOR_INDEX_BACKGROUND, mTerminal.mBackColor); assertEquals(TextStyle.COLOR_INDEX_BACKGROUND, mTerminal.mBackColor);
enterString("\033[38;2;255;127;2;48;2;1;2;254m"); enterString("\033[38;2;255;127;2;48;2;1;2;254m");
assertEquals(expectedForeground, mTerminal.mForeColor); assertEquals(expectedForeground, mTerminal.mForeColor);
assertEquals(expectedBackground, mTerminal.mBackColor); assertEquals(expectedBackground, mTerminal.mBackColor);
// 24 bit colors, invalid input: // 24 bit colors, invalid input:
enterString("\033[38;2;300;127;2;48;2;1;300;254m"); enterString("\033[38;2;300;127;2;48;2;1;300;254m");
assertEquals(expectedForeground, mTerminal.mForeColor); assertEquals(expectedForeground, mTerminal.mForeColor);
assertEquals(expectedBackground, mTerminal.mBackColor); assertEquals(expectedBackground, mTerminal.mBackColor);
} }
public void testBackgroundColorErase() { public void testBackgroundColorErase() {
final int rows = 3; final int rows = 3;
@@ -200,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++) {
long 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));
} }
@@ -213,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++) {
long 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));
} }
@@ -224,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++) {
long 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));
} }
} }
@@ -289,9 +289,9 @@ public class TerminalTest extends TerminalTestCase {
withTerminalSized(3, 3).enterString("abc\r ").assertLinesAre(" bc", " ", " ").assertCursorAt(0, 1); withTerminalSized(3, 3).enterString("abc\r ").assertLinesAre(" bc", " ", " ").assertCursorAt(0, 1);
} }
public void testTab() { public void testTab() {
withTerminalSized(11, 2).enterString("01234567890\r\tXX").assertLinesAre("01234567XX0", " "); withTerminalSized(11, 2).enterString("01234567890\r\tXX").assertLinesAre("01234567XX0", " ");
withTerminalSized(11, 2).enterString("01234567890\033[44m\r\tXX").assertLinesAre("01234567XX0", " "); withTerminalSized(11, 2).enterString("01234567890\033[44m\r\tXX").assertLinesAre("01234567XX0", " ");
} }
} }

View File

@@ -18,7 +18,7 @@ public abstract class TerminalTestCase extends TestCase {
public final List<ChangedTitle> titleChanges = new ArrayList<>(); public final List<ChangedTitle> titleChanges = new ArrayList<>();
public final List<String> clipboardPuts = new ArrayList<>(); public final List<String> clipboardPuts = new ArrayList<>();
public int bellsRung = 0; public int bellsRung = 0;
public int colorsChanged = 0; public int colorsChanged = 0;
@Override @Override
public void write(byte[] data, int offset, int count) { public void write(byte[] data, int offset, int count) {
@@ -26,10 +26,10 @@ public abstract class TerminalTestCase extends TestCase {
} }
public String getOutputAndClear() { public String getOutputAndClear() {
String result = new String(baos.toByteArray(), StandardCharsets.UTF_8); String result = new String(baos.toByteArray(), StandardCharsets.UTF_8);
baos.reset(); baos.reset();
return result; return result;
} }
@Override @Override
public void titleChanged(String oldTitle, String newTitle) { public void titleChanged(String oldTitle, String newTitle) {
@@ -46,11 +46,11 @@ public abstract class TerminalTestCase extends TestCase {
bellsRung++; bellsRung++;
} }
@Override @Override
public void onColorsChanged() { public void onColorsChanged() {
colorsChanged++; colorsChanged++;
} }
} }
public TerminalEmulator mTerminal; public TerminalEmulator mTerminal;
public MockTerminalOutput mOutput; public MockTerminalOutput mOutput;

View File

@@ -22,23 +22,23 @@ public class TextStyleTest extends TestCase {
} }
} }
public void testEncoding24Bit() { public void testEncoding24Bit() {
int[] values = {255, 240, 127, 1, 0}; int[] values = {255, 240, 127, 1, 0};
for (int red : values) { for (int red : values) {
for (int green : values) { for (int green : values) {
for (int blue : values) { for (int blue : values) {
int argb = 0xFF000000 | (red << 16) | (green << 8) | blue; int argb = 0xFF000000 | (red << 16) | (green << 8) | blue;
long encoded = TextStyle.encode(argb, 0, 0); long encoded = TextStyle.encode(argb, 0, 0);
assertEquals(argb, TextStyle.decodeForeColor(encoded)); assertEquals(argb, TextStyle.decodeForeColor(encoded));
encoded = TextStyle.encode(0, argb, 0); encoded = TextStyle.encode(0, argb, 0);
assertEquals(argb, TextStyle.decodeBackColor(encoded)); assertEquals(argb, TextStyle.decodeBackColor(encoded));
} }
} }
} }
} }
public void testEncodingCombinations() { public void testEncodingCombinations() {
for (int f1 : ALL_EFFECTS) { for (int f1 : ALL_EFFECTS) {
for (int f2 : ALL_EFFECTS) { for (int f2 : ALL_EFFECTS) {
int combined = f1 | f2; int combined = f1 | f2;
@@ -54,9 +54,9 @@ public class TextStyleTest extends TestCase {
} }
public void testEncodingProtected() { public void testEncodingProtected() {
long encoded = TextStyle.encode(TextStyle.COLOR_INDEX_FOREGROUND, TextStyle.COLOR_INDEX_BACKGROUND, long encoded = TextStyle.encode(TextStyle.COLOR_INDEX_FOREGROUND, TextStyle.COLOR_INDEX_BACKGROUND,
TextStyle.CHARACTER_ATTRIBUTE_STRIKETHROUGH); TextStyle.CHARACTER_ATTRIBUTE_STRIKETHROUGH);
assertEquals(0, (TextStyle.decodeEffect(encoded) & TextStyle.CHARACTER_ATTRIBUTE_PROTECTED)); assertEquals(0, (TextStyle.decodeEffect(encoded) & TextStyle.CHARACTER_ATTRIBUTE_PROTECTED));
encoded = TextStyle.encode(TextStyle.COLOR_INDEX_FOREGROUND, TextStyle.COLOR_INDEX_BACKGROUND, encoded = TextStyle.encode(TextStyle.COLOR_INDEX_FOREGROUND, TextStyle.COLOR_INDEX_BACKGROUND,
TextStyle.CHARACTER_ATTRIBUTE_STRIKETHROUGH | TextStyle.CHARACTER_ATTRIBUTE_PROTECTED); TextStyle.CHARACTER_ATTRIBUTE_STRIKETHROUGH | TextStyle.CHARACTER_ATTRIBUTE_PROTECTED);
assertTrue((TextStyle.decodeEffect(encoded) & TextStyle.CHARACTER_ATTRIBUTE_PROTECTED) != 0); assertTrue((TextStyle.decodeEffect(encoded) & TextStyle.CHARACTER_ATTRIBUTE_PROTECTED) != 0);

View File

@@ -15,12 +15,12 @@ public class WcWidthTest extends TestCase {
} }
} }
public void testSomeWidthOne() { public void testSomeWidthOne() {
assertWidthIs(1, 'å'); assertWidthIs(1, 'å');
assertWidthIs(1, 'ä'); assertWidthIs(1, 'ä');
assertWidthIs(1, 'ö'); assertWidthIs(1, 'ö');
assertWidthIs(1, 0x23F2); assertWidthIs(1, 0x23F2);
} }
public void testSomeWide() { public void testSomeWide() {
assertWidthIs(2, ''); assertWidthIs(2, '');
@@ -44,18 +44,18 @@ 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); assertWidthIs(0, 0xFE0F);
} }
public void testWordJoiner() { public void testWordJoiner() {
// https://en.wikipedia.org/wiki/Word_joiner // https://en.wikipedia.org/wiki/Word_joiner
// The word joiner (WJ) is a code point in Unicode used to separate words when using scripts // The word joiner (WJ) is a code point in Unicode used to separate words when using scripts
// that do not use explicit spacing. It is encoded since Unicode version 3.2 // that do not use explicit spacing. It is encoded since Unicode version 3.2
// (released in 2002) as U+2060 WORD JOINER (HTML &#8288;). // (released in 2002) as U+2060 WORD JOINER (HTML &#8288;).
// The word joiner does not produce any space, and prohibits a line break at its position. // The word joiner does not produce any space, and prohibits a line break at its position.
assertWidthIs(0, 0x2060); assertWidthIs(0, 0x2060);
} }
public void testSofthyphen() { public void testSofthyphen() {
@@ -72,10 +72,10 @@ public class WcWidthTest extends TestCase {
assertWidthIs(1, 0x11A3); assertWidthIs(1, 0x11A3);
} }
public void testEmojis() { public void testEmojis() {
assertWidthIs(2, 0x1F428); // KOALA. assertWidthIs(2, 0x1F428); // KOALA.
assertWidthIs(2, 0x231a); // WATCH. assertWidthIs(2, 0x231a); // WATCH.
assertWidthIs(2, 0x1F643); // UPSIDE-DOWN FACE (Unicode 8). assertWidthIs(2, 0x1F643); // UPSIDE-DOWN FACE (Unicode 8).
} }
} }

View File

@@ -1,6 +1,6 @@
plugins { plugins {
id "com.jfrog.bintray" version "1.7.3" id "com.jfrog.bintray" version "1.8.5"
id "com.github.dcendents.android-maven" version "2.0" id "com.github.dcendents.android-maven" version "2.1"
} }
apply plugin: 'com.android.library' apply plugin: 'com.android.library'
@@ -11,8 +11,8 @@ ext {
libraryName = 'TerminalView' libraryName = 'TerminalView'
artifact = 'terminal-view' artifact = 'terminal-view'
libraryDescription = 'The terminal view used in Termux' libraryDescription = 'The terminal view used in Termux'
siteUrl = 'https://github.com/termux/termux' siteUrl = 'https://github.com/termux/termux-app'
gitUrl = 'https://github.com/termux/termux.git' gitUrl = 'https://github.com/termux/termux-app.git'
libraryVersion = '0.50' libraryVersion = '0.50'
} }
@@ -44,7 +44,7 @@ android {
} }
dependencies { dependencies {
testImplementation 'junit:junit:4.13' testImplementation 'junit:junit:4.13.1'
} }
apply from: '../scripts/bintray-publish.gradle' apply from: '../scripts/bintray-publish.gradle'

View File

@@ -31,12 +31,15 @@ import android.view.ViewParent;
import android.view.ViewTreeObserver; import android.view.ViewTreeObserver;
import android.view.WindowManager; import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityManager;
import android.view.autofill.AutofillValue;
import android.view.inputmethod.BaseInputConnection; import android.view.inputmethod.BaseInputConnection;
import android.view.inputmethod.EditorInfo; import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputConnection;
import android.widget.PopupWindow; import android.widget.PopupWindow;
import android.widget.Scroller; import android.widget.Scroller;
import androidx.annotation.RequiresApi;
import com.termux.terminal.EmulatorDebug; import com.termux.terminal.EmulatorDebug;
import com.termux.terminal.KeyHandler; import com.termux.terminal.KeyHandler;
import com.termux.terminal.TerminalBuffer; import com.termux.terminal.TerminalBuffer;
@@ -44,6 +47,12 @@ import com.termux.terminal.TerminalEmulator;
import com.termux.terminal.TerminalSession; import com.termux.terminal.TerminalSession;
import com.termux.terminal.WcWidth; import com.termux.terminal.WcWidth;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Properties;
/** View displaying and interacting with a {@link TerminalSession}. */ /** View displaying and interacting with a {@link TerminalSession}. */
public final class TerminalView extends View { public final class TerminalView extends View {
@@ -246,14 +255,23 @@ public final class TerminalView extends View {
@Override @Override
public InputConnection onCreateInputConnection(EditorInfo outAttrs) { public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
// Using InputType.NULL is the most correct input type and avoids issues with other hacks. Properties props = getProperties();
//
// Previous keyboard issues: if (props.getProperty("enforce-char-based-input", "false").equals("true")) {
// https://github.com/termux/termux-packages/issues/25 // Some keyboards seems do not reset the internal state on TYPE_NULL.
// https://github.com/termux/termux-app/issues/87. // Affects mostly Samsung stock keyboards.
// https://github.com/termux/termux-app/issues/126. // https://github.com/termux/termux-app/issues/686
// https://github.com/termux/termux-app/issues/137 (japanese chars and TYPE_NULL). outAttrs.inputType = InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS;
outAttrs.inputType = InputType.TYPE_NULL; } else {
// Using InputType.NULL is the most correct input type and avoids issues with other hacks.
//
// Previous keyboard issues:
// https://github.com/termux/termux-packages/issues/25
// https://github.com/termux/termux-app/issues/87.
// https://github.com/termux/termux-app/issues/126.
// https://github.com/termux/termux-app/issues/137 (japanese chars and TYPE_NULL).
outAttrs.inputType = InputType.TYPE_NULL;
}
// Note that IME_ACTION_NONE cannot be used as that makes it impossible to input newlines using the on-screen // Note that IME_ACTION_NONE cannot be used as that makes it impossible to input newlines using the on-screen
// keyboard on Android TV (see https://github.com/termux/termux-app/issues/221). // keyboard on Android TV (see https://github.com/termux/termux-app/issues/221).
@@ -521,6 +539,8 @@ public final class TerminalView extends View {
@Override @Override
public boolean onKeyPreIme(int keyCode, KeyEvent event) { public boolean onKeyPreIme(int keyCode, KeyEvent event) {
Properties props = getProperties();
if (LOG_KEY_EVENTS) if (LOG_KEY_EVENTS)
Log.i(EmulatorDebug.LOG_TAG, "onKeyPreIme(keyCode=" + keyCode + ", event=" + event + ")"); Log.i(EmulatorDebug.LOG_TAG, "onKeyPreIme(keyCode=" + keyCode + ", event=" + event + ")");
if (keyCode == KeyEvent.KEYCODE_BACK) { if (keyCode == KeyEvent.KEYCODE_BACK) {
@@ -536,6 +556,11 @@ public final class TerminalView extends View {
return onKeyUp(keyCode, event); return onKeyUp(keyCode, event);
} }
} }
} else if (props.getProperty("ctrl-space-workaround", "false").equals("true") &&
keyCode == KeyEvent.KEYCODE_SPACE && event.isCtrlPressed()) {
/* ctrl + space does not work on some ROMs without this workaround.
However, this breaks it on devices where it works out of the box. */
return onKeyDown(keyCode, event);
} }
return super.onKeyPreIme(keyCode, event); return super.onKeyPreIme(keyCode, event);
} }
@@ -1166,7 +1191,7 @@ public final class TerminalView extends View {
final ActionMode.Callback callback = new ActionMode.Callback() { final ActionMode.Callback callback = new ActionMode.Callback() {
@Override @Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) { public boolean onCreateActionMode(ActionMode mode, Menu menu) {
int show = MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT; int show = MenuItem.SHOW_AS_ACTION_IF_ROOM | MenuItem.SHOW_AS_ACTION_WITH_TEXT;
ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE); ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
menu.add(Menu.NONE, 1, Menu.NONE, R.string.copy_text).setShowAsAction(show); menu.add(Menu.NONE, 1, Menu.NONE, R.string.copy_text).setShowAsAction(show);
@@ -1512,4 +1537,52 @@ public final class TerminalView extends View {
} }
} }
} }
private Properties getProperties() {
File propsFile;
Properties props = new Properties();
String possiblePropLocations[] = {
getContext().getFilesDir() + "/home/.termux/termux.properties",
getContext().getFilesDir() + "/home/.config/termux/termux.properties"
};
propsFile = new File(possiblePropLocations[0]);
int i = 0;
while (!propsFile.exists() && i < possiblePropLocations.length) {
propsFile = new File(possiblePropLocations[i]);
i += 1;
}
try {
if (propsFile.isFile() && propsFile.canRead()) {
try (FileInputStream in = new FileInputStream(propsFile)) {
props.load(new InputStreamReader(in, StandardCharsets.UTF_8));
}
}
} catch (Exception e) {
Log.e("termux", "Error loading props", e);
}
return props;
}
@RequiresApi(api = Build.VERSION_CODES.O)
@Override
public void autofill(AutofillValue value) {
if (value.isText()) {
mTermSession.write(value.getTextValue().toString());
}
}
@RequiresApi(api = Build.VERSION_CODES.O)
@Override
public int getAutofillType() {
return AUTOFILL_TYPE_TEXT;
}
@RequiresApi(api = Build.VERSION_CODES.O)
@Override
public AutofillValue getAutofillValue() {
return AutofillValue.forText("");
}
} }