
GTK4/libadwaita apps (Ghostty, Trayscale, and others) show grey placeholder icons instead of proper symbolic icons (tab-new-symbolic , window-close-symbolic , open-menu-symbolic , etc.) on KDE Plasma 6 running on NixOS.
This is a three-layer problem : a portal architecture mismatch, a dconf overwrite race, and NixOS path fragmentation — all stacked on top of each other.
The Root Cause
Layer 1: GTK4 can't hear KDE's portal
GTK4 on Wayland queries the xdg-desktop-portal for settings using these patterns:
org.gnome.desktop.interface / icon-theme
org.freedesktop.appearance / *
KDE's portal (xdg-desktop-portal-kde ) exposes the icon theme ONLY as:
org.kde.kdeglobals.Icons / Theme
GTK4 asks for org.gnome.* . KDE answers with org.kde.* . Neither side translates. When GTK4 gets no answer, it falls back to hicolor — which has no symbolic icons.
Confirmed via GTK_DEBUG=icontheme ghostty — the debug output shows GTK4 scanning ONLY hicolor directories on the filesystem, never Adwaita or Breeze .
Layer 2: KDE overwrites dconf on every login
KDE's kde-gtk-config daemon reads ~/.config/kdeglobals and writes the icon theme to dconf (org.gnome.desktop.interface icon-theme = "breeze" ) on every login. Even if you set dconf to "Adwaita" via gsettings or home-manager's dconf.settings , KDE overwrites it at session start.
Layer 3: NixOS icon path fragmentation
On NixOS, icon themes live in isolated Nix store paths. GTK4 finds icons via XDG_DATA_DIRS . The Adwaita icon theme must be both installed AND reachable via XDG_DATA_DIRS — just installing the package isn't enough if its store path isn't in the search path.
What We Tried (and why it failed)
Setting gtk.iconTheme in home-manager
gtk = {
enable = true;
iconTheme = { package = pkgs.adwaita-icon-theme; name = "Adwaita"; };
};
Writes gtk-icon-theme-name=Adwaita to ~/.config/gtk-3.0/settings.ini and ~/.config/gtk-4.0/settings.ini . GTK4 on Wayland ignores settings.ini for icon-theme — it reads from the portal/dconf instead.
Setting dconf via home-manager
dconf.settings = {
"org/gnome/desktop/interface" = { icon-theme = "Adwaita"; };
};
Sets dconf correctly on activation, but kde-gtk-config overwrites it with "breeze" on every login , after home-manager has already run.
Patching Breeze's index.theme to inherit Adwaita
Changed Inherits=hicolor to Inherits=hicolor,Adwaita . Didn't work because GTK4 never even looks at Breeze — it falls back to hicolor directly since the portal returns nothing GTK4 understands.
gsettings set from a terminal
gsettings set org.gnome.desktop.interface icon-theme 'Adwaita'
Sets dconf, but with the KDE portal still active and no GTK portal fallback, GTK4 reads from the portal (which says nothing) rather than dconf.
System-level xdg.portal.config.common
xdg.portal.config.common = {
"org.freedesktop.impl.portal.Settings" = [ "kde" "gtk" ];
};
Doesn't generate a portals.conf file at either system or user level on NixOS. The option exists but produces no output. Confirmed by checking both /etc/xdg-desktop-portal/ and ~/.config/xdg-desktop-portal/ — empty.
home-manager portals.conf (reverted prematurely)
We had this working but reverted it in favor of the system-level approach (which turned out to be a dead end). The user-level portals.conf IS the correct approach.
The Fix (what actually works)
Three pieces, all required:
1. Install Adwaita and configure GTK (home-manager)
# home/base/programs/gtk.nix
{ pkgs, ... }:
{
gtk = {
enable = true;
iconTheme = {
package = pkgs.adwaita-icon-theme;
name = "Adwaita";
};
};
dconf.settings = {
"org/gnome/desktop/interface" = {
icon-theme = "Adwaita";
};
};
}
This ensures Adwaita is installed, settings.ini is written (for GTK3), and dconf has the correct value (for the GTK portal to serve).
2. User-level portals.conf — GTK portal as Settings fallback
# In the same gtk.nix
xdg.configFile."xdg-desktop-portal/portals.conf".text = ''
[preferred]
default=kde
org.freedesktop.impl.portal.Settings=gtk;kde
'';
This tells xdg-desktop-portal: KDE portal handles everything by default (file chooser, etc.), but for the Settings interface, try GTK portal first, then KDE . The GTK portal reads dconf, where we've set icon-theme = "Adwaita" . KDE portal handles org.freedesktop.appearance (color-scheme, accent).
You must also have xdg-desktop-portal-gtk installed:
# modules/desktop/default.nix (or equivalent)
xdg.portal = {
enable = true;
extraPortals = [
pkgs.kdePackages.xdg-desktop-portal-kde
pkgs.xdg-desktop-portal-gtk
];
config.common.default = [ "kde" ];
};
3. Systemd service to override kde-gtk-config
# In the same gtk.nix
systemd.user.services.gtk-icon-theme-override = {
Unit = {
Description = "Force GTK icon theme to Adwaita (override kde-gtk-config)";
After = [ "plasma-kde-gtk-config.service" "graphical-session.target" ];
};
Service = {
Type = "oneshot";
ExecStart = "${pkgs.glib}/bin/gsettings set org.gnome.desktop.interface icon-theme Adwaita";
};
Install = {
WantedBy = [ "graphical-session.target" ];
};
};
This runs AFTER KDE's kde-gtk-config writes "breeze" to dconf, overwriting it with "Adwaita". The GTK portal then serves the correct value to GTK4 apps.
Bonus: Nuke stale home-manager backups
If you hit .hm-backup or .hm-bak collision errors during rebuild (common when adding gtk.enable = true for the first time):
# lib/default.nix or wherever home-manager is configured
home-manager.backupFileExtension = "hm-bak";
home-manager.sharedModules = [{
home.activation.nukeHmBackups = {
before = [ "checkLinkTargets" ];
after = [];
data = ''
find "$HOME" -maxdepth 3 \( -name "*.hm-backup" -o -name "*.hm-bak" -o -name "*.hm-bak-old" \) -delete 2>/dev/null || true
'';
};
}];
Diagnosis Commands
Confirm the problem
GTK_DEBUG=icontheme ghostty 2>&1 | head -100
If you see GTK4 scanning only hicolor directories and /org/gnome/Adwaita/icons/ GResource paths (but never filesystem Adwaita), this is your issue.
Check dconf state
dconf read /org/gnome/desktop/interface/icon-theme
# Should say 'Adwaita' — if it says 'breeze', kde-gtk-config overwrote it
Check portal config
cat ~/.config/xdg-desktop-portal/portals.conf
# Should exist and contain the [preferred] section
Verify GTK portal is running
systemctl --user status xdg-desktop-portal-gtk
# Should be active
Verify override service ran
systemctl --user status gtk-icon-theme-override
# Should show completed successfully
Why This Is So Hard
1.
2.
3.
NixOS's system-level xdg.portal.config doesn't generate a file — forcing user-level config
4.
home-manager's gtk.enable creates backup collisions — requiring pre-activation cleanup
5.
References
GTK4 Wayland settings source (reads org.gnome.* only): gdk/wayland/gdksettings-wayland.c line 440