9

I'm implementing USB plug/unplug notification (here's related question), and I need to execute something like notify-send "device plugged" "My Device Title". The problem is that to make this command work, I should firstly set DISPLAY, like this:

export DISPLAY=":0.0"

And secondly, this command should be called by appropriate user. Say, for user dimon:

su dimon -c "notify-send 'device plugged' 'My Device Title'"

So, I need to get the list of all active X sessions and appropriate users, and call notify-send for each user on his DISPLAY.

I tried to use w for that, example output at Linux Mint 13 MATE:

USER     TTY      FROM              LOGIN@   IDLE   JCPU   PCPU WHAT
dimon    tty8     :0               Sun15    3days  1:38m  1.95s x-session-manager
dimon    pts/0    :0               Sun15    0.00s  0.20s  0.00s tmux

So we have both username and display. I decided to parse it like that:

declare -a logged_users=(`w |grep -vP "^(USER| )" |awk '{if (NF==8){print $1" "$3} else {print $1" :0"}}' |sort |uniq`)

Now, I have array logged_users: [0] contains dimon, and [1] contains :0. This would be great, but unfortunately it works not everywhere. Say, on Ubuntu 12.04 with lightdm we have this w output:

USER     TTY      FROM              LOGIN@   IDLE   JCPU   PCPU WHAT
nui      tty7                      18:22   35:56   1.66s  0.11s gnome-session -
nui      pts/0    :0.0             18:55    5.00s  0.20s  0.00s w

No idea why there's no FROM value for gnome-session. And even worse one, at xubuntu:

USER     TTY      FROM              LOGIN@   IDLE   JCPU   PCPU WHAT
nui      tty7                      15:50   31:07  52.55s  0.13s /bin/sh /etc/xd

No display at all! If other user is logged in, no display is specified for him too (but actually it's :1.0)

So, I'm looking for another approach. I also know we have a list of all active X sessions here: /tmp/.X11-unix , and I can get the list of them like this:

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

But then, I don't know how to retrieve users.

So, how to get the list of all active X sessions and appropriate users?

Dmitry Frank
  • 2,738

2 Answers2

4

This is a solution for the users of local X servers (ignoring local X clients with remote or virtual X servers).

You determine the PID of the display manager (which is the parent PID of Xorg), determine the PIDs of its children and determine the user of all processes which have one of them as parent:

#! /bin/bash
xorg_pid=$(pidof -s /usr/bin/Xorg)
test -n "$xorg_pid" || exit 1
dm_pid=$(ps -eo pid,ppid,args | \
    awk -v xorg_pid=$xorg_pid '$1 == xorg_pid {print $2}')
pid_list="$(ps -eo pid,ppid,cmd | \
    awk -v dm_pid=$dm_pid '$2 == dm_pid {if (matchnr == 0) '\
'{ printf "%s%d ","$2 == ",$1; matchnr++;} '\
'else printf "%s%d ","|| $2 == ",$1;}')"
ps -eo pid,ppid,user,cmd | awk "$pid_list"'{print $3}'
cprn
  • 1,025
Hauke Laging
  • 90,279
  • What if you used pgrep Xorg instead. I have 2 running now so how would this handle multiples? – slm Feb 26 '14 at 19:08
  • @slm You don't happen to have tried the code...? – Hauke Laging Feb 26 '14 at 19:10
  • Of course I did. I'm seeing that it only finds one. – slm Feb 26 '14 at 19:10
  • @slm That's the intention. All instances have the same parent (at least that is the assumption of my script) thus using the first hit only is enough. I have two Xorg instances running, too. Would be difficult to test such code otherwise... – Hauke Laging Feb 26 '14 at 19:13
  • Maybe I'm confused, doesn't he want to see all the usernames that are running X? – slm Feb 26 '14 at 19:22
  • @slm If I run this code then it gives me the user names running X. – Hauke Laging Feb 26 '14 at 19:23
  • Seems like there are issues with it, I just ran it on a CentOS system that I xrdp into and it doesn't show me or another user that are logged into it. – slm Feb 26 '14 at 19:28
  • @slm OK, depends on how you define "X session". I understood that as "connected to a local X server". – Hauke Laging Feb 26 '14 at 19:34
  • No biggie, I was just trying to follow along, I'm curious if it works for the OP. – slm Feb 26 '14 at 19:44
  • Hmm, the code above returns just nothing at my Linux Mint 13. I tried to verify it step-by-step, but I failed on the first one: pidof -s /usr/bin/Xorg returns nothing. And, actually, I'm looking for the way that will work on (almost) all *nix systems, but people complains above that it doesn't work for them, so, it's not an option for me, unfortunately. – Dmitry Frank Feb 27 '14 at 07:16
  • @DmitryFrank I guess there is no "official" way. And as long as nobody else (including yourself) is offering anything you probably should find out why this does not work on other systems (maybe the path is different or it's an upper case lower case thing) and thus help improve this suggestion. – Hauke Laging Feb 27 '14 at 10:12
3

I had the same notify-send problem.

This method (also posted here) uses the environment information which ps e provides. ps e -u username | sed -rn 's/.* DISPLAY=(:[0-9]*).*/\1/p' outputs a list of all the DISPLAY numbers in the environments of all user username's processes.

If there is a root-owned window on your desktop then root will also have some processes with the same DISPLAY number, but otherwise one DISPLAY should only be associated with one user. (Not the other way round - right now I'm logged in at two tty's with two X sessions, so I am using both :0 and :1.)

This code will output all the currently used DISPLAYs (hence all the X sessions) of all the currently logged-in users. (root is skipped)

Note the sudo on the ps command: root permissions are needed to view the environments of other users' processes. Of course if the script is run by root it's unnecessary.

Usernames and display numbers are used as the indices of associative arrays to keep only unique values.

#!/bin/bash

declare -A disps usrs
usrs=()
disps=()

for i in $(users);do
    [[ $i = root ]] && continue # skip root
    usrs[$i]=1
done # unique names

for u in "${!usrs[@]}"; do
    for i in $(sudo ps e -u "$u" | sed -rn 's/.* DISPLAY=(:[0-9]*).*/\1/p');do
        disps[$i]=$u
    done
done

for d in "${!disps[@]}";do
    echo "User: ${disps[$d]}, Display: $d"
    #sudo -u "${disps[$d]}" DISPLAY="$d" notify-send "Title" "Message"
done

The final commented-out line shows how notify-send could be called.

cprn
  • 1,025
johnraff
  • 153