paste_via_clipboard() called send_keycode(22, false) which sends evdev
keycode 22 = KEY_U (letter 'u'), not KEY_BACKSPACE (evdev 14). This
caused garbled output whenever Vietnamese Unicode text was pasted via
clipboard (every VNI correction with accented characters).
send_backspace() had the same wrong evdev keycode.
- install.sh: rewritten to download prebuilt tarball from GitHub releases
(or fallback to .deb extraction), removing verbose cargo build output
- release.yml: new CI workflow to build & upload tarball on tag push
- uninstall.sh: add systemctl --global daemon-reload after removing service
- daemon/src/main.rs: fix VNI backspace offset in X11 keymap capture path
(missing +1 adjustment for control keys that reach the app directly)
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.
On X11 (Linux Mint, Ubuntu), clipboard-based Unicode injection was
failing — backspace was sent but the Vietnamese character never
appeared because xclip paste via Ctrl+V wasn't reliable from a
root uinput process.
Now send_string tries xdotool type first (XTest-based, doesn't touch
the user's clipboard), falling back to clipboard paste only on Wayland
or when xdotool is unavailable.
- 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