Awesome WM and Dbus

    I think it’s no secret to anyone that Awesome has a bottleneck, if we run an external script that, for example, needs to read data from a file or the Internet and return the result to the widget or the system itself, we can periodically observe the phenomenon “ frieze ", i.e. when the system stops responding to keystrokes and mouse clicks until a processing result is received (though the active client continues to work). Most often this happens when using io.popen or awful.util.pread

    This situation has happened to me more than once, for example, when listening to music in moc / mocp when changing a track, I get an external script that receives data about the track and loads the album art, if any, and displays them. But periodically (since the script was tested on a laptop), the system “hung”. For a long time I could not understand why this was happening, and then I found out that if at this moment the disk is heavily loaded with something, then the read data is queued and as a result, because the conclusion of the result depends on the answer, the system “hung up”. The problem was partially solved through the “crutches” in the form of a spring script, which through ' echo $ result | awesome-client - 'forwarded 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 the ntfs partition on the disk, then it may periodically “fall off”, and at this moment 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 the “crutch”. Agree, it is very unpleasant when crutches have to be invented for the simplest actions.

    Meanwhile, in Awesome, there is such a wonderful thing as DBus, which allows the interaction of various components of the system and applications.

    == What is DBus ==
    D-BusIs an interprocess communication system that provides applications with several buses for sending messages. It provides a seamless connection between desktop applications and the connection between desktop applications and system services. Not only broadcasting of messages (signals) is supported, but also remote calling of methods.

    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 through the DBus bus, and finally call the function handler for this event. And at the same time no “freezes”, because we do not make Awesome expect the result.

    To work with Dbus, you can use the standard utilities 'dbus-send' - to send signals to applications from scripts or shell, and 'dbus-monitor'- which allows you to track all the 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 the third-party utility from the KDE kit 'qdbus' - this is a console utility with minimal dependencies and occupying a little less than 1Mb.

    If you run dbus-monitor, you will monitor all signals sent by applications and the system. But if you are interested in any particular signal, then 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 convenient for debugging your scripts.

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

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

    We will analyze what is here and what.
    --session - indicates that a session (rather than system) data bus is being used. Those. the session bus is the user bus to which the applications launched on behalf of the user are connected, while the system bus most often does not have its 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 Awesome
    / ru / console / mocp - the unique name of the object (usually the service name, path to the object and interface), in our case we create it ourselves.
    ru.console.mocp.songChanged - the used method, in fact the called function which generates a signal if the application is used.

    You can also use the same way to send signals from Awesome to various applications, for example, to switch the track in different players, change the status in Pidgin, etc. At the same time, the advantage of this method is that you do not need to run a copy of the terminal and send it a command for processing, which, although not very much, loads the system’s resources to create a terminal, process the command in it, and then destroy this terminal. We will talk about this method and examples a bit 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 it is another to transmit the result in the signal. There is such an opportunity, you can transmit data for your widgets or functions through Dbus signals.
    For example, the same kbdd, in addition to the signal itself, also transfers the selected layout (as a number, and in another function the layout name, experiment). If you use a different layout manager, then the situation is the same there. The heaviest signal in KDE, a lot of information is transmitted there, including binary.

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

    == Practical implementation ==
    === Work with the file system ===
    We will make all changes 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()

    Then create a timer that will request 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 )

    if you are using Awesome 3.5, then just replace add_singal with connect_signal

    and update the value when you receive a signal:
    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 )

    That's it, 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 as the second value, and accordingly in Awesome itself check it and call the desired 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 )

    === 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/ %f %a %t %d %r %n"

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

    Then, create a script ( to get the cover and form the text:
    #файл с обложкой по умолчанию
    [ -n "$1" ] && FULLDIR=`dirname "$1"`
    [ -n "$FULLDIR" ] && COVERS=`ls "$FULLDIR" | grep "\.jpg\|\.png\|\.gif"`
    if [ -z "$COVERS" ]; then
    	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
    MTITLE= "
    	Исполнитель:	$2
    	Название:	$3
    	Альбом: 	$5
    	Трек:	$6
    	Время:  	$4"
    dbus-send --session --dest=org.naquadah.awesome.awful /ru/console/mocp ru.console.mocp.songChanged \
    		  string:"$MTITLE" \
    #обязательно помещаем переменную в кавычки, т.к. иначе некорректно передается строка (особенность bash)

    We give the script the right to execute:
    chmod +x

    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 does not hang if the hard drive is busy, plus we use one script instead of 2x in the original version. Also in the script, we do not check for the mocp state (pause / play state switching) and if the application is running at all, if you need it, add the appropriate code.

    == Sending a signal from Awesome ==
    The standard Awesome unfortunately does not have a function to send dbus signals, at least there is not a word about this feature on the wiki. Therefore, you have to use sending signals through the shell, for example, this can be done as follows (using the kbdd keyboard switch as an example):
    --виджет клавиатуры
    kbdwidget = widget({type = "textbox", name = "kbdwidget"})
    kbdwidget.border_color = beautiful.fg_normal
    kbdwidget.border_width = 1
    kbdwidget.text = ' Eng '
    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 )
    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] = ' Eng ', [1] = ' Рус '} 
    		 kbdwidget.text = " "..lts[layout].." " 
    		 if layout == 1
    		     then next_layout = 0
    		     next_layout = 1
    kbdwidget:buttons(awful.util.table.join(awful.button({}, 1, function ()

    Here we, by clicking on the widget with the left mouse button, change the keyboard layout, sending a message about it via dbus. You also need to add kbdd to startup, otherwise nothing will work. By the way, if you do not have widgets in the Russian layout (for example, after changing the layout to Russian, clicking on the widget does not change the layout 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, put applications in full screen mode, change statuses, etc. To get a list of all possible signals and methods, start the application (without this, the registration of available events will not occur), then run qdbus, find the desired interface, for example:
    qdbus | grep clementine
    Получим следующий вывод:
    Затем, запустим:
    qdbus org.mpris.clementine 
    Получим следующиее:
    А затем вызовем:
    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 just call it:
    dbus-send --type=method_call --session --dest=org.mpris.clementine /Player org.freedesktop.MediaPlayer.Next

    Actually, that's all. Experiment further and search for yourself.

    == Tracking signals from scripts ==

    If you want to perform more complex tasks than calling individual methods, 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. There are two aspects of D-Bus here: the script waits for a signal from the screensaver, and then it calls the method in Pidgin.

    I’ll make a reservation right away, the script is not mine, the link to the original is given below, but I simply could not describe this possibility of interaction.
    #!/usr/bin/env python def pidgin_status_func(state): 
    obj = bus.get_object("im.pidgin.purple.PurpleService", 
    pidgin = dbus.Interface(obj, "im.pidgin.purple.PurpleInterface") 
    status = pidgin.PurpleSavedstatusFind("afk") 
    if status == 0: 
    status = pidgin.PurpleSavedstatusNew("afk", 5) 
    if state: 
    "Away from keyboard") 
    import dbus, gobject 
    from dbus.mainloop.glib import DBusGMainLoop 
    bus = dbus.SessionBus() 
    loop = gobject.MainLoop() 

    Let's take a look at this script. The pidgin_status_func function sets your status to Pidgin. It receives 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 a 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, then the message “new afk” is assigned the value “Away from keyboard”, and the status is activated. As a result, Pidgin displays your status as “afk”, with the message “Away from keyboard”.

    Now we must call this function along 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 state of the splash screen (1 - active, 0 - not active), we use only one argument (state) in the pidgin_status_func function. For constant listening, we run an endless loop that works 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
    Linux desktop management via D-Bus

    Update 1. Small update: ru.gentoo.kbdd replaced by ru.gentoo.KbddService since only the name changed, without changing the layout itself. In addition, a separate function for changing the layout is rendered, because it is convenient to automatically change the layout when calling Mod4 + r or Mod4 + p (added changeKeyboardLayout (0) before the call

    Also popular now: