diff --git a/.gitattributes b/.gitattributes index 21c2769ab9..253738449b 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,5 +1,5 @@ * text=auto -*.bat eol=crlf -*.gradle eol=lf -*.mk eol=lf -*.sh eol=lf +*.bat text eol=crlf +*.gradle text eol=lf +*.mk text eol=lf +*.sh text eol=lf diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 7871554d6e..c7fc532b31 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,2 +1 @@ -patreon: termux -custom: https://paypal.me/fornwall +custom: https://termux.dev/donate diff --git a/.github/ISSUE_TEMPLATE/01-bug-report.yml b/.github/ISSUE_TEMPLATE/01-bug-report.yml new file mode 100644 index 0000000000..01104998a7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/01-bug-report.yml @@ -0,0 +1,44 @@ +name: "Bug report" +description: "Create a report to help us improve" +title: "[Bug]: " +labels: ["bug report"] +body: + - type: markdown + attributes: + value: | + This is a bug tracker of the Termux app. If you have issues with a package inside the app, then please open an issue at [termux-packages](https://github.com/termux/termux-packages) instead. + + Use search before you open an issue to check whether your issue has been already reported and perhaps solved. + + Android versions 5.x and 6.x are not supported anymore. + + If you have issues installing packages then please see https://github.com/termux/termux-packages/issues/6726. + - type: textarea + attributes: + label: Problem description + description: | + A clear and concise description of what the problem is. You may attach the logs, screenshots, screen video recording and whatever else that will help to understand the issue. + + Issues without proper description will be closed without solution. + validations: + required: true + - type: textarea + attributes: + label: Steps to reproduce the behavior. + description: | + Please post all necessary commands that are needed to reproduce the issue. + validations: + required: true + - type: textarea + attributes: + label: What is the expected behavior? + - type: textarea + attributes: + label: System information + description: Please provide info about your device + value: | + * Termux application version: + * Android OS version: + * Device model: + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/02-feature-request.yml b/.github/ISSUE_TEMPLATE/02-feature-request.yml new file mode 100644 index 0000000000..d09d97f81b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/02-feature-request.yml @@ -0,0 +1,19 @@ +name: "Feature request" +description: "Suggest a new feature for Termux application" +title: "[Feature]: " +labels: ["feature request"] +body: + - type: textarea + attributes: + label: Feature description + description: Describe the feature and why you want it. + validations: + required: true + - type: textarea + attributes: + label: Additional information + description: | + Does another app/terminal emulator have this feature? + Provide links to more background information. + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 440cc3fe4f..0000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve Termux application - ---- - - - -**Problem description** - - -**Steps to reproduce** - - -**Expected behavior** - - -**Additional information** - -* Termux application version: -* Android OS version: -* Device model: diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000000..29c4cebe4c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: Want ask questions about the project? + url: https://github.com/termux/termux-app/discussions + about: Join GitHub Discussions diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index c1e99f1048..0000000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -name: Feature request -about: Suggest a new feature for Termux application - ---- - - - -**Feature description** - - -**Reference implementation** - -Does another app/terminal emulator have this feature? -Provide links to more background information. diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..af8a2fdafb --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,9 @@ +version: 2 +updates: +- package-ecosystem: github-actions + directory: / + schedule: + interval: daily + commit-message: + # Prefix all commit messages with "Changed: " + prefix: "Changed" diff --git a/.github/workflows/attach_debug_apks_to_release.yml b/.github/workflows/attach_debug_apks_to_release.yml new file mode 100644 index 0000000000..a17b326443 --- /dev/null +++ b/.github/workflows/attach_debug_apks_to_release.yml @@ -0,0 +1,84 @@ +name: Attach Debug APKs To Release + +on: + release: + types: + - published + +jobs: + attach-apks: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + package_variant: [ apt-android-7, apt-android-5 ] + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + steps: + - name: Clone repository + uses: actions/checkout@v4 + with: + ref: ${{ env.GITHUB_REF }} + + - name: Build and attach APKs to release + shell: bash {0} + env: + PACKAGE_VARIANT: ${{ matrix.package_variant }} + run: | + exit_on_error() { + echo "$1" + echo "Deleting '$RELEASE_VERSION_NAME' release and '$GITHUB_REF' tag" + hub release delete "$RELEASE_VERSION_NAME" + git push --delete origin "$GITHUB_REF" + exit 1 + } + + echo "Setting vars" + RELEASE_VERSION_NAME="${GITHUB_REF/refs\/tags\//}" + if ! printf "%s" "${RELEASE_VERSION_NAME/v/}" | grep -qP '^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$'; then + 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+${{ env.PACKAGE_VARIANT }}-github-debug" + APK_BASENAME_PREFIX="termux-app_$APK_VERSION_TAG" + + echo "Building APKs for 'APK_VERSION_TAG' release" + export TERMUX_APK_VERSION_TAG="$APK_VERSION_TAG" # Used by app/build.gradle + export TERMUX_PACKAGE_VARIANT="${{ env.PACKAGE_VARIANT }}" # Used by app/build.gradle + if ! ./gradlew assembleDebug; then + exit_on_error "Build failed for '$APK_VERSION_TAG' release." + fi + + echo "Validating APKs" + for abi in universal arm64-v8a armeabi-v7a x86_64 x86; do + if ! test -f "$APK_DIR_PATH/${APK_BASENAME_PREFIX}_$abi.apk"; then + files_found="$(ls "$APK_DIR_PATH")" + 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 '$APK_VERSION_TAG' release." + fi + + echo "Attaching APKs to github release" + if ! hub release edit \ + -m "" \ + -a "$APK_DIR_PATH/${APK_BASENAME_PREFIX}_universal.apk" \ + -a "$APK_DIR_PATH/${APK_BASENAME_PREFIX}_arm64-v8a.apk" \ + -a "$APK_DIR_PATH/${APK_BASENAME_PREFIX}_armeabi-v7a.apk" \ + -a "$APK_DIR_PATH/${APK_BASENAME_PREFIX}_x86_64.apk" \ + -a "$APK_DIR_PATH/${APK_BASENAME_PREFIX}_x86.apk" \ + -a "$APK_DIR_PATH/${APK_BASENAME_PREFIX}_sha256sums" \ + "$RELEASE_VERSION_NAME"; then + exit_on_error "Attach APKs to release failed for '$APK_VERSION_TAG' release." + fi diff --git a/.github/workflows/debug_build.yml b/.github/workflows/debug_build.yml index 0439523ebf..a1df15ae15 100644 --- a/.github/workflows/debug_build.yml +++ b/.github/workflows/debug_build.yml @@ -4,23 +4,124 @@ on: push: branches: - master - - android-10 + - 'github-releases/**' pull_request: branches: - master - - android-10 jobs: build: runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + package_variant: [ apt-android-7, apt-android-5 ] + steps: - - name: Clone repository - uses: actions/checkout@v2 - - name: Build - run: | - ./gradlew assembleDebug - - name: Store generated APK file - uses: actions/upload-artifact@v2 - with: - name: termux-app - path: ./app/build/outputs/apk/debug + - name: Clone repository + uses: actions/checkout@v4 + + - name: Build APKs + shell: bash {0} + env: + PACKAGE_VARIANT: ${{ matrix.package_variant }} + run: | + exit_on_error() { echo "$1"; exit 1; } + + echo "Setting vars" + + if [ "$GITHUB_EVENT_NAME" == "pull_request" ]; then + GITHUB_SHA="${{ github.event.pull_request.head.sha }}" # Do not use last merge commit set in GITHUB_SHA + fi + + # Set RELEASE_VERSION_NAME to "+" + 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-${{ env.PACKAGE_VARIANT }}-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 'APK_VERSION_TAG' 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 + export TERMUX_PACKAGE_VARIANT="${{ env.PACKAGE_VARIANT }}" # Used by app/build.gradle + if ! ./gradlew assembleDebug; then + exit_on_error "Build failed for '$APK_VERSION_TAG' 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" \ + > "${APK_BASENAME_PREFIX}_sha256sums"); then + exit_on_error "Generate sha25sums failed for '$APK_VERSION_TAG' 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: ${{ env.APK_BASENAME_PREFIX }}_sha256sums + path: | + ${{ env.APK_DIR_PATH }}/${{ env.APK_BASENAME_PREFIX }}_sha256sums + ${{ env.APK_DIR_PATH }}/output-metadata.json diff --git a/.github/workflows/dependency-submission.yml b/.github/workflows/dependency-submission.yml new file mode 100644 index 0000000000..9d3978b13b --- /dev/null +++ b/.github/workflows/dependency-submission.yml @@ -0,0 +1,23 @@ +name: Automatic Dependency Submission + +on: + push: + branches: [ 'master' ] + workflow_dispatch: + +permissions: + contents: write + +jobs: + dependency-submission: + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v4 + - name: Setup Java + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: 17 + - name: Generate and submit dependency graph + uses: gradle/actions/dependency-submission@v4 diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml index 7c8edafc63..b9306a157b 100644 --- a/.github/workflows/gradle-wrapper-validation.yml +++ b/.github/workflows/gradle-wrapper-validation.yml @@ -15,5 +15,5 @@ jobs: name: "Validation" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: gradle/wrapper-validation-action@v1 + - uses: actions/checkout@v4 + - uses: gradle/wrapper-validation-action@v3 diff --git a/.github/workflows/publish_libraries.yml b/.github/workflows/publish_libraries.yml deleted file mode 100644 index 41d76af4e0..0000000000 --- a/.github/workflows/publish_libraries.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: Publish library packages - -on: - push: - branches: - - master - paths: - - 'terminal-emulator/build.gradle' - - 'terminal-view/build.gradle' - -jobs: - build: - runs-on: ubuntu-latest - steps: - - name: Clone repository - uses: actions/checkout@v2 - - name: Perform release build - run: | - ./gradlew assembleRelease - - name: Publish libraries on Github Packages - env: - GH_USERNAME: xeffyr - GH_TOKEN: ${{ secrets.GH_TOKEN }} - run: | - ./gradlew publish diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index 2647499554..f8d33fd4fc 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -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 diff --git a/.github/workflows/trigger_library_builds_on_jitpack.yml b/.github/workflows/trigger_library_builds_on_jitpack.yml new file mode 100644 index 0000000000..fd00f13bfe --- /dev/null +++ b/.github/workflows/trigger_library_builds_on_jitpack.yml @@ -0,0 +1,21 @@ +name: Trigger Termux Library Builds on Jitpack + +on: + release: + types: + - published + +jobs: + trigger-termux-library-builds: + runs-on: ubuntu-latest + steps: + - 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 + 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/$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" diff --git a/.gitignore b/.gitignore index 5f7ee3c3dc..a52cbb3394 100644 --- a/.gitignore +++ b/.gitignore @@ -4,15 +4,13 @@ # Built application files build/ +release/ *.apk *.so .externalNativeBuild .cxx *.zip -# Crashlytics configuations -com_crashlytics_export_strings.xml - # Local configuration file (sdk path, etc) local.properties @@ -26,6 +24,10 @@ local.properties .idea/ *.iml +# Vim +*.swo +*.swp + # OS-specific files .DS_Store .DS_Store? diff --git a/LICENSE.md b/LICENSE.md index 7e951d4453..4b66170283 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,3 +1,6 @@ -Released under [the GPLv3 license](https://www.gnu.org/licenses/gpl.html). +The `termux/termux-app` repository is released under [GPLv3 only](https://www.gnu.org/licenses/gpl-3.0.html) license. -Contains code from `Terminal Emulator for Android` by which is released under [the Apache License 2.0](https://www.apache.org/licenses/). +### Exceptions + +- [Terminal Emulator for Android](https://github.com/jackpal/Android-Terminal-Emulator) code is used which is released under [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0) license. Check [`terminal-view`](terminal-view) and [`terminal-emulator`](terminal-emulator) libraries. +- Check [`termux-shared/LICENSE.md`](termux-shared/LICENSE.md) for `termux-shared` library related exceptions. diff --git a/README.md b/README.md index 0eecba0068..8233bf4ef6 100644 --- a/README.md +++ b/README.md @@ -3,68 +3,293 @@ [![Build status](https://github.com/termux/termux-app/workflows/Build/badge.svg)](https://github.com/termux/termux-app/actions) [![Testing status](https://github.com/termux/termux-app/workflows/Unit%20tests/badge.svg)](https://github.com/termux/termux-app/actions) [![Join the chat at https://gitter.im/termux/termux](https://badges.gitter.im/termux/termux.svg)](https://gitter.im/termux/termux) +[![Join the Termux discord server](https://img.shields.io/discord/641256914684084234.svg?label=&logo=discord&logoColor=ffffff&color=5865F2)](https://discord.gg/HXpF69X) +[![Termux library releases at Jitpack](https://jitpack.io/v/termux/termux-app.svg)](https://jitpack.io/#termux/termux-app) + [Termux](https://termux.com) is an Android terminal application and Linux environment. -- [Termux Reddit community](https://reddit.com/r/termux) -- [Termux Wiki](https://wiki.termux.com/wiki/) -- [Termux Twitter](http://twitter.com/termux/) +Note that this repository is for the app itself (the user interface and the terminal emulation). For the packages installable inside the app, see [termux/termux-packages](https://github.com/termux/termux-packages). -Note that this repository is for the app itself (the user interface and the -terminal emulation). For the packages installable inside the app, see -[termux/termux-packages](https://github.com/termux/termux-packages) +Quick how-to about Termux package management is available at [Package Management](https://github.com/termux/termux-packages/wiki/Package-Management). It also has info on how to fix **`repository is under maintenance or down`** errors when running `apt` or `pkg` commands. -*** +**We are looking for Termux Android application maintainers.** -**@termux is looking for Termux Application maintainer for implementing new features, -fixing bugs and reviewing pull requests since current one (@fornwall) is inactive.** +*** -Issue https://github.com/termux/termux-app/issues/1072 needs extra attention. +**NOTICE: Termux may be unstable on Android 12+.** Android OS will kill any (phantom) processes greater than 32 (limit is for all apps combined) and also kill any processes using excessive CPU. You may get `[Process completed (signal 9) - press Enter]` message in the terminal without actually exiting the shell process yourself. Check the related issue [#2366](https://github.com/termux/termux-app/issues/2366), [issue tracker](https://issuetracker.google.com/u/1/issues/205156966), [phantom cached and empty processes docs](https://github.com/agnostic-apollo/Android-Docs/blob/master/en/docs/apps/processes/phantom-cached-and-empty-processes.md) and [this TLDR comment](https://github.com/termux/termux-app/issues/2366#issuecomment-1237468220) on how to disable trimming of phantom and excessive cpu usage processes. A proper docs page will be added later. An option to disable the killing should be available in Android 12L or 13, so upgrade at your own risk if you are on Android 11, specially if you are not rooted. *** +## Contents +- [Termux App and Plugins](#termux-app-and-plugins) +- [Installation](#installation) +- [Uninstallation](#uninstallation) +- [Important Links](#important-links) +- [Debugging](#debugging) +- [For Maintainers and Contributors](#for-maintainers-and-contributors) +- [Forking](#forking) +- [Sponsors and Funders](#sponsors-and-funders) +## + + + +## Termux App and Plugins + +The core [Termux](https://github.com/termux/termux-app) app comes with the following optional plugin apps. + +- [Termux:API](https://github.com/termux/termux-api) +- [Termux:Boot](https://github.com/termux/termux-boot) +- [Termux:Float](https://github.com/termux/termux-float) +- [Termux:Styling](https://github.com/termux/termux-styling) +- [Termux:Tasker](https://github.com/termux/termux-tasker) +- [Termux:Widget](https://github.com/termux/termux-widget) +## + + + ## Installation -Termux application can be obtained from [F-Droid](https://f-droid.org/en/packages/com.termux/). +Latest version is `v0.118.3`. + +**NOTICE: It is highly recommended that you update to `v0.118.0` or higher ASAP for various bug fixes, including a critical world-readable vulnerability reported [here](https://termux.github.io/general/2022/02/15/termux-apps-vulnerability-disclosures.html). See [below](#google-play-store-experimental-branch) for information regarding Termux on Google Play.** + +Termux can be obtained through various sources listed below for **only** Android `>= 7` with full support for apps and packages. + +Support for both app and packages was dropped for Android `5` and `6` on [2020-01-01](https://www.reddit.com/r/termux/comments/dnzdbs/end_of_android56_support_on_20200101/) at `v0.83`, however it was re-added just for the app *without any support for package updates* on [2022-05-24](https://github.com/termux/termux-app/pull/2740) via the [GitHub](#github) sources. Check [here](https://github.com/termux/termux-app/wiki/Termux-on-android-5-or-6) for the details. + +The APK files of different sources are signed with different signature keys. The `Termux` app and all its plugins use the same [`sharedUserId`](https://developer.android.com/guide/topics/manifest/manifest-element) `com.termux` and so all their APKs installed on a device must have been signed with the same signature key to work together and so they must all be installed from the same source. Do not attempt to mix them together, i.e do not try to install an app or plugin from `F-Droid` and another one from a different source like `GitHub`. Android Package Manager will also normally not allow installation of APKs with different signatures and you will get errors on installation like `App not installed`, `Failed to install due to an unknown error`, `INSTALL_FAILED_UPDATE_INCOMPATIBLE`, `INSTALL_FAILED_SHARED_USER_INCOMPATIBLE`, `signatures do not match previously installed version`, etc. This restriction can be bypassed with root or with custom roms. + +If you wish to install from a different source, then you must **uninstall any and all existing Termux or its plugin app APKs** from your device first, then install all new APKs from the same new source. Check [Uninstallation](#uninstallation) section for details. You may also want to consider [Backing up Termux](https://wiki.termux.com/wiki/Backing_up_Termux) before the uninstallation so that you can restore it after re-installing from Termux different source. + +In the following paragraphs, *"bootstrap"* refers to the minimal packages that are shipped with the `termux-app` itself to start a working shell environment. Its zips are built and released [here](https://github.com/termux/termux-packages/releases). + +### F-Droid + +Termux application can be obtained from `F-Droid` from [here](https://f-droid.org/en/packages/com.termux/). + +You **do not** need to download the `F-Droid` app (via the `Download F-Droid` link) to install Termux. You can download the Termux APK directly from the site by clicking the `Download APK` link at the bottom of each version section. + +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 version `>= 0.118.0` or from [`GitHub Build Action`](https://github.com/termux/termux-app/actions/workflows/debug_build.yml?query=branch%3Amaster+event%3Apush) workflows. **For android `>= 7`, only install `apt-android-7` variants. For android `5` and `6`, only install `apt-android-5` variants.** + +The APKs for `GitHub Releases` will be listed under `Assets` drop-down of a release. These are automatically attached when a new version is released. + +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. + +Both universal and architecture specific APKs are released. The APK and bootstrap installation size will be `~180MB` if using universal and `~120MB` if using architecture specific. Check [here](https://github.com/termux/termux-app/issues/2153) for details. + +**Security warning**: APK files on GitHub are signed with a test key that has been [shared with community](https://github.com/termux/termux-app/blob/master/app/testkey_untrusted.jks). This IS NOT an official developer key and everyone can use it to generate releases for own testing. Be very careful when using Termux GitHub builds obtained elsewhere except https://github.com/termux/termux-app. Everyone is able to use it to forge a malicious Termux update installable over the GitHub build. Think twice about installing Termux builds distributed via Telegram or other social media. If your device get caught by malware, we will not be able to help you. + +The [test key](https://github.com/termux/termux-app/blob/master/app/testkey_untrusted.jks) shall not be used to impersonate @termux and can't be used for this anyway. This key is not trusted by us and it is quite easy to detect its use in user generated content. + +
+Keystore information + +``` +Alias name: alias +Creation date: Oct 4, 2019 +Entry type: PrivateKeyEntry +Certificate chain length: 1 +Certificate[1]: +Owner: CN=APK Signer, OU=Earth, O=Earth +Issuer: CN=APK Signer, OU=Earth, O=Earth +Serial number: 29be297b +Valid from: Wed Sep 04 02:03:24 EEST 2019 until: Tue Oct 26 02:03:24 EEST 2049 +Certificate fingerprints: + SHA1: 51:79:55:EA:BF:69:FC:05:7C:41:C7:D3:79:DB:BC:EF:20:AD:85:F2 + SHA256: B6:DA:01:48:0E:EF:D5:FB:F2:CD:37:71:B8:D1:02:1E:C7:91:30:4B:DD:6C:4B:F4:1D:3F:AA:BA:D4:8E:E5:E1 +Signature algorithm name: SHA1withRSA (disabled) +Subject Public Key Algorithm: 2048-bit RSA key +Version: 3 +``` + +
+ +### Google Play Store **(Experimental branch)** + +There is currently a build of Termux available on Google Play for Android 11+ devices, with extensive adjustments in order to pass policy requirements there. This is under development and has missing functionality and bugs (see [here](https://github.com/termux-play-store/) for status updates) compared to the stable F-Droid build, which is why most users who can should still use F-Droid or GitHub build as mentioned above. + +Currently, Google Play will try to update installations away from F-Droid ones. Updating will still fail as [sharedUserId](https://developer.android.com/guide/topics/manifest/manifest-element#uid) has been removed. A planned 0.118.1 F-Droid release will fix this by setting a higher version code than used for the PlayStore app. Meanwhile, to prevent Google Play from attempting to download and then fail to install the Google Play releases over existing installations, you can open the Termux apps pages on Google Play and then click on the 3 dots options button in the top right and then disable the Enable auto update toggle. However, the Termux apps updates will still show in the PlayStore app updates list. + +If you want to help out with testing the Google Play build (or cannot install Termux from other sources), be aware that it's built from a separate repository (https://github.com/termux-play-store/) - be sure to report issues [there](https://github.com/termux-play-store/termux-issues/issues/new/choose), as any issues encountered might very well be specific to that repository. + +## Uninstallation + +Uninstallation may be required if a user doesn't want Termux installed in their device anymore or is switching to a different [install source](#installation). You may also want to consider [Backing up Termux](https://wiki.termux.com/wiki/Backing_up_Termux) before the uninstallation. + +To uninstall Termux completely, you must uninstall **any and all existing Termux or its plugin app APKs** listed in [Termux App and Plugins](#termux-app-and-plugins). + +Go to `Android Settings` -> `Applications` and then look for those apps. You can also use the search feature if it’s available on your device and search `termux` in the applications list. + +Even if you think you have not installed any of the plugins, it's strongly suggested to go through the application list in Android settings and double-check. +## + + + +## Important Links + +### Community +All community links are available [here](https://wiki.termux.com/wiki/Community). -Additionally we provide per-commit debug builds for those who want to try -out the latest features or test their pull request. This build can be obtained -from one of the workflow runs listed on [Github Actions](https://github.com/termux/termux-app/actions) -page. +The main ones are the following. -Signature keys of all offered builds are different. Before you switch the -installation source, you will have to uninstall the Termux application and -all currently installed plugins. +- [Termux Reddit community](https://reddit.com/r/termux) +- [Termux User Matrix Channel](https://matrix.to/#/#termux_termux:gitter.im) ([Gitter](https://gitter.im/termux/termux)) +- [Termux Dev Matrix Channel](https://matrix.to/#/#termux_dev:gitter.im) ([Gitter](https://gitter.im/termux/dev)) +- [Termux X (Twitter)](https://twitter.com/termuxdevs) +- [Termux Support Email](mailto:support@termux.dev) + +### Wikis + +- [Termux Wiki](https://wiki.termux.com/wiki/) +- [Termux App Wiki](https://github.com/termux/termux-app/wiki) +- [Termux Packages Wiki](https://github.com/termux/termux-packages/wiki) + +### Miscellaneous +- [FAQ](https://wiki.termux.com/wiki/FAQ) +- [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) +- [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) +- [Android Storage and Sharing Data with Other Apps](https://wiki.termux.com/wiki/Internal_and_external_storage) +- [Android APIs](https://wiki.termux.com/wiki/Termux:API) +- [Moved Termux Packages Hosting From Bintray to IPFS](https://github.com/termux/termux-packages/issues/6348) +- [Running Commands in Termux From Other Apps via `RUN_COMMAND` intent](https://github.com/termux/termux-app/wiki/RUN_COMMAND-Intent) +- [Termux and Android 10](https://github.com/termux/termux-packages/wiki/Termux-and-Android-10) + + +### Terminal -## Terminal resources +
+ -- [XTerm control sequences](http://invisible-island.net/xterm/ctlseqs/ctlseqs.html) -- [vt100.net](http://vt100.net/) -- [Terminal codes (ANSI and terminfo equivalents)](http://wiki.bash-hackers.org/scripting/terminalcodes) +### Terminal resources -## Terminal emulators +- [XTerm control sequences](https://invisible-island.net/xterm/ctlseqs/ctlseqs.html) +- [vt100.net](https://vt100.net/) +- [Terminal codes (ANSI and terminfo equivalents)](https://wiki.bash-hackers.org/scripting/terminalcodes) -- VTE (libvte): Terminal emulator widget for GTK+, mainly used in gnome-terminal. - [Source](https://github.com/GNOME/vte), [Open Issues](https://bugzilla.gnome.org/buglist.cgi?quicksearch=product%3A%22vte%22+), - and [All (including closed) issues](https://bugzilla.gnome.org/buglist.cgi?bug_status=RESOLVED&bug_status=VERIFIED&chfield=resolution&chfieldfrom=-2000d&chfieldvalue=FIXED&product=vte&resolution=FIXED). +### Terminal emulators -- iTerm 2: OS X terminal application. [Source](https://github.com/gnachman/iTerm2), - [Issues](https://gitlab.com/gnachman/iterm2/issues) and [Documentation](http://www.iterm2.com/documentation.html) - (which includes [iTerm2 proprietary escape codes](http://www.iterm2.com/documentation-escape-codes.html)). +- VTE (libvte): Terminal emulator widget for GTK+, mainly used in gnome-terminal. [Source](https://github.com/GNOME/vte), [Open Issues](https://bugzilla.gnome.org/buglist.cgi?quicksearch=product%3A%22vte%22+), and [All (including closed) issues](https://bugzilla.gnome.org/buglist.cgi?bug_status=RESOLVED&bug_status=VERIFIED&chfield=resolution&chfieldfrom=-2000d&chfieldvalue=FIXED&product=vte&resolution=FIXED). -- Konsole: KDE terminal application. [Source](https://projects.kde.org/projects/kde/applications/konsole/repository), - in particular [tests](https://projects.kde.org/projects/kde/applications/konsole/repository/revisions/master/show/tests), - [Bugs](https://bugs.kde.org/buglist.cgi?bug_severity=critical&bug_severity=grave&bug_severity=major&bug_severity=crash&bug_severity=normal&bug_severity=minor&bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&product=konsole) - and [Wishes](https://bugs.kde.org/buglist.cgi?bug_severity=wishlist&bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&product=konsole). +- iTerm 2: OS X terminal application. [Source](https://github.com/gnachman/iTerm2), [Issues](https://gitlab.com/gnachman/iterm2/issues) and [Documentation](https://iterm2.com/documentation.html) (which includes [iTerm2 proprietary escape codes](https://iterm2.com/documentation-escape-codes.html)). -- hterm: JavaScript terminal implementation from Chromium. [Source](https://github.com/chromium/hterm), - including [tests](https://github.com/chromium/hterm/blob/master/js/hterm_vt_tests.js), - and [Google group](https://groups.google.com/a/chromium.org/forum/#!forum/chromium-hterm). +- Konsole: KDE terminal application. [Source](https://projects.kde.org/projects/kde/applications/konsole/repository), in particular [tests](https://projects.kde.org/projects/kde/applications/konsole/repository/revisions/master/show/tests), [Bugs](https://bugs.kde.org/buglist.cgi?bug_severity=critical&bug_severity=grave&bug_severity=major&bug_severity=crash&bug_severity=normal&bug_severity=minor&bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&product=konsole) and [Wishes](https://bugs.kde.org/buglist.cgi?bug_severity=wishlist&bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&product=konsole). -- xterm: The grandfather of terminal emulators. - [Source](http://invisible-island.net/datafiles/release/xterm.tar.gz). +- hterm: JavaScript terminal implementation from Chromium. [Source](https://github.com/chromium/hterm), including [tests](https://github.com/chromium/hterm/blob/master/js/hterm_vt_tests.js), and [Google group](https://groups.google.com/a/chromium.org/forum/#!forum/chromium-hterm). + +- xterm: The grandfather of terminal emulators. [Source](https://invisible-island.net/datafiles/release/xterm.tar.gz). - Connectbot: Android SSH client. [Source](https://github.com/connectbot/connectbot) -- Android Terminal Emulator: Android terminal app which Termux terminal handling - is based on. Inactive. [Source](https://github.com/jackpal/Android-Terminal-Emulator). +- Android Terminal Emulator: Android terminal app which Termux terminal handling is based on. Inactive. [Source](https://github.com/jackpal/Android-Terminal-Emulator). +
+ +## + + + +### Debugging + +You can help debug problems of the `Termux` app and its plugins by setting appropriate `logcat` `Log Level` in `Termux` app settings -> `` -> `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. 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. + +The `versionName` in `build.gradle` files of Termux and its plugin apps must follow the [semantic version `2.0.0` spec](https://semver.org/spec/v2.0.0.html) in the format `major.minor.patch(-prerelease)(+buildmetadata)`. When bumping `versionName` in `build.gradle` files and when creating a tag for new releases on GitHub, make sure to include the patch number as well, like `v0.1.0` instead of just `v0.1`. The `build.gradle` files and `attach_debug_apks_to_release` workflow validates the version as well and the build/attachment will fail if `versionName` does not follow the spec. + +### Commit Messages Guidelines + +Commit messages **must** use the [Conventional Commits](https://www.conventionalcommits.org) spec so that chagelogs as per the [Keep a Changelog](https://github.com/olivierlacan/keep-a-changelog) spec can automatically be generated by the [`create-conventional-changelog`](https://github.com/termux/create-conventional-changelog) script, check its repo for further details on the spec. **The first letter for `type` and `description` must be capital and description should be in the present tense.** The space after the colon `:` is necessary. For a breaking change, add an exclamation mark `!` before the colon `:`, so that it is highlighted in the chagelog automatically. + +``` +[optional scope]: + +[optional body] + +[optional footer(s)] +``` + +**Only the `types` listed below must be used exactly as they are used in the changelog headings.** For example, `Added: Add foo`, `Added|Fixed: Add foo and fix bar`, `Changed!: Change baz as a breaking change`, etc. You can optionally add a scope as well, like `Fixed(terminal): Fix some bug`. **Do not use anything else as type, like `add` instead of `Added`, etc.** + +- **Added** for new features. +- **Changed** for changes in existing functionality. +- **Deprecated** for soon-to-be removed features. +- **Removed** for now removed features. +- **Fixed** for any bug fixes. +- **Security** in case of vulnerabilities. +## + + + +## 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. +- You also need to recompile bootstrap zip for the new package name. Check [building bootstrap](https://github.com/termux/termux-packages/wiki/For-maintainers#build-bootstrap-archives), [here](https://github.com/termux/termux-app/issues/1983) and [here](https://github.com/termux/termux-app/issues/2081#issuecomment-865280111). +- Currently, not all plugins use `TermuxConstants` from `termux-shared` library and have hardcoded `com.termux` values and will need to be manually patched. +- If forking termux plugins, check [Forking and Local Development](https://github.com/termux/termux-app/wiki/Termux-Libraries#forking-and-local-development) for info on how to use termux libraries for plugins. +## + + + +## Sponsors and Funders + +[GitHub Accelerator](https://github.com) +*[GitHub Accelerator](https://github.com/accelerator) ([1](https://github.blog/2023-04-12-github-accelerator-our-first-cohort-and-whats-next))* + +  + +[GitHub Secure Open Source Fund](https://github.com) +*[GitHub Secure Open Source Fund](https://resources.github.com/github-secure-open-source-fund) ([1](https://github.blog/open-source/maintainers/securing-the-supply-chain-at-scale-starting-with-71-important-open-source-projects), [2](https://termux.dev/en/posts/general/2025/08/11/termux-selected-for-github-secure-open-source-fund-session-2.html))* + +  + +[NLnet NGI Mobifree](https://nlnet.nl/mobifree) +*[NLnet NGI Mobifree](https://nlnet.nl/mobifree) ([1](https://nlnet.nl/news/2024/20241111-NGI-Mobifree-grants.html), [2](https://termux.dev/en/posts/general/2024/11/11/termux-selected-for-nlnet-ngi-mobifree-grant.html))* + +  + +[Cloudflare](https://www.cloudflare.com) +*[Cloudflare](https://www.cloudflare.com) ([1](https://packages-cf.termux.dev))* + +  + +[Warp](https://www.warp.dev/?utm_source=github&utm_medium=readme&utm_campaign=termux) +[*Warp, built for coding with multiple AI agents*](https://www.warp.dev/?utm_source=github&utm_medium=readme&utm_campaign=termux) diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000000..479f15cb81 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1 @@ +Check https://termux.dev/security for info on Termux security policies and how to report vulnerabilities. diff --git a/app/build.gradle b/app/build.gradle index 0cbce7936d..3df93fef10 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,24 +1,62 @@ plugins { - id "com.android.application" + id "com.android.application" +} + +ext { + // The packageVariant defines the bootstrap variant that will be included in the app APK. + // This must be supported by com.termux.shared.termux.TermuxBootstrap.PackageVariant or app will + // crash at startup. + // Bootstrap of a different variant must not be manually installed by the user after app installation + // by replacing $PREFIX since app code is dependant on the variant used to build the APK. + // Currently supported values are: [ "apt-android-7" "apt-android-5" ] + packageVariant = System.getenv("TERMUX_PACKAGE_VARIANT") ?: "apt-android-7" // Default: "apt-android-7" } android { compileSdkVersion project.properties.compileSdkVersion.toInteger() - ndkVersion project.properties.ndkVersion + 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.1.0" - implementation "androidx.viewpager:viewpager:1.0.0" + 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" + implementation "com.google.android.material:material:1.4.0" + implementation "com.google.guava:guava:24.1-jre" + implementation "io.noties.markwon:core:$markwonVersion" + implementation "io.noties.markwon:ext-strikethrough:$markwonVersion" + implementation "io.noties.markwon:linkify:$markwonVersion" + implementation "io.noties.markwon:recycler:$markwonVersion" + implementation project(":terminal-view") + implementation project(":termux-shared") } defaultConfig { applicationId "com.termux" minSdkVersion project.properties.minSdkVersion.toInteger() targetSdkVersion project.properties.targetSdkVersion.toInteger() - versionCode 108 - versionName "0.108" + versionCode 118 + versionName "0.118.0" + + if (appVersionName) versionName = appVersionName + validateVersionName(versionName) + + buildConfigField "String", "TERMUX_PACKAGE_VARIANT", "\"" + project.ext.packageVariant + "\"" // Used by TermuxApplication class + + manifestPlaceholders.TERMUX_PACKAGE_NAME = "com.termux" + manifestPlaceholders.TERMUX_APP_NAME = "Termux" + manifestPlaceholders.TERMUX_API_APP_NAME = "Termux:API" + manifestPlaceholders.TERMUX_BOOT_APP_NAME = "Termux:Boot" + manifestPlaceholders.TERMUX_FLOAT_APP_NAME = "Termux:Float" + manifestPlaceholders.TERMUX_STYLING_APP_NAME = "Termux:Styling" + manifestPlaceholders.TERMUX_TASKER_APP_NAME = "Termux:Tasker" + manifestPlaceholders.TERMUX_WIDGET_APP_NAME = "Termux:Widget" externalNativeBuild { ndkBuild { @@ -26,15 +64,20 @@ android { } } - ndk { - abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a' + splits { + abi { + 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 + } } - } signingConfigs { debug { - storeFile file('dev_keystore.jks') + storeFile file('testkey_untrusted.jks') keyAlias 'alias' storePassword 'xrj45yWGLbsO7W0v' keyPassword 'xrj45yWGLbsO7W0v' @@ -44,7 +87,7 @@ android { buildTypes { release { minifyEnabled true - shrinkResources true + shrinkResources false // Reproducible builds proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } @@ -54,6 +97,9 @@ android { } compileOptions { + // Flag to enable support for the new language APIs + coreLibraryDesugaringEnabled true + sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } @@ -73,17 +119,44 @@ android { includeAndroidResources = true } } + + packagingOptions { + jniLibs { + useLegacyPackaging true + } + } + + applicationVariants.all { variant -> + variant.outputs.all { output -> + if (variant.buildType.name == "debug") { + def abi = output.getFilter(com.android.build.OutputFile.ABI) + outputFileName = new File("termux-app_" + (apkVersionTag ? apkVersionTag : project.ext.packageVariant + "-" + "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 : project.ext.packageVariant + "-" + "release") + "_" + (abi ? abi : "universal") + ".apk") + } + } + } + } dependencies { - testImplementation 'junit:junit:4.13.1' - testImplementation 'org.robolectric:robolectric:4.4' + testImplementation "junit:junit:4.13.2" + testImplementation "org.robolectric:robolectric:4.10" + coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:1.1.5" } task versionName { - doLast { - print android.defaultConfig.versionName - } + doLast { + print android.defaultConfig.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) { @@ -100,10 +173,11 @@ 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 { - logger.quiet("Deleting old local file with wrong hash: " + localUrl) + logger.quiet("Deleting old local file with wrong hash: " + localUrl + ": expected: " + expectedChecksum + ", actual: " + checksum) file.delete() } } @@ -121,6 +195,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) @@ -135,18 +210,29 @@ clean { } } -task downloadBootstraps(){ +task downloadBootstraps() { doLast { - def version = "2021.02.19-r1" - downloadBootstrap("aarch64", "1e3d80bd8cc8771715845ab4a1e67fc125d84c4deda3a1a435116fe4d1f86160", version) - downloadBootstrap("arm", "317a987cab3d4da6f9d42f024a5f25e3aaf8557b8ec68eaf80411fde6b8ea78d", version) - downloadBootstrap("i686", "dd4632350474058fe5dec14b7bd882798bb3e9fe738b2de8c84a51a0b6395f4b", version) - downloadBootstrap("x86_64", "5458119186c3ea631420833e59730461dc2a5faf17b8fad239823c88aa4569ae", version) + def packageVariant = project.ext.packageVariant + if (packageVariant == "apt-android-7") { + def version = "2022.04.28-r5" + "+" + packageVariant + downloadBootstrap("aarch64", "4a51a7eb209fe82efc24d52e3cccc13165f27377290687cb82038cbd8e948430", version) + downloadBootstrap("arm", "6459a786acbae50d4c8a36fa1c3de6a4dd2d482572f6d54f73274709bd627325", version) + downloadBootstrap("i686", "919d212b2f19e08600938db4079e794e947365022dbfd50ac342c50fcedcd7be", version) + downloadBootstrap("x86_64", "61b02fdc03ea4f5d9da8d8cf018013fdc6659e6da6cbf44e9b24d1c623580b89", version) + } else if (packageVariant == "apt-android-5") { + def version = "2022.04.28-r6" + "+" + packageVariant + downloadBootstrap("aarch64", "913609d439415c828c5640be1b0561467e539cb1c7080662decaaca2fb4820e7", version) + downloadBootstrap("arm", "26bfb45304c946170db69108e5eb6e3641aad751406ce106c80df80cad2eccf8", version) + downloadBootstrap("i686", "46dcfeb5eef67ba765498db9fe4c50dc4690805139aa0dd141a9d8ee0693cd27", version) + downloadBootstrap("x86_64", "615b590679ee6cd885b7fd2ff9473c845e920f9b422f790bb158c63fe42b8481", version) + } else { + throw new GradleException("Unsupported TERMUX_PACKAGE_VARIANT \"" + packageVariant + "\"") + } } } afterEvaluate { - android.applicationVariants.all { variant -> - variant.javaCompileProvider.get().dependsOn(downloadBootstraps) - } + android.applicationVariants.all { variant -> + variant.javaCompileProvider.get().dependsOn(downloadBootstraps) + } } diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 4306bcc419..a01c038960 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -7,5 +7,11 @@ # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html --renamesourcefileattribute SourceFile --keepattributes SourceFile,LineNumberTable +-dontobfuscate +#-renamesourcefileattribute SourceFile +#-keepattributes SourceFile,LineNumberTable + +# Temp fix for androidx.window:window:1.0.0-alpha09 imported by termux-shared +# https://issuetracker.google.com/issues/189001730 +# https://android-review.googlesource.com/c/platform/frameworks/support/+/1757630 +-keep class androidx.window.** { *; } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 035fc8dafa..7c566c8f0d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,81 +1,135 @@ + - - - - - + + + + + + + - - + + + + + + + - - - + android:requestLegacyExternalStorage="true" + android:roundIcon="@mipmap/ic_launcher_round" + android:supportsRtl="false" + android:theme="@style/Theme.TermuxApp.DayNight.DarkActionBar" + tools:targetApi="m"> + android:theme="@style/Theme.TermuxActivity.DayNight.NoActionBar" + tools:targetApi="n"> + + - + + + + + + + + + + + + + + tools:targetApi="n" /> + + + + + android:taskAffinity="${TERMUX_PACKAGE_NAME}.filereceiver" + tools:targetApi="n"> + + + + - + + + @@ -84,63 +138,100 @@ + + + + - + + + - - - - - - - - - - + + + + + + + + + + + + + + + android:permission="${TERMUX_PACKAGE_NAME}.permission.RUN_COMMAND"> - + - - - - + + + + + + + + + + + + + + diff --git a/app/src/main/java/com/termux/app/BackgroundJob.java b/app/src/main/java/com/termux/app/BackgroundJob.java deleted file mode 100644 index cfb11cd4c6..0000000000 --- a/app/src/main/java/com/termux/app/BackgroundJob.java +++ /dev/null @@ -1,243 +0,0 @@ -package com.termux.app; - -import android.app.Activity; -import android.app.PendingIntent; -import android.content.Intent; -import android.os.Bundle; -import android.util.Log; - -import com.termux.BuildConfig; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.lang.reflect.Field; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -/** - * A background job launched by Termux. - */ -public final class BackgroundJob { - - private static final String LOG_TAG = "termux-task"; - - final Process mProcess; - - public BackgroundJob(String cwd, String fileToExecute, final String[] args, final TermuxService service){ - this(cwd, fileToExecute, args, service, null); - } - - public BackgroundJob(String cwd, String fileToExecute, final String[] args, final TermuxService service, PendingIntent pendingIntent) { - String[] env = buildEnvironment(false, cwd); - if (cwd == null) cwd = TermuxService.HOME_PATH; - - final String[] progArray = setupProcessArgs(fileToExecute, args); - final String processDescription = Arrays.toString(progArray); - - Process process; - try { - process = Runtime.getRuntime().exec(progArray, env, new File(cwd)); - } catch (IOException e) { - mProcess = null; - // TODO: Visible error message? - Log.e(LOG_TAG, "Failed running background job: " + processDescription, e); - return; - } - - mProcess = process; - final int pid = getPid(mProcess); - final Bundle result = new Bundle(); - final StringBuilder outResult = new StringBuilder(); - final StringBuilder errResult = new StringBuilder(); - - Thread errThread = new Thread() { - @Override - public void run() { - InputStream stderr = mProcess.getErrorStream(); - BufferedReader reader = new BufferedReader(new InputStreamReader(stderr, StandardCharsets.UTF_8)); - String line; - try { - // FIXME: Long lines. - while ((line = reader.readLine()) != null) { - errResult.append(line).append('\n'); - Log.i(LOG_TAG, "[" + pid + "] stderr: " + line); - } - } catch (IOException e) { - // Ignore. - } - } - }; - errThread.start(); - - new Thread() { - @Override - public void run() { - Log.i(LOG_TAG, "[" + pid + "] starting: " + processDescription); - InputStream stdout = mProcess.getInputStream(); - BufferedReader reader = new BufferedReader(new InputStreamReader(stdout, StandardCharsets.UTF_8)); - - String line; - try { - // FIXME: Long lines. - while ((line = reader.readLine()) != null) { - Log.i(LOG_TAG, "[" + pid + "] stdout: " + line); - outResult.append(line).append('\n'); - } - } catch (IOException e) { - Log.e(LOG_TAG, "Error reading output", e); - } - - try { - int exitCode = mProcess.waitFor(); - service.onBackgroundJobExited(BackgroundJob.this); - if (exitCode == 0) { - Log.i(LOG_TAG, "[" + pid + "] exited normally"); - } else { - Log.w(LOG_TAG, "[" + pid + "] exited with code: " + exitCode); - } - - result.putString("stdout", outResult.toString()); - result.putInt("exitCode", exitCode); - - errThread.join(); - result.putString("stderr", errResult.toString()); - - Intent data = new Intent(); - data.putExtra("result", result); - - if(pendingIntent != null) { - try { - pendingIntent.send(service.getApplicationContext(), Activity.RESULT_OK, data); - } catch (PendingIntent.CanceledException e) { - // The caller doesn't want the result? That's fine, just ignore - } - } - } catch (InterruptedException e) { - // Ignore - } - } - }.start(); - } - - private static void addToEnvIfPresent(List environment, String name) { - String value = System.getenv(name); - if (value != null) { - environment.add(name + "=" + value); - } - } - - static String[] buildEnvironment(boolean failSafe, String cwd) { - new File(TermuxService.HOME_PATH).mkdirs(); - - if (cwd == null) cwd = TermuxService.HOME_PATH; - - List environment = new ArrayList<>(); - - environment.add("TERMUX_VERSION=" + BuildConfig.VERSION_NAME); - environment.add("TERM=xterm-256color"); - environment.add("COLORTERM=truecolor"); - environment.add("HOME=" + TermuxService.HOME_PATH); - environment.add("PREFIX=" + TermuxService.PREFIX_PATH); - environment.add("BOOTCLASSPATH=" + System.getenv("BOOTCLASSPATH")); - environment.add("ANDROID_ROOT=" + System.getenv("ANDROID_ROOT")); - environment.add("ANDROID_DATA=" + System.getenv("ANDROID_DATA")); - // EXTERNAL_STORAGE is needed for /system/bin/am to work on at least - // Samsung S7 - see https://plus.google.com/110070148244138185604/posts/gp8Lk3aCGp3. - environment.add("EXTERNAL_STORAGE=" + System.getenv("EXTERNAL_STORAGE")); - - // These variables are needed if running on Android 10 and higher. - addToEnvIfPresent(environment, "ANDROID_ART_ROOT"); - addToEnvIfPresent(environment, "DEX2OATBOOTCLASSPATH"); - addToEnvIfPresent(environment, "ANDROID_I18N_ROOT"); - addToEnvIfPresent(environment, "ANDROID_RUNTIME_ROOT"); - addToEnvIfPresent(environment, "ANDROID_TZDATA_ROOT"); - - if (failSafe) { - // Keep the default path so that system binaries can be used in the failsafe session. - environment.add("PATH= " + System.getenv("PATH")); - } else { - environment.add("LANG=en_US.UTF-8"); - environment.add("PATH=" + TermuxService.PREFIX_PATH + "/bin"); - environment.add("PWD=" + cwd); - environment.add("TMPDIR=" + TermuxService.PREFIX_PATH + "/tmp"); - } - - return environment.toArray(new String[0]); - } - - public static int getPid(Process p) { - try { - Field f = p.getClass().getDeclaredField("pid"); - f.setAccessible(true); - try { - return f.getInt(p); - } finally { - f.setAccessible(false); - } - } catch (Throwable e) { - return -1; - } - } - - static String[] setupProcessArgs(String fileToExecute, String[] args) { - // The file to execute may either be: - // - An elf file, in which we execute it directly. - // - A script file without shebang, which we execute with our standard shell $PREFIX/bin/sh instead of the - // system /system/bin/sh. The system shell may vary and may not work at all due to LD_LIBRARY_PATH. - // - A file with shebang, which we try to handle with e.g. /bin/foo -> $PREFIX/bin/foo. - String interpreter = null; - try { - File file = new File(fileToExecute); - try (FileInputStream in = new FileInputStream(file)) { - byte[] buffer = new byte[256]; - int bytesRead = in.read(buffer); - if (bytesRead > 4) { - if (buffer[0] == 0x7F && buffer[1] == 'E' && buffer[2] == 'L' && buffer[3] == 'F') { - // Elf file, do nothing. - } else if (buffer[0] == '#' && buffer[1] == '!') { - // Try to parse shebang. - StringBuilder builder = new StringBuilder(); - for (int i = 2; i < bytesRead; i++) { - char c = (char) buffer[i]; - if (c == ' ' || c == '\n') { - if (builder.length() == 0) { - // Skip whitespace after shebang. - } else { - // End of shebang. - String executable = builder.toString(); - if (executable.startsWith("/usr") || executable.startsWith("/bin")) { - String[] parts = executable.split("/"); - String binary = parts[parts.length - 1]; - interpreter = TermuxService.PREFIX_PATH + "/bin/" + binary; - } - break; - } - } else { - builder.append(c); - } - } - } else { - // No shebang and no ELF, use standard shell. - interpreter = TermuxService.PREFIX_PATH + "/bin/sh"; - } - } - } - } catch (IOException e) { - // Ignore. - } - - List result = new ArrayList<>(); - if (interpreter != null) result.add(interpreter); - result.add(fileToExecute); - if (args != null) Collections.addAll(result, args); - return result.toArray(new String[0]); - } - -} diff --git a/app/src/main/java/com/termux/app/ExtraKeysInfos.java b/app/src/main/java/com/termux/app/ExtraKeysInfos.java deleted file mode 100644 index 1274c22441..0000000000 --- a/app/src/main/java/com/termux/app/ExtraKeysInfos.java +++ /dev/null @@ -1,340 +0,0 @@ -package com.termux.app; - -import android.text.TextUtils; - -import androidx.annotation.Nullable; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; -import java.util.stream.Collectors; - -public class ExtraKeysInfos { - - /** - * Matrix of buttons displayed - */ - private ExtraKeyButton[][] buttons; - - /** - * This corresponds to one of the CharMapDisplay below - */ - private String style = "default"; - - public ExtraKeysInfos(String propertiesInfo, String style) throws JSONException { - this.style = style; - - // Convert String propertiesInfo to Array of Arrays - JSONArray arr = new JSONArray(propertiesInfo); - Object[][] matrix = new Object[arr.length()][]; - for (int i = 0; i < arr.length(); i++) { - JSONArray line = arr.getJSONArray(i); - matrix[i] = new Object[line.length()]; - for (int j = 0; j < line.length(); j++) { - matrix[i][j] = line.get(j); - } - } - - // convert matrix to buttons - this.buttons = new ExtraKeyButton[matrix.length][]; - for (int i = 0; i < matrix.length; i++) { - this.buttons[i] = new ExtraKeyButton[matrix[i].length]; - for (int j = 0; j < matrix[i].length; j++) { - Object key = matrix[i][j]; - - JSONObject jobject = normalizeKeyConfig(key); - - ExtraKeyButton button; - - if(! jobject.has("popup")) { - // no popup - button = new ExtraKeyButton(getSelectedCharMap(), jobject); - } else { - // a popup - JSONObject popupJobject = normalizeKeyConfig(jobject.get("popup")); - ExtraKeyButton popup = new ExtraKeyButton(getSelectedCharMap(), popupJobject); - button = new ExtraKeyButton(getSelectedCharMap(), jobject, popup); - } - - this.buttons[i][j] = button; - } - } - } - - /** - * "hello" -> {"key": "hello"} - */ - private static JSONObject normalizeKeyConfig(Object key) throws JSONException { - JSONObject jobject; - if(key instanceof String) { - jobject = new JSONObject(); - jobject.put("key", key); - } else if(key instanceof JSONObject) { - jobject = (JSONObject) key; - } else { - throw new JSONException("An key in the extra-key matrix must be a string or an object"); - } - return jobject; - } - - public ExtraKeyButton[][] getMatrix() { - return buttons; - } - - /** - * HashMap that implements Python dict.get(key, default) function. - * Default java.util .get(key) is then the same as .get(key, null); - */ - static class CleverMap extends HashMap { - V get(K key, V defaultValue) { - if(containsKey(key)) - return get(key); - else - return defaultValue; - } - } - - static class CharDisplayMap extends CleverMap {} - - /** - * Keys are displayed in a natural looking way, like "→" for "RIGHT" - */ - static final CharDisplayMap classicArrowsDisplay = new CharDisplayMap() {{ - // classic arrow keys (for ◀ ▶ ▲ ▼ @see arrowVariationDisplay) - put("LEFT", "←"); // U+2190 ← LEFTWARDS ARROW - put("RIGHT", "→"); // U+2192 → RIGHTWARDS ARROW - put("UP", "↑"); // U+2191 ↑ UPWARDS ARROW - put("DOWN", "↓"); // U+2193 ↓ DOWNWARDS ARROW - }}; - - static final CharDisplayMap wellKnownCharactersDisplay = new CharDisplayMap() {{ - // well known characters // https://en.wikipedia.org/wiki/{Enter_key, Tab_key, Delete_key} - put("ENTER", "↲"); // U+21B2 ↲ DOWNWARDS ARROW WITH TIP LEFTWARDS - put("TAB", "↹"); // U+21B9 ↹ LEFTWARDS ARROW TO BAR OVER RIGHTWARDS ARROW TO BAR - put("BKSP", "⌫"); // U+232B ⌫ ERASE TO THE LEFT sometimes seen and easy to understand - put("DEL", "⌦"); // U+2326 ⌦ ERASE TO THE RIGHT not well known but easy to understand - put("DRAWER", "☰"); // U+2630 ☰ TRIGRAM FOR HEAVEN not well known but easy to understand - put("KEYBOARD", "⌨"); // U+2328 ⌨ KEYBOARD not well known but easy to understand - }}; - - static final CharDisplayMap lessKnownCharactersDisplay = new CharDisplayMap() {{ - // https://en.wikipedia.org/wiki/{Home_key, End_key, Page_Up_and_Page_Down_keys} - // home key can mean "goto the beginning of line" or "goto first page" depending on context, hence the diagonal - put("HOME", "⇱"); // from IEC 9995 // U+21F1 ⇱ NORTH WEST ARROW TO CORNER - put("END", "⇲"); // from IEC 9995 // ⇲ // U+21F2 ⇲ SOUTH EAST ARROW TO CORNER - put("PGUP", "⇑"); // no ISO character exists, U+21D1 ⇑ UPWARDS DOUBLE ARROW will do the trick - put("PGDN", "⇓"); // no ISO character exists, U+21D3 ⇓ DOWNWARDS DOUBLE ARROW will do the trick - }}; - - static final CharDisplayMap arrowTriangleVariationDisplay = new CharDisplayMap() {{ - // alternative to classic arrow keys - put("LEFT", "◀"); // U+25C0 ◀ BLACK LEFT-POINTING TRIANGLE - put("RIGHT", "▶"); // U+25B6 ▶ BLACK RIGHT-POINTING TRIANGLE - put("UP", "▲"); // U+25B2 ▲ BLACK UP-POINTING TRIANGLE - put("DOWN", "▼"); // U+25BC ▼ BLACK DOWN-POINTING TRIANGLE - }}; - - static final CharDisplayMap notKnownIsoCharacters = new CharDisplayMap() {{ - // Control chars that are more clear as text // https://en.wikipedia.org/wiki/{Function_key, Alt_key, Control_key, Esc_key} - // put("FN", "FN"); // no ISO character exists - put("CTRL", "⎈"); // ISO character "U+2388 ⎈ HELM SYMBOL" is unknown to people and never printed on computers, however "U+25C7 ◇ WHITE DIAMOND" is a nice presentation, and "^" for terminal app and mac is often used - put("ALT", "⎇"); // ISO character "U+2387 ⎇ ALTERNATIVE KEY SYMBOL'" is unknown to people and only printed as the Option key "⌥" on Mac computer - put("ESC", "⎋"); // ISO character "U+238B ⎋ BROKEN CIRCLE WITH NORTHWEST ARROW" is unknown to people and not often printed on computers - }}; - - static final CharDisplayMap nicerLookingDisplay = new CharDisplayMap() {{ - // nicer looking for most cases - put("-", "―"); // U+2015 ― HORIZONTAL BAR - }}; - - /** - * Multiple maps are available to quickly change - * the style of the keys. - */ - - /** - * Some classic symbols everybody knows - */ - private static final CharDisplayMap defaultCharDisplay = new CharDisplayMap() {{ - putAll(classicArrowsDisplay); - putAll(wellKnownCharactersDisplay); - putAll(nicerLookingDisplay); - // all other characters are displayed as themselves - }}; - - /** - * Classic symbols and less known symbols - */ - private static final CharDisplayMap lotsOfArrowsCharDisplay = new CharDisplayMap() {{ - putAll(classicArrowsDisplay); - putAll(wellKnownCharactersDisplay); - putAll(lessKnownCharactersDisplay); // NEW - putAll(nicerLookingDisplay); - }}; - - /** - * Only arrows - */ - private static final CharDisplayMap arrowsOnlyCharDisplay = new CharDisplayMap() {{ - putAll(classicArrowsDisplay); - // putAll(wellKnownCharactersDisplay); // REMOVED - // putAll(lessKnownCharactersDisplay); // REMOVED - putAll(nicerLookingDisplay); - }}; - - /** - * Full Iso - */ - private static final CharDisplayMap fullIsoCharDisplay = new CharDisplayMap() {{ - putAll(classicArrowsDisplay); - putAll(wellKnownCharactersDisplay); - putAll(lessKnownCharactersDisplay); // NEW - putAll(nicerLookingDisplay); - putAll(notKnownIsoCharacters); // NEW - }}; - - /** - * Some people might call our keys differently - */ - static private final CharDisplayMap controlCharsAliases = new CharDisplayMap() {{ - put("ESCAPE", "ESC"); - put("CONTROL", "CTRL"); - put("RETURN", "ENTER"); // Technically different keys, but most applications won't see the difference - put("FUNCTION", "FN"); - // no alias for ALT - - // Directions are sometimes written as first and last letter for brevety - put("LT", "LEFT"); - put("RT", "RIGHT"); - put("DN", "DOWN"); - // put("UP", "UP"); well, "UP" is already two letters - - put("PAGEUP", "PGUP"); - put("PAGE_UP", "PGUP"); - put("PAGE UP", "PGUP"); - put("PAGE-UP", "PGUP"); - - // no alias for HOME - // no alias for END - - put("PAGEDOWN", "PGDN"); - put("PAGE_DOWN", "PGDN"); - put("PAGE-DOWN", "PGDN"); - - put("DELETE", "DEL"); - put("BACKSPACE", "BKSP"); - - // easier for writing in termux.properties - put("BACKSLASH", "\\"); - put("QUOTE", "\""); - put("APOSTROPHE", "'"); - }}; - - CharDisplayMap getSelectedCharMap() { - switch (style) { - case "arrows-only": - return arrowsOnlyCharDisplay; - case "arrows-all": - return lotsOfArrowsCharDisplay; - case "all": - return fullIsoCharDisplay; - case "none": - return new CharDisplayMap(); - default: - return defaultCharDisplay; - } - } - - /** - * Applies the 'controlCharsAliases' mapping to all the strings in *buttons* - * Modifies the array, doesn't return a new one. - */ - public static String replaceAlias(String key) { - return controlCharsAliases.get(key, key); - } -} - -class ExtraKeyButton { - - /** - * The key that will be sent to the terminal, either a control character - * defined in ExtraKeysView.keyCodesForString (LEFT, RIGHT, PGUP...) or - * some text. - */ - private String key; - - /** - * If the key is a macro, i.e. a sequence of keys separated by space. - */ - private boolean macro; - - /** - * The text that will be shown on the button. - */ - private String display; - - /** - * The information of the popup (triggered by swipe up). - */ - @Nullable - private ExtraKeyButton popup = null; - - public ExtraKeyButton(ExtraKeysInfos.CharDisplayMap charDisplayMap, JSONObject config) throws JSONException { - this(charDisplayMap, config, null); - } - - public ExtraKeyButton(ExtraKeysInfos.CharDisplayMap charDisplayMap, JSONObject config, ExtraKeyButton popup) throws JSONException { - String keyFromConfig = config.optString("key", null); - String macroFromConfig = config.optString("macro", null); - String[] keys; - if (keyFromConfig != null && macroFromConfig != null) { - throw new JSONException("Both key and macro can't be set for the same key"); - } else if (keyFromConfig != null) { - keys = new String[]{keyFromConfig}; - this.macro = false; - } else if (macroFromConfig != null) { - keys = macroFromConfig.split(" "); - this.macro = true; - } else { - throw new JSONException("All keys have to specify either key or macro"); - } - - for (int i = 0; i < keys.length; i++) { - keys[i] = ExtraKeysInfos.replaceAlias(keys[i]); - } - - this.key = TextUtils.join(" ", keys); - - String displayFromConfig = config.optString("display", null); - if (displayFromConfig != null) { - this.display = displayFromConfig; - } else { - this.display = Arrays.stream(keys) - .map(key -> charDisplayMap.get(key, key)) - .collect(Collectors.joining(" ")); - } - - this.popup = popup; - } - - public String getKey() { - return key; - } - - public boolean isMacro() { - return macro; - } - - public String getDisplay() { - return display; - } - - @Nullable - public ExtraKeyButton getPopup() { - return popup; - } -} diff --git a/app/src/main/java/com/termux/app/ExtraKeysView.java b/app/src/main/java/com/termux/app/ExtraKeysView.java deleted file mode 100644 index e475e32a14..0000000000 --- a/app/src/main/java/com/termux/app/ExtraKeysView.java +++ /dev/null @@ -1,375 +0,0 @@ -package com.termux.app; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.os.Build; -import android.provider.Settings; -import android.util.AttributeSet; - -import java.util.ArrayList; -import java.util.List; -import java.util.Set; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.ScheduledExecutorService; - -import java.util.Map; -import java.util.HashMap; -import java.util.Arrays; -import java.util.stream.Collectors; - -import android.view.Gravity; -import android.view.HapticFeedbackConstants; -import android.view.KeyEvent; -import android.view.MotionEvent; -import android.view.View; -import android.view.inputmethod.InputMethodManager; -import android.widget.Button; -import android.widget.GridLayout; -import android.widget.PopupWindow; - -import com.termux.R; -import com.termux.view.TerminalView; - -import androidx.drawerlayout.widget.DrawerLayout; - -/** - * A view showing extra keys (such as Escape, Ctrl, Alt) not normally available on an Android soft - * keyboard. - */ -public final class ExtraKeysView extends GridLayout { - - private static final int TEXT_COLOR = 0xFFFFFFFF; - private static final int BUTTON_COLOR = 0x00000000; - private static final int INTERESTING_COLOR = 0xFF80DEEA; - private static final int BUTTON_PRESSED_COLOR = 0xFF7F7F7F; - - public ExtraKeysView(Context context, AttributeSet attrs) { - super(context, attrs); - } - - static final Map keyCodesForString = new HashMap() {{ - put("SPACE", KeyEvent.KEYCODE_SPACE); - put("ESC", KeyEvent.KEYCODE_ESCAPE); - put("TAB", KeyEvent.KEYCODE_TAB); - put("HOME", KeyEvent.KEYCODE_MOVE_HOME); - put("END", KeyEvent.KEYCODE_MOVE_END); - put("PGUP", KeyEvent.KEYCODE_PAGE_UP); - put("PGDN", KeyEvent.KEYCODE_PAGE_DOWN); - put("INS", KeyEvent.KEYCODE_INSERT); - put("DEL", KeyEvent.KEYCODE_FORWARD_DEL); - put("BKSP", KeyEvent.KEYCODE_DEL); - put("UP", KeyEvent.KEYCODE_DPAD_UP); - put("LEFT", KeyEvent.KEYCODE_DPAD_LEFT); - put("RIGHT", KeyEvent.KEYCODE_DPAD_RIGHT); - put("DOWN", KeyEvent.KEYCODE_DPAD_DOWN); - put("ENTER", KeyEvent.KEYCODE_ENTER); - put("F1", KeyEvent.KEYCODE_F1); - put("F2", KeyEvent.KEYCODE_F2); - put("F3", KeyEvent.KEYCODE_F3); - put("F4", KeyEvent.KEYCODE_F4); - put("F5", KeyEvent.KEYCODE_F5); - put("F6", KeyEvent.KEYCODE_F6); - put("F7", KeyEvent.KEYCODE_F7); - put("F8", KeyEvent.KEYCODE_F8); - put("F9", KeyEvent.KEYCODE_F9); - put("F10", KeyEvent.KEYCODE_F10); - put("F11", KeyEvent.KEYCODE_F11); - put("F12", KeyEvent.KEYCODE_F12); - }}; - - private void sendKey(View view, String keyName, boolean forceCtrlDown, boolean forceLeftAltDown) { - TerminalView terminalView = view.findViewById(R.id.terminal_view); - if ("KEYBOARD".equals(keyName)) { - InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); - imm.toggleSoftInput(0, 0); - } else if ("DRAWER".equals(keyName)) { - DrawerLayout drawer = view.findViewById(R.id.drawer_layout); - drawer.openDrawer(Gravity.LEFT); - } else if (keyCodesForString.containsKey(keyName)) { - int keyCode = keyCodesForString.get(keyName); - int metaState = 0; - if (forceCtrlDown) { - metaState |= KeyEvent.META_CTRL_ON | KeyEvent.META_CTRL_LEFT_ON; - } - if (forceLeftAltDown) { - metaState |= KeyEvent.META_ALT_ON | KeyEvent.META_ALT_LEFT_ON; - } - KeyEvent keyEvent = new KeyEvent(0, 0, KeyEvent.ACTION_UP, keyCode, 0, metaState); - terminalView.onKeyDown(keyCode, keyEvent); - } else { - // not a control char - keyName.codePoints().forEach(codePoint -> { - terminalView.inputCodePoint(codePoint, forceCtrlDown, forceLeftAltDown); - }); - } - } - - private void sendKey(View view, ExtraKeyButton buttonInfo) { - if (buttonInfo.isMacro()) { - String[] keys = buttonInfo.getKey().split(" "); - boolean ctrlDown = false; - boolean altDown = false; - for (String key : keys) { - if ("CTRL".equals(key)) { - ctrlDown = true; - } else if ("ALT".equals(key)) { - altDown = true; - } else { - sendKey(view, key, ctrlDown, altDown); - ctrlDown = false; - altDown = false; - } - } - } else { - sendKey(view, buttonInfo.getKey(), false, false); - } - } - - public enum SpecialButton { - CTRL, ALT, FN - } - - private static class SpecialButtonState { - boolean isOn = false; - boolean isActive = false; - List