diff --git a/distrho/DistrhoPluginUtils.hpp b/distrho/DistrhoPluginUtils.hpp index 414dcd3b5..aea47cc26 100644 --- a/distrho/DistrhoPluginUtils.hpp +++ b/distrho/DistrhoPluginUtils.hpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2024 Filipe Coelho + * Copyright (C) 2012-2025 Filipe Coelho * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -37,9 +37,29 @@ START_NAMESPACE_DISTRHO */ const char* getBinaryFilename(); +/** + Get an OS-specific directory intended to store persistent configuration data about the plugin.@n + Calling this function will ensure the dictory exists on the filesystem.@n + The returned path already includes DISTRHO_PLUGIN_NAME and final OS separator. +*/ +const char* getConfigDir(); + +/** + Get an OS-specific directory intended to store "documents" for the plugin.@n + Calling this function will ensure the dictory exists on the filesystem.@n + The returned path already includes DISTRHO_PLUGIN_NAME and final OS separator. +*/ +const char* getDocumentsDir(); + +/** + Get the user "home" directory.@n + This function is provided only for convenience, it should not be needed under normal circunstances. +*/ +const char* getHomeDir(); + /** Get a string representation of the current plugin format we are building against.@n - This can be "JACK/Standalone", "LADSPA", "DSSI", "LV2", "VST2" or "VST3".@n + This can be "AudioUnit", "JACK/Standalone", "LADSPA", "DSSI", "LV2", "VST2" or "VST3" or "CLAP".@n This string is purely informational and must not be used to tweak plugin behaviour. @note DO NOT CHANGE PLUGIN BEHAVIOUR BASED ON PLUGIN FORMAT. diff --git a/distrho/src/DistrhoUtils.cpp b/distrho/src/DistrhoUtils.cpp index c7146fcd9..d43f85479 100644 --- a/distrho/src/DistrhoUtils.cpp +++ b/distrho/src/DistrhoUtils.cpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2024 Filipe Coelho + * Copyright (C) 2012-2025 Filipe Coelho * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -23,13 +23,19 @@ #include "../DistrhoStandaloneUtils.hpp" #ifdef DISTRHO_OS_WINDOWS +# include +# include # include #else # ifndef STATIC_BUILD # include # endif +# include # include +# include # include +# include +# include #endif #ifdef DISTRHO_OS_WINDOWS @@ -76,6 +82,177 @@ const char* getBinaryFilename() return filename; } +const char* getConfigDir() +{ + #if defined(DISTRHO_OS_MAC) || defined(DISTRHO_OS_WASM) || defined(DISTRHO_OS_WINDOWS) + return getDocumentsDir(); + #else + static String dir; + + if (dir.isEmpty()) + { + if (const char* const xdgEnv = getenv("XDG_CONFIG_HOME")) + dir = xdgEnv; + + if (dir.isEmpty()) + { + dir = getHomeDir(); + dir += "/.config"; + } + + // ensure main config dir exists + if (access(dir, F_OK) != 0) + mkdir(dir, 0755); + + // and also our custom subdir + dir += "/" DISTRHO_PLUGIN_NAME "/"; + if (access(dir, F_OK) != 0) + mkdir(dir, 0755); + } + + return dir; + #endif +} + +const char* getDocumentsDir() +{ + static String dir; + + if (dir.isEmpty()) + { + #if defined(DISTRHO_OS_MAC) + dir = getHomeDir(); + dir += "/Documents/" DISTRHO_PLUGIN_NAME "/"; + #elif defined(DISTRHO_OS_WASM) + dir = getHomeDir(); + dir += "/"; + #elif defined(DISTRHO_OS_WINDOWS) + WCHAR wpath[MAX_PATH]; + if (SHGetFolderPathW(nullptr, CSIDL_MYDOCUMENTS, nullptr, SHGFP_TYPE_CURRENT, wpath) == S_OK) + { + CHAR apath[MAX_PATH]; + if (WideCharToMultiByte(CP_UTF8, 0, wpath, -1, apath, MAX_PATH, nullptr, nullptr) != 0) + { + dir = apath; + dir += "\\" DISTRHO_PLUGIN_NAME "\\"; + wcscat(wpath, L"\\" DISTRHO_PLUGIN_NAME "\\"); + } + } + #else + String xdgDirsConfigPath(getConfigDir()); + xdgDirsConfigPath += "/user-dirs.dirs"; + + if (FILE* const f = std::fopen(xdgDirsConfigPath, "r")) + { + std::fseek(f, 0, SEEK_END); + const long size = std::ftell(f); + std::fseek(f, 0, SEEK_SET); + + // something is wrong if config dirs file is longer than 1MiB! + if (size > 0 && size < 1024 * 1024) + { + if (char* filedata = static_cast(std::malloc(size))) + { + for (long r = 0, total = 0; total < size;) + { + r = std::fread(filedata + total, 1, size - total, f); + + if (r == 0) + { + std::free(filedata); + filedata = nullptr; + break; + } + + total += r; + } + + if (filedata != nullptr) + { + if (char* const xdgDocsDir = std::strstr(filedata, "XDG_DOCUMENTS_DIR=\"")) + { + if (char* const xdgDocsDirNL = std::strstr(xdgDocsDir, "\"\n")) + { + *xdgDocsDirNL = '\0'; + String sdir(xdgDocsDir + 19); + + if (sdir.startsWith("$HOME")) + { + dir = getHomeDir(); + dir += sdir.buffer() + 5; + } + else + { + dir = sdir; + } + + // ensure main config dir exists + if (access(dir, F_OK) != 0) + mkdir(dir, 0755); + } + } + + std::free(filedata); + } + } + } + + std::fclose(f); + } + + // ${XDG_CONFIG_HOME}/user-dirs.dirs does not exist or has bad data + if (dir.isEmpty()) + { + dir = getDocumentsDir(); + dir += DISTRHO_PLUGIN_NAME "/"; + } + #endif + + // ensure our custom subdir exists + if (dir.isNotEmpty()) + { + #ifdef DISTRHO_OS_WINDOWS + _wmkdir(wpath); + #else + if (access(dir, F_OK) != 0) + mkdir(dir, 0755); + #endif + } + } + + return dir; +} + +const char* getHomeDir() +{ + static String dir; + + if (dir.isEmpty()) + { + #ifdef DISTRHO_OS_WINDOWS + WCHAR wpath[MAX_PATH]; + if (SHGetFolderPathW(nullptr, CSIDL_PROFILE, nullptr, SHGFP_TYPE_CURRENT, wpath) == S_OK) + { + CHAR apath[MAX_PATH]; + if (WideCharToMultiByte(CP_UTF8, 0, wpath, -1, apath, MAX_PATH, nullptr, nullptr) != 0) + dir = apath; + } + #else + if (const char* const homeEnv = getenv("HOME")) + dir = homeEnv; + + if (dir.isEmpty()) + if (struct passwd* const pwd = getpwuid(getuid())) + dir = pwd->pw_dir; + + if (dir.isNotEmpty() && ! dir.endsWith('/')) + dir += "/"; + #endif + } + + return dir; +} + const char* getPluginFormatName() noexcept { #if defined(DISTRHO_PLUGIN_TARGET_AU)