From 4459074258fd629b825e05194fc2f7709d501947 Mon Sep 17 00:00:00 2001 From: hthienloc Date: Sun, 29 Mar 2026 16:07:08 +0700 Subject: [PATCH 01/12] refactor: update state reset logic to handle focus out events and adjust icon fill colors to white --- data/icons/breeze/status/22/fcitx-lotus-default.svg | 2 +- data/icons/breeze/status/22/fcitx-lotus-emoji-default.svg | 2 +- data/icons/breeze/status/22/fcitx-lotus-off-default.svg | 2 +- src/lotus-engine.cpp | 2 +- src/lotus-state.cpp | 4 ++-- src/lotus-state.h | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/data/icons/breeze/status/22/fcitx-lotus-default.svg b/data/icons/breeze/status/22/fcitx-lotus-default.svg index b4247abe..d7cfb70f 100644 --- a/data/icons/breeze/status/22/fcitx-lotus-default.svg +++ b/data/icons/breeze/status/22/fcitx-lotus-default.svg @@ -58,7 +58,7 @@ visible="true" /> diff --git a/data/icons/breeze/status/22/fcitx-lotus-emoji-default.svg b/data/icons/breeze/status/22/fcitx-lotus-emoji-default.svg index aaece7f5..07bcd92e 100644 --- a/data/icons/breeze/status/22/fcitx-lotus-emoji-default.svg +++ b/data/icons/breeze/status/22/fcitx-lotus-emoji-default.svg @@ -58,5 +58,5 @@ d="m11 3c-4.418278 0-8 3.581722-8 8s3.581722 8 8 8 8-3.581722 8-8-3.581722-8-8-8zm0 1a7 7 0 0 1 7 7 7 7 0 0 1-7 7 7 7 0 0 1-7-7 7 7 0 0 1 7-7zm-3 3.0371094c-0.240631 0-0.285719 0.00835-0.423828 0.076172-0.196744 0.096612-0.372138 0.2720064-0.46875 0.46875-0.06701 0.1364636-0.07617 0.1842797-0.07617 0.4101563 0 0.2199463 0.0093 0.2761342 0.07031 0.40625 0.128242 0.2735029 0.360806 0.4753041 0.640625 0.5546875 0.07193 0.020407 0.204416 0.030031 0.318359 0.023437 0.361715-0.02093 0.657793-0.2206576 0.820312-0.5507813 0.08009-0.1626882 0.08789-0.1989581 0.08789-0.4335937 0-0.2264971-0.0091-0.27362-0.07617-0.4101563-0.09661-0.1967436-0.272006-0.3721379-0.46875-0.46875-0.138109-0.0678186-0.183197-0.0761715-0.423828-0.0761715zm6 0c-0.240632 0-0.28572 0.00835-0.423828 0.076172-0.196744 0.096612-0.37214 0.2720064-0.46875 0.46875-0.06703 0.1364636-0.07617 0.1842797-0.07617 0.4101563 0 0.2199463 0.0093 0.2761342 0.07031 0.40625 0.128242 0.2735029 0.360807 0.4753041 0.640626 0.5546875 0.07193 0.020406 0.204415 0.030027 0.318359 0.023437 0.361715-0.02093 0.657792-0.2206576 0.820312-0.5507813 0.08014-0.1626882 0.08789-0.1989581 0.08789-0.4335937 0-0.2264971-0.0091-0.27362-0.07617-0.4101563-0.09661-0.1967436-0.272006-0.3721379-0.46875-0.46875-0.13811-0.0678186-0.183198-0.0761715-0.423829-0.0761715zm-6.822266 5.9863286 0.01758 0.07422c0.0098 0.04069 0.05244 0.166157 0.09375 0.279297 0.493965 1.353051 1.696757 2.345003 3.105469 2.5625 1.031171 0.159206 2.141757-0.114951 2.958985-0.732422 0.351957-0.265927 0.742778-0.689066 0.988281-1.070312 0.162933-0.253023 0.40294-0.790858 0.462891-1.039063l0.01758-0.07422h-3.82227z" fill="currentColor" id="path1" - style="fill:#000000" /> + style="fill:#ffffff" /> diff --git a/data/icons/breeze/status/22/fcitx-lotus-off-default.svg b/data/icons/breeze/status/22/fcitx-lotus-off-default.svg index fc956d4d..02332508 100644 --- a/data/icons/breeze/status/22/fcitx-lotus-off-default.svg +++ b/data/icons/breeze/status/22/fcitx-lotus-off-default.svg @@ -58,7 +58,7 @@ visible="true" /> reset(); + state->reset(event.type() == EventType::InputContextFocusOut); } } diff --git a/src/lotus-state.cpp b/src/lotus-state.cpp index a07d9fec..b830b30a 100644 --- a/src/lotus-state.cpp +++ b/src/lotus-state.cpp @@ -1037,7 +1037,7 @@ namespace fcitx { } } - void LotusState::reset() { + void LotusState::reset(bool isFocusOut) { const auto& surrounding = ic_->surroundingText(); const auto& text = surrounding.text(); size_t textLen = utf8::length(text); @@ -1050,7 +1050,7 @@ namespace fcitx { isPrevSpace_ = false; shouldCapitalize_ = false; isPrevPunctuation_ = false; - if (realMode == LotusMode::Preedit) { + if (realMode == LotusMode::Preedit && isFocusOut) { EngineCommitPreedit(lotusEngine_.handle()); UniqueCPtr commit(EnginePullCommit(lotusEngine_.handle())); if (commit && (*commit.get() != 0)) { diff --git a/src/lotus-state.h b/src/lotus-state.h index 901b0ff3..5989ce21 100644 --- a/src/lotus-state.h +++ b/src/lotus-state.h @@ -65,7 +65,7 @@ namespace fcitx { /** * @brief Resets the input state. */ - void reset(); + void reset(bool isFocusOut = false); /** * @brief Commits the current buffer. From 132e47c0889f97d44030548484839d777076c70c Mon Sep 17 00:00:00 2001 From: hthienloc Date: Sun, 29 Mar 2026 16:15:40 +0700 Subject: [PATCH 02/12] feat: add isFocusOut parameter to reset method in lotus-state.h --- src/lotus-state.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lotus-state.h b/src/lotus-state.h index 5989ce21..c509fe62 100644 --- a/src/lotus-state.h +++ b/src/lotus-state.h @@ -64,6 +64,7 @@ namespace fcitx { /** * @brief Resets the input state. + * @param isFocusOut If true, indicates the reset is due to a focus-out event, which may trigger committing the preedit text. */ void reset(bool isFocusOut = false); From 97df9fbfe1ce9978057224c05a39fc037e4c92ba Mon Sep 17 00:00:00 2001 From: hthienloc Date: Sun, 29 Mar 2026 16:22:33 +0700 Subject: [PATCH 03/12] feat: add support for black default status icons and include necessary icon assets --- .../status/22/fcitx-lotus-default-black.svg | 65 +++++++++++++++++ .../22/fcitx-lotus-emoji-default-black.svg | 61 ++++++++++++++++ .../22/fcitx-lotus-off-default-black.svg | 66 ++++++++++++++++++ .../status/24/fcitx-lotus-default-black.svg | 65 +++++++++++++++++ .../24/fcitx-lotus-emoji-default-black.svg | 69 +++++++++++++++++++ .../24/fcitx-lotus-off-default-black.svg | 66 ++++++++++++++++++ .../status/22/fcitx-lotus-default-black.svg | 65 +++++++++++++++++ .../22/fcitx-lotus-emoji-default-black.svg | 62 +++++++++++++++++ .../22/fcitx-lotus-off-default-black.svg | 66 ++++++++++++++++++ .../status/24/fcitx-lotus-default-black.svg | 65 +++++++++++++++++ .../24/fcitx-lotus-emoji-default-black.svg | 69 +++++++++++++++++++ .../24/fcitx-lotus-off-default-black.svg | 66 ++++++++++++++++++ .../apps/fcitx-lotus-default-black.svg | 45 ++++++++++++ .../apps/fcitx-lotus-emoji-default-black.svg | 69 +++++++++++++++++++ .../apps/fcitx-lotus-off-default-black.svg | 43 ++++++++++++ settings-gui/ui/pages/dynamic_settings.py | 2 +- src/lotus-config.h | 1 + src/lotus-engine.cpp | 7 +- 18 files changed, 948 insertions(+), 4 deletions(-) create mode 100644 data/icons/breeze-dark/status/22/fcitx-lotus-default-black.svg create mode 100644 data/icons/breeze-dark/status/22/fcitx-lotus-emoji-default-black.svg create mode 100644 data/icons/breeze-dark/status/22/fcitx-lotus-off-default-black.svg create mode 100644 data/icons/breeze-dark/status/24/fcitx-lotus-default-black.svg create mode 100644 data/icons/breeze-dark/status/24/fcitx-lotus-emoji-default-black.svg create mode 100644 data/icons/breeze-dark/status/24/fcitx-lotus-off-default-black.svg create mode 100644 data/icons/breeze/status/22/fcitx-lotus-default-black.svg create mode 100644 data/icons/breeze/status/22/fcitx-lotus-emoji-default-black.svg create mode 100644 data/icons/breeze/status/22/fcitx-lotus-off-default-black.svg create mode 100644 data/icons/breeze/status/24/fcitx-lotus-default-black.svg create mode 100644 data/icons/breeze/status/24/fcitx-lotus-emoji-default-black.svg create mode 100644 data/icons/breeze/status/24/fcitx-lotus-off-default-black.svg create mode 100644 data/icons/hicolor/scalable/apps/fcitx-lotus-default-black.svg create mode 100644 data/icons/hicolor/scalable/apps/fcitx-lotus-emoji-default-black.svg create mode 100644 data/icons/hicolor/scalable/apps/fcitx-lotus-off-default-black.svg diff --git a/data/icons/breeze-dark/status/22/fcitx-lotus-default-black.svg b/data/icons/breeze-dark/status/22/fcitx-lotus-default-black.svg new file mode 100644 index 00000000..909d4f14 --- /dev/null +++ b/data/icons/breeze-dark/status/22/fcitx-lotus-default-black.svg @@ -0,0 +1,65 @@ + + + + + + + + + + + + diff --git a/data/icons/breeze-dark/status/22/fcitx-lotus-emoji-default-black.svg b/data/icons/breeze-dark/status/22/fcitx-lotus-emoji-default-black.svg new file mode 100644 index 00000000..2086b99b --- /dev/null +++ b/data/icons/breeze-dark/status/22/fcitx-lotus-emoji-default-black.svg @@ -0,0 +1,61 @@ + + + + + + + + + + diff --git a/data/icons/breeze-dark/status/22/fcitx-lotus-off-default-black.svg b/data/icons/breeze-dark/status/22/fcitx-lotus-off-default-black.svg new file mode 100644 index 00000000..5608b669 --- /dev/null +++ b/data/icons/breeze-dark/status/22/fcitx-lotus-off-default-black.svg @@ -0,0 +1,66 @@ + + + + + + + + + + + + diff --git a/data/icons/breeze-dark/status/24/fcitx-lotus-default-black.svg b/data/icons/breeze-dark/status/24/fcitx-lotus-default-black.svg new file mode 100644 index 00000000..909d4f14 --- /dev/null +++ b/data/icons/breeze-dark/status/24/fcitx-lotus-default-black.svg @@ -0,0 +1,65 @@ + + + + + + + + + + + + diff --git a/data/icons/breeze-dark/status/24/fcitx-lotus-emoji-default-black.svg b/data/icons/breeze-dark/status/24/fcitx-lotus-emoji-default-black.svg new file mode 100644 index 00000000..831df9ba --- /dev/null +++ b/data/icons/breeze-dark/status/24/fcitx-lotus-emoji-default-black.svg @@ -0,0 +1,69 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/data/icons/breeze-dark/status/24/fcitx-lotus-off-default-black.svg b/data/icons/breeze-dark/status/24/fcitx-lotus-off-default-black.svg new file mode 100644 index 00000000..5608b669 --- /dev/null +++ b/data/icons/breeze-dark/status/24/fcitx-lotus-off-default-black.svg @@ -0,0 +1,66 @@ + + + + + + + + + + + + diff --git a/data/icons/breeze/status/22/fcitx-lotus-default-black.svg b/data/icons/breeze/status/22/fcitx-lotus-default-black.svg new file mode 100644 index 00000000..d3532518 --- /dev/null +++ b/data/icons/breeze/status/22/fcitx-lotus-default-black.svg @@ -0,0 +1,65 @@ + + + + + + + + + + + + diff --git a/data/icons/breeze/status/22/fcitx-lotus-emoji-default-black.svg b/data/icons/breeze/status/22/fcitx-lotus-emoji-default-black.svg new file mode 100644 index 00000000..e0d1ef99 --- /dev/null +++ b/data/icons/breeze/status/22/fcitx-lotus-emoji-default-black.svg @@ -0,0 +1,62 @@ + + + + + + + + + + diff --git a/data/icons/breeze/status/22/fcitx-lotus-off-default-black.svg b/data/icons/breeze/status/22/fcitx-lotus-off-default-black.svg new file mode 100644 index 00000000..603e7883 --- /dev/null +++ b/data/icons/breeze/status/22/fcitx-lotus-off-default-black.svg @@ -0,0 +1,66 @@ + + + + + + + + + + + + diff --git a/data/icons/breeze/status/24/fcitx-lotus-default-black.svg b/data/icons/breeze/status/24/fcitx-lotus-default-black.svg new file mode 100644 index 00000000..909d4f14 --- /dev/null +++ b/data/icons/breeze/status/24/fcitx-lotus-default-black.svg @@ -0,0 +1,65 @@ + + + + + + + + + + + + diff --git a/data/icons/breeze/status/24/fcitx-lotus-emoji-default-black.svg b/data/icons/breeze/status/24/fcitx-lotus-emoji-default-black.svg new file mode 100644 index 00000000..831df9ba --- /dev/null +++ b/data/icons/breeze/status/24/fcitx-lotus-emoji-default-black.svg @@ -0,0 +1,69 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/data/icons/breeze/status/24/fcitx-lotus-off-default-black.svg b/data/icons/breeze/status/24/fcitx-lotus-off-default-black.svg new file mode 100644 index 00000000..5608b669 --- /dev/null +++ b/data/icons/breeze/status/24/fcitx-lotus-off-default-black.svg @@ -0,0 +1,66 @@ + + + + + + + + + + + + diff --git a/data/icons/hicolor/scalable/apps/fcitx-lotus-default-black.svg b/data/icons/hicolor/scalable/apps/fcitx-lotus-default-black.svg new file mode 100644 index 00000000..4ea3f991 --- /dev/null +++ b/data/icons/hicolor/scalable/apps/fcitx-lotus-default-black.svg @@ -0,0 +1,45 @@ + + + + + + diff --git a/data/icons/hicolor/scalable/apps/fcitx-lotus-emoji-default-black.svg b/data/icons/hicolor/scalable/apps/fcitx-lotus-emoji-default-black.svg new file mode 100644 index 00000000..831df9ba --- /dev/null +++ b/data/icons/hicolor/scalable/apps/fcitx-lotus-emoji-default-black.svg @@ -0,0 +1,69 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/data/icons/hicolor/scalable/apps/fcitx-lotus-off-default-black.svg b/data/icons/hicolor/scalable/apps/fcitx-lotus-off-default-black.svg new file mode 100644 index 00000000..4b9a40c3 --- /dev/null +++ b/data/icons/hicolor/scalable/apps/fcitx-lotus-off-default-black.svg @@ -0,0 +1,43 @@ + + + + + + diff --git a/settings-gui/ui/pages/dynamic_settings.py b/settings-gui/ui/pages/dynamic_settings.py index 7b09b85a..7ea80281 100644 --- a/settings-gui/ui/pages/dynamic_settings.py +++ b/settings-gui/ui/pages/dynamic_settings.py @@ -40,7 +40,7 @@ class SettingsCategory(Enum): "INPUT METHOD": ["InputMethod", "Mode", "OutputCharset"], }, SettingsCategory.APPEARANCE: { - "THEME & ICONS": ["UseLotusIcons"], + "THEME & ICONS": ["UseLotusIcons", "UseBlackDefaultIcons"], }, SettingsCategory.TYPING: { "SPELLING & CORRECTIONS": ["SpellCheck", "AutoNonVnRestore", "DdFreeStyle"], diff --git a/src/lotus-config.h b/src/lotus-config.h index 901ca908..68a2cc7d 100644 --- a/src/lotus-config.h +++ b/src/lotus-config.h @@ -227,6 +227,7 @@ namespace fcitx { Option ddFreeStyle{this, "DdFreeStyle", _("Allow dd To Produce đ When Auto Restore Keys With Invalid Words Is On"), true}; Option fixUinputWithAck{this, "FixUinputWithAck", _("Fix Uinput Mode With Ack"), false}; Option useLotusIcons{this, "UseLotusIcons", _("Use Lotus Status Icons"), false}; + Option useBlackDefaultIcons{this, "UseBlackDefaultIcons", _("Use Black Default Icons"), false}; Option enableDictionary{this, "EnableDictionary", _("Enable Custom Dictionary"), false}; Option enableCustomKeymap{this, "EnableCustomKeymap", _("Enable Custom Keymap"), false}; OptionWithAnnotation timeFormat{this, "TimeFormat", _("Time Format ($TIME in macro)"), "%H:%M", {}, {}, TimeFormatAnnotation()}; diff --git a/src/lotus-engine.cpp b/src/lotus-engine.cpp index 1318315d..d101d50b 100644 --- a/src/lotus-engine.cpp +++ b/src/lotus-engine.cpp @@ -830,10 +830,11 @@ namespace fcitx { FCITX_UNUSED(entry); FCITX_UNUSED(inputContext); if (!*config_.useLotusIcons) { + bool useBlack = *config_.useBlackDefaultIcons; switch (realMode) { - case LotusMode::Off: return "fcitx-lotus-off-default"; - case LotusMode::Emoji: return "fcitx-lotus-emoji-default"; - default: return "fcitx-lotus-default"; + case LotusMode::Off: return useBlack ? "fcitx-lotus-off-default-black" : "fcitx-lotus-off-default"; + case LotusMode::Emoji: return useBlack ? "fcitx-lotus-emoji-default-black" : "fcitx-lotus-emoji-default"; + default: return useBlack ? "fcitx-lotus-default-black" : "fcitx-lotus-default"; } } switch (realMode) { From 2a0b5390998002fc5f3843c8de4a20f052b5c2d4 Mon Sep 17 00:00:00 2001 From: hthienloc Date: Sun, 29 Mar 2026 16:27:41 +0700 Subject: [PATCH 04/12] feat: add black icon variants to Fedora and openSUSE package specifications --- packaging/rpm/fedora/fcitx5-lotus.spec | 15 +++++++++++++++ packaging/rpm/opensuse/fcitx5-lotus.spec | 15 +++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/packaging/rpm/fedora/fcitx5-lotus.spec b/packaging/rpm/fedora/fcitx5-lotus.spec index 389b9d10..080d541a 100644 --- a/packaging/rpm/fedora/fcitx5-lotus.spec +++ b/packaging/rpm/fedora/fcitx5-lotus.spec @@ -76,10 +76,16 @@ Vietnamese input method for fcitx5 %{_datadir}/icons/hicolor/scalable/apps/fcitx-lotus-emoji-default.svg %{_datadir}/icons/hicolor/scalable/apps/fcitx-lotus-default.svg %{_datadir}/icons/hicolor/scalable/apps/fcitx-lotus-off-default.svg +%{_datadir}/icons/hicolor/scalable/apps/fcitx-lotus-emoji-default-black.svg +%{_datadir}/icons/hicolor/scalable/apps/fcitx-lotus-default-black.svg +%{_datadir}/icons/hicolor/scalable/apps/fcitx-lotus-off-default-black.svg %{_datadir}/icons/breeze/status/22/fcitx-lotus-default.svg %{_datadir}/icons/breeze/status/22/fcitx-lotus-off-default.svg %{_datadir}/icons/breeze/status/22/fcitx-lotus-emoji-default.svg +%{_datadir}/icons/breeze/status/22/fcitx-lotus-default-black.svg +%{_datadir}/icons/breeze/status/22/fcitx-lotus-off-default-black.svg +%{_datadir}/icons/breeze/status/22/fcitx-lotus-emoji-default-black.svg %{_datadir}/icons/breeze/status/22/fcitx-lotus.svg %{_datadir}/icons/breeze/status/22/fcitx-lotus-off.svg %{_datadir}/icons/breeze/status/22/fcitx-lotus-emoji.svg @@ -87,6 +93,9 @@ Vietnamese input method for fcitx5 %{_datadir}/icons/breeze/status/24/fcitx-lotus-default.svg %{_datadir}/icons/breeze/status/24/fcitx-lotus-off-default.svg %{_datadir}/icons/breeze/status/24/fcitx-lotus-emoji-default.svg +%{_datadir}/icons/breeze/status/24/fcitx-lotus-default-black.svg +%{_datadir}/icons/breeze/status/24/fcitx-lotus-off-default-black.svg +%{_datadir}/icons/breeze/status/24/fcitx-lotus-emoji-default-black.svg %{_datadir}/icons/breeze/status/24/fcitx-lotus.svg %{_datadir}/icons/breeze/status/24/fcitx-lotus-off.svg %{_datadir}/icons/breeze/status/24/fcitx-lotus-emoji.svg @@ -94,6 +103,9 @@ Vietnamese input method for fcitx5 %{_datadir}/icons/breeze-dark/status/22/fcitx-lotus-default.svg %{_datadir}/icons/breeze-dark/status/22/fcitx-lotus-off-default.svg %{_datadir}/icons/breeze-dark/status/22/fcitx-lotus-emoji-default.svg +%{_datadir}/icons/breeze-dark/status/22/fcitx-lotus-default-black.svg +%{_datadir}/icons/breeze-dark/status/22/fcitx-lotus-off-default-black.svg +%{_datadir}/icons/breeze-dark/status/22/fcitx-lotus-emoji-default-black.svg %{_datadir}/icons/breeze-dark/status/22/fcitx-lotus.svg %{_datadir}/icons/breeze-dark/status/22/fcitx-lotus-off.svg %{_datadir}/icons/breeze-dark/status/22/fcitx-lotus-emoji.svg @@ -101,6 +113,9 @@ Vietnamese input method for fcitx5 %{_datadir}/icons/breeze-dark/status/24/fcitx-lotus-default.svg %{_datadir}/icons/breeze-dark/status/24/fcitx-lotus-off-default.svg %{_datadir}/icons/breeze-dark/status/24/fcitx-lotus-emoji-default.svg +%{_datadir}/icons/breeze-dark/status/24/fcitx-lotus-default-black.svg +%{_datadir}/icons/breeze-dark/status/24/fcitx-lotus-off-default-black.svg +%{_datadir}/icons/breeze-dark/status/24/fcitx-lotus-emoji-default-black.svg %{_datadir}/icons/breeze-dark/status/24/fcitx-lotus.svg %{_datadir}/icons/breeze-dark/status/24/fcitx-lotus-off.svg %{_datadir}/icons/breeze-dark/status/24/fcitx-lotus-emoji.svg diff --git a/packaging/rpm/opensuse/fcitx5-lotus.spec b/packaging/rpm/opensuse/fcitx5-lotus.spec index d667ab50..16c28a0a 100644 --- a/packaging/rpm/opensuse/fcitx5-lotus.spec +++ b/packaging/rpm/opensuse/fcitx5-lotus.spec @@ -76,10 +76,16 @@ Vietnamese input method for fcitx5 %{_datadir}/icons/hicolor/scalable/apps/fcitx-lotus-emoji-default.svg %{_datadir}/icons/hicolor/scalable/apps/fcitx-lotus-default.svg %{_datadir}/icons/hicolor/scalable/apps/fcitx-lotus-off-default.svg +%{_datadir}/icons/hicolor/scalable/apps/fcitx-lotus-emoji-default-black.svg +%{_datadir}/icons/hicolor/scalable/apps/fcitx-lotus-default-black.svg +%{_datadir}/icons/hicolor/scalable/apps/fcitx-lotus-off-default-black.svg %{_datadir}/icons/breeze/status/22/fcitx-lotus-default.svg %{_datadir}/icons/breeze/status/22/fcitx-lotus-off-default.svg %{_datadir}/icons/breeze/status/22/fcitx-lotus-emoji-default.svg +%{_datadir}/icons/breeze/status/22/fcitx-lotus-default-black.svg +%{_datadir}/icons/breeze/status/22/fcitx-lotus-off-default-black.svg +%{_datadir}/icons/breeze/status/22/fcitx-lotus-emoji-default-black.svg %{_datadir}/icons/breeze/status/22/fcitx-lotus.svg %{_datadir}/icons/breeze/status/22/fcitx-lotus-off.svg %{_datadir}/icons/breeze/status/22/fcitx-lotus-emoji.svg @@ -87,6 +93,9 @@ Vietnamese input method for fcitx5 %{_datadir}/icons/breeze/status/24/fcitx-lotus-default.svg %{_datadir}/icons/breeze/status/24/fcitx-lotus-off-default.svg %{_datadir}/icons/breeze/status/24/fcitx-lotus-emoji-default.svg +%{_datadir}/icons/breeze/status/24/fcitx-lotus-default-black.svg +%{_datadir}/icons/breeze/status/24/fcitx-lotus-off-default-black.svg +%{_datadir}/icons/breeze/status/24/fcitx-lotus-emoji-default-black.svg %{_datadir}/icons/breeze/status/24/fcitx-lotus.svg %{_datadir}/icons/breeze/status/24/fcitx-lotus-off.svg %{_datadir}/icons/breeze/status/24/fcitx-lotus-emoji.svg @@ -94,6 +103,9 @@ Vietnamese input method for fcitx5 %{_datadir}/icons/breeze-dark/status/22/fcitx-lotus-default.svg %{_datadir}/icons/breeze-dark/status/22/fcitx-lotus-off-default.svg %{_datadir}/icons/breeze-dark/status/22/fcitx-lotus-emoji-default.svg +%{_datadir}/icons/breeze-dark/status/22/fcitx-lotus-default-black.svg +%{_datadir}/icons/breeze-dark/status/22/fcitx-lotus-off-default-black.svg +%{_datadir}/icons/breeze-dark/status/22/fcitx-lotus-emoji-default-black.svg %{_datadir}/icons/breeze-dark/status/22/fcitx-lotus.svg %{_datadir}/icons/breeze-dark/status/22/fcitx-lotus-off.svg %{_datadir}/icons/breeze-dark/status/22/fcitx-lotus-emoji.svg @@ -101,6 +113,9 @@ Vietnamese input method for fcitx5 %{_datadir}/icons/breeze-dark/status/24/fcitx-lotus-default.svg %{_datadir}/icons/breeze-dark/status/24/fcitx-lotus-off-default.svg %{_datadir}/icons/breeze-dark/status/24/fcitx-lotus-emoji-default.svg +%{_datadir}/icons/breeze-dark/status/24/fcitx-lotus-default-black.svg +%{_datadir}/icons/breeze-dark/status/24/fcitx-lotus-off-default-black.svg +%{_datadir}/icons/breeze-dark/status/24/fcitx-lotus-emoji-default-black.svg %{_datadir}/icons/breeze-dark/status/24/fcitx-lotus.svg %{_datadir}/icons/breeze-dark/status/24/fcitx-lotus-off.svg %{_datadir}/icons/breeze-dark/status/24/fcitx-lotus-emoji.svg From a4c2105939da81e99c415ad8dc1339ed8c4a7938 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=E1=BB=B3nh=20Thi=E1=BB=87n=20L=E1=BB=99c?= Date: Sun, 29 Mar 2026 18:55:00 +0700 Subject: [PATCH 05/12] Implement security fixes and performance optimizations (#182) --------- Co-authored-by: Nguyen Hoang Ky Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> --- server/lotus-server.cpp | 44 ++++++++++-- server/lotus-server.h | 7 ++ src/CMakeLists.txt | 3 + src/lotus-candidates.cpp | 3 +- src/lotus-engine.cpp | 142 ++++++++++++++++++++++++--------------- src/lotus-engine.h | 6 +- src/lotus-state.cpp | 2 +- src/lotus-utils.cpp | 7 +- 8 files changed, 146 insertions(+), 68 deletions(-) diff --git a/server/lotus-server.cpp b/server/lotus-server.cpp index da4ed61e..df51ea57 100644 --- a/server/lotus-server.cpp +++ b/server/lotus-server.cpp @@ -114,6 +114,17 @@ std::string get_current_username() { return (pw != nullptr) ? pw->pw_name : "unknown"; } +uid_t get_uid_for_user(const std::string& username) { + struct passwd pw_buf{}; + struct passwd* pw = nullptr; + char buf[1024]; + int res = getpwnam_r(username.c_str(), &pw_buf, buf, sizeof(buf), &pw); + if (res == 0 && pw != nullptr) { + return pw->pw_uid; + } + return (uid_t)-1; +} + void boost_process_priority() { if (setpriority(PRIO_PROCESS, 0, -10) != 0) { //NOLINT LotusLogger::instance().error("Failed to boost process priority"); @@ -152,6 +163,13 @@ int main(int argc, char* argv[]) { target_user = get_current_username(); } LotusLogger::instance().info("Target user: " + target_user); + + uid_t expected_uid = get_uid_for_user(target_user); + if (expected_uid == (uid_t)-1) { + LotusLogger::instance().error("Failed to find UID for target user: " + target_user); + return 1; + } + boost_process_priority(); pin_to_pcore(); @@ -261,22 +279,34 @@ int main(int argc, char* argv[]) { socklen_t len = sizeof(struct ucred); char exe_path[PATH_MAX] = {0}; + bool authorized = false; if (getsockopt(client_fd, SOL_SOCKET, SO_PEERCRED, &cred, &len) == 0) { - char path[64]; - snprintf(path, sizeof(path), "/proc/%d/exe", cred.pid); + if (cred.uid == expected_uid) { + char path[64]; + snprintf(path, sizeof(path), "/proc/%d/exe", cred.pid); + + ssize_t ret = readlink(path, exe_path, sizeof(exe_path) - 1); + if (ret != -1) { + exe_path[ret] = '\0'; // NOLINT + } - ssize_t ret = readlink(path, exe_path, sizeof(exe_path) - 1); - if (ret != -1) { - exe_path[ret] = '\0'; // NOLINT + if (strcmp(exe_path, "/usr/bin/fcitx5") == 0) { + authorized = true; + } else { + LotusLogger::instance().warn("Unauthorized executable connection attempt to keyboard socket from: " + std::string(exe_path)); + } + } else { + LotusLogger::instance().warn("Unauthorized UID connection attempt to keyboard socket from UID: " + std::to_string(cred.uid)); } + } else { + LotusLogger::instance().warn("Failed to get peer credentials for keyboard socket"); } - if (strcmp(exe_path, "/usr/bin/fcitx5") == 0) { + if (authorized) { LotusLogger::instance().info("Fcitx5 connected to keyboard socket (PID: " + std::to_string(cred.pid) + ")"); kb_client_fd.reset(client_fd); fds[KB_CLIENT_INDEX].fd = kb_client_fd.get(); } else { - LotusLogger::instance().warn("Unauthorized connection attempt from: " + std::string(exe_path)); close(client_fd); } } diff --git a/server/lotus-server.h b/server/lotus-server.h index 246c87c4..80075cf8 100644 --- a/server/lotus-server.h +++ b/server/lotus-server.h @@ -148,6 +148,13 @@ void signal_handler(int sig); */ std::string get_current_username(); +/** + * @brief Gets the UID for a given username. + * @param username Username to resolve. + * @return UID of the user, or (uid_t)-1 if not found. + */ +uid_t get_uid_for_user(const std::string& username); + /** * @brief Boosts process priority for real-time responsiveness. */ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 11040c9e..073dfd12 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -22,6 +22,9 @@ target_link_libraries(lotus target_include_directories(lotus PRIVATE ${PROJECT_BINARY_DIR} ) +target_compile_definitions(lotus PRIVATE + FCITX5_LOTUS_SETTINGS_PATH=\"${CMAKE_INSTALL_FULL_BINDIR}/fcitx5-lotus-settings\" +) install(TARGETS lotus DESTINATION "${CMAKE_INSTALL_LIBDIR}/fcitx5") fcitx5_translate_desktop_file(lotus.conf.in lotus.conf) diff --git a/src/lotus-candidates.cpp b/src/lotus-candidates.cpp index 4e715fcd..0a56684d 100644 --- a/src/lotus-candidates.cpp +++ b/src/lotus-candidates.cpp @@ -15,8 +15,7 @@ namespace fcitx { // EmojiCandidateWord implementation EmojiCandidateWord::EmojiCandidateWord(Text text, LotusState* state, EmojiEntry entry) : CandidateWord(std::move(text)), state_(state), entry_(std::move(entry)) {} - void EmojiCandidateWord::select(InputContext* inputContext) const { - FCITX_UNUSED(inputContext); + void EmojiCandidateWord::select(InputContext* /*inputContext*/) const { state_->ic_->commitString(entry_.output); LOTUS_INFO("Emoji committed: " + entry_.output); diff --git a/src/lotus-engine.cpp b/src/lotus-engine.cpp index d101d50b..91f19c63 100644 --- a/src/lotus-engine.cpp +++ b/src/lotus-engine.cpp @@ -100,6 +100,7 @@ namespace fcitx { LotusEngine::LotusEngine(Instance* instance) : instance_(instance), factory_([this](InputContext& ic) { return new LotusState(this, &ic); }) { //NOLINT const char* desktop = std::getenv("XDG_CURRENT_DESKTOP"); isGnome_ = (desktop != nullptr) && std::string(desktop).find("GNOME") != std::string::npos; + // emptyCustomKeymap_.customKeymap is implicitly initialized to empty by fcitx::Option default value macro. startMonitoring(); Init(); { @@ -161,7 +162,7 @@ namespace fcitx { settingsAction_->setIcon("configure"); connections_.emplace_back(settingsAction_->connect([](InputContext*) { if (fork() == 0) { - execlp("fcitx5-lotus-settings", "fcitx5-lotus-settings", nullptr); + execl(FCITX5_LOTUS_SETTINGS_PATH, FCITX5_LOTUS_SETTINGS_PATH, nullptr); _exit(1); } })); @@ -178,14 +179,14 @@ namespace fcitx { } reloadConfig(); instance_->inputContextManager().registerProperty("LotusState", &factory_); + appRulesPath_ = configDir + "/lotus-app-rules.conf"; + loadAppRules(); toggleActions_ = { #ifndef DISABLE_VERSION_ACTION versionAction_.get(), #endif charsetAction_.get(), spellCheckAction_.get(), macroAction_.get(), capitalizeMacroAction_.get(), autoNonVnRestoreAction_.get(), enableDictionaryAction_.get(), settingsAction_.get()}; - - emptyCustomKeymap_.customKeymap.setValue(std::vector{}); } void LotusEngine::initToggleAction(std::unique_ptr& action, Option& option, const std::string& actionId, const std::string& iconName, @@ -319,6 +320,19 @@ namespace fcitx { refreshEngine(); } else if (path == "app_rules") { appRulesTables_.load(config, true); + { + std::lock_guard lock(appRulesMutex_); + for (auto it = appRules_.begin(); it != appRules_.end();) { + if (!isStartsWith(it->first, "ctx_")) { + it = appRules_.erase(it); + } else { + ++it; + } + } + for (const auto& rule : *appRulesTables_.rules) { + appRules_[*rule.app] = static_cast(*rule.mode); + } + } saveAppRules(); refreshEngine(); } @@ -328,8 +342,7 @@ namespace fcitx { return *config_.inputMethod; } - void LotusEngine::activate(const InputMethodEntry& entry, InputContextEvent& event) { - FCITX_UNUSED(entry); + void LotusEngine::activate(const InputMethodEntry& /*entry*/, InputContextEvent& event) { auto* ic = event.inputContext(); const bool surrvalid = ic->surroundingText().isValid(); const bool is_dbus = getFrontendName(ic) == "dbus"; @@ -391,8 +404,7 @@ namespace fcitx { } } - void LotusEngine::keyEvent(const InputMethodEntry& entry, KeyEvent& keyEvent) { - FCITX_UNUSED(entry); + void LotusEngine::keyEvent(const InputMethodEntry& /*entry*/, KeyEvent& keyEvent) { auto* ic = keyEvent.inputContext(); if (isSelectingAppMode_ && g_mouse_clicked.load(std::memory_order_acquire)) { @@ -571,9 +583,8 @@ namespace fcitx { realtextLen.store(static_cast(textLen), std::memory_order_release); } - void LotusEngine::reset(const InputMethodEntry& entry, InputContextEvent& event) { + void LotusEngine::reset(const InputMethodEntry& /*entry*/, InputContextEvent& event) { LOTUS_INFO("Reset engine"); - FCITX_UNUSED(entry); auto* state = event.inputContext()->propertyFor(&factory_); if (!state->isEmptyHistory() && event.type() != EventType::InputContextFocusOut) { return; @@ -584,8 +595,7 @@ namespace fcitx { } } - void LotusEngine::deactivate(const InputMethodEntry& entry, InputContextEvent& event) { - FCITX_UNUSED(entry); + void LotusEngine::deactivate(const InputMethodEntry& /*entry*/, InputContextEvent& event) { auto* ic = event.inputContext(); auto* state = ic->propertyFor(&factory_); const bool surrvalid = ic->surroundingText().isValid(); @@ -643,62 +653,84 @@ namespace fcitx { } void LotusEngine::loadAppRules() { -#if LOTUS_USE_MODERN_FCITX_API - std::string configDir = (StandardPaths::global().userDirectory(StandardPathsType::Config) / "fcitx5" / "conf").string(); -#else - std::string configDir = StandardPath::global().userDirectory(StandardPath::Type::Config) + "/fcitx5/conf"; -#endif - std::ifstream file(configDir + "/lotus-app-rules.conf"); - if (!file.is_open()) { - return; + { + std::lock_guard lock(appRulesMutex_); + std::unordered_map ctxRules; + for (const auto& [app, mode] : appRules_) { + if (isStartsWith(app, "ctx_")) { + ctxRules[app] = mode; + } + } + appRules_ = std::move(ctxRules); } - std::vector rules; - std::string line; - while (std::getline(file, line)) { - if (line.empty() || line[0] == '#') - continue; - auto delimiterPos = line.find('='); - if (delimiterPos != std::string::npos) { - std::string app = line.substr(0, delimiterPos); - std::string mode = line.substr(delimiterPos + 1); - lotusAppRule rule; - rule.app.setValue(app); - try { - rule.mode.setValue(std::stoi(mode)); - } catch (const std::exception& e) { rule.mode.setValue(0); } - rules.push_back(std::move(rule)); + auto loadFromFile = [this](const std::string& path) { + if (path.empty()) { + LOTUS_WARN("App rules path is empty, skipping load"); + return; + } + std::ifstream file(path); + if (!file.is_open()) + return; + + std::unordered_map tempRules; + std::string line; + while (std::getline(file, line)) { + if (line.empty() || line[0] == '#') + continue; + auto delimiterPos = line.find('='); + if (delimiterPos != std::string::npos) { + std::string app = line.substr(0, delimiterPos); + std::string mode = line.substr(delimiterPos + 1); + try { + tempRules[app] = static_cast(std::stoi(mode)); + } catch (const std::exception&) { LOTUS_WARN("Invalid mode value for app: " + app); } + } + } + file.close(); + + std::lock_guard lock(appRulesMutex_); + for (const auto& [app, mode] : tempRules) { + appRules_[app] = mode; } + }; + loadFromFile(appRulesPath_); + + std::lock_guard lock(appRulesMutex_); + std::vector rules; + for (const auto& pair : appRules_) { + if (pair.first.find("ctx_") == 0) + continue; + lotusAppRule rule; + rule.app.setValue(pair.first); + rule.mode.setValue(static_cast(pair.second)); + rules.push_back(std::move(rule)); } - file.close(); appRulesTables_.rules.setValue(std::move(rules)); } void LotusEngine::saveAppRules() const { -#if LOTUS_USE_MODERN_FCITX_API - std::string configDir = (StandardPaths::global().userDirectory(StandardPathsType::Config) / "fcitx5" / "conf").string(); -#else - std::string configDir = StandardPath::global().userDirectory(StandardPath::Type::Config) + "/fcitx5/conf"; -#endif - std::ofstream file(configDir + "/lotus-app-rules.conf"); + // Method is const but locks mutable appRulesMutex_ to safely read appRules_ state + std::ofstream file(appRulesPath_, std::ios::trunc); if (!file.is_open()) return; file << "# Lotus Per-App Configuration\n"; file << "# 0 = Off, 1 = Uinput (Smooth), 2 = Uinput (Slow), 3 = Uinput (Hardcore), 4 = Surrounding Text, 5 = Preedit, 6 = Emoji Picker\n"; - auto appRules = appRulesTables_.rules.value(); - for (const auto& pair : appRules) { - if (!isStartsWith(pair.app.value(), "ctx_")) { - file << pair.app.value() << "=" << static_cast(pair.mode.value()) << "\n"; + std::lock_guard lock(appRulesMutex_); + for (const auto& pair : appRules_) { + bool currentIsCtx = isStartsWith(pair.first, "ctx_"); + if (!currentIsCtx) { + file << pair.first << "=" << static_cast(pair.second) << "\n"; } } file.close(); } LotusMode LotusEngine::getAppRule(const std::string& appName) const { - for (const auto& rule : *appRulesTables_.rules) { - if (*rule.app == appName) { - return static_cast(*rule.mode); - } + std::lock_guard lock(appRulesMutex_); + auto it = appRules_.find(appName); + if (it != appRules_.end()) { + return it->second; } return modeStringToEnum(config_.mode.value()); } @@ -722,6 +754,10 @@ namespace fcitx { rules.push_back(std::move(newRule)); } + { + std::lock_guard lock(appRulesMutex_); + appRules_[appName] = mode; + } appRulesTables_.rules.setValue(std::move(rules)); } @@ -826,9 +862,7 @@ namespace fcitx { } } - std::string LotusEngine::subModeIconImpl(const InputMethodEntry& entry, InputContext& inputContext) { - FCITX_UNUSED(entry); - FCITX_UNUSED(inputContext); + std::string LotusEngine::subModeIconImpl(const InputMethodEntry& /*entry*/, InputContext& /*inputContext*/) { if (!*config_.useLotusIcons) { bool useBlack = *config_.useBlackDefaultIcons; switch (realMode) { @@ -844,9 +878,7 @@ namespace fcitx { } } - std::string LotusEngine::subModeLabelImpl(const InputMethodEntry& entry, InputContext& inputContext) { - FCITX_UNUSED(entry); - FCITX_UNUSED(inputContext); + std::string LotusEngine::subModeLabelImpl(const InputMethodEntry& /*entry*/, InputContext& /*inputContext*/) { switch (realMode) { case LotusMode::Off: return _("Lotus - Off"); case LotusMode::Emoji: return "😄"; diff --git a/src/lotus-engine.h b/src/lotus-engine.h index 277ca55c..0b617e33 100644 --- a/src/lotus-engine.h +++ b/src/lotus-engine.h @@ -18,6 +18,7 @@ #include "lotus-config.h" #include "emoji.h" #include "lotus.h" +#include #include #include #include @@ -216,11 +217,14 @@ namespace fcitx { std::vector toggleActions_; std::vector connections_; CGoObject dictionary_; - bool isGnome_; + std::unordered_map appRules_; + std::string appRulesPath_; bool isSelectingAppMode_ = false; std::string currentConfigureApp_; FCITX_ADDON_DEPENDENCY_LOADER(emoji, instance_->addonManager()); std::unique_ptr emojiLoader_; + bool isGnome_ = false; + mutable std::mutex appRulesMutex_; /** * @brief Refreshes the bamboo engine with current settings. diff --git a/src/lotus-state.cpp b/src/lotus-state.cpp index b830b30a..6cca5744 100644 --- a/src/lotus-state.cpp +++ b/src/lotus-state.cpp @@ -388,7 +388,7 @@ namespace fcitx { { std::string utf8Char = Key::keySymToUTF8(currentSym); if (!utf8Char.empty()) { - emojiBuffer_ += utf8Char; + emojiBuffer_.append(utf8Char); keyEvent.filterAndAccept(); updateEmojiPreedit(); } else { diff --git a/src/lotus-utils.cpp b/src/lotus-utils.cpp index ea8c9ea5..1c5ccc5f 100644 --- a/src/lotus-utils.cpp +++ b/src/lotus-utils.cpp @@ -10,6 +10,8 @@ #include #include +#include +#include // Global variables std::atomic realMode{fcitx::LotusMode::Smooth}; @@ -32,8 +34,9 @@ std::condition_variable monitor_cv; FCITX_DEFINE_LOG_CATEGORY(lotus, "lotus", fcitx::LogLevel::NoLog); std::string buildSocketPath(const char* base_path_suffix) { - const char* username_c = std::getenv("USER"); - std::string path; + struct passwd* pw = getpwuid(getuid()); + const char* username_c = (pw != nullptr) ? pw->pw_name : nullptr; + std::string path; path.reserve(32); path += "lotussocket-"; path += ((username_c != nullptr) ? username_c : "unknown"); From e3057844df0bf80f266d143b100b8d8450845689 Mon Sep 17 00:00:00 2001 From: hthienloc Date: Sun, 29 Mar 2026 20:30:12 +0700 Subject: [PATCH 06/12] fix: ensure buffer is committed before resetting engine state fix: ensure buffer is committed before reset and simplify engine state cleanup clean up fix: reset engine state after committing string and remove unused header file hmm --- src/lotus-engine.cpp | 6 ++++++ src/lotus-state.cpp | 14 ++++---------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/lotus-engine.cpp b/src/lotus-engine.cpp index 91f19c63..8d3dc092 100644 --- a/src/lotus-engine.cpp +++ b/src/lotus-engine.cpp @@ -412,6 +412,7 @@ namespace fcitx { ic->inputPanel().reset(); ic->updateUserInterface(UserInterfaceComponent::InputPanel); auto* state = ic->propertyFor(&factory_); + state->commitBuffer(); state->reset(); } @@ -528,6 +529,7 @@ namespace fcitx { ic->inputPanel().reset(); ic->updateUserInterface(UserInterfaceComponent::InputPanel); auto* state = ic->propertyFor(&factory_); + state->commitBuffer(); state->reset(); ic->commitString(charStr); return; @@ -554,6 +556,9 @@ namespace fcitx { ic->inputPanel().reset(); ic->updateUserInterface(UserInterfaceComponent::InputPanel); auto* state = ic->propertyFor(&factory_); + if (selectedMode != LotusMode::NoMode) { + state->commitBuffer(); + } state->reset(); if (selectedMode != LotusMode::NoMode) { setMode(selectedMode, ic); @@ -786,6 +791,7 @@ namespace fcitx { ic->inputPanel().reset(); ic->updateUserInterface(UserInterfaceComponent::InputPanel); auto* state = ic->propertyFor(&factory_); + state->commitBuffer(); state->reset(); }; diff --git a/src/lotus-state.cpp b/src/lotus-state.cpp index 6cca5744..69d42d96 100644 --- a/src/lotus-state.cpp +++ b/src/lotus-state.cpp @@ -1098,6 +1098,7 @@ namespace fcitx { UniqueCPtr commit(EnginePullCommit(lotusEngine_.handle())); if (commit && (*commit.get() != 0)) ic_->commitString(commit.get()); + ResetEngine(lotusEngine_.handle()); } ic_->updateUserInterface(UserInterfaceComponent::InputPanel); ic_->updatePreedit(); @@ -1105,18 +1106,11 @@ namespace fcitx { } case LotusMode::Uinput: case LotusMode::UinputHC: - case LotusMode::Smooth: { - if (lotusEngine_) { - UniqueCPtr preedit(EnginePullPreedit(lotusEngine_.handle())); - if (preedit && (*preedit.get() != 0)) { - ic_->commitString(preedit.get()); - } - } - break; - } + case LotusMode::Smooth: case LotusMode::SurroundingText: { - if (lotusEngine_) + if (lotusEngine_) { ResetEngine(lotusEngine_.handle()); + } break; } default: { From 359010b1b1b492716842b70b85c30d24a2913624 Mon Sep 17 00:00:00 2001 From: hthienloc Date: Sun, 29 Mar 2026 22:27:41 +0700 Subject: [PATCH 07/12] refactor: simplify state reset logic and conditional mode switching in lotus-engine --- src/lotus-engine.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/lotus-engine.cpp b/src/lotus-engine.cpp index 8d3dc092..5ab4c865 100644 --- a/src/lotus-engine.cpp +++ b/src/lotus-engine.cpp @@ -556,11 +556,10 @@ namespace fcitx { ic->inputPanel().reset(); ic->updateUserInterface(UserInterfaceComponent::InputPanel); auto* state = ic->propertyFor(&factory_); + if (selectedMode != LotusMode::NoMode) { state->commitBuffer(); - } - state->reset(); - if (selectedMode != LotusMode::NoMode) { + state->reset(); setMode(selectedMode, ic); if (selectedMode == LotusMode::Emoji) { state->updateEmojiPreedit(); From 6b70ed27d129e8e0800ecd4587c47ec5b7ec2818 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=E1=BB=B3nh=20Thi=E1=BB=87n=20L=E1=BB=99c?= <63589389+loccun@users.noreply.github.com> Date: Mon, 30 Mar 2026 18:44:25 +0700 Subject: [PATCH 08/12] Update Add button state in Keymap Editor (#191) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🎨 Palette: Update Add button state in Keymap Editor * refactor: consolidate row lookup logic and remove redundant icon update calls in editors * refactor: implement upsert logic for keymap and macro table entries to ensure consistent state updates * refactor: switch backup file format from .lotusbak to .json --------- Co-authored-by: hthienloc --- settings-gui/ui/pages/backup.py | 8 ++-- settings-gui/ui/pages/dict_editor.py | 1 - settings-gui/ui/pages/keymap_editor.py | 65 ++++++++++++++++++++------ settings-gui/ui/pages/macro_editor.py | 32 +++++++------ 4 files changed, 74 insertions(+), 32 deletions(-) diff --git a/settings-gui/ui/pages/backup.py b/settings-gui/ui/pages/backup.py index 6c213fab..dd269158 100644 --- a/settings-gui/ui/pages/backup.py +++ b/settings-gui/ui/pages/backup.py @@ -58,7 +58,7 @@ def _setup_ui(self): self.import_desc = QLabel( _( - "Save your configurations to a JSON file, or restore them from a .lotusbak file. Select what you want to include:" + "Save or restore your configurations via JSON files. Select the components you wish to include:" ) ) self.import_desc.setWordWrap(True) @@ -134,13 +134,13 @@ def do_export(self): return default_filename = ( - f"lotus-backup-{datetime.now().strftime('%Y%m%d-%H%M%S')}.lotusbak" + f"lotus-backup-{datetime.now().strftime('%Y%m%d-%H%M%S')}.json" ) path, _filter = QFileDialog.getSaveFileName( self, _("Export Backup"), os.path.join(os.path.expanduser("~"), default_filename), - _("Lotus Backup (*.lotusbak);;All Files (*)"), + _("JSON Backup (*.json);;All Files (*)"), ) if not path: return @@ -194,7 +194,7 @@ def on_select_import_file(self): self, _("Select Backup File"), os.path.expanduser("~"), - _("Lotus Backup (*.lotusbak);;All Files (*)"), + _("JSON Backup (*.json);;All Files (*)"), ) if not path: return diff --git a/settings-gui/ui/pages/dict_editor.py b/settings-gui/ui/pages/dict_editor.py index 274e5808..c0aeae69 100644 --- a/settings-gui/ui/pages/dict_editor.py +++ b/settings-gui/ui/pages/dict_editor.py @@ -294,7 +294,6 @@ def on_add(self): self.upsert_row(word) self.input_word.clear() - self._update_add_button_icon() self.input_word.setFocus() def _update_add_button_icon(self): diff --git a/settings-gui/ui/pages/keymap_editor.py b/settings-gui/ui/pages/keymap_editor.py index d111ce90..512fda27 100644 --- a/settings-gui/ui/pages/keymap_editor.py +++ b/settings-gui/ui/pages/keymap_editor.py @@ -298,13 +298,14 @@ def _setup_ui(self): for action_code, action_name in BAMBOO_ACTIONS: self.combo_action.addItem(action_name, action_code) - btn_add = QPushButton(QIcon.fromTheme("list-add"), _("Add")) - btn_add.setToolTip(_("Add / Update Keymap")) - btn_add.clicked.connect(self.on_add) + self.btn_add = QPushButton(QIcon.fromTheme("list-add"), _("Add")) + self.btn_add.setToolTip(_("Add Keymap")) + self.btn_add.clicked.connect(self.on_add) + self.input_key.textChanged.connect(self._update_add_button_icon) input_layout.addWidget(self.input_key) input_layout.addWidget(self.combo_action) - input_layout.addWidget(btn_add) + input_layout.addWidget(self.btn_add) editor_layout.addLayout(input_layout) # Table @@ -420,23 +421,59 @@ def on_search_changed(self): self.table.setRowHidden(row, search_text not in key and search_text not in action) def on_add(self): - """Adds a new keymap entry.""" + """Adds or updates a keymap entry.""" key = self.input_key.text().strip() if not key: return - # Check for update - for row in range(self.table.rowCount()): - item = self.table.item(row, 0) - if item and item.text() == key: - combo = self.table.cellWidget(row, 1) - if combo: - combo.setCurrentIndex(self.combo_action.currentIndex()) - return + self.upsert_row(key, self.combo_action.currentData()) + self.input_key.clear() + self.input_key.setFocus() + + def upsert_row(self, key: str, action_code: str): + """Adds or updates a row in the keymap table.""" + row = self._find_row_by_key(key) + if row is not None: + # Update existing + cell_combo = self.table.cellWidget(row, 1) + if cell_combo: + idx = cell_combo.findData(action_code) + if idx >= 0: + cell_combo.setCurrentIndex(idx) + self._on_item_changed() + return - self._add_row(key, self.combo_action.currentData()) + # Insert new + self._add_row(key, action_code) + self.on_search_changed() + self.update_button_states() self._on_item_changed() + def _find_row_by_key(self, key: str) -> int | None: + """Finds row index for a given key. Returns None if not found.""" + for r in range(self.table.rowCount()): + item = self.table.item(r, 0) + if item and item.text() == key: + return r + return None + + def _update_add_button_icon(self, *_args): + """Changes the Add button icon to Update if key exists.""" + key = self.input_key.text().strip() + + # Disable button if key is empty + self.btn_add.setEnabled(bool(key)) + + found = self._find_row_by_key(key) is not None + if found: + self.btn_add.setIcon(QIcon.fromTheme("document-save")) + self.btn_add.setText(_("Update")) + self.btn_add.setToolTip(_("Update Keymap")) + else: + self.btn_add.setIcon(QIcon.fromTheme("list-add")) + self.btn_add.setText(_("Add")) + self.btn_add.setToolTip(_("Add Keymap")) + def on_load_preset(self): """Loads a predefined set of keymaps.""" preset_name = self.combo_preset.currentText() diff --git a/settings-gui/ui/pages/macro_editor.py b/settings-gui/ui/pages/macro_editor.py index a70f13e1..dedceb4b 100644 --- a/settings-gui/ui/pages/macro_editor.py +++ b/settings-gui/ui/pages/macro_editor.py @@ -323,16 +323,25 @@ def save_data(self): self.dbus.set_sub_config_list("lotus-macro", "Macro", data) self.initial_state = self._get_current_state() + def _find_row_by_key(self, key: str) -> int | None: + """Finds row index for a given key. Returns None if not found.""" + for r in range(self.table.rowCount()): + item = self.table.item(r, 0) + if item and item.text() == key: + return r + return None + def upsert_row(self, key: str, value: str, sort: bool = True): # Update existing - for row in range(self.table.rowCount()): - if self.table.item(row, 0) and self.table.item(row, 0).text() == key: - self.table.item(row, 1).setText(value) - self._apply_row_highlight(row, key) - if sort: - self.on_search_changed() # Re-apply filter - self.update_button_states() - return + row = self._find_row_by_key(key) + if row is not None: + self.table.item(row, 1).setText(value) + self._apply_row_highlight(row, key) + if sort: + self.on_search_changed() # Re-apply filter + self.update_button_states() + self._on_item_changed() + return # Insert new row = self.table.rowCount() @@ -340,6 +349,7 @@ def upsert_row(self, key: str, value: str, sort: bool = True): self.table.setItem(row, 0, QTableWidgetItem(key)) self.table.setItem(row, 1, QTableWidgetItem(value)) self._apply_row_highlight(row, key) + self.on_search_changed() if sort: self.on_search_changed() # Re-apply filter self.update_button_states() @@ -420,7 +430,6 @@ def on_add(self): self.upsert_row(key, val) self.input_key.clear() self.input_val.clear() - self._update_add_button_icon() self.input_key.setFocus() def _update_add_button_icon(self): @@ -440,10 +449,7 @@ def _update_add_button_icon(self): # Disable button if key is invalid, empty, or value is empty self.btn_add.setEnabled(not is_invalid and bool(key) and bool(val)) - found = any( - self.table.item(r, 0) and self.table.item(r, 0).text() == key - for r in range(self.table.rowCount()) - ) + found = self._find_row_by_key(key) is not None if found: self.btn_add.setIcon(QIcon.fromTheme("document-save")) self.btn_add.setText(_("Update")) From f714694c0597cc7ea083b7614068acd77e10b492 Mon Sep 17 00:00:00 2001 From: Nguyen Hoang Ky Date: Mon, 30 Mar 2026 19:09:17 +0700 Subject: [PATCH 09/12] refactor: simplify engine state handling by removing history replay --- src/lotus-state.cpp | 104 ++++++++------------------------------------ src/lotus-state.h | 10 +---- 2 files changed, 19 insertions(+), 95 deletions(-) diff --git a/src/lotus-state.cpp b/src/lotus-state.cpp index 69d42d96..764ab813 100644 --- a/src/lotus-state.cpp +++ b/src/lotus-state.cpp @@ -10,6 +10,7 @@ #include "lotus-engine.h" #include "lotus-candidates.h" #include "lotus-utils.h" +#include "lotus.h" #include #include @@ -137,20 +138,6 @@ namespace fcitx { } } - void LotusState::replayBufferToEngine(const std::string& buffer) { - if (lotusEngine_.handle() == 0U) - return; - - ResetEngine(lotusEngine_.handle()); - for (uint32_t c : utf8::MakeUTF8CharRange(buffer)) { - if (c == static_cast('\b')) { - EngineProcessKeyEvent(lotusEngine_.handle(), FcitxKey_BackSpace, 0); - } else { - EngineProcessKeyEvent(lotusEngine_.handle(), c, 0); - } - } - } - bool LotusState::isAutofillCertain(const SurroundingText& s) { if (!s.isValid() || oldPreBuffer_.empty()) { return false; @@ -518,7 +505,7 @@ namespace fcitx { expected_backspaces_ = 0; current_backspace_count_ = 0; pending_commit_string_.clear(); - history_.clear(); + hasHistory_ = false; ResetEngine(lotusEngine_.handle()); oldPreBuffer_.clear(); return true; @@ -583,12 +570,12 @@ namespace fcitx { if (isBackspace(currentSym) || currentSym == FcitxKey_Return) { if (isBackspace(currentSym)) { - history_.push_back('\b'); - replayBufferToEngine(history_); + hasHistory_ = true; + EngineProcessKeyEvent(lotusEngine_.handle(), FcitxKey_BackSpace, 0); UniqueCPtr preeditC(EnginePullPreedit(lotusEngine_.handle())); oldPreBuffer_ = (preeditC && (*preeditC.get() != 0)) ? preeditC.get() : ""; } else { - history_.clear(); + hasHistory_ = false; ResetEngine(lotusEngine_.handle()); oldPreBuffer_.clear(); } @@ -635,7 +622,7 @@ namespace fcitx { } } - history_.clear(); + hasHistory_ = false; ResetEngine(lotusEngine_.handle()); oldPreBuffer_.clear(); @@ -646,7 +633,8 @@ namespace fcitx { if (checkEmptyPreedit) { UniqueCPtr preeditC(EnginePullPreedit(lotusEngine_.handle())); if (!preeditC || (*preeditC.get() == 0)) { - history_.clear(); + hasHistory_ = false; + ResetEngine(lotusEngine_.handle()); oldPreBuffer_.clear(); keyEvent.forward(); } @@ -654,34 +642,9 @@ namespace fcitx { return; } - history_ += keyUtf8; + hasHistory_ = true; realtextLen.fetch_add(1, std::memory_order_acq_rel); - replayBufferToEngine(history_); - - auto commitAfterReplay = UniqueCPtr(EnginePullCommit(lotusEngine_.handle())); - if (commitAfterReplay && (*commitAfterReplay.get() != 0)) { - std::string commitStr = commitAfterReplay.get(); - std::string commonPrefix; - std::string deletedPart; - std::string addedPart; - compareAndSplitStrings(oldPreBuffer_, commitStr, commonPrefix, deletedPart, addedPart); - - if (!deletedPart.empty()) { - performReplacement(deletedPart, addedPart); - } else if (!addedPart.empty()) { - ic_->commitString(addedPart); - LOTUS_INFO("Commit: " + addedPart); - } - - history_.clear(); - ResetEngine(lotusEngine_.handle()); - oldPreBuffer_.clear(); - - keyEvent.filterAndAccept(); - return; - } - UniqueCPtr preeditC(EnginePullPreedit(lotusEngine_.handle())); std::string preeditStr = (preeditC && (*preeditC.get() != 0)) ? preeditC.get() : ""; @@ -907,7 +870,7 @@ namespace fcitx { if (needEngineReset.load() && realMode != LotusMode::Off) { LOTUS_INFO("Need engine reset"); oldPreBuffer_.clear(); - history_.clear(); + hasHistory_ = false; ResetEngine(lotusEngine_.handle()); is_deleting_.store(false); current_backspace_count_ = 0; @@ -1125,7 +1088,7 @@ namespace fcitx { return; } oldPreBuffer_.clear(); - history_.clear(); + hasHistory_ = false; if (!is_deleting_.load(std::memory_order_acquire)) { expected_backspaces_ = 0; current_backspace_count_ = 0; @@ -1140,8 +1103,8 @@ namespace fcitx { ResetEngine(lotusEngine_.handle()); } - bool LotusState::isEmptyHistory() { - return history_.empty(); + bool LotusState::isEmptyHistory() const { + return !hasHistory_; } void LotusState::replayBufferedKeys() { @@ -1152,7 +1115,7 @@ namespace fcitx { auto keys = std::move(buffered_keys_); buffered_keys_.clear(); for (size_t i = 0; i < keys.size(); ++i) { - KeySym sym = static_cast(keys[i].sym); + auto sym = static_cast(keys[i].sym); uint32_t state = keys[i].state; std::string keyUtf8 = Key::keySymToUTF8(sym); if (keyUtf8.empty()) { @@ -1177,7 +1140,7 @@ namespace fcitx { } } performReplacement(deletedPart, addedPart); - history_.clear(); + hasHistory_ = false; ResetEngine(lotusEngine_.handle()); oldPreBuffer_.clear(); return; @@ -1186,7 +1149,7 @@ namespace fcitx { ic_->commitString(addedPart); } - history_.clear(); + hasHistory_ = false; ResetEngine(lotusEngine_.handle()); oldPreBuffer_.clear(); continue; @@ -1197,42 +1160,9 @@ namespace fcitx { continue; } - history_ += keyUtf8; + hasHistory_ = true; realtextLen.fetch_add(1, std::memory_order_acq_rel); - replayBufferToEngine(history_); - - auto commitAfterReplay = UniqueCPtr(EnginePullCommit(lotusEngine_.handle())); - if (commitAfterReplay && (*commitAfterReplay.get() != 0)) { - std::string commitStr = commitAfterReplay.get(); - std::string commonPrefix; - std::string deletedPart; - std::string addedPart; - compareAndSplitStrings(oldPreBuffer_, commitStr, commonPrefix, deletedPart, addedPart); - - if (!deletedPart.empty()) { - // Re-buffer remaining keys for next replay cycle. - for (size_t j = i + 1; j < keys.size(); ++j) { - if (buffered_keys_.size() < MAX_BUFFERED_KEYS) { - buffered_keys_.push_back(keys[j]); - } - } - performReplacement(deletedPart, addedPart); - history_.clear(); - ResetEngine(lotusEngine_.handle()); - oldPreBuffer_.clear(); - return; - } - if (!addedPart.empty()) { - ic_->commitString(addedPart); - } - - history_.clear(); - ResetEngine(lotusEngine_.handle()); - oldPreBuffer_.clear(); - continue; - } - UniqueCPtr preeditC(EnginePullPreedit(lotusEngine_.handle())); std::string preeditStr = (preeditC && (*preeditC.get() != 0)) ? preeditC.get() : ""; diff --git a/src/lotus-state.h b/src/lotus-state.h index c509fe62..cdb2fc59 100644 --- a/src/lotus-state.h +++ b/src/lotus-state.h @@ -82,7 +82,7 @@ namespace fcitx { * @brief Checks if history buffer is empty. * @return True if no history. */ - bool isEmptyHistory(); + bool isEmptyHistory() const; friend class EmojiCandidateWord; friend class LotusEngine; @@ -93,7 +93,7 @@ namespace fcitx { InputContext* ic_; CGoObject lotusEngine_; std::string oldPreBuffer_; - std::string history_; + bool hasHistory_ = false; int expected_backspaces_ = 0; int current_backspace_count_ = 0; std::string pending_commit_string_; @@ -125,12 +125,6 @@ namespace fcitx { */ void send_backspace_uinput(int count) const; - /** - * @brief Replays buffer content to the engine. - * @param buffer The string buffer to replay. - */ - void replayBufferToEngine(const std::string& buffer); - /** * @brief Checks if autofill is certain for surrounding text. * @param s The surrounding text. From 7613568f1d72de7952087e5dee7cfd5b8752bd82 Mon Sep 17 00:00:00 2001 From: Zebra2711 Date: Mon, 30 Mar 2026 20:49:51 +0700 Subject: [PATCH 10/12] Workaround for Chromium suggestions --- src/lotus-engine.cpp | 23 +++++++++++++++++------ src/lotus-state.cpp | 17 ++++++++++++----- src/lotus-state.h | 1 + 3 files changed, 30 insertions(+), 11 deletions(-) diff --git a/src/lotus-engine.cpp b/src/lotus-engine.cpp index 5ab4c865..30ab2573 100644 --- a/src/lotus-engine.cpp +++ b/src/lotus-engine.cpp @@ -366,21 +366,32 @@ namespace fcitx { auto* state = ic->propertyFor(&factory_); + // Workaround for chromium wayland issue where suggestions cause a doubled + // first character. Forwarding may prevent BS from being sent + // to the client. + // + // Note that with chromium x11 we can't do anything to fixes this because + // it not support surrounding text so can't know when it show suggestions + // + // TODO: Properly fixes instead ugly WA + state->wa_chromium_flag = false; + state->waitAck_ = false; if (*config_.fixUinputWithAck) { if (targetMode == LotusMode::Uinput || targetMode == LotusMode::UinputHC || targetMode == LotusMode::Smooth) { - if (is_dbus) { #if __cplusplus >= 202002L - std::ranges::transform(appName, appName.begin(), ::tolower); + std::ranges::transform(appName, appName.begin(), ::tolower); #else - std::transform(appName.begin(), appName.end(), appName.begin(), ::tolower); + std::transform(appName.begin(), appName.end(), appName.begin(), ::tolower); #endif - for (const auto& ackApp : ack_apps) { - if (appName.find(ackApp) != std::string::npos) { + for (const auto& ackApp : ack_apps) { + if (appName.find(ackApp) != std::string::npos) { + if (is_dbus) { state->waitAck_ = true; LOTUS_INFO(ackApp + " detected, waiting for ack"); - break; } + state->wa_chromium_flag = true; + break; } } } diff --git a/src/lotus-state.cpp b/src/lotus-state.cpp index 764ab813..15cddb2f 100644 --- a/src/lotus-state.cpp +++ b/src/lotus-state.cpp @@ -651,20 +651,26 @@ namespace fcitx { std::string commonPrefix; std::string deletedPart; std::string addedPart; + + if (wa_chromium_flag) + keyEvent.filterAndAccept(); + if (compareAndSplitStrings(oldPreBuffer_, preeditStr, commonPrefix, deletedPart, addedPart) != 0) { if (deletedPart.empty()) { bool isCommit = false; bool wasAutoCapitalized = (currentSym != keyEvent.rawKey().sym()); if (!addedPart.empty()) { oldPreBuffer_ = preeditStr; - if (addedPart != keyUtf8 || wasAutoCapitalized) { + if (wa_chromium_flag || wasAutoCapitalized || addedPart != keyUtf8) { ic_->commitString(addedPart); LOTUS_INFO("Commit: " + addedPart); - keyEvent.filterAndAccept(); - isCommit = true; + if (!wa_chromium_flag) { + keyEvent.filterAndAccept(); + isCommit = true; + } } } - if (!isCommit) { + if (!wa_chromium_flag && !isCommit) { keyEvent.forward(); } } else { @@ -681,7 +687,8 @@ namespace fcitx { is_deleting_.store(false, std::memory_order_release); } - keyEvent.filterAndAccept(); + if (!wa_chromium_flag) + keyEvent.filterAndAccept(); performReplacement(deletedPart, addedPart); oldPreBuffer_ = preeditStr; } diff --git a/src/lotus-state.h b/src/lotus-state.h index cdb2fc59..cbef0034 100644 --- a/src/lotus-state.h +++ b/src/lotus-state.h @@ -106,6 +106,7 @@ namespace fcitx { bool shouldCapitalize_ = false; bool isPrevPunctuation_ = false; int64_t lastDeactivateTime_ = 0; + bool wa_chromium_flag = false; /** * @brief Connects to the uinput server. From b7e0135c567c0169a9a8fa393aaac23bd4989445 Mon Sep 17 00:00:00 2001 From: Nguyen Hoang Ky Date: Mon, 30 Mar 2026 22:14:57 +0700 Subject: [PATCH 11/12] Ready bump to 1.8.1 --- CMakeLists.txt | 2 +- packaging/debian/changelog | 11 +- packaging/rpm/fedora/fcitx5-lotus.spec | 11 +- packaging/rpm/opensuse/fcitx5-lotus.spec | 11 +- po/fcitx5-lotus.pot | 597 +++++++++++----------- po/vi.po | 614 ++++++++++++----------- 6 files changed, 635 insertions(+), 611 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c7adcacd..dc70896e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.10) -project(fcitx5-lotus VERSION 1.8.0) +project(fcitx5-lotus VERSION 1.8.1) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) configure_file( diff --git a/packaging/debian/changelog b/packaging/debian/changelog index aa07e4e0..40d1dc0f 100644 --- a/packaging/debian/changelog +++ b/packaging/debian/changelog @@ -1,7 +1,8 @@ -fcitx5-lotus (1.8.0-1) unstable; urgency=medium +fcitx5-lotus (1.8.1-1) unstable; urgency=medium - * Add backup/restore support - * Add dynamic macro ($TIME, $DATE) - * Fix some bugs + * Fix some bug in preedit mode + * Add black icons option for Breeze icon theme + * Fix bug duplicated character in chromium-based in wayland + * Fix bug lag when hold a key in uinput mode - -- Nguyen Hoang Ky Sat, 29 Mar 2026 00:03:00 +0700 \ No newline at end of file + -- Nguyen Hoang Ky Mon, 30 Mar 2026 22:04:00 +0700 \ No newline at end of file diff --git a/packaging/rpm/fedora/fcitx5-lotus.spec b/packaging/rpm/fedora/fcitx5-lotus.spec index 080d541a..aa27c815 100644 --- a/packaging/rpm/fedora/fcitx5-lotus.spec +++ b/packaging/rpm/fedora/fcitx5-lotus.spec @@ -1,5 +1,5 @@ Name: fcitx5-lotus -Version: 1.8.0 +Version: 1.8.1 Release: 1 Summary: Vietnamese input method for fcitx5 License: GPL-3.0-or-later @@ -164,7 +164,8 @@ fi %systemd_postun_with_restart fcitx5-lotus-server@.service %changelog -* Sat Mar 28 2026 Nguyen Hoang Ky - 1.8.0-1 -- Add backup/restore support -- Add dynamic macro ($TIME, $DATE) -- Fix some bugs +* Mon Mar 30 2026 Nguyen Hoang Ky - 1.8.1-1 +- Fix some bug in preedit mode +- Add black icons option for Breeze icon theme +- Fix bug duplicated character in chromium-based in wayland +- Fix bug lag when hold a key in uinput mode diff --git a/packaging/rpm/opensuse/fcitx5-lotus.spec b/packaging/rpm/opensuse/fcitx5-lotus.spec index 16c28a0a..1ff48e94 100644 --- a/packaging/rpm/opensuse/fcitx5-lotus.spec +++ b/packaging/rpm/opensuse/fcitx5-lotus.spec @@ -1,5 +1,5 @@ Name: fcitx5-lotus -Version: 1.8.0 +Version: 1.8.1 Release: 1 Summary: Vietnamese input method for fcitx5 License: GPL-3.0-or-later @@ -167,7 +167,8 @@ fi %systemd_postun_with_restart fcitx5-lotus-server@.service %changelog -* Sat Mar 28 2026 Nguyen Hoang Ky - 1.8.0-1 -- Add backup/restore support -- Add dynamic macro ($TIME, $DATE) -- Fix some bugs +* Mon Mar 30 2026 Nguyen Hoang Ky - 1.8.1-1 +- Fix some bug in preedit mode +- Add black icons option for Breeze icon theme +- Fix bug duplicated character in chromium-based in wayland +- Fix bug lag when hold a key in uinput mode diff --git a/po/fcitx5-lotus.pot b/po/fcitx5-lotus.pot index f4b14214..02a6363f 100644 --- a/po/fcitx5-lotus.pot +++ b/po/fcitx5-lotus.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-03-28 17:31+0700\n" +"POT-Creation-Date: 2026-03-30 20:49+0700\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -25,12 +25,12 @@ msgstr "" msgid "Value" msgstr "" -#: src/lotus-config.h:199 src/lotus-config.h:234 src/lotus-engine.cpp:151 -#: src/lotus-engine.cpp:304 +#: src/lotus-config.h:199 src/lotus-config.h:235 src/lotus-engine.cpp:152 +#: src/lotus-engine.cpp:305 msgid "Macro" msgstr "" -#: src/lotus-config.h:203 src/lotus-config.h:235 +#: src/lotus-config.h:203 src/lotus-config.h:236 msgid "Custom Keymap" msgstr "" @@ -54,15 +54,15 @@ msgstr "" msgid "Output Charset" msgstr "" -#: src/lotus-config.h:220 src/lotus-engine.cpp:150 +#: src/lotus-config.h:220 src/lotus-engine.cpp:151 msgid "Enable Spell Check" msgstr "" -#: src/lotus-config.h:220 src/lotus-engine.cpp:151 +#: src/lotus-config.h:220 src/lotus-engine.cpp:152 msgid "Enable Macro" msgstr "" -#: src/lotus-config.h:221 src/lotus-engine.cpp:152 src/lotus-engine.cpp:305 +#: src/lotus-config.h:221 src/lotus-engine.cpp:153 src/lotus-engine.cpp:306 msgid "Capitalize Macro" msgstr "" @@ -80,7 +80,7 @@ msgstr "" msgid "Type w to Produce ư" msgstr "" -#: src/lotus-config.h:224 src/lotus-engine.cpp:154 +#: src/lotus-config.h:224 src/lotus-engine.cpp:155 msgid "Auto Restore Keys With Invalid Words" msgstr "" @@ -104,95 +104,99 @@ msgstr "" msgid "Use Lotus Status Icons" msgstr "" -#: src/lotus-config.h:230 src/lotus-engine.cpp:156 +#: src/lotus-config.h:230 +msgid "Use Black Default Icons" +msgstr "" + +#: src/lotus-config.h:231 src/lotus-engine.cpp:157 msgid "Enable Custom Dictionary" msgstr "" -#: src/lotus-config.h:231 +#: src/lotus-config.h:232 msgid "Enable Custom Keymap" msgstr "" -#: src/lotus-config.h:232 +#: src/lotus-config.h:233 msgid "Time Format ($TIME in macro)" msgstr "" -#: src/lotus-config.h:233 +#: src/lotus-config.h:234 msgid "Date Format ($DATE in macro)" msgstr "" -#: src/lotus-config.h:236 +#: src/lotus-config.h:237 msgid "App Rules" msgstr "" -#: src/lotus-config.h:237 +#: src/lotus-config.h:238 msgid "Mode Menu Hotkey" msgstr "" -#: src/lotus-engine.cpp:123 +#: src/lotus-engine.cpp:124 msgid "Charset" msgstr "" -#: src/lotus-engine.cpp:150 src/lotus-engine.cpp:303 +#: src/lotus-engine.cpp:151 src/lotus-engine.cpp:304 msgid "Spell Check" msgstr "" -#: src/lotus-engine.cpp:155 src/lotus-engine.cpp:306 +#: src/lotus-engine.cpp:156 src/lotus-engine.cpp:307 msgid "Auto Non-VN Restore" msgstr "" -#: src/lotus-engine.cpp:156 src/lotus-engine.cpp:307 +#: src/lotus-engine.cpp:157 src/lotus-engine.cpp:308 msgid "Custom Dictionary" msgstr "" -#: src/lotus-engine.cpp:160 +#: src/lotus-engine.cpp:161 msgid "Settings" msgstr "" -#: src/lotus-engine.cpp:774 +#: src/lotus-engine.cpp:826 msgid "App: " msgstr "" -#: src/lotus-engine.cpp:775 +#: src/lotus-engine.cpp:827 msgid "[1] Uinput (Smooth)" msgstr "" -#: src/lotus-engine.cpp:776 +#: src/lotus-engine.cpp:828 msgid "[2] Uinput (Slow)" msgstr "" -#: src/lotus-engine.cpp:777 +#: src/lotus-engine.cpp:829 msgid "[3] Uinput (Hardcore)" msgstr "" -#: src/lotus-engine.cpp:778 +#: src/lotus-engine.cpp:830 msgid "[4] Surrounding Text" msgstr "" -#: src/lotus-engine.cpp:779 +#: src/lotus-engine.cpp:831 msgid "[q] Preedit" msgstr "" -#: src/lotus-engine.cpp:780 +#: src/lotus-engine.cpp:832 msgid "[w] Emoji Picker" msgstr "" -#: src/lotus-engine.cpp:781 +#: src/lotus-engine.cpp:833 msgid "[e] OFF" msgstr "" -#: src/lotus-engine.cpp:783 +#: src/lotus-engine.cpp:835 msgid "[r] Default Typing" msgstr "" -#: src/lotus-engine.cpp:795 +#: src/lotus-engine.cpp:847 msgid "Type" msgstr "" -#: src/lotus-engine.cpp:850 +#: src/lotus-engine.cpp:899 msgid "Lotus - Off" msgstr "" -#: src/lotus-state.cpp:243 +#: src/lotus-state.cpp:230 msgid "Page " msgstr "" @@ -213,6 +217,224 @@ msgstr "" msgid "Lotus Wrapper For Fcitx" msgstr "" +#: settings-gui/ui/pages/about.py:64 +#, python-brace-format +msgid "Version {}" +msgstr "" + +#: settings-gui/ui/pages/about.py:79 +msgid "" +"A state-of-the-art Vietnamese input method engine for Linux, designed for " +"speed, stability, and a premium user experience." +msgstr "" + +#: settings-gui/ui/pages/about.py:96 +msgid "Report Bug" +msgstr "" + +#: settings-gui/ui/pages/about.py:101 +msgid "Request Feature" +msgstr "" + +#: settings-gui/ui/pages/about.py:111 +msgid "Export Debug Logs" +msgstr "" + +#: settings-gui/ui/pages/about.py:123 +msgid "DEVELOPED BY" +msgstr "" + +#: settings-gui/ui/pages/about.py:157 +msgid "Licensed under the GNU General Public License v3.0" +msgstr "" + +#: settings-gui/ui/pages/about.py:167 +msgid "Save Debug Log" +msgstr "" + +#: settings-gui/ui/pages/about.py:206 settings-gui/ui/pages/backup.py:183 +#: settings-gui/ui/pages/backup.py:296 settings-gui/ui/main_window.py:252 +msgid "Success" +msgstr "" + +#: settings-gui/ui/pages/about.py:206 +msgid "Debug logs exported successfully to:\n" +msgstr "" + +#: settings-gui/ui/pages/about.py:208 settings-gui/ui/pages/mode_manager.py:705 +#: settings-gui/ui/pages/mode_manager.py:788 +#: settings-gui/ui/pages/backup.py:188 settings-gui/ui/pages/backup.py:242 +#: settings-gui/ui/pages/backup.py:314 settings-gui/ui/pages/dict_editor.py:247 +#: settings-gui/ui/pages/dict_editor.py:355 +#: settings-gui/ui/pages/dict_editor.py:418 +#: settings-gui/ui/pages/keymap_editor.py:537 +#: settings-gui/ui/pages/keymap_editor.py:625 +#: settings-gui/ui/pages/macro_editor.py:482 +#: settings-gui/ui/pages/macro_editor.py:552 +msgid "Error" +msgstr "" + +#: settings-gui/ui/pages/about.py:208 +msgid "Failed to export logs:\n" +msgstr "" + +#: settings-gui/ui/pages/mode_manager.py:118 +#: settings-gui/ui/pages/mode_manager.py:131 +#: settings-gui/ui/pages/mode_manager.py:374 +msgid "Add Application" +msgstr "" + +#: settings-gui/ui/pages/mode_manager.py:133 +msgid "Assign a specific input mode to an application" +msgstr "" + +#: settings-gui/ui/pages/mode_manager.py:148 +msgid "Search process name..." +msgstr "" + +#: settings-gui/ui/pages/mode_manager.py:152 +msgid "Refresh Process List" +msgstr "" + +#: settings-gui/ui/pages/mode_manager.py:166 +msgid "Running" +msgstr "" + +#: settings-gui/ui/pages/mode_manager.py:172 +msgid "Enter application name or path..." +msgstr "" + +#: settings-gui/ui/pages/mode_manager.py:176 +msgid "Manual input" +msgstr "" + +#: settings-gui/ui/pages/mode_manager.py:180 +msgid "No application selected" +msgstr "" + +#: settings-gui/ui/pages/mode_manager.py:183 settings-gui/ui/main_window.py:140 +msgid "&Cancel" +msgstr "" + +#: settings-gui/ui/pages/mode_manager.py:186 +msgid "&Add" +msgstr "" + +#: settings-gui/ui/pages/mode_manager.py:322 +msgid "Selected:" +msgstr "" + +#: settings-gui/ui/pages/mode_manager.py:363 +msgid "Search applications..." +msgstr "" + +#: settings-gui/ui/pages/mode_manager.py:375 +#: settings-gui/ui/pages/dict_editor.py:135 +#: settings-gui/ui/pages/keymap_editor.py:329 +#: settings-gui/ui/pages/macro_editor.py:201 +msgid "Remove" +msgstr "" + +#: settings-gui/ui/pages/mode_manager.py:396 settings-gui/ui/main_window.py:170 +msgid "Applications" +msgstr "" + +#: settings-gui/ui/pages/mode_manager.py:403 +msgid "Global Default Mode:" +msgstr "" + +#: settings-gui/ui/pages/mode_manager.py:425 +msgid "Select an application" +msgstr "" + +#: settings-gui/ui/pages/mode_manager.py:655 +msgid "Confirm Remove" +msgstr "" + +#: settings-gui/ui/pages/mode_manager.py:656 +msgid "Are you sure you want to remove rules for this application?" +msgstr "" + +#: settings-gui/ui/pages/mode_manager.py:694 +msgid "Import Application Rules" +msgstr "" + +#: settings-gui/ui/pages/mode_manager.py:696 +#: settings-gui/ui/pages/mode_manager.py:770 +#: settings-gui/ui/pages/dict_editor.py:403 +#: settings-gui/ui/pages/keymap_editor.py:604 +#: settings-gui/ui/pages/macro_editor.py:534 +msgid "Tab-separated (*.tsv);;Text files (*.txt);;All files (*)" +msgstr "" + +#: settings-gui/ui/pages/mode_manager.py:705 +msgid "Cannot open file for reading:" +msgstr "" + +#: settings-gui/ui/pages/mode_manager.py:735 +#: settings-gui/ui/pages/dict_editor.py:368 +#: settings-gui/ui/pages/keymap_editor.py:557 +#: settings-gui/ui/pages/macro_editor.py:503 +msgid "Confirm Import" +msgstr "" + +#: settings-gui/ui/pages/mode_manager.py:736 +msgid "Merge imported application rules?" +msgstr "" + +#: settings-gui/ui/pages/mode_manager.py:754 +#: settings-gui/ui/pages/dict_editor.py:389 +#: settings-gui/ui/pages/keymap_editor.py:588 +#: settings-gui/ui/pages/macro_editor.py:520 +msgid "Import Complete" +msgstr "" + +#: settings-gui/ui/pages/mode_manager.py:755 +#, python-brace-format +msgid "Imported {} rules, skipped {} invalid lines." +msgstr "" + +#: settings-gui/ui/pages/mode_manager.py:762 +#: settings-gui/ui/pages/dict_editor.py:396 +#: settings-gui/ui/pages/keymap_editor.py:596 +#: settings-gui/ui/pages/macro_editor.py:527 +msgid "Export" +msgstr "" + +#: settings-gui/ui/pages/mode_manager.py:762 +msgid "The application rules list is empty, nothing to export." +msgstr "" + +#: settings-gui/ui/pages/mode_manager.py:768 +msgid "Export Application Rules" +msgstr "" + +#: settings-gui/ui/pages/mode_manager.py:784 +#: settings-gui/ui/pages/dict_editor.py:414 +#: settings-gui/ui/pages/keymap_editor.py:621 +#: settings-gui/ui/pages/macro_editor.py:548 +msgid "Export Complete" +msgstr "" + +#: settings-gui/ui/pages/mode_manager.py:785 +#, python-brace-format +msgid "" +"Exported {} rules to:\n" +"{}" +msgstr "" + +#: settings-gui/ui/pages/mode_manager.py:788 +msgid "Cannot open file for writing:" +msgstr "" + +#: settings-gui/ui/pages/dynamic_settings.py:117 +msgid "Failed to load configuration." +msgstr "" + +#: settings-gui/ui/pages/dynamic_settings.py:165 +msgid "No interface settings available yet." +msgstr "" + #: settings-gui/ui/pages/backup.py:52 msgid "Backup & Restore" msgstr "" @@ -223,8 +445,8 @@ msgstr "" #: settings-gui/ui/pages/backup.py:61 msgid "" -"Save your configurations to a JSON file, or restore them from a .lotusbak " -"file. Select what you want to include:" +"Save or restore your configurations via JSON files. Select the components " +"you wish to include:" msgstr "" #: settings-gui/ui/pages/backup.py:70 settings-gui/ui/pages/backup.py:216 @@ -273,31 +495,13 @@ msgid "Export Backup" msgstr "" #: settings-gui/ui/pages/backup.py:143 settings-gui/ui/pages/backup.py:197 -msgid "Lotus Backup (*.lotusbak);;All Files (*)" -msgstr "" - -#: settings-gui/ui/pages/backup.py:183 settings-gui/ui/pages/backup.py:296 -#: settings-gui/ui/pages/about.py:206 settings-gui/ui/main_window.py:252 -msgid "Success" +msgid "JSON Backup (*.json);;All Files (*)" msgstr "" #: settings-gui/ui/pages/backup.py:183 msgid "Backup exported successfully to:\n" msgstr "" -#: settings-gui/ui/pages/backup.py:188 settings-gui/ui/pages/backup.py:242 -#: settings-gui/ui/pages/backup.py:314 settings-gui/ui/pages/dict_editor.py:247 -#: settings-gui/ui/pages/dict_editor.py:356 -#: settings-gui/ui/pages/dict_editor.py:419 -#: settings-gui/ui/pages/keymap_editor.py:500 -#: settings-gui/ui/pages/keymap_editor.py:588 -#: settings-gui/ui/pages/macro_editor.py:476 -#: settings-gui/ui/pages/macro_editor.py:546 -#: settings-gui/ui/pages/mode_manager.py:705 -#: settings-gui/ui/pages/mode_manager.py:788 settings-gui/ui/pages/about.py:208 -msgid "Error" -msgstr "" - #: settings-gui/ui/pages/backup.py:188 msgid "Failed to export backup:\n" msgstr "" @@ -359,10 +563,11 @@ msgid "Word (e.g. khongdau)" msgstr "" #: settings-gui/ui/pages/dict_editor.py:109 -#: settings-gui/ui/pages/dict_editor.py:320 +#: settings-gui/ui/pages/dict_editor.py:319 #: settings-gui/ui/pages/keymap_editor.py:301 +#: settings-gui/ui/pages/keymap_editor.py:474 #: settings-gui/ui/pages/macro_editor.py:171 -#: settings-gui/ui/pages/macro_editor.py:452 +#: settings-gui/ui/pages/macro_editor.py:458 msgid "Add" msgstr "" @@ -370,119 +575,68 @@ msgstr "" msgid "Word:" msgstr "" -#: settings-gui/ui/pages/dict_editor.py:135 -#: settings-gui/ui/pages/keymap_editor.py:328 -#: settings-gui/ui/pages/macro_editor.py:201 -#: settings-gui/ui/pages/mode_manager.py:375 -msgid "Remove" -msgstr "" - #: settings-gui/ui/pages/dict_editor.py:247 #, python-brace-format msgid "Failed to save dictionary: {}" msgstr "" #: settings-gui/ui/pages/dict_editor.py:274 -#: settings-gui/ui/pages/dict_editor.py:307 +#: settings-gui/ui/pages/dict_editor.py:306 msgid "Warning: Dictionary words should not contain spaces." msgstr "" -#: settings-gui/ui/pages/dict_editor.py:316 +#: settings-gui/ui/pages/dict_editor.py:315 msgid "Exists" msgstr "" -#: settings-gui/ui/pages/dict_editor.py:346 +#: settings-gui/ui/pages/dict_editor.py:345 msgid "Import Custom Dictionary" msgstr "" -#: settings-gui/ui/pages/dict_editor.py:348 +#: settings-gui/ui/pages/dict_editor.py:347 msgid "Dictionary files (*.tsv *.txt);;All files (*)" msgstr "" -#: settings-gui/ui/pages/dict_editor.py:356 -#: settings-gui/ui/pages/keymap_editor.py:500 -#: settings-gui/ui/pages/macro_editor.py:476 +#: settings-gui/ui/pages/dict_editor.py:355 +#: settings-gui/ui/pages/keymap_editor.py:537 +#: settings-gui/ui/pages/macro_editor.py:482 #, python-brace-format msgid "Cannot open file for reading: {}" msgstr "" -#: settings-gui/ui/pages/dict_editor.py:369 -#: settings-gui/ui/pages/keymap_editor.py:520 -#: settings-gui/ui/pages/macro_editor.py:497 -#: settings-gui/ui/pages/mode_manager.py:735 -msgid "Confirm Import" -msgstr "" - -#: settings-gui/ui/pages/dict_editor.py:371 +#: settings-gui/ui/pages/dict_editor.py:370 msgid "" "The current dictionary is not empty. Imported entries will be merged. " "Continue?" msgstr "" #: settings-gui/ui/pages/dict_editor.py:390 -#: settings-gui/ui/pages/keymap_editor.py:551 -#: settings-gui/ui/pages/macro_editor.py:514 -#: settings-gui/ui/pages/mode_manager.py:754 -msgid "Import Complete" -msgstr "" - -#: settings-gui/ui/pages/dict_editor.py:391 #, python-brace-format msgid "Imported {} words." msgstr "" -#: settings-gui/ui/pages/dict_editor.py:397 -#: settings-gui/ui/pages/keymap_editor.py:559 -#: settings-gui/ui/pages/macro_editor.py:521 -#: settings-gui/ui/pages/mode_manager.py:762 -msgid "Export" -msgstr "" - -#: settings-gui/ui/pages/dict_editor.py:397 +#: settings-gui/ui/pages/dict_editor.py:396 msgid "The custom dictionary is empty, nothing to export." msgstr "" -#: settings-gui/ui/pages/dict_editor.py:402 +#: settings-gui/ui/pages/dict_editor.py:401 msgid "Export Custom Dictionary" msgstr "" -#: settings-gui/ui/pages/dict_editor.py:404 -#: settings-gui/ui/pages/keymap_editor.py:567 -#: settings-gui/ui/pages/macro_editor.py:528 -#: settings-gui/ui/pages/mode_manager.py:696 -#: settings-gui/ui/pages/mode_manager.py:770 -msgid "Tab-separated (*.tsv);;Text files (*.txt);;All files (*)" -msgstr "" - #: settings-gui/ui/pages/dict_editor.py:415 -#: settings-gui/ui/pages/keymap_editor.py:584 -#: settings-gui/ui/pages/macro_editor.py:542 -#: settings-gui/ui/pages/mode_manager.py:784 -msgid "Export Complete" -msgstr "" - -#: settings-gui/ui/pages/dict_editor.py:416 #, python-brace-format msgid "" "Exported {} words to:\n" "{}" msgstr "" -#: settings-gui/ui/pages/dict_editor.py:419 -#: settings-gui/ui/pages/keymap_editor.py:588 -#: settings-gui/ui/pages/macro_editor.py:546 +#: settings-gui/ui/pages/dict_editor.py:418 +#: settings-gui/ui/pages/keymap_editor.py:625 +#: settings-gui/ui/pages/macro_editor.py:552 #, python-brace-format msgid "Cannot open file for writing: {}" msgstr "" -#: settings-gui/ui/pages/dynamic_settings.py:117 -msgid "Failed to load configuration." -msgstr "" - -#: settings-gui/ui/pages/dynamic_settings.py:165 -msgid "No interface settings available yet." -msgstr "" - #: settings-gui/ui/pages/keymap_editor.py:242 #: settings-gui/ui/main_window.py:185 msgid "Keymap" @@ -505,58 +659,68 @@ msgid "Key (Example: s)" msgstr "" #: settings-gui/ui/pages/keymap_editor.py:302 -msgid "Add / Update Keymap" +#: settings-gui/ui/pages/keymap_editor.py:475 +msgid "Add Keymap" msgstr "" -#: settings-gui/ui/pages/keymap_editor.py:312 +#: settings-gui/ui/pages/keymap_editor.py:313 msgid "Action" msgstr "" -#: settings-gui/ui/pages/keymap_editor.py:329 +#: settings-gui/ui/pages/keymap_editor.py:330 msgid "Remove selected row" msgstr "" -#: settings-gui/ui/pages/keymap_editor.py:445 +#: settings-gui/ui/pages/keymap_editor.py:470 +#: settings-gui/ui/pages/macro_editor.py:455 +msgid "Update" +msgstr "" + +#: settings-gui/ui/pages/keymap_editor.py:471 +msgid "Update Keymap" +msgstr "" + +#: settings-gui/ui/pages/keymap_editor.py:482 msgid "Confirm" msgstr "" -#: settings-gui/ui/pages/keymap_editor.py:446 +#: settings-gui/ui/pages/keymap_editor.py:483 msgid "This operation will replace all existing keys with " msgstr "" -#: settings-gui/ui/pages/keymap_editor.py:448 +#: settings-gui/ui/pages/keymap_editor.py:485 msgid ". Are you sure?" msgstr "" -#: settings-gui/ui/pages/keymap_editor.py:489 +#: settings-gui/ui/pages/keymap_editor.py:526 msgid "Import Keymap" msgstr "" -#: settings-gui/ui/pages/keymap_editor.py:491 -#: settings-gui/ui/pages/macro_editor.py:468 +#: settings-gui/ui/pages/keymap_editor.py:528 +#: settings-gui/ui/pages/macro_editor.py:474 msgid "Tab-separated (*.tsv *.txt);;All files (*)" msgstr "" -#: settings-gui/ui/pages/keymap_editor.py:521 +#: settings-gui/ui/pages/keymap_editor.py:558 msgid "Merge imported entries?" msgstr "" -#: settings-gui/ui/pages/keymap_editor.py:552 -#: settings-gui/ui/pages/macro_editor.py:515 +#: settings-gui/ui/pages/keymap_editor.py:589 +#: settings-gui/ui/pages/macro_editor.py:521 #, python-brace-format msgid "Imported {} entries, skipped {} invalid lines." msgstr "" -#: settings-gui/ui/pages/keymap_editor.py:559 +#: settings-gui/ui/pages/keymap_editor.py:596 msgid "The keymap list is empty, nothing to export." msgstr "" -#: settings-gui/ui/pages/keymap_editor.py:565 +#: settings-gui/ui/pages/keymap_editor.py:602 msgid "Export Keymap" msgstr "" -#: settings-gui/ui/pages/keymap_editor.py:585 -#: settings-gui/ui/pages/macro_editor.py:543 +#: settings-gui/ui/pages/keymap_editor.py:622 +#: settings-gui/ui/pages/macro_editor.py:549 #, python-brace-format msgid "" "Exported {} entries to:\n" @@ -628,182 +792,29 @@ msgstr "" msgid "Move Down" msgstr "" -#: settings-gui/ui/pages/macro_editor.py:366 -#: settings-gui/ui/pages/macro_editor.py:435 +#: settings-gui/ui/pages/macro_editor.py:376 +#: settings-gui/ui/pages/macro_editor.py:444 msgid "Warning: Macro key should not contain spaces or special characters." msgstr "" -#: settings-gui/ui/pages/macro_editor.py:449 -msgid "Update" -msgstr "" - -#: settings-gui/ui/pages/macro_editor.py:466 +#: settings-gui/ui/pages/macro_editor.py:472 msgid "Import Macros" msgstr "" -#: settings-gui/ui/pages/macro_editor.py:499 +#: settings-gui/ui/pages/macro_editor.py:505 msgid "" "The current macro list is not empty. Imported entries will be merged. " "Continue?" msgstr "" -#: settings-gui/ui/pages/macro_editor.py:521 +#: settings-gui/ui/pages/macro_editor.py:527 msgid "The macro list is empty, nothing to export." msgstr "" -#: settings-gui/ui/pages/macro_editor.py:526 +#: settings-gui/ui/pages/macro_editor.py:532 msgid "Export Macros" msgstr "" -#: settings-gui/ui/pages/mode_manager.py:118 -#: settings-gui/ui/pages/mode_manager.py:131 -#: settings-gui/ui/pages/mode_manager.py:374 -msgid "Add Application" -msgstr "" - -#: settings-gui/ui/pages/mode_manager.py:133 -msgid "Assign a specific input mode to an application" -msgstr "" - -#: settings-gui/ui/pages/mode_manager.py:148 -msgid "Search process name..." -msgstr "" - -#: settings-gui/ui/pages/mode_manager.py:152 -msgid "Refresh Process List" -msgstr "" - -#: settings-gui/ui/pages/mode_manager.py:166 -msgid "Running" -msgstr "" - -#: settings-gui/ui/pages/mode_manager.py:172 -msgid "Enter application name or path..." -msgstr "" - -#: settings-gui/ui/pages/mode_manager.py:176 -msgid "Manual input" -msgstr "" - -#: settings-gui/ui/pages/mode_manager.py:180 -msgid "No application selected" -msgstr "" - -#: settings-gui/ui/pages/mode_manager.py:183 settings-gui/ui/main_window.py:140 -msgid "&Cancel" -msgstr "" - -#: settings-gui/ui/pages/mode_manager.py:186 -msgid "&Add" -msgstr "" - -#: settings-gui/ui/pages/mode_manager.py:322 -msgid "Selected:" -msgstr "" - -#: settings-gui/ui/pages/mode_manager.py:363 -msgid "Search applications..." -msgstr "" - -#: settings-gui/ui/pages/mode_manager.py:396 settings-gui/ui/main_window.py:170 -msgid "Applications" -msgstr "" - -#: settings-gui/ui/pages/mode_manager.py:403 -msgid "Global Default Mode:" -msgstr "" - -#: settings-gui/ui/pages/mode_manager.py:425 -msgid "Select an application" -msgstr "" - -#: settings-gui/ui/pages/mode_manager.py:655 -msgid "Confirm Remove" -msgstr "" - -#: settings-gui/ui/pages/mode_manager.py:656 -msgid "Are you sure you want to remove rules for this application?" -msgstr "" - -#: settings-gui/ui/pages/mode_manager.py:694 -msgid "Import Application Rules" -msgstr "" - -#: settings-gui/ui/pages/mode_manager.py:705 -msgid "Cannot open file for reading:" -msgstr "" - -#: settings-gui/ui/pages/mode_manager.py:736 -msgid "Merge imported application rules?" -msgstr "" - -#: settings-gui/ui/pages/mode_manager.py:755 -#, python-brace-format -msgid "Imported {} rules, skipped {} invalid lines." -msgstr "" - -#: settings-gui/ui/pages/mode_manager.py:762 -msgid "The application rules list is empty, nothing to export." -msgstr "" - -#: settings-gui/ui/pages/mode_manager.py:768 -msgid "Export Application Rules" -msgstr "" - -#: settings-gui/ui/pages/mode_manager.py:785 -#, python-brace-format -msgid "" -"Exported {} rules to:\n" -"{}" -msgstr "" - -#: settings-gui/ui/pages/mode_manager.py:788 -msgid "Cannot open file for writing:" -msgstr "" - -#: settings-gui/ui/pages/about.py:64 -#, python-brace-format -msgid "Version {}" -msgstr "" - -#: settings-gui/ui/pages/about.py:79 -msgid "" -"A state-of-the-art Vietnamese input method engine for Linux, designed for " -"speed, stability, and a premium user experience." -msgstr "" - -#: settings-gui/ui/pages/about.py:96 -msgid "Report Bug" -msgstr "" - -#: settings-gui/ui/pages/about.py:101 -msgid "Request Feature" -msgstr "" - -#: settings-gui/ui/pages/about.py:111 -msgid "Export Debug Logs" -msgstr "" - -#: settings-gui/ui/pages/about.py:123 -msgid "DEVELOPED BY" -msgstr "" - -#: settings-gui/ui/pages/about.py:157 -msgid "Licensed under the GNU General Public License v3.0" -msgstr "" - -#: settings-gui/ui/pages/about.py:167 -msgid "Save Debug Log" -msgstr "" - -#: settings-gui/ui/pages/about.py:206 -msgid "Debug logs exported successfully to:\n" -msgstr "" - -#: settings-gui/ui/pages/about.py:208 -msgid "Failed to export logs:\n" -msgstr "" - #: settings-gui/ui/components.py:46 settings-gui/ui/components.py:56 msgid "None" msgstr "" diff --git a/po/vi.po b/po/vi.po index 296620d4..b2643bf8 100644 --- a/po/vi.po +++ b/po/vi.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: fcitx5-lotus\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-03-28 17:31+0700\n" -"PO-Revision-Date: 2026-03-28 22:26+0700\n" +"POT-Creation-Date: 2026-03-30 20:49+0700\n" +"PO-Revision-Date: 2026-03-30 21:55+0700\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" "Language: vi\n" @@ -26,12 +26,12 @@ msgstr "Phím" msgid "Value" msgstr "Giá trị" -#: src/lotus-config.h:199 src/lotus-config.h:234 src/lotus-engine.cpp:151 -#: src/lotus-engine.cpp:304 +#: src/lotus-config.h:199 src/lotus-config.h:235 src/lotus-engine.cpp:152 +#: src/lotus-engine.cpp:305 msgid "Macro" msgstr "Gõ tắt" -#: src/lotus-config.h:203 src/lotus-config.h:235 +#: src/lotus-config.h:203 src/lotus-config.h:236 msgid "Custom Keymap" msgstr "Keymap tuỳ chỉnh" @@ -55,15 +55,15 @@ msgstr "Kiểu gõ" msgid "Output Charset" msgstr "Bảng mã" -#: src/lotus-config.h:220 src/lotus-engine.cpp:150 +#: src/lotus-config.h:220 src/lotus-engine.cpp:151 msgid "Enable Spell Check" msgstr "Bật kiểm tra chính tả" -#: src/lotus-config.h:220 src/lotus-engine.cpp:151 +#: src/lotus-config.h:220 src/lotus-engine.cpp:152 msgid "Enable Macro" msgstr "Bật gõ tắt" -#: src/lotus-config.h:221 src/lotus-engine.cpp:152 src/lotus-engine.cpp:305 +#: src/lotus-config.h:221 src/lotus-engine.cpp:153 src/lotus-engine.cpp:306 msgid "Capitalize Macro" msgstr "Gõ tắt chữ hoa" @@ -81,7 +81,7 @@ msgstr "Phím cách 2 lần để chấm câu (thử nghiệm)" msgid "Type w to Produce ư" msgstr "Gõ w thành ư" -#: src/lotus-config.h:224 src/lotus-engine.cpp:154 +#: src/lotus-config.h:224 src/lotus-engine.cpp:155 msgid "Auto Restore Keys With Invalid Words" msgstr "Tự động khôi phục phím với từ sai" @@ -105,95 +105,99 @@ msgstr "Sửa lỗi chế độ Uinput với Ack" msgid "Use Lotus Status Icons" msgstr "Sử dụng biểu tượng trạng thái Lotus" -#: src/lotus-config.h:230 src/lotus-engine.cpp:156 +#: src/lotus-config.h:230 +msgid "Use Black Default Icons" +msgstr "Sử dụng biểu tượng đen mặc định" + +#: src/lotus-config.h:231 src/lotus-engine.cpp:157 msgid "Enable Custom Dictionary" msgstr "Bật từ điển tuỳ chỉnh" -#: src/lotus-config.h:231 +#: src/lotus-config.h:232 msgid "Enable Custom Keymap" msgstr "Bật keymap tuỳ chỉnh" -#: src/lotus-config.h:232 +#: src/lotus-config.h:233 msgid "Time Format ($TIME in macro)" msgstr "Định dạng giờ ($TIME trong gõ tắt)" -#: src/lotus-config.h:233 +#: src/lotus-config.h:234 msgid "Date Format ($DATE in macro)" msgstr "Định dạng ngày ($DATE trong macro)" -#: src/lotus-config.h:236 +#: src/lotus-config.h:237 msgid "App Rules" msgstr "Quy tắc ứng dụng" -#: src/lotus-config.h:237 +#: src/lotus-config.h:238 msgid "Mode Menu Hotkey" msgstr "Phím tắt menu chế độ gõ" -#: src/lotus-engine.cpp:123 +#: src/lotus-engine.cpp:124 msgid "Charset" msgstr "Bảng mã" -#: src/lotus-engine.cpp:150 src/lotus-engine.cpp:303 +#: src/lotus-engine.cpp:151 src/lotus-engine.cpp:304 msgid "Spell Check" msgstr "Kiểm tra chính tả" -#: src/lotus-engine.cpp:155 src/lotus-engine.cpp:306 +#: src/lotus-engine.cpp:156 src/lotus-engine.cpp:307 msgid "Auto Non-VN Restore" msgstr "Tự động khôi phục với từ không phải tiếng Việt" -#: src/lotus-engine.cpp:156 src/lotus-engine.cpp:307 +#: src/lotus-engine.cpp:157 src/lotus-engine.cpp:308 msgid "Custom Dictionary" msgstr "Từ điển tuỳ chỉnh" -#: src/lotus-engine.cpp:160 +#: src/lotus-engine.cpp:161 msgid "Settings" msgstr "Cài đặt" -#: src/lotus-engine.cpp:774 +#: src/lotus-engine.cpp:826 msgid "App: " msgstr "App: " -#: src/lotus-engine.cpp:775 +#: src/lotus-engine.cpp:827 msgid "[1] Uinput (Smooth)" msgstr "[1] Uinput (Mượt)" -#: src/lotus-engine.cpp:776 +#: src/lotus-engine.cpp:828 msgid "[2] Uinput (Slow)" msgstr "[2] Uinput (Chậm)" -#: src/lotus-engine.cpp:777 +#: src/lotus-engine.cpp:829 msgid "[3] Uinput (Hardcore)" msgstr "[3] Uinput (Cứng)" -#: src/lotus-engine.cpp:778 +#: src/lotus-engine.cpp:830 msgid "[4] Surrounding Text" msgstr "[4] Văn bản xung quanh" -#: src/lotus-engine.cpp:779 +#: src/lotus-engine.cpp:831 msgid "[q] Preedit" msgstr "[q] Gạch chân" -#: src/lotus-engine.cpp:780 +#: src/lotus-engine.cpp:832 msgid "[w] Emoji Picker" msgstr "[w] Chọn Emoji" -#: src/lotus-engine.cpp:781 +#: src/lotus-engine.cpp:833 msgid "[e] OFF" msgstr "[e] Tắt" -#: src/lotus-engine.cpp:783 +#: src/lotus-engine.cpp:835 msgid "[r] Default Typing" msgstr "[r] Chế độ gõ mặc định" -#: src/lotus-engine.cpp:795 +#: src/lotus-engine.cpp:847 msgid "Type" msgstr "Gõ" -#: src/lotus-engine.cpp:850 +#: src/lotus-engine.cpp:899 msgid "Lotus - Off" msgstr "Lotus - Tắt" -#: src/lotus-state.cpp:243 +#: src/lotus-state.cpp:230 msgid "Page " msgstr "Trang " @@ -214,6 +218,228 @@ msgstr "Lotus là bộ gõ tiếng Việt cho Fcitx5, hỗ trợ nhiều chế msgid "Lotus Wrapper For Fcitx" msgstr "Bộ gõ Lotus cho Fcitx" +#: settings-gui/ui/pages/about.py:64 +#, python-brace-format +msgid "Version {}" +msgstr "Phiên bản {}" + +#: settings-gui/ui/pages/about.py:79 +msgid "" +"A state-of-the-art Vietnamese input method engine for Linux, designed for " +"speed, stability, and a premium user experience." +msgstr "" +"Một bộ gõ tiếng Việt cho Linux, thiết kế cho tốc độ, ổn định và trải nghiệm " +"cao cấp." + +#: settings-gui/ui/pages/about.py:96 +msgid "Report Bug" +msgstr "Báo cáo lỗi" + +#: settings-gui/ui/pages/about.py:101 +msgid "Request Feature" +msgstr "Đề xuất tính năng" + +#: settings-gui/ui/pages/about.py:111 +msgid "Export Debug Logs" +msgstr "Xuất nhật ký gỡ lỗi" + +#: settings-gui/ui/pages/about.py:123 +msgid "DEVELOPED BY" +msgstr "PHÁT TRIỂN BỞI" + +#: settings-gui/ui/pages/about.py:157 +msgid "Licensed under the GNU General Public License v3.0" +msgstr "Được cấp phép dưới Giấy phép Công cộng GNU v3.0" + +#: settings-gui/ui/pages/about.py:167 +msgid "Save Debug Log" +msgstr "Lưu nhật ký gỡ lỗi" + +#: settings-gui/ui/pages/about.py:206 settings-gui/ui/pages/backup.py:183 +#: settings-gui/ui/pages/backup.py:296 settings-gui/ui/main_window.py:252 +msgid "Success" +msgstr "Thành công" + +#: settings-gui/ui/pages/about.py:206 +msgid "Debug logs exported successfully to:\n" +msgstr "Nhật ký gỡ lỗi được xuất thành công vào:\n" + +#: settings-gui/ui/pages/about.py:208 settings-gui/ui/pages/mode_manager.py:705 +#: settings-gui/ui/pages/mode_manager.py:788 +#: settings-gui/ui/pages/backup.py:188 settings-gui/ui/pages/backup.py:242 +#: settings-gui/ui/pages/backup.py:314 settings-gui/ui/pages/dict_editor.py:247 +#: settings-gui/ui/pages/dict_editor.py:355 +#: settings-gui/ui/pages/dict_editor.py:418 +#: settings-gui/ui/pages/keymap_editor.py:537 +#: settings-gui/ui/pages/keymap_editor.py:625 +#: settings-gui/ui/pages/macro_editor.py:482 +#: settings-gui/ui/pages/macro_editor.py:552 +msgid "Error" +msgstr "Lỗi" + +#: settings-gui/ui/pages/about.py:208 +msgid "Failed to export logs:\n" +msgstr "Xuất nhật ký gỡ lỗi thất bại\n" + +#: settings-gui/ui/pages/mode_manager.py:118 +#: settings-gui/ui/pages/mode_manager.py:131 +#: settings-gui/ui/pages/mode_manager.py:374 +msgid "Add Application" +msgstr "Thêm ứng dụng" + +#: settings-gui/ui/pages/mode_manager.py:133 +msgid "Assign a specific input mode to an application" +msgstr "Gán một chế độ gõ cụ thể cho một ứng dụng" + +#: settings-gui/ui/pages/mode_manager.py:148 +msgid "Search process name..." +msgstr "Tìm tên tiến trình..." + +#: settings-gui/ui/pages/mode_manager.py:152 +msgid "Refresh Process List" +msgstr "Làm mới danh sách tiến trình" + +#: settings-gui/ui/pages/mode_manager.py:166 +msgid "Running" +msgstr "Đang chạy" + +#: settings-gui/ui/pages/mode_manager.py:172 +msgid "Enter application name or path..." +msgstr "Nhập tên ứng dụng hoặc đường dẫn..." + +#: settings-gui/ui/pages/mode_manager.py:176 +msgid "Manual input" +msgstr "Nhập thủ công" + +#: settings-gui/ui/pages/mode_manager.py:180 +msgid "No application selected" +msgstr "Không có ứng dụng được chọn" + +#: settings-gui/ui/pages/mode_manager.py:183 settings-gui/ui/main_window.py:140 +msgid "&Cancel" +msgstr "&Huỷ" + +#: settings-gui/ui/pages/mode_manager.py:186 +msgid "&Add" +msgstr "&Thêm" + +#: settings-gui/ui/pages/mode_manager.py:322 +msgid "Selected:" +msgstr "Được chọn:" + +#: settings-gui/ui/pages/mode_manager.py:363 +msgid "Search applications..." +msgstr "Tìm ứng dụng..." + +#: settings-gui/ui/pages/mode_manager.py:375 +#: settings-gui/ui/pages/dict_editor.py:135 +#: settings-gui/ui/pages/keymap_editor.py:329 +#: settings-gui/ui/pages/macro_editor.py:201 +msgid "Remove" +msgstr "Xoá" + +#: settings-gui/ui/pages/mode_manager.py:396 settings-gui/ui/main_window.py:170 +msgid "Applications" +msgstr "Ứng dụng" + +#: settings-gui/ui/pages/mode_manager.py:403 +msgid "Global Default Mode:" +msgstr "Chế độ toàn cục mặc định:" + +#: settings-gui/ui/pages/mode_manager.py:425 +msgid "Select an application" +msgstr "Tìm một ứng dụng" + +#: settings-gui/ui/pages/mode_manager.py:655 +msgid "Confirm Remove" +msgstr "Xác nhận xoá" + +#: settings-gui/ui/pages/mode_manager.py:656 +msgid "Are you sure you want to remove rules for this application?" +msgstr "Bạn có chắc chắn muốn xoá quy tắc cho ứng dụng này?" + +#: settings-gui/ui/pages/mode_manager.py:694 +msgid "Import Application Rules" +msgstr "Nhập quy tắc ứng dụng" + +#: settings-gui/ui/pages/mode_manager.py:696 +#: settings-gui/ui/pages/mode_manager.py:770 +#: settings-gui/ui/pages/dict_editor.py:403 +#: settings-gui/ui/pages/keymap_editor.py:604 +#: settings-gui/ui/pages/macro_editor.py:534 +msgid "Tab-separated (*.tsv);;Text files (*.txt);;All files (*)" +msgstr "Phân cách tab (*.tsv);; Tệp văn bản (*txt);; Tất cả tệp (*)" + +#: settings-gui/ui/pages/mode_manager.py:705 +msgid "Cannot open file for reading:" +msgstr "Không thể mở tệp để đọc:" + +#: settings-gui/ui/pages/mode_manager.py:735 +#: settings-gui/ui/pages/dict_editor.py:368 +#: settings-gui/ui/pages/keymap_editor.py:557 +#: settings-gui/ui/pages/macro_editor.py:503 +msgid "Confirm Import" +msgstr "Xác nhận nhập" + +#: settings-gui/ui/pages/mode_manager.py:736 +msgid "Merge imported application rules?" +msgstr "Gộp các quy tắc ứng dụng đã nhập?" + +#: settings-gui/ui/pages/mode_manager.py:754 +#: settings-gui/ui/pages/dict_editor.py:389 +#: settings-gui/ui/pages/keymap_editor.py:588 +#: settings-gui/ui/pages/macro_editor.py:520 +msgid "Import Complete" +msgstr "Nhập thành công" + +#: settings-gui/ui/pages/mode_manager.py:755 +#, python-brace-format +msgid "Imported {} rules, skipped {} invalid lines." +msgstr "Nhập {} quy tắc, bỏ qua {} dòng không hợp lệ." + +#: settings-gui/ui/pages/mode_manager.py:762 +#: settings-gui/ui/pages/dict_editor.py:396 +#: settings-gui/ui/pages/keymap_editor.py:596 +#: settings-gui/ui/pages/macro_editor.py:527 +msgid "Export" +msgstr "Xuất" + +#: settings-gui/ui/pages/mode_manager.py:762 +msgid "The application rules list is empty, nothing to export." +msgstr "Danh sách quy tắc ứng dụng đang rỗng, không có gì để xuất." + +#: settings-gui/ui/pages/mode_manager.py:768 +msgid "Export Application Rules" +msgstr "Xuất quy tắc ứng dụng" + +#: settings-gui/ui/pages/mode_manager.py:784 +#: settings-gui/ui/pages/dict_editor.py:414 +#: settings-gui/ui/pages/keymap_editor.py:621 +#: settings-gui/ui/pages/macro_editor.py:548 +msgid "Export Complete" +msgstr "Xuất hoàn tất" + +#: settings-gui/ui/pages/mode_manager.py:785 +#, python-brace-format +msgid "" +"Exported {} rules to:\n" +"{}" +msgstr "" +"Xuất {} quy tắc vào:\n" +"{}" + +#: settings-gui/ui/pages/mode_manager.py:788 +msgid "Cannot open file for writing:" +msgstr "Không thể mở file để ghi:" + +#: settings-gui/ui/pages/dynamic_settings.py:117 +msgid "Failed to load configuration." +msgstr "Tải cấu hình thất bại." + +#: settings-gui/ui/pages/dynamic_settings.py:165 +msgid "No interface settings available yet." +msgstr "Không có cài đặt nào hiện khả dụng." + #: settings-gui/ui/pages/backup.py:52 msgid "Backup & Restore" msgstr "Sao lưu & Khôi phục" @@ -224,11 +450,10 @@ msgstr "Xuất/Nhập cài đặt" #: settings-gui/ui/pages/backup.py:61 msgid "" -"Save your configurations to a JSON file, or restore them from a .lotusbak " -"file. Select what you want to include:" +"Save or restore your configurations via JSON files. Select the components " +"you wish to include:" msgstr "" -"Lưu cấu hình của bạn vào một tệp JSON, hoặc khôi phục từ một tệp .lotusbak. " -"Chọn cái bạn muốn:" +"Lưu hoặc khôi phục cấu hình của bạn vào một tệp JSON. Chọn cái bạn muốn:" #: settings-gui/ui/pages/backup.py:70 settings-gui/ui/pages/backup.py:216 msgid "Main Settings" @@ -276,31 +501,13 @@ msgid "Export Backup" msgstr "Xuất sao lưu" #: settings-gui/ui/pages/backup.py:143 settings-gui/ui/pages/backup.py:197 -msgid "Lotus Backup (*.lotusbak);;All Files (*)" -msgstr "Sao lưu lotus (*.lotusbak);;Tất cả tệp(*)" - -#: settings-gui/ui/pages/backup.py:183 settings-gui/ui/pages/backup.py:296 -#: settings-gui/ui/pages/about.py:206 settings-gui/ui/main_window.py:252 -msgid "Success" -msgstr "Thành công" +msgid "JSON Backup (*.json);;All Files (*)" +msgstr "Sao lưu JSON(*.json);;Tất cả tệp(*)" #: settings-gui/ui/pages/backup.py:183 msgid "Backup exported successfully to:\n" msgstr "Sao lưu được xuất thành công vào:\n" -#: settings-gui/ui/pages/backup.py:188 settings-gui/ui/pages/backup.py:242 -#: settings-gui/ui/pages/backup.py:314 settings-gui/ui/pages/dict_editor.py:247 -#: settings-gui/ui/pages/dict_editor.py:356 -#: settings-gui/ui/pages/dict_editor.py:419 -#: settings-gui/ui/pages/keymap_editor.py:500 -#: settings-gui/ui/pages/keymap_editor.py:588 -#: settings-gui/ui/pages/macro_editor.py:476 -#: settings-gui/ui/pages/macro_editor.py:546 -#: settings-gui/ui/pages/mode_manager.py:705 -#: settings-gui/ui/pages/mode_manager.py:788 settings-gui/ui/pages/about.py:208 -msgid "Error" -msgstr "Lỗi" - #: settings-gui/ui/pages/backup.py:188 msgid "Failed to export backup:\n" msgstr "Xuất sao lưu thất bại\n" @@ -369,10 +576,11 @@ msgid "Word (e.g. khongdau)" msgstr "Từ (ví dụ: khongdau)" #: settings-gui/ui/pages/dict_editor.py:109 -#: settings-gui/ui/pages/dict_editor.py:320 +#: settings-gui/ui/pages/dict_editor.py:319 #: settings-gui/ui/pages/keymap_editor.py:301 +#: settings-gui/ui/pages/keymap_editor.py:474 #: settings-gui/ui/pages/macro_editor.py:171 -#: settings-gui/ui/pages/macro_editor.py:452 +#: settings-gui/ui/pages/macro_editor.py:458 msgid "Add" msgstr "Thêm" @@ -380,98 +588,55 @@ msgstr "Thêm" msgid "Word:" msgstr "Từ:" -#: settings-gui/ui/pages/dict_editor.py:135 -#: settings-gui/ui/pages/keymap_editor.py:328 -#: settings-gui/ui/pages/macro_editor.py:201 -#: settings-gui/ui/pages/mode_manager.py:375 -msgid "Remove" -msgstr "Xoá" - #: settings-gui/ui/pages/dict_editor.py:247 #, python-brace-format msgid "Failed to save dictionary: {}" msgstr "Lưu từ điển thất bại: {}" #: settings-gui/ui/pages/dict_editor.py:274 -#: settings-gui/ui/pages/dict_editor.py:307 +#: settings-gui/ui/pages/dict_editor.py:306 msgid "Warning: Dictionary words should not contain spaces." msgstr "Cảnh báo: Từ trong từ điển không chứa dấu cách." -#: settings-gui/ui/pages/dict_editor.py:316 +#: settings-gui/ui/pages/dict_editor.py:315 msgid "Exists" msgstr "Đã tồn tại" -#: settings-gui/ui/pages/dict_editor.py:346 +#: settings-gui/ui/pages/dict_editor.py:345 msgid "Import Custom Dictionary" msgstr "Nhập từ điển tuỳ chỉnh" -#: settings-gui/ui/pages/dict_editor.py:348 +#: settings-gui/ui/pages/dict_editor.py:347 msgid "Dictionary files (*.tsv *.txt);;All files (*)" msgstr "Tệp từ điển (*.tsv *.txt);; Tất cả tệp (*)" -#: settings-gui/ui/pages/dict_editor.py:356 -#: settings-gui/ui/pages/keymap_editor.py:500 -#: settings-gui/ui/pages/macro_editor.py:476 +#: settings-gui/ui/pages/dict_editor.py:355 +#: settings-gui/ui/pages/keymap_editor.py:537 +#: settings-gui/ui/pages/macro_editor.py:482 #, python-brace-format msgid "Cannot open file for reading: {}" msgstr "Không thể mở tệp để đọc: {}" -#: settings-gui/ui/pages/dict_editor.py:369 -#: settings-gui/ui/pages/keymap_editor.py:520 -#: settings-gui/ui/pages/macro_editor.py:497 -#: settings-gui/ui/pages/mode_manager.py:735 -msgid "Confirm Import" -msgstr "Xác nhận nhập" - -#: settings-gui/ui/pages/dict_editor.py:371 +#: settings-gui/ui/pages/dict_editor.py:370 msgid "" "The current dictionary is not empty. Imported entries will be merged. " "Continue?" msgstr "Từ điển hiện tại không rỗng. Các mục nhập sẽ được gộp. Tiếp tục?" #: settings-gui/ui/pages/dict_editor.py:390 -#: settings-gui/ui/pages/keymap_editor.py:551 -#: settings-gui/ui/pages/macro_editor.py:514 -#: settings-gui/ui/pages/mode_manager.py:754 -msgid "Import Complete" -msgstr "Nhập thành công" - -#: settings-gui/ui/pages/dict_editor.py:391 #, python-brace-format msgid "Imported {} words." msgstr "Nhập {} từ." -#: settings-gui/ui/pages/dict_editor.py:397 -#: settings-gui/ui/pages/keymap_editor.py:559 -#: settings-gui/ui/pages/macro_editor.py:521 -#: settings-gui/ui/pages/mode_manager.py:762 -msgid "Export" -msgstr "Xuất" - -#: settings-gui/ui/pages/dict_editor.py:397 +#: settings-gui/ui/pages/dict_editor.py:396 msgid "The custom dictionary is empty, nothing to export." msgstr "Từ điển tuỳ chỉnh đang rỗng, không có gì để xuất." -#: settings-gui/ui/pages/dict_editor.py:402 +#: settings-gui/ui/pages/dict_editor.py:401 msgid "Export Custom Dictionary" msgstr "Xuất từ điển tuỳ chỉnh" -#: settings-gui/ui/pages/dict_editor.py:404 -#: settings-gui/ui/pages/keymap_editor.py:567 -#: settings-gui/ui/pages/macro_editor.py:528 -#: settings-gui/ui/pages/mode_manager.py:696 -#: settings-gui/ui/pages/mode_manager.py:770 -msgid "Tab-separated (*.tsv);;Text files (*.txt);;All files (*)" -msgstr "Phân cách tab (*.tsv);; Tệp văn bản (*txt);; Tất cả tệp (*)" - #: settings-gui/ui/pages/dict_editor.py:415 -#: settings-gui/ui/pages/keymap_editor.py:584 -#: settings-gui/ui/pages/macro_editor.py:542 -#: settings-gui/ui/pages/mode_manager.py:784 -msgid "Export Complete" -msgstr "Xuất hoàn tất" - -#: settings-gui/ui/pages/dict_editor.py:416 #, python-brace-format msgid "" "Exported {} words to:\n" @@ -480,21 +645,13 @@ msgstr "" "Xuất {} từ vào:\n" "{}" -#: settings-gui/ui/pages/dict_editor.py:419 -#: settings-gui/ui/pages/keymap_editor.py:588 -#: settings-gui/ui/pages/macro_editor.py:546 +#: settings-gui/ui/pages/dict_editor.py:418 +#: settings-gui/ui/pages/keymap_editor.py:625 +#: settings-gui/ui/pages/macro_editor.py:552 #, python-brace-format msgid "Cannot open file for writing: {}" msgstr "Không thể mở file để ghi: {}" -#: settings-gui/ui/pages/dynamic_settings.py:117 -msgid "Failed to load configuration." -msgstr "Tải cấu hình thất bại." - -#: settings-gui/ui/pages/dynamic_settings.py:165 -msgid "No interface settings available yet." -msgstr "Không có cài đặt nào hiện khả dụng." - #: settings-gui/ui/pages/keymap_editor.py:242 #: settings-gui/ui/main_window.py:185 msgid "Keymap" @@ -517,58 +674,68 @@ msgid "Key (Example: s)" msgstr "Phím (Ví dụ: s)" #: settings-gui/ui/pages/keymap_editor.py:302 -msgid "Add / Update Keymap" -msgstr "Thêm / Cập Nhật Keymap" +#: settings-gui/ui/pages/keymap_editor.py:475 +msgid "Add Keymap" +msgstr "Thêm Keymap" -#: settings-gui/ui/pages/keymap_editor.py:312 +#: settings-gui/ui/pages/keymap_editor.py:313 msgid "Action" msgstr "Hành động" -#: settings-gui/ui/pages/keymap_editor.py:329 +#: settings-gui/ui/pages/keymap_editor.py:330 msgid "Remove selected row" msgstr "Xoá dòng đang chọn" -#: settings-gui/ui/pages/keymap_editor.py:445 +#: settings-gui/ui/pages/keymap_editor.py:470 +#: settings-gui/ui/pages/macro_editor.py:455 +msgid "Update" +msgstr "Cập nhật" + +#: settings-gui/ui/pages/keymap_editor.py:471 +msgid "Update Keymap" +msgstr "Cập Nhật Keymap" + +#: settings-gui/ui/pages/keymap_editor.py:482 msgid "Confirm" msgstr "Xác nhận" -#: settings-gui/ui/pages/keymap_editor.py:446 +#: settings-gui/ui/pages/keymap_editor.py:483 msgid "This operation will replace all existing keys with " msgstr "Việc này sẽ thay thế mọi phím sẵn có với " -#: settings-gui/ui/pages/keymap_editor.py:448 +#: settings-gui/ui/pages/keymap_editor.py:485 msgid ". Are you sure?" msgstr ". Bạn có chắc chắn?" -#: settings-gui/ui/pages/keymap_editor.py:489 +#: settings-gui/ui/pages/keymap_editor.py:526 msgid "Import Keymap" msgstr "Nhập Keymap" -#: settings-gui/ui/pages/keymap_editor.py:491 -#: settings-gui/ui/pages/macro_editor.py:468 +#: settings-gui/ui/pages/keymap_editor.py:528 +#: settings-gui/ui/pages/macro_editor.py:474 msgid "Tab-separated (*.tsv *.txt);;All files (*)" msgstr "Phân cách tab (*.tsv *.txt);; Tất cả tệp (*)" -#: settings-gui/ui/pages/keymap_editor.py:521 +#: settings-gui/ui/pages/keymap_editor.py:558 msgid "Merge imported entries?" msgstr "Gộp các mục đã nhập?" -#: settings-gui/ui/pages/keymap_editor.py:552 -#: settings-gui/ui/pages/macro_editor.py:515 +#: settings-gui/ui/pages/keymap_editor.py:589 +#: settings-gui/ui/pages/macro_editor.py:521 #, python-brace-format msgid "Imported {} entries, skipped {} invalid lines." msgstr "Nhập {} mục, bỏ qua {} dòng không hợp lệ." -#: settings-gui/ui/pages/keymap_editor.py:559 +#: settings-gui/ui/pages/keymap_editor.py:596 msgid "The keymap list is empty, nothing to export." msgstr "Danh sách keymap trống, không có gì để xuất." -#: settings-gui/ui/pages/keymap_editor.py:565 +#: settings-gui/ui/pages/keymap_editor.py:602 msgid "Export Keymap" msgstr "Xuất Keymap" -#: settings-gui/ui/pages/keymap_editor.py:585 -#: settings-gui/ui/pages/macro_editor.py:543 +#: settings-gui/ui/pages/keymap_editor.py:622 +#: settings-gui/ui/pages/macro_editor.py:549 #, python-brace-format msgid "" "Exported {} entries to:\n" @@ -647,187 +814,30 @@ msgstr "Chuyển lên" msgid "Move Down" msgstr "Chuyển xuống" -#: settings-gui/ui/pages/macro_editor.py:366 -#: settings-gui/ui/pages/macro_editor.py:435 +#: settings-gui/ui/pages/macro_editor.py:376 +#: settings-gui/ui/pages/macro_editor.py:444 msgid "Warning: Macro key should not contain spaces or special characters." msgstr "Cảnh báo: từ gõ tắt không được chứa phím cách hoặc ký tự đặc biệt." -#: settings-gui/ui/pages/macro_editor.py:449 -msgid "Update" -msgstr "Cập nhật" - -#: settings-gui/ui/pages/macro_editor.py:466 +#: settings-gui/ui/pages/macro_editor.py:472 msgid "Import Macros" msgstr "Nhập bảng gõ tắt" -#: settings-gui/ui/pages/macro_editor.py:499 +#: settings-gui/ui/pages/macro_editor.py:505 msgid "" "The current macro list is not empty. Imported entries will be merged. " "Continue?" msgstr "" "Danh sách gõ tắt hiện tại không rỗng. Các mục nhập sẽ được gộp. Tiếp tục?" -#: settings-gui/ui/pages/macro_editor.py:521 +#: settings-gui/ui/pages/macro_editor.py:527 msgid "The macro list is empty, nothing to export." msgstr "Danh sách gõ tắt đang rỗng, không có gì để xuất." -#: settings-gui/ui/pages/macro_editor.py:526 +#: settings-gui/ui/pages/macro_editor.py:532 msgid "Export Macros" msgstr "Xuất bảng gõ tắt" -#: settings-gui/ui/pages/mode_manager.py:118 -#: settings-gui/ui/pages/mode_manager.py:131 -#: settings-gui/ui/pages/mode_manager.py:374 -msgid "Add Application" -msgstr "Thêm ứng dụng" - -#: settings-gui/ui/pages/mode_manager.py:133 -msgid "Assign a specific input mode to an application" -msgstr "Gán một chế độ gõ cụ thể cho một ứng dụng" - -#: settings-gui/ui/pages/mode_manager.py:148 -msgid "Search process name..." -msgstr "Tìm tên tiến trình..." - -#: settings-gui/ui/pages/mode_manager.py:152 -msgid "Refresh Process List" -msgstr "Làm mới danh sách tiến trình" - -#: settings-gui/ui/pages/mode_manager.py:166 -msgid "Running" -msgstr "Đang chạy" - -#: settings-gui/ui/pages/mode_manager.py:172 -msgid "Enter application name or path..." -msgstr "Nhập tên ứng dụng hoặc đường dẫn..." - -#: settings-gui/ui/pages/mode_manager.py:176 -msgid "Manual input" -msgstr "Nhập thủ công" - -#: settings-gui/ui/pages/mode_manager.py:180 -msgid "No application selected" -msgstr "Không có ứng dụng được chọn" - -#: settings-gui/ui/pages/mode_manager.py:183 settings-gui/ui/main_window.py:140 -msgid "&Cancel" -msgstr "&Huỷ" - -#: settings-gui/ui/pages/mode_manager.py:186 -msgid "&Add" -msgstr "&Thêm" - -#: settings-gui/ui/pages/mode_manager.py:322 -msgid "Selected:" -msgstr "Được chọn:" - -#: settings-gui/ui/pages/mode_manager.py:363 -msgid "Search applications..." -msgstr "Tìm ứng dụng..." - -#: settings-gui/ui/pages/mode_manager.py:396 settings-gui/ui/main_window.py:170 -msgid "Applications" -msgstr "Ứng dụng" - -#: settings-gui/ui/pages/mode_manager.py:403 -msgid "Global Default Mode:" -msgstr "Chế độ toàn cục mặc định:" - -#: settings-gui/ui/pages/mode_manager.py:425 -msgid "Select an application" -msgstr "Tìm một ứng dụng" - -#: settings-gui/ui/pages/mode_manager.py:655 -msgid "Confirm Remove" -msgstr "Xác nhận xoá" - -#: settings-gui/ui/pages/mode_manager.py:656 -msgid "Are you sure you want to remove rules for this application?" -msgstr "Bạn có chắc chắn muốn xoá quy tắc cho ứng dụng này?" - -#: settings-gui/ui/pages/mode_manager.py:694 -msgid "Import Application Rules" -msgstr "Nhập quy tắc ứng dụng" - -#: settings-gui/ui/pages/mode_manager.py:705 -msgid "Cannot open file for reading:" -msgstr "Không thể mở tệp để đọc:" - -#: settings-gui/ui/pages/mode_manager.py:736 -msgid "Merge imported application rules?" -msgstr "Gộp các quy tắc ứng dụng đã nhập?" - -#: settings-gui/ui/pages/mode_manager.py:755 -#, python-brace-format -msgid "Imported {} rules, skipped {} invalid lines." -msgstr "Nhập {} quy tắc, bỏ qua {} dòng không hợp lệ." - -#: settings-gui/ui/pages/mode_manager.py:762 -msgid "The application rules list is empty, nothing to export." -msgstr "Danh sách quy tắc ứng dụng đang rỗng, không có gì để xuất." - -#: settings-gui/ui/pages/mode_manager.py:768 -msgid "Export Application Rules" -msgstr "Xuất quy tắc ứng dụng" - -#: settings-gui/ui/pages/mode_manager.py:785 -#, python-brace-format -msgid "" -"Exported {} rules to:\n" -"{}" -msgstr "" -"Xuất {} quy tắc vào:\n" -"{}" - -#: settings-gui/ui/pages/mode_manager.py:788 -msgid "Cannot open file for writing:" -msgstr "Không thể mở file để ghi:" - -#: settings-gui/ui/pages/about.py:64 -#, python-brace-format -msgid "Version {}" -msgstr "Phiên bản {}" - -#: settings-gui/ui/pages/about.py:79 -msgid "" -"A state-of-the-art Vietnamese input method engine for Linux, designed for " -"speed, stability, and a premium user experience." -msgstr "" -"Một bộ gõ tiếng Việt cho Linux, thiết kế cho tốc độ, ổn định và trải nghiệm " -"cao cấp." - -#: settings-gui/ui/pages/about.py:96 -msgid "Report Bug" -msgstr "Báo cáo lỗi" - -#: settings-gui/ui/pages/about.py:101 -msgid "Request Feature" -msgstr "Đề xuất tính năng" - -#: settings-gui/ui/pages/about.py:111 -msgid "Export Debug Logs" -msgstr "Xuất nhật ký gỡ lỗi" - -#: settings-gui/ui/pages/about.py:123 -msgid "DEVELOPED BY" -msgstr "PHÁT TRIỂN BỞI" - -#: settings-gui/ui/pages/about.py:157 -msgid "Licensed under the GNU General Public License v3.0" -msgstr "Được cấp phép dưới Giấy phép Công cộng GNU v3.0" - -#: settings-gui/ui/pages/about.py:167 -msgid "Save Debug Log" -msgstr "Lưu nhật ký gỡ lỗi" - -#: settings-gui/ui/pages/about.py:206 -msgid "Debug logs exported successfully to:\n" -msgstr "Nhật ký gỡ lỗi được xuất thành công vào:\n" - -#: settings-gui/ui/pages/about.py:208 -msgid "Failed to export logs:\n" -msgstr "Xuất nhật ký gỡ lỗi thất bại\n" - #: settings-gui/ui/components.py:46 settings-gui/ui/components.py:56 msgid "None" msgstr "Rỗng" From 19f776ffc473f79553537eed843e7749b5da614e Mon Sep 17 00:00:00 2001 From: Nguyen Hoang Ky Date: Mon, 30 Mar 2026 22:32:41 +0700 Subject: [PATCH 12/12] use getpwuid_r for thread-safe --- server/lotus-server.cpp | 17 +++++++++++++++-- src/lotus-utils.cpp | 20 ++++++++++++++++---- 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/server/lotus-server.cpp b/server/lotus-server.cpp index df51ea57..5f9ad664 100644 --- a/server/lotus-server.cpp +++ b/server/lotus-server.cpp @@ -110,8 +110,21 @@ void signal_handler(int sig) { } std::string get_current_username() { - struct passwd* pw = getpwuid(getuid()); - return (pw != nullptr) ? pw->pw_name : "unknown"; + struct passwd pwd{}; + struct passwd* result = nullptr; + long buf_size = sysconf(_SC_GETPW_R_SIZE_MAX); + if (buf_size == -1) { + buf_size = 16384; + } + std::vector buf(buf_size); + std::string username; + int res = getpwuid_r(getuid(), &pwd, buf.data(), buf_size, &result); + if (res == 0 && result != nullptr) { + username = result->pw_name; + } else { + username = "unknown"; + } + return username; } uid_t get_uid_for_user(const std::string& username) { diff --git a/src/lotus-utils.cpp b/src/lotus-utils.cpp index 1c5ccc5f..4beb8fd2 100644 --- a/src/lotus-utils.cpp +++ b/src/lotus-utils.cpp @@ -34,12 +34,24 @@ std::condition_variable monitor_cv; FCITX_DEFINE_LOG_CATEGORY(lotus, "lotus", fcitx::LogLevel::NoLog); std::string buildSocketPath(const char* base_path_suffix) { - struct passwd* pw = getpwuid(getuid()); - const char* username_c = (pw != nullptr) ? pw->pw_name : nullptr; - std::string path; + struct passwd pwd{}; + struct passwd* result = nullptr; + long buf_size = sysconf(_SC_GETPW_R_SIZE_MAX); + if (buf_size == -1) { + buf_size = 16384; + } + std::vector buf(buf_size); + std::string username; + int res = getpwuid_r(getuid(), &pwd, buf.data(), buf_size, &result); + if (res == 0 && result != nullptr) { + username = result->pw_name; + } else { + username = "unknown"; + } + std::string path; path.reserve(32); path += "lotussocket-"; - path += ((username_c != nullptr) ? username_c : "unknown"); + path += username; path += '-'; path += base_path_suffix; const size_t max_socket_path_length = UNIX_PATH_MAX - 1;