]> www.opopop.net Git - reminder/blobdiff - Harnessing_DBus.mdwn
(no commit message)
[reminder] / Harnessing_DBus.mdwn
diff --git a/Harnessing_DBus.mdwn b/Harnessing_DBus.mdwn
new file mode 100644 (file)
index 0000000..049a345
--- /dev/null
@@ -0,0 +1,397 @@
+# 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:
+<http://www.freedesktop.org/wiki/Software/dbus/> 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: <http://www.phoronix.com/scan.php?page=news_item&px=MTQ0NDg>
+
+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.