… but won’t if one has a custom theme loaded, as far as I can tell.

I don’t particularly like dark mode. Its use seems more preference than science, so for those who like it, cheers! However, from time to time dark mode would be good. In the evening when I’m simply reading my RSS feeds in elfeed, for example.

I wanted something that would make the switch without adding much weight. Here’s the journey:

  1. Looked at what Prot uses/recommends Modus Themes | Protesilaos Stavrou
  2. Led me to GitHub – guidoschmidt/circadian.el: Theme-switching for Emacs based on daytime
  3. That wasn’t gonna work for me. I realized I wanted this to happen in early-init.el so a package-based option wasn’t right.
  4. Light DDG-ing later and I found Emacs and Dark Mode
  5. That code would work but only on emacs-plus. But the system signals other apps when to switch, so emacs-mac must have something too …
  6. Dug around in the docs for emacs-mac and found some breadcrumbs.
  7. Which led me to Custom Emacs functions No. 4 – UI | macOS & (open-source) Software
  8. Yes! Nearly there. Timu’s code is good but is customized to his use case. I want something more utilitarian.
  9. Stripped out the bits that work for emacs-mac and maybe emacs-plus and rebuilt it into this with a little help from the robot:

(defvar my/light-theme 'modus-operandi-tinted
  "Theme to use when system appearance is light.")

(defvar my/dark-theme 'modus-vivendi-tinted
  "Theme to use when system appearance is dark.")

(defun my/apply-theme-based-on-appearance (&optional appearance)
  "Switch between `my/light-theme` and `my/dark-theme` based on APPEARANCE.

Handles both Emacs Plus (ns, using `appearance`) and Emacs Mac Port (mac, via `mac-application-state`)."
  (let ((mode
         (cond
          ;; Emacs Plus: appearance is passed as symbol
          ((boundp 'ns-system-appearance-change-functions) appearance)

          ;; Emacs Mac Port: detect from system plist
          ((plist-get (mac-application-state) :appearance)))))

    (pcase mode
      ((or 'light "NSAppearanceNameAqua")
       (mapc #'disable-theme custom-enabled-themes)
       (load-theme my/light-theme t))

      ((or 'dark "NSAppearanceNameDarkAqua")
       (mapc #'disable-theme custom-enabled-themes)
       (load-theme my/dark-theme t)))))

(defun my/setup-theme-switching ()
  "Set up automatic theme switching on macOS appearance change."
  (if (boundp 'ns-system-appearance-change-functions)
      ;; Emacs Plus
      (add-to-list 'ns-system-appearance-change-functions #'my/apply-theme-based-on-appearance)
    ;; Emacs Mac Port
    (progn
      (add-hook 'mac-effective-appearance-change-hook #'my/apply-theme-based-on-appearance)
      (add-hook 'after-init-hook #'my/apply-theme-based-on-appearance))))

(my/setup-theme-switching)

This seems to check all the boxes:

  • appropriate for early-init.el
  • doesn’t call external scripts or tools
  • relies on the OS and emacs-mac and emacs-plus built-in capabilities
  • it works, for me at least

Some notes:

  • Because this runs in early-init.el, needs setting up with built-in themes that are available before the package system loads. Thankfully, Prot’s modus-themes are built-in now.
  • Any other GUI setup stuff like initial-frame-alist, default-frame-alist and set-face-attribute needs configuring before this (I think; maybe not?).

As always, YMMV. Please play around with this and let me know how it works for you. I’m working on my LISP coding skills, so buyer beware.

Comments are closed.

To respond on your own website, enter the URL of your response which should contain a link to this post's permalink URL. Your response will then appear (possibly after moderation) on this page. Want to update or remove your response? Update or delete your post and re-enter your post's URL again. (Find out more about Webmentions.)

Reposts