├── .envrc ├── termux-shared ├── .gitignore ├── src │ ├── main │ │ ├── cpp │ │ │ ├── Application.mk │ │ │ └── Android.mk │ │ ├── res │ │ │ ├── raw │ │ │ │ ├── bell.ogg │ │ │ │ ├── keep.xml │ │ │ │ └── apt_info_script.sh │ │ │ ├── values │ │ │ │ ├── attrs.xml │ │ │ │ ├── dimens.xml │ │ │ │ ├── colors.xml │ │ │ │ └── styles.xml │ │ │ ├── drawable │ │ │ │ ├── ic_info.xml │ │ │ │ ├── ic_copy.xml │ │ │ │ ├── ic_share.xml │ │ │ │ ├── ic_settings.xml │ │ │ │ └── ic_error_notification.xml │ │ │ ├── menu │ │ │ │ ├── menu_text_io.xml │ │ │ │ └── menu_report.xml │ │ │ ├── layout │ │ │ │ ├── markdown_adapter_node_default.xml │ │ │ │ ├── activity_report.xml │ │ │ │ ├── partial_primary_toolbar.xml │ │ │ │ ├── markdown_adapter_node_code_block.xml │ │ │ │ └── dialog_show_message.xml │ │ │ └── values-night │ │ │ │ └── themes.xml │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── com │ │ │ └── termux │ │ │ └── shared │ │ │ ├── termux │ │ │ ├── models │ │ │ │ └── UserAction.java │ │ │ ├── theme │ │ │ │ └── TermuxThemeUtils.java │ │ │ ├── settings │ │ │ │ └── properties │ │ │ │ │ └── TermuxAppSharedProperties.java │ │ │ ├── extrakeys │ │ │ │ ├── SpecialButton.java │ │ │ │ └── SpecialButtonState.java │ │ │ ├── shell │ │ │ │ └── command │ │ │ │ │ └── environment │ │ │ │ │ ├── TermuxAPIShellEnvironment.java │ │ │ │ │ └── TermuxShellCommandShellEnvironment.java │ │ │ ├── terminal │ │ │ │ ├── TermuxTerminalSessionClientBase.java │ │ │ │ ├── io │ │ │ │ │ └── BellHandler.java │ │ │ │ └── TermuxTerminalViewClientBase.java │ │ │ └── interact │ │ │ │ └── TextInputDialogUtils.java │ │ │ ├── shell │ │ │ ├── am │ │ │ │ └── AmSocketServerErrno.java │ │ │ ├── command │ │ │ │ ├── environment │ │ │ │ │ ├── ShellEnvironmentVariable.java │ │ │ │ │ └── IShellEnvironment.java │ │ │ │ └── result │ │ │ │ │ └── ResultSenderErrno.java │ │ │ └── ShellUtils.java │ │ │ ├── activity │ │ │ └── ActivityErrno.java │ │ │ ├── file │ │ │ └── filesystem │ │ │ │ ├── FileType.java │ │ │ │ ├── NativeDispatcher.java │ │ │ │ ├── FileKey.java │ │ │ │ └── FilePermission.java │ │ │ ├── net │ │ │ ├── uri │ │ │ │ └── UriScheme.java │ │ │ └── socket │ │ │ │ └── local │ │ │ │ └── LocalSocketManagerClientBase.java │ │ │ ├── errors │ │ │ └── FunctionErrno.java │ │ │ ├── settings │ │ │ ├── properties │ │ │ │ └── SharedPropertiesParser.java │ │ │ └── preferences │ │ │ │ └── AppSharedPreferences.java │ │ │ ├── android │ │ │ └── ProcessUtils.java │ │ │ └── theme │ │ │ └── NightMode.java │ └── androidTest │ │ └── java │ │ └── com │ │ └── termux │ │ └── shared │ │ └── ExampleInstrumentedTest.java ├── proguard-rules.pro ├── LICENSE.md └── build.gradle ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── 02-feature-request.yml │ └── 01-bug-report.yml ├── dependabot.yml └── workflows │ ├── run_tests.yml │ ├── gradle-wrapper-validation.yml │ └── trigger_library_builds_on_jitpack.yml ├── jitpack.yml ├── 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 │ │ └── support │ │ └── PopupWindowCompatGingerbread.java ├── proguard-rules.pro └── build.gradle ├── 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 ├── terminal-emulator ├── src │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── jni │ │ │ └── Android.mk │ │ └── java │ │ │ └── com │ │ │ └── termux │ │ │ └── terminal │ │ │ ├── TerminalOutput.java │ │ │ ├── TerminalSessionClient.java │ │ │ ├── JNI.java │ │ │ └── Logger.java │ └── test │ │ └── java │ │ └── com │ │ └── termux │ │ └── terminal │ │ ├── ApcTest.java │ │ ├── HistoryTest.java │ │ ├── ByteQueueTest.java │ │ ├── DeviceControlStringTest.java │ │ ├── WcWidthTest.java │ │ ├── TextStyleTest.java │ │ ├── ScreenBufferTest.java │ │ └── DecSetTest.java ├── proguard-rules.pro └── build.gradle ├── app ├── src │ ├── main │ │ ├── res │ │ │ ├── values │ │ │ │ ├── colors.xml │ │ │ │ ├── attrs.xml │ │ │ │ ├── styles.xml │ │ │ │ └── themes.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 │ │ │ ├── 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 │ │ │ ├── layout │ │ │ │ ├── view_terminal_toolbar_extra_keys.xml │ │ │ │ ├── item_terminal_sessions_list.xml │ │ │ │ ├── activity_settings.xml │ │ │ │ ├── view_terminal_toolbar_text_input.xml │ │ │ │ └── preference_markdown_text.xml │ │ │ └── values-night │ │ │ │ └── themes.xml │ │ ├── cpp │ │ │ ├── Android.mk │ │ │ ├── termux-bootstrap.c │ │ │ └── termux-bootstrap-zip.S │ │ └── java │ │ │ └── com │ │ │ └── termux │ │ │ └── app │ │ │ ├── terminal │ │ │ ├── io │ │ │ │ ├── KeyboardShortcut.java │ │ │ │ └── FullScreenWorkAround.java │ │ │ └── TermuxTerminalSessionServiceClient.java │ │ │ ├── models │ │ │ └── UserAction.java │ │ │ ├── fragments │ │ │ └── settings │ │ │ │ ├── TermuxPreferencesFragment.java │ │ │ │ ├── TermuxAPIPreferencesFragment.java │ │ │ │ ├── TermuxFloatPreferencesFragment.java │ │ │ │ ├── TermuxTaskerPreferencesFragment.java │ │ │ │ ├── TermuxWidgetPreferencesFragment.java │ │ │ │ └── termux │ │ │ │ ├── TerminalViewPreferencesFragment.java │ │ │ │ └── TerminalIOPreferencesFragment.java │ │ │ └── activities │ │ │ └── HelpActivity.java │ └── test │ │ └── java │ │ └── com │ │ └── termux │ │ └── app │ │ ├── TermuxActivityTest.java │ │ └── api │ │ └── file │ │ └── FileReceiverActivityTest.java ├── testkey_untrusted.jks └── proguard-rules.pro ├── 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 ├── docs └── en │ └── index.md ├── nix ├── init.gradle ├── url2json-fix-printing.patch ├── url2json.nix └── go-maven-resolver.nix ├── .editorconfig ├── LICENSE.md ├── .gitignore ├── gradle.properties ├── flake.lock └── gradlew.bat /.envrc: -------------------------------------------------------------------------------- 1 | use flake 2 | -------------------------------------------------------------------------------- /termux-shared/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | custom: https://termux.dev/donate 2 | -------------------------------------------------------------------------------- /termux-shared/src/main/cpp/Application.mk: -------------------------------------------------------------------------------- 1 | APP_STL := c++_static 2 | -------------------------------------------------------------------------------- /jitpack.yml: -------------------------------------------------------------------------------- 1 | jdk: 2 | - openjdk11 3 | env: 4 | JITPACK_NDK_VERSION: "21.1.6352462" 5 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':termux-shared', ':terminal-emulator', ':terminal-view' 2 | -------------------------------------------------------------------------------- /terminal-view/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /art/ic_launcher2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nix-community/nix-on-droid-app/HEAD/art/ic_launcher2.png -------------------------------------------------------------------------------- /terminal-emulator/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/testkey_untrusted.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nix-community/nix-on-droid-app/HEAD/app/testkey_untrusted.jks -------------------------------------------------------------------------------- /art/ic_launcher2_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nix-community/nix-on-droid-app/HEAD/art/ic_launcher2_round.png -------------------------------------------------------------------------------- /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/nix-community/nix-on-droid-app/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/drawable/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nix-community/nix-on-droid-app/HEAD/app/src/main/res/drawable/banner.png -------------------------------------------------------------------------------- /termux-shared/src/main/res/raw/bell.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nix-community/nix-on-droid-app/HEAD/termux-shared/src/main/res/raw/bell.ogg -------------------------------------------------------------------------------- /.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-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nix-community/nix-on-droid-app/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nix-community/nix-on-droid-app/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nix-community/nix-on-droid-app/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nix-community/nix-on-droid-app/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nix-community/nix-on-droid-app/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nix-community/nix-on-droid-app/HEAD/fastlane/metadata/android/en-US/images/icon.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nix-community/nix-on-droid-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/nix-community/nix-on-droid-app/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nix-community/nix-on-droid-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/nix-community/nix-on-droid-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/nix-community/nix-on-droid-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/nix-community/nix-on-droid-app/HEAD/fastlane/metadata/android/en-US/images/phoneScreenshots/1.jpg -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nix-community/nix-on-droid-app/HEAD/fastlane/metadata/android/en-US/images/phoneScreenshots/2.jpg -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nix-community/nix-on-droid-app/HEAD/fastlane/metadata/android/en-US/images/phoneScreenshots/3.jpg -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nix-community/nix-on-droid-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 | -------------------------------------------------------------------------------- /termux-shared/src/main/res/raw/keep.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /termux-shared/src/main/cpp/Android.mk: -------------------------------------------------------------------------------- 1 | LOCAL_PATH:= $(call my-dir) 2 | include $(CLEAR_VARS) 3 | LOCAL_LDLIBS := -llog 4 | LOCAL_MODULE := local-socket 5 | LOCAL_SRC_FILES := local-socket.cpp 6 | include $(BUILD_SHARED_LIBRARY) 7 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /app/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: / 5 | schedule: 6 | interval: daily 7 | commit-message: 8 | # Prefix all commit messages with "Changed: " 9 | prefix: "Changed" 10 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /termux-shared/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /docs/en/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | page_ref: /docs/apps/termux/index.html 3 | --- 4 | 5 | # Termux App Docs 6 | 7 | 8 | 9 | Welcome to documentation for the [Termux App]. 10 | 11 | ## 12 | 13 | [Termux App]: https://github.com/termux/termux-app 14 | -------------------------------------------------------------------------------- /nix/init.gradle: -------------------------------------------------------------------------------- 1 | initscript { 2 | repositories { 3 | gradlePluginPortal() 4 | } 5 | dependencies { 6 | classpath "org.gradle:github-dependency-graph-gradle-plugin:+" 7 | } 8 | } 9 | apply plugin: org.gradle.dependencygraph.simple.SimpleDependencyGraphPlugin 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/session_ripple.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /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/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/xml/termux_api_preferences.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /termux-shared/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | REPORT_ISSUE_FROM_TRANSCRIPT("report issue from transcript"); 7 | 8 | private final String name; 9 | 10 | UserAction(final String name) { 11 | this.name = name; 12 | } 13 | 14 | public String getName() { 15 | return name; 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /termux-shared/src/main/res/drawable/ic_info.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.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@v4 19 | - name: Execute tests 20 | run: | 21 | ./gradlew test 22 | -------------------------------------------------------------------------------- /nix/url2json-fix-printing.patch: -------------------------------------------------------------------------------- 1 | --- a/url2json.sh 2 | +++ b/url2json.sh 3 | @@ -115,7 +115,8 @@ PKG_URL_NO_EXT="${POM_URL%.pom}" 4 | # Name of package without extension. 5 | PKG_NAME="$(basename "${PKG_URL_NO_EXT}")" 6 | 7 | -echo -en "${CLR} - Nix entry for: ${1##*/}\r" >&2 8 | +printf -v spaces '%*s' 60 '' 9 | +echo -en "${CLR} - Nix entry for: ${1##*/}$spaces\r" >&2 10 | 11 | REPO_URL=$(match_repo_url "${PKG_URL_NO_EXT}") 12 | 13 | -------------------------------------------------------------------------------- /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@v4 19 | - uses: gradle/wrapper-validation-action@v3 20 | -------------------------------------------------------------------------------- /termux-shared/src/main/java/com/termux/shared/termux/models/UserAction.java: -------------------------------------------------------------------------------- 1 | package com.termux.shared.termux.models; 2 | 3 | public enum UserAction { 4 | 5 | CRASH_REPORT("crash report"), 6 | PLUGIN_EXECUTION_COMMAND("plugin execution command"); 7 | 8 | private final String name; 9 | 10 | UserAction(final String name) { 11 | this.name = name; 12 | } 13 | 14 | public String getName() { 15 | return name; 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/res/layout/view_terminal_toolbar_extra_keys.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | -------------------------------------------------------------------------------- /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/layout/item_terminal_sessions_list.xml: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /termux-shared/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16dp 5 | 16dp 6 | 7 | 4dip 8 | 8dip 9 | 16dip 10 | 32dip 11 | 12 | -------------------------------------------------------------------------------- /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 2.0](https://www.apache.org/licenses/LICENSE-2.0) license. 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/layout/activity_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/src/main/res/xml/termux_terminal_view_preferences.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /.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 | # Local configuration file (sdk path, etc) 15 | local.properties 16 | 17 | # Gradle generated files 18 | .gradle/ 19 | 20 | # Signing files 21 | .signing/ 22 | 23 | # Intellij 24 | .idea/ 25 | *.iml 26 | 27 | # Vim 28 | *.swo 29 | *.swp 30 | 31 | # OS-specific files 32 | .DS_Store 33 | .DS_Store? 34 | ._* 35 | .Spotlight-V100 36 | .Trashes 37 | .swp 38 | ehthumbs.db 39 | Thumbs.db 40 | 41 | # nix 42 | result 43 | result-* 44 | .direnv 45 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_new_session.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 13 | 14 | 17 | 18 | -------------------------------------------------------------------------------- /nix/url2json.nix: -------------------------------------------------------------------------------- 1 | # This script url2json.sh is the heart behind gradle dependency lock generation 2 | { stdenv, fetchurl }: 3 | stdenv.mkDerivation { 4 | name = "url2json"; 5 | phases = [ "buildPhase" ]; 6 | src = fetchurl { 7 | url = "https://github.com/status-im/status-mobile/raw/2df7a7cf6d46c8d1add73b8965ce8b04e6f7d014/nix/deps/gradle/url2json.sh"; 8 | hash = "sha256-McEyQPvofpMYv7mvX/7m/eRNYxJOUkm98foSYmYOyE4="; 9 | executable = true; 10 | }; 11 | buildPhase = '' 12 | mkdir -p $out/bin; cd $out/bin 13 | cp $src url2json.sh 14 | chmod +w url2json.sh; patch -p1 < ${./url2json-fix-printing.patch}; chmod -w url2json.sh 15 | mv url2json.sh url2json 16 | ''; 17 | meta.mainProgram = "url2json"; 18 | } 19 | -------------------------------------------------------------------------------- /nix/go-maven-resolver.nix: -------------------------------------------------------------------------------- 1 | { 2 | lib, 3 | buildGoModule, 4 | fetchFromGitHub, 5 | }: 6 | 7 | buildGoModule (finalAttrs: { 8 | pname = "go-maven-resolver"; 9 | version = "1.1.2-unstable-2025-06-16"; 10 | 11 | vendorHash = "sha256-dlqI+onfeo4tTwmHeq8heVKRzLU1gFEQ+4iv+8egN90="; 12 | 13 | src = fetchFromGitHub { 14 | owner = "status-im"; 15 | repo = "go-maven-resolver"; 16 | rev = "473b36df1d12996fc5fbcb8b7cc4f60c9aa4f8e0"; 17 | hash = "sha256-wYGjOcNnhMU2hwKGNLEAT4CcienKw5CvWieH1wV7bA8="; 18 | }; 19 | 20 | meta = { 21 | description = "go maven resolver"; 22 | homepage = "https://github.com/status-im/go-maven-resolver"; 23 | license = lib.licenses.mit; 24 | mainProgram = "go-maven-resolver"; 25 | }; 26 | }) 27 | -------------------------------------------------------------------------------- /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/layout/markdown_adapter_node_default.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /termux-shared/src/main/java/com/termux/shared/shell/am/AmSocketServerErrno.java: -------------------------------------------------------------------------------- 1 | package com.termux.shared.shell.am; 2 | 3 | import com.termux.shared.errors.Errno; 4 | 5 | public class AmSocketServerErrno extends Errno { 6 | 7 | public static final String TYPE = "AmSocketServer Error"; 8 | 9 | 10 | /** Errors for {@link AmSocketServer} (100-150) */ 11 | public static final Errno ERRNO_PARSE_AM_COMMAND_FAILED_WITH_EXCEPTION = new Errno(TYPE, 100, "Parse am command `%1$s` failed.\nException: %2$s"); 12 | public static final Errno ERRNO_RUN_AM_COMMAND_FAILED_WITH_EXCEPTION = new Errno(TYPE, 101, "Run am command `%1$s` failed.\nException: %2$s"); 13 | 14 | AmSocketServerErrno(final String type, final int code, final String message) { 15 | super(type, code, message); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 12 | 13 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | #BDBDBD 14 | #9E9E9E 15 | #424242 16 | #212121 17 | 18 | #DC143C 19 | #FC143C 20 | 21 | #0969DA 22 | #58A6FF 23 | 24 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/src/main/res/layout/preference_markdown_text.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 10 | 11 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /termux-shared/src/main/java/com/termux/shared/activity/ActivityErrno.java: -------------------------------------------------------------------------------- 1 | package com.termux.shared.activity; 2 | 3 | import com.termux.shared.errors.Errno; 4 | 5 | public class ActivityErrno extends Errno { 6 | 7 | public static final String TYPE = "Activity Error"; 8 | 9 | 10 | /* Errors for starting activities (100-150) */ 11 | public static final Errno ERRNO_START_ACTIVITY_FAILED_WITH_EXCEPTION = new Errno(TYPE, 100, "Failed to start \"%1$s\" activity.\nException: %2$s"); 12 | public static final Errno ERRNO_START_ACTIVITY_FOR_RESULT_FAILED_WITH_EXCEPTION = new Errno(TYPE, 101, "Failed to start \"%1$s\" activity for result.\nException: %2$s"); 13 | public static final Errno ERRNO_STARTING_ACTIVITY_WITH_NULL_CONTEXT = new Errno(TYPE, 102, "Cannot start \"%1$s\" activity with null Context"); 14 | 15 | 16 | ActivityErrno(final String type, final int code, final String message) { 17 | super(type, code, message); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /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/java/com/termux/shared/shell/command/environment/ShellEnvironmentVariable.java: -------------------------------------------------------------------------------- 1 | package com.termux.shared.shell.command.environment; 2 | 3 | public class ShellEnvironmentVariable implements Comparable { 4 | 5 | /** The name for environment variable */ 6 | public String name; 7 | 8 | /** The value for environment variable */ 9 | public String value; 10 | 11 | /** If environment variable {@link #value} is already escaped. */ 12 | public boolean escaped; 13 | 14 | public ShellEnvironmentVariable(String name, String value) { 15 | this(name, value, false); 16 | } 17 | 18 | public ShellEnvironmentVariable(String name, String value, boolean escaped) { 19 | this.name = name; 20 | this.value = value; 21 | this.escaped = escaped; 22 | } 23 | 24 | @Override 25 | public int compareTo(ShellEnvironmentVariable other) { 26 | return this.name.compareTo(other.name); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /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), // 00000000 7 | REGULAR("regular", 1), // 00000001 8 | DIRECTORY("directory", 2), // 00000010 9 | SYMLINK("symlink", 4), // 00000100 10 | SOCKET("socket", 8), // 00001000 11 | CHARACTER("character", 16), // 00010000 12 | FIFO("fifo", 32), // 00100000 13 | BLOCK("block", 64), // 01000000 14 | UNKNOWN("unknown", 128); // 10000000 15 | 16 | private final String name; 17 | private final int value; 18 | 19 | FileType(final String name, final int value) { 20 | this.name = name; 21 | this.value = value; 22 | } 23 | 24 | public String getName() { 25 | return name; 26 | } 27 | 28 | public int getValue() { 29 | return value; 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /termux-shared/src/main/java/com/termux/shared/termux/theme/TermuxThemeUtils.java: -------------------------------------------------------------------------------- 1 | package com.termux.shared.termux.theme; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.annotation.NonNull; 6 | import androidx.annotation.Nullable; 7 | 8 | import com.termux.shared.termux.settings.properties.TermuxPropertyConstants; 9 | import com.termux.shared.termux.settings.properties.TermuxSharedProperties; 10 | import com.termux.shared.theme.NightMode; 11 | 12 | public class TermuxThemeUtils { 13 | 14 | /** Get the {@link TermuxPropertyConstants#KEY_NIGHT_MODE} value from the properties file on disk 15 | * and set it to app wide night mode value. */ 16 | public static void setAppNightMode(@NonNull Context context) { 17 | NightMode.setAppNightMode(TermuxSharedProperties.getNightMode(context)); 18 | } 19 | 20 | /** Set name as app wide night mode value. */ 21 | public static void setAppNightMode(@Nullable String name) { 22 | NightMode.setAppNightMode(name); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /termux-shared/src/main/java/com/termux/shared/net/uri/UriScheme.java: -------------------------------------------------------------------------------- 1 | package com.termux.shared.net.uri; 2 | 3 | import android.net.Uri; 4 | 5 | /** 6 | * The {@link Uri} schemes. 7 | * 8 | * https://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml 9 | * https://en.wikipedia.org/wiki/List_of_URI_schemes 10 | */ 11 | public class UriScheme { 12 | 13 | /** Android app resource. */ 14 | public static final String SCHEME_ANDROID_RESOURCE = "android.resource"; 15 | 16 | /** Android content provider. https://www.iana.org/assignments/uri-schemes/prov/content. */ 17 | public static final String SCHEME_CONTENT = "content"; 18 | 19 | /** Filesystem or android app asset. https://www.rfc-editor.org/rfc/rfc8089.html. */ 20 | public static final String SCHEME_FILE = "file"; 21 | 22 | /* Hypertext Transfer Protocol. */ 23 | public static final String SCHEME_HTTP = "http"; 24 | 25 | /* Hypertext Transfer Protocol Secure. */ 26 | public static final String SCHEME_HTTPS = "https"; 27 | 28 | } 29 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /art/feature-graphic.svg: -------------------------------------------------------------------------------- 1 | 2 | 12 | 15 | 16 | 17 | 18 | 22 | 27 | $ Termux █ 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /termux-shared/src/main/res/layout/partial_primary_toolbar.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /terminal-emulator/src/test/java/com/termux/terminal/ApcTest.java: -------------------------------------------------------------------------------- 1 | package com.termux.terminal; 2 | 3 | public class ApcTest extends TerminalTestCase { 4 | 5 | public void testApcConsumed() { 6 | // At time of writing this is part of what yazi sends for probing for kitty graphics protocol support: 7 | // https://github.com/sxyazi/yazi/blob/0cdaff98d0b3723caff63eebf1974e7907a43a2c/yazi-adapter/src/emulator.rs#L129 8 | // This should not result in anything being written to the screen: If kitty graphics protocol support 9 | // is implemented it should instead result in an error code on stdin, and if not it should be consumed 10 | // silently just as xterm does. See https://sw.kovidgoyal.net/kitty/graphics-protocol/. 11 | withTerminalSized(2, 2) 12 | .enterString("\033_Gi=31,s=1,v=1,a=q,t=d,f=24;AAAA\033\\") 13 | .assertLinesAre(" ", " "); 14 | 15 | // It is ok for the APC content to be non printable characters: 16 | withTerminalSized(12, 2) 17 | .enterString("hello \033_some\023\033_\\apc#end\033\\ world") 18 | .assertLinesAre("hello world", " "); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_settings.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /termux-shared/src/main/res/drawable/ic_settings.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | --add-exports=java.base/sun.nio.ch=ALL-UNNAMED \ 17 | --add-opens=java.base/java.lang=ALL-UNNAMED \ 18 | --add-opens=java.base/java.lang.reflect=ALL-UNNAMED \ 19 | --add-opens=java.base/java.io=ALL-UNNAMED \ 20 | --add-exports=jdk.unsupported/sun.misc=ALL-UNNAMED 21 | android.useAndroidX=true 22 | 23 | minSdkVersion=21 24 | targetSdkVersion=28 25 | ndkVersion=22.1.7171670 26 | compileSdkVersion=30 27 | 28 | markwonVersion=4.6.2 29 | 30 | -------------------------------------------------------------------------------- /termux-shared/src/main/res/layout/markdown_adapter_node_code_block.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/test/java/com/termux/app/TermuxActivityTest.java: -------------------------------------------------------------------------------- 1 | package com.termux.app; 2 | 3 | import com.termux.shared.termux.data.TermuxUrlUtils; 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, TermuxUrlUtils.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 | -------------------------------------------------------------------------------- /termux-shared/src/main/java/com/termux/shared/shell/command/result/ResultSenderErrno.java: -------------------------------------------------------------------------------- 1 | package com.termux.shared.shell.command.result; 2 | 3 | import com.termux.shared.errors.Errno; 4 | 5 | /** The {@link Class} that defines ResultSender error messages and codes. */ 6 | public class ResultSenderErrno extends Errno { 7 | 8 | public static final String TYPE = "ResultSender Error"; 9 | 10 | 11 | /* Errors for null or empty parameters (100-150) */ 12 | 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 \"/\"."); 13 | public static final Errno ERROR_RESULT_FILES_SUFFIX_INVALID = new Errno(TYPE, 101, "The result files suffix \"%1$s\" contains forward slashes \"/\"."); 14 | public static final Errno ERROR_FORMAT_RESULT_ERROR_FAILED_WITH_EXCEPTION = new Errno(TYPE, 102, "Formatting result error failed.\nException: %1$s"); 15 | public static final Errno ERROR_FORMAT_RESULT_OUTPUT_FAILED_WITH_EXCEPTION = new Errno(TYPE, 103, "Formatting result output failed.\nException: %1$s"); 16 | 17 | 18 | ResultSenderErrno(final String type, final int code, final String message) { 19 | super(type, code, message); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /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 | resize(cols, 2); 15 | assertHistoryStartsWith("777", "666", "555"); 16 | 17 | 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/main/java/com/termux/app/terminal/TermuxTerminalSessionServiceClient.java: -------------------------------------------------------------------------------- 1 | package com.termux.app.terminal; 2 | 3 | import android.app.Service; 4 | 5 | import androidx.annotation.NonNull; 6 | 7 | import com.termux.app.TermuxService; 8 | import com.termux.shared.termux.shell.command.runner.terminal.TermuxSession; 9 | import com.termux.shared.termux.terminal.TermuxTerminalSessionClientBase; 10 | import com.termux.terminal.TerminalSession; 11 | import com.termux.terminal.TerminalSessionClient; 12 | 13 | /** The {@link TerminalSessionClient} implementation that may require a {@link Service} for its interface methods. */ 14 | public class TermuxTerminalSessionServiceClient extends TermuxTerminalSessionClientBase { 15 | 16 | private static final String LOG_TAG = "TermuxTerminalSessionServiceClient"; 17 | 18 | private final TermuxService mService; 19 | 20 | public TermuxTerminalSessionServiceClient(TermuxService service) { 21 | this.mService = service; 22 | } 23 | 24 | @Override 25 | public void setTerminalShellPid(@NonNull TerminalSession terminalSession, int pid) { 26 | TermuxSession termuxSession = mService.getTermuxSessionForTerminalSession(terminalSession); 27 | if (termuxSession != null) 28 | termuxSession.getExecutionCommand().mPid = pid; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /termux-shared/src/main/java/com/termux/shared/errors/FunctionErrno.java: -------------------------------------------------------------------------------- 1 | package com.termux.shared.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 | public static final Errno ERRNO_PARAMETER_NOT_INSTANCE_OF = new Errno(TYPE, 104, "The %1$s parameter passed to \"%2$s\" is not an instance of %3$s."); 16 | 17 | 18 | FunctionErrno(final String type, final int code, final String message) { 19 | super(type, code, message); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /termux-shared/LICENSE.md: -------------------------------------------------------------------------------- 1 | The `termux-shared` library is released under [MIT](https://opensource.org/licenses/MIT) license. 2 | 3 | ### Exceptions 4 | 5 | #### [GPLv3 only](https://www.gnu.org/licenses/gpl-3.0.html) 6 | 7 | - [`src/main/java/com/termux/shared/termux/*`](src/main/java/com/termux/shared/termux). 8 | 9 | The `GPLv3 only` license applies to all files unless specifically specified by a file/directory, like the [`src/main/java/com/termux/shared/termux/TermuxConstants.java`](src/main/java/com/termux/shared/termux/TermuxConstants.java) and [`src/main/java/com/termux/shared/termux/settings/properties/TermuxPropertyConstants.java`](src/main/java/com/termux/shared/termux/settings/properties/TermuxPropertyConstants.java) files are released under the `MIT` license. 10 | ## 11 | 12 | 13 | #### [GPLv2 only with "Classpath" exception](https://openjdk.java.net/legal/gplv2+ce.html) 14 | 15 | - [`src/main/java/com/termux/shared/file/filesystem/*`](src/main/java/com/termux/shared/file/filesystem) files that use code from [libcore/ojluni](https://cs.android.com/android/platform/superproject/+/android-11.0.0_r3:libcore/ojluni/). 16 | ## 17 | 18 | 19 | #### [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0) 20 | 21 | - [`src/main/java/com/termux/shared/shell/StreamGobbler.java`](src/main/java/com/termux/shared/shell/StreamGobbler.java) uses code from [libsuperuser ](https://github.com/Chainfire/libsuperuser). 22 | ## 23 | -------------------------------------------------------------------------------- /app/src/test/java/com/termux/app/api/file/FileReceiverActivityTest.java: -------------------------------------------------------------------------------- 1 | package com.termux.app.api.file; 2 | 3 | import com.termux.app.api.file.FileReceiverActivity; 4 | 5 | import org.junit.Assert; 6 | import org.junit.Test; 7 | import org.junit.runner.RunWith; 8 | import org.robolectric.RobolectricTestRunner; 9 | 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | @RunWith(RobolectricTestRunner.class) 14 | public class FileReceiverActivityTest { 15 | 16 | @Test 17 | public void testIsSharedTextAnUrl() { 18 | List validUrls = new ArrayList<>(); 19 | validUrls.add("http://example.com"); 20 | validUrls.add("https://example.com"); 21 | validUrls.add("https://example.com/path/parameter=foo"); 22 | 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"); 23 | for (String url : validUrls) { 24 | Assert.assertTrue(FileReceiverActivity.isSharedTextAnUrl(url)); 25 | } 26 | 27 | List invalidUrls = new ArrayList<>(); 28 | invalidUrls.add("a test with example.com"); 29 | invalidUrls.add(""); 30 | invalidUrls.add(null); 31 | for (String url : invalidUrls) { 32 | Assert.assertFalse(FileReceiverActivity.isSharedTextAnUrl(url)); 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /termux-shared/src/main/java/com/termux/shared/termux/settings/properties/TermuxAppSharedProperties.java: -------------------------------------------------------------------------------- 1 | package com.termux.shared.termux.settings.properties; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.annotation.NonNull; 6 | 7 | import com.termux.shared.termux.TermuxConstants; 8 | 9 | public class TermuxAppSharedProperties extends TermuxSharedProperties { 10 | 11 | private static TermuxAppSharedProperties properties; 12 | 13 | 14 | private TermuxAppSharedProperties(@NonNull Context context) { 15 | super(context, TermuxConstants.TERMUX_APP_NAME, 16 | TermuxConstants.TERMUX_PROPERTIES_FILE_PATHS_LIST, TermuxPropertyConstants.TERMUX_APP_PROPERTIES_LIST, 17 | new TermuxSharedProperties.SharedPropertiesParserClient()); 18 | } 19 | 20 | /** 21 | * Initialize the {@link #properties} and load properties from disk. 22 | * 23 | * @param context The {@link Context} for operations. 24 | * @return Returns the {@link TermuxAppSharedProperties}. 25 | */ 26 | public static TermuxAppSharedProperties init(@NonNull Context context) { 27 | if (properties == null) 28 | properties = new TermuxAppSharedProperties(context); 29 | 30 | return properties; 31 | } 32 | 33 | /** 34 | * Get the {@link #properties}. 35 | * 36 | * @return Returns the {@link TermuxAppSharedProperties}. 37 | */ 38 | public static TermuxAppSharedProperties getProperties() { 39 | return properties; 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /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/settings/properties/SharedPropertiesParser.java: -------------------------------------------------------------------------------- 1 | package com.termux.shared.settings.properties; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.annotation.NonNull; 6 | 7 | import java.util.HashMap; 8 | import java.util.Properties; 9 | 10 | /** 11 | * An interface that must be defined by the caller of the {@link SharedProperties} class. 12 | */ 13 | public interface SharedPropertiesParser { 14 | 15 | /** 16 | * Called when properties are loaded from file to allow client to update the {@link Properties} 17 | * loaded from properties file before key/value pairs are stored in the {@link HashMap <>} in-memory 18 | * cache. 19 | * 20 | * @param context The context for operations. 21 | * @param properties The key for which the internal object is required. 22 | */ 23 | @NonNull 24 | Properties preProcessPropertiesOnReadFromDisk(@NonNull Context context, @NonNull Properties properties); 25 | 26 | /** 27 | * A function that should return the internal {@link Object} to be stored for a key/value pair 28 | * read from properties file in the {@link HashMap <>} in-memory cache. 29 | * 30 | * @param context The context for operations. 31 | * @param key The key for which the internal object is required. 32 | * @param value The literal value for the property found is the properties file. 33 | * @return Returns the {@link Object} object to store in the {@link HashMap <>} in-memory cache. 34 | */ 35 | Object getInternalPropertyValueFromValue(@NonNull Context context, String key, String value); 36 | 37 | } 38 | -------------------------------------------------------------------------------- /terminal-emulator/src/main/java/com/termux/terminal/TerminalSessionClient.java: -------------------------------------------------------------------------------- 1 | package com.termux.terminal; 2 | 3 | import androidx.annotation.NonNull; 4 | import androidx.annotation.Nullable; 5 | 6 | /** 7 | * The interface for communication between {@link TerminalSession} and its client. It is used to 8 | * send callbacks to the client when {@link TerminalSession} changes or for sending other 9 | * back data to the client like logs. 10 | */ 11 | public interface TerminalSessionClient { 12 | 13 | void onTextChanged(@NonNull TerminalSession changedSession); 14 | 15 | void onTitleChanged(@NonNull TerminalSession changedSession); 16 | 17 | void onSessionFinished(@NonNull TerminalSession finishedSession); 18 | 19 | void onCopyTextToClipboard(@NonNull TerminalSession session, String text); 20 | 21 | void onPasteTextFromClipboard(@Nullable TerminalSession session); 22 | 23 | void onBell(@NonNull TerminalSession session); 24 | 25 | void onColorsChanged(@NonNull TerminalSession session); 26 | 27 | void onTerminalCursorStateChange(boolean state); 28 | 29 | void setTerminalShellPid(@NonNull TerminalSession session, int pid); 30 | 31 | 32 | 33 | Integer getTerminalCursorStyle(); 34 | 35 | 36 | 37 | void logError(String tag, String message); 38 | 39 | void logWarn(String tag, String message); 40 | 41 | void logInfo(String tag, String message); 42 | 43 | void logDebug(String tag, String message); 44 | 45 | void logVerbose(String tag, String message); 46 | 47 | void logStackTraceWithMessage(String tag, String message, Exception e); 48 | 49 | void logStackTrace(String tag, Exception e); 50 | 51 | } 52 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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.termux.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 | -------------------------------------------------------------------------------- /termux-shared/src/main/java/com/termux/shared/termux/extrakeys/SpecialButton.java: -------------------------------------------------------------------------------- 1 | package com.termux.shared.termux.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 | -------------------------------------------------------------------------------- /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.termux.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.termux.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.termux.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.termux.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 | -------------------------------------------------------------------------------- /termux-shared/src/main/java/com/termux/shared/termux/extrakeys/SpecialButtonState.java: -------------------------------------------------------------------------------- 1 | package com.termux.shared.termux.extrakeys; 2 | 3 | import com.google.android.material.button.MaterialButton; 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 | for (MaterialButton button : buttons) { 42 | button.setTextColor(value ? mExtraKeysView.getButtonActiveTextColor() : mExtraKeysView.getButtonTextColor()); 43 | } 44 | } 45 | 46 | /** Set {@link #isLocked}. */ 47 | public void setIsLocked(boolean value) { 48 | isLocked = value; 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /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/termux/shell/command/environment/TermuxAPIShellEnvironment.java: -------------------------------------------------------------------------------- 1 | package com.termux.shared.termux.shell.command.environment; 2 | 3 | import android.content.Context; 4 | import android.content.pm.PackageInfo; 5 | 6 | import androidx.annotation.NonNull; 7 | import androidx.annotation.Nullable; 8 | 9 | import com.termux.shared.android.PackageUtils; 10 | import com.termux.shared.shell.command.environment.ShellEnvironmentUtils; 11 | import com.termux.shared.termux.TermuxConstants; 12 | import com.termux.shared.termux.TermuxUtils; 13 | 14 | import java.util.HashMap; 15 | 16 | /** 17 | * Environment for {@link TermuxConstants#TERMUX_API_PACKAGE_NAME} app. 18 | */ 19 | public class TermuxAPIShellEnvironment { 20 | 21 | /** Environment variable prefix for the Termux:API app. */ 22 | public static final String TERMUX_API_APP_ENV_PREFIX = TermuxConstants.TERMUX_ENV_PREFIX_ROOT + "_API_APP__"; 23 | 24 | /** Environment variable for the Termux:API app version. */ 25 | public static final String ENV_TERMUX_API_APP__VERSION_NAME = TERMUX_API_APP_ENV_PREFIX + "VERSION_NAME"; 26 | 27 | /** Get shell environment for Termux:API app. */ 28 | @Nullable 29 | public static HashMap getEnvironment(@NonNull Context currentPackageContext) { 30 | if (TermuxUtils.isTermuxAPIAppInstalled(currentPackageContext) != null) return null; 31 | 32 | String packageName = TermuxConstants.TERMUX_API_PACKAGE_NAME; 33 | PackageInfo packageInfo = PackageUtils.getPackageInfoForPackage(currentPackageContext, packageName); 34 | if (packageInfo == null) return null; 35 | 36 | HashMap environment = new HashMap<>(); 37 | 38 | ShellEnvironmentUtils.putToEnvIfSet(environment, ENV_TERMUX_API_APP__VERSION_NAME, PackageUtils.getVersionNameForPackage(packageInfo)); 39 | 40 | return environment; 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /termux-shared/src/main/java/com/termux/shared/shell/command/environment/IShellEnvironment.java: -------------------------------------------------------------------------------- 1 | package com.termux.shared.shell.command.environment; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.annotation.NonNull; 6 | import androidx.annotation.Nullable; 7 | 8 | import com.termux.shared.shell.command.ExecutionCommand; 9 | 10 | import java.util.HashMap; 11 | 12 | public interface IShellEnvironment { 13 | 14 | /** 15 | * Get the default working directory path for the environment in case the path that was passed 16 | * was {@code null} or empty. 17 | * 18 | * @return Should return the default working directory path. 19 | */ 20 | @NonNull 21 | String getDefaultWorkingDirectoryPath(); 22 | 23 | /** 24 | * Get the default "/bin" path, like $PREFIX/bin. 25 | * 26 | * @return Should return the "/bin" path. 27 | */ 28 | @NonNull 29 | String getDefaultBinPath(); 30 | 31 | /** 32 | * Setup shell command arguments for the file to execute, like interpreter, etc. 33 | * 34 | * @param fileToExecute The file to execute. 35 | * @param arguments The arguments to pass to the executable. 36 | * @return Should return the final process arguments. 37 | */ 38 | @NonNull 39 | String[] setupShellCommandArguments(@NonNull String fileToExecute, @Nullable String[] arguments); 40 | 41 | /** 42 | * Setup shell command environment to be used for commands. 43 | * 44 | * @param currentPackageContext The {@link Context} for the current package. 45 | * @param executionCommand The {@link ExecutionCommand} for which to set environment. 46 | * @return Should return the shell environment. 47 | */ 48 | @NonNull 49 | HashMap setupShellCommandEnvironment(@NonNull Context currentPackageContext, 50 | @NonNull ExecutionCommand executionCommand); 51 | 52 | } 53 | -------------------------------------------------------------------------------- /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/settings/preferences/AppSharedPreferences.java: -------------------------------------------------------------------------------- 1 | package com.termux.shared.settings.preferences; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | 6 | import androidx.annotation.NonNull; 7 | import androidx.annotation.Nullable; 8 | 9 | /** A class that holds {@link SharedPreferences} objects for apps. */ 10 | public class AppSharedPreferences { 11 | 12 | /** The {@link Context} for operations. */ 13 | protected final Context mContext; 14 | 15 | /** The {@link SharedPreferences} that ideally should be created with {@link SharedPreferenceUtils#getPrivateSharedPreferences(Context, String)}. */ 16 | protected final SharedPreferences mSharedPreferences; 17 | 18 | /** The {@link SharedPreferences}that ideally should be created with {@link SharedPreferenceUtils#getPrivateAndMultiProcessSharedPreferences(Context, String)}. */ 19 | protected final SharedPreferences mMultiProcessSharedPreferences; 20 | 21 | protected AppSharedPreferences(@NonNull Context context, @Nullable SharedPreferences sharedPreferences) { 22 | this(context, sharedPreferences, null); 23 | } 24 | 25 | protected AppSharedPreferences(@NonNull Context context, @Nullable SharedPreferences sharedPreferences, 26 | @Nullable SharedPreferences multiProcessSharedPreferences) { 27 | mContext = context; 28 | mSharedPreferences = sharedPreferences; 29 | mMultiProcessSharedPreferences = multiProcessSharedPreferences; 30 | } 31 | 32 | 33 | 34 | /** Get {@link #mContext}. */ 35 | public Context getContext() { 36 | return mContext; 37 | } 38 | 39 | /** Get {@link #mSharedPreferences}. */ 40 | public SharedPreferences getSharedPreferences() { 41 | return mSharedPreferences; 42 | } 43 | 44 | /** Get {@link #mMultiProcessSharedPreferences}. */ 45 | public SharedPreferences getMultiProcessSharedPreferences() { 46 | return mMultiProcessSharedPreferences; 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /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, int cellWidth, int cellHeight); 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, int cellWidth, int cellHeight); 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 | -------------------------------------------------------------------------------- /termux-shared/src/main/java/com/termux/shared/net/socket/local/LocalSocketManagerClientBase.java: -------------------------------------------------------------------------------- 1 | package com.termux.shared.net.socket.local; 2 | 3 | import androidx.annotation.NonNull; 4 | import androidx.annotation.Nullable; 5 | 6 | import com.termux.shared.errors.Error; 7 | import com.termux.shared.logger.Logger; 8 | 9 | /** Base helper implementation for {@link ILocalSocketManager}. */ 10 | public abstract class LocalSocketManagerClientBase implements ILocalSocketManager { 11 | 12 | @Nullable 13 | @Override 14 | public Thread.UncaughtExceptionHandler getLocalSocketManagerClientThreadUEH( 15 | @NonNull LocalSocketManager localSocketManager) { 16 | return null; 17 | } 18 | 19 | @Override 20 | public void onError(@NonNull LocalSocketManager localSocketManager, 21 | @Nullable LocalClientSocket clientSocket, @NonNull Error error) { 22 | // Only log if log level is debug or higher since PeerCred.cmdline may contain private info 23 | Logger.logErrorPrivate(getLogTag(), "onError"); 24 | Logger.logErrorPrivateExtended(getLogTag(), LocalSocketManager.getErrorLogString(error, 25 | localSocketManager.getLocalSocketRunConfig(), clientSocket)); 26 | } 27 | 28 | @Override 29 | public void onDisallowedClientConnected(@NonNull LocalSocketManager localSocketManager, 30 | @NonNull LocalClientSocket clientSocket, @NonNull Error error) { 31 | Logger.logWarn(getLogTag(), "onDisallowedClientConnected"); 32 | Logger.logWarnExtended(getLogTag(), LocalSocketManager.getErrorLogString(error, 33 | localSocketManager.getLocalSocketRunConfig(), clientSocket)); 34 | } 35 | 36 | @Override 37 | public void onClientAccepted(@NonNull LocalSocketManager localSocketManager, 38 | @NonNull LocalClientSocket clientSocket) { 39 | // Just close socket and let child class handle any required communication 40 | clientSocket.closeClientSocket(true); 41 | } 42 | 43 | 44 | 45 | protected abstract String getLogTag(); 46 | 47 | } 48 | -------------------------------------------------------------------------------- /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 | implementation "androidx.annotation:annotation:1.3.0" 54 | testImplementation "junit:junit:4.13.2" 55 | } 56 | 57 | task sourceJar(type: Jar) { 58 | from android.sourceSets.main.java.srcDirs 59 | classifier "sources" 60 | } 61 | 62 | afterEvaluate { 63 | publishing { 64 | publications { 65 | // Creates a Maven publication called "release". 66 | release(MavenPublication) { 67 | from components.release 68 | groupId = 'com.termux' 69 | artifactId = 'terminal-emulator' 70 | version = '0.118.0' 71 | artifact(sourceJar) 72 | } 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /termux-shared/src/main/java/com/termux/shared/termux/shell/command/environment/TermuxShellCommandShellEnvironment.java: -------------------------------------------------------------------------------- 1 | package com.termux.shared.termux.shell.command.environment; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.annotation.NonNull; 6 | 7 | import com.termux.shared.shell.command.ExecutionCommand; 8 | import com.termux.shared.shell.command.environment.ShellCommandShellEnvironment; 9 | import com.termux.shared.shell.command.environment.ShellEnvironmentUtils; 10 | import com.termux.shared.termux.settings.preferences.TermuxAppSharedPreferences; 11 | import com.termux.shared.termux.shell.TermuxShellManager; 12 | 13 | import java.util.HashMap; 14 | 15 | /** 16 | * Environment for Termux {@link ExecutionCommand}. 17 | */ 18 | public class TermuxShellCommandShellEnvironment extends ShellCommandShellEnvironment { 19 | 20 | /** Get shell environment containing info for Termux {@link ExecutionCommand}. */ 21 | @NonNull 22 | @Override 23 | public HashMap getEnvironment(@NonNull Context currentPackageContext, 24 | @NonNull ExecutionCommand executionCommand) { 25 | HashMap environment = super.getEnvironment(currentPackageContext, executionCommand); 26 | 27 | TermuxAppSharedPreferences preferences = TermuxAppSharedPreferences.build(currentPackageContext); 28 | if (preferences == null) return environment; 29 | 30 | if (ExecutionCommand.Runner.APP_SHELL.equalsRunner(executionCommand.runner)) { 31 | ShellEnvironmentUtils.putToEnvIfSet(environment, ENV_SHELL_CMD__APP_SHELL_NUMBER_SINCE_BOOT, 32 | String.valueOf(preferences.getAndIncrementAppShellNumberSinceBoot())); 33 | ShellEnvironmentUtils.putToEnvIfSet(environment, ENV_SHELL_CMD__APP_SHELL_NUMBER_SINCE_APP_START, 34 | String.valueOf(TermuxShellManager.getAndIncrementAppShellNumberSinceAppStart())); 35 | 36 | } else if (ExecutionCommand.Runner.TERMINAL_SESSION.equalsRunner(executionCommand.runner)) { 37 | ShellEnvironmentUtils.putToEnvIfSet(environment, ENV_SHELL_CMD__TERMINAL_SESSION_NUMBER_SINCE_BOOT, 38 | String.valueOf(preferences.getAndIncrementTerminalSessionNumberSinceBoot())); 39 | ShellEnvironmentUtils.putToEnvIfSet(environment, ENV_SHELL_CMD__TERMINAL_SESSION_NUMBER_SINCE_APP_START, 40 | String.valueOf(TermuxShellManager.getAndIncrementTerminalSessionNumberSinceAppStart())); 41 | } else { 42 | return environment; 43 | } 44 | 45 | return environment; 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /termux-shared/src/main/java/com/termux/shared/shell/ShellUtils.java: -------------------------------------------------------------------------------- 1 | package com.termux.shared.shell; 2 | 3 | import androidx.annotation.NonNull; 4 | import androidx.annotation.Nullable; 5 | 6 | import com.termux.shared.file.FileUtils; 7 | import com.termux.terminal.TerminalBuffer; 8 | import com.termux.terminal.TerminalEmulator; 9 | import com.termux.terminal.TerminalSession; 10 | 11 | import java.lang.reflect.Field; 12 | 13 | import java.util.ArrayList; 14 | import java.util.Collections; 15 | import java.util.List; 16 | 17 | public class ShellUtils { 18 | 19 | /** Get process id of {@link Process}. */ 20 | public static int getPid(Process p) { 21 | try { 22 | Field f = p.getClass().getDeclaredField("pid"); 23 | f.setAccessible(true); 24 | try { 25 | return f.getInt(p); 26 | } finally { 27 | f.setAccessible(false); 28 | } 29 | } catch (Throwable e) { 30 | return -1; 31 | } 32 | } 33 | 34 | /** Setup shell command arguments for the execute. */ 35 | @NonNull 36 | public static String[] setupShellCommandArguments(@NonNull String executable, @Nullable String[] arguments) { 37 | List result = new ArrayList<>(); 38 | result.add(executable); 39 | if (arguments != null) Collections.addAll(result, arguments); 40 | return result.toArray(new String[0]); 41 | } 42 | 43 | /** Get basename for executable. */ 44 | @Nullable 45 | public static String getExecutableBasename(@Nullable String executable) { 46 | return FileUtils.getFileBasename(executable); 47 | } 48 | 49 | 50 | 51 | /** Get transcript for {@link TerminalSession}. */ 52 | public static String getTerminalSessionTranscriptText(TerminalSession terminalSession, boolean linesJoined, boolean trim) { 53 | if (terminalSession == null) return null; 54 | 55 | TerminalEmulator terminalEmulator = terminalSession.getEmulator(); 56 | if (terminalEmulator == null) return null; 57 | 58 | TerminalBuffer terminalBuffer = terminalEmulator.getScreen(); 59 | if (terminalBuffer == null) return null; 60 | 61 | String transcriptText; 62 | 63 | if (linesJoined) 64 | transcriptText = terminalBuffer.getTranscriptTextWithFullLinesJoined(); 65 | else 66 | transcriptText = terminalBuffer.getTranscriptTextWithoutJoinedLines(); 67 | 68 | if (transcriptText == null) return null; 69 | 70 | if (trim) 71 | transcriptText = transcriptText.trim(); 72 | 73 | return transcriptText; 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /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.termux.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/termux/terminal/TermuxTerminalSessionClientBase.java: -------------------------------------------------------------------------------- 1 | package com.termux.shared.termux.terminal; 2 | 3 | import androidx.annotation.NonNull; 4 | import androidx.annotation.Nullable; 5 | 6 | import com.termux.shared.logger.Logger; 7 | import com.termux.terminal.TerminalSession; 8 | import com.termux.terminal.TerminalSessionClient; 9 | 10 | public class TermuxTerminalSessionClientBase implements TerminalSessionClient { 11 | 12 | public TermuxTerminalSessionClientBase() { 13 | } 14 | 15 | @Override 16 | public void onTextChanged(@NonNull TerminalSession changedSession) { 17 | } 18 | 19 | @Override 20 | public void onTitleChanged(@NonNull TerminalSession updatedSession) { 21 | } 22 | 23 | @Override 24 | public void onSessionFinished(@NonNull TerminalSession finishedSession) { 25 | } 26 | 27 | @Override 28 | public void onCopyTextToClipboard(@NonNull TerminalSession session, String text) { 29 | } 30 | 31 | @Override 32 | public void onPasteTextFromClipboard(@Nullable TerminalSession session) { 33 | } 34 | 35 | @Override 36 | public void onBell(@NonNull TerminalSession session) { 37 | } 38 | 39 | @Override 40 | public void onColorsChanged(@NonNull TerminalSession changedSession) { 41 | } 42 | 43 | @Override 44 | public void onTerminalCursorStateChange(boolean state) { 45 | } 46 | 47 | @Override 48 | public void setTerminalShellPid(@NonNull TerminalSession session, int pid) { 49 | } 50 | 51 | 52 | @Override 53 | public Integer getTerminalCursorStyle() { 54 | return null; 55 | } 56 | 57 | 58 | 59 | @Override 60 | public void logError(String tag, String message) { 61 | Logger.logError(tag, message); 62 | } 63 | 64 | @Override 65 | public void logWarn(String tag, String message) { 66 | Logger.logWarn(tag, message); 67 | } 68 | 69 | @Override 70 | public void logInfo(String tag, String message) { 71 | Logger.logInfo(tag, message); 72 | } 73 | 74 | @Override 75 | public void logDebug(String tag, String message) { 76 | Logger.logDebug(tag, message); 77 | } 78 | 79 | @Override 80 | public void logVerbose(String tag, String message) { 81 | Logger.logVerbose(tag, message); 82 | } 83 | 84 | @Override 85 | public void logStackTraceWithMessage(String tag, String message, Exception e) { 86 | Logger.logStackTraceWithMessage(tag, message, e); 87 | } 88 | 89 | @Override 90 | public void logStackTrace(String tag, Exception e) { 91 | Logger.logStackTrace(tag, e); 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /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/src/main/java/com/termux/shared/android/ProcessUtils.java: -------------------------------------------------------------------------------- 1 | package com.termux.shared.android; 2 | 3 | import android.app.ActivityManager; 4 | import android.content.Context; 5 | 6 | import androidx.annotation.NonNull; 7 | import androidx.annotation.Nullable; 8 | 9 | import com.termux.shared.logger.Logger; 10 | 11 | import java.util.List; 12 | 13 | public class ProcessUtils { 14 | 15 | public static final String LOG_TAG = "ProcessUtils"; 16 | 17 | /** 18 | * Get the app process name for a pid with a call to {@link ActivityManager#getRunningAppProcesses()}. 19 | * 20 | * This will not return child process names. Android did not keep track of them before android 12 21 | * phantom process addition, but there is no API via IActivityManager to get them. 22 | * 23 | * To get process name for pids of own app's child processes, check `get_process_name_from_cmdline()` 24 | * in `local-socket.cpp`. 25 | * 26 | * https://cs.android.com/android/platform/superproject/+/android-12.0.0_r32:frameworks/base/core/java/android/app/ActivityManager.java;l=3362 27 | * https://cs.android.com/android/platform/superproject/+/android-12.0.0_r32:frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java;l=8434 28 | * https://cs.android.com/android/_/android/platform/frameworks/base/+/refs/tags/android-12.0.0_r32:services/core/java/com/android/server/am/PhantomProcessList.java 29 | * https://cs.android.com/android/_/android/platform/frameworks/base/+/refs/tags/android-12.0.0_r32:services/core/java/com/android/server/am/PhantomProcessRecord.java 30 | * 31 | * @param context The {@link Context} for operations. 32 | * @param pid The pid of the process. 33 | * @return Returns the app process name if found, otherwise {@code null}. 34 | */ 35 | @Nullable 36 | public static String getAppProcessNameForPid(@NonNull Context context, int pid) { 37 | if (pid < 0) return null; 38 | 39 | ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); 40 | if (activityManager == null) return null; 41 | try { 42 | List runningApps = activityManager.getRunningAppProcesses(); 43 | if (runningApps == null) { 44 | return null; 45 | } 46 | for (ActivityManager.RunningAppProcessInfo procInfo : runningApps) { 47 | if (procInfo.pid == pid) { 48 | return procInfo.processName; 49 | } 50 | } 51 | } catch (Exception e) { 52 | Logger.logStackTraceWithMessage(LOG_TAG, "Failed to get app process name for pid " + pid, e); 53 | } 54 | 55 | return null; 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /terminal-emulator/src/main/java/com/termux/terminal/Logger.java: -------------------------------------------------------------------------------- 1 | package com.termux.terminal; 2 | 3 | import android.util.Log; 4 | 5 | import java.io.IOException; 6 | import java.io.PrintWriter; 7 | import java.io.StringWriter; 8 | 9 | public class Logger { 10 | 11 | public static void logError(TerminalSessionClient client, String logTag, String message) { 12 | if (client != null) 13 | client.logError(logTag, message); 14 | else 15 | Log.e(logTag, message); 16 | } 17 | 18 | public static void logWarn(TerminalSessionClient client, String logTag, String message) { 19 | if (client != null) 20 | client.logWarn(logTag, message); 21 | else 22 | Log.w(logTag, message); 23 | } 24 | 25 | public static void logInfo(TerminalSessionClient client, String logTag, String message) { 26 | if (client != null) 27 | client.logInfo(logTag, message); 28 | else 29 | Log.i(logTag, message); 30 | } 31 | 32 | public static void logDebug(TerminalSessionClient client, String logTag, String message) { 33 | if (client != null) 34 | client.logDebug(logTag, message); 35 | else 36 | Log.d(logTag, message); 37 | } 38 | 39 | public static void logVerbose(TerminalSessionClient client, String logTag, String message) { 40 | if (client != null) 41 | client.logVerbose(logTag, message); 42 | else 43 | Log.v(logTag, message); 44 | } 45 | 46 | public static void logStackTraceWithMessage(TerminalSessionClient client, String tag, String message, Throwable throwable) { 47 | logError(client, tag, getMessageAndStackTraceString(message, throwable)); 48 | } 49 | 50 | public static String getMessageAndStackTraceString(String message, Throwable throwable) { 51 | if (message == null && throwable == null) 52 | return null; 53 | else if (message != null && throwable != null) 54 | return message + ":\n" + getStackTraceString(throwable); 55 | else if (throwable == null) 56 | return message; 57 | else 58 | return getStackTraceString(throwable); 59 | } 60 | 61 | public static String getStackTraceString(Throwable throwable) { 62 | if (throwable == null) return null; 63 | 64 | String stackTraceString = null; 65 | 66 | try { 67 | StringWriter errors = new StringWriter(); 68 | PrintWriter pw = new PrintWriter(errors); 69 | throwable.printStackTrace(pw); 70 | pw.close(); 71 | stackTraceString = errors.toString(); 72 | errors.close(); 73 | } catch (IOException e) { 74 | e.printStackTrace(); 75 | } 76 | 77 | return stackTraceString; 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-utils": { 4 | "inputs": { 5 | "systems": "systems" 6 | }, 7 | "locked": { 8 | "lastModified": 1731533236, 9 | "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", 10 | "owner": "numtide", 11 | "repo": "flake-utils", 12 | "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", 13 | "type": "github" 14 | }, 15 | "original": { 16 | "owner": "numtide", 17 | "repo": "flake-utils", 18 | "type": "github" 19 | } 20 | }, 21 | "gradle2nix": { 22 | "inputs": { 23 | "flake-utils": "flake-utils", 24 | "nixpkgs": "nixpkgs" 25 | }, 26 | "locked": { 27 | "lastModified": 1743629487, 28 | "narHash": "sha256-MjnEgT9MhO2HknLhrx7GvBRVxdOzSKydIJMyzawe2Fk=", 29 | "owner": "tadfisher", 30 | "repo": "gradle2nix", 31 | "rev": "293ecbdc10d32d9d4bdc2d23213b9be09ce247ee", 32 | "type": "github" 33 | }, 34 | "original": { 35 | "owner": "tadfisher", 36 | "ref": "v2", 37 | "repo": "gradle2nix", 38 | "type": "github" 39 | } 40 | }, 41 | "nixpkgs": { 42 | "locked": { 43 | "lastModified": 1743448293, 44 | "narHash": "sha256-bmEPmSjJakAp/JojZRrUvNcDX2R5/nuX6bm+seVaGhs=", 45 | "owner": "NixOS", 46 | "repo": "nixpkgs", 47 | "rev": "77b584d61ff80b4cef9245829a6f1dfad5afdfa3", 48 | "type": "github" 49 | }, 50 | "original": { 51 | "owner": "NixOS", 52 | "ref": "nixos-unstable", 53 | "repo": "nixpkgs", 54 | "type": "github" 55 | } 56 | }, 57 | "nixpkgs_2": { 58 | "locked": { 59 | "lastModified": 1749794982, 60 | "narHash": "sha256-Kh9K4taXbVuaLC0IL+9HcfvxsSUx8dPB5s5weJcc9pc=", 61 | "owner": "nixos", 62 | "repo": "nixpkgs", 63 | "rev": "ee930f9755f58096ac6e8ca94a1887e0534e2d81", 64 | "type": "github" 65 | }, 66 | "original": { 67 | "owner": "nixos", 68 | "ref": "nixos-unstable", 69 | "repo": "nixpkgs", 70 | "type": "github" 71 | } 72 | }, 73 | "root": { 74 | "inputs": { 75 | "gradle2nix": "gradle2nix", 76 | "nixpkgs": "nixpkgs_2" 77 | } 78 | }, 79 | "systems": { 80 | "locked": { 81 | "lastModified": 1681028828, 82 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 83 | "owner": "nix-systems", 84 | "repo": "default", 85 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 86 | "type": "github" 87 | }, 88 | "original": { 89 | "owner": "nix-systems", 90 | "repo": "default", 91 | "type": "github" 92 | } 93 | } 94 | }, 95 | "root": "root", 96 | "version": 7 97 | } 98 | -------------------------------------------------------------------------------- /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.termux.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 | -------------------------------------------------------------------------------- /termux-shared/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 10 | 28 | 29 | 30 | 34 | 35 | 36 | 37 | 38 | 39 | 43 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /termux-shared/src/main/java/com/termux/shared/theme/NightMode.java: -------------------------------------------------------------------------------- 1 | package com.termux.shared.theme; 2 | 3 | import androidx.annotation.NonNull; 4 | import androidx.annotation.Nullable; 5 | import androidx.appcompat.app.AppCompatDelegate; 6 | 7 | import com.termux.shared.logger.Logger; 8 | 9 | /** The modes used by to decide night mode for themes. */ 10 | public enum NightMode { 11 | 12 | /** Night theme should be enabled. */ 13 | TRUE("true", AppCompatDelegate.MODE_NIGHT_YES), 14 | 15 | /** Dark theme should be enabled. */ 16 | FALSE("false", AppCompatDelegate.MODE_NIGHT_NO), 17 | 18 | /** 19 | * Use night or dark theme depending on system night mode. 20 | * https://developer.android.com/guide/topics/resources/providing-resources#NightQualifier 21 | */ 22 | SYSTEM("system", AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM); 23 | 24 | /** The current app wide night mode used by various libraries. Defaults to {@link #SYSTEM}. */ 25 | private static NightMode APP_NIGHT_MODE; 26 | 27 | private static final String LOG_TAG = "NightMode"; 28 | 29 | private final String name; 30 | private final @AppCompatDelegate.NightMode int mode; 31 | 32 | NightMode(final String name, int mode) { 33 | this.name = name; 34 | this.mode = mode; 35 | } 36 | 37 | public String getName() { 38 | return name; 39 | } 40 | 41 | public int getMode() { 42 | return mode; 43 | } 44 | 45 | 46 | /** Get {@link NightMode} for {@code name} if found, otherwise {@code null}. */ 47 | @Nullable 48 | public static NightMode modeOf(String name) { 49 | for (NightMode v : NightMode.values()) { 50 | if (v.name.equals(name)) { 51 | return v; 52 | } 53 | } 54 | 55 | return null; 56 | } 57 | 58 | /** Get {@link NightMode} for {@code name} if found, otherwise {@code def}. */ 59 | @NonNull 60 | public static NightMode modeOf(@Nullable String name, @NonNull NightMode def) { 61 | NightMode nightMode = modeOf(name); 62 | return nightMode != null ? nightMode : def; 63 | } 64 | 65 | 66 | /** Set {@link #APP_NIGHT_MODE}. */ 67 | public static void setAppNightMode(@Nullable String name) { 68 | if (name == null || name.isEmpty()) { 69 | APP_NIGHT_MODE = SYSTEM; 70 | } else { 71 | NightMode nightMode = NightMode.modeOf(name); 72 | if (nightMode == null) { 73 | Logger.logError(LOG_TAG, "Invalid APP_NIGHT_MODE \"" + name + "\""); 74 | return; 75 | } 76 | APP_NIGHT_MODE = nightMode; 77 | } 78 | 79 | Logger.logVerbose(LOG_TAG, "Set APP_NIGHT_MODE to \"" + APP_NIGHT_MODE.getName() + "\""); 80 | } 81 | 82 | /** Get {@link #APP_NIGHT_MODE}. */ 83 | @NonNull 84 | public static NightMode getAppNightMode() { 85 | if (APP_NIGHT_MODE == null) 86 | APP_NIGHT_MODE = SYSTEM; 87 | 88 | return APP_NIGHT_MODE; 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /app/src/main/java/com/termux/app/activities/HelpActivity.java: -------------------------------------------------------------------------------- 1 | package com.termux.app.activities; 2 | 3 | import android.content.ActivityNotFoundException; 4 | import android.content.Intent; 5 | import android.net.Uri; 6 | import android.os.Bundle; 7 | import android.view.ViewGroup; 8 | import android.webkit.WebSettings; 9 | import android.webkit.WebView; 10 | import android.webkit.WebViewClient; 11 | import android.widget.ProgressBar; 12 | import android.widget.RelativeLayout; 13 | 14 | import androidx.appcompat.app.AppCompatActivity; 15 | 16 | import com.termux.shared.termux.TermuxConstants; 17 | 18 | /** Basic embedded browser for viewing help pages. */ 19 | public final class HelpActivity extends AppCompatActivity { 20 | 21 | WebView mWebView; 22 | 23 | @Override 24 | protected void onCreate(Bundle savedInstanceState) { 25 | super.onCreate(savedInstanceState); 26 | 27 | final RelativeLayout progressLayout = new RelativeLayout(this); 28 | RelativeLayout.LayoutParams lParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); 29 | lParams.addRule(RelativeLayout.CENTER_IN_PARENT); 30 | ProgressBar progressBar = new ProgressBar(this); 31 | progressBar.setIndeterminate(true); 32 | progressBar.setLayoutParams(lParams); 33 | progressLayout.addView(progressBar); 34 | 35 | mWebView = new WebView(this); 36 | WebSettings settings = mWebView.getSettings(); 37 | settings.setCacheMode(WebSettings.LOAD_NO_CACHE); 38 | settings.setAppCacheEnabled(false); 39 | setContentView(progressLayout); 40 | mWebView.clearCache(true); 41 | 42 | mWebView.setWebViewClient(new WebViewClient() { 43 | @Override 44 | public boolean shouldOverrideUrlLoading(WebView view, String url) { 45 | if (url.equals(TermuxConstants.TERMUX_WIKI_URL) || url.startsWith(TermuxConstants.TERMUX_WIKI_URL + "/")) { 46 | // Inline help. 47 | setContentView(progressLayout); 48 | return false; 49 | } 50 | 51 | try { 52 | startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); 53 | } catch (ActivityNotFoundException e) { 54 | // Android TV does not have a system browser. 55 | setContentView(progressLayout); 56 | return false; 57 | } 58 | return true; 59 | } 60 | 61 | @Override 62 | public void onPageFinished(WebView view, String url) { 63 | setContentView(mWebView); 64 | } 65 | }); 66 | mWebView.loadUrl(TermuxConstants.TERMUX_WIKI_URL); 67 | } 68 | 69 | @Override 70 | public void onBackPressed() { 71 | if (mWebView.canGoBack()) { 72 | mWebView.goBack(); 73 | } else { 74 | super.onBackPressed(); 75 | } 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /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/termux/terminal/io/BellHandler.java: -------------------------------------------------------------------------------- 1 | package com.termux.shared.termux.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 | -------------------------------------------------------------------------------- /terminal-view/src/main/java/com/termux/view/support/PopupWindowCompatGingerbread.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License 15 | */ 16 | package com.termux.view.support; 17 | 18 | import android.util.Log; 19 | import android.widget.PopupWindow; 20 | 21 | import java.lang.reflect.Method; 22 | 23 | /** 24 | * Implementation of PopupWindow compatibility that can call Gingerbread APIs. 25 | * https://chromium.googlesource.com/android_tools/+/HEAD/sdk/extras/android/support/v4/src/gingerbread/android/support/v4/widget/PopupWindowCompatGingerbread.java 26 | */ 27 | public class PopupWindowCompatGingerbread { 28 | 29 | private static Method sSetWindowLayoutTypeMethod; 30 | private static boolean sSetWindowLayoutTypeMethodAttempted; 31 | private static Method sGetWindowLayoutTypeMethod; 32 | private static boolean sGetWindowLayoutTypeMethodAttempted; 33 | 34 | public static void setWindowLayoutType(PopupWindow popupWindow, int layoutType) { 35 | if (!sSetWindowLayoutTypeMethodAttempted) { 36 | try { 37 | sSetWindowLayoutTypeMethod = PopupWindow.class.getDeclaredMethod( 38 | "setWindowLayoutType", int.class); 39 | sSetWindowLayoutTypeMethod.setAccessible(true); 40 | } catch (Exception e) { 41 | // Reflection method fetch failed. Oh well. 42 | } 43 | sSetWindowLayoutTypeMethodAttempted = true; 44 | } 45 | if (sSetWindowLayoutTypeMethod != null) { 46 | try { 47 | sSetWindowLayoutTypeMethod.invoke(popupWindow, layoutType); 48 | } catch (Exception e) { 49 | // Reflection call failed. Oh well. 50 | } 51 | } 52 | } 53 | 54 | public static int getWindowLayoutType(PopupWindow popupWindow) { 55 | if (!sGetWindowLayoutTypeMethodAttempted) { 56 | try { 57 | sGetWindowLayoutTypeMethod = PopupWindow.class.getDeclaredMethod( 58 | "getWindowLayoutType"); 59 | sGetWindowLayoutTypeMethod.setAccessible(true); 60 | } catch (Exception e) { 61 | // Reflection method fetch failed. Oh well. 62 | } 63 | sGetWindowLayoutTypeMethodAttempted = true; 64 | } 65 | if (sGetWindowLayoutTypeMethod != null) { 66 | try { 67 | return (Integer) sGetWindowLayoutTypeMethod.invoke(popupWindow); 68 | } catch (Exception e) { 69 | // Reflection call failed. Oh well. 70 | } 71 | } 72 | return 0; 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /termux-shared/src/main/java/com/termux/shared/termux/terminal/TermuxTerminalViewClientBase.java: -------------------------------------------------------------------------------- 1 | package com.termux.shared.termux.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/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 23 | 31 | 32 | 33 | 38 | 43 | 44 | 49 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /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 | ndkVersion = System.getenv("JITPACK_NDK_VERSION") ?: project.properties.ndkVersion 7 | 8 | dependencies { 9 | implementation "androidx.appcompat:appcompat:1.3.1" 10 | implementation "androidx.annotation:annotation:1.3.0" 11 | implementation "androidx.core:core:1.6.0" 12 | implementation "com.google.android.material:material:1.4.0" 13 | implementation "com.google.guava:guava:24.1-jre" 14 | implementation "io.noties.markwon:core:$markwonVersion" 15 | implementation "io.noties.markwon:ext-strikethrough:$markwonVersion" 16 | implementation "io.noties.markwon:linkify:$markwonVersion" 17 | implementation "io.noties.markwon:recycler:$markwonVersion" 18 | implementation "org.lsposed.hiddenapibypass:hiddenapibypass:6.1" 19 | 20 | // Do not increment version higher than 1.0.0-alpha09 since it will break ViewUtils and needs to be looked into 21 | // noinspection GradleDependency 22 | implementation "androidx.window:window:1.0.0-alpha09" 23 | 24 | // Do not increment version higher than 2.5 or there 25 | // will be runtime exceptions on android < 8 26 | // due to missing classes like java.nio.file.Path. 27 | implementation "commons-io:commons-io:2.5" 28 | 29 | implementation project(":terminal-view") 30 | 31 | implementation "com.termux:termux-am-library:v2.0.0" 32 | } 33 | 34 | defaultConfig { 35 | minSdkVersion project.properties.minSdkVersion.toInteger() 36 | targetSdkVersion project.properties.targetSdkVersion.toInteger() 37 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 38 | externalNativeBuild { 39 | ndkBuild { 40 | cppFlags '' 41 | } 42 | } 43 | } 44 | 45 | buildTypes { 46 | release { 47 | minifyEnabled false 48 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 49 | } 50 | } 51 | 52 | compileOptions { 53 | // Flag to enable support for the new language APIs 54 | coreLibraryDesugaringEnabled true 55 | 56 | sourceCompatibility JavaVersion.VERSION_1_8 57 | targetCompatibility JavaVersion.VERSION_1_8 58 | } 59 | externalNativeBuild { 60 | ndkBuild { 61 | path file('src/main/cpp/Android.mk') 62 | } 63 | } 64 | } 65 | 66 | dependencies { 67 | testImplementation "junit:junit:4.13.2" 68 | androidTestImplementation "androidx.test.ext:junit:1.1.3" 69 | androidTestImplementation "androidx.test.espresso:espresso-core:3.4.0" 70 | coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:1.1.5" 71 | } 72 | 73 | task sourceJar(type: Jar) { 74 | from android.sourceSets.main.java.srcDirs 75 | classifier "sources" 76 | } 77 | 78 | afterEvaluate { 79 | publishing { 80 | publications { 81 | // Creates a Maven publication called "release". 82 | release(MavenPublication) { 83 | from components.release 84 | groupId = 'com.termux' 85 | artifactId = 'termux-shared' 86 | version = '0.118.0' 87 | artifact(sourceJar) 88 | } 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /termux-shared/src/main/java/com/termux/shared/termux/interact/TextInputDialogUtils.java: -------------------------------------------------------------------------------- 1 | package com.termux.shared.termux.interact; 2 | 3 | import android.app.Activity; 4 | import android.app.AlertDialog; 5 | import android.content.DialogInterface; 6 | import android.text.Selection; 7 | import android.util.TypedValue; 8 | import android.view.KeyEvent; 9 | import android.view.ViewGroup.LayoutParams; 10 | import android.widget.EditText; 11 | import android.widget.LinearLayout; 12 | 13 | public final class TextInputDialogUtils { 14 | 15 | public interface TextSetListener { 16 | void onTextSet(String text); 17 | } 18 | 19 | public static void textInput(Activity activity, int titleText, String initialText, 20 | int positiveButtonText, final TextSetListener onPositive, 21 | int neutralButtonText, final TextSetListener onNeutral, 22 | int negativeButtonText, final TextSetListener onNegative, 23 | final DialogInterface.OnDismissListener onDismiss) { 24 | final EditText input = new EditText(activity); 25 | input.setSingleLine(); 26 | if (initialText != null) { 27 | input.setText(initialText); 28 | Selection.setSelection(input.getText(), initialText.length()); 29 | } 30 | 31 | final AlertDialog[] dialogHolder = new AlertDialog[1]; 32 | input.setImeActionLabel(activity.getResources().getString(positiveButtonText), KeyEvent.KEYCODE_ENTER); 33 | input.setOnEditorActionListener((v, actionId, event) -> { 34 | onPositive.onTextSet(input.getText().toString()); 35 | dialogHolder[0].dismiss(); 36 | return true; 37 | }); 38 | 39 | float dipInPixels = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, activity.getResources().getDisplayMetrics()); 40 | // https://www.google.com/design/spec/components/dialogs.html#dialogs-specs 41 | int paddingTopAndSides = Math.round(16 * dipInPixels); 42 | int paddingBottom = Math.round(24 * dipInPixels); 43 | 44 | LinearLayout layout = new LinearLayout(activity); 45 | layout.setOrientation(LinearLayout.VERTICAL); 46 | layout.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); 47 | layout.setPadding(paddingTopAndSides, paddingTopAndSides, paddingTopAndSides, paddingBottom); 48 | layout.addView(input); 49 | 50 | AlertDialog.Builder builder = new AlertDialog.Builder(activity) 51 | .setTitle(titleText).setView(layout) 52 | .setPositiveButton(positiveButtonText, (d, whichButton) -> onPositive.onTextSet(input.getText().toString())); 53 | 54 | if (onNeutral != null) { 55 | builder.setNeutralButton(neutralButtonText, (dialog, which) -> onNeutral.onTextSet(input.getText().toString())); 56 | } 57 | 58 | if (onNegative == null) { 59 | builder.setNegativeButton(android.R.string.cancel, null); 60 | } else { 61 | builder.setNegativeButton(negativeButtonText, (dialog, which) -> onNegative.onTextSet(input.getText().toString())); 62 | } 63 | 64 | if (onDismiss != null) 65 | builder.setOnDismissListener(onDismiss); 66 | 67 | dialogHolder[0] = builder.create(); 68 | dialogHolder[0].setCanceledOnTouchOutside(false); 69 | dialogHolder[0].show(); 70 | } 71 | 72 | } 73 | --------------------------------------------------------------------------------