From 004a9cb3708fbc450e0c0900719b9fa92ce476ef Mon Sep 17 00:00:00 2001 From: Michael Koloberdin Date: Fri, 26 Aug 2022 22:57:32 +0300 Subject: [PATCH 1/3] Require C++17 (reqd for xdgpp), it's been default in both g++ and clang++ for years anyway --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) mode change 100755 => 100644 CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt old mode 100755 new mode 100644 index 9013ce6f..6219b5af --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,8 +2,8 @@ cmake_minimum_required(VERSION 2.8.12) #compiler settings -#set(CMAKE_CXX_STANDARD 11) -#set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) #set(CMAKE_CXX_EXTENSIONS OFF) #set(CMAKE_C_COMPILER gcc) #set(CMAKE_CXX_COMPILER g++) From c3cd5a762bd261e01f6d20dd22d7b878565ca1ec Mon Sep 17 00:00:00 2001 From: Michael Koloberdin Date: Fri, 26 Aug 2022 23:03:36 +0300 Subject: [PATCH 2/3] Add xdgpp @ f01f8107 --- 3rdparty/xdgpp/.builds/archlinux.yml | 14 ++ 3rdparty/xdgpp/.clang-format | 5 + 3rdparty/xdgpp/.clang-tidy | 37 +++++ 3rdparty/xdgpp/.gitignore | 2 + 3rdparty/xdgpp/LICENSE | 20 +++ 3rdparty/xdgpp/Makefile | 21 +++ 3rdparty/xdgpp/README.md | 70 +++++++++ 3rdparty/xdgpp/xdg.hpp | 216 +++++++++++++++++++++++++++ 3rdparty/xdgpp/xdg_test.cpp | 137 +++++++++++++++++ 9 files changed, 522 insertions(+) create mode 100644 3rdparty/xdgpp/.builds/archlinux.yml create mode 100644 3rdparty/xdgpp/.clang-format create mode 100644 3rdparty/xdgpp/.clang-tidy create mode 100644 3rdparty/xdgpp/.gitignore create mode 100644 3rdparty/xdgpp/LICENSE create mode 100644 3rdparty/xdgpp/Makefile create mode 100644 3rdparty/xdgpp/README.md create mode 100644 3rdparty/xdgpp/xdg.hpp create mode 100644 3rdparty/xdgpp/xdg_test.cpp diff --git a/3rdparty/xdgpp/.builds/archlinux.yml b/3rdparty/xdgpp/.builds/archlinux.yml new file mode 100644 index 00000000..1d717ba2 --- /dev/null +++ b/3rdparty/xdgpp/.builds/archlinux.yml @@ -0,0 +1,14 @@ +image: archlinux +packages: + - gcc + - clang + - catch2 +sources: + - https://git.sr.ht/~danyspin97/xdgpp +tasks: + - test-gcc: | + cd xdgpp + CXX=g++ make clean && make test -j2 + - test-clang: | + cd xdgpp + CXX=clang++ make clean && make test -j2 diff --git a/3rdparty/xdgpp/.clang-format b/3rdparty/xdgpp/.clang-format new file mode 100644 index 00000000..cc17704a --- /dev/null +++ b/3rdparty/xdgpp/.clang-format @@ -0,0 +1,5 @@ +--- +Language: Cpp +# BasedOnStyle: Google +AccessModifierOffset: -4 +IndentWidth: 4 diff --git a/3rdparty/xdgpp/.clang-tidy b/3rdparty/xdgpp/.clang-tidy new file mode 100644 index 00000000..2b1535c4 --- /dev/null +++ b/3rdparty/xdgpp/.clang-tidy @@ -0,0 +1,37 @@ +--- +Checks: 'clang-diagnostic-*,clang-analyzer-*,misc-*,modernize-*,performance-*,readability-*,portability-*,cppcoreguidelines-*,bugprone-*,hicpp-*' +WarningsAsErrors: '' +HeaderFilterRegex: '' +AnalyzeTemporaryDtors: false +FormatStyle: none +CheckOptions: + - key: cert-dcl16-c.NewSuffixes + value: 'L;LL;LU;LLU' + - key: cert-oop54-cpp.WarnOnlyIfThisHasSuspiciousField + value: '0' + - key: cppcoreguidelines-explicit-virtual-functions.IgnoreDestructors + value: '1' + - key: cppcoreguidelines-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic + value: '1' + - key: google-readability-braces-around-statements.ShortStatementLines + value: '1' + - key: google-readability-function-size.StatementThreshold + value: '800' + - key: google-readability-namespace-comments.ShortNamespaceLines + value: '10' + - key: google-readability-namespace-comments.SpacesBeforeComments + value: '2' + - key: llvm-qualified-auto.AddConstToQualified + value: '0' + - key: modernize-loop-convert.MaxCopySize + value: '16' + - key: modernize-loop-convert.MinConfidence + value: reasonable + - key: modernize-pass-by-value.IncludeStyle + value: google + - key: modernize-replace-auto-ptr.IncludeStyle + value: google + - key: modernize-use-nullptr.NullMacros + value: 'NULL' +... + diff --git a/3rdparty/xdgpp/.gitignore b/3rdparty/xdgpp/.gitignore new file mode 100644 index 00000000..3c78355e --- /dev/null +++ b/3rdparty/xdgpp/.gitignore @@ -0,0 +1,2 @@ +*.o +xdg_test diff --git a/3rdparty/xdgpp/LICENSE b/3rdparty/xdgpp/LICENSE new file mode 100644 index 00000000..6aec5963 --- /dev/null +++ b/3rdparty/xdgpp/LICENSE @@ -0,0 +1,20 @@ +Copyright © 2020 Danilo Spinella + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/3rdparty/xdgpp/Makefile b/3rdparty/xdgpp/Makefile new file mode 100644 index 00000000..24468ea2 --- /dev/null +++ b/3rdparty/xdgpp/Makefile @@ -0,0 +1,21 @@ +CXX?=g++ +CXXFLAGS?= +DEFAULT_CXXFLAGS:=-std=c++17 -Werror -Wall +CXXFLAGS+=$(DEFAULT_CXXFLAGS) +CATCH2_CXXFLAGS:=-DCATCH_CONFIG_MAIN -DCATCH_CONFIG_FAST_COMPILE +CXXFLAGS+=$(CATCH2_CXXFLAGS) +INCLUDES:=-I. +NAME:=xdg_test + +SOURCES=xdg_test.cpp +OBJ=$(SOURCES:.cpp=.o) + +xdg_test: $(OBJ) + $(CXX) $(CXXFLAGS) $(INCLUDES) $^ -o $(NAME) + +test: xdg_test + ./xdg_test + +.PHONY: clean +clean: + rm -f $(OBJ) $(NAME) diff --git a/3rdparty/xdgpp/README.md b/3rdparty/xdgpp/README.md new file mode 100644 index 00000000..5ef7c294 --- /dev/null +++ b/3rdparty/xdgpp/README.md @@ -0,0 +1,70 @@ +# xdgpp + +[![builds.sr.ht status](https://builds.sr.ht/~danyspin97/xdgpp.svg)](https://builds.sr.ht/~danyspin97/xdgpp?) +![Liberapay receiving](https://img.shields.io/liberapay/receives/danyspin97?logo=liberapay) + +C++17 header-only implementation of the [XDG Base Directory Specification](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html). + +## Installation + +Copy `xdg.hpp` into your source tree. + +## Usage + +```cpp +#include + +#include "xdg.hpp" + +int main() { + std::cout << "$XDG_DATA_HOME = " << xdg::DataHomeDir() << std::endl; + std::cout << "$XDG_CONFIG_HOME = " << xdg::ConfigHomeDir() << std::endl; + std::cout << "$XDG_CACHE_HOME = " << xdg::CacheHomeDir() << std::endl; + + auto data_dirs = xdg::DataDirs(); + std::cout << "$XDG_DATA_DIRS = \"" << data_dirs.front().c_str(); + for (int i = 1; i < data_dirs.size(); i++) { + std::cout << ":" << data_dirs.at(i).c_str(); + } + std::cout << "\"" << std::endl; + + auto config_dirs = xdg::ConfigDirs(); + std::cout << "$XDG_CONFIG_DIRS = \"" << config_dirs.front().c_str(); + for (int i = 1; i < config_dirs.size(); i++) { + std::cout << ":" << config_dirs.at(i).c_str(); + } + std::cout << "\"" << std::endl; + + // XDG_RUNTIME_DIR might not be set, the API returns a std::optional + auto runtime_dir = xdg::RuntimeDir(); + if (runtime_dir) { + std::cout << "$XDG_RUNTIME_DIR = " << runtime_dir.value(); + } + + return 0; +} +``` + +Alternatively you can create and use an instance of `xdg::BaseDirectories` like: + +```cpp +#include + +#include "xdg.hpp" + +int main() { + xdg::BaseDirectories dirs; + std::cout << "$XDG_DATA_HOME = " << dirs.DataHome() << std::endl; +} +``` + +## Contributing + +Pull requests are welcome. + +**xdgpp** follows the [Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html); run `clang-format` on changes to automatically format the code. + +## License + +**xdgpp** is licensed under [the Mit License](https://mit-license.org/). + diff --git a/3rdparty/xdgpp/xdg.hpp b/3rdparty/xdgpp/xdg.hpp new file mode 100644 index 00000000..f3eec1bf --- /dev/null +++ b/3rdparty/xdgpp/xdg.hpp @@ -0,0 +1,216 @@ +/* + * Copyright © 2020 Danilo Spinella + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include + +namespace xdg { + +class BaseDirectoryException : public std::exception { +public: + explicit BaseDirectoryException(std::string msg) : msg_(std::move(msg)) {} + + [[nodiscard]] auto what() const noexcept -> const char * override { + return msg_.c_str(); + } + [[nodiscard]] auto msg() const noexcept -> std::string { return msg_; } + +private: + const std::string msg_; +}; + +class BaseDirectories { +public: + BaseDirectories() { + const char *home_env = getenv("HOME"); + if (home_env == nullptr) { + throw BaseDirectoryException("$HOME must be set"); + } + home_ = std::filesystem::path{home_env}; + + data_home_ = GetAbsolutePathFromEnvOrDefault( + "XDG_DATA_HOME", home_ / ".local" / "share"); + config_home_ = GetAbsolutePathFromEnvOrDefault("XDG_CONFIG_HOME", + home_ / ".config"); + data_ = GetPathsFromEnvOrDefault("XDG_DATA_DIRS", + std::vector{ + "/usr/local/share", "/usr/share"}); + config_ = GetPathsFromEnvOrDefault( + "XDG_CONFIG_DIRS", std::vector{"/etc/xdg"}); + cache_home_ = + GetAbsolutePathFromEnvOrDefault("XDG_CACHE_HOME", home_ / ".cache"); + + SetRuntimeDir(); + } + + static auto GetInstance() -> BaseDirectories & { + static BaseDirectories dirs; + + return dirs; + } + + [[nodiscard]] auto DataHome() -> const std::filesystem::path & { + return data_home_; + } + [[nodiscard]] auto ConfigHome() -> const std::filesystem::path & { + return config_home_; + } + [[nodiscard]] auto Data() -> const std::vector & { + return data_; + } + [[nodiscard]] auto Config() -> const std::vector & { + return config_; + } + [[nodiscard]] auto CacheHome() -> const std::filesystem::path & { + return cache_home_; + } + [[nodiscard]] auto Runtime() + -> const std::optional & { + return runtime_; + } + +private: + void SetRuntimeDir() { + const char *runtime_env = getenv("XDG_RUNTIME_DIR"); + if (runtime_env != nullptr) { + std::filesystem::path runtime_dir{runtime_env}; + if (runtime_dir.is_absolute()) { + if (!std::filesystem::exists(runtime_dir)) { + throw BaseDirectoryException( + "$XDG_RUNTIME_DIR must exist on the system"); + } + + auto runtime_dir_perms = + std::filesystem::status(runtime_dir).permissions(); + using perms = std::filesystem::perms; + // Check XDG_RUNTIME_DIR permissions are 0700 + if (((runtime_dir_perms & perms::owner_all) == perms::none) || + ((runtime_dir_perms & perms::group_all) != perms::none) || + ((runtime_dir_perms & perms::others_all) != perms::none)) { + throw BaseDirectoryException( + "$XDG_RUNTIME_DIR must have 0700 as permissions"); + } + runtime_.emplace(runtime_dir); + } + } + } + + static auto + GetAbsolutePathFromEnvOrDefault(const char *env_name, + std::filesystem::path &&default_path) + -> std::filesystem::path { + const char *env_var = getenv(env_name); + if (env_var == nullptr) { + return std::move(default_path); + } + auto path = std::filesystem::path{env_var}; + if (!path.is_absolute()) { + return std::move(default_path); + } + + return path; + } + + static auto + GetPathsFromEnvOrDefault(const char *env_name, + std::vector &&default_paths) + -> std::vector { + auto *env = getenv(env_name); + if (env == nullptr) { + return std::move(default_paths); + } + std::string paths{env}; + + std::vector dirs{}; + size_t start = 0; + size_t pos = 0; + while ((pos = paths.find_first_of(':', start)) != std::string::npos) { + std::filesystem::path current_path{ + paths.substr(start, pos - start)}; + if (current_path.is_absolute() && + !VectorContainsPath(dirs, current_path)) { + dirs.emplace_back(current_path); + } + start = pos + 1; + } + std::filesystem::path current_path{paths.substr(start, pos - start)}; + if (current_path.is_absolute() && + !VectorContainsPath(dirs, current_path)) { + dirs.emplace_back(current_path); + } + + if (dirs.empty()) { + return std::move(default_paths); + } + + return dirs; + } + + static auto + VectorContainsPath(const std::vector &paths, + const std::filesystem::path &path) -> bool { + return std::find(std::begin(paths), std::end(paths), path) != + paths.end(); + } + + std::filesystem::path home_; + + std::filesystem::path data_home_; + std::filesystem::path config_home_; + std::vector data_; + std::vector config_; + std::filesystem::path cache_home_; + std::optional runtime_; + +}; // namespace xdg + +[[nodiscard]] inline auto DataHomeDir() -> const std::filesystem::path & { + return BaseDirectories::GetInstance().DataHome(); +} +[[nodiscard]] inline auto ConfigHomeDir() -> const std::filesystem::path & { + return BaseDirectories::GetInstance().ConfigHome(); +} +[[nodiscard]] inline auto DataDirs() + -> const std::vector & { + return BaseDirectories::GetInstance().Data(); +} +[[nodiscard]] inline auto ConfigDirs() + -> const std::vector & { + return BaseDirectories::GetInstance().Config(); +} +[[nodiscard]] inline auto CacheHomeDir() -> const std::filesystem::path & { + return BaseDirectories::GetInstance().CacheHome(); +} +[[nodiscard]] inline auto RuntimeDir() + -> const std::optional & { + return BaseDirectories::GetInstance().Runtime(); +} + +} // namespace xdg diff --git a/3rdparty/xdgpp/xdg_test.cpp b/3rdparty/xdgpp/xdg_test.cpp new file mode 100644 index 00000000..6dfffdb4 --- /dev/null +++ b/3rdparty/xdgpp/xdg_test.cpp @@ -0,0 +1,137 @@ +/* + * Copyright © 2020 Danilo Spinella + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "xdg.hpp" + +#include "catch2/catch.hpp" + +#include + +template +void TestSingleDirectory(T function, const char *env_name, + const std::filesystem::path &default_path) { + unsetenv(env_name); + xdg::BaseDirectories dirs; + CHECK(std::bind(function, &dirs)() == default_path); + + const auto *new_env = "/tmp/mydir"; + setenv(env_name, new_env, 1); + dirs = xdg::BaseDirectories{}; + CHECK(std::bind(function, &dirs)() == std::filesystem::path{new_env}); + + setenv(env_name, &new_env[1], 1); + dirs = xdg::BaseDirectories{}; + CHECK(std::bind(function, &dirs)() == default_path); +} + +template +void TestMultipleDirectories( + T function, const char *env_name, + const std::vector &default_paths) { + unsetenv(env_name); + xdg::BaseDirectories dirs; + CHECK(std::bind(function, &dirs)() == default_paths); + + const auto *new_env = "/tmp/mydir:/tmp/mydir2"; + setenv(env_name, new_env, 1); + dirs = xdg::BaseDirectories{}; + auto paths = + std::vector{"/tmp/mydir", "/tmp/mydir2"}; + CHECK(std::bind(function, &dirs)() == paths); + + new_env = "tmp/mydir:tmp/mydir1"; + setenv(env_name, new_env, 1); + dirs = xdg::BaseDirectories{}; + CHECK(std::bind(function, &dirs)() == default_paths); + + new_env = "/tmp/mydir:tmp/mydir1"; + setenv(env_name, new_env, 1); + dirs = xdg::BaseDirectories{}; + CHECK(std::bind(function, &dirs)() == + std::vector{ + std::filesystem::path{"/tmp/mydir"}}); +} + +TEST_CASE("xdg::Dirs") { + SECTION("XDG_DATA_HOME") { + TestSingleDirectory(&xdg::BaseDirectories::DataHome, "XDG_DATA_HOME", + std::filesystem::path{getenv("HOME")} / ".local" / + "share"); + } + SECTION("XDG_CONFIG_HOME") { + TestSingleDirectory(&xdg::BaseDirectories::ConfigHome, + "XDG_CONFIG_HOME", + std::filesystem::path{getenv("HOME")} / ".config"); + } + SECTION("XDG_DATA_DIRS") { + TestMultipleDirectories(&xdg::BaseDirectories::Data, "XDG_DATA_DIRS", + {std::filesystem::path{"/usr/local/share"}, + std::filesystem::path{"/usr/share"}}); + } + + SECTION("XDG_CONFIG_DIRS") { + TestMultipleDirectories(&xdg::BaseDirectories::Config, + "XDG_CONFIG_DIRS", + {std::filesystem::path{"/etc/xdg"}}); + } + SECTION("XDG_CACHE_HOME") { + TestSingleDirectory(&xdg::BaseDirectories::CacheHome, "XDG_CACHE_HOME", + std::filesystem::path{getenv("HOME")} / ".cache"); + } + + SECTION("XDG_RUNTIME_DIR") { + std::filesystem::path new_dir{std::filesystem::current_path()}; + new_dir /= "runtime_dir"; + if (std::filesystem::exists(new_dir)) { + // Clean up previous tests + std::filesystem::remove(new_dir); + } + setenv("XDG_RUNTIME_DIR", new_dir.c_str(), 1); + CHECK_THROWS_AS(xdg::BaseDirectories{}, xdg::BaseDirectoryException); + + // Create directory + std::filesystem::create_directory(new_dir); + // Apply 0777 mode + std::filesystem::permissions(new_dir, + std::filesystem::perms::owner_all | + std::filesystem::perms::group_all + + | std::filesystem::perms::others_all, + std::filesystem::perm_options::add); + REQUIRE_THROWS_AS(xdg::BaseDirectories{}, xdg::BaseDirectoryException); + + // Remove group_all and other_all, so that permissions will be 0700 + std::filesystem::permissions(new_dir, + std::filesystem::perms::group_all + + | std::filesystem::perms::others_all, + std::filesystem::perm_options::remove); + + xdg::BaseDirectories *dirs = nullptr; + REQUIRE_NOTHROW(dirs = new xdg::BaseDirectories{}); + REQUIRE(dirs->Runtime()); + CHECK(dirs->Runtime().value() == new_dir); + + std::filesystem::remove(new_dir); + delete dirs; + } +} From 0ed438d6d4f7f6b8e3fc26eee6f5edf44b20eecf Mon Sep 17 00:00:00 2001 From: Michael Koloberdin Date: Sat, 27 Aug 2022 01:46:23 +0300 Subject: [PATCH 3/3] XDG compliance / make Xpeccy packageable * Try to read ROM files from `~/.local/share` first (XDG data dir rather than `~/.config`) * In case the above fails, then try global XDG data dirs * UI (ROMset editor) still shows ROMs only from user's dir. Needs more work. (Custom multi-directory QT filesystem model) --- CMakeLists.txt | 2 ++ src/xcore/config.cpp | 35 +++++++++++++++++++++++++++------ src/xcore/profiles.cpp | 8 ++++---- src/xcore/xcore.h | 32 +++++++++++++++++++++++++++++- src/xgui/options/opt_romset.cpp | 4 ++-- 5 files changed, 68 insertions(+), 13 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6219b5af..051aaae3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -273,6 +273,8 @@ endif() # other +include_directories(${PROJECT_SOURCE_DIR}/3rdparty/xdgpp) + if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") add_executable(${PROJECT_NAME} ${SOURCES} ${HEADERS} ${UIHEADERS} ${RESOURCES} ${MOCHEADERS}) elseif(${CMAKE_SYSTEM_NAME} MATCHES "/BSD$/") diff --git a/src/xcore/config.cpp b/src/xcore/config.cpp index 62d7d771..742f95b1 100644 --- a/src/xcore/config.cpp +++ b/src/xcore/config.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include @@ -56,9 +57,31 @@ void conf_init(char* wpath, char* confdir) { conf.path.confDir = std::string(confdir); } mkdir(conf.path.confDir.c_str(), 0777); - conf.path.romDir = conf.path.confDir + "/roms"; - mkdir(conf.path.romDir.c_str() ,0777); - conf.path.prfDir = conf.path.confDir + "/profiles"; + + fs::path dataDirsSuffix = "samstyle/xpeccy"; + + conf.path.dataHomeDir = xdg::DataHomeDir() / dataDirsSuffix; + fs::create_directories(conf.path.dataHomeDir); + + conf.path.romHomeDir = conf.path.dataHomeDir / "roms"; + + // TEMPORARY, to handle legacy setups: + // ROMs should be in the XDG "data" directory rather than in the "config" one. + // Detect if the user has ROMs in the "config" directory and move them where they belong. + auto oldRomDir = fs::path{conf.path.confDir} / "roms"; + if (fs::exists(oldRomDir) && !fs::exists(conf.path.romHomeDir)) { + std::cout << "Moving " << oldRomDir << " -> " << conf.path.romHomeDir << std::endl; + fs::rename(oldRomDir, conf.path.romHomeDir); + } + + fs::create_directory(conf.path.romHomeDir); + + for (auto &dir : xdg::DataDirs()) { + conf.path.romDataDirs.emplace_back(dir / dataDirsSuffix / "roms"); + } + + + conf.path.prfDir = conf.path.confDir + "/profiles"; mkdir(conf.path.prfDir.c_str() ,0777); conf.path.shdDir = conf.path.confDir + "/shaders"; mkdir(conf.path.shdDir.c_str() ,0777); @@ -75,13 +98,13 @@ void conf_init(char* wpath, char* confdir) { } else { conf.path.confDir = std::string(confdir); } - conf.path.romDir = conf.path.confDir + "\\roms"; + conf.path.romHomeDir = conf.path.confDir + "\\roms"; conf.path.prfDir = conf.path.confDir + "\\profiles"; conf.path.shdDir = conf.path.confDir + "\\shaders"; conf.path.confFile = conf.path.confDir + "\\config.conf"; conf.path.boot = conf.path.confDir + "\\boot.$B"; mkdir(conf.path.confDir.c_str()); - mkdir(conf.path.romDir.c_str()); + mkdir(conf.path.romHomeDir.c_str()); mkdir(conf.path.prfDir.c_str()); mkdir(conf.path.shdDir.c_str()); #endif @@ -247,7 +270,7 @@ void loadConfig() { strcat(fname, SLASH); strcat(fname, "xpeccy.conf"); copyFile(":/conf/xpeccy.conf", fname); - strcpy(fname, conf.path.romDir.c_str()); + strcpy(fname, conf.path.romHomeDir.c_str()); strcat(fname, SLASH); strcat(fname, "1982.rom"); copyFile(":/conf/1982.rom", fname); diff --git a/src/xcore/profiles.cpp b/src/xcore/profiles.cpp index 43a3fd35..91898e37 100644 --- a/src/xcore/profiles.cpp +++ b/src/xcore/profiles.cpp @@ -244,7 +244,7 @@ void prfSetRomset(xProfile* prf, std::string rnm) { foreach(xRomFile xrf, rset->roms) { foff = xrf.foffset * 1024; roff = xrf.roffset * 1024; - fpath = conf.path.romDir + SLASH + xrf.name; + fpath = conf.path.findRom(xrf.name); file = fopen(fpath.c_str(), "rb"); if (file) { if (xrf.fsize <= 0) { // check part size @@ -272,7 +272,7 @@ void prfSetRomset(xProfile* prf, std::string rnm) { memSetSize(prf->zx->mem, -1, romsz); // load GS ROM if (!rset->gsFile.empty()) { - fpath = conf.path.romDir + SLASH + rset->gsFile; + fpath = conf.path.findRom(rset->gsFile); file = fopen(fpath.c_str(), "rb"); if (file) { fread(prf->zx->gs->mem->romData, MEM_32K, 1, file); @@ -284,7 +284,7 @@ void prfSetRomset(xProfile* prf, std::string rnm) { } // load ATM2 font data if (!rset->fntFile.empty()) { - fpath = conf.path.romDir + SLASH + rset->fntFile; + fpath = conf.path.findRom(rset->fntFile); file = fopen(fpath.c_str(), "rb"); if (file) { fread(prf->zx->vid->font, MEM_8K, 1, file); @@ -295,7 +295,7 @@ void prfSetRomset(xProfile* prf, std::string rnm) { memset(prf->zx->vid->bios, 0xff, MEM_64K); prf->zx->vid->vga.cga = 1; if (!rset->vBiosFile.empty()) { - fpath = conf.path.romDir + SLASH + rset->vBiosFile; + fpath = conf.path.findRom(rset->vBiosFile); file = fopen(fpath.c_str(), "rb"); if (file) { fread(prf->zx->vid->bios, MEM_64K, 1, file); diff --git a/src/xcore/xcore.h b/src/xcore/xcore.h index 671c20bc..ff7a41ce 100644 --- a/src/xcore/xcore.h +++ b/src/xcore/xcore.h @@ -3,6 +3,7 @@ #include #include #include +#include #if defined(__linux) || defined(__BSD) #include @@ -10,6 +11,8 @@ #include +#include + #include #include #include @@ -44,6 +47,8 @@ #define X_KeepEmptyParts QString::KeepEmptyParts #endif +namespace fs = std::filesystem; + // common std::string getTimeString(int); @@ -450,7 +455,31 @@ struct xConfig { struct { std::string confDir; std::string confFile; - std::string romDir; +#if defined(__linux) || defined(__APPLE__) || defined(__BSD) + fs::path dataHomeDir; + fs::path romHomeDir; + std::vector romDataDirs; + + auto findRom(const fs::path &FileName) -> fs::path { + auto fileInHomeDir = romHomeDir / FileName; + if (fs::exists(fileInHomeDir)) { + return fileInHomeDir; + } + for (const auto &dir : romDataDirs) { + auto file = dir / FileName; + if (fs::exists(file)) { + return file; + } + } + return fileInHomeDir; + } +#elif defined(__WIN32) + std::string romHomeDir; + + auto findRom(const std::string &FileName) -> std::string { + return romDir + SLASH + FileName; + } +#endif std::string prfDir; std::string shdDir; std::string font; @@ -466,6 +495,7 @@ struct xConfig { int dwsize; int dmsize; } dbg; + }; extern xConfig conf; diff --git a/src/xgui/options/opt_romset.cpp b/src/xgui/options/opt_romset.cpp index ec5d56d2..755ac03f 100644 --- a/src/xgui/options/opt_romset.cpp +++ b/src/xgui/options/opt_romset.cpp @@ -10,7 +10,7 @@ std::string getRFText(QComboBox*); xRomsetEditor::xRomsetEditor(QWidget* par):QDialog(par) { ui.setupUi(this); - ui.cbFile->setDir(conf.path.romDir.c_str()); + ui.cbFile->setDir(conf.path.romHomeDir.c_str()); connect(ui.rse_apply, SIGNAL(clicked()), this, SLOT(store())); connect(ui.rse_cancel, SIGNAL(clicked()), this, SLOT(hide())); } @@ -124,7 +124,7 @@ QVariant xRomsetModel::data(const QModelIndex& idx, int role) const { if (rset->roms[row].fsize > 0) { res = rset->roms[row].fsize; } else { - buf = conf.path.romDir + SLASH + rset->roms[row].name; + buf = conf.path.findRom(rset->roms[row].name); inf.setFile(tr(buf.c_str())); res = QString("( %0 )").arg(inf.size() >> 10); }