Commit graph

67 commits

Author SHA1 Message Date
Khoa Vo
035aeca997 fix: VNI/Telex control key flicker in non-grabbed mode
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.
2026-07-02 15:35:53 +07:00
Khoa Vo
2553f20466 password: call check_password_field() instead of stale cached is_password_field()
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.
2026-07-02 14:54:22 +07:00
Khoa Vo
fbe0baf7ab password: reset engine buffer and replay on password field transition
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
2026-07-02 14:49:35 +07:00
Khoa Vo
6b2b42639f evdev: poll all keyboard devices simultaneously; x11: replace XRecord capture with XQueryKeymap polling
- 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
2026-07-02 14:10:54 +07:00
Khoa Vo
88a64224b6 x11_capture: pass shared_window_class to run_with_x11; fix app change detection
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.
2026-07-02 13:41:44 +07:00
Khoa Vo
8d68edb321 daemon: fast grab fallback (300ms) to non-grabbed evdev when grab produces no events
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.
2026-07-02 13:41:01 +07:00
Khoa Vo
24f9bc8c7e daemon: fall back to X11 capture when evdev produces no events
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)
2026-07-02 13:33:41 +07:00
Khoa Vo
41ecc48b0a daemon: use poll() with 100ms timeout for evdev reads instead of blocking fetch_events
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.
2026-07-02 13:32:54 +07:00
Khoa Vo
8f8b4abf6d daemon: add diagnostics logging + guard VNI control key consumption behind engine.is_enabled()
- 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
2026-07-02 13:19:18 +07:00
Khoa Vo
fcd465c2b0 fix: exclude daemon's own sudo ancestor from password detection
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.
2026-07-02 12:12:32 +07:00
Khoa Vo
3ccf243f52 feat: terminal VNI input — force VNI in terminals, remove from bypass_apps
Some checks are pending
Build & Release / Build & test (push) Waiting to run
Build & Release / Build .deb (push) Blocked by required conditions
- 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
2026-07-02 08:57:17 +07:00
Khoa Vo
a9844221a7 Revert "debug: trace space handling in grab mode"
Some checks are pending
Build & Release / Build & test (push) Waiting to run
Build & Release / Build .deb (push) Blocked by required conditions
This reverts commit efed6f7e30.
2026-07-01 16:17:47 +07:00
Khoa Vo
efed6f7e30 debug: trace space handling in grab mode
Some checks are pending
Build & Release / Build & test (push) Waiting to run
Build & Release / Build .deb (push) Blocked by required conditions
2026-07-01 16:11:45 +07:00
Khoa Vo
58ff9e145e fix: handle EINTR in evdev loop, silence SelectionRequest log spam
Some checks are pending
Build & Release / Build & test (push) Waiting to run
Build & Release / Build .deb (push) Blocked by required conditions
2026-07-01 16:01:15 +07:00
Khoa Vo
db140c3ca6 fix: use sigaction without SA_RESTART so Ctrl+C interrupts blocking fetch_events
Some checks are pending
Build & Release / Build & test (push) Waiting to run
Build & Release / Build .deb (push) Blocked by required conditions
2026-07-01 15:56:34 +07:00
Khoa Vo
9e073714f1 fix: non-blocking evdev poll (200ms timeout) so Ctrl+C ungrabs reliably
Some checks are pending
Build & Release / Build & test (push) Waiting to run
Build & Release / Build .deb (push) Blocked by required conditions
2026-07-01 15:53:25 +07:00
Khoa Vo
ffd0bc26c8 debug: add keystroke tracing for space forwarding in grab mode
Some checks are pending
Build & Release / Build & test (push) Waiting to run
Build & Release / Build .deb (push) Blocked by required conditions
2026-07-01 15:31:56 +07:00
Khoa Vo
e7b7864937 fix: revert non-grab mode to process_key with +1 backspace for control keys
Some checks are pending
Build & Release / Build & test (push) Waiting to run
Build & Release / Build .deb (push) Blocked by required conditions
2026-07-01 15:02:17 +07:00
Khoa Vo
3612939643 fix: non-grab mode uses event sourcing (replay_and_inject) to avoid double-letter race conditions
Some checks are pending
Build & Release / Build & test (push) Waiting to run
Build & Release / Build .deb (push) Blocked by required conditions
2026-07-01 13:42:02 +07:00
Khoa Vo
82d0796059 fix: non-grabbing mode double-injection — extra backspace for control keys on screen
Some checks are pending
Build & Release / Build & test (push) Waiting to run
Build & Release / Build .deb (push) Blocked by required conditions
2026-07-01 13:34:11 +07:00
Khoa Vo
7fe03b7f44 fix: detect Wayland window switches via class change (not just X11 window ID)
Some checks are pending
Build & Release / Build & test (push) Waiting to run
Build & Release / Build .deb (push) Blocked by required conditions
2026-07-01 13:28:18 +07:00
Khoa Vo
19ee25784d fix: add SIGINT/SIGTERM handler to release keyboard grab before exit
Some checks are pending
Build & Release / Build & test (push) Waiting to run
Build & Release / Build .deb (push) Blocked by required conditions
2026-07-01 13:21:50 +07:00
Khoa Vo
36a6426894 fix: disable auto-restore by default (fixes space consumption on valid Vietnamese)
Some checks are pending
Build & Release / Build & test (push) Waiting to run
Build & Release / Build .deb (push) Blocked by required conditions
2026-07-01 13:11:51 +07:00
Khoa Vo
63c495894e fix: GNOME Shell D-Bus queries via gdbus subprocess as original user
Some checks are pending
Build & Release / Build & test (push) Waiting to run
Build & Release / Build .deb (push) Blocked by required conditions
2026-07-01 13:05:28 +07:00
Khoa Vo
e025ead244 fix: add GNOME Shell D-Bus PID query for Wayland password detection
Some checks are pending
Build & Release / Build & test (push) Waiting to run
Build & Release / Build .deb (push) Blocked by required conditions
2026-07-01 12:59:00 +07:00
Khoa Vo
ddf9f34ad0 fix: add process-based sudo/passwd detection for terminal password prompts
Some checks are pending
Build & Release / Build & test (push) Waiting to run
Build & Release / Build .deb (push) Blocked by required conditions
2026-07-01 12:52:07 +07:00
Khoa Vo
f77b7ea682 fix: recover DBUS_SESSION_BUS_ADDRESS when running as root for AT-SPI2 password detection
Some checks are pending
Build & Release / Build & test (push) Waiting to run
Build & Release / Build .deb (push) Blocked by required conditions
2026-07-01 12:46:38 +07:00
Khoa Vo
7ac73485e4 fix: add xprop/wmctrl fallbacks for window detection when xdotool is not installed
Some checks are pending
Build & Release / Build & test (push) Waiting to run
Build & Release / Build .deb (push) Blocked by required conditions
2026-07-01 12:05:20 +07:00
Khoa Vo
81b483e7ac fix: AT-SPI2 connects to a11y bus, not session bus (password detection was silently failing)
Some checks are pending
Build & Release / Build & test (push) Waiting to run
Build & Release / Build .deb (push) Blocked by required conditions
2026-07-01 11:59:40 +07:00
Khoa Vo
94c08bb0da fix: periodic password field re-check every 30 keystrokes for in-terminal prompts
Some checks are pending
Build & Release / Build & test (push) Waiting to run
Build & Release / Build .deb (push) Blocked by required conditions
2026-07-01 11:51:57 +07:00
Khoa Vo
ff607f0559 fix: double space on Ctrl+Space toggle (flush char forwarded twice when engine disabled)
Some checks are pending
Build & Release / Build & test (push) Waiting to run
Build & Release / Build .deb (push) Blocked by required conditions
2026-07-01 11:34:25 +07:00
Khoa Vo
81a2baa5eb fix: improve single-instance lock with PID + stale detection
Some checks are pending
Build & Release / Build & test (push) Waiting to run
Build & Release / Build .deb (push) Blocked by required conditions
2026-07-01 11:25:53 +07:00
Khoa Vo
6beeee2e69 release: v0.1.7 — password detection, Telex enabled, GNOME Wayland support 2026-07-01 11:00:11 +07:00
Khoa Vo
5242473b93 fix: use file locking instead of abstract socket for single instance to avoid rust null byte error
Some checks are pending
Build & Release / Build & test (push) Waiting to run
Build & Release / Build .deb (push) Blocked by required conditions
2026-06-29 21:09:48 +07:00
Khoa Vo
389c58e1fa feat: ensure single instance to prevent duplicate daemon and tray icons
Some checks are pending
Build & Release / Build & test (push) Waiting to run
Build & Release / Build .deb (push) Blocked by required conditions
2026-06-29 20:33:10 +07:00
Khoa Vo
88d39b4475 release: v0.1.6 — uinput-first injection, window-switch fix, Telex disabled
Some checks are pending
Build & Release / Build & test (push) Waiting to run
Build & Release / Build .deb (push) Blocked by required conditions
- 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
2026-06-29 16:07:15 +07:00
Khoa Vo
7d0b2e520c fix: Vietnamese mode default, Flatpak tray UX, Cinnamon menu entry
Some checks failed
Build & Release / Build & test (push) Has been cancelled
Build & Release / Build packages (push) Has been cancelled
- 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
2026-06-29 14:43:49 +07:00
Khoa Vo
98ce9def79 feat: Flatpak tray, X11 dlopen window query, desktop menu entry
Some checks are pending
Build & Release / Build & test (push) Waiting to run
Build & Release / Build packages (push) Blocked by required conditions
- 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
2026-06-29 14:32:30 +07:00
Khoa Vo
24e4425665 feat: window-switch engine reset, xprop fallback, clean up dead code
Some checks are pending
Build & Release / Build & test (push) Waiting to run
Build & Release / Build packages (push) Blocked by required conditions
- Fix window-switch engine state carryover (Alt+Tab between apps)
- Add xprop -root _NET_ACTIVE_WINDOW fallback for get_active_window_id()
- Update last_key_time only on character key presses (not modifiers)
- Use log_info for change detection (no per-key eprintln)
- Fix Flatpak build: add mkdir -p /app/share/applications
- Remove unused X11 clipboard code (~300 lines of dead unsafe code)
- Remove unused engine methods: is_empty, is_tone_or_mark_key,
  process_string, last_base_char, apply_cluster_mark, apply_mark
- Remove unused RuleEffect enum and special_rules field
- Suppress verbose paste debug logging in uinput_monitor
2026-06-29 14:12:30 +07:00
Khoa Vo
a714dca0be release: v0.1.5 — Event Sourcing, Flatpak build fixes, icons
Some checks are pending
Build & Release / Build & test (push) Waiting to run
Build & Release / Build packages (push) Blocked by required conditions
2026-06-28 21:20:19 +07:00
Devin AI
a5bc2add40 Fix TELEX ua-horn, word-spacing/control-key consumption, and clipboard preservation
Co-Authored-By: vndangkhoa <vonguyendangkhoa@gmail.com>
2026-06-26 12:10:48 +00:00
Devin AI
51949fe02b Fix flush spacing regression while preserving auto-restore
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>
2026-06-26 10:47:43 +00:00
vndangkhoa
19589d279a
Merge branch 'main' into devin/1782470334-fix-flush-spacing 2026-06-26 17:44:02 +07:00
Devin AI
bbd273bdd6 Fix spacing bug: stop retyping finished word on flush char
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>
2026-06-26 10:38:54 +00:00
Devin AI
7569e7e218 feat: auto-restore English words and invalid Vietnamese syllables
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>
2026-06-26 10:31:37 +00:00
Khoa Vo
758eea45b3 fix: flush char forwarded as raw key — space no longer in clipboard paste
- 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)
2026-06-26 16:54:40 +07:00
Khoa Vo
ebfff3db11 fix: skip auto-repeat only (value=2), not real key presses
- 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
2026-06-26 16:16:09 +07:00
Khoa Vo
4c9acfe772 fix: skip auto-repeat pile-up after injection, prevent stuck keys
- 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
2026-06-26 15:54:03 +07:00
Khoa Vo
42a0dad026 fix: Telex mode broken — is_vn_control_key consumed normal letters a/e/o/d/u
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().
2026-06-26 15:32:13 +07:00
Khoa Vo
d4102088b8 fix: X11 key lookup, bamboo engine port, uinput injection overhaul
- 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
2026-06-26 15:20:03 +07:00