diff --git a/app/src/main/java/com/termux/app/TermuxActivity.java b/app/src/main/java/com/termux/app/TermuxActivity.java index a2bb11e8c4..08ea0a4c35 100644 --- a/app/src/main/java/com/termux/app/TermuxActivity.java +++ b/app/src/main/java/com/termux/app/TermuxActivity.java @@ -9,6 +9,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; +import android.content.res.Configuration; import android.net.Uri; import android.os.Build; import android.os.Bundle; @@ -18,6 +19,7 @@ import android.view.Gravity; import android.view.Menu; import android.view.MenuItem; +import android.view.SubMenu; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; @@ -30,6 +32,7 @@ import com.termux.R; import com.termux.app.api.file.FileReceiverActivity; +import com.termux.app.style.TermuxBackgroundManager; import com.termux.app.terminal.TermuxActivityRootView; import com.termux.app.terminal.TermuxTerminalSessionActivityClient; import com.termux.app.terminal.io.TermuxTerminalExtraKeys; @@ -140,6 +143,11 @@ public final class TermuxActivity extends AppCompatActivity implements ServiceCo */ TermuxSessionsListViewController mTermuxSessionListViewController; + /** + * The termux background manager for updating background. + */ + TermuxBackgroundManager mTermuxBackgroundManager; + /** * The {@link TermuxActivity} broadcast receiver for various things like terminal style configuration changes. */ @@ -185,6 +193,9 @@ public final class TermuxActivity extends AppCompatActivity implements ServiceCo private static final int CONTEXT_MENU_RESET_TERMINAL_ID = 3; private static final int CONTEXT_MENU_KILL_PROCESS_ID = 4; private static final int CONTEXT_MENU_STYLING_ID = 5; + private static final int CONTEXT_SUBMENU_FONT_AND_COLOR_ID = 11; + private static final int CONTEXT_SUBMENU_SET_BACKROUND_IMAGE_ID = 12; + private static final int CONTEXT_SUBMENU_REMOVE_BACKGROUND_IMAGE_ID = 13; private static final int CONTEXT_MENU_TOGGLE_KEEP_SCREEN_ON = 6; private static final int CONTEXT_MENU_HELP_ID = 7; private static final int CONTEXT_MENU_SETTINGS_ID = 8; @@ -242,6 +253,10 @@ public void onCreate(Bundle savedInstanceState) { getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); } + // Must be done every time activity is created in order to registerForActivityResult, + // Even if the logic of launching is based on user input. + setBackgroundManager(); + setTermuxTerminalViewAndClients(); setTerminalToolbarView(savedInstanceState); @@ -375,6 +390,14 @@ public void onSaveInstanceState(@NonNull Bundle savedInstanceState) { savedInstanceState.putBoolean(ARG_ACTIVITY_RECREATED, true); } + @Override + public void onConfigurationChanged(@NonNull Configuration newConfig) { + Logger.logVerbose(LOG_TAG, "onConfigurationChanged"); + + super.onConfigurationChanged(newConfig); + mTermuxTerminalSessionActivityClient.onConfigurationChanged(newConfig); + } + @@ -595,6 +618,10 @@ private void setToggleKeyboardView() { }); } + private void setBackgroundManager() { + this.mTermuxBackgroundManager = new TermuxBackgroundManager(TermuxActivity.this); + } + @@ -648,7 +675,13 @@ public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuIn menu.add(Menu.NONE, CONTEXT_MENU_AUTOFILL_ID, Menu.NONE, R.string.action_autofill_password); menu.add(Menu.NONE, CONTEXT_MENU_RESET_TERMINAL_ID, Menu.NONE, R.string.action_reset_terminal); menu.add(Menu.NONE, CONTEXT_MENU_KILL_PROCESS_ID, Menu.NONE, getResources().getString(R.string.action_kill_process, getCurrentSession().getPid())).setEnabled(currentSession.isRunning()); - menu.add(Menu.NONE, CONTEXT_MENU_STYLING_ID, Menu.NONE, R.string.action_style_terminal); + + SubMenu subMenu = menu.addSubMenu(Menu.NONE, CONTEXT_MENU_STYLING_ID, Menu.NONE, R.string.action_style_terminal); + subMenu.clearHeader(); + subMenu.add(SubMenu.NONE, CONTEXT_SUBMENU_FONT_AND_COLOR_ID, SubMenu.NONE, R.string.action_font_and_color); + subMenu.add(SubMenu.NONE, CONTEXT_SUBMENU_SET_BACKROUND_IMAGE_ID, SubMenu.NONE, R.string.action_set_background_image); + subMenu.add(SubMenu.NONE, CONTEXT_SUBMENU_REMOVE_BACKGROUND_IMAGE_ID, SubMenu.NONE, R.string.action_remove_background_image); + menu.add(Menu.NONE, CONTEXT_MENU_TOGGLE_KEEP_SCREEN_ON, Menu.NONE, R.string.action_toggle_keep_screen_on).setCheckable(true).setChecked(mPreferences.shouldKeepScreenOn()); menu.add(Menu.NONE, CONTEXT_MENU_HELP_ID, Menu.NONE, R.string.action_open_help); menu.add(Menu.NONE, CONTEXT_MENU_SETTINGS_ID, Menu.NONE, R.string.action_open_settings); @@ -685,8 +718,14 @@ public boolean onContextItemSelected(MenuItem item) { case CONTEXT_MENU_KILL_PROCESS_ID: showKillSessionDialog(session); return true; - case CONTEXT_MENU_STYLING_ID: - showStylingDialog(); + case CONTEXT_SUBMENU_FONT_AND_COLOR_ID: + showFontAndColorDialog(); + return true; + case CONTEXT_SUBMENU_SET_BACKROUND_IMAGE_ID: + mTermuxBackgroundManager.setBackgroundImage(); + return true; + case CONTEXT_SUBMENU_REMOVE_BACKGROUND_IMAGE_ID: + mTermuxBackgroundManager.removeBackgroundImage(true); return true; case CONTEXT_MENU_TOGGLE_KEEP_SCREEN_ON: toggleKeepScreenOn(); @@ -736,7 +775,7 @@ private void onResetTerminalSession(TerminalSession session) { } } - private void showStylingDialog() { + private void showFontAndColorDialog() { Intent stylingIntent = new Intent(); stylingIntent.setClassName(TermuxConstants.TERMUX_STYLING_PACKAGE_NAME, TermuxConstants.TERMUX_STYLING.TERMUX_STYLING_ACTIVITY_NAME); try { @@ -916,6 +955,10 @@ public TermuxAppSharedProperties getProperties() { return mProperties; } + public TermuxBackgroundManager getmTermuxBackgroundManager() { + return mTermuxBackgroundManager; + } + diff --git a/app/src/main/java/com/termux/app/fragments/settings/termux/TermuxStylePreferencesFragment.java b/app/src/main/java/com/termux/app/fragments/settings/termux/TermuxStylePreferencesFragment.java new file mode 100644 index 0000000000..b5feb697cc --- /dev/null +++ b/app/src/main/java/com/termux/app/fragments/settings/termux/TermuxStylePreferencesFragment.java @@ -0,0 +1,104 @@ +package com.termux.app.fragments.settings.termux; + +import android.content.Context; +import android.os.Bundle; + +import androidx.annotation.Keep; +import androidx.annotation.NonNull; +import androidx.preference.PreferenceDataStore; +import androidx.preference.PreferenceFragmentCompat; +import androidx.preference.PreferenceManager; +import androidx.preference.SwitchPreferenceCompat; + +import com.termux.R; +import com.termux.app.style.TermuxBackgroundManager; +import com.termux.shared.termux.settings.preferences.TermuxAppSharedPreferences; + +@Keep +public class TermuxStylePreferencesFragment extends PreferenceFragmentCompat { + + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + Context context = getContext(); + if (context == null) return; + + PreferenceManager preferenceManager = getPreferenceManager(); + preferenceManager.setPreferenceDataStore(TermuxStylePreferencesDataStore.getInstance(context)); + + setPreferencesFromResource(R.xml.termux_style_preferences, rootKey); + + configureBackgroundPreferences(context); + } + + /** + * Configure background preferences and make appropriate changes in the state of components. + * + * @param context The context for operations. + */ + private void configureBackgroundPreferences(@NonNull Context context) { + SwitchPreferenceCompat backgroundImagePreference = findPreference("background_image_enabled"); + + if (backgroundImagePreference != null) { + TermuxAppSharedPreferences preferences = TermuxAppSharedPreferences.build(context, true); + + if (preferences == null) return; + + // If background image preference is disabled and background images are + // missing, then don't allow user to enable it from setting. + if (!preferences.isBackgroundImageEnabled() && !TermuxBackgroundManager.isImageFilesExist(context, false)) { + backgroundImagePreference.setEnabled(false); + } + } + } + +} + +class TermuxStylePreferencesDataStore extends PreferenceDataStore { + + private final Context mContext; + + private final TermuxAppSharedPreferences mPreferences; + + private static TermuxStylePreferencesDataStore mInstance; + + private TermuxStylePreferencesDataStore(Context context) { + mContext = context; + mPreferences = TermuxAppSharedPreferences.build(context, true); + } + + public static synchronized TermuxStylePreferencesDataStore getInstance(Context context) { + if (mInstance == null) { + mInstance = new TermuxStylePreferencesDataStore(context); + } + + return mInstance; + } + + + + @Override + public void putBoolean(String key, boolean value) { + if (mPreferences == null) return; + if (key == null) return; + + switch (key) { + case "background_image_enabled": + mPreferences.setBackgroundImageEnabled(value); + default: + break; + } + } + + @Override + public boolean getBoolean(String key, boolean defValue) { + if (mPreferences == null) return false; + + switch (key) { + case "background_image_enabled": + return mPreferences.isBackgroundImageEnabled(); + default: + return false; + } + } + +} diff --git a/app/src/main/java/com/termux/app/style/TermuxBackgroundManager.java b/app/src/main/java/com/termux/app/style/TermuxBackgroundManager.java new file mode 100644 index 0000000000..4601cd06e4 --- /dev/null +++ b/app/src/main/java/com/termux/app/style/TermuxBackgroundManager.java @@ -0,0 +1,330 @@ +package com.termux.app.style; + +import android.app.Activity; +import android.content.Context; +import android.content.res.Configuration; +import android.graphics.Bitmap; +import android.graphics.Color; +import android.graphics.Point; +import android.graphics.drawable.Drawable; +import android.os.Handler; +import android.os.Looper; + +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContract; +import androidx.activity.result.contract.ActivityResultContracts; +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; +import androidx.viewpager.widget.ViewPager; + +import com.termux.R; +import com.termux.app.TermuxActivity; +import com.termux.shared.activity.ActivityUtils; +import com.termux.shared.data.DataUtils; +import com.termux.shared.errors.Error; +import com.termux.shared.file.FileUtils; +import com.termux.shared.image.ImageUtils; +import com.termux.shared.logger.Logger; +import com.termux.shared.termux.TermuxConstants; +import com.termux.shared.termux.extrakeys.ExtraKeysView; +import com.termux.shared.termux.settings.preferences.TermuxAppSharedPreferences; +import com.termux.shared.theme.ThemeUtils; +import com.termux.shared.view.ViewUtils; +import com.termux.terminal.TerminalSession; +import com.termux.terminal.TextStyle; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class TermuxBackgroundManager { + + /** Require {@link TermuxActivity} to perform operations. */ + private final TermuxActivity mActivity; + + /** A launcher for start the process of executing an {@link ActivityResultContract}. */ + private final ActivityResultLauncher mActivityResultLauncher; + + /** Termux app shared preferences manager. */ + private final TermuxAppSharedPreferences mPreferences; + + /** ExecutorService to execute task in background. */ + private final ExecutorService executor; + + /** Handler allows to send and process {@link android.os.Message Message}. */ + private final Handler handler; + + + private static final String LOG_TAG = "TermuxBackgroundManager"; + + public TermuxBackgroundManager(TermuxActivity activity) { + this.mActivity = activity; + this.mPreferences = activity.getPreferences(); + this.mActivityResultLauncher = registerActivityResultLauncher(); + this.executor = Executors.newSingleThreadExecutor(); + this.handler = new Handler(Looper.getMainLooper()); + } + + /** + * Registers for activity result launcher. It's safe to call before fragment + * or activity is created. + * + * @return A launcher for executing an {@link ActivityResultContract}. + */ + private ActivityResultLauncher registerActivityResultLauncher() { + return mActivity.registerForActivityResult(new ActivityResultContracts.GetContent(), uri -> { + + if (uri != null) { + try { + executor.execute(() -> { + Bitmap bitmap = ImageUtils.getBitmap(mActivity, uri); + + if (bitmap == null) { + Logger.logErrorAndShowToast(mActivity, LOG_TAG, mActivity.getString(R.string.error_background_image_loading_from_gallery_failed)); + return; + } + + ImageUtils.compressAndSaveBitmap(bitmap, TermuxConstants.TERMUX_BACKGROUND_IMAGE_PATH); + boolean success = generateImageFiles(mActivity, bitmap); + + if (success) { + notifyBackgroundUpdated(true); + + Logger.logInfo(LOG_TAG, "Image received successfully from the gallary."); + Logger.logDebug(LOG_TAG, "Storing background original image to " + TermuxConstants.TERMUX_BACKGROUND_IMAGE_PATH); + Logger.logDebug(LOG_TAG, "Storing background portrait image to " + TermuxConstants.TERMUX_BACKGROUND_IMAGE_PORTRAIT_PATH); + Logger.logDebug(LOG_TAG, "Storing background landscape image to " + TermuxConstants.TERMUX_BACKGROUND_IMAGE_LANDSCAPE_PATH); + + } else { + Logger.logErrorAndShowToast(mActivity, LOG_TAG, mActivity.getString(R.string.error_background_image_loading_from_gallery_failed)); + } + }); + + } catch (Exception e) { + Logger.logStackTraceWithMessage(LOG_TAG, "Failed to load image", e); + Logger.showToast(mActivity, mActivity.getString(R.string.error_background_image_loading_from_gallery_failed), true); + } + } + }); + } + + /** + * Check whether the optimized background image for {@link Configuration#ORIENTATION_PORTRAIT + * Portrait} and {@link Configuration#ORIENTATION_LANDSCAPE Landscape} display view exist. + * + * @param context The context for operation. + * @return Returns whether the optimized background image exist or not. + */ + public static boolean isImageFilesExist(@NonNull Context context, boolean shouldGenerate) { + boolean isLandscape = (ViewUtils.getDisplayOrientation(context) == Configuration.ORIENTATION_LANDSCAPE); + Point size = ViewUtils.getDisplaySize(context, true); + + String imagePath1 = isLandscape ? TermuxConstants.TERMUX_BACKGROUND_IMAGE_LANDSCAPE_PATH : TermuxConstants.TERMUX_BACKGROUND_IMAGE_PORTRAIT_PATH; + + String imagePath2 = isLandscape ? TermuxConstants.TERMUX_BACKGROUND_IMAGE_PORTRAIT_PATH : TermuxConstants.TERMUX_BACKGROUND_IMAGE_LANDSCAPE_PATH; + + boolean exist = ImageUtils.isImageOptimized(imagePath1, size) + && ImageUtils.isImageOptimized(imagePath2, DataUtils.swap(size)); + + if (!exist && shouldGenerate && ImageUtils.isImage(TermuxConstants.TERMUX_BACKGROUND_IMAGE_PATH)) { + Bitmap bitmap = ImageUtils.getBitmap(TermuxConstants.TERMUX_BACKGROUND_IMAGE_PATH); + return generateImageFiles(context, bitmap); + } + + return exist; + } + + + + /** + * Enable background image loading. If the image already exist then ask for restore otherwise pick from gallery. + */ + public void setBackgroundImage() { + if (!mPreferences.isBackgroundImageEnabled() && isImageFilesExist(mActivity, true)) { + restoreBackgroundImages(); + + } else { + pickImageFromGallery(); + } + } + + /** + * Disable background image loading and notify about the changes. + * If image files are not deleted then it can be used to restore + * when resetting background to image. + * + * @param deleteFiles The {@code boolean} that decides if it should delete the image files. + */ + public void removeBackgroundImage(boolean deleteFiles) { + if (deleteFiles) { + FileUtils.deleteDirectoryFile(null, TermuxConstants.TERMUX_BACKGROUND_DIR_PATH, true); + } + + notifyBackgroundUpdated(false); + } + + /** {@link ActivityResultLauncher#launch(Object) Launch} Activity for result to pick image from gallery. */ + private void pickImageFromGallery() { + ActivityUtils.startActivityForResult(mActivity, mActivityResultLauncher, ImageUtils.ANY_IMAGE_TYPE); + } + + /** + * If the background images exist then ask user whether to restore them or not. + * If denied pick from gallery. + */ + private void restoreBackgroundImages() { + AlertDialog.Builder b = new AlertDialog.Builder(mActivity); + + b.setMessage(R.string.title_restore_background_image); + b.setPositiveButton(R.string.action_yes, (dialog, id) -> { + notifyBackgroundUpdated(true); + }); + + b.setNegativeButton(R.string.action_no, ((dialog, id) -> { + pickImageFromGallery(); + })); + + b.show(); + } + + /** + * Generate background image files using original image. {@link Context} + * passed to this method must be of an {@link Activity} to determine the size + * of display. + * + * @param context The context require for the operations. + * @param bitmap Image bitmap to save as background. + * @return Returns whether the images generated successfully. + */ + public static boolean generateImageFiles(@NonNull Context context, Bitmap bitmap) { + + if (bitmap == null || !(context instanceof Activity)) { + return false; + } + + Point size = ViewUtils.getDisplaySize(context, true); + boolean isLandscape = ViewUtils.getDisplayOrientation(context) == Configuration.ORIENTATION_LANDSCAPE; + Error error; + + if (isLandscape) { + error = ImageUtils.saveForDisplayResolution(bitmap, size, TermuxConstants.TERMUX_BACKGROUND_IMAGE_LANDSCAPE_PATH, TermuxConstants.TERMUX_BACKGROUND_IMAGE_PORTRAIT_PATH); + + } else { + error = ImageUtils.saveForDisplayResolution(bitmap, DataUtils.swap(size), TermuxConstants.TERMUX_BACKGROUND_IMAGE_LANDSCAPE_PATH, TermuxConstants.TERMUX_BACKGROUND_IMAGE_PORTRAIT_PATH); + } + + return error == null; + } + + + /** + * Updates background to image or solid color. If forced then load again even if + * the background is already set. Forced update is require when the display orientation + * is changed. + * + * @param forced Force background update task. + */ + public void updateBackground(boolean forced) { + if (!mActivity.isVisible()) return; + + if (mActivity.getPreferences().isBackgroundImageEnabled()) { + + Drawable drawable = mActivity.getWindow().getDecorView().getBackground(); + + // If it's not forced update and background is already drawn, + // then avoid reloading of image. + if (!forced && ImageUtils.isBitmapDrawable(drawable)) { + return; + } + + updateBackgroundImage(); + } else { + updateBackgroundColor(); + } + + updateToolbarBackground(); + } + + /** + * Set background to color. + */ + public void updateBackgroundColor() { + if (!mActivity.isVisible()) return; + + TerminalSession session = mActivity.getCurrentSession(); + + if (session != null && session.getEmulator() != null) { + mActivity.getWindow().getDecorView().setBackgroundColor(session.getEmulator().mColors.mCurrentColors[TextStyle.COLOR_INDEX_BACKGROUND]); + } + } + + /** + * Set background to image corresponding to display orientation. + */ + public void updateBackgroundImage() { + boolean isLandscape = ViewUtils.getDisplayOrientation(mActivity) == Configuration.ORIENTATION_LANDSCAPE; + String imagePath = isLandscape ? TermuxConstants.TERMUX_BACKGROUND_IMAGE_LANDSCAPE_PATH : TermuxConstants.TERMUX_BACKGROUND_IMAGE_PORTRAIT_PATH; + + try { + // Performing on main Thread may cause ANR and lag. + executor.execute(() -> { + if (isImageFilesExist(mActivity, true)) { + Drawable drawable = ImageUtils.getDrawable(imagePath); + ImageUtils.addOverlay(drawable, mActivity.getProperties().getBackgroundOverlayColor()); + + handler.post(() -> mActivity.getWindow().getDecorView().setBackground(drawable)); + } else { + Logger.logErrorAndShowToast(mActivity, LOG_TAG, mActivity.getString(R.string.error_background_image_loading_failed)); + + // Image files are unable to load so set background to solid color and notify update. + handler.post(this::updateBackgroundColor); + notifyBackgroundUpdated(false); + } + }); + + } catch (Exception e) { + Logger.logStackTraceWithMessage(LOG_TAG, "Failed to load image", e); + Logger.showToast(mActivity, mActivity.getString(R.string.error_background_image_loading_failed), true); + + // Since loading of image is failed, Set background to solid color. + updateBackgroundColor(); + notifyBackgroundUpdated(false); + } + } + + /** + * Set backgroudn of the ExtraKey toolbar and buttons. + * Must be called when background preference is changed. + */ + public void updateToolbarBackground() { + ViewPager viewPager = mActivity.getTerminalToolbarViewPager(); + ExtraKeysView extraKeysView = mActivity.getExtraKeysView(); + + if (viewPager == null || extraKeysView == null) { + return; + } + + if (mPreferences.isBackgroundImageEnabled()) { + // Set overlay background to ToolbarViewPager and make button transparent. + mActivity.getTerminalToolbarViewPager().setBackgroundColor(mActivity.getProperties().getBackgroundOverlayColor()); + extraKeysView.setButtonBackgroundColor(Color.TRANSPARENT); + + } else { + // Use default background color of ToolbarViewPager and button. + viewPager.setBackgroundColor(Color.BLACK); + extraKeysView.setButtonBackgroundColor(ThemeUtils.getSystemAttrColor(mActivity, ExtraKeysView.ATTR_BUTTON_BACKGROUND_COLOR, ExtraKeysView.DEFAULT_BUTTON_BACKGROUND_COLOR)); + } + } + + + + /** + * Notify that the background is changed. New background can be image or solid color. + * + * @param isImage The {@code boolean} indicates that new background is image or not. + */ + public void notifyBackgroundUpdated(boolean isImage) { + mPreferences.setBackgroundImageEnabled(isImage); + TermuxActivity.updateTermuxActivityStyling(mActivity, false); + } + +} diff --git a/app/src/main/java/com/termux/app/terminal/TermuxTerminalSessionActivityClient.java b/app/src/main/java/com/termux/app/terminal/TermuxTerminalSessionActivityClient.java index cd38163116..a6688ebf0c 100644 --- a/app/src/main/java/com/termux/app/terminal/TermuxTerminalSessionActivityClient.java +++ b/app/src/main/java/com/termux/app/terminal/TermuxTerminalSessionActivityClient.java @@ -7,6 +7,7 @@ import android.content.ClipboardManager; import android.content.Context; import android.content.pm.PackageManager; +import android.content.res.Configuration; import android.graphics.Typeface; import android.media.AudioAttributes; import android.media.SoundPool; @@ -30,7 +31,6 @@ import com.termux.terminal.TerminalColors; import com.termux.terminal.TerminalSession; import com.termux.terminal.TerminalSessionClient; -import com.termux.terminal.TextStyle; import java.io.File; import java.io.FileInputStream; @@ -77,6 +77,10 @@ public void onStart() { // The current terminal session may have changed while being away, force // a refresh of the displayed terminal. mActivity.getTerminalView().onScreenUpdated(); + + // Set background image or color. The display orientation may have changed + // while being away, force a background update. + mActivity.getmTermuxBackgroundManager().updateBackground(true); } /** @@ -104,12 +108,20 @@ public void onStop() { releaseBellSoundPool(); } + public void onConfigurationChanged(@NonNull Configuration newConfig) { + // Display orientation may have changed, force a background update. + mActivity.getmTermuxBackgroundManager().updateBackground(true); + } + /** * Should be called when mActivity.reloadActivityStyling() is called */ public void onReloadActivityStyling() { // Set terminal fonts and colors checkForFontAndColors(); + + // Set background image or color + mActivity.getmTermuxBackgroundManager().updateBackground(true); } @@ -218,7 +230,9 @@ public void onBell(@NonNull TerminalSession session) { @Override public void onColorsChanged(@NonNull TerminalSession changedSession) { if (mActivity.getCurrentSession() == changedSession) - updateBackgroundColor(); + // Background color may have changed. If the background is image and already set, + // no need for update. + mActivity.getmTermuxBackgroundManager().updateBackground(false); } @Override @@ -301,7 +315,10 @@ public void setCurrentSession(TerminalSession session) { // We call the following even when the session is already being displayed since config may // be stale, like current session not selected or scrolled to. checkAndScrollToSession(session); - updateBackgroundColor(); + + // Background color may have changed. If the background is image and already set, + // no need for update. + mActivity.getmTermuxBackgroundManager().updateBackground(false); } void notifyOfSessionChange() { @@ -508,7 +525,6 @@ public void checkForFontAndColors() { if (session != null && session.getEmulator() != null) { session.getEmulator().mColors.reset(); } - updateBackgroundColor(); final Typeface newTypeface = (fontFile.exists() && fontFile.length() > 0) ? Typeface.createFromFile(fontFile) : Typeface.MONOSPACE; mActivity.getTerminalView().setTypeface(newTypeface); @@ -517,12 +533,4 @@ public void checkForFontAndColors() { } } - public void updateBackgroundColor() { - if (!mActivity.isVisible()) return; - TerminalSession session = mActivity.getCurrentSession(); - if (session != null && session.getEmulator() != null) { - mActivity.getWindow().getDecorView().setBackgroundColor(session.getEmulator().mColors.mCurrentColors[TextStyle.COLOR_INDEX_BACKGROUND]); - } - } - } diff --git a/app/src/main/java/com/termux/app/terminal/io/TerminalToolbarViewPager.java b/app/src/main/java/com/termux/app/terminal/io/TerminalToolbarViewPager.java index a526570b09..82530d4a3e 100644 --- a/app/src/main/java/com/termux/app/terminal/io/TerminalToolbarViewPager.java +++ b/app/src/main/java/com/termux/app/terminal/io/TerminalToolbarViewPager.java @@ -55,6 +55,9 @@ public Object instantiateItem(@NonNull ViewGroup collection, int position) { FullScreenWorkAround.apply(mActivity); } + // Update toolbar background corresponding to prefs + mActivity.getmTermuxBackgroundManager().updateToolbarBackground(); + } else { layout = inflater.inflate(R.layout.view_terminal_toolbar_text_input, collection, false); final EditText editText = layout.findViewById(R.id.terminal_toolbar_text_input); diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 794d8df3e0..e13755e6fb 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -82,6 +82,10 @@ Really kill this session? Style + Font and Color + Set background image + Remove background image + Keep screen on Help Settings @@ -128,6 +132,13 @@ + + Background image detected. Do you want to restore it? + Couldn\'t load background image. Setting background color. + Failed to load image. Try another image. + + + Failed to start TermuxService. Check logcat for exception message. Failed to start TermuxService while app is in background due to android bg restrictions. @@ -203,6 +214,21 @@ If it causes screen flickering on your devices, then disable it. (Default) + + Termux Style + Preferences for termux style + + + Background + + + Background Image Enabled + Background image will be disabled. + To enable it appropriate portrait and landscape file should be present. + If you want to set new background, set it from context menu by long click on terminal. (Default) + Background image will be enabled. + + &TERMUX_API_APP_NAME; diff --git a/app/src/main/res/xml/termux_preferences.xml b/app/src/main/res/xml/termux_preferences.xml index 90353c51e1..79ad123dda 100644 --- a/app/src/main/res/xml/termux_preferences.xml +++ b/app/src/main/res/xml/termux_preferences.xml @@ -15,4 +15,9 @@ app:summary="@string/termux_terminal_view_preferences_summary" app:fragment="com.termux.app.fragments.settings.termux.TerminalViewPreferencesFragment"/> + + diff --git a/app/src/main/res/xml/termux_style_preferences.xml b/app/src/main/res/xml/termux_style_preferences.xml new file mode 100644 index 0000000000..f1eb29791a --- /dev/null +++ b/app/src/main/res/xml/termux_style_preferences.xml @@ -0,0 +1,16 @@ + + + + + + + + + + diff --git a/termux-shared/src/main/java/com/termux/shared/activity/ActivityUtils.java b/termux-shared/src/main/java/com/termux/shared/activity/ActivityUtils.java index 0ff28126aa..3d2bf644a3 100644 --- a/termux-shared/src/main/java/com/termux/shared/activity/ActivityUtils.java +++ b/termux-shared/src/main/java/com/termux/shared/activity/ActivityUtils.java @@ -5,6 +5,7 @@ import android.content.Intent; import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContract; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; @@ -134,4 +135,49 @@ else if (context instanceof Activity) return null; } + + + /** + * Wrapper for {@link #startActivityForResult(Context, ActivityResultLauncher, Object, boolean, boolean)}. + */ + public static Error startActivityForResult(Context context, @NonNull ActivityResultLauncher activityResultLauncher, + T input) { + return startActivityForResult(context, activityResultLauncher, input, true, true); + } + + /** + * Generic method to start an {@link Activity} for result. + * @param context The context for operations. It must be an instance of {@link Activity} or + * {@link AppCompatActivity}. It is ignored if {@code activityResultLauncher} + * is not {@code null}. + * @param activityResultLauncher A launcher for start the process of executing an {@link ActivityResultContract}. + * @param input The data required to {@link ActivityResultLauncher#launch(Object) launch} Activity. + * @param logErrorMessage If an error message should be logged if failed to start activity. + * @param showErrorMessage If an error message toast should be shown if failed to start activity + * in addition to logging a message. The {@code context} must not be + * {@code null}. + * @param Type of the input required to {@link ActivityResultLauncher#launch(Object) launch}. + * @return Returns the {@code error} if starting activity was not successful, otherwise {@code null}. + */ + public static Error startActivityForResult(Context context, @NonNull ActivityResultLauncher activityResultLauncher, + T input, boolean logErrorMessage, boolean showErrorMessage) { + Error error; + String activityName = "Unknown"; + + if (input instanceof Intent && ((Intent) input).getComponent() != null) { + activityName = ((Intent) input).getComponent().getClassName(); + } + + try { + activityResultLauncher.launch(input); + } catch (Exception e) { + error = ActivityErrno.ERRNO_START_ACTIVITY_FOR_RESULT_FAILED_WITH_EXCEPTION.getError(e, activityName, e.getMessage()); + if (logErrorMessage) + error.logErrorAndShowToast(showErrorMessage ? context : null, LOG_TAG); + return error; + } + + return null; + } + } diff --git a/termux-shared/src/main/java/com/termux/shared/data/DataUtils.java b/termux-shared/src/main/java/com/termux/shared/data/DataUtils.java index f95f2b52f8..ae7e0d1a5f 100644 --- a/termux-shared/src/main/java/com/termux/shared/data/DataUtils.java +++ b/termux-shared/src/main/java/com/termux/shared/data/DataUtils.java @@ -1,5 +1,7 @@ package com.termux.shared.data; +import android.graphics.Color; +import android.graphics.Point; import android.os.Bundle; import androidx.annotation.NonNull; @@ -10,7 +12,6 @@ import java.io.ByteArrayOutputStream; import java.io.ObjectOutputStream; import java.io.Serializable; -import java.util.Collections; public class DataUtils { @@ -255,4 +256,50 @@ public static long getSerializedSize(Serializable object) { } } + + /** + * Wrapper for {@link #getIntColorFromString(String, int, boolean)} with `setAlpha` `false`. + */ + public static int getIntColorFromString(String value, int def) { + return getIntColorFromString(value, def, false); + } + + /** + * Get an {@code int} color from {@link String} with alpha value change. If {@code setAlpha} + * is {@code true} and given value is missing alpha then set it using def alpha. + * + * @param value The {@link String} value. + * @param def The default value if failed to read a valid value. + * @param setAlpha The {@code boolean} value that decides whether to set alpha or not. + * @return Returns the {@code int} color value after parsing the {@link String} + * value, otherwise returns default value. + */ + public static int getIntColorFromString(String value, int def, boolean setAlpha) { + if (value == null) return def; + + try { + int color = Color.parseColor(value); + + if (setAlpha && value.length() == 7) { + // Use alpha value of `def` color and rgb value of given `value`. + color = (def & 0xff000000) | (color & 0x00ffffff); + } + + return color; + } catch (Exception e) { + return def; + } + } + + + /** + * Exchanges the value of x and y in {@link Point}. + * + * @param point The original source point to swap. + * @return Returns new swaped point. + */ + public static Point swap(Point point) { + return new Point(point.y, point.x); + } + } diff --git a/termux-shared/src/main/java/com/termux/shared/image/ImageUtils.java b/termux-shared/src/main/java/com/termux/shared/image/ImageUtils.java new file mode 100644 index 0000000000..a021b5002d --- /dev/null +++ b/termux-shared/src/main/java/com/termux/shared/image/ImageUtils.java @@ -0,0 +1,278 @@ +package com.termux.shared.image; + +import android.content.Context; +import android.content.res.Configuration; +import android.graphics.Bitmap; +import android.graphics.Bitmap.CompressFormat; +import android.graphics.BitmapFactory; +import android.graphics.BlendMode; +import android.graphics.BlendModeColorFilter; +import android.graphics.ImageDecoder; +import android.graphics.Point; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.media.ThumbnailUtils; +import android.net.Uri; +import android.os.Build; +import android.provider.MediaStore; + +import com.termux.shared.errors.Error; +import com.termux.shared.file.FileUtils; +import com.termux.shared.file.FileUtilsErrno; +import com.termux.shared.logger.Logger; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; + +public final class ImageUtils { + + /** + * Request code that can be used to distinguish Activity result. + */ + public static final int REQUEST_CODE_IMAGE = 100; + + /** + * Compression quality used to compress image. The value is interpreted differently depending on the {@link CompressFormat CompressFormat}. + */ + public static final int COMPRESS_QUALITY = 80; + + /** + * Tolerance for diffrence in original image required optimized image. + */ + public static final int OPTIMALITY_TOLERANCE = 50; + + public static final String IMAGE_TYPE = "image"; + + public static final String ANY_IMAGE_TYPE = IMAGE_TYPE + "/*"; + + + private static final String LOG_TAG = "ImageUtils"; + + /** + * Don't let anyone instantiate this class. + */ + private ImageUtils() { + } + + /** + * Get an {@link Bitmap} image from the {@link Uri}. + * + * @param context The context for the operations. + * @param uri The uri from where image content will be loaded. + * @return Bitmap containing the image, or return {@code null} if failed to + * load bitmap content. + */ + public static Bitmap getBitmap(final Context context, Uri uri) { + Bitmap bitmap = null; + + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + bitmap = ImageDecoder.decodeBitmap(ImageDecoder.createSource(context.getContentResolver(), uri)); + } else { + bitmap = MediaStore.Images.Media.getBitmap(context.getContentResolver(), uri); + } + bitmap = MediaStore.Images.Media.getBitmap(context.getContentResolver(), uri); + } catch (IOException e) { + Logger.logStackTraceWithMessage(LOG_TAG, "Failed to load bitmap from " + uri, e); + } + return bitmap; + } + + /** + * Generate image bitmap from the path. + * + * @param path The path for image file + * @return Bitmap generated from image path, if fails to + * to generate returns {@code null} + */ + public static Bitmap getBitmap(String path) { + return BitmapFactory.decodeFile(path); + } + + + /** + * Creates an centered and resized {@link Bitmap} according to given size. + * + * @param bitmap Original bitmap source to resize. + * @param point Target size containing width and height to resize. + * @return Returns the resized bitmap for given size. + */ + public static Bitmap resizeBitmap(Bitmap bitmap, Point point) { + return ThumbnailUtils.extractThumbnail(bitmap, point.x, point.y); + } + + /** + * Wrapper for {@link #compressAndSaveBitmap(Bitmap, int, String)} with `{@link #COMPRESS_QUALITY} `quality`. + */ + public static Error compressAndSaveBitmap(Bitmap bitmap, String path) { + return compressAndSaveBitmap(bitmap, COMPRESS_QUALITY, path); + } + + /** + * Wrapper for {@link #compressAndSaveBitmap(Bitmap, CompressFormat, int, String)} with `{@link Bitmap.CompressFormat#JPEG}` `format`. + */ + public static Error compressAndSaveBitmap(Bitmap bitmap, int quality, String path) { + return compressAndSaveBitmap(bitmap, Bitmap.CompressFormat.JPEG, quality, path); + } + + /** + * Compress the given bitmap image file for given format and quality. + * + * @param bitmap Original source bitmap. + * @param foramt The format for image compression. + * @param quality Hint to the compressor, 0-100. The value is interpreted differently + * depending on the {@link Bitmap.CompressFormat}. + * @param path The path to store compressed bitmap. + * @return Returns the {@code error} if compression and save operation was not successful, + * otherwise {@code null}. + */ + public static Error compressAndSaveBitmap(Bitmap bitmap, Bitmap.CompressFormat foramt, int quality, String path) { + FileUtils.deleteRegularFile(null, path, true); + Error error = FileUtils.createRegularFile(path); + + if (error != null) + return error; + + try (FileOutputStream out = new FileOutputStream(path)) { + bitmap.compress(foramt, quality, out); + } catch (Exception e) { + FileUtils.deleteRegularFile(null, path, true); + error = FileUtilsErrno.ERRNO_CREATING_FILE_FAILED_WITH_EXCEPTION.getError(e, e.getMessage()); + } + + return error; + } + + + /** + * Wrapper for {@link #getDrawable(String)} with `file.getAbsolutePath()` `path` of file. + */ + public static Drawable getDrawable(File file) { + String path = file.getAbsolutePath(); + + return getDrawable(path); + } + + /** + * Create {@link BitmapDrawable} from specified file path. + * + * @param path The path file to load image bitmap drawable. + * @return Drawable created from image file path. + */ + public static Drawable getDrawable(String path) { + return BitmapDrawable.createFromPath(path); + } + + /** + * Add an overlay color/tint on image with {@link BlendMode#MULTIPLY}. + * + * @param drawable The source image bitmap drawable. + * @param color Overlay color for image. + */ + public static void addOverlay(Drawable drawable, int color) { + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + drawable.setColorFilter(new BlendModeColorFilter(color, BlendMode.DARKEN)); + } else { + drawable.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.DARKEN)); + } + } + + + /** + * Wrapper for {@link #isImageOptimized(String, Point, int)} with `{@link #OPTIMALITY_TOLERANCE}` `tolerance`. + */ + public static boolean isImageOptimized(String path, Point point) { + return isImageOptimized(path, point, OPTIMALITY_TOLERANCE); + } + + /** + * Wrapper for {@link #isImageOptimized(String, int, int, int)} with `width` and `height` obtained from {@link Point}. + */ + public static boolean isImageOptimized(String path, Point point, int tolerance) { + return isImageOptimized(path, point.x, point.y, tolerance); + } + + /** + * Check whether the image file present at file location is optimized corresponding + * to given width and height. It can tolorent error upto given value. + * + * @param path The file path of image. + * @param width The required width of image file. + * @param height The required width of image file. + * @param tolerance The tolerance value upto which diffrence is ignored. + * @return Returns whether the given image is optimized or not. + */ + public static boolean isImageOptimized(String path, int width, int height, int tolerance) { + + if (!FileUtils.regularFileExists(path, false)) { + Logger.logInfo(LOG_TAG, "Image file " + path + " does not exist."); + return false; + } + + BitmapFactory.Options opt = new BitmapFactory.Options(); + opt.inJustDecodeBounds = true; + BitmapFactory.decodeFile(path, opt); + + int imgWidth = opt.outWidth; + int imgHeight = opt.outHeight; + + return Math.abs(imgWidth - width) <= tolerance && Math.abs(imgHeight - height) <= tolerance; + } + + public static boolean isImage(String path) { + if (!FileUtils.regularFileExists(path, false)) { + Logger.logInfo(LOG_TAG, "Image file " + path + " does not exist."); + return false; + } + + BitmapFactory.Options opt = new BitmapFactory.Options(); + opt.inJustDecodeBounds = true; + BitmapFactory.decodeFile(path, opt); + + return opt.outWidth != -1 && opt.outHeight != -1; + } + + /** + * Resize bitmap for {@link Configuration#ORIENTATION_PORTRAIT Portrait} and {@link + * Configuration#ORIENTATION_LANDSCAPE Landscape} display view. + * Also Compress the image bitmap before saving it to given path. + * + * @param bitmap The original bitmap image to resize and store. + * @param point Display resolution containing width and height. + * @param path1 The path for storing image with width point.x and height point.y + * @param path2 The path for storing image with width point.y and height point.x + * @return Returns the {@code error} if save operation was not successful, + * otherwise {@code null}. + */ + public static Error saveForDisplayResolution(Bitmap bitmap, Point point, String path1, String path2) { + + Error error; + Bitmap bitmap1 = resizeBitmap(bitmap, point); + error = compressAndSaveBitmap(bitmap1, path1); + + if (error != null) { + return error; + } + + Bitmap bitmap2 = resizeBitmap(bitmap, new Point(point.y, point.x)); + error = compressAndSaveBitmap(bitmap2, path2); + + return error; + } + + /** + * Check for the given {@link Drawable} whether it is instance of {@link + * BitmapDrawable} or not. + * + * @param drawable The drawable to check. + * @return Retruns whether drawable is bitmap drawable. + */ + public static boolean isBitmapDrawable(Drawable drawable) { + return drawable instanceof BitmapDrawable; + } + +} diff --git a/termux-shared/src/main/java/com/termux/shared/termux/TermuxConstants.java b/termux-shared/src/main/java/com/termux/shared/termux/TermuxConstants.java index 59f523af72..38f41120ec 100644 --- a/termux-shared/src/main/java/com/termux/shared/termux/TermuxConstants.java +++ b/termux-shared/src/main/java/com/termux/shared/termux/TermuxConstants.java @@ -277,6 +277,12 @@ * * - 0.52.0 (2022-06-18) * - Added `TERMUX_PREFIX_DIR_IGNORED_SUB_FILES_PATHS_TO_CONSIDER_AS_EMPTY`. + * + * - 0.53.0 (2022-11-04) + * - Added `TERMUX_BACKGROUND_DIR_PATH`, `TERMUX_BACKGROUND_DIR`, + * `TERMUX_BACKGROUND_IMAGE_PATH`, `TERMUX_BACKGROUND_IMAGE_FILE`, + * `TERMUX_BACKGROUND_IMAGE_PORTRAIT_PATH`, `TERMUX_BACKGROUND_IMAGE_PORTRAIT_FILE`, + * `TERMUX_BACKGROUND_IMAGE_LANDSCAPE_PATH`, and `TERMUX_BACKGROUND_IMAGE_LANDSCAPE_FILE`. */ /** @@ -678,6 +684,31 @@ public final class TermuxConstants { + /** Termux app background directory path */ + public static final String TERMUX_BACKGROUND_DIR_PATH = TERMUX_DATA_HOME_DIR_PATH + "/background"; // Default: "/data/data/com.termux/files/.termux/background" + /** Termux app background directory */ + public static final File TERMUX_BACKGROUND_DIR = new File(TERMUX_BACKGROUND_DIR_PATH); + + /** Termux app backgorund original image file path */ + public static final String TERMUX_BACKGROUND_IMAGE_PATH = TERMUX_BACKGROUND_DIR_PATH + "/background.jpeg"; // Default: "/data/data/com.termux/files/home/.termux/background.jpeg" + + /** Termux app backgorund original image file */ + public static final File TERMUX_BACKGROUND_IMAGE_FILE = new File(TERMUX_BACKGROUND_IMAGE_PATH); + + /** Termux app portrait backgorund image file path */ + public static final String TERMUX_BACKGROUND_IMAGE_PORTRAIT_PATH = TERMUX_BACKGROUND_DIR_PATH + "/background_portrait.jpeg"; // Default: "/data/data/com.termux/files/home/.termux/background/background_portrait.jpeg" + + /** Termux app portrait backgorund image file */ + public static final File TERMUX_BACKGROUND_IMAGE_PORTRAIT_FILE = new File(TERMUX_BACKGROUND_IMAGE_PORTRAIT_PATH); + + /** Termux app landscape backgorund image file path */ + public static final String TERMUX_BACKGROUND_IMAGE_LANDSCAPE_PATH = TERMUX_BACKGROUND_DIR_PATH + "/background_landscape.jpeg"; // Default: "/data/data/com.termux/files/home/.termux/background/background_landscape.jpeg" + + /** Termux app landscape backgorund image file */ + public static final File TERMUX_BACKGROUND_IMAGE_LANDSCAPE_FILE = new File(TERMUX_BACKGROUND_IMAGE_LANDSCAPE_PATH); + + + /** Termux and plugin apps directory path */ public static final String TERMUX_APPS_DIR_PATH = TERMUX_FILES_DIR_PATH + "/apps"; // Default: "/data/data/com.termux/files/apps" /** Termux and plugin apps directory */ diff --git a/termux-shared/src/main/java/com/termux/shared/termux/settings/preferences/TermuxAppSharedPreferences.java b/termux-shared/src/main/java/com/termux/shared/termux/settings/preferences/TermuxAppSharedPreferences.java index cd3812fe49..aefc79274e 100644 --- a/termux-shared/src/main/java/com/termux/shared/termux/settings/preferences/TermuxAppSharedPreferences.java +++ b/termux-shared/src/main/java/com/termux/shared/termux/settings/preferences/TermuxAppSharedPreferences.java @@ -234,6 +234,16 @@ public void setTerminalViewKeyLoggingEnabled(boolean value) { + public boolean isBackgroundImageEnabled() { + return SharedPreferenceUtils.getBoolean(mSharedPreferences, TERMUX_APP.KEY_BACKGROUND_IMAGE_ENABLED, TERMUX_APP.DEFAULT_VALUE_BACKGROUND_IMAGE_ENABLED); + } + + public void setBackgroundImageEnabled(boolean value) { + SharedPreferenceUtils.setBoolean(mSharedPreferences, TERMUX_APP.KEY_BACKGROUND_IMAGE_ENABLED, value, false); + } + + + public boolean arePluginErrorNotificationsEnabled(boolean readFromFile) { if (readFromFile) return SharedPreferenceUtils.getBoolean(mMultiProcessSharedPreferences, TERMUX_APP.KEY_PLUGIN_ERROR_NOTIFICATIONS_ENABLED, TERMUX_APP.DEFAULT_VALUE_PLUGIN_ERROR_NOTIFICATIONS_ENABLED); diff --git a/termux-shared/src/main/java/com/termux/shared/termux/settings/preferences/TermuxPreferenceConstants.java b/termux-shared/src/main/java/com/termux/shared/termux/settings/preferences/TermuxPreferenceConstants.java index 15bc74c9f2..228d94d96d 100644 --- a/termux-shared/src/main/java/com/termux/shared/termux/settings/preferences/TermuxPreferenceConstants.java +++ b/termux-shared/src/main/java/com/termux/shared/termux/settings/preferences/TermuxPreferenceConstants.java @@ -69,6 +69,10 @@ * - 0.16.0 (2022-06-11) * - Added following to `TERMUX_APP`: * `KEY_APP_SHELL_NUMBER_SINCE_BOOT` and `KEY_TERMINAL_SESSION_NUMBER_SINCE_BOOT`. + * + * - 0.17.0 (2022-11-04) + * - Added following to `TERMUX_APP`: + * `KEY_BACKGROUND_IMAGE_ENABLED` and `DEFAULT_VALUE_BACKGROUND_IMAGE_ENABLED`. */ import com.termux.shared.shell.command.ExecutionCommand; @@ -180,6 +184,12 @@ public static final class TERMUX_APP { public static final String KEY_CRASH_REPORT_NOTIFICATIONS_ENABLED = "crash_report_notifications_enabled"; public static final boolean DEFAULT_VALUE_CRASH_REPORT_NOTIFICATIONS_ENABLED = true; + /** + * Defines the key for whether background image is enabled or not. + */ + public static final String KEY_BACKGROUND_IMAGE_ENABLED = "background_image_enabled"; + public static final boolean DEFAULT_VALUE_BACKGROUND_IMAGE_ENABLED = false; + } diff --git a/termux-shared/src/main/java/com/termux/shared/termux/settings/properties/TermuxPropertyConstants.java b/termux-shared/src/main/java/com/termux/shared/termux/settings/properties/TermuxPropertyConstants.java index dd935f3ab5..e99fb7a8dc 100644 --- a/termux-shared/src/main/java/com/termux/shared/termux/settings/properties/TermuxPropertyConstants.java +++ b/termux-shared/src/main/java/com/termux/shared/termux/settings/properties/TermuxPropertyConstants.java @@ -80,6 +80,9 @@ * * - 0.18.0 (2022-06-13) * - Add `KEY_DISABLE_FILE_SHARE_RECEIVER` and `KEY_DISABLE_FILE_VIEW_RECEIVER`. + * + * - 0.19.0 (2022-11-04) + * - Add `KEY_BACKGROUND_OVERLAY_COLOR` and `DEFAULT_IVALUE_BACKGROUND_OVERLAY_COLOR` */ /** @@ -384,6 +387,12 @@ public final class TermuxPropertyConstants { + /** Defines the key for background overlay color */ + public static final String KEY_BACKGROUND_OVERLAY_COLOR = "background-overlay-color"; // Default: "background-overlay-color + public static final int DEFAULT_IVALUE_BACKGROUND_OVERLAY_COLOR = 0x59000000; + + + /** Defines the set for keys loaded by termux @@ -430,7 +439,8 @@ public final class TermuxPropertyConstants { KEY_EXTRA_KEYS_STYLE, KEY_NIGHT_MODE, KEY_SOFT_KEYBOARD_TOGGLE_BEHAVIOUR, - KEY_VOLUME_KEYS_BEHAVIOUR + KEY_VOLUME_KEYS_BEHAVIOUR, + KEY_BACKGROUND_OVERLAY_COLOR )); /** Defines the set for keys loaded by termux that have default boolean behaviour with false as default. diff --git a/termux-shared/src/main/java/com/termux/shared/termux/settings/properties/TermuxSharedProperties.java b/termux-shared/src/main/java/com/termux/shared/termux/settings/properties/TermuxSharedProperties.java index e277f4506a..58e63359b8 100644 --- a/termux-shared/src/main/java/com/termux/shared/termux/settings/properties/TermuxSharedProperties.java +++ b/termux-shared/src/main/java/com/termux/shared/termux/settings/properties/TermuxSharedProperties.java @@ -273,6 +273,8 @@ public static Object getInternalTermuxPropertyValueFromValue(Context context, St return (int) getTerminalMarginVerticalInternalPropertyValueFromValue(value); case TermuxPropertyConstants.KEY_TERMINAL_TRANSCRIPT_ROWS: return (int) getTerminalTranscriptRowsInternalPropertyValueFromValue(value); + case TermuxPropertyConstants.KEY_BACKGROUND_OVERLAY_COLOR: + return (int) getBackgroundOverlayInternalPropertyValueFromValue(value); /* float */ case TermuxPropertyConstants.KEY_TERMINAL_TOOLBAR_HEIGHT_SCALE_FACTOR: @@ -438,6 +440,18 @@ public static int getTerminalTranscriptRowsInternalPropertyValueFromValue(String true, true, LOG_TAG); } + /** + * Returns the int for the color value if its not null and is in form of {@code #AARRGGBB}. + * If the value does not contain alpha value then it will borrow it from {@link + * TermuxPropertyConstants#DEFAULT_IVALUE_BACKGROUND_OVERLAY_COLOR} + * + * @param value The {@link String} value to convert. + * @return Returns the internal value for value. + */ + public static int getBackgroundOverlayInternalPropertyValueFromValue(String value) { + return DataUtils.getIntColorFromString(value, TermuxPropertyConstants.DEFAULT_IVALUE_BACKGROUND_OVERLAY_COLOR, true); + } + /** * Returns the int for the value if its not null and is between * {@link TermuxPropertyConstants#IVALUE_TERMINAL_TOOLBAR_HEIGHT_SCALE_FACTOR_MIN} and @@ -654,6 +668,10 @@ public int getTerminalTranscriptRows() { return (int) getInternalPropertyValue(TermuxPropertyConstants.KEY_TERMINAL_TRANSCRIPT_ROWS, true); } + public int getBackgroundOverlayColor() { + return (int) getInternalPropertyValue(TermuxPropertyConstants.KEY_BACKGROUND_OVERLAY_COLOR, true); + } + public float getTerminalToolbarHeightScaleFactor() { return (float) getInternalPropertyValue(TermuxPropertyConstants.KEY_TERMINAL_TOOLBAR_HEIGHT_SCALE_FACTOR, true); }