When a VNI/Telex control key (e.g. digit 6 for ô, w for â/ê/ô) is
pressed, the engine absorbs it in-place without emitting an event. In
non-grabbed mode the raw character already reached the terminal. Fix:
capture buf_before, compare with buf_after, and synthesize
Backspace(len+1) + Type(buf_after) when the buffer changed.
The keymap and evdev paths were calling is_password_field() which returns
the cached value from AppStateManager. But check_password_field() (the
fresh AT-SPI2 check) was never called in these paths, so password
detection always returned false — engine remained enabled in password
fields, causing VNI processing of password input.
When switching between a text field and a password field within the same
window, the engine buffer and event store retained stale content from the
previous field, causing old text to bleed into the password input.
- Add daemon.engine.reset() + daemon.replay_reset() on window change
- Add same resets when password detection fires (both keymap and evdev
non-grabbed paths)
- Add same resets on periodic password re-check (XRecord path)
- Add same resets when re-enabling engine after leaving password field
- open_keyboard_device() -> open_keyboard_devices(): returns Vec of all
keyboard-capable evdev devices instead of just the first one
- run_with_evdev() polls all device FDs via single libc::poll() call
- Each device maintains independent key_state tracking
- Added XQueryKeymap/XLookupString to X11Lib in protocol crate
- X11KeymapCapture: new struct that polls X11 keymap every 10ms via
XQueryKeymap, diffs consecutive polls for press/release detection,
and uses XLookupString/Xutf8LookupString for char conversion
- run_with_x11_keymap(): replaces segfaulting XRecord-based run_with_x11
as the primary X11 fallback path
The X11 capture path was calling check_app_change_with X11 window IDs
instead of class names (gnome-terminal-server), corrupting the app
state. Also pass shared_window_class to run_with_x11 so it can use
the correct class for app detection.
In VM environments, EVIOCGRAB on the AT keyboard device succeeds but
produces no events — the kernel/VM routing prevents event delivery
to the grabber. Previously the daemon waited 30 seconds then exited.
Now: after 3 consecutive 100ms poll timeouts (~300ms) with no events
received, the grab is released and the daemon continues in non-grabbed
evdev mode. In this mode events reach both X (characters appear on
screen) and the daemon simultaneously; the daemon applies backspace
corrections via uinput.
Also removes the 30-second-exit behavior (which locked the keyboard
for 30 seconds unnecessarily) and replaces it with the fast fallback.
When evdev's EVIOCGRAB works (returns success) but no keyboard events
arrive (common in VMs where input bypasses the grabbed device), the
daemon previously exited silently after the 30-second safety timeout.
Now it falls through to X11 XRecord capture as a fallback, which works
reliably in VMs by intercepting keystrokes at the X11 protocol level
rather than the evdev level.
- run_with_evdev no longer uses 'return', so main() continues to X11
capture after evdev exits (timeout or error)
The original fetch_events() call blocked indefinitely on the evdev device.
In VM environments, the grabbed keyboard device may not deliver events
after the initial batch, causing the 30-second safety timeout to trigger
silently — the daemon exits, the grab is released, and subsequent
keystrokes bypass the IME entirely.
Replace with libc::poll() with a 100ms timeout so the event loop stays
responsive. When poll returns 0 (timeout), the loop checks signals, the
30-second grab-safety timeout, and also polls for background window
changes. This ensures the safety timeout actually fires as expected,
and the daemon correctly detects and handles the no-event condition.
Also check for background window class changes during idle periods
(no keypress events) so app detection works consistently.
- Add 'Event loop started' log at beginning of run_with_evdev
- Add reason for non-interrupted fetch_events errors
- Log each injected key with engine state, character, buffer length, and commands
- Fix VNI control digits being silently consumed when engine is disabled
When the daemon is started via `sudo vietc-daemon` from a terminal,
is_sudo_process would see sudo( in the terminal's process tree and
disable the engine, making all keystrokes pass through untransformed.
Now is_sudo_process builds the daemon's ancestor PID chain and skips
any sudo process that is an ancestor of the daemon itself.
- Add terminal_apps / terminal_input_method to config
- AppStateManager tracks global vs effective method
- Engine uses effective method (VNI in terminals, global elsewhere)
- Terminals removed from bypass_apps, moved to terminal_apps
- Tray still shows global method (user's setting)
- NOTE: NOTES/terminal-vni.md documents the design
- uinput injection is now primary on X11 (XTest fallback)
- X11 XTest keycode offset +8 fixed for all send_keycode paths
- Window switch detection on every keystroke (no more gap > 100ms guard)
- Telex greyed out in tray with '(next version)' label
- Flatpak and AppImage removed; only .deb packaging
- All Cargo.toml versions bumped to 0.1.6
- Change start_enabled default to true (Vietnamese active on launch)
- Tray: detect Flatpak sandbox, skip pointless password prompt
- Tray: write first-launch flag file always (not just after sudo)
- Desktop file: StartupNotify=true, wider categories for Cinnamon menu
- Update tests for new default, README config example
- Add system tray (vietc-tray) to Flatpak build; command changed to
vietc-tray which spawns the daemon
- Desktop menu entry: Viet+ appears in app launcher for search/install/uninstall
- Tray fixes: find_sibling_binary tries {name}-daemon fallback for Flatpak;
is_daemon_running checks both vietc and vietc-daemon process names
- Native X11 _NET_ACTIVE_WINDOW via dlopen(libX11.so.6) — third fallback in
get_active_window_id() that works inside Flatpak sandbox (no xdotool/xprop)
- Update README with install/uninstall commands
- Update CHANGELOG
The auto-restore merge reintroduced the spacing bug: on every flush char
the engine re-emitted a Replace and the daemon backspaced+retyped the
already-on-screen composed word, racing against the separately-forwarded
flush char and eating spaces (mất sự->mấtsự, đầu ngã xuống->đầungãxuống).
Now flush only backspaces+retypes when auto-restore actually changes the
word (English/invalid Vietnamese -> raw keystrokes). For a normal composed
word the engine returns None and the daemon types only the flush char,
leaving the correct word untouched on screen.
Co-Authored-By: vndangkhoa <vonguyendangkhoa@gmail.com>
The flush-char handling backspaced and re-typed the already-on-screen
word before/around the forwarded space. In the grabbed-device injection
path this raced against the separately-forwarded space, eating spaces
and merging finished words (e.g. "mất sự" -> "mấtsự",
"đầu ngã xuống" -> "đầungãxuống").
The composed word is already correct on screen, so a non-macro flush
now finalizes state without backspace+retype:
- engine: process_key returns None on flush (macros still Replace)
- daemon replay_and_inject: just types the flush char
- daemon did_flush branch: clears state without retyping
Add regression tests for flush behavior and multi-word spacing.
Co-Authored-By: vndangkhoa <vonguyendangkhoa@gmail.com>
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: vndangkhoa <vonguyendangkhoa@gmail.com>
- Engine no longer includes flush char in Replace insert text
- Daemon forwards raw flush key (space/enter/etc) after injection
- Clipboard paste only contains the word, not trailing whitespace
- Fixes 'thịtrâm' → 'thị trâm' (space reliably arrives via raw forward)
- skip_count=3 applied only to auto-repeat events, never to press/release
- Prevents rrrrrrrrr while letting spacebar and real typing through
- Reverted broken drain approach that corrupted source file
- After each Unicode injection, skip next 10 events (auto-repeat backlog)
- Prevents 'rrrrrrrrrrrrrr' and '555555' from auto-repeat during injection delay
- Also fixes Ctrl+V/Ctrl+C clipboard conflict during injection window
CHANGELOG: document v0.1.1 Telex fixes, injection improvements, AppImage flags
Only pure control keys (f,s,r,x,j,w) should be consumed silently.
Base letters used in double-letter marks (aa→â, ee→ê, etc.)
are normal typing keys that must be forwarded when no mark triggers.
Removed uppercase variants too — consolidated with to_ascii_lowercase().
- Fix Xutf8LookupString signature (missing XIC param caused all keys to map to \0)
- Port bamboo-core Vietnamese engine to Rust (bamboo.rs, input_method.rs)
- Flexible backtracking for mark/tone keys (scan up to 5 chars back)
- Correct tone placement for io, uâ, yê clusters
- Evdev capture preferred over X11 XRecord (more reliable)
- Uinput injection with correct Linux keycodes
- Vietnamese Unicode via clipboard paste + trailing ASCII via uinput
- Persistent X11 connection for Ctrl+V (no per-call dlopen overhead)
- Consume stale VNI/Telex control keys when no match found
- Fix execute_commands backspace count for evdev grabbing path
- Add vietc-uinputd privileged injection daemon
- AppImage: bundle uinputd, preserve LD_LIBRARY_PATH, fix xrecord build flags
- Remove old generated test files, add 63 focused engine tests