6

I have a script that runs via cron to change the desktop wallpaper. I can get it to work by exporting the DISPLAY variable.

But the issue I am concerned about it that the script should work on all systems. How to find the value of DISPLAY set by the X server without using $DISPLAY?

So how can I find the correct value for DISPLAY programmatically. I can get it to work only when DISPLAY=:1. Setting it to ":0" makes the script exit with

No protocol specified
Cannot open display.

3 Answers3

9

You can't for sure. You have to make assumptions.

Pretend you're cron and you're facing the worst case scenario for a second: there are multiple users logged in, and each user is running multiple X sessions. You'll have to guess the user (easy enough, we're executing their crontab) and one of that user's X sessions.

If we want to assume the user is running one and only one X session from a tty, and get that session's $DISPLAY value we can use w:

USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
user     tty1                      16:32    7:15   0.21s  0.19s -zsh
user     tty2     :1               15:52   48:13   2:17   0.08s /sbin/upstart
user     pts/3    :1               16:19    0.00s  0.66s  0.00s w

For example here I'm logged in on tty1, on tty2 (where I'm running an X session) and on pts/3 (the terminal from which I'm running the command).

With a bit of parsing:

% w $(id -un) | awk 'NF > 7 && $2 ~ /tty[0-9]+/ {print $3; exit}'
:1

So, assuming all the above:

0 0 * * * DISPLAY=$(w $(id -un) | awk 'NF > 7 && $2 ~ /tty[0-9]+/ {print $3; exit}') command

Will make cron execute command with $DISPLAY set to the first X session running in a tty's $DISPLAY value found for the user.

kos
  • 41,268
2

For a more detailed discussion, refer to https://unix.stackexchange.com/questions/17255/is-there-a-command-to-list-all-open-displays-on-a-machine

I will simply list the relevant information from that answer here:

There seems to be two simple ways to find the X server instances running on your system.

  • w:
    The w command lists all the open displays. You can then use awk to filter out the information you need. The values under FROM are the values corresponding to DISPLAY.

    ashhar@xenon:[/tmp/.X11-unix] 
    $ w
    21:18:24 up  3:39,  4 users,  load average: 0.31, 0.27, 0.30
    USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
    ashhar   tty2     :1               17:40    3:39m  6:11   0.08s /usr/bin/dunst
    ashhar   pts/0    :1               17:41    3:36m  0.13s  0.05s vim .i3/config
    ashhar   pts/1    :1               18:07    0.00s  1.44s  0.00s w
    ashhar   pts/2    :1               18:15    9:59   0.79s  0.79s bash
    
  • Local displays correspond to a socket in /tmp/.X11-unix, so we can simply do:

    cd /tmp/.X11-unix && for x in X*; do echo ":${x#X}"; done
    
0

Inspired on Ashhar Hasan's answer, I would use (tested on Ubuntu 23.04):

export DISPLAY=$(
  find /tmp/.X11-unix/ -user $USER -type s -printf "%f" -quit | tr X :
)

By filtering by UID, it will work even in presence of multiple logged-in users.

I have a X11wrap.sh script that lets me run X11 application from system scripts, cron, at etc.

#!/bin/sh

set -eu

export DISPLAY=$( find /tmp/.X11-unix/ -user $USER -type s -printf "%f" -quit | tr X : )

For applications that additionally require DBUS.

export DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$(id -u)/bus

exec "$@"

With it, I can issue the following example, and it will get the X11 system right:

echo X11wrap.sh xeyes | at now