#!/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 extra '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) # Really bad - needs work to clean it up (like xmessage) gxmessage -name "$title" -title "$title" -timeout $timeout \ -geometry $geometry -fg DarkGray -bg Black \ -buttons '' "$*" >/dev/null