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 💻 |
 Minh Tran 📦 |
+
+  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 💻 |
 Minh Tran 📦 |
+
+  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.