Skip to content

Commit

Permalink
enhancement(LorieView.java): improve input handling
Browse files Browse the repository at this point in the history
  • Loading branch information
twaik committed Jan 14, 2025
1 parent 989d08a commit 2d4c31d
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 83 deletions.
2 changes: 1 addition & 1 deletion app/src/main/cpp/lorie/activity.c
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,7 @@ static void sendTextEvent(JNIEnv *env, __unused jobject thiz, jbyteArray text) {
p += len;
if (p - (char*) str >= length)
break;
usleep(30000);
usleep(10000);
}

(*env)->ReleaseByteArrayElements(env, text, str, JNI_ABORT);
Expand Down
154 changes: 86 additions & 68 deletions app/src/main/java/com/termux/x11/LorieView.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.os.Build;
import android.text.Editable;
import android.text.InputType;
import android.util.AttributeSet;
Expand All @@ -23,18 +22,18 @@
import android.view.inputmethod.BaseInputConnection;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.InputMethodSubtype;
import android.view.inputmethod.TextSnapshot;

import androidx.annotation.Keep;
import androidx.annotation.NonNull;

import com.termux.x11.input.InputStub;
import com.termux.x11.input.TouchInputHandler;

import java.nio.charset.StandardCharsets;
import java.util.regex.PatternSyntaxException;

import static java.nio.charset.StandardCharsets.UTF_8;

import dalvik.annotation.optimization.CriticalNative;
import dalvik.annotation.optimization.FastNative;

Expand All @@ -53,12 +52,86 @@ interface PixelFormat {
private long lastClipboardTimestamp = System.currentTimeMillis();
private static boolean clipboardSyncEnabled = false;
private static boolean hardwareKbdScancodesWorkaround = false;
private final InputMethodManager mIMM = (InputMethodManager)getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
private String mImeLang;
private boolean mImeCJK;
public boolean enableGboardCJK;
private Callback mCallback;
private final Point p = new Point();
private final InputConnection mConnection = new BaseInputConnection(this, true) {
private final MainActivity a = MainActivity.getInstance();
private final CharSequence seq = " ";
CharSequence currentText = null;

@Override public Editable getEditable() {
return null;
}

// Needed to send arrow keys with IME's cursor control feature
@Override public CharSequence getTextBeforeCursor(int length, int flags) {
return seq;
}

// Needed to send arrow keys with IME's cursor control feature
@Override public CharSequence getTextAfterCursor(int length, int flags) {
return seq;
}

void sendKey(int k) {
LorieView.this.sendKeyEvent(0, k, true);
LorieView.this.sendKeyEvent(0, k, false);
}

@Override public boolean deleteSurroundingText(int beforeLength, int afterLength) {
for (int i=0; i<beforeLength; i++) sendKey(KeyEvent.KEYCODE_DEL);
for (int i=0; i<afterLength; i++) sendKey(KeyEvent.KEYCODE_FORWARD_DEL);
return true;
}

// Needed to send arrow keys with IME's cursor control feature
@Override public boolean setComposingRegion(int start, int end) {
return true;
}

/** @noinspection SameReturnValue*/
boolean replaceText(CharSequence text, boolean reuse) {
if (text != null && currentText != null && 0 == CharSequence.compare(text, currentText)) {
if (!reuse)
currentText = null;
return true;
}

int l = currentText != null ? currentText.length() : 0;
for (int i=0; i<l; i++)
sendKey(KeyEvent.KEYCODE_DEL);

if (text != null)
sendTextEvent(text.toString().getBytes(UTF_8));

currentText = reuse ? text : null;

// Do not steal dedicated buttons from a full external keyboard.
if (a.useTermuxEKBarBehaviour && a.mExtraKeys != null)
a.mExtraKeys.unsetSpecialKeys();
return true;
}

@Override public boolean setComposingText(CharSequence text, int newCursorPosition) {
return replaceText(text, true);
}

@Override
public boolean commitText(CharSequence text, int newCursorPosition) {
return replaceText(text, false);
}

@Override
public boolean finishComposingText() {
currentText = null;
return true;
}

@Override
public TextSnapshot takeSnapshot() {
return null;
}
};
private final SurfaceHolder.Callback mSurfaceCallback = new SurfaceHolder.Callback() {
@Override public void surfaceCreated(@NonNull SurfaceHolder holder) {
holder.setFormat(PixelFormat.BGRA_8888);
Expand Down Expand Up @@ -235,8 +308,6 @@ public void reloadPreferences(Prefs p) {
clipboardSyncEnabled = p.clipboardEnable.get();
setClipboardSyncEnabled(clipboardSyncEnabled, clipboardSyncEnabled);
TouchInputHandler.refreshInputDevices();
enableGboardCJK = p.enableGboardCJK.get();
mIMM.restartInput(this);
}

// It is used in native code
Expand All @@ -252,14 +323,14 @@ void setClipboardText(String text) {
/** @noinspection unused*/ // It is used in native code
void requestClipboard() {
if (!clipboardSyncEnabled) {
sendClipboardEvent("".getBytes(StandardCharsets.UTF_8));
sendClipboardEvent("".getBytes(UTF_8));
return;
}

CharSequence clip = clipboard.getText();
if (clip != null) {
String text = String.valueOf(clipboard.getText());
sendClipboardEvent(text.getBytes(StandardCharsets.UTF_8));
sendClipboardEvent(text.getBytes(UTF_8));
Log.d("CLIP", "sending clipboard contents: " + text);
}
}
Expand Down Expand Up @@ -298,67 +369,14 @@ public void onWindowFocusChanged(boolean hasFocus) {
TouchInputHandler.refreshInputDevices();
}

public void checkRestartInput(boolean recheck) {
if (!enableGboardCJK)
return;

InputMethodSubtype methodSubtype = mIMM.getCurrentInputMethodSubtype();
String languageTag = methodSubtype == null ? null : methodSubtype.getLanguageTag();
if (languageTag != null && languageTag.length() >= 2 && !languageTag.substring(0, 2).equals(mImeLang))
mIMM.restartInput(this);
else if (recheck) { // recheck needed because sometimes requestCursorUpdates() is called too fast, before InputMethodManager detect change in IM subtype
MainActivity.handler.postDelayed(() -> checkRestartInput(false), 40);
}
}

@Override
public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
outAttrs.inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD;

outAttrs.inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS | InputType.TYPE_TEXT_VARIATION_NORMAL;
outAttrs.actionLabel = "↵";
// 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;

if (enableGboardCJK) {
InputMethodSubtype methodSubtype = mIMM.getCurrentInputMethodSubtype();
mImeLang = methodSubtype == null ? null : methodSubtype.getLanguageTag();
if (mImeLang != null && mImeLang.length() > 2)
mImeLang = mImeLang.substring(0, 2);
mImeCJK = mImeLang != null && (mImeLang.equals("zh") || mImeLang.equals("ko") || mImeLang.equals("ja"));
outAttrs.inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS |
(mImeCJK ? InputType.TYPE_TEXT_VARIATION_NORMAL : InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
return new BaseInputConnection(this, false) {
// workaround for Gboard
// Gboard calls requestCursorUpdates() whenever switching language
// check and then restart keyboard in different inputType when needed
@Override
public Editable getEditable() {
checkRestartInput(true);
return super.getEditable();
}
@Override
public boolean requestCursorUpdates(int cursorUpdateMode) {
checkRestartInput(true);
return super.requestCursorUpdates(cursorUpdateMode);
}

@Override
public boolean commitText(CharSequence text, int newCursorPosition) {
boolean result = super.commitText(text, newCursorPosition);
if (mImeCJK)
// suppress Gboard CJK keyboard suggestion
// this workaround does not work well for non-CJK keyboards
// , when typing fast and two keypresses (commitText) are close in time
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
mIMM.invalidateInput(LorieView.this);
else
mIMM.restartInput(LorieView.this);
return result;
}
};
} else {
return super.onCreateInputConnection(outAttrs);
}
return mConnection;
}

static native boolean renderingInActivity();
Expand Down
13 changes: 2 additions & 11 deletions app/src/main/java/com/termux/x11/MainActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import android.content.res.Configuration;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Rect;
import android.net.Uri;
import android.os.Build;
import android.os.Build.VERSION_CODES;
Expand Down Expand Up @@ -90,7 +91,7 @@ public class MainActivity extends AppCompatActivity implements View.OnApplyWindo
private static boolean externalKeyboardConnected = false;
private View.OnKeyListener mLorieKeyListener;
private boolean filterOutWinKey = false;
private boolean useTermuxEKBarBehaviour = false;
boolean useTermuxEKBarBehaviour = false;
private boolean isInPictureInPictureMode = false;

public static Prefs prefs = null;
Expand Down Expand Up @@ -232,7 +233,6 @@ else if (SamsungDexUtils.checkDeXEnabled(this))
onPreferencesChanged("");

toggleExtraKeys(false, false);
checkRestartInput();

initStylusAuxButtons();
initMouseAuxButtons();
Expand Down Expand Up @@ -879,15 +879,6 @@ public static boolean isConnected() {
return LorieView.connected();
}

private void checkRestartInput() {
// an imperfect workaround for Gboard CJK keyboard in DeX soft keyboard mode
// in that particular mode during language switching, InputConnection#requestCursorUpdates() is not called and no signal can be picked up.
// therefore, check to activate CJK keyboard is done upon a keypress.
if (getLorieView().enableGboardCJK && SamsungDexUtils.checkDeXEnabled(this))
getLorieView().checkRestartInput(false);
handler.postDelayed(this::checkRestartInput, 300);
}

public static void getRealMetrics(DisplayMetrics m) {
if (getInstance() != null &&
getInstance().getLorieView() != null &&
Expand Down
2 changes: 0 additions & 2 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,6 @@ E.g. <b>META</b> for <i>META key</i>, \n
<string name="pref_pauseKeyInterceptingWithEsc">Pause key intercepting with Esc key</string>
<string name="pref_filterOutWinkey">Filter out intercepted Win (Meta/Mod4) key.</string>
<string name="pref_filterOutWinkey_summary">Allows you to use Dex shortcuts while intercepting. Requires Accessibility service to work.</string>
<string name="pref_enableGboardCJK">Workaround to enable CJK Gboard</string>
<string name="pref_enableGboardCJK_summary">May require Android 14 and Gboard 14</string>

<string name="pref_clipboardEnable">Clipboard sharing</string>
<string name="pref_requestNotificationPermission">Request notification permission</string>
Expand Down
1 change: 0 additions & 1 deletion app/src/main/res/xml/preferences.xml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@
<SwitchPreferenceCompat app:key="enableAccessibilityServiceAutomatically" app:defaultValue="false" />
<SwitchPreferenceCompat app:key="pauseKeyInterceptingWithEsc" app:defaultValue="false" />
<SwitchPreferenceCompat app:key="filterOutWinkey" app:defaultValue="false" />
<SwitchPreferenceCompat app:key="enableGboardCJK" app:defaultValue="false" />
</PreferenceScreen>
<PreferenceScreen app:key="other">
<SwitchPreferenceCompat app:key="clipboardEnable" app:defaultValue="true" />
Expand Down

0 comments on commit 2d4c31d

Please sign in to comment.