起因

从 KDE 换到 Gnome 之后,最让我思念的功能就是自动更换壁纸了。虽然 Gnome 下也有不少 Extensions 可以做到更换壁纸的效果,但是无奈无法完全满足我的需求其实就是 KDE 自带的实现

  • 对于长宽比不合适的图片,不是单纯的进行拉伸、居中或者平铺,而是:
    先保持图片比例拉伸 -> 居中显示 -> 周围的黑边平铺填充
  • 可以对添加的目录进行递归搜索,并且一次性添加上万张图片也不会崩溃。
  • 随机更换壁纸而不是使用固定的顺序,支持跳过重复壁纸的功能。

在寻找相关工具未果之后,我决定利用 ImageMagick 自己造一个轮子。

思路

  1. 使用 find 命令生成包含所有图片地址的列表。
  2. 从列表中随机挑选一张图片。
  3. 利用 convert(ImageMagick) 转换处理图片。
  4. 使用 gsettings 设置壁纸。
  5. 使用 systemd 定期执行脚本。

Bash 脚本

首先写一个 Bash 脚本,实现更换壁纸的目的,同时为了响应速度和硬盘寿命着想,所有相关文件都保存在 $XDG_RUNTIME_DIR

生成地址列表

查找 $1 下面的图片,并且生成列表到 $XDG_RUNTIME_DIR/wp_db,如果已经生成过不需要重复生成。

1
2
3
if [[ ! -f "${XDG_RUNTIME_DIR}/wp_db" ]]; then
    find "${1}" \( -iname '*.jpg' -o -iname '*.jpeg' -o -iname '*.png' \) -print > "${XDG_RUNTIME_DIR}/wp_db"
fi

随机挑选一张图片

使用 shuf 命令挑选一张图片,如果文件不存在则重新挑选,注意如果上一步没有找到任何图片会陷入死循环。

5
6
7
while [[ ! -f "${TARGET}" ]]; do
    TARGET=$(shuf -n 1 "${XDG_RUNTIME_DIR}/wp_db")
done

假如需要跳过重复壁纸只需要每次挑选图片后删掉相应的行,然后在地址列表为空时重新扫描即可。

处理图片

把图片高度缩放到 1440, 同时保持宽高比。

8
convert "${TARGET}" -resize 'x1440' "${XDG_RUNTIME_DIR}/wp"

将图片重复 n 次,n = 向上取整到偶数(向上取整(显示器宽度/图片宽度)-1)。之所以要向上取整到偶数是因为要避免重复后的图片(本身算作一次)数量为偶数,从而导致“接合的缝隙”显示在屏幕正中间这一悲剧。
例如我的屏幕分辨率为 5120x1440,resize 后的图片分辨率为 3000x1440,那么这张图片需要被重复两次,算上自身就会生成 3 张并排的图片,保证了宽度大于显示器宽度。
n 的计算、重复图片均可以通过 convert 命令完成:

8
9
DUPES=$(convert "${TARGET}" -format "%[fx:int(int(5120/1440*h/w+1)/2)*2]" info:)
convert "${TARGET}" -resize 'x1440' -duplicate ${DUPES} +append "${XDG_RUNTIME_DIR}/wp"

最后只要按照屏幕宽度裁掉两侧多余的部分即可。

8
9
DUPES=$(convert "${TARGET}" -format "%[fx:int(int(5120/1440*h/w+1)/2)*2]" info:)
convert "${TARGET}" -resize 'x1440' -duplicate "${DUPES}" +append -gravity center -extent 5120 "${XDG_RUNTIME_DIR}/wp"

设置壁纸

通过 gsettings 来进行设置。

9
gsettings set org.gnome.desktop.background picture-uri "file://${XDG_RUNTIME_DIR}/wp" || true

合体后的脚本 wp.sh

运行这个脚本就可以自动更换壁纸了。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#!/usr/bin/env bash
set -Eeo pipefail

if [[ ! -f "${XDG_RUNTIME_DIR}/wp_db" ]]; then
    find "${1}" \( -iname '*.jpg' -o -iname '*.jpeg' -o -iname '*.png' \) -print > "${XDG_RUNTIME_DIR}/wp_db"
fi

while [[ ! -f "${TARGET}" ]]; do
    TARGET=$(shuf -n 1 "${XDG_RUNTIME_DIR}/wp_db")
done

DUPES=$(convert "${TARGET}" -format "%[fx:int(int(5120/1440*h/w+1)/2)*2]" info:)
convert "${TARGET}" -resize 'x1440' -duplicate "${DUPES}" +append -gravity center -extent 5120 "${XDG_RUNTIME_DIR}/wp"

gsettings set org.gnome.desktop.background picture-uri "file://${XDG_RUNTIME_DIR}/wp" || true

Systemd

使用 systemd --user 定期运行脚本,一时换一时爽,一直换一直爽。

wallpaper@.service

其中 %h 对于 systemd --user 而言相当于 $HOME 的作用,而 %I 则用于传递参数也就是存储的图片文件夹路径。

[Unit]
Description=Select a random wallpaper from %I

[Service]
Type=oneshot
ExecStart=%h/path/to/wp.sh "%I"

wallpaper@.timer

使用 AccuracySec=1s 避免随机延迟,另外 PartOf 以及 WantedBy 确保了只有处于 Gnome 桌面环境时才会触发。

[Unit]
Description=Select a random wallpaper from %I every 1 min
PartOf=graphical-session.target

[Timer]
OnStartupSec=1
OnUnitActiveSec=1min
AccuracySec=1s

[Install]
WantedBy=gnome-session.target

启动 Timer

install -m640 wallpaper@.service wallpaper@.timer ~/.config/systemd/user/
systemctl --user daemon-reload
PATH=$(systemd-escape "path/to/wallpaper/directory")
systemctl --user enable --now wallpaper@$PATH.timer

注意 @ 后面的路径需要使用 systemd-escape 进行转义。

查看当前壁纸

有时候需要知道当前壁纸的原图位置,只需要在 wp.sh 相应位置添加:

echo "$TARGET" > "$XDG_RUNTIME_DIR/wp_path"

然后就可以方便做各种奇奇怪怪的事情了:

# 显示当前壁纸原图的位置
cat "$XDG_RUNTIME_DIR/wp_path"
# 在桌面环境打开原图
xdg-open $(cat "$XDG_RUNTIME_DIR/wp_path")
# 删除壁纸原图
rm "$(cat "$XDG_RUNTIME_DIR/wp_path")"