Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 50 additions & 17 deletions modules/misc/xdg-user-dirs.nix
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ in
'';
};

package = lib.mkPackageOption pkgs "xdg-user-dirs" { nullable = true; };

# Well-known directory list from
# https://gitlab.freedesktop.org/xdg/xdg-user-dirs/blob/master/man/user-dirs.dirs.xml

Expand Down Expand Up @@ -101,45 +103,76 @@ in
defaultText = literalExpression "{ }";
example = literalExpression ''
{
XDG_MISC_DIR = "''${config.home.homeDirectory}/Misc";
MISC = "''${config.home.homeDirectory}/Misc";
}
'';
description = "Other user directories.";
description = ''
Other user directories.

Prior to 25.11, these should be named like `XDG_MISC_DIR`.
'';
};

createDirectories = lib.mkEnableOption "automatic creation of the XDG user directories";

setSessionVariables = mkOption {
type = with types; bool;
default = lib.versionOlder config.home.stateVersion "25.11";
defaultText = literalExpression ''
lib.versionOlder config.home.stateVersion "25.11"
'';
description = ''
Whether to set the XDG user dir environment variables, like
`XDG_DESKTOP_DIR`. The recommended way to get these values is via the
`xdg-user-dir` command or by processing
`$XDG_CONFIG_HOME/user-dirs.dirs` directly in your application.

This defaults to `true` for state version < 25.11 and `false` otherwise.
'';
};
};

config =
let
directories =
(lib.filterAttrs (n: v: !isNull v) {
XDG_DESKTOP_DIR = cfg.desktop;
XDG_DOCUMENTS_DIR = cfg.documents;
XDG_DOWNLOAD_DIR = cfg.download;
XDG_MUSIC_DIR = cfg.music;
XDG_PICTURES_DIR = cfg.pictures;
XDG_PUBLICSHARE_DIR = cfg.publicShare;
XDG_TEMPLATES_DIR = cfg.templates;
XDG_VIDEOS_DIR = cfg.videos;
DESKTOP = cfg.desktop;
DOCUMENTS = cfg.documents;
DOWNLOAD = cfg.download;
MUSIC = cfg.music;
PICTURES = cfg.pictures;
PUBLICSHARE = cfg.publicShare;
TEMPLATES = cfg.templates;
VIDEOS = cfg.videos;
})
// cfg.extraConfig;
// (
if lib.versionOlder config.home.stateVersion "25.11" then
lib.mapAttrs' (
k:
let
name = lib.match "XDG_(.*)_DIR" k;
in
lib.nameValuePair (if name == null then k else lib.elemAt name 0)
) cfg.extraConfig
else
cfg.extraConfig
Comment on lines +148 to +158
Copy link
Collaborator

@khaneliman khaneliman Oct 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we make this a louder transition that gives people a chance to know about the effect through an apply function with a warning? That way, we can detect if the string matches the old format, perform the transform, and then warn about the deprecation.

Just gives us the ability to be a true deprecation and can eventually remove the extra logic / tech debt.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for letting this languish …

Does this mean the "25.11" conditionalization should be removed completely in favor of the warning?

And should the true/false conditionalization for setSessionVariables stay the way it is (since it’s more of a trivial switch), or should it also move to the warning/deprecation style?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't mind the stateVersion stuff as a way to prevent breaking existing configs. But, it would be nice to add a warning inside this function when we are going through the old behavior so people can update to the new behavior and we have the ability to remove this stateVersion logic in the future to keep the module simpler.

);

bindings = lib.mapAttrs' (k: lib.nameValuePair "XDG_${k}_DIR") directories;
in
lib.mkIf cfg.enable {
assertions = [
(lib.hm.assertions.assertPlatform "xdg.userDirs" pkgs lib.platforms.linux)
];

xdg.configFile."user-dirs.dirs".text =
let
# For some reason, these need to be wrapped with quotes to be valid.
wrapped = lib.mapAttrs (_: value: ''"${value}"'') directories;
wrapped = lib.mapAttrs (_: value: ''"${value}"'') bindings;
in
lib.generators.toKeyValue { } wrapped;

xdg.configFile."user-dirs.conf".text = "enabled=False";

home.sessionVariables = directories;
home.packages = lib.mkIf (cfg.package != null) [ cfg.package ];

home.sessionVariables = lib.mkIf cfg.setSessionVariables bindings;

home.activation.createXdgUserDirectories = lib.mkIf cfg.createDirectories (
let
Expand Down
3 changes: 3 additions & 0 deletions tests/modules/misc/xdg/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,7 @@
xdg-mime-disabled = ./mime-disabled.nix;
xdg-autostart = ./autostart.nix;
xdg-autostart-readonly = ./autostart-readonly.nix;
xdg-user-dirs-mixed = ./user-dirs-mixed.nix;
xdg-user-dirs-null = ./user-dirs-null.nix;
xdg-user-dirs-short = ./user-dirs-short.nix;
}
1 change: 0 additions & 1 deletion tests/modules/misc/xdg/linux.nix
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
xdg-mime-apps-basics = ./mime-apps-basics.nix;
xdg-system-dirs = ./system-dirs.nix;
xdg-desktop-entries = ./desktop-entries.nix;
xdg-user-dirs-null = ./user-dirs-null.nix;
xdg-portal = ./portal.nix;
xdg-mime = ./mime.nix;
xdg-mime-package = ./mime-packages.nix;
Expand Down
35 changes: 35 additions & 0 deletions tests/modules/misc/xdg/user-dirs-mixed.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
config,
pkgs,
...
}:

{
config = {
home.stateVersion = "25.05";

xdg.userDirs = {
enable = true;
extraConfig.PROJECTS = "${config.home.homeDirectory}/Projects";
## This will stop working with stateVersion 25.11.
extraConfig.XDG_MISC_DIR = "${config.home.homeDirectory}/Misc";
};

nmt.script = ''
configFile=home-files/.config/user-dirs.dirs
assertFileExists $configFile
assertFileContent $configFile ${pkgs.writeText "expected" ''
XDG_DESKTOP_DIR="/home/hm-user/Desktop"
XDG_DOCUMENTS_DIR="/home/hm-user/Documents"
XDG_DOWNLOAD_DIR="/home/hm-user/Downloads"
XDG_MISC_DIR="/home/hm-user/Misc"
XDG_MUSIC_DIR="/home/hm-user/Music"
XDG_PICTURES_DIR="/home/hm-user/Pictures"
XDG_PROJECTS_DIR="/home/hm-user/Projects"
XDG_PUBLICSHARE_DIR="/home/hm-user/Public"
XDG_TEMPLATES_DIR="/home/hm-user/Templates"
XDG_VIDEOS_DIR="/home/hm-user/Videos"
''}
'';
};
}
30 changes: 30 additions & 0 deletions tests/modules/misc/xdg/user-dirs-short.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
config,
pkgs,
...
}:

{
config = {
xdg.userDirs = {
enable = true;
extraConfig.PROJECTS = "${config.home.homeDirectory}/Projects";
};

nmt.script = ''
configFile=home-files/.config/user-dirs.dirs
assertFileExists $configFile
assertFileContent $configFile ${pkgs.writeText "expected" ''
XDG_DESKTOP_DIR="/home/hm-user/Desktop"
XDG_DOCUMENTS_DIR="/home/hm-user/Documents"
XDG_DOWNLOAD_DIR="/home/hm-user/Downloads"
XDG_MUSIC_DIR="/home/hm-user/Music"
XDG_PICTURES_DIR="/home/hm-user/Pictures"
XDG_PROJECTS_DIR="/home/hm-user/Projects"
XDG_PUBLICSHARE_DIR="/home/hm-user/Public"
XDG_TEMPLATES_DIR="/home/hm-user/Templates"
XDG_VIDEOS_DIR="/home/hm-user/Videos"
''}
'';
};
}