This commit is contained in:
Fredrik Fornwall 2023-10-16 01:43:19 +02:00
parent 7979b975de
commit 49955f2c50
No known key found for this signature in database
8 changed files with 193 additions and 145 deletions

View File

@ -12,14 +12,14 @@
android:required="false" />
<permission
android:name="${TERMUX_PACKAGE_NAME}.permission.RUN_COMMAND"
android:name="com.termux.permission.RUN_COMMAND"
android:description="@string/permission_run_command_description"
android:icon="@mipmap/ic_launcher"
android:label="@string/permission_run_command_label"
android:protectionLevel="dangerous" />
<permission
android:name="com.termux.permission.internal"
android:name="com.termux.permission.TERMUX_INTERNAL"
android:description="@string/permission_run_command_description"
android:icon="@mipmap/ic_launcher"
android:label="@string/permission_run_command_label"
@ -70,14 +70,15 @@
</activity>
<activity-alias
android:name=".app.TermuxActivityInternal"
android:targetActivity=".app.TermuxActivity"/>
android:targetActivity=".app.TermuxActivity"
android:exported="true"
android:permission="com.termux.permission.TERMUX_INTERNAL" />
<activity
android:name=".app.TermuxHelpActivity"
android:exported="false"
android:parentActivityName=".app.TermuxActivity"
android:resizeableActivity="true"
tools:targetApi="n" />
android:resizeableActivity="true" />
<activity
android:name=".app.TermuxFileReceiverActivity"

View File

@ -44,6 +44,7 @@ import androidx.viewpager.widget.ViewPager;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
@ -80,8 +81,6 @@ public final class TermuxActivity extends AppCompatActivity implements ServiceCo
private static final String ARG_TERMINAL_TOOLBAR_TEXT_INPUT = "terminal_toolbar_text_input";
private static final String ARG_ACTIVITY_RECREATED = "activity_recreated";
private static final String LOG_TAG = "TermuxActivity";
/**
* The connection to the {@link TermuxService}. Requested in {@link #onCreate(Bundle)} with a call to
* {@link #bindService(Intent, ServiceConnection, int)}, and obtained and stored in
@ -104,7 +103,7 @@ public final class TermuxActivity extends AppCompatActivity implements ServiceCo
* The {@link TerminalSessionClient} interface implementation to allow for communication between
* {@link TerminalSession} and {@link TermuxActivity}.
*/
TermuxTerminalSessionActivityClient mTermuxTerminalSessionActivityClient;
final TermuxTerminalSessionActivityClient mTermuxTerminalSessionActivityClient = new TermuxTerminalSessionActivityClient(this);
/**
* The terminal extra keys view.
@ -132,7 +131,7 @@ public final class TermuxActivity extends AppCompatActivity implements ServiceCo
Toast mLastToast;
/**
* If between onResume() and onStop(). Note that only one session is in the foreground of the terminal view at the
* If between onStart() and onStop(). Note that only one session is in the foreground of the terminal view at the
* time, so if the session causing a change is not in the foreground it should probably be treated as background.
*/
private boolean mIsVisible;
@ -156,7 +155,6 @@ public final class TermuxActivity extends AppCompatActivity implements ServiceCo
setContentView(R.layout.activity_termux);
mTermuxTerminalSessionActivityClient = new TermuxTerminalSessionActivityClient(this);
mTermuxTerminalViewClient = new TermuxTerminalViewClient(this, mTermuxTerminalSessionActivityClient);
mTerminalView = findViewById(R.id.terminal_view);
@ -208,38 +206,26 @@ public final class TermuxActivity extends AppCompatActivity implements ServiceCo
@Override
public void onStart() {
super.onStart();
mIsVisible = true;
mTermuxTerminalSessionActivityClient.onStart();
if (mTermuxTerminalViewClient != null) {
mTermuxTerminalViewClient.onStart();
}
registerTermuxActivityBroadcastReceiver();
}
@Override
public void onResume() {
super.onResume();
if (mTermuxTerminalSessionActivityClient != null) {
mTermuxTerminalSessionActivityClient.onResume();
}
mTermuxTerminalSessionActivityClient.onResume();
}
@Override
protected void onStop() {
super.onStop();
mIsVisible = false;
if (mTermuxTerminalSessionActivityClient != null) {
mTermuxTerminalSessionActivityClient.onStop();
}
mTermuxTerminalSessionActivityClient.onStop();
unregisterReceiver(mTermuxActivityBroadcastReceiver);
getDrawer().closeDrawers();
}
@ -267,6 +253,42 @@ public final class TermuxActivity extends AppCompatActivity implements ServiceCo
savedInstanceState.putBoolean(ARG_ACTIVITY_RECREATED, true);
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
Log.e("termux", "NEW INTENT: " + intent.getAction());
try {
if ("com.termux.app.NEW_STYLE".equals(intent.getAction())) {
byte[] newFont = intent.getByteArrayExtra("com.termux.app.extra.NEW_FONT");
if (newFont != null) {
File fontFile = new File(TermuxConstants.FONT_PATH);
if (newFont.length == 0) {
fontFile.delete();
} else {
try (FileOutputStream fos = new FileOutputStream(fontFile)) {
fos.write(newFont);
}
}
}
byte[] newColors = intent.getByteArrayExtra("com.termux.app.extra.NEW_COLORS");
if (newColors != null) {
File colorsFile = new File(TermuxConstants.COLORS_PATH);
if (newColors.length == 0) {
colorsFile.delete();
} else {
try (FileOutputStream fos = new FileOutputStream(colorsFile)) {
fos.write(newColors);
}
}
}
mTermuxTerminalSessionActivityClient.onReloadActivityStyling();
}
} catch (Exception e) {
Log.e(TermuxConstants.LOG_TAG, "Error handling new intent", e);
TermuxMessageDialogUtils.showToast(this, "Error handling new intent: " + e.getMessage());
}
}
/**
* Part of the {@link ServiceConnection} interface. The service is bound with
* {@link #bindService(Intent, ServiceConnection, int)} in {@link #onCreate(Bundle)} which will cause a call to this
@ -516,6 +538,7 @@ public final class TermuxActivity extends AppCompatActivity implements ServiceCo
try {
startActivity(stylingIntent);
} catch (ActivityNotFoundException | IllegalArgumentException e) {
Log.e(TermuxConstants.LOG_TAG, "Error starting Termux:Style", e);
// The startActivity() call is not documented to throw IllegalArgumentException.
// However, crash reporting shows that it sometimes does, so catch it here.
String installationUrl = isInstalledFromGooglePlay()
@ -612,7 +635,6 @@ public final class TermuxActivity extends AppCompatActivity implements ServiceCo
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(ACTION_RELOAD_STYLE);
intentFilter.addAction(ACTION_REQUEST_PERMISSIONS);
if (Build.VERSION.SDK_INT >= 33) {
registerReceiver(mTermuxActivityBroadcastReceiver, intentFilter, Context.RECEIVER_NOT_EXPORTED);
} else {
@ -623,6 +645,7 @@ public final class TermuxActivity extends AppCompatActivity implements ServiceCo
class TermuxActivityBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Log.e("termux", "onReceive. visible=" + mIsVisible + ", action=" + intent.getAction());
if (mIsVisible) {
switch (intent.getAction()) {
case ACTION_RELOAD_STYLE:
@ -644,7 +667,6 @@ public final class TermuxActivity extends AppCompatActivity implements ServiceCo
setTerminalToolbarHeight();
if (mTermuxTerminalSessionActivityClient != null)
mTermuxTerminalSessionActivityClient.onReloadActivityStyling();
mTermuxTerminalSessionActivityClient.onReloadActivityStyling();
}
}

View File

@ -1,5 +1,7 @@
package com.termux.app;
import java.io.File;
public class TermuxConstants {
public static final String LOG_TAG = "termux";
@ -9,6 +11,9 @@ public class TermuxConstants {
public static final String BIN_PATH = PREFIX_PATH + "/bin";
public static final String HOME_PATH = FILES_PATH + "/home";
public static final String FONT_PATH = TermuxConstants.HOME_PATH + "/.termux/font.ttf";
public static final String COLORS_PATH = TermuxConstants.HOME_PATH + "/.termux/colors.properties";
public static final int TERMUX_APP_NOTIFICATION_ID = 1337;
}

View File

@ -39,7 +39,7 @@ public class TermuxPreferences {
}
public String getCurrentSession() {
return prefs.getString(PREF_KEEP_SCREEN_ON, null);
return prefs.getString(PREF_CURRENT_SESSION, null);
}
public void setShowTerminalToolbar(boolean newValue) {

View File

@ -33,7 +33,8 @@ public class TermuxProperties {
}
boolean isBackKeyTheEscapeKey() {
return properties.getProperty("back-key", "escape").equalsIgnoreCase("escape");
Log.e("termux", "isBackThe='" + properties.getProperty("back-key", "back"));
return properties.getProperty("back-key", "back").equalsIgnoreCase("escape");
}
boolean isEnforcingCharBasedInput() {
@ -41,7 +42,7 @@ public class TermuxProperties {
}
boolean areVirtualVolumeKeysDisabled() {
return false; // TODO
return properties.getProperty("volume-keys", "normal").equalsIgnoreCase("volume");
}
public String getExtraKeys() {

View File

@ -113,8 +113,6 @@ public final class TermuxTerminalSessionActivityClient implements TerminalSessio
private int mBellSoundId;
private static final String LOG_TAG = "TermuxTerminalSessionActivityClient";
public TermuxTerminalSessionActivityClient(TermuxActivity activity) {
this.mActivity = activity;
}
@ -124,7 +122,7 @@ public final class TermuxTerminalSessionActivityClient implements TerminalSessio
*/
public void onCreate() {
// Set terminal fonts and colors
checkForFontAndColors();
onReloadActivityStyling();
}
/**
@ -160,7 +158,8 @@ public final class TermuxTerminalSessionActivityClient implements TerminalSessio
public void onStop() {
// Store current session in shared preferences so that it can be restored later in
// {@link #onStart} if needed.
setCurrentStoredSession();
TerminalSession currentSession = mActivity.getCurrentSession();
mActivity.mPreferences.setCurrentSession(currentSession == null ? null : currentSession.mHandle);
// Release mBellSoundPool resources, specially to prevent exceptions like the following to be thrown
// java.util.concurrent.TimeoutException: android.media.SoundPool.finalize() timed out after 10 seconds
@ -173,8 +172,29 @@ public final class TermuxTerminalSessionActivityClient implements TerminalSessio
* Should be called when mActivity.reloadActivityStyling() is called
*/
public void onReloadActivityStyling() {
// Set terminal fonts and colors
checkForFontAndColors();
try {
File fontFile = new File(TermuxConstants.FONT_PATH);
File colorsFile = new File(TermuxConstants.COLORS_PATH);
final Properties props = new Properties();
if (colorsFile.isFile()) {
try (InputStream in = new FileInputStream(colorsFile)) {
props.load(in);
}
}
TerminalColors.COLOR_SCHEME.updateWith(props);
TerminalSession session = mActivity.getCurrentSession();
if (session != null && session.getEmulator() != null) {
session.getEmulator().mColors.reset();
}
updateBackgroundColor();
final Typeface newTypeface = (fontFile.exists() && fontFile.length() > 0) ? Typeface.createFromFile(fontFile) : Typeface.MONOSPACE;
mActivity.getTerminalView().setTypeface(newTypeface);
} catch (Exception e) {
Log.e(TermuxConstants.LOG_TAG, "Error in onReloadActivityStyling()", e);
}
}
@Override
@ -287,7 +307,7 @@ public final class TermuxTerminalSessionActivityClient implements TerminalSessio
mBellSoundId = mBellSoundPool.load(mActivity, com.termux.R.raw.bell, 1);
} catch (Exception e) {
// Catch java.lang.RuntimeException: Unable to resume activity {com.termux/com.termux.app.TermuxActivity}: android.content.res.Resources$NotFoundException: File res/raw/bell.ogg from drawable resource ID
Log.e(LOG_TAG, "Failed to load bell sound pool", e);
Log.e(TermuxConstants.LOG_TAG, "Failed to load bell sound pool", e);
}
}
}
@ -392,25 +412,15 @@ public final class TermuxTerminalSessionActivityClient implements TerminalSessio
}
}
public void setCurrentStoredSession() {
TerminalSession currentSession = mActivity.getCurrentSession();
mActivity.mPreferences.setCurrentSession(currentSession == null ? null : currentSession.mHandle);
;
}
/**
* The current session as stored or the last one if that does not exist.
*/
public TerminalSession getCurrentStoredSessionOrLast() {
String currentSessionHandle = mActivity.mPreferences.getCurrentSession();
if (currentSessionHandle == null) {
return null;
}
// Check if the session handle found matches one of the currently running sessions
TermuxService service = mActivity.getTermuxService();
if (service == null) return null;
String currentSessionHandle = mActivity.mPreferences.getCurrentSession();
TerminalSession currentSession = service.getTerminalSessionForHandle(currentSessionHandle);
if (currentSession == null) {
@ -482,33 +492,6 @@ public final class TermuxTerminalSessionActivityClient implements TerminalSessio
return toastTitle.toString();
}
public void checkForFontAndColors() {
try {
File fontFile = new File(TermuxConstants.HOME_PATH + "/.termux/font.ttf");
File colorsFile = new File(TermuxConstants.HOME_PATH + "/.termux/colors.properties");
final Properties props = new Properties();
if (colorsFile.isFile()) {
try (InputStream in = new FileInputStream(colorsFile)) {
props.load(in);
}
}
TerminalColors.COLOR_SCHEME.updateWith(props);
TerminalSession session = mActivity.getCurrentSession();
if (session != null && session.getEmulator() != null) {
session.getEmulator().mColors.reset();
}
updateBackgroundColor();
final Typeface newTypeface = (fontFile.exists() && fontFile.length() > 0) ? Typeface.createFromFile(fontFile) : Typeface.MONOSPACE;
mActivity.getTerminalView().setTypeface(newTypeface);
} catch (Exception e) {
Log.e(LOG_TAG, "Error in checkForFontAndColors()", e);
}
}
public void updateBackgroundColor() {
if (!mActivity.isVisible()) return;
TerminalSession session = mActivity.getCurrentSession();

View File

@ -44,16 +44,24 @@ import com.termux.terminal.TerminalEmulator;
import com.termux.terminal.TerminalSession;
import com.termux.view.textselection.TextSelectionCursorController;
/** View displaying and interacting with a {@link TerminalSession}. */
/**
* View displaying and interacting with a {@link TerminalSession}.
*/
public final class TerminalView extends View {
/** Log terminal view key and IME events. */
/**
* Log terminal view key and IME events.
*/
private static final boolean TERMINAL_VIEW_KEY_LOGGING_ENABLED = false;
/** The currently displayed terminal session, whose emulator is {@link #mEmulator}. */
/**
* The currently displayed terminal session, whose emulator is {@link #mEmulator}.
*/
public TerminalSession mTermSession;
/** Our terminal emulator whose session is {@link #mTermSession}. */
/**
* Our terminal emulator whose session is {@link #mTermSession}.
*/
public TerminalEmulator mEmulator;
public TerminalRenderer mRenderer;
@ -66,35 +74,47 @@ public final class TerminalView extends View {
private TextSelectionCursorController mTextSelectionCursorController;
/** 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[] mDefaultSelectors = new int[]{-1,-1,-1,-1};
float mScaleFactor = 1.f;
final GestureAndScaleRecognizer mGestureRecognizer;
/** Keep track of where mouse touch event started which we report as mouse scroll. */
/**
* 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. */
/**
* 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. */
/**
* 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. */
/**
* If non-zero, this is the last unicode code point received if that was a combining character.
*/
int mCombiningAccent;
private final boolean mAccessibilityEnabled;
/** The {@link KeyEvent} is generated from a virtual keyboard, like manually with the {@link KeyEvent#KeyEvent(int, int)} constructor. */
/**
* 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()}. */
/**
* 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;
private static final String LOG_TAG = "TerminalView";
@ -229,7 +249,7 @@ public final class TerminalView extends View {
/**
* @param client The {@link TerminalViewClient} interface implementation to allow
* for communication between {@link TerminalView} and its client.
* for communication between {@link TerminalView} and its client.
*/
public void setTerminalViewClient(TerminalViewClient client) {
this.mClient = client;
@ -284,7 +304,7 @@ public final class TerminalView extends View {
}
} else {
// Corresponds to android:inputType="text"
outAttrs.inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_NORMAL;
outAttrs.inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_NORMAL;
}
// Note that IME_ACTION_NONE cannot be used as that makes it impossible to input newlines using the on-screen
@ -451,9 +471,11 @@ public final class TerminalView extends View {
if (mAccessibilityEnabled) setContentDescription(getText());
}
/** This must be called by the hosting activity in {@link Activity#onContextMenuClosed(Menu)}
/**
* This must be called by the hosting activity in {@link Activity#onContextMenuClosed(Menu)}
* when context menu for the {@link TerminalView} is started by
* {@link TextSelectionCursorController#ACTION_MORE} is closed. */
* {@link TextSelectionCursorController#ACTION_MORE} is closed.
*/
public void onContextMenuClosed(Menu menu) {
// Unset the stored text since it shouldn't be used anymore and should be cleared from memory
unsetStoredSelectedText();
@ -489,11 +511,11 @@ public final class TerminalView extends View {
* 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 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.
* 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) {
@ -502,10 +524,12 @@ public final class TerminalView extends View {
if (relativeToScroll) {
row += mTopRow;
}
return new int[] { column, row };
return new int[]{column, row};
}
/** Send a single mouse event code to the terminal. */
/**
* Send a single mouse event code to the terminal.
*/
void sendMouseEventCode(MotionEvent e, int button, boolean pressed) {
int[] columnAndRow = getColumnAndRow(e, false);
int x = columnAndRow[0] + 1;
@ -523,7 +547,9 @@ public final class TerminalView extends View {
mEmulator.sendMouseEvent(button, x, y, pressed);
}
/** Perform a scroll, either from dragging the screen or by scrolling a mouse wheel. */
/**
* 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);
@ -541,7 +567,9 @@ public final class TerminalView extends View {
}
}
/** Overriding {@link View#onGenericMotionEvent(MotionEvent)}. */
/**
* Overriding {@link View#onGenericMotionEvent(MotionEvent)}.
*/
@Override
public boolean onGenericMotionEvent(MotionEvent event) {
if (mEmulator != null && event.isFromSource(InputDevice.SOURCE_MOUSE) && event.getAction() == MotionEvent.ACTION_SCROLL) {
@ -596,8 +624,9 @@ public final class TerminalView extends View {
@Override
public boolean onKeyPreIme(int keyCode, KeyEvent event) {
if (TERMINAL_VIEW_KEY_LOGGING_ENABLED)
if (TERMINAL_VIEW_KEY_LOGGING_ENABLED) {
Log.i(LOG_TAG, "onKeyPreIme(keyCode=" + keyCode + ", event=" + event + ")");
}
if (keyCode == KeyEvent.KEYCODE_BACK) {
if (isSelectingText()) {
stopTextSelectionMode();
@ -621,7 +650,7 @@ public final class TerminalView extends View {
* 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().
*
* <p>
* 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
@ -629,7 +658,7 @@ public final class TerminalView extends View {
* 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.
*
* <p>
* 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
@ -639,7 +668,7 @@ public final class TerminalView extends View {
* 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.
*
* <p>
* 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
@ -648,7 +677,7 @@ public final class TerminalView extends View {
* 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.
*
* <p>
* 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
@ -661,7 +690,7 @@ public final class TerminalView extends View {
* 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
*
* <p>
* 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
@ -669,14 +698,14 @@ public final class TerminalView extends View {
* 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
*
* <p>
* 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
*
* <p>
* `dumpsys input`:
* https://cs.android.com/android/platform/superproject/+/android-11.0.0_r40:frameworks/native/services/inputflinger/reader/EventHub.cpp;l=1917
*
* <p>
* 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
@ -684,18 +713,18 @@ public final class TerminalView extends View {
* 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
*
* <p>
* 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
*
* <p>
* `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
*
* <p>
* 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
@ -852,7 +881,9 @@ public final class TerminalView extends View {
}
}
/** Input the specified keyCode if applicable and return if the input was consumed. */
/**
* Input the specified keyCode if applicable and return if the input was consumed.
*/
public boolean handleKeyCode(int keyCode, int keyMod) {
// Ensure cursor is shown when a key is pressed down like long hold on (arrow) keys
if (mEmulator != null)
@ -885,7 +916,7 @@ public final class TerminalView extends View {
}
}
return false;
return false;
}
/**
@ -924,7 +955,9 @@ public final class TerminalView extends View {
updateSize();
}
/** Check if the terminal size in rows and columns should be updated. */
/**
* Check if the terminal size in rows and columns should be updated.
*/
public void updateSize() {
int viewWidth = getWidth();
int viewHeight = getHeight();
@ -978,17 +1011,32 @@ public final class TerminalView extends View {
protected void onDraw(Canvas canvas) {
if (mEmulator == null) {
canvas.drawColor(0XFF000000);
} else {
// render the terminal view and highlight any selected text
int[] sel = mDefaultSelectors;
if (mTextSelectionCursorController != null) {
mTextSelectionCursorController.getSelectors(sel);
}
return;
}
mRenderer.render(mEmulator, canvas, mTopRow, sel[0], sel[1], sel[2], sel[3]);
int selectionY1 = -1;
int selectionY2 = -1;
int selectionX1 = -1;
int selectionX2 = -1;
if (mTextSelectionCursorController != null) {
selectionY1 = mTextSelectionCursorController.mSelY1;
selectionY2 = mTextSelectionCursorController.mSelY2;
selectionX1 = mTextSelectionCursorController.mSelX1;
selectionX2 = mTextSelectionCursorController.mSelX2;
}
// render the text selection handles
renderTextSelection();
mRenderer.render(
mEmulator,
canvas,
mTopRow,
selectionY1,
selectionY2,
selectionX1,
selectionX2
);
if (mTextSelectionCursorController != null) {
mTextSelectionCursorController.render();
}
}
@ -1028,7 +1076,6 @@ public final class TerminalView extends View {
}
/**
* Define functions required for AutoFill API
*/
@ -1076,11 +1123,6 @@ public final class TerminalView extends View {
return getTextSelectionCursorController().hide();
}
private void renderTextSelection() {
if (mTextSelectionCursorController != null)
mTextSelectionCursorController.render();
}
public boolean isSelectingText() {
if (mTextSelectionCursorController != null) {
return mTextSelectionCursorController.isActive();
@ -1089,15 +1131,20 @@ public final class TerminalView extends View {
}
}
/** Get the selected text stored before "MORE" button was pressed on the context menu. */
/**
* Get the selected text stored before "MORE" button was pressed on the context menu.
*/
@Nullable
public String getStoredSelectedText() {
return mTextSelectionCursorController != null ? mTextSelectionCursorController.getStoredSelectedText() : null;
}
/** Unset the selected text stored before "MORE" button was pressed on the context menu. */
/**
* Unset the selected text stored before "MORE" button was pressed on the context menu.
*/
public void unsetStoredSelectedText() {
if (mTextSelectionCursorController != null) mTextSelectionCursorController.unsetStoredSelectedText();
if (mTextSelectionCursorController != null)
mTextSelectionCursorController.unsetStoredSelectedText();
}
private ActionMode getTextSelectionActionMode() {
@ -1156,7 +1203,6 @@ public final class TerminalView extends View {
}
/**
* Define functions required for long hold toolbar.
*/

View File

@ -27,7 +27,7 @@ public class TextSelectionCursorController implements CursorController {
private long mShowStartTime = System.currentTimeMillis();
private final int mHandleHeight;
private int mSelX1 = -1, mSelX2 = -1, mSelY1 = -1, mSelY2 = -1;
public int mSelX1 = -1, mSelX2 = -1, mSelY1 = -1, mSelY2 = -1;
private ActionMode mActionMode;
public final int ACTION_COPY = 1;
@ -353,16 +353,6 @@ public class TextSelectionCursorController implements CursorController {
return mIsSelectingText;
}
public void getSelectors(int[] sel) {
if (sel == null || sel.length != 4) {
return;
}
sel[0] = mSelY1;
sel[1] = mSelY2;
sel[2] = mSelX1;
sel[3] = mSelX2;
}
/** Get the currently selected text. */
public String getSelectedText() {