Script day – different default browser per KDE activity

This is a bit of a weird script day – the script is pretty simple but the integration is interesting. I’m scratching my own itch here and also demonstrating how to:

  • Use dbus-monitor to listen to D-Bus events
  • Use SystemD user services to run a session service
  • Update KDE configuration safely from scripts

The Problem:

KDE – the Linux desktop environment I use – has a very neat feature called “Activities” where you can have multiple desktop configurations – such as wallpapers, desktop icons, application menu, application shortcuts and various others – and switch between them on the fly. You can give activities names, start and stop them on demand and so on. I use this to great effect to have a “work desktop” vs “a personal life desktop” (and a few others that are useful now and then).

The main problem is that there are some things that aren’t configurable per activity (as far as I know), and one of the more infuriating ones is the default browser: if my default browser is set to my personal browser – Firefox, then whenever I click a link in my work Slack or a link in the console on my work activity, Firefox gets invoked – while I would have liked to open this in my work Google Chrome identity, where I have my work logins; and also in the other direction.

The Solution:

KDE is heavily invested in D-Bus – the Linux messaging bus (quite understandable as the KDE project invented the technology under the name DCOP) – and sends D-Bus events for almost anything that happens in the system. We can listen to the activity change event and update the default browser according to the new activity. In order to listen to D-Bus events, we will use the dbus-monitor tool that allows a script to capture all message bus events and consume them easily. This is not the preferred method of listening to specific D-Bus events: you are supposed to register with the D-Bus service for the signal you wish to receive, but I don’t think this can be done by a shell script at this point.

The KDE default browser configuration can be updated from the System Settings application, under Personalization -> Applications – > Web Browser. This configuration gets written into the ~/.config/kdeglobals configuration file which is using the KDE preferred configuration file format which is the venerable INI format, which unfortunately is not very easy to manipulate via scripts. Fortunately the KDE project is power-user friendly and provides a useful set of command line tools to manipulate KDE configuration, among them are kreadconfig5 and kwriteconfig5. The main benefit to using these tools is that while updating the configuration file they are also updating KDE’s system configuration caches – so that any change made is also immediately visible by the system.

The Script

With these two issues understood, its time to write the script. It is pretty simple – run dbus-monitor and monitor its output for the KDE activity change signal, then use the D-Bus command line to ask KDE what the current activity is called, then use kwriteconfig5 to update KDE of the selected default browser:

function readconf() {
  kreadconfig5 --file ~/.config/kdeglobals --group General --key BrowserApplication
function writeconf() {
  kwriteconfig5 --file ~/.config/kdeglobals --group General --key BrowserApplication "$1"
dbus-monitor --profile "type=signal,path=$path,interface=$interface,member=$signal" | \ 
      while read type timestamp serial sender destination path interface member; do
  [ "$member" == "$signal" ] || continue
  curact=$(qdbus $service $path $interface.CurrentActivity)
  name="$(qdbus $service $path $interface.ActivityName $curact)"
  echo "Switched to activity $name"
  echo "Previous browser is $(readconf)"
  case "$name" in
      writeconf firefox.desktop
      writeconf google-chrome-work.desktop
    *) # default in case a new activity is created
      writeconf firefox.desktop
  echo "Current Browser is $(readconf)"


You can see in the read line all the fields output by dbus-monitor when run with the --profile output type. Ideally we would read the changed activity’s ID from the message (instead of reading it from currentActivity) but the --profile display doesn’t afford a place for the message’s data, and the other output formats are harder to read using a simple read command. Also dbus-monitor always has some noise output in the beginning, hence the $member == $signal filter at the start of the event handler.

Also note that KDE’s default browser configuration takes a Desktop Entry file instead of a path to an executable, so if you want to use a non-standard configuration, make sure you create a desktop entry for it, possibly using KDE’s menu editor1.

So we can run this script and then every time we switch to another activity, the script will update the default browser. But remembering to run the script (and possibly having a terminal window taken up by it) every time we log in is quite a hassle – can we automate this further?

SystemD Service

Here comes SystemD to the rescue with one of its greatest features – user services. If you’re running any modern Linux, your system is probably managed by SystemD (unless you’re a putz that doesn’t like convenience and stability), so the only thing you have to do is create a SystemD user service, which is incredibly simple.

Supposedly you saved the activity monitor script in some directory and gave it execute permissions. In my system it sits in ~/.local/bin/activity-monitor, so step 1 is to create a service file for your script and put it in the SystemD user unit directory: ~/.config/systemd/user (you should create it if it doesn’t exist), named with the .service extension. In my case I named my service activity-monitor, so the full path will be ~/.config/systemd/user/activity-monitor.service and it should look something like this:

Description=KDE Activity Monitor


The first section is required and allows us to put in a nice description of the service so that tools that show system status can show it. The second section is required for a service (like we want to do) and tells SystemD which executable to start. This section has a ton of features, but in the case of the “service” just being a single application that is started and when it terminates the “service” ends, this is all we need. The last section tells SystemD when we want our service to run. We can also provide this information on the command line when enabling the service, but this is much easier.

After the service file is saved in the SystemD user units directory, we just need to tell SystemD that we added something new it should look at, by running:

systemctl --user daemon-reload


And then enable our new service by running:

systemctl --user enable activity-monitor


And now the activity monitor will start automatically when we next log in and stop automatically when we log out. If you want to start it now (without needing to re-log in), we can just run:

systemctl --user start activity-monitor


And that’s it – this is all there is to SystemD user services.

Important note

The above implementation requires integration between SystemD user services, DBus and the X11 display.

On Ubuntu/Debian systems this is done by the dbus-user-session package, which may not be installed by default in some configurations. Run systemctl --user status dbus.socket to check – if it doesn’t look like that SystemD unit is available and active, then you’d need to install the missing package by running apt install dbus-user-session and then rebooting (from limited testing, it looks like rebooting is required to get SystemD to properly set everything up, logging out and back in is not enough).

Less important note

In KDE Plasma 5.19, which is the future at the time this is written, it should be possible to just let the Plasma desktop run the script on an activity change, so the SystemD path can be avoided.

  1. you can get to it by right-clicking the application launcher and choosing “Edit Applications” []

6 Responses to “Script day – different default browser per KDE activity”

  1. David:
    Fatal error: Uncaught Error: Call to undefined function comment_dir() in /vhosts/coil/geek/public_html/wp-content/themes/modern-bluish/single-comment2.php:17 Stack trace: #0 /vhosts/coil/geek/public_html/wp-content/themes/modern-bluish/functions.php(8): include() #1 /vhosts/coil/geek/public_html/wp-includes/class-walker-comment.php(179): themed_comment() #2 /vhosts/coil/geek/public_html/wp-includes/class-wp-walker.php(144): Walker_Comment->start_el() #3 /vhosts/coil/geek/public_html/wp-includes/class-walker-comment.php(139): Walker->display_element() #4 /vhosts/coil/geek/public_html/wp-includes/class-wp-walker.php(387): Walker_Comment->display_element() #5 /vhosts/coil/geek/public_html/wp-includes/comment-template.php(2229): Walker->paged_walk() #6 /vhosts/coil/geek/public_html/wp-content/themes/modern-bluish/comments.php(26): wp_list_comments() #7 /vhosts/coil/geek/public_html/wp-includes/comment-template.php(1539): require('/vhosts/coil/ge...') #8 /vhosts/coil/geek/public_html/wp-content/themes/modern-bluish/single.php( in /vhosts/coil/geek/public_html/wp-content/themes/modern-bluish/single-comment2.php on line 17