From 1a5a66d0ee6726889c2f6c1e92eca67ff519d9a3 Mon Sep 17 00:00:00 2001 From: Trygve Aaberge Date: Mon, 7 Jun 2021 17:48:36 +0200 Subject: [PATCH] Support clicking directly on a URL to open it This allows you to click/press directly on a URL in the terminal view to open it. It takes priority over opening the keyboard, so if you click on a URL it is opened, and if you click anywhere else the keyboard opens like before. Currently, if the application in the terminal is tracking the mouse and you click on a URL, both actions happen. The mouse event is sent to the application, and the URL is also opened. To enable support for this, you have to set `terminal-onclick-url-open=true` in `termux.properties`. --- .../terminal/TermuxTerminalViewClient.java | 34 ++++++++++------ .../com/termux/terminal/TerminalBuffer.java | 39 +++++++++++++++++++ .../com/termux/terminal/ScreenBufferTest.java | 17 ++++++++ .../java/com/termux/view/TerminalView.java | 14 ++----- .../TextSelectionCursorController.java | 10 +++-- 5 files changed, 90 insertions(+), 24 deletions(-) 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 62c09ed11d..eb62901f20 100644 --- a/app/src/main/java/com/termux/app/terminal/TermuxTerminalViewClient.java +++ b/app/src/main/java/com/termux/app/terminal/TermuxTerminalViewClient.java @@ -25,6 +25,7 @@ import com.termux.shared.data.UrlUtils; import com.termux.shared.file.FileUtils; import com.termux.shared.interact.MessageDialogUtils; +import com.termux.shared.interact.ShareUtils; import com.termux.shared.shell.ShellUtils; import com.termux.shared.terminal.TermuxTerminalViewClientBase; import com.termux.shared.terminal.io.extrakeys.SpecialButton; @@ -42,6 +43,7 @@ import com.termux.shared.view.KeyboardUtils; import com.termux.shared.view.ViewUtils; import com.termux.terminal.KeyHandler; +import com.termux.terminal.TerminalBuffer; import com.termux.terminal.TerminalEmulator; import com.termux.terminal.TerminalSession; @@ -172,10 +174,26 @@ public float onScale(float scale) { @Override public void onSingleTapUp(MotionEvent e) { - if (!KeyboardUtils.areDisableSoftKeyboardFlagsSet(mActivity)) - KeyboardUtils.showSoftKeyboard(mActivity, mActivity.getTerminalView()); - else - Logger.logVerbose(LOG_TAG, "Not showing soft keyboard onSingleTapUp since its disabled"); + TerminalEmulator term = mActivity.getCurrentSession().getEmulator(); + + if (mActivity.getProperties().shouldOpenTerminalTranscriptURLOnClick()) { + int[] xAndY = mActivity.getTerminalView().getTextSelectionCursorController().getXAndYFromEvent(e); + String wordAtTap = term.getScreen().getWordAtLocation(xAndY[0], xAndY[1]); + LinkedHashSet urlSet = UrlUtils.extractUrls(wordAtTap); + + if (!urlSet.isEmpty()) { + String url = (String) urlSet.iterator().next(); + ShareUtils.openURL(mActivity, url); + return; + } + } + + if (!term.isMouseTrackingActive() && !e.isFromSource(InputDevice.SOURCE_MOUSE)) { + if (!KeyboardUtils.areDisableSoftKeyboardFlagsSet(mActivity)) + KeyboardUtils.showSoftKeyboard(mActivity, mActivity.getTerminalView()); + else + Logger.logVerbose(LOG_TAG, "Not showing soft keyboard onSingleTapUp since its disabled"); + } } @Override @@ -670,13 +688,7 @@ public void showUrlSelection() { lv.setOnItemLongClickListener((parent, view, position, id) -> { dialog.dismiss(); String url = (String) urls[position]; - Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); - try { - mActivity.startActivity(i, null); - } catch (ActivityNotFoundException e) { - // If no applications match, Android displays a system message. - mActivity.startActivity(Intent.createChooser(i, null)); - } + ShareUtils.openURL(mActivity, url); return true; }); }); diff --git a/terminal-emulator/src/main/java/com/termux/terminal/TerminalBuffer.java b/terminal-emulator/src/main/java/com/termux/terminal/TerminalBuffer.java index 1e3af9dee5..8b898434ff 100644 --- a/terminal-emulator/src/main/java/com/termux/terminal/TerminalBuffer.java +++ b/terminal-emulator/src/main/java/com/termux/terminal/TerminalBuffer.java @@ -102,6 +102,45 @@ public String getSelectedText(int selX1, int selY1, int selX2, int selY2, boolea return builder.toString(); } + public String getWordAtLocation(int x, int y) { + // Set y1 and y2 to the lines where the wrapped line starts and ends. + // I.e. if a line that is wrapped to 3 lines starts at line 4, and this + // is called with y=5, then y1 would be set to 4 and y2 would be set to 6. + int y1 = y; + int y2 = y; + while (y1 > 0 && !getSelectedText(0, y1 - 1, mColumns, y, true, true).contains("\n")) { + y1--; + } + while (y2 < mScreenRows && !getSelectedText(0, y, mColumns, y2 + 1, true, true).contains("\n")) { + y2++; + } + + // Get the text for the whole wrapped line + String text = getSelectedText(0, y1, mColumns, y2, true, true); + // The index of x in text + int textOffset = (y - y1) * mColumns + x; + + if (textOffset >= text.length()) { + // The click was to the right of the last word on the line, so + // there's no word to return + return ""; + } + + // Set x1 and x2 to the indices of the last space before x and the + // first space after x in text respectively + int x1 = text.lastIndexOf(' ', textOffset); + int x2 = text.indexOf(' ', textOffset); + if (x2 == -1) { + x2 = text.length(); + } + + if (x1 == x2) { + // The click was on a space, so there's no word to return + return ""; + } + return text.substring(x1 + 1, x2); + } + public int getActiveTranscriptRows() { return mActiveTranscriptRows; } diff --git a/terminal-emulator/src/test/java/com/termux/terminal/ScreenBufferTest.java b/terminal-emulator/src/test/java/com/termux/terminal/ScreenBufferTest.java index 23942bf517..9a8f115ef2 100644 --- a/terminal-emulator/src/test/java/com/termux/terminal/ScreenBufferTest.java +++ b/terminal-emulator/src/test/java/com/termux/terminal/ScreenBufferTest.java @@ -45,4 +45,21 @@ public void testGetSelectedTextJoinFullLines() { withTerminalSized(5, 3).enterString("ABC\r\nFG"); assertEquals("ABC\nFG", mTerminal.getScreen().getSelectedText(0, 0, 1, 1, true, true)); } + + public void testGetWordAtLocation() { + withTerminalSized(5, 3).enterString("ABCDEFGHIJ\r\nKLMNO"); + assertEquals("ABCDEFGHIJKLMNO", mTerminal.getScreen().getWordAtLocation(0, 0)); + assertEquals("ABCDEFGHIJKLMNO", mTerminal.getScreen().getWordAtLocation(4, 1)); + assertEquals("ABCDEFGHIJKLMNO", mTerminal.getScreen().getWordAtLocation(4, 2)); + + withTerminalSized(5, 3).enterString("ABC DEF GHI "); + assertEquals("ABC", mTerminal.getScreen().getWordAtLocation(0, 0)); + assertEquals("", mTerminal.getScreen().getWordAtLocation(3, 0)); + assertEquals("DEF", mTerminal.getScreen().getWordAtLocation(4, 0)); + assertEquals("DEF", mTerminal.getScreen().getWordAtLocation(0, 1)); + assertEquals("DEF", mTerminal.getScreen().getWordAtLocation(1, 1)); + assertEquals("GHI", mTerminal.getScreen().getWordAtLocation(0, 2)); + assertEquals("", mTerminal.getScreen().getWordAtLocation(1, 2)); + assertEquals("", mTerminal.getScreen().getWordAtLocation(2, 2)); + } } 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 a6ef7106ec..25773840b7 100644 --- a/terminal-view/src/main/java/com/termux/view/TerminalView.java +++ b/terminal-view/src/main/java/com/termux/view/TerminalView.java @@ -94,7 +94,7 @@ public TerminalView(Context context, AttributeSet attributes) { // NO_UCD (unuse @Override public boolean onUp(MotionEvent event) { mScrollRemainder = 0.0f; - if (mEmulator != null && mEmulator.isMouseTrackingActive() && !isSelectingText() && !scrolledWithFinger) { + if (mEmulator != null && mEmulator.isMouseTrackingActive() && !event.isFromSource(InputDevice.SOURCE_MOUSE) && !isSelectingText() && !scrolledWithFinger) { // Quick event processing when mouse tracking is active - do not wait for check of double tapping // for zooming. sendMouseEventCode(event, TerminalEmulator.MOUSE_LEFT_BUTTON, true); @@ -114,13 +114,8 @@ public boolean onSingleTapUp(MotionEvent event) { return true; } requestFocus(); - if (!mEmulator.isMouseTrackingActive()) { - if (!event.isFromSource(InputDevice.SOURCE_MOUSE)) { - mClient.onSingleTapUp(event); - return true; - } - } - return false; + mClient.onSingleTapUp(event); + return true; } @Override @@ -550,7 +545,6 @@ public boolean onTouchEvent(MotionEvent event) { sendMouseEventCode(event, TerminalEmulator.MOUSE_LEFT_BUTTON_MOVED, true); break; } - return true; } } @@ -1135,7 +1129,7 @@ public void run() { /** * Define functions required for text selection and its handles. */ - TextSelectionCursorController getTextSelectionCursorController() { + public TextSelectionCursorController getTextSelectionCursorController() { if (mTextSelectionCursorController == null) { mTextSelectionCursorController = new TextSelectionCursorController(this); 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 954bf84c14..fe137a181b 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 @@ -88,15 +88,19 @@ public void render() { } } - public void setInitialTextSelectionPosition(MotionEvent event) { + public int[] getXAndYFromEvent(MotionEvent event) { int cx = (int) (event.getX() / terminalView.mRenderer.getFontWidth()); final boolean eventFromMouse = event.isFromSource(InputDevice.SOURCE_MOUSE); // Offset for finger: final int SELECT_TEXT_OFFSET_Y = eventFromMouse ? 0 : -40; int cy = (int) ((event.getY() + SELECT_TEXT_OFFSET_Y) / terminalView.mRenderer.getFontLineSpacing()) + terminalView.getTopRow(); + return new int[] { cx, cy }; + } - mSelX1 = mSelX2 = cx; - mSelY1 = mSelY2 = cy; + public void setInitialTextSelectionPosition(MotionEvent event) { + int[] xAndY = getXAndYFromEvent(event); + mSelX1 = mSelX2 = xAndY[0]; + mSelY1 = mSelY2 = xAndY[1]; TerminalBuffer screen = terminalView.mEmulator.getScreen(); if (!" ".equals(screen.getSelectedText(mSelX1, mSelY1, mSelX1, mSelY1))) {