I've decided to go outside more, touching grass and all that. You obviously want to bring your laptop when doing so, which surfaced the problem of not being able to read text on my screen when using a dark theme.
Phones, some proprietary operating systems and even some Linux desktop environments have the concept of "adaptive themes", which means basically switching between a light and a dark theme dynamically, without the need to restart applications.
Linux at day, linux at night
The dark/light preference used to be indicated in different ways on Linux.
GTK-based applications wrote to ~/.config/gtk-3.0/settings.ini, Qt did
something else and others tried to create their own standards.
The cool kids now have agreed on using the XDG desktop portal D-Bus
API.
It exposes the system's preferred color scheme through the
org.freedesktop.appearance namespace with the color-scheme key.
It can be queried with dbus-send:
The number at the end shows the preference
0: No preference1: Prefer dark appearance2: Prefer light appearance
Some applications natively listen to that these days (e.g. firefox). Others need custom configuration for it.
Neovim
First, the editor. I wrote a listener in lua, which can be used in the Neovim
config. Save this
file
as theme-toggle.lua, then it can be required and used like this:
require.
I made it a configurable function for what to be run on toggle, so it can be
extended to not only change vim.opt.background but also switch theme settings
for the statusline (e.g. lua-line) and more.
Terminal
Some terminals, e.g. Ghostty already support configuring two themes, a dark and a light one, which get switched automatically.
My terminal emulator, Rio terminal was missing that feature on Linux so I added it in this pull request, which should be included in the next release.
Sway
For sway (and other applications) I created a SystemD user service in NixOS in a module that allows passing it script to toggle things.
{
config,
lib,
pkgs,
...
}:
with lib;
let
themeWatcherScript = pkgs.writeShellScript "theme-watcher" ''
update_theme() {
local scheme=$(pkgs.dconf/bin/dconf read /org/gnome/desktop/interface/color-scheme 2>/dev/null)
local theme="prefer-dark"
if [[ "$scheme" == "'prefer-light'" ]]; then
theme="prefer-light"
fi
concatStringsSep "\n" (
map (script: ''
script "$theme"
'') cfg.scripts
)
}
# Set initial theme
update_theme
# Monitor dbus for changes to color-scheme
pkgs.dconf/bin/dconf watch /org/gnome/desktop/interface/color-scheme | while read -r line; do
update_theme
done
'';
in
{
options.pinpox.services.theme-switcher = {
enable = mkEnableOption "theme switcher service";
scripts = mkOption {
type = types.listOf types.str;
default = [ ];
description = ''
List of scripts to call when theme changes. Each script will be called
with one argument: either "prefer-light" or "prefer-dark".
'';
};
};
config = mkIf cfg.enable {
systemd.user.services.theme-watcher = {
Unit = {
Description = "Theme watcher - updates applications on dbus theme change";
PartOf = [ "graphical-session.target" ];
After = [ "graphical-session.target" ];
};
Service = {
Type = "simple";
ExecStart = "themeWatcherScript";
Restart = "on-failure";
RestartSec = 3;
};
Install = {
WantedBy = [ "graphical-session.target" ];
};
};
};
The pinpox.services.theme-switcher.scripts option is a list of strings that
can be set from all other parts of the configuration (e.g. sway) that want to be
switched. It expects script that can be called with exactly one argument:
"prefer-light" or "prefer-dark" and calls all of them when the preferred theme
changes.
For sway I used:
pinpox.services.theme-switcher =
let
swayThemeSwitcher = pkgs.writeShellScript "sway-theme-switcher" ''
theme="$1"
if [[ "$theme" == "prefer-light" ]]; then
pkgs.sway/bin/swaymsg "lightColors"
else
pkgs.sway/bin/swaymsg "darkColors"
fi
'';
in
{
scripts = [ "swayThemeSwitcher" ];
};
Switcher Script
The following script can be used to toggle between light and dark themes easily. I have created a keybind for it in sway.
#!/usr/bin/env bash
# Read current setting
current=
if || ; then
else
fi