Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4dfed3320e | ||
|
|
7ac62c9840 | ||
|
|
fd80cdaf23 | ||
|
|
19c690d02b | ||
|
|
e119d34bca | ||
|
|
f545ebf0bd | ||
|
|
0b4bbaf23d | ||
|
|
e7dd0eeebe | ||
|
|
7ef9255437 | ||
|
|
7225e2b379 | ||
|
|
1ad038ece5 | ||
|
|
cb8b0225ca | ||
|
|
7620800cd5 | ||
|
|
6837db0015 | ||
|
|
e08e3b536e |
@@ -26,8 +26,8 @@ android {
|
||||
applicationId "com.termux"
|
||||
minSdkVersion project.properties.minSdkVersion.toInteger()
|
||||
targetSdkVersion project.properties.targetSdkVersion.toInteger()
|
||||
versionCode 113
|
||||
versionName "0.113"
|
||||
versionCode 114
|
||||
versionName "0.114"
|
||||
|
||||
manifestPlaceholders.TERMUX_PACKAGE_NAME = "com.termux"
|
||||
manifestPlaceholders.TERMUX_APP_NAME = "Termux"
|
||||
@@ -155,11 +155,11 @@ clean {
|
||||
|
||||
task downloadBootstraps() {
|
||||
doLast {
|
||||
def version = "2021.05.16-r1"
|
||||
downloadBootstrap("aarch64", "6e340d8ab11d1225b89ee920e0884cbbd944d37765d81c5b06ef34579564fd9a", version)
|
||||
downloadBootstrap("arm", "3f02bc2b5bd45c2ec5170527e39ee0413246698f11be4799c7bde6d364cfd780", version)
|
||||
downloadBootstrap("i686", "36a3733fb2d8531d7f8abd989b711919872b9e8a79d7eb2e8b00bef467199187", version)
|
||||
downloadBootstrap("x86_64", "3885376cc514220c0803e38f70b25f837854029fff2b7fda7a81452623cd9074", version)
|
||||
def version = "2021.06.04-r1"
|
||||
downloadBootstrap("aarch64", "115409ec8abe096080f9e6d9b2765e009c818b3e5aff0133792182009388b81e", version)
|
||||
downloadBootstrap("arm", "c289688d4f9aabd86240a4d3e25bd89c1a96641e0d085d9ee5a471e9f019e16f", version)
|
||||
downloadBootstrap("i686", "63cfb8e39cac20cecbe9e9890f49a30d7454d4c4dfa2b22b5d0cc6de1dc59726", version)
|
||||
downloadBootstrap("x86_64", "db47007825e65f8ed15fc46d6b8e3119b01ca4a9856d7c7e5604cf804e753ba2", version)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ import android.widget.ListView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.termux.R;
|
||||
import com.termux.app.terminal.TermuxActivityRootView;
|
||||
import com.termux.shared.termux.TermuxConstants;
|
||||
import com.termux.shared.termux.TermuxConstants.TERMUX_APP.TERMUX_ACTIVITY;
|
||||
import com.termux.app.activities.HelpActivity;
|
||||
@@ -68,7 +69,6 @@ import androidx.viewpager.widget.ViewPager;
|
||||
*/
|
||||
public final class TermuxActivity extends Activity implements ServiceConnection {
|
||||
|
||||
|
||||
/**
|
||||
* The connection to the {@link TermuxService}. Requested in {@link #onCreate(Bundle)} with a call to
|
||||
* {@link #bindService(Intent, ServiceConnection, int)}, and obtained and stored in
|
||||
@@ -77,7 +77,7 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
||||
TermuxService mTermuxService;
|
||||
|
||||
/**
|
||||
* The main view of the activity showing the terminal. Initialized in onCreate().
|
||||
* The {@link TerminalView} shown in {@link TermuxActivity} that displays the terminal.
|
||||
*/
|
||||
TerminalView mTerminalView;
|
||||
|
||||
@@ -103,6 +103,16 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
||||
*/
|
||||
private TermuxAppSharedProperties mProperties;
|
||||
|
||||
/**
|
||||
* The root view of the {@link TermuxActivity}.
|
||||
*/
|
||||
TermuxActivityRootView mTermuxActivityRootView;
|
||||
|
||||
/**
|
||||
* The space at the bottom of {@link @mTermuxActivityRootView} of the {@link TermuxActivity}.
|
||||
*/
|
||||
View mTermuxActivityBottomSpaceView;
|
||||
|
||||
/**
|
||||
* The terminal extra keys view.
|
||||
*/
|
||||
@@ -129,6 +139,11 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
||||
*/
|
||||
private boolean mIsVisible;
|
||||
|
||||
/**
|
||||
* If onResume() was called after onCreate().
|
||||
*/
|
||||
private boolean isOnResumeAfterOnCreate = false;
|
||||
|
||||
/**
|
||||
* The {@link TermuxActivity} is in an invalid state and must not be run.
|
||||
*/
|
||||
@@ -160,6 +175,7 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
|
||||
Logger.logDebug(LOG_TAG, "onCreate");
|
||||
isOnResumeAfterOnCreate = true;
|
||||
|
||||
// Check if a crash happened on last run of the app and show a
|
||||
// notification with the crash details if it did
|
||||
@@ -183,6 +199,10 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
||||
return;
|
||||
}
|
||||
|
||||
mTermuxActivityRootView = findViewById(R.id.activity_termux_root_view);
|
||||
mTermuxActivityRootView.setActivity(this);
|
||||
mTermuxActivityBottomSpaceView = findViewById(R.id.activity_termux_bottom_space_view);
|
||||
|
||||
View content = findViewById(android.R.id.content);
|
||||
content.setOnApplyWindowInsetsListener((v, insets) -> {
|
||||
mNavBarHeight = insets.getSystemWindowInsetBottom();
|
||||
@@ -235,6 +255,8 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
||||
if (mTermuxTerminalViewClient != null)
|
||||
mTermuxTerminalViewClient.onStart();
|
||||
|
||||
addTermuxActivityRootViewGlobalLayoutListener();
|
||||
|
||||
registerTermuxActivityBroadcastReceiver();
|
||||
}
|
||||
|
||||
@@ -251,6 +273,8 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
||||
|
||||
if (mTermuxTerminalViewClient != null)
|
||||
mTermuxTerminalViewClient.onResume();
|
||||
|
||||
isOnResumeAfterOnCreate = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -269,6 +293,8 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
||||
if (mTermuxTerminalViewClient != null)
|
||||
mTermuxTerminalViewClient.onStop();
|
||||
|
||||
removeTermuxActivityRootViewGlobalLayoutListener();
|
||||
|
||||
unregisterTermuxActivityBroadcastReceiever();
|
||||
getDrawer().closeDrawers();
|
||||
}
|
||||
@@ -382,6 +408,17 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
||||
|
||||
|
||||
|
||||
public void addTermuxActivityRootViewGlobalLayoutListener() {
|
||||
getTermuxActivityRootView().getViewTreeObserver().addOnGlobalLayoutListener(getTermuxActivityRootView());
|
||||
}
|
||||
|
||||
public void removeTermuxActivityRootViewGlobalLayoutListener() {
|
||||
if (getTermuxActivityRootView() != null)
|
||||
getTermuxActivityRootView().getViewTreeObserver().removeOnGlobalLayoutListener(getTermuxActivityRootView());
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void setTermuxTerminalViewAndClients() {
|
||||
// Set termux terminal view and session clients
|
||||
mTermuxTerminalSessionClient = new TermuxTerminalSessionClient(this);
|
||||
@@ -430,8 +467,8 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
||||
if (terminalToolbarViewPager == null) return;
|
||||
ViewGroup.LayoutParams layoutParams = terminalToolbarViewPager.getLayoutParams();
|
||||
layoutParams.height = (int) Math.round(mTerminalToolbarDefaultHeight *
|
||||
(mProperties.getExtraKeysInfo() == null ? 0 : mProperties.getExtraKeysInfo().getMatrix().length) *
|
||||
mProperties.getTerminalToolbarHeightScaleFactor());
|
||||
(mProperties.getExtraKeysInfo() == null ? 0 : mProperties.getExtraKeysInfo().getMatrix().length) *
|
||||
mProperties.getTerminalToolbarHeightScaleFactor());
|
||||
terminalToolbarViewPager.setLayoutParams(layoutParams);
|
||||
}
|
||||
|
||||
@@ -671,6 +708,14 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
||||
return mNavBarHeight;
|
||||
}
|
||||
|
||||
public TermuxActivityRootView getTermuxActivityRootView() {
|
||||
return mTermuxActivityRootView;
|
||||
}
|
||||
|
||||
public View getTermuxActivityBottomSpaceView() {
|
||||
return mTermuxActivityBottomSpaceView;
|
||||
}
|
||||
|
||||
public ExtraKeysView getExtraKeysView() {
|
||||
return mExtraKeysView;
|
||||
}
|
||||
@@ -691,6 +736,10 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
||||
return mIsVisible;
|
||||
}
|
||||
|
||||
public boolean isOnResumeAfterOnCreate() {
|
||||
return isOnResumeAfterOnCreate;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public TermuxService getTermuxService() {
|
||||
@@ -797,6 +846,9 @@ public final class TermuxActivity extends Activity implements ServiceConnection
|
||||
if (mTermuxTerminalViewClient != null)
|
||||
mTermuxTerminalViewClient.onReload();
|
||||
|
||||
if (mTermuxService != null)
|
||||
mTermuxService.setTerminalTranscriptRows();
|
||||
|
||||
// To change the activity and drawer theme, activity needs to be recreated.
|
||||
// But this will destroy the activity, and will call the onCreate() again.
|
||||
// We need to investigate if enabling this is wise, since all stored variables and
|
||||
|
||||
@@ -173,6 +173,7 @@ final class TermuxInstaller {
|
||||
activity.finish();
|
||||
}).setPositiveButton(R.string.bootstrap_error_try_again, (dialog, which) -> {
|
||||
dialog.dismiss();
|
||||
FileUtils.deleteFile(activity, "prefix directory", PREFIX_FILE_PATH, true);
|
||||
TermuxInstaller.setupBootstrapIfNeeded(activity, whenDone);
|
||||
}).show();
|
||||
} catch (WindowManager.BadTokenException e1) {
|
||||
|
||||
@@ -20,6 +20,7 @@ import android.provider.Settings;
|
||||
import android.widget.ArrayAdapter;
|
||||
|
||||
import com.termux.R;
|
||||
import com.termux.app.settings.properties.TermuxAppSharedProperties;
|
||||
import com.termux.app.terminal.TermuxTerminalSessionClient;
|
||||
import com.termux.app.utils.PluginUtils;
|
||||
import com.termux.shared.termux.TermuxConstants;
|
||||
@@ -106,6 +107,8 @@ public final class TermuxService extends Service implements TermuxTask.TermuxTas
|
||||
/** If the user has executed the {@link TERMUX_SERVICE#ACTION_STOP_SERVICE} intent. */
|
||||
boolean mWantsToStop = false;
|
||||
|
||||
public Integer mTerminalTranscriptRows;
|
||||
|
||||
private static final String LOG_TAG = "TermuxService";
|
||||
|
||||
@Override
|
||||
@@ -503,6 +506,7 @@ public final class TermuxService extends Service implements TermuxTask.TermuxTas
|
||||
// If the execution command was started for a plugin, only then will the stdout be set
|
||||
// Otherwise if command was manually started by the user like by adding a new terminal session,
|
||||
// then no need to set stdout
|
||||
executionCommand.terminalTranscriptRows = getTerminalTranscriptRows();
|
||||
TermuxSession newTermuxSession = TermuxSession.execute(this, executionCommand, getTermuxTerminalSessionClient(), this, sessionName, executionCommand.isPluginExecutionCommand);
|
||||
if (newTermuxSession == null) {
|
||||
Logger.logError(LOG_TAG, "Failed to execute new TermuxSession command for:\n" + executionCommand.getCommandIdAndLabelLogString());
|
||||
@@ -560,6 +564,19 @@ public final class TermuxService extends Service implements TermuxTask.TermuxTas
|
||||
updateNotification();
|
||||
}
|
||||
|
||||
/** Get the terminal transcript rows to be used for new {@link TermuxSession}. */
|
||||
public Integer getTerminalTranscriptRows() {
|
||||
if (mTerminalTranscriptRows == null)
|
||||
setTerminalTranscriptRows();
|
||||
return mTerminalTranscriptRows;
|
||||
}
|
||||
|
||||
public void setTerminalTranscriptRows() {
|
||||
// TermuxService only uses this termux property currently, so no need to load them all into
|
||||
// an internal values map like TermuxActivity does
|
||||
mTerminalTranscriptRows = TermuxAppSharedProperties.getTerminalTranscriptRows(this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@ import android.webkit.WebViewClient;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.RelativeLayout;
|
||||
|
||||
import com.termux.shared.termux.TermuxConstants;
|
||||
|
||||
/** Basic embedded browser for viewing help pages. */
|
||||
public final class HelpActivity extends Activity {
|
||||
|
||||
@@ -39,7 +41,7 @@ public final class HelpActivity extends Activity {
|
||||
mWebView.setWebViewClient(new WebViewClient() {
|
||||
@Override
|
||||
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
||||
if (url.startsWith("https://wiki.termux.com")) {
|
||||
if (url.equals(TermuxConstants.TERMUX_WIKI_URL) || url.startsWith(TermuxConstants.TERMUX_WIKI_URL + "/")) {
|
||||
// Inline help.
|
||||
setContentView(progressLayout);
|
||||
return false;
|
||||
@@ -60,7 +62,7 @@ public final class HelpActivity extends Activity {
|
||||
setContentView(mWebView);
|
||||
}
|
||||
});
|
||||
mWebView.loadUrl("https://wiki.termux.com/wiki/Main_Page");
|
||||
mWebView.loadUrl(TermuxConstants.TERMUX_WIKI_URL);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -5,7 +5,6 @@ import android.content.Context;
|
||||
import com.termux.app.terminal.io.KeyboardShortcut;
|
||||
import com.termux.app.terminal.io.extrakeys.ExtraKeysInfo;
|
||||
import com.termux.shared.logger.Logger;
|
||||
import com.termux.shared.settings.properties.SharedPropertiesParser;
|
||||
import com.termux.shared.settings.properties.TermuxPropertyConstants;
|
||||
import com.termux.shared.settings.properties.TermuxSharedProperties;
|
||||
|
||||
@@ -17,7 +16,7 @@ import java.util.Map;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
public class TermuxAppSharedProperties extends TermuxSharedProperties implements SharedPropertiesParser {
|
||||
public class TermuxAppSharedProperties extends TermuxSharedProperties {
|
||||
|
||||
private ExtraKeysInfo mExtraKeysInfo;
|
||||
private List<KeyboardShortcut> mSessionShortcuts = new ArrayList<>();
|
||||
@@ -96,4 +95,13 @@ public class TermuxAppSharedProperties extends TermuxSharedProperties implements
|
||||
return mExtraKeysInfo;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Load the {@link TermuxPropertyConstants#KEY_TERMINAL_TRANSCRIPT_ROWS} value from termux properties file on disk.
|
||||
*/
|
||||
public static int getTerminalTranscriptRows(Context context) {
|
||||
return (int) TermuxSharedProperties.getInternalPropertyValue(context, TermuxPropertyConstants.KEY_TERMINAL_TRANSCRIPT_ROWS);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
package com.termux.app.terminal;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Rect;
|
||||
import android.inputmethodservice.InputMethodService;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.termux.app.TermuxActivity;
|
||||
import com.termux.shared.logger.Logger;
|
||||
import com.termux.shared.view.ViewUtils;
|
||||
|
||||
|
||||
/**
|
||||
* The {@link TermuxActivity} relies on {@link android.view.WindowManager.LayoutParams#SOFT_INPUT_ADJUST_RESIZE)}
|
||||
* set by {@link TermuxTerminalViewClient#setSoftKeyboardState(boolean, boolean)} to automatically
|
||||
* resize the view and push the terminal up when soft keyboard is opened. However, this does not
|
||||
* always work properly. When `enforce-char-based-input=true` is set in `termux.properties`
|
||||
* and {@link com.termux.view.TerminalView#onCreateInputConnection(EditorInfo)} sets the inputType
|
||||
* to `InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS`
|
||||
* instead of the default `InputType.TYPE_NULL` for termux, some keyboards may still show suggestions.
|
||||
* Gboard does too, but only when text is copied and clipboard suggestions **and** number keys row
|
||||
* toggles are enabled in its settings. When number keys row toggle is not enabled, Gboard will still
|
||||
* show the row but will switch it with suggestions if needed. If its enabled, then number keys row
|
||||
* is always shown and suggestions are shown in an additional row on top of it. This additional row is likely
|
||||
* part of the candidates view returned by the keyboard app in {@link InputMethodService#onCreateCandidatesView()}.
|
||||
*
|
||||
* With the above configuration, the additional clipboard suggestions row partially covers the
|
||||
* extra keys/terminal. Reopening the keyboard/activity does not fix the issue. This is either a bug
|
||||
* in the Android OS where it does not consider the candidate's view height in its calculation to push
|
||||
* up the view or because Gboard does not include the candidate's view height in the height reported
|
||||
* to android that should be used, hence causing an overlap.
|
||||
*
|
||||
* Gboard logs the following entry to `logcat` when its opened with or without the suggestions bar showing:
|
||||
* I/KeyboardViewUtil: KeyboardViewUtil.calculateMaxKeyboardBodyHeight():62 leave 500 height for app when screen height:2392, header height:176 and isFullscreenMode:false, so the max keyboard body height is:1716
|
||||
* where `keyboard_height = screen_height - height_for_app - header_height` (62 is a hardcoded value in Gboard source code and may be a version number)
|
||||
* So this may in fact be due to Gboard but https://stackoverflow.com/questions/57567272 suggests
|
||||
* otherwise. Another similar report https://stackoverflow.com/questions/66761661.
|
||||
* Also check https://github.com/termux/termux-app/issues/1539.
|
||||
*
|
||||
* This overlap may happen even without `enforce-char-based-input=true` for keyboards with extended layouts
|
||||
* like number row, etc.
|
||||
*
|
||||
* To fix these issues, `activity_termux.xml` has the constant 1sp transparent
|
||||
* `activity_termux_bottom_space_view` View at the bottom. This will appear as a line matching the
|
||||
* activity theme. When {@link TermuxActivity} {@link ViewTreeObserver.OnGlobalLayoutListener} is
|
||||
* called when any of the sub view layouts change, like keyboard opening/closing keyboard,
|
||||
* extra keys/input view switched, etc, we check if the bottom space view is visible or not.
|
||||
* If its not, then we add a margin to the bottom of the root view, so that the keyboard does not
|
||||
* overlap the extra keys/terminal, since the margin will push up the view. By default the margin
|
||||
* added is equal to the height of the hidden part of extra keys/terminal. For Gboard's case, the
|
||||
* hidden part equals the `header_height`. The updates to margins may cause a jitter in some cases
|
||||
* when the view is redrawn if the margin is incorrect, but logic has been implemented to avoid that.
|
||||
*/
|
||||
public class TermuxActivityRootView extends LinearLayout implements ViewTreeObserver.OnGlobalLayoutListener {
|
||||
|
||||
public TermuxActivity mActivity;
|
||||
public Integer marginBottom;
|
||||
public Integer lastMarginBottom;
|
||||
|
||||
/** Log root view events. */
|
||||
private boolean ROOT_VIEW_LOGGING_ENABLED = false;
|
||||
|
||||
private static final String LOG_TAG = "TermuxActivityRootView";
|
||||
|
||||
public TermuxActivityRootView(Context context) {
|
||||
super(context);
|
||||
|
||||
}
|
||||
|
||||
public TermuxActivityRootView(Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public TermuxActivityRootView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
public void setActivity(TermuxActivity activity) {
|
||||
mActivity = activity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether root view logging is enabled or not.
|
||||
*
|
||||
* @param value The boolean value that defines the state.
|
||||
*/
|
||||
public void setIsRootViewLoggingEnabled(boolean value) {
|
||||
ROOT_VIEW_LOGGING_ENABLED = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
|
||||
if (marginBottom != null) {
|
||||
if (ROOT_VIEW_LOGGING_ENABLED)
|
||||
Logger.logVerbose(LOG_TAG, "onMeasure: Setting bottom margin to " + marginBottom);
|
||||
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) getLayoutParams();
|
||||
params.setMargins(0, 0, 0, marginBottom);
|
||||
setLayoutParams(params);
|
||||
marginBottom = null;
|
||||
requestLayout();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGlobalLayout() {
|
||||
if (mActivity == null || !mActivity.isVisible()) return;
|
||||
|
||||
View bottomSpaceView = mActivity.getTermuxActivityBottomSpaceView();
|
||||
if (bottomSpaceView == null) return;
|
||||
|
||||
FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) getLayoutParams();
|
||||
|
||||
// Get the position Rects of the bottom space view and the main window holding it
|
||||
Rect[] windowAndViewRects = ViewUtils.getWindowAndViewRects(bottomSpaceView);
|
||||
if (windowAndViewRects == null)
|
||||
return;
|
||||
|
||||
Rect windowAvailableRect = windowAndViewRects[0];
|
||||
Rect bottomSpaceViewRect = windowAndViewRects[1];
|
||||
|
||||
// If the bottomSpaceViewRect is inside the windowAvailableRect, then it must be completely visible
|
||||
boolean isVisible = windowAvailableRect.contains(bottomSpaceViewRect);
|
||||
boolean isVisibleBecauseMargin = (windowAvailableRect.bottom == bottomSpaceViewRect.bottom) && params.bottomMargin > 0;
|
||||
boolean isVisibleBecauseExtraMargin = (bottomSpaceViewRect.bottom - windowAvailableRect.bottom) < 0;
|
||||
|
||||
if (ROOT_VIEW_LOGGING_ENABLED)
|
||||
Logger.logVerbose(LOG_TAG, "onGlobalLayout: windowAvailableRect " + windowAvailableRect.bottom + ", bottomSpaceViewRect " + bottomSpaceViewRect.bottom + ", diff " + (bottomSpaceViewRect.bottom - windowAvailableRect.bottom) + ", bottom " + params.bottomMargin + ", isVisible " + isVisible + ", isVisibleBecauseMargin " + isVisibleBecauseMargin + ", isVisibleBecauseExtraMargin " + isVisibleBecauseExtraMargin);
|
||||
|
||||
// If the bottomSpaceViewRect is visible, then remove the margin if needed
|
||||
if (isVisible) {
|
||||
// If visible because of margin, i.e the bottom of bottomSpaceViewRect equals that of windowAvailableRect
|
||||
// and a margin has been added
|
||||
// Necessary so that we don't get stuck in an infinite loop since setting margin
|
||||
// will call OnGlobalLayoutListener again and next time bottom space view
|
||||
// will be visible and margin will be set to 0, which again will call
|
||||
// OnGlobalLayoutListener...
|
||||
// Calling addTermuxActivityRootViewGlobalLayoutListener with a delay fails to
|
||||
// set appropriate margins when views are changed quickly since some changes
|
||||
// may be missed.
|
||||
if (isVisibleBecauseMargin) {
|
||||
if (ROOT_VIEW_LOGGING_ENABLED)
|
||||
Logger.logVerbose(LOG_TAG, "onGlobalLayout: Visible due to margin");
|
||||
// Once the view has been redrawn with new margin, we set margin back to 0 so that
|
||||
// when next time onMeasure() is called, margin 0 is used. This is necessary for
|
||||
// cases when view has been redrawn with new margin because bottom space view was
|
||||
// hidden by keyboard and then view was redrawn again due to layout change (like
|
||||
// keyboard symbol view is switched to), android will add margin below its new position
|
||||
// if its greater than 0, which was already above the keyboard creating x2x margin.
|
||||
marginBottom = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
boolean setMargin = params.bottomMargin != 0;
|
||||
|
||||
// If visible because of extra margin, i.e the bottom of bottomSpaceViewRect is above that of windowAvailableRect
|
||||
// onGlobalLayout: windowAvailableRect 1408, bottomSpaceViewRect 1232, diff -176, bottom 0, isVisible true, isVisibleBecauseMargin false, isVisibleBecauseExtraMargin false
|
||||
// onGlobalLayout: Bottom margin already equals 0
|
||||
if (isVisibleBecauseExtraMargin) {
|
||||
if (ROOT_VIEW_LOGGING_ENABLED)
|
||||
Logger.logVerbose(LOG_TAG, "onGlobalLayout: Resetting margin since visible due to extra margin");
|
||||
setMargin = true;
|
||||
// lastMarginBottom must be invalid. May also happen when keyboards are changed.
|
||||
lastMarginBottom = null;
|
||||
}
|
||||
|
||||
if (setMargin) {
|
||||
if (ROOT_VIEW_LOGGING_ENABLED)
|
||||
Logger.logVerbose(LOG_TAG, "onGlobalLayout: Setting bottom margin to 0");
|
||||
params.setMargins(0, 0, 0, 0);
|
||||
setLayoutParams(params);
|
||||
} else {
|
||||
if (ROOT_VIEW_LOGGING_ENABLED)
|
||||
Logger.logVerbose(LOG_TAG, "onGlobalLayout: Bottom margin already equals 0");
|
||||
// This is done so that when next time onMeasure() is called, lastMarginBottom is used.
|
||||
// This is done since we **expect** the keyboard to have same dimensions next time layout
|
||||
// changes, so best set margin while view is drawn the first time, otherwise it will
|
||||
// cause a jitter when OnGlobalLayoutListener is called with margin 0 and it sets the
|
||||
// likely same lastMarginBottom again and requesting a redraw. Hopefully, this logic
|
||||
// works fine for all cases.
|
||||
marginBottom = lastMarginBottom;
|
||||
}
|
||||
}
|
||||
// ELse find the part of the extra keys/terminal that is hidden and add a margin accordingly
|
||||
else {
|
||||
int pxHidden = bottomSpaceViewRect.bottom - windowAvailableRect.bottom;
|
||||
if (ROOT_VIEW_LOGGING_ENABLED)
|
||||
Logger.logVerbose(LOG_TAG, "onGlobalLayout: pxHidden " + pxHidden + ", bottom " + params.bottomMargin);
|
||||
|
||||
boolean setMargin = params.bottomMargin != pxHidden;
|
||||
|
||||
// If invisible despite margin, i.e a margin was added, but the bottom of bottomSpaceViewRect
|
||||
// is still below that of windowAvailableRect, this will trigger OnGlobalLayoutListener
|
||||
// again, so that margins are set properly. May happen when toolbar/extra keys is disabled
|
||||
// and enabled from left drawer, just like case for isVisibleBecauseExtraMargin.
|
||||
// onMeasure: Setting bottom margin to 176
|
||||
// onGlobalLayout: windowAvailableRect 1232, bottomSpaceViewRect 1408, diff 176, bottom 176, isVisible false, isVisibleBecauseMargin false, isVisibleBecauseExtraMargin false
|
||||
// onGlobalLayout: Bottom margin already equals 176
|
||||
if ((bottomSpaceViewRect.bottom - windowAvailableRect.bottom) > 0) {
|
||||
if (ROOT_VIEW_LOGGING_ENABLED)
|
||||
Logger.logVerbose(LOG_TAG, "onGlobalLayout: Force setting margin since not visible despite margin");
|
||||
setMargin = true;
|
||||
}
|
||||
|
||||
if (setMargin) {
|
||||
if (ROOT_VIEW_LOGGING_ENABLED)
|
||||
Logger.logVerbose(LOG_TAG, "onGlobalLayout: Setting bottom margin to " + pxHidden);
|
||||
params.setMargins(0, 0, 0, pxHidden);
|
||||
setLayoutParams(params);
|
||||
lastMarginBottom = pxHidden;
|
||||
} else {
|
||||
if (ROOT_VIEW_LOGGING_ENABLED)
|
||||
Logger.logVerbose(LOG_TAG, "onGlobalLayout: Bottom margin already equals " + pxHidden);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -207,6 +207,13 @@ public class TermuxTerminalSessionClient extends TermuxTerminalSessionClientBase
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public Integer getTerminalCursorStyle() {
|
||||
return mActivity.getProperties().getTerminalCursorStyle();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** Initialize and get mBellSoundPool */
|
||||
private synchronized SoundPool getBellSoundPool() {
|
||||
if (mBellSoundPool == null) {
|
||||
@@ -248,8 +255,10 @@ public class TermuxTerminalSessionClient extends TermuxTerminalSessionClientBase
|
||||
void notifyOfSessionChange() {
|
||||
if (!mActivity.isVisible()) return;
|
||||
|
||||
TerminalSession session = mActivity.getCurrentSession();
|
||||
mActivity.showToast(toToastTitle(session), false);
|
||||
if (!mActivity.getProperties().areTerminalSessionChangeToastsDisabled()) {
|
||||
TerminalSession session = mActivity.getCurrentSession();
|
||||
mActivity.showToast(toToastTitle(session), false);
|
||||
}
|
||||
}
|
||||
|
||||
public void switchToSession(boolean forward) {
|
||||
|
||||
@@ -15,6 +15,7 @@ import android.view.InputDevice;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ListView;
|
||||
import android.widget.Toast;
|
||||
|
||||
@@ -56,6 +57,11 @@ public class TermuxTerminalViewClient extends TermuxTerminalViewClientBase {
|
||||
|
||||
private Runnable mShowSoftKeyboardRunnable;
|
||||
|
||||
private boolean mShowSoftKeyboardIgnoreOnce;
|
||||
private boolean mShowSoftKeyboardWithDelayOnce;
|
||||
|
||||
private boolean mTerminalCursorBlinkerStateAlreadySet;
|
||||
|
||||
private static final String LOG_TAG = "TermuxTerminalViewClient";
|
||||
|
||||
public TermuxTerminalViewClient(TermuxActivity activity, TermuxTerminalSessionClient termuxTerminalSessionClient) {
|
||||
@@ -75,10 +81,13 @@ public class TermuxTerminalViewClient extends TermuxTerminalViewClientBase {
|
||||
* Should be called when mActivity.onStart() is called
|
||||
*/
|
||||
public void onStart() {
|
||||
|
||||
// Set {@link TerminalView#TERMINAL_VIEW_KEY_LOGGING_ENABLED} value
|
||||
// Also required if user changed the preference from {@link TermuxSettings} activity and returns
|
||||
mActivity.getTerminalView().setIsTerminalViewKeyLoggingEnabled(mActivity.getPreferences().isTerminalViewKeyLoggingEnabled());
|
||||
boolean isTerminalViewKeyLoggingEnabled = mActivity.getPreferences().isTerminalViewKeyLoggingEnabled();
|
||||
mActivity.getTerminalView().setIsTerminalViewKeyLoggingEnabled(isTerminalViewKeyLoggingEnabled);
|
||||
|
||||
// Piggyback on the terminal view key logging toggle for now, should add a separate toggle in future
|
||||
mActivity.getTermuxActivityRootView().setIsRootViewLoggingEnabled(isTerminalViewKeyLoggingEnabled);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -88,8 +97,7 @@ public class TermuxTerminalViewClient extends TermuxTerminalViewClientBase {
|
||||
// Show the soft keyboard if required
|
||||
setSoftKeyboardState(true, false);
|
||||
|
||||
// Start terminal cursor blinking if enabled
|
||||
setTerminalCursorBlinkerState(true);
|
||||
mTerminalCursorBlinkerStateAlreadySet = false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -111,6 +119,23 @@ public class TermuxTerminalViewClient extends TermuxTerminalViewClientBase {
|
||||
setTerminalCursorBlinkerState(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be called when {@link com.termux.view.TerminalView#mEmulator}
|
||||
*/
|
||||
@Override
|
||||
public void onEmulatorSet() {
|
||||
if (!mTerminalCursorBlinkerStateAlreadySet) {
|
||||
// Start terminal cursor blinking if enabled
|
||||
// We need to wait for the first session to be attached that's set in
|
||||
// TermuxActivity.onServiceConnected() and then the multiple calls to TerminalView.updateSize()
|
||||
// where the final one eventually sets the mEmulator when width/height is not 0. Otherwise
|
||||
// blinker will not start again if TermuxActivity is started again after exiting it with
|
||||
// double back press. Check TerminalView.setTerminalCursorBlinkerState().
|
||||
setTerminalCursorBlinkerState(true);
|
||||
mTerminalCursorBlinkerStateAlreadySet = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
@@ -409,10 +434,19 @@ public class TermuxTerminalViewClient extends TermuxTerminalViewClientBase {
|
||||
mActivity.getPreferences().setSoftKeyboardEnabled(false);
|
||||
KeyboardUtils.disableSoftKeyboard(mActivity, mActivity.getTerminalView());
|
||||
} else {
|
||||
// Show with a delay, otherwise pressing keyboard toggle won't show the keyboard after
|
||||
// switching back from another app if keyboard was previously disabled by user.
|
||||
// Also request focus, since it wouldn't have been requested at startup by
|
||||
// setSoftKeyboardState if keyboard was disabled. #2112
|
||||
Logger.logVerbose(LOG_TAG, "Enabling soft keyboard on toggle");
|
||||
mActivity.getPreferences().setSoftKeyboardEnabled(true);
|
||||
KeyboardUtils.clearDisableSoftKeyboardFlags(mActivity);
|
||||
KeyboardUtils.showSoftKeyboard(mActivity, mActivity.getTerminalView());
|
||||
if(mShowSoftKeyboardWithDelayOnce) {
|
||||
mShowSoftKeyboardWithDelayOnce = false;
|
||||
mActivity.getTerminalView().postDelayed(getShowSoftKeyboardRunnable(), 500);
|
||||
mActivity.getTerminalView().requestFocus();
|
||||
} else
|
||||
KeyboardUtils.showSoftKeyboard(mActivity, mActivity.getTerminalView());
|
||||
}
|
||||
}
|
||||
// If soft keyboard toggle behaviour is show/hide
|
||||
@@ -430,15 +464,23 @@ public class TermuxTerminalViewClient extends TermuxTerminalViewClientBase {
|
||||
}
|
||||
|
||||
public void setSoftKeyboardState(boolean isStartup, boolean isReloadTermuxProperties) {
|
||||
boolean noRequestFocus = false;
|
||||
|
||||
// If soft keyboard is disabled by user for Termux (check function docs for Termux behaviour info)
|
||||
if (KeyboardUtils.shouldSoftKeyboardBeDisabled(mActivity,
|
||||
mActivity.getPreferences().isSoftKeyboardEnabled(),
|
||||
mActivity.getPreferences().isSoftKeyboardEnabledOnlyIfNoHardware())) {
|
||||
mActivity.getPreferences().isSoftKeyboardEnabled(),
|
||||
mActivity.getPreferences().isSoftKeyboardEnabledOnlyIfNoHardware())) {
|
||||
Logger.logVerbose(LOG_TAG, "Maintaining disabled soft keyboard");
|
||||
KeyboardUtils.disableSoftKeyboard(mActivity, mActivity.getTerminalView());
|
||||
noRequestFocus = true;
|
||||
// Delay is only required if onCreate() is called like when Termux app is exited with
|
||||
// double back press, not when Termux app is switched back from another app and keyboard
|
||||
// toggle is pressed to enable keyboard
|
||||
if (isStartup && mActivity.isOnResumeAfterOnCreate())
|
||||
mShowSoftKeyboardWithDelayOnce = true;
|
||||
} else {
|
||||
// Set flag to automatically push up TerminalView when keyboard is opened instead of showing over it
|
||||
KeyboardUtils.setResizeTerminalViewForSoftKeyboardFlags(mActivity);
|
||||
KeyboardUtils.setSoftInputModeAdjustResize(mActivity);
|
||||
|
||||
// Clear any previous flags to disable soft keyboard in case setting updated
|
||||
KeyboardUtils.clearDisableSoftKeyboardFlags(mActivity);
|
||||
@@ -449,30 +491,55 @@ public class TermuxTerminalViewClient extends TermuxTerminalViewClientBase {
|
||||
KeyboardUtils.hideSoftKeyboard(mActivity, mActivity.getTerminalView());
|
||||
// Required to keep keyboard hidden when Termux app is switched back from another app
|
||||
KeyboardUtils.setSoftKeyboardAlwaysHiddenFlags(mActivity);
|
||||
} else {
|
||||
// Do not force show soft keyboard if termux-reload-settings command was run with hardware keyboard
|
||||
if (isReloadTermuxProperties)
|
||||
return;
|
||||
|
||||
if (mShowSoftKeyboardRunnable == null) {
|
||||
mShowSoftKeyboardRunnable = () -> {
|
||||
Logger.logVerbose(LOG_TAG, "Showing soft keyboard on focus change");
|
||||
KeyboardUtils.showSoftKeyboard(mActivity, mActivity.getTerminalView());
|
||||
};
|
||||
}
|
||||
|
||||
mActivity.getTerminalView().setOnFocusChangeListener(new View.OnFocusChangeListener() {
|
||||
@Override
|
||||
public void onFocusChange(View view, boolean hasFocus) {
|
||||
// Force show soft keyboard if TerminalView has focus and close it if it doesn't
|
||||
KeyboardUtils.setSoftKeyboardVisibility(mShowSoftKeyboardRunnable, mActivity, mActivity.getTerminalView(), hasFocus);
|
||||
}
|
||||
});
|
||||
|
||||
// Request focus for TerminalView
|
||||
mActivity.getTerminalView().requestFocus();
|
||||
noRequestFocus = true;
|
||||
// Required to keep keyboard hidden on app startup
|
||||
mShowSoftKeyboardIgnoreOnce = true;
|
||||
}
|
||||
}
|
||||
|
||||
mActivity.getTerminalView().setOnFocusChangeListener(new View.OnFocusChangeListener() {
|
||||
@Override
|
||||
public void onFocusChange(View view, boolean hasFocus) {
|
||||
// Force show soft keyboard if TerminalView or toolbar text input view has
|
||||
// focus and close it if they don't
|
||||
boolean textInputViewHasFocus = false;
|
||||
final EditText textInputView = mActivity.findViewById(R.id.terminal_toolbar_text_input);
|
||||
if (textInputView != null) textInputViewHasFocus = textInputView.hasFocus();
|
||||
|
||||
if (hasFocus || textInputViewHasFocus) {
|
||||
if (mShowSoftKeyboardIgnoreOnce) {
|
||||
mShowSoftKeyboardIgnoreOnce = false; return;
|
||||
}
|
||||
Logger.logVerbose(LOG_TAG, "Showing soft keyboard on focus change");
|
||||
} else {
|
||||
Logger.logVerbose(LOG_TAG, "Hiding soft keyboard on focus change");
|
||||
}
|
||||
|
||||
KeyboardUtils.setSoftKeyboardVisibility(getShowSoftKeyboardRunnable(), mActivity, mActivity.getTerminalView(), hasFocus || textInputViewHasFocus);
|
||||
}
|
||||
});
|
||||
|
||||
// Do not force show soft keyboard if termux-reload-settings command was run with hardware keyboard
|
||||
// or soft keyboard is to be hidden or is disabled
|
||||
if (!isReloadTermuxProperties && !noRequestFocus) {
|
||||
// Request focus for TerminalView
|
||||
// Also show the keyboard, since onFocusChange will not be called if TerminalView already
|
||||
// had focus on startup to show the keyboard, like when opening url with context menu
|
||||
// "Select URL" long press and returning to Termux app with back button. This
|
||||
// will also show keyboard even if it was closed before opening url. #2111
|
||||
Logger.logVerbose(LOG_TAG, "Requesting TerminalView focus and showing soft keyboard");
|
||||
mActivity.getTerminalView().requestFocus();
|
||||
mActivity.getTerminalView().postDelayed(getShowSoftKeyboardRunnable(), 300);
|
||||
}
|
||||
}
|
||||
|
||||
private Runnable getShowSoftKeyboardRunnable() {
|
||||
if (mShowSoftKeyboardRunnable == null) {
|
||||
mShowSoftKeyboardRunnable = () -> {
|
||||
KeyboardUtils.showSoftKeyboard(mActivity, mActivity.getTerminalView());
|
||||
};
|
||||
}
|
||||
return mShowSoftKeyboardRunnable;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,80 +1,96 @@
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<com.termux.app.terminal.TermuxActivityRootView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/activity_termux_root_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:fitsSystemWindows="true">
|
||||
|
||||
<androidx.drawerlayout.widget.DrawerLayout
|
||||
android:id="@+id/drawer_layout"
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_above="@+id/terminal_toolbar_view_pager"
|
||||
android:layout_height="match_parent">
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.termux.view.TerminalView
|
||||
android:id="@+id/terminal_view"
|
||||
<androidx.drawerlayout.widget.DrawerLayout
|
||||
android:id="@+id/drawer_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginRight="3dp"
|
||||
android:layout_marginLeft="3dp"
|
||||
android:focusableInTouchMode="true"
|
||||
android:scrollbarThumbVertical="@drawable/terminal_scroll_shape"
|
||||
android:scrollbars="vertical"
|
||||
android:importantForAutofill="no"
|
||||
android:autofillHints="password" />
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_above="@+id/terminal_toolbar_view_pager"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/left_drawer"
|
||||
android:layout_width="240dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="start"
|
||||
android:background="@android:color/white"
|
||||
android:choiceMode="singleChoice"
|
||||
android:divider="@android:color/transparent"
|
||||
android:dividerHeight="0dp"
|
||||
android:descendantFocusability="blocksDescendants"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ListView
|
||||
android:id="@+id/terminal_sessions_list"
|
||||
<com.termux.view.TerminalView
|
||||
android:id="@+id/terminal_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_gravity="top"
|
||||
android:layout_weight="1"
|
||||
android:choiceMode="singleChoice"
|
||||
android:longClickable="true" />
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginRight="3dp"
|
||||
android:layout_marginLeft="3dp"
|
||||
android:focusableInTouchMode="true"
|
||||
android:scrollbarThumbVertical="@drawable/terminal_scroll_shape"
|
||||
android:scrollbars="vertical"
|
||||
android:importantForAutofill="no"
|
||||
android:autofillHints="password" />
|
||||
|
||||
<LinearLayout
|
||||
style="?android:attr/buttonBarStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
android:id="@+id/left_drawer"
|
||||
android:layout_width="240dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="start"
|
||||
android:background="@android:color/white"
|
||||
android:choiceMode="singleChoice"
|
||||
android:divider="@android:color/transparent"
|
||||
android:dividerHeight="0dp"
|
||||
android:descendantFocusability="blocksDescendants"
|
||||
android:orientation="vertical">
|
||||
|
||||
<Button
|
||||
android:id="@+id/toggle_keyboard_button"
|
||||
style="?android:attr/buttonBarButtonStyle"
|
||||
<ListView
|
||||
android:id="@+id/terminal_sessions_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_gravity="top"
|
||||
android:layout_weight="1"
|
||||
android:choiceMode="singleChoice"
|
||||
android:longClickable="true" />
|
||||
|
||||
<LinearLayout
|
||||
style="?android:attr/buttonBarStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/action_toggle_soft_keyboard" />
|
||||
android:orientation="horizontal">
|
||||
|
||||
<Button
|
||||
android:id="@+id/new_session_button"
|
||||
style="?android:attr/buttonBarButtonStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/action_new_session" />
|
||||
<Button
|
||||
android:id="@+id/toggle_keyboard_button"
|
||||
style="?android:attr/buttonBarButtonStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/action_toggle_soft_keyboard" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/new_session_button"
|
||||
style="?android:attr/buttonBarButtonStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/action_new_session" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.drawerlayout.widget.DrawerLayout>
|
||||
</androidx.drawerlayout.widget.DrawerLayout>
|
||||
|
||||
<androidx.viewpager.widget.ViewPager
|
||||
android:id="@+id/terminal_toolbar_view_pager"
|
||||
android:visibility="gone"
|
||||
<androidx.viewpager.widget.ViewPager
|
||||
android:id="@+id/terminal_toolbar_view_pager"
|
||||
android:visibility="gone"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="37.5dp"
|
||||
android:background="@android:drawable/screen_background_dark_transparent"
|
||||
android:layout_alignParentBottom="true" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<View
|
||||
android:id="@+id/activity_termux_bottom_space_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="37.5dp"
|
||||
android:background="@android:drawable/screen_background_dark_transparent"
|
||||
android:layout_alignParentBottom="true" />
|
||||
</RelativeLayout>
|
||||
android:layout_height="1dp"
|
||||
android:background="@android:color/transparent" />
|
||||
|
||||
</com.termux.app.terminal.TermuxActivityRootView>
|
||||
|
||||
@@ -63,7 +63,7 @@ publishing {
|
||||
bar(MavenPublication) {
|
||||
groupId 'com.termux'
|
||||
artifactId 'terminal-emulator'
|
||||
version "0.113"
|
||||
version "0.114"
|
||||
artifact(sourceJar)
|
||||
artifact("$buildDir/outputs/aar/terminal-emulator-release.aar")
|
||||
}
|
||||
|
||||
@@ -38,10 +38,6 @@ public final class TerminalEmulator {
|
||||
public static final int MOUSE_WHEELUP_BUTTON = 64;
|
||||
public static final int MOUSE_WHEELDOWN_BUTTON = 65;
|
||||
|
||||
public static final int CURSOR_STYLE_BLOCK = 0;
|
||||
public static final int CURSOR_STYLE_UNDERLINE = 1;
|
||||
public static final int CURSOR_STYLE_BAR = 2;
|
||||
|
||||
/** Used for invalid data - http://en.wikipedia.org/wiki/Replacement_character#Replacement_character */
|
||||
public static final int UNICODE_REPLACEMENT_CHAR = 0xFFFD;
|
||||
|
||||
@@ -126,17 +122,35 @@ public final class TerminalEmulator {
|
||||
/** Not really DECSET bit... - http://www.vt100.net/docs/vt510-rm/DECSACE */
|
||||
private static final int DECSET_BIT_RECTANGULAR_CHANGEATTRIBUTE = 1 << 12;
|
||||
|
||||
|
||||
private String mTitle;
|
||||
private final Stack<String> mTitleStack = new Stack<>();
|
||||
|
||||
|
||||
/** The cursor position. Between (0,0) and (mRows-1, mColumns-1). */
|
||||
private int mCursorRow, mCursorCol;
|
||||
|
||||
private int mCursorStyle = CURSOR_STYLE_BLOCK;
|
||||
|
||||
/** The number of character rows and columns in the terminal screen. */
|
||||
public int mRows, mColumns;
|
||||
|
||||
/** The number of terminal transcript rows that can be scrolled back to. */
|
||||
public static final int TERMINAL_TRANSCRIPT_ROWS_MIN = 100;
|
||||
public static final int TERMINAL_TRANSCRIPT_ROWS_MAX = 50000;
|
||||
public static final int DEFAULT_TERMINAL_TRANSCRIPT_ROWS = 2000;
|
||||
|
||||
|
||||
/* The supported terminal cursor styles. */
|
||||
|
||||
public static final int TERMINAL_CURSOR_STYLE_BLOCK = 0;
|
||||
public static final int TERMINAL_CURSOR_STYLE_UNDERLINE = 1;
|
||||
public static final int TERMINAL_CURSOR_STYLE_BAR = 2;
|
||||
public static final int DEFAULT_TERMINAL_CURSOR_STYLE = TERMINAL_CURSOR_STYLE_BLOCK;
|
||||
public static final Integer[] TERMINAL_CURSOR_STYLES_LIST = new Integer[]{TERMINAL_CURSOR_STYLE_BLOCK, TERMINAL_CURSOR_STYLE_UNDERLINE, TERMINAL_CURSOR_STYLE_BAR};
|
||||
|
||||
/** The terminal cursor styles. */
|
||||
private int mCursorStyle = DEFAULT_TERMINAL_CURSOR_STYLE;
|
||||
|
||||
|
||||
/** The normal screen buffer. Stores the characters that appear on the screen of the emulated terminal. */
|
||||
private final TerminalBuffer mMainBuffer;
|
||||
/**
|
||||
@@ -294,9 +308,9 @@ public final class TerminalEmulator {
|
||||
}
|
||||
}
|
||||
|
||||
public TerminalEmulator(TerminalOutput session, int columns, int rows, int transcriptRows, TerminalSessionClient client) {
|
||||
public TerminalEmulator(TerminalOutput session, int columns, int rows, Integer transcriptRows, TerminalSessionClient client) {
|
||||
mSession = session;
|
||||
mScreen = mMainBuffer = new TerminalBuffer(columns, transcriptRows, rows);
|
||||
mScreen = mMainBuffer = new TerminalBuffer(columns, getTerminalTranscriptRows(transcriptRows), rows);
|
||||
mAltBuffer = new TerminalBuffer(columns, rows, rows);
|
||||
mClient = client;
|
||||
mRows = rows;
|
||||
@@ -307,6 +321,8 @@ public final class TerminalEmulator {
|
||||
|
||||
public void updateTerminalSessionClient(TerminalSessionClient client) {
|
||||
mClient = client;
|
||||
setCursorStyle();
|
||||
setCursorBlinkState(true);
|
||||
}
|
||||
|
||||
public TerminalBuffer getScreen() {
|
||||
@@ -317,6 +333,13 @@ public final class TerminalEmulator {
|
||||
return mScreen == mAltBuffer;
|
||||
}
|
||||
|
||||
private int getTerminalTranscriptRows(Integer transcriptRows) {
|
||||
if (transcriptRows == null || transcriptRows < TERMINAL_TRANSCRIPT_ROWS_MIN || transcriptRows > TERMINAL_TRANSCRIPT_ROWS_MAX)
|
||||
return DEFAULT_TERMINAL_TRANSCRIPT_ROWS;
|
||||
else
|
||||
return transcriptRows;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mouseButton one of the MOUSE_* constants of this class.
|
||||
*/
|
||||
@@ -384,11 +407,24 @@ public final class TerminalEmulator {
|
||||
return mCursorCol;
|
||||
}
|
||||
|
||||
/** {@link #CURSOR_STYLE_BAR}, {@link #CURSOR_STYLE_BLOCK} or {@link #CURSOR_STYLE_UNDERLINE} */
|
||||
/** Get the terminal cursor style. It will be one of {@link #TERMINAL_CURSOR_STYLES_LIST} */
|
||||
public int getCursorStyle() {
|
||||
return mCursorStyle;
|
||||
}
|
||||
|
||||
/** Set the terminal cursor style. */
|
||||
public void setCursorStyle() {
|
||||
Integer cursorStyle = null;
|
||||
|
||||
if (mClient != null)
|
||||
cursorStyle = mClient.getTerminalCursorStyle();
|
||||
|
||||
if (cursorStyle == null || !Arrays.asList(TERMINAL_CURSOR_STYLES_LIST).contains(cursorStyle))
|
||||
mCursorStyle = DEFAULT_TERMINAL_CURSOR_STYLE;
|
||||
else
|
||||
mCursorStyle = cursorStyle;
|
||||
}
|
||||
|
||||
public boolean isReverseVideo() {
|
||||
return isDecsetInternalBitSet(DECSET_BIT_REVERSE_VIDEO);
|
||||
}
|
||||
@@ -806,15 +842,15 @@ public final class TerminalEmulator {
|
||||
case 0: // Blinking block.
|
||||
case 1: // Blinking block.
|
||||
case 2: // Steady block.
|
||||
mCursorStyle = CURSOR_STYLE_BLOCK;
|
||||
mCursorStyle = TERMINAL_CURSOR_STYLE_BLOCK;
|
||||
break;
|
||||
case 3: // Blinking underline.
|
||||
case 4: // Steady underline.
|
||||
mCursorStyle = CURSOR_STYLE_UNDERLINE;
|
||||
mCursorStyle = TERMINAL_CURSOR_STYLE_UNDERLINE;
|
||||
break;
|
||||
case 5: // Blinking bar (xterm addition).
|
||||
case 6: // Steady bar (xterm addition).
|
||||
mCursorStyle = CURSOR_STYLE_BAR;
|
||||
mCursorStyle = TERMINAL_CURSOR_STYLE_BAR;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
@@ -2330,7 +2366,7 @@ public final class TerminalEmulator {
|
||||
|
||||
/** Reset terminal state so user can interact with it regardless of present state. */
|
||||
public void reset() {
|
||||
mCursorStyle = CURSOR_STYLE_BLOCK;
|
||||
setCursorStyle();
|
||||
mArgIndex = 0;
|
||||
mContinueSequence = false;
|
||||
mEscapeState = ESC_NONE;
|
||||
|
||||
@@ -74,14 +74,17 @@ public final class TerminalSession extends TerminalOutput {
|
||||
private final String mCwd;
|
||||
private final String[] mArgs;
|
||||
private final String[] mEnv;
|
||||
private final Integer mTranscriptRows;
|
||||
|
||||
|
||||
private static final String LOG_TAG = "TerminalSession";
|
||||
|
||||
public TerminalSession(String shellPath, String cwd, String[] args, String[] env, TerminalSessionClient client) {
|
||||
public TerminalSession(String shellPath, String cwd, String[] args, String[] env, Integer transcriptRows, TerminalSessionClient client) {
|
||||
this.mShellPath = shellPath;
|
||||
this.mCwd = cwd;
|
||||
this.mArgs = args;
|
||||
this.mEnv = env;
|
||||
this.mTranscriptRows = transcriptRows;
|
||||
this.mClient = client;
|
||||
}
|
||||
|
||||
@@ -118,7 +121,7 @@ public final class TerminalSession extends TerminalOutput {
|
||||
* @param rows The number of rows in the terminal window.
|
||||
*/
|
||||
public void initializeEmulator(int columns, int rows) {
|
||||
mEmulator = new TerminalEmulator(this, columns, rows, /* transcript= */2000, mClient);
|
||||
mEmulator = new TerminalEmulator(this, columns, rows, mTranscriptRows, mClient);
|
||||
|
||||
int[] processId = new int[1];
|
||||
mTerminalFileDescriptor = JNI.createSubprocess(mShellPath, mCwd, mArgs, mEnv, processId, rows, columns);
|
||||
|
||||
@@ -22,6 +22,11 @@ public interface TerminalSessionClient {
|
||||
void onTerminalCursorStateChange(boolean state);
|
||||
|
||||
|
||||
|
||||
Integer getTerminalCursorStyle();
|
||||
|
||||
|
||||
|
||||
void logError(String tag, String message);
|
||||
|
||||
void logWarn(String tag, String message);
|
||||
|
||||
@@ -103,23 +103,23 @@ public class TerminalTest extends TerminalTestCase {
|
||||
/** Test the cursor shape changes using DECSCUSR. */
|
||||
public void testSetCursorStyle() throws Exception {
|
||||
withTerminalSized(5, 5);
|
||||
assertEquals(TerminalEmulator.CURSOR_STYLE_BLOCK, mTerminal.getCursorStyle());
|
||||
assertEquals(TerminalEmulator.TERMINAL_CURSOR_STYLE_BLOCK, mTerminal.getCursorStyle());
|
||||
enterString("\033[3 q");
|
||||
assertEquals(TerminalEmulator.CURSOR_STYLE_UNDERLINE, mTerminal.getCursorStyle());
|
||||
assertEquals(TerminalEmulator.TERMINAL_CURSOR_STYLE_UNDERLINE, mTerminal.getCursorStyle());
|
||||
enterString("\033[5 q");
|
||||
assertEquals(TerminalEmulator.CURSOR_STYLE_BAR, mTerminal.getCursorStyle());
|
||||
assertEquals(TerminalEmulator.TERMINAL_CURSOR_STYLE_BAR, mTerminal.getCursorStyle());
|
||||
enterString("\033[0 q");
|
||||
assertEquals(TerminalEmulator.CURSOR_STYLE_BLOCK, mTerminal.getCursorStyle());
|
||||
assertEquals(TerminalEmulator.TERMINAL_CURSOR_STYLE_BLOCK, mTerminal.getCursorStyle());
|
||||
enterString("\033[6 q");
|
||||
assertEquals(TerminalEmulator.CURSOR_STYLE_BAR, mTerminal.getCursorStyle());
|
||||
assertEquals(TerminalEmulator.TERMINAL_CURSOR_STYLE_BAR, mTerminal.getCursorStyle());
|
||||
enterString("\033[4 q");
|
||||
assertEquals(TerminalEmulator.CURSOR_STYLE_UNDERLINE, mTerminal.getCursorStyle());
|
||||
assertEquals(TerminalEmulator.TERMINAL_CURSOR_STYLE_UNDERLINE, mTerminal.getCursorStyle());
|
||||
enterString("\033[1 q");
|
||||
assertEquals(TerminalEmulator.CURSOR_STYLE_BLOCK, mTerminal.getCursorStyle());
|
||||
assertEquals(TerminalEmulator.TERMINAL_CURSOR_STYLE_BLOCK, mTerminal.getCursorStyle());
|
||||
enterString("\033[4 q");
|
||||
assertEquals(TerminalEmulator.CURSOR_STYLE_UNDERLINE, mTerminal.getCursorStyle());
|
||||
assertEquals(TerminalEmulator.TERMINAL_CURSOR_STYLE_UNDERLINE, mTerminal.getCursorStyle());
|
||||
enterString("\033[2 q");
|
||||
assertEquals(TerminalEmulator.CURSOR_STYLE_BLOCK, mTerminal.getCursorStyle());
|
||||
assertEquals(TerminalEmulator.TERMINAL_CURSOR_STYLE_BLOCK, mTerminal.getCursorStyle());
|
||||
}
|
||||
|
||||
public void testPaste() {
|
||||
|
||||
@@ -42,7 +42,7 @@ publishing {
|
||||
bar(MavenPublication) {
|
||||
groupId 'com.termux'
|
||||
artifactId 'terminal-view'
|
||||
version "0.113"
|
||||
version "0.114"
|
||||
artifact(sourceJar)
|
||||
artifact("$buildDir/outputs/aar/terminal-view-release.aar")
|
||||
}
|
||||
|
||||
@@ -200,8 +200,8 @@ public final class TerminalRenderer {
|
||||
if (cursor != 0) {
|
||||
mTextPaint.setColor(cursor);
|
||||
float cursorHeight = mFontLineSpacingAndAscent - mFontAscent;
|
||||
if (cursorStyle == TerminalEmulator.CURSOR_STYLE_UNDERLINE) cursorHeight /= 4.;
|
||||
else if (cursorStyle == TerminalEmulator.CURSOR_STYLE_BAR) right -= ((right - left) * 3) / 4.;
|
||||
if (cursorStyle == TerminalEmulator.TERMINAL_CURSOR_STYLE_UNDERLINE) cursorHeight /= 4.;
|
||||
else if (cursorStyle == TerminalEmulator.TERMINAL_CURSOR_STYLE_BAR) right -= ((right - left) * 3) / 4.;
|
||||
canvas.drawRect(left, y - cursorHeight, right, y, mTextPaint);
|
||||
}
|
||||
|
||||
|
||||
@@ -755,6 +755,7 @@ public final class TerminalView extends View {
|
||||
if (mEmulator == null || (newColumns != mEmulator.mColumns || newRows != mEmulator.mRows)) {
|
||||
mTermSession.updateSize(newColumns, newRows);
|
||||
mEmulator = mTermSession.getEmulator();
|
||||
mClient.onEmulatorSet();
|
||||
|
||||
mTopRow = 0;
|
||||
scrollTo(0, 0);
|
||||
@@ -880,7 +881,13 @@ public final class TerminalView extends View {
|
||||
* {@link #TERMINAL_CURSOR_BLINK_RATE_MIN} and {@link #TERMINAL_CURSOR_BLINK_RATE_MAX}.
|
||||
*
|
||||
* This should be called when the view holding this activity is resumed or stopped so that
|
||||
* cursor blinker does not run when activity is not visible.
|
||||
* cursor blinker does not run when activity is not visible. Ensure that {@link #mEmulator}
|
||||
* is set when you call this to start cursor blinking by waiting for {@link TerminalViewClient#onEmulatorSet()}
|
||||
* event after calling {@link #attachSession(TerminalSession)} for the first session added in the
|
||||
* activity, otherwise blinking will not start. Do not call this directly after
|
||||
* {@link #attachSession(TerminalSession)} since {@link #updateSize()} may return without
|
||||
* setting {@link #mEmulator} since width/height may be 0. Its called again in
|
||||
* {@link #onSizeChanged(int, int, int, int)}.
|
||||
*
|
||||
* It should also be called on the
|
||||
* {@link com.termux.terminal.TerminalSessionClient#onTerminalCursorStateChange(boolean)}
|
||||
|
||||
@@ -56,6 +56,8 @@ public interface TerminalViewClient {
|
||||
boolean onCodePoint(int codePoint, boolean ctrlDown, TerminalSession session);
|
||||
|
||||
|
||||
void onEmulatorSet();
|
||||
|
||||
|
||||
void logError(String tag, String message);
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ publishing {
|
||||
bar(MavenPublication) {
|
||||
groupId 'com.termux'
|
||||
artifactId 'termux-shared'
|
||||
version "0.113"
|
||||
version "0.114"
|
||||
artifact(sourceJar)
|
||||
artifact("$buildDir/outputs/aar/termux-shared-release.aar")
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.termux.shared.models;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -80,6 +81,10 @@ public class ExecutionCommand {
|
||||
public String workingDirectory;
|
||||
|
||||
|
||||
/** The terminal transcript rows for the {@link ExecutionCommand}. */
|
||||
public Integer terminalTranscriptRows;
|
||||
|
||||
|
||||
/** If the {@link ExecutionCommand} is a background or a foreground terminal session command. */
|
||||
public boolean inBackground;
|
||||
/** If the {@link ExecutionCommand} is meant to start a failsafe terminal session. */
|
||||
@@ -106,9 +111,10 @@ public class ExecutionCommand {
|
||||
|
||||
|
||||
/** Defines if {@link ExecutionCommand} was started because of an external plugin request
|
||||
* like {@link TERMUX_SERVICE#ACTION_SERVICE_EXECUTE} intent or from within Termux app itself.
|
||||
*/
|
||||
* like {@link TERMUX_SERVICE#ACTION_SERVICE_EXECUTE} intent or from within Termux app itself. */
|
||||
public boolean isPluginExecutionCommand;
|
||||
/** Defines the {@link Intent} received from the external plugin which started the {@link ExecutionCommand}. */
|
||||
public Intent pluginIntent;
|
||||
/** Defines {@link PendingIntent} that should be sent if an external plugin requested the execution. */
|
||||
public PendingIntent pluginPendingIntent;
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.termux.shared.settings.properties;
|
||||
import com.google.common.collect.ImmutableBiMap;
|
||||
import com.termux.shared.termux.TermuxConstants;
|
||||
import com.termux.shared.logger.Logger;
|
||||
import com.termux.terminal.TerminalEmulator;
|
||||
import com.termux.view.TerminalView;
|
||||
|
||||
import java.io.File;
|
||||
@@ -11,7 +12,7 @@ import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/*
|
||||
* Version: v0.10.0
|
||||
* Version: v0.12.0
|
||||
*
|
||||
* Changelog
|
||||
*
|
||||
@@ -49,6 +50,11 @@ import java.util.Set;
|
||||
* - 0.10.0 (2021-05-15)
|
||||
* - Add `MAP_BACK_KEY_BEHAVIOUR`, `MAP_SOFT_KEYBOARD_TOGGLE_BEHAVIOUR`, `MAP_VOLUME_KEYS_BEHAVIOUR`.
|
||||
*
|
||||
* - 0.11.0 (2021-06-10)
|
||||
* - Add `*KEY_TERMINAL_TRANSCRIPT_ROWS*`.
|
||||
*
|
||||
* - 0.12.0 (2021-06-10)
|
||||
* - Add `*KEY_TERMINAL_CURSOR_STYLE*`.
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -66,6 +72,11 @@ public final class TermuxPropertyConstants {
|
||||
|
||||
/* boolean */
|
||||
|
||||
/** Defines the key for whether a toast will be shown when user changes the terminal session */
|
||||
public static final String KEY_DISABLE_TERMINAL_SESSION_CHANGE_TOAST = "disable-terminal-session-change-toast"; // Default: "disable-terminal-session-change-toast"
|
||||
|
||||
|
||||
|
||||
/** Defines the key for whether to enforce character based input to fix the issue where for some devices like Samsung, the letters might not appear until enter is pressed */
|
||||
public static final String KEY_ENFORCE_CHAR_BASED_INPUT = "enforce-char-based-input"; // Default: "enforce-char-based-input"
|
||||
|
||||
@@ -131,6 +142,36 @@ public final class TermuxPropertyConstants {
|
||||
|
||||
|
||||
|
||||
/** Defines the key for the terminal cursor style */
|
||||
public static final String KEY_TERMINAL_CURSOR_STYLE = "terminal-cursor-style"; // Default: "terminal-cursor-style"
|
||||
|
||||
public static final String VALUE_TERMINAL_CURSOR_STYLE_BLOCK = "block";
|
||||
public static final String VALUE_TERMINAL_CURSOR_STYLE_UNDERLINE = "underline";
|
||||
public static final String VALUE_TERMINAL_CURSOR_STYLE_BAR = "bar";
|
||||
|
||||
public static final int IVALUE_TERMINAL_CURSOR_STYLE_BLOCK = TerminalEmulator.TERMINAL_CURSOR_STYLE_BLOCK;
|
||||
public static final int IVALUE_TERMINAL_CURSOR_STYLE_UNDERLINE = TerminalEmulator.TERMINAL_CURSOR_STYLE_UNDERLINE;
|
||||
public static final int IVALUE_TERMINAL_CURSOR_STYLE_BAR = TerminalEmulator.TERMINAL_CURSOR_STYLE_BAR;
|
||||
public static final int DEFAULT_IVALUE_TERMINAL_CURSOR_STYLE = TerminalEmulator.DEFAULT_TERMINAL_CURSOR_STYLE;
|
||||
|
||||
/** Defines the bidirectional map for terminal cursor styles and their internal values */
|
||||
public static final ImmutableBiMap<String, Integer> MAP_TERMINAL_CURSOR_STYLE =
|
||||
new ImmutableBiMap.Builder<String, Integer>()
|
||||
.put(VALUE_TERMINAL_CURSOR_STYLE_BLOCK, IVALUE_TERMINAL_CURSOR_STYLE_BLOCK)
|
||||
.put(VALUE_TERMINAL_CURSOR_STYLE_UNDERLINE, IVALUE_TERMINAL_CURSOR_STYLE_UNDERLINE)
|
||||
.put(VALUE_TERMINAL_CURSOR_STYLE_BAR, IVALUE_TERMINAL_CURSOR_STYLE_BAR)
|
||||
.build();
|
||||
|
||||
|
||||
|
||||
/** Defines the key for the terminal transcript rows */
|
||||
public static final String KEY_TERMINAL_TRANSCRIPT_ROWS = "terminal-transcript-rows"; // Default: "terminal-transcript-rows"
|
||||
public static final int IVALUE_TERMINAL_TRANSCRIPT_ROWS_MIN = TerminalEmulator.TERMINAL_TRANSCRIPT_ROWS_MIN;
|
||||
public static final int IVALUE_TERMINAL_TRANSCRIPT_ROWS_MAX = TerminalEmulator.TERMINAL_TRANSCRIPT_ROWS_MAX;
|
||||
public static final int DEFAULT_IVALUE_TERMINAL_TRANSCRIPT_ROWS = TerminalEmulator.DEFAULT_TERMINAL_TRANSCRIPT_ROWS;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* float */
|
||||
@@ -201,7 +242,8 @@ public final class TermuxPropertyConstants {
|
||||
|
||||
/** Defines the key for extra keys */
|
||||
public static final String KEY_EXTRA_KEYS = "extra-keys"; // Default: "extra-keys"
|
||||
public static final String DEFAULT_IVALUE_EXTRA_KEYS = "[[ESC, TAB, CTRL, ALT, {key: '-', popup: '|'}, DOWN, UP]]";
|
||||
//public static final String DEFAULT_IVALUE_EXTRA_KEYS = "[[ESC, TAB, CTRL, ALT, {key: '-', popup: '|'}, DOWN, UP]]"; // Single row
|
||||
public static final String DEFAULT_IVALUE_EXTRA_KEYS = "[['ESC','/',{key: '-', popup: '|'},'HOME','UP','END','PGUP'], ['TAB','CTRL','ALT','LEFT','DOWN','RIGHT','PGDN']]"; // Double row
|
||||
|
||||
/** Defines the key for extra keys style */
|
||||
public static final String KEY_EXTRA_KEYS_STYLE = "extra-keys-style"; // Default: "extra-keys-style"
|
||||
@@ -248,6 +290,7 @@ public final class TermuxPropertyConstants {
|
||||
* */
|
||||
public static final Set<String> TERMUX_PROPERTIES_LIST = new HashSet<>(Arrays.asList(
|
||||
/* boolean */
|
||||
KEY_DISABLE_TERMINAL_SESSION_CHANGE_TOAST,
|
||||
KEY_ENFORCE_CHAR_BASED_INPUT,
|
||||
KEY_HIDE_SOFT_KEYBOARD_ON_STARTUP,
|
||||
KEY_USE_BLACK_UI,
|
||||
@@ -259,6 +302,8 @@ public final class TermuxPropertyConstants {
|
||||
/* int */
|
||||
KEY_BELL_BEHAVIOUR,
|
||||
KEY_TERMINAL_CURSOR_BLINK_RATE,
|
||||
KEY_TERMINAL_CURSOR_STYLE,
|
||||
KEY_TERMINAL_TRANSCRIPT_ROWS,
|
||||
|
||||
/* float */
|
||||
KEY_TERMINAL_TOOLBAR_HEIGHT_SCALE_FACTOR,
|
||||
@@ -284,6 +329,7 @@ public final class TermuxPropertyConstants {
|
||||
* default: false
|
||||
* */
|
||||
public static final Set<String> TERMUX_DEFAULT_BOOLEAN_BEHAVIOUR_PROPERTIES_LIST = new HashSet<>(Arrays.asList(
|
||||
KEY_DISABLE_TERMINAL_SESSION_CHANGE_TOAST,
|
||||
KEY_ENFORCE_CHAR_BASED_INPUT,
|
||||
KEY_HIDE_SOFT_KEYBOARD_ON_STARTUP,
|
||||
KEY_USE_CTRL_SPACE_WORKAROUND,
|
||||
|
||||
@@ -13,7 +13,7 @@ import java.util.Properties;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
public class TermuxSharedProperties implements SharedPropertiesParser {
|
||||
public class TermuxSharedProperties {
|
||||
|
||||
protected final Context mContext;
|
||||
protected final SharedProperties mSharedProperties;
|
||||
@@ -24,7 +24,7 @@ public class TermuxSharedProperties implements SharedPropertiesParser {
|
||||
public TermuxSharedProperties(@Nonnull Context context) {
|
||||
mContext = context;
|
||||
mPropertiesFile = TermuxPropertyConstants.getTermuxPropertiesFile();
|
||||
mSharedProperties = new SharedProperties(context, mPropertiesFile, TermuxPropertyConstants.TERMUX_PROPERTIES_LIST, this);
|
||||
mSharedProperties = new SharedProperties(context, mPropertiesFile, TermuxPropertyConstants.TERMUX_PROPERTIES_LIST, new SharedPropertiesParserClient());
|
||||
loadTermuxPropertiesFromDisk();
|
||||
}
|
||||
|
||||
@@ -146,24 +146,47 @@ public class TermuxSharedProperties implements SharedPropertiesParser {
|
||||
// {@link #loadTermuxPropertiesFromDisk()} call
|
||||
// A null value can still be returned by
|
||||
// {@link #getInternalPropertyValueFromValue(Context,String,String)} for some keys
|
||||
value = getInternalPropertyValueFromValue(mContext, key, null);
|
||||
value = getInternalTermuxPropertyValueFromValue(mContext, key, null);
|
||||
Logger.logWarn(LOG_TAG, "The value for \"" + key + "\" not found in SharedProperties cache, 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));
|
||||
return getInternalTermuxPropertyValueFromValue(mContext, key, mSharedProperties.getProperty(key, false));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Override the
|
||||
* {@link SharedPropertiesParser#getInternalPropertyValueFromValue(Context,String,String)}
|
||||
* interface function.
|
||||
* Get the internal {@link Object} value for the key passed from the file returned by
|
||||
* {@link TermuxPropertyConstants#getTermuxPropertiesFile()}. The {@link Properties} object is
|
||||
* read directly from the file and internal value is returned for the property value against the key.
|
||||
*
|
||||
* @param context The context for operations.
|
||||
* @param key The key for which the internal object is required.
|
||||
* @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}.
|
||||
*/
|
||||
@Override
|
||||
public Object getInternalPropertyValueFromValue(Context context, String key, String value) {
|
||||
return getInternalTermuxPropertyValueFromValue(context, key, value);
|
||||
public static Object getInternalPropertyValue(Context context, String key) {
|
||||
return SharedProperties.getInternalProperty(context, TermuxPropertyConstants.getTermuxPropertiesFile(), key, new SharedPropertiesParserClient());
|
||||
}
|
||||
|
||||
/**
|
||||
* The class that implements the {@link SharedPropertiesParser} interface.
|
||||
*/
|
||||
public static class SharedPropertiesParserClient implements SharedPropertiesParser {
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -194,6 +217,10 @@ public class TermuxSharedProperties implements SharedPropertiesParser {
|
||||
return (int) getBellBehaviourInternalPropertyValueFromValue(value);
|
||||
case TermuxPropertyConstants.KEY_TERMINAL_CURSOR_BLINK_RATE:
|
||||
return (int) getTerminalCursorBlinkRateInternalPropertyValueFromValue(value);
|
||||
case TermuxPropertyConstants.KEY_TERMINAL_CURSOR_STYLE:
|
||||
return (int) getTerminalCursorStyleInternalPropertyValueFromValue(value);
|
||||
case TermuxPropertyConstants.KEY_TERMINAL_TRANSCRIPT_ROWS:
|
||||
return (int) getTerminalTranscriptRowsInternalPropertyValueFromValue(value);
|
||||
|
||||
/* float */
|
||||
case TermuxPropertyConstants.KEY_TERMINAL_TOOLBAR_HEIGHT_SCALE_FACTOR:
|
||||
@@ -252,7 +279,7 @@ public class TermuxSharedProperties implements SharedPropertiesParser {
|
||||
/**
|
||||
* Returns the internal value after mapping it based on
|
||||
* {@code TermuxPropertyConstants#MAP_BELL_BEHAVIOUR} if the value is not {@code null}
|
||||
* and is valid, otherwise returns {@code TermuxPropertyConstants#DEFAULT_IVALUE_BELL_BEHAVIOUR}.
|
||||
* and is valid, otherwise returns {@link TermuxPropertyConstants#DEFAULT_IVALUE_BELL_BEHAVIOUR}.
|
||||
*
|
||||
* @param value The {@link String} value to convert.
|
||||
* @return Returns the internal value for value.
|
||||
@@ -263,14 +290,14 @@ public class TermuxSharedProperties implements SharedPropertiesParser {
|
||||
|
||||
/**
|
||||
* Returns the int for the value if its not null and is between
|
||||
* {@code TermuxPropertyConstants#IVALUE_TERMINAL_TOOLBAR_HEIGHT_SCALE_FACTOR_MIN} and
|
||||
* {@code TermuxPropertyConstants#IVALUE_TERMINAL_TOOLBAR_HEIGHT_SCALE_FACTOR_MAX},
|
||||
* otherwise returns {@code TermuxPropertyConstants#DEFAULT_IVALUE_TERMINAL_TOOLBAR_HEIGHT_SCALE_FACTOR}.
|
||||
* {@link TermuxPropertyConstants#IVALUE_TERMINAL_CURSOR_BLINK_RATE_MIN} and
|
||||
* {@link TermuxPropertyConstants#IVALUE_TERMINAL_CURSOR_BLINK_RATE_MAX},
|
||||
* otherwise returns {@link TermuxPropertyConstants#DEFAULT_IVALUE_TERMINAL_CURSOR_BLINK_RATE}.
|
||||
*
|
||||
* @param value The {@link String} value to convert.
|
||||
* @return Returns the internal value for value.
|
||||
*/
|
||||
public static float getTerminalCursorBlinkRateInternalPropertyValueFromValue(String value) {
|
||||
public static int getTerminalCursorBlinkRateInternalPropertyValueFromValue(String value) {
|
||||
return SharedProperties.getDefaultIfNotInRange(TermuxPropertyConstants.KEY_TERMINAL_CURSOR_BLINK_RATE,
|
||||
DataUtils.getIntFromString(value, TermuxPropertyConstants.DEFAULT_IVALUE_TERMINAL_CURSOR_BLINK_RATE),
|
||||
TermuxPropertyConstants.DEFAULT_IVALUE_TERMINAL_CURSOR_BLINK_RATE,
|
||||
@@ -279,11 +306,41 @@ public class TermuxSharedProperties implements SharedPropertiesParser {
|
||||
true, true, LOG_TAG);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the internal value after mapping it based on
|
||||
* {@link TermuxPropertyConstants#MAP_TERMINAL_CURSOR_STYLE} if the value is not {@code null}
|
||||
* and is valid, otherwise returns {@link TermuxPropertyConstants#DEFAULT_IVALUE_TERMINAL_CURSOR_STYLE}.
|
||||
*
|
||||
* @param value The {@link String} value to convert.
|
||||
* @return Returns the internal value for value.
|
||||
*/
|
||||
public static int getTerminalCursorStyleInternalPropertyValueFromValue(String value) {
|
||||
return (int) SharedProperties.getDefaultIfNotInMap(TermuxPropertyConstants.KEY_TERMINAL_CURSOR_STYLE, TermuxPropertyConstants.MAP_TERMINAL_CURSOR_STYLE, SharedProperties.toLowerCase(value), TermuxPropertyConstants.DEFAULT_IVALUE_TERMINAL_CURSOR_STYLE, true, LOG_TAG);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the int for the value if its not null and is between
|
||||
* {@code TermuxPropertyConstants#IVALUE_TERMINAL_TOOLBAR_HEIGHT_SCALE_FACTOR_MIN} and
|
||||
* {@code TermuxPropertyConstants#IVALUE_TERMINAL_TOOLBAR_HEIGHT_SCALE_FACTOR_MAX},
|
||||
* otherwise returns {@code TermuxPropertyConstants#DEFAULT_IVALUE_TERMINAL_TOOLBAR_HEIGHT_SCALE_FACTOR}.
|
||||
* {@link TermuxPropertyConstants#IVALUE_TERMINAL_TRANSCRIPT_ROWS_MIN} and
|
||||
* {@link TermuxPropertyConstants#IVALUE_TERMINAL_TRANSCRIPT_ROWS_MAX},
|
||||
* otherwise returns {@link TermuxPropertyConstants#DEFAULT_IVALUE_TERMINAL_TRANSCRIPT_ROWS}.
|
||||
*
|
||||
* @param value The {@link String} value to convert.
|
||||
* @return Returns the internal value for value.
|
||||
*/
|
||||
public static int getTerminalTranscriptRowsInternalPropertyValueFromValue(String value) {
|
||||
return SharedProperties.getDefaultIfNotInRange(TermuxPropertyConstants.KEY_TERMINAL_TRANSCRIPT_ROWS,
|
||||
DataUtils.getIntFromString(value, TermuxPropertyConstants.DEFAULT_IVALUE_TERMINAL_TRANSCRIPT_ROWS),
|
||||
TermuxPropertyConstants.DEFAULT_IVALUE_TERMINAL_TRANSCRIPT_ROWS,
|
||||
TermuxPropertyConstants.IVALUE_TERMINAL_TRANSCRIPT_ROWS_MIN,
|
||||
TermuxPropertyConstants.IVALUE_TERMINAL_TRANSCRIPT_ROWS_MAX,
|
||||
true, true, LOG_TAG);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the int for the value if its not null and is between
|
||||
* {@link TermuxPropertyConstants#IVALUE_TERMINAL_TOOLBAR_HEIGHT_SCALE_FACTOR_MIN} and
|
||||
* {@link TermuxPropertyConstants#IVALUE_TERMINAL_TOOLBAR_HEIGHT_SCALE_FACTOR_MAX},
|
||||
* otherwise returns {@link TermuxPropertyConstants#DEFAULT_IVALUE_TERMINAL_TOOLBAR_HEIGHT_SCALE_FACTOR}.
|
||||
*
|
||||
* @param value The {@link String} value to convert.
|
||||
* @return Returns the internal value for value.
|
||||
@@ -403,6 +460,10 @@ public class TermuxSharedProperties implements SharedPropertiesParser {
|
||||
|
||||
|
||||
|
||||
public boolean areTerminalSessionChangeToastsDisabled() {
|
||||
return (boolean) getInternalPropertyValue(TermuxPropertyConstants.KEY_DISABLE_TERMINAL_SESSION_CHANGE_TOAST, true);
|
||||
}
|
||||
|
||||
public boolean isEnforcingCharBasedInput() {
|
||||
return (boolean) getInternalPropertyValue(TermuxPropertyConstants.KEY_ENFORCE_CHAR_BASED_INPUT, true);
|
||||
}
|
||||
@@ -435,6 +496,14 @@ public class TermuxSharedProperties implements SharedPropertiesParser {
|
||||
return (int) getInternalPropertyValue(TermuxPropertyConstants.KEY_TERMINAL_CURSOR_BLINK_RATE, true);
|
||||
}
|
||||
|
||||
public int getTerminalCursorStyle() {
|
||||
return (int) getInternalPropertyValue(TermuxPropertyConstants.KEY_TERMINAL_CURSOR_STYLE, true);
|
||||
}
|
||||
|
||||
public int getTerminalTranscriptRows() {
|
||||
return (int) getInternalPropertyValue(TermuxPropertyConstants.KEY_TERMINAL_TRANSCRIPT_ROWS, true);
|
||||
}
|
||||
|
||||
public float getTerminalToolbarHeightScaleFactor() {
|
||||
return (float) getInternalPropertyValue(TermuxPropertyConstants.KEY_TERMINAL_TOOLBAR_HEIGHT_SCALE_FACTOR, true);
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ public class TermuxSession {
|
||||
Logger.logDebug(LOG_TAG, executionCommand.toString());
|
||||
|
||||
Logger.logDebug(LOG_TAG, "Running \"" + executionCommand.getCommandIdAndLabelLogString() + "\" TermuxSession");
|
||||
TerminalSession terminalSession = new TerminalSession(executionCommand.executable, executionCommand.workingDirectory, executionCommand.arguments, environment, terminalSessionClient);
|
||||
TerminalSession terminalSession = new TerminalSession(executionCommand.executable, executionCommand.workingDirectory, executionCommand.arguments, environment, executionCommand.terminalTranscriptRows, terminalSessionClient);
|
||||
|
||||
if (sessionName != null) {
|
||||
terminalSession.mSessionName = sessionName;
|
||||
|
||||
@@ -39,6 +39,13 @@ public class TermuxTerminalSessionClientBase implements TerminalSessionClient {
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public Integer getTerminalCursorStyle() {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void logError(String tag, String message) {
|
||||
Logger.logError(tag, message);
|
||||
|
||||
@@ -67,6 +67,11 @@ public class TermuxTerminalViewClientBase implements TerminalViewClient {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEmulatorSet() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logError(String tag, String message) {
|
||||
Logger.logError(tag, message);
|
||||
|
||||
@@ -102,7 +102,7 @@ public class KeyboardUtils {
|
||||
activity.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
|
||||
}
|
||||
|
||||
public static void setResizeTerminalViewForSoftKeyboardFlags(final Activity activity) {
|
||||
public static void setSoftInputModeAdjustResize(final Activity activity) {
|
||||
// TODO: The flag is deprecated for API 30 and WindowInset API should be used
|
||||
// https://developer.android.com/reference/android/view/WindowManager.LayoutParams#SOFT_INPUT_ADJUST_RESIZE
|
||||
// https://medium.com/androiddevelopers/animating-your-keyboard-fb776a8fb66d
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
package com.termux.shared.view;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.ContextWrapper;
|
||||
import android.graphics.Rect;
|
||||
import android.util.TypedValue;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
public class ViewUtils {
|
||||
|
||||
/**
|
||||
* Check if a {@link View} is fully visible and not hidden or partially covered by another view.
|
||||
*
|
||||
* https://stackoverflow.com/a/51078418/14686958
|
||||
*
|
||||
* @param view The {@link View} to check.
|
||||
* @return Returns {@code true} if view is fully visible.
|
||||
*/
|
||||
public static boolean isViewFullyVisible(View view) {
|
||||
Rect[] windowAndViewRects = getWindowAndViewRects(view);
|
||||
if (windowAndViewRects == null)
|
||||
return false;
|
||||
return windowAndViewRects[0].contains(windowAndViewRects[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link Rect} of a {@link View} and the {@link Rect} of the window inside which it
|
||||
* exists.
|
||||
*
|
||||
* https://stackoverflow.com/a/51078418/14686958
|
||||
*
|
||||
* @param view The {@link View} inside the window whose {@link Rect} to get.
|
||||
* @return Returns {@link Rect[]} if view is visible where Rect[0] will contain window
|
||||
* {@link Rect} and Rect[1] will contain view {@link Rect}. This will be {@code null}
|
||||
* if view is not visible.
|
||||
*/
|
||||
@Nullable
|
||||
public static Rect[] getWindowAndViewRects(View view) {
|
||||
if (view == null || !view.isShown())
|
||||
return null;
|
||||
|
||||
// windowRect - will hold available area where content remain visible to users
|
||||
// Takes into account screen decorations (e.g. statusbar)
|
||||
Rect windowRect = new Rect();
|
||||
view.getWindowVisibleDisplayFrame(windowRect);
|
||||
|
||||
// If there is actionbar, get his height
|
||||
int actionBarHeight = 0;
|
||||
Context context = view.getContext();
|
||||
if (context instanceof AppCompatActivity) {
|
||||
androidx.appcompat.app.ActionBar actionBar = ((AppCompatActivity) context).getSupportActionBar();
|
||||
if (actionBar != null) actionBarHeight = actionBar.getHeight();
|
||||
} else if (context instanceof Activity) {
|
||||
android.app.ActionBar actionBar = ((Activity) context).getActionBar();
|
||||
if (actionBar != null) actionBarHeight = actionBar.getHeight();
|
||||
}
|
||||
|
||||
// windowAvailableRect - takes into account actionbar and statusbar height
|
||||
Rect windowAvailableRect;
|
||||
windowAvailableRect = new Rect(windowRect.left, windowRect.top + actionBarHeight, windowRect.right, windowRect.bottom);
|
||||
|
||||
// viewRect - holds position of the view in window
|
||||
// (methods as getGlobalVisibleRect, getHitRect, getDrawingRect can return different result,
|
||||
// when partialy visible)
|
||||
Rect viewRect;
|
||||
final int[] viewsLocationInWindow = new int[2];
|
||||
view.getLocationInWindow(viewsLocationInWindow);
|
||||
int viewLeft = viewsLocationInWindow[0];
|
||||
int viewTop = viewsLocationInWindow[1];
|
||||
int viewRight = viewLeft + view.getWidth();
|
||||
int viewBottom = viewTop + view.getHeight();
|
||||
viewRect = new Rect(viewLeft, viewTop, viewRight, viewBottom);
|
||||
|
||||
return new Rect[]{windowAvailableRect, viewRect};
|
||||
}
|
||||
|
||||
/** Get the {@link Activity} associated with the {@link Context} if available. */
|
||||
@Nullable
|
||||
public static Activity getActivity(Context context) {
|
||||
while (context instanceof ContextWrapper) {
|
||||
if (context instanceof Activity) {
|
||||
return (Activity)context;
|
||||
}
|
||||
context = ((ContextWrapper)context).getBaseContext();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Convert value in device independent pixels (dp) to pixels (px) units. */
|
||||
public static int dpToPx(Context context, int dp) {
|
||||
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, context.getResources().getDisplayMetrics());
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user