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}_x86_64.apk" \
"${APK_BASENAME_PREFIX}_x86.apk" \
> sha256sums); then
> "${APK_BASENAME_PREFIX}_sha256sums"); then
exit_on_error "Generate sha25sums failed for '$RELEASE_VERSION_NAME' release."
fi
echo "Attaching APKs to github release"
if ! hub release edit \
-m "" \
-a "$APK_DIR_PATH/${APK_BASENAME_PREFIX}_universal.apk" \
-a "$APK_DIR_PATH/${APK_BASENAME_PREFIX}_arm64-v8a.apk" \
-a "$APK_DIR_PATH/${APK_BASENAME_PREFIX}_armeabi-v7a.apk" \
-a "$APK_DIR_PATH/${APK_BASENAME_PREFIX}_x86_64.apk" \
-a "$APK_DIR_PATH/${APK_BASENAME_PREFIX}_x86.apk" \
-a "$APK_DIR_PATH/sha256sums" \
"$RELEASE_VERSION_NAME"; then
if ! gh release upload "$RELEASE_VERSION_NAME" \
"$APK_DIR_PATH/${APK_BASENAME_PREFIX}_universal.apk" \
"$APK_DIR_PATH/${APK_BASENAME_PREFIX}_arm64-v8a.apk" \
"$APK_DIR_PATH/${APK_BASENAME_PREFIX}_armeabi-v7a.apk" \
"$APK_DIR_PATH/${APK_BASENAME_PREFIX}_x86_64.apk" \
"$APK_DIR_PATH/${APK_BASENAME_PREFIX}_x86.apk" \
"$APK_DIR_PATH/${APK_BASENAME_PREFIX}_sha256sums" \
; then
exit_on_error "Attach APKs to release failed for '$RELEASE_VERSION_NAME' release."
fi

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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