#!/bin/bash # ============================================================================= # DaVinci Resolve Free - Arch Linux + AMD (RDNA2/3/4) + Hyprland Install Script # ============================================================================= # # Version 4.1 - Mirrors the proven NVIDIA-Open install path; only the GPU # stack (ROCm vs CUDA) and HSA_OVERRIDE handling differ. # 4.1: pinned ROCm 7.1.1 from Arch Linux Archive as default OpenCL provider, # because Arch's current ROCm 7.2.x breaks DaVinci Resolve on AMD. # # Install method (shared with NVIDIA script): # 1. Find the Resolve ZIP in ~/Downloads/ # 2. unzip ZIP -> .run AppImage -> --appimage-extract -> squashfs-root # 3. Replace bundled glib/gio/gmodule with system symlinks (ABI-safe) # KEEP vendored libc++/libc++abi (removing breaks Resolve) # 4. rsync to /opt/resolve, patchelf --set-rpath every ELF # 5. Install desktop entries, icons, udev rules system-wide # 6. Apply audio fixes: DeckLink->ALSA, snd-aloop, PipeWire bridge # 7. Apply Hyprland window rules and create XWayland launcher # # AMD-specific bits: # - ROCm 7.2 (Arch extra) for OpenCL, with auto-detected HSA_OVERRIDE for # RDNA2/3/4 cards that aren't natively in ROCm's supported list # - Generation-aware gfx target detection (gfx1030/1100/1101/1102/1200/1201) # - switcherooctl integration for hybrid Intel/AMD systems # - BlackmagicRaw OpenCL decoder disable (prevents ROCm conflicts) # # Pacman does NOT track this install. To remove: # sudo rm -rf /opt/resolve # sudo rm -f /usr/share/applications/{DaVinciResolve,blackmagicraw-*}.desktop # sudo rm -f /usr/lib/udev/rules.d/{99-BlackmagicDevices,99-ResolveKeyboardHID,99-DavinciPanel}.rules # # Based on techniques from: # - The Omarchy NVIDIA-Open Resolve installer (this script is its mirror) # - https://github.com/zelikos/davincibox # - https://wiki.archlinux.org/title/DaVinci_Resolve # # Usage: # chmod +x install-davinci-resolve.sh # ./install-davinci-resolve.sh # # Env vars: # RESOLVE_NO_ALOOP=1 Skip snd-aloop setup (you have a real audio interface) # # ============================================================================= set -e # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' CYAN='\033[0;36m' MAGENTA='\033[0;35m' BOLD='\033[1m' NC='\033[0m' # No Color # Logging functions info() { echo -e "${BLUE}[INFO]${NC} $1"; } success() { echo -e "${GREEN}[OK]${NC} $1"; } warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } error() { echo -e "${RED}[ERROR]${NC} $1"; exit 1; } header() { echo -e "\n${CYAN}=== $1 ===${NC}\n"; } scan_item() { echo -e " ${MAGENTA}•${NC} $1: ${BOLD}$2${NC}"; } # ============================================================================= # CONFIGURATION OPTIONS # ============================================================================= # OpenCL Provider: # "rocm-pinned-7.1.1" - DEFAULT. ROCm 7.1.1 stack from Arch Linux Archive, # pinned in /etc/pacman.conf IgnorePkg. This is the # ONLY currently-known-working configuration for # DaVinci Resolve on AMD as of May 2026. # Confirmed by CachyOS community + this script's # own testing on RX 9060 XT (Navi 44, gfx1200). # Refs: # https://github.com/ROCm/ROCm/issues/5982 # https://discuss.cachyos.org/t/davinci-resolve-amd-rocm-fails-without-downgrade/28036 # "rocm-full" - Current Arch repos ROCm 7.2.x. KNOWN BROKEN with # DaVinci Resolve - clCreateContext fails or hangs # on Color page. Will work again when ROCm 7.3+ ships. # "opencl-amd" - AUR opencl-amd 7.2.x. Same ROCm 7.2 ABI bug. # KNOWN BROKEN with Resolve currently. OPENCL_PROVIDER="rocm-pinned-7.1.1" # ============================================================================= # SYSTEM SCAN VARIABLES (populated during scan) # ============================================================================= # System info SCAN_KERNEL_VERSION="" SCAN_KERNEL_MAJOR=0 SCAN_KERNEL_MINOR=0 SCAN_ARCH="" # GPU info - Single GPU (legacy/primary) SCAN_GPU_VENDOR="" SCAN_GPU_MODEL="" SCAN_GPU_DRIVER="" SCAN_GPU_RDNA_VERSION="" SCAN_IS_RDNA4=false # GPU info - Multi-GPU support SCAN_GPU_COUNT=0 SCAN_GPUS=() SCAN_GPU_VENDORS=() SCAN_GPU_MODELS=() SCAN_GPU_TYPES=() SCAN_GPU_PCI_IDS=() SCAN_GPU_RDNA_VERSIONS=() SCAN_GPU_GFX_TARGETS=() # Per-GPU gfx target (e.g. gfx1030, gfx1102, gfx1200) SCAN_GPU_HSA_OVERRIDES=() # Per-GPU recommended HSA_OVERRIDE_GFX_VERSION ("" if natively supported) SCAN_GPU_DRIVERS_LOADED=() SCAN_PRIMARY_GPU_INDEX=0 SCAN_HSA_OVERRIDE_VALUE="" # Set from primary GPU after scan SCAN_GFX_TARGET="" # Set from primary GPU after scan # Hybrid GPU flags SCAN_HAS_IGPU=false SCAN_HAS_DGPU=false SCAN_HAS_AMD_DGPU=false SCAN_HAS_NVIDIA_DGPU=false SCAN_HAS_AMD_IGPU=false SCAN_HAS_INTEL_IGPU=false SCAN_IS_HYBRID=false SCAN_PRIME_AVAILABLE=false # Mesa/Graphics SCAN_MESA_VERSION="" SCAN_MESA_MAJOR=0 SCAN_VULKAN_AVAILABLE=false SCAN_OPENGL_RENDERER="" # OpenCL SCAN_OPENCL_INSTALLED=false SCAN_OPENCL_PROVIDER="" SCAN_OPENCL_PLATFORMS="" SCAN_AMD_OPENCL_WORKING=false # Audio SCAN_AUDIO_SERVER="" SCAN_PIPEWIRE_INSTALLED=false SCAN_PULSEAUDIO_INSTALLED=false # Display SCAN_DISPLAY_SERVER="" SCAN_COMPOSITOR="" SCAN_HYPRLAND_INSTALLED=false SCAN_HYPRLAND_RUNNING=false # DaVinci Resolve SCAN_RESOLVE_INSTALLED=false SCAN_RESOLVE_VERSION="" SCAN_RESOLVE_PATH="" # Package manager SCAN_AUR_HELPER="" # Disk space SCAN_ROOT_FREE_GB=0 SCAN_HOME_FREE_GB=0 # Existing packages (to avoid reinstalling) SCAN_INSTALLED_PACKAGES="" # ============================================================================= # SYSTEM SCAN FUNCTIONS # ============================================================================= scan_kernel() { SCAN_KERNEL_VERSION=$(uname -r) SCAN_KERNEL_MAJOR=$(echo "$SCAN_KERNEL_VERSION" | cut -d'.' -f1) SCAN_KERNEL_MINOR=$(echo "$SCAN_KERNEL_VERSION" | cut -d'.' -f2) SCAN_ARCH=$(uname -m) } scan_gpu() { # Arrays for multi-GPU detection SCAN_GPU_COUNT=0 SCAN_GPUS=() # Array of GPU info strings SCAN_GPU_VENDORS=() # Array of vendors SCAN_GPU_MODELS=() # Array of models SCAN_GPU_TYPES=() # "integrated" or "discrete" SCAN_GPU_PCI_IDS=() # PCI bus IDs # Flags for GPU types present SCAN_HAS_IGPU=false SCAN_HAS_DGPU=false SCAN_HAS_AMD_DGPU=false SCAN_HAS_NVIDIA_DGPU=false SCAN_HAS_AMD_IGPU=false SCAN_HAS_INTEL_IGPU=false SCAN_IS_HYBRID=false # Primary GPU for DaVinci Resolve (discrete preferred) SCAN_PRIMARY_GPU_INDEX=0 if ! command -v lspci &>/dev/null; then return fi # Get all GPUs while IFS= read -r gpu_line; do [[ -z "$gpu_line" ]] && continue local pci_id=$(echo "$gpu_line" | cut -d' ' -f1) local gpu_info=$(echo "$gpu_line" | sed 's/^[^ ]* //' | sed 's/.*: //') local vendor="" local gpu_type="discrete" # Default assumption local rdna_ver="" # Detect vendor and type. # Order matters: check Intel/NVIDIA first because "ATI" matches # "Corpor[ATI]on" in Intel/NVIDIA lspci lines case-insensitively. if echo "$gpu_line" | grep -qi "Intel"; then vendor="Intel" gpu_type="integrated" SCAN_HAS_IGPU=true SCAN_HAS_INTEL_IGPU=true rdna_ver="N/A" elif echo "$gpu_line" | grep -qi "NVIDIA"; then vendor="NVIDIA" gpu_type="discrete" SCAN_HAS_DGPU=true SCAN_HAS_NVIDIA_DGPU=true rdna_ver="N/A" elif echo "$gpu_line" | grep -qiE '\bAMD\b|\bATI\b|Radeon'; then vendor="AMD" # Check if integrated (APU) - common identifiers if echo "$gpu_line" | grep -qiE "Radeon.*(Graphics|Vega|RX Vega|Renoir|Cezanne|Barcelo|Rembrandt|Phoenix|Hawk|Strix Point)|AMD.*Ryzen.*Radeon|Integrated"; then gpu_type="integrated" SCAN_HAS_IGPU=true SCAN_HAS_AMD_IGPU=true else gpu_type="discrete" SCAN_HAS_DGPU=true SCAN_HAS_AMD_DGPU=true fi # Detect RDNA generation if echo "$gpu_line" | grep -qiE "9070|9080|Navi 4|gfx1201"; then rdna_ver="RDNA4" SCAN_IS_RDNA4=true elif echo "$gpu_line" | grep -qiE "7[0-9]{3}|Navi 3|gfx11"; then rdna_ver="RDNA3" elif echo "$gpu_line" | grep -qiE "6[0-9]{3}|Navi 2|gfx103"; then rdna_ver="RDNA2" elif echo "$gpu_line" | grep -qiE "5[0-9]{3}|Navi 1|gfx101"; then rdna_ver="RDNA1" elif echo "$gpu_line" | grep -qiE "Vega|gfx9"; then rdna_ver="Vega" elif echo "$gpu_line" | grep -qiE "Phoenix|Hawk Point|Strix"; then rdna_ver="RDNA3 (iGPU)" elif echo "$gpu_line" | grep -qiE "Rembrandt|Barcelo"; then rdna_ver="RDNA2 (iGPU)" else rdna_ver="Pre-RDNA/GCN" fi # Detect gfx target and required HSA_OVERRIDE_GFX_VERSION. # ROCm 7.2 officially supports: gfx1030, gfx1100, gfx1101, gfx1200, gfx1201. # Other RDNA2/3 silicon needs HSA_OVERRIDE to spoof as the nearest supported target. local gfx_target="" local hsa_override="" # RDNA4 - all natively supported if echo "$gpu_line" | grep -qiE "Navi 48|9070|9080|gfx1201"; then gfx_target="gfx1201" elif echo "$gpu_line" | grep -qiE "Navi 44|RX 90[6-9][0-9]|gfx1200"; then gfx_target="gfx1200" # RDNA3 elif echo "$gpu_line" | grep -qiE "Navi 31|RX 79[0-9]{2}|gfx1100"; then gfx_target="gfx1100" elif echo "$gpu_line" | grep -qiE "Navi 32|RX 7[78][0-9]{2}|gfx1101"; then gfx_target="gfx1101" elif echo "$gpu_line" | grep -qiE "Navi 33|RX 76[0-9]{2}|gfx1102"; then gfx_target="gfx1102" hsa_override="11.0.0" # gfx1102 not natively supported, spoof as gfx1100 # RDNA2 elif echo "$gpu_line" | grep -qiE "Navi 21|RX 6[89][0-9]{2}|gfx1030"; then gfx_target="gfx1030" elif echo "$gpu_line" | grep -qiE "Navi 22|RX 67[0-9]{2}|gfx1031"; then gfx_target="gfx1031" hsa_override="10.3.0" # spoof as gfx1030 elif echo "$gpu_line" | grep -qiE "Navi 23|RX 66[0-9]{2}|gfx1032"; then gfx_target="gfx1032" hsa_override="10.3.0" elif echo "$gpu_line" | grep -qiE "Navi 24|RX 6[45][0-9]{2}|gfx1034"; then gfx_target="gfx1034" hsa_override="10.3.0" fi fi # Store GPU info SCAN_GPUS+=("$gpu_info") SCAN_GPU_VENDORS+=("$vendor") SCAN_GPU_MODELS+=("$gpu_info") SCAN_GPU_TYPES+=("$gpu_type") SCAN_GPU_PCI_IDS+=("$pci_id") # Store RDNA version, gfx target, and HSA override for AMD GPUs if [[ "$vendor" == "AMD" ]]; then SCAN_GPU_RDNA_VERSIONS+=("$rdna_ver") SCAN_GPU_GFX_TARGETS+=("$gfx_target") SCAN_GPU_HSA_OVERRIDES+=("$hsa_override") else SCAN_GPU_RDNA_VERSIONS+=("") SCAN_GPU_GFX_TARGETS+=("") SCAN_GPU_HSA_OVERRIDES+=("") fi SCAN_GPU_COUNT=$((SCAN_GPU_COUNT + 1)) done < <(lspci 2>/dev/null | grep -iE "VGA|3D|Display") # Detect hybrid graphics if [[ "$SCAN_HAS_IGPU" == true && "$SCAN_HAS_DGPU" == true ]]; then SCAN_IS_HYBRID=true fi # Select primary GPU for DaVinci Resolve (prefer discrete AMD, then discrete NVIDIA, then any AMD) for i in "${!SCAN_GPU_VENDORS[@]}"; do if [[ "${SCAN_GPU_VENDORS[$i]}" == "AMD" && "${SCAN_GPU_TYPES[$i]}" == "discrete" ]]; then SCAN_PRIMARY_GPU_INDEX=$i break fi done # If no discrete AMD, check for discrete NVIDIA if [[ ${#SCAN_GPU_TYPES[@]} -gt 0 && "${SCAN_GPU_TYPES[$SCAN_PRIMARY_GPU_INDEX]}" != "discrete" ]]; then for i in "${!SCAN_GPU_VENDORS[@]}"; do if [[ "${SCAN_GPU_VENDORS[$i]}" == "NVIDIA" && "${SCAN_GPU_TYPES[$i]}" == "discrete" ]]; then SCAN_PRIMARY_GPU_INDEX=$i break fi done fi # Set legacy variables for compatibility (using primary GPU) if [[ $SCAN_GPU_COUNT -gt 0 ]]; then SCAN_GPU_VENDOR="${SCAN_GPU_VENDORS[$SCAN_PRIMARY_GPU_INDEX]}" SCAN_GPU_MODEL="${SCAN_GPU_MODELS[$SCAN_PRIMARY_GPU_INDEX]}" SCAN_GPU_RDNA_VERSION="${SCAN_GPU_RDNA_VERSIONS[$SCAN_PRIMARY_GPU_INDEX]}" SCAN_GFX_TARGET="${SCAN_GPU_GFX_TARGETS[$SCAN_PRIMARY_GPU_INDEX]}" SCAN_HSA_OVERRIDE_VALUE="${SCAN_GPU_HSA_OVERRIDES[$SCAN_PRIMARY_GPU_INDEX]}" fi # Check loaded kernel drivers SCAN_GPU_DRIVERS_LOADED=() if lsmod 2>/dev/null | grep -q "^amdgpu"; then SCAN_GPU_DRIVERS_LOADED+=("amdgpu") SCAN_GPU_DRIVER="amdgpu" fi if lsmod 2>/dev/null | grep -q "^nvidia"; then SCAN_GPU_DRIVERS_LOADED+=("nvidia") [[ -z "$SCAN_GPU_DRIVER" ]] && SCAN_GPU_DRIVER="nvidia" fi if lsmod 2>/dev/null | grep -q "^i915"; then SCAN_GPU_DRIVERS_LOADED+=("i915") [[ -z "$SCAN_GPU_DRIVER" ]] && SCAN_GPU_DRIVER="i915" fi if lsmod 2>/dev/null | grep -q "^radeon"; then SCAN_GPU_DRIVERS_LOADED+=("radeon") fi # Detect DRI_PRIME / PRIME render offload status SCAN_PRIME_AVAILABLE=false if [[ "$SCAN_IS_HYBRID" == true ]]; then # Check if DRI_PRIME would work if [[ -d "/sys/class/drm" ]]; then local render_nodes=$(ls /dev/dri/renderD* 2>/dev/null | wc -l) if [[ $render_nodes -gt 1 ]]; then SCAN_PRIME_AVAILABLE=true fi fi fi } scan_mesa() { if command -v glxinfo &>/dev/null; then SCAN_MESA_VERSION=$(glxinfo 2>/dev/null | grep "OpenGL version" | grep -oP "Mesa \K[0-9]+\.[0-9]+(\.[0-9]+)?" || echo "") SCAN_OPENGL_RENDERER=$(glxinfo 2>/dev/null | grep "OpenGL renderer" | sed 's/OpenGL renderer string: //' || echo "") if [[ -n "$SCAN_MESA_VERSION" ]]; then SCAN_MESA_MAJOR=$(echo "$SCAN_MESA_VERSION" | cut -d'.' -f1) fi fi # Check Vulkan if command -v vulkaninfo &>/dev/null && vulkaninfo --summary &>/dev/null; then SCAN_VULKAN_AVAILABLE=true fi } scan_opencl() { # Check what OpenCL packages are installed # Check for full ROCm stack first (preferred for RDNA4) if pacman -Qq rocm-opencl-runtime &>/dev/null; then SCAN_OPENCL_INSTALLED=true # Check if full ROCm stack is installed if pacman -Qq rocm-hip-runtime rocm-core &>/dev/null; then SCAN_OPENCL_PROVIDER="rocm-full" # Get ROCm version local rocm_ver=$(pacman -Q rocm-core 2>/dev/null | awk '{print $2}' | cut -d'-' -f1) [[ -n "$rocm_ver" ]] && SCAN_OPENCL_PROVIDER="rocm-full ($rocm_ver)" else SCAN_OPENCL_PROVIDER="rocm-opencl-runtime" fi elif pacman -Qq opencl-amd &>/dev/null; then SCAN_OPENCL_INSTALLED=true SCAN_OPENCL_PROVIDER="opencl-amd" elif pacman -Qq mesa-opencl &>/dev/null || pacman -Qq opencl-mesa &>/dev/null; then SCAN_OPENCL_INSTALLED=true SCAN_OPENCL_PROVIDER="mesa (rusticl)" elif pacman -Qq intel-compute-runtime &>/dev/null; then SCAN_OPENCL_INSTALLED=true SCAN_OPENCL_PROVIDER="intel-compute-runtime" fi # Check if OpenCL actually works with AMD if command -v clinfo &>/dev/null; then SCAN_OPENCL_PLATFORMS=$(clinfo --list 2>/dev/null | head -5 || echo "") if clinfo --list 2>/dev/null | grep -qi "AMD\|gfx\|Radeon"; then SCAN_AMD_OPENCL_WORKING=true fi fi } scan_audio() { # Detect audio server if command -v pactl &>/dev/null; then local server_name=$(pactl info 2>/dev/null | grep "Server Name" || echo "") if echo "$server_name" | grep -qi "PipeWire"; then SCAN_AUDIO_SERVER="PipeWire" SCAN_PIPEWIRE_INSTALLED=true elif echo "$server_name" | grep -qi "PulseAudio"; then SCAN_AUDIO_SERVER="PulseAudio" SCAN_PULSEAUDIO_INSTALLED=true fi fi # Double-check with package detection if pacman -Qq pipewire &>/dev/null; then SCAN_PIPEWIRE_INSTALLED=true fi if pacman -Qq pulseaudio &>/dev/null; then SCAN_PULSEAUDIO_INSTALLED=true fi # If we couldn't detect from pactl, use package info if [[ -z "$SCAN_AUDIO_SERVER" ]]; then if [[ "$SCAN_PIPEWIRE_INSTALLED" == true ]]; then SCAN_AUDIO_SERVER="PipeWire (detected from packages)" elif [[ "$SCAN_PULSEAUDIO_INSTALLED" == true ]]; then SCAN_AUDIO_SERVER="PulseAudio (detected from packages)" else SCAN_AUDIO_SERVER="Unknown/None" fi fi } scan_display() { # Detect display server if [[ -n "$WAYLAND_DISPLAY" ]]; then SCAN_DISPLAY_SERVER="Wayland" elif [[ "$XDG_SESSION_TYPE" == "wayland" ]]; then # Wayland session but WAYLAND_DISPLAY not set (XWayland context) SCAN_DISPLAY_SERVER="Wayland (XWayland available)" elif [[ -n "$DISPLAY" ]]; then SCAN_DISPLAY_SERVER="X11" else SCAN_DISPLAY_SERVER="None/TTY" fi # Detect compositor if [[ -n "$HYPRLAND_INSTANCE_SIGNATURE" ]]; then SCAN_COMPOSITOR="Hyprland" SCAN_HYPRLAND_RUNNING=true elif [[ -n "$SWAYSOCK" ]]; then SCAN_COMPOSITOR="Sway" elif [[ -n "$GNOME_DESKTOP_SESSION_ID" ]]; then SCAN_COMPOSITOR="GNOME" elif [[ -n "$KDE_FULL_SESSION" ]]; then SCAN_COMPOSITOR="KDE Plasma" elif pgrep -x "hyprland" &>/dev/null; then SCAN_COMPOSITOR="Hyprland" SCAN_HYPRLAND_RUNNING=true else SCAN_COMPOSITOR="Unknown" fi # Check if Hyprland is installed if pacman -Qq hyprland &>/dev/null || command -v Hyprland &>/dev/null; then SCAN_HYPRLAND_INSTALLED=true fi } scan_resolve() { # Check if DaVinci Resolve is installed if [[ -d "/opt/resolve" ]] || [[ -f "/opt/resolve/bin/resolve" ]]; then SCAN_RESOLVE_INSTALLED=true SCAN_RESOLVE_PATH="/opt/resolve" # Try to get version if [[ -f "/opt/resolve/docs/License.txt" ]]; then SCAN_RESOLVE_VERSION=$(grep -oP "DaVinci Resolve \K[0-9]+\.[0-9]+(\.[0-9]+)?" /opt/resolve/docs/License.txt 2>/dev/null | head -1 || echo "Unknown") fi fi # Also check via pacman if pacman -Qq davinci-resolve &>/dev/null || pacman -Qq davinci-resolve-studio &>/dev/null; then SCAN_RESOLVE_INSTALLED=true fi } scan_aur_helper() { if command -v paru &>/dev/null; then SCAN_AUR_HELPER="paru" elif command -v yay &>/dev/null; then SCAN_AUR_HELPER="yay" elif command -v trizen &>/dev/null; then SCAN_AUR_HELPER="trizen" elif command -v pikaur &>/dev/null; then SCAN_AUR_HELPER="pikaur" else SCAN_AUR_HELPER="" fi } scan_disk_space() { # Get free space in GB SCAN_ROOT_FREE_GB=$(df -BG / 2>/dev/null | awk 'NR==2 {print $4}' | tr -d 'G' || echo "0") SCAN_HOME_FREE_GB=$(df -BG "$HOME" 2>/dev/null | awk 'NR==2 {print $4}' | tr -d 'G' || echo "0") } scan_installed_packages() { # Get list of installed packages for comparison SCAN_INSTALLED_PACKAGES=$(pacman -Qq 2>/dev/null || echo "") } is_pkg_installed() { echo "$SCAN_INSTALLED_PACKAGES" | grep -qx "$1" } # ============================================================================= # MAIN SYSTEM SCAN # ============================================================================= run_system_scan() { header "System Scan" echo -e "${BOLD}Scanning your system to optimize installation...${NC}\n" # Run all scans echo -n " Scanning kernel... " scan_kernel echo -e "${GREEN}done${NC}" echo -n " Scanning GPU... " scan_gpu echo -e "${GREEN}done${NC}" echo -n " Scanning graphics stack... " scan_mesa echo -e "${GREEN}done${NC}" echo -n " Scanning OpenCL... " scan_opencl echo -e "${GREEN}done${NC}" echo -n " Scanning audio... " scan_audio echo -e "${GREEN}done${NC}" echo -n " Scanning display... " scan_display echo -e "${GREEN}done${NC}" echo -n " Scanning for DaVinci Resolve... " scan_resolve echo -e "${GREEN}done${NC}" echo -n " Scanning AUR helpers... " scan_aur_helper echo -e "${GREEN}done${NC}" echo -n " Scanning disk space... " scan_disk_space echo -e "${GREEN}done${NC}" echo -n " Scanning installed packages... " scan_installed_packages echo -e "${GREEN}done${NC}" echo "" } # ============================================================================= # DISPLAY SCAN RESULTS # ============================================================================= display_scan_results() { header "System Configuration Detected" echo -e "${BOLD}System:${NC}" scan_item "Kernel" "$SCAN_KERNEL_VERSION" scan_item "Architecture" "$SCAN_ARCH" echo "" echo -e "${BOLD}GPU(s) Detected: ${SCAN_GPU_COUNT}${NC}" if [[ $SCAN_GPU_COUNT -eq 0 ]]; then scan_item "GPU" "None detected" else for i in "${!SCAN_GPUS[@]}"; do local gpu_label="GPU $((i+1))" local gpu_type_label="${SCAN_GPU_TYPES[$i]}" local vendor="${SCAN_GPU_VENDORS[$i]}" # Mark primary GPU local primary_marker="" if [[ $i -eq $SCAN_PRIMARY_GPU_INDEX ]]; then primary_marker=" ${GREEN}← PRIMARY${NC}" fi echo "" echo -e " ${BOLD}$gpu_label ($gpu_type_label):${NC}$primary_marker" echo -e " Vendor: ${SCAN_GPU_VENDORS[$i]}" echo -e " Model: ${SCAN_GPU_MODELS[$i]}" if [[ "$vendor" == "AMD" && -n "${SCAN_GPU_RDNA_VERSIONS[$i]}" ]]; then echo -e " Gen: ${SCAN_GPU_RDNA_VERSIONS[$i]}" fi if [[ "$vendor" == "AMD" && -n "${SCAN_GPU_GFX_TARGETS[$i]}" ]]; then local override_note="" if [[ -n "${SCAN_GPU_HSA_OVERRIDES[$i]}" ]]; then override_note=" ${YELLOW}(needs HSA_OVERRIDE_GFX_VERSION=${SCAN_GPU_HSA_OVERRIDES[$i]})${NC}" else override_note=" ${GREEN}(natively supported)${NC}" fi echo -e " GFX: ${SCAN_GPU_GFX_TARGETS[$i]}$override_note" fi echo -e " PCI: ${SCAN_GPU_PCI_IDS[$i]}" done echo "" if [[ "$SCAN_IS_HYBRID" == true ]]; then echo -e " ${YELLOW}⚡ Hybrid Graphics Detected${NC}" if [[ "$SCAN_PRIME_AVAILABLE" == true ]]; then echo -e " DRI_PRIME offloading available" fi fi if [[ ${#SCAN_GPU_DRIVERS_LOADED[@]} -gt 0 ]]; then echo -e " Loaded drivers: ${SCAN_GPU_DRIVERS_LOADED[*]}" fi fi echo "" echo -e "${BOLD}Graphics Stack:${NC}" scan_item "Mesa" "${SCAN_MESA_VERSION:-Not detected}" scan_item "OpenGL Renderer" "${SCAN_OPENGL_RENDERER:-Not detected}" scan_item "Vulkan" "$( [[ "$SCAN_VULKAN_AVAILABLE" == true ]] && echo "Available" || echo "Not available" )" echo "" echo -e "${BOLD}OpenCL:${NC}" if [[ "$SCAN_OPENCL_INSTALLED" == true ]]; then scan_item "Provider" "$SCAN_OPENCL_PROVIDER" scan_item "AMD Working" "$( [[ "$SCAN_AMD_OPENCL_WORKING" == true ]] && echo "Yes ✓" || echo "No ✗" )" else scan_item "Status" "Not installed" fi echo "" echo -e "${BOLD}Audio:${NC}" scan_item "Server" "$SCAN_AUDIO_SERVER" echo "" echo -e "${BOLD}Display:${NC}" scan_item "Server" "$SCAN_DISPLAY_SERVER" scan_item "Compositor" "$SCAN_COMPOSITOR" scan_item "Hyprland" "$( [[ "$SCAN_HYPRLAND_INSTALLED" == true ]] && echo "Installed" || echo "Not installed" )" echo "" echo -e "${BOLD}DaVinci Resolve:${NC}" if [[ "$SCAN_RESOLVE_INSTALLED" == true ]]; then scan_item "Status" "Installed" scan_item "Version" "${SCAN_RESOLVE_VERSION:-Unknown}" scan_item "Path" "$SCAN_RESOLVE_PATH" else scan_item "Status" "Not installed" fi echo "" echo -e "${BOLD}Package Management:${NC}" scan_item "AUR Helper" "${SCAN_AUR_HELPER:-None detected}" echo "" echo -e "${BOLD}Disk Space:${NC}" scan_item "Root partition" "${SCAN_ROOT_FREE_GB}GB free" scan_item "Home partition" "${SCAN_HOME_FREE_GB}GB free" echo "" } # ============================================================================= # ANALYZE SCAN AND PROVIDE RECOMMENDATIONS # ============================================================================= analyze_scan_results() { header "Analysis & Recommendations" local issues=0 local warnings=0 # Check kernel compatibility if [[ $SCAN_KERNEL_MAJOR -lt 6 ]] || [[ $SCAN_KERNEL_MAJOR -eq 6 && $SCAN_KERNEL_MINOR -lt 12 ]]; then if [[ "$SCAN_IS_RDNA4" == true ]]; then echo -e "${RED}[ISSUE]${NC} Kernel $SCAN_KERNEL_VERSION is too old for RDNA 4. Need 6.12+." issues=$((issues+1)) fi fi if [[ $SCAN_KERNEL_MAJOR -eq 6 && ($SCAN_KERNEL_MINOR -eq 14 || $SCAN_KERNEL_MINOR -eq 15) ]]; then echo -e "${YELLOW}[WARNING]${NC} Kernel 6.14/6.15 has ROCm DKMS issues. Consider 6.12 or 6.13." warnings=$((warnings+1)) fi # Kernel 7.x is bleeding edge - ROCm DKMS may not yet support it if [[ $SCAN_KERNEL_MAJOR -ge 7 ]]; then echo -e "${YELLOW}[WARNING]${NC} Kernel $SCAN_KERNEL_VERSION is very new. ROCm DKMS may not support it yet." warnings=$((warnings+1)) fi # ========================================================================= # HYBRID GPU HANDLING # ========================================================================= if [[ "$SCAN_IS_HYBRID" == true ]]; then echo -e "${CYAN}[HYBRID GPU]${NC} Multiple GPUs detected - special configuration needed." # AMD iGPU + AMD dGPU if [[ "$SCAN_HAS_AMD_IGPU" == true && "$SCAN_HAS_AMD_DGPU" == true ]]; then echo -e "${YELLOW}[NOTE]${NC} AMD iGPU + AMD dGPU detected." echo " DaVinci Resolve should use the discrete GPU automatically." echo " If it uses the wrong GPU, the launcher pins via PCI tag (DRI_PRIME=pci-DDDD_BB_DD_F)" warnings=$((warnings+1)) fi # Intel iGPU + AMD dGPU if [[ "$SCAN_HAS_INTEL_IGPU" == true && "$SCAN_HAS_AMD_DGPU" == true ]]; then echo -e "${YELLOW}[NOTE]${NC} Intel iGPU + AMD dGPU detected." echo " You may need to launch with: DRI_PRIME=pci-DDDD_BB_DD_F davinci-resolve (use lspci to find the AMD bus)" echo " The launcher script will be configured for this." warnings=$((warnings+1)) fi # Intel iGPU + NVIDIA dGPU if [[ "$SCAN_HAS_INTEL_IGPU" == true && "$SCAN_HAS_NVIDIA_DGPU" == true ]]; then echo -e "${YELLOW}[NOTE]${NC} Intel iGPU + NVIDIA dGPU (Optimus) detected." echo " For NVIDIA GPU: use prime-run or __NV_PRIME_RENDER_OFFLOAD=1" echo " Consider using NVIDIA GPU for better Resolve performance." warnings=$((warnings+1)) fi # AMD iGPU + NVIDIA dGPU if [[ "$SCAN_HAS_AMD_IGPU" == true && "$SCAN_HAS_NVIDIA_DGPU" == true ]]; then echo -e "${YELLOW}[NOTE]${NC} AMD iGPU + NVIDIA dGPU detected." echo " For NVIDIA GPU: use prime-run or __NV_PRIME_RENDER_OFFLOAD=1" warnings=$((warnings+1)) fi # Check if PRIME is available if [[ "$SCAN_PRIME_AVAILABLE" == true ]]; then echo -e "${GREEN}[OK]${NC} PRIME GPU switching is available (multiple render nodes detected)." else echo -e "${YELLOW}[WARNING]${NC} PRIME switching may not be configured correctly." warnings=$((warnings+1)) fi echo "" fi # Check GPU vendor if [[ "$SCAN_GPU_VENDOR" != "AMD" && "$SCAN_HAS_AMD_DGPU" != true ]]; then if [[ "$SCAN_GPU_VENDOR" == "NVIDIA" ]]; then echo -e "${YELLOW}[NOTE]${NC} NVIDIA GPU detected. This script is optimized for AMD." echo " NVIDIA GPUs work well with Resolve but use different drivers (nvidia + CUDA)." warnings=$((warnings+1)) elif [[ "$SCAN_GPU_VENDOR" == "Intel" ]]; then echo -e "${YELLOW}[WARNING]${NC} Only Intel GPU detected. DaVinci Resolve has limited Intel support." warnings=$((warnings+1)) else echo -e "${YELLOW}[WARNING]${NC} GPU vendor not recognized. This script is optimized for AMD GPUs." warnings=$((warnings+1)) fi fi # Check Mesa for RDNA 4 if [[ "$SCAN_IS_RDNA4" == true && $SCAN_MESA_MAJOR -lt 25 ]]; then echo -e "${YELLOW}[WARNING]${NC} Mesa $SCAN_MESA_VERSION may not fully support RDNA 4. Consider updating." warnings=$((warnings+1)) fi # Check OpenCL if [[ "$SCAN_OPENCL_INSTALLED" == true && "$SCAN_AMD_OPENCL_WORKING" == false ]]; then echo -e "${YELLOW}[WARNING]${NC} OpenCL installed but AMD platform not detected. May need reconfiguration." warnings=$((warnings+1)) fi # RDNA4 info if [[ "$SCAN_IS_RDNA4" == true ]]; then echo -e "${CYAN}[RDNA4]${NC} Detected RDNA4 GPU (gfx1201)" echo " Note: RDNA4 support is new - compatibility may vary" echo " If Resolve fails, try switching OPENCL_PROVIDER in script" fi # Show selected OpenCL provider with explanation echo -e "${CYAN}[CONFIG]${NC} OpenCL provider: $OPENCL_PROVIDER" if [[ "$OPENCL_PROVIDER" == "opencl-amd" ]]; then echo " Using AUR package (recommended by Arch Wiki for Resolve)" else echo " Using official Arch ROCm packages" fi # Check disk space (DaVinci Resolve needs ~3GB, opencl-amd ~500MB) if [[ $SCAN_ROOT_FREE_GB -lt 5 ]]; then echo -e "${RED}[ISSUE]${NC} Low disk space on root (${SCAN_ROOT_FREE_GB}GB). Need at least 5GB free." issues=$((issues+1)) fi # Check AUR helper if [[ -z "$SCAN_AUR_HELPER" ]]; then echo -e "${RED}[ISSUE]${NC} No AUR helper found. Install yay or paru first." issues=$((issues+1)) fi # Check display server if [[ "$SCAN_DISPLAY_SERVER" == "None/TTY" ]]; then echo -e "${YELLOW}[WARNING]${NC} No display server detected. Run this script from a graphical session." warnings=$((warnings+1)) fi # Check existing Resolve installation if [[ "$SCAN_RESOLVE_INSTALLED" == true ]]; then echo -e "${YELLOW}[NOTE]${NC} DaVinci Resolve already installed (v${SCAN_RESOLVE_VERSION}). Will update/reconfigure." warnings=$((warnings+1)) fi # Summary echo "" if [[ $issues -eq 0 && $warnings -eq 0 ]]; then echo -e "${GREEN}✓ No issues detected. System looks ready for installation.${NC}" elif [[ $issues -eq 0 ]]; then echo -e "${YELLOW}⚠ $warnings warning(s) detected, but installation can proceed.${NC}" else echo -e "${RED}✗ $issues issue(s) and $warnings warning(s) detected.${NC}" echo -e "${RED} Please resolve issues before continuing.${NC}" fi echo "" return $issues } # ============================================================================= # SMART PACKAGE SELECTION # ============================================================================= build_package_list() { header "Building Package List" # Arrays for packages to install PACMAN_PACKAGES=() AUR_PACKAGES=() SKIP_PACKAGES=() # ---- Build/extraction tools (one-time, used by install step) ---- # Resolve ships as a ZIP containing a .run AppImage; we extract, patchelf, # rsync to /opt/resolve, and update desktop/icon caches afterward. local tools=( unzip patchelf libarchive xdg-user-dirs desktop-file-utils file gtk-update-icon-cache rsync ) # ---- Resolve runtime deps NOT bundled in the AppImage ---- # Resolve bundles its own copies of Qt5, libpng12, libxcb-*, etc. and we # patch RPATH so they're found in /opt/resolve/libs. The few libs below # genuinely have to come from the host system. local runtime=( libxcrypt-compat # provides libcrypt.so.1 (Arch dropped it) ffmpeg4.4 # Resolve links against this older FFmpeg ABI glu # OpenGL Utility (3D rendering) fuse2 # AppImage runtime compat mesa # graphics stack (usually already present) ) # ---- Display/Wayland compat for Hyprland ---- local display=( xorg-xwayland # Resolve has no native Wayland support xdg-desktop-portal xdg-desktop-portal-gtk ) if [[ "$SCAN_IS_HYBRID" == true ]]; then display+=(switcheroo-control) fi # ---- AMD-specific OpenCL provider ---- # rocm-pinned-7.1.1 is the default and only currently-working path with # DaVinci Resolve. The actual install of the 7.1.1 packages happens in # install_rocm_pinned() (downloads from Arch Linux Archive). local amd_opencl=() case "$OPENCL_PROVIDER" in rocm-pinned-7.1.1) info "Using ROCm 7.1.1 pinned (working stack for DaVinci Resolve on AMD)" # ocl-icd provides libOpenCL.so.1 loader. rocminfo for diagnostics. # rocm-smi-lib for power/clock monitoring. rocm-* 7.1.1 are # installed by install_rocm_pinned() below. amd_opencl=(ocl-icd rocminfo rocm-smi-lib) ;; rocm-full) warn "rocm-full = ROCm 7.2.x - KNOWN BROKEN with DaVinci Resolve as of May 2026" warn " See https://github.com/ROCm/ROCm/issues/5982" warn " Switch OPENCL_PROVIDER to 'rocm-pinned-7.1.1' for the working stack." amd_opencl=( rocm-core rocm-opencl-runtime rocm-hip-runtime hsa-rocr rocminfo rocm-smi-lib ) ;; opencl-amd) warn "opencl-amd = AUR ROCm 7.2.x - KNOWN BROKEN with DaVinci Resolve as of May 2026" warn " Same ABI bug as rocm-full. Switch to 'rocm-pinned-7.1.1' for working stack." amd_opencl=(ocl-icd) AUR_PACKAGES+=(opencl-amd) ;; *) error "Unknown OPENCL_PROVIDER: '$OPENCL_PROVIDER'" ;; esac # ---- Audio (smart-pick based on detected server) ---- local audio=() if [[ "$SCAN_AUDIO_SERVER" == *"PipeWire"* ]]; then info "PipeWire detected - installing pipewire-pulse + pipewire-alsa" audio=(pipewire-pulse pipewire-alsa) else info "PulseAudio detected - installing pulseaudio-alsa" audio=(pulseaudio-alsa) fi # ---- Diagnostics ---- local diag=(clinfo mesa-utils expac python-distro) # Combine and split into "need install" vs "skip" for pkg in "${tools[@]}" "${runtime[@]}" "${display[@]}" \ "${amd_opencl[@]}" "${audio[@]}" "${diag[@]}"; do if is_pkg_installed "$pkg"; then SKIP_PACKAGES+=("$pkg") else PACMAN_PACKAGES+=("$pkg") fi done # Display summary echo -e "${BOLD}Packages to install from official repos:${NC}" if [[ ${#PACMAN_PACKAGES[@]} -gt 0 ]]; then echo " ${PACMAN_PACKAGES[*]}" else echo " (none needed)" fi echo "" echo -e "${BOLD}Packages to install from AUR:${NC}" if [[ ${#AUR_PACKAGES[@]} -gt 0 ]]; then echo " ${AUR_PACKAGES[*]}" else echo " (none needed)" fi echo "" echo -e "${BOLD}Packages already installed (skipping):${NC}" if [[ ${#SKIP_PACKAGES[@]} -gt 0 ]]; then echo " ${SKIP_PACKAGES[*]}" else echo " (none)" fi echo "" } # ============================================================================= # INSTALLATION FUNCTIONS # ============================================================================= install_pacman_packages() { if [[ ${#PACMAN_PACKAGES[@]} -eq 0 ]]; then info "No official repo packages to install." return 0 fi header "Installing Official Repo Packages" # Handle opencl-amd removal when using full ROCm stack if [[ "$OPENCL_PROVIDER" == "rocm-full" ]] && is_pkg_installed "opencl-amd"; then warn "Removing opencl-amd (AUR) to install official ROCm stack..." sudo pacman -Rns opencl-amd --noconfirm 2>/dev/null || true fi # Handle OpenCL conflicts - rusticl/mesa-opencl breaks ROCm (confirmed by davincibox) # Reference: https://github.com/zelikos/davincibox - "mesa-libOpenCL breaks rocm-opencl" local -a opencl_conflicts=() for pkg in opencl-rusticl-mesa opencl-clover-mesa mesa-opencl pocl; do if is_pkg_installed "$pkg"; then opencl_conflicts+=("$pkg") fi done if [[ ${#opencl_conflicts[@]} -gt 0 ]]; then warn "Removing conflicting OpenCL packages (breaks ROCm): ${opencl_conflicts[*]}" sudo pacman -Rns "${opencl_conflicts[@]}" --noconfirm 2>/dev/null || true fi info "Installing ${#PACMAN_PACKAGES[@]} packages..." sudo pacman -S --needed --noconfirm "${PACMAN_PACKAGES[@]}" success "Official packages installed." } install_aur_packages() { if [[ ${#AUR_PACKAGES[@]} -eq 0 ]]; then info "No AUR packages to install." return 0 fi header "Installing AUR Packages" # Handle OpenCL conflicts if [[ " ${AUR_PACKAGES[*]} " =~ " opencl-amd " ]]; then # Check for conflicting packages local -a conflicts=() for pkg in mesa-opencl pocl intel-compute-runtime rocm-opencl-runtime; do if is_pkg_installed "$pkg"; then conflicts+=("$pkg") fi done if [[ ${#conflicts[@]} -gt 0 ]]; then warn "Found conflicting OpenCL packages: ${conflicts[*]}" read -p "Remove them to install opencl-amd? [Y/n] " -n 1 -r echo if [[ ! $REPLY =~ ^[Nn]$ ]]; then sudo pacman -Rns "${conflicts[@]}" --noconfirm 2>/dev/null || true fi fi fi info "Installing AUR packages..." $SCAN_AUR_HELPER -S --needed --noconfirm "${AUR_PACKAGES[@]}" success "AUR packages installed." } install_rocm_pinned() { # Install ROCm 7.1.1 from the Arch Linux Archive (ALA) and pin it in # /etc/pacman.conf IgnorePkg. # # Why: ROCm 7.2.x (current Arch extra) breaks DaVinci Resolve - the # OpenCL clCreateContext call fails on AMD GPUs after the 7.2 update. # See ROCm/ROCm#5982 (filed 2026-02-19, still open). # Confirmed broken on 760M iGPU, 7800 XT, RX 9060 XT (this user). # 7.1.1 is the last working release. # # Why not opencl-amd: it bundles its own 7.2.x ROCm internally and # has the same ABI bug. It also conflicts with rocm-opencl-runtime # so you can only have one or the other. # # When ROCm 7.3+ ships with the fix, lift the pin: # sudo sed -i '/^IgnorePkg.*rocm-/d' /etc/pacman.conf # sudo pacman -Syu [[ "$OPENCL_PROVIDER" != "rocm-pinned-7.1.1" ]] && return 0 header "Installing ROCm 7.1.1 (pinned)" # If the working stack is already present at the right version, skip. local need_install=0 for pkg in rocm-core rocm-device-libs rocm-llvm rocm-opencl-runtime comgr; do local ver ver=$(pacman -Q "$pkg" 2>/dev/null | awk '{print $2}') if [[ -z "$ver" || ! "$ver" =~ 7\.1\.1 ]]; then need_install=1 break fi done if [[ "$need_install" == 0 ]] && pacman -Q spirv-llvm-translator 2>/dev/null | grep -q "21.1.3"; then info "ROCm 7.1.1 stack already present at correct versions - skipping ALA download." else # Remove conflicting package: opencl-amd bundles ROCm 7.2.1 and # conflicts with rocm-opencl-runtime if pacman -Q opencl-amd &>/dev/null; then warn "Removing opencl-amd (AUR, conflicts with rocm-opencl-runtime)..." sudo pacman -Rns opencl-amd --noconfirm 2>/dev/null || true fi if pacman -Q opencl-amd-debug &>/dev/null; then sudo pacman -Rns opencl-amd-debug --noconfirm 2>/dev/null || true fi # Download the 6 packages from Arch Linux Archive local ala="https://archive.archlinux.org/packages" local pkgs=( "${ala}/r/rocm-core/rocm-core-7.1.1-1-x86_64.pkg.tar.zst" "${ala}/r/rocm-device-libs/rocm-device-libs-2:7.1.1-1-x86_64.pkg.tar.zst" "${ala}/r/rocm-llvm/rocm-llvm-2:7.1.1-1-x86_64.pkg.tar.zst" "${ala}/r/rocm-opencl-runtime/rocm-opencl-runtime-7.1.1-1-x86_64.pkg.tar.zst" "${ala}/c/comgr/comgr-2:7.1.1-1-x86_64.pkg.tar.zst" "${ala}/s/spirv-llvm-translator/spirv-llvm-translator-21.1.3-1-x86_64.pkg.tar.zst" ) local tmpdir tmpdir=$(mktemp -d -t rocm-pinned-XXXXXX) local trap_old # shellcheck disable=SC2064 trap "rm -rf '$tmpdir'" EXIT info "Downloading ROCm 7.1.1 packages from Arch Linux Archive..." local downloaded=() for url in "${pkgs[@]}"; do local fname fname=$(basename "$url") info " $fname" if curl -fsSL --output "$tmpdir/$fname" "$url"; then downloaded+=("$tmpdir/$fname") else error "Failed to download $url" fi done # numactl + gflags are runtime deps usually already installed sudo pacman -S --needed --noconfirm numactl gflags 2>&1 | tail -3 || true info "Installing pinned ROCm 7.1.1 packages..." sudo pacman -U --noconfirm "${downloaded[@]}" \ || error "Failed to install pinned ROCm 7.1.1 packages from ALA - aborting before launcher/config steps run against a broken stack." rm -rf "$tmpdir" trap - EXIT fi # Pin in /etc/pacman.conf [options] IgnorePkg so future -Syu doesn't # break Resolve again. Idempotent. local pin_pkgs="rocm-core rocm-device-libs rocm-llvm rocm-opencl-runtime comgr spirv-llvm-translator" if grep -q "^IgnorePkg.*rocm-core" /etc/pacman.conf; then info "IgnorePkg already pins ROCm packages." else info "Adding IgnorePkg pin for ROCm 7.1.1 packages..." # Insert immediately after [options] section header sudo sed -i "/^\[options\]/a IgnorePkg = ${pin_pkgs}" /etc/pacman.conf success "Pinned in /etc/pacman.conf [options]." fi success "ROCm 7.1.1 installed and pinned." } install_davinci_resolve() { # NVIDIA-mirrored install path: # 1. Find ZIP in ~/Downloads # 2. Disk-space check (~10 GB free) # 3. mktemp + trap to clean up on any exit # 4. unzip ZIP -> find .run -> --appimage-extract -> squashfs-root # 5. Replace bundled glib/gio/gmodule with system symlinks # (KEEP vendored libc++/libc++abi - removing breaks Resolve) # 6. Extract DaVinci panel-framework tarball if present # 7. rsync squashfs-root to /opt/resolve # 8. patchelf --set-rpath on every ELF in /opt/resolve # 9. libcrypt.so.1 fallback symlink # 10. Install desktop entries / icons / udev rules system-wide # # No AUR PKGBUILD, no qt5-webengine source build. Resolve's bundled Qt5 # is used via RPATH. Pacman does not track this install - to remove: # `sudo rm -rf /opt/resolve` and the desktop/icon/udev files. if [[ "$SCAN_RESOLVE_INSTALLED" == true ]]; then info "DaVinci Resolve already installed at /opt/resolve." info "Will apply workarounds and configuration updates." return 0 fi header "Installing DaVinci Resolve" # ---- Locate the ZIP installer ---- local downloads_dir="$HOME/Downloads" local resolve_zip="" while IFS= read -r -d '' file; do resolve_zip="$file" done < <(find "$downloads_dir" -maxdepth 1 -name "DaVinci_Resolve_*_Linux.zip" -type f -print0 2>/dev/null | sort -zV) if [[ -z "$resolve_zip" ]]; then echo "" warn "DaVinci Resolve ZIP not found in ~/Downloads" echo " 1. Go to: https://www.blackmagicdesign.com/products/davinciresolve" echo " 2. Click 'Download' -> 'DaVinci Resolve' (Free)" echo " 3. Select 'Linux' and save the ZIP to ~/Downloads" echo "" read -p "Open download page? [Y/n] " -n 1 -r echo if [[ ! $REPLY =~ ^[Nn]$ ]]; then xdg-open "https://www.blackmagicdesign.com/products/davinciresolve" 2>/dev/null || true fi return 1 fi success "Found installer: $resolve_zip" local resolve_version=$(basename "$resolve_zip" | grep -oP 'Resolve_\K[0-9]+\.[0-9]+(\.[0-9]+)?' || echo "") if [[ "$SCAN_IS_RDNA4" == true && -n "$resolve_version" ]]; then local major=$(echo "$resolve_version" | cut -d'.' -f1) if [[ "$major" -lt 20 ]]; then warn "DaVinci Resolve $resolve_version may not work with RDNA 4 (need 20.x+)" fi fi # ---- Disk space check (~10 GB needed for staged extraction) ---- local needed_gb=10 local free_kb=$(df --output=avail -k "$downloads_dir" | tail -n1) local free_gb=$((free_kb / 1024 / 1024)) if (( free_gb < needed_gb )); then error "Not enough free space in $downloads_dir: ${free_gb} GiB < ${needed_gb} GiB" fi # ---- Stage extraction in a temp dir under ~/Downloads ---- local workdir workdir=$(mktemp -d -p "$downloads_dir" .resolve-extract-XXXXXXXX) local original_dir="$PWD" cleanup_resolve_build() { cd "$original_dir" 2>/dev/null || cd ~ if [[ -n "${workdir:-}" && -d "$workdir" ]]; then info "Cleaning up extraction directory..." rm -rf "$workdir" 2>/dev/null || true fi } trap cleanup_resolve_build EXIT info "Unpacking ZIP to $workdir ..." unzip -q "$resolve_zip" -d "$workdir" local run_file run_file=$(find "$workdir" -maxdepth 2 -type f -name 'DaVinci_Resolve_*_Linux.run' | head -n1 || true) [[ -n "$run_file" ]] || error "Could not find the .run installer inside the ZIP" chmod +x "$run_file" local ex_dir=$(dirname "$run_file") info "Extracting AppImage payload..." if ! ( cd "$ex_dir" && "./$(basename "$run_file")" --appimage-extract >/dev/null ); then error "Failed to extract AppImage payload from $run_file" fi local appdir="$ex_dir/squashfs-root" [[ -d "$appdir" ]] || error "Extraction produced no squashfs-root directory" [[ -s "$appdir/bin/resolve" ]] || error "resolve binary missing or zero-size after extraction" chmod -R u+rwX,go+rX,go-w "$appdir" 2>/dev/null || warn "Could not normalize all permissions" # ---- ABI-safe bundled-library replacement ---- # Replace bundled glib family with system versions (stable C ABI - # safe to swap and avoids the well-known Resolve+Arch glib clash). # # All five glib-family libs must be swapped together: GObject's vtable # layout has to match the GLib it was built against, and GThread shares # internal state with GLib. Mixing a system libglib with bundled # libgobject means GObject calls into GLib symbols that have moved # between versions (Arch ships glib2 2.88+, bundled GObject is from # 2.68) and segfaults on the first signal emit / type registration. # Symptoms: random crashes in file pickers, GIO async ops, gstreamer # media import. # # KEEP vendored libc++/libc++abi - replacing them causes immediate crashes # because Resolve was compiled against specific libc++ ABI versions. info "Replacing bundled glib family (glib/gio/gmodule/gobject/gthread) with system versions..." pushd "$appdir" >/dev/null declare -A glib_libs=( ["/usr/lib/libglib-2.0.so.0"]="libs/libglib-2.0.so.0" ["/usr/lib/libgio-2.0.so.0"]="libs/libgio-2.0.so.0" ["/usr/lib/libgmodule-2.0.so.0"]="libs/libgmodule-2.0.so.0" ["/usr/lib/libgobject-2.0.so.0"]="libs/libgobject-2.0.so.0" ["/usr/lib/libgthread-2.0.so.0"]="libs/libgthread-2.0.so.0" ) for syslib in "${!glib_libs[@]}"; do local target="${glib_libs[$syslib]}" if [[ -e "$syslib" ]]; then rm -f "$target" || true ln -sf "$syslib" "$target" || warn "Failed to symlink $syslib -> $target" else warn "System library $syslib not found, keeping bundled version" fi done # Extract bundled DaVinci panel-framework (control surface support libs) if [[ -d "share/panels" ]]; then pushd "share/panels" >/dev/null tar -zxf dvpanel-framework-linux-x86_64.tgz 2>/dev/null || true mkdir -p "$appdir/libs" find . -maxdepth 1 -type f -name '*.so' -exec mv -f {} "$appdir/libs" \; 2>/dev/null || true if [[ -d lib ]]; then find lib -type f -name '*.so*' -exec mv -f {} "$appdir/libs" \; 2>/dev/null || true fi popd >/dev/null fi # AppImage launcher leftovers - we install to /opt/resolve, not run as AppImage rm -f AppRun AppRun* 2>/dev/null || true rm -rf installer installer* 2>/dev/null || true mkdir -p bin ln -sf "../BlackmagicRAWPlayer/BlackmagicRawAPI" "bin/" 2>/dev/null || true popd >/dev/null # ---- Install to /opt/resolve ---- info "Installing to /opt/resolve (rsync) ..." sudo rm -rf /opt/resolve sudo mkdir -p /opt/resolve sudo rsync -a --delete "$appdir/" /opt/resolve/ sudo mkdir -p /opt/resolve/.license # ---- RPATH patching ---- # Resolve's bundled binaries have RPATHs pointing to AppImage extraction # paths that no longer exist. Patch every ELF (executable + shared object) # to search /opt/resolve/libs/ and its plugin subdirectories. info "Patching RPATH on every ELF in /opt/resolve (may take a minute)..." local rpath_dirs=( "libs" "libs/plugins/sqldrivers" "libs/plugins/xcbglintegrations" "libs/plugins/imageformats" "libs/plugins/platforms" "libs/Fusion" "plugins" "bin" "BlackmagicRAWSpeedTest/BlackmagicRawAPI" "BlackmagicRAWSpeedTest/plugins/platforms" "BlackmagicRAWSpeedTest/plugins/imageformats" "BlackmagicRAWSpeedTest/plugins/mediaservice" "BlackmagicRAWSpeedTest/plugins/audio" "BlackmagicRAWSpeedTest/plugins/xcbglintegrations" "BlackmagicRAWSpeedTest/plugins/bearer" "BlackmagicRAWPlayer/BlackmagicRawAPI" "BlackmagicRAWPlayer/plugins/mediaservice" "BlackmagicRAWPlayer/plugins/imageformats" "BlackmagicRAWPlayer/plugins/audio" "BlackmagicRAWPlayer/plugins/platforms" "BlackmagicRAWPlayer/plugins/xcbglintegrations" "BlackmagicRAWPlayer/plugins/bearer" "Onboarding/plugins/xcbglintegrations" "Onboarding/plugins/qtwebengine" "Onboarding/plugins/platforms" "Onboarding/plugins/imageformats" "DaVinci Control Panels Setup/plugins/platforms" "DaVinci Control Panels Setup/plugins/imageformats" "DaVinci Control Panels Setup/plugins/bearer" "DaVinci Control Panels Setup/AdminUtility/PlugIns/DaVinciKeyboards" "DaVinci Control Panels Setup/AdminUtility/PlugIns/DaVinciPanels" ) local rpath_abs="" for p in "${rpath_dirs[@]}"; do rpath_abs+="/opt/resolve/${p}:"; done rpath_abs+="\$ORIGIN" local patch_count=0 patch_fail=0 patch_skip=0 while IFS= read -r -d '' f; do local file_info file_info=$(file -b "$f" 2>/dev/null) if [[ "$file_info" =~ ELF.*executable ]] || [[ "$file_info" =~ ELF.*shared\ object ]]; then local current_rpath current_rpath=$(patchelf --print-rpath "$f" 2>/dev/null || true) if [[ "$current_rpath" == "$rpath_abs" ]]; then patch_skip=$((patch_skip + 1)) continue fi if sudo patchelf --set-rpath "$rpath_abs" "$f" 2>/dev/null; then patch_count=$((patch_count + 1)) else patch_fail=$((patch_fail + 1)) local file_size=$(stat -c%s "$f" 2>/dev/null || echo 0) if (( file_size > 33554432 )); then warn "Failed to patch large file (>32MB): ${f##/opt/resolve/}" fi fi fi done < <(find /opt/resolve -type f -print0) success "Patched RPATH: $patch_count files ($patch_fail failures, $patch_skip already correct)" # ---- libcrypt.so.1 fallback symlink ---- # libxcrypt-compat already provides /usr/lib/libcrypt.so.1; symlink it # into /opt/resolve/libs as a belt-and-braces fallback. sudo ldconfig 2>/dev/null || true if [[ -e /usr/lib/libcrypt.so.1 ]]; then sudo ln -sf /usr/lib/libcrypt.so.1 /opt/resolve/libs/libcrypt.so.1 fi # ---- Desktop entries (system-wide) ---- info "Installing desktop entries, icons, and udev rules..." declare -A desktop_files=( ["/opt/resolve/share/DaVinciResolve.desktop"]="/usr/share/applications/DaVinciResolve.desktop" ["/opt/resolve/share/DaVinciControlPanelsSetup.desktop"]="/usr/share/applications/DaVinciControlPanelsSetup.desktop" ["/opt/resolve/share/blackmagicraw-player.desktop"]="/usr/share/applications/blackmagicraw-player.desktop" ["/opt/resolve/share/blackmagicraw-speedtest.desktop"]="/usr/share/applications/blackmagicraw-speedtest.desktop" ) for src in "${!desktop_files[@]}"; do local dest="${desktop_files[$src]}" if [[ -f "$src" ]]; then sudo install -D -m 0644 "$src" "$dest" # Critical: Resolve's bundled .desktop files have a literal # "RESOLVE_INSTALL_LOCATION" placeholder that the official # installer would substitute. Our manual copy doesn't, so the # app menu launcher would do nothing. Substitute now. sudo sed -i "s|RESOLVE_INSTALL_LOCATION|/opt/resolve|g" "$dest" else warn "Desktop file not found: $src" fi done declare -A icon_files=( ["/opt/resolve/graphics/DV_Resolve.png"]="/usr/share/icons/hicolor/128x128/apps/davinci-resolve.png" ["/opt/resolve/graphics/DV_Panels.png"]="/usr/share/icons/hicolor/128x128/apps/davinci-resolve-panels-setup.png" ["/opt/resolve/graphics/blackmagicraw-player_256x256_apps.png"]="/usr/share/icons/hicolor/256x256/apps/blackmagicraw-player.png" ["/opt/resolve/graphics/blackmagicraw-speedtest_256x256_apps.png"]="/usr/share/icons/hicolor/256x256/apps/blackmagicraw-speedtest.png" ) for src in "${!icon_files[@]}"; do local dest="${icon_files[$src]}" if [[ -f "$src" ]]; then sudo install -D -m 0644 "$src" "$dest" else warn "Icon file not found: $src" fi done sudo update-desktop-database >/dev/null 2>&1 || true sudo gtk-update-icon-cache -f /usr/share/icons/hicolor >/dev/null 2>&1 || true # ---- udev rules for Blackmagic capture cards / control surfaces ---- for r in 99-BlackmagicDevices.rules 99-ResolveKeyboardHID.rules 99-DavinciPanel.rules; do local src="/opt/resolve/share/etc/udev/rules.d/$r" if [[ -f "$src" ]]; then sudo install -D -m 0644 "$src" "/usr/lib/udev/rules.d/$r" fi done sudo udevadm control --reload-rules 2>/dev/null && sudo udevadm trigger 2>/dev/null || true # Cleanup extraction tree cleanup_resolve_build trap - EXIT # Refresh scan flag so downstream functions see the install SCAN_RESOLVE_INSTALLED=true SCAN_RESOLVE_PATH="/opt/resolve" success "DaVinci Resolve installed to /opt/resolve." } disable_opencl_decoders() { # Davincibox-style fix: disable BlackmagicRaw OpenCL decoders to prevent conflicts # These are the exact paths from davincibox setup-davinci script header "Disabling Problematic OpenCL Decoders" local decoder_paths=( # Exact paths from davincibox "/opt/resolve/BlackmagicRAWPlayer/BlackmagicRawAPI/libDecoderOpenCL.so" "/opt/resolve/BlackmagicRAWSpeedTest/BlackmagicRawAPI/libDecoderOpenCL.so" # Additional possible locations "/opt/resolve/libs/BlackmagicRawAPI/libDecoderOpenCL.so" "/opt/resolve/BlackmagicRawAPI/libDecoderOpenCL.so" ) local disabled=0 for decoder in "${decoder_paths[@]}"; do if [[ -f "$decoder" && ! -f "${decoder}.bak" ]]; then info "Disabling: $decoder" sudo mv "$decoder" "${decoder}.bak" disabled=$((disabled + 1)) elif [[ -f "${decoder}.bak" ]]; then info "Already disabled: $decoder" fi done if [[ $disabled -gt 0 ]]; then success "Disabled $disabled OpenCL decoder(s) to prevent conflicts." else info "No OpenCL decoders needed disabling." fi } setup_tls_symlink() { # Resolve's built-in extras downloader expects RHEL/CentOS cert path # /etc/pki/tls instead of Arch's /etc/ssl. Symlink so updates work. header "Configuring TLS Cert Path for Resolve" if [[ -e /etc/pki/tls ]]; then info "/etc/pki/tls already exists - skipping." return fi sudo mkdir -p /etc/pki sudo ln -sf /etc/ssl /etc/pki/tls success "Linked /etc/pki/tls -> /etc/ssl" } patch_audio_backend() { # Resolve ships with `Local.Audio.Type = DeckLink` in its system-wide # config template. On systems without a Blackmagic DeckLink card this # makes Resolve fail on first launch. Switch the default to ALSA. header "Patching Resolve Audio Backend (DeckLink -> ALSA)" local template="/opt/resolve/share/default-config.dat" if [[ -f "$template" ]] && grep -q '^Local\.Audio\.Type = DeckLink$' "$template"; then sudo sed -i 's|^Local\.Audio\.Type = DeckLink$|Local.Audio.Type = ALSA|' "$template" success "Patched system template: $template" elif [[ -f "$template" ]]; then info "System template already patched or non-default." else warn "System template not found - skipping." fi local user_cfg="${HOME}/.local/share/DaVinciResolve/configs/config.dat" if [[ -f "$user_cfg" ]] && grep -q '^Local\.Audio\.Type = DeckLink$' "$user_cfg"; then cp "$user_cfg" "$user_cfg.bak.$(date +%s)" sed -i 's|^Local\.Audio\.Type = DeckLink$|Local.Audio.Type = ALSA|' "$user_cfg" success "Patched user config (backup .bak. created)" fi } setup_snd_aloop() { # Resolve's audio engine opens raw ALSA hw: devices and never uses ALSA's # plugin layer (default/pulse/pipewire). When PipeWire owns every real # card, Resolve's enumerator loops looking for a free PCM and renders # never start. Loading snd-aloop gives Resolve a virtual card it can fully # own; PipeWire ignores it. Strace evidence: 14k+ SNDRV_CTL_IOCTL_PCM_INFO # ENXIO / 47k+ /dev/snd/controlCN ENOENT during a stuck render. # # Skip with RESOLVE_NO_ALOOP=1 (e.g. user has a dedicated audio interface). header "snd-aloop Render-Blocker Fix" if [[ "${RESOLVE_NO_ALOOP:-0}" == "1" ]]; then info "Skipping snd-aloop setup (RESOLVE_NO_ALOOP=1)." return fi # 1. Load module now if lsmod | grep -qE '^snd_aloop'; then info "snd-aloop already loaded." else if sudo modprobe snd-aloop 2>/dev/null; then success "snd-aloop loaded for the current session." else warn "modprobe snd-aloop failed. Verify with: modinfo snd-aloop" warn "On Arch this is part of linux/linux-zen/linux-lts kernel packages." fi fi # 2. Persist across reboots local aloop_conf="/etc/modules-load.d/snd-aloop.conf" if [[ ! -f "$aloop_conf" ]] || ! grep -qx 'snd-aloop' "$aloop_conf" 2>/dev/null; then echo 'snd-aloop' | sudo tee "$aloop_conf" >/dev/null success "Wrote $aloop_conf (autoloads at boot)." else info "$aloop_conf already configured." fi # 3. PipeWire loopback bridge - sends aloop capture to system default sink # so Resolve monitor audio is audible while editing. local bridge_dir="${HOME}/.config/pipewire/pipewire.conf.d" local bridge_file="${bridge_dir}/50-resolve-aloop-bridge.conf" mkdir -p "$bridge_dir" if [[ ! -f "$bridge_file" ]]; then cat > "$bridge_file" <<'EOF' # DaVinci Resolve aloop monitor bridge - managed by install-davinci-resolve.sh # Bridges snd-aloop's capture side to the system default sink so Resolve's # monitor audio is audible while editing. Without this, renders complete but # you hear nothing during playback. Remove this file + restart PipeWire to # disable. context.modules = [ { name = libpipewire-module-loopback args = { node.description = "DaVinci Resolve aloop monitor bridge" capture.props = { node.name = "resolve-aloop-capture" target.object = "alsa_input.platform-snd_aloop.0.analog-stereo" node.passive = true } playback.props = { node.name = "resolve-aloop-playback" media.class = "Stream/Output/Audio" } } } ] EOF success "Wrote $bridge_file (PipeWire loopback bridge)." else info "$bridge_file already in place." fi # 4. Wireplumber rule - keep aloop OUT of default-sink rotation. # Without this, wireplumber promotes aloop to default whenever Resolve # makes it RUNNING, and the bridge feeds aloop back into itself. local wp_dir="${HOME}/.config/wireplumber/wireplumber.conf.d" local wp_file="${wp_dir}/51-resolve-aloop-no-default.conf" mkdir -p "$wp_dir" if [[ ! -f "$wp_file" ]]; then cat > "$wp_file" <<'EOF' # DaVinci Resolve aloop default-sink exclusion - managed by install-davinci-resolve.sh # Without this, wireplumber promotes aloop to default whenever Resolve makes # it RUNNING and the loopback bridge sends audio back into aloop. Setting # both dont-fallback and disable-fallback covers minor key renames in the # wireplumber 0.5.x series. monitor.alsa.rules = [ { matches = [ { node.name = "alsa_output.platform-snd_aloop.0.analog-stereo" } { node.name = "alsa_input.platform-snd_aloop.0.analog-stereo" } ] actions = { update-props = { priority.session = 0 priority.driver = 0 node.dont-fallback = true node.disable-fallback = true } } } ] EOF success "Wrote $wp_file (wireplumber default-sink exclusion)." else info "$wp_file already in place." fi # 5. Restart user services so the configs are picked up. wireplumber # first, then pipewire/pipewire-pulse so the alsa rule re-applies # when pipewire republishes aloop nodes. if systemctl --user is-active --quiet pipewire 2>/dev/null; then systemctl --user restart wireplumber pipewire pipewire-pulse 2>/dev/null || true info "Reloaded user wireplumber + PipeWire services." fi } configure_user_groups() { # Davincibox requirement: user must be in 'render' and 'video' groups # Without these, DaVinci Resolve may hang during rendering or fail to detect GPU header "Configuring User Groups" local groups_to_add=() local current_groups=$(groups "$USER" 2>/dev/null) # Check render group if ! echo "$current_groups" | grep -qw "render"; then if getent group render &>/dev/null; then groups_to_add+=("render") else info "Group 'render' does not exist on this system (may not be needed)" fi else info "User already in 'render' group" fi # Check video group if ! echo "$current_groups" | grep -qw "video"; then if getent group video &>/dev/null; then groups_to_add+=("video") fi else info "User already in 'video' group" fi # Add user to missing groups if [[ ${#groups_to_add[@]} -gt 0 ]]; then info "Adding user to groups: ${groups_to_add[*]}" for grp in "${groups_to_add[@]}"; do sudo usermod -a -G "$grp" "$USER" done warn "You will need to log out and back in for group changes to take effect!" success "User added to required groups." else success "User already has required group memberships." fi } reset_stale_configs() { # Resolve defaults to GPU Processing Mode = CUDA on Linux. If a previous # launch couldn't find an OpenCL device (e.g. ROCm wasn't installed yet, # or an install was interrupted), Resolve writes a config snapshot and # segfaults with "Unsupported GPU Processing Mode". Subsequent launches # reuse that broken snapshot and crash the same way - even after the # OpenCL stack is fixed. The fix is to delete configs/ and logs/ so # Resolve goes through first-launch onboarding again. # # Project databases under "Resolve Disk Database/" and "Resolve Project # Library/" are NOT touched. Set RESOLVE_RESET_CONFIG=1 to force. header "Checking for stale Resolve configs" local resolve_user_dir="${HOME}/.local/share/DaVinciResolve" local prior_crash=0 # Match any of the known crash signatures. ROCm 7.2 produces several: # 'Unsupported GPU Processing Mode' is the original symptom (no OpenCL # device visible at all). 'OpenCL Context Manager failed to create # context' / 'Failed to create OpenCL context' fire when the device IS # visible but clCreateContext fails - the actual recurrence the v4.1 # script missed on fresh installs. Once any of these poisoned configs # land, every subsequent launch reuses them and crashes the same way. local crash_markers='Unsupported GPU Processing Mode|OpenCL Context Manager failed to create context|Failed to create OpenCL context' if [[ -f "${resolve_user_dir}/logs/ResolveDebug.txt" ]] && \ grep -qE "${crash_markers}" "${resolve_user_dir}/logs/ResolveDebug.txt" 2>/dev/null; then prior_crash=1 local hit hit=$(grep -oE "${crash_markers}" "${resolve_user_dir}/logs/ResolveDebug.txt" 2>/dev/null | head -1) warn "Detected prior crash marker: '${hit}'" fi # On a fresh Resolve install (SCAN_RESOLVE_INSTALLED=false), any configs # present can only have been written by a failed launch during this same # install run - there is no real user state to preserve. Wipe them # unconditionally so we never re-read a poisoned snapshot. local fresh_install=0 if [[ "${SCAN_RESOLVE_INSTALLED}" != "true" ]]; then fresh_install=1 fi if (( prior_crash )) || (( fresh_install )) || [[ "${RESOLVE_RESET_CONFIG:-0}" == "1" ]]; then if (( prior_crash )); then info "Resetting stale configs to recover from prior failed launch." elif (( fresh_install )); then info "Fresh Resolve install - clearing any configs from in-install launches." else info "RESOLVE_RESET_CONFIG=1 set - forcing config reset." fi info "Project databases preserved (Resolve Disk Database, Resolve Project Library)." rm -rf "${resolve_user_dir}/configs" "${resolve_user_dir}/logs" 2>/dev/null || true success "Stale configs cleared. Next launch will run first-launch onboarding." else info "No stale config markers found." fi } verify_opencl() { # Sanity-check that an AMD OpenCL platform is visible after install. # Resolve crashes on first launch with 'Unsupported GPU Processing Mode' # if it can't see ANY OpenCL device, so this is the most useful single # check we can do without launching Resolve. header "OpenCL Sanity Check" if ! command -v clinfo &>/dev/null; then warn "clinfo not installed - cannot verify OpenCL visibility." return fi local platforms platforms=$(clinfo -l 2>/dev/null || true) if echo "$platforms" | grep -qiE "AMD|gfx|Radeon"; then success "AMD OpenCL platform visible to clinfo:" echo "$platforms" | head -10 else warn "No AMD OpenCL platform detected. Resolve will crash with 'Unsupported GPU Processing Mode'." warn "Run: clinfo -l (and check rocminfo for ROCm device detection)" warn "Verify ICD vendor file: ls /etc/OpenCL/vendors/ (should include amdocl64.icd)" fi # Pinned-stack health check if [[ "$OPENCL_PROVIDER" == "rocm-pinned-7.1.1" ]]; then local stack_ok=1 for pkg in rocm-core rocm-device-libs rocm-llvm rocm-opencl-runtime comgr; do local ver ver=$(pacman -Q "$pkg" 2>/dev/null | awk '{print $2}') if [[ -z "$ver" ]]; then warn "Missing pinned package: $pkg" stack_ok=0 elif [[ ! "$ver" =~ 7\.1\.1 ]]; then warn "$pkg is at $ver but expected 7.1.1 - pin may have been overridden!" stack_ok=0 fi done local spirv_ver spirv_ver=$(pacman -Q spirv-llvm-translator 2>/dev/null | awk '{print $2}') if [[ -n "$spirv_ver" && ! "$spirv_ver" =~ 21\.1\.3 ]]; then warn "spirv-llvm-translator is at $spirv_ver but expected 21.1.3" stack_ok=0 fi if [[ "$stack_ok" == 1 ]]; then success "ROCm 7.1.1 pinned stack confirmed at correct versions." else warn "ROCm pinned stack drift detected - re-run install_rocm_pinned to repair." fi # IgnorePkg sanity if grep -qE "^IgnorePkg.*rocm-core" /etc/pacman.conf; then success "IgnorePkg pin is in place in /etc/pacman.conf [options]." else warn "IgnorePkg pin missing - next 'pacman -Syu' will break Resolve." warn "Re-run install_rocm_pinned to fix." fi fi } cleanup_stale_extracts() { # Previous failed runs may leave .resolve-extract-XXXXXX dirs (5-10 GB each) # in ~/Downloads. Always clean these at the start. local downloads_dir="$HOME/Downloads" local stale=() while IFS= read -r -d '' d; do stale+=("$d") done < <(find "$downloads_dir" -maxdepth 1 -type d -name '.resolve-extract-*' -print0 2>/dev/null) if [[ ${#stale[@]} -gt 0 ]]; then info "Cleaning ${#stale[@]} stale extraction dir(s) in ~/Downloads..." rm -rf "${stale[@]}" 2>/dev/null || true fi } create_launcher() { header "Creating Launcher" local launcher_dir="$HOME/.local/bin" mkdir -p "$launcher_dir" # GPU lock: whenever an AMD discrete GPU is present, pin OpenGL/Vulkan # to that exact PCI bus address. This is unconditional - not gated on # SCAN_IS_HYBRID, not gated on switcherooctl - because: # # - DRI_PRIME=1 is index-based and unsafe: it means "the OTHER card # relative to Mesa's default", which on systems where the monitor # is plugged into the AMD card is already AMD. =1 then FLIPS OpenGL # to the iGPU, OpenCL stays on AMD via ROCR_VISIBLE_DEVICES, and # CL/GL interop (clCreateContext with CL_GL_CONTEXT_KHR) fails - # Resolve hangs on the Color page. # - switcherooctl internally also uses DRI_PRIME=1, so it inherits # the same bug. Skipping it avoids a needless layer. # - On a single-GPU AMD box the pin is a no-op (correct card by # default) but harmless, so we always emit it for safety. # # The explicit pci-DDDD_BB_DD_F tag pins by bus address and always # lands on the real AMD card regardless of enumeration order, monitor # routing, or compositor GBM device selection. local gpu_launch_method="" local prime_config="" if [[ "$SCAN_HAS_AMD_DGPU" == true ]]; then local amd_pci_tag="" for i in "${!SCAN_GPU_VENDORS[@]}"; do if [[ "${SCAN_GPU_VENDORS[$i]}" == "AMD" && "${SCAN_GPU_TYPES[$i]}" == "discrete" ]]; then amd_pci_tag="pci-0000_${SCAN_GPU_PCI_IDS[$i]//[:.]/_}" break fi done if [[ -n "$amd_pci_tag" ]]; then gpu_launch_method="dri_prime" prime_config=" # Lock GPU to the discrete AMD card by PCI bus address. See create_launcher() # in install-davinci-resolve.sh for why this is unconditional and why # DRI_PRIME=1 / switcherooctl are deliberately not used. export DRI_PRIME=${amd_pci_tag} export MESA_VK_DEVICE_SELECT_FORCE_DEFAULT_DEVICE=1 export MESA_VK_DEVICE_SELECT=${amd_pci_tag}" else warn "AMD discrete GPU detected but PCI ID missing - falling back to no DRI_PRIME pin." fi elif [[ "$SCAN_IS_HYBRID" == true && "$SCAN_HAS_NVIDIA_DGPU" == true ]]; then # Hybrid + NVIDIA discrete (no AMD discrete): use NVIDIA offload gpu_launch_method="nvidia_prime" prime_config=' # Hybrid GPU: Force discrete NVIDIA GPU export __NV_PRIME_RENDER_OFFLOAD=1 export __GLX_VENDOR_LIBRARY_NAME=nvidia export __VK_LAYER_NV_optimus=NVIDIA_only' fi # ROCm environment configuration. # For rocm-pinned-7.1.1 (the working stack), ALWAYS export # HSA_OVERRIDE_GFX_VERSION (matched to the detected gfx target) and # ROCR_VISIBLE_DEVICES=0. These were empirically required to make # Resolve's clCreateContext succeed on RDNA4 + ROCm 7.1.1 even though # gfx1200 is "natively supported". local rocm_config="" if [[ "$OPENCL_PROVIDER" == "rocm-pinned-7.1.1" || "$OPENCL_PROVIDER" == "rocm-full" ]]; then # Choose HSA value: # - if scan detected a card needing spoof override, use that # - else if we know the gfx target, use the matching native value # - else leave unset (let ROCm autodetect) local hsa_value="${SCAN_HSA_OVERRIDE_VALUE}" if [[ -z "$hsa_value" && -n "$SCAN_GFX_TARGET" ]]; then case "$SCAN_GFX_TARGET" in gfx1201) hsa_value="12.0.1" ;; gfx1200) hsa_value="12.0.0" ;; gfx1101) hsa_value="11.0.1" ;; gfx1100) hsa_value="11.0.0" ;; gfx1030) hsa_value="10.3.0" ;; esac fi local hsa_section="" if [[ -n "$hsa_value" ]]; then hsa_section=" # Detected ${SCAN_GFX_TARGET}. Setting HSA_OVERRIDE_GFX_VERSION explicitly # (required for Resolve to succeed at clCreateContext on AMD even though # this target is natively supported by ROCm). export HSA_OVERRIDE_GFX_VERSION=${hsa_value} # Pin Resolve to the discrete AMD GPU (defensive) export ROCR_VISIBLE_DEVICES=0" else hsa_section=' # Could not auto-detect gfx target. Uncomment one of the lines below: # export HSA_OVERRIDE_GFX_VERSION=12.0.1 # RX 9070 / 9070 XT (Navi 48, gfx1201) # export HSA_OVERRIDE_GFX_VERSION=12.0.0 # RX 9060 / 9060 XT (Navi 44, gfx1200) # export HSA_OVERRIDE_GFX_VERSION=11.0.1 # RX 7700/7800 (Navi 32, gfx1101) # export HSA_OVERRIDE_GFX_VERSION=11.0.0 # RX 7900 (Navi 31, gfx1100) / RX 7600 (Navi 33, gfx1102) # export HSA_OVERRIDE_GFX_VERSION=10.3.0 # RX 6800/6900 / RX 6600/6700 (RDNA2) # export ROCR_VISIBLE_DEVICES=0' fi rocm_config=" # ROCm environment export ROCM_PATH=/opt/rocm export PATH=\"\$ROCM_PATH/bin:\$PATH\" export LD_LIBRARY_PATH=\"\$ROCM_PATH/lib:\$ROCM_PATH/lib64:\$LD_LIBRARY_PATH\" ${hsa_section} # ROCm OpenCL configuration export OCL_ICD_VENDORS=/etc/OpenCL/vendors" fi # Create main launcher. AMD-discrete pin (prime_config) is unconditional # when an AMD dGPU is present, so there is only one launcher template. cat > "$launcher_dir/davinci-resolve" << EOF #!/bin/bash # DaVinci Resolve Launcher with workarounds # Generated by install script on $(date) # OpenCL Provider: $OPENCL_PROVIDER # Fixes applied: patchelf (davincibox method) # Clear stale single-instance Qt lockfiles (left behind by crashes/SIGKILL) for lockfile in /tmp/qtsingleapp-DaVinci*lockfile; do [[ -f "\$lockfile" ]] && rm -f "\$lockfile" 2>/dev/null || true done # Qt configuration for XWayland export QT_QPA_PLATFORM=xcb export QT_AUTO_SCREEN_SCALE_FACTOR=1 ${prime_config} ${rocm_config} # Increase file descriptor limit for large projects ulimit -n 65535 2>/dev/null exec /opt/resolve/bin/resolve "\$@" EOF chmod +x "$launcher_dir/davinci-resolve" # Create Rusticl fallback launcher (davincibox -c flag equivalent) # Use this if ROCm OpenCL doesn't work with your GPU cat > "$launcher_dir/davinci-resolve-rusticl" << 'EOF' #!/bin/bash # DaVinci Resolve Launcher - Rusticl OpenCL Fallback # Use this if ROCm doesn't work with your GPU (davincibox -c equivalent) # Requires: opencl-rusticl-mesa package export QT_QPA_PLATFORM=xcb export QT_AUTO_SCREEN_SCALE_FACTOR=1 # Rusticl configuration (Mesa OpenCL implementation) export RUSTICL_ENABLE=radeonsi,iris,nouveau export OCL_ICD_VENDORS=rusticl.icd ulimit -n 65535 2>/dev/null exec /opt/resolve/bin/resolve "$@" EOF chmod +x "$launcher_dir/davinci-resolve-rusticl" info "Created Rusticl fallback launcher: davinci-resolve-rusticl" # Also create a version that forces integrated GPU (useful for testing/power saving) if [[ "$SCAN_IS_HYBRID" == true ]]; then cat > "$launcher_dir/davinci-resolve-igpu" << 'EOF' #!/bin/bash # DaVinci Resolve Launcher - Force Integrated GPU # Use this for power saving or if discrete GPU causes issues export QT_QPA_PLATFORM=xcb export QT_AUTO_SCREEN_SCALE_FACTOR=1 export DRI_PRIME=0 ulimit -n 65535 2>/dev/null exec /opt/resolve/bin/resolve "$@" EOF chmod +x "$launcher_dir/davinci-resolve-igpu" info "Created alternate launcher: davinci-resolve-igpu (uses integrated GPU)" fi # Update PATH in shell configs for rcfile in ~/.bashrc ~/.zshrc; do if [[ -f "$rcfile" ]] && ! grep -q 'HOME/.local/bin' "$rcfile"; then echo 'export PATH="$HOME/.local/bin:$PATH"' >> "$rcfile" fi done # Patch the Resolve-bundled system .desktop files to invoke our wrapper # instead of the bare /opt/resolve/bin/resolve. The bare binary skips Qt # lockfile cleanup, DRI_PRIME pinning, HSA_OVERRIDE, etc. This is the # difference between "app-menu launch silently does nothing" and # "Resolve actually starts". # # We deliberately DO NOT also create ~/.local/share/applications/davinci-resolve.desktop # - the basenames differ from the system entry (davinci-resolve.desktop vs # DaVinciResolve.desktop), so XDG would not shadow it and walker / app # menus would show TWO "DaVinci Resolve" entries. One patched system # entry is sufficient. local wrapper="$HOME/.local/bin/davinci-resolve" for sysdesktop in \ /usr/share/applications/DaVinciResolve.desktop \ /usr/share/applications/DaVinciResolveCaptureLogs.desktop; do if [[ -f "$sysdesktop" ]]; then sudo sed -i "s|^Exec=.*|Exec=$wrapper %U|" "$sysdesktop" fi done # Remove any user-level davinci-resolve.desktop left over from older # script runs (would duplicate the system entry in walker). rm -f "$HOME/.local/share/applications/davinci-resolve.desktop" 2>/dev/null || true sudo update-desktop-database >/dev/null 2>&1 || true update-desktop-database "$HOME/.local/share/applications" >/dev/null 2>&1 || true # System-wide convenience symlink so `davinci-resolve` works from any # shell even when ~/.local/bin isn't on PATH (e.g. cron, ssh). if [[ ! -e /usr/bin/davinci-resolve ]]; then echo -e "#!/usr/bin/env bash\nexec $wrapper \"\$@\"" | \ sudo tee /usr/bin/davinci-resolve >/dev/null sudo chmod +x /usr/bin/davinci-resolve info "Created /usr/bin/davinci-resolve symlink" fi success "Launcher created." echo "" info "Available launchers:" echo " davinci-resolve - Main launcher (default)" echo " davinci-resolve-rusticl - Rusticl OpenCL fallback (if ROCm fails)" if [[ "$SCAN_IS_HYBRID" == true ]]; then echo " davinci-resolve-igpu - Force integrated GPU" fi echo "" if [[ "$SCAN_HAS_AMD_DGPU" == true ]]; then info "Launcher locked to AMD discrete GPU via PCI tag (DRI_PRIME=${amd_pci_tag:-})" elif [[ "$SCAN_IS_HYBRID" == true && "$SCAN_HAS_NVIDIA_DGPU" == true ]]; then info "Hybrid GPU detected - launcher uses NVIDIA discrete via __NV_PRIME_RENDER_OFFLOAD" fi echo "" info "If Resolve fails to use GPU, try: davinci-resolve-rusticl" echo " (requires: sudo pacman -S opencl-rusticl-mesa)" } configure_hyprland() { if [[ "$SCAN_HYPRLAND_INSTALLED" != true ]]; then info "Hyprland not installed. Skipping configuration." return fi header "Configuring Hyprland" local hypr_conf="$HOME/.config/hypr/hyprland.conf" if [[ ! -f "$hypr_conf" ]]; then warn "Hyprland config not found at $hypr_conf" echo "Add these rules manually (Hyprland 0.53+ syntax):" echo "" cat << 'EOF' # Tag every floating Resolve window for easy targeting of the cosmetic # tweaks below. Do NOT add `stay_focused on` here - it locks focus inside # modal dialogs (Project Settings, Preferences, Render) so clicks on the # main window do nothing, which feels like a trapped cursor. windowrule = tag +drpopup, match:class ^(resolve)$, match:float 1 windowrule = border_size 0, match:tag drpopup windowrule = no_shadow on, match:tag drpopup windowrule = rounding 0, match:tag drpopup windowrule = opacity 1, match:tag drpopup EOF return fi if grep -q "drpopup.*resolve\|tag +drpopup" "$hypr_conf" 2>/dev/null; then info "Hyprland rules already configured." return fi cp "$hypr_conf" "$hypr_conf.backup.$(date +%Y%m%d%H%M%S)" # Hyprland 0.53+ syntax (windowrulev2 was removed). Verified against # Omarchy's own default windowrules + Hyprland 0.54.3 binary symbols. cat >> "$hypr_conf" << 'EOF' # DaVinci Resolve - Fix floating dialogs (Hyprland 0.53+ syntax) # Tag every floating Resolve window for easy targeting of the cosmetic # tweaks below. Do NOT add `stay_focused on` here - it locks focus inside # modal dialogs (Project Settings, Preferences, Render) so clicks on the # main window do nothing, which feels like a trapped cursor. windowrule = tag +drpopup, match:class ^(resolve)$, match:float 1 windowrule = border_size 0, match:tag drpopup windowrule = no_shadow on, match:tag drpopup windowrule = rounding 0, match:tag drpopup windowrule = opacity 1, match:tag drpopup EOF success "Hyprland rules added." info "Reload with: hyprctl reload && hyprctl configerrors" } create_conversion_script() { header "Creating Media Conversion Helper" cat > "$HOME/.local/bin/resolve-convert" << 'EOF' #!/bin/bash # Convert media to DNxHR for DaVinci Resolve Free (Linux) convert_file() { local input="$1" local output="${2:-${input%.*}_dnxhr.mov}" echo "Converting: $input -> $output" ffmpeg -i "$input" -c:v dnxhd -profile:v dnxhr_hq -pix_fmt yuv422p -c:a pcm_s16le -y "$output" echo "Done: $output" } [[ $# -eq 0 ]] && { echo "Usage: resolve-convert input.mp4 [output.mov]"; exit 1; } if [[ $# -eq 2 && ! "$2" =~ \.(mp4|mkv|avi|webm)$ ]]; then convert_file "$1" "$2" else for f in "$@"; do [[ -f "$f" ]] && convert_file "$f"; done fi EOF chmod +x "$HOME/.local/bin/resolve-convert" success "Created: resolve-convert" } install_diagnostics() { header "Installing Diagnostics" local checker_dir="$HOME/.local/share/davinci-resolve-checker" $SCAN_AUR_HELPER -S --needed --noconfirm python-pylspci 2>/dev/null || true if [[ -d "$checker_dir" ]]; then cd "$checker_dir" && git pull --quiet else git clone --quiet https://github.com/Ashark/davinci-resolve-checker.git "$checker_dir" fi ln -sf "$checker_dir/davinci-resolve-checker.py" "$HOME/.local/bin/davinci-resolve-checker" chmod +x "$checker_dir/davinci-resolve-checker.py" success "Installed: davinci-resolve-checker" } print_summary() { echo "" echo "============================================================" echo -e "${GREEN} Installation Complete!${NC}" echo "============================================================" echo "" echo " Launch: davinci-resolve" echo "" echo " OpenCL Provider: $OPENCL_PROVIDER" echo " Install method: NVIDIA-mirrored (manual extract + RPATH patch to /opt/resolve)" echo " Fixes applied: glib symlink replace, RPATH patching, OpenCL decoder disable," echo " audio backend (DeckLink->ALSA), snd-aloop, TLS cert symlink," echo " Qt lockfile cleanup, libcrypt.so.1 fallback" if [[ "$OPENCL_PROVIDER" == "rocm-pinned-7.1.1" ]]; then echo " + ROCm 7.1.1 pinned from Arch Linux Archive" echo " + IgnorePkg in /etc/pacman.conf [options]" echo " + HSA_OVERRIDE_GFX_VERSION + ROCR_VISIBLE_DEVICES baked into launcher" fi echo "" echo " Env vars (set before re-running this script):" echo " RESOLVE_NO_ALOOP=1 Skip snd-aloop setup (you have a real audio interface)" echo " RESOLVE_RESET_CONFIG=1 Force-wipe ~/.local/share/DaVinciResolve/{configs,logs}" echo " (use after fixing OpenCL stack to clear crash snapshots)" echo "" if [[ "$OPENCL_PROVIDER" == "rocm-pinned-7.1.1" ]]; then echo " IMPORTANT - ROCm 7.1.1 is PINNED:" echo " ROCm 7.2.x breaks DaVinci Resolve (https://github.com/ROCm/ROCm/issues/5982)" echo " These packages will NOT update on 'pacman -Syu':" echo " rocm-core rocm-device-libs rocm-llvm rocm-opencl-runtime" echo " comgr spirv-llvm-translator" echo " When ROCm 7.3+ ships with the fix, lift the pin:" echo " sudo sed -i '/^IgnorePkg.*rocm-/d' /etc/pacman.conf" echo " sudo pacman -Syu" echo "" fi echo " ROCm/diagnostic tools:" echo " rocminfo # Show ROCm GPU info (Name: + Marketing Name)" echo " rocm-smi # ROCm System Management Interface" echo " clinfo -l # Check OpenCL platforms" echo " davinci-resolve-checker # Diagnose Resolve issues" echo "" echo " Free version codec limitation (Linux):" echo " No H.264/H.265/MP4 - convert with: resolve-convert video.mp4" echo "" echo " To upgrade Resolve: download new ZIP to ~/Downloads, re-run this script." echo " To uninstall:" echo " sudo rm -rf /opt/resolve" echo " sudo rm -f /usr/share/applications/{DaVinciResolve,DaVinciControlPanelsSetup,blackmagicraw-*}.desktop" echo " sudo rm -f /usr/lib/udev/rules.d/{99-BlackmagicDevices,99-ResolveKeyboardHID,99-DavinciPanel}.rules" echo " sudo sed -i '/^IgnorePkg.*rocm-/d' /etc/pacman.conf # lift the pin" echo "" if [[ -n "$SCAN_GFX_TARGET" ]]; then echo " GPU Detection:" echo " Detected $SCAN_GFX_TARGET. Launcher pre-configured with:" echo " HSA_OVERRIDE_GFX_VERSION (matched to your gfx target)" echo " ROCR_VISIBLE_DEVICES=0" echo " These were empirically required to make Resolve clCreateContext" echo " succeed on AMD even though gfx1100+ targets are 'natively supported'." echo "" echo " Verify GPU detection: rocminfo | grep -E 'Name:|gfx'" echo "" fi echo "============================================================" } # ============================================================================= # MAIN # ============================================================================= main() { echo "" echo "============================================================" echo " DaVinci Resolve Installer v4.1" echo " Arch Linux + AMD (RDNA2/3/4) + Hyprland" echo " NVIDIA-mirrored install path (extract + RPATH to /opt/resolve)" echo " ROCm 7.1.1 pinned (working stack for Resolve on AMD, May 2026)" echo "============================================================" echo "" # Run system scan run_system_scan display_scan_results # Analyze and check for blockers if ! analyze_scan_results; then echo "" read -p "Continue despite issues? [y/N] " -n 1 -r echo [[ ! $REPLY =~ ^[Yy]$ ]] && exit 1 fi # Check AUR helper if [[ -z "$SCAN_AUR_HELPER" ]]; then error "No AUR helper found. Install yay or paru first." fi # Build package list build_package_list echo "" read -p "Proceed with installation? [Y/n] " -n 1 -r echo [[ $REPLY =~ ^[Nn]$ ]] && exit 0 # Full system upgrade first - Arch is a rolling distro and partial upgrades # (pacman -Sy without -u) are unsupported and can break the system. # This also ensures we get the latest ROCm. header "System Update" info "Arch is a rolling-release distro - a full system upgrade is recommended" info "before installing new packages, to avoid partial-upgrade breakage and" info "to pull the latest ROCm release." echo "" read -p "Run 'sudo pacman -Syu' now? [Y/n] " -n 1 -r echo if [[ ! $REPLY =~ ^[Nn]$ ]]; then sudo pacman -Syu --noconfirm # Refresh the cached package list since the upgrade may have changed it scan_installed_packages success "System updated." else warn "Skipping system upgrade - proceed at your own risk." warn "If pacman complains about stale databases, re-run after 'sudo pacman -Syu'." fi # Configure user groups for GPU access configure_user_groups # Clean up any leftover extraction dirs from prior failed runs cleanup_stale_extracts # Install install_pacman_packages install_aur_packages install_rocm_pinned # downloads ROCm 7.1.1 from ALA + pins it install_davinci_resolve disable_opencl_decoders setup_tls_symlink patch_audio_backend setup_snd_aloop create_launcher configure_hyprland create_conversion_script install_diagnostics # Reset stale Resolve user configs if a prior crash marker exists reset_stale_configs # Final sanity check verify_opencl print_summary } main "$@"