aboutsummaryrefslogtreecommitdiffstats
path: root/config/hypr/UserScripts/RainbowBorders-low-cpu.sh
blob: 894c4848e90318e9c5c4e7a3d1aada59307035d0 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
#!/usr/bin/env bash
# RainbowBorders-low-gpu.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 a persistent Hyprland command socket (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 (default: 1)
#     RB_LOCKFILE    Path to a PID lock file         (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
#
#   Example (slower animation):
#     RB_INTERVAL=1.5 RB_STEP_DEG=12 ~/.config/hypr/UserScripts/RainbowBorders-low-gpu.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.0–2.0s) and larger RB_STEP_DEG (10–20)
#     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}"

# ---------- helpers ----------
log() { printf '[RainbowBorders-low-gpu] %s\n' "$*" >&2; }

die() { log "ERROR: $*"; exit 1; }

is_float() { [[ "$1" =~ ^[0-9]+(\.[0-9]+)?$|^\.[0-9]+$ ]]; }

is_int()   { [[ "$1" =~ ^[0-9]+$ ]]; }

# ---------- 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 [[ -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"

# ---------- transport (socat persistent socket 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" ]]; 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
}
trap on_exit INT TERM EXIT

# ---------- main loop ----------
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
}

# 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
send patches to the email below
yukais@pinapelz.com
include the subject [PATCH repo_name]
pinapelz.com
homepage