;;; auto-dark.el --- Automatically set the dark-mode theme based on system status -*- lexical-binding: t; -*- ;; Author: Rahul M. Juliato ;; Tim Harper ;; Vincent Zhang ;; Jonathan Arnett ;; Greg Pfeil ;; Created: July 16 2019 ;; Version: 0.13.10 ;; Keywords: macos, windows, linux, themes, tools, faces ;; URL: https://github.com/LionyxML/auto-dark-emacs ;; Package-Requires: ((emacs "24.4")) ;; SPDX-License-Identifier: GPL-2.0-or-later ;;; Commentary: ;; Auto-Dark is an auto-changer between 2 themes, dark/light, respecting the ;; overall settings of MacOS, Linux and Windows. ;; To enable it, install the package and add it to your load path: ;; ;; (require 'auto-dark) ;; (auto-dark-mode t) ;; ;; To customize the themes used by light/dark mode: ;; ;; M-x customize-group auto-dark ;; ;; If you're using Doom Emacs or Spacemacs, follow the installation tips ;; on https://github.com/LionyxML/auto-dark-emacs. ;; ;;; Code: ;; Optional require of dbus squelches elisp compilation warnings (require 'dbus nil t) (defgroup auto-dark nil "Automatically changes Emacs theme acording to MacOS/Windows dark-mode status." :group 'tools :prefix "auto-dark-*") (defcustom auto-dark-polling-interval-seconds 5 "The number of seconds between which to poll for dark mode state. Emacs must be restarted for this value to take effect." :group 'auto-dark :type 'integer) (defcustom auto-dark-allow-osascript nil "Whether to allow function `auto-dark-mode' to shell out to osascript: to check dark-mode state, if `ns-do-applescript' or `mac-do-applescript' is not available." :group 'auto-dark :type 'boolean) (defcustom auto-dark-allow-powershell nil "Whether to allow function `auto-dark-mode' to shell out to powershell: to check dark-mode state." :group 'auto-dark :type 'boolean) (defcustom auto-dark-detection-method nil "The method auto-dark should use to detect the system theme. Defaults to nil and will be populated through feature detection if left as such. Only change this value if you know what you're doing!" :group 'auto-dark :type 'symbol :options '(applescript osascript dbus powershell winreg termux)) (defvar auto-dark--last-dark-mode-state 'unknown) (defvar auto-dark--dbus-listener-object nil) (defun auto-dark--current-mode-applescript () "Invoke AppleScript using Emacs built-in AppleScript support. In order to check if dark mode is enabled. Return true if it is." (if (fboundp 'ns-do-applescript) (if (auto-dark--is-dark-mode-ns) 'dark 'light) (if (fboundp 'mac-do-applescript) (if (auto-dark--is-dark-mode-mac) 'dark 'light) (error "No AppleScript support available in this Emacs build. Try setting `auto-dark-allow-osascript` to t")))) (defun auto-dark--is-dark-mode-ns () "Check if dark mode is enabled using `ns-do-applescript'." ;; FIXME: We shouldn’t need to check `fboundp' on every call, just when ;; setting the detection method. (when (fboundp 'ns-do-applescript) (string-equal "true" (ns-do-applescript "tell application \"System Events\" tell appearance preferences if (dark mode) then return \"true\" else return \"false\" end if end tell end tell")))) (defun auto-dark--is-dark-mode-mac () "Check if dark mode is enabled using `mac-do-applescript'." ;; FIXME: We shouldn’t need to check `fboundp' on every call, just when ;; setting the detection method. (when (fboundp 'mac-do-applescript) (string-equal "\"true\"" (mac-do-applescript "tell application \"System Events\" tell appearance preferences if (dark mode) then return \"true\" else return \"false\" end if end tell end tell")))) (defun auto-dark--is-dark-mode-osascript () "Invoke applescript using Emacs using external shell command; this is less efficient, but works for non-GUI Emacs." (string-equal "true" (string-trim (shell-command-to-string "osascript -e 'tell application \"System Events\" to tell appearance preferences to return dark mode'")))) (defun auto-dark--current-mode-dbus () "Use Emacs built-in D-Bus function to determine if dark theme is enabled." (pcase (caar (dbus-ignore-errors (dbus-call-method :session "org.freedesktop.portal.Desktop" "/org/freedesktop/portal/desktop" "org.freedesktop.portal.Settings" "Read" "org.freedesktop.appearance" "color-scheme"))) (1 'dark) ((or 0 2) 'light))) (defun auto-dark--is-dark-mode-powershell () "Invoke PowerShell and detect dark mode. Compatible with both bash pure output and zsh mixed output." (let* ((raw-output (shell-command-to-string "powershell.exe -noprofile -noninteractive -nologo -ex bypass -command \ Get-ItemPropertyValue 'HKCU:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize' \ -Name AppsUseLightTheme")) (lines (split-string raw-output "\n" t)) (numeric-result (catch 'found (dolist (line lines) (let ((trimmed (string-trim line))) (when (string-match-p "^[0-9]+$" trimmed) (throw 'found trimmed)))) nil))) (and numeric-result (string-equal "0" numeric-result)))) (defun auto-dark--is-dark-mode-winreg () "Use Emacs built-in Windows Registry function. In order to determine if dark theme is enabled." ;; FIXME: We shouldn’t need to check `fboundp' on every call, just when ;; setting the detection method. (when (fboundp 'w32-read-registry) (eq 0 (w32-read-registry 'HKCU "Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize" "AppsUseLightTheme")))) (defun auto-dark--is-dark-mode-termux () "Use Termux way to determine if dark theme is enabled. ref: https://github.com/termux/termux-api/issues/425." (string-equal "Night mode: yes" (shell-command-to-string "echo -n $(cmd uimode night 2>&1