How can I run a local command (to run a script) on (just before) log out of a Unity session?

Solution 1:

Basic theory

As you may or may not know , a lot of Unity actions are implemented using dbus . The big convenience about dbus is that you can perform lots of root-required actions by calling appropriate methods in command-line without need of having admin account, for instance shutting down your system.

Specifically in our case here we are interested in com.canonical.Unity service, /com/canonical/Unity/Session path. You can list information about it with qdbus command. Particularly we're interested in signals that have Requested in them.

$ qdbus com.canonical.Unity /com/canonical/Unity/Session | grep 'signal.*Requested.*'                             
signal void com.canonical.Unity.Session.LockRequested()
signal void com.canonical.Unity.Session.LogoutRequested(bool have_inhibitors)
signal void com.canonical.Unity.Session.RebootRequested(bool have_inhibitors)
signal void com.canonical.Unity.Session.ShutdownRequested(bool have_inhibitors)
signal void com.canonical.Unity.Session.UnlockRequested()

These signals appear on the bus when user clicks any of the shutdown, lock, logout, or reboot items in the drop-down session menu on the right side of the Unity's top bar. So what we need to do is this:

  • monitor the bus for those signals to appear.
  • once they appear - perform a certain action.

Of course , the big disclamer is that if a user uses sudo shutdown -P now or gnome-session-quit , that won't be told to the dbus , so whatever action is wanted won't be done.

Monitoring the dbus

Conveniently there is a command for that, dbus-monitor and we can filter its output using a set of options, and perform an action on it using while read do … done structure

Bellow is an example of a simple monitoring script.

dbus-monitor --profile "interface='com.canonical.Unity.Session',type=signal" | \
while read -r line;
do
  echo "$line" 
  sleep 0.25
done

What it does is merely monitor the bus for signals , and echo them back to stdout if they appear; of course echo could be any command. Run it , and try clicking on each item in the session menu that should generate signals, and you should see something like this:

$ ./logout_monitor.sh                                                                                             
sig 1458319246  172587  2   /org/freedesktop/DBus   org.freedesktop.DBus    NameAcquired
sig 1458319251  213766  5496    /com/canonical/Unity/Session    com.canonical.Unity.Session RebootRequested
sig 1458319265  62555   5525    /com/canonical/Unity/Session    com.canonical.Unity.Session LogoutRequested
sig 1458319271  856770  5555    /com/canonical/Unity/Session    com.canonical.Unity.Session LockRequested
sig 1458319273  223940  5564    /com/canonical/Unity/Session    com.canonical.Unity.Session Locked
sig 1458319276  991413  5604    /com/canonical/Unity/Session    com.canonical.Unity.Session UnlockRequested
sig 1458319278  3443    5606    /com/canonical/Unity/Session    com.canonical.Unity.Session Unlocked

So now we have a monitoring function that can perform an action once an interrupt occurs. Now , to address the specific question that you were trying to solve , your script is taking snapshots of the icons on the desktop. There's no harm done if we take snapshot each time a signal occurs - it doesn't have to be reboot or shutdown specifically. So if we monitor that specific Unity interface for signals and take snapshots upon each signal's arrival, we can simplify the scripting.

Interrupt-driven script

Bellow you can see an interrupt-driven script which will monitor the bus for signals (ignoring the first one ) and if we have a signal coming in , then call an interrupt function (which in your case will be the icon snapshot script that you've written).

#!/bin/bash

main()
{
  ARG="cow"
  dbus-monitor --profile "interface='com.canonical.Unity.Session',type=signal" | 
  while read -r line;
  do
    grep -q '.*NameAcquired.*' <<< "$line"  && continue  #  Ignore that first line
    if [ -n "$line"  ];then
       interrupt $ARG # call your python snapshot script here
    fi
  done
}

interrupt()
{
  echo 'Old McDonald had a ' $ARG ' e-i-e-i-o '
}

main

NOTE: Dbus relies on having GUI session , since (as far as I know) it starts with Unity/Gnome login. Older environments such as blackbox and openbox, as well as TTY environments do not start dbus session, hence that script will puke if you try to launch it in ~/.profile (I had one user who tried to do something like that and he got into trouble). Start such script using Unity's or Gnome's Startup Applications program.

Going beyond this script

  • Dbus has a lot of API's including python
  • I prefer using qdbus because it's simple, but one could always use dbus-send . Examples
  • The interrupt function can be anything, even other dbus methods. There's a lot to look into.

Trivia: I've used this same method for preventing shutdown if apt is running