move some key press logic from LatinIme into KeyboardActionListener

This commit is contained in:
Helium314 2025-06-14 12:16:29 +02:00
parent a37de668c0
commit 9c97a6b9bf
7 changed files with 98 additions and 96 deletions

View file

@ -139,6 +139,16 @@ class Event private constructor(
null, if (isKeyRepeat) FLAG_REPEAT else FLAG_NONE, null)
}
// A helper method to split the code point and the key code.
// todo: Ultimately, they should not be squashed into the same variable, and this method should be removed.
@JvmStatic
fun createSoftwareKeypressEvent(keyCodeOrCodePoint: Int, metaState: Int, keyX: Int, keyY: Int, isKeyRepeat: Boolean) =
if (keyCodeOrCodePoint <= 0) {
createSoftwareKeypressEvent(NOT_A_CODE_POINT, keyCodeOrCodePoint, metaState, keyX, keyY, isKeyRepeat)
} else {
createSoftwareKeypressEvent(keyCodeOrCodePoint, NOT_A_KEY_CODE, metaState, keyX, keyY, isKeyRepeat)
}
fun createHardwareKeypressEvent(codePoint: Int, keyCode: Int, metaState: Int, next: Event?, isKeyRepeat: Boolean): Event {
return Event(EVENT_TYPE_INPUT_KEYPRESS, null, codePoint, keyCode, metaState,
Constants.EXTERNAL_KEYBOARD_COORDINATE, Constants.EXTERNAL_KEYBOARD_COORDINATE,

View file

@ -6,6 +6,8 @@
package helium314.keyboard.keyboard;
import android.view.KeyEvent;
import helium314.keyboard.latin.common.Constants;
import helium314.keyboard.latin.common.InputPointers;
@ -31,6 +33,12 @@ public interface KeyboardActionListener {
*/
void onReleaseKey(int primaryCode, boolean withSliding);
/** For handling hardware key presses. Returns whether the event was handled. */
boolean onKeyDown(int keyCode, KeyEvent keyEvent);
/** For handling hardware key presses. Returns whether the event was handled. */
boolean onKeyUp(int keyCode, KeyEvent keyEvent);
/**
* Send a key code to the listener.
*
@ -117,6 +125,10 @@ public interface KeyboardActionListener {
@Override
public void onReleaseKey(int primaryCode, boolean withSliding) {}
@Override
public boolean onKeyDown(int keyCode, KeyEvent keyEvent) { return false; }
@Override
public boolean onKeyUp(int keyCode, KeyEvent keyEvent) { return false; }
@Override
public void onCodeInput(int primaryCode, int x, int y, boolean isKeyRepeat) {}
@Override
public void onTextInput(String text) {}

View file

@ -1,16 +1,24 @@
package helium314.keyboard.keyboard
import android.text.InputType
import android.util.SparseArray
import android.view.KeyEvent
import android.view.inputmethod.InputMethodSubtype
import helium314.keyboard.event.Event
import helium314.keyboard.event.HangulEventDecoder.decodeHardwareKeyEvent
import helium314.keyboard.event.HardwareEventDecoder
import helium314.keyboard.event.HardwareKeyboardEventDecoder
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode
import helium314.keyboard.latin.EmojiAltPhysicalKeyDetector
import helium314.keyboard.latin.LatinIME
import helium314.keyboard.latin.RichInputMethodManager
import helium314.keyboard.latin.common.Constants
import helium314.keyboard.latin.common.InputPointers
import helium314.keyboard.latin.common.StringUtils
import helium314.keyboard.latin.common.combiningRange
import helium314.keyboard.latin.common.loopOverCodePoints
import helium314.keyboard.latin.common.loopOverCodePointsBackwards
import helium314.keyboard.latin.define.ProductionFlags
import helium314.keyboard.latin.inputlogic.InputLogic
import helium314.keyboard.latin.settings.Settings
import kotlin.math.abs
@ -19,6 +27,11 @@ import kotlin.math.min
class KeyboardActionListenerImpl(private val latinIME: LatinIME, private val inputLogic: InputLogic) : KeyboardActionListener {
private val connection = inputLogic.mConnection
private val emojiAltPhysicalKeyDetector by lazy { EmojiAltPhysicalKeyDetector(latinIME.resources) }
// We expect to have only one decoder in almost all cases, hence the default capacity of 1.
// If it turns out we need several, it will get grown seamlessly.
private val hardwareEventDecoders: SparseArray<HardwareEventDecoder> = SparseArray(1)
private val keyboardSwitcher = KeyboardSwitcher.getInstance()
private val settings = Settings.getInstance()
@ -58,9 +71,56 @@ class KeyboardActionListenerImpl(private val latinIME: LatinIME, private val inp
keyboardSwitcher.onReleaseKey(primaryCode, withSliding, latinIME.currentAutoCapsState, latinIME.currentRecapitalizeState)
}
override fun onKeyUp(keyCode: Int, keyEvent: KeyEvent): Boolean {
emojiAltPhysicalKeyDetector.onKeyUp(keyEvent)
if (!ProductionFlags.IS_HARDWARE_KEYBOARD_SUPPORTED)
return false
val keyIdentifier = keyEvent.deviceId.toLong() shl 32 + keyEvent.keyCode
return inputLogic.mCurrentlyPressedHardwareKeys.remove(keyIdentifier)
}
override fun onKeyDown(keyCode: Int, keyEvent: KeyEvent): Boolean {
emojiAltPhysicalKeyDetector.onKeyDown(keyEvent)
if (!ProductionFlags.IS_HARDWARE_KEYBOARD_SUPPORTED)
return false
val event: Event
if (settings.current.mLocale.language == "ko") { // todo: this does not appear to be the right place
val subtype = keyboardSwitcher.keyboard?.mId?.mSubtype ?: RichInputMethodManager.getInstance().currentSubtype
event = decodeHardwareKeyEvent(subtype, keyEvent) {
getHardwareKeyEventDecoder(keyEvent.deviceId).decodeHardwareKey(keyEvent)
}
} else {
event = getHardwareKeyEventDecoder(keyEvent.deviceId).decodeHardwareKey(keyEvent)
}
if (event.isHandled) {
inputLogic.onCodeInput(
settings.current, event,
keyboardSwitcher.getKeyboardShiftMode(), // TODO: this is not necessarily correct for a hardware keyboard right now
keyboardSwitcher.getCurrentKeyboardScript(),
latinIME.mHandler
)
return true
}
return false
}
override fun onCodeInput(primaryCode: Int, x: Int, y: Int, isKeyRepeat: Boolean) {
when (primaryCode) {
KeyCode.TOGGLE_AUTOCORRECT -> return Settings.getInstance().toggleAutoCorrect()
KeyCode.TOGGLE_INCOGNITO_MODE -> return Settings.getInstance().toggleAlwaysIncognitoMode()
}
val mkv = keyboardSwitcher.mainKeyboardView
latinIME.onCodeInput(primaryCode, metaState, mkv.getKeyX(x), mkv.getKeyY(y), isKeyRepeat)
// checking if the character is a combining accent
val event = if (primaryCode in combiningRange) { // todo: should this be done later, maybe in inputLogic?
Event.createSoftwareDeadEvent(primaryCode, 0, metaState, mkv.getKeyX(x), mkv.getKeyY(y), null)
} else {
Event.createSoftwareKeypressEvent(primaryCode, metaState, mkv.getKeyX(x), mkv.getKeyY(y), isKeyRepeat)
}
latinIME.onEvent(event)
}
override fun onTextInput(text: String?) = latinIME.onTextInput(text)
@ -257,4 +317,13 @@ class KeyboardActionListenerImpl(private val latinIME: LatinIME, private val inp
}
return -min(-actualSteps, text.length)
}
private fun getHardwareKeyEventDecoder(deviceId: Int): HardwareEventDecoder {
hardwareEventDecoders.get(deviceId)?.let { return it }
// TODO: create the decoder according to the specification
val newDecoder = HardwareKeyboardEventDecoder(deviceId)
hardwareEventDecoders.put(deviceId, newDecoder)
return newDecoder
}
}

View file

@ -23,7 +23,7 @@ import java.util.List;
/**
* A class for detecting Emoji-Alt physical key.
*/
final class EmojiAltPhysicalKeyDetector {
public final class EmojiAltPhysicalKeyDetector {
private static final String TAG = "EmojiAltPhysKeyDetector";
private static final boolean DEBUG = false;

View file

@ -26,7 +26,6 @@ import android.os.Process;
import android.text.InputType;
import android.util.PrintWriterPrinter;
import android.util.Printer;
import android.util.SparseArray;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.View;
@ -51,9 +50,6 @@ import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode;
import helium314.keyboard.latin.common.InsetsOutlineProvider;
import helium314.keyboard.dictionarypack.DictionaryPackConstants;
import helium314.keyboard.event.Event;
import helium314.keyboard.event.HangulEventDecoder;
import helium314.keyboard.event.HardwareEventDecoder;
import helium314.keyboard.event.HardwareKeyboardEventDecoder;
import helium314.keyboard.event.InputTransaction;
import helium314.keyboard.keyboard.Keyboard;
import helium314.keyboard.keyboard.KeyboardId;
@ -68,7 +64,6 @@ import helium314.keyboard.latin.common.InputPointers;
import helium314.keyboard.latin.common.LocaleUtils;
import helium314.keyboard.latin.common.ViewOutlineProviderUtilsKt;
import helium314.keyboard.latin.define.DebugFlags;
import helium314.keyboard.latin.define.ProductionFlags;
import helium314.keyboard.latin.inputlogic.InputLogic;
import helium314.keyboard.latin.personalization.PersonalizationHelper;
import helium314.keyboard.latin.settings.Settings;
@ -134,9 +129,6 @@ public class LatinIME extends InputMethodService implements
private final DictionaryFacilitator mDictionaryFacilitator =
DictionaryFacilitatorProvider.getDictionaryFacilitator(false);
final InputLogic mInputLogic = new InputLogic(this, this, mDictionaryFacilitator);
// We expect to have only one decoder in almost all cases, hence the default capacity of 1.
// If it turns out we need several, it will get grown seamlessly.
final SparseArray<HardwareEventDecoder> mHardwareEventDecoders = new SparseArray<>(1);
// TODO: Move these {@link View}s to {@link KeyboardSwitcher}.
private View mInputView;
@ -146,7 +138,6 @@ public class LatinIME extends InputMethodService implements
private RichInputMethodManager mRichImm;
final KeyboardSwitcher mKeyboardSwitcher;
private final SubtypeState mSubtypeState = new SubtypeState();
private EmojiAltPhysicalKeyDetector mEmojiAltPhysicalKeyDetector;
private final StatsUtilsManager mStatsUtilsManager;
// Working variable for {@link #startShowingInputView()} and
// {@link #onEvaluateInputViewShown()}.
@ -1540,25 +1531,6 @@ public class LatinIME extends InputMethodService implements
mKeyboardActionListener.onCodeInput(codePoint, x, y, isKeyRepeat);
}
// called by KeyboardActionListener
public void onCodeInput(final int codePoint, final int metaState, final int x, final int y, final boolean isKeyRepeat) {
if (codePoint < 0) {
switch (codePoint) {
case KeyCode.TOGGLE_AUTOCORRECT -> {mSettings.toggleAutoCorrect(); return; }
case KeyCode.TOGGLE_INCOGNITO_MODE -> {mSettings.toggleAlwaysIncognitoMode(); return; }
}
}
final Event event;
// checking if the character is a combining accent
if (0x300 <= codePoint && codePoint <= 0x35b) {
event = Event.createSoftwareDeadEvent(codePoint, 0, metaState, x, y, null);
} else {
event = createSoftwareKeypressEvent(codePoint, metaState, x, y, isKeyRepeat);
}
onEvent(event);
}
// This method is public for testability of LatinIME, but also in the future it should
// completely replace #onCodeInput.
public void onEvent(@NonNull final Event event) {
@ -1573,24 +1545,6 @@ public class LatinIME extends InputMethodService implements
mKeyboardSwitcher.onEvent(event, getCurrentAutoCapsState(), getCurrentRecapitalizeState());
}
// A helper method to split the code point and the key code. Ultimately, they should not be
// squashed into the same variable, and this method should be removed.
// public for testing, as we don't want to copy the same logic into test code
@NonNull
public static Event createSoftwareKeypressEvent(final int keyCodeOrCodePoint, final int metaState,
final int keyX, final int keyY, final boolean isKeyRepeat) {
final int keyCode;
final int codePoint;
if (keyCodeOrCodePoint <= 0) {
keyCode = keyCodeOrCodePoint;
codePoint = Event.NOT_A_CODE_POINT;
} else {
keyCode = Event.NOT_A_KEY_CODE;
codePoint = keyCodeOrCodePoint;
}
return Event.createSoftwareKeypressEvent(codePoint, keyCode, metaState, keyX, keyY, isKeyRepeat);
}
public void onTextInput(final String rawText) {
// TODO: have the keyboard pass the correct key code when we need it.
final Event event = Event.createSoftwareTextEvent(rawText, KeyCode.MULTIPLE_CODE_POINTS);
@ -1830,63 +1784,18 @@ public class LatinIME extends InputMethodService implements
feedbackManager.performAudioFeedback(code);
}
private HardwareEventDecoder getHardwareKeyEventDecoder(final int deviceId) {
final HardwareEventDecoder decoder = mHardwareEventDecoders.get(deviceId);
if (null != decoder) return decoder;
// TODO: create the decoder according to the specification
final HardwareEventDecoder newDecoder = new HardwareKeyboardEventDecoder(deviceId);
mHardwareEventDecoders.put(deviceId, newDecoder);
return newDecoder;
}
// Hooks for hardware keyboard
@Override
public boolean onKeyDown(final int keyCode, final KeyEvent keyEvent) {
if (mEmojiAltPhysicalKeyDetector == null) {
mEmojiAltPhysicalKeyDetector = new EmojiAltPhysicalKeyDetector(
getApplicationContext().getResources());
}
mEmojiAltPhysicalKeyDetector.onKeyDown(keyEvent);
if (!ProductionFlags.IS_HARDWARE_KEYBOARD_SUPPORTED) {
return super.onKeyDown(keyCode, keyEvent);
}
final Event event;
if (mRichImm.getCurrentSubtypeLocale().getLanguage().equals("ko")) {
final RichInputMethodSubtype subtype = mKeyboardSwitcher.getKeyboard() == null
? mRichImm.getCurrentSubtype()
: mKeyboardSwitcher.getKeyboard().mId.mSubtype;
event = HangulEventDecoder.decodeHardwareKeyEvent(subtype, keyEvent,
() -> getHardwareKeyEventDecoder(keyEvent.getDeviceId()).decodeHardwareKey(keyEvent));
} else {
event = getHardwareKeyEventDecoder(keyEvent.getDeviceId()).decodeHardwareKey(keyEvent);
}
// If the event is not handled by LatinIME, we just pass it to the parent implementation.
// If it's handled, we return true because we did handle it.
if (event.isHandled()) {
mInputLogic.onCodeInput(mSettings.getCurrent(), event,
mKeyboardSwitcher.getKeyboardShiftMode(),
// TODO: this is not necessarily correct for a hardware keyboard right now
mKeyboardSwitcher.getCurrentKeyboardScript(),
mHandler);
if (mKeyboardActionListener.onKeyDown(keyCode, keyEvent))
return true;
}
return super.onKeyDown(keyCode, keyEvent);
}
@Override
public boolean onKeyUp(final int keyCode, final KeyEvent keyEvent) {
if (mEmojiAltPhysicalKeyDetector == null) {
mEmojiAltPhysicalKeyDetector = new EmojiAltPhysicalKeyDetector(
getApplicationContext().getResources());
}
mEmojiAltPhysicalKeyDetector.onKeyUp(keyEvent);
if (!ProductionFlags.IS_HARDWARE_KEYBOARD_SUPPORTED) {
return super.onKeyUp(keyCode, keyEvent);
}
final long keyIdentifier = (long) keyEvent.getDeviceId() << 32 + keyEvent.getKeyCode();
if (mInputLogic.mCurrentlyPressedHardwareKeys.remove(keyIdentifier)) {
if (mKeyboardActionListener.onKeyUp(keyCode, keyEvent))
return true;
}
return super.onKeyUp(keyCode, keyEvent);
}

View file

@ -12,3 +12,5 @@ object Links {
const val CUSTOM_LAYOUTS = "$GITHUB/discussions/categories/custom-layout"
const val CUSTOM_COLORS = "$GITHUB/discussions/categories/custom-colors"
}
val combiningRange = 0x300..0x35b

View file

@ -718,7 +718,7 @@ public final class InputLogic {
if (mConnection.hasSelection()) {
mConnection.copyText(true);
// fake delete keypress to remove the text
final Event backspaceEvent = LatinIME.createSoftwareKeypressEvent(KeyCode.DELETE, 0,
final Event backspaceEvent = Event.createSoftwareKeypressEvent(KeyCode.DELETE, 0,
event.getMX(), event.getMY(), event.isKeyRepeat());
handleBackspaceEvent(backspaceEvent, inputTransaction, currentKeyboardScript);
inputTransaction.setDidAffectContents();