2015-10-25 14:27:32 +00:00
package com.termux.view ;
import android.annotation.SuppressLint ;
2016-01-13 02:00:21 +00:00
import android.annotation.TargetApi ;
import android.content.ClipData ;
import android.content.ClipboardManager ;
2015-10-25 14:27:32 +00:00
import android.content.Context ;
import android.graphics.Canvas ;
import android.graphics.Typeface ;
2016-01-13 02:00:21 +00:00
import android.os.Build ;
2021-05-15 11:35:54 +00:00
import android.os.Handler ;
import android.os.Looper ;
2016-09-04 16:56:28 +00:00
import android.text.Editable ;
2015-10-25 14:27:32 +00:00
import android.text.InputType ;
import android.text.TextUtils ;
import android.util.AttributeSet ;
2016-01-13 02:00:21 +00:00
import android.view.ActionMode ;
2015-10-25 14:27:32 +00:00
import android.view.HapticFeedbackConstants ;
import android.view.InputDevice ;
import android.view.KeyCharacterMap ;
import android.view.KeyEvent ;
import android.view.MotionEvent ;
import android.view.View ;
2019-10-05 10:05:42 +00:00
import android.view.ViewConfiguration ;
import android.view.ViewTreeObserver ;
2022-06-16 13:31:08 +00:00
import android.view.accessibility.AccessibilityEvent ;
2019-10-05 10:05:42 +00:00
import android.view.accessibility.AccessibilityManager ;
2020-07-21 10:28:48 +00:00
import android.view.autofill.AutofillValue ;
2015-10-25 14:27:32 +00:00
import android.view.inputmethod.BaseInputConnection ;
import android.view.inputmethod.EditorInfo ;
import android.view.inputmethod.InputConnection ;
import android.widget.Scroller ;
2020-07-21 10:28:48 +00:00
import androidx.annotation.RequiresApi ;
2015-12-28 19:47:34 +00:00
import com.termux.terminal.KeyHandler ;
import com.termux.terminal.TerminalEmulator ;
import com.termux.terminal.TerminalSession ;
2022-06-16 13:31:08 +00:00
import com.termux.view.accessibility.TerminalAccessibilityDelegate ;
2021-03-06 13:25:10 +00:00
import com.termux.view.textselection.TextSelectionCursorController ;
2015-12-28 19:47:34 +00:00
2015-10-25 14:27:32 +00:00
/** View displaying and interacting with a {@link TerminalSession}. */
public final class TerminalView extends View {
2021-03-13 11:49:29 +00:00
/** Log terminal view key and IME events. */
private static boolean TERMINAL_VIEW_KEY_LOGGING_ENABLED = false ;
2016-05-20 08:41:07 +00:00
2016-06-27 23:03:03 +00:00
/** The currently displayed terminal session, whose emulator is {@link #mEmulator}. */
2021-03-06 13:25:10 +00:00
public TerminalSession mTermSession ;
2016-06-27 23:03:03 +00:00
/** Our terminal emulator whose session is {@link #mTermSession}. */
2021-03-06 13:25:10 +00:00
public TerminalEmulator mEmulator ;
2016-06-27 23:03:03 +00:00
2021-03-06 13:25:10 +00:00
public TerminalRenderer mRenderer ;
2016-06-27 23:03:03 +00:00
2021-03-06 13:25:10 +00:00
public TerminalViewClient mClient ;
private TextSelectionCursorController mTextSelectionCursorController ;
2016-06-27 23:03:03 +00:00
2021-05-15 11:35:54 +00:00
private Handler mTerminalCursorBlinkerHandler ;
2021-05-16 14:08:11 +00:00
private TerminalCursorBlinkerRunnable mTerminalCursorBlinkerRunnable ;
2021-05-15 11:35:54 +00:00
private int mTerminalCursorBlinkerRate ;
2021-05-16 14:08:11 +00:00
private boolean mCursorInvisibleIgnoreOnce ;
2021-05-15 11:35:54 +00:00
public static final int TERMINAL_CURSOR_BLINK_RATE_MIN = 100 ;
public static final int TERMINAL_CURSOR_BLINK_RATE_MAX = 2000 ;
2016-06-27 23:03:03 +00:00
/** The top row of text to display. Ranges from -activeTranscriptRows to 0. */
int mTopRow ;
2021-03-06 13:25:10 +00:00
int [ ] mDefaultSelectors = new int [ ] { - 1 , - 1 , - 1 , - 1 } ;
2016-06-27 23:03:03 +00:00
float mScaleFactor = 1 . f ;
final GestureAndScaleRecognizer mGestureRecognizer ;
/** Keep track of where mouse touch event started which we report as mouse scroll. */
private int mMouseScrollStartX = - 1 , mMouseScrollStartY = - 1 ;
/** Keep track of the time when a touch event leading to sending mouse scroll events started. */
private long mMouseStartDownTime = - 1 ;
final Scroller mScroller ;
/** What was left in from scrolling movement. */
float mScrollRemainder ;
/** If non-zero, this is the last unicode code point received if that was a combining character. */
int mCombiningAccent ;
2021-03-06 13:25:10 +00:00
private final boolean mAccessibilityEnabled ;
2018-06-27 18:03:27 +00:00
2022-05-29 17:44:57 +00:00
/** The {@link KeyEvent} is generated from a virtual keyboard, like manually with the {@link KeyEvent#KeyEvent(int, int)} constructor. */
public final static int KEY_EVENT_SOURCE_VIRTUAL_KEYBOARD = KeyCharacterMap . VIRTUAL_KEYBOARD ; // -1
/** The {@link KeyEvent} is generated from a non-physical device, like if 0 value is returned by {@link KeyEvent#getDeviceId()}. */
public final static int KEY_EVENT_SOURCE_SOFT_KEYBOARD = 0 ;
2021-03-13 11:49:29 +00:00
private static final String LOG_TAG = " TerminalView " ;
2016-06-27 23:03:03 +00:00
public TerminalView ( Context context , AttributeSet attributes ) { // NO_UCD (unused code)
super ( context , attributes ) ;
mGestureRecognizer = new GestureAndScaleRecognizer ( context , new GestureAndScaleRecognizer . Listener ( ) {
2016-07-21 22:47:47 +00:00
boolean scrolledWithFinger ;
2016-06-27 23:03:03 +00:00
@Override
2021-03-06 13:25:10 +00:00
public boolean onUp ( MotionEvent event ) {
2016-06-27 23:03:03 +00:00
mScrollRemainder = 0 . 0f ;
2021-06-07 15:48:36 +00:00
if ( mEmulator ! = null & & mEmulator . isMouseTrackingActive ( ) & & ! event . isFromSource ( InputDevice . SOURCE_MOUSE ) & & ! isSelectingText ( ) & & ! scrolledWithFinger ) {
2016-06-27 23:03:03 +00:00
// Quick event processing when mouse tracking is active - do not wait for check of double tapping
// for zooming.
2021-03-06 13:25:10 +00:00
sendMouseEventCode ( event , TerminalEmulator . MOUSE_LEFT_BUTTON , true ) ;
sendMouseEventCode ( event , TerminalEmulator . MOUSE_LEFT_BUTTON , false ) ;
2016-06-27 23:03:03 +00:00
return true ;
}
2016-07-21 22:47:47 +00:00
scrolledWithFinger = false ;
2016-06-27 23:03:03 +00:00
return false ;
}
@Override
2021-03-06 13:25:10 +00:00
public boolean onSingleTapUp ( MotionEvent event ) {
2016-06-27 23:03:03 +00:00
if ( mEmulator = = null ) return true ;
2021-03-06 13:25:10 +00:00
if ( isSelectingText ( ) ) {
2019-10-05 10:05:42 +00:00
stopTextSelectionMode ( ) ;
2016-06-27 23:03:03 +00:00
return true ;
}
requestFocus ( ) ;
2021-06-07 15:48:36 +00:00
mClient . onSingleTapUp ( event ) ;
return true ;
2016-06-27 23:03:03 +00:00
}
@Override
2016-07-21 22:47:47 +00:00
public boolean onScroll ( MotionEvent e , float distanceX , float distanceY ) {
2019-10-05 10:05:42 +00:00
if ( mEmulator = = null ) return true ;
2016-07-21 22:47:47 +00:00
if ( mEmulator . isMouseTrackingActive ( ) & & e . isFromSource ( InputDevice . SOURCE_MOUSE ) ) {
2016-06-27 23:03:03 +00:00
// If moving with mouse pointer while pressing button, report that instead of scroll.
// This means that we never report moving with button press-events for touch input,
// since we cannot just start sending these events without a starting press event,
// which we do not do for touch input, only mouse in onTouchEvent().
2016-07-21 22:47:47 +00:00
sendMouseEventCode ( e , TerminalEmulator . MOUSE_LEFT_BUTTON_MOVED , true ) ;
2016-06-27 23:03:03 +00:00
} else {
2016-07-21 22:47:47 +00:00
scrolledWithFinger = true ;
2016-06-27 23:03:03 +00:00
distanceY + = mScrollRemainder ;
int deltaRows = ( int ) ( distanceY / mRenderer . mFontLineSpacing ) ;
mScrollRemainder = distanceY - deltaRows * mRenderer . mFontLineSpacing ;
2016-07-21 22:47:47 +00:00
doScroll ( e , deltaRows ) ;
2016-06-27 23:03:03 +00:00
}
return true ;
}
@Override
public boolean onScale ( float focusX , float focusY , float scale ) {
2021-03-06 13:25:10 +00:00
if ( mEmulator = = null | | isSelectingText ( ) ) return true ;
2016-06-27 23:03:03 +00:00
mScaleFactor * = scale ;
2017-04-02 12:25:34 +00:00
mScaleFactor = mClient . onScale ( mScaleFactor ) ;
2016-06-27 23:03:03 +00:00
return true ;
}
@Override
public boolean onFling ( final MotionEvent e2 , float velocityX , float velocityY ) {
2019-10-05 10:30:54 +00:00
if ( mEmulator = = null ) return true ;
2016-06-27 23:03:03 +00:00
// Do not start scrolling until last fling has been taken care of:
if ( ! mScroller . isFinished ( ) ) return true ;
final boolean mouseTrackingAtStartOfFling = mEmulator . isMouseTrackingActive ( ) ;
float SCALE = 0 . 25f ;
if ( mouseTrackingAtStartOfFling ) {
mScroller . fling ( 0 , 0 , 0 , - ( int ) ( velocityY * SCALE ) , 0 , 0 , - mEmulator . mRows / 2 , mEmulator . mRows / 2 ) ;
} else {
mScroller . fling ( 0 , mTopRow , 0 , - ( int ) ( velocityY * SCALE ) , 0 , 0 , - mEmulator . getScreen ( ) . getActiveTranscriptRows ( ) , 0 ) ;
}
post ( new Runnable ( ) {
private int mLastY = 0 ;
@Override
public void run ( ) {
if ( mouseTrackingAtStartOfFling ! = mEmulator . isMouseTrackingActive ( ) ) {
mScroller . abortAnimation ( ) ;
return ;
}
if ( mScroller . isFinished ( ) ) return ;
boolean more = mScroller . computeScrollOffset ( ) ;
int newY = mScroller . getCurrY ( ) ;
int diff = mouseTrackingAtStartOfFling ? ( newY - mLastY ) : ( newY - mTopRow ) ;
doScroll ( e2 , diff ) ;
mLastY = newY ;
if ( more ) post ( this ) ;
}
} ) ;
return true ;
}
@Override
public boolean onDown ( float x , float y ) {
2021-03-06 13:25:10 +00:00
// Why is true not returned here?
// https://developer.android.com/training/gestures/detector.html#detect-a-subset-of-supported-gestures
// Although setting this to true still does not solve the following errors when long pressing in terminal view text area
// ViewDragHelper: Ignoring pointerId=0 because ACTION_DOWN was not received for this pointer before ACTION_MOVE
// Commenting out the call to mGestureDetector.onTouchEvent(event) in GestureAndScaleRecognizer#onTouchEvent() removes
// the error logging, so issue is related to GestureDetector
2016-06-27 23:03:03 +00:00
return false ;
}
@Override
2021-03-06 13:25:10 +00:00
public boolean onDoubleTap ( MotionEvent event ) {
2016-06-27 23:03:03 +00:00
// Do not treat is as a single confirmed tap - it may be followed by zoom.
return false ;
}
@Override
2021-03-06 13:25:10 +00:00
public void onLongPress ( MotionEvent event ) {
2017-04-02 12:07:19 +00:00
if ( mGestureRecognizer . isInProgress ( ) ) return ;
2021-03-06 13:25:10 +00:00
if ( mClient . onLongPress ( event ) ) return ;
if ( ! isSelectingText ( ) ) {
2016-06-27 23:03:03 +00:00
performHapticFeedback ( HapticFeedbackConstants . LONG_PRESS ) ;
2021-03-06 13:25:10 +00:00
startTextSelectionMode ( event ) ;
2016-06-27 23:03:03 +00:00
}
}
} ) ;
mScroller = new Scroller ( context ) ;
2019-01-11 20:19:06 +00:00
AccessibilityManager am = ( AccessibilityManager ) context . getSystemService ( Context . ACCESSIBILITY_SERVICE ) ;
2018-06-27 18:03:27 +00:00
mAccessibilityEnabled = am . isEnabled ( ) ;
2022-06-16 13:31:08 +00:00
setAccessibilityDelegate ( new TerminalAccessibilityDelegate ( this ) ) ;
2016-06-27 23:03:03 +00:00
}
2021-05-15 11:35:54 +00:00
2016-06-27 23:03:03 +00:00
/ * *
2021-03-15 22:46:34 +00:00
* @param client The { @link TerminalViewClient } interface implementation to allow
* for communication between { @link TerminalView } and its client .
2021-03-13 11:49:29 +00:00
* /
2021-03-15 22:46:34 +00:00
public void setTerminalViewClient ( TerminalViewClient client ) {
this . mClient = client ;
2021-03-13 11:49:29 +00:00
}
/ * *
2021-05-15 11:35:54 +00:00
* Sets whether terminal view key logging is enabled or not .
2021-03-13 11:49:29 +00:00
*
* @param value The boolean value that defines the state .
2016-06-27 23:03:03 +00:00
* /
2021-03-13 11:49:29 +00:00
public void setIsTerminalViewKeyLoggingEnabled ( boolean value ) {
TERMINAL_VIEW_KEY_LOGGING_ENABLED = value ;
2016-06-27 23:03:03 +00:00
}
2021-05-15 11:35:54 +00:00
2016-06-27 23:03:03 +00:00
/ * *
* Attach a { @link TerminalSession } to this view .
*
* @param session The { @link TerminalSession } this view will be displaying .
* /
public boolean attachSession ( TerminalSession session ) {
if ( session = = mTermSession ) return false ;
mTopRow = 0 ;
mTermSession = session ;
mEmulator = null ;
mCombiningAccent = 0 ;
updateSize ( ) ;
// Wait with enabling the scrollbar until we have a terminal to get scroll position from.
setVerticalScrollBarEnabled ( true ) ;
return true ;
}
@Override
public InputConnection onCreateInputConnection ( EditorInfo outAttrs ) {
2021-07-29 19:32:46 +00:00
// Ensure that inputType is only set if TerminalView is selected view with the keyboard and
// an alternate view is not selected, like an EditText. This is necessary if an activity is
// initially started with the alternate view or if activity is returned to from another app
// and the alternate view was the one selected the last time.
if ( mClient . isTerminalViewSelected ( ) ) {
if ( mClient . shouldEnforceCharBasedInput ( ) ) {
// Some keyboards seems do not reset the internal state on TYPE_NULL.
// Affects mostly Samsung stock keyboards.
// https://github.com/termux/termux-app/issues/686
2021-08-26 01:01:25 +00:00
// However, this is not a valid value as per AOSP since `InputType.TYPE_CLASS_*` is
// not set and it logs a warning:
// W/InputAttributes: Unexpected input class: inputType=0x00080090 imeOptions=0x02000000
// https://cs.android.com/android/platform/superproject/+/android-11.0.0_r40:packages/inputmethods/LatinIME/java/src/com/android/inputmethod/latin/InputAttributes.java;l=79
2021-07-29 19:32:46 +00:00
outAttrs . inputType = InputType . TYPE_TEXT_VARIATION_VISIBLE_PASSWORD | InputType . TYPE_TEXT_FLAG_NO_SUGGESTIONS ;
} else {
// Using InputType.NULL is the most correct input type and avoids issues with other hacks.
//
// Previous keyboard issues:
// https://github.com/termux/termux-packages/issues/25
// https://github.com/termux/termux-app/issues/87.
// https://github.com/termux/termux-app/issues/126.
// https://github.com/termux/termux-app/issues/137 (japanese chars and TYPE_NULL).
outAttrs . inputType = InputType . TYPE_NULL ;
}
2020-08-26 17:04:17 +00:00
} else {
2021-07-29 19:32:46 +00:00
// Corresponds to android:inputType="text"
outAttrs . inputType = InputType . TYPE_CLASS_TEXT | InputType . TYPE_TEXT_VARIATION_NORMAL ;
2020-08-26 17:04:17 +00:00
}
2015-10-25 14:27:32 +00:00
2016-12-30 00:42:47 +00:00
// Note that IME_ACTION_NONE cannot be used as that makes it impossible to input newlines using the on-screen
// keyboard on Android TV (see https://github.com/termux/termux-app/issues/221).
outAttrs . imeOptions = EditorInfo . IME_FLAG_NO_FULLSCREEN ;
2015-10-25 14:27:32 +00:00
2016-06-27 23:03:03 +00:00
return new BaseInputConnection ( this , true ) {
2015-10-25 14:27:32 +00:00
2016-05-09 13:39:11 +00:00
@Override
public boolean finishComposingText ( ) {
2021-03-13 11:49:29 +00:00
if ( TERMINAL_VIEW_KEY_LOGGING_ENABLED ) mClient . logInfo ( LOG_TAG , " IME: finishComposingText() " ) ;
2016-09-04 16:56:28 +00:00
super . finishComposingText ( ) ;
2016-06-27 22:56:30 +00:00
2016-09-04 16:56:28 +00:00
sendTextToTerminal ( getEditable ( ) ) ;
2016-06-27 22:56:30 +00:00
getEditable ( ) . clear ( ) ;
2016-05-09 13:39:11 +00:00
return true ;
}
2015-10-25 14:27:32 +00:00
2016-06-27 22:56:30 +00:00
@Override
public boolean commitText ( CharSequence text , int newCursorPosition ) {
2021-03-13 11:49:29 +00:00
if ( TERMINAL_VIEW_KEY_LOGGING_ENABLED ) {
mClient . logInfo ( LOG_TAG , " IME: commitText( \" " + text + " \" , " + newCursorPosition + " ) " ) ;
2016-09-04 16:56:28 +00:00
}
super . commitText ( text , newCursorPosition ) ;
2016-06-27 23:03:03 +00:00
if ( mEmulator = = null ) return true ;
2016-09-04 16:56:28 +00:00
Editable content = getEditable ( ) ;
sendTextToTerminal ( content ) ;
content . clear ( ) ;
return true ;
}
@Override
public boolean deleteSurroundingText ( int leftLength , int rightLength ) {
2021-03-13 11:49:29 +00:00
if ( TERMINAL_VIEW_KEY_LOGGING_ENABLED ) {
mClient . logInfo ( LOG_TAG , " IME: deleteSurroundingText( " + leftLength + " , " + rightLength + " ) " ) ;
2016-11-27 23:26:37 +00:00
}
// The stock Samsung keyboard with 'Auto check spelling' enabled sends leftLength > 1.
KeyEvent deleteKey = new KeyEvent ( KeyEvent . ACTION_DOWN , KeyEvent . KEYCODE_DEL ) ;
for ( int i = 0 ; i < leftLength ; i + + ) sendKeyEvent ( deleteKey ) ;
2016-09-04 16:56:28 +00:00
return super . deleteSurroundingText ( leftLength , rightLength ) ;
}
void sendTextToTerminal ( CharSequence text ) {
2019-10-05 10:54:15 +00:00
stopTextSelectionMode ( ) ;
2016-06-27 23:03:03 +00:00
final int textLengthInChars = text . length ( ) ;
for ( int i = 0 ; i < textLengthInChars ; i + + ) {
char firstChar = text . charAt ( i ) ;
int codePoint ;
if ( Character . isHighSurrogate ( firstChar ) ) {
if ( + + i < textLengthInChars ) {
codePoint = Character . toCodePoint ( firstChar , text . charAt ( i ) ) ;
} else {
// At end of string, with no low surrogate following the high:
codePoint = TerminalEmulator . UNICODE_REPLACEMENT_CHAR ;
}
} else {
codePoint = firstChar ;
}
2016-06-27 22:56:30 +00:00
2021-08-26 01:01:25 +00:00
// Check onKeyDown() for details.
if ( mClient . readShiftKey ( ) )
codePoint = Character . toUpperCase ( codePoint ) ;
2016-06-27 22:56:30 +00:00
boolean ctrlHeld = false ;
if ( codePoint < = 31 & & codePoint ! = 27 ) {
2016-12-29 23:45:11 +00:00
if ( codePoint = = '\n' ) {
// The AOSP keyboard and descendants seems to send \n as text when the enter key is pressed,
// instead of a key event like most other keyboard apps. A terminal expects \r for the enter
// key (although when icrnl is enabled this doesn't make a difference - run 'stty -icrnl' to
// check the behaviour).
codePoint = '\r' ;
}
2016-06-27 22:56:30 +00:00
// E.g. penti keyboard for ctrl input.
ctrlHeld = true ;
switch ( codePoint ) {
2016-06-27 23:03:03 +00:00
case 31 :
codePoint = '_' ;
break ;
case 30 :
codePoint = '^' ;
break ;
case 29 :
codePoint = ']' ;
break ;
case 28 :
codePoint = '\\' ;
break ;
default :
codePoint + = 96 ;
break ;
2016-06-27 22:56:30 +00:00
}
}
2022-05-29 17:44:57 +00:00
inputCodePoint ( KEY_EVENT_SOURCE_SOFT_KEYBOARD , codePoint , ctrlHeld , false ) ;
2016-06-27 23:03:03 +00:00
}
}
2015-10-25 14:27:32 +00:00
2016-06-27 22:56:30 +00:00
} ;
2016-06-27 23:03:03 +00:00
}
@Override
protected int computeVerticalScrollRange ( ) {
return mEmulator = = null ? 1 : mEmulator . getScreen ( ) . getActiveRows ( ) ;
}
@Override
protected int computeVerticalScrollExtent ( ) {
return mEmulator = = null ? 1 : mEmulator . mRows ;
}
@Override
protected int computeVerticalScrollOffset ( ) {
return mEmulator = = null ? 1 : mEmulator . getScreen ( ) . getActiveRows ( ) + mTopRow - mEmulator . mRows ;
}
public void onScreenUpdated ( ) {
if ( mEmulator = = null ) return ;
2019-01-25 03:31:35 +00:00
int rowsInHistory = mEmulator . getScreen ( ) . getActiveTranscriptRows ( ) ;
if ( mTopRow < - rowsInHistory ) mTopRow = - rowsInHistory ;
2016-06-27 23:03:03 +00:00
boolean skipScrolling = false ;
2021-03-06 13:25:10 +00:00
if ( isSelectingText ( ) ) {
2016-06-27 23:03:03 +00:00
// Do not scroll when selecting text.
int rowShift = mEmulator . getScrollCounter ( ) ;
if ( - mTopRow + rowShift > rowsInHistory ) {
// .. unless we're hitting the end of history transcript, in which
// case we abort text selection and scroll to end.
2019-10-05 10:05:42 +00:00
stopTextSelectionMode ( ) ;
2016-06-27 23:03:03 +00:00
} else {
skipScrolling = true ;
mTopRow - = rowShift ;
2021-03-06 13:25:10 +00:00
decrementYTextSelectionCursors ( rowShift ) ;
2016-06-27 23:03:03 +00:00
}
}
if ( ! skipScrolling & & mTopRow ! = 0 ) {
// Scroll down if not already there.
if ( mTopRow < - 3 ) {
// Awaken scroll bars only if scrolling a noticeable amount
// - we do not want visible scroll bars during normal typing
// of one row at a time.
awakenScrollBars ( ) ;
}
mTopRow = 0 ;
}
mEmulator . clearScrollCounter ( ) ;
invalidate ( ) ;
2022-06-16 13:31:08 +00:00
if ( mAccessibilityEnabled )
sendAccessibilityEvent ( AccessibilityEvent . TYPE_VIEW_TEXT_CHANGED ) ;
2016-06-27 23:03:03 +00:00
}
/ * *
* Sets the text size , which in turn sets the number of rows and columns .
*
* @param textSize the new font size , in density - independent pixels .
* /
public void setTextSize ( int textSize ) {
mRenderer = new TerminalRenderer ( textSize , mRenderer = = null ? Typeface . MONOSPACE : mRenderer . mTypeface ) ;
updateSize ( ) ;
}
2015-10-25 14:27:32 +00:00
2016-06-27 22:56:30 +00:00
public void setTypeface ( Typeface newTypeface ) {
mRenderer = new TerminalRenderer ( mRenderer . mTextSize , newTypeface ) ;
updateSize ( ) ;
invalidate ( ) ;
}
2016-06-27 23:03:03 +00:00
@Override
public boolean onCheckIsTextEditor ( ) {
return true ;
}
@Override
public boolean isOpaque ( ) {
return true ;
}
2021-09-14 19:06:16 +00:00
/ * *
* Get the zero indexed column and row of the terminal view for the
* position of the event .
*
* @param event The event with the position to get the column and row for .
* @param relativeToScroll If true the column number will take the scroll
* position into account . E . g . if scrolled 3 lines up and the event
* position is in the top left , column will be - 3 if relativeToScroll is
* true and 0 if relativeToScroll is false .
* @return Array with the column and row .
* /
public int [ ] getColumnAndRow ( MotionEvent event , boolean relativeToScroll ) {
int column = ( int ) ( event . getX ( ) / mRenderer . mFontWidth ) ;
int row = ( int ) ( ( event . getY ( ) - mRenderer . mFontLineSpacingAndAscent ) / mRenderer . mFontLineSpacing ) ;
if ( relativeToScroll ) {
row + = mTopRow ;
}
return new int [ ] { column , row } ;
}
2016-06-27 23:03:03 +00:00
/** Send a single mouse event code to the terminal. */
void sendMouseEventCode ( MotionEvent e , int button , boolean pressed ) {
2021-09-14 19:06:16 +00:00
int [ ] columnAndRow = getColumnAndRow ( e , false ) ;
int x = columnAndRow [ 0 ] + 1 ;
int y = columnAndRow [ 1 ] + 1 ;
2016-06-27 23:03:03 +00:00
if ( pressed & & ( button = = TerminalEmulator . MOUSE_WHEELDOWN_BUTTON | | button = = TerminalEmulator . MOUSE_WHEELUP_BUTTON ) ) {
if ( mMouseStartDownTime = = e . getDownTime ( ) ) {
x = mMouseScrollStartX ;
y = mMouseScrollStartY ;
} else {
mMouseStartDownTime = e . getDownTime ( ) ;
mMouseScrollStartX = x ;
mMouseScrollStartY = y ;
}
}
mEmulator . sendMouseEvent ( button , x , y , pressed ) ;
}
/** Perform a scroll, either from dragging the screen or by scrolling a mouse wheel. */
void doScroll ( MotionEvent event , int rowsDown ) {
boolean up = rowsDown < 0 ;
int amount = Math . abs ( rowsDown ) ;
for ( int i = 0 ; i < amount ; i + + ) {
if ( mEmulator . isMouseTrackingActive ( ) ) {
sendMouseEventCode ( event , up ? TerminalEmulator . MOUSE_WHEELUP_BUTTON : TerminalEmulator . MOUSE_WHEELDOWN_BUTTON , true ) ;
} else if ( mEmulator . isAlternateBufferActive ( ) ) {
// Send up and down key events for scrolling, which is what some terminals do to make scroll work in
// e.g. less, which shifts to the alt screen without mouse handling.
handleKeyCode ( up ? KeyEvent . KEYCODE_DPAD_UP : KeyEvent . KEYCODE_DPAD_DOWN , 0 ) ;
} else {
mTopRow = Math . min ( 0 , Math . max ( - ( mEmulator . getScreen ( ) . getActiveTranscriptRows ( ) ) , mTopRow + ( up ? - 1 : 1 ) ) ) ;
if ( ! awakenScrollBars ( ) ) invalidate ( ) ;
}
}
}
/** Overriding {@link View#onGenericMotionEvent(MotionEvent)}. */
@Override
public boolean onGenericMotionEvent ( MotionEvent event ) {
if ( mEmulator ! = null & & event . isFromSource ( InputDevice . SOURCE_MOUSE ) & & event . getAction ( ) = = MotionEvent . ACTION_SCROLL ) {
// Handle mouse wheel scrolling.
boolean up = event . getAxisValue ( MotionEvent . AXIS_VSCROLL ) > 0 . 0f ;
doScroll ( event , up ? - 3 : 3 ) ;
return true ;
}
return false ;
}
@SuppressLint ( " ClickableViewAccessibility " )
@Override
@TargetApi ( 23 )
2021-03-06 13:25:10 +00:00
public boolean onTouchEvent ( MotionEvent event ) {
2016-06-27 23:03:03 +00:00
if ( mEmulator = = null ) return true ;
2021-03-06 13:25:10 +00:00
final int action = event . getAction ( ) ;
2016-06-27 23:03:03 +00:00
2021-03-06 13:25:10 +00:00
if ( isSelectingText ( ) ) {
updateFloatingToolbarVisibility ( event ) ;
mGestureRecognizer . onTouchEvent ( event ) ;
2016-06-27 23:03:03 +00:00
return true ;
2021-03-06 13:25:10 +00:00
} else if ( event . isFromSource ( InputDevice . SOURCE_MOUSE ) ) {
if ( event . isButtonPressed ( MotionEvent . BUTTON_SECONDARY ) ) {
2016-06-27 23:03:03 +00:00
if ( action = = MotionEvent . ACTION_DOWN ) showContextMenu ( ) ;
return true ;
2021-03-06 13:25:10 +00:00
} else if ( event . isButtonPressed ( MotionEvent . BUTTON_TERTIARY ) ) {
2016-06-27 23:03:03 +00:00
ClipboardManager clipboard = ( ClipboardManager ) getContext ( ) . getSystemService ( Context . CLIPBOARD_SERVICE ) ;
ClipData clipData = clipboard . getPrimaryClip ( ) ;
if ( clipData ! = null ) {
CharSequence paste = clipData . getItemAt ( 0 ) . coerceToText ( getContext ( ) ) ;
if ( ! TextUtils . isEmpty ( paste ) ) mEmulator . paste ( paste . toString ( ) ) ;
}
} else if ( mEmulator . isMouseTrackingActive ( ) ) { // BUTTON_PRIMARY.
2021-03-06 13:25:10 +00:00
switch ( event . getAction ( ) ) {
2016-06-27 23:03:03 +00:00
case MotionEvent . ACTION_DOWN :
case MotionEvent . ACTION_UP :
2021-03-06 13:25:10 +00:00
sendMouseEventCode ( event , TerminalEmulator . MOUSE_LEFT_BUTTON , event . getAction ( ) = = MotionEvent . ACTION_DOWN ) ;
2016-06-27 23:03:03 +00:00
break ;
case MotionEvent . ACTION_MOVE :
2021-03-06 13:25:10 +00:00
sendMouseEventCode ( event , TerminalEmulator . MOUSE_LEFT_BUTTON_MOVED , true ) ;
2016-06-27 23:03:03 +00:00
break ;
}
}
}
2021-03-06 13:25:10 +00:00
mGestureRecognizer . onTouchEvent ( event ) ;
2016-06-27 23:03:03 +00:00
return true ;
}
@Override
public boolean onKeyPreIme ( int keyCode , KeyEvent event ) {
2021-03-13 11:49:29 +00:00
if ( TERMINAL_VIEW_KEY_LOGGING_ENABLED )
mClient . logInfo ( LOG_TAG , " onKeyPreIme(keyCode= " + keyCode + " , event= " + event + " ) " ) ;
2016-06-27 23:03:03 +00:00
if ( keyCode = = KeyEvent . KEYCODE_BACK ) {
2021-03-06 13:25:10 +00:00
if ( isSelectingText ( ) ) {
2019-10-05 10:05:42 +00:00
stopTextSelectionMode ( ) ;
2016-06-27 23:03:03 +00:00
return true ;
2017-04-02 12:25:34 +00:00
} else if ( mClient . shouldBackButtonBeMappedToEscape ( ) ) {
2016-06-27 23:03:03 +00:00
// Intercept back button to treat it as escape:
switch ( event . getAction ( ) ) {
case KeyEvent . ACTION_DOWN :
return onKeyDown ( keyCode , event ) ;
case KeyEvent . ACTION_UP :
return onKeyUp ( keyCode , event ) ;
}
}
2021-03-11 16:06:42 +00:00
} else if ( mClient . shouldUseCtrlSpaceWorkaround ( ) & &
2020-07-07 16:04:47 +00:00
keyCode = = KeyEvent . KEYCODE_SPACE & & event . isCtrlPressed ( ) ) {
2021-03-11 16:06:42 +00:00
/ * ctrl + space does not work on some ROMs without this workaround .
2020-07-07 16:04:47 +00:00
However , this breaks it on devices where it works out of the box . * /
return onKeyDown ( keyCode , event ) ;
2016-06-27 23:03:03 +00:00
}
return super . onKeyPreIme ( keyCode , event ) ;
}
2021-08-26 01:01:25 +00:00
/ * *
* Key presses in software keyboards will generally NOT trigger this listener , although some
* may elect to do so in some situations . Do not rely on this to catch software key presses .
* Gboard calls this when shouldEnforceCharBasedInput ( ) is disabled ( InputType . TYPE_NULL ) instead
* of calling commitText ( ) , with deviceId = - 1 . However , Hacker ' s Keyboard , OpenBoard , LG Keyboard
* call commitText ( ) .
*
* This function may also be called directly without android calling it , like by
* ` TerminalExtraKeys ` which generates a KeyEvent manually which uses { @link KeyCharacterMap # VIRTUAL_KEYBOARD }
* as the device ( deviceId = - 1 ) , as does Gboard . That would normally use mappings defined in
* ` / system / usr / keychars / Virtual . kcm ` . You can run ` dumpsys input ` to find the ` KeyCharacterMapFile `
* used by virtual keyboard or hardware keyboard . Note that virtual keyboard device is not the
* same as software keyboard , like Gboard , etc . Its a fake device used for generating events and
* for testing .
*
* We handle shift key in ` commitText ( ) ` to convert codepoint to uppercase case there with a
* call to { @link Character # toUpperCase ( int ) } , but here we instead rely on getUnicodeChar ( ) for
* conversion of keyCode , for both hardware keyboard shift key ( via effectiveMetaState ) and
* ` mClient . readShiftKey ( ) ` , based on value in kcm files .
* This may result in different behaviour depending on keyboard and android kcm files set for the
* InputDevice for the event passed to this function . This will likely be an issue for non - english
* languages since ` Virtual . kcm ` in english only by default or at least in AOSP . For both hardware
* shift key ( via effectiveMetaState ) and ` mClient . readShiftKey ( ) ` , ` getUnicodeChar ( ) ` is used
* for shift specific behaviour which usually is to uppercase .
*
* For fn key on hardware keyboard , android checks kcm files for hardware keyboards , which is
* ` Generic . kcm ` by default , unless a vendor specific one is defined . The event passed will have
* { @link KeyEvent # META_FUNCTION_ON } set . If the kcm file only defines a single character or unicode
* code point ` \ \ uxxxx ` , then only one event is passed with that value . However , if kcm defines
* a ` fallback ` key for fn or others , like ` key DPAD_UP { . . . fn : fallback PAGE_UP } ` , then
* android will first pass an event with original key ` DPAD_UP ` and { @link KeyEvent # META_FUNCTION_ON }
* set . But this function will not consume it and android will pass another event with ` PAGE_UP `
* and { @link KeyEvent # META_FUNCTION_ON } not set , which will be consumed .
*
* Now there are some other issues as well , firstly ctrl and alt flags are not passed to
* ` getUnicodeChar ( ) ` , so modified key values in kcm are not used . Secondly , if the kcm file
* for other modifiers like shift or fn define a non - alphabet , like { fn : '\u0015' } to act as
* DPAD_LEFT , the ` getUnicodeChar ( ) ` will correctly return ` 21 ` as the code point but action will
* not happen because the ` handleKeyCode ( ) ` function that transforms DPAD_LEFT to ` \ 033 [ D `
* escape sequence for the terminal to perform the left action would not be called since its
* called before ` getUnicodeChar ( ) ` and terminal will instead get ` 21 0x15 Negative Acknowledgement ` .
* The solution to such issues is calling ` getUnicodeChar ( ) ` before the call to ` handleKeyCode ( ) `
* if user has defined a custom kcm file , like done in POC mentioned in # 2237 . Note that
* Hacker ' s Keyboard calls ` commitText ( ) ` so don ' t test fn / shift with it for this function .
* https : //github.com/termux/termux-app/pull/2237
* https : //github.com/agnostic-apollo/termux-app/blob/terminal-code-point-custom-mapping/terminal-view/src/main/java/com/termux/view/TerminalView.java
*
* Key Character Map ( kcm ) and Key Layout ( kl ) files info :
* https : //source.android.com/devices/input/key-character-map-files
* https : //source.android.com/devices/input/key-layout-files
* https : //source.android.com/devices/input/keyboard-devices
* AOSP kcm and kl files :
* https : //cs.android.com/android/platform/superproject/+/android-11.0.0_r40:frameworks/base/data/keyboards
* https : //cs.android.com/android/platform/superproject/+/android-11.0.0_r40:frameworks/base/packages/InputDevices/res/raw
*
* KeyCodes :
* https : //cs.android.com/android/platform/superproject/+/android-11.0.0_r40:frameworks/base/core/java/android/view/KeyEvent.java
* https : //cs.android.com/android/platform/superproject/+/master:frameworks/native/include/android/keycodes.h
*
* ` dumpsys input ` :
* https : //cs.android.com/android/platform/superproject/+/android-11.0.0_r40:frameworks/native/services/inputflinger/reader/EventHub.cpp;l=1917
*
* Loading of keymap :
* https : //cs.android.com/android/platform/superproject/+/android-11.0.0_r40:frameworks/native/services/inputflinger/reader/EventHub.cpp;l=1644
* https : //cs.android.com/android/platform/superproject/+/android-11.0.0_r40:frameworks/native/libs/input/Keyboard.cpp;l=41
* https : //cs.android.com/android/platform/superproject/+/android-11.0.0_r40:frameworks/native/libs/input/InputDevice.cpp
* OVERLAY keymaps for hardware keyboards may be combined as well :
* https : //cs.android.com/android/platform/superproject/+/android-11.0.0_r40:frameworks/native/libs/input/KeyCharacterMap.cpp;l=165
* https : //cs.android.com/android/platform/superproject/+/android-11.0.0_r40:frameworks/native/libs/input/KeyCharacterMap.cpp;l=831
*
* Parse kcm file :
* https : //cs.android.com/android/platform/superproject/+/android-11.0.0_r40:frameworks/native/libs/input/KeyCharacterMap.cpp;l=727
* Parse key value :
* https : //cs.android.com/android/platform/superproject/+/android-11.0.0_r40:frameworks/native/libs/input/KeyCharacterMap.cpp;l=981
*
* ` KeyEvent . getUnicodeChar ( ) `
* https : //cs.android.com/android/platform/superproject/+/android-11.0.0_r40:frameworks/base/core/java/android/view/KeyEvent.java;l=2716
* https : //cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/view/KeyCharacterMap.java;l=368
* https : //cs.android.com/android/platform/superproject/+/android-11.0.0_r40:frameworks/base/core/jni/android_view_KeyCharacterMap.cpp;l=117
* https : //cs.android.com/android/platform/superproject/+/android-11.0.0_r40:frameworks/native/libs/input/KeyCharacterMap.cpp;l=231
*
* Keyboard layouts advertised by applications , like for hardware keyboards via # ACTION_QUERY_KEYBOARD_LAYOUTS
* Config is stored in ` / data / system / input - manager - state . xml `
* https : //github.com/ris58h/custom-keyboard-layout
* Loading from apps :
* https : //cs.android.com/android/platform/superproject/+/master:frameworks/base/services/core/java/com/android/server/input/InputManagerService.java;l=1221
* Set :
* https : //cs.android.com/android/platform/superproject/+/android-11.0.0_r40:frameworks/base/core/java/android/hardware/input/InputManager.java;l=89
* https : //cs.android.com/android/platform/superproject/+/android-11.0.0_r40:frameworks/base/core/java/android/hardware/input/InputManager.java;l=543
* https : //cs.android.com/android/platform/superproject/+/android-11.0.0_r40:packages/apps/Settings/src/com/android/settings/inputmethod/KeyboardLayoutDialogFragment.java;l=167
* https : //cs.android.com/android/platform/superproject/+/master:frameworks/base/services/core/java/com/android/server/input/InputManagerService.java;l=1385
* https : //cs.android.com/android/platform/superproject/+/master:frameworks/base/services/core/java/com/android/server/input/PersistentDataStore.java
* Get overlay keyboard layout
* https : //cs.android.com/android/platform/superproject/+/master:frameworks/base/services/core/java/com/android/server/input/InputManagerService.java;l=2158
* https : //cs.android.com/android/platform/superproject/+/android-11.0.0_r40:frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp;l=616
* /
2016-06-27 23:03:03 +00:00
@Override
public boolean onKeyDown ( int keyCode , KeyEvent event ) {
2021-03-13 11:49:29 +00:00
if ( TERMINAL_VIEW_KEY_LOGGING_ENABLED )
mClient . logInfo ( LOG_TAG , " onKeyDown(keyCode= " + keyCode + " , isSystem()= " + event . isSystem ( ) + " , event= " + event + " ) " ) ;
2016-06-27 23:03:03 +00:00
if ( mEmulator = = null ) return true ;
2021-03-06 13:25:10 +00:00
if ( isSelectingText ( ) ) {
stopTextSelectionMode ( ) ;
}
2016-06-27 23:03:03 +00:00
2017-04-02 12:25:34 +00:00
if ( mClient . onKeyDown ( keyCode , event , mTermSession ) ) {
2016-06-27 23:03:03 +00:00
invalidate ( ) ;
return true ;
2017-04-02 12:25:34 +00:00
} else if ( event . isSystem ( ) & & ( ! mClient . shouldBackButtonBeMappedToEscape ( ) | | keyCode ! = KeyEvent . KEYCODE_BACK ) ) {
2016-06-27 23:03:03 +00:00
return super . onKeyDown ( keyCode , event ) ;
} else if ( event . getAction ( ) = = KeyEvent . ACTION_MULTIPLE & & keyCode = = KeyEvent . KEYCODE_UNKNOWN ) {
2016-06-27 22:56:30 +00:00
mTermSession . write ( event . getCharacters ( ) ) ;
return true ;
}
2015-10-25 14:27:32 +00:00
2016-06-27 22:56:30 +00:00
final int metaState = event . getMetaState ( ) ;
Improvements to extra keys (#1479)
* Make popup keys for extra keys row configurable
This makes the keys you get when swiping up on a key configurable. You
can configure such a key by using an array of strings instead of a
single string in the row. The first entry will be the normal key and the
second will be the extra key.
This is a slightly breaking change, as people that have configured
custom extra keys with "-" or "/" will have to change the config to keep
the popup keys. The default config will remain the same in terms of
functionality, i.e. it includes the same popup key for "-".
* Make popup keys interact well with long press keys
This stops the repeat action when the popup is shown, and makes sure the
popup is closed when you release even if there has been some repeat
actions.
* Support configuring the style of the extra keys
This adds a setting for choosing between the different ways to render
key names that were already present in ExtraKeysView.
The available setting values are "arrows-only", "arrows-all", "all",
"none" and "default". Other values will fallback to "default".
Can be used as a workaround for #1410
* Support using modifier keys with letter keys in extra keys
This allows you to use the modifier keys on the extra keys rows, e.g.
ctrl, together with another button on the extra keys rows, as long as
that button is a normal letter and not a special key. Support for
special keys will come in the next commit.
* Support using modifier keys with special keys in extra keys
This allows you to use the modifier keys on the extra keys rows together
with a special key on the extra keys rows, e.g. CTRL+LEFT.
Fixes #745, fixes most of #895 and possibly #154
* Support mapping extra keys to other actions
This adds a setting called extra-keys-map which allows you to map a key
on the extra keys rows to another action. The value is a json object
where the key is the button text as configured in extra-keys and the
value is the action. Multiple actions can be used, but if they are
special characters (like ESC or LEFT) they have to be separated from the
other characters with a space on each side. If you want an actual space
character, use SPACE.
For example if you want to add a key to go to the next active channel in
weechat, you can use this:
extra-keys-map = {"weechat next": "ESC a"}
And then add "weechat next" to extra-keys. The name can of course be
whatever you want.
Or if you want the button for the UP arrow to show ⇧ instead of ↑, you
can use this:
extra-keys-map = {"⇧": "UP"}
And put "⇧" in extra-keys instead of "UP".
Modifier keys (ctrl, alt and shift) can't be used in this map yet.
Support for ctrl and alt will come in the next commit.
I think this fixes #1186
* Support CTRL and ALT in extra keys map
This allows you to use CTRL and ALT in extra-keys-map.
For example if you want a button to exit the terminal, you can use this:
extra-keys-map = {"exit": "CTRL d"}
And add "exit" to extra-keys.
* Support a KEYBOARD button in extra keys
This toggles showing the keyboard input method.
* Support specifying macro keys in the extra-keys option
Instead of specifying macros in the separate extra-keys-map option by
matching the key name in the two options, you can now use "macro"
instead of "key" in extra-keys, and it will be a macro, i.e. a sequence
of multiple keys separated by space.
* Remove option extra-keys-map
Now that you can specify macro in extra-keys, there is no point in
having this separate option. Instead of specifying the value to display
as key, and the macro to perform in extra-keys-map, you would now
specify the value to display in the display property and the macro to
perform in the macro property.
* Lookup display text when creating ExtraKeyButton
This will make it easier to support key aliases for macros in the next
commit.
* Add support for a key to open the drawer
Fixes (I think) #1325
2020-06-09 09:17:07 +00:00
final boolean controlDown = event . isCtrlPressed ( ) | | mClient . readControlKey ( ) ;
final boolean leftAltDown = ( metaState & KeyEvent . META_ALT_LEFT_ON ) ! = 0 | | mClient . readAltKey ( ) ;
2021-08-23 03:51:30 +00:00
final boolean shiftDown = event . isShiftPressed ( ) | | mClient . readShiftKey ( ) ;
2016-06-27 22:56:30 +00:00
final boolean rightAltDownFromEvent = ( metaState & KeyEvent . META_ALT_RIGHT_ON ) ! = 0 ;
int keyMod = 0 ;
Improvements to extra keys (#1479)
* Make popup keys for extra keys row configurable
This makes the keys you get when swiping up on a key configurable. You
can configure such a key by using an array of strings instead of a
single string in the row. The first entry will be the normal key and the
second will be the extra key.
This is a slightly breaking change, as people that have configured
custom extra keys with "-" or "/" will have to change the config to keep
the popup keys. The default config will remain the same in terms of
functionality, i.e. it includes the same popup key for "-".
* Make popup keys interact well with long press keys
This stops the repeat action when the popup is shown, and makes sure the
popup is closed when you release even if there has been some repeat
actions.
* Support configuring the style of the extra keys
This adds a setting for choosing between the different ways to render
key names that were already present in ExtraKeysView.
The available setting values are "arrows-only", "arrows-all", "all",
"none" and "default". Other values will fallback to "default".
Can be used as a workaround for #1410
* Support using modifier keys with letter keys in extra keys
This allows you to use the modifier keys on the extra keys rows, e.g.
ctrl, together with another button on the extra keys rows, as long as
that button is a normal letter and not a special key. Support for
special keys will come in the next commit.
* Support using modifier keys with special keys in extra keys
This allows you to use the modifier keys on the extra keys rows together
with a special key on the extra keys rows, e.g. CTRL+LEFT.
Fixes #745, fixes most of #895 and possibly #154
* Support mapping extra keys to other actions
This adds a setting called extra-keys-map which allows you to map a key
on the extra keys rows to another action. The value is a json object
where the key is the button text as configured in extra-keys and the
value is the action. Multiple actions can be used, but if they are
special characters (like ESC or LEFT) they have to be separated from the
other characters with a space on each side. If you want an actual space
character, use SPACE.
For example if you want to add a key to go to the next active channel in
weechat, you can use this:
extra-keys-map = {"weechat next": "ESC a"}
And then add "weechat next" to extra-keys. The name can of course be
whatever you want.
Or if you want the button for the UP arrow to show ⇧ instead of ↑, you
can use this:
extra-keys-map = {"⇧": "UP"}
And put "⇧" in extra-keys instead of "UP".
Modifier keys (ctrl, alt and shift) can't be used in this map yet.
Support for ctrl and alt will come in the next commit.
I think this fixes #1186
* Support CTRL and ALT in extra keys map
This allows you to use CTRL and ALT in extra-keys-map.
For example if you want a button to exit the terminal, you can use this:
extra-keys-map = {"exit": "CTRL d"}
And add "exit" to extra-keys.
* Support a KEYBOARD button in extra keys
This toggles showing the keyboard input method.
* Support specifying macro keys in the extra-keys option
Instead of specifying macros in the separate extra-keys-map option by
matching the key name in the two options, you can now use "macro"
instead of "key" in extra-keys, and it will be a macro, i.e. a sequence
of multiple keys separated by space.
* Remove option extra-keys-map
Now that you can specify macro in extra-keys, there is no point in
having this separate option. Instead of specifying the value to display
as key, and the macro to perform in extra-keys-map, you would now
specify the value to display in the display property and the macro to
perform in the macro property.
* Lookup display text when creating ExtraKeyButton
This will make it easier to support key aliases for macros in the next
commit.
* Add support for a key to open the drawer
Fixes (I think) #1325
2020-06-09 09:17:07 +00:00
if ( controlDown ) keyMod | = KeyHandler . KEYMOD_CTRL ;
if ( event . isAltPressed ( ) | | leftAltDown ) keyMod | = KeyHandler . KEYMOD_ALT ;
2021-08-23 03:51:30 +00:00
if ( shiftDown ) keyMod | = KeyHandler . KEYMOD_SHIFT ;
2021-01-17 03:22:01 +00:00
if ( event . isNumLockOn ( ) ) keyMod | = KeyHandler . KEYMOD_NUM_LOCK ;
2021-08-23 03:56:36 +00:00
// https://github.com/termux/termux-app/issues/731
2021-08-26 01:01:25 +00:00
if ( ! event . isFunctionPressed ( ) & & handleKeyCode ( keyCode , keyMod ) ) {
2021-03-13 11:49:29 +00:00
if ( TERMINAL_VIEW_KEY_LOGGING_ENABLED ) mClient . logInfo ( LOG_TAG , " handleKeyCode() took key event " ) ;
2016-06-27 23:03:03 +00:00
return true ;
}
// Clear Ctrl since we handle that ourselves:
int bitsToClear = KeyEvent . META_CTRL_MASK ;
if ( rightAltDownFromEvent ) {
// Let right Alt/Alt Gr be used to compose characters.
} else {
// Use left alt to send to terminal (e.g. Left Alt+B to jump back a word), so remove:
bitsToClear | = KeyEvent . META_ALT_ON | KeyEvent . META_ALT_LEFT_ON ;
}
int effectiveMetaState = event . getMetaState ( ) & ~ bitsToClear ;
2021-08-23 03:51:30 +00:00
if ( shiftDown ) effectiveMetaState | = KeyEvent . META_SHIFT_ON | KeyEvent . META_SHIFT_LEFT_ON ;
2021-08-26 01:01:25 +00:00
if ( mClient . readFnKey ( ) ) effectiveMetaState | = KeyEvent . META_FUNCTION_ON ;
2021-08-23 03:51:30 +00:00
2016-06-27 23:03:03 +00:00
int result = event . getUnicodeChar ( effectiveMetaState ) ;
2021-03-13 11:49:29 +00:00
if ( TERMINAL_VIEW_KEY_LOGGING_ENABLED )
mClient . logInfo ( LOG_TAG , " KeyEvent#getUnicodeChar( " + effectiveMetaState + " ) returned: " + result ) ;
2016-06-27 23:03:03 +00:00
if ( result = = 0 ) {
2018-07-31 16:09:12 +00:00
return false ;
2016-06-27 23:03:03 +00:00
}
int oldCombiningAccent = mCombiningAccent ;
if ( ( result & KeyCharacterMap . COMBINING_ACCENT ) ! = 0 ) {
// If entered combining accent previously, write it out:
if ( mCombiningAccent ! = 0 )
2022-05-29 17:44:57 +00:00
inputCodePoint ( event . getDeviceId ( ) , mCombiningAccent , controlDown , leftAltDown ) ;
2016-06-27 23:03:03 +00:00
mCombiningAccent = result & KeyCharacterMap . COMBINING_ACCENT_MASK ;
} else {
if ( mCombiningAccent ! = 0 ) {
int combinedChar = KeyCharacterMap . getDeadChar ( mCombiningAccent , result ) ;
if ( combinedChar > 0 ) result = combinedChar ;
mCombiningAccent = 0 ;
}
2022-05-29 17:44:57 +00:00
inputCodePoint ( event . getDeviceId ( ) , result , controlDown , leftAltDown ) ;
2016-06-27 23:03:03 +00:00
}
if ( mCombiningAccent ! = oldCombiningAccent ) invalidate ( ) ;
return true ;
}
2022-05-29 17:44:57 +00:00
public void inputCodePoint ( int eventSource , int codePoint , boolean controlDownFromEvent , boolean leftAltDownFromEvent ) {
2021-03-13 11:49:29 +00:00
if ( TERMINAL_VIEW_KEY_LOGGING_ENABLED ) {
2022-05-29 17:44:57 +00:00
mClient . logInfo ( LOG_TAG , " inputCodePoint(eventSource= " + eventSource + " , codePoint= " + codePoint + " , controlDownFromEvent= " + controlDownFromEvent + " , leftAltDownFromEvent= "
2016-06-27 23:03:03 +00:00
+ leftAltDownFromEvent + " ) " ) ;
}
2015-10-25 14:27:32 +00:00
2017-11-25 23:50:13 +00:00
if ( mTermSession = = null ) return ;
2021-08-26 01:03:23 +00:00
// Ensure cursor is shown when a key is pressed down like long hold on (arrow) keys
if ( mEmulator ! = null )
mEmulator . setCursorBlinkState ( true ) ;
2017-04-02 12:25:34 +00:00
final boolean controlDown = controlDownFromEvent | | mClient . readControlKey ( ) ;
final boolean altDown = leftAltDownFromEvent | | mClient . readAltKey ( ) ;
2016-05-20 08:41:07 +00:00
2017-04-02 12:25:34 +00:00
if ( mClient . onCodePoint ( codePoint , controlDown , mTermSession ) ) return ;
2016-06-27 22:56:30 +00:00
if ( controlDown ) {
2016-06-27 23:03:03 +00:00
if ( codePoint > = 'a' & & codePoint < = 'z' ) {
codePoint = codePoint - 'a' + 1 ;
} else if ( codePoint > = 'A' & & codePoint < = 'Z' ) {
codePoint = codePoint - 'A' + 1 ;
} else if ( codePoint = = ' ' | | codePoint = = '2' ) {
codePoint = 0 ;
} else if ( codePoint = = '[' | | codePoint = = '3' ) {
codePoint = 27 ; // ^[ (Esc)
} else if ( codePoint = = '\\' | | codePoint = = '4' ) {
codePoint = 28 ;
} else if ( codePoint = = ']' | | codePoint = = '5' ) {
codePoint = 29 ;
} else if ( codePoint = = '^' | | codePoint = = '6' ) {
codePoint = 30 ; // control-^
2016-07-26 22:26:50 +00:00
} else if ( codePoint = = '_' | | codePoint = = '7' | | codePoint = = '/' ) {
// "Ctrl-/ sends 0x1f which is equivalent of Ctrl-_ since the days of VT102"
// - http://apple.stackexchange.com/questions/24261/how-do-i-send-c-that-is-control-slash-to-the-terminal
2016-06-27 23:03:03 +00:00
codePoint = 31 ;
} else if ( codePoint = = '8' ) {
codePoint = 127 ; // DEL
}
}
2016-04-22 00:20:10 +00:00
2016-06-27 22:56:30 +00:00
if ( codePoint > - 1 ) {
2022-05-29 17:44:57 +00:00
// If not virtual or soft keyboard.
if ( eventSource > KEY_EVENT_SOURCE_SOFT_KEYBOARD ) {
// Work around bluetooth keyboards sending funny unicode characters instead
// of the more normal ones from ASCII that terminal programs expect - the
// desire to input the original characters should be low.
switch ( codePoint ) {
case 0x02DC : // SMALL TILDE.
codePoint = 0x007E ; // TILDE (~).
break ;
case 0x02CB : // MODIFIER LETTER GRAVE ACCENT.
codePoint = 0x0060 ; // GRAVE ACCENT (`).
break ;
case 0x02C6 : // MODIFIER LETTER CIRCUMFLEX ACCENT.
codePoint = 0x005E ; // CIRCUMFLEX ACCENT (^).
break ;
}
2016-04-22 00:20:10 +00:00
}
2015-10-25 14:27:32 +00:00
2016-06-27 22:56:30 +00:00
// If left alt, send escape before the code point to make e.g. Alt+B and Alt+F work in readline:
mTermSession . writeCodePoint ( altDown , codePoint ) ;
}
}
2015-10-25 14:27:32 +00:00
2016-06-27 23:03:03 +00:00
/** Input the specified keyCode if applicable and return if the input was consumed. */
public boolean handleKeyCode ( int keyCode , int keyMod ) {
2021-05-16 14:08:11 +00:00
// Ensure cursor is shown when a key is pressed down like long hold on (arrow) keys
if ( mEmulator ! = null )
mEmulator . setCursorBlinkState ( true ) ;
2016-06-27 23:03:03 +00:00
TerminalEmulator term = mTermSession . getEmulator ( ) ;
String code = KeyHandler . getCode ( keyCode , keyMod , term . isCursorKeysApplicationMode ( ) , term . isKeypadApplicationMode ( ) ) ;
if ( code = = null ) return false ;
mTermSession . write ( code ) ;
return true ;
}
/ * *
* Called when a key is released in the view .
*
* @param keyCode The keycode of the key which was released .
* @param event A { @link KeyEvent } describing the event .
* @return Whether the event was handled .
* /
@Override
public boolean onKeyUp ( int keyCode , KeyEvent event ) {
2021-03-13 11:49:29 +00:00
if ( TERMINAL_VIEW_KEY_LOGGING_ENABLED )
mClient . logInfo ( LOG_TAG , " onKeyUp(keyCode= " + keyCode + " , event= " + event + " ) " ) ;
2021-06-28 04:26:42 +00:00
// Do not return for KEYCODE_BACK and send it to the client since user may be trying
// to exit the activity.
if ( mEmulator = = null & & keyCode ! = KeyEvent . KEYCODE_BACK ) return true ;
2016-06-27 23:03:03 +00:00
2017-04-02 12:25:34 +00:00
if ( mClient . onKeyUp ( keyCode , event ) ) {
2016-06-27 23:03:03 +00:00
invalidate ( ) ;
return true ;
} else if ( event . isSystem ( ) ) {
// Let system key events through.
return super . onKeyUp ( keyCode , event ) ;
}
return true ;
}
/ * *
* 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 .
* /
@Override
protected void onSizeChanged ( int w , int h , int oldw , int oldh ) {
updateSize ( ) ;
}
/** Check if the terminal size in rows and columns should be updated. */
public void updateSize ( ) {
int viewWidth = getWidth ( ) ;
int viewHeight = getHeight ( ) ;
if ( viewWidth = = 0 | | viewHeight = = 0 | | mTermSession = = null ) return ;
// Set to 80 and 24 if you want to enable vttest.
int newColumns = Math . max ( 4 , ( int ) ( viewWidth / mRenderer . mFontWidth ) ) ;
int newRows = Math . max ( 4 , ( viewHeight - mRenderer . mFontLineSpacingAndAscent ) / mRenderer . mFontLineSpacing ) ;
if ( mEmulator = = null | | ( newColumns ! = mEmulator . mColumns | | newRows ! = mEmulator . mRows ) ) {
mTermSession . updateSize ( newColumns , newRows ) ;
mEmulator = mTermSession . getEmulator ( ) ;
2021-06-10 03:03:12 +00:00
mClient . onEmulatorSet ( ) ;
2016-06-27 23:03:03 +00:00
2021-06-28 06:57:12 +00:00
// Update mTerminalCursorBlinkerRunnable inner class mEmulator on session change
if ( mTerminalCursorBlinkerRunnable ! = null )
mTerminalCursorBlinkerRunnable . setEmulator ( mEmulator ) ;
2016-06-27 23:03:03 +00:00
mTopRow = 0 ;
scrollTo ( 0 , 0 ) ;
invalidate ( ) ;
}
}
@Override
protected void onDraw ( Canvas canvas ) {
if ( mEmulator = = null ) {
canvas . drawColor ( 0XFF000000 ) ;
} else {
2021-03-06 13:25:10 +00:00
// render the terminal view and highlight any selected text
int [ ] sel = mDefaultSelectors ;
if ( mTextSelectionCursorController ! = null ) {
mTextSelectionCursorController . getSelectors ( sel ) ;
2016-06-27 23:03:03 +00:00
}
2021-05-15 11:35:54 +00:00
2021-03-06 13:25:10 +00:00
mRenderer . render ( mEmulator , canvas , mTopRow , sel [ 0 ] , sel [ 1 ] , sel [ 2 ] , sel [ 3 ] ) ;
2016-06-27 23:03:03 +00:00
2021-03-06 13:25:10 +00:00
// render the text selection handles
renderTextSelection ( ) ;
2019-10-05 10:05:42 +00:00
}
}
2016-06-27 23:03:03 +00:00
2019-10-05 10:05:42 +00:00
public TerminalSession getCurrentSession ( ) {
return mTermSession ;
}
2022-06-16 13:31:08 +00:00
public CharSequence getText ( ) {
2019-10-05 10:05:42 +00:00
return mEmulator . getScreen ( ) . getSelectedText ( 0 , mTopRow , mEmulator . mColumns , mTopRow + mEmulator . mRows ) ;
}
2021-03-06 13:25:10 +00:00
public int getCursorX ( float x ) {
2019-10-05 10:05:42 +00:00
return ( int ) ( x / mRenderer . mFontWidth ) ;
}
2021-03-06 13:25:10 +00:00
public int getCursorY ( float y ) {
2019-10-05 10:05:42 +00:00
return ( int ) ( ( ( y - 40 ) / mRenderer . mFontLineSpacing ) + mTopRow ) ;
}
2021-03-06 13:25:10 +00:00
public int getPointX ( int cx ) {
2019-10-05 10:05:42 +00:00
if ( cx > mEmulator . mColumns ) {
cx = mEmulator . mColumns ;
}
return Math . round ( cx * mRenderer . mFontWidth ) ;
}
2021-03-06 13:25:10 +00:00
public int getPointY ( int cy ) {
2019-10-05 10:05:42 +00:00
return Math . round ( ( cy - mTopRow ) * mRenderer . mFontLineSpacing ) ;
}
2021-03-06 13:25:10 +00:00
public int getTopRow ( ) {
return mTopRow ;
2019-10-05 10:05:42 +00:00
}
2021-03-06 13:25:10 +00:00
public void setTopRow ( int mTopRow ) {
this . mTopRow = mTopRow ;
2019-10-05 10:05:42 +00:00
}
2016-07-31 22:30:11 +00:00
2021-03-06 13:25:10 +00:00
/ * *
* Define functions required for AutoFill API
* /
@RequiresApi ( api = Build . VERSION_CODES . O )
@Override
public void autofill ( AutofillValue value ) {
if ( value . isText ( ) ) {
mTermSession . write ( value . getTextValue ( ) . toString ( ) ) ;
2019-10-05 10:05:42 +00:00
}
2021-03-06 13:25:10 +00:00
}
2019-10-05 10:05:42 +00:00
2021-03-06 13:25:10 +00:00
@RequiresApi ( api = Build . VERSION_CODES . O )
@Override
public int getAutofillType ( ) {
return AUTOFILL_TYPE_TEXT ;
}
2016-06-27 23:03:03 +00:00
2021-03-06 13:25:10 +00:00
@RequiresApi ( api = Build . VERSION_CODES . O )
@Override
public AutofillValue getAutofillValue ( ) {
return AutofillValue . forText ( " " ) ;
}
2019-10-05 10:05:42 +00:00
2019-12-18 15:50:23 +00:00
2021-05-15 11:35:54 +00:00
/ * *
* Set terminal cursor blinker rate . It must be between { @link # TERMINAL_CURSOR_BLINK_RATE_MIN }
2021-05-16 14:08:11 +00:00
* and { @link # TERMINAL_CURSOR_BLINK_RATE_MAX } , otherwise it will be disabled .
*
* The { @link # setTerminalCursorBlinkerState ( boolean , boolean ) } must be called after this
* for changes to take effect if not disabling .
2021-05-15 11:35:54 +00:00
*
* @param blinkRate The value to set .
2021-05-16 14:08:11 +00:00
* @return Returns { @code true } if setting blinker rate was successfully set , otherwise [ @code false } .
2021-05-15 11:35:54 +00:00
* /
2021-05-16 14:08:11 +00:00
public synchronized boolean setTerminalCursorBlinkerRate ( int blinkRate ) {
boolean result ;
// If cursor blinking rate is not valid
if ( blinkRate ! = 0 & & ( blinkRate < TERMINAL_CURSOR_BLINK_RATE_MIN | | blinkRate > TERMINAL_CURSOR_BLINK_RATE_MAX ) ) {
mClient . logError ( LOG_TAG , " The cursor blink rate must be in between " + TERMINAL_CURSOR_BLINK_RATE_MIN + " - " + TERMINAL_CURSOR_BLINK_RATE_MAX + " : " + blinkRate ) ;
mTerminalCursorBlinkerRate = 0 ;
result = false ;
} else {
mClient . logVerbose ( LOG_TAG , " Setting cursor blinker rate to " + blinkRate ) ;
mTerminalCursorBlinkerRate = blinkRate ;
result = true ;
}
if ( mTerminalCursorBlinkerRate = = 0 ) {
mClient . logVerbose ( LOG_TAG , " Cursor blinker disabled " ) ;
stopTerminalCursorBlinker ( ) ;
}
return result ;
2021-05-15 11:35:54 +00:00
}
/ * *
2021-05-16 14:08:11 +00:00
* Sets whether cursor blinker should be started or stopped . Cursor blinker will only be
2021-05-15 11:35:54 +00:00
* started if { @link # mTerminalCursorBlinkerRate } does not equal 0 and is between
* { @link # TERMINAL_CURSOR_BLINK_RATE_MIN } and { @link # TERMINAL_CURSOR_BLINK_RATE_MAX } .
*
2021-05-16 14:08:11 +00:00
* This should be called when the view holding this activity is resumed or stopped so that
2021-06-20 00:57:57 +00:00
* cursor blinker does not run when activity is not visible . If you call this on onResume ( )
* to start cursor blinking , then ensure that { @link # mEmulator } is set , otherwise wait for the
* { @link TerminalViewClient # onEmulatorSet ( ) } event after calling { @link # attachSession ( TerminalSession ) }
* for the first session added in the activity since blinking will not start if { @link # mEmulator }
* is not set , like if activity is started again after exiting it with double back press . 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 ) } . Calling on onResume ( ) if emulator is already set
* is necessary , since onEmulatorSet ( ) may not be called after activity is started after device
* display timeout with double tap and not power button .
2021-05-16 14:08:11 +00:00
*
* It should also be called on the
* { @link com . termux . terminal . TerminalSessionClient # onTerminalCursorStateChange ( boolean ) }
* callback when cursor is enabled or disabled so that blinker is disabled if cursor is not
* to be shown . It should also be checked if activity is visible if blinker is to be started
* before calling this .
*
2021-06-28 07:19:06 +00:00
* It should also be called after terminal is reset with { @link TerminalSession # reset ( ) } in case
* cursor blinker was disabled before reset due to call to
* { @link com . termux . terminal . TerminalSessionClient # onTerminalCursorStateChange ( boolean ) } .
*
2021-05-16 14:08:11 +00:00
* How cursor blinker starting works is by registering a { @link Runnable } with the looper of
* the main thread of the app which when run , toggles the cursor blinking state and re - registers
* itself to be called with the delay set by { @link # mTerminalCursorBlinkerRate } . When cursor
* blinking needs to be disabled , we just cancel any callbacks registered . We don ' t run our own
* " thread " and let the thread for the main looper do the work for us , whose usage is also
* required to update the UI , since it also handles other calls to update the UI as well based
* on a queue .
*
* Note that when moving cursor in text editors like nano , the cursor state is quickly
* toggled ` - > off - > on ` , which would call this very quickly sequentially . So that if cursor
* is moved 2 or more times quickly , like long hold on arrow keys , it would trigger
* ` - > off - > on - > off - > on - > . . . ` , and the " on " callback at index 2 is automatically
* cancelled by next " off " callback at index 3 before getting a chance to be run . For this case
* we log only if { @link # TERMINAL_VIEW_KEY_LOGGING_ENABLED } is enabled , otherwise would clutter
* the log . We don ' t start the blinking with a delay to immediately show cursor in case it was
* previously not visible .
*
* @param start If cursor blinker should be started or stopped .
2021-05-15 11:35:54 +00:00
* @param startOnlyIfCursorEnabled If set to { @code true } , then it will also be checked if the
* cursor is even enabled by { @link TerminalEmulator } before
2021-05-16 14:08:11 +00:00
* starting the cursor blinker .
2021-05-15 11:35:54 +00:00
* /
public synchronized void setTerminalCursorBlinkerState ( boolean start , boolean startOnlyIfCursorEnabled ) {
2021-05-16 14:08:11 +00:00
// Stop any existing cursor blinker callbacks
stopTerminalCursorBlinker ( ) ;
2021-05-15 11:35:54 +00:00
if ( mEmulator = = null ) return ;
mEmulator . setCursorBlinkingEnabled ( false ) ;
if ( start ) {
2021-05-16 14:08:11 +00:00
// If cursor blinker is not enabled or is not valid
if ( mTerminalCursorBlinkerRate < TERMINAL_CURSOR_BLINK_RATE_MIN | | mTerminalCursorBlinkerRate > TERMINAL_CURSOR_BLINK_RATE_MAX )
2021-05-15 11:35:54 +00:00
return ;
2021-05-16 14:08:11 +00:00
// If cursor blinder is to be started only if cursor is enabled
2021-05-15 11:35:54 +00:00
else if ( startOnlyIfCursorEnabled & & ! mEmulator . isCursorEnabled ( ) ) {
2021-05-16 14:08:11 +00:00
if ( TERMINAL_VIEW_KEY_LOGGING_ENABLED )
mClient . logVerbose ( LOG_TAG , " Ignoring call to start cursor blinker since cursor is not enabled " ) ;
2021-05-15 11:35:54 +00:00
return ;
}
2021-05-16 14:08:11 +00:00
// Start cursor blinker runnable
if ( TERMINAL_VIEW_KEY_LOGGING_ENABLED )
mClient . logVerbose ( LOG_TAG , " Starting cursor blinker with the blink rate " + mTerminalCursorBlinkerRate ) ;
if ( mTerminalCursorBlinkerHandler = = null )
mTerminalCursorBlinkerHandler = new Handler ( Looper . getMainLooper ( ) ) ;
mTerminalCursorBlinkerRunnable = new TerminalCursorBlinkerRunnable ( mEmulator , mTerminalCursorBlinkerRate ) ;
2021-05-15 11:35:54 +00:00
mEmulator . setCursorBlinkingEnabled ( true ) ;
2021-05-16 14:08:11 +00:00
mTerminalCursorBlinkerRunnable . run ( ) ;
2021-05-15 11:35:54 +00:00
}
}
/ * *
2021-05-16 14:08:11 +00:00
* Cancel the terminal cursor blinker callbacks
2021-05-15 11:35:54 +00:00
* /
2021-05-16 14:08:11 +00:00
private void stopTerminalCursorBlinker ( ) {
if ( mTerminalCursorBlinkerHandler ! = null & & mTerminalCursorBlinkerRunnable ! = null ) {
if ( TERMINAL_VIEW_KEY_LOGGING_ENABLED )
mClient . logVerbose ( LOG_TAG , " Stopping cursor blinker " ) ;
mTerminalCursorBlinkerHandler . removeCallbacks ( mTerminalCursorBlinkerRunnable ) ;
2021-05-15 11:35:54 +00:00
}
}
2021-05-16 14:08:11 +00:00
private class TerminalCursorBlinkerRunnable implements Runnable {
2021-05-15 11:35:54 +00:00
2021-06-28 06:57:12 +00:00
private TerminalEmulator mEmulator ;
2021-05-16 14:08:11 +00:00
private final int mBlinkRate ;
// Initialize with false so that initial blink state is visible after toggling
boolean mCursorVisible = false ;
public TerminalCursorBlinkerRunnable ( TerminalEmulator emulator , int blinkRate ) {
mEmulator = emulator ;
2021-05-15 11:35:54 +00:00
mBlinkRate = blinkRate ;
}
2021-06-28 06:57:12 +00:00
public void setEmulator ( TerminalEmulator emulator ) {
mEmulator = emulator ;
}
2021-05-15 11:35:54 +00:00
public void run ( ) {
try {
if ( mEmulator ! = null ) {
// Toggle the blink state and then invalidate() the view so
// that onDraw() is called, which then calls TerminalRenderer.render()
// which checks with TerminalEmulator.shouldCursorBeVisible() to decide whether
// to draw the cursor or not
2021-05-16 14:08:11 +00:00
mCursorVisible = ! mCursorVisible ;
//mClient.logVerbose(LOG_TAG, "Toggling cursor blink state to " + mCursorVisible);
2021-05-15 11:35:54 +00:00
mEmulator . setCursorBlinkState ( mCursorVisible ) ;
2021-05-16 14:08:11 +00:00
invalidate ( ) ;
2021-05-15 11:35:54 +00:00
}
} finally {
// Recall the Runnable after mBlinkRate milliseconds to toggle the blink state
2021-05-16 14:08:11 +00:00
mTerminalCursorBlinkerHandler . postDelayed ( this , mBlinkRate ) ;
2021-05-15 11:35:54 +00:00
}
}
}
2019-12-18 15:50:23 +00:00
2021-03-06 13:25:10 +00:00
/ * *
* Define functions required for text selection and its handles .
* /
2021-09-14 19:06:16 +00:00
TextSelectionCursorController getTextSelectionCursorController ( ) {
2021-03-06 13:25:10 +00:00
if ( mTextSelectionCursorController = = null ) {
mTextSelectionCursorController = new TextSelectionCursorController ( this ) ;
2019-12-18 15:50:23 +00:00
2021-03-06 13:25:10 +00:00
final ViewTreeObserver observer = getViewTreeObserver ( ) ;
if ( observer ! = null ) {
observer . addOnTouchModeChangeListener ( mTextSelectionCursorController ) ;
2019-12-18 15:50:23 +00:00
}
}
2021-03-06 13:25:10 +00:00
return mTextSelectionCursorController ;
}
2019-10-05 10:05:42 +00:00
2021-03-06 13:25:10 +00:00
private void showTextSelectionCursors ( MotionEvent event ) {
getTextSelectionCursorController ( ) . show ( event ) ;
}
2019-10-05 10:05:42 +00:00
2021-03-06 13:25:10 +00:00
private boolean hideTextSelectionCursors ( ) {
return getTextSelectionCursorController ( ) . hide ( ) ;
}
2019-10-05 10:05:42 +00:00
2021-03-06 13:25:10 +00:00
private void renderTextSelection ( ) {
if ( mTextSelectionCursorController ! = null )
mTextSelectionCursorController . render ( ) ;
}
2019-10-05 10:05:42 +00:00
2021-03-06 13:25:10 +00:00
public boolean isSelectingText ( ) {
if ( mTextSelectionCursorController ! = null ) {
return mTextSelectionCursorController . isActive ( ) ;
} else {
2019-10-05 10:05:42 +00:00
return false ;
}
2021-03-06 13:25:10 +00:00
}
2019-10-05 10:05:42 +00:00
2021-03-06 13:25:10 +00:00
private ActionMode getTextSelectionActionMode ( ) {
if ( mTextSelectionCursorController ! = null ) {
return mTextSelectionCursorController . getActionMode ( ) ;
} else {
return null ;
2019-10-05 10:05:42 +00:00
}
2021-03-06 13:25:10 +00:00
}
2019-10-05 10:05:42 +00:00
2021-03-06 13:25:10 +00:00
public void startTextSelectionMode ( MotionEvent event ) {
if ( ! requestFocus ( ) ) {
return ;
2019-10-05 10:05:42 +00:00
}
2021-03-06 13:25:10 +00:00
showTextSelectionCursors ( event ) ;
mClient . copyModeChanged ( isSelectingText ( ) ) ;
2019-10-05 10:05:42 +00:00
2021-03-06 13:25:10 +00:00
invalidate ( ) ;
2019-10-05 10:05:42 +00:00
}
2021-03-06 13:25:10 +00:00
public void stopTextSelectionMode ( ) {
if ( hideTextSelectionCursors ( ) ) {
mClient . copyModeChanged ( isSelectingText ( ) ) ;
invalidate ( ) ;
2019-10-05 10:05:42 +00:00
}
}
2021-03-06 13:25:10 +00:00
private void decrementYTextSelectionCursors ( int decrement ) {
if ( mTextSelectionCursorController ! = null ) {
mTextSelectionCursorController . decrementYTextSelectionCursors ( decrement ) ;
2019-10-05 10:05:42 +00:00
}
}
2021-03-06 13:25:10 +00:00
@Override
protected void onAttachedToWindow ( ) {
super . onAttachedToWindow ( ) ;
2019-10-05 10:05:42 +00:00
2021-03-06 13:25:10 +00:00
if ( mTextSelectionCursorController ! = null ) {
getViewTreeObserver ( ) . addOnTouchModeChangeListener ( mTextSelectionCursorController ) ;
2019-10-05 10:05:42 +00:00
}
}
2021-03-06 13:25:10 +00:00
@Override
protected void onDetachedFromWindow ( ) {
super . onDetachedFromWindow ( ) ;
2019-10-05 10:05:42 +00:00
2021-03-06 13:25:10 +00:00
if ( mTextSelectionCursorController ! = null ) {
// Might solve the following exception
// android.view.WindowLeaked: Activity com.termux.app.TermuxActivity has leaked window android.widget.PopupWindow
stopTextSelectionMode ( ) ;
2019-10-05 10:05:42 +00:00
2021-03-06 13:25:10 +00:00
getViewTreeObserver ( ) . removeOnTouchModeChangeListener ( mTextSelectionCursorController ) ;
mTextSelectionCursorController . onDetached ( ) ;
2016-06-27 23:03:03 +00:00
}
}
2019-10-05 10:05:42 +00:00
2021-03-06 13:25:10 +00:00
/ * *
* Define functions required for long hold toolbar .
* /
2019-10-05 10:05:42 +00:00
private final Runnable mShowFloatingToolbar = new Runnable ( ) {
2022-04-25 21:31:21 +00:00
@RequiresApi ( api = Build . VERSION_CODES . M )
2019-10-05 10:05:42 +00:00
@Override
public void run ( ) {
2021-03-06 13:25:10 +00:00
if ( getTextSelectionActionMode ( ) ! = null ) {
getTextSelectionActionMode ( ) . hide ( 0 ) ; // hide off.
2019-10-05 10:05:42 +00:00
}
}
} ;
2022-04-25 21:31:21 +00:00
@RequiresApi ( api = Build . VERSION_CODES . M )
2019-10-05 10:05:42 +00:00
private void showFloatingToolbar ( ) {
2021-03-06 13:25:10 +00:00
if ( getTextSelectionActionMode ( ) ! = null ) {
2019-10-05 10:05:42 +00:00
int delay = ViewConfiguration . getDoubleTapTimeout ( ) ;
postDelayed ( mShowFloatingToolbar , delay ) ;
}
2017-06-20 18:55:41 +00:00
}
2022-04-25 21:31:21 +00:00
@RequiresApi ( api = Build . VERSION_CODES . M )
2021-03-06 13:25:10 +00:00
void hideFloatingToolbar ( ) {
if ( getTextSelectionActionMode ( ) ! = null ) {
removeCallbacks ( mShowFloatingToolbar ) ;
getTextSelectionActionMode ( ) . hide ( - 1 ) ;
}
}
public void updateFloatingToolbarVisibility ( MotionEvent event ) {
2022-04-25 21:31:21 +00:00
if ( Build . VERSION . SDK_INT > = Build . VERSION_CODES . M & & getTextSelectionActionMode ( ) ! = null ) {
2019-10-05 10:05:42 +00:00
switch ( event . getActionMasked ( ) ) {
case MotionEvent . ACTION_MOVE :
2021-03-06 13:25:10 +00:00
hideFloatingToolbar ( ) ;
2019-10-05 10:05:42 +00:00
break ;
case MotionEvent . ACTION_UP : // fall through
case MotionEvent . ACTION_CANCEL :
showFloatingToolbar ( ) ;
}
}
}
2020-07-21 10:28:48 +00:00
2015-10-25 14:27:32 +00:00
}