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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>
This commit is contained in:
Jatin K Malik
2026-02-04 22:31:24 -08:00
committed by GitHub
co-authored by Claude Sonnet 4.5 ACT900
parent b5f67b4bc0
commit 6f54dd80e0
5 changed files with 52 additions and 11 deletions
+2 -2
View File
@@ -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
+1 -1
View File
@@ -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
+40 -3
View File
@@ -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
}
+2 -1
View File
@@ -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 = '''
/(
@@ -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