From 007cc4a0b8bfcb7d0048422e429217ab13f08f69 Mon Sep 17 00:00:00 2001 From: WhiredPlanck Date: Sat, 4 Jan 2025 11:07:37 +0800 Subject: [PATCH 1/2] Support XDG Base directory specification for Linux Ref: 1. https://specifications.freedesktop.org/basedir-spec/latest 2. https://github.com/aria2/aria2/commit/8bc1d37 --- libaegisub/common/path.cpp | 4 +++- libaegisub/unix/path.cpp | 33 +++++++++++++++++++++++++++++--- libaegisub/windows/path_win.cpp | 1 + src/dialog_autosave.cpp | 2 +- src/dialog_shift_times.cpp | 2 +- src/libresrc/default_config.json | 4 ++-- src/main.cpp | 5 +++-- 7 files changed, 41 insertions(+), 10 deletions(-) diff --git a/libaegisub/common/path.cpp b/libaegisub/common/path.cpp index 9b29e652c5..3f8092a7af 100644 --- a/libaegisub/common/path.cpp +++ b/libaegisub/common/path.cpp @@ -29,7 +29,8 @@ constexpr std::string_view tokens[] = { "?script", "?temp", "?user", - "?video" + "?video", + "?state" }; int find_token(std::string_view str) { @@ -44,6 +45,7 @@ int find_token(std::string_view str) { case 't' + 'p': idx = 5; break; case 'u' + 'r': idx = 6; break; case 'v' + 'e': idx = 7; break; + case 's' + 't': idx = 8; break; default: return -1; } return str.starts_with(tokens[idx]) ? idx : -1; diff --git a/libaegisub/unix/path.cpp b/libaegisub/unix/path.cpp index 5dada5b06b..670813bfb0 100644 --- a/libaegisub/unix/path.cpp +++ b/libaegisub/unix/path.cpp @@ -41,6 +41,12 @@ std::string home_dir() { throw agi::EnvironmentError("Could not get home directory. Make sure HOME is set."); } +std::string xdg_dir(std::string_view token, agi::fs::path const& fallback) { + const char *env = getenv(token.data()); + if (env) return env; + return fallback.string(); +} + #ifdef APPIMAGE_BUILD std::string exe_dir() { char *exe, *dir; @@ -70,12 +76,32 @@ namespace agi { void Path::FillPlatformSpecificPaths() { #ifndef __APPLE__ agi::fs::path home = home_dir(); - SetToken("?user", home/".aegisub"); - SetToken("?local", home/".aegisub"); + agi::fs::path old_root = home/".aegisub"; + + agi::fs::path xdg_config = xdg_dir("XDG_CONFIG_HOME", home/".config"); + agi::fs::path xdg_cache = xdg_dir("XDG_CACHE_HOME", home/".cache"); + agi::fs::path xdg_state = xdg_dir("XDG_STATE_HOME", home/".local/state"); + agi::fs::path xdg_data = xdg_dir("XDG_DATA_HOME", home/".local/share"); + + if (agi::fs::DirectoryExists(old_root)) { + SetToken("?user", old_root); + SetToken("?local", old_root); + SetToken("?state", old_root); + } else { + SetToken("?user", xdg_config/"aegisub"); + SetToken("?local", xdg_cache/"aegisub"); + SetToken("?state", xdg_state/"aegisub"); + } #ifdef APPIMAGE_BUILD agi::fs::path data = exe_dir(); - if (data == "") data = home/".aegisub"; + if (data == "") { + if (agi::fs::DirectoryExists(old_root)) { + data = old_root; + } else { + data = xdg_data/"aegisub" + } + } SetToken("?data", data); SetToken("?dictionary", Decode("?data/dictionaries")); #else @@ -87,6 +113,7 @@ void Path::FillPlatformSpecificPaths() { agi::fs::path app_support = agi::util::GetApplicationSupportDirectory(); SetToken("?user", app_support/"Aegisub"); SetToken("?local", app_support/"Aegisub"); + SetToken("?state", app_support/"Aegisub"); SetToken("?data", agi::util::GetBundleSharedSupportDirectory()); SetToken("?dictionary", Decode("?data/dictionaries")); #endif diff --git a/libaegisub/windows/path_win.cpp b/libaegisub/windows/path_win.cpp index c1d21761ef..eac28e51fa 100644 --- a/libaegisub/windows/path_win.cpp +++ b/libaegisub/windows/path_win.cpp @@ -45,6 +45,7 @@ void Path::FillPlatformSpecificPaths() { SetToken("?user", WinGetFolderPath(CSIDL_APPDATA)/"Aegisub"); SetToken("?local", WinGetFolderPath(CSIDL_LOCAL_APPDATA)/"Aegisub"); + SetToken("?state", WinGetFolderPath(CSIDL_APPDATA)/"Aegisub"); std::wstring filename(MAX_PATH + 1, L'\0'); while (static_cast(filename.size()) == GetModuleFileNameW(nullptr, &filename[0], filename.size())) diff --git a/src/dialog_autosave.cpp b/src/dialog_autosave.cpp index 2c78c5df51..f63858d1ec 100644 --- a/src/dialog_autosave.cpp +++ b/src/dialog_autosave.cpp @@ -92,7 +92,7 @@ DialogAutosave::DialogAutosave(wxWindow *parent) std::map files_map; Populate(files_map, OPT_GET("Path/Auto/Save")->GetString(), ".AUTOSAVE.ass", "%s"); Populate(files_map, OPT_GET("Path/Auto/Backup")->GetString(), ".ORIGINAL.ass", _("%s [ORIGINAL BACKUP]")); - Populate(files_map, "?user/recovered", ".ass", _("%s [RECOVERED]")); + Populate(files_map, "?state/recovered", ".ass", _("%s [RECOVERED]")); for (auto& file : files_map | boost::adaptors::map_values) files.emplace_back(std::move(file)); diff --git a/src/dialog_shift_times.cpp b/src/dialog_shift_times.cpp index b98e060d27..b9179e7030 100644 --- a/src/dialog_shift_times.cpp +++ b/src/dialog_shift_times.cpp @@ -133,7 +133,7 @@ static wxString get_history_string(json::Object &obj) { DialogShiftTimes::DialogShiftTimes(agi::Context *context) : wxDialog(context->parent, -1, _("Shift Times")) , context(context) -, history_filename(config::path->Decode("?user/shift_history.json")) +, history_filename(config::path->Decode("?state/shift_history.json")) , timecodes_loaded_slot(context->project->AddTimecodesListener(&DialogShiftTimes::OnTimecodesLoaded, this)) , selected_set_changed_slot(context->selectionController->AddSelectionListener(&DialogShiftTimes::OnSelectedSetChanged, this)) { diff --git a/src/libresrc/default_config.json b/src/libresrc/default_config.json index 8eb910fee4..9d770a83dd 100644 --- a/src/libresrc/default_config.json +++ b/src/libresrc/default_config.json @@ -285,8 +285,8 @@ "Path" : { "Auto" : { - "Backup" : "?user/autoback", - "Save" : "?user/autosave" + "Backup" : "?state/autoback", + "Save" : "?state/autosave" }, "Automation" : { "Autoload" : "?user/automation/autoload/|?data/automation/autoload/", diff --git a/src/main.cpp b/src/main.cpp index e63225df47..3481dce76c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -154,6 +154,7 @@ bool AegisubApp::OnInit() { // Local config, make ?user mean ?data so all user settings are placed in install dir config::path->SetToken("?user", config::path->Decode("?data")); config::path->SetToken("?local", config::path->Decode("?data")); + config::path->SetToken("?state", config::path->Decode("?data")); crash_writer::Initialize(config::path->Decode("?user")); } catch (agi::fs::FileSystemError const&) { // File doesn't exist or we can't read it @@ -162,7 +163,7 @@ bool AegisubApp::OnInit() { #endif StartupLog("Create log writer"); - auto path_log = config::path->Decode("?user/log/"); + auto path_log = config::path->Decode("?state/log/"); agi::fs::CreateDirectory(path_log); agi::log::log->Subscribe(std::make_unique(path_log)); CleanCache(path_log, "*.json", 10, 100); @@ -373,7 +374,7 @@ void AegisubApp::UnhandledException(bool stackWalk) { auto c = frame->context.get(); if (!c || !c->ass || !c->subsController) continue; - path = config::path->Decode("?user/recovered"); + path = config::path->Decode("?state/recovered"); agi::fs::CreateDirectory(path); auto filename = c->subsController->Filename().stem(); From fa912a08c81b3b44e1238a6bd79c3c0e915c8bc9 Mon Sep 17 00:00:00 2001 From: WhiredPlanck Date: Sat, 4 Jan 2025 11:16:51 +0800 Subject: [PATCH 2/2] Fix compile issue caused by token to path mapping Co-authored-by: witchymary <63314339+witchymary@users.noreply.github.com> --- libaegisub/common/path.cpp | 20 ++++++++++---------- libaegisub/include/libaegisub/path.h | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/libaegisub/common/path.cpp b/libaegisub/common/path.cpp index 3f8092a7af..8bc14f3829 100644 --- a/libaegisub/common/path.cpp +++ b/libaegisub/common/path.cpp @@ -36,16 +36,16 @@ constexpr std::string_view tokens[] = { int find_token(std::string_view str) { if (str.size() < 5 || str[0] != '?') return -1; int idx; - switch (str[1] + str[4]) { - case 'a' + 'i': idx = 0; break; - case 'd' + 'a': idx = 1; break; - case 'd' + 't': idx = 2; break; - case 'l' + 'a': idx = 3; break; - case 's' + 'i': idx = 4; break; - case 't' + 'p': idx = 5; break; - case 'u' + 'r': idx = 6; break; - case 'v' + 'e': idx = 7; break; - case 's' + 't': idx = 8; break; + switch (str[2] + str[4]) { + case 'u' + 'i': idx = 0; break; + case 'a' + 'a': idx = 1; break; + case 'i' + 't': idx = 2; break; + case 'o' + 'a': idx = 3; break; + case 'c' + 'i': idx = 4; break; + case 'e' + 'p': idx = 5; break; + case 's' + 'r': idx = 6; break; + case 'i' + 'e': idx = 7; break; + case 't' + 't': idx = 8; break; default: return -1; } return str.starts_with(tokens[idx]) ? idx : -1; diff --git a/libaegisub/include/libaegisub/path.h b/libaegisub/include/libaegisub/path.h index c980d9146a..4d32d0a2d5 100644 --- a/libaegisub/include/libaegisub/path.h +++ b/libaegisub/include/libaegisub/path.h @@ -22,7 +22,7 @@ namespace agi { /// Class for handling everything path-related in Aegisub class Path { /// Token -> Path map - std::array paths; + std::array paths; /// Platform-specific code to fill in the default paths, called in the constructor void FillPlatformSpecificPaths();