3

I have a directory filled with png files

$ tree ~/wallpaper/
~/wallpaper/
├── foo--1366x768.png
├── foo--1920x1080.png
├── foo--2048x1080.png
├── foo--3440x1440.png
└── foo--3840x2160.png

0 directories, 5 files

I'm often changing monitors, sometimes with multiple attached. I want a script that detects the sizes of the monitors attached to my computer and sets the wallpaper from ~/wallpaper according to the monitor's size.

So, if monitors with resolutions 1366x768 and 3440x1440 are connected, I want the script to set the wallpapers to ~/wallpaper/foo--1366x768.png and ~/wallpaper/foo--3440x1440.png respectively. In this particular example, I know that issuing the command

feh --bg-fill ~/wallpaper/foo--1366x768.png ~/wallpaper/foo--3440x1440.png

will set the wallpapers correctly.

To solve the general problem, I know I can issue

xrandr | grep ' connected' | awk '{print $3}' | cut -f1 -d"+"

to get the dimensions of the monitors attached. In our running example, the output here is:

1366x768
3440x1440

I want to now somewhow pipe this into a command to set the wallpaper with feh. How can I do this?

Brian Fitzpatrick
  • 2,755
  • 3
  • 23
  • 43

2 Answers2

3

You can use --no-xinerama, in which case any image you specify will appear across the entirety of all screens. The only question then would be how would you combine all your images together to make one desktop background, in accordance with how you have your monitors setup...

The only thing that came to my mind was using ImageMagick, even though that would be pretty outlandish and convoluted. So, I decided to make it anyway... Here's the whole thing:

#!/bin/sh

AWK_XRANDR_PARSE='/ connected/ {
    split($3,sizePos,"+")
    split(sizePos[1],size,"x")
    print size[1] "," size[2] "," sizePos[2] "," sizePos[3]
}'

# Fetches a wallpaper for the monitor of size $1x$2. $1 is the required width,
# and $2 is the required height.
wallpaper_file() {
    # Use a case or string substitution to pick out any image files you fancy on
    # your system... They must be printf'd.
    printf "$HOME/.desktop"
}

# Writes any line whose $2nth column is equal to that of the first line.
# Columns are split by $1.
matching() {
    SENTINEL="$([ "$#" -gt 2 ] && printf '%s' "$3" || printf 'sentinel')"
    awk -F"$1" -v first="$SENTINEL" \
        "{if (first == \"$SENTINEL\") first = \$$2; if (\$$2 == first) print}"
}

# Writes the value within the variable named "$1".
cat() {
    printf '%s' "${!1}"
}

# Writes the $2nth column. Columns are split by $1.
nth() {
    awk -F"$1" "{print \$$2}"
}

# This one variable assignment takes xrandr's output, parses it via awk, runs
# the wallpaper_file, takes it's output, and combines all that information into
# a list of tuples. Each item in the list represents a monitor, and the tuple
# is roughly equivalent to 'W,H,X,Y,F', where W is width, H is height, X is the
# X position, Y is the Y position, and F is the file of the image.
DISPLAYS="$(while read X Y REST; do
    printf '%s,%s,%s,%s\n' "$X" "$Y" "$REST" "$(wallpaper_file "$X" "$Y")"
done < <(xrandr | awk "$AWK_XRANDR_PARSE" | \
    awk -F',' '{print $1 " " $2 " " $3 "," $4}'))"

# This simply finds the monitor that is the farthest out in the X direction,
# and is the widest. It then combines the X position and the width of the
# monitor to find the absolute width of your desktop.
PLACEMENT_WIDTH="$(cat DISPLAYS | sort -rnt, -k3 | matching , 3)"
SIZE_WIDTH="$(cat PLACEMENT_WIDTH | sort -rnt, -k1 | head -n1)"
WIDTH="$(("$(cat SIZE_WIDTH | nth , 1)" + "$(cat SIZE_WIDTH | nth , 3)"))"

# Same goes on as above, but with the height and Y direction.
PLACEMENT_HEIGHT="$(cat DISPLAYS | sort -rnt, -k4 | matching , 4)"
SIZE_HEIGHT="$(cat PLACEMENT_HEIGHT | sort -rnt, -k2 | head -n1)"
HEIGHT="$(("$(cat SIZE_HEIGHT | nth , 2)" + "$(cat SIZE_HEIGHT | nth , 4)"))"

# Take all that information, and make a wallpaper file from it via imagemagick.
magick -size $WIDTH'x'$HEIGHT canvas:black \
    $(cat DISPLAYS | awk -F',' '{print $5 " -geometry " $1 "x" $2 "+" $3 "+" $4 " -composite"}') \
    /tmp/wallpaper.png

# Then set it as the background.
feh --bg-fill --no-xinerama /tmp/wallpaper.png

I published the whole script as a gist here, if you prefer.

I know I probably shouldn't have done it, but gosh darnit I did it anyway.

Danii
  • 91
  • 1
  • 6
0

If you always had exactly 2 monitors connected that would be very easy to do with Bash:

#!/usr/bin/env bash

screen_size=$(xrandr | grep ' connected' | awk '{print $3}' | cut -f1 -d"+")

IFS=$'\n'
readarray -t <<<"$screen_size"
feh --bg-fill ~/wallpaper/foo--"${MAPFILE[0]}".png ~/wallpaper/foo--"${MAPFILE[1]}".png

But if number of connected monitors can vary, which is most probably the case in practice you could construct a feh command and then run it with eval like that:

#!/usr/bin/env bash

screen_size=$(xrandr | grep ' connected' | awk '{print $3}' | cut -f1 -d"+")

IFS=$'\n'
readarray -t <<<"$screen_size"
feh_command="feh --bg-fill"

for i in "${MAPFILE[@]}"
do
    echo size: "$i"
    feh_command="$feh_command ~/wallpaper/foo--$i.png"
done

eval "$feh_command"
Arkadiusz Drabczyk
  • 25,049
  • 5
  • 53
  • 68