This is the official Termux Android terminal emulator application — an open-source terminal emulator that provides a Linux-like environment on Android devices. The project enables users to run shell commands, install packages via APT, and access a full suite of command-line tools (Bash, Zsh, Python, Git, SSH, etc.) without requiring root access.
The codebase is written in Java (Android application layer) with C (JNI native layer for PTY management), built using Gradle with the Android Gradle Plugin. It targets Android SDK 28 (Android 9.0 Pie) as targetSdkVersion — permanently locked due to Google's restrictions on native executable execution from apps targeting SDK 29+. The minimum SDK is 21 (Android 5.0 Lollipop).
License: GPLv3 (root app), Apache 2.0 (terminal-emulator and terminal-view libraries), MIT (termux-shared core).
Version: 0.118.0 (versionCode 118).
Termux/
├── .editorconfig # Editor config (4-space indent, LF, UTF-8)
├── .gitattributes # Line ending rules per file type
├── .gitignore # Build artifacts, IDE files, devnotes.md
├── .github/
│ ├── dependabot.yml # Daily GitHub Actions dependency updates
│ ├── FUNDING.yml # https://termux.dev/donate
│ ├── ISSUE_TEMPLATE/
│ │ ├── 01-bug-report.yml # Structured bug report template
│ │ ├── 02-feature-request.yml # Feature request template
│ │ └── config.yml # Disables blank issues
│ └── workflows/ # 7 CI/CD workflows
│ ├── attach_debug_apks_to_release.yml
│ ├── debug_build.yml
│ ├── dependency-submission.yml
│ ├── gradle-wrapper-validation.yml
│ ├── release.yml # NEW: auto-versioning + GitHub Releases
│ ├── run_tests.yml
│ └── trigger_library_builds_on_jitpack.yml
├── app/ # Main Android application module
│ ├── build.gradle # App build config, bootstrap download, APK signing
│ ├── proguard-rules.pro # -dontobfuscate
│ ├── testkey_untrusted.jks # PUBLIC shared test signing key
│ └── src/
│ ├── main/
│ │ ├── AndroidManifest.xml # 18 permissions, 5 activities, 2 services
│ │ ├── cpp/ # Native bootstrap extraction layer
│ │ │ ├── Android.mk # Builds libtermux-bootstrap.so
│ │ │ ├── termux-bootstrap.c # JNI: getZip() returns embedded ZIP bytes
│ │ │ └── termux-bootstrap-zip.S # Assembly: .incbin embeds bootstrap ZIP
│ │ ├── java/com/termux/app/ # 35 Java source files
│ │ │ ├── TermuxApplication.java
│ │ │ ├── TermuxActivity.java
│ │ │ ├── TermuxService.java
│ │ │ ├── TermuxOpenReceiver.java
│ │ │ ├── TermuxInstaller.java
│ │ │ ├── RunCommandService.java
│ │ │ ├── activities/
│ │ │ │ ├── SettingsActivity.java
│ │ │ │ └── HelpActivity.java
│ │ │ ├── api/file/
│ │ │ │ └── FileReceiverActivity.java
│ │ │ ├── event/
│ │ │ │ └── SystemEventReceiver.java
│ │ │ ├── fragments/settings/ # 12 preference fragments
│ │ │ ├── models/
│ │ │ │ └── UserAction.java
│ │ │ └── terminal/
│ │ │ ├── TermuxTerminalSessionActivityClient.java
│ │ │ ├── TermuxTerminalSessionServiceClient.java
│ │ │ ├── TermuxTerminalViewClient.java
│ │ │ ├── TermuxSessionsListViewController.java
│ │ │ ├── TermuxActivityRootView.java
│ │ │ └── io/
│ │ │ ├── TermuxTerminalExtraKeys.java
│ │ │ ├── TerminalToolbarViewPager.java
│ │ │ ├── KeyboardShortcut.java
│ │ │ └── FullScreenWorkAround.java
│ │ └── res/ # 44 resource files across 11 directories
│ └── test/
│ └── java/com/termux/app/ # 2 unit test files
├── terminal-emulator/ # Core VT100 terminal emulator library
│ ├── build.gradle # Published to JitPack as com.termux:terminal-emulator
│ └── src/
│ ├── main/
│ │ ├── AndroidManifest.xml # Empty stub
│ │ ├── jni/
│ │ │ ├── Android.mk # Builds libtermux.so
│ │ │ └── termux.c # PTY creation, process spawning (230 lines)
│ │ └── java/com/termux/terminal/ # 15 Java source files
│ │ ├── ByteQueue.java
│ │ ├── JNI.java
│ │ ├── KeyHandler.java
│ │ ├── Logger.java
│ │ ├── TerminalBuffer.java
│ │ ├── TerminalColors.java
│ │ ├── TerminalColorScheme.java
│ │ ├── TerminalEmulator.java # 2617 lines — the VT100 engine
│ │ ├── TerminalOutput.java
│ │ ├── TerminalRow.java
│ │ ├── TerminalSession.java
│ │ ├── TerminalSessionClient.java
│ │ ├── TextStyle.java
│ │ └── WcWidth.java
│ └── test/ # 18 unit test files
├── terminal-view/ # Android View widget for terminal display
│ ├── build.gradle # Published to JitPack as com.termux:terminal-view
│ └── src/main/java/com/termux/view/ # 8 Java source files
│ ├── GestureAndScaleRecognizer.java
│ ├── TerminalRenderer.java
│ ├── TerminalView.java # 1500 lines — the main rendering View
│ ├── TerminalViewClient.java
│ ├── support/
│ │ └── PopupWindowCompatGingerbread.java
│ └── textselection/
│ ├── CursorController.java
│ ├── TextSelectionCursorController.java
│ └── TextSelectionHandleView.java
├── termux-shared/ # Shared library for all Termux apps
│ ├── build.gradle # Published to JitPack as com.termux:termux-shared
│ ├── LICENSE.md # MIT (with GPLv3 exceptions for termux/*)
│ └── src/main/java/com/termux/shared/ # ~121 Java source files across 20 subdirs
│ ├── activities/
│ ├── activity/
│ ├── android/
│ ├── crash/
│ ├── data/
│ ├── errors/
│ ├── file/
│ ├── interact/
│ ├── jni/
│ ├── logger/
│ ├── markdown/
│ ├── models/
│ ├── net/
│ ├── notification/
│ ├── reflection/
│ ├── settings/
│ │ └── properties/ # TermuxAppSharedProperties, TermuxPropertyConstants
│ ├── shell/
│ │ ├── command/
│ │ │ ├── ExecutionCommand.java
│ │ │ ├── runner/
│ │ │ │ ├── app/AppShell.java
│ │ │ │ └── terminal/TermuxSession.java
│ │ │ └── ...
│ │ └── ...
│ ├── termux/
│ │ ├── TermuxConstants.java # 1338 lines — single source of truth for all constants
│ │ ├── TermuxUtils.java # 730 lines — utility methods
│ │ ├── am/
│ │ │ └── TermuxAmSocketServer.java
│ │ ├── extrakeys/
│ │ │ ├── ExtraKeysView.java # 681 lines — extra keys rendering
│ │ │ ├── ExtraKeysInfo.java # 213 lines — JSON config parser
│ │ │ └── ExtraKeyButton.java
│ │ ├── plugins/
│ │ │ └── TermuxPluginUtils.java
│ │ ├── shell/
│ │ │ ├── TermuxShellManager.java
│ │ │ └── TermuxShellEnvironment.java
│ │ └── ...
│ ├── theme/
│ └── view/
├── art/ # Icon/artwork generation scripts and SVGs
├── docs/ # Minimal docs placeholder
├── fastlane/ # F-Droid/Play Store metadata
├── site/ # Sponsor logos
├── build.gradle # Root build: AGP 8.13.2
├── settings.gradle # 4 modules
├── gradle.properties # SDK versions, NDK, JVM args
├── gradle/wrapper/ # Gradle 9.2.1
├── gradlew / gradlew.bat # Gradle wrapper scripts
├── devnotes.md # Developer notes (gitignored)
├── LICENSE.md # GPLv3 with Apache 2.0 exceptions
├── README.md # Comprehensive project docs
└── SECURITY.md # Links to termux.dev/security
app
├── terminal-view (implementation)
│ └── terminal-emulator (api)
└── termux-shared (implementation)
└── terminal-view (implementation)
└── terminal-emulator (api)
The app module depends on both terminal-view and termux-shared. The termux-shared module depends on terminal-view, which depends on terminal-emulator. This creates a layered architecture:
- terminal-emulator — Pure Java/C terminal emulation engine (no Android UI dependencies beyond JNI)
- terminal-view — Android
Viewsubclass that renders terminal content and handles touch/key input - termux-shared — Shared utilities, constants, settings, and Termux-specific abstractions used by all Termux apps
- app — The main Termux Android application that ties everything together
All three library modules are published to JitPack as Maven artifacts for use by Termux plugin apps.
┌─────────────────────────────────────────────────────┐
│ APPLICATION LAYER │
│ TermuxApplication → TermuxActivity → TermuxService │
│ RunCommandService, FileReceiverActivity, Settings │
├─────────────────────────────────────────────────────┤
│ CLIENT LAYER │
│ TermuxTerminalSessionActivityClient │
│ TermuxTerminalViewClient │
│ TermuxTerminalSessionServiceClient │
├─────────────────────────────────────────────────────┤
│ VIEW LAYER │
│ TerminalView → TerminalRenderer │
│ TextSelectionCursorController → HandleViews │
│ ExtraKeysView → ExtraKeysInfo │
│ TerminalToolbarViewPager │
├─────────────────────────────────────────────────────┤
│ EMULATOR LAYER │
│ TerminalEmulator (VT100 state machine) │
│ TerminalBuffer (circular screen memory) │
│ TerminalRow (variable-width character storage) │
│ TerminalColors / TerminalColorScheme │
│ KeyHandler (key → escape sequence mapping) │
│ WcWidth (Unicode character width) │
│ TextStyle (bit-packed cell style) │
├─────────────────────────────────────────────────────┤
│ SYSTEM LAYER │
│ TerminalSession (PTY management) │
│ JNI → termux.c (fork/exec, PTY, TIOCSWINSZ) │
│ ByteQueue (lock-free I/O queues) │
├─────────────────────────────────────────────────────┤
│ SHARED LAYER │
│ TermuxConstants (paths, packages, URLs) │
│ TermuxShellManager (session/task tracking) │
│ TermuxShellEnvironment (env variables) │
│ TermuxAppSharedProperties (runtime config) │
│ TermuxPluginUtils (plugin result delivery) │
│ TermuxAmSocketServer (local socket server) │
│ ExecutionCommand (command lifecycle model) │
└─────────────────────────────────────────────────────┘
| Component | Thread | Notes |
|---|---|---|
TermuxApplication.onCreate |
Main | App startup |
TermuxActivity lifecycle |
Main | UI operations |
TermuxService.onStartCommand |
Main | Synchronized |
TermuxSession PTY reader |
Background | Reads from PTY fd |
TermuxSession PTY writer |
Background | Writes to PTY fd |
TermuxSession waitpid |
Background | Blocking wait |
TerminalSession.MainThreadHandler |
Main | Processes MSG_NEW_INPUT |
AppShell execution |
Background | Runtime.exec() |
StreamGobbler (stdout/stderr) |
Background | Reads subprocess output |
| Bootstrap installation | Background Thread | Extracts ZIP |
| Storage symlink creation | Background Thread | Creates ~/storage |
| Cursor blinker | Main (Handler) | Periodic invalidate |
| ExtraKeysView repeat | ScheduledExecutor | Background |
| TermuxAmSocketServer | Dedicated listener | LocalSocket |
Purpose: Top-level build configuration. Defines Android Gradle Plugin version and repository sources.
- AGP Version:
com.android.tools.build:gradle:8.13.2 - Repositories:
mavenCentral(),google(),maven { url "https://jitpack.io" }— JitPack is used for thetermux-am-librarydependency - No plugins applied at root level — plugins are applied per module
Content: include ':app', ':termux-shared', ':terminal-emulator', ':terminal-view'
Four modules. No rootProject.name set.
| Property | Value | Significance |
|---|---|---|
org.gradle.jvmargs |
-Xmx2048M |
2GB heap for Gradle daemon |
android.useAndroidX |
true |
AndroidX migration complete |
minSdkVersion |
21 |
Android 5.0 Lollipop — oldest supported |
targetSdkVersion |
28 |
PERMANENT — Google restricts native exec from SDK 29+ |
ndkVersion |
29.0.14206865 |
Latest stable NDK |
compileSdkVersion |
36 |
Compile against latest SDK |
markwonVersion |
4.6.2 |
Markdown rendering library |
- Gradle version: 9.2.1 (
gradle-9.2.1-bin.zip) - Network timeout: 10000ms
- Distribution URL validation enabled
- LF line endings, UTF-8 encoding
- 4-space indent for all files
- 2-space indent for YAML files
- Final newline required
Ignores: build/, release/, *.apk, *.so, .externalNativeBuild, .cxx, *.zip, local.properties, .gradle/, .signing/, .idea/, *.iml, Vim swap files, OS files, devnotes.md.
*— text=auto (auto-detect binary/text)*.bat— CRLF*.gradle,*.mk,*.sh— LF
- JDK: OpenJDK 17
- NDK:
JITPACK_NDK_VERSION: "29.0.14206865"— matchesgradle.properties
Lines: 243
This is the most complex build file in the project. Key sections:
ext.packageVariant = System.getenv("TERMUX_PACKAGE_VARIANT") ?: "apt-android-7"Supports two bootstrap variants: apt-android-7 (current, version 2026.02.12-r1) and apt-android-5 (legacy, version 2022.04.28-r6).
implementation "androidx.annotation:annotation:1.9.0"
implementation "androidx.core:core:1.13.1"
implementation "androidx.drawerlayout:drawerlayout:1.2.0"
implementation "androidx.preference:preference:1.2.1"
implementation "androidx.viewpager:viewpager:1.0.0"
implementation "com.google.android.material:material:1.12.0"
implementation "com.google.guava:guava:24.1-jre"
implementation "io.noties.markwon:core:$markwonVersion"
// ... more markwon modules
implementation project(":terminal-view")
implementation project(":termux-shared")At configuration time, versionName is validated against Semantic Version 2.0.0 regex. This enforces semver compliance.
Debug builds use testkey_untrusted.jks with a publicly known password (xrj45yWGLbsO7W0v). Release builds have NO signing config — they are unsigned. F-Droid signs its own builds.
Per-ABI APK splitting: x86, x86_64, armeabi-v7a, arm64-v8a, plus universal. Controlled by env vars TERMUX_SPLIT_APKS_FOR_DEBUG_BUILDS (default "1") and TERMUX_SPLIT_APKS_FOR_RELEASE_BUILDS (default "0").
Custom Gradle task downloadBootstrap downloads ZIP files from https://github.com/termux/termux-packages/releases/ with SHA-256 checksum verification. All 4 architectures × 2 variants = 8 possible ZIP files. Downloaded files are placed in app/src/main/cpp/ for embedding via the assembly .incbin directive.
android.externalNativeBuild.ndkBuild {
path "src/main/cpp/Android.mk"
}C flags: -std=c11 -Wall -Wextra -Werror -Os -fno-stack-protector -Wl,--gc-sections
minifyEnabled true(ProGuard/R8)shrinkResources false— explicitly disabled for reproducible builds- ProGuard rules:
-dontobfuscateonly
Lines: 85
Library module published as com.termux:terminal-emulator:0.118.0.
- NDK build:
src/main/jni/Android.mk unitTests.returnDefaultValues = true— allows testing Android APIs without Robolectric- Dependencies:
annotation:1.9.0,junit:4.13.2 - Java 1.8 compatibility
Lines: 63
Library module published as com.termux:terminal-view:0.118.0.
- API dependency on
project(":terminal-emulator")— transitive - No NDK build
- Dependencies:
annotation:1.9.0,junit:4.13.2
Lines: 100
Library module published as com.termux:termux-shared:0.118.0.
Key dependencies:
androidx.appcompat:appcompat:1.6.1com.google.android.material:material:1.12.0com.google.guava:guava:24.1-jreio.noties.markwon(multiple modules)org.lsposed.hiddenapibypass:hiddenapibypass:6.1— bypasses hidden API restrictionsandroidx.window:window:1.1.0commons-io:commons-io:2.5— pinned at 2.5 to avoid runtime exceptions on Android < 8com.termux:termux-am-library:v2.0.0— Android Messaging socket client librarycoreLibraryDesugaring "com.android.tools:desugar_jdk_libs:1.1.5"— Java 8+ API backport
Lines: 235
INTERNET, READ_EXTERNAL_STORAGE, WRITE_EXTERNAL_STORAGE,
MANAGE_EXTERNAL_STORAGE, WAKE_LOCK, VIBRATE, FOREGROUND_SERVICE,
REQUEST_IGNORE_BATTERY_OPTIMIZATIONS, SYSTEM_ALERT_WINDOW,
READ_LOGS, DUMP, WRITE_SECURE_SETTINGS, REQUEST_INSTALL_PACKAGES,
RECEIVE_BOOT_COMPLETED, PACKAGE_USAGE_STATS, SET_ALARM
Plus custom permission: com.termux.permission.RUN_COMMAND (dangerous).
Activities (5 + 2 aliases):
TermuxActivity— main launcher, singleTask, exportedHelpActivity— embedded WebView for wikiSettingsActivity— preference fragmentsReportActivity— crash/error reportsFileReceiverActivity— handles shared files/URLs.HomeActivity— IOT (Internet of Things) launcher aliasFileShareReceiverActivity— SEND intent aliasFileViewReceiverActivity— VIEW intent alias
Services (2):
TermuxService— not exported, manages terminal sessionsRunCommandService— exported, protected by RUN_COMMAND permission
Providers (2):
TermuxDocumentsProvider— SAF documents providerTermuxOpenReceiver$ContentProvider— file sharing with external apps
Receivers (3):
TermuxOpenReceiver— ACTION_VIEW/SEND handlingSystemEventReceiver— BOOT_COMPLETED, PACKAGE eventsReportActivityBroadcastReceiver
Metadata for: keepalive, multidisplay, multiwindow. android.max_aspect = 10.0 for Galaxy S8 full-screen.
Builds libtermux-bootstrap.so from:
termux-bootstrap-zip.S— assembly filetermux-bootstrap.c— JNI glue
Uses .incbin directive to embed pre-compiled bootstrap ZIP directly into the .rodata section of the shared library. Architecture-conditional selection:
#if defined(__aarch64__)
.incbin "bootstrap-aarch64.zip"
#elif defined(__arm__)
.incbin "bootstrap-arm.zip"
#elif defined(__i386__)
.incbin "bootstrap-i686.zip"
#elif defined(__x86_64__)
.incbin "bootstrap-x86_64.zip"
#endifExports blob (raw ZIP bytes) and blob_size (length). This is the mechanism that provides the initial $PREFIX filesystem without requiring network access.
Single JNI function: Java_com_termux_app_TermuxInstaller_getZip — returns the embedded bootstrap ZIP blob as a jbyteArray. Called by TermuxInstaller.loadZipBytes().
Lines: 230
The core native layer that manages PTY creation and process spawning.
Finds java/lang/RuntimeException class and throws it. Returns -1 for convenience (allows return throw_runtime_exception(...) in error paths).
NULL-safe helper that frees a malloc()'d NULL-terminated array of strings, then frees the array itself. Iterates with for (char** tmp = arr; *tmp; ++tmp) free(*tmp).
This is the most critical function in the native layer:
Step 1: Open PTY Master
int ptm = open("/dev/ptmx", O_RDWR | O_CLOEXEC);Step 2: Configure Slave
grantpt(ptm);
unlockpt(ptm);
ptsname_r(ptm, devname, sizeof(devname)); // or ptsname() on macOSStep 3: Set Terminal Flags
tcgetattr(pts, &term);
term.c_iflag |= IUTF8; // UTF-8 mode
term.c_iflag &= ~(IXON | IXOFF); // Disable Ctrl+S/Ctrl+Q flow control
tcsetattr(pts, TCSANOW, &term);Step 4: Set Window Size
struct winsize ws = {rows, columns, columns*cell_width, rows*cell_height};
ioctl(pts, TIOCSWINSZ, &ws);Step 5: Fork
pid_t pid = fork();Parent process (pid > 0): Stores child PID, returns master fd.
Child process (pid == 0):
- Unblock all signals:
sigfillset()+sigprocmask(SIG_UNBLOCK) - Close master fd
- Create new session:
setsid()— detaches from controlling terminal - Open slave PTY:
open(devname, O_RDWR) - Redirect stdio:
dup2(pts, 0/1/2) - Close all fds > 2: iterates
/proc/self/fd - Set environment:
clearenv()+putenv()for each var - Change directory:
chdir(cwd) - Exec:
execvp(cmd, argv)
Converts Java arrays to C arrays via GetStringUTFChars() + strdup(). Uses free_string_array() for cleanup. Writes PID via GetPrimitiveArrayCritical() (zero-copy).
Memory leak fix (commit 10b01ead): Error paths now properly free argv and envp before returning. Previously, allocation failures leaked previously allocated arrays.
Bug fix (commit 10b01ead): ReleaseStringUTFChars(env, cmd, cmd_cwd) was incorrectly using cmd instead of cwd. This caused undefined behavior per JNI spec.
struct winsize ws = {rows, cols, cell_width*cols, cell_height*rows};
ioctl(fd, TIOCSWINSZ, &ws);waitpid(pid, &status, 0);
// Returns WEXITSTATUS(status) for normal exit
// Returns -WTERMSIG(status) for signal killclose(fd);Purpose: Lock-free circular byte buffer for producer-consumer I/O between PTY threads.
Algorithm: Classic ring buffer with mHead (read cursor) and mStoredBytes (fill level). Uses synchronized + wait()/notify() for blocking. When buffer is full, write() blocks. When data is read from full buffer, notify() wakes writers.
Two instances per session:
mProcessToTerminalIOQueue— 64KB (PTY → emulator)mTerminalToProcessIOQueue— 4KB (keyboard → PTY)
Two-phase copy handles wraparound: If head is near end of array, copies in two parts: [head, end] then [0, remaining].
Loads libtermux.so via System.loadLibrary("termux"). Declares 4 native methods:
createSubprocess()— returns master fdsetPtyWindowSize()— sends TIOCSWINSZwaitFor()— waitpid wrapperclose()— close fd wrapper
Maps Android key codes + modifier bitmasks to terminal escape sequences.
Modifier constants:
KEYMOD_ALT = 0x80000000KEYMOD_CTRL = 0x40000000KEYMOD_SHIFT = 0x20000000KEYMOD_NUM_LOCK = 0x10000000
Main method getCode() is a giant switch over all Android KeyEvent.KEYCODE_* values. Handles:
- Arrow keys (application cursor mode
ESC Ovs normalESC [) - Function keys F1-F12
- Numpad with numlock dual behavior
- Insert/Delete/PageUp/PageDown
- Tab (back-tab when shifted)
- Enter (Alt+Enter prepends ESC)
- DEL (Ctrl+DEL → BS)
transformForModifiers() encodes modifier parameter per xterm spec:
- Shift=2, Alt=3, Shift+Alt=4, Ctrl=5, Ctrl+Shift=6, Ctrl+Alt=7, etc.
- Example: Ctrl+Right →
\033[1;5C
Termcap support: TERMCAP_TO_KEYCODE maps termcap key names to Android keycodes for DCS termcap/terminfo responses. Recent fix (commit 30ebb2de) corrected inverted kN/kP mappings for Page Up/Down.
Logging facade. If a TerminalSessionClient is available, delegates to it. Otherwise falls back to android.util.Log. Provides logError, logWarn, logInfo, logDebug, logVerbose, and stack trace formatting methods.
Purpose: Circular buffer of TerminalRow objects managing screen content and scrollback history.
Core data structure: mLines array of TerminalRow objects, indexed via circular mapping.
Circular buffer mapping:
internalRow = (mScreenFirstRow + externalRow + mTotalRows) % mTotalRowsExternal row 0 is the first visible screen row. Negative rows are scrollback history.
Key methods:
getTranscriptText()— returns full transcript as stringgetSelectedText()— extracts text from coordinate rangeresize()— handles column width changes (re-wraps all content)scrollDownOneLine()— shiftsmScreenFirstRow, incrementsmActiveTranscriptRowsblockCopy()/blockSet()— rectangular region operationssetChar()— sets single character in row
Resize algorithm:
- Fast path: only rows changed → copy lines, skip blanks
- Slow path: columns changed → reconstruct entire screen character by character, re-wrapping lines and relocating cursor
Manages the 259-color palette (256 indexed + foreground/background/cursor). Colors can be dynamically changed via OSC 4 (set color) and OSC 104 (reset colors) escape sequences.
Color parsing: Supports #RGB, #RRGGBB, rgb:R/G/B formats.
HSP brightness: getPerceivedBrightnessOfColor() calculates Human Short-term Perceptual brightness for cursor auto-color.
Defines the default 259-color palette. Xterm-compatible with brighter blue (#5555FF instead of #0000AA). Supports overriding colors from termux.properties via updateWith(Properties). Auto-selects cursor color (white/black) based on background brightness.
The heart of the project. A complete VT100/xterm terminal emulator engine that parses byte streams and interprets escape sequences.
Escape state machine: 24 states covering ESC, CSI, OSC, DCS, APC, and various intermediate states.
Key features:
- UTF-8 decoding with overlong validation
- VT100 line drawing character translation (G0/G1)
- SGR (Select Graphic Rendition) with 24-bit truecolor
- DECSET/DECRST with 17+ mode flags
- Mouse tracking (X10, button event, SGR protocols)
- Alternate screen buffer
- Scrolling regions (top/bottom/left/right margins)
- Insert/replace mode
- Tab stops
- Cursor styles (block/underline/bar)
- Bracketed paste mode
- Title stack (push/pop)
append(byte[], int) — Entry point. Feeds raw bytes from PTY.
processByte(byte) — UTF-8 state machine → processCodePoint().
processCodePoint(int) — Main dispatch. Checks for string terminators and control characters, then dispatches to appropriate handler based on mEscapeState.
doCsi(int) — CSI handler. 40+ cases including: cursor movement (CUU/CUD/CUF/CUB/CUP), erase (ED/EL), scroll (SU/SD), SGR, margins (DECSTBM/DECSLRM), device status report, mode set/reset.
selectGraphicRendition() — SGR parser. Handles: bold, dim, italic, underline, blink, inverse, invisible, strikethrough, protected, foreground/background colors (standard 8, 256-indexed, 24-bit truecolor).
emitCodePoint(int) — VT100 line drawing translation, autowrap handling, insert mode, character placement in TerminalRow.
Single row of terminal cells. Handles variable-width characters, combining characters, and wide (CJK) characters in a compact char[] + long[] style array.
mText — Variable-length char array storing the row's character data.
mStyle — Fixed-length long array (one per display column) storing bit-packed styles.
setChar() algorithm:
- Fast path: if no wide/surrogate chars, direct array write
- Handles combining characters (added to existing column)
- Handles replacing half of a wide character
- Uses
findStartOfColumn()for column→index mapping (handles wide chars and combining marks) - Shifts
mTextarray when char count changes - Grows array by
mColumnswhen needed
Top-level session object. Owns the subprocess, I/O threads, emulator, and bridges to the UI client.
Thread architecture:
- TermSessionInputReader — reads from PTY fd → writes to
mProcessToTerminalIOQueue→ sendsMSG_NEW_INPUT - TermSessionOutputWriter — reads from
mTerminalToProcessIOQueue→ writes to PTY fd - TermSessionWaiter — calls
JNI.waitFor(pid)→ sendsMSG_PROCESS_EXITED - Main thread — processes
MSG_NEW_INPUT(emulator append + screen update)
initializeEmulator() — Creates the emulator, opens the PTY via JNI.createSubprocess(), and starts the 3 background threads.
updateSize() — Called when terminal is resized. Calls JNI.setPtyWindowSize() + mEmulator.resize().
Bit-packed encoding of foreground color, background color, and effect flags into a 64-bit long.
Bit layout:
Bits 0-8: Effect flags (11 bits: bold, italic, underline, blink, inverse, invisible, strikethrough, protected, dim, truecolor_fg, truecolor_bg)
Bits 9-15: Unused
Bits 16-39: Background color (9-bit index or 24-bit truecolor)
Bits 40-63: Foreground color (9-bit index or 24-bit truecolor)
If color top byte is 0xff, it's a true color stored in lower 24 bits. Otherwise it's a 9-bit index.
Unicode character width calculation (0, 1, or 2 columns). Implements wcwidth(3) for Unicode 15.
Algorithm:
- Returns 0 for: null, zero-width characters (U+200B-U+200F, etc.)
- Returns 0 for control characters (< 32 or 0x7F-0x9F)
- Binary search in
ZERO_WIDTHtable for combining characters → 0 - Binary search in
WIDE_EASTASIANtable → 2 - Otherwise → 1
Data: 363 zero-width ranges, 123 wide East Asian ranges.
Wraps GestureDetector and ScaleGestureDetector into a single touch processor. Delegates scroll/fling/down/longPress/scale/tap events to a Listener interface. Used by TerminalView for touch input handling.
Purpose: Renders terminal content onto an Android Canvas.
render() method:
- Gets emulator state: reverse video, cursor position/shape, palette colors, screen buffer
- Draws reverse-video background if active
- Iterates rows from
topRowtotopRow + mRows - For each row, groups characters into runs sharing: same style, cursor state, selection state
- Handles surrogate pairs and wide characters via
WcWidth.width() - Calls
drawTextRun()for each completed run
drawTextRun() method:
- Decodes
TextStyleinto foreground/background colors and effect flags - Bold brightens foreground colors 0-7 by adding 8
- Applies reverse-video XOR
- If measured width ≠ expected width, applies canvas scaling (non-monospace font fix)
- Draws background rectangle (only if non-default)
- Draws cursor: block (full height, inverts text), underline (¼ height), bar (¾ width)
- Applies dim (2/3 RGB channels per libvte/xterm spec)
- Draws text via
canvas.drawTextRun()
The main Android View that displays terminal content.
Key responsibilities:
- Touch event handling → keyboard input
- Key event handling → escape sequence generation
- Mouse event handling → terminal mouse protocol
- Screen update rendering
- Text selection with drag handles
- Cursor blinking animation
- Pinch-to-zoom font size
- AutoFill support (API 26+)
- Soft keyboard management
- Scroll bar support
onKeyDown() (~170 lines):
Massive method handling: Ctrl+key combinations, Fn key mappings (WASD→arrows, etc.), virtual keys from ExtraKeysView, text selection mode keys, hardware keyboard shortcuts, Enter key behavior for finished sessions.
onCreateInputConnection():
Creates a BaseInputConnection that intercepts commitText() and finishComposingText() to send text to the terminal. Handles deleteSurroundingText() by sending KEYCODE_DEL events.
inputCodePoint() — Central input method. Filters through:
mClient.onCodePoint()— allows client to intercepthandleKeyCode()— checks if codepoint is a special key mappinghandleKeyCodeAction()— executes key actionmTermSession.writeCodePoint()— sends to terminal
Cursor blinking:
TerminalCursorBlinkerRunnable toggles mEmulator.setCursorBlinkState(), calls invalidate(), and re-posts itself via mTerminalCursorBlinkerHandler.postDelayed(this, mBlinkRate).
Text selection:
Delegates to TextSelectionCursorController which manages two TextSelectionHandleView instances as PopupWindows. Supports word expansion, auto-scroll when dragging past edges, and wide-character boundary detection.
Pure interface — callback contract between TerminalView and its host Activity. Splits into: configuration queries, input interception, state notifications, and logging.
Manages text selection within TerminalView. Handles:
- Initial selection via word boundary expansion
- Two drag handles (LEFT/RIGHT) as
PopupWindows - ActionMode with Copy/Paste/More menu
- Auto-scroll when dragging past top/bottom edge
- Wide-character-aware column clamping
- 300ms debounce in
hide()to prevent accidental dismissal
Individual selection handle as a PopupWindow. Handles:
- Drag tracking via
onTouchEvent()(DOWN/MOVE/UP) - Auto-flip (LEFT↔RIGHT) near terminal view edges (throttled to 50ms)
- Smooth repositioning via
PopupWindow.update() - Position validation against terminal bounds
Application entry point. onCreate() initializes:
- Crash handler via
TermuxCrashUtils.setDefaultCrashHandler() - Log configuration from SharedPreferences
TermuxBootstrappackage manager and variant from BuildConfigTermuxAppSharedProperties(loadstermux.properties)TermuxShellManager(session/task tracking singleton)- Night mode from properties
- Termux files directory accessibility check
TermuxAmSocketServer(local socket server foramcommand)TermuxShellEnvironmentconstants and cache- Writes environment to file
The main terminal activity. Extends AppCompatActivity, implements ServiceConnection.
Lifecycle:
onCreate(): Loads properties, sets theme, initializes views, starts+binds toTermuxService, sends "Termux opened" broadcastonServiceConnected(): If no sessions, runs bootstrap installer then creates first session. If sessions exist, restores stored session or creates from intentonStart()/onResume()/onStop(): Delegates to client classes, manages broadcast receiver registration
Key fields:
mTermuxService— bound service referencemTerminalView— the terminal displaymTermuxTerminalViewClient— input/key handlingmTermuxTerminalSessionActivityClient— UI callbacksmExtraKeysView/mTermuxTerminalExtraKeys— extra keysmTermuxSessionsListViewController— session list adaptermIsInvalidState— prevents running if setup fails
Context menu (long-press): Select URL, share transcript, share text, paste, reset terminal, kill process, change style, toggle keep screen on, help, settings, report issue.
Broadcast receiver: Handles ACTION_NOTIFY_APP_CRASH, ACTION_RELOAD_STYLE, ACTION_REQUEST_PERMISSIONS.
The foreground service that manages terminal sessions. Extends Service, implements AppShell.AppShellClient and TermuxSession.TermuxSessionClient.
Session management:
mShellManager.mTermuxSessions— foreground sessionsmShellManager.mTermuxTasks— background tasks- All session/task methods are
synchronized
Intent handling (onStartCommand):
ACTION_STOP_SERVICE— kills all commands, stops serviceACTION_WAKE_LOCK/ACTION_WAKE_UNLOCK— power managementACTION_SERVICE_EXECUTE— parses intent extras intoExecutionCommand, routes to session or task creation
Client swap pattern:
When activity binds: swaps all sessions to use TermuxTerminalSessionActivityClient (full UI).
When activity unbinds: swaps to TermuxTerminalSessionServiceClient (minimal fallback).
Notification: Shows session count, task count, wake lock status. Actions: exit service, toggle wake lock.
Foreground service: Returns START_NOT_STICKY. Must outlive the activity.
Handles bootstrap package installation.
setupBootstrapIfNeeded():
- Validates primary user
- Checks if prefix directory exists and is non-empty (skip if so)
- Shows ProgressDialog
- Spawns background thread:
- Deletes staging/prefix directories
- Creates them
- Extracts bootstrap ZIP from native library via
getZip()JNI - Processes
SYMLINKS.txt(creates symbolic links) - Renames staging to prefix
- Writes environment file
setupStorageSymlinks(): Creates symlinks in ~/storage/ for shared, documents, downloads, dcim, pictures, music, movies, podcasts, audiobooks, external-N, media-N.
Entry point for RUN_COMMAND intents from third-party apps. Validates the intent, builds an ExecutionCommand, and forwards to TermuxService via intent.
Validation pipeline:
- Action must be
ACTION_RUN_COMMAND - Runner must be valid (TERMINAL_SESSION or APP_SHELL)
allow-external-appspolicy check (forced notification on violation)- Executable must be set
- Canonical path resolution
- File permissions validation (read + execute)
- Working directory validation (if set)
- Symlink handling for applets (coreutils/busybox)
BroadcastReceiver + ContentProvider for file sharing.
BroadcastReceiver: Handles ACTION_VIEW (opens URLs) and ACTION_SEND (shares files/text via content URIs with FLAG_GRANT_READ_URI_PERMISSION).
ContentProvider: Serves files to external apps. Security checks:
- File must be under
TERMUX_FILES_DIRor external storage allow-external-appspolicy must be enabled- Property files are forced read-only
Singleton BroadcastReceiver for BOOT_COMPLETED and PACKAGE_ADDED/REMOVED/REPLACED.
Boot completed: Delegates to TermuxShellManager.onActionBootCompleted().
Package update: If the updated package is a Termux plugin, rewrites the environment file (which includes plugin paths).
Registered dynamically in TermuxService.onCreate(), unregistered in TermuxService.onDestroy().
Full UI client for terminal sessions. Handles:
- Session lifecycle (add/remove/switch/rename)
- Text changes and title updates
- Bell sound (vibrate, beep, or ignore)
- Font and color loading from properties
- Background color updates
- Session list notifications
- Max 8 sessions enforcement
- Auto-remove on exit 0 or 130
Input handling client. Handles:
- Volume keys as virtual Ctrl (volume down) and Fn (volume up)
- Fn key mappings: WASD→arrows, P/N→page, H→~, U→_, L→|, 1-9→F1-F9, E→Escape, V→volume
- Ctrl+Alt hardware shortcuts: N/P→sessions, arrows→drawer, K→keyboard, M→menu, etc.
- Session shortcuts loaded from
termux.properties - Soft keyboard management (enable/disable toggle and show/hide toggle modes)
- Cursor blinker state management
- Report generation from transcript
ArrayAdapter for the session list in the navigation drawer. Renders each session as [N] sessionName\nsessionTitle with bold for number+name, italic for title. Running sessions show normal text; exited sessions show strikethrough + red for non-zero exit status.
Custom LinearLayout implementing keyboard overlap detection. Uses a 1sp transparent "bottom space view" at the bottom of the layout. onGlobalLayout() compares its position to the window available rect — if hidden by keyboard, calculates pixel offset and adds bottom margin to push content up. Uses 40ms time-based throttling to prevent infinite layout loops.
Workaround for SOFT_INPUT_ADJUST_RESIZE not working in fullscreen. When keyboard appears (>25% height difference), manually resizes the content view to exclude keyboard + nav bar.
Handles extra key button clicks. Special keys: KEYBOARD (toggle keyboard), DRAWER (toggle nav drawer), PASTE (paste from clipboard), SCROLL (toggle auto-scroll). Falls back to default extra keys on JSON parse error.
2-page ViewPager at the bottom of the terminal:
- Page 0: Extra keys view (ExtraKeysView)
- Page 1: Text input (EditText that writes to terminal on enter)
Simple data class holding codePoint → shortcutAction mapping.
Transparent activity that handles shared files/URLs. Files are saved to ~/downloads/, user is prompted for filename, and ~/bin/termux-file-editor script is run. URLs are passed to ~/bin/termux-url-opener script.
Preferences UI. Loads RootPreferencesFragment which shows:
- Termux (navigates to sub-screens for debugging, I/O, view settings)
- Termux:API (hidden if not installed)
- Termux:Float (hidden if not installed)
- Termux:Tasker (hidden if not installed)
- Termux:Widget (hidden if not installed)
- About
- Donate (hidden for Google Play builds)
Embedded WebView for Termux wiki. Only loads URLs starting with the wiki URL inline; external URLs open in system browser.
All follow the same pattern: PreferenceFragmentCompat + singleton PreferenceDataStore subclass wrapping a specific *AppSharedPreferences class. Handle: log level, key logging, plugin error notifications, crash reports, soft keyboard settings, terminal margin adjustment.
Single source of truth for ALL shared constants. Defines:
- Organization URLs and F-Droid/GitHub/Play Store links
- Package names for all 7 Termux apps
- APK signing certificate SHA-256 digests (F-Droid, GitHub, Play, Termux Devs)
- Complete directory tree paths (
/data/data/com.termux/files/usr/bin, etc.) - Properties file paths (primary:
~/.termux/termux.properties, secondary:~/.config/termux/termux.properties) - Notification channel IDs and names
- Intent action and extra constants
RUN_COMMANDAPI extras- Nested classes for each app's constants
All constants are public static final. Path constants are built compositionally. Both String and File variants provided for directory paths.
Utility methods for:
- Package context retrieval (one method per plugin)
- App install/access checks
- APK reflection (reads BuildConfig fields via classloader)
- URI helpers
- Broadcast sending (Oreo+ workaround for explicit broadcasts)
- Markdown report generation (app info, plugin info, issue reports)
- APT info retrieval (runs embedded shell script)
- Debug logcat dump (runs
logcat -d -t 3000) - APK release identification (maps signing cert to release name)
Singleton managing session/task lists and counters.
Fields: mTermuxSessions (List), mTermuxTasks (List), mPendingPluginExecutionCommands, auto-incrementing SHELL_ID.
Counters: APP_SHELL_NUMBER_SINCE_APP_START, TERMINAL_SESSION_NUMBER_SINCE_APP_START — exported to shell environments. Reset on boot completed and app exit.
Builds the complete environment for shell sessions. Extends AndroidShellEnvironment with Termux-specific additions.
Environment construction:
- Android base environment
- Termux app environment overlay
- Termux API environment overlay
HOMEandPREFIXset- Non-failsafe:
TMPDIR,PATH(Termux$PREFIX/bin),LD_LIBRARY_PATH - Android 5 variant: includes
/appletssubdirectory
Failsafe mode: Preserves system PATH for /system/bin/sh fallback.
Custom GridLayout rendering configurable extra keys (ESC, CTRL, ALT, arrows, etc.).
Core rendering: reload(ExtraKeysInfo, float heightPx) — clears grid, iterates 2D button matrix, creates MaterialButton per key, sets click/touch listeners.
Touch handling: Active background color on press, long-press start/stop, popup show/dismiss on swipe up, click dispatch.
Key repeat: ScheduledExecutorService with scheduleWithFixedDelay for repetitive keys (arrows, space). Special buttons lock on long-press via Handler(Looper.getMainLooper()).
Special buttons: CTRL, ALT, SHIFT, FN — each has active/inactive/locked states.
Haptic feedback: Checks system haptic setting, handles DND mode pre-API 28.
Parses JSON configuration for extra keys into a 2D matrix of ExtraKeyButton objects.
JSON format: Array of arrays (rows of keys). Each key can be a string ("ESC") or object ({"key": "CTRL", "popup": {"macro": "CTRL f d", "display": "tmux exit"}}).
Display maps: Style-based alias resolution: "arrows-only", "arrows-all", "all", "none".
Thin singleton wrapper. Loads from first found file in TERMUX_PROPERTIES_FILE_PATHS_LIST (primary ~/.termux/termux.properties, fallback ~/.config/termux/termux.properties). Actual parsing/validation in parent TermuxSharedProperties.
Defines ALL property key names, value constants, bidirectional maps, defaults, and valid sets.
30+ properties including:
- Booleans:
allow-external-apps,hide-soft-keyboard-on-startup,fullscreen, etc. - Integers:
bell-character,terminal-cursor-blink-rate,terminal-cursor-style, etc. - Strings:
back-key,extra-keys,extra-keys-style,night-mode,volume-keys, etc. - Hardware key shortcuts:
shortcut.create-session,shortcut.next-session, etc.
Foreground interactive terminal sessions. Wraps TerminalSession with ExecutionCommand lifecycle.
Shell selection logic:
- Non-failsafe:
bash→zsh→shin$PREFIX/bin - Failsafe:
/system/bin/sh(mksh), NOT a login shell - Login shell name prefixed with
-(e.g.,-bash)
Static factory execute(): Resolves executable, sets up args, builds environment, creates TerminalSession, returns wrapped TermuxSession.
Background shell execution using Runtime.exec(). Supports synchronous and async execution with stdout/stderr capture via StreamGobbler threads.
Execution flow:
Runtime.getRuntime().exec(commandArray, environmentArray, workDir)- Creates
DataOutputStreamfor STDIN,StreamGobblerfor STDOUT/STDERR - Writes stdin (handles EPIPE gracefully)
process.waitFor()- Joins gobbler threads, destroys process
- Calls
processAppShellResult()
Plugin command result delivery. Two mechanisms:
- PendingIntent — result sent back to caller app via Bundle extras
- Directory — result written to files in specified directory
Also handles: error reporting, error notifications, external app policy checking.
Local filesystem socket server providing a fast alternative to Android's am command. Uses Unix domain sockets instead of starting a new dalvik VM per command.
Socket path: .../apps/com.termux/termux-am/am.sock
Security: Only termux user and root can connect.
Started by TermuxApplication. State exported to shell environments via TERMUX_APP__AM_SOCKET_SERVER_ENABLED.
1. Android creates TermuxApplication process
2. TermuxApplication.onCreate()
├── Sets crash handler
├── Configures logging
├── Sets TermuxBootstrap constants
├── Loads TermuxAppSharedProperties (termux.properties)
├── Initializes TermuxShellManager singleton
├── Sets night mode
├── Checks/files files directory accessibility
├── Starts TermuxAmSocketServer (if enabled)
├── Initializes TermuxShellEnvironment constants
└── Writes environment to file
1. User opens Termux or requests new session
2. TermuxActivity.onCreate()
├── Loads properties, sets theme
├── Initializes views (TerminalView, ExtraKeysView, Drawer)
├── Starts + binds to TermuxService
└── Sends "Termux opened" broadcast
3. TermuxActivity.onServiceConnected()
├── If no sessions exist:
│ ├── TermuxInstaller.setupBootstrapIfNeeded()
│ │ ├── Validates primary user
│ │ ├── Shows ProgressDialog
│ │ ├── Background: extracts bootstrap ZIP via JNI
│ │ ├── Processes SYMLINKS.txt
│ │ └── Writes environment file
│ └── Creates first session
└── If sessions exist:
└── Restores stored session or creates from intent
4. TermuxTerminalSessionActivityClient.addNewSession()
├── Enforces max 8 sessions
├── Gets working directory from current session
├── Calls TermuxService.createTermuxSession()
│ ├── TermuxSession.execute()
│ │ ├── Resolves shell (bash/zsh/sh)
│ │ ├── Sets up command args
│ │ ├── Builds environment
│ │ └── Creates TerminalSession (actual PTY)
│ ├── Adds to ShellManager.mTermuxSessions
│ ├── Notifies activity list
│ └── Updates notification
└── Sets as current session
PTY → Screen (reading):
PTY fd → TermSessionInputReader thread → mProcessToTerminalIOQueue
→ MSG_NEW_INPUT → Main thread → mEmulator.append()
→ processByte() → processCodePoint() → escape state machine
→ emitCodePoint() → mScreen.setChar() → TerminalRow.mText/mStyle
Keyboard → PTY (writing):
Key event → TerminalView.onKeyDown() → mTermSession.write()
→ mTerminalToProcessIOQueue → TermSessionOutputWriter thread → PTY fd
Raw bytes → TerminalEmulator.append()
→ processByte() — UTF-8 state machine
→ processCodePoint() — main dispatch
├── Control chars: BEL, BS, HT, LF, CR, ESC, etc.
├── ESC states: ESC, CSI, OSC, DCS, APC
├── CSI handler: cursor movement, erase, scroll, SGR, margins
└── SGR: bold, colors, effects → TextStyle encoding
termux.properties (disk)
→ TermuxAppSharedProperties (loaded at startup)
→ TermuxPropertyConstants (validated)
→ UI components read via getProperties()
→ TerminalView, ExtraKeysView, Session settings
Android base environment
→ TermuxAppShellEnvironment overlay
→ TermuxAPIShellEnvironment overlay
→ HOME, PREFIX, TMPDIR, PATH, LD_LIBRARY_PATH
→ Written to TERMUX_ENV_FILE_PATH (for sourcing)
→ Each TermuxSession/AppShell gets merged env
Third-party app → RUN_COMMAND intent → RunCommandService
→ Validates (action, runner, permissions, executable)
→ Builds ExecutionCommand
→ Forwards to TermuxService via intent
→ TermuxService.actionServiceExecute()
→ Parses into ExecutionCommand
→ Routes to:
├── executeTermuxSessionCommand() → TermuxSession.execute()
└── executeTermuxTaskCommand() → AppShell.execute()
→ After completion:
→ TermuxPluginUtils.processPluginExecutionCommandResult()
→ ResultSender → PendingIntent / File directory
TermuxService maintains two TerminalSessionClient implementations:
TermuxTerminalSessionActivityClient— full UI (when activity bound)TermuxTerminalSessionServiceClient— minimal fallback (when no activity)
When activity binds/unbinds, the service swaps clients on all active sessions. This ensures sessions survive activity destruction.
All PreferenceDataStore subclasses use synchronized static getInstance(). TermuxShellManager is a singleton. SystemEventReceiver is a singleton.
Both TermuxService and RunCommandService use inner LocalBinder classes for in-process binding without IPC. The binder directly exposes the service reference.
TerminalSessionClient and TerminalViewClient interfaces serve as observer callbacks from the terminal emulator back to the UI layer.
TermuxSession.execute() and AppShell.execute() are static factories that encapsulate the complex initialization logic.
TermuxActivityRootView— keyboard overlap via dynamic marginsFullScreenWorkAround— manual content view resize for fullscreen- Both address Android keyboard handling bugs that Google hasn't fixed.
- TerminalRow.mText — Variable-length
char[]with lazy allocation and 1.5× over-allocation factor - TextStyle — 64-bit packed encoding, one long per display column
- ByteQueue — Fixed-size ring buffers (64KB + 4KB)
- JNI — All strings
strdup()'d and freed viafree_string_array(). Critical section for PID write (zero-copy) - Bootstrap ZIP — Embedded in
.rodatasection of shared library via assembly.incbin
- JNI errors — Throw
RuntimeExceptionwith descriptive message - Bootstrap errors — Show dialog with retry/abort, send crash report notification
- Plugin errors — Log errors, optionally show notification, deliver error result via PendingIntent
- Socket errors —
TermuxAmSocketServershows error notification if server is running - Memory leaks — Fixed in commit 10b01ead: error paths in
createSubprocess()now properly freeargv/envp
The app requests 18 permissions, many of which are powerful:
READ_LOGS,DUMP— can read system logsWRITE_SECURE_SETTINGS— can modify system settingsMANAGE_EXTERNAL_STORAGE— full storage accessSYSTEM_ALERT_WINDOW— draw over other appsREQUEST_INSTALL_PACKAGES— install APKs
These are necessary for Termux's functionality as a terminal emulator but create a significant attack surface.
- Debug builds use a publicly shared test key with known passwords
- Release builds are unsigned (F-Droid signs its own)
- All Termux apps use
sharedUserId="com.termux"requiring identical signing keys
allow-external-appsproperty controls whether external apps can send commandsRunCommandServicevalidates executable existence and permissionsFileReceiverActivityvalidates file paths are under allowed directoriesTermuxOpenReceiver$ContentProviderforces read-only on property files
Bootstrap ZIPs are verified via SHA-256 checksums during download. The checksums are hardcoded in app/build.gradle.
- Shared test key — Anyone can sign APKs with the test key, potentially creating malicious "Termux" APKs
READ_LOGSpermission — Can read other apps' logs on older Android versionsWRITE_SECURE_SETTINGS— Can modify system settings- Native code — JNI layer handles PTY creation; buffer overflows in C code could be exploitable
allow-external-apps— When enabled, any app can execute commands in Termux
- TerminalRenderer groups characters into runs sharing the same style, minimizing Canvas draw calls
- TextStyle bit-packed into 64-bit longs for cache-friendly storage
- TerminalRow uses lazy allocation (
allocateFullLineIfNecessary()) for sparse rows - WcWidth uses binary search for character width lookup
- TerminalView.onDraw() only redraws visible rows
- ByteQueue uses lock-free-ish circular buffer with
wait()/notify()for minimal contention - PTY reader uses 64KB buffer for high throughput
- Keyboard writer uses 4KB buffer (sufficient for keystrokes)
- TerminalRow.mText uses 1.5× over-allocation to minimize array copies
- TerminalBuffer reuses
TerminalRowobjects during scroll (ring buffer) - JNI critical section for PID write (zero-copy access)
- Bootstrap ZIP is memory-mapped via shared library
.rodata
- Bootstrap installation is the main startup bottleneck (ZIP extraction on first run)
- Properties loading happens on main thread (could be moved to background)
- Environment file is written on every startup (atomic write pattern)
- Move properties loading to background thread — Currently blocks main thread during
TermuxApplication.onCreate() - Replace SharedPreferences with DataStore — Modern Android persistence library
- Add dependency injection — Replace manual singleton management with Hilt/Dagger
- Separate concerns in TermuxTerminalViewClient — 802 lines handling too many responsibilities
- Batch screen updates — Currently invalidates on every
MSG_NEW_INPUT; could batch updates withpostDelayed() - Reduce object allocation in TerminalRow.setChar() — Creates intermediate objects during wide character handling
- Cache WcWidth results — Binary search per character is expensive for large buffers
- Use RenderScript/GPU for rendering — TerminalRenderer uses Canvas which is CPU-based
- Remove
READ_LOGSpermission — Unnecessary on modern Android - Remove
WRITE_SECURE_SETTINGS— Consider alternatives - Add certificate pinning — For bootstrap download
- Implement app sandboxing — Restrict command execution scope
- Add SELinux policy — Additional containment layer
- Add Kotlin migration — Gradually convert Java to Kotlin
- Increase test coverage — Only 2 unit tests in app module
- Add integration tests — Test PTY creation, escape sequence parsing
- Document public APIs — Many public methods lack documentation
- Remove dead code — Some commented-out resources and unused methods
- Add Material 3 motion — Transitions between activities (M3 theme/colors/typography are complete; motion transitions remain)
- Add haptic feedback for special keys — Currently only for long-press
- Add keyboard shortcuts help — Document all Fn/Ctrl+Alt mappings
- Add session search — Find text in session list
- Add themes/presets — Pre-configured color schemes
The following improvements have been implemented as part of the Material 3 migration:
- ✅ Dynamic Color support on Android 12+
- ✅ MaterialAlertDialogBuilder for all dialogs
- ✅ M3 typography scale in XML layouts
- ✅ Theme-resolved colors (no hardcoded Java colors)
- ✅ Night mode variants for markdown backgrounds
- ✅ MaterialToolbar imports in Java
- ✅ M3 text selection handle colors
- ✅ ViewPager2 (replaced deprecated ViewPager)
- ✅ RecyclerView (replaced deprecated ListView)
- ✅ Cleaned up hardcoded drawable tints
- ✅ Critical BiDi char-vs-column mismatch (column-based processing)
- ✅ BiDi L1 rule fix (trailing whitespace reset)
- ✅ BiDi Arabic/Syriac/Thaana combining mark classification
- ✅ BiDi override handling on stack pop
- ✅ TerminalRow.copyInterval() BiDi cache invalidation
- ✅ TerminalView.getCursorY() hardcoded pixel value
- ✅ TermuxSessionsListViewController getAdapterPosition() crash
- ✅ TermuxActivity RECEIVER_NOT_EXPORTED for API 34+
- ✅ TextSelectionCursorController font ascent offset
- ✅ Dead TermuxAlertDialogStyle removed
- ✅ CI AlertDialog type mismatches
- Date: 2026-07-22
- Files:
app/src/main/java/com/termux/app/TermuxApplication.java(+4, -0) - Change: Added
DynamicColors.applyToActivitiesIfAvailable(this)inTermuxApplication.onCreate()after night mode setup. Enables wallpaper-based theming on Android 12+ with graceful fallback on older devices. - Why: Completes M3 dynamic color support as part of the comprehensive Material 3 migration.
- Date: 2026-07-22
- Files: 7 files (+16, -12)
- Change: Replaced
android.app.AlertDialog.Builderandandroidx.appcompat.app.AlertDialog.Builderwithcom.google.android.material.dialog.MaterialAlertDialogBuilderacross all dialog creation sites. - Files: TermuxActivity, TermuxInstaller, TermuxTerminalViewClient, TermuxTerminalSessionActivityClient, MessageDialogUtils, TextInputDialogUtils
- Why: MaterialAlertDialogBuilder is the recommended M3 dialog builder with proper theming.
- Date: 2026-07-22
- Files: 6 files (+16, -12)
- Change: Replaced hardcoded
Color.RED,Color.BLACK, and0xFF607D8Bwith dynamically resolved theme attributes (?attr/colorError,?attr/colorPrimary,?android:attr/textColorPrimary). - Why: Ensures proper dark mode support and M3 color system compliance.
- Date: 2026-07-22
- Files: 4 files (+5, -5)
- Change: Replaced framework text appearances (
?android:attr/textAppearanceLarge,?android:attr/textAppearanceMedium) with M3 typography scale (TextAppearance.Material3.BodyLarge,TextAppearance.Material3.BodyMedium). Replaced?android:attr/textColorPrimarywith?attr/colorOnSurface. - Why: Consistent M3 typography across all text elements.
Commit: 261055c8 — feat(ui): replace hardcoded Material 2 blue with M3 primary in text selection handles
- Date: 2026-07-22
- Files: 2 files (+2, -2)
- Change: Replaced
#2196F3(Material 2 Blue 500) with?attr/colorPrimaryin text selection handle drawables. - Why: M3 color system compliance.
- Date: 2026-07-22
- Files: 1 new file (+6)
- Change: Created
termux-shared/src/main/res/values-night/colors.xmlwith semi-transparent white variants for markdown code backgrounds. Previous semi-transparent black was nearly invisible on dark backgrounds. - Why: Dark mode compatibility for markdown rendering.
- Date: 2026-07-22
- Files: 3 files (+8, -8)
- Change: Replaced
androidx.appcompat.widget.Toolbarimports withcom.google.android.material.appbar.MaterialToolbarin AppCompatActivityUtils, TextIOActivity, and ReportActivity. - Why: Consistency with XML layouts that already use MaterialToolbar.
- Date: 2026-07-22
- Files: 1 file (+1, -1)
- Change: Removed
android:tint="#FF000000"fromic_settings.xml. Layout already overrides with?attr/termuxActivityDrawerImageTint. - Why: Remove hardcoded color that would be black on dark backgrounds.
- Date: 2026-07-22
- Files: 4 files (+51, -34)
- Change: Replaced deprecated
androidx.viewpager.widget.ViewPagerwithandroidx.viewpager2.widget.ViewPager2for the terminal toolbar. RewroteTerminalToolbarViewPager.PageAdapterfromPagerAdaptertoRecyclerView.Adapterwith ViewHolder pattern. - Why: ViewPager2 is the modern replacement with better performance and lifecycle support.
- Date: 2026-07-22
- Files: 4 files (+43, -34)
- Change: Replaced
android.widget.ListViewwithandroidx.recyclerview.widget.RecyclerViewfor the terminal sessions list. RewroteTermuxSessionsListViewControllerfromArrayAdaptertoRecyclerView.AdapterwithSessionViewHolderpattern. - Why: RecyclerView is the modern replacement with better performance and animation support.
- Date: 2026-07-22
- Change: Updated Project_explain.md with Git History Analysis, Architectural Milestones, and Possible Improvements sections. Documented all M3 migration commits and the RTL/BiDi implementation.
- Date: 2026-07-22
- Files: 5 new/modified files
- Change: Full UAX #9 BiDi algorithm for right-to-left text support (Arabic, Hebrew). Creates
BidiReorderer.java, adds BiDi cache toTerminalRow.java, integrates visual reordering intoTerminalRenderer.java, adds visual-to-logical translation for touch input inTerminalView.javaandTextSelectionCursorController.java. - Why: Enables correct rendering and interaction with RTL text in terminal output.
- Date: 2026-07-22
- Files: 4 files
- Change: Fixed
android.app.AlertDialog→androidx.appcompat.app.AlertDialogtype mismatches in MessageDialogUtils, TextInputDialogUtils, TermuxNotificationUtils, and TermuxTerminalViewClient.
- Date: 2026-07-22
- Files:
terminal-emulator/src/main/java/com/termux/terminal/BidiReorderer.java - Change: Fixed 5 instances of implicit int-to-byte conversion that caused compiler errors in CI.
- Date: 2026-07-22
- Files: 7 files (+317, -471)
- Change: Major bug fix batch:
- BidiReorderer rewrite: Changed from char-based to column-based processing to prevent
ArrayIndexOutOfBoundsExceptionwith CJK wide characters. Each terminal column now has one code point fed to the algorithm. - L1 rule fix: Now passes
origTypestoreorder()for proper trailing whitespace reset instead of comparing levels against character type constants. - Arabic combining mark classification: Fixed catch-all AL ranges overriding specific NSM ranges by reordering the if-else chain.
- Syriac/Thaana combining marks: Fixed misclassification as AL by catch-all ranges.
- TerminalRow.copyInterval(): Added
invalidateBiDiMapping()after copying content. - TerminalRow.getVisualToLogicalMapping(): Rewritten to convert
char[]to column-levelcodePoints+widthsfor the new BidiReorderer API. - TerminalView.getCursorY(): Replaced hardcoded
40withmRenderer.mFontLineSpacingAndAscentfor accurate touch coordinates. - TermuxSessionsListViewController: Added
NO_POSITIONbounds checks forgetAdapterPosition()to preventIndexOutOfBoundsException. - TermuxActivity: Added
RECEIVER_NOT_EXPORTEDflag forregisterReceiver()on API 34+ (Android 14 security requirement). - TextSelectionCursorController.onGetContentRect(): Added font ascent offset to y-coordinates for proper toolbar positioning.
- styles.xml: Removed dead
TermuxAlertDialogStyle(defined but never referenced).
- BidiReorderer rewrite: Changed from char-based to column-based processing to prevent
- Why: Critical correctness and stability fixes across BiDi rendering, touch handling, and API compatibility.
- Author: Y-0x
- Date: 2026-07-21 17:56:17 UTC
- Files:
termux-shared/src/main/res/layout/partial_primary_toolbar.xml(+2, -3) - Change: Replaced
androidx.appcompat.widget.Toolbarwithcom.google.android.material.appbar.MaterialToolbar. Removedandroid:theme="@style/ThemeOverlay.Material3.Toolbar"(no longer needed with MaterialToolbar). - Why: Part of the Material 3 migration. MaterialToolbar is the recommended M3 toolbar widget with built-in M3 theming.
- Impact: Cosmetic only — proper M3 toolbar rendering.
- Relationship: Follow-up to commit 56acb262 (Material 3 migration).
Commit 2: e7d737ae — fix(ci): correct Material3 style references and gradle wrapper validation action
- Author: Y-0x
- Date: 2026-07-21 17:39:48 UTC
- Files: 3 files (+4, -4)
.github/workflows/gradle-wrapper-validation.yml—@5→@v6termux-shared/src/main/res/layout/dialog_show_message.xml—Title.Large→TitleLargetermux-shared/src/main/res/values/styles.xml— Same fix for title/subtitle
- Why: Material 3 style names don't use dots (e.g.,
TextAppearance.Material3.TitleLargenotTitle.Large). The CI action reference also needed updating. - Impact: Fixes build-breaking M3 style references.
- Relationship: Follow-up to commit 56acb262 (Material 3 migration).
- Author: Y-0x
- Date: 2026-07-21 17:29:26 UTC
- Files: 2 files (+133, +3)
.github/workflows/release.yml— New 130-line workflow.gitignore— Addeddevnotes.md
- Change: Adds a complete CI/CD pipeline that:
- Auto-increments patch version from latest git tag
- Builds debug APKs for both bootstrap variants
- Validates all 5 ABI APKs exist
- Generates SHA-256 checksums
- Creates GitHub release with auto-generated notes
- Why: Automates the release process that was previously manual.
- Impact: Enables automated releases on push to master.
- Relationship: New feature — no predecessor.
- Author: Y-0x
- Date: 2026-07-21 06:28:08 UTC
- Files: 22 files (+237, -289)
- Change: Major UI migration:
- Theme:
Theme.MaterialComponents→Theme.Material3with full M3 color token set - Colors: Replaced hardcoded colors with M3 theme attributes (
?attr/colorSurface,?attr/colorPrimary, etc.) - Styles: Updated to M3 text appearances and button styles
- Drawables: Removed 5 redundant light/dark drawable files, unified to theme attributes
- Java: Removed manual dark theme checking in
TermuxSessionsListViewController - Dialogs: Switched from
Theme_AppCompat_Light_Dialogto M3materialAlertDialogTheme
- Theme:
- Why: Modernize UI to Material 3 (Material You) design system with dynamic color support.
- Impact: Significant visual update. Terminal background stays black. Drawer and extra keys use M3 surface colors.
- Relationship: Largest single commit in the history. Foundation for commits 1 and 2.
- Author: Y-0x
- Date: 2026-07-21 06:27:42 UTC
- Files:
termux-shared/src/main/java/com/termux/shared/termux/extrakeys/ExtraKeysView.java(+1, -1) - Change: Fixed
if (mLongPressRepeatDelay >= ...)toif (longPressRepeatDelay >= ...). - Why: The validation was checking the current field value instead of the new parameter. This inverted the logic: valid new values were rejected if the current value was out of range, and invalid values were accepted if the current value was in range.
- Impact: Bug fix — extra key repeat delay now correctly validated.
- Relationship: Independent bug fix.
Commit 6: 10b01ead — fix(jni): correct ReleaseStringUTFChars argument and fix memory leaks on error paths
- Author: Y-0x
- Date: 2026-07-21 06:27:29 UTC
- Files:
terminal-emulator/src/main/jni/termux.c(+24, -12) - Changes:
- Fixed
ReleaseStringUTFChars(env, cmd, cmd_cwd)→ReleaseStringUTFChars(env, cwd, cmd_cwd)— was passing wrong JNI string reference - Added
free_string_array()helper for clean C memory management - Error paths in
createSubprocess()now properly freeargvandenvpbefore returning
- Fixed
- Why: Two bugs: (a) wrong argument caused undefined behavior per JNI spec, (b) memory leaks on allocation failure paths.
- Impact: Critical bug fixes — undefined behavior and memory leaks in the core PTY creation path.
- Relationship: Independent bug fixes.
- Author: agnostic-apollo
- Date: 2026-07-15 01:47:01 UTC
- Files:
README.md(-5 lines) - Change: Removed Warp sponsor logo and link from README.
- Why: Sponsorship ended after 3 months.
- Impact: Documentation only.
- Relationship: Reverts a previous sponsorship addition.
- Author: agnostic-apollo
- Date: 2026-05-13 02:57:05 UTC
- Files:
termux-shared/src/main/java/com/termux/shared/notification/NotificationUtils.java(+3, -1) - Change: Added null check:
if (notificationBigText != null) { builder.setStyle(new Notification.BigTextStyle().bigText(notificationBigText)); } - Why:
BigTextStylewith null text causedNullPointerExceptionon Android 6. The stack trace showsNotification$Builder.processLegacyText()calling.toString()on null. - Impact: Crash fix for Android 6 devices when notification big text is null.
- Relationship: Independent bug fix. Closes termux#5113.
Commit 9: 30ebb2de — Fixed: Fix inverted typo in termcap values for KEYCODE_PAGE_UP and KEYCODE_PAGE_DOWN
- Author: agnostic-apollo
- Date: 2026-04-07 04:11:27 UTC
- Files:
terminal-emulator/src/main/java/com/termux/terminal/KeyHandler.java(+2, -2) - Change: Swapped
kN→KEYCODE_PAGE_UPtokP→KEYCODE_PAGE_UPand vice versa. - Why: The termcap names were inverted:
kNis "key Next" (Page Down),kPis "key Previous" (Page Up). The code had them backwards. - Impact: Fixed Page Up/Down key mapping for programs using termcap/terminfo (e.g., vim, less).
- Relationship: Independent bug fix. Closes #5052.
- Author: Fredrik Fornwall
- Date: 2026-02-21 11:37:36 UTC
- Files:
app/src/main/AndroidManifest.xml(-1 line) - Change: Removed
android:extractNativeLibs="true"from the manifest. - Why: The attribute is deprecated when
useLegacyPackaging = trueis set inapp/build.gradle(which it is). AGP warns about this during builds. - Impact: Removes deprecation warning. No functional change since
useLegacyPackagingalready controls this behavior. - Relationship: Independent cleanup.
The 10 most recent commits show three distinct development threads:
- 56acb262 — Major migration (22 files, +237/-289) — theme, colors, styles, drawables, dialogs
- e7d737ae — Fix broken M3 style references (3 files)
- 7f77c3da — Finalize toolbar migration (1 file)
- 0c730379 — Add Dynamic Color support (1 file)
- aa0c173c — Migrate AlertDialog.Builder → MaterialAlertDialogBuilder (7 files)
- 34b8d7a0 — Replace hardcoded Java colors with theme attributes (6 files)
- 18813e34 — Migrate XML layouts to M3 typography (4 files)
- 261055c8 — Replace hardcoded M2 blue in text selection handles (2 files)
- dfce2a9b — Add night mode variants for markdown code backgrounds (1 file)
- fc358800 — Update Java Toolbar imports to MaterialToolbar (3 files)
- 12b5f129 — Remove hardcoded black tint from ic_settings drawable (1 file)
- 9b7a85ad — Migrate ViewPager → ViewPager2 (4 files)
- 9bef0bce — Migrate ListView → RecyclerView for session list (4 files)
The Material 3 migration is now complete. The project uses:
Theme.Material3.Light.NoActionBar/Theme.Material3.Dark.NoActionBaras base themes- Full M3 color token system (primary, secondary, tertiary, error, surface hierarchy)
- Dynamic Colors on Android 12+ with fallback to static M3 scheme
- MaterialToolbar, MaterialButton, MaterialTextView, MaterialAlertDialogBuilder
- M3 typography scale (TextAppearance.Material3.*)
- ViewPager2 and RecyclerView as modern widget replacements
- Proper dark mode support with night-mode color variants
- a11af0ab — New release workflow with auto-versioning
This adds automated release pipeline that was previously manual or handled by external processes.
- 3fc8252c — ExtraKeysView validation bug
- 10b01ead — JNI memory leaks and wrong argument
- 401bbe54 — Notification NPE on Android 6
- 30ebb2de — Termcap key mapping inversion
- 3f0dec35 — Deprecated manifest attribute
- 30b668d2 — CI AlertDialog type mismatches
- b34b3b41 — BiDi int-to-byte lossy conversions
- 733d3219 — Critical BiDi + codebase bug fixes (10 bugs fixed)
These span 5 months (Feb-Jul 2026) and address issues across different layers: UI, JNI, notifications, key handling, build configuration, BiDi rendering, touch handling, and API compatibility.
-
Terminal emulator engine — The core VT100 emulator (
TerminalEmulator.java, 2617 lines) is mature and stable. Recent changes are bug fixes, not feature additions. -
Material 3 migration — The most significant recent architectural change, modernizing the entire UI layer. Now complete with Dynamic Color support, M3 typography, MaterialAlertDialogBuilder, ViewPager2, and RecyclerView.
-
RTL/BiDi support — Full Unicode Bidirectional Algorithm (UAX #9) implementation enabling correct rendering and interaction with Arabic, Hebrew, and other RTL text. Uses column-based processing with visual-to-logical mapping for accurate touch handling.
-
Bootstrap system — The native ZIP embedding approach (assembly
.incbin) is a unique and efficient solution for providing the initial Linux environment. -
Plugin architecture — The shared library (
termux-shared) enables 6 companion apps to share code, constants, and UI components.
-
targetSdkVersion lock — Stuck at 28 due to Google's native executable restrictions. May eventually become impossible to update as Google Play enforces higher minimums.
-
Java-only codebase — No Kotlin adoption yet. May face developer onboarding challenges as Android development increasingly assumes Kotlin.
-
Large monolithic files —
TerminalEmulator.java(2617 lines),TermuxActivity.java(1013 lines),TermuxService.java(959 lines),TerminalView.java(1500 lines) are all very large files that are difficult to maintain. -
Limited test coverage — Only 2 unit tests in the app module, 18 in terminal-emulator, 0 in terminal-view and termux-shared.
-
Dependency on JitPack — Three library modules are published to JitPack for plugin consumption. JitPack availability is a single point of failure for the plugin ecosystem.
This is a mature, well-architected Android application that has evolved over many years. The codebase demonstrates deep understanding of Android internals, terminal emulation, and Unix system programming.
-
Robust terminal emulation — The VT100/xterm emulator is comprehensive, handling UTF-8, wide characters, 24-bit color, mouse tracking, and dozens of escape sequences.
-
Elegant native layer — The PTY management in
termux.cis clean and correct. The bootstrap ZIP embedding via assembly.incbinis clever and efficient. -
Clean module separation — The four-module architecture (
terminal-emulator→terminal-view→termux-shared→app) provides good separation of concerns and enables library reuse by plugins. -
Comprehensive constant management —
TermuxConstants.javaprovides a single source of truth for all shared values, preventing inconsistencies across the ecosystem. -
Active maintenance — Recent commits show attention to detail (JNI memory leaks, null checks, deprecated APIs) and modernization (Material 3 migration).
-
Large files — Several files exceed 1000 lines (
TerminalEmulator: 2617,TermuxConstants: 1338,TermuxActivity: 1013,TermuxService: 959,TerminalView: 1500). These should be decomposed. -
Limited testing — The app module has only 2 unit tests. The terminal-emulator has 18 tests but no integration tests. Terminal-view and termux-shared have no tests.
-
Java-only — No Kotlin adoption. The codebase uses Java 1.8 compatibility with desugaring, which limits access to modern language features.
-
Hardcoded values — Some magic numbers and hardcoded colors remain despite the Material 3 migration.
-
Documentation — Public APIs are inconsistently documented. Some classes have extensive Javadoc, others have none.
| Category | Score | Notes |
|---|---|---|
| Architecture | 9/10 | Clean layered design, good separation |
| Code Organization | 8/10 | Well-structured packages, some large files |
| Testing | 3/10 | Very limited test coverage |
| Documentation | 6/10 | Inconsistent Javadoc, good README |
| Security | 7/10 | Good practices, but powerful permissions |
| Performance | 8/10 | Efficient rendering, good I/O design |
| Maintainability | 7/10 | Large files hinder modification |
| Overall | 7/10 | Mature, well-built project with room for improvement |