Emacs Themes¶
Automatic Theme Switching¶
I’m often switching between a light and a dark theme depending on light levels and time of day so the more applications that can automatically sync with the system setting the better. Of course, with Emacs, it’s possible to wire up a couple of DBus incantations to both query and detect changes to the system’s light/dark preference.
But first, let’s create a few options to control which themes I want to load, plus a function to automatically call the right one given a preference.
emacs/lisp/alc-theme.el
(defvar alc-theme-load-light-theme-function nil
"The function to call in order to load the configured light theme")
(defvar alc-theme-load-dark-theme-function nil
"The function to call in order to load the configured dark theme")
(defun alc-theme-load-theme (variant)
"Load the light or dark theme according to VARIANT."
(pcase variant
('light (funcall alc-theme-load-light-theme-function))
('dark (funcall alc-theme-load-dark-theme-function))
(_ (error "Unknown theme variant: %s" variant))))
(defun alc-theme--system-preference-to-variant (pref)
"Simple helper function for converting the system PREF into the
corresponding variant."
(if (eq pref 1) 'dark 'light))
Why use functions?
As you will see below, I almost exclusively use themes created by Protesilaos <https://protesilaos.com/> and these themes often provide a load-theme
function that will run some hooks after the theme is loaded, allowing for further customisation.
Plus, with a function there’s room to perform any arbitrary setup you might need.
Next, a function that will call the relevant function for the given theme variant (Thanks to auto-dark-emacs for the DBus specifics!)
emacs/lisp/alc-theme.el
(require 'dbus)
(defun alc-theme-sync-to-system-theme (_frame)
"Choose a light/dark theme based on the current system preference."
(let ((pref (caar (dbus-call-method
:session
"org.freedesktop.portal.Desktop"
"/org/freedesktop/portal/desktop"
"org.freedesktop.portal.Settings" "Read"
"org.freedesktop.appearance" "color-scheme"))))
(alc-theme-load-theme (alc-theme--system-preference-to-variant pref))
(alc-theme--register-dbus-listener)))
The above function takes an unused _frame
parameter since I currently add this to the after-make-frame-functions
hook.
I’m not an expert in Emacs’ startup, but based on my experience so far, it seems to be the best time to have this function execute.
The alc-theme--register-dbus-listener
function, defined below, is repsonsible for setting up a dbus listener that will automatically rerun the alc-theme-load-theme
function when the system preference changes.
Again, thank you to auto-dark-emacs
for the DBus specifics.
emacs/lisp/alc-theme.el
(defvar alc-theme--dbus-listener nil
"Variable in which to store the dbus listener.")
(defun alc-theme--register-dbus-listener ()
"Register (if necessary) a DBus listener to react to changes in the
system's light/dark preference."
(unless alc-theme--dbus-listener
(setq alc-theme--dbus-listener
(dbus-register-signal
:session
"org.freedesktop.portal.Desktop"
"/org/freedesktop/portal/desktop"
"org.freedesktop.portal.Settings"
"SettingChanged"
(lambda (path var val)
(when (and (string= path "org.freedesktop.appearance")
(string= var "color-scheme"))
(alc-theme-load-theme
(alc-theme--system-preference-to-variant (car val)))))))))
If I ever need to, the listener can be disabled by running (dbus-unregister-object alc-theme--dbus-listener)
Theme Customisations¶
Doric Themes¶
Prot’s new doric-themes are worth exploring
Modus Themes¶
The excellent modus-themes are available out of the box in recent Emacs versions.
emacs/lisp/alc-theme.el
(provide 'alc-theme)