Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -199,13 +199,19 @@ public void onSingleTapUp(MotionEvent e) {
}

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");
onRequestSoftKeyboard();
}
}

@Override
public void onRequestSoftKeyboard() {
if (!mActivity.isVisible()) return;
if (!KeyboardUtils.areDisableSoftKeyboardFlagsSet(mActivity))
KeyboardUtils.showSoftKeyboard(mActivity, mActivity.getTerminalView());
else
Logger.logVerbose(LOG_TAG, "Not showing soft keyboard since its disabled");
}

@Override
public boolean shouldBackButtonBeMappedToEscape() {
return mActivity.getProperties().isBackKeyTheEscapeKey();
Expand Down
150 changes: 150 additions & 0 deletions app/src/test/java/com/termux/view/TerminalViewKeyboardTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
package com.termux.view;

import android.os.SystemClock;
import android.view.InputDevice;
import android.view.MotionEvent;
import android.view.ViewConfiguration;

import org.junit.Test;

import static org.junit.Assert.assertEquals;

/** Tests keyboard-request intent, not Android IME display or a remote phone. */
public class TerminalViewKeyboardTest extends TerminalViewTestCase {
private void trackingTap() {
input("\033[?1000h\033[?25l");
tap(InputDevice.SOURCE_TOUCHSCREEN);
}

private void cursorResponse() {
input("\033[?25h");
view.onScreenUpdated();
}

@Test
public void cursorResponseAfterTrackingTapRequestsKeyboardOnce() {
trackingTap();
assertEquals(0, client.keyboardRequests);
cursorResponse();
assertEquals(1, client.keyboardRequests);
cursorResponse();
view.onScreenUpdated();
assertEquals(1, client.keyboardRequests);
}

@Test
public void unsolicitedCursorVisibilityDoesNotRequestKeyboard() {
input("\033[?1000h");
cursorResponse();
assertEquals(0, client.keyboardRequests);
}

@Test
public void animationPhaseDoesNotSubstituteForEnabledCursor() {
trackingTap();
view.mEmulator.setCursorBlinkState(true);
view.onScreenUpdated();
assertEquals(0, client.keyboardRequests);
cursorResponse();
assertEquals(1, client.keyboardRequests);
view.mEmulator.setCursorBlinkState(false);
view.mEmulator.setCursorBlinkState(true);
view.onScreenUpdated();
assertEquals(1, client.keyboardRequests);
}

@Test
public void responseAfterRecentTapWindowHasExpiredIsIgnored() {
trackingTap();
advance(TerminalView.MOUSE_TAP_KEYBOARD_TIMEOUT_MILLIS + 1);
cursorResponse();
assertEquals(0, client.keyboardRequests);
}

@Test
public void newDownInvalidatesEarlierTap() {
trackingTap();
touch(SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN, 2.5f, 3.5f, InputDevice.SOURCE_TOUCHSCREEN);
cursorResponse();
assertEquals(0, client.keyboardRequests);
}

@Test
public void cancelInvalidatesEarlierTap() {
trackingTap();
touch(SystemClock.uptimeMillis(), MotionEvent.ACTION_CANCEL, 2.5f, 3.5f, InputDevice.SOURCE_TOUCHSCREEN);
cursorResponse();
assertEquals(0, client.keyboardRequests);
}

@Test
public void lostWindowFocusInvalidatesEarlierTap() {
trackingTap();
view.onWindowFocusChanged(false);
cursorResponse();
assertEquals(0, client.keyboardRequests);
}

@Test
public void detachInvalidatesEarlierTap() {
trackingTap();
view.onDetachedFromWindow();
cursorResponse();
assertEquals(0, client.keyboardRequests);
}

@Test
public void trackingOffClearsAuthorizationBeforeTrackingReturns() {
trackingTap();
input("\033[?1000l");
view.onScreenUpdated();
input("\033[?1000h");
cursorResponse();
assertEquals(0, client.keyboardRequests);
}

@Test
public void trackingOffTapKeepsExistingClientCallback() {
tap(InputDevice.SOURCE_TOUCHSCREEN);
advance(ViewConfiguration.getDoubleTapTimeout() + 1);
assertEquals(1, client.taps);
cursorResponse();
assertEquals(0, client.keyboardRequests);
}

@Test
public void mouseDeviceTapDoesNotAuthorizeKeyboard() {
input("\033[?1000h\033[?25l");
tap(InputDevice.SOURCE_MOUSE);
cursorResponse();
assertEquals(0, client.keyboardRequests);
}

@Test
public void longPressDoesNotAuthorizeKeyboardOnRelease() {
input("\033[?1000h\033[?25l");
longPress(InputDevice.SOURCE_TOUCHSCREEN);
cursorResponse();
assertEquals(0, client.keyboardRequests);
}

@Test
public void fingerScrollDoesNotAuthorizeKeyboardOnRelease() {
input("\033[?1000h\033[?25l");
long down = SystemClock.uptimeMillis();
touch(down, MotionEvent.ACTION_DOWN, 2.5f, 15.5f, InputDevice.SOURCE_TOUCHSCREEN);
touch(down, MotionEvent.ACTION_MOVE, 2.5f, 3.5f, InputDevice.SOURCE_TOUCHSCREEN);
touch(down, MotionEvent.ACTION_UP, 2.5f, 3.5f, InputDevice.SOURCE_TOUCHSCREEN);
cursorResponse();
assertEquals(0, client.keyboardRequests);
}

@Test
public void anotherTapCanRequestKeyboardForAnAlreadyEnabledCursor() {
trackingTap();
cursorResponse();
tap(InputDevice.SOURCE_TOUCHSCREEN);
view.onScreenUpdated();
assertEquals(2, client.keyboardRequests);
}
}
106 changes: 106 additions & 0 deletions app/src/test/java/com/termux/view/TerminalViewTestCase.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package com.termux.view;

import android.app.Application;
import android.os.Looper;
import android.os.SystemClock;
import android.view.MotionEvent;
import android.view.ViewConfiguration;

import com.termux.shared.termux.terminal.TermuxTerminalSessionClientBase;
import com.termux.shared.termux.terminal.TermuxTerminalViewClientBase;
import com.termux.terminal.TerminalEmulator;
import com.termux.terminal.TerminalOutput;

import org.junit.Before;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.Shadows;
import org.robolectric.annotation.Config;
import org.robolectric.annotation.GraphicsMode;
import org.robolectric.annotation.LooperMode;

import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;

import static org.junit.Assert.assertTrue;

/** Shared real-view/recognizer/emulator fixture; no renderer or gesture mocks. */
@RunWith(RobolectricTestRunner.class)
@Config(sdk = 28, manifest = Config.NONE, application = Application.class)
@GraphicsMode(GraphicsMode.Mode.NATIVE)
@LooperMode(LooperMode.Mode.PAUSED)
public abstract class TerminalViewTestCase {
protected final RecordingOutput output = new RecordingOutput();
protected final RecordingClient client = new RecordingClient();
protected TerminalView view;

@Before
public void setUp() {
view = new TerminalView(RuntimeEnvironment.getApplication(), null);
view.setTerminalViewClient(client);
view.setTextSize(16);
assertTrue("Gesture coordinates require real font width", view.mRenderer.mFontWidth > 0);
assertTrue("Gesture coordinates require real line spacing", view.mRenderer.mFontLineSpacing > 0);
view.mEmulator = new TerminalEmulator(output, 80, 24, 8, 16, null,
new TermuxTerminalSessionClientBase());
view.mGestureRecognizer.mListener.onDown(0, 0);
}

protected void input(String text) {
byte[] bytes = text.getBytes(StandardCharsets.UTF_8);
view.mEmulator.append(bytes, bytes.length);
}

protected MotionEvent event(long down, int action, float column, float row, int source) {
MotionEvent event = MotionEvent.obtain(down, SystemClock.uptimeMillis(), action,
column * view.mRenderer.mFontWidth,
view.mRenderer.mFontLineSpacingAndAscent + row * view.mRenderer.mFontLineSpacing, 0);
event.setSource(source);
return event;
}

protected void touch(long down, int action, float column, float row, int source) {
MotionEvent event = event(down, action, column, row, source);
view.onTouchEvent(event);
event.recycle();
}

protected void tap(int source) {
long down = SystemClock.uptimeMillis();
touch(down, MotionEvent.ACTION_DOWN, 2.5f, 3.5f, source);
touch(down, MotionEvent.ACTION_UP, 2.5f, 3.5f, source);
}

protected void longPress(int source) {
long down = SystemClock.uptimeMillis();
touch(down, MotionEvent.ACTION_DOWN, 2.5f, 3.5f, source);
advance(ViewConfiguration.getTapTimeout() + ViewConfiguration.getLongPressTimeout() + 1);
touch(down, MotionEvent.ACTION_UP, 2.5f, 3.5f, source);
}

protected void advance(long milliseconds) {
Shadows.shadowOf(Looper.getMainLooper()).idleFor(milliseconds, TimeUnit.MILLISECONDS);
}

protected static final class RecordingClient extends TermuxTerminalViewClientBase {
int longPresses;
int taps;
int keyboardRequests;
@Override public boolean onLongPress(MotionEvent event) { longPresses++; return true; }
@Override public void onSingleTapUp(MotionEvent event) { taps++; }
@Override public void onRequestSoftKeyboard() { keyboardRequests++; }
}

protected static final class RecordingOutput extends TerminalOutput {
private final ByteArrayOutputStream bytes = new ByteArrayOutputStream();
String text() { return new String(bytes.toByteArray(), StandardCharsets.UTF_8); }
@Override public void write(byte[] data, int offset, int count) { bytes.write(data, offset, count); }
@Override public void titleChanged(String oldTitle, String newTitle) { }
@Override public void onCopyTextToClipboard(String text) { }
@Override public void onPasteTextFromClipboard() { }
@Override public void onBell() { }
@Override public void onColorsChanged() { }
}
}
27 changes: 27 additions & 0 deletions terminal-view/src/main/java/com/termux/view/TerminalView.java
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ public final class TerminalView extends View {
/** Keep track of the time when a touch event leading to sending mouse scroll events started. */
private long mMouseStartDownTime = -1;

/** A remote cursor response may request the keyboard once after a finger tap. */
static final long MOUSE_TAP_KEYBOARD_TIMEOUT_MILLIS = 3000;
private long mMouseTapKeyboardDeadline;

final Scroller mScroller;

/** What was left in from scrolling movement. */
Expand Down Expand Up @@ -145,6 +149,8 @@ public boolean onUp(MotionEvent event) {
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.
requestFocus();
mMouseTapKeyboardDeadline = SystemClock.uptimeMillis() + MOUSE_TAP_KEYBOARD_TIMEOUT_MILLIS;
sendMouseEventCode(event, TerminalEmulator.MOUSE_LEFT_BUTTON, true);
sendMouseEventCode(event, TerminalEmulator.MOUSE_LEFT_BUTTON, false);
return true;
Expand Down Expand Up @@ -290,6 +296,7 @@ public void setIsTerminalViewKeyLoggingEnabled(boolean value) {
public boolean attachSession(TerminalSession session) {
if (session == mTermSession) return false;

mMouseTapKeyboardDeadline = 0;
mTermSession = session;
mEmulator = null;
mCombiningAccent = 0;
Expand Down Expand Up @@ -459,6 +466,15 @@ public void onScreenUpdated() {
public void onScreenUpdated(boolean skipScrolling) {
if (mEmulator == null) return;

// Read only this view's emulator: the shared cursor callback has no session identity.
// Use DECTCEM, not the cursor's local animation phase, and consume before showing IME.
if (!mEmulator.isMouseTrackingActive() || isSelectingText()) {
mMouseTapKeyboardDeadline = 0;
} else if (mMouseTapKeyboardDeadline > SystemClock.uptimeMillis() && mEmulator.isCursorEnabled()) {
mMouseTapKeyboardDeadline = 0;
mClient.onRequestSoftKeyboard();
}

int rowsInHistory = mEmulator.getScreen().getActiveTranscriptRows();
if (mTopRow < -rowsInHistory) setTopRow(-rowsInHistory);

Expand Down Expand Up @@ -607,6 +623,10 @@ public boolean onGenericMotionEvent(MotionEvent event) {
public boolean onTouchEvent(MotionEvent event) {
if (mEmulator == null) return true;
final int action = event.getAction();
if (action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_CANCEL
|| event.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) {
mMouseTapKeyboardDeadline = 0;
}

if (isSelectingText()) {
updateFloatingToolbarVisibility(event);
Expand Down Expand Up @@ -643,6 +663,12 @@ public boolean onTouchEvent(MotionEvent event) {
return true;
}

@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
if (!hasWindowFocus) mMouseTapKeyboardDeadline = 0;
super.onWindowFocusChanged(hasWindowFocus);
}

@Override
public boolean onKeyPreIme(int keyCode, KeyEvent event) {
if (TERMINAL_VIEW_KEY_LOGGING_ENABLED)
Expand Down Expand Up @@ -1461,6 +1487,7 @@ protected void onAttachedToWindow() {

@Override
protected void onDetachedFromWindow() {
mMouseTapKeyboardDeadline = 0;
super.onDetachedFromWindow();

if (mTextSelectionCursorController != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ public interface TerminalViewClient {
*/
void onSingleTapUp(MotionEvent e);

/** Request the client's soft keyboard, subject to its visibility and keyboard preferences. */
default void onRequestSoftKeyboard() { }

boolean shouldBackButtonBeMappedToEscape();

boolean shouldEnforceCharBasedInput();
Expand Down