Compare commits

..

84 Commits
v0.48 ... v0.56

Author SHA1 Message Date
Fredrik Fornwall
02af113dda Bump app version to 0.56 2017-11-19 20:54:16 +01:00
Fredrik Fornwall
fc4ef838bf Configure proguard to keep line numbers 2017-11-19 20:53:56 +01:00
Fredrik Fornwall
86bd3ca21b Bump app version to 0.54 2017-11-19 15:27:17 +01:00
Fredrik Fornwall
367398bafb Update gradle configuration 2017-11-19 15:27:00 +01:00
Lauri Tirkkonen
dd502e55f8 set default notification priority to low
since we're a foreground service, in oreo we already get a higher
priority; see
https://developer.android.com/reference/android/app/NotificationManager.html#IMPORTANCE_MIN
2017-11-12 23:44:14 +01:00
Fredrik Fornwall
798125ef7a Remove outdated comment 2017-11-12 23:06:28 +01:00
Fredrik Fornwall
5126f06e06 Update .travis.yml to latest setup 2017-11-12 22:30:09 +01:00
Fredrik Fornwall
0bdbf314ef Update gradle configuration 2017-11-07 04:25:48 +01:00
Fredrik Fornwall
2deb9899bd Update buildToolsVersion to 27.0.1 2017-11-07 04:13:26 +01:00
Fredrik Fornwall
694ccc38c4 Remove WakefulBroadcastReceiver usage 2017-11-04 23:38:49 +01:00
Fredrik Fornwall
c949940374 Update support deps 2017-11-02 01:56:42 +01:00
Fredrik Fornwall
d09f70de1e Use a notification channel on Android O 2017-11-01 21:15:41 +01:00
Fredrik Fornwall
ac338ce2c5 Update gradle to 4.3 2017-10-31 19:07:00 +01:00
Fredrik Fornwall
092a83a688 Validate file path in TermuxOpenReceiver 2017-10-28 16:19:58 +02:00
Fredrik Fornwall
3533b13de8 Get rid of two android studio warnings 2017-10-28 11:05:43 +02:00
Fredrik Fornwall
d68a0f05be Update gradle configuration 2017-10-28 11:05:43 +02:00
Fredrik Fornwall
9e5293a08e Do not scroll when below bottom margin 2017-10-28 11:05:43 +02:00
Fredrik Fornwall
3f04a0a0d5 Bump build tools version in .travis.yml 2017-10-28 11:05:43 +02:00
Fredrik Fornwall
e558f824c5 Update gradle configuration 2017-10-28 11:05:43 +02:00
Auxilus
8ff72ddd8b replace help url with wiki url
HELP page wiki changed to https://wiki.termux.com/wiki/Main_Page as it now has more info than http://termux.com/help.html
2017-09-26 18:14:19 +02:00
Fredrik Fornwall
3e05356a69 Merge pull request #421 from sdrausty/master
Added two important links to README.md
2017-09-20 22:37:49 +02:00
Fredrik Fornwall
82673d3f82 Update gradle configuration 2017-09-17 11:47:12 +02:00
Fredrik Fornwall
f2757fdb76 Merge pull request #424 from EdwardBetts/patch-1
correct spelling mistake
2017-09-15 00:21:47 +02:00
Edward Betts
81fb669d0e correct spelling mistake 2017-09-13 16:16:08 +01:00
S D Rausty
4a45b1b617 Commit on 20170911 at 13:23:22 and at 1505150602 in Unix time. 2017-09-11 13:23:22 -04:00
S D Rausty
62b08b3cf1 Merge pull request #1 from termux/master
Hello
2017-09-10 13:24:32 -04:00
Fredrik Fornwall
1e83ea3151 Merge pull request #412 from Auxilus/patch-2
Added wiki link
2017-09-06 16:26:54 +02:00
Auxilus
9c42fdb3d6 Added wiki link 2017-09-05 10:43:30 +05:30
Fredrik Fornwall
42e3163e92 Update gradle to 4.1 2017-08-24 20:49:43 +02:00
Fredrik Fornwall
68912139f6 Additional REP test 2017-08-23 23:29:59 +02:00
Fredrik Fornwall
edc92bbcbb Start supporting the REP escape sequence 2017-08-13 16:14:31 +02:00
Fredrik Fornwall
d7520642de Update gradle 2017-07-29 03:05:48 +02:00
Fredrik Fornwall
adae111d5c Update gradle config 2017-07-09 21:07:18 +02:00
Fredrik Fornwall
dc9272790b Merge pull request #301 from kzlin129/android-things-auto-launch
[Android-things] Launch TermuxActivity automatically on boot
2017-06-26 02:04:03 +02:00
Fredrik Fornwall
05ea2ea238 Bump version to 0.53 2017-06-26 01:30:08 +02:00
Fredrik Fornwall
ad293562bc Fix possible crash in the copy/paste/more dialog 2017-06-26 01:29:05 +02:00
Fredrik Fornwall
f9a565d1e0 Ignore C1 control codes
C1 control codes are not used nowadays and just risks messing up
the terminal when they are used by accident.
2017-06-26 01:28:18 +02:00
Will Lin
5b3909cb73 Update AndroidManifest.xml with expanded comment 2017-06-17 15:39:23 +08:00
Fredrik Fornwall
7913e765d5 Bump version to 0.52 2017-06-13 16:44:33 +02:00
Fredrik Fornwall
ed3a3269d8 Merge pull request #339 from bumper314/master
Add Multi Window support for Samsung devices
2017-06-13 10:13:26 +02:00
Steven Audette
dc3994d2cf Add Multi Window support for Samsung devices
A single meta-data entry is enough to make Termux "Multi Window" aware on supported Samsung devices.
Tested and confirmed working on Samsung Note Pro 12.2 running Android 5.1.1.

This addition should not interfer with Android 6+'s split screen feature.
Tested and confirmed split screen functions as normal on a Nexus 6 running Android 7.0.

This addition should go unnoticed on non-Smasung Android 5 devices.
Tested and confirmed no functionality visible on Nexus 5 running Android 5.1.
2017-06-12 01:47:27 -06:00
Fredrik Fornwall
c972377bd1 Update gradle config 2017-06-11 22:47:59 +02:00
Fredrik Fornwall
d4be782c03 Bump version to 0.51 2017-06-05 22:49:11 +02:00
Fredrik Fornwall
c29909726c Bump terminal-emulator libraryVersion to 0.50 2017-06-05 22:45:44 +02:00
Fredrik Fornwall
45bac89298 Remove unused xmlns:android declaration 2017-06-05 22:45:15 +02:00
Fredrik Fornwall
c06770c353 Merge pull request #330 from whydoubt/cursor_preserve_color
Make cursor save/restore affect color attributes
2017-06-05 22:35:39 +02:00
Fredrik Fornwall
52a627efc8 Merge pull request #331 from whydoubt/fix_parameter_fc
Fix FC from too many control code parameters
2017-06-05 22:35:32 +02:00
Jeff Smith
2e9383720c Make cursor save/restore affect color attributes
SGR attributes are stored in three variables: mEffect, mForeColor, and
mBackColor.  Saving/restoring the cursor only preserves mEffect.

Change the cursor save/restore methods to additionally preserve
mForeColor and mBackColor.  This affects both 'explit' saving/restoring
the cursor and switching to/from the alternate screen buffer.
2017-06-03 17:45:36 -05:00
Jeff Smith
58f9f1be71 Add tests for cursor save/restore 2017-06-03 17:45:36 -05:00
Jeff Smith
058441dda6 Fix FC from too many control code parameters
When the number of parameters in a CSI control code exceeds the size of
the mArgs array, the code may attempt to read past the end of the array,
resulting in a force close of the app.

Stop the index from moving past the last element of the mArgs array.
2017-06-03 00:15:09 -05:00
Jeff Smith
888802a519 Add test for CSI followed by many parameters 2017-06-03 00:14:20 -05:00
Fredrik Fornwall
1a09b6d2a6 Change android:extractNativeLibs to false
We're trying this to see if that fixes an F-Droid build issue.

See #319.
2017-05-18 13:25:41 +02:00
Fredrik Fornwall
9d3f7734a1 Update android build tools 2017-05-16 11:11:57 +02:00
Fredrik Fornwall
61f766b59f Bump build tools in .travis.yml 2017-05-16 00:21:15 +02:00
Fredrik Fornwall
3f08376881 Bump version to 0.49 2017-05-15 23:42:55 +02:00
Fredrik Fornwall
cf06e70429 Update gradle configuration 2017-05-15 23:42:30 +02:00
Fredrik Fornwall
0714e435cb Do not show a Toast on clipboard text
Fixes https://github.com/termux/termux-app/issues/149.
2017-05-15 23:42:13 +02:00
Fredrik Fornwall
c26315185f Export TMPDIR to $PREFIX/tmp
Fixes https://github.com/termux/termux-packages/issues/1010.
Fixes https://github.com/termux/termux-app/issues/306.
2017-05-15 23:40:55 +02:00
Will Lin
1be5139253 [Android-things] Launch TermuxActivity automatically on boot 2017-04-18 20:13:47 +08:00
Fredrik Fornwall
adc43c40c5 Perform haptic feedback on extra keys
Fixes #269.
2017-04-16 12:18:13 +02:00
Fredrik Fornwall
779b1ca1f8 Update the Android gradle plugin to 2.3.1 2017-04-05 09:10:54 +02:00
Fredrik Fornwall
e375f99b0c Remove float/ module from .idea/gradle.xml 2017-04-05 09:09:48 +02:00
Fredrik Fornwall
0ac55cd77a Remove float/
The Termux:Float app has a new home at:

https://github.com/termux/termux-float
2017-04-04 23:39:05 +02:00
Fredrik Fornwall
4aefa5049b Add gradle setup to publish to jcenter 2017-04-04 23:31:00 +02:00
Fredrik Fornwall
5b14124258 Make GestureAndScaleRecognizer non-public 2017-04-04 20:56:19 +02:00
Fredrik Fornwall
1a9c38374c Fix some invalid html in javadoc 2017-04-02 15:11:56 +02:00
Fredrik Fornwall
eefd504f08 Use getLocationOnScreen() to calculate position
This avoids problem when the window has been prevented from scrolling
outside of the screen, or when the touch keyboard has made it move.
2017-04-02 14:46:47 +02:00
Fredrik Fornwall
19f838e3d1 Show window if launched when hidden 2017-04-02 14:46:00 +02:00
Fredrik Fornwall
63adb2b132 Rename TerminalKeyListener to TerminalViewClient 2017-04-02 14:25:34 +02:00
Fredrik Fornwall
7d3a988d2f Restructure library modules
terminal/ -> terminal-emulator/
view/ -> terminal-view/
2017-04-02 14:21:36 +02:00
Fredrik Fornwall
5cd705d6d1 Reformat all code in float/, getting rid of tabs 2017-04-02 14:10:26 +02:00
Fredrik Fornwall
af8b269b51 Work on Termux:Float input handling 2017-04-02 14:09:43 +02:00
Fredrik Fornwall
8c220eaaea Do not call long press listener when scaling 2017-04-02 14:07:19 +02:00
Fredrik Fornwall
0142e1ed0e Update Termux:Float launcher icon 2017-04-02 07:33:06 +02:00
Fredrik Fornwall
ac0a349ed9 Remove versionCode&versionName from libraries 2017-04-02 07:12:03 +02:00
Fredrik Fornwall
548b0916b6 Clean up {terminal,view}/build.gradle 2017-04-02 06:58:59 +02:00
Fredrik Fornwall
009de5a3ee Split up into modules and add float module
Split the app/ module into three modules

terminal/ - Terminal emulator library module.
view/ - Terminal view library module (depending on terminal/).
app/ - The main Termux app (depending on view/).

Also add the

float/ - The Termux:Float app (depending on view/).
2017-04-01 19:06:02 +02:00
Fredrik Fornwall
41d0d60017 Respect content type termux-open for url:s 2017-03-28 23:52:52 +02:00
Fredrik Fornwall
12ac0fa73c Add android:extractNativeLibs="false" to manifest 2017-03-12 20:55:58 +01:00
Fredrik Fornwall
835dfc0276 Avoid synthetic accessor method 2017-03-06 01:53:09 +01:00
Fredrik Fornwall
f60316835f Fix some android studio lint warnings 2017-03-06 01:47:08 +01:00
Fredrik Fornwall
f74a091be6 Remove useless continue statement 2017-03-06 01:44:16 +01:00
Fredrik Fornwall
dd6cb5221d Work around Android < 7.0 wifi manager leak
http://tools.android.com/tech-docs/lint-in-studio-2-3#TOC-WifiManager-Leak
2017-03-06 01:42:33 +01:00
Fredrik Fornwall
cab6df5c0e Update gradle config 2017-03-06 01:41:12 +01:00
73 changed files with 534 additions and 169 deletions

2
.idea/gradle.xml generated
View File

@@ -10,6 +10,8 @@
<set> <set>
<option value="$PROJECT_DIR$" /> <option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" /> <option value="$PROJECT_DIR$/app" />
<option value="$PROJECT_DIR$/terminal-emulator" />
<option value="$PROJECT_DIR$/terminal-view" />
</set> </set>
</option> </option>
<option name="resolveModulePerSourceSet" value="false" /> <option name="resolveModulePerSourceSet" value="false" />

View File

@@ -12,8 +12,8 @@ android:
components: components:
- platform-tools - platform-tools
- tools - tools
- build-tools-25.0.2 - build-tools-27.0.1
- android-25 - android-27
- extra-android-m2repository - extra-android-m2repository
before_install: before_install:

3
LICENSE.md Normal file
View File

@@ -0,0 +1,3 @@
Released under [the GPLv3 license](https://www.gnu.org/licenses/gpl.html).
Contains code from `Terminal Emulator for Android` by which is released under [the Apache License 2.0](https://www.apache.org/licenses/).

View File

@@ -7,15 +7,14 @@ Termux app
* [Termux on Google Play Store](https://play.google.com/store/apps/details?id=com.termux) * [Termux on Google Play Store](https://play.google.com/store/apps/details?id=com.termux)
* [Termux on F-Droid](https://f-droid.org/repository/browse/?fdid=com.termux) * [Termux on F-Droid](https://f-droid.org/repository/browse/?fdid=com.termux)
* [Termux Help](http://termux.com/help/) * [Termux Facebook](https://facebook.com/termux/)
* [Termux Google+ community](http://termux.com/community/) * [Termux Google+ community](http://termux.com/community/)
* [Termux Help](http://termux.com/help/)
* [Termux Twitter](http://twitter.com/termux/)
* [Termux Wiki](https://wiki.termux.com/wiki/)
Note that this repository is for the app itself (the user interface and the terminal emulation). For the packages installable inside the app, see [termux/termux-packages](https://github.com/termux/termux-packages) Note that this repository is for the app itself (the user interface and the terminal emulation). For the packages installable inside the app, see [termux/termux-packages](https://github.com/termux/termux-packages)
License
=======
Released under [the GPLv3 license](https://www.gnu.org/licenses/gpl.html). Contains code from `Terminal Emulator for Android` which is released under [the Apache License 2.0](https://www.apache.org/licenses/).
Terminal resources Terminal resources
================== ==================
* [XTerm control sequences](http://invisible-island.net/xterm/ctlseqs/ctlseqs.html) * [XTerm control sequences](http://invisible-island.net/xterm/ctlseqs/ctlseqs.html)

View File

@@ -1,30 +1,21 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
android { android {
compileSdkVersion 25 compileSdkVersion 27
buildToolsVersion "25.0.2" buildToolsVersion "27.0.1"
dependencies { dependencies {
compile 'com.android.support:support-annotations:25.1.0' implementation 'com.android.support:support-annotations:27.0.0'
compile "com.android.support:support-v4:25.1.0" implementation "com.android.support:support-core-ui:27.0.0"
implementation project(":terminal-view")
} }
defaultConfig { defaultConfig {
applicationId "com.termux" applicationId "com.termux"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 25 targetSdkVersion 27
versionCode 48 versionCode 56
versionName "0.48" versionName "0.56"
externalNativeBuild {
ndkBuild {
cFlags "-std=c11", "-Wall", "-Wextra", "-Werror", "-Os", "-fno-stack-protector", "-Wl,--gc-sections"
}
}
ndk {
abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'
}
} }
buildTypes { buildTypes {
@@ -34,14 +25,8 @@ android {
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
} }
} }
externalNativeBuild {
ndkBuild {
path "src/main/jni/Android.mk"
}
}
} }
dependencies { dependencies {
testCompile 'junit:junit:4.12' testImplementation 'junit:junit:4.12'
} }

View File

@@ -7,11 +7,5 @@
# For more details, see # For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html # http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here: -renamesourcefileattribute SourceFile
-keepattributes SourceFile,LineNumberTable
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

View File

@@ -1,13 +0,0 @@
package com.termux;
import android.app.Application;
import android.test.ApplicationTestCase;
/**
* <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
*/
public class ApplicationTest extends ApplicationTestCase<Application> {
public ApplicationTest() {
super(Application.class);
}
}

View File

@@ -13,6 +13,7 @@
<uses-permission android:name="android.permission.VIBRATE" /> <uses-permission android:name="android.permission.VIBRATE" />
<application <application
android:extractNativeLibs="true"
android:allowBackup="true" android:allowBackup="true"
android:fullBackupContent="@xml/backupscheme" android:fullBackupContent="@xml/backupscheme"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
@@ -75,6 +76,18 @@
</intent-filter> </intent-filter>
</activity> </activity>
<activity-alias
android:name=".HomeActivity"
android:targetActivity="com.termux.app.TermuxActivity">
<!-- Launch activity automatically on boot on Android Things devices -->
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.IOT_LAUNCHER"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity-alias>
<provider <provider
android:name=".filepicker.TermuxDocumentsProvider" android:name=".filepicker.TermuxDocumentsProvider"
android:authorities="com.termux.documents" android:authorities="com.termux.documents"
@@ -98,6 +111,7 @@
android:grantUriPermissions="true" android:grantUriPermissions="true"
android:name="com.termux.app.TermuxOpenReceiver$ContentProvider" /> android:name="com.termux.app.TermuxOpenReceiver$ContentProvider" />
<meta-data android:name="com.sec.android.support.multiwindow" android:value="true" />
</application> </application>
</manifest> </manifest>

View File

@@ -116,8 +116,9 @@ public final class BackgroundJob {
final String langEnv = "LANG=en_US.UTF-8"; final String langEnv = "LANG=en_US.UTF-8";
final String pathEnv = "PATH=" + TermuxService.PREFIX_PATH + "/bin:" + TermuxService.PREFIX_PATH + "/bin/applets"; final String pathEnv = "PATH=" + TermuxService.PREFIX_PATH + "/bin:" + TermuxService.PREFIX_PATH + "/bin/applets";
final String pwdEnv = "PWD=" + cwd; final String pwdEnv = "PWD=" + cwd;
final String tmpdirEnv = "TMPDIR=" + TermuxService.PREFIX_PATH + "/tmp";
return new String[]{termEnv, homeEnv, prefixEnv, ps1Env, ldEnv, langEnv, pathEnv, pwdEnv, androidRootEnv, androidDataEnv, externalStorageEnv}; return new String[]{termEnv, homeEnv, prefixEnv, ps1Env, ldEnv, langEnv, pathEnv, pwdEnv, androidRootEnv, androidDataEnv, externalStorageEnv, tmpdirEnv};
} }
} }
@@ -158,7 +159,6 @@ public final class BackgroundJob {
if (c == ' ' || c == '\n') { if (c == ' ' || c == '\n') {
if (builder.length() == 0) { if (builder.length() == 0) {
// Skip whitespace after shebang. // Skip whitespace after shebang.
continue;
} else { } else {
// End of shebang. // End of shebang.
String executable = builder.toString(); String executable = builder.toString();

View File

@@ -3,6 +3,7 @@ package com.termux.app;
import android.content.Context; import android.content.Context;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.Gravity; import android.view.Gravity;
import android.view.HapticFeedbackConstants;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.View; import android.view.View;
import android.widget.Button; import android.widget.Button;
@@ -144,6 +145,7 @@ public final class ExtraKeysView extends GridLayout {
button.setOnClickListener(new OnClickListener() { button.setOnClickListener(new OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
finalButton.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP);
View root = getRootView(); View root = getRootView();
switch (buttonText) { switch (buttonText) {
case "CTRL": case "CTRL":

View File

@@ -49,8 +49,6 @@ import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener; import android.view.View.OnLongClickListener;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.WindowManager; import android.view.WindowManager;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView; import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener; import android.widget.AdapterView.OnItemClickListener;
@@ -159,7 +157,6 @@ public final class TermuxActivity extends Activity implements ServiceConnection
void checkForFontAndColors() { void checkForFontAndColors() {
try { try {
// Hard-coded paths since this file is used also in Termux:Float.
@SuppressLint("SdCardPath") File fontFile = new File("/data/data/com.termux/files/home/.termux/font.ttf"); @SuppressLint("SdCardPath") File fontFile = new File("/data/data/com.termux/files/home/.termux/font.ttf");
@SuppressLint("SdCardPath") File colorsFile = new File("/data/data/com.termux/files/home/.termux/colors.properties"); @SuppressLint("SdCardPath") File colorsFile = new File("/data/data/com.termux/files/home/.termux/colors.properties");
@@ -215,7 +212,7 @@ public final class TermuxActivity extends Activity implements ServiceConnection
setContentView(R.layout.drawer_layout); setContentView(R.layout.drawer_layout);
mTerminalView = (TerminalView) findViewById(R.id.terminal_view); mTerminalView = (TerminalView) findViewById(R.id.terminal_view);
mTerminalView.setOnKeyListener(new TermuxKeyListener(this)); mTerminalView.setOnKeyListener(new TermuxViewClient(this));
mTerminalView.setTextSize(mSettings.getFontSize()); mTerminalView.setTextSize(mSettings.getFontSize());
mFullScreenHelper.setImmersive(mSettings.isFullScreen()); mFullScreenHelper.setImmersive(mSettings.isFullScreen());
@@ -399,7 +396,6 @@ public final class TermuxActivity extends Activity implements ServiceConnection
@Override @Override
public void onClipboardText(TerminalSession session, String text) { public void onClipboardText(TerminalSession session, String text) {
if (!mIsVisible) return; if (!mIsVisible) return;
showToast("Clipboard:\n\"" + text + "\"", false);
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
clipboard.setPrimaryClip(new ClipData(null, new String[]{"text/plain"}, new ClipData.Item(text))); clipboard.setPrimaryClip(new ClipData(null, new String[]{"text/plain"}, new ClipData.Item(text)));
} }

View File

@@ -39,7 +39,7 @@ public final class TermuxHelpActivity extends Activity {
mWebView.setWebViewClient(new WebViewClient() { mWebView.setWebViewClient(new WebViewClient() {
@Override @Override
public boolean shouldOverrideUrlLoading(WebView view, String url) { public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (url.startsWith("https://termux.com")) { if (url.startsWith("https://wiki.termux.com")) {
// Inline help. // Inline help.
setContentView(progressLayout); setContentView(progressLayout);
return false; return false;
@@ -60,7 +60,7 @@ public final class TermuxHelpActivity extends Activity {
setContentView(mWebView); setContentView(mWebView);
} }
}); });
mWebView.loadUrl("https://termux.com/help.html"); mWebView.loadUrl("https://wiki.termux.com/wiki/Main_Page");
} }
@Override @Override

View File

@@ -43,7 +43,7 @@ import java.util.zip.ZipInputStream;
* (4) The architecture is determined and an appropriate bootstrap zip url is determined in {@link #determineZipUrl()}. * (4) The architecture is determined and an appropriate bootstrap zip url is determined in {@link #determineZipUrl()}.
* <p/> * <p/>
* (5) The zip, containing entries relative to the $PREFIX, is is downloaded and extracted by a zip input stream * (5) The zip, containing entries relative to the $PREFIX, is is downloaded and extracted by a zip input stream
* continously encountering zip file entries: * continuously encountering zip file entries:
* <p/> * <p/>
* (5.1) If the zip entry encountered is SYMLINKS.txt, go through it and remember all symlinks to setup. * (5.1) If the zip entry encountered is SYMLINKS.txt, go through it and remember all symlinks to setup.
* <p/> * <p/>

View File

@@ -8,8 +8,10 @@ import android.content.Intent;
import android.database.Cursor; import android.database.Cursor;
import android.database.MatrixCursor; import android.database.MatrixCursor;
import android.net.Uri; import android.net.Uri;
import android.os.Environment;
import android.os.ParcelFileDescriptor; import android.os.ParcelFileDescriptor;
import android.provider.MediaStore; import android.provider.MediaStore;
import android.support.annotation.NonNull;
import android.util.Log; import android.util.Log;
import android.webkit.MimeTypeMap; import android.webkit.MimeTypeMap;
@@ -17,6 +19,7 @@ import com.termux.terminal.EmulatorDebug;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException;
public class TermuxOpenReceiver extends BroadcastReceiver { public class TermuxOpenReceiver extends BroadcastReceiver {
@@ -28,23 +31,10 @@ public class TermuxOpenReceiver extends BroadcastReceiver {
return; return;
} }
final boolean isExternalUrl = data.getScheme() != null && !data.getScheme().equals("file");
if (isExternalUrl) {
Intent viewIntent = new Intent(Intent.ACTION_VIEW, data);
viewIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
context.startActivity(viewIntent);
} catch (ActivityNotFoundException e) {
Log.e(EmulatorDebug.LOG_TAG, "termux-open: No app handles the url " + data);
}
return;
}
final String filePath = data.getPath(); final String filePath = data.getPath();
final String contentTypeExtra = intent.getStringExtra("content-type"); final String contentTypeExtra = intent.getStringExtra("content-type");
final boolean useChooser = intent.getBooleanExtra("chooser", false); final boolean useChooser = intent.getBooleanExtra("chooser", false);
final String intentAction = intent.getAction() == null ? Intent.ACTION_VIEW : intent.getAction(); final String intentAction = intent.getAction() == null ? Intent.ACTION_VIEW : intent.getAction();
switch (intentAction) { switch (intentAction) {
case Intent.ACTION_SEND: case Intent.ACTION_SEND:
case Intent.ACTION_VIEW: case Intent.ACTION_VIEW:
@@ -55,6 +45,24 @@ public class TermuxOpenReceiver extends BroadcastReceiver {
break; break;
} }
final boolean isExternalUrl = data.getScheme() != null && !data.getScheme().equals("file");
if (isExternalUrl) {
Intent urlIntent = new Intent(intentAction, data);
if (intentAction.equals(Intent.ACTION_SEND)) {
urlIntent.putExtra(Intent.EXTRA_TEXT, data.toString());
urlIntent.setData(null);
} else if (contentTypeExtra != null) {
urlIntent.setDataAndType(data, contentTypeExtra);
}
urlIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
context.startActivity(urlIntent);
} catch (ActivityNotFoundException e) {
Log.e(EmulatorDebug.LOG_TAG, "termux-open: No app handles the url " + data);
}
return;
}
final File fileToShare = new File(filePath); final File fileToShare = new File(filePath);
if (!(fileToShare.isFile() && fileToShare.canRead())) { if (!(fileToShare.isFile() && fileToShare.canRead())) {
Log.e(EmulatorDebug.LOG_TAG, "termux-open: Not a readable file: '" + fileToShare.getAbsolutePath() + "'"); Log.e(EmulatorDebug.LOG_TAG, "termux-open: Not a readable file: '" + fileToShare.getAbsolutePath() + "'");
@@ -106,7 +114,7 @@ public class TermuxOpenReceiver extends BroadcastReceiver {
} }
@Override @Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { public Cursor query(@NonNull Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
File file = new File(uri.getPath()); File file = new File(uri.getPath());
if (projection == null) { if (projection == null) {
@@ -143,28 +151,38 @@ public class TermuxOpenReceiver extends BroadcastReceiver {
} }
@Override @Override
public String getType(Uri uri) { public String getType(@NonNull Uri uri) {
return null; return null;
} }
@Override @Override
public Uri insert(Uri uri, ContentValues values) { public Uri insert(@NonNull Uri uri, ContentValues values) {
return null; return null;
} }
@Override @Override
public int delete(Uri uri, String selection, String[] selectionArgs) { public int delete(@NonNull Uri uri, String selection, String[] selectionArgs) {
return 0; return 0;
} }
@Override @Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { public int update(@NonNull Uri uri, ContentValues values, String selection, String[] selectionArgs) {
return 0; return 0;
} }
@Override @Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException { public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) throws FileNotFoundException {
File file = new File(uri.getPath()); File file = new File(uri.getPath());
try {
String path = file.getCanonicalPath();
String storagePath = Environment.getExternalStorageDirectory().getCanonicalPath();
// See https://support.google.com/faqs/answer/7496913:
if (!(path.startsWith(TermuxService.FILES_PATH) || path.startsWith(storagePath))) {
throw new IllegalArgumentException("Invalid path: " + path);
}
} catch (IOException e) {
throw new IllegalArgumentException(e);
}
return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
} }
} }

View File

@@ -2,6 +2,7 @@ package com.termux.app;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.Notification; import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.app.Service; import android.app.Service;
@@ -11,10 +12,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.Build;
import android.os.Handler; import android.os.Handler;
import android.os.IBinder; import android.os.IBinder;
import android.os.PowerManager; import android.os.PowerManager;
import android.support.v4.content.WakefulBroadcastReceiver;
import android.util.Log; import android.util.Log;
import android.widget.ArrayAdapter; import android.widget.ArrayAdapter;
@@ -41,6 +42,8 @@ import java.util.List;
*/ */
public final class TermuxService extends Service implements SessionChangedCallback { public final class TermuxService extends Service implements SessionChangedCallback {
private static final String NOTIFICATION_CHANNEL_ID = "termux_notification_channel";
/** Note that this is a symlink on the Android M preview. */ /** Note that this is a symlink on the Android M preview. */
@SuppressLint("SdCardPath") @SuppressLint("SdCardPath")
public static final String FILES_PATH = "/data/data/com.termux/files"; public static final String FILES_PATH = "/data/data/com.termux/files";
@@ -104,7 +107,8 @@ public final class TermuxService extends Service implements SessionChangedCallba
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, EmulatorDebug.LOG_TAG); mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, EmulatorDebug.LOG_TAG);
mWakeLock.acquire(); mWakeLock.acquire();
WifiManager wm = (WifiManager) getSystemService(Context.WIFI_SERVICE); // http://tools.android.com/tech-docs/lint-in-studio-2-3#TOC-WifiManager-Leak
WifiManager wm = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
mWifiLock = wm.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, EmulatorDebug.LOG_TAG); mWifiLock = wm.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, EmulatorDebug.LOG_TAG);
mWifiLock.acquire(); mWifiLock.acquire();
@@ -152,11 +156,6 @@ public final class TermuxService extends Service implements SessionChangedCallba
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;
@@ -169,11 +168,12 @@ public final class TermuxService extends Service implements SessionChangedCallba
@Override @Override
public void onCreate() { public void onCreate() {
setupNotificationChannel();
startForeground(NOTIFICATION_ID, buildNotification()); startForeground(NOTIFICATION_ID, buildNotification());
} }
/** Update the shown foreground service notification after making any changes that affect it. */ /** Update the shown foreground service notification after making any changes that affect it. */
private void updateNotification() { void updateNotification() {
if (mWakeLock == null && mTerminalSessions.isEmpty() && mBackgroundTasks.isEmpty()) { if (mWakeLock == null && mTerminalSessions.isEmpty() && mBackgroundTasks.isEmpty()) {
// Exit if we are updating after the user disabled all locks with no sessions or tasks running. // Exit if we are updating after the user disabled all locks with no sessions or tasks running.
stopSelf(); stopSelf();
@@ -207,8 +207,8 @@ public final class TermuxService extends Service implements SessionChangedCallba
builder.setOngoing(true); builder.setOngoing(true);
// If holding a wake or wifi lock consider the notification of high priority since it's using power, // If holding a wake or wifi lock consider the notification of high priority since it's using power,
// otherwise use a minimal priority since this is just a background service notification: // otherwise use a low priority
builder.setPriority((wakeLockHeld) ? Notification.PRIORITY_HIGH : Notification.PRIORITY_MIN); builder.setPriority((wakeLockHeld) ? Notification.PRIORITY_HIGH : Notification.PRIORITY_LOW);
// No need to show a timestamp: // No need to show a timestamp:
builder.setShowWhen(false); builder.setShowWhen(false);
@@ -216,6 +216,10 @@ public final class TermuxService extends Service implements SessionChangedCallba
// Background color for small notification icon: // Background color for small notification icon:
builder.setColor(0xFF000000); builder.setColor(0xFF000000);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
builder.setChannelId(NOTIFICATION_CHANNEL_ID);
}
Resources res = getResources(); Resources res = getResources();
Intent exitIntent = new Intent(this, TermuxService.class).setAction(ACTION_STOP_SERVICE); Intent exitIntent = new Intent(this, TermuxService.class).setAction(ACTION_STOP_SERVICE);
builder.addAction(android.R.drawable.ic_delete, res.getString(R.string.notification_action_exit), PendingIntent.getService(this, 0, exitIntent, 0)); builder.addAction(android.R.drawable.ic_delete, res.getString(R.string.notification_action_exit), PendingIntent.getService(this, 0, exitIntent, 0));
@@ -340,4 +344,17 @@ public final class TermuxService extends Service implements SessionChangedCallba
} }
}); });
} }
private void setupNotificationChannel() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return;
String channelName = "Termux";
String channelDescription = "Notifications from Termux";
int importance = NotificationManager.IMPORTANCE_LOW;
NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, channelName,importance);
channel.setDescription(channelDescription);
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
manager.createNotificationChannel(channel);
}
} }

View File

@@ -12,18 +12,18 @@ import android.view.inputmethod.InputMethodManager;
import com.termux.terminal.KeyHandler; import com.termux.terminal.KeyHandler;
import com.termux.terminal.TerminalEmulator; import com.termux.terminal.TerminalEmulator;
import com.termux.terminal.TerminalSession; import com.termux.terminal.TerminalSession;
import com.termux.view.TerminalKeyListener; import com.termux.view.TerminalViewClient;
import java.util.List; import java.util.List;
public final class TermuxKeyListener implements TerminalKeyListener { public final class TermuxViewClient implements TerminalViewClient {
final TermuxActivity mActivity; final TermuxActivity mActivity;
/** Keeping track of the special keys acting as Ctrl and Fn for the soft keyboard and other hardware keys. */ /** Keeping track of the special keys acting as Ctrl and Fn for the soft keyboard and other hardware keys. */
boolean mVirtualControlKeyDown, mVirtualFnKeyDown; boolean mVirtualControlKeyDown, mVirtualFnKeyDown;
public TermuxKeyListener(TermuxActivity activity) { public TermuxViewClient(TermuxActivity activity) {
this.mActivity = activity; this.mActivity = activity;
} }
@@ -256,6 +256,11 @@ public final class TermuxKeyListener implements TerminalKeyListener {
return false; return false;
} }
@Override
public boolean onLongPress(MotionEvent event) {
return false;
}
/** Handle dedicated volume buttons as virtual keys if applicable. */ /** Handle dedicated volume buttons as virtual keys if applicable. */
private boolean handleVirtualKeys(int keyCode, KeyEvent event, boolean down) { private boolean handleVirtualKeys(int keyCode, KeyEvent event, boolean down) {
InputDevice inputDevice = event.getDevice(); InputDevice inputDevice = event.getDevice();

View File

@@ -30,10 +30,6 @@
<string name="select_url_copied_to_clipboard">URL copied to clipboard</string> <string name="select_url_copied_to_clipboard">URL copied to clipboard</string>
<string name="share_transcript_chooser_title">Send text to:</string> <string name="share_transcript_chooser_title">Send text to:</string>
<string name="paste_text">Paste</string>
<string name="copy_text">Copy</string>
<string name="text_selection_more">More…</string>
<string name="kill_process">Kill process (%d)</string> <string name="kill_process">Kill process (%d)</string>
<string name="confirm_kill_process">Really kill this session?</string> <string name="confirm_kill_process">Really kill this session?</string>

View File

@@ -9,8 +9,7 @@
- If adding text, use large font sizes. - If adding text, use large font sizes.
- Your graphic may be displayed alone without the app icon. - Your graphic may be displayed alone without the app icon.
--> -->
<svg xmlns:svg="http://www.w3.org/2000/svg" <svg xmlns="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
version="1.1" version="1.1"
viewBox="0 0 1024 500"> viewBox="0 0 1024 500">

Before

Width:  |  Height:  |  Size: 1006 B

After

Width:  |  Height:  |  Size: 962 B

View File

@@ -7,8 +7,7 @@
- Text must be included in the image. If your app is available in more - 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. than one language, you must provide versions of the banner image for each supported language.
--> -->
<svg xmlns:svg="http://www.w3.org/2000/svg" <svg xmlns="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
version="1.1" version="1.1"
viewBox="0 0 1280 720"> viewBox="0 0 1280 720">

Before

Width:  |  Height:  |  Size: 780 B

After

Width:  |  Height:  |  Size: 736 B

View File

@@ -1,17 +1,19 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript { buildscript {
repositories { repositories {
jcenter() jcenter()
google()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:2.2.3' classpath 'com.android.tools.build:gradle:3.0.0'
} }
} }
allprojects { allprojects {
repositories { repositories {
jcenter() jcenter()
maven {
url "https://maven.google.com"
}
} }
} }

Binary file not shown.

View File

@@ -1,6 +1,5 @@
#Sun Dec 04 17:26:05 CET 2016 distributionUrl=https\://services.gradle.org/distributions/gradle-4.3.1-bin.zip
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-3.2.1-bin.zip zipStoreBase=GRADLE_USER_HOME

6
gradlew vendored
View File

@@ -33,11 +33,11 @@ DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value. # Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum" MAX_FD="maximum"
warn ( ) { warn () {
echo "$*" echo "$*"
} }
die ( ) { die () {
echo echo
echo "$*" echo "$*"
echo echo
@@ -155,7 +155,7 @@ if $cygwin ; then
fi fi
# Escape application args # Escape application args
save ( ) { save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " " echo " "
} }

View File

@@ -0,0 +1,77 @@
// Start https://raw.githubusercontent.com/nuuneoi/JCenter/master/installv1.gradle
group = publishedGroupId // Maven Group ID for the artifact
install {
repositories.mavenInstaller {
pom {
project {
packaging 'aar'
groupId publishedGroupId
artifactId artifact
name libraryName
description libraryDescription
url siteUrl
licenses {
license {
name 'GNU General Public License version 3'
url 'https://opensource.org/licenses/gpl-3.0.html'
}
}
developers {
developer {
id 'fornwall'
name 'Fredrik Fornwall'
email 'fredrik@fornwall.net'
}
}
scm {
connection gitUrl
developerConnection gitUrl
url siteUrl
}
}
}
}
}
// End https://raw.githubusercontent.com/nuuneoi/JCenter/master/installv1.gradle
// Start https://raw.githubusercontent.com/nuuneoi/JCenter/master/bintrayv1.gradle
apply plugin: 'com.jfrog.bintray'
version = libraryVersion
task sourcesJar(type: Jar) {
classifier = 'sources'
from android.sourceSets.main.java.srcDirs
}
artifacts {
archives sourcesJar
}
bintray {
user = System.getenv('BINTRAY_USER')
key = System.getenv('BINTRAY_API_KEY')
configurations = ['archives']
pkg {
repo = 'maven'
name = bintrayName
userOrg = 'termux'
desc = libraryDescription
websiteUrl = siteUrl
vcsUrl = gitUrl
licenses = ['GPL-3.0']
publish = true
publicDownloadNumbers = true
version {
desc = libraryDescription
gpg {
sign = false //Determines whether to GPG sign the files. The default is false
// passphrase = properties.getProperty("bintray.gpg.password")
}
}
}
}

View File

@@ -1 +1 @@
include ':app' include ':app', ':terminal-emulator', ':terminal-view'

View File

@@ -0,0 +1,62 @@
plugins {
id "com.jfrog.bintray" version "1.7.3"
id "com.github.dcendents.android-maven" version "2.0"
}
apply plugin: 'com.android.library'
ext {
bintrayName = 'terminal-emulator'
publishedGroupId = 'com.termux'
libraryName = 'TerminalEmulator'
artifact = 'terminal-emulator'
libraryDescription = 'The terminal emulator used in Termux'
siteUrl = 'https://github.com/termux/termux'
gitUrl = 'https://github.com/termux/termux.git'
libraryVersion = '0.52'
}
android {
compileSdkVersion 27
buildToolsVersion "27.0.1"
defaultConfig {
minSdkVersion 21
targetSdkVersion 27
externalNativeBuild {
ndkBuild {
cFlags "-std=c11", "-Wall", "-Wextra", "-Werror", "-Os", "-fno-stack-protector", "-Wl,--gc-sections"
}
}
ndk {
abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
externalNativeBuild {
ndkBuild {
path "src/main/jni/Android.mk"
}
}
}
tasks.withType(Test) {
testLogging {
events "started", "passed", "skipped", "failed"
}
}
dependencies {
testImplementation 'junit:junit:4.12'
}
apply from: '../scripts/bintray-publish.gradle'

25
terminal-emulator/proguard-rules.pro vendored Normal file
View File

@@ -0,0 +1,25 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Users/fornwall/lib/android-sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@@ -0,0 +1,2 @@
<manifest package="com.termux.terminal">
</manifest>

View File

@@ -3,7 +3,7 @@ package com.termux.terminal;
/** /**
* A circular buffer of {@link TerminalRow}:s which keeps notes about what is visible on a logical screen and the scroll * A circular buffer of {@link TerminalRow}:s which keeps notes about what is visible on a logical screen and the scroll
* history. * history.
* <p/> * <p>
* See {@link #externalToInternalRow(int)} for how to map from logical screen rows to array indices. * See {@link #externalToInternalRow(int)} for how to map from logical screen rows to array indices.
*/ */
public final class TerminalBuffer { public final class TerminalBuffer {
@@ -92,22 +92,20 @@ public final class TerminalBuffer {
/** /**
* Convert a row value from the public external coordinate system to our internal private coordinate system. * Convert a row value from the public external coordinate system to our internal private coordinate system.
* <p/> *
* <ul>
* <li>External coordinate system: -mActiveTranscriptRows to mScreenRows-1, with the screen being 0..mScreenRows-1.
* <li>Internal coordinate system: the mScreenRows lines starting at mScreenFirstRow comprise the screen, while the
* mActiveTranscriptRows lines ending at mScreenFirstRow-1 form the transcript (as a circular buffer).
* </ul>
* <p/>
* External <---> Internal:
* <p/>
* <pre> * <pre>
* [ ... ] [ ... ] * - External coordinate system: -mActiveTranscriptRows to mScreenRows-1, with the screen being 0..mScreenRows-1.
* [ -mActiveTranscriptRows ] [ mScreenFirstRow - mActiveTranscriptRows ] * - Internal coordinate system: the mScreenRows lines starting at mScreenFirstRow comprise the screen, while the
* [ ... ] [ ... ] * mActiveTranscriptRows lines ending at mScreenFirstRow-1 form the transcript (as a circular buffer).
* [ 0 (visible screen starts here) ] <-----> [ mScreenFirstRow ] *
* [ ... ] [ ... ] * External Internal:
* [ mScreenRows-1 ] [ mScreenFirstRow + mScreenRows-1 ] *
* [ ... ] [ ... ]
* [ -mActiveTranscriptRows ] [ mScreenFirstRow - mActiveTranscriptRows ]
* [ ... ] [ ... ]
* [ 0 (visible screen starts here) ] [ mScreenFirstRow ]
* [ ... ] [ ... ]
* [ mScreenRows-1 ] [ mScreenFirstRow + mScreenRows-1 ]
* </pre> * </pre>
* *
* @param externalRow a row in the external coordinate system. * @param externalRow a row in the external coordinate system.

View File

@@ -56,8 +56,6 @@ public final class TerminalEmulator {
private static final int ESC_SELECT_LEFT_PAREN = 3; private static final int ESC_SELECT_LEFT_PAREN = 3;
/** Escape processing: Have seen ESC and a character-set-select ) char */ /** Escape processing: Have seen ESC and a character-set-select ) char */
private static final int ESC_SELECT_RIGHT_PAREN = 4; private static final int ESC_SELECT_RIGHT_PAREN = 4;
/** Escape processing: Have seen ESC and a character-set-select + char */
// private static final int ESC_SELECT_PLUS = 5;
/** Escape processing: "ESC [" or CSI (Control Sequence Introducer). */ /** Escape processing: "ESC [" or CSI (Control Sequence Introducer). */
private static final int ESC_CSI = 6; private static final int ESC_CSI = 6;
/** Escape processing: ESC [ ? */ /** Escape processing: ESC [ ? */
@@ -225,6 +223,7 @@ public final class TerminalEmulator {
private byte mUtf8ToFollow, mUtf8Index; private byte mUtf8ToFollow, mUtf8Index;
private final byte[] mUtf8InputBuffer = new byte[4]; private final byte[] mUtf8InputBuffer = new byte[4];
private int mLastEmittedCodePoint = -1;
public final TerminalColors mColors = new TerminalColors(); public final TerminalColors mColors = new TerminalColors();
@@ -421,10 +420,11 @@ public final class TerminalEmulator {
mUtf8Index = mUtf8ToFollow = 0; mUtf8Index = mUtf8ToFollow = 0;
if (codePoint >= 0x80 && codePoint <= 0x9F) { if (codePoint >= 0x80 && codePoint <= 0x9F) {
// Sequence decoded to a C1 control character which is the same as escape followed by // Sequence decoded to a C1 control character which we ignore. They are
// ((code & 0x7F) + 0x40). // not used nowadays and increases the risk of messing up the terminal state
processCodePoint(/* escape (hexadecimal=0x1B, octal=033): */27); // on binary input. XTerm does not allow them in utf-8:
processCodePoint((codePoint & 0x7F) + 0x40); // "It is not possible to use a C1 control obtained from decoding the
// UTF-8 text" - http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
} else { } else {
switch (Character.getType(codePoint)) { switch (Character.getType(codePoint)) {
case Character.UNASSIGNED: case Character.UNASSIGNED:
@@ -634,6 +634,7 @@ public final class TerminalEmulator {
int bottom = Math.min(getArg(2, mRows, true) + 1, effectiveBottomMargin - 1) + effectiveTopMargin; int bottom = Math.min(getArg(2, mRows, true) + 1, effectiveBottomMargin - 1) + effectiveTopMargin;
int right = Math.min(getArg(3, mColumns, true) + 1, effectiveRightMargin - 1) + effectiveLeftMargin; int right = Math.min(getArg(3, mColumns, true) + 1, effectiveRightMargin - 1) + effectiveLeftMargin;
if (mArgIndex >= 4) { if (mArgIndex >= 4) {
if (mArgIndex >= mArgs.length) mArgIndex = mArgs.length - 1;
for (int i = 4; i <= mArgIndex; i++) { for (int i = 4; i <= mArgIndex; i++) {
int bits = 0; int bits = 0;
boolean setOrClear = true; // True if setting, false if clearing. boolean setOrClear = true; // True if setting, false if clearing.
@@ -967,6 +968,7 @@ public final class TerminalEmulator {
break; break;
case 'h': case 'h':
case 'l': case 'l':
if (mArgIndex >= mArgs.length) mArgIndex = mArgs.length - 1;
for (int i = 0; i <= mArgIndex; i++) for (int i = 0; i <= mArgIndex; i++)
doDecSetOrReset(b == 'h', mArgs[i]); doDecSetOrReset(b == 'h', mArgs[i]);
break; break;
@@ -983,6 +985,7 @@ public final class TerminalEmulator {
break; break;
case 'r': case 'r':
case 's': case 's':
if (mArgIndex >= mArgs.length) mArgIndex = mArgs.length - 1;
for (int i = 0; i <= mArgIndex; i++) { for (int i = 0; i <= mArgIndex; i++) {
int externalBit = mArgs[i]; int externalBit = mArgs[i];
int internalBit = mapDecSetBitToInternalBit(externalBit); int internalBit = mapDecSetBitToInternalBit(externalBit);
@@ -1189,12 +1192,20 @@ public final class TerminalEmulator {
} }
private void doLinefeed() { private void doLinefeed() {
boolean belowScrollingRegion = mCursorRow >= mBottomMargin;
int newCursorRow = mCursorRow + 1; int newCursorRow = mCursorRow + 1;
if (newCursorRow >= mBottomMargin) { if (belowScrollingRegion) {
scrollDownOneLine(); // Move down (but not scroll) as long as we are above the last row.
newCursorRow = mBottomMargin - 1; if (mCursorRow != mRows - 1) {
setCursorRow(newCursorRow);
}
} else {
if (newCursorRow == mBottomMargin) {
scrollDownOneLine();
newCursorRow = mBottomMargin - 1;
}
setCursorRow(newCursorRow);
} }
setCursorRow(newCursorRow);
} }
private void continueSequence(int state) { private void continueSequence(int state) {
@@ -1309,6 +1320,8 @@ public final class TerminalEmulator {
state.mSavedCursorRow = mCursorRow; state.mSavedCursorRow = mCursorRow;
state.mSavedCursorCol = mCursorCol; state.mSavedCursorCol = mCursorCol;
state.mSavedEffect = mEffect; state.mSavedEffect = mEffect;
state.mSavedForeColor = mForeColor;
state.mSavedBackColor = mBackColor;
state.mSavedDecFlags = mCurrentDecSetFlags; state.mSavedDecFlags = mCurrentDecSetFlags;
state.mUseLineDrawingG0 = mUseLineDrawingG0; state.mUseLineDrawingG0 = mUseLineDrawingG0;
state.mUseLineDrawingG1 = mUseLineDrawingG1; state.mUseLineDrawingG1 = mUseLineDrawingG1;
@@ -1320,6 +1333,8 @@ public final class TerminalEmulator {
SavedScreenState state = (mScreen == mMainBuffer) ? mSavedStateMain : mSavedStateAlt; SavedScreenState state = (mScreen == mMainBuffer) ? mSavedStateMain : mSavedStateAlt;
setCursorRowCol(state.mSavedCursorRow, state.mSavedCursorCol); setCursorRowCol(state.mSavedCursorRow, state.mSavedCursorCol);
mEffect = state.mSavedEffect; mEffect = state.mSavedEffect;
mForeColor = state.mSavedForeColor;
mBackColor = state.mSavedBackColor;
int mask = (DECSET_BIT_AUTOWRAP | DECSET_BIT_ORIGIN_MODE); int mask = (DECSET_BIT_AUTOWRAP | DECSET_BIT_ORIGIN_MODE);
mCurrentDecSetFlags = (mCurrentDecSetFlags & ~mask) | (state.mSavedDecFlags & mask); mCurrentDecSetFlags = (mCurrentDecSetFlags & ~mask) | (state.mSavedDecFlags & mask);
mUseLineDrawingG0 = state.mUseLineDrawingG0; mUseLineDrawingG0 = state.mUseLineDrawingG0;
@@ -1503,6 +1518,11 @@ public final class TerminalEmulator {
case '`': // Horizontal position absolute (HPA - http://www.vt100.net/docs/vt510-rm/HPA). case '`': // Horizontal position absolute (HPA - http://www.vt100.net/docs/vt510-rm/HPA).
setCursorColRespectingOriginMode(getArg0(1) - 1); setCursorColRespectingOriginMode(getArg0(1) - 1);
break; break;
case 'b': // Repeat the preceding graphic character Ps times (REP).
if (mLastEmittedCodePoint == -1) break;
final int numRepeat = getArg0(1);
for (int i = 0; i < numRepeat; i++) emitCodePoint(mLastEmittedCodePoint);
break;
case 'c': // Primary Device Attributes (http://www.vt100.net/docs/vt510-rm/DA1) if argument is missing or zero. case 'c': // Primary Device Attributes (http://www.vt100.net/docs/vt510-rm/DA1) if argument is missing or zero.
// The important part that may still be used by some (tmux stores this value but does not currently use it) // The important part that may still be used by some (tmux stores this value but does not currently use it)
// is the first response parameter identifying the terminal service class, where we send 64 for "vt420". // is the first response parameter identifying the terminal service class, where we send 64 for "vt420".
@@ -1559,7 +1579,7 @@ public final class TerminalEmulator {
break; break;
case 'r': // "CSI${top};${bottom}r" - set top and bottom Margins (DECSTBM). case 'r': // "CSI${top};${bottom}r" - set top and bottom Margins (DECSTBM).
{ {
// http://www.vt100.net/docs/vt510-rm/DECSTBM // https://vt100.net/docs/vt510-rm/DECSTBM.html
// The top margin defaults to 1, the bottom margin defaults to mRows. // The top margin defaults to 1, the bottom margin defaults to mRows.
// The escape sequence numbers top 1..23, but we number top 0..22. // The escape sequence numbers top 1..23, but we number top 0..22.
// The escape sequence numbers bottom 2..24, and so do we (because we use a zero based numbering // The escape sequence numbers bottom 2..24, and so do we (because we use a zero based numbering
@@ -1568,6 +1588,7 @@ public final class TerminalEmulator {
// Also require that top + 2 <= bottom. // Also require that top + 2 <= bottom.
mTopMargin = Math.max(0, Math.min(getArg0(1) - 1, mRows - 2)); mTopMargin = Math.max(0, Math.min(getArg0(1) - 1, mRows - 2));
mBottomMargin = Math.max(mTopMargin + 2, Math.min(getArg1(mRows), mRows)); mBottomMargin = Math.max(mTopMargin + 2, Math.min(getArg1(mRows), mRows));
// DECSTBM moves the cursor to column 1, line 1 of the page respecting origin mode. // DECSTBM moves the cursor to column 1, line 1 of the page respecting origin mode.
setCursorPosition(0, 0); setCursorPosition(0, 0);
} }
@@ -1641,6 +1662,7 @@ public final class TerminalEmulator {
/** Select Graphic Rendition (SGR) - see http://en.wikipedia.org/wiki/ANSI_escape_code#graphics. */ /** Select Graphic Rendition (SGR) - see http://en.wikipedia.org/wiki/ANSI_escape_code#graphics. */
private void selectGraphicRendition() { private void selectGraphicRendition() {
if (mArgIndex >= mArgs.length) mArgIndex = mArgs.length - 1;
for (int i = 0; i <= mArgIndex; i++) { for (int i = 0; i <= mArgIndex; i++) {
int code = mArgs[i]; int code = mArgs[i];
if (code < 0) { if (code < 0) {
@@ -2051,6 +2073,7 @@ public final class TerminalEmulator {
buf.append(", escapeState="); buf.append(", escapeState=");
buf.append(mEscapeState); buf.append(mEscapeState);
boolean firstArg = true; boolean firstArg = true;
if (mArgIndex >= mArgs.length) mArgIndex = mArgs.length - 1;
for (int i = 0; i <= mArgIndex; i++) { for (int i = 0; i <= mArgIndex; i++) {
int value = mArgs[i]; int value = mArgs[i];
if (value >= 0) { if (value >= 0) {
@@ -2083,6 +2106,7 @@ public final class TerminalEmulator {
* @param codePoint The code point of the character to display * @param codePoint The code point of the character to display
*/ */
private void emitCodePoint(int codePoint) { private void emitCodePoint(int codePoint) {
mLastEmittedCodePoint = codePoint;
if (mUseLineDrawingUsesG0 ? mUseLineDrawingG0 : mUseLineDrawingG1) { if (mUseLineDrawingUsesG0 ? mUseLineDrawingG0 : mUseLineDrawingG1) {
// http://www.vt100.net/docs/vt102-ug/table5-15.html. // http://www.vt100.net/docs/vt102-ug/table5-15.html.
switch (codePoint) { switch (codePoint) {
@@ -2265,8 +2289,8 @@ public final class TerminalEmulator {
mBottomMargin = mRows; mBottomMargin = mRows;
mRightMargin = mColumns; mRightMargin = mColumns;
mAboutToAutoWrap = false; mAboutToAutoWrap = false;
mForeColor = TextStyle.COLOR_INDEX_FOREGROUND; mForeColor = mSavedStateMain.mSavedForeColor = mSavedStateAlt.mSavedForeColor = TextStyle.COLOR_INDEX_FOREGROUND;
mBackColor = TextStyle.COLOR_INDEX_BACKGROUND; mBackColor = mSavedStateMain.mSavedBackColor = mSavedStateAlt.mSavedBackColor = TextStyle.COLOR_INDEX_BACKGROUND;
setDefaultTabStops(); setDefaultTabStops();
mUseLineDrawingG0 = mUseLineDrawingG1 = false; mUseLineDrawingG0 = mUseLineDrawingG1 = false;
@@ -2320,7 +2344,7 @@ public final class TerminalEmulator {
static final class SavedScreenState { static final class SavedScreenState {
/** Saved state of the cursor position, Used to implement the save/restore cursor position escape sequences. */ /** Saved state of the cursor position, Used to implement the save/restore cursor position escape sequences. */
int mSavedCursorRow, mSavedCursorCol; int mSavedCursorRow, mSavedCursorCol;
int mSavedEffect; int mSavedEffect, mSavedForeColor, mSavedBackColor;
int mSavedDecFlags; int mSavedDecFlags;
boolean mUseLineDrawingG0, mUseLineDrawingG1, mUseLineDrawingUsesG0 = true; boolean mUseLineDrawingG0, mUseLineDrawingG1, mUseLineDrawingUsesG0 = true;
} }

View File

@@ -4,7 +4,7 @@ import java.util.Arrays;
/** /**
* A row in a terminal, composed of a fixed number of cells. * A row in a terminal, composed of a fixed number of cells.
* <p/> * <p>
* The text in the row is stored in a char[] array, {@link #mText}, for quick access during rendering. * The text in the row is stored in a char[] array, {@link #mText}, for quick access during rendering.
*/ */
public final class TerminalRow { public final class TerminalRow {

View File

@@ -19,13 +19,13 @@ import java.util.UUID;
/** /**
* A terminal session, consisting of a process coupled to a terminal interface. * A terminal session, consisting of a process coupled to a terminal interface.
* <p/> * <p>
* The subprocess will be executed by the constructor, and when the size is made known by a call to * The subprocess will be executed by the constructor, and when the size is made known by a call to
* {@link #updateSize(int, int)} terminal emulation will begin and threads will be spawned to handle the subprocess I/O. * {@link #updateSize(int, int)} terminal emulation will begin and threads will be spawned to handle the subprocess I/O.
* All terminal emulation and callback methods will be performed on the main thread. * All terminal emulation and callback methods will be performed on the main thread.
* <p/> * <p>
* The child process may be exited forcefully by using the {@link #finishIfRunning()} method. * The child process may be exited forcefully by using the {@link #finishIfRunning()} method.
* <p/> * <p>
* NOTE: The terminal session may outlive the EmulatorView, so be careful with callbacks! * NOTE: The terminal session may outlive the EmulatorView, so be careful with callbacks!
*/ */
public final class TerminalSession extends TerminalOutput { public final class TerminalSession extends TerminalOutput {

View File

@@ -1,10 +1,13 @@
package com.termux.terminal; package com.termux.terminal;
/** /**
* <p>
* Encodes effects, foreground and background colors into a 64 bit long, which are stored for each cell in a terminal * Encodes effects, foreground and background colors into a 64 bit long, which are stored for each cell in a terminal
* row in {@link TerminalRow#mStyle}. * row in {@link TerminalRow#mStyle}.
* <p/> * </p>
* <p>
* The bit layout is: * The bit layout is:
* </p>
* - 16 flags (11 currently used). * - 16 flags (11 currently used).
* - 24 for foreground color (only 9 first bits if a color index). * - 24 for foreground color (only 9 first bits if a color index).
* - 24 for background color (only 9 first bits if a color index). * - 24 for background color (only 9 first bits if a color index).
@@ -20,9 +23,10 @@ public final class TextStyle {
public final static int CHARACTER_ATTRIBUTE_STRIKETHROUGH = 1 << 6; public final static int CHARACTER_ATTRIBUTE_STRIKETHROUGH = 1 << 6;
/** /**
* The selective erase control functions (DECSED and DECSEL) can only erase characters defined as erasable. * The selective erase control functions (DECSED and DECSEL) can only erase characters defined as erasable.
* <p/> * <p>
* This bit is set if DECSCA (Select Character Protection Attribute) has been used to define the characters that * This bit is set if DECSCA (Select Character Protection Attribute) has been used to define the characters that
* come after it as erasable from the screen. * come after it as erasable from the screen.
* </p>
*/ */
public final static int CHARACTER_ATTRIBUTE_PROTECTED = 1 << 7; public final static int CHARACTER_ATTRIBUTE_PROTECTED = 1 << 7;
/** Dim colors. Also known as faint or half intensity. */ /** Dim colors. Also known as faint or half intensity. */

View File

@@ -29,4 +29,21 @@ public class ControlSequenceIntroducerTest extends TerminalTestCase {
withTerminalSized(13, 2).enterString("abcdefghijkl\b\b\b\b\b\033[20X").assertLinesAre("abcdefg ", " "); withTerminalSized(13, 2).enterString("abcdefghijkl\b\b\b\b\b\033[20X").assertLinesAre("abcdefg ", " ");
} }
/** CSI Pm m Set SGR parameter(s) from semicolon-separated list Pm. */
public void testCsiSGRParameters() {
// Set more parameters (19) than supported (16). Additional parameters should be silently consumed.
withTerminalSized(3, 2).enterString("\033[0;38;2;255;255;255;48;2;0;0;0;1;2;3;4;5;7;8;9mabc").assertLinesAre("abc", " ");
}
/** CSI Ps b Repeat the preceding graphic character Ps times (REP). */
public void testRepeat() {
withTerminalSized(3, 2).enterString("a\033[b").assertLinesAre("aa ", " ");
withTerminalSized(3, 2).enterString("a\033[2b").assertLinesAre("aaa", " ");
// When no char has been output we ignore REP:
withTerminalSized(3, 2).enterString("\033[b").assertLinesAre(" ", " ");
// This shows that REP outputs the last emitted code point and not the one relative to the
// current cursor position:
withTerminalSized(5, 2).enterString("abcde\033[2G\033[2b\n").assertLinesAre("aeede", " ");
}
} }

View File

@@ -227,4 +227,44 @@ public class CursorAndScreenTest extends TerminalTestCase {
withTerminalSized(3, 3).enterString("\b\b\b\bhi").assertLinesAre("hi ", " ", " "); withTerminalSized(3, 3).enterString("\b\b\b\bhi").assertLinesAre("hi ", " ", " ");
} }
public void testCursorSaveRestoreLocation() {
// DEC save/restore
withTerminalSized(4, 2).enterString("t\0337est\r\nme\0338ry ").assertLinesAre("try ", "me ");
// ANSI.SYS save/restore
withTerminalSized(4, 2).enterString("t\033[sest\r\nme\033[ury ").assertLinesAre("try ", "me ");
// Alternate screen enter/exit
withTerminalSized(4, 2).enterString("t\033[?1049h\033[Hest\r\nme").assertLinesAre("est ", "me ").enterString("\033[?1049lry").assertLinesAre("try ", " ");
}
public void testCursorSaveRestoreTextStyle() {
long s;
// DEC save/restore
withTerminalSized(4, 2).enterString("\033[31;42;4m..\0337\033[36;47;24m\0338..");
s = getStyleAt(0, 3);
Assert.assertEquals(1, TextStyle.decodeForeColor(s));
Assert.assertEquals(2, TextStyle.decodeBackColor(s));
Assert.assertEquals(TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE, TextStyle.decodeEffect(s));
// ANSI.SYS save/restore
withTerminalSized(4, 2).enterString("\033[31;42;4m..\033[s\033[36;47;24m\033[u..");
s = getStyleAt(0, 3);
Assert.assertEquals(1, TextStyle.decodeForeColor(s));
Assert.assertEquals(2, TextStyle.decodeBackColor(s));
Assert.assertEquals(TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE, TextStyle.decodeEffect(s));
// Alternate screen enter/exit
withTerminalSized(4, 2);
enterString("\033[31;42;4m..\033[?1049h\033[H\033[36;47;24m.");
s = getStyleAt(0, 0);
Assert.assertEquals(6, TextStyle.decodeForeColor(s));
Assert.assertEquals(7, TextStyle.decodeBackColor(s));
Assert.assertEquals(0, TextStyle.decodeEffect(s));
enterString("\033[?1049l..");
s = getStyleAt(0, 3);
Assert.assertEquals(1, TextStyle.decodeForeColor(s));
Assert.assertEquals(2, TextStyle.decodeBackColor(s));
Assert.assertEquals(TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE, TextStyle.decodeEffect(s));
}
} }

View File

@@ -98,4 +98,13 @@ public class ScrollRegionTest extends TerminalTestCase {
withTerminalSized(2, 5).enterString("1\r\n2\r\n3\r\n4\r\n5").assertLinesAre("1 ", "2 ", "3 ", "4 ", "5 "); withTerminalSized(2, 5).enterString("1\r\n2\r\n3\r\n4\r\n5").assertLinesAre("1 ", "2 ", "3 ", "4 ", "5 ");
enterString("\033[3r").enterString("\033[2T").assertLinesAre("1 ", "2 ", " ", " ", "3 "); enterString("\033[3r").enterString("\033[2T").assertLinesAre("1 ", "2 ", " ", " ", "3 ");
} }
public void testScrollDownBelowScrollRegion() {
withTerminalSized(2, 5).enterString("1\r\n2\r\n3\r\n4\r\n5").assertLinesAre("1 ", "2 ", "3 ", "4 ", "5 ");
enterString("\033[1;3r"); // DECSTBM margins.
enterString("\033[4;1H"); // Place cursor just below bottom margin.
enterString("QQ\r\nRR\r\n\r\n\r\nYY");
assertLinesAre("1 ", "2 ", "3 ", "QQ", "YY");
}
} }

View File

@@ -27,6 +27,7 @@ public class TerminalRowTest extends TestCase {
@Override @Override
protected void setUp() throws Exception { protected void setUp() throws Exception {
super.setUp();
row = new TerminalRow(COLUMNS, TextStyle.NORMAL); row = new TerminalRow(COLUMNS, TextStyle.NORMAL);
} }

View File

@@ -103,6 +103,7 @@ public abstract class TerminalTestCase extends TestCase {
@Override @Override
protected void setUp() throws Exception { protected void setUp() throws Exception {
super.setUp();
mOutput = new MockTerminalOutput(); mOutput = new MockTerminalOutput();
} }

View File

@@ -0,0 +1,46 @@
plugins {
id "com.jfrog.bintray" version "1.7.3"
id "com.github.dcendents.android-maven" version "2.0"
}
apply plugin: 'com.android.library'
ext {
bintrayName = 'terminal-view'
publishedGroupId = 'com.termux'
libraryName = 'TerminalView'
artifact = 'terminal-view'
libraryDescription = 'The terminal view used in Termux'
siteUrl = 'https://github.com/termux/termux'
gitUrl = 'https://github.com/termux/termux.git'
libraryVersion = '0.50'
}
android {
compileSdkVersion 27
buildToolsVersion "27.0.1"
dependencies {
implementation 'com.android.support:support-annotations:27.0.0'
api project(":terminal-emulator")
}
defaultConfig {
minSdkVersion 21
targetSdkVersion 27
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
testImplementation 'junit:junit:4.12'
}
apply from: '../scripts/bintray-publish.gradle'

25
terminal-view/proguard-rules.pro vendored Normal file
View File

@@ -0,0 +1,25 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Users/fornwall/lib/android-sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@@ -0,0 +1,2 @@
<manifest package="com.termux.view">
</manifest>

View File

@@ -6,7 +6,7 @@ import android.view.MotionEvent;
import android.view.ScaleGestureDetector; import android.view.ScaleGestureDetector;
/** A combination of {@link GestureDetector} and {@link ScaleGestureDetector}. */ /** A combination of {@link GestureDetector} and {@link ScaleGestureDetector}. */
public final class GestureAndScaleRecognizer { final class GestureAndScaleRecognizer {
public interface Listener { public interface Listener {
boolean onSingleTapUp(MotionEvent e); boolean onSingleTapUp(MotionEvent e);

View File

@@ -29,7 +29,6 @@ import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputConnection;
import android.widget.Scroller; import android.widget.Scroller;
import com.termux.R;
import com.termux.terminal.EmulatorDebug; import com.termux.terminal.EmulatorDebug;
import com.termux.terminal.KeyHandler; import com.termux.terminal.KeyHandler;
import com.termux.terminal.TerminalBuffer; import com.termux.terminal.TerminalBuffer;
@@ -49,7 +48,7 @@ public final class TerminalView extends View {
TerminalRenderer mRenderer; TerminalRenderer mRenderer;
TerminalKeyListener mOnKeyListener; TerminalViewClient mClient;
/** The top row of text to display. Ranges from -activeTranscriptRows to 0. */ /** The top row of text to display. Ranges from -activeTranscriptRows to 0. */
int mTopRow; int mTopRow;
@@ -106,7 +105,7 @@ public final class TerminalView extends View {
requestFocus(); requestFocus();
if (!mEmulator.isMouseTrackingActive()) { if (!mEmulator.isMouseTrackingActive()) {
if (!e.isFromSource(InputDevice.SOURCE_MOUSE)) { if (!e.isFromSource(InputDevice.SOURCE_MOUSE)) {
mOnKeyListener.onSingleTapUp(e); mClient.onSingleTapUp(e);
return true; return true;
} }
} }
@@ -136,7 +135,7 @@ public final class TerminalView extends View {
public boolean onScale(float focusX, float focusY, float scale) { public boolean onScale(float focusX, float focusY, float scale) {
if (mEmulator == null || mIsSelectingText) return true; if (mEmulator == null || mIsSelectingText) return true;
mScaleFactor *= scale; mScaleFactor *= scale;
mScaleFactor = mOnKeyListener.onScale(mScaleFactor); mScaleFactor = mClient.onScale(mScaleFactor);
return true; return true;
} }
@@ -189,7 +188,9 @@ public final class TerminalView extends View {
@Override @Override
public void onLongPress(MotionEvent e) { public void onLongPress(MotionEvent e) {
if (!mGestureRecognizer.isInProgress() && !mIsSelectingText) { if (mGestureRecognizer.isInProgress()) return;
if (mClient.onLongPress(e)) return;
if (!mIsSelectingText) {
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
toggleSelectingText(e); toggleSelectingText(e);
} }
@@ -202,8 +203,8 @@ public final class TerminalView extends View {
* @param onKeyListener Listener for all kinds of key events, both hardware and IME (which makes it different from that * @param onKeyListener Listener for all kinds of key events, both hardware and IME (which makes it different from that
* available with {@link View#setOnKeyListener(OnKeyListener)}. * available with {@link View#setOnKeyListener(OnKeyListener)}.
*/ */
public void setOnKeyListener(TerminalKeyListener onKeyListener) { public void setOnKeyListener(TerminalViewClient onKeyListener) {
this.mOnKeyListener = onKeyListener; this.mClient = onKeyListener;
} }
/** /**
@@ -555,7 +556,7 @@ public final class TerminalView extends View {
if (mIsSelectingText) { if (mIsSelectingText) {
toggleSelectingText(null); toggleSelectingText(null);
return true; return true;
} else if (mOnKeyListener.shouldBackButtonBeMappedToEscape()) { } else if (mClient.shouldBackButtonBeMappedToEscape()) {
// Intercept back button to treat it as escape: // Intercept back button to treat it as escape:
switch (event.getAction()) { switch (event.getAction()) {
case KeyEvent.ACTION_DOWN: case KeyEvent.ACTION_DOWN:
@@ -574,10 +575,10 @@ public final class TerminalView extends View {
Log.i(EmulatorDebug.LOG_TAG, "onKeyDown(keyCode=" + keyCode + ", isSystem()=" + event.isSystem() + ", event=" + event + ")"); Log.i(EmulatorDebug.LOG_TAG, "onKeyDown(keyCode=" + keyCode + ", isSystem()=" + event.isSystem() + ", event=" + event + ")");
if (mEmulator == null) return true; if (mEmulator == null) return true;
if (mOnKeyListener.onKeyDown(keyCode, event, mTermSession)) { if (mClient.onKeyDown(keyCode, event, mTermSession)) {
invalidate(); invalidate();
return true; return true;
} else if (event.isSystem() && (!mOnKeyListener.shouldBackButtonBeMappedToEscape() || keyCode != KeyEvent.KEYCODE_BACK)) { } else if (event.isSystem() && (!mClient.shouldBackButtonBeMappedToEscape() || keyCode != KeyEvent.KEYCODE_BACK)) {
return super.onKeyDown(keyCode, event); return super.onKeyDown(keyCode, event);
} else if (event.getAction() == KeyEvent.ACTION_MULTIPLE && keyCode == KeyEvent.KEYCODE_UNKNOWN) { } else if (event.getAction() == KeyEvent.ACTION_MULTIPLE && keyCode == KeyEvent.KEYCODE_UNKNOWN) {
mTermSession.write(event.getCharacters()); mTermSession.write(event.getCharacters());
@@ -641,10 +642,10 @@ public final class TerminalView extends View {
+ leftAltDownFromEvent + ")"); + leftAltDownFromEvent + ")");
} }
final boolean controlDown = controlDownFromEvent || mOnKeyListener.readControlKey(); final boolean controlDown = controlDownFromEvent || mClient.readControlKey();
final boolean altDown = leftAltDownFromEvent || mOnKeyListener.readAltKey(); final boolean altDown = leftAltDownFromEvent || mClient.readAltKey();
if (mOnKeyListener.onCodePoint(codePoint, controlDown, mTermSession)) return; if (mClient.onCodePoint(codePoint, controlDown, mTermSession)) return;
if (controlDown) { if (controlDown) {
if (codePoint >= 'a' && codePoint <= 'z') { if (codePoint >= 'a' && codePoint <= 'z') {
@@ -713,7 +714,7 @@ public final class TerminalView extends View {
Log.i(EmulatorDebug.LOG_TAG, "onKeyUp(keyCode=" + keyCode + ", event=" + event + ")"); Log.i(EmulatorDebug.LOG_TAG, "onKeyUp(keyCode=" + keyCode + ", event=" + event + ")");
if (mEmulator == null) return true; if (mEmulator == null) return true;
if (mOnKeyListener.onKeyUp(keyCode, event)) { if (mClient.onKeyUp(keyCode, event)) {
invalidate(); invalidate();
return true; return true;
} else if (event.isSystem()) { } else if (event.isSystem()) {
@@ -781,7 +782,7 @@ public final class TerminalView extends View {
@TargetApi(23) @TargetApi(23)
public void toggleSelectingText(MotionEvent ev) { public void toggleSelectingText(MotionEvent ev) {
mIsSelectingText = !mIsSelectingText; mIsSelectingText = !mIsSelectingText;
mOnKeyListener.copyModeChanged(mIsSelectingText); mClient.copyModeChanged(mIsSelectingText);
if (mIsSelectingText) { if (mIsSelectingText) {
if (mLeftSelectionHandle == null) { if (mLeftSelectionHandle == null) {
@@ -833,6 +834,10 @@ public final class TerminalView extends View {
@Override @Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) { public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
if (!mIsSelectingText) {
// Fix issue where the dialog is pressed while being dismissed.
return true;
}
switch (item.getItemId()) { switch (item.getItemId()) {
case 1: case 1:
String selectedText = mEmulator.getSelectedText(mSelX1, mSelY1, mSelX2, mSelY2).trim(); String selectedText = mEmulator.getSelectedText(mSelX1, mSelY1, mSelX2, mSelY2).trim();

View File

@@ -8,16 +8,19 @@ import com.termux.terminal.TerminalSession;
/** /**
* Input and scale listener which may be set on a {@link TerminalView} through * Input and scale listener which may be set on a {@link TerminalView} through
* {@link TerminalView#setOnKeyListener(TerminalKeyListener)}. * {@link TerminalView#setOnKeyListener(TerminalViewClient)}.
* <p/> * <p/>
* TODO: Rename to TerminalViewClient.
*/ */
public interface TerminalKeyListener { public interface TerminalViewClient {
/** Callback function on scale events according to {@link ScaleGestureDetector#getScaleFactor()}. */ /**
* Callback function on scale events according to {@link ScaleGestureDetector#getScaleFactor()}.
*/
float onScale(float scale); float onScale(float scale);
/** On a single tap on the terminal if terminal mouse reporting not enabled. */ /**
* On a single tap on the terminal if terminal mouse reporting not enabled.
*/
void onSingleTapUp(MotionEvent e); void onSingleTapUp(MotionEvent e);
boolean shouldBackButtonBeMappedToEscape(); boolean shouldBackButtonBeMappedToEscape();
@@ -34,4 +37,6 @@ public interface TerminalKeyListener {
boolean onCodePoint(int codePoint, boolean ctrlDown, TerminalSession session); boolean onCodePoint(int codePoint, boolean ctrlDown, TerminalSession session);
boolean onLongPress(MotionEvent event);
} }

View File

@@ -0,0 +1,5 @@
<resources>
<string name="paste_text">Paste</string>
<string name="copy_text">Copy</string>
<string name="text_selection_more">More…</string>
</resources>