deaddabe

Autodetecting GNOME Dark and Light Mode for Dotfiles Adjustments

Summer is gone, and so are the bright days. This is usually the time of the year when I switch my system to to “dark” mode for the next coming months. However, my terminal and CLI applications do not detect this mode automatically. Nothing that cannot be scripted, though!

Results

For the TL;DR readers, here is the result for the dark theme:

TODO

And for the light theme:

TODO

Switching themes

Since I switched from sway to GNOME for ease of use in daily tasks, one thing that I liked this the ability to flawlessly switch from light to dark theme in the click of a button, and admire the whole system adapt its colors almost imediately: GTK apps, as well as most websites using the correct CSS queries.

To select GNOME’s dark theme, one has to open gnome-tweaks and select the Adwaita-dark theme instead of Adwaita.

Screenshot of the GNOME theme selection window, with the Adwaita-dark theme selected

Once done, the whole system should adapt to the new selected dark variant.

All of it? No. One has to manually switch the terminal theme from black text on white background to white text on black background. Since I am currently using the One Half themes in gnome-terminal, I have to manually switch the profile to the dark variant.

Screenshot of gnome-terminal theme selector

I have not found how to automate this part, but I am sure that there is some magic gsettings command to switch the default profile using the terminal. I could create a winter_is_coming.sh script to change the default gnome-terminal profile from One Half Light to One Half Dark.

Manual change in dotfiles

Now that the terminal is configured to use a white font on black background, I need to adapt my dotfiles to use the dark variants as well. Especially:

Previously, I was setting them manually:

diff --git a/oh-my-zsh-agnoster2/.oh-my-zsh/themes/agnoster2.zsh-theme b/oh-my-zsh-agnoster2/.oh-my-zsh/themes/agnoster2.zsh-theme
index d3a6b31..f2681f9 100644
--- a/oh-my-zsh-agnoster2/.oh-my-zsh/themes/agnoster2.zsh-theme
+++ b/oh-my-zsh-agnoster2/.oh-my-zsh/themes/agnoster2.zsh-theme
@@ -26,7 +26,7 @@
 ### Segment drawing
 # A few utility functions to make it easy and re-usable to draw segmented prompts

-THEME='light'  # can be 'light' or 'dark'
+THEME='dark'  # can be 'light' or 'dark'
 CURRENT_BG='NONE'

 # Special Powerline characters
diff --git a/vim/.vim/init.vim b/vim/.vim/init.vim
index 2057f7c..c528f38 100644
--- a/vim/.vim/init.vim
+++ b/vim/.vim/init.vim
@@ -16,8 +16,8 @@ if has("gui_running")
 endif

 " Color scheme
-set background=light
-colorscheme onehalflight
+set background=dark
+colorscheme onehalfdark

 " Typing {{{1

But this time, it was the occasion to set the theme automatically. This way, if I find myself in a bright day during winter, coding outside thanks to climate change, I can switch my whole system to light mode by changing the GNOME theme.

I'd need to find out how to switch gnome-terminal's default theme from One Half Dark to One Half Light, but still.

Dark theme detection

The first step is to write a small executable script that can be used to detect the theme. Instead of using return codes — for example 0 for dark and 1 for light — I decided to print a string (without \n) in order to easily check for it in my dotfiles, and always return zero to indicate success.

#!/bin/sh
#
# ~/bin/get_theme.sh
#
# Script to detect if dark mode is activated on the system.
# Currently only GNOME is supported.

set -eu

THEME=$(gsettings get org.gnome.desktop.interface gtk-theme)

THEME=${THEME%\'}  # Remove final quote returned by gsettings
SUBSTR=${THEME%-dark}

if [ "$SUBSTR" = "$THEME" ]
then
        printf light
else
        printf dark
fi

Note that gsettings is returning a string surrounded by single quotes:

$ gsettings get org.gnome.desktop.interface gtk-theme
'Adwaita-dark'

This is why I had to use POSIX string substitution in order to remove the last quote, before comparing the THEME to itself with the trailing -dark suffix removed.

We can then call this script from everywhere and detect the current theme:

$ ~/bin/get_theme.sh
dark%

Note that ZSH is printing a % to indicate that there is no newline at the end of the command's write to stdout.

Calling the script in dotfiles

Now that the script is ready, we can call it in the dotfiles that require it.

Inside oh-my-zsh theme

This is the easy part:

THEME=$(~/bin/get_theme.sh)

Then the THEME can be checked in different places of the script:

if [ "$THEME" = 'light' ]; then
    prompt_bg=white
    prompt_fg=black
fi

Inside vim

I had to document myself on how to run commands using vimscript, as well as writing an if statement. Nothing complicated, but this is the first time I am writing vimscript in years of vim usage:

" Color scheme
let g:theme = system("~/bin/get_theme.sh")
if g:theme == "dark"
    set background=dark
    colorscheme onehalfdark
else
    set background=light
    colorscheme onehalflight
endif

Possible improvements

It would be better to detect the current gnome-terminal profile instead of the GNOME theme. This way, I would be able to start light and dark sessions terminal by just switching the profile I want to use. However, detecting the currently running gnome-terminal profile seems like an unsolved problem.

Also, when SSH'ing into remote servers, the server cannot detect the theme. This is because the GNOME session is not running on the server, but on the SSH client. One option to make it work would be to set an environment variable upon ZSH session startup, and then pass and reuse it during SSH sessions. The environment variable could also be checked by (neo) vim, instead of re-running the command on each editor startup.

Not perfect, but not bad either!