Files
chiasson-nix/modules/desktop/shells/dms/_private/hm.nix
T
Olivier 5357ba307b Fix dms-plugins
- Transitioned to an auto-derived plugin registry from flake inputs, simplifying the addition of new plugins by requiring edits only in `flake.nix`.
- Implemented a camelCase transformation for plugin IDs to align with upstream conventions, with exceptions for specific plugins.
- Enhanced the `shipPlugin` function to route plugin sources correctly, ensuring compatibility with the DMS structure.
- Added assertions to validate Obsidian vault paths at build time, improving error handling and user guidance.
2026-07-13 12:39:05 -03:00

277 lines
10 KiB
Nix

# Home Manager wiring for DMS (imported by default.nix; lives under _private/ for import-tree).
{
inputs,
config,
pkgs,
lib,
osConfig ? { },
...
}:
let
home = config.home.homeDirectory;
dmsEnabled = lib.attrByPath [ "chiasson" "desktop" "shell" ] null osConfig == "dms";
dmsOs = lib.attrByPath [ "chiasson" "desktop" "shells" "dms" ] { } osConfig;
hostName = lib.attrByPath [ "networking" "hostName" ] "nixos" osConfig;
rebuildCommand =
if (dmsOs.rebuildCommand or null) != null then
dmsOs.rebuildCommand
else
[ "sudo" "nixos-rebuild" "switch" "--flake" ".#${hostName}" ];
gpuTempEnabled = dmsOs.enableGpuTemp or true;
hyprlandEnabled = lib.attrByPath [ "chiasson" "desktop" "hyprland" "enable" ] false osConfig;
discordEnabled = lib.attrByPath [ "chiasson" "home" "apps" "discord" "enable" ] false config;
ohMyPoshEnabled = config.programs.oh-my-posh.enable or false;
obsidianSnippetDirs = map (v: v + "/snippets") (dmsOs.obsidianVaults or [ ]);
matugenOutputDirs =
lib.optionals hyprlandEnabled [ "${home}/.config/hypr" ]
++ lib.optionals ohMyPoshEnabled [ "${home}/.config/oh-my-posh" ]
++ lib.optionals discordEnabled [ "${home}/.config/vesktop/themes" ]
++ [
"${home}/.config/qt6ct/colors"
"${home}/.config/qt5ct/colors"
];
ensureMatugenBody =
lib.concatStringsSep "\n" (map (dir: "mkdir -p ${lib.escapeShellArg dir}") matugenOutputDirs)
+ lib.concatStringsSep "\n" (map (dir: ''
snippetDir=${lib.escapeShellArg dir}
parentDir="$(dirname "$snippetDir")"
if [ -d "$snippetDir" ]; then
:
elif [ ! -d "$parentDir" ] || [ ! -w "$parentDir" ]; then
echo "dms: skipping Obsidian snippet dir '$snippetDir' (parent missing or not writable)" >&2
else
mkdir -p "$snippetDir"
fi
'') obsidianSnippetDirs)
+ lib.optionalString ohMyPoshEnabled ''
dest="$HOME/.config/oh-my-posh/theme.omp.json"
template="$HOME/.config/matugen/templates/ohmyposh-theme.omp.json"
if [ -L "$dest" ] || { [ -e "$dest" ] && ! [ -f "$dest" ]; }; then
rm -f "$dest"
fi
if [ ! -f "$dest" ]; then
cp -L "$template" "$dest"
fi
'';
ensureMatugenOutputDirsScript = pkgs.writeShellScript "dms-ensure-matugen-output-dirs" ''
set -euo pipefail
${ensureMatugenBody}
'';
dmsRestartOnceScript = pkgs.writeShellScript "dms-plugin-restart-once" (
builtins.replaceStrings
[ "@FLOCK@" "@SYSTEMCTL@" ]
[
"${pkgs.util-linux}/bin/flock"
"${pkgs.systemd}/bin/systemctl"
]
(builtins.readFile ../scripts/dms-restart-once.sh)
);
jsonFormat = pkgs.formats.json { };
dmsConfigDir = "${config.xdg.configHome}/DankMaterialShell";
defaultSeedDir = dmsOs.defaultSeedDir or null;
dmsSeedSources =
if defaultSeedDir != null then
{
"settings.json" = defaultSeedDir + "/settings.json";
}
// lib.optionalAttrs (builtins.pathExists (defaultSeedDir + "/plugin_settings.json")) {
"plugin_settings.json" = defaultSeedDir + "/plugin_settings.json";
}
else
{
"settings.json" = jsonFormat.generate "dms-settings-seed.json" (
import ./default-settings.nix {
inherit lib gpuTempEnabled;
extraRightBarWidgets = dmsOs.extraRightBarWidgets or [ ];
}
);
};
dmsSeedBody = lib.concatMapStringsSep "\n" (
fileName:
let
dest = "${dmsConfigDir}/${fileName}";
src = dmsSeedSources.${fileName};
in
''
dest=${lib.escapeShellArg dest}
mkdir -p "$(dirname "$dest")"
if [ -L "$dest" ]; then
${pkgs.coreutils}/bin/cp --remove-destination -L "$dest" "$dest"
fi
if [ ! -e "$dest" ]; then
${pkgs.coreutils}/bin/cp ${lib.escapeShellArg src} "$dest"
chmod u+w "$dest"
fi
''
) (lib.attrNames dmsSeedSources);
dmsSeedConfigScript = pkgs.writeShellScript "dms-seed-config" ''
set -euo pipefail
${dmsSeedBody}
'';
matugenConfigText = import ./matugen-config.nix {
inherit
lib
home
hyprlandEnabled
ohMyPoshEnabled
discordEnabled
obsidianSnippetDirs
;
};
# Plugin registry auto-derived from flake inputs matching `dms-plugin-*`.
# Adding a plugin now requires editing only `flake.nix`. The on-disk
# plugin id (= path under ~/.config/DankMaterialShell/plugins/) is camelCase
# per upstream convention; the kebab→camel transform matches it. The
# `idExceptions` map carries the one plugin whose manifest id does not
# follow the default rule.
pluginInputs = lib.filterAttrs (n: _: lib.hasPrefix "dms-plugin-" n) inputs;
# `dank-vault` → `dankVault`. `lib.splitString` yields ≥ 1 element so
# `head parts` is safe; `map` runs over the tail only (no-op for
# single-word names like `calculator`). `builtins.substring 1 (-1) p`
# returns the rest of `p` from index 1, dropping the first char
# (Nix treats a negative `len` as "rest of string from `start`").
toCamel = s:
let parts = lib.splitString "-" s; in
builtins.head parts
+ lib.concatStrings (map (
p: if p == "" then ""
else lib.toUpper (builtins.substring 0 1 p)
+ builtins.substring 1 (-1) p
) (builtins.tail parts));
# The `home-assistant` plugin ships its manifest under id
# `homeAssistantMonitor` (not the default kebab→camel result).
idExceptions = { "home-assistant" = "homeAssistantMonitor"; };
# NB: don't rewrite as `idExceptions.${stripped} or toCamel stripped`.
# Nix grammar: `or` binds tighter than function application, so the
# `toCamel` and its arg parse as separate postfix args to the select.
# When the lookup succeeds the resolved string is then applied as a
# function, producing `attempt to call something which is not a function
# but a string: "homeAssistantMonitor"`. Explicit `if/hasAttr` sidesteps
# the parse entirely.
camelMap = lib.mapAttrs' (n: src:
let stripped = lib.removePrefix "dms-plugin-" n; in
lib.nameValuePair
(if builtins.hasAttr stripped idExceptions
then idExceptions.${stripped}
else toCamel stripped)
src
) pluginInputs;
# The official plugin pack bundles multiple QML apps. Ship only the
# DankDesktopWeather subdir (verified by its manifest) to slim closure
# and avoid overlapping with future dedicated `dms-plugin-*` flakes.
thirdPartyPlugins =
builtins.removeAttrs camelMap [ "official" ] // {
dankDesktopWeather = inputs.dms-plugin-official + "/DankDesktopWeather";
};
# DMS reads plugins from ~/.config/DankMaterialShell/plugins/<id>/, so
# we route each plugin source there via HM's `xdg.configFile.target`.
# The attrset name is scoped under `dms-plugin-${id}` (not the bare
# camelCase id) so unrelated HM modules can ship a same-named file
# (e.g. `xdg.configFile.calculator`) without colliding with us.
shipPlugin = id: src: {
source = src;
target = "DankMaterialShell/plugins/${id}";
onChange = "${dmsRestartOnceScript}";
};
in
{
imports = [
inputs.dms.homeModules.dank-material-shell
inputs.nix-monitor.homeManagerModules.default
];
config = lib.mkIf dmsEnabled (lib.mkMerge [
(lib.mkIf (dmsOs.bundleThirdPartyPlugins or true) {
xdg.configFile = lib.mapAttrs' (id: src: lib.nameValuePair
"dms-plugin-${id}" (shipPlugin id src)) thirdPartyPlugins;
})
{
programs.nix-monitor = {
enable = true;
inherit rebuildCommand;
generationsCommand = [
"bash"
"-c"
"readlink /nix/var/nix/profiles/system | cut -d- -f2"
];
};
home.packages = with pkgs; [
kdePackages.qtdeclarative
kdePackages.kirigami.unwrapped
kdePackages.qtmultimedia
kdePackages.sonnet
tesseract
mpv
socat
wl-clipboard
jq
cava
curl
libsecret
];
programs.dank-material-shell = {
enable = true;
package = inputs.dms.packages.${pkgs.stdenv.hostPlatform.system}.dms-shell.override {
extraQtPackages = with pkgs.kdePackages; [ qtwebsockets ];
};
systemd = {
enable = true;
restartIfChanged = true;
};
enableSystemMonitoring = true;
dgop.package = inputs.dgop.packages.${pkgs.stdenv.hostPlatform.system}.default;
enableVPN = true;
enableDynamicTheming = true;
enableAudioWavelength = true;
enableCalendarEvents = false;
# Empty on purpose: DMS owns ~/.config/DankMaterialShell/{settings,session}.json
# at runtime (seeded by `home.activation.dmsSeedConfig`). Setting these
# to non-empty here would clobber the runtime seed on every activation.
settings = { };
session = { };
};
# Surface bad obsidianVaults paths at build time instead of at first matugen run.
# Each entry must point at the `<vault>/.obsidian` dir (matugen writes
# `<vault>/.obsidian/snippets/matugen.css` from there).
assertions = lib.mkIf ((dmsOs.obsidianVaults or [ ]) != [ ]) [
{
assertion = builtins.all (v: lib.hasSuffix "/.obsidian" v) (dmsOs.obsidianVaults or [ ]);
message = "chiasson.desktop.shells.dms.obsidianVaults entries must end in '/.obsidian' (matugen writes <vault>/.obsidian/snippets/matugen.css). Example: /home/olivier/Documents/HomeLab/.obsidian";
}
];
home.activation.dmsSeedConfig = lib.hm.dag.entryAfter [ "writeBoundary" ] "${dmsSeedConfigScript}";
systemd.user.services.dms.serviceConfig.ExecStartPre =
lib.mkOrder 100 "${ensureMatugenOutputDirsScript}";
xdg.configFile."matugen/config.toml".text = matugenConfigText;
xdg.configFile."matugen/templates/dank-discord.css".source = ../templates/dank-discord.css;
xdg.configFile."matugen/templates/hyprland-colors.conf".source = ../templates/hyprland-colors.conf;
xdg.configFile."matugen/templates/ohmyposh-theme.omp.json".source = ../templates/ohmyposh-theme.omp.json;
xdg.configFile."matugen/templates/obsidian-minimal-matugen-colors.css".source =
../templates/obsidian-minimal-matugen-colors.css;
}
(lib.mkIf ohMyPoshEnabled {
programs.oh-my-posh = {
useTheme = lib.mkForce null;
configFile = lib.mkForce "${config.xdg.configHome}/oh-my-posh/theme.omp.json";
};
})
]);
}