diff --git a/bamboo/bamboo-c.go b/bamboo/bamboo-c.go index 1965f7b6..2e75d4a1 100644 --- a/bamboo/bamboo-c.go +++ b/bamboo/bamboo-c.go @@ -158,6 +158,7 @@ func NewEngine(name *C.cchar, dictHandle uintptr, tableHandle uintptr) uintptr { timeFormat: "%H:%M", dateFormat: "%d/%m/%Y", } + engine.rebuildAppendingKeySet() return uintptr(cgo.NewHandle(engine)) } @@ -201,7 +202,7 @@ func NewCustomEngine(definition **C.char, dictHandle uintptr, tableHandle uintpt timeFormat: "%H:%M", dateFormat: "%d/%m/%Y", } - + engine.rebuildAppendingKeySet() return uintptr(cgo.NewHandle(engine)) } diff --git a/bamboo/bamboo-core b/bamboo/bamboo-core index de3d5b08..5f1974ac 160000 --- a/bamboo/bamboo-core +++ b/bamboo/bamboo-core @@ -1 +1 @@ -Subproject commit de3d5b08c52b8862b38b75fea67baf0631564df1 +Subproject commit 5f1974ac5eb6a540fdb05887dcd21131137f1605 diff --git a/bamboo/fcitxbambooengine.go b/bamboo/fcitxbambooengine.go index c0dcbf6c..b4ba24d5 100644 --- a/bamboo/fcitxbambooengine.go +++ b/bamboo/fcitxbambooengine.go @@ -17,6 +17,7 @@ import ( type FcitxBambooEngine struct { preeditor bamboo.IEngine + appendingKeySet map[rune]struct{} macroTable *MacroTable dictionary map[string]bool autoNonVnRestore bool @@ -208,6 +209,14 @@ func (e *FcitxBambooEngine) getBambooInputMode() bamboo.Mode { return bamboo.VietnameseMode } +func (e *FcitxBambooEngine) rebuildAppendingKeySet() { + keys := e.preeditor.GetInputMethod().AppendingKeys + e.appendingKeySet = make(map[rune]struct{}, len(keys)) + for _, k := range keys { + e.appendingKeySet[k] = struct{}{} + } +} + func inKeyList(list []rune, key rune) bool { for _, s := range list { if s == key { @@ -225,7 +234,10 @@ func (e *FcitxBambooEngine) toUpper(keyRune rune) rune { '}': ']', } - if upperSpecialKey, found := keyMapping[keyRune]; found && inKeyList(e.preeditor.GetInputMethod().AppendingKeys, keyRune) { + if upperSpecialKey, found := keyMapping[keyRune]; found { + if _, ok := e.appendingKeySet[keyRune]; !ok { + return keyRune + } keyRune = upperSpecialKey } return keyRune @@ -272,7 +284,7 @@ func (e *FcitxBambooEngine) getCommitText(keyVal, state uint32) (string, bool) { keyRune = e.toUpper(keyRune) } e.preeditor.ProcessKey(keyRune, e.getBambooInputMode()) - if inKeyList(e.preeditor.GetInputMethod().AppendingKeys, keyRune) { + if _, ok := e.appendingKeySet[keyRune]; ok { var newText string if e.shouldFallbackToEnglish(true) { newText = e.getProcessedString(bamboo.EnglishMode) diff --git a/src/ack-apps.h b/src/app_quirks.h similarity index 50% rename from src/ack-apps.h rename to src/app_quirks.h index 06ca67b7..30d6ac1f 100644 --- a/src/ack-apps.h +++ b/src/app_quirks.h @@ -6,18 +6,25 @@ */ /** - * @file ack-apps.h + * @file app_quirks.h * @brief List of applications requiring acknowledgment workaround. * * These browsers need special handling for uinput mode to work correctly. */ #include -#include +#include /** * @brief List of application names requiring ACK workaround. * * Chromium-based browsers that need special handling for text replacement. */ -static std::vector ack_apps = {"chrome", "chromium", "brave", "edge", "vivaldi", "opera", "coccoc", "cromite", "helium", "thorium", "slimjet", "yandex"}; +inline constexpr std::array ack_apps = {"chrome", "chromium", "brave", "edge", "vivaldi", "opera", + "coccoc", "cromite", "helium", "thorium", "slimjet", "yandex"}; + +/** + * @brief List of application names have goood support surrowding text + * + */ +inline constexpr std::array surrtp_apps = {"soffice"}; diff --git a/src/lotus-engine.cpp b/src/lotus-engine.cpp index 30ab2573..9e9a6b7d 100644 --- a/src/lotus-engine.cpp +++ b/src/lotus-engine.cpp @@ -12,7 +12,7 @@ #include "lotus-candidates.h" #include "lotus-monitor.h" #include "lotus-utils.h" -#include "ack-apps.h" +#include "app_quirks.h" #include #include #ifndef DISABLE_VERSION_ACTION @@ -376,6 +376,7 @@ namespace fcitx { // TODO: Properly fixes instead ugly WA state->wa_chromium_flag = false; + state->surrtp = false; state->waitAck_ = false; if (*config_.fixUinputWithAck) { if (targetMode == LotusMode::Uinput || targetMode == LotusMode::UinputHC || targetMode == LotusMode::Smooth) { @@ -388,12 +389,18 @@ namespace fcitx { if (appName.find(ackApp) != std::string::npos) { if (is_dbus) { state->waitAck_ = true; - LOTUS_INFO(ackApp + " detected, waiting for ack"); + LOTUS_INFO(std::string(ackApp) + " detected, waiting for ack"); } state->wa_chromium_flag = true; break; } } + for (const auto& _App : surrtp_apps) { + if (appName.find(_App) != std::string::npos) { + state->surrtp = true; + break; + } + } } } if (event.type() == EventType::InputContextFocusIn && is_dbus && !surrvalid) { @@ -611,11 +618,10 @@ namespace fcitx { } void LotusEngine::deactivate(const InputMethodEntry& /*entry*/, InputContextEvent& event) { - auto* ic = event.inputContext(); - auto* state = ic->propertyFor(&factory_); - const bool surrvalid = ic->surroundingText().isValid(); - const bool is_dbus = getFrontendName(ic) == "dbus"; - state->lastDeactivateTime_ = now_ms(); + auto* ic = event.inputContext(); + auto* state = ic->propertyFor(&factory_); + const bool surrvalid = ic->surroundingText().isValid(); + const bool is_dbus = getFrontendName(ic) == "dbus"; if (realMode == LotusMode::Preedit && event.type() != EventType::InputContextFocusOut) { state->commitBuffer(); } else { diff --git a/src/lotus-engine.h b/src/lotus-engine.h index 0b617e33..8f10cf22 100644 --- a/src/lotus-engine.h +++ b/src/lotus-engine.h @@ -324,7 +324,7 @@ namespace fcitx { * @param ic Current input context. * @return Name of current program */ - static std::string getProgramName(InputContext* ic); + std::string getProgramName(InputContext* ic); }; /** diff --git a/src/lotus-state.cpp b/src/lotus-state.cpp index 15cddb2f..c4cd0340 100644 --- a/src/lotus-state.cpp +++ b/src/lotus-state.cpp @@ -117,9 +117,7 @@ namespace fcitx { LOTUS_ERROR("Cannot send backspace since cannot connect to uinput server"); return; } - ssize_t n = send(uinput_client_fd_, &count, sizeof(count), MSG_NOSIGNAL); - if (n < 0) { LOTUS_WARN("Failed to send backspace: " + std::string(strerror(errno))); int old_fd = uinput_client_fd_.exchange(-1); @@ -138,6 +136,16 @@ namespace fcitx { } } + void LotusState::send_backspace_forward(int count) const { + if (count <= 0) + return; + for (int i = 0; i < count - 1; ++i) { + ic_->forwardKey(Key(FcitxKey_BackSpace, KeyState::NoState), false); + ic_->forwardKey(Key(FcitxKey_BackSpace, KeyState::NoState), true); + } + send_backspace_uinput(0); // trigger 1bs to make all bs prev release + } + bool LotusState::isAutofillCertain(const SurroundingText& s) { if (!s.isValid() || oldPreBuffer_.empty()) { return false; @@ -451,16 +459,16 @@ namespace fcitx { replacement_thread_id_.store(0, std::memory_order_release); std::this_thread::sleep_for(std::chrono::milliseconds(sleepTime)); // Validate surr cursor pos should match realtextLen after all BS applied - const auto& surr = ic_->surroundingText(); - if (surr.isValid() && surr.cursor() == realtextLen.load(std::memory_order_acquire)) { - LOTUS_INFO("Skip retry"); - } else { - // Retry x3 (2 ms each), khi can (chromium,electron,...) - for (int retry = 0; retry < 3; ++retry) { - std::this_thread::sleep_for(std::chrono::milliseconds(2)); - const auto& surr2 = ic_->surroundingText(); - if (surr2.isValid() && surr2.cursor() == realtextLen.load(std::memory_order_acquire)) { - break; + if (waitAck_) { + const auto& surr = ic_->surroundingText(); + if (!surr.isValid() || surr.cursor() != realtextLen.load(std::memory_order_acquire)) { + // Retry x5 (1 ms each), khi can (chromium,electron,...) + for (int retry = 0; retry < 5; ++retry) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + const auto& surr2 = ic_->surroundingText(); + if (surr2.isValid() && surr2.cursor() == realtextLen.load(std::memory_order_acquire)) { + break; + } } } } @@ -471,31 +479,68 @@ namespace fcitx { pending_commit_string_ = ""; event.filterAndAccept(); // Filter out the final trigger backspace. - if (getFrontendName(ic_) == "dbus" && !ic_->surroundingText().isValid()) - replayBufferedKeys(); // Does we need drop this? + //if (getFrontendName(ic_) == "dbus" && !ic_->surroundingText().isValid()) + // replayBufferedKeys(); // Does we need drop this? return true; } return false; } - void LotusState::performReplacement(const std::string& deletedPart, const std::string& addedPart) { + bool LotusState::performReplacement(const std::string& deletedPart, const std::string& addedPart) { LOTUS_INFO("Perform replacement: " + deletedPart + " -> " + addedPart); //NOLINT int my_id = ++current_thread_id_; current_backspace_count_ = 0; pending_commit_string_ = addedPart; const auto& surrounding = ic_->surroundingText(); - // Enable Autofill detection for all frontends (Wayland/IBus). - // This fixes the "toôi" duplication bug in Chromium-based search bars. - // The isAutofillCertain function has been optimized to differentiate - // between browser autofill and AI ghost text. - int autofillOffset = isAutofillCertain(surrounding) ? 1 : 0; + LOTUS_INFO("surroundingText: \"" + surrounding.text() + "\""); + int autofillOffset = isAutofillCertain(surrounding) ? 1 : 0; + // This is bushjt wa + if (wa_chromium_flag && !autofillOffset) { + if (surrounding.isValid()) + everHadValidSurr_ = true; + bool surroundingCapable = ic_->capabilityFlags().test(CapabilityFlag::SurroundingText); + if (!everHadValidSurr_ && surroundingCapable && !oldPreBuffer_.empty()) + autofillOffset = 1; + } + + LOTUS_INFO("surroundingText: \"" + surrounding.text() + "\""); expected_backspaces_ = static_cast(utf8::length(deletedPart)) + 1 + autofillOffset; - replacement_thread_id_.store(my_id, std::memory_order_release); - replacement_start_ms_.store(now_ms(), std::memory_order_release); - is_deleting_.store(true, std::memory_order_release); - monitor_cv.notify_one(); - send_backspace_uinput(expected_backspaces_); - LOTUS_INFO("Send " + std::to_string(expected_backspaces_) + " backspaces"); + // Use deleteSurroundingText for apps that support it for smooth typing + if (surrtp // Lmfao, only this work :> + && surrounding.isValid() && ic_->capabilityFlags().test(CapabilityFlag::SurroundingText) && + (surrounding.text()).back() != '\n' // firefox and discord insert '\n' into surrounding cause bug + && !(autofillOffset) // TODO: Guard, remove this when bug of surrounding is fixes + ) { + auto cur = static_cast(surrounding.cursor()); + const int bsCount = static_cast(utf8::length(deletedPart)); + if (autofillOffset) { + int surrLen = static_cast(utf8::length(surrounding.text())); + int realLen = static_cast(cur); + int suggestionLen = surrLen - realLen; + // delete suggestion tail + if (suggestionLen > 0) + ic_->deleteSurroundingText(0, 1); + // delete addedPart + if (bsCount > 0) + ic_->deleteSurroundingText(-bsCount, static_cast(bsCount)); + } else { + if (bsCount > 0) { + ic_->deleteSurroundingText(-bsCount, static_cast(bsCount)); + } + } + ic_->commitString(addedPart); + //clearAllBuffers(); + return true; + } else { + replacement_thread_id_.store(my_id, std::memory_order_release); + replacement_start_ms_.store(now_ms(), std::memory_order_release); + is_deleting_.store(true, std::memory_order_release); + monitor_cv.notify_one(); + //send_backspace_forward(expected_backspaces_ - 1); + send_backspace_uinput(expected_backspaces_); + LOTUS_INFO("Send " + std::to_string(expected_backspaces_) + " backspaces"); + } + return false; } bool LotusState::checkForwardSpecialKey(KeyEvent& keyEvent, KeySym& currentSym) { @@ -600,8 +645,9 @@ namespace fcitx { compareAndSplitStrings(oldPreBuffer_, commitStr, commonPrefix, deletedPart, addedPart); if (!deletedPart.empty()) { - performReplacement(deletedPart, addedPart); keyEvent.filterAndAccept(); + if (performReplacement(deletedPart, addedPart)) + keyEvent.forward(); } else { bool wasAutoCapitalized = (currentSym != keyEvent.rawKey().sym()); if (!addedPart.empty() && (keyUtf8 != addedPart || wasAutoCapitalized)) { @@ -655,6 +701,7 @@ namespace fcitx { if (wa_chromium_flag) keyEvent.filterAndAccept(); + LOTUS_INFO("surroundingText: \"" + ic_->surroundingText().text() + "\""); if (compareAndSplitStrings(oldPreBuffer_, preeditStr, commonPrefix, deletedPart, addedPart) != 0) { if (deletedPart.empty()) { bool isCommit = false; @@ -669,6 +716,21 @@ namespace fcitx { isCommit = true; } } + if (!wa_chromium_flag) + if (!isCommit) { + keyEvent.forward(); + bool hasMultibyte = false; + for (unsigned char c : oldPreBuffer_) + if (c > 0x7F) { + hasMultibyte = true; + break; + } + if (!hasMultibyte && utf8::length(oldPreBuffer_) > 8) { + ResetEngine(lotusEngine_.handle()); + hasHistory_ = false; + oldPreBuffer_.clear(); + } + } } if (!wa_chromium_flag && !isCommit) { keyEvent.forward(); @@ -689,7 +751,8 @@ namespace fcitx { if (!wa_chromium_flag) keyEvent.filterAndAccept(); - performReplacement(deletedPart, addedPart); + if (performReplacement(deletedPart, addedPart)) + keyEvent.forward(); oldPreBuffer_ = preeditStr; } } @@ -903,8 +966,8 @@ namespace fcitx { } replacement_thread_id_.store(0, std::memory_order_release); replacement_start_ms_.store(0, std::memory_order_release); - if (getFrontendName(ic_) == "dbus" && !ic_->surroundingText().isValid()) - replayBufferedKeys(); // Does we need drop this? + //if (getFrontendName(ic_) == "dbus" && !ic_->surroundingText().isValid()) + // replayBufferedKeys(); // Does we need drop this? } KeySym currentSym = keyEvent.rawKey().sym(); if (*engine_->config().autoCapitalizeAfterPunctuation && realMode != LotusMode::Off) { @@ -1095,17 +1158,16 @@ namespace fcitx { return; } oldPreBuffer_.clear(); - hasHistory_ = false; - if (!is_deleting_.load(std::memory_order_acquire)) { - expected_backspaces_ = 0; - current_backspace_count_ = 0; - pending_commit_string_.clear(); - } + hasHistory_ = false; + expected_backspaces_ = 0; + current_backspace_count_ = 0; + pending_commit_string_.clear(); emojiBuffer_.clear(); emojiCandidates_.clear(); buffered_keys_.clear(); shouldCapitalize_ = false; isPrevPunctuation_ = false; + everHadValidSurr_ = false; if (lotusEngine_) ResetEngine(lotusEngine_.handle()); } @@ -1113,14 +1175,13 @@ namespace fcitx { bool LotusState::isEmptyHistory() const { return !hasHistory_; } - + /* void LotusState::replayBufferedKeys() { LOTUS_INFO("Starting replay buffered keys"); if (buffered_keys_.empty()) { return; } auto keys = std::move(buffered_keys_); - buffered_keys_.clear(); for (size_t i = 0; i < keys.size(); ++i) { auto sym = static_cast(keys[i].sym); uint32_t state = keys[i].state; @@ -1199,6 +1260,7 @@ namespace fcitx { } } performReplacement(deletedPart, addedPart); + oldPreBuffer_ = preeditStr; return; } @@ -1206,4 +1268,5 @@ namespace fcitx { } LOTUS_INFO("Replay buffered keys done"); } +*/ } // namespace fcitx diff --git a/src/lotus-state.h b/src/lotus-state.h index cbef0034..1f2f6f57 100644 --- a/src/lotus-state.h +++ b/src/lotus-state.h @@ -107,6 +107,8 @@ namespace fcitx { bool isPrevPunctuation_ = false; int64_t lastDeactivateTime_ = 0; bool wa_chromium_flag = false; + bool surrtp = false; + bool everHadValidSurr_ = false; /** * @brief Connects to the uinput server. @@ -125,7 +127,7 @@ namespace fcitx { * @param count Number of backspaces to send. */ void send_backspace_uinput(int count) const; - + void send_backspace_forward(int count) const; /** * @brief Checks if autofill is certain for surrounding text. * @param s The surrounding text. @@ -171,7 +173,7 @@ namespace fcitx { * @param deletedPart Text to delete. * @param addedPart Text to insert. */ - void performReplacement(const std::string& deletedPart, const std::string& addedPart); + bool performReplacement(const std::string& deletedPart, const std::string& addedPart); /** * @brief Handles the double space to period replacement.