Titan 2 Elite: physical keyboard fix + streaming scroll-hold
Some checks failed
Build / build (apt-android-5) (push) Has been cancelled
Build / build (apt-android-7) (push) Has been cancelled
Automatic Dependency Submission / dependency-submission (push) Has been cancelled
Validate Gradle Wrapper / Validation (push) Has been cancelled
Unit tests / testing (push) Has been cancelled
Attach Debug APKs To Release / attach-apks (apt-android-5) (release) Has been cancelled
Attach Debug APKs To Release / attach-apks (apt-android-7) (release) Has been cancelled
Trigger Termux Library Builds on Jitpack / trigger-termux-library-builds (release) Has been cancelled
Some checks failed
Build / build (apt-android-5) (push) Has been cancelled
Build / build (apt-android-7) (push) Has been cancelled
Automatic Dependency Submission / dependency-submission (push) Has been cancelled
Validate Gradle Wrapper / Validation (push) Has been cancelled
Unit tests / testing (push) Has been cancelled
Attach Debug APKs To Release / attach-apks (apt-android-5) (release) Has been cancelled
Attach Debug APKs To Release / attach-apks (apt-android-7) (release) Has been cancelled
Trigger Termux Library Builds on Jitpack / trigger-termux-library-builds (release) Has been cancelled
- Only bubble real system keys (isSystem() synthetic letter events injected by the Titan 2 keyboard layer now reach the KCM path). - Latch hardware Shift/Alt from modifier key events; injected letter events arrive without meta state. Cleared on keyup, focus loss and session attach. - Consult the kcm alt layer for Alt+<key> (Alt+W -> '1') and send the resolved char without the ESC alt-prefix. - Hold the scroll viewport when the user scrolled up while output is streaming; auto-follow resumes on scroll-to-bottom or typing. - versionCode 1023 / versionName 0.119.0-titan2.1
This commit is contained in:
@@ -43,8 +43,8 @@ android {
|
|||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion project.properties.minSdkVersion.toInteger()
|
minSdkVersion project.properties.minSdkVersion.toInteger()
|
||||||
targetSdkVersion project.properties.targetSdkVersion.toInteger()
|
targetSdkVersion project.properties.targetSdkVersion.toInteger()
|
||||||
versionCode 118
|
versionCode 1023
|
||||||
versionName "0.118.0"
|
versionName "0.119.0-titan2.1"
|
||||||
|
|
||||||
if (appVersionName) versionName = appVersionName
|
if (appVersionName) versionName = appVersionName
|
||||||
validateVersionName(versionName)
|
validateVersionName(versionName)
|
||||||
|
|||||||
@@ -68,6 +68,15 @@ public final class TerminalView extends View {
|
|||||||
|
|
||||||
/** The top row of text to display. Ranges from -activeTranscriptRows to 0. */
|
/** The top row of text to display. Ranges from -activeTranscriptRows to 0. */
|
||||||
int mTopRow;
|
int mTopRow;
|
||||||
|
|
||||||
|
/** TITAN2 PATCH: user scrolled away from the bottom; hold the viewport until they return. */
|
||||||
|
boolean mUserScrollLock;
|
||||||
|
|
||||||
|
/** TITAN2 PATCH: latched hardware Shift/Alt state. The Titan 2 keyboard layer re-injects
|
||||||
|
* synthetic key events (FLAG_FROM_SYSTEM) whose letter events often arrive without meta
|
||||||
|
* state, so the modifier state is tracked from the modifier key events themselves. */
|
||||||
|
boolean mHardwareShiftDown;
|
||||||
|
boolean mHardwareAltDown;
|
||||||
int[] mDefaultSelectors = new int[]{-1,-1,-1,-1};
|
int[] mDefaultSelectors = new int[]{-1,-1,-1,-1};
|
||||||
|
|
||||||
float mScaleFactor = 1.f;
|
float mScaleFactor = 1.f;
|
||||||
@@ -290,6 +299,9 @@ public final class TerminalView extends View {
|
|||||||
public boolean attachSession(TerminalSession session) {
|
public boolean attachSession(TerminalSession session) {
|
||||||
if (session == mTermSession) return false;
|
if (session == mTermSession) return false;
|
||||||
mTopRow = 0;
|
mTopRow = 0;
|
||||||
|
mUserScrollLock = false; // TITAN2 PATCH
|
||||||
|
mHardwareShiftDown = false; // TITAN2 PATCH
|
||||||
|
mHardwareAltDown = false; // TITAN2 PATCH
|
||||||
|
|
||||||
mTermSession = session;
|
mTermSession = session;
|
||||||
mEmulator = null;
|
mEmulator = null;
|
||||||
@@ -479,6 +491,17 @@ public final class TerminalView extends View {
|
|||||||
mTopRow -= rowShift;
|
mTopRow -= rowShift;
|
||||||
decrementYTextSelectionCursors(rowShift);
|
decrementYTextSelectionCursors(rowShift);
|
||||||
}
|
}
|
||||||
|
} else if (mUserScrollLock && mTopRow != 0) {
|
||||||
|
// TITAN2 PATCH: the user scrolled up, so hold the viewport instead of snapping
|
||||||
|
// back to the bottom on every screen update (e.g. while an app streams output).
|
||||||
|
// Keep the same content in view when new lines are appended.
|
||||||
|
int rowShift = mEmulator.getScrollCounter();
|
||||||
|
if (-mTopRow + rowShift > rowsInHistory) {
|
||||||
|
mTopRow = -rowsInHistory;
|
||||||
|
} else {
|
||||||
|
mTopRow -= rowShift;
|
||||||
|
}
|
||||||
|
skipScrolling = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!skipScrolling && mTopRow != 0) {
|
if (!skipScrolling && mTopRow != 0) {
|
||||||
@@ -583,6 +606,9 @@ public final class TerminalView extends View {
|
|||||||
handleKeyCode(up ? KeyEvent.KEYCODE_DPAD_UP : KeyEvent.KEYCODE_DPAD_DOWN, 0);
|
handleKeyCode(up ? KeyEvent.KEYCODE_DPAD_UP : KeyEvent.KEYCODE_DPAD_DOWN, 0);
|
||||||
} else {
|
} else {
|
||||||
mTopRow = Math.min(0, Math.max(-(mEmulator.getScreen().getActiveTranscriptRows()), mTopRow + (up ? -1 : 1)));
|
mTopRow = Math.min(0, Math.max(-(mEmulator.getScreen().getActiveTranscriptRows()), mTopRow + (up ? -1 : 1)));
|
||||||
|
// TITAN2 PATCH: scrolling away from the bottom pauses auto-follow;
|
||||||
|
// scrolling back to the bottom (mTopRow == 0) resumes it.
|
||||||
|
mUserScrollLock = mTopRow != 0;
|
||||||
if (!awakenScrollBars()) invalidate();
|
if (!awakenScrollBars()) invalidate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -770,6 +796,21 @@ public final class TerminalView extends View {
|
|||||||
if (TERMINAL_VIEW_KEY_LOGGING_ENABLED)
|
if (TERMINAL_VIEW_KEY_LOGGING_ENABLED)
|
||||||
mClient.logInfo(LOG_TAG, "onKeyDown(keyCode=" + keyCode + ", isSystem()=" + event.isSystem() + ", event=" + event + ")");
|
mClient.logInfo(LOG_TAG, "onKeyDown(keyCode=" + keyCode + ", isSystem()=" + event.isSystem() + ", event=" + event + ")");
|
||||||
if (mEmulator == null) return true;
|
if (mEmulator == null) return true;
|
||||||
|
|
||||||
|
// TITAN2 PATCH: latch hardware Shift/Alt state from the modifier key events
|
||||||
|
// themselves. The synthetic letter events injected by the Titan 2 keyboard layer
|
||||||
|
// are flagged FLAG_FROM_SYSTEM and often arrive without meta state.
|
||||||
|
switch (keyCode) {
|
||||||
|
case KeyEvent.KEYCODE_SHIFT_LEFT:
|
||||||
|
case KeyEvent.KEYCODE_SHIFT_RIGHT:
|
||||||
|
mHardwareShiftDown = true;
|
||||||
|
break;
|
||||||
|
case KeyEvent.KEYCODE_ALT_LEFT:
|
||||||
|
case KeyEvent.KEYCODE_ALT_RIGHT:
|
||||||
|
mHardwareAltDown = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if (isSelectingText()) {
|
if (isSelectingText()) {
|
||||||
stopTextSelectionMode();
|
stopTextSelectionMode();
|
||||||
}
|
}
|
||||||
@@ -777,7 +818,11 @@ public final class TerminalView extends View {
|
|||||||
if (mClient.onKeyDown(keyCode, event, mTermSession)) {
|
if (mClient.onKeyDown(keyCode, event, mTermSession)) {
|
||||||
invalidate();
|
invalidate();
|
||||||
return true;
|
return true;
|
||||||
} else if (event.isSystem() && (!mClient.shouldBackButtonBeMappedToEscape() || keyCode != KeyEvent.KEYCODE_BACK)) {
|
} else if (event.isSystem() && isRealSystemKey(keyCode)
|
||||||
|
&& (!mClient.shouldBackButtonBeMappedToEscape() || keyCode != KeyEvent.KEYCODE_BACK)) {
|
||||||
|
// TITAN2 PATCH: only bubble *real* system keys (back, home, volume, ...). The
|
||||||
|
// Titan 2 keyboard layer re-injects letter events with FLAG_FROM_SYSTEM, which
|
||||||
|
// must reach the KCM path below instead of being dropped on the activity.
|
||||||
return super.onKeyDown(keyCode, event);
|
return super.onKeyDown(keyCode, event);
|
||||||
} else if (event.getAction() == KeyEvent.ACTION_MULTIPLE && keyCode == KeyEvent.KEYCODE_UNKNOWN) {
|
} else if (event.getAction() == KeyEvent.ACTION_MULTIPLE && keyCode == KeyEvent.KEYCODE_UNKNOWN) {
|
||||||
mTermSession.write(event.getCharacters());
|
mTermSession.write(event.getCharacters());
|
||||||
@@ -788,8 +833,9 @@ public final class TerminalView extends View {
|
|||||||
|
|
||||||
final int metaState = event.getMetaState();
|
final int metaState = event.getMetaState();
|
||||||
final boolean controlDown = event.isCtrlPressed() || mClient.readControlKey();
|
final boolean controlDown = event.isCtrlPressed() || mClient.readControlKey();
|
||||||
final boolean leftAltDown = (metaState & KeyEvent.META_ALT_LEFT_ON) != 0 || mClient.readAltKey();
|
// TITAN2 PATCH: also use the latched hardware modifier state (injected events lack meta).
|
||||||
final boolean shiftDown = event.isShiftPressed() || mClient.readShiftKey();
|
final boolean leftAltDown = (metaState & KeyEvent.META_ALT_LEFT_ON) != 0 || mClient.readAltKey() || mHardwareAltDown;
|
||||||
|
final boolean shiftDown = event.isShiftPressed() || mClient.readShiftKey() || mHardwareShiftDown;
|
||||||
final boolean rightAltDownFromEvent = (metaState & KeyEvent.META_ALT_RIGHT_ON) != 0;
|
final boolean rightAltDownFromEvent = (metaState & KeyEvent.META_ALT_RIGHT_ON) != 0;
|
||||||
|
|
||||||
int keyMod = 0;
|
int keyMod = 0;
|
||||||
@@ -817,8 +863,19 @@ public final class TerminalView extends View {
|
|||||||
if (mClient.readFnKey()) effectiveMetaState |= KeyEvent.META_FUNCTION_ON;
|
if (mClient.readFnKey()) effectiveMetaState |= KeyEvent.META_FUNCTION_ON;
|
||||||
|
|
||||||
int result = event.getUnicodeChar(effectiveMetaState);
|
int result = event.getUnicodeChar(effectiveMetaState);
|
||||||
|
boolean altLayerUsed = false;
|
||||||
|
if (!rightAltDownFromEvent && leftAltDown && result != 0) {
|
||||||
|
// TITAN2 PATCH: consult the kcm alt layer (e.g. Alt+W -> '1' in TitanKey.kcm).
|
||||||
|
// The base lookup above strips left-alt from the meta state, so upstream never
|
||||||
|
// uses the alt layer and instead sends ESC + <char>.
|
||||||
|
int altLayerResult = event.getUnicodeChar(effectiveMetaState | KeyEvent.META_ALT_LEFT_ON | KeyEvent.META_ALT_ON);
|
||||||
|
if (altLayerResult != 0 && altLayerResult != result) {
|
||||||
|
result = altLayerResult;
|
||||||
|
altLayerUsed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (TERMINAL_VIEW_KEY_LOGGING_ENABLED)
|
if (TERMINAL_VIEW_KEY_LOGGING_ENABLED)
|
||||||
mClient.logInfo(LOG_TAG, "KeyEvent#getUnicodeChar(" + effectiveMetaState + ") returned: " + result);
|
mClient.logInfo(LOG_TAG, "KeyEvent#getUnicodeChar(" + effectiveMetaState + ") returned: " + result + (altLayerUsed ? " (kcm alt layer)" : ""));
|
||||||
if (result == 0) {
|
if (result == 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -835,7 +892,7 @@ public final class TerminalView extends View {
|
|||||||
if (combinedChar > 0) result = combinedChar;
|
if (combinedChar > 0) result = combinedChar;
|
||||||
mCombiningAccent = 0;
|
mCombiningAccent = 0;
|
||||||
}
|
}
|
||||||
inputCodePoint(event.getDeviceId(), result, controlDown, leftAltDown);
|
inputCodePoint(event.getDeviceId(), result, controlDown, leftAltDown, altLayerUsed);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mCombiningAccent != oldCombiningAccent) invalidate();
|
if (mCombiningAccent != oldCombiningAccent) invalidate();
|
||||||
@@ -844,6 +901,10 @@ public final class TerminalView extends View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void inputCodePoint(int eventSource, int codePoint, boolean controlDownFromEvent, boolean leftAltDownFromEvent) {
|
public void inputCodePoint(int eventSource, int codePoint, boolean controlDownFromEvent, boolean leftAltDownFromEvent) {
|
||||||
|
inputCodePoint(eventSource, codePoint, controlDownFromEvent, leftAltDownFromEvent, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void inputCodePoint(int eventSource, int codePoint, boolean controlDownFromEvent, boolean leftAltDownFromEvent, boolean altConsumedByKcm) {
|
||||||
if (TERMINAL_VIEW_KEY_LOGGING_ENABLED) {
|
if (TERMINAL_VIEW_KEY_LOGGING_ENABLED) {
|
||||||
mClient.logInfo(LOG_TAG, "inputCodePoint(eventSource=" + eventSource + ", codePoint=" + codePoint + ", controlDownFromEvent=" + controlDownFromEvent + ", leftAltDownFromEvent="
|
mClient.logInfo(LOG_TAG, "inputCodePoint(eventSource=" + eventSource + ", codePoint=" + codePoint + ", controlDownFromEvent=" + controlDownFromEvent + ", leftAltDownFromEvent="
|
||||||
+ leftAltDownFromEvent + ")");
|
+ leftAltDownFromEvent + ")");
|
||||||
@@ -856,7 +917,11 @@ public final class TerminalView extends View {
|
|||||||
mEmulator.setCursorBlinkState(true);
|
mEmulator.setCursorBlinkState(true);
|
||||||
|
|
||||||
final boolean controlDown = controlDownFromEvent || mClient.readControlKey();
|
final boolean controlDown = controlDownFromEvent || mClient.readControlKey();
|
||||||
final boolean altDown = leftAltDownFromEvent || mClient.readAltKey();
|
// TITAN2 PATCH: if the kcm alt layer already produced the character (altConsumedByKcm),
|
||||||
|
// do not also send the ESC alt-prefix.
|
||||||
|
final boolean altDown = !altConsumedByKcm && (leftAltDownFromEvent || mClient.readAltKey());
|
||||||
|
|
||||||
|
mUserScrollLock = false; // TITAN2 PATCH: input returns the view to the live bottom
|
||||||
|
|
||||||
if (mClient.onCodePoint(codePoint, controlDown, mTermSession)) return;
|
if (mClient.onCodePoint(codePoint, controlDown, mTermSession)) return;
|
||||||
|
|
||||||
@@ -921,6 +986,7 @@ public final class TerminalView extends View {
|
|||||||
String code = KeyHandler.getCode(keyCode, keyMod, term.isCursorKeysApplicationMode(), term.isKeypadApplicationMode());
|
String code = KeyHandler.getCode(keyCode, keyMod, term.isCursorKeysApplicationMode(), term.isKeypadApplicationMode());
|
||||||
if (code == null) return false;
|
if (code == null) return false;
|
||||||
mTermSession.write(code);
|
mTermSession.write(code);
|
||||||
|
mUserScrollLock = false; // TITAN2 PATCH: input returns the view to the live bottom
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -956,6 +1022,18 @@ public final class TerminalView extends View {
|
|||||||
if (TERMINAL_VIEW_KEY_LOGGING_ENABLED)
|
if (TERMINAL_VIEW_KEY_LOGGING_ENABLED)
|
||||||
mClient.logInfo(LOG_TAG, "onKeyUp(keyCode=" + keyCode + ", event=" + event + ")");
|
mClient.logInfo(LOG_TAG, "onKeyUp(keyCode=" + keyCode + ", event=" + event + ")");
|
||||||
|
|
||||||
|
// TITAN2 PATCH: release latched hardware modifier state (see onKeyDown).
|
||||||
|
switch (keyCode) {
|
||||||
|
case KeyEvent.KEYCODE_SHIFT_LEFT:
|
||||||
|
case KeyEvent.KEYCODE_SHIFT_RIGHT:
|
||||||
|
mHardwareShiftDown = false;
|
||||||
|
break;
|
||||||
|
case KeyEvent.KEYCODE_ALT_LEFT:
|
||||||
|
case KeyEvent.KEYCODE_ALT_RIGHT:
|
||||||
|
mHardwareAltDown = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// Do not return for KEYCODE_BACK and send it to the client since user may be trying
|
// Do not return for KEYCODE_BACK and send it to the client since user may be trying
|
||||||
// to exit the activity.
|
// to exit the activity.
|
||||||
if (mEmulator == null && keyCode != KeyEvent.KEYCODE_BACK) return true;
|
if (mEmulator == null && keyCode != KeyEvent.KEYCODE_BACK) return true;
|
||||||
@@ -971,6 +1049,60 @@ public final class TerminalView extends View {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onWindowFocusChanged(boolean hasWindowFocus) {
|
||||||
|
super.onWindowFocusChanged(hasWindowFocus);
|
||||||
|
// TITAN2 PATCH: drop latched hardware modifier state when focus is lost, in case
|
||||||
|
// the keyup was delivered elsewhere (app switch, notification shade, dialog, ...).
|
||||||
|
if (!hasWindowFocus) {
|
||||||
|
mHardwareShiftDown = false;
|
||||||
|
mHardwareAltDown = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TITAN2 PATCH: mirror of the @hide {@code KeyEvent.isSystemKey(int)}. Used to tell
|
||||||
|
* real system keys (back, home, volume, ...) apart from the synthetic letter events
|
||||||
|
* that the Titan 2 keyboard layer injects with FLAG_FROM_SYSTEM.
|
||||||
|
*/
|
||||||
|
private static boolean isRealSystemKey(int keyCode) {
|
||||||
|
switch (keyCode) {
|
||||||
|
case KeyEvent.KEYCODE_MENU:
|
||||||
|
case KeyEvent.KEYCODE_SOFT_RIGHT:
|
||||||
|
case KeyEvent.KEYCODE_HOME:
|
||||||
|
case KeyEvent.KEYCODE_BACK:
|
||||||
|
case KeyEvent.KEYCODE_CALL:
|
||||||
|
case KeyEvent.KEYCODE_ENDCALL:
|
||||||
|
case KeyEvent.KEYCODE_VOLUME_UP:
|
||||||
|
case KeyEvent.KEYCODE_VOLUME_DOWN:
|
||||||
|
case KeyEvent.KEYCODE_MUTE:
|
||||||
|
case KeyEvent.KEYCODE_HEADSETHOOK:
|
||||||
|
case KeyEvent.KEYCODE_MEDIA_PLAY:
|
||||||
|
case KeyEvent.KEYCODE_MEDIA_PAUSE:
|
||||||
|
case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
|
||||||
|
case KeyEvent.KEYCODE_MEDIA_STOP:
|
||||||
|
case KeyEvent.KEYCODE_MEDIA_NEXT:
|
||||||
|
case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
|
||||||
|
case KeyEvent.KEYCODE_MEDIA_REWIND:
|
||||||
|
case KeyEvent.KEYCODE_MEDIA_RECORD:
|
||||||
|
case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
|
||||||
|
case KeyEvent.KEYCODE_CAMERA:
|
||||||
|
case KeyEvent.KEYCODE_FOCUS:
|
||||||
|
case KeyEvent.KEYCODE_SEARCH:
|
||||||
|
case KeyEvent.KEYCODE_BRIGHTNESS_DOWN:
|
||||||
|
case KeyEvent.KEYCODE_BRIGHTNESS_UP:
|
||||||
|
case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK:
|
||||||
|
case KeyEvent.KEYCODE_SLEEP:
|
||||||
|
case KeyEvent.KEYCODE_WAKEUP:
|
||||||
|
case KeyEvent.KEYCODE_VOICE_ASSIST:
|
||||||
|
case KeyEvent.KEYCODE_ASSIST:
|
||||||
|
case KeyEvent.KEYCODE_APP_SWITCH:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is called during layout when the size of this view has changed. If you were just added to the view
|
* This is called during layout when the size of this view has changed. If you were just added to the view
|
||||||
* hierarchy, you're called with the old values of 0.
|
* hierarchy, you're called with the old values of 0.
|
||||||
@@ -1000,6 +1132,7 @@ public final class TerminalView extends View {
|
|||||||
mTerminalCursorBlinkerRunnable.setEmulator(mEmulator);
|
mTerminalCursorBlinkerRunnable.setEmulator(mEmulator);
|
||||||
|
|
||||||
mTopRow = 0;
|
mTopRow = 0;
|
||||||
|
mUserScrollLock = false; // TITAN2 PATCH
|
||||||
scrollTo(0, 0);
|
scrollTo(0, 0);
|
||||||
invalidate();
|
invalidate();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user