mirror of
https://github.com/fankes/termux-app.git
synced 2025-09-06 18:55:31 +08:00
Implement support for termux.properties
Also some symlink-to-storage improvements and experimenting with requesting read storage permission.
This commit is contained in:
@@ -1,19 +1,8 @@
|
|||||||
package com.termux.app;
|
package com.termux.app;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import android.Manifest;
|
||||||
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.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
|
import android.annotation.TargetApi;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.content.ActivityNotFoundException;
|
import android.content.ActivityNotFoundException;
|
||||||
@@ -27,11 +16,15 @@ import android.content.DialogInterface.OnShowListener;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
import android.content.ServiceConnection;
|
import android.content.ServiceConnection;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.graphics.Paint;
|
import android.graphics.Paint;
|
||||||
import android.graphics.Typeface;
|
import android.graphics.Typeface;
|
||||||
|
import android.media.AudioAttributes;
|
||||||
|
import android.media.SoundPool;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.Vibrator;
|
import android.os.Vibrator;
|
||||||
@@ -64,6 +57,19 @@ import android.widget.ListView;
|
|||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
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.
|
* A terminal emulator activity.
|
||||||
*
|
*
|
||||||
@@ -114,6 +120,11 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
*/
|
*/
|
||||||
boolean mIsVisible;
|
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() {
|
private final BroadcastReceiver mBroadcastReceiever = new BroadcastReceiver() {
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(Context context, Intent intent) {
|
public void onReceive(Context context, Intent intent) {
|
||||||
@@ -125,6 +136,16 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** For processes to access shared internal storage (/sdcard) we need this permission. */
|
||||||
|
@TargetApi(Build.VERSION_CODES.M)
|
||||||
|
public void ensureStoragePermissionGranted() {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
if (checkSelfPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
|
||||||
|
requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1234);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle bundle) {
|
public void onCreate(Bundle bundle) {
|
||||||
super.onCreate(bundle);
|
super.onCreate(bundle);
|
||||||
@@ -229,11 +250,22 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSingleTapUp(MotionEvent e) {
|
public void onSingleTapUp(MotionEvent e) {
|
||||||
|
switch (mSettings.mTapBehaviour) {
|
||||||
|
case TermuxPreferences.TAP_TOGGLE_KEYBOARD:
|
||||||
// Toggle keyboard visibility if tapping with a finger:
|
// Toggle keyboard visibility if tapping with a finger:
|
||||||
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
|
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||||
imm.toggleSoftInput(InputMethodManager.SHOW_IMPLICIT, 0);
|
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() {
|
findViewById(R.id.new_session_button).setOnClickListener(new OnClickListener() {
|
||||||
@@ -294,7 +326,11 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
mTerminalView.checkForTypeface();
|
mTerminalView.checkForTypeface();
|
||||||
mTerminalView.checkForColors();
|
mTerminalView.checkForColors();
|
||||||
|
|
||||||
TermuxInstaller.setupStorageSymlink(this);
|
ensureStoragePermissionGranted();
|
||||||
|
|
||||||
|
TermuxInstaller.setupStorageSymlinks(this);
|
||||||
|
|
||||||
|
mBellSoundId = mBellSoundPool.load(this, R.raw.bell, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -351,7 +387,17 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBell(TerminalSession session) {
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -7,6 +7,7 @@ import android.content.Context;
|
|||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.content.DialogInterface.OnClickListener;
|
import android.content.DialogInterface.OnClickListener;
|
||||||
import android.content.DialogInterface.OnDismissListener;
|
import android.content.DialogInterface.OnDismissListener;
|
||||||
|
import android.os.Environment;
|
||||||
import android.system.Os;
|
import android.system.Os;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
@@ -200,31 +201,50 @@ final class TermuxInstaller {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void setupStorageSymlink(final Context context) {
|
public static void setupStorageSymlinks(final Context context) {
|
||||||
final File[] dirs = context.getExternalFilesDirs(null);
|
|
||||||
if (dirs == null || dirs.length < 2) return;
|
|
||||||
new Thread() {
|
new Thread() {
|
||||||
public void run() {
|
public void run() {
|
||||||
try {
|
try {
|
||||||
final File externalDir = dirs[1];
|
|
||||||
File homeDir = new File(TermuxService.HOME_PATH);
|
File homeDir = new File(TermuxService.HOME_PATH);
|
||||||
homeDir.mkdirs();
|
homeDir.mkdirs();
|
||||||
File externalLink = new File(homeDir, "storage");
|
File storageDir = new File(homeDir, "storage");
|
||||||
|
|
||||||
if (externalLink.exists()) {
|
if (storageDir.exists()) {
|
||||||
if (externalLink.getCanonicalPath().equals(externalDir.getPath())) {
|
if (storageDir.isDirectory()) {
|
||||||
// Keeping existing link.
|
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
// Removing old link to give place to new.
|
storageDir.delete();
|
||||||
if (!externalLink.delete()) {
|
|
||||||
Log.e("termux", "Unable to remove old $HOME/storage to give place for new");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Os.symlink(externalDir.getAbsolutePath(), externalLink.getAbsolutePath());
|
storageDir.mkdirs();
|
||||||
|
|
||||||
|
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 documentsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS);
|
||||||
|
Os.symlink(documentsDir.getAbsolutePath(), new File(storageDir, "documents").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) {
|
} catch (Exception e) {
|
||||||
Log.e("termux", "Error setting up link", e);
|
Log.e("termux", "Error setting up link", e);
|
||||||
}
|
}
|
||||||
|
@@ -6,6 +6,8 @@ import android.view.ScaleGestureDetector;
|
|||||||
/**
|
/**
|
||||||
* Input and scale listener which may be set on a {@link TerminalView} through
|
* Input and scale listener which may be set on a {@link TerminalView} through
|
||||||
* {@link TerminalView#setOnKeyListener(TerminalKeyListener)}.
|
* {@link TerminalView#setOnKeyListener(TerminalKeyListener)}.
|
||||||
|
*
|
||||||
|
* TODO: Rename to TerminalViewClient.
|
||||||
*/
|
*/
|
||||||
public interface TerminalKeyListener {
|
public interface TerminalKeyListener {
|
||||||
|
|
||||||
@@ -17,4 +19,6 @@ public interface TerminalKeyListener {
|
|||||||
/** On a single tap on the terminal if terminal mouse reporting not enabled. */
|
/** On a single tap on the terminal if terminal mouse reporting not enabled. */
|
||||||
void onSingleTapUp(MotionEvent e);
|
void onSingleTapUp(MotionEvent e);
|
||||||
|
|
||||||
|
boolean shouldBackButtonBeMappedToEscape();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -12,7 +12,6 @@ import com.termux.terminal.TerminalEmulator;
|
|||||||
import com.termux.terminal.TerminalSession;
|
import com.termux.terminal.TerminalSession;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
import android.graphics.Typeface;
|
import android.graphics.Typeface;
|
||||||
@@ -492,7 +491,7 @@ public final class TerminalView extends View {
|
|||||||
@Override
|
@Override
|
||||||
public boolean onKeyPreIme(int keyCode, KeyEvent event) {
|
public boolean onKeyPreIme(int keyCode, KeyEvent event) {
|
||||||
if (LOG_KEY_EVENTS) Log.i(EmulatorDebug.LOG_TAG, "onKeyPreIme(keyCode=" + keyCode + ", event=" + event + ")");
|
if (LOG_KEY_EVENTS) Log.i(EmulatorDebug.LOG_TAG, "onKeyPreIme(keyCode=" + keyCode + ", event=" + event + ")");
|
||||||
if (keyCode == KeyEvent.KEYCODE_ESCAPE || keyCode == KeyEvent.KEYCODE_BACK) {
|
if (keyCode == KeyEvent.KEYCODE_ESCAPE || (keyCode == KeyEvent.KEYCODE_BACK && mOnKeyListener.shouldBackButtonBeMappedToEscape())) {
|
||||||
// Handle the escape key ourselves to avoid the system from treating it as back key
|
// Handle the escape key ourselves to avoid the system from treating it as back key
|
||||||
// and e.g. close keyboard.
|
// and e.g. close keyboard.
|
||||||
switch (event.getAction()) {
|
switch (event.getAction()) {
|
||||||
@@ -518,7 +517,7 @@ public final class TerminalView extends View {
|
|||||||
if (handleVirtualKeys(keyCode, event, true)) {
|
if (handleVirtualKeys(keyCode, event, true)) {
|
||||||
invalidate();
|
invalidate();
|
||||||
return true;
|
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);
|
return super.onKeyDown(keyCode, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BIN
app/src/main/res/raw/bell.ogg
Normal file
BIN
app/src/main/res/raw/bell.ogg
Normal file
Binary file not shown.
Reference in New Issue
Block a user