diff --git a/app/src/main/java/com/termux/app/TermuxActivity.java b/app/src/main/java/com/termux/app/TermuxActivity.java index 1aa35a32e1..a2bb11e8c4 100644 --- a/app/src/main/java/com/termux/app/TermuxActivity.java +++ b/app/src/main/java/com/termux/app/TermuxActivity.java @@ -38,6 +38,7 @@ import com.termux.shared.activity.media.AppCompatActivityUtils; import com.termux.shared.data.IntentUtils; import com.termux.shared.android.PermissionUtils; +import com.termux.shared.data.DataUtils; import com.termux.shared.termux.TermuxConstants; import com.termux.shared.termux.TermuxConstants.TERMUX_APP.TERMUX_ACTIVITY; import com.termux.app.activities.HelpActivity; @@ -179,6 +180,7 @@ public final class TermuxActivity extends AppCompatActivity implements ServiceCo private static final int CONTEXT_MENU_SELECT_URL_ID = 0; private static final int CONTEXT_MENU_SHARE_TRANSCRIPT_ID = 1; + private static final int CONTEXT_MENU_SHARE_SELECTED_TEXT = 10; private static final int CONTEXT_MENU_AUTOFILL_ID = 2; private static final int CONTEXT_MENU_RESET_TERMINAL_ID = 3; private static final int CONTEXT_MENU_KILL_PROCESS_ID = 4; @@ -640,7 +642,10 @@ public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuIn menu.add(Menu.NONE, CONTEXT_MENU_SELECT_URL_ID, Menu.NONE, R.string.action_select_url); menu.add(Menu.NONE, CONTEXT_MENU_SHARE_TRANSCRIPT_ID, Menu.NONE, R.string.action_share_transcript); - if (addAutoFillMenu) menu.add(Menu.NONE, CONTEXT_MENU_AUTOFILL_ID, Menu.NONE, R.string.action_autofill_password); + if (!DataUtils.isNullOrEmpty(mTerminalView.getStoredSelectedText())) + menu.add(Menu.NONE, CONTEXT_MENU_SHARE_SELECTED_TEXT, Menu.NONE, R.string.action_share_selected_text); + if (addAutoFillMenu) + 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); @@ -668,6 +673,9 @@ public boolean onContextItemSelected(MenuItem item) { case CONTEXT_MENU_SHARE_TRANSCRIPT_ID: mTermuxTerminalViewClient.shareSessionTranscript(); return true; + case CONTEXT_MENU_SHARE_SELECTED_TEXT: + mTermuxTerminalViewClient.shareSelectedText(); + return true; case CONTEXT_MENU_AUTOFILL_ID: requestAutoFill(); return true; @@ -697,6 +705,13 @@ public boolean onContextItemSelected(MenuItem item) { } } + @Override + public void onContextMenuClosed(Menu menu) { + super.onContextMenuClosed(menu); + // onContextMenuClosed() is triggered twice if back button is pressed to dismiss instead of tap for some reason + mTerminalView.onContextMenuClosed(menu); + } + private void showKillSessionDialog(TerminalSession session) { if (session == null) return; 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 e3143635e9..cd38163116 100644 --- a/app/src/main/java/com/termux/app/terminal/TermuxTerminalSessionActivityClient.java +++ b/app/src/main/java/com/termux/app/terminal/TermuxTerminalSessionActivityClient.java @@ -17,6 +17,7 @@ import androidx.annotation.Nullable; import com.termux.R; +import com.termux.shared.interact.ShareUtils; import com.termux.shared.termux.shell.command.runner.terminal.TermuxSession; import com.termux.shared.termux.interact.TextInputDialogUtils; import com.termux.app.TermuxActivity; diff --git a/app/src/main/java/com/termux/app/terminal/TermuxTerminalViewClient.java b/app/src/main/java/com/termux/app/terminal/TermuxTerminalViewClient.java index 90350a9c1a..a3d09d3d52 100644 --- a/app/src/main/java/com/termux/app/terminal/TermuxTerminalViewClient.java +++ b/app/src/main/java/com/termux/app/terminal/TermuxTerminalViewClient.java @@ -684,6 +684,13 @@ public void shareSessionTranscript() { transcriptText, mActivity.getString(R.string.title_share_transcript_with)); } + public void shareSelectedText() { + String selectedText = mActivity.getTerminalView().getStoredSelectedText(); + if (DataUtils.isNullOrEmpty(selectedText)) return; + ShareUtils.shareText(mActivity, mActivity.getString(R.string.title_share_selected_text), + selectedText, mActivity.getString(R.string.title_share_selected_text_with)); + } + public void showUrlSelection() { TerminalSession session = mActivity.getCurrentSession(); if (session == null) return; diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 642fbb5e07..794d8df3e0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -69,6 +69,10 @@ Terminal transcript Send transcript to: + Share selected text + Terminal Text + Send selected text to: + Autofill password Reset diff --git a/terminal-view/src/main/java/com/termux/view/TerminalView.java b/terminal-view/src/main/java/com/termux/view/TerminalView.java index 365e300001..3baecd2481 100644 --- a/terminal-view/src/main/java/com/termux/view/TerminalView.java +++ b/terminal-view/src/main/java/com/termux/view/TerminalView.java @@ -2,6 +2,7 @@ import android.annotation.SuppressLint; import android.annotation.TargetApi; +import android.app.Activity; import android.content.ClipData; import android.content.ClipboardManager; import android.content.Context; @@ -19,6 +20,7 @@ import android.view.InputDevice; import android.view.KeyCharacterMap; import android.view.KeyEvent; +import android.view.Menu; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; @@ -30,6 +32,7 @@ import android.view.inputmethod.InputConnection; import android.widget.Scroller; +import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import com.termux.terminal.KeyHandler; @@ -456,6 +459,14 @@ public void onScreenUpdated(boolean skipScrolling) { if (mAccessibilityEnabled) setContentDescription(getText()); } + /** This must be called by the hosting activity in {@link Activity#onContextMenuClosed(Menu)} + * when context menu for the {@link TerminalView} is started by + * {@link TextSelectionCursorController#ACTION_MORE} is closed. */ + public void onContextMenuClosed(Menu menu) { + // Unset the stored text since it shouldn't be used anymore and should be cleared from memory + unsetStoredSelectedText(); + } + /** * Sets the text size, which in turn sets the number of rows and columns. * @@ -1206,6 +1217,25 @@ public boolean isSelectingText() { } } + /** Get the currently selected text if selecting. */ + public String getSelectedText() { + if (isSelectingText() && mTextSelectionCursorController != null) + return mTextSelectionCursorController.getSelectedText(); + else + return null; + } + + /** Get the selected text stored before "MORE" button was pressed on the context menu. */ + @Nullable + public String getStoredSelectedText() { + return mTextSelectionCursorController != null ? mTextSelectionCursorController.getStoredSelectedText() : null; + } + + /** Unset the selected text stored before "MORE" button was pressed on the context menu. */ + public void unsetStoredSelectedText() { + if (mTextSelectionCursorController != null) mTextSelectionCursorController.unsetStoredSelectedText(); + } + private ActionMode getTextSelectionActionMode() { if (mTextSelectionCursorController != null) { return mTextSelectionCursorController.getActionMode(); diff --git a/terminal-view/src/main/java/com/termux/view/textselection/TextSelectionCursorController.java b/terminal-view/src/main/java/com/termux/view/textselection/TextSelectionCursorController.java index c08dca4470..c2cd7c6c0a 100644 --- a/terminal-view/src/main/java/com/termux/view/textselection/TextSelectionCursorController.java +++ b/terminal-view/src/main/java/com/termux/view/textselection/TextSelectionCursorController.java @@ -11,6 +11,8 @@ import android.view.MotionEvent; import android.view.View; +import androidx.annotation.Nullable; + import com.termux.terminal.TerminalBuffer; import com.termux.terminal.WcWidth; import com.termux.view.R; @@ -20,6 +22,7 @@ public class TextSelectionCursorController implements CursorController { private final TerminalView terminalView; private final TextSelectionHandleView mStartHandle, mEndHandle; + private String mStoredSelectedText; private boolean mIsSelectingText = false; private long mShowStartTime = System.currentTimeMillis(); @@ -27,9 +30,9 @@ public class TextSelectionCursorController implements CursorController { private int mSelX1 = -1, mSelX2 = -1, mSelY1 = -1, mSelY2 = -1; private ActionMode mActionMode; - private final int ACTION_COPY = 1; - private final int ACTION_PASTE = 2; - private final int ACTION_MORE = 3; + public final int ACTION_COPY = 1; + public final int ACTION_PASTE = 2; + public final int ACTION_MORE = 3; public TextSelectionCursorController(TerminalView terminalView) { this.terminalView = terminalView; @@ -112,7 +115,7 @@ public boolean onCreateActionMode(ActionMode mode, Menu menu) { ClipboardManager clipboard = (ClipboardManager) terminalView.getContext().getSystemService(Context.CLIPBOARD_SERVICE); menu.add(Menu.NONE, ACTION_COPY, Menu.NONE, R.string.copy_text).setShowAsAction(show); - menu.add(Menu.NONE, ACTION_PASTE, Menu.NONE, R.string.paste_text).setEnabled(clipboard.hasPrimaryClip()).setShowAsAction(show); + menu.add(Menu.NONE, ACTION_PASTE, Menu.NONE, R.string.paste_text).setEnabled(clipboard != null && clipboard.hasPrimaryClip()).setShowAsAction(show); menu.add(Menu.NONE, ACTION_MORE, Menu.NONE, R.string.text_selection_more); return true; } @@ -131,7 +134,7 @@ public boolean onActionItemClicked(ActionMode mode, MenuItem item) { switch (item.getItemId()) { case ACTION_COPY: - String selectedText = terminalView.mEmulator.getSelectedText(mSelX1, mSelY1, mSelX2, mSelY2).trim(); + String selectedText = getSelectedText(); terminalView.mTermSession.onCopyTextToClipboard(selectedText); terminalView.stopTextSelectionMode(); break; @@ -140,7 +143,13 @@ public boolean onActionItemClicked(ActionMode mode, MenuItem item) { terminalView.mTermSession.onPasteTextFromClipboard(); break; case ACTION_MORE: - terminalView.stopTextSelectionMode(); //we stop text selection first, otherwise handles will show above popup + // We first store the selected text in case TerminalViewClient needs the + // selected text before MORE button was pressed since we are going to + // stop selection mode + mStoredSelectedText = getSelectedText(); + // The text selection needs to be stopped before showing context menu, + // otherwise handles will show above popup + terminalView.stopTextSelectionMode(); terminalView.showContextMenu(); break; } @@ -361,6 +370,22 @@ public void getSelectors(int[] sel) { sel[3] = mSelX2; } + /** Get the currently selected text. */ + public String getSelectedText() { + return terminalView.mEmulator.getSelectedText(mSelX1, mSelY1, mSelX2, mSelY2); + } + + /** Get the selected text stored before "MORE" button was pressed on the context menu. */ + @Nullable + public String getStoredSelectedText() { + return mStoredSelectedText; + } + + /** Unset the selected text stored before "MORE" button was pressed on the context menu. */ + public void unsetStoredSelectedText() { + mStoredSelectedText = null; + } + public ActionMode getActionMode() { return mActionMode; } diff --git a/termux-shared/src/main/java/com/termux/shared/interact/ShareUtils.java b/termux-shared/src/main/java/com/termux/shared/interact/ShareUtils.java index 8d42215bcc..969502447c 100644 --- a/termux-shared/src/main/java/com/termux/shared/interact/ShareUtils.java +++ b/termux-shared/src/main/java/com/termux/shared/interact/ShareUtils.java @@ -156,6 +156,7 @@ public static CharSequence getTextFromClipboard(Context context, boolean coerceT + /** * Open a url. * * @param context The context for operations.