Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Linux: Save Bookmarks under XDG_STATE_HOME #9

Open
wants to merge 2 commits into
base: excelsior
Choose a base branch
from
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
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -149,6 +150,7 @@ set_target_properties(tangerine
RUNTIME_OUTPUT_DIRECTORY
$<PATH:APPEND,${CMAKE_BINARY_DIR},${CMAKE_INSTALL_BINDIR}>)
target_compile_definitions(tangerine PRIVATE
$<$<BOOL:${SELF_CONTAINED}>:"TANGERINE_SELF_CONTAINED">
"TANGERINE_PKGDATADIR_FROM_BINDIR=${PKGDATADIR_FROM_BINDIR}")
target_link_libraries(tangerine PRIVATE
fmt
Expand Down
143 changes: 140 additions & 3 deletions tangerine/installation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,24 @@
#include "whereami.h"
#include <iostream>

#if !_WIN64
#include <unistd.h>
#include <sys/types.h>
#include <pwd.h>
#include <cerrno>
#include <cstring>
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<std::filesystem::path> GetXDGStateHome();
#endif


StatusCode TangerinePaths::PopulateInstallationPaths()
{
std::filesystem::path ExecutablePath;
{
int Length = wai_getExecutablePath(NULL, 0, NULL);
if (Length > -1)
Expand All @@ -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<std::filesystem::path> 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<std::filesystem::path> 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<std::filesystem::path> 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<std::filesystem::path> Home = GetHomeDir())
{
return Home.value() / std::filesystem::path(DefaultSubpath);
}
else
{
return {};
}
}
#endif /* !_WIN64 */
10 changes: 7 additions & 3 deletions tangerine/installation.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,21 @@

#pragma once
#include <filesystem>
#include <optional>
#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<std::filesystem::path> BookmarksPath;
};
21 changes: 14 additions & 7 deletions tangerine/tangerine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::filesystem::path> 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;
Expand All @@ -1772,12 +1771,16 @@ void LoadBookmarks()

void SaveBookmarks()
{
std::filesystem::path BookmarksPath =
// FIXME might be read-only
Installed.ExecutableDir / "bookmarks.txt";
std::optional<std::filesystem::path> MaybeBookmarksPath = Installed.BookmarksPath;
if (!MaybeBookmarksPath)
{
return;
}
std::filesystem::path BookmarksPath = MaybeBookmarksPath.value();
const std::vector<std::string>& 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)
Expand All @@ -1786,6 +1789,10 @@ void SaveBookmarks()
}
BookmarksFile.close();
}
else if (std::filesystem::exists(BookmarksPath))
{
std::filesystem::remove(BookmarksPath);
}
}


Expand Down