Compare commits

..

53 Commits

Author SHA1 Message Date
a749a89431 Titan 2 Elite: physical keyboard fix + streaming scroll-hold
Some checks failed
Build / build (apt-android-5) (push) Has been cancelled
Build / build (apt-android-7) (push) Has been cancelled
Automatic Dependency Submission / dependency-submission (push) Has been cancelled
Validate Gradle Wrapper / Validation (push) Has been cancelled
Unit tests / testing (push) Has been cancelled
Attach Debug APKs To Release / attach-apks (apt-android-5) (release) Has been cancelled
Attach Debug APKs To Release / attach-apks (apt-android-7) (release) Has been cancelled
Trigger Termux Library Builds on Jitpack / trigger-termux-library-builds (release) Has been cancelled
- Only bubble real system keys (isSystem() synthetic letter events
  injected by the Titan 2 keyboard layer now reach the KCM path).
- Latch hardware Shift/Alt from modifier key events; injected letter
  events arrive without meta state. Cleared on keyup, focus loss and
  session attach.
- Consult the kcm alt layer for Alt+<key> (Alt+W -> '1') and send the
  resolved char without the ESC alt-prefix.
- Hold the scroll viewport when the user scrolled up while output is
  streaming; auto-follow resumes on scroll-to-bottom or typing.
- versionCode 1023 / versionName 0.119.0-titan2.1
2026-08-18 13:44:33 +02:00
agnostic-apollo
3df69d1da1 Revert: Add Warp sponsors logo
Some checks failed
Build / build (apt-android-5) (push) Has been cancelled
Build / build (apt-android-7) (push) Has been cancelled
Automatic Dependency Submission / dependency-submission (push) Has been cancelled
Validate Gradle Wrapper / Validation (push) Has been cancelled
Unit tests / testing (push) Has been cancelled
This reverts commit 8aca6dbbf4 as sponsorship ended after 3 months.
2026-07-15 01:47:01 +05:00
agnostic-apollo
401bbe54b8 Fixed: Do not add BigTextStyle to notification if big text is null
It may cause a `NullPointerException` on Android `6`.

```
java.lang.RuntimeException: Unable to create service com.termux.app.TermuxService: java.lang.NullPointerException: Attempt to invoke interface method 'java.lang.String java.lang.CharSequence.toString()' on a null object reference
  at android.app.ActivityThread.handleCreateService(ActivityThread.java:3048)
  at android.app.ActivityThread.access$2000(ActivityThread.java:156)
  at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1493)
  at android.os.Handler.dispatchMessage(Handler.java:102)
  at android.os.Looper.loop(Looper.java:148)
  at android.app.ActivityThread.main(ActivityThread.java:5609)
  at java.lang.reflect.Method.invoke(Native Method)
  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:746)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:636)
Caused by: java.lang.NullPointerException: Attempt to invoke interface method 'java.lang.String java.lang.CharSequence.toString()' on a null object reference
  at android.app.Notification$Builder.processLegacyText(Notification.java:3217)
  at android.app.Notification$Builder.access$1100(Notification.java:2009)
  at android.app.Notification$BigTextStyle.makeBigContentView(Notification.java:4283)
  at android.app.Notification$BigTextStyle.populateBigContentView(Notification.java:4321)
  at android.app.Notification$Style.buildStyled(Notification.java:3858)
  at android.app.Notification$Builder.build(Notification.java:3661)
  at com.termux.app.TermuxService.buildNotification(TermuxService.java:841)
  at com.termux.app.TermuxService.runStartForeground(TermuxService.java:206)
  at com.termux.app.TermuxService.onCreate(TermuxService.java:120)
  at android.app.ActivityThread.handleCreateService(ActivityThread.java:3038)
  ... 8 more
```

- https://cs.android.com/android/platform/superproject/+/android-6.0.1_r1:frameworks/base/core/java/android/app/Notification.java;l=4135

Closes termux/termux-app#5113
2026-06-05 04:15:42 +05:00
agnostic-apollo
30ebb2dee3 Fixed: Fix inverted typo in termcap values for KEYCODE_PAGE_UP and KEYCODE_PAGE_DOWN
Found by AstroSnail (https://github.com/termux/termux-app/issues/5052#issuecomment-4195304769)

Closes #5052
2026-04-07 04:11:27 +05:00
Fredrik Fornwall
3f0dec3574 Fixed: Remove deprecated attribute extractNativeLibs
Fixes the following deprecation warning when builing:

> AndroidManifest.xml:44:9-41 Warning:
> android:extractNativeLibs should not be specified in this source AndroidManifest.xml file. See https://d.android.com/guide/topics/manifest/application-element#extractNativeLibs for more information.
> The AGP Upgrade Assistant can remove the attribute from the AndroidManifest.xml file and update the build file accordingly. See https://d.android.com/studio/build/agp-upgrade-assistant for more information.

The useLegacyPackaging DSL option is already used in `app/build.gradle`.
2026-02-21 11:47:26 +01:00
Jonathan LEI
ef4775b651 Increase read buffer size from 4KB to 64KB
The small buffer size has caused serious lags in large content use cases
like scrolling on terminal multiplexers.
2026-02-21 11:04:42 +01:00
agnostic-apollo
e50ac9e019 Changed: Change README termux.com links to termux.dev 2026-02-12 11:25:43 +05:00
Fredrik Fornwall
5f578d56fc Update bootstrap packages from 2022.04.28 to 2026.02.12
Having up-to-date bootstrap packages speeds up development for everyone
who doesn't need to await a big initial update.
2026-02-12 01:13:36 +01:00
Robert Kirkman
1937595c57 Fixed: Revert desugar_jdk_libs back to 1.1.5
- This is necessary because `desugar_jdk_libs` version 2.1.2 adds new `java.nio.file` APIs that are untested, and additionally, `java.nio.file` APIs are known to be broken, so will be removed from Termux in the future rather than being bumped. More information here: https://github.com/termux/termux-app/pull/3619#discussion_r2680770189

- This is necessary to prevent errors like this while using the [local Maven repository guide](https://github.com/termux/termux-app/wiki/Termux-Libraries#forking-and-local-development) to build reverse dependencies like Termux:API after https://github.com/termux/termux-app/pull/3619

```
1.  Dependency 'com.termux:termux-shared:0.118.0' requires desugar_jdk_libs version to be
           2.1.2 or above for :app, which is currently 1.1.5
```

- Partially reverts https://github.com/termux/termux-app/pull/3619
2026-01-12 16:02:31 +01:00
Robert Kirkman
a1457d1254 Fixed: Correct publication of Maven libraries after update of Gradle to 9.2.1
- After https://github.com/termux/termux-app/pull/3619, unfortunately this error began to occur when one attempts to use the [Publication to Maven Local guide](https://github.com/termux/termux-app/wiki/Termux-Libraries#forking-and-local-development) to compile all Termux APKs entirely from source, which previously worked:

```
> Task :app:processDebugResources FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':app:processDebugResources'.
> A failure occurred while executing com.android.build.gradle.internal.res.LinkApplicationAndroidResourcesTask$TaskAction
   > Android resource linking failed
     ERROR: /home/tacokoneko/code/termux/termux-generator/termux-apps-main/termux-api/app/src/main/AndroidManifest.xml:72:9-76:20: AAPT: error: resource style/Theme.BaseActivity.DayNight.NoActionBar (aka com.termux.api:style/Theme.BaseActivity.DayNight.NoActionBar) not found.

     ERROR: /home/tacokoneko/code/termux/termux-generator/termux-apps-main/termux-api/app/src/main/AndroidManifest.xml:88:9-93:20: AAPT: error: resource style/Theme.BaseActivity.DayNight.NoActionBar (aka com.termux.api:style/Theme.BaseActivity.DayNight.NoActionBar) not found.

     ERROR: /home/tacokoneko/code/termux/termux-generator/termux-apps-main/termux-api/app/build/intermediates/packaged_manifests/debug/processDebugManifestForPackage/AndroidManifest.xml:183: AAPT: error: resource style/Theme.MarkdownViewActivity.DayNight (aka three.termux.api:style/Theme.MarkdownViewActivity.DayNight) not found.
```

- This PR fixes that error by using `components.default` in the `publications` block instead of `components.findByName('release')` and adding a `publishing` block to the `android` section of the `build.gradle` of each affected library, in accordance with https://developer.android.com/reference/tools/gradle-api/9.1/com/android/build/api/dsl/PublishingOptions and https://stackoverflow.com/a/71366104. `components.release` no longer works, but testing indicates that `components.default` works.
2026-01-11 11:26:36 +01:00
Yaksh Bariya
2ad6112803 Changed: bug-report-template: scream that this is not the place to report bugs about packages (#3989)
I'm seeing a lot of bug reports that should have been made to
termux/termux-packages, but people don't just read things not written in
BOLD and caps it seems. So now scream out, so that they do read.

Also direct confused users to termux/termux-packages, in case it does
actually belong to termux/termux-app, we can always transfer the issue
there
2026-01-07 01:44:26 +01:00
Fredrik Fornwall
93c99973cc Changed: Remove no longer needed gradle jvm args
Remove gradle jvm arguments no longer necessary after updating gradle.
2026-01-07 01:26:44 +01:00
scarf
7d87ed7629 Fixed: Allow language switch key on external keyboards (#4923)
Handle KEYCODE_LANGUAGE_SWITCH to allow keyboard layout switching
on external keyboards by passing the event to system handler.

Fixes #4133

Co-authored-by: uncher <36965412+uncher@users.noreply.github.com>
2026-01-07 01:15:55 +01:00
Fredrik Fornwall
53f75a8da3 Changed: Update gradle, android gradle plugin and dependencies 2026-01-04 03:16:28 +01:00
dependabot[bot]
bcb61c387c Changed: Bump actions/upload-artifact from 4 to 6
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 6.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4...v6)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-04 03:08:14 +01:00
dependabot[bot]
c6ec1cb64f Changed: Bump actions/checkout from 4 to 6 (#4859)
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Fredrik Fornwall <fredrik@fornwall.net>
2026-01-04 03:06:03 +01:00
Fredrik Fornwall
382c9708b3 Fixed: Update gradle wrapper validation in CI
Replace `gradle/wrapper-validation-action@v3` with
`gradle/actions/wrapper-validation@5` which should fix the error seen
e.g. [here](https://github.com/termux/termux-app/actions/runs/20685857619/job/59386383274):

> Error: The action gradle/actions/wrapper-validation@v3.5.0 is not allowed in termux/termux-app because all actions must be from a repository owned by termux, created by GitHub, or match one of the patterns: gradle/actions/dependency-submission@*, gradle/wrapper-validation-action@v*.
2026-01-04 02:55:26 +01:00
dependabot[bot]
edd44d2ce9 Changed: Bump actions/setup-java from 4 to 5
Bumps [actions/setup-java](https://github.com/actions/setup-java) from 4 to 5.
- [Release notes](https://github.com/actions/setup-java/releases)
- [Commits](https://github.com/actions/setup-java/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/setup-java
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-04 02:42:55 +01:00
dependabot[bot]
4183ffa122 Changed: Bump gradle/actions from 4 to 5
Bumps [gradle/actions](https://github.com/gradle/actions) from 4 to 5.
- [Release notes](https://github.com/gradle/actions/releases)
- [Commits](https://github.com/gradle/actions/compare/v4...v5)

---
updated-dependencies:
- dependency-name: gradle/actions
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-04 02:42:31 +01:00
agnostic-apollo
8aca6dbbf4 Added: Add Warp sponsors logo
- https://www.warp.dev

The logo is sourced from 640dffd347/Github/Sponsor/Warp-Github-LG-03.png
2025-09-26 23:04:22 +05:00
agnostic-apollo
1812a405ee Added: Add Clouflare funders logo
- https://www.cloudflare.com
- https://packages-cf.termux.dev

Clouflare has served around 36TB (32TB cached) data, 39M requests, 1M unique visitors in last 30 days for our https://termux.dev domain, and has potentially served 100s to 1000s of TBs of data over the years for free for Termux, primarily from the https://packages-cf.termux.dev `apt` packages repo, which is a cloudflare backed variant of our primary repo https://packages.termux.dev.

The `cloudflare.png` logo is sourced from www.cloudflare.com/press-kit
2025-09-26 23:04:22 +05:00
agnostic-apollo
338fb66e5c Added: Add NLnet NGI Mobifree sponsors logo
- https://nlnet.nl/mobifree
- https://nlnet.nl/news/2024/20241111-NGI-Mobifree-grants.html
- https://termux.dev/en/posts/general/2024/11/11/termux-selected-for-nlnet-ngi-mobifree-grant.html

The `nlnet-ngi-mobifree.png` logo is sourced from https://nlnet.nl/logo
2025-09-25 00:56:11 +05:00
agnostic-apollo
f852dca0ef Added: Add GitHub Secure Open Source Fund sponsors logo
- https://resources.github.com/github-secure-open-source-fund
- https://github.blog/open-source/maintainers/securing-the-supply-chain-at-scale-starting-with-71-important-open-source-projects
- https://termux.dev/en/posts/general/2025/08/11/termux-selected-for-github-secure-open-source-fund-session-2.html

The `github.png` logo is sourced from https://github.com/logos
2025-09-25 00:56:05 +05:00
agnostic-apollo
8174a995ae Added: Add GitHub Accelerator sponsors logo
- https://github.com/accelerator
- https://github.blog/2023-04-12-github-accelerator-our-first-cohort-and-whats-next

The `github.png` logo is sourced from https://github.com/logos
2025-09-25 00:56:05 +05:00
agnostic-apollo
7bceab88e2 Added: Add SECURITY.md 2025-06-25 21:10:52 +05:00
agnostic-apollo
2c1bd650b0 Added: Enable workflow dispatch trigger for Automatic Dependency Submission workflow 2025-06-20 13:30:51 +05:00
agnostic-apollo
4a9ad910dd Added: Add Automatic Dependency Submission for SBOM
- https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/configuring-automatic-dependency-submission-for-your-repository#gradle-projects
- https://github.com/actions/gradle-build-tools-actions#the-dependency-submission-action
2025-06-17 10:17:44 +05:00
agnostic-apollo
9d2ac14557 Fixed: Use ndkVersion specified in project properties or $JITPACK_NDK_VERSION env variable for termux-shared library 2025-05-30 12:06:45 +05:00
agnostic-apollo
c6e2ca7fde Changed(README): Set latest version to 0.118.3 2025-05-23 15:01:53 +05:00
agnostic-apollo
da3a0ac4e2 Fixed: Add explicit serialVersionUID to Serializable classes like ReportInfo and TextIOInfo
Reading `ReportInfo` with `Bundle.getSerializable()` by `ReportActivity` is triggering exception when default algorithm is used for `serialVersionUID` in Termux:API plugin app when error notification created in `ResultReturner.returnData()` by `TermuxPluginUtils.sendPluginCommandErrorNotification()` is clicked.

```
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.termux/com.termux.shared.activities.ReportActivity}: android.os.BadParcelableException: Parcelable encountered IOException reading a Serializable object (name = com.termux.shared.models.ReportInfo)
	at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:4280)
	at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:4467)
	at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:222)
	at android.app.servertransaction.TransactionExecutor.executeNonLifecycleItem(TransactionExecutor.java:133)
	at android.app.servertransaction.TransactionExecutor.executeTransactionItems(TransactionExecutor.java:103)
	at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:80)
	at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2823)
	at android.os.Handler.dispatchMessage(Handler.java:110)
	at android.os.Looper.loopOnce(Looper.java:248)
	at android.os.Looper.loop(Looper.java:338)
	at android.app.ActivityThread.main(ActivityThread.java:9067)
	at java.lang.reflect.Method.invoke(Native Method)
	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:593)
	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:932)
Caused by: android.os.BadParcelableException: Parcelable encountered IOException reading a Serializable object (name = com.termux.shared.models.ReportInfo)
	at android.os.Parcel.readSerializableInternal(Parcel.java:5520)
	at android.os.Parcel.readValue(Parcel.java:5038)
	at android.os.Parcel.readValue(Parcel.java:4702)
	at android.os.Parcel.-$$Nest$mreadValue(Unknown Source:0)
	at android.os.Parcel$LazyValue.apply(Parcel.java:4811)
	at android.os.Parcel$LazyValue.apply(Parcel.java:4764)
	at android.os.BaseBundle.unwrapLazyValueFromMapLocked(BaseBundle.java:446)
	at android.os.BaseBundle.getValueAt(BaseBundle.java:426)
	at android.os.BaseBundle.getValue(BaseBundle.java:397)
	at android.os.BaseBundle.getValue(BaseBundle.java:380)
	at android.os.BaseBundle.getValue(BaseBundle.java:373)
	at android.os.BaseBundle.getSerializable(BaseBundle.java:1522)
	at android.os.Bundle.getSerializable(Bundle.java:1339)
	at com.termux.shared.activities.ReportActivity.updateUI(ReportActivity.java:140)
	at com.termux.shared.activities.ReportActivity.onCreate(ReportActivity.java:93)
	at android.app.Activity.performCreate(Activity.java:9155)
	at android.app.Activity.performCreate(Activity.java:9133)
	at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1521)
	at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:4262)
	... 13 more
Caused by: java.io.InvalidClassException: com.termux.shared.models.ReportInfo; local class incompatible: stream classdesc serialVersionUID = -5165426368218339031, local class serialVersionUID = 1
	at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:652)
	at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1743)
	at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1624)
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1902)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1442)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:430)
	at android.os.Parcel.readSerializableInternal(Parcel.java:5507)
	... 31 more

```

If using release APK with obfuscation enabled, then following exception will be triggered.

```
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.termux/com.termux.shared.activities.ReportActivity}: android.os.BadParcelableException: Parcelable encountered ClassNotFoundException reading a Serializable object (name = I0.a)
	at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3864)
	at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:4006)
	at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:111)
	at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
	at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
	at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2462)
	at android.os.Handler.dispatchMessage(Handler.java:106)
	at android.os.Looper.loopOnce(Looper.java:240)
	at android.os.Looper.loop(Looper.java:351)
	at android.app.ActivityThread.main(ActivityThread.java:8377)
	at java.lang.reflect.Method.invoke(Native Method)
	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:584)
	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1013)
Caused by: android.os.BadParcelableException: Parcelable encountered ClassNotFoundException reading a Serializable object (name = I0.a)
	at android.os.Parcel.readSerializableInternal(Parcel.java:5113)
	at android.os.Parcel.readValue(Parcel.java:4655)
	at android.os.Parcel.readValue(Parcel.java:4363)
	at android.os.Parcel.-$$Nest$mreadValue(Unknown Source:0)
	at android.os.Parcel$LazyValue.apply(Parcel.java:4461)
	at android.os.Parcel$LazyValue.apply(Parcel.java:4420)
	at android.os.BaseBundle.getValueAt(BaseBundle.java:394)
	at android.os.BaseBundle.getValue(BaseBundle.java:374)
	at android.os.BaseBundle.getValue(BaseBundle.java:357)
	at android.os.BaseBundle.getValue(BaseBundle.java:350)
	at android.os.BaseBundle.getSerializable(BaseBundle.java:1451)
	at android.os.Bundle.getSerializable(Bundle.java:1144)
	at com.termux.shared.activities.ReportActivity.updateUI(ReportActivity.java:136)
	at com.termux.shared.activities.ReportActivity.onCreate(ReportActivity.java:89)
	at android.app.Activity.performCreate(Activity.java:8397)
	at android.app.Activity.performCreate(Activity.java:8370)
	at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1403)
	at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3837)
	... 12 more
Caused by: java.lang.ClassNotFoundException: I0.a
	at java.lang.Class.classForName(Native Method)
	at java.lang.Class.forName(Class.java:536)
	at android.os.Parcel$2.resolveClass(Parcel.java:5090)
	at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1733)
	at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1624)
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1902)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1442)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:430)
	at android.os.Parcel.readSerializableInternal(Parcel.java:5096)
	... 29 more
Caused by: java.lang.ClassNotFoundException: I0.a
	... 38 more
```

Related issue https://github.com/termux/termux-api/issues/762
2025-05-23 02:27:29 +05:00
Wang Han
4de0caac5b Changed|Fixed: Bump org.lsposed.hiddenapibypass:hiddenapibypass to 6.1 to fix crash on Android 16 QPR1
```
Build fingerprint: 'google/shiba_beta/shiba:16/BP31.250502.008/13497110:user/release-keys'
Revision: 'MP1.0'
ABI: 'arm64'
Executable: /system/bin/app_process64
Cmdline: com.termux
pid: 22617, tid: 22617, name: com.termux  >>> com.termux <<<
uid: 10323
tagged_addr_ctrl: 0000000000000001 (PR_TAGGED_ADDR_ENABLE)
pac_enabled_keys: 000000000000000f (PR_PAC_APIAKEY, PR_PAC_APIBKEY, PR_PAC_APDAKEY, PR_PAC_APDBKEY)
esr: 0000000092000006 (Data Abort Exception 0x24)
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x000000000000000c
Cause: null pointer dereference
    x0  0000000070b6f798  x1  0000000002159228  x2  000000000000000c  x3  00000000ebad6073
    x4  00000000ebad6074  x5  00000000ebad6075  x6  00000000ebad6076  x7  00000000ebad6077
    x8  00000000ebad6078  x9  00000000ebad6079  x10 00000000ebad607a  x11 00000000ebad607b
    x12 00000000ebad607c  x13 00000000ebad607d  x14 00000000ebad607e  x15 00000000ebad607f
    x16 0000007feda203f0  x17 0000007976776a9c  x18 0000007cd0e44000  x19 b400007b850b8be0
    x20 0000000000000000  x21 b400007b850b8ca0  x22 0000000000000000  x23 00000000021592b8
    x24 000000006413b378  x25 0000000070b99ee0  x26 0000000070b9fc78  x27 0000000000000000
    x28 0000000000000053  x29 0000000070b99ee0
    lr  0000007976776a98  sp  0000007feda203f0  pc  0000007976776b0c  pst 0000000080001000
    esr 0000000092000006
26 total frames
backtrace:
      #00 pc 00000000000c8b0c  /data/app/~~p_sHRwZKj3QVf_xeRMrR3g==/com.termux-rodTRD4IY6G2qtCPCrHfhw==/oat/arm64/base.odex (org.lsposed.hiddenapibypass.HiddenApiBypass.getDeclaredMethods+780)
      #01 pc 00000000000c8dc4  /data/app/~~p_sHRwZKj3QVf_xeRMrR3g==/com.termux-rodTRD4IY6G2qtCPCrHfhw==/oat/arm64/base.odex (org.lsposed.hiddenapibypass.HiddenApiBypass.setHiddenApiExemptions+68)
      #02 pc 00000000000ae560  /data/app/~~p_sHRwZKj3QVf_xeRMrR3g==/com.termux-rodTRD4IY6G2qtCPCrHfhw==/oat/arm64/base.odex (com.termux.shared.reflection.ReflectionUtils.bypassHiddenAPIReflectionRestrictions+448)
      #03 pc 00000000000a342c  /data/app/~~p_sHRwZKj3QVf_xeRMrR3g==/com.termux-rodTRD4IY6G2qtCPCrHfhw==/oat/arm64/base.odex (com.termux.shared.android.SELinuxUtils.getContext+108)
      #04 pc 00000000000c0b60  /data/app/~~p_sHRwZKj3QVf_xeRMrR3g==/com.termux-rodTRD4IY6G2qtCPCrHfhw==/oat/arm64/base.odex (com.termux.shared.termux.shell.command.environment.TermuxAppShellEnvironment.setTermuxAppEnvironment+3216)
      #05 pc 000000000009eafc  /data/app/~~p_sHRwZKj3QVf_xeRMrR3g==/com.termux-rodTRD4IY6G2qtCPCrHfhw==/oat/arm64/base.odex (com.termux.app.TermuxApplication.onCreate+1596)
      ...
      #22 pc 0000000000105c98  /system/lib64/libandroid_runtime.so (_JNIEnv::CallStaticVoidMethod(_jclass*, _jmethodID*, ...)+104) (BuildId: ea93df5e792bbd2e0cbb71b2a7599aaa)
      #23 pc 000000000013121c  /system/lib64/libandroid_runtime.so (android::AndroidRuntime::start(char const*, android::Vector<android::String8> const&, bool)+908) (BuildId: ea93df5e792bbd2e0cbb71b2a7599aaa)
      #24 pc 000000000000459c  /system/bin/app_process64 (main+1212) (BuildId: a237cfae6965d7f0b950e5955c73432c)
      #25 pc 000000000006bb88  /apex/com.android.runtime/lib64/bionic/libc.so (__libc_init+120) (BuildId: eecb0fc2ec8128ec92c091c0160586d1)
```

Related commit https://github.com/LSPosed/AndroidHiddenApiBypass/commit/9efadf06
Related commit https://android-review.googlesource.com/c/platform/libcore/+/3380841
Related commit https://cs.android.com/android/_/android/platform/libcore/+/0dc31afe

Closes #4556
2025-05-23 02:26:47 +05:00
agnostic-apollo
bc321d0a7c Changed(README): Set latest version to 0.118.2 2025-03-29 12:15:10 +05:00
agnostic-apollo
9ee1c9d5ad Fixed: Use openjdk11 for jitpack builds of termux libraries
`Unrecognized option: --add-exports=java.base/sun.nio.ch=ALL-UNNAMED`

Related commit 52da00e5
2025-03-29 10:34:17 +05:00
Johannes Altmanninger
a988383e01 Fixed: Fully consume unknown CSI sequences containing unsupported parameter and intermediate bytes
Standard ECMA-48: Control Functions for Coded Character Sets specifies the format of CSI commands.
- https://en.wikipedia.org/wiki/ANSI_escape_code#Control_Sequence_Introducer_commands
- https://invisible-island.net/xterm/ecma-48-parameter-format.html#section5.4

Previously unsupported bytes would be echoed to the terminal.

```shell
$ printf '\x1b[=u' # PF
u
$ printf '\x1b[=5u' # PPF
5u
$ printf '\x1b[=5!u' # PPIF
5!u
$ printf '\x1b[=5!%u' # PPIIF
5!0
$ printf '\x1b[=?5!%u' # PPPIIF
?5!0
```

This fixes a problem with fish shell 4.0.0 which uses that sequence.

Closes #4338

Co-authored-by: @krobelus <aclopte@gmail.com>
Co-authored-by: @agnostic-apollo  <agnosticapollo@gmail.com>
2025-03-15 11:26:11 +05:00
agnostic-apollo
d2cd6ac2e5 Changed|Fixed: Bump org.lsposed.hiddenapibypass:hiddenapibypass to 5.0 to fix crash on Android 16
```
Build fingerprint: 'google/sdk_gphone64_x86_64/emu64xa:Baklava/BP22.250103.008/12932282:userdebug/dev-keys'
Revision: '0'
ABI: 'x86_64'
Timestamp: 2025-01-25
Process uptime: 1s
Cmdline: com.termux
pid: 4700, tid: 4700, name: com.termux  >>> com.termux <<<
uid: 10212
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x000000000000000c
Cause: null pointer dereference
    rax 0000000000000000  rbx 000071ad67c247b0  rcx 000000000000000c  rdx 000071abc520c888
    r8  00005f2000006018  r9  0000607c00006004  r10 000071abc423d68c  r11 000071abc4cca7c0
    r12 00007ffc65da8240  r13 000071ad67c24858  r14 00007ffc65da85d8  r15 000071ad67c247b0
    rdi 000071ad87c26110  rsi 00007ffc65da8148
    rbp 00007ffc65da8050  rsp 00007ffc65da8040  rip 000071abc4cca817
124 total frames
backtrace:
      #00 pc 00000000008ca817  /apex/com.android.art/lib64/libart.so (art::Unsafe_getObject(_JNIEnv*, _jobject*, _jobject*, long) (.__uniq.306581074569039686346581217366878976736)+87) (BuildId: 99c067c739342eb9769974bbb229d3b3)
      #01 pc 000000000022c80b  /apex/com.android.art/lib64/libart.so (art_quick_generic_jni_trampoline+219) (BuildId: 99c067c739342eb9769974bbb229d3b3)
      #02 pc 0000000000211dd4  /apex/com.android.art/lib64/libart.so (art_quick_invoke_stub+756) (BuildId: 99c067c739342eb9769974bbb229d3b3)
      #03 pc 0000000000556155  /apex/com.android.art/lib64/libart.so (art::ArtMethod::Invoke(art::Thread*, unsigned int*, unsigned int, art::JValue*, char const*)+181) (BuildId: 99c067c739342eb9769974bbb229d3b3)
      #04 pc 00000000006dd182  /apex/com.android.art/lib64/libart.so (bool art::interpreter::DoCall<false>(art::ArtMethod*, art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, bool, art::JValue*)+2434) (BuildId: 99c067c739342eb9769974bbb229d3b3)
      #05 pc 0000000000233564  /apex/com.android.art/lib64/libart.so (void art::interpreter::ExecuteSwitchImplCpp<false>(art::interpreter::SwitchImplContext*)+10804) (BuildId: 99c067c739342eb9769974bbb229d3b3)
      #06 pc 000000000022eb25  /apex/com.android.art/lib64/libart.so (ExecuteSwitchImplAsm+5) (BuildId: 99c067c739342eb9769974bbb229d3b3)
      #07 pc 0000000000080eb4  <anonymous:71ab3d335000> (org.lsposed.hiddenapibypass.HiddenApiBypass.getDeclaredMethods+0)
      ...
      #13 pc 0000000000080dd0  <anonymous:71ab3d335000> (org.lsposed.hiddenapibypass.HiddenApiBypass.setHiddenApiExemptions+0)
      ...
      #19 pc 0000000000080cf8  <anonymous:71ab3d335000> (org.lsposed.hiddenapibypass.HiddenApiBypass.addHiddenApiExemptions+0)
      ...
      #25 pc 0000000000005ab4  <anonymous:71ae82992000> (com.termux.shared.reflection.ReflectionUtils.bypassHiddenAPIReflectionRestrictions+0)
      ...
      #31 pc 0000000000004738  <anonymous:71ae86607000> (com.termux.shared.android.SELinuxUtils.getContext+0)
      ...
      #37 pc 0000000000005b48  <anonymous:71ae825d8000> (com.termux.shared.termux.shell.command.environment.TermuxAppShellEnvironment.setTermuxAppEnvironment+0)
      ...
      #43 pc 000000000000603c  <anonymous:71ae825d8000> (com.termux.shared.termux.shell.command.environment.TermuxShellEnvironment.init+0)
      ...
      #49 pc 0000000000006a68  <anonymous:71ae7e1c2000> (com.termux.app.TermuxApplication.onCreate+0)
```

Related commit 40b4cafa47
Related issue https://github.com/LSPosed/AndroidHiddenApiBypass/issues/52

Closes #4368
2025-01-25 02:30:20 +05:00
agnostic-apollo
52da00e5c2 Fixed: Fix gradle build error if using jdk 17
`Unable to make field private final java.lang.String java.io.File.path accessible: module java.base does not "opens java.io" to unnamed module`
2025-01-13 03:41:34 +05:00
agnostic-apollo
b0e1dbc3da Fixed: Use TERMUX_STYLING_APP for TERMUX_STYLING_ACTIVITY_NAME as per 078eea2b 2025-01-13 01:50:45 +05:00
agnostic-apollo
078eea2b74 Added: Rename app classes in TermuxConstants with _APP suffix added and add TERMUX_*_MAIN_ACTIVITY_NAME and TERMUX_*_LAUNCHER_ACTIVITY_NAME constants to each app class 2025-01-13 01:30:06 +05:00
agnostic-apollo
fb01127ff2 Fixed: Fix tests added in b84dc703 2024-10-28 12:45:39 +05:00
Jason Yu
b84dc703e8 Added: Add empty and null strings tests for invalid urls to FileReceiverActivityTest 2024-10-28 12:34:31 +05:00
Fredrik Fornwall
8be53336c3 Fixed: Implement colon separated CSI parameters 2024-10-02 17:27:08 +02:00
Evgeny Zhabotinsky
d90e3fbca1 Fixed: Make ScrollDown escape respect margins
SD sequence (`${CSI}${N}T`) was scrolling the whole width
 of the terminal instead of just between the margins.
RI sequence (`${ESC}M`, move cursor up 1 line) was doing the same.
Fixed that.

Fixes #2576 where in tmux scrolling one of several
 side-by-side panels down resulted in all visually scrolling.
2024-09-29 23:37:28 +02:00
Tom Kranz
245158ceb9 Added: Basic MIME type recognition in ContentProvider 2024-09-28 01:19:50 +02:00
Matan Ziv-Av
6c00f1fc61 Fixed: Use Canvas.drawTextRun instead of drawText
drawText does (very) basic BiDi, which causes inconsistent behaviour.
This ensures everything is LtR.
2024-09-27 11:27:38 +02:00
Fredrik Fornwall
03142590ff Added: Terminal CSI reporting of window and cell pixel size
Implement the following CSI escape sequences from
https://invisible-island.net/xterm/ctlseqs/ctlseqs.html:

> CSI Ps ; Ps ; Ps t
> [..]
>    Ps = 1 4  ⇒  Report xterm text area size in pixels.
>    Result is CSI  4 ;  height ;  width t
> [..]
>    Ps = 1 6  ⇒  Report xterm character cell size in pixels.
>    Result is CSI  6 ;  height ;  width t

Extracted from changes in https://github.com/termux/termux-app/pull/2973
by @MatanZ and adopted to play well with the just merged #3098 (.ws_xpixel
and .ws_ypixel values in winsize).
2024-09-26 21:53:52 +02:00
Artem Chepurnyi
4443b657bf Fixed: Mark view as important for AutoFill before requesting an AutoFill
Co-authored-by: @AChep <mail@artemchep.com>
Co-authored-by: @agnostic-apollo  <agnosticapollo@gmail.com>
2024-09-26 20:34:18 +05:00
Dvd-Znf
a8a69c6d80 Update latest version in README.md to v0.118.1 2024-09-26 16:55:56 +02:00
Krunal Patel
438cd73fff Added|Changed: Fill .ws_xpixel and .ws_ypixel in winsize
This allows to get terminal size in pixel using `TIOCGWINSZ` ioctl.
Set `.ws_xpixel` using `columns * cell_width` and set `.ws_ypixel` using `rows * cell_height`.
Cell width and height is font width and line spacing, respectively.
2024-09-26 15:09:09 +02:00
Fredrik Fornwall
36d811ea7d Fixed: Parse (but ignore for now) terminal APC sequences 2024-09-18 22:28:16 +02:00
agnostic-apollo
c2d57f2ed8 Added|Fixed: Do not show AutoFill UI on Termux start and add support for usernames
- The AutoFill type and hints are no longer hardcoded in `TerminalView` class and `TermuxActivity` layout xml. They are dynamically set to required values before making a manual AutoFill request and reverted back afterwards to default values. The hardcoded value `AUTOFILL_TYPE_TEXT` returned by `getAutofillType()` was causing the AutoFill UI to show on Activity starts, this will return `AUTOFILL_TYPE_NONE` by default now so that AutoFill UI isn't shown automatically.
- The AutoFill importance is no longer hardcoded in `TermuxActivity` layout xml and is returned by `TerminalView` class itself by `getImportantForAutofill()`.
- The AutoFill function in `TermuxActivity` for making a manual AutoFill request is moved to `TerminalView` class. This and moving of hardcoded values to `TerminalView` class mentioned above is done as complete logic of AutoFill should be handled by `TerminalView` class itself and not scattered in various places.
- The Terminal context menu now supports AutoFilling a username. Note that GBoard/Google Password Manager seems to have a bug where it will still show `Pick a saved password` instead of username, even though `AUTOFILL_HINT_USERNAME` is being requested, however it will still AutoFill a username of selected entry correctly.
- Pressing the back button to close the keyboard will also cancel the current manually requested AutoFill request and UI will not show when keyboard is opened again.

Closes #3909
2024-08-27 20:44:05 +05:00
Josh Triplett
661c37501f Make Shift-PgUp and Shift-PgDn scroll by pages rather than lines
In other terminals, such as gnome-terminal, Shift-PgUp and Shift-PgDn
scroll the screen by a full page, rather than a single line. Adjust
termux to match.
2024-08-16 12:18:54 +02:00
Fredrik Fornwall
f80b46487d Fixed: Improve handling of empty ';' SGR sequences
Currently the Termux terminal emulator prints "HI" in red with:

```sh
printf "\e[31;m HI \e[0m"
```

This is not how other terminals (tested on xterm, gnome-terminal,
alacritty and the mac built in terminal) handle it, since they parse
""\e[31;m" as "\e[31;0m", where the "0" resets the colors.

This change aligns with other terminals, as well as improves performance
by avoiding allocating a new int[] array for each byte processed by
`parseArg()`, and most importantly simplifies things by removing the
`mIsCSIStart` and `mLastCSIArg` state, preparing for supporting ':'
separated sub parameters such as used in
https://sw.kovidgoyal.net/kitty/underlines/
2024-08-13 19:20:38 +02:00
Fredrik Fornwall
a5de3d3a2b Fixed: Use current bg color when scrolling with horizontal margins
Fixes https://github.com/termux/termux-packages/issues/12556

Issue was also reported here:
https://www.reddit.com/r/termux/comments/1df1dii/how_can_i_fix_this_annoying_screenfilling_thing/
2024-08-13 19:14:53 +02:00
50 changed files with 607 additions and 470 deletions

View File

@@ -6,7 +6,8 @@ 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.
**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/TERMUX-PACKAGES](https://github.com/termux/termux-packages) INSTEAD.**
If you are unsure if this is a bug with the Termux app itself or any of the packages, kindly open up the bug at [termux/termux-packages](https://github.com/termux/termux-packages) as it's very likely that it belongs there.
Use search before you open an issue to check whether your issue has been already reported and perhaps solved.

View File

@@ -17,7 +17,7 @@ jobs:
steps:
- name: Clone repository
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
ref: ${{ env.GITHUB_REF }}
@@ -71,13 +71,14 @@ jobs:
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
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

View File

@@ -19,7 +19,13 @@ jobs:
steps:
- name: Clone repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Setup java 17
uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: '17'
- name: Build APKs
shell: bash {0}
@@ -79,7 +85,7 @@ jobs:
fi
- name: Attach universal APK file
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v6
with:
name: ${{ env.APK_BASENAME_PREFIX }}_universal
path: |
@@ -87,7 +93,7 @@ jobs:
${{ env.APK_DIR_PATH }}/output-metadata.json
- name: Attach arm64-v8a APK file
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v6
with:
name: ${{ env.APK_BASENAME_PREFIX }}_arm64-v8a
path: |
@@ -95,7 +101,7 @@ jobs:
${{ env.APK_DIR_PATH }}/output-metadata.json
- name: Attach armeabi-v7a APK file
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v6
with:
name: ${{ env.APK_BASENAME_PREFIX }}_armeabi-v7a
path: |
@@ -103,7 +109,7 @@ jobs:
${{ env.APK_DIR_PATH }}/output-metadata.json
- name: Attach x86_64 APK file
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v6
with:
name: ${{ env.APK_BASENAME_PREFIX }}_x86_64
path: |
@@ -111,7 +117,7 @@ jobs:
${{ env.APK_DIR_PATH }}/output-metadata.json
- name: Attach x86 APK file
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v6
with:
name: ${{ env.APK_BASENAME_PREFIX }}_x86
path: |
@@ -119,7 +125,7 @@ jobs:
${{ env.APK_DIR_PATH }}/output-metadata.json
- name: Attach sha256sums file
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v6
with:
name: ${{ env.APK_BASENAME_PREFIX }}_sha256sums
path: |

View File

@@ -0,0 +1,23 @@
name: Automatic Dependency Submission
on:
push:
branches: [ 'master' ]
workflow_dispatch:
permissions:
contents: write
jobs:
dependency-submission:
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v6
- name: Setup Java
uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: 17
- name: Generate and submit dependency graph
uses: gradle/actions/dependency-submission@v5

View File

@@ -15,5 +15,5 @@ jobs:
name: "Validation"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: gradle/wrapper-validation-action@v3
- uses: actions/checkout@v6
- uses: gradle/actions/wrapper-validation@5

View File

@@ -15,7 +15,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Clone repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Setup java 17
uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: '17'
- name: Execute tests
run: |
./gradlew test

View File

@@ -7,7 +7,7 @@
[![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](https://termux.dev) is an Android terminal application and Linux environment.
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).
@@ -29,6 +29,7 @@ Quick how-to about Termux package management is available at [Package Management
- [Debugging](#debugging)
- [For Maintainers and Contributors](#for-maintainers-and-contributors)
- [Forking](#forking)
- [Sponsors and Funders](#sponsors-and-funders)
##
@@ -49,7 +50,7 @@ The core [Termux](https://github.com/termux/termux-app) app comes with the follo
## Installation
Latest version is `v0.118.1`.
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.**
@@ -59,7 +60,7 @@ Support for both app and packages was dropped for Android `5` and `6` on [2020-0
The APK files of different sources are signed with different signature keys. The `Termux` app and all its plugins use the same [`sharedUserId`](https://developer.android.com/guide/topics/manifest/manifest-element) `com.termux` and so all their APKs installed on a device must have been signed with the same signature key to work together and so they must all be installed from the same source. Do not attempt to mix them together, i.e do not try to install an app or plugin from `F-Droid` and another one from a different source like `GitHub`. Android Package Manager will also normally not allow installation of APKs with different signatures and you will get errors on installation like `App not installed`, `Failed to install due to an unknown error`, `INSTALL_FAILED_UPDATE_INCOMPATIBLE`, `INSTALL_FAILED_SHARED_USER_INCOMPATIBLE`, `signatures do not match previously installed version`, etc. This restriction can be bypassed with root or with custom roms.
If you wish to install from a different source, then you must **uninstall any and all existing Termux or its plugin app APKs** from your device first, then install all new APKs from the same new source. Check [Uninstallation](#uninstallation) section for details. You may also want to consider [Backing up Termux](https://wiki.termux.com/wiki/Backing_up_Termux) before the uninstallation so that you can restore it after re-installing from Termux different source.
If you wish to install from a different source, then you must **uninstall any and all existing Termux or its plugin app APKs** from your device first, then install all new APKs from the same new source. Check [Uninstallation](#uninstallation) section for details. You may also want to consider [Backing up Termux](https://wiki.termux.dev/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).
@@ -264,3 +265,26 @@ Commit messages **must** use the [Conventional Commits](https://www.conventional
- You also need to recompile bootstrap zip for the new package name. Check [building bootstrap](https://github.com/termux/termux-packages/wiki/For-maintainers#build-bootstrap-archives), [here](https://github.com/termux/termux-app/issues/1983) and [here](https://github.com/termux/termux-app/issues/2081#issuecomment-865280111).
- Currently, not all plugins use `TermuxConstants` from `termux-shared` library and have hardcoded `com.termux` values and will need to be manually patched.
- If forking termux plugins, check [Forking and Local Development](https://github.com/termux/termux-app/wiki/Termux-Libraries#forking-and-local-development) for info on how to use termux libraries for plugins.
##
## Sponsors and Funders
[<img alt="GitHub Accelerator" width="25%" src="site/assets/sponsors/github.png" />](https://github.com)
*[GitHub Accelerator](https://github.com/accelerator) ([1](https://github.blog/2023-04-12-github-accelerator-our-first-cohort-and-whats-next))*
&nbsp;
[<img alt="GitHub Secure Open Source Fund" width="25%" src="site/assets/sponsors/github.png" />](https://github.com)
*[GitHub Secure Open Source Fund](https://resources.github.com/github-secure-open-source-fund) ([1](https://github.blog/open-source/maintainers/securing-the-supply-chain-at-scale-starting-with-71-important-open-source-projects), [2](https://termux.dev/en/posts/general/2025/08/11/termux-selected-for-github-secure-open-source-fund-session-2.html))*
&nbsp;
[<img alt="NLnet NGI Mobifree" width="25%" src="site/assets/sponsors/nlnet-ngi-mobifree.png" />](https://nlnet.nl/mobifree)
*[NLnet NGI Mobifree](https://nlnet.nl/mobifree) ([1](https://nlnet.nl/news/2024/20241111-NGI-Mobifree-grants.html), [2](https://termux.dev/en/posts/general/2024/11/11/termux-selected-for-nlnet-ngi-mobifree-grant.html))*
&nbsp;
[<img alt="Cloudflare" width="25%" src="site/assets/sponsors/cloudflare.png" />](https://www.cloudflare.com)
*[Cloudflare](https://www.cloudflare.com) ([1](https://packages-cf.termux.dev))*

1
SECURITY.md Normal file
View File

@@ -0,0 +1 @@
Check https://termux.dev/security for info on Termux security policies and how to report vulnerabilities.

View File

@@ -10,16 +10,11 @@ ext {
// 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"
bootstrapMinSdk = packageVariant == "apt-android-5" ? 21 : 24
bootstrapMinRelease = packageVariant == "apt-android-5" ? "5.0" : "7.0"
bootstrapMaxSdk = packageVariant == "apt-android-5" ? 23 : null
bootstrapMaxRelease = packageVariant == "apt-android-5" ? "6.0" : null
buildMinSdk = bootstrapMinSdk
buildTargetSdk = project.properties.targetSdkVersion.toInteger()
}
android {
namespace "com.termux"
compileSdkVersion project.properties.compileSdkVersion.toInteger()
ndkVersion = System.getenv("JITPACK_NDK_VERSION") ?: project.properties.ndkVersion
def appVersionName = System.getenv("TERMUX_APP_VERSION_NAME") ?: ""
@@ -28,38 +23,32 @@ android {
def splitAPKsForReleaseBuilds = System.getenv("TERMUX_SPLIT_APKS_FOR_RELEASE_BUILDS") ?: "0" // F-Droid does not support split APKs #1904
dependencies {
implementation "androidx.annotation:annotation:1.3.0"
implementation "androidx.core:core:1.6.0"
implementation "androidx.drawerlayout:drawerlayout:1.1.1"
implementation "androidx.preference:preference:1.1.1"
implementation "androidx.annotation:annotation:1.9.0"
implementation "androidx.core:core:1.13.1"
implementation "androidx.drawerlayout:drawerlayout:1.2.0"
implementation "androidx.preference:preference:1.2.1"
implementation "androidx.viewpager:viewpager:1.0.0"
implementation "com.google.android.material:material:1.4.0"
implementation "com.google.android.material:material:1.12.0"
implementation "com.google.guava:guava:24.1-jre"
implementation "io.noties.markwon:core:$markwonVersion"
implementation "io.noties.markwon:ext-strikethrough:$markwonVersion"
implementation "io.noties.markwon:linkify:$markwonVersion"
implementation "io.noties.markwon:recycler:$markwonVersion"
implementation 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava'
implementation project(":terminal-view")
implementation project(":termux-shared")
}
defaultConfig {
applicationId "com.termux"
minSdk buildMinSdk
targetSdk buildTargetSdk
versionCode 1021
versionName "0.119.0-beta.2"
minSdkVersion project.properties.minSdkVersion.toInteger()
targetSdkVersion project.properties.targetSdkVersion.toInteger()
versionCode 1023
versionName "0.119.0-titan2.1"
if (appVersionName) versionName = appVersionName
validateVersionName(versionName)
buildConfigField "Integer", "TERMUX_APP__BOOTSTRAP_MIN_SDK", project.ext.bootstrapMinSdk.toString()
buildConfigField "String", "TERMUX_APP__BOOTSTRAP_MIN_RELEASE",
project.ext.bootstrapMinRelease ? "\"" + project.ext.bootstrapMinRelease + "\"" : "null"
buildConfigField "Integer", "TERMUX_APP__BOOTSTRAP_MAX_SDK", project.ext.bootstrapMaxSdk.toString()
buildConfigField "String", "TERMUX_APP__BOOTSTRAP_MAX_RELEASE",
project.ext.bootstrapMaxRelease ? "\"" + project.ext.bootstrapMaxRelease + "\"" : "null"
buildConfigField "String", "TERMUX_PACKAGE_VARIANT", "\"" + project.ext.packageVariant + "\"" // Used by TermuxApplication class
manifestPlaceholders.TERMUX_PACKAGE_NAME = "com.termux"
@@ -123,7 +112,7 @@ android {
}
}
lintOptions {
lint {
disable 'ProtectedPermissions'
}
@@ -151,6 +140,9 @@ android {
}
}
buildFeatures {
buildConfig true
}
}
dependencies {
@@ -227,17 +219,17 @@ task downloadBootstraps() {
doLast {
def packageVariant = project.ext.packageVariant
if (packageVariant == "apt-android-7") {
def version = "2025.03.28-r1" + "+" + packageVariant
downloadBootstrap("aarch64", "c8d702b6f742935001c37cda81b8ac69504a95d5cf28f2899532dd8cd4b057eb", version)
downloadBootstrap("arm", "f3bb9d1b32552b34fff41861dbf193ec5ba2848d67d779ac1c7256da6640f85d", version)
downloadBootstrap("i686", "36db3e1ac3547f9a174fd763bd9a484fa1a3449cdd81e1cf2408ff0454f839c6", version)
downloadBootstrap("x86_64", "1c124ec2396ee70a51b0b0a574f29aa659526aa2b9f558f993b2fb05d1e51855", version)
def version = "2026.02.12-r1" + "%2B" + "apt.android-7"
downloadBootstrap("aarch64", "ea2aeba8819e517db711f8c32369e89e7c52cee73e07930ff91185e1ab93f4f3", version)
downloadBootstrap("arm", "a38f4d3b2f735f83be2bf54eff463e86dc32a3e2f9f861c1557c4378d249c018", version)
downloadBootstrap("i686", "f5bc0b025b9f3b420b5fcaeefc064f888f5f22a0d6fd7090f4aac0c33eb3555b", version)
downloadBootstrap("x86_64", "b7fd0f2e3a4de534be3144f9f91acc768630fc463eaf134ab2e64c545e834f7a", version)
} else if (packageVariant == "apt-android-5") {
def version = "2025.03.28-r3" + "+" + packageVariant
downloadBootstrap("aarch64", "147c98e610a30588665a89776314833c293006b12c70e65dcde6eb54c2344113", version)
downloadBootstrap("arm", "363c28dd4b70c995302498beae79fb5917af7d3b0ca9fbd9da7de96ab64c6122", version)
downloadBootstrap("i686", "a2e742381ab24cf8c9a78ae4e2425ee44d8fb9625a2b0ef63e4cd32e292f7186", version)
downloadBootstrap("x86_64", "4f6866c222b0f1ae1b180220ffc6e8e1afbc10b9cbb7a462c062c490eda90044", version)
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 + "\"")
}

View File

@@ -10,8 +10,3 @@
-dontobfuscate
#-renamesourcefileattribute SourceFile
#-keepattributes SourceFile,LineNumberTable
# Temp fix for androidx.window:window:1.0.0-alpha09 imported by termux-shared
# https://issuetracker.google.com/issues/189001730
# https://android-review.googlesource.com/c/platform/frameworks/support/+/1757630
-keep class androidx.window.** { *; }

View File

@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.termux"
android:installLocation="internalOnly"
android:sharedUserId="${TERMUX_PACKAGE_NAME}"
android:sharedUserLabel="@string/shared_user_label">
@@ -42,7 +41,6 @@
android:name=".app.TermuxApplication"
android:allowBackup="false"
android:banner="@drawable/banner"
android:extractNativeLibs="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/application_name"
android:requestLegacyExternalStorage="true"

View File

@@ -774,7 +774,7 @@ public final class TermuxActivity extends AppCompatActivity implements ServiceCo
// If permission is granted, then also setup storage symlinks.
if(PermissionUtils.checkAndRequestLegacyOrManageExternalStoragePermission(
TermuxActivity.this, requestCode, true, !isPermissionCallback)) {
TermuxActivity.this, requestCode, !isPermissionCallback)) {
if (isPermissionCallback)
Logger.logInfoAndShowToast(TermuxActivity.this, LOG_TAG,
getString(com.termux.shared.R.string.msg_storage_permission_granted_on_request));

View File

@@ -52,7 +52,7 @@ public class TermuxApplication extends Application {
boolean isTermuxFilesDirectoryAccessible = error == null;
if (isTermuxFilesDirectoryAccessible) {
Logger.logInfo(LOG_TAG, "Termux files directory is accessible");
/*
error = TermuxFileUtils.isAppsTermuxAppDirectoryAccessible(true, true);
if (error != null) {
Logger.logErrorExtended(LOG_TAG, "Create apps/termux-app directory failed\n" + error);
@@ -61,7 +61,6 @@ public class TermuxApplication extends Application {
// Setup termux-am-socket server
TermuxAmSocketServer.setupTermuxAmSocketServer(context);
*/
} else {
Logger.logErrorExtended(LOG_TAG, "Termux files directory is not accessible\n" + error);
}

View File

@@ -10,12 +10,8 @@ import android.system.Os;
import android.util.Pair;
import android.view.WindowManager;
import com.termux.BuildConfig;
import com.termux.R;
import com.termux.shared.file.FileUtils;
import com.termux.shared.shell.command.ExecutionCommand;
import com.termux.shared.shell.command.runner.app.AppShell;
import com.termux.shared.termux.TermuxBootstrap;
import com.termux.shared.termux.crash.TermuxCrashUtils;
import com.termux.shared.termux.file.TermuxFileUtils;
import com.termux.shared.interact.MessageDialogUtils;
@@ -106,12 +102,6 @@ final class TermuxInstaller {
return;
}
if (!checkIfMinOrMaxSdkVersionIsIncompatible(activity,
BuildConfig.TERMUX_APP__BOOTSTRAP_MIN_SDK, BuildConfig.TERMUX_APP__BOOTSTRAP_MIN_RELEASE,
BuildConfig.TERMUX_APP__BOOTSTRAP_MAX_SDK, BuildConfig.TERMUX_APP__BOOTSTRAP_MAX_RELEASE)) {
return;
}
// If prefix directory exists, even if its a symlink to a valid directory and symlink is not broken/dangling
if (FileUtils.directoryFileExists(TERMUX_PREFIX_DIR_PATH, true)) {
if (TermuxFileUtils.isTermuxPrefixDirectoryEmpty()) {
@@ -205,8 +195,7 @@ 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.equals("etc/termux/bootstrap/termux-bootstrap-second-stage.sh")) {
zipEntryName.startsWith("lib/apt/apt-helper") || zipEntryName.startsWith("lib/apt/methods")) {
//noinspection OctalInteger
Os.chmod(targetFile.getAbsolutePath(), 0700);
}
@@ -227,35 +216,6 @@ final class TermuxInstaller {
throw new RuntimeException("Moving termux prefix staging to prefix directory failed");
}
// Run Termux bootstrap second stage.
String termuxBootstrapSecondStageFile = TERMUX_PREFIX_DIR_PATH + "/etc/termux/bootstrap/termux-bootstrap-second-stage.sh";
if (!FileUtils.fileExists(termuxBootstrapSecondStageFile, false)) {
Logger.logInfo(LOG_TAG, "Not running Termux bootstrap second stage since script not found at \"" + termuxBootstrapSecondStageFile + "\" path.");
} else {
if (!FileUtils.fileExists(TermuxConstants.TERMUX_BIN_PREFIX_DIR_PATH + "/bash", true)) {
Logger.logInfo(LOG_TAG, "Not running Termux bootstrap second stage since bash not found.");
}
Logger.logInfo(LOG_TAG, "Running Termux bootstrap second stage.");
ExecutionCommand executionCommand = new ExecutionCommand(-1,
termuxBootstrapSecondStageFile, null, null,
null, ExecutionCommand.Runner.APP_SHELL.getName(), false);
executionCommand.commandLabel = "Termux Bootstrap Second Stage Command";
executionCommand.backgroundCustomLogLevel = Logger.LOG_LEVEL_NORMAL;
AppShell appShell = AppShell.execute(activity, executionCommand, null, new TermuxShellEnvironment(), null, true);
if (appShell == null || !executionCommand.isSuccessful() || executionCommand.resultData.exitCode != 0) {
// Generate debug report before deleting broken prefix directory to get `stat` info at time of failure.
showBootstrapErrorDialog(activity, whenDone, MarkdownUtils.getMarkdownCodeForString(executionCommand.toString(), true));
// Delete prefix directory as otherwise when app is restarted, the broken prefix directory would be used and logged into.
Logger.logInfo(LOG_TAG, "Deleting broken termux prefix.");
error = FileUtils.deleteFile("termux prefix directory", TERMUX_PREFIX_DIR_PATH, true);
if (error != null)
Logger.logErrorExtended(LOG_TAG, error.toString());
return;
}
}
Logger.logInfo(LOG_TAG, "Bootstrap packages installed successfully.");
// Recreate env file since termux prefix was wiped earlier
@@ -279,42 +239,6 @@ final class TermuxInstaller {
}.start();
}
public static boolean checkIfMinOrMaxSdkVersionIsIncompatible(Activity activity,
Integer minSdk, String minRelease,
Integer maxSdk, String maxRelease) {
if (minSdk != null && Build.VERSION.SDK_INT < minSdk) {
String bootstrapErrorMessage = activity.getString(R.string.bootstrap_error_apk_bootstrap_variant_min_sdk_incompatible,
MarkdownUtils.getMarkdownCodeForString(TermuxBootstrap.TERMUX_APP_PACKAGE_VARIANT.getName(), false),
MarkdownUtils.getMarkdownCodeForString(Build.VERSION.RELEASE, false),
Build.VERSION.SDK_INT,
MarkdownUtils.getMarkdownCodeForString(minRelease, false),
minSdk);
Logger.logError(LOG_TAG, bootstrapErrorMessage);
sendBootstrapCrashReportNotification(activity, bootstrapErrorMessage);
MessageDialogUtils.exitAppWithErrorMessage(activity,
activity.getString(R.string.bootstrap_error_title),
bootstrapErrorMessage);
return false;
}
if (maxSdk != null && Build.VERSION.SDK_INT > maxSdk) {
String bootstrapErrorMessage = activity.getString(R.string.bootstrap_error_apk_bootstrap_variant_max_sdk_incompatible,
MarkdownUtils.getMarkdownCodeForString(TermuxBootstrap.TERMUX_APP_PACKAGE_VARIANT.getName(), false),
MarkdownUtils.getMarkdownCodeForString(Build.VERSION.RELEASE, false),
Build.VERSION.SDK_INT,
MarkdownUtils.getMarkdownCodeForString(maxRelease, false),
maxSdk);
Logger.logError(LOG_TAG, bootstrapErrorMessage);
sendBootstrapCrashReportNotification(activity, bootstrapErrorMessage);
MessageDialogUtils.exitAppWithErrorMessage(activity,
activity.getString(R.string.bootstrap_error_title),
bootstrapErrorMessage);
return false;
}
return true;
}
public static void showBootstrapErrorDialog(Activity activity, Runnable whenDone, String message) {
Logger.logErrorExtended(LOG_TAG, "Bootstrap Error:\n" + message);

View File

@@ -35,7 +35,6 @@ public final class HelpActivity extends AppCompatActivity {
mWebView = new WebView(this);
WebSettings settings = mWebView.getSettings();
settings.setCacheMode(WebSettings.LOAD_NO_CACHE);
settings.setAppCacheEnabled(false);
setContentView(progressLayout);
mWebView.clearCache(true);

View File

@@ -271,7 +271,7 @@ public class TermuxTerminalSessionActivityClient extends TermuxTerminalSessionCl
.setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION).build()).build();
try {
mBellSoundId = mBellSoundPool.load(mActivity, R.raw.bell, 1);
mBellSoundId = mBellSoundPool.load(mActivity, com.termux.shared.R.raw.bell, 1);
} catch (Exception e){
// Catch java.lang.RuntimeException: Unable to resume activity {com.termux/com.termux.app.TermuxActivity}: android.content.res.Resources$NotFoundException: File res/raw/bell.ogg from drawable resource ID
Logger.logStackTraceWithMessage(LOG_TAG, "Failed to load bell sound pool", e);

View File

@@ -735,8 +735,8 @@ public class TermuxTerminalViewClient extends TermuxTerminalViewClientBase {
MessageDialogUtils.showMessage(mActivity, TermuxConstants.TERMUX_APP_NAME + " Report Issue",
mActivity.getString(R.string.msg_add_termux_debug_info),
mActivity.getString(R.string.action_yes), (dialog, which) -> reportIssueFromTranscript(transcriptText, true),
mActivity.getString(R.string.action_no), (dialog, which) -> reportIssueFromTranscript(transcriptText, false),
mActivity.getString(com.termux.shared.R.string.action_yes), (dialog, which) -> reportIssueFromTranscript(transcriptText, true),
mActivity.getString(com.termux.shared.R.string.action_no), (dialog, which) -> reportIssueFromTranscript(transcriptText, false),
null);
}

View File

@@ -27,18 +27,10 @@
<!-- Termux Bootstrap Packages Installation -->
<string name="bootstrap_installer_body">Installing bootstrap packages…</string>
<string name="bootstrap_error_title">&TERMUX_APP_NAME; Bootstrap Error</string>
<string name="bootstrap_error_title">Unable to install bootstrap</string>
<string name="bootstrap_error_body">&TERMUX_APP_NAME; was unable to install the bootstrap packages.</string>
<string name="bootstrap_error_abort">Abort</string>
<string name="bootstrap_error_try_again">Try again</string>
<string name="bootstrap_error_apk_bootstrap_variant_min_sdk_incompatible">The APK bootstrap variant %1$s
of currently installed &TERMUX_APP_NAME; app is not compatible with the Android version %2$s
(sdk `%3$d`) of the device and it requires minimum Android version %4$s (sdk `%5$d`).
\n\nUninstall the &TERMUX_APP_NAME; app and reinstall the correct APK build variant.</string>
<string name="bootstrap_error_apk_bootstrap_variant_max_sdk_incompatible">The APK bootstrap variant %1$s
of currently installed &TERMUX_APP_NAME; app is not compatible with the Android version %2$s
(sdk `%3$d`) of the device and it requires maximum Android version %4$s (sdk `%5$d`).
\n\nUninstall the &TERMUX_APP_NAME; app and reinstall the correct APK build variant.</string>
<string name="bootstrap_error_not_primary_user_message">&TERMUX_APP_NAME; can only be run as the primary user.
\nBootstrap binaries compiled for &TERMUX_APP_NAME; have hardcoded $PREFIX path and cannot be installed
under any path other than:\n%1$s.</string>

View File

@@ -4,7 +4,7 @@ buildscript {
google()
}
dependencies {
classpath "com.android.tools.build:gradle:4.2.2"
classpath "com.android.tools.build:gradle:8.13.2"
}
}
@@ -15,7 +15,3 @@ allprojects {
maven { url "https://jitpack.io" }
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}

View File

@@ -12,18 +12,12 @@
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
org.gradle.jvmargs=-Xmx2048M \
--add-exports=java.base/sun.nio.ch=ALL-UNNAMED \
--add-opens=java.base/java.lang=ALL-UNNAMED \
--add-opens=java.base/java.lang.reflect=ALL-UNNAMED \
--add-opens=java.base/java.io=ALL-UNNAMED \
--add-exports=jdk.unsupported/sun.misc=ALL-UNNAMED
org.gradle.jvmargs=-Xmx2048M
android.useAndroidX=true
minSdkVersion=21
targetSdkVersion=28
ndkVersion=22.1.7171670
compileSdkVersion=30
ndkVersion=29.0.14206865
compileSdkVersion=36
markwonVersion=4.6.2

Binary file not shown.

View File

@@ -1,5 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

295
gradlew vendored
View File

@@ -1,7 +1,7 @@
#!/usr/bin/env sh
#!/bin/sh
#
# Copyright 2015 the original author or authors.
# Copyright © 2015 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -15,81 +15,114 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
##
## Gradle start up script for UN*X
##
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
MAX_FD=maximum
warn () {
echo "$*"
}
} >&2
die () {
echo
echo "$*"
echo
exit 1
}
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD="$JAVA_HOME/bin/java"
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
@@ -98,88 +131,118 @@ Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

40
gradlew.bat vendored
View File

@@ -13,8 +13,10 @@
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%" == "" @echo off
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@@ -25,7 +27,8 @@
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@@ -40,13 +43,13 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
@@ -56,32 +59,33 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal

View File

@@ -1,2 +1,4 @@
jdk:
- openjdk17
env:
JITPACK_NDK_VERSION: "21.1.6352462"
JITPACK_NDK_VERSION: "29.0.14206865"

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -2,6 +2,8 @@ apply plugin: 'com.android.library'
apply plugin: 'maven-publish'
android {
namespace "com.termux.emulator"
compileSdkVersion project.properties.compileSdkVersion.toInteger()
ndkVersion = System.getenv("JITPACK_NDK_VERSION") ?: project.properties.ndkVersion
@@ -41,6 +43,14 @@ android {
testOptions {
unitTests.returnDefaultValues = true
}
publishing {
multipleVariants {
withSourcesJar()
withJavadocJar()
allVariants()
}
}
}
tasks.withType(Test) {
@@ -50,13 +60,13 @@ tasks.withType(Test) {
}
dependencies {
implementation "androidx.annotation:annotation:1.3.0"
implementation "androidx.annotation:annotation:1.9.0"
testImplementation "junit:junit:4.13.2"
}
task sourceJar(type: Jar) {
from android.sourceSets.main.java.srcDirs
classifier "sources"
archiveClassifier = "sources"
}
afterEvaluate {
@@ -64,7 +74,7 @@ afterEvaluate {
publications {
// Creates a Maven publication called "release".
release(MavenPublication) {
from components.release
from components.default
groupId = 'com.termux'
artifactId = 'terminal-emulator'
version = '0.118.0'

View File

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

View File

@@ -120,8 +120,8 @@ public final class KeyHandler {
TERMCAP_TO_KEYCODE.put("kDN", KEYMOD_SHIFT | KEYCODE_DPAD_DOWN); // non-standard shifted arrow down
TERMCAP_TO_KEYCODE.put("kF", KEYMOD_SHIFT | KEYCODE_DPAD_DOWN); // terminfo=kind, scroll-forward key
TERMCAP_TO_KEYCODE.put("kI", KEYCODE_INSERT);
TERMCAP_TO_KEYCODE.put("kN", KEYCODE_PAGE_UP);
TERMCAP_TO_KEYCODE.put("kP", KEYCODE_PAGE_DOWN);
TERMCAP_TO_KEYCODE.put("kP", KEYCODE_PAGE_UP);
TERMCAP_TO_KEYCODE.put("kN", KEYCODE_PAGE_DOWN);
TERMCAP_TO_KEYCODE.put("kR", KEYMOD_SHIFT | KEYCODE_DPAD_UP); // terminfo=kri, scroll-backward key
TERMCAP_TO_KEYCODE.put("kUP", KEYMOD_SHIFT | KEYCODE_DPAD_UP); // non-standard shifted up

View File

@@ -41,7 +41,7 @@ public final class TerminalSession extends TerminalOutput {
* A queue written to from a separate thread when the process outputs, and read by main thread to process by
* terminal emulator.
*/
final ByteQueue mProcessToTerminalIOQueue = new ByteQueue(4096);
final ByteQueue mProcessToTerminalIOQueue = new ByteQueue(64 * 1024);
/**
* A queue written to from the main thread due to user interaction, and read by another thread which forwards by
* writing to the {@link #mTerminalFileDescriptor}.
@@ -336,7 +336,7 @@ public final class TerminalSession extends TerminalOutput {
@SuppressLint("HandlerLeak")
class MainThreadHandler extends Handler {
final byte[] mReceiveBuffer = new byte[4 * 1024];
final byte[] mReceiveBuffer = new byte[64 * 1024];
@Override
public void handleMessage(Message msg) {

View File

@@ -2,10 +2,11 @@ apply plugin: 'com.android.library'
apply plugin: 'maven-publish'
android {
namespace "com.termux.view"
compileSdkVersion project.properties.compileSdkVersion.toInteger()
dependencies {
implementation "androidx.annotation:annotation:1.3.0"
implementation "androidx.annotation:annotation:1.9.0"
api project(":terminal-emulator")
}
@@ -26,6 +27,14 @@ android {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
publishing {
multipleVariants {
withSourcesJar()
withJavadocJar()
allVariants()
}
}
}
dependencies {
@@ -34,7 +43,7 @@ dependencies {
task sourceJar(type: Jar) {
from android.sourceSets.main.java.srcDirs
classifier "sources"
archiveClassifier = "sources"
}
afterEvaluate {
@@ -42,7 +51,7 @@ afterEvaluate {
publications {
// Creates a Maven publication called "release".
release(MavenPublication) {
from components.release
from components.default
groupId = 'com.termux'
artifactId = 'terminal-view'
version = '0.118.0'
@@ -51,3 +60,4 @@ afterEvaluate {
}
}
}

View File

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

View File

@@ -68,6 +68,15 @@ public final class TerminalView extends View {
/** The top row of text to display. Ranges from -activeTranscriptRows to 0. */
int mTopRow;
/** TITAN2 PATCH: user scrolled away from the bottom; hold the viewport until they return. */
boolean mUserScrollLock;
/** TITAN2 PATCH: latched hardware Shift/Alt state. The Titan 2 keyboard layer re-injects
* synthetic key events (FLAG_FROM_SYSTEM) whose letter events often arrive without meta
* state, so the modifier state is tracked from the modifier key events themselves. */
boolean mHardwareShiftDown;
boolean mHardwareAltDown;
int[] mDefaultSelectors = new int[]{-1,-1,-1,-1};
float mScaleFactor = 1.f;
@@ -290,6 +299,9 @@ public final class TerminalView extends View {
public boolean attachSession(TerminalSession session) {
if (session == mTermSession) return false;
mTopRow = 0;
mUserScrollLock = false; // TITAN2 PATCH
mHardwareShiftDown = false; // TITAN2 PATCH
mHardwareAltDown = false; // TITAN2 PATCH
mTermSession = session;
mEmulator = null;
@@ -479,6 +491,17 @@ public final class TerminalView extends View {
mTopRow -= rowShift;
decrementYTextSelectionCursors(rowShift);
}
} else if (mUserScrollLock && mTopRow != 0) {
// TITAN2 PATCH: the user scrolled up, so hold the viewport instead of snapping
// back to the bottom on every screen update (e.g. while an app streams output).
// Keep the same content in view when new lines are appended.
int rowShift = mEmulator.getScrollCounter();
if (-mTopRow + rowShift > rowsInHistory) {
mTopRow = -rowsInHistory;
} else {
mTopRow -= rowShift;
}
skipScrolling = true;
}
if (!skipScrolling && mTopRow != 0) {
@@ -583,6 +606,9 @@ public final class TerminalView extends View {
handleKeyCode(up ? KeyEvent.KEYCODE_DPAD_UP : KeyEvent.KEYCODE_DPAD_DOWN, 0);
} else {
mTopRow = Math.min(0, Math.max(-(mEmulator.getScreen().getActiveTranscriptRows()), mTopRow + (up ? -1 : 1)));
// TITAN2 PATCH: scrolling away from the bottom pauses auto-follow;
// scrolling back to the bottom (mTopRow == 0) resumes it.
mUserScrollLock = mTopRow != 0;
if (!awakenScrollBars()) invalidate();
}
}
@@ -770,6 +796,21 @@ public final class TerminalView extends View {
if (TERMINAL_VIEW_KEY_LOGGING_ENABLED)
mClient.logInfo(LOG_TAG, "onKeyDown(keyCode=" + keyCode + ", isSystem()=" + event.isSystem() + ", event=" + event + ")");
if (mEmulator == null) return true;
// TITAN2 PATCH: latch hardware Shift/Alt state from the modifier key events
// themselves. The synthetic letter events injected by the Titan 2 keyboard layer
// are flagged FLAG_FROM_SYSTEM and often arrive without meta state.
switch (keyCode) {
case KeyEvent.KEYCODE_SHIFT_LEFT:
case KeyEvent.KEYCODE_SHIFT_RIGHT:
mHardwareShiftDown = true;
break;
case KeyEvent.KEYCODE_ALT_LEFT:
case KeyEvent.KEYCODE_ALT_RIGHT:
mHardwareAltDown = true;
break;
}
if (isSelectingText()) {
stopTextSelectionMode();
}
@@ -777,17 +818,24 @@ public final class TerminalView extends View {
if (mClient.onKeyDown(keyCode, event, mTermSession)) {
invalidate();
return true;
} else if (event.isSystem() && (!mClient.shouldBackButtonBeMappedToEscape() || keyCode != KeyEvent.KEYCODE_BACK)) {
} else if (event.isSystem() && isRealSystemKey(keyCode)
&& (!mClient.shouldBackButtonBeMappedToEscape() || keyCode != KeyEvent.KEYCODE_BACK)) {
// TITAN2 PATCH: only bubble *real* system keys (back, home, volume, ...). The
// Titan 2 keyboard layer re-injects letter events with FLAG_FROM_SYSTEM, which
// must reach the KCM path below instead of being dropped on the activity.
return super.onKeyDown(keyCode, event);
} else if (event.getAction() == KeyEvent.ACTION_MULTIPLE && keyCode == KeyEvent.KEYCODE_UNKNOWN) {
mTermSession.write(event.getCharacters());
return true;
} else if (keyCode == KeyEvent.KEYCODE_LANGUAGE_SWITCH) {
return super.onKeyDown(keyCode, event);
}
final int metaState = event.getMetaState();
final boolean controlDown = event.isCtrlPressed() || mClient.readControlKey();
final boolean leftAltDown = (metaState & KeyEvent.META_ALT_LEFT_ON) != 0 || mClient.readAltKey();
final boolean shiftDown = event.isShiftPressed() || mClient.readShiftKey();
// TITAN2 PATCH: also use the latched hardware modifier state (injected events lack meta).
final boolean leftAltDown = (metaState & KeyEvent.META_ALT_LEFT_ON) != 0 || mClient.readAltKey() || mHardwareAltDown;
final boolean shiftDown = event.isShiftPressed() || mClient.readShiftKey() || mHardwareShiftDown;
final boolean rightAltDownFromEvent = (metaState & KeyEvent.META_ALT_RIGHT_ON) != 0;
int keyMod = 0;
@@ -815,8 +863,19 @@ public final class TerminalView extends View {
if (mClient.readFnKey()) effectiveMetaState |= KeyEvent.META_FUNCTION_ON;
int result = event.getUnicodeChar(effectiveMetaState);
boolean altLayerUsed = false;
if (!rightAltDownFromEvent && leftAltDown && result != 0) {
// TITAN2 PATCH: consult the kcm alt layer (e.g. Alt+W -> '1' in TitanKey.kcm).
// The base lookup above strips left-alt from the meta state, so upstream never
// uses the alt layer and instead sends ESC + <char>.
int altLayerResult = event.getUnicodeChar(effectiveMetaState | KeyEvent.META_ALT_LEFT_ON | KeyEvent.META_ALT_ON);
if (altLayerResult != 0 && altLayerResult != result) {
result = altLayerResult;
altLayerUsed = true;
}
}
if (TERMINAL_VIEW_KEY_LOGGING_ENABLED)
mClient.logInfo(LOG_TAG, "KeyEvent#getUnicodeChar(" + effectiveMetaState + ") returned: " + result);
mClient.logInfo(LOG_TAG, "KeyEvent#getUnicodeChar(" + effectiveMetaState + ") returned: " + result + (altLayerUsed ? " (kcm alt layer)" : ""));
if (result == 0) {
return false;
}
@@ -833,7 +892,7 @@ public final class TerminalView extends View {
if (combinedChar > 0) result = combinedChar;
mCombiningAccent = 0;
}
inputCodePoint(event.getDeviceId(), result, controlDown, leftAltDown);
inputCodePoint(event.getDeviceId(), result, controlDown, leftAltDown, altLayerUsed);
}
if (mCombiningAccent != oldCombiningAccent) invalidate();
@@ -842,6 +901,10 @@ public final class TerminalView extends View {
}
public void inputCodePoint(int eventSource, int codePoint, boolean controlDownFromEvent, boolean leftAltDownFromEvent) {
inputCodePoint(eventSource, codePoint, controlDownFromEvent, leftAltDownFromEvent, false);
}
public void inputCodePoint(int eventSource, int codePoint, boolean controlDownFromEvent, boolean leftAltDownFromEvent, boolean altConsumedByKcm) {
if (TERMINAL_VIEW_KEY_LOGGING_ENABLED) {
mClient.logInfo(LOG_TAG, "inputCodePoint(eventSource=" + eventSource + ", codePoint=" + codePoint + ", controlDownFromEvent=" + controlDownFromEvent + ", leftAltDownFromEvent="
+ leftAltDownFromEvent + ")");
@@ -854,7 +917,11 @@ public final class TerminalView extends View {
mEmulator.setCursorBlinkState(true);
final boolean controlDown = controlDownFromEvent || mClient.readControlKey();
final boolean altDown = leftAltDownFromEvent || mClient.readAltKey();
// TITAN2 PATCH: if the kcm alt layer already produced the character (altConsumedByKcm),
// do not also send the ESC alt-prefix.
final boolean altDown = !altConsumedByKcm && (leftAltDownFromEvent || mClient.readAltKey());
mUserScrollLock = false; // TITAN2 PATCH: input returns the view to the live bottom
if (mClient.onCodePoint(codePoint, controlDown, mTermSession)) return;
@@ -919,6 +986,7 @@ public final class TerminalView extends View {
String code = KeyHandler.getCode(keyCode, keyMod, term.isCursorKeysApplicationMode(), term.isKeypadApplicationMode());
if (code == null) return false;
mTermSession.write(code);
mUserScrollLock = false; // TITAN2 PATCH: input returns the view to the live bottom
return true;
}
@@ -954,6 +1022,18 @@ public final class TerminalView extends View {
if (TERMINAL_VIEW_KEY_LOGGING_ENABLED)
mClient.logInfo(LOG_TAG, "onKeyUp(keyCode=" + keyCode + ", event=" + event + ")");
// TITAN2 PATCH: release latched hardware modifier state (see onKeyDown).
switch (keyCode) {
case KeyEvent.KEYCODE_SHIFT_LEFT:
case KeyEvent.KEYCODE_SHIFT_RIGHT:
mHardwareShiftDown = false;
break;
case KeyEvent.KEYCODE_ALT_LEFT:
case KeyEvent.KEYCODE_ALT_RIGHT:
mHardwareAltDown = false;
break;
}
// Do not return for KEYCODE_BACK and send it to the client since user may be trying
// to exit the activity.
if (mEmulator == null && keyCode != KeyEvent.KEYCODE_BACK) return true;
@@ -969,6 +1049,60 @@ public final class TerminalView extends View {
return true;
}
@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
super.onWindowFocusChanged(hasWindowFocus);
// TITAN2 PATCH: drop latched hardware modifier state when focus is lost, in case
// the keyup was delivered elsewhere (app switch, notification shade, dialog, ...).
if (!hasWindowFocus) {
mHardwareShiftDown = false;
mHardwareAltDown = false;
}
}
/**
* TITAN2 PATCH: mirror of the @hide {@code KeyEvent.isSystemKey(int)}. Used to tell
* real system keys (back, home, volume, ...) apart from the synthetic letter events
* that the Titan 2 keyboard layer injects with FLAG_FROM_SYSTEM.
*/
private static boolean isRealSystemKey(int keyCode) {
switch (keyCode) {
case KeyEvent.KEYCODE_MENU:
case KeyEvent.KEYCODE_SOFT_RIGHT:
case KeyEvent.KEYCODE_HOME:
case KeyEvent.KEYCODE_BACK:
case KeyEvent.KEYCODE_CALL:
case KeyEvent.KEYCODE_ENDCALL:
case KeyEvent.KEYCODE_VOLUME_UP:
case KeyEvent.KEYCODE_VOLUME_DOWN:
case KeyEvent.KEYCODE_MUTE:
case KeyEvent.KEYCODE_HEADSETHOOK:
case KeyEvent.KEYCODE_MEDIA_PLAY:
case KeyEvent.KEYCODE_MEDIA_PAUSE:
case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
case KeyEvent.KEYCODE_MEDIA_STOP:
case KeyEvent.KEYCODE_MEDIA_NEXT:
case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
case KeyEvent.KEYCODE_MEDIA_REWIND:
case KeyEvent.KEYCODE_MEDIA_RECORD:
case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
case KeyEvent.KEYCODE_CAMERA:
case KeyEvent.KEYCODE_FOCUS:
case KeyEvent.KEYCODE_SEARCH:
case KeyEvent.KEYCODE_BRIGHTNESS_DOWN:
case KeyEvent.KEYCODE_BRIGHTNESS_UP:
case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK:
case KeyEvent.KEYCODE_SLEEP:
case KeyEvent.KEYCODE_WAKEUP:
case KeyEvent.KEYCODE_VOICE_ASSIST:
case KeyEvent.KEYCODE_ASSIST:
case KeyEvent.KEYCODE_APP_SWITCH:
return true;
default:
return false;
}
}
/**
* This is called during layout when the size of this view has changed. If you were just added to the view
* hierarchy, you're called with the old values of 0.
@@ -998,6 +1132,7 @@ public final class TerminalView extends View {
mTerminalCursorBlinkerRunnable.setEmulator(mEmulator);
mTopRow = 0;
mUserScrollLock = false; // TITAN2 PATCH
scrollTo(0, 0);
invalidate();
}

View File

@@ -2,23 +2,24 @@ apply plugin: 'com.android.library'
apply plugin: 'maven-publish'
android {
namespace = "com.termux.shared"
compileSdkVersion project.properties.compileSdkVersion.toInteger()
ndkVersion = System.getenv("JITPACK_NDK_VERSION") ?: project.properties.ndkVersion
dependencies {
implementation "androidx.appcompat:appcompat:1.3.1"
implementation "androidx.annotation:annotation:1.3.0"
implementation "androidx.core:core:1.6.0"
implementation "com.google.android.material:material:1.4.0"
implementation "androidx.appcompat:appcompat:1.6.1"
implementation "androidx.annotation:annotation:1.9.0"
implementation "androidx.core:core:1.13.1"
implementation "com.google.android.material:material:1.12.0"
implementation "com.google.guava:guava:24.1-jre"
implementation "io.noties.markwon:core:$markwonVersion"
implementation "io.noties.markwon:ext-strikethrough:$markwonVersion"
implementation "io.noties.markwon:linkify:$markwonVersion"
implementation "io.noties.markwon:recycler:$markwonVersion"
implementation "org.lsposed.hiddenapibypass:hiddenapibypass:5.0"
implementation "org.lsposed.hiddenapibypass:hiddenapibypass:6.1"
// Do not increment version higher than 1.0.0-alpha09 since it will break ViewUtils and needs to be looked into
// noinspection GradleDependency
implementation "androidx.window:window:1.0.0-alpha09"
implementation "androidx.window:window:1.1.0"
// Do not increment version higher than 2.5 or there
// will be runtime exceptions on android < 8
@@ -31,6 +32,7 @@ android {
}
defaultConfig {
compileSdkVersion project.properties.compileSdkVersion.toInteger()
minSdkVersion project.properties.minSdkVersion.toInteger()
targetSdkVersion project.properties.targetSdkVersion.toInteger()
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -55,23 +57,31 @@ android {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
externalNativeBuild {
ndkBuild {
path file('src/main/cpp/Android.mk')
}
}
publishing {
multipleVariants {
withSourcesJar()
withJavadocJar()
allVariants()
}
}
}
dependencies {
testImplementation "junit:junit:4.13.2"
androidTestImplementation "androidx.test.ext:junit:1.1.3"
androidTestImplementation "androidx.test.espresso:espresso-core:3.4.0"
androidTestImplementation "androidx.test.ext:junit:1.1.5"
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:1.1.5"
}
task sourceJar(type: Jar) {
from android.sourceSets.main.java.srcDirs
classifier "sources"
archiveClassifier = "sources"
}
afterEvaluate {
@@ -79,7 +89,7 @@ afterEvaluate {
publications {
// Creates a Maven publication called "release".
release(MavenPublication) {
from components.release
from components.default
groupId = 'com.termux'
artifactId = 'termux-shared'
version = '0.118.0'

View File

@@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.termux.shared">
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.VIBRATE" />
</manifest>

View File

@@ -5,8 +5,6 @@ import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.os.Build;
import android.system.Os;
import android.system.OsConstants;
import androidx.annotation.NonNull;
@@ -160,19 +158,7 @@ public class AndroidUtils {
appendPropertyToMarkdown(markdownString, "BOARD", Build.BOARD);
appendPropertyToMarkdown(markdownString, "HARDWARE", Build.HARDWARE);
appendPropertyToMarkdown(markdownString, "DEVICE", Build.DEVICE);
appendPropertyToMarkdown(markdownString, "SUPPORTED_ABIS", Joiner.on(", ").skipNulls().join(Build.SUPPORTED_ABIS));
appendPropertyToMarkdown(markdownString, "SUPPORTED_32_BIT_ABIS", Joiner.on(", ").skipNulls().join(Build.SUPPORTED_32_BIT_ABIS));
appendPropertyToMarkdown(markdownString, "SUPPORTED_64_BIT_ABIS", Joiner.on(", ").skipNulls().join(Build.SUPPORTED_64_BIT_ABIS));
// If on Android >= 15
if (Build.VERSION.SDK_INT >= 35) {
try {
appendPropertyToMarkdownIfSet(markdownString, "PAGE_SIZE", Os.sysconf(OsConstants._SC_PAGESIZE));
} catch (Throwable t) {
// Ignore
}
}
markdownString.append("\n##\n");

View File

@@ -209,44 +209,31 @@ public class PermissionUtils {
/** If path is under primary external storage directory and storage permission is missing,
* then legacy or manage external storage permission will be requested from the user via a call
* to {@link #checkAndRequestLegacyOrManageExternalStoragePermission(Context, int, boolean, boolean)}.
* to {@link #checkAndRequestLegacyOrManageExternalStoragePermission(Context, int, boolean)}.
*
* @param context The context for operations.
* @param filePath The path to check.
* @param requestCode The request code to use while asking for permission.
* @param prioritizeManageExternalStoragePermission If {@link Manifest.permission#MANAGE_EXTERNAL_STORAGE}
* permission should be requested if on
* Android `>= 11` instead of getting legacy
* storage permission.
* @param showErrorMessage If an error message toast should be shown if permission is not granted.
* @return Returns {@code true} if permission is granted, otherwise {@code false}.
*/
@SuppressLint("SdCardPath")
public static boolean checkAndRequestLegacyOrManageExternalStoragePermissionIfPathOnPrimaryExternalStorage(
@NonNull Context context, String filePath, int requestCode,
boolean prioritizeManageExternalStoragePermission, boolean showErrorMessage) {
@NonNull Context context, String filePath, int requestCode, boolean showErrorMessage) {
// If path is under primary external storage directory, then check for missing permissions.
if (!FileUtils.isPathInDirPaths(filePath,
Arrays.asList(Environment.getExternalStorageDirectory().getAbsolutePath(), "/sdcard"), true))
return true;
return checkAndRequestLegacyOrManageExternalStoragePermission(context, requestCode, prioritizeManageExternalStoragePermission, showErrorMessage);
return checkAndRequestLegacyOrManageExternalStoragePermission(context, requestCode, showErrorMessage);
}
/**
* Check if legacy or manage external storage permissions has been granted.
*
* - If `prioritizeManageExternalStoragePermission` is `true and running on Android `>= 11`, then
* it will be checked if app has been granted the
* {@link Manifest.permission#MANAGE_EXTERNAL_STORAGE}.
* - If `prioritizeManageExternalStoragePermission` is `false` and running on Android `>= 11`, then
* if {@link #isLegacyExternalStoragePossible(Context)} returns `true`, them it will be
* checked if app has has been granted {@link Manifest.permission#READ_EXTERNAL_STORAGE} and
* {@link Manifest.permission#WRITE_EXTERNAL_STORAGE} permissions, otherwise it will be checked
* if app has been granted the {@link Manifest.permission#MANAGE_EXTERNAL_STORAGE} permission.
* - If running on Android `< 11`, then it will only be checked if app has been granted
* {@link Manifest.permission#READ_EXTERNAL_STORAGE} and
* {@link Manifest.permission#WRITE_EXTERNAL_STORAGE} permissions.
* Check if legacy or manage external storage permissions has been granted. If
* {@link #isLegacyExternalStoragePossible(Context)} returns {@code true}, them it will be
* checked if app has has been granted {@link Manifest.permission#READ_EXTERNAL_STORAGE} and
* {@link Manifest.permission#WRITE_EXTERNAL_STORAGE} permissions, otherwise it will be checked
* if app has been granted the {@link Manifest.permission#MANAGE_EXTERNAL_STORAGE} permission.
*
* If storage permission is missing, it will be requested from the user if {@code context} is an
* instance of {@link Activity} or {@link AppCompatActivity} and {@code requestCode}
@@ -269,34 +256,16 @@ public class PermissionUtils {
*}
* @param context The context for operations.
* @param requestCode The request code to use while asking for permission.
* @param prioritizeManageExternalStoragePermission If {@link Manifest.permission#MANAGE_EXTERNAL_STORAGE}
* permission should be requested if on
* Android `>= 11` instead of getting legacy
* storage permission.
* @param showErrorMessage If an error message toast should be shown if permission is not granted.
* @return Returns {@code true} if permission is granted, otherwise {@code false}.
*/
public static boolean checkAndRequestLegacyOrManageExternalStoragePermission(@NonNull Context context,
int requestCode,
boolean prioritizeManageExternalStoragePermission,
boolean showErrorMessage) {
Logger.logVerbose(LOG_TAG, "Checking storage permission");
String errmsg;
Boolean requestLegacyStoragePermission = null;
if (prioritizeManageExternalStoragePermission && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R)
requestLegacyStoragePermission = false;
if (requestLegacyStoragePermission == null)
requestLegacyStoragePermission = isLegacyExternalStoragePossible(context);
boolean requestLegacyStoragePermission = isLegacyExternalStoragePossible(context);
boolean checkIfHasRequestedLegacyExternalStorage = checkIfHasRequestedLegacyExternalStorage(context);
Logger.logVerbose(LOG_TAG, "prioritizeManageExternalStoragePermission=" + prioritizeManageExternalStoragePermission +
", requestLegacyStoragePermission=" + requestLegacyStoragePermission +
", checkIfHasRequestedLegacyExternalStorage=" + checkIfHasRequestedLegacyExternalStorage);
if (requestLegacyStoragePermission && checkIfHasRequestedLegacyExternalStorage) {
// Check if requestLegacyExternalStorage is set to true in app manifest
if (!hasRequestedLegacyExternalStorage(context, showErrorMessage))

View File

@@ -51,7 +51,7 @@ public class MessageDialogUtils {
final DialogInterface.OnClickListener onNegativeButton,
final DialogInterface.OnDismissListener onDismiss) {
AlertDialog.Builder builder = new AlertDialog.Builder(context, R.style.Theme_AppCompat_Light_Dialog);
AlertDialog.Builder builder = new AlertDialog.Builder(context, androidx.appcompat.R.style.Theme_AppCompat_Light_Dialog);
LayoutInflater inflater = (LayoutInflater) context.getSystemService( Context.LAYOUT_INFLATER_SERVICE );
View view = inflater.inflate(R.layout.dialog_show_message, null);

View File

@@ -1,5 +1,7 @@
package com.termux.shared.models;
import androidx.annotation.Keep;
import com.termux.shared.markdown.MarkdownUtils;
import com.termux.shared.android.AndroidUtils;
@@ -10,6 +12,25 @@ import java.io.Serializable;
*/
public class ReportInfo implements Serializable {
/**
* Explicitly define `serialVersionUID` to prevent exceptions on deserialization.
*
* Like when calling `Bundle.getSerializable()` on Android.
* `android.os.BadParcelableException: Parcelable encountered IOException reading a Serializable object` (name = <class_name>)
* `java.io.InvalidClassException: <class_name>; local class incompatible`
*
* The `@Keep` annotation is necessary to prevent the field from being removed by proguard when
* app is compiled, even if its kept during library compilation.
*
* **See Also:**
* - https://docs.oracle.com/javase/8/docs/platform/serialization/spec/version.html#a6678
* - https://docs.oracle.com/javase/8/docs/platform/serialization/spec/class.html#a4100
*/
@Keep
private static final long serialVersionUID = 1L;
/** The user action that was being processed for which the report was generated. */
public final String userAction;
/** The internal app component that sent the report. */

View File

@@ -3,6 +3,7 @@ package com.termux.shared.models;
import android.graphics.Color;
import android.graphics.Typeface;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
import com.termux.shared.activities.TextIOActivity;
@@ -19,6 +20,25 @@ import java.io.Serializable;
*/
public class TextIOInfo implements Serializable {
/**
* Explicitly define `serialVersionUID` to prevent exceptions on deserialization.
*
* Like when calling `Bundle.getSerializable()` on Android.
* `android.os.BadParcelableException: Parcelable encountered IOException reading a Serializable object` (name = <class_name>)
* `java.io.InvalidClassException: <class_name>; local class incompatible`
*
* The `@Keep` annotation is necessary to prevent the field from being removed by proguard when
* app is compiled, even if its kept during library compilation.
*
* **See Also:**
* - https://docs.oracle.com/javase/8/docs/platform/serialization/spec/version.html#a6678
* - https://docs.oracle.com/javase/8/docs/platform/serialization/spec/class.html#a4100
*/
@Keep
private static final long serialVersionUID = 1L;
public static final int GENERAL_DATA_SIZE_LIMIT_IN_BYTES = 1000;
public static final int LABEL_SIZE_LIMIT_IN_BYTES = 4000;
public static final int TEXT_SIZE_LIMIT_IN_BYTES = 100000 - GENERAL_DATA_SIZE_LIMIT_IN_BYTES - LABEL_SIZE_LIMIT_IN_BYTES; // < 100KB

View File

@@ -71,7 +71,9 @@ public class NotificationUtils {
Notification.Builder builder = new Notification.Builder(context);
builder.setContentTitle(title);
builder.setContentText(notificationText);
builder.setStyle(new Notification.BigTextStyle().bigText(notificationBigText));
if (notificationBigText != null) {
builder.setStyle(new Notification.BigTextStyle().bigText(notificationBigText));
}
builder.setContentIntent(contentIntent);
builder.setDeleteIntent(deleteIntent);

View File

@@ -1,7 +1,6 @@
package com.termux.shared.shell.command.environment;
import android.content.Context;
import android.os.Build;
import androidx.annotation.NonNull;
@@ -21,20 +20,6 @@ import java.util.HashMap;
*/
public class AndroidShellEnvironment extends UnixShellEnvironment {
/** Environment variable scope for Android. */
public static final String ANDROID_ENV_SCOPE = "ANDROID__"; // Default: "ANDROID__"
/**
* Environment variable for the Android build SDK version currently running on the device that
* is defined by {@link Build.VERSION#SDK_INT} and `ro.build.version.sdk` system property.
*
* - https://developer.android.com/reference/android/os/Build.VERSION#SDK_INT
* - https://developer.android.com/reference/android/os/Build.VERSION_CODES
*
* Default value: `ANDROID__BUILD_VERSION_SDK`
*/
public static final String ENV_ANDROID__BUILD_VERSION_SDK = ANDROID_ENV_SCOPE + "BUILD_VERSION_SDK";
protected ShellCommandShellEnvironment shellCommandShellEnvironment;
public AndroidShellEnvironment() {
@@ -76,8 +61,6 @@ public class AndroidShellEnvironment extends UnixShellEnvironment {
ShellEnvironmentUtils.putToEnvIfInSystemEnv(environment, "DEX2OATBOOTCLASSPATH");
ShellEnvironmentUtils.putToEnvIfInSystemEnv(environment, "SYSTEMSERVERCLASSPATH");
ShellEnvironmentUtils.putToEnvIfSet(environment, ENV_ANDROID__BUILD_VERSION_SDK, String.valueOf(Build.VERSION.SDK_INT));
return environment;
}

View File

@@ -33,13 +33,13 @@ public class ShellCommandShellEnvironment {
public static final String ENV_SHELL_CMD__APP_SHELL_NUMBER_SINCE_BOOT = SHELL_CMD_ENV_PREFIX + "APP_SHELL_NUMBER_SINCE_BOOT";
/** Environment variable for the {{@link ExecutionCommand.Runner#TERMINAL_SESSION} number since boot. */
public static final String ENV_SHELL_CMD__APP_TERMINAL_SESSION_NUMBER_SINCE_BOOT = SHELL_CMD_ENV_PREFIX + "APP_TERMINAL_SESSION_NUMBER_SINCE_BOOT";
public static final String ENV_SHELL_CMD__TERMINAL_SESSION_NUMBER_SINCE_BOOT = SHELL_CMD_ENV_PREFIX + "TERMINAL_SESSION_NUMBER_SINCE_BOOT";
/** Environment variable for the {@link ExecutionCommand.Runner#APP_SHELL} number since app start. */
public static final String ENV_SHELL_CMD__APP_SHELL_NUMBER_SINCE_APP_START = SHELL_CMD_ENV_PREFIX + "APP_SHELL_NUMBER_SINCE_APP_START";
/** Environment variable for the {@link ExecutionCommand.Runner#TERMINAL_SESSION} number since app start. */
public static final String ENV_SHELL_CMD__APP_TERMINAL_SESSION_NUMBER_SINCE_APP_START = SHELL_CMD_ENV_PREFIX + "APP_TERMINAL_SESSION_NUMBER_SINCE_APP_START";
public static final String ENV_SHELL_CMD__TERMINAL_SESSION_NUMBER_SINCE_APP_START = SHELL_CMD_ENV_PREFIX + "TERMINAL_SESSION_NUMBER_SINCE_APP_START";
/** Get shell environment containing info for {@link ExecutionCommand}. */

View File

@@ -326,7 +326,7 @@ public final class TermuxPropertyConstants {
/** Defines the key for extra keys */
public static final String KEY_EXTRA_KEYS = "extra-keys"; // Default: "extra-keys"
//public static final String DEFAULT_IVALUE_EXTRA_KEYS = "[[ESC, TAB, CTRL, ALT, {key: '-', popup: '|'}, DOWN, UP]]"; // Single row
public static final String DEFAULT_IVALUE_EXTRA_KEYS = "[['ESC',{key: 'DRAWER', popup: 'PASTE'},'SCROLL','HOME','UP','END','PGUP'], ['TAB','CTRL','ALT','LEFT','DOWN','RIGHT','PGDN']]"; // Double row
public static final String DEFAULT_IVALUE_EXTRA_KEYS = "[['ESC','/',{key: '-', popup: '|'},'HOME','UP','END','PGUP'], ['TAB','CTRL','ALT','LEFT','DOWN','RIGHT','PGDN']]"; // Double row
/** Defines the key for extra keys style */
public static final String KEY_EXTRA_KEYS_STYLE = "extra-keys-style"; // Default: "extra-keys-style"

View File

@@ -27,12 +27,6 @@ public class TermuxAppShellEnvironment {
/** Termux app environment variables. */
public static HashMap<String, String> termuxAppEnvironment;
/** Environment variable root scope. */
public static final String TERMUX_ENV__S_ROOT = "TERMUX_"; // Default: "TERMUX_"
/** Environment variable scope for Termux. */
public static final String TERMUX_ENV__S_TERMUX = TERMUX_ENV__S_ROOT + "_"; // Default: "TERMUX__"
/** Environment variable for the Termux app version. */
public static final String ENV_TERMUX_VERSION = TermuxConstants.TERMUX_ENV_PREFIX_ROOT + "_VERSION";
@@ -40,15 +34,15 @@ public class TermuxAppShellEnvironment {
public static final String TERMUX_APP_ENV_PREFIX = TermuxConstants.TERMUX_ENV_PREFIX_ROOT + "_APP__";
/** Environment variable for the Termux app version name. */
public static final String ENV_TERMUX_APP__APP_VERSION_NAME = TERMUX_APP_ENV_PREFIX + "APP_VERSION_NAME";
public static final String ENV_TERMUX_APP__VERSION_NAME = TERMUX_APP_ENV_PREFIX + "VERSION_NAME";
/** Environment variable for the Termux app version code. */
public static final String ENV_TERMUX_APP__APP_VERSION_CODE = TERMUX_APP_ENV_PREFIX + "APP_VERSION_CODE";
public static final String ENV_TERMUX_APP__VERSION_CODE = TERMUX_APP_ENV_PREFIX + "VERSION_CODE";
/** Environment variable for the Termux app package name. */
public static final String ENV_TERMUX_APP__PACKAGE_NAME = TERMUX_APP_ENV_PREFIX + "PACKAGE_NAME";
/** Environment variable for the Termux app process id. */
public static final String ENV_TERMUX_APP__PID = TERMUX_APP_ENV_PREFIX + "PID";
/** Environment variable for the Termux app uid. */
public static final String ENV_TERMUX__UID = TERMUX_ENV__S_TERMUX + "UID";
public static final String ENV_TERMUX_APP__UID = TERMUX_APP_ENV_PREFIX + "UID";
/** Environment variable for the Termux app targetSdkVersion. */
public static final String ENV_TERMUX_APP__TARGET_SDK = TERMUX_APP_ENV_PREFIX + "TARGET_SDK";
/** Environment variable for the Termux app is debuggable apk build. */
@@ -56,29 +50,28 @@ public class TermuxAppShellEnvironment {
/** Environment variable for the Termux app {@link TermuxConstants} APK_RELEASE_*. */
public static final String ENV_TERMUX_APP__APK_RELEASE = TERMUX_APP_ENV_PREFIX + "APK_RELEASE";
/** Environment variable for the Termux app install path. */
public static final String ENV_TERMUX_APP__APK_FILE = TERMUX_APP_ENV_PREFIX + "APK_FILE";
public static final String ENV_TERMUX_APP__APK_PATH = TERMUX_APP_ENV_PREFIX + "APK_PATH";
/** Environment variable for the Termux app is installed on external/portable storage. */
public static final String ENV_TERMUX_APP__IS_INSTALLED_ON_EXTERNAL_STORAGE = TERMUX_APP_ENV_PREFIX + "IS_INSTALLED_ON_EXTERNAL_STORAGE";
/** Environment variable for the Termux app process selinux context. */
public static final String ENV_TERMUX_APP__SE_PROCESS_CONTEXT = TERMUX_APP_ENV_PREFIX + "SE_PROCESS_CONTEXT";
/** Environment variable for the Termux app data files selinux context. */
public static final String ENV_TERMUX_APP__SE_FILE_CONTEXT = TERMUX_APP_ENV_PREFIX + "SE_FILE_CONTEXT";
/** Environment variable for the Termux app seInfo tag found in selinux policy used to set app process and app data files selinux context. */
public static final String ENV_TERMUX_APP__SE_INFO = TERMUX_APP_ENV_PREFIX + "SE_INFO";
/** Environment variable for the current Termux process selinux context. */
public static final String ENV_TERMUX__SE_PROCESS_CONTEXT = TERMUX_ENV__S_TERMUX + "SE_PROCESS_CONTEXT";
/** Environment variable for the Termux app user id. */
public static final String ENV_TERMUX__USER_ID = TERMUX_ENV__S_TERMUX + "USER_ID";
public static final String ENV_TERMUX_APP__USER_ID = TERMUX_APP_ENV_PREFIX + "USER_ID";
/** Environment variable for the Termux app profile owner. */
public static final String ENV_TERMUX__PROFILE_OWNER = TERMUX_ENV__S_TERMUX + "PROFILE_OWNER";
public static final String ENV_TERMUX_APP__PROFILE_OWNER = TERMUX_APP_ENV_PREFIX + "PROFILE_OWNER";
/** Environment variable for the Termux app {@link TermuxBootstrap#TERMUX_APP_PACKAGE_MANAGER}. */
public static final String ENV_TERMUX_APP__PACKAGE_MANAGER = TERMUX_APP_ENV_PREFIX + "PACKAGE_MANAGER";
/** Environment variable for the Termux app {@link TermuxBootstrap#TERMUX_APP_PACKAGE_VARIANT}. */
public static final String ENV_TERMUX_APP__PACKAGE_VARIANT = TERMUX_APP_ENV_PREFIX + "PACKAGE_VARIANT";
/** Environment variable for the Termux app files directory. */
public static final String ENV_TERMUX_APP__DATA_DIR = TERMUX_APP_ENV_PREFIX + "DATA_DIR";
public static final String ENV_TERMUX_APP__LEGACY_DATA_DIR = TERMUX_APP_ENV_PREFIX + "LEGACY_DATA_DIR";
public static final String ENV_TERMUX_APP__BUILD_DATA_DIR = TERMUX_APP_ENV_PREFIX + "BUILD_DATA_DIR";
public static final String ENV_TERMUX_APP__FILES_DIR = TERMUX_APP_ENV_PREFIX + "FILES_DIR";
/** Environment variable for the Termux app {@link TermuxAmSocketServer#getTermuxAppAMSocketServerEnabled(Context)}. */
public static final String ENV_TERMUX_APP__AM_SOCKET_SERVER_ENABLED = TERMUX_APP_ENV_PREFIX + "AM_SOCKET_SERVER_ENABLED";
@@ -112,22 +105,21 @@ public class TermuxAppShellEnvironment {
HashMap<String, String> environment = new HashMap<>();
ShellEnvironmentUtils.putToEnvIfSet(environment, ENV_TERMUX_VERSION, PackageUtils.getVersionNameForPackage(packageInfo));
ShellEnvironmentUtils.putToEnvIfSet(environment, ENV_TERMUX_APP__APP_VERSION_NAME, PackageUtils.getVersionNameForPackage(packageInfo));
ShellEnvironmentUtils.putToEnvIfSet(environment, ENV_TERMUX_APP__APP_VERSION_CODE, String.valueOf(PackageUtils.getVersionCodeForPackage(packageInfo)));
ShellEnvironmentUtils.putToEnvIfSet(environment, ENV_TERMUX_APP__VERSION_NAME, PackageUtils.getVersionNameForPackage(packageInfo));
ShellEnvironmentUtils.putToEnvIfSet(environment, ENV_TERMUX_APP__VERSION_CODE, String.valueOf(PackageUtils.getVersionCodeForPackage(packageInfo)));
ShellEnvironmentUtils.putToEnvIfSet(environment, ENV_TERMUX_APP__PACKAGE_NAME, packageName);
ShellEnvironmentUtils.putToEnvIfSet(environment, ENV_TERMUX_APP__PID, TermuxUtils.getTermuxAppPID(currentPackageContext));
ShellEnvironmentUtils.putToEnvIfSet(environment, ENV_TERMUX__UID, String.valueOf(PackageUtils.getUidForPackage(applicationInfo)));
ShellEnvironmentUtils.putToEnvIfSet(environment, ENV_TERMUX_APP__UID, String.valueOf(PackageUtils.getUidForPackage(applicationInfo)));
ShellEnvironmentUtils.putToEnvIfSet(environment, ENV_TERMUX_APP__TARGET_SDK, String.valueOf(PackageUtils.getTargetSDKForPackage(applicationInfo)));
ShellEnvironmentUtils.putToEnvIfSet(environment, ENV_TERMUX_APP__IS_DEBUGGABLE_BUILD, PackageUtils.isAppForPackageADebuggableBuild(applicationInfo));
ShellEnvironmentUtils.putToEnvIfSet(environment, ENV_TERMUX_APP__APK_FILE, PackageUtils.getBaseAPKPathForPackage(applicationInfo));
ShellEnvironmentUtils.putToEnvIfSet(environment, ENV_TERMUX_APP__APK_PATH, PackageUtils.getBaseAPKPathForPackage(applicationInfo));
ShellEnvironmentUtils.putToEnvIfSet(environment, ENV_TERMUX_APP__IS_INSTALLED_ON_EXTERNAL_STORAGE, PackageUtils.isAppInstalledOnExternalStorage(applicationInfo));
putTermuxAPKSignature(currentPackageContext, environment);
Context termuxPackageContext = TermuxUtils.getTermuxPackageContext(currentPackageContext);
if (termuxPackageContext != null) {
/*
// An app that does not have the same sharedUserId as termux app will not be able to get
// get termux context's classloader to get BuildConfig.TERMUX_PACKAGE_VARIANT via reflection.
// Check TermuxBootstrap.setTermuxPackageManagerAndVariantFromTermuxApp()
@@ -135,28 +127,24 @@ public class TermuxAppShellEnvironment {
environment.put(ENV_TERMUX_APP__PACKAGE_MANAGER, TermuxBootstrap.TERMUX_APP_PACKAGE_MANAGER.getName());
if (TermuxBootstrap.TERMUX_APP_PACKAGE_VARIANT != null)
environment.put(ENV_TERMUX_APP__PACKAGE_VARIANT, TermuxBootstrap.TERMUX_APP_PACKAGE_VARIANT.getName());
*/
/*
// Will not be set for plugins
ShellEnvironmentUtils.putToEnvIfSet(environment, ENV_TERMUX_APP__AM_SOCKET_SERVER_ENABLED,
TermuxAmSocketServer.getTermuxAppAMSocketServerEnabled(currentPackageContext));
*/
ShellEnvironmentUtils.putToEnvIfSet(environment, ENV_TERMUX_APP__DATA_DIR, applicationInfo.dataDir);
ShellEnvironmentUtils.putToEnvIfSet(environment, ENV_TERMUX_APP__LEGACY_DATA_DIR, "/data/data/" + applicationInfo.packageName);
ShellEnvironmentUtils.putToEnvIfSet(environment, ENV_TERMUX_APP__BUILD_DATA_DIR, TermuxConstants.TERMUX_INTERNAL_PRIVATE_APP_DATA_DIR_PATH);
String filesDirPath = currentPackageContext.getFilesDir().getAbsolutePath();
ShellEnvironmentUtils.putToEnvIfSet(environment, ENV_TERMUX_APP__FILES_DIR, filesDirPath);
ShellEnvironmentUtils.putToEnvIfSet(environment, ENV_TERMUX__SE_PROCESS_CONTEXT, SELinuxUtils.getContext());
ShellEnvironmentUtils.putToEnvIfSet(environment, ENV_TERMUX_APP__SE_FILE_CONTEXT, SELinuxUtils.getFileContext(applicationInfo.dataDir));
ShellEnvironmentUtils.putToEnvIfSet(environment, ENV_TERMUX_APP__SE_PROCESS_CONTEXT, SELinuxUtils.getContext());
ShellEnvironmentUtils.putToEnvIfSet(environment, ENV_TERMUX_APP__SE_FILE_CONTEXT, SELinuxUtils.getFileContext(filesDirPath));
String seInfoUser = PackageUtils.getApplicationInfoSeInfoUserForPackage(applicationInfo);
ShellEnvironmentUtils.putToEnvIfSet(environment, ENV_TERMUX_APP__SE_INFO, PackageUtils.getApplicationInfoSeInfoForPackage(applicationInfo) +
(DataUtils.isNullOrEmpty(seInfoUser) ? "" : seInfoUser));
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
ShellEnvironmentUtils.putToEnvIfSet(environment, ENV_TERMUX__USER_ID, String.valueOf(PackageUtils.getUserIdForPackage(currentPackageContext)));
ShellEnvironmentUtils.putToEnvIfSet(environment, ENV_TERMUX__PROFILE_OWNER, PackageUtils.getProfileOwnerPackageNameForUser(currentPackageContext));
ShellEnvironmentUtils.putToEnvIfSet(environment, ENV_TERMUX_APP__USER_ID, String.valueOf(PackageUtils.getUserIdForPackage(currentPackageContext)));
ShellEnvironmentUtils.putToEnvIfSet(environment, ENV_TERMUX_APP__PROFILE_OWNER, PackageUtils.getProfileOwnerPackageNameForUser(currentPackageContext));
}
termuxAppEnvironment = environment;

View File

@@ -34,9 +34,9 @@ public class TermuxShellCommandShellEnvironment extends ShellCommandShellEnviron
String.valueOf(TermuxShellManager.getAndIncrementAppShellNumberSinceAppStart()));
} else if (ExecutionCommand.Runner.TERMINAL_SESSION.equalsRunner(executionCommand.runner)) {
ShellEnvironmentUtils.putToEnvIfSet(environment, ENV_SHELL_CMD__APP_TERMINAL_SESSION_NUMBER_SINCE_BOOT,
ShellEnvironmentUtils.putToEnvIfSet(environment, ENV_SHELL_CMD__TERMINAL_SESSION_NUMBER_SINCE_BOOT,
String.valueOf(preferences.getAndIncrementTerminalSessionNumberSinceBoot()));
ShellEnvironmentUtils.putToEnvIfSet(environment, ENV_SHELL_CMD__APP_TERMINAL_SESSION_NUMBER_SINCE_APP_START,
ShellEnvironmentUtils.putToEnvIfSet(environment, ENV_SHELL_CMD__TERMINAL_SESSION_NUMBER_SINCE_APP_START,
String.valueOf(TermuxShellManager.getAndIncrementTerminalSessionNumberSinceAppStart()));
} else {
return environment;

View File

@@ -1,12 +1,9 @@
package com.termux.shared.termux.shell.command.environment;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import androidx.annotation.NonNull;
import com.termux.shared.android.PackageUtils;
import com.termux.shared.errors.Error;
import com.termux.shared.file.FileUtils;
import com.termux.shared.logger.Logger;
@@ -74,25 +71,12 @@ public class TermuxShellEnvironment extends AndroidShellEnvironment {
if (termuxAppEnvironment != null)
environment.putAll(termuxAppEnvironment);
/*
HashMap<String, String> termuxApiAppEnvironment = TermuxAPIShellEnvironment.getEnvironment(currentPackageContext);
if (termuxApiAppEnvironment != null)
environment.putAll(termuxApiAppEnvironment);
*/
ApplicationInfo applicationInfo = PackageUtils.getApplicationInfoForPackage(currentPackageContext, TermuxConstants.TERMUX_PACKAGE_NAME);
if (applicationInfo != null && !applicationInfo.enabled) {
applicationInfo = null;
}
if (applicationInfo != null) {
environment.put("TERMUX__APPS_DIR", applicationInfo.dataDir + "/termux/apps");
}
environment.put("TERMUX__ROOTFS", TermuxConstants.TERMUX_FILES_DIR_PATH);
environment.put(ENV_HOME, TermuxConstants.TERMUX_HOME_DIR_PATH);
environment.put("TERMUX__HOME", TermuxConstants.TERMUX_HOME_DIR_PATH);
environment.put(ENV_PREFIX, TermuxConstants.TERMUX_PREFIX_DIR_PATH);
environment.put("TERMUX__PREFIX", TermuxConstants.TERMUX_PREFIX_DIR_PATH);
// If failsafe is not enabled, then we keep default PATH and TMPDIR so that system binaries can be used
if (!isFailSafe) {

View File

@@ -185,12 +185,11 @@ public class ViewUtils {
public static Point getDisplaySize( @NonNull Context context, boolean activitySize) {
// android.view.WindowManager.getDefaultDisplay() and Display.getSize() are deprecated in
// API 30 and give wrong values in API 30 for activitySize=false in multi-window
androidx.window.WindowManager windowManager = new androidx.window.WindowManager(context);
androidx.window.WindowMetrics windowMetrics;
androidx.window.layout.WindowMetrics windowMetrics;
if (activitySize)
windowMetrics = windowManager.getCurrentWindowMetrics();
windowMetrics = androidx.window.layout.WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(context);
else
windowMetrics = windowManager.getMaximumWindowMetrics();
windowMetrics = androidx.window.layout.WindowMetricsCalculator.getOrCreate().computeMaximumWindowMetrics(context);
return new Point(windowMetrics.getBounds().width(), windowMetrics.getBounds().height());
}