diff --git a/dotfiles/lib/bin/set-hyprpaper-wallpaper b/dotfiles/lib/bin/set-hyprpaper-wallpaper new file mode 100755 index 00000000..ae93c6b4 --- /dev/null +++ b/dotfiles/lib/bin/set-hyprpaper-wallpaper @@ -0,0 +1,60 @@ +#!/usr/bin/env bash + +set -u + +wallpaper_dir="${WALLPAPER_DIR:-/var/lib/syncthing/sync/Wallpaper/use}" +hyprctl_bin="${HYPRCTL:-hyprctl}" +runtime_dir="${XDG_RUNTIME_DIR:-}" +sig="${HYPRLAND_INSTANCE_SIGNATURE:-}" +sock_path="" + +if [ -n "$runtime_dir" ] && [ -n "$sig" ]; then + sock_path="$runtime_dir/hypr/$sig/.hyprpaper.sock" +fi + +# Don't fail the service if Hyprland has not exported its instance yet. +if [ -z "$sock_path" ]; then + echo "set-hyprpaper-wallpaper: missing XDG_RUNTIME_DIR/HYPRLAND_INSTANCE_SIGNATURE" >&2 + exit 0 +fi + +for _ in {1..50}; do + if [ -S "$sock_path" ]; then + break + fi + sleep 0.1 +done + +if [ ! -S "$sock_path" ]; then + echo "set-hyprpaper-wallpaper: hyprpaper socket not ready at $sock_path" >&2 + exit 0 +fi + +if [ ! -d "$wallpaper_dir" ]; then + echo "set-hyprpaper-wallpaper: wallpaper directory missing: $wallpaper_dir" >&2 + exit 0 +fi + +wallpaper="$(find -L "$wallpaper_dir" -type f -regextype posix-extended -iregex '.*\.(png|jpe?g|webp)$' -print0 | shuf -z -n 1 | tr -d '\0')" + +if [ -z "$wallpaper" ]; then + echo "set-hyprpaper-wallpaper: no wallpapers found in $wallpaper_dir" >&2 + exit 0 +fi + +mons="$("$hyprctl_bin" -j monitors 2>/dev/null | jq -r '.[].name' 2>/dev/null || true)" +if [ -z "$mons" ]; then + mons="$("$hyprctl_bin" monitors 2>/dev/null | grep -oE '^Monitor [^ ]+' | cut -d' ' -f2 || true)" +fi + +for mon in $mons; do + # The socket file can exist before hyprpaper is actually ready to accept IPC. + for _ in {1..50}; do + if "$hyprctl_bin" hyprpaper wallpaper "$mon,$wallpaper" >/dev/null 2>&1; then + break + fi + sleep 0.1 + done +done + +exit 0 diff --git a/dotfiles/lib/bin/start-hyprpaper b/dotfiles/lib/bin/start-hyprpaper new file mode 100755 index 00000000..15825cc0 --- /dev/null +++ b/dotfiles/lib/bin/start-hyprpaper @@ -0,0 +1,40 @@ +#!/usr/bin/env bash + +set -euo pipefail + +config="${HYPRPAPER_CONFIG:-${XDG_CONFIG_HOME:-$HOME/.config}/hypr/hyprpaper.conf}" + +for _ in {1..50}; do + if [ -n "${WAYLAND_DISPLAY:-}" ] && [ -n "${XDG_RUNTIME_DIR:-}" ] && [ -S "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" ]; then + break + fi + sleep 0.1 +done + +if [ -z "${WAYLAND_DISPLAY:-}" ] || [ -z "${XDG_RUNTIME_DIR:-}" ] || [ ! -S "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" ]; then + echo "start-hyprpaper: Wayland socket not ready: WAYLAND_DISPLAY=${WAYLAND_DISPLAY:-} XDG_RUNTIME_DIR=${XDG_RUNTIME_DIR:-}" >&2 + exit 1 +fi + +nvidia_version="$(sed -n 's/^NVRM version:.* \([0-9][0-9.]*\) .*/\1/p' /proc/driver/nvidia/version 2>/dev/null | head -n 1 || true)" +current_vendor="/run/opengl-driver/share/glvnd/egl_vendor.d/10_nvidia.json" + +if [ -n "$nvidia_version" ] && [ -r "$current_vendor" ] && ! grep -q "$nvidia_version" "$current_vendor"; then + matching_vendor="" + for candidate in /nix/store/*-nvidia-x11-"$nvidia_version"*/share/glvnd/egl_vendor.d/10_nvidia.json; do + if [ -r "$candidate" ]; then + matching_vendor="$candidate" + break + fi + done + + if [ -n "$matching_vendor" ]; then + echo "start-hyprpaper: using NVIDIA EGL vendor file for loaded driver $nvidia_version: $matching_vendor" >&2 + export __EGL_VENDOR_LIBRARY_FILENAMES="$matching_vendor" + else + echo "start-hyprpaper: NVIDIA driver mismatch, but no EGL vendor file for loaded driver $nvidia_version was found" >&2 + exit 75 + fi +fi + +exec hyprpaper -c "$config" diff --git a/nixos/imalison.nix b/nixos/imalison.nix index 0d5544b0..e5e62952 100644 --- a/nixos/imalison.nix +++ b/nixos/imalison.nix @@ -53,92 +53,18 @@ in { }; systemd.user.services.hyprpaper = let - wallpaperDir = "/var/lib/syncthing/sync/Wallpaper/use"; - waitForWayland = pkgs.writeShellScript "wait-for-wayland" '' - # Hyprpaper needs a Wayland socket. systemd "Condition*" checks are - # brittle here (they skip the unit entirely, with no retry) and also - # don't support expanding $WAYLAND_DISPLAY in paths. - for _ in {1..50}; do - if [ -n "$WAYLAND_DISPLAY" ] && [ -n "$XDG_RUNTIME_DIR" ] && [ -S "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" ]; then - exit 0 - fi - ${pkgs.coreutils}/bin/sleep 0.1 - done - - echo "Wayland socket not ready: WAYLAND_DISPLAY=$WAYLAND_DISPLAY XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR" >&2 - exit 1 - ''; - setWallpaper = pkgs.writeShellScript "set-hyprpaper-wallpaper" '' - # hyprpaper 0.8.x doesn't seem to apply `wallpaper = ...` entries from - # its config on startup reliably. Explicitly set the wallpaper via IPC - # once the hyprpaper socket is ready. - # - # NOTE: hyprctl hyprpaper currently ignores `--instance` and uses - # $HYPRLAND_INSTANCE_SIGNATURE to find hyprpaper's socket, so we rely on - # the environment imported by Hyprland on login. - set -u - - runtimeDir="''${XDG_RUNTIME_DIR:-}" - sig="''${HYPRLAND_INSTANCE_SIGNATURE:-}" - sockPath="" - if [ -n "$runtimeDir" ] && [ -n "$sig" ]; then - sockPath="$runtimeDir/hypr/$sig/.hyprpaper.sock" - fi - - # Don't fail the service if env isn't available yet. - if [ -z "$sockPath" ]; then - echo "set-hyprpaper-wallpaper: missing XDG_RUNTIME_DIR/HYPRLAND_INSTANCE_SIGNATURE" >&2 - exit 0 - fi - - for _ in {1..50}; do - if [ -S "$sockPath" ]; then - break - fi - ${pkgs.coreutils}/bin/sleep 0.1 - done - - if [ ! -S "$sockPath" ]; then - echo "set-hyprpaper-wallpaper: hyprpaper socket not ready at $sockPath" >&2 - exit 0 - fi - - if [ ! -d "${wallpaperDir}" ]; then - echo "set-hyprpaper-wallpaper: wallpaper directory missing: ${wallpaperDir}" >&2 - exit 0 - fi - - wallpaper="$(${pkgs.findutils}/bin/find "${wallpaperDir}" -type f -regextype posix-extended -iregex '.*\.(png|jpe?g|webp)$' -print0 | ${pkgs.coreutils}/bin/shuf -z -n 1 | ${pkgs.coreutils}/bin/tr -d '\0')" - - if [ -z "$wallpaper" ]; then - echo "set-hyprpaper-wallpaper: no wallpapers found in ${wallpaperDir}" >&2 - exit 0 - fi - - # Apply to all current monitors. - mons=$(/run/current-system/sw/bin/hyprctl -j monitors 2>/dev/null | ${pkgs.jq}/bin/jq -r '.[].name' 2>/dev/null || true) - if [ -z "$mons" ]; then - # Fallback to parsing non-JSON output. - mons=$(/run/current-system/sw/bin/hyprctl monitors 2>/dev/null | ${pkgs.gnugrep}/bin/grep -oE '^Monitor [^ ]+' | ${pkgs.coreutils}/bin/cut -d' ' -f2 || true) - fi - - for mon in $mons; do - # The socket file can exist before hyprpaper is actually ready to - # accept connections, so retry briefly. - for _ in {1..50}; do - if /run/current-system/sw/bin/hyprctl hyprpaper wallpaper "$mon,$wallpaper" >/dev/null 2>&1; then - break - fi - ${pkgs.coreutils}/bin/sleep 0.1 - done - done - - exit 0 - ''; hyprpaperConf = pkgs.writeText "hyprpaper.conf" '' ipc = true splash = false ''; + hyprpaperPath = pkgs.lib.makeBinPath [ + pkgs.coreutils + pkgs.findutils + pkgs.gnugrep + pkgs.gnused + pkgs.hyprpaper + pkgs.jq + ]; in { Unit = { Description = "Hyprpaper (managed by home-manager)"; @@ -148,10 +74,16 @@ in { }; Service = { - ExecStartPre = waitForWayland; - ExecStart = "${pkgs.hyprpaper}/bin/hyprpaper -c ${hyprpaperConf}"; - ExecStartPost = setWallpaper; + Environment = [ + "HYPRPAPER_CONFIG=${hyprpaperConf}" + "HYPRCTL=/run/current-system/sw/bin/hyprctl" + "PATH=${hyprpaperPath}:/run/current-system/sw/bin" + "WALLPAPER_DIR=/var/lib/syncthing/sync/Wallpaper/use" + ]; + ExecStart = "${pkgs.bash}/bin/bash ${../dotfiles/lib/bin/start-hyprpaper}"; + ExecStartPost = "${pkgs.bash}/bin/bash ${../dotfiles/lib/bin/set-hyprpaper-wallpaper}"; Restart = "on-failure"; + RestartPreventExitStatus = 75; RestartSec = 1; };