dotfiles/.config/hypr/UserScripts/RainbowBorders-low-cpu.sh
2026-05-13 21:22:17 +02:00

237 lines
8.2 KiB
Bash
Executable file
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
# ==================================================
# KoolDots (2026)
# Project URL: https://github.com/LinuxBeginnings
# License: GNU GPLv3
# SPDX-License-Identifier: GPL-3.0-or-later
# ==================================================
# RainbowBorders-low-cpu.sh — low-overhead animated rainbow border for Hyprland
#
# Goal
# Animate Hyprland's active border with a rotating rainbow gradient while
# minimizing CPU usage on older systems by:
# - Using a modest update rate (default 1.0s) and larger angle steps
# - Avoiding subshell-heavy work inside the loop
# - Using Hyprland's command socket via socat when available
# - Quoting/validating inputs and suppressing noisy output
# - Preventing multiple concurrent instances
# - Optionally restoring the previous border value on exit
#
# Credits
# Initial source/idea by: DemiGoD
# Adaptation and optimization for low-CPU usage by: Hyprland-Dots maintainers
#
# Usage
# You can customize behavior via environment variables when launching:
# RB_INTERVAL Float seconds between updates (default: 1.0)
# RB_STEP_DEG Integer degrees per tick (default: 10)
# RB_START_DEG Integer starting angle (default: 0)
# RB_TARGET Hypr option to update (default: general:col.active_border)
# RB_COLORS Space-separated color list (default: 10-color rainbow below)
# RB_RESTORE If "1", attempt to restore previous value on exit (loop mode; default: 1)
# RB_LOCKFILE Path to a PID lock file (loop mode; default: /tmp/hypr-rainbowborders.lock)
# RB_TRANSPORT auto|socat|hyprctl (default: auto)
# - socat: send each command via Hyprland's command socket
# using socat (one short-lived connection per tick)
# - hyprctl: spawn hyprctl each tick
# - auto: prefer socat if possible, otherwise hyprctl
# RB_ONCE 1 to apply once and exit (no animation; default: 0)
#
# Example (slower animation):
# RB_INTERVAL=1.5 RB_STEP_DEG=12 ~/.config/hypr/UserScripts/RainbowBorders-low-cpu.sh &
#
# Notes
# - This focuses on the active border only. Animating inactive borders too
# will increase updates and CPU usage.
# - Higher RB_INTERVAL (e.g., 1.02.0s) and larger RB_STEP_DEG (1020)
# reduce per-second work substantially.
set -u
# Defaults (can be overridden by env vars)
RB_INTERVAL="${RB_INTERVAL:-1.0}"
RB_STEP_DEG="${RB_STEP_DEG:-10}"
RB_START_DEG="${RB_START_DEG:-0}"
RB_TARGET="${RB_TARGET:-general:col.active_border}"
RB_COLORS_DEFAULT="0xffff0000 0xffff8000 0xffffff00 0xff80ff00 0xff00ff00 0xff00ff80 0xff00ffff 0xff0080ff 0xff0000ff 0xff8000ff"
RB_COLORS="${RB_COLORS:-$RB_COLORS_DEFAULT}"
RB_RESTORE="${RB_RESTORE:-1}"
RB_LOCKFILE="${RB_LOCKFILE:-/tmp/hypr-rainbowborders.lock}"
RB_TRANSPORT="${RB_TRANSPORT:-auto}"
RB_ONCE="${RB_ONCE:-0}"
# ---------- helpers ----------
log() { printf '[RainbowBorders-low-cpu] %s\n' "$*" >&2; }
die() { log "ERROR: $*"; exit 1; }
usage() {
cat <<'EOF'
Usage: RainbowBorders-low-cpu.sh [options]
Options:
-h, --help Show this help and exit
--once, --run-once, -1
Apply the current gradient once and exit (no animation).
In this mode, RB_RESTORE is ignored (the color persists).
Environment overrides:
RB_INTERVAL Seconds between updates (default: 1.0)
RB_STEP_DEG Degrees per tick (default: 10)
RB_START_DEG Starting angle (default: 0)
RB_TARGET Hypr option to update (default: general:col.active_border)
RB_COLORS Space-separated colors (default: 10-color rainbow)
RB_RESTORE 1 to restore previous value on exit (loop mode only; default: 1)
RB_LOCKFILE PID lock path (loop mode only; default: /tmp/hypr-rainbowborders.lock)
RB_TRANSPORT auto|socat|hyprctl (default: auto)
RB_ONCE 1 for one-shot mode (same as --once)
Examples:
Animate (light CPU):
RB_INTERVAL=1.5 RB_STEP_DEG=12 ./RainbowBorders-low-cpu.sh &
Set a static rainbow once (no animation):
./RainbowBorders-low-cpu.sh --once
EOF
}
is_float() { [[ "$1" =~ ^[0-9]+(\.[0-9]+)?$|^\.[0-9]+$ ]]; }
is_int() { [[ "$1" =~ ^[0-9]+$ ]]; }
# ---------- parse CLI flags ----------
while (( $# )); do
case "$1" in
-h|--help) usage; exit 0 ;;
--once|--run-once|-1) RB_ONCE=1 ;;
*) log "Unknown option: $1"; usage; exit 2 ;;
esac
shift
done
# ---------- validation ----------
if ! is_float "$RB_INTERVAL"; then
log "WARN: RB_INTERVAL='$RB_INTERVAL' invalid; defaulting to 1.0"
RB_INTERVAL="1.0"
fi
if ! is_int "$RB_STEP_DEG"; then
log "WARN: RB_STEP_DEG='$RB_STEP_DEG' invalid; defaulting to 10"
RB_STEP_DEG="10"
fi
if ! is_int "$RB_START_DEG"; then
log "WARN: RB_START_DEG='$RB_START_DEG' invalid; defaulting to 0"
RB_START_DEG="0"
fi
# ---------- single-instance lock (PID file) ----------
cleanup_lock() { [[ -f "$RB_LOCKFILE" ]] && rm -f "$RB_LOCKFILE"; }
if [[ "$RB_ONCE" != "1" ]]; then
if [[ -f "$RB_LOCKFILE" ]]; then
oldpid="$(cat "$RB_LOCKFILE" 2>/dev/null || true)"
if [[ -n "${oldpid:-}" ]] && kill -0 "$oldpid" 2>/dev/null; then
log "Another instance is running (pid=$oldpid). Exiting."
exit 0
else
# Stale lock
rm -f "$RB_LOCKFILE" || true
fi
fi
printf '%d' "$$" >"$RB_LOCKFILE" 2>/dev/null || die "Cannot write lockfile $RB_LOCKFILE"
fi
# ---------- transport (socat vs hyprctl) ----------
RB_MODE=""
RB_SOCK=""
open_transport() {
local want="$RB_TRANSPORT"
local uid; uid=$(id -u 2>/dev/null || echo 0)
local base="${XDG_RUNTIME_DIR:-/run/user/$uid}"
local sig="${HYPRLAND_INSTANCE_SIGNATURE:-}"
if [[ -n "$sig" ]]; then
RB_SOCK="$base/hypr/$sig/.socket.sock"
fi
# Prefer socat if requested/allowed and socket is available
if [[ "$want" == "socat" || "$want" == "auto" ]]; then
if command -v socat >/dev/null 2>&1 && [[ -n "$RB_SOCK" && -S "$RB_SOCK" ]]; then
RB_MODE="socat"
return 0
elif [[ "$want" == "socat" ]]; then
die "RB_TRANSPORT=socat requested but 'socat' or Hyprland socket is unavailable"
fi
fi
# Fallback to hyprctl: require presence and connectivity
command -v hyprctl >/dev/null 2>&1 || die "hyprctl not found and socat transport unavailable"
if ! hyprctl monitors >/dev/null 2>&1; then
die "hyprctl cannot reach a running Hyprland instance"
fi
RB_MODE="hyprctl"
return 0
}
open_transport || exit 1
log "Using transport: $RB_MODE"
# ---------- optional restore of previous border value ----------
PREV_VALUE=""
if [[ "$RB_RESTORE" == "1" && "$RB_ONCE" != "1" ]]; then
if command -v hyprctl >/dev/null 2>&1; then
# hyprctl getoption <opt> prints various formats; try common keys
PREV_VALUE="$(hyprctl getoption "$RB_TARGET" 2>/dev/null \
| sed -n 's/^.*str:[[:space:]]\+//p; s/^.*string:[[:space:]]\+//p; s/^.*value:[[:space:]]\+//p' \
| tail -n1)"
fi
fi
restore_previous() {
if [[ "$RB_RESTORE" == "1" && -n "${PREV_VALUE:-}" ]]; then
if [[ "$RB_MODE" == "socat" ]]; then
printf 'keyword %s %s\n' "$RB_TARGET" "$PREV_VALUE" | socat - "UNIX-CONNECT:$RB_SOCK" >/dev/null 2>&1 || true
else
hyprctl keyword "$RB_TARGET" "$PREV_VALUE" >/dev/null 2>&1 || true
fi
fi
}
on_exit() {
restore_previous
cleanup_lock
}
# In loop mode, set traps for cleanup/restore
if [[ "$RB_ONCE" != "1" ]]; then
trap on_exit INT TERM EXIT
fi
# ---------- main logic ----------
angle=$(( RB_START_DEG % 360 ))
STEP=$(( RB_STEP_DEG % 360 ))
(( STEP == 0 )) && STEP=10
write_border() {
local a="$1"
if [[ "$RB_MODE" == "socat" ]]; then
printf 'keyword %s %s %sdeg\n' "$RB_TARGET" "$RB_COLORS" "$a" | socat - "UNIX-CONNECT:$RB_SOCK" >/dev/null 2>&1 || true
else
hyprctl keyword "$RB_TARGET" "$RB_COLORS ${a}deg" >/dev/null 2>&1 || true
fi
}
if [[ "$RB_ONCE" == "1" ]]; then
# Single write and exit; do not restore previous (intended to persist)
write_border "$angle" || log "WARN: one-shot write failed"
exit 0
fi
# Prime first write (avoid waiting one interval)
write_border "$angle" || log "WARN: initial write failed"
while :; do
# Advance angle and write; failures are non-fatal to keep CPU use minimal
angle=$(( (angle + STEP) % 360 ))
write_border "$angle"
sleep "$RB_INTERVAL"
done