Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
746dc750df | ||
|
|
7db1f6c5a1 | ||
|
|
b7f3fdf528 | ||
|
|
6e7f777d04 | ||
|
|
fc15bd2355 | ||
|
|
a87cbdd70c | ||
|
|
fb7f7d249e | ||
|
|
026d0b495e | ||
|
|
533fa60516 | ||
|
|
dc086a1e0b | ||
|
|
2a056aeb2e | ||
|
|
9e70ebc2a6 | ||
|
|
9686127f81 | ||
|
|
395c36ee83 | ||
|
|
906ff24e76 | ||
|
|
c8af974852 | ||
|
|
481339e2f5 | ||
|
|
b2ecae63a8 | ||
|
|
a67f798f2f | ||
|
|
d69485b70b | ||
|
|
421dfcca39 | ||
|
|
3aaa0ab267 | ||
|
|
e7f9647beb | ||
|
|
5558f371b4 | ||
|
|
0882ed6470 | ||
|
|
5c02448521 | ||
|
|
17382fb190 | ||
|
|
d6eea83bfc | ||
|
|
51181c2d49 | ||
|
|
480b8a4f7e | ||
|
|
f989157f10 | ||
|
|
0e942f90a6 | ||
|
|
5b8eca46a1 | ||
|
|
493900d60b | ||
|
|
c6d6a63637 | ||
|
|
ca71265f23 | ||
|
|
46c9c4b80e | ||
|
|
6ca055bb25 | ||
|
|
ce7ad530cd | ||
|
|
d0015cbe82 | ||
|
|
9e19217f8f | ||
|
|
048af64093 | ||
|
|
a8f7bf1b6e | ||
|
|
62e229e184 |
@@ -12,3 +12,5 @@ root = true
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
229
.idea/codeStyleSettings.xml
generated
229
.idea/codeStyleSettings.xml
generated
@@ -1,229 +0,0 @@
|
||||
<?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>
|
||||
8
.idea/gradle.xml
generated
8
.idea/gradle.xml
generated
@@ -3,16 +3,20 @@
|
||||
<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" />
|
||||
<option name="modules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
<option value="$PROJECT_DIR$/app" />
|
||||
</set>
|
||||
</option>
|
||||
<option name="myModules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
<option value="$PROJECT_DIR$/app" />
|
||||
</set>
|
||||
</option>
|
||||
</GradleProjectSettings>
|
||||
</option>
|
||||
</component>
|
||||
|
||||
15
.idea/inspectionProfiles/Project_Default.xml
generated
15
.idea/inspectionProfiles/Project_Default.xml
generated
@@ -1,15 +0,0 @@
|
||||
<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
7
.idea/inspectionProfiles/profiles_settings.xml
generated
@@ -1,7 +0,0 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="PROJECT_PROFILE" value="Project Default" />
|
||||
<option name="USE_PROJECT_PROFILE" value="true" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
||||
@@ -5,7 +5,7 @@ android {
|
||||
buildToolsVersion "23.0.2"
|
||||
|
||||
dependencies {
|
||||
compile 'com.android.support:support-annotations:23.1.1'
|
||||
compile 'com.android.support:support-annotations:23.3.0'
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
@@ -18,8 +18,8 @@ android {
|
||||
applicationId "com.termux"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 23
|
||||
versionCode 28
|
||||
versionName "0.28"
|
||||
versionCode 34
|
||||
versionName "0.34"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
|
||||
@@ -44,29 +44,45 @@
|
||||
android:label="@string/application_name" />
|
||||
|
||||
<activity
|
||||
android:name="com.termux.filepicker.TermuxFilePickerActivity"
|
||||
android:name="com.termux.filepicker.TermuxFileReceiverActivity"
|
||||
android:label="@string/application_name"
|
||||
android:theme="@android:style/Theme.Material.Light.DarkActionBar"
|
||||
android:taskAffinity="com.termux.filereceiver"
|
||||
android:excludeFromRecents="true"
|
||||
android:noHistory="true">
|
||||
<!-- Accept multiple file types when sending. -->
|
||||
<intent-filter>
|
||||
<!--
|
||||
http://stackoverflow.com/questions/6486716/using-intent-action-pick-for-specific-path
|
||||
"That said, you should consider ACTION_PICK deprecated. The modern action is ACTION_GET_CONTENT
|
||||
which is much better supported; you will find support of ACTION_PICK spotty and inconsistent.
|
||||
Unfortunately ACTION_GET_CONTENT also does not let you specify a directory."
|
||||
-->
|
||||
<action android:name="android.intent.action.GET_CONTENT" />
|
||||
<action android:name="android.intent.action.SEND"/>
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.OPENABLE" />
|
||||
<data android:mimeType="*/*" />
|
||||
<data android:mimeType="application/*" />
|
||||
<data android:mimeType="audio/*" />
|
||||
<data android:mimeType="image/*" />
|
||||
<data android:mimeType="message/*" />
|
||||
<data android:mimeType="multipart/*" />
|
||||
<data android:mimeType="text/*" />
|
||||
<data android:mimeType="video/*" />
|
||||
</intent-filter>
|
||||
<!-- Be more restrictive for viewing files, restricting ourselves to text files. -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="text/*" />
|
||||
<data android:mimeType="application/json" />
|
||||
<data android:mimeType="application/*xml*" />
|
||||
<data android:mimeType="application/*latex*" />
|
||||
<data android:mimeType="application/javascript" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<provider android:authorities="com.termux.filepicker.provider"
|
||||
android:readPermission="com.termux.filepickder.READ"
|
||||
android:exported="true"
|
||||
<provider
|
||||
android:name=".filepicker.TermuxDocumentsProvider"
|
||||
android:authorities="com.termux.documents"
|
||||
android:grantUriPermissions="true"
|
||||
android:name="com.termux.filepicker.TermuxFilePickerProvider" />
|
||||
android:exported="true"
|
||||
android:permission="android.permission.MANAGE_DOCUMENTS">
|
||||
<intent-filter>
|
||||
<action android:name="android.content.action.DOCUMENTS_PROVIDER" />
|
||||
</intent-filter>
|
||||
</provider>
|
||||
|
||||
<service
|
||||
android:name="com.termux.app.TermuxService"
|
||||
|
||||
@@ -3,24 +3,25 @@ package com.termux.app;
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.res.Configuration;
|
||||
import android.text.Selection;
|
||||
import android.util.TypedValue;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.ViewGroup.LayoutParams;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
final class DialogUtils {
|
||||
public final class DialogUtils {
|
||||
|
||||
public interface TextSetListener {
|
||||
void onTextSet(String text);
|
||||
}
|
||||
|
||||
static void textInput(Activity activity, int titleText, int positiveButtonText, String initialText, final TextSetListener onPositive,
|
||||
int neutralButtonText, final TextSetListener onNeutral) {
|
||||
public static void textInput(Activity activity, int titleText, String initialText,
|
||||
int positiveButtonText, final TextSetListener onPositive,
|
||||
int neutralButtonText, final TextSetListener onNeutral,
|
||||
int negativeButtonText, final TextSetListener onNegative,
|
||||
final DialogInterface.OnDismissListener onDismiss) {
|
||||
final EditText input = new EditText(activity);
|
||||
input.setSingleLine();
|
||||
if (initialText != null) {
|
||||
@@ -57,23 +58,32 @@ final class DialogUtils {
|
||||
public void onClick(DialogInterface d, int whichButton) {
|
||||
onPositive.onTextSet(input.getText().toString());
|
||||
}
|
||||
})
|
||||
.setNegativeButton(android.R.string.cancel, null);
|
||||
|
||||
if (onNeutral != null) {
|
||||
builder.setNeutralButton(neutralButtonText, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
onNeutral.onTextSet(input.getText().toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (onNeutral != null) {
|
||||
builder.setNeutralButton(neutralButtonText, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
onNeutral.onTextSet(input.getText().toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (onNegative == null) {
|
||||
builder.setNegativeButton(android.R.string.cancel, null);
|
||||
} else {
|
||||
builder.setNegativeButton(negativeButtonText, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
onNegative.onTextSet(input.getText().toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (onDismiss != null) builder.setOnDismissListener(onDismiss);
|
||||
|
||||
dialogHolder[0] = builder.create();
|
||||
if ((activity.getResources().getConfiguration().hardKeyboardHidden & Configuration.HARDKEYBOARDHIDDEN_YES) == 0) {
|
||||
// Show soft keyboard unless hardware keyboard available.
|
||||
dialogHolder[0].getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
|
||||
}
|
||||
dialogHolder[0].setCanceledOnTouchOutside(false);
|
||||
dialogHolder[0].show();
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ 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;
|
||||
@@ -287,28 +286,27 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
||||
}
|
||||
});
|
||||
|
||||
newSessionButton.setOnLongClickListener(new OnLongClickListener() {
|
||||
@Override
|
||||
public boolean onLongClick(View v) {
|
||||
Resources res = getResources();
|
||||
DialogUtils.textInput(TermuxActivity.this, R.string.session_new_named_title, R.string.session_new_named_positive_button, null,
|
||||
new DialogUtils.TextSetListener() {
|
||||
@Override
|
||||
public void onTextSet(String text) {
|
||||
addNewSession(false, text);
|
||||
}
|
||||
}, R.string.new_session_failsafe, new DialogUtils.TextSetListener() {
|
||||
@Override
|
||||
public void onTextSet(String text) {
|
||||
addNewSession(true, text);
|
||||
}
|
||||
}
|
||||
);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
newSessionButton.setOnLongClickListener(new OnLongClickListener() {
|
||||
@Override
|
||||
public boolean onLongClick(View v) {
|
||||
DialogUtils.textInput(TermuxActivity.this, R.string.session_new_named_title, null, R.string.session_new_named_positive_button,
|
||||
new DialogUtils.TextSetListener() {
|
||||
@Override
|
||||
public void onTextSet(String text) {
|
||||
addNewSession(false, text);
|
||||
}
|
||||
}, R.string.new_session_failsafe, new DialogUtils.TextSetListener() {
|
||||
@Override
|
||||
public void onTextSet(String text) {
|
||||
addNewSession(true, text);
|
||||
}
|
||||
}
|
||||
, -1, null, null);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
findViewById(R.id.toggle_keyboard_button).setOnClickListener(new OnClickListener() {
|
||||
findViewById(R.id.toggle_keyboard_button).setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
@@ -391,6 +389,9 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
||||
case TermuxPreferences.BELL_VIBRATE:
|
||||
((Vibrator) getSystemService(VIBRATOR_SERVICE)).vibrate(50);
|
||||
break;
|
||||
case TermuxPreferences.BELL_IGNORE:
|
||||
// Ignore the bell character.
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -492,14 +493,13 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
||||
|
||||
@SuppressLint("InflateParams")
|
||||
void renameSession(final TerminalSession sessionToRename) {
|
||||
DialogUtils.textInput(this, R.string.session_rename_title, R.string.session_rename_positive_button, sessionToRename.mSessionName,
|
||||
new DialogUtils.TextSetListener() {
|
||||
@Override
|
||||
public void onTextSet(String text) {
|
||||
sessionToRename.mSessionName = text;
|
||||
}
|
||||
}, -1, null);
|
||||
}
|
||||
DialogUtils.textInput(this, R.string.session_rename_title, sessionToRename.mSessionName, R.string.session_rename_positive_button, new DialogUtils.TextSetListener() {
|
||||
@Override
|
||||
public void onTextSet(String text) {
|
||||
sessionToRename.mSessionName = text;
|
||||
}
|
||||
}, -1, null, -1, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName name) {
|
||||
@@ -525,6 +525,10 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
||||
}
|
||||
|
||||
registerReceiver(mBroadcastReceiever, new IntentFilter(RELOAD_STYLE_ACTION));
|
||||
|
||||
// The current terminal session may have changed while being away, force
|
||||
// a refresh of the displayed terminal:
|
||||
mTerminalView.onScreenUpdated();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -676,7 +680,13 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
||||
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
dialog.dismiss();
|
||||
String url = (String) urls[position];
|
||||
startActivity(Intent.createChooser(new Intent(Intent.ACTION_VIEW, Uri.parse(url)), null));
|
||||
Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
|
||||
try {
|
||||
startActivity(i, null);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
// If no applications match, Android displays a system message.
|
||||
startActivity(Intent.createChooser(i, null));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
@@ -727,21 +737,23 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case CONTEXTMENU_STYLING_ID: {
|
||||
Intent stylingIntent = new Intent();
|
||||
stylingIntent.setClassName("com.termux.styling", "com.termux.styling.TermuxStyleActivity");
|
||||
try {
|
||||
startActivity(stylingIntent);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
new AlertDialog.Builder(this).setMessage(R.string.styling_not_installed)
|
||||
.setPositiveButton(R.string.styling_install, new android.content.DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://play.google.com/store/apps/details?id=com.termux.styling")));
|
||||
}
|
||||
}).setNegativeButton(android.R.string.cancel, null).show();
|
||||
}
|
||||
}
|
||||
case CONTEXTMENU_STYLING_ID: {
|
||||
Intent stylingIntent = new Intent();
|
||||
stylingIntent.setClassName("com.termux.styling", "com.termux.styling.TermuxStyleActivity");
|
||||
try {
|
||||
startActivity(stylingIntent);
|
||||
} catch (ActivityNotFoundException | IllegalArgumentException e) {
|
||||
// The startActivity() call is not documented to throw IllegalArgumentException.
|
||||
// However, crash reporting shows that it sometimes does, so catch it here.
|
||||
new AlertDialog.Builder(this).setMessage(R.string.styling_not_installed)
|
||||
.setPositiveButton(R.string.styling_install, new android.content.DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://play.google.com/store/apps/details?id=com.termux.styling")));
|
||||
}
|
||||
}).setNegativeButton(android.R.string.cancel, null).show();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
case CONTEXTMENU_TOGGLE_FULLSCREEN_ID:
|
||||
toggleImmersive();
|
||||
|
||||
@@ -7,6 +7,7 @@ import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.DialogInterface.OnClickListener;
|
||||
import android.content.DialogInterface.OnDismissListener;
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
import android.system.Os;
|
||||
import android.util.Log;
|
||||
@@ -23,6 +24,7 @@ import java.io.InputStreamReader;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
@@ -176,17 +178,28 @@ final class TermuxInstaller {
|
||||
}.start();
|
||||
}
|
||||
|
||||
/** Get bootstrap zip url for this systems cpu architecture. */
|
||||
static URL determineZipUrl() throws MalformedURLException {
|
||||
String arch = System.getProperty("os.arch");
|
||||
if (arch.startsWith("arm") || arch.equals("aarch64")) {
|
||||
// Handle different arm variants such as armv7l:
|
||||
arch = "arm";
|
||||
} else if (arch.equals("x86_64")) {
|
||||
arch = "i686";
|
||||
}
|
||||
return new URL("https://termux.net/bootstrap/bootstrap-" + arch + ".zip");
|
||||
}
|
||||
/** Get bootstrap zip url for this systems cpu architecture. */
|
||||
static URL determineZipUrl() throws MalformedURLException {
|
||||
String termuxArch = null;
|
||||
// Note that we cannot use System.getProperty("os.arch") since that may give e.g. "aarch64"
|
||||
// while a 64-bit runtime may not be installed (like on the Samsung Galaxy S5 Neo).
|
||||
// Instead we search through the supported abi:s on the device, see:
|
||||
// http://developer.android.com/ndk/guides/abis.html
|
||||
// Note that we search for abi:s in preferred order, and want to avoid installing arm on
|
||||
// an x86 system where arm emulation is available.
|
||||
final String[] androidArchNames = {"arm64-v8a", "x86", "armeabi-v7a"};
|
||||
final String[] termuxArchNames = {"aarch64", "i686", "arm"};
|
||||
|
||||
final List<String> supportedArches = Arrays.asList(Build.SUPPORTED_ABIS);
|
||||
for (int i = 0; i < termuxArchNames.length; i++) {
|
||||
if (supportedArches.contains(androidArchNames[i])) {
|
||||
termuxArch = termuxArchNames[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return new URL("https://termux.net/bootstrap/bootstrap-" + termuxArch + ".zip");
|
||||
}
|
||||
|
||||
/** Delete a folder and all its content or throw. */
|
||||
static void deleteFolder(File fileOrDirectory) {
|
||||
|
||||
@@ -55,7 +55,11 @@ public final class TermuxService extends Service implements SessionChangedCallba
|
||||
/** Intent action to toggle the wifi lock, {@link #mWifiLock}, which this service may hold. */
|
||||
private static final String ACTION_LOCK_WIFI = "com.termux.service_toggle_wifi_lock";
|
||||
/** Intent action to launch a new terminal session. Executed from TermuxWidgetProvider. */
|
||||
private static final String ACTION_EXECUTE = "com.termux.service_execute";
|
||||
public static final String ACTION_EXECUTE = "com.termux.service_execute";
|
||||
|
||||
public static final String EXTRA_ARGUMENTS = "com.termux.execute.arguments";
|
||||
|
||||
public static final String EXTRA_CURRENT_WORKING_DIRECTORY = "com.termux.execute.cwd";
|
||||
|
||||
/** This service is only bound from inside the same process and never uses IPC. */
|
||||
class LocalBinder extends Binder {
|
||||
@@ -113,8 +117,8 @@ public final class TermuxService extends Service implements SessionChangedCallba
|
||||
} else if (ACTION_EXECUTE.equals(action)) {
|
||||
Uri executableUri = intent.getData();
|
||||
String executablePath = (executableUri == null ? null : executableUri.getPath());
|
||||
String[] arguments = (executableUri == null ? null : intent.getStringArrayExtra("com.termux.execute.arguments"));
|
||||
String cwd = intent.getStringExtra("com.termux.execute.cwd");
|
||||
String[] arguments = (executableUri == null ? null : intent.getStringArrayExtra(EXTRA_ARGUMENTS));
|
||||
String cwd = intent.getStringExtra(EXTRA_CURRENT_WORKING_DIRECTORY);
|
||||
TerminalSession newSession = createTermSession(executablePath, arguments, cwd, false);
|
||||
|
||||
// Transform executable path to session name, e.g. "/bin/do-something.sh" => "do something.sh".
|
||||
@@ -237,17 +241,22 @@ public final class TermuxService extends Service implements SessionChangedCallba
|
||||
final String prefixEnv = "PREFIX=" + PREFIX_PATH;
|
||||
final String androidRootEnv = "ANDROID_ROOT=" + System.getenv("ANDROID_ROOT");
|
||||
final String androidDataEnv = "ANDROID_DATA=" + System.getenv("ANDROID_DATA");
|
||||
// EXTERNAL_STORAGE is needed for /system/bin/am to work on at least
|
||||
// Samsung S7 - see https://plus.google.com/110070148244138185604/posts/gp8Lk3aCGp3.
|
||||
final String externalStorageEnv = "EXTERNAL_STORAGE=" + System.getenv("EXTERNAL_STORAGE");
|
||||
String[] env;
|
||||
if (failSafe) {
|
||||
env = new String[] { termEnv, homeEnv, prefixEnv, androidRootEnv, androidDataEnv };
|
||||
// Keep the default path so that system binaries can be used in the failsafe session.
|
||||
final String pathEnv = "PATH=" + System.getenv("PATH");
|
||||
env = new String[] { termEnv, homeEnv, prefixEnv, androidRootEnv, androidDataEnv, pathEnv, externalStorageEnv };
|
||||
} else {
|
||||
final String ps1Env = "PS1=$ ";
|
||||
final String ldEnv = "LD_LIBRARY_PATH=" + PREFIX_PATH + "/lib";
|
||||
final String langEnv = "LANG=en_US.UTF-8";
|
||||
final String pathEnv = "PATH=" + PREFIX_PATH + "/bin:" + PREFIX_PATH + "/bin/applets:" + System.getenv("PATH");
|
||||
final String pathEnv = "PATH=" + PREFIX_PATH + "/bin:" + PREFIX_PATH + "/bin/applets";
|
||||
final String pwdEnv = "PWD=" + cwd;
|
||||
|
||||
env = new String[] { termEnv, homeEnv, prefixEnv, ps1Env, ldEnv, langEnv, pathEnv, pwdEnv, androidRootEnv, androidDataEnv };
|
||||
env = new String[] { termEnv, homeEnv, prefixEnv, ps1Env, ldEnv, langEnv, pathEnv, pwdEnv, androidRootEnv, androidDataEnv, externalStorageEnv };
|
||||
}
|
||||
|
||||
String shellName;
|
||||
|
||||
@@ -0,0 +1,242 @@
|
||||
package com.termux.filepicker;
|
||||
|
||||
import android.content.res.AssetFileDescriptor;
|
||||
import android.database.Cursor;
|
||||
import android.database.MatrixCursor;
|
||||
import android.graphics.Point;
|
||||
import android.os.CancellationSignal;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.provider.DocumentsContract.Document;
|
||||
import android.provider.DocumentsContract.Root;
|
||||
import android.provider.DocumentsProvider;
|
||||
import android.webkit.MimeTypeMap;
|
||||
|
||||
import com.termux.R;
|
||||
import com.termux.app.TermuxService;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
|
||||
/**
|
||||
* A document provider for the Storage Access Framework which exposes the files in the
|
||||
* $HOME/ folder to other apps.
|
||||
* <p>
|
||||
* Note that this replaces providing an activity matching the ACTION_GET_CONTENT intent:
|
||||
* <p>
|
||||
* "A document provider and ACTION_GET_CONTENT should be considered mutually exclusive. If you
|
||||
* support both of them simultaneously, your app will appear twice in the system picker UI,
|
||||
* offering two different ways of accessing your stored data. This would be confusing for users."
|
||||
* - http://developer.android.com/guide/topics/providers/document-provider.html#43
|
||||
*/
|
||||
public class TermuxDocumentsProvider extends DocumentsProvider {
|
||||
|
||||
private static final String ALL_MIME_TYPES = "*/*";
|
||||
|
||||
private static final File BASE_DIR = new File(TermuxService.HOME_PATH);
|
||||
|
||||
|
||||
// The default columns to return information about a root if no specific
|
||||
// columns are requested in a query.
|
||||
private static final String[] DEFAULT_ROOT_PROJECTION = new String[]{
|
||||
Root.COLUMN_ROOT_ID,
|
||||
Root.COLUMN_MIME_TYPES,
|
||||
Root.COLUMN_FLAGS,
|
||||
Root.COLUMN_ICON,
|
||||
Root.COLUMN_TITLE,
|
||||
Root.COLUMN_SUMMARY,
|
||||
Root.COLUMN_DOCUMENT_ID,
|
||||
Root.COLUMN_AVAILABLE_BYTES
|
||||
};
|
||||
|
||||
// The default columns to return information about a document if no specific
|
||||
// columns are requested in a query.
|
||||
private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[]{
|
||||
Document.COLUMN_DOCUMENT_ID,
|
||||
Document.COLUMN_MIME_TYPE,
|
||||
Document.COLUMN_DISPLAY_NAME,
|
||||
Document.COLUMN_LAST_MODIFIED,
|
||||
Document.COLUMN_FLAGS,
|
||||
Document.COLUMN_SIZE
|
||||
};
|
||||
|
||||
@Override
|
||||
public Cursor queryRoots(String[] projection) throws FileNotFoundException {
|
||||
final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_ROOT_PROJECTION);
|
||||
@SuppressWarnings("ConstantConditions") final String applicationName = getContext().getString(R.string.application_name);
|
||||
|
||||
final MatrixCursor.RowBuilder row = result.newRow();
|
||||
row.add(Root.COLUMN_ROOT_ID, getDocIdForFile(BASE_DIR));
|
||||
row.add(Root.COLUMN_DOCUMENT_ID, getDocIdForFile(BASE_DIR));
|
||||
row.add(Root.COLUMN_SUMMARY, null);
|
||||
row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_SEARCH);
|
||||
row.add(Root.COLUMN_TITLE, applicationName);
|
||||
row.add(Root.COLUMN_MIME_TYPES, ALL_MIME_TYPES);
|
||||
row.add(Root.COLUMN_AVAILABLE_BYTES, BASE_DIR.getFreeSpace());
|
||||
row.add(Root.COLUMN_ICON, R.drawable.ic_launcher);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor queryDocument(String documentId, String[] projection) throws FileNotFoundException {
|
||||
final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
|
||||
includeFile(result, documentId, null);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor queryChildDocuments(String parentDocumentId, String[] projection, String sortOrder) throws FileNotFoundException {
|
||||
final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
|
||||
final File parent = getFileForDocId(parentDocumentId);
|
||||
for (File file : parent.listFiles()) {
|
||||
if (!file.getName().startsWith(".")) {
|
||||
includeFile(result, null, file);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParcelFileDescriptor openDocument(final String documentId, String mode, CancellationSignal signal) throws FileNotFoundException {
|
||||
final File file = getFileForDocId(documentId);
|
||||
final int accessMode = ParcelFileDescriptor.parseMode(mode);
|
||||
return ParcelFileDescriptor.open(file, accessMode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AssetFileDescriptor openDocumentThumbnail(String documentId, Point sizeHint, CancellationSignal signal) throws FileNotFoundException {
|
||||
final File file = getFileForDocId(documentId);
|
||||
final ParcelFileDescriptor pfd = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
|
||||
return new AssetFileDescriptor(pfd, 0, file.length());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteDocument(String documentId) throws FileNotFoundException {
|
||||
File file = getFileForDocId(documentId);
|
||||
if (!file.delete()) {
|
||||
throw new FileNotFoundException("Failed to delete document with id " + documentId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDocumentType(String documentId) throws FileNotFoundException {
|
||||
File file = getFileForDocId(documentId);
|
||||
return getMimeType(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor querySearchDocuments(String rootId, String query, String[] projection) throws FileNotFoundException {
|
||||
final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
|
||||
final File parent = getFileForDocId(rootId);
|
||||
|
||||
// This example implementation searches file names for the query and doesn't rank search
|
||||
// results, so we can stop as soon as we find a sufficient number of matches. Other
|
||||
// implementations might rank results and use other data about files, rather than the file
|
||||
// name, to produce a match.
|
||||
final LinkedList<File> pending = new LinkedList<>();
|
||||
pending.add(parent);
|
||||
|
||||
final int MAX_SEARCH_RESULTS = 50;
|
||||
while (!pending.isEmpty() && result.getCount() < MAX_SEARCH_RESULTS) {
|
||||
final File file = pending.removeFirst();
|
||||
// Avoid folders outside the $HOME folders linked in to symlinks (to avoid e.g. search
|
||||
// through the whole SD card).
|
||||
boolean isInsideHome;
|
||||
try {
|
||||
isInsideHome = file.getCanonicalPath().startsWith(TermuxService.HOME_PATH);
|
||||
} catch (IOException e) {
|
||||
isInsideHome = true;
|
||||
}
|
||||
final boolean isHidden = file.getName().startsWith(".");
|
||||
if (isInsideHome && !isHidden) {
|
||||
if (file.isDirectory()) {
|
||||
Collections.addAll(pending, file.listFiles());
|
||||
} else {
|
||||
if (file.getName().toLowerCase().contains(query)) {
|
||||
includeFile(result, null, file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the document id given a file. This document id must be consistent across time as other
|
||||
* applications may save the ID and use it to reference documents later.
|
||||
* <p>
|
||||
* The reverse of @{link #getFileForDocId}.
|
||||
*/
|
||||
private static String getDocIdForFile(File file) {
|
||||
return file.getAbsolutePath();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the file given a document id (the reverse of {@link #getDocIdForFile(File)}).
|
||||
*/
|
||||
private static File getFileForDocId(String docId) throws FileNotFoundException {
|
||||
final File f = new File(docId);
|
||||
if (!f.exists()) throw new FileNotFoundException(f.getAbsolutePath() + " not found");
|
||||
return f;
|
||||
}
|
||||
|
||||
private static String getMimeType(File file) {
|
||||
if (file.isDirectory()) {
|
||||
return Document.MIME_TYPE_DIR;
|
||||
} else {
|
||||
final String name = file.getName();
|
||||
final int lastDot = name.lastIndexOf('.');
|
||||
if (lastDot >= 0) {
|
||||
final String extension = name.substring(lastDot + 1);
|
||||
final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
|
||||
if (mime != null) return mime;
|
||||
}
|
||||
return "application/octet-stream";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a representation of a file to a cursor.
|
||||
*
|
||||
* @param result the cursor to modify
|
||||
* @param docId the document ID representing the desired file (may be null if given file)
|
||||
* @param file the File object representing the desired file (may be null if given docID)
|
||||
*/
|
||||
private void includeFile(MatrixCursor result, String docId, File file)
|
||||
throws FileNotFoundException {
|
||||
if (docId == null) {
|
||||
docId = getDocIdForFile(file);
|
||||
} else {
|
||||
file = getFileForDocId(docId);
|
||||
}
|
||||
|
||||
int flags = 0;
|
||||
if (file.isDirectory()) {
|
||||
if (file.isDirectory() && file.canWrite()) flags |= Document.FLAG_DIR_SUPPORTS_CREATE;
|
||||
} else if (file.canWrite()) {
|
||||
flags |= Document.FLAG_SUPPORTS_WRITE | Document.FLAG_SUPPORTS_DELETE;
|
||||
}
|
||||
|
||||
final String displayName = file.getName();
|
||||
final String mimeType = getMimeType(file);
|
||||
if (mimeType.startsWith("image/")) flags |= Document.FLAG_SUPPORTS_THUMBNAIL;
|
||||
|
||||
final MatrixCursor.RowBuilder row = result.newRow();
|
||||
row.add(Document.COLUMN_DOCUMENT_ID, docId);
|
||||
row.add(Document.COLUMN_DISPLAY_NAME, displayName);
|
||||
row.add(Document.COLUMN_SIZE, file.length());
|
||||
row.add(Document.COLUMN_MIME_TYPE, mimeType);
|
||||
row.add(Document.COLUMN_LAST_MODIFIED, file.lastModified());
|
||||
row.add(Document.COLUMN_FLAGS, flags);
|
||||
row.add(Document.COLUMN_ICON, R.drawable.ic_launcher);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
package com.termux.filepicker;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.ListActivity;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ListView;
|
||||
|
||||
import com.termux.R;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
/** Activity allowing picking files from the $HOME folder. */
|
||||
public class TermuxFilePickerActivity extends ListActivity {
|
||||
|
||||
@SuppressLint("SdCardPath")
|
||||
final String TERMUX_HOME = "/data/data/com.termux/files/home";
|
||||
|
||||
private File mCurrentDirectory;
|
||||
private final List<File> mFiles = new ArrayList<>();
|
||||
private final List<String> mFileNames = new ArrayList<>();
|
||||
private ArrayAdapter mAdapter;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.file_picker);
|
||||
|
||||
mAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, mFileNames);
|
||||
|
||||
enterDirectory(new File(TERMUX_HOME));
|
||||
setListAdapter(mAdapter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
int id = item.getItemId();
|
||||
if (id == android.R.id.home) {
|
||||
enterDirectory(mCurrentDirectory.getParentFile());
|
||||
return true;
|
||||
} else {
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onListItemClick(ListView l, View v, int position, long id) {
|
||||
super.onListItemClick(l, v, position, id);
|
||||
File requestFile = mFiles.get(position);
|
||||
if (requestFile.isDirectory()) {
|
||||
enterDirectory(requestFile);
|
||||
} else {
|
||||
Uri returnUri = Uri.withAppendedPath(Uri.parse("content://com.termux.filepicker.provider/"), requestFile.getAbsolutePath());
|
||||
Intent returnIntent = new Intent().setData(returnUri);
|
||||
returnIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
setResult(Activity.RESULT_OK, returnIntent);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
void enterDirectory(File directory) {
|
||||
getActionBar().setDisplayHomeAsUpEnabled(!directory.getAbsolutePath().equals(TERMUX_HOME));
|
||||
|
||||
String title = directory.getAbsolutePath() + "/";
|
||||
if (title.startsWith(TERMUX_HOME)) {
|
||||
title = "~" + title.substring(TERMUX_HOME.length(), title.length());
|
||||
}
|
||||
setTitle(title);
|
||||
|
||||
mCurrentDirectory = directory;
|
||||
mFiles.clear();
|
||||
mFileNames.clear();
|
||||
mFiles.addAll(Arrays.asList(mCurrentDirectory.listFiles()));
|
||||
|
||||
Collections.sort(mFiles, new Comparator<File>() {
|
||||
@Override
|
||||
public int compare(File f1, File f2) {
|
||||
final String n1 = f1.getName();
|
||||
final String n2 = f2.getName();
|
||||
// Display dot folders last:
|
||||
if (n1.startsWith(".") && !n2.startsWith(".")) {
|
||||
return 1;
|
||||
} else if (n2.startsWith(".") && !n1.startsWith(".")) {
|
||||
return -1;
|
||||
}
|
||||
return n1.compareToIgnoreCase(n2);
|
||||
}
|
||||
});
|
||||
|
||||
for (File file : mFiles) {
|
||||
mFileNames.add(file.getName() + (file.isDirectory() ? "/" : ""));
|
||||
}
|
||||
mAdapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
package com.termux.filepicker;
|
||||
|
||||
|
||||
import android.content.ContentProvider;
|
||||
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;
|
||||
|
||||
/** Provider of files content uris picked from {@link com.termux.filepicker.TermuxFilePickerActivity}. */
|
||||
public class TermuxFilePickerProvider extends ContentProvider {
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor query(@NonNull Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType(@NonNull Uri uri) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri insert(@NonNull Uri uri, ContentValues values) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int delete(@NonNull Uri uri, String selection, String[] selectionArgs) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int update(@NonNull Uri uri, ContentValues values, String selection, String[] selectionArgs) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
package com.termux.filepicker;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.provider.OpenableColumns;
|
||||
import android.util.Log;
|
||||
import android.util.Patterns;
|
||||
|
||||
import com.termux.R;
|
||||
import com.termux.app.DialogUtils;
|
||||
import com.termux.app.TermuxService;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class TermuxFileReceiverActivity extends Activity {
|
||||
|
||||
static final String TERMUX_RECEIVEDIR = TermuxService.FILES_PATH + "/home/downloads";
|
||||
static final String EDITOR_PROGRAM = TermuxService.HOME_PATH + "/bin/termux-file-editor";
|
||||
static final String URL_OPENER_PROGRAM = TermuxService.HOME_PATH + "/bin/termux-url-opener";
|
||||
|
||||
/**
|
||||
* If the activity should be finished when the name input dialog is dismissed. This is disabled
|
||||
* before showing an error dialog, since the act of showing the error dialog will cause the
|
||||
* name input dialog to be implicitly dismissed, and we do not want to finish the activity directly
|
||||
* when showing the error dialog.
|
||||
*/
|
||||
private boolean mFinishOnDismissNameDialog = true;
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
final Intent intent = getIntent();
|
||||
final String action = intent.getAction();
|
||||
final String type = intent.getType();
|
||||
final String scheme = intent.getScheme();
|
||||
|
||||
if (Intent.ACTION_SEND.equals(action) && type != null) {
|
||||
final String sharedText = intent.getStringExtra(Intent.EXTRA_TEXT);
|
||||
final Uri sharedUri = intent.getParcelableExtra(Intent.EXTRA_STREAM);
|
||||
|
||||
if (sharedText != null) {
|
||||
if (Patterns.WEB_URL.matcher(sharedText).matches()) {
|
||||
handleUrlAndFinish(sharedText);
|
||||
} else {
|
||||
String subject = intent.getStringExtra(Intent.EXTRA_SUBJECT);
|
||||
if (subject == null) subject = intent.getStringExtra(Intent.EXTRA_TITLE);
|
||||
if (subject != null) subject += ".txt";
|
||||
promptNameAndSave(new ByteArrayInputStream(sharedText.getBytes(StandardCharsets.UTF_8)), subject);
|
||||
}
|
||||
} else if (sharedUri != null) {
|
||||
handleContentUri(sharedUri, intent.getStringExtra(Intent.EXTRA_TITLE));
|
||||
} else {
|
||||
showErrorDialogAndQuit("Send action without content - nothing to save.");
|
||||
}
|
||||
} else if ("content".equals(scheme)) {
|
||||
handleContentUri(intent.getData(), intent.getStringExtra(Intent.EXTRA_TITLE));
|
||||
} else if ("file".equals(scheme)) {
|
||||
// When e.g. clicking on a downloaded apk:
|
||||
String path = intent.getData().getPath();
|
||||
File file = new File(path);
|
||||
try {
|
||||
FileInputStream in = new FileInputStream(file);
|
||||
promptNameAndSave(in, file.getName());
|
||||
} catch (FileNotFoundException e) {
|
||||
showErrorDialogAndQuit("Cannot open file: " + e.getMessage() + ".");
|
||||
}
|
||||
} else {
|
||||
showErrorDialogAndQuit("Unable to receive any file or URL.");
|
||||
}
|
||||
}
|
||||
|
||||
void showErrorDialogAndQuit(String message) {
|
||||
mFinishOnDismissNameDialog = false;
|
||||
new AlertDialog.Builder(this).setMessage(message).setOnDismissListener(new DialogInterface.OnDismissListener() {
|
||||
@Override
|
||||
public void onDismiss(DialogInterface dialog) {
|
||||
finish();
|
||||
}
|
||||
}).setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
finish();
|
||||
}
|
||||
}).show();
|
||||
}
|
||||
|
||||
void handleContentUri(final Uri uri, String subjectFromIntent) {
|
||||
try {
|
||||
String attachmentFileName = null;
|
||||
|
||||
String[] projection = new String[]{OpenableColumns.DISPLAY_NAME};
|
||||
try (Cursor c = getContentResolver().query(uri, projection, null, null, null)) {
|
||||
if (c != null && c.moveToFirst()) {
|
||||
final int fileNameColumnId = c.getColumnIndex(OpenableColumns.DISPLAY_NAME);
|
||||
if (fileNameColumnId >= 0) attachmentFileName = c.getString(fileNameColumnId);
|
||||
}
|
||||
}
|
||||
|
||||
if (attachmentFileName == null) attachmentFileName = subjectFromIntent;
|
||||
|
||||
InputStream in = getContentResolver().openInputStream(uri);
|
||||
promptNameAndSave(in, attachmentFileName);
|
||||
} catch (Exception e) {
|
||||
showErrorDialogAndQuit("Unable to handle shared content:\n\n" + e.getMessage());
|
||||
Log.e("termux", "handleContentUri(uri=" + uri + ") failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
void promptNameAndSave(final InputStream in, final String attachmentFileName) {
|
||||
DialogUtils.textInput(this, R.string.file_received_title, attachmentFileName, R.string.file_received_edit_button, new DialogUtils.TextSetListener() {
|
||||
@Override
|
||||
public void onTextSet(String text) {
|
||||
File outFile = saveStreamWithName(in, text);
|
||||
if (outFile == null) return;
|
||||
|
||||
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.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Do this for the user if necessary:
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
editorProgramFile.setExecutable(true);
|
||||
|
||||
final Uri scriptUri = new Uri.Builder().scheme("file").path(EDITOR_PROGRAM).build();
|
||||
|
||||
Intent executeIntent = new Intent(TermuxService.ACTION_EXECUTE, scriptUri);
|
||||
executeIntent.setClass(TermuxFileReceiverActivity.this, TermuxService.class);
|
||||
executeIntent.putExtra(TermuxService.EXTRA_ARGUMENTS, new String[]{outFile.getAbsolutePath()});
|
||||
startService(executeIntent);
|
||||
finish();
|
||||
}
|
||||
},
|
||||
R.string.file_received_open_folder_button, new DialogUtils.TextSetListener() {
|
||||
@Override
|
||||
public void onTextSet(String text) {
|
||||
if (saveStreamWithName(in, text) == null) return;
|
||||
|
||||
Intent executeIntent = new Intent(TermuxService.ACTION_EXECUTE);
|
||||
executeIntent.putExtra(TermuxService.EXTRA_CURRENT_WORKING_DIRECTORY, TERMUX_RECEIVEDIR);
|
||||
executeIntent.setClass(TermuxFileReceiverActivity.this, TermuxService.class);
|
||||
startService(executeIntent);
|
||||
finish();
|
||||
}
|
||||
},
|
||||
android.R.string.cancel, new DialogUtils.TextSetListener() {
|
||||
@Override
|
||||
public void onTextSet(final String text) {
|
||||
finish();
|
||||
}
|
||||
}, new DialogInterface.OnDismissListener() {
|
||||
@Override
|
||||
public void onDismiss(DialogInterface dialog) {
|
||||
if (mFinishOnDismissNameDialog) finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public File saveStreamWithName(InputStream in, String attachmentFileName) {
|
||||
File receiveDir = new File(TERMUX_RECEIVEDIR);
|
||||
if (!receiveDir.isDirectory() && !receiveDir.mkdirs()) {
|
||||
showErrorDialogAndQuit("Cannot create directory: " + receiveDir.getAbsolutePath());
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
final File outFile = new File(receiveDir, attachmentFileName);
|
||||
try (FileOutputStream f = new FileOutputStream(outFile)) {
|
||||
byte[] buffer = new byte[4096];
|
||||
int readBytes;
|
||||
while ((readBytes = in.read(buffer)) > 0) {
|
||||
f.write(buffer, 0, readBytes);
|
||||
}
|
||||
}
|
||||
return outFile;
|
||||
} catch (IOException e) {
|
||||
showErrorDialogAndQuit("Error saving file:\n\n" + e);
|
||||
Log.e("termux", "Error saving file", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
void handleUrlAndFinish(final String url) {
|
||||
final File urlOpenerProgramFile = new File(URL_OPENER_PROGRAM);
|
||||
if (!urlOpenerProgramFile.isFile()) {
|
||||
showErrorDialogAndQuit("The following file does not exist:\n$HOME/bin/termux-url-opener\n\n"
|
||||
+ "Create this file as a script or a symlink - it will be called with the shared URL as only argument.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Do this for the user if necessary:
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
urlOpenerProgramFile.setExecutable(true);
|
||||
|
||||
final Uri urlOpenerProgramUri = new Uri.Builder().scheme("file").path(URL_OPENER_PROGRAM).build();
|
||||
|
||||
Intent executeIntent = new Intent(TermuxService.ACTION_EXECUTE, urlOpenerProgramUri);
|
||||
executeIntent.setClass(TermuxFileReceiverActivity.this, TermuxService.class);
|
||||
executeIntent.putExtra(TermuxService.EXTRA_ARGUMENTS, new String[]{url});
|
||||
startService(executeIntent);
|
||||
finish();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -127,10 +127,14 @@ public final class TerminalBuffer {
|
||||
mLines[externalToInternalRow(row)].mLineWrap = true;
|
||||
}
|
||||
|
||||
private boolean getLineWrap(int row) {
|
||||
public boolean getLineWrap(int row) {
|
||||
return mLines[externalToInternalRow(row)].mLineWrap;
|
||||
}
|
||||
|
||||
public void clearLineWrap(int row) {
|
||||
mLines[externalToInternalRow(row)].mLineWrap = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resize the screen which this transcript backs. Currently, this only works if the number of columns does not
|
||||
* change or the rows expand (that is, it only works when shrinking the number of rows).
|
||||
|
||||
@@ -425,9 +425,9 @@ public final class TerminalEmulator {
|
||||
processCodePoint((codePoint & 0x7F) + 0x40);
|
||||
} else {
|
||||
switch (Character.getType(codePoint)) {
|
||||
case Character.UNASSIGNED:
|
||||
case Character.SURROGATE:
|
||||
codePoint = UNICODE_REPLACEMENT_CHAR;
|
||||
case Character.UNASSIGNED:
|
||||
case Character.SURROGATE:
|
||||
codePoint = UNICODE_REPLACEMENT_CHAR;
|
||||
}
|
||||
processCodePoint(codePoint);
|
||||
}
|
||||
@@ -474,16 +474,28 @@ public final class TerminalEmulator {
|
||||
else
|
||||
mSession.onBell();
|
||||
break;
|
||||
case 8: // BS
|
||||
setCursorCol(Math.max(mLeftMargin, mCursorCol - 1));
|
||||
break;
|
||||
case 9: // Horizontal tab - move to next tab stop, but not past edge of screen
|
||||
int nextTabStop = nextTabStop(1);
|
||||
while (mCursorCol < nextTabStop) {
|
||||
// Emit newlines to get background color right.
|
||||
processCodePoint(' ');
|
||||
}
|
||||
break;
|
||||
case 8: // Backspace (BS, ^H).
|
||||
if (mLeftMargin == mCursorCol) {
|
||||
// Jump to previous line if it was auto-wrapped.
|
||||
int previousRow = mCursorRow - 1;
|
||||
if (previousRow >= 0 && mScreen.getLineWrap(previousRow)) {
|
||||
mScreen.clearLineWrap(previousRow);
|
||||
setCursorRowCol(previousRow, mRightMargin - 1);
|
||||
}
|
||||
} else {
|
||||
setCursorCol(mCursorCol - 1);
|
||||
}
|
||||
break;
|
||||
case 9: // Horizontal tab (HT, \t) - move to next tab stop, but not past edge of screen
|
||||
// XXX: Should perhaps use color if writing to new cells. Try with
|
||||
// printf "\033[41m\tXX\033[0m\n"
|
||||
// The OSX Terminal.app colors the spaces from the tab red, but xterm does not.
|
||||
// Note that Terminal.app only colors on new cells, in e.g.
|
||||
// printf "\033[41m\t\r\033[42m\tXX\033[0m\n"
|
||||
// the first cells are created with a red background, but when tabbing over
|
||||
// them again with a green background they are not overwritten.
|
||||
mCursorCol = nextTabStop(1);
|
||||
break;
|
||||
case 10: // Line feed (LF, \n).
|
||||
case 11: // Vertical tab (VT, \v).
|
||||
case 12: // Form feed (FF, \f).
|
||||
@@ -1331,7 +1343,7 @@ public final class TerminalEmulator {
|
||||
continueSequence(ESC_CSI_ARGS_ASTERIX);
|
||||
break;
|
||||
case '@': {
|
||||
// "CSI{n}@" - Insert ${n} space characters (ICH) - http://www.vt100.net/docs/vt510-rm/ICH.
|
||||
// "CSI{n}@" - Insert ${n} space characters (ICH) - http://www.vt100.net/docs/vt510-rm/ICH.
|
||||
mAboutToAutoWrap = false;
|
||||
int columnsAfterCursor = mColumns - mCursorCol;
|
||||
int spacesToInsert = Math.min(getArg0(1), columnsAfterCursor);
|
||||
|
||||
@@ -90,7 +90,7 @@ public final class TerminalSession extends TerminalOutput {
|
||||
|
||||
/**
|
||||
* The file descriptor referencing the master half of a pseudo-terminal pair, resulting from calling
|
||||
* {@link JNI#createSubprocess(String, String, String[], String[], int[])}.
|
||||
* {@link JNI#createSubprocess(String, String, String[], String[], int[], int, int)}.
|
||||
*/
|
||||
private int mTerminalFileDescriptor;
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ import java.util.Properties;
|
||||
public final class TerminalView extends View {
|
||||
|
||||
/** Log view key and IME events. */
|
||||
private static final boolean LOG_KEY_EVENTS = true;
|
||||
private static final boolean LOG_KEY_EVENTS = false;
|
||||
|
||||
/** The currently displayed terminal session, whose emulator is {@link #mEmulator}. */
|
||||
TerminalSession mTermSession;
|
||||
@@ -652,52 +652,55 @@ public final class TerminalView extends View {
|
||||
resultingKeyCode = KeyEvent.KEYCODE_F12;
|
||||
}
|
||||
} else if (mVirtualFnKeyDown) {
|
||||
if (codePoint == 'w' || codePoint == 'W') {
|
||||
resultingKeyCode = KeyEvent.KEYCODE_DPAD_UP;
|
||||
} else if (codePoint == 'a' || codePoint == 'A') {
|
||||
resultingKeyCode = KeyEvent.KEYCODE_DPAD_LEFT;
|
||||
} else if (codePoint == 's' || codePoint == 'S') {
|
||||
resultingKeyCode = KeyEvent.KEYCODE_DPAD_DOWN;
|
||||
} else if (codePoint == 'd' || codePoint == 'D') {
|
||||
resultingKeyCode = KeyEvent.KEYCODE_DPAD_RIGHT;
|
||||
} else if (codePoint == 'p' || codePoint == 'P') {
|
||||
resultingKeyCode = KeyEvent.KEYCODE_PAGE_UP;
|
||||
} else if (codePoint == 'n' || codePoint == 'N') {
|
||||
resultingKeyCode = KeyEvent.KEYCODE_PAGE_DOWN;
|
||||
} else if (codePoint == 't' || codePoint == 'T') {
|
||||
resultingKeyCode = KeyEvent.KEYCODE_TAB;
|
||||
} else if (codePoint == 'l' || codePoint == 'L') {
|
||||
codePoint = '|';
|
||||
} else if (codePoint == 'u' || codePoint == 'U') {
|
||||
codePoint = '_';
|
||||
} else if (codePoint == 'e' || codePoint == 'E') {
|
||||
codePoint = 27; // ^[ (Esc)
|
||||
} else if (codePoint == '.') {
|
||||
codePoint = 28; // ^\
|
||||
} else if (codePoint > '0' && codePoint <= '9') {
|
||||
// F1-F9
|
||||
resultingKeyCode = (codePoint - '1') + KeyEvent.KEYCODE_F1;
|
||||
} else if (codePoint == '0') {
|
||||
resultingKeyCode = KeyEvent.KEYCODE_F10;
|
||||
} else if (codePoint == 'i' || codePoint == 'I') {
|
||||
resultingKeyCode = KeyEvent.KEYCODE_INSERT;
|
||||
} else if (codePoint == 'x' || codePoint == 'X') {
|
||||
resultingKeyCode = KeyEvent.KEYCODE_FORWARD_DEL;
|
||||
} else if (codePoint == 'h' || codePoint == 'H') {
|
||||
resultingKeyCode = KeyEvent.KEYCODE_MOVE_HOME;
|
||||
} else if (codePoint == 'f' || codePoint == 'F') {
|
||||
// As left alt+f, jumping forward in readline:
|
||||
codePoint = 'f';
|
||||
leftAltDownFromEvent = true;
|
||||
} else if (codePoint == 'b' || codePoint == 'B') {
|
||||
// As left alt+b, jumping forward in readline:
|
||||
codePoint = 'b';
|
||||
leftAltDownFromEvent = true;
|
||||
} else if (codePoint == 'v' || codePoint == 'V') {
|
||||
codePoint = -1;
|
||||
AudioManager audio = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
|
||||
audio.adjustSuggestedStreamVolume(AudioManager.ADJUST_SAME, AudioManager.USE_DEFAULT_STREAM_TYPE, AudioManager.FLAG_SHOW_UI);
|
||||
}
|
||||
int lowerCase = Character.toLowerCase(codePoint);
|
||||
switch (lowerCase) {
|
||||
// Arrow keys.
|
||||
case 'w': resultingKeyCode = KeyEvent.KEYCODE_DPAD_UP; break;
|
||||
case 'a': resultingKeyCode = KeyEvent.KEYCODE_DPAD_LEFT; break;
|
||||
case 's': resultingKeyCode = KeyEvent.KEYCODE_DPAD_DOWN; break;
|
||||
case 'd': resultingKeyCode = KeyEvent.KEYCODE_DPAD_RIGHT; break;
|
||||
|
||||
// Page up and down.
|
||||
case 'p': resultingKeyCode = KeyEvent.KEYCODE_PAGE_UP; break;
|
||||
case 'n': resultingKeyCode = KeyEvent.KEYCODE_PAGE_DOWN; break;
|
||||
|
||||
// Some special keys:
|
||||
case 't': resultingKeyCode = KeyEvent.KEYCODE_TAB; break;
|
||||
case 'i': resultingKeyCode = KeyEvent.KEYCODE_INSERT; break;
|
||||
case 'h': resultingKeyCode = KeyEvent.KEYCODE_MOVE_HOME; break;
|
||||
|
||||
// Special characters to input.
|
||||
case 'u': codePoint = '_'; break;
|
||||
case 'l': codePoint = '|'; break;
|
||||
|
||||
// Function keys.
|
||||
case '1': case '2': case '3':
|
||||
case '4': case '5': case '6':
|
||||
case '7': case '8': case '9':
|
||||
resultingKeyCode = (codePoint - '1') + KeyEvent.KEYCODE_F1;
|
||||
break;
|
||||
case '0':
|
||||
resultingKeyCode = KeyEvent.KEYCODE_F10;
|
||||
break;
|
||||
|
||||
// Other special keys.
|
||||
case 'e': codePoint = /*Escape*/ 27; break;
|
||||
case '.': codePoint = /*^.*/ 28; break;
|
||||
|
||||
case 'b': // alt+b, jumping backward in readline.
|
||||
case 'f': // alf+f, jumping forward in readline.
|
||||
case 'x': // alt+x, common in emacs.
|
||||
codePoint = lowerCase;
|
||||
leftAltDownFromEvent = true;
|
||||
break;
|
||||
|
||||
// Volume control.
|
||||
case 'v':
|
||||
codePoint = -1;
|
||||
AudioManager audio = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
|
||||
audio.adjustSuggestedStreamVolume(AudioManager.ADJUST_SAME, AudioManager.USE_DEFAULT_STREAM_TYPE, AudioManager.FLAG_SHOW_UI);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (codePoint > -1) {
|
||||
@@ -779,8 +782,9 @@ public final class TerminalView extends View {
|
||||
|
||||
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");
|
||||
// Hard-coded paths since this file is used also in Termux:Float.
|
||||
@SuppressLint("SdCardPath") File fontFile = new File("/data/data/com.termux/files/home/.termux/font.ttf");
|
||||
@SuppressLint("SdCardPath") File colorsFile = new File("/data/data/com.termux/files/home/.termux/colors.properties");
|
||||
|
||||
final Properties props = new Properties();
|
||||
if (colorsFile.isFile()) {
|
||||
@@ -896,12 +900,16 @@ public final class TerminalView extends View {
|
||||
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
|
||||
final int[] ACTION_MODE_ATTRS = { android.R.attr.actionModeCopyDrawable, android.R.attr.actionModePasteDrawable, };
|
||||
TypedArray styledAttributes = getContext().obtainStyledAttributes(ACTION_MODE_ATTRS);
|
||||
int show = MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT;
|
||||
try {
|
||||
int show = MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT;
|
||||
|
||||
ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
menu.add(Menu.NONE, 1, Menu.NONE, R.string.copy_text).setIcon(styledAttributes.getResourceId(0, 0)).setShowAsAction(show);
|
||||
menu.add(Menu.NONE, 2, Menu.NONE, R.string.paste_text).setIcon(styledAttributes.getResourceId(1, 0)).setEnabled(clipboard.hasPrimaryClip()).setShowAsAction(show);
|
||||
menu.add(Menu.NONE, 3, Menu.NONE, R.string.text_selection_more);
|
||||
ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
menu.add(Menu.NONE, 1, Menu.NONE, R.string.copy_text).setIcon(styledAttributes.getResourceId(0, 0)).setShowAsAction(show);
|
||||
menu.add(Menu.NONE, 2, Menu.NONE, R.string.paste_text).setIcon(styledAttributes.getResourceId(1, 0)).setEnabled(clipboard.hasPrimaryClip()).setShowAsAction(show);
|
||||
menu.add(Menu.NONE, 3, Menu.NONE, R.string.text_selection_more);
|
||||
} finally {
|
||||
styledAttributes.recycle();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
APP_ABI := armeabi-v7a x86
|
||||
APP_ABI := arm64-v8a armeabi-v7a x86
|
||||
APP_PLATFORM := android-21
|
||||
NDK_TOOLCHAIN_VERSION := 4.9
|
||||
APP_CFLAGS := -std=c11 -Wall -Wextra -Os -fno-stack-protector
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingLeft="8dp"
|
||||
android:paddingRight="8dp">
|
||||
|
||||
<ListView android:id="@android:id/list"
|
||||
android:layout_width="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="@string/empty_folder"/>
|
||||
</LinearLayout>
|
||||
@@ -12,7 +12,7 @@
|
||||
<string name="help">Help</string>
|
||||
|
||||
<string name="welcome_dialog_title">Welcome to Termux</string>
|
||||
<string name="welcome_dialog_body">Long press anywhere on the terminal for a context menu where Help is available.\n\nExecute \'apt update\' to update the packages list before installing packages.</string>
|
||||
<string name="welcome_dialog_body">Long press and select <i>More…</i> to show a menu where <i>Help</i> is available.\n\nExecute <b>apt update</b> to update the packages list before installing packages.</string>
|
||||
<string name="welcome_dialog_dont_show_again_button">Do not show again</string>
|
||||
|
||||
<string name="bootstrap_installer_body">Installing…</string>
|
||||
@@ -53,6 +53,8 @@
|
||||
<string name="notification_action_wakelock">Wake</string>
|
||||
<string name="notification_action_wifilock">Wifi</string>
|
||||
|
||||
<string name="empty_folder">Empty folder.</string>
|
||||
<string name="file_received_title">Save file in ~/downloads/</string>
|
||||
<string name="file_received_edit_button">Edit</string>
|
||||
<string name="file_received_open_folder_button">Open folder</string>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -163,7 +163,12 @@ public class CursorAndScreenTest extends TerminalTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
public void testHorizontalTabColorsBackground() {
|
||||
/**
|
||||
* See comments on horizontal tab handling in TerminalEmulator.java.
|
||||
*
|
||||
* We do not want to color already written cells when tabbing over them.
|
||||
*/
|
||||
public void DISABLED_testHorizontalTabColorsBackground() {
|
||||
withTerminalSized(10, 3).enterString("\033[48;5;15m").enterString("\t");
|
||||
assertCursorAt(0, 8);
|
||||
for (int i = 0; i < 10; i++) {
|
||||
@@ -213,4 +218,13 @@ public class CursorAndScreenTest extends TerminalTestCase {
|
||||
" -");
|
||||
}
|
||||
|
||||
public void testBackspaceAcrossWrappedLines() {
|
||||
// Backspace should not go to previous line if not auto-wrapped:
|
||||
withTerminalSized(3, 3).enterString("hi\r\n\b\byou").assertLinesAre("hi ", "you", " ");
|
||||
// Backspace should go to previous line if auto-wrapped:
|
||||
withTerminalSized(3, 3).enterString("hi y").assertLinesAre("hi ", "y ", " ").enterString("\b\b#").assertLinesAre("hi#", "y ", " ");
|
||||
// Initial backspace should do nothing:
|
||||
withTerminalSized(3, 3).enterString("\b\b\b\bhi").assertLinesAre("hi ", " ", " ");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -258,4 +258,9 @@ public class TerminalTest extends TerminalTestCase {
|
||||
withTerminalSized(3, 3).enterString("abc\r ").assertLinesAre(" bc", " ", " ").assertCursorAt(0, 1);
|
||||
}
|
||||
|
||||
public void testTab() {
|
||||
withTerminalSized(11, 2).enterString("01234567890\r\tXX").assertLinesAre("01234567XX0", " ");
|
||||
withTerminalSized(11, 2).enterString("01234567890\033[44m\r\tXX").assertLinesAre("01234567XX0", " ");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ buildscript {
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:1.5.0'
|
||||
classpath 'com.android.tools.build:gradle:2.0.0'
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
|
||||
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 @@
|
||||
#Wed Dec 23 01:44:47 CET 2015
|
||||
#Tue Mar 15 00:24:33 CET 2016
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-2.12-bin.zip
|
||||
|
||||
Reference in New Issue
Block a user