Compare commits
70 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
edcebf1336 | ||
|
|
fcfd131ccd | ||
|
|
ce5e9a9042 | ||
|
|
7884cb3bea | ||
|
|
d8fcc1f221 | ||
|
|
397a78f248 | ||
|
|
8baf53b09a | ||
|
|
4139bf9424 | ||
|
|
94deecb7b1 | ||
|
|
a4381b7827 | ||
|
|
866da75fa9 | ||
|
|
2b6e9ade07 | ||
|
|
6d1b0efd3b | ||
|
|
e03858f065 | ||
|
|
01929397cf | ||
|
|
496da3f877 | ||
|
|
407e4e003a | ||
|
|
fec61d315f | ||
|
|
f3a3a89f93 | ||
|
|
5b084f0851 | ||
|
|
a7eb173178 | ||
|
|
fa5117a098 | ||
|
|
2ef45c85b2 | ||
|
|
58b7a26b33 | ||
|
|
6e2a2ed946 | ||
|
|
7be1fe5555 | ||
|
|
0e94d52094 | ||
|
|
096dedffb1 | ||
|
|
b5d491a54c | ||
|
|
f6822d6c24 | ||
|
|
32c3ffd57b | ||
|
|
92570bee06 | ||
|
|
05bb399893 | ||
|
|
fe584940e1 | ||
|
|
78cdaef6d2 | ||
|
|
7b4a69f839 | ||
|
|
831aa69da8 | ||
|
|
a56cba6843 | ||
|
|
9228982632 | ||
|
|
38114784f1 | ||
|
|
b805f1486c | ||
|
|
7d31b7f480 | ||
|
|
a0298285e3 | ||
|
|
538a1d5cdf | ||
|
|
f1e973f0d2 | ||
|
|
b467b68f7b | ||
|
|
b895cbbb1e | ||
|
|
fc30eba247 | ||
|
|
b1d4c0c7fe | ||
|
|
7fe5bd32c8 | ||
|
|
43bbef9a11 | ||
|
|
eaea0f74a5 | ||
|
|
cb13a5a531 | ||
|
|
d267843e36 | ||
|
|
5ca67dd885 | ||
|
|
6b0d531758 | ||
|
|
1b3283bd69 | ||
|
|
2820f6a7b8 | ||
|
|
8925ae67bb | ||
|
|
4066c5df42 | ||
|
|
20aac6aa72 | ||
|
|
e1f799f9a1 | ||
|
|
4479de4b9b | ||
|
|
3a8f53a54c | ||
|
|
2f04a6186b | ||
|
|
ad64dd7c3d | ||
|
|
dfc4595ec5 | ||
|
|
8c80efb904 | ||
|
|
564079c7e9 | ||
|
|
3bb2849a88 |
8
.gitattributes
vendored
@@ -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
@@ -0,0 +1,2 @@
|
|||||||
|
patreon: termux
|
||||||
|
custom: https://paypal.me/fornwall
|
||||||
4
.github/workflows/debug_build.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
./gradlew assembleDebug
|
./gradlew assembleDebug
|
||||||
- name: Store generated APK file
|
- name: Store generated APK file
|
||||||
uses: actions/upload-artifact@v1
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: termux-app
|
name: termux-app
|
||||||
path: ./app/build/outputs/apk/debug/app-debug.apk
|
path: ./app/build/outputs/apk/debug
|
||||||
|
|||||||
25
.github/workflows/publish_libraries.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
name: Publish library packages
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
paths:
|
||||||
|
- 'terminal-emulator/build.gradle'
|
||||||
|
- 'terminal-view/build.gradle'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Clone repository
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
- name: Perform release build
|
||||||
|
run: |
|
||||||
|
./gradlew assembleRelease
|
||||||
|
- name: Publish libraries on Github Packages
|
||||||
|
env:
|
||||||
|
GH_USERNAME: xeffyr
|
||||||
|
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||||
|
run: |
|
||||||
|
./gradlew publish
|
||||||
15
README.md
@@ -14,13 +14,18 @@ Note that this repository is for the app itself (the user interface and the
|
|||||||
terminal emulation). For the packages installable inside the app, see
|
terminal emulation). For the packages installable inside the app, see
|
||||||
[termux/termux-packages](https://github.com/termux/termux-packages)
|
[termux/termux-packages](https://github.com/termux/termux-packages)
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
**@termux is looking for Termux Application maintainer for implementing new features,
|
||||||
|
fixing bugs and reviewing pull requests since current one (@fornwall) is inactive.**
|
||||||
|
|
||||||
|
Issue https://github.com/termux/termux-app/issues/1072 needs extra attention.
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
Termux application can be obtained from:
|
Termux application can be obtained from [F-Droid](https://f-droid.org/en/packages/com.termux/).
|
||||||
|
|
||||||
- [Google Play](https://play.google.com/store/apps/details?id=com.termux)
|
|
||||||
- [F-Droid](https://f-droid.org/en/packages/com.termux/)
|
|
||||||
- [Kali Nethunter Store](https://store.nethunter.com/en/packages/com.termux/)
|
|
||||||
|
|
||||||
Additionally we provide per-commit debug builds for those who want to try
|
Additionally we provide per-commit debug builds for those who want to try
|
||||||
out the latest features or test their pull request. This build can be obtained
|
out the latest features or test their pull request. This build can be obtained
|
||||||
|
|||||||
@@ -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.1.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 101
|
versionCode 108
|
||||||
versionName "0.101"
|
versionName "0.108"
|
||||||
|
|
||||||
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 {
|
||||||
@@ -82,7 +86,7 @@ task versionName {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def downloadBootstrap(String arch, String expectedChecksum, int version) {
|
def downloadBootstrap(String arch, String expectedChecksum, String version) {
|
||||||
def digest = java.security.MessageDigest.getInstance("SHA-256")
|
def digest = java.security.MessageDigest.getInstance("SHA-256")
|
||||||
|
|
||||||
def localUrl = "src/main/cpp/bootstrap-" + arch + ".zip"
|
def localUrl = "src/main/cpp/bootstrap-" + arch + ".zip"
|
||||||
@@ -104,7 +108,7 @@ def downloadBootstrap(String arch, String expectedChecksum, int version) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def remoteUrl = "https://bintray.com/termux/bootstrap/download_file?file_path=bootstrap-" + arch + "-v" + version + ".zip"
|
def remoteUrl = "https://github.com/termux/termux-packages/releases/download/bootstrap-" + version + "/bootstrap-" + arch + ".zip"
|
||||||
logger.quiet("Downloading " + remoteUrl + " ...")
|
logger.quiet("Downloading " + remoteUrl + " ...")
|
||||||
|
|
||||||
file.parentFile.mkdirs()
|
file.parentFile.mkdirs()
|
||||||
@@ -133,11 +137,11 @@ clean {
|
|||||||
|
|
||||||
task downloadBootstraps(){
|
task downloadBootstraps(){
|
||||||
doLast {
|
doLast {
|
||||||
def version = 30
|
def version = "2021.02.19-r1"
|
||||||
downloadBootstrap("aarch64", "7a90034285c614d23fa450547a5e2aec77d4242c9891ad662bf0c6fd3bd7ef4e", version)
|
downloadBootstrap("aarch64", "1e3d80bd8cc8771715845ab4a1e67fc125d84c4deda3a1a435116fe4d1f86160", version)
|
||||||
downloadBootstrap("arm", "f030869ce9a43f84d88560d7ac5153ee4f7e517bca0b37ab01df3e1acba0fe37", version)
|
downloadBootstrap("arm", "317a987cab3d4da6f9d42f024a5f25e3aaf8557b8ec68eaf80411fde6b8ea78d", version)
|
||||||
downloadBootstrap("i686", "1ea9b63f21602231140d58a5545cfbc6bc2ded56ef2b3c31cba2759d913eef00", version)
|
downloadBootstrap("i686", "dd4632350474058fe5dec14b7bd882798bb3e9fe738b2de8c84a51a0b6395f4b", version)
|
||||||
downloadBootstrap("x86_64", "a50eb8a4dd02b7898bbd4a9653a25c14b56c1737409ce7f64110fd33c2c69382", version)
|
downloadBootstrap("x86_64", "5458119186c3ea631420833e59730461dc2a5faf17b8fad239823c88aa4569ae", version)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<permission android:name="com.termux.permission.RUN_COMMAND"
|
<permission android:name="com.termux.permission.RUN_COMMAND"
|
||||||
android:label="@string/run_command_permission_label"
|
android:label="@string/run_command_permission_label"
|
||||||
android:description="@string/run_command_permission_description"
|
android:description="@string/run_command_permission_description"
|
||||||
android:icon="@drawable/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:protectionLevel="dangerous" />
|
android:protectionLevel="dangerous" />
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
@@ -22,11 +22,14 @@
|
|||||||
<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.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"
|
||||||
android:allowBackup="false"
|
android:allowBackup="false"
|
||||||
android:icon="@drawable/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:banner="@drawable/banner"
|
android:banner="@drawable/banner"
|
||||||
android:label="@string/application_name"
|
android:label="@string/application_name"
|
||||||
android:theme="@style/Theme.Termux"
|
android:theme="@style/Theme.Termux"
|
||||||
@@ -81,16 +84,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>
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import android.content.Intent;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.termux.BuildConfig;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
@@ -138,6 +140,7 @@ public final class BackgroundJob {
|
|||||||
|
|
||||||
List<String> environment = new ArrayList<>();
|
List<String> environment = new ArrayList<>();
|
||||||
|
|
||||||
|
environment.add("TERMUX_VERSION=" + BuildConfig.VERSION_NAME);
|
||||||
environment.add("TERM=xterm-256color");
|
environment.add("TERM=xterm-256color");
|
||||||
environment.add("COLORTERM=truecolor");
|
environment.add("COLORTERM=truecolor");
|
||||||
environment.add("HOME=" + TermuxService.HOME_PATH);
|
environment.add("HOME=" + TermuxService.HOME_PATH);
|
||||||
@@ -161,7 +164,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");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,9 @@ import android.os.Build;
|
|||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
@@ -13,6 +16,7 @@ import java.util.concurrent.ScheduledExecutorService;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import android.view.Gravity;
|
import android.view.Gravity;
|
||||||
import android.view.HapticFeedbackConstants;
|
import android.view.HapticFeedbackConstants;
|
||||||
@@ -23,7 +27,6 @@ import android.view.inputmethod.InputMethodManager;
|
|||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.GridLayout;
|
import android.widget.GridLayout;
|
||||||
import android.widget.PopupWindow;
|
import android.widget.PopupWindow;
|
||||||
import android.widget.ToggleButton;
|
|
||||||
|
|
||||||
import com.termux.R;
|
import com.termux.R;
|
||||||
import com.termux.view.TerminalView;
|
import com.termux.view.TerminalView;
|
||||||
@@ -129,15 +132,27 @@ public final class ExtraKeysView extends GridLayout {
|
|||||||
|
|
||||||
private static class SpecialButtonState {
|
private static class SpecialButtonState {
|
||||||
boolean isOn = false;
|
boolean isOn = false;
|
||||||
ToggleButton button = null;
|
boolean isActive = false;
|
||||||
|
List<Button> buttons = new ArrayList<>();
|
||||||
|
|
||||||
|
void setIsActive(boolean value) {
|
||||||
|
isActive = value;
|
||||||
|
buttons.forEach(button -> button.setTextColor(value ? INTERESTING_COLOR : TEXT_COLOR));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<SpecialButton, SpecialButtonState> specialButtons = new HashMap<SpecialButton, SpecialButtonState>() {{
|
private final Map<SpecialButton, SpecialButtonState> specialButtons = new HashMap<SpecialButton, SpecialButtonState>() {{
|
||||||
put(SpecialButton.CTRL, new SpecialButtonState());
|
put(SpecialButton.CTRL, new SpecialButtonState());
|
||||||
put(SpecialButton.ALT, new SpecialButtonState());
|
put(SpecialButton.ALT, new SpecialButtonState());
|
||||||
put(SpecialButton.FN, new SpecialButtonState());
|
put(SpecialButton.FN, new SpecialButtonState());
|
||||||
}};
|
}};
|
||||||
|
|
||||||
|
private final Set<String> specialButtonsKeys = specialButtons.keySet().stream().map(Enum::name).collect(Collectors.toSet());
|
||||||
|
|
||||||
|
private boolean isSpecialButton(ExtraKeyButton button) {
|
||||||
|
return specialButtonsKeys.contains(button.getKey());
|
||||||
|
}
|
||||||
|
|
||||||
private ScheduledExecutorService scheduledExecutor;
|
private ScheduledExecutorService scheduledExecutor;
|
||||||
private PopupWindow popupWindow;
|
private PopupWindow popupWindow;
|
||||||
private int longPressCount;
|
private int longPressCount;
|
||||||
@@ -147,30 +162,36 @@ public final class ExtraKeysView extends GridLayout {
|
|||||||
if (state == null)
|
if (state == null)
|
||||||
throw new RuntimeException("Must be a valid special button (see source)");
|
throw new RuntimeException("Must be a valid special button (see source)");
|
||||||
|
|
||||||
if (! state.isOn)
|
if (!state.isOn || !state.isActive)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (state.button == null) {
|
state.setIsActive(false);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state.button.isPressed())
|
|
||||||
return true;
|
|
||||||
|
|
||||||
if (! state.button.isChecked())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
state.button.setChecked(false);
|
|
||||||
state.button.setTextColor(TEXT_COLOR);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void popup(View view, String text) {
|
private Button createSpecialButton(String buttonKey, boolean needUpdate) {
|
||||||
|
SpecialButtonState state = specialButtons.get(SpecialButton.valueOf(buttonKey));
|
||||||
|
state.isOn = true;
|
||||||
|
Button button = new Button(getContext(), null, android.R.attr.buttonBarButtonStyle);
|
||||||
|
button.setTextColor(state.isActive ? INTERESTING_COLOR : TEXT_COLOR);
|
||||||
|
if (needUpdate) {
|
||||||
|
state.buttons.add(button);
|
||||||
|
}
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
|
||||||
|
void popup(View view, ExtraKeyButton extraButton) {
|
||||||
int width = view.getMeasuredWidth();
|
int width = view.getMeasuredWidth();
|
||||||
int height = view.getMeasuredHeight();
|
int height = view.getMeasuredHeight();
|
||||||
Button button = new Button(getContext(), null, android.R.attr.buttonBarButtonStyle);
|
Button button;
|
||||||
button.setText(text);
|
if(isSpecialButton(extraButton)) {
|
||||||
button.setTextColor(TEXT_COLOR);
|
button = createSpecialButton(extraButton.getKey(), false);
|
||||||
|
} else {
|
||||||
|
button = new Button(getContext(), null, android.R.attr.buttonBarButtonStyle);
|
||||||
|
button.setTextColor(TEXT_COLOR);
|
||||||
|
}
|
||||||
|
button.setText(extraButton.getDisplay());
|
||||||
button.setPadding(0, 0, 0, 0);
|
button.setPadding(0, 0, 0, 0);
|
||||||
button.setMinHeight(0);
|
button.setMinHeight(0);
|
||||||
button.setMinWidth(0);
|
button.setMinWidth(0);
|
||||||
@@ -219,7 +240,7 @@ public final class ExtraKeysView extends GridLayout {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
for(SpecialButtonState state : specialButtons.values())
|
for(SpecialButtonState state : specialButtons.values())
|
||||||
state.button = null;
|
state.buttons = new ArrayList<>();
|
||||||
|
|
||||||
removeAllViews();
|
removeAllViews();
|
||||||
|
|
||||||
@@ -233,11 +254,8 @@ public final class ExtraKeysView extends GridLayout {
|
|||||||
final ExtraKeyButton buttonInfo = buttons[row][col];
|
final ExtraKeyButton buttonInfo = buttons[row][col];
|
||||||
|
|
||||||
Button button;
|
Button button;
|
||||||
if(Arrays.asList("CTRL", "ALT", "FN").contains(buttonInfo.getKey())) {
|
if(isSpecialButton(buttonInfo)) {
|
||||||
SpecialButtonState state = specialButtons.get(SpecialButton.valueOf(buttonInfo.getKey())); // for valueOf: https://stackoverflow.com/a/604426/1980630
|
button = createSpecialButton(buttonInfo.getKey(), true);
|
||||||
state.isOn = true;
|
|
||||||
button = state.button = new ToggleButton(getContext(), null, android.R.attr.buttonBarButtonStyle);
|
|
||||||
button.setClickable(true);
|
|
||||||
} else {
|
} else {
|
||||||
button = new Button(getContext(), null, android.R.attr.buttonBarButtonStyle);
|
button = new Button(getContext(), null, android.R.attr.buttonBarButtonStyle);
|
||||||
}
|
}
|
||||||
@@ -262,10 +280,9 @@ public final class ExtraKeysView extends GridLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
View root = getRootView();
|
View root = getRootView();
|
||||||
if (Arrays.asList("CTRL", "ALT", "FN").contains(buttonInfo.getKey())) {
|
if (isSpecialButton(buttonInfo)) {
|
||||||
ToggleButton self = (ToggleButton) finalButton;
|
SpecialButtonState state = specialButtons.get(SpecialButton.valueOf(buttonInfo.getKey()));
|
||||||
self.setChecked(self.isChecked());
|
state.setIsActive(!state.isActive);
|
||||||
self.setTextColor(self.isChecked() ? INTERESTING_COLOR : TEXT_COLOR);
|
|
||||||
} else {
|
} else {
|
||||||
sendKey(root, buttonInfo);
|
sendKey(root, buttonInfo);
|
||||||
}
|
}
|
||||||
@@ -295,8 +312,7 @@ public final class ExtraKeysView extends GridLayout {
|
|||||||
scheduledExecutor = null;
|
scheduledExecutor = null;
|
||||||
}
|
}
|
||||||
v.setBackgroundColor(BUTTON_COLOR);
|
v.setBackgroundColor(BUTTON_COLOR);
|
||||||
String extraButtonDisplayedText = buttonInfo.getPopup().getDisplay();
|
popup(v, buttonInfo.getPopup());
|
||||||
popup(v, extraButtonDisplayedText);
|
|
||||||
}
|
}
|
||||||
if (popupWindow != null && event.getY() > 0) {
|
if (popupWindow != null && event.getY() > 0) {
|
||||||
v.setBackgroundColor(BUTTON_PRESSED_COLOR);
|
v.setBackgroundColor(BUTTON_PRESSED_COLOR);
|
||||||
@@ -325,7 +341,12 @@ public final class ExtraKeysView extends GridLayout {
|
|||||||
popupWindow.dismiss();
|
popupWindow.dismiss();
|
||||||
popupWindow = null;
|
popupWindow = null;
|
||||||
if (buttonInfo.getPopup() != null) {
|
if (buttonInfo.getPopup() != null) {
|
||||||
sendKey(root, buttonInfo.getPopup());
|
if (isSpecialButton(buttonInfo.getPopup())) {
|
||||||
|
SpecialButtonState state = specialButtons.get(SpecialButton.valueOf(buttonInfo.getPopup().getKey()));
|
||||||
|
state.setIsActive(!state.isActive);
|
||||||
|
} else {
|
||||||
|
sendKey(root, buttonInfo.getPopup());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
v.performClick();
|
v.performClick();
|
||||||
|
|||||||
66
app/src/main/java/com/termux/app/FullScreenWorkAround.java
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
package com.termux.app;
|
||||||
|
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Work around for fullscreen mode in Termux to fix ExtraKeysView not being visible.
|
||||||
|
* This class is derived from:
|
||||||
|
* https://stackoverflow.com/questions/7417123/android-how-to-adjust-layout-in-full-screen-mode-when-softkeyboard-is-visible
|
||||||
|
* and has some additional tweaks
|
||||||
|
* ---
|
||||||
|
* For more information, see https://issuetracker.google.com/issues/36911528
|
||||||
|
*/
|
||||||
|
public class FullScreenWorkAround {
|
||||||
|
private View mChildOfContent;
|
||||||
|
private int mUsableHeightPrevious;
|
||||||
|
private ViewGroup.LayoutParams mViewGroupLayoutParams;
|
||||||
|
|
||||||
|
private int mNavBarHeight;
|
||||||
|
|
||||||
|
|
||||||
|
public static void apply(TermuxActivity activity) {
|
||||||
|
new FullScreenWorkAround(activity);
|
||||||
|
}
|
||||||
|
|
||||||
|
private FullScreenWorkAround(TermuxActivity activity) {
|
||||||
|
ViewGroup content = activity.findViewById(android.R.id.content);
|
||||||
|
mChildOfContent = content.getChildAt(0);
|
||||||
|
mViewGroupLayoutParams = mChildOfContent.getLayoutParams();
|
||||||
|
mNavBarHeight = activity.getNavBarHeight();
|
||||||
|
mChildOfContent.getViewTreeObserver().addOnGlobalLayoutListener(this::possiblyResizeChildOfContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void possiblyResizeChildOfContent() {
|
||||||
|
int usableHeightNow = computeUsableHeight();
|
||||||
|
if (usableHeightNow != mUsableHeightPrevious) {
|
||||||
|
int usableHeightSansKeyboard = mChildOfContent.getRootView().getHeight();
|
||||||
|
int heightDifference = usableHeightSansKeyboard - usableHeightNow;
|
||||||
|
if (heightDifference > (usableHeightSansKeyboard / 4)) {
|
||||||
|
// keyboard probably just became visible
|
||||||
|
|
||||||
|
// ensures that usable layout space does not extend behind the
|
||||||
|
// soft keyboard, causing the extra keys to not be visible
|
||||||
|
mViewGroupLayoutParams.height = (usableHeightSansKeyboard - heightDifference) + getNavBarHeight();
|
||||||
|
} else {
|
||||||
|
// keyboard probably just became hidden
|
||||||
|
mViewGroupLayoutParams.height = usableHeightSansKeyboard;
|
||||||
|
}
|
||||||
|
mChildOfContent.requestLayout();
|
||||||
|
mUsableHeightPrevious = usableHeightNow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getNavBarHeight() {
|
||||||
|
return mNavBarHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int computeUsableHeight() {
|
||||||
|
Rect r = new Rect();
|
||||||
|
mChildOfContent.getWindowVisibleDisplayFrame(r);
|
||||||
|
return (r.bottom - r.top);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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
|
||||||
@@ -129,6 +134,8 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
|
|
||||||
boolean mIsUsingBlackUI;
|
boolean mIsUsingBlackUI;
|
||||||
|
|
||||||
|
int mNavBarHeight;
|
||||||
|
|
||||||
final SoundPool mBellSoundPool = new SoundPool.Builder().setMaxStreams(1).setAudioAttributes(
|
final SoundPool mBellSoundPool = new SoundPool.Builder().setMaxStreams(1).setAudioAttributes(
|
||||||
new AudioAttributes.Builder().setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
|
new AudioAttributes.Builder().setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
|
||||||
.setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION).build()).build();
|
.setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION).build()).build();
|
||||||
@@ -209,8 +216,19 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
|
|
||||||
super.onCreate(bundle);
|
super.onCreate(bundle);
|
||||||
|
|
||||||
|
|
||||||
setContentView(R.layout.drawer_layout);
|
setContentView(R.layout.drawer_layout);
|
||||||
|
|
||||||
|
View content = findViewById(android.R.id.content);
|
||||||
|
content.setOnApplyWindowInsetsListener((v, insets) -> {
|
||||||
|
mNavBarHeight = insets.getSystemWindowInsetBottom();
|
||||||
|
return insets;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (mSettings.isUsingFullScreen()) {
|
||||||
|
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||||
|
}
|
||||||
|
|
||||||
if (mIsUsingBlackUI) {
|
if (mIsUsingBlackUI) {
|
||||||
findViewById(R.id.left_drawer).setBackgroundColor(
|
findViewById(R.id.left_drawer).setBackgroundColor(
|
||||||
getResources().getColor(android.R.color.background_dark)
|
getResources().getColor(android.R.color.background_dark)
|
||||||
@@ -251,6 +269,12 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
if (position == 0) {
|
if (position == 0) {
|
||||||
layout = mExtraKeysView = (ExtraKeysView) inflater.inflate(R.layout.extra_keys_main, collection, false);
|
layout = mExtraKeysView = (ExtraKeysView) inflater.inflate(R.layout.extra_keys_main, collection, false);
|
||||||
mExtraKeysView.reload(mSettings.mExtraKeys);
|
mExtraKeysView.reload(mSettings.mExtraKeys);
|
||||||
|
|
||||||
|
// apply extra keys fix if enabled in prefs
|
||||||
|
if (mSettings.isUsingFullScreen() && mSettings.isUsingFullScreenWorkAround()) {
|
||||||
|
FullScreenWorkAround.apply(TermuxActivity.this);
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
layout = inflater.inflate(R.layout.extra_keys_right, collection, false);
|
layout = inflater.inflate(R.layout.extra_keys_right, collection, false);
|
||||||
final EditText editText = layout.findViewById(R.id.text_input);
|
final EditText editText = layout.findViewById(R.id.text_input);
|
||||||
@@ -322,6 +346,30 @@ 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getNavBarHeight() {
|
||||||
|
return mNavBarHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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() {
|
||||||
@@ -604,7 +652,14 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
.setPositiveButton(android.R.string.ok, null).show();
|
.setPositiveButton(android.R.string.ok, null).show();
|
||||||
} else {
|
} else {
|
||||||
TerminalSession currentSession = getCurrentTermSession();
|
TerminalSession currentSession = getCurrentTermSession();
|
||||||
String workingDirectory = (currentSession == null) ? null : currentSession.getCwd();
|
|
||||||
|
String workingDirectory;
|
||||||
|
if (currentSession == null) {
|
||||||
|
workingDirectory = mSettings.mDefaultWorkingDir;
|
||||||
|
} else {
|
||||||
|
workingDirectory = currentSession.getCwd();
|
||||||
|
}
|
||||||
|
|
||||||
TerminalSession newSession = mTermService.createTermSession(null, null, workingDirectory, failSafe);
|
TerminalSession newSession = mTermService.createTermSession(null, null, workingDirectory, failSafe);
|
||||||
if (sessionName != null) {
|
if (sessionName != null) {
|
||||||
newSession.mSessionName = sessionName;
|
newSession.mSessionName = sessionName;
|
||||||
@@ -655,6 +710,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 +914,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 +931,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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ import java.util.Properties;
|
|||||||
|
|
||||||
import androidx.annotation.IntDef;
|
import androidx.annotation.IntDef;
|
||||||
|
|
||||||
|
import static com.termux.terminal.EmulatorDebug.LOG_TAG;
|
||||||
|
|
||||||
final class TermuxPreferences {
|
final class TermuxPreferences {
|
||||||
|
|
||||||
@IntDef({BELL_VIBRATE, BELL_BEEP, BELL_IGNORE})
|
@IntDef({BELL_VIBRATE, BELL_BEEP, BELL_IGNORE})
|
||||||
@@ -67,12 +69,16 @@ final class TermuxPreferences {
|
|||||||
private boolean mScreenAlwaysOn;
|
private boolean mScreenAlwaysOn;
|
||||||
private int mFontSize;
|
private int mFontSize;
|
||||||
|
|
||||||
|
private boolean mUseFullScreen;
|
||||||
|
private boolean mUseFullScreenWorkAround;
|
||||||
|
|
||||||
@AsciiBellBehaviour
|
@AsciiBellBehaviour
|
||||||
int mBellBehaviour = BELL_VIBRATE;
|
int mBellBehaviour = BELL_VIBRATE;
|
||||||
|
|
||||||
boolean mBackIsEscape;
|
boolean mBackIsEscape;
|
||||||
boolean mDisableVolumeVirtualKeys;
|
boolean mDisableVolumeVirtualKeys;
|
||||||
boolean mShowExtraKeys;
|
boolean mShowExtraKeys;
|
||||||
|
String mDefaultWorkingDir;
|
||||||
|
|
||||||
ExtraKeysInfos mExtraKeys;
|
ExtraKeysInfos mExtraKeys;
|
||||||
|
|
||||||
@@ -108,7 +114,7 @@ final class TermuxPreferences {
|
|||||||
} catch (NumberFormatException | ClassCastException e) {
|
} catch (NumberFormatException | ClassCastException e) {
|
||||||
mFontSize = defaultFontSize;
|
mFontSize = defaultFontSize;
|
||||||
}
|
}
|
||||||
mFontSize = clamp(mFontSize, MIN_FONTSIZE, MAX_FONTSIZE);
|
mFontSize = clamp(mFontSize, MIN_FONTSIZE, MAX_FONTSIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean toggleShowExtraKeys(Context context) {
|
boolean toggleShowExtraKeys(Context context) {
|
||||||
@@ -137,6 +143,14 @@ final class TermuxPreferences {
|
|||||||
return mUseDarkUI;
|
return mUseDarkUI;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean isUsingFullScreen() {
|
||||||
|
return mUseFullScreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isUsingFullScreenWorkAround() {
|
||||||
|
return mUseFullScreenWorkAround;
|
||||||
|
}
|
||||||
|
|
||||||
void setScreenAlwaysOn(Context context, boolean newValue) {
|
void setScreenAlwaysOn(Context context, boolean newValue) {
|
||||||
mScreenAlwaysOn = newValue;
|
mScreenAlwaysOn = newValue;
|
||||||
PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean(SCREEN_ALWAYS_ON_KEY, newValue).apply();
|
PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean(SCREEN_ALWAYS_ON_KEY, newValue).apply();
|
||||||
@@ -196,6 +210,17 @@ final class TermuxPreferences {
|
|||||||
mUseDarkUI = nightMode == Configuration.UI_MODE_NIGHT_YES;
|
mUseDarkUI = nightMode == Configuration.UI_MODE_NIGHT_YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mUseFullScreen = "true".equals(props.getProperty("fullscreen", "false").toLowerCase());
|
||||||
|
mUseFullScreenWorkAround = "true".equals(props.getProperty("use-fullscreen-workaround", "false").toLowerCase());
|
||||||
|
|
||||||
|
mDefaultWorkingDir = props.getProperty("default-working-directory", TermuxService.HOME_PATH);
|
||||||
|
File workDir = new File(mDefaultWorkingDir);
|
||||||
|
if (!workDir.exists() || !workDir.isDirectory()) {
|
||||||
|
// Fallback to home directory if user configured working directory is not exist
|
||||||
|
// or is a regular file.
|
||||||
|
mDefaultWorkingDir = TermuxService.HOME_PATH;
|
||||||
|
}
|
||||||
|
|
||||||
String defaultExtraKeys = "[[ESC, TAB, CTRL, ALT, {key: '-', popup: '|'}, DOWN, UP]]";
|
String defaultExtraKeys = "[[ESC, TAB, CTRL, ALT, {key: '-', popup: '|'}, DOWN, UP]]";
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ public class TermuxDocumentsProvider extends DocumentsProvider {
|
|||||||
row.add(Root.COLUMN_TITLE, applicationName);
|
row.add(Root.COLUMN_TITLE, applicationName);
|
||||||
row.add(Root.COLUMN_MIME_TYPES, ALL_MIME_TYPES);
|
row.add(Root.COLUMN_MIME_TYPES, ALL_MIME_TYPES);
|
||||||
row.add(Root.COLUMN_AVAILABLE_BYTES, BASE_DIR.getFreeSpace());
|
row.add(Root.COLUMN_AVAILABLE_BYTES, BASE_DIR.getFreeSpace());
|
||||||
row.add(Root.COLUMN_ICON, R.drawable.ic_launcher);
|
row.add(Root.COLUMN_ICON, R.mipmap.ic_launcher);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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 {
|
||||||
@@ -265,7 +262,7 @@ public class TermuxDocumentsProvider extends DocumentsProvider {
|
|||||||
row.add(Document.COLUMN_MIME_TYPE, mimeType);
|
row.add(Document.COLUMN_MIME_TYPE, mimeType);
|
||||||
row.add(Document.COLUMN_LAST_MODIFIED, file.lastModified());
|
row.add(Document.COLUMN_LAST_MODIFIED, file.lastModified());
|
||||||
row.add(Document.COLUMN_FLAGS, flags);
|
row.add(Document.COLUMN_FLAGS, flags);
|
||||||
row.add(Document.COLUMN_ICON, R.drawable.ic_launcher);
|
row.add(Document.COLUMN_ICON, R.mipmap.ic_launcher);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:height="108dp"
|
|
||||||
android:width="108dp"
|
|
||||||
android:viewportWidth="108"
|
|
||||||
android:viewportHeight="108">
|
|
||||||
|
|
||||||
<path
|
|
||||||
android:fillColor="#000000"
|
|
||||||
android:pathData="M18,54
|
|
||||||
A36,36 0 1,1 90,54
|
|
||||||
A36,36 0 1,1 18,54 Z" />
|
|
||||||
|
|
||||||
<!-- Keep in sync with adaptive ic_foreground.xml: -->
|
|
||||||
<path
|
|
||||||
android:fillColor="#FFFFFF"
|
|
||||||
android:pathData="M34,38
|
|
||||||
h6
|
|
||||||
l12,16
|
|
||||||
l-12,16
|
|
||||||
h-6
|
|
||||||
l12,-16
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<path
|
|
||||||
android:fillColor="#FFFFFF"
|
|
||||||
android:pathData="M56,66
|
|
||||||
h18
|
|
||||||
v4
|
|
||||||
h-18
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
|
|
||||||
</vector>
|
|
||||||
@@ -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"
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
5
app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@android:color/black"/>
|
||||||
|
<foreground android:drawable="@drawable/ic_foreground"/>
|
||||||
|
</adaptive-icon>
|
||||||
BIN
app/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
app/src/main/res/mipmap-hdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
@@ -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>
|
||||||
|
|||||||
BIN
art/ic_launcher2.png
Normal file
|
After Width: | Height: | Size: 9.9 KiB |
BIN
art/ic_launcher2_round.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
@@ -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'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
13
fastlane/metadata/android/en-US/full_description.txt
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
Termux is a terminal emulator application enhanced with a large set of command line utilities ported to Android OS. The main goal is to bring a Linux command line experience to users of mobile devices with no rooting or other special setup required.
|
||||||
|
|
||||||
|
* Enjoy the Bash and Zsh shells.
|
||||||
|
* Edit files with nano and vim.
|
||||||
|
* Access servers over SSH.
|
||||||
|
* Compile C/C++ code with clang.
|
||||||
|
* Use the Python console as a pocket calculator.
|
||||||
|
* Check out projects with Git and Subversion.
|
||||||
|
* Run text-based games with frotz.
|
||||||
|
|
||||||
|
At first start a small base system is being configured. The GNU Bash, Coreutils, Findutils and other core utilities are available out-of-box. Additionally, we provide more than 1000 other packages installable by using the 'pkg' utility which currently is a frontend for the 'apt' package manager. All provided software has been patched and compiled with Android NDK to provide max compatibility with Android OS.
|
||||||
|
|
||||||
|
To learn more about application usage tips and tricks, long-press anywhere on the terminal and select the Help menu option to access Termux Wiki. This resource is also accessible directly in a web browser: https://wiki.termux.com/wiki/Main_Page.
|
||||||
BIN
fastlane/metadata/android/en-US/images/icon.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
fastlane/metadata/android/en-US/images/phoneScreenshots/1.jpg
Normal file
|
After Width: | Height: | Size: 138 KiB |
BIN
fastlane/metadata/android/en-US/images/phoneScreenshots/2.jpg
Normal file
|
After Width: | Height: | Size: 118 KiB |
BIN
fastlane/metadata/android/en-US/images/phoneScreenshots/3.jpg
Normal file
|
After Width: | Height: | Size: 96 KiB |
BIN
fastlane/metadata/android/en-US/images/phoneScreenshots/4.jpg
Normal file
|
After Width: | Height: | Size: 158 KiB |
1
fastlane/metadata/android/en-US/short_description.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Terminal emulator app with a large set of command line utilities
|
||||||
@@ -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
|
||||||
|
|||||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -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.6.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
|
||||||
|
|||||||
@@ -1,77 +0,0 @@
|
|||||||
// Start https://raw.githubusercontent.com/nuuneoi/JCenter/master/installv1.gradle
|
|
||||||
group = publishedGroupId // Maven Group ID for the artifact
|
|
||||||
install {
|
|
||||||
repositories.mavenInstaller {
|
|
||||||
pom {
|
|
||||||
project {
|
|
||||||
packaging 'aar'
|
|
||||||
groupId publishedGroupId
|
|
||||||
artifactId artifact
|
|
||||||
|
|
||||||
name libraryName
|
|
||||||
description libraryDescription
|
|
||||||
url siteUrl
|
|
||||||
|
|
||||||
licenses {
|
|
||||||
license {
|
|
||||||
name 'GNU General Public License version 3'
|
|
||||||
url 'https://opensource.org/licenses/gpl-3.0.html'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
developers {
|
|
||||||
developer {
|
|
||||||
id 'fornwall'
|
|
||||||
name 'Fredrik Fornwall'
|
|
||||||
email 'fredrik@fornwall.net'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
scm {
|
|
||||||
connection gitUrl
|
|
||||||
developerConnection gitUrl
|
|
||||||
url siteUrl
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// End https://raw.githubusercontent.com/nuuneoi/JCenter/master/installv1.gradle
|
|
||||||
|
|
||||||
// Start https://raw.githubusercontent.com/nuuneoi/JCenter/master/bintrayv1.gradle
|
|
||||||
apply plugin: 'com.jfrog.bintray'
|
|
||||||
|
|
||||||
version = libraryVersion
|
|
||||||
|
|
||||||
task sourcesJar(type: Jar) {
|
|
||||||
classifier = 'sources'
|
|
||||||
from android.sourceSets.main.java.srcDirs
|
|
||||||
}
|
|
||||||
|
|
||||||
artifacts {
|
|
||||||
archives sourcesJar
|
|
||||||
}
|
|
||||||
|
|
||||||
bintray {
|
|
||||||
user = System.getenv('BINTRAY_USER')
|
|
||||||
key = System.getenv('BINTRAY_API_KEY')
|
|
||||||
|
|
||||||
configurations = ['archives']
|
|
||||||
pkg {
|
|
||||||
repo = 'maven'
|
|
||||||
name = bintrayName
|
|
||||||
userOrg = 'termux'
|
|
||||||
desc = libraryDescription
|
|
||||||
websiteUrl = siteUrl
|
|
||||||
vcsUrl = gitUrl
|
|
||||||
licenses = ['GPL-3.0']
|
|
||||||
publish = true
|
|
||||||
publicDownloadNumbers = true
|
|
||||||
version {
|
|
||||||
desc = libraryDescription
|
|
||||||
gpg {
|
|
||||||
sign = false //Determines whether to GPG sign the files. The default is false
|
|
||||||
// passphrase = properties.getProperty("bintray.gpg.password")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +1,5 @@
|
|||||||
plugins {
|
|
||||||
id "com.jfrog.bintray" version "1.7.3"
|
|
||||||
id "com.github.dcendents.android-maven" version "2.0"
|
|
||||||
}
|
|
||||||
|
|
||||||
apply plugin: 'com.android.library'
|
apply plugin: 'com.android.library'
|
||||||
|
apply plugin: 'maven-publish'
|
||||||
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 {
|
android {
|
||||||
compileSdkVersion project.properties.compileSdkVersion.toInteger()
|
compileSdkVersion project.properties.compileSdkVersion.toInteger()
|
||||||
@@ -52,6 +37,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 +50,29 @@ tasks.withType(Test) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
testImplementation 'junit:junit:4.13'
|
testImplementation 'junit:junit:4.13.1'
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: '../scripts/bintray-publish.gradle'
|
|
||||||
|
publishing {
|
||||||
|
publications {
|
||||||
|
bar(MavenPublication) {
|
||||||
|
groupId 'com.termux'
|
||||||
|
artifactId 'terminal-emulator'
|
||||||
|
version '0.106.1'
|
||||||
|
artifact("$buildDir/outputs/aar/terminal-emulator-release.aar")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
maven {
|
||||||
|
name = "GitHubPackages"
|
||||||
|
url = uri("https://maven.pkg.github.com/termux/termux-app")
|
||||||
|
|
||||||
|
credentials {
|
||||||
|
username = System.getenv("GH_USERNAME")
|
||||||
|
password = System.getenv("GH_TOKEN")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ public final class KeyHandler {
|
|||||||
public static final int KEYMOD_ALT = 0x80000000;
|
public static final int KEYMOD_ALT = 0x80000000;
|
||||||
public static final int KEYMOD_CTRL = 0x40000000;
|
public static final int KEYMOD_CTRL = 0x40000000;
|
||||||
public static final int KEYMOD_SHIFT = 0x20000000;
|
public static final int KEYMOD_SHIFT = 0x20000000;
|
||||||
|
public static final int KEYMOD_NUM_LOCK = 0x10000000;
|
||||||
|
|
||||||
private static final Map<String, Integer> TERMCAP_TO_KEYCODE = new HashMap<>();
|
private static final Map<String, Integer> TERMCAP_TO_KEYCODE = new HashMap<>();
|
||||||
|
|
||||||
@@ -145,10 +146,16 @@ public final class KeyHandler {
|
|||||||
keyMod |= KEYMOD_ALT;
|
keyMod |= KEYMOD_ALT;
|
||||||
keyCode &= ~KEYMOD_ALT;
|
keyCode &= ~KEYMOD_ALT;
|
||||||
}
|
}
|
||||||
|
if ((keyCode & KEYMOD_NUM_LOCK) != 0) {
|
||||||
|
keyMod |= KEYMOD_NUM_LOCK;
|
||||||
|
keyCode &= ~KEYMOD_NUM_LOCK;
|
||||||
|
}
|
||||||
return getCode(keyCode, keyMod, cursorKeysApplication, keypadApplication);
|
return getCode(keyCode, keyMod, cursorKeysApplication, keypadApplication);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getCode(int keyCode, int keyMode, boolean cursorApp, boolean keypadApplication) {
|
public static String getCode(int keyCode, int keyMode, boolean cursorApp, boolean keypadApplication) {
|
||||||
|
boolean numLockOn = (keyMode & KEYMOD_NUM_LOCK) != 0;
|
||||||
|
keyMode &= ~KEYMOD_NUM_LOCK;
|
||||||
switch (keyCode) {
|
switch (keyCode) {
|
||||||
case KEYCODE_DPAD_CENTER:
|
case KEYCODE_DPAD_CENTER:
|
||||||
return "\015";
|
return "\015";
|
||||||
@@ -228,8 +235,11 @@ public final class KeyHandler {
|
|||||||
// Just do what xterm and gnome-terminal does:
|
// Just do what xterm and gnome-terminal does:
|
||||||
return prefix + (((keyMode & KEYMOD_CTRL) == 0) ? "\u007F" : "\u0008");
|
return prefix + (((keyMode & KEYMOD_CTRL) == 0) ? "\u007F" : "\u0008");
|
||||||
case KEYCODE_NUM_LOCK:
|
case KEYCODE_NUM_LOCK:
|
||||||
return "\033OP";
|
if (keypadApplication) {
|
||||||
|
return "\033OP";
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
case KEYCODE_SPACE:
|
case KEYCODE_SPACE:
|
||||||
// If ctrl is not down, return null so that it goes through normal input processing (which may e.g. cause a
|
// If ctrl is not down, return null so that it goes through normal input processing (which may e.g. cause a
|
||||||
// combining accent to be written):
|
// combining accent to be written):
|
||||||
@@ -249,31 +259,81 @@ public final class KeyHandler {
|
|||||||
case KEYCODE_NUMPAD_COMMA:
|
case KEYCODE_NUMPAD_COMMA:
|
||||||
return ",";
|
return ",";
|
||||||
case KEYCODE_NUMPAD_DOT:
|
case KEYCODE_NUMPAD_DOT:
|
||||||
return keypadApplication ? "\033On" : ".";
|
if (numLockOn) {
|
||||||
|
return keypadApplication ? "\033On" : ".";
|
||||||
|
} else {
|
||||||
|
// DELETE
|
||||||
|
return transformForModifiers("\033[3", keyMode, '~');
|
||||||
|
}
|
||||||
case KEYCODE_NUMPAD_SUBTRACT:
|
case KEYCODE_NUMPAD_SUBTRACT:
|
||||||
return keypadApplication ? transformForModifiers("\033O", keyMode, 'm') : "-";
|
return keypadApplication ? transformForModifiers("\033O", keyMode, 'm') : "-";
|
||||||
case KEYCODE_NUMPAD_DIVIDE:
|
case KEYCODE_NUMPAD_DIVIDE:
|
||||||
return keypadApplication ? transformForModifiers("\033O", keyMode, 'o') : "/";
|
return keypadApplication ? transformForModifiers("\033O", keyMode, 'o') : "/";
|
||||||
case KEYCODE_NUMPAD_0:
|
case KEYCODE_NUMPAD_0:
|
||||||
return keypadApplication ? transformForModifiers("\033O", keyMode, 'p') : "0";
|
if (numLockOn) {
|
||||||
|
return keypadApplication ? transformForModifiers("\033O", keyMode, 'p') : "0";
|
||||||
|
} else {
|
||||||
|
// INSERT
|
||||||
|
return transformForModifiers("\033[2", keyMode, '~');
|
||||||
|
}
|
||||||
case KEYCODE_NUMPAD_1:
|
case KEYCODE_NUMPAD_1:
|
||||||
return keypadApplication ? transformForModifiers("\033O", keyMode, 'q') : "1";
|
if (numLockOn) {
|
||||||
|
return keypadApplication ? transformForModifiers("\033O", keyMode, 'q') : "1";
|
||||||
|
} else {
|
||||||
|
// END
|
||||||
|
return (keyMode == 0) ? (cursorApp ? "\033OF" : "\033[F") : transformForModifiers("\033[1", keyMode, 'F');
|
||||||
|
}
|
||||||
case KEYCODE_NUMPAD_2:
|
case KEYCODE_NUMPAD_2:
|
||||||
return keypadApplication ? transformForModifiers("\033O", keyMode, 'r') : "2";
|
if (numLockOn) {
|
||||||
|
return keypadApplication ? transformForModifiers("\033O", keyMode, 'r') : "2";
|
||||||
|
} else {
|
||||||
|
// DOWN
|
||||||
|
return (keyMode == 0) ? (cursorApp ? "\033OB" : "\033[B") : transformForModifiers("\033[1", keyMode, 'B');
|
||||||
|
}
|
||||||
case KEYCODE_NUMPAD_3:
|
case KEYCODE_NUMPAD_3:
|
||||||
return keypadApplication ? transformForModifiers("\033O", keyMode, 's') : "3";
|
if (numLockOn) {
|
||||||
|
return keypadApplication ? transformForModifiers("\033O", keyMode, 's') : "3";
|
||||||
|
} else {
|
||||||
|
// PGDN
|
||||||
|
return "\033[6~";
|
||||||
|
}
|
||||||
case KEYCODE_NUMPAD_4:
|
case KEYCODE_NUMPAD_4:
|
||||||
return keypadApplication ? transformForModifiers("\033O", keyMode, 't') : "4";
|
if (numLockOn) {
|
||||||
|
return keypadApplication ? transformForModifiers("\033O", keyMode, 't') : "4";
|
||||||
|
} else {
|
||||||
|
// LEFT
|
||||||
|
return (keyMode == 0) ? (cursorApp ? "\033OD" : "\033[D") : transformForModifiers("\033[1", keyMode, 'D');
|
||||||
|
}
|
||||||
case KEYCODE_NUMPAD_5:
|
case KEYCODE_NUMPAD_5:
|
||||||
return keypadApplication ? transformForModifiers("\033O", keyMode, 'u') : "5";
|
return keypadApplication ? transformForModifiers("\033O", keyMode, 'u') : "5";
|
||||||
case KEYCODE_NUMPAD_6:
|
case KEYCODE_NUMPAD_6:
|
||||||
return keypadApplication ? transformForModifiers("\033O", keyMode, 'v') : "6";
|
if (numLockOn) {
|
||||||
|
return keypadApplication ? transformForModifiers("\033O", keyMode, 'v') : "6";
|
||||||
|
} else {
|
||||||
|
// RIGHT
|
||||||
|
return (keyMode == 0) ? (cursorApp ? "\033OC" : "\033[C") : transformForModifiers("\033[1", keyMode, 'C');
|
||||||
|
}
|
||||||
case KEYCODE_NUMPAD_7:
|
case KEYCODE_NUMPAD_7:
|
||||||
return keypadApplication ? transformForModifiers("\033O", keyMode, 'w') : "7";
|
if (numLockOn) {
|
||||||
|
return keypadApplication ? transformForModifiers("\033O", keyMode, 'w') : "7";
|
||||||
|
} else {
|
||||||
|
// HOME
|
||||||
|
return (keyMode == 0) ? (cursorApp ? "\033OH" : "\033[H") : transformForModifiers("\033[1", keyMode, 'H');
|
||||||
|
}
|
||||||
case KEYCODE_NUMPAD_8:
|
case KEYCODE_NUMPAD_8:
|
||||||
return keypadApplication ? transformForModifiers("\033O", keyMode, 'x') : "8";
|
if (numLockOn) {
|
||||||
|
return keypadApplication ? transformForModifiers("\033O", keyMode, 'x') : "8";
|
||||||
|
} else {
|
||||||
|
// UP
|
||||||
|
return (keyMode == 0) ? (cursorApp ? "\033OA" : "\033[A") : transformForModifiers("\033[1", keyMode, 'A');
|
||||||
|
}
|
||||||
case KEYCODE_NUMPAD_9:
|
case KEYCODE_NUMPAD_9:
|
||||||
return keypadApplication ? transformForModifiers("\033O", keyMode, 'y') : "9";
|
if (numLockOn) {
|
||||||
|
return keypadApplication ? transformForModifiers("\033O", keyMode, 'y') : "9";
|
||||||
|
} else {
|
||||||
|
// PGUP
|
||||||
|
return "\033[5~";
|
||||||
|
}
|
||||||
case KEYCODE_NUMPAD_EQUALS:
|
case KEYCODE_NUMPAD_EQUALS:
|
||||||
return keypadApplication ? transformForModifiers("\033O", keyMode, 'X') : "=";
|
return keypadApplication ? transformForModifiers("\033O", keyMode, 'X') : "=";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -174,18 +174,30 @@ 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, KeyHandler.KEYMOD_NUM_LOCK, false, false));
|
||||||
assertKeysEquals("1", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_1, 0, false, false));
|
assertKeysEquals("1", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_1, KeyHandler.KEYMOD_NUM_LOCK, false, false));
|
||||||
assertKeysEquals("2", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_2, 0, false, false));
|
assertKeysEquals("2", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_2, KeyHandler.KEYMOD_NUM_LOCK, false, false));
|
||||||
assertKeysEquals("3", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_3, 0, false, false));
|
assertKeysEquals("3", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_3, KeyHandler.KEYMOD_NUM_LOCK, false, false));
|
||||||
assertKeysEquals("4", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_4, 0, false, false));
|
assertKeysEquals("4", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_4, KeyHandler.KEYMOD_NUM_LOCK, false, false));
|
||||||
|
assertKeysEquals("5", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_5, KeyHandler.KEYMOD_NUM_LOCK, false, false));
|
||||||
|
assertKeysEquals("6", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_6, KeyHandler.KEYMOD_NUM_LOCK, false, false));
|
||||||
|
assertKeysEquals("7", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_7, KeyHandler.KEYMOD_NUM_LOCK, false, false));
|
||||||
|
assertKeysEquals("8", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_8, KeyHandler.KEYMOD_NUM_LOCK, false, false));
|
||||||
|
assertKeysEquals("9", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_9, KeyHandler.KEYMOD_NUM_LOCK, false, false));
|
||||||
|
assertKeysEquals(",", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_COMMA, KeyHandler.KEYMOD_NUM_LOCK, false, false));
|
||||||
|
assertKeysEquals(".", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_DOT, KeyHandler.KEYMOD_NUM_LOCK, false, false));
|
||||||
|
|
||||||
|
assertKeysEquals("\033[2~", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_0, 0, false, false));
|
||||||
|
assertKeysEquals("\033[F", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_1, 0, false, false));
|
||||||
|
assertKeysEquals("\033[B", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_2, 0, false, false));
|
||||||
|
assertKeysEquals("\033[6~", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_3, 0, false, false));
|
||||||
|
assertKeysEquals("\033[D", 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("\033[C", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_6, 0, false, false));
|
||||||
assertKeysEquals("7", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_7, 0, false, false));
|
assertKeysEquals("\033[H", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_7, 0, false, false));
|
||||||
assertKeysEquals("8", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_8, 0, false, false));
|
assertKeysEquals("\033[A", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_8, 0, false, false));
|
||||||
assertKeysEquals("9", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_9, 0, false, false));
|
assertKeysEquals("\033[5~", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_9, 0, false, false));
|
||||||
assertKeysEquals(",", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_COMMA, 0, false, false));
|
assertKeysEquals("\033[3~", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_DOT, 0, false, false));
|
||||||
assertKeysEquals(".", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_DOT, 0, false, false));
|
}
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ public class ResizeTest extends TerminalTestCase {
|
|||||||
enterString("\033[2J");
|
enterString("\033[2J");
|
||||||
for (int r = 0; r < rows; r++) {
|
for (int r = 0; r < rows; r++) {
|
||||||
for (int c = 0; c < cols; c++) {
|
for (int c = 0; c < cols; c++) {
|
||||||
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));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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", " ");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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, 'A');
|
assertWidthIs(2, 'A');
|
||||||
@@ -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 ⁠).
|
// (released in 2002) as U+2060 WORD JOINER (HTML ⁠).
|
||||||
// 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).
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,5 @@
|
|||||||
plugins {
|
|
||||||
id "com.jfrog.bintray" version "1.7.3"
|
|
||||||
id "com.github.dcendents.android-maven" version "2.0"
|
|
||||||
}
|
|
||||||
|
|
||||||
apply plugin: 'com.android.library'
|
apply plugin: 'com.android.library'
|
||||||
|
apply plugin: 'maven-publish'
|
||||||
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 {
|
android {
|
||||||
compileSdkVersion project.properties.compileSdkVersion.toInteger()
|
compileSdkVersion project.properties.compileSdkVersion.toInteger()
|
||||||
@@ -44,7 +29,28 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
testImplementation 'junit:junit:4.13'
|
testImplementation 'junit:junit:4.13.1'
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: '../scripts/bintray-publish.gradle'
|
publishing {
|
||||||
|
publications {
|
||||||
|
bar(MavenPublication) {
|
||||||
|
groupId 'com.termux'
|
||||||
|
artifactId 'terminal-view'
|
||||||
|
version '0.106.1'
|
||||||
|
artifact("$buildDir/outputs/aar/terminal-view-release.aar")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
maven {
|
||||||
|
name = "GitHubPackages"
|
||||||
|
url = uri("https://maven.pkg.github.com/termux/termux-app")
|
||||||
|
|
||||||
|
credentials {
|
||||||
|
username = System.getenv("GH_USERNAME")
|
||||||
|
password = System.getenv("GH_TOKEN")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -252,20 +255,7 @@ public final class TerminalView extends View {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
|
public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
|
||||||
File propsFile = new File(getContext().getFilesDir() + "/home/.termux/termux.properties");
|
Properties props = getProperties();
|
||||||
if (!propsFile.exists())
|
|
||||||
propsFile = new File(getContext().getFilesDir() + "/home/.config/termux/termux.properties");
|
|
||||||
|
|
||||||
Properties props = new Properties();
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (props.getProperty("enforce-char-based-input", "false").equals("true")) {
|
if (props.getProperty("enforce-char-based-input", "false").equals("true")) {
|
||||||
// Some keyboards seems do not reset the internal state on TYPE_NULL.
|
// Some keyboards seems do not reset the internal state on TYPE_NULL.
|
||||||
@@ -549,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) {
|
||||||
@@ -564,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);
|
||||||
}
|
}
|
||||||
@@ -594,6 +591,7 @@ public final class TerminalView extends View {
|
|||||||
if (controlDown) keyMod |= KeyHandler.KEYMOD_CTRL;
|
if (controlDown) keyMod |= KeyHandler.KEYMOD_CTRL;
|
||||||
if (event.isAltPressed() || leftAltDown) keyMod |= KeyHandler.KEYMOD_ALT;
|
if (event.isAltPressed() || leftAltDown) keyMod |= KeyHandler.KEYMOD_ALT;
|
||||||
if (event.isShiftPressed()) keyMod |= KeyHandler.KEYMOD_SHIFT;
|
if (event.isShiftPressed()) keyMod |= KeyHandler.KEYMOD_SHIFT;
|
||||||
|
if (event.isNumLockOn()) keyMod |= KeyHandler.KEYMOD_NUM_LOCK;
|
||||||
if (!event.isFunctionPressed() && handleKeyCode(keyCode, keyMod)) {
|
if (!event.isFunctionPressed() && handleKeyCode(keyCode, keyMod)) {
|
||||||
if (LOG_KEY_EVENTS) Log.i(EmulatorDebug.LOG_TAG, "handleKeyCode() took key event");
|
if (LOG_KEY_EVENTS) Log.i(EmulatorDebug.LOG_TAG, "handleKeyCode() took key event");
|
||||||
return true;
|
return true;
|
||||||
@@ -1194,7 +1192,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);
|
||||||
@@ -1540,4 +1538,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("");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||