diff --git a/README.md b/README.md
index 0d1c039..09ec6a5 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
-
+
Fcitx5 VMK
@@ -51,7 +51,6 @@ Dự án này là một bản fork được tối ưu hóa từ [bộ gõ VMK g
Bật bộ gõ
Hướng dẫn sử dụng
Gỡ cài đặt
-
Cải tiến nổi bật
Đóng góp
Giấy phép
@@ -75,13 +74,17 @@ Hiện tại AUR có 3 gói cài đặt để bạn lựa chọn:
| `fcitx5-vmk-bin` | Dùng binary đã build sẵn (không cần biên dịch) |
| `fcitx5-vmk-git` | Build từ danh sách commit mới nhất |
-Cài đặt bằng `yay` hoặc `paru`:
+Cài đặt bằng `yay`:
```bash
# Cú pháp: yay -S
yay -S fcitx5-vmk
+```
+
+Hoặc `paru`:
-# Hoặc nếu dùng paru
+```bash
+# Cú pháp: paru -S
paru -S fcitx5-vmk
```
@@ -108,7 +111,7 @@ Hoặc có thể xem cách cài của từng distro [tại đây](INSTALL.md).
Thêm input của fcitx5-vmk vào `flake.nix`:
-```
+```nix
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
@@ -127,7 +130,7 @@ Thêm input của fcitx5-vmk vào `flake.nix`:
Bật fcitx5-vmk service trong `configuration.nix`:
-```
+```nix
{
inputs,
...
@@ -159,14 +162,21 @@ Rebuild lại system để cài đặt.
##### Yêu cầu hệ thống
+- Ubuntu/Debian
+
```bash
-# Ubuntu/Debian
sudo apt-get install cmake extra-cmake-modules libfcitx5core-dev libfcitx5config-dev libfcitx5utils-dev libinput-dev libudev-dev g++ golang hicolor-icon-theme pkg-config libx11-dev
+```
+
+- Fedora/RHEL
-# Fedora/RHEL
+```bash
sudo dnf install cmake extra-cmake-modules fcitx5-devel libinput-devel libudev-devel gcc-c++ golang hicolor-icon-theme systemd-devel libX11-devel
+```
+
+- openSUSE
-# openSUSE
+```bash
sudo zypper install cmake extra-cmake-modules fcitx5-devel libinput-devel systemd-devel gcc-c++ go hicolor-icon-theme systemd-devel libX11-devel udev
```
@@ -262,12 +272,11 @@ killall ibus-daemon || ibus exit
Thêm `fcitx5` vào danh sách ứng dụng khởi động cùng hệ thống (Autostart).
-Hướng dẫn Autostart cho từng Desktop Environment (GNOME, KDE, ...)
-
+Hướng dẫn thêm Autostart cho từng DE / WM (GNOME, Hyprland ...)
| DE / WM | Hướng dẫn chi tiết |
| :------------- | :----------------------------------------------------------------------------------------------------------------------------- |
-| **GNOME** | Mở **GNOME Tweaks** → _Startup Applications_ → Add → `Fcitx 5` |
+| **GNOME** | **GNOME Tweaks** → _Startup Applications_ → Add → `Fcitx 5` |
| **KDE Plasma** | **System Settings** → _Autostart_ → Add... → Add Application... → `Fcitx 5` |
| **Xfce** | **Settings** → _Session and Startup_ → _Application Autostart_ → Add → `Fcitx 5` |
| **Cinnamon** | **System Settings** → _Startup Applications_ → `+` → Choose application → `Fcitx 5` |
@@ -291,16 +300,17 @@ Sau khi đã log out và log in lại:
2. Tìm **VMK** ở cột bên phải.
3. Nhấn mũi tên **<** để thêm nó sang cột bên trái.
4. Apply.
+
+Cấu hình thêm cho Wayland (KDE, Hyprland)
-### 5. Cấu hình cho Wayland (KDE và Hyprland)
-
-Nếu bạn sử dụng **Wayland**, Fcitx5 cần được cấp quyền để hoạt động như bàn phím ảo:
+Nếu bạn sử dụng Wayland, Fcitx5 cần được cấu hình thêm để hoạt động như bàn phím ảo:
- **KDE Plasma:** Vào _System Settings_ → _Keyboard_ → _Virtual Keyboard_ → Chọn **Fcitx 5**.
- **Hyprland:** Thêm dòng sau vào `~/.config/hypr/hyprland.conf`:
```ini
permission = fcitx5-vmk-server, keyboard, allow
```
+
---
@@ -310,23 +320,25 @@ Nếu bạn sử dụng **Wayland**, Fcitx5 cần được cấp quyền để h
### 1. Menu chuyển mode nhanh
-Khi đang ở trong bất kỳ ứng dụng nào, nhấn phím **`** (dấu huyền) để mở menu chọn nhanh:
+Khi đang ở trong bất kỳ ứng dụng nào, nhấn phím **`** (dấu huyền) để mở menu chọn chế độ gõ:
+
+| Chế độ | Mô tả |
+| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| 🚀 **Mode 1 — Uinput (Smooth)** | Chế độ mặc định, phản hồi nhanh. Sử dụng server để gửi phím xoá.
_Hạn chế:_ Có thể không tương thích với ứng dụng xử lý chậm (ví dụ: LibreOffice). |
+| 🐢 **Mode 2 — Uinput (Slow)** | Tương tự Mode 1 nhưng tốc độ gửi phím chậm hơn.
_Khuyên dùng:_ Cho ứng dụng có tốc độ xử lý input thấp. |
+| 🍷 **Mode 3 — Uinput (Hardcore)** | Biến thể của Mode 1.
_Khuyên dùng:_ Khi chạy ứng dụng Windows qua Wine. |
+| ✨ **Mode 4 — Surrounding Text** | Dùng cơ chế Surrounding Text của ứng dụng (tối ưu cho Qt/GTK). Cho phép sửa dấu trên văn bản đã gõ, hoạt động mượt.
_Lưu ý:_ Phụ thuộc mức hỗ trợ của ứng dụng (có thể không ổn định trên Firefox). |
+| 📝 **Mode 5 — Preedit** | Hiển thị gạch chân khi gõ. Độ tương thích cao nhất nhưng trải nghiệm kém tự nhiên hơn các mode trên. |
+| 😃 **Emoji Picker** | Tìm kiếm và nhập Emoji (nguồn EmojiOne, hỗ trợ fuzzy search). Xem danh sách [tại đây](data/emoji/EMOJI_GUIDE.md). |
+| 📴 **OFF** | Tắt bộ gõ. |
+| 🔄 **Remove App Settings** | Khôi phục cấu hình mặc định cho ứng dụng hiện tại. |
+| 🚪 **Type `** | Nhập ký tự dấu huyền. |
-| Chế độ | Mô tả |
-| :------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| 🚀 **Mode 1 (Uinput smooth)** | Chế độ mặc định, tốc độ phản hồi cao. Sử dụng server để gửi phím xóa.
_Hạn chế:_ Không tương thích với ứng dụng xử lý chậm (ví dụ: LibreOffice). |
-| 🐢 **Mode 2 (Uinput)** | Tương tự Mode 1 nhưng tốc độ gửi phím chậm hơn.
_Khuyên dùng:_ Cho các ứng dụng có tốc độ xử lý input thấp. |
-| 🍷 **Mode 3 (Uinput hardcore)** | Biến thể của Mode 1.
_Khuyên dùng:_ Chạy ứng dụng Windows qua Wine. |
-| ✨ **Mode 4 (Surrounding Text)** | Sử dụng cơ chế Surrounding Text của ứng dụng (tối ưu cho Qt/GTK). Cho phép sửa dấu từ đã gõ và hoạt động mượt mà.
_Lưu ý:_ Phụ thuộc vào sự hỗ trợ của ứng dụng (có thể không ổn định trên Firefox). |
-| 📝 **Mode 5 (Preedit)** | Hiển thị gạch chân khi gõ. Độ tương thích cao nhất nhưng trải nghiệm không tự nhiên bằng các mode trên. |
-| 😃 **Emoji mode** | Chế độ tìm kiếm và nhập Emoji (nguồn EmojiOne, hỗ trợ Fuzzy Search). Xem danh sách [tại đây](data/emoji/EMOJI_GUIDE.md). |
-| 📴 **OFF** | Tắt bộ gõ cho ứng dụng hiện tại. |
-| 🔄 **Remove app settings** | Khôi phục cấu hình mặc định cho ứng dụng. |
-| 🚪 **Close menu and type `** | Đóng menu và nhập ký tự dấu huyền. |
+Bộ gõ sẽ lưu chế độ đã dùng gần nhất cho từng ứng dụng và tự động khôi phục cấu hình đó khi bạn mở lại cùng ứng dụng.
-### 2. Cơ chế đặt lại thông minh
+### 2. Cơ chế Smart Reset
-Khi bạn click chuột hoặc chạm vào touchpad để đổi vị trí nhập liệu, bộ gõ sẽ tự động đặt lại trạng thái ngay lập tức. Điều này giúp tránh lỗi dính chữ cũ vào từ mới (một lỗi rất phổ biến trên các bộ gõ Linux khác).
+Tự động reset trạng thái bộ gõ khi người dùng click chuột hoặc chạm touchpad để di chuyển con trỏ. Điều này ngăn chặn hiện tượng dính ký tự giữa các từ.
---
@@ -338,16 +350,17 @@ Khi bạn click chuột hoặc chạm vào touchpad để đổi vị trí nhậ
Arch / Arch-based - AUR
-Bạn có thể dùng `pacman`, `yay` hoặc `paru` để gỡ cài đặt:
+Bạn có thể dùng `pacman` (khuyên dùng), `yay` hoặc `paru` để gỡ cài đặt:
```bash
-# Sử dụng pacman (Khuyên dùng)
sudo pacman -Rns fcitx5-vmk
+```
-# Nếu dùng yay
+```bash
yay -Rns fcitx5-vmk
+```
-# Nếu dùng paru
+```bash
paru -Rns fcitx5-vmk
```
@@ -361,14 +374,21 @@ paru -Rns fcitx5-vmk
Gỡ package thông thường qua trình quản lý gói:
+- Debian/Ubuntu
+
```bash
-# Debian/Ubuntu
sudo apt remove fcitx5-vmk
+```
-# Fedora
+- Fedora
+
+```bash
sudo dnf remove fcitx5-vmk
+```
-# openSUSE
+- openSUSE
+
+```bash
sudo zypper remove fcitx5-vmk
```
@@ -396,46 +416,13 @@ sudo make uninstall
---
-
-
-## 🚀 Cải tiến nổi bật
-
-
-Click để xem chi tiết kỹ thuật
-
-
-Bản fork này thay đổi hoàn toàn kiến trúc của Server và Addon để đạt hiệu năng tối ưu và bảo mật tốt hơn.
-
-### 1. VMK Server (Backend)
-
-Server được viết lại theo phong cách **System Programming** hiện đại:
-
-- **Kiến trúc Event-Driven (Sử dụng `poll`):** Thay thế cơ chế polling liên tục (gây tốn CPU) bằng `poll()` với timeout hợp lý. Server sẽ "ngủ đông" khi không có sự kiện, giúp mức tiêu thụ CPU khi nhàn rỗi gần như 0%.
-- **Single-Threaded:** Loại bỏ đa luồng phức tạp, gộp chung việc lắng nghe Socket và sự kiện đầu vào vào một vòng lặp sự kiện duy nhất, giảm overhead và dung lượng binary.
-- **Real-time I/O:** Sử dụng socket để giao tiếp trực tiếp giữa server và addon thay vì ghi file log, giúp phản hồi tức thì và bảo vệ ổ cứng.
-- **Bảo mật Socket:**
- - Sử dụng **Abstract Socket** (không tạo file trên đĩa) kết hợp với xác thực `getsockopt` để đảm bảo chỉ tiến trình hợp lệ mới có thể gửi tín hiệu.
- - Khắc phục các rủi ro bảo mật liên quan đến quyền truy cập file socket công khai ở phiên bản cũ.
-
-### 2. VMK Addon (Frontend)
-
-Cải thiện trải nghiệm người dùng với các tính năng tiện ích:
-
-- **Per-App Configuration:** Tự động ghi nhớ chế độ gõ (Mode) riêng biệt cho từng ứng dụng (Ví dụ: Tắt bộ gõ ở Terminal, bật ở Trình duyệt).
-- **Menu Phím Tắt Thông Minh (`):** Menu ngữ cảnh hiển thị ngay tại con trỏ văn bản, cho phép chuyển đổi chế độ nhanh chóng.
-- **Tính năng mở rộng:** Hỗ trợ sửa dấu từ cũ (Surrounding Text), chế độ nhập Emoji và nhiều cải tiến khác.
-
-
-
----
-
## 🤝 Đóng góp
Đóng góp là điều làm cho cộng đồng mã nguồn mở trở thành một nơi tuyệt vời để học hỏi, truyền cảm hứng và sáng tạo. Mọi đóng góp của bạn đều được **đánh giá cao**.
-Vui lòng xem hướng dẫn chi tiết tại [đây](CONTRIBUTING.md) để biết cách tham gia phát triển dự án, quy trình Pull Request và quy tắc code style.
+Vui lòng xem hướng dẫn chi tiết [tại đây](CONTRIBUTING.md) để biết cách tham gia phát triển dự án, quy trình Pull Request và quy tắc code style.
Đừng quên tặng dự án một ⭐! Cảm ơn bạn rất nhiều!
diff --git a/data/icons/scalable/apps/fcitx-vmk-emoji.svg b/data/icons/scalable/apps/fcitx-vmk-emoji.svg
new file mode 100644
index 0000000..4848ff3
--- /dev/null
+++ b/data/icons/scalable/apps/fcitx-vmk-emoji.svg
@@ -0,0 +1,10 @@
+
+
\ No newline at end of file
diff --git a/data/icons/scalable/apps/fcitx-vmk-logo.svg b/data/icons/scalable/apps/fcitx-vmk-logo.svg
new file mode 100644
index 0000000..9a907bc
--- /dev/null
+++ b/data/icons/scalable/apps/fcitx-vmk-logo.svg
@@ -0,0 +1,41 @@
+
+
\ No newline at end of file
diff --git a/data/icons/scalable/apps/fcitx-vmk-off.svg b/data/icons/scalable/apps/fcitx-vmk-off.svg
new file mode 100644
index 0000000..ca3397c
--- /dev/null
+++ b/data/icons/scalable/apps/fcitx-vmk-off.svg
@@ -0,0 +1,6 @@
+
+
\ No newline at end of file
diff --git a/data/icons/scalable/apps/fcitx-vmk.svg b/data/icons/scalable/apps/fcitx-vmk.svg
index 3ed0d28..93f7e02 100644
--- a/data/icons/scalable/apps/fcitx-vmk.svg
+++ b/data/icons/scalable/apps/fcitx-vmk.svg
@@ -1,8 +1,6 @@
+
+
+
\ No newline at end of file
diff --git a/data/icons/scalable/apps/org.fcitx.Fcitx5.fcitx-vmk-emoji.svg b/data/icons/scalable/apps/org.fcitx.Fcitx5.fcitx-vmk-emoji.svg
new file mode 120000
index 0000000..fdc96e5
--- /dev/null
+++ b/data/icons/scalable/apps/org.fcitx.Fcitx5.fcitx-vmk-emoji.svg
@@ -0,0 +1 @@
+fcitx-vmk-emoji.svg
\ No newline at end of file
diff --git a/data/icons/scalable/apps/org.fcitx.Fcitx5.fcitx-vmk-off.svg b/data/icons/scalable/apps/org.fcitx.Fcitx5.fcitx-vmk-off.svg
new file mode 120000
index 0000000..7dcb9cd
--- /dev/null
+++ b/data/icons/scalable/apps/org.fcitx.Fcitx5.fcitx-vmk-off.svg
@@ -0,0 +1 @@
+fcitx-vmk-off.svg
\ No newline at end of file
diff --git a/misc/fcitx5-vmk-server@.service b/misc/fcitx5-vmk-server@.service
index 39c338e..89835d7 100644
--- a/misc/fcitx5-vmk-server@.service
+++ b/misc/fcitx5-vmk-server@.service
@@ -16,7 +16,7 @@ Type=simple
ExecStart=/usr/bin/fcitx5-vmk-server -u %i
Restart=on-failure
-RestartSec=5
+RestartSec=0
[Install]
WantedBy=multi-user.target
\ No newline at end of file
diff --git a/po/fcitx5-vmk.pot b/po/fcitx5-vmk.pot
index 33469bc..44a9f4b 100644
--- a/po/fcitx5-vmk.pot
+++ b/po/fcitx5-vmk.pot
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: fcitx5-vmk 0.12.1\n"
"Report-Msgid-Bugs-To: nhktmdzhg@gmail.com\n"
-"POT-Creation-Date: 2026-02-15 12:12+0700\n"
+"POT-Creation-Date: 2026-02-17 20:07+0700\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
@@ -29,7 +29,7 @@ msgstr ""
msgid "Macro"
msgstr ""
-#: src/vmk-config.h:124 src/vmk-config.h:138
+#: src/vmk-config.h:124 src/vmk-config.h:139
msgid "Custom Keymap"
msgstr ""
@@ -45,126 +45,142 @@ msgstr ""
msgid "Output Charset"
msgstr ""
-#: src/vmk-config.h:133 src/vmk.cpp:1345
+#: src/vmk-config.h:133 src/vmk.cpp:1399
msgid "Enable spell check"
msgstr ""
-#: src/vmk-config.h:133 src/vmk.cpp:1357
+#: src/vmk-config.h:133 src/vmk.cpp:1411
msgid "Enable Macro"
msgstr ""
-#: src/vmk-config.h:134 src/vmk.cpp:1369
+#: src/vmk-config.h:134 src/vmk.cpp:1423
msgid "Capitalize Macro"
msgstr ""
-#: src/vmk-config.h:135 src/vmk.cpp:1381
+#: src/vmk-config.h:135 src/vmk.cpp:1435
msgid "Auto restore keys with invalid words"
msgstr ""
-#: src/vmk-config.h:136 src/vmk.cpp:1393
+#: src/vmk-config.h:136 src/vmk.cpp:1447
msgid "Use oà, _uý (instead of òa, úy)"
msgstr ""
-#: src/vmk-config.h:137 src/vmk.cpp:1405
+#: src/vmk-config.h:137 src/vmk.cpp:1459
msgid "Allow type with more freedom"
msgstr ""
-#: src/vmk.cpp:1260
-msgid "Typing Mode"
+#: src/vmk-config.h:138 src/vmk.cpp:1471
+msgid "Fix uinput mode with ack"
+msgstr ""
+
+#: src/vmk.cpp:420
+msgid "Page "
msgstr ""
#: src/vmk.cpp:1317
+msgid "Typing Mode"
+msgstr ""
+
+#: src/vmk.cpp:1371
msgid "Charset"
msgstr ""
-#: src/vmk.cpp:1734
+#: src/vmk.cpp:1878
msgid "Typing Mode: "
msgstr ""
-#: src/vmk.cpp:1765
+#: src/vmk.cpp:1909
msgid "Spell Check: On"
msgstr ""
-#: src/vmk.cpp:1765
+#: src/vmk.cpp:1909
msgid "Spell Check: Off"
msgstr ""
-#: src/vmk.cpp:1773
+#: src/vmk.cpp:1917
msgid "Macro: On"
msgstr ""
-#: src/vmk.cpp:1773
+#: src/vmk.cpp:1917
msgid "Macro: Off"
msgstr ""
-#: src/vmk.cpp:1781
+#: src/vmk.cpp:1925
msgid "Capitalize Macro: On"
msgstr ""
-#: src/vmk.cpp:1781
+#: src/vmk.cpp:1925
msgid "Capitalize Macro: Off"
msgstr ""
-#: src/vmk.cpp:1789
+#: src/vmk.cpp:1933
msgid "Auto Non-VN Restore: On"
msgstr ""
-#: src/vmk.cpp:1789
+#: src/vmk.cpp:1933
msgid "Auto Non-VN Restore: Off"
msgstr ""
-#: src/vmk.cpp:1797
+#: src/vmk.cpp:1941
msgid "Modern Style: On"
msgstr ""
-#: src/vmk.cpp:1797
+#: src/vmk.cpp:1941
msgid "Modern Style: Off"
msgstr ""
-#: src/vmk.cpp:1805
+#: src/vmk.cpp:1949
msgid "Free Marking: On"
msgstr ""
-#: src/vmk.cpp:1805
+#: src/vmk.cpp:1949
msgid "Free Marking: Off"
msgstr ""
-#: src/vmk.cpp:1865
-msgid " (Default)"
+#: src/vmk.cpp:1957
+msgid "Fix Vmk1 With Ack: On"
+msgstr ""
+
+#: src/vmk.cpp:1957
+msgid "Fix Vmk1 With Ack: Off"
+msgstr ""
+
+#: src/vmk.cpp:2063
+msgid "App: "
msgstr ""
-#: src/vmk.cpp:1871
-msgid "App name detected by fcitx5: "
+#: src/vmk.cpp:2064
+msgid "[1] Uinput (smooth)"
msgstr ""
-#: src/vmk.cpp:1872
-msgid "1. Fake backspace by Uinput (smooth)"
+#: src/vmk.cpp:2065
+msgid "[2] Uinput (Slow)"
msgstr ""
-#: src/vmk.cpp:1873
-msgid "2. Fake backspace by Uinput"
+#: src/vmk.cpp:2066
+msgid "[3] Uinput (Hardcore)"
msgstr ""
-#: src/vmk.cpp:1874
-msgid "3. Fake backspace by Uinput for wine apps"
+#: src/vmk.cpp:2067
+msgid "[4] Surrounding Text"
msgstr ""
-#: src/vmk.cpp:1875
-msgid "4. Surrounding Text"
+#: src/vmk.cpp:2068
+msgid "[q] Preedit"
msgstr ""
-#: src/vmk.cpp:1876
-msgid "5. Preedit"
+#: src/vmk.cpp:2069
+msgid "[w] Emoji Picker"
msgstr ""
-#: src/vmk.cpp:1877
-msgid "6. Emoji mode"
+#: src/vmk.cpp:2070
+msgid "[e] OFF"
msgstr ""
-#: src/vmk.cpp:1879
-msgid "8. Remove app settings"
+#: src/vmk.cpp:2072
+msgid "[r] Remove App Settings"
msgstr ""
-#: src/vmk.cpp:1880
-msgid "`. Close menu and type `"
+#: src/vmk.cpp:2080
+msgid "[`] Type `"
msgstr ""
diff --git a/po/vi.po b/po/vi.po
index fa6938e..f9a6cbf 100644
--- a/po/vi.po
+++ b/po/vi.po
@@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: nhktmdzhg@gmail.com\n"
-"POT-Creation-Date: 2026-02-15 12:12+0700\n"
-"PO-Revision-Date: 2026-02-13 21:59+0700\n"
+"POT-Creation-Date: 2026-02-17 20:07+0700\n"
+"PO-Revision-Date: 2026-02-17 20:44+0700\n"
"Last-Translator: Loc Huynh \n"
"Language-Team: none\n"
"Language: vi\n"
@@ -30,7 +30,7 @@ msgstr "Giá trị"
msgid "Macro"
msgstr "Gõ tắt"
-#: src/vmk-config.h:124 src/vmk-config.h:138
+#: src/vmk-config.h:124 src/vmk-config.h:139
msgid "Custom Keymap"
msgstr "Keymap tùy chỉnh"
@@ -46,126 +46,142 @@ msgstr "Kiểu gõ"
msgid "Output Charset"
msgstr "Bảng mã"
-#: src/vmk-config.h:133 src/vmk.cpp:1345
+#: src/vmk-config.h:133 src/vmk.cpp:1399
msgid "Enable spell check"
msgstr "Bật kiểm tra chính tả"
-#: src/vmk-config.h:133 src/vmk.cpp:1357
+#: src/vmk-config.h:133 src/vmk.cpp:1411
msgid "Enable Macro"
msgstr "Bật gõ tắt"
-#: src/vmk-config.h:134 src/vmk.cpp:1369
+#: src/vmk-config.h:134 src/vmk.cpp:1423
msgid "Capitalize Macro"
msgstr "Gõ tắt chữ hoa"
-#: src/vmk-config.h:135 src/vmk.cpp:1381
+#: src/vmk-config.h:135 src/vmk.cpp:1435
msgid "Auto restore keys with invalid words"
msgstr "Tự động khôi phục phím với từ sai"
-#: src/vmk-config.h:136 src/vmk.cpp:1393
+#: src/vmk-config.h:136 src/vmk.cpp:1447
msgid "Use oà, _uý (instead of òa, úy)"
msgstr "Dùng oà, _uý (thay vì òa, úy)"
-#: src/vmk-config.h:137 src/vmk.cpp:1405
+#: src/vmk-config.h:137 src/vmk.cpp:1459
msgid "Allow type with more freedom"
msgstr "Cho phép gõ dấu tự do"
-#: src/vmk.cpp:1260
+#: src/vmk-config.h:138 src/vmk.cpp:1471
+msgid "Fix uinput mode with ack"
+msgstr "Sửa lỗi chế độ uinput với ack"
+
+#: src/vmk.cpp:420
+msgid "Page "
+msgstr "Trang "
+
+#: src/vmk.cpp:1317
msgid "Typing Mode"
msgstr "Chế độ gõ"
-#: src/vmk.cpp:1317
+#: src/vmk.cpp:1371
msgid "Charset"
msgstr "Bảng mã"
-#: src/vmk.cpp:1734
+#: src/vmk.cpp:1878
msgid "Typing Mode: "
msgstr "Chế độ gõ: "
-#: src/vmk.cpp:1765
+#: src/vmk.cpp:1909
msgid "Spell Check: On"
msgstr "Kiểm tra chính tả: Bật"
-#: src/vmk.cpp:1765
+#: src/vmk.cpp:1909
msgid "Spell Check: Off"
msgstr "Kiểm tra chính tả: Tắt"
-#: src/vmk.cpp:1773
+#: src/vmk.cpp:1917
msgid "Macro: On"
msgstr "Gõ tắt: Bật"
-#: src/vmk.cpp:1773
+#: src/vmk.cpp:1917
msgid "Macro: Off"
msgstr "Gõ tắt: Tắt"
-#: src/vmk.cpp:1781
+#: src/vmk.cpp:1925
msgid "Capitalize Macro: On"
msgstr "Gõ tắt chữ hoa: Bật"
-#: src/vmk.cpp:1781
+#: src/vmk.cpp:1925
msgid "Capitalize Macro: Off"
msgstr "Gõ tắt chữ hoa: Tắt"
-#: src/vmk.cpp:1789
+#: src/vmk.cpp:1933
msgid "Auto Non-VN Restore: On"
msgstr "Tự động khôi phục từ không phải Tiếng Việt: Bật"
-#: src/vmk.cpp:1789
+#: src/vmk.cpp:1933
msgid "Auto Non-VN Restore: Off"
msgstr "Tự động khôi phục từ không phải Tiếng Việt: Tắt"
-#: src/vmk.cpp:1797
+#: src/vmk.cpp:1941
msgid "Modern Style: On"
msgstr "Dấu chuẩn: Bật"
-#: src/vmk.cpp:1797
+#: src/vmk.cpp:1941
msgid "Modern Style: Off"
msgstr "Dấu chuẩn: Tắt"
-#: src/vmk.cpp:1805
+#: src/vmk.cpp:1949
msgid "Free Marking: On"
msgstr "Dấu tự do: Bật"
-#: src/vmk.cpp:1805
+#: src/vmk.cpp:1949
msgid "Free Marking: Off"
msgstr "Dấu tự do: Tắt"
-#: src/vmk.cpp:1865
-msgid " (Default)"
-msgstr " (Mặc định)"
+#: src/vmk.cpp:1957
+msgid "Fix Vmk1 With Ack: On"
+msgstr "Sửa lỗi Vmk1 với Ack: Bật"
+
+#: src/vmk.cpp:1957
+msgid "Fix Vmk1 With Ack: Off"
+msgstr "Sửa lỗi Vmk1 với Ack: Tắt"
+
+#: src/vmk.cpp:2063
+msgid "App: "
+msgstr "Ứng dụng: "
-#: src/vmk.cpp:1871
-msgid "App name detected by fcitx5: "
-msgstr "Tên ứng dụng nhận dạng bởi fcitx5: "
+#: src/vmk.cpp:2064
+msgid "[1] Uinput (smooth)"
+msgstr "[1] Uinput (mượt)"
-#: src/vmk.cpp:1872
-msgid "1. Fake backspace by Uinput (smooth)"
-msgstr "1. Phím xóa giả bởi uinput (mượt)"
+#: src/vmk.cpp:2065
+msgid "[2] Uinput (Slow)"
+msgstr "[2] Uinput (chậm)"
-#: src/vmk.cpp:1873
-msgid "2. Fake backspace by Uinput"
-msgstr "2. Phím xóa giả bởi uinput"
+#: src/vmk.cpp:2066
+msgid "[3] Uinput (Hardcore)"
+msgstr "[3] Uinput (Hardcore)"
-#: src/vmk.cpp:1874
-msgid "3. Fake backspace by Uinput for wine apps"
-msgstr "3. Phím xóa giả bởi uinput cho các ứng dụng wine"
+#: src/vmk.cpp:2067
+msgid "[4] Surrounding Text"
+msgstr "[4] Văn bản xung quanh"
-#: src/vmk.cpp:1875
-msgid "4. Surrounding Text"
-msgstr "4. Văn bản xung quanh"
+#: src/vmk.cpp:2068
+msgid "[q] Preedit"
+msgstr "[q] Gạch chân"
-#: src/vmk.cpp:1876
-msgid "5. Preedit"
-msgstr "5. Gạch chân"
+#: src/vmk.cpp:2069
+msgid "[w] Emoji Picker"
+msgstr "[w] Chọn Emoji"
-#: src/vmk.cpp:1877
-msgid "6. Emoji mode"
-msgstr "6. Chế độ emoji"
+#: src/vmk.cpp:2070
+msgid "[e] OFF"
+msgstr "[e] Tắt"
-#: src/vmk.cpp:1879
-msgid "8. Remove app settings"
-msgstr "8. Xóa cài đặt ứng dụng"
+#: src/vmk.cpp:2072
+msgid "[r] Remove App Settings"
+msgstr "[r] Xóa cài đặt ứng dụng"
-#: src/vmk.cpp:1880
-msgid "`. Close menu and type `"
-msgstr "`. Đóng menu và gõ `"
+#: src/vmk.cpp:2080
+msgid "[`] Type `"
+msgstr "[`] Nhập `"
\ No newline at end of file
diff --git a/server/vmk-server.cpp b/server/vmk-server.cpp
index b5dceee..fba91d3 100644
--- a/server/vmk-server.cpp
+++ b/server/vmk-server.cpp
@@ -123,10 +123,15 @@ int main(int argc, char* argv[]) {
if (uinput_fd_ >= 0) {
ioctl(uinput_fd_, UI_SET_EVBIT, EV_KEY);
ioctl(uinput_fd_, UI_SET_KEYBIT, KEY_BACKSPACE);
- struct uinput_user_dev uidev{};
- snprintf(uidev.name, UINPUT_MAX_NAME_SIZE, "Fcitx5_Uinput_Server");
- (void)write(uinput_fd_, &uidev, sizeof(uidev));
+ struct uinput_setup usetup;
+ memset(&usetup, 0, sizeof(usetup));
+ usetup.id.bustype = BUS_USB;
+ usetup.id.vendor = 0x1234;
+ usetup.id.product = 0x5678;
+ strncpy(usetup.name, "VMK-Uinput-Server", UINPUT_MAX_NAME_SIZE - 1);
+ ioctl(uinput_fd_, UI_DEV_SETUP, &usetup);
ioctl(uinput_fd_, UI_DEV_CREATE);
+ sleep(1);
}
int server_fd = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0); // Non-blocking socket
@@ -200,6 +205,10 @@ int main(int argc, char* argv[]) {
if (pending_backspaces > 0) {
send_single_backspace();
--pending_backspaces;
+ if (pending_backspaces == 0) {
+ char ack = '7';
+ send(fds[3].fd, &ack, sizeof(ack), MSG_NOSIGNAL | MSG_DONTWAIT);
+ }
}
}
diff --git a/src/ack-apps.h b/src/ack-apps.h
new file mode 100644
index 0000000..ad1d602
--- /dev/null
+++ b/src/ack-apps.h
@@ -0,0 +1,4 @@
+#include
+#include
+
+static std::vector ack_apps = {"chrome", "chromium", "brave", "edge", "vivaldi", "opera", "coccoc", "cromite", "helium", "thorium", "slimjet", "yandex"};
\ No newline at end of file
diff --git a/src/vmk-config.h b/src/vmk-config.h
index fc3b5bf..6cd0ae4 100644
--- a/src/vmk-config.h
+++ b/src/vmk-config.h
@@ -135,6 +135,7 @@ namespace fcitx {
Option autoNonVnRestore{this, "AutoNonVnRestore", _("Auto restore keys with invalid words"), true};
Option modernStyle{this, "ModernStyle", _("Use oà, _uý (instead of òa, úy)"), true};
Option freeMarking{this, "FreeMarking", _("Allow type with more freedom"), true};
+ Option fixVmk1WithAck{this, "FixVmk1WithAck", _("Fix uinput mode with ack"), false};
SubConfigOption customKeymap{this, "CustomKeymap", _("Custom Keymap"), "fcitx://config/addon/vmk/custom_keymap"};);
} // namespace fcitx
diff --git a/src/vmk.cpp b/src/vmk.cpp
index 4885120..69701b5 100644
--- a/src/vmk.cpp
+++ b/src/vmk.cpp
@@ -7,6 +7,7 @@
*
*/
#include "vmk.h"
+#include "ack-apps.h"
#include
#include
@@ -67,9 +68,8 @@ std::atomic needEngineReset{false};
std::string BASE_SOCKET_PATH;
// Global flag to signal mouse click for closing app mode menu
static std::atomic g_mouse_clicked{false};
-
std::atomic is_deleting_{false};
-static const int MAX_BACKSPACE_COUNT = 15;
+static const int MAX_BACKSPACE_COUNT = 8;
std::string SubstrChar(const std::string& s, size_t start, size_t len);
int compareAndSplitStrings(const std::string& A, const std::string& B, std::string& commonPrefix, std::string& deletedPart, std::string& addedPart);
std::once_flag monitor_init_flag;
@@ -77,6 +77,7 @@ std::atomic stop_flag_monitor{false};
std::atomic monitor_running{false};
int uinput_client_fd_ = -1;
int realtextLen = 0;
+bool waitAck = false;
std::atomic mouse_socket_fd{-1};
std::atomic replacement_start_ms_{0};
@@ -304,6 +305,11 @@ namespace fcitx {
send(uinput_client_fd_, &count, sizeof(count), MSG_NOSIGNAL);
}
}
+
+ if (waitAck) {
+ char ack;
+ recv(uinput_client_fd_, &ack, sizeof(ack), MSG_NOSIGNAL);
+ }
}
void replayBufferToEngine(const std::string& buffer) {
@@ -397,6 +403,24 @@ namespace fcitx {
}
// Helper function for emoji mode
+ void updateEmojiPageStatus(CommonCandidateList* commonList) {
+ if (!commonList || commonList->empty()) {
+ return;
+ }
+
+ int pageSize = commonList->pageSize();
+ if (pageSize <= 0) {
+ pageSize = 9;
+ }
+
+ int totalItems = commonList->totalSize();
+ int currentPage = commonList->currentPage() + 1;
+ int totalPages = (totalItems + pageSize - 1) / pageSize;
+
+ std::string status = _("Page ") + std::to_string(currentPage) + "/" + std::to_string(totalPages);
+ ic_->inputPanel().setAuxDown(Text(status));
+ }
+
void handleEmojiMode(KeyEvent& keyEvent) {
if (keyEvent.key().hasModifier()) {
keyEvent.forward();
@@ -482,6 +506,7 @@ namespace fcitx {
}
if (handled) {
+ updateEmojiPageStatus(commonList.get());
ic_->updateUserInterface(UserInterfaceComponent::InputPanel);
keyEvent.filterAndAccept();
return;
@@ -589,6 +614,8 @@ namespace fcitx {
candidateList->setGlobalCursorIndex(0);
ic_->inputPanel().setCandidateList(std::move(candidateList));
+ auto currentList = std::dynamic_pointer_cast(ic_->inputPanel().candidateList());
+ updateEmojiPageStatus(currentList.get());
} else {
ic_->inputPanel().setCandidateList(nullptr);
}
@@ -1061,6 +1088,22 @@ namespace fcitx {
return;
}
is_deleting_.store(false);
+
+ // Commit pending preedit text before clearing buffers to prevent data loss.
+ // Strategy: Call EngineCommitPreedit() first (for Preedit mode finalization),
+ // then pull any remaining preedit text (for other modes like VMK1/VMKSmooth).
+ // This ensures text is saved regardless of which mode we're switching from.
+ if (vmkEngine_) {
+ // Finalize preedit properly (required for Preedit mode)
+ if (realMode == VMKMode::Preedit) {
+ EngineCommitPreedit(vmkEngine_.handle());
+ UniqueCPtr commit(EnginePullCommit(vmkEngine_.handle()));
+ if (commit && commit.get()[0]) {
+ ic_->commitString(commit.get());
+ }
+ }
+ }
+
clearAllBuffers();
switch (realMode) {
@@ -1078,8 +1121,6 @@ namespace fcitx {
break;
}
case VMKMode::Emoji: {
- emojiBuffer_.clear();
- emojiCandidates_.clear();
ic_->inputPanel().reset();
ic_->updateUserInterface(UserInterfaceComponent::InputPanel);
ic_->updatePreedit();
@@ -1105,6 +1146,19 @@ namespace fcitx {
ic_->updatePreedit();
break;
}
+ case VMKMode::VMK1:
+ case VMKMode::VMK1HC:
+ case VMKMode::VMKSmooth: {
+ // For uinput modes: commit pending preedit when focus changes
+ // (e.g., when user tabs to another field while typing)
+ if (vmkEngine_) {
+ UniqueCPtr preedit(EnginePullPreedit(vmkEngine_.handle()));
+ if (preedit && preedit.get()[0]) {
+ ic_->commitString(preedit.get());
+ }
+ }
+ break;
+ }
case VMKMode::VMK2: {
if (vmkEngine_)
ResetEngine(vmkEngine_.handle());
@@ -1117,6 +1171,9 @@ namespace fcitx {
}
void clearAllBuffers() {
+ if (is_deleting_.load(std::memory_order_acquire)) {
+ return;
+ }
oldPreBuffer_.clear();
history_.clear();
if (!is_deleting_.load(std::memory_order_acquire)) {
@@ -1275,13 +1332,10 @@ namespace fcitx {
config_.mode.setValue(modeEnumToString(mode));
saveConfig();
- realMode = mode;
+ setMode(mode, ic);
globalMode_ = mode;
reloadConfig();
updateModeAction(ic);
- if (ic) {
- ic->updateUserInterface(UserInterfaceComponent::StatusArea);
- }
}));
modeMenu_->addAction(action.get());
modeSubAction_.push_back(std::move(action));
@@ -1413,6 +1467,18 @@ namespace fcitx {
}));
uiManager.registerAction("vmk-freemarking", freeMarkingAction_.get());
+ fixVmk1WithAckAction_ = std::make_unique();
+ fixVmk1WithAckAction_->setLongText(_("Fix uinput mode with ack"));
+ fixVmk1WithAckAction_->setIcon("network-transmit-receive");
+ fixVmk1WithAckAction_->setCheckable(true);
+ connections_.emplace_back(fixVmk1WithAckAction_->connect([this](InputContext* ic) {
+ config_.fixVmk1WithAck.setValue(!*config_.fixVmk1WithAck);
+ saveConfig();
+ refreshOption();
+ updateFixVmk1WithAckAction(ic);
+ }));
+ uiManager.registerAction("vmk-fixvmk1withack", fixVmk1WithAckAction_.get());
+
reloadConfig();
globalMode_ = modeStringToEnum(config_.mode.value());
updateModeAction(nullptr);
@@ -1480,6 +1546,7 @@ namespace fcitx {
updateAutoNonVnRestoreAction(nullptr);
updateModernStyleAction(nullptr);
updateFreeMarkingAction(nullptr);
+ updateFixVmk1WithAckAction(nullptr);
}
void vmkEngine::setSubConfig(const std::string& path, const RawConfig& config) {
@@ -1527,7 +1594,20 @@ namespace fcitx {
updateInputMethodAction(event.inputContext());
updateCharsetAction(event.inputContext());
- realMode = targetMode;
+ if (*config_.fixVmk1WithAck) {
+ if (targetMode == VMKMode::VMK1 || targetMode == VMKMode::VMK1HC || targetMode == VMKMode::VMKSmooth) {
+ bool needWaitAck = false;
+ for (const auto& ackApp : ack_apps) {
+ if (appName.find(ackApp) != std::string::npos) {
+ needWaitAck = true;
+ break;
+ }
+ }
+ waitAck = needWaitAck;
+ }
+ }
+
+ setMode(targetMode, event.inputContext());
auto state = ic->propertyFor(&factory_);
@@ -1547,13 +1627,15 @@ namespace fcitx {
statusArea.addAction(StatusGroup::InputMethod, autoNonVnRestoreAction_.get());
statusArea.addAction(StatusGroup::InputMethod, modernStyleAction_.get());
statusArea.addAction(StatusGroup::InputMethod, freeMarkingAction_.get());
+ statusArea.addAction(StatusGroup::InputMethod, fixVmk1WithAckAction_.get());
}
void vmkEngine::keyEvent(const InputMethodEntry& entry, KeyEvent& keyEvent) {
FCITX_UNUSED(entry);
auto ic = keyEvent.inputContext();
- // Check if mouse was clicked to close app mode menu
+ // Handle mouse click event from fcitx5-vmk-server to close app mode menu
+ // The server sends signal via Unix socket when user clicks outside the menu
if (isSelectingAppMode_ && g_mouse_clicked.load(std::memory_order_relaxed)) {
closeAppModeMenu();
ic->inputPanel().reset();
@@ -1562,19 +1644,79 @@ namespace fcitx {
state->reset();
}
- // logic when opening app mode menu
+ // Handle keyboard input when app mode selection menu is active
if (isSelectingAppMode_) {
if (keyEvent.isRelease())
return;
+ auto baseList = ic->inputPanel().candidateList();
+ auto menuList = std::dynamic_pointer_cast(baseList);
+ KeySym keySym = keyEvent.key().sym();
+
+ // Lambda to move cursor in candidate list with wrap-around
+ // Note: Index 0 is reserved for header ("App: ..."), so valid range is [1, totalSize-1]
+ auto moveCursor = [&](int delta) {
+ if (!menuList || menuList->empty()) {
+ return false;
+ }
+
+ int totalSize = menuList->totalSize();
+ if (totalSize <= 1) {
+ return false;
+ }
+
+ int cursorIndex = menuList->globalCursorIndex();
+ if (cursorIndex < 1 || cursorIndex >= totalSize) {
+ cursorIndex = 1;
+ }
+
+ int nextIndex = cursorIndex + delta;
+ // Wrap around: bottom → top or top → bottom
+ if (nextIndex < 1) {
+ nextIndex = totalSize - 1;
+ } else if (nextIndex >= totalSize) {
+ nextIndex = 1;
+ }
+
+ menuList->setGlobalCursorIndex(nextIndex);
+ ic->updateUserInterface(UserInterfaceComponent::InputPanel);
+ return true;
+ };
+
keyEvent.filterAndAccept();
+
VMKMode selectedMode = VMKMode::NoMode;
bool selectionMade = false;
- KeySym keySym = keyEvent.key().sym();
-
- // map number key to mode
switch (keySym) {
+ case FcitxKey_Tab:
+ case FcitxKey_Down: {
+ if (moveCursor(1)) {
+ return;
+ }
+ break;
+ }
+ case FcitxKey_ISO_Left_Tab:
+ case FcitxKey_Up: {
+ if (moveCursor(-1)) {
+ return;
+ }
+ break;
+ }
+ case FcitxKey_space:
+ case FcitxKey_Return: {
+ if (menuList && !menuList->empty()) {
+ int selectedIndex = menuList->globalCursorIndex();
+ if (selectedIndex < 1 || selectedIndex >= menuList->totalSize()) {
+ selectedIndex = 1;
+ }
+ menuList->candidateFromAll(selectedIndex).select(ic);
+ return;
+ }
+ break;
+ }
+ // Map keyboard shortcuts to modes
+ // Numbers [1-4]: VMK input modes, Letters [q/w/e/r]: Special modes/actions
case FcitxKey_1: {
selectedMode = VMKMode::VMKSmooth;
break;
@@ -1591,19 +1733,19 @@ namespace fcitx {
selectedMode = VMKMode::VMK2;
break;
}
- case FcitxKey_5: {
+ case FcitxKey_q: {
selectedMode = VMKMode::Preedit;
break;
}
- case FcitxKey_6: {
+ case FcitxKey_w: {
selectedMode = VMKMode::Emoji;
break;
}
- case FcitxKey_7: {
+ case FcitxKey_e: {
selectedMode = VMKMode::Off;
break;
}
- case FcitxKey_8: {
+ case FcitxKey_r: {
if (appRules_.count(currentConfigureApp_)) {
appRules_.erase(currentConfigureApp_);
saveAppRules();
@@ -1621,9 +1763,7 @@ namespace fcitx {
ic->updateUserInterface(UserInterfaceComponent::InputPanel);
auto state = ic->propertyFor(&factory_);
state->reset();
- Key key(FcitxKey_grave);
- ic->forwardKey(key, false);
- ic->forwardKey(key, true);
+ ic->commitString("`");
return;
}
default: break;
@@ -1635,7 +1775,7 @@ namespace fcitx {
saveAppRules();
}
- realMode = selectedMode;
+ setMode(selectedMode, ic);
selectionMade = true;
}
@@ -1649,7 +1789,8 @@ namespace fcitx {
return;
}
- // logic when typing `
+ // Open app mode selection menu when user presses backtick/grave key (`)
+ // Triggered by Shift+` key combination, allows user to change input mode per-app
if (!keyEvent.isRelease() && keyEvent.rawKey().check(FcitxKey_grave)) {
currentConfigureApp_ = ic->program();
if (currentConfigureApp_.empty())
@@ -1676,7 +1817,9 @@ namespace fcitx {
return;
}
- state->reset();
+ if (event.type() == EventType::InputContextFocusOut) {
+ state->reset();
+ }
}
void vmkEngine::deactivate(const InputMethodEntry& entry, InputContextEvent& event) {
@@ -1723,8 +1866,9 @@ namespace fcitx {
void vmkEngine::updateModeAction(InputContext* ic) {
std::string currentModeStr = config_.mode.value();
- realMode = modeStringToEnum(currentModeStr);
- globalMode_ = realMode;
+ VMKMode newMode = modeStringToEnum(currentModeStr);
+ globalMode_ = newMode;
+ setMode(newMode, ic);
for (const auto& action : modeSubAction_) {
action->setChecked(action->name() == "vmk-mode-" + currentModeStr);
@@ -1807,6 +1951,15 @@ namespace fcitx {
freeMarkingAction_->update(ic);
}
}
+
+ void vmkEngine::updateFixVmk1WithAckAction(InputContext* ic) {
+ fixVmk1WithAckAction_->setChecked(*config_.fixVmk1WithAck);
+ fixVmk1WithAckAction_->setShortText(*config_.fixVmk1WithAck ? _("Fix Vmk1 With Ack: On") : _("Fix Vmk1 With Ack: Off"));
+ if (ic) {
+ fixVmk1WithAckAction_->update(ic);
+ }
+ }
+
void vmkEngine::loadAppRules() {
appRules_.clear();
std::ifstream file(appRulesPath_);
@@ -1845,6 +1998,25 @@ namespace fcitx {
g_mouse_clicked.store(false, std::memory_order_relaxed);
}
+ namespace {
+ // Custom candidate word class that supports both mouse click and keyboard selection
+ // Unlike DisplayOnlyCandidateWord, this executes a callback when selected,
+ // enabling interactive menu items in the app mode selection UI
+ class AppModeCandidateWord : public CandidateWord {
+ public:
+ AppModeCandidateWord(Text text, std::function callback) : CandidateWord(std::move(text)), callback_(std::move(callback)) {}
+
+ void select(InputContext* ic) const override {
+ if (callback_) {
+ callback_(ic);
+ }
+ }
+
+ private:
+ std::function callback_;
+ };
+ } // namespace
+
void vmkEngine::showAppModeMenu(InputContext* ic) {
isSelectingAppMode_ = true;
@@ -1853,36 +2025,97 @@ namespace fcitx {
candidateList->setLayoutHint(CandidateLayoutHint::Vertical);
candidateList->setPageSize(10);
- VMKMode currentAppRules = VMKMode::Off;
- if (appRules_.count(currentConfigureApp_)) {
- currentAppRules = appRules_[currentConfigureApp_];
- } else {
- currentAppRules = globalMode_;
- }
-
+ // Helper lambda: Add ">>" marker to highlight current active mode
auto getLabel = [&](const VMKMode& modeName, const std::string& modeLabel) {
- if (modeName == currentAppRules) {
- return Text(modeLabel + _(" (Default)"));
+ if (modeName == realMode) {
+ return Text(">> " + modeLabel);
} else {
- return Text(modeLabel);
+ return Text(" " + modeLabel);
}
};
- candidateList->append(std::make_unique(Text(_("App name detected by fcitx5: ") + currentConfigureApp_)));
- candidateList->append(std::make_unique(getLabel(VMKMode::VMKSmooth, _("1. Fake backspace by Uinput (smooth)"))));
- candidateList->append(std::make_unique(getLabel(VMKMode::VMK1, _("2. Fake backspace by Uinput"))));
- candidateList->append(std::make_unique(getLabel(VMKMode::VMK1HC, _("3. Fake backspace by Uinput for wine apps"))));
- candidateList->append(std::make_unique(getLabel(VMKMode::VMK2, _("4. Surrounding Text"))));
- candidateList->append(std::make_unique(getLabel(VMKMode::Preedit, _("5. Preedit"))));
- candidateList->append(std::make_unique(Text(_("6. Emoji mode"))));
- candidateList->append(std::make_unique(getLabel(VMKMode::Off, "7. OFF - Disable Input Method")));
- candidateList->append(std::make_unique(Text(_("8. Remove app settings"))));
- candidateList->append(std::make_unique(Text(_("`. Close menu and type `"))));
+ // Helper lambda: Cleanup after mode selection (reset UI and commit pending text)
+ auto cleanup = [this](InputContext* ic) {
+ isSelectingAppMode_ = false;
+ ic->inputPanel().reset();
+ ic->updateUserInterface(UserInterfaceComponent::InputPanel);
+ auto state = ic->propertyFor(&factory_);
+ state->reset(); // This will commit any pending preedit text
+ };
+
+ // Helper lambda: Create callback to apply selected mode and save app-specific settings
+ // Note: Emoji mode is transient (not saved to appRules), user must explicitly
+ // select it each time they open the menu
+ auto applyMode = [this, cleanup](VMKMode mode) {
+ return [this, mode, cleanup](InputContext* ic) {
+ if (mode != VMKMode::Emoji) {
+ appRules_[currentConfigureApp_] = mode;
+ saveAppRules();
+ }
+
+ setMode(mode, ic);
+ cleanup(ic);
+ };
+ };
+
+ // Build candidate list for app mode menu
+ // Structure: Header + 8 selectable items (4 VMK modes + 4 special options)
+ candidateList->append(std::make_unique(Text(_("App: ") + currentConfigureApp_)));
+ candidateList->append(std::make_unique(getLabel(VMKMode::VMKSmooth, _("[1] Uinput (smooth)")), applyMode(VMKMode::VMKSmooth)));
+ candidateList->append(std::make_unique(getLabel(VMKMode::VMK1, _("[2] Uinput (Slow)")), applyMode(VMKMode::VMK1)));
+ candidateList->append(std::make_unique(getLabel(VMKMode::VMK1HC, _("[3] Uinput (Hardcore)")), applyMode(VMKMode::VMK1HC)));
+ candidateList->append(std::make_unique(getLabel(VMKMode::VMK2, _("[4] Surrounding Text")), applyMode(VMKMode::VMK2)));
+ candidateList->append(std::make_unique(getLabel(VMKMode::Preedit, _("[q] Preedit")), applyMode(VMKMode::Preedit)));
+ candidateList->append(std::make_unique(getLabel(VMKMode::Emoji, _("[w] Emoji Picker")), applyMode(VMKMode::Emoji)));
+ candidateList->append(std::make_unique(getLabel(VMKMode::Off, _("[e] OFF")), applyMode(VMKMode::Off)));
+
+ candidateList->append(std::make_unique(Text(_("[r] Remove App Settings")), [this, cleanup](InputContext* ic) {
+ if (appRules_.count(currentConfigureApp_)) {
+ appRules_.erase(currentConfigureApp_);
+ saveAppRules();
+ }
+ cleanup(ic);
+ }));
+
+ candidateList->append(std::make_unique(Text(_("[`] Type `")), [cleanup](InputContext* ic) {
+ cleanup(ic);
+ ic->commitString("`");
+ }));
+
+ // Set initial cursor position to highlight current active mode
+ // Index mapping: 1=VMKSmooth, 2=VMK1, 3=VMK1HC, 4=VMK2, 5=Preedit, 6=Emoji, 7=Off
+ int selectedIndex = 1;
+ switch (realMode) {
+ case VMKMode::VMKSmooth: selectedIndex = 1; break;
+ case VMKMode::VMK1: selectedIndex = 2; break;
+ case VMKMode::VMK1HC: selectedIndex = 3; break;
+ case VMKMode::VMK2: selectedIndex = 4; break;
+ case VMKMode::Preedit: selectedIndex = 5; break;
+ case VMKMode::Emoji: selectedIndex = 6; break;
+ case VMKMode::Off: selectedIndex = 7; break;
+ default: selectedIndex = 1; break;
+ }
+ candidateList->setGlobalCursorIndex(selectedIndex);
ic->inputPanel().reset();
ic->inputPanel().setCandidateList(std::move(candidateList));
ic->updateUserInterface(UserInterfaceComponent::InputPanel);
}
+
+ void vmkEngine::setMode(VMKMode mode, InputContext* ic) {
+ realMode = mode;
+ if (ic) {
+ ic->updateUserInterface(UserInterfaceComponent::StatusArea);
+ }
+ }
+
+ std::string vmkEngine::overrideIcon(const fcitx::InputMethodEntry& /*entry*/) {
+ switch (realMode) {
+ case VMKMode::Off: return "fcitx-vmk-off";
+ case VMKMode::Emoji: return "fcitx-vmk-emoji";
+ default: return {};
+ }
+ }
} // namespace fcitx
FCITX_ADDON_FACTORY(fcitx::vmkFactory)
@@ -1921,4 +2154,4 @@ int compareAndSplitStrings(const std::string& A, const std::string& B, std::stri
deletedPart.assign(ptrA, endA);
addedPart.assign(ptrB, endB);
return (deletedPart.empty() && addedPart.empty()) ? 1 : 2;
-}
\ No newline at end of file
+}
diff --git a/src/vmk.h b/src/vmk.h
index ddad168..0fb4290 100644
--- a/src/vmk.h
+++ b/src/vmk.h
@@ -111,6 +111,8 @@ namespace fcitx {
std::string subMode(const fcitx::InputMethodEntry& entry, fcitx::InputContext& inputContext) override;
+ std::string overrideIcon(const fcitx::InputMethodEntry& entry) override;
+
const auto& config() const {
return config_;
}
@@ -146,6 +148,7 @@ namespace fcitx {
void updateAutoNonVnRestoreAction(InputContext* ic);
void updateModernStyleAction(InputContext* ic);
void updateFreeMarkingAction(InputContext* ic);
+ void updateFixVmk1WithAckAction(InputContext* ic);
void updateInputMethodAction(InputContext* ic);
void updateCharsetAction(InputContext* ic);
void populateConfig();
@@ -157,6 +160,7 @@ namespace fcitx {
EmojiLoader& emojiLoader() {
return emojiLoader_;
}
+ void setMode(VMKMode mode, InputContext* ic);
private:
Instance* instance_;
@@ -185,6 +189,7 @@ namespace fcitx {
std::unique_ptr autoNonVnRestoreAction_;
std::unique_ptr modernStyleAction_;
std::unique_ptr freeMarkingAction_;
+ std::unique_ptr fixVmk1WithAckAction_;
std::vector connections_;
CGoObject dictionary_;
// ibus-bamboo mode save/load