Output dependent wallpaper selection for i3


Output dependent wallpaper selection for i3

The initial situation

As window manager I am using i3, while launching, I have set xwallpaper to be executed and set the background of my outputs. To keep it simple I used to set the same image for all my outputs. The background image file was a symbolic link placed in my home directory as ~/.background_image.

To select a background image, I was using sxiv1, but I switched over to nsxiv2, after sxiv maintenance stopped. The image viewer supports full keyboard control and can be extended with shell scripts. Through a keyboard shortcut I could execute a script to overwrite the symbolic link file and run xwallpaper to update the background.

At first, I never have cared that much for having different background images per output, but I didn’t want to just create a separate shortcut for each output itself.

A possible solution

Solution concept

The idea was to have nsxiv know which monitor it is on and only update the background for this specific output.

A nice extra, because why not, would be to put the output into the window title. This also would help the user to confirm on which output nsxiv is currently seeing itself.

Each output will have its own background image symbolic link. For example could the symbolic link be named .background_image-DisplayPort-0 and so on.

i3 setup and script

In my setup I have a bindsym command to execute nsxiv browsing my local wallpaper collection. The bindsym command is a sub-command to a mode in which I have configured different i3 commands to handle workspaces, this is why in the following configuration example the bound key is just b.

bindsym b exec --no-startup-id nsxiv -r -t -b ~/Pictures/wallpapers/by-resolution, mode "default"

for_window [class="^(?i).?sxiv$"] floating enable, border normal

Command line switches explanation:

  • -r: recursively browse the wallpaper location
  • -t: start in thumbnail mode
  • -b: do not show the status bar at the bottom

The wallpapers are sorted by resolution. For that I wrote a script, but it is not necessary for setup to run. Also, current setup does not take the size of the output into consideration, which means all outputs will be shown all images.

The last line in the configuration, makes nsxiv or sxiv windows floating with border and a title bar at the top.

With i3 setup, the nsxiv window will not show the current output it is located on. To update a window title we have can use the (IPC)[https://i3wm.org/docs/ipc.html)3 provided by i3.

To run the follwing script, the Python i3ipc module needs to be installed. As external dependency for this script, the tool xprop is required.

Xprop can show and update X window properties, this command is later used to change the window title.

Consult your distribution package manager on how to install the module. For many distributions the package is often called python-i3ipc, or when using pip it is called i3ipc-python.

#!/usr/bin/env python

import subprocess

from i3ipc import Connection, Event
from i3ipc.events import IpcBaseEvent


def find_window_workspace(i3, window_name):
    for con in i3.get_tree().leaves():
        print(con)


def on_window_event(i3: Connection, event: IpcBaseEvent):
    ipc_data = event.container.ipc_data
    display = ipc_data["output"]

    if event.change == "new":
        window_id = ipc_data["window"]
        window_name = ipc_data["window_properties"]["instance"]
    elif event.change == "move":
        window_id = ipc_data["nodes"][0]["window"]
        window_name = ipc_data["nodes"][0]["window_properties"]["instance"]

    if window_name == "nsxiv":
        subprocess.check_output(
            [
                "xprop",
                "-id",
                f"{window_id}",
                "-format",
                "_NET_WM_NAME",
                "8u",
                "-set",
                "_NET_WM_NAME",
                f"{window_name} :: {display}",
            ],
            text=True,
        )


def main():
    i3 = Connection()

    i3.on(Event.WINDOW_MOVE, on_window_event)
    i3.on(Event.WINDOW_NEW, on_window_event)

    i3.main()


if __name__ == "__main__":
    main()

Subscribing to the Event.WINDOW_MOVE and Event.WINDOW_NEW events, the registered event handler will first retrieve the output name and then the name and ID for the window created or moved. When the window is moved to a new output, the title will be updated.

Title bar of a newly created nsxiv instance reading “nsxiv :: DisplayPort-0”

A simple check for the window name nsxiv, derived from the window instance, will ensure that only this window will be updated.

The script needs to be started with i3 and an exec_always command should be placed to the i3 configuration.

exec_always --no-startup-id ~/.config/i3/scripts/i3-nsxiv.py

For each output the xwallpaper should also be executed during i3 start up.

exec_always --no-startup-id xwallpaper --output DisplayPort-0 --zoom ~/.background_image-DisplayPort-0
exec_always --no-startup-id xwallpaper --output DisplayPort-1 --zoom ~/.background_image-DisplayPort-1

When starting i3, the script should be automatically started.

nsxiv setup and key handler

For nsxiv or sxiv a key handler can be added to set the currently selected image as background image of the output.

The file ~/.config/nsxiv/exec/key-handler should already have some example key bindings. If the file does not exist, check the package contents or the path /usr/share/doc/nsxiv/examples/key-handler and copy it to your home.

The key-handler has two dependencies:

  1. Xwallpaper: which is also needed an mentioned during the i3 configuration.
  2. Jq: to run queries on JSON data.
#!/bin/bash
readonly KEY="$1"
shift

set_background() {
		ln -sf "$1" "$HOME/.background_image-$2"
		xwallpaper --output "$2" --zoom "$HOME/.background_image-$2" &
}

case "$KEY" in
"C-w")
	read -r file
	focused_output=$(i3-msg -t get_workspaces | jq -r '.[] | select(.focused==true).output')
	set_background "$(realpath "$file")" "$focused_output"
	;;
esac

This minimal example should be adapted to fit into the own personal nxsiv configuration. The above script was also shortened a, I have removed unnecessary complexity and default key bindings.

The jq command will extract the current output name for the workspace nsxiv is on.

When the key-handler is configured correctly, hitting Ctrl-x Ctrl-w will update the background image. First the set_background() function will create a symbolic link to the selected image. At last, xwallpaper will update the background of the output.


  1. Simple X Image Viewer, project was archived on Jan 16, 2023. ↩︎

  2. Neo (or New or Not) Simple (or Small or Suckless) X Image Viewer, for of the unmaintained sxiv. ↩︎

  3. IPC interface (interprocess communication) enables data exchange between running processes. ↩︎