Compare commits
39 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
43317b78c9 | ||
|
|
2a008d836e | ||
|
|
daa7ca4d43 | ||
|
|
2c82a5581f | ||
|
|
708281cea2 | ||
|
|
eb0cb408a4 | ||
|
|
d11c95b996 | ||
|
|
9735ae284d | ||
|
|
0813e46330 | ||
|
|
6ece249c03 | ||
|
|
fc8245bba3 | ||
|
|
63833d9c2d | ||
|
|
c9e2a75e82 | ||
|
|
9433f10757 | ||
|
|
fbf55fd40c | ||
|
|
160ab68e5b | ||
|
|
903f2496cb | ||
|
|
2dc7381b89 | ||
|
|
e55639e41d | ||
|
|
087da0b576 | ||
|
|
d7f22982a1 | ||
|
|
f222315b0f | ||
|
|
e11bcfc9a1 | ||
|
|
e3a50cbf32 | ||
|
|
af5fef4c4a | ||
|
|
03e31d190d | ||
|
|
d24a04a10d | ||
|
|
aee0da49a0 | ||
|
|
87c8f3d35a | ||
|
|
9259ef0be1 | ||
|
|
480f92880c | ||
|
|
b01a738791 | ||
|
|
0eaaa1372a | ||
|
|
903b1c75a2 | ||
|
|
085b17e496 | ||
|
|
897d911a52 | ||
|
|
cd5962c696 | ||
|
|
6e6da752bd | ||
|
|
6d60bc669b |
@@ -6,31 +6,70 @@ on:
|
||||
- published
|
||||
|
||||
jobs:
|
||||
build:
|
||||
attach-apks:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
- name: Set tag
|
||||
id: vars
|
||||
run: echo ::set-output name=tag::${GITHUB_REF/refs\/tags\//}
|
||||
- name: Clone repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
ref: ${{ env.GITHUB_REF }}
|
||||
- name: Build
|
||||
env:
|
||||
RELEASE_TAG: ${{ steps.vars.outputs.tag }}
|
||||
run: ./gradlew assembleDebug
|
||||
- name: Attach debug APKs to release
|
||||
env:
|
||||
RELEASE_TAG: ${{ steps.vars.outputs.tag }}
|
||||
run: >-
|
||||
hub release edit
|
||||
-m ""
|
||||
-a ./app/build/outputs/apk/debug/termux-app-universal-$RELEASE_TAG-debug.apk
|
||||
-a ./app/build/outputs/apk/debug/termux-app-arm64-v8a-$RELEASE_TAG-debug.apk
|
||||
-a ./app/build/outputs/apk/debug/termux-app-armeabi-v7a-$RELEASE_TAG-debug.apk
|
||||
-a ./app/build/outputs/apk/debug/termux-app-x86_64-$RELEASE_TAG-debug.apk
|
||||
-a ./app/build/outputs/apk/debug/termux-app-x86-$RELEASE_TAG-debug.apk
|
||||
$RELEASE_TAG
|
||||
|
||||
- name: Build and attach APKs to release
|
||||
shell: bash {0}
|
||||
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
|
||||
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"
|
||||
APK_VERSION_TAG="$RELEASE_VERSION_NAME+github-debug"
|
||||
APK_BASENAME_PREFIX="termux-app_$APK_VERSION_TAG"
|
||||
|
||||
echo "Building APKs for '$RELEASE_VERSION_NAME' release"
|
||||
export TERMUX_APK_VERSION_TAG="$APK_VERSION_TAG" # Used by app/build.gradle
|
||||
if ! ./gradlew assembleDebug; then
|
||||
exit_on_error "Build failed for '$RELEASE_VERSION_NAME' 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")"
|
||||
exit_on_error "Failed to find built APK at '$APK_DIR_PATH/${APK_BASENAME_PREFIX}_$abi.apk'. Files found: "$'\n'"$files_found"
|
||||
fi
|
||||
done
|
||||
|
||||
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 '$RELEASE_VERSION_NAME' release."
|
||||
fi
|
||||
|
||||
echo "Attaching APKs to github release"
|
||||
if ! gh release upload "$RELEASE_VERSION_NAME" \
|
||||
"$APK_DIR_PATH/${APK_BASENAME_PREFIX}_universal.apk" \
|
||||
"$APK_DIR_PATH/${APK_BASENAME_PREFIX}_arm64-v8a.apk" \
|
||||
"$APK_DIR_PATH/${APK_BASENAME_PREFIX}_armeabi-v7a.apk" \
|
||||
"$APK_DIR_PATH/${APK_BASENAME_PREFIX}_x86_64.apk" \
|
||||
"$APK_DIR_PATH/${APK_BASENAME_PREFIX}_x86.apk" \
|
||||
"$APK_DIR_PATH/${APK_BASENAME_PREFIX}_sha256sums" \
|
||||
; then
|
||||
exit_on_error "Attach APKs to release failed for '$RELEASE_VERSION_NAME' release."
|
||||
fi
|
||||
|
||||
142
.github/workflows/debug_build.yml
vendored
142
.github/workflows/debug_build.yml
vendored
@@ -4,53 +4,111 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- android-10
|
||||
- 'github-releases/**'
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- android-10
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Clone repository
|
||||
uses: actions/checkout@v2
|
||||
- name: Build
|
||||
run: |
|
||||
./gradlew assembleDebug
|
||||
- name: Store generated universal APK file
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: termux-app-universal
|
||||
path: |
|
||||
./app/build/outputs/apk/debug/termux-app-universal-debug.apk
|
||||
./app/build/outputs/apk/debug/output-metadata.json
|
||||
- name: Store generated arm64-v8a APK file
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: termux-app-arm64-v8a
|
||||
path: |
|
||||
./app/build/outputs/apk/debug/termux-app-arm64-v8a-debug.apk
|
||||
./app/build/outputs/apk/debug/output-metadata.json
|
||||
- name: Store generated armeabi-v7a APK file
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: termux-app-armeabi-v7a
|
||||
path: |
|
||||
./app/build/outputs/apk/debug/termux-app-armeabi-v7a-debug.apk
|
||||
./app/build/outputs/apk/debug/output-metadata.json
|
||||
- name: Store generated x86_64 APK file
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: termux-app-x86_64
|
||||
path: |
|
||||
./app/build/outputs/apk/debug/termux-app-x86_64-debug.apk
|
||||
./app/build/outputs/apk/debug/output-metadata.json
|
||||
- name: Store generated x86 APK file
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: termux-app-x86
|
||||
path: |
|
||||
./app/build/outputs/apk/debug/termux-app-x86-debug.apk
|
||||
./app/build/outputs/apk/debug/output-metadata.json
|
||||
- name: Clone repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Build APKs
|
||||
shell: bash {0}
|
||||
run: |
|
||||
exit_on_error() { echo "$1"; exit 1; }
|
||||
|
||||
echo "Setting vars"
|
||||
# Set RELEASE_VERSION_NAME to "<CURRENT_VERSION_NAME>+<last_commit_hash>"
|
||||
CURRENT_VERSION_NAME_REGEX='\s+versionName "([^"]+)"$'
|
||||
CURRENT_VERSION_NAME="$(grep -m 1 -E "$CURRENT_VERSION_NAME_REGEX" ./app/build.gradle | sed -r "s/$CURRENT_VERSION_NAME_REGEX/\1/")"
|
||||
RELEASE_VERSION_NAME="v$CURRENT_VERSION_NAME+${GITHUB_SHA:0:7}" # The "+" is necessary so that versioning precedence is not affected
|
||||
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
|
||||
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"
|
||||
APK_VERSION_TAG="$RELEASE_VERSION_NAME-github-debug" # Note the "-", GITHUB_SHA will already have "+" before it
|
||||
APK_BASENAME_PREFIX="termux-app_$APK_VERSION_TAG"
|
||||
|
||||
# Used by attachment steps later
|
||||
echo "APK_DIR_PATH=$APK_DIR_PATH" >> $GITHUB_ENV
|
||||
echo "APK_VERSION_TAG=$APK_VERSION_TAG" >> $GITHUB_ENV
|
||||
echo "APK_BASENAME_PREFIX=$APK_BASENAME_PREFIX" >> $GITHUB_ENV
|
||||
|
||||
echo "Building APKs for '$RELEASE_VERSION_NAME' build"
|
||||
export TERMUX_APP_VERSION_NAME="${RELEASE_VERSION_NAME/v/}" # Used by app/build.gradle
|
||||
export TERMUX_APK_VERSION_TAG="$APK_VERSION_TAG" # Used by app/build.gradle
|
||||
if ! ./gradlew assembleDebug; then
|
||||
exit_on_error "Build failed for '$RELEASE_VERSION_NAME' build."
|
||||
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")"
|
||||
exit_on_error "Failed to find built APK at '$APK_DIR_PATH/${APK_BASENAME_PREFIX}_$abi.apk'. Files found: "$'\n'"$files_found"
|
||||
fi
|
||||
done
|
||||
|
||||
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" \
|
||||
> sha256sums); then
|
||||
exit_on_error "Generate sha25sums failed for '$RELEASE_VERSION_NAME' release."
|
||||
fi
|
||||
|
||||
- name: Attach universal APK file
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ env.APK_BASENAME_PREFIX }}_universal
|
||||
path: |
|
||||
${{ env.APK_DIR_PATH }}/${{ env.APK_BASENAME_PREFIX }}_universal.apk
|
||||
${{ env.APK_DIR_PATH }}/output-metadata.json
|
||||
|
||||
- name: Attach arm64-v8a APK file
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ env.APK_BASENAME_PREFIX }}_arm64-v8a
|
||||
path: |
|
||||
${{ env.APK_DIR_PATH }}/${{ env.APK_BASENAME_PREFIX }}_arm64-v8a.apk
|
||||
${{ env.APK_DIR_PATH }}/output-metadata.json
|
||||
|
||||
- name: Attach armeabi-v7a APK file
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ env.APK_BASENAME_PREFIX }}_armeabi-v7a
|
||||
path: |
|
||||
${{ env.APK_DIR_PATH }}/${{ env.APK_BASENAME_PREFIX }}_armeabi-v7a.apk
|
||||
${{ env.APK_DIR_PATH }}/output-metadata.json
|
||||
|
||||
- name: Attach x86_64 APK file
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ env.APK_BASENAME_PREFIX }}_x86_64
|
||||
path: |
|
||||
${{ env.APK_DIR_PATH }}/${{ env.APK_BASENAME_PREFIX }}_x86_64.apk
|
||||
${{ env.APK_DIR_PATH }}/output-metadata.json
|
||||
|
||||
- name: Attach x86 APK file
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ env.APK_BASENAME_PREFIX }}_x86
|
||||
path: |
|
||||
${{ env.APK_DIR_PATH }}/${{ env.APK_BASENAME_PREFIX }}_x86.apk
|
||||
${{ env.APK_DIR_PATH }}/output-metadata.json
|
||||
|
||||
- name: Attach sha256sums file
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sha256sums
|
||||
path: |
|
||||
${{ env.APK_DIR_PATH }}/sha256sums
|
||||
${{ env.APK_DIR_PATH }}/output-metadata.json
|
||||
|
||||
@@ -15,5 +15,5 @@ jobs:
|
||||
name: "Validation"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
- uses: gradle/wrapper-validation-action@v1
|
||||
|
||||
2
.github/workflows/run_tests.yml
vendored
2
.github/workflows/run_tests.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Clone repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
- name: Execute tests
|
||||
run: |
|
||||
./gradlew test
|
||||
|
||||
@@ -6,19 +6,16 @@ on:
|
||||
- published
|
||||
|
||||
jobs:
|
||||
build:
|
||||
trigger-termux-library-builds:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set tag
|
||||
id: vars
|
||||
run: echo ::set-output name=tag::${GITHUB_REF/refs\/tags\/v/}
|
||||
- name: Echo tag
|
||||
run: echo "Triggering termux library builds on jitpack for '${{ steps.vars.outputs.tag }}' after waiting for 3 mins"
|
||||
- name: Set vars
|
||||
run: echo "TERMUX_LIB_VERSION=${GITHUB_REF/refs\/tags\/v/}" >> $GITHUB_ENV # Do not include "v" prefix
|
||||
- name: Echo release
|
||||
run: echo "Triggering termux library builds on jitpack for '$TERMUX_LIB_VERSION' release after waiting for 3 mins"
|
||||
- name: Trigger termux library builds on jitpack
|
||||
env:
|
||||
RELEASE_VERSION: ${{ steps.vars.outputs.tag }}
|
||||
run: |
|
||||
sleep 180 # It will take some time for the new tag to be detected by Jitpack
|
||||
curl --max-time 600 --no-progress-meter https://jitpack.io/com/termux/termux-app/terminal-emulator/$RELEASE_VERSION/terminal-emulator-$RELEASE_VERSION.pom
|
||||
curl --max-time 600 --no-progress-meter https://jitpack.io/com/termux/termux-app/terminal-view/$RELEASE_VERSION/terminal-view-$RELEASE_VERSION.pom
|
||||
curl --max-time 600 --no-progress-meter https://jitpack.io/com/termux/termux-app/termux-shared/$RELEASE_VERSION/termux-shared-$RELEASE_VERSION.pom
|
||||
curl --max-time 600 --no-progress-meter "https://jitpack.io/com/termux/termux-app/terminal-emulator/$TERMUX_LIB_VERSION/terminal-emulator-$TERMUX_LIB_VERSION.pom"
|
||||
curl --max-time 600 --no-progress-meter "https://jitpack.io/com/termux/termux-app/terminal-view/$TERMUX_LIB_VERSION/terminal-view-$TERMUX_LIB_VERSION.pom"
|
||||
curl --max-time 600 --no-progress-meter "https://jitpack.io/com/termux/termux-app/termux-shared/$TERMUX_LIB_VERSION/termux-shared-$TERMUX_LIB_VERSION.pom"
|
||||
|
||||
51
README.md
51
README.md
@@ -26,6 +26,7 @@ Issue https://github.com/termux/termux-app/issues/1072 needs extra attention.
|
||||
- [Installation](#Installation)
|
||||
- [Uninstallation](#Uninstallation)
|
||||
- [Important Links](#Important-Links)
|
||||
- [Debugging](#Debugging)
|
||||
- [For Maintainers and Contributors](#For-Maintainers-and-Contributors)
|
||||
- [Forking](#Forking)
|
||||
##
|
||||
@@ -48,6 +49,8 @@ The core [Termux](https://github.com/termux/termux-app) app comes with the follo
|
||||
|
||||
## Installation
|
||||
|
||||
Latest version is `v0.118.0`.
|
||||
|
||||
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).
|
||||
|
||||
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.
|
||||
@@ -64,15 +67,17 @@ You **do not** need to download the `F-Droid` app (via the `Download F-Droid` li
|
||||
|
||||
It usually takes a few days (or even a week or more) for updates to be available on `F-Droid` once an update has been released on `Github`. The `F-Droid` releases are built and published by `F-Droid` once they [detect](https://gitlab.com/fdroid/fdroiddata/-/blob/master/metadata/com.termux.yml) a new `Github` release. The Termux maintainers **do not** have any control over the building and publishing of the Termux apps on `F-Droid`. Moreover, the Termux maintainers also do not have access to the APK signing keys of `F-Droid` releases, so we cannot release an APK ourselves on `Github` that would be compatible with `F-Droid` releases.
|
||||
|
||||
The `F-Droid` app often may not notify you of updates and you will manually have to do a pull down swipe action in the `Updates` tab of the app for it to check updates. Make sure battery optimizations are disabled for the app, check https://dontkillmyapp.com/ for details on how to do that.
|
||||
|
||||
Only a universal APK is released, which will work on all supported architectures. The APK and bootstrap installation size will be `~180MB`. `F-Droid` does [not support](https://github.com/termux/termux-app/pull/1904) architecture specific APKs.
|
||||
|
||||
### Github
|
||||
|
||||
Termux application can be obtained on `Github` either from [`Github Releases`](https://github.com/termux/termux-app/releases) for `>= v0.118` 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`](https://github.com/termux/termux-app/actions/workflows/debug_build.yml) action workflows.
|
||||
|
||||
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.
|
||||
|
||||
The APKs for `Github Build` action workflows will be listed under `Artifacts` section of a workflow run. These are created for each commit/push done to the repository and can be used by users who don't want to wait for releases and want to try out the latest features immediately or want to test their pull requests.
|
||||
The APKs for `Github Build` action workflows will be listed under `Artifacts` section of a workflow run. These are created for each commit/push done to the repository and can be used by users who don't want to wait for releases and want to try out the latest features immediately or want to test their pull requests. Note that for action workflows, you need to be [**logged into a `Github` account**](https://github.com/login) for the `Artifacts` links to be enabled/clickable. If you are using the [`Github` app](https://github.com/mobile), then make sure to open workflow link in a browser like Chrome or Firefox that has your Github account logged in since the in-app browser may not be logged in.
|
||||
|
||||
The APKs for both of these are [`debuggable`](https://developer.android.com/studio/debug) and are compatible with each other but they are not compatible with other sources.
|
||||
|
||||
@@ -88,9 +93,11 @@ You **will not need to buy plugins again** if you bought them on Play Store. All
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
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.
|
||||
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?
|
||||
|
||||
@@ -138,7 +145,7 @@ The main ones are the following.
|
||||
- [Termux Matrix Channel](https://matrix.to/#termux_termux:gitter.im)
|
||||
- [Termux Dev Matrix Channel](https://matrix.to/#termux_dev:gitter.im)
|
||||
- [Termux Twitter](https://twitter.com/termux/)
|
||||
- [Termux Reports Email](mailto:termuxreports@groups.io)
|
||||
- [Termux Reports Email](mailto:support@termux.dev)
|
||||
|
||||
### Wikis
|
||||
|
||||
@@ -151,7 +158,7 @@ The main ones are the following.
|
||||
- [Termux File System Layout](https://github.com/termux/termux-packages/wiki/Termux-file-system-layout)
|
||||
- [Differences From Linux](https://wiki.termux.com/wiki/Differences_from_Linux)
|
||||
- [Package Management](https://wiki.termux.com/wiki/Package_Management)
|
||||
- [Remote_Access](https://wiki.termux.com/wiki/Remote_Access)
|
||||
- [Remote Access](https://wiki.termux.com/wiki/Remote_Access)
|
||||
- [Backing up Termux](https://wiki.termux.com/wiki/Backing_up_Termux)
|
||||
- [Terminal Settings](https://wiki.termux.com/wiki/Terminal_Settings)
|
||||
- [Touch Keyboard](https://wiki.termux.com/wiki/Touch_Keyboard)
|
||||
@@ -194,15 +201,37 @@ The main ones are the following.
|
||||
|
||||
|
||||
|
||||
### Debugging
|
||||
|
||||
You can help debug problems of the `Termux` app and its plugins by setting appropriate `logcat` `Log Level` in `Termux` app settings -> `<APP_NAME>` -> `Debugging` -> `Log Level` (Requires `Termux` app version `>= 0.118.0`). The `Log Level` defaults to `Normal` and log level `Verbose` currently logs additional information. Its best to revert log level to `Normal` after you have finished debugging since private data may otherwise be passed to `logcat` during normal operation and moreover, additional logging increases execution time.
|
||||
|
||||
The plugin apps **do not execute the commands themselves** but send execution intents to `Termux` app, which has its own log level which can be set in `Termux` app settings -> `Termux` -> `Debugging` -> `Log Level`. So you must set log level for both `Termux` and the respective plugin app settings to get all the info.
|
||||
|
||||
Once log levels have been set, you can run the `logcat` command in `Termux` app terminal to view the logs in realtime (`Ctrl+c` to stop) or use `logcat -d > logcat.txt` to take a dump of the log. You can also view the logs from a PC over `ADB`. For more information, check official android `logcat` guide [here](https://developer.android.com/studio/command-line/logcat).
|
||||
|
||||
Moreover, users can generate termux files `stat` info and `logcat` dump automatically too with terminal's long hold options menu `More` -> `Report Issue` option and selecting `YES` in the prompt shown to add debug info. This can be helpful for reporting and debugging other issues. If the report generated is too large, then `Save To File` option in context menu (3 dots on top right) of `ReportActivity` can be used and the file viewed/shared instead.
|
||||
|
||||
Users must post complete report (optionally without sensitive info) when reporting issues. Issues opened with **(partial) screenshots of error reports** instead of text will likely be automatically closed/deleted.
|
||||
|
||||
##### Log Levels
|
||||
|
||||
- `Off` - Log nothing.
|
||||
- `Normal` - Start logging error, warn and info messages and stacktraces.
|
||||
- `Debug` - Start logging debug messages.
|
||||
- `Verbose` - Start logging verbose messages.
|
||||
##
|
||||
|
||||
|
||||
|
||||
## For Maintainers and Contributors
|
||||
|
||||
The [termux-shared](termux-shared) library was added in [`v0.109`](https://github.com/termux/termux-app/releases/tag/v0.109). It defines shared constants and utils of the Termux app and its plugins. It was created to allow for the removal of all hardcoded paths in the Termux app. Some of the termux plugins are using this as well and rest will in future. If you are contributing code that is using a constant or a util that may be shared, then define it in `termux-shared` library if it currently doesn't exist and reference it from there. Update the relevant changelogs as well. Pull requests using hardcoded values **will/should not** be accepted.
|
||||
The [termux-shared](termux-shared) library was added in [`v0.109`](https://github.com/termux/termux-app/releases/tag/v0.109). It defines shared constants and utils of the Termux app and its plugins. It was created to allow for the removal of all hardcoded paths in the Termux app. Some of the termux plugins are using this as well and rest will in future. If you are contributing code that is using a constant or a util that may be shared, then define it in `termux-shared` library if it currently doesn't exist and reference it from there. Update the relevant changelogs as well. Pull requests using hardcoded values **will/should not** be accepted. Termux app and plugin specific classes must be added under `com.termux.shared.termux` package and general classes outside it. The [`termux-shared` `LICENSE`](termux-shared/LICENSE.md) must also be checked and updated if necessary when contributing code. The licenses of any external library or code must be honoured.
|
||||
|
||||
The main Termux constants are defined by [`TermuxConstants`](https://github.com/termux/termux-app/blob/master/termux-shared/src/main/java/com/termux/shared/termux/TermuxConstants.java) class. It also contains information on how to fork Termux or build it with your own package name. Changing the package name will require building the bootstrap zip packages and other packages with the new `$PREFIX`, check [Building Packages](https://github.com/termux/termux-packages/wiki/Building-packages) for more info.
|
||||
|
||||
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 should preferably use [Conventional Commits](https://www.conventionalcommits.org) specs. Use the following `types` as `Added: Add foo`, `Added|Fixed: Add foo and fix bar`, `Changed!: Change baz as a breaking change`, etc. This should also allow automated generation of Changelogs in future.
|
||||
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.
|
||||
|
||||
- **Added** for new features.
|
||||
- **Changed** for changes in existing functionality.
|
||||
@@ -210,9 +239,15 @@ Commit messages should preferably use [Conventional Commits](https://www.convent
|
||||
- **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.
|
||||
##
|
||||
|
||||
|
||||
|
||||
## Forking
|
||||
|
||||
- Check [`TermuxConstants`](https://github.com/termux/termux-app/blob/master/termux-shared/src/main/java/com/termux/shared/termux/TermuxConstants.java) javadocs for instructions on what changes to make in the app to change package name.
|
||||
|
||||
@@ -5,10 +5,14 @@ plugins {
|
||||
android {
|
||||
compileSdkVersion project.properties.compileSdkVersion.toInteger()
|
||||
ndkVersion = System.getenv("JITPACK_NDK_VERSION") ?: project.properties.ndkVersion
|
||||
def appVersionName = System.getenv("TERMUX_APP_VERSION_NAME") ?: ""
|
||||
def apkVersionTag = System.getenv("TERMUX_APK_VERSION_TAG") ?: ""
|
||||
def splitAPKsForDebugBuilds = System.getenv("TERMUX_SPLIT_APKS_FOR_DEBUG_BUILDS") ?: "1"
|
||||
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.2.0"
|
||||
implementation "androidx.core:core:1.6.0-rc01"
|
||||
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.viewpager:viewpager:1.0.0"
|
||||
@@ -26,8 +30,11 @@ android {
|
||||
applicationId "com.termux"
|
||||
minSdkVersion project.properties.minSdkVersion.toInteger()
|
||||
targetSdkVersion project.properties.targetSdkVersion.toInteger()
|
||||
versionCode 117
|
||||
versionName "0.117"
|
||||
versionCode 1000
|
||||
versionName "0.118.1"
|
||||
|
||||
if (appVersionName) versionName = appVersionName
|
||||
validateVersionName(versionName)
|
||||
|
||||
manifestPlaceholders.TERMUX_PACKAGE_NAME = "com.termux"
|
||||
manifestPlaceholders.TERMUX_APP_NAME = "Termux"
|
||||
@@ -46,7 +53,8 @@ android {
|
||||
|
||||
splits {
|
||||
abi {
|
||||
enable gradle.startParameter.taskNames.any { it.contains("Debug") }
|
||||
enable ((gradle.startParameter.taskNames.any { it.contains("Debug") } && splitAPKsForDebugBuilds == "1") ||
|
||||
(gradle.startParameter.taskNames.any { it.contains("Release") } && splitAPKsForReleaseBuilds == "1"))
|
||||
reset ()
|
||||
include 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'
|
||||
universalApk true
|
||||
@@ -106,8 +114,10 @@ android {
|
||||
variant.outputs.all { output ->
|
||||
if (variant.buildType.name == "debug") {
|
||||
def abi = output.getFilter(com.android.build.OutputFile.ABI)
|
||||
def releaseTag = System.getenv("RELEASE_TAG")
|
||||
outputFileName = new File("termux-app-" + (abi ? abi : "universal") + (releaseTag ? "-" + releaseTag : "") + "-debug.apk")
|
||||
outputFileName = new File("termux-app_" + (apkVersionTag ? apkVersionTag : "debug") + "_" + (abi ? abi : "universal") + ".apk")
|
||||
} else if (variant.buildType.name == "release") {
|
||||
def abi = output.getFilter(com.android.build.OutputFile.ABI)
|
||||
outputFileName = new File("termux-app_" + (apkVersionTag ? apkVersionTag : "release") + "_" + (abi ? abi : "universal") + ".apk")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -125,6 +135,13 @@ task versionName {
|
||||
}
|
||||
}
|
||||
|
||||
def validateVersionName(String versionName) {
|
||||
// https://semver.org/spec/v2.0.0.html#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string
|
||||
// ^(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-]+)*))?$
|
||||
if (!java.util.regex.Pattern.matches("^(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-]+)*))?\$", versionName))
|
||||
throw new GradleException("The versionName '" + versionName + "' 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.")
|
||||
}
|
||||
|
||||
def downloadBootstrap(String arch, String expectedChecksum, String version) {
|
||||
def digest = java.security.MessageDigest.getInstance("SHA-256")
|
||||
|
||||
@@ -139,6 +156,7 @@ def downloadBootstrap(String arch, String expectedChecksum, String version) {
|
||||
digest.update(buffer, 0, readBytes)
|
||||
}
|
||||
def checksum = new BigInteger(1, digest.digest()).toString(16)
|
||||
while (checksum.length() < 64) { checksum = "0" + checksum }
|
||||
if (checksum == expectedChecksum) {
|
||||
return
|
||||
} else {
|
||||
@@ -160,6 +178,7 @@ def downloadBootstrap(String arch, String expectedChecksum, String version) {
|
||||
out.close()
|
||||
|
||||
def checksum = new BigInteger(1, digest.digest()).toString(16)
|
||||
while (checksum.length() < 64) { checksum = "0" + checksum }
|
||||
if (checksum != expectedChecksum) {
|
||||
file.delete()
|
||||
throw new GradleException("Wrong checksum for " + remoteUrl + ": expected: " + expectedChecksum + ", actual: " + checksum)
|
||||
@@ -176,11 +195,11 @@ clean {
|
||||
|
||||
task downloadBootstraps() {
|
||||
doLast {
|
||||
def version = "2021.10.03-r1"
|
||||
downloadBootstrap("aarch64", "abba0bab1155ca081b96556f5ac69c7f85f332f67665503fc488a82338a739de", version)
|
||||
downloadBootstrap("arm", "3ab34567d69be26183f1bd30b2a9a20fb8d95c4217c55fbe4ea0dda92f9ebc35", version)
|
||||
downloadBootstrap("i686", "fda0463b7a448362d91f56448518eae9985f3d069e24608e17c2c66097993037", version)
|
||||
downloadBootstrap("x86_64", "de38b87a74760b268eb5f6ae289edd3fc5b3c86e02cb32f5f4a863161f4de354", version)
|
||||
def version = "2024.06.17-r1+apt-android-7"
|
||||
downloadBootstrap("aarch64", "91a90661597fe14bb3c3563f5f65b243c0baaec42f2bc3d2243ff459e3942fb6", version)
|
||||
downloadBootstrap("arm", "d54b5eb2a305d72f267f9704deaca721b2bebbd3d4cca134aec31da719707997", version)
|
||||
downloadBootstrap("i686", "06a51ac1c679d68d52045509f1a705622c8f41748ef753660e31e3b6a846eba2", version)
|
||||
downloadBootstrap("x86_64", "4c8e43474c8d9543e01d4cbf3c4d7f59bbe4d696c38f6dece2b6ab3ba8881f2e", version)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
|
||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||
<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"
|
||||
@@ -180,13 +181,24 @@
|
||||
<meta-data
|
||||
android:name="android.max_aspect"
|
||||
android:value="10.0" />
|
||||
|
||||
|
||||
<!-- https://developer.samsung.com/samsung-dex/modify-optimizing.html -->
|
||||
|
||||
<!-- Version < 3.0. DeX Mode and Screen Mirroring support -->
|
||||
<meta-data
|
||||
android:name="com.sec.android.support.multiwindow"
|
||||
android:name="com.samsung.android.keepalive.density"
|
||||
android:value="true" />
|
||||
|
||||
<!-- Version >= 3.0. DeX Dual Mode support -->
|
||||
<meta-data
|
||||
android:name="com.samsung.android.multidisplay.keep_process_alive"
|
||||
android:value="true" />
|
||||
|
||||
<meta-data
|
||||
android:name="com.sec.android.support.multiwindow"
|
||||
android:value="true" />
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
||||
@@ -16,8 +16,11 @@ import com.termux.shared.file.TermuxFileUtils;
|
||||
import com.termux.shared.interact.MessageDialogUtils;
|
||||
import com.termux.shared.logger.Logger;
|
||||
import com.termux.shared.markdown.MarkdownUtils;
|
||||
import com.termux.shared.models.ExecutionCommand;
|
||||
import com.termux.shared.models.errors.Error;
|
||||
import com.termux.shared.packages.PackageUtils;
|
||||
import com.termux.shared.shell.TermuxShellEnvironmentClient;
|
||||
import com.termux.shared.shell.TermuxTask;
|
||||
import com.termux.shared.termux.TermuxConstants;
|
||||
import com.termux.shared.termux.TermuxUtils;
|
||||
|
||||
@@ -187,7 +190,8 @@ final class TermuxInstaller {
|
||||
outStream.write(buffer, 0, readBytes);
|
||||
}
|
||||
if (zipEntryName.startsWith("bin/") || zipEntryName.startsWith("libexec") ||
|
||||
zipEntryName.startsWith("lib/apt/apt-helper") || zipEntryName.startsWith("lib/apt/methods")) {
|
||||
zipEntryName.startsWith("lib/apt/apt-helper") || zipEntryName.startsWith("lib/apt/methods") ||
|
||||
zipEntryName.equals("etc/termux/bootstrap/termux-bootstrap-second-stage.sh")) {
|
||||
//noinspection OctalInteger
|
||||
Os.chmod(targetFile.getAbsolutePath(), 0700);
|
||||
}
|
||||
@@ -208,6 +212,28 @@ final class TermuxInstaller {
|
||||
throw new RuntimeException("Moving termux prefix staging to prefix directory failed");
|
||||
}
|
||||
|
||||
// Run Termux bootstrap second stage
|
||||
Logger.logInfo(LOG_TAG, "Running Termux bootstrap second stage.");
|
||||
String termuxBootstrapSecondStageFile = TERMUX_PREFIX_DIR_PATH + "/etc/termux/bootstrap/termux-bootstrap-second-stage.sh";
|
||||
if (FileUtils.fileExists(termuxBootstrapSecondStageFile, false)) {
|
||||
ExecutionCommand executionCommand = new ExecutionCommand(-1,
|
||||
termuxBootstrapSecondStageFile, null, null,
|
||||
null, true, false);
|
||||
executionCommand.commandLabel = "Termux Bootstrap Second Stage Command";
|
||||
executionCommand.backgroundCustomLogLevel = Logger.LOG_LEVEL_NORMAL;
|
||||
TermuxTask termuxTask = TermuxTask.execute(activity, executionCommand, null, new TermuxShellEnvironmentClient(), true);
|
||||
boolean stderrSet = !executionCommand.resultData.stderr.toString().isEmpty();
|
||||
if (termuxTask == null || !executionCommand.isSuccessful() || executionCommand.resultData.exitCode != 0 || stderrSet) {
|
||||
// Delete prefix directory as otherwise when app is restarted, the broken prefix directory would be used and logged into
|
||||
error = FileUtils.deleteFile("termux prefix directory", TERMUX_PREFIX_DIR_PATH, true);
|
||||
if (error != null)
|
||||
Logger.logErrorExtended(LOG_TAG, error.toString());
|
||||
|
||||
showBootstrapErrorDialog(activity, whenDone, MarkdownUtils.getMarkdownCodeForString(executionCommand.toString(), true));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Logger.logInfo(LOG_TAG, "Bootstrap packages installed successfully.");
|
||||
activity.runOnUiThread(whenDone);
|
||||
|
||||
|
||||
@@ -213,7 +213,7 @@ public class TermuxTerminalViewClient extends TermuxTerminalViewClientBase {
|
||||
|
||||
@Override
|
||||
public boolean isTerminalViewSelected() {
|
||||
return mActivity.getTerminalToolbarViewPager() == null || mActivity.isTerminalViewSelected();
|
||||
return mActivity.getTerminalToolbarViewPager() == null || mActivity.isTerminalViewSelected() || mActivity.getTerminalView().hasFocus();
|
||||
}
|
||||
|
||||
|
||||
@@ -541,7 +541,8 @@ public class TermuxTerminalViewClient extends TermuxTerminalViewClientBase {
|
||||
// disabled or hidden at startup, otherwise if hardware keyboard is attached and user
|
||||
// starts typing on hardware keyboard without tapping on the terminal first, then a colour
|
||||
// tint will be added to the terminal as highlight for the focussed view. Test with a light
|
||||
// theme.
|
||||
// theme. For android 8.+, the "defaultFocusHighlightEnabled" attribute is also set to false
|
||||
// in TerminalView layout to fix the issue.
|
||||
|
||||
// If soft keyboard is disabled by user for Termux (check function docs for Termux behaviour info)
|
||||
if (KeyboardUtils.shouldSoftKeyboardBeDisabled(mActivity,
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<com.termux.app.terminal.TermuxActivityRootView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/activity_termux_root_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
@@ -25,11 +27,13 @@
|
||||
android:id="@+id/terminal_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:defaultFocusHighlightEnabled="false"
|
||||
android:focusableInTouchMode="true"
|
||||
android:scrollbarThumbVertical="@drawable/terminal_scroll_shape"
|
||||
android:scrollbars="vertical"
|
||||
android:importantForAutofill="no"
|
||||
android:autofillHints="password" />
|
||||
android:autofillHints="password"
|
||||
tools:ignore="UnusedAttribute" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/left_drawer"
|
||||
@@ -96,7 +100,7 @@
|
||||
android:visibility="gone"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="37.5dp"
|
||||
android:background="@android:drawable/screen_background_dark_transparent"
|
||||
android:background="@color/black"
|
||||
android:layout_alignParentBottom="true" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
@@ -66,7 +66,7 @@ afterEvaluate {
|
||||
from components.release
|
||||
groupId = 'com.termux'
|
||||
artifactId = 'terminal-emulator'
|
||||
version = '0.117'
|
||||
version = '0.118.0'
|
||||
artifact(sourceJar)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
package com.termux.terminal;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
|
||||
public class Logger {
|
||||
|
||||
public static void logError(TerminalSessionClient client, String logTag, String message) {
|
||||
if (client != null)
|
||||
client.logError(logTag, message);
|
||||
else
|
||||
Log.e(logTag, message);
|
||||
}
|
||||
|
||||
public static void logWarn(TerminalSessionClient client, String logTag, String message) {
|
||||
if (client != null)
|
||||
client.logWarn(logTag, message);
|
||||
else
|
||||
Log.w(logTag, message);
|
||||
}
|
||||
|
||||
public static void logInfo(TerminalSessionClient client, String logTag, String message) {
|
||||
if (client != null)
|
||||
client.logInfo(logTag, message);
|
||||
else
|
||||
Log.i(logTag, message);
|
||||
}
|
||||
|
||||
public static void logDebug(TerminalSessionClient client, String logTag, String message) {
|
||||
if (client != null)
|
||||
client.logDebug(logTag, message);
|
||||
else
|
||||
Log.d(logTag, message);
|
||||
}
|
||||
|
||||
public static void logVerbose(TerminalSessionClient client, String logTag, String message) {
|
||||
if (client != null)
|
||||
client.logVerbose(logTag, message);
|
||||
else
|
||||
Log.v(logTag, message);
|
||||
}
|
||||
|
||||
public static void logStackTraceWithMessage(TerminalSessionClient client, String tag, String message, Throwable throwable) {
|
||||
logError(client, tag, getMessageAndStackTraceString(message, throwable));
|
||||
}
|
||||
|
||||
public static String getMessageAndStackTraceString(String message, Throwable throwable) {
|
||||
if (message == null && throwable == null)
|
||||
return null;
|
||||
else if (message != null && throwable != null)
|
||||
return message + ":\n" + getStackTraceString(throwable);
|
||||
else if (throwable == null)
|
||||
return message;
|
||||
else
|
||||
return getStackTraceString(throwable);
|
||||
}
|
||||
|
||||
public static String getStackTraceString(Throwable throwable) {
|
||||
if (throwable == null) return null;
|
||||
|
||||
String stackTraceString = null;
|
||||
|
||||
try {
|
||||
StringWriter errors = new StringWriter();
|
||||
PrintWriter pw = new PrintWriter(errors);
|
||||
throwable.printStackTrace(pw);
|
||||
pw.close();
|
||||
stackTraceString = errors.toString();
|
||||
errors.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return stackTraceString;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -54,7 +54,7 @@ public final class TerminalBuffer {
|
||||
}
|
||||
|
||||
public String getSelectedText(int selX1, int selY1, int selX2, int selY2, boolean joinBackLines) {
|
||||
return getSelectedText(selX1, selY1, selX2, selY2, true, false);
|
||||
return getSelectedText(selX1, selY1, selX2, selY2, joinBackLines, false);
|
||||
}
|
||||
|
||||
public String getSelectedText(int selX1, int selY1, int selX2, int selY2, boolean joinBackLines, boolean joinFullLines) {
|
||||
@@ -93,8 +93,11 @@ public final class TerminalBuffer {
|
||||
if (c != ' ') lastPrintingCharIndex = i;
|
||||
}
|
||||
}
|
||||
if (lastPrintingCharIndex != -1)
|
||||
builder.append(line, x1Index, lastPrintingCharIndex - x1Index + 1);
|
||||
|
||||
int len = lastPrintingCharIndex - x1Index + 1;
|
||||
if (lastPrintingCharIndex != -1 && len > 0)
|
||||
builder.append(line, x1Index, len);
|
||||
|
||||
boolean lineFillsWidth = lastPrintingCharIndex == x2Index - 1;
|
||||
if ((!joinBackLines || !rowLineWrap) && (!joinFullLines || !lineFillsWidth)
|
||||
&& row < selY2 && row < mScreenRows - 1) builder.append('\n');
|
||||
@@ -446,8 +449,8 @@ public final class TerminalBuffer {
|
||||
}
|
||||
|
||||
public void setChar(int column, int row, int codePoint, long style) {
|
||||
if (row >= mScreenRows || column >= mColumns)
|
||||
throw new IllegalArgumentException("row=" + row + ", column=" + column + ", mScreenRows=" + mScreenRows + ", mColumns=" + mColumns);
|
||||
if (row < 0 || row >= mScreenRows || column < 0 || column >= mColumns)
|
||||
throw new IllegalArgumentException("TerminalBuffer.setChar(): row=" + row + ", column=" + column + ", mScreenRows=" + mScreenRows + ", mColumns=" + mColumns);
|
||||
row = externalToInternalRow(row);
|
||||
allocateFullLineIfNecessary(row).setChar(column, codePoint, style);
|
||||
}
|
||||
|
||||
@@ -71,6 +71,7 @@ public final class TerminalColorScheme {
|
||||
|
||||
public void updateWith(Properties props) {
|
||||
reset();
|
||||
boolean cursorPropExists = false;
|
||||
for (Map.Entry<Object, Object> entries : props.entrySet()) {
|
||||
String key = (String) entries.getKey();
|
||||
String value = (String) entries.getValue();
|
||||
@@ -82,6 +83,7 @@ public final class TerminalColorScheme {
|
||||
colorIndex = TextStyle.COLOR_INDEX_BACKGROUND;
|
||||
} else if (key.equals("cursor")) {
|
||||
colorIndex = TextStyle.COLOR_INDEX_CURSOR;
|
||||
cursorPropExists = true;
|
||||
} else if (key.startsWith("color")) {
|
||||
try {
|
||||
colorIndex = Integer.parseInt(key.substring(5));
|
||||
@@ -98,6 +100,27 @@ public final class TerminalColorScheme {
|
||||
|
||||
mDefaultColors[colorIndex] = colorValue;
|
||||
}
|
||||
|
||||
if (!cursorPropExists)
|
||||
setCursorColorForBackground();
|
||||
}
|
||||
|
||||
/**
|
||||
* If the "cursor" color is not set by user, we need to decide on the appropriate color that will
|
||||
* be visible on the current terminal background. White will not be visible on light backgrounds
|
||||
* and black won't be visible on dark backgrounds. So we find the perceived brightness of the
|
||||
* background color and if its below the threshold (too dark), we use white cursor and if its
|
||||
* above (too bright), we use black cursor.
|
||||
*/
|
||||
public void setCursorColorForBackground() {
|
||||
int backgroundColor = mDefaultColors[TextStyle.COLOR_INDEX_BACKGROUND];
|
||||
int brightness = TerminalColors.getPerceivedBrightnessOfColor(backgroundColor);
|
||||
if (brightness > 0) {
|
||||
if (brightness < 130)
|
||||
mDefaultColors[TextStyle.COLOR_INDEX_CURSOR] = 0xffffffff;
|
||||
else
|
||||
mDefaultColors[TextStyle.COLOR_INDEX_CURSOR] = 0xff000000;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.termux.terminal;
|
||||
|
||||
import android.graphics.Color;
|
||||
|
||||
/** Current terminal colors (if different from default). */
|
||||
public final class TerminalColors {
|
||||
|
||||
@@ -73,4 +75,22 @@ public final class TerminalColors {
|
||||
if (c != 0) mCurrentColors[intoIndex] = c;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the perceived brightness of the color based on its RGB components.
|
||||
*
|
||||
* https://www.nbdtech.com/Blog/archive/2008/04/27/Calculating-the-Perceived-Brightness-of-a-Color.aspx
|
||||
* http://alienryderflex.com/hsp.html
|
||||
*
|
||||
* @param color The color code int.
|
||||
* @return Returns value between 0-255.
|
||||
*/
|
||||
public static int getPerceivedBrightnessOfColor(int color) {
|
||||
return (int)
|
||||
Math.floor(Math.sqrt(
|
||||
Math.pow(Color.red(color), 2) * 0.241 +
|
||||
Math.pow(Color.green(color), 2) * 0.691 +
|
||||
Math.pow(Color.blue(color), 2) * 0.068
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -126,6 +126,10 @@ 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;
|
||||
@@ -796,7 +800,6 @@ public final class TerminalEmulator {
|
||||
int columnsToDelete = Math.min(getArg0(1), columnsAfterCursor);
|
||||
int columnsToMove = columnsAfterCursor - columnsToDelete;
|
||||
mScreen.blockCopy(mCursorCol + columnsToDelete, 0, columnsToMove, mRows, mCursorCol, 0);
|
||||
blockClear(mCursorRow + columnsToMove, 0, columnsToDelete, mRows);
|
||||
} else {
|
||||
unknownSequence(b);
|
||||
}
|
||||
@@ -825,7 +828,7 @@ public final class TerminalEmulator {
|
||||
if (internalBit != -1) {
|
||||
value = isDecsetInternalBitSet(internalBit) ? 1 : 2; // 1=set, 2=reset.
|
||||
} else {
|
||||
mClient.logError(LOG_TAG, "Got DECRQM for unrecognized private DEC mode=" + mode);
|
||||
Logger.logError(mClient, LOG_TAG, "Got DECRQM for unrecognized private DEC mode=" + mode);
|
||||
value = 0; // 0=not recognized, 3=permanently set, 4=permanently reset
|
||||
}
|
||||
}
|
||||
@@ -936,10 +939,17 @@ public final class TerminalEmulator {
|
||||
for (String part : dcs.substring(2).split(";")) {
|
||||
if (part.length() % 2 == 0) {
|
||||
StringBuilder transBuffer = new StringBuilder();
|
||||
char c;
|
||||
for (int i = 0; i < part.length(); i += 2) {
|
||||
char c = (char) Long.decode("0x" + part.charAt(i) + "" + part.charAt(i + 1)).longValue();
|
||||
try {
|
||||
c = (char) Long.decode("0x" + part.charAt(i) + "" + part.charAt(i + 1)).longValue();
|
||||
} catch (NumberFormatException e) {
|
||||
Logger.logStackTraceWithMessage(mClient, LOG_TAG, "Invalid device termcap/terminfo encoded name \"" + part + "\"", e);
|
||||
continue;
|
||||
}
|
||||
transBuffer.append(c);
|
||||
}
|
||||
|
||||
String trans = transBuffer.toString();
|
||||
String responseValue;
|
||||
switch (trans) {
|
||||
@@ -962,7 +972,7 @@ public final class TerminalEmulator {
|
||||
case "&8": // Undo key - ignore.
|
||||
break;
|
||||
default:
|
||||
mClient.logWarn(LOG_TAG, "Unhandled termcap/terminfo name: '" + trans + "'");
|
||||
Logger.logWarn(mClient, LOG_TAG, "Unhandled termcap/terminfo name: '" + trans + "'");
|
||||
}
|
||||
// Respond with invalid request:
|
||||
mSession.write("\033P0+r" + part + "\033\\");
|
||||
@@ -974,12 +984,12 @@ public final class TerminalEmulator {
|
||||
mSession.write("\033P1+r" + part + "=" + hexEncoded + "\033\\");
|
||||
}
|
||||
} else {
|
||||
mClient.logError(LOG_TAG, "Invalid device termcap/terminfo name of odd length: " + part);
|
||||
Logger.logError(mClient, LOG_TAG, "Invalid device termcap/terminfo name of odd length: " + part);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (LOG_ESCAPE_SEQUENCES)
|
||||
mClient.logError(LOG_TAG, "Unrecognized device control string: " + dcs);
|
||||
Logger.logError(mClient, LOG_TAG, "Unrecognized device control string: " + dcs);
|
||||
}
|
||||
finishSequence();
|
||||
}
|
||||
@@ -1069,7 +1079,7 @@ public final class TerminalEmulator {
|
||||
int externalBit = mArgs[i];
|
||||
int internalBit = mapDecSetBitToInternalBit(externalBit);
|
||||
if (internalBit == -1) {
|
||||
mClient.logWarn(LOG_TAG, "Ignoring request to save/recall decset bit=" + externalBit);
|
||||
Logger.logWarn(mClient, LOG_TAG, "Ignoring request to save/recall decset bit=" + externalBit);
|
||||
} else {
|
||||
if (b == 's') {
|
||||
mSavedDecSetFlags |= internalBit;
|
||||
@@ -1259,7 +1269,7 @@ public final class TerminalEmulator {
|
||||
// (1) enables this feature for keys except for those with well-known behavior, e.g., Tab, Backarrow and
|
||||
// some special control character cases, e.g., Control-Space to make a NUL.
|
||||
// (2) enables this feature for keys including the exceptions listed.
|
||||
mClient.logError(LOG_TAG, "(ignored) CSI > MODIFY RESOURCE: " + getArg0(-1) + " to " + getArg1(-1));
|
||||
Logger.logError(mClient, LOG_TAG, "(ignored) CSI > MODIFY RESOURCE: " + getArg0(-1) + " to " + getArg1(-1));
|
||||
break;
|
||||
default:
|
||||
parseArg(b);
|
||||
@@ -1380,6 +1390,8 @@ public final class TerminalEmulator {
|
||||
break;
|
||||
case '[':
|
||||
continueSequence(ESC_CSI);
|
||||
mIsCSIStart = true;
|
||||
mLastCSIArg = null;
|
||||
break;
|
||||
case '=': // DECKPAM
|
||||
setDecsetinternalBit(DECSET_BIT_APPLICATION_KEYPAD, true);
|
||||
@@ -1806,7 +1818,7 @@ public final class TerminalEmulator {
|
||||
int firstArg = mArgs[i + 1];
|
||||
if (firstArg == 2) {
|
||||
if (i + 4 > mArgIndex) {
|
||||
mClient.logWarn(LOG_TAG, "Too few CSI" + code + ";2 RGB arguments");
|
||||
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];
|
||||
if (red < 0 || green < 0 || blue < 0 || red > 255 || green > 255 || blue > 255) {
|
||||
@@ -1831,7 +1843,7 @@ public final class TerminalEmulator {
|
||||
mBackColor = color;
|
||||
}
|
||||
} else {
|
||||
if (LOG_ESCAPE_SEQUENCES) mClient.logWarn(LOG_TAG, "Invalid color index: " + color);
|
||||
if (LOG_ESCAPE_SEQUENCES) Logger.logWarn(mClient, LOG_TAG, "Invalid color index: " + color);
|
||||
}
|
||||
} else {
|
||||
finishSequenceAndLogError("Invalid ISO-8613-3 SGR first argument: " + firstArg);
|
||||
@@ -1848,7 +1860,7 @@ public final class TerminalEmulator {
|
||||
mBackColor = code - 100 + 8;
|
||||
} else {
|
||||
if (LOG_ESCAPE_SEQUENCES)
|
||||
mClient.logWarn(LOG_TAG, String.format("SGR unknown code %d", code));
|
||||
Logger.logWarn(mClient, LOG_TAG, String.format("SGR unknown code %d", code));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1982,7 +1994,7 @@ public final class TerminalEmulator {
|
||||
String clipboardText = new String(Base64.decode(textParameter.substring(startIndex), 0), StandardCharsets.UTF_8);
|
||||
mSession.onCopyTextToClipboard(clipboardText);
|
||||
} catch (Exception e) {
|
||||
mClient.logError(LOG_TAG, "OSC Manipulate selection, invalid string '" + textParameter + "");
|
||||
Logger.logError(mClient, LOG_TAG, "OSC Manipulate selection, invalid string '" + textParameter + "");
|
||||
}
|
||||
break;
|
||||
case 104:
|
||||
@@ -2087,28 +2099,57 @@ public final class TerminalEmulator {
|
||||
}
|
||||
}
|
||||
|
||||
/** Process the next ASCII character of a parameter. */
|
||||
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;
|
||||
/**
|
||||
* 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
|
||||
* 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
|
||||
* */
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
mArgs[mArgIndex] = value;
|
||||
continueSequence(mEscapeState);
|
||||
} else if (b == ';') {
|
||||
if (mArgIndex < mArgs.length) {
|
||||
mArgIndex++;
|
||||
}
|
||||
continueSequence(mEscapeState);
|
||||
} else {
|
||||
unknownSequence(b);
|
||||
}
|
||||
continueSequence(mEscapeState);
|
||||
} else if (b == ';') {
|
||||
if (mArgIndex < mArgs.length) {
|
||||
mArgIndex++;
|
||||
}
|
||||
continueSequence(mEscapeState);
|
||||
} else {
|
||||
unknownSequence(b);
|
||||
mLastCSIArg = b;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2178,7 +2219,7 @@ public final class TerminalEmulator {
|
||||
}
|
||||
|
||||
private void finishSequenceAndLogError(String error) {
|
||||
if (LOG_ESCAPE_SEQUENCES) mClient.logWarn(LOG_TAG, error);
|
||||
if (LOG_ESCAPE_SEQUENCES) Logger.logWarn(mClient, LOG_TAG, error);
|
||||
finishSequence();
|
||||
}
|
||||
|
||||
@@ -2326,7 +2367,14 @@ public final class TerminalEmulator {
|
||||
}
|
||||
|
||||
int offsetDueToCombiningChar = ((displayWidth <= 0 && mCursorCol > 0 && !mAboutToAutoWrap) ? 1 : 0);
|
||||
mScreen.setChar(mCursorCol - offsetDueToCombiningChar, mCursorRow, codePoint, getStyle());
|
||||
int column = mCursorCol - offsetDueToCombiningChar;
|
||||
|
||||
// Fix TerminalRow.setChar() ArrayIndexOutOfBoundsException index=-1 exception reported
|
||||
// The offsetDueToCombiningChar would never be 1 if mCursorCol was 0 to get column/index=-1,
|
||||
// so was mCursorCol changed after the offsetDueToCombiningChar conditional by another thread?
|
||||
// TODO: Check if there are thread synchronization issues with mCursorCol and mCursorRow, possibly causing others bugs too.
|
||||
if (column < 0) column = 0;
|
||||
mScreen.setChar(column, mCursorRow, codePoint, getStyle());
|
||||
|
||||
if (autoWrap && displayWidth > 0)
|
||||
mAboutToAutoWrap = (mCursorCol == mRightMargin - displayWidth);
|
||||
|
||||
@@ -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;
|
||||
@@ -124,6 +150,9 @@ public final class TerminalRow {
|
||||
|
||||
// https://github.com/steven676/Android-Terminal-Emulator/commit/9a47042620bec87617f0b4f5d50568535668fe26
|
||||
public void setChar(int columnToSet, int codePoint, long style) {
|
||||
if (columnToSet < 0 || columnToSet >= mStyle.length)
|
||||
throw new IllegalArgumentException("TerminalRow.setChar(): columnToSet=" + columnToSet + ", codePoint=" + codePoint + ", style=" + style);
|
||||
|
||||
mStyle[columnToSet] = style;
|
||||
|
||||
final int newCodePointDisplayWidth = WcWidth.width(codePoint);
|
||||
@@ -160,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;
|
||||
}
|
||||
@@ -186,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 {
|
||||
|
||||
@@ -236,7 +236,7 @@ public final class TerminalSession extends TerminalOutput {
|
||||
try {
|
||||
Os.kill(mShellPid, OsConstants.SIGKILL);
|
||||
} catch (ErrnoException e) {
|
||||
mClient.logWarn(LOG_TAG, "Failed sending SIGKILL: " + e.getMessage());
|
||||
Logger.logWarn(mClient, LOG_TAG, "Failed sending SIGKILL: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -308,7 +308,7 @@ public final class TerminalSession extends TerminalOutput {
|
||||
return outputPath;
|
||||
}
|
||||
} catch (IOException | SecurityException e) {
|
||||
mClient.logStackTraceWithMessage(LOG_TAG, "Error getting current directory", e);
|
||||
Logger.logStackTraceWithMessage(mClient, LOG_TAG, "Error getting current directory", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -326,7 +326,7 @@ public final class TerminalSession extends TerminalOutput {
|
||||
descriptorField.setAccessible(true);
|
||||
descriptorField.set(result, fileDescriptor);
|
||||
} catch (NoSuchFieldException | IllegalAccessException | IllegalArgumentException e) {
|
||||
client.logStackTraceWithMessage(LOG_TAG, "Error accessing FileDescriptor#descriptor private field", e);
|
||||
Logger.logStackTraceWithMessage(client, LOG_TAG, "Error accessing FileDescriptor#descriptor private field", e);
|
||||
System.exit(1);
|
||||
}
|
||||
return result;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.termux.terminal;
|
||||
|
||||
/**
|
||||
* Implementation of wcwidth(3) for Unicode 9.
|
||||
* Implementation of wcwidth(3) for Unicode 15.
|
||||
*
|
||||
* Implementation from https://github.com/jquast/wcwidth but we return 0 for unprintable characters.
|
||||
*
|
||||
@@ -9,12 +9,13 @@ package com.termux.terminal;
|
||||
* Must be kept in sync with the following:
|
||||
* https://github.com/termux/wcwidth
|
||||
* https://github.com/termux/libandroid-support
|
||||
* https://github.com/termux/termux-packages/tree/master/libandroid-support
|
||||
* https://github.com/termux/termux-packages/tree/master/packages/libandroid-support
|
||||
*/
|
||||
public final class WcWidth {
|
||||
|
||||
// From https://github.com/jquast/wcwidth/blob/master/wcwidth/table_zero.py
|
||||
// at commit b29897e5a1b403a0e36f7fc991614981cbc42475 (2020-07-14):
|
||||
// from https://github.com/jquast/wcwidth/pull/64
|
||||
// at commit 1b9b6585b0080ea5cb88dc9815796505724793fe (2022-12-16):
|
||||
private static final int[][] ZERO_WIDTH = {
|
||||
{0x00300, 0x0036f}, // Combining Grave Accent ..Combining Latin Small Le
|
||||
{0x00483, 0x00489}, // Combining Cyrillic Titlo..Combining Cyrillic Milli
|
||||
@@ -40,7 +41,8 @@ public final class WcWidth {
|
||||
{0x00825, 0x00827}, // Samaritan Vowel Sign Sho..Samaritan Vowel Sign U
|
||||
{0x00829, 0x0082d}, // Samaritan Vowel Sign Lon..Samaritan Mark Nequdaa
|
||||
{0x00859, 0x0085b}, // Mandaic Affrication Mark..Mandaic Gemination Mark
|
||||
{0x008d3, 0x008e1}, // Arabic Small Low Waw ..Arabic Small High Sign S
|
||||
{0x00898, 0x0089f}, // Arabic Small High Word A..Arabic Half Madda Over M
|
||||
{0x008ca, 0x008e1}, // Arabic Small High Farsi ..Arabic Small High Sign S
|
||||
{0x008e3, 0x00902}, // Arabic Turned Damma Belo..Devanagari Sign Anusvara
|
||||
{0x0093a, 0x0093a}, // Devanagari Vowel Sign Oe..Devanagari Vowel Sign Oe
|
||||
{0x0093c, 0x0093c}, // Devanagari Sign Nukta ..Devanagari Sign Nukta
|
||||
@@ -74,13 +76,14 @@ public final class WcWidth {
|
||||
{0x00b3f, 0x00b3f}, // Oriya Vowel Sign I ..Oriya Vowel Sign I
|
||||
{0x00b41, 0x00b44}, // Oriya Vowel Sign U ..Oriya Vowel Sign Vocalic
|
||||
{0x00b4d, 0x00b4d}, // Oriya Sign Virama ..Oriya Sign Virama
|
||||
{0x00b55, 0x00b56}, // (nil) ..Oriya Ai Length Mark
|
||||
{0x00b55, 0x00b56}, // Oriya Sign Overline ..Oriya Ai Length Mark
|
||||
{0x00b62, 0x00b63}, // Oriya Vowel Sign Vocalic..Oriya Vowel Sign Vocalic
|
||||
{0x00b82, 0x00b82}, // Tamil Sign Anusvara ..Tamil Sign Anusvara
|
||||
{0x00bc0, 0x00bc0}, // Tamil Vowel Sign Ii ..Tamil Vowel Sign Ii
|
||||
{0x00bcd, 0x00bcd}, // Tamil Sign Virama ..Tamil Sign Virama
|
||||
{0x00c00, 0x00c00}, // Telugu Sign Combining Ca..Telugu Sign Combining Ca
|
||||
{0x00c04, 0x00c04}, // Telugu Sign Combining An..Telugu Sign Combining An
|
||||
{0x00c3c, 0x00c3c}, // Telugu Sign Nukta ..Telugu Sign Nukta
|
||||
{0x00c3e, 0x00c40}, // Telugu Vowel Sign Aa ..Telugu Vowel Sign Ii
|
||||
{0x00c46, 0x00c48}, // Telugu Vowel Sign E ..Telugu Vowel Sign Ai
|
||||
{0x00c4a, 0x00c4d}, // Telugu Vowel Sign O ..Telugu Sign Virama
|
||||
@@ -97,7 +100,7 @@ public final class WcWidth {
|
||||
{0x00d41, 0x00d44}, // Malayalam Vowel Sign U ..Malayalam Vowel Sign Voc
|
||||
{0x00d4d, 0x00d4d}, // Malayalam Sign Virama ..Malayalam Sign Virama
|
||||
{0x00d62, 0x00d63}, // Malayalam Vowel Sign Voc..Malayalam Vowel Sign Voc
|
||||
{0x00d81, 0x00d81}, // (nil) ..(nil)
|
||||
{0x00d81, 0x00d81}, // Sinhala Sign Candrabindu..Sinhala Sign Candrabindu
|
||||
{0x00dca, 0x00dca}, // Sinhala Sign Al-lakuna ..Sinhala Sign Al-lakuna
|
||||
{0x00dd2, 0x00dd4}, // Sinhala Vowel Sign Ketti..Sinhala Vowel Sign Ketti
|
||||
{0x00dd6, 0x00dd6}, // Sinhala Vowel Sign Diga ..Sinhala Vowel Sign Diga
|
||||
@@ -106,7 +109,7 @@ public final class WcWidth {
|
||||
{0x00e47, 0x00e4e}, // Thai Character Maitaikhu..Thai Character Yamakkan
|
||||
{0x00eb1, 0x00eb1}, // Lao Vowel Sign Mai Kan ..Lao Vowel Sign Mai Kan
|
||||
{0x00eb4, 0x00ebc}, // Lao Vowel Sign I ..Lao Semivowel Sign Lo
|
||||
{0x00ec8, 0x00ecd}, // Lao Tone Mai Ek ..Lao Niggahita
|
||||
{0x00ec8, 0x00ece}, // Lao Tone Mai Ek ..(nil)
|
||||
{0x00f18, 0x00f19}, // Tibetan Astrological Sig..Tibetan Astrological Sig
|
||||
{0x00f35, 0x00f35}, // Tibetan Mark Ngas Bzung ..Tibetan Mark Ngas Bzung
|
||||
{0x00f37, 0x00f37}, // Tibetan Mark Ngas Bzung ..Tibetan Mark Ngas Bzung
|
||||
@@ -130,7 +133,7 @@ public final class WcWidth {
|
||||
{0x0109d, 0x0109d}, // Myanmar Vowel Sign Aiton..Myanmar Vowel Sign Aiton
|
||||
{0x0135d, 0x0135f}, // Ethiopic Combining Gemin..Ethiopic Combining Gemin
|
||||
{0x01712, 0x01714}, // Tagalog Vowel Sign I ..Tagalog Sign Virama
|
||||
{0x01732, 0x01734}, // Hanunoo Vowel Sign I ..Hanunoo Sign Pamudpod
|
||||
{0x01732, 0x01733}, // Hanunoo Vowel Sign I ..Hanunoo Vowel Sign U
|
||||
{0x01752, 0x01753}, // Buhid Vowel Sign I ..Buhid Vowel Sign U
|
||||
{0x01772, 0x01773}, // Tagbanwa Vowel Sign I ..Tagbanwa Vowel Sign U
|
||||
{0x017b4, 0x017b5}, // Khmer Vowel Inherent Aq ..Khmer Vowel Inherent Aa
|
||||
@@ -139,6 +142,7 @@ public final class WcWidth {
|
||||
{0x017c9, 0x017d3}, // Khmer Sign Muusikatoan ..Khmer Sign Bathamasat
|
||||
{0x017dd, 0x017dd}, // Khmer Sign Atthacan ..Khmer Sign Atthacan
|
||||
{0x0180b, 0x0180d}, // Mongolian Free Variation..Mongolian Free Variation
|
||||
{0x0180f, 0x0180f}, // Mongolian Free Variation..Mongolian Free Variation
|
||||
{0x01885, 0x01886}, // Mongolian Letter Ali Gal..Mongolian Letter Ali Gal
|
||||
{0x018a9, 0x018a9}, // Mongolian Letter Ali Gal..Mongolian Letter Ali Gal
|
||||
{0x01920, 0x01922}, // Limbu Vowel Sign A ..Limbu Vowel Sign U
|
||||
@@ -154,7 +158,7 @@ public final class WcWidth {
|
||||
{0x01a65, 0x01a6c}, // Tai Tham Vowel Sign I ..Tai Tham Vowel Sign Oa B
|
||||
{0x01a73, 0x01a7c}, // Tai Tham Vowel Sign Oa A..Tai Tham Sign Khuen-lue
|
||||
{0x01a7f, 0x01a7f}, // Tai Tham Combining Crypt..Tai Tham Combining Crypt
|
||||
{0x01ab0, 0x01ac0}, // Combining Doubled Circum..(nil)
|
||||
{0x01ab0, 0x01ace}, // Combining Doubled Circum..Combining Latin Small Le
|
||||
{0x01b00, 0x01b03}, // Balinese Sign Ulu Ricem ..Balinese Sign Surang
|
||||
{0x01b34, 0x01b34}, // Balinese Sign Rerekan ..Balinese Sign Rerekan
|
||||
{0x01b36, 0x01b3a}, // Balinese Vowel Sign Ulu ..Balinese Vowel Sign Ra R
|
||||
@@ -177,8 +181,7 @@ public final class WcWidth {
|
||||
{0x01ced, 0x01ced}, // Vedic Sign Tiryak ..Vedic Sign Tiryak
|
||||
{0x01cf4, 0x01cf4}, // Vedic Tone Candra Above ..Vedic Tone Candra Above
|
||||
{0x01cf8, 0x01cf9}, // Vedic Tone Ring Above ..Vedic Tone Double Ring A
|
||||
{0x01dc0, 0x01df9}, // Combining Dotted Grave A..Combining Wide Inverted
|
||||
{0x01dfb, 0x01dff}, // Combining Deletion Mark ..Combining Right Arrowhea
|
||||
{0x01dc0, 0x01dff}, // Combining Dotted Grave A..Combining Right Arrowhea
|
||||
{0x020d0, 0x020f0}, // Combining Left Harpoon A..Combining Asterisk Above
|
||||
{0x02cef, 0x02cf1}, // Coptic Combining Ni Abov..Coptic Combining Spiritu
|
||||
{0x02d7f, 0x02d7f}, // Tifinagh Consonant Joine..Tifinagh Consonant Joine
|
||||
@@ -193,7 +196,7 @@ public final class WcWidth {
|
||||
{0x0a806, 0x0a806}, // Syloti Nagri Sign Hasant..Syloti Nagri Sign Hasant
|
||||
{0x0a80b, 0x0a80b}, // Syloti Nagri Sign Anusva..Syloti Nagri Sign Anusva
|
||||
{0x0a825, 0x0a826}, // Syloti Nagri Vowel Sign ..Syloti Nagri Vowel Sign
|
||||
{0x0a82c, 0x0a82c}, // (nil) ..(nil)
|
||||
{0x0a82c, 0x0a82c}, // Syloti Nagri Sign Altern..Syloti Nagri Sign Altern
|
||||
{0x0a8c4, 0x0a8c5}, // Saurashtra Sign Virama ..Saurashtra Sign Candrabi
|
||||
{0x0a8e0, 0x0a8f1}, // Combining Devanagari Dig..Combining Devanagari Sig
|
||||
{0x0a8ff, 0x0a8ff}, // Devanagari Vowel Sign Ay..Devanagari Vowel Sign Ay
|
||||
@@ -233,13 +236,18 @@ public final class WcWidth {
|
||||
{0x10a3f, 0x10a3f}, // Kharoshthi Virama ..Kharoshthi Virama
|
||||
{0x10ae5, 0x10ae6}, // Manichaean Abbreviation ..Manichaean Abbreviation
|
||||
{0x10d24, 0x10d27}, // Hanifi Rohingya Sign Har..Hanifi Rohingya Sign Tas
|
||||
{0x10eab, 0x10eac}, // (nil) ..(nil)
|
||||
{0x10eab, 0x10eac}, // Yezidi Combining Hamza M..Yezidi Combining Madda M
|
||||
{0x10efd, 0x10eff}, // (nil) ..(nil)
|
||||
{0x10f46, 0x10f50}, // Sogdian Combining Dot Be..Sogdian Combining Stroke
|
||||
{0x10f82, 0x10f85}, // Old Uyghur Combining Dot..Old Uyghur Combining Two
|
||||
{0x11001, 0x11001}, // Brahmi Sign Anusvara ..Brahmi Sign Anusvara
|
||||
{0x11038, 0x11046}, // Brahmi Vowel Sign Aa ..Brahmi Virama
|
||||
{0x11070, 0x11070}, // Brahmi Sign Old Tamil Vi..Brahmi Sign Old Tamil Vi
|
||||
{0x11073, 0x11074}, // Brahmi Vowel Sign Old Ta..Brahmi Vowel Sign Old Ta
|
||||
{0x1107f, 0x11081}, // Brahmi Number Joiner ..Kaithi Sign Anusvara
|
||||
{0x110b3, 0x110b6}, // Kaithi Vowel Sign U ..Kaithi Vowel Sign Ai
|
||||
{0x110b9, 0x110ba}, // Kaithi Sign Virama ..Kaithi Sign Nukta
|
||||
{0x110c2, 0x110c2}, // Kaithi Vowel Sign Vocali..Kaithi Vowel Sign Vocali
|
||||
{0x11100, 0x11102}, // Chakma Sign Candrabindu ..Chakma Sign Visarga
|
||||
{0x11127, 0x1112b}, // Chakma Vowel Sign A ..Chakma Vowel Sign Uu
|
||||
{0x1112d, 0x11134}, // Chakma Vowel Sign Ai ..Chakma Maayyaa
|
||||
@@ -247,11 +255,12 @@ public final class WcWidth {
|
||||
{0x11180, 0x11181}, // Sharada Sign Candrabindu..Sharada Sign Anusvara
|
||||
{0x111b6, 0x111be}, // Sharada Vowel Sign U ..Sharada Vowel Sign O
|
||||
{0x111c9, 0x111cc}, // Sharada Sandhi Mark ..Sharada Extra Short Vowe
|
||||
{0x111cf, 0x111cf}, // (nil) ..(nil)
|
||||
{0x111cf, 0x111cf}, // Sharada Sign Inverted Ca..Sharada Sign Inverted Ca
|
||||
{0x1122f, 0x11231}, // Khojki Vowel Sign U ..Khojki Vowel Sign Ai
|
||||
{0x11234, 0x11234}, // Khojki Sign Anusvara ..Khojki Sign Anusvara
|
||||
{0x11236, 0x11237}, // Khojki Sign Nukta ..Khojki Sign Shadda
|
||||
{0x1123e, 0x1123e}, // Khojki Sign Sukun ..Khojki Sign Sukun
|
||||
{0x11241, 0x11241}, // (nil) ..(nil)
|
||||
{0x112df, 0x112df}, // Khudawadi Sign Anusvara ..Khudawadi Sign Anusvara
|
||||
{0x112e3, 0x112ea}, // Khudawadi Vowel Sign U ..Khudawadi Sign Virama
|
||||
{0x11300, 0x11301}, // Grantha Sign Combining A..Grantha Sign Candrabindu
|
||||
@@ -283,9 +292,9 @@ public final class WcWidth {
|
||||
{0x11727, 0x1172b}, // Ahom Vowel Sign Aw ..Ahom Sign Killer
|
||||
{0x1182f, 0x11837}, // Dogra Vowel Sign U ..Dogra Sign Anusvara
|
||||
{0x11839, 0x1183a}, // Dogra Sign Virama ..Dogra Sign Nukta
|
||||
{0x1193b, 0x1193c}, // (nil) ..(nil)
|
||||
{0x1193e, 0x1193e}, // (nil) ..(nil)
|
||||
{0x11943, 0x11943}, // (nil) ..(nil)
|
||||
{0x1193b, 0x1193c}, // Dives Akuru Sign Anusvar..Dives Akuru Sign Candrab
|
||||
{0x1193e, 0x1193e}, // Dives Akuru Virama ..Dives Akuru Virama
|
||||
{0x11943, 0x11943}, // Dives Akuru Sign Nukta ..Dives Akuru Sign Nukta
|
||||
{0x119d4, 0x119d7}, // Nandinagari Vowel Sign U..Nandinagari Vowel Sign V
|
||||
{0x119da, 0x119db}, // Nandinagari Vowel Sign E..Nandinagari Vowel Sign A
|
||||
{0x119e0, 0x119e0}, // Nandinagari Sign Virama ..Nandinagari Sign Virama
|
||||
@@ -313,12 +322,20 @@ public final class WcWidth {
|
||||
{0x11d95, 0x11d95}, // Gunjala Gondi Sign Anusv..Gunjala Gondi Sign Anusv
|
||||
{0x11d97, 0x11d97}, // Gunjala Gondi Virama ..Gunjala Gondi Virama
|
||||
{0x11ef3, 0x11ef4}, // Makasar Vowel Sign I ..Makasar Vowel Sign U
|
||||
{0x11f00, 0x11f01}, // (nil) ..(nil)
|
||||
{0x11f36, 0x11f3a}, // (nil) ..(nil)
|
||||
{0x11f40, 0x11f40}, // (nil) ..(nil)
|
||||
{0x11f42, 0x11f42}, // (nil) ..(nil)
|
||||
{0x13440, 0x13440}, // (nil) ..(nil)
|
||||
{0x13447, 0x13455}, // (nil) ..(nil)
|
||||
{0x16af0, 0x16af4}, // Bassa Vah Combining High..Bassa Vah Combining High
|
||||
{0x16b30, 0x16b36}, // Pahawh Hmong Mark Cim Tu..Pahawh Hmong Mark Cim Ta
|
||||
{0x16f4f, 0x16f4f}, // Miao Sign Consonant Modi..Miao Sign Consonant Modi
|
||||
{0x16f8f, 0x16f92}, // Miao Tone Right ..Miao Tone Below
|
||||
{0x16fe4, 0x16fe4}, // (nil) ..(nil)
|
||||
{0x16fe4, 0x16fe4}, // Khitan Small Script Fill..Khitan Small Script Fill
|
||||
{0x1bc9d, 0x1bc9e}, // Duployan Thick Letter Se..Duployan Double Mark
|
||||
{0x1cf00, 0x1cf2d}, // Znamenny Combining Mark ..Znamenny Combining Mark
|
||||
{0x1cf30, 0x1cf46}, // Znamenny Combining Tonal..Znamenny Priznak Modifie
|
||||
{0x1d167, 0x1d169}, // Musical Symbol Combining..Musical Symbol Combining
|
||||
{0x1d17b, 0x1d182}, // Musical Symbol Combining..Musical Symbol Combining
|
||||
{0x1d185, 0x1d18b}, // Musical Symbol Combining..Musical Symbol Combining
|
||||
@@ -335,15 +352,19 @@ public final class WcWidth {
|
||||
{0x1e01b, 0x1e021}, // Combining Glagolitic Let..Combining Glagolitic Let
|
||||
{0x1e023, 0x1e024}, // Combining Glagolitic Let..Combining Glagolitic Let
|
||||
{0x1e026, 0x1e02a}, // Combining Glagolitic Let..Combining Glagolitic Let
|
||||
{0x1e08f, 0x1e08f}, // (nil) ..(nil)
|
||||
{0x1e130, 0x1e136}, // Nyiakeng Puachue Hmong T..Nyiakeng Puachue Hmong T
|
||||
{0x1e2ae, 0x1e2ae}, // Toto Sign Rising Tone ..Toto Sign Rising Tone
|
||||
{0x1e2ec, 0x1e2ef}, // Wancho Tone Tup ..Wancho Tone Koini
|
||||
{0x1e4ec, 0x1e4ef}, // (nil) ..(nil)
|
||||
{0x1e8d0, 0x1e8d6}, // Mende Kikakui Combining ..Mende Kikakui Combining
|
||||
{0x1e944, 0x1e94a}, // Adlam Alif Lengthener ..Adlam Nukta
|
||||
{0xe0100, 0xe01ef}, // Variation Selector-17 ..Variation Selector-256
|
||||
};
|
||||
|
||||
// https://github.com/jquast/wcwidth/blob/master/wcwidth/table_wide.py
|
||||
// at commit b29897e5a1b403a0e36f7fc991614981cbc42475 (2020-07-14):
|
||||
// from https://github.com/jquast/wcwidth/pull/64
|
||||
// at commit 1b9b6585b0080ea5cb88dc9815796505724793fe (2022-12-16):
|
||||
private static final int[][] WIDE_EASTASIAN = {
|
||||
{0x01100, 0x0115f}, // Hangul Choseong Kiyeok ..Hangul Choseong Filler
|
||||
{0x0231a, 0x0231b}, // Watch ..Hourglass
|
||||
@@ -392,7 +413,7 @@ public final class WcWidth {
|
||||
{0x03190, 0x031e3}, // Ideographic Annotation L..Cjk Stroke Q
|
||||
{0x031f0, 0x0321e}, // Katakana Letter Small Ku..Parenthesized Korean Cha
|
||||
{0x03220, 0x03247}, // Parenthesized Ideograph ..Circled Ideograph Koto
|
||||
{0x03250, 0x04dbf}, // Partnership Sign ..(nil)
|
||||
{0x03250, 0x04dbf}, // Partnership Sign ..Cjk Unified Ideograph-4d
|
||||
{0x04e00, 0x0a48c}, // Cjk Unified Ideograph-4e..Yi Syllable Yyr
|
||||
{0x0a490, 0x0a4c6}, // Yi Radical Qot ..Yi Radical Ke
|
||||
{0x0a960, 0x0a97c}, // Hangul Choseong Tikeut-m..Hangul Choseong Ssangyeo
|
||||
@@ -404,13 +425,18 @@ public final class WcWidth {
|
||||
{0x0fe68, 0x0fe6b}, // Small Reverse Solidus ..Small Commercial At
|
||||
{0x0ff01, 0x0ff60}, // Fullwidth Exclamation Ma..Fullwidth Right White Pa
|
||||
{0x0ffe0, 0x0ffe6}, // Fullwidth Cent Sign ..Fullwidth Won Sign
|
||||
{0x16fe0, 0x16fe4}, // Tangut Iteration Mark ..(nil)
|
||||
{0x16ff0, 0x16ff1}, // (nil) ..(nil)
|
||||
{0x16fe0, 0x16fe4}, // Tangut Iteration Mark ..Khitan Small Script Fill
|
||||
{0x16ff0, 0x16ff1}, // Vietnamese Alternate Rea..Vietnamese Alternate Rea
|
||||
{0x17000, 0x187f7}, // (nil) ..(nil)
|
||||
{0x18800, 0x18cd5}, // Tangut Component-001 ..(nil)
|
||||
{0x18800, 0x18cd5}, // Tangut Component-001 ..Khitan Small Script Char
|
||||
{0x18d00, 0x18d08}, // (nil) ..(nil)
|
||||
{0x1b000, 0x1b11e}, // Katakana Letter Archaic ..Hentaigana Letter N-mu-m
|
||||
{0x1aff0, 0x1aff3}, // Katakana Letter Minnan T..Katakana Letter Minnan T
|
||||
{0x1aff5, 0x1affb}, // Katakana Letter Minnan T..Katakana Letter Minnan N
|
||||
{0x1affd, 0x1affe}, // Katakana Letter Minnan N..Katakana Letter Minnan N
|
||||
{0x1b000, 0x1b122}, // Katakana Letter Archaic ..Katakana Letter Archaic
|
||||
{0x1b132, 0x1b132}, // (nil) ..(nil)
|
||||
{0x1b150, 0x1b152}, // Hiragana Letter Small Wi..Hiragana Letter Small Wo
|
||||
{0x1b155, 0x1b155}, // (nil) ..(nil)
|
||||
{0x1b164, 0x1b167}, // Katakana Letter Small Wi..Katakana Letter Small N
|
||||
{0x1b170, 0x1b2fb}, // Nushu Character-1b170 ..Nushu Character-1b2fb
|
||||
{0x1f004, 0x1f004}, // Mahjong Tile Red Dragon ..Mahjong Tile Red Dragon
|
||||
@@ -443,24 +469,24 @@ public final class WcWidth {
|
||||
{0x1f680, 0x1f6c5}, // Rocket ..Left Luggage
|
||||
{0x1f6cc, 0x1f6cc}, // Sleeping Accommodation ..Sleeping Accommodation
|
||||
{0x1f6d0, 0x1f6d2}, // Place Of Worship ..Shopping Trolley
|
||||
{0x1f6d5, 0x1f6d7}, // Hindu Temple ..(nil)
|
||||
{0x1f6d5, 0x1f6d7}, // Hindu Temple ..Elevator
|
||||
{0x1f6dc, 0x1f6df}, // (nil) ..Ring Buoy
|
||||
{0x1f6eb, 0x1f6ec}, // Airplane Departure ..Airplane Arriving
|
||||
{0x1f6f4, 0x1f6fc}, // Scooter ..(nil)
|
||||
{0x1f6f4, 0x1f6fc}, // Scooter ..Roller Skate
|
||||
{0x1f7e0, 0x1f7eb}, // Large Orange Circle ..Large Brown Square
|
||||
{0x1f90c, 0x1f93a}, // (nil) ..Fencer
|
||||
{0x1f7f0, 0x1f7f0}, // Heavy Equals Sign ..Heavy Equals Sign
|
||||
{0x1f90c, 0x1f93a}, // Pinched Fingers ..Fencer
|
||||
{0x1f93c, 0x1f945}, // Wrestlers ..Goal Net
|
||||
{0x1f947, 0x1f978}, // First Place Medal ..(nil)
|
||||
{0x1f97a, 0x1f9cb}, // Face With Pleading Eyes ..(nil)
|
||||
{0x1f9cd, 0x1f9ff}, // Standing Person ..Nazar Amulet
|
||||
{0x1fa70, 0x1fa74}, // Ballet Shoes ..(nil)
|
||||
{0x1fa78, 0x1fa7a}, // Drop Of Blood ..Stethoscope
|
||||
{0x1fa80, 0x1fa86}, // Yo-yo ..(nil)
|
||||
{0x1fa90, 0x1faa8}, // Ringed Planet ..(nil)
|
||||
{0x1fab0, 0x1fab6}, // (nil) ..(nil)
|
||||
{0x1fac0, 0x1fac2}, // (nil) ..(nil)
|
||||
{0x1fad0, 0x1fad6}, // (nil) ..(nil)
|
||||
{0x1f947, 0x1f9ff}, // First Place Medal ..Nazar Amulet
|
||||
{0x1fa70, 0x1fa7c}, // Ballet Shoes ..Crutch
|
||||
{0x1fa80, 0x1fa88}, // Yo-yo ..(nil)
|
||||
{0x1fa90, 0x1fabd}, // Ringed Planet ..(nil)
|
||||
{0x1fabf, 0x1fac5}, // (nil) ..Person With Crown
|
||||
{0x1face, 0x1fadb}, // (nil) ..(nil)
|
||||
{0x1fae0, 0x1fae8}, // Melting Face ..(nil)
|
||||
{0x1faf0, 0x1faf8}, // Hand With Index Finger A..(nil)
|
||||
{0x20000, 0x2fffd}, // Cjk Unified Ideograph-20..(nil)
|
||||
{0x30000, 0x3fffd}, // (nil) ..(nil)
|
||||
{0x30000, 0x3fffd}, // Cjk Unified Ideograph-30..(nil)
|
||||
};
|
||||
|
||||
|
||||
@@ -512,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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -151,6 +151,19 @@ public class TerminalTest extends TerminalTestCase {
|
||||
assertEquals(TextStyle.COLOR_INDEX_FOREGROUND, mTerminal.mForeColor);
|
||||
assertEquals(TextStyle.COLOR_INDEX_BACKGROUND, mTerminal.mBackColor);
|
||||
|
||||
// Test CSI resetting to default if sequence starts with ; or has sequential ;;
|
||||
// Check TerminalEmulator.parseArg()
|
||||
enterString("\033[31m\033[m");
|
||||
assertEquals(TextStyle.COLOR_INDEX_FOREGROUND, mTerminal.mForeColor);
|
||||
enterString("\033[31m\033[;m");
|
||||
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");
|
||||
assertEquals(TextStyle.COLOR_INDEX_FOREGROUND, mTerminal.mForeColor);
|
||||
enterString("\033[31;;m");
|
||||
assertEquals(TextStyle.COLOR_INDEX_FOREGROUND, mTerminal.mForeColor);
|
||||
|
||||
// 256 colors:
|
||||
enterString("\033[38;5;119m");
|
||||
assertEquals(119, mTerminal.mForeColor);
|
||||
|
||||
@@ -5,7 +5,7 @@ android {
|
||||
compileSdkVersion project.properties.compileSdkVersion.toInteger()
|
||||
|
||||
dependencies {
|
||||
implementation "androidx.annotation:annotation:1.2.0"
|
||||
implementation "androidx.annotation:annotation:1.3.0"
|
||||
api project(":terminal-emulator")
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ afterEvaluate {
|
||||
from components.release
|
||||
groupId = 'com.termux'
|
||||
artifactId = 'terminal-view'
|
||||
version = '0.117'
|
||||
version = '0.118.0'
|
||||
artifact(sourceJar)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,14 +183,19 @@ public class TextSelectionCursorController implements CursorController {
|
||||
int y1 = Math.round((mSelY1 - 1 - terminalView.getTopRow()) * terminalView.mRenderer.getFontLineSpacing());
|
||||
int y2 = Math.round((mSelY2 + 1 - terminalView.getTopRow()) * terminalView.mRenderer.getFontLineSpacing());
|
||||
|
||||
|
||||
if (x1 > x2) {
|
||||
int tmp = x1;
|
||||
x1 = x2;
|
||||
x2 = tmp;
|
||||
}
|
||||
|
||||
outRect.set(x1, y1 + mHandleHeight, x2, y2 + mHandleHeight);
|
||||
int terminalBottom = terminalView.getBottom();
|
||||
int top = y1 + mHandleHeight;
|
||||
int bottom = y2 + mHandleHeight;
|
||||
if (top > terminalBottom) top = terminalBottom;
|
||||
if (bottom > terminalBottom) bottom = terminalBottom;
|
||||
|
||||
outRect.set(x1, top, x2, bottom);
|
||||
}
|
||||
}, ActionMode.TYPE_FLOATING);
|
||||
}
|
||||
|
||||
@@ -6,9 +6,8 @@ android {
|
||||
|
||||
dependencies {
|
||||
implementation 'androidx.appcompat:appcompat:1.3.1'
|
||||
implementation "androidx.annotation:annotation:1.2.0"
|
||||
implementation "androidx.core:core:1.6.0-rc01"
|
||||
implementation "androidx.window:window:1.0.0-alpha09"
|
||||
implementation "androidx.annotation:annotation:1.3.0"
|
||||
implementation "androidx.core:core:1.6.0"
|
||||
implementation 'com.google.android.material:material:1.4.0'
|
||||
implementation "com.google.guava:guava:24.1-jre"
|
||||
implementation "io.noties.markwon:core:$markwonVersion"
|
||||
@@ -16,6 +15,10 @@ android {
|
||||
implementation "io.noties.markwon:linkify:$markwonVersion"
|
||||
implementation "io.noties.markwon:recycler:$markwonVersion"
|
||||
|
||||
// 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"
|
||||
|
||||
// Do not increment version higher than 2.5 or there
|
||||
// will be runtime exceptions on android < 8
|
||||
// due to missing classes like java.nio.file.Path.
|
||||
@@ -62,7 +65,7 @@ afterEvaluate {
|
||||
from components.release
|
||||
groupId = 'com.termux'
|
||||
artifactId = 'termux-shared'
|
||||
version = '0.117'
|
||||
version = '0.118.0'
|
||||
artifact(sourceJar)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,11 +4,14 @@ import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.graphics.Color;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.termux.shared.R;
|
||||
import com.termux.shared.logger.Logger;
|
||||
|
||||
public class MessageDialogUtils {
|
||||
|
||||
@@ -74,7 +77,19 @@ public class MessageDialogUtils {
|
||||
if (onDismiss != null)
|
||||
builder.setOnDismissListener(onDismiss);
|
||||
|
||||
builder.show();
|
||||
AlertDialog dialog = builder.create();
|
||||
|
||||
dialog.setOnShowListener(dialogInterface -> {
|
||||
Logger.logError("dialog");
|
||||
Button button = dialog.getButton(AlertDialog.BUTTON_POSITIVE);
|
||||
if (button != null)
|
||||
button.setTextColor(Color.BLACK);
|
||||
button = dialog.getButton(AlertDialog.BUTTON_NEGATIVE);
|
||||
if (button != null)
|
||||
button.setTextColor(Color.BLACK);
|
||||
});
|
||||
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
public static void exitAppWithErrorMessage(Context context, String titleText, String messageText) {
|
||||
|
||||
@@ -7,6 +7,7 @@ import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.termux.shared.R;
|
||||
import com.termux.shared.data.DataUtils;
|
||||
import com.termux.shared.termux.TermuxConstants;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -363,7 +364,7 @@ public class Logger {
|
||||
|
||||
|
||||
public static void showToast(final Context context, final String toastText, boolean longDuration) {
|
||||
if (context == null) return;
|
||||
if (context == null || DataUtils.isNullOrEmpty(toastText)) return;
|
||||
|
||||
new Handler(Looper.getMainLooper()).post(() -> Toast.makeText(context, toastText, longDuration ? Toast.LENGTH_LONG : Toast.LENGTH_SHORT).show());
|
||||
}
|
||||
|
||||
@@ -191,9 +191,8 @@ public class MarkdownUtils {
|
||||
}
|
||||
|
||||
public static Spanned getSpannedMarkdownText(Context context, String string) {
|
||||
|
||||
if (context == null || string == null) return null;
|
||||
final Markwon markwon = getSpannedMarkwonBuilder(context);
|
||||
|
||||
return markwon.toMarkdown(string);
|
||||
}
|
||||
|
||||
|
||||
@@ -512,7 +512,7 @@ public abstract class TermuxSharedProperties {
|
||||
|
||||
|
||||
public boolean areHardwareKeyboardShortcutsDisabled() {
|
||||
return (boolean) getInternalPropertyValue(TermuxPropertyConstants.KEY_DISABLE_TERMINAL_SESSION_CHANGE_TOAST, true);
|
||||
return (boolean) getInternalPropertyValue(TermuxPropertyConstants.KEY_DISABLE_HARDWARE_KEYBOARD_SHORTCUTS, true);
|
||||
}
|
||||
|
||||
public boolean areTerminalSessionChangeToastsDisabled() {
|
||||
|
||||
@@ -485,6 +485,12 @@ public final class TermuxConstants {
|
||||
* Termux miscellaneous urls.
|
||||
*/
|
||||
|
||||
/** Termux Site */
|
||||
public static final String TERMUX_SITE = TERMUX_APP_NAME + " Site"; // Default: "Termux Site"
|
||||
|
||||
/** Termux Site url */
|
||||
public static final String TERMUX_SITE_URL = "https://termux.dev"; // Default: "https://termux.dev"
|
||||
|
||||
/** Termux Wiki */
|
||||
public static final String TERMUX_WIKI = TERMUX_APP_NAME + " Wiki"; // Default: "Termux Wiki"
|
||||
|
||||
@@ -499,10 +505,10 @@ public final class TermuxConstants {
|
||||
|
||||
|
||||
/** Termux support email url */
|
||||
public static final String TERMUX_SUPPORT_EMAIL_URL = "termuxreports@groups.io"; // Default: "termuxreports@groups.io"
|
||||
public static final String TERMUX_SUPPORT_EMAIL_URL = "support@termux.dev"; // Default: "support@termux.dev"
|
||||
|
||||
/** Termux support email mailto url */
|
||||
public static final String TERMUX_SUPPORT_EMAIL_MAILTO_URL = "mailto:" + TERMUX_SUPPORT_EMAIL_URL; // Default: "mailto:termuxreports@groups.io"
|
||||
public static final String TERMUX_SUPPORT_EMAIL_MAILTO_URL = "mailto:" + TERMUX_SUPPORT_EMAIL_URL; // Default: "mailto:support@termux.dev"
|
||||
|
||||
|
||||
/** Termux Reddit subreddit */
|
||||
@@ -513,7 +519,7 @@ public final class TermuxConstants {
|
||||
|
||||
|
||||
/** Termux donate url */
|
||||
public static final String TERMUX_DONATE_URL = TERMUX_PACKAGES_GITHUB_REPO_URL + "/wiki/Donate"; // Default: "https://github.com/termux/termux-packages/wiki/Donate"
|
||||
public static final String TERMUX_DONATE_URL = TERMUX_SITE_URL + "/donate"; // Default: "https://termux.dev/donate"
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user