From 6f54dd80e0e3e8ce97d835ec77f8da46c6196832 Mon Sep 17 00:00:00 2001 From: Jatin K Malik Date: Wed, 4 Feb 2026 22:31:24 -0800 Subject: [PATCH] Fix Python 3.13 and Wayland compatibility (#125) * Fix Python 3.13 and Wayland compatibility This commit addresses installation failures on modern Linux distributions (Ubuntu 25.10+) with Python 3.13 and Wayland-only sessions. Changes: 1. **Add Python 3.13 support** (pyproject.toml) - Add Python 3.13 to classifiers - Update black target versions to include py312, py313 - No code changes needed - already compatible 2. **Use system site-packages for PyGObject** (install.sh) - Change venv creation to use --system-site-packages flag - Avoids PyGObject build failures with Python 3.13 - System PyGObject (python3-gi) is already installed as dependency - Building from source is unnecessary and fragile with new Python versions 3. **Improve Wayland text injection support** (install.sh) - Install ydotool as fallback (wtype doesn't work with GNOME Wayland) - Add user to 'input' group for uinput device access - Install udev rule for /dev/uinput permissions - Provide clear warning about logout requirement Technical details: - wtype uses Wayland virtual keyboard protocol (not supported by GNOME) - ydotool uses Linux uinput kernel module (works with all compositors) - System PyGObject 3.50.0+ supports Python 3.13 - Building PyGObject 3.54.5 from source fails with Python 3.13 build tools Fixes installation on: - Ubuntu 25.10 (Questing Quokka) - Wayland-only, Python 3.13 - Arch Linux with Python 3.13 - Fedora 41+ with Python 3.13 Tested on: Ubuntu 25.10 with Python 3.13.7, GNOME 49 Wayland Co-Authored-By: Claude Sonnet 4.5 * Prioritize ydotool over wtype for universal Wayland compatibility wtype uses the virtual-keyboard Wayland protocol which is not supported by GNOME/Mutter compositors. ydotool uses the Linux uinput kernel module which works with all Wayland compositors including GNOME. This change swaps the priority order to check ydotool first, ensuring text injection works on GNOME Wayland (Ubuntu 25.10+) while still falling back to wtype for compositors that support it. Tested on Ubuntu 25.10 with GNOME 49 Wayland - text injection now works perfectly with ydotool. Co-Authored-By: Claude Sonnet 4.5 * ci: trigger workflow run * ci: run lint/tests on python 3.13 --------- Co-authored-by: ACT900 <179795730+ACT900@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.5 --- .github/workflows/unified-pipeline.yml | 4 +- .pre-commit-config.yaml | 2 +- install.sh | 43 +++++++++++++++++-- pyproject.toml | 3 +- src/vocalinux/text_injection/text_injector.py | 11 +++-- 5 files changed, 52 insertions(+), 11 deletions(-) diff --git a/.github/workflows/unified-pipeline.yml b/.github/workflows/unified-pipeline.yml index d472ffc..886dbde 100644 --- a/.github/workflows/unified-pipeline.yml +++ b/.github/workflows/unified-pipeline.yml @@ -60,7 +60,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: '3.10' + python-version: '3.13' cache: 'pip' - name: Install linting tools @@ -87,7 +87,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ['3.8', '3.10', '3.11'] + python-version: ['3.8', '3.10', '3.11', '3.13'] steps: - uses: actions/checkout@v4 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f39c5fd..152b885 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,7 +4,7 @@ repos: # Code formatting with Black - repo: https://github.com/psf/black - rev: 24.1.1 + rev: 26.1.0 hooks: - id: black language_version: python3 diff --git a/install.sh b/install.sh index 8497478..fb8a50e 100755 --- a/install.sh +++ b/install.sh @@ -656,6 +656,43 @@ install_text_input_tools() { print_warning "Please install 'wtype' manually for Wayland text input support." ;; esac + + # Try to install ydotool as additional fallback for Wayland + # ydotool works better with some compositors (like GNOME) where wtype may fail + print_info "Attempting to install ydotool for better Wayland compatibility..." + case "$DISTRO_FAMILY" in + ubuntu|debian) + if ! apt_package_installed "ydotool"; then + sudo apt install -y ydotool 2>/dev/null || print_info "ydotool not available in repos (optional)" + fi + ;; + fedora) + if command_exists dnf; then + sudo dnf install -y ydotool 2>/dev/null || print_info "ydotool not available in repos (optional)" + fi + ;; + arch) + if ! pacman_package_installed "ydotool"; then + sudo pacman -S --noconfirm ydotool 2>/dev/null || print_info "ydotool not available in repos (optional)" + fi + ;; + esac + + # Add user to input group for ydotool/dotool support + if ! groups | grep -q '\binput\b'; then + print_info "Adding $USER to 'input' group for text injection..." + sudo usermod -aG input "$USER" || print_warning "Failed to add user to input group" + print_warning "You will need to LOG OUT and back in for text injection to work with ydotool/dotool" + fi + + # Install udev rule for ydotool/dotool + if [ ! -f /etc/udev/rules.d/80-dotool.rules ]; then + print_info "Installing udev rule for input device access..." + echo 'KERNEL=="uinput", GROUP="input", MODE="0620", OPTIONS+="static_node=uinput"' \ + | sudo tee /etc/udev/rules.d/80-dotool.rules >/dev/null 2>&1 || print_warning "Failed to install udev rule" + sudo udevadm control --reload 2>/dev/null || true + sudo udevadm trigger 2>/dev/null || true + fi ;; x11|"") @@ -786,9 +823,9 @@ setup_virtual_environment() { fi # Create virtual environment - # Note: We don't use --system-site-packages to avoid conflicts with system packages - # that may be incompatible with Python 3.12+ (e.g., old coverage versions) - python3 -m venv "$VENV_DIR" || { + # Use --system-site-packages to access pre-compiled system packages like PyGObject + # This avoids build failures with Python 3.13+ where PyGObject may not build from source + python3 -m venv --system-site-packages "$VENV_DIR" || { print_error "Failed to create virtual environment. Please check your Python installation." exit 1 } diff --git a/pyproject.toml b/pyproject.toml index e694b52..45a4033 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,6 +31,7 @@ classifiers = [ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Topic :: Desktop Environment :: Gnome", "Topic :: Multimedia :: Sound/Audio :: Speech", "Topic :: Text Processing", @@ -95,7 +96,7 @@ version = {attr = "vocalinux.version.__version__"} [tool.black] line-length = 100 -target-version = ['py38', 'py39', 'py310', 'py311'] +target-version = ['py38', 'py39', 'py310', 'py311', 'py312', 'py313'] include = '\.pyi?$' exclude = ''' /( diff --git a/src/vocalinux/text_injection/text_injector.py b/src/vocalinux/text_injection/text_injector.py index b7d466f..f7d9fd9 100644 --- a/src/vocalinux/text_injection/text_injector.py +++ b/src/vocalinux/text_injection/text_injector.py @@ -118,15 +118,18 @@ class TextInjector: raise RuntimeError("Missing required dependency: xdotool") else: # Check for wtype or ydotool for Wayland + # Prefer ydotool as it works universally (including GNOME Wayland) wtype_available = shutil.which("wtype") is not None ydotool_available = shutil.which("ydotool") is not None xdotool_available = shutil.which("xdotool") is not None - if wtype_available: - self.wayland_tool = "wtype" - logger.info(f"Using {self.wayland_tool} for Wayland text injection") - elif ydotool_available: + if ydotool_available: self.wayland_tool = "ydotool" + logger.info( + f"Using {self.wayland_tool} for Wayland text injection (universal compatibility)" + ) + elif wtype_available: + self.wayland_tool = "wtype" logger.info(f"Using {self.wayland_tool} for Wayland text injection") elif xdotool_available: # Fallback to xdotool with XWayland