fix: Phase 1 - Critical cross-distribution compatibility fixes (#158)
* fix: Phase 1 - Critical cross-distribution compatibility fixes This commit implements the first phase of cross-distribution compatibility improvements for Vocalinux, addressing the most critical issues that prevent the application from working on non-Ubuntu distributions. ## Changes ### install.sh - Add detect_typelib_path() function that dynamically discovers GI_TYPELIB_PATH using pkg-config with fallback to distribution-specific paths - Update wrapper scripts (vocalinux, vocalinux-gui) to use detected path - Support for: x86_64, arm64, armhf, riscv64, powerpc64le, s390x architectures - Specific paths for: Ubuntu multiarch, Fedora/RHEL (lib64), Arch (lib) ### src/vocalinux/speech_recognition/recognition_manager.py - Add _get_system_model_paths() function for dynamic model path discovery - Enhance ALSA library detection with multiple library name fallbacks: - libasound.so.2, libasound.so, libasound.so.0, asound - Support XDG_DATA_DIRS environment variable for model paths - Distribution-specific paths for Fedora/RHEL/CentOS/Rocky/AlmaLinux - Arch-specific handling (excludes /usr/local) ### src/vocalinux/utils/resource_manager.py - Enhanced _find_resources_dir() with more candidate paths - Support for XDG_DATA_DIRS environment variable - Add lib64 and lib paths for Fedora/RHEL distributions - Add Flatpak-specific path (/app/share/vocalinux/resources) ## Testing All 462 existing tests pass. ## Related Issues Fixes #157 (partial - Phase 1 of 5) * chore: Remove analysis docs from PR, add to .gitignore * style: Apply black formatting to pass lint checks
This commit is contained in:
@@ -146,3 +146,6 @@ temp/
|
||||
# Local Netlify folder
|
||||
.netlify
|
||||
.claude-workflow-rules.md
|
||||
# Analysis documents (keep local)
|
||||
CROSS_DISTRO_ANALYSIS.md
|
||||
FIX_PLAN.md
|
||||
|
||||
+41
-2
@@ -245,6 +245,41 @@ detect_nvidia_gpu() {
|
||||
fi
|
||||
}
|
||||
|
||||
# Detect GI_TYPELIB_PATH for cross-distro compatibility
|
||||
detect_typelib_path() {
|
||||
# Try pkg-config first (most reliable)
|
||||
if command -v pkg-config >/dev/null 2>&1; then
|
||||
local path=$(pkg-config --variable=typelibdir gobject-introspection-1.0 2>/dev/null)
|
||||
if [ -n "$path" ] && [ -d "$path" ]; then
|
||||
echo "$path"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Fallback to common distribution-specific paths
|
||||
# Order matters: more specific paths first
|
||||
for path in \
|
||||
/usr/lib/x86_64-linux-gnu/girepository-1.0 \
|
||||
/usr/lib/aarch64-linux-gnu/girepository-1.0 \
|
||||
/usr/lib/arm-linux-gnueabihf/girepository-1.0 \
|
||||
/usr/lib/riscv64-linux-gnu/girepository-1.0 \
|
||||
/usr/lib/powerpc64le-linux-gnu/girepository-1.0 \
|
||||
/usr/lib/s390x-linux-gnu/girepository-1.0 \
|
||||
/usr/lib64/girepository-1.0 \
|
||||
/usr/lib/girepository-1.0 \
|
||||
/usr/local/lib/girepository-1.0 \
|
||||
/usr/local/lib64/girepository-1.0; do
|
||||
if [ -d "$path" ]; then
|
||||
echo "$path"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
# Ultimate fallback - will cause issues if wrong, but at least we try
|
||||
echo "/usr/lib/girepository-1.0"
|
||||
return 1
|
||||
}
|
||||
|
||||
# Print section header for interactive mode
|
||||
clear_screen() {
|
||||
if [ -t 1 ] && command -v clear >/dev/null 2>&1 && [ -n "${TERM:-}" ]; then
|
||||
@@ -1069,6 +1104,10 @@ VOSK_INTERACTIVE_CONFIG
|
||||
# Clean up log file if installation was successful
|
||||
rm -rf "$PIP_LOG_DIR"
|
||||
|
||||
# Detect GI_TYPELIB_PATH for cross-distro compatibility
|
||||
GI_TYPELIB_DETECTED=$(detect_typelib_path)
|
||||
print_info "Detected GI_TYPELIB_PATH: $GI_TYPELIB_DETECTED"
|
||||
|
||||
# Create wrapper scripts in ~/.local/bin for easy access
|
||||
mkdir -p "$HOME/.local/bin"
|
||||
|
||||
@@ -1079,7 +1118,7 @@ VOSK_INTERACTIVE_CONFIG
|
||||
#!/bin/bash
|
||||
# Wrapper script for Vocalinux that sets required environment variables
|
||||
# and applies the 'input' group for keyboard shortcuts on Wayland
|
||||
export GI_TYPELIB_PATH=/usr/lib/girepository-1.0
|
||||
export GI_TYPELIB_PATH=$GI_TYPELIB_DETECTED
|
||||
|
||||
# Check if user is in input group but current session doesn't have it
|
||||
if grep -q "^input:.*\b\$(whoami)\b" /etc/group 2>/dev/null && ! groups | grep -q '\binput\b'; then
|
||||
@@ -1097,7 +1136,7 @@ WRAPPER_EOF
|
||||
#!/bin/bash
|
||||
# Wrapper script for Vocalinux GUI that sets required environment variables
|
||||
# and applies the 'input' group for keyboard shortcuts on Wayland
|
||||
export GI_TYPELIB_PATH=/usr/lib/girepository-1.0
|
||||
export GI_TYPELIB_PATH=$GI_TYPELIB_DETECTED
|
||||
|
||||
# Check if user is in input group but current session doesn't have it
|
||||
if grep -q "^input:.*\b\$(whoami)\b" /etc/group 2>/dev/null && ! groups | grep -q '\binput\b'; then
|
||||
|
||||
@@ -23,24 +23,33 @@ from .command_processor import CommandProcessor
|
||||
def _setup_alsa_error_handler():
|
||||
"""Set up an error handler to suppress ALSA warnings."""
|
||||
try:
|
||||
asound = ctypes.CDLL("libasound.so.2")
|
||||
# Define error handler type
|
||||
ERROR_HANDLER_FUNC = ctypes.CFUNCTYPE(
|
||||
None,
|
||||
ctypes.c_char_p,
|
||||
ctypes.c_int,
|
||||
ctypes.c_char_p,
|
||||
ctypes.c_int,
|
||||
ctypes.c_char_p,
|
||||
)
|
||||
# Try multiple library name variations for cross-distro compatibility
|
||||
# Different distributions may use different soname or library naming
|
||||
for lib_name in ["libasound.so.2", "libasound.so", "libasound.so.0", "asound"]:
|
||||
try:
|
||||
asound = ctypes.CDLL(lib_name)
|
||||
# Define error handler type
|
||||
ERROR_HANDLER_FUNC = ctypes.CFUNCTYPE(
|
||||
None,
|
||||
ctypes.c_char_p,
|
||||
ctypes.c_int,
|
||||
ctypes.c_char_p,
|
||||
ctypes.c_int,
|
||||
ctypes.c_char_p,
|
||||
)
|
||||
|
||||
# Create a no-op error handler
|
||||
def _error_handler(filename, line, function, err, fmt):
|
||||
pass
|
||||
# Create a no-op error handler
|
||||
def _error_handler(filename, line, function, err, fmt):
|
||||
pass
|
||||
|
||||
_alsa_error_handler = ERROR_HANDLER_FUNC(_error_handler)
|
||||
asound.snd_lib_error_set_handler(_alsa_error_handler)
|
||||
return _alsa_error_handler # Keep reference to prevent GC
|
||||
_alsa_error_handler = ERROR_HANDLER_FUNC(_error_handler)
|
||||
asound.snd_lib_error_set_handler(_alsa_error_handler)
|
||||
# Note: Can't use logger here as it's not defined yet
|
||||
return _alsa_error_handler # Keep reference to prevent GC
|
||||
except OSError:
|
||||
continue
|
||||
# If all library names fail, return None
|
||||
return None
|
||||
except (OSError, AttributeError):
|
||||
# ALSA not available or different platform
|
||||
return None
|
||||
@@ -218,11 +227,64 @@ def _show_notification(title: str, message: str, icon: str = "dialog-warning"):
|
||||
|
||||
# Define constants
|
||||
MODELS_DIR = os.path.expanduser("~/.local/share/vocalinux/models")
|
||||
# Alternative locations for pre-installed models
|
||||
SYSTEM_MODELS_DIRS = [
|
||||
"/usr/local/share/vocalinux/models",
|
||||
"/usr/share/vocalinux/models",
|
||||
]
|
||||
|
||||
|
||||
def _get_system_model_paths() -> list:
|
||||
"""
|
||||
Get system-wide model paths based on distro standards.
|
||||
|
||||
This function dynamically determines where system-wide models might be
|
||||
installed based on XDG standards and distribution-specific conventions.
|
||||
|
||||
Returns:
|
||||
List of paths to check for pre-installed models
|
||||
"""
|
||||
paths = []
|
||||
|
||||
# XDG standard paths from XDG_DATA_DIRS
|
||||
xdg_data = os.environ.get("XDG_DATA_DIRS", "/usr/local/share:/usr/share")
|
||||
for base in xdg_data.split(":"):
|
||||
if base: # Skip empty strings
|
||||
paths.append(os.path.join(base, "vocalinux", "models"))
|
||||
|
||||
# Distribution-specific paths
|
||||
# Try to detect the distribution from /etc/os-release
|
||||
try:
|
||||
with open("/etc/os-release", "r") as f:
|
||||
os_release = f.read().lower()
|
||||
|
||||
# Fedora/RHEL/CentOS/Rocky/AlmaLinux use /usr/lib64
|
||||
if any(
|
||||
id in os_release
|
||||
for id in ["fedora", "rhel", "centos", "rocky", "almalinux", "red hat"]
|
||||
):
|
||||
paths.append("/usr/lib64/vocalinux/models")
|
||||
paths.append("/usr/lib/vocalinux/models")
|
||||
|
||||
# Arch Linux doesn't use /usr/local
|
||||
if "arch" in os_release:
|
||||
paths.remove("/usr/local/share/vocalinux/models")
|
||||
|
||||
except (IOError, OSError, FileNotFoundError):
|
||||
pass # File doesn't exist on all systems
|
||||
|
||||
# Add common fallback paths that might be used
|
||||
additional_paths = [
|
||||
"/usr/local/lib/vocalinux/models",
|
||||
"/usr/lib/vocalinux/models",
|
||||
"/usr/lib64/vocalinux/models",
|
||||
"/opt/vocalinux/models", # Some distros use /opt
|
||||
]
|
||||
|
||||
for path in additional_paths:
|
||||
if path not in paths:
|
||||
paths.append(path)
|
||||
|
||||
return paths
|
||||
|
||||
|
||||
# Alternative locations for pre-installed models (now dynamic)
|
||||
SYSTEM_MODELS_DIRS = _get_system_model_paths()
|
||||
|
||||
|
||||
class SpeechRecognitionManager:
|
||||
|
||||
@@ -47,7 +47,7 @@ class ResourceManager:
|
||||
# Get the directory where this module is located
|
||||
module_dir = Path(__file__).parent.absolute()
|
||||
|
||||
# Try several methods to find the resources directory
|
||||
# Build list of candidate paths in priority order
|
||||
candidates = [
|
||||
# For direct repository execution (go up from src/vocalinux/utils to root)
|
||||
module_dir.parent.parent.parent / "resources",
|
||||
@@ -55,11 +55,31 @@ class ResourceManager:
|
||||
Path(sys.prefix) / "share" / "vocalinux" / "resources",
|
||||
# For development in virtual environment
|
||||
Path(sys.prefix).parent / "resources",
|
||||
# Additional fallbacks
|
||||
]
|
||||
|
||||
# Add XDG data directory paths
|
||||
xdg_data = os.environ.get("XDG_DATA_DIRS", "/usr/local/share:/usr/share")
|
||||
for base in xdg_data.split(":"):
|
||||
if base: # Skip empty strings
|
||||
candidates.append(Path(base) / "vocalinux" / "resources")
|
||||
|
||||
# Add distribution-specific standard paths
|
||||
additional_paths = [
|
||||
Path("/usr/local/share/vocalinux/resources"),
|
||||
Path("/usr/share/vocalinux/resources"),
|
||||
# Some distros use lib64 or lib for package data
|
||||
Path("/usr/local/lib/vocalinux/resources"),
|
||||
Path("/usr/lib/vocalinux/resources"),
|
||||
Path("/usr/local/lib64/vocalinux/resources"),
|
||||
Path("/usr/lib64/vocalinux/resources"),
|
||||
# Flatpak specific path
|
||||
Path("/app/share/vocalinux/resources"),
|
||||
]
|
||||
|
||||
for path in additional_paths:
|
||||
if path not in candidates:
|
||||
candidates.append(path)
|
||||
|
||||
# Log all candidates for debugging
|
||||
for candidate in candidates:
|
||||
logger.debug(
|
||||
|
||||
Reference in New Issue
Block a user