This repository has been archived by the owner on Aug 29, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathaura.sh
executable file
·319 lines (275 loc) · 8.81 KB
/
aura.sh
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
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
#!/bin/bash
### Options start
interval=$(( 3 * 3600 )) # 3h
recheck=$(( 3600 )) # 1h
activity_timeout=$(( 30 * 60 )) # 30min
max_log_size=$(( 2**20 )) # 1 MiB, current+last files are kept
monitor_count=$(xrandr --listactivemonitors 2>/dev/null | awk '/^Monitors:/ {print $2}' || echo 1)
monitor_id= # can be set to only use specific monitor id
gimp_cmd="nice ionice -c3 gimp"
wps_dir=~/.aura
favelist="$wps_dir"/favelist
blacklist="$wps_dir"/blacklist
log_err="$wps_dir"/picker.log
log_hist="$wps_dir"/history.log
log_curr="$wps_dir"/current
pid="$wps_dir"/picker.pid
hook_onchange="$wps_dir"/hook.onchange
# Cache for processed images
cache_enabled= # will be enabled if non-empty
cache_dir="$wps_dir"/cache
cache_cleanup_keep=$(( 100 * 2**20 )) # how many MiB of cached files to keep (100 MiB)
cache_cleanup_chance=8 # chance (percent) to run a cleanup routine on image change
# Resets sleep timer in daemon (if running) after oneshot script invocation
delay_daemon_on_oneshot_change=true # empty for false
# Site-local option overrides, if any
[[ -r ~/.aurarc ]] && source ~/.aurarc
### Options end
## Since pid can be re-used (and it's surprisingly common), pidfile lock is also checked
with_pid() {
local pid_instance pid_check
[[ ! -e "$pid" ]] && return 1
flock -n 3 3<"$pid" && return 1
pid_instance=$(pgrep -F "$pid")
pid_check=$?
if [[ -n "$1" ]]
then
"$@" $pid_instance
return $?
else return $pid_check
fi
}
## Commanline processing
action=
force_break=
no_fork=
no_init=
reexec=
bg_paths=()
result=0
while [[ -n "$1" ]]; do
case "$1" in
-m|--monitor) shift; monitor_id="$1" ;;
-m*) monitor_id="${1:2}" ;;
-d|--daemon) action=daemon ;;
--no-fork) no_fork=true ;;
--no-init) no_init=true ;;
--favepick)
if [[ -z "$2" || ! -d "$2" ]]; then
echo >&2 "ERROR: not a directory - $2"
action=break force_break=true result=1
else
readarray -t bg_paths < <(sed 's~^[0-9]\+ ~'"${2%/}"'/~' "$favelist")
fi ;;
-n|--next)
action=break
with_pid kill -HUP
result=$? ;;
-f|--fave|-b|--blacklist|-bn|-nb)
action=break
if [[ -z "$monitor_id" && $(wc -l < "$log_curr") -gt 1 ]]; then
echo >&2 "ERROR: -m/--monitor-id option required with >1 displays"
force_break=true result=1
else
bg=$(sed -n "${monitor_id:-1}p" "$log_curr")
if [[ "$1" = -f || "$1" = --fave ]]; then
printf -v ts '%(%s)T' -1
echo "$ts $bg" >>"$favelist"
else
echo "$bg" >>"$blacklist"
[[ "$1" = -nb || "$1" = -bn ]] && { with_pid kill -HUP; result=$?; }
fi
fi ;;
-k|--kill)
action=break
with_pid kill
result=$? ;;
-c|--current)
action=break
cat "$log_curr" 2>/dev/null ;;
-x)
reexec=true
action=daemon ;;
-h|--help)
action=break force_break=true
cat <<EOF
Usage:
$(basename "$0") [opts] paths...
$(basename "$0") [opts] --favepick directory
$(basename "$0") [opts] ( -d | --daemon ) [ --no-fork ] [ --no-init ] paths...
$(basename "$0") [ { -m | --monitor } n ] [ -f | --fave ] [ -b | --blacklist ]
$(basename "$0") [ -n | --next ] [ -c | --current ] [ -k | --kill ] [ -h | --help ]
Set background image, randomly selected from the specified paths.
--favepick command makes it weighted-random among fave-list (see also --fave).
Blacklisted paths never get picked (see --blacklist).
--daemon command starts instance in the background (unless --no-fork is also
specified), and picks/sets a new image on start (unless --no-init is specified),
and every ${interval}s afterwards.
Some commands (or their one-letter equivalents) can be given instead of paths to
control already-running instance (started with --daemon flag):
--next cycle to then next background immediately.
--fave give +1 rating (used with --favepick) to current bg image.
--blacklist add current background to blacklist (skip it from now on).
--kill stop currently running instance.
--current echo current background image name(s)
--help this text
Options:
-m/--monitor n - only use specific monitor id, where 0 is the first one.
Various paths and option defaults are specified at the top of this script,
and can be overidden via site-local ~/.aurarc file.
EOF
;;
*) break ;;
esac
shift
done
[[ "$action" = break || -n "$force_break" ]] && exit $result
## Pre-start sanity checks
[[ ${#bg_paths[@]} -eq 0 ]] && bg_paths=( "$@" )
if [[ ${#bg_paths[@]} -eq 0 ]]; then
echo >&2 "ERROR: no bg paths specified"
exit 1
fi
if [[ "$action" = daemon ]] && pid_instance=$(with_pid echo); then
echo >&2 "ERROR: detected already running instance (pid: $pid_instance)"
exit 0
fi
mkdir -p "$wps_dir"
[[ ! -e "$blacklist" ]] && touch "$blacklist"
if [[ -z "$reexec" ]]; then
if [[ "$action" = daemon && -z "$no_fork" ]]; then
setsid "$0" -x "$@" &
disown
exit 0
fi
[[ $(ps -o 'pgid=' $$) -ne $$ ]] && exec setsid "$0" -x "$@"
fi
if [[ "$action" = daemon ]]; then
touch "$pid"
exec 3<"$pid"
flock 3
echo $$ >"$pid"
fi
## Interruptable and extensible (by signals) sleep function hack
trap_action= # set from trap handlers
sleep_int() {
[[ "$action" != daemon ]] && {
[[ -n "$delay_daemon_on_oneshot_change" ]] && with_pid kill -USR1
return 1
}
sleep "$1" &
echo $! >"$pid"
trap_action=
wait $! &>/dev/null
local err=$(( $? - 128 ))
[[ "$err" -gt 0 ]] && kill "-${err}" 0
echo $$ >"$pid"
# Sleep extension via recursion - hopefully this won't get too deep
[[ "$trap_action" != timer_reset ]] || sleep_int "$interval"
return 0
}
## Log update with rotation
log() {
[[ -e "$1" && "$(stat --format=%s "$1")" -gt "$max_log_size" ]] && mv "$1"{,.old}
echo "$2" >>"$1"
}
## Cache parameters
[[ -n "$cache_enabled" ]] && {
[[ ! -e "$cache_dir" ]] && { mkdir -p "$cache_dir" || exit 1; }
export LQR_WPSET_CACHE_DIR="$cache_dir"
export LQR_WPSET_CACHE_SIZE="$cache_cleanup_keep"
export LQR_WPSET_CACHE_CLEANUP="$cache_cleanup_chance"
}
## Monitors
mon_seq=0 mon_seq_all=0 log_curr_pad=t
[[ $monitor_count -gt 1 ]] && mon_seq_all=$(seq 0 $(($monitor_count-1)))
[[ -n "$monitor_id" ]] && mon_seq=$monitor_id || mon_seq=$mon_seq_all
[[ action = daemon ]] || {
pid_instance=$(with_pid echo)
[[ -z "$pid_instance" ]] || log_curr_pad= # don't overwrite stuff from daemon
}
[[ -z "$log_curr_pad" ]] || {
: >"$log_curr"
for n in $mon_seq_all; do echo >>"$log_curr"; done
}
## Main loop
bg_list_ts=0
bg_count=0
bg_used=0
set +m
trap trap_action=next HUP # "snap outta sleep" signal
trap trap_action=timer_reset USR1 # "reset sleep" signal
trap "trap 'exit 0' TERM; pkill -g 0" EXIT # cleanup of backgrounded processes
while :; do
# Just sleep if there's no activity
idle_time=$(xprintidle 2>/dev/null)
idle_time="$(( ${idle_time:-0} / 1000 ))"
if [[ "$idle_time" -gt "$activity_timeout" ]]; then
sleep_int "$recheck" || break
continue
fi
# Update bg_list array on dirs' mtime changes or when it gets empty
bg_list_update=
[[ "$bg_count" -ne 0 ]] || bg_list_update=t
[[ "$bg_used" -eq 0 ]] || {
[[ "$(($bg_count-$bg_used))" -ge "$monitor_count" ]]\
|| { bg_list_update=t; bg_used=0; }
[[ -n "$bg_list_update" ]]\
|| for dir in "${bg_paths[@]}"; do
[[ "$(stat --printf=%Y "$dir")" -le "$bg_list_ts" ]]\
|| { bg_list_update=t; break; }
done
}
if [[ -n "$bg_list_update" ]]; then
readarray -t bg_list < <(
find "${bg_paths[@]}" -type f \( -name '*.jpg' -o -name '*.png' \) | shuf )
bg_count="${#bg_list[@]}"
fi
if [[ "$bg_count" -eq 0 ]]; then
echo >&2 "ERROR: no bgz found in the specified paths"
sleep_int "$recheck" || break
continue
fi
# bg update
ts="$(date --rfc-3339=seconds)"
for n in $mon_seq; do
export LQR_WPSET_MONITOR=$n
[[ -z "$no_init" ]] && err=next || err=
while [[ "$err" = next ]]; do
[[ "$bg_used" -lt "$bg_count" ]] || { err=; break; }
bg_n=$(shuf -n1 -i 0-$(($bg_count-1)))
bg="${bg_list[$bg_n]}"
[[ -z "$bg" ]] && continue # not particulary good idea
# Pop selected bg from array
unset bg_list[$bg_n]
(( bg_used += 1 ))
# Blacklist check
grep -q "^\(.*/\)\?$(basename "$bg")$" "$blacklist" && continue
# Actual bg setting
log "$log_err" "--- ${ts}: [monitor-${n}] ${bg}"
err=$($gimp_cmd -ib "(catch\
(gimp-message \"WPS-ERR:gimp_error\")\
(gimp-message-set-handler ERROR-CONSOLE)\
(python-fu-lqr-wpset RUN-NONINTERACTIVE \"${bg}\"))\
(gimp-quit TRUE)" 2>&1 1>/dev/null |
tee -a "$log_err" | grep -o 'WPS-ERR:.\+')
err="${err#*:}"
# History/current entry update
[[ -n "$err" ]] || {
log "$log_hist" "${ts} (id=${bg_n}, mon=${n}): ${bg}"
sed -i "$(($n+1))c $(basename "${bg}")" "$log_curr"
}
done
done
no_init= # reset oneshot --no-init flag
# Check for unexpected errors
if [[ -n "$err" ]]; then
echo >&2 "ERROR: failed setting bg, see log (${log_err}) for details"
sleep_int "$recheck" || break
continue
fi
# Try running the hook script
[[ -z "$no_init" && -x "$hook_onchange" ]] && "$hook_onchange"
# Main cycle delay
sleep_int "$interval" || break
done