📜 ⬆️ ⬇️

Awesome WM and Dbus

I think it will not be a secret for anyone that Awesome has a “bottleneck” if we run an external script that, for example, should read data from a file or the Internet and return the result to the widget or the system itself, then we can periodically observe the phenomenon “ frieze " when the system stops responding to keystrokes and mouse until the processing result is received (although the active client continues to work). This most often happens when using io.popen or awful.util.pread


I had such a situation more than once, for example, when listening to music in moc / mocp when changing a track, I get an external script that receives the data about the track and loads the album cover, if it exists and displays them. But periodically (because the script was tested on a laptop), the system “hung”. I could not understand for a long time why this happens, and then I found out that if at this moment the disk is heavily loaded with something else, then the data for reading is put in a queue and as a result, since the output of the result depends on the answer, the system “hung”. The problem was partially solved through the “crutches” in the form of a vernal script, which through ' echo $ result | awesome-client - 'send data.

Or another option, there are widgets that display free space on the disk (for example, the / partition), working most often through 'df -h' , but if we have an ntfs partition on the disk, then it can periodically “fall off”, and at this point the system stops responding. One solution is to save the output of the command to a file, and then read it from there. But again this is still a “crutch”. Agree, it is very unpleasant when for the simplest actions it is necessary to invent crutches.
')
In the meantime, in Awesome, there is such a wonderful thing as DBus, which allows the interaction of various system components and applications.

== What is DBus ==
D-Bus is an interprocess communication system that provides applications with several buses to send messages. It provides a seamless connection between desktop applications and the connection between desktop applications and system services. It supports not only broadcast messages (signals), but also a remote method call.

Those. it is possible from Awesome, an external application or your widget to send a request for processing any data, and get the result of processing via the DBus bus, and finally call the function handler of this event. And at the same time no “freezes”, because we are not making Awesome expect a result.

To work with Dbus, you can use the standard 'dbus-send' utilities - to send signals to applications from scripts or a shell, and 'dbus-monitor' - which allows you to track all signals sent between applications and / or the system. If you want to get more complete information about which applications are registered in dbus, what methods they can provide you, you can use a third-party utility from the KDE 'qdbus' kit - this is a console utility with minimal dependencies and occupying just under 1 MB.

If you start dbus-monitor, you will track all signals sent by applications and the system. But if you are interested in any particular signal, you can filter the output:
dbus-monitor "interface='ru.gentoo.kbdd' " 

In this case, we will receive signals about a change in the keyboard layout sent by kbdd. For more information, contact LOR . In principle, dbus-monitor is useful for debugging your scripts.

But we are interested in the possibility of interaction between our scripts and Awesome.

To send a signal to Awesome, you can use the following code:
 dbus-send --session --dest=org.naquadah.awesome.awful /ru/console/mocp ru.console.mocp.songChanged 

Let us consider what is here and what.
--session - indicates that the session (and not system) data transfer bus is used. Those. A session bus is a user bus to which applications launched on behalf of the user are connected, while the system bus most often does not have its own user (HAL services, network stack, bluetooth, etc.)
--dest = org.naquadah.awesome.awful - here we indicate who will be the recipient of our signal, in this case it is awesome
/ ru / console / mocp - unique object name (usually the service name, object path and interface), in our case we create it ourselves.
ru.console.mocp.songChanged is the method used, which is essentially a function to be called which generates a signal if the application is used.

Similarly, you can use the sending of signals from Awesome to various applications, for example, to switch the track in different players, change the status in Pidgin, etc. In this case, the advantage of this method is that it does not need to launch a copy of the terminal, and send it a command for processing, which, although not much, loads the system resources, creating a terminal, processing the command in it, and then destroying this terminal. We will talk about this method and examples a little later.

As you can see everything is quite simple. But this is not enough. It is one thing to send a message that such a function has worked, and another to transmit the result in the signal. There is such an opportunity; you can transfer data for your widgets or functions via Dbus signals.
For example, the same kbdd transmits, in addition to the signal itself, also the selected layout (as a number, and in a different function and the name of the layout, experiment). If you use another layout manager, then the situation is the same there. The heaviest signal in KDE, there is a lot of information, including binary information.

Supported data types: string, byte, boolean, int16, uint16, int32, uint32, int64, uint64, dooble, object_path

== Practical implementation ==
=== Work with file system ===
All changes we will make only in rc.lua.

First, create a widget that will display information:
 --Awesome 3.4 fs_root = widget({type = "textbox"}) fs_root.text = ":" --Awesome 3.5 fs_root = wibox.widget.textbox() fs_root:set_text(":") 

Then create a timer that will request the data:
 fs_timer = timer ({timeout = 600}) --  10  fs_timer:add_singal ("timeout", function () awful.util.spawn_with_shell("dbus-send --session --dest=org.naquadah.awesome.awful /ru/console/df ru.console.df.fsValue string:$(df -h --output='pcent' /home | sed '1d;s/ //g' )" ) end ) fs_timer:start() 

if you are using Awesome 3.5, simply replace add_singal with connect_signal

And update the value when a signal is received:
 dbus.request_name("session", "ru.console.df") dbus.add_match("session", "interface='ru.console.df', member='fsValue' " ) dbus.add_singal("ru.console.df", function (...) local data = {...} local dbustext = data[2] fs_root.text = ": " .. dbustext -- 3.5 fs_root:set_text(":" .. dbustext) end ) 

Everything, we restart awesome!

The advantage of this method is that Awesome will not wait (and therefore hang) while the called code is being processed.

If you call the same command with different parameters, you can return this parameter with the second value, and, accordingly, check it in Awesome itself and call the necessary handler. In our example, we slightly modify the timer function:
 path = '/home' fs_timer:add_singal ("timeout", function () awful.util.spawn_with_shell("dbus-send --session --dest=org.naquadah.awesome.awful /ru/console/df ru.console.df.fsValue string:$(df -h --output='pcent' " ..path.. " | sed '1d;s/ //g' )" string:"..path) end ) dbus.request_name("session", "ru.console.df") dbus.add_match("session", "interface='ru.console.df', member='fsValue' " ) dbus.add_singal("ru.console.df", function (...) local data = {...} local dbustext = data[2] local dbuspath = data[3] if dbustext == '/' then fs_root.text = ": " .. dbustext -- 3.5 fs_root:set_text(":" .. dbustext) elseif dbustext == '/home' then fs_home.text = ": " .. dbustext -- 3.5 fs_home:set_text(":" .. dbustext) end end ) 


=== Interaction with mocp ===
Unfortunately, mocp itself does not support dbus, but it can call an external command when changing a track (and not only, for details on the documentation).

In the config for mocp, I added my handler:
 OnSongChange = "/home/user/script/changesong.sh %f %a %t %d %r %n" 

Here we pass all the necessary values: the path to the file, the artist, the name, the time, the album, in order to not pull the mocp again to get this data, as indicated in the original versions of these scripts .

Then, create a script (changesong.sh) to get the cover and form the text:
 #!/bin/bash # changesong.sh #     DEFAULT_COVER="/home/user/Images/no-cover.jpg" [ -n "$1" ] && FULLDIR=`dirname "$1"` [ -n "$FULLDIR" ] && COVERS=`ls "$FULLDIR" | grep "\.jpg\|\.png\|\.gif"` if [ -z "$COVERS" ]; then COVERS="$DEFAULT_COVER" else TRYCOVERS=`echo "$COVERS" | grep -i "cover\|front\|folder\|albumart" | head -n 1` if [ -z "$TRYCOVERS" ]; then TRYCOVERS=`echo "$COVERS" | head -n 1` if [ -z "$TRYCOVERS" ]; then TRYCOVERS="$DEFAULT_COVER" else TRYCOVERS="$FULLDIR/$TRYCOVERS" fi else TRYCOVERS="$FULLDIR/$TRYCOVERS" fi COVERS="$TRYCOVERS" fi MTITLE= " : $2 : $3 : $5 : $6 : $4" dbus-send --session --dest=org.naquadah.awesome.awful /ru/console/mocp ru.console.mocp.songChanged \ string:"$MTITLE" \ string:"$COVERS" #    , ..     ( bash) 

We give the script the right to execute:
 chmod +x changesong.sh 

Add a handler to Awesome:
 dbus.request_name("session", "ru.console.mocp" dbus.add_match("session", "interface='ru.console.mocp', member='songChanged' ") dbus.add_signal("ru.console.mocp", function(...) local data = {...} coverart_nf = naughty.notify({icon = data[3], icon_size = 100, text = data[2], position = "bottom_left"}) end ) 

Although the result will be the same as in the original version, the difference will be that the system will not freeze if the hard disk is busy, plus we use one script instead of 2x in the original version. Also in the script we do not check for the state of mocp (switching the state of pause / play) and whether the application is running at all, if you need it, add the appropriate code.

== Sending a signal from awesome ==
Standard Awesome unfortunately does not have a function for sending dbus signals, at least on the wiki there is not a word about this feature. Therefore, you have to use sending signals through the shell, for example, this can be done as follows (for example, using the kbdd keyboard switch):
 --  kbdwidget = widget({type = "textbox", name = "kbdwidget"}) kbdwidget.border_color = beautiful.fg_normal kbdwidget.border_width = 1 kbdwidget.text = '<span color="#F8EC5D"><b> Eng </b></span>' next_layout=1 function changeKeyboardLayout(keyboard_layout) awful.util.spawn( "dbus-send --type=method_call --session --dest=ru.gentoo.KbddService /ru/gentoo/KbddService ru.gentoo.kbdd.set_layout uint32:".. keyboard_layout ) end dbus.request_name("session", "ru.gentoo.kbdd") dbus.add_match("session", "interface='ru.gentoo.kbdd',member='layoutChanged'") dbus.add_signal("ru.gentoo.kbdd", function(...) local data = {...} local layout = data[2] lts = {[0] = '<span color="#F8EC5D"><b> Eng </b></span>', [1] = '<span color="#FF3000"><b>  </b></span>'} kbdwidget.text = " "..lts[layout].." " if layout == 1 then next_layout = 0 else next_layout = 1 end end ) kbdwidget:buttons(awful.util.table.join(awful.button({}, 1, function () changeKeyboardLayout(next_layout) end))) 

Here we, by clicking on the widget with the left mouse button, change the keyboard layout by sending a message about it through dbus. You also need to add kbdd to the autoload, otherwise nothing will work. By the way, if widgets in the Russian layout do not work for you (for example, after changing the layout to Russian, when you click on the widget, the layout does not change to English) read the article known problems .

== Search for desired application signals ==
Most applications can be directly controlled via dbus, i.e. you can switch tracks, transfer applications to full screen mode, change statuses, etc. To get a list of all possible signals and methods, start the application (without this, registration of available events will not occur), then start qdbus, find the interface you need, for example:
 qdbus | grep clementine   : org.mpris.MediaPlayer2.clementine org.mpris.clementine , : qdbus org.mpris.clementine  : / /Player /TrackList /org /org/mpris /org/mpris/MediaPlayer2   : qdbus org.mpris.clementine /Player 

And we get all the possible methods and signals for this application. For example, we are interested in switching to the next track, the method for this is as follows:
 method void org.freedesktop.MediaPlayer.Next() 

In this case, the method does not require any parameters, so we simply call it:
 dbus-send --type=method_call --session --dest=org.mpris.clementine /Player org.freedesktop.MediaPlayer.Next 

Actually, everything. Further experiment and look for yourself.

== Tracking Signals from Scripts ==

If you want to perform more complex tasks than calling individual methods, then you can write a shell script containing dbus-send commands, or use a higher level language to simplify the task. There are D-Bus bindings for Python, Ruby and Java languages.

In the following example, a Python script will be shown that changes the status in Pidgin to “Away from keyboard”, when the screensaver is activated. Here are two aspects of D-Bus: the script waits for a signal from the screensaver, and then it calls the method in Pidgin.

Immediately make a reservation, the script is not mine, the link to the original is given below, but I just could not describe this interaction.
pidgin_screensaver.py
 #!/usr/bin/env python def pidgin_status_func(state): obj = bus.get_object("im.pidgin.purple.PurpleService", "/im/pidgin/purple/PurpleObject") pidgin = dbus.Interface(obj, "im.pidgin.purple.PurpleInterface") status = pidgin.PurpleSavedstatusFind("afk") if status == 0: status = pidgin.PurpleSavedstatusNew("afk", 5) if state: pidgin.PurpleSavedstatusSetMessage(status, "Away from keyboard") pidgin.PurpleSavedstatusActivate(status) import dbus, gobject from dbus.mainloop.glib import DBusGMainLoop dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) bus = dbus.SessionBus() bus.add_signal_receiver(pidgin_status_func, dbus_interface="org.gnome.ScreenSaver", signal_name="ActiveChanged") loop = gobject.MainLoop() loop.run() 

Let's take this script apart. The pidgin_status_func function sets your status to Pidgin. It gets the im / pidgin / purple / PurpleObject object and the im.pidgin.purple.PurpleInterface interface from the session bus. Next, the interface method is called. It creates a new “saved status” type, after checking the existence of the status type with the name “afk” (“afk” means “Away From Keyboard”, and 5 is the kind of “away” status).

Next, the function checks the state variable, which is an argument to the pidgin_status_func function (I will explain what this argument means below). If the argument is true, the new status message “afk” is assigned the value “Away from keyboard”, and the status is activated. As a result, Pidgin shows your status as “afk", with the message “Away from keyboard”.

Now we have to call this function together with the activation of the screensaver. Therefore, run dbus.mainloop and connect to the session bus. Next, add a signal receiver that listens to the ActiveChanged signal from the org.gnome.ScreenSaver interface. If / when the signal is triggered, it calls the pidgin_status_func function. Since the ActiveChanged signal has a boolean argument that indicates the current status of the splash screen (1 is active, 0 is not active), we use only one argument (state) in the pidgin_status_func function. For continuous listening, we run an infinite loop that runs while the script is running.

In general, many applications have a dbus interface, so the possibilities for managing them are very extensive, and are limited only by your imagination and desire!

== Related Links ==
Introduction to DBus OpenNET
D-Bus Tutorial
Lor
Managing Linux desktop via D-Bus

Update 1. Small update: ru.gentoo.kbdd is replaced by ru.gentoo.KbddService. only the name changed, without changing the layout itself. Additionally, the function for changing the layout is taken out separately, since it is convenient to automatically change the layout when calling Mod4 + r or Mod4 + p (added changeKeyboardLayout (0) before calling)

Source: https://habr.com/ru/post/235231/


All Articles