diff --git a/app/src/main/java/helium314/keyboard/event/Event.kt b/app/src/main/java/helium314/keyboard/event/Event.kt index 5903b0c9f..37dc1ea79 100644 --- a/app/src/main/java/helium314/keyboard/event/Event.kt +++ b/app/src/main/java/helium314/keyboard/event/Event.kt @@ -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, diff --git a/app/src/main/java/helium314/keyboard/keyboard/KeyboardActionListener.java b/app/src/main/java/helium314/keyboard/keyboard/KeyboardActionListener.java index fb177e806..20fb20839 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/KeyboardActionListener.java +++ b/app/src/main/java/helium314/keyboard/keyboard/KeyboardActionListener.java @@ -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) {} diff --git a/app/src/main/java/helium314/keyboard/keyboard/KeyboardActionListenerImpl.kt b/app/src/main/java/helium314/keyboard/keyboard/KeyboardActionListenerImpl.kt index fc002a187..a86eaeee6 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/KeyboardActionListenerImpl.kt +++ b/app/src/main/java/helium314/keyboard/keyboard/KeyboardActionListenerImpl.kt @@ -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 = 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 + } } diff --git a/app/src/main/java/helium314/keyboard/latin/EmojiAltPhysicalKeyDetector.java b/app/src/main/java/helium314/keyboard/latin/EmojiAltPhysicalKeyDetector.java index 79bfeeb24..9b06420ee 100644 --- a/app/src/main/java/helium314/keyboard/latin/EmojiAltPhysicalKeyDetector.java +++ b/app/src/main/java/helium314/keyboard/latin/EmojiAltPhysicalKeyDetector.java @@ -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; diff --git a/app/src/main/java/helium314/keyboard/latin/LatinIME.java b/app/src/main/java/helium314/keyboard/latin/LatinIME.java index a7701684d..1ebcba6b8 100644 --- a/app/src/main/java/helium314/keyboard/latin/LatinIME.java +++ b/app/src/main/java/helium314/keyboard/latin/LatinIME.java @@ -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 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); } diff --git a/app/src/main/java/helium314/keyboard/latin/common/Constants.kt b/app/src/main/java/helium314/keyboard/latin/common/Constants.kt index 17b36d1da..43b9e66dd 100644 --- a/app/src/main/java/helium314/keyboard/latin/common/Constants.kt +++ b/app/src/main/java/helium314/keyboard/latin/common/Constants.kt @@ -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 diff --git a/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java b/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java index 3aeb602d9..d34c276aa 100644 --- a/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java +++ b/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java @@ -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();