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

@ -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);
}
}