# Harnessing DBus
## Introduction
I will discuss here how to run some script when specific DBus events
happen. The subject has already been covered by various tutorials, see
for instance:
[how-do-i-run-a-script-on-a-dbus-signal](http://ubuntu.aspcode.net/view/635400140124705175242338/how-do-i-run-a-script-on-a-dbus-signal)
or
[using-dbus-to-take-action-on-a-usb-storage-device](http://www.danplanet.com/blog/2007/11/11/using-dbus-to-take-action-on-a-usb-storage-device/)
but most of them are of the recipe type or need programming skills.
I will explore a bit more deeply the subject here, using nothing more
than shell scripts. I hope you will learn in the process useful stuff
about how a modern Linux system works under the hood. Even if projects
like
[kdbus](http://www.linuxuser.co.uk/features/kernel-3-17-and-kdbus-the-kernel-column)
take ground, the method described below will stay valid with only
minor adjustements needed.
More specifically, I will try to **trigger a script as soon as you
switch [virtual
consoles](https://en.wikipedia.org/wiki/Linux_Virtual_Consoles)**.
I become interested in that subject because, due to some faulty video
hardware or driver or what else, the
[gamma](http://www.cambridgeincolour.com/tutorials/gamma-correction.htm)
tuning of my latest screen becomes always too high when switching
virtual consoles. The problem is not really visible in text mode, but
it's obvious when going from text mode to graphic mode, or switching
between two X sessions. No screen or video driver settings
help. Manually issuing
[xgamma](http://www.x.org/releases/X11R7.7/doc/man/man1/xgamma.1.xhtml)
like that:
xgamma -gamma 0.5
will solve the issue, but a way to automate that would be welcome. You
probably don't have that specific problem, so in the script I will
describe below you should perform some other useful action you may see
fit: popup some message, play a tune...
As a prerequisite, you are supposed to be a bit familiar with DBus
concepts. If not, look first here:
and the tutorial
links there. If after reading this, you still have only an incomplete
picture of DBus, don't worry: fuzzy specifications and poor
documentation can be observed in most system tools in Linux today, and
courtesy of systemd, that trend is not showing any sign of going away.
I will use these DBus tools: dbus-monitor and dbus-send. You quite
certainly have them and their (terse) man pages; if not, they can be
found for instance in Debian package dbus.
## Details of the problem
I have hinted above that DBus will be used in the solution to our
problem. But a DBus daemon will run only if you have some advanced X
environnement, which excludes pure startx/xinit environnement from the
last millenium. So if you have some modern destkop environnement (be it
a bare one like XFCE or a bloated one like Gnome) you can use the
method below.
In addition, a DBus session alone with no services will not allow us
to catch interesting events (a.k.a. DBus signals send by processes or
calls made by processes), and here we will need the DBus aware
application [ConsoleKit or
systemd-logind](http://www.freedesktop.org/wiki/Software/ConsoleKit/)
running. No problem, it runs on every modern destkop environnement.
It should be noted that switching virtual consoles happens in 4 cases:
1. At boot time when you are starting your X server: this triggers a
switch to the virtual console (often /dev/tty7) where the X server
runs; and then the gamma problem happens
2. When switching to a text console; going back to the X
server screen will trigger the gamma problem
3. When switching between several X sessions (if the first X session
starts in /dev/tty7 as said above, the second one will use the virtual
console /dev/tty8 etc...); the new visible X session will exhibit the
gamma problem
4. When resuming from suspended state; not sure if all video hardware
goes first to text mode when resuming, but my hardware does, and the
gamma problem becomes visible when the switch to graphical mode is
triggered
Some remarks about the 4 cases above:
* Case 1: for the boot time case, putting in your $HOME/.xsessionrc that line:
xgamma -gamma 0.5
is enough. But won't work for cases 2, 3 and 4. In a multiuser
environment, it doesn't scale well.
* Case 2 and 3: you can switch to a given virtual console using the command
[chvt](http://nixdoc.net/man-pages/Linux/chvt.1.html), or with some
[keyboard
combination](http://www.tldp.org/HOWTO/Keyboard-and-Console-HOWTO-7.html).
* Case 4: the suspend/hibernate system offers some hook points where
commands can be run when resuming. But you must be sure when running
that command that you are indeed in graphical mode: if not, the xgamma
command won't work.
But there is more. In a multiuser environment, you have to find out
the correct X display (the one active and visible when suspended), and
feed it to xgamma as an argument. So the correct command to run in my
case is actually:
XAUTHORITY=$USERHOME/.Xauthority xgamma -gamma 0.5 -display $DISPLAY
where you cannot assume that DISPLAY has value :0. Here USERHOME is
the home directory of the user logged in the X session of the active
display, and XAUTHORITY will allow root (which is the user running
the resume script) to access the credentials to connect to that
display.
And no, issuing xgamma on a non-visible and non-active display will not
restore gamma for the active display.
Actually, there is no easy way to find out the active display and it's
corresponding session user while staying inside traditional X and the
suspend/hibernate system. A workaround would be to loop over all
active X displays; this is feasable (they can be found in
/tmp/.X11-unix). You will also need to loop over all possible
$HOME/.Xauthority, which is easy because you are root.
At that point I hope you are convinced that that solution is clunky.
I will present something more general below.
## Looking for interesting DBus events when switching virtual consoles
Let's run dbus-monitor on the system DBus daemon (you need to do that
as root in a terminal), and let's observe what happens when we switch
to a different virtual console and back. The output looks somethink
like (# being the root shell prompt):
# dbus-monitor --system
signal sender=org.freedesktop.DBus -> dest=:1.67 serial=2 path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=NameAcquired
string ":1.67"
Now switch to a text console with the key combo Ctrl+Alt+F1, and back
to your session (supposed running at /dev/tty7) with the combo
Alt+F7. Something happened, we now have on the terminal:
# dbus-monitor --system
signal sender=org.freedesktop.DBus -> dest=:1.67 serial=2 path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=NameAcquired
string ":1.67"
signal sender=:1.7 -> dest=(null destination) serial=418 path=/org/freedesktop/ConsoleKit/Session4; interface=org.freedesktop.ConsoleKit.Session; member=ActiveChanged
boolean false
signal sender=:1.7 -> dest=(null destination) serial=419 path=/org/freedesktop/ConsoleKit/Seat1; interface=org.freedesktop.ConsoleKit.Seat; member=ActiveSessionChanged
string ""
signal sender=:1.6 -> dest=(null destination) serial=195 path=/org/freedesktop/PolicyKit1/Authority; interface=org.freedesktop.PolicyKit1.Authority; member=Changed
signal sender=:1.6 -> dest=(null destination) serial=196 path=/org/freedesktop/PolicyKit1/Authority; interface=org.freedesktop.PolicyKit1.Authority; member=Changed
signal sender=:1.7 -> dest=(null destination) serial=430 path=/org/freedesktop/ConsoleKit/Session4; interface=org.freedesktop.ConsoleKit.Session; member=ActiveChanged
boolean true
signal sender=:1.7 -> dest=(null destination) serial=431 path=/org/freedesktop/ConsoleKit/Seat1; interface=org.freedesktop.ConsoleKit.Seat; member=ActiveSessionChanged
string "/org/freedesktop/ConsoleKit/Session4"
signal sender=:1.6 -> dest=(null destination) serial=203 path=/org/freedesktop/PolicyKit1/Authority; interface=org.freedesktop.PolicyKit1.Authority; member=Changed
signal sender=:1.6 -> dest=(null destination) serial=204 path=/org/freedesktop/PolicyKit1/Authority; interface=org.freedesktop.PolicyKit1.Authority; member=Changed
*Disclaimer: I will handle here the case where you use ConsoleKit. If
you use a very recent desktop, you may be using the systemd equivalent
of ConsoleKit called systemd-logind, where the messages below look a
bit different, with interfaces like: org.freedesktop.login1 and paths
like: /org/freedesktop/login1/session. I have so far only virtual
machines running with systemd, so unable to explore console switching
on them; I will update that page as soon as possible to cover the
systemd case.*
To not be flooded with messages, we can print only messages related to
the org.freedesktop.ConsoleKit.Session interface by adding a watch
expression argument to dbus-monitor. Redoing the previous experiment,
we get now something much more short:
# dbus-monitor --system interface=org.freedesktop.ConsoleKit.Session
signal sender=org.freedesktop.DBus -> dest=:1.68 serial=2 path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=NameAcquired
string ":1.68"
signal sender=:1.7 -> dest=(null destination) serial=450 path=/org/freedesktop/ConsoleKit/Session4; interface=org.freedesktop.ConsoleKit.Session; member=ActiveChanged
boolean false
signal sender=:1.7 -> dest=(null destination) serial=462 path=/org/freedesktop/ConsoleKit/Session4; interface=org.freedesktop.ConsoleKit.Session; member=ActiveChanged
boolean true
There are three 2 lines messages above. The first one is noise due to
dbus-monitore registering on the DBus daemon. The second one signals
to the world that your current session (called Session4) is no more
active (boolean false) because no more visible as you switched to
/dev/tty1. And the third one says it's visible and active again
(boolean true).
You will be able to verify that the same kind of message is triggered
when switching to another X session runningfor instance on /dev/tty8,
then going back:
# dbus-monitor --system interface=org.freedesktop.ConsoleKit.Session
signal sender=org.freedesktop.DBus -> dest=:1.82 serial=2 path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=NameAcquired
string ":1.82"
signal sender=:1.7 -> dest=(null destination) serial=850 path=/org/freedesktop/ConsoleKit/Session4; interface=org.freedesktop.ConsoleKit.Session; member=ActiveChanged
boolean false
signal sender=:1.7 -> dest=(null destination) serial=851 path=/org/freedesktop/ConsoleKit/Session6; interface=org.freedesktop.ConsoleKit.Session; member=ActiveChanged
boolean true
signal sender=:1.7 -> dest=(null destination) serial=873 path=/org/freedesktop/ConsoleKit/Session6; interface=org.freedesktop.ConsoleKit.Session; member=ActiveChanged
boolean false
signal sender=:1.7 -> dest=(null destination) serial=874 path=/org/freedesktop/ConsoleKit/Session4; interface=org.freedesktop.ConsoleKit.Session; member=ActiveChanged
boolean true
Here the second X session is called Session6. You should now
understand that the third and fourth messages happen when you hit
Ctrl+Alt+F8: Session4 signals it becomes inactive because invisible,
then Session6 becomes visible and active. And the last two 2 lines
messages happen when you hit Ctrl+Alt+F7, always ending with Session4
signaling it's visible and active again.
So our solution would be to run our xgamma command as soon as that
last DBus signal is caught.
## Looking for interesting DBus events when resuming, and reacting on
these events
But wait, what about suspend/resume? Lets run dbus-monitor again and
see what happens when you suspend, the resume:
# dbus-monitor --system interface=org.freedesktop.ConsoleKit.Session
signal sender=org.freedesktop.DBus -> dest=:1.83 serial=2 path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=NameAcquired
string ":1.83"
signal sender=:1.7 -> dest=(null destination) serial=948 path=/org/freedesktop/ConsoleKit/Session4; interface=org.freedesktop.ConsoleKit.Session; member=IdleHintChanged
boolean true
signal sender=:1.7 -> dest=(null destination) serial=962 path=/org/freedesktop/ConsoleKit/Session4; interface=org.freedesktop.ConsoleKit.Session; member=IdleHintChanged
boolean false
That's even simpler, the second signal says we are going idle
a.k.a. suspend state (boolean true) and then going live again (not
idle, so boolean false).
So we have two signals to monitor: ActiveChanged for direct console
switching (and boot) and IdleHintChanged for console switching
triggered by resume.
To be completely sure to work only on those signals, we will use two
specific watch expressions as arguments to dbus-monitor; several such
watch arguments are or-ed, that is not obvious from dbus-monitor man
page. Our final dbus-monitor becomes then:
dbus-monitor --system "interface='org.freedesktop.ConsoleKit.Session',member='ActiveChanged'" "interface='org.freedesktop.ConsoleKit.Session',member='IdleHintChanged'"
Following that [tutorial
method](http://ubuntu.aspcode.net/view/635400140124705175242338/how-do-i-run-a-script-on-a-dbus-signal),
plus some bashism like =~ and \[[ ]], we end-up with the following
script, which will also use logger to trace what happens in syslog:
#!/bin/bash
interface=org.freedesktop.ConsoleKit.Session
member=ActiveChanged
resume=IdleHintChanged
# beware the pipe | at end of next line
dbus-monitor --system "interface='$interface',member='$member'" "interface='$interface',member='$resume'" |
while read -r line; do # we loop over all messages; get first line of message in variable 'line'
# reading second line of message in variables 'type' and 'value'
read type value
# 'ret' is 0 if 'line' contains string $member
\[[ "$line" =~ "$member" ]]; ret=$?
# test ActiveChanged to be true
if [ "$ret" = "0" -a "$type" = "boolean" -a "$value" = "true" ]; then
echo Boot or virtual console switching case: $line |& logger
xgamma -gamma 0.5 |& logger
fi
# 'ret' is 0 if 'line' contains string $resume
\[[ "$line" =~ "$resume" ]]; ret=$?
# test IdleHintChanged to be false
if [ "$ret" = "0" -a "$type" = "boolean" -a "$value" = "false" ]; then
echo Resume case: $line |& logger
xgamma -gamma 0.5 |& logger
fi
done
If you start this script in background as root while in your X
session, things will work fine. But not in X sessions owned by other
users: because, as said above, you will be running it with the DISPLAY
and XAUTHORITY of your session. But DBus can help you find out these
values as we will see now.
## Obtaining information through DBus
Let's interrogate the ConsoleKit session object for our current X
session, which is in the example above has path
/org/freedesktop/ConsoleKit/Session4. We will use for that dbus-send,
and the requests and answers will look like this:
# dbus-send --system --print-reply --type=method_call --dest=org.freedesktop.ConsoleKit "/org/freedesktop/ConsoleKit/Session4" org.freedesktop.ConsoleKit.Session.GetUser
method return sender=:1.7 -> dest=:1.108 reply_serial=2
uint32 1000
# dbus-send --system --print-reply --type=method_call --dest=org.freedesktop.ConsoleKit "/org/freedesktop/ConsoleKit/Session4" org.freedesktop.ConsoleKit.Session.GetX11Display
method return sender=:1.7 -> dest=:1.109 reply_serial=2
string ":0"
It should be obvious that the first dbus-send command obtains the UID
of the X session owner (1000), and the second one get's the X display,
here ":0".
So our final version of the script becomes:
#!/bin/bash
interface=org.freedesktop.ConsoleKit.Session
member=ActiveChanged
resume=IdleHintChanged
run() {
DISPLAY=`dbus-send --system --print-reply --type=method_call --dest=org.freedesktop.ConsoleKit $object_path ${interface}.GetX11Display | grep string | cut -d ' ' -f 5 | cut -d '"' -f 2`
XUID=`dbus-send --system --print-reply --type=method_call --dest=org.freedesktop.ConsoleKit $object_path ${interface}.GetUser | grep uint32 | cut -d ' ' -f 5`
if [ "$DISPLAY" = "" -o "$XUID" = "" ]; then
echo DISPLAY=$DISPLAY and user-uid=$XUID not both non empty, impossible to run xgamma |& logger -t logger
else
XAUTHORITY=`grep :x:${XUID}: /etc/passwd | cut -d ':' -f 6`/.Xauthority
# we don't run any X app if XAUTHORITY doesn't exist or is empty
if [ -f $XAUTHORITY -a "`wc -c $XAUTHORITY | cut -d ' ' -f 1`" != "0" ]; then
echo Running "XAUTHORITY=$XAUTHORITY xgamma -gamma 0.5 -display $DISPLAY" |& logger -t logger
XAUTHORITY=$XAUTHORITY xgamma -gamma 0.5 -display $DISPLAY |& logger -t logger
# in case your gamma stays ok, you can still test this script by uncommenting the next line
#XAUTHORITY=$XAUTHORITY xeyes -display $DISPLAY &
else
echo $XAUTHORITY file non-existent or empty, impossible to run xgamma |& logger -t logger
fi
fi
}
# beware the pipe | at end of next line
dbus-monitor --system "interface='$interface',member='$member'" "interface='$interface',member='$resume'" |
while read -r line; do # we loop over all messages; get first line of message in variable 'line'
# reading second line of message in variables 'type' and 'value'
read type value
# 'ret' is 0 if 'line' contains string $member
\[[ "$line" =~ "$member" ]]; ret=$?
# test ActiveChanged to be true
if [ "$ret" = "0" -a "$type" = "boolean" -a "$value" = "true" ]; then
object_path=`echo "$line" | cut -d ' ' -f 7 | cut -d '=' -f 2 | cut -d ';' -f 1`
echo Boot or virtual console switching case for session $object_path |& logger -t logger
run
fi
# 'ret' is 0 if 'line' contains string $resume
\[[ "$line" =~ "$resume" ]]; ret=$?
# test IdleHintChanged to be false
if [ "$ret" = "0" -a "$type" = "boolean" -a "$value" = "false" ]; then
object_path=`echo "$line" | cut -d ' ' -f 7 | cut -d '=' -f 2 | cut -d ';' -f 1`
echo Resume case for session $object_path |& logger -t logger
run
fi
done
We finally have to start the script at boot time, which has to be done
after the system DBus daemon is started, which in current stable
Debian is done through /etc/init.d/dbus. I suggest to simply drop the
path of the above script there, backgrounded with &.
If you do that, reboot and look at /var/log/syslog, you will notice
that the first call to xgamma (and xeyes) happens under the desktop
manager user (this is for sure the case for lightdm which I use).
Then, when you have logged in, xgamma (and xeyes) won't run because
XAUTHORITY is empty. I suspect this is due to a lack of synchronicity
between setting the DISPLAY in PolicyKit and setting it fully (with
valid XAUTHORITY) in X. The $HOME/.xsessionrc trick discussed at the
beginning above will save that case for you.
After this first logon, things work as expected on resume and console
switch. Notice that the screeensaver authorization screen never shows
xeyes, if you have uncommented that line in the script.
## Conclusion
We have gone a long way to finally devise a general solution to our
problem of running a script when changing virtual consoles. It is
fundamentally based on ConsoleKit or systemd-logind tracking these
virtual consoles changes, and announcing them on the DBus.
An improvement to the script would be to write it using a language
with a DBus binding, like Python.
## Other interesting links:
An in-depth view of the virtual consoles:
A useful exploratory DBus tool is
[d-feet](http://live.gnome.org/DFeet/). A d-feet Debian
package exists.
Programmers may be interested in [gdbus](http://dev.man-online.org/man1/gdbus/) available in Debian package
libglib2.0-dev.
[userland-dbus-event-manager](https://github.com/airtonix/userland-dbus-event-manager)
is a general tool to react on DBus events.