mirror of
https://github.com/fankes/termux-app.git
synced 2025-09-06 02:35:19 +08:00
Move Termux Properties to com.termux.app.settings.properties package
The termux properties handling was mixed in with termux preferences. They are now moved out of into a separate sub package, the following classes are added: - `SharedProperties` class which is an implementation similar to android's `SharedPreferences` interface for reading from ".properties" files which also maintains an in-memory cache for the key/value pairs. Two types of in-memory cache maps are maintained, one for the literal `String` values found in the file for the keys and an additional one that stores (near) primitive `Object` values for internal use by the caller. Write support is currently not implemented, but may be added if we provide users a GUI to modify the properties. We cannot just overwrite the ".properties" files, since comments also exits, so in-place editing would be required. - `SharedPropertiesParser` interface that the caller of `SharedProperties` must implement. It is currently only used to map `String` values to internal `Object` values. - `TermuxPropertyConstants` class that defines shared constants of the properties used by Termux app and its plugins. This class should be imported by other termux plugin apps instead of copying and defining their own constants. - `TermuxSharedProperties` class that acts as manager for handling termux properties. It implements the `SharedPropertiesParser` interface and acts as the wrapper for the `SharedProperties` class.
This commit is contained in:
@@ -11,6 +11,7 @@ android {
|
||||
implementation "androidx.viewpager:viewpager:1.0.0"
|
||||
implementation "androidx.drawerlayout:drawerlayout:1.1.1"
|
||||
implementation 'androidx.core:core:1.5.0-beta02'
|
||||
implementation 'com.google.guava:guava:24.1-jre'
|
||||
implementation project(":terminal-view")
|
||||
}
|
||||
|
||||
|
@@ -51,6 +51,8 @@ import com.termux.app.TermuxConstants.TERMUX_APP.TERMUX_ACTIVITY;
|
||||
import com.termux.app.input.BellHandler;
|
||||
import com.termux.app.input.extrakeys.ExtraKeysView;
|
||||
import com.termux.app.input.FullScreenWorkAround;
|
||||
import com.termux.app.settings.properties.TermuxPropertyConstants;
|
||||
import com.termux.app.settings.properties.TermuxSharedProperties;
|
||||
import com.termux.terminal.EmulatorDebug;
|
||||
import com.termux.terminal.TerminalColors;
|
||||
import com.termux.terminal.TerminalSession;
|
||||
@@ -110,6 +112,7 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
||||
ExtraKeysView mExtraKeysView;
|
||||
|
||||
TermuxPreferences mSettings;
|
||||
TermuxSharedProperties mProperties;
|
||||
|
||||
/**
|
||||
* The connection to the {@link TermuxService}. Requested in {@link #onCreate(Bundle)} with a call to
|
||||
@@ -144,16 +147,19 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (mIsVisible) {
|
||||
String whatToReload = intent.getStringExtra(TERMUX_ACTIVITY.EXTRA_RELOAD_STYLE);
|
||||
Log.d("termux", "Reloading termux style for: " + whatToReload);
|
||||
if ("storage".equals(whatToReload)) {
|
||||
if (ensureStoragePermissionGranted())
|
||||
TermuxInstaller.setupStorageSymlinks(TermuxActivity.this);
|
||||
return;
|
||||
}
|
||||
|
||||
checkForFontAndColors();
|
||||
mSettings.reloadFromProperties(TermuxActivity.this);
|
||||
|
||||
mProperties.loadTermuxPropertiesFromDisk();
|
||||
|
||||
if (mExtraKeysView != null) {
|
||||
mExtraKeysView.reload(mSettings.mExtraKeys);
|
||||
mExtraKeysView.reload(mProperties.getExtraKeysInfo());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -205,7 +211,9 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
||||
@Override
|
||||
public void onCreate(Bundle bundle) {
|
||||
mSettings = new TermuxPreferences(this);
|
||||
mIsUsingBlackUI = mSettings.isUsingBlackUI();
|
||||
mProperties = new TermuxSharedProperties(this);
|
||||
|
||||
mIsUsingBlackUI = mProperties.isUsingBlackUI();
|
||||
if (mIsUsingBlackUI) {
|
||||
this.setTheme(R.style.Theme_Termux_Black);
|
||||
} else {
|
||||
@@ -223,7 +231,7 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
||||
return insets;
|
||||
});
|
||||
|
||||
if (mSettings.isUsingFullScreen()) {
|
||||
if (mProperties.isUsingFullScreen()) {
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||
}
|
||||
|
||||
@@ -245,7 +253,7 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
||||
|
||||
|
||||
ViewGroup.LayoutParams layoutParams = viewPager.getLayoutParams();
|
||||
layoutParams.height = layoutParams.height * (mSettings.mExtraKeys == null ? 0 : mSettings.mExtraKeys.getMatrix().length);
|
||||
layoutParams.height = layoutParams.height * (mProperties.getExtraKeysInfo() == null ? 0 : mProperties.getExtraKeysInfo().getMatrix().length);
|
||||
viewPager.setLayoutParams(layoutParams);
|
||||
|
||||
viewPager.setAdapter(new PagerAdapter() {
|
||||
@@ -266,10 +274,10 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
||||
View layout;
|
||||
if (position == 0) {
|
||||
layout = mExtraKeysView = (ExtraKeysView) inflater.inflate(R.layout.extra_keys_main, collection, false);
|
||||
mExtraKeysView.reload(mSettings.mExtraKeys);
|
||||
mExtraKeysView.reload(mProperties.getExtraKeysInfo());
|
||||
|
||||
// apply extra keys fix if enabled in prefs
|
||||
if (mSettings.isUsingFullScreen() && mSettings.isUsingFullScreenWorkAround()) {
|
||||
if (mProperties.isUsingFullScreen() && mProperties.isUsingFullScreenWorkAround()) {
|
||||
FullScreenWorkAround.apply(TermuxActivity.this);
|
||||
}
|
||||
|
||||
@@ -451,14 +459,14 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
||||
public void onBell(TerminalSession session) {
|
||||
if (!mIsVisible) return;
|
||||
|
||||
switch (mSettings.mBellBehaviour) {
|
||||
case TermuxPreferences.BELL_BEEP:
|
||||
mBellSoundPool.play(mBellSoundId, 1.f, 1.f, 1, 0, 1.f);
|
||||
break;
|
||||
case TermuxPreferences.BELL_VIBRATE:
|
||||
switch (mProperties.getBellBehaviour()) {
|
||||
case TermuxPropertyConstants.IVALUE_BELL_BEHAVIOUR_VIBRATE:
|
||||
BellHandler.getInstance(TermuxActivity.this).doBell();
|
||||
break;
|
||||
case TermuxPreferences.BELL_IGNORE:
|
||||
case TermuxPropertyConstants.IVALUE_BELL_BEHAVIOUR_BEEP:
|
||||
mBellSoundPool.play(mBellSoundId, 1.f, 1.f, 1, 0, 1.f);
|
||||
break;
|
||||
case TermuxPropertyConstants.IVALUE_BELL_BEHAVIOUR_IGNORE:
|
||||
// Ignore the bell character.
|
||||
break;
|
||||
}
|
||||
@@ -665,7 +673,7 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
||||
|
||||
String workingDirectory;
|
||||
if (currentSession == null) {
|
||||
workingDirectory = mSettings.mDefaultWorkingDir;
|
||||
workingDirectory = mProperties.getDefaultWorkingDirectory();
|
||||
} else {
|
||||
workingDirectory = currentSession.getCwd();
|
||||
}
|
||||
|
@@ -2,61 +2,13 @@ package com.termux.app;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Configuration;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.util.Log;
|
||||
import android.util.TypedValue;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.termux.terminal.TerminalSession;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import androidx.annotation.IntDef;
|
||||
|
||||
import static com.termux.terminal.EmulatorDebug.LOG_TAG;
|
||||
|
||||
final class TermuxPreferences {
|
||||
|
||||
@IntDef({BELL_VIBRATE, BELL_BEEP, BELL_IGNORE})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@interface AsciiBellBehaviour {
|
||||
}
|
||||
|
||||
final static class KeyboardShortcut {
|
||||
|
||||
KeyboardShortcut(int codePoint, int shortcutAction) {
|
||||
this.codePoint = codePoint;
|
||||
this.shortcutAction = shortcutAction;
|
||||
}
|
||||
|
||||
final int codePoint;
|
||||
final int shortcutAction;
|
||||
}
|
||||
|
||||
static final int SHORTCUT_ACTION_CREATE_SESSION = 1;
|
||||
static final int SHORTCUT_ACTION_NEXT_SESSION = 2;
|
||||
static final int SHORTCUT_ACTION_PREVIOUS_SESSION = 3;
|
||||
static final int SHORTCUT_ACTION_RENAME_SESSION = 4;
|
||||
|
||||
static final int BELL_VIBRATE = 1;
|
||||
static final int BELL_BEEP = 2;
|
||||
static final int BELL_IGNORE = 3;
|
||||
|
||||
private final int MIN_FONTSIZE;
|
||||
private static final int MAX_FONTSIZE = 256;
|
||||
|
||||
@@ -65,34 +17,11 @@ final class TermuxPreferences {
|
||||
private static final String CURRENT_SESSION_KEY = "current_session";
|
||||
private static final String SCREEN_ALWAYS_ON_KEY = "screen_always_on";
|
||||
|
||||
private boolean mUseDarkUI;
|
||||
private boolean mScreenAlwaysOn;
|
||||
private int mFontSize;
|
||||
|
||||
private boolean mUseFullScreen;
|
||||
private boolean mUseFullScreenWorkAround;
|
||||
|
||||
@AsciiBellBehaviour
|
||||
int mBellBehaviour = BELL_VIBRATE;
|
||||
|
||||
boolean mBackIsEscape;
|
||||
boolean mDisableVolumeVirtualKeys;
|
||||
boolean mShowExtraKeys;
|
||||
String mDefaultWorkingDir;
|
||||
|
||||
ExtraKeysInfos mExtraKeys;
|
||||
|
||||
final List<KeyboardShortcut> shortcuts = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* If value is not in the range [min, max], set it to either min or max.
|
||||
*/
|
||||
static int clamp(int value, int min, int max) {
|
||||
return Math.min(Math.max(value, min), max);
|
||||
}
|
||||
|
||||
TermuxPreferences(Context context) {
|
||||
reloadFromProperties(context);
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
|
||||
float dipInPixels = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, context.getResources().getDisplayMetrics());
|
||||
@@ -139,18 +68,6 @@ final class TermuxPreferences {
|
||||
return mScreenAlwaysOn;
|
||||
}
|
||||
|
||||
boolean isUsingBlackUI() {
|
||||
return mUseDarkUI;
|
||||
}
|
||||
|
||||
boolean isUsingFullScreen() {
|
||||
return mUseFullScreen;
|
||||
}
|
||||
|
||||
boolean isUsingFullScreenWorkAround() {
|
||||
return mUseFullScreenWorkAround;
|
||||
}
|
||||
|
||||
void setScreenAlwaysOn(Context context, boolean newValue) {
|
||||
mScreenAlwaysOn = newValue;
|
||||
PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean(SCREEN_ALWAYS_ON_KEY, newValue).apply();
|
||||
@@ -169,108 +86,11 @@ final class TermuxPreferences {
|
||||
return null;
|
||||
}
|
||||
|
||||
void reloadFromProperties(Context context) {
|
||||
File propsFile = new File(TermuxConstants.TERMUX_PROPERTIES_PRIMARY_PATH);
|
||||
if (!propsFile.exists())
|
||||
propsFile = new File(TermuxConstants.TERMUX_PROPERTIES_SECONDARY_PATH);
|
||||
|
||||
Properties props = new Properties();
|
||||
try {
|
||||
if (propsFile.isFile() && propsFile.canRead()) {
|
||||
try (FileInputStream in = new FileInputStream(propsFile)) {
|
||||
props.load(new InputStreamReader(in, StandardCharsets.UTF_8));
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Toast.makeText(context, "Could not open properties file termux.properties: " + e.getMessage(), Toast.LENGTH_LONG).show();
|
||||
Log.e("termux", "Error loading props", e);
|
||||
}
|
||||
|
||||
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("use-black-ui", "").toLowerCase()) {
|
||||
case "true":
|
||||
mUseDarkUI = true;
|
||||
break;
|
||||
case "false":
|
||||
mUseDarkUI = false;
|
||||
break;
|
||||
default:
|
||||
int nightMode = context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
|
||||
mUseDarkUI = nightMode == Configuration.UI_MODE_NIGHT_YES;
|
||||
}
|
||||
|
||||
mUseFullScreen = "true".equals(props.getProperty("fullscreen", "false").toLowerCase());
|
||||
mUseFullScreenWorkAround = "true".equals(props.getProperty("use-fullscreen-workaround", "false").toLowerCase());
|
||||
|
||||
mDefaultWorkingDir = props.getProperty("default-working-directory", TermuxConstants.HOME_PATH);
|
||||
File workDir = new File(mDefaultWorkingDir);
|
||||
if (!workDir.exists() || !workDir.isDirectory()) {
|
||||
// Fallback to home directory if user configured working directory is not exist
|
||||
// or is a regular file.
|
||||
mDefaultWorkingDir = TermuxConstants.HOME_PATH;
|
||||
}
|
||||
|
||||
String defaultExtraKeys = "[[ESC, TAB, CTRL, ALT, {key: '-', popup: '|'}, DOWN, UP]]";
|
||||
|
||||
try {
|
||||
String extrakeyProp = props.getProperty("extra-keys", defaultExtraKeys);
|
||||
String extraKeysStyle = props.getProperty("extra-keys-style", "default");
|
||||
mExtraKeys = new ExtraKeysInfos(extrakeyProp, extraKeysStyle);
|
||||
} catch (JSONException e) {
|
||||
Toast.makeText(context, "Could not load the extra-keys property from the config: " + e.toString(), Toast.LENGTH_LONG).show();
|
||||
Log.e("termux", "Error loading props", e);
|
||||
|
||||
try {
|
||||
mExtraKeys = new ExtraKeysInfos(defaultExtraKeys, "default");
|
||||
} catch (JSONException e2) {
|
||||
e2.printStackTrace();
|
||||
Toast.makeText(context, "Can't create default extra keys", Toast.LENGTH_LONG).show();
|
||||
mExtraKeys = null;
|
||||
}
|
||||
}
|
||||
|
||||
mBackIsEscape = "escape".equals(props.getProperty("back-key", "back"));
|
||||
mDisableVolumeVirtualKeys = "volume".equals(props.getProperty("volume-keys", "virtual"));
|
||||
|
||||
shortcuts.clear();
|
||||
parseAction("shortcut.create-session", SHORTCUT_ACTION_CREATE_SESSION, props);
|
||||
parseAction("shortcut.next-session", SHORTCUT_ACTION_NEXT_SESSION, props);
|
||||
parseAction("shortcut.previous-session", SHORTCUT_ACTION_PREVIOUS_SESSION, props);
|
||||
parseAction("shortcut.rename-session", SHORTCUT_ACTION_RENAME_SESSION, props);
|
||||
}
|
||||
|
||||
private void parseAction(String name, int shortcutAction, Properties props) {
|
||||
String value = props.getProperty(name);
|
||||
if (value == null) return;
|
||||
String[] parts = value.toLowerCase().trim().split("\\+");
|
||||
String input = parts.length == 2 ? parts[1].trim() : null;
|
||||
if (!(parts.length == 2 && parts[0].trim().equals("ctrl")) || input.isEmpty() || input.length() > 2) {
|
||||
Log.e("termux", "Keyboard shortcut '" + name + "' is not Ctrl+<something>");
|
||||
return;
|
||||
}
|
||||
|
||||
char c = input.charAt(0);
|
||||
int codePoint = c;
|
||||
if (Character.isLowSurrogate(c)) {
|
||||
if (input.length() != 2 || Character.isHighSurrogate(input.charAt(1))) {
|
||||
Log.e("termux", "Keyboard shortcut '" + name + "' is not Ctrl+<something>");
|
||||
return;
|
||||
} else {
|
||||
codePoint = Character.toCodePoint(input.charAt(1), c);
|
||||
}
|
||||
}
|
||||
shortcuts.add(new KeyboardShortcut(codePoint, shortcutAction));
|
||||
/**
|
||||
* If value is not in the range [min, max], set it to either min or max.
|
||||
*/
|
||||
static int clamp(int value, int min, int max) {
|
||||
return Math.min(Math.max(value, min), max);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -8,7 +8,9 @@ import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
|
||||
import com.termux.app.input.KeyboardShortcut;
|
||||
import com.termux.app.input.extrakeys.ExtraKeysView;
|
||||
import com.termux.app.settings.properties.TermuxPropertyConstants;
|
||||
import com.termux.terminal.KeyHandler;
|
||||
import com.termux.terminal.TerminalEmulator;
|
||||
import com.termux.terminal.TerminalSession;
|
||||
@@ -47,7 +49,7 @@ public final class TermuxViewClient implements TerminalViewClient {
|
||||
|
||||
@Override
|
||||
public boolean shouldBackButtonBeMappedToEscape() {
|
||||
return mActivity.mSettings.mBackIsEscape;
|
||||
return mActivity.mProperties.isBackKeyTheEscapeKey();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -230,23 +232,23 @@ public final class TermuxViewClient implements TerminalViewClient {
|
||||
return true;
|
||||
}
|
||||
|
||||
List<TermuxPreferences.KeyboardShortcut> shortcuts = mActivity.mSettings.shortcuts;
|
||||
List<KeyboardShortcut> shortcuts = mActivity.mProperties.getSessionShortcuts();
|
||||
if (!shortcuts.isEmpty()) {
|
||||
int codePointLowerCase = Character.toLowerCase(codePoint);
|
||||
for (int i = shortcuts.size() - 1; i >= 0; i--) {
|
||||
TermuxPreferences.KeyboardShortcut shortcut = shortcuts.get(i);
|
||||
KeyboardShortcut shortcut = shortcuts.get(i);
|
||||
if (codePointLowerCase == shortcut.codePoint) {
|
||||
switch (shortcut.shortcutAction) {
|
||||
case TermuxPreferences.SHORTCUT_ACTION_CREATE_SESSION:
|
||||
case TermuxPropertyConstants.ACTION_SHORTCUT_CREATE_SESSION:
|
||||
mActivity.addNewSession(false, null);
|
||||
return true;
|
||||
case TermuxPreferences.SHORTCUT_ACTION_PREVIOUS_SESSION:
|
||||
mActivity.switchToSession(false);
|
||||
return true;
|
||||
case TermuxPreferences.SHORTCUT_ACTION_NEXT_SESSION:
|
||||
case TermuxPropertyConstants.ACTION_SHORTCUT_NEXT_SESSION:
|
||||
mActivity.switchToSession(true);
|
||||
return true;
|
||||
case TermuxPreferences.SHORTCUT_ACTION_RENAME_SESSION:
|
||||
case TermuxPropertyConstants.ACTION_SHORTCUT_PREVIOUS_SESSION:
|
||||
mActivity.switchToSession(false);
|
||||
return true;
|
||||
case TermuxPropertyConstants.ACTION_SHORTCUT_RENAME_SESSION:
|
||||
mActivity.renameSession(mActivity.getCurrentTermSession());
|
||||
return true;
|
||||
}
|
||||
@@ -266,7 +268,7 @@ public final class TermuxViewClient implements TerminalViewClient {
|
||||
/** Handle dedicated volume buttons as virtual keys if applicable. */
|
||||
private boolean handleVirtualKeys(int keyCode, KeyEvent event, boolean down) {
|
||||
InputDevice inputDevice = event.getDevice();
|
||||
if (mActivity.mSettings.mDisableVolumeVirtualKeys) {
|
||||
if (mActivity.mProperties.areVirtualVolumeKeysDisabled()) {
|
||||
return false;
|
||||
} else if (inputDevice != null && inputDevice.getKeyboardType() == InputDevice.KEYBOARD_TYPE_ALPHABETIC) {
|
||||
// Do not steal dedicated buttons from a full external keyboard.
|
||||
|
@@ -0,0 +1,312 @@
|
||||
package com.termux.app.settings.properties;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.google.common.primitives.Primitives;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* An implementation similar to android's {@link android.content.SharedPreferences} interface for
|
||||
* reading and writing to and from ".properties" files which also maintains an in-memory cache for
|
||||
* the key/value pairs. Operations are does under synchronization locks and should be thread safe.
|
||||
*
|
||||
* Two types of in-memory cache maps are maintained, one for the literal {@link String} values found
|
||||
* in the file for the keys and an additional one that stores (near) primitive {@link Object} values for
|
||||
* internal use by the caller.
|
||||
*
|
||||
* This currently only has read support, write support can/will be added later if needed. Check android's
|
||||
* SharedPreferencesImpl class for reference implementation.
|
||||
*
|
||||
* https://cs.android.com/android/platform/superproject/+/android-11.0.0_r3:frameworks/base/core/java/android/app/SharedPreferencesImpl.java
|
||||
*/
|
||||
public class SharedProperties {
|
||||
|
||||
/**
|
||||
* The {@link Properties} object that maintains an in-memory cache of values loaded from the
|
||||
* {@link #mPropertiesFile} file. The key/value pairs are of any keys defined by
|
||||
* {@link #mPropertiesList} that are found in the file against their literal values in the file.
|
||||
*/
|
||||
private Properties mProperties;
|
||||
|
||||
/**
|
||||
* The {@link HashMap<>} object that maintains an in-memory cache of internal values for the values
|
||||
* loaded from the {@link #mPropertiesFile} file. The key/value pairs are of any keys defined by
|
||||
* {@link #mPropertiesList} that are found in the file against their internal {@link Object} values
|
||||
* returned by the call to
|
||||
* {@link SharedPropertiesParser#getInternalPropertyValueFromValue(Context, String, String)} interface.
|
||||
*/
|
||||
private Map<String, Object> mMap;
|
||||
|
||||
private final Context mContext;
|
||||
private final File mPropertiesFile;
|
||||
private final Set<String> mPropertiesList;
|
||||
private final SharedPropertiesParser mSharedPropertiesParser;
|
||||
|
||||
private final Object mLock = new Object();
|
||||
|
||||
/**
|
||||
* Constructor for the SharedProperties class.
|
||||
*
|
||||
* @param context The Context for operations.
|
||||
* @param propertiesFile The {@link File} object to load properties from.
|
||||
* @param propertiesList The {@link Set<String>} object that defined which properties to load.
|
||||
* @param sharedPropertiesParser that implements the {@link SharedPropertiesParser} interface.
|
||||
*/
|
||||
public SharedProperties(@Nonnull Context context, @Nullable File propertiesFile, @Nonnull Set<String> propertiesList, @Nonnull SharedPropertiesParser sharedPropertiesParser) {
|
||||
mContext = context;
|
||||
mPropertiesFile = propertiesFile;
|
||||
mPropertiesList = propertiesList;
|
||||
mSharedPropertiesParser = sharedPropertiesParser;
|
||||
|
||||
mProperties = new Properties();
|
||||
mMap = new HashMap<String, Object>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the properties defined by {@link #mPropertiesList} from the {@link #mPropertiesFile} file
|
||||
* to update the {@link #mProperties} and {@link #mMap} in-memory cache.
|
||||
* Properties are not loading automatically when constructor is called and must be manually called.
|
||||
*/
|
||||
public void loadPropertiesFromDisk() {
|
||||
synchronized (mLock) {
|
||||
// Get properties from mPropertiesFile
|
||||
Properties properties = getProperties(false);
|
||||
|
||||
// We still need to load default values into mMap, so we assume no properties defined if
|
||||
// reading from mPropertiesFile failed
|
||||
if (properties == null)
|
||||
properties = new Properties();
|
||||
|
||||
HashMap<String, Object> map = new HashMap<String, Object>();
|
||||
Properties newProperties = new Properties();
|
||||
|
||||
String value;
|
||||
Object internalValue;
|
||||
for (String key : mPropertiesList) {
|
||||
value = properties.getProperty(key); // value will be null if key does not exist in propertiesFile
|
||||
Log.d("termux", key + " : " + value);
|
||||
|
||||
// Call the {@link SharedPropertiesParser#getInternalPropertyValueFromValue(Context,String,String)}
|
||||
// interface method to get the internal value to store in the {@link #mMap}.
|
||||
internalValue = mSharedPropertiesParser.getInternalPropertyValueFromValue(mContext, key, value);
|
||||
|
||||
// If the internal value was successfully added to map, then also add value to newProperties
|
||||
// We only store values in-memory defined by {@link #mPropertiesList}
|
||||
if (putToMap(map, key, internalValue)) { // null internalValue will be put into map
|
||||
putToProperties(newProperties, key, value); // null value will **not** be into properties
|
||||
}
|
||||
}
|
||||
|
||||
mMap = map;
|
||||
mProperties = newProperties;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Put a value in a {@link #mMap}.
|
||||
* The key cannot be {@code null}.
|
||||
* Only {@code null}, primitive or their wrapper classes or String class objects are allowed to be added to
|
||||
* the map, although this limitation may be changed.
|
||||
*
|
||||
* @param map The {@link Map} object to add value to.
|
||||
* @param key The key for which to add the value to the map.
|
||||
* @param value The {@link Object} to add to the map.
|
||||
* @return Returns {@code true} if value was successfully added, otherwise {@code false}.
|
||||
*/
|
||||
public static boolean putToMap(HashMap<String, Object> map, String key, Object value) {
|
||||
|
||||
if (map == null) {
|
||||
Log.e("termux", "Map passed to SharedProperties.putToProperties() is null");
|
||||
return false;
|
||||
}
|
||||
|
||||
// null keys are not allowed to be stored in mMap
|
||||
if (key == null) {
|
||||
Log.e("termux", "Cannot put a null key into properties map");
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean put = false;
|
||||
if (value != null) {
|
||||
Class<?> clazz = value.getClass();
|
||||
if (clazz.isPrimitive() || Primitives.isWrapperType(clazz) || value instanceof String) {
|
||||
put = true;
|
||||
}
|
||||
} else {
|
||||
put = true;
|
||||
}
|
||||
|
||||
if (put) {
|
||||
map.put(key, value);
|
||||
return true;
|
||||
} else {
|
||||
Log.e("termux", "Cannot put a non-primitive value for the key \"" + key + "\" into properties map");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Put a value in a {@link Map}.
|
||||
* The key cannot be {@code null}.
|
||||
* Passing {@code null} as the value argument is equivalent to removing the key from the
|
||||
* properties.
|
||||
*
|
||||
* @param properties The {@link Properties} object to add value to.
|
||||
* @param key The key for which to add the value to the properties.
|
||||
* @param value The {@link String} to add to the properties.
|
||||
* @return Returns {@code true} if value was successfully added, otherwise {@code false}.
|
||||
*/
|
||||
public static boolean putToProperties(Properties properties, String key, String value) {
|
||||
|
||||
if (properties == null) {
|
||||
Log.e("termux", "Properties passed to SharedProperties.putToProperties() is null");
|
||||
return false;
|
||||
}
|
||||
|
||||
// null keys are not allowed to be stored in mMap
|
||||
if (key == null) {
|
||||
Log.e("termux", "Cannot put a null key into properties");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (value != null) {
|
||||
properties.put(key, value);
|
||||
return true;
|
||||
} else {
|
||||
properties.remove(key);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* A static function to get the {@link Properties} object for the propertiesFile. A lock is not
|
||||
* taken when this function is called.
|
||||
*
|
||||
* @param context The {@link Context} to use to show a flash if an exception is raised while
|
||||
* reading the file. If context is {@code null}, then flash will not be shown.
|
||||
* @param propertiesFile The {@link File} to read the {@link Properties} from.
|
||||
* @return Returns the {@link Properties} object. It will be {@code null} if an exception is
|
||||
* raised while reading the file.
|
||||
*/
|
||||
public static Properties getPropertiesFromFile(Context context, File propertiesFile) {
|
||||
Properties properties = new Properties();
|
||||
|
||||
if (propertiesFile == null) {
|
||||
Log.e("termux", "Not loading properties since file is null");
|
||||
return properties;
|
||||
}
|
||||
|
||||
try {
|
||||
try (FileInputStream in = new FileInputStream(propertiesFile)) {
|
||||
Log.v("termux", "Loading properties from \"" + propertiesFile.getAbsolutePath() + "\" file");
|
||||
properties.load(new InputStreamReader(in, StandardCharsets.UTF_8));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if(context != null)
|
||||
Toast.makeText(context, "Could not open properties file \"" + propertiesFile.getAbsolutePath() + "\": " + e.getMessage(), Toast.LENGTH_LONG).show();
|
||||
Log.e("termux", "Error loading properties file \"" + propertiesFile.getAbsolutePath() + "\"", e);
|
||||
return null;
|
||||
}
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link Properties} object for the {@link #mPropertiesFile}. The {@link Properties}
|
||||
* object will also contain properties not defined by the {@link #mPropertiesList} if cache
|
||||
* value is {@code false}.
|
||||
*
|
||||
* @param cached If {@code true}, then the {@link #mProperties} in-memory cache is returned. Otherwise
|
||||
* the {@link Properties} object is directly read from the {@link #mPropertiesFile}.
|
||||
* @return Returns the {@link Properties} object if read from file, otherwise a copy of {@link #mProperties}.
|
||||
*/
|
||||
public Properties getProperties(boolean cached) {
|
||||
synchronized (mLock) {
|
||||
if (cached) {
|
||||
if (mProperties == null) mProperties = new Properties();
|
||||
return getPropertiesCopy(mProperties);
|
||||
} else {
|
||||
return getPropertiesFromFile(mContext, mPropertiesFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link String} value for the key passed from the {@link #mPropertiesFile}.
|
||||
*
|
||||
* @param key The key to read from the {@link Properties} object.
|
||||
* @param cached If {@code true}, then the value is returned from the {@link #mProperties} in-memory cache.
|
||||
* Otherwise the {@link Properties} object is read directly from the {@link #mPropertiesFile}
|
||||
* and value is returned from it against the key.
|
||||
* @return Returns the {@link String} object. This will be {@code null} if key is not found.
|
||||
*/
|
||||
public String getProperty(String key, boolean cached) {
|
||||
synchronized (mLock) {
|
||||
return (String) getProperties(cached).get(key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link #mMap} object for the {@link #mPropertiesFile}. A call to
|
||||
* {@link #loadPropertiesFromDisk()} must be made before this.
|
||||
*
|
||||
* @return Returns a copy of {@link #mMap} object.
|
||||
*/
|
||||
public Map<String, Object> getInternalProperties() {
|
||||
synchronized (mLock) {
|
||||
if (mMap == null) mMap = new HashMap<String, Object>();
|
||||
return getMapCopy(mMap);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the internal {@link Object} value for the key passed from the {@link #mPropertiesFile}.
|
||||
* The value is returned from the {@link #mMap} in-memory cache, so a call to
|
||||
* {@link #loadPropertiesFromDisk()} must be made before this.
|
||||
*
|
||||
* @param key The key to read from the {@link #mMap} object.
|
||||
* @return Returns the {@link Object} object. This will be {@code null} if key is not found or
|
||||
* if object was {@code null}. Use {@link HashMap#containsKey(Object)} to detect the later.
|
||||
* situation.
|
||||
*/
|
||||
public Object getInternalProperty(String key) {
|
||||
synchronized (mLock) {
|
||||
// null keys are not allowed to be stored in mMap
|
||||
if (key != null)
|
||||
return getInternalProperties().get(key);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static Properties getPropertiesCopy(Properties inputProperties) {
|
||||
if(inputProperties == null) return null;
|
||||
|
||||
Properties outputProperties = new Properties();
|
||||
for (String key : inputProperties.stringPropertyNames()) {
|
||||
outputProperties.put(key, inputProperties.get(key));
|
||||
}
|
||||
|
||||
return outputProperties;
|
||||
}
|
||||
|
||||
public static Map<String, Object> getMapCopy(Map<String, Object> map) {
|
||||
if(map == null) return null;
|
||||
return new HashMap<String, Object>(map);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,23 @@
|
||||
package com.termux.app.settings.properties;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* An interface that must be defined by the caller of the {@link SharedProperties} class.
|
||||
*/
|
||||
public interface SharedPropertiesParser {
|
||||
|
||||
/**
|
||||
* A function that should return the internal {@link Object} to be stored for a key/value pair
|
||||
* read from properties file in the {@link HashMap <>} in-memory cache.
|
||||
*
|
||||
* @param context The context for operations.
|
||||
* @param key The key for which the internal object is required.
|
||||
* @param value The literal value for the property found is the properties file.
|
||||
* @return Returns the {@link Object} object to store in the {@link HashMap <>} in-memory cache.
|
||||
*/
|
||||
public Object getInternalPropertyValueFromValue(Context context, String key, String value);
|
||||
|
||||
}
|
@@ -0,0 +1,228 @@
|
||||
package com.termux.app.settings.properties;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.common.collect.ImmutableBiMap;
|
||||
import com.termux.app.TermuxConstants;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
/*
|
||||
* Version: v0.1.0
|
||||
*
|
||||
* Changelog
|
||||
*
|
||||
* - 0.1.0 (2021-03-08)
|
||||
* - Initial Release
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* A class that defines shared constants of the properties used by Termux app and its plugins.
|
||||
* This class will be hosted by termux-app and should be imported by other termux plugin apps as is
|
||||
* instead of copying constants to random classes. The 3rd party apps can also import it for
|
||||
* interacting with termux apps. If changes are made to this file, increment the version number
|
||||
* * and add an entry in the Changelog section above.
|
||||
*
|
||||
* The properties are loaded from the first file found at
|
||||
* {@link TermuxConstants#TERMUX_PROPERTIES_PRIMARY_PATH} or
|
||||
* {@link TermuxConstants#TERMUX_PROPERTIES_SECONDARY_PATH}
|
||||
*/
|
||||
public final class TermuxPropertyConstants {
|
||||
|
||||
/** Defines the bidirectional map for boolean values and their internal values */
|
||||
public static final ImmutableBiMap<String, Boolean> MAP_GENERIC_BOOLEAN =
|
||||
new ImmutableBiMap.Builder<String, Boolean>()
|
||||
.put("true", true)
|
||||
.put("false", false)
|
||||
.build();
|
||||
|
||||
/** Defines the bidirectional map for inverted boolean values and their internal values */
|
||||
public static final ImmutableBiMap<String, Boolean> MAP_GENERIC_INVERTED_BOOLEAN =
|
||||
new ImmutableBiMap.Builder<String, Boolean>()
|
||||
.put("true", false)
|
||||
.put("false", true)
|
||||
.build();
|
||||
|
||||
|
||||
|
||||
/** Defines the key for whether to use back key as the escape key */
|
||||
public static final String KEY_USE_BACK_KEY_AS_ESCAPE_KEY = "back-key"; // Default: "back-key"
|
||||
|
||||
public static final String VALUE_BACK_KEY_BEHAVIOUR_BACK = "back";
|
||||
public static final String VALUE_BACK_KEY_BEHAVIOUR_ESCAPE = "escape";
|
||||
|
||||
|
||||
|
||||
/** Defines the key for whether to use black UI */
|
||||
public static final String KEY_USE_BLACK_UI = "use-black-ui"; // Default: "use-black-ui"
|
||||
|
||||
|
||||
|
||||
/** Defines the key for whether to use fullscreen */
|
||||
public static final String KEY_USE_FULLSCREEN = "fullscreen"; // Default: "fullscreen"
|
||||
|
||||
|
||||
|
||||
/** Defines the key for whether to use fullscreen workaround */
|
||||
public static final String KEY_USE_FULLSCREEN_WORKAROUND = "use-fullscreen-workaround"; // Default: "use-fullscreen-workaround"
|
||||
|
||||
|
||||
|
||||
/** Defines the key for whether virtual volume keys are disabled */
|
||||
public static final String KEY_VIRTUAL_VOLUME_KEYS_DISABLED = "volume-keys"; // Default: "volume-keys"
|
||||
|
||||
public static final String VALUE_VOLUME_KEY_BEHAVIOUR_VOLUME = "volume";
|
||||
public static final String VALUE_VOLUME_KEY_BEHAVIOUR_VIRTUAL = "virtual";
|
||||
|
||||
|
||||
|
||||
/** Defines the key for the bell behaviour */
|
||||
public static final String KEY_BELL_BEHAVIOUR = "bell-character"; // Default: "bell-character"
|
||||
|
||||
public static final String VALUE_BELL_BEHAVIOUR_VIBRATE = "vibrate";
|
||||
public static final String VALUE_BELL_BEHAVIOUR_BEEP = "beep";
|
||||
public static final String VALUE_BELL_BEHAVIOUR_IGNORE = "ignore";
|
||||
public static final String DEFAULT_VALUE_BELL_BEHAVIOUR = VALUE_BELL_BEHAVIOUR_VIBRATE;
|
||||
|
||||
public static final int IVALUE_BELL_BEHAVIOUR_VIBRATE = 1;
|
||||
public static final int IVALUE_BELL_BEHAVIOUR_BEEP = 2;
|
||||
public static final int IVALUE_BELL_BEHAVIOUR_IGNORE = 3;
|
||||
public static final int DEFAULT_IVALUE_BELL_BEHAVIOUR = IVALUE_BELL_BEHAVIOUR_VIBRATE;
|
||||
|
||||
/** Defines the bidirectional map for bell behaviour values and their internal values */
|
||||
public static final ImmutableBiMap<String, Integer> MAP_BELL_BEHAVIOUR =
|
||||
new ImmutableBiMap.Builder<String, Integer>()
|
||||
.put(VALUE_BELL_BEHAVIOUR_VIBRATE, IVALUE_BELL_BEHAVIOUR_VIBRATE)
|
||||
.put(VALUE_BELL_BEHAVIOUR_BEEP, IVALUE_BELL_BEHAVIOUR_BEEP)
|
||||
.put(VALUE_BELL_BEHAVIOUR_IGNORE, IVALUE_BELL_BEHAVIOUR_IGNORE)
|
||||
.build();
|
||||
|
||||
|
||||
|
||||
/** Defines the key for create session shortcut */
|
||||
public static final String KEY_SHORTCUT_CREATE_SESSION = "shortcut.create-session"; // Default: "shortcut.create-session"
|
||||
/** Defines the key for next session shortcut */
|
||||
public static final String KEY_SHORTCUT_NEXT_SESSION = "shortcut.next-session"; // Default: "shortcut.next-session"
|
||||
/** Defines the key for previous session shortcut */
|
||||
public static final String KEY_SHORTCUT_PREVIOUS_SESSION = "shortcut.previous-session"; // Default: "shortcut.previous-session"
|
||||
/** Defines the key for rename session shortcut */
|
||||
public static final String KEY_SHORTCUT_RENAME_SESSION = "shortcut.rename-session"; // Default: "shortcut.rename-session"
|
||||
|
||||
public static final int ACTION_SHORTCUT_CREATE_SESSION = 1;
|
||||
public static final int ACTION_SHORTCUT_NEXT_SESSION = 2;
|
||||
public static final int ACTION_SHORTCUT_PREVIOUS_SESSION = 3;
|
||||
public static final int ACTION_SHORTCUT_RENAME_SESSION = 4;
|
||||
|
||||
/** Defines the bidirectional map for session shortcut values and their internal actions */
|
||||
public static final ImmutableBiMap<String, Integer> MAP_SESSION_SHORTCUTS =
|
||||
new ImmutableBiMap.Builder<String, Integer>()
|
||||
.put(KEY_SHORTCUT_CREATE_SESSION, ACTION_SHORTCUT_CREATE_SESSION)
|
||||
.put(KEY_SHORTCUT_NEXT_SESSION, ACTION_SHORTCUT_NEXT_SESSION)
|
||||
.put(KEY_SHORTCUT_PREVIOUS_SESSION, ACTION_SHORTCUT_PREVIOUS_SESSION)
|
||||
.put(KEY_SHORTCUT_RENAME_SESSION, ACTION_SHORTCUT_RENAME_SESSION)
|
||||
.build();
|
||||
|
||||
|
||||
|
||||
/** Defines the key for the default working directory */
|
||||
public static final String KEY_DEFAULT_WORKING_DIRECTORY = "default-working-directory"; // Default: "default-working-directory"
|
||||
/** Defines the default working directory */
|
||||
public static final String DEFAULT_IVALUE_DEFAULT_WORKING_DIRECTORY = TermuxConstants.HOME_PATH;
|
||||
|
||||
|
||||
|
||||
/** Defines the key for extra keys */
|
||||
public static final String KEY_EXTRA_KEYS = "extra-keys"; // Default: "extra-keys"
|
||||
/** Defines the key for extra keys style */
|
||||
public static final String KEY_EXTRA_KEYS_STYLE = "extra-keys-style"; // Default: "extra-keys-style"
|
||||
public static final String DEFAULT_IVALUE_EXTRA_KEYS = "[[ESC, TAB, CTRL, ALT, {key: '-', popup: '|'}, DOWN, UP]]";
|
||||
public static final String DEFAULT_IVALUE_EXTRA_KEYS_STYLE = "default";
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/** Defines the set for keys loaded by termux
|
||||
* Setting this to {@code null} will make {@link SharedProperties} throw an exception.
|
||||
* */
|
||||
public static final Set<String> TERMUX_PROPERTIES_LIST = new HashSet<>(Arrays.asList(
|
||||
// boolean
|
||||
KEY_USE_BACK_KEY_AS_ESCAPE_KEY,
|
||||
KEY_USE_BLACK_UI,
|
||||
KEY_USE_FULLSCREEN,
|
||||
KEY_USE_FULLSCREEN_WORKAROUND,
|
||||
KEY_VIRTUAL_VOLUME_KEYS_DISABLED,
|
||||
TermuxConstants.PROP_ALLOW_EXTERNAL_APPS,
|
||||
|
||||
// int
|
||||
KEY_BELL_BEHAVIOUR,
|
||||
|
||||
// Integer
|
||||
KEY_SHORTCUT_CREATE_SESSION,
|
||||
KEY_SHORTCUT_NEXT_SESSION,
|
||||
KEY_SHORTCUT_PREVIOUS_SESSION,
|
||||
KEY_SHORTCUT_RENAME_SESSION,
|
||||
|
||||
// String
|
||||
KEY_DEFAULT_WORKING_DIRECTORY,
|
||||
KEY_EXTRA_KEYS,
|
||||
KEY_EXTRA_KEYS_STYLE
|
||||
));
|
||||
|
||||
/** Defines the set for keys loaded by termux that have default boolean behaviour
|
||||
* "true" -> true
|
||||
* "false" -> false
|
||||
* default: false
|
||||
* */
|
||||
public static final Set<String> TERMUX_DEFAULT_BOOLEAN_BEHAVIOUR_PROPERTIES_LIST = new HashSet<>(Arrays.asList(
|
||||
KEY_USE_FULLSCREEN,
|
||||
KEY_USE_FULLSCREEN_WORKAROUND,
|
||||
TermuxConstants.PROP_ALLOW_EXTERNAL_APPS
|
||||
));
|
||||
|
||||
/** Defines the set for keys loaded by termux that have default inverted boolean behaviour
|
||||
* "false" -> true
|
||||
* "true" -> false
|
||||
* default: true
|
||||
* */
|
||||
public static final Set<String> TERMUX_DEFAULT_INVERETED_BOOLEAN_BEHAVIOUR_PROPERTIES_LIST = new HashSet<>(Arrays.asList(
|
||||
));
|
||||
|
||||
|
||||
|
||||
|
||||
/** Returns the first {@link File} found at
|
||||
* {@link TermuxConstants#TERMUX_PROPERTIES_PRIMARY_PATH} or
|
||||
* {@link TermuxConstants#TERMUX_PROPERTIES_SECONDARY_PATH}
|
||||
* from which termux properties can be loaded.
|
||||
* If the {@link File} found is not a regular file or is not readable then null is returned.
|
||||
*
|
||||
* @return Returns the {@link File} object for termux properties.
|
||||
*/
|
||||
public static File getTermuxPropertiesFile() {
|
||||
String[] possiblePropertiesFileLocations = {
|
||||
TermuxConstants.TERMUX_PROPERTIES_PRIMARY_PATH,
|
||||
TermuxConstants.TERMUX_PROPERTIES_SECONDARY_PATH
|
||||
};
|
||||
|
||||
File propertiesFile = new File(possiblePropertiesFileLocations[0]);
|
||||
int i = 0;
|
||||
while (!propertiesFile.exists() && i < possiblePropertiesFileLocations.length) {
|
||||
propertiesFile = new File(possiblePropertiesFileLocations[i]);
|
||||
i += 1;
|
||||
}
|
||||
|
||||
if (propertiesFile.isFile() && propertiesFile.canRead()) {
|
||||
return propertiesFile;
|
||||
} else {
|
||||
Log.d("termux", "No readable termux.properties file found");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,569 @@
|
||||
package com.termux.app.settings.properties;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.termux.app.input.extrakeys.ExtraKeysInfo;
|
||||
import com.termux.app.input.KeyboardShortcut;
|
||||
|
||||
import org.json.JSONException;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
public class TermuxSharedProperties implements SharedPropertiesParser {
|
||||
|
||||
private final Context mContext;
|
||||
private final SharedProperties mSharedProperties;
|
||||
private final File mPropertiesFile;
|
||||
|
||||
private ExtraKeysInfo mExtraKeysInfo;
|
||||
private final List<KeyboardShortcut> mSessionShortcuts = new ArrayList<>();
|
||||
|
||||
public TermuxSharedProperties(@Nonnull Context context) {
|
||||
mContext = context;
|
||||
mPropertiesFile = TermuxPropertyConstants.getTermuxPropertiesFile();
|
||||
mSharedProperties = new SharedProperties(context, mPropertiesFile, TermuxPropertyConstants.TERMUX_PROPERTIES_LIST, this);
|
||||
loadTermuxPropertiesFromDisk();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Reload the termux properties from disk into an in-memory cache.
|
||||
*/
|
||||
public void loadTermuxPropertiesFromDisk() {
|
||||
mSharedProperties.loadPropertiesFromDisk();
|
||||
dumpPropertiesToLog();
|
||||
dumpInternalPropertiesToLog();
|
||||
setExtraKeys();
|
||||
setSessionShortcuts();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the terminal extra keys and style.
|
||||
*/
|
||||
private void setExtraKeys() {
|
||||
mExtraKeysInfo = null;
|
||||
|
||||
try {
|
||||
// The mMap stores the extra key and style string values while loading properties
|
||||
// Check {@link #getExtraKeysInternalPropertyValueFromValue(String)} and
|
||||
// {@link #getExtraKeysStyleInternalPropertyValueFromValue(String)}
|
||||
String extrakeys = (String) getInternalPropertyValue(TermuxPropertyConstants.KEY_EXTRA_KEYS, true);
|
||||
String extraKeysStyle = (String) getInternalPropertyValue(TermuxPropertyConstants.KEY_EXTRA_KEYS_STYLE, true);
|
||||
mExtraKeysInfo = new ExtraKeysInfo(extrakeys, extraKeysStyle);
|
||||
} catch (JSONException e) {
|
||||
Toast.makeText(mContext, "Could not load and set the \"" + TermuxPropertyConstants.KEY_EXTRA_KEYS + "\" property from the properties file: " + e.toString(), Toast.LENGTH_LONG).show();
|
||||
Log.e("termux", "Could not load and set the \"" + TermuxPropertyConstants.KEY_EXTRA_KEYS + "\" property from the properties file: ", e);
|
||||
|
||||
try {
|
||||
mExtraKeysInfo = new ExtraKeysInfo(TermuxPropertyConstants.DEFAULT_IVALUE_EXTRA_KEYS, TermuxPropertyConstants.DEFAULT_IVALUE_EXTRA_KEYS_STYLE);
|
||||
} catch (JSONException e2) {
|
||||
Toast.makeText(mContext, "Can't create default extra keys", Toast.LENGTH_LONG).show();
|
||||
Log.e("termux", "Could create default extra keys: ", e);
|
||||
mExtraKeysInfo = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the terminal sessions shortcuts.
|
||||
*/
|
||||
private void setSessionShortcuts() {
|
||||
mSessionShortcuts.clear();
|
||||
// The {@link TermuxPropertyConstants#MAP_SESSION_SHORTCUTS} stores the session shortcut key and action pair
|
||||
for (Map.Entry<String, Integer> entry : TermuxPropertyConstants.MAP_SESSION_SHORTCUTS.entrySet()) {
|
||||
// The mMap stores the code points for the session shortcuts while loading properties
|
||||
Integer codePoint = (Integer) getInternalPropertyValue(entry.getKey(), true);
|
||||
// If codePoint is null, then session shortcut did not exist in properties or was invalid
|
||||
// as parsed by {@link #getCodePointForSessionShortcuts(String,String)}
|
||||
// If codePoint is not null, then get the action for the MAP_SESSION_SHORTCUTS key and
|
||||
// add the code point to sessionShortcuts
|
||||
if (codePoint != null)
|
||||
mSessionShortcuts.add(new KeyboardShortcut(codePoint, entry.getValue()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A static function to get the {@link Properties} from the propertiesFile file.
|
||||
*
|
||||
* @param context The {@link Context} for the {@link SharedProperties#getPropertiesFromFile(Context,File)} call.
|
||||
* @param propertiesFile The {@link File} to read the {@link Properties} from.
|
||||
* @return Returns the {@link Properties} object. It will be {@code null} if an exception is
|
||||
* raised while reading the file.
|
||||
*/
|
||||
public static Properties getPropertiesFromFile(Context context, File propertiesFile) {
|
||||
return SharedProperties.getPropertiesFromFile(context, propertiesFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* A static function to get the {@link String} value for the {@link Properties} key read from
|
||||
* the propertiesFile file.
|
||||
*
|
||||
* @param context The {@link Context} for the {@link SharedProperties#getPropertiesFromFile(Context,File)} call.
|
||||
* @param propertiesFile The {@link File} to read the {@link Properties} from.
|
||||
* @param key The key to read.
|
||||
* @param def The default value.
|
||||
* @return Returns the {@link String} object. This will be {@code null} if key is not found.
|
||||
*/
|
||||
public static String getPropertyValue(Context context, File propertiesFile, String key, String def) {
|
||||
return (String) getDefaultIfNull(getDefaultIfNull(getPropertiesFromFile(context, propertiesFile), new Properties()).get(key), def);
|
||||
}
|
||||
|
||||
/**
|
||||
* A static function to check if the value is {@code true} for {@link Properties} key read from
|
||||
* the propertiesFile file.
|
||||
*
|
||||
* @param context The {@link Context} for the {@link SharedProperties#getPropertiesFromFile(Context,File)}call.
|
||||
* @param propertiesFile The {@link File} to read the {@link Properties} from.
|
||||
* @param key The key to read.
|
||||
* @return Returns the {@code true} if the {@link Properties} key {@link String} value equals "true",
|
||||
* regardless of case. If the key does not exist in the file or does not equal "true", then
|
||||
* {@code false} will be returned.
|
||||
*/
|
||||
public static boolean isPropertyValueTrue(Context context, File propertiesFile, String key) {
|
||||
return (boolean) getBooleanValueForStringValue((String) getPropertyValue(context, propertiesFile, key, null), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* A static function to check if the value is {@code false} for {@link Properties} key read from
|
||||
* the propertiesFile file.
|
||||
*
|
||||
* @param context The {@link Context} for the {@link SharedProperties#getPropertiesFromFile(Context,File)} call.
|
||||
* @param propertiesFile The {@link File} to read the {@link Properties} from.
|
||||
* @param key The key to read.
|
||||
* @return Returns the {@code true} if the {@link Properties} key {@link String} value equals "false",
|
||||
* regardless of case. If the key does not exist in the file or does not equal "false", then
|
||||
* {@code true} will be returned.
|
||||
*/
|
||||
public static boolean isPropertyValueFalse(Context context, File propertiesFile, String key) {
|
||||
return (boolean) getInvertedBooleanValueForStringValue((String) getPropertyValue(context, propertiesFile, key, null), true);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Get the {@link Properties} from the {@link #mPropertiesFile} file.
|
||||
*
|
||||
* @param cached If {@code true}, then the {@link Properties} in-memory cache is returned.
|
||||
* Otherwise the {@link Properties} object is read directly from the
|
||||
* {@link #mPropertiesFile} file.
|
||||
* @return Returns the {@link Properties} object. It will be {@code null} if an exception is
|
||||
* raised while reading the file.
|
||||
*/
|
||||
public Properties getProperties(boolean cached) {
|
||||
return mSharedProperties.getProperties(cached);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link String} value for the key passed from the {@link #mPropertiesFile} file.
|
||||
*
|
||||
* @param key The key to read.
|
||||
* @param def The default value.
|
||||
* @param cached If {@code true}, then the value is returned from the the {@link Properties} in-memory cache.
|
||||
* Otherwise the {@link Properties} object is read directly from the file
|
||||
* and value is returned from it against the key.
|
||||
* @return Returns the {@link String} object. This will be {@code null} if key is not found.
|
||||
*/
|
||||
public String getPropertyValue(String key, String def, boolean cached) {
|
||||
return getDefaultIfNull(mSharedProperties.getProperty(key, cached), def);
|
||||
}
|
||||
|
||||
/**
|
||||
* A function to check if the value is {@code true} for {@link Properties} key read from
|
||||
* the {@link #mPropertiesFile} file.
|
||||
*
|
||||
* @param key The key to read.
|
||||
* @param cached If {@code true}, then the value is checked from the the {@link Properties} in-memory cache.
|
||||
* Otherwise the {@link Properties} object is read directly from the file
|
||||
* and value is checked from it.
|
||||
* @return Returns the {@code true} if the {@link Properties} key {@link String} value equals "true",
|
||||
* regardless of case. If the key does not exist in the file or does not equal "true", then
|
||||
* {@code false} will be returned.
|
||||
*/
|
||||
public boolean isPropertyValueTrue(String key, boolean cached) {
|
||||
return (boolean) getBooleanValueForStringValue((String) getPropertyValue(key, null, cached), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* A function to check if the value is {@code false} for {@link Properties} key read from
|
||||
* the {@link #mPropertiesFile} file.
|
||||
*
|
||||
* @param key The key to read.
|
||||
* @param cached If {@code true}, then the value is checked from the the {@link Properties} in-memory cache.
|
||||
* Otherwise the {@link Properties} object is read directly from the file
|
||||
* and value is checked from it.
|
||||
* @return Returns {@code true} if the {@link Properties} key {@link String} value equals "false",
|
||||
* regardless of case. If the key does not exist in the file or does not equal "false", then
|
||||
* {@code true} will be returned.
|
||||
*/
|
||||
public boolean isPropertyValueFalse(String key, boolean cached) {
|
||||
return (boolean) getInvertedBooleanValueForStringValue((String) getPropertyValue(key, null, cached), true);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Get the internal value {@link Object} {@link HashMap <>} in-memory cache for the
|
||||
* {@link #mPropertiesFile} file. A call to {@link #loadTermuxPropertiesFromDisk()} must be made
|
||||
* before this.
|
||||
*
|
||||
* @return Returns a copy of {@link Map} object.
|
||||
*/
|
||||
public Map<String, Object> getInternalProperties() {
|
||||
return mSharedProperties.getInternalProperties();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the internal {@link Object} value for the key passed from the {@link #mPropertiesFile} file.
|
||||
* If cache is {@code true}, then value is returned from the {@link HashMap <>} in-memory cache,
|
||||
* so a call to {@link #loadTermuxPropertiesFromDisk()} must be made before this.
|
||||
*
|
||||
* @param key The key to read from the {@link HashMap<>} in-memory cache.
|
||||
* @param cached If {@code true}, then the value is returned from the the {@link HashMap <>} in-memory cache,
|
||||
* but if the value is null, then an attempt is made to return the default value.
|
||||
* If {@code false}, then the {@link Properties} object is read directly from the file
|
||||
* and internal value is returned for the property value against the key.
|
||||
* @return Returns the {@link Object} object. This will be {@code null} if key is not found or
|
||||
* the object stored against the key is {@code null}.
|
||||
*/
|
||||
public Object getInternalPropertyValue(String key, boolean cached) {
|
||||
Object value;
|
||||
if (cached) {
|
||||
value = mSharedProperties.getInternalProperty(key);
|
||||
// If the value is not null since key was found or if the value was null since the
|
||||
// object stored for the key was itself null, we detect the later by checking if the key
|
||||
// exists in the map.
|
||||
if (value != null || mSharedProperties.getInternalProperties().containsKey(key)) {
|
||||
return value;
|
||||
} else {
|
||||
// This should not happen normally unless mMap was modified after the
|
||||
// {@link #loadTermuxPropertiesFromDisk()} call
|
||||
// A null value can still be returned by
|
||||
// {@link #getInternalPropertyValueFromValue(Context,String,String)} for some keys
|
||||
value = getInternalPropertyValueFromValue(mContext, key, null);
|
||||
Log.w("termux", "The value for \"" + key + "\" not found in SharedProperties cahce, force returning default value: `" + value + "`");
|
||||
return value;
|
||||
}
|
||||
} else {
|
||||
// We get the property value directly from file and return its internal value
|
||||
return getInternalPropertyValueFromValue(mContext, key, mSharedProperties.getProperty(key, false));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the
|
||||
* {@link SharedPropertiesParser#getInternalPropertyValueFromValue(Context,String,String)}
|
||||
* interface function.
|
||||
*/
|
||||
@Override
|
||||
public Object getInternalPropertyValueFromValue(Context context, String key, String value) {
|
||||
return getInternalTermuxPropertyValueFromValue(context, key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* A static function that should return the internal termux {@link Object} for a key/value pair
|
||||
* read from properties file.
|
||||
*
|
||||
* @param context The context for operations.
|
||||
* @param key The key for which the internal object is required.
|
||||
* @param value The literal value for the property found is the properties file.
|
||||
* @return Returns the internal termux {@link Object} object.
|
||||
*/
|
||||
public static Object getInternalTermuxPropertyValueFromValue(Context context, String key, String value) {
|
||||
if(key == null) return null;
|
||||
/*
|
||||
For keys where a MAP_* is checked by respective functions. Note that value to this function
|
||||
would actually be the key for the MAP_*:
|
||||
- If the value is currently null, then searching MAP_* should also return null and internal default value will be used.
|
||||
- If the value is not null and does not exist in MAP_*, then internal default value will be used.
|
||||
- If the value is not null and does exist in MAP_*, then internal value returned by map will be used.
|
||||
*/
|
||||
switch (key) {
|
||||
// boolean
|
||||
case TermuxPropertyConstants.KEY_USE_BACK_KEY_AS_ESCAPE_KEY:
|
||||
return (boolean) getUseBackKeyAsEscapeKeyInternalPropertyValueFromValue(value);
|
||||
case TermuxPropertyConstants.KEY_USE_BLACK_UI:
|
||||
return (boolean) getUseBlackUIInternalPropertyValueFromValue(context, value);
|
||||
case TermuxPropertyConstants.KEY_VIRTUAL_VOLUME_KEYS_DISABLED:
|
||||
return (boolean) getVolumeKeysDisabledInternalPropertyValueFromValue(value);
|
||||
|
||||
// int
|
||||
case TermuxPropertyConstants.KEY_BELL_BEHAVIOUR:
|
||||
return (int) getBellBehaviourInternalPropertyValueFromValue(value);
|
||||
|
||||
// Integer (may be null)
|
||||
case TermuxPropertyConstants.KEY_SHORTCUT_CREATE_SESSION:
|
||||
case TermuxPropertyConstants.KEY_SHORTCUT_NEXT_SESSION:
|
||||
case TermuxPropertyConstants.KEY_SHORTCUT_PREVIOUS_SESSION:
|
||||
case TermuxPropertyConstants.KEY_SHORTCUT_RENAME_SESSION:
|
||||
return (Integer) getCodePointForSessionShortcuts(key, value);
|
||||
|
||||
// String (may be null)
|
||||
case TermuxPropertyConstants.KEY_DEFAULT_WORKING_DIRECTORY:
|
||||
return (String) getDefaultWorkingDirectoryInternalPropertyValueFromValue(value);
|
||||
case TermuxPropertyConstants.KEY_EXTRA_KEYS:
|
||||
return (String) getExtraKeysInternalPropertyValueFromValue(value);
|
||||
case TermuxPropertyConstants.KEY_EXTRA_KEYS_STYLE:
|
||||
return (String) getExtraKeysStyleInternalPropertyValueFromValue(value);
|
||||
default:
|
||||
// default boolean behaviour
|
||||
if(TermuxPropertyConstants.TERMUX_DEFAULT_BOOLEAN_BEHAVIOUR_PROPERTIES_LIST.contains(key))
|
||||
return (boolean) getBooleanValueForStringValue(value, false);
|
||||
// default inverted boolean behaviour
|
||||
else if(TermuxPropertyConstants.TERMUX_DEFAULT_INVERETED_BOOLEAN_BEHAVIOUR_PROPERTIES_LIST.contains(key))
|
||||
return (boolean) getInvertedBooleanValueForStringValue(value, true);
|
||||
// just use String object as is (may be null)
|
||||
else
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Get the boolean value for the {@link String} value.
|
||||
*
|
||||
* @param value The {@link String} value to convert.
|
||||
* @param def The default {@link boolean} value to return.
|
||||
* @return Returns {@code true} or {@code false} if value is the literal string "true" or "false" respectively,
|
||||
* regardless of case. Otherwise returns default value.
|
||||
*/
|
||||
public static boolean getBooleanValueForStringValue(String value, boolean def) {
|
||||
return (boolean) getDefaultIfNull(TermuxPropertyConstants.MAP_GENERIC_BOOLEAN.get(toLowerCase(value)), def);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the inverted boolean value for the {@link String} value.
|
||||
*
|
||||
* @param value The {@link String} value to convert.
|
||||
* @param def The default {@link boolean} value to return.
|
||||
* @return Returns {@code true} or {@code false} if value is the literal string "false" or "true" respectively,
|
||||
* regardless of case. Otherwise returns default value.
|
||||
*/
|
||||
public static boolean getInvertedBooleanValueForStringValue(String value, boolean def) {
|
||||
return (boolean) getDefaultIfNull(TermuxPropertyConstants.MAP_GENERIC_INVERTED_BOOLEAN.get(toLowerCase(value)), def);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if value is not {@code null} and equals {@link TermuxPropertyConstants#VALUE_BACK_KEY_BEHAVIOUR_ESCAPE}, otherwise false.
|
||||
*
|
||||
* @param value The {@link String} value to convert.
|
||||
* @return Returns the internal value for value.
|
||||
*/
|
||||
public static boolean getUseBackKeyAsEscapeKeyInternalPropertyValueFromValue(String value) {
|
||||
return getDefaultIfNull(value, TermuxPropertyConstants.VALUE_BACK_KEY_BEHAVIOUR_BACK).equals(TermuxPropertyConstants.VALUE_BACK_KEY_BEHAVIOUR_ESCAPE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} or {@code false} if value is the literal string "true" or "false" respectively regardless of case.
|
||||
* Otherwise returns {@code true} if the night mode is currently enabled in the system.
|
||||
*
|
||||
* @param value The {@link String} value to convert.
|
||||
* @return Returns the internal value for value.
|
||||
*/
|
||||
public static boolean getUseBlackUIInternalPropertyValueFromValue(Context context, String value) {
|
||||
int nightMode = context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
|
||||
return getBooleanValueForStringValue(value, nightMode == Configuration.UI_MODE_NIGHT_YES);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if value is not {@code null} and equals {@link TermuxPropertyConstants#VALUE_VOLUME_KEY_BEHAVIOUR_VOLUME}, otherwise {@code false}.
|
||||
*
|
||||
* @param value The {@link String} value to convert.
|
||||
* @return Returns the internal value for value.
|
||||
*/
|
||||
public static boolean getVolumeKeysDisabledInternalPropertyValueFromValue(String value) {
|
||||
return getDefaultIfNull(value, TermuxPropertyConstants.VALUE_VOLUME_KEY_BEHAVIOUR_VIRTUAL).equals(TermuxPropertyConstants.VALUE_VOLUME_KEY_BEHAVIOUR_VOLUME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if value is not {@code null} and equals {@link TermuxPropertyConstants#VALUE_BACK_KEY_BEHAVIOUR_ESCAPE}, otherwise {@code false}.
|
||||
*
|
||||
* @param value The {@link String} value to convert.
|
||||
* @return Returns the internal value for value.
|
||||
*/
|
||||
public static int getBellBehaviourInternalPropertyValueFromValue(String value) {
|
||||
return getDefaultIfNull(TermuxPropertyConstants.MAP_BELL_BEHAVIOUR.get(toLowerCase(value)), TermuxPropertyConstants.DEFAULT_IVALUE_BELL_BEHAVIOUR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the code point for the value if key is not {@code null} and value is not {@code null} and is valid,
|
||||
* otherwise returns {@code null}.
|
||||
*
|
||||
* @param key The key for session shortcut.
|
||||
* @param value The {@link String} value to convert.
|
||||
* @return Returns the internal value for value.
|
||||
*/
|
||||
public static Integer getCodePointForSessionShortcuts(String key, String value) {
|
||||
if (key == null) return null;
|
||||
if (value == null) return null;
|
||||
String[] parts = value.toLowerCase().trim().split("\\+");
|
||||
String input = parts.length == 2 ? parts[1].trim() : null;
|
||||
if (!(parts.length == 2 && parts[0].trim().equals("ctrl")) || input.isEmpty() || input.length() > 2) {
|
||||
Log.e("termux", "Keyboard shortcut '" + key + "' is not Ctrl+<something>");
|
||||
return null;
|
||||
}
|
||||
|
||||
char c = input.charAt(0);
|
||||
int codePoint = c;
|
||||
if (Character.isLowSurrogate(c)) {
|
||||
if (input.length() != 2 || Character.isHighSurrogate(input.charAt(1))) {
|
||||
Log.e("termux", "Keyboard shortcut '" + key + "' is not Ctrl+<something>");
|
||||
return null;
|
||||
} else {
|
||||
codePoint = Character.toCodePoint(input.charAt(1), c);
|
||||
}
|
||||
}
|
||||
|
||||
return codePoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path itself if a directory exists at it and is readable, otherwise returns
|
||||
* {@link TermuxPropertyConstants#DEFAULT_IVALUE_DEFAULT_WORKING_DIRECTORY}.
|
||||
*
|
||||
* @param path The {@link String} path to check.
|
||||
* @return Returns the internal value for value.
|
||||
*/
|
||||
public static String getDefaultWorkingDirectoryInternalPropertyValueFromValue(String path) {
|
||||
if (path == null || path.isEmpty()) return TermuxPropertyConstants.DEFAULT_IVALUE_DEFAULT_WORKING_DIRECTORY;
|
||||
File workDir = new File(path);
|
||||
if (!workDir.exists() || !workDir.isDirectory() || !workDir.canRead()) {
|
||||
// Fallback to default directory if user configured working directory does not exist
|
||||
// or is not a directory or is not readable.
|
||||
return TermuxPropertyConstants.DEFAULT_IVALUE_DEFAULT_WORKING_DIRECTORY;
|
||||
} else {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value itself if it is not {@code null}, otherwise returns {@link TermuxPropertyConstants#DEFAULT_IVALUE_EXTRA_KEYS}.
|
||||
*
|
||||
* @param value The {@link String} value to convert.
|
||||
* @return Returns the internal value for value.
|
||||
*/
|
||||
public static String getExtraKeysInternalPropertyValueFromValue(String value) {
|
||||
return getDefaultIfNull(value, TermuxPropertyConstants.DEFAULT_IVALUE_EXTRA_KEYS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value itself if it is not {@code null}, otherwise returns {@link TermuxPropertyConstants#DEFAULT_IVALUE_EXTRA_KEYS_STYLE}.
|
||||
*
|
||||
* @param value {@link String} value to convert.
|
||||
* @return Returns the internal value for value.
|
||||
*/
|
||||
public static String getExtraKeysStyleInternalPropertyValueFromValue(String value) {
|
||||
return getDefaultIfNull(value, TermuxPropertyConstants.DEFAULT_IVALUE_EXTRA_KEYS_STYLE);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public boolean isBackKeyTheEscapeKey() {
|
||||
return (boolean) getInternalPropertyValue(TermuxPropertyConstants.KEY_USE_BACK_KEY_AS_ESCAPE_KEY, true);
|
||||
}
|
||||
|
||||
public boolean isUsingBlackUI() {
|
||||
return (boolean) getInternalPropertyValue(TermuxPropertyConstants.KEY_USE_BLACK_UI, true);
|
||||
}
|
||||
|
||||
public boolean isUsingFullScreen() {
|
||||
return (boolean) getInternalPropertyValue(TermuxPropertyConstants.KEY_USE_FULLSCREEN, true);
|
||||
}
|
||||
|
||||
public boolean isUsingFullScreenWorkAround() {
|
||||
return (boolean) getInternalPropertyValue(TermuxPropertyConstants.KEY_USE_FULLSCREEN_WORKAROUND, true);
|
||||
}
|
||||
|
||||
public boolean areVirtualVolumeKeysDisabled() {
|
||||
return (boolean) getInternalPropertyValue(TermuxPropertyConstants.KEY_VIRTUAL_VOLUME_KEYS_DISABLED, true);
|
||||
}
|
||||
|
||||
public int getBellBehaviour() {
|
||||
return (int) getInternalPropertyValue(TermuxPropertyConstants.KEY_BELL_BEHAVIOUR, true);
|
||||
}
|
||||
|
||||
public List<KeyboardShortcut> getSessionShortcuts() {
|
||||
return mSessionShortcuts;
|
||||
}
|
||||
|
||||
public String getDefaultWorkingDirectory() {
|
||||
return (String) getInternalPropertyValue(TermuxPropertyConstants.KEY_DEFAULT_WORKING_DIRECTORY, true);
|
||||
}
|
||||
|
||||
public ExtraKeysInfo getExtraKeysInfo() {
|
||||
return mExtraKeysInfo;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static <T> T getDefaultIfNull(@Nullable T object, @Nullable T def) {
|
||||
return (object == null) ? def : object;
|
||||
}
|
||||
|
||||
private static String toLowerCase(String value) {
|
||||
if (value == null) return null; else return value.toLowerCase();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public void dumpPropertiesToLog() {
|
||||
Properties properties = getProperties(true);
|
||||
StringBuilder propertiesDump = new StringBuilder();
|
||||
|
||||
propertiesDump.append("Termux Properties:");
|
||||
if (properties != null) {
|
||||
for (String key : properties.stringPropertyNames()) {
|
||||
propertiesDump.append("\n").append(key).append(": `").append(properties.get(key)).append("`");
|
||||
}
|
||||
} else {
|
||||
propertiesDump.append(" null");
|
||||
}
|
||||
|
||||
Log.d("termux", propertiesDump.toString());
|
||||
}
|
||||
|
||||
public void dumpInternalPropertiesToLog() {
|
||||
HashMap<String, Object> internalProperties = (HashMap<String, Object>) getInternalProperties();
|
||||
StringBuilder internalPropertiesDump = new StringBuilder();
|
||||
|
||||
internalPropertiesDump.append("Termux Internal Properties:");
|
||||
if (internalProperties != null) {
|
||||
for (String key : internalProperties.keySet()) {
|
||||
internalPropertiesDump.append("\n").append(key).append(": `").append(internalProperties.get(key)).append("`");
|
||||
}
|
||||
}
|
||||
|
||||
Log.d("termux", internalPropertiesDump.toString());
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user