diff --git a/hypr/bin/hyprlock-background.sh b/hypr/bin/hyprlock-background.sh index e2abd3c..b56fdd6 100755 --- a/hypr/bin/hyprlock-background.sh +++ b/hypr/bin/hyprlock-background.sh @@ -1,5 +1,5 @@ #!/bin/sh -background=$(ls ~/Pictures/wallpaper/*.png | shuf -n 1) +background=$(ls ~/Pictures/wallpaper/* | shuf -n 1) sed -i "s|.*Pictures.*|\tpath = $background|" ~/dotfiles/hypr/hyprlock.conf hyprlock diff --git a/hypr/bin/hyprlock-music.sh b/hypr/bin/hyprlock-music.sh new file mode 100755 index 0000000..1a30d62 --- /dev/null +++ b/hypr/bin/hyprlock-music.sh @@ -0,0 +1,421 @@ +#!/usr/bin/env bash +# hyprlock-music.sh - Music player integration for Hyprlock +# Displays current track info, album art, and playback controls +# Dependencies: playerctl (required), curl (for remote art), ImageMagick (for cropping) + +set -Eeuo pipefail + +# ============================================================================ +# Configuration +# ============================================================================ + +# Player priority order (first available will be used) +PREFERRED_PLAYERS="spotify,mpv,vlc,firefox,chromium,brave,chrome" + +# Album art cache settings +CACHE_DIR="${XDG_CACHE_HOME:-$HOME/.cache}/hyprlock-art" +SQUARE_SIZE=1024 +mkdir -p "$CACHE_DIR" + +# Progress bar appearance +BAR_LENGTH=16 +BAR_CHAR="━" +BAR_HANDLE="⦿" +COLOR_PLAYED="ffffff99" +COLOR_REMAINING="ffffff30" + +# ============================================================================ +# Helper Functions +# ============================================================================ + +# Check if command exists +have() { command -v "$1" >/dev/null 2>&1; } + +# Select the best available player +select_player() { + if have playerctl && playerctl -p "$PREFERRED_PLAYERS" status >/dev/null 2>&1; then + echo "$PREFERRED_PLAYERS" + else + echo "" + fi +} + +# Get metadata from active player +get_metadata() { + local key="$1" + local fmt="{{ $key }}" + local player + player="$(select_player)" + + if [[ -n "$player" ]]; then + playerctl -p "$player" metadata --format "$fmt" 2>/dev/null || true + else + playerctl metadata --format "$fmt" 2>/dev/null || true + fi +} + +# Get player status (Playing/Paused/Stopped) +get_status() { + local player + player="$(select_player)" + + if [[ -n "$player" ]]; then + playerctl -p "$player" status 2>/dev/null || true + else + playerctl status 2>/dev/null || true + fi +} + +# Truncate string with ellipsis if too long +trim_string() { + local str="${1:-}" + local max_len="${2:-30}" + local str_len=${#str} + + if ((str_len <= max_len)); then + printf '%s' "$str" + else + printf '%s…' "${str:0:max_len}" + fi +} + +# ============================================================================ +# Time Conversion Functions +# ============================================================================ + +# Convert microseconds to mm:ss format +microseconds_to_mmss() { + local us="$1" + + [[ "$us" =~ ^[0-9]+$ ]] || { + printf "0:00" + return + } + + local seconds=$((us / 1000000)) + printf '%d:%02d' $((seconds / 60)) $((seconds % 60)) +} + +# Get track length in mm:ss +get_track_length() { + local us + us="$(get_metadata 'mpris:length')" + + if [[ -z "$us" || "$us" == "0" ]]; then + printf '0:00' + else + microseconds_to_mmss "$us" + fi +} + +# Get current position in mm:ss +get_current_position() { + local us + local player + player="$(select_player)" + + if [[ -n "$player" ]]; then + us="$(playerctl -p "$player" position 2>/dev/null | awk '{print int($1 * 1000000)}')" || true + else + us="$(playerctl position 2>/dev/null | awk '{print int($1 * 1000000)}')" || true + fi + + if [[ -z "$us" || "$us" == "0" ]]; then + printf '0:00' + else + microseconds_to_mmss "$us" + fi +} + +# ============================================================================ +# Progress Functions +# ============================================================================ + +# Calculate progress percentage (0-100) +calculate_progress_percent() { + local pos_us length_us + local player + player="$(select_player)" + + if [[ -n "$player" ]]; then + pos_us="$(playerctl -p "$player" position 2>/dev/null | awk '{print int($1 * 1000000)}')" || true + length_us="$(playerctl -p "$player" metadata mpris:length 2>/dev/null)" || true + else + pos_us="$(playerctl position 2>/dev/null | awk '{print int($1 * 1000000)}')" || true + length_us="$(playerctl metadata mpris:length 2>/dev/null)" || true + fi + + if [[ -n "$pos_us" && -n "$length_us" && "$length_us" -gt 0 ]]; then + local percent=$((pos_us * 100 / length_us)) + printf '%d' "$percent" + else + printf '0' + fi +} + +# Generate progress bar with Pango markup +generate_progress_bar() { + local percent + percent="$(calculate_progress_percent)" + + local current_status + current_status="$(get_status)" + + # Return empty bar if nothing is playing + if [[ -z "$current_status" || "$current_status" == "Stopped" ]]; then + local empty_bar="" + for ((i = 0; i < BAR_LENGTH; i++)); do + empty_bar+="$BAR_CHAR" + done + printf '%s' "$COLOR_REMAINING" "$empty_bar" + return + fi + + # Treat 95%+ as 100% to handle players switching tracks early + [[ $percent -ge 95 ]] && percent=100 + + # Calculate filled segments + local progress=$((percent * BAR_LENGTH / 100)) + [[ $progress -gt $BAR_LENGTH ]] && progress=$BAR_LENGTH + [[ $progress -lt 0 ]] && progress=0 + + # Build bar segments + local bar_played="" + local bar_remaining="" + + for ((i = 0; i < progress; i++)); do + bar_played+="$BAR_CHAR" + done + + for ((i = progress; i < BAR_LENGTH; i++)); do + bar_remaining+="$BAR_CHAR" + done + + # Generate Pango markup based on position + if [[ $progress -eq $BAR_LENGTH ]]; then + # Track complete - handle at end + printf '%s%s' \ + "$COLOR_PLAYED" "$bar_played" "$BAR_HANDLE" + elif [[ $progress -eq 0 ]]; then + # Track just started - handle at beginning + printf '%s%s' \ + "$BAR_HANDLE" "$COLOR_REMAINING" "${bar_remaining}" + else + # In progress - played + handle + remaining + printf '%s%s%s' \ + "$COLOR_PLAYED" "$bar_played" "$BAR_HANDLE" "$COLOR_REMAINING" "$bar_remaining" + fi +} + +# ============================================================================ +# Album Art Functions +# ============================================================================ + +# Download remote URL to cache +download_to_cache() { + local url="$1" + local filename + filename="$(printf '%s' "$url" | sha256sum | awk '{print $1}').img" + local output="$CACHE_DIR/$filename" + + if [[ ! -s "$output" ]]; then + have curl && curl -fsSL --max-time 5 "$url" -o "$output" || true + fi + + printf '%s' "$output" +} + +# Create square-cropped album art +create_square_cover() { + local input="$1" + local basename + basename="$(basename "$input")" + local output="$CACHE_DIR/${basename%.*}_sq_${SQUARE_SIZE}.jpg" + + # Return cached version if it exists and is newer + if [[ -s "$output" && "$output" -nt "$input" ]]; then + printf '%s' "$output" + return + fi + + # Create square crop with ImageMagick + if have convert; then + convert "$input" -auto-orient -gravity center \ + -thumbnail "${SQUARE_SIZE}x${SQUARE_SIZE}^" \ + -extent "${SQUARE_SIZE}x${SQUARE_SIZE}" \ + -quality 90 "$output" && printf '%s' "$output" && return + fi + + # Fallback to original if ImageMagick unavailable + printf '%s' "$input" +} + +# Get path to square album art +get_album_art_path() { + local url + url="$(get_metadata 'mpris:artUrl')" + + [[ -n "$url" ]] || { + printf '' + return + } + + local local_path="" + + case "$url" in + file://*) + local_path="${url#file://}" + ;; + http://* | https://*) + local_path="$(download_to_cache "$url")" + ;; + *) + printf '' + return + ;; + esac + + [[ -n "$local_path" && -s "$local_path" ]] || { + printf '' + return + } + + create_square_cover "$local_path" +} + +# ============================================================================ +# Display Functions +# ============================================================================ + +# Get player status icon +get_status_icon() { + case "$(get_status | tr '[:upper:]' '[:lower:]')" in + playing) + printf '󰏤' + ;; + paused) + printf '󰐊' + ;; + stopped | *) + printf '󰓛' + ;; + esac +} + +# Get active player name +get_active_player() { + local active_player="" + local player + player="$(select_player)" + + if [[ -n "$player" ]]; then + active_player="$(playerctl -p "$player" -l 2>/dev/null | head -n1)" + else + active_player="$(playerctl -l 2>/dev/null | head -n1)" + fi + + printf '%s' "$active_player" +} + +# Get formatted player name with icon +get_player_display() { + local player + player="$(get_active_player)" + + case "${player,,}" in + spotify*) + printf '󰓇 Spotify' + ;; + firefox*) + printf '󰈹 Firefox' + ;; + chromium*) + printf '󰊯 Chromium' + ;; + brave*) + printf '󰞀 Brave' + ;; + chrome*) + printf '󰊯 Chrome' + ;; + mpv*) + printf '󰕼 mpv' + ;; + vlc*) + printf '󰕼 VLC' + ;; + *) + printf '%s' "${player:-Unknown}" + ;; + esac +} + +# ============================================================================ +# Command-Line Interface +# ============================================================================ + +case "${1:-}" in +--title) + title="$(get_metadata 'xesam:title')" + printf '%s\n' "$(trim_string "${title:-Nothing Playing}" 29)" + ;; + +--artist) + artist="$(get_metadata 'xesam:artist')" + printf '%s\n' "$(trim_string "${artist:-}" 26)" + ;; + +--status) + printf '%s\n' "$(get_status_icon)" + ;; + +--length) + printf '%s\n' "$(get_track_length)" + ;; + +--position) + printf '%s\n' "$(get_current_position)" + ;; + +--progress) + printf '%s\n' "$(calculate_progress_percent)" + ;; + +--progress-bar) + printf '%s\n' "$(generate_progress_bar)" + ;; + +--art) + printf '%s\n' "$(get_album_art_path)" + ;; + +--player) + printf '%s\n' "$(get_player_display)" + ;; + +--help | *) + cat <