Skip to content

gtk: support NixOS standalone#2107

Open
danth wants to merge 6 commits intonix-community:masterfrom
danth:gtk-system-level
Open

gtk: support NixOS standalone#2107
danth wants to merge 6 commits intonix-community:masterfrom
danth:gtk-system-level

Conversation

@danth
Copy link
Copy Markdown
Member

@danth danth commented Dec 26, 2025

This branch (work in progress) adds support for GTK at the NixOS level.

Currently the theme works on non-libadwaita apps, and on libadwaita apps when $GTK_THEME is set. I've added a patch to allow libadwaita to load the theme by default, but I've not had time to test this yet, since it causes most apps on my system to be built from source. The patch is necessary since $GTK_THEME is meant for debugging purposes, so it takes priority over Home Manager and any imperative settings.

The reason why libadwaita does not load themes by default is because they create additional work for upstream maintainers who never intended for their app to be themed. As a general note, we might want to add a warning in our README about this, asking users to disable Stylix before sending any bug reports upstream.

Before this is merged, it seems like a good idea to split libadwaita into a separate target, so that users can disable the overlay if none of their apps need it.

Later, I believe the Home Manager module could be refactored to use the same theme package I've created here.


@stylix-automation stylix-automation bot added topic: nixos NixOS target topic: overlay Overlay changes topic: modules /modules/ subsystem labels Dec 26, 2025
danth added a commit to danth/stylix-fork that referenced this pull request Jan 21, 2026
@stylix-automation stylix-automation bot added the topic: testbed Testbed changes label Jan 22, 2026
@danth danth marked this pull request as ready for review January 22, 2026 14:29
@danth danth requested a review from 0xda157 January 22, 2026 14:29
@danth
Copy link
Copy Markdown
Member Author

danth commented Jan 22, 2026

libadwaita is now a separate target which is off by default, and both targets have descriptions to explain how they interact. Updating the Home Manager module to use the new theme package can be done in a separate PR, so this is now ready for review.

I've tested that things work with either NixOS standalone or NixOS + Home Manager. The testbed system only provides the latter combination, but changing that isn't directly relevant to this PR, so for now if you want to test it standalone you need to manually remove ~/.config/gtk-* and then dconf reset /org/gnome/desktop/interface/gtk-theme after starting the testbed.

I've not tested Home Manager standalone as of yet.

@danth danth changed the title gtk: support NixOS gtk: support NixOS standalone Jan 22, 2026
Copy link
Copy Markdown
Member

@trueNAHO trueNAHO left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR looks very nice :)

I've not tested Home Manager standalone as of yet.

Considering the or false fallback in

(config.stylix.enable && config.stylix.targets.libadwaita.enable or false)

I believe none of the added code should be triggered on standalone Home Manager. Supporting standalone Home Manager could be done in a follow-up PR:

Updating the Home Manager module to use the new theme package can be done in a separate PR

On a side note, unsure which of "NixOS standalone" and "standalone NixOS" is more "correct", as they imply different hierarchies.

@stylix-automation stylix-automation bot added topic: ci /.github/ subsystem topic: flake /flake.nix, /flake.lock, and /flake/ subsystems labels Jan 31, 2026
@danth danth removed topic: ci /.github/ subsystem topic: flake /flake.nix, /flake.lock, and /flake/ subsystems labels Jan 31, 2026
@danth danth requested a review from trueNAHO February 1, 2026 00:30
@danth
Copy link
Copy Markdown
Member Author

danth commented Feb 1, 2026

Build failures are:

  • MPV assertion which I believe is unrelated to this PR
  • For some reason Chromium is being compiled from source, which I did not expect

Comment on lines +45 to +82
linkFarm "stylix-gtk" [
{
name = "etc/xdg/gtk-3.0/settings.ini";
path = settings;
}
{
name = "etc/xdg/gtk-4.0/settings.ini";
path = settings;
}
{
name = "share/themes/Stylix/index.theme";
path = index;
}
{
name = "share/themes/Stylix/gtk-3.0/assets";
path = "${adw-gtk3}/share/themes/adw-gtk3/gtk-3.0/assets";
}
{
name = "share/themes/Stylix/gtk-3.0/gtk.css";
path = css "gtk-3.0/gtk.css";
}
{
name = "share/themes/Stylix/gtk-3.0/gtk-dark.css";
path = css "gtk-3.0/gtk-dark.css";
}
{
name = "share/themes/Stylix/gtk-4.0/assets";
path = "${adw-gtk3}/share/themes/adw-gtk3/gtk-4.0/assets";
}
{
name = "share/themes/Stylix/gtk-4.0/gtk.css";
path = css "gtk-4.0/gtk.css";
}
{
name = "share/themes/Stylix/gtk-4.0/gtk-dark.css";
path = css "gtk-4.0/gtk-dark.css";
}
]
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about parametrizing on the GTK version to deduplicate:

Suggested change
linkFarm "stylix-gtk" [
{
name = "etc/xdg/gtk-3.0/settings.ini";
path = settings;
}
{
name = "etc/xdg/gtk-4.0/settings.ini";
path = settings;
}
{
name = "share/themes/Stylix/index.theme";
path = index;
}
{
name = "share/themes/Stylix/gtk-3.0/assets";
path = "${adw-gtk3}/share/themes/adw-gtk3/gtk-3.0/assets";
}
{
name = "share/themes/Stylix/gtk-3.0/gtk.css";
path = css "gtk-3.0/gtk.css";
}
{
name = "share/themes/Stylix/gtk-3.0/gtk-dark.css";
path = css "gtk-3.0/gtk-dark.css";
}
{
name = "share/themes/Stylix/gtk-4.0/assets";
path = "${adw-gtk3}/share/themes/adw-gtk3/gtk-4.0/assets";
}
{
name = "share/themes/Stylix/gtk-4.0/gtk.css";
path = css "gtk-4.0/gtk.css";
}
{
name = "share/themes/Stylix/gtk-4.0/gtk-dark.css";
path = css "gtk-4.0/gtk-dark.css";
}
]
linkFarm "stylix-gtk" (
lib.singleton {
name = "share/themes/Stylix/index.theme";
path = index;
}
++
lib.concatMap
(version: [
{
name = "etc/xdg/gtk-${version}/settings.ini";
path = settings;
}
{
name = "share/themes/Stylix/gtk-${version}/assets";
path = "${adw-gtk3}/share/themes/adw-gtk3/gtk-${version}/assets";
}
{
name = "share/themes/Stylix/gtk-${version}/gtk.css";
path = css "${version}/gtk.css";
}
{
name = "share/themes/Stylix/gtk-${version}/gtk-dark.css";
path = css "${version}/gtk-dark.css";
}
])
[
"3.0"
"4.0"
]
)

This could be further deduplicated but would probably not be worth it due to over-engineering.

danth added 2 commits February 2, 2026 17:12
This adds support for GTK apps at the system level, in addition to the
existing Home Manager support.

This currently only works for non-libadwaita apps because libadwaita
overrides the theme name [1] for compatibility reasons.

[1]:
https://gitlab.gnome.org/GNOME/libadwaita/-/blob/43192bc3e2870a142e57f971b00b60d69abc9492/src/adw-style-manager.c#L492-495
This adds support for libadwaita apps at the system level, in addition
to the existing Home Manager support.
@trueNAHO
Copy link
Copy Markdown
Member

trueNAHO commented Feb 2, 2026

Build failures are:

  • MPV assertion which I believe is unrelated to this PR

The

Failed assertions:
- guest profile: The programs.mpv "package" option is mutually exclusive with "scripts" option.

-- https://buildbot.nix-community.org/api/v2/logs/2109309/raw_inline

assertion is triggered by Home Manager:

          {
            assertion = (cfg.scripts == [ ]) || (cfg.package == pkgs.mpv);
            message = ''The programs.mpv "package" option is mutually exclusive with "scripts" option.'';
          }

-- github:nix-community/home-manager, /modules/programs/mpv.nix:218

The cfg.package == pkgs.mpv condition may be false if this PR overlays pkgs.mpv without also overlaying cfg.package.

The cfg.package value is defined as:

      package = lib.mkPackageOption pkgs "mpv" {
        example = "pkgs.mpv-unwrapped.wrapper { mpv = pkgs.mpv-unwrapped.override { vapoursynthSupport = true; }; youtubeSupport = true; }";
      };

-- github:nix-community/home-manager, /modules/programs/mpv.nix:78

Maybe the pkgs.mpv.override value does not properly inherit the pkgs.mpv overlay:

What is the best fix here?

@danth
Copy link
Copy Markdown
Member Author

danth commented Feb 8, 2026

I'm not quite sure why that assertion is triggering but as you say, it's probably because one of the packages was overlayed.

In that case it would be possible to change upstream to detect whether the option was changed, rather than comparing its value directly, using something like our check in palette.nix:

stylix/stylix/palette.nix

Lines 189 to 202 in 044ac0c

# Only do this when `base16Scheme` is still the option default, which
# is when the generated palette is used. Depending on the file in other
# cases would force the palette generator to run when we never read the
# output.
#
# Controlling this by comparing against the default value with == would
# also force the palette generator to run, as we would have to evaluate
# the default value to check for equality. To work around this, we
# check only the priority of the resolved value. The priority of option
# defaults is 1500 [1], and any value less than this means the user has
# changed the option.
#
# [1]: https://github.com/NixOS/nixpkgs/blob/5f30488d37f91fd41f0d40437621a8563a70b285/lib/modules.nix#L1063
enable = options.stylix.base16Scheme.highestPrio >= 1500;

However that isn't the most elegant solution.

danth added 2 commits February 8, 2026 01:15
This makes it possible to disable the libadwaita patch without disabling
GTK as a whole.
danth added 2 commits February 8, 2026 01:16
This is in preparation for unifying the implementation of our NixOS and
Home Manager modules.
@trueNAHO
Copy link
Copy Markdown
Member

trueNAHO commented Feb 8, 2026

I'm not quite sure why that assertion is triggering but as you say, it's probably because one of the packages was overlayed.

In that case it would be possible to change upstream to detect whether the option was changed, rather than comparing its value directly, using something like our check in palette.nix:

stylix/stylix/palette.nix

Lines 189 to 202 in 044ac0c

# Only do this when `base16Scheme` is still the option default, which
# is when the generated palette is used. Depending on the file in other
# cases would force the palette generator to run when we never read the
# output.
#
# Controlling this by comparing against the default value with == would
# also force the palette generator to run, as we would have to evaluate
# the default value to check for equality. To work around this, we
# check only the priority of the resolved value. The priority of option
# defaults is 1500 [1], and any value less than this means the user has
# changed the option.
#
# [1]: https://github.com/NixOS/nixpkgs/blob/5f30488d37f91fd41f0d40437621a8563a70b285/lib/modules.nix#L1063
enable = options.stylix.base16Scheme.highestPrio >= 1500;

However that isn't the most elegant solution.

The nix eval .#testbed:mpv-uosc:dark test still fails with the following Home Manager patch:

diff --git a/modules/programs/mpv.nix b/modules/programs/mpv.nix
index a9b6f8d87..0d41c8e9c 100644
--- a/modules/programs/mpv.nix
+++ b/modules/programs/mpv.nix
@@ -1,6 +1,7 @@
 {
   config,
   lib,
+  options,
   pkgs,
   ...
 }:
@@ -216,7 +217,7 @@ in
       {
         assertions = [
           {
-            assertion = (cfg.scripts == [ ]) || (cfg.package == pkgs.mpv);
+            assertion = (cfg.scripts == [ ]) || (options.programs.mpv.package.highestPrio >= 1500);
             message = ''The programs.mpv "package" option is mutually exclusive with "scripts" option.'';
           }
         ];

Not explicitly overriding

programs.mpv.package = pkgs.mpv;

in our testbed as follows resolves the nix eval .#testbed:mpv-uosc:dark assertion failure:

diff --git a/modules/mpv/testbeds/mpv-uosc.nix b/modules/mpv/testbeds/mpv-uosc.nix
index 25420857..7a57a453 100644
--- a/modules/mpv/testbeds/mpv-uosc.nix
+++ b/modules/mpv/testbeds/mpv-uosc.nix
@@ -11,7 +11,6 @@ in
   home-manager.sharedModules = lib.singleton {
     programs.mpv = {
       enable = true;
-      inherit package;
       scripts = [ pkgs.mpvScripts.uosc ];
     };
   };

Do our testbeds incorrectly inherit the overlays or is the same behavior expected in end-user configurations? Maybe removing programs.${program}.package in the other testbeds also "fixes" the other issues. This problem might be highly related to the following unresolved problem:

$ nix build .#testbed:obsidian:dark
error:
       [...]
       error: Package ‘obsidian-1.10.3’ in /nix/store/c9asdiqw1rf5qwdjz26f57hp68ppb8fc-source/pkgs/by-name/ob/obsidian/package.nix:18 has an unfree license (‘obsidian’), refusing to evaluate.

       a) To temporarily allow unfree packages, you can use an environment variable
          for a single invocation of the nix tools.

            $ export NIXPKGS_ALLOW_UNFREE=1

          Note: When using `nix shell`, `nix build`, `nix develop`, etc with a flake,
                then pass `--impure` in order to allow use of environment variables.

       b) For `nixos-rebuild` you can set
         { nixpkgs.config.allowUnfree = true; }
       in configuration.nix to override this.

       Alternatively you can configure a predicate to allow specific packages:
         { nixpkgs.config.allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [
             "obsidian"
           ];
         }

       c) For `nix-env`, `nix-build`, `nix-shell` or any other Nix command you can add
         { allowUnfree = true; }
       to ~/.config/nixpkgs/config.nix.

Why does setting programs.obsidian.package to its supposedly default value resolve the error:

diff --git a/modules/obsidian/testbeds/obsidian.nix b/modules/obsidian/testbeds/obsidian.nix
index d648e67a..c2bff776 100644
--- a/modules/obsidian/testbeds/obsidian.nix
+++ b/modules/obsidian/testbeds/obsidian.nix
@@ -1,4 +1,4 @@
-{ lib, ... }:
+{ lib, pkgs, ... }:
 {
   stylix.testbed.ui.command.text = "obsidian";

@@ -14,6 +14,7 @@

       programs.obsidian = {
         enable = true;
+        package = pkgs.obsidian;
         vaults.${vault}.enable = true;
       };
     };

Why does

nixpkgs.config.allowUnfreePredicate =
pkg: builtins.elem (lib.getName pkg) [ "obsidian" ];

seemingly have no impact, although it does for the following instances:

-- #1662 (comment)

For reference, treewide removing the programs.${program}.package declarations will eventually be done by [PATCH 8/9] treewide: simplify and standardize testbeds from #1662.

@trueNAHO trueNAHO mentioned this pull request Feb 19, 2026
6 tasks
@CodedNil
Copy link
Copy Markdown

Is there any possibility of this supporting opacity using stylix.opacity.applications?

@trueNAHO
Copy link
Copy Markdown
Member

Is there any possibility of this supporting opacity using stylix.opacity.applications?

Are you referring to the new /modules/gtk/nixos.nix or /modules/libadwaita/overlay.nix module?

Either way, this should probably be done in a follow-up PR because the current
scope is very good and the only blockers are edge case bugs.

As previously mentioned, extending the scope should be done in follow-up PRs:

Supporting standalone Home Manager could be done in a follow-up PR:

Updating the Home Manager module to use the new theme package can be done in a separate PR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

topic: modules /modules/ subsystem topic: nixos NixOS target topic: overlay Overlay changes topic: testbed Testbed changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants