From e7496a890ba5e7fb1a8e3a6f9f7373a1bd60cf53 Mon Sep 17 00:00:00 2001 From: Philip McGrath Date: Wed, 15 Feb 2023 02:20:23 -0500 Subject: [PATCH 1/2] Linux: Save Bookmarks under `XDG_STATE_HOME` An installation path like "/usr/share/tangerine/" might be read-only. Define the CPP macro `TANGERINE_SELF_CONTAINED` to use a path relative to the executable, instead. Currently, Windows always uses `TANGERINE_SELF_CONTAINED`, but support for both options could be useful there, too. --- CMakeLists.txt | 2 + tangerine/installation.cpp | 143 ++++++++++++++++++++++++++++++++++++- tangerine/installation.h | 10 ++- tangerine/tangerine.cpp | 17 +++-- 4 files changed, 159 insertions(+), 13 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6e9f1ec..f5f4bc2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,6 +15,7 @@ include(GNUInstallDirs) option(EMBED_LUA "Embed Lua support" ON) option(EMBED_RACKET "Embed Racket support" OFF) +option(SELF_CONTAINED "Use directory tree instead of XDG_STATE_HOME" OFF) set(INSTALL_PKG_SUBPATH "tangerine" CACHE PATH "Subdirectory to form PKGDATADIR from DATADIR") @@ -149,6 +150,7 @@ set_target_properties(tangerine RUNTIME_OUTPUT_DIRECTORY $) target_compile_definitions(tangerine PRIVATE + $<$:"TANGERINE_SELF_CONTAINED"> "TANGERINE_PKGDATADIR_FROM_BINDIR=${PKGDATADIR_FROM_BINDIR}") target_link_libraries(tangerine PRIVATE fmt diff --git a/tangerine/installation.cpp b/tangerine/installation.cpp index 27639ae..76be3a4 100644 --- a/tangerine/installation.cpp +++ b/tangerine/installation.cpp @@ -17,9 +17,24 @@ #include "whereami.h" #include +#if !_WIN64 +#include +#include +#include +#include +#include +const char *const TangerineAppID = + // TODO: consider a reverse-dns name with the escaping recommended in: + // https://docs.gtk.org/gio/type_func.Application.id_is_valid.html + // See rationale in: https://docs.gtk.org/gtk4/migrating-3to4.html#set-a-proper-application-id + "tangerine"; +std::optional GetXDGStateHome(); +#endif + StatusCode TangerinePaths::PopulateInstallationPaths() { + std::filesystem::path ExecutablePath; { int Length = wai_getExecutablePath(NULL, 0, NULL); if (Length > -1) @@ -44,20 +59,142 @@ StatusCode TangerinePaths::PopulateInstallationPaths() } } - ExecutableDir = ExecutablePath.parent_path(); + std::filesystem::path ExecutableDir = ExecutablePath.parent_path(); #ifdef TANGERINE_PKGDATADIR_FROM_BINDIR #define STRINGIFY(x) #x #define EXPAND_AS_STR(x) STRINGIFY(x) - PkgDataDir = ExecutableDir / std::filesystem::path(EXPAND_AS_STR(TANGERINE_PKGDATADIR_FROM_BINDIR)); + std::filesystem::path PkgDataDir = ExecutableDir / std::filesystem::path(EXPAND_AS_STR(TANGERINE_PKGDATADIR_FROM_BINDIR)); #undef EXPAND_AS_STR #undef STRINGIFY #else - PkgDataDir = ExecutableDir; + std::filesystem::path PkgDataDir = ExecutableDir; #endif ShadersDir = PkgDataDir / std::filesystem::path("shaders"); ModelsDir = PkgDataDir / std::filesystem::path("models"); +#if defined(TANGERINE_SELF_CONTAINED) + BookmarksPath = PkgDataDir / std::filesystem::path("bookmarks.txt"); +#elif !_WIN64 + if (std::optional HomeDir = GetXDGStateHome()) + { + BookmarksPath = HomeDir.value() / std::filesystem::path(TangerineAppID) / std::filesystem::path("bookmarks.txt"); + } + else + { + BookmarksPath = std::nullopt; + } +#else // Shouldn't get here: handled in "installation.h". Using %APPDATA% / CSIDL_APPDATA / FOLDERID_RoamingAppData might be useful, though. +# error "Windows currently requires TANGERINE_SELF_CONTAINED." +#endif + return StatusCode::PASS; } + + +#if !_WIN64 +std::optional GetHomeDir() { + // Based on `rktio_expand_user_tilde()`. + // License: (Apache-2.0 OR MIT) + + // $HOME overrides everything. + if (const char *Home = std::getenv("HOME")) + { + return std::filesystem::path(Home); + } + + // $USER and $LOGNAME (in that order) override `getuid()`. + const char *AltUserVar = "USER"; + const char *AltUser = std::getenv(AltUserVar); + if (!AltUser) + { + AltUserVar = "LOGNAME"; + AltUser = std::getenv(AltUserVar); + } + + /* getpwnam(3) man page says: "If one wants to check errno after the + call, it should be set to zero before the call." */ + errno = 0; + struct passwd *Passwd = AltUser ? getpwnam(AltUser) : getpwuid(getuid()); + int PasswdError = errno; + + // Did we find it? + if (Passwd && Passwd->pw_dir) + { + if (0 != PasswdError) + { + std::cout << "Warning: Found home directory, but " << (AltUser ? "getpwnam" : "getpwuid") << " reported an error.\n"; + } + else + { + // No warning + return std::filesystem::path(Passwd->pw_dir); + } + } + else if (Passwd) + { + std::cout << "Warning: User exists, but does not have a home directory.\n"; + } + else + { + std::cout << "Warning: Could not find home directory: user not found.\n"; + } + + // Add warning details: + // Was `getuid()` overridden? + if (AltUser) + { + std::cout << " user: " << AltUser << " (from $" << AltUserVar << ");\n"; + } + // Report system error. + if (0 != PasswdError) + { + std::cout << " error: " << std::strerror(PasswdError) << "\n"; + std::cout << " errno: " << PasswdError << "\n"; + } + else + { + std::cout << " errno: not set by " << (AltUser ? "getpwnam" : "getpwuid") << "\n"; + } + + if (Passwd && Passwd->pw_dir) + { + return std::filesystem::path(Passwd->pw_dir); + } + else + { + return {}; + } +} + +std::optional GetXDGStateHome() +{ + // Based on `rktio_system_path()`. + // License: (Apache-2.0 OR MIT) + + const char *EnvVar = "XDG_STATE_HOME"; + const char *DefaultSubpath = ".local/state"; + + // Check the environment variable. + if (const char *FromEnv = std::getenv(EnvVar)) + { + std::filesystem::path Candidate = std::filesystem::path(FromEnv); + // We must ignore the environment variable if it is not an absolute path. + if (Candidate.is_absolute()) { + return Candidate; + } + } + + + // Environment variable was unset or is invalid. + if (std::optional Home = GetHomeDir()) + { + return Home.value() / std::filesystem::path(DefaultSubpath); + } + else + { + return {}; + } +} +#endif /* !_WIN64 */ diff --git a/tangerine/installation.h b/tangerine/installation.h index b805a5d..23d8780 100644 --- a/tangerine/installation.h +++ b/tangerine/installation.h @@ -15,17 +15,21 @@ #pragma once #include +#include #include "embedding.h" #include "errors.h" +#ifndef TANGERINE_SELF_CONTAINED +# ifdef _WIN64 +# define TANGERINE_SELF_CONTAINED +# endif +#endif struct TangerinePaths { StatusCode PopulateInstallationPaths(); - std::filesystem::path ExecutablePath; - std::filesystem::path ExecutableDir; - std::filesystem::path PkgDataDir; std::filesystem::path ShadersDir; std::filesystem::path ModelsDir; + std::optional BookmarksPath; }; diff --git a/tangerine/tangerine.cpp b/tangerine/tangerine.cpp index c10b011..6cb29af 100644 --- a/tangerine/tangerine.cpp +++ b/tangerine/tangerine.cpp @@ -1747,11 +1747,10 @@ void RenderUI(SDL_Window* Window, bool& Live) void LoadBookmarks() { - std::filesystem::path BookmarksPath = - // FIXME might be read-only - Installed.ExecutableDir / "bookmarks.txt"; - if (std::filesystem::is_regular_file(BookmarksPath)) + std::optional MaybeBookmarksPath = Installed.BookmarksPath; + if (MaybeBookmarksPath && std::filesystem::is_regular_file(MaybeBookmarksPath.value())) { + std::filesystem::path BookmarksPath = MaybeBookmarksPath.value(); std::ifstream BookmarksFile; BookmarksFile.open(BookmarksPath); std::string Bookmark; @@ -1772,12 +1771,16 @@ void LoadBookmarks() void SaveBookmarks() { - std::filesystem::path BookmarksPath = - // FIXME might be read-only - Installed.ExecutableDir / "bookmarks.txt"; + std::optional MaybeBookmarksPath = Installed.BookmarksPath; + if (!MaybeBookmarksPath) + { + return; + } + std::filesystem::path BookmarksPath = MaybeBookmarksPath.value(); const std::vector& Bookmarks = ifd::FileDialog::Instance().GetFavorites(); if (Bookmarks.size() > 0) { + std::filesystem::create_directories(BookmarksPath.parent_path()); std::ofstream BookmarksFile; BookmarksFile.open(BookmarksPath); for (const std::string& Bookmark : Bookmarks) From 6528fc4431e5e649991b2e923f811026e06f7d28 Mon Sep 17 00:00:00 2001 From: Philip McGrath Date: Wed, 22 Feb 2023 14:57:30 -0500 Subject: [PATCH 2/2] Fix Removing All File Bookmarks Without this change, if you started Tangerine with some saved bookmarks and removed them all, the update would not be saved. --- tangerine/tangerine.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tangerine/tangerine.cpp b/tangerine/tangerine.cpp index 6cb29af..c666eb1 100644 --- a/tangerine/tangerine.cpp +++ b/tangerine/tangerine.cpp @@ -1789,6 +1789,10 @@ void SaveBookmarks() } BookmarksFile.close(); } + else if (std::filesystem::exists(BookmarksPath)) + { + std::filesystem::remove(BookmarksPath); + } }