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:
Jatin K Malik
2026-02-07 16:26:49 -08:00
committed by GitHub
parent 3bd9f8e1db
commit 2b498f0888
4 changed files with 149 additions and 25 deletions
+3
View File
@@ -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
View File
@@ -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:
+22 -2
View File
@@ -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(