#!/bin/bash
#
# notify_message message
#
# Pop-up a temporary notification of some event, letting the user know what is
# going on.
#
# It should be as small and minimalistic as possible, not grab focus, or
# otherwise interfere with whatever the user is currently doing. They should
# timeout and pop-down (exit) on their own, without user intervention
#
# A great deal of effort is put into ensuring multiple notifications do not
# pop-up on top of each other, but remain neatly ordered in the top-left corner
# of the screen. I hate how many linux pop-up appear at bottom-left on top of
# terminal windows I have at that location!
#
# Note that is may be called without display being set or even active. As such
# the user may not always see messages, and messages may not appear. They are
# purely for background notifications, not error reporting.
#
# OPTIONS
# -p placement method (I have trials many different methods)
# -m method of message pop-up (program to use)
#
###
# Notes...
#
# * This is an example of a very 'in your face' notification
# zenity --info --no-markup --no-wrap --timeout=3 --text="All Done!" &
#
# It is big, centered, with an image, and an 'ok' button, and provides no easy
# way to remove any of that 'fluff'. It is not very minimal at all.
#
# * I generally have my "openbox" window manager remove titlebars and borders
# around any window named "notify" in its configuration, so as to make the
# window popups more minimal...
#
#
#
#
#
#
# no
# all
# yes
#
#
#
#
# * The reliance of DBus Error service is a pain for notifications is unless
# user is using gnome (which is not me!)
#
# * Some sort of exclusive lock is need so only one notification process is
# calculating the placement position, until that position has had its window
# popped up, (filling that 'slot'). A openfile lock does not work as that
# can be duplicated by the background fork of the window!
#
# Anthony Thyssen 2012
#
case $# in
0) echo >&2 "Usage: notify_message Message"
exit 10 ;;
# Must be a fixed title, so a popup position can be found
#1) title="notify_message" ;;
#*) title="$1"; shift ;;
esac
if [ "X$DISPLAY" = "X" ]; then
echo >&2 "$*"; exit 1 # no X window display
fi
# defaults
title="notify" # window name and title (window manager removes decoration)
timeout=30 # how long to notify for (1/2 minute).
geometry_x=5 # where to place message (left margin added)
geometry_y=5 # top most position of notifiers
height=30 # notifier window height (may depend on font and program)
border=4 # difference between reported position and placement height
# Look up desktop margins from window manager (avoid side panels)
margin=$( xprop -root _NET_WORKAREA | tr -dc '0-9 ' | awk '{print $1}' )
(( geometry_x += margin )) # add panel margin to geometry
# Work out placement so as not to block previous notifications.
# There is rarely more than 1 or 2 notifies at any one time,
# before older ones timeout, so this is not a big problem.
# But I have popped up many notifications while debugging a background script.
#
if [[ $1 == -p ]]; then
shift; placement="$1"; shift
else
placement="empty_spot"
fi
case "$placement" in
on_top)
: # fixed position all notifications
# Just put them on top of each other -- Aargh...
;;
count)
# Count the number of notify windows and place in that position down page
# Fails as older notifications exit and count go wrong
height=30 # notifier window height
count=$( wmctrl -l | grep -c ' notify$' )
(( geometry_y += height*count ))
;;
at_end)
# Place new notify on end of the display list.
# Get all Y locations of current windows, and place next one after the largest
# This tends to grow, even though older notifications have finished.
yoffset=$( wmctrl -lG |
awk '$8 == "'$title'" { if( h<$4 ) h=$4 }
END { print h }' )
if [ "X$yoffset" != 'X' ]; then
(( geometry_y = yoffset+height-border ))
fi
;;
empty_spot)
# Place in first position that has no notifier in it.
# Warning: Output from "wmctrl" may not be in correct order,
# so we need to read all notifies first, to find the first free space.
#
# This is the best and most complex solution yet. If a lot of notification
# happen, it could still go right to the bottom of the display, but that
# has never happened yet! And not considered to be a problem.
#
# A bigger problem is if a notification comes in before the last one has
# finished starting up. A lock and wait for window to appear may be needed.
#
# Example:
# for i in {0..10}; do notify_message "**** $i ****"; done
# can cause notifications to appear on top of each other. It gets worse
# if you also background the notification calls, though that is not
# normally needed or done.
#
yoffset=$( wmctrl -lG |
awk '$8 == "'$title'" { # get position, note it, and the largest
i=int($4/'$height'); a[i]++; if(l", i > "/dev/stderr"
}
END { for(i=0;i<=l;i++) if(!a[i]) { print i; exit } # gap
print i; # place notifier here after the last one
}' )
# echo >&2 "===> $yoffset"
(( geometry_y += height*yoffset ))
;;
*)
error_message "notify_message" "Unknown notification positioning method"
exit 10
;;
esac
# FUTURE: start overlaying into a second column, when there are a lot of them
# set the geometry placement of the notifier popup
geometry=+$geometry_x+$geometry_y
#------------------------------------------------------
# Is the command available?
type -t cmd_found >/dev/null ||
cmd_found() { type -t "$1" >/dev/null; }
if [[ $1 = -m ]]; then
shift; method="$1"; shift;
fi
for cmd in xmessage xterm \
zenity kdialog pinentry gxmessage notify-send
do
cmd_found "$cmd" && : ${method:="$cmd"} && break
done
# The following is to prevent errors of the form...
# "Warning: Missing charsets in String to FontSet conversion’.
export LC_ALL=C
case "$method" in
xmessage)
xmessage -name "$title" -title "$title" -timeout $timeout \
-geometry "$geometry" -fg DarkGray -bg Black \
-buttons '' -xrm '*message.borderWidth: 0' \
-xrm '*message.scrollVertical: false' \
-xrm 'notify*.displayList: ' \
-xrm 'notify*baseTranslations: #override : exit(0)' \
"$*" >/dev/null /dev/null is needed to prevent it hanging, if used in pipeline
# EG: This hangs... notify_message "hey hey" | cat >/dev/null
;;
xterm)
# Simple and works well, apart for a 'cursour' on end
width=$(echo -n "$*" | wc -c)
(( width += 1 )) # space for cursour
xterm -name "$title" -title "$title" \
-geometry "${width}x1$geometry" -fg DarkGray -bg Black \
-rightbar +sb +aw -uc -cr Black -b 5 \
-e sh -c "echo -n '$*'; sleep $timeout;" &
;;
# ---- the rest are not small clean message popups ----
zenity)
# Simple 'in your face' notification, centered on screen
zenity --info --no-markup --no-wrap --timeout=$timeout \
--title="$title" --text="$*" &
;;
kdialog)
# fairly clean, but does not timeout (user click required)
# Adding -geometry option turns clean box into a voice bubble! -- Arrgghhh
kdialog --geometry $geometry --passivepopup "$*" $timeout &
;;
pinentry)
# fairly clean but bold popup --- GRABS Keyboard!
{ echo "SETTITLE $title"
echo "SETPROMPT Note..."
echo "SETDESC $0"
echo "SETTIMEOUT $timeout"
echo "MESSAGE";
} | pinentry >/dev/null &
;;
gxmessage)
# Like xmessage above, but needs more work to clean it up
gxmessage -name "$title" -title "$title" -timeout $timeout \
-geometry $geometry -fg DarkGray -bg Black \
-buttons '' "$*" >/dev/null