Sen Việt Sen Việt
Báo cáo Nghiên cứu & Thực nghiệm · v1.1 · Tác giả: Trần Phát Đạt (Steve Dat) · Lõi: VieLotusCore (Pure Swift 6)

Kiến trúc bộ gõ tiếng Việt nguyên bản không dùng Event Tap và bài toán phân giải va chạm song ngữ đối xứng

Báo cáo này trình bày các nhận định kỹ thuật và kết quả đo kiểm thực nghiệm của dự án Sen Việt (VieLotusIM). Chúng tôi phân tích nguyên nhân gốc rễ khiến phương pháp chặn phím tầng thấp (CGEventTap / tiêm phím xóa giả lập) gây nuốt chữ trên trình duyệt hiện đại, đề xuất kiến trúc đồng bộ trạng thái nguyên bản qua InputMethodKit / TSF / IBus, và thiết lập chuẩn đánh giá đối xứng hai chiều cho bài toán gõ trộn tiếng Việt – tiếng Anh trên tập dữ liệu 9.091 ca kiểm thử.

100.00%
9.091 / 9.091 ca chính
Độ chính xác tổng thể trên 5 nhóm kiểm thử có ngữ cảnh (Telex & VNI).
97.33%
365 / 375 đối xứng hai chiều
Chỉ số ambiguous_both_sides: Đúng tiếng Anh khi có ngữ cảnh VÀ giữ đúng âm tiết Việt khi gõ độc lập.
182 / 182
100.00% đường gõ duy nhất
Bảo toàn tuyệt đối (0 mất mát) các âm tiết tiếng Việt chỉ có một cách gõ phím (whitelist_safe = never).
< 200 µs
Độ trễ đuôi p99 (Telex)
Thông lượng > 40.000 phím/giây qua 250.000 phím liên tục, 0 rò rỉ bộ nhớ, 0 quyền Accessibility.

§1. Nhận định cốt lõi: Vì sao kiến trúc CGEventTap truyền thống gặp giới hạn trên hệ điều hành hiện đại?

Trong suốt hai thập kỷ qua, phần lớn các bộ gõ tiếng Việt bên thứ ba trên macOS đều lựa chọn kiến trúc chặn sự kiện bàn phím tầng thấp (CGEventTap). Khi người dùng gõ chữ t-i-e-e, chữ tiê chưa được tạo ra trong vùng đệm hệ thống; thay vào đó, chữ tie đã lọt vào ứng dụng, sau đó bộ gõ phát sinh một chuỗi sự kiện Backspace giả lập qua CGEventSource để xóa chữ cũ rồi bắn chuỗi Unicode tiê mới vào luồng sự kiện cửa sổ.

Cách tiếp cận này từng hoạt động tốt trên các khung soạn thảo văn bản đồng bộ thế hệ cũ, nhưng bộc lộ 4 xung đột cấu trúc không thể vá triệt để trên macOS hiện đại và các ứng dụng kiến trúc bất đồng bộ (Chromium, Electron, React Virtual DOM, IDE hiện đại):

Khía cạnh kỹ thuật Bộ gõ dựa trên CGEventTap (Tiêm Backspace) Sen Việt (Pure InputMethodKit / TSF / IBus)
Đồng bộ hóa giao diện bất đồng bộ (React / Electron / URL Bar) Phím Backspace giả lập chạy đua (race condition) với vòng lặp đối chiếu Virtual DOM hoặc gợi ý tự động trên thanh địa chỉ, gây hiện tượng nhân đôi âm tiết (t-ti-tiế-tiếng) hoặc nhảy con trỏ. Thay thế nguyên tử qua insertText:replacementRange: (UTF-16) trên AppKit hoặc phát đúng vòng đời W3C compositionstart / compositionupdate trên trình duyệt.
Quyền hệ thống & lỗi câm TCC (csreq mismatch) Bắt buộc đòi quyền Accessibility (Trợ năng) toàn hệ thống. Khi cập nhật phiên bản mới, chữ ký mã (Designated Requirement) thay đổi khiến macOS âm thầm chặn sự kiện dù nút bật trong Cài đặt vẫn sáng. 0 quyền Accessibility, 0 quyền Input Monitoring. Vận hành đúng giao thức bàn phím chính thống của hệ điều hành.
Lịch sử hoàn tác (Undo Stack Cmd+Z) Mỗi lần tiêm phím xóa và chèn lại chữ sẽ làm vỡ ngăn xếp hoàn tác của trình soạn thảo thành từng mảnh ký tự rời rạc. Bảo toàn nguyên vẹn ngăn xếp hoàn tác theo từng từ của ứng dụng đích.
Ô nhập mật khẩu (Secure Event Input) Bị vô hiệu hóa đột ngột hoặc gây kẹt trạng thái khi trình duyệt/Terminal bật EnableSecureEventInput(). Kiểm tra chủ động IsSecureEventInputEnabled() và tự động chuyển sang passthrough an toàn, tuyệt đối không lưu vết phím.

§2. Mô phỏng thực nghiệm: So sánh phản ứng máy trạng thái trên các ca gõ thực tế

Chọn một trong 6 kịch bản kiểm thử thực tế dưới đây để quan sát sự khác biệt giữa cơ chế tiêm phím xóa truyền thống, bộ gõ mặc định Simple Telex, và máy trạng thái InputSessionManager + SmartBilingualDetector của Sen Việt:

Bảng đối chiếu chuỗi phím & trạng thái đầu ra
Nhấn vào từng chuỗi phím để xem cách phân giải âm vị, dấu thanh và ngữ cảnh song ngữ.
Chuỗi phím đầu vào: t-r-u-o-n-w-g-f-s-w-o + Space
Ngữ cảnh: độc lập (SEN Flex)
Bộ gõ CGEventTap cũ
trườngwơ / lỗi dấu
Bắn nhiều đợt Backspace giả lập; dễ rớt chữ trên thanh địa chỉ Chrome hoặc rò phím phụ âm cuối khi đổi dấu liên tiếp.
Simple Telex mặc định
truonwgfswo
Yêu cầu gõ dấu ngay sau nguyên âm chuẩn; gõ tự do vượt qua phụ âm cuối hoặc đổi ý dấu thanh nhiều lần sẽ làm vỡ âm tiết.
Sen Việt (VieLotusCore)
trưởng
SEN Flex hợp nhất nguyên âm đôi ươ, cho phép ghi đè dấu huyền (f) sang sắc (s)/hỏi, loại bỏ phím thừa (No Ghost Keys) và thay thế nguyên tử UTF-16.

§3. Nghịch lý va chạm song ngữ Anh – Việt và nguyên lý công bố đối xứng

Khi người Việt làm việc hàng ngày, hàng nghìn từ tiếng Anh phổ biến chứa các chữ cái trùng khớp với phím điều khiển Telex: s (dấu sắc), f (dấu huyền), r (dấu hỏi), x (dấu ngã), j (dấu nặng), w (ư/ơ/ă), và cặp nguyên âm oo, ee, aa. Ví dụ: gõ test bị biến thành tết, toast thành toát, floor thành flô, restrict bị biến dạng dấu hỏi/sắc.

Tuy nhiên, một nhận định khoa học quan trọng của nghiên cứu này là: Mọi tuyên bố "đạt 100% khôi phục từ tiếng Anh" nếu chỉ đo một chiều đều là phóng đại năng lực của bộ gõ. Nếu một bộ gõ chỉ đơn giản nhét toàn bộ từ điển tiếng Anh vào danh sách trắng (whitelist) vô điều kiện, nó sẽ đạt 100% ở phía tiếng Anh nhưng lại phá hủy khả năng gõ các âm tiết tiếng Việt đồng hình (chẳng hạn người dùng không bao giờ gõ được chữ tết bằng test, chữ đất bằng ddats, hay chữ lốt bằng loots khi đứng đầu câu).

Quy tắc công bố đối xứng bắt buộc của Sen Việt: Kết quả 100.00% (9.091 / 9.091) trên bộ kiểm thử chính luôn phải được công bố đi kèm với phép thử ngược không ngữ cảnh: 97.33% (365 / 375) ở chỉ số ambiguous_both_sides và 100.00% (182 / 182 — 0 mất mát nghiêm trọng) ở nhóm âm tiết tiếng Việt chỉ có duy nhất một đường gõ (whitelist_safe = never).

Bảng 3.1 — Ma trận kiểm thử chính (9.091 ca, có cô lập ngữ cảnh từng dòng)

Nhóm kiểm thử (Bucket) Mô tả điều kiện thực thi Đạt / Tổng Tỷ lệ Ngưỡng chốt (Ratchet)
transform Biến đổi âm tiết tiếng Việt thuần túy (Telex & SEN Flex) 400 / 400 100.00% 100.00%
keep_as_typed Từ vựng tiếng Anh không chứa chuỗi biến âm hợp lệ của tiếng Việt 362 / 362 100.00% 100.00%
cancel_keep_composed Hủy dấu / thoát mũ ở cuối từ (zz, ss, lặp ba nguyên âm aaa → aa) 67 / 67 100.00% 100.00%
restore_raw Khôi phục từ tiếng Anh bị biến dạng bởi phím dấu Telex (kiểm tra luật âm vị tắt -c, -ch, -p, -t, nguyên âm đôi bất khả và băm từ điển 64-bit) 7.213 / 7.213 100.00% 100.00%
ambiguous_needs_context Từ lưỡng nghĩa (vừa là từ tiếng Anh hợp lệ, vừa tạo thành âm tiết tiếng Việt hợp lệ) khi được mồi ngữ cảnh tiếng Anh đi trước 1.049 / 1.049 100.00% 100.00%
Tổng hợp bộ kiểm thử chính 0 xung đột thỏa mãn (Satisfiability) · 0 xung đột chính sách (Policy Coherence) 9.091 / 9.091 100.00% 100.00%

Bảng 3.2 — Ma trận kiểm thử đối xứng không ngữ cảnh (Chạy lại các ca lưỡng nghĩa với context rỗng)

Chỉ số đối xứng Ý nghĩa ngôn ngữ học & kiểm định Đạt / Tổng Tỷ lệ
vn_survives_collision 375 từ lưỡng nghĩa viết thường khi gõ độc lập không có ngữ cảnh tiếng Anh: âm tiết tiếng Việt phải được giữ lại 365 / 375 97.33%
↳ whitelist_safe = never Nhóm âm tiết tiếng Việt mà chuỗi phím đó là con đường duy nhất để gõ ra chữ Việt (không được phép mất dù chỉ 1 từ) 182 / 182 100.00% (0 lỗi)
↳ whitelist_safe = degrades_vn Nhóm âm tiết có nhiều cách gõ thay thế (ví dụ gõ kiểu Telex mở rộng) 183 / 193 94.82%
ambiguous_both_sides Chỉ số chuẩn mực kép: Ra đúng tiếng Anh khi có ngữ cảnh tiếng Anh VÀ ra đúng tiếng Việt khi gõ độc lập 365 / 375 97.33%

§4. Cơ chế trình diễn thích ứng theo đặc tính khung soạn thảo (ClientAdapter)

Một nhận định thực tế khác của nhóm phát triển là: Không có một chế độ hiển thị đơn lẻ nào tối ưu cho mọi ứng dụng trên macOS nếu tuân thủ thuần túy InputMethodKit. Các ứng dụng Cocoa/AppKit truyền thống xử lý hoàn hảo phép thay thế phạm vi trực tiếp (insertText:replacementRange:) không cần gạch chân, trong khi các trình soạn thảo web dùng React, ProseMirror, Monaco hay thanh địa chỉ Chromium lại dựa vào sự kiện compositionstart / compositionend (tức chế độ setMarkedText có gạch chân tạm thời) để không nhân đôi ký tự khi đối chiếu DOM bất đồng bộ.

Thay vì dùng CGEventTap để cưỡng bức màn hình, Sen Việt kết hợp bộ phân loại ClientAdapter, lớp bảo vệ biên độ DirectReplacementGuard (đảm bảo không bao giờ xóa lố ra ngoài phạm vi [compositionStartLocation, virtualCursorLocation]) và tập luật dữ liệu mở typing-modes.yml:

Nhóm ứng dụng Ứng dụng đại diện Chế độ trình diễn Cơ chế đồng bộ kỹ thuật
Standard AppKit / Cocoa Notes, Pages, TextEdit, Mail, Safari, Xcode, Finder, MS Office Direct Replacement Thay thế trực tiếp theo đơn vị mã UTF-16 qua insertText:replacementRange:, không gạch chân, giữ nguyên Undo stack.
Chromium, Electron & CEF Chrome, Arc, Edge, Brave, VS Code, Cursor, Slack, Notion, Obsidian, Zalo Marked Text (W3C) Đồng bộ với vòng đời W3C IME composition để triệt tiêu hoàn toàn lỗi lặp chữ (t-ti-tiế-tiếng) và nhảy con trỏ trong React/Virtual DOM.
GPU & POSIX Terminals Ghostty, Alacritty, Kitty, WezTerm, iTerm2, Terminal.app Terminal Direct / Marked Đồng bộ byte xóa PTY (\u{7F}) và độ dài UTF-16 code units, tương thích với các công cụ dòng lệnh như Claude Code, tmux, zsh.
Dynamic Fallback Ô nhập liệu tùy biến trả về NSNotFound Auto Marked Fallback Khi DirectReplacementGuard phát hiện ứng dụng đích bỏ qua replacementRange, hệ thống tự động chuyển sang setMarkedText ngay trong phiên gõ.

§5. Kiến trúc đa nền tảng (Pure Swift 6 Core) và bài toán nạp In-Process COM trên Windows 11 ARM64

Để cùng một lõi ngôn ngữ học phục vụ đồng nhất cả 5 hệ điều hành (macOS, iOS/iPadOS, Windows, Linux, Android), toàn bộ thư viện VieLotusCore được viết bằng Swift 6 thuần túy không phụ thuộc giao diện đồ họa hệ thống, và xuất giao diện cầu nối C-ABI không sao chép bộ nhớ (VieLotusCAPI, 26 hàm trong vielotus.h) cho Windows TSF và Linux IBus / Fcitx5.

Nghiên cứu tình huống kỹ thuật trên Windows 11 (x64 & ARM64): Trên Windows, một bộ gõ chuẩn Text Services Framework (TSF) là một thư viện COM In-Process (VieLotusTIP.dll) được nạp trực tiếp vào không gian địa chỉ của từng tiến trình ứng dụng (notepad.exe, explorer.exe, trình duyệt) và được đăng ký qua regsvr32.exe. Nếu VieLotusTIP.dll liên kết tĩnh (Static PE Import) vào VieLotusCAPI.dll:

1) Khi trình cài đặt gọi regsvr32.exe hoặc khi một ứng dụng có thư mục làm việc tại C:\Windows\System32 khởi tạo COM đối tượng, trình nạp PE của Windows sẽ tìm VieLotusCAPI.dll cùng chuỗi thư viện Swift Runtime (swiftCore.dll, swiftDispatch.dll, FoundationEssentials.dll) trong System32 thay vì thư mục cài đặt, gây ra lỗi RegSvr32 exit code 0x3 (ERROR_MOD_NOT_FOUND).
2) Để giải quyết triệt để ở cấp kiến trúc, VieLotusTIP.dll được biên dịch với Static MSVC Runtime (/MT) và chỉ phụ thuộc vào 4 DLL nhân Windows (KERNEL32, USER32, OLE32, ADVAPI32). Khi phiên gõ kích hoạt (ActivateEx), VieLotusTIP.dll tự xác định thư mục chứa chính nó qua GetModuleFileNameW(g_hInst, ...) và nạp động VieLotusCAPI.dll bằng cờ LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | LOAD_LIBRARY_SEARCH_DEFAULT_DIRS, đảm bảo cô lập hoàn toàn đường dẫn DLL cho cả bản x64 lẫn bản ARM64 nguyên bản.

§6. Hiệu năng độ trễ đuôi (p99 Tail Latency) & quy trình kiểm chứng độc lập (Open-Core)

Trong trải nghiệm gõ phím thời gian thực, độ trễ trung bình (mean latency) thường che giấu các nhịp khựng vi mô khi bộ phân tích ngữ cảnh hoặc bộ thu gom rác bộ nhớ hoạt động. Do đó, Sen Việt kiểm soát chất lượng bằng độ trễ đuôi phân vị thứ 99 (p99 latency) trên bài kiểm thử chịu tải 250.000 phím liên tục (VieLotusCLI --stress) chạy qua 3 luồng tải trộn lẫn:

Luồng tải kiểm thử (250.000 phím) Ngưỡng trần cho phép (Gate) Kết quả thực đo (Apple Silicon) Trạng thái bộ nhớ
Telex tiếng Việt thuần (Độ trễ p99) ≤ 500.00 µs 150 – 220 µs Ổn định < 30 MB RSS (0 rò rỉ)
VNI tiếng Việt thuần (Độ trễ p99) ≤ 300.00 µs 60 – 110 µs Ổn định < 30 MB RSS (0 rò rỉ)
Song ngữ Anh – Việt & mã nguồn (Độ trễ p99) ≤ 700.00 µs 250 – 380 µs Ổn định < 30 MB RSS (0 rò rỉ)
Thông lượng xử lý duy trì (Sustained Throughput) ≥ 25.000 phím/giây > 40.000 phím/giây Chạy 3 tiến trình độc lập, lấy kết quả thấp nhất

Tự tay kiểm chứng hộp đen trong 30 giây (Open-Core Verification)

Sen Việt được phát hành theo mô hình Open-Core: toàn bộ mã nguồn phần vỏ hệ thống (Sources/VieLotusIM, CrossPlatform/) được mở công khai theo giấy phép MIT để bất kỳ kỹ sư bảo mật nào cũng xác minh được tính chất Zero CGEventTap / Zero Keylogging, đi kèm công cụ dòng lệnh đã biên dịch VieLotusCLI để kiểm chứng độc lập toàn bộ 9.091 ca kiểm thử và bài đo 250.000 phím:

# 1. Kiểm tra chữ ký băm SHA-256 của các binary phát hành
shasum -a 256 -c Binaries/checksums.sha256

# 2. Chạy toàn bộ 9.091 ca kiểm thử chính + 1.049 ca đối xứng không ngữ cảnh
./Binaries/VieLotusCLI --benchmark

# 3. Chạy cổng kiểm định 3 tiến trình độc lập + đo độ trễ đuôi p99 (250.000 phím)
./scripts/benchmark_ratchet.sh --with-latency

# 4. Xác minh mã nguồn vỏ không chứa bất kỳ lời gọi CGEventTap nào
grep -rn "CGEvent" Sources/VieLotusIM/