diff --git a/app/src/main/cpp/androidfrontend/androidfrontend.cpp b/app/src/main/cpp/androidfrontend/androidfrontend.cpp index 2f29797d..f62eeab1 100644 --- a/app/src/main/cpp/androidfrontend/androidfrontend.cpp +++ b/app/src/main/cpp/androidfrontend/androidfrontend.cpp @@ -5,10 +5,12 @@ #include #include #include +#include #include #include -#include +#include #include +#include #include #include "androidfrontend.h" @@ -286,8 +288,20 @@ AndroidFrontend::AndroidFrontend(Instance *instance) EventType::InputContextInputMethodActivated, EventWatcherPhase::Default, [this](Event &event) { - FCITX_UNUSED(event); - imChangeCallback(); + auto &e = static_cast(event); + if (e.inputContext() != activeIC_) return; + imChangeCallback(makeInputMethodStatus(activeIC_)); + } + )); + eventHandlers_.emplace_back(instance_->watchEvent( + EventType::InputContextSwitchInputMethod, + EventWatcherPhase::Default, + [this](Event &event) { + auto &e = static_cast(event); + if (e.inputContext() != activeIC_) return; + const auto &reason = static_cast::type>(e.reason()); + const std::string &oldIM = e.oldInputMethod(); + switchInputMethodCallback(reason, oldIM); } )); eventHandlers_.emplace_back(instance_->watchEvent( @@ -295,20 +309,19 @@ AndroidFrontend::AndroidFrontend(Instance *instance) EventWatcherPhase::Default, [this](Event &event) { auto &e = static_cast(event); + if (e.inputContext() != activeIC_) return; switch (e.component()) { case UserInterfaceComponent::InputPanel: { - if (activeIC_) { - activeIC_->updateInputPanel(); - if (pagingMode_ == 0) { - activeIC_->updateCandidatesBulk(); - } else { - activeIC_->updateCandidatesPaged(); - } + activeIC_->updateInputPanel(); + if (pagingMode_ == 0) { + activeIC_->updateCandidatesBulk(); + } else { + activeIC_->updateCandidatesPaged(); } break; } case UserInterfaceComponent::StatusArea: { - statusAreaUpdateCallback(); + statusAreaUpdateCallback(makeStatusAreaActions(activeIC_), makeInputMethodStatus(activeIC_)); break; } } @@ -494,6 +507,28 @@ void AndroidFrontend::setPagedCandidateCallback(const PagedCandidateCallback &ca pagedCandidateCallback = callback; } +void AndroidFrontend::setSwitchInputMethodCallback(const SwitchInputMethodCallback &callback) { + switchInputMethodCallback = callback; +} + +InputMethodStatus AndroidFrontend::makeInputMethodStatus(InputContext *ic) { + auto *entry = instance_->inputMethodEntry(ic); + auto *engine = instance_->inputMethodEngine(ic); + return {entry, engine, ic}; +} + +std::vector AndroidFrontend::makeStatusAreaActions(fcitx::InputContext *ic) { + auto actions = std::vector(); + for (auto group: {fcitx::StatusGroup::BeforeInputMethod, + fcitx::StatusGroup::InputMethod, + fcitx::StatusGroup::AfterInputMethod}) { + for (auto act: ic->statusArea().actions(group)) { + actions.emplace_back(act, ic); + } + } + return actions; +} + class AndroidFrontendFactory : public AddonFactory { public: AddonInstance *create(AddonManager *manager) override { diff --git a/app/src/main/cpp/androidfrontend/androidfrontend.h b/app/src/main/cpp/androidfrontend/androidfrontend.h index 9b9db800..cf1cbe19 100644 --- a/app/src/main/cpp/androidfrontend/androidfrontend.h +++ b/app/src/main/cpp/androidfrontend/androidfrontend.h @@ -23,30 +23,30 @@ public: Instance *instance() { return instance_; } - void updateCandidateList(const std::vector &candidates, const int size); - void commitString(const std::string &str, const int cursor); + void updateCandidateList(const std::vector &candidates, int size); + void commitString(const std::string &str, int cursor); void updateClientPreedit(const Text &clientPreedit); void updateInputPanel(const Text &preedit, const Text &auxUp, const Text &auxDown); - void releaseInputContext(const int uid); + void releaseInputContext(int uid); void updatePagedCandidate(const PagedCandidateEntity &paged); - void keyEvent(const Key &key, bool isRelease, const int timestamp); + void keyEvent(const Key &key, bool isRelease, int timestamp); void forwardKey(const Key &key, bool isRelease); bool selectCandidate(int idx); bool isInputPanelEmpty(); void resetInputContext(); void repositionCursor(int idx); void focusInputContext(bool focus); - void activateInputContext(const int uid, const std::string &pkgName); - void deactivateInputContext(const int uid); + void activateInputContext(int uid, const std::string &pkgName); + void deactivateInputContext(int uid); [[nodiscard]] InputContext *activeInputContext() const; void setCapabilityFlags(uint64_t flag); - std::vector getCandidates(const int offset, const int limit); - std::vector getCandidateActions(const int idx); - void triggerCandidateAction(const int idx, const int actionIdx); - void deleteSurrounding(const int before, const int after); + std::vector getCandidates(int offset, int limit); + std::vector getCandidateActions(int idx); + void triggerCandidateAction(int idx, int actionIdx); + void deleteSurrounding(int before, int after); void showToast(const std::string &s); - void setCandidatePagingMode(const int mode); + void setCandidatePagingMode(int mode); void offsetCandidatePage(int delta); void setCandidateListCallback(const CandidateListCallback &callback); void setCommitStringCallback(const CommitStringCallback &callback); @@ -58,6 +58,7 @@ public: void setDeleteSurroundingCallback(const DeleteSurroundingCallback &callback); void setToastCallback(const ToastCallback &callback); void setPagedCandidateCallback(const PagedCandidateCallback &callback); + void setSwitchInputMethodCallback(const SwitchInputMethodCallback &callback); private: FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, keyEvent); @@ -86,6 +87,7 @@ private: FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, setDeleteSurroundingCallback); FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, setToastCallback); FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, setPagedCandidateCallback); + FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, setSwitchInputMethodCallback); Instance *instance_; FocusGroup focusGroup_; @@ -99,11 +101,15 @@ private: ClientPreeditCallback preeditCallback = [](const Text &) {}; InputPanelCallback inputPanelCallback = [](const fcitx::Text &, const fcitx::Text &, const Text &) {}; KeyEventCallback keyEventCallback = [](const int, const uint32_t, const uint32_t, const bool, const int) {}; - InputMethodChangeCallback imChangeCallback = [] {}; - StatusAreaUpdateCallback statusAreaUpdateCallback = [] {}; + InputMethodChangeCallback imChangeCallback = [](const InputMethodStatus &) {}; + StatusAreaUpdateCallback statusAreaUpdateCallback = [](const std::vector &, const InputMethodStatus &) {}; DeleteSurroundingCallback deleteSurroundingCallback = [](const int, const int) {}; ToastCallback toastCallback = [](const std::string &) {}; PagedCandidateCallback pagedCandidateCallback = [](const PagedCandidateEntity &) {}; + SwitchInputMethodCallback switchInputMethodCallback = [](const int, const std::string &) {}; + + InputMethodStatus makeInputMethodStatus(InputContext* ic); + std::vector makeStatusAreaActions(InputContext* ic); }; } // namespace fcitx diff --git a/app/src/main/cpp/androidfrontend/androidfrontend_public.h b/app/src/main/cpp/androidfrontend/androidfrontend_public.h index 69bcbbdd..86d83c96 100644 --- a/app/src/main/cpp/androidfrontend/androidfrontend_public.h +++ b/app/src/main/cpp/androidfrontend/androidfrontend_public.h @@ -17,11 +17,12 @@ typedef std::function CommitStringCallback typedef std::function ClientPreeditCallback; typedef std::function InputPanelCallback; typedef std::function KeyEventCallback; -typedef std::function InputMethodChangeCallback; -typedef std::function StatusAreaUpdateCallback; +typedef std::function InputMethodChangeCallback; +typedef std::function &, const InputMethodStatus &)> StatusAreaUpdateCallback; typedef std::function DeleteSurroundingCallback; typedef std::function ToastCallback; typedef std::function PagedCandidateCallback; +typedef std::function SwitchInputMethodCallback; FCITX_ADDON_DECLARE_FUNCTION(AndroidFrontend, keyEvent, void(const fcitx::Key &, bool isRelease, const int timestamp)) @@ -101,4 +102,7 @@ FCITX_ADDON_DECLARE_FUNCTION(AndroidFrontend, setToastCallback, FCITX_ADDON_DECLARE_FUNCTION(AndroidFrontend, setPagedCandidateCallback, void(const PagedCandidateCallback &)) +FCITX_ADDON_DECLARE_FUNCTION(AndroidFrontend, setSwitchInputMethodCallback, + void(const SwitchInputMethodCallback &)) + #endif // FCITX5_ANDROID_ANDROIDFRONTEND_PUBLIC_H diff --git a/app/src/main/cpp/native-lib.cpp b/app/src/main/cpp/native-lib.cpp index 0ccc671c..a875f796 100644 --- a/app/src/main/cpp/native-lib.cpp +++ b/app/src/main/cpp/native-lib.cpp @@ -154,7 +154,7 @@ public: auto *ic = p_frontend->call(); if (!ic) return nullptr; auto *entry = p_instance->inputMethodEntry(ic); - auto *engine = static_cast(p_instance->addonManager().addon(entry->addon(), true)); + auto *engine = p_instance->inputMethodEngine(ic); return std::make_unique(entry, engine, ic); } @@ -637,21 +637,16 @@ Java_org_fcitx_fcitx5_android_core_Fcitx_startupFcitx( env->SetObjectArrayElement(vararg, 4, env->NewObject(GlobalRef->Integer, GlobalRef->IntegerInit, timestamp)); env->CallStaticVoidMethod(GlobalRef->Fcitx, GlobalRef->HandleFcitxEvent, 5, *vararg); }; - auto imChangeCallback = []() { - std::unique_ptr status = Fcitx::Instance().inputMethodStatus(); - if (!status) return; + auto imChangeCallback = [](const InputMethodStatus &status) { auto env = GlobalRef->AttachEnv(); - auto vararg = JRef(env, env->NewObjectArray(1, GlobalRef->Object, nullptr)); - auto obj = JRef(env, fcitxInputMethodStatusToJObject(env, *status)); + auto vararg = JRef(env, env->NewObjectArray(2, GlobalRef->Object, nullptr)); + auto obj = JRef(env, fcitxInputMethodStatusToJObject(env, status)); env->SetObjectArrayElement(vararg, 0, obj); env->CallStaticVoidMethod(GlobalRef->Fcitx, GlobalRef->HandleFcitxEvent, 6, *vararg); }; - auto statusAreaUpdateCallback = []() { - std::unique_ptr status = Fcitx::Instance().inputMethodStatus(); - if (!status) return; + auto statusAreaUpdateCallback = [](const std::vector &actions, const InputMethodStatus &status) { auto env = GlobalRef->AttachEnv(); - auto vararg = JRef(env, env->NewObjectArray(static_cast(2), GlobalRef->Object, nullptr)); - const auto actions = Fcitx::Instance().statusAreaActions(); + auto vararg = JRef(env, env->NewObjectArray(2, GlobalRef->Object, nullptr)); auto actionArray = JRef(env, env->NewObjectArray(static_cast(actions.size()), GlobalRef->Action, nullptr)); int i = 0; for (const auto &a: actions) { @@ -659,7 +654,7 @@ Java_org_fcitx_fcitx5_android_core_Fcitx_startupFcitx( env->SetObjectArrayElement(actionArray, i++, obj); } env->SetObjectArrayElement(vararg, 0, actionArray); - auto statusObj = JRef(env, fcitxInputMethodStatusToJObject(env, *status)); + auto statusObj = JRef(env, fcitxInputMethodStatusToJObject(env, status)); env->SetObjectArrayElement(vararg, 1, statusObj); env->CallStaticVoidMethod(GlobalRef->Fcitx, GlobalRef->HandleFcitxEvent, 7, *vararg); }; @@ -696,6 +691,14 @@ Java_org_fcitx_fcitx5_android_core_Fcitx_startupFcitx( env->SetObjectArrayElement(vararg, 4, hasNext); env->CallStaticVoidMethod(GlobalRef->Fcitx, GlobalRef->HandleFcitxEvent, 9, *vararg); }; + auto switchInputMethodCallback = [](const int reason, const std::string &oldIM) { + auto env = GlobalRef->AttachEnv(); + auto vararg = JRef(env, env->NewObjectArray(2, GlobalRef->Object, nullptr)); + auto reasonInteger = JRef(env, env->NewObject(GlobalRef->Integer, GlobalRef->IntegerInit, reason)); + env->SetObjectArrayElement(vararg, 0, reasonInteger); + env->SetObjectArrayElement(vararg, 1, JString(env, oldIM)); + env->CallStaticVoidMethod(GlobalRef->Fcitx, GlobalRef->HandleFcitxEvent, 10, *vararg); + }; auto toastCallback = [](const std::string &s) { auto env = GlobalRef->AttachEnv(); env->CallStaticVoidMethod(GlobalRef->Fcitx, GlobalRef->ShowToast, *JString(env, s)); @@ -716,6 +719,7 @@ Java_org_fcitx_fcitx5_android_core_Fcitx_startupFcitx( androidfrontend->template call(statusAreaUpdateCallback); androidfrontend->template call(deleteSurroundingCallback); androidfrontend->template call(pagedCandidateCallback); + androidfrontend->template call(switchInputMethodCallback); androidfrontend->template call(toastCallback); }); FCITX_INFO() << "Finishing startup"; @@ -996,7 +1000,7 @@ extern "C" JNIEXPORT void JNICALL Java_org_fcitx_fcitx5_android_core_Fcitx_focusInputContextOutIn(JNIEnv *env, jclass clazz) { RETURN_IF_NOT_RUNNING - auto& instance = Fcitx::Instance(); + auto &instance = Fcitx::Instance(); instance.focusInputContext(false); instance.focusInputContext(true); } diff --git a/app/src/main/java/org/fcitx/fcitx5/android/core/FcitxEvent.kt b/app/src/main/java/org/fcitx/fcitx5/android/core/FcitxEvent.kt index 1c119fab..55e3d8c0 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/core/FcitxEvent.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/core/FcitxEvent.kt @@ -133,8 +133,8 @@ sealed class FcitxEvent(open val data: T) { override val eventType = EventType.PagedCandidate - enum class LayoutHint(value: Int) { - NotSet(0), Vertical(1), Horizontal(2); + enum class LayoutHint { + NotSet, Vertical, Horizontal; companion object { private val Types = entries.toTypedArray() @@ -180,6 +180,30 @@ sealed class FcitxEvent(open val data: T) { } } + data class SwitchInputMethodEvent(override val data: Data) : + FcitxEvent(data) { + + override val eventType: EventType + get() = EventType.SwitchInputMethod + + /** + * The reason why input method is switched to another. + * + * translated from + * [fcitx/event.h](https://github.com/fcitx/fcitx5/blob/5.1.16/src/lib/fcitx/event.h#L41) + */ + enum class Reason { + Trigger, Deactivate, AltTrigger, Activate, Enumerate, GroupChange, CapabilityChanged, Other; + + companion object { + private val Types = entries.toTypedArray() + fun of(value: Int) = Types[value] + } + } + + data class Data(val reason: Reason, val oldInputMethod: String) + } + data class UnknownEvent(override val data: Array) : FcitxEvent>(data) { override val eventType = EventType.Unknown @@ -211,6 +235,7 @@ sealed class FcitxEvent(open val data: T) { StatusArea, DeleteSurrounding, PagedCandidate, + SwitchInputMethod, Unknown } @@ -274,6 +299,12 @@ sealed class FcitxEvent(open val data: T) { ) ) } + EventType.SwitchInputMethod -> SwitchInputMethodEvent( + SwitchInputMethodEvent.Data( + SwitchInputMethodEvent.Reason.of(params[0] as Int), + params[1] as String + ) + ) else -> UnknownEvent(params) } } diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/FcitxInputMethodService.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/FcitxInputMethodService.kt index 67d32e6e..15044d11 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/FcitxInputMethodService.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/FcitxInputMethodService.kt @@ -307,9 +307,16 @@ class FcitxInputMethodService : LifecycleInputMethodService() { // [^1]: notify system that input method subtype has changed switchInputMethod(InputMethodUtil.componentName, subtype) } - if (inputDeviceMgr.evaluateOnInputMethodChange()) { - // show inputView for [CandidatesView] when it's likely changed by the user - forceShowSelf() + } + is FcitxEvent.SwitchInputMethodEvent -> { + val (reason) = event.data + if (reason != FcitxEvent.SwitchInputMethodEvent.Reason.CapabilityChanged && + reason != FcitxEvent.SwitchInputMethodEvent.Reason.Other + ) { + if (inputDeviceMgr.evaluateOnInputMethodChange()) { + // show inputView for [CandidatesView] when input method switched by user + forceShowSelf() + } } } else -> {} diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/InputDeviceManager.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/InputDeviceManager.kt index 58113f54..44ac28f9 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/InputDeviceManager.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/InputDeviceManager.kt @@ -78,7 +78,6 @@ class InputDeviceManager(private val onChange: (Boolean) -> Unit) { private var startedInputView = false private var isNullInputType = true - private var hasKeyDown = false private var candidatesViewMode by AppPrefs.getInstance().candidates.mode @@ -105,7 +104,6 @@ class InputDeviceManager(private val onChange: (Boolean) -> Unit) { * @return should force show input views on hardware key down */ fun evaluateOnKeyDown(e: KeyEvent, service: FcitxInputMethodService): Boolean { - hasKeyDown = true if (startedInputView) { // filter out back/home/volume buttons and combination keys if (e.unicodeChar != 0) { @@ -156,14 +154,13 @@ class InputDeviceManager(private val onChange: (Boolean) -> Unit) { } /** - * @return should force show inputView for [CandidatesView] when input method changes + * @return should force show inputView for [CandidatesView] when input method switched by user */ fun evaluateOnInputMethodChange(): Boolean { - return if (isVirtualKeyboard || startedInputView) false else hasKeyDown + return !isVirtualKeyboard && !startedInputView } fun onFinishInputView() { startedInputView = false - hasKeyDown = false } }