]> www.opopop.net Git - reminder/blob - Harnessing_DBus.mdwn
attachment upload
[reminder] / Harnessing_DBus.mdwn
1 # Harnessing DBus
2
3 ## Introduction
4
5 I will discuss here how to run some script when specific DBus events
6 happen. The subject has already been covered by various tutorials, see
7 for instance:
8 [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)
9 or
10 [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/)
11 but most of them are of the recipe type or need programming skills.  
12
13 I will explore a bit more deeply the subject here, using nothing more
14 than shell scripts. I hope you will learn in the process useful stuff
15 about how a modern Linux system works under the hood. Even if projects
16 like
17 [kdbus](http://www.linuxuser.co.uk/features/kernel-3-17-and-kdbus-the-kernel-column)
18 take ground, the method described below will stay valid with only
19 minor adjustements needed.
20
21 More specifically, I will try to **trigger a script as soon as you
22 switch [virtual
23 consoles](https://en.wikipedia.org/wiki/Linux_Virtual_Consoles)**.
24
25 I become interested in that subject because, due to some faulty video
26 hardware or driver or what else, the
27 [gamma](http://www.cambridgeincolour.com/tutorials/gamma-correction.htm)
28 tuning of my latest screen becomes always too high when switching
29 virtual consoles. The problem is not really visible in text mode, but
30 it's obvious when going from text mode to graphic mode, or switching
31 between two X sessions. No screen or video driver settings
32 help. Manually issuing
33 [xgamma](http://www.x.org/releases/X11R7.7/doc/man/man1/xgamma.1.xhtml)
34 like that:
35
36     xgamma -gamma 0.5 
37
38 will solve the issue, but a way to automate that would be welcome. You
39 probably don't have that specific problem, so in the script I will
40 describe below you should perform some other useful action you may see
41 fit: popup some message, play a tune...
42
43 As a prerequisite, you are supposed to be a bit familiar with DBus
44 concepts. If not, look first here:
45 <http://www.freedesktop.org/wiki/Software/dbus/> and the tutorial
46 links there. If after reading this, you still have only an incomplete
47 picture of DBus, don't worry: fuzzy specifications and poor
48 documentation can be observed in most system tools in Linux today, and
49 courtesy of systemd, that trend is not showing any sign of going away.
50
51 I will use these DBus tools: dbus-monitor and dbus-send. You quite
52 certainly have them and their (terse) man pages; if not, they can be
53 found for instance in Debian package dbus.
54
55 ## Details of the problem
56
57 I have hinted above that DBus will be used in the solution to our
58 problem. But a DBus daemon will run only if you have some advanced X
59 environnement, which excludes pure startx/xinit environnement from the
60 last millenium. So if you have some modern destkop environnement (be it
61 a bare one like XFCE or a bloated one like Gnome) you can use the
62 method below. 
63
64 In addition, a DBus session alone with no services will not allow us
65 to catch interesting events (a.k.a. DBus signals send by processes or
66 calls made by processes), and here we will need the DBus aware
67 application [ConsoleKit or
68 systemd-logind](http://www.freedesktop.org/wiki/Software/ConsoleKit/)
69 running. No problem, it runs on every modern destkop environnement.
70
71 It should be noted that switching virtual consoles happens in 4 cases:
72
73 1. At boot time when you are starting your X server: this triggers a
74 switch to the virtual console (often /dev/tty7) where the X server
75 runs; and then the gamma problem happens
76
77 2. When switching to a text console; going back to the X
78 server screen will trigger the gamma problem
79
80 3. When switching between several X sessions (if the first X session
81 starts in /dev/tty7 as said above, the second one will use the virtual
82 console /dev/tty8 etc...); the new visible X session will exhibit the
83 gamma problem
84
85 4. When resuming from suspended state; not sure if all video hardware
86 goes first to text mode when resuming, but my hardware does, and the
87 gamma problem becomes visible when the switch to graphical mode is
88 triggered
89
90 Some remarks about the 4 cases above:
91
92 * Case 1: for the boot time case, putting in your $HOME/.xsessionrc that line:
93
94     xgamma -gamma 0.5 
95
96 is enough. But won't work for cases 2, 3 and 4. In a multiuser
97 environment, it doesn't scale well.
98
99 * Case 2 and 3: you can switch to a given virtual console using the command
100  [chvt](http://nixdoc.net/man-pages/Linux/chvt.1.html), or with some
101  [keyboard
102  combination](http://www.tldp.org/HOWTO/Keyboard-and-Console-HOWTO-7.html).
103
104 * Case 4: the suspend/hibernate system offers some hook points where
105 commands can be run when resuming. But you must be sure when running
106 that command that you are indeed in graphical mode: if not, the xgamma
107 command won't work. 
108
109 But there is more. In a multiuser environment, you have to find out
110 the correct X display (the one active and visible when suspended), and
111 feed it to xgamma as an argument. So the correct command to run in my
112 case is actually:
113
114     XAUTHORITY=$USERHOME/.Xauthority xgamma -gamma 0.5 -display $DISPLAY
115
116 where you cannot assume that DISPLAY has value :0. Here USERHOME is
117 the home directory of the user logged in the X session of the active
118 display, and XAUTHORITY will allow root (which is the user running
119 the resume script) to access the credentials to connect to that
120 display.
121
122 And no, issuing xgamma on a non-visible and non-active display will not
123 restore gamma for the active display.
124
125 Actually, there is no easy way to find out the active display and it's
126 corresponding session user while staying inside traditional X and the
127 suspend/hibernate system. A workaround would be to loop over all
128 active X displays; this is feasable (they can be found in
129 /tmp/.X11-unix). You will also need to loop over all possible
130 $HOME/.Xauthority, which is easy because you are root. 
131
132 At that point I hope you are convinced that that solution is clunky.
133 I will present something more general below.
134
135 ## Looking for interesting DBus events when switching virtual consoles
136
137 Let's run dbus-monitor on the system DBus daemon (you need to do that
138 as root in a terminal), and let's observe what happens when we switch
139 to a different virtual console and back. The output looks somethink
140 like (# being the root shell prompt):
141
142     # dbus-monitor --system
143     signal sender=org.freedesktop.DBus -> dest=:1.67 serial=2 path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=NameAcquired
144        string ":1.67"
145
146 Now switch to a text console with the key combo Ctrl+Alt+F1, and back
147 to your session (supposed running at /dev/tty7) with the combo
148 Alt+F7. Something happened, we now have on the terminal:
149
150     # dbus-monitor --system
151     signal sender=org.freedesktop.DBus -> dest=:1.67 serial=2 path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=NameAcquired
152        string ":1.67"
153     signal sender=:1.7 -> dest=(null destination) serial=418 path=/org/freedesktop/ConsoleKit/Session4; interface=org.freedesktop.ConsoleKit.Session; member=ActiveChanged
154        boolean false
155     signal sender=:1.7 -> dest=(null destination) serial=419 path=/org/freedesktop/ConsoleKit/Seat1; interface=org.freedesktop.ConsoleKit.Seat; member=ActiveSessionChanged
156        string ""
157     signal sender=:1.6 -> dest=(null destination) serial=195 path=/org/freedesktop/PolicyKit1/Authority; interface=org.freedesktop.PolicyKit1.Authority; member=Changed
158     signal sender=:1.6 -> dest=(null destination) serial=196 path=/org/freedesktop/PolicyKit1/Authority; interface=org.freedesktop.PolicyKit1.Authority; member=Changed
159     signal sender=:1.7 -> dest=(null destination) serial=430 path=/org/freedesktop/ConsoleKit/Session4; interface=org.freedesktop.ConsoleKit.Session; member=ActiveChanged
160        boolean true
161     signal sender=:1.7 -> dest=(null destination) serial=431 path=/org/freedesktop/ConsoleKit/Seat1; interface=org.freedesktop.ConsoleKit.Seat; member=ActiveSessionChanged
162        string "/org/freedesktop/ConsoleKit/Session4"
163     signal sender=:1.6 -> dest=(null destination) serial=203 path=/org/freedesktop/PolicyKit1/Authority; interface=org.freedesktop.PolicyKit1.Authority; member=Changed
164     signal sender=:1.6 -> dest=(null destination) serial=204 path=/org/freedesktop/PolicyKit1/Authority; interface=org.freedesktop.PolicyKit1.Authority; member=Changed
165
166 *Disclaimer: I will handle here the case where you use ConsoleKit. If
167 you use a very recent desktop, you may be using the systemd equivalent
168 of ConsoleKit called systemd-logind, where the messages below look a
169 bit different, with interfaces like: org.freedesktop.login1 and paths
170 like: /org/freedesktop/login1/session. I have so far only virtual
171 machines running with systemd, so unable to explore console switching
172 on them; I will update that page as soon as possible to cover the
173 systemd case.*
174
175 To not be flooded with messages, we can print only messages related to
176 the org.freedesktop.ConsoleKit.Session interface by adding a watch
177 expression argument to dbus-monitor. Redoing the previous experiment,
178 we get now something much more short:
179
180     # dbus-monitor --system interface=org.freedesktop.ConsoleKit.Session
181     signal sender=org.freedesktop.DBus -> dest=:1.68 serial=2 path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=NameAcquired
182        string ":1.68"
183     signal sender=:1.7 -> dest=(null destination) serial=450 path=/org/freedesktop/ConsoleKit/Session4; interface=org.freedesktop.ConsoleKit.Session; member=ActiveChanged
184        boolean false
185     signal sender=:1.7 -> dest=(null destination) serial=462 path=/org/freedesktop/ConsoleKit/Session4; interface=org.freedesktop.ConsoleKit.Session; member=ActiveChanged
186        boolean true
187
188 There are three 2 lines messages above. The first one is noise due to
189 dbus-monitore registering on the DBus daemon. The second one signals
190 to the world that your current session (called Session4) is no more
191 active (boolean false) because no more visible as you switched to
192 /dev/tty1. And the third one says it's visible and active again
193 (boolean true).
194
195 You will be able to verify that the same kind of message is triggered
196 when switching to another X session runningfor instance on /dev/tty8,
197 then going back:
198
199     # dbus-monitor --system interface=org.freedesktop.ConsoleKit.Session
200     signal sender=org.freedesktop.DBus -> dest=:1.82 serial=2 path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=NameAcquired
201        string ":1.82"
202     signal sender=:1.7 -> dest=(null destination) serial=850 path=/org/freedesktop/ConsoleKit/Session4; interface=org.freedesktop.ConsoleKit.Session; member=ActiveChanged
203        boolean false
204     signal sender=:1.7 -> dest=(null destination) serial=851 path=/org/freedesktop/ConsoleKit/Session6; interface=org.freedesktop.ConsoleKit.Session; member=ActiveChanged
205        boolean true
206     signal sender=:1.7 -> dest=(null destination) serial=873 path=/org/freedesktop/ConsoleKit/Session6; interface=org.freedesktop.ConsoleKit.Session; member=ActiveChanged
207        boolean false
208     signal sender=:1.7 -> dest=(null destination) serial=874 path=/org/freedesktop/ConsoleKit/Session4; interface=org.freedesktop.ConsoleKit.Session; member=ActiveChanged
209        boolean true
210     
211 Here the second X session is called Session6. You should now
212 understand that the third and fourth messages happen when you hit
213 Ctrl+Alt+F8: Session4 signals it becomes inactive because invisible,
214 then Session6 becomes visible and active. And the last two 2 lines
215 messages happen when you hit Ctrl+Alt+F7, always ending with Session4
216 signaling it's visible and active again.
217
218 So our solution would be to run our xgamma command as soon as that
219 last DBus signal is caught.
220
221 ## Looking for interesting DBus events when resuming, and reacting on
222    these events
223
224 But wait, what about suspend/resume? Lets run dbus-monitor again and
225 see what happens when you suspend, the resume:
226
227     # dbus-monitor --system interface=org.freedesktop.ConsoleKit.Session
228     signal sender=org.freedesktop.DBus -> dest=:1.83 serial=2 path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=NameAcquired
229        string ":1.83"
230     signal sender=:1.7 -> dest=(null destination) serial=948 path=/org/freedesktop/ConsoleKit/Session4; interface=org.freedesktop.ConsoleKit.Session; member=IdleHintChanged
231        boolean true
232     signal sender=:1.7 -> dest=(null destination) serial=962 path=/org/freedesktop/ConsoleKit/Session4; interface=org.freedesktop.ConsoleKit.Session; member=IdleHintChanged
233        boolean false
234
235 That's even simpler, the second signal says we are going idle
236 a.k.a. suspend state (boolean true) and then going live again (not
237 idle, so boolean false).
238
239 So we have two signals to monitor: ActiveChanged for direct console
240 switching (and boot) and IdleHintChanged for console switching
241 triggered by resume.
242
243 To be completely sure to work only on those signals, we will use two
244 specific watch expressions as arguments to dbus-monitor; several such
245 watch arguments are or-ed, that is not obvious from dbus-monitor man
246 page. Our final dbus-monitor becomes then:
247
248     dbus-monitor --system "interface='org.freedesktop.ConsoleKit.Session',member='ActiveChanged'" "interface='org.freedesktop.ConsoleKit.Session',member='IdleHintChanged'"
249
250 Following that [tutorial
251 method](http://ubuntu.aspcode.net/view/635400140124705175242338/how-do-i-run-a-script-on-a-dbus-signal),
252 plus some bashism like =~ and \[[ ]], we end-up with the following
253 script, which will also use logger to trace what happens in syslog:
254
255     #!/bin/bash
256     
257     interface=org.freedesktop.ConsoleKit.Session
258     member=ActiveChanged
259     resume=IdleHintChanged
260     
261     # beware the pipe | at end of next line
262     dbus-monitor --system "interface='$interface',member='$member'" "interface='$interface',member='$resume'" |
263     while read -r line; do # we loop over all messages; get first line of message in variable 'line'
264         # reading second line of message in variables 'type' and 'value'
265         read type value
266         # 'ret' is 0 if 'line' contains string $member
267         \[[ "$line" =~ "$member" ]]; ret=$? 
268         # test ActiveChanged to be true
269         if [ "$ret" = "0" -a "$type" = "boolean" -a "$value" = "true" ]; then
270         echo Boot or virtual console switching case: $line |& logger
271         xgamma -gamma 0.5 |& logger
272         fi
273         # 'ret' is 0 if 'line' contains string $resume
274         \[[ "$line" =~ "$resume" ]]; ret=$? 
275         # test IdleHintChanged to be false
276         if [ "$ret" = "0" -a "$type" = "boolean" -a "$value" = "false" ]; then
277         echo Resume case: $line |& logger
278         xgamma -gamma 0.5 |& logger
279         fi
280     done
281     
282 If you start this script in background as root while in your X
283 session, things will work fine. But not in X sessions owned by other
284 users: because, as said above, you will be running it with the DISPLAY
285 and XAUTHORITY of your session. But DBus can help you find out these
286 values as we will see now.
287
288 ## Obtaining information through DBus
289
290 Let's interrogate the ConsoleKit session object for our current X
291 session, which is in the example above has path
292 /org/freedesktop/ConsoleKit/Session4. We will use for that dbus-send,
293 and the requests and answers will look like this:
294
295     # dbus-send --system --print-reply --type=method_call --dest=org.freedesktop.ConsoleKit "/org/freedesktop/ConsoleKit/Session4" org.freedesktop.ConsoleKit.Session.GetUser
296     method return sender=:1.7 -> dest=:1.108 reply_serial=2
297        uint32 1000
298     # dbus-send --system --print-reply --type=method_call --dest=org.freedesktop.ConsoleKit "/org/freedesktop/ConsoleKit/Session4" org.freedesktop.ConsoleKit.Session.GetX11Display
299     method return sender=:1.7 -> dest=:1.109 reply_serial=2
300        string ":0"
301
302 It should be obvious that the first dbus-send command obtains the UID
303 of the X session owner (1000), and the second one get's the X display,
304 here ":0".
305
306 So our final version of the script becomes:
307
308     #!/bin/bash
309     
310     interface=org.freedesktop.ConsoleKit.Session
311     member=ActiveChanged
312     resume=IdleHintChanged
313     
314     run() {
315         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`
316         XUID=`dbus-send --system --print-reply --type=method_call --dest=org.freedesktop.ConsoleKit $object_path ${interface}.GetUser | grep uint32 | cut -d ' ' -f 5`
317         if [ "$DISPLAY" = "" -o "$XUID" = "" ]; then
318         echo DISPLAY=$DISPLAY and user-uid=$XUID not both non empty, impossible to run xgamma |& logger -t logger
319         else
320         XAUTHORITY=`grep :x:${XUID}: /etc/passwd | cut -d ':' -f 6`/.Xauthority
321         # we don't run any X app if XAUTHORITY doesn't exist or is empty 
322         if [ -f $XAUTHORITY -a "`wc -c $XAUTHORITY | cut -d ' ' -f 1`" != "0" ]; then
323             echo Running "XAUTHORITY=$XAUTHORITY xgamma -gamma 0.5 -display $DISPLAY" |& logger -t logger
324             XAUTHORITY=$XAUTHORITY xgamma -gamma 0.5 -display $DISPLAY |& logger -t logger
325             # in case your gamma stays ok, you can still test this script by uncommenting the next line
326             #XAUTHORITY=$XAUTHORITY xeyes -display $DISPLAY &
327         else
328              echo $XAUTHORITY file non-existent or empty, impossible to run xgamma |& logger -t logger
329         fi
330         fi
331     }
332     
333     # beware the pipe | at end of next line
334     dbus-monitor --system "interface='$interface',member='$member'" "interface='$interface',member='$resume'" |
335     while read -r line; do # we loop over all messages; get first line of message in variable 'line'
336         # reading second line of message in variables 'type' and 'value'
337         read type value
338         # 'ret' is 0 if 'line' contains string $member
339         \[[ "$line" =~ "$member" ]]; ret=$? 
340         # test ActiveChanged to be true
341         if [ "$ret" = "0" -a "$type" = "boolean" -a "$value" = "true" ]; then
342         object_path=`echo "$line" | cut -d ' ' -f 7 | cut -d '=' -f 2 | cut -d ';' -f 1`
343         echo Boot or virtual console switching case for session $object_path |& logger -t logger
344         run
345         fi
346         # 'ret' is 0 if 'line' contains string $resume
347         \[[ "$line" =~ "$resume" ]]; ret=$? 
348         # test IdleHintChanged to be false
349         if [ "$ret" = "0" -a "$type" = "boolean" -a "$value" = "false" ]; then
350         object_path=`echo "$line" | cut -d ' ' -f 7 | cut -d '=' -f 2 | cut -d ';' -f 1`
351         echo Resume case for session $object_path |& logger -t logger
352         run
353          fi
354     done
355
356 We finally have to start the script at boot time, which has to be done
357 after the system DBus daemon is started, which in current stable
358 Debian is done through /etc/init.d/dbus. I suggest to simply drop the
359 path of the above script there, backgrounded with &.
360
361 If you do that, reboot and look at /var/log/syslog, you will notice
362 that the first call to xgamma (and xeyes) happens under the desktop
363 manager user (this is for sure the case for lightdm which I use).
364
365 Then, when you have logged in, xgamma (and xeyes) won't run because
366 XAUTHORITY is empty. I suspect this is due to a lack of synchronicity
367 between setting the DISPLAY in PolicyKit and setting it fully (with
368 valid XAUTHORITY) in X. The $HOME/.xsessionrc trick discussed at the
369 beginning above will save that case for you.
370
371 After this first logon, things work as expected on resume and console
372 switch. Notice that the screeensaver authorization screen never shows
373 xeyes, if you have uncommented that line in the script.
374
375 ## Conclusion
376
377 We have gone a long way to finally devise a general solution to our
378 problem of running a script when changing virtual consoles. It is
379 fundamentally based on ConsoleKit or systemd-logind tracking these
380 virtual consoles changes, and announcing them on the DBus. 
381
382 An improvement to the script would be to write it using a language
383 with a DBus binding, like Python.
384
385 ## Other interesting links:
386
387 An in-depth view of the virtual consoles: <http://www.phoronix.com/scan.php?page=news_item&px=MTQ0NDg>
388
389 A useful exploratory DBus tool is
390 [d-feet](http://live.gnome.org/DFeet/). A d-feet Debian
391 package exists.
392
393 Programmers may be interested in [gdbus](http://dev.man-online.org/man1/gdbus/) available in Debian package 
394 libglib2.0-dev.
395
396 [userland-dbus-event-manager](https://github.com/airtonix/userland-dbus-event-manager)
397 is a general tool to react on DBus events.