system: resolve user-facing paths against the app root so a macOS bundle keeps them beside the .app
This commit is contained in:
parent
705b78f53e
commit
3e842e62a6
5 changed files with 42 additions and 5 deletions
|
|
@ -36,6 +36,14 @@ std::filesystem::path GetExecutablePath();
|
||||||
// Get executable folder.
|
// Get executable folder.
|
||||||
std::filesystem::path GetExecutableFolder();
|
std::filesystem::path GetExecutableFolder();
|
||||||
|
|
||||||
|
// Get the application's root folder: the executable folder, or, when the
|
||||||
|
// executable lives inside a macOS application bundle
|
||||||
|
// ("<root>/<name>.app/Contents/MacOS/<exe>"), the folder containing the
|
||||||
|
// bundle. User-facing files (config, game data, saves, DLC, logs) resolve
|
||||||
|
// against this so a bundled app keeps them beside the bundle rather than
|
||||||
|
// inside it.
|
||||||
|
std::filesystem::path GetAppRootFolder();
|
||||||
|
|
||||||
// Get user folder.
|
// Get user folder.
|
||||||
std::filesystem::path GetUserFolder();
|
std::filesystem::path GetUserFolder();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -95,6 +95,21 @@ std::filesystem::path GetExecutableFolder() {
|
||||||
return GetExecutablePath().parent_path();
|
return GetExecutablePath().parent_path();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::filesystem::path GetAppRootFolder() {
|
||||||
|
auto folder = GetExecutableFolder();
|
||||||
|
#if REX_PLATFORM_MAC
|
||||||
|
// <root>/<name>.app/Contents/MacOS/<exe> -> <root>
|
||||||
|
if (folder.filename() == "MacOS") {
|
||||||
|
const auto contents = folder.parent_path();
|
||||||
|
const auto bundle = contents.parent_path();
|
||||||
|
if (contents.filename() == "Contents" && bundle.extension() == ".app") {
|
||||||
|
return bundle.parent_path();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return folder;
|
||||||
|
}
|
||||||
|
|
||||||
std::filesystem::path GetUserFolder() {
|
std::filesystem::path GetUserFolder() {
|
||||||
// get preferred data home
|
// get preferred data home
|
||||||
char* home = std::getenv("XDG_DATA_HOME");
|
char* home = std::getenv("XDG_DATA_HOME");
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,12 @@ std::filesystem::path GetExecutableFolder() {
|
||||||
return GetExecutablePath().parent_path();
|
return GetExecutablePath().parent_path();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::filesystem::path GetAppRootFolder() {
|
||||||
|
// Application bundles are a macOS concept; the executable folder is the
|
||||||
|
// application root everywhere else.
|
||||||
|
return GetExecutableFolder();
|
||||||
|
}
|
||||||
|
|
||||||
std::filesystem::path GetUserFolder() {
|
std::filesystem::path GetUserFolder() {
|
||||||
std::filesystem::path result;
|
std::filesystem::path result;
|
||||||
PWSTR path;
|
PWSTR path;
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
#include <rex/assert.h>
|
#include <rex/assert.h>
|
||||||
#include <rex/chrono/clock.h>
|
#include <rex/chrono/clock.h>
|
||||||
#include <rex/cvar.h>
|
#include <rex/cvar.h>
|
||||||
|
#include <rex/filesystem.h>
|
||||||
#include <rex/input/flags.h>
|
#include <rex/input/flags.h>
|
||||||
#include <rex/input/sdl/sdl_input_driver.h>
|
#include <rex/input/sdl/sdl_input_driver.h>
|
||||||
#include <rex/logging.h>
|
#include <rex/logging.h>
|
||||||
|
|
@ -96,18 +97,23 @@ void SDLInputDriver::OnWindowAvailable(rex::ui::Window* window) {
|
||||||
}
|
}
|
||||||
SDL_Gamepad_initialized_ = true;
|
SDL_Gamepad_initialized_ = true;
|
||||||
|
|
||||||
// Load custom controller mappings if available
|
// Load custom controller mappings if available. A relative path is
|
||||||
|
// resolved against the application root rather than the working
|
||||||
|
// directory, which is undefined when launched from a bundle/Finder.
|
||||||
if (!REXCVAR_GET(hid_mappings_file).empty()) {
|
if (!REXCVAR_GET(hid_mappings_file).empty()) {
|
||||||
std::filesystem::path mappings_path(REXCVAR_GET(hid_mappings_file));
|
std::filesystem::path mappings_path(REXCVAR_GET(hid_mappings_file));
|
||||||
|
if (mappings_path.is_relative()) {
|
||||||
|
mappings_path = rex::filesystem::GetAppRootFolder() / mappings_path;
|
||||||
|
}
|
||||||
if (!std::filesystem::exists(mappings_path)) {
|
if (!std::filesystem::exists(mappings_path)) {
|
||||||
REXLOG_WARN("SDL GameControllerDB: file '{}' does not exist.",
|
REXLOG_WARN("SDL GameControllerDB: file '{}' does not exist.",
|
||||||
REXCVAR_GET(hid_mappings_file));
|
mappings_path.string());
|
||||||
} else {
|
} else {
|
||||||
auto mappings_result =
|
auto mappings_result =
|
||||||
SDL_AddGamepadMappingsFromFile(REXCVAR_GET(hid_mappings_file).c_str());
|
SDL_AddGamepadMappingsFromFile(mappings_path.string().c_str());
|
||||||
if (mappings_result < 0) {
|
if (mappings_result < 0) {
|
||||||
REXLOG_ERROR("SDL GameControllerDB: error loading file '{}': {}.",
|
REXLOG_ERROR("SDL GameControllerDB: error loading file '{}': {}.",
|
||||||
REXCVAR_GET(hid_mappings_file), mappings_result);
|
mappings_path.string(), mappings_result);
|
||||||
} else {
|
} else {
|
||||||
REXLOG_INFO("SDL GameControllerDB: loaded {} mappings.", mappings_result);
|
REXLOG_INFO("SDL GameControllerDB: loaded {} mappings.", mappings_result);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -312,7 +312,9 @@ bool ReXApp::OnInitialize() {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ReXApp::SetupEnvironment() {
|
bool ReXApp::SetupEnvironment() {
|
||||||
auto exe_dir = rex::filesystem::GetExecutableFolder();
|
// App root, not executable folder: inside a macOS .app bundle the config
|
||||||
|
// and logs stay next to the bundle, where users can find them.
|
||||||
|
auto exe_dir = rex::filesystem::GetAppRootFolder();
|
||||||
auto config_path = exe_dir / (std::string(GetName()) + ".toml");
|
auto config_path = exe_dir / (std::string(GetName()) + ".toml");
|
||||||
|
|
||||||
// Load config before resolving cvar-backed paths such as game_data_root.
|
// Load config before resolving cvar-backed paths such as game_data_root.
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue