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.
  • If the server is listening on TCP DISPLAY = portnumber - 6000 But ... I doubt cron is smart enough to get this to check for a free port. So you probably need a script to set it. – Rinzwind Mar 11 '16 at 15:27
  • UNIX/Linux is an inherently multi-user, multi-session environment: one of the Ubuntu systems I'm currently logged in to has X servers running on displays :0, :2, and :5 (and often has more). So first you need to think about what you mean by "the X server" – steeldriver Mar 11 '16 at 15:28
  • 1
    This seems a good starting point: http://unix.stackexchange.com/questions/17255/is-there-a-command-to-list-all-open-displays-on-a-machine – Rinzwind Mar 11 '16 at 15:29
  • By the X server I mean the WM and/or the DM instance. I need to change the wallpaper for the desktop. I guess I can use a loop and run the commands on all DISPLAY from 0 through 10? – Ashhar Hasan Mar 11 '16 at 15:33
  • Thanks @Rinzwind. I found the answer and will be adding an answer here after proper testing. – Ashhar Hasan Mar 11 '16 at 15:36
  • @JacobVlijm I'm using different tools for different DM's. I have got gsettings worked out. I was having the problem with feh on i3. – Ashhar Hasan Mar 11 '16 at 15:46
  • Ah, I see, will remove my vote :) – Jacob Vlijm Mar 11 '16 at 15:46

3 Answers3

8

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
  • 35,891
  • 1
    Well, I guess cron is too low level stuff to be handling wallpapers then. Thanks for the great answer. – Ashhar Hasan Mar 11 '16 at 18:00
  • 2
    @AshharHasan I think so as well. Perhaps you should demonize a script and let it do the task, it looks both easier and more reliable. – kos Mar 11 '16 at 18:18
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