From ba269e2a77cd87492571bcac040b5a8206c1e823 Mon Sep 17 00:00:00 2001 From: nguyenphivn Date: Sun, 6 Sep 2026 22:13:11 +0700 Subject: [PATCH 1/3] =?UTF-8?q?fix:=20gi=E1=BB=AF=20lu=E1=BA=ADt=20theo=20?= =?UTF-8?q?app=20khi=20n=E1=BA=A1p=20l=E1=BA=A1i=20c=E1=BA=A5u=20h=C3=ACnh?= =?UTF-8?q?=20(B28)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `LotusState::setEngine()` ghi thẳng `realMode = config.mode` — mặc định CHUNG. Nhưng `setEngine()` chạy trong hàm dựng của MỌI `LotusState` và trong `refreshEngine()` cho MỌI input context, mà cả hai đều không biết cửa sổ nào đang gõ. Kết quả: một input context mới (ứng dụng khác vừa mở) hoặc một lần nạp lại cấu hình sẽ thổi bay luật riêng của cửa sổ đang hoạt động, cho tới lần đổi focus kế tiếp. Sửa: - `setEngine()` không còn ghi `realMode`. - `refreshEngine()` đặt lại chế độ theo đúng luật của app đang có focus qua `setMode(getAppRule(getProgramName(ic)), ic)`. `reloadConfig()` đã gọi `loadAppRules()` trước `populateConfig()` nên luật đọc ở đây là luật mới. - Không có cửa sổ nào đang gõ thì giữ mặc định chung như hành vi cũ; lần focus kế tiếp `activate()` đặt lại cho đúng. Sau thay đổi này `realMode` chỉ còn được ghi ở `setMode()`, tức mọi lần ghi đều đi qua tra luật theo app. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01TvNe693ge85eB4QPs6hpjV --- src/lotus-engine.cpp | 16 ++++++++++++++-- src/lotus-state.cpp | 7 ++++++- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/lotus-engine.cpp b/src/lotus-engine.cpp index b30222c0..13fccbb2 100644 --- a/src/lotus-engine.cpp +++ b/src/lotus-engine.cpp @@ -780,13 +780,25 @@ namespace fcitx { void LotusEngine::refreshEngine() { if (!factory_.registered()) return; - instance_->inputContextManager().foreach ([this](InputContext* ic) { + bool coCuaSoDangGo = false; + instance_->inputContextManager().foreach ([this, &coCuaSoDangGo](InputContext* ic) { auto* state = ic->propertyFor(&factory_); state->setEngine(); - if (ic->hasFocus()) + if (ic->hasFocus()) { + // Đặt lại chế độ theo ĐÚNG luật của app đang gõ. Trước đây setEngine() + // ghi thẳng mặc định chung vào realMode nên nạp lại cấu hình là mất luật + // riêng của cửa sổ đang hoạt động. + setMode(getAppRule(getProgramName(ic)), ic); state->reset(); + coCuaSoDangGo = true; + } return true; }); + // Không có cửa sổ nào đang gõ thì không có luật riêng nào để theo; giữ mặc định + // chung như hành vi cũ. Lần focus kế tiếp activate() sẽ đặt lại cho đúng. + if (!coCuaSoDangGo) { + realMode = config_.mode.value(); + } } void LotusEngine::refreshOption() { diff --git a/src/lotus-state.cpp b/src/lotus-state.cpp index 600424c0..4df43a0e 100644 --- a/src/lotus-state.cpp +++ b/src/lotus-state.cpp @@ -41,7 +41,12 @@ namespace fcitx { void LotusState::setEngine() { lotusEngine_.reset(); - realMode = engine_->config().mode.value(); + // KHÔNG đặt realMode ở đây. setEngine() chạy trong hàm dựng của MỌI LotusState và + // trong refreshEngine() cho MỌI input context — cả hai đều không biết cửa sổ nào + // đang gõ. Ghi giá trị mặc định chung vào đây là thổi bay luật theo app của cửa sổ + // đang hoạt động, cho tới lần đổi focus kế tiếp. + // Chế độ do activate() đặt qua setMode(getAppRule(appName), ic), và refreshEngine() + // đặt lại cho đúng input context đang có focus. if (engine_->config().inputMethod.value() == "Custom") { const auto& keymaps = *engine_->customKeymap().customKeymap; From 3e44a04259188a8982689e3d5d82190bb146dbc8 Mon Sep 17 00:00:00 2001 From: nguyenphivn Date: Tue, 8 Sep 2026 21:17:24 +0700 Subject: [PATCH 2/3] =?UTF-8?q?th=E1=BB=AD=20nghi=E1=BB=87m:=20ch=E1=BB=9D?= =?UTF-8?q?=20s=E1=BB=B1=20ki=E1=BB=87n=20surrounding=20text=20thay=20v?= =?UTF-8?q?=C3=AC=20ng=E1=BB=A7=20theo=20h=E1=BA=B1ng=20s=E1=BB=91=20(m?= =?UTF-8?q?=E1=BA=B7c=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 13fccbb2..151486c1 100644 --- a/src/lotus-engine.cpp +++ b/src/lotus-engine.cpp @@ -757,6 +757,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 4df43a0e..aacab5bb 100644 --- a/src/lotus-state.cpp +++ b/src/lotus-state.cpp @@ -442,6 +442,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; @@ -451,10 +537,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,...) @@ -485,6 +669,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() && @@ -505,11 +709,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; @@ -813,7 +1017,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()) { @@ -1010,7 +1214,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 1f1df591..16839b08 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; @@ -126,6 +137,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. From 62275a86cfff12cf4ad7fa889a2433a77c97c101 Mon Sep 17 00:00:00 2001 From: onlyvibe Date: Fri, 11 Sep 2026 19:28:11 +0700 Subject: [PATCH 3/3] style: apply clang-format Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01EahWp9MhUvHaPU2SpWPTLa --- src/lotus-config.h | 44 ++++++++++++++++++----------------- src/lotus-engine.cpp | 10 ++++---- src/lotus-state.cpp | 55 ++++++++++++++++++++++---------------------- src/lotus-state.h | 22 +++++++++--------- 4 files changed, 66 insertions(+), 65 deletions(-) diff --git a/src/lotus-config.h b/src/lotus-config.h index e98b2a45..cf74b784 100644 --- a/src/lotus-config.h +++ b/src/lotus-config.h @@ -233,27 +233,29 @@ namespace fcitx { Option capitalizeMacro{this, "CapitalizeMacro", _("Capitalize Macro"), true}; OptionWithAnnotation macroSkipTriggerModifier{ this, "MacroSkipTriggerModifier", _("Modifier to Skip Macro for Next Word"), MacroSkipTriggerModifier::Disabled}; - Option autoCapitalizeAfterPunctuation{this, "AutoCapitalizeAfterPunctuation", _("Auto capitalize after sentence-ending punctuation (. ! ? Enter)"), false}; - Option doubleSpaceToPeriod{this, "DoubleSpaceToPeriod", _("Double Space to Period"), false}; - Option doubleHyphenToEmDash{this, "DoubleHyphenToEmDash", _("Double Hyphen to Em-Dash (--)"), false}; - Option autoNonVnRestore{this, "AutoNonVnRestore", _("Auto Restore Invalid Words"), true}; - Option modernStyle{this, "ModernStyle", _("Use oà, uý (Instead Of òa, úy)"), true}; - 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}; - Option enableCustomKeymap{this, "EnableCustomKeymap", _("Custom Keymap"), false}; - - Option showModeSmooth{this, "ShowModeSmooth", _("Show Uinput (Smooth)"), true}; + Option autoCapitalizeAfterPunctuation{this, "AutoCapitalizeAfterPunctuation", _("Auto capitalize after sentence-ending punctuation (. ! ? Enter)"), false}; + Option doubleSpaceToPeriod{this, "DoubleSpaceToPeriod", _("Double Space to Period"), false}; + Option doubleHyphenToEmDash{this, "DoubleHyphenToEmDash", _("Double Hyphen to Em-Dash (--)"), false}; + Option autoNonVnRestore{this, "AutoNonVnRestore", _("Auto Restore Invalid Words"), true}; + Option modernStyle{this, "ModernStyle", _("Use oà, uý (Instead Of òa, úy)"), true}; + 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}; + Option enableCustomKeymap{this, "EnableCustomKeymap", _("Custom Keymap"), false}; + + Option showModeSmooth{this, "ShowModeSmooth", _("Show Uinput (Smooth)"), true}; Option shortcutSmooth{this, "ShortcutSmooth", _("Shortcut for Uinput (Smooth)"), "1"}; Option showModeUinput{this, "ShowModeUinput", _("Show Uinput (Slow)"), true}; Option shortcutUinput{this, "ShortcutUinput", _("Shortcut for Uinput (Slow)"), "2"}; diff --git a/src/lotus-engine.cpp b/src/lotus-engine.cpp index 151486c1..fd538d87 100644 --- a/src/lotus-engine.cpp +++ b/src/lotus-engine.cpp @@ -753,11 +753,11 @@ 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->xaChoDangCho(); // v13: chữ còn treo thì giao vào ô CŨ trước khi rời đi + auto* ic = event.inputContext(); + 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 aacab5bb..0ba075af 100644 --- a/src/lotus-state.cpp +++ b/src/lotus-state.cpp @@ -447,7 +447,7 @@ namespace fcitx { if (!s.isValid()) { return false; } - const std::string& t = s.text(); + 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_) { @@ -455,23 +455,20 @@ namespace fcitx { // 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)); + 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(); + 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; - }; + 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 false; // ảnh cũ: phần cần xoá vẫn còn } return endsWith(before, cho_prefix_); } @@ -505,8 +502,8 @@ namespace fcitx { 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_anh_tin_cay_ = true; + cho_qua_han_lien_tiep_ = 0; cho_dong_bang_lien_tiep_ = 0; if (cho_dong_bang_) { cho_dong_bang_ = false; @@ -515,7 +512,7 @@ namespace fcitx { } 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ó + 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_); @@ -570,7 +567,7 @@ namespace fcitx { 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_.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()) { @@ -580,8 +577,8 @@ namespace fcitx { // 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 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_; @@ -600,18 +597,17 @@ namespace fcitx { // 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 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 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) { + 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; } @@ -637,7 +633,7 @@ namespace fcitx { // Validate surr cursor pos should match realtextLen after all BS applied const auto& surr = ic_->surroundingText(); 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 + 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 { @@ -666,18 +662,21 @@ namespace fcitx { void LotusState::performReplacement(const std::string& deletedPart, const std::string& addedPart) { LOTUS_INFO("Perform replacement: " + deletedPart + " -> " + addedPart); //NOLINT - 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(); + 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(); + const auto& sg2 = ic_->surroundingText(); if (sg2.isValid()) { const std::string& t = sg2.text(); auto it = t.begin(); diff --git a/src/lotus-state.h b/src/lotus-state.h index 16839b08..c7741f89 100644 --- a/src/lotus-state.h +++ b/src/lotus-state.h @@ -142,19 +142,19 @@ namespace fcitx { 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á + 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 + 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);