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
|
#!/bin/sh
argv0=$(basename "${0}")
# @FUNCTION: err
# @USAGE: [-x] <message> ...
# @DESCRIPTION:
# Print given messages to stderr line by line and exit with status 1.
#
# If "-x" is specified, suppress the program prefix (`program: `)
# from the output. Otherwise, the first line is prefixed with "program: ".
#
# This function is intended for fatal errors; it always exits the script.
# @EXAMPLE:
# err "Invalid usage" "Try '${argv0} -h' for help."
err() {
if [ "${1}" != "-x" ]; then
printf "%s: " "${argv0}"
else
shift
fi
for line in "${@}"; do
echo "${line}" >&2
done
exit 1
}
# @FUNCTION: invalid_use
# USAGE: [-h]
# @DESCRIPTION:
# Output a usage error message. If `-h` is not specified output:
# "<program>: Invalid usage "
# "Try 'program -h' for help."
# else output only:
# "Try 'program -h' for help."
invalid_use() {
[ "${1}" = "-h" ] && err -x "Try '${argv0} -h' for help."
err "Invalid usage" "Try '${argv0} -h' for help."
}
# @FUNCTION: check_program
# USAGE: <command> [error-msg]
# @DESCRIPTION:
# Check if command exists on the system.
# If not, print an error message to stderr and exit with status 1.
# The default error message is "`command` must be installed"
# but can optionally be overwritten.
#
# @EXAMPLE:
# Check if pulseaudio is installed.
#
# check_program "pactl" "pulseaudio must be installed"
check_program() {
command -v "${1}" > /dev/null 2>&1 && return 0
[ -n "${2}" ] && err "${2}"
err "${1} must be installed"
}
# @FUNCTION: send_notification
# @USAGE: send_notification <message> ...
# @DESCRIPTION:
# Write a notification message to a temporary file and trigger a dwm notification signal.
# The message is written to /tmp/noti.txt. If writing fails, a warning is printed to stderr.
# After writing, a dwm signal is sent using `xsetroot -name "fsignal:1"`.
#
# @EXAMPLE:
# Send a desktop notification with the message "Backup complete".
#
# send_notification "Backup complete"
send_notification() {
echo "$@" > /tmp/noti.txt || echo "Warning: failed to write to notification file" >&2
xsetroot -name "fsignal:1"
}
# @FUNCTION: get_random_filename
# @USAGE: get_random_filename [parentdir] <extension>
# @DESCRIPTION:
# Write a random file path to stdout, under parentdir, (if provided else under /tmp) with the extension.
#
# @EXAMPLE:
# Get a filepath in the `/var` directory with the extension `.png`
#
# get_random_filename /var .png
get_random_filename() {
[ "${#}" -eq 2 ] && parentdir="${2}" || parentdir="/tmp"
extension="${1}"
echo "${parentdir}/$(date '+%b%d::%H%M%S')${extension}"
}
# @FUNCTION: run
# @USAGE: [--reload-status] [--reload-compositor] [--success-notify <msg>] [--failure-notify <msg>] <command> [success-msg] [failure-msg]
# @DESCRIPTION:
# Safely execute a simple shell command, print optional success/failure messages,
# and optionally reload the status bar or restart the compositor.
#
# Success messages are printed to stdout; failure messages are printed to stderr.
# Optional desktop notifications can be sent using --success-notify and --failure-notify.
# Command output is not suppressed.
#
# Options:
# --reload-status Reload the status bar (via `slreload`).
# --reload-compositor Restart the compositor (kills and restarts `picom`).
# --success-notify <msg> Send a desktop notification on success.
# --failure-notify <msg> Send a desktop notification on failure.
#
# Restrictions:
# - Does NOT use `eval`; only simple commands and arguments are supported.
# - Shell operators like `&&`, `||`, `|`, `>`, `>>`, etc. will NOT work.
# - This design prevents unintended execution and makes the function safe in scripts.
#
# Return value:
# Exits with 0 if the command succeeds, 1 otherwise.
#
# @EXAMPLES:
# # Run xwallpaper and print the image path on success
# run "xwallpaper --zoom ${image}" "${image}"
#
# # Restart compositor and show a success message
# run --reload-compositor "xwallpaper --zoom ${image}" "Wallpaper updated" "Wallpaper failed"
#
# # With notification hooks
# run --success-notify "Wallpaper set" \
# --failure-notify "Wallpaper failed" \
# "xwallpaper --zoom ${image}" "Wallpaper updated" "Wallpaper failed"
run() {
relstat=0
compstat=0
exitval=0
while [ $# -gt 0 ]; do
case "$1" in
--reload-status) relstat=1 ;;
--reload-compositor) compstat=1 ;;
--success-notify) success_msg="${2}"; shift ;;
--failure-notify) failure_msg="${2}"; shift ;;
*) break;
esac
shift
done
if [ "${compstat}" -eq 1 ]; then
pgrep -x picom > /dev/null && killall picom
fi
if ${1}; then
[ -n "${2}" ] && echo "${2}"
[ -n "${success_msg}" ] && send_notification "${argv0}:" "${success_msg}"
else
[ -n "${3}" ] && err "${3}"
[ -n "${failure_msg}" ] && send_notification "${argv0}:" "${failure_msg}"
exitval=1
fi
if [ "${relstat}" -eq 1 ]; then
slreload || echo "Warning: Failed to reload slstatus" >&2
fi
if [ "${compstat}" -eq 1 ]; then
picom -b || echo "Warning: Failed to start picom" >&2
fi
exit "${exitval}"
}
|