├── termux-shared ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── raw │ │ │ │ ├── bell.ogg │ │ │ │ └── apt_info_script.sh │ │ │ ├── values │ │ │ │ ├── dimens.xml │ │ │ │ ├── colors.xml │ │ │ │ └── themes.xml │ │ │ ├── drawable │ │ │ │ ├── ic_copy.xml │ │ │ │ ├── ic_share.xml │ │ │ │ └── ic_error_notification.xml │ │ │ ├── layout │ │ │ │ ├── markdown_adapter_node_default.xml │ │ │ │ ├── activity_report.xml │ │ │ │ ├── partial_toolbar.xml │ │ │ │ ├── markdown_adapter_node_code_block.xml │ │ │ │ ├── dialog_show_message.xml │ │ │ │ └── activity_text_io.xml │ │ │ ├── menu │ │ │ │ ├── menu_text_io.xml │ │ │ │ └── menu_report.xml │ │ │ └── values-night │ │ │ │ └── themes.xml │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── com │ │ │ └── termux │ │ │ └── shared │ │ │ ├── file │ │ │ └── filesystem │ │ │ │ ├── FileType.java │ │ │ │ ├── NativeDispatcher.java │ │ │ │ ├── FileKey.java │ │ │ │ └── FilePermission.java │ │ │ ├── settings │ │ │ └── properties │ │ │ │ └── SharedPropertiesParser.java │ │ │ ├── crash │ │ │ └── TermuxCrashUtils.java │ │ │ ├── shell │ │ │ ├── TermuxShellEnvironmentClient.java │ │ │ ├── ShellEnvironmentClient.java │ │ │ └── ShellUtils.java │ │ │ ├── models │ │ │ ├── errors │ │ │ │ ├── ResultSenderErrno.java │ │ │ │ ├── FunctionErrno.java │ │ │ │ └── Errno.java │ │ │ └── ReportInfo.java │ │ │ ├── terminal │ │ │ ├── io │ │ │ │ ├── extrakeys │ │ │ │ │ ├── SpecialButton.java │ │ │ │ │ └── SpecialButtonState.java │ │ │ │ ├── BellHandler.java │ │ │ │ └── TerminalExtraKeys.java │ │ │ ├── TermuxTerminalSessionClientBase.java │ │ │ └── TermuxTerminalViewClientBase.java │ │ │ ├── notification │ │ │ └── TermuxNotificationUtils.java │ │ │ └── interact │ │ │ ├── TextInputDialogUtils.java │ │ │ └── MessageDialogUtils.java │ └── androidTest │ │ └── java │ │ └── com │ │ └── termux │ │ └── shared │ │ └── ExampleInstrumentedTest.java ├── proguard-rules.pro └── build.gradle ├── jitpack.yml ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── 02-feature-request.yml │ └── 01-bug-report.yml └── workflows │ ├── run_tests.yml │ ├── gradle-wrapper-validation.yml │ ├── trigger_library_builds_on_jitpack.yml │ └── attach_debug_apks_to_release.yml ├── app ├── dev_keystore.jks ├── src │ ├── main │ │ ├── res │ │ │ ├── values │ │ │ │ ├── colors.xml │ │ │ │ └── styles.xml │ │ │ ├── drawable │ │ │ │ ├── banner.png │ │ │ │ ├── current_session.xml │ │ │ │ ├── current_session_black.xml │ │ │ │ ├── session_ripple.xml │ │ │ │ ├── session_ripple_black.xml │ │ │ │ ├── session_background_selected.xml │ │ │ │ ├── session_background_black_selected.xml │ │ │ │ ├── terminal_scroll_shape.xml │ │ │ │ ├── ic_new_session.xml │ │ │ │ ├── ic_service_notification.xml │ │ │ │ ├── ic_foreground.xml │ │ │ │ └── ic_settings.xml │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ ├── layout │ │ │ │ ├── activity_settings.xml │ │ │ │ ├── view_terminal_toolbar_extra_keys.xml │ │ │ │ ├── item_terminal_sessions_list.xml │ │ │ │ ├── view_terminal_toolbar_text_input.xml │ │ │ │ └── preference_markdown_text.xml │ │ │ └── xml │ │ │ │ ├── termux_api_preferences.xml │ │ │ │ ├── termux_float_preferences.xml │ │ │ │ ├── termux_tasker_preferences.xml │ │ │ │ ├── termux_widget_preferences.xml │ │ │ │ ├── termux_api_debugging_preferences.xml │ │ │ │ ├── termux_tasker_debugging_preferences.xml │ │ │ │ ├── termux_widget_debugging_preferences.xml │ │ │ │ ├── termux_terminal_view_preferences.xml │ │ │ │ ├── termux_float_debugging_preferences.xml │ │ │ │ ├── termux_preferences.xml │ │ │ │ ├── termux_terminal_io_preferences.xml │ │ │ │ ├── termux_debugging_preferences.xml │ │ │ │ ├── root_preferences.xml │ │ │ │ └── shortcuts.xml │ │ ├── cpp │ │ │ ├── Android.mk │ │ │ ├── termux-bootstrap.c │ │ │ └── termux-bootstrap-zip.S │ │ └── java │ │ │ └── com │ │ │ └── termux │ │ │ └── app │ │ │ ├── terminal │ │ │ └── io │ │ │ │ ├── KeyboardShortcut.java │ │ │ │ ├── TermuxTerminalExtraKeys.java │ │ │ │ └── FullScreenWorkAround.java │ │ │ ├── models │ │ │ └── UserAction.java │ │ │ ├── TermuxApplication.java │ │ │ ├── fragments │ │ │ └── settings │ │ │ │ ├── TermuxPreferencesFragment.java │ │ │ │ ├── TermuxAPIPreferencesFragment.java │ │ │ │ ├── TermuxFloatPreferencesFragment.java │ │ │ │ ├── TermuxTaskerPreferencesFragment.java │ │ │ │ ├── TermuxWidgetPreferencesFragment.java │ │ │ │ ├── termux │ │ │ │ ├── TerminalViewPreferencesFragment.java │ │ │ │ └── TerminalIOPreferencesFragment.java │ │ │ │ ├── termux_api │ │ │ │ └── DebuggingPreferencesFragment.java │ │ │ │ ├── termux_tasker │ │ │ │ └── DebuggingPreferencesFragment.java │ │ │ │ └── termux_widget │ │ │ │ └── DebuggingPreferencesFragment.java │ │ │ └── activities │ │ │ └── HelpActivity.java │ └── test │ │ └── java │ │ └── com │ │ └── termux │ │ ├── app │ │ └── TermuxActivityTest.java │ │ └── filepicker │ │ └── TermuxFileReceiverActivityTest.java └── proguard-rules.pro ├── art ├── ic_launcher2.png ├── ic_launcher2_round.png ├── generate-feature-graphic.sh ├── copy-to-other-apps.sh ├── generate-tv-banner.sh ├── generate-launcher-images.sh ├── ic_launcher.svg ├── generate-big-icon.sh ├── tv-banner.svg └── feature-graphic.svg ├── settings.gradle ├── terminal-view ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ ├── res │ │ ├── values │ │ │ └── strings.xml │ │ └── drawable │ │ │ ├── text_select_handle_right_material.xml │ │ │ └── text_select_handle_left_material.xml │ │ └── java │ │ └── com │ │ └── termux │ │ └── view │ │ ├── textselection │ │ └── CursorController.java │ │ └── TerminalViewClient.java ├── proguard-rules.pro └── build.gradle ├── terminal-emulator ├── src │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── jni │ │ │ └── Android.mk │ │ └── java │ │ │ └── com │ │ │ └── termux │ │ │ └── terminal │ │ │ ├── TerminalOutput.java │ │ │ ├── TerminalSessionClient.java │ │ │ ├── JNI.java │ │ │ ├── TerminalColors.java │ │ │ └── TextStyle.java │ └── test │ │ └── java │ │ └── com │ │ └── termux │ │ └── terminal │ │ ├── HistoryTest.java │ │ ├── ByteQueueTest.java │ │ ├── DeviceControlStringTest.java │ │ ├── WcWidthTest.java │ │ ├── TextStyleTest.java │ │ ├── ScreenBufferTest.java │ │ ├── DecSetTest.java │ │ └── ControlSequenceIntroducerTest.java ├── proguard-rules.pro └── build.gradle ├── fastlane └── metadata │ └── android │ └── en-US │ ├── short_description.txt │ ├── images │ ├── icon.png │ └── phoneScreenshots │ │ ├── 1.jpg │ │ ├── 2.jpg │ │ ├── 3.jpg │ │ └── 4.jpg │ └── full_description.txt ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitattributes ├── .editorconfig ├── LICENSE.md ├── .gitignore ├── gradle.properties └── gradlew.bat /termux-shared/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /jitpack.yml: -------------------------------------------------------------------------------- 1 | env: 2 | JITPACK_NDK_VERSION: "21.1.6352462" 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | patreon: termux 2 | custom: https://paypal.me/fornwall 3 | -------------------------------------------------------------------------------- /app/dev_keystore.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hax4us/termux-app/HEAD/app/dev_keystore.jks -------------------------------------------------------------------------------- /art/ic_launcher2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hax4us/termux-app/HEAD/art/ic_launcher2.png -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':termux-shared', ':terminal-emulator', ':terminal-view' 2 | -------------------------------------------------------------------------------- /terminal-view/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /art/ic_launcher2_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hax4us/termux-app/HEAD/art/ic_launcher2_round.png -------------------------------------------------------------------------------- /terminal-emulator/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/short_description.txt: -------------------------------------------------------------------------------- 1 | Terminal emulator app with a large set of command line utilities 2 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hax4us/termux-app/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/drawable/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hax4us/termux-app/HEAD/app/src/main/res/drawable/banner.png -------------------------------------------------------------------------------- /termux-shared/src/main/res/raw/bell.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hax4us/termux-app/HEAD/termux-shared/src/main/res/raw/bell.ogg -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hax4us/termux-app/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hax4us/termux-app/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hax4us/termux-app/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hax4us/termux-app/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hax4us/termux-app/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hax4us/termux-app/HEAD/fastlane/metadata/android/en-US/images/icon.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hax4us/termux-app/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hax4us/termux-app/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.bat text eol=crlf 3 | *.gradle text eol=lf 4 | *.mk text eol=lf 5 | *.sh text eol=lf 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hax4us/termux-app/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hax4us/termux-app/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hax4us/termux-app/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hax4us/termux-app/HEAD/fastlane/metadata/android/en-US/images/phoneScreenshots/1.jpg -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hax4us/termux-app/HEAD/fastlane/metadata/android/en-US/images/phoneScreenshots/2.jpg -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hax4us/termux-app/HEAD/fastlane/metadata/android/en-US/images/phoneScreenshots/3.jpg -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hax4us/termux-app/HEAD/fastlane/metadata/android/en-US/images/phoneScreenshots/4.jpg -------------------------------------------------------------------------------- /terminal-emulator/src/main/jni/Android.mk: -------------------------------------------------------------------------------- 1 | LOCAL_PATH:= $(call my-dir) 2 | include $(CLEAR_VARS) 3 | LOCAL_MODULE:= libtermux 4 | LOCAL_SRC_FILES:= termux.c 5 | include $(BUILD_SHARED_LIBRARY) 6 | -------------------------------------------------------------------------------- /app/src/main/cpp/Android.mk: -------------------------------------------------------------------------------- 1 | LOCAL_PATH:= $(call my-dir) 2 | include $(CLEAR_VARS) 3 | LOCAL_MODULE := libtermux-bootstrap 4 | LOCAL_SRC_FILES := termux-bootstrap-zip.S termux-bootstrap.c 5 | include $(BUILD_SHARED_LIBRARY) 6 | -------------------------------------------------------------------------------- /terminal-view/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Paste 3 | Copy 4 | More… 5 | 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Want ask questions about the project? 4 | url: https://github.com/termux/termux-app/discussions 5 | about: Join GitHub Discussions 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/current_session.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /art/generate-feature-graphic.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "Generating feature graphics to ~/termux-icons/termux-feature-graphic.png..." 4 | mkdir -p ~/termux-icons/ 5 | rsvg-convert feature-graphic.svg > ~/termux-icons/feature-graphic.png 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/current_session_black.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /termux-shared/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/session_ripple.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/session_ripple_black.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/session_background_selected.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/session_background_black_selected.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_settings.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/xml/termux_api_preferences.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/cpp/termux-bootstrap.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | extern jbyte blob[]; 4 | extern int blob_size; 5 | 6 | JNIEXPORT jbyteArray JNICALL Java_com_termux_app_TermuxInstaller_getZip(JNIEnv *env, __attribute__((__unused__)) jobject This) 7 | { 8 | jbyteArray ret = (*env)->NewByteArray(env, blob_size); 9 | (*env)->SetByteArrayRegion(env, ret, 0, blob_size, blob); 10 | return ret; 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/res/xml/termux_float_preferences.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/xml/termux_tasker_preferences.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/xml/termux_widget_preferences.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/java/com/termux/app/terminal/io/KeyboardShortcut.java: -------------------------------------------------------------------------------- 1 | package com.termux.app.terminal.io; 2 | 3 | public class KeyboardShortcut { 4 | 5 | public final int codePoint; 6 | public final int shortcutAction; 7 | 8 | public KeyboardShortcut(int codePoint, int shortcutAction) { 9 | this.codePoint = codePoint; 10 | this.shortcutAction = shortcutAction; 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /termux-shared/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | -dontobfuscate 9 | #-renamesourcefileattribute SourceFile 10 | #-keepattributes SourceFile,LineNumberTable 11 | -------------------------------------------------------------------------------- /art/copy-to-other-apps.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e -u 3 | 4 | for APP in api boot styling tasker widget; do 5 | APPDIR=../../termux-$APP 6 | for file in ic_foreground ic_launcher; do 7 | cp ../app/src/main/res/drawable/$file.xml \ 8 | $APPDIR/app/src/main/res/drawable/$file.xml 9 | done 10 | 11 | cp ../app/src/main/res/drawable-anydpi-v26/ic_launcher.xml \ 12 | $APPDIR/app/src/main/res/drawable-anydpi-v26/$file.xml 13 | done 14 | -------------------------------------------------------------------------------- /.github/workflows/run_tests.yml: -------------------------------------------------------------------------------- 1 | name: Unit tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - android-10 8 | pull_request: 9 | branches: 10 | - master 11 | - android-10 12 | 13 | jobs: 14 | testing: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Clone repository 18 | uses: actions/checkout@v2 19 | - name: Execute tests 20 | run: | 21 | ./gradlew test 22 | -------------------------------------------------------------------------------- /art/generate-tv-banner.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "Generating feature graphics to ~/termux-icons/termux-feature-graphic.png..." 4 | mkdir -p ~/termux-icons/ 5 | 6 | # The Android TV banner on google play (1280x720) has same aspect ratio 7 | # as the banner in the app (320x180). 8 | rsvg-convert -w 1280 -h 720 tv-banner.svg > ~/termux-icons/tv-banner.png 9 | rsvg-convert -w 320 -h 180 tv-banner.svg > ../app/src/main/res/drawable/banner.png 10 | -------------------------------------------------------------------------------- /.github/workflows/gradle-wrapper-validation.yml: -------------------------------------------------------------------------------- 1 | name: "Validate Gradle Wrapper" 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - android-10 8 | pull_request: 9 | branches: 10 | - master 11 | - android-10 12 | 13 | jobs: 14 | validation: 15 | name: "Validation" 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v2 19 | - uses: gradle/wrapper-validation-action@v1 20 | -------------------------------------------------------------------------------- /app/src/main/res/layout/view_terminal_toolbar_extra_keys.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | -------------------------------------------------------------------------------- /termux-shared/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16dp 5 | 16dp 6 | 7 | 8dip 8 | 16dip 9 | 4dip 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_terminal_sessions_list.xml: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /termux-shared/src/main/res/drawable/ic_copy.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/cpp/termux-bootstrap-zip.S: -------------------------------------------------------------------------------- 1 | .global blob 2 | .global blob_size 3 | .section .rodata 4 | blob: 5 | #if defined __i686__ 6 | .incbin "bootstrap-i686.zip" 7 | #elif defined __x86_64__ 8 | .incbin "bootstrap-x86_64.zip" 9 | #elif defined __aarch64__ 10 | .incbin "bootstrap-aarch64.zip" 11 | #elif defined __arm__ 12 | .incbin "bootstrap-arm.zip" 13 | #else 14 | # error Unsupported arch 15 | #endif 16 | 1: 17 | blob_size: 18 | .int 1b - blob 19 | -------------------------------------------------------------------------------- /art/generate-launcher-images.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | for DENSITY in mdpi hdpi xhdpi xxhdpi xxxhdpi; do 4 | case $DENSITY in 5 | mdpi) SIZE=48;; 6 | hdpi) SIZE=72;; 7 | xhdpi) SIZE=96;; 8 | xxhdpi) SIZE=144;; 9 | xxxhdpi) SIZE=192;; 10 | esac 11 | 12 | FOLDER=../app/src/main/res/mipmap-$DENSITY 13 | mkdir -p $FOLDER 14 | 15 | for FILE in ic_launcher ic_launcher_round; do 16 | PNG=$FOLDER/$FILE.png 17 | rsvg-convert -w $SIZE -h $SIZE $FILE.svg > $PNG 18 | zopflipng -y $PNG $PNG 19 | done 20 | done 21 | -------------------------------------------------------------------------------- /app/src/main/res/xml/termux_api_debugging_preferences.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/xml/termux_tasker_debugging_preferences.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/xml/termux_widget_debugging_preferences.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Copying and distribution of this file, with or without modification, 2 | # are permitted in any medium without royalty provided this notice is 3 | # preserved. This file is offered as-is, without any warranty. 4 | 5 | # EditorConfig 6 | # http://EditorConfig.org 7 | 8 | # top-most EditorConfig file 9 | root = true 10 | 11 | [*] 12 | end_of_line = lf 13 | insert_final_newline = true 14 | charset = utf-8 15 | indent_style = space 16 | indent_size = 4 17 | 18 | [*.y{a,}ml] 19 | indent_size = 2 20 | indent_style = space 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/termux/app/models/UserAction.java: -------------------------------------------------------------------------------- 1 | package com.termux.app.models; 2 | 3 | public enum UserAction { 4 | 5 | ABOUT("about"), 6 | CRASH_REPORT("crash report"), 7 | PLUGIN_EXECUTION_COMMAND("plugin execution command"), 8 | REPORT_ISSUE_FROM_TRANSCRIPT("report issue from transcript"); 9 | 10 | private final String name; 11 | 12 | UserAction(final String name) { 13 | this.name = name; 14 | } 15 | 16 | public String getName() { 17 | return name; 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The `termux/termux-app` repository is released under [GPLv3 only](https://www.gnu.org/licenses/gpl-3.0.html) license. 2 | 3 | ### Exceptions 4 | 5 | - [Terminal Emulator for Android](https://github.com/jackpal/Android-Terminal-Emulator) code is used which is released under [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). Check [`terminal-view`](terminal-view) and [`terminal-emulator`](terminal-emulator) libraries. 6 | - Check [`termux-shared/LICENSE.md`](termux-shared/LICENSE.md) for `termux-shared` library related exceptions. 7 | -------------------------------------------------------------------------------- /terminal-view/src/main/res/drawable/text_select_handle_right_material.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/terminal_scroll_shape.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 21 | 22 | -------------------------------------------------------------------------------- /terminal-view/src/main/res/drawable/text_select_handle_left_material.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 11 | -------------------------------------------------------------------------------- /art/ic_launcher.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | 19 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /art/generate-big-icon.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e -u 3 | 4 | echo "Generating ~/termux-icons/ic_launcher.png..." 5 | mkdir -p ~/termux-icons/ 6 | 7 | vector2svg ../app/src/main/res/drawable/ic_launcher.xml ~/termux-icons/ic_launcher.svg 8 | 9 | sed -i "" 's/viewBox="0 0 108 108"/viewBox="18 18 72 72"/' ~/termux-icons/ic_launcher.svg 10 | 11 | SIZE=512 12 | rsvg-convert \ 13 | -w $SIZE \ 14 | -h $SIZE \ 15 | -o ~/termux-icons/ic_launcher_$SIZE.png \ 16 | ~/termux-icons/ic_launcher.svg 17 | 18 | rsvg-convert \ 19 | -b black \ 20 | -w $SIZE \ 21 | -h $SIZE \ 22 | -o ~/termux-icons/ic_launcher_square_$SIZE.png \ 23 | ~/termux-icons/ic_launcher.svg 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/02-feature-request.yml: -------------------------------------------------------------------------------- 1 | name: "Feature request" 2 | description: "Suggest a new feature for Termux application" 3 | title: "[Feature]: " 4 | labels: ["feature request"] 5 | body: 6 | - type: textarea 7 | attributes: 8 | label: Feature description 9 | description: Describe the feature and why you want it. 10 | validations: 11 | required: true 12 | - type: textarea 13 | attributes: 14 | label: Additional information 15 | description: | 16 | Does another app/terminal emulator have this feature? 17 | Provide links to more background information. 18 | validations: 19 | required: true 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # From https://gist.github.com/iainconnor/8605514 2 | # with the addition of the /captures below. 3 | /captures 4 | 5 | # Built application files 6 | build/ 7 | release/ 8 | *.apk 9 | *.so 10 | .externalNativeBuild 11 | .cxx 12 | *.zip 13 | 14 | # Crashlytics configuations 15 | com_crashlytics_export_strings.xml 16 | 17 | # Local configuration file (sdk path, etc) 18 | local.properties 19 | 20 | # Gradle generated files 21 | .gradle/ 22 | 23 | # Signing files 24 | .signing/ 25 | 26 | # Intellij 27 | .idea/ 28 | *.iml 29 | 30 | # OS-specific files 31 | .DS_Store 32 | .DS_Store? 33 | ._* 34 | .Spotlight-V100 35 | .Trashes 36 | .swp 37 | ehthumbs.db 38 | Thumbs.db 39 | -------------------------------------------------------------------------------- /app/src/main/res/xml/termux_terminal_view_preferences.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /termux-shared/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #1F000000 4 | #0F000000 5 | 6 | #FF000000 7 | #FFFFFFFF 8 | 9 | #FF0000 10 | #C4001D 11 | 12 | #EEEEEE 13 | #424242 14 | #212121 15 | 16 | #DC143C 17 | #FC143C 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_new_session.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 13 | 14 | 17 | 18 | -------------------------------------------------------------------------------- /termux-shared/src/main/res/layout/markdown_adapter_node_default.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | -------------------------------------------------------------------------------- /termux-shared/src/main/res/drawable/ic_share.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/layout/view_terminal_toolbar_text_input.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | -------------------------------------------------------------------------------- /termux-shared/src/main/res/menu/menu_text_io.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | 14 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in android-sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | -dontobfuscate 11 | #-renamesourcefileattribute SourceFile 12 | #-keepattributes SourceFile,LineNumberTable 13 | 14 | # Temp fix for androidx.window:window:1.0.0-alpha09 imported by termux-shared 15 | # https://issuetracker.google.com/issues/189001730 16 | # https://android-review.googlesource.com/c/platform/frameworks/support/+/1757630 17 | -keep class androidx.window.** { *; } 18 | -------------------------------------------------------------------------------- /termux-shared/src/main/res/menu/menu_report.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 15 | 16 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_service_notification.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 11 | 12 | 14 | 15 | 18 | 19 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /art/tv-banner.svg: -------------------------------------------------------------------------------- 1 | 2 | 10 | 13 | 14 | 15 | 16 | 20 | Termux ▌ 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /termux-shared/src/main/res/layout/activity_report.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/xml/termux_float_debugging_preferences.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 12 | 13 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_foreground.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 18 | 19 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/res/xml/termux_preferences.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 12 | 13 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /termux-shared/src/androidTest/java/com/termux/shared/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.termux.shared; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.test.platform.app.InstrumentationRegistry; 6 | import androidx.test.ext.junit.runners.AndroidJUnit4; 7 | 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | /** 14 | * Instrumented test, which will execute on an Android device. 15 | * 16 | * @see Testing documentation 17 | */ 18 | @RunWith(AndroidJUnit4.class) 19 | public class ExampleInstrumentedTest { 20 | @Test 21 | public void useAppContext() { 22 | // Context of the app under test. 23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 24 | assertEquals("com.termux.shared.test", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /termux-shared/src/main/java/com/termux/shared/file/filesystem/FileType.java: -------------------------------------------------------------------------------- 1 | package com.termux.shared.file.filesystem; 2 | 3 | /** The {@link Enum} that defines file types. */ 4 | public enum FileType { 5 | 6 | NO_EXIST("no exist", 0), // 0000000 7 | REGULAR("regular", 1), // 0000001 8 | DIRECTORY("directory", 2), // 0000010 9 | SYMLINK("symlink", 4), // 0000100 10 | CHARACTER("character", 8), // 0001000 11 | FIFO("fifo", 16), // 0010000 12 | BLOCK("block", 32), // 0100000 13 | UNKNOWN("unknown", 64); // 1000000 14 | 15 | private final String name; 16 | private final int value; 17 | 18 | FileType(final String name, final int value) { 19 | this.name = name; 20 | this.value = value; 21 | } 22 | 23 | public String getName() { 24 | return name; 25 | } 26 | 27 | public int getValue() { 28 | return value; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/res/layout/preference_markdown_text.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 10 | 11 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | ## Project-wide Gradle settings. 2 | # 3 | # For more details on how to configure your build environment visit 4 | # http://www.gradle.org/docs/current/userguide/build_environment.html 5 | # 6 | # Specifies the JVM arguments used for the daemon process. 7 | # The setting is particularly useful for tweaking memory settings. 8 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 9 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 10 | # 11 | # When configured, Gradle will run in incubating parallel mode. 12 | # This option should only be used with decoupled projects. More details, visit 13 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 14 | # org.gradle.parallel=true 15 | org.gradle.jvmargs=-Xmx2048M 16 | android.useAndroidX=true 17 | 18 | minSdkVersion=24 19 | targetSdkVersion=28 20 | ndkVersion=22.1.7171670 21 | compileSdkVersion=30 22 | 23 | markwonVersion=4.6.2 24 | 25 | -------------------------------------------------------------------------------- /termux-shared/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/xml/termux_terminal_io_preferences.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 12 | 13 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /termux-shared/src/main/res/layout/partial_toolbar.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /termux-shared/src/main/java/com/termux/shared/settings/properties/SharedPropertiesParser.java: -------------------------------------------------------------------------------- 1 | package com.termux.shared.settings.properties; 2 | 3 | import android.content.Context; 4 | 5 | import java.util.HashMap; 6 | 7 | /** 8 | * An interface that must be defined by the caller of the {@link SharedProperties} class. 9 | */ 10 | public interface SharedPropertiesParser { 11 | 12 | /** 13 | * A function that should return the internal {@link Object} to be stored for a key/value pair 14 | * read from properties file in the {@link HashMap <>} in-memory cache. 15 | * 16 | * @param context The context for operations. 17 | * @param key The key for which the internal object is required. 18 | * @param value The literal value for the property found is the properties file. 19 | * @return Returns the {@link Object} object to store in the {@link HashMap <>} in-memory cache. 20 | */ 21 | Object getInternalPropertyValueFromValue(Context context, String key, String value); 22 | 23 | } 24 | -------------------------------------------------------------------------------- /terminal-view/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/fornwall/lib/android-sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /terminal-emulator/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/fornwall/lib/android-sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/termux/app/TermuxApplication.java: -------------------------------------------------------------------------------- 1 | package com.termux.app; 2 | 3 | import android.app.Application; 4 | 5 | import com.termux.shared.crash.TermuxCrashUtils; 6 | import com.termux.shared.settings.preferences.TermuxAppSharedPreferences; 7 | import com.termux.shared.logger.Logger; 8 | 9 | 10 | public class TermuxApplication extends Application { 11 | public void onCreate() { 12 | super.onCreate(); 13 | 14 | // Set crash handler for the app 15 | TermuxCrashUtils.setCrashHandler(this); 16 | 17 | // Set log level for the app 18 | setLogLevel(); 19 | } 20 | 21 | private void setLogLevel() { 22 | // Load the log level from shared preferences and set it to the {@link Logger.CURRENT_LOG_LEVEL} 23 | TermuxAppSharedPreferences preferences = TermuxAppSharedPreferences.build(getApplicationContext()); 24 | if (preferences == null) return; 25 | preferences.setLogLevel(null, preferences.getLogLevel()); 26 | Logger.logDebug("Starting Application"); 27 | } 28 | } 29 | 30 | -------------------------------------------------------------------------------- /art/feature-graphic.svg: -------------------------------------------------------------------------------- 1 | 2 | 12 | 15 | 16 | 17 | 18 | 22 | 27 | $ Termux █ 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /termux-shared/src/main/java/com/termux/shared/crash/TermuxCrashUtils.java: -------------------------------------------------------------------------------- 1 | package com.termux.shared.crash; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.annotation.NonNull; 6 | 7 | import com.termux.shared.termux.TermuxConstants; 8 | import com.termux.shared.termux.TermuxUtils; 9 | 10 | public class TermuxCrashUtils implements CrashHandler.CrashHandlerClient { 11 | 12 | /** 13 | * Set default uncaught crash handler of current thread to {@link CrashHandler} for Termux app 14 | * and its plugin to log crashes at {@link TermuxConstants#TERMUX_CRASH_LOG_FILE_PATH}. 15 | */ 16 | public static void setCrashHandler(@NonNull final Context context) { 17 | CrashHandler.setCrashHandler(context, new TermuxCrashUtils()); 18 | } 19 | 20 | @NonNull 21 | @Override 22 | public String getCrashLogFilePath(Context context) { 23 | return TermuxConstants.TERMUX_CRASH_LOG_FILE_PATH; 24 | } 25 | 26 | @Override 27 | public String getAppInfoMarkdownString(Context context) { 28 | return TermuxUtils.getAppInfoMarkdownString(context, true); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /termux-shared/src/main/java/com/termux/shared/shell/TermuxShellEnvironmentClient.java: -------------------------------------------------------------------------------- 1 | package com.termux.shared.shell; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.annotation.NonNull; 6 | 7 | public class TermuxShellEnvironmentClient implements ShellEnvironmentClient { 8 | 9 | @NonNull 10 | @Override 11 | public String getDefaultWorkingDirectoryPath() { 12 | return TermuxShellUtils.getDefaultWorkingDirectoryPath(); 13 | } 14 | 15 | @NonNull 16 | @Override 17 | public String getDefaultBinPath() { 18 | return TermuxShellUtils.getDefaultBinPath(); 19 | } 20 | 21 | @NonNull 22 | @Override 23 | public String[] buildEnvironment(Context currentPackageContext, boolean isFailSafe, String workingDirectory) { 24 | return TermuxShellUtils.buildEnvironment(currentPackageContext, isFailSafe, workingDirectory); 25 | } 26 | 27 | @NonNull 28 | @Override 29 | public String[] setupProcessArgs(@NonNull String fileToExecute, String[] arguments) { 30 | return TermuxShellUtils.setupProcessArgs(fileToExecute, arguments); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /termux-shared/src/main/res/layout/markdown_adapter_node_code_block.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_settings.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /termux-shared/src/main/java/com/termux/shared/models/errors/ResultSenderErrno.java: -------------------------------------------------------------------------------- 1 | package com.termux.shared.models.errors; 2 | 3 | /** The {@link Class} that defines ResultSender error messages and codes. */ 4 | public class ResultSenderErrno extends Errno { 5 | 6 | public static final String TYPE = "ResultSender Error"; 7 | 8 | 9 | /* Errors for null or empty parameters (100-150) */ 10 | public static final Errno ERROR_RESULT_FILE_BASENAME_NULL_OR_INVALID = new Errno(TYPE, 100, "The result file basename \"%1$s\" is null, empty or contains forward slashes \"/\"."); 11 | public static final Errno ERROR_RESULT_FILES_SUFFIX_INVALID = new Errno(TYPE, 101, "The result files suffix \"%1$s\" contains forward slashes \"/\"."); 12 | public static final Errno ERROR_FORMAT_RESULT_ERROR_FAILED_WITH_EXCEPTION = new Errno(TYPE, 102, "Formatting result error failed.\nException: %1$s"); 13 | public static final Errno ERROR_FORMAT_RESULT_OUTPUT_FAILED_WITH_EXCEPTION = new Errno(TYPE, 103, "Formatting result output failed.\nException: %1$s"); 14 | 15 | 16 | ResultSenderErrno(final String type, final int code, final String message) { 17 | super(type, code, message); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /termux-shared/src/main/java/com/termux/shared/models/errors/FunctionErrno.java: -------------------------------------------------------------------------------- 1 | package com.termux.shared.models.errors; 2 | 3 | /** The {@link Class} that defines function error messages and codes. */ 4 | public class FunctionErrno extends Errno { 5 | 6 | public static final String TYPE = "Function Error"; 7 | 8 | 9 | /* Errors for null or empty parameters (100-150) */ 10 | public static final Errno ERRNO_NULL_OR_EMPTY_PARAMETER = new Errno(TYPE, 100, "The %1$s parameter passed to \"%2$s\" is null or empty."); 11 | public static final Errno ERRNO_NULL_OR_EMPTY_PARAMETERS = new Errno(TYPE, 101, "The %1$s parameters passed to \"%2$s\" are null or empty."); 12 | public static final Errno ERRNO_UNSET_PARAMETER = new Errno(TYPE, 102, "The %1$s parameter passed to \"%2$s\" must be set."); 13 | public static final Errno ERRNO_UNSET_PARAMETERS = new Errno(TYPE, 103, "The %1$s parameters passed to \"%2$s\" must be set."); 14 | public static final Errno ERRNO_INVALID_PARAMETER = new Errno(TYPE, 104, "The %1$s parameter passed to \"%2$s\" is invalid.\"%3$s\""); 15 | 16 | 17 | FunctionErrno(final String type, final int code, final String message) { 18 | super(type, code, message); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /.github/workflows/trigger_library_builds_on_jitpack.yml: -------------------------------------------------------------------------------- 1 | name: Trigger Termux Library Builds on Jitpack 2 | 3 | on: 4 | release: 5 | types: 6 | - published 7 | 8 | jobs: 9 | trigger-termux-library-builds: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Set vars 13 | run: echo "TERMUX_LIB_VERSION=${GITHUB_REF/refs\/tags\/v/}" >> $GITHUB_ENV # Do not include "v" prefix 14 | - name: Echo release 15 | run: echo "Triggering termux library builds on jitpack for '$TERMUX_LIB_VERSION' release after waiting for 3 mins" 16 | - name: Trigger termux library builds on jitpack 17 | run: | 18 | sleep 180 # It will take some time for the new tag to be detected by Jitpack 19 | curl --max-time 600 --no-progress-meter "https://jitpack.io/com/termux/termux-app/terminal-emulator/$TERMUX_LIB_VERSION/terminal-emulator-$TERMUX_LIB_VERSION.pom" 20 | curl --max-time 600 --no-progress-meter "https://jitpack.io/com/termux/termux-app/terminal-view/$TERMUX_LIB_VERSION/terminal-view-$TERMUX_LIB_VERSION.pom" 21 | curl --max-time 600 --no-progress-meter "https://jitpack.io/com/termux/termux-app/termux-shared/$TERMUX_LIB_VERSION/termux-shared-$TERMUX_LIB_VERSION.pom" 22 | -------------------------------------------------------------------------------- /app/src/test/java/com/termux/app/TermuxActivityTest.java: -------------------------------------------------------------------------------- 1 | package com.termux.app; 2 | 3 | import com.termux.shared.data.UrlUtils; 4 | 5 | import org.junit.Assert; 6 | import org.junit.Test; 7 | 8 | import java.util.Collections; 9 | import java.util.LinkedHashSet; 10 | 11 | public class TermuxActivityTest { 12 | 13 | private void assertUrlsAre(String text, String... urls) { 14 | LinkedHashSet expected = new LinkedHashSet<>(); 15 | Collections.addAll(expected, urls); 16 | Assert.assertEquals(expected, UrlUtils.extractUrls(text)); 17 | } 18 | 19 | @Test 20 | public void testExtractUrls() { 21 | assertUrlsAre("hello http://example.com world", "http://example.com"); 22 | 23 | assertUrlsAre("http://example.com\nhttp://another.com", "http://example.com", "http://another.com"); 24 | 25 | assertUrlsAre("hello http://example.com world and http://more.example.com with secure https://more.example.com", 26 | "http://example.com", "http://more.example.com", "https://more.example.com"); 27 | 28 | assertUrlsAre("hello https://example.com/#bar https://example.com/foo#bar", 29 | "https://example.com/#bar", "https://example.com/foo#bar"); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/full_description.txt: -------------------------------------------------------------------------------- 1 | Termux is a terminal emulator application enhanced with a large set of command line utilities ported to Android OS. The main goal is to bring a Linux command line experience to users of mobile devices with no rooting or other special setup required. 2 | 3 | * Enjoy the Bash and Zsh shells. 4 | * Edit files with nano and vim. 5 | * Access servers over SSH. 6 | * Compile C/C++ code with clang. 7 | * Use the Python console as a pocket calculator. 8 | * Check out projects with Git and Subversion. 9 | * Run text-based games with frotz. 10 | 11 | At first start a small base system is being configured. The GNU Bash, Coreutils, Findutils and other core utilities are available out-of-box. Additionally, we provide more than 1000 other packages installable by using the 'pkg' utility which currently is a frontend for the 'apt' package manager. All provided software has been patched and compiled with Android NDK to provide max compatibility with Android OS. 12 | 13 | To learn more about application usage tips and tricks, long-press anywhere on the terminal and select the Help menu option to access Termux Wiki. This resource is also accessible directly in a web browser: https://wiki.termux.com/wiki/Main_Page. 14 | -------------------------------------------------------------------------------- /terminal-emulator/src/test/java/com/termux/terminal/HistoryTest.java: -------------------------------------------------------------------------------- 1 | package com.termux.terminal; 2 | 3 | 4 | public class HistoryTest extends TerminalTestCase { 5 | 6 | public void testHistory() { 7 | final int rows = 3; 8 | final int cols = 3; 9 | withTerminalSized(cols, rows).enterString("111222333444555666777888999"); 10 | assertCursorAt(2, 2); 11 | assertLinesAre("777", "888", "999"); 12 | assertHistoryStartsWith("666", "555"); 13 | 14 | mTerminal.resize(cols, 2); 15 | assertHistoryStartsWith("777", "666", "555"); 16 | 17 | mTerminal.resize(cols, 3); 18 | assertHistoryStartsWith("666", "555"); 19 | } 20 | 21 | public void testHistoryWithScrollRegion() { 22 | // "CSI P_s ; P_s r" - set Scrolling Region [top;bottom] (default = full size of window) (DECSTBM). 23 | withTerminalSized(3, 4).enterString("111222333444"); 24 | assertLinesAre("111", "222", "333", "444"); 25 | enterString("\033[2;3r"); 26 | // NOTE: "DECSTBM moves the cursor to column 1, line 1 of the page." 27 | assertCursorAt(0, 0); 28 | enterString("\nCDEFGH").assertLinesAre("111", "CDE", "FGH", "444"); 29 | enterString("IJK").assertLinesAre("111", "FGH", "IJK", "444").assertHistoryStartsWith("CDE"); 30 | enterString("LMN").assertLinesAre("111", "IJK", "LMN", "444").assertHistoryStartsWith("FGH", "CDE"); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /app/src/test/java/com/termux/filepicker/TermuxFileReceiverActivityTest.java: -------------------------------------------------------------------------------- 1 | package com.termux.filepicker; 2 | 3 | import org.junit.Assert; 4 | import org.junit.Test; 5 | import org.junit.runner.RunWith; 6 | import org.robolectric.RobolectricTestRunner; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | @RunWith(RobolectricTestRunner.class) 12 | public class TermuxFileReceiverActivityTest { 13 | 14 | @Test 15 | public void testIsSharedTextAnUrl() { 16 | List validUrls = new ArrayList<>(); 17 | validUrls.add("http://example.com"); 18 | validUrls.add("https://example.com"); 19 | validUrls.add("https://example.com/path/parameter=foo"); 20 | validUrls.add("magnet:?xt=urn:btih:d540fc48eb12f2833163eed6421d449dd8f1ce1f&dn=Ubuntu+desktop+19.04+%2864bit%29&tr=udp%3A%2F%2Ftracker.openbittorrent.com%3A80&tr=udp%3A%2F%2Ftracker.publicbt.com%3A80&tr=udp%3A%2F%2Ftracker.ccc.de%3A80"); 21 | for (String url : validUrls) { 22 | Assert.assertTrue(TermuxFileReceiverActivity.isSharedTextAnUrl(url)); 23 | } 24 | 25 | List invalidUrls = new ArrayList<>(); 26 | invalidUrls.add("a test with example.com"); 27 | for (String url : invalidUrls) { 28 | Assert.assertFalse(TermuxFileReceiverActivity.isSharedTextAnUrl(url)); 29 | } 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /termux-shared/src/main/res/drawable/ic_error_notification.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 11 | 12 | 14 | 15 | 18 | 19 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /terminal-emulator/src/main/java/com/termux/terminal/TerminalOutput.java: -------------------------------------------------------------------------------- 1 | package com.termux.terminal; 2 | 3 | import java.nio.charset.StandardCharsets; 4 | 5 | /** A client which receives callbacks from events triggered by feeding input to a {@link TerminalEmulator}. */ 6 | public abstract class TerminalOutput { 7 | 8 | /** Write a string using the UTF-8 encoding to the terminal client. */ 9 | public final void write(String data) { 10 | if (data == null) return; 11 | byte[] bytes = data.getBytes(StandardCharsets.UTF_8); 12 | write(bytes, 0, bytes.length); 13 | } 14 | 15 | /** Write bytes to the terminal client. */ 16 | public abstract void write(byte[] data, int offset, int count); 17 | 18 | /** Notify the terminal client that the terminal title has changed. */ 19 | public abstract void titleChanged(String oldTitle, String newTitle); 20 | 21 | /** Notify the terminal client that text should be copied to clipboard. */ 22 | public abstract void onCopyTextToClipboard(String text); 23 | 24 | /** Notify the terminal client that text should be pasted from clipboard. */ 25 | public abstract void onPasteTextFromClipboard(); 26 | 27 | /** Notify the terminal client that a bell character (ASCII 7, bell, BEL, \a, ^G)) has been received. */ 28 | public abstract void onBell(); 29 | 30 | public abstract void onColorsChanged(); 31 | 32 | } 33 | -------------------------------------------------------------------------------- /terminal-emulator/src/main/java/com/termux/terminal/TerminalSessionClient.java: -------------------------------------------------------------------------------- 1 | package com.termux.terminal; 2 | 3 | /** 4 | * The interface for communication between {@link TerminalSession} and its client. It is used to 5 | * send callbacks to the client when {@link TerminalSession} changes or for sending other 6 | * back data to the client like logs. 7 | */ 8 | public interface TerminalSessionClient { 9 | 10 | void onTextChanged(TerminalSession changedSession); 11 | 12 | void onTitleChanged(TerminalSession changedSession); 13 | 14 | void onSessionFinished(TerminalSession finishedSession); 15 | 16 | void onCopyTextToClipboard(TerminalSession session, String text); 17 | 18 | void onPasteTextFromClipboard(TerminalSession session); 19 | 20 | void onBell(TerminalSession session); 21 | 22 | void onColorsChanged(TerminalSession session); 23 | 24 | void onTerminalCursorStateChange(boolean state); 25 | 26 | 27 | 28 | Integer getTerminalCursorStyle(); 29 | 30 | 31 | 32 | void logError(String tag, String message); 33 | 34 | void logWarn(String tag, String message); 35 | 36 | void logInfo(String tag, String message); 37 | 38 | void logDebug(String tag, String message); 39 | 40 | void logVerbose(String tag, String message); 41 | 42 | void logStackTraceWithMessage(String tag, String message, Exception e); 43 | 44 | void logStackTrace(String tag, Exception e); 45 | 46 | } 47 | -------------------------------------------------------------------------------- /termux-shared/src/main/res/raw/apt_info_script.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | subscribed_repositories() { 4 | local main_sources 5 | main_sources=$(grep -P '^\s*deb\s' "@TERMUX_PREFIX@/etc/apt/sources.list") 6 | 7 | if [ -n "$main_sources" ]; then 8 | echo "#### sources.list" 9 | echo "\`$main_sources\`" 10 | fi 11 | 12 | local filename repo_package supl_sources 13 | while read -r filename; do 14 | repo_package=$(dpkg -S "$filename" 2>/dev/null | cut -d : -f 1) 15 | supl_sources=$(grep -P '^\s*deb\s' "$filename") 16 | 17 | if [ -n "$supl_sources" ]; then 18 | if [ -n "$repo_package" ]; then 19 | echo "#### $repo_package (sources.list.d/$(basename "$filename"))" 20 | else 21 | echo "#### sources.list.d/$(basename "$filename")" 22 | fi 23 | echo "\`$supl_sources\` " 24 | fi 25 | done < <(find "@TERMUX_PREFIX@/etc/apt/sources.list.d" -maxdepth 1 ! -type d) 26 | } 27 | 28 | updatable_packages() { 29 | local updatable 30 | 31 | if [ "$(id -u)" = "0" ]; then 32 | echo "Running as root. Cannot check updatable packages." 33 | else 34 | apt update >/dev/null 2>&1 35 | updatable=$(apt list --upgradable 2>/dev/null | tail -n +2) 36 | 37 | if [ -z "$updatable" ];then 38 | echo "All packages up to date" 39 | else 40 | echo $'```\n'"$updatable"$'\n```\n' 41 | fi 42 | fi 43 | } 44 | 45 | output=" 46 | ### Subscribed Repositories 47 | 48 | $(subscribed_repositories) 49 | ## 50 | 51 | 52 | ### Updatable Packages 53 | 54 | $(updatable_packages) 55 | ## 56 | 57 | " 58 | 59 | echo "$output" 60 | -------------------------------------------------------------------------------- /app/src/main/res/xml/termux_debugging_preferences.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 12 | 13 | 18 | 19 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /terminal-view/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'maven-publish' 3 | 4 | android { 5 | compileSdkVersion project.properties.compileSdkVersion.toInteger() 6 | 7 | dependencies { 8 | implementation "androidx.annotation:annotation:1.3.0" 9 | api project(":terminal-emulator") 10 | } 11 | 12 | defaultConfig { 13 | minSdkVersion project.properties.minSdkVersion.toInteger() 14 | targetSdkVersion project.properties.targetSdkVersion.toInteger() 15 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 16 | } 17 | 18 | buildTypes { 19 | release { 20 | minifyEnabled false 21 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 22 | } 23 | } 24 | 25 | compileOptions { 26 | sourceCompatibility JavaVersion.VERSION_1_8 27 | targetCompatibility JavaVersion.VERSION_1_8 28 | } 29 | } 30 | 31 | dependencies { 32 | testImplementation 'junit:junit:4.13.2' 33 | } 34 | 35 | task sourceJar(type: Jar) { 36 | from android.sourceSets.main.java.srcDirs 37 | classifier "sources" 38 | } 39 | 40 | afterEvaluate { 41 | publishing { 42 | publications { 43 | // Creates a Maven publication called "release". 44 | release(MavenPublication) { 45 | from components.release 46 | groupId = 'com.termux' 47 | artifactId = 'terminal-view' 48 | version = '0.118.0' 49 | artifact(sourceJar) 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /termux-shared/src/main/java/com/termux/shared/shell/ShellEnvironmentClient.java: -------------------------------------------------------------------------------- 1 | package com.termux.shared.shell; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.annotation.NonNull; 6 | 7 | public interface ShellEnvironmentClient { 8 | 9 | /** 10 | * Get the default working directory path for the environment in case the path that was passed 11 | * was {@code null} or empty. 12 | * 13 | * @return Should return the default working directory path. 14 | */ 15 | @NonNull 16 | String getDefaultWorkingDirectoryPath(); 17 | 18 | /** 19 | * Get the default "/bin" path, likely $PREFIX/bin. 20 | * 21 | * @return Should return the "/bin" path. 22 | */ 23 | @NonNull 24 | String getDefaultBinPath(); 25 | 26 | /** 27 | * Build the shell environment to be used for commands. 28 | * 29 | * @param currentPackageContext The {@link Context} for the current package. 30 | * @param isFailSafe If running a failsafe session. 31 | * @param workingDirectory The working directory for the environment. 32 | * @return Should return the build environment. 33 | */ 34 | @NonNull 35 | String[] buildEnvironment(Context currentPackageContext, boolean isFailSafe, String workingDirectory); 36 | 37 | /** 38 | * Setup process arguments for the file to execute, like interpreter, etc. 39 | * 40 | * @param fileToExecute The file to execute. 41 | * @param arguments The arguments to pass to the executable. 42 | * @return Should return the final process arguments. 43 | */ 44 | @NonNull 45 | String[] setupProcessArgs(@NonNull String fileToExecute, String[] arguments); 46 | 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/java/com/termux/app/fragments/settings/TermuxPreferencesFragment.java: -------------------------------------------------------------------------------- 1 | package com.termux.app.fragments.settings; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | 6 | import androidx.annotation.Keep; 7 | import androidx.preference.PreferenceDataStore; 8 | import androidx.preference.PreferenceFragmentCompat; 9 | import androidx.preference.PreferenceManager; 10 | 11 | import com.termux.R; 12 | import com.termux.shared.settings.preferences.TermuxAppSharedPreferences; 13 | 14 | @Keep 15 | public class TermuxPreferencesFragment extends PreferenceFragmentCompat { 16 | 17 | @Override 18 | public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { 19 | Context context = getContext(); 20 | if (context == null) return; 21 | 22 | PreferenceManager preferenceManager = getPreferenceManager(); 23 | preferenceManager.setPreferenceDataStore(TermuxPreferencesDataStore.getInstance(context)); 24 | 25 | setPreferencesFromResource(R.xml.termux_preferences, rootKey); 26 | } 27 | 28 | } 29 | 30 | class TermuxPreferencesDataStore extends PreferenceDataStore { 31 | 32 | private final Context mContext; 33 | private final TermuxAppSharedPreferences mPreferences; 34 | 35 | private static TermuxPreferencesDataStore mInstance; 36 | 37 | private TermuxPreferencesDataStore(Context context) { 38 | mContext = context; 39 | mPreferences = TermuxAppSharedPreferences.build(context, true); 40 | } 41 | 42 | public static synchronized TermuxPreferencesDataStore getInstance(Context context) { 43 | if (mInstance == null) { 44 | mInstance = new TermuxPreferencesDataStore(context); 45 | } 46 | return mInstance; 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /terminal-view/src/main/java/com/termux/view/textselection/CursorController.java: -------------------------------------------------------------------------------- 1 | package com.termux.view.textselection; 2 | 3 | import android.view.MotionEvent; 4 | import android.view.ViewTreeObserver; 5 | 6 | import com.termux.view.TerminalView; 7 | 8 | /** 9 | * A CursorController instance can be used to control cursors in the text. 10 | * It is not used outside of {@link TerminalView}. 11 | */ 12 | public interface CursorController extends ViewTreeObserver.OnTouchModeChangeListener { 13 | /** 14 | * Show the cursors on screen. Will be drawn by {@link #render()} by a call during onDraw. 15 | * See also {@link #hide()}. 16 | */ 17 | void show(MotionEvent event); 18 | 19 | /** 20 | * Hide the cursors from screen. 21 | * See also {@link #show(MotionEvent event)}. 22 | */ 23 | boolean hide(); 24 | 25 | /** 26 | * Render the cursors. 27 | */ 28 | void render(); 29 | 30 | /** 31 | * Update the cursor positions. 32 | */ 33 | void updatePosition(TextSelectionHandleView handle, int x, int y); 34 | 35 | /** 36 | * This method is called by {@link #onTouchEvent(MotionEvent)} and gives the cursors 37 | * a chance to become active and/or visible. 38 | * 39 | * @param event The touch event 40 | */ 41 | boolean onTouchEvent(MotionEvent event); 42 | 43 | /** 44 | * Called when the view is detached from window. Perform house keeping task, such as 45 | * stopping Runnable thread that would otherwise keep a reference on the context, thus 46 | * preventing the activity to be recycled. 47 | */ 48 | void onDetached(); 49 | 50 | /** 51 | * @return true if the cursors are currently active. 52 | */ 53 | boolean isActive(); 54 | 55 | } 56 | -------------------------------------------------------------------------------- /termux-shared/src/main/java/com/termux/shared/terminal/io/extrakeys/SpecialButton.java: -------------------------------------------------------------------------------- 1 | package com.termux.shared.terminal.io.extrakeys; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import java.util.HashMap; 6 | 7 | /** The {@link Class} that implements special buttons for {@link ExtraKeysView}. */ 8 | public class SpecialButton { 9 | 10 | private static final HashMap map = new HashMap<>(); 11 | 12 | public static final SpecialButton CTRL = new SpecialButton("CTRL"); 13 | public static final SpecialButton ALT = new SpecialButton("ALT"); 14 | public static final SpecialButton SHIFT = new SpecialButton("SHIFT"); 15 | public static final SpecialButton FN = new SpecialButton("FN"); 16 | 17 | /** The special button key. */ 18 | private final String key; 19 | 20 | /** 21 | * Initialize a {@link SpecialButton}. 22 | * 23 | * @param key The unique key name for the special button. The key is registered in {@link #map} 24 | * with which the {@link SpecialButton} can be retrieved via a call to 25 | * {@link #valueOf(String)}. 26 | */ 27 | public SpecialButton(@NonNull final String key) { 28 | this.key = key; 29 | map.put(key, this); 30 | } 31 | 32 | /** Get {@link #key} for this {@link SpecialButton}. */ 33 | public String getKey() { 34 | return key; 35 | } 36 | 37 | /** 38 | * Get the {@link SpecialButton} for {@code key}. 39 | * 40 | * @param key The unique key name for the special button. 41 | */ 42 | public static SpecialButton valueOf(String key) { 43 | return map.get(key); 44 | } 45 | 46 | @NonNull 47 | @Override 48 | public String toString() { 49 | return key; 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /termux-shared/src/main/java/com/termux/shared/terminal/io/extrakeys/SpecialButtonState.java: -------------------------------------------------------------------------------- 1 | package com.termux.shared.terminal.io.extrakeys; 2 | 3 | import android.widget.Button; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | /** The {@link Class} that maintains a state of a {@link SpecialButton} */ 9 | public class SpecialButtonState { 10 | 11 | /** If special button has been created for the {@link ExtraKeysView}. */ 12 | boolean isCreated = false; 13 | /** If special button is active. */ 14 | boolean isActive = false; 15 | /** If special button is locked due to long hold on it and should not be deactivated if its 16 | * state is read. */ 17 | boolean isLocked = false; 18 | 19 | List buttons = new ArrayList<>(); 20 | 21 | ExtraKeysView mExtraKeysView; 22 | 23 | /** 24 | * Initialize a {@link SpecialButtonState} to maintain state of a {@link SpecialButton}. 25 | * 26 | * @param extraKeysView The {@link ExtraKeysView} instance in which the {@link SpecialButton} 27 | * is to be registered. 28 | */ 29 | public SpecialButtonState(ExtraKeysView extraKeysView) { 30 | mExtraKeysView = extraKeysView; 31 | } 32 | 33 | /** Set {@link #isCreated}. */ 34 | public void setIsCreated(boolean value) { 35 | isCreated = value; 36 | } 37 | 38 | /** Set {@link #isActive}. */ 39 | public void setIsActive(boolean value) { 40 | isActive = value; 41 | buttons.forEach(button -> button.setTextColor(value ? mExtraKeysView.getButtonActiveTextColor() : mExtraKeysView.getButtonTextColor())); 42 | } 43 | 44 | /** Set {@link #isLocked}. */ 45 | public void setIsLocked(boolean value) { 46 | isLocked = value; 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/java/com/termux/app/fragments/settings/TermuxAPIPreferencesFragment.java: -------------------------------------------------------------------------------- 1 | package com.termux.app.fragments.settings; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | 6 | import androidx.annotation.Keep; 7 | import androidx.preference.PreferenceDataStore; 8 | import androidx.preference.PreferenceFragmentCompat; 9 | import androidx.preference.PreferenceManager; 10 | 11 | import com.termux.R; 12 | import com.termux.shared.settings.preferences.TermuxAPIAppSharedPreferences; 13 | 14 | @Keep 15 | public class TermuxAPIPreferencesFragment extends PreferenceFragmentCompat { 16 | 17 | @Override 18 | public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { 19 | Context context = getContext(); 20 | if (context == null) return; 21 | 22 | PreferenceManager preferenceManager = getPreferenceManager(); 23 | preferenceManager.setPreferenceDataStore(TermuxAPIPreferencesDataStore.getInstance(context)); 24 | 25 | setPreferencesFromResource(R.xml.termux_api_preferences, rootKey); 26 | } 27 | 28 | } 29 | 30 | class TermuxAPIPreferencesDataStore extends PreferenceDataStore { 31 | 32 | private final Context mContext; 33 | private final TermuxAPIAppSharedPreferences mPreferences; 34 | 35 | private static TermuxAPIPreferencesDataStore mInstance; 36 | 37 | private TermuxAPIPreferencesDataStore(Context context) { 38 | mContext = context; 39 | mPreferences = TermuxAPIAppSharedPreferences.build(context, true); 40 | } 41 | 42 | public static synchronized TermuxAPIPreferencesDataStore getInstance(Context context) { 43 | if (mInstance == null) { 44 | mInstance = new TermuxAPIPreferencesDataStore(context); 45 | } 46 | return mInstance; 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/01-bug-report.yml: -------------------------------------------------------------------------------- 1 | name: "Bug report" 2 | description: "Create a report to help us improve" 3 | title: "[Bug]: " 4 | labels: ["bug report"] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | This is a bug tracker of the Termux app. If you have issues with a package inside the app, then please open an issue at [termux-packages](https://github.com/termux/termux-packages) instead. 10 | 11 | Use search before you open an issue to check whether your issue has been already reported and perhaps solved. 12 | 13 | Android versions 5.x and 6.x are not supported anymore. 14 | 15 | If you have issues installing packages then please see https://github.com/termux/termux-packages/issues/6726. 16 | - type: textarea 17 | attributes: 18 | label: Problem description 19 | description: | 20 | A clear and concise description of what the problem is. You may attach the logs, screenshots, screen video recording and whatever else that will help to understand the issue. 21 | 22 | Issues without proper description will be closed without solution. 23 | validations: 24 | required: true 25 | - type: textarea 26 | attributes: 27 | label: Steps to reproduce the behavior. 28 | description: | 29 | Please post all necessary commands that are needed to reproduce the issue. 30 | validations: 31 | required: true 32 | - type: textarea 33 | attributes: 34 | label: What is the expected behavior? 35 | - type: textarea 36 | attributes: 37 | label: System information 38 | description: Please provide info about your device 39 | value: | 40 | * Termux application version: 41 | * Android OS version: 42 | * Device model: 43 | validations: 44 | required: true 45 | -------------------------------------------------------------------------------- /app/src/main/java/com/termux/app/fragments/settings/TermuxFloatPreferencesFragment.java: -------------------------------------------------------------------------------- 1 | package com.termux.app.fragments.settings; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | 6 | import androidx.annotation.Keep; 7 | import androidx.preference.PreferenceDataStore; 8 | import androidx.preference.PreferenceFragmentCompat; 9 | import androidx.preference.PreferenceManager; 10 | 11 | import com.termux.R; 12 | import com.termux.shared.settings.preferences.TermuxFloatAppSharedPreferences; 13 | 14 | @Keep 15 | public class TermuxFloatPreferencesFragment extends PreferenceFragmentCompat { 16 | 17 | @Override 18 | public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { 19 | Context context = getContext(); 20 | if (context == null) return; 21 | 22 | PreferenceManager preferenceManager = getPreferenceManager(); 23 | preferenceManager.setPreferenceDataStore(TermuxFloatPreferencesDataStore.getInstance(context)); 24 | 25 | setPreferencesFromResource(R.xml.termux_float_preferences, rootKey); 26 | } 27 | 28 | } 29 | 30 | class TermuxFloatPreferencesDataStore extends PreferenceDataStore { 31 | 32 | private final Context mContext; 33 | private final TermuxFloatAppSharedPreferences mPreferences; 34 | 35 | private static TermuxFloatPreferencesDataStore mInstance; 36 | 37 | private TermuxFloatPreferencesDataStore(Context context) { 38 | mContext = context; 39 | mPreferences = TermuxFloatAppSharedPreferences.build(context, true); 40 | } 41 | 42 | public static synchronized TermuxFloatPreferencesDataStore getInstance(Context context) { 43 | if (mInstance == null) { 44 | mInstance = new TermuxFloatPreferencesDataStore(context); 45 | } 46 | return mInstance; 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/java/com/termux/app/fragments/settings/TermuxTaskerPreferencesFragment.java: -------------------------------------------------------------------------------- 1 | package com.termux.app.fragments.settings; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | 6 | import androidx.annotation.Keep; 7 | import androidx.preference.PreferenceDataStore; 8 | import androidx.preference.PreferenceFragmentCompat; 9 | import androidx.preference.PreferenceManager; 10 | 11 | import com.termux.R; 12 | import com.termux.shared.settings.preferences.TermuxTaskerAppSharedPreferences; 13 | 14 | @Keep 15 | public class TermuxTaskerPreferencesFragment extends PreferenceFragmentCompat { 16 | 17 | @Override 18 | public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { 19 | Context context = getContext(); 20 | if (context == null) return; 21 | 22 | PreferenceManager preferenceManager = getPreferenceManager(); 23 | preferenceManager.setPreferenceDataStore(TermuxTaskerPreferencesDataStore.getInstance(context)); 24 | 25 | setPreferencesFromResource(R.xml.termux_tasker_preferences, rootKey); 26 | } 27 | 28 | } 29 | 30 | class TermuxTaskerPreferencesDataStore extends PreferenceDataStore { 31 | 32 | private final Context mContext; 33 | private final TermuxTaskerAppSharedPreferences mPreferences; 34 | 35 | private static TermuxTaskerPreferencesDataStore mInstance; 36 | 37 | private TermuxTaskerPreferencesDataStore(Context context) { 38 | mContext = context; 39 | mPreferences = TermuxTaskerAppSharedPreferences.build(context, true); 40 | } 41 | 42 | public static synchronized TermuxTaskerPreferencesDataStore getInstance(Context context) { 43 | if (mInstance == null) { 44 | mInstance = new TermuxTaskerPreferencesDataStore(context); 45 | } 46 | return mInstance; 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/java/com/termux/app/fragments/settings/TermuxWidgetPreferencesFragment.java: -------------------------------------------------------------------------------- 1 | package com.termux.app.fragments.settings; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | 6 | import androidx.annotation.Keep; 7 | import androidx.preference.PreferenceDataStore; 8 | import androidx.preference.PreferenceFragmentCompat; 9 | import androidx.preference.PreferenceManager; 10 | 11 | import com.termux.R; 12 | import com.termux.shared.settings.preferences.TermuxWidgetAppSharedPreferences; 13 | 14 | @Keep 15 | public class TermuxWidgetPreferencesFragment extends PreferenceFragmentCompat { 16 | 17 | @Override 18 | public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { 19 | Context context = getContext(); 20 | if (context == null) return; 21 | 22 | PreferenceManager preferenceManager = getPreferenceManager(); 23 | preferenceManager.setPreferenceDataStore(TermuxWidgetPreferencesDataStore.getInstance(context)); 24 | 25 | setPreferencesFromResource(R.xml.termux_widget_preferences, rootKey); 26 | } 27 | 28 | } 29 | 30 | class TermuxWidgetPreferencesDataStore extends PreferenceDataStore { 31 | 32 | private final Context mContext; 33 | private final TermuxWidgetAppSharedPreferences mPreferences; 34 | 35 | private static TermuxWidgetPreferencesDataStore mInstance; 36 | 37 | private TermuxWidgetPreferencesDataStore(Context context) { 38 | mContext = context; 39 | mPreferences = TermuxWidgetAppSharedPreferences.build(context, true); 40 | } 41 | 42 | public static synchronized TermuxWidgetPreferencesDataStore getInstance(Context context) { 43 | if (mInstance == null) { 44 | mInstance = new TermuxWidgetPreferencesDataStore(context); 45 | } 46 | return mInstance; 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /terminal-emulator/src/test/java/com/termux/terminal/ByteQueueTest.java: -------------------------------------------------------------------------------- 1 | package com.termux.terminal; 2 | 3 | import junit.framework.TestCase; 4 | 5 | public class ByteQueueTest extends TestCase { 6 | 7 | private static void assertArrayEquals(byte[] expected, byte[] actual) { 8 | if (expected.length != actual.length) { 9 | fail("Difference array length"); 10 | } 11 | for (int i = 0; i < expected.length; i++) { 12 | if (expected[i] != actual[i]) { 13 | fail("Inequals at index=" + i + ", expected=" + (int) expected[i] + ", actual=" + (int) actual[i]); 14 | } 15 | } 16 | } 17 | 18 | public void testCompleteWrites() throws Exception { 19 | ByteQueue q = new ByteQueue(10); 20 | assertTrue(q.write(new byte[]{1, 2, 3}, 0, 3)); 21 | 22 | byte[] arr = new byte[10]; 23 | assertEquals(3, q.read(arr, true)); 24 | assertArrayEquals(new byte[]{1, 2, 3}, new byte[]{arr[0], arr[1], arr[2]}); 25 | 26 | assertTrue(q.write(new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, 0, 10)); 27 | assertEquals(10, q.read(arr, true)); 28 | assertArrayEquals(new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, arr); 29 | } 30 | 31 | public void testQueueWraparound() throws Exception { 32 | ByteQueue q = new ByteQueue(10); 33 | 34 | byte[] origArray = new byte[]{1, 2, 3, 4, 5, 6}; 35 | byte[] readArray = new byte[origArray.length]; 36 | for (int i = 0; i < 20; i++) { 37 | q.write(origArray, 0, origArray.length); 38 | assertEquals(origArray.length, q.read(readArray, true)); 39 | assertArrayEquals(origArray, readArray); 40 | } 41 | } 42 | 43 | public void testWriteNotesClosing() throws Exception { 44 | ByteQueue q = new ByteQueue(10); 45 | q.close(); 46 | assertFalse(q.write(new byte[]{1, 2, 3}, 0, 3)); 47 | } 48 | 49 | public void testReadNonBlocking() throws Exception { 50 | ByteQueue q = new ByteQueue(10); 51 | assertEquals(0, q.read(new byte[128], false)); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /termux-shared/src/main/java/com/termux/shared/shell/ShellUtils.java: -------------------------------------------------------------------------------- 1 | package com.termux.shared.shell; 2 | 3 | import com.termux.terminal.TerminalBuffer; 4 | import com.termux.terminal.TerminalEmulator; 5 | import com.termux.terminal.TerminalSession; 6 | 7 | import java.lang.reflect.Field; 8 | 9 | public class ShellUtils { 10 | 11 | public static int getPid(Process p) { 12 | try { 13 | Field f = p.getClass().getDeclaredField("pid"); 14 | f.setAccessible(true); 15 | try { 16 | return f.getInt(p); 17 | } finally { 18 | f.setAccessible(false); 19 | } 20 | } catch (Throwable e) { 21 | return -1; 22 | } 23 | } 24 | 25 | public static String getExecutableBasename(String executable) { 26 | if (executable == null) return null; 27 | int lastSlash = executable.lastIndexOf('/'); 28 | return (lastSlash == -1) ? executable : executable.substring(lastSlash + 1); 29 | } 30 | 31 | public static String getTerminalSessionTranscriptText(TerminalSession terminalSession, boolean linesJoined, boolean trim) { 32 | if (terminalSession == null) return null; 33 | 34 | TerminalEmulator terminalEmulator = terminalSession.getEmulator(); 35 | if (terminalEmulator == null) return null; 36 | 37 | TerminalBuffer terminalBuffer = terminalEmulator.getScreen(); 38 | if (terminalBuffer == null) return null; 39 | 40 | String transcriptText; 41 | 42 | if (linesJoined) 43 | transcriptText = terminalBuffer.getTranscriptTextWithFullLinesJoined(); 44 | else 45 | transcriptText = terminalBuffer.getTranscriptTextWithoutJoinedLines(); 46 | 47 | if (transcriptText == null) return null; 48 | 49 | if (trim) 50 | transcriptText = transcriptText.trim(); 51 | 52 | return transcriptText; 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /termux-shared/src/main/res/layout/dialog_show_message.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 15 | 21 | 22 | 23 | 26 | 34 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /termux-shared/src/main/java/com/termux/shared/notification/TermuxNotificationUtils.java: -------------------------------------------------------------------------------- 1 | package com.termux.shared.notification; 2 | 3 | import android.content.Context; 4 | 5 | import com.termux.shared.settings.preferences.TermuxAppSharedPreferences; 6 | import com.termux.shared.settings.preferences.TermuxPreferenceConstants; 7 | import com.termux.shared.termux.TermuxConstants; 8 | 9 | public class TermuxNotificationUtils { 10 | /** 11 | * Try to get the next unique notification id that isn't already being used by the app. 12 | * 13 | * Termux app and its plugin must use unique notification ids from the same pool due to usage of android:sharedUserId. 14 | * https://commonsware.com/blog/2017/06/07/jobscheduler-job-ids-libraries.html 15 | * 16 | * @param context The {@link Context} for operations. 17 | * @return Returns the notification id that should be safe to use. 18 | */ 19 | public synchronized static int getNextNotificationId(final Context context) { 20 | if (context == null) return TermuxPreferenceConstants.TERMUX_APP.DEFAULT_VALUE_KEY_LAST_NOTIFICATION_ID; 21 | 22 | TermuxAppSharedPreferences preferences = TermuxAppSharedPreferences.build(context); 23 | if (preferences == null) return TermuxPreferenceConstants.TERMUX_APP.DEFAULT_VALUE_KEY_LAST_NOTIFICATION_ID; 24 | 25 | int lastNotificationId = preferences.getLastNotificationId(); 26 | 27 | int nextNotificationId = lastNotificationId + 1; 28 | while(nextNotificationId == TermuxConstants.TERMUX_APP_NOTIFICATION_ID || nextNotificationId == TermuxConstants.TERMUX_RUN_COMMAND_NOTIFICATION_ID) { 29 | nextNotificationId++; 30 | } 31 | 32 | if (nextNotificationId == Integer.MAX_VALUE || nextNotificationId < 0) 33 | nextNotificationId = TermuxPreferenceConstants.TERMUX_APP.DEFAULT_VALUE_KEY_LAST_NOTIFICATION_ID; 34 | 35 | preferences.setLastNotificationId(nextNotificationId); 36 | return nextNotificationId; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /terminal-emulator/src/main/java/com/termux/terminal/JNI.java: -------------------------------------------------------------------------------- 1 | package com.termux.terminal; 2 | 3 | /** 4 | * Native methods for creating and managing pseudoterminal subprocesses. C code is in jni/termux.c. 5 | */ 6 | final class JNI { 7 | 8 | static { 9 | System.loadLibrary("termux"); 10 | } 11 | 12 | /** 13 | * Create a subprocess. Differs from {@link ProcessBuilder} in that a pseudoterminal is used to communicate with the 14 | * subprocess. 15 | * 16 | * Callers are responsible for calling {@link #close(int)} on the returned file descriptor. 17 | * 18 | * @param cmd The command to execute 19 | * @param cwd The current working directory for the executed command 20 | * @param args An array of arguments to the command 21 | * @param envVars An array of strings of the form "VAR=value" to be added to the environment of the process 22 | * @param processId A one-element array to which the process ID of the started process will be written. 23 | * @return the file descriptor resulting from opening /dev/ptmx master device. The sub process will have opened the 24 | * slave device counterpart (/dev/pts/$N) and have it as stdint, stdout and stderr. 25 | */ 26 | public static native int createSubprocess(String cmd, String cwd, String[] args, String[] envVars, int[] processId, int rows, int columns); 27 | 28 | /** Set the window size for a given pty, which allows connected programs to learn how large their screen is. */ 29 | public static native void setPtyWindowSize(int fd, int rows, int cols); 30 | 31 | /** 32 | * Causes the calling thread to wait for the process associated with the receiver to finish executing. 33 | * 34 | * @return if >= 0, the exit status of the process. If < 0, the signal causing the process to stop negated. 35 | */ 36 | public static native int waitFor(int processId); 37 | 38 | /** Close a file descriptor through the close(2) system call. */ 39 | public static native void close(int fileDescriptor); 40 | 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/res/xml/root_preferences.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 15 | 16 | 22 | 23 | 29 | 30 | 36 | 37 | 41 | 42 | 43 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /termux-shared/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 22 | 23 | 26 | 27 | 30 | 31 | 34 | 35 | 42 | 43 | -------------------------------------------------------------------------------- /terminal-emulator/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'maven-publish' 3 | 4 | android { 5 | compileSdkVersion project.properties.compileSdkVersion.toInteger() 6 | ndkVersion = System.getenv("JITPACK_NDK_VERSION") ?: project.properties.ndkVersion 7 | 8 | defaultConfig { 9 | minSdkVersion project.properties.minSdkVersion.toInteger() 10 | targetSdkVersion project.properties.targetSdkVersion.toInteger() 11 | 12 | externalNativeBuild { 13 | ndkBuild { 14 | cFlags "-std=c11", "-Wall", "-Wextra", "-Werror", "-Os", "-fno-stack-protector", "-Wl,--gc-sections" 15 | } 16 | } 17 | 18 | ndk { 19 | abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a' 20 | } 21 | } 22 | 23 | buildTypes { 24 | release { 25 | minifyEnabled false 26 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 27 | } 28 | } 29 | 30 | externalNativeBuild { 31 | ndkBuild { 32 | path "src/main/jni/Android.mk" 33 | } 34 | } 35 | 36 | compileOptions { 37 | sourceCompatibility JavaVersion.VERSION_1_8 38 | targetCompatibility JavaVersion.VERSION_1_8 39 | } 40 | 41 | testOptions { 42 | unitTests.returnDefaultValues = true 43 | } 44 | } 45 | 46 | tasks.withType(Test) { 47 | testLogging { 48 | events "started", "passed", "skipped", "failed" 49 | } 50 | } 51 | 52 | dependencies { 53 | testImplementation 'junit:junit:4.13.2' 54 | } 55 | 56 | task sourceJar(type: Jar) { 57 | from android.sourceSets.main.java.srcDirs 58 | classifier "sources" 59 | } 60 | 61 | afterEvaluate { 62 | publishing { 63 | publications { 64 | // Creates a Maven publication called "release". 65 | release(MavenPublication) { 66 | from components.release 67 | groupId = 'com.termux' 68 | artifactId = 'terminal-emulator' 69 | version = '0.118.0' 70 | artifact(sourceJar) 71 | } 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /app/src/main/java/com/termux/app/terminal/io/TermuxTerminalExtraKeys.java: -------------------------------------------------------------------------------- 1 | package com.termux.app.terminal.io; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.view.Gravity; 5 | import android.view.View; 6 | 7 | import androidx.annotation.NonNull; 8 | import androidx.drawerlayout.widget.DrawerLayout; 9 | 10 | import com.termux.app.terminal.TermuxTerminalSessionClient; 11 | import com.termux.app.terminal.TermuxTerminalViewClient; 12 | import com.termux.shared.terminal.io.TerminalExtraKeys; 13 | import com.termux.view.TerminalView; 14 | 15 | public class TermuxTerminalExtraKeys extends TerminalExtraKeys { 16 | 17 | 18 | TermuxTerminalViewClient mTermuxTerminalViewClient; 19 | TermuxTerminalSessionClient mTermuxTerminalSessionClient; 20 | 21 | public TermuxTerminalExtraKeys(@NonNull TerminalView terminalView, 22 | TermuxTerminalViewClient termuxTerminalViewClient, 23 | TermuxTerminalSessionClient termuxTerminalSessionClient) { 24 | super(terminalView); 25 | mTermuxTerminalViewClient = termuxTerminalViewClient; 26 | mTermuxTerminalSessionClient = termuxTerminalSessionClient; 27 | } 28 | 29 | @SuppressLint("RtlHardcoded") 30 | @Override 31 | public void onTerminalExtraKeyButtonClick(View view, String key, boolean ctrlDown, boolean altDown, boolean shiftDown, boolean fnDown) { 32 | if ("KEYBOARD".equals(key)) { 33 | if(mTermuxTerminalViewClient != null) 34 | mTermuxTerminalViewClient.onToggleSoftKeyboardRequest(); 35 | } else if ("DRAWER".equals(key)) { 36 | DrawerLayout drawerLayout = mTermuxTerminalViewClient.getActivity().getDrawer(); 37 | if (drawerLayout.isDrawerOpen(Gravity.LEFT)) 38 | drawerLayout.closeDrawer(Gravity.LEFT); 39 | else 40 | drawerLayout.openDrawer(Gravity.LEFT); 41 | } else if ("PASTE".equals(key)) { 42 | if(mTermuxTerminalSessionClient != null) 43 | mTermuxTerminalSessionClient.onPasteTextFromClipboard(null); 44 | } else { 45 | super.onTerminalExtraKeyButtonClick(view, key, ctrlDown, altDown, shiftDown, fnDown); 46 | } 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /terminal-emulator/src/test/java/com/termux/terminal/DeviceControlStringTest.java: -------------------------------------------------------------------------------- 1 | package com.termux.terminal; 2 | 3 | /** 4 | * "\033P" is a device control string. 5 | */ 6 | public class DeviceControlStringTest extends TerminalTestCase { 7 | 8 | private static String hexEncode(String s) { 9 | StringBuilder result = new StringBuilder(); 10 | for (int i = 0; i < s.length(); i++) 11 | result.append(String.format("%02X", (int) s.charAt(i))); 12 | return result.toString(); 13 | } 14 | 15 | private void assertCapabilityResponse(String cap, String expectedResponse) { 16 | String input = "\033P+q" + hexEncode(cap) + "\033\\"; 17 | assertEnteringStringGivesResponse(input, "\033P1+r" + hexEncode(cap) + "=" + hexEncode(expectedResponse) + "\033\\"); 18 | } 19 | 20 | public void testReportColorsAndName() { 21 | // Request Termcap/Terminfo String. The string following the "q" is a list of names encoded in 22 | // hexadecimal (2 digits per character) separated by ; which correspond to termcap or terminfo key 23 | // names. 24 | // Two special features are also recognized, which are not key names: Co for termcap colors (or colors 25 | // for terminfo colors), and TN for termcap name (or name for terminfo name). 26 | // xterm responds with DCS 1 + r P t ST for valid requests, adding to P t an = , and the value of the 27 | // corresponding string that xterm would send, or DCS 0 + r P t ST for invalid requests. The strings are 28 | // encoded in hexadecimal (2 digits per character). 29 | withTerminalSized(3, 3).enterString("A"); 30 | assertCapabilityResponse("Co", "256"); 31 | assertCapabilityResponse("colors", "256"); 32 | assertCapabilityResponse("TN", "xterm"); 33 | assertCapabilityResponse("name", "xterm"); 34 | enterString("B").assertLinesAre("AB ", " ", " "); 35 | } 36 | 37 | public void testReportKeys() { 38 | withTerminalSized(3, 3); 39 | assertCapabilityResponse("kB", "\033[Z"); 40 | } 41 | 42 | public void testReallyLongDeviceControlString() { 43 | withTerminalSized(3, 3).enterString("\033P"); 44 | for (int i = 0; i < 10000; i++) { 45 | enterString("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); 46 | } 47 | // The terminal should ignore the overlong DCS sequence and continue printing "aaa." and fill at least the first two lines with 48 | // them: 49 | assertLineIs(0, "aaa"); 50 | assertLineIs(1, "aaa"); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /terminal-view/src/main/java/com/termux/view/TerminalViewClient.java: -------------------------------------------------------------------------------- 1 | package com.termux.view; 2 | 3 | import android.view.KeyEvent; 4 | import android.view.MotionEvent; 5 | import android.view.ScaleGestureDetector; 6 | import android.view.View; 7 | 8 | import com.termux.terminal.TerminalSession; 9 | 10 | /** 11 | * The interface for communication between {@link TerminalView} and its client. It allows for getting 12 | * various configuration options from the client and for sending back data to the client like logs, 13 | * key events, both hardware and IME (which makes it different from that available with 14 | * {@link View#setOnKeyListener(View.OnKeyListener)}, etc. It must be set for the 15 | * {@link TerminalView} through {@link TerminalView#setTerminalViewClient(TerminalViewClient)}. 16 | */ 17 | public interface TerminalViewClient { 18 | 19 | /** 20 | * Callback function on scale events according to {@link ScaleGestureDetector#getScaleFactor()}. 21 | */ 22 | float onScale(float scale); 23 | 24 | 25 | 26 | /** 27 | * On a single tap on the terminal if terminal mouse reporting not enabled. 28 | */ 29 | void onSingleTapUp(MotionEvent e); 30 | 31 | boolean shouldBackButtonBeMappedToEscape(); 32 | 33 | boolean shouldEnforceCharBasedInput(); 34 | 35 | boolean shouldUseCtrlSpaceWorkaround(); 36 | 37 | boolean isTerminalViewSelected(); 38 | 39 | 40 | 41 | void copyModeChanged(boolean copyMode); 42 | 43 | 44 | 45 | boolean onKeyDown(int keyCode, KeyEvent e, TerminalSession session); 46 | 47 | boolean onKeyUp(int keyCode, KeyEvent e); 48 | 49 | boolean onLongPress(MotionEvent event); 50 | 51 | 52 | 53 | boolean readControlKey(); 54 | 55 | boolean readAltKey(); 56 | 57 | boolean readShiftKey(); 58 | 59 | boolean readFnKey(); 60 | 61 | 62 | 63 | boolean onCodePoint(int codePoint, boolean ctrlDown, TerminalSession session); 64 | 65 | 66 | void onEmulatorSet(); 67 | 68 | 69 | void logError(String tag, String message); 70 | 71 | void logWarn(String tag, String message); 72 | 73 | void logInfo(String tag, String message); 74 | 75 | void logDebug(String tag, String message); 76 | 77 | void logVerbose(String tag, String message); 78 | 79 | void logStackTraceWithMessage(String tag, String message, Exception e); 80 | 81 | void logStackTrace(String tag, Exception e); 82 | 83 | } 84 | -------------------------------------------------------------------------------- /termux-shared/src/main/java/com/termux/shared/terminal/TermuxTerminalSessionClientBase.java: -------------------------------------------------------------------------------- 1 | package com.termux.shared.terminal; 2 | 3 | import com.termux.shared.logger.Logger; 4 | import com.termux.terminal.TerminalSession; 5 | import com.termux.terminal.TerminalSessionClient; 6 | 7 | public class TermuxTerminalSessionClientBase implements TerminalSessionClient { 8 | 9 | public TermuxTerminalSessionClientBase() { 10 | } 11 | 12 | @Override 13 | public void onTextChanged(TerminalSession changedSession) { 14 | } 15 | 16 | @Override 17 | public void onTitleChanged(TerminalSession updatedSession) { 18 | } 19 | 20 | @Override 21 | public void onSessionFinished(final TerminalSession finishedSession) { 22 | } 23 | 24 | @Override 25 | public void onCopyTextToClipboard(TerminalSession session, String text) { 26 | } 27 | 28 | @Override 29 | public void onPasteTextFromClipboard(TerminalSession session) { 30 | } 31 | 32 | @Override 33 | public void onBell(TerminalSession session) { 34 | } 35 | 36 | @Override 37 | public void onColorsChanged(TerminalSession changedSession) { 38 | } 39 | 40 | @Override 41 | public void onTerminalCursorStateChange(boolean state) { 42 | } 43 | 44 | 45 | 46 | @Override 47 | public Integer getTerminalCursorStyle() { 48 | return null; 49 | } 50 | 51 | 52 | 53 | @Override 54 | public void logError(String tag, String message) { 55 | Logger.logError(tag, message); 56 | } 57 | 58 | @Override 59 | public void logWarn(String tag, String message) { 60 | Logger.logWarn(tag, message); 61 | } 62 | 63 | @Override 64 | public void logInfo(String tag, String message) { 65 | Logger.logInfo(tag, message); 66 | } 67 | 68 | @Override 69 | public void logDebug(String tag, String message) { 70 | Logger.logDebug(tag, message); 71 | } 72 | 73 | @Override 74 | public void logVerbose(String tag, String message) { 75 | Logger.logVerbose(tag, message); 76 | } 77 | 78 | @Override 79 | public void logStackTraceWithMessage(String tag, String message, Exception e) { 80 | Logger.logStackTraceWithMessage(tag, message, e); 81 | } 82 | 83 | @Override 84 | public void logStackTrace(String tag, Exception e) { 85 | Logger.logStackTrace(tag, e); 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /app/src/main/res/xml/shortcuts.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 12 | 13 | 19 | 24 | 25 | 26 | 32 | 37 | 38 | 39 | 40 | 41 | 47 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /termux-shared/src/main/java/com/termux/shared/file/filesystem/NativeDispatcher.java: -------------------------------------------------------------------------------- 1 | package com.termux.shared.file.filesystem; 2 | 3 | import android.system.ErrnoException; 4 | import android.system.Os; 5 | 6 | import java.io.File; 7 | import java.io.FileDescriptor; 8 | import java.io.IOException; 9 | 10 | public class NativeDispatcher { 11 | 12 | public static void stat(String filePath, FileAttributes fileAttributes) throws IOException { 13 | validateFileExistence(filePath); 14 | 15 | try { 16 | fileAttributes.loadFromStructStat(Os.stat(filePath)); 17 | } catch (ErrnoException e) { 18 | throw new IOException("Failed to run Os.stat() on file at path \"" + filePath + "\": " + e.getMessage()); 19 | } 20 | } 21 | 22 | public static void lstat(String filePath, FileAttributes fileAttributes) throws IOException { 23 | validateFileExistence(filePath); 24 | 25 | try { 26 | fileAttributes.loadFromStructStat(Os.lstat(filePath)); 27 | } catch (ErrnoException e) { 28 | throw new IOException("Failed to run Os.lstat() on file at path \"" + filePath + "\": " + e.getMessage()); 29 | } 30 | } 31 | 32 | public static void fstat(FileDescriptor fileDescriptor, FileAttributes fileAttributes) throws IOException { 33 | validateFileDescriptor(fileDescriptor); 34 | 35 | try { 36 | fileAttributes.loadFromStructStat(Os.fstat(fileDescriptor)); 37 | } catch (ErrnoException e) { 38 | throw new IOException("Failed to run Os.fstat() on file descriptor \"" + fileDescriptor.toString() + "\": " + e.getMessage()); 39 | } 40 | } 41 | 42 | public static void validateFileExistence(String filePath) throws IOException { 43 | if (filePath == null || filePath.isEmpty()) throw new IOException("The path is null or empty"); 44 | 45 | File file = new File(filePath); 46 | 47 | //if (!file.exists()) 48 | // throw new IOException("No such file or directory: \"" + filePath + "\""); 49 | } 50 | 51 | public static void validateFileDescriptor(FileDescriptor fileDescriptor) throws IOException { 52 | if (fileDescriptor == null) throw new IOException("The file descriptor is null"); 53 | 54 | if (!fileDescriptor.valid()) 55 | throw new IOException("No such file descriptor: \"" + fileDescriptor.toString() + "\""); 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /terminal-emulator/src/test/java/com/termux/terminal/WcWidthTest.java: -------------------------------------------------------------------------------- 1 | package com.termux.terminal; 2 | 3 | import junit.framework.TestCase; 4 | 5 | public class WcWidthTest extends TestCase { 6 | 7 | private static void assertWidthIs(int expectedWidth, int codePoint) { 8 | int wcWidth = WcWidth.width(codePoint); 9 | assertEquals(expectedWidth, wcWidth); 10 | } 11 | 12 | public void testPrintableAscii() { 13 | for (int i = 0x20; i <= 0x7E; i++) { 14 | assertWidthIs(1, i); 15 | } 16 | } 17 | 18 | public void testSomeWidthOne() { 19 | assertWidthIs(1, 'å'); 20 | assertWidthIs(1, 'ä'); 21 | assertWidthIs(1, 'ö'); 22 | assertWidthIs(1, 0x23F2); 23 | } 24 | 25 | public void testSomeWide() { 26 | assertWidthIs(2, 'A'); 27 | assertWidthIs(2, 'B'); 28 | assertWidthIs(2, 'C'); 29 | assertWidthIs(2, '中'); 30 | assertWidthIs(2, '文'); 31 | 32 | assertWidthIs(2, 0x679C); 33 | assertWidthIs(2, 0x679D); 34 | 35 | assertWidthIs(2, 0x2070E); 36 | assertWidthIs(2, 0x20731); 37 | 38 | assertWidthIs(1, 0x1F781); 39 | } 40 | 41 | public void testSomeNonWide() { 42 | assertWidthIs(1, 0x1D11E); 43 | assertWidthIs(1, 0x1D11F); 44 | } 45 | 46 | public void testCombining() { 47 | assertWidthIs(0, 0x0302); 48 | assertWidthIs(0, 0x0308); 49 | assertWidthIs(0, 0xFE0F); 50 | } 51 | 52 | public void testWordJoiner() { 53 | // https://en.wikipedia.org/wiki/Word_joiner 54 | // The word joiner (WJ) is a code point in Unicode used to separate words when using scripts 55 | // that do not use explicit spacing. It is encoded since Unicode version 3.2 56 | // (released in 2002) as U+2060 WORD JOINER (HTML ). 57 | // The word joiner does not produce any space, and prohibits a line break at its position. 58 | assertWidthIs(0, 0x2060); 59 | } 60 | 61 | public void testSofthyphen() { 62 | // http://osdir.com/ml/internationalization.linux/2003-05/msg00006.html: 63 | // "Existing implementation practice in terminals is that the SOFT HYPHEN is 64 | // a spacing graphical character, and the purpose of my wcwidth() was to 65 | // predict the advancement of the cursor position after a string is sent to 66 | // a terminal. Hence, I have no choice but to keep wcwidth(SOFT HYPHEN) = 1. 67 | // VT100-style terminals do not hyphenate." 68 | assertWidthIs(1, 0x00AD); 69 | } 70 | 71 | public void testHangul() { 72 | assertWidthIs(1, 0x11A3); 73 | } 74 | 75 | public void testEmojis() { 76 | assertWidthIs(2, 0x1F428); // KOALA. 77 | assertWidthIs(2, 0x231a); // WATCH. 78 | assertWidthIs(2, 0x1F643); // UPSIDE-DOWN FACE (Unicode 8). 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /app/src/main/java/com/termux/app/fragments/settings/termux/TerminalViewPreferencesFragment.java: -------------------------------------------------------------------------------- 1 | package com.termux.app.fragments.settings.termux; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | 6 | import androidx.annotation.Keep; 7 | import androidx.preference.PreferenceDataStore; 8 | import androidx.preference.PreferenceFragmentCompat; 9 | import androidx.preference.PreferenceManager; 10 | 11 | import com.termux.R; 12 | import com.termux.shared.settings.preferences.TermuxAppSharedPreferences; 13 | 14 | @Keep 15 | public class TerminalViewPreferencesFragment extends PreferenceFragmentCompat { 16 | 17 | @Override 18 | public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { 19 | Context context = getContext(); 20 | if (context == null) return; 21 | 22 | PreferenceManager preferenceManager = getPreferenceManager(); 23 | preferenceManager.setPreferenceDataStore(TerminalViewPreferencesDataStore.getInstance(context)); 24 | 25 | setPreferencesFromResource(R.xml.termux_terminal_view_preferences, rootKey); 26 | } 27 | 28 | } 29 | 30 | class TerminalViewPreferencesDataStore extends PreferenceDataStore { 31 | 32 | private final Context mContext; 33 | private final TermuxAppSharedPreferences mPreferences; 34 | 35 | private static TerminalViewPreferencesDataStore mInstance; 36 | 37 | private TerminalViewPreferencesDataStore(Context context) { 38 | mContext = context; 39 | mPreferences = TermuxAppSharedPreferences.build(context, true); 40 | } 41 | 42 | public static synchronized TerminalViewPreferencesDataStore getInstance(Context context) { 43 | if (mInstance == null) { 44 | mInstance = new TerminalViewPreferencesDataStore(context); 45 | } 46 | return mInstance; 47 | } 48 | 49 | 50 | 51 | @Override 52 | public void putBoolean(String key, boolean value) { 53 | if (mPreferences == null) return; 54 | if (key == null) return; 55 | 56 | switch (key) { 57 | case "terminal_margin_adjustment": 58 | mPreferences.setTerminalMarginAdjustment(value); 59 | break; 60 | default: 61 | break; 62 | } 63 | } 64 | 65 | @Override 66 | public boolean getBoolean(String key, boolean defValue) { 67 | if (mPreferences == null) return false; 68 | 69 | switch (key) { 70 | case "terminal_margin_adjustment": 71 | return mPreferences.isTerminalMarginAdjustmentEnabled(); 72 | default: 73 | return false; 74 | } 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /termux-shared/src/main/java/com/termux/shared/file/filesystem/FileKey.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2008, 2009, Oracle and/or its affiliates. All rights reserved. 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 | * 5 | * This code is free software; you can redistribute it and/or modify it 6 | * under the terms of the GNU General Public License version 2 only, as 7 | * published by the Free Software Foundation. Oracle designates this 8 | * particular file as subject to the "Classpath" exception as provided 9 | * by Oracle in the LICENSE file that accompanied this code. 10 | * 11 | * This code is distributed in the hope that it will be useful, but WITHOUT 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 | * version 2 for more details (a copy is included in the LICENSE file that 15 | * accompanied this code). 16 | * 17 | * You should have received a copy of the GNU General Public License version 18 | * 2 along with this work; if not, write to the Free Software Foundation, 19 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 | * 21 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 | * or visit www.oracle.com if you need additional information or have any 23 | * questions. 24 | */ 25 | 26 | package com.termux.shared.file.filesystem; 27 | 28 | /** 29 | * Container for device/inode to uniquely identify file. 30 | * https://cs.android.com/android/platform/superproject/+/android-11.0.0_r3:libcore/ojluni/src/main/java/sun/nio/fs/UnixFileKey.java 31 | */ 32 | 33 | public class FileKey { 34 | private final long st_dev; 35 | private final long st_ino; 36 | 37 | FileKey(long st_dev, long st_ino) { 38 | this.st_dev = st_dev; 39 | this.st_ino = st_ino; 40 | } 41 | 42 | @Override 43 | public int hashCode() { 44 | return (int)(st_dev ^ (st_dev >>> 32)) + 45 | (int)(st_ino ^ (st_ino >>> 32)); 46 | } 47 | 48 | @Override 49 | public boolean equals(Object obj) { 50 | if (obj == this) 51 | return true; 52 | if (!(obj instanceof FileKey)) 53 | return false; 54 | FileKey other = (FileKey)obj; 55 | return (this.st_dev == other.st_dev) && (this.st_ino == other.st_ino); 56 | } 57 | 58 | @Override 59 | public String toString() { 60 | StringBuilder sb = new StringBuilder(); 61 | sb.append("(dev=") 62 | .append(Long.toHexString(st_dev)) 63 | .append(",ino=") 64 | .append(st_ino) 65 | .append(')'); 66 | return sb.toString(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /termux-shared/src/main/java/com/termux/shared/file/filesystem/FilePermission.java: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright (c) 2007, 2011, Oracle and/or its affiliates. All rights reserved. 4 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 5 | * 6 | * This code is free software; you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License version 2 only, as 8 | * published by the Free Software Foundation. Oracle designates this 9 | * particular file as subject to the "Classpath" exception as provided 10 | * by Oracle in the LICENSE file that accompanied this code. 11 | * 12 | * This code is distributed in the hope that it will be useful, but WITHOUT 13 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 14 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 15 | * version 2 for more details (a copy is included in the LICENSE file that 16 | * accompanied this code). 17 | * 18 | * You should have received a copy of the GNU General Public License version 19 | * 2 along with this work; if not, write to the Free Software Foundation, 20 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 21 | * 22 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 23 | * or visit www.oracle.com if you need additional information or have any 24 | * questions. 25 | */ 26 | 27 | package com.termux.shared.file.filesystem; 28 | 29 | /** 30 | * Defines the bits for use with the {@link FileAttributes#permissions() 31 | * permissions} attribute. 32 | * 33 | * The {@link FileAttributes} class defines methods for manipulating 34 | * set of permissions. 35 | * 36 | * https://cs.android.com/android/platform/superproject/+/android-11.0.0_r3:libcore/ojluni/src/main/java/java/nio/file/attribute/PosixFilePermission.java 37 | * 38 | * @since 1.7 39 | */ 40 | 41 | public enum FilePermission { 42 | 43 | /** 44 | * Read permission, owner. 45 | */ 46 | OWNER_READ, 47 | 48 | /** 49 | * Write permission, owner. 50 | */ 51 | OWNER_WRITE, 52 | 53 | /** 54 | * Execute/search permission, owner. 55 | */ 56 | OWNER_EXECUTE, 57 | 58 | /** 59 | * Read permission, group. 60 | */ 61 | GROUP_READ, 62 | 63 | /** 64 | * Write permission, group. 65 | */ 66 | GROUP_WRITE, 67 | 68 | /** 69 | * Execute/search permission, group. 70 | */ 71 | GROUP_EXECUTE, 72 | 73 | /** 74 | * Read permission, others. 75 | */ 76 | OTHERS_READ, 77 | 78 | /** 79 | * Write permission, others. 80 | */ 81 | OTHERS_WRITE, 82 | 83 | /** 84 | * Execute/search permission, others. 85 | */ 86 | OTHERS_EXECUTE 87 | 88 | } 89 | -------------------------------------------------------------------------------- /termux-shared/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'maven-publish' 3 | 4 | android { 5 | compileSdkVersion project.properties.compileSdkVersion.toInteger() 6 | 7 | dependencies { 8 | implementation 'androidx.appcompat:appcompat:1.3.1' 9 | implementation "androidx.annotation:annotation:1.3.0" 10 | implementation "androidx.core:core:1.6.0" 11 | implementation 'com.google.android.material:material:1.4.0' 12 | implementation "com.google.guava:guava:24.1-jre" 13 | implementation "io.noties.markwon:core:$markwonVersion" 14 | implementation "io.noties.markwon:ext-strikethrough:$markwonVersion" 15 | implementation "io.noties.markwon:linkify:$markwonVersion" 16 | implementation "io.noties.markwon:recycler:$markwonVersion" 17 | 18 | // Do not increment version higher than 1.0.0-alpha09 since it will break ViewUtils and needs to be looked into 19 | // noinspection GradleDependency 20 | implementation "androidx.window:window:1.0.0-alpha09" 21 | 22 | // Do not increment version higher than 2.5 or there 23 | // will be runtime exceptions on android < 8 24 | // due to missing classes like java.nio.file.Path. 25 | implementation "commons-io:commons-io:2.5" 26 | 27 | implementation project(":terminal-view") 28 | } 29 | 30 | defaultConfig { 31 | minSdkVersion project.properties.minSdkVersion.toInteger() 32 | targetSdkVersion project.properties.targetSdkVersion.toInteger() 33 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 34 | } 35 | 36 | buildTypes { 37 | release { 38 | minifyEnabled false 39 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 40 | } 41 | } 42 | 43 | compileOptions { 44 | sourceCompatibility JavaVersion.VERSION_1_8 45 | targetCompatibility JavaVersion.VERSION_1_8 46 | } 47 | } 48 | 49 | dependencies { 50 | testImplementation "junit:junit:4.13.2" 51 | androidTestImplementation 'androidx.test.ext:junit:1.1.3' 52 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' 53 | } 54 | 55 | task sourceJar(type: Jar) { 56 | from android.sourceSets.main.java.srcDirs 57 | classifier "sources" 58 | } 59 | 60 | afterEvaluate { 61 | publishing { 62 | publications { 63 | // Creates a Maven publication called "release". 64 | release(MavenPublication) { 65 | from components.release 66 | groupId = 'com.termux' 67 | artifactId = 'termux-shared' 68 | version = '0.118.0' 69 | artifact(sourceJar) 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 23 | 24 | 25 | 26 | 27 | 45 | 46 | 47 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /app/src/main/java/com/termux/app/terminal/io/FullScreenWorkAround.java: -------------------------------------------------------------------------------- 1 | package com.termux.app.terminal.io; 2 | 3 | import android.graphics.Rect; 4 | import android.view.View; 5 | import android.view.ViewGroup; 6 | 7 | import com.termux.app.TermuxActivity; 8 | 9 | /** 10 | * Work around for fullscreen mode in Termux to fix ExtraKeysView not being visible. 11 | * This class is derived from: 12 | * https://stackoverflow.com/questions/7417123/android-how-to-adjust-layout-in-full-screen-mode-when-softkeyboard-is-visible 13 | * and has some additional tweaks 14 | * --- 15 | * For more information, see https://issuetracker.google.com/issues/36911528 16 | */ 17 | public class FullScreenWorkAround { 18 | private final View mChildOfContent; 19 | private int mUsableHeightPrevious; 20 | private final ViewGroup.LayoutParams mViewGroupLayoutParams; 21 | 22 | private final int mNavBarHeight; 23 | 24 | 25 | public static void apply(TermuxActivity activity) { 26 | new FullScreenWorkAround(activity); 27 | } 28 | 29 | private FullScreenWorkAround(TermuxActivity activity) { 30 | ViewGroup content = activity.findViewById(android.R.id.content); 31 | mChildOfContent = content.getChildAt(0); 32 | mViewGroupLayoutParams = mChildOfContent.getLayoutParams(); 33 | mNavBarHeight = activity.getNavBarHeight(); 34 | mChildOfContent.getViewTreeObserver().addOnGlobalLayoutListener(this::possiblyResizeChildOfContent); 35 | } 36 | 37 | private void possiblyResizeChildOfContent() { 38 | int usableHeightNow = computeUsableHeight(); 39 | if (usableHeightNow != mUsableHeightPrevious) { 40 | int usableHeightSansKeyboard = mChildOfContent.getRootView().getHeight(); 41 | int heightDifference = usableHeightSansKeyboard - usableHeightNow; 42 | if (heightDifference > (usableHeightSansKeyboard / 4)) { 43 | // keyboard probably just became visible 44 | 45 | // ensures that usable layout space does not extend behind the 46 | // soft keyboard, causing the extra keys to not be visible 47 | mViewGroupLayoutParams.height = (usableHeightSansKeyboard - heightDifference) + getNavBarHeight(); 48 | } else { 49 | // keyboard probably just became hidden 50 | mViewGroupLayoutParams.height = usableHeightSansKeyboard; 51 | } 52 | mChildOfContent.requestLayout(); 53 | mUsableHeightPrevious = usableHeightNow; 54 | } 55 | } 56 | 57 | private int getNavBarHeight() { 58 | return mNavBarHeight; 59 | } 60 | 61 | private int computeUsableHeight() { 62 | Rect r = new Rect(); 63 | mChildOfContent.getWindowVisibleDisplayFrame(r); 64 | return (r.bottom - r.top); 65 | } 66 | 67 | } 68 | 69 | -------------------------------------------------------------------------------- /terminal-emulator/src/test/java/com/termux/terminal/TextStyleTest.java: -------------------------------------------------------------------------------- 1 | package com.termux.terminal; 2 | 3 | import junit.framework.TestCase; 4 | 5 | public class TextStyleTest extends TestCase { 6 | 7 | private static final int[] ALL_EFFECTS = new int[]{0, TextStyle.CHARACTER_ATTRIBUTE_BOLD, TextStyle.CHARACTER_ATTRIBUTE_ITALIC, 8 | TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE, TextStyle.CHARACTER_ATTRIBUTE_BLINK, TextStyle.CHARACTER_ATTRIBUTE_INVERSE, 9 | TextStyle.CHARACTER_ATTRIBUTE_INVISIBLE, TextStyle.CHARACTER_ATTRIBUTE_STRIKETHROUGH, TextStyle.CHARACTER_ATTRIBUTE_PROTECTED, 10 | TextStyle.CHARACTER_ATTRIBUTE_DIM}; 11 | 12 | public void testEncodingSingle() { 13 | for (int fx : ALL_EFFECTS) { 14 | for (int fg = 0; fg < TextStyle.NUM_INDEXED_COLORS; fg++) { 15 | for (int bg = 0; bg < TextStyle.NUM_INDEXED_COLORS; bg++) { 16 | long encoded = TextStyle.encode(fg, bg, fx); 17 | assertEquals(fg, TextStyle.decodeForeColor(encoded)); 18 | assertEquals(bg, TextStyle.decodeBackColor(encoded)); 19 | assertEquals(fx, TextStyle.decodeEffect(encoded)); 20 | } 21 | } 22 | } 23 | } 24 | 25 | public void testEncoding24Bit() { 26 | int[] values = {255, 240, 127, 1, 0}; 27 | for (int red : values) { 28 | for (int green : values) { 29 | for (int blue : values) { 30 | int argb = 0xFF000000 | (red << 16) | (green << 8) | blue; 31 | long encoded = TextStyle.encode(argb, 0, 0); 32 | assertEquals(argb, TextStyle.decodeForeColor(encoded)); 33 | encoded = TextStyle.encode(0, argb, 0); 34 | assertEquals(argb, TextStyle.decodeBackColor(encoded)); 35 | } 36 | } 37 | } 38 | } 39 | 40 | 41 | public void testEncodingCombinations() { 42 | for (int f1 : ALL_EFFECTS) { 43 | for (int f2 : ALL_EFFECTS) { 44 | int combined = f1 | f2; 45 | assertEquals(combined, TextStyle.decodeEffect(TextStyle.encode(0, 0, combined))); 46 | } 47 | } 48 | } 49 | 50 | public void testEncodingStrikeThrough() { 51 | long encoded = TextStyle.encode(TextStyle.COLOR_INDEX_FOREGROUND, TextStyle.COLOR_INDEX_BACKGROUND, 52 | TextStyle.CHARACTER_ATTRIBUTE_STRIKETHROUGH); 53 | assertTrue((TextStyle.decodeEffect(encoded) & TextStyle.CHARACTER_ATTRIBUTE_STRIKETHROUGH) != 0); 54 | } 55 | 56 | public void testEncodingProtected() { 57 | long encoded = TextStyle.encode(TextStyle.COLOR_INDEX_FOREGROUND, TextStyle.COLOR_INDEX_BACKGROUND, 58 | TextStyle.CHARACTER_ATTRIBUTE_STRIKETHROUGH); 59 | assertEquals(0, (TextStyle.decodeEffect(encoded) & TextStyle.CHARACTER_ATTRIBUTE_PROTECTED)); 60 | encoded = TextStyle.encode(TextStyle.COLOR_INDEX_FOREGROUND, TextStyle.COLOR_INDEX_BACKGROUND, 61 | TextStyle.CHARACTER_ATTRIBUTE_STRIKETHROUGH | TextStyle.CHARACTER_ATTRIBUTE_PROTECTED); 62 | assertTrue((TextStyle.decodeEffect(encoded) & TextStyle.CHARACTER_ATTRIBUTE_PROTECTED) != 0); 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /app/src/main/java/com/termux/app/fragments/settings/termux/TerminalIOPreferencesFragment.java: -------------------------------------------------------------------------------- 1 | package com.termux.app.fragments.settings.termux; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | 6 | import androidx.annotation.Keep; 7 | import androidx.preference.PreferenceDataStore; 8 | import androidx.preference.PreferenceFragmentCompat; 9 | import androidx.preference.PreferenceManager; 10 | 11 | import com.termux.R; 12 | import com.termux.shared.settings.preferences.TermuxAppSharedPreferences; 13 | 14 | @Keep 15 | public class TerminalIOPreferencesFragment extends PreferenceFragmentCompat { 16 | 17 | @Override 18 | public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { 19 | Context context = getContext(); 20 | if (context == null) return; 21 | 22 | PreferenceManager preferenceManager = getPreferenceManager(); 23 | preferenceManager.setPreferenceDataStore(TerminalIOPreferencesDataStore.getInstance(context)); 24 | 25 | setPreferencesFromResource(R.xml.termux_terminal_io_preferences, rootKey); 26 | } 27 | 28 | } 29 | 30 | class TerminalIOPreferencesDataStore extends PreferenceDataStore { 31 | 32 | private final Context mContext; 33 | private final TermuxAppSharedPreferences mPreferences; 34 | 35 | private static TerminalIOPreferencesDataStore mInstance; 36 | 37 | private TerminalIOPreferencesDataStore(Context context) { 38 | mContext = context; 39 | mPreferences = TermuxAppSharedPreferences.build(context, true); 40 | } 41 | 42 | public static synchronized TerminalIOPreferencesDataStore getInstance(Context context) { 43 | if (mInstance == null) { 44 | mInstance = new TerminalIOPreferencesDataStore(context); 45 | } 46 | return mInstance; 47 | } 48 | 49 | 50 | 51 | @Override 52 | public void putBoolean(String key, boolean value) { 53 | if (mPreferences == null) return; 54 | if (key == null) return; 55 | 56 | switch (key) { 57 | case "soft_keyboard_enabled": 58 | mPreferences.setSoftKeyboardEnabled(value); 59 | break; 60 | case "soft_keyboard_enabled_only_if_no_hardware": 61 | mPreferences.setSoftKeyboardEnabledOnlyIfNoHardware(value); 62 | break; 63 | default: 64 | break; 65 | } 66 | } 67 | 68 | @Override 69 | public boolean getBoolean(String key, boolean defValue) { 70 | if (mPreferences == null) return false; 71 | 72 | switch (key) { 73 | case "soft_keyboard_enabled": 74 | return mPreferences.isSoftKeyboardEnabled(); 75 | case "soft_keyboard_enabled_only_if_no_hardware": 76 | return mPreferences.isSoftKeyboardEnabledOnlyIfNoHardware(); 77 | default: 78 | return false; 79 | } 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /app/src/main/java/com/termux/app/activities/HelpActivity.java: -------------------------------------------------------------------------------- 1 | package com.termux.app.activities; 2 | 3 | import android.app.Activity; 4 | import android.content.ActivityNotFoundException; 5 | import android.content.Intent; 6 | import android.net.Uri; 7 | import android.os.Bundle; 8 | import android.view.ViewGroup; 9 | import android.webkit.WebSettings; 10 | import android.webkit.WebView; 11 | import android.webkit.WebViewClient; 12 | import android.widget.ProgressBar; 13 | import android.widget.RelativeLayout; 14 | 15 | import com.termux.shared.termux.TermuxConstants; 16 | 17 | /** Basic embedded browser for viewing help pages. */ 18 | public final class HelpActivity extends Activity { 19 | 20 | WebView mWebView; 21 | 22 | @Override 23 | protected void onCreate(Bundle savedInstanceState) { 24 | super.onCreate(savedInstanceState); 25 | 26 | final RelativeLayout progressLayout = new RelativeLayout(this); 27 | RelativeLayout.LayoutParams lParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); 28 | lParams.addRule(RelativeLayout.CENTER_IN_PARENT); 29 | ProgressBar progressBar = new ProgressBar(this); 30 | progressBar.setIndeterminate(true); 31 | progressBar.setLayoutParams(lParams); 32 | progressLayout.addView(progressBar); 33 | 34 | mWebView = new WebView(this); 35 | WebSettings settings = mWebView.getSettings(); 36 | settings.setCacheMode(WebSettings.LOAD_NO_CACHE); 37 | settings.setAppCacheEnabled(false); 38 | setContentView(progressLayout); 39 | mWebView.clearCache(true); 40 | 41 | mWebView.setWebViewClient(new WebViewClient() { 42 | @Override 43 | public boolean shouldOverrideUrlLoading(WebView view, String url) { 44 | if (url.equals(TermuxConstants.TERMUX_WIKI_URL) || url.startsWith(TermuxConstants.TERMUX_WIKI_URL + "/")) { 45 | // Inline help. 46 | setContentView(progressLayout); 47 | return false; 48 | } 49 | 50 | try { 51 | startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); 52 | } catch (ActivityNotFoundException e) { 53 | // Android TV does not have a system browser. 54 | setContentView(progressLayout); 55 | return false; 56 | } 57 | return true; 58 | } 59 | 60 | @Override 61 | public void onPageFinished(WebView view, String url) { 62 | setContentView(mWebView); 63 | } 64 | }); 65 | mWebView.loadUrl(TermuxConstants.TERMUX_WIKI_URL); 66 | } 67 | 68 | @Override 69 | public void onBackPressed() { 70 | if (mWebView.canGoBack()) { 71 | mWebView.goBack(); 72 | } else { 73 | super.onBackPressed(); 74 | } 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /termux-shared/src/main/java/com/termux/shared/terminal/io/BellHandler.java: -------------------------------------------------------------------------------- 1 | package com.termux.shared.terminal.io; 2 | 3 | import android.content.Context; 4 | import android.os.Build; 5 | import android.os.Handler; 6 | import android.os.Looper; 7 | import android.os.SystemClock; 8 | import android.os.VibrationEffect; 9 | import android.os.Vibrator; 10 | 11 | import com.termux.shared.logger.Logger; 12 | 13 | public class BellHandler { 14 | private static BellHandler instance = null; 15 | private static final Object lock = new Object(); 16 | 17 | private static final String LOG_TAG = "BellHandler"; 18 | 19 | public static BellHandler getInstance(Context context) { 20 | if (instance == null) { 21 | synchronized (lock) { 22 | if (instance == null) { 23 | instance = new BellHandler((Vibrator) context.getApplicationContext().getSystemService(Context.VIBRATOR_SERVICE)); 24 | } 25 | } 26 | } 27 | 28 | return instance; 29 | } 30 | 31 | private static final long DURATION = 50; 32 | private static final long MIN_PAUSE = 3 * DURATION; 33 | 34 | private final Handler handler = new Handler(Looper.getMainLooper()); 35 | private long lastBell = 0; 36 | private final Runnable bellRunnable; 37 | 38 | private BellHandler(final Vibrator vibrator) { 39 | bellRunnable = new Runnable() { 40 | @Override 41 | public void run() { 42 | if (vibrator != null) { 43 | try { 44 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 45 | vibrator.vibrate(VibrationEffect.createOneShot(DURATION, VibrationEffect.DEFAULT_AMPLITUDE)); 46 | } else { 47 | vibrator.vibrate(DURATION); 48 | } 49 | } catch (Exception e) { 50 | // Issue on samsung devices on android 8 51 | // java.lang.NullPointerException: Attempt to read from field 'android.os.VibrationEffect com.android.server.VibratorService$Vibration.mEffect' on a null object reference 52 | Logger.logStackTraceWithMessage(LOG_TAG, "Failed to run vibrator", e); 53 | } 54 | } 55 | } 56 | }; 57 | } 58 | 59 | public synchronized void doBell() { 60 | long now = now(); 61 | long timeSinceLastBell = now - lastBell; 62 | 63 | if (timeSinceLastBell < 0) { 64 | // there is a next bell pending; don't schedule another one 65 | } else if (timeSinceLastBell < MIN_PAUSE) { 66 | // there was a bell recently, schedule the next one 67 | handler.postDelayed(bellRunnable, MIN_PAUSE - timeSinceLastBell); 68 | lastBell = lastBell + MIN_PAUSE; 69 | } else { 70 | // the last bell was long ago, do it now 71 | bellRunnable.run(); 72 | lastBell = now; 73 | } 74 | } 75 | 76 | private long now() { 77 | return SystemClock.uptimeMillis(); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /terminal-emulator/src/test/java/com/termux/terminal/ScreenBufferTest.java: -------------------------------------------------------------------------------- 1 | package com.termux.terminal; 2 | 3 | public class ScreenBufferTest extends TerminalTestCase { 4 | 5 | public void testBasics() { 6 | TerminalBuffer screen = new TerminalBuffer(5, 3, 3); 7 | assertEquals("", screen.getTranscriptText()); 8 | screen.setChar(0, 0, 'a', 0); 9 | assertEquals("a", screen.getTranscriptText()); 10 | screen.setChar(0, 0, 'b', 0); 11 | assertEquals("b", screen.getTranscriptText()); 12 | screen.setChar(2, 0, 'c', 0); 13 | assertEquals("b c", screen.getTranscriptText()); 14 | screen.setChar(2, 2, 'f', 0); 15 | assertEquals("b c\n\n f", screen.getTranscriptText()); 16 | screen.blockSet(0, 0, 2, 2, 'X', 0); 17 | } 18 | 19 | public void testBlockSet() { 20 | TerminalBuffer screen = new TerminalBuffer(5, 3, 3); 21 | screen.blockSet(0, 0, 2, 2, 'X', 0); 22 | assertEquals("XX\nXX", screen.getTranscriptText()); 23 | screen.blockSet(1, 1, 2, 2, 'Y', 0); 24 | assertEquals("XX\nXYY\n YY", screen.getTranscriptText()); 25 | } 26 | 27 | public void testGetSelectedText() { 28 | withTerminalSized(5, 3).enterString("ABCDEFGHIJ").assertLinesAre("ABCDE", "FGHIJ", " "); 29 | assertEquals("AB", mTerminal.getSelectedText(0, 0, 1, 0)); 30 | assertEquals("BC", mTerminal.getSelectedText(1, 0, 2, 0)); 31 | assertEquals("CDE", mTerminal.getSelectedText(2, 0, 4, 0)); 32 | assertEquals("FG", mTerminal.getSelectedText(0, 1, 1, 1)); 33 | assertEquals("GH", mTerminal.getSelectedText(1, 1, 2, 1)); 34 | assertEquals("HIJ", mTerminal.getSelectedText(2, 1, 4, 1)); 35 | 36 | assertEquals("ABCDEFG", mTerminal.getSelectedText(0, 0, 1, 1)); 37 | withTerminalSized(5, 3).enterString("ABCDE\r\nFGHIJ").assertLinesAre("ABCDE", "FGHIJ", " "); 38 | assertEquals("ABCDE\nFG", mTerminal.getSelectedText(0, 0, 1, 1)); 39 | } 40 | 41 | public void testGetSelectedTextJoinFullLines() { 42 | withTerminalSized(5, 3).enterString("ABCDE\r\nFG"); 43 | assertEquals("ABCDEFG", mTerminal.getScreen().getSelectedText(0, 0, 1, 1, true, true)); 44 | 45 | withTerminalSized(5, 3).enterString("ABC\r\nFG"); 46 | assertEquals("ABC\nFG", mTerminal.getScreen().getSelectedText(0, 0, 1, 1, true, true)); 47 | } 48 | 49 | public void testGetWordAtLocation() { 50 | withTerminalSized(5, 3).enterString("ABCDEFGHIJ\r\nKLMNO"); 51 | assertEquals("ABCDEFGHIJKLMNO", mTerminal.getScreen().getWordAtLocation(0, 0)); 52 | assertEquals("ABCDEFGHIJKLMNO", mTerminal.getScreen().getWordAtLocation(4, 1)); 53 | assertEquals("ABCDEFGHIJKLMNO", mTerminal.getScreen().getWordAtLocation(4, 2)); 54 | 55 | withTerminalSized(5, 3).enterString("ABC DEF GHI "); 56 | assertEquals("ABC", mTerminal.getScreen().getWordAtLocation(0, 0)); 57 | assertEquals("", mTerminal.getScreen().getWordAtLocation(3, 0)); 58 | assertEquals("DEF", mTerminal.getScreen().getWordAtLocation(4, 0)); 59 | assertEquals("DEF", mTerminal.getScreen().getWordAtLocation(0, 1)); 60 | assertEquals("DEF", mTerminal.getScreen().getWordAtLocation(1, 1)); 61 | assertEquals("GHI", mTerminal.getScreen().getWordAtLocation(0, 2)); 62 | assertEquals("", mTerminal.getScreen().getWordAtLocation(1, 2)); 63 | assertEquals("", mTerminal.getScreen().getWordAtLocation(2, 2)); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /termux-shared/src/main/java/com/termux/shared/terminal/TermuxTerminalViewClientBase.java: -------------------------------------------------------------------------------- 1 | package com.termux.shared.terminal; 2 | 3 | import android.view.KeyEvent; 4 | import android.view.MotionEvent; 5 | 6 | import com.termux.shared.logger.Logger; 7 | import com.termux.terminal.TerminalSession; 8 | import com.termux.view.TerminalViewClient; 9 | 10 | public class TermuxTerminalViewClientBase implements TerminalViewClient { 11 | 12 | public TermuxTerminalViewClientBase() { 13 | } 14 | 15 | @Override 16 | public float onScale(float scale) { 17 | return 1.0f; 18 | } 19 | 20 | @Override 21 | public void onSingleTapUp(MotionEvent e) { 22 | } 23 | 24 | public boolean shouldBackButtonBeMappedToEscape() { 25 | return false; 26 | } 27 | 28 | public boolean shouldEnforceCharBasedInput() { 29 | return false; 30 | } 31 | 32 | public boolean shouldUseCtrlSpaceWorkaround() { 33 | return false; 34 | } 35 | 36 | @Override 37 | public boolean isTerminalViewSelected() { 38 | return true; 39 | } 40 | 41 | @Override 42 | public void copyModeChanged(boolean copyMode) { 43 | } 44 | 45 | @Override 46 | public boolean onKeyDown(int keyCode, KeyEvent e, TerminalSession session) { 47 | return false; 48 | } 49 | 50 | @Override 51 | public boolean onKeyUp(int keyCode, KeyEvent e) { 52 | return false; 53 | } 54 | 55 | @Override 56 | public boolean onLongPress(MotionEvent event) { 57 | return false; 58 | } 59 | 60 | @Override 61 | public boolean readControlKey() { 62 | return false; 63 | } 64 | 65 | @Override 66 | public boolean readAltKey() { 67 | return false; 68 | } 69 | 70 | @Override 71 | public boolean readShiftKey() { 72 | return false; 73 | } 74 | 75 | @Override 76 | public boolean readFnKey() { 77 | return false; 78 | } 79 | 80 | 81 | 82 | @Override 83 | public boolean onCodePoint(int codePoint, boolean ctrlDown, TerminalSession session) { 84 | return false; 85 | } 86 | 87 | @Override 88 | public void onEmulatorSet() { 89 | 90 | } 91 | 92 | @Override 93 | public void logError(String tag, String message) { 94 | Logger.logError(tag, message); 95 | } 96 | 97 | @Override 98 | public void logWarn(String tag, String message) { 99 | Logger.logWarn(tag, message); 100 | } 101 | 102 | @Override 103 | public void logInfo(String tag, String message) { 104 | Logger.logInfo(tag, message); 105 | } 106 | 107 | @Override 108 | public void logDebug(String tag, String message) { 109 | Logger.logDebug(tag, message); 110 | } 111 | 112 | @Override 113 | public void logVerbose(String tag, String message) { 114 | Logger.logVerbose(tag, message); 115 | } 116 | 117 | @Override 118 | public void logStackTraceWithMessage(String tag, String message, Exception e) { 119 | Logger.logStackTraceWithMessage(tag, message, e); 120 | } 121 | 122 | @Override 123 | public void logStackTrace(String tag, Exception e) { 124 | Logger.logStackTrace(tag, e); 125 | } 126 | 127 | } 128 | -------------------------------------------------------------------------------- /terminal-emulator/src/test/java/com/termux/terminal/DecSetTest.java: -------------------------------------------------------------------------------- 1 | package com.termux.terminal; 2 | 3 | /** 4 | * 5 | * "CSI ? Pm h", DEC Private Mode Set (DECSET) 6 | * 7 | * 8 | * and 9 | * 10 | * 11 | * "CSI ? Pm l", DEC Private Mode Reset (DECRST) 12 | * 13 | * 14 | * controls various aspects of the terminal 15 | */ 16 | public class DecSetTest extends TerminalTestCase { 17 | 18 | /** DECSET 25, DECTCEM, controls visibility of the cursor. */ 19 | public void testEnableDisableCursor() { 20 | withTerminalSized(3, 3); 21 | assertTrue("Initially the cursor should be enabled", mTerminal.isCursorEnabled()); 22 | enterString("\033[?25l"); // Disable Cursor (DECTCEM). 23 | assertFalse(mTerminal.isCursorEnabled()); 24 | enterString("\033[?25h"); // Enable Cursor (DECTCEM). 25 | assertTrue(mTerminal.isCursorEnabled()); 26 | 27 | enterString("\033[?25l"); // Disable Cursor (DECTCEM), again. 28 | assertFalse(mTerminal.isCursorEnabled()); 29 | mTerminal.reset(); 30 | assertTrue("Resetting the terminal should enable the cursor", mTerminal.isCursorEnabled()); 31 | 32 | enterString("\033[?25l"); 33 | assertFalse(mTerminal.isCursorEnabled()); 34 | enterString("\033c"); // RIS resetting should enabled cursor. 35 | assertTrue(mTerminal.isCursorEnabled()); 36 | } 37 | 38 | /** DECSET 2004, controls bracketed paste mode. */ 39 | public void testBracketedPasteMode() { 40 | withTerminalSized(3, 3); 41 | 42 | mTerminal.paste("a"); 43 | assertEquals("Pasting 'a' should output 'a' when bracketed paste mode is disabled", "a", mOutput.getOutputAndClear()); 44 | 45 | enterString("\033[?2004h"); // Enable bracketed paste mode. 46 | mTerminal.paste("a"); 47 | assertEquals("Pasting when in bracketed paste mode should be bracketed", "\033[200~a\033[201~", mOutput.getOutputAndClear()); 48 | 49 | enterString("\033[?2004l"); // Disable bracketed paste mode. 50 | mTerminal.paste("a"); 51 | assertEquals("Pasting 'a' should output 'a' when bracketed paste mode is disabled", "a", mOutput.getOutputAndClear()); 52 | 53 | enterString("\033[?2004h"); // Enable bracketed paste mode, again. 54 | mTerminal.paste("a"); 55 | assertEquals("Pasting when in bracketed paste mode again should be bracketed", "\033[200~a\033[201~", mOutput.getOutputAndClear()); 56 | 57 | mTerminal.paste("\033ab\033cd\033"); 58 | assertEquals("Pasting an escape character should not input it", "\033[200~abcd\033[201~", mOutput.getOutputAndClear()); 59 | mTerminal.paste("\u0081ab\u0081cd\u009F"); 60 | assertEquals("Pasting C1 control codes should not input it", "\033[200~abcd\033[201~", mOutput.getOutputAndClear()); 61 | 62 | mTerminal.reset(); 63 | mTerminal.paste("a"); 64 | assertEquals("Terminal reset() should disable bracketed paste mode", "a", mOutput.getOutputAndClear()); 65 | } 66 | 67 | /** DECSET 7, DECAWM, controls wraparound mode. */ 68 | public void testWrapAroundMode() { 69 | // Default with wraparound: 70 | withTerminalSized(3, 3).enterString("abcd").assertLinesAre("abc", "d ", " "); 71 | // With wraparound disabled: 72 | withTerminalSized(3, 3).enterString("\033[?7labcd").assertLinesAre("abd", " ", " "); 73 | enterString("efg").assertLinesAre("abg", " ", " "); 74 | // Re-enabling wraparound: 75 | enterString("\033[?7hhij").assertLinesAre("abh", "ij ", " "); 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /termux-shared/src/main/java/com/termux/shared/terminal/io/TerminalExtraKeys.java: -------------------------------------------------------------------------------- 1 | package com.termux.shared.terminal.io; 2 | 3 | import android.view.KeyEvent; 4 | import android.view.View; 5 | import android.widget.Button; 6 | 7 | import androidx.annotation.NonNull; 8 | 9 | import com.termux.shared.terminal.io.extrakeys.ExtraKeyButton; 10 | import com.termux.shared.terminal.io.extrakeys.ExtraKeysView; 11 | import com.termux.shared.terminal.io.extrakeys.SpecialButton; 12 | import com.termux.view.TerminalView; 13 | 14 | import static com.termux.shared.terminal.io.extrakeys.ExtraKeysConstants.PRIMARY_KEY_CODES_FOR_STRINGS; 15 | 16 | 17 | public class TerminalExtraKeys implements ExtraKeysView.IExtraKeysView { 18 | 19 | private final TerminalView mTerminalView; 20 | 21 | public TerminalExtraKeys(@NonNull TerminalView terminalView) { 22 | mTerminalView = terminalView; 23 | } 24 | 25 | @Override 26 | public void onExtraKeyButtonClick(View view, ExtraKeyButton buttonInfo, Button button) { 27 | if (buttonInfo.isMacro()) { 28 | String[] keys = buttonInfo.getKey().split(" "); 29 | boolean ctrlDown = false; 30 | boolean altDown = false; 31 | boolean shiftDown = false; 32 | boolean fnDown = false; 33 | for (String key : keys) { 34 | if (SpecialButton.CTRL.getKey().equals(key)) { 35 | ctrlDown = true; 36 | } else if (SpecialButton.ALT.getKey().equals(key)) { 37 | altDown = true; 38 | } else if (SpecialButton.SHIFT.getKey().equals(key)) { 39 | shiftDown = true; 40 | } else if (SpecialButton.FN.getKey().equals(key)) { 41 | fnDown = true; 42 | } else { 43 | onTerminalExtraKeyButtonClick(view, key, ctrlDown, altDown, shiftDown, fnDown); 44 | ctrlDown = false; altDown = false; shiftDown = false; fnDown = false; 45 | } 46 | } 47 | } else { 48 | onTerminalExtraKeyButtonClick(view, buttonInfo.getKey(), false, false, false, false); 49 | } 50 | } 51 | 52 | protected void onTerminalExtraKeyButtonClick(View view, String key, boolean ctrlDown, boolean altDown, boolean shiftDown, boolean fnDown) { 53 | if (PRIMARY_KEY_CODES_FOR_STRINGS.containsKey(key)) { 54 | Integer keyCode = PRIMARY_KEY_CODES_FOR_STRINGS.get(key); 55 | if (keyCode == null) return; 56 | int metaState = 0; 57 | if (ctrlDown) metaState |= KeyEvent.META_CTRL_ON | KeyEvent.META_CTRL_LEFT_ON; 58 | if (altDown) metaState |= KeyEvent.META_ALT_ON | KeyEvent.META_ALT_LEFT_ON; 59 | if (shiftDown) metaState |= KeyEvent.META_SHIFT_ON | KeyEvent.META_SHIFT_LEFT_ON; 60 | if (fnDown) metaState |= KeyEvent.META_FUNCTION_ON; 61 | 62 | KeyEvent keyEvent = new KeyEvent(0, 0, KeyEvent.ACTION_UP, keyCode, 0, metaState); 63 | mTerminalView.onKeyDown(keyCode, keyEvent); 64 | } else { 65 | // not a control char 66 | key.codePoints().forEach(codePoint -> { 67 | mTerminalView.inputCodePoint(codePoint, ctrlDown, altDown); 68 | }); 69 | } 70 | } 71 | 72 | @Override 73 | public boolean performExtraKeyButtonHapticFeedback(View view, ExtraKeyButton buttonInfo, Button button) { 74 | return false; 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /terminal-emulator/src/main/java/com/termux/terminal/TerminalColors.java: -------------------------------------------------------------------------------- 1 | package com.termux.terminal; 2 | 3 | /** Current terminal colors (if different from default). */ 4 | public final class TerminalColors { 5 | 6 | /** Static data - a bit ugly but ok for now. */ 7 | public static final TerminalColorScheme COLOR_SCHEME = new TerminalColorScheme(); 8 | 9 | /** 10 | * The current terminal colors, which are normally set from the color theme, but may be set dynamically with the OSC 11 | * 4 control sequence. 12 | */ 13 | public final int[] mCurrentColors = new int[TextStyle.NUM_INDEXED_COLORS]; 14 | 15 | /** Create a new instance with default colors from the theme. */ 16 | public TerminalColors() { 17 | reset(); 18 | } 19 | 20 | /** Reset a particular indexed color with the default color from the color theme. */ 21 | public void reset(int index) { 22 | mCurrentColors[index] = COLOR_SCHEME.mDefaultColors[index]; 23 | } 24 | 25 | /** Reset all indexed colors with the default color from the color theme. */ 26 | public void reset() { 27 | System.arraycopy(COLOR_SCHEME.mDefaultColors, 0, mCurrentColors, 0, TextStyle.NUM_INDEXED_COLORS); 28 | } 29 | 30 | /** 31 | * Parse color according to http://manpages.ubuntu.com/manpages/intrepid/man3/XQueryColor.3.html 32 | * 33 | * Highest bit is set if successful, so return value is 0xFF${R}${G}${B}. Return 0 if failed. 34 | */ 35 | static int parse(String c) { 36 | try { 37 | int skipInitial, skipBetween; 38 | if (c.charAt(0) == '#') { 39 | // #RGB, #RRGGBB, #RRRGGGBBB or #RRRRGGGGBBBB. Most significant bits. 40 | skipInitial = 1; 41 | skipBetween = 0; 42 | } else if (c.startsWith("rgb:")) { 43 | // rgb:// where , , := h | hh | hhh | hhhh. Scaled. 44 | skipInitial = 4; 45 | skipBetween = 1; 46 | } else { 47 | return 0; 48 | } 49 | int charsForColors = c.length() - skipInitial - 2 * skipBetween; 50 | if (charsForColors % 3 != 0) return 0; // Unequal lengths. 51 | int componentLength = charsForColors / 3; 52 | double mult = 255 / (Math.pow(2, componentLength * 4) - 1); 53 | 54 | int currentPosition = skipInitial; 55 | String rString = c.substring(currentPosition, currentPosition + componentLength); 56 | currentPosition += componentLength + skipBetween; 57 | String gString = c.substring(currentPosition, currentPosition + componentLength); 58 | currentPosition += componentLength + skipBetween; 59 | String bString = c.substring(currentPosition, currentPosition + componentLength); 60 | 61 | int r = (int) (Integer.parseInt(rString, 16) * mult); 62 | int g = (int) (Integer.parseInt(gString, 16) * mult); 63 | int b = (int) (Integer.parseInt(bString, 16) * mult); 64 | return 0xFF << 24 | r << 16 | g << 8 | b; 65 | } catch (NumberFormatException | IndexOutOfBoundsException e) { 66 | return 0; 67 | } 68 | } 69 | 70 | /** Try parse a color from a text parameter and into a specified index. */ 71 | public void tryParseColor(int intoIndex, String textParameter) { 72 | int c = parse(textParameter); 73 | if (c != 0) mCurrentColors[intoIndex] = c; 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /.github/workflows/attach_debug_apks_to_release.yml: -------------------------------------------------------------------------------- 1 | name: Attach Debug APKs To Release 2 | 3 | on: 4 | release: 5 | types: 6 | - published 7 | 8 | jobs: 9 | attach-apks: 10 | runs-on: ubuntu-latest 11 | env: 12 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 13 | steps: 14 | - name: Clone repository 15 | uses: actions/checkout@v2 16 | with: 17 | ref: ${{ env.GITHUB_REF }} 18 | 19 | - name: Build and attach APKs to release 20 | shell: bash {0} 21 | run: | 22 | exit_on_error() { 23 | echo "$1" 24 | echo "Deleting '$RELEASE_VERSION_NAME' release and '$GITHUB_REF' tag" 25 | hub release delete "$RELEASE_VERSION_NAME" 26 | git push --delete origin "$GITHUB_REF" 27 | exit 1 28 | } 29 | 30 | echo "Setting vars" 31 | RELEASE_VERSION_NAME="${GITHUB_REF/refs\/tags\//}" 32 | if ! printf "%s" "${RELEASE_VERSION_NAME/v/}" | grep -qP '^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$'; then 33 | exit_on_error "The versionName '${RELEASE_VERSION_NAME/v/}' is not a valid version as per semantic version '2.0.0' spec in the format 'major.minor.patch(-prerelease)(+buildmetadata)'. https://semver.org/spec/v2.0.0.html." 34 | fi 35 | 36 | APK_DIR_PATH="./app/build/outputs/apk/debug" 37 | APK_VERSION_TAG="$RELEASE_VERSION_NAME+github-debug" 38 | APK_BASENAME_PREFIX="termux-app_$APK_VERSION_TAG" 39 | 40 | echo "Building APKs for '$RELEASE_VERSION_NAME' release" 41 | export TERMUX_APK_VERSION_TAG="$APK_VERSION_TAG" # Used by app/build.gradle 42 | if ! ./gradlew assembleDebug; then 43 | exit_on_error "Build failed for '$RELEASE_VERSION_NAME' release." 44 | fi 45 | 46 | echo "Validating APKs" 47 | for abi in universal arm64-v8a armeabi-v7a x86_64 x86; do 48 | if ! test -f "$APK_DIR_PATH/${APK_BASENAME_PREFIX}_$abi.apk"; then 49 | files_found="$(ls "$APK_DIR_PATH")" 50 | exit_on_error "Failed to find built APK at '$APK_DIR_PATH/${APK_BASENAME_PREFIX}_$abi.apk'. Files found: "$'\n'"$files_found" 51 | fi 52 | done 53 | 54 | echo "Generating sha25sums file" 55 | if ! (cd "$APK_DIR_PATH"; sha256sum \ 56 | "${APK_BASENAME_PREFIX}_universal.apk" \ 57 | "${APK_BASENAME_PREFIX}_arm64-v8a.apk" \ 58 | "${APK_BASENAME_PREFIX}_armeabi-v7a.apk" \ 59 | "${APK_BASENAME_PREFIX}_x86_64.apk" \ 60 | "${APK_BASENAME_PREFIX}_x86.apk" \ 61 | > sha256sums); then 62 | exit_on_error "Generate sha25sums failed for '$RELEASE_VERSION_NAME' release." 63 | fi 64 | 65 | echo "Attaching APKs to github release" 66 | if ! hub release edit \ 67 | -m "" \ 68 | -a "$APK_DIR_PATH/${APK_BASENAME_PREFIX}_universal.apk" \ 69 | -a "$APK_DIR_PATH/${APK_BASENAME_PREFIX}_arm64-v8a.apk" \ 70 | -a "$APK_DIR_PATH/${APK_BASENAME_PREFIX}_armeabi-v7a.apk" \ 71 | -a "$APK_DIR_PATH/${APK_BASENAME_PREFIX}_x86_64.apk" \ 72 | -a "$APK_DIR_PATH/${APK_BASENAME_PREFIX}_x86.apk" \ 73 | -a "$APK_DIR_PATH/sha256sums" \ 74 | "$RELEASE_VERSION_NAME"; then 75 | exit_on_error "Attach APKs to release failed for '$RELEASE_VERSION_NAME' release." 76 | fi 77 | -------------------------------------------------------------------------------- /terminal-emulator/src/test/java/com/termux/terminal/ControlSequenceIntroducerTest.java: -------------------------------------------------------------------------------- 1 | package com.termux.terminal; 2 | 3 | /** "\033[" is the Control Sequence Introducer char sequence (CSI). */ 4 | public class ControlSequenceIntroducerTest extends TerminalTestCase { 5 | 6 | /** CSI Ps P Scroll down Ps lines (default = 1) (SD). */ 7 | public void testCsiT() { 8 | withTerminalSized(4, 6).enterString("1\r\n2\r\n3\r\nhi\033[2Tyo\r\nA\r\nB").assertLinesAre(" ", " ", "1 ", "2 yo", "A ", 9 | "Bi "); 10 | // Default value (1): 11 | withTerminalSized(4, 6).enterString("1\r\n2\r\n3\r\nhi\033[Tyo\r\nA\r\nB").assertLinesAre(" ", "1 ", "2 ", "3 yo", "Ai ", 12 | "B "); 13 | } 14 | 15 | /** CSI Ps S Scroll up Ps lines (default = 1) (SU). */ 16 | public void testCsiS() { 17 | // The behaviour here is a bit inconsistent between terminals - this is how the OS X Terminal.app does it: 18 | withTerminalSized(3, 4).enterString("1\r\n2\r\n3\r\nhi\033[2Sy").assertLinesAre("3 ", "hi ", " ", " y"); 19 | // Default value (1): 20 | withTerminalSized(3, 4).enterString("1\r\n2\r\n3\r\nhi\033[Sy").assertLinesAre("2 ", "3 ", "hi ", " y"); 21 | } 22 | 23 | /** CSI Ps X Erase Ps Character(s) (default = 1) (ECH). */ 24 | public void testCsiX() { 25 | // See https://code.google.com/p/chromium/issues/detail?id=212712 where test was extraced from. 26 | withTerminalSized(13, 2).enterString("abcdefghijkl\b\b\b\b\b\033[X").assertLinesAre("abcdefg ijkl ", " "); 27 | withTerminalSized(13, 2).enterString("abcdefghijkl\b\b\b\b\b\033[1X").assertLinesAre("abcdefg ijkl ", " "); 28 | withTerminalSized(13, 2).enterString("abcdefghijkl\b\b\b\b\b\033[2X").assertLinesAre("abcdefg jkl ", " "); 29 | withTerminalSized(13, 2).enterString("abcdefghijkl\b\b\b\b\b\033[20X").assertLinesAre("abcdefg ", " "); 30 | } 31 | 32 | /** CSI Pm m Set SGR parameter(s) from semicolon-separated list Pm. */ 33 | public void testCsiSGRParameters() { 34 | // Set more parameters (19) than supported (16). Additional parameters should be silently consumed. 35 | withTerminalSized(3, 2).enterString("\033[0;38;2;255;255;255;48;2;0;0;0;1;2;3;4;5;7;8;9mabc").assertLinesAre("abc", " "); 36 | } 37 | 38 | /** CSI Ps b Repeat the preceding graphic character Ps times (REP). */ 39 | public void testRepeat() { 40 | withTerminalSized(3, 2).enterString("a\033[b").assertLinesAre("aa ", " "); 41 | withTerminalSized(3, 2).enterString("a\033[2b").assertLinesAre("aaa", " "); 42 | // When no char has been output we ignore REP: 43 | withTerminalSized(3, 2).enterString("\033[b").assertLinesAre(" ", " "); 44 | // This shows that REP outputs the last emitted code point and not the one relative to the 45 | // current cursor position: 46 | withTerminalSized(5, 2).enterString("abcde\033[2G\033[2b\n").assertLinesAre("aeede", " "); 47 | } 48 | 49 | /** CSI 3 J Clear scrollback (xterm, libvte; non-standard). */ 50 | public void testCsi3J() { 51 | withTerminalSized(3, 2).enterString("a\r\nb\r\nc\r\nd"); 52 | assertEquals("a\nb\nc\nd", mTerminal.getScreen().getTranscriptText()); 53 | enterString("\033[3J"); 54 | assertEquals("c\nd", mTerminal.getScreen().getTranscriptText()); 55 | 56 | withTerminalSized(3, 2).enterString("Lorem_ipsum"); 57 | assertEquals("Lorem_ipsum", mTerminal.getScreen().getTranscriptText()); 58 | enterString("\033[3J"); 59 | assertEquals("ipsum", mTerminal.getScreen().getTranscriptText()); 60 | 61 | withTerminalSized(3, 2).enterString("w\r\nx\r\ny\r\nz\033[?1049h\033[3J\033[?1049l"); 62 | assertEquals("y\nz", mTerminal.getScreen().getTranscriptText()); 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /termux-shared/src/main/java/com/termux/shared/interact/TextInputDialogUtils.java: -------------------------------------------------------------------------------- 1 | package com.termux.shared.interact; 2 | 3 | import android.app.Activity; 4 | import android.app.AlertDialog; 5 | import android.content.Context; 6 | import android.content.DialogInterface; 7 | import android.text.Selection; 8 | import android.util.TypedValue; 9 | import android.view.KeyEvent; 10 | import android.view.LayoutInflater; 11 | import android.view.View; 12 | import android.view.ViewGroup.LayoutParams; 13 | import android.widget.EditText; 14 | import android.widget.LinearLayout; 15 | import android.widget.TextView; 16 | 17 | import com.termux.shared.R; 18 | 19 | public final class TextInputDialogUtils { 20 | 21 | public interface TextSetListener { 22 | void onTextSet(String text); 23 | } 24 | 25 | public static void textInput(Activity activity, int titleText, String initialText, 26 | int positiveButtonText, final TextSetListener onPositive, 27 | int neutralButtonText, final TextSetListener onNeutral, 28 | int negativeButtonText, final TextSetListener onNegative, 29 | final DialogInterface.OnDismissListener onDismiss) { 30 | final EditText input = new EditText(activity); 31 | input.setSingleLine(); 32 | if (initialText != null) { 33 | input.setText(initialText); 34 | Selection.setSelection(input.getText(), initialText.length()); 35 | } 36 | 37 | final AlertDialog[] dialogHolder = new AlertDialog[1]; 38 | input.setImeActionLabel(activity.getResources().getString(positiveButtonText), KeyEvent.KEYCODE_ENTER); 39 | input.setOnEditorActionListener((v, actionId, event) -> { 40 | onPositive.onTextSet(input.getText().toString()); 41 | dialogHolder[0].dismiss(); 42 | return true; 43 | }); 44 | 45 | float dipInPixels = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, activity.getResources().getDisplayMetrics()); 46 | // https://www.google.com/design/spec/components/dialogs.html#dialogs-specs 47 | int paddingTopAndSides = Math.round(16 * dipInPixels); 48 | int paddingBottom = Math.round(24 * dipInPixels); 49 | 50 | LinearLayout layout = new LinearLayout(activity); 51 | layout.setOrientation(LinearLayout.VERTICAL); 52 | layout.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); 53 | layout.setPadding(paddingTopAndSides, paddingTopAndSides, paddingTopAndSides, paddingBottom); 54 | layout.addView(input); 55 | 56 | AlertDialog.Builder builder = new AlertDialog.Builder(activity) 57 | .setTitle(titleText).setView(layout) 58 | .setPositiveButton(positiveButtonText, (d, whichButton) -> onPositive.onTextSet(input.getText().toString())); 59 | 60 | if (onNeutral != null) { 61 | builder.setNeutralButton(neutralButtonText, (dialog, which) -> onNeutral.onTextSet(input.getText().toString())); 62 | } 63 | 64 | if (onNegative == null) { 65 | builder.setNegativeButton(android.R.string.cancel, null); 66 | } else { 67 | builder.setNegativeButton(negativeButtonText, (dialog, which) -> onNegative.onTextSet(input.getText().toString())); 68 | } 69 | 70 | if (onDismiss != null) 71 | builder.setOnDismissListener(onDismiss); 72 | 73 | dialogHolder[0] = builder.create(); 74 | dialogHolder[0].setCanceledOnTouchOutside(false); 75 | dialogHolder[0].show(); 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /termux-shared/src/main/java/com/termux/shared/models/ReportInfo.java: -------------------------------------------------------------------------------- 1 | package com.termux.shared.models; 2 | 3 | import com.termux.shared.markdown.MarkdownUtils; 4 | import com.termux.shared.termux.AndroidUtils; 5 | 6 | import java.io.Serializable; 7 | 8 | /** 9 | * An object that stored info for {@link com.termux.shared.activities.ReportActivity}. 10 | */ 11 | public class ReportInfo implements Serializable { 12 | 13 | /** The user action that was being processed for which the report was generated. */ 14 | public final String userAction; 15 | /** The internal app component that sent the report. */ 16 | public final String sender; 17 | /** The report title. */ 18 | public final String reportTitle; 19 | /** The markdown report text prefix. Will not be part of copy and share operations, etc. */ 20 | public String reportStringPrefix; 21 | /** The markdown report text. */ 22 | public String reportString; 23 | /** The markdown report text suffix. Will not be part of copy and share operations, etc. */ 24 | public String reportStringSuffix; 25 | /** If set to {@code true}, then report, app and device info will be added to the report when 26 | * markdown is generated. 27 | */ 28 | public final boolean addReportInfoHeaderToMarkdown; 29 | /** The timestamp for the report. */ 30 | public final String reportTimestamp; 31 | 32 | /** The label for the report file to save if user selects menu_item_save_report_to_file. */ 33 | public final String reportSaveFileLabel; 34 | /** The path for the report file to save if user selects menu_item_save_report_to_file. */ 35 | public final String reportSaveFilePath; 36 | 37 | public ReportInfo(String userAction, String sender, String reportTitle, String reportStringPrefix, 38 | String reportString, String reportStringSuffix, boolean addReportInfoHeaderToMarkdown, 39 | String reportSaveFileLabel, String reportSaveFilePath) { 40 | this.userAction = userAction; 41 | this.sender = sender; 42 | this.reportTitle = reportTitle; 43 | this.reportStringPrefix = reportStringPrefix; 44 | this.reportString = reportString; 45 | this.reportStringSuffix = reportStringSuffix; 46 | this.addReportInfoHeaderToMarkdown = addReportInfoHeaderToMarkdown; 47 | this.reportSaveFileLabel = reportSaveFileLabel; 48 | this.reportSaveFilePath = reportSaveFilePath; 49 | this.reportTimestamp = AndroidUtils.getCurrentMilliSecondUTCTimeStamp(); 50 | } 51 | 52 | /** 53 | * Get a markdown {@link String} for {@link ReportInfo}. 54 | * 55 | * @param reportInfo The {@link ReportInfo} to convert. 56 | * @return Returns the markdown {@link String}. 57 | */ 58 | public static String getReportInfoMarkdownString(final ReportInfo reportInfo) { 59 | if (reportInfo == null) return "null"; 60 | 61 | StringBuilder markdownString = new StringBuilder(); 62 | 63 | if (reportInfo.addReportInfoHeaderToMarkdown) { 64 | markdownString.append("## Report Info\n\n"); 65 | markdownString.append("\n").append(MarkdownUtils.getSingleLineMarkdownStringEntry("User Action", reportInfo.userAction, "-")); 66 | markdownString.append("\n").append(MarkdownUtils.getSingleLineMarkdownStringEntry("Sender", reportInfo.sender, "-")); 67 | markdownString.append("\n").append(MarkdownUtils.getSingleLineMarkdownStringEntry("Report Timestamp", reportInfo.reportTimestamp, "-")); 68 | markdownString.append("\n##\n\n"); 69 | } 70 | 71 | markdownString.append(reportInfo.reportString); 72 | 73 | return markdownString.toString(); 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /app/src/main/java/com/termux/app/fragments/settings/termux_api/DebuggingPreferencesFragment.java: -------------------------------------------------------------------------------- 1 | package com.termux.app.fragments.settings.termux_api; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | 6 | import androidx.annotation.Keep; 7 | import androidx.annotation.NonNull; 8 | import androidx.annotation.Nullable; 9 | import androidx.preference.ListPreference; 10 | import androidx.preference.PreferenceCategory; 11 | import androidx.preference.PreferenceDataStore; 12 | import androidx.preference.PreferenceFragmentCompat; 13 | import androidx.preference.PreferenceManager; 14 | 15 | import com.termux.R; 16 | import com.termux.shared.settings.preferences.TermuxAPIAppSharedPreferences; 17 | 18 | @Keep 19 | public class DebuggingPreferencesFragment extends PreferenceFragmentCompat { 20 | 21 | @Override 22 | public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { 23 | Context context = getContext(); 24 | if (context == null) return; 25 | 26 | PreferenceManager preferenceManager = getPreferenceManager(); 27 | preferenceManager.setPreferenceDataStore(DebuggingPreferencesDataStore.getInstance(context)); 28 | 29 | setPreferencesFromResource(R.xml.termux_api_debugging_preferences, rootKey); 30 | 31 | configureLoggingPreferences(context); 32 | } 33 | 34 | private void configureLoggingPreferences(@NonNull Context context) { 35 | PreferenceCategory loggingCategory = findPreference("logging"); 36 | if (loggingCategory == null) return; 37 | 38 | ListPreference logLevelListPreference = findPreference("log_level"); 39 | if (logLevelListPreference != null) { 40 | TermuxAPIAppSharedPreferences preferences = TermuxAPIAppSharedPreferences.build(context, true); 41 | if (preferences == null) return; 42 | 43 | com.termux.app.fragments.settings.termux.DebuggingPreferencesFragment. 44 | setLogLevelListPreferenceData(logLevelListPreference, context, preferences.getLogLevel(true)); 45 | loggingCategory.addPreference(logLevelListPreference); 46 | } 47 | } 48 | } 49 | 50 | class DebuggingPreferencesDataStore extends PreferenceDataStore { 51 | 52 | private final Context mContext; 53 | private final TermuxAPIAppSharedPreferences mPreferences; 54 | 55 | private static DebuggingPreferencesDataStore mInstance; 56 | 57 | private DebuggingPreferencesDataStore(Context context) { 58 | mContext = context; 59 | mPreferences = TermuxAPIAppSharedPreferences.build(context, true); 60 | } 61 | 62 | public static synchronized DebuggingPreferencesDataStore getInstance(Context context) { 63 | if (mInstance == null) { 64 | mInstance = new DebuggingPreferencesDataStore(context); 65 | } 66 | return mInstance; 67 | } 68 | 69 | 70 | 71 | @Override 72 | @Nullable 73 | public String getString(String key, @Nullable String defValue) { 74 | if (mPreferences == null) return null; 75 | if (key == null) return null; 76 | 77 | switch (key) { 78 | case "log_level": 79 | return String.valueOf(mPreferences.getLogLevel(true)); 80 | default: 81 | return null; 82 | } 83 | } 84 | 85 | @Override 86 | public void putString(String key, @Nullable String value) { 87 | if (mPreferences == null) return; 88 | if (key == null) return; 89 | 90 | switch (key) { 91 | case "log_level": 92 | if (value != null) { 93 | mPreferences.setLogLevel(mContext, Integer.parseInt(value), true); 94 | } 95 | break; 96 | default: 97 | break; 98 | } 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /app/src/main/java/com/termux/app/fragments/settings/termux_tasker/DebuggingPreferencesFragment.java: -------------------------------------------------------------------------------- 1 | package com.termux.app.fragments.settings.termux_tasker; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | 6 | import androidx.annotation.Keep; 7 | import androidx.annotation.NonNull; 8 | import androidx.annotation.Nullable; 9 | import androidx.preference.ListPreference; 10 | import androidx.preference.PreferenceCategory; 11 | import androidx.preference.PreferenceDataStore; 12 | import androidx.preference.PreferenceFragmentCompat; 13 | import androidx.preference.PreferenceManager; 14 | 15 | import com.termux.R; 16 | import com.termux.shared.settings.preferences.TermuxTaskerAppSharedPreferences; 17 | 18 | @Keep 19 | public class DebuggingPreferencesFragment extends PreferenceFragmentCompat { 20 | 21 | @Override 22 | public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { 23 | Context context = getContext(); 24 | if (context == null) return; 25 | 26 | PreferenceManager preferenceManager = getPreferenceManager(); 27 | preferenceManager.setPreferenceDataStore(DebuggingPreferencesDataStore.getInstance(context)); 28 | 29 | setPreferencesFromResource(R.xml.termux_tasker_debugging_preferences, rootKey); 30 | 31 | configureLoggingPreferences(context); 32 | } 33 | 34 | private void configureLoggingPreferences(@NonNull Context context) { 35 | PreferenceCategory loggingCategory = findPreference("logging"); 36 | if (loggingCategory == null) return; 37 | 38 | ListPreference logLevelListPreference = findPreference("log_level"); 39 | if (logLevelListPreference != null) { 40 | TermuxTaskerAppSharedPreferences preferences = TermuxTaskerAppSharedPreferences.build(context, true); 41 | if (preferences == null) return; 42 | 43 | com.termux.app.fragments.settings.termux.DebuggingPreferencesFragment. 44 | setLogLevelListPreferenceData(logLevelListPreference, context, preferences.getLogLevel(true)); 45 | loggingCategory.addPreference(logLevelListPreference); 46 | } 47 | } 48 | } 49 | 50 | class DebuggingPreferencesDataStore extends PreferenceDataStore { 51 | 52 | private final Context mContext; 53 | private final TermuxTaskerAppSharedPreferences mPreferences; 54 | 55 | private static DebuggingPreferencesDataStore mInstance; 56 | 57 | private DebuggingPreferencesDataStore(Context context) { 58 | mContext = context; 59 | mPreferences = TermuxTaskerAppSharedPreferences.build(context, true); 60 | } 61 | 62 | public static synchronized DebuggingPreferencesDataStore getInstance(Context context) { 63 | if (mInstance == null) { 64 | mInstance = new DebuggingPreferencesDataStore(context); 65 | } 66 | return mInstance; 67 | } 68 | 69 | 70 | 71 | @Override 72 | @Nullable 73 | public String getString(String key, @Nullable String defValue) { 74 | if (mPreferences == null) return null; 75 | if (key == null) return null; 76 | 77 | switch (key) { 78 | case "log_level": 79 | return String.valueOf(mPreferences.getLogLevel(true)); 80 | default: 81 | return null; 82 | } 83 | } 84 | 85 | @Override 86 | public void putString(String key, @Nullable String value) { 87 | if (mPreferences == null) return; 88 | if (key == null) return; 89 | 90 | switch (key) { 91 | case "log_level": 92 | if (value != null) { 93 | mPreferences.setLogLevel(mContext, Integer.parseInt(value), true); 94 | } 95 | break; 96 | default: 97 | break; 98 | } 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /app/src/main/java/com/termux/app/fragments/settings/termux_widget/DebuggingPreferencesFragment.java: -------------------------------------------------------------------------------- 1 | package com.termux.app.fragments.settings.termux_widget; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | 6 | import androidx.annotation.Keep; 7 | import androidx.annotation.NonNull; 8 | import androidx.annotation.Nullable; 9 | import androidx.preference.ListPreference; 10 | import androidx.preference.PreferenceCategory; 11 | import androidx.preference.PreferenceDataStore; 12 | import androidx.preference.PreferenceFragmentCompat; 13 | import androidx.preference.PreferenceManager; 14 | 15 | import com.termux.R; 16 | import com.termux.shared.settings.preferences.TermuxWidgetAppSharedPreferences; 17 | 18 | @Keep 19 | public class DebuggingPreferencesFragment extends PreferenceFragmentCompat { 20 | 21 | @Override 22 | public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { 23 | Context context = getContext(); 24 | if (context == null) return; 25 | 26 | PreferenceManager preferenceManager = getPreferenceManager(); 27 | preferenceManager.setPreferenceDataStore(DebuggingPreferencesDataStore.getInstance(context)); 28 | 29 | setPreferencesFromResource(R.xml.termux_widget_debugging_preferences, rootKey); 30 | 31 | configureLoggingPreferences(context); 32 | } 33 | 34 | private void configureLoggingPreferences(@NonNull Context context) { 35 | PreferenceCategory loggingCategory = findPreference("logging"); 36 | if (loggingCategory == null) return; 37 | 38 | ListPreference logLevelListPreference = findPreference("log_level"); 39 | if (logLevelListPreference != null) { 40 | TermuxWidgetAppSharedPreferences preferences = TermuxWidgetAppSharedPreferences.build(context, true); 41 | if (preferences == null) return; 42 | 43 | com.termux.app.fragments.settings.termux.DebuggingPreferencesFragment. 44 | setLogLevelListPreferenceData(logLevelListPreference, context, preferences.getLogLevel(true)); 45 | loggingCategory.addPreference(logLevelListPreference); 46 | } 47 | } 48 | } 49 | 50 | class DebuggingPreferencesDataStore extends PreferenceDataStore { 51 | 52 | private final Context mContext; 53 | private final TermuxWidgetAppSharedPreferences mPreferences; 54 | 55 | private static DebuggingPreferencesDataStore mInstance; 56 | 57 | private DebuggingPreferencesDataStore(Context context) { 58 | mContext = context; 59 | mPreferences = TermuxWidgetAppSharedPreferences.build(context, true); 60 | } 61 | 62 | public static synchronized DebuggingPreferencesDataStore getInstance(Context context) { 63 | if (mInstance == null) { 64 | mInstance = new DebuggingPreferencesDataStore(context); 65 | } 66 | return mInstance; 67 | } 68 | 69 | 70 | 71 | @Override 72 | @Nullable 73 | public String getString(String key, @Nullable String defValue) { 74 | if (mPreferences == null) return null; 75 | if (key == null) return null; 76 | 77 | switch (key) { 78 | case "log_level": 79 | return String.valueOf(mPreferences.getLogLevel(true)); 80 | default: 81 | return null; 82 | } 83 | } 84 | 85 | @Override 86 | public void putString(String key, @Nullable String value) { 87 | if (mPreferences == null) return; 88 | if (key == null) return; 89 | 90 | switch (key) { 91 | case "log_level": 92 | if (value != null) { 93 | mPreferences.setLogLevel(mContext, Integer.parseInt(value), true); 94 | } 95 | break; 96 | default: 97 | break; 98 | } 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /terminal-emulator/src/main/java/com/termux/terminal/TextStyle.java: -------------------------------------------------------------------------------- 1 | package com.termux.terminal; 2 | 3 | /** 4 | * 5 | * Encodes effects, foreground and background colors into a 64 bit long, which are stored for each cell in a terminal 6 | * row in {@link TerminalRow#mStyle}. 7 | * 8 | * 9 | * The bit layout is: 10 | * 11 | * - 16 flags (11 currently used). 12 | * - 24 for foreground color (only 9 first bits if a color index). 13 | * - 24 for background color (only 9 first bits if a color index). 14 | */ 15 | public final class TextStyle { 16 | 17 | public final static int CHARACTER_ATTRIBUTE_BOLD = 1; 18 | public final static int CHARACTER_ATTRIBUTE_ITALIC = 1 << 1; 19 | public final static int CHARACTER_ATTRIBUTE_UNDERLINE = 1 << 2; 20 | public final static int CHARACTER_ATTRIBUTE_BLINK = 1 << 3; 21 | public final static int CHARACTER_ATTRIBUTE_INVERSE = 1 << 4; 22 | public final static int CHARACTER_ATTRIBUTE_INVISIBLE = 1 << 5; 23 | public final static int CHARACTER_ATTRIBUTE_STRIKETHROUGH = 1 << 6; 24 | /** 25 | * The selective erase control functions (DECSED and DECSEL) can only erase characters defined as erasable. 26 | * 27 | * This bit is set if DECSCA (Select Character Protection Attribute) has been used to define the characters that 28 | * come after it as erasable from the screen. 29 | * 30 | */ 31 | public final static int CHARACTER_ATTRIBUTE_PROTECTED = 1 << 7; 32 | /** Dim colors. Also known as faint or half intensity. */ 33 | public final static int CHARACTER_ATTRIBUTE_DIM = 1 << 8; 34 | /** If true (24-bit) color is used for the cell for foreground. */ 35 | private final static int CHARACTER_ATTRIBUTE_TRUECOLOR_FOREGROUND = 1 << 9; 36 | /** If true (24-bit) color is used for the cell for foreground. */ 37 | private final static int CHARACTER_ATTRIBUTE_TRUECOLOR_BACKGROUND= 1 << 10; 38 | 39 | public final static int COLOR_INDEX_FOREGROUND = 256; 40 | public final static int COLOR_INDEX_BACKGROUND = 257; 41 | public final static int COLOR_INDEX_CURSOR = 258; 42 | 43 | /** The 256 standard color entries and the three special (foreground, background and cursor) ones. */ 44 | public final static int NUM_INDEXED_COLORS = 259; 45 | 46 | /** Normal foreground and background colors and no effects. */ 47 | final static long NORMAL = encode(COLOR_INDEX_FOREGROUND, COLOR_INDEX_BACKGROUND, 0); 48 | 49 | static long encode(int foreColor, int backColor, int effect) { 50 | long result = effect & 0b111111111; 51 | if ((0xff000000 & foreColor) == 0xff000000) { 52 | // 24-bit color. 53 | result |= CHARACTER_ATTRIBUTE_TRUECOLOR_FOREGROUND | ((foreColor & 0x00ffffffL) << 40L); 54 | } else { 55 | // Indexed color. 56 | result |= (foreColor & 0b111111111L) << 40; 57 | } 58 | if ((0xff000000 & backColor) == 0xff000000) { 59 | // 24-bit color. 60 | result |= CHARACTER_ATTRIBUTE_TRUECOLOR_BACKGROUND | ((backColor & 0x00ffffffL) << 16L); 61 | } else { 62 | // Indexed color. 63 | result |= (backColor & 0b111111111L) << 16L; 64 | } 65 | 66 | return result; 67 | } 68 | 69 | public static int decodeForeColor(long style) { 70 | if ((style & CHARACTER_ATTRIBUTE_TRUECOLOR_FOREGROUND) == 0) { 71 | return (int) ((style >>> 40) & 0b111111111L); 72 | } else { 73 | return 0xff000000 | (int) ((style >>> 40) & 0x00ffffffL); 74 | } 75 | 76 | } 77 | 78 | public static int decodeBackColor(long style) { 79 | if ((style & CHARACTER_ATTRIBUTE_TRUECOLOR_BACKGROUND) == 0) { 80 | return (int) ((style >>> 16) & 0b111111111L); 81 | } else { 82 | return 0xff000000 | (int) ((style >>> 16) & 0x00ffffffL); 83 | } 84 | } 85 | 86 | public static int decodeEffect(long style) { 87 | return (int) (style & 0b11111111111); 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /termux-shared/src/main/java/com/termux/shared/interact/MessageDialogUtils.java: -------------------------------------------------------------------------------- 1 | package com.termux.shared.interact; 2 | 3 | import android.app.Activity; 4 | import android.app.AlertDialog; 5 | import android.content.Context; 6 | import android.content.DialogInterface; 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | import android.widget.TextView; 10 | 11 | import com.termux.shared.R; 12 | 13 | public class MessageDialogUtils { 14 | 15 | /** 16 | * Show a message in a dialog 17 | * 18 | * @param context The {@link Context} to use to start the dialog. An {@link Activity} {@link Context} 19 | * must be passed, otherwise exceptions will be thrown. 20 | * @param titleText The title text of the dialog. 21 | * @param messageText The message text of the dialog. 22 | * @param onDismiss The {@link DialogInterface.OnDismissListener} to run when dialog is dismissed. 23 | */ 24 | public static void showMessage(Context context, String titleText, String messageText, final DialogInterface.OnDismissListener onDismiss) { 25 | showMessage(context, titleText, messageText, null, null, null, null, onDismiss); 26 | } 27 | 28 | /** 29 | * Show a message in a dialog 30 | * 31 | * @param context The {@link Context} to use to start the dialog. An {@link Activity} {@link Context} 32 | * must be passed, otherwise exceptions will be thrown. 33 | * @param titleText The title text of the dialog. 34 | * @param messageText The message text of the dialog. 35 | * @param positiveText The positive button text of the dialog. 36 | * @param onPositiveButton The {@link DialogInterface.OnClickListener} to run when positive button 37 | * is pressed. 38 | * @param negativeText The negative button text of the dialog. If this is {@code null}, then 39 | * negative button will not be shown. 40 | * @param onNegativeButton The {@link DialogInterface.OnClickListener} to run when negative button 41 | * is pressed. 42 | * @param onDismiss The {@link DialogInterface.OnDismissListener} to run when dialog is dismissed. 43 | */ 44 | public static void showMessage(Context context, String titleText, String messageText, 45 | String positiveText, 46 | final DialogInterface.OnClickListener onPositiveButton, 47 | String negativeText, 48 | final DialogInterface.OnClickListener onNegativeButton, 49 | final DialogInterface.OnDismissListener onDismiss) { 50 | 51 | AlertDialog.Builder builder = new AlertDialog.Builder(context, R.style.Theme_AppCompat_Light_Dialog); 52 | 53 | LayoutInflater inflater = (LayoutInflater) context.getSystemService( Context.LAYOUT_INFLATER_SERVICE ); 54 | View view = inflater.inflate(R.layout.dialog_show_message, null); 55 | if (view != null) { 56 | builder.setView(view); 57 | 58 | TextView titleView = view.findViewById(R.id.dialog_title); 59 | if (titleView != null) 60 | titleView.setText(titleText); 61 | 62 | TextView messageView = view.findViewById(R.id.dialog_message); 63 | if (messageView != null) 64 | messageView.setText(messageText); 65 | } 66 | 67 | if (positiveText == null) 68 | positiveText = context.getString(android.R.string.ok); 69 | builder.setPositiveButton(positiveText, onPositiveButton); 70 | 71 | if (negativeText != null) 72 | builder.setNegativeButton(negativeText, onNegativeButton); 73 | 74 | if (onDismiss != null) 75 | builder.setOnDismissListener(onDismiss); 76 | 77 | builder.show(); 78 | } 79 | 80 | public static void exitAppWithErrorMessage(Context context, String titleText, String messageText) { 81 | showMessage(context, titleText, messageText, dialog -> System.exit(0)); 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /termux-shared/src/main/res/layout/activity_text_io.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 10 | 11 | 21 | 22 | 29 | 30 | 31 | 32 | 36 | 37 | 38 | 48 | 49 | 53 | 54 | 59 | 60 | 65 | 66 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /termux-shared/src/main/java/com/termux/shared/models/errors/Errno.java: -------------------------------------------------------------------------------- 1 | package com.termux.shared.models.errors; 2 | 3 | import android.app.Activity; 4 | 5 | import androidx.annotation.NonNull; 6 | 7 | import com.termux.shared.logger.Logger; 8 | 9 | import java.util.Arrays; 10 | import java.util.Collections; 11 | import java.util.HashMap; 12 | import java.util.List; 13 | 14 | /** The {@link Class} that defines error messages and codes. */ 15 | public class Errno { 16 | 17 | private static final HashMap map = new HashMap<>(); 18 | 19 | public static final String TYPE = "Error"; 20 | 21 | 22 | public static final Errno ERRNO_SUCCESS = new Errno(TYPE, Activity.RESULT_OK, "Success"); 23 | public static final Errno ERRNO_CANCELLED = new Errno(TYPE, Activity.RESULT_CANCELED, "Cancelled"); 24 | public static final Errno ERRNO_MINOR_FAILURES = new Errno(TYPE, Activity.RESULT_FIRST_USER, "Minor failure"); 25 | public static final Errno ERRNO_FAILED = new Errno(TYPE, Activity.RESULT_FIRST_USER + 1, "Failed"); 26 | 27 | /** The errno type. */ 28 | protected String type; 29 | /** The errno code. */ 30 | protected final int code; 31 | /** The errno message. */ 32 | protected final String message; 33 | 34 | private static final String LOG_TAG = "Errno"; 35 | 36 | 37 | public Errno(final String type, final int code, final String message) { 38 | this.type = type; 39 | this.code = code; 40 | this.message = message; 41 | map.put(type + ":" + code, this); 42 | } 43 | 44 | @NonNull 45 | @Override 46 | public String toString() { 47 | return "type=" + type + ", code=" + code + ", message=\"" + message + "\""; 48 | } 49 | 50 | 51 | public String getType() { 52 | return type; 53 | } 54 | 55 | public String getMessage() { 56 | return message; 57 | } 58 | 59 | public int getCode() { 60 | return code; 61 | } 62 | 63 | /** 64 | * Get the {@link Errno} of a specific type and code. 65 | * 66 | * @param type The unique type of the {@link Errno}. 67 | * @param code The unique code of the {@link Errno}. 68 | */ 69 | public static Errno valueOf(String type, Integer code) { 70 | if (type == null || type.isEmpty() || code == null) return null; 71 | return map.get(type + ":" + code); 72 | } 73 | 74 | 75 | 76 | public Error getError() { 77 | return new Error(getType(), getCode(), getMessage()); 78 | } 79 | 80 | public Error getError(Object... args) { 81 | try { 82 | return new Error(getType(), getCode(), String.format(getMessage(), args)); 83 | } catch (Exception e) { 84 | Logger.logWarn(LOG_TAG, "Exception raised while calling String.format() for error message of errno " + this + " with args" + Arrays.toString(args) + "\n" + e.getMessage()); 85 | // Return unformatted message as a backup 86 | return new Error(getType(), getCode(), getMessage() + ": " + Arrays.toString(args)); 87 | } 88 | } 89 | 90 | public Error getError(Throwable throwable, Object... args) { 91 | if (throwable == null) 92 | return getError(args); 93 | else 94 | return getError(Collections.singletonList(throwable), args); 95 | } 96 | 97 | public Error getError(List throwablesList, Object... args) { 98 | try { 99 | if (throwablesList == null) 100 | return new Error(getType(), getCode(), String.format(getMessage(), args)); 101 | else 102 | return new Error(getType(), getCode(), String.format(getMessage(), args), throwablesList); 103 | } catch (Exception e) { 104 | Logger.logWarn(LOG_TAG, "Exception raised while calling String.format() for error message of errno " + this + " with args" + Arrays.toString(args) + "\n" + e.getMessage()); 105 | // Return unformatted message as a backup 106 | return new Error(getType(), getCode(), getMessage() + ": " + Arrays.toString(args), throwablesList); 107 | } 108 | } 109 | 110 | } 111 | --------------------------------------------------------------------------------
The {@link FileAttributes} class defines methods for manipulating 34 | * set of permissions. 35 | * 36 | * https://cs.android.com/android/platform/superproject/+/android-11.0.0_r3:libcore/ojluni/src/main/java/java/nio/file/attribute/PosixFilePermission.java 37 | * 38 | * @since 1.7 39 | */ 40 | 41 | public enum FilePermission { 42 | 43 | /** 44 | * Read permission, owner. 45 | */ 46 | OWNER_READ, 47 | 48 | /** 49 | * Write permission, owner. 50 | */ 51 | OWNER_WRITE, 52 | 53 | /** 54 | * Execute/search permission, owner. 55 | */ 56 | OWNER_EXECUTE, 57 | 58 | /** 59 | * Read permission, group. 60 | */ 61 | GROUP_READ, 62 | 63 | /** 64 | * Write permission, group. 65 | */ 66 | GROUP_WRITE, 67 | 68 | /** 69 | * Execute/search permission, group. 70 | */ 71 | GROUP_EXECUTE, 72 | 73 | /** 74 | * Read permission, others. 75 | */ 76 | OTHERS_READ, 77 | 78 | /** 79 | * Write permission, others. 80 | */ 81 | OTHERS_WRITE, 82 | 83 | /** 84 | * Execute/search permission, others. 85 | */ 86 | OTHERS_EXECUTE 87 | 88 | } 89 | -------------------------------------------------------------------------------- /termux-shared/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'maven-publish' 3 | 4 | android { 5 | compileSdkVersion project.properties.compileSdkVersion.toInteger() 6 | 7 | dependencies { 8 | implementation 'androidx.appcompat:appcompat:1.3.1' 9 | implementation "androidx.annotation:annotation:1.3.0" 10 | implementation "androidx.core:core:1.6.0" 11 | implementation 'com.google.android.material:material:1.4.0' 12 | implementation "com.google.guava:guava:24.1-jre" 13 | implementation "io.noties.markwon:core:$markwonVersion" 14 | implementation "io.noties.markwon:ext-strikethrough:$markwonVersion" 15 | implementation "io.noties.markwon:linkify:$markwonVersion" 16 | implementation "io.noties.markwon:recycler:$markwonVersion" 17 | 18 | // Do not increment version higher than 1.0.0-alpha09 since it will break ViewUtils and needs to be looked into 19 | // noinspection GradleDependency 20 | implementation "androidx.window:window:1.0.0-alpha09" 21 | 22 | // Do not increment version higher than 2.5 or there 23 | // will be runtime exceptions on android < 8 24 | // due to missing classes like java.nio.file.Path. 25 | implementation "commons-io:commons-io:2.5" 26 | 27 | implementation project(":terminal-view") 28 | } 29 | 30 | defaultConfig { 31 | minSdkVersion project.properties.minSdkVersion.toInteger() 32 | targetSdkVersion project.properties.targetSdkVersion.toInteger() 33 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 34 | } 35 | 36 | buildTypes { 37 | release { 38 | minifyEnabled false 39 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 40 | } 41 | } 42 | 43 | compileOptions { 44 | sourceCompatibility JavaVersion.VERSION_1_8 45 | targetCompatibility JavaVersion.VERSION_1_8 46 | } 47 | } 48 | 49 | dependencies { 50 | testImplementation "junit:junit:4.13.2" 51 | androidTestImplementation 'androidx.test.ext:junit:1.1.3' 52 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' 53 | } 54 | 55 | task sourceJar(type: Jar) { 56 | from android.sourceSets.main.java.srcDirs 57 | classifier "sources" 58 | } 59 | 60 | afterEvaluate { 61 | publishing { 62 | publications { 63 | // Creates a Maven publication called "release". 64 | release(MavenPublication) { 65 | from components.release 66 | groupId = 'com.termux' 67 | artifactId = 'termux-shared' 68 | version = '0.118.0' 69 | artifact(sourceJar) 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 23 | 24 | 25 | 26 | 27 | 45 | 46 | 47 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /app/src/main/java/com/termux/app/terminal/io/FullScreenWorkAround.java: -------------------------------------------------------------------------------- 1 | package com.termux.app.terminal.io; 2 | 3 | import android.graphics.Rect; 4 | import android.view.View; 5 | import android.view.ViewGroup; 6 | 7 | import com.termux.app.TermuxActivity; 8 | 9 | /** 10 | * Work around for fullscreen mode in Termux to fix ExtraKeysView not being visible. 11 | * This class is derived from: 12 | * https://stackoverflow.com/questions/7417123/android-how-to-adjust-layout-in-full-screen-mode-when-softkeyboard-is-visible 13 | * and has some additional tweaks 14 | * --- 15 | * For more information, see https://issuetracker.google.com/issues/36911528 16 | */ 17 | public class FullScreenWorkAround { 18 | private final View mChildOfContent; 19 | private int mUsableHeightPrevious; 20 | private final ViewGroup.LayoutParams mViewGroupLayoutParams; 21 | 22 | private final int mNavBarHeight; 23 | 24 | 25 | public static void apply(TermuxActivity activity) { 26 | new FullScreenWorkAround(activity); 27 | } 28 | 29 | private FullScreenWorkAround(TermuxActivity activity) { 30 | ViewGroup content = activity.findViewById(android.R.id.content); 31 | mChildOfContent = content.getChildAt(0); 32 | mViewGroupLayoutParams = mChildOfContent.getLayoutParams(); 33 | mNavBarHeight = activity.getNavBarHeight(); 34 | mChildOfContent.getViewTreeObserver().addOnGlobalLayoutListener(this::possiblyResizeChildOfContent); 35 | } 36 | 37 | private void possiblyResizeChildOfContent() { 38 | int usableHeightNow = computeUsableHeight(); 39 | if (usableHeightNow != mUsableHeightPrevious) { 40 | int usableHeightSansKeyboard = mChildOfContent.getRootView().getHeight(); 41 | int heightDifference = usableHeightSansKeyboard - usableHeightNow; 42 | if (heightDifference > (usableHeightSansKeyboard / 4)) { 43 | // keyboard probably just became visible 44 | 45 | // ensures that usable layout space does not extend behind the 46 | // soft keyboard, causing the extra keys to not be visible 47 | mViewGroupLayoutParams.height = (usableHeightSansKeyboard - heightDifference) + getNavBarHeight(); 48 | } else { 49 | // keyboard probably just became hidden 50 | mViewGroupLayoutParams.height = usableHeightSansKeyboard; 51 | } 52 | mChildOfContent.requestLayout(); 53 | mUsableHeightPrevious = usableHeightNow; 54 | } 55 | } 56 | 57 | private int getNavBarHeight() { 58 | return mNavBarHeight; 59 | } 60 | 61 | private int computeUsableHeight() { 62 | Rect r = new Rect(); 63 | mChildOfContent.getWindowVisibleDisplayFrame(r); 64 | return (r.bottom - r.top); 65 | } 66 | 67 | } 68 | 69 | -------------------------------------------------------------------------------- /terminal-emulator/src/test/java/com/termux/terminal/TextStyleTest.java: -------------------------------------------------------------------------------- 1 | package com.termux.terminal; 2 | 3 | import junit.framework.TestCase; 4 | 5 | public class TextStyleTest extends TestCase { 6 | 7 | private static final int[] ALL_EFFECTS = new int[]{0, TextStyle.CHARACTER_ATTRIBUTE_BOLD, TextStyle.CHARACTER_ATTRIBUTE_ITALIC, 8 | TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE, TextStyle.CHARACTER_ATTRIBUTE_BLINK, TextStyle.CHARACTER_ATTRIBUTE_INVERSE, 9 | TextStyle.CHARACTER_ATTRIBUTE_INVISIBLE, TextStyle.CHARACTER_ATTRIBUTE_STRIKETHROUGH, TextStyle.CHARACTER_ATTRIBUTE_PROTECTED, 10 | TextStyle.CHARACTER_ATTRIBUTE_DIM}; 11 | 12 | public void testEncodingSingle() { 13 | for (int fx : ALL_EFFECTS) { 14 | for (int fg = 0; fg < TextStyle.NUM_INDEXED_COLORS; fg++) { 15 | for (int bg = 0; bg < TextStyle.NUM_INDEXED_COLORS; bg++) { 16 | long encoded = TextStyle.encode(fg, bg, fx); 17 | assertEquals(fg, TextStyle.decodeForeColor(encoded)); 18 | assertEquals(bg, TextStyle.decodeBackColor(encoded)); 19 | assertEquals(fx, TextStyle.decodeEffect(encoded)); 20 | } 21 | } 22 | } 23 | } 24 | 25 | public void testEncoding24Bit() { 26 | int[] values = {255, 240, 127, 1, 0}; 27 | for (int red : values) { 28 | for (int green : values) { 29 | for (int blue : values) { 30 | int argb = 0xFF000000 | (red << 16) | (green << 8) | blue; 31 | long encoded = TextStyle.encode(argb, 0, 0); 32 | assertEquals(argb, TextStyle.decodeForeColor(encoded)); 33 | encoded = TextStyle.encode(0, argb, 0); 34 | assertEquals(argb, TextStyle.decodeBackColor(encoded)); 35 | } 36 | } 37 | } 38 | } 39 | 40 | 41 | public void testEncodingCombinations() { 42 | for (int f1 : ALL_EFFECTS) { 43 | for (int f2 : ALL_EFFECTS) { 44 | int combined = f1 | f2; 45 | assertEquals(combined, TextStyle.decodeEffect(TextStyle.encode(0, 0, combined))); 46 | } 47 | } 48 | } 49 | 50 | public void testEncodingStrikeThrough() { 51 | long encoded = TextStyle.encode(TextStyle.COLOR_INDEX_FOREGROUND, TextStyle.COLOR_INDEX_BACKGROUND, 52 | TextStyle.CHARACTER_ATTRIBUTE_STRIKETHROUGH); 53 | assertTrue((TextStyle.decodeEffect(encoded) & TextStyle.CHARACTER_ATTRIBUTE_STRIKETHROUGH) != 0); 54 | } 55 | 56 | public void testEncodingProtected() { 57 | long encoded = TextStyle.encode(TextStyle.COLOR_INDEX_FOREGROUND, TextStyle.COLOR_INDEX_BACKGROUND, 58 | TextStyle.CHARACTER_ATTRIBUTE_STRIKETHROUGH); 59 | assertEquals(0, (TextStyle.decodeEffect(encoded) & TextStyle.CHARACTER_ATTRIBUTE_PROTECTED)); 60 | encoded = TextStyle.encode(TextStyle.COLOR_INDEX_FOREGROUND, TextStyle.COLOR_INDEX_BACKGROUND, 61 | TextStyle.CHARACTER_ATTRIBUTE_STRIKETHROUGH | TextStyle.CHARACTER_ATTRIBUTE_PROTECTED); 62 | assertTrue((TextStyle.decodeEffect(encoded) & TextStyle.CHARACTER_ATTRIBUTE_PROTECTED) != 0); 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /app/src/main/java/com/termux/app/fragments/settings/termux/TerminalIOPreferencesFragment.java: -------------------------------------------------------------------------------- 1 | package com.termux.app.fragments.settings.termux; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | 6 | import androidx.annotation.Keep; 7 | import androidx.preference.PreferenceDataStore; 8 | import androidx.preference.PreferenceFragmentCompat; 9 | import androidx.preference.PreferenceManager; 10 | 11 | import com.termux.R; 12 | import com.termux.shared.settings.preferences.TermuxAppSharedPreferences; 13 | 14 | @Keep 15 | public class TerminalIOPreferencesFragment extends PreferenceFragmentCompat { 16 | 17 | @Override 18 | public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { 19 | Context context = getContext(); 20 | if (context == null) return; 21 | 22 | PreferenceManager preferenceManager = getPreferenceManager(); 23 | preferenceManager.setPreferenceDataStore(TerminalIOPreferencesDataStore.getInstance(context)); 24 | 25 | setPreferencesFromResource(R.xml.termux_terminal_io_preferences, rootKey); 26 | } 27 | 28 | } 29 | 30 | class TerminalIOPreferencesDataStore extends PreferenceDataStore { 31 | 32 | private final Context mContext; 33 | private final TermuxAppSharedPreferences mPreferences; 34 | 35 | private static TerminalIOPreferencesDataStore mInstance; 36 | 37 | private TerminalIOPreferencesDataStore(Context context) { 38 | mContext = context; 39 | mPreferences = TermuxAppSharedPreferences.build(context, true); 40 | } 41 | 42 | public static synchronized TerminalIOPreferencesDataStore getInstance(Context context) { 43 | if (mInstance == null) { 44 | mInstance = new TerminalIOPreferencesDataStore(context); 45 | } 46 | return mInstance; 47 | } 48 | 49 | 50 | 51 | @Override 52 | public void putBoolean(String key, boolean value) { 53 | if (mPreferences == null) return; 54 | if (key == null) return; 55 | 56 | switch (key) { 57 | case "soft_keyboard_enabled": 58 | mPreferences.setSoftKeyboardEnabled(value); 59 | break; 60 | case "soft_keyboard_enabled_only_if_no_hardware": 61 | mPreferences.setSoftKeyboardEnabledOnlyIfNoHardware(value); 62 | break; 63 | default: 64 | break; 65 | } 66 | } 67 | 68 | @Override 69 | public boolean getBoolean(String key, boolean defValue) { 70 | if (mPreferences == null) return false; 71 | 72 | switch (key) { 73 | case "soft_keyboard_enabled": 74 | return mPreferences.isSoftKeyboardEnabled(); 75 | case "soft_keyboard_enabled_only_if_no_hardware": 76 | return mPreferences.isSoftKeyboardEnabledOnlyIfNoHardware(); 77 | default: 78 | return false; 79 | } 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /app/src/main/java/com/termux/app/activities/HelpActivity.java: -------------------------------------------------------------------------------- 1 | package com.termux.app.activities; 2 | 3 | import android.app.Activity; 4 | import android.content.ActivityNotFoundException; 5 | import android.content.Intent; 6 | import android.net.Uri; 7 | import android.os.Bundle; 8 | import android.view.ViewGroup; 9 | import android.webkit.WebSettings; 10 | import android.webkit.WebView; 11 | import android.webkit.WebViewClient; 12 | import android.widget.ProgressBar; 13 | import android.widget.RelativeLayout; 14 | 15 | import com.termux.shared.termux.TermuxConstants; 16 | 17 | /** Basic embedded browser for viewing help pages. */ 18 | public final class HelpActivity extends Activity { 19 | 20 | WebView mWebView; 21 | 22 | @Override 23 | protected void onCreate(Bundle savedInstanceState) { 24 | super.onCreate(savedInstanceState); 25 | 26 | final RelativeLayout progressLayout = new RelativeLayout(this); 27 | RelativeLayout.LayoutParams lParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); 28 | lParams.addRule(RelativeLayout.CENTER_IN_PARENT); 29 | ProgressBar progressBar = new ProgressBar(this); 30 | progressBar.setIndeterminate(true); 31 | progressBar.setLayoutParams(lParams); 32 | progressLayout.addView(progressBar); 33 | 34 | mWebView = new WebView(this); 35 | WebSettings settings = mWebView.getSettings(); 36 | settings.setCacheMode(WebSettings.LOAD_NO_CACHE); 37 | settings.setAppCacheEnabled(false); 38 | setContentView(progressLayout); 39 | mWebView.clearCache(true); 40 | 41 | mWebView.setWebViewClient(new WebViewClient() { 42 | @Override 43 | public boolean shouldOverrideUrlLoading(WebView view, String url) { 44 | if (url.equals(TermuxConstants.TERMUX_WIKI_URL) || url.startsWith(TermuxConstants.TERMUX_WIKI_URL + "/")) { 45 | // Inline help. 46 | setContentView(progressLayout); 47 | return false; 48 | } 49 | 50 | try { 51 | startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); 52 | } catch (ActivityNotFoundException e) { 53 | // Android TV does not have a system browser. 54 | setContentView(progressLayout); 55 | return false; 56 | } 57 | return true; 58 | } 59 | 60 | @Override 61 | public void onPageFinished(WebView view, String url) { 62 | setContentView(mWebView); 63 | } 64 | }); 65 | mWebView.loadUrl(TermuxConstants.TERMUX_WIKI_URL); 66 | } 67 | 68 | @Override 69 | public void onBackPressed() { 70 | if (mWebView.canGoBack()) { 71 | mWebView.goBack(); 72 | } else { 73 | super.onBackPressed(); 74 | } 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /termux-shared/src/main/java/com/termux/shared/terminal/io/BellHandler.java: -------------------------------------------------------------------------------- 1 | package com.termux.shared.terminal.io; 2 | 3 | import android.content.Context; 4 | import android.os.Build; 5 | import android.os.Handler; 6 | import android.os.Looper; 7 | import android.os.SystemClock; 8 | import android.os.VibrationEffect; 9 | import android.os.Vibrator; 10 | 11 | import com.termux.shared.logger.Logger; 12 | 13 | public class BellHandler { 14 | private static BellHandler instance = null; 15 | private static final Object lock = new Object(); 16 | 17 | private static final String LOG_TAG = "BellHandler"; 18 | 19 | public static BellHandler getInstance(Context context) { 20 | if (instance == null) { 21 | synchronized (lock) { 22 | if (instance == null) { 23 | instance = new BellHandler((Vibrator) context.getApplicationContext().getSystemService(Context.VIBRATOR_SERVICE)); 24 | } 25 | } 26 | } 27 | 28 | return instance; 29 | } 30 | 31 | private static final long DURATION = 50; 32 | private static final long MIN_PAUSE = 3 * DURATION; 33 | 34 | private final Handler handler = new Handler(Looper.getMainLooper()); 35 | private long lastBell = 0; 36 | private final Runnable bellRunnable; 37 | 38 | private BellHandler(final Vibrator vibrator) { 39 | bellRunnable = new Runnable() { 40 | @Override 41 | public void run() { 42 | if (vibrator != null) { 43 | try { 44 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 45 | vibrator.vibrate(VibrationEffect.createOneShot(DURATION, VibrationEffect.DEFAULT_AMPLITUDE)); 46 | } else { 47 | vibrator.vibrate(DURATION); 48 | } 49 | } catch (Exception e) { 50 | // Issue on samsung devices on android 8 51 | // java.lang.NullPointerException: Attempt to read from field 'android.os.VibrationEffect com.android.server.VibratorService$Vibration.mEffect' on a null object reference 52 | Logger.logStackTraceWithMessage(LOG_TAG, "Failed to run vibrator", e); 53 | } 54 | } 55 | } 56 | }; 57 | } 58 | 59 | public synchronized void doBell() { 60 | long now = now(); 61 | long timeSinceLastBell = now - lastBell; 62 | 63 | if (timeSinceLastBell < 0) { 64 | // there is a next bell pending; don't schedule another one 65 | } else if (timeSinceLastBell < MIN_PAUSE) { 66 | // there was a bell recently, schedule the next one 67 | handler.postDelayed(bellRunnable, MIN_PAUSE - timeSinceLastBell); 68 | lastBell = lastBell + MIN_PAUSE; 69 | } else { 70 | // the last bell was long ago, do it now 71 | bellRunnable.run(); 72 | lastBell = now; 73 | } 74 | } 75 | 76 | private long now() { 77 | return SystemClock.uptimeMillis(); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /terminal-emulator/src/test/java/com/termux/terminal/ScreenBufferTest.java: -------------------------------------------------------------------------------- 1 | package com.termux.terminal; 2 | 3 | public class ScreenBufferTest extends TerminalTestCase { 4 | 5 | public void testBasics() { 6 | TerminalBuffer screen = new TerminalBuffer(5, 3, 3); 7 | assertEquals("", screen.getTranscriptText()); 8 | screen.setChar(0, 0, 'a', 0); 9 | assertEquals("a", screen.getTranscriptText()); 10 | screen.setChar(0, 0, 'b', 0); 11 | assertEquals("b", screen.getTranscriptText()); 12 | screen.setChar(2, 0, 'c', 0); 13 | assertEquals("b c", screen.getTranscriptText()); 14 | screen.setChar(2, 2, 'f', 0); 15 | assertEquals("b c\n\n f", screen.getTranscriptText()); 16 | screen.blockSet(0, 0, 2, 2, 'X', 0); 17 | } 18 | 19 | public void testBlockSet() { 20 | TerminalBuffer screen = new TerminalBuffer(5, 3, 3); 21 | screen.blockSet(0, 0, 2, 2, 'X', 0); 22 | assertEquals("XX\nXX", screen.getTranscriptText()); 23 | screen.blockSet(1, 1, 2, 2, 'Y', 0); 24 | assertEquals("XX\nXYY\n YY", screen.getTranscriptText()); 25 | } 26 | 27 | public void testGetSelectedText() { 28 | withTerminalSized(5, 3).enterString("ABCDEFGHIJ").assertLinesAre("ABCDE", "FGHIJ", " "); 29 | assertEquals("AB", mTerminal.getSelectedText(0, 0, 1, 0)); 30 | assertEquals("BC", mTerminal.getSelectedText(1, 0, 2, 0)); 31 | assertEquals("CDE", mTerminal.getSelectedText(2, 0, 4, 0)); 32 | assertEquals("FG", mTerminal.getSelectedText(0, 1, 1, 1)); 33 | assertEquals("GH", mTerminal.getSelectedText(1, 1, 2, 1)); 34 | assertEquals("HIJ", mTerminal.getSelectedText(2, 1, 4, 1)); 35 | 36 | assertEquals("ABCDEFG", mTerminal.getSelectedText(0, 0, 1, 1)); 37 | withTerminalSized(5, 3).enterString("ABCDE\r\nFGHIJ").assertLinesAre("ABCDE", "FGHIJ", " "); 38 | assertEquals("ABCDE\nFG", mTerminal.getSelectedText(0, 0, 1, 1)); 39 | } 40 | 41 | public void testGetSelectedTextJoinFullLines() { 42 | withTerminalSized(5, 3).enterString("ABCDE\r\nFG"); 43 | assertEquals("ABCDEFG", mTerminal.getScreen().getSelectedText(0, 0, 1, 1, true, true)); 44 | 45 | withTerminalSized(5, 3).enterString("ABC\r\nFG"); 46 | assertEquals("ABC\nFG", mTerminal.getScreen().getSelectedText(0, 0, 1, 1, true, true)); 47 | } 48 | 49 | public void testGetWordAtLocation() { 50 | withTerminalSized(5, 3).enterString("ABCDEFGHIJ\r\nKLMNO"); 51 | assertEquals("ABCDEFGHIJKLMNO", mTerminal.getScreen().getWordAtLocation(0, 0)); 52 | assertEquals("ABCDEFGHIJKLMNO", mTerminal.getScreen().getWordAtLocation(4, 1)); 53 | assertEquals("ABCDEFGHIJKLMNO", mTerminal.getScreen().getWordAtLocation(4, 2)); 54 | 55 | withTerminalSized(5, 3).enterString("ABC DEF GHI "); 56 | assertEquals("ABC", mTerminal.getScreen().getWordAtLocation(0, 0)); 57 | assertEquals("", mTerminal.getScreen().getWordAtLocation(3, 0)); 58 | assertEquals("DEF", mTerminal.getScreen().getWordAtLocation(4, 0)); 59 | assertEquals("DEF", mTerminal.getScreen().getWordAtLocation(0, 1)); 60 | assertEquals("DEF", mTerminal.getScreen().getWordAtLocation(1, 1)); 61 | assertEquals("GHI", mTerminal.getScreen().getWordAtLocation(0, 2)); 62 | assertEquals("", mTerminal.getScreen().getWordAtLocation(1, 2)); 63 | assertEquals("", mTerminal.getScreen().getWordAtLocation(2, 2)); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /termux-shared/src/main/java/com/termux/shared/terminal/TermuxTerminalViewClientBase.java: -------------------------------------------------------------------------------- 1 | package com.termux.shared.terminal; 2 | 3 | import android.view.KeyEvent; 4 | import android.view.MotionEvent; 5 | 6 | import com.termux.shared.logger.Logger; 7 | import com.termux.terminal.TerminalSession; 8 | import com.termux.view.TerminalViewClient; 9 | 10 | public class TermuxTerminalViewClientBase implements TerminalViewClient { 11 | 12 | public TermuxTerminalViewClientBase() { 13 | } 14 | 15 | @Override 16 | public float onScale(float scale) { 17 | return 1.0f; 18 | } 19 | 20 | @Override 21 | public void onSingleTapUp(MotionEvent e) { 22 | } 23 | 24 | public boolean shouldBackButtonBeMappedToEscape() { 25 | return false; 26 | } 27 | 28 | public boolean shouldEnforceCharBasedInput() { 29 | return false; 30 | } 31 | 32 | public boolean shouldUseCtrlSpaceWorkaround() { 33 | return false; 34 | } 35 | 36 | @Override 37 | public boolean isTerminalViewSelected() { 38 | return true; 39 | } 40 | 41 | @Override 42 | public void copyModeChanged(boolean copyMode) { 43 | } 44 | 45 | @Override 46 | public boolean onKeyDown(int keyCode, KeyEvent e, TerminalSession session) { 47 | return false; 48 | } 49 | 50 | @Override 51 | public boolean onKeyUp(int keyCode, KeyEvent e) { 52 | return false; 53 | } 54 | 55 | @Override 56 | public boolean onLongPress(MotionEvent event) { 57 | return false; 58 | } 59 | 60 | @Override 61 | public boolean readControlKey() { 62 | return false; 63 | } 64 | 65 | @Override 66 | public boolean readAltKey() { 67 | return false; 68 | } 69 | 70 | @Override 71 | public boolean readShiftKey() { 72 | return false; 73 | } 74 | 75 | @Override 76 | public boolean readFnKey() { 77 | return false; 78 | } 79 | 80 | 81 | 82 | @Override 83 | public boolean onCodePoint(int codePoint, boolean ctrlDown, TerminalSession session) { 84 | return false; 85 | } 86 | 87 | @Override 88 | public void onEmulatorSet() { 89 | 90 | } 91 | 92 | @Override 93 | public void logError(String tag, String message) { 94 | Logger.logError(tag, message); 95 | } 96 | 97 | @Override 98 | public void logWarn(String tag, String message) { 99 | Logger.logWarn(tag, message); 100 | } 101 | 102 | @Override 103 | public void logInfo(String tag, String message) { 104 | Logger.logInfo(tag, message); 105 | } 106 | 107 | @Override 108 | public void logDebug(String tag, String message) { 109 | Logger.logDebug(tag, message); 110 | } 111 | 112 | @Override 113 | public void logVerbose(String tag, String message) { 114 | Logger.logVerbose(tag, message); 115 | } 116 | 117 | @Override 118 | public void logStackTraceWithMessage(String tag, String message, Exception e) { 119 | Logger.logStackTraceWithMessage(tag, message, e); 120 | } 121 | 122 | @Override 123 | public void logStackTrace(String tag, Exception e) { 124 | Logger.logStackTrace(tag, e); 125 | } 126 | 127 | } 128 | -------------------------------------------------------------------------------- /terminal-emulator/src/test/java/com/termux/terminal/DecSetTest.java: -------------------------------------------------------------------------------- 1 | package com.termux.terminal; 2 | 3 | /** 4 | *
5 | * "CSI ? Pm h", DEC Private Mode Set (DECSET) 6 | *
11 | * "CSI ? Pm l", DEC Private Mode Reset (DECRST) 12 | *
5 | * Encodes effects, foreground and background colors into a 64 bit long, which are stored for each cell in a terminal 6 | * row in {@link TerminalRow#mStyle}. 7 | *
9 | * The bit layout is: 10 | *
27 | * This bit is set if DECSCA (Select Character Protection Attribute) has been used to define the characters that 28 | * come after it as erasable from the screen. 29 | *