# 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//, 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 `/.obsidian` dir (matugen writes # `/.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 /.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"; }; }) ]); }