From 86954fe993ededa227d2c0b499c0b3a86960b37f Mon Sep 17 00:00:00 2001 From: admin Date: Sun, 11 Jan 2015 21:57:40 +0000 Subject: [PATCH] --- Harnessing_DBus.mdwn | 397 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 397 insertions(+) create mode 100644 Harnessing_DBus.mdwn diff --git a/Harnessing_DBus.mdwn b/Harnessing_DBus.mdwn new file mode 100644 index 0000000..049a345 --- /dev/null +++ b/Harnessing_DBus.mdwn @@ -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: + 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. -- 2.39.2