Import the vocalinux source tree (tray app, Next.js site in web/, packaging, installers, docs and tests) on top of the Gitea template commit. The project README replaces the template README, and the template CI files (.gitea/workflows/ci.yml, .jscpd.json, CI.md) are kept.
889 lines
40 KiB
Bash
889 lines
40 KiB
Bash
#!/bin/bash
|
|
# Sourced by install.sh after it has resolved the local or tagged repository.
|
|
# Keep this file declarative: function definitions only, with no top-level actions.
|
|
|
|
# Function to check if a command exists
|
|
command_exists() {
|
|
command -v "$1" >/dev/null 2>&1
|
|
}
|
|
|
|
# Function to check if a package is installed (for apt-based systems)
|
|
apt_package_installed() {
|
|
dpkg -s "$1" >/dev/null 2>&1
|
|
}
|
|
|
|
# Function to check if a package is installed (for dnf-based systems)
|
|
dnf_package_installed() {
|
|
rpm -q "$1" >/dev/null 2>&1
|
|
}
|
|
|
|
# Function to check if a package is installed (for pacman-based systems)
|
|
pacman_package_installed() {
|
|
pacman -Q "$1" >/dev/null 2>&1
|
|
}
|
|
|
|
# Install an AppIndicator/StatusNotifierItem provider, preferring the
|
|
# actively maintained Ayatana fork over the legacy Canonical package
|
|
# (unmaintained since ~2013). The legacy package can install and import
|
|
# without error yet silently fail to register a tray icon with KDE's
|
|
# StatusNotifierWatcher, leaving no icon and no logged error. $1 is the
|
|
# package-manager install command (e.g. "sudo dnf install -y"), $2/$3 are
|
|
# the Ayatana/legacy package names for that package manager, $4 is the
|
|
# "is this package installed" checker function name.
|
|
install_preferred_appindicator() {
|
|
local install_cmd="$1"
|
|
local ayatana_pkg="$2"
|
|
local legacy_pkg="$3"
|
|
local checker="$4"
|
|
|
|
if "$checker" "$ayatana_pkg"; then
|
|
return 0
|
|
fi
|
|
|
|
if $install_cmd "$ayatana_pkg" 2>/dev/null; then
|
|
print_info "Installed $ayatana_pkg (Ayatana AppIndicator; required for a working KDE tray icon)"
|
|
return 0
|
|
fi
|
|
|
|
if "$checker" "$legacy_pkg"; then
|
|
print_info "$ayatana_pkg not available; $legacy_pkg is already installed but may not show a tray icon on KDE Plasma"
|
|
return 0
|
|
fi
|
|
|
|
print_info "$ayatana_pkg not available, falling back to $legacy_pkg (may not show a tray icon on KDE Plasma)..."
|
|
if $install_cmd "$legacy_pkg"; then
|
|
return 0
|
|
fi
|
|
|
|
print_error "Failed to install an AppIndicator package (tried $ayatana_pkg and $legacy_pkg)"
|
|
return 1
|
|
}
|
|
|
|
suse_python_package_prefix() {
|
|
python3 -c 'import sys; print(f"python{sys.version_info.major}{sys.version_info.minor}")' 2>/dev/null || echo "python3"
|
|
}
|
|
|
|
suse_python_package_candidates() {
|
|
local suffix="$1"
|
|
local PY_PREFIX
|
|
PY_PREFIX=$(suse_python_package_prefix)
|
|
|
|
if [[ "$PY_PREFIX" != "python3" ]]; then
|
|
echo "${PY_PREFIX}-${suffix} python3-${suffix}"
|
|
else
|
|
echo "python3-${suffix}"
|
|
fi
|
|
}
|
|
|
|
suse_package_installed() {
|
|
rpm -q "$1" >/dev/null 2>&1
|
|
}
|
|
|
|
suse_install_first_available() {
|
|
local DESCRIPTION="$1"
|
|
shift
|
|
|
|
local PKG
|
|
for PKG in "$@"; do
|
|
[ -z "$PKG" ] && continue
|
|
|
|
if suse_package_installed "$PKG"; then
|
|
print_info "$DESCRIPTION is already installed ($PKG)."
|
|
return 0
|
|
fi
|
|
|
|
if sudo zypper install -y "$PKG" 2>/dev/null; then
|
|
print_success "Installed $DESCRIPTION ($PKG)."
|
|
return 0
|
|
fi
|
|
|
|
print_info "$DESCRIPTION package '$PKG' not available, trying next option..."
|
|
done
|
|
|
|
return 1
|
|
}
|
|
|
|
suse_appindicator_gi_available() {
|
|
python3 - <<'PY' >/dev/null 2>&1
|
|
import importlib
|
|
import gi
|
|
|
|
for namespace in ("AppIndicator3", "AyatanaAppIndicator3", "AyatanaAppindicator3"):
|
|
try:
|
|
gi.require_version(namespace, "0.1")
|
|
importlib.import_module(f"gi.repository.{namespace}")
|
|
raise SystemExit(0)
|
|
except (ImportError, ValueError):
|
|
pass
|
|
|
|
raise SystemExit(1)
|
|
PY
|
|
}
|
|
|
|
suse_install_appindicator_runtime() {
|
|
if suse_appindicator_gi_available; then
|
|
print_info "AppIndicator/Ayatana GI namespace is already available."
|
|
return 0
|
|
fi
|
|
|
|
local PKG
|
|
for PKG in "${APPINDICATOR_PACKAGES[@]}"; do
|
|
if suse_package_installed "$PKG"; then
|
|
print_info "AppIndicator/Ayatana package is already installed ($PKG); verifying GI namespace..."
|
|
elif sudo zypper install -y "$PKG" 2>/dev/null; then
|
|
print_success "Installed AppIndicator/Ayatana package ($PKG)."
|
|
# Refresh the shared-library cache so the GI typelib is discoverable
|
|
sudo ldconfig 2>/dev/null || true
|
|
else
|
|
print_info "AppIndicator/Ayatana package '$PKG' not available, trying next option..."
|
|
continue
|
|
fi
|
|
|
|
if suse_appindicator_gi_available; then
|
|
print_success "AppIndicator/Ayatana GI namespace is available."
|
|
return 0
|
|
fi
|
|
done
|
|
|
|
return 1
|
|
}
|
|
|
|
suse_shader_compiler_available() {
|
|
command_exists glslc || command_exists glslangValidator
|
|
}
|
|
|
|
resolve_debian_package_map_key() {
|
|
local DEBIAN_MAJOR="${DISTRO_VERSION%%.*}"
|
|
|
|
# Only Debian's own VERSION_ID identifies a Debian release. Derivatives
|
|
# often use an unrelated product version, so select their compatible map
|
|
# by probing the package inventory instead.
|
|
if [[ "$DISTRO_ID" == "debian" && "$DEBIAN_MAJOR" =~ ^[0-9]+$ ]]; then
|
|
if [ "$DEBIAN_MAJOR" -lt 12 ]; then
|
|
print_error "Debian 12 or newer is required (detected Debian $DEBIAN_MAJOR)." >&2
|
|
return "$EXIT_MISSING_DEPS"
|
|
elif [ "$DEBIAN_MAJOR" -ge 13 ]; then
|
|
echo "debian_13_plus"
|
|
else
|
|
echo "debian_12"
|
|
fi
|
|
else
|
|
# A missing cache is not evidence of an older Debian base. Refresh
|
|
# before probing; keep command substitution's stdout for the key only.
|
|
sudo apt update >&2 || {
|
|
print_error "Failed to refresh package indexes for Debian base detection." >&2
|
|
return "$EXIT_NETWORK"
|
|
}
|
|
if apt-cache show "$DEBIAN_13_PLUS_PROBE_PACKAGE" &>/dev/null 2>&1; then
|
|
echo "debian_13_plus"
|
|
else
|
|
echo "debian_12"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# Function to install system dependencies based on the detected distribution
|
|
install_system_dependencies() {
|
|
print_info "Installing system dependencies..."
|
|
|
|
local PACKAGE_MAP_KEY="$DISTRO_FAMILY"
|
|
if [[ "$DISTRO_FAMILY" == "debian" ]]; then
|
|
PACKAGE_MAP_KEY=$(resolve_debian_package_map_key) || exit "$?"
|
|
fi
|
|
case "$PACKAGE_MAP_KEY" in
|
|
ubuntu|debian_12|debian_13_plus|fedora|arch|suse|gentoo|alpine|void|solus|mageia)
|
|
load_distro_package_map "$PACKAGE_MAP_KEY" || exit "$EXIT_MISSING_DEPS"
|
|
;;
|
|
esac
|
|
|
|
# GObject Introspection / GLib headers for building PyGObject and friends.
|
|
# Prefer libgirepository-2.0-dev when available (Ubuntu 24.04+, Pop!_OS Cosmic+,
|
|
# Debian 13+). When both 1.0 and 2.0 packages exist, install both: 2.0 provides
|
|
# the modern GLib GI headers that pip builds need, while 1.0 still pulls
|
|
# gobject-introspection tooling. Older distros that only ship 1.0 keep that.
|
|
# See #571 (installer previously kept 1.0 whenever apt-cache still listed it).
|
|
if [[ "$DISTRO_FAMILY" == "ubuntu" ]]; then
|
|
local GI_DEV_PACKAGE
|
|
local GI_DEV_FOUND="no"
|
|
for GI_DEV_PACKAGE in "${GI_DEVELOPMENT_PACKAGES[@]}"; do
|
|
if apt-cache show "$GI_DEV_PACKAGE" &>/dev/null 2>&1; then
|
|
SYSTEM_PACKAGES+=("$GI_DEV_PACKAGE")
|
|
GI_DEV_FOUND="yes"
|
|
fi
|
|
done
|
|
if [[ "$GI_DEV_FOUND" == "no" ]]; then
|
|
SYSTEM_PACKAGES+=("${GI_DEVELOPMENT_PACKAGES[1]}")
|
|
fi
|
|
SYSTEM_PACKAGES+=("${APPINDICATOR_PACKAGES[0]}")
|
|
fi
|
|
|
|
if [[ "$DISTRO_FAMILY" == "ubuntu" || "$DISTRO_FAMILY" == "debian" ]]; then
|
|
local SHADER_PACKAGE="${SHADER_COMPILER_PACKAGES[1]}"
|
|
local SHADER_CANDIDATE
|
|
for SHADER_CANDIDATE in "${SHADER_COMPILER_PACKAGES[@]}"; do
|
|
if apt-cache show "$SHADER_CANDIDATE" &>/dev/null 2>&1; then
|
|
SHADER_PACKAGE="$SHADER_CANDIDATE"
|
|
break
|
|
fi
|
|
done
|
|
SYSTEM_PACKAGES+=("$SHADER_PACKAGE")
|
|
|
|
local OPTIONAL_PACKAGE
|
|
for OPTIONAL_PACKAGE in "${OPTIONAL_SYSTEM_PACKAGES[@]}"; do
|
|
if apt-cache show "$OPTIONAL_PACKAGE" &>/dev/null 2>&1; then
|
|
SYSTEM_PACKAGES+=("$OPTIONAL_PACKAGE")
|
|
fi
|
|
done
|
|
fi
|
|
|
|
local MISSING_PACKAGES=""
|
|
local INSTALL_CMD=""
|
|
local UPDATE_CMD=""
|
|
|
|
case "$DISTRO_FAMILY" in
|
|
ubuntu|debian)
|
|
# Check for missing packages
|
|
for pkg in "${SYSTEM_PACKAGES[@]}"; do
|
|
if ! apt_package_installed "$pkg"; then
|
|
MISSING_PACKAGES="$MISSING_PACKAGES $pkg"
|
|
fi
|
|
done
|
|
|
|
if [ -n "$MISSING_PACKAGES" ]; then
|
|
print_info "Installing missing packages:$MISSING_PACKAGES"
|
|
sudo apt update || { print_error "Failed to update package lists"; exit "$EXIT_NETWORK"; }
|
|
|
|
# Handle appindicator package for Ubuntu (old package deprecated in newer releases)
|
|
if [[ "$DISTRO_FAMILY" == "ubuntu" ]] && echo "$MISSING_PACKAGES" | grep -q "${APPINDICATOR_PACKAGES[0]}"; then
|
|
FILTERED_PACKAGES=$(echo "$MISSING_PACKAGES" | sed "s/${APPINDICATOR_PACKAGES[0]}//" | xargs)
|
|
|
|
if ! DEBIAN_FRONTEND=noninteractive sudo apt install -y "${APPINDICATOR_PACKAGES[0]}" 2>/dev/null; then
|
|
print_info "${APPINDICATOR_PACKAGES[0]} not available, trying ${APPINDICATOR_PACKAGES[1]}..."
|
|
if ! DEBIAN_FRONTEND=noninteractive sudo apt install -y "${APPINDICATOR_PACKAGES[1]}"; then
|
|
print_error "Failed to install appindicator package (tried both ${APPINDICATOR_PACKAGES[*]})"
|
|
exit "$EXIT_MISSING_DEPS"
|
|
fi
|
|
print_info "Successfully installed ${APPINDICATOR_PACKAGES[1]} (modern replacement)"
|
|
fi
|
|
|
|
if [ -n "$FILTERED_PACKAGES" ]; then
|
|
DEBIAN_FRONTEND=noninteractive sudo apt install -y $FILTERED_PACKAGES || { print_error "Failed to install dependencies"; exit "$EXIT_MISSING_DEPS"; }
|
|
fi
|
|
else
|
|
DEBIAN_FRONTEND=noninteractive sudo apt install -y $MISSING_PACKAGES || { print_error "Failed to install dependencies"; exit "$EXIT_MISSING_DEPS"; }
|
|
fi
|
|
else
|
|
print_info "All required packages are already installed."
|
|
fi
|
|
;;
|
|
|
|
fedora)
|
|
# For Fedora/RHEL-based systems
|
|
if command_exists dnf; then
|
|
INSTALL_CMD="sudo dnf install -y"
|
|
UPDATE_CMD="sudo dnf check-update"
|
|
elif command_exists yum; then
|
|
INSTALL_CMD="sudo yum install -y"
|
|
UPDATE_CMD="sudo yum check-update"
|
|
else
|
|
print_error "No supported package manager found (dnf/yum)"
|
|
exit "$EXIT_MISSING_DEPS"
|
|
fi
|
|
|
|
# Check for missing packages
|
|
for pkg in "${SYSTEM_PACKAGES[@]}"; do
|
|
if ! dnf_package_installed "$pkg"; then
|
|
MISSING_PACKAGES="$MISSING_PACKAGES $pkg"
|
|
fi
|
|
done
|
|
|
|
if [ -n "$MISSING_PACKAGES" ]; then
|
|
print_info "Installing missing packages:$MISSING_PACKAGES"
|
|
$UPDATE_CMD || true # dnf check-update returns 100 if updates available
|
|
$INSTALL_CMD $MISSING_PACKAGES || { print_error "Failed to install dependencies"; exit "$EXIT_MISSING_DEPS"; }
|
|
else
|
|
print_info "All required packages are already installed."
|
|
fi
|
|
|
|
install_preferred_appindicator "$INSTALL_CMD" "${APPINDICATOR_PACKAGES[0]}" "${APPINDICATOR_PACKAGES[1]}" dnf_package_installed || exit "$EXIT_MISSING_DEPS"
|
|
;;
|
|
|
|
arch)
|
|
# For Arch-based systems
|
|
if ! command_exists pacman; then
|
|
print_error "Pacman package manager not found"
|
|
exit "$EXIT_MISSING_DEPS"
|
|
fi
|
|
|
|
# Check for missing packages
|
|
for pkg in "${SYSTEM_PACKAGES[@]}"; do
|
|
if ! pacman_package_installed "$pkg"; then
|
|
MISSING_PACKAGES="$MISSING_PACKAGES $pkg"
|
|
fi
|
|
done
|
|
|
|
if [ -n "$MISSING_PACKAGES" ]; then
|
|
print_info "Installing missing packages:$MISSING_PACKAGES"
|
|
sudo pacman -Sy
|
|
sudo pacman -S --noconfirm $MISSING_PACKAGES || { print_error "Failed to install dependencies"; exit "$EXIT_MISSING_DEPS"; }
|
|
else
|
|
print_info "All required packages are already installed."
|
|
fi
|
|
|
|
install_preferred_appindicator "sudo pacman -S --noconfirm" "${APPINDICATOR_PACKAGES[0]}" "${APPINDICATOR_PACKAGES[1]}" pacman_package_installed || exit "$EXIT_MISSING_DEPS"
|
|
;;
|
|
|
|
suse)
|
|
# For openSUSE
|
|
if ! command_exists zypper; then
|
|
print_error "Zypper package manager not found"
|
|
exit "$EXIT_MISSING_DEPS"
|
|
fi
|
|
|
|
sudo zypper refresh || true
|
|
|
|
if [[ "${SELECTED_ENGINE:-whisper_cpp}" == "whisper_cpp" && "${WHISPERCPP_BACKEND:-}" != "cpu" ]]; then
|
|
SYSTEM_PACKAGES+=("${VULKAN_PACKAGES[@]}")
|
|
fi
|
|
|
|
local MISSING_ZYPPER_PACKAGES=()
|
|
for pkg in "${SYSTEM_PACKAGES[@]}"; do
|
|
if ! suse_package_installed "$pkg"; then
|
|
MISSING_ZYPPER_PACKAGES+=("$pkg")
|
|
fi
|
|
done
|
|
|
|
if [ "${#MISSING_ZYPPER_PACKAGES[@]}" -gt 0 ]; then
|
|
print_info "Installing missing packages: ${MISSING_ZYPPER_PACKAGES[*]}"
|
|
sudo zypper install -y "${MISSING_ZYPPER_PACKAGES[@]}" || {
|
|
print_error "Failed to install openSUSE base dependencies"
|
|
exit "$EXIT_MISSING_DEPS"
|
|
}
|
|
else
|
|
print_info "All base openSUSE packages are already installed."
|
|
fi
|
|
|
|
local PY_PIP_CANDIDATES=()
|
|
local PY_GOBJECT_CANDIDATES=()
|
|
local PY_GOBJECT_CAIRO_CANDIDATES=()
|
|
local PY_DEVEL_CANDIDATES=()
|
|
local PY_VIRTUALENV_CANDIDATES=()
|
|
local PY_VENV_CANDIDATES=()
|
|
|
|
read -r -a PY_PIP_CANDIDATES <<< "$(suse_python_package_candidates "$PYTHON_PIP_SUFFIX")"
|
|
read -r -a PY_GOBJECT_CANDIDATES <<< "$(suse_python_package_candidates "$PYTHON_GOBJECT_SUFFIX")"
|
|
read -r -a PY_GOBJECT_CAIRO_CANDIDATES <<< "$(suse_python_package_candidates "$PYTHON_GOBJECT_CAIRO_SUFFIX")"
|
|
read -r -a PY_DEVEL_CANDIDATES <<< "$(suse_python_package_candidates "$PYTHON_DEVEL_SUFFIX")"
|
|
read -r -a PY_VIRTUALENV_CANDIDATES <<< "$(suse_python_package_candidates "$PYTHON_VIRTUALENV_SUFFIX")"
|
|
read -r -a PY_VENV_CANDIDATES <<< "$(suse_python_package_candidates "$PYTHON_VENV_SUFFIX")"
|
|
|
|
print_info "Resolving openSUSE Python packages for $(suse_python_package_prefix)..."
|
|
|
|
if ! suse_install_first_available "Python pip" "${PY_PIP_CANDIDATES[@]}"; then
|
|
print_error "Failed to install Python pip package (tried: ${PY_PIP_CANDIDATES[*]})"
|
|
exit "$EXIT_MISSING_DEPS"
|
|
fi
|
|
|
|
if ! suse_install_first_available "PyGObject bindings" "${PY_GOBJECT_CANDIDATES[@]}"; then
|
|
print_error "Failed to install PyGObject package (tried: ${PY_GOBJECT_CANDIDATES[*]})"
|
|
exit "$EXIT_MISSING_DEPS"
|
|
fi
|
|
|
|
if ! suse_install_first_available "PyGObject Cairo bindings" "${PY_GOBJECT_CAIRO_CANDIDATES[@]}"; then
|
|
print_error "Failed to install PyGObject Cairo package (tried: ${PY_GOBJECT_CAIRO_CANDIDATES[*]})"
|
|
exit "$EXIT_MISSING_DEPS"
|
|
fi
|
|
|
|
if ! suse_install_first_available "Python development headers" "${PY_DEVEL_CANDIDATES[@]}"; then
|
|
print_error "Failed to install Python development headers (tried: ${PY_DEVEL_CANDIDATES[*]})"
|
|
exit "$EXIT_MISSING_DEPS"
|
|
fi
|
|
|
|
if ! suse_install_first_available "Python virtualenv/venv" "${PY_VIRTUALENV_CANDIDATES[@]}" "${PY_VENV_CANDIDATES[@]}"; then
|
|
print_warning "Python virtualenv/venv package was not found (tried: ${PY_VIRTUALENV_CANDIDATES[*]} ${PY_VENV_CANDIDATES[*]})"
|
|
print_warning "Continuing because python3 -m venv may still be available."
|
|
fi
|
|
|
|
if ! suse_install_appindicator_runtime; then
|
|
print_error "Failed to install a working AppIndicator/Ayatana GI runtime on openSUSE."
|
|
print_error "Try manually: sudo zypper install ${APPINDICATOR_PACKAGES[0]} ${APPINDICATOR_PACKAGES[3]}"
|
|
exit "$EXIT_MISSING_DEPS"
|
|
fi
|
|
|
|
if [[ "${SELECTED_ENGINE:-whisper_cpp}" == "whisper_cpp" && "${WHISPERCPP_BACKEND:-}" != "cpu" ]]; then
|
|
if ! suse_shader_compiler_available; then
|
|
if ! suse_install_first_available "Vulkan shader compiler" "${SHADER_COMPILER_PACKAGES[@]}"; then
|
|
print_warning "No Vulkan shader compiler found - whisper.cpp Vulkan build may fail"
|
|
print_warning "Install shaderc manually for glslc support if you want GPU acceleration."
|
|
fi
|
|
fi
|
|
|
|
if ! suse_shader_compiler_available; then
|
|
print_warning "glslc/glslangValidator is still unavailable; CPU fallback will be used if Vulkan build fails."
|
|
fi
|
|
fi
|
|
;;
|
|
|
|
gentoo)
|
|
# For Gentoo Linux
|
|
if ! command_exists emerge; then
|
|
print_error "Emerge package manager not found"
|
|
exit "$EXIT_MISSING_DEPS"
|
|
fi
|
|
|
|
print_info "Gentoo detected. Installing dependencies..."
|
|
print_warning "Gentoo uses emerge. This may take longer as packages are compiled from source."
|
|
|
|
# Check for missing packages
|
|
MISSING_PACKAGES=""
|
|
for pkg in "${SYSTEM_PACKAGES[@]}"; do
|
|
# Gentoo uses qlist to check if packages are installed
|
|
if ! qlist -I "$pkg" >/dev/null 2>&1; then
|
|
MISSING_PACKAGES="$MISSING_PACKAGES $pkg"
|
|
fi
|
|
done
|
|
|
|
if [ -n "$MISSING_PACKAGES" ]; then
|
|
print_info "Installing packages:$MISSING_PACKAGES"
|
|
# Update Portage tree first
|
|
sudo emerge --sync || { print_error "Failed to sync Portage tree"; exit "$EXIT_NETWORK"; }
|
|
# Install missing packages
|
|
sudo emerge $MISSING_PACKAGES || { print_error "Failed to install dependencies"; exit "$EXIT_MISSING_DEPS"; }
|
|
else
|
|
print_info "All required packages are already installed."
|
|
fi
|
|
;;
|
|
|
|
alpine)
|
|
# For Alpine Linux
|
|
if ! command_exists apk; then
|
|
print_error "Apk package manager not found"
|
|
exit "$EXIT_MISSING_DEPS"
|
|
fi
|
|
|
|
print_info "Alpine Linux detected."
|
|
print_warning "Alpine uses musl libc. Some Python packages may not have pre-built wheels."
|
|
|
|
# Check for missing packages
|
|
MISSING_PACKAGES=""
|
|
for pkg in "${SYSTEM_PACKAGES[@]}"; do
|
|
if ! apk info -e "$pkg" >/dev/null 2>&1; then
|
|
MISSING_PACKAGES="$MISSING_PACKAGES $pkg"
|
|
fi
|
|
done
|
|
|
|
if [ -n "$MISSING_PACKAGES" ]; then
|
|
print_info "Installing packages:$MISSING_PACKAGES"
|
|
sudo apk update || { print_error "Failed to update package indexes"; exit "$EXIT_NETWORK"; }
|
|
sudo apk add $MISSING_PACKAGES || { print_error "Failed to install dependencies"; exit "$EXIT_MISSING_DEPS"; }
|
|
else
|
|
print_info "All required packages are already installed."
|
|
fi
|
|
;;
|
|
|
|
void)
|
|
# For Void Linux
|
|
if ! command_exists xbps; then
|
|
print_error "Xbps package manager not found"
|
|
exit "$EXIT_MISSING_DEPS"
|
|
fi
|
|
|
|
print_info "Void Linux detected."
|
|
|
|
# Check for missing packages
|
|
MISSING_PACKAGES=""
|
|
for pkg in "${SYSTEM_PACKAGES[@]}"; do
|
|
if ! xbps-query "$pkg" >/dev/null 2>&1; then
|
|
MISSING_PACKAGES="$MISSING_PACKAGES $pkg"
|
|
fi
|
|
done
|
|
|
|
if [ -n "$MISSING_PACKAGES" ]; then
|
|
print_info "Installing packages:$MISSING_PACKAGES"
|
|
sudo xbps-install -Sy $MISSING_PACKAGES || { print_error "Failed to install dependencies"; exit "$EXIT_MISSING_DEPS"; }
|
|
else
|
|
print_info "All required packages are already installed."
|
|
fi
|
|
;;
|
|
|
|
solus)
|
|
# For Solus
|
|
if ! command_exists eopkg; then
|
|
print_error "Eopkg package manager not found"
|
|
exit "$EXIT_MISSING_DEPS"
|
|
fi
|
|
|
|
print_info "Solus detected."
|
|
|
|
# Check for missing packages
|
|
MISSING_PACKAGES=""
|
|
for pkg in "${SYSTEM_PACKAGES[@]}"; do
|
|
if ! eopkg list-installed | grep -qw "$pkg"; then
|
|
MISSING_PACKAGES="$MISSING_PACKAGES $pkg"
|
|
fi
|
|
done
|
|
|
|
if [ -n "$MISSING_PACKAGES" ]; then
|
|
print_info "Installing packages:$MISSING_PACKAGES"
|
|
sudo eopkg install $MISSING_PACKAGES || { print_error "Failed to install dependencies"; exit "$EXIT_MISSING_DEPS"; }
|
|
else
|
|
print_info "All required packages are already installed."
|
|
fi
|
|
;;
|
|
|
|
mageia)
|
|
# For Mageia
|
|
if command_exists dnf; then
|
|
INSTALL_CMD="sudo dnf install -y"
|
|
UPDATE_CMD="sudo dnf check-update"
|
|
elif command_exists urpmi; then
|
|
INSTALL_CMD="sudo urpmi --force"
|
|
UPDATE_CMD="sudo urpmi.update -a"
|
|
else
|
|
print_error "No supported package manager found (dnf/urpmi)"
|
|
exit "$EXIT_MISSING_DEPS"
|
|
fi
|
|
|
|
# Use similar packages to Fedora/RHEL
|
|
for pkg in "${SYSTEM_PACKAGES[@]}"; do
|
|
# Mageia uses rpm like Fedora
|
|
if ! rpm -q "$pkg" >/dev/null 2>&1; then
|
|
MISSING_PACKAGES="$MISSING_PACKAGES $pkg"
|
|
fi
|
|
done
|
|
|
|
if [ -n "$MISSING_PACKAGES" ]; then
|
|
print_info "Installing missing packages:$MISSING_PACKAGES"
|
|
$UPDATE_CMD 2>/dev/null || true
|
|
$INSTALL_CMD $MISSING_PACKAGES || { print_error "Failed to install dependencies"; exit "$EXIT_MISSING_DEPS"; }
|
|
else
|
|
print_info "All required packages are already installed."
|
|
fi
|
|
;;
|
|
|
|
*)
|
|
print_error "Unsupported distribution family: $DISTRO_FAMILY"
|
|
print_info ""
|
|
print_info "Your distribution ($DISTRO_NAME) is not officially supported."
|
|
print_info "However, you can still install Vocalinux manually:"
|
|
print_info ""
|
|
print_info "1. Run the dependency checker:"
|
|
print_info " bash scripts/check-system-deps.sh"
|
|
print_info ""
|
|
print_info "2. Install missing dependencies using your package manager"
|
|
print_info ""
|
|
print_info "3. Run the installer with --skip-system-deps:"
|
|
print_info " ./install.sh --skip-system-deps"
|
|
print_info ""
|
|
print_info "4. Or install from source in a virtual environment:"
|
|
print_info " /usr/bin/python3 -m venv --system-site-packages venv"
|
|
print_info " source venv/bin/activate"
|
|
print_info " pip install -e .[whisper,vad]"
|
|
print_info ""
|
|
print_info "For more information, see the project wiki:"
|
|
print_info " https://github.com/VocaHQ/vocalinux/wiki"
|
|
print_info ""
|
|
if [[ "$NON_INTERACTIVE" != "yes" ]]; then
|
|
read_prompt -p "Continue anyway? (y/n) " -n 1 -r
|
|
echo
|
|
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
|
exit "$EXIT_USER_ABORT"
|
|
fi
|
|
else
|
|
print_info "Non-interactive mode: continuing (dependencies may be missing)..."
|
|
fi
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# Function to detect and install text input tools
|
|
install_text_input_tools() {
|
|
if [[ "$SKIP_SYSTEM_DEPS" == "yes" ]]; then
|
|
print_warning "Skipping text input tool installation (--skip-system-deps specified)."
|
|
return 0
|
|
fi
|
|
|
|
# Unsupported distros can continue past install_system_dependencies in
|
|
# non-interactive mode, but they have no generated package inventory.
|
|
if [[ -z "${XDOTOOL_PACKAGES+x}" ]]; then
|
|
print_warning "No text input package map is available for $DISTRO_NAME."
|
|
print_warning "Install xdotool or wtype manually for your display server."
|
|
return 0
|
|
fi
|
|
|
|
local XDOTOOL_PKG="${XDOTOOL_PACKAGES[0]}"
|
|
local WTYPE_PKG="${WTYPE_PACKAGES[0]}"
|
|
local YDOTOOL_PKG="${YDOTOOL_PACKAGES[0]}"
|
|
|
|
# Detect session type more robustly
|
|
local SESSION_TYPE="unknown"
|
|
|
|
# Check XDG_SESSION_TYPE first. These are often unset without a login
|
|
# session even when DISPLAY is set; ${var:-} keeps set -u from aborting.
|
|
if [ -n "${XDG_SESSION_TYPE:-}" ]; then
|
|
SESSION_TYPE="${XDG_SESSION_TYPE:-}"
|
|
# Check for Wayland-specific environment variables
|
|
elif [ -n "${WAYLAND_DISPLAY:-}" ]; then
|
|
SESSION_TYPE="wayland"
|
|
# Check if X server is running
|
|
elif [ -n "${DISPLAY:-}" ] && command_exists xset && xset q &>/dev/null; then
|
|
SESSION_TYPE="x11"
|
|
# Check loginctl if available
|
|
elif command_exists loginctl; then
|
|
SESSION_TYPE=$(loginctl show-session $(loginctl | grep $(whoami) | awk '{print $1}') -p Type | cut -d= -f2 || true)
|
|
fi
|
|
|
|
print_info "Detected session type: $SESSION_TYPE"
|
|
if [[ "$SESSION_TYPE" == "wayland" ]] && is_kde_plasma_session; then
|
|
print_kde_wayland_ibus_hint
|
|
fi
|
|
|
|
# Install appropriate tools based on session type and distribution
|
|
case "$SESSION_TYPE" in
|
|
wayland)
|
|
print_info "Installing Wayland text input tools..."
|
|
case "$DISTRO_FAMILY" in
|
|
ubuntu|debian)
|
|
if ! apt_package_installed "$WTYPE_PKG"; then
|
|
DEBIAN_FRONTEND=noninteractive sudo apt install -y "$WTYPE_PKG" || { print_warning "Failed to install wtype. Text injection may not work properly."; }
|
|
else
|
|
print_info "wtype is already installed."
|
|
fi
|
|
;;
|
|
fedora)
|
|
if command_exists dnf && ! dnf_package_installed "$WTYPE_PKG"; then
|
|
sudo dnf install -y "$WTYPE_PKG" || { print_warning "Failed to install wtype. Text injection may not work properly."; }
|
|
elif command_exists yum && ! rpm -q "$WTYPE_PKG" &>/dev/null; then
|
|
sudo yum install -y "$WTYPE_PKG" || { print_warning "Failed to install wtype. Text injection may not work properly."; }
|
|
else
|
|
print_info "wtype is already installed."
|
|
fi
|
|
;;
|
|
arch)
|
|
if ! pacman_package_installed "$WTYPE_PKG"; then
|
|
sudo pacman -S --noconfirm "$WTYPE_PKG" || { print_warning "Failed to install wtype. Text injection may not work properly."; }
|
|
else
|
|
print_info "wtype is already installed."
|
|
fi
|
|
;;
|
|
suse)
|
|
sudo zypper install -y "$WTYPE_PKG" || { print_warning "Failed to install wtype. Text injection may not work properly."; }
|
|
;;
|
|
gentoo)
|
|
if ! qlist -I "$WTYPE_PKG" >/dev/null 2>&1; then
|
|
sudo emerge "$WTYPE_PKG" || { print_warning "Failed to install wtype. Text injection may not work properly."; }
|
|
else
|
|
print_info "wtype is already installed."
|
|
fi
|
|
;;
|
|
alpine)
|
|
if ! apk info -e "$WTYPE_PKG" >/dev/null 2>&1; then
|
|
sudo apk add "$WTYPE_PKG" || { print_warning "Failed to install wtype. Text injection may not work properly."; }
|
|
else
|
|
print_info "wtype is already installed."
|
|
fi
|
|
;;
|
|
void)
|
|
if ! xbps-query "$WTYPE_PKG" >/dev/null 2>&1; then
|
|
sudo xbps-install -Sy "$WTYPE_PKG" || { print_warning "Failed to install wtype. Text injection may not work properly."; }
|
|
else
|
|
print_info "wtype is already installed."
|
|
fi
|
|
;;
|
|
solus)
|
|
if ! eopkg list-installed | grep -qw "$WTYPE_PKG"; then
|
|
sudo eopkg install "$WTYPE_PKG" || { print_warning "Failed to install wtype. Text injection may not work properly."; }
|
|
else
|
|
print_info "wtype is already installed."
|
|
fi
|
|
;;
|
|
mageia)
|
|
if command_exists dnf && ! rpm -q "$WTYPE_PKG" >/dev/null 2>&1; then
|
|
sudo dnf install -y "$WTYPE_PKG" || { print_warning "Failed to install wtype. Text injection may not work properly."; }
|
|
elif command_exists urpmi && ! rpm -q "$WTYPE_PKG" >/dev/null 2>&1; then
|
|
sudo urpmi -y "$WTYPE_PKG" || { print_warning "Failed to install wtype. Text injection may not work properly."; }
|
|
else
|
|
print_info "wtype is already installed."
|
|
fi
|
|
;;
|
|
*)
|
|
print_warning "Unsupported distribution for Wayland 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_PKG"; then
|
|
if ! DEBIAN_FRONTEND=noninteractive sudo apt install -y "$YDOTOOL_PKG" 2>/dev/null; then
|
|
if [[ "$DISTRO_FAMILY" == "debian" ]]; then
|
|
print_warning "ydotool is not packaged in Debian's standard repos."
|
|
print_info "For full Wayland input support, you can compile ydotool from source:"
|
|
print_info " sudo apt install -y git cmake libevdev-dev"
|
|
print_info " git clone https://github.com/ReimuNotMoe/ydotool.git /tmp/ydotool"
|
|
print_info " cmake -S /tmp/ydotool -B /tmp/ydotool/build && sudo cmake --build /tmp/ydotool/build --target install"
|
|
print_info " sudo systemctl enable --now ydotoold"
|
|
print_info "Alternatively, wtype (already installed) will handle most Wayland compositors."
|
|
else
|
|
print_info "ydotool not available in repos (optional)"
|
|
fi
|
|
fi
|
|
fi
|
|
;;
|
|
fedora)
|
|
if command_exists dnf; then
|
|
sudo dnf install -y "$YDOTOOL_PKG" 2>/dev/null || print_info "ydotool not available in repos (optional)"
|
|
fi
|
|
;;
|
|
arch)
|
|
if ! pacman_package_installed "$YDOTOOL_PKG"; then
|
|
sudo pacman -S --noconfirm "$YDOTOOL_PKG" 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 — and for the evdev
|
|
# backend's uinput clone, which opens /dev/uinput O_RDWR, so the
|
|
# rule needs 0660 (0620 is write-only). Appending (not overwriting)
|
|
# preserves any rules the user already keeps in this file; when an
|
|
# older MODE=0620 line is present our later line still wins.
|
|
UINPUT_UDEV_RULE='KERNEL=="uinput", GROUP="input", MODE="0660", OPTIONS+="static_node=uinput"'
|
|
if ! grep -qxF "$UINPUT_UDEV_RULE" /etc/udev/rules.d/80-dotool.rules 2>/dev/null; then
|
|
print_info "Installing udev rule for input device access..."
|
|
printf '\n%s\n' "$UINPUT_UDEV_RULE" \
|
|
| sudo tee -a /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|"")
|
|
print_info "Installing X11 text input tools..."
|
|
case "$DISTRO_FAMILY" in
|
|
ubuntu|debian)
|
|
if ! apt_package_installed "$XDOTOOL_PKG"; then
|
|
DEBIAN_FRONTEND=noninteractive sudo apt install -y "$XDOTOOL_PKG" || { print_warning "Failed to install xdotool. Text injection may not work properly."; }
|
|
else
|
|
print_info "xdotool is already installed."
|
|
fi
|
|
;;
|
|
fedora)
|
|
if command_exists dnf && ! dnf_package_installed "$XDOTOOL_PKG"; then
|
|
sudo dnf install -y "$XDOTOOL_PKG" || { print_warning "Failed to install xdotool. Text injection may not work properly."; }
|
|
elif command_exists yum && ! rpm -q "$XDOTOOL_PKG" &>/dev/null; then
|
|
sudo yum install -y "$XDOTOOL_PKG" || { print_warning "Failed to install xdotool. Text injection may not work properly."; }
|
|
else
|
|
print_info "xdotool is already installed."
|
|
fi
|
|
;;
|
|
arch)
|
|
if ! pacman_package_installed "$XDOTOOL_PKG"; then
|
|
sudo pacman -S --noconfirm "$XDOTOOL_PKG" || { print_warning "Failed to install xdotool. Text injection may not work properly."; }
|
|
else
|
|
print_info "xdotool is already installed."
|
|
fi
|
|
;;
|
|
suse)
|
|
sudo zypper install -y "$XDOTOOL_PKG" || { print_warning "Failed to install xdotool. Text injection may not work properly."; }
|
|
;;
|
|
gentoo)
|
|
if ! qlist -I "$XDOTOOL_PKG" >/dev/null 2>&1; then
|
|
sudo emerge "$XDOTOOL_PKG" || { print_warning "Failed to install xdotool. Text injection may not work properly."; }
|
|
else
|
|
print_info "xdotool is already installed."
|
|
fi
|
|
;;
|
|
alpine)
|
|
if ! apk info -e "$XDOTOOL_PKG" >/dev/null 2>&1; then
|
|
sudo apk add "$XDOTOOL_PKG" || { print_warning "Failed to install xdotool. Text injection may not work properly."; }
|
|
else
|
|
print_info "xdotool is already installed."
|
|
fi
|
|
;;
|
|
void)
|
|
if ! xbps-query "$XDOTOOL_PKG" >/dev/null 2>&1; then
|
|
sudo xbps-install -Sy "$XDOTOOL_PKG" || { print_warning "Failed to install xdotool. Text injection may not work properly."; }
|
|
else
|
|
print_info "xdotool is already installed."
|
|
fi
|
|
;;
|
|
solus)
|
|
if ! eopkg list-installed | grep -qw "$XDOTOOL_PKG"; then
|
|
sudo eopkg install "$XDOTOOL_PKG" || { print_warning "Failed to install xdotool. Text injection may not work properly."; }
|
|
else
|
|
print_info "xdotool is already installed."
|
|
fi
|
|
;;
|
|
mageia)
|
|
if command_exists dnf && ! rpm -q "$XDOTOOL_PKG" >/dev/null 2>&1; then
|
|
sudo dnf install -y "$XDOTOOL_PKG" || { print_warning "Failed to install xdotool. Text injection may not work properly."; }
|
|
elif command_exists urpmi && ! rpm -q "$XDOTOOL_PKG" >/dev/null 2>&1; then
|
|
sudo urpmi -y "$XDOTOOL_PKG" || { print_warning "Failed to install xdotool. Text injection may not work properly."; }
|
|
else
|
|
print_info "xdotool is already installed."
|
|
fi
|
|
;;
|
|
*)
|
|
print_warning "Unsupported distribution for X11 text input tools."
|
|
print_warning "Please install 'xdotool' manually for X11 text input support."
|
|
;;
|
|
esac
|
|
;;
|
|
|
|
*)
|
|
print_warning "Unknown session type: $SESSION_TYPE"
|
|
print_warning "Installing both Wayland and X11 text input tools for compatibility..."
|
|
|
|
# Install both tools based on distribution
|
|
case "$DISTRO_FAMILY" in
|
|
ubuntu|debian)
|
|
DEBIAN_FRONTEND=noninteractive sudo apt install -y "$XDOTOOL_PKG" "$WTYPE_PKG" || { print_warning "Failed to install text input tools. Text injection may not work properly."; }
|
|
;;
|
|
fedora|mageia)
|
|
if command_exists dnf; then
|
|
sudo dnf install -y "$XDOTOOL_PKG" "$WTYPE_PKG" || { print_warning "Failed to install text input tools. Text injection may not work properly."; }
|
|
elif command_exists yum; then
|
|
sudo yum install -y "$XDOTOOL_PKG" "$WTYPE_PKG" || { print_warning "Failed to install text input tools. Text injection may not work properly."; }
|
|
fi
|
|
# Mageia also supports urpmi
|
|
if [[ "$DISTRO_FAMILY" == "mageia" ]] && command_exists urpmi; then
|
|
sudo urpmi -y "$XDOTOOL_PKG" "$WTYPE_PKG" || { print_warning "Failed to install text input tools. Text injection may not work properly."; }
|
|
fi
|
|
;;
|
|
arch)
|
|
sudo pacman -S --noconfirm "$XDOTOOL_PKG" "$WTYPE_PKG" || { print_warning "Failed to install text input tools. Text injection may not work properly."; }
|
|
;;
|
|
suse)
|
|
sudo zypper install -y "$XDOTOOL_PKG" "$WTYPE_PKG" || { print_warning "Failed to install text input tools. Text injection may not work properly."; }
|
|
;;
|
|
gentoo)
|
|
sudo emerge "$XDOTOOL_PKG" "$WTYPE_PKG" || { print_warning "Failed to install text input tools. Text injection may not work properly."; }
|
|
;;
|
|
alpine)
|
|
sudo apk add "$XDOTOOL_PKG" "$WTYPE_PKG" || { print_warning "Failed to install text input tools. Text injection may not work properly."; }
|
|
;;
|
|
void)
|
|
sudo xbps-install -Sy "$XDOTOOL_PKG" "$WTYPE_PKG" || { print_warning "Failed to install text input tools. Text injection may not work properly."; }
|
|
;;
|
|
solus)
|
|
sudo eopkg install "$XDOTOOL_PKG" "$WTYPE_PKG" || { print_warning "Failed to install text input tools. Text injection may not work properly."; }
|
|
;;
|
|
*)
|
|
print_warning "Unsupported distribution for text input tools."
|
|
print_warning "Please install 'xdotool' and 'wtype' manually for text input support."
|
|
;;
|
|
esac
|
|
;;
|
|
esac
|
|
}
|