Compare commits
47 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
657c270d97 | ||
|
|
7763931035 | ||
|
|
50005bc794 | ||
|
|
96f5ed985a | ||
|
|
c3aa9d9662 | ||
|
|
47634ca237 | ||
|
|
d9ec1bf40b | ||
|
|
bc1b742a36 | ||
|
|
86e2945069 | ||
|
|
961e06379b | ||
|
|
468185efb3 | ||
|
|
e006e36dd0 | ||
|
|
dd38965c46 | ||
|
|
79d56b778d | ||
|
|
2326c52199 | ||
|
|
f907684ef2 | ||
|
|
9d37461ac7 | ||
|
|
4de0f98fa4 | ||
|
|
f153a72592 | ||
|
|
b0f4efb0bc | ||
|
|
5c03c2d77e | ||
|
|
bce65f7db1 | ||
|
|
e18579164f | ||
|
|
16273a1981 | ||
|
|
ce82979e2b | ||
|
|
625aeab398 | ||
|
|
bad6712338 | ||
|
|
b54c7909bd | ||
|
|
4ccc703fcf | ||
|
|
7348820caf | ||
|
|
525985b1f2 | ||
|
|
ab3852d2e4 | ||
|
|
9928073e48 | ||
|
|
3091da64bc | ||
|
|
74dca95101 | ||
|
|
2a6a3b76b7 | ||
|
|
0e4ea95d74 | ||
|
|
7389dbb56f | ||
|
|
d982c71efe | ||
|
|
1b36c684d6 | ||
|
|
a6a83b1fcd | ||
|
|
d6f01bfe9a | ||
|
|
271dd7dcee | ||
|
|
95fbb810e2 | ||
|
|
1aa439311b | ||
|
|
12ddaccaf7 | ||
|
|
09fe7e5941 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -30,6 +30,7 @@ local.properties
|
||||
.idea/modules.xml
|
||||
.idea/scopes/scope_settings.xml
|
||||
.idea/vcs.xml
|
||||
.idea/dictionaries/
|
||||
*.iml
|
||||
|
||||
# OS-specific files
|
||||
|
||||
229
.idea/codeStyleSettings.xml
generated
Normal file
229
.idea/codeStyleSettings.xml
generated
Normal file
@@ -0,0 +1,229 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectCodeStyleSettingsManager">
|
||||
<option name="PER_PROJECT_SETTINGS">
|
||||
<value>
|
||||
<option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" />
|
||||
<option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" />
|
||||
<option name="PACKAGES_TO_USE_IMPORT_ON_DEMAND">
|
||||
<value />
|
||||
</option>
|
||||
<option name="IMPORT_LAYOUT_TABLE">
|
||||
<value>
|
||||
<package name="android" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="com" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="junit" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="net" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="org" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="java" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="javax" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="" withSubpackages="true" static="true" />
|
||||
<emptyLine />
|
||||
</value>
|
||||
</option>
|
||||
<option name="RIGHT_MARGIN" value="100" />
|
||||
<AndroidXmlCodeStyleSettings>
|
||||
<option name="USE_CUSTOM_SETTINGS" value="true" />
|
||||
</AndroidXmlCodeStyleSettings>
|
||||
<Objective-C-extensions>
|
||||
<option name="GENERATE_INSTANCE_VARIABLES_FOR_PROPERTIES" value="ASK" />
|
||||
<option name="RELEASE_STYLE" value="IVAR" />
|
||||
<option name="TYPE_QUALIFIERS_PLACEMENT" value="BEFORE" />
|
||||
<file>
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Import" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Macro" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Typedef" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Enum" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Constant" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Global" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Struct" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="FunctionPredecl" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Function" />
|
||||
</file>
|
||||
<class>
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Property" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Synthesize" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InitMethod" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="StaticMethod" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InstanceMethod" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="DeallocMethod" />
|
||||
</class>
|
||||
<extensions>
|
||||
<pair source="cpp" header="h" />
|
||||
<pair source="c" header="h" />
|
||||
</extensions>
|
||||
</Objective-C-extensions>
|
||||
<XML>
|
||||
<option name="XML_LEGACY_SETTINGS_IMPORTED" value="true" />
|
||||
</XML>
|
||||
<codeStyleSettings language="XML">
|
||||
<option name="FORCE_REARRANGE_MODE" value="1" />
|
||||
<indentOptions>
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
</indentOptions>
|
||||
<arrangement>
|
||||
<rules>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>xmlns:android</NAME>
|
||||
<XML_NAMESPACE>Namespace:</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>xmlns:.*</NAME>
|
||||
<XML_NAMESPACE>Namespace:</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:id</NAME>
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:name</NAME>
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>name</NAME>
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>style</NAME>
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:layout_width</NAME>
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:layout_height</NAME>
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:layout_.*</NAME>
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:width</NAME>
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:height</NAME>
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_NAMESPACE>.*</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
</rules>
|
||||
</arrangement>
|
||||
</codeStyleSettings>
|
||||
</value>
|
||||
</option>
|
||||
<option name="PREFERRED_PROJECT_CODE_STYLE" value="TermuxCodeStyle" />
|
||||
</component>
|
||||
</project>
|
||||
1
.idea/gradle.xml
generated
1
.idea/gradle.xml
generated
@@ -3,6 +3,7 @@
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
<option name="disableWrapperSourceDistributionNotification" value="true" />
|
||||
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="gradleJvm" value="1.8" />
|
||||
|
||||
15
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
15
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@@ -0,0 +1,15 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="AndroidLintGoogleAppIndexingWarning" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="EmptyStatementBody" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="m_reportEmptyBlocks" value="true" />
|
||||
<option name="commentsAreContent" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
|
||||
<option name="processCode" value="true" />
|
||||
<option name="processLiterals" value="true" />
|
||||
<option name="processComments" value="true" />
|
||||
</inspection_tool>
|
||||
</profile>
|
||||
</component>
|
||||
7
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
7
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
@@ -0,0 +1,7 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="PROJECT_PROFILE" value="Project Default" />
|
||||
<option name="USE_PROJECT_PROFILE" value="true" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
||||
@@ -11,9 +11,9 @@ android:
|
||||
components:
|
||||
- platform-tools
|
||||
- tools
|
||||
- build-tools-23.0.1
|
||||
- build-tools-23.0.2
|
||||
- android-23
|
||||
- sys-img-x86-android-23
|
||||
- extra-android-m2repository
|
||||
|
||||
script:
|
||||
- ./gradlew testDebugUnitTest
|
||||
|
||||
@@ -2,7 +2,11 @@ apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion 23
|
||||
buildToolsVersion "23.0.1"
|
||||
buildToolsVersion "23.0.2"
|
||||
|
||||
dependencies {
|
||||
compile 'com.android.support:support-annotations:23.1.1'
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
@@ -13,9 +17,9 @@ android {
|
||||
defaultConfig {
|
||||
applicationId "com.termux"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 22
|
||||
versionCode 20
|
||||
versionName "0.20"
|
||||
targetSdkVersion 23
|
||||
versionCode 24
|
||||
versionName "0.24"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:fullBackupContent="@xml/backupscheme"
|
||||
android:icon="@drawable/ic_launcher"
|
||||
android:banner="@drawable/banner"
|
||||
android:label="@string/application_name"
|
||||
@@ -38,12 +39,14 @@
|
||||
<activity
|
||||
android:name="com.termux.app.TermuxHelpActivity"
|
||||
android:exported="false"
|
||||
android:label="@string/application_help" />
|
||||
android:theme="@android:style/Theme.Material.Light.DarkActionBar"
|
||||
android:parentActivityName=".app.TermuxActivity"
|
||||
android:label="@string/application_name" />
|
||||
|
||||
<activity
|
||||
android:name="com.termux.filepicker.TermuxFilePickerActivity"
|
||||
android:label="@string/application_name"
|
||||
android:theme="@android:style/Theme.Material"
|
||||
android:theme="@android:style/Theme.Material.Light.DarkActionBar"
|
||||
android:noHistory="true">
|
||||
<intent-filter>
|
||||
<!--
|
||||
|
||||
@@ -1,229 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>Termux Help</title>
|
||||
|
||||
<style>
|
||||
html { font-family: 'sans-serif-light', sans-serif; height: 100%; margin: auto; padding: 0; color: black; background-color: white; }
|
||||
.page { max-width: 820px; margin: auto; padding: 0 1em; }
|
||||
body { margin-left: auto; margin-right: auto; margin-top: 0; padding: 0; width: 100%; }
|
||||
p { font-size: 16px; line-height: 1.3em; }
|
||||
ul.index { padding-left: 0; }
|
||||
.index li { list-style-type: none; line-height: 1.8em; }
|
||||
dt { margin-left: 1em; list-style-type: bullet; }
|
||||
a, a:visited { color: #0000EE }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page help">
|
||||
<h1 id="index">Termux Help</h1>
|
||||
|
||||
<ul class="index">
|
||||
<li><a href="#introduction">Introduction</a></li>
|
||||
<li><a href="#user_interface">User interface</a></li>
|
||||
<li><a href="#touch_keyboard">Using a touch keyboard</a></li>
|
||||
<li><a href="#hardware_keyboard">Using a hardware keyboard</a></li>
|
||||
<li><a href="#package_management">Package management</a></li>
|
||||
<li><a href="#text_editing">Text editing</a></li>
|
||||
<li><a href="#using_ssh">Using SSH</a></li>
|
||||
<li><a href="#interactive_shells">Interactive shells</a></li>
|
||||
<li><a href="#termux_android">Termux and Android</a></li>
|
||||
<li><a href="#add_on_api">Add-on: API</a></li>
|
||||
<li><a href="#add_on_float">Add-on: Float</a></li>
|
||||
<li><a href="#add_on_styling">Add-on: Styling</a></li>
|
||||
<li><a href="#add_on_widget">Add-on: Widget</a></li>
|
||||
<li><a href="#source_and_licenses">Source and licenses</a>
|
||||
</ul>
|
||||
|
||||
<h2 id="introduction">Introduction</h2>
|
||||
<p>Termux is a terminal emulator for Android combined with a collection of packages for command line software. This help
|
||||
explains both the terminal interface and the packaging tool available from inside the terminal.</p>
|
||||
<p>Want to ask a question, report a bug or have an idea for a new package or feature?
|
||||
Visit the <a href="https://plus.google.com/communities/101692629528551299417">Google+ Termux Community</a>!</p>
|
||||
|
||||
<h2 id="user_interface">User interface</h2>
|
||||
<p>At launch Termux shows a terminal interface, whose text size can be adjusted by pinch zooming or double tapping
|
||||
and pulling the content towards or from you.</p>
|
||||
<p>Besides the terminal (with keyboard shortcuts explained below) there are three additional interface elements available:
|
||||
A <strong>context menu</strong>, <strong>navigation drawer</strong>
|
||||
and <strong>notification</strong>.</p>
|
||||
<p>The <strong>context menu</strong> can be shown by long pressing anywhere on the terminal. It provides menu entries for:</p>
|
||||
<ul>
|
||||
<li>Selecting and pasting text.</li>
|
||||
<li>Sharing text from the terminal to other apps (e.g. email or SMS)</li>
|
||||
<li>Resetting the terminal if it gets stuck.</li>
|
||||
<li>Switching the terminal to full-screen.</li>
|
||||
<li>Hangup (exiting the current terminal session).</li>
|
||||
<li>Styling the terminal by selecting a font and a color scheme.</li>
|
||||
<li>Showing this help page.</li>
|
||||
</ul>
|
||||
<p>The <strong>navigation drawer</strong> is revealed by swiping from the left part of the screen. It has three
|
||||
elements:</p>
|
||||
<ul>
|
||||
<li>A list of sessions. Clicking on a session shows it in the terminal while long pressing allows you to specify a session title.</li>
|
||||
<li>A button to toggle visibility of the touch keyboard.</li>
|
||||
<li>A button to create new terminal sessions (long press for creating a named session or a fail-safe one).</li>
|
||||
</ul>
|
||||
<p>The <strong>notification</strong>, available when a terminal session is running, is available by pulling down the notification menu.
|
||||
Pressing the notification leads to the most current terminal session. The notification may also be expanded
|
||||
(by pinch-zooming or performing a single-finger glide) to expose three actions:</p>
|
||||
<ul>
|
||||
<li>Exiting all running terminal sessions.</li>
|
||||
<li>Use a wake lock to avoid entering sleep mode.</li>
|
||||
<li>Use a high performance wifi lock to maximize wifi performance.</li>
|
||||
</ul>
|
||||
<p>With a wake or wifi lock held the notification and Termux background processes will be available even if no terminal
|
||||
session is running, which allows server and other background processes to run more reliably.</p>
|
||||
|
||||
<h2 id="touch_keyboard">Using a touch keyboard</h2>
|
||||
<p>Using the Ctrl key is necessary for working with a terminal - but most touch keyboards
|
||||
does not include one. For that purpose Termux uses the <em>Volume down</em> button to emulate
|
||||
the Ctrl key. For example, pressing <em>Volume down+L</em> on a touch keyboard sends the same input as
|
||||
pressing <em>Ctrl+L</em> on a hardware keyboard. The result of using Ctrl in combination
|
||||
with a key depends on which program is used, but for many command line tools the following
|
||||
shortcuts works:</p>
|
||||
<ul>
|
||||
<li>Ctrl+A → Move cursor to the beginning of line.</li>
|
||||
<li>Ctrl+C → Abort (send SIGINT to) current process.</li>
|
||||
<li>Ctrl+D → Logout of a terminal session.</li>
|
||||
<li>Ctrl+E → Move cursor to the end of line.</li>
|
||||
<li>Ctrl+K → Delete from cursor to the end of line.</li>
|
||||
<li>Ctrl+L → Clear the terminal.</li>
|
||||
<li>Ctrl+Z → Suspend (send SIGTSTP to) current process.</li>
|
||||
</ul>
|
||||
<p>The <em>Volume up</em> key also serves as a special key to produce certain input:</p>
|
||||
<ul>
|
||||
<li>Volume Up+L → | (the pipe character).</li>
|
||||
<li>Volume Up+E → Escape key.</li>
|
||||
<li>Volume Up+T → Tab key.</li>
|
||||
<li>Volume Up+1 → F1 (and Volume Up+2 → F2, etc).</li>
|
||||
<li>Volume Up+B → Alt+B, back a word when using readline.</li>
|
||||
<li>Volume Up+F → Alt+F, forward a word when using readline.</li>
|
||||
<li>Volume Up+W → Up arrow key.</li>
|
||||
<li>Volume Up+A → Left arrow key.</li>
|
||||
<li>Volume Up+S → Down arrow key.</li>
|
||||
<li>Volume Up+D → Right arrow key.</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="hardware_keyboard">Using a hardware keyboard</h2>
|
||||
<p>The following shortcuts are available when using Termux with a hardware (e.g. bluetooth) keyboard by combining them with <em>Ctrl+Shift</em>:</p>
|
||||
<ul>
|
||||
<li>'C' → Create new session</li>
|
||||
<li>'R' → Rename current session</li>
|
||||
<li>Down arrow (or 'N') → Next session</li>
|
||||
<li>Up arrow (or 'P') → Previous session</li>
|
||||
<li>Right arrow → Open drawer</li>
|
||||
<li>Left arrow → Close drawer</li>
|
||||
<li>'F' → Toggle full screen</li>
|
||||
<li>'M' → Show menu</li>
|
||||
<li>'V' → Paste</li>
|
||||
<li>+/- → Adjust text size</li>
|
||||
<li>1-9 → Go to numbered session</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="package_management">Package management</h2>
|
||||
<p>A minimal base system consisting of the Apt package manager and the busybox collection of system utilities
|
||||
is installed when first starting Termux. Additional packages are available using the apt command:</p>
|
||||
<dl>
|
||||
<dt>apt update</dt><dd>Updates the list of available packages. This commands needs to be run initially directly after installation
|
||||
and regularly afterwards to receive updates.</dd>
|
||||
<dt>apt search <query></dt><dd>Search among available packages.</dd>
|
||||
<dt>apt install <package></dt><dd>Install a new package.</dd>
|
||||
<dt>apt upgrade</dt><dd>Upgrade outdated packages. For Apt to know about newer packages you will need to update the package index, so you will normally want to run <em>apt update</em> before upgrading.</dd>
|
||||
<dt>apt show <package></dt><dd>Show information about a package.</dd>
|
||||
<dt>apt list</dt><dd>List all available packages.</dd>
|
||||
<dt>apt list --installed</dt><dd>List all installed packages.</dd>
|
||||
<dt>apt remove <package></dt><dd>Remove an installed package.</dd>
|
||||
</dl>
|
||||
|
||||
<p>Apt as a package manager uses a package format named <em>dpkg</em>. Normally direct use of dpkg is not necessary, but the
|
||||
following two commands may be of use:</p>
|
||||
<dl>
|
||||
<dt>dpkg -L <package></dt>
|
||||
<dd>List installed files of a package.</dd>
|
||||
<dt>dpkg --verify</dt>
|
||||
<dd>Verify the integrity of installed packages.</dd>
|
||||
</dl>
|
||||
<p>View the apt manual page (execute <em>apt install man</em> to install a man page viewer first) for more information.</p>
|
||||
|
||||
<h2 id="text_editing">Text editing</h2>
|
||||
<p>By default the busybox version of <em>vi</em> is available. This is a barebone and somewhat unfriendly editor -
|
||||
install <a href="http://www.nano-editor.org/dist/v2.2/nano.html">nano</a> for a more straight-forward editor and
|
||||
<a href="http://vimdoc.sourceforge.net/htmldoc/usr_toc.html">vim</a> for a more powerful one.</p>
|
||||
|
||||
<h2 id="using_ssh">Using SSH</h2>
|
||||
<p>By installing the <strong>openssh</strong> package (by executing <em>apt install openssh</em>) you may SSH into remote systems,
|
||||
optionally putting private keys or configuration under $HOME/.ssh/.</p>
|
||||
<p>If you wish to use an SSH agent to avoid entering passwords, the Termux openssh package provides
|
||||
a wrapper script named <strong>ssha</strong> (note the 'a' at the end) for ssh which:</p>
|
||||
<ol>
|
||||
<li>Starts the ssh agent if necessary (or connect to it if already running).</li>
|
||||
<li>Runs <strong>ssh-add</strong> if necessary.</li>
|
||||
<li>Runs <strong>ssh</strong> with the provided arguments.</li>
|
||||
</ol>
|
||||
<p>This means that the agent will prompt for a key password at first run, but remember the authorization for subsequent ones.</p>
|
||||
|
||||
<h2 id="interactive_shells">Interactive shells</h2>
|
||||
<p>The base system that is installed when first starting Termux uses the <em>bash</em> shell while zsh is available as
|
||||
an installable alternative:</p>
|
||||
<ul>
|
||||
<li>bash - the default shell on most Linux distributions, with resources such as
|
||||
<a href="http://www.tldp.org/LDP/Bash-Beginners-Guide/html/">Bash Guide for Beginners</a>,
|
||||
the <a href="https://www.gnu.org/software/bash/manual/bash.html">Bash Reference Manual</a>
|
||||
or the <a href="http://www.tldp.org/LDP/abs/html/">Advanced Bash-Scripting Guide</a> available.</li>
|
||||
<li>zsh - a powerful shell with information available at
|
||||
<a href="http://zsh.sourceforge.net/Guide/zshguide.html">A User's Guide to the Z-Shell</a>, the
|
||||
<a href="http://zsh.sourceforge.net/Doc/Release/zsh_toc.html">Z Shell Manual</a> or
|
||||
<a href="http://www.rayninfo.co.uk/tips/zshtips.html">ZSH Tips by ZZapper</a>.
|
||||
After installing zsh through <em>apt install zsh</em>, execute <em>chsh -s zsh</em> to set it as the default login shell when starting Termux
|
||||
(and change back with <em>chsh -s bash</em> if necessary).</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="termux_android">Termux and Android</h2>
|
||||
<p>Termux is designed to cope with the restrictions of running as an ordinary Android app without requiring root, which
|
||||
leads to several differences between Termux and a traditional desktop system. The file system layout is drastically different:</p>
|
||||
<ul>
|
||||
<li>Common folders such as /bin, /usr/, /var and /etc does not exist.</li>
|
||||
<li>The Android system provides a basic non-standard file system hierarchy, where e.g. /system/bin contains some system binaries.</li>
|
||||
<li>The user folder $HOME is inside the private file area exposed to Termux as an ordinary Android app.
|
||||
Uninstalling Termux will cause this file area to be wiped - so save important files outside this area such as in /sdcard
|
||||
or use a version control system such as <em>git</em>.</li>
|
||||
<li>Termux installs its packages in a folder exposed through the $PREFIX environment variable (with e.g. binaries in $PREFIX/bin,
|
||||
and configuration in $PREFIX/etc).</li>
|
||||
<li>Shared libraries are installed in $PREFIX/lib, which are available from binaries due to Termux setting the $LD_LIBRARY_PATH
|
||||
environment variable. These may clash with Android system binaries in /system/bin, which may force LD_LIBRARY_PATH to be
|
||||
cleared before running system binaries.</li>
|
||||
</ul>
|
||||
<p>Besides the file system being different, Termux is running as a single-user system without root - each Android app is running as
|
||||
its own Linux user, so running commands inside Termux may not interfere with other installed applications.</p>
|
||||
<p>Running as non-root implies that ports below 1024 cannot be bound to. Many packages have been configured to have compatible
|
||||
default values - the ftpd, httpd, and sshd servers default to 8021, 8080 and 8022, respectively.</p>
|
||||
|
||||
<h2 id="add_on_api">Add-on: API</h2>
|
||||
<p>The API add-on exposes Android system functionality such as SMS messages, GPS location or the Text-to-speech functionality through command line tools.</p>
|
||||
<ul><li><a href="http://play.google.com/store/apps/details?id=com.termux.api">See more and install from Google Play</a></li></ul>
|
||||
|
||||
<h2 id="add_on_float">Add-on: Float</h2>
|
||||
<p>The Float add-on consists of a floating terminal window visible while running other apps.</p>
|
||||
<ul><li><a href="http://play.google.com/store/apps/details?id=com.termux.window">See more and install from Google Play</a></li></ul>
|
||||
|
||||
<h2 id="add_on_styling">Add-on: Styling</h2>
|
||||
<p>The Styling add-on provides color schemes and fonts to beabeautify and customize the appearance of the Termux terminal.</p>
|
||||
<ul><li><a href="http://play.google.com/store/apps/details?id=com.termux.styling">See more and install from Google Play</a></li></ul>
|
||||
|
||||
<h2 id="add_on_widget">Add-on: Widget</h2>
|
||||
<p>The Widget add-on brings a widget to your homescreen, providing links to run scripts in your $HOME/.shortcuts/ folder.</p>
|
||||
<ul><li><a href="http://play.google.com/store/apps/details?id=com.termux.widget">See more and install from Google Play</a></li></ul>
|
||||
|
||||
<h2 id="source_and_licenses">Source and licenses</h2>
|
||||
<p>Termux uses terminal emulation code from <a href="https://github.com/jackpal/Android-Terminal-Emulator">Terminal Emulator for Android</a>
|
||||
which is under the <a href="https://raw.githubusercontent.com/jackpal/Android-Terminal-Emulator/master/NOTICE">Apache License, Version 2.0</a>.
|
||||
Packages available through Termux are distributed under their respective licenses with scripts and patches used to build them
|
||||
<a href="https://github.com/termux/termux-packages">available on github</a>.</p>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,19 +1,8 @@
|
||||
package com.termux.app;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import com.termux.R;
|
||||
import com.termux.drawer.DrawerLayout;
|
||||
import com.termux.terminal.TerminalSession;
|
||||
import com.termux.terminal.TerminalSession.SessionChangedCallback;
|
||||
import com.termux.view.TerminalKeyListener;
|
||||
import com.termux.view.TerminalView;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.ActivityNotFoundException;
|
||||
@@ -27,14 +16,20 @@ import android.content.DialogInterface.OnShowListener;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.ServiceConnection;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Typeface;
|
||||
import android.media.AudioAttributes;
|
||||
import android.media.SoundPool;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.os.Vibrator;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.SpannableString;
|
||||
import android.text.Spanned;
|
||||
import android.text.TextUtils;
|
||||
@@ -62,6 +57,19 @@ import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.termux.R;
|
||||
import com.termux.drawer.DrawerLayout;
|
||||
import com.termux.terminal.TerminalSession;
|
||||
import com.termux.terminal.TerminalSession.SessionChangedCallback;
|
||||
import com.termux.view.TerminalKeyListener;
|
||||
import com.termux.view.TerminalView;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* A terminal emulator activity.
|
||||
*
|
||||
@@ -84,10 +92,12 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
||||
|
||||
private static final int MAX_SESSIONS = 8;
|
||||
|
||||
private static final int REQUESTCODE_PERMISSION_STORAGE = 1234;
|
||||
|
||||
private static final String RELOAD_STYLE_ACTION = "com.termux.app.reload_style";
|
||||
|
||||
/** The main view of the activity showing the terminal. */
|
||||
TerminalView mTerminalView;
|
||||
/** The main view of the activity showing the terminal. Initialized in onCreate(). */
|
||||
@SuppressWarnings("NullableProblems") @NonNull TerminalView mTerminalView;
|
||||
|
||||
final FullScreenHelper mFullScreenHelper = new FullScreenHelper(this);
|
||||
|
||||
@@ -112,17 +122,42 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
||||
*/
|
||||
boolean mIsVisible;
|
||||
|
||||
private SoundPool mBellSoundPool = new SoundPool.Builder().setMaxStreams(1).setAudioAttributes(
|
||||
new AudioAttributes.Builder().setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
|
||||
.setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION).build()).build();
|
||||
private int mBellSoundId;
|
||||
|
||||
private final BroadcastReceiver mBroadcastReceiever = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (mIsVisible) {
|
||||
String whatToReload = intent.getStringExtra(RELOAD_STYLE_ACTION);
|
||||
if (whatToReload == null || "colors".equals(whatToReload)) mTerminalView.checkForColors();
|
||||
if (whatToReload == null || "font".equals(whatToReload)) mTerminalView.checkForTypeface();
|
||||
if ("storage".equals(whatToReload)) {
|
||||
if (ensureStoragePermissionGranted()) TermuxInstaller.setupStorageSymlinks(TermuxActivity.this);
|
||||
return;
|
||||
}
|
||||
mTerminalView.checkForFontAndColors();
|
||||
mSettings.reloadFromProperties(TermuxActivity.this);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** For processes to access shared internal storage (/sdcard) we need this permission. */
|
||||
@TargetApi(Build.VERSION_CODES.M)
|
||||
public boolean ensureStoragePermissionGranted() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
if (checkSelfPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
|
||||
return true;
|
||||
} else {
|
||||
requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUESTCODE_PERMISSION_STORAGE);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// Always granted before Android 6.0.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle bundle) {
|
||||
super.onCreate(bundle);
|
||||
@@ -143,6 +178,7 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
||||
if (event.getAction() != KeyEvent.ACTION_DOWN) return false;
|
||||
|
||||
final TerminalSession currentSession = getCurrentTermSession();
|
||||
if (currentSession == null) return false;
|
||||
|
||||
if (keyCode == KeyEvent.KEYCODE_ENTER && !currentSession.isRunning()) {
|
||||
// Return pressed with finished session - remove it.
|
||||
@@ -226,21 +262,34 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
||||
|
||||
@Override
|
||||
public void onSingleTapUp(MotionEvent e) {
|
||||
// Toggle keyboard visibility if tapping with a finger:
|
||||
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
imm.toggleSoftInput(InputMethodManager.SHOW_IMPLICIT, 0);
|
||||
switch (mSettings.mTapBehaviour) {
|
||||
case TermuxPreferences.TAP_TOGGLE_KEYBOARD:
|
||||
// Toggle keyboard visibility if tapping with a finger:
|
||||
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
imm.toggleSoftInput(InputMethodManager.SHOW_IMPLICIT, 0);
|
||||
break;
|
||||
case TermuxPreferences.TAP_SHOW_MENU:
|
||||
mTerminalView.showContextMenu();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldBackButtonBeMappedToEscape() {
|
||||
return mSettings.mBackIsEscape;
|
||||
}
|
||||
});
|
||||
|
||||
findViewById(R.id.new_session_button).setOnClickListener(new OnClickListener() {
|
||||
View newSessionButton = findViewById(R.id.new_session_button);
|
||||
|
||||
newSessionButton.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
addNewSession(false, null);
|
||||
}
|
||||
});
|
||||
|
||||
findViewById(R.id.new_session_button).setOnLongClickListener(new OnLongClickListener() {
|
||||
newSessionButton.setOnLongClickListener(new OnLongClickListener() {
|
||||
@Override
|
||||
public boolean onLongClick(View v) {
|
||||
Resources res = getResources();
|
||||
@@ -288,8 +337,9 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
||||
startService(serviceIntent);
|
||||
if (!bindService(serviceIntent, this, 0)) throw new RuntimeException("bindService() failed");
|
||||
|
||||
mTerminalView.checkForTypeface();
|
||||
mTerminalView.checkForColors();
|
||||
mTerminalView.checkForFontAndColors();
|
||||
|
||||
mBellSoundId = mBellSoundPool.load(this, R.raw.bell, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -346,7 +396,17 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
||||
|
||||
@Override
|
||||
public void onBell(TerminalSession session) {
|
||||
if (mIsVisible) ((Vibrator) getSystemService(VIBRATOR_SERVICE)).vibrate(50);
|
||||
if (mIsVisible) {
|
||||
switch (mSettings.mBellBehaviour) {
|
||||
case TermuxPreferences.BELL_BEEP:
|
||||
mBellSoundPool.play(mBellSoundId, 1.f, 1.f, 1, 0, 1.f);
|
||||
break;
|
||||
case TermuxPreferences.BELL_VIBRATE:
|
||||
((Vibrator) getSystemService(VIBRATOR_SERVICE)).vibrate(50);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -461,7 +521,7 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
||||
}
|
||||
}
|
||||
|
||||
TerminalSession getCurrentTermSession() {
|
||||
@Nullable TerminalSession getCurrentTermSession() {
|
||||
return mTerminalView.getCurrentSession();
|
||||
}
|
||||
|
||||
@@ -580,12 +640,11 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
||||
return false;
|
||||
}
|
||||
|
||||
void showUrlSelection() {
|
||||
String text = getCurrentTermSession().getEmulator().getScreen().getTranscriptText();
|
||||
static LinkedHashSet<CharSequence> extractUrls(String text) {
|
||||
// Pattern for recognizing a URL, based off RFC 3986
|
||||
// http://stackoverflow.com/questions/5713558/detect-and-extract-url-from-a-string
|
||||
final Pattern urlPattern = Pattern.compile(
|
||||
"(?:^|[\\W])((ht|f)tp(s?):\\/\\/|www\\.)" + "(([\\w\\-]+\\.){1,}?([\\w\\-.~]+\\/?)*" + "[\\p{Alnum}.,%_=?&#\\-+()\\[\\]\\*$~@!:/{};']*)",
|
||||
"(?:^|[\\W])((ht|f)tp(s?)://|www\\.)" + "(([\\w\\-]+\\.)+?([\\w\\-.~]+/?)*" + "[\\p{Alnum}.,%_=?&#\\-+()\\[\\]\\*$~@!:/{};']*)",
|
||||
Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
|
||||
LinkedHashSet<CharSequence> urlSet = new LinkedHashSet<>();
|
||||
Matcher matcher = urlPattern.matcher(text);
|
||||
@@ -595,7 +654,12 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
||||
String url = text.substring(matchStart, matchEnd);
|
||||
urlSet.add(url);
|
||||
}
|
||||
return urlSet;
|
||||
}
|
||||
|
||||
void showUrlSelection() {
|
||||
String text = getCurrentTermSession().getEmulator().getScreen().getTranscriptText();
|
||||
LinkedHashSet<CharSequence> urlSet = extractUrls(text);
|
||||
if (urlSet.isEmpty()) {
|
||||
new AlertDialog.Builder(this).setMessage(R.string.select_url_no_found).show();
|
||||
return;
|
||||
@@ -718,6 +782,13 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
|
||||
if (requestCode == REQUESTCODE_PERMISSION_STORAGE && grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
TermuxInstaller.setupStorageSymlinks(this);
|
||||
}
|
||||
}
|
||||
|
||||
void toggleImmersive() {
|
||||
boolean newValue = !mSettings.isFullScreen();
|
||||
mSettings.setFullScreen(this, newValue);
|
||||
|
||||
@@ -5,33 +5,63 @@ import android.content.ActivityNotFoundException;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.view.ViewGroup;
|
||||
import android.webkit.WebSettings;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.RelativeLayout;
|
||||
|
||||
/** Basic embedded browser for viewing the bundled help page. */
|
||||
/** Basic embedded browser for viewing help pages. */
|
||||
public final class TermuxHelpActivity extends Activity {
|
||||
|
||||
private WebView mWebView;
|
||||
private ProgressBar mProgressBar;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
final RelativeLayout progressLayout = new RelativeLayout(this);
|
||||
RelativeLayout.LayoutParams lParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
lParams.addRule(RelativeLayout.CENTER_IN_PARENT);
|
||||
mProgressBar = new ProgressBar(this);
|
||||
mProgressBar.setIndeterminate(true);
|
||||
mProgressBar.setLayoutParams(lParams);
|
||||
progressLayout.addView(mProgressBar);
|
||||
|
||||
mWebView = new WebView(this);
|
||||
setContentView(mWebView);
|
||||
WebSettings settings = mWebView.getSettings();
|
||||
settings.setCacheMode(WebSettings.LOAD_NO_CACHE);
|
||||
settings.setAppCacheEnabled(false);
|
||||
setContentView(progressLayout);
|
||||
mWebView.clearCache(true);
|
||||
|
||||
mWebView.setWebViewClient(new WebViewClient() {
|
||||
@Override
|
||||
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
||||
if (url.startsWith("https://termux.com")) {
|
||||
// Inline help.
|
||||
setContentView(progressLayout);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
|
||||
} catch (ActivityNotFoundException e) {
|
||||
// TODO: Android TV does not have a system browser - but needs better method of getting back
|
||||
// than navigating deep here.
|
||||
// Android TV does not have a system browser.
|
||||
setContentView(progressLayout);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageFinished(WebView view, String url) {
|
||||
setContentView(mWebView);
|
||||
}
|
||||
});
|
||||
mWebView.loadUrl("file:///android_asset/help.html");
|
||||
mWebView.loadUrl("https://termux.com/help.html");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
package com.termux.app;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.DialogInterface.OnClickListener;
|
||||
import android.content.DialogInterface.OnDismissListener;
|
||||
import android.os.Environment;
|
||||
import android.system.Os;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import com.termux.R;
|
||||
import com.termux.terminal.EmulatorDebug;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
@@ -11,21 +27,6 @@ import java.util.List;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.DialogInterface.OnClickListener;
|
||||
import android.content.DialogInterface.OnDismissListener;
|
||||
import android.system.Os;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import com.termux.R;
|
||||
import com.termux.terminal.EmulatorDebug;
|
||||
|
||||
/**
|
||||
* Install the Termux bootstrap packages if necessary by following the below steps:
|
||||
*
|
||||
@@ -184,7 +185,7 @@ final class TermuxInstaller {
|
||||
} else if (arch.equals("x86_64")) {
|
||||
arch = "i686";
|
||||
}
|
||||
return new URL("http://apt.termux.com/bootstrap/bootstrap-" + arch + ".zip");
|
||||
return new URL("https://termux.net/bootstrap/bootstrap-" + arch + ".zip");
|
||||
}
|
||||
|
||||
/** Delete a folder and all its content or throw. */
|
||||
@@ -200,4 +201,51 @@ final class TermuxInstaller {
|
||||
}
|
||||
}
|
||||
|
||||
public static void setupStorageSymlinks(final Context context) {
|
||||
final String LOG_TAG = "termux-storage";
|
||||
new Thread() {
|
||||
public void run() {
|
||||
try {
|
||||
File storageDir = new File(TermuxService.HOME_PATH, "storage");
|
||||
|
||||
if (storageDir.exists() && !storageDir.delete()) {
|
||||
Log.e(LOG_TAG, "Could not delete old $HOME/storage");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!storageDir.mkdirs()) {
|
||||
Log.e(LOG_TAG, "Unable to mkdirs() for $HOME/storage");
|
||||
return;
|
||||
}
|
||||
|
||||
File sharedDir = Environment.getExternalStorageDirectory();
|
||||
Os.symlink(sharedDir.getAbsolutePath(), new File(storageDir, "shared").getAbsolutePath());
|
||||
|
||||
File downloadsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
|
||||
Os.symlink(downloadsDir.getAbsolutePath(), new File(storageDir, "downloads").getAbsolutePath());
|
||||
|
||||
File dcimDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM);
|
||||
Os.symlink(dcimDir.getAbsolutePath(), new File(storageDir, "dcim").getAbsolutePath());
|
||||
|
||||
File picturesDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
|
||||
Os.symlink(picturesDir.getAbsolutePath(), new File(storageDir, "pictures").getAbsolutePath());
|
||||
|
||||
File musicDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC);
|
||||
Os.symlink(musicDir.getAbsolutePath(), new File(storageDir, "music").getAbsolutePath());
|
||||
|
||||
File moviesDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES);
|
||||
Os.symlink(moviesDir.getAbsolutePath(), new File(storageDir, "movies").getAbsolutePath());
|
||||
|
||||
final File[] dirs = context.getExternalFilesDirs(null);
|
||||
if (dirs != null && dirs.length >= 2) {
|
||||
final File externalDir = dirs[1];
|
||||
Os.symlink(externalDir.getAbsolutePath(), new File(storageDir, "external").getAbsolutePath());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(LOG_TAG, "Error setting up link", e);
|
||||
}
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,12 +5,38 @@ import com.termux.terminal.TerminalSession;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.IntDef;
|
||||
import android.util.Log;
|
||||
import android.util.TypedValue;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.Properties;
|
||||
|
||||
final class TermuxPreferences {
|
||||
|
||||
@IntDef({BELL_VIBRATE, BELL_BEEP, BELL_IGNORE})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface AsciiBellBehaviour {}
|
||||
|
||||
static final int BELL_VIBRATE = 1;
|
||||
static final int BELL_BEEP = 2;
|
||||
static final int BELL_IGNORE = 3;
|
||||
|
||||
@IntDef({TAP_TOGGLE_KEYBOARD, TAP_SHOW_MENU, TAP_IGNORE})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface TapTerminalBehaviour {}
|
||||
|
||||
static final int TAP_TOGGLE_KEYBOARD = 1;
|
||||
static final int TAP_SHOW_MENU = 2;
|
||||
static final int TAP_IGNORE = 3;
|
||||
|
||||
private final int MIN_FONTSIZE;
|
||||
private static final int MAX_FONTSIZE = 256;
|
||||
|
||||
private static final String FULLSCREEN_KEY = "fullscreen";
|
||||
private static final String FONTSIZE_KEY = "fontsize";
|
||||
private static final String CURRENT_SESSION_KEY = "current_session";
|
||||
@@ -19,7 +45,16 @@ final class TermuxPreferences {
|
||||
private boolean mFullScreen;
|
||||
private int mFontSize;
|
||||
|
||||
@AsciiBellBehaviour
|
||||
int mBellBehaviour = BELL_VIBRATE;
|
||||
|
||||
@TapTerminalBehaviour
|
||||
int mTapBehaviour = TAP_TOGGLE_KEYBOARD;
|
||||
|
||||
boolean mBackIsEscape = true;
|
||||
|
||||
TermuxPreferences(Context context) {
|
||||
reloadFromProperties(context);
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
|
||||
float dipInPixels = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, context.getResources().getDisplayMetrics());
|
||||
@@ -86,4 +121,49 @@ final class TermuxPreferences {
|
||||
PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean(SHOW_WELCOME_DIALOG_KEY, false).apply();
|
||||
}
|
||||
|
||||
public void reloadFromProperties(Context context) {
|
||||
try {
|
||||
File propsFile = new File(TermuxService.HOME_PATH + "/.config/termux/termux.properties");
|
||||
if (propsFile.isFile() && propsFile.canRead()) {
|
||||
Properties props = new Properties();
|
||||
try (FileInputStream in = new FileInputStream(propsFile)) {
|
||||
props.load(in);
|
||||
}
|
||||
|
||||
switch (props.getProperty("bell-character", "vibrate")) {
|
||||
case "beep":
|
||||
mBellBehaviour = BELL_BEEP;
|
||||
break;
|
||||
case "ignore":
|
||||
mBellBehaviour = BELL_IGNORE;
|
||||
break;
|
||||
default: // "vibrate".
|
||||
mBellBehaviour = BELL_VIBRATE;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (props.getProperty("tap-screen", "toggle-keyboard")) {
|
||||
case "show-menu":
|
||||
mTapBehaviour = TAP_SHOW_MENU;
|
||||
break;
|
||||
case "ignore":
|
||||
mTapBehaviour = TAP_IGNORE;
|
||||
break;
|
||||
default: // "toggle-keyboard".
|
||||
mTapBehaviour = TAP_TOGGLE_KEYBOARD;
|
||||
break;
|
||||
}
|
||||
|
||||
mBackIsEscape = !"back".equals(props.getProperty("back-key", "escape"));
|
||||
} else {
|
||||
mBellBehaviour = BELL_VIBRATE;
|
||||
mTapBehaviour = TAP_TOGGLE_KEYBOARD;
|
||||
mBackIsEscape = true;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Toast.makeText(context, "Error loading properties: " + e.getMessage(), Toast.LENGTH_LONG).show();
|
||||
Log.e("termux", "Error loading props", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -138,11 +138,11 @@ public class DrawerLayout extends ViewGroup {
|
||||
|
||||
private final ChildAccessibilityDelegate mChildAccessibilityDelegate = new ChildAccessibilityDelegate();
|
||||
|
||||
private int mMinDrawerMargin;
|
||||
private final int mMinDrawerMargin;
|
||||
|
||||
private int mScrimColor = DEFAULT_SCRIM_COLOR;
|
||||
private float mScrimOpacity;
|
||||
private Paint mScrimPaint = new Paint();
|
||||
private final Paint mScrimPaint = new Paint();
|
||||
|
||||
private final ViewDragHelper mLeftDragger;
|
||||
private final ViewDragHelper mRightDragger;
|
||||
@@ -768,16 +768,10 @@ public class DrawerLayout extends ViewGroup {
|
||||
// or pick a magic number from thin air otherwise.
|
||||
// TODO Better communication with tools of this bogus state.
|
||||
// It will crash on a real device.
|
||||
if (widthMode == MeasureSpec.AT_MOST) {
|
||||
widthMode = MeasureSpec.EXACTLY;
|
||||
} else if (widthMode == MeasureSpec.UNSPECIFIED) {
|
||||
widthMode = MeasureSpec.EXACTLY;
|
||||
if (widthMode == MeasureSpec.UNSPECIFIED) {
|
||||
widthSize = 300;
|
||||
}
|
||||
if (heightMode == MeasureSpec.AT_MOST) {
|
||||
heightMode = MeasureSpec.EXACTLY;
|
||||
} else if (heightMode == MeasureSpec.UNSPECIFIED) {
|
||||
heightMode = MeasureSpec.EXACTLY;
|
||||
if (heightMode == MeasureSpec.UNSPECIFIED) {
|
||||
heightSize = 300;
|
||||
}
|
||||
} else {
|
||||
@@ -934,10 +928,7 @@ public class DrawerLayout extends ViewGroup {
|
||||
|
||||
private static boolean hasOpaqueBackground(View v) {
|
||||
final Drawable bg = v.getBackground();
|
||||
if (bg != null) {
|
||||
return bg.getOpacity() == PixelFormat.OPAQUE;
|
||||
}
|
||||
return false;
|
||||
return bg != null && bg.getOpacity() == PixelFormat.OPAQUE;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1114,7 +1105,6 @@ public class DrawerLayout extends ViewGroup {
|
||||
mRightDragger.processTouchEvent(ev);
|
||||
|
||||
final int action = ev.getAction();
|
||||
boolean wantTouchEvents = true;
|
||||
|
||||
switch (action & MotionEvent.ACTION_MASK) {
|
||||
case MotionEvent.ACTION_DOWN: {
|
||||
@@ -1154,7 +1144,7 @@ public class DrawerLayout extends ViewGroup {
|
||||
}
|
||||
}
|
||||
|
||||
return wantTouchEvents;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1317,10 +1307,7 @@ public class DrawerLayout extends ViewGroup {
|
||||
*/
|
||||
public boolean isDrawerOpen(int drawerGravity) {
|
||||
final View drawerView = findDrawerWithGravity(drawerGravity);
|
||||
if (drawerView != null) {
|
||||
return isDrawerOpen(drawerView);
|
||||
}
|
||||
return false;
|
||||
return drawerView != null && isDrawerOpen(drawerView);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1350,10 +1337,7 @@ public class DrawerLayout extends ViewGroup {
|
||||
*/
|
||||
public boolean isDrawerVisible(int drawerGravity) {
|
||||
final View drawerView = findDrawerWithGravity(drawerGravity);
|
||||
if (drawerView != null) {
|
||||
return isDrawerVisible(drawerView);
|
||||
}
|
||||
return false;
|
||||
return drawerView != null && isDrawerVisible(drawerView);
|
||||
}
|
||||
|
||||
private boolean hasPeekingDrawer() {
|
||||
@@ -1776,10 +1760,7 @@ public class DrawerLayout extends ViewGroup {
|
||||
|
||||
@Override
|
||||
public boolean onRequestSendAccessibilityEvent(ViewGroup host, View child, AccessibilityEvent event) {
|
||||
if (CAN_HIDE_DESCENDANTS || includeChildForAccessibility(child)) {
|
||||
return super.onRequestSendAccessibilityEvent(host, child, event);
|
||||
}
|
||||
return false;
|
||||
return (CAN_HIDE_DESCENDANTS || includeChildForAccessibility(child)) && super.onRequestSendAccessibilityEvent(host, child, event);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1796,4 +1777,4 @@ public class DrawerLayout extends ViewGroup {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ public class ViewDragHelper {
|
||||
/**
|
||||
* Edge flag indicating that the left edge should be affected.
|
||||
*/
|
||||
public static final int EDGE_LEFT = 1 << 0;
|
||||
public static final int EDGE_LEFT = 1 /*1 << 0*/;
|
||||
|
||||
/**
|
||||
* Edge flag indicating that the right edge should be affected.
|
||||
@@ -82,7 +82,7 @@ public class ViewDragHelper {
|
||||
/**
|
||||
* Indicates that a check should occur along the horizontal axis
|
||||
*/
|
||||
public static final int DIRECTION_HORIZONTAL = 1 << 0;
|
||||
public static final int DIRECTION_HORIZONTAL = 1 /*1 << 0*/;
|
||||
|
||||
/**
|
||||
* Indicates that a check should occur along the vertical axis
|
||||
@@ -1485,10 +1485,7 @@ public class ViewDragHelper {
|
||||
* @return true if the supplied view is under the given point, false otherwise
|
||||
*/
|
||||
public boolean isViewUnder(View view, int x, int y) {
|
||||
if (view == null) {
|
||||
return false;
|
||||
}
|
||||
return x >= view.getLeft() && x < view.getRight() && y >= view.getTop() && y < view.getBottom();
|
||||
return view != null && x >= view.getLeft() && x < view.getRight() && y >= view.getTop() && y < view.getBottom();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1522,4 +1519,4 @@ public class ViewDragHelper {
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,17 +36,12 @@ public class TermuxFilePickerActivity extends ListActivity {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.file_picker);
|
||||
|
||||
mAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, mFileNames);
|
||||
mAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, mFileNames);
|
||||
|
||||
enterDirectory(new File(TERMUX_HOME));
|
||||
setListAdapter(mAdapter);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
int id = item.getItemId();
|
||||
|
||||
@@ -6,6 +6,7 @@ import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
@@ -18,32 +19,32 @@ public class TermuxFilePickerProvider extends ContentProvider {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
|
||||
public Cursor query(@NonNull Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType(Uri uri) {
|
||||
public String getType(@NonNull Uri uri) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri insert(Uri uri, ContentValues values) {
|
||||
public Uri insert(@NonNull Uri uri, ContentValues values) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int delete(Uri uri, String selection, String[] selectionArgs) {
|
||||
public int delete(@NonNull Uri uri, String selection, String[] selectionArgs) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
|
||||
public int update(@NonNull Uri uri, ContentValues values, String selection, String[] selectionArgs) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
|
||||
public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) throws FileNotFoundException {
|
||||
File file = new File(uri.getPath());
|
||||
return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
|
||||
}
|
||||
|
||||
@@ -33,8 +33,7 @@ public final class TerminalEmulator {
|
||||
private static final boolean LOG_ESCAPE_SEQUENCES = false;
|
||||
|
||||
public static final int MOUSE_LEFT_BUTTON = 0;
|
||||
public static final int MOUSE_MIDDLE_BUTTON = 1;
|
||||
public static final int MOUSE_RIGHT_BUTTON = 2;
|
||||
|
||||
/** Mouse moving while having left mouse button pressed. */
|
||||
public static final int MOUSE_LEFT_BUTTON_MOVED = 32;
|
||||
public static final int MOUSE_WHEELUP_BUTTON = 64;
|
||||
@@ -1444,7 +1443,7 @@ public final class TerminalEmulator {
|
||||
final int linesToScrollArg = getArg0(1);
|
||||
final int linesBetweenTopAndBottomMargins = mBottomMargin - mTopMargin;
|
||||
final int linesToScroll = Math.min(linesBetweenTopAndBottomMargins, linesToScrollArg);
|
||||
mScreen.blockCopy(0, mTopMargin, mColumns, linesBetweenTopAndBottomMargins - linesToScroll, 0, linesToScroll);
|
||||
mScreen.blockCopy(0, mTopMargin, mColumns, linesBetweenTopAndBottomMargins - linesToScroll, 0, mTopMargin + linesToScroll);
|
||||
blockClear(0, mTopMargin, mColumns, linesToScroll);
|
||||
} else {
|
||||
// "${CSI}${func};${startx};${starty};${firstrow};${lastrow}T" - initiate highlight mouse tracking.
|
||||
@@ -2156,15 +2155,22 @@ public final class TerminalEmulator {
|
||||
|
||||
final boolean autoWrap = isDecsetInternalBitSet(DECSET_BIT_AUTOWRAP);
|
||||
final int displayWidth = WcWidth.width(codePoint);
|
||||
final boolean cursorInLastColumn = mCursorCol == mRightMargin - 1;
|
||||
|
||||
if (autoWrap && (mCursorCol == mRightMargin - 1 && ((mAboutToAutoWrap && displayWidth == 1) || displayWidth == 2))) {
|
||||
mScreen.setLineWrap(mCursorRow);
|
||||
mCursorCol = mLeftMargin;
|
||||
if (mCursorRow + 1 < mBottomMargin) {
|
||||
mCursorRow++;
|
||||
} else {
|
||||
scrollDownOneLine();
|
||||
if (autoWrap) {
|
||||
if (cursorInLastColumn && ((mAboutToAutoWrap && displayWidth == 1) || displayWidth == 2)) {
|
||||
mScreen.setLineWrap(mCursorRow);
|
||||
mCursorCol = mLeftMargin;
|
||||
if (mCursorRow + 1 < mBottomMargin) {
|
||||
mCursorRow++;
|
||||
} else {
|
||||
scrollDownOneLine();
|
||||
}
|
||||
}
|
||||
} else if (cursorInLastColumn && displayWidth == 2) {
|
||||
// The behaviour when a wide character is output with cursor in the last column when
|
||||
// autowrap is disabled is not obvious - it's ignored here.
|
||||
return;
|
||||
}
|
||||
|
||||
if (mInsertMode && displayWidth > 0) {
|
||||
|
||||
@@ -6,7 +6,7 @@ import android.view.MotionEvent;
|
||||
import android.view.ScaleGestureDetector;
|
||||
|
||||
/** A combination of {@link GestureDetector} and {@link ScaleGestureDetector}. */
|
||||
public class GestureAndScaleRecognizer {
|
||||
public final class GestureAndScaleRecognizer {
|
||||
|
||||
public interface Listener {
|
||||
boolean onSingleTapUp(MotionEvent e);
|
||||
@@ -29,6 +29,7 @@ public class GestureAndScaleRecognizer {
|
||||
private final GestureDetector mGestureDetector;
|
||||
private final ScaleGestureDetector mScaleDetector;
|
||||
final Listener mListener;
|
||||
boolean isAfterLongPress;
|
||||
|
||||
public GestureAndScaleRecognizer(Context context, Listener listener) {
|
||||
mListener = listener;
|
||||
@@ -52,6 +53,7 @@ public class GestureAndScaleRecognizer {
|
||||
@Override
|
||||
public void onLongPress(MotionEvent e) {
|
||||
mListener.onLongPress(e);
|
||||
isAfterLongPress = true;
|
||||
}
|
||||
}, null, true /* ignoreMultitouch */);
|
||||
|
||||
@@ -88,8 +90,17 @@ public class GestureAndScaleRecognizer {
|
||||
public void onTouchEvent(MotionEvent event) {
|
||||
mGestureDetector.onTouchEvent(event);
|
||||
mScaleDetector.onTouchEvent(event);
|
||||
if (event.getAction() == MotionEvent.ACTION_UP) {
|
||||
mListener.onUp(event);
|
||||
switch (event.getAction()) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
isAfterLongPress = false;
|
||||
break;
|
||||
case MotionEvent.ACTION_UP:
|
||||
if (!isAfterLongPress) {
|
||||
// This behaviour is desired when in e.g. vim with mouse events, where we do not
|
||||
// want to move the cursor when lifting finger after a long press.
|
||||
mListener.onUp(event);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,4 +108,4 @@ public class GestureAndScaleRecognizer {
|
||||
return mScaleDetector.isInProgress();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ import android.view.ScaleGestureDetector;
|
||||
/**
|
||||
* Input and scale listener which may be set on a {@link TerminalView} through
|
||||
* {@link TerminalView#setOnKeyListener(TerminalKeyListener)}.
|
||||
*
|
||||
* TODO: Rename to TerminalViewClient.
|
||||
*/
|
||||
public interface TerminalKeyListener {
|
||||
|
||||
@@ -17,4 +19,6 @@ public interface TerminalKeyListener {
|
||||
/** On a single tap on the terminal if terminal mouse reporting not enabled. */
|
||||
void onSingleTapUp(MotionEvent e);
|
||||
|
||||
boolean shouldBackButtonBeMappedToEscape();
|
||||
|
||||
}
|
||||
|
||||
@@ -1,18 +1,6 @@
|
||||
package com.termux.view;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.util.Properties;
|
||||
|
||||
import com.termux.terminal.EmulatorDebug;
|
||||
import com.termux.terminal.KeyHandler;
|
||||
import com.termux.terminal.TerminalColors;
|
||||
import com.termux.terminal.TerminalEmulator;
|
||||
import com.termux.terminal.TerminalSession;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Typeface;
|
||||
@@ -31,6 +19,17 @@ import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputConnection;
|
||||
import android.widget.Scroller;
|
||||
|
||||
import com.termux.terminal.EmulatorDebug;
|
||||
import com.termux.terminal.KeyHandler;
|
||||
import com.termux.terminal.TerminalColors;
|
||||
import com.termux.terminal.TerminalEmulator;
|
||||
import com.termux.terminal.TerminalSession;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.util.Properties;
|
||||
|
||||
/** View displaying and interacting with a {@link TerminalSession}. */
|
||||
public final class TerminalView extends View {
|
||||
|
||||
@@ -492,9 +491,8 @@ public final class TerminalView extends View {
|
||||
@Override
|
||||
public boolean onKeyPreIme(int keyCode, KeyEvent event) {
|
||||
if (LOG_KEY_EVENTS) Log.i(EmulatorDebug.LOG_TAG, "onKeyPreIme(keyCode=" + keyCode + ", event=" + event + ")");
|
||||
if (keyCode == KeyEvent.KEYCODE_ESCAPE || keyCode == KeyEvent.KEYCODE_BACK) {
|
||||
// Handle the escape key ourselves to avoid the system from treating it as back key
|
||||
// and e.g. close keyboard.
|
||||
if (keyCode == KeyEvent.KEYCODE_BACK && mOnKeyListener.shouldBackButtonBeMappedToEscape()) {
|
||||
// Intercept back button to treat it as escape:
|
||||
switch (event.getAction()) {
|
||||
case KeyEvent.ACTION_DOWN:
|
||||
return onKeyDown(keyCode, event);
|
||||
@@ -518,7 +516,7 @@ public final class TerminalView extends View {
|
||||
if (handleVirtualKeys(keyCode, event, true)) {
|
||||
invalidate();
|
||||
return true;
|
||||
} else if (event.isSystem() && keyCode != KeyEvent.KEYCODE_BACK) {
|
||||
} else if (event.isSystem() && (!mOnKeyListener.shouldBackButtonBeMappedToEscape() || keyCode != KeyEvent.KEYCODE_BACK)) {
|
||||
return super.onKeyDown(keyCode, event);
|
||||
}
|
||||
|
||||
@@ -712,67 +710,28 @@ public final class TerminalView extends View {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void checkForTypeface() {
|
||||
new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
File fontFile = new File(getContext().getFilesDir().getPath() + "/home/.termux/font.ttf");
|
||||
final Typeface newTypeface = fontFile.exists() ? Typeface.createFromFile(fontFile) : Typeface.MONOSPACE;
|
||||
if (newTypeface != mRenderer.mTypeface) {
|
||||
((Activity) getContext()).runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
mRenderer = new TerminalRenderer(mRenderer.mTextSize, newTypeface);
|
||||
updateSize();
|
||||
invalidate();
|
||||
} catch (Exception e) {
|
||||
Log.e(EmulatorDebug.LOG_TAG, "Error loading font", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(EmulatorDebug.LOG_TAG, "Error loading font", e);
|
||||
}
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
public void checkForFontAndColors() {
|
||||
try {
|
||||
File fontFile = new File("/data/data/com.termux/files/home/.termux/font.ttf");
|
||||
File colorsFile = new File("/data/data/com.termux/files/home/.termux/colors.properties");
|
||||
|
||||
public void checkForColors() {
|
||||
new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
File colorsFile = new File(getContext().getFilesDir().getPath() + "/home/.termux/colors.properties");
|
||||
final Properties props = colorsFile.isFile() ? new Properties() : null;
|
||||
if (props != null) {
|
||||
try (InputStream in = new FileInputStream(colorsFile)) {
|
||||
props.load(in);
|
||||
}
|
||||
}
|
||||
((Activity) getContext()).runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
if (props == null) {
|
||||
TerminalColors.COLOR_SCHEME.reset();
|
||||
} else {
|
||||
TerminalColors.COLOR_SCHEME.updateWith(props);
|
||||
}
|
||||
if (mEmulator != null) mEmulator.mColors.reset();
|
||||
invalidate();
|
||||
} catch (Exception e) {
|
||||
Log.e(EmulatorDebug.LOG_TAG, "Setting colors failed: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
Log.e(EmulatorDebug.LOG_TAG, "Failed colors handling", e);
|
||||
final Properties props = new Properties();
|
||||
if (colorsFile.isFile()) {
|
||||
try (InputStream in = new FileInputStream(colorsFile)) {
|
||||
props.load(in);
|
||||
}
|
||||
}
|
||||
}.start();
|
||||
TerminalColors.COLOR_SCHEME.updateWith(props);
|
||||
if (mEmulator != null) mEmulator.mColors.reset();
|
||||
|
||||
final Typeface newTypeface = fontFile.exists() ? Typeface.createFromFile(fontFile) : Typeface.MONOSPACE;
|
||||
mRenderer = new TerminalRenderer(mRenderer.mTextSize, newTypeface);
|
||||
updateSize();
|
||||
|
||||
invalidate();
|
||||
} catch (Exception e) {
|
||||
Log.e(EmulatorDebug.LOG_TAG, "Error in checkForFontAndColors()", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,12 +9,13 @@
|
||||
|
||||
<ListView android:id="@android:id/list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:drawSelectorOnTop="false"/>
|
||||
|
||||
<TextView android:id="@android:id/empty"
|
||||
android:gravity="center"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:text="$HOME is empty!"/>
|
||||
android:text="@string/empty_folder"/>
|
||||
</LinearLayout>
|
||||
|
||||
BIN
app/src/main/res/raw/bell.ogg
Normal file
BIN
app/src/main/res/raw/bell.ogg
Normal file
Binary file not shown.
@@ -1,7 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="application_name">Termux</string>
|
||||
<string name="application_help">Termux help</string>
|
||||
<string name="shared_user_label">Termux user</string>
|
||||
<string name="new_session">New session</string>
|
||||
<string name="new_session_normal_unnamed">Normal - unnamed</string>
|
||||
@@ -56,4 +55,6 @@
|
||||
<string name="notification_action_wakelock">Wake</string>
|
||||
<string name="notification_action_wifilock">Wifi</string>
|
||||
|
||||
<string name="empty_folder">Empty folder.</string>
|
||||
|
||||
</resources>
|
||||
|
||||
5
app/src/main/res/xml/backupscheme.xml
Normal file
5
app/src/main/res/xml/backupscheme.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<full-backup-content>
|
||||
<!-- See https://developer.android.com/training/backup/autosyncapi.html -->
|
||||
<include domain="file" path="home/backup" />
|
||||
</full-backup-content>
|
||||
25
app/src/test/java/com/termux/app/TermuxActivityTest.java
Normal file
25
app/src/test/java/com/termux/app/TermuxActivityTest.java
Normal file
@@ -0,0 +1,25 @@
|
||||
package com.termux.app;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
|
||||
public class TermuxActivityTest extends TestCase {
|
||||
|
||||
private void assertUrlsAre(String text, String... urls) {
|
||||
LinkedHashSet<String> expected = new LinkedHashSet<>();
|
||||
Collections.addAll(expected, urls);
|
||||
assertEquals(expected, TermuxActivity.extractUrls(text));
|
||||
}
|
||||
|
||||
public void testExtractUrls() {
|
||||
assertUrlsAre("hello http://example.com world", "http://example.com");
|
||||
|
||||
assertUrlsAre("http://example.com\nhttp://another.com", "http://example.com", "http://another.com");
|
||||
|
||||
assertUrlsAre("hello http://example.com world and http://more.example.com with secure https://more.example.com",
|
||||
"http://example.com", "http://more.example.com", "https://more.example.com");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -17,21 +17,21 @@ public class ByteQueueTest extends TestCase {
|
||||
|
||||
public void testCompleteWrites() throws Exception {
|
||||
ByteQueue q = new ByteQueue(10);
|
||||
assertEquals(true, q.write(new byte[] { 1, 2, 3 }, 0, 3));
|
||||
assertEquals(true, q.write(new byte[]{1, 2, 3}, 0, 3));
|
||||
|
||||
byte[] arr = new byte[10];
|
||||
assertEquals(3, q.read(arr, true));
|
||||
assertArrayEquals(new byte[] { 1, 2, 3 }, new byte[] { arr[0], arr[1], arr[2] });
|
||||
assertArrayEquals(new byte[]{1, 2, 3}, new byte[]{arr[0], arr[1], arr[2]});
|
||||
|
||||
assertEquals(true, q.write(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }, 0, 10));
|
||||
assertEquals(true, q.write(new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, 0, 10));
|
||||
assertEquals(10, q.read(arr, true));
|
||||
assertArrayEquals(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }, arr);
|
||||
assertArrayEquals(new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, arr);
|
||||
}
|
||||
|
||||
public void testQueueWraparound() throws Exception {
|
||||
ByteQueue q = new ByteQueue(10);
|
||||
|
||||
byte[] origArray = new byte[] { 1, 2, 3, 4, 5, 6 };
|
||||
byte[] origArray = new byte[]{1, 2, 3, 4, 5, 6};
|
||||
byte[] readArray = new byte[origArray.length];
|
||||
for (int i = 0; i < 20; i++) {
|
||||
q.write(origArray, 0, origArray.length);
|
||||
@@ -43,7 +43,7 @@ public class ByteQueueTest extends TestCase {
|
||||
public void testWriteNotesClosing() throws Exception {
|
||||
ByteQueue q = new ByteQueue(10);
|
||||
q.close();
|
||||
assertEquals(false, q.write(new byte[] { 1, 2, 3 }, 0, 3));
|
||||
assertEquals(false, q.write(new byte[]{1, 2, 3}, 0, 3));
|
||||
}
|
||||
|
||||
public void testReadNonBlocking() throws Exception {
|
||||
|
||||
@@ -4,13 +4,13 @@ package com.termux.terminal;
|
||||
* <pre>
|
||||
* "CSI ? Pm h", DEC Private Mode Set (DECSET)
|
||||
* </pre>
|
||||
*
|
||||
* <p/>
|
||||
* and
|
||||
*
|
||||
* <p/>
|
||||
* <pre>
|
||||
* "CSI ? Pm l", DEC Private Mode Reset (DECRST)
|
||||
* </pre>
|
||||
*
|
||||
* <p/>
|
||||
* controls various aspects of the terminal
|
||||
*/
|
||||
public class DecSetTest extends TerminalTestCase {
|
||||
@@ -59,4 +59,15 @@ public class DecSetTest extends TerminalTestCase {
|
||||
assertEquals("Terminal reset() should disable bracketed paste mode", "a", mOutput.getOutputAndClear());
|
||||
}
|
||||
|
||||
/** DECSET 7, DECAWM, controls wraparound mode. */
|
||||
public void testWrapAroundMode() {
|
||||
// Default with wraparound:
|
||||
withTerminalSized(3, 3).enterString("abcd").assertLinesAre("abc", "d ", " ");
|
||||
// With wraparound disabled:
|
||||
withTerminalSized(3, 3).enterString("\033[?7labcd").assertLinesAre("abd", " ", " ");
|
||||
enterString("efg").assertLinesAre("abg", " ", " ");
|
||||
// Re-enabling wraparound:
|
||||
enterString("\033[?7hhij").assertLinesAre("abh", "ij ", " ");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.termux.terminal;
|
||||
|
||||
import android.view.KeyEvent;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
public class KeyHandlerTest extends TestCase {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package com.termux.terminal;
|
||||
|
||||
import android.util.Base64;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import android.util.Base64;
|
||||
|
||||
/** "ESC ]" is the Operating System Command. */
|
||||
public class OperatingSystemControlTest extends TerminalTestCase {
|
||||
|
||||
@@ -15,7 +15,7 @@ public class OperatingSystemControlTest extends TerminalTestCase {
|
||||
withTerminalSized(10, 10);
|
||||
enterString("\033]0;Hello, world\007");
|
||||
assertEquals("Hello, world", mTerminal.getTitle());
|
||||
expectedTitleChanges.add(new ChangedTitle((String) null, "Hello, world"));
|
||||
expectedTitleChanges.add(new ChangedTitle(null, "Hello, world"));
|
||||
assertEquals(expectedTitleChanges, mOutput.titleChanges);
|
||||
|
||||
enterString("\033]0;Goodbye, world\007");
|
||||
|
||||
@@ -2,7 +2,7 @@ package com.termux.terminal;
|
||||
|
||||
/**
|
||||
* ${CSI}${top};${bottom}r" - set Scrolling Region [top;bottom] (default = full size of window) (DECSTBM).
|
||||
*
|
||||
* <p/>
|
||||
* "DECSTBM moves the cursor to column 1, line 1 of the page" (http://www.vt100.net/docs/vt510-rm/DECSTBM).
|
||||
*/
|
||||
public class ScrollRegionTest extends TerminalTestCase {
|
||||
@@ -94,4 +94,8 @@ public class ScrollRegionTest extends TerminalTestCase {
|
||||
withTerminalSized(3, 3).enterString("\033[?69h\033[0;2sABCD\0339").assertLinesAre("B ", "D ", " ");
|
||||
}
|
||||
|
||||
public void testScrollDownWithScrollRegion() {
|
||||
withTerminalSized(2, 5).enterString("1\r\n2\r\n3\r\n4\r\n5").assertLinesAre("1 ", "2 ", "3 ", "4 ", "5 ");
|
||||
enterString("\033[3r").enterString("\033[2T").assertLinesAre("1 ", "2 ", " ", " ", "3 ");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package com.termux.terminal;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Random;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
public class TerminalRowTest extends TestCase {
|
||||
|
||||
/** The properties of these code points are validated in {@link #testStaticConstants()}. */
|
||||
@@ -96,7 +96,7 @@ public class TerminalRowTest extends TestCase {
|
||||
assertEquals(80, row.getSpaceUsed());
|
||||
assertColumnCharIndicesStartsWith(0, 1, 2, 3);
|
||||
|
||||
char[] someChars = new char[] { 'a', 'c', 'e', '4', '5', '6', '7', '8' };
|
||||
char[] someChars = new char[]{'a', 'c', 'e', '4', '5', '6', '7', '8'};
|
||||
|
||||
char[] rawLine = new char[80];
|
||||
Arrays.fill(rawLine, ' ');
|
||||
@@ -373,13 +373,13 @@ public class TerminalRowTest extends TestCase {
|
||||
assertEquals(0, WcWidth.width(0x009F));
|
||||
assertEquals(1, Character.charCount(0x009F));
|
||||
|
||||
int[] points = new int[] { 0xC2541, 'a', '8', 0x73EE, 0x009F, 0x881F, 0x8324, 0xD4C9, 0xFFFD, 'B', 0x009B, 0x61C9, 'Z' };
|
||||
int[] points = new int[]{0xC2541, 'a', '8', 0x73EE, 0x009F, 0x881F, 0x8324, 0xD4C9, 0xFFFD, 'B', 0x009B, 0x61C9, 'Z'};
|
||||
// int[] expected = new int[] { TerminalEmulator.UNICODE_REPLACEMENT_CHAR, 'a', '8', 0x73EE, 0x009F, 0x881F, 0x8324, 0xD4C9, 0xFFFD,
|
||||
// 'B', 0x009B, 0x61C9, 'Z' };
|
||||
int currentColumn = 0;
|
||||
for (int i = 0; i < points.length; i++) {
|
||||
row.setChar(currentColumn, points[i], 0);
|
||||
currentColumn += WcWidth.width(points[i]);
|
||||
for (int point : points) {
|
||||
row.setChar(currentColumn, point, 0);
|
||||
currentColumn += WcWidth.width(point);
|
||||
}
|
||||
// assertLineStartsWith(points);
|
||||
// assertEquals(Character.highSurrogate(0xC2541), line.mText[0]);
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package com.termux.terminal;
|
||||
|
||||
import junit.framework.AssertionFailedError;
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
@@ -9,16 +12,10 @@ import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import junit.framework.AssertionFailedError;
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import com.termux.terminal.TerminalEmulator;
|
||||
import com.termux.terminal.TerminalOutput;
|
||||
|
||||
public abstract class TerminalTestCase extends TestCase {
|
||||
|
||||
public static class MockTerminalOutput extends TerminalOutput {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
public final List<ChangedTitle> titleChanges = new ArrayList<>();
|
||||
public final List<String> clipboardPuts = new ArrayList<>();
|
||||
public int bellsRung = 0;
|
||||
@@ -57,7 +54,7 @@ public abstract class TerminalTestCase extends TestCase {
|
||||
public TerminalEmulator mTerminal;
|
||||
public MockTerminalOutput mOutput;
|
||||
|
||||
public static class ChangedTitle {
|
||||
public static final class ChangedTitle {
|
||||
final String oldTitle;
|
||||
final String newTitle;
|
||||
|
||||
@@ -68,6 +65,7 @@ public abstract class TerminalTestCase extends TestCase {
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof ChangedTitle)) return false;
|
||||
ChangedTitle other = (ChangedTitle) o;
|
||||
return Objects.equals(oldTitle, other.oldTitle) && Objects.equals(newTitle, other.newTitle);
|
||||
}
|
||||
@@ -115,8 +113,8 @@ public abstract class TerminalTestCase extends TestCase {
|
||||
}
|
||||
}
|
||||
|
||||
private static class LineWrapper {
|
||||
TerminalRow mLine;
|
||||
private static final class LineWrapper {
|
||||
final TerminalRow mLine;
|
||||
|
||||
public LineWrapper(TerminalRow line) {
|
||||
mLine = line;
|
||||
@@ -129,7 +127,7 @@ public abstract class TerminalTestCase extends TestCase {
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return ((LineWrapper) o).mLine == mLine;
|
||||
return o instanceof LineWrapper && ((LineWrapper) o).mLine == mLine;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,10 +4,10 @@ import junit.framework.TestCase;
|
||||
|
||||
public class TextStyleTest extends TestCase {
|
||||
|
||||
private static final int[] ALL_EFFECTS = new int[] { 0, TextStyle.CHARACTER_ATTRIBUTE_BOLD, TextStyle.CHARACTER_ATTRIBUTE_ITALIC,
|
||||
private static final int[] ALL_EFFECTS = new int[]{0, TextStyle.CHARACTER_ATTRIBUTE_BOLD, TextStyle.CHARACTER_ATTRIBUTE_ITALIC,
|
||||
TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE, TextStyle.CHARACTER_ATTRIBUTE_BLINK, TextStyle.CHARACTER_ATTRIBUTE_INVERSE,
|
||||
TextStyle.CHARACTER_ATTRIBUTE_INVISIBLE, TextStyle.CHARACTER_ATTRIBUTE_STRIKETHROUGH, TextStyle.CHARACTER_ATTRIBUTE_PROTECTED,
|
||||
TextStyle.CHARACTER_ATTRIBUTE_DIM };
|
||||
TextStyle.CHARACTER_ATTRIBUTE_DIM};
|
||||
|
||||
public void testEncodingSingle() {
|
||||
for (int fx : ALL_EFFECTS) {
|
||||
@@ -34,7 +34,7 @@ public class TextStyleTest extends TestCase {
|
||||
public void testEncodingStrikeThrough() {
|
||||
int encoded = TextStyle.encode(TextStyle.COLOR_INDEX_FOREGROUND, TextStyle.COLOR_INDEX_BACKGROUND,
|
||||
TextStyle.CHARACTER_ATTRIBUTE_STRIKETHROUGH);
|
||||
assertTrue((TextStyle.decodeEffect(encoded) | TextStyle.CHARACTER_ATTRIBUTE_STRIKETHROUGH) != 0);
|
||||
assertTrue((TextStyle.decodeEffect(encoded) & TextStyle.CHARACTER_ATTRIBUTE_STRIKETHROUGH) != 0);
|
||||
}
|
||||
|
||||
public void testEncodingProtected() {
|
||||
|
||||
@@ -10,14 +10,14 @@ public class UnicodeInputTest extends TerminalTestCase {
|
||||
// continue with valid successor bytes (see Table 3-7), it must not consume the successor bytes as part of the ill-formed
|
||||
// subsequence whenever those successor bytes themselves constitute part of a well-formed UTF-8 code unit subsequence."
|
||||
withTerminalSized(5, 5);
|
||||
mTerminal.append(new byte[] { (byte) 0b11101111, (byte) 'a' }, 2);
|
||||
mTerminal.append(new byte[]{(byte) 0b11101111, (byte) 'a'}, 2);
|
||||
assertLineIs(0, ((char) TerminalEmulator.UNICODE_REPLACEMENT_CHAR) + "a ");
|
||||
}
|
||||
|
||||
public void testUnassignedCodePoint() throws UnsupportedEncodingException {
|
||||
withTerminalSized(3, 3);
|
||||
// UTF-8 for U+C2541, an unassigned code point:
|
||||
byte[] b = new byte[] { (byte) 0xf3, (byte) 0x82, (byte) 0x95, (byte) 0x81 };
|
||||
byte[] b = new byte[]{(byte) 0xf3, (byte) 0x82, (byte) 0x95, (byte) 0x81};
|
||||
mTerminal.append(b, b.length);
|
||||
enterString("Y");
|
||||
assertEquals(1, Character.charCount(TerminalEmulator.UNICODE_REPLACEMENT_CHAR));
|
||||
@@ -26,10 +26,10 @@ public class UnicodeInputTest extends TerminalTestCase {
|
||||
|
||||
public void testStuff() {
|
||||
withTerminalSized(80, 24);
|
||||
byte[] b = new byte[] { (byte) 0xf3, (byte) 0x82, (byte) 0x95, (byte) 0x81, (byte) 0x61, (byte) 0x38, (byte) 0xe7, (byte) 0x8f,
|
||||
byte[] b = new byte[]{(byte) 0xf3, (byte) 0x82, (byte) 0x95, (byte) 0x81, (byte) 0x61, (byte) 0x38, (byte) 0xe7, (byte) 0x8f,
|
||||
(byte) 0xae, (byte) 0xc2, (byte) 0x9f, (byte) 0xe8, (byte) 0xa0, (byte) 0x9f, (byte) 0xe8, (byte) 0x8c, (byte) 0xa4,
|
||||
(byte) 0xed, (byte) 0x93, (byte) 0x89, (byte) 0xef, (byte) 0xbf, (byte) 0xbd, (byte) 0x42, (byte) 0xc2, (byte) 0x9b,
|
||||
(byte) 0xe6, (byte) 0x87, (byte) 0x89, (byte) 0x5a };
|
||||
(byte) 0xe6, (byte) 0x87, (byte) 0x89, (byte) 0x5a};
|
||||
mTerminal.append(b, b.length);
|
||||
}
|
||||
|
||||
@@ -80,8 +80,16 @@ public class UnicodeInputTest extends TerminalTestCase {
|
||||
public void testOverlongUtf8Encoding() throws Exception {
|
||||
// U+0020 should be encoded as 0x20, 0xc0 0xa0 is an overlong encoding
|
||||
// so should be replaced with the replacement char U+FFFD.
|
||||
withTerminalSized(5, 5).mTerminal.append(new byte[] { (byte) 0xc0, (byte) 0xa0, 'Y' }, 3);
|
||||
withTerminalSized(5, 5).mTerminal.append(new byte[]{(byte) 0xc0, (byte) 0xa0, 'Y'}, 3);
|
||||
assertLineIs(0, "\uFFFDY ");
|
||||
}
|
||||
|
||||
public void testWideCharacterWithoutWrapping() throws Exception {
|
||||
// With wraparound disabled. The behaviour when a wide character is output with cursor in
|
||||
// the last column when autowrap is disabled is not obvious, but we expect the wide
|
||||
// character to be ignored here.
|
||||
withTerminalSized(3, 3).enterString("\033[?7l").enterString("枝枝枝").assertLinesAre("枝 ", " ", " ");
|
||||
enterString("a枝").assertLinesAre("枝a", " ", " ");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,6 @@
|
||||
#Tue Nov 17 21:52:59 CET 2015
|
||||
#Wed Dec 23 01:44:47 CET 2015
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-2.9-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-bin.zip
|
||||
|
||||
90
gradlew.bat
vendored
90
gradlew.bat
vendored
@@ -1,90 +0,0 @@
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windowz variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
if "%@eval[2+2]" == "4" goto 4NT_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
goto execute
|
||||
|
||||
:4NT_args
|
||||
@rem Get arguments from the 4NT Shell from JP Software
|
||||
set CMD_LINE_ARGS=%$
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
BIN
travis.keystore
BIN
travis.keystore
Binary file not shown.
Reference in New Issue
Block a user