From f5b0ec6facc8eff5fe6ab6ec6c3029f38b605215 Mon Sep 17 00:00:00 2001 From: dvchd Date: Thu, 2 Apr 2026 07:06:08 +0000 Subject: [PATCH 1/3] fix(fcitx5): fallback to preedit mode for sandbox apps without SurroundingText MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flatpak/Snap apps (e.g. OnlyOffice Flathub, Firefox snap) do not support the SurroundingText capability due to sandboxing. Previously, the direct commit mode would call commitString() for every keystroke but could not call deleteSurroundingText() to correct previously committed characters. This caused doubled characters: e.g. typing 'tieeng' produced 'Goõ tieengếng Vieêệt' instead of 'Gõ tiếng Việt'. Fix: at runtime, detect missing SurroundingText capability and route these apps through the existing preedit mode (same code path used for terminal emulators). Preedit mode maintains an internal buffer and only calls commitString() when a word boundary is reached, ensuring characters are committed atomically without needing deleteSurroundingText. Additionally, updatePreeditDisplay() now also sets server-side preedit (setPreedit) alongside client-side preedit (setClientPreedit), so apps that don't support ClientPreedit still see feedback via the Fcitx5 panel. Fixes: #18 See-also: https://github.com/marixdev/vnkey/issues/18 --- vnkey-fcitx5/src/vnkey-fcitx5.cpp | 32 +++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/vnkey-fcitx5/src/vnkey-fcitx5.cpp b/vnkey-fcitx5/src/vnkey-fcitx5.cpp index b754931..baa004e 100644 --- a/vnkey-fcitx5/src/vnkey-fcitx5.cpp +++ b/vnkey-fcitx5/src/vnkey-fcitx5.cpp @@ -871,13 +871,18 @@ bool VnKeyState::isTerminalProgram(const std::string &prog) { void VnKeyState::updatePreeditDisplay() { if (preedit_.empty()) { ic_->inputPanel().setClientPreedit(Text()); + ic_->inputPanel().setPreedit(Text()); ic_->updatePreedit(); return; } Text preeditText; preeditText.append(preedit_); preeditText.setCursor(static_cast(preedit_.size())); + /* Ưu tiên client-side preedit (hiển thị ngay trong app); + * nếu app không hỗ trợ ClientPreedit (vd: Flatpak/Snap sandbox), + * dùng server-side preedit (hiển thị qua panel của Fcitx5). */ ic_->inputPanel().setClientPreedit(preeditText); + ic_->inputPanel().setPreedit(preeditText); ic_->updatePreedit(); } @@ -975,7 +980,13 @@ void VnKeyState::keyEvent(KeyEvent &keyEvent) { /* Xử lý Backspace */ if (key.check(FcitxKey_BackSpace)) { - if (isTerminal_) { + /* Flatpak/Snap sandbox app không hỗ trợ SurroundingText → + * không thể deleteSurroundingText → phải dùng preedit mode. + * Giống logic terminal: duy trì preedit buffer nội bộ. */ + bool hasSurrounding = ic_->capabilityFlags().test(CapabilityFlag::SurroundingText); + bool usePreedit = isTerminal_ || !hasSurrounding; + + if (usePreedit) { /* Chế độ preedit cho terminal: cập nhật preedit */ if (preedit_.empty()) { return; /* không có gì để xóa, để hệ thống xử lý */ @@ -1015,7 +1026,7 @@ void VnKeyState::keyEvent(KeyEvent &keyEvent) { return; } - /* Chế độ commit trực tiếp (ứng dụng GUI) */ + /* Chế độ commit trực tiếp (ứng dụng GUI có SurroundingText) */ uint8_t buf[256]; size_t actualLen = 0; size_t backspaces = 0; @@ -1024,8 +1035,7 @@ void VnKeyState::keyEvent(KeyEvent &keyEvent) { if (processed && (backspaces > 0 || actualLen > 0)) { /* Xóa ký tự đã commit */ - if (backspaces > 0 && - ic_->capabilityFlags().test(CapabilityFlag::SurroundingText)) { + if (backspaces > 0) { ic_->deleteSurroundingText( -static_cast(backspaces), static_cast(backspaces)); @@ -1053,8 +1063,18 @@ void VnKeyState::keyEvent(KeyEvent &keyEvent) { int processed = vnkey_engine_process( vnkeyEngine_, keyCode, buf, sizeof(buf), &actualLen, &backspaces, nullptr); - if (isTerminal_) { - /* Chế độ preedit cho terminal: cập nhật preedit */ + /* Flatpak/Snap sandbox app không hỗ trợ SurroundingText → + * không thể deleteSurroundingText để sửa ký tự đã commit. + * Phải dùng preedit mode: duy trì buffer nội bộ, + * chỉ commitString() khi hoàn thành từ. + * Điều này đảm bảo ký tự tiếng Việt được gửi nguyên vẹn, + * không bị nhân đôi như khi dùng direct commit + * mà không có capability SurroundingText. */ + bool hasSurrounding = ic_->capabilityFlags().test(CapabilityFlag::SurroundingText); + bool usePreedit = isTerminal_ || !hasSurrounding; + + if (usePreedit) { + /* Chế độ preedit cho terminal/sandbox: cập nhật preedit */ if (processed) { /* Xóa backspaces ký tự UTF-8 cuối từ preedit */ for (size_t i = 0; i < backspaces && !preedit_.empty(); i++) { From 2849ff8cdd77410179c691e411b208f1551d57b5 Mon Sep 17 00:00:00 2001 From: dvchd Date: Thu, 2 Apr 2026 07:19:02 +0000 Subject: [PATCH 2/3] fix(fcitx5): remove duplicate hasSurrounding variable declaration The direct commit code path already has hasSurrounding declared earlier in the same function scope (line 1073). Remove the duplicate declaration at line 1108 to fix compilation error. --- vnkey-fcitx5/src/vnkey-fcitx5.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vnkey-fcitx5/src/vnkey-fcitx5.cpp b/vnkey-fcitx5/src/vnkey-fcitx5.cpp index baa004e..a55127b 100644 --- a/vnkey-fcitx5/src/vnkey-fcitx5.cpp +++ b/vnkey-fcitx5/src/vnkey-fcitx5.cpp @@ -1104,8 +1104,8 @@ void VnKeyState::keyEvent(KeyEvent &keyEvent) { return; } - /* Chế độ commit trực tiếp (ứng dụng GUI) */ - bool hasSurrounding = ic_->capabilityFlags().test(CapabilityFlag::SurroundingText); + /* Chế độ commit trực tiếp (ứng dụng GUI có SurroundingText) */ + /* hasSurrounding đã khai báo ở trên (dòng 1073) */ fprintf(stderr, "[vnkey] process: key=%c(0x%x) processed=%d bs=%zu len=%zu surrounding=%d\n", (keyCode >= 0x20 && keyCode < 0x7f) ? (char)keyCode : '?', keyCode, processed, backspaces, actualLen, hasSurrounding); From 1b395c02e22c75073b244e50b0c293a4f38c96f1 Mon Sep 17 00:00:00 2001 From: dvchd Date: Thu, 2 Apr 2026 07:26:11 +0000 Subject: [PATCH 3/3] fix(fcitx5): clear server-side preedit panel in commitPreedit After committing preedit text, also clear both client-side (setClientPreedit) and server-side (setPreedit) inputPanel preedit to prevent stale text from lingering on the Fcitx5 panel. This is particularly important for sandbox apps (Flatpak/Snap) that don't support ClientPreedit and rely on the server-side preedit display. --- vnkey-fcitx5/src/vnkey-fcitx5.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/vnkey-fcitx5/src/vnkey-fcitx5.cpp b/vnkey-fcitx5/src/vnkey-fcitx5.cpp index a55127b..a87a0ea 100644 --- a/vnkey-fcitx5/src/vnkey-fcitx5.cpp +++ b/vnkey-fcitx5/src/vnkey-fcitx5.cpp @@ -813,6 +813,12 @@ void VnKeyState::commitPreedit(bool soft) { } } preedit_.clear(); + /* Xóa cả client-side và server-side preedit sau khi commit, + * tránh text cũ còn sót lại trên panel Fcitx5 (đặc biệt với + * sandbox apps không hỗ trợ ClientPreedit). */ + ic_->inputPanel().setClientPreedit(Text()); + ic_->inputPanel().setPreedit(Text()); + ic_->updatePreedit(); } if (soft) vnkey_engine_soft_reset(vnkeyEngine_);