Set Desktop Color Scheme with Pywal
Wouldn't it be cool to have your wallpaper and all app and desktop colors match in a unified color scheme!
Concept
eylles/pywal16 is a tool that extracts dominant and accent colors from an image and creates a 16-color scheme from it.
Once this program has extracted a color scheme, it outputs that color scheme in many different formats. Each format is created to serve the need of some client program(s).
After a color scheme has been set, the client program picks up the change automatically, or is informed, and it sets its colors according to the color scheme in the appropriate file.
Thus, from the image, usually a wallpaper background, to all/most client programs, you have a unified color scheme.
Installation
To install pywal, you'll need Python. Then:
pip install pywalVerify your install:
wal -v
wal 3.8.14Other install options are in the docs.
To see what later versions may exist:
pip index versions pywal
pywal (3.8.15)
Available versions: 3.8.15, 3.8.14, 3.8.13, 3.3.0 [...]
INSTALLED: 3.8.14
LATEST: 3.8.15To update:
pip install -U pywalCreate a Template
Pywal, out of the box, supports many different client program color scheme formats. If you want to create support for another, all you have to do is create a template file. Here are a couple that I've created:
ls -1 ~/.config/wal/templates
colors-alacritty.toml
colors-dwm-xrdbLet's use the colors-dwm-xrdb file as an example going forward. It's for a minimalistic/hackable window manager called dwm. Its template looks like this:
dwm.normbgcolor: {background}
dwm.normbordercolor: {background}
dwm.normfgcolor: {foreground}
dwm.selbgcolor: {color5}
dwm.selbordercolor: {color3}
dwm.selfgcolor: {background}Notice that dwm doesn't even use all 16 colors. What this file is doing is bridging the client format (eg dwm.normbgcolor) with the pywal format (eg background). When wal runs, it's going to use this template and output a color scheme:
cat ~/.cache/wal/colors-dwm-xrdb
dwm.normbgcolor: #5a2200
dwm.normbordercolor: #5a2200
dwm.normfgcolor: #d1c9c4
dwm.selbgcolor: #edd3a3
dwm.selbordercolor: #f3a06d
dwm.selfgcolor: #5a2200Generate Colors
To generate a color scheme from an image, run:
wal -i myimage.jpgBy default, this also sets the image as your wallpaper background.
If you'd rather use a random image, point it at a directory of images:
wal -i ~/my/imagesI also like to add this option to make the color scheme brighter:
wal --saturate 0.4 -i ~/my/imagesTo see the color scheme output to your terminal, type:
wal --previewThere are many other options, seen with wal --help.
Automatically Generating a Color Scheme
In addition to calling wal via an interactive shell, you can have scripts run this at startup time.
I'm starting my system with a display manager called lightdm. The details aren't important. I'm using xorg as my display server. When lightdm starts xorg, ~/.xprofile is run. That's my hook to running whatever script I want that creates color schemes. So inside ~/.xprofile, I run:
wal --saturate 0.4 -i ~/my/images -o ~/bin/postpywal16We haven't talked about the -o flag. For now, know that it's a script that runs after pywal runs. We'll talk about the contents of that script presently.
Notifying Client Programs
Once pywal has written a new color scheme based on an image's colors (ie, when our .xprofile script runs on startup), we need to notify any running client programs of this change. This is the job of the postpywal16 script. First, here's the full contents of postpywal16:
cat ~/bin/postpywal16
#!/bin/bash
# Merge pywal colors into X resources database
xrdb -merge ~/.cache/wal/colors.Xresources
xrdb -merge ~/.cache/wal/colors-dwm-xrdb
# Restart/notify apps
killall dunst && dunst &
killall dwmblocks && dwmblocks &
touch ~/.config/alacritty/alacritty.toml
pkill -USR1 dwm
for server in "${XDG_RUNTIME_DIR:-/tmp}"/nvim.*.0; do
[ -S "$server" ] || continue
nvim --server "$server" --remote-send '<Cmd>colorscheme pywal<CR>' &
done
There are two things happening in this script. First, xrdb is an xorg built-in utility that parses xrdb files, which are in the format of key: value, which is the format that we chose for the colors-dwm-xrdb file (and is the same for colors.Xresources). It parses those files and merges (-merge is important) those values into a root window dictionary value of RESOURCE_MANAGER. This xorg value will later be read by client programs (as opposed to those programs directly reading a color scheme file).
Secondly in this script, some select client programs are being restarted, signalled or commanded in a way that will let them know of color scheme changes.
Desktop Color Scheme
Each client program is going to respond to signals and load color schemes differently. Let's focus here on dwm. This is a window manager, a layer like a desktop environment in the Linux desktop stack. Dwm itself doesn't come out of the box with pywal support, so you apply a patch to have it read pywal color scheme files like the one we made above. I used the patch dwm-xrdb-6.4. You may have to do the same with your client application so it can support pywal colors.
Here are some key lines from dwm.c that demonstrate how it loads the colors. In setup(), signal handlers are registered. Patched, we now are registered to listen for USR1:
sa.sa_handler = sigusr1;
sigaction(SIGUSR1, &sa, NULL);
That handler marks a boolean indicating xrdb has new values to load as true:
void
sigusr1(int unused)
{
reload_xrdb = 1;
}
Then in the run() event loop, that boolean is checked over and over. If it's true, it'll call a function xrdb() to reload values from xorg:
if (reload_xrdb) {
reload_xrdb = 0;
xrdb(NULL);
}
xrdb() delegates to further inner functions to eventually load colors via XrmGetResource, something like this:
static char normbgcolor[] = "#222222";
// ...
XrmGetResource(xrdb, "dwm.normbordercolor", NULL, &type, &normbordercolor);
It reads the values like dwm.normbordercolor and the others we set in the colors-dwm-xrdb file, parses the hex value and sets the internal config.h char color variables. (The initial values in these color vars, such as #222222, are only used as fallback values.) Once new colors are set, on a re-paint of the window environment, those colors are displayed.
Besides in the event loop, I've also bound xrdb() reload to the hot key Mod1+F5 in config.h so the color scheme can be reloaded manually if needed:
{MODKEY, XK_F5, xrdb, {.v = NULL}},
dwm is not the simplest case. But it gives a good demonstration of the innards of how this works all the way down to touching the display server. There are many client programs that will come prebuilt with support. Anyone who wants top-to-bottom matching color schemes is going to be prepared to tinker a bit for it.