From 8598b72ec45a4004f5c642862f42304d391424f0 Mon Sep 17 00:00:00 2001 From: nguyenphivn Date: Tue, 8 Sep 2026 21:17:24 +0700 Subject: [PATCH] =?UTF-8?q?th=E1=BB=AD=20nghi=E1=BB=87m:=20ch=E1=BB=9D=20s?= =?UTF-8?q?=E1=BB=B1=20ki=E1=BB=87n=20surrounding=20text=20thay=20v=C3=AC?= =?UTF-8?q?=20ng=E1=BB=A7=20theo=20h=E1=BA=B1ng=20s=E1=BB=91=20(m=E1=BA=B7?= =?UTF-8?q?c=20=C4=91=E1=BB=8Bnh=20T=E1=BA=AET)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Chế độ uinput bắn phím xoá qua nhân rồi giao chữ mới qua Wayland. Hai đường khác nhau nên chữ mới có thể vượt mặt phím xoá, gõ nhanh là mất chữ. Bản hiện tại chờ bằng cách ngủ 2 ms mỗi phím xoá rồi thử lại 3 lần × 2 ms. Chỗ ngủ đó không thể đúng được: fcitx5 chỉ có MỘT vòng lặp sự kiện, mà tin "ô nhập đã đổi" (InputContextSurroundingTextUpdated) lại đi qua đúng vòng lặp đang bị chặn. Ngủ bao lâu cũng không thấy tin tới; vòng thử lại 3 × 2 ms vì thế vô nghĩa về mặt cấu trúc. Thay bằng: trả vòng lặp về ngay, đăng ký nghe sự kiện đó, kiểm bằng NỘI DUNG ảnh ô nhập, quá hạn thì giao như cũ. Phím người gõ trong lúc chờ vẫn vào buffered_keys_ như trước. Bảy công tắc cấu hình, tất cả mặc định giữ nguyên hành vi cũ (WaitSurroundingEvent mặc định false). Để nguyên để ai muốn đo lại có chỗ vặn: WaitSurroundingEvent bật đường chờ sự kiện (mặc định false) WaitSurroundingTimeoutMs hạn chờ, 50 WaitSurroundingShortMs hạn rút sau hai lần quá hạn liền, 40 WaitSurroundingProbeEvery app đóng băng ảnh thì thăm dò lại mỗi N lần, 4 WaitSurroundingMinPerKeyMs ngưỡng tin ảnh "trông như xong", 8 ms mỗi phím xoá SurrDeleteSleepMs hằng số ngủ cũ của đường surrounding text, 4 SurrCommitSleepMs hằng số ngủ cũ sau khi giao chữ, 3 Số đo trên KDE Wayland (CachyOS), bàn phím ảo uinput, cùng một bản dựng, đối chứng là tắt công tắc: Firefox 4 ô soạn 60/60 ở nhịp 5 ms và ở nhịp ngẫu nhiên 5-120 ms (bản hiện tại: 9-14/15) Kate, Konsole, 15/15 mỗi app ở nhịp 50 ms và 5 ms Alacritty, KDialog, Meld, Electron Edge 4 ô trang 60/60 ở nhịp 50 ms Edge thanh địa chỉ 15/15 ở nhịp 50 ms; 14/15 ở nhịp 5 ms (ca sai là lỗi Chromium #190, có ở cả bản hiện tại) Chữ hiện trễ thêm: trung vị 6-8 ms ở app thường, 25 ms ở Firefox. Đánh đổi đã đo được, xin khai thẳng: đường này giữ chữ đang treo qua một nhịp của vòng lặp, nên nếu người dùng đổi cửa sổ trong vòng 50 ms sau khi bấm phím dấu thì ô CŨ mất chữ đó. Đã thử vá bằng cách giao nốt lúc mất tiêu điểm — nhật ký ghi đúng nhưng chữ vẫn không tới nơi, vì fcitx5 không giao được vào input context đang mất tiêu điểm. Đường ngủ cũ không có khe này vì nó chặn vòng lặp. Chữ KHÔNG rơi sang cửa sổ mới (đã đo), và cửa sổ mới vẫn gõ bình thường. ctest 8/8 (bài smooth_buffered_key_replay cần chạy trong network namespace riêng vì fcitx5-lotus-server đang giữ socket). --- src/lotus-config.h | 7 ++ src/lotus-engine.cpp | 1 + src/lotus-state.cpp | 218 +++++++++++++++++++++++++++++++++++++++++-- src/lotus-state.h | 32 +++++++ 4 files changed, 252 insertions(+), 6 deletions(-) diff --git a/src/lotus-config.h b/src/lotus-config.h index 6c2177a7..e98b2a45 100644 --- a/src/lotus-config.h +++ b/src/lotus-config.h @@ -241,6 +241,13 @@ namespace fcitx { Option freeMarking{this, "FreeMarking", _("Allow Type With More Freedom"), true}; Option ddFreeStyle{this, "DdFreeStyle", _("Allow dd To Produce đ When Auto Restore Invalid Words Is On"), true}; Option fixUinputWithAck{this, "FixUinputWithAck", _("Fix Uinput Mode With Ack"), false}; + Option waitSurroundingEvent{this, "WaitSurroundingEvent", _("Experiment: after uinput backspaces, wait for the surrounding-text event instead of sleeping"), false}; + Option waitSurroundingTimeoutMs{this, "WaitSurroundingTimeoutMs", _("Experiment: timeout (ms) for that wait"), 50}; + Option waitSurroundingShortMs{this, "WaitSurroundingShortMs", _("Experiment: shorter timeout (ms) used after two consecutive timeouts without a matching event"), 40}; + Option waitSurroundingProbeEvery{this, "WaitSurroundingProbeEvery", _("Experiment: when the app's snapshot is frozen during deletion, stop waiting and re-probe once every N replacements"), 4}; + Option waitSurroundingMinPerKeyMs{this, "WaitSurroundingMinPerKeyMs", _("Experiment: a snapshot identical to the one at send time counts as done only after this many ms per backspace"), 8}; + Option surrDeleteSleepMs{this, "SurrDeleteSleepMs", _("Experiment: ms per deleted char to sleep after deleteSurroundingText"), 4}; + Option surrCommitSleepMs{this, "SurrCommitSleepMs", _("Experiment: ms per committed char to sleep after commitString on the surrounding-text path"), 3}; Option useLotusIcons{this, "UseLotusIcons", _("Use Lotus Status Icons"), false}; Option enableDictionary{this, "EnableDictionary", _("Custom Dictionary"), false}; diff --git a/src/lotus-engine.cpp b/src/lotus-engine.cpp index 72857bf1..c5422e24 100644 --- a/src/lotus-engine.cpp +++ b/src/lotus-engine.cpp @@ -777,6 +777,7 @@ namespace fcitx { auto* state = ic->propertyFor(&factory_); const bool surrvalid = ic->surroundingText().isValid(); const bool is_dbus = getFrontendName(ic) == "dbus"; + state->xaChoDangCho(); // v13: chữ còn treo thì giao vào ô CŨ trước khi rời đi state->lastDeactivateTime_ = now_ms(); if (realMode == LotusMode::Preedit && event.type() != EventType::InputContextFocusOut) { state->commitBuffer(); diff --git a/src/lotus-state.cpp b/src/lotus-state.cpp index a62df55e..4910b54b 100644 --- a/src/lotus-state.cpp +++ b/src/lotus-state.cpp @@ -436,6 +436,92 @@ namespace fcitx { ic_->updateUserInterface(UserInterfaceComponent::InputPanel); } + bool LotusState::oDaXoaXong() const { + const auto& s = ic_->surroundingText(); + if (!s.isValid()) { + return false; + } + const std::string& t = s.text(); + // v4: ảnh y hệt ảnh lúc bắn phím xoá thì chưa thể là "xoá xong" — ở nhịp 20 ms ảnh cũ tụt + // lại hai phím trông giống hệt trạng thái sau khi xoá (đo được: 'đươợ'). + if (!cho_anh_luc_gui_.empty() && t + "\x1f" + std::to_string(s.cursor()) == cho_anh_luc_gui_) { + // v8: ở 5 ms ảnh lúc bắn LUÔN chậm một phím nên trông y hệt trạng thái xong, và Firefox không + // gửi trạng thái trung gian — đo được 37/52 lần quá hạn là tin đúng bị gạt, mỗi lần 200 ms. + // Nội dung không phân biệt được; dùng thời gian: đã chờ đủ ngưỡng Slow (8 ms × phím xoá, + // đo 60/60) thì giao không kém an toàn hơn Slow. Kiểm "ngay" (0 ms) vẫn bị chặn như v4. + const auto da_cho = (::fcitx::now(CLOCK_MONOTONIC) - cho_surr_bat_dau_) / 1000; + const auto toi_thieu = static_cast(engine_->config().waitSurroundingMinPerKeyMs.value()) + * static_cast(std::max(expected_backspaces_, 1)); + if (da_cho < toi_thieu) { + return false; + } + } + auto it = t.begin(); + for (unsigned int i = 0; i < s.cursor() && it != t.end(); ++i) { + it = utf8::nextChar(it); + } + const std::string before(t.begin(), it); + auto endsWith = [](const std::string& a, const std::string& b) { + return a.size() >= b.size() && a.compare(a.size() - b.size(), b.size(), b) == 0; + }; + if (!cho_deleted_.empty() && endsWith(before, cho_prefix_ + cho_deleted_)) { + return false; // ảnh cũ: phần cần xoá vẫn còn + } + return endsWith(before, cho_prefix_); + } + + // v13: đường chờ sự kiện trả vòng lặp về NGAY, nên người dùng kịp đổi cửa sổ trong lúc chữ + // còn treo. deactivate() tắt is_deleting_ ⇒ đồng hồ nổ sau đó thấy cờ tắt là bỏ chữ không một + // lời (đo được: thanh địa chỉ Edge còn 't' đáng lẽ 'tô'). Đường ngủ cũ không có khe này vì nó + // ngủ ngay trong hàm xử lý phím. Giao nốt trước khi rời ô. + void LotusState::xaChoDangCho() { + if (!cho_dang_cho_) { + return; + } + ketThucThayChu("mat tieu diem", false); + } + + void LotusState::ketThucThayChu(const char* ly_do, bool tu_timer) { + const auto tre_ms = (::fcitx::now(CLOCK_MONOTONIC) - cho_surr_bat_dau_) / 1000; + LOTUS_INFO("Surr wait " + std::string(ly_do) + " after " + std::to_string(tre_ms) + " ms"); + if (std::string(ly_do) == "qua han") { + cho_anh_tin_cay_ = false; + ++cho_qua_han_lien_tiep_; + // v11: đòi ≥ 2 tin và KHÔNG tin nào khác ảnh lúc bắn — Firefox tụt thật thường im hẳn + // (0 tin) hoặc gửi ảnh đang nhích, không tính là đóng băng. + if (cho_so_tin_ >= 2 && !cho_tin_khac_ && cho_anh_gui_cap_nhat_) { + if (++cho_dong_bang_lien_tiep_ >= 2 && !cho_dong_bang_) { + cho_dong_bang_ = true; + cho_dem_tham_do_ = 0; + LOTUS_INFO("Surr frozen: stop waiting"); + } + } else { + cho_dong_bang_lien_tiep_ = 0; + } + } else if (std::string(ly_do) == "su kien" || std::string(ly_do) == "nguong") { + cho_anh_tin_cay_ = true; + cho_qua_han_lien_tiep_ = 0; + cho_dong_bang_lien_tiep_ = 0; + if (cho_dong_bang_) { + cho_dong_bang_ = false; + LOTUS_INFO("Surr frozen: resume waiting"); + } + } + cho_dang_cho_ = false; + if (!tu_timer && cho_surr_timer_) { + cho_surr_timer_.reset(); // không reset từ trong chính callback của nó + } + if (!pending_commit_string_.empty()) { + ic_->commitString(pending_commit_string_); + LOTUS_INFO("Commit: " + pending_commit_string_); + } + expected_backspaces_ = 0; + current_backspace_count_ = 0; + pending_commit_string_.clear(); + is_deleting_.store(false); + replayBufferedKeys(); + } + bool LotusState::handleUInputKeyPress(KeyEvent& event, KeySym currentSym, int sleepTime) { if (!is_deleting_.load()) { return false; @@ -445,10 +531,108 @@ namespace fcitx { if (current_backspace_count_ < expected_backspaces_) { return false; // Allow intermediate backspaces to reach the app to clear autofill/old text. } - std::this_thread::sleep_for(std::chrono::milliseconds(sleepTime * (expected_backspaces_ - 1))); + // v7: app khai "có surrounding text" nhưng gửi ảnh RỖNG (Konsole: valid=1 len=0 suốt) thì + // không tin nào khớp được, chờ chỉ tốn trọn hạn mỗi dấu → đi đường ngủ cũ ở dưới. + const bool anh_rong = ic_->surroundingText().text().empty(); + if (engine_->config().waitSurroundingEvent.value() && anh_rong) { + LOTUS_INFO("Surr wait skip: empty snapshot"); + } + bool bo_cho_dong_bang = false; + if (engine_->config().waitSurroundingEvent.value() && !anh_rong && cho_dong_bang_) { + const int moi = std::max(engine_->config().waitSurroundingProbeEvery.value(), 1); + ++cho_dem_tham_do_; + if (cho_dem_tham_do_ % moi != 0) { + bo_cho_dong_bang = true; + } + } + if (engine_->config().waitSurroundingEvent.value() && !anh_rong && !bo_cho_dong_bang) { + // fcitx5 chỉ có MỘT vòng lặp sự kiện, nên ngủ + thử lại ở dưới + // không bao giờ thấy được tin "ô đã đổi" đến trong lúc ngủ. Ở đây trả vòng lặp + // về ngay, đăng ký nghe InputContextSurroundingTextUpdated ĐÚNG LÚC NÀY (v1 để + // người nghe cũ sống qua lần thay sau → bắt tin trễ của phím trước → giao sớm → + // 0/15). Kiểm bằng NỘI DUNG (oDaXoaXong), không dùng realtextLen. Quá hạn thì + // giao như cũ. Phím người gõ trong lúc chờ vẫn vào buffered_keys_. + event.filterAndAccept(); + cho_surr_bat_dau_ = ::fcitx::now(CLOCK_MONOTONIC); + // v6: sau một lần quá hạn thì Firefox đang tụt lại, ảnh không đáng tin → bỏ kiểm ngay + if (cho_anh_tin_cay_ && oDaXoaXong()) { + LOTUS_INFO("Skip retry"); + ketThucThayChu("ngay", false); + return true; + } + auto* instance = engine_->instance(); + cho_dang_cho_ = true; + cho_so_tin_ = 0; + cho_tin_khac_ = false; + cho_surr_watcher_.reset(); // ngoài dispatch của nó, an toàn + cho_surr_watcher_ = instance->watchEvent(EventType::InputContextSurroundingTextUpdated, EventWatcherPhase::Default, [this](Event& e) { + auto& ice = static_cast(e); + if (!cho_dang_cho_ || ice.inputContext() != ic_ || !is_deleting_.load()) { + return; + } + // v9: vừa quá hạn = app đang tụt, ảnh nó báo là bộ đệm cập nhật trễ ('trươnờ': tin + // 0 ms hiện 'trư' là ảnh cũ vừa bắt kịp, không phải xoá xong). Lúc đó KHÔNG tin tin nào + // tới trước ngưỡng Slow (8 ms × phím xoá). + const auto da_cho_us = ::fcitx::now(CLOCK_MONOTONIC) - cho_surr_bat_dau_; + const auto toi_thieu_us = static_cast(engine_->config().waitSurroundingMinPerKeyMs.value()) + * static_cast(std::max(expected_backspaces_, 1)) * 1000ULL; + { + const auto& sd0 = ic_->surroundingText(); + ++cho_so_tin_; + if (sd0.text() + "\x1f" + std::to_string(sd0.cursor()) != cho_anh_luc_gui_) { + cho_tin_khac_ = true; + } + } + if (!cho_anh_tin_cay_ && da_cho_us < toi_thieu_us) { + // v9: vừa quá hạn xong thì tin tới sớm là bộ đệm cũ bắt kịp, không tin. + } else if (oDaXoaXong()) { + ketThucThayChu("su kien", false); + } + // Ảnh chưa khớp thì im lặng chờ tiếp: mọi thứ đáng in ở đây đều là chữ + // người dùng vừa gõ, không đưa vào nhật ký. + }); + // v7: hai lần quá hạn liên tiếp mà chưa có tin khớp = app này không cập nhật ảnh trong lúc + // xoá (Edge thanh địa chỉ: ảnh chỉ đổi khi gõ chữ thường) → rút hạn xuống mức ngắn, đủ + // trên cửa sổ vượt mặt (Slow 8 ms/phím đo 60/60); có tin khớp thì trả hạn dài lại. + const int han_ms = cho_qua_han_lien_tiep_ >= 2 ? engine_->config().waitSurroundingShortMs.value() + : engine_->config().waitSurroundingTimeoutMs.value(); + const auto han = static_cast(han_ms) * 1000ULL; + // v7.1: accuracy 0 với sd-event = MẶC ĐỊNH 250 ms (đồng hồ được phép nổ muộn tới 250 ms): + // đo được: hạn 40 nổ ở 64, hạn 200 nổ ở 243, tệ nhất 430. Đặt 1 ms. + // v10: 16/27 lần quá hạn còn lại của v9 là tin "xong" tới TRƯỚC ngưỡng Slow rồi app im + // luôn → bị gạt và chờ trọn 200 ms. Đặt thêm một mốc đúng tại ngưỡng: kiểm lại ảnh mới nhất + // đã nhận, khớp thì giao ngay ("nguong"); chưa thì dời đồng hồ tới hạn. + const auto nguong = static_cast(engine_->config().waitSurroundingMinPerKeyMs.value()) + * static_cast(std::max(expected_backspaces_, 1)) * 1000ULL; + const auto moc_dau = nguong < han ? cho_surr_bat_dau_ + nguong : cho_surr_bat_dau_ + han; + cho_surr_timer_ = instance->eventLoop().addTimeEvent(CLOCK_MONOTONIC, moc_dau, 1000, [this, han](EventSourceTime* t, uint64_t) { + if (!cho_dang_cho_ || !is_deleting_.load()) { + return false; + } + const auto da_cho = ::fcitx::now(CLOCK_MONOTONIC) - cho_surr_bat_dau_; + if (da_cho + 1000 < han) { + if (oDaXoaXong()) { + ketThucThayChu("nguong", true); + return false; + } + t->setTime(cho_surr_bat_dau_ + han); + t->setOneShot(); + return true; + } + ketThucThayChu("qua han", true); + return false; + }); + return true; + } + // v11: đóng băng → đường ngủ cũ nhưng với hằng số của Slow (8 × (N − 1), đo 60/60 trên + // Firefox), không ngủ chồng: đo được: ngủ thêm 8 × N làm N=1 mất 24 ms, N=2 mất 34 ms. + const int ngu_moi_phim = bo_cho_dong_bang ? std::max(sleepTime, engine_->config().waitSurroundingMinPerKeyMs.value()) : sleepTime; + std::this_thread::sleep_for(std::chrono::milliseconds(ngu_moi_phim * (expected_backspaces_ - 1))); // 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)) { + if (bo_cho_dong_bang) { + LOTUS_INFO("Skip retry (frozen)"); // v12: thử lại 3 × 2 ms là vô ích khi ảnh đóng băng + } else 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,...) @@ -479,6 +663,26 @@ namespace fcitx { current_backspace_count_ = 0; pending_commit_string_ = addedPart; expected_backspaces_ = static_cast(utf8::length(deletedPart)); + cho_deleted_ = deletedPart; + { const auto& sg = ic_->surroundingText(); cho_anh_luc_gui_ = sg.isValid() ? sg.text() + "\x1f" + std::to_string(sg.cursor()) : std::string(); } + cho_prefix_ = (oldPreBuffer_.size() >= deletedPart.size()) ? oldPreBuffer_.substr(0, oldPreBuffer_.size() - deletedPart.size()) : std::string(); + { + // v12: ảnh lúc bắn "cập nhật" = phần trước con trỏ kết thúc bằng prefix+deleted. Firefox tụt + // thì ảnh lúc bắn đã chậm (không thấy phần sắp xoá) → không được tính là đóng băng + // (nhịp ngẫu nhiên kích hoạt nhầm 3 lần trên Firefox). + cho_anh_gui_cap_nhat_ = false; + const auto& sg2 = ic_->surroundingText(); + if (sg2.isValid()) { + const std::string& t = sg2.text(); + auto it = t.begin(); + for (unsigned int i = 0; i < sg2.cursor() && it != t.end(); ++i) { + it = utf8::nextChar(it); + } + const std::string truoc(t.begin(), it); + const std::string can = cho_prefix_ + cho_deleted_; + cho_anh_gui_cap_nhat_ = truoc.size() >= can.size() && truoc.compare(truoc.size() - can.size(), can.size(), can) == 0; + } + } const auto& surrounding = ic_->surroundingText(); const std::string surrText = surrounding.text(); bool isSurrText = engine_->config().useSurroundingTextIfPossible.value() && ic_->capabilityFlags().test(CapabilityFlag::SurroundingText) && surrounding.isValid() && @@ -499,11 +703,11 @@ namespace fcitx { if (isSurrText) { ic_->deleteSurroundingText(-expected_backspaces_, expected_backspaces_); LOTUS_INFO("Delete using surrounding text"); - std::this_thread::sleep_for(std::chrono::milliseconds(4 * expected_backspaces_)); + std::this_thread::sleep_for(std::chrono::milliseconds(engine_->config().surrDeleteSleepMs.value() * expected_backspaces_)); if (!pending_commit_string_.empty()) { ic_->commitString(pending_commit_string_); LOTUS_INFO("Commit: " + pending_commit_string_); - std::this_thread::sleep_for(std::chrono::milliseconds(3 * utf8::length(addedPart))); + std::this_thread::sleep_for(std::chrono::milliseconds(engine_->config().surrCommitSleepMs.value() * utf8::length(addedPart))); } expected_backspaces_ = 0; current_backspace_count_ = 0; @@ -807,7 +1011,7 @@ namespace fcitx { if (charsToDelete > 0) { ic->deleteSurroundingText(-static_cast(charsToDelete), static_cast(charsToDelete)); - std::this_thread::sleep_for(std::chrono::milliseconds(4 * charsToDelete)); + std::this_thread::sleep_for(std::chrono::milliseconds(engine_->config().surrDeleteSleepMs.value() * charsToDelete)); } if (!addedPart.empty()) { @@ -1004,7 +1208,9 @@ namespace fcitx { LOTUS_WARN("Cannot connect to uinput server, reconnecting...."); connect_uinput_server(); } - if (current_backspace_count_ >= expected_backspaces_ && is_deleting_.load()) { + // Van an toàn này tắt cờ lặng lẽ ở phím kế tiếp; khi đang chờ sự kiện thì phải bỏ qua, + // nếu không chữ chờ giao bị vứt (v2: " Nam"). + if (!cho_dang_cho_ && current_backspace_count_ >= expected_backspaces_ && is_deleting_.load()) { is_deleting_.store(false); current_backspace_count_ = 0; expected_backspaces_ = 0; diff --git a/src/lotus-state.h b/src/lotus-state.h index f72645b6..6a8ef14d 100644 --- a/src/lotus-state.h +++ b/src/lotus-state.h @@ -22,6 +22,11 @@ #include #include #include +#include +#include +#include +#include +#include struct EmojiEntry; @@ -84,6 +89,12 @@ namespace fcitx { friend class EmojiCandidateWord; friend class LotusEngine; + /** + * @brief v13: giao nốt chữ đang treo khi ô nhập sắp mất tiêu điểm. + * Không có nó thì đồng hồ hết hạn sau đó thấy is_deleting_ đã tắt và lặng lẽ bỏ chữ. + */ + void xaChoDangCho(); + private: static constexpr size_t MAX_BUFFERED_KEYS = 50; @@ -127,6 +138,27 @@ namespace fcitx { */ void send_backspace_uinput(int count) const; + // Chờ theo sự kiện thay vì ngủ (xem handleUInputKeyPress) + std::unique_ptr> cho_surr_watcher_; + std::unique_ptr cho_surr_timer_; + uint64_t cho_surr_bat_dau_ = 0; + bool cho_dang_cho_ = false; + std::string cho_prefix_; // phần từ giữ lại sau khi xoá + std::string cho_deleted_; // phần phải biến mất + std::string cho_anh_luc_gui_; // ảnh chụp lúc bắn phím xoá + // v11: ảnh ĐÓNG BĂNG = quá hạn mà mọi tin đều y hệt ảnh lúc bắn (Edge thanh địa chỉ). Hai lần liền + // → bỏ chờ, ngủ 8 ms × phím xoá như Slow; cứ `probeEvery` lần thăm dò lại một lần. + int cho_so_tin_ = 0; // số tin trong lần chờ này + bool cho_tin_khac_ = false; // có tin nào khác ảnh lúc bắn + bool cho_anh_gui_cap_nhat_ = false; // v12: ảnh lúc bắn còn thấy phần sắp xoá + int cho_dong_bang_lien_tiep_ = 0; + bool cho_dong_bang_ = false; + int cho_dem_tham_do_ = 0; + int cho_qua_han_lien_tiep_ = 0; // v7: >=2 thì rút hạn chờ (Edge thanh địa chỉ không bao giờ khớp) + bool cho_anh_tin_cay_ = true; // false sau một lần quá hạn, true lại khi có tin khớp + bool oDaXoaXong() const; + void ketThucThayChu(const char* ly_do, bool tu_timer); + /** * @brief Checks if autofill is certain for surrounding text. * @param s The surrounding text.