Screenshot of the "Dconf Editor" entry in the Gnome Software store

This is not a nice post. This is a post about Gnome/GDM customization.

I recently got the opportunity to work on adapting the default Debian Gnome experience to a client’s corporate design, and it felt a LOT like reverse-engineering deeply into the undocumented.

I found the work to fall into a number of categories, which I will classify as “dconf policy”, “css”, “xml-manifest” and “packaging”.

GDM logodconf policy, packaging
GDM banner messagedconf policy
GDM background colorcss, xml-manifest
GDM wallpapercss, xml-manifest, packaging
Gnome default wallpaperdconf policy, packaging
Gnome default themedconf policy
Gnome shell pluginsdconf policy, packaging
Gnome UI and plugin defaultsdconf policy
Gnome wallpapersxml-manifest

Note I’m not familiar with any underlying Gnome/GTK philosopy aspects but come from a Linux engineering role and just need to get the job done.

Packaging

The “packaging” class really just means that required assets need to be packaged onto the system and that any shell plugins that should be enabled by default, must be installed.

GDM

The GDM-Settings workflow

For GDM customization, GDM-Settings proved immensely helpful for identifying where to make changes.

# Install via flatpak
sudo apt-get install flatpak gnome-software-plugin-flatpak
flatpak remote-add --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo
flatpak -y install io.github.realmazharhussain.GdmSettings

# Keep track of where we started off
touch /tmp/now

# Run gdm-settings
flatpak run io.github.realmazharhussain.GdmSettings

# See what changed
find / -type f -newer /tmp/now 2>/dev/null | egrep -v '^/(dev|run|proc|sys|home|var|tmp)'

For this post, I will stick with the default dconf policy filename used by GDM-Settings.

Logo and banner

# /etc/dconf/db/gdm.d/95-gdm-settings
[org/gnome/login-screen]
logo='/usr/share/icons/hicolor/48x48/apps/gvim.png'
banner-message-enable=true
banner-message-text='Welcome to VIMnux'

dconf needs an accompanying profile definition, /etc/dconf/profile/gdm:

user-db:user
system-db:gdm

dconf update needs to be run after modifying these files.

Background color and wallpaper

GDM background settings are hidden deep in the global gnome-shell theme CSS, which itself is hidden in /usr/share/gnome-shell/gnome-shell-theme.gresource.

GDM-Settings completely hides the tedious process of drilling down to the CSS away from the user, which is great from a user perspective, but not what I needed for my customizations. I went with the following workflow for unpacking the files. gresource list lists the file names contained in the gresource file, gresource extract extracts them one by one.

# Unpack /usr/share/gnome-shell/gnome-shell-theme.gresource
# to a temporary directory:
T=$(mktemp -d /tmp/gres.XXX); printf "Resources tempdir: %s\n" $T
cd $T
while read R
do
  gresource extract /usr/share/gnome-shell/gnome-shell-theme.gresource $R > $(basename $R)
done < <(gresource list /usr/share/gnome-shell/gnome-shell-theme.gresource)

At this point, the only file I’m interested in is gnome-shell.css, where I set a black background for my application.

.login-dialog { background: transparent; }
#lockDialogGroup { background-color: rgb(0,0,0); }

Similar CSS for a wallpaper:

.login-dialog { background: transparent; }
#lockDialogGroup {
  background-image: url('file:///usr/share/backgrounds/gnome/wood-d.webp');
  background-position: center;
  background-size: cover;
}

Reassembly of the gresource file requires an XML manifest which I generate using the following script, manifest.py:

#!/usr/bin/env python3

import os, sys, glob
import xml.etree.ElementTree as ET
from io import BytesIO

os.chdir(sys.argv[1])
gresources = ET.Element('gresources')
gresource = ET.SubElement(gresources, 'gresource', attrib = {'prefix': '/org/gnome/shell/theme'})
for resourcefile in glob.glob('*'):
    file = ET.SubElement(gresource, 'file')
    file.text = resourcefile

out = BytesIO()
xmldoc = ET.ElementTree(gresources)
ET.indent(xmldoc)
xmldoc.write(out, encoding='utf-8', xml_declaration=True)
print(out.getvalue().decode())

First generate the XML manifest, then compile the gresources file.

# Generate XML
./manifest.py $T > gnome-shell-theme.gresource.xml

# Compile gresources (glib-compile-resources from libglib2.0-dev-bin)
glib-compile-resources gnome-shell-theme.gresource.xml --sourcedir=$T --target=gnome-shell-theme.gresource

Someone over here decided to indirect /usr/share/gnome-shell/gnome-shell-theme.gresource via /etc/alternatives, do whatever you like.

Note that on the systems I tested this on, gdm.css and gdm3.css could be left out of the gresource file and all changes were made in gnome-shell.css.

Gnome Wallpapers

Speaking of XML, Wallpapers can be installed to someplace intuitive such as /usr/share/backgrounds/corporate but must be accompanied by another XML manifest in /usr/share/gnome-background-properties, which I generate using another XML generator, properties-xml.py:

#!/usr/bin/env python3

import os, sys, glob
import xml.etree.ElementTree as ET
from io import BytesIO

os.chdir(sys.argv[1])
dirname='/usr/share/backgrounds/corporate'
wallpapers = ET.Element('wallpapers')
for wallpaper in glob.glob('*'):
    wallpaper_element = ET.SubElement(wallpapers, 'wallpaper', attrib = {'deleted': 'false'})
    filename = ET.SubElement(wallpaper_element, 'filename')
    filename.text = f"{dirname}/{wallpaper}"
    name = ET.SubElement(wallpaper_element, 'name')
    name.text = wallpaper
    options = ET.SubElement(wallpaper_element, 'options')
    options.text = 'zoom'
    pcolor = ET.SubElement(wallpaper_element, 'pcolor')
    pcolor.text = '#000000'
    scolor = ET.SubElement(wallpaper_element, 'scolor')
    scolor.text = '#ffffff'

out = BytesIO()
xmldoc = ET.ElementTree(wallpapers)
ET.indent(xmldoc)
xmldoc.write(out)
print('<?xml version="1.0" encoding="UTF-8"?>')
print('<!DOCTYPE wallpapers SYSTEM "gnome-wp-list.dtd">')
print(out.getvalue().decode())

Which I run as follows:

./properties-xml.py backgrounds > corporate.xml

/usr/share/backgrounds/corporate/* and /usr/share/gnome-background-properties/corporate.xml then get packaged onto the system.

Gnome Extensions and Defaults

At this point, a dconf user profile needs to be introduced:

$ cat /etc/dconf/profile/user 
user-db:user
system-db:local

(Things get easier from here.)

Default-enabled extensions

I chose to enable the extensions and set related defaults in /etc/dconf/db/local.d/99-extensions:

[org/gnome/shell]
enabled-extensions=[ 'ding@rastersoft.com', 'no-overview@fthx', 'dash-to-dock@micxgx.gmail.com', 'ubuntu-appindicators@ubuntu.com', 'TopIcons@phocean.net' ]

[org/gnome/shell/extensions/dash-to-dock]
dock-fixed=true
background-opacity=0.2
transparency-mode='FIXED'

dconf update needs to be run after modifying this file.

Other Gnome defaults

dconf watch /

dconf watch / in a terminal makes it possible to take note of what configuration options change as changes are being made. They can now be made the defaults in a policy file such as /etc/dconf/db/local.d/99-misc-defaults:

[org/gnome/desktop/wm/preferences]
button-layout='appmenu:minimize,maximize,close'

[org/gnome/terminal/legacy]
theme-variant='dark'

[org/gnome/desktop/interface]
color-scheme='prefer-dark'
gtk-theme='Adwaita-dark'

dconf update needs to be run after modifying this file.

tl;dr: Example customization package

A debian package that provides live examples, can be found here: https://github.com/mschmitt/gnome-local-custom