#!/usr/bin/env bash set -euo pipefail # Global (not per-monitor) wallpaper per workspace 1..9 for Hyprland + wpaperd (wpaperctl v1.2.1) # # Expected images: # ~/nixos_conf/wallpaperstuff/pictures/wallpaper{1..9}.jpg # # What it does: # - Maintains a single symlink: # ~/nixos_conf/wallpaperstuff/current/workspace.jpg # - Writes/ensures wpaperd config: # ~/.config/wpaperd/config.toml # with: # [default] mode = "stretch" # [any] path = "" # - On workspace change, updates symlink and runs: # wpaperctl reload-wallpaper # # Notes: # - This is intentionally NOT per-monitor. If you have 2 monitors on different workspaces at once, # the wallpaper will follow the *currently active/focused workspace*. log() { echo "[wpaperd-ws] $*" >&2; } PICTURES_DIR="${HOME}/nixos_conf/wallpaperstuff/pictures" STATE_DIR="${HOME}/nixos_conf/wallpaperstuff/current" SYMLINK="${STATE_DIR}/workspace.jpg" XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}" WPAPERD_DIR="${XDG_CONFIG_HOME}/wpaperd" CONF="${WPAPERD_DIR}/config.toml" : "${XDG_RUNTIME_DIR:?XDG_RUNTIME_DIR not set}" : "${HYPRLAND_INSTANCE_SIGNATURE:?HYPRLAND_INSTANCE_SIGNATURE not set}" SOCK="${XDG_RUNTIME_DIR}/hypr/${HYPRLAND_INSTANCE_SIGNATURE}/.socket2.sock" mkdir -p "$STATE_DIR" "$WPAPERD_DIR" # Wait for Hyprland socket so we don't exit too early for i in $(seq 1 120); do [[ -S "$SOCK" ]] && break sleep 0.1 done [[ -S "$SOCK" ]] || { log "Hyprland socket not found: $SOCK"; exit 1; } ws_num() { # workspace name might be "8" or "8:foo" local w="${1%%:*}" [[ "$w" =~ ^[0-9]+$ ]] || return 1 printf '%s\n' "$w" } img_for_ws() { local n="$1" case "$n" in 1|2|3|4|5|6|7|8|9) printf '%s/wallpaper%s.jpg\n' "$PICTURES_DIR" "$n" ;; *) return 1 ;; esac } ensure_config() { # Keep it simple and stable: always ensure config points [any] to the symlink. # wpaperd reads this path by default. cat >"$CONF" </dev/null 2>&1 || true } apply_workspace() { local ws_raw="$1" local n img n="$(ws_num "$ws_raw")" || { log "Ignoring non-numeric workspace: $ws_raw"; return 0; } img="$(img_for_ws "$n")" || return 0 if [[ ! -f "$img" ]]; then log "Missing image for ws=$n: $img" return 0 fi # If already set, skip work if [[ -L "$SYMLINK" ]] && [[ "$(readlink -f "$SYMLINK")" == "$(readlink -f "$img")" ]]; then return 0 fi ln -sf "$img" "$SYMLINK" log "Workspace $n -> $img" ensure_config reload_wallpaper } apply_initial() { # Use focused monitor's active workspace as "current workspace" for the global wallpaper. # This keeps behavior consistent with "per workspace (focused)". local ws ws="$(hyprctl -j monitors | jq -r '.[] | select(.focused==true) | .activeWorkspace.name' | head -n1)" if [[ -n "$ws" && "$ws" != "null" ]]; then apply_workspace "$ws" else log "Could not determine initial focused workspace" fi } handle_line() { case "$1" in # workspace>>NAME workspace\>\>*) apply_workspace "${1#workspace>>}" ;; # workspacev2>>ID,NAME workspacev2\>\>*) local payload="${1#workspacev2>>}" apply_workspace "${payload#*,}" ;; # focusedmon>>MON,WORKSPACE (also catches some focus-driven changes) focusedmon\>\>*) local payload="${1#focusedmon>>}" local ws="${payload#*,}" apply_workspace "$ws" ;; esac } ensure_config apply_initial log "Listening on $SOCK" exec socat -U - "UNIX-CONNECT:$SOCK" \ | while IFS= read -r line; do handle_line "$line" || true done