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:

#!/bin/bash
# SPDX-License-Identifier: MIT
# SPDX-FileCopyrightText: 2018 Oded Arbel <oded@geek.co.il>
 
function readconf() {
  kreadconfig5 --file ~/.config/kdeglobals --group General --key BrowserApplication
}
 
function writeconf() {
  kwriteconfig5 --file ~/.config/kdeglobals --group General --key BrowserApplication "$1"
  # Additional configuration changes to support GNOME and others
  # as discussed in the comments
  xdg-settings set default-web-browser “$1”
  xdg-mime default "$1" x-scheme-handler/https
  xdg-mime default "$1" x-scheme-handler/http
  xdg-mime default "$1" text/html
}
 
service=org.kde.ActivityManager
interface=$service.Activities
path=/ActivityManager/Activities
signal=CurrentActivityChanged
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
    Personal)
      writeconf firefox.desktop
      ;;
    Work)
      writeconf google-chrome-work.desktop
      ;;
    *) # default in case a new activity is created
      writeconf firefox.desktop
      ;;
  esac
  echo "Current Browser is $(readconf)"
done

 

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:

[Unit]
Description=KDE Activity Monitor
 
[Service]
ExecStart=/home/odeda/.local/bin/activity-monitor
 
[Install]
WantedBy=default.target

 

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.

Licensing Note

The code above is free to use for any purpose without limitation – I don’t consider copying it from this website for your own personal use as “copying” for the purpose of copyright. If you intend to modify and redistribute the above code for your own needs, the code is licensed under the MIT license, as specified in the script itself.

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

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

  1. David:

    Hi,

    I just found this post when trying to solve the exact same problem with multiple browsers using KDE’s Activities. Your solution works perfectly and it saved my some time figuring that out myself, so thanks a lot! 😉

    David

  2. Raphael:

    Created this same script in python: https://gist.github.com/en-lofty/991d33b11877ab177dbf6ab8e72d10b3

  3. Paco:

    Hi!
    I was just looking for that, a way to define default browser per current plasma activity. I’ve been following the article, but at the end something is not working on my setup.

    When I let systemd run the script, it’s failing because it can not contact DBUS:

    cigro systemd[1604]: Started KDE Activity Monitor.
    cigro change_default_browser_per_activity.sh[8737]: Failed to open connection to session bus: Unable to autolaunch a dbus-daemon without a $DISPLAY for X11

    • Oded:

      That is very weird – it sounds like you do not have DBus running. Try running `systemctl –user status dbus.service` – you should get a large status page with a green “active” label and a lot of information that should all sound positive. If this *is* the case – is it possible that you did not install the activity monitor as a user service, but instead as a system service?

  4. Paco:

    I’m using Ubuntu, so in order to enable `dbus.service` I need to install `dbus-user-session`. After that I could run my the `activity-monitor` but the DBUS_SESSION_BUS_ADDRESS on the systemd process and the one I got on a terminal are very different. So the systemd process is not aware about the activity events.

    DBUS_SESSION_BUS_ADDRESS inside the systemd process is `/run/user/$UID/bus` and on the terminal is something like `/tmp/dbus-RxW9pceAsk,guid=1d3ae57411c17ecacf1e3fa55e9e94e5`

    I’m sure it’s an ubuntu / debian related problem but I don’t know how to act.

    • Oded:

      From my experience dbus-user-session is installed by default in current Ubuntu version (I just tested that it is installed by default in Ubuntu 19.10). Apparently, it is required for SystemD user services to have access to the user’s DBus session and display (the alternative is having only dbus-x11 installed which means that neither DBus user session nor the X11 display is visible to SystemD services. I failed to account for that in the article and I will update that.

      The /tmp dbus address is created by dbus-x11 when it doesn’t have dbus-user-session. If you did install dbus-user-session manually, you’d need to reboot your system for that to take effect (I tried logging out and back in, but that doesn’t seem to help – it could be an issue with gdm, but I had limited time to do testing).

  5. Ed:

    Hello! I was looking for a solution to the same issue and I found this page. First of all, thank you so much for this, I have no knowledge of coding so this is really helpful. Anyway I followed the instructions, with the sole differences being the name of one activity (I put Home where you wrote Personal), the name of the second browser (brave-browser.desktop) and the location of the script in the service file. The service starts without errors, but it has no effect.

    In fact running “systemctl –user status activity-monitor.service” returns “install activity-monitor[17299]: /home/tw/.bin/activity-monitor: line 15: : command not found”.

    Line 15 is “dbus-monitor –profile “type=signal,path=$path,interface=$interface,member=$signal” | \ “. Is there something I should modify there to make it work?

    I am running opensuse with plasma 5.22. Sorry to bother you and thanks again!

    • Oded:

      It looks like you are missing the `dbus-monitor` command, which is kind of the crux of the matter (I mentioned it in the first paragraph of this article). It should be part of the dbus package so I find is very weird that it is not available on your installation.

      Please check if you can run `dbus-monitor` in a terminal.

    • irfan798:

      Hey I’m on KDE 25.24.5 and it seems deleting `\` from the line starting with `dbus-monitor –profile` to make it single line works.

      So full line would be
      “`
      /usr/bin/dbus-monitor –profile “type=signal,path=/ActivityManager/Activities,interface=org.kde.ActivityManager.Activities,member=CurrentActivityChanged” | while read type timestamp serial sender destination path interface member; do
      “`

      —-

      Also on the writeconf i needed to add these lines to make it really global:
      “`
      function writeconf() {
      kwriteconfig5 –file ~/.config/kdeglobals –group General –key BrowserApplication “$1”
      xdg-settings set default-web-browser “$1”
      xdg-mime default “$1” x-scheme-handler/https
      xdg-mime default “$1” x-scheme-handler/http
      }
      “`

  6. BWPanda:

    I know it’s been a while, but any updates on:

    “In KDE Plasma 5.19 […] it should be possible to just let the Plasma desktop run the script on an activity change, so the SystemD path can be avoided.”

  7. Ricardo:

    Sorry for necro-bumping, I found this post very useful and just wanted to add my $0.02 mentioning a tool that makes INI file “very easy to manipulate via scripts”:

    https://github.com/pixelb/crudini

    I’ve been using it for several years now, it’s not perfect but has served me well for manipulating mysql, openstack and ceph configs to name a few.

    Cheers!

  8. Morgan:

    Thanks so much for this script, I’ve been using it for years now.

    In KDE6 the kwriteconfig line doesn’t do anything so I’ve just commented it out but the xdg-settings and xdg-mime lines cause quite a delay each time the activity changes. If I run those lines in a new process the delay will go away but the logging will be lost. Is there a way to keep the logging but fix the activity switch delay?

    • Oded:

      The kwriteconfig still affects the Plasma setting for “Default Applications” – how much that does anything more than the MIME settings – it is probably relevant for some use cases, though none come to mind at this point. If kwriteconfig5 does not work for you, you can try using kwriteconfig6 (though the KXMLConfig framework’s file format hasn’t changed and from my experience kwriteconfig5 does not cause issues with Plasma 6 – at least not in this use).

      As for slowness – on my system xdg-settings takes 0.5s to run and xdg-mime takes about 0.2s. But more than that – I’m not sure what delay is of an issue here: the setup described in this post is about running a standalone service that monitors events and acts independently – it does not block any Plasma operation so any slowness in the script shouldn’t affect behavior, except for maybe that the browser change will not take effect immediately after an activity change – so if you get into the habit of positioning your cursor on where a link would appear on the next activity, then switching and immediately clicking the link – that could be a problem, though I don’t think that this use case is reasonable. Also – I’m not sure about the logging issue: the writeconf function is the last thing executed in the monitor loop and it doesn’t do any logging. Generally I wouldn’t recommend running the file modification commands in parallel – as they all basically modify the same files and you don’t want to get into multiple commands modifying the same file concurrently – they either know how to handle this well (using file locking), in which case you gain almost nothing, or they don’t – and you get corruption.

Leave a Reply