-
-
Save rifazn/584a94d6f79e13b320180e7c9ec81eea to your computer and use it in GitHub Desktop.
#!/bin/sh | |
# A small POSIX compliant script to toggle between dark and light variant | |
# of a theme for GNOME based desktops. | |
# Copyright (C) 2021 Rifaz Nahiyan | |
# This code is licensed under the MIT License. | |
# View the license in its entirety at: https://opensource.org/licenses/MIT | |
get_current_theme () { | |
gsettings get org.gnome.desktop.interface gtk-theme | tr --delete \' | |
} | |
set_theme () { | |
gsettings set org.gnome.desktop.interface gtk-theme "$1" | |
# Unfortunately, gsettings always reports exit status 0 | |
} | |
## Sanity checks | |
deps_check () { | |
deps="gsettings notify-send" | |
missing="" | |
for dep in ${deps}; do | |
command -v "${dep}" >/dev/null || missing="${missing} ${dep}" | |
done | |
if [ -n "${missing}" ] | |
then | |
die "Missing necessary dependencies: ${missing}" | |
fi | |
unset missing | |
} | |
# Display a formatted message, then exit with error | |
die () { | |
printf "%s: %s\n" "${0##*/}" "${*}" >&2 | |
exit 1 | |
} | |
# Get the script's basename and trim the '.sh' (if any) at the end | |
SCRIPTNAME="${0##*/}" | |
SCRIPTNAME="${SCRIPTNAME%.sh}" | |
main () { | |
# Check if necessary and optional dependecies are there, else exit | |
deps_check | |
current_theme="$(get_current_theme)" | |
# Check if the theme name has "dark" or "Dark" at the end of its name, | |
# then set new theme accordingly | |
case $current_theme in | |
*-[dD]ark) | |
new_theme="${current_theme%-[Dd]ark}" | |
;; | |
*) | |
# Extra check for Arc theme as it breaks convention by using captial 'D' | |
if [ "$current_theme" = "Arc" ]; then | |
new_theme="$current_theme"-Dark | |
else | |
new_theme="$current_theme"-dark | |
fi | |
DARK="dark" | |
;; | |
esac | |
set_theme "$new_theme" | |
notify_msg="Theme switched to ${DARK:-light} variant." | |
notify-send -t 5000 "${SCRIPTNAME}" "$notify_msg" | |
} | |
main "${@}" |
Oh, just thought of something that might be good to add:
sanity_check() {
okmap=0
gsettings --help 2<&- >/dev/null || okmap=$(( okmap + 1 ))
notify-send --help 2<&- >/dev/null || okmap=$(( okmap + 2 ))
case "${okmap}" in
0) die "Need both 'notify-send' and 'gsettings' to work!" ;;
1) die "Need the 'notify-send' utility!" ;;
2) die "Need the 'gsettings' utility!" ;;
3) : ;; ## All necessary external tools available, nothing to report
esac
## Not needed here, but a useful practice to be familiar with as not all sh(1) implementations have local variables
unset okmap
}
## Display a formatted message, then exit with error
die() {
printf "%s: %s\n" "${0##*/}" "${*}"
exit 1
}
Excellent idea about initializing notify_msg
just once! Will add that!
Do you have the redirection symbols inverted in sanity_check()
? I think they should be 2>&-
...
That's a nice way to do dependency check, but on this repo for example (and a few others) on this line, I've seen this form of checking dependency: command -v "$dep" 1>/dev/null || missing="$missing $dep"
. Is the shell built-in command
a reliable method for this purpose while keeping POSIX compatibility in mind? Which method would you prefer?
Edit: Another thing I forgot to add. gsettings --help
exits with a status of 1
. 🤷
2<&-
closes stderr
, >&-
closes stdout
.
Constructing a list like missing="${missing} ${dep}"
definitely makes for a more flexible sanity check and message system. Both work just fine at this scale.
Since gsettings --help
has an exit code of 1
, something like this could be used instead:
sanity_check() {
deps="gsettings notify-send"
missing=""
for dep in ${deps}
do
${dep} --help 2<&- >/dev/null
case $? in
127) missing="${missing} ${dep}" ;;
*) : ;; ## command exists, but returned an error code for some reason
esac
if [ -n "${missing}" ]
then
die "Missing necessary dependencies: ${missing}"
fi
unset missing
}
I prefer this method of probing for external commands because it's faster than manually scanning the contents of ${PATH}
and I'm not sure how reliable the command
builtin is. I'll have to pull up the POSIX sh(1)
spec to be sure it's supposed to be available everywhere.
Hi @newnix!
It just occurred to me that not all programs include the --help
flag. Some examples that come to mind are, dash
, imv
and gammastep
. And executing --help
might take some noticeable amount of time if the --help
is particularly long. What might we do in this case?
Also, will it be better if the script die
s with a relevant exit code? For example, in case of a dependency problem, instead of exiting with 1, how about exiting with 127? In that case die
might be changed to
die () {
printf "%s: %s\n" "${0##*/}" "${*}" >&2
exit $2
}
Edit: Come to think of it... The default case in sanity_check()
does handle that situation pretty well. But still, please let me know if that the intended behavior.
Unfortunately, there's no universal way to verify if a command is usable without actually trying to invoke it (for example, docker
may be installed, but if you're not in the docker
group, then commands like docker info
will return a nonzero status other than 127
), so it really comes down to how much you want your script to be able to handle and which tools you're anticipating to work with.
Simply checking for their existence, seems to be well defined via POSIX with command -v ${utility}
found a reference.
Your modification to die()
makes sense, with the caveat that you probably don't (usually) want your script exiting with status 127, as something else could be watching for that then erroneously report to it's caller that your script doesn't exist. Also, you'd want to be sure it actually gets passed a value that can be accessed as $2
, so something like ${2:-255}
, or whatever the default exit code should be, would prevent it from potentially resolving to exit 0
.
This is one problem that some people undoubtedly have some pretty strong opinions on, but the best solution is the one that works best for your needs.
Right. Exiting with 1
almost objectively makes more sense and we are also printing to stderr for removing ambiguity. Also, command -v $program
itself exits with 1
and not 127
or something, so we can piggy-back off that.
About checking whether a theme exists, gsettings
does not provide any API for that, so the best thing currently to do is to check whether a directory with the theme name exists in: /usr/share/themes/
and ~/.themes/
.
Updated code with the dependency checks.
I'm not familiar with
gsettings
or how the theme data is stored, but if there's a world-readable path to check for the availability of a dark variant, which could be used as a sanity-check before givinggsettings
a potentially nonexistent theme to use.I'm also not familiar with
notify-send
, but depending on the information it reports, it may be beneficial to do something like this:though if
notify-send
already gets the process name, then theMYNAME
variable only becomes helpful for theusage()
function when you want to add functionality.It looks good! Since nothing can signal failure, attempting to handle invalid switches is harder and depends on information I don't have available off-hand. Everything else is just some stylistic decisions at this scale. Some of these choices might not be as desirable in larger scripts or those with more complex variable usage, but here it's mostly just decisions to be aware of and consider as you write future scripts.