A modern Vietnamese Input Method Engine (IME) for Linux with direct Unicode input—no pre-edit buffer, no underlines.
Find a file
Devin AI 4595ce7044 Fix clipboard-into-text race and add CI/CD for .deb + AppImage
Debounce the clipboard restore so the user's clipboard is never written
back while a just-pasted Vietnamese word may still be read by the target
app, which caused old clipboard content to appear in the text mid-typing.
Applied to both vietc-uinputd and the in-process UinputInjector.

Add a GitHub Actions workflow that builds the .deb and AppImage on the
runner (artifacts on push/PR, GitHub Release on v* tags), and include
vietc-uinputd + vietc-xrecord in the .deb.

Co-Authored-By: vndangkhoa <vonguyendangkhoa@gmail.com>
2026-06-27 01:16:48 +00:00
.github/workflows Fix clipboard-into-text race and add CI/CD for .deb + AppImage 2026-06-27 01:16:48 +00:00
cli Optimize typing performance and preserve casing on replaced syllables 2026-06-25 19:59:46 +07:00
daemon Fix TELEX ua-horn, word-spacing/control-key consumption, and clipboard preservation 2026-06-26 12:10:48 +00:00
engine Fix TELEX ua-horn, word-spacing/control-key consumption, and clipboard preservation 2026-06-26 12:10:48 +00:00
packaging Fix clipboard-into-text race and add CI/CD for .deb + AppImage 2026-06-27 01:16:48 +00:00
protocol Fix clipboard-into-text race and add CI/CD for .deb + AppImage 2026-06-27 01:16:48 +00: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 clipboard-into-text race and add CI/CD for .deb + AppImage 2026-06-27 01:16:48 +00: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 release: v0.1.3 — 106 tests, clipboard fix, ua-horn cluster 2026-06-26 19:27:24 +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 release: v0.1.3 — 106 tests, clipboard fix, ua-horn cluster 2026-06-26 19:27:24 +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 Tests


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