From 7db20cd2ac2c8b8774a8ae02a7b233541666c865 Mon Sep 17 00:00:00 2001 From: "Christopher R. Nelson" Date: Mon, 20 Jul 2026 22:56:01 -0400 Subject: Add XDG applications directory scanner Resolves the ordered list of "applications" directories to search per the XDG Base Directory Specification: XDG_DATA_HOME (default ~/.local/share) first, then XDG_DATA_DIRS (default /usr/local/share, /usr/share). Each variable falls back to its default independently when unset or empty, so a Flatpak-style XDG_DATA_DIRS override is honored in place of the default rather than merged with it. find_desktop_files/0,1 recursively collects .desktop files from those dirs, skipping ones that don't exist. Co-Authored-By: Claude Sonnet 5 --- src/er_xdg_pipe_menu_scanner.erl | 62 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 src/er_xdg_pipe_menu_scanner.erl (limited to 'src') diff --git a/src/er_xdg_pipe_menu_scanner.erl b/src/er_xdg_pipe_menu_scanner.erl new file mode 100644 index 0000000..131b4f6 --- /dev/null +++ b/src/er_xdg_pipe_menu_scanner.erl @@ -0,0 +1,62 @@ +%%%------------------------------------------------------------------- +%% @doc Locates .desktop files under the XDG application directories. +%% +%% Directory precedence follows the XDG Base Directory Specification: +%% the user's data home (most specific, so its entries should win over +%% system ones downstream) is searched first, then each directory in +%% `XDG_DATA_DIRS'. Each of `XDG_DATA_HOME' and `XDG_DATA_DIRS' falls +%% back to its spec-defined default independently when unset or empty +%% -- so a Flatpak-aware `XDG_DATA_DIRS' (which typically adds +%% `~/.local/share/flatpak/exports/share') is honored in place of the +%% default rather than merged with it. +%% @end +%%%------------------------------------------------------------------- + +-module(er_xdg_pipe_menu_scanner). + +-export([data_dirs/0, find_desktop_files/0, find_desktop_files/1]). + +-define(DEFAULT_DATA_DIRS, ["/usr/local/share", "/usr/share"]). + +-spec data_dirs() -> [file:filename_all()]. +data_dirs() -> + [filename:join(Dir, "applications") || Dir <- data_home() ++ data_dirs_list()]. + +-spec find_desktop_files() -> [file:filename_all()]. +find_desktop_files() -> + find_desktop_files(data_dirs()). + +-spec find_desktop_files([file:filename_all()]) -> [file:filename_all()]. +find_desktop_files(Dirs) -> + lists:flatmap(fun find_in_dir/1, Dirs). + +find_in_dir(Dir) -> + case filelib:is_dir(Dir) of + true -> filelib:wildcard(filename:join(Dir, "**/*.desktop")); + false -> [] + end. + +data_home() -> + case env("XDG_DATA_HOME") of + undefined -> + case env("HOME") of + undefined -> []; + Home -> [filename:join([Home, ".local", "share"])] + end; + Dir -> + [Dir] + end. + +data_dirs_list() -> + case env("XDG_DATA_DIRS") of + undefined -> ?DEFAULT_DATA_DIRS; + Value -> [D || D <- string:split(Value, ":", all), D =/= ""] + end. + +%% The spec treats an unset and an empty variable the same way. +env(Name) -> + case os:getenv(Name) of + false -> undefined; + "" -> undefined; + Value -> Value + end. -- cgit v1.2.3