Random wallpaper with just bash and systemd

I’m a big fan of Variety, probably the most popular wallpaper changer among Linux users. It’s been sitting in my GNOME desktop’s system tray for a long time. (Yes, I know GNOME doesn’t actually have a system tray, but I can’t live without it so I installed Ubuntu’s AppIndicator and KStatusNotifierItem Support extension).

Recently, Variety started having some hiccups, for the simple reason that the API key used for fetching images from Unsplash is shared among all Variety users, which means API rate limits are often exceeded. Since I don’t use most of Variety’s features and I have only Unsplash enabled as image source, I scratched an itch by writing my own little wallpaper changer. Truth be told, it’s nothing fancy, but it does the job.

Here’s the script:

#!/usr/bin/env bash

set -o pipefail
set -o errexit

# What? Associative arrays?
declare -A topics
topics=(
  [current_events]="BJJMtteDJA4"
  [wallpapers]="bo8jQKTaE0Y"
  [3d_renders]="CDwuwXJAbEw"
  [textures_and_patterns]="iUIsnVtjB0Y"
  [experimental]="qPYsDzvJOYc"
  [architecture]="rnSKDHwwYUk"
  [nature]="6sMVjTLSkeQ"
  [business_and_work]="aeu6rL-j6ew"
  [fashion]="S4MKLAsBB74"
  [film]="hmenvQhUmxM"
)

api_url="https://api.unsplash.com/photos/random?orientation=landscape&topics="
access_key="YOUR_API_KEY"
images_dir="${HOME}/Pictures/wallchanger"
image_prefix="wallchanger_"
my_topics=("wallpapers" "nature" "architecture")
delete_older_than_days=30

# Delete images older than X days
find "${images_dir}" -type f -name "${image_prefix}*.jpg" -mtime "+${delete_older_than_days}" -exec rm --force '{}' +

image_path="$(mktemp -p "${images_dir}" "${image_prefix}XXXXXXXXXX.jpg")"

# Build request URL with comma-separated topics
request_url="${api_url}"
for topic in "${my_topics[@]}"; do
  request_url+="${topics[$topic]}%2C"
done
image_url="$(curl --silent --show-error --header "Authorization: Client-ID ${access_key}" "${request_url}" \
  | jq --raw-output ".links.download")"
curl --silent --show-error --location --header "Authorization: Client-ID ${access_key}" \
  --output "${image_path}" "${image_url}"

# Set as light, dark and lockscreen wallpaper regardless of current mode
gsettings set "org.gnome.desktop.background" "picture-uri" "file:///${image_path}"
gsettings set "org.gnome.desktop.background" "picture-uri-dark" "file:///${image_path}"
gsettings set "org.gnome.desktop.screensaver" "picture-uri" "file:///${image_path}"

I know, I cheated. But you know what? Grepping JSON data ain’t no fun, and I’ve jq already installed on my machine (who hasn’t, by the way?).

If you’re interested in a different set of topics, you can change the my_topics array to include any of the keys in the topics array. I personally don’t like my home folder growing indefinitely, so the script deletes images older than $delete_older_than_days. If there’s a wallpaper that I like particularly, I should have enough time to make a copy of it.

I use this script with GNOME but it should work on other DEs as well, barring minor modifications. It’s worth noting that both light and dark wallpapers are changed, regardless of which theme mode is currently in use. To keep things consistent, the wallpaper of the lockscreen is also changed.

Anyway, we’ve done only half of the job, as the script alone doesn’t automate wallpaper switching. The standard way of achieving that is a systemd service/timer combo. Since we’re only interested in running the script as regular, non-root user, unit files can be conveniently installed under ~/.config/systemd/user. Here is the service unit I use:

# ~/.config/systemd/user/wallchanger.service
[Unit]
Description=Change GNOME wallpaper

[Service]
ExecStart=/path/to/wallchanger.sh

Of course, replace /path/to/wallchanger.sh with whatever the script’s path is. As mentioned, we also need a timer unit, which is responsible for the actual automation:

# ~/.config/systemd/user/wallchanger.timer
[Unit]
Description=Start wallchanger.service hourly

[Timer]
OnCalendar=hourly

[Install]
WantedBy=timers.target

Notice that for this to work, the two unit files must have the same name (except for the .timer/.service suffix). You can change hourly to your preferred interval; for example, to switch wallpaper once a day, you can put daily. Systemd has its own syntax for specifying schedules, which is well documented.

Once everything is in place, we need to tell systemd to reload its configuration with:

systemctl --user daemon-reload

Now, it’s time to test the service unit:

systemctl --user start wallchanger.service

If you don’t see any output, don’t despair, that’s the expected outcome! Actually, your desktop’s wallpaper should have already changed by now. If that’s not the case, you can inspect what’s going on with journalctl (add --follow to monitor the unit in realtime):

journalctl --user --unit wallchanger.service

Lastly, we need to enable and start the timer so that it’s preseved across reboots:

systemctl enable --now wallchanger.timer

Enough wasted time for today. If you really want to use my ugly script, make sure you get an API key from Unsplash. The default, free, “demo” mode is capped at 50 requests per hour, which is more than enough for this use case… Right?