23

Using the command line, I'd like show a notification on every running X display. ( and running console )

Something like:

notify-send-all 'Warning' 'Nuclear launch in 5 minutes, please evacuate'

Is there a program that will do this? If not, can this be implemented with bash?

1
  • 1
    For people coming here years later, there is a simple notify_all function in this answer which works in Ubuntu 16.04, and can be used in scripts started by root.
    – mivk
    Feb 12, 2017 at 11:48

8 Answers 8

18

You can send a message to all consoles with the command wall.

For sending notifications under X there is notify-send which sends a notification to the current user on the current display. (From your question, I guess you already know this one.) You can build upon this with some bash scripting. Basically you have to find out which users are on which X-Displays. Once you got this info you can use notify-send like this:

DISPLAY=:0 sudo -u fschmitt notify-send "Message"

Where fschmitt is the user at display 0. You can parse the output of the "who" command to find all users and their displays. The output looks like this

[edinburgh:~]$ who
markmerk3 tty7         2010-09-23 10:59 (:0)
markmerk3 pts/1        2010-09-30 13:30 (:0.0)
fschmitt pts/2        2010-10-08 11:44 (ip-77-25-137-234.web.vodafone.de)
markmerk3 pts/0        2010-09-29 18:51 (:0.0)
seamonkey pts/6        2010-09-27 15:50 (:1.0)
markmerk3 pts/5        2010-09-27 14:04 (:0.0)
seamonkey tty8         2010-09-27 15:49 (:1)
markmerk3 pts/13       2010-09-28 17:23 (:0.0)
markmerk3 pts/3        2010-10-05 10:40 (:0.0)

You see, there are two users running X sessions, markmerk3 at display 0 and seamonkey at display 1. I think you need to grep for tty[0-9]* then assure that at the end of the line there is (:[0-9.]*) to get rid of console logins and extract the display id from the string between the parentheses.

3
  • 2
    The command who tells you who is logged in and on which X display that login is. You just might have to filter it somewhat.
    – tante
    Oct 8, 2010 at 9:49
  • 1
    While it is probably better to just use a loop in a shell script you could always do something like who | awk '/\(:[0-9]+\)/ {gsub("[:|(|)]","");print "DISPLAY=:"$5 " sudo -u " $1 " notify-send \"Message\""}' | bash. Also, you might want to see unix.stackexchange.com/questions/1596/…
    – Steven D
    Oct 8, 2010 at 15:34
  • It doesn't work: Error spawning command line “dbus-launch --autolaunch=8c77b800842a42468876d979195a5a03 --binary-syntax --close-stderr”: Child process exited with code 1 Sep 14, 2023 at 12:42
17

This thread is a bit old, sorry, but I hope I still can add something useful to the topic. (also Josef Kufner wrote a nice script, it was just a little bit too long for my taste, and it uses PHP).

I also needed a tool as described in the original question (to send a message to all active X-users). And based on the answers here, I wrote this little bash-only script, which searches for active X-users (using who), and then running notify-send for every active user.

And the best: you can use my script exactly like "notify-send", with all of its parameters! ;-)

notify-send-all:

#!/bin/bash
PATH=/usr/bin:/bin

XUSERS=($(who|grep -E "\(:[0-9](\.[0-9])*\)"|awk '{print $1$NF}'|sort -u))
for XUSER in "${XUSERS[@]}"; do
    NAME=(${XUSER/(/ })
    DISPLAY=${NAME[1]/)/}
    DBUS_ADDRESS=unix:path=/run/user/$(id -u ${NAME[0]})/bus
    sudo -u ${NAME[0]} DISPLAY=${DISPLAY} \
                       DBUS_SESSION_BUS_ADDRESS=${DBUS_ADDRESS} \
                       PATH=${PATH} \
                       notify-send "$@"
done

Copy the above code into a file named notify-send-all, make it executable and copy it to /usr/local/bin or /usr/bin (as you like). Then run it e.g. as root in a console session like this:

notify-send-all -t 10000 "Warning" "The hovercraft is full of eels!"

I'm using it several months now, on different machines, and didn't have any problems so far, and I've tested it with MATE and Cinnamon desktops. Also successfully running it within cron and anacron.

I wrote this script for/under Arch Linux, so please tell me if you're having problems on another Linux distributions or desktops.

10
  • |egrep ?? is egrep a command ?
    – Sw0ut
    Apr 21, 2017 at 9:06
  • @Sw0ut, egrep is indeed a command. But in the man page of grep(1) says that egrep, fgrep and rgrep are deprecated, and the use of their equivalent forms "grep -E", "grep -F" and "grep -r" is recommended.
    – rsuarez
    May 21, 2017 at 16:49
  • 1
    Instead of awk '{print $1$5}' it is better to use awk '{print $1$NF}', so that it doesn't break on some locales where date is formatted with spaces (e.g. Jun 3 instead of 2017-06-03). Here is also a version to notify specific user instead of all users: gist.github.com/shvchk/ba2f0da49bf2f571d6bf606d96f289d7
    – Shevchuk
    Jun 3, 2017 at 19:11
  • 1
    Works splendidly on Ubuntu after using grep -E and adding /bin to the path (see the edit). Feel free to revert if you object
    – serv-inc
    Oct 18, 2018 at 8:14
  • I bodged in some help text: if [ $# -eq 0 ] ; then echo "No summary specified"; notify-send --help | sed -e 's/notify-send/&-all/g' -e 's/create a notification/& to all users/g'; exit 0; fi
    – iheggie
    Feb 11, 2020 at 0:09
4

In Ubuntu 16.04, I wanted notifications from a script running as root from crontab. After setting the environment variables, sudo -u $user didn't work for some reason, but sh -c "..." $user does work.

So I now use this function:

notify_all() {
    local title=$1
    local msg=$2

    who | awk '{print $1, $NF}' | tr -d "()" |
    while read u d; do
        id=$(id -u $u)
        . /run/user/$id/dbus-session
        export DBUS_SESSION_BUS_ADDRESS
        export DISPLAY=$d
        su $u -c "/usr/bin/notify-send '$title' '$msg'"
    done 
}

How to find the DBUS_SESSION_BUS_ADDRESS variable probably depends on your distribution. In Ubuntu 16.04, it is in /run/user/$UID/dbus-session, which can simply be sourced. id -u is used in the function above to get the UID from the username returned by who.

3
  • How to use it? Can you help me? Apr 1, 2017 at 17:09
  • This works for me while the other didn't!
    – Student
    Mar 26, 2020 at 14:40
  • The method of using /run/user/$UID/dbus-session stopped working in Ubuntu 20.04.
    – hackerb9
    Jun 8, 2023 at 0:23
3

I needed this too for some system-wide notifications. Here is my solution. It scans /proc to find all session busses and then it executes notify-send on each of it (once per bus). All arguments are passed unchanged to real notify-send.

#!/bin/bash

/bin/grep -sozZe '^DBUS_SESSION_BUS_ADDRESS=[a-zA-Z0-9:=,/-]*$' /proc/*/environ \
| /usr/bin/php -r '
        $busses = array();
        array_shift($argv);
        while($ln = fgets(STDIN)) {
                list($f, $env) = explode("\0", $ln, 2);
                if (file_exists($f)) {
                        $user = fileowner($f);
                        $busses[$user][trim($env)] = true;
                }
        }
        foreach ($busses as $user => $user_busses) {
                foreach ($user_busses as $env => $true) {
                        if (pcntl_fork()) {
                                posix_seteuid($user);
                                $env_array = array("DBUS_SESSION_BUS_ADDRESS" => preg_replace("/^DBUS_SESSION_BUS_ADDRESS=/", "", $env));
                                pcntl_exec("/usr/bin/notify-send", $argv, $env_array);
                        }
                }
        }
' -- "$@"
1

Here's an update of Andy's script: The way it determined the DBUS_SESSION_BUS_ADDRESS does not work on Centos 7. Also the who command did not list some sessions for some reason, so I parse the ps aux output instead. This script assumes users are logged in using X2GO (nxagent), but it should be simple to adjust for other cases.

#!/bin/bash
PATH=/usr/bin:/bin
NOTIFY_ARGS='-u critical "Shutdown notice" "THE SYSTEM IS GOING DOWN TODAY AT 23:00.\nWe recommend you to save your work in time\!" -i /usr/share/icons/Adwaita/32x32/devices/computer.png -t 28800000'

function extract_displays {
    local processes=$1
    processes=$(printf '%s\n' "$processes" | grep -P "nxagent.+:\d+")
    ids=$(printf '%s\n' "$processes" | grep -oP "\W\K:(\d)+")
    echo $ids
}


function find_dbus_address {
    local name=$1
    PID=$(pgrep 'mate-session' -u $name)
    if [ -z "$PID" ]; then
        PID=$(pgrep 'gnome-session' -u $name)
    fi
    if [ -z "$PID" ]; then
        PID=$(pgrep 'xfce4-session' -u $name)
    fi

    exp=$(cat /proc/$PID/environ | grep -z "^DBUS_SESSION_BUS_ADDRESS=")
    echo $exp
}

PROCESSES=$(ps aux)
DISPLAY_IDS=$(extract_displays "$PROCESSES")
echo "Found the following DISPLAYS: $DISPLAY_IDS"
for DISPLAY in $DISPLAY_IDS; do
    NAME=$(printf '%s\n' "$PROCESSES" | grep -P "nxagent.+$DISPLAY" | cut -f1 -d ' ')
    DBUS_ADDRESS=$(find_dbus_address $NAME)
    echo "Sending message to NAME=$NAME DISPLAY=$DISPLAY DBUS_ADDRESS=$DBUS_ADDRESS"
    echo "NOTIFY_ARGS=$NOTIFY_ARGS"
    eval sudo -u ${NAME} DISPLAY=${DISPLAY} ${DBUS_ADDRESS} PATH=${PATH} notify-send $NOTIFY_ARGS
done
1

Too low rep to comment, so here goes a separate answer.

My company has very strict rules enforced and passing shellcheck is one of them. I've adapted Andy's answer a bit and wanted to share it in case anybody else has to address SC2086 and SC2206.

XUSERS=("$(who | grep -E "\(:[0-9](\.[0-9])*\)" | awk '{print $1$NF}' | sort -u)" )
for XUSER in "${XUSERS[@]}"
do
    NAME="${XUSER%%(*}"
    DISPLAY="${XUSER#*\(}"
    DISPLAY="${DISPLAY%*)}"
    DBUS_ADDRESS=unix:path=/run/user/$(id -u "${NAME[0]}")/bus
    sudo -u "${NAME[0]}" DISPLAY="${DISPLAY}" \
         DBUS_SESSION_BUS_ADDRESS="${DBUS_ADDRESS}" \
         PATH="${PATH}" \
         notify-send "$@"
done
1

Solution: use my notify-send-all script

#!/bin/bash
# notify-send-all

PATH=/usr/bin:/bin

send-to() {
    local name busroute
    name="$1";  shift
    busroute="/run/user/$(id -u "$name")/bus"  ||  return 1
    sudo -u "$name" \
        PATH="$PATH" \
        DBUS_SESSION_BUS_ADDRESS="unix:path=$busroute" \
        -- \
        notify-send "$@"  2>&1  |  sed "s/^/$name\t/" 
}

send-all() {
    for name in $(who | cut -f1 -d" " | sort -u)
    do
        send-to "$name" "$@" &
    done
    wait
}

sudo --validate
send-all "$@"

Features

  • Works in X and Wayland (as of 2023).
  • Runs asynchronously, does not block when libnotify waits for a timeout or response.
  • Handles --action in a reasonable way; prefixes output with the username and then a TAB character.
  • Does not rely on DISPLAY to send the notification.

Example usage

notify-send-all "My hovercraft is full of eels"

More complex example:

notify-send-all --urgency=critical \
                --action=run="Run away!" \
                --action=hide="Hide!" \
                "Warning: Nuclear launch imminent"

Example output:

    andy        run
    circus      hide
    hackerb9    run

Download the script

A more full featured version of this script is available at https://github.com/hackerb9/notify-send-all/ . The main improvements are help, error handling, and ability to send to a specific user.

0
users=$(who | awk '{print $1}')

for user in $users<br>
do
        DISPLAY=:0 sudo -u $user notify-send "hello!!"
done

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .