A modern Vietnamese Input Method Engine (IME) for Linux with direct Unicode input—no pre-edit buffer, no underlines.
Find a file
vndangkhoa 141df163e5
Merge pull request #2 from vndangkhoa/devin/1782469883-auto-restore-english
When Vietnamese mode is on, the engine transformed every word including
English (test->tét, cargo->cảgo, status->státu). This wires up the
previously-dead english.rs dictionary and spelling.rs validator so that on
word commit, words that are clearly English or not phonologically valid
Vietnamese are reverted to the raw keystrokes typed. Genuine Vietnamese
(tiếng, việt, quả) is kept. Gated by the existing [auto_restore] enabled
config (default on).

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: vndangkhoa <vonguyendangkhoa@gmail.com>
2026-06-26 17:35:19 +07:00
cli Optimize typing performance and preserve casing on replaced syllables 2026-06-25 19:59:46 +07:00
daemon feat: auto-restore English words and invalid Vietnamese syllables 2026-06-26 10:31:37 +00:00
engine feat: auto-restore English words and invalid Vietnamese syllables 2026-06-26 10:31:37 +00:00
packaging fix: add xdotool bundling to build-appimage.sh 2026-06-26 16:44:42 +07:00
protocol fix: remove clipboard save/restore — leaked content into text 2026-06-26 17:18:04 +07:00
scripts Fix typing race conditions with unified channel injection, add persistent logging, and align config schemas 2026-06-24 20:30:14 +07:00
ui Optimize typing performance and preserve casing on replaced syllables 2026-06-25 19:59:46 +07:00
uinputd fix: X11 key lookup, bamboo engine port, uinput injection overhaul 2026-06-26 15:20:03 +07:00
.gitignore fix: X11 key lookup, bamboo engine port, uinput injection overhaul 2026-06-26 15:20:03 +07:00
Cargo.toml fix: X11 key lookup, bamboo engine port, uinput injection overhaul 2026-06-26 15:20:03 +07:00
CHANGELOG.md docs: update CHANGELOG for v0.1.1 2026-06-26 15:49:57 +07:00
LICENSE Viet+ v0.1.0 - Vietnamese Input Method for Linux 2026-06-24 10:13:10 +07:00
Makefile Optimize typing performance and preserve casing on replaced syllables 2026-06-25 19:59:46 +07:00
README.md docs: update README and CHANGELOG for v0.1.0 overhaul 2026-06-26 15:22:07 +07:00
vietc.service Viet+ v0.1.1 2026-06-24 17:29:12 +07:00
vietc.toml fix: start_enabled=true by default, log daemon to file instead of /dev/null 2026-06-26 09:09:04 +07:00

Platform Rust License Version


Viet+

Vietnamese Input Method for Linux
Zero underline • No pre-edit buffer • Backspace-Replay sync • Built in Rust


What is Viet+?

Viet+ is a Vietnamese input method for Linux that takes a fundamentally different approach from every other IME: Direct Input.

Most Vietnamese IMEs use a pre-edit buffer — you type into a temporary buffer with an ugly underline, and the text only becomes real Vietnamese when you commit it. This causes:

  • Duplicate text (buffer + committed)
  • Underline distraction
  • Broken copy/paste
  • Desync between engine state and what's on screen

Viet+ eliminates all of this. Keystrokes are instantly converted to Unicode — what you type is what you see. No buffer. No underline. No duplication.


How It Works

Data Flow: Keypress to Screen

Physical Keyboard
       │
       ▼
┌──────────────────────────────────────────────────────────────┐
│  Stage 1: KEY CAPTURE                                        │
│                                                              │
│  evdev: /dev/input/event* grabs keyboard (primary, reliable) │
│  X11: XRecord passive monitoring (fallback)                   │
│                                                              │
│  ┌─────────────┐  ┌──────────────┐  ┌──────────────────┐   │
│  │ evdev grab  │  │ X11Capture   │  │ FocusIn/FocusOut │   │
│  │ (libevdev)  │  │ (XRecord)    │  │ detection        │   │
│  └─────────────┘  └──────────────┘  └──────────────────┘   │
└──────────────────────────────────────────────────────────────┘
       │
       ▼
┌──────────────────────────────────────────────────────────────┐
│  Stage 2: KEY ROUTING                                        │
│                                                              │
│  Modifier keys (Ctrl/Alt/Super) → forward directly           │
│  Ctrl+Space → toggle Vietnamese ON/OFF                       │
│  Backspace → replay_backspace()                              │
│  Characters → replay_and_inject(ch)                          │
│  VNI/Telex control keys → consume when no match              │
└──────────────────────────────────────────────────────────────┘
       │
       ▼
┌──────────────────────────────────────────────────────────────┐
│  Stage 3: BAMBOO ENGINE                                      │
│                                                              │
│  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.                         │
│  Only emits Replace events when output actually changes.     │
└──────────────────────────────────────────────────────────────┘
       │
       ▼
┌──────────────────────────────────────────────────────────────┐
│  Stage 4: KEY INJECTION                                      │
│                                                              │
│  ASCII: direct Linux keycodes via /dev/uinput                │
│  Backspace: Linux keycode 14 via uinput                      │
│  Vietnamese Unicode: clipboard paste + trailing ASCII via    │
│    uinput (split only at whitespace/punctuation boundary)    │
│  Persistent X11 connection for Ctrl+V (no per-call overhead) │
│                                                              │
│  Fallback: vietc-uinputd Unix socket daemon (privileged)     │
└──────────────────────────────────────────────────────────────┘
       │
       ▼
   Application receives keystrokes
   and renders Vietnamese text on screen

The Backspace-Replay Pattern

This is Viet+'s core innovation. Traditional IMEs track state incrementally — each keystroke updates an internal buffer. But this buffer can desync from what's actually on screen (due to focus changes, external pastes, etc.).

Viet+ solves this by never tracking incremental state:

Traditional IME:
  keystroke → update buffer → emit event → hope it matches screen
  
Viet+ (Backspace-Replay):
  keystroke → add to history → replay ALL history in fresh engine → compute diff

On every keystroke:

  1. The keystroke is appended to keystroke_history
  2. A brand new Engine is created
  3. The entire history is replayed through it
  4. The engine's buffer is the correct screen output
  5. Viet+ computes the diff: how many backspaces to erase old text, what new text to type

This means:

  • Zero state desync — always recomputed from scratch
  • Self-healing — if anything goes wrong, the next keystroke fixes it
  • Simple — no complex state tracking or synchronization

Architecture

vietc/
├── engine/                  # Vietnamese composition engine (bamboo-core Rust port)
│   ├── engine.rs            # Orchestrator + replay_keystrokes()
│   ├── bamboo.rs            # Bamboo engine: transformation model, composition, tone placement
│   ├── input_method.rs      # Telex/VNI rule definitions
│   └── spelling.rs          # Vietnamese syllable validation
│
├── protocol/                # Keyboard capture & injection
│   ├── inject.rs            # KeyInjector trait
│   ├── x11_capture.rs       # XRecord keyboard capture via C helper
│   ├── 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
│
├── daemon/                  # Main daemon process
│   ├── main.rs              # Event loops, Backspace-Replay, CPU pinning
│   ├── config.rs            # TOML config loader + hot reload
│   ├── app_state.rs         # Per-app Vietnamese/English memory
│   └── 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
│   └── main.rs              # Tray + daemon launcher
│
├── cli/                     # Interactive test harness
├── packaging/               # AppImage + deb build scripts
└── vietc.toml               # Default configuration

Component Interaction

┌─────────────────────────────────────────────────────────────┐
│                      vietc-tray                             │
│  (System tray icon, daemon launcher, password prompt)       │
└───────────────────────┬─────────────────────────────────────┘
                        │ starts
                        ▼
┌─────────────────────────────────────────────────────────────┐
│                      vietc (daemon)                          │
│                                                              │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────────┐  │
│  │ Config       │  │ App State    │  │ Display          │  │
│  │ (hot reload) │  │ (per-app)    │  │ (X11/Wayland)    │  │
│  └──────┬───────┘  └──────┬───────┘  └────────┬─────────┘  │
│         │                 │                    │             │
│         └─────────────────┼────────────────────┘             │
│                           │                                  │
│                    ┌──────▼──────┐                           │
│                    │ Event Loop  │                           │
│                    │             │                           │
│                    │ X11: grab   │                           │
│                    │ keyboard    │                           │
│                    │             │                           │
│                    │ Process     │                           │
│                    │ keystroke   │                           │
│                    │             │                           │
│                    │ Replay all  │                           │
│                    │ history     │                           │
│                    │             │                           │
│                    │ Inject      │                           │
│                    │ diff        │                           │
│                    └─────────────┘                           │
│                                                              │
│  ┌────────────────────────────────────────────────────────┐ │
│  │                   vietc-engine                         │ │
│  │  TelexEngine / VniEngine / EnglishDict / Spelling     │ │
│  └────────────────────────────────────────────────────────┘ │
│                                                              │
│  ┌────────────────────────────────────────────────────────┐ │
│  │               vietc-protocol                           │ │
│  │  X11Capture / X11Injector / UinputInjector / Wayland  │ │
│  └────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘

Input Methods

Telex

Key Result Example
aa â tantân
aw ă tantăn
ee ê menmên
oo ô to
ow ơ to
uw ư tu
s á (sắc) asá
f à (huyền) afà
r ả (hỏi) ar
x ã (ngã) axã
j ạ (nặng) aj
dd đ ddđ

VNI

Key Result Example
1 á (sắc) a1á
2 à (huyền) a2à
3 ả (hỏi) a3
4 ã (ngã) a4ã
5 ạ (nặng) a5
6 â/ê/ô a6â, e6ê, o6ô
7 ơ/ư o7ơ, u7ư
8 ă a8ă
9 đ d9đ

Flexible typing: type the full syllable, then add marks/tone keys at the end. Example: nguye6n4nguyễn. The engine scans backward up to 5 characters to find the target vowel.


Features

Feature How It Works
Direct Input No pre-edit buffer. Keystrokes instantly become text via uinput/XTest injection
Bamboo Engine Transformation model ported from bamboo-core — composition, marks, tones, flexible backtracking
Flexible Backtrack Type tone/modifier at end of syllable (tranaftrần). Scans up to 5 chars backward
Smart Clusters uoươ with backtrack (chuong7chương)
Tone Placement Correct tone positioning for all Vietnamese diphthongs (io→gió, uâ→xuất, yê→nguyễn)
Macro Expansion kokhông, dcđược, custom shortcuts
Casing Preservation TieengsTiếng, TIEENGSTIẾNG
App Memory Per-app Vietnamese/English state, saved to overrides.toml
Hot Reload Config changes apply without restart (polls mtime every 1.5s)
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
Uinput Daemon Privileged vietc-uinputd for clean backspace injection (Unix socket, VMK-style)

Installation

./Viet+-0.1.0-x86_64.AppImage

Includes daemon + tray + CLI + xclip. No special permissions needed on X11.

Debian/Ubuntu

sudo dpkg -i vietc_0.1.0-1_amd64.deb

Recommends: libxtst6, xclip

Manual

git clone https://git.khoavo.myds.me/vndangkhoa/vietc.git
cd vietc
make build-all
sudo make install

Configuration

Config file: ~/.config/vietc/config.toml or ./vietc.toml

input_method = "vni"       # "vni" or "telex"
toggle_key = "space"       # Ctrl+Space to toggle
start_enabled = false      # English by default
grab = true                # grab keyboard (AppImage)

[auto_restore]
enabled = true
trigger_keys = ["space", "escape"]

[app_state]
enabled = true
english_apps = ["code", "vim", "kitty", "foot"]
vietnamese_apps = ["telegram", "discord", "firefox"]

[macros]
ko = "không"
dc = "được"
vs = "với"
lm = "làm"

Building

make build-all     # Build with X11 + Wayland
make test          # Run 255+ tests
make deb           # Build .deb package
make appimage      # Build AppImage

License

MIT License — see LICENSE for details.


Made with love for the Vietnamese Linux community