Compare commits

...

5 commits

Author SHA1 Message Date
raf
16149ad4b1
Merge pull request #6 from NotAShelf/notashelf/push-ywtxuwlzmqxr
nix: refactor flake; vendor GTK and QT theme packages for applications
2026-05-14 11:43:06 +03:00
77624a3960
docs: clean up wording; add new theme package features
Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: Id2b7e8d633c04425a3cc27e7fd81acb76a6a6964
2026-05-14 11:41:04 +03:00
571f60b1a0
nix: minor flake refactor; provide GTK and QT theme packages
Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I136253e0d928e71e61824bc3f3b5a0726a6a6964
2026-05-14 11:38:39 +03:00
3f1f0ddc17
ci: drop magic Nix cache
Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I0b65ccd5e3d5877c5dc2edd02f6e5b1c6a6a6964
2026-05-14 11:36:08 +03:00
72707b0bd2
ci: set up Dependabot
Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: Id3ec87ade0375ca3d7ef4418fb9f2fca6a6a6964
2026-05-14 11:36:07 +03:00
8 changed files with 430 additions and 37 deletions

15
.github/dependabot.yml vendored Normal file
View file

@ -0,0 +1,15 @@
version: 2
updates:
# Update used workflows
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: daily
# Update Nixpkgs
- package-ecosystem: nix
directory: "/"
cooldown:
default-days: 7
schedule:
interval: daily

View file

@ -25,9 +25,6 @@ jobs:
experimental-features = nix-command flakes
allow-import-from-derivation = false
- name: "Nix Magic Cache"
uses: DeterminateSystems/magic-nix-cache-action@main
- name: Run conversion script
run: |
tmpdir=$(mktemp -d)

View file

@ -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.<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?
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)

View file

@ -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;
};
};
});
}

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;
};
}