User Tools

Site Tools


sway:dark-mode

On themes, sway, emacs and kitty - the dark-mode script

Unlike Desktop Environments like Gnome and KDE, Window Managers like sway do not come with all the tools you might need or want. It's up to the user to perceive a need, do the research and then install, configure and use the appropriate tools for the job. Notwithstanding the earnest attempts by the DE's to bury the implementation details in obscure code and non-existent documentation, this approach gives me a lean and mean working environment albeit not as pretty. Plus the satisfaction of knowing what's going on.

In my own history through fvwm, fluxbox, i3wm and now sway I've never really wanted to bother with themes. I just made terminals and emacs have a black background and other programs could do what ever they liked. Such a heathen!

Recently I felt that need and did some research into the topic. It was rather more complex and harder to discover than I had thought.

I wanted to have a simple switch I could flick, perhaps a script, that would turn night into day. Let's have black text on a bright, white background in the daytime and reverse it in the evening. I wanted to have the changes applied to running programs without restarting them.

Sounds simple? Here are some notes on what I discovered for customising and a script that I use to do the flick.

All of this is on Fedora-31 but should be similar for other Linux distros.

Apologies if this is all obvious and well documented elsewhere. It was a journey for me and non-trivial.

GTK2 programs

GTK2 programs start up referring to ~/.gtkrc-2.0 for their theme hints. Simple really, just lob in a new value:

gtk-theme-name=“Adwaita”

or

gtk-theme-name=“Adwaita-dark”

The hard part was understanding how to get running programs to notice the change. lxappearance(1) is a program from the LXDE desktop environment that does both jobs. Unfortunately, it lacks a CLI interface so it can only be used in the pointy-clicky mode and not in a script.

By using strace(1) on the different components, I was able to track this down to a new (for me) system call eventfd(2) which GTK2 uses as a signalling mechanism. With a bit more research I found this script fragment at https://crunchbang.org/forums/viewtopic.php?id=39646 which invokes the correct event in the GTK2 world and makes those programs re-read ~/.gtkrc-2.0:

#!/usr/bin/python2
import sys, gtk
events=gtk.gdk.Event(gtk.gdk.CLIENT_EVENT)
data=gtk.gdk.atom_intern("_GTK_READ_RCFILES", False)
events.data_format=8
events.send_event=True
events.message_type=data
events.send_clientmessage_toall()''

GTK3 programs

GTK3 programs start up referring to ~/.config/gtk-3.0/settings.ini but the format is slightly different (of course!)

gtk-theme-name=Adwaita-dark

Note the cunning change in the use of quotation marks.

Again, getting programs that are already running to notice the change is lightly documented, if at all. But I found that by installing gnome-settings-daemon (a bit of an overkill for my lightweight purposes) there is a daemon that can do the job. So I added this to my sway config:

exec /usr/libexec/gsd-xsettings

I just wonder if this could be replaced by a simple script like the GTK2 one?

But that appears to be not the entire story.

dconf

Some programs do not respond to gsd-xsettings and dconf is needed:

dconf write /org/gnome/desktop/interface/gtk-theme "'Adwaita-dark'"

Again, note the amusing change in quotation - single quotes are essential. The dconf approach also appears to work for live GTK2 programs and overrides the value in ~/.gtkrc-2.0 and ~/.config/gtk-3.0/settings.ini

gsettings

More comedy arises from yet another path to the same outcome. This invocation appears to be completely equivalent with it's own permutation on the quotation merry-go-around:

gsettings set org.gnome.desktop.interface gtk-theme Adwaita-dark

emacs

emacs(1). Hmmm. I've grown up with that program and am unlikely to wean myself off it. There are 'PureGTK' versions floating around as experiments but they are yet to hit the Fedora repositories outside COPR so I haven't tried them to see how they respond to the standard GTK signals.

In any case, for emacs(1) I need to also modify the internal text areas and emacs(1) has its own themes spelled out in elisp. I decided on a couple of good themes and added some code to my script to flip from one theme to another. The script should be self explanatory. The themes I chose were a modified dichromacy (for the colourblind) and a derived dichromacy-dark which I created by 'inverting' the original using a script at https://explog.in/notes/poet.html#monochrome

kitty

Now for my favorite wayland terminal emulator, kitty(1). It has it's own unique configuration language and can have its colour changed on the fly like this:

kitty @ set_colors --all foreground=white background=black

bash

My bash(1) prompt contains colours and I have a separate script (actually a function in ~/.bashrc) that responds appropriately to the setting of TERM_BACKGROUND in {dark,light} so my script outputs that setting and invokes setup_prompt.

eval $( dark-mode on )

… 'dark-mode on' outputs TERM_BACKGROUND=dark; setup_prompt and that's what gets eval'd.

These final 2 steps are the reason I don't put dark-mode(1) into a cron job - it needs to run in my stack of kitty(1) terminal tabs.

KDE

KDE/Qt5/Plasma/whatcha-ma-call-it

Thanks to redditor r/progandy for this:

qt5ct(1) is the key - install it (Fedora) with:

sudo dnf install qt5ct

This needs to be in the session environment:

export QT_QPA_PLATFORMTHEME=qt5ct

After running qt5ct(1) I now have a ~/.config/qt5ct/qt5ct.conf file that contains a line:

style=Adwaita

… I can hit that with my script and the qt5 programs automatically change their appearance!

Oddly, there's a 4s delay after changing the style in the file before the new style takes effect. But that's OK.

I also built qt5ct-refresh but it doesn't seem to speed up the 4s delay - or do anything AFAICT.

dark-mode

Here is the result of all this rambling:

#!/usr/bin/env bash

# dark-mode [on|off]
# sets dark mode on or off

# best to run as:
# eval $( dark-mode on )

# for emacs, these themes can be chosen from the standard ones (in
# custom-theme-load-path) or they can be installed in
# custom-theme-directory (usually ~/.emacs.d)
# They must have a filename of $THEMENAME-theme.el

light_emacs_theme="dichromacy-bh"
light_emacs_modeline_theme="smart-mode-line-light"
#dark_emacs_theme="manoj-dark-bh"
dark_emacs_theme="dichromacy-dark-bh"
dark_emacs_modeline_theme="smart-mode-line-dark"
case "$1" in
    off|stop|reset|end|light)
        # light mode
        new_emacs_theme="$light_emacs_theme"
        prev_emacs_theme="$dark_emacs_theme"
        new_emacs_modeline_theme="$light_emacs_modeline_theme"
        prev_emacs_modeline_theme="$dark_emacs_modeline_theme"

        new_gtk_theme='Adwaita'
        new_fg='black'
        new_bg='white'
        BRIGHTNESS=100
        TERM_BACKGROUND=light
        ;;
    *)
        # dark mode
        new_emacs_theme="$dark_emacs_theme"
        prev_emacs_theme="$light_emacs_theme"
        new_emacs_modeline_theme="$dark_emacs_modeline_theme"
        prev_emacs_modeline_theme="$light_emacs_modeline_theme"

        new_gtk_theme='Adwaita-dark'
        new_fg='white'
        new_bg='black'
        BRIGHTNESS=0
        TERM_BACKGROUND=dark
        ;;
esac

(
    # look for an emacs running as this user:
    emacs_pid=$(pgrep -u $USER emacs | head -n 1)
    [[ "$emacs_pid" ]] &&
        emacsclient --eval "
          (progn
            (disable-theme '$prev_emacs_theme)
            (disable-theme '$prev_emacs_modeline_theme)
            (load-theme '$new_emacs_theme)
            (load-theme '$new_emacs_modeline_theme))"

    f=~/.gtkrc-2.0
    [[ -w $f ]] &&
        sed -i "s/^gtk-theme-name=.*/gtk-theme-name=\"$new_gtk_theme\"/" $f
    gtkreload # gtk2 only!

    f=~/.config/gtk-3.0/settings.ini
    [[ -w $f ]] &&
        sed -i "s/^gtk-theme-name=.*/gtk-theme-name=$new_gtk_theme/" $f
    # /usr/libexec/gsd-xsettings notifies gtk3 clients - start it in sway

    # these two appear to be comletely equivalent:
    dconf write /org/gnome/desktop/interface/gtk-theme "'$new_gtk_theme'"
    #gsettings set org.gnome.desktop.interface gtk-theme $new_gtk_theme

    f=~/.config/qt5ct/qt5ct.conf
    [[ -w $f ]] &&
        sed -i "s/^style=.*/style=$new_gtk_theme/" $f

    [[ "$KITTY_WINDOW_ID" ]] &&
        kitty @ set_colors --all foreground=$new_fg background=$new_bg

    # from https://bbs.archlinux.org/viewtopic.php?id=134972:
    brightness $BRIGHTNESS
) >/dev/null

echo "TERM_BACKGROUND=$TERM_BACKGROUND; setup_prompt"
sway/dark-mode.txt · Last modified: 2020/10/14 22:33 by admin