Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Contributing to Termux

## Shared Library

The [termux-shared](termux-shared) library (introduced in v0.109) defines shared constants and utilities for the Termux app and its plugins. It exists to eliminate hardcoded paths.

**Rules:**
- Never use hardcoded values. Use `TermuxConstants` from `termux-shared`
- Shared classes go under `com.termux.shared.termux` (Termux-specific) or the base package (general)
- Check and update the [termux-shared LICENSE](termux-shared/LICENSE.md) when contributing

Key classes:
- [`TermuxConstants`](https://github.com/termux/termux-app/blob/master/termux-shared/src/main/java/com/termux/shared/termux/TermuxConstants.java) — main constants, package name docs, forking info

See [Termux Libraries](https://github.com/termux/termux-app/wiki/Termux-Libraries) for importing in plugin apps.

## Versioning

`versionName` in `build.gradle` must follow [SemVer 2.0.0](https://semver.org/spec/v2.0.0.html): `major.minor.patch(-prerelease)(+buildmetadata)`.

Always include the patch number in tags (e.g., `v0.1.0`, not `v0.1`). The build workflow validates this.

## Commit Messages

Commits **must** follow the [Conventional Commits](https://www.conventionalcommits.org) spec:

```
<type>[optional scope]: <description>

[optional body]
```

Rules:
- First letter of `type` and `description` must be capitalized
- Description in present tense
- Space after `:` is required
- For breaking changes, add `!` before `:`

**Allowed types:**

| Type | Meaning |
|------|---------|
| Added | New features |
| Changed | Changes to existing functionality |
| Deprecated | Soon-to-be-removed features |
| Removed | Removed features |
| Fixed | Bug fixes |
| Security | Vulnerability fixes |

Examples:
- `Added: Add floating terminal support`
- `Fixed(terminal): Fix paste crash with ESC characters`
- `Changed!: Remove sharedUserId (breaking change)`

## Forking

1. Check [`TermuxConstants`](https://github.com/termux/termux-app/blob/master/termux-shared/src/main/java/com/termux/shared/termux/TermuxConstants.java) javadocs for package name changes
2. Recompile the bootstrap zip for the new package name ([Building Packages](https://github.com/termux/termux-packages/wiki/Building-packages))
3. Some plugins still have hardcoded `com.termux` values — patch them manually
4. See [Forking and Local Development](https://github.com/termux/termux-app/wiki/Termux-Libraries#forking-and-local-development) for plugin library setup

## Sponsors and Funders

Termux is supported by:

- [GitHub Accelerator](https://github.com/accelerator)
- [GitHub Secure Open Source Fund](https://resources.github.com/github-secure-open-source-fund)
- [NLnet NGI Mobifree](https://nlnet.nl/mobifree)
- [Cloudflare](https://www.cloudflare.com)
292 changes: 32 additions & 260 deletions README.md

Large diffs are not rendered by default.

15 changes: 15 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,21 @@
android:name=".app.TermuxOpenReceiver"
android:exported="false" />

<activity
android:name=".app.TermuxSchemeOpenerActivity"
android:exported="true"
android:excludeFromRecents="true"
android:noHistory="true"
android:theme="@android:style/Theme.Translucent.NoTitleBar"
tools:targetApi="n">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="termux" />
</intent-filter>
</activity>

<receiver
android:name=".app.event.SystemEventReceiver"
android:exported="false">
Expand Down
29 changes: 27 additions & 2 deletions app/src/main/java/com/termux/app/TermuxOpenReceiver.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import com.termux.shared.logger.Logger;
import com.termux.shared.net.uri.UriScheme;
import com.termux.shared.termux.TermuxConstants;
import com.termux.shared.termux.TermuxConstants.TERMUX_APP.TERMUX_SERVICE;

import java.io.File;
import java.io.FileNotFoundException;
Expand Down Expand Up @@ -56,6 +57,10 @@ public void onReceive(Context context, Intent intent) {
}

String scheme = data.getScheme();
if (scheme != null && "termux".equalsIgnoreCase(scheme)) {
handleTermuxScheme(context, data);
return;
}
if (scheme != null && !UriScheme.SCHEME_FILE.equals(scheme)) {
Intent urlIntent = new Intent(intentAction, data);
if (intentAction.equals(Intent.ACTION_SEND)) {
Expand All @@ -81,8 +86,8 @@ public void onReceive(Context context, Intent intent) {
}

final File fileToShare = new File(filePath);
if (!(fileToShare.isFile() && fileToShare.canRead())) {
Logger.logError(LOG_TAG, "Not a readable file: '" + fileToShare.getAbsolutePath() + "'");
if (!fileToShare.isFile()) {
Logger.logError(LOG_TAG, "Not a file: '" + fileToShare.getAbsolutePath() + "'");
return;
}

Expand Down Expand Up @@ -128,6 +133,26 @@ public void onReceive(Context context, Intent intent) {
}
}

private void handleTermuxScheme(Context context, Uri uri) {
final String scriptPath = TermuxConstants.TERMUX_HOME_DIR_PATH + "/bin/termux-scheme-opener";
final File scriptFile = new File(scriptPath);
if (!scriptFile.isFile()) {
Logger.logWarn(LOG_TAG, "termux: scheme received but script not found: " + scriptPath);
return;
}
try {
scriptFile.setExecutable(true);
Intent executeIntent = new Intent(TERMUX_SERVICE.ACTION_SERVICE_EXECUTE,
UriUtils.getFileUri(scriptPath));
executeIntent.setClass(context, TermuxService.class);
executeIntent.putExtra(TERMUX_SERVICE.EXTRA_ARGUMENTS,
new String[]{uri.toString()});
context.startService(executeIntent);
} catch (Exception e) {
Logger.logError(LOG_TAG, "Failed to run termux-scheme-opener for " + uri, e);
}
}

public static class ContentProvider extends android.content.ContentProvider {

private static final String LOG_TAG = "TermuxContentProvider";
Expand Down
40 changes: 40 additions & 0 deletions app/src/main/java/com/termux/app/TermuxSchemeOpenerActivity.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.termux.app;

import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;

/**
* Trampoline activity that handles {@code termux:} URI schemes by forwarding
* to {@link TermuxOpenReceiver} which runs the scheme-opener script. The activity
* is immediately finished after forwarding the intent. (#3945)
*/
public class TermuxSchemeOpenerActivity extends android.app.Activity {

private static final String LOG_TAG = "TermuxSchemeOpener";

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

Intent intent = getIntent();
Uri data = intent.getData();
if (data == null) {
android.util.Log.e(LOG_TAG, "Called without intent data");
finish();
return;
}

android.util.Log.d(LOG_TAG, "termux: URI received: " + data);

Intent receiverIntent = new Intent(this, TermuxOpenReceiver.class);
receiverIntent.setData(data);
receiverIntent.setAction(intent.getAction());
if (intent.getExtras() != null) {
receiverIntent.putExtras(intent.getExtras());
}
sendBroadcast(receiverIntent);

finish();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -221,8 +221,11 @@ void promptNameAndSave(final InputStream in, final String attachmentFileName) {
runOnUiThread(() -> {
final File editorProgramFile = new File(EDITOR_PROGRAM);
if (!editorProgramFile.isFile()) {
showErrorDialogAndQuit("The following file does not exist:\n$HOME/bin/termux-file-editor\n\n"
+ "Create this file as a script or a symlink - it will be called with the received file as only argument.");
// No termux-file-editor script found - just save the file to
// downloads and inform the user instead of failing. (#4957)
showErrorDialogAndQuit("File saved to " + outFile.getAbsolutePath()
+ "\n\nNo termux-file-editor script found at $HOME/bin/termux-file-editor."
+ "\nCreate this file as a script or a symlink to enable in-app editing.");
return;
}
// Do this for the user if necessary:
Expand Down
61 changes: 57 additions & 4 deletions docs/en/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,63 @@ page_ref: /docs/apps/termux/index.html

# Termux App Docs

<!--- DOC_HEADER_PLACEHOLDER -->
Welcome to the Termux app documentation.

Welcome to documentation for the [Termux App].
## Android 12+ Compatibility

##
Termux may be unstable on Android 12 and higher. The OS aggressively terminates "phantom processes" (limit of 32 system-wide) and processes using excessive CPU. This can cause sessions to end with `[Process completed (signal 9) — press Enter]`.

[Termux App]: https://github.com/termux/termux-app
- Related: [issue #2366](https://github.com/termux/termux-app/issues/2366), [Android issue tracker](https://issuetracker.google.com/u/1/issues/205156966)
- A proper docs page is planned. Android 12L/13 may provide options to disable this behavior.

## Debugging

### Log Levels

Set the log level in Termux app settings → Debugging → Log Level (requires v0.118.0+).

| Level | Description |
|-------|-------------|
| Off | No logging |
| Normal | Errors, warnings, info, stacktraces |
| Debug | Debug-level messages |
| Verbose | All information (may include private data) |

Revert to `Normal` after debugging. Verbose mode may expose private data to logcat and increases execution time.

Plugin apps send execution intents to the main Termux app. Set log levels for **both** the plugin and the main app to get full info.

### Viewing Logs

- **In Termux:** `logcat` for realtime output, or `logcat -d > logcat.txt` for a dump
- **From PC:** Use `logcat` via ADB. See the [Android logcat guide](https://developer.android.com/studio/command-line/logcat)

### Reporting Issues

Long-hold the terminal → **More** → **Report Issue** to generate a debug report with file stats and logcat dump.

- Post the **complete report as text** when filing issues
- Issues with screenshots of reports instead of text will be closed
- If the report is too large, use **Save To File** (3-dot menu) to share it

## Important Links

### Community

- [Reddit](https://reddit.com/r/termux)
- [Matrix / Gitter (users)](https://matrix.to/#/#termux_termux:gitter.im)
- [Matrix / Gitter (dev)](https://matrix.to/#/#termux_dev:gitter.im)
- [X (Twitter)](https://twitter.com/termuxdevs)
- [Support email](mailto:support@termux.dev)

### Wikis

- [Termux Wiki](https://wiki.termux.com/wiki/)
- [App Wiki](https://github.com/termux/termux-app/wiki)
- [Packages Wiki](https://github.com/termux/termux-packages/wiki)

### Reference

- [XTerm control sequences](https://invisible-island.net/xterm/ctlseqs/ctlseqs.html)
- [vt100.net](https://vt100.net/)
- [Terminal codes (ANSI/terminfo)](https://wiki.bash-hackers.org/scripting/terminalcodes)
6 changes: 2 additions & 4 deletions terminal-view/src/main/java/com/termux/view/TerminalView.java
Original file line number Diff line number Diff line change
Expand Up @@ -660,10 +660,8 @@ public boolean onKeyPreIme(int keyCode, KeyEvent event) {
return onKeyUp(keyCode, event);
}
}
} else if (mClient.shouldUseCtrlSpaceWorkaround() &&
keyCode == KeyEvent.KEYCODE_SPACE && event.isCtrlPressed()) {
/* ctrl+space does not work on some ROMs without this workaround.
However, this breaks it on devices where it works out of the box. */
} else if (keyCode == KeyEvent.KEYCODE_SPACE && event.isCtrlPressed()) {
// Ctrl+Space should send Ctrl-@ (NUL) in the terminal. (#3896)
return onKeyDown(keyCode, event);
}
return super.onKeyPreIme(keyCode, event);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import com.termux.shared.markdown.MarkdownUtils;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
Expand Down Expand Up @@ -160,13 +161,41 @@ public static String getDeviceInfoMarkdownString(@NonNull final Context context,
appendPropertyToMarkdown(markdownString, "DEVICE", Build.DEVICE);
appendPropertyToMarkdown(markdownString, "SUPPORTED_ABIS", Joiner.on(", ").skipNulls().join(Build.SUPPORTED_ABIS));

// CPU information
String cpuInfo = getCpuInfo();
if (cpuInfo != null && !cpuInfo.isEmpty())
appendPropertyToMarkdown(markdownString, "CPU_INFO", cpuInfo);

markdownString.append("\n##\n");

return markdownString.toString();
}



/**
* Get CPU information by reading /proc/cpuinfo.
*
* @return Returns the CPU info string, or {@code null} if failed.
*/
public static String getCpuInfo() {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(
new FileInputStream("/proc/cpuinfo")))) {
StringBuilder sb = new StringBuilder();
String line;
int count = 0;
while ((line = reader.readLine()) != null && count < 20) {
sb.append(line).append("\n");
count++;
}
return sb.toString().trim();
} catch (Exception e) {
return null;
}
}



public static Properties getSystemProperties() {
Properties systemProperties = new Properties();

Expand Down
Loading