Compare commits

...

29 Commits

Author SHA1 Message Date
agnostic-apollo
43317b78c9 Release: v0.118.1
The `versionCode` has been bumped to `1000` so that users who have installed from F-Droid or GitHub should not have the app attempted to be updated by Google PlayStore and failing and also shown in PlayStore app updates list due to non-collaborative `v0.120` app release on PlayStore that set the `versionCode` higher than the latest F-Droid or GitHub `118` release. Unlike F-Droid, PlayStore does not check for difference in app APK signature before attempting to download and then failing to install due to signature mismatch.

- https://github.com/termux/termux-app/discussions/4000
- https://github.com/termux/termux-app/issues/4012
2024-06-18 04:10:09 +05:00
agnostic-apollo
2a008d836e Changed: Update support and donate users to termux.dev domain 2024-06-18 04:00:51 +05:00
agnostic-apollo
daa7ca4d43 Changed: Bump apt-android-7 bootstraps to 2024.06.17-r1 2024-06-18 03:55:30 +05:00
agnostic-apollo
2c82a5581f Added: Add support for Termux bootstrap second stage by running termux-bootstrap-second-stage.sh
- 7827140577
- 7827140577/scripts/bootstrap/termux-bootstrap-second-stage.sh
2024-06-18 03:55:30 +05:00
agnostic-apollo
708281cea2 Changed: Bump actions/checkout and actions/upload-artifact to v4 2024-06-18 03:36:37 +05:00
agnostic-apollo
eb0cb408a4 Changed: Use GitHub cli instead of hub for uploading GitHub release files as later has been removed from runner images
- https://github.com/actions/runner-images/issues/8362
2024-06-18 03:32:45 +05:00
agnostic-apollo
d11c95b996 Added: Add support for GitHub action builds for github-releases/** branches 2024-06-18 03:28:50 +05:00
agnostic-apollo
9735ae284d Added: Request SET_ALARM permission to allow broadcasting an intent to set an alarm or timer in an alarm clock app
- https://developer.android.com/reference/android/Manifest.permission#SET_ALARM
- https://developer.android.com/reference/android/provider/AlarmClock

Closes #3990
2024-06-18 03:28:17 +05:00
agnostic-apollo
0813e46330 Fixed: Limit max combining characters in TerminalRow to 15 characters to prevent buffer overflows
The exception below causing app crash happens because of malicious input where combining characters keep getting added to same column of the row and this increases the size of `mSpaceUsed` and `mText`, eventually causing a buffer overflow of `mSpaceUsed`, which is limited to max `32767` value as per java `short` limit, but the limit itself isn't the issue, but an endless number of combining characters being added. Check `MAX_COMBINING_CHARACTERS_PER_COLUMN` field javadocs for why the limit `15` was chosen.

```
curl -o matroska.js https://kimapr.net/lappy/matroska.js
cat matroska.js
```

The `charCount` below refers to value of `Character.charCount(codePoint)`, like before `oldCharactersUsedForColumn` is appended to `newCharactersUsedForColumn`.

```
TerminalRow: codePoint=112, mColumns=98, mText=637, columnToSet=18, mSpaceUsed=590, javaCharDifference=0, oldStartOfColumnIndex=510, oldCharactersUsedForColumn=1, newCharactersUsedForColumn=1, oldNextColumnIndex=511, newNextColumnIndex=511, charCount=1, oldCodePointDisplayWidth=1, newCodePointDisplayWidth=1
TerminalRow: codePoint=40, mColumns=98, mText=637, columnToSet=19, mSpaceUsed=590, javaCharDifference=0, oldStartOfColumnIndex=511, oldCharactersUsedForColumn=1, newCharactersUsedForColumn=1, oldNextColumnIndex=512, newNextColumnIndex=512, charCount=1, oldCodePointDisplayWidth=1, newCodePointDisplayWidth=1
TerminalRow: codePoint=40, mColumns=98, mText=637, columnToSet=20, mSpaceUsed=590, javaCharDifference=0, oldStartOfColumnIndex=512, oldCharactersUsedForColumn=1, newCharactersUsedForColumn=1, oldNextColumnIndex=513, newNextColumnIndex=513, charCount=1, oldCodePointDisplayWidth=1, newCodePointDisplayWidth=1
TerminalRow: codePoint=101, mColumns=98, mText=637, columnToSet=21, mSpaceUsed=590, javaCharDifference=0, oldStartOfColumnIndex=513, oldCharactersUsedForColumn=1, newCharactersUsedForColumn=1, oldNextColumnIndex=514, newNextColumnIndex=514, charCount=1, oldCodePointDisplayWidth=1, newCodePointDisplayWidth=1
TerminalRow: codePoint=917772, mColumns=98, mText=147, columnToSet=18, mSpaceUsed=98, javaCharDifference=2, oldStartOfColumnIndex=18, oldCharactersUsedForColumn=1, newCharactersUsedForColumn=3, oldNextColumnIndex=19, newNextColumnIndex=21, charCount=2, oldCodePointDisplayWidth=1, newCodePointDisplayWidth=0
I TerminalRow: codePoint=65024, mColumns=98, mText=147, columnToSet=18, mSpaceUsed=100, javaCharDifference=1, oldStartOfColumnIndex=18, oldCharactersUsedForColumn=3, newCharactersUsedForColumn=4, oldNextColumnIndex=21, newNextColumnIndex=22, charCount=1, oldCodePointDisplayWidth=1, newCodePointDisplayWidth=0
TerminalRow: codePoint=917772, mColumns=98, mText=147, columnToSet=18, mSpaceUsed=101, javaCharDifference=2, oldStartOfColumnIndex=18, oldCharactersUsedForColumn=4, newCharactersUsedForColumn=6, oldNextColumnIndex=22, newNextColumnIndex=24, charCount=2, oldCodePointDisplayWidth=1, newCodePointDisplayWidth=0
...
TerminalRow: codePoint=917959, mColumns=98, mText=32781, columnToSet=18, mSpaceUsed=32763, javaCharDifference=2, oldStartOfColumnIndex=18, oldCharactersUsedForColumn=32666, newCharactersUsedForColumn=32668, oldNextColumnIndex=32684, newNextColumnIndex=32686, charCount=2, oldCodePointDisplayWidth=1, newCodePointDisplayWidth=0
TerminalRow: codePoint=917939, mColumns=98, mText=32781, columnToSet=18, mSpaceUsed=32765, javaCharDifference=2, oldStartOfColumnIndex=18, oldCharactersUsedForColumn=32668, newCharactersUsedForColumn=32670, oldNextColumnIndex=32686, newNextColumnIndex=32688, charCount=2, oldCodePointDisplayWidth=1, newCodePointDisplayWidth=0
TerminalRow: codePoint=917961, mColumns=98, mText=32781, columnToSet=18, mSpaceUsed=32767, javaCharDifference=2, oldStartOfColumnIndex=18, oldCharactersUsedForColumn=32670, newCharactersUsedForColumn=32672, oldNextColumnIndex=32688, newNextColumnIndex=32690, charCount=2, oldCodePointDisplayWidth=1, newCodePointDisplayWidth=0
TerminalRow: codePoint=917804, mColumns=98, mText=32781, columnToSet=18, mSpaceUsed=-32767, javaCharDifference=2, oldStartOfColumnIndex=18, oldCharactersUsedForColumn=1, newCharactersUsedForColumn=3, oldNextColumnIndex=19, newNextColumnIndex=21, charCount=2, oldCodePointDisplayWidth=1, newCodePointDisplayWidth=0
```

```
java.lang.ArrayIndexOutOfBoundsException: src.length=32781 srcPos=19 dst.length=32781 dstPos=21 length=-32786
	at java.lang.System.arraycopy(System.java:469)
	at com.termux.terminal.TerminalRow.setChar(TerminalRow.java:196)
	at com.termux.terminal.TerminalBuffer.setChar(TerminalBuffer.java:455)
	at com.termux.terminal.TerminalEmulator.emitCodePoint(TerminalEmulator.java:2380)
	at com.termux.terminal.TerminalEmulator.processCodePoint(TerminalEmulator.java:624)
	at com.termux.terminal.TerminalEmulator.processByte(TerminalEmulator.java:520)
	at com.termux.terminal.TerminalEmulator.append(TerminalEmulator.java:487)
	at com.termux.terminal.TerminalSession$MainThreadHandler.handleMessage(TerminalSession.java:358)
	at android.os.Handler.dispatchMessage(Handler.java:106)
	at android.os.Looper.loop(Looper.java:223)
	at android.app.ActivityThread.main(ActivityThread.java:7664)
	at java.lang.reflect.Method.invoke(Native Method)
	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
```

See also following links for history of related changes to `TerminalRow` for combining characters. Note that jackpal terminal does not crash for above, which termux-app is based on, but changes were done by fornwall in initial commit of termux-app to change the behaviour, hence the crash, but he added the `FIXME: Put a limit of combining characters` comment as a note to solve the current issue in future, which is now.

- 9a47042620
- https://github.com/jackpal/Android-Terminal-Emulator/pull/338
- a18ee58f7a (diff-f84d215b18106c037e01986a3968fa54b74691174a78fcc99493f745d3805be5)

Closes #3839
2024-06-18 03:20:11 +05:00
Lucy Phipps
6ece249c03 WcWidth.c: fix 2nd typo 2024-06-18 03:20:11 +05:00
Lucy Phipps
fc8245bba3 WcWidth.c: fix typo 2024-06-18 03:20:11 +05:00
Lucy Phipps
63833d9c2d update WcWidth.java to Unicode 15.0.0 2024-06-18 03:20:11 +05:00
agnostic-apollo
c9e2a75e82 Fixed: Fix shared terminal transcript joining back lines
Regression of 370ac2bd caused in 5f71e3e7 by the (in)famous @trygveaa
2024-06-18 03:20:11 +05:00
agnostic-apollo
9433f10757 Fixed: Ensure CSI parameter value is not greater than 9999 as per vt510 2024-06-18 03:20:11 +05:00
agnostic-apollo
fbf55fd40c Fixed: Fix CSI parameters parsing like for SGR sequences that start with a ; or have sequential ; characters
https://vt100.net/docs/vt510-rm/chapter4.html#S4.3.3

https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_(Control_Sequence_Introducer)_sequences

Credits for finding the issue belongs to @Screwtapello

https://github.com/mawww/kakoune/issues/4339#issuecomment-916980723

Closes #2272, Closes mawww/kakoune#4339
2024-06-18 03:20:10 +05:00
agnostic-apollo
160ab68e5b Changed: Use black or white cursor color based on terminal background instead of always white if colors.properties didn't have cursor color set
Credit for algorithm link belong to @Jamie-Landeg-Jones

Closes #2653
2024-06-18 03:20:10 +05:00
agnostic-apollo
903f2496cb Fixed: Fix message dialog button text not showing in day mode due to white text 2024-06-18 03:20:10 +05:00
agnostic-apollo
2dc7381b89 Fixed: Fix wrong input type selected if toolbar is switched back to extra keys after tapping terminal if in text input mode
Closes #2503
2024-06-18 03:20:10 +05:00
agnostic-apollo
e55639e41d Fixed: Change extra keys and terminal input view background to black
Required for day/night theming and should fix issues where both views were translucent with light terminal color themes.
2024-06-18 03:20:10 +05:00
agnostic-apollo
087da0b576 Fixed: Fix issue where a colour tint/highlight would be added to the terminal on activity re-creation
The fix in c6b4114f was not working for it.
2024-06-18 03:20:10 +05:00
agnostic-apollo
d7f22982a1 Fixed: Fix termux app restarting on samsung dex version < 3.0 when switching modes 2024-06-18 03:20:10 +05:00
agnostic-apollo
f222315b0f Fixed: Fix ArrayIndexOutOfBoundsException when setting zero width terminal character
java.lang.ArrayIndexOutOfBoundsException: length=64; index=-1
at com.termux.terminal.TerminalRow.setChar(TerminalRow.java:127)
at com.termux.terminal.TerminalBuffer.setChar(TerminalBuffer.java:413)
at com.termux.terminal.TerminalEmulator.emitCodePoint(TerminalEmulator.java:2329)
at com.termux.terminal.TerminalEmulator.processCodePoint(TerminalEmulator.java:617)
at com.termux.terminal.TerminalEmulator.processByte(TerminalEmulator.java:513)
at com.termux.terminal.TerminalEmulator.append(TerminalEmulator.java:480)
at com.termux.terminal.TerminalSession$MainThreadHandler.handleMessage(TerminalSession.java:339)
at android.os.Handler.dispatchMessage(Handler.java:110)
at android.os.Looper.loop(Looper.java:219)
at android.app.ActivityThread.main(ActivityThread.java:8349)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:513)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1055)
2024-06-18 03:20:10 +05:00
agnostic-apollo
e11bcfc9a1 Fixed: Log exception instead of crashing app on NumberFormatException for invalid termcap/terminfo string requested
java.lang.NumberFormatException: For input string: " a"
at java.lang.Long.parseLong(Long.java:583)
at java.lang.Long.valueOf(Long.java:781)
at java.lang.Long.decode(Long.java:933)
at com.termux.terminal.TerminalEmulator.doDeviceControl(TerminalEmulator.java:940)
at com.termux.terminal.TerminalEmulator.processCodePoint(TerminalEmulator.java:813)
2024-06-18 03:20:09 +05:00
agnostic-apollo
e3a50cbf32 Fixed: Use android.util.Log for terminal-emulator logging if TerminalSessionClient is null like when running tests 2024-06-18 03:20:09 +05:00
agnostic-apollo
af5fef4c4a Fixed: Fix CSI Delete Ps Column(s) (DECDC)
Firstly, `TerminalBuffer.blockSet()` was throwing the exception since `sx + w > mColumns` which was technically passed by TerminalEmulator.blockClear()`. Actual value would be `mCursorRow + columnsToMove + columnsToDelete > mColumns`.

Secondly, the call to `blockClear()` should not be needed since it the `blockCopy()` would overwrite the columns to be deleted on copy.

Run `printf "\e['~"` to delete 1 column and `printf "\e[3'~"` to delete 3 columns. Run `printf "\e[3'}"` to insert 2 columns.

java.lang.IllegalArgumentException: Illegal arguments! blockSet(78, 0, 1, 30, 32, 56, 30)
at com.termux.terminal.TerminalBuffer.blockSet(TerminalBuffer.java:397)
at com.termux.terminal.TerminalEmulator.blockClear(TerminalEmulator.java:2035)
at com.termux.terminal.TerminalEmulator.processCodePoint(TerminalEmulator.java:799)
2024-06-18 03:20:09 +05:00
agnostic-apollo
03e31d190d Fixed: Fix ArrayIndexOutOfBoundsException thrown because length was less than 0 when selecting text from terminal buffer
java.lang.ArrayIndexOutOfBoundsException: src.length=132 srcPos=90 dst.length=16 dstPos=0 length=-2
at java.lang.System.arraycopy(System.java:469)
at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:597)
at java.lang.StringBuilder.append(StringBuilder.java:191)
at com.termux.terminal.TerminalBuffer.getSelectedText(TerminalBuffer.java:97)
at com.termux.terminal.TerminalBuffer.getSelectedText(TerminalBuffer.java:57)
at com.termux.terminal.TerminalBuffer.getSelectedText(TerminalBuffer.java:53)
at com.termux.terminal.TerminalEmulator.getSelectedText(TerminalEmulator.java:2401)
at com.termux.view.textselection.TextSelectionCursorController$1.onActionItemClicked(TextSelectionCursorController.java:140)
2024-06-18 03:20:09 +05:00
agnostic-apollo
d24a04a10d Fixed: Fix issue where menu wouldn't show when text on bottom row of terminal was selected
Closes #2233
2024-06-18 03:20:09 +05:00
agnostic-apollo
aee0da49a0 Changed: Do not show toast if text null or empty 2024-06-18 03:20:09 +05:00
agnostic-apollo
87c8f3d35a Fixed: Fix NullPointerException when getting spanned markdown like for notification 2024-06-18 03:20:09 +05:00
24 changed files with 469 additions and 126 deletions

View File

@@ -58,19 +58,18 @@ jobs:
"${APK_BASENAME_PREFIX}_armeabi-v7a.apk" \ "${APK_BASENAME_PREFIX}_armeabi-v7a.apk" \
"${APK_BASENAME_PREFIX}_x86_64.apk" \ "${APK_BASENAME_PREFIX}_x86_64.apk" \
"${APK_BASENAME_PREFIX}_x86.apk" \ "${APK_BASENAME_PREFIX}_x86.apk" \
> sha256sums); then > "${APK_BASENAME_PREFIX}_sha256sums"); then
exit_on_error "Generate sha25sums failed for '$RELEASE_VERSION_NAME' release." exit_on_error "Generate sha25sums failed for '$RELEASE_VERSION_NAME' release."
fi fi
echo "Attaching APKs to github release" echo "Attaching APKs to github release"
if ! hub release edit \ if ! gh release upload "$RELEASE_VERSION_NAME" \
-m "" \ "$APK_DIR_PATH/${APK_BASENAME_PREFIX}_universal.apk" \
-a "$APK_DIR_PATH/${APK_BASENAME_PREFIX}_universal.apk" \ "$APK_DIR_PATH/${APK_BASENAME_PREFIX}_arm64-v8a.apk" \
-a "$APK_DIR_PATH/${APK_BASENAME_PREFIX}_arm64-v8a.apk" \ "$APK_DIR_PATH/${APK_BASENAME_PREFIX}_armeabi-v7a.apk" \
-a "$APK_DIR_PATH/${APK_BASENAME_PREFIX}_armeabi-v7a.apk" \ "$APK_DIR_PATH/${APK_BASENAME_PREFIX}_x86_64.apk" \
-a "$APK_DIR_PATH/${APK_BASENAME_PREFIX}_x86_64.apk" \ "$APK_DIR_PATH/${APK_BASENAME_PREFIX}_x86.apk" \
-a "$APK_DIR_PATH/${APK_BASENAME_PREFIX}_x86.apk" \ "$APK_DIR_PATH/${APK_BASENAME_PREFIX}_sha256sums" \
-a "$APK_DIR_PATH/sha256sums" \ ; then
"$RELEASE_VERSION_NAME"; then
exit_on_error "Attach APKs to release failed for '$RELEASE_VERSION_NAME' release." exit_on_error "Attach APKs to release failed for '$RELEASE_VERSION_NAME' release."
fi fi

View File

@@ -4,6 +4,7 @@ on:
push: push:
branches: branches:
- master - master
- 'github-releases/**'
pull_request: pull_request:
branches: branches:
- master - master
@@ -13,7 +14,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Clone repository - name: Clone repository
uses: actions/checkout@v2 uses: actions/checkout@v4
- name: Build APKs - name: Build APKs
shell: bash {0} shell: bash {0}
@@ -65,7 +66,7 @@ jobs:
fi fi
- name: Attach universal APK file - name: Attach universal APK file
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v4
with: with:
name: ${{ env.APK_BASENAME_PREFIX }}_universal name: ${{ env.APK_BASENAME_PREFIX }}_universal
path: | path: |
@@ -73,7 +74,7 @@ jobs:
${{ env.APK_DIR_PATH }}/output-metadata.json ${{ env.APK_DIR_PATH }}/output-metadata.json
- name: Attach arm64-v8a APK file - name: Attach arm64-v8a APK file
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v4
with: with:
name: ${{ env.APK_BASENAME_PREFIX }}_arm64-v8a name: ${{ env.APK_BASENAME_PREFIX }}_arm64-v8a
path: | path: |
@@ -81,7 +82,7 @@ jobs:
${{ env.APK_DIR_PATH }}/output-metadata.json ${{ env.APK_DIR_PATH }}/output-metadata.json
- name: Attach armeabi-v7a APK file - name: Attach armeabi-v7a APK file
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v4
with: with:
name: ${{ env.APK_BASENAME_PREFIX }}_armeabi-v7a name: ${{ env.APK_BASENAME_PREFIX }}_armeabi-v7a
path: | path: |
@@ -89,7 +90,7 @@ jobs:
${{ env.APK_DIR_PATH }}/output-metadata.json ${{ env.APK_DIR_PATH }}/output-metadata.json
- name: Attach x86_64 APK file - name: Attach x86_64 APK file
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v4
with: with:
name: ${{ env.APK_BASENAME_PREFIX }}_x86_64 name: ${{ env.APK_BASENAME_PREFIX }}_x86_64
path: | path: |
@@ -97,7 +98,7 @@ jobs:
${{ env.APK_DIR_PATH }}/output-metadata.json ${{ env.APK_DIR_PATH }}/output-metadata.json
- name: Attach x86 APK file - name: Attach x86 APK file
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v4
with: with:
name: ${{ env.APK_BASENAME_PREFIX }}_x86 name: ${{ env.APK_BASENAME_PREFIX }}_x86
path: | path: |
@@ -105,7 +106,7 @@ jobs:
${{ env.APK_DIR_PATH }}/output-metadata.json ${{ env.APK_DIR_PATH }}/output-metadata.json
- name: Attach sha256sums file - name: Attach sha256sums file
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v4
with: with:
name: sha256sums name: sha256sums
path: | path: |

View File

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

View File

@@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Clone repository - name: Clone repository
uses: actions/checkout@v2 uses: actions/checkout@v4
- name: Execute tests - name: Execute tests
run: | run: |
./gradlew test ./gradlew test

View File

@@ -145,7 +145,7 @@ The main ones are the following.
- [Termux Matrix Channel](https://matrix.to/#termux_termux:gitter.im) - [Termux Matrix Channel](https://matrix.to/#termux_termux:gitter.im)
- [Termux Dev Matrix Channel](https://matrix.to/#termux_dev:gitter.im) - [Termux Dev Matrix Channel](https://matrix.to/#termux_dev:gitter.im)
- [Termux Twitter](https://twitter.com/termux/) - [Termux Twitter](https://twitter.com/termux/)
- [Termux Reports Email](mailto:termuxreports@groups.io) - [Termux Reports Email](mailto:support@termux.dev)
### Wikis ### Wikis

View File

@@ -30,8 +30,8 @@ android {
applicationId "com.termux" applicationId "com.termux"
minSdkVersion project.properties.minSdkVersion.toInteger() minSdkVersion project.properties.minSdkVersion.toInteger()
targetSdkVersion project.properties.targetSdkVersion.toInteger() targetSdkVersion project.properties.targetSdkVersion.toInteger()
versionCode 118 versionCode 1000
versionName "0.118.0" versionName "0.118.1"
if (appVersionName) versionName = appVersionName if (appVersionName) versionName = appVersionName
validateVersionName(versionName) validateVersionName(versionName)
@@ -195,11 +195,11 @@ clean {
task downloadBootstraps() { task downloadBootstraps() {
doLast { doLast {
def version = "2022.01.07-r1" def version = "2024.06.17-r1+apt-android-7"
downloadBootstrap("aarch64", "0fe6d0159d12fcb8baf7750ce9072b9b36f742662b02ad4da145ab85873614cd", version) downloadBootstrap("aarch64", "91a90661597fe14bb3c3563f5f65b243c0baaec42f2bc3d2243ff459e3942fb6", version)
downloadBootstrap("arm", "0a6014e2ec3b7079524fee3caabd02be05bcb4add3c6cd9e5ad98408b428c717", version) downloadBootstrap("arm", "d54b5eb2a305d72f267f9704deaca721b2bebbd3d4cca134aec31da719707997", version)
downloadBootstrap("i686", "c55369a9af1316dc3d99457aa23cce64b29c1e60e375159352c0d4b9cfed4ac6", version) downloadBootstrap("i686", "06a51ac1c679d68d52045509f1a705622c8f41748ef753660e31e3b6a846eba2", version)
downloadBootstrap("x86_64", "e93a7c66d15edb7d1b5ceca906255c85ead35a8b6a52f93524e117cfb07e6778", version) downloadBootstrap("x86_64", "4c8e43474c8d9543e01d4cbf3c4d7f59bbe4d696c38f6dece2b6ab3ba8881f2e", version)
} }
} }

View File

@@ -33,6 +33,7 @@
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" /> <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" tools:ignore="ProtectedPermissions" /> <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" tools:ignore="ProtectedPermissions" />
<uses-permission android:name="com.android.alarm.permission.SET_ALARM" />
<application <application
android:name=".app.TermuxApplication" android:name=".app.TermuxApplication"
@@ -180,13 +181,24 @@
<meta-data <meta-data
android:name="android.max_aspect" android:name="android.max_aspect"
android:value="10.0" /> android:value="10.0" />
<!-- https://developer.samsung.com/samsung-dex/modify-optimizing.html -->
<!-- Version < 3.0. DeX Mode and Screen Mirroring support -->
<meta-data <meta-data
android:name="com.sec.android.support.multiwindow" android:name="com.samsung.android.keepalive.density"
android:value="true" /> android:value="true" />
<!-- Version >= 3.0. DeX Dual Mode support -->
<meta-data <meta-data
android:name="com.samsung.android.multidisplay.keep_process_alive" android:name="com.samsung.android.multidisplay.keep_process_alive"
android:value="true" /> android:value="true" />
<meta-data
android:name="com.sec.android.support.multiwindow"
android:value="true" />
</application> </application>
</manifest> </manifest>

View File

@@ -16,8 +16,11 @@ import com.termux.shared.file.TermuxFileUtils;
import com.termux.shared.interact.MessageDialogUtils; import com.termux.shared.interact.MessageDialogUtils;
import com.termux.shared.logger.Logger; import com.termux.shared.logger.Logger;
import com.termux.shared.markdown.MarkdownUtils; import com.termux.shared.markdown.MarkdownUtils;
import com.termux.shared.models.ExecutionCommand;
import com.termux.shared.models.errors.Error; import com.termux.shared.models.errors.Error;
import com.termux.shared.packages.PackageUtils; import com.termux.shared.packages.PackageUtils;
import com.termux.shared.shell.TermuxShellEnvironmentClient;
import com.termux.shared.shell.TermuxTask;
import com.termux.shared.termux.TermuxConstants; import com.termux.shared.termux.TermuxConstants;
import com.termux.shared.termux.TermuxUtils; import com.termux.shared.termux.TermuxUtils;
@@ -187,7 +190,8 @@ final class TermuxInstaller {
outStream.write(buffer, 0, readBytes); outStream.write(buffer, 0, readBytes);
} }
if (zipEntryName.startsWith("bin/") || zipEntryName.startsWith("libexec") || if (zipEntryName.startsWith("bin/") || zipEntryName.startsWith("libexec") ||
zipEntryName.startsWith("lib/apt/apt-helper") || zipEntryName.startsWith("lib/apt/methods")) { zipEntryName.startsWith("lib/apt/apt-helper") || zipEntryName.startsWith("lib/apt/methods") ||
zipEntryName.equals("etc/termux/bootstrap/termux-bootstrap-second-stage.sh")) {
//noinspection OctalInteger //noinspection OctalInteger
Os.chmod(targetFile.getAbsolutePath(), 0700); Os.chmod(targetFile.getAbsolutePath(), 0700);
} }
@@ -208,6 +212,28 @@ final class TermuxInstaller {
throw new RuntimeException("Moving termux prefix staging to prefix directory failed"); throw new RuntimeException("Moving termux prefix staging to prefix directory failed");
} }
// Run Termux bootstrap second stage
Logger.logInfo(LOG_TAG, "Running Termux bootstrap second stage.");
String termuxBootstrapSecondStageFile = TERMUX_PREFIX_DIR_PATH + "/etc/termux/bootstrap/termux-bootstrap-second-stage.sh";
if (FileUtils.fileExists(termuxBootstrapSecondStageFile, false)) {
ExecutionCommand executionCommand = new ExecutionCommand(-1,
termuxBootstrapSecondStageFile, null, null,
null, true, false);
executionCommand.commandLabel = "Termux Bootstrap Second Stage Command";
executionCommand.backgroundCustomLogLevel = Logger.LOG_LEVEL_NORMAL;
TermuxTask termuxTask = TermuxTask.execute(activity, executionCommand, null, new TermuxShellEnvironmentClient(), true);
boolean stderrSet = !executionCommand.resultData.stderr.toString().isEmpty();
if (termuxTask == null || !executionCommand.isSuccessful() || executionCommand.resultData.exitCode != 0 || stderrSet) {
// Delete prefix directory as otherwise when app is restarted, the broken prefix directory would be used and logged into
error = FileUtils.deleteFile("termux prefix directory", TERMUX_PREFIX_DIR_PATH, true);
if (error != null)
Logger.logErrorExtended(LOG_TAG, error.toString());
showBootstrapErrorDialog(activity, whenDone, MarkdownUtils.getMarkdownCodeForString(executionCommand.toString(), true));
return;
}
}
Logger.logInfo(LOG_TAG, "Bootstrap packages installed successfully."); Logger.logInfo(LOG_TAG, "Bootstrap packages installed successfully.");
activity.runOnUiThread(whenDone); activity.runOnUiThread(whenDone);

View File

@@ -213,7 +213,7 @@ public class TermuxTerminalViewClient extends TermuxTerminalViewClientBase {
@Override @Override
public boolean isTerminalViewSelected() { public boolean isTerminalViewSelected() {
return mActivity.getTerminalToolbarViewPager() == null || mActivity.isTerminalViewSelected(); return mActivity.getTerminalToolbarViewPager() == null || mActivity.isTerminalViewSelected() || mActivity.getTerminalView().hasFocus();
} }
@@ -541,7 +541,8 @@ public class TermuxTerminalViewClient extends TermuxTerminalViewClientBase {
// disabled or hidden at startup, otherwise if hardware keyboard is attached and user // disabled or hidden at startup, otherwise if hardware keyboard is attached and user
// starts typing on hardware keyboard without tapping on the terminal first, then a colour // starts typing on hardware keyboard without tapping on the terminal first, then a colour
// tint will be added to the terminal as highlight for the focussed view. Test with a light // tint will be added to the terminal as highlight for the focussed view. Test with a light
// theme. // theme. For android 8.+, the "defaultFocusHighlightEnabled" attribute is also set to false
// in TerminalView layout to fix the issue.
// If soft keyboard is disabled by user for Termux (check function docs for Termux behaviour info) // If soft keyboard is disabled by user for Termux (check function docs for Termux behaviour info)
if (KeyboardUtils.shouldSoftKeyboardBeDisabled(mActivity, if (KeyboardUtils.shouldSoftKeyboardBeDisabled(mActivity,

View File

@@ -1,4 +1,6 @@
<com.termux.app.terminal.TermuxActivityRootView xmlns:android="http://schemas.android.com/apk/res/android" <com.termux.app.terminal.TermuxActivityRootView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_termux_root_view" android:id="@+id/activity_termux_root_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
@@ -25,11 +27,13 @@
android:id="@+id/terminal_view" android:id="@+id/terminal_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:defaultFocusHighlightEnabled="false"
android:focusableInTouchMode="true" android:focusableInTouchMode="true"
android:scrollbarThumbVertical="@drawable/terminal_scroll_shape" android:scrollbarThumbVertical="@drawable/terminal_scroll_shape"
android:scrollbars="vertical" android:scrollbars="vertical"
android:importantForAutofill="no" android:importantForAutofill="no"
android:autofillHints="password" /> android:autofillHints="password"
tools:ignore="UnusedAttribute" />
<LinearLayout <LinearLayout
android:id="@+id/left_drawer" android:id="@+id/left_drawer"
@@ -96,7 +100,7 @@
android:visibility="gone" android:visibility="gone"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="37.5dp" android:layout_height="37.5dp"
android:background="@android:drawable/screen_background_dark_transparent" android:background="@color/black"
android:layout_alignParentBottom="true" /> android:layout_alignParentBottom="true" />
</RelativeLayout> </RelativeLayout>

View File

@@ -0,0 +1,80 @@
package com.termux.terminal;
import android.util.Log;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
public class Logger {
public static void logError(TerminalSessionClient client, String logTag, String message) {
if (client != null)
client.logError(logTag, message);
else
Log.e(logTag, message);
}
public static void logWarn(TerminalSessionClient client, String logTag, String message) {
if (client != null)
client.logWarn(logTag, message);
else
Log.w(logTag, message);
}
public static void logInfo(TerminalSessionClient client, String logTag, String message) {
if (client != null)
client.logInfo(logTag, message);
else
Log.i(logTag, message);
}
public static void logDebug(TerminalSessionClient client, String logTag, String message) {
if (client != null)
client.logDebug(logTag, message);
else
Log.d(logTag, message);
}
public static void logVerbose(TerminalSessionClient client, String logTag, String message) {
if (client != null)
client.logVerbose(logTag, message);
else
Log.v(logTag, message);
}
public static void logStackTraceWithMessage(TerminalSessionClient client, String tag, String message, Throwable throwable) {
logError(client, tag, getMessageAndStackTraceString(message, throwable));
}
public static String getMessageAndStackTraceString(String message, Throwable throwable) {
if (message == null && throwable == null)
return null;
else if (message != null && throwable != null)
return message + ":\n" + getStackTraceString(throwable);
else if (throwable == null)
return message;
else
return getStackTraceString(throwable);
}
public static String getStackTraceString(Throwable throwable) {
if (throwable == null) return null;
String stackTraceString = null;
try {
StringWriter errors = new StringWriter();
PrintWriter pw = new PrintWriter(errors);
throwable.printStackTrace(pw);
pw.close();
stackTraceString = errors.toString();
errors.close();
} catch (IOException e) {
e.printStackTrace();
}
return stackTraceString;
}
}

View File

@@ -54,7 +54,7 @@ public final class TerminalBuffer {
} }
public String getSelectedText(int selX1, int selY1, int selX2, int selY2, boolean joinBackLines) { public String getSelectedText(int selX1, int selY1, int selX2, int selY2, boolean joinBackLines) {
return getSelectedText(selX1, selY1, selX2, selY2, true, false); return getSelectedText(selX1, selY1, selX2, selY2, joinBackLines, false);
} }
public String getSelectedText(int selX1, int selY1, int selX2, int selY2, boolean joinBackLines, boolean joinFullLines) { public String getSelectedText(int selX1, int selY1, int selX2, int selY2, boolean joinBackLines, boolean joinFullLines) {
@@ -93,8 +93,11 @@ public final class TerminalBuffer {
if (c != ' ') lastPrintingCharIndex = i; if (c != ' ') lastPrintingCharIndex = i;
} }
} }
if (lastPrintingCharIndex != -1)
builder.append(line, x1Index, lastPrintingCharIndex - x1Index + 1); int len = lastPrintingCharIndex - x1Index + 1;
if (lastPrintingCharIndex != -1 && len > 0)
builder.append(line, x1Index, len);
boolean lineFillsWidth = lastPrintingCharIndex == x2Index - 1; boolean lineFillsWidth = lastPrintingCharIndex == x2Index - 1;
if ((!joinBackLines || !rowLineWrap) && (!joinFullLines || !lineFillsWidth) if ((!joinBackLines || !rowLineWrap) && (!joinFullLines || !lineFillsWidth)
&& row < selY2 && row < mScreenRows - 1) builder.append('\n'); && row < selY2 && row < mScreenRows - 1) builder.append('\n');
@@ -446,8 +449,8 @@ public final class TerminalBuffer {
} }
public void setChar(int column, int row, int codePoint, long style) { public void setChar(int column, int row, int codePoint, long style) {
if (row >= mScreenRows || column >= mColumns) if (row < 0 || row >= mScreenRows || column < 0 || column >= mColumns)
throw new IllegalArgumentException("row=" + row + ", column=" + column + ", mScreenRows=" + mScreenRows + ", mColumns=" + mColumns); throw new IllegalArgumentException("TerminalBuffer.setChar(): row=" + row + ", column=" + column + ", mScreenRows=" + mScreenRows + ", mColumns=" + mColumns);
row = externalToInternalRow(row); row = externalToInternalRow(row);
allocateFullLineIfNecessary(row).setChar(column, codePoint, style); allocateFullLineIfNecessary(row).setChar(column, codePoint, style);
} }

View File

@@ -71,6 +71,7 @@ public final class TerminalColorScheme {
public void updateWith(Properties props) { public void updateWith(Properties props) {
reset(); reset();
boolean cursorPropExists = false;
for (Map.Entry<Object, Object> entries : props.entrySet()) { for (Map.Entry<Object, Object> entries : props.entrySet()) {
String key = (String) entries.getKey(); String key = (String) entries.getKey();
String value = (String) entries.getValue(); String value = (String) entries.getValue();
@@ -82,6 +83,7 @@ public final class TerminalColorScheme {
colorIndex = TextStyle.COLOR_INDEX_BACKGROUND; colorIndex = TextStyle.COLOR_INDEX_BACKGROUND;
} else if (key.equals("cursor")) { } else if (key.equals("cursor")) {
colorIndex = TextStyle.COLOR_INDEX_CURSOR; colorIndex = TextStyle.COLOR_INDEX_CURSOR;
cursorPropExists = true;
} else if (key.startsWith("color")) { } else if (key.startsWith("color")) {
try { try {
colorIndex = Integer.parseInt(key.substring(5)); colorIndex = Integer.parseInt(key.substring(5));
@@ -98,6 +100,27 @@ public final class TerminalColorScheme {
mDefaultColors[colorIndex] = colorValue; mDefaultColors[colorIndex] = colorValue;
} }
if (!cursorPropExists)
setCursorColorForBackground();
}
/**
* If the "cursor" color is not set by user, we need to decide on the appropriate color that will
* be visible on the current terminal background. White will not be visible on light backgrounds
* and black won't be visible on dark backgrounds. So we find the perceived brightness of the
* background color and if its below the threshold (too dark), we use white cursor and if its
* above (too bright), we use black cursor.
*/
public void setCursorColorForBackground() {
int backgroundColor = mDefaultColors[TextStyle.COLOR_INDEX_BACKGROUND];
int brightness = TerminalColors.getPerceivedBrightnessOfColor(backgroundColor);
if (brightness > 0) {
if (brightness < 130)
mDefaultColors[TextStyle.COLOR_INDEX_CURSOR] = 0xffffffff;
else
mDefaultColors[TextStyle.COLOR_INDEX_CURSOR] = 0xff000000;
}
} }
} }

View File

@@ -1,5 +1,7 @@
package com.termux.terminal; package com.termux.terminal;
import android.graphics.Color;
/** Current terminal colors (if different from default). */ /** Current terminal colors (if different from default). */
public final class TerminalColors { public final class TerminalColors {
@@ -73,4 +75,22 @@ public final class TerminalColors {
if (c != 0) mCurrentColors[intoIndex] = c; if (c != 0) mCurrentColors[intoIndex] = c;
} }
/**
* Get the perceived brightness of the color based on its RGB components.
*
* https://www.nbdtech.com/Blog/archive/2008/04/27/Calculating-the-Perceived-Brightness-of-a-Color.aspx
* http://alienryderflex.com/hsp.html
*
* @param color The color code int.
* @return Returns value between 0-255.
*/
public static int getPerceivedBrightnessOfColor(int color) {
return (int)
Math.floor(Math.sqrt(
Math.pow(Color.red(color), 2) * 0.241 +
Math.pow(Color.green(color), 2) * 0.691 +
Math.pow(Color.blue(color), 2) * 0.068
));
}
} }

View File

@@ -126,6 +126,10 @@ public final class TerminalEmulator {
private String mTitle; private String mTitle;
private final Stack<String> mTitleStack = new Stack<>(); private final Stack<String> mTitleStack = new Stack<>();
/** If processing first character of first parameter of {@link #ESC_CSI}. */
private boolean mIsCSIStart;
/** The last character processed of a parameter of {@link #ESC_CSI}. */
private Integer mLastCSIArg;
/** The cursor position. Between (0,0) and (mRows-1, mColumns-1). */ /** The cursor position. Between (0,0) and (mRows-1, mColumns-1). */
private int mCursorRow, mCursorCol; private int mCursorRow, mCursorCol;
@@ -796,7 +800,6 @@ public final class TerminalEmulator {
int columnsToDelete = Math.min(getArg0(1), columnsAfterCursor); int columnsToDelete = Math.min(getArg0(1), columnsAfterCursor);
int columnsToMove = columnsAfterCursor - columnsToDelete; int columnsToMove = columnsAfterCursor - columnsToDelete;
mScreen.blockCopy(mCursorCol + columnsToDelete, 0, columnsToMove, mRows, mCursorCol, 0); mScreen.blockCopy(mCursorCol + columnsToDelete, 0, columnsToMove, mRows, mCursorCol, 0);
blockClear(mCursorRow + columnsToMove, 0, columnsToDelete, mRows);
} else { } else {
unknownSequence(b); unknownSequence(b);
} }
@@ -825,7 +828,7 @@ public final class TerminalEmulator {
if (internalBit != -1) { if (internalBit != -1) {
value = isDecsetInternalBitSet(internalBit) ? 1 : 2; // 1=set, 2=reset. value = isDecsetInternalBitSet(internalBit) ? 1 : 2; // 1=set, 2=reset.
} else { } else {
mClient.logError(LOG_TAG, "Got DECRQM for unrecognized private DEC mode=" + mode); Logger.logError(mClient, LOG_TAG, "Got DECRQM for unrecognized private DEC mode=" + mode);
value = 0; // 0=not recognized, 3=permanently set, 4=permanently reset value = 0; // 0=not recognized, 3=permanently set, 4=permanently reset
} }
} }
@@ -936,10 +939,17 @@ public final class TerminalEmulator {
for (String part : dcs.substring(2).split(";")) { for (String part : dcs.substring(2).split(";")) {
if (part.length() % 2 == 0) { if (part.length() % 2 == 0) {
StringBuilder transBuffer = new StringBuilder(); StringBuilder transBuffer = new StringBuilder();
char c;
for (int i = 0; i < part.length(); i += 2) { for (int i = 0; i < part.length(); i += 2) {
char c = (char) Long.decode("0x" + part.charAt(i) + "" + part.charAt(i + 1)).longValue(); try {
c = (char) Long.decode("0x" + part.charAt(i) + "" + part.charAt(i + 1)).longValue();
} catch (NumberFormatException e) {
Logger.logStackTraceWithMessage(mClient, LOG_TAG, "Invalid device termcap/terminfo encoded name \"" + part + "\"", e);
continue;
}
transBuffer.append(c); transBuffer.append(c);
} }
String trans = transBuffer.toString(); String trans = transBuffer.toString();
String responseValue; String responseValue;
switch (trans) { switch (trans) {
@@ -962,7 +972,7 @@ public final class TerminalEmulator {
case "&8": // Undo key - ignore. case "&8": // Undo key - ignore.
break; break;
default: default:
mClient.logWarn(LOG_TAG, "Unhandled termcap/terminfo name: '" + trans + "'"); Logger.logWarn(mClient, LOG_TAG, "Unhandled termcap/terminfo name: '" + trans + "'");
} }
// Respond with invalid request: // Respond with invalid request:
mSession.write("\033P0+r" + part + "\033\\"); mSession.write("\033P0+r" + part + "\033\\");
@@ -974,12 +984,12 @@ public final class TerminalEmulator {
mSession.write("\033P1+r" + part + "=" + hexEncoded + "\033\\"); mSession.write("\033P1+r" + part + "=" + hexEncoded + "\033\\");
} }
} else { } else {
mClient.logError(LOG_TAG, "Invalid device termcap/terminfo name of odd length: " + part); Logger.logError(mClient, LOG_TAG, "Invalid device termcap/terminfo name of odd length: " + part);
} }
} }
} else { } else {
if (LOG_ESCAPE_SEQUENCES) if (LOG_ESCAPE_SEQUENCES)
mClient.logError(LOG_TAG, "Unrecognized device control string: " + dcs); Logger.logError(mClient, LOG_TAG, "Unrecognized device control string: " + dcs);
} }
finishSequence(); finishSequence();
} }
@@ -1069,7 +1079,7 @@ public final class TerminalEmulator {
int externalBit = mArgs[i]; int externalBit = mArgs[i];
int internalBit = mapDecSetBitToInternalBit(externalBit); int internalBit = mapDecSetBitToInternalBit(externalBit);
if (internalBit == -1) { if (internalBit == -1) {
mClient.logWarn(LOG_TAG, "Ignoring request to save/recall decset bit=" + externalBit); Logger.logWarn(mClient, LOG_TAG, "Ignoring request to save/recall decset bit=" + externalBit);
} else { } else {
if (b == 's') { if (b == 's') {
mSavedDecSetFlags |= internalBit; mSavedDecSetFlags |= internalBit;
@@ -1259,7 +1269,7 @@ public final class TerminalEmulator {
// (1) enables this feature for keys except for those with well-known behavior, e.g., Tab, Backarrow and // (1) enables this feature for keys except for those with well-known behavior, e.g., Tab, Backarrow and
// some special control character cases, e.g., Control-Space to make a NUL. // some special control character cases, e.g., Control-Space to make a NUL.
// (2) enables this feature for keys including the exceptions listed. // (2) enables this feature for keys including the exceptions listed.
mClient.logError(LOG_TAG, "(ignored) CSI > MODIFY RESOURCE: " + getArg0(-1) + " to " + getArg1(-1)); Logger.logError(mClient, LOG_TAG, "(ignored) CSI > MODIFY RESOURCE: " + getArg0(-1) + " to " + getArg1(-1));
break; break;
default: default:
parseArg(b); parseArg(b);
@@ -1380,6 +1390,8 @@ public final class TerminalEmulator {
break; break;
case '[': case '[':
continueSequence(ESC_CSI); continueSequence(ESC_CSI);
mIsCSIStart = true;
mLastCSIArg = null;
break; break;
case '=': // DECKPAM case '=': // DECKPAM
setDecsetinternalBit(DECSET_BIT_APPLICATION_KEYPAD, true); setDecsetinternalBit(DECSET_BIT_APPLICATION_KEYPAD, true);
@@ -1806,7 +1818,7 @@ public final class TerminalEmulator {
int firstArg = mArgs[i + 1]; int firstArg = mArgs[i + 1];
if (firstArg == 2) { if (firstArg == 2) {
if (i + 4 > mArgIndex) { if (i + 4 > mArgIndex) {
mClient.logWarn(LOG_TAG, "Too few CSI" + code + ";2 RGB arguments"); Logger.logWarn(mClient, LOG_TAG, "Too few CSI" + code + ";2 RGB arguments");
} else { } else {
int red = mArgs[i + 2], green = mArgs[i + 3], blue = mArgs[i + 4]; int red = mArgs[i + 2], green = mArgs[i + 3], blue = mArgs[i + 4];
if (red < 0 || green < 0 || blue < 0 || red > 255 || green > 255 || blue > 255) { if (red < 0 || green < 0 || blue < 0 || red > 255 || green > 255 || blue > 255) {
@@ -1831,7 +1843,7 @@ public final class TerminalEmulator {
mBackColor = color; mBackColor = color;
} }
} else { } else {
if (LOG_ESCAPE_SEQUENCES) mClient.logWarn(LOG_TAG, "Invalid color index: " + color); if (LOG_ESCAPE_SEQUENCES) Logger.logWarn(mClient, LOG_TAG, "Invalid color index: " + color);
} }
} else { } else {
finishSequenceAndLogError("Invalid ISO-8613-3 SGR first argument: " + firstArg); finishSequenceAndLogError("Invalid ISO-8613-3 SGR first argument: " + firstArg);
@@ -1848,7 +1860,7 @@ public final class TerminalEmulator {
mBackColor = code - 100 + 8; mBackColor = code - 100 + 8;
} else { } else {
if (LOG_ESCAPE_SEQUENCES) if (LOG_ESCAPE_SEQUENCES)
mClient.logWarn(LOG_TAG, String.format("SGR unknown code %d", code)); Logger.logWarn(mClient, LOG_TAG, String.format("SGR unknown code %d", code));
} }
} }
} }
@@ -1982,7 +1994,7 @@ public final class TerminalEmulator {
String clipboardText = new String(Base64.decode(textParameter.substring(startIndex), 0), StandardCharsets.UTF_8); String clipboardText = new String(Base64.decode(textParameter.substring(startIndex), 0), StandardCharsets.UTF_8);
mSession.onCopyTextToClipboard(clipboardText); mSession.onCopyTextToClipboard(clipboardText);
} catch (Exception e) { } catch (Exception e) {
mClient.logError(LOG_TAG, "OSC Manipulate selection, invalid string '" + textParameter + ""); Logger.logError(mClient, LOG_TAG, "OSC Manipulate selection, invalid string '" + textParameter + "");
} }
break; break;
case 104: case 104:
@@ -2087,28 +2099,57 @@ public final class TerminalEmulator {
} }
} }
/** Process the next ASCII character of a parameter. */ /**
private void parseArg(int b) { * Process the next ASCII character of a parameter.
if (b >= '0' && b <= '9') { *
if (mArgIndex < mArgs.length) { * Parameter characters modify the action or interpretation of the sequence. You can use up to
int oldValue = mArgs[mArgIndex]; * 16 parameters per sequence. You must use the ; character to separate parameters.
int thisDigit = b - '0'; * All parameters are unsigned, positive decimal integers, with the most significant
int value; * digit sent first. Any parameter greater than 9999 (decimal) is set to 9999
if (oldValue >= 0) { * (decimal). If you do not specify a value, a 0 value is assumed. A 0 value
value = oldValue * 10 + thisDigit; * or omitted parameter indicates a default value for the sequence. For most
} else { * sequences, the default value is 1.
value = thisDigit; *
* https://vt100.net/docs/vt510-rm/chapter4.html#S4.3.3
* */
private void parseArg(int inputByte) {
int[] bytes = new int[]{inputByte};
// Only doing this for ESC_CSI and not for other ESC_CSI_* since they seem to be using their
// own defaults with getArg*() calls, but there may be missed cases
if (mEscapeState == ESC_CSI) {
if ((mIsCSIStart && inputByte == ';') || // If sequence starts with a ; character, like \033[;m
(!mIsCSIStart && mLastCSIArg != null && mLastCSIArg == ';' && inputByte == ';')) { // If sequence contains sequential ; characters, like \033[;;m
bytes = new int[]{'0', ';'}; // Assume 0 was passed
}
}
mIsCSIStart = false;
for (int b : bytes) {
if (b >= '0' && b <= '9') {
if (mArgIndex < mArgs.length) {
int oldValue = mArgs[mArgIndex];
int thisDigit = b - '0';
int value;
if (oldValue >= 0) {
value = oldValue * 10 + thisDigit;
} else {
value = thisDigit;
}
if (value > 9999)
value = 9999;
mArgs[mArgIndex] = value;
} }
mArgs[mArgIndex] = value; continueSequence(mEscapeState);
} else if (b == ';') {
if (mArgIndex < mArgs.length) {
mArgIndex++;
}
continueSequence(mEscapeState);
} else {
unknownSequence(b);
} }
continueSequence(mEscapeState); mLastCSIArg = b;
} else if (b == ';') {
if (mArgIndex < mArgs.length) {
mArgIndex++;
}
continueSequence(mEscapeState);
} else {
unknownSequence(b);
} }
} }
@@ -2178,7 +2219,7 @@ public final class TerminalEmulator {
} }
private void finishSequenceAndLogError(String error) { private void finishSequenceAndLogError(String error) {
if (LOG_ESCAPE_SEQUENCES) mClient.logWarn(LOG_TAG, error); if (LOG_ESCAPE_SEQUENCES) Logger.logWarn(mClient, LOG_TAG, error);
finishSequence(); finishSequence();
} }
@@ -2326,7 +2367,14 @@ public final class TerminalEmulator {
} }
int offsetDueToCombiningChar = ((displayWidth <= 0 && mCursorCol > 0 && !mAboutToAutoWrap) ? 1 : 0); int offsetDueToCombiningChar = ((displayWidth <= 0 && mCursorCol > 0 && !mAboutToAutoWrap) ? 1 : 0);
mScreen.setChar(mCursorCol - offsetDueToCombiningChar, mCursorRow, codePoint, getStyle()); int column = mCursorCol - offsetDueToCombiningChar;
// Fix TerminalRow.setChar() ArrayIndexOutOfBoundsException index=-1 exception reported
// The offsetDueToCombiningChar would never be 1 if mCursorCol was 0 to get column/index=-1,
// so was mCursorCol changed after the offsetDueToCombiningChar conditional by another thread?
// TODO: Check if there are thread synchronization issues with mCursorCol and mCursorRow, possibly causing others bugs too.
if (column < 0) column = 0;
mScreen.setChar(column, mCursorRow, codePoint, getStyle());
if (autoWrap && displayWidth > 0) if (autoWrap && displayWidth > 0)
mAboutToAutoWrap = (mCursorCol == mRightMargin - displayWidth); mAboutToAutoWrap = (mCursorCol == mRightMargin - displayWidth);

View File

@@ -11,11 +11,37 @@ public final class TerminalRow {
private static final float SPARE_CAPACITY_FACTOR = 1.5f; private static final float SPARE_CAPACITY_FACTOR = 1.5f;
/**
* Max combining characters that can exist in a column, that are separate from the base character
* itself. Any additional combining characters will be ignored and not added to the column.
*
* There does not seem to be limit in unicode standard for max number of combination characters
* that can be combined but such characters are primarily under 10.
*
* "Section 3.6 Combination" of unicode standard contains combining characters info.
* - https://www.unicode.org/versions/Unicode15.0.0/ch03.pdf
* - https://en.wikipedia.org/wiki/Combining_character#Unicode_ranges
* - https://stackoverflow.com/questions/71237212/what-is-the-maximum-number-of-unicode-combined-characters-that-may-be-needed-to
*
* UAX15-D3 Stream-Safe Text Format limits to max 30 combining characters.
* > The value of 30 is chosen to be significantly beyond what is required for any linguistic or technical usage.
* > While it would have been feasible to chose a smaller number, this value provides a very wide margin,
* > yet is well within the buffer size limits of practical implementations.
* - https://unicode.org/reports/tr15/#Stream_Safe_Text_Format
* - https://stackoverflow.com/a/11983435/14686958
*
* We choose the value 15 because it should be enough for terminal based applications and keep
* the memory usage low for a terminal row, won't affect performance or cause terminal to
* lag or hang, and will keep malicious applications from causing harm. The value can be
* increased if ever needed for legitimate applications.
*/
private static final int MAX_COMBINING_CHARACTERS_PER_COLUMN = 15;
/** The number of columns in this terminal row. */ /** The number of columns in this terminal row. */
private final int mColumns; private final int mColumns;
/** The text filling this terminal row. */ /** The text filling this terminal row. */
public char[] mText; public char[] mText;
/** The number of java char:s used in {@link #mText}. */ /** The number of java chars used in {@link #mText}. */
private short mSpaceUsed; private short mSpaceUsed;
/** If this row has been line wrapped due to text output at the end of line. */ /** If this row has been line wrapped due to text output at the end of line. */
boolean mLineWrap; boolean mLineWrap;
@@ -124,6 +150,9 @@ public final class TerminalRow {
// https://github.com/steven676/Android-Terminal-Emulator/commit/9a47042620bec87617f0b4f5d50568535668fe26 // https://github.com/steven676/Android-Terminal-Emulator/commit/9a47042620bec87617f0b4f5d50568535668fe26
public void setChar(int columnToSet, int codePoint, long style) { public void setChar(int columnToSet, int codePoint, long style) {
if (columnToSet < 0 || columnToSet >= mStyle.length)
throw new IllegalArgumentException("TerminalRow.setChar(): columnToSet=" + columnToSet + ", codePoint=" + codePoint + ", style=" + style);
mStyle[columnToSet] = style; mStyle[columnToSet] = style;
final int newCodePointDisplayWidth = WcWidth.width(codePoint); final int newCodePointDisplayWidth = WcWidth.width(codePoint);
@@ -160,18 +189,25 @@ public final class TerminalRow {
// Get the number of elements in the mText array this column uses now // Get the number of elements in the mText array this column uses now
int oldCharactersUsedForColumn; int oldCharactersUsedForColumn;
if (columnToSet + oldCodePointDisplayWidth < mColumns) { if (columnToSet + oldCodePointDisplayWidth < mColumns) {
oldCharactersUsedForColumn = findStartOfColumn(columnToSet + oldCodePointDisplayWidth) - oldStartOfColumnIndex; int oldEndOfColumnIndex = findStartOfColumn(columnToSet + oldCodePointDisplayWidth);
oldCharactersUsedForColumn = oldEndOfColumnIndex - oldStartOfColumnIndex;
} else { } else {
// Last character. // Last character.
oldCharactersUsedForColumn = mSpaceUsed - oldStartOfColumnIndex; oldCharactersUsedForColumn = mSpaceUsed - oldStartOfColumnIndex;
} }
// If MAX_COMBINING_CHARACTERS_PER_COLUMN already exist in column, then ignore adding additional combining characters.
if (newIsCombining) {
int combiningCharsCount = WcWidth.zeroWidthCharsCount(mText, oldStartOfColumnIndex, oldStartOfColumnIndex + oldCharactersUsedForColumn);
if (combiningCharsCount >= MAX_COMBINING_CHARACTERS_PER_COLUMN)
return;
}
// Find how many chars this column will need // Find how many chars this column will need
int newCharactersUsedForColumn = Character.charCount(codePoint); int newCharactersUsedForColumn = Character.charCount(codePoint);
if (newIsCombining) { if (newIsCombining) {
// Combining characters are added to the contents of the column instead of overwriting them, so that they // Combining characters are added to the contents of the column instead of overwriting them, so that they
// modify the existing contents. // modify the existing contents.
// FIXME: Put a limit of combining characters.
// FIXME: Unassigned characters also get width=0. // FIXME: Unassigned characters also get width=0.
newCharactersUsedForColumn += oldCharactersUsedForColumn; newCharactersUsedForColumn += oldCharactersUsedForColumn;
} }
@@ -186,7 +222,7 @@ public final class TerminalRow {
if (mSpaceUsed + javaCharDifference > text.length) { if (mSpaceUsed + javaCharDifference > text.length) {
// We need to grow the array // We need to grow the array
char[] newText = new char[text.length + mColumns]; char[] newText = new char[text.length + mColumns];
System.arraycopy(text, 0, newText, 0, oldStartOfColumnIndex + oldCharactersUsedForColumn); System.arraycopy(text, 0, newText, 0, oldNextColumnIndex);
System.arraycopy(text, oldNextColumnIndex, newText, newNextColumnIndex, oldCharactersAfterColumn); System.arraycopy(text, oldNextColumnIndex, newText, newNextColumnIndex, oldCharactersAfterColumn);
mText = text = newText; mText = text = newText;
} else { } else {

View File

@@ -236,7 +236,7 @@ public final class TerminalSession extends TerminalOutput {
try { try {
Os.kill(mShellPid, OsConstants.SIGKILL); Os.kill(mShellPid, OsConstants.SIGKILL);
} catch (ErrnoException e) { } catch (ErrnoException e) {
mClient.logWarn(LOG_TAG, "Failed sending SIGKILL: " + e.getMessage()); Logger.logWarn(mClient, LOG_TAG, "Failed sending SIGKILL: " + e.getMessage());
} }
} }
} }
@@ -308,7 +308,7 @@ public final class TerminalSession extends TerminalOutput {
return outputPath; return outputPath;
} }
} catch (IOException | SecurityException e) { } catch (IOException | SecurityException e) {
mClient.logStackTraceWithMessage(LOG_TAG, "Error getting current directory", e); Logger.logStackTraceWithMessage(mClient, LOG_TAG, "Error getting current directory", e);
} }
return null; return null;
} }
@@ -326,7 +326,7 @@ public final class TerminalSession extends TerminalOutput {
descriptorField.setAccessible(true); descriptorField.setAccessible(true);
descriptorField.set(result, fileDescriptor); descriptorField.set(result, fileDescriptor);
} catch (NoSuchFieldException | IllegalAccessException | IllegalArgumentException e) { } catch (NoSuchFieldException | IllegalAccessException | IllegalArgumentException e) {
client.logStackTraceWithMessage(LOG_TAG, "Error accessing FileDescriptor#descriptor private field", e); Logger.logStackTraceWithMessage(client, LOG_TAG, "Error accessing FileDescriptor#descriptor private field", e);
System.exit(1); System.exit(1);
} }
return result; return result;

View File

@@ -1,7 +1,7 @@
package com.termux.terminal; package com.termux.terminal;
/** /**
* Implementation of wcwidth(3) for Unicode 9. * Implementation of wcwidth(3) for Unicode 15.
* *
* Implementation from https://github.com/jquast/wcwidth but we return 0 for unprintable characters. * Implementation from https://github.com/jquast/wcwidth but we return 0 for unprintable characters.
* *
@@ -9,12 +9,13 @@ package com.termux.terminal;
* Must be kept in sync with the following: * Must be kept in sync with the following:
* https://github.com/termux/wcwidth * https://github.com/termux/wcwidth
* https://github.com/termux/libandroid-support * https://github.com/termux/libandroid-support
* https://github.com/termux/termux-packages/tree/master/libandroid-support * https://github.com/termux/termux-packages/tree/master/packages/libandroid-support
*/ */
public final class WcWidth { public final class WcWidth {
// From https://github.com/jquast/wcwidth/blob/master/wcwidth/table_zero.py // From https://github.com/jquast/wcwidth/blob/master/wcwidth/table_zero.py
// at commit b29897e5a1b403a0e36f7fc991614981cbc42475 (2020-07-14): // from https://github.com/jquast/wcwidth/pull/64
// at commit 1b9b6585b0080ea5cb88dc9815796505724793fe (2022-12-16):
private static final int[][] ZERO_WIDTH = { private static final int[][] ZERO_WIDTH = {
{0x00300, 0x0036f}, // Combining Grave Accent ..Combining Latin Small Le {0x00300, 0x0036f}, // Combining Grave Accent ..Combining Latin Small Le
{0x00483, 0x00489}, // Combining Cyrillic Titlo..Combining Cyrillic Milli {0x00483, 0x00489}, // Combining Cyrillic Titlo..Combining Cyrillic Milli
@@ -40,7 +41,8 @@ public final class WcWidth {
{0x00825, 0x00827}, // Samaritan Vowel Sign Sho..Samaritan Vowel Sign U {0x00825, 0x00827}, // Samaritan Vowel Sign Sho..Samaritan Vowel Sign U
{0x00829, 0x0082d}, // Samaritan Vowel Sign Lon..Samaritan Mark Nequdaa {0x00829, 0x0082d}, // Samaritan Vowel Sign Lon..Samaritan Mark Nequdaa
{0x00859, 0x0085b}, // Mandaic Affrication Mark..Mandaic Gemination Mark {0x00859, 0x0085b}, // Mandaic Affrication Mark..Mandaic Gemination Mark
{0x008d3, 0x008e1}, // Arabic Small Low Waw ..Arabic Small High Sign S {0x00898, 0x0089f}, // Arabic Small High Word A..Arabic Half Madda Over M
{0x008ca, 0x008e1}, // Arabic Small High Farsi ..Arabic Small High Sign S
{0x008e3, 0x00902}, // Arabic Turned Damma Belo..Devanagari Sign Anusvara {0x008e3, 0x00902}, // Arabic Turned Damma Belo..Devanagari Sign Anusvara
{0x0093a, 0x0093a}, // Devanagari Vowel Sign Oe..Devanagari Vowel Sign Oe {0x0093a, 0x0093a}, // Devanagari Vowel Sign Oe..Devanagari Vowel Sign Oe
{0x0093c, 0x0093c}, // Devanagari Sign Nukta ..Devanagari Sign Nukta {0x0093c, 0x0093c}, // Devanagari Sign Nukta ..Devanagari Sign Nukta
@@ -74,13 +76,14 @@ public final class WcWidth {
{0x00b3f, 0x00b3f}, // Oriya Vowel Sign I ..Oriya Vowel Sign I {0x00b3f, 0x00b3f}, // Oriya Vowel Sign I ..Oriya Vowel Sign I
{0x00b41, 0x00b44}, // Oriya Vowel Sign U ..Oriya Vowel Sign Vocalic {0x00b41, 0x00b44}, // Oriya Vowel Sign U ..Oriya Vowel Sign Vocalic
{0x00b4d, 0x00b4d}, // Oriya Sign Virama ..Oriya Sign Virama {0x00b4d, 0x00b4d}, // Oriya Sign Virama ..Oriya Sign Virama
{0x00b55, 0x00b56}, // (nil) ..Oriya Ai Length Mark {0x00b55, 0x00b56}, // Oriya Sign Overline ..Oriya Ai Length Mark
{0x00b62, 0x00b63}, // Oriya Vowel Sign Vocalic..Oriya Vowel Sign Vocalic {0x00b62, 0x00b63}, // Oriya Vowel Sign Vocalic..Oriya Vowel Sign Vocalic
{0x00b82, 0x00b82}, // Tamil Sign Anusvara ..Tamil Sign Anusvara {0x00b82, 0x00b82}, // Tamil Sign Anusvara ..Tamil Sign Anusvara
{0x00bc0, 0x00bc0}, // Tamil Vowel Sign Ii ..Tamil Vowel Sign Ii {0x00bc0, 0x00bc0}, // Tamil Vowel Sign Ii ..Tamil Vowel Sign Ii
{0x00bcd, 0x00bcd}, // Tamil Sign Virama ..Tamil Sign Virama {0x00bcd, 0x00bcd}, // Tamil Sign Virama ..Tamil Sign Virama
{0x00c00, 0x00c00}, // Telugu Sign Combining Ca..Telugu Sign Combining Ca {0x00c00, 0x00c00}, // Telugu Sign Combining Ca..Telugu Sign Combining Ca
{0x00c04, 0x00c04}, // Telugu Sign Combining An..Telugu Sign Combining An {0x00c04, 0x00c04}, // Telugu Sign Combining An..Telugu Sign Combining An
{0x00c3c, 0x00c3c}, // Telugu Sign Nukta ..Telugu Sign Nukta
{0x00c3e, 0x00c40}, // Telugu Vowel Sign Aa ..Telugu Vowel Sign Ii {0x00c3e, 0x00c40}, // Telugu Vowel Sign Aa ..Telugu Vowel Sign Ii
{0x00c46, 0x00c48}, // Telugu Vowel Sign E ..Telugu Vowel Sign Ai {0x00c46, 0x00c48}, // Telugu Vowel Sign E ..Telugu Vowel Sign Ai
{0x00c4a, 0x00c4d}, // Telugu Vowel Sign O ..Telugu Sign Virama {0x00c4a, 0x00c4d}, // Telugu Vowel Sign O ..Telugu Sign Virama
@@ -97,7 +100,7 @@ public final class WcWidth {
{0x00d41, 0x00d44}, // Malayalam Vowel Sign U ..Malayalam Vowel Sign Voc {0x00d41, 0x00d44}, // Malayalam Vowel Sign U ..Malayalam Vowel Sign Voc
{0x00d4d, 0x00d4d}, // Malayalam Sign Virama ..Malayalam Sign Virama {0x00d4d, 0x00d4d}, // Malayalam Sign Virama ..Malayalam Sign Virama
{0x00d62, 0x00d63}, // Malayalam Vowel Sign Voc..Malayalam Vowel Sign Voc {0x00d62, 0x00d63}, // Malayalam Vowel Sign Voc..Malayalam Vowel Sign Voc
{0x00d81, 0x00d81}, // (nil) ..(nil) {0x00d81, 0x00d81}, // Sinhala Sign Candrabindu..Sinhala Sign Candrabindu
{0x00dca, 0x00dca}, // Sinhala Sign Al-lakuna ..Sinhala Sign Al-lakuna {0x00dca, 0x00dca}, // Sinhala Sign Al-lakuna ..Sinhala Sign Al-lakuna
{0x00dd2, 0x00dd4}, // Sinhala Vowel Sign Ketti..Sinhala Vowel Sign Ketti {0x00dd2, 0x00dd4}, // Sinhala Vowel Sign Ketti..Sinhala Vowel Sign Ketti
{0x00dd6, 0x00dd6}, // Sinhala Vowel Sign Diga ..Sinhala Vowel Sign Diga {0x00dd6, 0x00dd6}, // Sinhala Vowel Sign Diga ..Sinhala Vowel Sign Diga
@@ -106,7 +109,7 @@ public final class WcWidth {
{0x00e47, 0x00e4e}, // Thai Character Maitaikhu..Thai Character Yamakkan {0x00e47, 0x00e4e}, // Thai Character Maitaikhu..Thai Character Yamakkan
{0x00eb1, 0x00eb1}, // Lao Vowel Sign Mai Kan ..Lao Vowel Sign Mai Kan {0x00eb1, 0x00eb1}, // Lao Vowel Sign Mai Kan ..Lao Vowel Sign Mai Kan
{0x00eb4, 0x00ebc}, // Lao Vowel Sign I ..Lao Semivowel Sign Lo {0x00eb4, 0x00ebc}, // Lao Vowel Sign I ..Lao Semivowel Sign Lo
{0x00ec8, 0x00ecd}, // Lao Tone Mai Ek ..Lao Niggahita {0x00ec8, 0x00ece}, // Lao Tone Mai Ek ..(nil)
{0x00f18, 0x00f19}, // Tibetan Astrological Sig..Tibetan Astrological Sig {0x00f18, 0x00f19}, // Tibetan Astrological Sig..Tibetan Astrological Sig
{0x00f35, 0x00f35}, // Tibetan Mark Ngas Bzung ..Tibetan Mark Ngas Bzung {0x00f35, 0x00f35}, // Tibetan Mark Ngas Bzung ..Tibetan Mark Ngas Bzung
{0x00f37, 0x00f37}, // Tibetan Mark Ngas Bzung ..Tibetan Mark Ngas Bzung {0x00f37, 0x00f37}, // Tibetan Mark Ngas Bzung ..Tibetan Mark Ngas Bzung
@@ -130,7 +133,7 @@ public final class WcWidth {
{0x0109d, 0x0109d}, // Myanmar Vowel Sign Aiton..Myanmar Vowel Sign Aiton {0x0109d, 0x0109d}, // Myanmar Vowel Sign Aiton..Myanmar Vowel Sign Aiton
{0x0135d, 0x0135f}, // Ethiopic Combining Gemin..Ethiopic Combining Gemin {0x0135d, 0x0135f}, // Ethiopic Combining Gemin..Ethiopic Combining Gemin
{0x01712, 0x01714}, // Tagalog Vowel Sign I ..Tagalog Sign Virama {0x01712, 0x01714}, // Tagalog Vowel Sign I ..Tagalog Sign Virama
{0x01732, 0x01734}, // Hanunoo Vowel Sign I ..Hanunoo Sign Pamudpod {0x01732, 0x01733}, // Hanunoo Vowel Sign I ..Hanunoo Vowel Sign U
{0x01752, 0x01753}, // Buhid Vowel Sign I ..Buhid Vowel Sign U {0x01752, 0x01753}, // Buhid Vowel Sign I ..Buhid Vowel Sign U
{0x01772, 0x01773}, // Tagbanwa Vowel Sign I ..Tagbanwa Vowel Sign U {0x01772, 0x01773}, // Tagbanwa Vowel Sign I ..Tagbanwa Vowel Sign U
{0x017b4, 0x017b5}, // Khmer Vowel Inherent Aq ..Khmer Vowel Inherent Aa {0x017b4, 0x017b5}, // Khmer Vowel Inherent Aq ..Khmer Vowel Inherent Aa
@@ -139,6 +142,7 @@ public final class WcWidth {
{0x017c9, 0x017d3}, // Khmer Sign Muusikatoan ..Khmer Sign Bathamasat {0x017c9, 0x017d3}, // Khmer Sign Muusikatoan ..Khmer Sign Bathamasat
{0x017dd, 0x017dd}, // Khmer Sign Atthacan ..Khmer Sign Atthacan {0x017dd, 0x017dd}, // Khmer Sign Atthacan ..Khmer Sign Atthacan
{0x0180b, 0x0180d}, // Mongolian Free Variation..Mongolian Free Variation {0x0180b, 0x0180d}, // Mongolian Free Variation..Mongolian Free Variation
{0x0180f, 0x0180f}, // Mongolian Free Variation..Mongolian Free Variation
{0x01885, 0x01886}, // Mongolian Letter Ali Gal..Mongolian Letter Ali Gal {0x01885, 0x01886}, // Mongolian Letter Ali Gal..Mongolian Letter Ali Gal
{0x018a9, 0x018a9}, // Mongolian Letter Ali Gal..Mongolian Letter Ali Gal {0x018a9, 0x018a9}, // Mongolian Letter Ali Gal..Mongolian Letter Ali Gal
{0x01920, 0x01922}, // Limbu Vowel Sign A ..Limbu Vowel Sign U {0x01920, 0x01922}, // Limbu Vowel Sign A ..Limbu Vowel Sign U
@@ -154,7 +158,7 @@ public final class WcWidth {
{0x01a65, 0x01a6c}, // Tai Tham Vowel Sign I ..Tai Tham Vowel Sign Oa B {0x01a65, 0x01a6c}, // Tai Tham Vowel Sign I ..Tai Tham Vowel Sign Oa B
{0x01a73, 0x01a7c}, // Tai Tham Vowel Sign Oa A..Tai Tham Sign Khuen-lue {0x01a73, 0x01a7c}, // Tai Tham Vowel Sign Oa A..Tai Tham Sign Khuen-lue
{0x01a7f, 0x01a7f}, // Tai Tham Combining Crypt..Tai Tham Combining Crypt {0x01a7f, 0x01a7f}, // Tai Tham Combining Crypt..Tai Tham Combining Crypt
{0x01ab0, 0x01ac0}, // Combining Doubled Circum..(nil) {0x01ab0, 0x01ace}, // Combining Doubled Circum..Combining Latin Small Le
{0x01b00, 0x01b03}, // Balinese Sign Ulu Ricem ..Balinese Sign Surang {0x01b00, 0x01b03}, // Balinese Sign Ulu Ricem ..Balinese Sign Surang
{0x01b34, 0x01b34}, // Balinese Sign Rerekan ..Balinese Sign Rerekan {0x01b34, 0x01b34}, // Balinese Sign Rerekan ..Balinese Sign Rerekan
{0x01b36, 0x01b3a}, // Balinese Vowel Sign Ulu ..Balinese Vowel Sign Ra R {0x01b36, 0x01b3a}, // Balinese Vowel Sign Ulu ..Balinese Vowel Sign Ra R
@@ -177,8 +181,7 @@ public final class WcWidth {
{0x01ced, 0x01ced}, // Vedic Sign Tiryak ..Vedic Sign Tiryak {0x01ced, 0x01ced}, // Vedic Sign Tiryak ..Vedic Sign Tiryak
{0x01cf4, 0x01cf4}, // Vedic Tone Candra Above ..Vedic Tone Candra Above {0x01cf4, 0x01cf4}, // Vedic Tone Candra Above ..Vedic Tone Candra Above
{0x01cf8, 0x01cf9}, // Vedic Tone Ring Above ..Vedic Tone Double Ring A {0x01cf8, 0x01cf9}, // Vedic Tone Ring Above ..Vedic Tone Double Ring A
{0x01dc0, 0x01df9}, // Combining Dotted Grave A..Combining Wide Inverted {0x01dc0, 0x01dff}, // Combining Dotted Grave A..Combining Right Arrowhea
{0x01dfb, 0x01dff}, // Combining Deletion Mark ..Combining Right Arrowhea
{0x020d0, 0x020f0}, // Combining Left Harpoon A..Combining Asterisk Above {0x020d0, 0x020f0}, // Combining Left Harpoon A..Combining Asterisk Above
{0x02cef, 0x02cf1}, // Coptic Combining Ni Abov..Coptic Combining Spiritu {0x02cef, 0x02cf1}, // Coptic Combining Ni Abov..Coptic Combining Spiritu
{0x02d7f, 0x02d7f}, // Tifinagh Consonant Joine..Tifinagh Consonant Joine {0x02d7f, 0x02d7f}, // Tifinagh Consonant Joine..Tifinagh Consonant Joine
@@ -193,7 +196,7 @@ public final class WcWidth {
{0x0a806, 0x0a806}, // Syloti Nagri Sign Hasant..Syloti Nagri Sign Hasant {0x0a806, 0x0a806}, // Syloti Nagri Sign Hasant..Syloti Nagri Sign Hasant
{0x0a80b, 0x0a80b}, // Syloti Nagri Sign Anusva..Syloti Nagri Sign Anusva {0x0a80b, 0x0a80b}, // Syloti Nagri Sign Anusva..Syloti Nagri Sign Anusva
{0x0a825, 0x0a826}, // Syloti Nagri Vowel Sign ..Syloti Nagri Vowel Sign {0x0a825, 0x0a826}, // Syloti Nagri Vowel Sign ..Syloti Nagri Vowel Sign
{0x0a82c, 0x0a82c}, // (nil) ..(nil) {0x0a82c, 0x0a82c}, // Syloti Nagri Sign Altern..Syloti Nagri Sign Altern
{0x0a8c4, 0x0a8c5}, // Saurashtra Sign Virama ..Saurashtra Sign Candrabi {0x0a8c4, 0x0a8c5}, // Saurashtra Sign Virama ..Saurashtra Sign Candrabi
{0x0a8e0, 0x0a8f1}, // Combining Devanagari Dig..Combining Devanagari Sig {0x0a8e0, 0x0a8f1}, // Combining Devanagari Dig..Combining Devanagari Sig
{0x0a8ff, 0x0a8ff}, // Devanagari Vowel Sign Ay..Devanagari Vowel Sign Ay {0x0a8ff, 0x0a8ff}, // Devanagari Vowel Sign Ay..Devanagari Vowel Sign Ay
@@ -233,13 +236,18 @@ public final class WcWidth {
{0x10a3f, 0x10a3f}, // Kharoshthi Virama ..Kharoshthi Virama {0x10a3f, 0x10a3f}, // Kharoshthi Virama ..Kharoshthi Virama
{0x10ae5, 0x10ae6}, // Manichaean Abbreviation ..Manichaean Abbreviation {0x10ae5, 0x10ae6}, // Manichaean Abbreviation ..Manichaean Abbreviation
{0x10d24, 0x10d27}, // Hanifi Rohingya Sign Har..Hanifi Rohingya Sign Tas {0x10d24, 0x10d27}, // Hanifi Rohingya Sign Har..Hanifi Rohingya Sign Tas
{0x10eab, 0x10eac}, // (nil) ..(nil) {0x10eab, 0x10eac}, // Yezidi Combining Hamza M..Yezidi Combining Madda M
{0x10efd, 0x10eff}, // (nil) ..(nil)
{0x10f46, 0x10f50}, // Sogdian Combining Dot Be..Sogdian Combining Stroke {0x10f46, 0x10f50}, // Sogdian Combining Dot Be..Sogdian Combining Stroke
{0x10f82, 0x10f85}, // Old Uyghur Combining Dot..Old Uyghur Combining Two
{0x11001, 0x11001}, // Brahmi Sign Anusvara ..Brahmi Sign Anusvara {0x11001, 0x11001}, // Brahmi Sign Anusvara ..Brahmi Sign Anusvara
{0x11038, 0x11046}, // Brahmi Vowel Sign Aa ..Brahmi Virama {0x11038, 0x11046}, // Brahmi Vowel Sign Aa ..Brahmi Virama
{0x11070, 0x11070}, // Brahmi Sign Old Tamil Vi..Brahmi Sign Old Tamil Vi
{0x11073, 0x11074}, // Brahmi Vowel Sign Old Ta..Brahmi Vowel Sign Old Ta
{0x1107f, 0x11081}, // Brahmi Number Joiner ..Kaithi Sign Anusvara {0x1107f, 0x11081}, // Brahmi Number Joiner ..Kaithi Sign Anusvara
{0x110b3, 0x110b6}, // Kaithi Vowel Sign U ..Kaithi Vowel Sign Ai {0x110b3, 0x110b6}, // Kaithi Vowel Sign U ..Kaithi Vowel Sign Ai
{0x110b9, 0x110ba}, // Kaithi Sign Virama ..Kaithi Sign Nukta {0x110b9, 0x110ba}, // Kaithi Sign Virama ..Kaithi Sign Nukta
{0x110c2, 0x110c2}, // Kaithi Vowel Sign Vocali..Kaithi Vowel Sign Vocali
{0x11100, 0x11102}, // Chakma Sign Candrabindu ..Chakma Sign Visarga {0x11100, 0x11102}, // Chakma Sign Candrabindu ..Chakma Sign Visarga
{0x11127, 0x1112b}, // Chakma Vowel Sign A ..Chakma Vowel Sign Uu {0x11127, 0x1112b}, // Chakma Vowel Sign A ..Chakma Vowel Sign Uu
{0x1112d, 0x11134}, // Chakma Vowel Sign Ai ..Chakma Maayyaa {0x1112d, 0x11134}, // Chakma Vowel Sign Ai ..Chakma Maayyaa
@@ -247,11 +255,12 @@ public final class WcWidth {
{0x11180, 0x11181}, // Sharada Sign Candrabindu..Sharada Sign Anusvara {0x11180, 0x11181}, // Sharada Sign Candrabindu..Sharada Sign Anusvara
{0x111b6, 0x111be}, // Sharada Vowel Sign U ..Sharada Vowel Sign O {0x111b6, 0x111be}, // Sharada Vowel Sign U ..Sharada Vowel Sign O
{0x111c9, 0x111cc}, // Sharada Sandhi Mark ..Sharada Extra Short Vowe {0x111c9, 0x111cc}, // Sharada Sandhi Mark ..Sharada Extra Short Vowe
{0x111cf, 0x111cf}, // (nil) ..(nil) {0x111cf, 0x111cf}, // Sharada Sign Inverted Ca..Sharada Sign Inverted Ca
{0x1122f, 0x11231}, // Khojki Vowel Sign U ..Khojki Vowel Sign Ai {0x1122f, 0x11231}, // Khojki Vowel Sign U ..Khojki Vowel Sign Ai
{0x11234, 0x11234}, // Khojki Sign Anusvara ..Khojki Sign Anusvara {0x11234, 0x11234}, // Khojki Sign Anusvara ..Khojki Sign Anusvara
{0x11236, 0x11237}, // Khojki Sign Nukta ..Khojki Sign Shadda {0x11236, 0x11237}, // Khojki Sign Nukta ..Khojki Sign Shadda
{0x1123e, 0x1123e}, // Khojki Sign Sukun ..Khojki Sign Sukun {0x1123e, 0x1123e}, // Khojki Sign Sukun ..Khojki Sign Sukun
{0x11241, 0x11241}, // (nil) ..(nil)
{0x112df, 0x112df}, // Khudawadi Sign Anusvara ..Khudawadi Sign Anusvara {0x112df, 0x112df}, // Khudawadi Sign Anusvara ..Khudawadi Sign Anusvara
{0x112e3, 0x112ea}, // Khudawadi Vowel Sign U ..Khudawadi Sign Virama {0x112e3, 0x112ea}, // Khudawadi Vowel Sign U ..Khudawadi Sign Virama
{0x11300, 0x11301}, // Grantha Sign Combining A..Grantha Sign Candrabindu {0x11300, 0x11301}, // Grantha Sign Combining A..Grantha Sign Candrabindu
@@ -283,9 +292,9 @@ public final class WcWidth {
{0x11727, 0x1172b}, // Ahom Vowel Sign Aw ..Ahom Sign Killer {0x11727, 0x1172b}, // Ahom Vowel Sign Aw ..Ahom Sign Killer
{0x1182f, 0x11837}, // Dogra Vowel Sign U ..Dogra Sign Anusvara {0x1182f, 0x11837}, // Dogra Vowel Sign U ..Dogra Sign Anusvara
{0x11839, 0x1183a}, // Dogra Sign Virama ..Dogra Sign Nukta {0x11839, 0x1183a}, // Dogra Sign Virama ..Dogra Sign Nukta
{0x1193b, 0x1193c}, // (nil) ..(nil) {0x1193b, 0x1193c}, // Dives Akuru Sign Anusvar..Dives Akuru Sign Candrab
{0x1193e, 0x1193e}, // (nil) ..(nil) {0x1193e, 0x1193e}, // Dives Akuru Virama ..Dives Akuru Virama
{0x11943, 0x11943}, // (nil) ..(nil) {0x11943, 0x11943}, // Dives Akuru Sign Nukta ..Dives Akuru Sign Nukta
{0x119d4, 0x119d7}, // Nandinagari Vowel Sign U..Nandinagari Vowel Sign V {0x119d4, 0x119d7}, // Nandinagari Vowel Sign U..Nandinagari Vowel Sign V
{0x119da, 0x119db}, // Nandinagari Vowel Sign E..Nandinagari Vowel Sign A {0x119da, 0x119db}, // Nandinagari Vowel Sign E..Nandinagari Vowel Sign A
{0x119e0, 0x119e0}, // Nandinagari Sign Virama ..Nandinagari Sign Virama {0x119e0, 0x119e0}, // Nandinagari Sign Virama ..Nandinagari Sign Virama
@@ -313,12 +322,20 @@ public final class WcWidth {
{0x11d95, 0x11d95}, // Gunjala Gondi Sign Anusv..Gunjala Gondi Sign Anusv {0x11d95, 0x11d95}, // Gunjala Gondi Sign Anusv..Gunjala Gondi Sign Anusv
{0x11d97, 0x11d97}, // Gunjala Gondi Virama ..Gunjala Gondi Virama {0x11d97, 0x11d97}, // Gunjala Gondi Virama ..Gunjala Gondi Virama
{0x11ef3, 0x11ef4}, // Makasar Vowel Sign I ..Makasar Vowel Sign U {0x11ef3, 0x11ef4}, // Makasar Vowel Sign I ..Makasar Vowel Sign U
{0x11f00, 0x11f01}, // (nil) ..(nil)
{0x11f36, 0x11f3a}, // (nil) ..(nil)
{0x11f40, 0x11f40}, // (nil) ..(nil)
{0x11f42, 0x11f42}, // (nil) ..(nil)
{0x13440, 0x13440}, // (nil) ..(nil)
{0x13447, 0x13455}, // (nil) ..(nil)
{0x16af0, 0x16af4}, // Bassa Vah Combining High..Bassa Vah Combining High {0x16af0, 0x16af4}, // Bassa Vah Combining High..Bassa Vah Combining High
{0x16b30, 0x16b36}, // Pahawh Hmong Mark Cim Tu..Pahawh Hmong Mark Cim Ta {0x16b30, 0x16b36}, // Pahawh Hmong Mark Cim Tu..Pahawh Hmong Mark Cim Ta
{0x16f4f, 0x16f4f}, // Miao Sign Consonant Modi..Miao Sign Consonant Modi {0x16f4f, 0x16f4f}, // Miao Sign Consonant Modi..Miao Sign Consonant Modi
{0x16f8f, 0x16f92}, // Miao Tone Right ..Miao Tone Below {0x16f8f, 0x16f92}, // Miao Tone Right ..Miao Tone Below
{0x16fe4, 0x16fe4}, // (nil) ..(nil) {0x16fe4, 0x16fe4}, // Khitan Small Script Fill..Khitan Small Script Fill
{0x1bc9d, 0x1bc9e}, // Duployan Thick Letter Se..Duployan Double Mark {0x1bc9d, 0x1bc9e}, // Duployan Thick Letter Se..Duployan Double Mark
{0x1cf00, 0x1cf2d}, // Znamenny Combining Mark ..Znamenny Combining Mark
{0x1cf30, 0x1cf46}, // Znamenny Combining Tonal..Znamenny Priznak Modifie
{0x1d167, 0x1d169}, // Musical Symbol Combining..Musical Symbol Combining {0x1d167, 0x1d169}, // Musical Symbol Combining..Musical Symbol Combining
{0x1d17b, 0x1d182}, // Musical Symbol Combining..Musical Symbol Combining {0x1d17b, 0x1d182}, // Musical Symbol Combining..Musical Symbol Combining
{0x1d185, 0x1d18b}, // Musical Symbol Combining..Musical Symbol Combining {0x1d185, 0x1d18b}, // Musical Symbol Combining..Musical Symbol Combining
@@ -335,15 +352,19 @@ public final class WcWidth {
{0x1e01b, 0x1e021}, // Combining Glagolitic Let..Combining Glagolitic Let {0x1e01b, 0x1e021}, // Combining Glagolitic Let..Combining Glagolitic Let
{0x1e023, 0x1e024}, // Combining Glagolitic Let..Combining Glagolitic Let {0x1e023, 0x1e024}, // Combining Glagolitic Let..Combining Glagolitic Let
{0x1e026, 0x1e02a}, // Combining Glagolitic Let..Combining Glagolitic Let {0x1e026, 0x1e02a}, // Combining Glagolitic Let..Combining Glagolitic Let
{0x1e08f, 0x1e08f}, // (nil) ..(nil)
{0x1e130, 0x1e136}, // Nyiakeng Puachue Hmong T..Nyiakeng Puachue Hmong T {0x1e130, 0x1e136}, // Nyiakeng Puachue Hmong T..Nyiakeng Puachue Hmong T
{0x1e2ae, 0x1e2ae}, // Toto Sign Rising Tone ..Toto Sign Rising Tone
{0x1e2ec, 0x1e2ef}, // Wancho Tone Tup ..Wancho Tone Koini {0x1e2ec, 0x1e2ef}, // Wancho Tone Tup ..Wancho Tone Koini
{0x1e4ec, 0x1e4ef}, // (nil) ..(nil)
{0x1e8d0, 0x1e8d6}, // Mende Kikakui Combining ..Mende Kikakui Combining {0x1e8d0, 0x1e8d6}, // Mende Kikakui Combining ..Mende Kikakui Combining
{0x1e944, 0x1e94a}, // Adlam Alif Lengthener ..Adlam Nukta {0x1e944, 0x1e94a}, // Adlam Alif Lengthener ..Adlam Nukta
{0xe0100, 0xe01ef}, // Variation Selector-17 ..Variation Selector-256 {0xe0100, 0xe01ef}, // Variation Selector-17 ..Variation Selector-256
}; };
// https://github.com/jquast/wcwidth/blob/master/wcwidth/table_wide.py // https://github.com/jquast/wcwidth/blob/master/wcwidth/table_wide.py
// at commit b29897e5a1b403a0e36f7fc991614981cbc42475 (2020-07-14): // from https://github.com/jquast/wcwidth/pull/64
// at commit 1b9b6585b0080ea5cb88dc9815796505724793fe (2022-12-16):
private static final int[][] WIDE_EASTASIAN = { private static final int[][] WIDE_EASTASIAN = {
{0x01100, 0x0115f}, // Hangul Choseong Kiyeok ..Hangul Choseong Filler {0x01100, 0x0115f}, // Hangul Choseong Kiyeok ..Hangul Choseong Filler
{0x0231a, 0x0231b}, // Watch ..Hourglass {0x0231a, 0x0231b}, // Watch ..Hourglass
@@ -392,7 +413,7 @@ public final class WcWidth {
{0x03190, 0x031e3}, // Ideographic Annotation L..Cjk Stroke Q {0x03190, 0x031e3}, // Ideographic Annotation L..Cjk Stroke Q
{0x031f0, 0x0321e}, // Katakana Letter Small Ku..Parenthesized Korean Cha {0x031f0, 0x0321e}, // Katakana Letter Small Ku..Parenthesized Korean Cha
{0x03220, 0x03247}, // Parenthesized Ideograph ..Circled Ideograph Koto {0x03220, 0x03247}, // Parenthesized Ideograph ..Circled Ideograph Koto
{0x03250, 0x04dbf}, // Partnership Sign ..(nil) {0x03250, 0x04dbf}, // Partnership Sign ..Cjk Unified Ideograph-4d
{0x04e00, 0x0a48c}, // Cjk Unified Ideograph-4e..Yi Syllable Yyr {0x04e00, 0x0a48c}, // Cjk Unified Ideograph-4e..Yi Syllable Yyr
{0x0a490, 0x0a4c6}, // Yi Radical Qot ..Yi Radical Ke {0x0a490, 0x0a4c6}, // Yi Radical Qot ..Yi Radical Ke
{0x0a960, 0x0a97c}, // Hangul Choseong Tikeut-m..Hangul Choseong Ssangyeo {0x0a960, 0x0a97c}, // Hangul Choseong Tikeut-m..Hangul Choseong Ssangyeo
@@ -404,13 +425,18 @@ public final class WcWidth {
{0x0fe68, 0x0fe6b}, // Small Reverse Solidus ..Small Commercial At {0x0fe68, 0x0fe6b}, // Small Reverse Solidus ..Small Commercial At
{0x0ff01, 0x0ff60}, // Fullwidth Exclamation Ma..Fullwidth Right White Pa {0x0ff01, 0x0ff60}, // Fullwidth Exclamation Ma..Fullwidth Right White Pa
{0x0ffe0, 0x0ffe6}, // Fullwidth Cent Sign ..Fullwidth Won Sign {0x0ffe0, 0x0ffe6}, // Fullwidth Cent Sign ..Fullwidth Won Sign
{0x16fe0, 0x16fe4}, // Tangut Iteration Mark ..(nil) {0x16fe0, 0x16fe4}, // Tangut Iteration Mark ..Khitan Small Script Fill
{0x16ff0, 0x16ff1}, // (nil) ..(nil) {0x16ff0, 0x16ff1}, // Vietnamese Alternate Rea..Vietnamese Alternate Rea
{0x17000, 0x187f7}, // (nil) ..(nil) {0x17000, 0x187f7}, // (nil) ..(nil)
{0x18800, 0x18cd5}, // Tangut Component-001 ..(nil) {0x18800, 0x18cd5}, // Tangut Component-001 ..Khitan Small Script Char
{0x18d00, 0x18d08}, // (nil) ..(nil) {0x18d00, 0x18d08}, // (nil) ..(nil)
{0x1b000, 0x1b11e}, // Katakana Letter Archaic ..Hentaigana Letter N-mu-m {0x1aff0, 0x1aff3}, // Katakana Letter Minnan T..Katakana Letter Minnan T
{0x1aff5, 0x1affb}, // Katakana Letter Minnan T..Katakana Letter Minnan N
{0x1affd, 0x1affe}, // Katakana Letter Minnan N..Katakana Letter Minnan N
{0x1b000, 0x1b122}, // Katakana Letter Archaic ..Katakana Letter Archaic
{0x1b132, 0x1b132}, // (nil) ..(nil)
{0x1b150, 0x1b152}, // Hiragana Letter Small Wi..Hiragana Letter Small Wo {0x1b150, 0x1b152}, // Hiragana Letter Small Wi..Hiragana Letter Small Wo
{0x1b155, 0x1b155}, // (nil) ..(nil)
{0x1b164, 0x1b167}, // Katakana Letter Small Wi..Katakana Letter Small N {0x1b164, 0x1b167}, // Katakana Letter Small Wi..Katakana Letter Small N
{0x1b170, 0x1b2fb}, // Nushu Character-1b170 ..Nushu Character-1b2fb {0x1b170, 0x1b2fb}, // Nushu Character-1b170 ..Nushu Character-1b2fb
{0x1f004, 0x1f004}, // Mahjong Tile Red Dragon ..Mahjong Tile Red Dragon {0x1f004, 0x1f004}, // Mahjong Tile Red Dragon ..Mahjong Tile Red Dragon
@@ -443,24 +469,24 @@ public final class WcWidth {
{0x1f680, 0x1f6c5}, // Rocket ..Left Luggage {0x1f680, 0x1f6c5}, // Rocket ..Left Luggage
{0x1f6cc, 0x1f6cc}, // Sleeping Accommodation ..Sleeping Accommodation {0x1f6cc, 0x1f6cc}, // Sleeping Accommodation ..Sleeping Accommodation
{0x1f6d0, 0x1f6d2}, // Place Of Worship ..Shopping Trolley {0x1f6d0, 0x1f6d2}, // Place Of Worship ..Shopping Trolley
{0x1f6d5, 0x1f6d7}, // Hindu Temple ..(nil) {0x1f6d5, 0x1f6d7}, // Hindu Temple ..Elevator
{0x1f6dc, 0x1f6df}, // (nil) ..Ring Buoy
{0x1f6eb, 0x1f6ec}, // Airplane Departure ..Airplane Arriving {0x1f6eb, 0x1f6ec}, // Airplane Departure ..Airplane Arriving
{0x1f6f4, 0x1f6fc}, // Scooter ..(nil) {0x1f6f4, 0x1f6fc}, // Scooter ..Roller Skate
{0x1f7e0, 0x1f7eb}, // Large Orange Circle ..Large Brown Square {0x1f7e0, 0x1f7eb}, // Large Orange Circle ..Large Brown Square
{0x1f90c, 0x1f93a}, // (nil) ..Fencer {0x1f7f0, 0x1f7f0}, // Heavy Equals Sign ..Heavy Equals Sign
{0x1f90c, 0x1f93a}, // Pinched Fingers ..Fencer
{0x1f93c, 0x1f945}, // Wrestlers ..Goal Net {0x1f93c, 0x1f945}, // Wrestlers ..Goal Net
{0x1f947, 0x1f978}, // First Place Medal ..(nil) {0x1f947, 0x1f9ff}, // First Place Medal ..Nazar Amulet
{0x1f97a, 0x1f9cb}, // Face With Pleading Eyes ..(nil) {0x1fa70, 0x1fa7c}, // Ballet Shoes ..Crutch
{0x1f9cd, 0x1f9ff}, // Standing Person ..Nazar Amulet {0x1fa80, 0x1fa88}, // Yo-yo ..(nil)
{0x1fa70, 0x1fa74}, // Ballet Shoes ..(nil) {0x1fa90, 0x1fabd}, // Ringed Planet ..(nil)
{0x1fa78, 0x1fa7a}, // Drop Of Blood ..Stethoscope {0x1fabf, 0x1fac5}, // (nil) ..Person With Crown
{0x1fa80, 0x1fa86}, // Yo-yo ..(nil) {0x1face, 0x1fadb}, // (nil) ..(nil)
{0x1fa90, 0x1faa8}, // Ringed Planet ..(nil) {0x1fae0, 0x1fae8}, // Melting Face ..(nil)
{0x1fab0, 0x1fab6}, // (nil) ..(nil) {0x1faf0, 0x1faf8}, // Hand With Index Finger A..(nil)
{0x1fac0, 0x1fac2}, // (nil) ..(nil)
{0x1fad0, 0x1fad6}, // (nil) ..(nil)
{0x20000, 0x2fffd}, // Cjk Unified Ideograph-20..(nil) {0x20000, 0x2fffd}, // Cjk Unified Ideograph-20..(nil)
{0x30000, 0x3fffd}, // (nil) ..(nil) {0x30000, 0x3fffd}, // Cjk Unified Ideograph-30..(nil)
}; };
@@ -512,4 +538,29 @@ public final class WcWidth {
return Character.isHighSurrogate(c) ? width(Character.toCodePoint(c, chars[index + 1])) : width(c); return Character.isHighSurrogate(c) ? width(Character.toCodePoint(c, chars[index + 1])) : width(c);
} }
/**
* The zero width characters count like combining characters in the `chars` array from start
* index to end index (exclusive).
*/
public static int zeroWidthCharsCount(char[] chars, int start, int end) {
if (start < 0 || start >= chars.length)
return 0;
int count = 0;
for (int i = start; i < end && i < chars.length;) {
if (Character.isHighSurrogate(chars[i])) {
if (width(Character.toCodePoint(chars[i], chars[i + 1])) <= 0) {
count++;
}
i += 2;
} else {
if (width(chars[i]) <= 0) {
count++;
}
i++;
}
}
return count;
}
} }

View File

@@ -151,6 +151,19 @@ public class TerminalTest extends TerminalTestCase {
assertEquals(TextStyle.COLOR_INDEX_FOREGROUND, mTerminal.mForeColor); assertEquals(TextStyle.COLOR_INDEX_FOREGROUND, mTerminal.mForeColor);
assertEquals(TextStyle.COLOR_INDEX_BACKGROUND, mTerminal.mBackColor); assertEquals(TextStyle.COLOR_INDEX_BACKGROUND, mTerminal.mBackColor);
// Test CSI resetting to default if sequence starts with ; or has sequential ;;
// Check TerminalEmulator.parseArg()
enterString("\033[31m\033[m");
assertEquals(TextStyle.COLOR_INDEX_FOREGROUND, mTerminal.mForeColor);
enterString("\033[31m\033[;m");
assertEquals(TextStyle.COLOR_INDEX_FOREGROUND, mTerminal.mForeColor);
enterString("\033[31m\033[0m");
assertEquals(TextStyle.COLOR_INDEX_FOREGROUND, mTerminal.mForeColor);
enterString("\033[31m\033[0;m");
assertEquals(TextStyle.COLOR_INDEX_FOREGROUND, mTerminal.mForeColor);
enterString("\033[31;;m");
assertEquals(TextStyle.COLOR_INDEX_FOREGROUND, mTerminal.mForeColor);
// 256 colors: // 256 colors:
enterString("\033[38;5;119m"); enterString("\033[38;5;119m");
assertEquals(119, mTerminal.mForeColor); assertEquals(119, mTerminal.mForeColor);

View File

@@ -183,14 +183,19 @@ public class TextSelectionCursorController implements CursorController {
int y1 = Math.round((mSelY1 - 1 - terminalView.getTopRow()) * terminalView.mRenderer.getFontLineSpacing()); int y1 = Math.round((mSelY1 - 1 - terminalView.getTopRow()) * terminalView.mRenderer.getFontLineSpacing());
int y2 = Math.round((mSelY2 + 1 - terminalView.getTopRow()) * terminalView.mRenderer.getFontLineSpacing()); int y2 = Math.round((mSelY2 + 1 - terminalView.getTopRow()) * terminalView.mRenderer.getFontLineSpacing());
if (x1 > x2) { if (x1 > x2) {
int tmp = x1; int tmp = x1;
x1 = x2; x1 = x2;
x2 = tmp; x2 = tmp;
} }
outRect.set(x1, y1 + mHandleHeight, x2, y2 + mHandleHeight); int terminalBottom = terminalView.getBottom();
int top = y1 + mHandleHeight;
int bottom = y2 + mHandleHeight;
if (top > terminalBottom) top = terminalBottom;
if (bottom > terminalBottom) bottom = terminalBottom;
outRect.set(x1, top, x2, bottom);
} }
}, ActionMode.TYPE_FLOATING); }, ActionMode.TYPE_FLOATING);
} }

View File

@@ -4,11 +4,14 @@ import android.app.Activity;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.graphics.Color;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.widget.Button;
import android.widget.TextView; import android.widget.TextView;
import com.termux.shared.R; import com.termux.shared.R;
import com.termux.shared.logger.Logger;
public class MessageDialogUtils { public class MessageDialogUtils {
@@ -74,7 +77,19 @@ public class MessageDialogUtils {
if (onDismiss != null) if (onDismiss != null)
builder.setOnDismissListener(onDismiss); builder.setOnDismissListener(onDismiss);
builder.show(); AlertDialog dialog = builder.create();
dialog.setOnShowListener(dialogInterface -> {
Logger.logError("dialog");
Button button = dialog.getButton(AlertDialog.BUTTON_POSITIVE);
if (button != null)
button.setTextColor(Color.BLACK);
button = dialog.getButton(AlertDialog.BUTTON_NEGATIVE);
if (button != null)
button.setTextColor(Color.BLACK);
});
dialog.show();
} }
public static void exitAppWithErrorMessage(Context context, String titleText, String messageText) { public static void exitAppWithErrorMessage(Context context, String titleText, String messageText) {

View File

@@ -7,6 +7,7 @@ import android.util.Log;
import android.widget.Toast; import android.widget.Toast;
import com.termux.shared.R; import com.termux.shared.R;
import com.termux.shared.data.DataUtils;
import com.termux.shared.termux.TermuxConstants; import com.termux.shared.termux.TermuxConstants;
import java.io.IOException; import java.io.IOException;
@@ -363,7 +364,7 @@ public class Logger {
public static void showToast(final Context context, final String toastText, boolean longDuration) { public static void showToast(final Context context, final String toastText, boolean longDuration) {
if (context == null) return; if (context == null || DataUtils.isNullOrEmpty(toastText)) return;
new Handler(Looper.getMainLooper()).post(() -> Toast.makeText(context, toastText, longDuration ? Toast.LENGTH_LONG : Toast.LENGTH_SHORT).show()); new Handler(Looper.getMainLooper()).post(() -> Toast.makeText(context, toastText, longDuration ? Toast.LENGTH_LONG : Toast.LENGTH_SHORT).show());
} }

View File

@@ -191,9 +191,8 @@ public class MarkdownUtils {
} }
public static Spanned getSpannedMarkdownText(Context context, String string) { public static Spanned getSpannedMarkdownText(Context context, String string) {
if (context == null || string == null) return null;
final Markwon markwon = getSpannedMarkwonBuilder(context); final Markwon markwon = getSpannedMarkwonBuilder(context);
return markwon.toMarkdown(string); return markwon.toMarkdown(string);
} }

View File

@@ -485,6 +485,12 @@ public final class TermuxConstants {
* Termux miscellaneous urls. * Termux miscellaneous urls.
*/ */
/** Termux Site */
public static final String TERMUX_SITE = TERMUX_APP_NAME + " Site"; // Default: "Termux Site"
/** Termux Site url */
public static final String TERMUX_SITE_URL = "https://termux.dev"; // Default: "https://termux.dev"
/** Termux Wiki */ /** Termux Wiki */
public static final String TERMUX_WIKI = TERMUX_APP_NAME + " Wiki"; // Default: "Termux Wiki" public static final String TERMUX_WIKI = TERMUX_APP_NAME + " Wiki"; // Default: "Termux Wiki"
@@ -499,10 +505,10 @@ public final class TermuxConstants {
/** Termux support email url */ /** Termux support email url */
public static final String TERMUX_SUPPORT_EMAIL_URL = "termuxreports@groups.io"; // Default: "termuxreports@groups.io" public static final String TERMUX_SUPPORT_EMAIL_URL = "support@termux.dev"; // Default: "support@termux.dev"
/** Termux support email mailto url */ /** Termux support email mailto url */
public static final String TERMUX_SUPPORT_EMAIL_MAILTO_URL = "mailto:" + TERMUX_SUPPORT_EMAIL_URL; // Default: "mailto:termuxreports@groups.io" public static final String TERMUX_SUPPORT_EMAIL_MAILTO_URL = "mailto:" + TERMUX_SUPPORT_EMAIL_URL; // Default: "mailto:support@termux.dev"
/** Termux Reddit subreddit */ /** Termux Reddit subreddit */
@@ -513,7 +519,7 @@ public final class TermuxConstants {
/** Termux donate url */ /** Termux donate url */
public static final String TERMUX_DONATE_URL = TERMUX_PACKAGES_GITHUB_REPO_URL + "/wiki/Donate"; // Default: "https://github.com/termux/termux-packages/wiki/Donate" public static final String TERMUX_DONATE_URL = TERMUX_SITE_URL + "/donate"; // Default: "https://termux.dev/donate"