From 571f60b1a062dca2187c21f5a2bd25d097dc2bca Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Thu, 14 May 2026 11:32:16 +0300 Subject: [PATCH 1/2] nix: minor flake refactor; provide GTK and QT theme packages Signed-off-by: NotAShelf Change-Id: I136253e0d928e71e61824bc3f3b5a0726a6a6964 --- flake.nix | 85 ++++++++++++++++++++------- lib.nix | 63 ++++++++++++++++++++ packages/gtk/package.nix | 109 +++++++++++++++++++++++++++++++++++ packages/kvantum/package.nix | 60 +++++++++++++++++++ packages/qtct/package.nix | 50 ++++++++++++++++ 5 files changed, 346 insertions(+), 21 deletions(-) create mode 100644 lib.nix create mode 100644 packages/gtk/package.nix create mode 100644 packages/kvantum/package.nix create mode 100644 packages/qtct/package.nix diff --git a/flake.nix b/flake.nix index 8b96047..f2d677b 100644 --- a/flake.nix +++ b/flake.nix @@ -2,48 +2,91 @@ description = "Base16/Base24 schemes for Nix"; inputs = { + nixpkgs.url = "github:NixOS/nixpkgs?ref=nixos-unstable"; flake-parts.url = "github:hercules-ci/flake-parts"; - nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; flake-compat = { url = "github:edolstra/flake-compat"; flake = false; }; }; - outputs = inputs @ { + outputs = { flake-parts, self, ... - }: - flake-parts.lib.mkFlake {inherit inputs;} { - systems = ["x86_64-linux" "aarch64-linux" "aarch64-darwin" "x86_64-darwin"]; - perSystem = {pkgs, ...}: { + } @ inputs: + flake-parts.lib.mkFlake {inherit inputs;} (let + systems = ["x86_64-linux" "aarch64-linux" "aarch64-darwin"]; + + # 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 = { + # Converts YAML -> JSON 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 - inherit (inputs.nixpkgs) lib; - 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 - ]; + inherit (basixLib) evalSchemeData; in { - lib = { - inherit evalSchemeData; - }; + lib = basixLib; schemeData = { base16 = evalSchemeData "${self}/json/base16"; base24 = evalSchemeData "${self}/json/base24"; }; + + themePackages = lib.genAttrs systems mkSystemThemePackages; }; - }; + }); } diff --git a/lib.nix b/lib.nix new file mode 100644 index 0000000..ba54c28 --- /dev/null +++ b/lib.nix @@ -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 + ; +} diff --git a/packages/gtk/package.nix b/packages/gtk/package.nix new file mode 100644 index 0000000..c4c9351 --- /dev/null +++ b/packages/gtk/package.nix @@ -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; + }; + } diff --git a/packages/kvantum/package.nix b/packages/kvantum/package.nix new file mode 100644 index 0000000..1081d05 --- /dev/null +++ b/packages/kvantum/package.nix @@ -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 = '' + + + + + + + ''; +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; + }; + } diff --git a/packages/qtct/package.nix b/packages/qtct/package.nix new file mode 100644 index 0000000..e844b54 --- /dev/null +++ b/packages/qtct/package.nix @@ -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; + }; + } From 77624a396095b144cac69fdb6f249358d368db15 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Thu, 14 May 2026 11:38:35 +0300 Subject: [PATCH 2/2] docs: clean up wording; add new theme package features Signed-off-by: NotAShelf Change-Id: Id2b7e8d633c04425a3cc27e7fd81acb76a6a6964 --- README.md | 82 ++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 69 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index e4d184f..6148aec 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,32 @@ # 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 -[tinted-theming/schemes](https://github.com/tinted-theming/schemes), exposed as -one convenient library. +[@tinted-theming/schemes] , exposed as one convenient library and opinionated +theme packages for GTK and QT theming sustems. ## How does it work? -For some obscure reason[^1] all schemes provided by tinted-theming is in YAML -and under one unified repository. We convert each YAML scheme to JSON to ensure -the schemes are in a format Nix can read, then read them and expose them under a -flake output. +For some obscure reason, [^1] all schemes provided by tinted-theming are YAML +files vendored in one massive repository. Basix, in turn, fetches the theme data +from the tinted-theming repository and converts them into JSON to ensure the +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` -from the outputs from this flake to import the color schemes for yourself. +Basix can be used as a flake input, or imported from a tarball. To get a color +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 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..base16.` +- `themePackages..base24.` +- `packages..themes-base16` +- `packages..themes-base24` +- `packages..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-/...` + - `gtk-2.0/gtkrc` + - `gtk-3.0/gtk.css` + - `gtk-4.0/gtk.css` +- Qt color schemes under: + - `share/qt5ct/colors/Basix-.conf` + - `share/qt6ct/colors/Basix-.conf` +- Kvantum theme assets under: + - `share/Kvantum/Basix-/Basix-.kvconfig` + - `share/Kvantum/Basix-/Basix-.svg` + +These are generated from Base16/Base24 palettes and intentionally keep styling +flat and conservative for broad compatibility. + ## Why? 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). -[^1]: - I'm being generous here. The obscure reason is the myth that YAML is human - readable. Guess what? It is [actually nowhere near human readable and you +[^1]: I'm being generous here. The obscure reason is the myth that YAML is human + readable. Guess what? It is + [actually nowhere near human readable and you should avoid it](https://ruudvanasseldonk.com/2023/01/11/the-yaml-document-from-hell)