* fix(text_injection): re-check for better tools after xdotool fallback When wtype fails due to compositor not supporting virtual keyboard protocol, the code switches to xdotool permanently. This prevents using ydotool even if its daemon is started later. This fix adds _try_recover_from_fallback() method that checks for better tools (ydotool with running daemon, or wtype with compositor support) on each text injection attempt when in fallback mode. Fixes issue #34 comment 3 (logs show xdotool but ydotool works) * feat(main): auto-start IBus daemon on Vocalinux launch This resolves the IBus autostart issue mentioned in issue #34. When Vocalinux starts, it now automatically starts the IBus daemon if: - IBus is installed on the system - IBus daemon is not already running This removes the need for users to manually configure IBus autostart on desktop environments where it doesn't start automatically (e.g., some KDE Plasma installations or minimal window managers). Changes: - Add start_ibus_daemon() function in ibus_engine.py - Call start_ibus_daemon() in main() after logging is initialized - Export start_ibus_daemon in text_injection/__init__.py
This commit is contained in:
@@ -188,6 +188,20 @@ def main():
|
||||
initialize_logging()
|
||||
logger.info("Logging system initialized")
|
||||
|
||||
# Try to start IBus daemon if not running (for text injection)
|
||||
# This helps on desktop environments where IBus doesn't start automatically
|
||||
try:
|
||||
from .text_injection import start_ibus_daemon
|
||||
|
||||
if start_ibus_daemon():
|
||||
logger.debug("IBus daemon started for text injection")
|
||||
except Exception as e:
|
||||
logger.debug(f"Could not start IBus daemon: {e}")
|
||||
|
||||
config_manager = ConfigManager()
|
||||
initialize_logging()
|
||||
logger.info("Logging system initialized")
|
||||
|
||||
config_manager = ConfigManager()
|
||||
saved_settings = config_manager.get_settings().get("speech_recognition", {})
|
||||
audio_settings = config_manager.get_settings().get("audio", {})
|
||||
|
||||
@@ -10,4 +10,5 @@ from .ibus_engine import ( # noqa: F401
|
||||
is_engine_active,
|
||||
is_engine_registered,
|
||||
is_ibus_available,
|
||||
start_ibus_daemon,
|
||||
)
|
||||
|
||||
@@ -22,6 +22,7 @@ import struct
|
||||
import subprocess
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
@@ -133,6 +134,43 @@ def is_ibus_daemon_running() -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def start_ibus_daemon():
|
||||
"""
|
||||
Start the IBus daemon if it's not already running.
|
||||
|
||||
This is useful for desktop environments where IBus doesn't start automatically,
|
||||
such as some KDE Plasma installations or minimal window managers.
|
||||
|
||||
Returns:
|
||||
True if daemon was started or already running, False on failure
|
||||
"""
|
||||
if is_ibus_daemon_running():
|
||||
return True
|
||||
|
||||
if not is_ibus_available():
|
||||
return False
|
||||
|
||||
try:
|
||||
# Start ibus-daemon in the background with XIM support
|
||||
# -x: Enable XIM (X Input Method)
|
||||
# -d: Run as daemon
|
||||
# -r: Replace existing daemon
|
||||
subprocess.Popen(
|
||||
["ibus-daemon", "-x", "-d", "-r"],
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
start_new_session=True,
|
||||
)
|
||||
# Give daemon time to start
|
||||
time.sleep(0.5)
|
||||
return is_ibus_daemon_running()
|
||||
except FileNotFoundError:
|
||||
# ibus-daemon not found
|
||||
return False
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def _get_expected_component_xml() -> str:
|
||||
"""Generate the expected component XML content for the current installation."""
|
||||
engine_script = Path(__file__).resolve()
|
||||
|
||||
@@ -239,6 +239,58 @@ class TextInjector:
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to test XWayland fallback: {e}")
|
||||
|
||||
def _try_recover_from_fallback(self):
|
||||
"""
|
||||
Try to recover from xdotool fallback mode by re-checking for better tools.
|
||||
|
||||
This allows switching to ydotool if the daemon was started after initial detection,
|
||||
or to wtype if the compositor now supports virtual keyboard.
|
||||
|
||||
Returns:
|
||||
True if a better tool was found and environment was updated, False otherwise
|
||||
"""
|
||||
if self.environment != DesktopEnvironment.WAYLAND_XDOTOOL:
|
||||
return False
|
||||
|
||||
logger.info("Checking for better Wayland text injection tools...")
|
||||
|
||||
# Check for ydotool with daemon running
|
||||
if shutil.which("ydotool"):
|
||||
try:
|
||||
subprocess.run(
|
||||
["ydotool", "type", ""],
|
||||
check=True,
|
||||
stderr=subprocess.PIPE,
|
||||
timeout=2,
|
||||
)
|
||||
self.wayland_tool = "ydotool"
|
||||
self.environment = DesktopEnvironment.WAYLAND
|
||||
logger.info("Recovered to ydotool - ydotoold daemon is now running")
|
||||
return True
|
||||
except (subprocess.CalledProcessError, subprocess.TimeoutExpired, FileNotFoundError):
|
||||
logger.debug("ydotool available but daemon not running")
|
||||
|
||||
# Check for wtype with compositor support
|
||||
if shutil.which("wtype"):
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["wtype", "test"],
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
check=False,
|
||||
)
|
||||
error_output = result.stderr.lower()
|
||||
if "compositor does not support" not in error_output and result.returncode == 0:
|
||||
self.wayland_tool = "wtype"
|
||||
self.environment = DesktopEnvironment.WAYLAND
|
||||
logger.info("Recovered to wtype - compositor now supports virtual keyboard")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.debug(f"Error testing wtype: {e}")
|
||||
|
||||
logger.debug("No better tools available, continuing with xdotool fallback")
|
||||
return False
|
||||
|
||||
def inject_text(self, text: str) -> bool:
|
||||
"""
|
||||
Inject text into the currently focused application.
|
||||
@@ -263,6 +315,11 @@ class TextInjector:
|
||||
# which passes text directly without shell interpretation
|
||||
logger.debug(f"Text to inject: '{text}'")
|
||||
|
||||
# Re-check for available tools in Wayland fallback mode
|
||||
# This allows switching to ydotool if the daemon was started after initial detection
|
||||
if self.environment == DesktopEnvironment.WAYLAND_XDOTOOL:
|
||||
self._try_recover_from_fallback()
|
||||
|
||||
try:
|
||||
if (
|
||||
self.environment == DesktopEnvironment.WAYLAND_IBUS
|
||||
@@ -289,7 +346,9 @@ class TextInjector:
|
||||
if "compositor does not support" in str(
|
||||
e
|
||||
).lower() + " " + stderr_msg.lower() and shutil.which("xdotool"):
|
||||
logger.info("Automatically switching to XWayland fallback permanently")
|
||||
logger.info(
|
||||
"Switching to XWayland fallback - will re-check for better tools"
|
||||
)
|
||||
self.environment = DesktopEnvironment.WAYLAND_XDOTOOL
|
||||
self._inject_with_xdotool(text)
|
||||
else:
|
||||
|
||||
Reference in New Issue
Block a user