Areca IME - Technical Architecture Specification
Tài liệu tả chi tiết kiến trúc kỹ thuật, luồng xử lý dữ liệu và sơ đồ tuần tự (Sequence Diagrams) của bộ gõ tiếng Việt Areca IME trên hệ điều hành Linux (Fcitx5 / Wayland).
1. Tổng quan hệ thống
Areca IME là bộ gõ tiếng Việt dành cho Linux dưới dạng mô-đun mở rộng (addon) của Fcitx5, được viết bằng C++20. Areca tích hợp trực tiếp thư viện core bamboo-core để xử lý quy tắc gõ tiếng Việt (Telex, VNI, VIQR, v.v.), đồng thời tự quản lý luồng sự kiện phím bất đồng bộ và cơ chế chỉnh sửa lại văn bản đã hiển thị.
Fcitx5 Key Event
├──► Idle first key: Bamboo → unchanged → event chưa accept
│ (Các text key còn lại: filterAndAccept)
▼
KeyQueue (FIFO Queue)
│ (Single-flight processing)
▼
InputScheduler ──► BambooEngineAdapter ──► BambooResult
│
deleteCount == 0 ───┼──► commitString
│
deleteCount > 0 ───┘
│
ReliabilityChecker & Policy
│
┌─────────────────────────────┼─────────────────────────────┐
▼ ▼ ▼
SurroundingTextBackend UinputShiftSelectBackend ForwardBackspaceBackend
(deleteSurroundingText) (uinput Shift+Left bôi đen) (forwardKey Backspace × N)
2. Các thành phần chính (System Components)
| Thành phần | Trách nhiệm chính |
|---|---|
ArecaEngine |
Quản lý vòng đời Fcitx5 addon, dispatch sự kiện theo chế độ gõ (PresentationMode) và lưu cache Reliability Verdict cho từng InputContext. |
InputModeHandler |
Interface trừu tượng xử lý sự kiện phím cho các chế độ gõ. |
RewriteModeHandler |
Quản lý chế độ Rewrite (gõ trực tiếp); lọc phím và nạp phím vào InputScheduler. |
PreeditModeHandler |
Quản lý chế độ Preedit (gõ qua gạch chân composition); xử lý đồng bộ trực tiếp với Bamboo mà không qua scheduler hay backend rewrite. |
RedirectModeHandler |
Chế độ chuyển tiếp phím gốc (dùng cho ô nhập mật khẩu); bypass toàn bộ Bamboo engine và scheduler. |
KeyQueue |
Hàng đợi FIFO lưu trữ sự kiện phím gốc, Unicode codepoint, UTF-8 sequence và watch reference tới InputContext. |
InputScheduler |
Bộ điều phối sự kiện đơn luồng (Single-flight Scheduler), quản lý ranh giới giao dịch (Transaction Barrier) và timer sau commit. |
BambooEngineAdapter |
Lớp cầu nối C++/Go gọi thư viện bamboo-core, biến đổi kết quả gõ thành BambooResult (bao gồm deleteCount và commitText). |
ReliabilityChecker |
Kiểm tra khả năng tương thích SurroundingText của ứng dụng ở lần gõ đầu tiên và quyết định chiến lược rewrite. |
RewriteBackend |
Interface trừu tượng định nghĩa phương thức thực thi một RewritePlan. |
SurroundingTextBackend |
Thực thi xóa văn bản qua API Fcitx deleteSurroundingText() và chèn chữ mới qua commitString(), tích hợp AdaptiveWait và kiểm tra xác nhận qua sự kiện SurroundingTextUpdated. |
SurroundingTextV2Backend |
Thực thi xóa văn bản từng ký tự tuần tự (Incremental Delete) cho các trình soạn thảo không ổn định với xóa gộp, tích hợp AdaptiveWait. |
UinputShiftSelectBackend |
Phát Shift down và Left × N+1 qua /dev/uinput; Fcitx forward N lần đầu, lọc lần xác nhận cuối, phát Shift up, chờ mặc định 20 ms rồi commit. Timeout xác nhận là 20 ms. |
UinputBackspaceBackend |
Phát đúng KEY_BACKSPACE × N qua /dev/uinput và forward mọi event Backspace quay lại Fcitx. Bộ đếm ACK chỉ là mốc tương đối để bắt đầu settling wait; timer phát phím độc lập với timer commit và commit luôn đợi phát hết N phím. Nếu chưa đủ ACK, timeout cấu hình (mặc định 150 ms) vẫn chuyển sang settling wait. |
ForwardBackspaceBackend |
Phát phím Backspace tuần tự qua InputContext::forwardKey(), áp dụng settling delay thích ứng và commit chữ mới. |
3. Quy tắc Invariant cốt lõi
- Thứ tự FIFO bảo toàn: Phím đi qua queue giữ thứ tự FIFO. Phím đầu chỉ được xử lý trực tiếp khi scheduler rảnh và không còn barrier bảo vệ.
- Xử lý đơn luồng (Single-flight): Chỉ một phím duy nhất được Bamboo xử lý tại một thời điểm. Phím mới đến trong lúc pipeline đang bận sẽ chờ ở
KeyQueue. - Ranh giới giao dịch (Transaction Barrier): Khi một Backend Rewrite đang chạy (xóa/bôi đen/commit), scheduler giữ trạng thái bận và không cho phím sau chen ngang.
- Không sleep trên Main Thread: Mọi khoảng chờ (delay) giữa các phím bấm ảo và post-commit đều sử dụng timer bất đồng bộ trên EventLoop của Fcitx.
4. Chính sách lựa chọn Backend (selectRewriteBackend)
Khi Bamboo trả về kết quả yêu cầu xóa ký tự (deleteCount > 0), ArecaEngine lựa chọn Rewrite Backend theo thứ tự ưu tiên sau:
sequenceDiagram
autonumber
participant S as InputScheduler
participant E as ArecaEngine
participant R as ReliabilityChecker
participant IC as InputContext
S->>E: selectRewriteBackend(inputContext, result)
E->>R: evaluate(inputContext, shownText, verdict)
R-->>E: ReliabilityDecision
E->>IC: surroundingText() & check (cursor != anchor)
alt plasmashell OR VS Code embedded terminal
E-->>S: Return ForwardBackspaceBackend
else browserAutocomplete OR (surrounding.isValid & cursor != anchor)
E-->>S: Return ForwardBackspaceBackend (+1 extra backspace)
else App Override: mode != Auto
alt Mode: SurroundingText
E-->>S: Return Surrounding / Shift-Select / ForwardKey
else Mode: Shift+Left (uinput)
E-->>S: Return UinputShiftSelectBackend
else Mode: Native (Libei/XTest)
E-->>S: Return NativeXTestBackend (fallback uinput/forward)
else Mode: Uinput Backspace
E-->>S: Return UinputBackspaceBackend (fallback native/forward)
else Mode: ForwardKey
E-->>S: Return ForwardBackspaceBackend
end
else decision.useSurrounding & UseUinputShiftSelectForBrowser & isBrowser & uinputAvailable
E-->>S: Return UinputShiftSelectBackend
else decision.useSurrounding
E-->>S: Return SurroundingTextBackend
else DBus terminal/unknown & uinputAvailable
E-->>S: Return UinputBackspaceBackend
else ForceUinput & uinputAvailable
E-->>S: Return UinputBackspaceBackend
else Fallback
E-->>S: Return ForwardBackspaceBackend
end
IMPORTANT
Cơ chế bảo vệ bôi đen (Selection Guard): Nếu phát hiện trong ô nhập liệu đang có văn bản bôi đen sẵn (
cursor != anchor) hoặc có gợi ý Autocomplete của trình duyệt, hệ thống tự động loại bỏUinputShiftSelectBackendvà chuyển sangForwardBackspaceBackendvới+1phím Backspace bổ sung để xóa sạch vùng bôi đen một cách an toàn.
5. Chi tiết sơ đồ tuần tự (Sequence Diagrams) các Backend
5.1. SurroundingTextBackend Sequence Diagram
A. Luồng Probe xác thực độ tin cậy và kiểm tra sự kiện SurroundingTextUpdated
sequenceDiagram
autonumber
participant S as InputScheduler
participant E as ArecaEngine
participant B as SurroundingTextBackend
participant IC as InputContext
participant EL as EventLoop
participant FB as FallbackBackend
S->>B: apply(inputContext, plan[probe=true], onDone)
B->>B: Lưu snapshot gốc và tính snapshot kỳ vọng
B->>IC: deleteSurroundingText(-count, count)
B->>EL: addTimeEvent(ProbeDeadline = 150ms)
alt Trường hợp 1: Nhận sự kiện InputContextSurroundingTextUpdated trước 150ms
IC-->>E: Sự kiện InputContextSurroundingTextUpdated
E->>B: notifySurroundingTextUpdated(inputContext, fromUpdateEvent=true)
B->>B: surroundingProbeMatches: kiểm tra ngữ nghĩa text trước con trỏ
Note over B: Xác thực khớp: con trỏ bằng anchor, không còn text cũ, khớp text kỳ vọng
B->>EL: timer.reset (Hủy deadline 150ms)
B->>E: onProbeSucceeded: probePending = false, reliable = true
B->>EL: Chờ effectiveWaitMs (AdaptiveWait)
EL-->>B: commitPost_.schedule (Pha post của EventLoop)
B->>IC: commitString(commitText)
B->>S: onDone(transactionId, Succeeded)
else Trường hợp 2: Hết hạn 150ms không có sự kiện hợp lệ (Timeout)
EL-->>B: Timer ProbeDeadline 150ms kích hoạt
B->>B: notifySurroundingTextUpdated(inputContext, fromUpdateEvent=false)
Note over B: Kiểm tra lần cuối snapshot hiện tại trong queue
alt Snapshot vẫn không khớp
B->>E: onProbeFailed: known = true, reliable = false, probePending = false
B->>FB: apply(inputContext, plan[probe=false], onDone)
Note over B,FB: Chuyển sang ShiftSelect nếu là trình duyệt hoặc Backspace
end
end
B. Luồng Rewrite thông thường với AdaptiveWait và EventLoopPostTask
sequenceDiagram
autonumber
participant S as InputScheduler
participant B as SurroundingTextBackend
participant IC as InputContext
participant AW as AdaptiveWait
participant EL as EventLoop
participant PT as EventLoopPostTask
S->>B: apply(inputContext, plan[probe=false], onDone)
alt backspaceCount > 0
B->>AW: beginTransaction
B->>IC: deleteSurroundingText(-count, count)
B->>B: updateSurroundingCacheAfterDelete
B->>AW: effectiveWaitMs(waitMs), effectiveExtraWaitMs(waitMs)
B->>EL: addTimeEvent(effectiveWaitMs)
EL-->>B: Timer callback (observeTimer ghi nhận độ trễ thực tế)
B->>PT: commitPost_.schedule
PT-->>B: Pha post của EventLoop
B->>B: commitAfterAdaptiveWait: kiểm tra nếu trễ tăng thì giãn tiếp
B->>IC: commitString(commitText)
B->>B: updateSurroundingCacheAfterCommit
B->>AW: completeTransaction (giảm dần độ trễ khi hệ thống ổn định)
B->>S: onDone(transactionId, Succeeded)
else backspaceCount == 0
B->>IC: commitString(commitText)
B->>B: updateSurroundingCacheAfterCommit
B->>S: onDone(transactionId, Succeeded)
end
5.2. UinputShiftSelectBackend Sequence Diagram
sequenceDiagram
autonumber
participant S as InputScheduler
participant B as UinputShiftSelectBackend
participant DEV as UinputDevice (/dev/uinput)
participant EL as EventLoop
participant IC as InputContext
S->>B: apply(inputContext, plan, onDone)
B->>DEV: sendKeyEvent(KEY_LEFTSHIFT, 1) (Shift DOWN)
B->>EL: addTimeEvent(UinputShiftSelectDelayMs = 1ms)
loop N+1 lần
EL-->>B: Timer callback
B->>DEV: sendKeyEvent(KEY_LEFT, 1) -> (KEY_LEFT, 0)
B->>EL: addTimeEvent(UinputShiftSelectDelayMs = 1ms)
end
B->>EL: addTimeEvent(AckTimeout = 20ms)
alt Left thứ N+1 về trước timeout
DEV-->>IC: N Left đầu quay lại Fcitx
IC-->>B: forward N Left
DEV-->>IC: Left thứ N+1 quay lại Fcitx
IC-->>B: filter Left dư và hủy timeout
else Timeout về trước
EL-->>B: Ack timeout
B->>B: clear left tracker
end
B->>DEV: sendKeyEvent(KEY_LEFTSHIFT, 0) (Shift UP)
B->>EL: addTimeEvent(AfterUinputShiftSelectWaitMs = 20ms mặc định)
EL-->>B: Timer callback (Settling wait hoàn tất)
loop Lần lượt từng ký tự UTF-8 trong commitText
B->>IC: commitString(utf8_char)
B->>EL: addTimeEvent(5ms)
EL-->>B: Timer callback
end
B->>B: finishTransaction()
B->>S: onDone(transactionId)
5.3. UinputBackspaceBackend Sequence Diagram
sequenceDiagram
autonumber
participant S as InputScheduler
participant B as UinputBackspaceBackend
participant DEV as UinputDevice (/dev/uinput)
participant EL as EventLoop
participant IC as InputContext
S->>B: apply(inputContext, plan, onDone)
loop N lần
B->>DEV: sendKeyEvent(KEY_BACKSPACE, 1) -> (KEY_BACKSPACE, 0)
B->>EL: sendTimer.addTimeEvent(BackspaceDelayMs)
EL-->>B: Timer callback
end
DEV-->>IC: Backspace quay lại Fcitx
IC-->>B: forward mọi press/release và tăng seen
alt seen đạt N
B->>EL: commitTimer.addTimeEvent(AfterBackspaceWaitMs)
else Chưa đạt N sau phím phát cuối
B->>EL: commitTimer.addTimeEvent(AckTimeoutMs)
EL-->>B: Ack timeout
B->>EL: commitTimer.addTimeEvent(AfterBackspaceWaitMs)
end
EL-->>B: Settling wait hoàn tất
alt Vẫn còn Backspace chưa phát
B->>B: Đánh dấu wait đã hết
B->>EL: Phát hết rồi chờ lại AfterBackspaceWaitMs
EL-->>B: Settling wait sau phím cuối hoàn tất
end
B->>IC: commitString(commitText)
B->>B: clearPending()
B->>S: onDone(transactionId)
5.4. ForwardBackspaceBackend Sequence Diagram
sequenceDiagram
autonumber
participant S as InputScheduler
participant B as ForwardBackspaceBackend
participant IC as InputContext
participant EL as EventLoop
S->>B: apply(inputContext, plan, onDone)
loop N = backspaceCount lần
B->>IC: forwardKey(Backspace press/release)
B->>EL: addTimeEvent(BackspaceDelayMs)
EL-->>B: Timer callback
end
B->>EL: addTimeEvent(AfterBackspaceWaitMs)
EL-->>B: Timer callback (Settling wait hoàn tất)
B->>IC: commitString(commitText)
B->>B: clearPending()
B->>S: onDone(transactionId)
6. Đề xuất cải tiến tương lai (Future Architecture Extensions)
Đề xuất SurroundingTextBackend xóa từng ký tự (Incremental Delete)
NOTE
Bối cảnh: Hiện tại
SurroundingTextBackendphát lệnh xóa gộpdeleteSurroundingText(-N, N)trong 1 lần gọi RPC. Bộ máy Firefox Gecko hoặc một số Web Editor (Monaco, CodeMirror) xử lý câu lệnh xóa gộp-Nký tự không ổn định khi phía sau con trỏ đang có các ký tự đặc biệt (như dấu ngoặc}}hoặc thẻ HTML).
sequenceDiagram
autonumber
participant S as InputScheduler
participant B as SurroundingTextBackend (Đề xuất)
participant IC as InputContext
participant EL as EventLoop
S->>B: apply(inputContext, plan, onDone)
alt backspaceCount > 0
loop N = backspaceCount lần
B->>IC: deleteSurroundingText(-1, 1)
B->>B: updateSurroundingCacheAfterDelete(-1, 1)
B->>EL: addTimeEvent(DeleteDelayMs = 1ms)
EL-->>B: Timer callback
end
B->>EL: addTimeEvent(WaitMs = 3ms)
EL-->>B: Timer callback (Settling wait hoàn tất)
B->>IC: commitString(commitText)
B->>B: updateSurroundingCacheAfterCommit(...)
B->>S: onDone(transactionId)
else backspaceCount == 0
B->>IC: commitString(commitText)
B->>S: onDone(transactionId)
end
7. Bảng thông số cấu hình Timing (Advanced Timing Configuration)
| Tên tùy chọn trong Config | Ý nghĩa & Mô tả | Giá trị mặc định |
|---|---|---|
BackspaceDelayMs |
Delay giữa các phím Backspace ảo (ms) | 1 ms |
AfterBackspaceWaitMs |
Thời gian chờ sau phím Backspace cuối (ms) | 10 ms |
WaylandAfterBackspaceWaitMs |
Thời gian chờ sau phím Backspace cuối trên Wayland (ms) | 3 ms |
XimAfterBackspaceWaitMs |
Thời gian chờ sau phím Backspace cuối trên XIM (ms) | 10 ms |
Fcitx4AfterBackspaceWaitMs |
Thời gian chờ sau phím Backspace cuối trên Fcitx4 (ms) | 10 ms |
DbusAfterBackspaceWaitMs |
Thời gian chờ sau phím Backspace cuối trên DBus (ms) | 10 ms |
UinputShiftSelectDelayMs |
Delay giữa các phím uinput Shift+Left (ms) | 1 ms |
AfterUinputShiftSelectWaitMs |
Thời gian chờ sau khi thả Shift uinput (ms) | 20 ms |
WaylandAfterUinputShiftSelectWaitMs |
Thời gian chờ sau khi thả Shift uinput Wayland (ms) | 20 ms |
XimAfterUinputShiftSelectWaitMs |
Thời gian chờ sau khi thả Shift uinput XIM (ms) | 20 ms |
Fcitx4AfterUinputShiftSelectWaitMs |
Thời gian chờ sau khi thả Shift uinput Fcitx4 (ms) | 20 ms |
DbusAfterUinputShiftSelectWaitMs |
Thời gian chờ sau khi thả Shift uinput DBus (ms) | 20 ms |
PostCommitDelayMs |
Delay bảo vệ sau mỗi lượt commit (ms) | 20 ms |
PreciseTiming |
Sử dụng timer độ chính xác cao (1µs) | True |
Tùy chọn nhập liệu
Trong Cấu hình nâng cao conf/areca-advanced.conf, EnableMouseTracking
và ForwardFirstCharacter đều mặc định True. Mouse tracking áp dụng
cho mọi ứng dụng. Thay đổi qua giao diện có hiệu lực ngay; sửa file bằng tay cần reload cấu hình.
- Tắt
EnableMouseTracking: hủy watcher, pipe, pending click và process helper. Bật lại tạo tracker mới. - Tắt
ForwardFirstCharacter: bỏ quahandleIdleKey(), text key dùng luồng accept/enqueue cũ. Tính năng này không tạo thread/process riêng.
Chưa có benchmark so sánh hiệu năng hai chế độ.
Phím đầu khi Rewrite đang rảnh
Một số editor web cần nhận sự kiện phím qua frontend để mở chế độ soạn thảo.
Khi ForwardFirstCharacter=True, trước filterAndAccept(), RewriteModeHandler thử
InputScheduler::handleIdleKey() khi không chờ Backspace release và phím không
bị đổi bởi tự viết hoa. Scheduler chỉ nhận nhánh này khi Bamboo không giữ text,
không processing, không rewrite pending, queue rỗng, không stalled và đã qua
khoảng bảo vệ reset sau commit (hiện là 20 ms).
Bamboo vẫn xử lý phím đúng một lần. Nếu deleteCount == 0, commitText bằng
UTF-8 đầu vào và không mở rộng macro, addon để event chưa filter/accept cho
frontend xử lý. Trạng thái Bamboo được giữ để phím sau vẫn ghép dấu, ví dụ
a rồi s thành á. Nhánh này không gọi commitString(), không phát cặp
forwardKey() giả, không tự cập nhật surrounding cache và không tạo barrier
sau commit vì addon chưa thực hiện commit.
KeyEvent::forward() trong API Fcitx đang dùng là getter; việc để event chưa
filter/accept mới là điều cho phép phím đi tiếp. Cách frontend giao event đến
trang web vẫn cần kiểm tra trên từng môi trường thực tế.
Nếu Bamboo đổi đầu ra, scheduler accept event và áp dụng ngay kết quả đã tính,
không enqueue lại để tránh xử lý cùng phím hai lần. Nếu chưa đủ điều kiện,
handleIdleKey() trả false mà không sửa engine/event; caller dùng FIFO như cũ.
Phím bị tự viết hoa cũng dùng luồng commit để giữ ký tự đã biến đổi.
Theo dõi click và reset composition
WindowFocusTracker là thread đọc AT-SPI trong Fcitx. MouseClickTracker
quản lý process con riêng areca-mouse-monitor; helper dùng libinput/udev trên
XDG_SEAT (mặc định seat0) và cùng quyền user với Fcitx. Không cần socket
server hoặc thread nhận chuột trong addon.
libinput/udev → areca-mouse-monitor → stdout pipe riêng (R/C)
→ MouseClickTracker trên event loop Fcitx → pending click
→ ArecaEngine::keyEvent → kiểm tra scheduler → reset handler
Rbáo context libinput đã khởi tạo, không đảm bảo có thiết bị đọc được.Cbáo nút chuột được nhấn, không phân biệt nút trái/phải/giữa. Helper bật tap-to-click trong context libinput riêng; di chuyển, cuộn và nhả nút không gửi reset. udev hỗ trợ thiết bị được cắm thêm sau khi helper chạy.- Pipe nonblocking tránh chặn event loop/dispatch; nhiều click gộp thành một cờ reset. stderr dành cho log, không lẫn vào protocol stdout.
- Callback pipe chỉ đánh dấu. Trước phím nhấn tiếp theo, addon đọc thêm pipe để lấy click đã đến nhưng callback chưa chạy. Nếu scheduler đang bảo vệ rewrite, cờ vẫn được giữ cho phím sau; nếu được phép thì reset handler đang active, xử lý cache verdict theo lifecycle và xóa cờ. Redirect không có composition để reset. Đây không phải reset tức thời ngay khi click.
- Activation thử khởi động lại helper đã mất kết nối và bỏ click cũ đã nhận.
Khi pipe đóng, watcher bị tắt; pending click đã nhận vẫn được giữ. Hủy tracker
sẽ đóng pipe, kết thúc và thu hồi process con bằng
waitpid(). Helper cũng theo dõi pipe để thoát khi Fcitx mất kết nối, kể cả không có click mới.
Helper được cài vào CMAKE_INSTALL_LIBEXECDIR; addon nhúng đường dẫn tuyệt đối
ứng với CMAKE_INSTALL_PREFIX. Build cần libinput và libudev. Rule
70-areca-pointer.rules cấp uaccess cho chuột/touchpad của phiên local đang
active, trước 73-seat-late.rules. Không có ACL/quyền đọc thiết bị thì tính năng
click không hoạt động; nhập liệu Fcitx bình thường vẫn hoạt động. Xem
Debug mouse tracker để kiểm tra cài đặt và log.