From 873c08255c5dbbd967e9701de24e6154c560e262 Mon Sep 17 00:00:00 2001 From: Rocka Date: Sat, 30 May 2026 21:22:23 +0800 Subject: [PATCH] Send candidate list tab actions on input panel update --- .../cpp/androidfrontend/androidfrontend.cpp | 42 +++++++++++++++++-- .../cpp/androidfrontend/androidfrontend.h | 12 ++++-- .../androidfrontend/androidfrontend_public.h | 10 ++++- app/src/main/cpp/native-lib.cpp | 23 ++++++++-- .../org/fcitx/fcitx5/android/core/Fcitx.kt | 6 +++ .../org/fcitx/fcitx5/android/core/FcitxAPI.kt | 2 + .../fcitx/fcitx5/android/core/FcitxEvent.kt | 35 ++++++++++++++-- .../src/main/cpp/fcitx5-chinese-addons | 2 +- lib/fcitx5-lua/src/main/cpp/fcitx5-lua | 2 +- lib/fcitx5/src/main/cpp/fcitx5 | 2 +- lib/libime/src/main/cpp/libime | 2 +- 11 files changed, 118 insertions(+), 20 deletions(-) diff --git a/app/src/main/cpp/androidfrontend/androidfrontend.cpp b/app/src/main/cpp/androidfrontend/androidfrontend.cpp index eccbd6b8..d1410715 100644 --- a/app/src/main/cpp/androidfrontend/androidfrontend.cpp +++ b/app/src/main/cpp/androidfrontend/androidfrontend.cpp @@ -65,10 +65,22 @@ public: void updateInputPanel() { const InputPanel &ip = inputPanel(); + std::vector tabs; + auto list = ip.candidateList(); + if (list) { + auto tabbed = ip.candidateList()->toTabbed(); + if (tabbed) { + auto tabActions = tabbed->tabActions(); + for (const auto &a: tabActions) { + tabs.emplace_back(a); + } + } + } frontend_->updateInputPanel( filterText(ip.preedit()), filterText(ip.auxUp()), - filterText(ip.auxDown()) + filterText(ip.auxDown()), + tabs ); } @@ -237,6 +249,18 @@ public: } } + void triggerTabAction(const int idx) { + const auto &list = inputPanel().candidateList(); + if (!list) return; + const auto &tabbed = list->toTabbed(); + if (!tabbed) return; + try { + tabbed->triggerTabAction(idx); + } catch (const std::exception &e) { + FCITX_WARN() << "triggerTabAction(" << idx << ") failed:" << e.what(); + } + } + void offsetCandidatePage(int delta) { if (delta == 0) { return; @@ -360,8 +384,8 @@ void AndroidFrontend::updateClientPreedit(const Text &clientPreedit) { preeditCallback(clientPreedit); } -void AndroidFrontend::updateInputPanel(const Text &preedit, const Text &auxUp, const Text &auxDown) { - inputPanelCallback(preedit, auxUp, auxDown); +void AndroidFrontend::updateInputPanel(const Text &preedit, const Text &auxUp, const Text &auxDown, const std::vector &tabs) { + inputPanelCallback(preedit, auxUp, auxDown, tabs); } void AndroidFrontend::releaseInputContext(const int uid) { @@ -387,6 +411,11 @@ void AndroidFrontend::triggerCandidateAction(const int idx, const int actionIdx) activeIC_->triggerCandidateAction(idx, actionIdx); } +void AndroidFrontend::triggerTabAction(const int idx) { + if (!activeIC_) return; + activeIC_->triggerTabAction(idx); +} + bool AndroidFrontend::isInputPanelEmpty() { if (!activeIC_) return true; return activeIC_->inputPanel().empty(); @@ -475,6 +504,11 @@ void AndroidFrontend::offsetCandidatePage(int delta) { activeIC_->offsetCandidatePage(delta); } +void AndroidFrontend::triggerCandidateListTabAction(int id) { + if (!activeIC_) return; + activeIC_->triggerTabAction(id); +} + void AndroidFrontend::setCommitStringCallback(const CommitStringCallback &callback) { commitStringCallback = callback; } @@ -483,7 +517,7 @@ void AndroidFrontend::setPreeditCallback(const ClientPreeditCallback &callback) preeditCallback = callback; } -void AndroidFrontend::setInputPanelAuxCallback(const InputPanelCallback &callback) { +void AndroidFrontend::setInputPanelCallback(const InputPanelCallback &callback) { inputPanelCallback = callback; } diff --git a/app/src/main/cpp/androidfrontend/androidfrontend.h b/app/src/main/cpp/androidfrontend/androidfrontend.h index 99bfe63c..4dc4704b 100644 --- a/app/src/main/cpp/androidfrontend/androidfrontend.h +++ b/app/src/main/cpp/androidfrontend/androidfrontend.h @@ -26,7 +26,7 @@ public: void updateCandidateList(const std::vector &candidates, int total); 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 updateInputPanel(const Text &preedit, const Text &auxUp, const Text &auxDown, const std::vector &tabs); void releaseInputContext(int uid); void updatePagedCandidate(const PagedCandidateEntity &paged); @@ -44,14 +44,16 @@ public: std::vector getCandidates(int offset, int limit); std::vector getCandidateActions(int idx); void triggerCandidateAction(int idx, int actionIdx); + void triggerTabAction(int idx); void deleteSurrounding(int before, int after); void showToast(const std::string &s); void setCandidatePagingMode(int mode); void offsetCandidatePage(int delta); + void triggerCandidateListTabAction(int id); void setCandidateListCallback(const CandidateListCallback &callback); void setCommitStringCallback(const CommitStringCallback &callback); void setPreeditCallback(const ClientPreeditCallback &callback); - void setInputPanelAuxCallback(const InputPanelCallback &callback); + void setInputPanelCallback(const InputPanelCallback &callback); void setKeyEventCallback(const KeyEventCallback &callback); void setInputMethodChangeCallback(const InputMethodChangeCallback &callback); void setStatusAreaUpdateCallback(const StatusAreaUpdateCallback &callback); @@ -74,13 +76,15 @@ private: FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, getCandidates); FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, getCandidateActions); FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, triggerCandidateAction); + FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, triggerTabAction); FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, showToast); FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, setCandidatePagingMode); FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, offsetCandidatePage); + FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, triggerCandidateListTabAction); FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, setCandidateListCallback); FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, setCommitStringCallback); FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, setPreeditCallback); - FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, setInputPanelAuxCallback); + FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, setInputPanelCallback); FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, setKeyEventCallback); FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, setInputMethodChangeCallback); FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, setStatusAreaUpdateCallback); @@ -99,7 +103,7 @@ private: CandidateListCallback candidateListCallback = [](const std::vector &, const int) {}; CommitStringCallback commitStringCallback = [](const std::string &, const int) {}; ClientPreeditCallback preeditCallback = [](const Text &) {}; - InputPanelCallback inputPanelCallback = [](const fcitx::Text &, const fcitx::Text &, const Text &) {}; + InputPanelCallback inputPanelCallback = [](const fcitx::Text &, const fcitx::Text &, const Text &, const std::vector &) {}; KeyEventCallback keyEventCallback = [](const int, const uint32_t, const uint32_t, const bool, const int) {}; InputMethodChangeCallback imChangeCallback = [](const InputMethodStatus &) {}; StatusAreaUpdateCallback statusAreaUpdateCallback = [](const std::vector &, const InputMethodStatus &) {}; diff --git a/app/src/main/cpp/androidfrontend/androidfrontend_public.h b/app/src/main/cpp/androidfrontend/androidfrontend_public.h index d7784803..5aa33559 100644 --- a/app/src/main/cpp/androidfrontend/androidfrontend_public.h +++ b/app/src/main/cpp/androidfrontend/androidfrontend_public.h @@ -15,7 +15,7 @@ typedef std::function &, const int)> CandidateListCallback; typedef std::function CommitStringCallback; typedef std::function ClientPreeditCallback; -typedef std::function InputPanelCallback; +typedef std::function &)> InputPanelCallback; typedef std::function KeyEventCallback; typedef std::function InputMethodChangeCallback; typedef std::function &, const InputMethodStatus &)> StatusAreaUpdateCallback; @@ -63,6 +63,9 @@ FCITX_ADDON_DECLARE_FUNCTION(AndroidFrontend, getCandidateActions, FCITX_ADDON_DECLARE_FUNCTION(AndroidFrontend, triggerCandidateAction, void(const int, const int)) +FCITX_ADDON_DECLARE_FUNCTION(AndroidFrontend, triggerTabAction, + void(const int)) + FCITX_ADDON_DECLARE_FUNCTION(AndroidFrontend, showToast, void(const std::string &)) @@ -72,6 +75,9 @@ FCITX_ADDON_DECLARE_FUNCTION(AndroidFrontend, setCandidatePagingMode, FCITX_ADDON_DECLARE_FUNCTION(AndroidFrontend, offsetCandidatePage, void(int)) +FCITX_ADDON_DECLARE_FUNCTION(AndroidFrontend, triggerCandidateListTabAction, + void(int)) + FCITX_ADDON_DECLARE_FUNCTION(AndroidFrontend, setCandidateListCallback, void(const CandidateListCallback &)) @@ -81,7 +87,7 @@ FCITX_ADDON_DECLARE_FUNCTION(AndroidFrontend, setCommitStringCallback, FCITX_ADDON_DECLARE_FUNCTION(AndroidFrontend, setPreeditCallback, void(const ClientPreeditCallback &)) -FCITX_ADDON_DECLARE_FUNCTION(AndroidFrontend, setInputPanelAuxCallback, +FCITX_ADDON_DECLARE_FUNCTION(AndroidFrontend, setInputPanelCallback, void(const InputPanelCallback &)) FCITX_ADDON_DECLARE_FUNCTION(AndroidFrontend, setKeyEventCallback, diff --git a/app/src/main/cpp/native-lib.cpp b/app/src/main/cpp/native-lib.cpp index dfc1e22f..28687b85 100644 --- a/app/src/main/cpp/native-lib.cpp +++ b/app/src/main/cpp/native-lib.cpp @@ -434,6 +434,10 @@ public: return p_frontend->call(delta); } + void triggerCandidateListTabAction(int id) { + return p_frontend->call(id); + } + void save() { p_instance->save(); } @@ -614,12 +618,18 @@ Java_org_fcitx_fcitx5_android_core_Fcitx_startupFcitx( env->SetObjectArrayElement(vararg, 0, fcitxTextToJObject(env, clientPreedit)); env->CallStaticVoidMethod(GlobalRef->Fcitx, GlobalRef->HandleFcitxEvent, 2, *vararg); }; - auto inputPanelAuxCallback = [](const fcitx::Text &preedit, const fcitx::Text &auxUp, const fcitx::Text &auxDown) { + auto inputPanelCallback = [](const fcitx::Text &preedit, const fcitx::Text &auxUp, const fcitx::Text &auxDown, const std::vector &tabs) { auto env = GlobalRef->AttachEnv(); - auto vararg = JRef(env, env->NewObjectArray(3, GlobalRef->FormattedText, nullptr)); + auto vararg = JRef(env, env->NewObjectArray(4, GlobalRef->Object, nullptr)); env->SetObjectArrayElement(vararg, 0, fcitxTextToJObject(env, preedit)); env->SetObjectArrayElement(vararg, 1, fcitxTextToJObject(env, auxUp)); env->SetObjectArrayElement(vararg, 2, fcitxTextToJObject(env, auxDown)); + auto tabsArray = JRef(env, env->NewObjectArray(static_cast(tabs.size()), GlobalRef->CandidateAction, nullptr)); + int i = 0; + for (const auto &tab: tabs) { + env->SetObjectArrayElement(tabsArray, i++, fcitxCandidateActionToObject(env, tab)); + } + env->SetObjectArrayElement(vararg, 3, *tabsArray); env->CallStaticVoidMethod(GlobalRef->Fcitx, GlobalRef->HandleFcitxEvent, 3, *vararg); }; auto readyCallback = []() { @@ -713,7 +723,7 @@ Java_org_fcitx_fcitx5_android_core_Fcitx_startupFcitx( androidfrontend->template call(candidateListCallback); androidfrontend->template call(commitStringCallback); androidfrontend->template call(preeditCallback); - androidfrontend->template call(inputPanelAuxCallback); + androidfrontend->template call(inputPanelCallback); androidfrontend->template call(keyEventCallback); androidfrontend->template call(imChangeCallback); androidfrontend->template call(statusAreaUpdateCallback); @@ -1096,6 +1106,13 @@ Java_org_fcitx_fcitx5_android_core_Fcitx_offsetFcitxCandidatePage(JNIEnv *env, j Fcitx::Instance().offsetCandidatePage(delta); } +extern "C" +JNIEXPORT void JNICALL +Java_org_fcitx_fcitx5_android_core_Fcitx_triggerFcitxCandidateListTabAction(JNIEnv *env, jclass clazz, jint id) { + RETURN_IF_NOT_RUNNING + Fcitx::Instance().triggerCandidateListTabAction(id); +} + extern "C" JNIEXPORT void JNICALL Java_org_fcitx_fcitx5_android_core_Fcitx_loopOnce(JNIEnv *env, jclass clazz) { diff --git a/app/src/main/java/org/fcitx/fcitx5/android/core/Fcitx.kt b/app/src/main/java/org/fcitx/fcitx5/android/core/Fcitx.kt index a11a38f6..4ebcf5d8 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/core/Fcitx.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/core/Fcitx.kt @@ -194,6 +194,9 @@ class Fcitx(private val context: Context) : FcitxAPI, FcitxLifecycleOwner { override suspend fun offsetCandidatePage(delta: Int) = withFcitxContext { offsetFcitxCandidatePage(delta) } + override suspend fun triggerCandidateListTabAction(id: Int) = + withFcitxContext { triggerFcitxCandidateListTabAction(id) } + init { if (lifecycle.currentState != FcitxLifecycle.State.STOPPED) throw IllegalAccessException("Fcitx5 has already been created!") @@ -378,6 +381,9 @@ class Fcitx(private val context: Context) : FcitxAPI, FcitxLifecycleOwner { @JvmStatic external fun offsetFcitxCandidatePage(delta: Int) + @JvmStatic + external fun triggerFcitxCandidateListTabAction(id: Int) + @JvmStatic external fun loopOnce() diff --git a/app/src/main/java/org/fcitx/fcitx5/android/core/FcitxAPI.kt b/app/src/main/java/org/fcitx/fcitx5/android/core/FcitxAPI.kt index 8e3bb437..1df5cc30 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/core/FcitxAPI.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/core/FcitxAPI.kt @@ -108,4 +108,6 @@ interface FcitxAPI { suspend fun setCandidatePagingMode(mode: Int) suspend fun offsetCandidatePage(delta: Int) + suspend fun triggerCandidateListTabAction(id: Int) + } \ No newline at end of file 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 6989f0a9..e803f9cd 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 @@ -61,9 +61,37 @@ sealed class FcitxEvent(open val data: T) { data class Data( val preedit: FormattedText, val auxUp: FormattedText, - val auxDown: FormattedText + val auxDown: FormattedText, + val tabs: Array ) { - constructor() : this(FormattedText.Empty, FormattedText.Empty, FormattedText.Empty) + constructor() : this( + FormattedText.Empty, + FormattedText.Empty, + FormattedText.Empty, + emptyArray() + ) + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Data + + if (preedit != other.preedit) return false + if (auxUp != other.auxUp) return false + if (auxDown != other.auxDown) return false + if (!tabs.contentEquals(other.tabs)) return false + + return true + } + + override fun hashCode(): Int { + var result = preedit.hashCode() + result = 31 * result + auxUp.hashCode() + result = 31 * result + auxDown.hashCode() + result = 31 * result + tabs.contentHashCode() + return result + } } } @@ -261,7 +289,8 @@ sealed class FcitxEvent(open val data: T) { InputPanelEvent.Data( params[0] as FormattedText, params[1] as FormattedText, - params[2] as FormattedText + params[2] as FormattedText, + params[3] as Array ) ) EventType.Ready -> ReadyEvent() diff --git a/lib/fcitx5-chinese-addons/src/main/cpp/fcitx5-chinese-addons b/lib/fcitx5-chinese-addons/src/main/cpp/fcitx5-chinese-addons index 940b3b3d..3a94a960 160000 --- a/lib/fcitx5-chinese-addons/src/main/cpp/fcitx5-chinese-addons +++ b/lib/fcitx5-chinese-addons/src/main/cpp/fcitx5-chinese-addons @@ -1 +1 @@ -Subproject commit 940b3b3db521a6fcb14e4d67ea522217cf244c3b +Subproject commit 3a94a96006f8e67665fa9bb1b9ef4602415ddd04 diff --git a/lib/fcitx5-lua/src/main/cpp/fcitx5-lua b/lib/fcitx5-lua/src/main/cpp/fcitx5-lua index e5809953..52978aa8 160000 --- a/lib/fcitx5-lua/src/main/cpp/fcitx5-lua +++ b/lib/fcitx5-lua/src/main/cpp/fcitx5-lua @@ -1 +1 @@ -Subproject commit e5809953e5c2d31c285f8b0c182e219962a12d4f +Subproject commit 52978aa88b4979c057fe94d68291883d9c27085f diff --git a/lib/fcitx5/src/main/cpp/fcitx5 b/lib/fcitx5/src/main/cpp/fcitx5 index ac7a6b93..04eff133 160000 --- a/lib/fcitx5/src/main/cpp/fcitx5 +++ b/lib/fcitx5/src/main/cpp/fcitx5 @@ -1 +1 @@ -Subproject commit ac7a6b937d69acf812a58eea6926182ceb3e502c +Subproject commit 04eff1334eb2eab45245c492bf5b85be4c4eda6b diff --git a/lib/libime/src/main/cpp/libime b/lib/libime/src/main/cpp/libime index c16d4e52..7197ff04 160000 --- a/lib/libime/src/main/cpp/libime +++ b/lib/libime/src/main/cpp/libime @@ -1 +1 @@ -Subproject commit c16d4e52c21d98ae71ffce355b395631a05acc42 +Subproject commit 7197ff04d43e192a87da157bebaaf386cf98ed3c