avatar image - a green paisley swirl bits all the way down
A logo similar to that belonging to D-Bus, but including my avatar.
August 4, 2025
By: Nundrum

Clojure and D-Bus

  1. 》Background
  2. 》D-Bus
  3. 》Clojure with D-Bus
    1. 》Revisting timedate1
    2. 》KWallet
    3. 》Getting the kwalletd5 Object
    4. 》The Rest is Interop - Or How to Use the Wallet
  4. 》Conclusion

》Background

I was looking for a way to store secrets — specifically web API credentials — in a secure way for use with command-line tools. Using a dotfile is a terrible idea, and rolling my own secure storage is just as bad.

Freedesktop.org has a secret service API which looks like a smarter way to go about this. There is also KDE Wallet Manager, and since I’m using KDE, it might be a good option.

This post is about exploring how to use D-Bus with Clojure, with the eventual goal of storing and retrieving a test secret.

》D-Bus

D-Bus is Linux’s standardized way to handle inter-process communication, including secure comms between applications and the kernel. This nice D-Bus Overview page explains more about it. If you’re not familiar with D-Bus yet, you should give that a quick read before proceeding. Don’t worry about understanding every bit of it.

A whole suite of tools exist for interaction with D-Bus. One of the most useful is d-feet which is a GUI for exploring the buses, objects, and interfaces available on your system. There is also busctl for a cli variant.

For simple testing and scripting, dbus-send can be used. Here’s an example of getting the current timezone from systemd:

=> dbus-send --system --dest=org.freedesktop.timedate1 \
   --print-reply \
   /org/freedesktop/timedate1 org.freedesktop.DBus.Properties.Get \
   string:org.freedesktop.timedate1 string:Timezone

method return time=1754400037.060292 sender=:1.196828 -> destination=:1.196833 serial=11 reply_serial=2
   variant       string "America/New_York"

The downside here is that you have to know quite a bit to make that simple call. Let’s break it down:

  • --system means to use the system bus, as opposed to the session bus

  • --dest is the service identifier, in this case org.freedesktop.timedate1

  • /org/freedesktop/timedate1 is the object path

As if that wasn’t tricky enough, the rest is trickier! D-Bus is an RPC or Remote Procedure Call system. The objects often have their own interfaces, but also implement common interfaces such as org.freedesktop.DBus.Properties. The Properties interface provides three standard methods: Get, GetAll, and Set.

And dbus-send has to know what kind of data is being sent, hence the string: prefix. Continuing on:

  • org.freedesktop.DBus.Properties.Get is the method being called

  • string:org.freedesktop.timedate1, string:Timezone are the parameters

In other words, we’re calling Get on org.freedesktop.timedate and getting the Timezone field.

I confess that I do not find this straightforward or very "unixy" and that surprised me. Writing the Clojure code was confusing at first because of the way the object interfaces work, and that’s incredibly important to understand.

》Clojure with D-Bus

Since D-Bus is an RPC system, we need a Java implementation that can speak the protocol. The dbus-java package handles that for us!

If you would like to follow along in your REPL, the examples that follow are pasteable. Of course, you need D-Bus libraries. The Leiningin coordinates I used for this are:

[com.github.hypfvieh/dbus-java-core "5.1.1"]
[com.github.hypfvieh/dbus-java-transport-native-unixsocket "5.1.1"]

》Revisting timedate1

To re-create the timedate1 example is easy. Dbus-send handles a few things that have to be separated out into steps in code. First up is importing the namespaces. We’ll need the dbus-java DBusConnectionBuilder as well as the Properties interface.

(import [org.freedesktop.dbus.connections.impl DBusConnectionBuilder])
(import [org.freedesktop.dbus.interfaces Properties])

connecting to the system bus:

(def sys-bus (DBusConnectionBuilder/forSystemBus))
(def sys-connection (.build sys-bus))

Next is making the call to Get by creating a local proxy object that connects to the remote object, and then simply invoking the method:

(def timedate1 (.getRemoteObject sys-connection
                "org.freedesktop.timedate1"
		"/org/freedesktop/timedate1"
		Properties))
(.Get timedate1 "org.freedesktop.timedate1" "Timezone")
; => "America/New_York"

Not so bad, right? But wait, what’s up with that Properties in the .getRemoteObject call? It’s the object’s interface class constructed as a Java interface. I did not expect to need Java interfaces to make the call since dbus-send and d-feet only needed the parameter list.

Thankfully dbus-java provides that interface, otherwise it would have to be built.

》KWallet

I’ll spare a ton of bits describing what happened next and just say that I decided to go with KWallet instead of the freedesktop.org secret service. There are quite a few dead and outdated links about the relationship between the two, and even now I’m not quite sure where it stands. Either should work, but I use KDE as my desktop so that’s the interface I will use going forward.

Exploring d-feet shows the following:

  • service: org.kde.kwalletd5

  • object path: /modules/kwalletd5

  • interface: org.kde.KWallet

》Getting the kwalletd5 Object

This part should be familiar. WKallet’s service is connected to the session bus, whereas the earlier example connected the system bus.

(def bus (DBusConnectionBuilder/forSystemBus))
(def connection (.build bus))

Like before, the remote object has to be connected, but what to use for the interface class?

(def kw (.getRemoteObject connection "org.kde.kwalletd5"
         "/modules/kwalletd5" ...what?...))

The /modules/kwalletd5 object implements Introspectable, Peer, and Properties interfaces from org.freedesktop.DBus, bit it also has it’s own org.kde.KWallet interface which dbus-java does not provide!

Introspectable will return an XML document describing the interface for the KWallet object, and perhaps that could be used with Clojure’s gen-interface and reify to make the Java interface? I considered that but did not explore it.

The dbus-java project describes another way to generate the interface code.

I’m very grateful that the kdewallet library for Java provides the necessary code so I didn’t have to generate it.

The Leiningen coordinates are [org.purejava/kdewallet "1.6.0"] if you want to follow along from here. Now to try getting the remote object again:

(import [org.purejava.kwallet KWallet])
(def kw (.getRemoteObject connection "org.kde.kwalletd5"
	 "/modules/kwalletd5" KWallet))

》The Rest is Interop - Or How to Use the Wallet

Now that the object is available, calls to the service are just normal Java interop. Either d-feet or busctl will show you the full list of methods.

What I found most useful was the test suite to guide how to use the service.

In short, the open call ensures the wallet is open and returns a handle to it. From there on out, most methods require both the handle and an identifier for your client application.

The code below checks that the KWallet folder named "Nextcloud" exists, writes a test password/token and then reads it back.

;First, try to open the default wallet and get a handle for my app
(def kw-handle (.open kw "kdewallet" 0 "deck-cli"))

;Check for the folder (as show in the KDE Wallet Manager)
(.hasFolder kw kw-handle "Nextcloud" "deck-cli")

;Or create it
;(.createFolder kw kw-handle "Nextcloud" "deck-cli"))

;Get a list of entries in the folder
(.entryList kw kw-handle "Nextcloud" "deck-cli")

;Store a password
(.writePassword kw kw-handle "Nextcloud"
  "deck-cli-token" "my-very-long-token" "deck-cli")
(.readPassword kw kw-handle "Nextcloud"
  "deck-clj-token" "deck-cli") ;=> "my-very-long-token"

》Conclusion

Perhaps in the future I’ll look into how to handle signals from D-Bus, or even how to provide a service through it. But for now this suffices for my needs. Happy coding!

End-of-Transmission

Tags: clojure
Clojure, Babashka, and Web CGI »

A blog by Nundrum

Links

  • @Nundrum on Mastodon
  • Archives
  • RSS
  • Clojurians Slack
  • Contacting Nundrum
  • P.E.P.R. Jacket Project

Recent Posts

  • Clojure and D-Bus
  • Clojure, Babashka, and Web CGI
  • The System Wayfinder

Tags

  • re-frame
  • clojure
  • svg
  • async
  • interop
  • jna
  • cli
  • screensaver
  • platformio
  • font
  • babashka
  • utility
  • pepr jacket project
  • cyberpunk
  • clojurescript
  • selfhosting

Copyright © 2025 Nundrum

Powered by Cryogen