docs: update README and CHANGELOG for v0.1.0 overhaul

- README: update architecture diagrams, feature table, VNI key table
- CHANGELOG: document engine rewrite, injection overhaul, bug fixes
This commit is contained in:
Khoa Vo 2026-06-26 15:22:07 +07:00
parent d4102088b8
commit ac7d5c24ec
2 changed files with 83 additions and 96 deletions

View file

@ -2,35 +2,45 @@
## v0.1.0 (2026-06-26) ## v0.1.0 (2026-06-26)
Initial release. Initial release and major overhaul.
### Engine ### Engine (major rewrite)
- Direct Input engine — no pre-edit buffer, no underline, no text duplication
- Telex and VNI input methods
- Flexible diacritic placement (tone/modifier at end of syllable)
- Auto-restore English words on space/ESC
- ESC undo (strip all tones from current word)
- Macro expansion with custom shortcuts
- Casing preservation (titlecase, uppercase)
### Injection - **Bamboo engine port** — Replaced custom Telex/VNI state machines with a Rust port of bamboo-core's transformation model. Marks and tones are applied to characters in a composition buffer, with proper tone placement for all Vietnamese diphthongs.
- X11 keyboard capture via XGrabKeyboard (no root, no /dev/input) - **Flexible backtracking** — Mark/tone keys scan up to 5 characters backward to find the target vowel. Type the full syllable, then add marks at the end: `nguye6n4``nguyễn`.
- Direct X11 clipboard injection (XSetSelectionOwner + XTest Ctrl+V) - **Smart uo→ươ cluster** — Single `w`/`7` key after a `uo` pair converts both to `ươ`, even through consonants: `chuong7``chương`.
- Bundled xclip + wl-copy for Wayland fallback - **Correct tone placement** — Fixed tone positioning for `io` (gió), `uâ` (xuất), `yê` (nguyễn), `oa`/`oe`, `uy`, `iê`, `uô`, `ươ` clusters.
- Unified injection channel to prevent ordering race conditions - **Consume stale marks** — VNI/Telex control keys (digits, `f`/`s`/`r`/`x`/`j`/`w`) are consumed silently when they produce no change (e.g., pressing `5` on an already-toned `ạ`).
- **63 focused unit tests** covering Telex, VNI, tone placement, marks, macros, and uppercase.
### Daemon ### Injection (major overhaul)
- **Backspace-Replay pattern** — replays entire keystroke history through a fresh engine on every keypress, eliminating state desync
- FocusIn/FocusOut detection for automatic engine reset - **Uinput injection** — ASCII and backspace via Linux evdev keycodes (`/dev/uinput`). Correct keycodes per keyboard hardware, no X11 keycode mismatches.
- CPU pinning to P-cores (0-3) + nice(-10) priority boost - **Vietnamese Unicode** — Clipboard paste via persistent X11 connection + XTest Ctrl+V. Text is split only at trailing whitespace/punctuation boundary (no mid-word splitting). Persistent X11 display opened once and reused.
- Hot-reload config without restart - **Uinput daemon** (`vietc-uinputd`) — Privileged Unix socket server for `/dev/uinput` injection. VMK-style architecture with capability separation. The main daemon communicates via socket, falling back to in-process uinput.
- Smart app memory (per-application Vietnamese/English) - **X11Injector** uses `XKeysymToKeycode` for Ctrl+V keycodes, adapting to the actual keyboard layout.
- Persistent logging with 10MB rotation
### Capture
- **Evdev preferred** — Keyboard capture via `/dev/input/event*` with device grab is now the primary path. More reliable than X11 XRecord.
- **X11 XRecord fallback** — X11 passive monitoring via C helper (`vietc-xrecord`) as fallback when evdev is unavailable.
### Bug Fixes
- **Fix `Xutf8LookupString` signature** — Missing `XIC` parameter caused all keycodes to map to `\0`. Fixed by adding `*mut c_void` as first argument and passing `NULL`.
- **Fix `execute_commands` backspace count** — The X11 path incorrectly passed `grabbed=true`, subtracting 1 from every backspace. Changed to `false` so full backspace count is used.
- **Fix flush backspace overcount**`prev_len + 1` erased one character beyond the word. Fixed to `prev_len`.
- **Fix `apply_mark` char removal** — Removed `pattern.len()` chars from composition, but the current key hadn't been appended yet. Fixed to `pattern.len() - 1`.
- **Fix mark backtrack position** — Marks were applied at the end of composition instead of at the found position. Added position-aware `apply_mark_at`.
### Packaging ### Packaging
- AppImage with bundled xclip (no manual setup needed)
- Debian package with proper conffiles, maintainer scripts, and lintian overrides - AppImage bundles `vietc-uinputd`, `vietc-xrecord`, `xclip`.
- Systemd user service - AppRun preserves `LD_LIBRARY_PATH` with system library paths for `dlopen`.
- AppRun auto-starts `vietc-uinputd` via `pkexec`/`sudo` when available.
- Cleaned up `vietc-xrecord` compilation flags (only `-lX11 -lXtst` needed).
### Testing ### Testing
- 255+ unit tests across engine, protocol, daemon config, and replay
- 63 focused engine tests covering Telex, VNI, marks, tones, macros, casing.
- Removed old auto-generated bulk tests (850+ tests for deprecated engine).

119
README.md
View file

@ -44,12 +44,12 @@ Physical Keyboard
┌──────────────────────────────────────────────────────────────┐ ┌──────────────────────────────────────────────────────────────┐
│ Stage 1: KEY CAPTURE │ │ Stage 1: KEY CAPTURE │
│ │ │ │
X11: XGrabKeyboard intercepts all key events evdev: /dev/input/event* grabs keyboard (primary, reliable)
evdev: /dev/input/event* reads kernel events X11: XRecord passive monitoring (fallback)
│ │ │ │
│ ┌─────────────┐ ┌──────────────┐ ┌──────────────────┐ │ │ ┌─────────────┐ ┌──────────────┐ ┌──────────────────┐ │
│ │ X11Capture │ │ evdev grab │ │ FocusIn/FocusOut │ │ │ │ evdev grab │ │ X11Capture │ │ FocusIn/FocusOut │ │
│ │ (libX11.so) │ │ (libevdev) │ │ detection │ │ │ │ (libevdev) │ │ (XRecord) │ │ detection │ │
│ └─────────────┘ └──────────────┘ └──────────────────┘ │ │ └─────────────┘ └──────────────┘ └──────────────────┘ │
└──────────────────────────────────────────────────────────────┘ └──────────────────────────────────────────────────────────────┘
@ -61,58 +61,31 @@ Physical Keyboard
│ Ctrl+Space → toggle Vietnamese ON/OFF │ │ Ctrl+Space → toggle Vietnamese ON/OFF │
│ Backspace → replay_backspace() │ │ Backspace → replay_backspace() │
│ Characters → replay_and_inject(ch) │ │ Characters → replay_and_inject(ch) │
│ VNI/Telex control keys → consume when no match │
└──────────────────────────────────────────────────────────────┘ └──────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────┐ ┌──────────────────────────────────────────────────────────────┐
│ Stage 3: BACKSPACE-REPLAY │ Stage 3: BAMBOO ENGINE
│ │ │ │
│ keystroke_history = ['c', 'h', 'a', 'o', 's'] │ │ Transformation model: keystrokes produce composition │
│ │ │ │ changes. Marks and tones modify existing characters. │
│ ▼ │ │ Flexible backtracking scans up to 5 chars for vowels. │
│ ┌──────────────────────────────────────────────┐ │ │ Smart uo→ươ cluster with backtrack. │
│ │ Create FRESH engine │ │ │ Only emits Replace events when output actually changes. │
│ │ Replay ALL keystrokes through it │ │
│ │ engine.buffer() = "cháo" ← correct output │ │
│ └──────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ screen_output = "cháo" │
│ diff = backspaces(0) + type("cháo") │
│ (or no change if screen already shows "cháo") │
└──────────────────────────────────────────────────────────────┘ └──────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────┐ ┌──────────────────────────────────────────────────────────────┐
│ Stage 4: OUTPUT COMMANDS │ Stage 4: KEY INJECTION │
│ │ │ │
│ EngineEvent::Replace { backspaces: 4, insert: "cháo" } │ │ ASCII: direct Linux keycodes via /dev/uinput │
│ │ │ │ Backspace: Linux keycode 14 via uinput │
│ ▼ │ │ Vietnamese Unicode: clipboard paste + trailing ASCII via │
│ OutputCommand::Backspace(4) │ │ uinput (split only at whitespace/punctuation boundary) │
│ OutputCommand::Type("cháo") │ │ Persistent X11 connection for Ctrl+V (no per-call overhead) │
└──────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────┐
│ Stage 5: KEY INJECTION │
│ │ │ │
│ X11 path: │ │ Fallback: vietc-uinputd Unix socket daemon (privileged) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 1. Ungrab keyboard (XUngrabKeyboard) │ │
│ │ 2. Send backspaces via XTestFakeKeyEvent │ │
│ │ 3. Set clipboard via XChangeProperty │ │
│ │ 4. Handle SelectionRequest events │ │
│ │ 5. Send Ctrl+V via XTestFakeKeyEvent │ │
│ │ 6. Regrab keyboard (XGrabKeyboard) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ uinput path (Wayland): │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 1. Send backspaces via /dev/uinput (EV_KEY 14) │ │
│ │ 2. For ASCII: send keycodes via uinput │ │
│ │ 3. For Unicode: wl-copy + Ctrl+V via uinput │ │
│ └─────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘ └──────────────────────────────────────────────────────────────┘
@ -153,17 +126,18 @@ This means:
``` ```
vietc/ vietc/
├── engine/ # Core Vietnamese composition engine ├── engine/ # Vietnamese composition engine (bamboo-core Rust port)
│ ├── engine.rs # Orchestrator + replay_keystrokes() │ ├── engine.rs # Orchestrator + replay_keystrokes()
│ ├── telex.rs # Telex state machine (688 lines) │ ├── bamboo.rs # Bamboo engine: transformation model, composition, tone placement
│ ├── vni.rs # VNI state machine (593 lines) │ ├── input_method.rs # Telex/VNI rule definitions
│ ├── english.rs # English auto-restore dictionary
│ └── spelling.rs # Vietnamese syllable validation │ └── spelling.rs # Vietnamese syllable validation
├── protocol/ # Keyboard capture & injection ├── protocol/ # Keyboard capture & injection
│ ├── inject.rs # KeyInjector trait │ ├── inject.rs # KeyInjector trait
│ ├── x11_capture.rs # XGrabKeyboard + XNextEvent loop │ ├── x11_capture.rs # XRecord keyboard capture via C helper
│ ├── x11_inject.rs # XTest injection + direct clipboard │ ├── x11_inject.rs # XTest injection + direct clipboard
│ ├── uinput_monitor.rs # /dev/uinput injection for ASCII + Unicode
│ ├── uinput_client.rs # Unix socket client for vietc-uinputd
│ └── wayland_im.rs # Wayland IM protocol │ └── wayland_im.rs # Wayland IM protocol
├── daemon/ # Main daemon process ├── daemon/ # Main daemon process
@ -172,6 +146,9 @@ vietc/
│ ├── app_state.rs # Per-app Vietnamese/English memory │ ├── app_state.rs # Per-app Vietnamese/English memory
│ └── display.rs # X11/Wayland/compositor detection │ └── display.rs # X11/Wayland/compositor detection
├── uinputd/ # Privileged uinput backspace daemon (VMK-style)
│ └── main.rs # Unix socket server for /dev/uinput injection
├── ui/ # System tray icon ├── ui/ # System tray icon
│ └── main.rs # Tray + daemon launcher │ └── main.rs # Tray + daemon launcher
@ -250,19 +227,19 @@ vietc/
### VNI ### VNI
| Key | Result | | Key | Result | Example |
|-----|--------| |-----|--------|---------|
| `a1` | á | | `1` | á (sắc) | `a1``á` |
| `a2` | à | | `2` | à (huyền) | `a2``à` |
| `a3` | ả | | `3` | ả (hỏi) | `a3``ả` |
| `a4` | ã | | `4` | ã (ngã) | `a4``ã` |
| `a5` | ạ | | `5` | ạ (nặng) | `a5``ạ` |
| `a6` | â | | `6` | â/ê/ô | `a6``â`, `e6``ê`, `o6``ô` |
| `a8` | ă | | `7` | ơ/ư | `o7``ơ`, `u7``ư` |
| `e6` | ê | | `8` | ă | `a8``ă` |
| `o6` | ô | | `9` | đ | `d9``đ` |
| `o7` | ơ |
| `u7` | ư | Flexible typing: type the full syllable, then add marks/tone keys at the end. Example: `nguye6n4``nguyễn`. The engine scans backward up to 5 characters to find the target vowel.
--- ---
@ -270,18 +247,18 @@ vietc/
| Feature | How It Works | | Feature | How It Works |
|---------|-------------| |---------|-------------|
| **Direct Input** | No pre-edit buffer. Keystrokes instantly become Unicode via XTest/uinput injection | | **Direct Input** | No pre-edit buffer. Keystrokes instantly become text via uinput/XTest injection |
| **Backspace-Replay** | Replays entire keystroke history in a fresh engine on every keypress — zero state desync | | **Bamboo Engine** | Transformation model ported from bamboo-core — composition, marks, tones, flexible backtracking |
| **Flexible Placement** | Type tone/modifier at end of syllable (`tranaf` → `trần`) — engine scans backward to find the vowel | | **Flexible Backtrack** | Type tone/modifier at end of syllable (`tranaf` → `trần`). Scans up to 5 chars backward |
| **Smart Clusters** | `uo``ươ`, `ươ` + `o``uô`, shape modifier overriding (â↔ă, ô↔ơ) | | **Smart Clusters** | `uo``ươ` with backtrack (`chuong7` → `chương`) |
| **Auto-Restore** | ~250 English words recognized — typing `hello` won't become Vietnamese. Triggered on space/ESC | | **Tone Placement** | Correct tone positioning for all Vietnamese diphthongs (io→gió, uâ→xuất, yê→nguyễn) |
| **ESC Undo** | Strip all tones from current word instantly |
| **Macro Expansion** | `ko``không`, `dc``được`, custom shortcuts | | **Macro Expansion** | `ko``không`, `dc``được`, custom shortcuts |
| **Casing Preservation** | `SATS` → `SÁT`, `Saa``Sả` — matches your typing pattern | | **Casing Preservation** | `Tieengs` → `Tiếng`, `TIEENGS``TIẾNG` |
| **App Memory** | Per-app Vietnamese/English state, saved to `overrides.toml` | | **App Memory** | Per-app Vietnamese/English state, saved to `overrides.toml` |
| **Hot Reload** | Config changes apply without restart (polls mtime every 1.5s) | | **Hot Reload** | Config changes apply without restart (polls mtime every 1.5s) |
| **Focus Reset** | FocusIn/FocusOut clears engine state — no stale injection on window switch | | **Focus Reset** | Focus change clears engine state — no stale injection on window switch |
| **CPU Priority** | Pins daemon to P-cores (0-3) + nice(-10) for low-latency input | | **CPU Priority** | Pins daemon to P-cores (0-3) + nice(-10) for low-latency input |
| **Uinput Daemon** | Privileged `vietc-uinputd` for clean backspace injection (Unix socket, VMK-style) |
--- ---