diff --git a/.all-contributorsrc b/.all-contributorsrc index 1e83cf6a..cc52b970 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -92,6 +92,15 @@ "contributions": [ "platform" ] + }, + { + "login": "hieutran21198", + "name": "Trần Minh Hiếu (Cirius)", + "avatar_url": "https://avatars.githubusercontent.com/u/87953912?v=4", + "profile": "https://github.com/hieutran21198", + "contributions": [ + "platform" + ] } ], "contributorsPerLine": 4, diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index 56a8572c..133319d4 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -16,7 +16,7 @@ jobs: name: Check 📋 runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Install Nix uses: cachix/install-nix-action@v31 @@ -84,7 +84,7 @@ jobs: - name: Upload build log if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: build-log path: build.log diff --git a/.gitignore b/.gitignore index a0d42df3..326c820f 100644 --- a/.gitignore +++ b/.gitignore @@ -38,4 +38,4 @@ refresh-contributor lotus-version.h # Logs *.log -__pycache__/ \ No newline at end of file +__pycache__/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 0e3f633b..1ae36231 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.10) -project(fcitx5-lotus VERSION 3.0.3) +project(fcitx5-lotus VERSION 3.1.0) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(REQUIRED_FCITX_VERSION 5.0.5) diff --git a/README.en.md b/README.en.md index 620941aa..dfb1db4b 100644 --- a/README.en.md +++ b/README.en.md @@ -124,6 +124,9 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d Ngo Phu Hien
Ngo Phu Hien

💻 Minh Tran
Minh Tran

📦 + + Trần Minh Hiếu (Cirius)
Trần Minh Hiếu (Cirius)

📦 + diff --git a/README.md b/README.md index b90d0ef3..5c714139 100644 --- a/README.md +++ b/README.md @@ -116,6 +116,9 @@ Cảm ơn những con người tuyệt vời ([chú thích emoji](https://allcon Ngo Phu Hien
Ngo Phu Hien

💻 Minh Tran
Minh Tran

📦 + + Trần Minh Hiếu (Cirius)
Trần Minh Hiếu (Cirius)

📦 + diff --git a/bamboo/bamboo-c.go b/bamboo/bamboo-c.go index 50b6e96b..6d163c87 100644 --- a/bamboo/bamboo-c.go +++ b/bamboo/bamboo-c.go @@ -21,6 +21,7 @@ import ( bool modernStyle; bool freeMarking; int w2u; + int bracketTransform; const char *timeFormat; const char *dateFormat; } FcitxBambooEngineOption; @@ -122,8 +123,15 @@ func EngineSetOption(engine uintptr, option *C.FcitxBambooEngineOption) { } else { flags &= ^bamboo.Ew2uEnabled } + + if int(option.bracketTransform) != 0 { + flags |= bamboo.EbracketTransformEnabled + } else { + flags &= ^bamboo.EbracketTransformEnabled + } bambooEngine.preeditor.SetFlag(flags) bambooEngine.preeditor.SetW2UMode(int(option.w2u)) + bambooEngine.preeditor.SetBracketTransformMode(int(option.bracketTransform)) bambooEngine.timeFormat = C.GoString(option.timeFormat) bambooEngine.dateFormat = C.GoString(option.dateFormat) } @@ -270,12 +278,11 @@ func GetInputMethodNames() **C.char { order := map[string]int{ "Telex": 0, "VNI": 1, - "Telex 2": 2, - "Telex + VNI": 3, - "Telex + VNI + VIQR": 4, - "VIQR": 5, - "Microsoft layout": 6, - "VNI Bàn phím tiếng Pháp": 7, + "Telex + VNI": 2, + "Telex + VNI + VIQR": 3, + "VIQR": 4, + "Microsoft layout": 5, + "VNI Bàn phím tiếng Pháp": 6, } names := make([]string, len(bamboo.InputMethodDefinitions)) i := 0 diff --git a/bamboo/bamboo-core b/bamboo/bamboo-core index 86aa4252..9197d2cc 160000 --- a/bamboo/bamboo-core +++ b/bamboo/bamboo-core @@ -1 +1 @@ -Subproject commit 86aa4252fb66935ad28dd97a7d65660329fb4e8f +Subproject commit 9197d2cc164380a80d219f12cc90576ff2fbf5e6 diff --git a/bamboo/fcitxbambooengine b/bamboo/fcitxbambooengine new file mode 100755 index 00000000..965f431b Binary files /dev/null and b/bamboo/fcitxbambooengine differ diff --git a/nix/modules/nixos/fcitx5-lotus/default.nix b/nix/modules/nixos/fcitx5-lotus/default.nix index 8085421c..1470a2df 100644 --- a/nix/modules/nixos/fcitx5-lotus/default.nix +++ b/nix/modules/nixos/fcitx5-lotus/default.nix @@ -5,30 +5,88 @@ pkgs, ... }: -with lib; let +with lib; +let cfg = config.services.fcitx5-lotus; fcitx5-lotus = inputs.self.packages.${pkgs.stdenv.hostPlatform.system}.fcitx5-lotus; -in { + + legacyUsers = optional (cfg.user != null && cfg.user != "") cfg.user; + effectiveUsers = unique (legacyUsers ++ cfg.users); + + syntacticallyInvalidUsers = filter ( + user: user == "" || (builtins.match "[A-Za-z_][A-Za-z0-9_-]*" user) == null + ) effectiveUsers; + + unknownUsers = filter (user: !(builtins.hasAttr user config.users.users)) effectiveUsers; +in +{ options.services.fcitx5-lotus = { - enable = mkEnableOption "Fcitx5 Lotus Server"; + enable = mkEnableOption "Fcitx5 Lotus integration"; + + package = mkOption { + type = types.package; + default = fcitx5-lotus; + defaultText = literalExpression "inputs.self.packages.\${pkgs.stdenv.hostPlatform.system}.fcitx5-lotus"; + description = "The fcitx5-lotus package to install."; + }; + + # Backward compatible with the old module API, but no longer defaults to "". user = mkOption { - type = types.str; - default = ""; - description = "User name of the Server"; + type = types.nullOr types.str; + default = null; + example = "alice"; + description = '' + Backward-compatible single user to start the Lotus server for. + + Prefer `services.fcitx5-lotus.users` for new configurations. + ''; + }; + + users = mkOption { + type = types.listOf types.str; + default = [ ]; + example = [ + "alice" + "bob" + ]; + description = '' + Linux users to start system-level fcitx5-lotus-server instances for. + + Each configured user gets one fcitx5-lotus-server@.service instance. + ''; }; }; config = mkIf cfg.enable { - i18n.inputMethod.fcitx5.addons = [fcitx5-lotus]; + assertions = [ + { + assertion = cfg.user != ""; + message = "services.fcitx5-lotus.user must not be an empty string; use null or services.fcitx5-lotus.users."; + } + { + assertion = effectiveUsers != [ ]; + message = "services.fcitx5-lotus requires at least one user. Set services.fcitx5-lotus.users = [ \"alice\" ];"; + } + { + assertion = syntacticallyInvalidUsers == [ ]; + message = "services.fcitx5-lotus.users/user contains invalid Linux usernames."; + } + { + assertion = unknownUsers == [ ]; + message = "services.fcitx5-lotus.users/user must contain users declared in users.users."; + } + ]; + + i18n.inputMethod.fcitx5.addons = [ cfg.package ]; users.users.uinput_proxy = { isSystemUser = true; group = "input"; }; - services.udev.packages = [fcitx5-lotus]; - systemd.packages = [fcitx5-lotus]; + services.udev.packages = [ cfg.package ]; + systemd.packages = [ cfg.package ]; - systemd.targets.multi-user.wants = ["fcitx5-lotus-server@${cfg.user}.service"]; + systemd.targets.multi-user.wants = map (user: "fcitx5-lotus-server@${user}.service") effectiveUsers; }; } diff --git a/packaging/debian/changelog b/packaging/debian/changelog index 3e19877b..33cefc3c 100644 --- a/packaging/debian/changelog +++ b/packaging/debian/changelog @@ -1,5 +1,7 @@ -fcitx5-lotus (3.0.3-1) unstable; urgency=medium +fcitx5-lotus (3.1.0-1) unstable; urgency=medium - * Small fix + * Add option to type em-dash from double hyphen + * Add bracket transform option + * Fix some bug - -- Nguyen Hoang Ky Sun, 03 May 2026 07:43:00 +0700 \ No newline at end of file + -- Nguyen Hoang Ky Sun, 14 May 2026 16:15: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 9ed04ff0..612cd060 100644 --- a/packaging/rpm/fedora/fcitx5-lotus.spec +++ b/packaging/rpm/fedora/fcitx5-lotus.spec @@ -1,5 +1,5 @@ Name: fcitx5-lotus -Version: 3.0.3 +Version: 3.1.0 Release: 1 Summary: Vietnamese input method for fcitx5 License: GPL-3.0-or-later @@ -165,5 +165,7 @@ fi %systemd_postun_with_restart fcitx5-lotus-server@.service %changelog -* Sun May 03 2026 Nguyen Hoang Ky - 3.0.3-1 -- Small fix +* Sun May 14 2026 Nguyen Hoang Ky - 3.1.0-1 +- Add option to type em-dash from double hyphen +- Add bracket transform option +- Fix some bug diff --git a/packaging/rpm/opensuse/fcitx5-lotus.spec b/packaging/rpm/opensuse/fcitx5-lotus.spec index 4b9925ac..b9575326 100644 --- a/packaging/rpm/opensuse/fcitx5-lotus.spec +++ b/packaging/rpm/opensuse/fcitx5-lotus.spec @@ -1,5 +1,5 @@ Name: fcitx5-lotus -Version: 3.0.3 +Version: 3.1.0 Release: 1 Summary: Vietnamese input method for fcitx5 License: GPL-3.0-or-later @@ -168,5 +168,7 @@ fi %systemd_postun_with_restart fcitx5-lotus-server@.service %changelog -* Sun May 02 2026 Nguyen Hoang Ky - 3.0.3-1 -- Small fix +* Sun May 14 2026 Nguyen Hoang Ky - 3.1.0-1 +- Add option to type em-dash from double hyphen +- Add bracket transform option +- Fix some bug diff --git a/settings-gui/ui/pages/dict_editor.py b/settings-gui/ui/pages/dict_editor.py index 4cc57045..cd975252 100644 --- a/settings-gui/ui/pages/dict_editor.py +++ b/settings-gui/ui/pages/dict_editor.py @@ -76,7 +76,7 @@ def _setup_ui(self): # Dictionary behavior toggles toggles_card = CardWidget("") toggles_layout = QHBoxLayout() - self.cb_enable = QCheckBox(_("Enable Custom Dictionary")) + self.cb_enable = QCheckBox(_("Custom Dictionary")) self.cb_enable.toggled.connect(self._on_item_changed) toggles_layout.addWidget(self.cb_enable) toggles_layout.addStretch() diff --git a/settings-gui/ui/pages/dynamic_settings.py b/settings-gui/ui/pages/dynamic_settings.py index e1ef3135..a8fbe8b9 100644 --- a/settings-gui/ui/pages/dynamic_settings.py +++ b/settings-gui/ui/pages/dynamic_settings.py @@ -51,7 +51,7 @@ class SettingsCategory(Enum): }, SettingsCategory.TYPING: { "SPELLING & CORRECTIONS": ["SpellCheck", "AutoNonVnRestore", "DdFreeStyle"], - "TYPING OPTIONS": ["W2U", "ModernStyle", "FreeMarking", "FixUinputWithAck", "DoubleSpaceToPeriod", "AutoCapitalizeAfterPunctuation"], + "TYPING OPTIONS": ["W2U", "BracketTransform", "ModernStyle", "FreeMarking", "FixUinputWithAck", "DoubleSpaceToPeriod", "DoubleHyphenToEmDash", "AutoCapitalizeAfterPunctuation"], }, SettingsCategory.SHORTCUTS: { "SHORTCUTS": ["ModeMenuKey"], diff --git a/settings-gui/ui/pages/keymap_editor.py b/settings-gui/ui/pages/keymap_editor.py index e7b253dc..31ddfd94 100644 --- a/settings-gui/ui/pages/keymap_editor.py +++ b/settings-gui/ui/pages/keymap_editor.py @@ -118,27 +118,6 @@ ("$", "_Ô"), ("0", "__đ"), (")", "_Đ"), - ("[", "__ư"), - ("{", "_Ư"), - ("]", "__ơ"), - ("}", "_Ơ"), - ], - "Telex 2": [ - ("z", "XoaDauThanh"), - ("s", "DauSac"), - ("f", "DauHuyen"), - ("r", "DauHoi"), - ("x", "DauNga"), - ("j", "DauNang"), - ("a", "A_Â"), - ("e", "E_Ê"), - ("o", "O_Ô"), - ("w", "UOA_ƯƠĂ__Ư"), - ("d", "D_Đ"), - ("]", "__ư"), - ("[", "__ơ"), - ("}", "_Ư"), - ("{", "_Ơ"), ], "Telex + VNI": [ ("z", "XoaDauThanh"), @@ -249,7 +228,7 @@ def _setup_ui(self): # Row 1: Enable checkbox and Search top_row = QHBoxLayout() - self.cb_enable = QCheckBox(_("Enable Custom Keymap")) + self.cb_enable = QCheckBox(_("Custom Keymap")) self.cb_enable.toggled.connect(self._on_item_changed) top_row.addWidget(self.cb_enable) top_row.addStretch() diff --git a/settings-gui/ui/pages/macro_editor.py b/settings-gui/ui/pages/macro_editor.py index 1ea7830a..7ffc44ca 100644 --- a/settings-gui/ui/pages/macro_editor.py +++ b/settings-gui/ui/pages/macro_editor.py @@ -57,7 +57,7 @@ def _setup_ui(self): # Macro behavior toggles toggles_card = CardWidget("") toggles_layout = QHBoxLayout() - self.cb_enable = QCheckBox(_("Enable Macro")) + self.cb_enable = QCheckBox(_("Macro")) self.cb_capitalize = QCheckBox(_("Capitalize Macro")) self.cb_enable.toggled.connect(self._on_item_changed) self.cb_capitalize.toggled.connect(self._on_item_changed) diff --git a/src/lotus-config.h b/src/lotus-config.h index 26956c02..736dd314 100644 --- a/src/lotus-config.h +++ b/src/lotus-config.h @@ -55,6 +55,17 @@ namespace fcitx { FCITX_CONFIG_ENUM_NAME_WITH_I18N(W2UMode, N_("Disabled"), N_("Non-Start"), N_("Everywhere")); + /** + * @brief Bracket transform mode for [ -> ơ, ] -> ư conversion. + */ + enum class BracketTransformMode : std::uint8_t { + Disabled = 0, + NonStart = 1, + Everywhere = 2, + }; + + FCITX_CONFIG_ENUM_NAME_WITH_I18N(BracketTransformMode, N_("Disabled"), N_("Non-Start"), N_("Everywhere")); + /** * @brief Icon theme options. */ @@ -198,13 +209,16 @@ namespace fcitx { OptionWithAnnotation outputCharset{this, "OutputCharset", _("Output Charset"), "Unicode", {}, {}, StringListAnnotation()}; KeyListOption modeMenuKey{ this, "ModeMenuKey", _("Mode Menu Hotkey"), {Key("grave")}, KeyListConstrain({KeyConstrainFlag::AllowModifierLess, KeyConstrainFlag::AllowModifierOnly})}; - SubConfigOption appRules{this, "AppRules", _("App Rules"), "fcitx://config/addon/lotus/app_rules"}; - OptionWithAnnotation w2u{this, "W2U", _("Type w to Produce ư"), W2UMode::NonStart}; + SubConfigOption appRules{this, "AppRules", _("App Rules"), "fcitx://config/addon/lotus/app_rules"}; + OptionWithAnnotation w2u{this, "W2U", _("Type w to Produce ư"), W2UMode::NonStart}; + OptionWithAnnotation bracketTransform{this, "BracketTransform", _("Type [ -> ơ, ] -> ư, { -> Ơ, } -> Ư"), + BracketTransformMode::Disabled}; Option spellCheck{this, "SpellCheck", _("Enable Spell Check"), true}; Option enableMacro{this, "EnableMacro", _("Enable Macro"), true}; Option capitalizeMacro{this, "CapitalizeMacro", _("Capitalize Macro"), true}; Option autoCapitalizeAfterPunctuation{ this, "AutoCapitalizeAfterPunctuation", _("Auto capitalize after sentence-ending punctuation (. ! ? Enter) (experimental)"), false}; Option doubleSpaceToPeriod{this, "DoubleSpaceToPeriod", _("Double Space to Period (experimental)"), false}; + Option doubleHyphenToEmDash{this, "DoubleHyphenToEmDash", _("Double Hyphen to Em-Dash (--)"), false}; Option autoNonVnRestore{this, "AutoNonVnRestore", _("Auto Restore Invalid Words"), true}; Option modernStyle{this, "ModernStyle", _("Use oà, uý (Instead Of òa, úy)"), true}; Option freeMarking{this, "FreeMarking", _("Allow Type With More Freedom"), true}; @@ -212,8 +226,8 @@ namespace fcitx { Option fixUinputWithAck{this, "FixUinputWithAck", _("Fix Uinput Mode With Ack"), false}; Option useLotusIcons{this, "UseLotusIcons", _("Use Lotus Status Icons"), false}; - Option enableDictionary{this, "EnableDictionary", _("Enable Custom Dictionary"), false}; - Option enableCustomKeymap{this, "EnableCustomKeymap", _("Enable Custom Keymap"), false}; + Option enableDictionary{this, "EnableDictionary", _("Custom Dictionary"), false}; + Option enableCustomKeymap{this, "EnableCustomKeymap", _("Custom Keymap"), false}; Option showModeSmooth{this, "ShowModeSmooth", _("Show Uinput (Smooth)"), true}; Option showModeUinput{this, "ShowModeUinput", _("Show Uinput (Slow)"), true}; Option showModeMinecraft{this, "ShowModeMinecraft", _("Show Minecraft"), true}; diff --git a/src/lotus-engine.cpp b/src/lotus-engine.cpp index 0bf0f5e2..347e827a 100644 --- a/src/lotus-engine.cpp +++ b/src/lotus-engine.cpp @@ -154,7 +154,6 @@ namespace fcitx { 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(); { auto imNames = convertToStringList(GetInputMethodNames()); @@ -193,13 +192,13 @@ namespace fcitx { } config_.outputCharset.annotation().setList(charsets); - initToggleAction(spellCheckAction_, config_.spellCheck, "lotus-spellcheck", "tools-check-spelling", _("Enable Spell Check"), _("Spell Check"), uiManager); - initToggleAction(macroAction_, config_.enableMacro, "lotus-macro", "document-edit", _("Enable Macro"), _("Macro"), uiManager); + initToggleAction(spellCheckAction_, config_.spellCheck, "lotus-spellcheck", "tools-check-spelling", _("Spell Check"), _("Spell Check"), uiManager); + initToggleAction(macroAction_, config_.enableMacro, "lotus-macro", "document-edit", _("Macro"), _("Macro"), uiManager); initToggleAction(capitalizeMacroAction_, config_.capitalizeMacro, "lotus-capitalizemacro", "format-text-uppercase", _("Capitalize Macro"), _("Capitalize Macro"), uiManager); initToggleAction(autoNonVnRestoreAction_, config_.autoNonVnRestore, "lotus-autonvnrestore", "edit-undo", _("Auto Restore Invalid Words"), _("Auto Non-VN Restore"), uiManager); - initToggleAction(enableDictionaryAction_, config_.enableDictionary, "lotus-dictionary", "accessories-dictionary", _("Enable Custom Dictionary"), _("Custom Dictionary"), + initToggleAction(enableDictionaryAction_, config_.enableDictionary, "lotus-dictionary", "accessories-dictionary", _("Custom Dictionary"), _("Custom Dictionary"), uiManager); settingsAction_ = std::make_unique(); @@ -254,7 +253,6 @@ namespace fcitx { LotusEngine::~LotusEngine() { stop_flag_monitor.store(true, std::memory_order_release); - monitor_cv.notify_all(); int fd = mouse_socket_fd.load(std::memory_order_acquire); if (fd >= 0) { shutdown(fd, SHUT_RDWR); @@ -262,9 +260,6 @@ namespace fcitx { if (mouse_thread.joinable()) { mouse_thread.join(); } - if (monitor_thread.joinable()) { - monitor_thread.join(); - } int old_fd = uinput_client_fd_.exchange(-1); if (old_fd != -1) { close(old_fd); @@ -449,7 +444,8 @@ namespace fcitx { } else { ic->inputPanel().reset(); ic->updateUserInterface(UserInterfaceComponent::InputPanel); - ic->updatePreedit(); + if (realMode == LotusMode::Preedit || realMode == LotusMode::SurroundingText) + ic->updatePreedit(); } for (const auto& action : toggleActions_) { statusArea.addAction(StatusGroup::InputMethod, action); @@ -487,15 +483,15 @@ namespace fcitx { } int cursorIndex = menuList->globalCursorIndex(); - if (cursorIndex < 1 || cursorIndex >= totalSize) { - cursorIndex = 1; + if (cursorIndex < 0 || cursorIndex >= totalSize) { + cursorIndex = 0; } int nextIndex = cursorIndex + delta; - if (nextIndex < 1) { + if (nextIndex < 0) { nextIndex = totalSize - 1; } else if (nextIndex >= totalSize) { - nextIndex = 1; + nextIndex = 0; } menuList->setGlobalCursorIndex(nextIndex); @@ -527,8 +523,8 @@ namespace fcitx { case FcitxKey_Return: { if (menuList && !menuList->empty()) { int selectedIndex = menuList->globalCursorIndex(); - if (selectedIndex < 1 || selectedIndex >= menuList->totalSize()) { - selectedIndex = 1; + if (selectedIndex < 0 || selectedIndex >= menuList->totalSize()) { + selectedIndex = 0; } menuList->candidateFromAll(selectedIndex).select(ic); return; @@ -657,7 +653,8 @@ namespace fcitx { needEngineReset.store(false); ic->inputPanel().reset(); ic->updateUserInterface(UserInterfaceComponent::InputPanel); - ic->updatePreedit(); + if (realMode == LotusMode::Preedit || realMode == LotusMode::Emoji || realMode == LotusMode::SurroundingText) + ic->updatePreedit(); } } @@ -870,10 +867,8 @@ namespace fcitx { const LotusMode defaultMode = config_.mode.value(); allModes.push_back({defaultMode, _("Default Typing"), FcitxKey_r, *config_.showModeDefault}); // Add reset option - candidateList->append(std::make_unique(Text(_("App: ") + currentConfigureApp_))); - int activeSelectionIdx = -1; - int currentCandidateIdx = 1; + int currentCandidateIdx = 0; modeMenuMapping_.clear(); @@ -919,12 +914,13 @@ namespace fcitx { if (activeSelectionIdx != -1) { candidateList->setGlobalCursorIndex(activeSelectionIdx); - } else if (candidateList->totalSize() > 1) { - candidateList->setGlobalCursorIndex(1); + } else if (candidateList->totalSize() > 0) { + candidateList->setGlobalCursorIndex(0); } ic->inputPanel().reset(); ic->inputPanel().setCandidateList(std::move(candidateList)); + ic->inputPanel().setAuxDown(Text(_("App: ") + currentConfigureApp_)); ic->updateUserInterface(UserInterfaceComponent::InputPanel); } diff --git a/src/lotus-monitor.cpp b/src/lotus-monitor.cpp index cff00c80..b7cdc341 100644 --- a/src/lotus-monitor.cpp +++ b/src/lotus-monitor.cpp @@ -19,71 +19,9 @@ #include #include -std::thread monitor_thread = std::thread(); -std::thread mouse_thread = std::thread(); +std::thread mouse_thread = std::thread(); -void deletingTimeMonitor() { - LOTUS_INFO("Deleting monitor thread started."); - while (!stop_flag_monitor.load()) { - int64_t deleting_since = 0; - - { - std::unique_lock lock(monitor_mutex); - monitor_cv.wait(lock, [] { return is_deleting_.load(std::memory_order_acquire) || stop_flag_monitor.load(std::memory_order_acquire); }); - } - - if (stop_flag_monitor.load()) - break; - - deleting_since = now_ms(); - - while (is_deleting_.load(std::memory_order_acquire) && !stop_flag_monitor.load()) { - int64_t current_time = now_ms(); - - int64_t rep_start = replacement_start_ms_.load(std::memory_order_acquire); - if (rep_start > 0 && (current_time - rep_start) > 200) { - LOTUS_WARN("Replacement timeout (200ms). Falling back to commit."); - int expected_id = replacement_thread_id_.load(std::memory_order_acquire); - if (expected_id > 0) { - is_deleting_.store(false, std::memory_order_release); - needFallbackCommit.store(true, std::memory_order_release); - replacement_start_ms_.store(0, std::memory_order_release); - break; - } - } - - if ((current_time - deleting_since) >= 1500) { - LOTUS_WARN("Critical delete timeout (1500ms). Forcing engine reset."); - is_deleting_.store(false); - needEngineReset.store(true); - replacement_start_ms_.store(0, std::memory_order_release); - break; - } - - { - std::unique_lock lock(monitor_mutex); - monitor_cv.wait_for(lock, std::chrono::milliseconds(2)); - } - } - } - monitor_running.store(false, std::memory_order_release); - LOTUS_INFO("Deleting monitor thread stopped."); -} - -void startMonitoring() { - if (monitor_running.load()) - return; - if (!monitor_running.exchange(true, std::memory_order_acq_rel)) { - LOTUS_INFO("Initializing monitor threads..."); - if (monitor_thread.joinable()) { - monitor_thread.join(); - } - stop_flag_monitor.store(false, std::memory_order_release); - monitor_thread = std::thread(deletingTimeMonitor); - } -} - -void mousePressResetThread() { +void mousePressResetThread() { const std::string mouse_socket_path = buildSocketPath("mouse_socket"); LOTUS_INFO("Mouse press reset thread started."); diff --git a/src/lotus-monitor.h b/src/lotus-monitor.h index ea6a00bd..5f62da27 100644 --- a/src/lotus-monitor.h +++ b/src/lotus-monitor.h @@ -16,21 +16,8 @@ #include -extern std::thread monitor_thread; extern std::thread mouse_thread; -/** - * @brief Monitors deletion timing to handle race conditions. - * - * Runs in background thread to track deletion operations. - */ -void deletingTimeMonitor(); - -/** - * @brief Starts the monitoring thread. - */ -void startMonitoring(); - /** * @brief Thread function for mouse press detection and reset. * diff --git a/src/lotus-state.cpp b/src/lotus-state.cpp index 703f49fb..449af9d3 100644 --- a/src/lotus-state.cpp +++ b/src/lotus-state.cpp @@ -72,6 +72,7 @@ namespace fcitx { .modernStyle = *engine_->config().modernStyle, .freeMarking = *engine_->config().freeMarking, .w2u = static_cast(*engine_->config().w2u), + .bracketTransform = static_cast(*engine_->config().bracketTransform), .timeFormat = engine_->config().timeFormat->data(), .dateFormat = engine_->config().dateFormat->data(), }; @@ -269,14 +270,10 @@ namespace fcitx { switch (currentSym) { case FcitxKey_Tab: case FcitxKey_Down: { - if (globalCursorIndex == totalSize - 1) { - commonList->setGlobalCursorIndex(globalCursorIndex); - } else if (localCursorIndex < pageSize - 1) { + if (localCursorIndex < pageSize - 1 && globalCursorIndex < totalSize - 1) { commonList->setGlobalCursorIndex(globalCursorIndex + 1); } else { - commonList->next(); - int newPage = commonList->currentPage(); - commonList->setGlobalCursorIndex(newPage * pageSize); + commonList->setGlobalCursorIndex(currentPage * pageSize); } handled = true; break; @@ -284,15 +281,11 @@ namespace fcitx { case FcitxKey_ISO_Left_Tab: case FcitxKey_Up: { - if (globalCursorIndex == 0) { - commonList->setGlobalCursorIndex(globalCursorIndex); - } else if (localCursorIndex > 0) { + if (localCursorIndex > 0) { commonList->setGlobalCursorIndex(globalCursorIndex - 1); } else { - commonList->prev(); - int newPage = commonList->currentPage(); - int newIndex = (newPage * pageSize) + pageSize - 1; - commonList->setGlobalCursorIndex(newIndex); + int lastIndex = std::min((currentPage * pageSize) + pageSize - 1, totalSize - 1); + commonList->setGlobalCursorIndex(lastIndex); } handled = true; break; @@ -450,9 +443,6 @@ namespace fcitx { if (current_backspace_count_ < expected_backspaces_) { return false; // Allow intermediate backspaces to reach the app to clear autofill/old text. } - is_deleting_.store(false); - replacement_start_ms_.store(0, std::memory_order_release); - replacement_thread_id_.store(0, std::memory_order_release); std::this_thread::sleep_for(std::chrono::milliseconds(sleepTime)); // Validate surr cursor pos should match realtextLen after all BS applied const auto& surr = ic_->surroundingText(); @@ -472,9 +462,10 @@ namespace fcitx { LOTUS_INFO("Commit: " + pending_commit_string_); expected_backspaces_ = 0; current_backspace_count_ = 0; - pending_commit_string_ = ""; + pending_commit_string_.clear(); event.filterAndAccept(); // Filter out the final trigger backspace. + is_deleting_.store(false); if (getFrontendName(ic_) == "dbus" && !ic_->surroundingText().isValid()) replayBufferedKeys(); // Does we need drop this? return true; @@ -484,7 +475,6 @@ namespace fcitx { void LotusState::performReplacement(const std::string& deletedPart, const std::string& addedPart) { LOTUS_INFO("Perform replacement: " + deletedPart + " -> " + addedPart); //NOLINT - int my_id = ++current_thread_id_; current_backspace_count_ = 0; pending_commit_string_ = addedPart; const auto& surrounding = ic_->surroundingText(); @@ -497,10 +487,7 @@ namespace fcitx { if (realMode == LotusMode::Minecraft) { --expected_backspaces_; } - replacement_thread_id_.store(my_id, std::memory_order_release); - replacement_start_ms_.store(now_ms(), std::memory_order_release); is_deleting_.store(true, std::memory_order_release); - monitor_cv.notify_one(); send_backspace_uinput(expected_backspaces_); LOTUS_INFO("Send " + std::to_string(expected_backspaces_) + " backspaces"); } @@ -864,6 +851,24 @@ namespace fcitx { } } + void LotusState::handleDoubleHyphenReplacement() { + // Em-dash (U+2014) + std::string emDash = "—"; + switch (realMode) { + case LotusMode::SurroundingText: { + ic_->deleteSurroundingText(-1, 1); + ic_->commitString(emDash); + LOTUS_INFO("Commit: — (em-dash)"); + break; + } + default: { // Uinput, Smooth, Preedit, etc. + performReplacement("-", emDash); + LOTUS_INFO("Commit: — (em-dash)"); + break; + } + } + } + void LotusState::keyEvent(KeyEvent& keyEvent) { if (!lotusEngine_ || keyEvent.isRelease()) return; @@ -893,21 +898,6 @@ namespace fcitx { g_mouse_clicked.store(false, std::memory_order_release); clearAllBuffers(); } - - if (needFallbackCommit.load(std::memory_order_acquire)) { - LOTUS_INFO("Need fallback commit"); - needFallbackCommit.store(false, std::memory_order_release); - if (current_thread_id_.load(std::memory_order_acquire) == replacement_thread_id_.load(std::memory_order_acquire)) { - if (!pending_commit_string_.empty()) { - ic_->commitString(pending_commit_string_); - pending_commit_string_.clear(); - } - } - replacement_thread_id_.store(0, std::memory_order_release); - replacement_start_ms_.store(0, std::memory_order_release); - if (getFrontendName(ic_) == "dbus" && !ic_->surroundingText().isValid()) - replayBufferedKeys(); // Does we need drop this? - } KeySym currentSym = keyEvent.rawKey().sym(); if (*engine_->config().autoCapitalizeAfterPunctuation && realMode != LotusMode::Off) { // Ignore auto-capitalize side-effects if we're processing automated replacement backspaces @@ -968,7 +958,8 @@ namespace fcitx { } if (*engine_->config().doubleSpaceToPeriod && realMode != LotusMode::Off) { - if (currentSym == FcitxKey_space) { + bool isSpaceKey = (currentSym == FcitxKey_space || currentSym == FcitxKey_KP_Space); + if (isSpaceKey && !keyEvent.key().hasModifier()) { if (isPrevSpace_) { keyEvent.filterAndAccept(); handleDoubleSpaceReplacement(); @@ -981,6 +972,21 @@ namespace fcitx { } } + if (*engine_->config().doubleHyphenToEmDash && realMode != LotusMode::Off) { + bool isHyphenKey = (currentSym == FcitxKey_minus || currentSym == FcitxKey_KP_Subtract); + if (isHyphenKey && !keyEvent.key().hasModifier()) { + if (isPrevHyphen_) { + keyEvent.filterAndAccept(); + handleDoubleHyphenReplacement(); + isPrevHyphen_ = false; + return; + } + isPrevHyphen_ = true; + } else { + isPrevHyphen_ = false; + } + } + switch (realMode) { case LotusMode::Uinput: case LotusMode::Smooth: @@ -1017,6 +1023,7 @@ namespace fcitx { if (lotusEngine_) { isPrevSpace_ = false; + isPrevHyphen_ = false; shouldCapitalize_ = false; isPrevPunctuation_ = false; if (realMode == LotusMode::Preedit && isFocusOut) { @@ -1104,6 +1111,8 @@ namespace fcitx { emojiCandidates_.clear(); buffered_keys_.clear(); shouldCapitalize_ = false; + isPrevSpace_ = false; + isPrevHyphen_ = false; isPrevPunctuation_ = false; if (lotusEngine_) ResetEngine(lotusEngine_.handle()); diff --git a/src/lotus-state.h b/src/lotus-state.h index e4ed908d..1f72b04a 100644 --- a/src/lotus-state.h +++ b/src/lotus-state.h @@ -23,8 +23,6 @@ #include #include -#include - struct EmojiEntry; namespace fcitx { @@ -97,12 +95,12 @@ namespace fcitx { int expected_backspaces_ = 0; int current_backspace_count_ = 0; std::string pending_commit_string_; - std::atomic current_thread_id_{0}; std::string emojiBuffer_; std::vector emojiCandidates_; bool waitAck_ = false; std::vector buffered_keys_; ///< Keystrokes buffered during replacement bool isPrevSpace_ = false; + bool isPrevHyphen_ = false; bool shouldCapitalize_ = false; bool isPrevPunctuation_ = false; int64_t lastDeactivateTime_ = 0; @@ -178,6 +176,11 @@ namespace fcitx { */ void handleDoubleSpaceReplacement(); + /** + * @brief Handles the double hyphen to em-dash replacement. + */ + void handleDoubleHyphenReplacement(); + /** * @brief Checks and forwards special keys. * @param keyEvent The key event. diff --git a/src/lotus-utils.cpp b/src/lotus-utils.cpp index dea76cce..4e943c48 100644 --- a/src/lotus-utils.cpp +++ b/src/lotus-utils.cpp @@ -14,6 +14,7 @@ #include #include +#include // Global variables std::atomic realMode{fcitx::LotusMode::Smooth}; @@ -21,18 +22,10 @@ std::atomic needEngineReset{false}; std::atomic g_mouse_clicked{false}; std::atomic is_deleting_{false}; std::atomic stop_flag_monitor{false}; -std::atomic monitor_running{false}; std::atomic uinput_client_fd_{-1}; std::atomic realtextLen{0}; std::atomic mouse_socket_fd{-1}; -std::atomic replacement_start_ms_{0}; -std::atomic replacement_thread_id_{0}; -std::atomic needFallbackCommit{false}; - -std::mutex monitor_mutex; -std::condition_variable monitor_cv; - FCITX_DEFINE_LOG_CATEGORY(lotus, "lotus", fcitx::LogLevel::NoLog); std::string buildSocketPath(const char* base_path_suffix) { diff --git a/src/lotus-utils.h b/src/lotus-utils.h index f2b6fbce..7142f48c 100644 --- a/src/lotus-utils.h +++ b/src/lotus-utils.h @@ -14,8 +14,6 @@ #ifndef _FCITX5_LOTUS_UTILS_H_ #define _FCITX5_LOTUS_UTILS_H_ -#include -#include #include #include #include @@ -39,20 +37,14 @@ FCITX_DECLARE_LOG_CATEGORY(lotus); using KeySym = uint32_t; // Global state variables for input processing -extern std::atomic realMode; ///< Current active input mode -extern std::atomic needEngineReset; ///< Flag to trigger engine reset -extern std::atomic g_mouse_clicked; ///< Mouse click detection flag -extern std::atomic is_deleting_; ///< Deletion in progress flag -extern std::atomic stop_flag_monitor; ///< Signal to stop monitor threads -extern std::atomic monitor_running; ///< Monitor thread status -extern std::atomic uinput_client_fd_; ///< Uinput client file descriptor -extern std::atomic realtextLen; ///< Current text length -extern std::atomic mouse_socket_fd; ///< Mouse socket file descriptor -extern std::atomic replacement_start_ms_; ///< Timestamp for replacement -extern std::atomic replacement_thread_id_; ///< Thread ID for replacement -extern std::atomic needFallbackCommit; ///< Fallback commit flag -extern std::mutex monitor_mutex; ///< Mutex for monitor synchronization -extern std::condition_variable monitor_cv; ///< Condition variable for monitor +extern std::atomic realMode; ///< Current active input mode +extern std::atomic needEngineReset; ///< Flag to trigger engine reset +extern std::atomic g_mouse_clicked; ///< Mouse click detection flag +extern std::atomic is_deleting_; ///< Deletion in progress flag +extern std::atomic stop_flag_monitor; ///< Signal to stop monitor threads +extern std::atomic uinput_client_fd_; ///< Uinput client file descriptor +extern std::atomic realtextLen; ///< Current text length +extern std::atomic mouse_socket_fd; ///< Mouse socket file descriptor /** * @brief Builds socket path from base suffix.