Skip to content

Commit

Permalink
Allow users to enable terminal cursor blinking with termux.properties
Browse files Browse the repository at this point in the history
This `terminal-cursor-blink-rate` key can be used to enable terminal cursor blinking. The user can set an int value between `100` and `2000` which will be used as blink rate in millisecond. The default value is `0`, which disables cursor blinking. So adding an entry like `terminal-cursor-blink-rate=600` to `~/termux.properties` file will make the cursor attempt to blink every 600ms. Running `termux-reload-settings` command will also update the cursor blinking rate instantaneously if changed.

A background thread is used to control the blinking by toggling the cursor visibility and then invalidating the view every x milliseconds set. This will have a performance impact, so use wisely and at your own risk.

If the cursor itself is disabled, which is controlled by whether DECSET_BIT_CURSOR_ENABLED (DECSET 25, DECTCEM), then blinking will be automatically disabled. You can enable the cursor with `tput cnorm` or `echo -e '\e[?25h'` and disable it with `tput civis` or `echo -e '\e[?25l'`.

Note that you can also change the cursor color by adding `cursor` property to `~/colors.properties` file, like `cursor=#FFFFFF` for a white cursor.

The `TermuxPropertyConstants` class has been updated to `v0.9.0`. Check its Changelog sections for info on changes.

Closes #153
  • Loading branch information
agnostic-apollo committed May 15, 2021
1 parent 11f5c0a commit 31298b8
Show file tree
Hide file tree
Showing 11 changed files with 254 additions and 24 deletions.
9 changes: 9 additions & 0 deletions app/src/main/java/com/termux/app/TermuxActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,9 @@ public void onResume() {
if (mIsInvalidState) return;

mTermuxTerminalViewClient.setSoftKeyboardState(true, false);

// Start terminal cursor blinking if enabled
mTermuxTerminalViewClient.setTerminalCursorBlinkerState(true);
}

/**
Expand Down Expand Up @@ -330,6 +333,9 @@ protected void onStop() {
// {@link #onStart} if needed.
mTermuxTerminalSessionClient.setCurrentStoredSession();

// Stop terminal cursor blinking if enabled
mTermuxTerminalViewClient.setTerminalCursorBlinkerState(false);

unregisterTermuxActivityBroadcastReceiever();
getDrawer().closeDrawers();
}
Expand Down Expand Up @@ -799,6 +805,9 @@ private void reloadTermuxActivityStyling() {

mTermuxTerminalViewClient.setSoftKeyboardState(false, true);

mTermuxTerminalViewClient.setTerminalCursorBlinkerState(true);


// To change the activity and drawer theme, activity needs to be recreated.
// But this will destroy the activity, and will call the onCreate() again.
// We need to investigate if enabling this is wise, since all stored variables and
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,19 @@ public void onColorsChanged(TerminalSession changedSession) {
updateBackgroundColor();
}

@Override
public void onTerminalCursorStateChange(boolean enabled) {
// Do not start cursor blinking thread if activity is not visible
if (enabled && !mActivity.isVisible()) {
Logger.logVerbose(LOG_TAG, "Ignoring call to start cursor blinking since activity is not visible");
return;
}

// If cursor is to enabled now, then start cursor blinking if blinking is enabled
// otherwise stop cursor blinking
mActivity.getTerminalView().setTerminalCursorBlinkerState(enabled, false);
}



/** Try switching to session. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,18 @@ public void onFocusChange(View view, boolean hasFocus) {



public void setTerminalCursorBlinkerState(boolean start) {
if (start) {
// Set/Update the cursor blinking rate
mActivity.getTerminalView().setTerminalCursorBlinkerRate(mActivity.getProperties().getTerminalCursorBlinkRate());
}

// Set the new state of cursor blinker
mActivity.getTerminalView().setTerminalCursorBlinkerState(start, true);
}



public void shareSessionTranscript() {
TerminalSession session = mActivity.getCurrentSession();
if (session == null) return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,8 @@ public final class TerminalEmulator {
* characters received when the cursor is at the right border of the page replace characters already on the page."
*/
private static final int DECSET_BIT_AUTOWRAP = 1 << 3;
/** DECSET 25 - if the cursor should be visible, {@link #isShowingCursor()}. */
private static final int DECSET_BIT_SHOWING_CURSOR = 1 << 4;
/** DECSET 25 - if the cursor should be enabled, {@link #isCursorEnabled()}. */
private static final int DECSET_BIT_CURSOR_ENABLED = 1 << 4;
private static final int DECSET_BIT_APPLICATION_KEYPAD = 1 << 5;
/** DECSET 1000 - if to report mouse press&release events. */
private static final int DECSET_BIT_MOUSE_TRACKING_PRESS_RELEASE = 1 << 6;
Expand Down Expand Up @@ -205,6 +205,18 @@ public final class TerminalEmulator {
*/
private boolean mAboutToAutoWrap;

/**
* If the cursor blinking is enabled. It requires cursor itself to be enabled, which is controlled
* byt whether {@link #DECSET_BIT_CURSOR_ENABLED} bit is set or not.
*/
private boolean mCursorBlinkingEnabled;

/**
* If currently cursor should be in a visible state or not if {@link #mCursorBlinkingEnabled}
* is {@code true}.
*/
private boolean mCursorBlinkState;

/**
* Current foreground and background colors. Can either be a color index in [0,259] or a truecolor (24-bit) value.
* For a 24-bit value the top byte (0xff000000) is set.
Expand Down Expand Up @@ -261,7 +273,7 @@ static int mapDecSetBitToInternalBit(int decsetBit) {
case 7:
return DECSET_BIT_AUTOWRAP;
case 25:
return DECSET_BIT_SHOWING_CURSOR;
return DECSET_BIT_CURSOR_ENABLED;
case 66:
return DECSET_BIT_APPLICATION_KEYPAD;
case 69:
Expand Down Expand Up @@ -381,10 +393,28 @@ public boolean isReverseVideo() {
return isDecsetInternalBitSet(DECSET_BIT_REVERSE_VIDEO);
}

public boolean isShowingCursor() {
return isDecsetInternalBitSet(DECSET_BIT_SHOWING_CURSOR);


public boolean isCursorEnabled() {
return isDecsetInternalBitSet(DECSET_BIT_CURSOR_ENABLED);
}
public boolean shouldCursorBeVisible() {
if (!isCursorEnabled())
return false;
else
return mCursorBlinkingEnabled ? mCursorBlinkState : true;
}

public void setCursorBlinkingEnabled(boolean cursorBlinkingEnabled) {
this.mCursorBlinkingEnabled = cursorBlinkingEnabled;
}

public void setCursorBlinkState(boolean cursorBlinkState) {
this.mCursorBlinkState = cursorBlinkState;
}



public boolean isKeypadApplicationMode() {
return isDecsetInternalBitSet(DECSET_BIT_APPLICATION_KEYPAD);
}
Expand Down Expand Up @@ -1054,7 +1084,10 @@ public void doDecSetOrReset(boolean setting, int externalBit) {
case 8: // Auto-repeat Keys (DECARM). Do not implement.
case 9: // X10 mouse reporting - outdated. Do not implement.
case 12: // Control cursor blinking - ignore.
case 25: // Hide/show cursor - no action needed, renderer will check with isShowingCursor().
case 25: // Hide/show cursor - no action needed, renderer will check with shouldCursorBeVisible().
if (mClient != null)
mClient.onTerminalCursorStateChange(setting);
break;
case 40: // Allow 80 => 132 Mode, ignore.
case 45: // TODO: Reverse wrap-around. Implement???
case 66: // Application keypad (DECNKM).
Expand Down Expand Up @@ -2318,7 +2351,7 @@ public void reset() {
mCurrentDecSetFlags = 0;
// Initial wrap-around is not accurate but makes terminal more useful, especially on a small screen:
setDecsetinternalBit(DECSET_BIT_AUTOWRAP, true);
setDecsetinternalBit(DECSET_BIT_SHOWING_CURSOR, true);
setDecsetinternalBit(DECSET_BIT_CURSOR_ENABLED, true);
mSavedDecSetFlags = mSavedStateMain.mSavedDecFlags = mSavedStateAlt.mSavedDecFlags = mCurrentDecSetFlags;

// XXX: Should we set terminal driver back to IUTF8 with termios?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ public interface TerminalSessionClient {

void onColorsChanged(TerminalSession session);

void onTerminalCursorStateChange(boolean state);


void logError(String tag, String message);

Expand Down
24 changes: 12 additions & 12 deletions terminal-emulator/src/test/java/com/termux/terminal/DecSetTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,23 @@
public class DecSetTest extends TerminalTestCase {

/** DECSET 25, DECTCEM, controls visibility of the cursor. */
public void testShowHideCursor() {
public void testEnableDisableCursor() {
withTerminalSized(3, 3);
assertTrue("Initially the cursor should be visible", mTerminal.isShowingCursor());
enterString("\033[?25l"); // Hide Cursor (DECTCEM).
assertFalse(mTerminal.isShowingCursor());
enterString("\033[?25h"); // Show Cursor (DECTCEM).
assertTrue(mTerminal.isShowingCursor());
assertTrue("Initially the cursor should be enabled", mTerminal.isCursorEnabled());
enterString("\033[?25l"); // Disable Cursor (DECTCEM).
assertFalse(mTerminal.isCursorEnabled());
enterString("\033[?25h"); // Enable Cursor (DECTCEM).
assertTrue(mTerminal.isCursorEnabled());

enterString("\033[?25l"); // Hide Cursor (DECTCEM), again.
assertFalse(mTerminal.isShowingCursor());
enterString("\033[?25l"); // Disable Cursor (DECTCEM), again.
assertFalse(mTerminal.isCursorEnabled());
mTerminal.reset();
assertTrue("Resetting the terminal should show the cursor", mTerminal.isShowingCursor());
assertTrue("Resetting the terminal should enable the cursor", mTerminal.isCursorEnabled());

enterString("\033[?25l");
assertFalse(mTerminal.isShowingCursor());
enterString("\033c"); // RIS resetting should reveal cursor.
assertTrue(mTerminal.isShowingCursor());
assertFalse(mTerminal.isCursorEnabled());
enterString("\033c"); // RIS resetting should enabled cursor.
assertTrue(mTerminal.isCursorEnabled());
}

/** DECSET 2004, controls bracketed paste mode. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public final void render(TerminalEmulator mEmulator, Canvas canvas, int topRow,
final int columns = mEmulator.mColumns;
final int cursorCol = mEmulator.getCursorCol();
final int cursorRow = mEmulator.getCursorRow();
final boolean cursorVisible = mEmulator.isShowingCursor();
final boolean cursorVisible = mEmulator.shouldCursorBeVisible();
final TerminalBuffer screen = mEmulator.getScreen();
final int[] palette = mEmulator.mColors.mCurrentColors;
final int cursorShape = mEmulator.getCursorStyle();
Expand Down
113 changes: 110 additions & 3 deletions terminal-view/src/main/java/com/termux/view/TerminalView.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import android.graphics.Canvas;
import android.graphics.Typeface;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.text.Editable;
import android.text.InputType;
import android.text.TextUtils;
Expand Down Expand Up @@ -52,6 +54,14 @@ public final class TerminalView extends View {

private TextSelectionCursorController mTextSelectionCursorController;

private Handler mTerminalCursorBlinkerHandler;
private TerminalCursorBlinkerThread mTerminalCursorBlinkerThread;
private int mTerminalCursorBlinkerRate;
public static final int TERMINAL_CURSOR_BLINK_RATE_MIN = 100;
public static final int TERMINAL_CURSOR_BLINK_RATE_MAX = 2000;

private boolean mRendering;

/** The top row of text to display. Ranges from -activeTranscriptRows to 0. */
int mTopRow;
int[] mDefaultSelectors = new int[]{-1,-1,-1,-1};
Expand Down Expand Up @@ -209,6 +219,8 @@ public void onLongPress(MotionEvent event) {
mAccessibilityEnabled = am.isEnabled();
}



/**
* @param client The {@link TerminalViewClient} interface implementation to allow
* for communication between {@link TerminalView} and its client.
Expand All @@ -218,14 +230,16 @@ public void setTerminalViewClient(TerminalViewClient client) {
}

/**
* Sets terminal view key logging is enabled or not.
* Sets whether terminal view key logging is enabled or not.
*
* @param value The boolean value that defines the state.
*/
public void setIsTerminalViewKeyLoggingEnabled(boolean value) {
TERMINAL_VIEW_KEY_LOGGING_ENABLED = value;
}



/**
* Attach a {@link TerminalSession} to this view.
*
Expand Down Expand Up @@ -755,7 +769,10 @@ protected void onDraw(Canvas canvas) {
if (mTextSelectionCursorController != null) {
mTextSelectionCursorController.getSelectors(sel);
}

mRendering = true;
mRenderer.render(mEmulator, canvas, mTopRow, sel[0], sel[1], sel[2], sel[3]);
mRendering = false;

// render the text selection handles
renderTextSelection();
Expand Down Expand Up @@ -799,7 +816,6 @@ public void setTopRow(int mTopRow) {




/**
* Define functions required for AutoFill API
*/
Expand All @@ -825,6 +841,98 @@ public AutofillValue getAutofillValue() {



/**
* Set terminal cursor blinker rate. It must be between {@link #TERMINAL_CURSOR_BLINK_RATE_MIN}
* and {@link #TERMINAL_CURSOR_BLINK_RATE_MAX}.
*
* @param blinkRate The value to set.
*/
public void setTerminalCursorBlinkerRate(int blinkRate) {
mTerminalCursorBlinkerRate = blinkRate;
}

/**
* Sets whether cursor blinking should be started or stopped. Cursor blinking will only be
* started if {@link #mTerminalCursorBlinkerRate} does not equal 0 and is between
* {@link #TERMINAL_CURSOR_BLINK_RATE_MIN} and {@link #TERMINAL_CURSOR_BLINK_RATE_MAX}.
*
* @param start If cursor blinking should be started or stopped.
* @param startOnlyIfCursorEnabled If set to {@code true}, then it will also be checked if the
* cursor is even enabled by {@link TerminalEmulator} before
* starting the cursor blinking thread.
*/
public synchronized void setTerminalCursorBlinkerState(boolean start, boolean startOnlyIfCursorEnabled) {
// Stop any existing cursor blinker threads
stopTerminalCursorBlinkerThread();

if (mEmulator == null) return;

mEmulator.setCursorBlinkingEnabled(false);

if (start) {
// If cursor blinking is not enabled
if (mTerminalCursorBlinkerRate == 0) {
mClient.logVerbose(LOG_TAG, "Cursor blinking is not enabled");
return;
}
// If cursor blinking rate is not valid
else if (mTerminalCursorBlinkerRate < TERMINAL_CURSOR_BLINK_RATE_MIN || mTerminalCursorBlinkerRate > TERMINAL_CURSOR_BLINK_RATE_MAX) {
mClient.logError(LOG_TAG, "startCursorBlinkerThread: The cursor blink rate must be in between " + TERMINAL_CURSOR_BLINK_RATE_MIN + "-" + TERMINAL_CURSOR_BLINK_RATE_MAX + ": " + mTerminalCursorBlinkerRate);
return;
}
// If cursor is not enabled
else if (startOnlyIfCursorEnabled && ! mEmulator.isCursorEnabled()) {
mClient.logVerbose(LOG_TAG, "Ignoring call to start cursor blinking since cursor is not enabled");
return;
}

// Start cursor blinker thread
mClient.logVerbose(LOG_TAG, "Starting cursor blinker thread with the blink rate: " + mTerminalCursorBlinkerRate);
if (mTerminalCursorBlinkerHandler == null) mTerminalCursorBlinkerHandler = new Handler(Looper.getMainLooper());
mTerminalCursorBlinkerThread = new TerminalCursorBlinkerThread(mTerminalCursorBlinkerRate);
mEmulator.setCursorBlinkingEnabled(true);
mTerminalCursorBlinkerThread.run();
}
}

/**
* Stops the terminal cursor blinker thread
*/
private void stopTerminalCursorBlinkerThread() {
if (mTerminalCursorBlinkerHandler != null && mTerminalCursorBlinkerThread != null) {
mClient.logVerbose(LOG_TAG, "Stopping cursor blinker thread");
mTerminalCursorBlinkerHandler.removeCallbacks(mTerminalCursorBlinkerThread);
}
}

private class TerminalCursorBlinkerThread implements Runnable {
int mBlinkRate;
boolean mCursorVisible;

public TerminalCursorBlinkerThread(int blinkRate) {
mBlinkRate = blinkRate;
}

public void run() {
try {
if (mEmulator != null) {
mCursorVisible = !mCursorVisible;
// 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
mEmulator.setCursorBlinkState(mCursorVisible);
if (!mRendering)
invalidate();
}
} finally {
// Recall the Runnable after mBlinkRate milliseconds to toggle the blink state
mTerminalCursorBlinkerHandler.postDelayed(mTerminalCursorBlinkerThread, mBlinkRate);
}
}
}



/**
* Define functions required for text selection and its handles.
Expand Down Expand Up @@ -920,7 +1028,6 @@ protected void onDetachedFromWindow() {




/**
* Define functions required for long hold toolbar.
*/
Expand Down
Loading

0 comments on commit 31298b8

Please sign in to comment.