1
0
Fork 0

added support for Termux

This commit is contained in:
sspanak 2023-08-23 14:51:36 +03:00 committed by Dimo Karaivanov
parent e45744e0b4
commit 5314ed1320
4 changed files with 111 additions and 48 deletions

View file

@ -96,7 +96,7 @@ If your phone has a dedicated "Del" or "Clear" key, you do not need to set anyth
On phones which have a combined "Delete"/"Back", that key will be selected automatically. However, you can assign "Backspace" function to another key, so "Back" will only navigate back.
_**NB:** Using "Back" as backspace does not work in all applications, most notably Firefox and Spotify. They are able to take full control of the key and redefine its function, meaning it will do whatever the app authors intended. Unfortunately, nothing can be done, because "Back" plays a special role in Android and its usage is restricted by the system._
_**NB:** Using "Back" as backspace does not work in all applications, most notably Firefox, Spotify and Termux. They are able to take full control of the key and redefine its function, meaning it will do whatever the app authors intended. Unfortunately, nothing can be done, because "Back" plays a special role in Android and its usage is restricted by the system._
_**NB 2:** Holding "Back" key will always trigger the default system action (i.e. show running applications list)._

View file

@ -36,7 +36,7 @@ import io.github.sspanak.tt9.ui.tray.SuggestionsBar;
public class TraditionalT9 extends KeyPadHandler {
// internal settings/data
private boolean isActive = false;
@NonNull private AppHacks appHacks = new AppHacks(null, null);
@NonNull private AppHacks appHacks = new AppHacks(null,null, null, null);
@NonNull private TextField textField = new TextField(null, null);
@NonNull private InputType inputType = new InputType(null, null);
@NonNull private final Handler autoAcceptHandler = new Handler(Looper.getMainLooper());
@ -204,9 +204,9 @@ public class TraditionalT9 extends KeyPadHandler {
protected void onStart(EditorInfo input) {
inputType = new InputType(currentInputConnection, input);
textField = new TextField(currentInputConnection, input);
appHacks = new AppHacks(input, textField);
appHacks = new AppHacks(settings, currentInputConnection, input, textField);
if (!inputType.isValid() || inputType.isLimited()) {
if (!inputType.isValid() || (inputType.isLimited() && !appHacks.isTermux())) {
// When the input is invalid or simple, let Android handle it.
onStop();
return;
@ -245,14 +245,11 @@ public class TraditionalT9 extends KeyPadHandler {
public boolean onBackspace() {
if (appHacks.onBackspace(mInputMode)) {
return true;
}
// 1. Dialer fields seem to handle backspace on their own and we must ignore it,
// otherwise, keyDown race condition occur for all keys.
// 2. Allow the assigned key to function normally, when there is no text (e.g. "Back" navigates back)
if (mInputMode.isPassthrough() || !textField.isThereText()) {
// 3. Some app may need special treatment, so let it be.
if (mInputMode.isPassthrough() || !(textField.isThereText() || appHacks.onBackspace(mInputMode))) {
Logger.d("onBackspace", "backspace ignored");
mInputMode.reset();
return false;
@ -317,7 +314,8 @@ public class TraditionalT9 extends KeyPadHandler {
cancelAutoAccept();
if (isSuggestionViewHidden()) {
return performOKAction();
int action = textField.getAction();
return action == TextField.IME_ACTION_ENTER ? appHacks.onEnter() : textField.performAction(action);
}
acceptCurrentSuggestion(KeyEvent.KEYCODE_ENTER);
@ -741,41 +739,6 @@ public class TraditionalT9 extends KeyPadHandler {
}
private boolean performOKAction() {
if (currentInputConnection == null) {
return false;
}
int action = textField.getAction();
switch (action) {
case EditorInfo.IME_ACTION_NONE:
return false;
case TextField.IME_ACTION_ENTER:
String oldText = textField.getTextBeforeCursor() + textField.getTextAfterCursor();
sendDownUpKeyEvents(KeyEvent.KEYCODE_DPAD_CENTER);
try {
// In Android there is no strictly defined confirmation key, hence DPAD_CENTER may have done nothing.
// If so, send an alternative key code as a final resort.
Thread.sleep(80);
String newText = textField.getTextBeforeCursor() + textField.getTextAfterCursor();
if (newText.equals(oldText)) {
sendDownUpKeyEvents(KeyEvent.KEYCODE_ENTER);
}
} catch (InterruptedException e) {
// This thread got interrupted. Assume it's because the connected application has taken an action
// after receiving DPAD_CENTER, so we don't need to do anything else.
return true;
}
return true;
default:
return currentInputConnection.performEditorAction(action);
}
}
/**
* createSoftKeyView
* Generates the actual UI of TT9.

View file

@ -1,27 +1,51 @@
package io.github.sspanak.tt9.ime.helpers;
import android.view.KeyEvent;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import androidx.annotation.NonNull;
import io.github.sspanak.tt9.ime.modes.InputMode;
import io.github.sspanak.tt9.preferences.SettingsStore;
public class AppHacks {
private final EditorInfo editorInfo;
private final InputConnection inputConnection;
private final SettingsStore settings;
private final TextField textField;
public AppHacks(EditorInfo inputField, TextField textField) {
public AppHacks(SettingsStore settings, InputConnection inputConnection, EditorInfo inputField, TextField textField) {
this.editorInfo = inputField;
this.inputConnection = inputConnection;
this.settings = settings;
this.textField = textField;
}
/**
* isKindleInvertedTextField
* When sharing a document to the Amazon Kindle app. It displays a screen where one could edit the title and the author of the
* document. These two fields do not support SpannableString, which is used for suggestion highlighting. When they receive one
* weird side effects occur. Nevertheless, all other text fields in the app are fine, so we detect only these two particular ones.
*/
private boolean isKindleInvertedTextField() {
return editorInfo != null && editorInfo.inputType == 1 && editorInfo.packageName.contains("com.amazon.kindle");
}
/**
* isTermux
* Termux is a terminal emulator and it naturally has a text input, but it incorrectly introduces itself as having a NULL input,
* instead of a plain text input. However NULL inputs are usually, buttons and dropdown menus, which indeed can not read text
* and are ignored by TT9 by default. In order not to ignore Termux, we need this.
*/
public boolean isTermux() {
return editorInfo != null && editorInfo.inputType == 0 && editorInfo.fieldId > 0 && editorInfo.packageName.contains("com.termux");
}
public boolean setComposingTextWithHighlightedStem(@NonNull String word) {
if (isKindleInvertedTextField()) {
textField.setComposingText(word);
@ -31,12 +55,76 @@ public class AppHacks {
return false;
}
/**
* onBackspace
* Performs extra Backspace operations and returns "false", or completely replaces Backspace and returns "true". When "true" is
* returned, you must not attempt to delete text. This function has already done everything necessary.
*/
public boolean onBackspace(InputMode inputMode) {
if (isKindleInvertedTextField()) {
inputMode.clearWordStem();
return true;
} else if (isTermux()) {
return settings.getKeyBackspace() != KeyEvent.KEYCODE_BACK;
}
return false;
}
/**
* onEnter
* Tries to guess and send the correct confirmation key code or sequence of key codes, depending on the connected application
* and input field. On invalid connection or field, it does nothing.
* This hack applies to all applications, not only selected ones.
*/
public boolean onEnter() {
if (isTermux()) {
sendDownUpKeyEvents(KeyEvent.KEYCODE_ENTER);
return true;
}
return onEnterDefault();
}
/**
* onEnterDefault
* This is the default "ENTER" routine for most applications that support send-with-enter functionality. It will attempt to
* guess and send the correct confirmation key code, be it "ENTER" or "DPAD_CENTER".
* On invalid textField, it does nothing.
*/
private boolean onEnterDefault() {
if (textField == null) {
return false;
}
String oldText = textField.getTextBeforeCursor() + textField.getTextAfterCursor();
sendDownUpKeyEvents(KeyEvent.KEYCODE_DPAD_CENTER);
try {
// In Android there is no strictly defined confirmation key, hence DPAD_CENTER may have done nothing.
// If so, send an alternative key code as a final resort.
Thread.sleep(80);
String newText = textField.getTextBeforeCursor() + textField.getTextAfterCursor();
if (newText.equals(oldText)) {
sendDownUpKeyEvents(KeyEvent.KEYCODE_ENTER);
}
} catch (InterruptedException e) {
// This thread got interrupted. Assume it's because the connected application has taken an action
// after receiving DPAD_CENTER, so we don't need to do anything else.
return true;
}
return true;
}
private void sendDownUpKeyEvents(int keyCode) {
if (inputConnection != null) {
inputConnection.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, keyCode));
inputConnection.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, keyCode));
}
}
}

View file

@ -309,7 +309,10 @@ public class TextField {
return styledWord;
}
/**
* getAction
* Returns the most appropriate action for the "OK" key. It could be "send", "act as ENTER key", "go (to URL)" and so on.
*/
public int getAction() {
if (field == null) {
return EditorInfo.IME_ACTION_NONE;
@ -333,4 +336,13 @@ public class TextField {
return IME_ACTION_ENTER;
}
}
/**
* performAction
* Sends an action ID to the connected application. Usually, the action is determined with "this.getAction()".
* Note that it is up to the app to decide what to do or ignore the action ID.
*/
public boolean performAction(int actionId) {
return connection != null && actionId != EditorInfo.IME_ACTION_NONE && connection.performEditorAction(actionId);
}
}