From 85e89723809b91a259400236f4dca6d0f1cb396d Mon Sep 17 00:00:00 2001 From: Chenx Dust Date: Tue, 23 Sep 2025 18:24:22 +0800 Subject: [PATCH 1/5] Support lookup table signal for IBus frontend --- src/frontend/ibusfrontend/ibusfrontend.cpp | 114 ++++++++++++++++++++- 1 file changed, 112 insertions(+), 2 deletions(-) diff --git a/src/frontend/ibusfrontend/ibusfrontend.cpp b/src/frontend/ibusfrontend/ibusfrontend.cpp index 6d7ee3bdc..ed7dd8fde 100644 --- a/src/frontend/ibusfrontend/ibusfrontend.cpp +++ b/src/frontend/ibusfrontend/ibusfrontend.cpp @@ -263,6 +263,7 @@ using AttachmentsType = FCITX_STRING_TO_DBUS_TYPE("a{sv}"); using IBusText = FCITX_STRING_TO_DBUS_TYPE("(sa{sv}sv)"); using IBusAttrList = FCITX_STRING_TO_DBUS_TYPE("(sa{sv}av)"); using IBusAttribute = FCITX_STRING_TO_DBUS_TYPE("(sa{sv}uuuu)"); +using IBusLookupTable = FCITX_STRING_TO_DBUS_TYPE("(sa{sv}uubbiavav)"); IBusAttribute makeIBusAttr(uint32_t type, uint32_t value, uint32_t start, uint32_t end) { @@ -289,6 +290,47 @@ IBusText makeSimpleIBusText(const std::string &str) { return text; } +IBusLookupTable makeIBusLookupTable(uint32_t pageSize, uint32_t cursorPos, + bool cursorVisible, bool round, + CandidateLayoutHint layoutHint, + const std::vector &candidates, + const std::vector &labels) { + IBusLookupTable table; + std::get<0>(table) = "IBusLookupTable"; + std::get<2>(table) = pageSize; + std::get<3>(table) = cursorPos; + std::get<4>(table) = cursorVisible; + std::get<5>(table) = round; + + enum IBusOrientation { + IBUS_ORIENTATION_HORIZONTAL = 0, + IBUS_ORIENTATION_VERTICAL = 1, + IBUS_ORIENTATION_SYSTEM = 2, + }; + switch (layoutHint) { + case CandidateLayoutHint::Horizontal: + std::get<6>(table) = IBUS_ORIENTATION_HORIZONTAL; + break; + case CandidateLayoutHint::NotSet: + std::get<6>(table) = IBUS_ORIENTATION_SYSTEM; + break; + case CandidateLayoutHint::Vertical: + std::get<6>(table) = IBUS_ORIENTATION_VERTICAL; + break; + } + + std::vector ibus_candidates(candidates.size()); + for (const auto &candidate : candidates) { + std::get<7>(table).emplace_back( + dbus::Variant(makeSimpleIBusText(candidate))); + } + for (const auto &label : labels) { + std::get<8>(table).emplace_back( + dbus::Variant(makeSimpleIBusText(label))); + } + return table; +} + } // namespace class IBusFrontend : public dbus::ObjectVTable { @@ -406,6 +448,9 @@ class IBusInputContext : public InputContext, } else { updatePreeditTextTo(name_, v, cursor, offset != 0); } + if (preeditString.empty() && capabilityFlags() & CapabilityFlag::ClientSideInputPanel) { + hideLookupTableTo(name_); + } } void deleteSurroundingTextImpl(int offset, unsigned int size) override { @@ -427,6 +472,67 @@ class IBusInputContext : public InputContext, static_cast(code), state); bus()->flush(); } + + void updateClientSideUIImpl() override { + auto *instance = im_->instance(); + auto candidateList = inputPanel().candidateList(); + + std::vector candidates, labels; + int cursorIndex = 0; + int pageSize = 0; + CandidateLayoutHint layoutHint; + if (!candidateList) { + return; + } + + auto processCandidate = [&](const CandidateWord& candidate, bool appendLabel, Text fallbackLabel) { + if (candidate.isPlaceHolder()) { + return; + } + Text candidateText = + instance->outputFilter(this, candidate.textWithComment()); + candidates.emplace_back(candidateText.toString()); + if (appendLabel) { + Text labelText = candidate.hasCustomLabel() + ? candidate.customLabel() + : fallbackLabel; + labelText = instance->outputFilter(this, labelText); + labels.emplace_back(labelText.toString()); + } + }; + + auto commonList = std::dynamic_pointer_cast(candidateList); + if (commonList) { + cursorIndex = commonList->globalCursorIndex(); + pageSize = commonList->pageSize(); + int e = std::min((commonList->currentPage() + 1) * + commonList->pageSize(), + commonList->totalSize()); + for (int i = 0; i < e; i++) { + int localIndex = + i - commonList->currentPage() * commonList->pageSize(); + if (localIndex >= 0 && localIndex < commonList->size()) { + processCandidate(commonList->candidateFromAll(i), true, + commonList->label(localIndex)); + } else { + processCandidate(commonList->candidateFromAll(i), false, + Text()); + } + } + } else { + cursorIndex = candidateList->cursorIndex(); + pageSize = candidateList->size(); + for (int i = 0, e = candidateList->size(); i < e; i++) { + processCandidate(candidateList->candidate(i), true, + candidateList->label(i)); + } + } + layoutHint = candidateList->layoutHint(); + + IBusLookupTable table = makeIBusLookupTable( + pageSize, cursorIndex, true, false, layoutHint, candidates, labels); + updateLookupTableTo(name_, table, true); + } #define CHECK_SENDER_OR_RETURN \ if (currentMessage()->sender() != name_) \ return @@ -485,6 +591,9 @@ class IBusInputContext : public InputContext, requireSurroundingTextTo(name_); } } + if (cap & IBUS_CAP_LOOKUP_TABLE) { + flags |= CapabilityFlag::ClientSideInputPanel; + } setCapabilityFlags(flags); } @@ -560,6 +669,8 @@ class IBusInputContext : public InputContext, "DeleteSurroundingText", "iu"); FCITX_OBJECT_VTABLE_SIGNAL(requireSurroundingText, "RequireSurroundingText", ""); + FCITX_OBJECT_VTABLE_SIGNAL(updateLookupTable, "UpdateLookupTable", "vb"); + FCITX_OBJECT_VTABLE_SIGNAL(hideLookupTable, "HideLookupTable", ""); // We don't use following FCITX_OBJECT_VTABLE_SIGNAL(showPreeditText, "ShowPreeditText", ""); @@ -568,9 +679,7 @@ class IBusInputContext : public InputContext, "vb"); FCITX_OBJECT_VTABLE_SIGNAL(showAuxiliaryText, "ShowAuxiliaryText", ""); FCITX_OBJECT_VTABLE_SIGNAL(hideAuxiliaryText, "hideAuxiliaryText", ""); - FCITX_OBJECT_VTABLE_SIGNAL(updateLookupTable, "UpdateLookupTable", "vb"); FCITX_OBJECT_VTABLE_SIGNAL(showLookupTable, "ShowLookupTable", ""); - FCITX_OBJECT_VTABLE_SIGNAL(hideLookupTable, "HideLookupTable", ""); FCITX_OBJECT_VTABLE_SIGNAL(pageUpLookupTable, "PageUpLookupTable", ""); FCITX_OBJECT_VTABLE_SIGNAL(pageDownLookupTable, "PageDownLookupTable", ""); FCITX_OBJECT_VTABLE_SIGNAL(cursorUpLookupTable, "CursorUpLookupTable", ""); @@ -737,6 +846,7 @@ IBusFrontendModule::IBusFrontendModule(Instance *instance) dbus::VariantTypeRegistry::defaultRegistry().registerType(); dbus::VariantTypeRegistry::defaultRegistry().registerType(); dbus::VariantTypeRegistry::defaultRegistry().registerType(); + dbus::VariantTypeRegistry::defaultRegistry().registerType(); // Do the resource initialization first. inputMethod1_ = std::make_unique( From afb3235143e78cad8343ea0c35560863fffd7312 Mon Sep 17 00:00:00 2001 From: Chenx Dust Date: Wed, 24 Sep 2025 02:51:16 +0800 Subject: [PATCH 2/5] Align capability behavior to IBus --- src/frontend/ibusfrontend/ibusfrontend.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/frontend/ibusfrontend/ibusfrontend.cpp b/src/frontend/ibusfrontend/ibusfrontend.cpp index ed7dd8fde..64ea1f5de 100644 --- a/src/frontend/ibusfrontend/ibusfrontend.cpp +++ b/src/frontend/ibusfrontend/ibusfrontend.cpp @@ -579,8 +579,17 @@ class IBusInputContext : public InputContext, IBUS_CAP_SURROUNDING_TEXT = 1 << 5 }; auto flags = capabilityFlags() + .unset(CapabilityFlag::Preedit) .unset(CapabilityFlag::FormattedPreedit) - .unset(CapabilityFlag::SurroundingText); + .unset(CapabilityFlag::SurroundingText) + .unset(CapabilityFlag::ClientSideInputPanel); + // No IBUS_CAP_FOCUS means to handle all and always focus in + if ((cap & IBUS_CAP_FOCUS) == 0) { + cap |= (IBUS_CAP_PREEDIT_TEXT | IBUS_CAP_AUXILIARY_TEXT | + IBUS_CAP_LOOKUP_TABLE | IBUS_CAP_PROPERTY); + this->focusIn(); + this->setFocusGroup(nullptr); + } if (cap & IBUS_CAP_PREEDIT_TEXT) { flags |= CapabilityFlag::Preedit; flags |= CapabilityFlag::FormattedPreedit; From fd7054d44d9270986663e03e345a023b3ede63b2 Mon Sep 17 00:00:00 2001 From: Chenx Dust Date: Wed, 24 Sep 2025 12:17:27 +0800 Subject: [PATCH 3/5] Refactor ibusfrontend candidate list publishing --- src/frontend/ibusfrontend/ibusfrontend.cpp | 45 +++++++++------------- 1 file changed, 19 insertions(+), 26 deletions(-) diff --git a/src/frontend/ibusfrontend/ibusfrontend.cpp b/src/frontend/ibusfrontend/ibusfrontend.cpp index 64ea1f5de..1b24cd958 100644 --- a/src/frontend/ibusfrontend/ibusfrontend.cpp +++ b/src/frontend/ibusfrontend/ibusfrontend.cpp @@ -485,6 +485,10 @@ class IBusInputContext : public InputContext, return; } + pageSize = candidateList->size(); + cursorIndex = candidateList->cursorIndex(); + layoutHint = candidateList->layoutHint(); + auto processCandidate = [&](const CandidateWord& candidate, bool appendLabel, Text fallbackLabel) { if (candidate.isPlaceHolder()) { return; @@ -501,34 +505,23 @@ class IBusInputContext : public InputContext, } }; - auto commonList = std::dynamic_pointer_cast(candidateList); - if (commonList) { - cursorIndex = commonList->globalCursorIndex(); - pageSize = commonList->pageSize(); - int e = std::min((commonList->currentPage() + 1) * - commonList->pageSize(), - commonList->totalSize()); - for (int i = 0; i < e; i++) { - int localIndex = - i - commonList->currentPage() * commonList->pageSize(); - if (localIndex >= 0 && localIndex < commonList->size()) { - processCandidate(commonList->candidateFromAll(i), true, - commonList->label(localIndex)); - } else { - processCandidate(commonList->candidateFromAll(i), false, - Text()); - } - } - } else { - cursorIndex = candidateList->cursorIndex(); - pageSize = candidateList->size(); - for (int i = 0, e = candidateList->size(); i < e; i++) { - processCandidate(candidateList->candidate(i), true, - candidateList->label(i)); + // All previous candidates should be sent to ibus. + auto cursorList = candidateList->toBulkCursor(); + auto bulkList = candidateList->toBulk(); + auto pageList = candidateList->toPageable(); + if (cursorList && bulkList && pageList) { + cursorIndex = cursorList->globalCursorIndex(); + int firstCandidateGlobalIndex = + cursorIndex - candidateList->cursorIndex(); + for (int i = 0; i < firstCandidateGlobalIndex; i++) { + processCandidate(bulkList->candidateFromAll(i), false, Text()); } } - layoutHint = candidateList->layoutHint(); - + for (int i = 0, e = candidateList->size(); i < e; i++) { + processCandidate(candidateList->candidate(i), true, + candidateList->label(i)); + } + IBusLookupTable table = makeIBusLookupTable( pageSize, cursorIndex, true, false, layoutHint, candidates, labels); updateLookupTableTo(name_, table, true); From 34e3366b3dd4f28b7e468105e5640b48dfadae80 Mon Sep 17 00:00:00 2001 From: Chenx Dust Date: Wed, 24 Sep 2025 12:32:24 +0800 Subject: [PATCH 4/5] Fix no candidate case to fit ibus behavior --- src/frontend/ibusfrontend/ibusfrontend.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/frontend/ibusfrontend/ibusfrontend.cpp b/src/frontend/ibusfrontend/ibusfrontend.cpp index 1b24cd958..30ea178b4 100644 --- a/src/frontend/ibusfrontend/ibusfrontend.cpp +++ b/src/frontend/ibusfrontend/ibusfrontend.cpp @@ -448,9 +448,6 @@ class IBusInputContext : public InputContext, } else { updatePreeditTextTo(name_, v, cursor, offset != 0); } - if (preeditString.empty() && capabilityFlags() & CapabilityFlag::ClientSideInputPanel) { - hideLookupTableTo(name_); - } } void deleteSurroundingTextImpl(int offset, unsigned int size) override { @@ -482,6 +479,7 @@ class IBusInputContext : public InputContext, int pageSize = 0; CandidateLayoutHint layoutHint; if (!candidateList) { + hideLookupTableTo(name_); return; } From 849c97b1b2e93e234d302339790e65f8c947a36e Mon Sep 17 00:00:00 2001 From: Chenx Dust Date: Wed, 24 Sep 2025 14:55:55 +0800 Subject: [PATCH 5/5] Better show/hide lookup table logic --- src/frontend/ibusfrontend/ibusfrontend.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/frontend/ibusfrontend/ibusfrontend.cpp b/src/frontend/ibusfrontend/ibusfrontend.cpp index 30ea178b4..45c5f5f0b 100644 --- a/src/frontend/ibusfrontend/ibusfrontend.cpp +++ b/src/frontend/ibusfrontend/ibusfrontend.cpp @@ -479,7 +479,10 @@ class IBusInputContext : public InputContext, int pageSize = 0; CandidateLayoutHint layoutHint; if (!candidateList) { + if (showingLookupTable_) { hideLookupTableTo(name_); + showingLookupTable_ = false; + } return; } @@ -522,6 +525,10 @@ class IBusInputContext : public InputContext, IBusLookupTable table = makeIBusLookupTable( pageSize, cursorIndex, true, false, layoutHint, candidates, labels); + if (!showingLookupTable_) { + showLookupTableTo(name_); + showingLookupTable_ = true; + } updateLookupTableTo(name_, table, true); } #define CHECK_SENDER_OR_RETURN \ @@ -821,6 +828,7 @@ class IBusInputContext : public InputContext, std::string name_; bool clientCommitPreedit_ = false; bool effectivePostProcessKeyEvent_ = false; + bool showingLookupTable_ = false; IBusService service_{this}; };