Skip to content

Instantly share code, notes, and snippets.

@logic
Last active September 6, 2017 02:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save logic/cec621d3d5cf165863dd to your computer and use it in GitHub Desktop.
Save logic/cec621d3d5cf165863dd to your computer and use it in GitHub Desktop.
GNOME background rotation script
[Unit]
Description=Rotate GNOME wallpaper
[Service]
Type=oneshot
ExecStart=/bin/bash ${HOME}/bin/rotate_background.sh
[Unit]
Description=Rotate GNOME wallpaper every five minutes
[Timer]
OnCalendar=*:0/5
Persistent=true
[Install]
WantedBy=timers.target
#!/bin/bash
# GNOME wallpaper rotation script
# Where to find the wallpapers.
WALLPAPERS="${HOME}/Pictures/Wallpapers"
# Percentage off the width and height of the screen that constitutes a
# "small" image, ie. one that should just be centered on the screen rather
# than stretched to fit. On lower-DPI screens, setting this lower is a good
# idea, or you'll end up with "fuzzy" images; on high-DPI displays, you can
# bump it up a bit.
SMALL="0.4"
# Percentage of our screen's height/width ratio that we'll allow an image
# to be stretched to fit. Set this too large, and you'll end up with very
# distorted backgrounds.
CLOSE="0.0045"
# Percentage of vertical image height we're willing to sacrifice before we
# resort to letterboxing. Top/bottom letterboxing is often a pleasing visual
# effect, and losing vertical resolution is usually more of a loss than
# horizontal trimming, so setting this lower is a good idea.
VERTICAL="0.1"
# Percentage of horizontal image width we're willing to sacrifice before we
# resort to letterboxing. "Sidebars" are often very visually distracting, so
# setting this higher than $VERTICAL is often a good call.
HORIZONTAL="0.6"
cmd_or_exit() {
type "${1}" > /dev/null 2>&1 || ( \
echo "Missing ${1} command" && \
exit 1
)
}
cmd_or_exit gm
cmd_or_exit grep
cmd_or_exit gsettings
cmd_or_exit pgrep
cmd_or_exit python
cmd_or_exit shuf
cmd_or_exit xwininfo
# Echo the filename of a random wallpaper.
pick_wallpaper() {
find "${WALLPAPERS}" -type f \
-iname '*.jpg' -o -iname '*.png' -o -iname '*.gif' \
| shuf -n1
}
# Given a filename of an image, echo the height and width of it.
image_size() {
gm identify -ping -format '%h %w\n' "${1}"
}
# Echo the height and width of the current display.
screen_size() {
[ -z "$DISPLAY" ] && exit 0 # No X means no reason to run.
local wininfo
wininfo="$(xwininfo -root -stats)"
(
echo "${wininfo}" | grep -F ' Height: '
echo "${wininfo}" | grep -F ' Width: '
) | awk '{ print $2 }'
}
# Given two numbers, calculate the ratio to four decimal places.
calc_ratio() {
# My kingdom for floating-point math.
python -c 'print int(1.0*'"$1"'/'"$2"'*10000)'
}
# Given a value and a percentage, echo the value minus that percentage.
percent_off() {
python -c 'print int('"$1"'-('"$1"'*'"$2"'))'
}
# Given a value and a percentage, echo the value plus that percentage.
percent_plus() {
python -c 'print int('"$1"'+('"$1"'*'"$2"'))'
}
# Given a "<num> <value>" list as stdin, echo the largest <num>'s <value>.
pick() {
sort -n | awk '{ print $2 }' | tail -1
}
# Given a filename and a geometry, echo the frequency of the most popular
# color, and the color value.
extract_edge() {
gm convert "${1}" -crop "${2}" txt:- | \
sed -e '/^#/d' | \
awk '{ print $3 }' | \
uniq -c | \
sort -n | \
tail -1
}
# Given a filename, height, and width, echo the frequency of the most popular
# color on the left edge, and it's color value.
#
# Note that we don't take the outside edges, but instead take from the
# second pixel row into the image; this ensures we don't get bitten by
# JPEG edge artifacts.
left_edge() {
extract_edge "${1}" "1x${2}+2"
}
# Given a filename, height, and width, echo the frequency of the most popular
# color on the right edge, and it's color value.
right_edge() {
extract_edge "${1}" "1x${2}+$(( $3 - 2 ))"
}
# Given a filename, height, and width, echo the frequency of the most popular
# color on the top edge, and it's color value.
top_edge() {
extract_edge "${1}" "${3}x1+1+2"
}
# Given a filename, height, and width, echo the frequency of the most popular
# color on the bottom edge, and it's color value.
bottom_edge() {
extract_edge "${1}" "${3}x1+1+$(( $2 - 2 ))"
}
# Given a filename, height, and width, echo the most popular color along the
# top abd bottom edges.
letterbox_topbottom() {
(
top_edge "$1" "$2" "$3"
bottom_edge "$1" "$2" "$3"
) | pick
}
# Given a filename, height, and width, echo the most popular color along the
# left and right edges.
letterbox_leftright() {
(
left_edge "$1" "$2" "$3"
right_edge "$1" "$2" "$3"
) | pick
}
# Given a filename, height, and width, echo the most popular color along
# any edge.
letterbox_all() {
(
top_edge "$1" "$2" "$3"
bottom_edge "$1" "$2" "$3"
left_edge "$1" "$2" "$3"
right_edge "$1" "$2" "$3"
) | pick
}
# Echo the address of the gnome-session dbus endpoint.
dbus_address() {
local gspid
gspid="$(pgrep -u "${USER}" gnome-session)"
[ -z "${gspid}" ] && exit 0 # No gnome-session, no reason to run.
grep -z '^DBUS_SESSION_BUS_ADDRESS=' "/proc/${gspid}/environ" \
| cut -d= -f2- | tr '\0' '\n'
}
# Given a filename, background color, and picture distortion option, set
# the background.
update_background() {
# gsettings needs to know how to communicate with gnome-session.
export DBUS_SESSION_BUS_ADDRESS
DBUS_SESSION_BUS_ADDRESS="$(dbus_address)"
[ -z "${DBUS_SESSION_BUS_ADDRESS}" ] && exit 0 # Can't run without it.
# clear the previous picture and set the new background color before
# changing the picture option, and set the picture option before setting
# the new image, so you don't get visually-jarring resizing animations
gsettings set org.gnome.desktop.background picture-uri ''
gsettings set org.gnome.desktop.background primary-color "$2"
gsettings set org.gnome.desktop.background picture-options "$3"
gsettings set org.gnome.desktop.background picture-uri "file://$1"
}
main() {
local image
if [[ -z $1 ]]; then
image=$(pick_wallpaper)
else
image=$1
fi
# shellcheck disable=SC2046
set $(image_size "${image}")
local height width ratio
height=$1
width=$2
ratio="$(calc_ratio "$height" "$width")"
# shellcheck disable=SC2046
set $(screen_size)
local sc_height sc_width sc_ratio
sc_height=$1
sc_width=$2
sc_ratio="$(calc_ratio "$sc_height" "$sc_width")"
local small_width small_height
small_width="$(percent_off "$sc_width" "$SMALL")"
small_width="$(percent_off "$sc_height" "$SMALL")"
local ratio_close ratio_vertical
ratio_close="$(percent_off "$sc_ratio" "$CLOSE")"
ratio_vertical="$(percent_off "$sc_ratio" "$VERTICAL")"
local ratio_close_wide
ratio_close_wide="$(percent_plus "$sc_ratio" "$CLOSE")"
ratio_horiz_wide="$(percent_plus "$sc_ratio" "$HORIZONTAL")"
local bgcolor
local opt
if [[ $width -lt $small_width && $height -lt $small_height ]]; then
# tinypic, center.
bgcolor="$(letterbox_all "$image" "$height" "$width")"
opt='centered'
elif [[ $ratio -lt $ratio_close ]]; then
# lower than our ratio
if [[ $ratio -lt $ratio_vertical ]]; then
bgcolor="$(letterbox_topbottom "$image" "$height" "$width")"
opt='scaled'
else
bgcolor="$(letterbox_leftright "$image" "$height" "$width")"
opt='zoom'
fi
elif [[ $ratio -gt $ratio_close_wide ]]; then
# higher than our ratio
if [[ $ratio -gt $ratio_horiz_wide ]]; then
bgcolor="$(letterbox_topbottom "$image" "$height" "$width")"
opt='scaled'
else
bgcolor="$(letterbox_leftright "$image" "$height" "$width")"
opt='zoom'
fi
else
# ~= ratio and big enough, stretch to fit.
bgcolor="$(letterbox_all "$image" "$height" "$width")"
opt='stretched'
fi
update_background "$image" "$bgcolor" "$opt"
}
main "$@"
# EOF
@logic
Copy link
Author

logic commented Feb 9, 2016

This was originally written for Fedora 23+, and requires GraphicsMagick (for gm convert and gm identify), xorg-x11-utils (for xwininfo), and python (for the percentage and ratio calculations).

Place both the .service and .timer files in ${HOME}/.config/systemd/user, and run:

systemctl --user enable gnome-background-rotation.timer
systemctl --user enable gnome-background-rotation.service
systemctl --user start gnome-background-rotation.timer

Put the rotate_background.sh script in ${HOME}/bin, creating the directory if necessary. To test that it's working, run:

~/bin/rotate_background.sh

If you make changes to either the .service or .timer files (changing the time span, for example), you'll need to reload them with:

systemctl --user daemon-reload

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment