Files
voculinux/install.sh
T
Jatin K MalikandGitHub f3fc8d6de6 fix(install): correct package detection and package names for multiple distros (#214)
* fix(install): correct package detection for Void and Solus, fix git fetch syntax

- Fix git fetch syntax: remove incorrect quoting around 'tag' argument
- Fix Void Linux: use 'xbps-query' instead of 'xbps-query -S' which checks
  remote repos instead of installed packages
- Fix Solus: use 'eopkg list-installed | grep' instead of 'eopkg info' which
  queries repository info, not installed status

These bugs caused the installer to always attempt reinstalling packages on
Void Linux and Solus, even when they were already installed.

* fix(install): correct package names for Arch, Void, and Gentoo

- Arch Linux: libappindicator-gtk3 -> libappindicator (package was renamed)
- Void Linux: libappindicator -> libappindicator-gtk3 (correct package name)
- Gentoo: dev-libs/libappindicator:3 -> dev-libs/libayatana-appindicator
  (libappindicator is deprecated, use ayatana fork)
2026-02-14 14:38:46 -08:00

2548 lines
104 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
# Vocalinux Installer
# This script installs the Vocalinux application and its dependencies
set -e # Exit on error
# Function to display colored output
print_info() {
echo -e "\e[1;34m[INFO]\e[0m $1"
}
print_success() {
echo -e "\e[1;32m[SUCCESS]\e[0m $1"
}
print_error() {
echo -e "\e[1;31m[ERROR]\e[0m $1"
}
print_warning() {
echo -e "\e[1;33m[WARNING]\e[0m $1"
}
# Parse command line arguments
INSTALL_MODE="user"
RUN_TESTS="no"
DEV_MODE="no"
VENV_DIR="venv"
SKIP_MODELS="no"
WITH_WHISPER="no"
WHISPER_CPU="no"
NO_WHISPER_EXPLICIT="no"
NON_INTERACTIVE="no"
INTERACTIVE_MODE="yes" # Default to interactive mode
AUTO_MODE="no"
HAS_NVIDIA_GPU="unknown"
GPU_NAME=""
GPU_MEMORY=""
HAS_VULKAN="no"
VULKAN_DEVICE=""
# Detect if running non-interactively (e.g., via curl | bash)
# Interactive mode is now default, so only auto-detect non-interactive for curl pipes
if [ ! -t 0 ]; then
# When piped via curl, we'll still try to be interactive if possible
# User can force non-interactive with --auto
INTERACTIVE_MODE="ask"
fi
while [[ $# -gt 0 ]]; do
case $1 in
--dev)
DEV_MODE="yes"
shift
;;
--test)
RUN_TESTS="yes"
shift
;;
--venv-dir=*)
VENV_DIR="${1#*=}"
shift
;;
--skip-models)
SKIP_MODELS="yes"
shift
;;
--engine=*)
SELECTED_ENGINE="${1#*=}"
shift
;;
--interactive|-i)
INTERACTIVE_MODE="yes"
shift
;;
--tag=*)
INSTALL_TAG="${1#*=}"
shift
;;
--auto)
AUTO_MODE="yes"
INTERACTIVE_MODE="no"
NON_INTERACTIVE="yes"
shift
;;
--help)
echo "Vocalinux Installer"
echo ""
echo "Usage: $0 [options]"
echo ""
echo "Installation Modes:"
echo " (no flags) Interactive mode - guided setup with recommendations"
echo " --auto Automatic mode - install with defaults (whisper.cpp)"
echo " --auto --engine=whisper Auto mode with specific engine"
echo ""
echo "Options:"
echo " --interactive, -i Force interactive mode (default)"
echo " --auto Non-interactive automatic installation"
echo " --engine=NAME Speech engine: whisper_cpp (default), whisper, vosk"
echo " --dev Install in development mode with all dev dependencies"
echo " --test Run tests after installation"
echo " --venv-dir=PATH Specify custom virtual environment directory"
echo " --skip-models Skip downloading speech models during installation"
echo " --tag=TAG Install specific release tag (default: v0.6.1-beta)"
echo " --help Show this help message"
echo ""
echo "Examples:"
echo " $0 # Interactive mode (recommended)"
echo " $0 --auto # Auto-install with whisper.cpp"
echo " $0 --auto --engine=vosk # Auto-install VOSK only"
echo " $0 --dev --test # Dev mode with tests"
exit 0
;;
*)
print_error "Unknown option: $1"
echo "Use --help to see available options"
exit 1
;;
esac
done
# If dev mode is enabled, automatically run tests
if [[ "$DEV_MODE" == "yes" ]]; then
RUN_TESTS="yes"
fi
# Display ASCII art banner
cat << "EOF"
▗▖ ▗▖ ▗▄▖ ▗▄▄▖ ▗▄▖ ▗▖ ▗▄▄▄▖▗▖ ▗▖▗▖ ▗▖▗▖ ▗▖
▐▌ ▐▌▐▌ ▐▌▐▌ ▐▌ ▐▌▐▌ █ ▐▛▚▖▐▌▐▌ ▐▌ ▝▚▞▘
▐▌ ▐▌▐▌ ▐▌▐▌ ▐▛▀▜▌▐▌ █ ▐▌ ▝▜▌▐▌ ▐▌ ▐▌
▝▚▞▘ ▝▚▄▞▘▝▚▄▄▖▐▌ ▐▌▐▙▄▄▖▗▄█▄▖▐▌ ▐▌▝▚▄▞▘▗▞▘▝▚▖
Voice Dictation for Linux
EOF
print_info "Vocalinux Installer"
print_info "=============================="
echo ""
# Default to installing from latest stable release instead of main branch
INSTALL_TAG="${INSTALL_TAG:-v0.6.1-beta}"
# Check if running from within the vocalinux repo or remotely (via curl)
REPO_URL="https://github.com/jatinkrmalik/vocalinux.git"
INSTALL_DIR=""
CLEANUP_ON_EXIT="no"
# Function to check and install git if needed
ensure_git_installed() {
if command -v git >/dev/null 2>&1; then
return 0
fi
print_warning "git is not installed. Attempting to install git..."
# Detect distribution for package manager selection
local DISTRO_FAMILY="unknown"
if [ -f /etc/os-release ]; then
. /etc/os-release
if [[ "$ID" == "ubuntu" || "$ID_LIKE" == *"ubuntu"* || "$ID" == "pop" || "$ID" == "linuxmint" || "$ID" == "elementary" || "$ID" == "zorin" ]]; then
DISTRO_FAMILY="ubuntu"
elif [[ "$ID" == "debian" || "$ID_LIKE" == *"debian"* ]]; then
DISTRO_FAMILY="debian"
elif [[ "$ID" == "fedora" || "$ID_LIKE" == *"fedora"* || "$ID" == "rhel" || "$ID" == "centos" || "$ID" == "rocky" || "$ID" == "almalinux" ]]; then
DISTRO_FAMILY="fedora"
elif [[ "$ID" == "arch" || "$ID_LIKE" == *"arch"* || "$ID" == "manjaro" || "$ID" == "endeavouros" ]]; then
DISTRO_FAMILY="arch"
elif [[ "$ID" == "opensuse" || "$ID_LIKE" == *"suse"* ]]; then
DISTRO_FAMILY="suse"
elif [[ "$ID" == "gentoo" ]]; then
DISTRO_FAMILY="gentoo"
elif [[ "$ID" == "alpine" ]]; then
DISTRO_FAMILY="alpine"
elif [[ "$ID" == "void" ]]; then
DISTRO_FAMILY="void"
elif [[ "$ID" == "solus" ]]; then
DISTRO_FAMILY="solus"
elif [[ "$ID" == "mageia" ]]; then
DISTRO_FAMILY="mageia"
fi
fi
case "$DISTRO_FAMILY" in
ubuntu|debian)
sudo apt update && sudo apt install -y git || {
print_error "Failed to install git. Please install git manually and run the installer again."
print_error " Ubuntu/Debian: sudo apt install git"
exit 1
}
;;
fedora)
sudo dnf install -y git || {
print_error "Failed to install git. Please install git manually and run the installer again."
print_error " Fedora: sudo dnf install git"
exit 1
}
;;
arch)
sudo pacman -S --noconfirm git || {
print_error "Failed to install git. Please install git manually and run the installer again."
print_error " Arch: sudo pacman -S git"
exit 1
}
;;
suse)
sudo zypper install -y git || {
print_error "Failed to install git. Please install git manually and run the installer again."
print_error " openSUSE: sudo zypper install git"
exit 1
}
;;
gentoo)
sudo emerge git || {
print_error "Failed to install git. Please install git manually and run the installer again."
print_error " Gentoo: sudo emerge git"
exit 1
}
;;
alpine)
sudo apk add git || {
print_error "Failed to install git. Please install git manually and run the installer again."
print_error " Alpine: sudo apk add git"
exit 1
}
;;
void)
sudo xbps-install -Sy git || {
print_error "Failed to install git. Please install git manually and run the installer again."
print_error " Void: sudo xbps-install -Sy git"
exit 1
}
;;
solus)
sudo eopkg install git || {
print_error "Failed to install git. Please install git manually and run the installer again."
print_error " Solus: sudo eopkg install git"
exit 1
}
;;
mageia)
if command -v dnf >/dev/null 2>&1; then
sudo dnf install -y git || {
print_error "Failed to install git. Please install git manually and run the installer again."
exit 1
}
else
sudo urpmi --force git || {
print_error "Failed to install git. Please install git manually and run the installer again."
exit 1
}
fi
;;
*)
print_error "git is not installed and could not auto-detect your distribution."
print_error "Please install git manually and run the installer again:"
print_error " Ubuntu/Debian: sudo apt install git"
print_error " Fedora/RHEL: sudo dnf install git"
print_error " Arch: sudo pacman -S git"
print_error " openSUSE: sudo zypper install git"
exit 1
;;
esac
print_success "git installed successfully!"
}
if [ -f "setup.py" ] || [ -f "pyproject.toml" ]; then
# Running from within the repo
INSTALL_DIR="$(pwd)"
print_info "Running from local repository: $INSTALL_DIR"
# Convert VENV_DIR to absolute path for wrapper scripts
VENV_DIR="$INSTALL_DIR/$VENV_DIR"
else
# Running remotely (e.g., via curl | bash)
print_info "Installing Vocalinux version: ${INSTALL_TAG}"
# Ensure git is installed before attempting to clone
ensure_git_installed
INSTALL_DIR="$HOME/.local/share/vocalinux-install"
mkdir -p "$INSTALL_DIR"
if [ -d "$INSTALL_DIR/.git" ]; then
print_info "Updating existing clone..."
cd "$INSTALL_DIR"
git fetch origin tag "$INSTALL_TAG"
git reset --hard "$INSTALL_TAG"
else
rm -rf "$INSTALL_DIR"
git clone --depth 1 --branch "$INSTALL_TAG" "$REPO_URL" "$INSTALL_DIR" || {
print_error "Failed to clone Vocalinux repository"
exit 1
}
cd "$INSTALL_DIR"
fi
CLEANUP_ON_EXIT="yes"
print_info "Repository cloned to: $INSTALL_DIR"
# When running remotely, install venv to user's home directory
VENV_DIR="$HOME/.local/share/vocalinux/venv"
fi
# Change to install directory
cd "$INSTALL_DIR"
print_info "Using virtual environment: $VENV_DIR"
[[ "$DEV_MODE" == "yes" ]] && print_info "Installing in development mode"
[[ "$RUN_TESTS" == "yes" ]] && print_info "Tests will be run after installation"
echo ""
# Check if running as root
if [ "$EUID" -eq 0 ]; then
print_error "Please do not run this script as root or with sudo."
exit 1
fi
# Detect Linux distribution and version
detect_distro() {
if [ -f /etc/os-release ]; then
. /etc/os-release
DISTRO_NAME="$NAME"
DISTRO_ID="$ID"
DISTRO_VERSION="$VERSION_ID"
DISTRO_FAMILY="unknown"
# Determine distribution family
if [[ "$ID" == "ubuntu" || "$ID_LIKE" == *"ubuntu"* || "$ID" == "pop" || "$ID" == "linuxmint" || "$ID" == "elementary" || "$ID" == "zorin" ]]; then
DISTRO_FAMILY="ubuntu"
elif [[ "$ID" == "debian" || "$ID_LIKE" == *"debian"* ]]; then
DISTRO_FAMILY="debian"
elif [[ "$ID" == "fedora" || "$ID_LIKE" == *"fedora"* || "$ID" == "rhel" || "$ID" == "centos" || "$ID" == "rocky" || "$ID" == "almalinux" ]]; then
DISTRO_FAMILY="fedora"
elif [[ "$ID" == "arch" || "$ID_LIKE" == *"arch"* || "$ID" == "manjaro" || "$ID" == "endeavouros" ]]; then
DISTRO_FAMILY="arch"
elif [[ "$ID" == "opensuse" || "$ID_LIKE" == *"suse"* ]]; then
DISTRO_FAMILY="suse"
elif [[ "$ID" == "gentoo" ]]; then
DISTRO_FAMILY="gentoo"
elif [[ "$ID" == "alpine" ]]; then
DISTRO_FAMILY="alpine"
elif [[ "$ID" == "void" ]]; then
DISTRO_FAMILY="void"
elif [[ "$ID" == "solus" ]]; then
DISTRO_FAMILY="solus"
elif [[ "$ID" == "mageia" ]]; then
DISTRO_FAMILY="mageia"
fi
print_info "Detected: $DISTRO_NAME $DISTRO_VERSION ($DISTRO_FAMILY family)"
return 0
else
print_error "Could not detect Linux distribution (missing /etc/os-release)"
return 1
fi
}
# Check minimum required version for Ubuntu-based systems
check_ubuntu_version() {
local MIN_VERSION="18.04"
if [[ "$DISTRO_FAMILY" == "ubuntu" ]]; then
if [[ $(echo -e "$DISTRO_VERSION\n$MIN_VERSION" | sort -V | head -n1) == "$MIN_VERSION" || "$DISTRO_VERSION" == "$MIN_VERSION" ]]; then
return 0
else
print_error "This application requires Ubuntu $MIN_VERSION or newer. Detected: $DISTRO_VERSION"
return 1
fi
fi
return 0
}
# Detect NVIDIA GPU presence
detect_nvidia_gpu() {
# Check if nvidia-smi command exists and can successfully query GPU
if command -v nvidia-smi >/dev/null 2>&1 && nvidia-smi >/dev/null 2>&1; then
# Extract GPU information for user feedback
GPU_NAME=$(nvidia-smi --query-gpu=name --format=csv,noheader 2>/dev/null | head -n1)
GPU_MEMORY=$(nvidia-smi --query-gpu=memory.total --format=csv,noheader 2>/dev/null | head -n1)
HAS_NVIDIA_GPU="yes"
return 0
else
HAS_NVIDIA_GPU="no"
return 1
fi
}
# Detect Vulkan support for whisper.cpp
detect_vulkan() {
# Check for vulkaninfo command
if command -v vulkaninfo >/dev/null 2>&1; then
local vulkan_output=$(vulkaninfo --summary 2>/dev/null | head -20)
if [ -n "$vulkan_output" ]; then
HAS_VULKAN="yes"
# Try to extract GPU name
VULKAN_DEVICE=$(echo "$vulkan_output" | grep -i "deviceName" | head -1 | cut -d'=' -f2 | xargs)
if [ -z "$VULKAN_DEVICE" ]; then
VULKAN_DEVICE="Vulkan-compatible GPU"
fi
return 0
fi
fi
HAS_VULKAN="no"
return 1
}
# Detect available GPU backends for whisper.cpp and recommend the best option
detect_whispercpp_backends() {
detect_nvidia_gpu || true
detect_vulkan || true
# Check for Vulkan dev libraries
local HAS_VULKAN_DEV=false
if pkg-config --exists vulkan 2>/dev/null || [ -f /usr/include/vulkan/vulkan.h ]; then
HAS_VULKAN_DEV=true
fi
# Check for CUDA
local HAS_CUDA_DEV=false
if command -v nvcc >/dev/null 2>&1; then
HAS_CUDA_DEV=true
fi
# Determine recommendation
local RECOMMENDED_BACKEND="cpu"
local RECOMMENDED_REASON=""
local CAN_BUILD_GPU=false
if [[ "$HAS_VULKAN" == "yes" && "$HAS_VULKAN_DEV" == "true" ]]; then
RECOMMENDED_BACKEND="vulkan"
RECOMMENDED_REASON="Vulkan GPU detected with dev libraries"
CAN_BUILD_GPU=true
elif [[ "$HAS_NVIDIA_GPU" == "yes" && "$HAS_CUDA_DEV" == "true" ]]; then
RECOMMENDED_BACKEND="cuda"
RECOMMENDED_REASON="NVIDIA GPU with CUDA toolkit"
CAN_BUILD_GPU=true
elif [[ "$HAS_VULKAN" == "yes" || "$HAS_NVIDIA_GPU" == "yes" ]]; then
RECOMMENDED_BACKEND="cpu"
if [[ "$HAS_VULKAN" == "yes" ]]; then
RECOMMENDED_REASON="GPU detected but dev libraries missing (will use CPU)"
else
RECOMMENDED_REASON="NVIDIA GPU detected but CUDA not installed (will use CPU)"
fi
CAN_BUILD_GPU=false
else
RECOMMENDED_BACKEND="cpu"
RECOMMENDED_REASON="No compatible GPU detected"
CAN_BUILD_GPU=false
fi
echo "${RECOMMENDED_BACKEND}:${RECOMMENDED_REASON}:${CAN_BUILD_GPU}:${HAS_VULKAN}:${HAS_NVIDIA_GPU}:${HAS_VULKAN_DEV}:${HAS_CUDA_DEV}"
}
# Detect hardware and recommend best engine
get_engine_recommendation() {
detect_nvidia_gpu || true
detect_vulkan || true
# Get RAM info
local TOTAL_RAM_GB=$(free -g 2>/dev/null | awk '/^Mem:/{print $2}' || echo "0")
if [[ "$HAS_NVIDIA_GPU" == "yes" ]]; then
# NVIDIA GPU detected - whisper.cpp can use CUDA
echo "whisper_cpp:✓:NVIDIA GPU detected ($GPU_NAME) - Best performance with whisper.cpp"
elif [[ "$HAS_VULKAN" == "yes" ]]; then
# Non-NVIDIA GPU with Vulkan support
echo "whisper_cpp:✓:$VULKAN_DEVICE detected - Great performance with whisper.cpp Vulkan"
elif [ "$TOTAL_RAM_GB" -ge 8 ]; then
# No GPU but decent RAM
echo "whisper_cpp:✓:No GPU detected, but ${TOTAL_RAM_GB}GB RAM - whisper.cpp CPU mode"
else
# Low RAM, no GPU
echo "vosk:⚠:Low RAM (${TOTAL_RAM_GB}GB) and no GPU - VOSK recommended for best performance"
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
clear >/dev/null 2>&1 || true
fi
}
print_header() {
local title="$1"
echo ""
echo "============================================================"
echo " $title"
echo "============================================================"
}
# Function to run interactive guided installation
run_interactive_install() {
clear_screen
cat << "EOF"
Interactive Installation Guide
===============================
EOF
echo "Welcome! This guided installation will help you set up Vocalinux"
echo "with the best options for your system."
echo ""
echo "All speech engines are 100% offline, local, and private."
echo "Your voice data never leaves your computer."
echo ""
# Step 1: Detect and display system info
print_header "Step 1: Your System"
echo "Detected: $DISTRO_NAME $DISTRO_VERSION"
# Get hardware recommendation
local RECOMMENDATION=$(get_engine_recommendation)
local RECOMMENDED_ENGINE=$(echo "$RECOMMENDATION" | cut -d':' -f1)
local RECOMMENDED_ICON=$(echo "$RECOMMENDATION" | cut -d':' -f2)
local RECOMMENDED_REASON=$(echo "$RECOMMENDATION" | cut -d':' -f3-)
echo "Hardware: $RECOMMENDED_REASON"
echo ""
# Step 2: Choose speech recognition engine
print_header "Step 2: Choose Speech Recognition Engine"
echo ""
echo " ┌─────────────────────────────────────────────────────────────┐"
echo " │ 1. WHISPER.CPP ★ RECOMMENDED │"
echo " │ • Fastest, most accurate, works with any GPU │"
echo " │ • Supports NVIDIA (CUDA), AMD, Intel (Vulkan) │"
echo " │ • CPU-only mode available for older systems │"
echo " │ • Models: tiny (39MB) to large (1.5GB) │"
echo " │ • 99+ languages with auto-detection │"
echo " └─────────────────────────────────────────────────────────────┘"
echo ""
echo " ┌─────────────────────────────────────────────────────────────┐"
echo " │ 2. WHISPER (OpenAI) │"
echo " │ • PyTorch-based, high accuracy │"
echo " │ • Only supports NVIDIA GPUs (CUDA) │"
echo " │ • Larger download (~2GB with CUDA) │"
echo " │ • Good for development/research │"
echo " └─────────────────────────────────────────────────────────────┘"
echo ""
echo " ┌─────────────────────────────────────────────────────────────┐"
echo " │ 3. VOSK │"
echo " │ • Lightweight and fast │"
echo " │ • Works on older/low-RAM systems │"
echo " │ • ~40MB download │"
echo " │ • Good for basic dictation needs │"
echo " └─────────────────────────────────────────────────────────────┘"
echo ""
# Show recommendation
case "$RECOMMENDED_ENGINE" in
whisper_cpp)
echo " → Recommendation: whisper.cpp (best performance for your hardware)"
DEFAULT_CHOICE="1"
;;
vosk)
echo " → Recommendation: VOSK (lightweight option for your system)"
DEFAULT_CHOICE="3"
;;
*)
echo " → Recommendation: whisper.cpp (best overall experience)"
DEFAULT_CHOICE="1"
;;
esac
echo ""
read -p "Choose engine [1-3] (default: $DEFAULT_CHOICE): " ENGINE_CHOICE
ENGINE_CHOICE=${ENGINE_CHOICE:-$DEFAULT_CHOICE}
case "$ENGINE_CHOICE" in
1)
SELECTED_ENGINE="whisper_cpp"
ENGINE_DISPLAY="Whisper.cpp (Recommended)"
;;
2)
SELECTED_ENGINE="whisper"
ENGINE_DISPLAY="Whisper (OpenAI)"
;;
3)
SELECTED_ENGINE="vosk"
ENGINE_DISPLAY="VOSK (Lightweight)"
;;
*)
SELECTED_ENGINE="whisper_cpp"
ENGINE_DISPLAY="Whisper.cpp (Recommended)"
;;
esac
# Step 3: Whisper.cpp backend selection (if whisper.cpp chosen)
if [[ "$SELECTED_ENGINE" == "whisper_cpp" ]]; then
print_header "Step 3: Choose Whisper.cpp Backend"
echo ""
# Detect available backends
local BACKEND_INFO=$(detect_whispercpp_backends)
local RECOMMENDED_BACKEND=$(echo "$BACKEND_INFO" | cut -d':' -f1)
local RECOMMENDED_REASON=$(echo "$BACKEND_INFO" | cut -d':' -f2)
local CAN_BUILD_GPU=$(echo "$BACKEND_INFO" | cut -d':' -f3)
local HAS_VULKAN=$(echo "$BACKEND_INFO" | cut -d':' -f4)
local HAS_NVIDIA=$(echo "$BACKEND_INFO" | cut -d':' -f5)
local HAS_VULKAN_DEV=$(echo "$BACKEND_INFO" | cut -d':' -f6)
local HAS_CUDA_DEV=$(echo "$BACKEND_INFO" | cut -d':' -f7)
echo "Whisper.cpp can use different backends for speech recognition:"
echo ""
if [[ "$CAN_BUILD_GPU" == "true" ]]; then
echo " ┌─────────────────────────────────────────────────────────────┐"
echo " │ 1. GPU (Vulkan/CUDA) ★ RECOMMENDED │"
echo " │ • Fastest performance with GPU acceleration │"
echo " │ • $RECOMMENDED_REASON │"
echo " │ • Requires building from source (takes ~2-5 min) │"
echo " └─────────────────────────────────────────────────────────────┘"
echo ""
echo " ┌─────────────────────────────────────────────────────────────┐"
echo " │ 2. CPU (Pre-built) │"
echo " │ • Works on all systems │"
echo " │ • Faster installation (no compilation) │"
echo " │ • Good performance on modern CPUs │"
echo " └─────────────────────────────────────────────────────────────┘"
echo ""
echo " → Recommendation: GPU backend for best performance"
local DEFAULT_BACKEND="1"
else
echo " ┌─────────────────────────────────────────────────────────────┐"
echo " │ 1. GPU (Vulkan/CUDA) │"
echo " │ • ⚠️ GPU libraries not detected │"
echo " │ • Requires: libvulkan-dev, glslc/glslang-tools (Vulkan) │"
echo " │ or: CUDA toolkit (NVIDIA) │"
echo " └─────────────────────────────────────────────────────────────┘"
echo ""
echo " ┌─────────────────────────────────────────────────────────────┐"
echo " │ 2. CPU (Pre-built) ★ RECOMMENDED │"
echo " │ • Works on all systems │"
echo " │ • Fast installation (no compilation) │"
echo " │ • Good performance on modern CPUs │"
echo " └─────────────────────────────────────────────────────────────┘"
echo ""
if [[ "$HAS_VULKAN" == "yes" && "$HAS_VULKAN_DEV" != "true" ]]; then
echo " 💡 Tip: Install 'libvulkan-dev' and a shader compiler for GPU support:"
echo " sudo apt install libvulkan-dev glslc 2>/dev/null || sudo apt install libvulkan-dev glslang-tools"
echo ""
elif [[ "$HAS_NVIDIA" == "yes" && "$HAS_CUDA_DEV" != "true" ]]; then
echo " 💡 Tip: Install CUDA toolkit for NVIDIA GPU support:"
echo " https://developer.nvidia.com/cuda-downloads"
echo ""
fi
echo " → Recommendation: CPU backend (GPU libraries not detected)"
local DEFAULT_BACKEND="2"
fi
read -p "Choose backend [1-2] (default: $DEFAULT_BACKEND): " BACKEND_CHOICE
BACKEND_CHOICE=${BACKEND_CHOICE:-$DEFAULT_BACKEND}
if [[ "$BACKEND_CHOICE" == "1" ]]; then
WHISPERCPP_BACKEND="gpu"
BACKEND_DISPLAY="GPU (Vulkan/CUDA)"
else
WHISPERCPP_BACKEND="cpu"
BACKEND_DISPLAY="CPU (Pre-built)"
fi
echo ""
fi
# Step 4: Model download preference
print_header "Step 4: Model Download"
echo ""
echo "Speech recognition models can be downloaded now or later."
echo ""
echo " 1. Download now (recommended)"
echo " • Faster first run - ready to use immediately"
echo " • Offline capable right after install"
echo ""
echo " 2. Download later"
echo " • Smaller initial install"
echo " • Models download automatically on first use"
echo ""
read -p "Download models now? [1-2] (default: 1): " MODELS_CHOICE
MODELS_CHOICE=${MODELS_CHOICE:-1}
if [[ "$MODELS_CHOICE" == "2" ]]; then
SKIP_MODELS="yes"
MODELS_DISPLAY="Download on first use"
else
MODELS_DISPLAY="Download now (recommended)"
fi
# Summary
print_header "Installation Summary"
echo ""
echo " Speech Engine: $ENGINE_DISPLAY"
if [[ "$SELECTED_ENGINE" == "whisper_cpp" ]]; then
echo " Backend: ${BACKEND_DISPLAY:-CPU (Pre-built)}"
if [[ "${WHISPERCPP_BACKEND}" == "gpu" ]]; then
echo " Note: GPU build will compile from source (2-5 minutes)"
fi
fi
echo " Models: $MODELS_DISPLAY"
echo " Install Location: ${INSTALL_DIR:-\$HOME/.local/share/vocalinux}"
echo ""
read -p "Press Enter to continue with installation, or Ctrl+C to cancel..."
echo ""
}
# Detect distribution
detect_distro
# Check compatibility
if [[ "$DISTRO_FAMILY" != "ubuntu" ]]; then
print_warning "This installer is primarily designed for Ubuntu-based systems. Your system: $DISTRO_NAME"
print_warning "The application may still work, but you might need to install dependencies manually."
if [[ "$NON_INTERACTIVE" == "yes" ]]; then
print_info "Non-interactive mode: continuing anyway..."
else
read -p "Do you want to continue anyway? (y/n) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
exit 1
fi
fi
else
# Check version for Ubuntu-based systems
if ! check_ubuntu_version; then
if [[ "$NON_INTERACTIVE" == "yes" ]]; then
print_info "Non-interactive mode: continuing anyway..."
else
read -p "Do you want to continue anyway? (y/n) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
exit 1
fi
fi
fi
fi
# Handle installation mode selection
if [[ "$INTERACTIVE_MODE" == "ask" ]]; then
# Running via curl pipe but we have a terminal - ask user preference
echo ""
echo "Installation Mode:"
echo " 1. Interactive (recommended) - guided setup with recommendations"
echo " 2. Automatic - quick install with defaults (whisper.cpp)"
echo ""
read -p "Choose mode [1-2] (default: 1): " MODE_CHOICE
MODE_CHOICE=${MODE_CHOICE:-1}
if [[ "$MODE_CHOICE" == "2" ]]; then
AUTO_MODE="yes"
INTERACTIVE_MODE="no"
NON_INTERACTIVE="yes"
else
INTERACTIVE_MODE="yes"
NON_INTERACTIVE="no"
fi
echo ""
fi
# Run interactive installation if selected
if [[ "$INTERACTIVE_MODE" == "yes" ]]; then
# Check if we have a TTY (required for interactive mode)
if [ ! -t 0 ]; then
print_error "Interactive mode requires a terminal (TTY)."
print_error "Please run from a terminal, or use automatic mode:"
print_error " curl -fsSL https://raw.githubusercontent.com/jatinkrmalik/vocalinux/v0.6.1-beta/install.sh | bash"
exit 1
fi
# Run interactive installation
run_interactive_install
fi
# Set default engine for auto/non-interactive mode
if [[ "$NON_INTERACTIVE" == "yes" ]] && [[ -z "$SELECTED_ENGINE" ]]; then
# Default to whisper.cpp for best performance
SELECTED_ENGINE="whisper_cpp"
print_info "Automatic mode: Installing with whisper.cpp (default engine)"
print_info "For other engines, use: --engine=whisper or --engine=vosk"
echo ""
fi
# 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
}
# Function to install system dependencies based on the detected distribution
install_system_dependencies() {
print_info "Installing system dependencies..."
# Check for pkg-config first (required for library detection)
if ! command_exists pkg-config; then
print_error "pkg-config is not installed but is required for library detection"
print_info "Please install pkg-config using your package manager:"
print_info " Ubuntu/Debian: sudo apt install pkg-config"
print_info " Fedora/RHEL: sudo dnf install pkg-config"
print_info " Arch: sudo pacman -S pkgconf"
print_info " openSUSE: sudo zypper install pkg-config"
print_info " Gentoo: sudo emerge pkgconf"
print_info " Alpine: sudo apk add pkgconf"
print_info " Void: sudo xbps-install -Sy pkg-config"
print_info " Solus: sudo eopkg install pkg-config"
exit 1
fi
# Determine which Vulkan shader package is available (glslc for Ubuntu 24.04+, glslang-tools for 22.04)
local VULKAN_SHADER_PKG="glslang-tools" # Default fallback
if apt-cache show glslc &>/dev/null 2>&1; then
VULKAN_SHADER_PKG="glslc"
fi
# Define package names for different distributions
local APT_PACKAGES_UBUNTU="python3-pip python3-gi python3-gi-cairo gir1.2-gtk-3.0 gir1.2-appindicator3-0.1 libgirepository1.0-dev python3-dev build-essential portaudio19-dev python3-venv pkg-config wget curl unzip vulkan-tools libvulkan-dev $VULKAN_SHADER_PKG"
local APT_PACKAGES_DEBIAN_BASE="python3-pip python3-gi python3-gi-cairo gir1.2-gtk-3.0 libcairo2-dev python3-dev build-essential portaudio19-dev python3-venv pkg-config wget curl unzip vulkan-tools libvulkan-dev $VULKAN_SHADER_PKG"
local APT_PACKAGES_DEBIAN_11_12="$APT_PACKAGES_DEBIAN_BASE libgirepository1.0-dev gir1.2-ayatanaappindicator3-0.1"
local APT_PACKAGES_DEBIAN_13_PLUS="$APT_PACKAGES_DEBIAN_BASE libgirepository-2.0-dev gir1.2-ayatanaappindicator3-0.1"
local DNF_PACKAGES="python3-pip python3-gobject gtk3 libappindicator-gtk3 gobject-introspection-devel python3-devel portaudio-devel python3-virtualenv pkg-config wget curl unzip vulkan-tools vulkan-loader-devel glslang"
local PACMAN_PACKAGES="python-pip python-gobject gtk3 libappindicator gobject-introspection python-cairo portaudio python-virtualenv pkg-config wget curl unzip base-devel vulkan-tools vulkan-headers glslang"
local ZYPPER_PACKAGES="python3-pip python3-gobject python3-gobject-cairo gtk3 libappindicator-gtk3 gobject-introspection-devel python3-devel portaudio-devel python3-virtualenv pkg-config wget curl unzip vulkan-tools vulkan-devel glslang"
# Gentoo uses Portage and different package naming convention
local EMERGE_PACKAGES="dev-python/pygobject:3 x11-libs/gtk+:3 dev-libs/libayatana-appindicator media-libs/portaudio dev-lang/python:3.8 pkgconf dev-util/glslang"
# Alpine Linux uses apk and has musl libc
local APK_PACKAGES="py3-gobject3 py3-pip gtk+3.0 py3-cairo portaudio-dev py3-virtualenv pkgconf wget curl unzip glslang vulkan-tools"
# Void Linux uses xbps
local XBPS_PACKAGES="python3-pip python3-gobject gtk+3 libappindicator-gtk3 gobject-introspection portaudio-devel python3-devel pkg-config wget curl unzip glslang Vulkan-Tools"
# Solus uses eopkg
local EOPKG_PACKAGES="python3-pip python3-gobject gtk3 libappindicator gobject-introspection-devel portaudio-devel python3-virtualenv pkg-config wget curl unzip glslang vulkan-tools"
local MISSING_PACKAGES=""
local INSTALL_CMD=""
local UPDATE_CMD=""
case "$DISTRO_FAMILY" in
ubuntu|debian)
local APT_PACKAGES="$APT_PACKAGES_UBUNTU"
if [[ "$DISTRO_FAMILY" == "debian" ]]; then
local DEBIAN_MAJOR="${DISTRO_VERSION%%.*}"
if [[ "$DEBIAN_MAJOR" =~ ^[0-9]+$ ]] && [ "$DEBIAN_MAJOR" -ge 13 ]; then
APT_PACKAGES="$APT_PACKAGES_DEBIAN_13_PLUS"
else
APT_PACKAGES="$APT_PACKAGES_DEBIAN_11_12"
fi
fi
# Check for missing packages
for pkg in $APT_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 1; }
# Handle appindicator package for Ubuntu (old package deprecated in newer releases)
if echo "$MISSING_PACKAGES" | grep -q "gir1.2-appindicator3-0.1"; then
FILTERED_PACKAGES=$(echo "$MISSING_PACKAGES" | sed 's/gir1.2-appindicator3-0.1//' | xargs)
if ! sudo apt install -y gir1.2-appindicator3-0.1 2>/dev/null; then
print_info "gir1.2-appindicator3-0.1 not available, trying gir1.2-ayatanaappindicator3-0.1..."
if ! sudo apt install -y gir1.2-ayatanaappindicator3-0.1; then
print_error "Failed to install appindicator package (tried both gir1.2-appindicator3-0.1 and gir1.2-ayatanaappindicator3-0.1)"
exit 1
fi
print_info "Successfully installed gir1.2-ayatanaappindicator3-0.1 (modern replacement)"
fi
if [ -n "$FILTERED_PACKAGES" ]; then
sudo apt install -y $FILTERED_PACKAGES || { print_error "Failed to install dependencies"; exit 1; }
fi
else
sudo apt install -y $MISSING_PACKAGES || { print_error "Failed to install dependencies"; exit 1; }
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 1
fi
# Check for missing packages
for pkg in $DNF_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
$INSTALL_CMD $MISSING_PACKAGES || { print_error "Failed to install dependencies"; exit 1; }
else
print_info "All required packages are already installed."
fi
;;
arch)
# For Arch-based systems
if ! command_exists pacman; then
print_error "Pacman package manager not found"
exit 1
fi
# Check for missing packages
for pkg in $PACMAN_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 1; }
else
print_info "All required packages are already installed."
fi
;;
suse)
# For openSUSE
if ! command_exists zypper; then
print_error "Zypper package manager not found"
exit 1
fi
print_info "Updating package lists and installing dependencies..."
sudo zypper refresh
sudo zypper install -y $ZYPPER_PACKAGES || { print_error "Failed to install dependencies"; exit 1; }
;;
gentoo)
# For Gentoo Linux
if ! command_exists emerge; then
print_error "Emerge package manager not found"
exit 1
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 $EMERGE_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 1; }
# Install missing packages
sudo emerge $MISSING_PACKAGES || { print_error "Failed to install dependencies"; exit 1; }
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 1
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 $APK_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 1; }
sudo apk add $MISSING_PACKAGES || { print_error "Failed to install dependencies"; exit 1; }
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 1
fi
print_info "Void Linux detected."
# Check for missing packages
MISSING_PACKAGES=""
for pkg in $XBPS_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 1; }
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 1
fi
print_info "Solus detected."
# Check for missing packages
MISSING_PACKAGES=""
for pkg in $EOPKG_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 1; }
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 1
fi
# Use similar packages to Fedora/RHEL
for pkg in $DNF_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 1; }
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 " python3 -m venv venv"
print_info " source venv/bin/activate"
print_info " pip install -e .[whisper]"
print_info ""
print_info "For more information, see the project wiki:"
print_info " https://github.com/jatinkrmalik/vocalinux/wiki"
print_info ""
if [[ "$NON_INTERACTIVE" != "yes" ]]; then
read -p "Continue anyway? (y/n) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
exit 1
fi
else
print_info "Non-interactive mode: continuing (dependencies may be missing)..."
fi
;;
esac
}
# Install system dependencies
install_system_dependencies
# Define XDG directories
CONFIG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/vocalinux"
DATA_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/vocalinux"
DESKTOP_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/applications"
ICON_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/icons/hicolor/scalable/apps"
# Function to detect and install text input tools
install_text_input_tools() {
# Detect session type more robustly
local SESSION_TYPE="unknown"
# Check XDG_SESSION_TYPE first
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)
fi
print_info "Detected session type: $SESSION_TYPE"
# 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"; then
sudo apt install -y wtype || { 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"; then
sudo dnf install -y wtype || { print_warning "Failed to install wtype. Text injection may not work properly."; }
elif command_exists yum && ! rpm -q wtype &>/dev/null; then
sudo yum install -y wtype || { 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"; then
sudo pacman -S --noconfirm wtype || { 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 || { print_warning "Failed to install wtype. Text injection may not work properly."; }
;;
gentoo)
if ! qlist -I wtype >/dev/null 2>&1; then
sudo emerge wtype || { 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 >/dev/null 2>&1; then
sudo apk add wtype || { 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 >/dev/null 2>&1; then
sudo xbps-install -Sy wtype || { 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; then
sudo eopkg install wtype || { 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 >/dev/null 2>&1; then
sudo dnf install -y wtype || { print_warning "Failed to install wtype. Text injection may not work properly."; }
elif command_exists urpmi && ! rpm -q wtype >/dev/null 2>&1; then
sudo urpmi -y wtype || { 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"; 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|"")
print_info "Installing X11 text input tools..."
case "$DISTRO_FAMILY" in
ubuntu|debian)
if ! apt_package_installed "xdotool"; then
sudo apt install -y xdotool || { 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"; then
sudo dnf install -y xdotool || { print_warning "Failed to install xdotool. Text injection may not work properly."; }
elif command_exists yum && ! rpm -q xdotool &>/dev/null; then
sudo yum install -y xdotool || { 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"; then
sudo pacman -S --noconfirm xdotool || { 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 || { print_warning "Failed to install xdotool. Text injection may not work properly."; }
;;
gentoo)
if ! qlist -I xdotool >/dev/null 2>&1; then
sudo emerge xdotool || { 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 >/dev/null 2>&1; then
sudo apk add xdotool || { 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 >/dev/null 2>&1; then
sudo xbps-install -Sy xdotool || { 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; then
sudo eopkg install xdotool || { 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 >/dev/null 2>&1; then
sudo dnf install -y xdotool || { print_warning "Failed to install xdotool. Text injection may not work properly."; }
elif command_exists urpmi && ! rpm -q xdotool >/dev/null 2>&1; then
sudo urpmi -y xdotool || { 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)
sudo apt install -y xdotool wtype || { 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 wtype || { print_warning "Failed to install text input tools. Text injection may not work properly."; }
elif command_exists yum; then
sudo yum install -y xdotool wtype || { 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 wtype || { print_warning "Failed to install text input tools. Text injection may not work properly."; }
fi
;;
arch)
sudo pacman -S --noconfirm xdotool wtype || { print_warning "Failed to install text input tools. Text injection may not work properly."; }
;;
suse)
sudo zypper install -y xdotool wtype || { print_warning "Failed to install text input tools. Text injection may not work properly."; }
;;
gentoo)
sudo emerge xdotool wtype || { print_warning "Failed to install text input tools. Text injection may not work properly."; }
;;
alpine)
sudo apk add xdotool wtype || { print_warning "Failed to install text input tools. Text injection may not work properly."; }
;;
void)
sudo xbps-install -Sy xdotool wtype || { print_warning "Failed to install text input tools. Text injection may not work properly."; }
;;
solus)
sudo eopkg install xdotool wtype || { 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
}
# Install text input tools based on session type
install_text_input_tools
# Create necessary directories
print_info "Creating application directories..."
mkdir -p "$CONFIG_DIR"
mkdir -p "$DATA_DIR/models"
mkdir -p "$DESKTOP_DIR"
mkdir -p "$ICON_DIR"
# Check Python version
check_python_version() {
local MIN_VERSION="3.8"
local PYTHON_CMD="python3"
# Check if python3 command exists
if ! command_exists python3; then
print_error "Python 3 is not installed or not in PATH"
return 1
fi
# Get Python version
local PY_VERSION=$(python3 -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')")
print_info "Detected Python version: $PY_VERSION"
# Compare versions
if [[ $(echo -e "$PY_VERSION\n$MIN_VERSION" | sort -V | head -n1) == "$MIN_VERSION" || "$PY_VERSION" == "$MIN_VERSION" ]]; then
return 0
else
print_error "This application requires Python $MIN_VERSION or newer. Detected: $PY_VERSION"
return 1
fi
}
# Set up virtual environment with error handling
setup_virtual_environment() {
print_info "Setting up Python virtual environment in $VENV_DIR..."
# Check if virtual environment already exists
if [ -d "$VENV_DIR" ] && [ -f "$VENV_DIR/bin/activate" ]; then
print_warning "Virtual environment already exists in $VENV_DIR"
if [[ "$NON_INTERACTIVE" == "yes" ]]; then
# In non-interactive mode, reuse existing venv
print_info "Non-interactive mode: using existing virtual environment."
source "$VENV_DIR/bin/activate" || { print_error "Failed to activate virtual environment"; exit 1; }
return 0
else
read -p "Do you want to recreate it? (y/n) " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
print_info "Removing existing virtual environment..."
rm -rf "$VENV_DIR"
else
print_info "Using existing virtual environment."
source "$VENV_DIR/bin/activate" || { print_error "Failed to activate virtual environment"; exit 1; }
return 0
fi
fi
fi
# Create virtual environment
# 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
}
# Activate virtual environment
source "$VENV_DIR/bin/activate" || { print_error "Failed to activate virtual environment"; exit 1; }
# Update pip and setuptools
print_info "Updating pip, setuptools, and wheel..."
pip install --upgrade pip setuptools wheel || { print_error "Failed to update pip, setuptools, and wheel"; exit 1; }
print_info "Virtual environment activated successfully."
}
# Check Python version
if ! check_python_version; then
print_warning "Continuing with unsupported Python version. Some features may not work correctly."
fi
# Set up virtual environment
setup_virtual_environment
# Create activation script for users
# Put it in ~/.local/bin when running remotely, or current dir when running locally
if [[ "$CLEANUP_ON_EXIT" == "yes" ]]; then
ACTIVATION_SCRIPT_DIR="$HOME/.local/bin"
mkdir -p "$ACTIVATION_SCRIPT_DIR"
else
ACTIVATION_SCRIPT_DIR="."
fi
ACTIVATION_SCRIPT="$ACTIVATION_SCRIPT_DIR/activate-vocalinux.sh"
cat > "$ACTIVATION_SCRIPT" << EOF
#!/bin/bash
# This script activates the Vocalinux virtual environment
source "$VENV_DIR/bin/activate"
echo "Vocalinux virtual environment activated."
echo "To start the application, run: vocalinux"
EOF
chmod +x "$ACTIVATION_SCRIPT"
print_info "Created activation script: $ACTIVATION_SCRIPT"
# Function to install Python package with error handling and verification
install_python_package() {
# Create a temporary directory for pip logs
local PIP_LOG_DIR=$(mktemp -d)
local PIP_LOG_FILE="$PIP_LOG_DIR/pip_log.txt"
# Detect GI_TYPELIB_PATH early for cross-distro compatibility
# This ensures the path is available for both verification and wrapper scripts
local GI_TYPELIB_DETECTED
GI_TYPELIB_DETECTED=$(detect_typelib_path)
print_info "Detected GI_TYPELIB_PATH: $GI_TYPELIB_DETECTED"
# Function to verify package installation
verify_package_installed() {
local PKG_NAME="vocalinux"
# Use venv python and set GI_TYPELIB_PATH for PyGObject
# Use the detected path for cross-distro compatibility
GI_TYPELIB_PATH="$GI_TYPELIB_DETECTED" "$VENV_DIR/bin/python" -c "import $PKG_NAME" 2>/dev/null
return $?
}
if [[ "$DEV_MODE" == "yes" ]]; then
print_info "Installing Vocalinux in development mode..."
# Install in development mode with logging
pip install -e . --log "$PIP_LOG_FILE" || {
print_error "Failed to install Vocalinux in development mode."
print_error "Check the pip log for details: $PIP_LOG_FILE"
return 1
}
# Install test dependencies
print_info "Installing test dependencies..."
pip install pytest pytest-mock pytest-cov --log "$PIP_LOG_FILE" || {
print_warning "Failed to install some test dependencies. Tests may not run correctly."
}
# Install all optional dependencies for development
print_info "Installing all optional dependencies for development..."
pip install ".[whisper,dev]" --log "$PIP_LOG_FILE" || {
print_warning "Failed to install some optional dependencies."
print_warning "Some features may not work correctly."
}
else
print_info "Installing Vocalinux..."
# Install the package with logging (includes pywhispercpp by default)
pip install . --log "$PIP_LOG_FILE" || {
print_error "Failed to install Vocalinux."
print_error "Check the pip log for details: $PIP_LOG_FILE"
return 1
}
# Engine installation logic:
# - SELECTED_ENGINE is set by interactive mode or --engine flag
# - WHISPERCPP_BACKEND is set by interactive mode ("gpu" or "cpu")
# - Default is whisper_cpp for best performance
case "${SELECTED_ENGINE:-whisper_cpp}" in
whisper_cpp)
print_info ""
print_info "╔════════════════════════════════════════════════════════╗"
print_info "║ Installing WHISPER.CPP (Recommended) ║"
print_info "╠════════════════════════════════════════════════════════╣"
print_info "║ • Fastest speech recognition ║"
print_info "║ • Works with any GPU: NVIDIA, AMD, Intel ║"
print_info "║ • Uses Vulkan for GPU acceleration ║"
print_info "║ • CPU-only mode available ║"
print_info "╚════════════════════════════════════════════════════════╝"
print_info ""
# Detect GPU and install pywhispercpp with appropriate GPU support
detect_nvidia_gpu || true
detect_vulkan || true
local GPU_BACKEND="CPU"
local GPU_INSTALL_SUCCESS=false
# Check if user explicitly chose CPU backend in interactive mode
if [[ "${WHISPERCPP_BACKEND}" == "cpu" ]]; then
print_info "ℹ Installing CPU-only version (as requested)..."
GPU_BACKEND="CPU"
else
# Try Vulkan first (works with all GPUs: NVIDIA, AMD, Intel)
if [[ "$HAS_VULKAN" == "yes" ]]; then
print_info "✓ Vulkan detected: $VULKAN_DEVICE"
print_info " Installing pywhispercpp with Vulkan support..."
GPU_BACKEND="Vulkan"
print_info "Installing pywhispercpp ($GPU_BACKEND backend)..."
if GGML_VULKAN=1 pip install --force-reinstall --no-cache-dir git+https://github.com/absadiki/pywhispercpp --log "$PIP_LOG_FILE" 2>&1; then
GPU_INSTALL_SUCCESS=true
else
print_warning "Vulkan build failed - checking for NVIDIA GPU to try CUDA..."
fi
fi
# If Vulkan failed or not available, try CUDA for NVIDIA GPUs
if [[ "$GPU_INSTALL_SUCCESS" != "true" && "$HAS_NVIDIA_GPU" == "yes" ]]; then
print_info "✓ NVIDIA GPU detected: $GPU_NAME"
print_info " Installing pywhispercpp with CUDA support..."
GPU_BACKEND="CUDA"
print_info "Installing pywhispercpp ($GPU_BACKEND backend)..."
if GGML_CUDA=1 pip install --force-reinstall --no-cache-dir git+https://github.com/absadiki/pywhispercpp --log "$PIP_LOG_FILE" 2>&1; then
GPU_INSTALL_SUCCESS=true
fi
fi
fi
# Fall back to CPU version if GPU install failed or no GPU detected
if [[ "$GPU_INSTALL_SUCCESS" != "true" ]]; then
if [[ "$GPU_BACKEND" != "CPU" ]]; then
print_warning "Failed to install pywhispercpp with $GPU_BACKEND support, falling back to CPU version..."
# Provide helpful error messages for common issues
if [[ "$GPU_BACKEND" == "Vulkan" ]]; then
print_info " To use Vulkan GPU acceleration, please install Vulkan development libraries:"
print_info " Ubuntu/Debian: sudo apt install libvulkan-dev vulkan-tools glslc || glslang-tools"
print_info " Fedora: sudo dnf install vulkan-loader-devel vulkan-tools glslang"
print_info " Arch: sudo pacman -S vulkan-headers vulkan-tools glslang"
elif [[ "$GPU_BACKEND" == "CUDA" ]]; then
print_info " To use CUDA GPU acceleration, please install CUDA toolkit:"
print_info " Visit: https://developer.nvidia.com/cuda-downloads"
fi
elif [[ "${WHISPERCPP_BACKEND}" != "cpu" ]]; then
print_info "ℹ No GPU detected - installing CPU-only version"
print_info " CPU mode is still very fast!"
fi
GPU_BACKEND="CPU"
print_info "Installing pywhispercpp ($GPU_BACKEND backend)..."
pip install pywhispercpp --log "$PIP_LOG_FILE" || {
print_error "Failed to install pywhispercpp"
return 1
}
fi
print_success "pywhispercpp installed with $GPU_BACKEND backend"
echo ""
;;
whisper)
print_info "Installing Whisper (OpenAI) with PyTorch..."
print_info "Note: This engine requires NVIDIA GPU for acceleration"
print_info " For AMD/Intel GPUs, whisper.cpp is recommended"
local WHISPER_INSTALL_SUCCESS=false
# Install PyTorch and whisper
print_info "Installing PyTorch..."
if pip install torch torchaudio --index-url https://download.pytorch.org/whl/cpu --log "$PIP_LOG_FILE" 2>&1; then
print_success "PyTorch installed successfully"
print_info "Installing openai-whisper..."
if pip install openai-whisper --log "$PIP_LOG_FILE" 2>&1; then
# Verify the installation by importing the module
if "$VENV_DIR/bin/python" -c "import whisper" 2>/dev/null; then
WHISPER_INSTALL_SUCCESS=true
print_success "Whisper installed and verified successfully"
else
print_error "Whisper package installed but import failed"
fi
else
print_error "Failed to install openai-whisper package"
fi
else
print_error "Failed to install PyTorch"
fi
if [[ "$WHISPER_INSTALL_SUCCESS" == "true" ]]; then
# Create config with whisper as default
local WHISPER_CONFIG="$CONFIG_DIR/config.json"
if [ ! -f "$WHISPER_CONFIG" ]; then
mkdir -p "$CONFIG_DIR"
cat > "$WHISPER_CONFIG" << 'WHISPER_CONFIG'
{
"speech_recognition": {
"engine": "whisper",
"model_size": "tiny",
"vosk_model_size": "small",
"whisper_model_size": "tiny",
"whisper_cpp_model_size": "tiny",
"vad_sensitivity": 3,
"silence_timeout": 2.0
},
"audio": {
"device_index": null,
"device_name": null
},
"shortcuts": {
"toggle_recognition": "ctrl+ctrl"
},
"ui": {
"start_minimized": false,
"show_notifications": true
},
"advanced": {
"debug_logging": false,
"wayland_mode": false
}
}
WHISPER_CONFIG
fi
else
print_warning "Failed to install Whisper (OpenAI)"
print_warning "Falling back to whisper.cpp (recommended engine)"
print_info ""
print_info "The Whisper (OpenAI) engine installation failed."
print_info "whisper.cpp will be installed instead, which is:"
print_info " - Faster and more accurate"
print_info " - Works with any GPU (NVIDIA, AMD, Intel)"
print_info " - Uses Vulkan for GPU acceleration"
print_info ""
# Fall back to whisper.cpp installation
pip install pywhispercpp --log "$PIP_LOG_FILE" || {
print_error "Failed to install pywhispercpp fallback"
print_error "Please try installing manually: pip install pywhispercpp"
return 1
}
print_success "Installed whisper.cpp as fallback"
# Create config with whisper_cpp as default
local FALLBACK_CONFIG="$CONFIG_DIR/config.json"
if [ ! -f "$FALLBACK_CONFIG" ]; then
mkdir -p "$CONFIG_DIR"
cat > "$FALLBACK_CONFIG" << 'FALLBACK_CONFIG'
{
"speech_recognition": {
"engine": "whisper_cpp",
"model_size": "tiny",
"vosk_model_size": "small",
"whisper_model_size": "tiny",
"whisper_cpp_model_size": "tiny",
"vad_sensitivity": 3,
"silence_timeout": 2.0
},
"audio": {
"device_index": null,
"device_name": null
},
"shortcuts": {
"toggle_recognition": "ctrl+ctrl"
},
"ui": {
"start_minimized": false,
"show_notifications": true
},
"advanced": {
"debug_logging": false,
"wayland_mode": false
}
}
FALLBACK_CONFIG
fi
fi
;;
vosk)
print_info "Installing VOSK (lightweight option)..."
print_info "VOSK is fast and works well on older systems."
# Create config with vosk as default
local VOSK_CONFIG_FILE="$CONFIG_DIR/config.json"
if [ ! -f "$VOSK_CONFIG_FILE" ]; then
mkdir -p "$CONFIG_DIR"
cat > "$VOSK_CONFIG_FILE" << 'VOSK_CONFIG'
{
"speech_recognition": {
"engine": "vosk",
"model_size": "small",
"vosk_model_size": "small",
"whisper_model_size": "tiny",
"whisper_cpp_model_size": "tiny",
"vad_sensitivity": 3,
"silence_timeout": 2.0
},
"audio": {
"device_index": null,
"device_name": null
},
"shortcuts": {
"toggle_recognition": "ctrl+ctrl"
},
"ui": {
"start_minimized": false,
"show_notifications": true
},
"advanced": {
"debug_logging": false,
"wayland_mode": false
}
}
VOSK_CONFIG
fi
;;
esac
fi
# Verify installation
if verify_package_installed; then
print_success "Vocalinux package installed successfully!"
# Clean up log file if installation was successful
rm -rf "$PIP_LOG_DIR"
# GI_TYPELIB_PATH was already detected at the start of install_python_package
# Create wrapper scripts in ~/.local/bin for easy access
mkdir -p "$HOME/.local/bin"
# Create vocalinux wrapper script
# Uses 'sg input' to run with input group for keyboard shortcuts on Wayland
# This allows shortcuts to work without logging out after installation
cat > "$HOME/.local/bin/vocalinux" << WRAPPER_EOF
#!/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=$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
# Use sg to run with input group without requiring logout
exec sg input -c "$VENV_DIR/bin/vocalinux \$*"
else
exec "$VENV_DIR/bin/vocalinux" "\$@"
fi
WRAPPER_EOF
chmod +x "$HOME/.local/bin/vocalinux"
print_info "Created wrapper: ~/.local/bin/vocalinux"
# Create vocalinux-gui wrapper script
cat > "$HOME/.local/bin/vocalinux-gui" << 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=$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
# Use sg to run with input group without requiring logout
exec sg input -c "$VENV_DIR/bin/vocalinux-gui \$*"
else
exec "$VENV_DIR/bin/vocalinux-gui" "\$@"
fi
WRAPPER_EOF
chmod +x "$HOME/.local/bin/vocalinux-gui"
print_info "Created wrapper: ~/.local/bin/vocalinux-gui"
# Check if ~/.local/bin is in PATH
if [[ ":$PATH:" != *":$HOME/.local/bin:"* ]]; then
print_warning "~/.local/bin is not in your PATH"
print_info "Add this line to your ~/.bashrc or ~/.zshrc:"
print_info ' export PATH="$HOME/.local/bin:$PATH"'
fi
return 0
else
print_error "Vocalinux package installation verification failed."
print_error "Check the pip log for details: $PIP_LOG_FILE"
return 1
fi
}
# Install Python package
if ! install_python_package; then
print_error "Failed to install Vocalinux package. Installation cannot continue."
exit 1
fi
# Function to download and install Whisper tiny model
install_whisper_model() {
print_info "Installing Whisper tiny model (~75MB)..."
# Create whisper models directory
local WHISPER_DIR="$DATA_DIR/models/whisper"
mkdir -p "$WHISPER_DIR"
# Whisper tiny model URL and path
local TINY_MODEL_URL="https://openaipublic.azureedge.net/main/whisper/models/65147644a518d12f04e32d6f3b26facc3f8dd46e5390956a9424a650c0ce22b9/tiny.pt"
local TINY_MODEL_PATH="$WHISPER_DIR/tiny.pt"
# Check if model already exists
if [ -f "$TINY_MODEL_PATH" ]; then
print_info "Whisper tiny model already exists at $TINY_MODEL_PATH"
return 0
fi
# Check internet connectivity
if ! command -v wget >/dev/null 2>&1 && ! command -v curl >/dev/null 2>&1; then
print_warning "Neither wget nor curl found. Cannot download Whisper model."
print_warning "Model will be downloaded on first application run."
return 1
fi
# Test internet connectivity
if ! ping -c 1 google.com >/dev/null 2>&1; then
print_warning "No internet connection detected."
print_warning "Whisper model will be downloaded on first application run."
return 1
fi
print_info "Downloading Whisper tiny model..."
print_info "This may take a few minutes depending on your internet connection."
local TEMP_FILE="$TINY_MODEL_PATH.tmp"
# Download the model
if command -v wget >/dev/null 2>&1; then
if ! wget --progress=bar:force:noscroll -O "$TEMP_FILE" "$TINY_MODEL_URL" 2>&1; then
print_error "Failed to download Whisper model with wget"
rm -f "$TEMP_FILE"
return 1
fi
elif command -v curl >/dev/null 2>&1; then
if ! curl -L --progress-bar -o "$TEMP_FILE" "$TINY_MODEL_URL"; then
print_error "Failed to download Whisper model with curl"
rm -f "$TEMP_FILE"
return 1
fi
fi
# Verify download
if [ ! -f "$TEMP_FILE" ] || [ ! -s "$TEMP_FILE" ]; then
print_error "Downloaded model file is empty or missing"
rm -f "$TEMP_FILE"
return 1
fi
# Move to final location
mv "$TEMP_FILE" "$TINY_MODEL_PATH"
# Verify the model file
if [ -f "$TINY_MODEL_PATH" ]; then
local MODEL_SIZE=$(du -h "$TINY_MODEL_PATH" | cut -f1)
print_success "Whisper tiny model installed successfully ($MODEL_SIZE)"
# Create a marker file to indicate this model was pre-installed
echo "$(date)" > "$WHISPER_DIR/.vocalinux_preinstalled"
return 0
else
print_error "Whisper model installation failed"
return 1
fi
}
# Function to download and install VOSK models
install_vosk_models() {
print_info "Installing VOSK speech recognition models..."
# Create models directory
local MODELS_DIR="$DATA_DIR/models"
mkdir -p "$MODELS_DIR"
# Define model information
local SMALL_MODEL_URL="https://alphacephei.com/vosk/models/vosk-model-small-en-us-0.15.zip"
local SMALL_MODEL_NAME="vosk-model-small-en-us-0.15"
local SMALL_MODEL_PATH="$MODELS_DIR/$SMALL_MODEL_NAME"
# Check if small model already exists
if [ -d "$SMALL_MODEL_PATH" ]; then
print_info "Small VOSK model already exists at $SMALL_MODEL_PATH"
return 0
fi
# Check internet connectivity
if ! command -v wget >/dev/null 2>&1 && ! command -v curl >/dev/null 2>&1; then
print_warning "Neither wget nor curl found. Cannot download VOSK models."
print_warning "Models will be downloaded on first application run."
return 1
fi
# Test internet connectivity
if ! ping -c 1 google.com >/dev/null 2>&1; then
print_warning "No internet connection detected."
print_warning "VOSK models will be downloaded on first application run."
return 1
fi
print_info "Downloading small VOSK model (approximately 40MB)..."
print_info "This may take a few minutes depending on your internet connection."
local TEMP_ZIP="$MODELS_DIR/$(basename $SMALL_MODEL_URL)"
# Download the model
if command -v wget >/dev/null 2>&1; then
if ! wget --progress=bar:force:noscroll -O "$TEMP_ZIP" "$SMALL_MODEL_URL" 2>&1; then
print_error "Failed to download VOSK model with wget"
rm -f "$TEMP_ZIP"
return 1
fi
elif command -v curl >/dev/null 2>&1; then
if ! curl -L --progress-bar -o "$TEMP_ZIP" "$SMALL_MODEL_URL"; then
print_error "Failed to download VOSK model with curl"
rm -f "$TEMP_ZIP"
return 1
fi
fi
# Verify download
if [ ! -f "$TEMP_ZIP" ] || [ ! -s "$TEMP_ZIP" ]; then
print_error "Downloaded model file is empty or missing"
rm -f "$TEMP_ZIP"
return 1
fi
print_info "Extracting VOSK model..."
# Extract the model
if command -v unzip >/dev/null 2>&1; then
if ! unzip -q "$TEMP_ZIP" -d "$MODELS_DIR"; then
print_error "Failed to extract VOSK model"
rm -f "$TEMP_ZIP"
return 1
fi
else
print_error "unzip command not found. Cannot extract VOSK model."
rm -f "$TEMP_ZIP"
return 1
fi
# Clean up zip file
rm -f "$TEMP_ZIP"
# Verify extraction
if [ -d "$SMALL_MODEL_PATH" ]; then
print_success "VOSK small model installed successfully at $SMALL_MODEL_PATH"
# Set proper permissions
chmod -R 755 "$SMALL_MODEL_PATH"
# Create a marker file to indicate this model was pre-installed
echo "$(date)" > "$SMALL_MODEL_PATH/.vocalinux_preinstalled"
return 0
else
print_error "VOSK model extraction failed - directory not found"
return 1
fi
}
# Function to download and install whisper.cpp tiny model
install_whispercpp_model() {
print_info "Installing whisper.cpp tiny model (~39MB)..."
# Create whisper.cpp models directory
local WHISPERCPP_DIR="$DATA_DIR/models/whispercpp"
mkdir -p "$WHISPERCPP_DIR"
# whisper.cpp tiny model URL and path
local TINY_MODEL_URL="https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-tiny.bin"
local TINY_MODEL_PATH="$WHISPERCPP_DIR/ggml-tiny.bin"
# Check if model already exists
if [ -f "$TINY_MODEL_PATH" ]; then
print_info "whisper.cpp tiny model already exists at $TINY_MODEL_PATH"
return 0
fi
# Check internet connectivity
if ! command -v wget >/dev/null 2>&1 && ! command -v curl >/dev/null 2>&1; then
print_warning "Neither wget nor curl found. Cannot download whisper.cpp model."
print_warning "Model will be downloaded on first application run."
return 1
fi
# Test internet connectivity
if ! ping -c 1 google.com >/dev/null 2>&1; then
print_warning "No internet connection detected."
print_warning "whisper.cpp model will be downloaded on first application run."
return 1
fi
print_info "Downloading whisper.cpp tiny model..."
print_info "This may take a few minutes depending on your internet connection."
local TEMP_FILE="$TINY_MODEL_PATH.tmp"
# Download the model
if command -v wget >/dev/null 2>&1; then
if ! wget --progress=bar:force:noscroll -O "$TEMP_FILE" "$TINY_MODEL_URL" 2>&1; then
print_error "Failed to download whisper.cpp model with wget"
rm -f "$TEMP_FILE"
return 1
fi
elif command -v curl >/dev/null 2>&1; then
if ! curl -L --progress-bar -o "$TEMP_FILE" "$TINY_MODEL_URL"; then
print_error "Failed to download whisper.cpp model with curl"
rm -f "$TEMP_FILE"
return 1
fi
fi
# Verify download
if [ ! -f "$TEMP_FILE" ] || [ ! -s "$TEMP_FILE" ]; then
print_error "Downloaded model file is empty or missing"
rm -f "$TEMP_FILE"
return 1
fi
# Move to final location
mv "$TEMP_FILE" "$TINY_MODEL_PATH"
# Verify the model file
if [ -f "$TINY_MODEL_PATH" ]; then
local MODEL_SIZE=$(du -h "$TINY_MODEL_PATH" | cut -f1)
print_success "whisper.cpp tiny model installed successfully ($MODEL_SIZE)"
# Create a marker file to indicate this model was pre-installed
echo "$(date)" > "$WHISPERCPP_DIR/.vocalinux_preinstalled"
return 0
else
print_error "whisper.cpp model installation failed"
return 1
fi
}
# Function to install desktop entry with error handling
install_desktop_entry() {
print_info "Installing desktop entry..."
# Check if desktop entry file exists
if [ ! -f "vocalinux.desktop" ]; then
print_error "Desktop entry file not found: vocalinux.desktop"
return 1
fi
# Create desktop directory if it doesn't exist
mkdir -p "$DESKTOP_DIR" || {
print_error "Failed to create desktop directory: $DESKTOP_DIR"
return 1
}
# Copy desktop entry
cp vocalinux.desktop "$DESKTOP_DIR/" || {
print_error "Failed to copy desktop entry to $DESKTOP_DIR"
return 1
}
# Update the desktop entry to use the wrapper script with GI_TYPELIB_PATH
WRAPPER_SCRIPT="$HOME/.local/bin/vocalinux-gui"
if [ ! -f "$WRAPPER_SCRIPT" ]; then
print_warning "Wrapper script not found at $WRAPPER_SCRIPT"
print_warning "Desktop entry may not work correctly"
else
# Update Exec line to include GI_TYPELIB_PATH for PyGObject
# Use the detected path for cross-distro compatibility
sed -i "s|^Exec=vocalinux|Exec=env GI_TYPELIB_PATH=$GI_TYPELIB_DETECTED $WRAPPER_SCRIPT|" "$DESKTOP_DIR/vocalinux.desktop" || {
print_warning "Failed to update desktop entry path"
}
print_info "Updated desktop entry to use wrapper script with GI_TYPELIB_PATH"
fi
# Make desktop entry executable
chmod +x "$DESKTOP_DIR/vocalinux.desktop" || {
print_warning "Failed to make desktop entry executable"
}
return 0
}
# Function to install icons with error handling
install_icons() {
print_info "Installing application icons..."
# Create icon directory if it doesn't exist
mkdir -p "$ICON_DIR" || {
print_error "Failed to create icon directory: $ICON_DIR"
return 1
}
# Check if icons directory exists
if [ ! -d "resources/icons/scalable" ]; then
print_warning "Custom icons not found in resources/icons/scalable directory"
return 1
fi
# List of icons to install
local ICONS=(
"vocalinux.svg"
"vocalinux-microphone.svg"
"vocalinux-microphone-off.svg"
"vocalinux-microphone-process.svg"
)
# Install each icon
local INSTALLED_COUNT=0
for icon in "${ICONS[@]}"; do
if [ -f "resources/icons/scalable/$icon" ]; then
cp "resources/icons/scalable/$icon" "$ICON_DIR/" || {
print_warning "Failed to copy icon: $icon"
continue
}
((INSTALLED_COUNT++))
else
print_warning "Icon not found: resources/icons/scalable/$icon"
fi
done
if [ "$INSTALLED_COUNT" -eq "${#ICONS[@]}" ]; then
print_success "Installed all custom Vocalinux icons"
return 0
elif [ "$INSTALLED_COUNT" -gt 0 ]; then
print_warning "Installed $INSTALLED_COUNT/${#ICONS[@]} custom Vocalinux icons"
return 0
else
print_error "Failed to install any icons"
return 1
fi
}
# Function to update icon cache and desktop database
update_icon_cache() {
print_info "Updating icon cache..."
# Check if gtk-update-icon-cache command exists
if command_exists gtk-update-icon-cache; then
gtk-update-icon-cache -f -t "${XDG_DATA_HOME:-$HOME/.local/share}/icons/hicolor" 2>/dev/null || {
print_warning "Failed to update icon cache"
}
else
print_warning "gtk-update-icon-cache command not found, skipping icon cache update"
fi
# Update desktop database so the app appears in application menus immediately
print_info "Updating desktop database..."
if command_exists update-desktop-database; then
update-desktop-database "${XDG_DATA_HOME:-$HOME/.local/share}/applications" 2>/dev/null || {
print_warning "Failed to update desktop database"
}
else
print_warning "update-desktop-database command not found - app may not appear in menu until next login"
fi
}
# Install desktop entry
install_desktop_entry || print_warning "Desktop entry installation failed"
# Install icons
install_icons || print_warning "Icon installation failed"
# Install models based on selected engine
# whisper.cpp is now the default engine
if [ "$SKIP_MODELS" = "no" ]; then
# Check which engines are installed and download appropriate models
# Install whisper.cpp model (default engine)
if "$VENV_DIR/bin/python" -c "from pywhispercpp.model import Model" 2>/dev/null; then
print_info "whisper.cpp is installed - downloading tiny model (default engine)..."
install_whispercpp_model || print_warning "whisper.cpp model download failed - model will be downloaded on first run"
fi
# Install OpenAI Whisper model if whisper engine is installed
if "$VENV_DIR/bin/python" -c "import whisper" 2>/dev/null; then
print_info "Whisper (OpenAI) is installed - downloading tiny model..."
install_whisper_model || print_warning "Whisper model download failed - model will be downloaded on first run"
fi
else
print_info "Skipping model downloads (--skip-models specified)"
print_info "Models will be downloaded automatically on first application run"
fi
# Install VOSK models (always useful as fallback)
if [ "$SKIP_MODELS" = "no" ]; then
install_vosk_models || print_warning "VOSK model installation failed - models will be downloaded on first run"
else
print_info "Skipping VOSK model installation (--skip-models specified)"
print_info "Models will be downloaded automatically on first application run"
fi
# Update icon cache
update_icon_cache
# Function to run tests with better error handling
run_tests() {
print_info "Running tests..."
# Check if pytest is installed in the virtual environment
if ! "$VENV_DIR/bin/python" -c "import pytest" &>/dev/null; then
print_info "Installing pytest and related packages..."
pip install pytest pytest-mock pytest-cov || {
print_error "Failed to install pytest. Cannot run tests."
return 1
}
fi
# Create a directory for test results
local TEST_RESULTS_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/vocalinux/test_results"
mkdir -p "$TEST_RESULTS_DIR"
local TEST_RESULTS_FILE="$TEST_RESULTS_DIR/pytest_$(date +%Y%m%d_%H%M%S).xml"
print_info "Running tests with pytest..."
print_info "This may take a few minutes..."
# Run the tests with pytest and capture output
local TEST_OUTPUT_FILE=$(mktemp)
if pytest -v --junitxml="$TEST_RESULTS_FILE" | tee "$TEST_OUTPUT_FILE"; then
print_success "All tests passed!"
print_info "Test results saved to: $TEST_RESULTS_FILE"
rm -f "$TEST_OUTPUT_FILE"
return 0
else
local FAILED_COUNT=$(grep -c "FAILED" "$TEST_OUTPUT_FILE")
print_error "$FAILED_COUNT tests failed!"
print_info "Test results saved to: $TEST_RESULTS_FILE"
print_info "Check the test output for details."
rm -f "$TEST_OUTPUT_FILE"
return 1
fi
}
# Run tests if requested
if [[ "$RUN_TESTS" == "yes" ]]; then
if run_tests; then
print_success "Test suite completed successfully."
else
print_warning "Test suite completed with failures."
print_warning "You can still use the application, but some features might not work as expected."
fi
fi
# Function to verify the installation
verify_installation() {
print_info "Verifying installation..."
local ISSUES=0
# Check if virtual environment exists and is activated
if [ ! -d "$VENV_DIR" ] || [ ! -f "$VENV_DIR/bin/activate" ]; then
print_error "Virtual environment not found or incomplete."
ISSUES=$((ISSUES + 1))
fi
# Check if vocalinux command is available
if ! command -v vocalinux &>/dev/null && [ ! -f "$VENV_DIR/bin/vocalinux" ]; then
print_error "Vocalinux command not found."
ISSUES=$((ISSUES + 1))
fi
# Check if desktop entry is installed
if [ ! -f "$DESKTOP_DIR/vocalinux.desktop" ]; then
print_warning "Desktop entry not found. Application may not appear in application menu."
ISSUES=$((ISSUES + 1))
fi
# Check if icons are installed
local ICON_COUNT=0
for icon in vocalinux.svg vocalinux-microphone.svg vocalinux-microphone-off.svg vocalinux-microphone-process.svg; do
if [ -f "$ICON_DIR/$icon" ]; then
ICON_COUNT=$((ICON_COUNT + 1))
fi
done
if [ "$ICON_COUNT" -lt 4 ]; then
print_warning "Some icons are missing. Application may not display correctly."
ISSUES=$((ISSUES + 1))
fi
# Check if Python package is importable using venv python
if ! "$VENV_DIR/bin/python" -c "import vocalinux" &>/dev/null; then
print_error "Vocalinux Python package cannot be imported."
ISSUES=$((ISSUES + 1))
fi
# Return the number of issues found
return $ISSUES
}
# Function to print beautiful welcome message
print_welcome_message() {
local ISSUES=$1
# ASCII art header
cat << 'EOF'
▗▖ ▗▖ ▗▄▖ ▗▄▄▖ ▗▄▖ ▗▖ ▗▄▄▄▖▗▖ ▗▖▗▖ ▗▖▗▖ ▗▖
▐▌ ▐▌▐▌ ▐▌▐▌ ▐▌ ▐▌▐▌ █ ▐▛▚▖▐▌▐▌ ▐▌ ▝▚▞▘
▐▌ ▐▌▐▌ ▐▌▐▌ ▐▛▀▜▌▐▌ █ ▐▌ ▝▜▌▐▌ ▐▌ ▐▌
▝▚▞▘ ▝▚▄▞▘▝▚▄▄▖▐▌ ▐▌▐▙▄▄▖▗▄█▄▖▐▌ ▐▌▝▚▄▞▘▗▞▘▝▚▖
✓ Installation Complete!
EOF
# Success or warning message
if [ "$ISSUES" -eq 0 ]; then
print_success "Vocalinux has been installed successfully!"
else
print_warning "Installation complete with $ISSUES minor issue(s)"
print_warning "The application should still work normally."
fi
# Get engine info for display
local ENGINE_INFO="${SELECTED_ENGINE:-whisper_cpp}"
local ENGINE_DISPLAY_NAME=""
local BACKEND_INFO=""
case "$ENGINE_INFO" in
whisper_cpp)
ENGINE_DISPLAY_NAME="Whisper.cpp"
if [[ "${WHISPERCPP_BACKEND}" == "gpu" ]]; then
BACKEND_INFO="GPU Accelerated"
else
BACKEND_INFO="CPU"
fi
;;
whisper)
ENGINE_DISPLAY_NAME="Whisper (OpenAI)"
BACKEND_INFO="PyTorch/CUDA"
;;
vosk)
ENGINE_DISPLAY_NAME="VOSK"
BACKEND_INFO="Lightweight"
;;
esac
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " 📦 What Was Installed"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo " Application: Vocalinux (voice dictation for Linux)"
echo " Engine: $ENGINE_DISPLAY_NAME"
if [[ -n "$BACKEND_INFO" ]]; then
echo " Backend: $BACKEND_INFO"
fi
echo " Location: ${INSTALL_DIR:-\$HOME/.local/share/vocalinux}"
echo " Virtual Env: $VENV_DIR"
echo " Config: $CONFIG_DIR"
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " 🚀 Getting Started"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "1. Launch Vocalinux"
echo " • From app menu: Look for 'Vocalinux'"
echo " • From terminal: Run 'vocalinux' command"
echo ""
echo "2. Find the icon in your system tray (top bar)"
echo " • Click for settings and status"
echo " • Right-click for menu options"
echo ""
echo "3. Start dictating!"
echo -e " \e[1mDouble-tap Ctrl\e[0m anywhere to toggle recording"
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " 🎤 Testing Your Setup"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "1. Open any text editor (gedit, VS Code, LibreOffice, etc.)"
echo "2. Double-tap Ctrl to start recording"
echo "3. Say: 'Hello world period'"
echo "4. Double-tap Ctrl to stop"
echo "5. You should see: 'Hello world.'"
echo ""
echo "💡 Voice commands: 'period' 'comma' 'new line' 'delete that'"
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " 🔧 Managing Vocalinux"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "Commands:"
echo " vocalinux Start the application"
echo " vocalinux --debug Start with debug logging"
echo " vocalinux-gui Open settings GUI"
echo ""
echo "To activate the virtual environment:"
echo " source ${ACTIVATION_SCRIPT:-activate-vocalinux.sh}"
echo ""
echo "To uninstall:"
echo " ./uninstall.sh"
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " 📚 Need Help?"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "• Issues & Bugs: https://github.com/jatinkrmalik/vocalinux/issues"
echo "• Documentation: https://github.com/jatinkrmalik/vocalinux"
echo "• Star on GitHub: ⭐ https://github.com/jatinkrmalik/vocalinux"
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo -e " \e[1m\e[32m✨ Happy Dictating! ✨\e[0m"
echo ""
# Installation details (optional, for debugging)
if [[ "$VERBOSE" == "yes" ]]; then
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " 🔍 Installation Details (Debug Mode)"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "Virtual environment: $VENV_DIR"
echo "Desktop entry: $DESKTOP_DIR/vocalinux.desktop"
echo "Configuration: $CONFIG_DIR"
echo "Data directory: $DATA_DIR"
echo "Wrapper script: $HOME/.local/bin/vocalinux"
echo ""
fi
}
# Verify the installation
verify_installation
INSTALL_ISSUES=$?
# Print welcome message
print_welcome_message $INSTALL_ISSUES
print_success "Installation process completed!"