From 42f60d52c770881f318b166d6afec299c7db1bd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sven=20P=C3=BCschel?= Date: Tue, 3 Feb 2026 13:59:25 +0100 Subject: [PATCH 1/5] fcitx-utils: OrderedSet: add erase method Add an erase method to erase an iterator from the given set. This allow erasing elements while iterating over the set. --- src/lib/fcitx-utils/misc_p.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/lib/fcitx-utils/misc_p.h b/src/lib/fcitx-utils/misc_p.h index 71f5da07e..d5bb477ef 100644 --- a/src/lib/fcitx-utils/misc_p.h +++ b/src/lib/fcitx-utils/misc_p.h @@ -53,6 +53,9 @@ class OrderedSet { using OrderList = std::list; public: + using iterator = typename OrderList::iterator; + using const_iterator = typename OrderList::const_iterator; + auto begin() { return order_.begin(); } auto end() { return order_.end(); } @@ -132,6 +135,11 @@ class OrderedSet { return true; } + iterator erase(const_iterator iter) { + dict_.erase(*iter); + return order_.erase(iter); + } + const OrderList &order() const { return order_; } private: From 26216c982dd9993c8a0055683565378fec9bc1ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sven=20P=C3=BCschel?= Date: Tue, 3 Feb 2026 22:42:51 +0100 Subject: [PATCH 2/5] clipboard: optimize deleting password entries Optimize deleting password entries by utilizing the new OrderedSet erase method. This allows to only iterate once over all passwords and directly remove the expired ones. As the erase method returns the iterator for the next element, we avoid incrementing the iterator in the for loop header. --- src/modules/clipboard/clipboard.cpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/modules/clipboard/clipboard.cpp b/src/modules/clipboard/clipboard.cpp index c5f8f6095..539bf8cb8 100644 --- a/src/modules/clipboard/clipboard.cpp +++ b/src/modules/clipboard/clipboard.cpp @@ -494,20 +494,20 @@ void Clipboard::refreshPasswordTimer() { minTimestamp = std::min(minTimestamp, primary_.passwordTimestamp); } - // Not efficient, but we don't have lots of entries anyway. - std::unordered_set needRemove; - for (const auto &entry : history_) { - if (shouldClearPassword(entry, *config_.clearPasswordAfter)) { - needRemove.insert(entry); - } else if (entry.passwordTimestamp) { - minTimestamp = std::min(minTimestamp, entry.passwordTimestamp); + size_t erasedPasswords = 0; + for (auto iter = history_.begin(); iter != history_.end();) { + if (shouldClearPassword(*iter, *config_.clearPasswordAfter)) { + iter = history_.erase(iter); + ++erasedPasswords; + continue; } + if (iter->passwordTimestamp) { + minTimestamp = std::min(minTimestamp, iter->passwordTimestamp); + } + ++iter; } - FCITX_CLIPBOARD_DEBUG() << "Clear " << needRemove.size() - << " password(s) in clipboard history."; - for (const auto &entry : needRemove) { - history_.remove(entry); - } + FCITX_CLIPBOARD_DEBUG() + << "Erased " << erasedPasswords << " password(s) in clipboard history."; if (minTimestamp != std::numeric_limits::max()) { clearPasswordTimer_->setTime( From fbe6aba3b4bd3063f2099f3a05287fe96905f224 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sven=20P=C3=BCschel?= Date: Mon, 2 Feb 2026 13:15:13 +0100 Subject: [PATCH 3/5] clipboard: don't limit the max number of entries Don't limit the max number of entries. While having a huge number of entries might cause performance/resource issues, allow the user to tune it to their hardware and his needs. --- src/modules/clipboard/clipboard.h | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/modules/clipboard/clipboard.h b/src/modules/clipboard/clipboard.h index 9eff4859c..782608e94 100644 --- a/src/modules/clipboard/clipboard.h +++ b/src/modules/clipboard/clipboard.h @@ -52,9 +52,8 @@ FCITX_CONFIGURATION( KeyListConstrain()}; KeyListOption pastePrimaryKey{ this, "PastePrimaryKey", _("Paste Primary"), {}, KeyListConstrain()}; - Option numOfEntries{this, "Number of entries", - _("Number of entries"), 5, - IntConstrain(3, 30)}; + Option numOfEntries{ + this, "Number of entries", _("Number of entries"), 5, IntConstrain(3)}; ConditionalHidden> ignorePasswordFromPasswordManager{ From 0f0dafaf16a94e91284f615cb859c93ab3eb441f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sven=20P=C3=BCschel?= Date: Tue, 3 Feb 2026 19:49:03 +0100 Subject: [PATCH 4/5] clipboard: don't delete the history with backspace In preparation of enabling the user to search the history, drop the backspace function of deleting the history. History can still be deleted with the delete key. --- src/modules/clipboard/clipboard.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/modules/clipboard/clipboard.cpp b/src/modules/clipboard/clipboard.cpp index 539bf8cb8..436868c75 100644 --- a/src/modules/clipboard/clipboard.cpp +++ b/src/modules/clipboard/clipboard.cpp @@ -330,8 +330,7 @@ Clipboard::Clipboard(Instance *instance) state->reset(inputContext); return; } - if (keyEvent.key().check(FcitxKey_Delete) || - keyEvent.key().check(FcitxKey_BackSpace)) { + if (keyEvent.key().check(FcitxKey_Delete)) { keyEvent.accept(); history_.clear(); primary_.clear(); @@ -394,7 +393,7 @@ void Clipboard::updateUI(InputContext *inputContext) { candidateList->setSelectionKey(selectionKeys_); candidateList->setLayoutHint(CandidateLayoutHint::Vertical); - Text auxUp(_("Clipboard (Press BackSpace/Delete to clear history):")); + Text auxUp(_("Clipboard (Press Delete to clear history):")); if (!candidateList->totalSize()) { Text auxDown(_("No clipboard history.")); inputContext->inputPanel().setAuxDown(auxDown); From 44b4e771e58113c17f46ab80f55c640c88683ea6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sven=20P=C3=BCschel?= Date: Tue, 3 Feb 2026 22:50:13 +0100 Subject: [PATCH 5/5] clipboard: implement search functionality Implement a search functionality to allow searching for previous clipboard entries. This enables the user to search for a given entry. It works similar to the unicode module, which allows searching for a given unicode symbol. --- src/modules/clipboard/clipboard.cpp | 63 +++++++++++++++++++++++++---- 1 file changed, 55 insertions(+), 8 deletions(-) diff --git a/src/modules/clipboard/clipboard.cpp b/src/modules/clipboard/clipboard.cpp index 436868c75..64c8bbf8e 100644 --- a/src/modules/clipboard/clipboard.cpp +++ b/src/modules/clipboard/clipboard.cpp @@ -18,6 +18,7 @@ #include "fcitx-utils/event.h" #include "fcitx-utils/eventloopinterface.h" #include "fcitx-utils/i18n.h" +#include "fcitx-utils/inputbuffer.h" #include "fcitx-utils/key.h" #include "fcitx-utils/keysym.h" #include "fcitx-utils/log.h" @@ -70,13 +71,16 @@ FCITX_DEFINE_LOG_CATEGORY(clipboard_log, "clipboard"); class ClipboardState : public InputContextProperty { public: - ClipboardState(Clipboard *q) : q_(q) {} + ClipboardState(Clipboard *q) : q_(q) { buffer_.setMaxSize(30); } bool enabled_ = false; Clipboard *q_; + InputBuffer buffer_; void reset(InputContext *ic) { enabled_ = false; + buffer_.clear(); + buffer_.shrinkToFit(); ic->inputPanel().reset(); ic->updatePreedit(); ic->updateUserInterface(UserInterfaceComponent::InputPanel); @@ -256,7 +260,7 @@ Clipboard::Clipboard(Instance *instance) auto candidateList = inputContext->inputPanel().candidateList(); if (candidateList) { - int idx = keyEvent.key().digitSelection(); + int idx = keyEvent.key().digitSelection(KeyState::Alt); if (idx >= 0) { keyEvent.accept(); if (idx < candidateList->size()) { @@ -264,8 +268,7 @@ Clipboard::Clipboard(Instance *instance) } return; } - if (keyEvent.key().check(FcitxKey_space) || - keyEvent.key().check(FcitxKey_Return) || + if (keyEvent.key().check(FcitxKey_Return) || keyEvent.key().check(FcitxKey_KP_Enter)) { keyEvent.accept(); if (!candidateList->empty() && @@ -337,6 +340,37 @@ Clipboard::Clipboard(Instance *instance) state->reset(inputContext); return; } + + if (keyEvent.key().check(FcitxKey_BackSpace)) { + event.accept(); + if (state->buffer_.empty()) { + state->reset(inputContext); + } else { + if (state->buffer_.backspace()) { + if (state->buffer_.empty()) { + state->reset(inputContext); + } else { + updateUI(inputContext); + } + } + } + return; + } + + // check compose first. + auto compose = instance_->processComposeString( + inputContext, keyEvent.key().sym()); + + // compose is invalid, ignore it. + if (!compose) { + return; + } + + if (!compose->empty()) { + state->buffer_.type(*compose); + } else { + state->buffer_.type(Key::keySymToUnicode(keyEvent.key().sym())); + } event.accept(); updateUI(inputContext); @@ -363,20 +397,31 @@ void Clipboard::trigger(InputContext *inputContext) { updateUI(inputContext); } void Clipboard::updateUI(InputContext *inputContext) { + auto *state = inputContext->propertyFor(&factory_); inputContext->inputPanel().reset(); + const auto &search = state->buffer_.userInput(); + + Text preedit; + preedit.append(search); + if (!state->buffer_.empty()) { + preedit.setCursor(state->buffer_.cursorByChar()); + } + inputContext->inputPanel().setPreedit(preedit); + auto candidateList = std::make_unique(); candidateList->setPageSize(instance_->globalConfig().defaultPageSize()); // Append first item from history_. auto iter = history_.begin(); - if (iter != history_.end()) { + if (iter != history_.end() && + iter->text.find(search) != std::string::npos) { candidateList->append(this, iter->text, iter->passwordTimestamp); iter++; } // Append primary_, but check duplication first. - if (!primary_.empty()) { + if (!primary_.empty() && primary_.text.find(search) != std::string::npos) { if (!history_.contains(primary_)) { candidateList->append( this, primary_.text, primary_.passwordTimestamp); @@ -387,8 +432,10 @@ void Clipboard::updateUI(InputContext *inputContext) { if (candidateList->totalSize() >= config_.numOfEntries.value()) { break; } - candidateList->append(this, iter->text, - iter->passwordTimestamp); + if (iter->text.find(search) != std::string::npos) { + candidateList->append( + this, iter->text, iter->passwordTimestamp); + } } candidateList->setSelectionKey(selectionKeys_); candidateList->setLayoutHint(CandidateLayoutHint::Vertical);