Compare commits
72 Commits
delete-rel
...
master
| Author | SHA1 | Date |
|---|---|---|
|
|
53f75a8da3 | |
|
|
bcb61c387c | |
|
|
c6ec1cb64f | |
|
|
382c9708b3 | |
|
|
edd44d2ce9 | |
|
|
4183ffa122 | |
|
|
8aca6dbbf4 | |
|
|
1812a405ee | |
|
|
338fb66e5c | |
|
|
f852dca0ef | |
|
|
8174a995ae | |
|
|
7bceab88e2 | |
|
|
2c1bd650b0 | |
|
|
4a9ad910dd | |
|
|
9d2ac14557 | |
|
|
c6e2ca7fde | |
|
|
da3a0ac4e2 | |
|
|
4de0caac5b | |
|
|
bc321d0a7c | |
|
|
9ee1c9d5ad | |
|
|
a988383e01 | |
|
|
d2cd6ac2e5 | |
|
|
52da00e5c2 | |
|
|
b0e1dbc3da | |
|
|
078eea2b74 | |
|
|
fb01127ff2 | |
|
|
b84dc703e8 | |
|
|
8be53336c3 | |
|
|
d90e3fbca1 | |
|
|
245158ceb9 | |
|
|
6c00f1fc61 | |
|
|
03142590ff | |
|
|
4443b657bf | |
|
|
a8a69c6d80 | |
|
|
438cd73fff | |
|
|
36d811ea7d | |
|
|
c2d57f2ed8 | |
|
|
661c37501f | |
|
|
f80b46487d | |
|
|
a5de3d3a2b | |
|
|
57e4ef456b | |
|
|
d90be9cd50 | |
|
|
062c9771a9 | |
|
|
2f40df91e5 | |
|
|
755b752a95 | |
|
|
67f4891580 | |
|
|
7b19cd2f5a | |
|
|
882da34fcd | |
|
|
8e3a8980a8 | |
|
|
e4385832b7 | |
|
|
3b5018b4c7 | |
|
|
c84d4804c8 | |
|
|
6727bbecc4 | |
|
|
e27f9fa979 | |
|
|
e2f0edf4d2 | |
|
|
c5b69975e1 | |
|
|
cce78cc274 | |
|
|
32cd8a9384 | |
|
|
eef5ac43a7 | |
|
|
55cdef01e7 | |
|
|
7c262b8d99 | |
|
|
06230f95df | |
|
|
9d308c2331 | |
|
|
11d8e4ff8f | |
|
|
66a9495d91 | |
|
|
33295decbb | |
|
|
ba1fb850bf | |
|
|
1240c5ca47 | |
|
|
c1dca29076 | |
|
|
93eafffb90 | |
|
|
9b274f9a0d | |
|
|
b800f1cc81 |
|
|
@ -4,3 +4,6 @@ updates:
|
|||
directory: /
|
||||
schedule:
|
||||
interval: daily
|
||||
commit-message:
|
||||
# Prefix all commit messages with "Changed: "
|
||||
prefix: "Changed"
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ on:
|
|||
- published
|
||||
|
||||
jobs:
|
||||
build:
|
||||
attach-apks:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
|
|
@ -17,19 +17,27 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Clone repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ env.GITHUB_REF }}
|
||||
|
||||
- name: Build
|
||||
- name: Build and attach APKs to release
|
||||
shell: bash {0}
|
||||
env:
|
||||
PACKAGE_VARIANT: ${{ matrix.package_variant }}
|
||||
run: |
|
||||
exit_on_error() {
|
||||
echo "$1"
|
||||
echo "Deleting '$RELEASE_VERSION_NAME' release and '$GITHUB_REF' tag"
|
||||
hub release delete "$RELEASE_VERSION_NAME"
|
||||
git push --delete origin "$GITHUB_REF"
|
||||
exit 1
|
||||
}
|
||||
|
||||
echo "Setting vars"
|
||||
RELEASE_VERSION_NAME="${GITHUB_REF/refs\/tags\//}"
|
||||
if ! printf "%s" "${RELEASE_VERSION_NAME/v/}" | grep -qP '^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$'; then
|
||||
echo "The versionName '${RELEASE_VERSION_NAME/v/}' is not a valid version as per semantic version '2.0.0' spec in the format 'major.minor.patch(-prerelease)(+buildmetadata)'. https://semver.org/spec/v2.0.0.html."
|
||||
exit 1
|
||||
exit_on_error "The versionName '${RELEASE_VERSION_NAME/v/}' is not a valid version as per semantic version '2.0.0' spec in the format 'major.minor.patch(-prerelease)(+buildmetadata)'. https://semver.org/spec/v2.0.0.html."
|
||||
fi
|
||||
|
||||
APK_DIR_PATH="./app/build/outputs/apk/debug"
|
||||
|
|
@ -39,35 +47,38 @@ jobs:
|
|||
echo "Building APKs for 'APK_VERSION_TAG' release"
|
||||
export TERMUX_APK_VERSION_TAG="$APK_VERSION_TAG" # Used by app/build.gradle
|
||||
export TERMUX_PACKAGE_VARIANT="${{ env.PACKAGE_VARIANT }}" # Used by app/build.gradle
|
||||
./gradlew assembleDebug
|
||||
if ! ./gradlew assembleDebug; then
|
||||
exit_on_error "Build failed for '$APK_VERSION_TAG' release."
|
||||
fi
|
||||
|
||||
echo "Validating APKs"
|
||||
for abi in universal arm64-v8a armeabi-v7a x86_64 x86; do
|
||||
if ! test -f "$APK_DIR_PATH/${APK_BASENAME_PREFIX}_$abi.apk"; then
|
||||
files_found="$(ls "$APK_DIR_PATH")"
|
||||
echo "Failed to find built APK at '$APK_DIR_PATH/${APK_BASENAME_PREFIX}_$abi.apk'. Files found: "$'\n'"$files_found"
|
||||
exit 1
|
||||
exit_on_error "Failed to find built APK at '$APK_DIR_PATH/${APK_BASENAME_PREFIX}_$abi.apk'. Files found: "$'\n'"$files_found"
|
||||
fi
|
||||
done
|
||||
- name: Upload APKs to GitHub artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ matrix.package_variant }}
|
||||
path: ./app/build/outputs/apk/debug/*.apk
|
||||
upload:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
- name: Download artifact
|
||||
uses: actions/download-artifact@v3
|
||||
- name: Upload APKs to release
|
||||
uses: termux/upload-release-action@v4.1.0
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
file: "**/*.apk"
|
||||
file_glob: true
|
||||
release_name:
|
||||
tag: ${{ github.event.release.tag_name }}
|
||||
checksums: sha256,sha512,md5
|
||||
|
||||
echo "Generating sha25sums file"
|
||||
if ! (cd "$APK_DIR_PATH"; sha256sum \
|
||||
"${APK_BASENAME_PREFIX}_universal.apk" \
|
||||
"${APK_BASENAME_PREFIX}_arm64-v8a.apk" \
|
||||
"${APK_BASENAME_PREFIX}_armeabi-v7a.apk" \
|
||||
"${APK_BASENAME_PREFIX}_x86_64.apk" \
|
||||
"${APK_BASENAME_PREFIX}_x86.apk" \
|
||||
> "${APK_BASENAME_PREFIX}_sha256sums"); then
|
||||
exit_on_error "Generate sha25sums failed for '$APK_VERSION_TAG' release."
|
||||
fi
|
||||
|
||||
echo "Attaching APKs to github release"
|
||||
if ! hub release edit \
|
||||
-m "" \
|
||||
-a "$APK_DIR_PATH/${APK_BASENAME_PREFIX}_universal.apk" \
|
||||
-a "$APK_DIR_PATH/${APK_BASENAME_PREFIX}_arm64-v8a.apk" \
|
||||
-a "$APK_DIR_PATH/${APK_BASENAME_PREFIX}_armeabi-v7a.apk" \
|
||||
-a "$APK_DIR_PATH/${APK_BASENAME_PREFIX}_x86_64.apk" \
|
||||
-a "$APK_DIR_PATH/${APK_BASENAME_PREFIX}_x86.apk" \
|
||||
-a "$APK_DIR_PATH/${APK_BASENAME_PREFIX}_sha256sums" \
|
||||
"$RELEASE_VERSION_NAME"; then
|
||||
exit_on_error "Attach APKs to release failed for '$APK_VERSION_TAG' release."
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ on:
|
|||
push:
|
||||
branches:
|
||||
- master
|
||||
- 'github-releases/**'
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
|
|
@ -18,7 +19,13 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Clone repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup java 17
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '17'
|
||||
|
||||
- name: Build APKs
|
||||
shell: bash {0}
|
||||
|
|
@ -78,7 +85,7 @@ jobs:
|
|||
fi
|
||||
|
||||
- name: Attach universal APK file
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: ${{ env.APK_BASENAME_PREFIX }}_universal
|
||||
path: |
|
||||
|
|
@ -86,7 +93,7 @@ jobs:
|
|||
${{ env.APK_DIR_PATH }}/output-metadata.json
|
||||
|
||||
- name: Attach arm64-v8a APK file
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: ${{ env.APK_BASENAME_PREFIX }}_arm64-v8a
|
||||
path: |
|
||||
|
|
@ -94,7 +101,7 @@ jobs:
|
|||
${{ env.APK_DIR_PATH }}/output-metadata.json
|
||||
|
||||
- name: Attach armeabi-v7a APK file
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: ${{ env.APK_BASENAME_PREFIX }}_armeabi-v7a
|
||||
path: |
|
||||
|
|
@ -102,7 +109,7 @@ jobs:
|
|||
${{ env.APK_DIR_PATH }}/output-metadata.json
|
||||
|
||||
- name: Attach x86_64 APK file
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: ${{ env.APK_BASENAME_PREFIX }}_x86_64
|
||||
path: |
|
||||
|
|
@ -110,7 +117,7 @@ jobs:
|
|||
${{ env.APK_DIR_PATH }}/output-metadata.json
|
||||
|
||||
- name: Attach x86 APK file
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: ${{ env.APK_BASENAME_PREFIX }}_x86
|
||||
path: |
|
||||
|
|
@ -118,7 +125,7 @@ jobs:
|
|||
${{ env.APK_DIR_PATH }}/output-metadata.json
|
||||
|
||||
- name: Attach sha256sums file
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: ${{ env.APK_BASENAME_PREFIX }}_sha256sums
|
||||
path: |
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
name: Automatic Dependency Submission
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ 'master' ]
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
dependency-submission:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v6
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: 17
|
||||
- name: Generate and submit dependency graph
|
||||
uses: gradle/actions/dependency-submission@v5
|
||||
|
|
@ -15,5 +15,5 @@ jobs:
|
|||
name: "Validation"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: gradle/wrapper-validation-action@v1
|
||||
- uses: actions/checkout@v6
|
||||
- uses: gradle/actions/wrapper-validation@5
|
||||
|
|
|
|||
|
|
@ -15,7 +15,12 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Clone repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v6
|
||||
- name: Setup java 17
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '17'
|
||||
- name: Execute tests
|
||||
run: |
|
||||
./gradlew test
|
||||
|
|
|
|||
|
|
@ -24,6 +24,10 @@ local.properties
|
|||
.idea/
|
||||
*.iml
|
||||
|
||||
# Vim
|
||||
*.swo
|
||||
*.swp
|
||||
|
||||
# OS-specific files
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
|
|
|
|||
134
README.md
134
README.md
|
|
@ -13,26 +13,23 @@ Note that this repository is for the app itself (the user interface and the term
|
|||
|
||||
Quick how-to about Termux package management is available at [Package Management](https://github.com/termux/termux-packages/wiki/Package-Management). It also has info on how to fix **`repository is under maintenance or down`** errors when running `apt` or `pkg` commands.
|
||||
|
||||
***
|
||||
|
||||
**NOTICE: Termux is broken on Android 12.** Android OS will kill any (phantom) processes greater than 32 (limit is for all apps combined) and also kill any processes using excessive CPU. You may get `[Process completed (signal 9) - press Enter]` message in the terminal without actually exiting the shell process yourself. Check the related issue [#2366](https://github.com/termux/termux-app/issues/2366), [issue tracker](https://issuetracker.google.com/u/1/issues/205156966), [phantom cached and empty processes docs](https://github.com/agnostic-apollo/Android-Docs/blob/master/en/docs/apps/processes/phantom-cached-and-empty-processes.md) and [this TLDR comment](https://github.com/termux/termux-app/issues/2366#issuecomment-1237468220) on how to disable trimming of phantom and excessive cpu usage processes. A proper docs page will be added later. An option to disable the killing should be available in Android 12L or 13, so upgrade at your own risk if you are on Android 11, specially if you are not rooted.
|
||||
**We are looking for Termux Android application maintainers.**
|
||||
|
||||
***
|
||||
|
||||
**@termux is looking for Termux Application maintainers for implementing new features, fixing bugs and reviewing pull requests since the current one (@fornwall) is inactive.**
|
||||
|
||||
Issue https://github.com/termux/termux-app/issues/1072 needs extra attention.
|
||||
**NOTICE: Termux may be unstable on Android 12+.** Android OS will kill any (phantom) processes greater than 32 (limit is for all apps combined) and also kill any processes using excessive CPU. You may get `[Process completed (signal 9) - press Enter]` message in the terminal without actually exiting the shell process yourself. Check the related issue [#2366](https://github.com/termux/termux-app/issues/2366), [issue tracker](https://issuetracker.google.com/u/1/issues/205156966), [phantom cached and empty processes docs](https://github.com/agnostic-apollo/Android-Docs/blob/master/en/docs/apps/processes/phantom-cached-and-empty-processes.md) and [this TLDR comment](https://github.com/termux/termux-app/issues/2366#issuecomment-1237468220) on how to disable trimming of phantom and excessive cpu usage processes. A proper docs page will be added later. An option to disable the killing should be available in Android 12L or 13, so upgrade at your own risk if you are on Android 11, specially if you are not rooted.
|
||||
|
||||
***
|
||||
|
||||
## Contents
|
||||
- [Termux App and Plugins](#Termux-App-and-Plugins)
|
||||
- [Installation](#Installation)
|
||||
- [Uninstallation](#Uninstallation)
|
||||
- [Important Links](#Important-Links)
|
||||
- [Debugging](#Debugging)
|
||||
- [For Maintainers and Contributors](#For-Maintainers-and-Contributors)
|
||||
- [Forking](#Forking)
|
||||
- [Termux App and Plugins](#termux-app-and-plugins)
|
||||
- [Installation](#installation)
|
||||
- [Uninstallation](#uninstallation)
|
||||
- [Important Links](#important-links)
|
||||
- [Debugging](#debugging)
|
||||
- [For Maintainers and Contributors](#for-maintainers-and-contributors)
|
||||
- [Forking](#forking)
|
||||
- [Sponsors and Funders](#sponsors-and-funders)
|
||||
##
|
||||
|
||||
|
||||
|
|
@ -53,15 +50,17 @@ The core [Termux](https://github.com/termux/termux-app) app comes with the follo
|
|||
|
||||
## Installation
|
||||
|
||||
Latest version is `v0.118.0`.
|
||||
Latest version is `v0.118.3`.
|
||||
|
||||
**NOTICE: It is highly recommended that you update to `v0.118.0` or higher ASAP for various bug fixes, including a critical world-readable vulnerability reported at https://termux.github.io/general/2022/02/15/termux-apps-vulnerability-disclosures.html. Also reminding [again](https://www.reddit.com/r/termux/comments/pkujfa/important_deprecation_notice_for_google_play) to users who have installed termux apps from google playstore that playstore builds are [deprecated](#google-play-store-deprecated) and no longer supported. It is recommended that you shift to F-Droid or GitHub releases.**
|
||||
**NOTICE: It is highly recommended that you update to `v0.118.0` or higher ASAP for various bug fixes, including a critical world-readable vulnerability reported [here](https://termux.github.io/general/2022/02/15/termux-apps-vulnerability-disclosures.html). See [below](#google-play-store-experimental-branch) for information regarding Termux on Google Play.**
|
||||
|
||||
Termux can be obtained through various sources listed below for **only** Android `>= 7`. Support was dropped for Android `5` and `6` on [2020-01-01](https://www.reddit.com/r/termux/comments/dnzdbs/end_of_android56_support_on_20200101/) at `v0.83`, old builds are available on [archive.org](https://archive.org/details/termux-repositories-legacy).
|
||||
Termux can be obtained through various sources listed below for **only** Android `>= 7` with full support for apps and packages.
|
||||
|
||||
Support for both app and packages was dropped for Android `5` and `6` on [2020-01-01](https://www.reddit.com/r/termux/comments/dnzdbs/end_of_android56_support_on_20200101/) at `v0.83`, however it was re-added just for the app *without any support for package updates* on [2022-05-24](https://github.com/termux/termux-app/pull/2740) via the [GitHub](#github) sources. Check [here](https://github.com/termux/termux-app/wiki/Termux-on-android-5-or-6) for the details.
|
||||
|
||||
The APK files of different sources are signed with different signature keys. The `Termux` app and all its plugins use the same [`sharedUserId`](https://developer.android.com/guide/topics/manifest/manifest-element) `com.termux` and so all their APKs installed on a device must have been signed with the same signature key to work together and so they must all be installed from the same source. Do not attempt to mix them together, i.e do not try to install an app or plugin from `F-Droid` and another one from a different source like `GitHub`. Android Package Manager will also normally not allow installation of APKs with different signatures and you will get errors on installation like `App not installed`, `Failed to install due to an unknown error`, `INSTALL_FAILED_UPDATE_INCOMPATIBLE`, `INSTALL_FAILED_SHARED_USER_INCOMPATIBLE`, `signatures do not match previously installed version`, etc. This restriction can be bypassed with root or with custom roms.
|
||||
|
||||
If you wish to install from a different source, then you must **uninstall any and all existing Termux or its plugin app APKs** from your device first, then install all new APKs from the same new source. Check [Uninstallation](#Uninstallation) section for details. You may also want to consider [Backing up Termux](https://wiki.termux.com/wiki/Backing_up_Termux) before the uninstallation so that you can restore it after re-installing from Termux different source.
|
||||
If you wish to install from a different source, then you must **uninstall any and all existing Termux or its plugin app APKs** from your device first, then install all new APKs from the same new source. Check [Uninstallation](#uninstallation) section for details. You may also want to consider [Backing up Termux](https://wiki.termux.com/wiki/Backing_up_Termux) before the uninstallation so that you can restore it after re-installing from Termux different source.
|
||||
|
||||
In the following paragraphs, *"bootstrap"* refers to the minimal packages that are shipped with the `termux-app` itself to start a working shell environment. Its zips are built and released [here](https://github.com/termux/termux-packages/releases).
|
||||
|
||||
|
|
@ -79,7 +78,7 @@ Only a universal APK is released, which will work on all supported architectures
|
|||
|
||||
### GitHub
|
||||
|
||||
Termux application can be obtained on `GitHub` either from [`GitHub Releases`](https://github.com/termux/termux-app/releases) for version `>= 0.118.0` or from [`GitHub Build`](https://github.com/termux/termux-app/actions/workflows/debug_build.yml) action workflows.
|
||||
Termux application can be obtained on `GitHub` either from [`GitHub Releases`](https://github.com/termux/termux-app/releases) for version `>= 0.118.0` or from [`GitHub Build Action`](https://github.com/termux/termux-app/actions/workflows/debug_build.yml?query=branch%3Amaster+event%3Apush) workflows. **For android `>= 7`, only install `apt-android-7` variants. For android `5` and `6`, only install `apt-android-5` variants.**
|
||||
|
||||
The APKs for `GitHub Releases` will be listed under `Assets` drop-down of a release. These are automatically attached when a new version is released.
|
||||
|
||||
|
|
@ -93,7 +92,9 @@ Both universal and architecture specific APKs are released. The APK and bootstra
|
|||
|
||||
The [test key](https://github.com/termux/termux-app/blob/master/app/testkey_untrusted.jks) shall not be used to impersonate @termux and can't be used for this anyway. This key is not trusted by us and it is quite easy to detect its use in user generated content.
|
||||
|
||||
Keystore information:
|
||||
<details>
|
||||
<summary>Keystore information</summary>
|
||||
|
||||
```
|
||||
Alias name: alias
|
||||
Creation date: Oct 4, 2019
|
||||
|
|
@ -112,51 +113,21 @@ Subject Public Key Algorithm: 2048-bit RSA key
|
|||
Version: 3
|
||||
```
|
||||
|
||||
### Google Play Store **(Deprecated)**
|
||||
|
||||
**Termux and its plugins are no longer updated on [Google Play Store](https://play.google.com/store/apps/details?id=com.termux) due to [android 10 issues](https://github.com/termux/termux-packages/wiki/Termux-and-Android-10) and have been deprecated.** The last version released for Android `>= 7` was `v0.101`. **It is highly recommended to not install Termux apps from Play Store any more.**
|
||||
|
||||
There are plans for **unpublishing** the Termux app and all its plugins on Play Store soon so that new users cannot install it and for **disabling** the Termux apps with updates so that existing users **cannot continue using outdated versions**. You are encouraged to move to `F-Droid` or `GitHub` builds as soon as possible.
|
||||
|
||||
You **will not need to buy plugins again** if you bought them on Play Store. All plugins are free on `F-Droid` and `GitHub`.
|
||||
|
||||
You can backup all your data under `$HOME/` and `$PREFIX/` before changing installation source, and then restore it afterwards, by following instructions at [Backing up Termux](https://wiki.termux.com/wiki/Backing_up_Termux) before the uninstallation.
|
||||
|
||||
There is currently no work being done to solve android `10` issues and *working* updates will not be resumed on Google Play Store any time soon. We will continue targeting sdk `28` for now. So there is not much point in staying on Play Store builds and waiting for updates to be resumed. If for some reason you don't want to move to `F-Droid` or `GitHub` sources for now, then at least check [Package Management](https://github.com/termux/termux-packages/wiki/Package-Management) to **change your mirror**, otherwise, you will get **`repository is under maintenance or down`** errors when running `apt` or `pkg` commands. After that, it is also **highly advisable** to run `pkg upgrade` command to update all packages to the latest available versions, or at least update `termux-tools` package with `pkg install termux-tools` command.
|
||||
|
||||
Note that by upgrading old packages to latest versions, like that of `python` may break your setups/scripts since they may not be compatible anymore. Moreover, you will not be able to downgrade the package versions since termux repos only keep the latest version and you will have to manually rebuild the old versions of the packages if required as per https://github.com/termux/termux-packages/wiki/Building-packages.
|
||||
|
||||
If you plan on staying on Play Store sources in future as well, then you may want to **disable automatic updates in Play Store** for Termux apps, since if and when updates to disable Termux apps are released, then **you will not be able to downgrade** and **will be forced** to move since apps won't work anymore. Only a way to backup `termux-app` data may be provided. The `termux-tools` [version `>= 0.135`](https://github.com/termux/termux-packages/pull/7493) will also show a banner at the top of the terminal saying `You are likely using a very old version of Termux, probably installed from the Google Play Store.`, you can remove it by running `rm -f /data/data/com.termux/files/usr/etc/motd-playstore` and restarting the app.
|
||||
|
||||
#### Why Disable?
|
||||
|
||||
<details>
|
||||
<summary></summary>
|
||||
|
||||
- Play store apps have multiple critical vulnerabilities as reported at https://termux.github.io/general/2022/02/15/termux-apps-vulnerability-disclosures.html and since they cannot be updated with fixes, any users using older versions would be vulnerable.
|
||||
|
||||
- They should be disabled because deprecated things get removed and are not supported after some time, its the standard practice. It has been many months now since deprecation was announced and updates have not been released on Play Store since after `29 September 2020`.
|
||||
|
||||
- The new versions have lots of **new features and fixes** which you can mostly check out in the Changelog of [`GitHub Releases`](https://github.com/termux/termux-app/releases) that you may be missing out. Extra detail is usually provided in [commit messages](https://github.com/termux/termux-app/commits/master).
|
||||
|
||||
- Users on old versions are quite often reporting issues in multiple repositories and support forums that were **fixed months ago**, which we then have to deal with. The maintainers of @termux work in their free time, majorly for free, to work on development and provide support and having to re-re-deal with old issues takes away the already limited time from current work and is not possible to continue doing. Play Store page of `termux-app` has been filled with bad reviews of *"broken app"*, even though its clearly mentioned on the page that app is not being updated, yet users don't read and still install and report issues.
|
||||
|
||||
- Asking people to pay for plugins when the `termux-app` at installation time is broken due to repository issues and has bugs is unethical.
|
||||
|
||||
- Old versions don't have proper logging/debugging and crash report support. Reporting bugs without logs or detailed info is not helpful in solving them.
|
||||
|
||||
- It's also easier for us to solve package related issues and provide custom functionality with app updates, which can't be done if users continue using old versions. For example, the [bintray shutdown](https://github.com/termux/termux-packages/wiki/Package-Management) causing package install/update failures for new Play Store users is/was not an issue for F-Droid users since it is being shipped with updated bootstrap and repo info, hence no reported issues from new F-Droid users.
|
||||
</details>
|
||||
|
||||
##
|
||||
### Google Play Store **(Experimental branch)**
|
||||
|
||||
There is currently a build of Termux available on Google Play for Android 11+ devices, with extensive adjustments in order to pass policy requirements there. This is under development and has missing functionality and bugs (see [here](https://github.com/termux-play-store/) for status updates) compared to the stable F-Droid build, which is why most users who can should still use F-Droid or GitHub build as mentioned above.
|
||||
|
||||
Currently, Google Play will try to update installations away from F-Droid ones. Updating will still fail as [sharedUserId](https://developer.android.com/guide/topics/manifest/manifest-element#uid) has been removed. A planned 0.118.1 F-Droid release will fix this by setting a higher version code than used for the PlayStore app. Meanwhile, to prevent Google Play from attempting to download and then fail to install the Google Play releases over existing installations, you can open the Termux apps pages on Google Play and then click on the 3 dots options button in the top right and then disable the Enable auto update toggle. However, the Termux apps updates will still show in the PlayStore app updates list.
|
||||
|
||||
If you want to help out with testing the Google Play build (or cannot install Termux from other sources), be aware that it's built from a separate repository (https://github.com/termux-play-store/) - be sure to report issues [there](https://github.com/termux-play-store/termux-issues/issues/new/choose), as any issues encountered might very well be specific to that repository.
|
||||
|
||||
## Uninstallation
|
||||
|
||||
Uninstallation may be required if a user doesn't want Termux installed in their device anymore or is switching to a different [install source](#Installation). You may also want to consider [Backing up Termux](https://wiki.termux.com/wiki/Backing_up_Termux) before the uninstallation.
|
||||
Uninstallation may be required if a user doesn't want Termux installed in their device anymore or is switching to a different [install source](#installation). You may also want to consider [Backing up Termux](https://wiki.termux.com/wiki/Backing_up_Termux) before the uninstallation.
|
||||
|
||||
To uninstall Termux completely, you must uninstall **any and all existing Termux or its plugin app APKs** listed in [Termux App and Plugins](#Termux-App-and-Plugins).
|
||||
To uninstall Termux completely, you must uninstall **any and all existing Termux or its plugin app APKs** listed in [Termux App and Plugins](#termux-app-and-plugins).
|
||||
|
||||
Go to `Android Settings` -> `Applications` and then look for those apps. You can also use the search feature if it’s available on your device and search `termux` in the applications list.
|
||||
|
||||
|
|
@ -175,7 +146,7 @@ The main ones are the following.
|
|||
- [Termux Reddit community](https://reddit.com/r/termux)
|
||||
- [Termux User Matrix Channel](https://matrix.to/#/#termux_termux:gitter.im) ([Gitter](https://gitter.im/termux/termux))
|
||||
- [Termux Dev Matrix Channel](https://matrix.to/#/#termux_dev:gitter.im) ([Gitter](https://gitter.im/termux/dev))
|
||||
- [Termux Twitter](https://twitter.com/termuxdevs)
|
||||
- [Termux X (Twitter)](https://twitter.com/termuxdevs)
|
||||
- [Termux Support Email](mailto:support@termux.dev)
|
||||
|
||||
### Wikis
|
||||
|
|
@ -262,7 +233,21 @@ The main Termux constants are defined by [`TermuxConstants`](https://github.com/
|
|||
|
||||
Check [Termux Libraries](https://github.com/termux/termux-app/wiki/Termux-Libraries) for how to import termux libraries in plugin apps and [Forking and Local Development](https://github.com/termux/termux-app/wiki/Termux-Libraries#forking-and-local-development) for how to update termux libraries for plugins.
|
||||
|
||||
Commit messages **must** use [Conventional Commits](https://www.conventionalcommits.org) specs so that chagelogs can automatically be generated by the [`create-conventional-changelog`](https://github.com/termux/create-conventional-changelog) script, check its repo for further details on the spec. Use the following `types` as `Added: Add foo`, `Added|Fixed: Add foo and fix bar`, `Changed!: Change baz as a breaking change`, etc. You can optionally add a scope as well, like `Fixed(terminal): Some bug`. The space after `:` is necessary.
|
||||
The `versionName` in `build.gradle` files of Termux and its plugin apps must follow the [semantic version `2.0.0` spec](https://semver.org/spec/v2.0.0.html) in the format `major.minor.patch(-prerelease)(+buildmetadata)`. When bumping `versionName` in `build.gradle` files and when creating a tag for new releases on GitHub, make sure to include the patch number as well, like `v0.1.0` instead of just `v0.1`. The `build.gradle` files and `attach_debug_apks_to_release` workflow validates the version as well and the build/attachment will fail if `versionName` does not follow the spec.
|
||||
|
||||
### Commit Messages Guidelines
|
||||
|
||||
Commit messages **must** use the [Conventional Commits](https://www.conventionalcommits.org) spec so that chagelogs as per the [Keep a Changelog](https://github.com/olivierlacan/keep-a-changelog) spec can automatically be generated by the [`create-conventional-changelog`](https://github.com/termux/create-conventional-changelog) script, check its repo for further details on the spec. **The first letter for `type` and `description` must be capital and description should be in the present tense.** The space after the colon `:` is necessary. For a breaking change, add an exclamation mark `!` before the colon `:`, so that it is highlighted in the chagelog automatically.
|
||||
|
||||
```
|
||||
<type>[optional scope]: <description>
|
||||
|
||||
[optional body]
|
||||
|
||||
[optional footer(s)]
|
||||
```
|
||||
|
||||
**Only the `types` listed below must be used exactly as they are used in the changelog headings.** For example, `Added: Add foo`, `Added|Fixed: Add foo and fix bar`, `Changed!: Change baz as a breaking change`, etc. You can optionally add a scope as well, like `Fixed(terminal): Fix some bug`. **Do not use anything else as type, like `add` instead of `Added`, etc.**
|
||||
|
||||
- **Added** for new features.
|
||||
- **Changed** for changes in existing functionality.
|
||||
|
|
@ -270,11 +255,6 @@ Commit messages **must** use [Conventional Commits](https://www.conventionalcomm
|
|||
- **Removed** for now removed features.
|
||||
- **Fixed** for any bug fixes.
|
||||
- **Security** in case of vulnerabilities.
|
||||
- **Docs** for updating documentation.
|
||||
|
||||
Changelogs for releases are generated based on [Keep a Changelog](https://github.com/olivierlacan/keep-a-changelog) specs.
|
||||
|
||||
The `versionName` in `build.gradle` files of Termux and its plugin apps must follow the [semantic version `2.0.0` spec](https://semver.org/spec/v2.0.0.html) in the format `major.minor.patch(-prerelease)(+buildmetadata)`. When bumping `versionName` in `build.gradle` files and when creating a tag for new releases on GitHub, make sure to include the patch number as well, like `v0.1.0` instead of just `v0.1`. The `build.gradle` files and `attach_debug_apks_to_release` workflow validates the version as well and the build/attachment will fail if `versionName` does not follow the spec.
|
||||
##
|
||||
|
||||
|
||||
|
|
@ -285,3 +265,31 @@ The `versionName` in `build.gradle` files of Termux and its plugin apps must fol
|
|||
- You also need to recompile bootstrap zip for the new package name. Check [building bootstrap](https://github.com/termux/termux-packages/wiki/For-maintainers#build-bootstrap-archives), [here](https://github.com/termux/termux-app/issues/1983) and [here](https://github.com/termux/termux-app/issues/2081#issuecomment-865280111).
|
||||
- Currently, not all plugins use `TermuxConstants` from `termux-shared` library and have hardcoded `com.termux` values and will need to be manually patched.
|
||||
- If forking termux plugins, check [Forking and Local Development](https://github.com/termux/termux-app/wiki/Termux-Libraries#forking-and-local-development) for info on how to use termux libraries for plugins.
|
||||
##
|
||||
|
||||
|
||||
|
||||
## Sponsors and Funders
|
||||
|
||||
[<img alt="GitHub Accelerator" width="25%" src="site/assets/sponsors/github.png" />](https://github.com)
|
||||
*[GitHub Accelerator](https://github.com/accelerator) ([1](https://github.blog/2023-04-12-github-accelerator-our-first-cohort-and-whats-next))*
|
||||
|
||||
|
||||
|
||||
[<img alt="GitHub Secure Open Source Fund" width="25%" src="site/assets/sponsors/github.png" />](https://github.com)
|
||||
*[GitHub Secure Open Source Fund](https://resources.github.com/github-secure-open-source-fund) ([1](https://github.blog/open-source/maintainers/securing-the-supply-chain-at-scale-starting-with-71-important-open-source-projects), [2](https://termux.dev/en/posts/general/2025/08/11/termux-selected-for-github-secure-open-source-fund-session-2.html))*
|
||||
|
||||
|
||||
|
||||
[<img alt="NLnet NGI Mobifree" width="25%" src="site/assets/sponsors/nlnet-ngi-mobifree.png" />](https://nlnet.nl/mobifree)
|
||||
*[NLnet NGI Mobifree](https://nlnet.nl/mobifree) ([1](https://nlnet.nl/news/2024/20241111-NGI-Mobifree-grants.html), [2](https://termux.dev/en/posts/general/2024/11/11/termux-selected-for-nlnet-ngi-mobifree-grant.html))*
|
||||
|
||||
|
||||
|
||||
[<img alt="Cloudflare" width="25%" src="site/assets/sponsors/cloudflare.png" />](https://www.cloudflare.com)
|
||||
*[Cloudflare](https://www.cloudflare.com) ([1](https://packages-cf.termux.dev))*
|
||||
|
||||
|
||||
|
||||
[<img alt="Warp" width="25%" src="https://github.com/warpdotdev/brand-assets/blob/640dffd347439bbcb535321ab36b7281cf4446c0/Github/Sponsor/Warp-Github-LG-03.png" />](https://www.warp.dev/?utm_source=github&utm_medium=readme&utm_campaign=termux)
|
||||
[*Warp, built for coding with multiple AI agents*](https://www.warp.dev/?utm_source=github&utm_medium=readme&utm_campaign=termux)
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
Check https://termux.dev/security for info on Termux security policies and how to report vulnerabilities.
|
||||
|
|
@ -13,6 +13,8 @@ ext {
|
|||
}
|
||||
|
||||
android {
|
||||
namespace "com.termux"
|
||||
|
||||
compileSdkVersion project.properties.compileSdkVersion.toInteger()
|
||||
ndkVersion = System.getenv("JITPACK_NDK_VERSION") ?: project.properties.ndkVersion
|
||||
def appVersionName = System.getenv("TERMUX_APP_VERSION_NAME") ?: ""
|
||||
|
|
@ -21,24 +23,24 @@ android {
|
|||
def splitAPKsForReleaseBuilds = System.getenv("TERMUX_SPLIT_APKS_FOR_RELEASE_BUILDS") ?: "0" // F-Droid does not support split APKs #1904
|
||||
|
||||
dependencies {
|
||||
implementation "androidx.annotation:annotation:1.3.0"
|
||||
implementation "androidx.core:core:1.6.0"
|
||||
implementation "androidx.drawerlayout:drawerlayout:1.1.1"
|
||||
implementation "androidx.preference:preference:1.1.1"
|
||||
implementation "androidx.annotation:annotation:1.9.0"
|
||||
implementation "androidx.core:core:1.13.1"
|
||||
implementation "androidx.drawerlayout:drawerlayout:1.2.0"
|
||||
implementation "androidx.preference:preference:1.2.1"
|
||||
implementation "androidx.viewpager:viewpager:1.0.0"
|
||||
implementation "com.google.android.material:material:1.4.0"
|
||||
implementation "com.google.android.material:material:1.12.0"
|
||||
implementation "com.google.guava:guava:24.1-jre"
|
||||
implementation "io.noties.markwon:core:$markwonVersion"
|
||||
implementation "io.noties.markwon:ext-strikethrough:$markwonVersion"
|
||||
implementation "io.noties.markwon:linkify:$markwonVersion"
|
||||
implementation "io.noties.markwon:recycler:$markwonVersion"
|
||||
implementation 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava'
|
||||
|
||||
implementation project(":terminal-view")
|
||||
implementation project(":termux-shared")
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.termux"
|
||||
minSdkVersion project.properties.minSdkVersion.toInteger()
|
||||
targetSdkVersion project.properties.targetSdkVersion.toInteger()
|
||||
versionCode 118
|
||||
|
|
@ -110,7 +112,7 @@ android {
|
|||
}
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
lint {
|
||||
disable 'ProtectedPermissions'
|
||||
}
|
||||
|
||||
|
|
@ -138,12 +140,15 @@ android {
|
|||
}
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
buildConfig true
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testImplementation "junit:junit:4.13.2"
|
||||
testImplementation "org.robolectric:robolectric:4.4"
|
||||
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:1.1.5"
|
||||
testImplementation "org.robolectric:robolectric:4.10"
|
||||
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:2.1.2"
|
||||
}
|
||||
|
||||
task versionName {
|
||||
|
|
|
|||
|
|
@ -10,8 +10,3 @@
|
|||
-dontobfuscate
|
||||
#-renamesourcefileattribute SourceFile
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# Temp fix for androidx.window:window:1.0.0-alpha09 imported by termux-shared
|
||||
# https://issuetracker.google.com/issues/189001730
|
||||
# https://android-review.googlesource.com/c/platform/frameworks/support/+/1757630
|
||||
-keep class androidx.window.** { *; }
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="com.termux"
|
||||
android:installLocation="internalOnly"
|
||||
android:sharedUserId="${TERMUX_PACKAGE_NAME}"
|
||||
android:sharedUserLabel="@string/shared_user_label">
|
||||
|
|
@ -36,6 +35,7 @@
|
|||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" tools:ignore="ProtectedPermissions" />
|
||||
<uses-permission android:name="com.android.alarm.permission.SET_ALARM" />
|
||||
|
||||
<application
|
||||
android:name=".app.TermuxApplication"
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import android.content.Intent;
|
|||
import android.content.IntentFilter;
|
||||
import android.content.ServiceConnection;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.view.ContextMenu;
|
||||
|
|
@ -21,7 +20,6 @@ import android.view.MenuItem;
|
|||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowManager;
|
||||
import android.view.autofill.AutofillManager;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ListView;
|
||||
|
|
@ -181,7 +179,8 @@ public final class TermuxActivity extends AppCompatActivity implements ServiceCo
|
|||
private static final int CONTEXT_MENU_SELECT_URL_ID = 0;
|
||||
private static final int CONTEXT_MENU_SHARE_TRANSCRIPT_ID = 1;
|
||||
private static final int CONTEXT_MENU_SHARE_SELECTED_TEXT = 10;
|
||||
private static final int CONTEXT_MENU_AUTOFILL_ID = 2;
|
||||
private static final int CONTEXT_MENU_AUTOFILL_USERNAME = 11;
|
||||
private static final int CONTEXT_MENU_AUTOFILL_PASSWORD = 2;
|
||||
private static final int CONTEXT_MENU_RESET_TERMINAL_ID = 3;
|
||||
private static final int CONTEXT_MENU_KILL_PROCESS_ID = 4;
|
||||
private static final int CONTEXT_MENU_STYLING_ID = 5;
|
||||
|
|
@ -590,7 +589,7 @@ public final class TermuxActivity extends AppCompatActivity implements ServiceCo
|
|||
});
|
||||
|
||||
findViewById(R.id.toggle_keyboard_button).setOnLongClickListener(v -> {
|
||||
//toggleTerminalToolbar();
|
||||
toggleTerminalToolbar();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
|
@ -632,20 +631,16 @@ public final class TermuxActivity extends AppCompatActivity implements ServiceCo
|
|||
TerminalSession currentSession = getCurrentSession();
|
||||
if (currentSession == null) return;
|
||||
|
||||
boolean addAutoFillMenu = false;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
AutofillManager autofillManager = getSystemService(AutofillManager.class);
|
||||
if (autofillManager != null && autofillManager.isEnabled()) {
|
||||
addAutoFillMenu = true;
|
||||
}
|
||||
}
|
||||
boolean autoFillEnabled = mTerminalView.isAutoFillEnabled();
|
||||
|
||||
menu.add(Menu.NONE, CONTEXT_MENU_SELECT_URL_ID, Menu.NONE, R.string.action_select_url);
|
||||
menu.add(Menu.NONE, CONTEXT_MENU_SHARE_TRANSCRIPT_ID, Menu.NONE, R.string.action_share_transcript);
|
||||
if (!DataUtils.isNullOrEmpty(mTerminalView.getStoredSelectedText()))
|
||||
menu.add(Menu.NONE, CONTEXT_MENU_SHARE_SELECTED_TEXT, Menu.NONE, R.string.action_share_selected_text);
|
||||
if (addAutoFillMenu)
|
||||
menu.add(Menu.NONE, CONTEXT_MENU_AUTOFILL_ID, Menu.NONE, R.string.action_autofill_password);
|
||||
if (autoFillEnabled)
|
||||
menu.add(Menu.NONE, CONTEXT_MENU_AUTOFILL_USERNAME, Menu.NONE, R.string.action_autofill_username);
|
||||
if (autoFillEnabled)
|
||||
menu.add(Menu.NONE, CONTEXT_MENU_AUTOFILL_PASSWORD, Menu.NONE, R.string.action_autofill_password);
|
||||
menu.add(Menu.NONE, CONTEXT_MENU_RESET_TERMINAL_ID, Menu.NONE, R.string.action_reset_terminal);
|
||||
menu.add(Menu.NONE, CONTEXT_MENU_KILL_PROCESS_ID, Menu.NONE, getResources().getString(R.string.action_kill_process, getCurrentSession().getPid())).setEnabled(currentSession.isRunning());
|
||||
menu.add(Menu.NONE, CONTEXT_MENU_STYLING_ID, Menu.NONE, R.string.action_style_terminal);
|
||||
|
|
@ -676,8 +671,11 @@ public final class TermuxActivity extends AppCompatActivity implements ServiceCo
|
|||
case CONTEXT_MENU_SHARE_SELECTED_TEXT:
|
||||
mTermuxTerminalViewClient.shareSelectedText();
|
||||
return true;
|
||||
case CONTEXT_MENU_AUTOFILL_ID:
|
||||
requestAutoFill();
|
||||
case CONTEXT_MENU_AUTOFILL_USERNAME:
|
||||
mTerminalView.requestAutoFillUsername();
|
||||
return true;
|
||||
case CONTEXT_MENU_AUTOFILL_PASSWORD:
|
||||
mTerminalView.requestAutoFillPassword();
|
||||
return true;
|
||||
case CONTEXT_MENU_RESET_TERMINAL_ID:
|
||||
onResetTerminalSession(session);
|
||||
|
|
@ -738,7 +736,7 @@ public final class TermuxActivity extends AppCompatActivity implements ServiceCo
|
|||
|
||||
private void showStylingDialog() {
|
||||
Intent stylingIntent = new Intent();
|
||||
stylingIntent.setClassName(TermuxConstants.TERMUX_STYLING_PACKAGE_NAME, TermuxConstants.TERMUX_STYLING.TERMUX_STYLING_ACTIVITY_NAME);
|
||||
stylingIntent.setClassName(TermuxConstants.TERMUX_STYLING_PACKAGE_NAME, TermuxConstants.TERMUX_STYLING_APP.TERMUX_STYLING_ACTIVITY_NAME);
|
||||
try {
|
||||
startActivity(stylingIntent);
|
||||
} catch (ActivityNotFoundException | IllegalArgumentException e) {
|
||||
|
|
@ -760,15 +758,6 @@ public final class TermuxActivity extends AppCompatActivity implements ServiceCo
|
|||
}
|
||||
}
|
||||
|
||||
private void requestAutoFill() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
AutofillManager autofillManager = getSystemService(AutofillManager.class);
|
||||
if (autofillManager != null && autofillManager.isEnabled()) {
|
||||
autofillManager.requestAutofill(mTerminalView);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -172,6 +172,13 @@ public class TermuxOpenReceiver extends BroadcastReceiver {
|
|||
|
||||
@Override
|
||||
public String getType(@NonNull Uri uri) {
|
||||
String path = uri.getLastPathSegment();
|
||||
int extIndex = path.lastIndexOf('.') + 1;
|
||||
if (extIndex > 0) {
|
||||
MimeTypeMap mimeMap = MimeTypeMap.getSingleton();
|
||||
String ext = path.substring(extIndex).toLowerCase();
|
||||
return mimeMap.getMimeTypeFromExtension(ext);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -35,7 +35,6 @@ public final class HelpActivity extends AppCompatActivity {
|
|||
mWebView = new WebView(this);
|
||||
WebSettings settings = mWebView.getSettings();
|
||||
settings.setCacheMode(WebSettings.LOAD_NO_CACHE);
|
||||
settings.setAppCacheEnabled(false);
|
||||
setContentView(progressLayout);
|
||||
mWebView.clearCache(true);
|
||||
|
||||
|
|
|
|||
|
|
@ -55,6 +55,8 @@ public class FileReceiverActivity extends AppCompatActivity {
|
|||
private static final String LOG_TAG = "FileReceiverActivity";
|
||||
|
||||
static boolean isSharedTextAnUrl(String sharedText) {
|
||||
if (sharedText == null || sharedText.isEmpty()) return false;
|
||||
|
||||
return Patterns.WEB_URL.matcher(sharedText).matches()
|
||||
|| Pattern.matches("magnet:\\?xt=urn:btih:.*?", sharedText);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -271,7 +271,7 @@ public class TermuxTerminalSessionActivityClient extends TermuxTerminalSessionCl
|
|||
.setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION).build()).build();
|
||||
|
||||
try {
|
||||
mBellSoundId = mBellSoundPool.load(mActivity, R.raw.bell, 1);
|
||||
mBellSoundId = mBellSoundPool.load(mActivity, com.termux.shared.R.raw.bell, 1);
|
||||
} catch (Exception e){
|
||||
// Catch java.lang.RuntimeException: Unable to resume activity {com.termux/com.termux.app.TermuxActivity}: android.content.res.Resources$NotFoundException: File res/raw/bell.ogg from drawable resource ID
|
||||
Logger.logStackTraceWithMessage(LOG_TAG, "Failed to load bell sound pool", e);
|
||||
|
|
|
|||
|
|
@ -735,8 +735,8 @@ public class TermuxTerminalViewClient extends TermuxTerminalViewClientBase {
|
|||
|
||||
MessageDialogUtils.showMessage(mActivity, TermuxConstants.TERMUX_APP_NAME + " Report Issue",
|
||||
mActivity.getString(R.string.msg_add_termux_debug_info),
|
||||
mActivity.getString(R.string.action_yes), (dialog, which) -> reportIssueFromTranscript(transcriptText, true),
|
||||
mActivity.getString(R.string.action_no), (dialog, which) -> reportIssueFromTranscript(transcriptText, false),
|
||||
mActivity.getString(com.termux.shared.R.string.action_yes), (dialog, which) -> reportIssueFromTranscript(transcriptText, true),
|
||||
mActivity.getString(com.termux.shared.R.string.action_no), (dialog, which) -> reportIssueFromTranscript(transcriptText, false),
|
||||
null);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -31,8 +31,6 @@
|
|||
android:focusableInTouchMode="true"
|
||||
android:scrollbarThumbVertical="@drawable/terminal_scroll_shape"
|
||||
android:scrollbars="vertical"
|
||||
android:importantForAutofill="no"
|
||||
android:autofillHints="password"
|
||||
tools:ignore="UnusedAttribute" />
|
||||
|
||||
<LinearLayout
|
||||
|
|
|
|||
|
|
@ -2,4 +2,5 @@
|
|||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@android:color/black"/>
|
||||
<foreground android:drawable="@drawable/ic_foreground"/>
|
||||
<monochrome android:drawable="@drawable/ic_foreground"/>
|
||||
</adaptive-icon>
|
||||
|
|
|
|||
|
|
@ -2,4 +2,5 @@
|
|||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@android:color/black"/>
|
||||
<foreground android:drawable="@drawable/ic_foreground"/>
|
||||
<monochrome android:drawable="@drawable/ic_foreground"/>
|
||||
</adaptive-icon>
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@
|
|||
<string name="title_share_selected_text">Terminal Text</string>
|
||||
<string name="title_share_selected_text_with">Send selected text to:</string>
|
||||
|
||||
<string name="action_autofill_username">Autofill username</string>
|
||||
<string name="action_autofill_password">Autofill password</string>
|
||||
|
||||
<string name="action_reset_terminal">Reset</string>
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@ public class FileReceiverActivityTest {
|
|||
|
||||
List<String> invalidUrls = new ArrayList<>();
|
||||
invalidUrls.add("a test with example.com");
|
||||
invalidUrls.add("");
|
||||
invalidUrls.add(null);
|
||||
for (String url : invalidUrls) {
|
||||
Assert.assertFalse(FileReceiverActivity.isSharedTextAnUrl(url));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ buildscript {
|
|||
google()
|
||||
}
|
||||
dependencies {
|
||||
classpath "com.android.tools.build:gradle:4.2.2"
|
||||
classpath "com.android.tools.build:gradle:8.13.2"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -15,7 +15,3 @@ allprojects {
|
|||
maven { url "https://jitpack.io" }
|
||||
}
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,13 +12,17 @@
|
|||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
org.gradle.jvmargs=-Xmx2048M
|
||||
org.gradle.jvmargs=-Xmx2048M \
|
||||
--add-exports=java.base/sun.nio.ch=ALL-UNNAMED \
|
||||
--add-opens=java.base/java.lang=ALL-UNNAMED \
|
||||
--add-opens=java.base/java.lang.reflect=ALL-UNNAMED \
|
||||
--add-opens=java.base/java.io=ALL-UNNAMED \
|
||||
--add-exports=jdk.unsupported/sun.misc=ALL-UNNAMED
|
||||
android.useAndroidX=true
|
||||
|
||||
minSdkVersion=21
|
||||
targetSdkVersion=28
|
||||
ndkVersion=22.1.7171670
|
||||
compileSdkVersion=30
|
||||
ndkVersion=29.0.14206865
|
||||
compileSdkVersion=36
|
||||
|
||||
markwonVersion=4.6.2
|
||||
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -1,5 +1,7 @@
|
|||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
#!/usr/bin/env sh
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright 2015 the original author or authors.
|
||||
# Copyright © 2015 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
|
|
@ -15,81 +15,114 @@
|
|||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
|
@ -98,88 +131,118 @@ Please set the JAVA_HOME variable in your environment to match the
|
|||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
JAVACMD=java
|
||||
if ! command -v java >/dev/null 2>&1
|
||||
then
|
||||
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=`expr $i + 1`
|
||||
done
|
||||
case $i in
|
||||
0) set -- ;;
|
||||
1) set -- "$args0" ;;
|
||||
2) set -- "$args0" "$args1" ;;
|
||||
3) set -- "$args0" "$args1" "$args2" ;;
|
||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=`save "$@"`
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
|
|
|
|||
|
|
@ -13,8 +13,10 @@
|
|||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
@rem SPDX-License-Identifier: Apache-2.0
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
|
|
@ -25,7 +27,8 @@
|
|||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
|
|
@ -40,13 +43,13 @@ if defined JAVA_HOME goto findJavaFromJavaHome
|
|||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
|
|
@ -56,32 +59,33 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
|||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
|
|
|||
|
|
@ -1,2 +1,4 @@
|
|||
jdk:
|
||||
- openjdk17
|
||||
env:
|
||||
JITPACK_NDK_VERSION: "21.1.6352462"
|
||||
JITPACK_NDK_VERSION: "29.0.14206865"
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
|
|
@ -2,6 +2,8 @@ apply plugin: 'com.android.library'
|
|||
apply plugin: 'maven-publish'
|
||||
|
||||
android {
|
||||
namespace "com.termux.emulator"
|
||||
|
||||
compileSdkVersion project.properties.compileSdkVersion.toInteger()
|
||||
ndkVersion = System.getenv("JITPACK_NDK_VERSION") ?: project.properties.ndkVersion
|
||||
|
||||
|
|
@ -50,13 +52,13 @@ tasks.withType(Test) {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
implementation "androidx.annotation:annotation:1.3.0"
|
||||
implementation "androidx.annotation:annotation:1.9.0"
|
||||
testImplementation "junit:junit:4.13.2"
|
||||
}
|
||||
|
||||
task sourceJar(type: Jar) {
|
||||
from android.sourceSets.main.java.srcDirs
|
||||
classifier "sources"
|
||||
archiveClassifier = "sources"
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
|
|
@ -64,7 +66,7 @@ afterEvaluate {
|
|||
publications {
|
||||
// Creates a Maven publication called "release".
|
||||
release(MavenPublication) {
|
||||
from components.release
|
||||
from components.findByName('release')
|
||||
groupId = 'com.termux'
|
||||
artifactId = 'terminal-emulator'
|
||||
version = '0.118.0'
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
<manifest package="com.termux.terminal">
|
||||
<manifest>
|
||||
</manifest>
|
||||
|
|
|
|||
|
|
@ -23,10 +23,10 @@ final class JNI {
|
|||
* @return the file descriptor resulting from opening /dev/ptmx master device. The sub process will have opened the
|
||||
* slave device counterpart (/dev/pts/$N) and have it as stdint, stdout and stderr.
|
||||
*/
|
||||
public static native int createSubprocess(String cmd, String cwd, String[] args, String[] envVars, int[] processId, int rows, int columns);
|
||||
public static native int createSubprocess(String cmd, String cwd, String[] args, String[] envVars, int[] processId, int rows, int columns, int cellWidth, int cellHeight);
|
||||
|
||||
/** Set the window size for a given pty, which allows connected programs to learn how large their screen is. */
|
||||
public static native void setPtyWindowSize(int fd, int rows, int cols);
|
||||
public static native void setPtyWindowSize(int fd, int rows, int cols, int cellWidth, int cellHeight);
|
||||
|
||||
/**
|
||||
* Causes the calling thread to wait for the process associated with the receiver to finish executing.
|
||||
|
|
|
|||
|
|
@ -79,9 +79,17 @@ public final class TerminalEmulator {
|
|||
private static final int ESC_CSI_SINGLE_QUOTE = 18;
|
||||
/** Escape processing: CSI ! */
|
||||
private static final int ESC_CSI_EXCLAMATION = 19;
|
||||
/** Escape processing: "ESC _" or Application Program Command (APC). */
|
||||
private static final int ESC_APC = 20;
|
||||
/** Escape processing: "ESC _" or Application Program Command (APC), followed by Escape. */
|
||||
private static final int ESC_APC_ESCAPE = 21;
|
||||
/** Escape processing: ESC [ <parameter bytes> */
|
||||
private static final int ESC_CSI_UNSUPPORTED_PARAMETER_BYTE = 22;
|
||||
/** Escape processing: ESC [ <parameter bytes> <intermediate bytes> */
|
||||
private static final int ESC_CSI_UNSUPPORTED_INTERMEDIATE_BYTE = 23;
|
||||
|
||||
/** The number of parameter arguments. This name comes from the ANSI standard for terminal escape codes. */
|
||||
private static final int MAX_ESCAPE_PARAMETERS = 16;
|
||||
/** The number of parameter arguments including colon separated sub-parameters. */
|
||||
private static final int MAX_ESCAPE_PARAMETERS = 32;
|
||||
|
||||
/** Needs to be large enough to contain reasonable OSC 52 pastes. */
|
||||
private static final int MAX_OSC_STRING_LENGTH = 8192;
|
||||
|
|
@ -126,17 +134,15 @@ public final class TerminalEmulator {
|
|||
private String mTitle;
|
||||
private final Stack<String> mTitleStack = new Stack<>();
|
||||
|
||||
/** If processing first character of first parameter of {@link #ESC_CSI}. */
|
||||
private boolean mIsCSIStart;
|
||||
/** The last character processed of a parameter of {@link #ESC_CSI}. */
|
||||
private Integer mLastCSIArg;
|
||||
|
||||
/** The cursor position. Between (0,0) and (mRows-1, mColumns-1). */
|
||||
private int mCursorRow, mCursorCol;
|
||||
|
||||
/** The number of character rows and columns in the terminal screen. */
|
||||
public int mRows, mColumns;
|
||||
|
||||
/** Size of a terminal cell in pixels. */
|
||||
private int mCellWidthPixels, mCellHeightPixels;
|
||||
|
||||
/** The number of terminal transcript rows that can be scrolled back to. */
|
||||
public static final int TERMINAL_TRANSCRIPT_ROWS_MIN = 100;
|
||||
public static final int TERMINAL_TRANSCRIPT_ROWS_MAX = 50000;
|
||||
|
|
@ -176,6 +182,8 @@ public final class TerminalEmulator {
|
|||
private int mArgIndex;
|
||||
/** Holds the arguments of the current escape sequence. */
|
||||
private final int[] mArgs = new int[MAX_ESCAPE_PARAMETERS];
|
||||
/** Holds the bit flags which arguments are sub parameters (after a colon) - bit N is set if <code>mArgs[N]</code> is a sub parameter. */
|
||||
private int mArgsSubParamsBitSet = 0;
|
||||
|
||||
/** Holds OSC and device control arguments, which can be strings. */
|
||||
private final StringBuilder mOSCOrDeviceControlArgs = new StringBuilder();
|
||||
|
|
@ -236,15 +244,17 @@ public final class TerminalEmulator {
|
|||
private boolean mCursorBlinkState;
|
||||
|
||||
/**
|
||||
* Current foreground and background colors. Can either be a color index in [0,259] or a truecolor (24-bit) value.
|
||||
* Current foreground, background and underline colors. Can either be a color index in [0,259] or a truecolor (24-bit) value.
|
||||
* For a 24-bit value the top byte (0xff000000) is set.
|
||||
*
|
||||
* <p>Note that the underline color is currently parsed but not yet used during rendering.
|
||||
*
|
||||
* @see TextStyle
|
||||
*/
|
||||
int mForeColor, mBackColor;
|
||||
int mForeColor, mBackColor, mUnderlineColor;
|
||||
|
||||
/** Current {@link TextStyle} effect. */
|
||||
private int mEffect;
|
||||
int mEffect;
|
||||
|
||||
/**
|
||||
* The number of scrolled lines since last calling {@link #clearScrollCounter()}. Used for moving selection up along
|
||||
|
|
@ -315,13 +325,15 @@ public final class TerminalEmulator {
|
|||
}
|
||||
}
|
||||
|
||||
public TerminalEmulator(TerminalOutput session, int columns, int rows, Integer transcriptRows, TerminalSessionClient client) {
|
||||
public TerminalEmulator(TerminalOutput session, int columns, int rows, int cellWidthPixels, int cellHeightPixels, Integer transcriptRows, TerminalSessionClient client) {
|
||||
mSession = session;
|
||||
mScreen = mMainBuffer = new TerminalBuffer(columns, getTerminalTranscriptRows(transcriptRows), rows);
|
||||
mAltBuffer = new TerminalBuffer(columns, rows, rows);
|
||||
mClient = client;
|
||||
mRows = rows;
|
||||
mColumns = columns;
|
||||
mCellWidthPixels = cellWidthPixels;
|
||||
mCellHeightPixels = cellHeightPixels;
|
||||
mTabStop = new boolean[mColumns];
|
||||
reset();
|
||||
}
|
||||
|
|
@ -371,7 +383,10 @@ public final class TerminalEmulator {
|
|||
}
|
||||
}
|
||||
|
||||
public void resize(int columns, int rows) {
|
||||
public void resize(int columns, int rows, int cellWidthPixels, int cellHeightPixels) {
|
||||
this.mCellWidthPixels = cellWidthPixels;
|
||||
this.mCellHeightPixels = cellHeightPixels;
|
||||
|
||||
if (mRows == rows && mColumns == columns) {
|
||||
return;
|
||||
} else if (columns < 2 || rows < 2) {
|
||||
|
|
@ -553,6 +568,15 @@ public final class TerminalEmulator {
|
|||
}
|
||||
|
||||
public void processCodePoint(int b) {
|
||||
// The Application Program-Control (APC) string might be arbitrary non-printable characters, so handle that early.
|
||||
if (mEscapeState == ESC_APC) {
|
||||
doApc(b);
|
||||
return;
|
||||
} else if (mEscapeState == ESC_APC_ESCAPE) {
|
||||
doApcEscape(b);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (b) {
|
||||
case 0: // Null character (NUL, ^@). Do nothing.
|
||||
break;
|
||||
|
|
@ -638,6 +662,10 @@ public final class TerminalEmulator {
|
|||
case ESC_CSI:
|
||||
doCsi(b);
|
||||
break;
|
||||
case ESC_CSI_UNSUPPORTED_PARAMETER_BYTE:
|
||||
case ESC_CSI_UNSUPPORTED_INTERMEDIATE_BYTE:
|
||||
doCsiUnsupportedParameterOrIntermediateByte(b);
|
||||
break;
|
||||
case ESC_CSI_EXCLAMATION:
|
||||
if (b == 'p') { // Soft terminal reset (DECSTR, http://vt100.net/docs/vt510-rm/DECSTR).
|
||||
reset();
|
||||
|
|
@ -1009,12 +1037,67 @@ public final class TerminalEmulator {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When in {@link #ESC_APC} (APC, Application Program Command) sequence.
|
||||
*/
|
||||
private void doApc(int b) {
|
||||
if (b == 27) {
|
||||
continueSequence(ESC_APC_ESCAPE);
|
||||
}
|
||||
// Eat APC sequences silently for now.
|
||||
}
|
||||
|
||||
/**
|
||||
* When in {@link #ESC_APC} (APC, Application Program Command) sequence.
|
||||
*/
|
||||
private void doApcEscape(int b) {
|
||||
if (b == '\\') {
|
||||
// A String Terminator (ST), ending the APC escape sequence.
|
||||
finishSequence();
|
||||
} else {
|
||||
// The Escape character was not the start of a String Terminator (ST),
|
||||
// but instead just data inside of the APC escape sequence.
|
||||
continueSequence(ESC_APC);
|
||||
}
|
||||
}
|
||||
|
||||
private int nextTabStop(int numTabs) {
|
||||
for (int i = mCursorCol + 1; i < mColumns; i++)
|
||||
if (mTabStop[i] && --numTabs == 0) return Math.min(i, mRightMargin);
|
||||
return mRightMargin - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process byte while in the {@link #ESC_CSI_UNSUPPORTED_PARAMETER_BYTE} or
|
||||
* {@link #ESC_CSI_UNSUPPORTED_INTERMEDIATE_BYTE} escape state.
|
||||
*
|
||||
* Parse unsupported parameter, intermediate and final bytes but ignore them.
|
||||
*
|
||||
* > For Control Sequence Introducer, ... the ESC [ is followed by
|
||||
* > - any number (including none) of "parameter bytes" in the range 0x30–0x3F (ASCII 0–9:;<=>?),
|
||||
* > - then by any number of "intermediate bytes" in the range 0x20–0x2F (ASCII space and !"#$%&'()*+,-./),
|
||||
* > - then finally by a single "final byte" in the range 0x40–0x7E (ASCII @A–Z[\]^_`a–z{|}~).
|
||||
*
|
||||
* - https://en.wikipedia.org/wiki/ANSI_escape_code#Control_Sequence_Introducer_commands
|
||||
* - https://invisible-island.net/xterm/ecma-48-parameter-format.html#section5.4
|
||||
*/
|
||||
private void doCsiUnsupportedParameterOrIntermediateByte(int b) {
|
||||
if (mEscapeState == ESC_CSI_UNSUPPORTED_PARAMETER_BYTE && b >= 0x30 && b <= 0x3F) {
|
||||
// Supported `0–9:;>?` or unsupported `<=` parameter byte after an
|
||||
// initial unsupported parameter byte in `doCsi()`, or a sequential parameter byte.
|
||||
continueSequence(ESC_CSI_UNSUPPORTED_PARAMETER_BYTE);
|
||||
} else if (b >= 0x20 && b <= 0x2F) {
|
||||
// Optional intermediate byte `!"#$%&'()*+,-./` after parameter or intermediate byte.
|
||||
continueSequence(ESC_CSI_UNSUPPORTED_INTERMEDIATE_BYTE);
|
||||
} else if (b >= 0x40 && b <= 0x7E) {
|
||||
// Final byte `@A–Z[\]^_`a–z{|}~` after parameter or intermediate byte.
|
||||
// Calling `unknownSequence()` would log an error with only a final byte, so ignore it for now.
|
||||
finishSequence();
|
||||
} else {
|
||||
unknownSequence(b);
|
||||
}
|
||||
}
|
||||
|
||||
/** Process byte while in the {@link #ESC_CSI_QUESTIONMARK} escape state. */
|
||||
private void doCsiQuestionMark(int b) {
|
||||
switch (b) {
|
||||
|
|
@ -1284,6 +1367,7 @@ public final class TerminalEmulator {
|
|||
mEscapeState = ESC;
|
||||
mArgIndex = 0;
|
||||
Arrays.fill(mArgs, -1);
|
||||
mArgsSubParamsBitSet = 0;
|
||||
}
|
||||
|
||||
private void doLinefeed() {
|
||||
|
|
@ -1378,8 +1462,8 @@ public final class TerminalEmulator {
|
|||
// http://www.vt100.net/docs/vt100-ug/chapter3.html: "Move the active position to the same horizontal
|
||||
// position on the preceding line. If the active position is at the top margin, a scroll down is performed".
|
||||
if (mCursorRow <= mTopMargin) {
|
||||
mScreen.blockCopy(0, mTopMargin, mColumns, mBottomMargin - (mTopMargin + 1), 0, mTopMargin + 1);
|
||||
blockClear(0, mTopMargin, mColumns);
|
||||
mScreen.blockCopy(mLeftMargin, mTopMargin, mRightMargin - mLeftMargin, mBottomMargin - (mTopMargin + 1), mLeftMargin, mTopMargin + 1);
|
||||
blockClear(mLeftMargin, mTopMargin, mRightMargin - mLeftMargin);
|
||||
} else {
|
||||
mCursorRow--;
|
||||
}
|
||||
|
|
@ -1393,8 +1477,6 @@ public final class TerminalEmulator {
|
|||
break;
|
||||
case '[':
|
||||
continueSequence(ESC_CSI);
|
||||
mIsCSIStart = true;
|
||||
mLastCSIArg = null;
|
||||
break;
|
||||
case '=': // DECKPAM
|
||||
setDecsetinternalBit(DECSET_BIT_APPLICATION_KEYPAD, true);
|
||||
|
|
@ -1406,6 +1488,9 @@ public final class TerminalEmulator {
|
|||
case '>': // DECKPNM
|
||||
setDecsetinternalBit(DECSET_BIT_APPLICATION_KEYPAD, false);
|
||||
break;
|
||||
case '_': // APC - Application Program Command.
|
||||
continueSequence(ESC_APC);
|
||||
break;
|
||||
default:
|
||||
unknownSequence(b);
|
||||
break;
|
||||
|
|
@ -1587,8 +1672,8 @@ public final class TerminalEmulator {
|
|||
final int linesToScrollArg = getArg0(1);
|
||||
final int linesBetweenTopAndBottomMargins = mBottomMargin - mTopMargin;
|
||||
final int linesToScroll = Math.min(linesBetweenTopAndBottomMargins, linesToScrollArg);
|
||||
mScreen.blockCopy(0, mTopMargin, mColumns, linesBetweenTopAndBottomMargins - linesToScroll, 0, mTopMargin + linesToScroll);
|
||||
blockClear(0, mTopMargin, mColumns, linesToScroll);
|
||||
mScreen.blockCopy(mLeftMargin, mTopMargin, mRightMargin - mLeftMargin, linesBetweenTopAndBottomMargins - linesToScroll, mLeftMargin, mTopMargin + linesToScroll);
|
||||
blockClear(mLeftMargin, mTopMargin, mRightMargin - mLeftMargin, linesToScroll);
|
||||
} else {
|
||||
// "${CSI}${func};${startx};${starty};${firstrow};${lastrow}T" - initiate highlight mouse tracking.
|
||||
unimplementedSequence(b);
|
||||
|
|
@ -1610,12 +1695,16 @@ public final class TerminalEmulator {
|
|||
}
|
||||
mCursorCol = newCol;
|
||||
break;
|
||||
case '?': // Esc [ ? -- start of a private mode set
|
||||
case '?': // Esc [ ? -- start of a private parameter byte
|
||||
continueSequence(ESC_CSI_QUESTIONMARK);
|
||||
break;
|
||||
case '>': // "Esc [ >" --
|
||||
case '>': // "Esc [ >" -- start of a private parameter byte
|
||||
continueSequence(ESC_CSI_BIGGERTHAN);
|
||||
break;
|
||||
case '<': // "Esc [ <" -- start of a private parameter byte
|
||||
case '=': // "Esc [ =" -- start of a private parameter byte
|
||||
continueSequence(ESC_CSI_UNSUPPORTED_PARAMETER_BYTE);
|
||||
break;
|
||||
case '`': // Horizontal position absolute (HPA - http://www.vt100.net/docs/vt510-rm/HPA).
|
||||
setCursorColRespectingOriginMode(getArg0(1) - 1);
|
||||
break;
|
||||
|
|
@ -1715,8 +1804,10 @@ public final class TerminalEmulator {
|
|||
mSession.write("\033[3;0;0t");
|
||||
break;
|
||||
case 14: // Report xterm window in pixels. Result is CSI 4 ; height ; width t
|
||||
// We just report characters time 12 here.
|
||||
mSession.write(String.format(Locale.US, "\033[4;%d;%dt", mRows * 12, mColumns * 12));
|
||||
mSession.write(String.format(Locale.US, "\033[4;%d;%dt", mRows * mCellHeightPixels, mColumns * mCellWidthPixels));
|
||||
break;
|
||||
case 16: // Report xterm character cell size in pixels. Result is CSI 6 ; height ; width t
|
||||
mSession.write(String.format(Locale.US, "\033[6;%d;%dt", mCellHeightPixels, mCellWidthPixels));
|
||||
break;
|
||||
case 18: // Report the size of the text area in characters. Result is CSI 8 ; height ; width t
|
||||
mSession.write(String.format(Locale.US, "\033[8;%d;%dt", mRows, mColumns));
|
||||
|
|
@ -1765,7 +1856,12 @@ public final class TerminalEmulator {
|
|||
private void selectGraphicRendition() {
|
||||
if (mArgIndex >= mArgs.length) mArgIndex = mArgs.length - 1;
|
||||
for (int i = 0; i <= mArgIndex; i++) {
|
||||
int code = mArgs[i];
|
||||
// Skip leading sub parameters:
|
||||
if ((mArgsSubParamsBitSet & (1 << i)) != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int code = getArg(i, 0, false);
|
||||
if (code < 0) {
|
||||
if (mArgIndex > 0) {
|
||||
continue;
|
||||
|
|
@ -1784,7 +1880,19 @@ public final class TerminalEmulator {
|
|||
} else if (code == 3) {
|
||||
mEffect |= TextStyle.CHARACTER_ATTRIBUTE_ITALIC;
|
||||
} else if (code == 4) {
|
||||
mEffect |= TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE;
|
||||
if (i + 1 <= mArgIndex && ((mArgsSubParamsBitSet & (1 << (i + 1))) != 0)) {
|
||||
// Sub parameter, see https://sw.kovidgoyal.net/kitty/underlines/
|
||||
i++;
|
||||
if (mArgs[i] == 0) {
|
||||
// No underline.
|
||||
mEffect &= ~TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE;
|
||||
} else {
|
||||
// Different variations of underlines: https://sw.kovidgoyal.net/kitty/underlines/
|
||||
mEffect |= TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE;
|
||||
}
|
||||
} else {
|
||||
mEffect |= TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE;
|
||||
}
|
||||
} else if (code == 5) {
|
||||
mEffect |= TextStyle.CHARACTER_ATTRIBUTE_BLINK;
|
||||
} else if (code == 7) {
|
||||
|
|
@ -1813,8 +1921,8 @@ public final class TerminalEmulator {
|
|||
mEffect &= ~TextStyle.CHARACTER_ATTRIBUTE_STRIKETHROUGH;
|
||||
} else if (code >= 30 && code <= 37) {
|
||||
mForeColor = code - 30;
|
||||
} else if (code == 38 || code == 48) {
|
||||
// Extended set foreground(38)/background (48) color.
|
||||
} else if (code == 38 || code == 48 || code == 58) {
|
||||
// Extended set foreground(38)/background(48)/underline(58) color.
|
||||
// This is followed by either "2;$R;$G;$B" to set a 24-bit color or
|
||||
// "5;$INDEX" to set an indexed color.
|
||||
if (i + 2 > mArgIndex) continue;
|
||||
|
|
@ -1823,27 +1931,30 @@ public final class TerminalEmulator {
|
|||
if (i + 4 > mArgIndex) {
|
||||
Logger.logWarn(mClient, LOG_TAG, "Too few CSI" + code + ";2 RGB arguments");
|
||||
} else {
|
||||
int red = mArgs[i + 2], green = mArgs[i + 3], blue = mArgs[i + 4];
|
||||
int red = getArg(i + 2, 0, false);
|
||||
int green = getArg(i + 3, 0, false);
|
||||
int blue = getArg(i + 4, 0, false);
|
||||
|
||||
if (red < 0 || green < 0 || blue < 0 || red > 255 || green > 255 || blue > 255) {
|
||||
finishSequenceAndLogError("Invalid RGB: " + red + "," + green + "," + blue);
|
||||
} else {
|
||||
int argbColor = 0xff000000 | (red << 16) | (green << 8) | blue;
|
||||
if (code == 38) {
|
||||
mForeColor = argbColor;
|
||||
} else {
|
||||
mBackColor = argbColor;
|
||||
int argbColor = 0xff_00_00_00 | (red << 16) | (green << 8) | blue;
|
||||
switch (code) {
|
||||
case 38: mForeColor = argbColor; break;
|
||||
case 48: mBackColor = argbColor; break;
|
||||
case 58: mUnderlineColor = argbColor; break;
|
||||
}
|
||||
}
|
||||
i += 4; // "2;P_r;P_g;P_r"
|
||||
}
|
||||
} else if (firstArg == 5) {
|
||||
int color = mArgs[i + 2];
|
||||
int color = getArg(i + 2, 0, false);
|
||||
i += 2; // "5;P_s"
|
||||
if (color >= 0 && color < TextStyle.NUM_INDEXED_COLORS) {
|
||||
if (code == 38) {
|
||||
mForeColor = color;
|
||||
} else {
|
||||
mBackColor = color;
|
||||
switch (code) {
|
||||
case 38: mForeColor = color; break;
|
||||
case 48: mBackColor = color; break;
|
||||
case 58: mUnderlineColor = color; break;
|
||||
}
|
||||
} else {
|
||||
if (LOG_ESCAPE_SEQUENCES) Logger.logWarn(mClient, LOG_TAG, "Invalid color index: " + color);
|
||||
|
|
@ -1857,6 +1968,8 @@ public final class TerminalEmulator {
|
|||
mBackColor = code - 40;
|
||||
} else if (code == 49) { // Set default background color.
|
||||
mBackColor = TextStyle.COLOR_INDEX_BACKGROUND;
|
||||
} else if (code == 59) { // Set default underline color.
|
||||
mUnderlineColor = TextStyle.COLOR_INDEX_FOREGROUND;
|
||||
} else if (code >= 90 && code <= 97) { // Bright foreground colors (aixterm codes).
|
||||
mForeColor = code - 90 + 8;
|
||||
} else if (code >= 100 && code <= 107) { // Bright background color (aixterm codes).
|
||||
|
|
@ -2092,67 +2205,64 @@ public final class TerminalEmulator {
|
|||
|
||||
private void scrollDownOneLine() {
|
||||
mScrollCounter++;
|
||||
long currentStyle = getStyle();
|
||||
if (mLeftMargin != 0 || mRightMargin != mColumns) {
|
||||
// Horizontal margin: Do not put anything into scroll history, just non-margin part of screen up.
|
||||
mScreen.blockCopy(mLeftMargin, mTopMargin + 1, mRightMargin - mLeftMargin, mBottomMargin - mTopMargin - 1, mLeftMargin, mTopMargin);
|
||||
// .. and blank bottom row between margins:
|
||||
mScreen.blockSet(mLeftMargin, mBottomMargin - 1, mRightMargin - mLeftMargin, 1, ' ', mEffect);
|
||||
mScreen.blockSet(mLeftMargin, mBottomMargin - 1, mRightMargin - mLeftMargin, 1, ' ', currentStyle);
|
||||
} else {
|
||||
mScreen.scrollDownOneLine(mTopMargin, mBottomMargin, getStyle());
|
||||
mScreen.scrollDownOneLine(mTopMargin, mBottomMargin, currentStyle);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the next ASCII character of a parameter.
|
||||
*
|
||||
* Parameter characters modify the action or interpretation of the sequence. You can use up to
|
||||
* 16 parameters per sequence. You must use the ; character to separate parameters.
|
||||
* All parameters are unsigned, positive decimal integers, with the most significant
|
||||
* <p>You must use the ; character to separate parameters and : to separate sub-parameters.
|
||||
*
|
||||
* <p>Parameter characters modify the action or interpretation of the sequence. Originally
|
||||
* you can use up to 16 parameters per sequence, but following at least xterm and alacritty
|
||||
* we use a common space for parameters and sub-parameters, allowing 32 in total.
|
||||
*
|
||||
* <p>All parameters are unsigned, positive decimal integers, with the most significant
|
||||
* digit sent first. Any parameter greater than 9999 (decimal) is set to 9999
|
||||
* (decimal). If you do not specify a value, a 0 value is assumed. A 0 value
|
||||
* or omitted parameter indicates a default value for the sequence. For most
|
||||
* sequences, the default value is 1.
|
||||
*
|
||||
* https://vt100.net/docs/vt510-rm/chapter4.html#S4.3.3
|
||||
* <p>References:
|
||||
* <a href="https://vt100.net/docs/vt510-rm/chapter4.html#S4.3.3">VT510 Video Terminal Programmer Information: Control Sequences</a>
|
||||
* <a href="https://github.com/alacritty/vte/issues/22">alacritty/vte: Implement colon separated CSI parameters</a>
|
||||
* */
|
||||
private void parseArg(int inputByte) {
|
||||
int[] bytes = new int[]{inputByte};
|
||||
// Only doing this for ESC_CSI and not for other ESC_CSI_* since they seem to be using their
|
||||
// own defaults with getArg*() calls, but there may be missed cases
|
||||
if (mEscapeState == ESC_CSI) {
|
||||
if ((mIsCSIStart && inputByte == ';') || // If sequence starts with a ; character, like \033[;m
|
||||
(!mIsCSIStart && mLastCSIArg != null && mLastCSIArg == ';' && inputByte == ';')) { // If sequence contains sequential ; characters, like \033[;;m
|
||||
bytes = new int[]{'0', ';'}; // Assume 0 was passed
|
||||
private void parseArg(int b) {
|
||||
if (b >= '0' && b <= '9') {
|
||||
if (mArgIndex < mArgs.length) {
|
||||
int oldValue = mArgs[mArgIndex];
|
||||
int thisDigit = b - '0';
|
||||
int value;
|
||||
if (oldValue >= 0) {
|
||||
value = oldValue * 10 + thisDigit;
|
||||
} else {
|
||||
value = thisDigit;
|
||||
}
|
||||
if (value > 9999)
|
||||
value = 9999;
|
||||
mArgs[mArgIndex] = value;
|
||||
}
|
||||
}
|
||||
|
||||
mIsCSIStart = false;
|
||||
|
||||
for (int b : bytes) {
|
||||
if (b >= '0' && b <= '9') {
|
||||
if (mArgIndex < mArgs.length) {
|
||||
int oldValue = mArgs[mArgIndex];
|
||||
int thisDigit = b - '0';
|
||||
int value;
|
||||
if (oldValue >= 0) {
|
||||
value = oldValue * 10 + thisDigit;
|
||||
} else {
|
||||
value = thisDigit;
|
||||
}
|
||||
if (value > 9999)
|
||||
value = 9999;
|
||||
mArgs[mArgIndex] = value;
|
||||
continueSequence(mEscapeState);
|
||||
} else if (b == ';' || b == ':') {
|
||||
if (mArgIndex + 1 < mArgs.length) {
|
||||
mArgIndex++;
|
||||
if (b == ':') {
|
||||
mArgsSubParamsBitSet |= 1 << mArgIndex;
|
||||
}
|
||||
continueSequence(mEscapeState);
|
||||
} else if (b == ';') {
|
||||
if (mArgIndex < mArgs.length) {
|
||||
mArgIndex++;
|
||||
}
|
||||
continueSequence(mEscapeState);
|
||||
} else {
|
||||
unknownSequence(b);
|
||||
logError("Too many parameters when in state: " + mEscapeState);
|
||||
}
|
||||
mLastCSIArg = b;
|
||||
continueSequence(mEscapeState);
|
||||
} else {
|
||||
unknownSequence(b);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,11 +11,37 @@ public final class TerminalRow {
|
|||
|
||||
private static final float SPARE_CAPACITY_FACTOR = 1.5f;
|
||||
|
||||
/**
|
||||
* Max combining characters that can exist in a column, that are separate from the base character
|
||||
* itself. Any additional combining characters will be ignored and not added to the column.
|
||||
*
|
||||
* There does not seem to be limit in unicode standard for max number of combination characters
|
||||
* that can be combined but such characters are primarily under 10.
|
||||
*
|
||||
* "Section 3.6 Combination" of unicode standard contains combining characters info.
|
||||
* - https://www.unicode.org/versions/Unicode15.0.0/ch03.pdf
|
||||
* - https://en.wikipedia.org/wiki/Combining_character#Unicode_ranges
|
||||
* - https://stackoverflow.com/questions/71237212/what-is-the-maximum-number-of-unicode-combined-characters-that-may-be-needed-to
|
||||
*
|
||||
* UAX15-D3 Stream-Safe Text Format limits to max 30 combining characters.
|
||||
* > The value of 30 is chosen to be significantly beyond what is required for any linguistic or technical usage.
|
||||
* > While it would have been feasible to chose a smaller number, this value provides a very wide margin,
|
||||
* > yet is well within the buffer size limits of practical implementations.
|
||||
* - https://unicode.org/reports/tr15/#Stream_Safe_Text_Format
|
||||
* - https://stackoverflow.com/a/11983435/14686958
|
||||
*
|
||||
* We choose the value 15 because it should be enough for terminal based applications and keep
|
||||
* the memory usage low for a terminal row, won't affect performance or cause terminal to
|
||||
* lag or hang, and will keep malicious applications from causing harm. The value can be
|
||||
* increased if ever needed for legitimate applications.
|
||||
*/
|
||||
private static final int MAX_COMBINING_CHARACTERS_PER_COLUMN = 15;
|
||||
|
||||
/** The number of columns in this terminal row. */
|
||||
private final int mColumns;
|
||||
/** The text filling this terminal row. */
|
||||
public char[] mText;
|
||||
/** The number of java char:s used in {@link #mText}. */
|
||||
/** The number of java chars used in {@link #mText}. */
|
||||
private short mSpaceUsed;
|
||||
/** If this row has been line wrapped due to text output at the end of line. */
|
||||
boolean mLineWrap;
|
||||
|
|
@ -163,18 +189,25 @@ public final class TerminalRow {
|
|||
// Get the number of elements in the mText array this column uses now
|
||||
int oldCharactersUsedForColumn;
|
||||
if (columnToSet + oldCodePointDisplayWidth < mColumns) {
|
||||
oldCharactersUsedForColumn = findStartOfColumn(columnToSet + oldCodePointDisplayWidth) - oldStartOfColumnIndex;
|
||||
int oldEndOfColumnIndex = findStartOfColumn(columnToSet + oldCodePointDisplayWidth);
|
||||
oldCharactersUsedForColumn = oldEndOfColumnIndex - oldStartOfColumnIndex;
|
||||
} else {
|
||||
// Last character.
|
||||
oldCharactersUsedForColumn = mSpaceUsed - oldStartOfColumnIndex;
|
||||
}
|
||||
|
||||
// If MAX_COMBINING_CHARACTERS_PER_COLUMN already exist in column, then ignore adding additional combining characters.
|
||||
if (newIsCombining) {
|
||||
int combiningCharsCount = WcWidth.zeroWidthCharsCount(mText, oldStartOfColumnIndex, oldStartOfColumnIndex + oldCharactersUsedForColumn);
|
||||
if (combiningCharsCount >= MAX_COMBINING_CHARACTERS_PER_COLUMN)
|
||||
return;
|
||||
}
|
||||
|
||||
// Find how many chars this column will need
|
||||
int newCharactersUsedForColumn = Character.charCount(codePoint);
|
||||
if (newIsCombining) {
|
||||
// Combining characters are added to the contents of the column instead of overwriting them, so that they
|
||||
// modify the existing contents.
|
||||
// FIXME: Put a limit of combining characters.
|
||||
// FIXME: Unassigned characters also get width=0.
|
||||
newCharactersUsedForColumn += oldCharactersUsedForColumn;
|
||||
}
|
||||
|
|
@ -189,7 +222,7 @@ public final class TerminalRow {
|
|||
if (mSpaceUsed + javaCharDifference > text.length) {
|
||||
// We need to grow the array
|
||||
char[] newText = new char[text.length + mColumns];
|
||||
System.arraycopy(text, 0, newText, 0, oldStartOfColumnIndex + oldCharactersUsedForColumn);
|
||||
System.arraycopy(text, 0, newText, 0, oldNextColumnIndex);
|
||||
System.arraycopy(text, oldNextColumnIndex, newText, newNextColumnIndex, oldCharactersAfterColumn);
|
||||
mText = text = newText;
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import java.util.UUID;
|
|||
* A terminal session, consisting of a process coupled to a terminal interface.
|
||||
* <p>
|
||||
* The subprocess will be executed by the constructor, and when the size is made known by a call to
|
||||
* {@link #updateSize(int, int)} terminal emulation will begin and threads will be spawned to handle the subprocess I/O.
|
||||
* {@link #updateSize(int, int, int, int)} terminal emulation will begin and threads will be spawned to handle the subprocess I/O.
|
||||
* All terminal emulation and callback methods will be performed on the main thread.
|
||||
* <p>
|
||||
* The child process may be exited forcefully by using the {@link #finishIfRunning()} method.
|
||||
|
|
@ -61,7 +61,7 @@ public final class TerminalSession extends TerminalOutput {
|
|||
|
||||
/**
|
||||
* The file descriptor referencing the master half of a pseudo-terminal pair, resulting from calling
|
||||
* {@link JNI#createSubprocess(String, String, String[], String[], int[], int, int)}.
|
||||
* {@link JNI#createSubprocess(String, String, String[], String[], int[], int, int, int, int)}.
|
||||
*/
|
||||
private int mTerminalFileDescriptor;
|
||||
|
||||
|
|
@ -100,12 +100,12 @@ public final class TerminalSession extends TerminalOutput {
|
|||
}
|
||||
|
||||
/** Inform the attached pty of the new size and reflow or initialize the emulator. */
|
||||
public void updateSize(int columns, int rows) {
|
||||
public void updateSize(int columns, int rows, int cellWidthPixels, int cellHeightPixels) {
|
||||
if (mEmulator == null) {
|
||||
initializeEmulator(columns, rows);
|
||||
initializeEmulator(columns, rows, cellWidthPixels, cellHeightPixels);
|
||||
} else {
|
||||
JNI.setPtyWindowSize(mTerminalFileDescriptor, rows, columns);
|
||||
mEmulator.resize(columns, rows);
|
||||
JNI.setPtyWindowSize(mTerminalFileDescriptor, rows, columns, cellWidthPixels, cellHeightPixels);
|
||||
mEmulator.resize(columns, rows, cellWidthPixels, cellHeightPixels);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -120,11 +120,11 @@ public final class TerminalSession extends TerminalOutput {
|
|||
* @param columns The number of columns in the terminal window.
|
||||
* @param rows The number of rows in the terminal window.
|
||||
*/
|
||||
public void initializeEmulator(int columns, int rows) {
|
||||
mEmulator = new TerminalEmulator(this, columns, rows, mTranscriptRows, mClient);
|
||||
public void initializeEmulator(int columns, int rows, int cellWidthPixels, int cellHeightPixels) {
|
||||
mEmulator = new TerminalEmulator(this, columns, rows, cellWidthPixels, cellHeightPixels, mTranscriptRows, mClient);
|
||||
|
||||
int[] processId = new int[1];
|
||||
mTerminalFileDescriptor = JNI.createSubprocess(mShellPath, mCwd, mArgs, mEnv, processId, rows, columns);
|
||||
mTerminalFileDescriptor = JNI.createSubprocess(mShellPath, mCwd, mArgs, mEnv, processId, rows, columns, cellWidthPixels, cellHeightPixels);
|
||||
mShellPid = processId[0];
|
||||
mClient.setTerminalShellPid(this, mShellPid);
|
||||
|
||||
|
|
|
|||
|
|
@ -538,4 +538,29 @@ public final class WcWidth {
|
|||
return Character.isHighSurrogate(c) ? width(Character.toCodePoint(c, chars[index + 1])) : width(c);
|
||||
}
|
||||
|
||||
/**
|
||||
* The zero width characters count like combining characters in the `chars` array from start
|
||||
* index to end index (exclusive).
|
||||
*/
|
||||
public static int zeroWidthCharsCount(char[] chars, int start, int end) {
|
||||
if (start < 0 || start >= chars.length)
|
||||
return 0;
|
||||
|
||||
int count = 0;
|
||||
for (int i = start; i < end && i < chars.length;) {
|
||||
if (Character.isHighSurrogate(chars[i])) {
|
||||
if (width(Character.toCodePoint(chars[i], chars[i + 1])) <= 0) {
|
||||
count++;
|
||||
}
|
||||
i += 2;
|
||||
} else {
|
||||
if (width(chars[i]) <= 0) {
|
||||
count++;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,9 @@ static int create_subprocess(JNIEnv* env,
|
|||
char** envp,
|
||||
int* pProcessId,
|
||||
jint rows,
|
||||
jint columns)
|
||||
jint columns,
|
||||
jint cell_width,
|
||||
jint cell_height)
|
||||
{
|
||||
int ptm = open("/dev/ptmx", O_RDWR | O_CLOEXEC);
|
||||
if (ptm < 0) return throw_runtime_exception(env, "Cannot open /dev/ptmx");
|
||||
|
|
@ -57,7 +59,7 @@ static int create_subprocess(JNIEnv* env,
|
|||
tcsetattr(ptm, TCSANOW, &tios);
|
||||
|
||||
/** Set initial winsize. */
|
||||
struct winsize sz = { .ws_row = (unsigned short) rows, .ws_col = (unsigned short) columns };
|
||||
struct winsize sz = { .ws_row = (unsigned short) rows, .ws_col = (unsigned short) columns, .ws_xpixel = (unsigned short) (columns * cell_width), .ws_ypixel = (unsigned short) (rows * cell_height)};
|
||||
ioctl(ptm, TIOCSWINSZ, &sz);
|
||||
|
||||
pid_t pid = fork();
|
||||
|
|
@ -121,7 +123,9 @@ JNIEXPORT jint JNICALL Java_com_termux_terminal_JNI_createSubprocess(
|
|||
jobjectArray envVars,
|
||||
jintArray processIdArray,
|
||||
jint rows,
|
||||
jint columns)
|
||||
jint columns,
|
||||
jint cell_width,
|
||||
jint cell_height)
|
||||
{
|
||||
jsize size = args ? (*env)->GetArrayLength(env, args) : 0;
|
||||
char** argv = NULL;
|
||||
|
|
@ -156,7 +160,7 @@ JNIEXPORT jint JNICALL Java_com_termux_terminal_JNI_createSubprocess(
|
|||
int procId = 0;
|
||||
char const* cmd_cwd = (*env)->GetStringUTFChars(env, cwd, NULL);
|
||||
char const* cmd_utf8 = (*env)->GetStringUTFChars(env, cmd, NULL);
|
||||
int ptm = create_subprocess(env, cmd_utf8, cmd_cwd, argv, envp, &procId, rows, columns);
|
||||
int ptm = create_subprocess(env, cmd_utf8, cmd_cwd, argv, envp, &procId, rows, columns, cell_width, cell_height);
|
||||
(*env)->ReleaseStringUTFChars(env, cmd, cmd_utf8);
|
||||
(*env)->ReleaseStringUTFChars(env, cmd, cmd_cwd);
|
||||
|
||||
|
|
@ -178,9 +182,9 @@ JNIEXPORT jint JNICALL Java_com_termux_terminal_JNI_createSubprocess(
|
|||
return ptm;
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_com_termux_terminal_JNI_setPtyWindowSize(JNIEnv* TERMUX_UNUSED(env), jclass TERMUX_UNUSED(clazz), jint fd, jint rows, jint cols)
|
||||
JNIEXPORT void JNICALL Java_com_termux_terminal_JNI_setPtyWindowSize(JNIEnv* TERMUX_UNUSED(env), jclass TERMUX_UNUSED(clazz), jint fd, jint rows, jint cols, jint cell_width, jint cell_height)
|
||||
{
|
||||
struct winsize sz = { .ws_row = (unsigned short) rows, .ws_col = (unsigned short) cols };
|
||||
struct winsize sz = { .ws_row = (unsigned short) rows, .ws_col = (unsigned short) cols, .ws_xpixel = (unsigned short) (cols * cell_width), .ws_ypixel = (unsigned short) (rows * cell_height) };
|
||||
ioctl(fd, TIOCSWINSZ, &sz);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
package com.termux.terminal;
|
||||
|
||||
public class ApcTest extends TerminalTestCase {
|
||||
|
||||
public void testApcConsumed() {
|
||||
// At time of writing this is part of what yazi sends for probing for kitty graphics protocol support:
|
||||
// https://github.com/sxyazi/yazi/blob/0cdaff98d0b3723caff63eebf1974e7907a43a2c/yazi-adapter/src/emulator.rs#L129
|
||||
// This should not result in anything being written to the screen: If kitty graphics protocol support
|
||||
// is implemented it should instead result in an error code on stdin, and if not it should be consumed
|
||||
// silently just as xterm does. See https://sw.kovidgoyal.net/kitty/graphics-protocol/.
|
||||
withTerminalSized(2, 2)
|
||||
.enterString("\033_Gi=31,s=1,v=1,a=q,t=d,f=24;AAAA\033\\")
|
||||
.assertLinesAre(" ", " ");
|
||||
|
||||
// It is ok for the APC content to be non printable characters:
|
||||
withTerminalSized(12, 2)
|
||||
.enterString("hello \033_some\023\033_\\apc#end\033\\ world")
|
||||
.assertLinesAre("hello world", " ");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
package com.termux.terminal;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/** "\033[" is the Control Sequence Introducer char sequence (CSI). */
|
||||
public class ControlSequenceIntroducerTest extends TerminalTestCase {
|
||||
|
||||
|
|
@ -62,4 +64,68 @@ public class ControlSequenceIntroducerTest extends TerminalTestCase {
|
|||
assertEquals("y\nz", mTerminal.getScreen().getTranscriptText());
|
||||
}
|
||||
|
||||
public void testReportPixelSize() {
|
||||
int columns = 3;
|
||||
int rows = 3;
|
||||
withTerminalSized(columns, rows);
|
||||
int cellWidth = TerminalTest.INITIAL_CELL_WIDTH_PIXELS;
|
||||
int cellHeight = TerminalTest.INITIAL_CELL_HEIGHT_PIXELS;
|
||||
assertEnteringStringGivesResponse("\033[14t", "\033[4;" + (rows*cellHeight) + ";" + (columns*cellWidth) + "t");
|
||||
assertEnteringStringGivesResponse("\033[16t", "\033[6;" + cellHeight + ";" + cellWidth + "t");
|
||||
columns = 23;
|
||||
rows = 33;
|
||||
resize(columns, rows);
|
||||
assertEnteringStringGivesResponse("\033[14t", "\033[4;" + (rows*cellHeight) + ";" + (columns*cellWidth) + "t");
|
||||
assertEnteringStringGivesResponse("\033[16t", "\033[6;" + cellHeight + ";" + cellWidth + "t");
|
||||
cellWidth = 8;
|
||||
cellHeight = 18;
|
||||
mTerminal.resize(columns, rows, cellWidth, cellHeight);
|
||||
assertEnteringStringGivesResponse("\033[14t", "\033[4;" + (rows*cellHeight) + ";" + (columns*cellWidth) + "t");
|
||||
assertEnteringStringGivesResponse("\033[16t", "\033[6;" + cellHeight + ";" + cellWidth + "t");
|
||||
}
|
||||
|
||||
/**
|
||||
* See <a href="https://sw.kovidgoyal.net/kitty/underlines/">Colored and styled underlines</a>:
|
||||
*
|
||||
* <pre>
|
||||
* <ESC>[4:0m # no underline
|
||||
* <ESC>[4:1m # straight underline
|
||||
* <ESC>[4:2m # double underline
|
||||
* <ESC>[4:3m # curly underline
|
||||
* <ESC>[4:4m # dotted underline
|
||||
* <ESC>[4:5m # dashed underline
|
||||
* <ESC>[4m # straight underline (for backwards compat)
|
||||
* <ESC>[24m # no underline (for backwards compat)
|
||||
* </pre>
|
||||
* <p>
|
||||
* We currently parse the variants, but map them to normal/no underlines as appropriate
|
||||
*/
|
||||
public void testUnderlineVariants() {
|
||||
for (String suffix : List.of("", ":1", ":2", ":3", ":4", ":5")) {
|
||||
for (String stop : List.of("24", "4:0")) {
|
||||
withTerminalSized(3, 3);
|
||||
enterString("\033[4" + suffix + "m").assertLinesAre(" ", " ", " ");
|
||||
assertEquals(TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE, mTerminal.mEffect);
|
||||
enterString("\033[4;1m").assertLinesAre(" ", " ", " ");
|
||||
assertEquals(TextStyle.CHARACTER_ATTRIBUTE_BOLD | TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE, mTerminal.mEffect);
|
||||
enterString("\033[" + stop + "m").assertLinesAre(" ", " ", " ");
|
||||
assertEquals(TextStyle.CHARACTER_ATTRIBUTE_BOLD, mTerminal.mEffect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void testManyParameters() {
|
||||
StringBuilder b = new StringBuilder("\033[");
|
||||
for (int i = 0; i < 30; i++) {
|
||||
b.append("0;");
|
||||
}
|
||||
b.append("4:2");
|
||||
// This clearing of underline should be ignored as the parameters pass the threshold for too many parameters:
|
||||
b.append("4:0m");
|
||||
withTerminalSized(3, 3)
|
||||
.enterString(b.toString())
|
||||
.assertLinesAre(" ", " ", " ");
|
||||
assertEquals(TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE, mTerminal.mEffect);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,10 +11,10 @@ public class HistoryTest extends TerminalTestCase {
|
|||
assertLinesAre("777", "888", "999");
|
||||
assertHistoryStartsWith("666", "555");
|
||||
|
||||
mTerminal.resize(cols, 2);
|
||||
resize(cols, 2);
|
||||
assertHistoryStartsWith("777", "666", "555");
|
||||
|
||||
mTerminal.resize(cols, 3);
|
||||
resize(cols, 3);
|
||||
assertHistoryStartsWith("666", "555");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -72,11 +72,11 @@ public class ResizeTest extends TerminalTestCase {
|
|||
enterString("\r\n");
|
||||
}
|
||||
assertLinesAre("998 ", "999 ", " ");
|
||||
mTerminal.resize(cols, 2);
|
||||
resize(cols, 2);
|
||||
assertLinesAre("999 ", " ");
|
||||
mTerminal.resize(cols, 5);
|
||||
resize(cols, 5);
|
||||
assertLinesAre("996 ", "997 ", "998 ", "999 ", " ");
|
||||
mTerminal.resize(cols, rows);
|
||||
resize(cols, rows);
|
||||
assertLinesAre("998 ", "999 ", " ");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -75,6 +75,16 @@ public class ScrollRegionTest extends TerminalTestCase {
|
|||
withTerminalSized(3, 3).enterString("\033[?69h\033[2sABC\033[?6h\033ED").assertLinesAre("ABC", " D ", " ");
|
||||
}
|
||||
|
||||
public void testRiRespectsLeftMargin() {
|
||||
// Reverse Index (RI), ${ESC}M, should respect horizontal margins:
|
||||
withTerminalSized(4, 3).enterString("ABCD\033[?69h\033[2;3s\033[?6h\033M").assertLinesAre("A D", " BC ", " ");
|
||||
}
|
||||
|
||||
public void testSdRespectsLeftMargin() {
|
||||
// Scroll Down (SD), ${CSI}${N}T, should respect horizontal margins:
|
||||
withTerminalSized(4, 3).enterString("ABCD\033[?69h\033[2;3s\033[?6h\033[2T").assertLinesAre("A D", " ", " BC ");
|
||||
}
|
||||
|
||||
public void testBackwardIndex() {
|
||||
// vttest "Menu 11.3.2: VT420 Cursor-Movement Test", test 7.
|
||||
// Without margins:
|
||||
|
|
@ -127,4 +137,31 @@ public class ScrollRegionTest extends TerminalTestCase {
|
|||
" xxx"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* See <a href="https://github.com/termux/termux-packages/issues/12556">reported issue</a>.
|
||||
*/
|
||||
public void testClearingWhenScrollingWithMargins() {
|
||||
int newForeground = 2;
|
||||
int newBackground = 3;
|
||||
int size = 3;
|
||||
TerminalTestCase terminal = withTerminalSized(size, size)
|
||||
// Enable horizontal margin and set left margin to 1:
|
||||
.enterString("\033[?69h\033[2s")
|
||||
// Set foreground and background color:
|
||||
.enterString("\033[" + (30 + newForeground) + ";" + (40 + newBackground) + "m")
|
||||
// Enter newlines to scroll down:
|
||||
.enterString("\r\n\r\n\r\n\r\n\r\n");
|
||||
for (int row = 0; row < size; row++) {
|
||||
for (int col = 0; col < size; col++) {
|
||||
// The first column (outside of the scrolling area, due to us setting a left scroll
|
||||
// margin of 1) should be unmodified, the others should use the current style:
|
||||
int expectedForeground = col == 0 ? TextStyle.COLOR_INDEX_FOREGROUND : newForeground;
|
||||
int expectedBackground = col == 0 ? TextStyle.COLOR_INDEX_BACKGROUND : newBackground;
|
||||
terminal.assertForegroundColorAt(row, col, expectedForeground);
|
||||
terminal.assertBackgroundColorAt(row, col, expectedBackground);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ public class TerminalTest extends TerminalTestCase {
|
|||
assertEnteringStringGivesResponse("\033[18t", "\033[8;5;5t");
|
||||
for (int width = 3; width < 12; width++) {
|
||||
for (int height = 3; height < 12; height++) {
|
||||
mTerminal.resize(width, height);
|
||||
resize(width, height);
|
||||
assertEnteringStringGivesResponse("\033[18t", "\033[8;" + height + ";" + width + "t");
|
||||
}
|
||||
}
|
||||
|
|
@ -137,6 +137,11 @@ public class TerminalTest extends TerminalTestCase {
|
|||
}
|
||||
|
||||
public void testSelectGraphics() {
|
||||
selectGraphicsTestRun(';');
|
||||
selectGraphicsTestRun(':');
|
||||
}
|
||||
|
||||
public void selectGraphicsTestRun(char separator) {
|
||||
withTerminalSized(5, 5);
|
||||
enterString("\033[31m");
|
||||
assertEquals(mTerminal.mForeColor, 1);
|
||||
|
|
@ -155,40 +160,59 @@ public class TerminalTest extends TerminalTestCase {
|
|||
// Check TerminalEmulator.parseArg()
|
||||
enterString("\033[31m\033[m");
|
||||
assertEquals(TextStyle.COLOR_INDEX_FOREGROUND, mTerminal.mForeColor);
|
||||
enterString("\033[31m\033[;m");
|
||||
enterString("\033[31m\033[;m".replace(';', separator));
|
||||
assertEquals(TextStyle.COLOR_INDEX_FOREGROUND, mTerminal.mForeColor);
|
||||
enterString("\033[31m\033[0m");
|
||||
assertEquals(TextStyle.COLOR_INDEX_FOREGROUND, mTerminal.mForeColor);
|
||||
enterString("\033[31m\033[0;m");
|
||||
enterString("\033[31m\033[0;m".replace(';', separator));
|
||||
assertEquals(TextStyle.COLOR_INDEX_FOREGROUND, mTerminal.mForeColor);
|
||||
enterString("\033[31;;m");
|
||||
assertEquals(TextStyle.COLOR_INDEX_FOREGROUND, mTerminal.mForeColor);
|
||||
enterString("\033[31::m");
|
||||
assertEquals(1, mTerminal.mForeColor);
|
||||
enterString("\033[31;m");
|
||||
assertEquals(TextStyle.COLOR_INDEX_FOREGROUND, mTerminal.mForeColor);
|
||||
enterString("\033[31:m");
|
||||
assertEquals(1, mTerminal.mForeColor);
|
||||
enterString("\033[31;;41m");
|
||||
assertEquals(TextStyle.COLOR_INDEX_FOREGROUND, mTerminal.mForeColor);
|
||||
assertEquals(1, mTerminal.mBackColor);
|
||||
enterString("\033[0m");
|
||||
assertEquals(TextStyle.COLOR_INDEX_BACKGROUND, mTerminal.mBackColor);
|
||||
|
||||
// 256 colors:
|
||||
enterString("\033[38;5;119m");
|
||||
enterString("\033[38;5;119m".replace(';', separator));
|
||||
assertEquals(119, mTerminal.mForeColor);
|
||||
assertEquals(TextStyle.COLOR_INDEX_BACKGROUND, mTerminal.mBackColor);
|
||||
enterString("\033[48;5;129m");
|
||||
enterString("\033[48;5;129m".replace(';', separator));
|
||||
assertEquals(119, mTerminal.mForeColor);
|
||||
assertEquals(129, mTerminal.mBackColor);
|
||||
|
||||
// Invalid parameter:
|
||||
enterString("\033[48;8;129m");
|
||||
enterString("\033[48;8;129m".replace(';', separator));
|
||||
assertEquals(119, mTerminal.mForeColor);
|
||||
assertEquals(129, mTerminal.mBackColor);
|
||||
|
||||
// Multiple parameters at once:
|
||||
enterString("\033[38;5;178;48;5;179;m");
|
||||
enterString("\033[38;5;178".replace(';', separator) + ";" + "48;5;179m".replace(';', separator));
|
||||
assertEquals(178, mTerminal.mForeColor);
|
||||
assertEquals(179, mTerminal.mBackColor);
|
||||
|
||||
// Omitted parameter means zero:
|
||||
enterString("\033[38;5;m".replace(';', separator));
|
||||
assertEquals(0, mTerminal.mForeColor);
|
||||
assertEquals(179, mTerminal.mBackColor);
|
||||
enterString("\033[48;5;m".replace(';', separator));
|
||||
assertEquals(0, mTerminal.mForeColor);
|
||||
assertEquals(0, mTerminal.mBackColor);
|
||||
|
||||
// 24 bit colors:
|
||||
enterString(("\033[0m")); // Reset fg and bg colors.
|
||||
enterString("\033[38;2;255;127;2m");
|
||||
enterString("\033[38;2;255;127;2m".replace(';', separator));
|
||||
int expectedForeground = 0xff000000 | (255 << 16) | (127 << 8) | 2;
|
||||
assertEquals(expectedForeground, mTerminal.mForeColor);
|
||||
assertEquals(TextStyle.COLOR_INDEX_BACKGROUND, mTerminal.mBackColor);
|
||||
enterString("\033[48;2;1;2;254m");
|
||||
enterString("\033[48;2;1;2;254m".replace(';', separator));
|
||||
int expectedBackground = 0xff000000 | (1 << 16) | (2 << 8) | 254;
|
||||
assertEquals(expectedForeground, mTerminal.mForeColor);
|
||||
assertEquals(expectedBackground, mTerminal.mBackColor);
|
||||
|
|
@ -197,14 +221,30 @@ public class TerminalTest extends TerminalTestCase {
|
|||
enterString(("\033[0m")); // Reset fg and bg colors.
|
||||
assertEquals(TextStyle.COLOR_INDEX_FOREGROUND, mTerminal.mForeColor);
|
||||
assertEquals(TextStyle.COLOR_INDEX_BACKGROUND, mTerminal.mBackColor);
|
||||
enterString("\033[38;2;255;127;2;48;2;1;2;254m");
|
||||
enterString("\033[38;2;255;127;2".replace(';', separator) + ";" + "48;2;1;2;254m".replace(';', separator));
|
||||
assertEquals(expectedForeground, mTerminal.mForeColor);
|
||||
assertEquals(expectedBackground, mTerminal.mBackColor);
|
||||
|
||||
// 24 bit colors, invalid input:
|
||||
enterString("\033[38;2;300;127;2;48;2;1;300;254m");
|
||||
enterString("\033[38;2;300;127;2;48;2;1;300;254m".replace(';', separator));
|
||||
assertEquals(expectedForeground, mTerminal.mForeColor);
|
||||
assertEquals(expectedBackground, mTerminal.mBackColor);
|
||||
|
||||
// 24 bit colors, omitted parameter means zero:
|
||||
enterString("\033[38;2;255;127;m".replace(';', separator));
|
||||
expectedForeground = 0xff000000 | (255 << 16) | (127 << 8);
|
||||
assertEquals(expectedForeground, mTerminal.mForeColor);
|
||||
assertEquals(expectedBackground, mTerminal.mBackColor);
|
||||
enterString("\033[38;2;123;;77m".replace(';', separator));
|
||||
expectedForeground = 0xff000000 | (123 << 16) | 77;
|
||||
assertEquals(expectedForeground, mTerminal.mForeColor);
|
||||
assertEquals(expectedBackground, mTerminal.mBackColor);
|
||||
|
||||
// 24 bit colors, extra sub-parameters are skipped:
|
||||
expectedForeground = 0xff000000 | (255 << 16) | (127 << 8) | 2;
|
||||
enterString("\033[0;38:2:255:127:2:48:2:1:2:254m");
|
||||
assertEquals(expectedForeground, mTerminal.mForeColor);
|
||||
assertEquals(TextStyle.COLOR_INDEX_BACKGROUND, mTerminal.mBackColor);
|
||||
}
|
||||
|
||||
public void testBackgroundColorErase() {
|
||||
|
|
|
|||
|
|
@ -13,7 +13,10 @@ import java.util.Set;
|
|||
|
||||
public abstract class TerminalTestCase extends TestCase {
|
||||
|
||||
public static class MockTerminalOutput extends TerminalOutput {
|
||||
public static final int INITIAL_CELL_WIDTH_PIXELS = 13;
|
||||
public static final int INITIAL_CELL_HEIGHT_PIXELS = 15;
|
||||
|
||||
public static class MockTerminalOutput extends TerminalOutput {
|
||||
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
public final List<ChangedTitle> titleChanges = new ArrayList<>();
|
||||
public final List<String> clipboardPuts = new ArrayList<>();
|
||||
|
|
@ -108,7 +111,7 @@ public abstract class TerminalTestCase extends TestCase {
|
|||
|
||||
protected TerminalTestCase withTerminalSized(int columns, int rows) {
|
||||
// The tests aren't currently using the client, so a null client will suffice, a dummy client should be implemented if needed
|
||||
mTerminal = new TerminalEmulator(mOutput, columns, rows, rows * 2, null);
|
||||
mTerminal = new TerminalEmulator(mOutput, columns, rows, INITIAL_CELL_WIDTH_PIXELS, INITIAL_CELL_HEIGHT_PIXELS, rows * 2, null);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
@ -201,7 +204,7 @@ public abstract class TerminalTestCase extends TestCase {
|
|||
}
|
||||
|
||||
public TerminalTestCase resize(int cols, int rows) {
|
||||
mTerminal.resize(cols, rows);
|
||||
mTerminal.resize(cols, rows, INITIAL_CELL_WIDTH_PIXELS, INITIAL_CELL_HEIGHT_PIXELS);
|
||||
assertInvariants();
|
||||
return this;
|
||||
}
|
||||
|
|
@ -301,6 +304,11 @@ public abstract class TerminalTestCase extends TestCase {
|
|||
assertEquals(color, TextStyle.decodeForeColor(style));
|
||||
}
|
||||
|
||||
public void assertBackgroundColorAt(int externalRow, int column, int color) {
|
||||
long style = mTerminal.getScreen().mLines[mTerminal.getScreen().externalToInternalRow(externalRow)].getStyle(column);
|
||||
assertEquals(color, TextStyle.decodeBackColor(style));
|
||||
}
|
||||
|
||||
public TerminalTestCase assertColor(int colorIndex, int expected) {
|
||||
int actual = mTerminal.mColors.mCurrentColors[colorIndex];
|
||||
if (expected != actual) {
|
||||
|
|
|
|||
|
|
@ -2,10 +2,11 @@ apply plugin: 'com.android.library'
|
|||
apply plugin: 'maven-publish'
|
||||
|
||||
android {
|
||||
namespace "com.termux.view"
|
||||
compileSdkVersion project.properties.compileSdkVersion.toInteger()
|
||||
|
||||
dependencies {
|
||||
implementation "androidx.annotation:annotation:1.3.0"
|
||||
implementation "androidx.annotation:annotation:1.9.0"
|
||||
api project(":terminal-emulator")
|
||||
}
|
||||
|
||||
|
|
@ -34,7 +35,7 @@ dependencies {
|
|||
|
||||
task sourceJar(type: Jar) {
|
||||
from android.sourceSets.main.java.srcDirs
|
||||
classifier "sources"
|
||||
archiveClassifier = "sources"
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
|
|
@ -42,7 +43,7 @@ afterEvaluate {
|
|||
publications {
|
||||
// Creates a Maven publication called "release".
|
||||
release(MavenPublication) {
|
||||
from components.release
|
||||
from components.findByName('release')
|
||||
groupId = 'com.termux'
|
||||
artifactId = 'terminal-view'
|
||||
version = '0.118.0'
|
||||
|
|
@ -51,3 +52,4 @@ afterEvaluate {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
<manifest package="com.termux.view">
|
||||
<manifest>
|
||||
</manifest>
|
||||
|
|
|
|||
|
|
@ -233,7 +233,7 @@ public final class TerminalRenderer {
|
|||
mTextPaint.setColor(foreColor);
|
||||
|
||||
// The text alignment is the default Paint.Align.LEFT.
|
||||
canvas.drawText(text, startCharIndex, runWidthChars, left, y - mFontLineSpacingAndAscent, mTextPaint);
|
||||
canvas.drawTextRun(text, startCharIndex, runWidthChars, startCharIndex, runWidthChars, left, y - mFontLineSpacingAndAscent, false, mTextPaint);
|
||||
}
|
||||
|
||||
if (savedMatrix) canvas.restore();
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import android.graphics.Typeface;
|
|||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.SystemClock;
|
||||
import android.text.Editable;
|
||||
import android.text.InputType;
|
||||
import android.text.TextUtils;
|
||||
|
|
@ -26,6 +27,7 @@ import android.view.View;
|
|||
import android.view.ViewConfiguration;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.view.accessibility.AccessibilityManager;
|
||||
import android.view.autofill.AutofillManager;
|
||||
import android.view.autofill.AutofillValue;
|
||||
import android.view.inputmethod.BaseInputConnection;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
|
|
@ -84,6 +86,43 @@ public final class TerminalView extends View {
|
|||
/** If non-zero, this is the last unicode code point received if that was a combining character. */
|
||||
int mCombiningAccent;
|
||||
|
||||
/**
|
||||
* The current AutoFill type returned for {@link View#getAutofillType()} by {@link #getAutofillType()}.
|
||||
*
|
||||
* The default is {@link #AUTOFILL_TYPE_NONE} so that AutoFill UI, like toolbar above keyboard
|
||||
* is not shown automatically, like on Activity starts/View create. This value should be updated
|
||||
* to required value, like {@link #AUTOFILL_TYPE_TEXT} before calling
|
||||
* {@link AutofillManager#requestAutofill(View)} so that AutoFill UI shows. The updated value
|
||||
* set will automatically be restored to {@link #AUTOFILL_TYPE_NONE} in
|
||||
* {@link #autofill(AutofillValue)} so that AutoFill UI isn't shown anymore by calling
|
||||
* {@link #resetAutoFill()}.
|
||||
*/
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
private int mAutoFillType = AUTOFILL_TYPE_NONE;
|
||||
|
||||
/**
|
||||
* The current AutoFill type returned for {@link View#getImportantForAutofill()} by
|
||||
* {@link #getImportantForAutofill()}.
|
||||
*
|
||||
* The default is {@link #IMPORTANT_FOR_AUTOFILL_NO} so that view is not considered important
|
||||
* for AutoFill. This value should be updated to required value, like
|
||||
* {@link #IMPORTANT_FOR_AUTOFILL_YES} before calling {@link AutofillManager#requestAutofill(View)}
|
||||
* so that Android and apps consider the view as important for AutoFill to process the request.
|
||||
* The updated value set will automatically be restored to {@link #IMPORTANT_FOR_AUTOFILL_NO} in
|
||||
* {@link #autofill(AutofillValue)} by calling {@link #resetAutoFill()}.
|
||||
*/
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
private int mAutoFillImportance = IMPORTANT_FOR_AUTOFILL_NO;
|
||||
|
||||
/**
|
||||
* The current AutoFill hints returned for {@link View#getAutofillHints()} ()} by {@link #getAutofillHints()} ()}.
|
||||
*
|
||||
* The default is an empty `string[]`. This value should be updated to required value. The
|
||||
* updated value set will automatically be restored an empty `string[]` in
|
||||
* {@link #autofill(AutofillValue)} by calling {@link #resetAutoFill()}.
|
||||
*/
|
||||
private String[] mAutoFillHints = new String[0];
|
||||
|
||||
private final boolean mAccessibilityEnabled;
|
||||
|
||||
/** The {@link KeyEvent} is generated from a virtual keyboard, like manually with the {@link KeyEvent#KeyEvent(int, int)} constructor. */
|
||||
|
|
@ -608,6 +647,7 @@ public final class TerminalView extends View {
|
|||
if (TERMINAL_VIEW_KEY_LOGGING_ENABLED)
|
||||
mClient.logInfo(LOG_TAG, "onKeyPreIme(keyCode=" + keyCode + ", event=" + event + ")");
|
||||
if (keyCode == KeyEvent.KEYCODE_BACK) {
|
||||
cancelRequestAutoFill();
|
||||
if (isSelectingText()) {
|
||||
stopTextSelectionMode();
|
||||
return true;
|
||||
|
|
@ -872,6 +912,9 @@ public final class TerminalView extends View {
|
|||
if (mEmulator != null)
|
||||
mEmulator.setCursorBlinkState(true);
|
||||
|
||||
if (handleKeyCodeAction(keyCode, keyMod))
|
||||
return true;
|
||||
|
||||
TerminalEmulator term = mTermSession.getEmulator();
|
||||
String code = KeyHandler.getCode(keyCode, keyMod, term.isCursorKeysApplicationMode(), term.isKeypadApplicationMode());
|
||||
if (code == null) return false;
|
||||
|
|
@ -879,6 +922,26 @@ public final class TerminalView extends View {
|
|||
return true;
|
||||
}
|
||||
|
||||
public boolean handleKeyCodeAction(int keyCode, int keyMod) {
|
||||
boolean shiftDown = (keyMod & KeyHandler.KEYMOD_SHIFT) != 0;
|
||||
|
||||
switch (keyCode) {
|
||||
case KeyEvent.KEYCODE_PAGE_UP:
|
||||
case KeyEvent.KEYCODE_PAGE_DOWN:
|
||||
// shift+page_up and shift+page_down should scroll scrollback history instead of
|
||||
// scrolling command history or changing pages
|
||||
if (shiftDown) {
|
||||
long time = SystemClock.uptimeMillis();
|
||||
MotionEvent motionEvent = MotionEvent.obtain(time, time, MotionEvent.ACTION_DOWN, 0, 0, 0);
|
||||
doScroll(motionEvent, keyCode == KeyEvent.KEYCODE_PAGE_UP ? -mEmulator.mRows : mEmulator.mRows);
|
||||
motionEvent.recycle();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a key is released in the view.
|
||||
*
|
||||
|
|
@ -926,7 +989,7 @@ public final class TerminalView extends View {
|
|||
int newRows = Math.max(4, (viewHeight - mRenderer.mFontLineSpacingAndAscent) / mRenderer.mFontLineSpacing);
|
||||
|
||||
if (mEmulator == null || (newColumns != mEmulator.mColumns || newRows != mEmulator.mRows)) {
|
||||
mTermSession.updateSize(newColumns, newRows);
|
||||
mTermSession.updateSize(newColumns, newRows, (int) mRenderer.getFontWidth(), mRenderer.getFontLineSpacing());
|
||||
mEmulator = mTermSession.getEmulator();
|
||||
mClient.onEmulatorSet();
|
||||
|
||||
|
|
@ -1004,12 +1067,20 @@ public final class TerminalView extends View {
|
|||
if (value.isText()) {
|
||||
mTermSession.write(value.getTextValue().toString());
|
||||
}
|
||||
|
||||
resetAutoFill();
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
@Override
|
||||
public int getAutofillType() {
|
||||
return AUTOFILL_TYPE_TEXT;
|
||||
return mAutoFillType;
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
@Override
|
||||
public String[] getAutofillHints() {
|
||||
return mAutoFillHints;
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
|
|
@ -1018,6 +1089,95 @@ public final class TerminalView extends View {
|
|||
return AutofillValue.forText("");
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
@Override
|
||||
public int getImportantForAutofill() {
|
||||
return mAutoFillImportance;
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
private synchronized void resetAutoFill() {
|
||||
// Restore none type so that AutoFill UI isn't shown anymore.
|
||||
mAutoFillType = AUTOFILL_TYPE_NONE;
|
||||
mAutoFillImportance = IMPORTANT_FOR_AUTOFILL_NO;
|
||||
mAutoFillHints = new String[0];
|
||||
}
|
||||
|
||||
public AutofillManager getAutoFillManagerService() {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return null;
|
||||
|
||||
try {
|
||||
Context context = getContext();
|
||||
if (context == null) return null;
|
||||
return context.getSystemService(AutofillManager.class);
|
||||
} catch (Exception e) {
|
||||
mClient.logStackTraceWithMessage(LOG_TAG, "Failed to get AutofillManager service", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isAutoFillEnabled() {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return false;
|
||||
|
||||
try {
|
||||
AutofillManager autofillManager = getAutoFillManagerService();
|
||||
return autofillManager != null && autofillManager.isEnabled();
|
||||
} catch (Exception e) {
|
||||
mClient.logStackTraceWithMessage(LOG_TAG, "Failed to check if Autofill is enabled", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void requestAutoFillUsername() {
|
||||
requestAutoFill(
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ? new String[]{View.AUTOFILL_HINT_USERNAME} :
|
||||
null);
|
||||
}
|
||||
|
||||
public synchronized void requestAutoFillPassword() {
|
||||
requestAutoFill(
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ? new String[]{View.AUTOFILL_HINT_PASSWORD} :
|
||||
null);
|
||||
}
|
||||
|
||||
public synchronized void requestAutoFill(String[] autoFillHints) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return;
|
||||
if (autoFillHints == null || autoFillHints.length < 1) return;
|
||||
|
||||
try {
|
||||
AutofillManager autofillManager = getAutoFillManagerService();
|
||||
if (autofillManager != null && autofillManager.isEnabled()) {
|
||||
// Update type that will be returned by `getAutofillType()` so that AutoFill UI is shown.
|
||||
mAutoFillType = AUTOFILL_TYPE_TEXT;
|
||||
// Update importance that will be returned by `getImportantForAutofill()` so that
|
||||
// AutoFill considers the view as important.
|
||||
mAutoFillImportance = IMPORTANT_FOR_AUTOFILL_YES;
|
||||
// Update hints that will be returned by `getAutofillHints()` for which to show AutoFill UI.
|
||||
mAutoFillHints = autoFillHints;
|
||||
autofillManager.requestAutofill(this);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
mClient.logStackTraceWithMessage(LOG_TAG, "Failed to request Autofill", e);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void cancelRequestAutoFill() {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return;
|
||||
if (mAutoFillType == AUTOFILL_TYPE_NONE) return;
|
||||
|
||||
try {
|
||||
AutofillManager autofillManager = getAutoFillManagerService();
|
||||
if (autofillManager != null && autofillManager.isEnabled()) {
|
||||
resetAutoFill();
|
||||
autofillManager.cancel();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
mClient.logStackTraceWithMessage(LOG_TAG, "Failed to cancel Autofill request", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -2,23 +2,24 @@ apply plugin: 'com.android.library'
|
|||
apply plugin: 'maven-publish'
|
||||
|
||||
android {
|
||||
namespace = "com.termux.shared"
|
||||
|
||||
compileSdkVersion project.properties.compileSdkVersion.toInteger()
|
||||
ndkVersion = System.getenv("JITPACK_NDK_VERSION") ?: project.properties.ndkVersion
|
||||
|
||||
dependencies {
|
||||
implementation "androidx.appcompat:appcompat:1.3.1"
|
||||
implementation "androidx.annotation:annotation:1.3.0"
|
||||
implementation "androidx.core:core:1.6.0"
|
||||
implementation "com.google.android.material:material:1.4.0"
|
||||
implementation "androidx.appcompat:appcompat:1.6.1"
|
||||
implementation "androidx.annotation:annotation:1.9.0"
|
||||
implementation "androidx.core:core:1.13.1"
|
||||
implementation "com.google.android.material:material:1.12.0"
|
||||
implementation "com.google.guava:guava:24.1-jre"
|
||||
implementation "io.noties.markwon:core:$markwonVersion"
|
||||
implementation "io.noties.markwon:ext-strikethrough:$markwonVersion"
|
||||
implementation "io.noties.markwon:linkify:$markwonVersion"
|
||||
implementation "io.noties.markwon:recycler:$markwonVersion"
|
||||
implementation "org.lsposed.hiddenapibypass:hiddenapibypass:2.0"
|
||||
implementation "org.lsposed.hiddenapibypass:hiddenapibypass:6.1"
|
||||
|
||||
// Do not increment version higher than 1.0.0-alpha09 since it will break ViewUtils and needs to be looked into
|
||||
// noinspection GradleDependency
|
||||
implementation "androidx.window:window:1.0.0-alpha09"
|
||||
implementation "androidx.window:window:1.1.0"
|
||||
|
||||
// Do not increment version higher than 2.5 or there
|
||||
// will be runtime exceptions on android < 8
|
||||
|
|
@ -31,6 +32,7 @@ android {
|
|||
}
|
||||
|
||||
defaultConfig {
|
||||
compileSdkVersion project.properties.compileSdkVersion.toInteger()
|
||||
minSdkVersion project.properties.minSdkVersion.toInteger()
|
||||
targetSdkVersion project.properties.targetSdkVersion.toInteger()
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
|
@ -55,6 +57,7 @@ android {
|
|||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
externalNativeBuild {
|
||||
ndkBuild {
|
||||
path file('src/main/cpp/Android.mk')
|
||||
|
|
@ -64,14 +67,13 @@ android {
|
|||
|
||||
dependencies {
|
||||
testImplementation "junit:junit:4.13.2"
|
||||
androidTestImplementation "androidx.test.ext:junit:1.1.3"
|
||||
androidTestImplementation "androidx.test.espresso:espresso-core:3.4.0"
|
||||
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:1.1.5"
|
||||
androidTestImplementation "androidx.test.ext:junit:1.1.5"
|
||||
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:2.1.2"
|
||||
}
|
||||
|
||||
task sourceJar(type: Jar) {
|
||||
from android.sourceSets.main.java.srcDirs
|
||||
classifier "sources"
|
||||
archiveClassifier = "sources"
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
|
|
@ -79,7 +81,7 @@ afterEvaluate {
|
|||
publications {
|
||||
// Creates a Maven publication called "release".
|
||||
release(MavenPublication) {
|
||||
from components.release
|
||||
from components.findByName('release')
|
||||
groupId = 'com.termux'
|
||||
artifactId = 'termux-shared'
|
||||
version = '0.118.0'
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.termux.shared">
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
</manifest>
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ public class MessageDialogUtils {
|
|||
final DialogInterface.OnClickListener onNegativeButton,
|
||||
final DialogInterface.OnDismissListener onDismiss) {
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(context, R.style.Theme_AppCompat_Light_Dialog);
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(context, androidx.appcompat.R.style.Theme_AppCompat_Light_Dialog);
|
||||
|
||||
LayoutInflater inflater = (LayoutInflater) context.getSystemService( Context.LAYOUT_INFLATER_SERVICE );
|
||||
View view = inflater.inflate(R.layout.dialog_show_message, null);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
package com.termux.shared.models;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
|
||||
import com.termux.shared.markdown.MarkdownUtils;
|
||||
import com.termux.shared.android.AndroidUtils;
|
||||
|
||||
|
|
@ -10,6 +12,25 @@ import java.io.Serializable;
|
|||
*/
|
||||
public class ReportInfo implements Serializable {
|
||||
|
||||
/**
|
||||
* Explicitly define `serialVersionUID` to prevent exceptions on deserialization.
|
||||
*
|
||||
* Like when calling `Bundle.getSerializable()` on Android.
|
||||
* `android.os.BadParcelableException: Parcelable encountered IOException reading a Serializable object` (name = <class_name>)
|
||||
* `java.io.InvalidClassException: <class_name>; local class incompatible`
|
||||
*
|
||||
* The `@Keep` annotation is necessary to prevent the field from being removed by proguard when
|
||||
* app is compiled, even if its kept during library compilation.
|
||||
*
|
||||
* **See Also:**
|
||||
* - https://docs.oracle.com/javase/8/docs/platform/serialization/spec/version.html#a6678
|
||||
* - https://docs.oracle.com/javase/8/docs/platform/serialization/spec/class.html#a4100
|
||||
*/
|
||||
@Keep
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
|
||||
|
||||
/** The user action that was being processed for which the report was generated. */
|
||||
public final String userAction;
|
||||
/** The internal app component that sent the report. */
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.termux.shared.models;
|
|||
import android.graphics.Color;
|
||||
import android.graphics.Typeface;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.termux.shared.activities.TextIOActivity;
|
||||
|
|
@ -19,6 +20,25 @@ import java.io.Serializable;
|
|||
*/
|
||||
public class TextIOInfo implements Serializable {
|
||||
|
||||
/**
|
||||
* Explicitly define `serialVersionUID` to prevent exceptions on deserialization.
|
||||
*
|
||||
* Like when calling `Bundle.getSerializable()` on Android.
|
||||
* `android.os.BadParcelableException: Parcelable encountered IOException reading a Serializable object` (name = <class_name>)
|
||||
* `java.io.InvalidClassException: <class_name>; local class incompatible`
|
||||
*
|
||||
* The `@Keep` annotation is necessary to prevent the field from being removed by proguard when
|
||||
* app is compiled, even if its kept during library compilation.
|
||||
*
|
||||
* **See Also:**
|
||||
* - https://docs.oracle.com/javase/8/docs/platform/serialization/spec/version.html#a6678
|
||||
* - https://docs.oracle.com/javase/8/docs/platform/serialization/spec/class.html#a4100
|
||||
*/
|
||||
@Keep
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
|
||||
|
||||
public static final int GENERAL_DATA_SIZE_LIMIT_IN_BYTES = 1000;
|
||||
public static final int LABEL_SIZE_LIMIT_IN_BYTES = 4000;
|
||||
public static final int TEXT_SIZE_LIMIT_IN_BYTES = 100000 - GENERAL_DATA_SIZE_LIMIT_IN_BYTES - LABEL_SIZE_LIMIT_IN_BYTES; // < 100KB
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.termux.shared.termux;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Intent;
|
||||
|
||||
import com.termux.shared.shell.command.ExecutionCommand;
|
||||
import com.termux.shared.shell.command.ExecutionCommand.Runner;
|
||||
|
|
@ -11,7 +12,7 @@ import java.util.Formatter;
|
|||
import java.util.List;
|
||||
|
||||
/*
|
||||
* Version: v0.52.0
|
||||
* Version: v0.53.0
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Changelog
|
||||
|
|
@ -277,6 +278,10 @@ import java.util.List;
|
|||
*
|
||||
* - 0.52.0 (2022-06-18)
|
||||
* - Added `TERMUX_PREFIX_DIR_IGNORED_SUB_FILES_PATHS_TO_CONSIDER_AS_EMPTY`.
|
||||
*
|
||||
* - 0.53.0 (2025-01-12)
|
||||
* - Renamed `TERMUX_API`, `TERMUX_STYLING`, `TERMUX_TASKER`, `TERMUX_WIDGET` classes with `_APP` suffix added.
|
||||
* - Added `TERMUX_*_MAIN_ACTIVITY_NAME` and `TERMUX_*_LAUNCHER_ACTIVITY_NAME` constants to each app class.
|
||||
*/
|
||||
|
||||
/**
|
||||
|
|
@ -1192,10 +1197,30 @@ public final class TermuxConstants {
|
|||
/**
|
||||
* Termux:API app constants.
|
||||
*/
|
||||
public static final class TERMUX_API {
|
||||
public static final class TERMUX_API_APP {
|
||||
|
||||
/** Termux:API app core activity name. */
|
||||
public static final String TERMUX_API_ACTIVITY_NAME = TERMUX_API_PACKAGE_NAME + ".activities.TermuxAPIActivity"; // Default: "com.termux.tasker.activities.TermuxAPIActivity"
|
||||
/** Termux:API app main activity name. */
|
||||
public static final String TERMUX_API_MAIN_ACTIVITY_NAME = TERMUX_API_PACKAGE_NAME + ".activities.TermuxAPIMainActivity"; // Default: "com.termux.api.activities.TermuxAPIMainActivity"
|
||||
|
||||
/** Termux:API app launcher activity name. This is an `activity-alias` for {@link #TERMUX_API_MAIN_ACTIVITY_NAME} used for launchers with {@link Intent#CATEGORY_LAUNCHER}. */
|
||||
public static final String TERMUX_API_LAUNCHER_ACTIVITY_NAME = TERMUX_API_PACKAGE_NAME + ".activities.TermuxAPILauncherActivity"; // Default: "com.termux.api.activities.TermuxAPILauncherActivity"
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Termux:Boot app constants.
|
||||
*/
|
||||
public static final class TERMUX_BOOT_APP {
|
||||
|
||||
/** Termux:Boot app main activity name. */
|
||||
public static final String TERMUX_BOOT_MAIN_ACTIVITY_NAME = TERMUX_BOOT_PACKAGE_NAME + ".activities.TermuxBootMainActivity"; // Default: "com.termux.boot.activities.TermuxBootMainActivity"
|
||||
|
||||
/** Termux:Boot app launcher activity name. This is an `activity-alias` for {@link #TERMUX_BOOT_MAIN_ACTIVITY_NAME} used for launchers with {@link Intent#CATEGORY_LAUNCHER}. */
|
||||
public static final String TERMUX_BOOT_LAUNCHER_ACTIVITY_NAME = TERMUX_BOOT_PACKAGE_NAME + ".activities.TermuxBootLauncherActivity"; // Default: "com.termux.boot.activities.TermuxBootLauncherActivity"
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -1208,6 +1233,9 @@ public final class TermuxConstants {
|
|||
*/
|
||||
public static final class TERMUX_FLOAT_APP {
|
||||
|
||||
/** Termux:Float app core activity name. */
|
||||
public static final String TERMUX_FLOAT_ACTIVITY_NAME = TERMUX_FLOAT_PACKAGE_NAME + ".TermuxFloatActivity"; // Default: "com.termux.window.TermuxFloatActivity"
|
||||
|
||||
/** Termux:Float app core service name. */
|
||||
public static final String TERMUX_FLOAT_SERVICE_NAME = TERMUX_FLOAT_PACKAGE_NAME + ".TermuxFloatService"; // Default: "com.termux.window.TermuxFloatService"
|
||||
|
||||
|
|
@ -1236,11 +1264,18 @@ public final class TermuxConstants {
|
|||
/**
|
||||
* Termux:Styling app constants.
|
||||
*/
|
||||
public static final class TERMUX_STYLING {
|
||||
public static final class TERMUX_STYLING_APP {
|
||||
|
||||
/** Termux:Styling app core activity name. */
|
||||
public static final String TERMUX_STYLING_ACTIVITY_NAME = TERMUX_STYLING_PACKAGE_NAME + ".TermuxStyleActivity"; // Default: "com.termux.styling.TermuxStyleActivity"
|
||||
|
||||
|
||||
/** Termux:Styling app main activity name. */
|
||||
public static final String TERMUX_STYLING_MAIN_ACTIVITY_NAME = TERMUX_STYLING_PACKAGE_NAME + ".activities.TermuxStylingMainActivity"; // Default: "com.termux.styling.activities.TermuxStylingMainActivity"
|
||||
|
||||
/** Termux:Styling app launcher activity name. This is an `activity-alias` for {@link #TERMUX_STYLING_MAIN_ACTIVITY_NAME} used for launchers with {@link Intent#CATEGORY_LAUNCHER}. */
|
||||
public static final String TERMUX_STYLING_LAUNCHER_ACTIVITY_NAME = TERMUX_STYLING_PACKAGE_NAME + ".activities.TermuxStylingLauncherActivity"; // Default: "com.termux.styling.activities.TermuxStylingLauncherActivity"
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1250,10 +1285,13 @@ public final class TermuxConstants {
|
|||
/**
|
||||
* Termux:Tasker app constants.
|
||||
*/
|
||||
public static final class TERMUX_TASKER {
|
||||
public static final class TERMUX_TASKER_APP {
|
||||
|
||||
/** Termux:Tasker app core activity name. */
|
||||
public static final String TERMUX_TASKER_ACTIVITY_NAME = TERMUX_TASKER_PACKAGE_NAME + ".activities.TermuxTaskerActivity"; // Default: "com.termux.tasker.activities.TermuxTaskerActivity"
|
||||
/** Termux:Tasker app main activity name. */
|
||||
public static final String TERMUX_TASKER_MAIN_ACTIVITY_NAME = TERMUX_TASKER_PACKAGE_NAME + ".activities.TermuxTaskerMainActivity"; // Default: "com.termux.tasker.activities.TermuxTaskerMainActivity"
|
||||
|
||||
/** Termux:Tasker app launcher activity name. This is an `activity-alias` for {@link #TERMUX_TASKER_MAIN_ACTIVITY_NAME} used for launchers with {@link Intent#CATEGORY_LAUNCHER}. */
|
||||
public static final String TERMUX_TASKER_LAUNCHER_ACTIVITY_NAME = TERMUX_TASKER_PACKAGE_NAME + ".activities.TermuxTaskerLauncherActivity"; // Default: "com.termux.tasker.activities.TermuxTaskerLauncherActivity"
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -1264,15 +1302,19 @@ public final class TermuxConstants {
|
|||
/**
|
||||
* Termux:Widget app constants.
|
||||
*/
|
||||
public static final class TERMUX_WIDGET {
|
||||
public static final class TERMUX_WIDGET_APP {
|
||||
|
||||
/** Termux:Widget app core activity name. */
|
||||
public static final String TERMUX_WIDGET_ACTIVITY_NAME = TERMUX_WIDGET_PACKAGE_NAME + ".activities.TermuxWidgetActivity"; // Default: "com.termux.widget.activities.TermuxWidgetActivity"
|
||||
/** Termux:Widget app main activity name. */
|
||||
public static final String TERMUX_WIDGET_MAIN_ACTIVITY_NAME = TERMUX_WIDGET_PACKAGE_NAME + ".activities.TermuxWidgetMainActivity"; // Default: "com.termux.widget.activities.TermuxWidgetMainActivity"
|
||||
|
||||
/** Termux:Widget app launcher activity name. This is an `activity-alias` for {@link #TERMUX_WIDGET_MAIN_ACTIVITY_NAME} used for launchers with {@link Intent#CATEGORY_LAUNCHER}. */
|
||||
public static final String TERMUX_WIDGET_LAUNCHER_ACTIVITY_NAME = TERMUX_WIDGET_PACKAGE_NAME + ".activities.TermuxWidgetLauncherActivity"; // Default: "com.termux.widget.activities.TermuxWidgetLauncherActivity"
|
||||
|
||||
|
||||
/** Intent {@code String} extra for the token of the Termux:Widget app shortcuts. */
|
||||
public static final String EXTRA_TOKEN_NAME = TERMUX_PACKAGE_NAME + ".shortcut.token"; // Default: "com.termux.shortcut.token"
|
||||
|
||||
|
||||
/**
|
||||
* Termux:Widget app {@link android.appwidget.AppWidgetProvider} class.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ public class TermuxUrlUtils {
|
|||
regex_sb.append("(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)|");
|
||||
|
||||
// Host name or domain.
|
||||
regex_sb.append("(?:(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)(?:(?:\\.(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)*(?:\\.(?:[a-z\\u00a1-\\uffff]{2,})))?|");
|
||||
regex_sb.append("(?:(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)(?:(?:\\.(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)*(?:\\.(?:[a-z\\u00a1-\\uffff0-9]-*){1,}[a-z\\u00a1-\\uffff0-9]{1,}))?|");
|
||||
|
||||
// Just path. Used in case of 'file://' scheme.
|
||||
regex_sb.append("/(?:(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)");
|
||||
|
|
|
|||
|
|
@ -10,7 +10,10 @@ import java.util.Map;
|
|||
public class ExtraKeysConstants {
|
||||
|
||||
/** Defines the repetitive keys that can be passed to {@link ExtraKeysView#setRepetitiveKeys(List)}. */
|
||||
public static List<String> PRIMARY_REPETITIVE_KEYS = Arrays.asList("UP", "DOWN", "LEFT", "RIGHT", "BKSP", "DEL");
|
||||
public static List<String> PRIMARY_REPETITIVE_KEYS = Arrays.asList(
|
||||
"UP", "DOWN", "LEFT", "RIGHT",
|
||||
"BKSP", "DEL",
|
||||
"PGUP", "PGDN");
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -185,12 +185,11 @@ public class ViewUtils {
|
|||
public static Point getDisplaySize( @NonNull Context context, boolean activitySize) {
|
||||
// android.view.WindowManager.getDefaultDisplay() and Display.getSize() are deprecated in
|
||||
// API 30 and give wrong values in API 30 for activitySize=false in multi-window
|
||||
androidx.window.WindowManager windowManager = new androidx.window.WindowManager(context);
|
||||
androidx.window.WindowMetrics windowMetrics;
|
||||
androidx.window.layout.WindowMetrics windowMetrics;
|
||||
if (activitySize)
|
||||
windowMetrics = windowManager.getCurrentWindowMetrics();
|
||||
windowMetrics = androidx.window.layout.WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(context);
|
||||
else
|
||||
windowMetrics = windowManager.getMaximumWindowMetrics();
|
||||
windowMetrics = androidx.window.layout.WindowMetricsCalculator.getOrCreate().computeMaximumWindowMetrics(context);
|
||||
return new Point(windowMetrics.getBounds().width(), windowMetrics.getBounds().height());
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue