Home whoami

Automatically change theme in Emacs

2023-04-17 10:03

When I code, I sometimes move from a place to another - where light condition is not the same. Usually, I would either adapt to these changed condition passively, or aggressively switch from dark to light mode, for instance.

Let's dive into the solution I'm using!

Main source

Emacs does not offer something out-of-the-box, but people already solved the problem - but it is not working for me (Ubuntu 22.10).

As it needed some adaptation, let's just write down what the solution is about, which is maybe interesting for someone else.

The dbus events

First thing to notice, is that "clicking" on the "Dark mode" button actually creates a signal on d-bus which may be used by other apps to react on the event.

This is pretty cool, as Emacs can read these events and react accordingly - and this is what we're going to do.

The first point is trying to understand what is the exact event we should listen to. An easy way to do it is to use dbus-monitor and inspect what it says when switching mode:

signal time=1681719008.674741 sender=:1.69 -> destination=(null destination) serial=409 path=/org/freedesktop/portal/desktop; interface=org.freedesktop.portal.Settings; member=SettingChanged
   string "org.gnome.desktop.interface"
   string "color-scheme"
   variant       string "prefer-dark"
signal time=1681719008.674781 sender=:1.69 -> destination=(null destination) serial=410 path=/org/freedesktop/portal/desktop; interface=org.freedesktop.portal.Settings; member=SettingChanged
   string "org.freedesktop.appearance"
   string "color-scheme"
   variant       uint32 1
...
...
signal time=1681719010.217220 sender=:1.69 -> destination=(null destination) serial=413 path=/org/freedesktop/portal/desktop; interface=org.freedesktop.portal.Settings; member=SettingChanged
   string "org.gnome.desktop.interface"
   string "color-scheme"
   variant       string "default"
signal time=1681719010.217262 sender=:1.69 -> destination=(null destination) serial=414 path=/org/freedesktop/portal/desktop; interface=org.freedesktop.portal.Settings; member=SettingChanged
   string "org.freedesktop.appearance"
   string "color-scheme"
   variant       uint32 0

Nice! We can immediately see what are the properties we should check when receiving the message, and also what is the value we should react to. In this specific case, we see that the org.freedesktop.appearance has a "property" named color-scheme that changes from 0 to 1 (and back). Let's modify the snippet.

Show the code already

As seen above, we will be reacting on a different property. Also, I'm not using Doom emacs, so let's just remove the extra code.

(defun theme-switcher--theme_dark ()
  (progn
  (message "Loaded dark theme"))
  (consult-theme 'zenburn))

(defun theme-switcher--theme_light ()
  (progn
  (message "Loaded light theme"))
  (consult-theme 'doom-acario-light))

(defun theme-switcher--handle-dbus-event (key setting values)
  "Handler for FreeDesktop theme changes."
  (when (string= key "org.freedesktop.appearance")
    (when (string= setting "color-scheme")
      (let ((scheme (car values)))
        (cond
         ((= 0 scheme)
          (theme-switcher--theme_light))
         ((= 1 scheme)
          (theme-switcher--theme_dark))
         (t (message "Unknown value for the color-scheme: %s" scheme)))))))


(dbus-register-signal :session
                      "org.freedesktop.portal"
                      "/org/freedesktop/portal/desktop"
                      "org.freedesktop.portal.Settings"
                      "SettingChanged"
                      #'theme-switcher--handle-dbus-event)

The code is pretty straightforward - and maybe can be done better, but whatever. We use dbus-register-signal to listen to the specific event we're interested in, and we add an hook.

The hook itself (theme-switcher--handle-dbus-event) receives 3 parameters, which are the key, the settings, and the value, and we use them to know what theme to apply.

This works now fine the "switching" theme, but we are unable to know what is the "default" theme - basically, what is the theme emacs should be into when starting the program? Right now, I'm defaulting to dark mode (the safest option) but it would be great to load the "settings" and set the theme on startup.

Anyway, hope this helps someone - it did help me!