Playing with D-Bus and KDE applications (Part 1)

Updates

The following updates have been applied:

2021-10-26:
  • Added another example about Klipper and the libzypp history
  • Moved the examples in a different paragraph.
2022-01-13:
  • Added a method to merge the last nth items in the history within a single one
2022-01-26:
  • Warning box about the qdbus filename alternative

Speaking about the several ways that a Linux system offers to users to create custom automation, there is a software technology that hides under the hoods of modern desktop environments, D-Bus. To make parallelism, in the same way we use piping | the output from a shell command to the input of another, we might altogether find interesting to get some info from an application running on our DE, no matter if it is a GUI application or an application running in the background, and use it in our scripts.

D-Bus in brief

D-Bus is primarily a protocol intended to replace similar technologies (i.e. Corba, DCop) related to the Inter-Process Communication between processes running on the same machine.

It works as base protocol or peer-to-peer protocol, so two applications can communicate directly, but also through a message bus: that way there is a particular application, a system daemon, that accepts and forwards messages to all the registered applications.

In particular, there are two types of communications to handle:

  • Between the underlying system and the user applications, through the system bus;
  • between two applications in the user space, through the user bus.

To take advantage of these D-Bus services there is an application that runs from the command line and thus can be used inside our shell scripts with ease: qdbus.

Warning!!!

In some KDE versions there is qtdbus-qt5 in place of qdbus, but the old file is still available and can be linked under the home bin path:

ln -s /usr/lib64/qt5/bin/qdbus ~/bin/qdbus

Otherwise just replace the qdbus filename with qtdbus-qt5 in the examples below.

Another useful tool, qdbusviewer, shows in a graphical window all the methods and properties shared by a D-Bus service with the ability to call them with a click and print the output in a panel.

Exploring D-Bus messages

Being the documentation on that matter practically missing, a tool like qdbusviewer is indispensable to discover which messages are supported by a particular application.

The first useful thing is that the two types of service bus are separated into two different tabs, but most of the time we want to dig into the Session bus which is populated by user applications services.

Not all the services available in the System bus can be accessed with the standard user permissions, but to be honest I found nothing so interesting to reuse in my scripts, indeed the same system info can be found in the /proc or /sys files too.

The action of listing all the methods and properties made available by a service is called introspection, which is completely transparent to us, because automatically activated by the click on each service listed in the left panel, but also by the tab completion while using dbus from the command line.

The search box on the top filters the list of services for a particular string, to know whether or not a particular application provides a service, we can just type its name. or part of it, in that area.

Browsing the paths in the right panel we must look for those namespaces containing methods and properties that might help to accomplish our goals.

Not all of these namespaces contain interesting stuff, so my advice is to look primarily inside those having in the last part of their name a reference to the path or the application itself, and discard those appearing frequently (org.freedesktop.DBus.Properties, org.freedesktop.DBus.Introspectable, org.freedesktop.Peer) for every service.

In the image below, under the /klipper path, the org.kde.klipper namespace is a good candidate to find interesting stuff!

How to send a message

The steps to bring an action tested on qdbusviewer to qdbus is quite straightforward, in the latter we also have the help of the auto-completion feature triggered by the Tab key.

The only thing to do is taking note of the service service-name, the method-path, and the method-namespace. This last, followed by a dot, introduces the wanted method-name or property-name with one or more required arguments separated by spaces:

$ qdbus <service-name> <method-path> <method-namespace>.<method-name> [arg1 [,arg2 &hellip; ] ]

When we want to address a system bus we must use as the first argument the --system option.

To make things more readable, we can avoid the namespace and use the method name directly, but at the cost of opt-out of the Tab-completion assistance in the terminal, that’s why I suggest the short form only within a script.

Among the plethora of D-Bus messages available there are some that improved my productivity and I decided to include them within my scripts, Bash aliases, or call them directly from my KDE Service Menus.

Klipper

This application keeps track of a configurable number of items that have been stored in the clipboard area. These items can also be edited within its popup menu and re-selected to be available again. It also allows to start some commands depending on the content of the copied data (Actions), but this feature won’t be discussed here.

Once upon a time, Klipper was available as a stand-alone application, nowadays it is a Plasma add-on usually enabled by default at the first install, what has never changed is the D-Bus service associated, useful to reuse within our shell, when for example we must copy a text in the clipboard:

$ qdbus org.kde.klipper /klipper org.kde.klipper.klipper.setClipboardContents 'Text to copy'

In a shell, we make large use of the pipe symbol | to reuse the output from a previous command as standard input for another, unfortunately qdbus belongs to those commands that don’t take advantage of that mechanism.

To overcome this situation the xargs command comes in handy: it transforms the standard input to the argument of the following command, using the curly brackets {} as a placeholder.

This is the how to make the standard input working with qdbus:

$ xargs -0 qdbus org.kde.klipper /klipper org.kde.klipper.klipper.setClipboardContents {}

The -0 argument is important to use all the standard input as a single text despite the new line char \n, that way for a text having nth new lines we will still have a single item in the clipboard list.

Now we can grab any content from the standard output, copy it to the clipboard, and finally paste it on our text editor:

$ ls -l | xargs -0 qdbus org.kde.klipper /klipper org.kde.klipper.klipper.setClipboardContents {}

Writing all that stuff everytime is not the best option, let’s create an alias in the .bashrc file and call it clipboard.copy,

alias clipboard.copy='xargs -0 qdbus org.kde.klipper /klipper org.kde.klipper.klipper.setClipboardContents {}'

things are way better:

$ cat filename.txt | clipboard.copy

$ cat filename.txt | grep 'the wanted text' | clipboard.copy

We might also want to paste the content of our clipboard, let’s say the last copied item, within our script or in a chain of commands thrown in the shell.

How to get the last item from the Klipper service:

$ qdbus org.kde.klipper /klipper org.kde.klipper.klipper.getClipboardContents

And again let’s create our alias:

alias clipboard.paste='qdbus org.kde.klipper /klipper org.kde.klipper.klipper.getClipboardContents'

We can also read more items from the Klipper list using the getClipboardHistoryMenu method:

$ qdbus org.kde.klipper /klipper org.kde.klipper.klipper.getClipboardHistoryMenu

It will print the whole saved items, but we can limit the number at the first nth items (which are the latest inserted), using the head command, while with tail we get the latest nth (those saved earlier).

Creating the clipboard.history alias:

alias clipboard.history='qdbus org.kde.klipper /klipper org.kde.klipper.klipper.getClipboardHistoryMenu'

We have:

$ clipboard.history | head -n 10

With the power of the Bash shell we can easily merge the last ten items in a single one and copy it in the clipboard:

$ clipboard.history | head -n 10 | clipboard.copy

That solution is very handful when for example we are collecting a long list of URLs, or any other text data we must include in a list, to send as e-mail or just to create a note.

Some scripting

Copying back and forth the data from the clipboard with the help of the aforementioned aliases and a few instructions we can build useful scripts to transform the text from a format to another or create filters using the power of the shell programming.

In this first example we might want to create a Markdown list using the text in the clipboard and send back the result to the clipboard:

text=''
for i in `clipboard.paste`
do
  text="$text- $i\n"
done

echo -e "$text" | clipboard.copy

The same script can be written in a single line directly from our terminal:

$ text=''; for i in `clipboard.paste`; do text="$text- $i\n"; done; echo -e "$text" | clipboard.copy

Let’s imagine we want install a bunch of packages we just installed in a machine to another through an ssh shell, first we make a copy of these packages in the clipboard:

$ sudo cat /var/logs/zypp/history | grep "^$(date %Y-%m-%d)" | grep install | cut -f 3 -d '|' | clipboard.copy

Then we install them, using zypper, on the wanted maching:

$ for i in `clipboard.paste`; do sudo zypper install $i; done

Final thoughts

The first part ends here, I will publish some more examples in the next article, meanwhile who is interested to learn more about this technology can follow this list of links:


Creative Commons License This work is licensed under a Creative Commons Attribution-NoCommercial-ShareAlike 4.0 International License


Follow-up articles


Leave a Comment