Commit Graph
22 Commits
Author SHA1 Message Date
SergeyandGitHub 65381b4582 fix(appimage): build against a glibc floor users actually have (#743)
* build(appimage): pin the interpreter, packages and tooling

* ci(appimage): build inside a pinned ubuntu 22.04 image

* test(appimage): guard the pins and the base image

* docs: state the AppImage glibc floor

* fix(appimage): bundle libibus with its typelib

* ci(appimage): let the container run the isolated GI smoke test

* fix(appimage): keep the bundle's libraries out of host binaries

* fix(appimage): bundle libgpg-error with the libgcrypt that needs it

* docs(agents): require host_env() for spawned binaries

* ci: run the AppImage jobs when its other inputs change

* fix(appimage): check every bundled typelib, not just the seeded ones

* fix(ibus): keep the bundle when respawning our own interpreter

* fix(injection): pass host_env() to setxkbmap and notify-send

* test(ibus): wait for the IBus warmup thread before asserting
2026-08-29 14:46:50 -07:00
Jatin K MalikandGitHub 65ce9daa19 fix(ibus): sync XWayland layout from GNOME after scoped inject (#742)
Super+Space during recording on GNOME Wayland updates Mutter and IBus
but can leave XWayland on the previous map. Scoped inject restores the
IBus engine and skips setxkbmap (#474), so X apps keep the old layout.

After engine restore, copy GNOME's current XKB source onto XWayland
when DISPLAY is set. Do not replay an empty capture and do not call
restore_xkb_layout for this. Sessions with no X display are left alone.
2026-08-28 23:29:53 -07:00
Jatin K MalikandGitHub 36b2d923c6 fix(ibus): restore XKB layout after scoped injection on X11 (#665)
Scoped IBus injection (#457) temporarily switches to the Vocalinux
engine but only restored the previous IBus engine, not the XKB map.
On X11 that left non-US layouts (e.g. Brazilian ABNT2) stuck on us
after dictation. Capture XKB before the switch and re-apply it in
the inject_text finally block (Wayland remains a no-op via #474).

Fixes #664
2026-08-11 02:32:46 +05:30
7a5ddfde38 fix(ibus): restore engine after register_component teardown (#558) (#643)
* fix(ibus): restore engine after register_component teardown

Stopping the Vocalinux IBus engine process destroys its
register_component registration. IBus then runs check_global_engine(),
which only searches register_engine_list and treats XML engines like
xkb:es::spa as missing, clearing GlobalEngine even when Vocalinux was
never selected. Capture a restorable engine before teardown and
reselect it afterward so dead keys keep working after quit.

Fixes #558

Co-authored-by: Jatin K Malik <jatinkrmalik@users.noreply.github.com>

* fix(ibus): wait for engine exit before restoring GlobalEngine

Adversarial review of #643 found that stop_engine_process() returned
right after SIGTERM, so switch_engine() could race ahead of IBus
check_global_engine() and still leave No global engine. Wait for the
process to exit (SIGKILL fallback), retry engine restore, capture a
shutdown fallback during prepare_engine, and re-apply XKB after the
post-teardown engine switch.

Addresses review of #558 / #643.

Co-authored-by: Jatin K Malik <jatinkrmalik@users.noreply.github.com>

* fix(ibus): prefer live engine over stale warmup on quit

Bugbot on #643: stop() restored the warmup-cached engine even when the
user had switched input source afterward, and inject_text refused to
refresh that cache once set. Prefer the live non-Vocalinux engine at
quit; fall back to the cache only when stuck on vocalinux/unavailable.
Always refresh the shutdown fallback from inject_text.

Also cover SIGKILL teardown escalation and restore-retry failure paths
to lift patch coverage.

Co-authored-by: Jatin K Malik <jatinkrmalik@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Jatin K Malik <jatinkrmalik@users.noreply.github.com>
2026-08-05 21:41:10 -07:00
Scrates1andGitHub 679ff9161b fix(ibus): require a restorable engine for scoped injection (#623)
* fix(ibus): require a restorable engine for scoped injection

* fix(ibus): preserve registered XKB restore targets

Resolve GNOME XKB sources against registered IBus engine IDs so bare-XKB Wayland sessions keep scoped injection without fabricated restore names. Keep unknown states on the safe fallback path.

* test(ibus): cover GNOME restore target failures
2026-08-02 19:09:38 -07:00
Benjamin EiseleandGitHub 29db3a05a4 fix(ibus): keep engine teardown correct when parent destroy fails (#613)
* fix(ibus): keep engine teardown correct when parent destroy fails

`do_destroy` passed `super().do_destroy` to `_handle_engine_destroy`.
PyGObject binds that vfunc to the GType rather than to the instance, so
calling it with no arguments raises:

    TypeError: IBus.Object.destroy() takes exactly 1 argument (0 given)

This fired on every engine teardown. The exception escaped
`_handle_engine_destroy` before its return, so neither
`_active_instance = ...` nor `_focus_event.clear()` in the caller ran,
leaving a destroyed engine registered as active with focus still set --
the two values `_ibus_ping_status` (#523) reads to decide commit
readiness.

Two changes:

- Bind the parent destroy to the instance, falling back to
  `IBus.Object.destroy(self)` only when the no-arg call raises TypeError,
  so this keeps working if PyGObject binds the vfunc correctly later.
- Guard the parent destroy call so no failure there can skip teardown.

Fixes #606

* refactor(ibus): extract parent-destroy workaround into a testable helper

Codecov flagged the new code as uncovered (36% patch, below the 80%
target). The whole `if IBUS_AVAILABLE:` branch of `do_destroy` is
unreachable from the suite, which only ever exercises that method with
IBUS_AVAILABLE False -- the single line it replaced was already
uncovered on main for the same reason.

Move the workaround into a module-level `_invoke_parent_destroy`, the
same pattern `_handle_engine_destroy` already uses to make `do_destroy`
testable, and bind it with functools.partial at the call site.

Behaviour is unchanged. Coverage of the branch goes from 0 to all but
the one-line partial, and three tests now pin the actual contract:
pass-through when the binding is correct, retry with the instance on the
GType-binding TypeError, and no masking of other exceptions.
2026-07-29 00:36:24 +05:30
4e3637dfb5 Fix: IBus engine restore uses wrong fallback on GNOME/Wayland (#497) (#500)
* Fix: IBus engine restore uses wrong fallback on GNOME/Wayland (#497)

On GNOME/Wayland, when IBus has no global engine (common when Mutter
manages input sources), 'ibus engine' can return a misleading
'xkb:us::eng' default that doesn't match the user's actual keyboard
layout. When Vocalinux restores this wrong value after text injection,
Electron apps switch to US layout.

Root cause: get_current_engine() trusted the output of 'ibus engine'
without checking whether IBus actually had a global engine set, and
had no fallback to discover the real keyboard layout.

Fix:
- Add get_current_engine_gnome_fallback() that reads the actual
  keyboard layout from org.gnome.desktop.input-sources via gsettings
- Modify get_current_engine() to detect 'No global engine' in stderr
  and to detect the suspicious 'xkb:us::eng' default on GNOME/Wayland,
  falling back to gsettings in both cases
- Modify stop() to log a message when skipping engine restoration
  because no valid engine was captured (None means don't restore)
- Modify inject_text() to log when skipping restoration after
  injection for the same reason
- Backward compatible: if gsettings is unavailable, falls back to
  current behavior (returning None or the IBus-reported value)

Tests: 12 new tests covering GNOME fallback, 'No global engine'
detection, suspicious default detection, gsettings parsing, and
edge cases.

* ponytail: simplify gsettings uint32 parsing

gsettings prints 'uint32 N' or just 'N' — split()[-1] handles both,
no need for separate branches

---------

Co-authored-by: Jatin K Malik <jatinkrmalik@users.noreply.github.com>
2026-07-11 05:42:00 +05:30
Jatin K MalikandGitHub 49858f14e3 fix(ibus): capture current engine during prepare_engine for scoped activation (#458)
* fix(ibus): capture current engine during prepare_engine for scoped activation

Two bugs were causing IBus injection to fail and fall back to ydotool:

1. get_current_engine() returned empty string (falsy) when 'ibus engine'
   output was empty, instead of None. This caused inject_text() to think
   no engine was available.

2. prepare_engine() didn't capture the current IBus engine, so when
   inject_text() tried to get it at runtime, it would fail on systems
   where 'ibus engine' returns empty output.

Fix: get_current_engine() now returns None for empty output. prepare_engine()
captures the current engine before starting the Vocalinux process. inject_text()
uses the captured _previous_engine as fallback when get_current_engine() fails.

Fixes the regression from PR #457 where IBus injection would fail with:
  WARNING  Could not determine current IBus engine
  ERROR    IBus engine instance is not active and the previous engine
           could not be captured safely

* fix(ibus): always activate engine for injection on Wayland

On Wayland, 'ibus engine' returns empty output, so get_current_engine()
returns None. The previous code would skip activation when it couldn't
determine the current engine, causing injection to fail and fall back
to ydotool.

Now we always activate the Vocalinux engine when it's not active,
regardless of whether we can capture the previous engine. If we couldn't
capture it, we simply don't restore anything after injection.

This fixes the regression where IBus injection would fail with:
  WARNING  Could not determine current IBus engine
  ERROR    IBus engine instance is not active and the previous engine
           could not be captured safely

Fixes #458

* test(ibus): add missing mocks for is_engine_active and get_current_engine

Existing tests didn't mock is_engine_active() and get_current_engine(),
which are now called at the start of inject_text() for scoped activation.
This caused CI failures on Python 3.9-3.13 where the unmocked calls
would try to run 'ibus engine' and get unexpected results.
2026-06-06 20:41:15 -07:00
Jatin K MalikandGitHub e1384aad2e fix(ibus): preserve user engine for dead keys (#457)
* fix(ibus): preserve user engine for dead keys

Keep the VocaLinux IBus engine warm without selecting it as the active
keyboard engine. Temporarily switch to the VocaLinux engine only while
committing dictated text, then restore the user's previous IBus engine.
Wait for the IBus commit to complete before acknowledging socket
injection, so engine restoration does not race the commit.

This fixes dead-key composition (e.g. ^ + e → ê) on French AZERTY and
other layouts that rely on the active IBus/XKB engine for compose state.

Fixes #442

Supersedes #446 (rebased onto main after thread-safety hardening in #452)

* test(ibus): add coverage for scoped activation error paths

Cover previously-untested error paths in inject_text() scoped activation:
- switch_engine failure during temporary Vocalinux activation
- unknown current engine (get_current_engine returns None)
- NO_ENGINE retry when previous engine could not be captured
- ConnectionRefusedError exhausting all retries
- FileNotFoundError retry-then-succeed and retry-then-fail
- Generic exception catch-all
2026-06-06 19:57:25 -07:00
9939ce6b63 fix(ibus): probe engine readiness at startup and harden NO_ENGINE retries (#391)
* fix(ibus): wait for engine readiness before first injection

Add a startup readiness probe that pings the IBus engine after activation so first-use dictation no longer races do_enable(). Also switch NO_ENGINE retry behavior to exponential backoff and cover the new flow in tests.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* test(ibus): cover readiness probe edge cases

Add focused tests for the new startup readiness path, including direct PING success, retry exhaustion, unexpected responses, and engine re-activation before injection. This raises coverage on vocalinux.text_injection.ibus_engine from 68% to 73% in the targeted suite.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* fix(text-injection): avoid blocking IBus and clipboard fallbacks

Warm IBus in the background so startup no longer stalls while readiness probing, and make clipboard helpers fail fast with per-session health caching so non-ASCII ydotool fallback does not lock dictation.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* test(ibus): update retry tests for merged NO_ENGINE recovery logic

---------

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-02 19:33:31 -07:00
Jatin K MalikandGitHub 653f0e3d90 fix(text-injection): recover from IBus runtime failures (#411)
* fix(ibus): retry and recover from transient socket failures

Harden IBus injection against connection-refused, timeout, and engine process races so dictation is more resilient in real-world desktop sessions.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

* fix(text-injection): fallback to non-IBus backend on runtime IBus failures

Allow dictation to continue when IBus runtime injection fails by automatically switching to xdotool/wtype/ydotool fallbacks instead of returning an immediate failure.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

* fix(ibus): preserve live socket injection during recovery

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

* test(coverage): add runtime fallback and ibus retry coverage

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
2026-05-02 19:11:47 -07:00
Jatin K MalikandGitHub 6eba384da7 test(ibus): fix do_destroy CI failure (issubclass TypeError without IBus) (#410)
* test(ibus): fix do_destroy super() test failing in CI without IBus installed

Patch builtins.super instead of relying on mock_ibus.Engine.do_destroy,
which fails with issubclass() TypeError when VocalinuxEngine is built
with object as its base (IBus unavailable in CI).

* test(ibus): fix MagicMock.__new__ TypeError on Python < 3.13

VocalinuxEngine inherits from MagicMock at import time (IBus is mocked
via sys.modules at test-file level). Calling VocalinuxEngine.__new__(VocalinuxEngine)
delegates to MagicMock.__new__ which runs issubclass(cls, AsyncMockMixin).
On Python < 3.13 that raises TypeError when the module-cached class is a
MagicMock spec object rather than a real type.

Fix: introduce _make_engine() helper that allocates via a thin concrete
subclass whose __new__ calls object.__new__ directly, bypassing the
MagicMock allocation path entirely. Verified across all three
TestVocalinuxEngineDestroy test methods.

* test(ibus): use unbound do_destroy call to avoid MagicMock interference

The previous fix (thin _ConcreteEngine subclass) still failed because
VocalinuxEngine inherits MagicMock.__getattribute__, which intercepts
.do_destroy on instances and returns a mock attribute rather than the
real method - regardless of Python version.

Call VocalinuxEngine.do_destroy(engine) directly with a plain object()
as the self argument instead. do_destroy only uses self for identity
comparison (is VocalinuxEngine._active_instance), so any object works.
This bypasses MagicMock's MRO entirely.

* test(ibus): reload module and instantiate real engine class for do_destroy tests

* refactor(ibus): extract destroy handler for stable cross-version tests

* test(ibus): cover destroy helper branches and do_destroy delegation

* test(ibus): stabilize do_destroy CI behavior across environments

Avoid brittle super() mocking in no-IBus environments and gate the IBus-only parent destroy path test behind runtime capability checks to prevent false CI failures on Python matrix runners.

* test(ibus): remove flaky do_destroy class-path assertions

Drop brittle class-method destroy tests that fail under mocked GI/IBus on older Python runners and keep deterministic helper-level coverage for destroy behavior.
2026-05-02 18:37:07 -07:00
jatinkrmalikandSbenazar 3e4089a45c fix(ibus): handle engine instance destruction on layout switch (fixes #388, closes #389)
Override do_destroy() to clear _active_instance when IBus destroys the
engine on keyboard layout switch (Super+Space). Add retry logic in
inject_text() — on NO_ENGINE, re-activates via switch_engine() and
retries once to recover cleanly.

Co-authored-by: Sbenazar <127324689+Sbenazar@users.noreply.github.com>
2026-05-02 17:33:42 -07:00
Jatin K MalikandGitHub 45a69d5946 test(ibus): cover socket-readiness timeout warning branch (#384)
Add a test for the for/else warning path in _setup_engine() introduced
in PR #361: when all 15 socket-readiness poll attempts exhaust without
SOCKET_PATH appearing, a WARNING is logged and activation is attempted
anyway. The new test mocks time.sleep to avoid real delays and asserts
the exact retry count (15) and log message.

Fixes Codecov patch-coverage gap reported on #361.
2026-04-05 17:37:47 -07:00
SbenazarandGitHub c4f5b797ce fix(ibus): start engine process before checking registration (fixes #360) (#361)
* fix(ibus): start engine process before checking registration

_setup_engine() gated on is_engine_registered() which queries
`ibus list-engine`. On systems where the IBus daemon doesn't scan
user-level component directories (~/.local/share/ibus/component/),
the engine never appeared in the list, blocking start_engine_process()
from ever being called — even though its register_component() D-Bus
call is the correct registration path for standalone-launched engines.

Reorder _setup_engine() to start the engine process first, relying on
register_component() for runtime registration. Remove the XML
install step from the setup path since install_ibus_component()
triggers `ibus restart` which kills the engine process.

Follow-up to PR #304 which added register_component() support.

* fix(ibus): remove dead code and add engine readiness check

Address review feedback:
- Remove install_ibus_component(), is_engine_registered(),
  is_component_up_to_date(), _get_expected_component_xml()
  and their tests — no longer called after setup order fix
- Add SOCKET_PATH poll after start_engine_process() to verify
  engine is fully initialized before activation
- Rebase onto current main
2026-04-05 17:31:35 -07:00
Jatin K MalikandGitHub 93032471c3 fix(ibus): detect IBus on Wayland without env vars and fix text injection (#381)
Fix two related IBus issues on KDE Plasma Wayland (and similar DEs):

1. IBus not detected without legacy environment variables:
   On Wayland, IBus is configured via the DE's Virtual Keyboard setting
   and IBus itself recommends unsetting GTK_IM_MODULE / QT_IM_MODULE.
   The detection logic only checked these env vars, so it never found
   IBus on a properly configured Wayland session.

   Fix: When no IM env vars are set, check if ibus-daemon is running
   and has an active engine. If another IM (e.g. Fcitx) is explicitly
   configured via env vars, respect that and don't override.

2. IBus text injection silently fails on Wayland:
   After switching to the Vocalinux IBus engine, the code called
   setxkbmap to capture/restore the keyboard layout. On Wayland,
   setxkbmap does not reflect the compositor's state and returns
   incorrect results (e.g. 'us' when the user has 'de'). Calling
   setxkbmap to restore this wrong layout interfered with IBus's
   ability to commit text, causing silent injection failures.

   Fix: Skip setxkbmap-based XKB layout management entirely on
   Wayland sessions. The Wayland compositor manages keyboard layouts
   natively, and setxkbmap can only cause harm there.

Fixes #380
2026-04-03 15:47:55 -07:00
Jatin K MalikandGitHub 9ba63484c2 fix(keyboard): preserve XKB layout when activating Vocalinux IBus engine (fixes #292) (#343)
* fix(keyboard): preserve XKB layout when activating Vocalinux IBus engine

When Vocalinux starts, it may start or restart the IBus daemon, which
can override the user's system-level keyboard layout with IBus's
configured layout (often defaulting to US).

This fix captures the current XKB layout before activating the
Vocalinux IBus engine and restores it after activation, ensuring
users with non-US keyboard layouts don't lose their settings.

Fixes #292

* fix(keyboard): restore XKB layout after engine activation, not just on shutdown (#292)

The draft PR only restored the user's XKB layout when Vocalinux shut
down. The French AZERTY reporter confirmed the real problem: switching
to the Vocalinux IBus engine overrides the system keyboard layout
immediately, so typing 'a' produces 'q' (AZERTY mapped through US
QWERTY). The same applies to Spanish and other non-US layouts.

Changes:
- Restore XKB layout immediately after IBus engine activation in
  _setup_engine(), not just in stop(). This preserves the user's
  keyboard layout while Vocalinux is actively running.
- Remove the 'layout != us' guard in stop() -- always restore
  whatever layout was captured, including US with custom options.
- Fix 4 broken tests: wrong expected options values, missing
  TimeoutExpired argument, incorrect call count assertion.
- Add test cases for Spanish and French AZERTY layouts.

* test(keyboard): add coverage for XKB layout capture/restore in IBusTextInjector

Addresses Codecov report on PR #343 - adds tests covering:
- stop() restores captured XKB layout (Spanish with variant/options)
- stop() skips restore when no layout was captured
- _setup_engine() captures XKB layout and restores it after engine activation (French AZERTY)

* fix(test): mock start_engine_process in setup_engine XKB test

The test was missing a mock for start_engine_process, causing it to
fail on CI where the IBus engine PID file path doesn't exist.
2026-03-27 14:31:00 -07:00
Jatin K MalikandGitHub bcd6d94eea fix(text-injection): detect if IBus is the active input method before using it (#277)
* fix(text-injection): detect if IBus is the active input method before using it

This commit fixes issue #249 where Vocalinux incorrectly tries to use
IBus for text injection when IBus is installed but not configured as
the active input method (e.g., user uses ydotool on Arch + KDE Plasma 6).

Changes:
- Add is_ibus_active_input_method() function that checks GTK_IM_MODULE,
  QT_IM_MODULE, and XMODIFIERS environment variables to determine if
  IBus is the active input method
- Update _check_dependencies() to skip IBus when it's not the active
  input method, falling back to ydotool/wtype/xdotool instead
- Add comprehensive unit tests for the new functionality

The fix ensures Vocalinux will use the appropriate text injection
method based on the user's actual input method configuration,
rather than just checking if IBus is installed.

Fixes #249

* test: fix existing tests to mock is_ibus_active_input_method

* test: add comprehensive test coverage for is_ibus_active_input_method

Add additional tests to ensure complete coverage of the new functionality:

- Edge cases for empty string env vars
- Case-insensitive detection for all three env vars
- Priority ordering (GTK > Qt > XMODIFIERS)
- X11 fallback when IBus is not active input method
- Exception handling during IBus setup falls back correctly

Total tests: 62 (was 54), all passing.
2026-02-28 17:08:47 -08:00
59455098d3 fix(ibus): raise exception on setup failure to enable fallback (#240)
* fix(ibus): raise exception on setup failure to enable fallback

- Add IBusSetupError exception class for detailed error context
- Modify _setup_engine() to raise exception instead of silent return
- Ensures text_injector.py fallback mechanism works when IBus fails
- Add unit tests for IBusSetupError and setup failure scenarios

Fixes silent IBus text injection failures on Wayland/Fedora reported in #34

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* test(text-injector): add tests for IBus setup failure fallback

- Test that TextInjector falls back to xdotool when IBus fails
- Test fallback on both X11 and Wayland environments
- Test that text injection still works after IBus failure

Related to #34

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

---------

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-02-21 00:39:19 -08:00
Jatin K MalikandGitHub 79c3ca7f88 feat(text-injection): extend IBus support to X11 for non-US keyboard layouts (#227)
IBus sends Unicode directly via commit_text(), bypassing keyboard layout
issues entirely. This provides the same reliable text injection on X11
that we already have on Wayland.

Changes:
- Add X11_IBUS desktop environment enum
- Prefer IBus on both X11 and Wayland (before falling back to xdotool)
- Add tests for X11 IBus preference and fallback behavior

This supersedes the layout-switching approach in PR #206, which only
worked on X11 and had overhead from layout switching.

Closes #199
2026-02-17 22:40:20 -08:00
Jatin K MalikandGitHub 0da4e74005 fix(test): add missing mocks for IBus engine test (#226)
* fix(test): add missing mocks for IBus engine test

The test_init_auto_activate test was missing mocks for:
- install_ibus_component
- start_engine_process

Without these mocks, the test would fail in CI where IBus is not
installed, as _setup_engine() would fail early and never reach
switch_engine().

* style: fix black formatting in test_ibus_engine.py
2026-02-17 22:26:38 -08:00
a1725f7cfc feat(text-injection): add IBus engine for Wayland text injection (#211)
* feat(text-injection): add IBus engine for Wayland text injection

Add a custom IBus input method engine for reliable text injection on
Wayland, solving keyboard layout issues with xdotool/wtype.

Key features:
- IBus proxy engine that passes through normal keyboard input
- Text injection via Unix socket IPC
- User-level component installation (no sudo required)
- Auto-reactivation when user switches keyboard layouts
- Graceful fallback when IBus is unavailable

Changes:
- New ibus_engine.py with VocalinuxEngine and IBusTextInjector
- TextInjector now prefers IBus on Wayland for best compatibility
- Add IBus GObject bindings to system dependencies in install.sh
- Add comprehensive tests for IBus engine

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* security: secure IBus socket with permissions and peer verification

- Set directory to 0o700 (owner-only access)
- Set socket to 0o600 (owner-only read/write)
- Add SO_PEERCRED verification to reject connections from other users

This provides defense-in-depth: even if directory permissions are
bypassed, the socket server verifies the connecting process UID.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: address PR review feedback for IBus text injection

- Fix infinite loop in socket server by adding _server_running flag
- Use sys.executable instead of hardcoded python3 for venv compatibility
  - Also inherit environment (env=os.environ.copy()) for venv deps
  - Fix component XML to use sys.executable in <exec> tag
- Replace unsafe pgrep with PID file tracking and /proc verification
- Integrate TextInjector.stop() in TrayIndicator._quit() for cleanup
- Remove unused is_engine_registered import
- Fix redundant hasattr check to use proper None comparison
- Auto-detect and update stale IBus component XML on startup

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: AntoineMontane <monnet.antoine@gmail.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-17 22:15:06 -08:00