Merge pull request #6 from NotAShelf/notashelf/push-ywtxuwlzmqxr

nix: refactor flake; vendor GTK and QT theme packages for applications
This commit is contained in:
raf 2026-05-14 11:43:06 +03:00 committed by GitHub
commit 16149ad4b1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 415 additions and 34 deletions

View file

@ -1,22 +1,32 @@
# Basix # Basix
[@tinted-theming/schemes]: https://github.com/tinted-theming/schemes
An over-engineered, reusable Nix flake for _all_ Base16 and Base24
An over-engineered Nix flake for _all_ Base16 and Base24 themes from An over-engineered Nix flake for _all_ Base16 and Base24 themes from
[tinted-theming/schemes](https://github.com/tinted-theming/schemes), exposed as [@tinted-theming/schemes] , exposed as one convenient library and opinionated
one convenient library. theme packages for GTK and QT theming sustems.
## How does it work? ## How does it work?
For some obscure reason[^1] all schemes provided by tinted-theming is in YAML For some obscure reason, [^1] all schemes provided by tinted-theming are YAML
and under one unified repository. We convert each YAML scheme to JSON to ensure files vendored in one massive repository. Basix, in turn, fetches the theme data
the schemes are in a format Nix can read, then read them and expose them under a from the tinted-theming repository and converts them into JSON to ensure the
flake output. schemes are available in a format Nix can read, parse and expose under the flake
outputs.
## How do I use this? Downloading and parsing is done by a
[quick and dirty Python script](./packages/convert-scheme/) and then exposed by
the flake as `schemeData`. You can also import the Nix2 endpoint provided by
`default.nix` and get `schemeData` that way.
Basix be used as a flake input, or imported from a tarball. ### How do I use this?
To get a color scheme, import either `schemeData.base16` or `schemeData.base24` Basix can be used as a flake input, or imported from a tarball. To get a color
from the outputs from this flake to import the color schemes for yourself. scheme, import either `schemeData.base16` or `schemeData.base24` from the
outputs from this flake to import the color schemes for yourself. For example,
in the Nix REPL:
```bash ```bash
nix-repl> :p schemeData.base16.decaf nix-repl> :p schemeData.base16.decaf
@ -46,6 +56,52 @@ nix-repl> :p schemeData.base16.decaf
} }
``` ```
You can get a list of schemes by looking into [`json/`](./json) for the
appropriate theming model or evaluate available themes in the Nix REPL using
`attrNames` or similar.
### Generated theme packages
Basix also generates "conservative" GTK/Qt themes for every Base16/Base24
scheme:
- `themePackages.<system>.base16.<slug>`
- `themePackages.<system>.base24.<slug>`
- `packages.<system>.themes-base16`
- `packages.<system>.themes-base24`
- `packages.<system>.themes-all`
You can build them if you so wish:
```bash
# Build all packages
$ nix build .#themes-all
# Build base16 theme packages
$ nix build .#themes-base16
# Build only the 'decaf' theme package
$ nix build .#themePackages.x86_64-linux.base16.decaf
```
`themePackages` is keyed by system, so the system segment is required.
Generated install paths include:
- GTK themes under `share/themes/Basix-<slug>/...`
- `gtk-2.0/gtkrc`
- `gtk-3.0/gtk.css`
- `gtk-4.0/gtk.css`
- Qt color schemes under:
- `share/qt5ct/colors/Basix-<slug>.conf`
- `share/qt6ct/colors/Basix-<slug>.conf`
- Kvantum theme assets under:
- `share/Kvantum/Basix-<slug>/Basix-<slug>.kvconfig`
- `share/Kvantum/Basix-<slug>/Basix-<slug>.svg`
These are generated from Base16/Base24 palettes and intentionally keep styling
flat and conservative for broad compatibility.
## Why? ## Why?
There are not many theming solutions for Nix. Those that already exist are There are not many theming solutions for Nix. Those that already exist are
@ -70,7 +126,7 @@ from a pre-defined color palette.
Licensed under the [GNU General Public License v3.0](LICENSE). Licensed under the [GNU General Public License v3.0](LICENSE).
[^1]: [^1]: I'm being generous here. The obscure reason is the myth that YAML is human
I'm being generous here. The obscure reason is the myth that YAML is human readable. Guess what? It is
readable. Guess what? It is [actually nowhere near human readable and you [actually nowhere near human readable and you
should avoid it](https://ruudvanasseldonk.com/2023/01/11/the-yaml-document-from-hell) should avoid it](https://ruudvanasseldonk.com/2023/01/11/the-yaml-document-from-hell)

View file

@ -2,48 +2,91 @@
description = "Base16/Base24 schemes for Nix"; description = "Base16/Base24 schemes for Nix";
inputs = { inputs = {
nixpkgs.url = "github:NixOS/nixpkgs?ref=nixos-unstable";
flake-parts.url = "github:hercules-ci/flake-parts"; flake-parts.url = "github:hercules-ci/flake-parts";
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-compat = { flake-compat = {
url = "github:edolstra/flake-compat"; url = "github:edolstra/flake-compat";
flake = false; flake = false;
}; };
}; };
outputs = inputs @ { outputs = {
flake-parts, flake-parts,
self, self,
... ...
}: } @ inputs:
flake-parts.lib.mkFlake {inherit inputs;} { flake-parts.lib.mkFlake {inherit inputs;} (let
systems = ["x86_64-linux" "aarch64-linux" "aarch64-darwin" "x86_64-darwin"]; systems = ["x86_64-linux" "aarch64-linux" "aarch64-darwin"];
perSystem = {pkgs, ...}: {
# FWIW this could also be lib.extend but lib.extend itself claims that
# it should not be used, so we do this instead.
inherit (inputs.nixpkgs) lib;
basixLib = import ./lib.nix {inherit lib;};
mkThemeAttrSet = pkgs: schemes: let
mkGtkTheme = pkgs.callPackage ./packages/gtk/package.nix {basixLib = self.lib;};
mkQtctTheme = pkgs.callPackage ./packages/qtct/package.nix {basixLib = self.lib;};
mkKvantumTheme = pkgs.callPackage ./packages/kvantum/package.nix {basixLib = self.lib;};
in
lib.mapAttrs (slug: scheme:
pkgs.symlinkJoin {
name = "basix-theme-${self.lib.sanitizeSlug slug}";
paths = [
(mkGtkTheme {inherit slug scheme;})
(mkQtctTheme {inherit slug scheme;})
(mkKvantumTheme {inherit slug scheme;})
];
})
schemes;
mkSystemThemePackages = system: let
pkgs = import inputs.nixpkgs {inherit system;};
in {
base16 = mkThemeAttrSet pkgs self.schemeData.base16;
base24 = mkThemeAttrSet pkgs self.schemeData.base24;
};
in {
inherit systems;
perSystem = {
pkgs,
system,
...
}: let
themePackages = self.themePackages.${system};
in {
packages = { packages = {
# Converts YAML -> JSON
convert-scheme = pkgs.callPackage ./packages/convert-scheme/package.nix {}; convert-scheme = pkgs.callPackage ./packages/convert-scheme/package.nix {};
# Theme collections
themes-base16 = pkgs.symlinkJoin {
name = "basix-themes-base16";
paths = lib.attrValues themePackages.base16;
};
themes-base24 = pkgs.symlinkJoin {
name = "basix-themes-base24";
paths = lib.attrValues themePackages.base24;
};
themes-all = pkgs.symlinkJoin {
name = "basix-themes-all-${system}";
paths = (lib.attrValues themePackages.base16) ++ (lib.attrValues themePackages.base24);
};
}; };
}; };
flake = let flake = let
inherit (inputs.nixpkgs) lib; inherit (basixLib) evalSchemeData;
evalSchemeData = lib.flip lib.pipe [
builtins.unsafeDiscardStringContext
lib.filesystem.listFilesRecursive
(builtins.filter (lib.hasSuffix ".json"))
(map (n: {
name = lib.removePrefix ((dirOf n) + "/") (lib.removeSuffix ".json" n);
value = lib.importJSON n;
}))
lib.listToAttrs
];
in { in {
lib = { lib = basixLib;
inherit evalSchemeData;
};
schemeData = { schemeData = {
base16 = evalSchemeData "${self}/json/base16"; base16 = evalSchemeData "${self}/json/base16";
base24 = evalSchemeData "${self}/json/base24"; base24 = evalSchemeData "${self}/json/base24";
}; };
themePackages = lib.genAttrs systems mkSystemThemePackages;
}; };
}; });
} }

63
lib.nix Normal file
View file

@ -0,0 +1,63 @@
{lib}: let
requiredBaseKeys = [
"base00"
"base01"
"base02"
"base03"
"base04"
"base05"
"base06"
"base07"
"base08"
"base09"
"base0A"
"base0B"
"base0C"
"base0D"
"base0E"
"base0F"
];
sanitizeSlug = slug:
lib.strings.sanitizeDerivationName
(lib.strings.toLower (toString slug));
normalizeHex = color:
lib.strings.toLower
(lib.removePrefix "#" (toString color));
missingBaseKeys = palette:
builtins.filter (key: !(builtins.hasAttr key palette)) requiredBaseKeys;
validatePalette = {
slug,
palette,
}: let
missing = missingBaseKeys palette;
in
if missing != []
then throw "Basix theme generation failed for `${slug}`: missing palette keys ${lib.concatStringsSep ", " missing} (required: base00-base0F)"
else palette;
mkThemeName = slug: "Basix-${sanitizeSlug slug}";
evalSchemeData = lib.flip lib.pipe [
builtins.unsafeDiscardStringContext
lib.filesystem.listFilesRecursive
(builtins.filter (lib.hasSuffix ".json"))
(map (n: {
name = lib.removePrefix ((dirOf n) + "/") (lib.removeSuffix ".json" n);
value = lib.importJSON n;
}))
lib.listToAttrs
];
in {
inherit
evalSchemeData
mkThemeName
normalizeHex
requiredBaseKeys
sanitizeSlug
validatePalette
;
}

109
packages/gtk/package.nix Normal file
View file

@ -0,0 +1,109 @@
{
basixLib,
lib,
stdenvNoCC,
}: {
slug,
scheme,
}: let
palette = basixLib.validatePalette {
inherit slug;
palette = scheme.palette or {};
};
slugSafe = basixLib.sanitizeSlug slug;
themeName = basixLib.mkThemeName slugSafe;
hex = key: basixLib.normalizeHex palette.${key};
gtkrc = ''
gtk-color-scheme = "bg_color:#${hex "base00"}\nfg_color:#${hex "base05"}\nbase_color:#${hex "base01"}\ntext_color:#${hex "base05"}\nselected_bg_color:#${hex "base0D"}\nselected_fg_color:#${hex "base00"}\ntooltip_bg_color:#${hex "base01"}\ntooltip_fg_color:#${hex "base05"}"
style "basix-default" {
bg[NORMAL] = "#${hex "base00"}"
bg[ACTIVE] = "#${hex "base02"}"
bg[PRELIGHT] = "#${hex "base01"}"
bg[SELECTED] = "#${hex "base0D"}"
fg[NORMAL] = "#${hex "base05"}"
fg[INSENSITIVE] = "#${hex "base04"}"
fg[SELECTED] = "#${hex "base00"}"
text[NORMAL] = "#${hex "base05"}"
text[SELECTED] = "#${hex "base00"}"
base[NORMAL] = "#${hex "base01"}"
base[INSENSITIVE] = "#${hex "base03"}"
base[SELECTED] = "#${hex "base0D"}"
}
class "*" style "basix-default"
'';
gtkCss = ''
@define-color bg_color #${hex "base00"};
@define-color fg_color #${hex "base05"};
@define-color base_color #${hex "base01"};
@define-color text_color #${hex "base05"};
@define-color selected_bg_color #${hex "base0D"};
@define-color selected_fg_color #${hex "base00"};
@define-color borders #${hex "base03"};
@define-color warning_color #${hex "base09"};
@define-color error_color #${hex "base08"};
@define-color success_color #${hex "base0B"};
window, dialog, popover, menu, viewport {
background-color: @bg_color;
color: @fg_color;
}
entry, textview, treeview, list {
background-color: @base_color;
color: @text_color;
border: 1px solid @borders;
}
button, headerbar, toolbar {
background-color: @base_color;
color: @fg_color;
border: 1px solid @borders;
box-shadow: none;
}
scrollbar slider {
background-color: @base_color;
border: 1px solid @borders;
}
selection {
background-color: @selected_bg_color;
color: @selected_fg_color;
}
'';
in
stdenvNoCC.mkDerivation {
pname = "basix-gtk-theme-${slugSafe}";
version = "1.0.0";
dontUnpack = true;
installPhase = ''
runHook preInstall
mkdir -p "$out/share/themes/${themeName}/gtk-2.0"
mkdir -p "$out/share/themes/${themeName}/gtk-3.0"
mkdir -p "$out/share/themes/${themeName}/gtk-4.0"
cat > "$out/share/themes/${themeName}/gtk-2.0/gtkrc" <<'EOF_GTK2'
${gtkrc}
EOF_GTK2
cat > "$out/share/themes/${themeName}/gtk-3.0/gtk.css" <<'EOF_GTK3'
${gtkCss}
EOF_GTK3
cat > "$out/share/themes/${themeName}/gtk-4.0/gtk.css" <<'EOF_GTK4'
${gtkCss}
EOF_GTK4
runHook postInstall
'';
meta = {
description = "Generated GTK theme assets for Basix schemes";
platforms = lib.platforms.all;
license = lib.licenses.gpl3Only;
};
}

View file

@ -0,0 +1,60 @@
{
basixLib,
lib,
stdenvNoCC,
}: {
slug,
scheme,
}: let
palette = basixLib.validatePalette {
inherit slug;
palette = scheme.palette or {};
};
slugSafe = basixLib.sanitizeSlug slug;
themeName = basixLib.mkThemeName slugSafe;
hex = key: basixLib.normalizeHex palette.${key};
kvConfig = ''
[GeneralColors]
window.color=#${hex "base00"}
button.color=#${hex "base02"}
text.color=#${hex "base05"}
disabled.text.color=#${hex "base04"}
highlight.color=#${hex "base0D"}
highlighted.text.color=#${hex "base00"}
'';
kvSvg = ''
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" version="1.1">
<rect x="0" y="0" width="64" height="64" fill="#${hex "base00"}"/>
<rect x="8" y="8" width="48" height="20" rx="2" ry="2" fill="#${hex "base02"}"/>
<rect x="8" y="36" width="48" height="20" rx="2" ry="2" fill="#${hex "base0D"}"/>
</svg>
'';
in
stdenvNoCC.mkDerivation {
pname = "basix-kvantum-theme-${slugSafe}";
version = "1.0.0";
dontUnpack = true;
installPhase = ''
runHook preInstall
mkdir -p "$out/share/Kvantum/${themeName}"
cat > "$out/share/Kvantum/${themeName}/${themeName}.kvconfig" <<'EOF_KVCONFIG'
${kvConfig}
EOF_KVCONFIG
cat > "$out/share/Kvantum/${themeName}/${themeName}.svg" <<'EOF_KVSVG'
${kvSvg}
EOF_KVSVG
runHook postInstall
'';
meta = {
description = "Generated Kvantum theme assets for Basix schemes";
platforms = lib.platforms.all;
license = lib.licenses.gpl3Only;
};
}

50
packages/qtct/package.nix Normal file
View file

@ -0,0 +1,50 @@
{
basixLib,
lib,
stdenvNoCC,
}: {
slug,
scheme,
}: let
palette = basixLib.validatePalette {
inherit slug;
palette = scheme.palette or {};
};
slugSafe = basixLib.sanitizeSlug slug;
themeName = basixLib.mkThemeName slugSafe;
hex = key: basixLib.normalizeHex palette.${key};
argb = key: "#ff${hex key}";
qtConf = ''
[ColorScheme]
active_colors=${argb "base00"},${argb "base05"},${argb "base01"},${argb "base02"},${argb "base03"},${argb "base04"},${argb "base0D"},${argb "base00"},${argb "base08"},${argb "base09"},${argb "base0A"},${argb "base0B"},${argb "base0C"},${argb "base0E"}
disabled_colors=${argb "base03"},${argb "base04"},${argb "base02"},${argb "base03"},${argb "base03"},${argb "base04"},${argb "base03"},${argb "base04"},${argb "base08"},${argb "base09"},${argb "base0A"},${argb "base0B"},${argb "base0C"},${argb "base0E"}
inactive_colors=${argb "base00"},${argb "base05"},${argb "base01"},${argb "base02"},${argb "base03"},${argb "base04"},${argb "base0D"},${argb "base00"},${argb "base08"},${argb "base09"},${argb "base0A"},${argb "base0B"},${argb "base0C"},${argb "base0E"}
'';
in
stdenvNoCC.mkDerivation {
pname = "basix-qtct-theme-${slugSafe}";
version = "1.0.0";
dontUnpack = true;
installPhase = ''
runHook preInstall
mkdir -p "$out/share/qt5ct/colors"
mkdir -p "$out/share/qt6ct/colors"
cat > "$out/share/qt5ct/colors/${themeName}.conf" <<'EOF_QT'
${qtConf}
EOF_QT
cat > "$out/share/qt6ct/colors/${themeName}.conf" <<'EOF_QT'
${qtConf}
EOF_QT
runHook postInstall
'';
meta = {
description = "Generated qt5ct and qt6ct color schemes for Basix schemes";
platforms = lib.platforms.all;
license = lib.licenses.gpl3Only;
};
}