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.
This commit is contained in:
@@ -9,6 +9,7 @@ from .ibus_engine import ( # noqa: F401
|
||||
install_ibus_component,
|
||||
is_engine_active,
|
||||
is_engine_registered,
|
||||
is_ibus_active_input_method,
|
||||
is_ibus_available,
|
||||
start_ibus_daemon,
|
||||
)
|
||||
|
||||
@@ -134,6 +134,45 @@ def is_ibus_daemon_running() -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def is_ibus_active_input_method() -> bool:
|
||||
"""
|
||||
Check if IBus is the currently active input method.
|
||||
|
||||
This checks environment variables to determine if IBus is configured
|
||||
as the active input method for GTK and Qt applications. On some systems,
|
||||
IBus may be installed and the daemon running, but not be the active
|
||||
input method (e.g., when using ydotool, Fcitx, or other input methods).
|
||||
|
||||
Returns:
|
||||
True if IBus appears to be the active input method, False otherwise
|
||||
"""
|
||||
# Check GTK_IM_MODULE for GTK applications
|
||||
gtk_im = os.environ.get("GTK_IM_MODULE", "").lower()
|
||||
if gtk_im and "ibus" in gtk_im:
|
||||
logger.debug(f"IBus detected as active input method via GTK_IM_MODULE={gtk_im}")
|
||||
return True
|
||||
|
||||
# Check QT_IM_MODULE for Qt applications
|
||||
qt_im = os.environ.get("QT_IM_MODULE", "").lower()
|
||||
if qt_im and "ibus" in qt_im:
|
||||
logger.debug(f"IBus detected as active input method via QT_IM_MODULE={qt_im}")
|
||||
return True
|
||||
|
||||
# On X11/XWayland, check XMODIFIERS for XIM compatibility
|
||||
xmodifiers = os.environ.get("XMODIFIERS", "").lower()
|
||||
if "@im=ibus" in xmodifiers:
|
||||
logger.debug(f"IBus detected as active input method via XMODIFIERS={xmodifiers}")
|
||||
return True
|
||||
|
||||
logger.debug(
|
||||
"IBus does not appear to be the active input method "
|
||||
f"(GTK_IM_MODULE={gtk_im or 'not set'}, "
|
||||
f"QT_IM_MODULE={qt_im or 'not set'}, "
|
||||
f"XMODIFIERS={xmodifiers or 'not set'})"
|
||||
)
|
||||
return False
|
||||
|
||||
|
||||
def start_ibus_daemon():
|
||||
"""
|
||||
Start the IBus daemon if it's not already running.
|
||||
|
||||
@@ -15,6 +15,7 @@ from typing import Optional # noqa: F401
|
||||
|
||||
from .ibus_engine import (
|
||||
IBusTextInjector,
|
||||
is_ibus_active_input_method,
|
||||
is_ibus_available,
|
||||
is_ibus_daemon_running,
|
||||
)
|
||||
@@ -135,8 +136,16 @@ class TextInjector:
|
||||
# Prefer IBus on both X11 and Wayland - it sends Unicode directly,
|
||||
# bypassing keyboard layout issues entirely
|
||||
if is_ibus_available():
|
||||
# Check if IBus is the active input method (not just installed)
|
||||
# This is important because IBus may be installed but not being used,
|
||||
# e.g., when the user has configured ydotool or Fcitx instead
|
||||
if not is_ibus_active_input_method():
|
||||
logger.info(
|
||||
"IBus is installed but not the active input method. "
|
||||
"Falling back to alternative text injection method."
|
||||
)
|
||||
# Check if ibus-daemon is running before attempting setup
|
||||
if not is_ibus_daemon_running():
|
||||
elif not is_ibus_daemon_running():
|
||||
logger.info(
|
||||
"IBus daemon not running. This is normal on some desktop environments "
|
||||
"(e.g., KDE Plasma). Using alternative text injection method. "
|
||||
@@ -155,7 +164,6 @@ class TextInjector:
|
||||
return
|
||||
except Exception as e:
|
||||
logger.warning(f"IBus initialization failed: {e}, trying alternatives")
|
||||
|
||||
if self.environment == DesktopEnvironment.X11:
|
||||
# Check for xdotool
|
||||
if not shutil.which("xdotool"):
|
||||
|
||||
+318
-43
@@ -2,6 +2,7 @@
|
||||
Tests for IBus engine functionality.
|
||||
"""
|
||||
|
||||
import os
|
||||
import socket
|
||||
import sys
|
||||
import tempfile
|
||||
@@ -62,10 +63,7 @@ class TestIBusTextInjectorSetupFailures(unittest.TestCase):
|
||||
mock_is_registered.return_value = False # Registration fails
|
||||
mock_install.return_value = True
|
||||
|
||||
from vocalinux.text_injection.ibus_engine import (
|
||||
IBusSetupError,
|
||||
IBusTextInjector,
|
||||
)
|
||||
from vocalinux.text_injection.ibus_engine import IBusSetupError, IBusTextInjector
|
||||
|
||||
with self.assertRaises(IBusSetupError) as context:
|
||||
IBusTextInjector(auto_activate=True)
|
||||
@@ -83,10 +81,7 @@ class TestIBusTextInjectorSetupFailures(unittest.TestCase):
|
||||
mock_is_registered.return_value = True
|
||||
mock_start_engine.return_value = False # Process start fails
|
||||
|
||||
from vocalinux.text_injection.ibus_engine import (
|
||||
IBusSetupError,
|
||||
IBusTextInjector,
|
||||
)
|
||||
from vocalinux.text_injection.ibus_engine import IBusSetupError, IBusTextInjector
|
||||
|
||||
with self.assertRaises(IBusSetupError) as context:
|
||||
IBusTextInjector(auto_activate=True)
|
||||
@@ -116,10 +111,7 @@ class TestIBusTextInjectorSetupFailures(unittest.TestCase):
|
||||
mock_get_current.return_value = "xkb:us::eng"
|
||||
mock_switch.return_value = False # Activation fails
|
||||
|
||||
from vocalinux.text_injection.ibus_engine import (
|
||||
IBusSetupError,
|
||||
IBusTextInjector,
|
||||
)
|
||||
from vocalinux.text_injection.ibus_engine import IBusSetupError, IBusTextInjector
|
||||
|
||||
with self.assertRaises(IBusSetupError) as context:
|
||||
IBusTextInjector(auto_activate=True)
|
||||
@@ -141,10 +133,7 @@ class TestIBusEngineHelpers(unittest.TestCase):
|
||||
|
||||
def test_is_ibus_available_returns_constant(self):
|
||||
"""Test is_ibus_available returns the module constant."""
|
||||
from vocalinux.text_injection.ibus_engine import (
|
||||
IBUS_AVAILABLE,
|
||||
is_ibus_available,
|
||||
)
|
||||
from vocalinux.text_injection.ibus_engine import IBUS_AVAILABLE, is_ibus_available
|
||||
|
||||
self.assertEqual(is_ibus_available(), IBUS_AVAILABLE)
|
||||
|
||||
@@ -651,18 +640,19 @@ class TestTextInjectorWithIBus(unittest.TestCase):
|
||||
|
||||
@patch("vocalinux.text_injection.ibus_engine.IBUS_AVAILABLE", True)
|
||||
@patch("vocalinux.text_injection.ibus_engine.is_ibus_available")
|
||||
@patch("vocalinux.text_injection.ibus_engine.is_ibus_active_input_method")
|
||||
@patch("vocalinux.text_injection.ibus_engine.IBusTextInjector")
|
||||
def test_wayland_prefers_ibus(self, mock_injector_class, mock_ibus_available):
|
||||
def test_wayland_prefers_ibus(
|
||||
self, mock_injector_class, mock_is_active_im, mock_ibus_available
|
||||
):
|
||||
"""Test that Wayland environment prefers IBus when available."""
|
||||
mock_ibus_available.return_value = True
|
||||
mock_is_active_im.return_value = True
|
||||
mock_injector_instance = MagicMock()
|
||||
mock_injector_class.return_value = mock_injector_instance
|
||||
|
||||
with patch.dict("os.environ", {"XDG_SESSION_TYPE": "wayland"}):
|
||||
from vocalinux.text_injection.text_injector import (
|
||||
DesktopEnvironment,
|
||||
TextInjector,
|
||||
)
|
||||
from vocalinux.text_injection.text_injector import DesktopEnvironment, TextInjector
|
||||
|
||||
# Patch the import inside text_injector
|
||||
with patch(
|
||||
@@ -670,40 +660,47 @@ class TestTextInjectorWithIBus(unittest.TestCase):
|
||||
return_value=True,
|
||||
):
|
||||
with patch(
|
||||
"vocalinux.text_injection.text_injector.IBusTextInjector",
|
||||
mock_injector_class,
|
||||
"vocalinux.text_injection.text_injector.is_ibus_active_input_method",
|
||||
return_value=True,
|
||||
):
|
||||
injector = TextInjector()
|
||||
with patch(
|
||||
"vocalinux.text_injection.text_injector.IBusTextInjector",
|
||||
mock_injector_class,
|
||||
):
|
||||
injector = TextInjector()
|
||||
|
||||
# Should be using IBus
|
||||
self.assertEqual(injector.environment, DesktopEnvironment.WAYLAND_IBUS)
|
||||
# Should be using IBus
|
||||
self.assertEqual(injector.environment, DesktopEnvironment.WAYLAND_IBUS)
|
||||
|
||||
@patch("vocalinux.text_injection.ibus_engine.IBUS_AVAILABLE", True)
|
||||
@patch("vocalinux.text_injection.ibus_engine.is_ibus_available")
|
||||
@patch("vocalinux.text_injection.ibus_engine.is_ibus_active_input_method")
|
||||
@patch("vocalinux.text_injection.ibus_engine.IBusTextInjector")
|
||||
def test_x11_prefers_ibus(self, mock_injector_class, mock_ibus_available):
|
||||
def test_x11_prefers_ibus(self, mock_injector_class, mock_is_active_im, mock_ibus_available):
|
||||
"""Test that X11 environment prefers IBus when available."""
|
||||
mock_ibus_available.return_value = True
|
||||
mock_is_active_im.return_value = True
|
||||
mock_injector_instance = MagicMock()
|
||||
mock_injector_class.return_value = mock_injector_instance
|
||||
|
||||
with patch.dict("os.environ", {"XDG_SESSION_TYPE": "x11"}):
|
||||
from vocalinux.text_injection.text_injector import (
|
||||
DesktopEnvironment,
|
||||
TextInjector,
|
||||
)
|
||||
from vocalinux.text_injection.text_injector import DesktopEnvironment, TextInjector
|
||||
|
||||
with patch(
|
||||
"vocalinux.text_injection.text_injector.is_ibus_available",
|
||||
return_value=True,
|
||||
):
|
||||
with patch(
|
||||
"vocalinux.text_injection.text_injector.IBusTextInjector",
|
||||
mock_injector_class,
|
||||
"vocalinux.text_injection.text_injector.is_ibus_active_input_method",
|
||||
return_value=True,
|
||||
):
|
||||
injector = TextInjector()
|
||||
with patch(
|
||||
"vocalinux.text_injection.text_injector.IBusTextInjector",
|
||||
mock_injector_class,
|
||||
):
|
||||
injector = TextInjector()
|
||||
|
||||
self.assertEqual(injector.environment, DesktopEnvironment.X11_IBUS)
|
||||
self.assertEqual(injector.environment, DesktopEnvironment.X11_IBUS)
|
||||
|
||||
@patch("vocalinux.text_injection.text_injector.is_ibus_available")
|
||||
def test_x11_fallback_when_ibus_unavailable(self, mock_ibus_available):
|
||||
@@ -715,10 +712,7 @@ class TestTextInjectorWithIBus(unittest.TestCase):
|
||||
"/usr/bin/xdotool" if cmd == "xdotool" else None
|
||||
)
|
||||
|
||||
from vocalinux.text_injection.text_injector import (
|
||||
DesktopEnvironment,
|
||||
TextInjector,
|
||||
)
|
||||
from vocalinux.text_injection.text_injector import DesktopEnvironment, TextInjector
|
||||
|
||||
injector = TextInjector()
|
||||
|
||||
@@ -735,10 +729,7 @@ class TestTextInjectorWithIBus(unittest.TestCase):
|
||||
"/usr/bin/ydotool" if cmd == "ydotool" else None
|
||||
)
|
||||
|
||||
from vocalinux.text_injection.text_injector import (
|
||||
DesktopEnvironment,
|
||||
TextInjector,
|
||||
)
|
||||
from vocalinux.text_injection.text_injector import DesktopEnvironment, TextInjector
|
||||
|
||||
injector = TextInjector()
|
||||
|
||||
@@ -772,6 +763,44 @@ class TestTextInjectorWithIBus(unittest.TestCase):
|
||||
mock_injector_instance = MagicMock()
|
||||
mock_injector_class.return_value = mock_injector_instance
|
||||
|
||||
with patch.dict("os.environ", {"XDG_SESSION_TYPE": "wayland"}):
|
||||
from vocalinux.text_injection.text_injector import TextInjector
|
||||
|
||||
injector = TextInjector()
|
||||
injector.stop()
|
||||
|
||||
@patch("vocalinux.text_injection.text_injector.is_ibus_available")
|
||||
@patch("vocalinux.text_injection.text_injector.is_ibus_active_input_method")
|
||||
@patch("vocalinux.text_injection.text_injector.IBusTextInjector")
|
||||
def test_ibus_inject_text(self, mock_injector_class, mock_is_active_im, mock_ibus_available):
|
||||
"""Test text injection via IBus."""
|
||||
mock_ibus_available.return_value = True
|
||||
mock_is_active_im.return_value = True
|
||||
mock_injector_instance = MagicMock()
|
||||
mock_injector_instance.inject_text.return_value = True
|
||||
mock_injector_class.return_value = mock_injector_instance
|
||||
|
||||
with patch.dict("os.environ", {"XDG_SESSION_TYPE": "wayland"}):
|
||||
from vocalinux.text_injection.text_injector import TextInjector
|
||||
|
||||
injector = TextInjector()
|
||||
result = injector.inject_text("Hello via IBus")
|
||||
|
||||
self.assertTrue(result)
|
||||
mock_injector_instance.inject_text.assert_called_once_with("Hello via IBus")
|
||||
|
||||
@patch("vocalinux.text_injection.text_injector.is_ibus_available")
|
||||
@patch("vocalinux.text_injection.text_injector.is_ibus_active_input_method")
|
||||
@patch("vocalinux.text_injection.text_injector.IBusTextInjector")
|
||||
def test_stop_calls_ibus_stop(
|
||||
self, mock_injector_class, mock_is_active_im, mock_ibus_available
|
||||
):
|
||||
"""Test that stop() calls IBus injector stop."""
|
||||
mock_ibus_available.return_value = True
|
||||
mock_is_active_im.return_value = True
|
||||
mock_injector_instance = MagicMock()
|
||||
mock_injector_class.return_value = mock_injector_instance
|
||||
|
||||
with patch.dict("os.environ", {"XDG_SESSION_TYPE": "wayland"}):
|
||||
from vocalinux.text_injection.text_injector import TextInjector
|
||||
|
||||
@@ -797,5 +826,251 @@ class TestDesktopEnvironmentEnumWithIBus(unittest.TestCase):
|
||||
self.assertEqual(DesktopEnvironment.X11_IBUS.value, "x11-ibus")
|
||||
|
||||
|
||||
class TestIsIbusActiveInputMethod(unittest.TestCase):
|
||||
"""Tests for is_ibus_active_input_method function."""
|
||||
|
||||
def test_detects_ibus_via_gtk_im_module(self):
|
||||
"""Test detection when GTK_IM_MODULE is set to ibus."""
|
||||
with patch.dict(os.environ, {"GTK_IM_MODULE": "ibus"}, clear=True):
|
||||
from vocalinux.text_injection.ibus_engine import is_ibus_active_input_method
|
||||
|
||||
result = is_ibus_active_input_method()
|
||||
self.assertTrue(result)
|
||||
|
||||
def test_detects_ibus_python_via_gtk_im_module(self):
|
||||
"""Test detection when GTK_IM_MODULE is set to ibus-python."""
|
||||
with patch.dict(os.environ, {"GTK_IM_MODULE": "ibus-python"}, clear=True):
|
||||
from vocalinux.text_injection.ibus_engine import is_ibus_active_input_method
|
||||
|
||||
result = is_ibus_active_input_method()
|
||||
self.assertTrue(result)
|
||||
|
||||
def test_detects_ibus_via_qt_im_module(self):
|
||||
"""Test detection when QT_IM_MODULE is set to ibus."""
|
||||
with patch.dict(os.environ, {"QT_IM_MODULE": "ibus"}, clear=True):
|
||||
from vocalinux.text_injection.ibus_engine import is_ibus_active_input_method
|
||||
|
||||
result = is_ibus_active_input_method()
|
||||
self.assertTrue(result)
|
||||
|
||||
def test_detects_ibus_via_xmodifiers(self):
|
||||
"""Test detection when XMODIFIERS contains @im=ibus."""
|
||||
with patch.dict(os.environ, {"XMODIFIERS": "@im=ibus"}, clear=True):
|
||||
from vocalinux.text_injection.ibus_engine import is_ibus_active_input_method
|
||||
|
||||
result = is_ibus_active_input_method()
|
||||
self.assertTrue(result)
|
||||
|
||||
def test_not_active_when_gtk_is_xim(self):
|
||||
"""Test returns False when GTK_IM_MODULE is xim."""
|
||||
with patch.dict(os.environ, {"GTK_IM_MODULE": "xim"}, clear=True):
|
||||
from vocalinux.text_injection.ibus_engine import is_ibus_active_input_method
|
||||
|
||||
result = is_ibus_active_input_method()
|
||||
self.assertFalse(result)
|
||||
|
||||
def test_not_active_when_qt_is_fcitx(self):
|
||||
"""Test returns False when QT_IM_MODULE is fcitx."""
|
||||
with patch.dict(os.environ, {"QT_IM_MODULE": "fcitx"}, clear=True):
|
||||
from vocalinux.text_injection.ibus_engine import is_ibus_active_input_method
|
||||
|
||||
result = is_ibus_active_input_method()
|
||||
self.assertFalse(result)
|
||||
|
||||
def test_not_active_when_xmodifiers_is_fcitx(self):
|
||||
"""Test returns False when XMODIFIERS is @im=fcitx."""
|
||||
with patch.dict(os.environ, {"XMODIFIERS": "@im=fcitx"}, clear=True):
|
||||
from vocalinux.text_injection.ibus_engine import is_ibus_active_input_method
|
||||
|
||||
result = is_ibus_active_input_method()
|
||||
self.assertFalse(result)
|
||||
|
||||
def test_not_active_when_no_env_vars_set(self):
|
||||
"""Test returns False when no input method env vars are set."""
|
||||
with patch.dict(os.environ, {}, clear=True):
|
||||
from vocalinux.text_injection.ibus_engine import is_ibus_active_input_method
|
||||
|
||||
result = is_ibus_active_input_method()
|
||||
self.assertFalse(result)
|
||||
|
||||
def test_empty_string_env_vars_return_false(self):
|
||||
"""Test returns False when env vars are empty strings."""
|
||||
with patch.dict(
|
||||
os.environ,
|
||||
{"GTK_IM_MODULE": "", "QT_IM_MODULE": "", "XMODIFIERS": ""},
|
||||
clear=True,
|
||||
):
|
||||
from vocalinux.text_injection.ibus_engine import is_ibus_active_input_method
|
||||
|
||||
result = is_ibus_active_input_method()
|
||||
self.assertFalse(result)
|
||||
|
||||
def test_gtk_im_module_case_insensitive(self):
|
||||
"""Test detection is case-insensitive for GTK_IM_MODULE."""
|
||||
with patch.dict(os.environ, {"GTK_IM_MODULE": "IBUS"}, clear=True):
|
||||
from vocalinux.text_injection.ibus_engine import is_ibus_active_input_method
|
||||
|
||||
result = is_ibus_active_input_method()
|
||||
self.assertTrue(result)
|
||||
|
||||
def test_qt_im_module_case_insensitive(self):
|
||||
"""Test detection is case-insensitive for QT_IM_MODULE."""
|
||||
with patch.dict(os.environ, {"QT_IM_MODULE": "IBUS"}, clear=True):
|
||||
from vocalinux.text_injection.ibus_engine import is_ibus_active_input_method
|
||||
|
||||
result = is_ibus_active_input_method()
|
||||
self.assertTrue(result)
|
||||
|
||||
def test_xmodifiers_case_insensitive(self):
|
||||
"""Test detection is case-insensitive for XMODIFIERS."""
|
||||
with patch.dict(os.environ, {"XMODIFIERS": "@IM=IBUS"}, clear=True):
|
||||
from vocalinux.text_injection.ibus_engine import is_ibus_active_input_method
|
||||
|
||||
result = is_ibus_active_input_method()
|
||||
self.assertTrue(result)
|
||||
|
||||
def test_priority_order_gtk_over_qt(self):
|
||||
"""Test GTK_IM_MODULE takes priority over QT_IM_MODULE."""
|
||||
with patch.dict(
|
||||
os.environ,
|
||||
{"GTK_IM_MODULE": "ibus", "QT_IM_MODULE": "fcitx"},
|
||||
clear=True,
|
||||
):
|
||||
from vocalinux.text_injection.ibus_engine import is_ibus_active_input_method
|
||||
|
||||
result = is_ibus_active_input_method()
|
||||
self.assertTrue(result)
|
||||
|
||||
def test_priority_order_qt_over_xmodifiers(self):
|
||||
"""Test QT_IM_MODULE takes priority over XMODIFIERS."""
|
||||
with patch.dict(
|
||||
os.environ,
|
||||
{"QT_IM_MODULE": "ibus", "XMODIFIERS": "@im=fcitx"},
|
||||
clear=True,
|
||||
):
|
||||
from vocalinux.text_injection.ibus_engine import is_ibus_active_input_method
|
||||
|
||||
result = is_ibus_active_input_method()
|
||||
self.assertTrue(result)
|
||||
|
||||
|
||||
class TestTextInjectorWithIbusActiveInputMethod(unittest.TestCase):
|
||||
"""Tests for TextInjector with is_ibus_active_input_method check."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up test fixtures."""
|
||||
self.patch_which = patch("shutil.which")
|
||||
self.mock_which = self.patch_which.start()
|
||||
|
||||
self.patch_subprocess = patch("subprocess.run")
|
||||
self.mock_subprocess = self.patch_subprocess.start()
|
||||
|
||||
# Default to having ydotool available
|
||||
self.mock_which.side_effect = lambda cmd: ("/usr/bin/ydotool" if cmd == "ydotool" else None)
|
||||
|
||||
mock_process = MagicMock()
|
||||
mock_process.returncode = 0
|
||||
mock_process.stdout = ""
|
||||
mock_process.stderr = ""
|
||||
self.mock_subprocess.return_value = mock_process
|
||||
|
||||
def tearDown(self):
|
||||
"""Clean up after tests."""
|
||||
self.patch_which.stop()
|
||||
self.patch_subprocess.stop()
|
||||
|
||||
@patch("vocalinux.text_injection.text_injector.is_ibus_available")
|
||||
@patch("vocalinux.text_injection.text_injector.is_ibus_active_input_method")
|
||||
@patch("vocalinux.text_injection.text_injector.is_ibus_daemon_running")
|
||||
def test_skips_ibus_when_not_active_input_method(
|
||||
self, mock_daemon_running, mock_is_active_im, mock_is_available
|
||||
):
|
||||
"""Test that IBus is skipped when not the active input method."""
|
||||
mock_is_available.return_value = True
|
||||
mock_is_active_im.return_value = False # IBus installed but not active
|
||||
mock_daemon_running.return_value = True
|
||||
|
||||
with patch.dict("os.environ", {"XDG_SESSION_TYPE": "wayland"}):
|
||||
from vocalinux.text_injection.text_injector import DesktopEnvironment, TextInjector
|
||||
|
||||
injector = TextInjector()
|
||||
|
||||
# Should fall back to WAYLAND with ydotool, not WAYLAND_IBUS
|
||||
self.assertEqual(injector.environment, DesktopEnvironment.WAYLAND)
|
||||
self.assertEqual(injector.wayland_tool, "ydotool")
|
||||
|
||||
@patch("vocalinux.text_injection.text_injector.is_ibus_available")
|
||||
@patch("vocalinux.text_injection.text_injector.is_ibus_active_input_method")
|
||||
@patch("vocalinux.text_injection.text_injector.is_ibus_daemon_running")
|
||||
@patch("vocalinux.text_injection.text_injector.IBusTextInjector")
|
||||
def test_uses_ibus_when_active_input_method(
|
||||
self, mock_injector_class, mock_daemon_running, mock_is_active_im, mock_is_available
|
||||
):
|
||||
"""Test that IBus is used when it is the active input method."""
|
||||
mock_is_available.return_value = True
|
||||
mock_is_active_im.return_value = True # IBus is the active input method
|
||||
mock_daemon_running.return_value = True
|
||||
|
||||
mock_injector_instance = MagicMock()
|
||||
mock_injector_class.return_value = mock_injector_instance
|
||||
|
||||
with patch.dict("os.environ", {"XDG_SESSION_TYPE": "wayland"}):
|
||||
from vocalinux.text_injection.text_injector import DesktopEnvironment, TextInjector
|
||||
|
||||
with patch(
|
||||
"vocalinux.text_injection.text_injector.IBusTextInjector",
|
||||
mock_injector_class,
|
||||
):
|
||||
injector = TextInjector()
|
||||
|
||||
# Should use IBus
|
||||
self.assertEqual(injector.environment, DesktopEnvironment.WAYLAND_IBUS)
|
||||
# Should use IBus
|
||||
self.assertEqual(injector.environment, DesktopEnvironment.WAYLAND_IBUS)
|
||||
|
||||
@patch("vocalinux.text_injection.text_injector.shutil.which")
|
||||
@patch("vocalinux.text_injection.text_injector.is_ibus_available")
|
||||
@patch("vocalinux.text_injection.text_injector.is_ibus_active_input_method")
|
||||
@patch("vocalinux.text_injection.text_injector.is_ibus_daemon_running")
|
||||
def test_x11_falls_back_when_ibus_not_active_input_method(
|
||||
self, mock_daemon_running, mock_is_active_im, mock_is_available, mock_which
|
||||
):
|
||||
"""Test that X11 falls back to xdotool when IBus is not the active input method."""
|
||||
mock_is_available.return_value = True
|
||||
mock_is_active_im.return_value = False # IBus installed but not active
|
||||
mock_daemon_running.return_value = True
|
||||
mock_which.return_value = "/usr/bin/xdotool" # xdotool is available
|
||||
|
||||
with patch.dict("os.environ", {"XDG_SESSION_TYPE": "x11"}):
|
||||
from vocalinux.text_injection.text_injector import DesktopEnvironment, TextInjector
|
||||
|
||||
injector = TextInjector()
|
||||
|
||||
# Should fall back to X11 (xdotool), not X11_IBUS
|
||||
self.assertEqual(injector.environment, DesktopEnvironment.X11)
|
||||
|
||||
@patch("vocalinux.text_injection.text_injector.is_ibus_available")
|
||||
@patch("vocalinux.text_injection.text_injector.is_ibus_active_input_method")
|
||||
@patch("vocalinux.text_injection.text_injector.is_ibus_daemon_running")
|
||||
@patch("vocalinux.text_injection.text_injector.IBusTextInjector")
|
||||
def test_ibus_setup_exception_falls_back(
|
||||
self, mock_injector_class, mock_daemon_running, mock_is_active_im, mock_is_available
|
||||
):
|
||||
"""Test that exceptions during IBus setup fall back to alternative methods."""
|
||||
mock_is_available.return_value = True
|
||||
mock_is_active_im.return_value = True # IBus is the active input method
|
||||
mock_daemon_running.return_value = True
|
||||
mock_injector_class.side_effect = Exception("IBus setup failed")
|
||||
|
||||
with patch.dict("os.environ", {"XDG_SESSION_TYPE": "wayland"}):
|
||||
from vocalinux.text_injection.text_injector import DesktopEnvironment, TextInjector
|
||||
|
||||
injector = TextInjector()
|
||||
|
||||
# Should fall back to WAYLAND with ydotool after IBus setup fails
|
||||
self.assertEqual(injector.environment, DesktopEnvironment.WAYLAND)
|
||||
self.assertEqual(injector.wayland_tool, "ydotool")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user