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
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)
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.
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
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.
21 More specifically, I will try to **trigger a script as soon as you
23 consoles](https://en.wikipedia.org/wiki/Linux_Virtual_Consoles)**.
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)
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...
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.
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.
55 ## Details of the problem
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
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.
71 It should be noted that switching virtual consoles happens in 4 cases:
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
77 2. When switching to a text console; going back to the X
78 server screen will trigger the gamma problem
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
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
90 Some remarks about the 4 cases above:
92 * Case 1: for the boot time case, putting in your $HOME/.xsessionrc that line:
96 is enough. But won't work for cases 2, 3 and 4. In a multiuser
97 environment, it doesn't scale well.
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
102 combination](http://www.tldp.org/HOWTO/Keyboard-and-Console-HOWTO-7.html).
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
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
114 XAUTHORITY=$USERHOME/.Xauthority xgamma -gamma 0.5 -display $DISPLAY
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
122 And no, issuing xgamma on a non-visible and non-active display will not
123 restore gamma for the active display.
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.
132 At that point I hope you are convinced that that solution is clunky.
133 I will present something more general below.
135 ## Looking for interesting DBus events when switching virtual consoles
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):
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
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:
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
153 signal sender=:1.7 -> dest=(null destination) serial=418 path=/org/freedesktop/ConsoleKit/Session4; interface=org.freedesktop.ConsoleKit.Session; member=ActiveChanged
155 signal sender=:1.7 -> dest=(null destination) serial=419 path=/org/freedesktop/ConsoleKit/Seat1; interface=org.freedesktop.ConsoleKit.Seat; member=ActiveSessionChanged
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
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
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
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:
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
183 signal sender=:1.7 -> dest=(null destination) serial=450 path=/org/freedesktop/ConsoleKit/Session4; interface=org.freedesktop.ConsoleKit.Session; member=ActiveChanged
185 signal sender=:1.7 -> dest=(null destination) serial=462 path=/org/freedesktop/ConsoleKit/Session4; interface=org.freedesktop.ConsoleKit.Session; member=ActiveChanged
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
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,
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
202 signal sender=:1.7 -> dest=(null destination) serial=850 path=/org/freedesktop/ConsoleKit/Session4; interface=org.freedesktop.ConsoleKit.Session; member=ActiveChanged
204 signal sender=:1.7 -> dest=(null destination) serial=851 path=/org/freedesktop/ConsoleKit/Session6; interface=org.freedesktop.ConsoleKit.Session; member=ActiveChanged
206 signal sender=:1.7 -> dest=(null destination) serial=873 path=/org/freedesktop/ConsoleKit/Session6; interface=org.freedesktop.ConsoleKit.Session; member=ActiveChanged
208 signal sender=:1.7 -> dest=(null destination) serial=874 path=/org/freedesktop/ConsoleKit/Session4; interface=org.freedesktop.ConsoleKit.Session; member=ActiveChanged
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.
218 So our solution would be to run our xgamma command as soon as that
219 last DBus signal is caught.
221 ## Looking for interesting DBus events when resuming, and reacting on
224 But wait, what about suspend/resume? Lets run dbus-monitor again and
225 see what happens when you suspend, the resume:
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
230 signal sender=:1.7 -> dest=(null destination) serial=948 path=/org/freedesktop/ConsoleKit/Session4; interface=org.freedesktop.ConsoleKit.Session; member=IdleHintChanged
232 signal sender=:1.7 -> dest=(null destination) serial=962 path=/org/freedesktop/ConsoleKit/Session4; interface=org.freedesktop.ConsoleKit.Session; member=IdleHintChanged
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).
239 So we have two signals to monitor: ActiveChanged for direct console
240 switching (and boot) and IdleHintChanged for console switching
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:
248 dbus-monitor --system "interface='org.freedesktop.ConsoleKit.Session',member='ActiveChanged'" "interface='org.freedesktop.ConsoleKit.Session',member='IdleHintChanged'"
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:
257 interface=org.freedesktop.ConsoleKit.Session
259 resume=IdleHintChanged
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'
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
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
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.
288 ## Obtaining information through DBus
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:
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
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
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,
306 So our final version of the script becomes:
310 interface=org.freedesktop.ConsoleKit.Session
312 resume=IdleHintChanged
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
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 &
328 echo $XAUTHORITY file non-existent or empty, impossible to run xgamma |& logger -t logger
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'
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
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
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 &.
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).
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.
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.
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.
382 An improvement to the script would be to write it using a language
383 with a DBus binding, like Python.
385 ## Other interesting links:
387 An in-depth view of the virtual consoles: <http://www.phoronix.com/scan.php?page=news_item&px=MTQ0NDg>
389 A useful exploratory DBus tool is
390 [d-feet](http://live.gnome.org/DFeet/). A d-feet Debian
393 Programmers may be interested in [gdbus](http://dev.man-online.org/man1/gdbus/) available in Debian package
396 [userland-dbus-event-manager](https://github.com/airtonix/userland-dbus-event-manager)
397 is a general tool to react on DBus events.