refactor: Integrate PlatformPaths for configuration directory management
- Replaced direct calls to GetConfigDirectory with PlatformPaths::GetConfigDirectory across multiple files to standardize configuration directory access. - Updated RecentFilesManager, EditorManager, and various agent components to handle potential errors when retrieving the configuration directory. - Enhanced file loading functions to utilize the new LoadFileFromConfigDir method for improved clarity and error handling. - Introduced new methods in file_util.h for better file management practices, leveraging std::filesystem for cross-platform consistency.
This commit is contained in:
@@ -10,6 +10,7 @@
|
||||
#include "absl/strings/str_join.h"
|
||||
#include "absl/strings/str_split.h"
|
||||
#include "util/file_util.h"
|
||||
#include "util/platform_paths.h"
|
||||
#include "app/gui/icons.h"
|
||||
#include "util/log.h"
|
||||
#include "app/zelda3/zelda3_labels.h"
|
||||
@@ -1059,13 +1060,19 @@ absl::Status YazeProject::SaveToJsonFormat() {
|
||||
|
||||
// RecentFilesManager implementation
|
||||
std::string RecentFilesManager::GetFilePath() const {
|
||||
return util::GetConfigDirectory() + "/" + kRecentFilesFilename;
|
||||
auto config_dir = util::PlatformPaths::GetConfigDirectory();
|
||||
if (!config_dir.ok()) {
|
||||
return ""; // Or handle error appropriately
|
||||
}
|
||||
return (*config_dir / kRecentFilesFilename).string();
|
||||
}
|
||||
|
||||
void RecentFilesManager::Save() {
|
||||
// Ensure config directory exists
|
||||
if (!util::EnsureConfigDirectoryExists()) {
|
||||
LOG_WARN("RecentFilesManager", "Could not create config directory for recent files");
|
||||
auto config_dir_status = util::PlatformPaths::GetConfigDirectory();
|
||||
if (!config_dir_status.ok()) {
|
||||
LOG_ERROR("Project", "Failed to get or create config directory: %s",
|
||||
config_dir_status.status().ToString().c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -28,8 +28,10 @@
|
||||
#include "app/rom.h"
|
||||
#include "imgui/imgui.h"
|
||||
#include "util/file_util.h"
|
||||
#include "util/platform_paths.h"
|
||||
|
||||
#include <SDL.h>
|
||||
#include <filesystem>
|
||||
|
||||
#if defined(YAZE_WITH_GRPC)
|
||||
#include "app/test/test_manager.h"
|
||||
@@ -37,6 +39,7 @@
|
||||
|
||||
namespace {
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
using yaze::cli::agent::ChatMessage;
|
||||
|
||||
std::filesystem::path ExpandUserPath(std::string path) {
|
||||
@@ -55,7 +58,13 @@ std::filesystem::path ExpandUserPath(std::string path) {
|
||||
}
|
||||
|
||||
std::filesystem::path ResolveHistoryPath(const std::string& session_id = "") {
|
||||
std::filesystem::path base = ExpandUserPath(yaze::util::GetConfigDirectory());
|
||||
auto config_dir = yaze::util::PlatformPaths::GetConfigDirectory();
|
||||
if (!config_dir.ok()) {
|
||||
// Fallback to a local directory if config can't be determined.
|
||||
return fs::current_path() / ".yaze" / "agent" / "history" / (session_id.empty() ? "default.json" : session_id + ".json");
|
||||
}
|
||||
|
||||
fs::path base = *config_dir;
|
||||
if (base.empty()) {
|
||||
base = ExpandUserPath(".yaze");
|
||||
}
|
||||
|
||||
@@ -18,8 +18,12 @@
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "absl/strings/strip.h"
|
||||
#include "util/file_util.h"
|
||||
#include "util/platform_paths.h"
|
||||
#include "util/macro.h"
|
||||
|
||||
#include <filesystem>
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace yaze {
|
||||
namespace editor {
|
||||
|
||||
@@ -259,11 +263,12 @@ std::string AgentCollaborationCoordinator::GenerateSessionCode() const {
|
||||
}
|
||||
|
||||
std::filesystem::path AgentCollaborationCoordinator::SessionsDirectory() const {
|
||||
std::filesystem::path base = ExpandUserPath(util::GetConfigDirectory());
|
||||
if (base.empty()) {
|
||||
base = ExpandUserPath(".yaze");
|
||||
auto config_dir = util::PlatformPaths::GetConfigDirectory();
|
||||
if (!config_dir.ok()) {
|
||||
// Fallback to a local directory if config can't be determined.
|
||||
return fs::current_path() / ".yaze" / "agent" / "sessions";
|
||||
}
|
||||
return base / "agent" / "sessions";
|
||||
return *config_dir / "agent" / "sessions";
|
||||
}
|
||||
|
||||
std::filesystem::path AgentCollaborationCoordinator::SessionFilePath(
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "app/gui/icons.h"
|
||||
#include "app/rom.h"
|
||||
#include "util/file_util.h"
|
||||
#include "util/platform_paths.h"
|
||||
|
||||
#ifdef YAZE_WITH_GRPC
|
||||
#include "app/editor/agent/network_collaboration_coordinator.h"
|
||||
@@ -1238,23 +1239,12 @@ absl::Status AgentEditor::ImportProfile(const std::filesystem::path& path) {
|
||||
}
|
||||
|
||||
std::filesystem::path AgentEditor::GetProfilesDirectory() const {
|
||||
std::filesystem::path config_dir(yaze::util::GetConfigDirectory());
|
||||
if (config_dir.empty()) {
|
||||
#ifdef _WIN32
|
||||
const char* appdata = std::getenv("APPDATA");
|
||||
if (appdata) {
|
||||
config_dir = std::filesystem::path(appdata) / "yaze";
|
||||
}
|
||||
#else
|
||||
const char* home_env = std::getenv("HOME");
|
||||
if (home_env) {
|
||||
config_dir = std::filesystem::path(home_env) / ".yaze";
|
||||
}
|
||||
#endif
|
||||
auto config_dir = yaze::util::PlatformPaths::GetConfigDirectory();
|
||||
if (!config_dir.ok()) {
|
||||
// Fallback to a local directory if config can't be determined.
|
||||
return std::filesystem::current_path() / ".yaze" / "agent" / "profiles";
|
||||
}
|
||||
|
||||
return config_dir / std::filesystem::path("agent") /
|
||||
std::filesystem::path("profiles");
|
||||
return *config_dir / "agent" / "profiles";
|
||||
}
|
||||
|
||||
absl::Status AgentEditor::EnsureProfilesDirectory() {
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "app/core/features.h"
|
||||
#include "app/core/timing.h"
|
||||
#include "util/file_util.h"
|
||||
#include "util/platform_paths.h"
|
||||
#include "app/core/project.h"
|
||||
#include "app/editor/code/assembly_editor.h"
|
||||
#include "app/editor/dungeon/dungeon_editor.h"
|
||||
@@ -82,8 +83,16 @@ std::string GetEditorName(EditorType type) {
|
||||
|
||||
// Settings + preset helpers
|
||||
void EditorManager::LoadUserSettings() {
|
||||
auto config_dir = util::PlatformPaths::GetConfigDirectory();
|
||||
if (!config_dir.ok()) {
|
||||
LOG_WARN("EditorManager", "Could not determine config directory for settings.");
|
||||
return;
|
||||
}
|
||||
|
||||
std::string settings_path = (*config_dir / settings_filename_).string();
|
||||
|
||||
try {
|
||||
auto data = util::LoadConfigFile(settings_filename_);
|
||||
auto data = util::LoadFile(settings_path);
|
||||
if (!data.empty()) {
|
||||
std::istringstream ss(data);
|
||||
std::string line;
|
||||
@@ -102,7 +111,9 @@ void EditorManager::LoadUserSettings() {
|
||||
}
|
||||
ImGui::GetIO().FontGlobalScale = font_global_scale_;
|
||||
}
|
||||
} catch (...) {}
|
||||
} catch (...) {
|
||||
// Could not load file, just use defaults.
|
||||
}
|
||||
}
|
||||
|
||||
void EditorManager::SaveUserSettings() {
|
||||
@@ -121,17 +132,21 @@ void EditorManager::RefreshWorkspacePresets() {
|
||||
|
||||
// Try to read a simple index file of presets
|
||||
try {
|
||||
auto data = util::LoadConfigFile("workspace_presets.txt");
|
||||
if (!data.empty()) {
|
||||
std::istringstream ss(data);
|
||||
std::string name;
|
||||
while (std::getline(ss, name)) {
|
||||
// Trim whitespace and validate
|
||||
name.erase(0, name.find_first_not_of(" \t\r\n"));
|
||||
name.erase(name.find_last_not_of(" \t\r\n") + 1);
|
||||
if (!name.empty() &&
|
||||
name.length() < 256) { // Reasonable length limit
|
||||
new_presets.emplace_back(std::move(name));
|
||||
auto config_dir = util::PlatformPaths::GetConfigDirectory();
|
||||
if (config_dir.ok()) {
|
||||
std::string presets_path = (*config_dir / "workspace_presets.txt").string();
|
||||
auto data = util::LoadFile(presets_path);
|
||||
if (!data.empty()) {
|
||||
std::istringstream ss(data);
|
||||
std::string name;
|
||||
while (std::getline(ss, name)) {
|
||||
// Trim whitespace and validate
|
||||
name.erase(0, name.find_first_not_of(" \t\r\n"));
|
||||
name.erase(name.find_last_not_of(" \t\r\n") + 1);
|
||||
if (!name.empty() &&
|
||||
name.length() < 256) { // Reasonable length limit
|
||||
new_presets.emplace_back(std::move(name));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,11 @@
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "app/gfx/performance_profiler.h"
|
||||
#include "app/editor/code/assembly_editor.h"
|
||||
#include "app/emu/emulator.h"
|
||||
#include "app/gui/icons.h"
|
||||
#include "app/gui/input.h"
|
||||
#include "imgui/imgui.h"
|
||||
#include "util/log.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace editor {
|
||||
@@ -210,5 +212,75 @@ void MusicEditor::DrawToolset() {
|
||||
ImGui::ProgressBar((float)current_time / SONG_DURATION);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Audio Control Methods (Emulator Integration)
|
||||
// ============================================================================
|
||||
|
||||
void MusicEditor::PlaySong(int song_id) {
|
||||
if (!emulator_) {
|
||||
LOG_WARN("MusicEditor", "No emulator instance - cannot play song");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!emulator_->snes().running()) {
|
||||
LOG_WARN("MusicEditor", "Emulator not running - cannot play song");
|
||||
return;
|
||||
}
|
||||
|
||||
// Write song request to game memory ($7E012C)
|
||||
// This triggers the NMI handler to send the song to APU
|
||||
try {
|
||||
emulator_->snes().Write(0x7E012C, static_cast<uint8_t>(song_id));
|
||||
LOG_INFO("MusicEditor", "Requested song %d (%s)", song_id,
|
||||
song_id < 30 ? kGameSongs[song_id] : "Unknown");
|
||||
|
||||
// Ensure audio backend is playing
|
||||
if (auto* audio = emulator_->audio_backend()) {
|
||||
auto status = audio->GetStatus();
|
||||
if (!status.is_playing) {
|
||||
audio->Play();
|
||||
LOG_INFO("MusicEditor", "Started audio backend playback");
|
||||
}
|
||||
}
|
||||
|
||||
is_playing_ = true;
|
||||
} catch (const std::exception& e) {
|
||||
LOG_ERROR("MusicEditor", "Failed to play song: %s", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void MusicEditor::StopSong() {
|
||||
if (!emulator_) return;
|
||||
|
||||
// Write stop command to game memory
|
||||
try {
|
||||
emulator_->snes().Write(0x7E012C, 0xFF); // 0xFF = stop music
|
||||
LOG_INFO("MusicEditor", "Stopped music playback");
|
||||
|
||||
// Optional: pause audio backend to save CPU
|
||||
if (auto* audio = emulator_->audio_backend()) {
|
||||
audio->Pause();
|
||||
}
|
||||
|
||||
is_playing_ = false;
|
||||
} catch (const std::exception& e) {
|
||||
LOG_ERROR("MusicEditor", "Failed to stop song: %s", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void MusicEditor::SetVolume(float volume) {
|
||||
if (!emulator_) return;
|
||||
|
||||
// Clamp volume to valid range
|
||||
volume = std::clamp(volume, 0.0f, 1.0f);
|
||||
|
||||
if (auto* audio = emulator_->audio_backend()) {
|
||||
audio->SetVolume(volume);
|
||||
LOG_DEBUG("MusicEditor", "Set volume to %.2f", volume);
|
||||
} else {
|
||||
LOG_WARN("MusicEditor", "No audio backend available");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace editor
|
||||
} // namespace yaze
|
||||
|
||||
@@ -9,6 +9,12 @@
|
||||
#include "imgui/imgui.h"
|
||||
|
||||
namespace yaze {
|
||||
|
||||
// Forward declaration
|
||||
namespace emu {
|
||||
class Emulator;
|
||||
}
|
||||
|
||||
namespace editor {
|
||||
|
||||
static const char* kGameSongs[] = {"Title",
|
||||
@@ -77,6 +83,15 @@ class MusicEditor : public Editor {
|
||||
|
||||
// Get the ROM pointer
|
||||
Rom* rom() const { return rom_; }
|
||||
|
||||
// Emulator integration for live audio playback
|
||||
void set_emulator(emu::Emulator* emulator) { emulator_ = emulator; }
|
||||
emu::Emulator* emulator() const { return emulator_; }
|
||||
|
||||
// Audio control methods
|
||||
void PlaySong(int song_id);
|
||||
void StopSong();
|
||||
void SetVolume(float volume); // 0.0 to 1.0
|
||||
|
||||
private:
|
||||
// UI Drawing Methods
|
||||
@@ -112,6 +127,7 @@ class MusicEditor : public Editor {
|
||||
ImGuiTableFlags_BordersV | ImGuiTableFlags_PadOuterX;
|
||||
|
||||
Rom* rom_;
|
||||
emu::Emulator* emulator_ = nullptr; // For live audio playback
|
||||
};
|
||||
|
||||
} // namespace editor
|
||||
|
||||
@@ -344,7 +344,7 @@ void EditorSelectionDialog::MarkRecentlyUsed(EditorType type) {
|
||||
|
||||
void EditorSelectionDialog::LoadRecentEditors() {
|
||||
try {
|
||||
auto data = util::LoadConfigFile("recent_editors.txt");
|
||||
auto data = util::LoadFileFromConfigDir("recent_editors.txt");
|
||||
if (!data.empty()) {
|
||||
std::istringstream ss(data);
|
||||
std::string line;
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "absl/strings/str_split.h"
|
||||
#include "util/file_util.h"
|
||||
#include "util/platform_paths.h"
|
||||
#include "app/gui/icons.h"
|
||||
#include "app/gui/style.h" // For ColorsYaze function
|
||||
#include "imgui/imgui.h"
|
||||
@@ -1917,8 +1918,10 @@ std::vector<std::string> ThemeManager::GetThemeSearchPaths() const {
|
||||
#endif
|
||||
|
||||
// User config directory
|
||||
std::string config_themes = util::GetConfigDirectory() + "/themes/";
|
||||
search_paths.push_back(config_themes);
|
||||
auto config_dir = util::PlatformPaths::GetConfigDirectory();
|
||||
if (config_dir.ok()) {
|
||||
search_paths.push_back((*config_dir / "themes/").string());
|
||||
}
|
||||
|
||||
return search_paths;
|
||||
}
|
||||
|
||||
@@ -1,39 +1,23 @@
|
||||
#include "util/file_util.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
// Include Windows-specific headers
|
||||
#include <shobjidl.h>
|
||||
#include <windows.h>
|
||||
#else // Linux and MacOS
|
||||
#include <dirent.h>
|
||||
#include <sys/stat.h>
|
||||
#include <cerrno>
|
||||
#endif
|
||||
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <cstring>
|
||||
|
||||
#include "app/core/features.h"
|
||||
#include "util/platform_paths.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace util {
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
std::string GetFileExtension(const std::string &filename) {
|
||||
size_t dot = filename.find_last_of('.');
|
||||
if (dot == std::string::npos) {
|
||||
return "";
|
||||
}
|
||||
return filename.substr(dot + 1);
|
||||
return fs::path(filename).extension().string();
|
||||
}
|
||||
|
||||
std::string GetFileName(const std::string &filename) {
|
||||
size_t slash = filename.find_last_of('/');
|
||||
if (slash == std::string::npos) {
|
||||
return filename;
|
||||
}
|
||||
return filename.substr(slash + 1);
|
||||
return fs::path(filename).filename().string();
|
||||
}
|
||||
|
||||
std::string LoadFile(const std::string &filename) {
|
||||
@@ -51,9 +35,14 @@ std::string LoadFile(const std::string &filename) {
|
||||
return contents;
|
||||
}
|
||||
|
||||
std::string LoadConfigFile(const std::string &filename) {
|
||||
std::string LoadFileFromConfigDir(const std::string &filename) {
|
||||
auto config_dir = PlatformPaths::GetConfigDirectory();
|
||||
if (!config_dir.ok()) {
|
||||
return ""; // Or handle error appropriately
|
||||
}
|
||||
|
||||
fs::path filepath = *config_dir / filename;
|
||||
std::string contents;
|
||||
std::string filepath = GetConfigDirectory() + "/" + filename;
|
||||
std::ifstream file(filepath);
|
||||
if (file.is_open()) {
|
||||
std::stringstream buffer;
|
||||
@@ -65,7 +54,12 @@ std::string LoadConfigFile(const std::string &filename) {
|
||||
}
|
||||
|
||||
void SaveFile(const std::string &filename, const std::string &contents) {
|
||||
std::string filepath = GetConfigDirectory() + "/" + filename;
|
||||
auto config_dir = PlatformPaths::GetConfigDirectory();
|
||||
if (!config_dir.ok()) {
|
||||
// Or handle error appropriately
|
||||
return;
|
||||
}
|
||||
fs::path filepath = *config_dir / filename;
|
||||
std::ofstream file(filepath);
|
||||
if (file.is_open()) {
|
||||
file << contents;
|
||||
@@ -86,642 +80,8 @@ std::string GetResourcePath(const std::string &resource_path) {
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string ExpandHomePath(const std::string& path) {
|
||||
if (path.empty() || path[0] != '~') {
|
||||
return path;
|
||||
}
|
||||
|
||||
const char* home = nullptr;
|
||||
#ifdef _WIN32
|
||||
home = std::getenv("USERPROFILE");
|
||||
if (!home) {
|
||||
home = std::getenv("HOMEDRIVE");
|
||||
const char* homePath = std::getenv("HOMEPATH");
|
||||
if (home && homePath) {
|
||||
static std::string full_path;
|
||||
full_path = std::string(home) + std::string(homePath);
|
||||
home = full_path.c_str();
|
||||
}
|
||||
}
|
||||
#else
|
||||
home = std::getenv("HOME");
|
||||
#endif
|
||||
|
||||
if (!home) {
|
||||
return path; // Fallback to original path if HOME not found
|
||||
}
|
||||
|
||||
// Replace ~ with home directory
|
||||
if (path.size() == 1 || path[1] == '/') {
|
||||
return std::string(home) + path.substr(1);
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
bool EnsureConfigDirectoryExists() {
|
||||
std::string config_dir = GetConfigDirectory();
|
||||
|
||||
#ifdef _WIN32
|
||||
// Windows directory creation
|
||||
DWORD attr = GetFileAttributesA(config_dir.c_str());
|
||||
if (attr == INVALID_FILE_ATTRIBUTES) {
|
||||
// Directory doesn't exist, create it
|
||||
if (!CreateDirectoryA(config_dir.c_str(), NULL)) {
|
||||
DWORD error = GetLastError();
|
||||
if (error != ERROR_ALREADY_EXISTS) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
// Unix-like directory creation
|
||||
struct stat st;
|
||||
if (stat(config_dir.c_str(), &st) != 0) {
|
||||
// Directory doesn't exist, create it with 0755 permissions
|
||||
if (mkdir(config_dir.c_str(), 0755) != 0) {
|
||||
if (errno != EEXIST) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string GetConfigDirectory() {
|
||||
std::string config_directory = ".yaze";
|
||||
Platform platform;
|
||||
#if defined(__APPLE__) && defined(__MACH__)
|
||||
#if TARGET_IPHONE_SIMULATOR == 1 || TARGET_OS_IPHONE == 1
|
||||
platform = Platform::kiOS;
|
||||
#elif TARGET_OS_MAC == 1
|
||||
platform = Platform::kMacOS;
|
||||
#else
|
||||
platform = Platform::kMacOS; // Default for macOS
|
||||
#endif
|
||||
#elif defined(_WIN32)
|
||||
platform = Platform::kWindows;
|
||||
#elif defined(__linux__)
|
||||
platform = Platform::kLinux;
|
||||
#else
|
||||
platform = Platform::kUnknown;
|
||||
#endif
|
||||
switch (platform) {
|
||||
case Platform::kWindows:
|
||||
config_directory = "~/AppData/Roaming/yaze";
|
||||
break;
|
||||
case Platform::kMacOS:
|
||||
case Platform::kLinux:
|
||||
config_directory = "~/.config/yaze";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
// Expand the home directory path
|
||||
return ExpandHomePath(config_directory);
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
#if defined(YAZE_ENABLE_NFD) && YAZE_ENABLE_NFD
|
||||
#include <nfd.h>
|
||||
#endif
|
||||
|
||||
std::string FileDialogWrapper::ShowOpenFileDialog() {
|
||||
// Use global feature flag to choose implementation
|
||||
if (core::FeatureFlags::get().kUseNativeFileDialog) {
|
||||
return ShowOpenFileDialogNFD();
|
||||
} else {
|
||||
return ShowOpenFileDialogBespoke();
|
||||
}
|
||||
}
|
||||
|
||||
std::string FileDialogWrapper::ShowOpenFileDialogNFD() {
|
||||
#if defined(YAZE_ENABLE_NFD) && YAZE_ENABLE_NFD
|
||||
NFD_Init();
|
||||
nfdu8char_t *out_path = NULL;
|
||||
nfdu8filteritem_t filters[1] = {{"ROM Files", "sfc,smc"}};
|
||||
nfdopendialogu8args_t args = {0};
|
||||
args.filterList = filters;
|
||||
args.filterCount = 1;
|
||||
nfdresult_t result = NFD_OpenDialogU8_With(&out_path, &args);
|
||||
if (result == NFD_OKAY) {
|
||||
std::string file_path(out_path);
|
||||
NFD_FreePath(out_path);
|
||||
NFD_Quit();
|
||||
return file_path;
|
||||
} else if (result == NFD_CANCEL) {
|
||||
NFD_Quit();
|
||||
return "";
|
||||
}
|
||||
NFD_Quit();
|
||||
return "";
|
||||
#else
|
||||
// NFD not available - fallback to bespoke
|
||||
return ShowOpenFileDialogBespoke();
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string FileDialogWrapper::ShowOpenFileDialogBespoke() {
|
||||
// Use Windows COM-based IFileOpenDialog as fallback when NFD not available
|
||||
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
|
||||
if (FAILED(hr)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string result;
|
||||
IFileOpenDialog *pFileOpen = NULL;
|
||||
|
||||
// Create the FileOpenDialog object
|
||||
hr = CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_ALL,
|
||||
IID_IFileOpenDialog, reinterpret_cast<void**>(&pFileOpen));
|
||||
|
||||
if (SUCCEEDED(hr)) {
|
||||
// Set file type filters
|
||||
COMDLG_FILTERSPEC rgSpec[] = {
|
||||
{L"ROM Files", L"*.sfc;*.smc"},
|
||||
{L"All Files", L"*.*"}
|
||||
};
|
||||
pFileOpen->SetFileTypes(ARRAYSIZE(rgSpec), rgSpec);
|
||||
pFileOpen->SetFileTypeIndex(1);
|
||||
pFileOpen->SetDefaultExtension(L"sfc");
|
||||
|
||||
// Show the Open dialog
|
||||
hr = pFileOpen->Show(NULL);
|
||||
|
||||
if (SUCCEEDED(hr)) {
|
||||
// Get the file name from the dialog
|
||||
IShellItem *pItem;
|
||||
hr = pFileOpen->GetResult(&pItem);
|
||||
if (SUCCEEDED(hr)) {
|
||||
PWSTR pszFilePath;
|
||||
hr = pItem->GetDisplayName(SIGDN_FILESYSPATH, &pszFilePath);
|
||||
|
||||
if (SUCCEEDED(hr)) {
|
||||
// Convert wide string to narrow string
|
||||
int size_needed = WideCharToMultiByte(CP_UTF8, 0, pszFilePath, -1, NULL, 0, NULL, NULL);
|
||||
if (size_needed > 0) {
|
||||
std::vector<char> buffer(size_needed);
|
||||
WideCharToMultiByte(CP_UTF8, 0, pszFilePath, -1, buffer.data(), size_needed, NULL, NULL);
|
||||
result = std::string(buffer.data());
|
||||
}
|
||||
CoTaskMemFree(pszFilePath);
|
||||
}
|
||||
pItem->Release();
|
||||
}
|
||||
}
|
||||
pFileOpen->Release();
|
||||
}
|
||||
|
||||
CoUninitialize();
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string FileDialogWrapper::ShowSaveFileDialog(const std::string& default_name,
|
||||
const std::string& default_extension) {
|
||||
// Use global feature flag to choose implementation
|
||||
if (core::FeatureFlags::get().kUseNativeFileDialog) {
|
||||
return ShowSaveFileDialogNFD(default_name, default_extension);
|
||||
} else {
|
||||
return ShowSaveFileDialogBespoke(default_name, default_extension);
|
||||
}
|
||||
}
|
||||
|
||||
std::string FileDialogWrapper::ShowSaveFileDialogNFD(const std::string& default_name,
|
||||
const std::string& default_extension) {
|
||||
#if defined(YAZE_ENABLE_NFD) && YAZE_ENABLE_NFD
|
||||
NFD_Init();
|
||||
nfdu8char_t *out_path = NULL;
|
||||
|
||||
nfdsavedialogu8args_t args = {0};
|
||||
if (!default_extension.empty()) {
|
||||
// Create filter for the save dialog
|
||||
static nfdu8filteritem_t filters[3] = {
|
||||
{"Theme Files", "theme"},
|
||||
{"YAZE Project Files", "yaze"},
|
||||
{"ROM Files", "sfc,smc"}
|
||||
};
|
||||
|
||||
if (default_extension == "theme") {
|
||||
args.filterList = &filters[0];
|
||||
args.filterCount = 1;
|
||||
} else if (default_extension == "yaze") {
|
||||
args.filterList = &filters[1];
|
||||
args.filterCount = 1;
|
||||
} else if (default_extension == "sfc" || default_extension == "smc") {
|
||||
args.filterList = &filters[2];
|
||||
args.filterCount = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!default_name.empty()) {
|
||||
args.defaultName = default_name.c_str();
|
||||
}
|
||||
|
||||
nfdresult_t result = NFD_SaveDialogU8_With(&out_path, &args);
|
||||
if (result == NFD_OKAY) {
|
||||
std::string file_path(out_path);
|
||||
NFD_FreePath(out_path);
|
||||
NFD_Quit();
|
||||
return file_path;
|
||||
} else if (result == NFD_CANCEL) {
|
||||
NFD_Quit();
|
||||
return "";
|
||||
}
|
||||
NFD_Quit();
|
||||
return "";
|
||||
#else
|
||||
// NFD not available - fallback to bespoke
|
||||
return ShowSaveFileDialogBespoke(default_name, default_extension);
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string FileDialogWrapper::ShowSaveFileDialogBespoke(const std::string& default_name,
|
||||
const std::string& default_extension) {
|
||||
// Use Windows COM-based IFileSaveDialog as fallback when NFD not available
|
||||
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
|
||||
if (FAILED(hr)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string result;
|
||||
IFileSaveDialog *pFileSave = NULL;
|
||||
|
||||
// Create the FileSaveDialog object
|
||||
hr = CoCreateInstance(CLSID_FileSaveDialog, NULL, CLSCTX_ALL,
|
||||
IID_IFileSaveDialog, reinterpret_cast<void**>(&pFileSave));
|
||||
|
||||
if (SUCCEEDED(hr)) {
|
||||
// Set file type filters based on extension
|
||||
if (default_extension == "theme") {
|
||||
COMDLG_FILTERSPEC rgSpec[] = {{L"Theme Files", L"*.theme"}};
|
||||
pFileSave->SetFileTypes(ARRAYSIZE(rgSpec), rgSpec);
|
||||
pFileSave->SetDefaultExtension(L"theme");
|
||||
} else if (default_extension == "yaze") {
|
||||
COMDLG_FILTERSPEC rgSpec[] = {{L"YAZE Project Files", L"*.yaze"}};
|
||||
pFileSave->SetFileTypes(ARRAYSIZE(rgSpec), rgSpec);
|
||||
pFileSave->SetDefaultExtension(L"yaze");
|
||||
} else if (default_extension == "sfc" || default_extension == "smc") {
|
||||
COMDLG_FILTERSPEC rgSpec[] = {
|
||||
{L"SFC ROM Files", L"*.sfc"},
|
||||
{L"SMC ROM Files", L"*.smc"}
|
||||
};
|
||||
pFileSave->SetFileTypes(ARRAYSIZE(rgSpec), rgSpec);
|
||||
pFileSave->SetDefaultExtension(default_extension == "sfc" ? L"sfc" : L"smc");
|
||||
}
|
||||
|
||||
// Set default filename if provided
|
||||
if (!default_name.empty()) {
|
||||
int size_needed = MultiByteToWideChar(CP_UTF8, 0, default_name.c_str(), -1, NULL, 0);
|
||||
if (size_needed > 0) {
|
||||
std::vector<wchar_t> wname(size_needed);
|
||||
MultiByteToWideChar(CP_UTF8, 0, default_name.c_str(), -1, wname.data(), size_needed);
|
||||
pFileSave->SetFileName(wname.data());
|
||||
}
|
||||
}
|
||||
|
||||
// Show the Save dialog
|
||||
hr = pFileSave->Show(NULL);
|
||||
|
||||
if (SUCCEEDED(hr)) {
|
||||
// Get the file name from the dialog
|
||||
IShellItem *pItem;
|
||||
hr = pFileSave->GetResult(&pItem);
|
||||
if (SUCCEEDED(hr)) {
|
||||
PWSTR pszFilePath;
|
||||
hr = pItem->GetDisplayName(SIGDN_FILESYSPATH, &pszFilePath);
|
||||
|
||||
if (SUCCEEDED(hr)) {
|
||||
// Convert wide string to narrow string
|
||||
int size_needed = WideCharToMultiByte(CP_UTF8, 0, pszFilePath, -1, NULL, 0, NULL, NULL);
|
||||
if (size_needed > 0) {
|
||||
std::vector<char> buffer(size_needed);
|
||||
WideCharToMultiByte(CP_UTF8, 0, pszFilePath, -1, buffer.data(), size_needed, NULL, NULL);
|
||||
result = std::string(buffer.data());
|
||||
}
|
||||
CoTaskMemFree(pszFilePath);
|
||||
}
|
||||
pItem->Release();
|
||||
}
|
||||
}
|
||||
pFileSave->Release();
|
||||
}
|
||||
|
||||
CoUninitialize();
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string FileDialogWrapper::ShowOpenFolderDialog() {
|
||||
// Use global feature flag to choose implementation
|
||||
if (core::FeatureFlags::get().kUseNativeFileDialog) {
|
||||
return ShowOpenFolderDialogNFD();
|
||||
} else {
|
||||
return ShowOpenFolderDialogBespoke();
|
||||
}
|
||||
}
|
||||
|
||||
std::string FileDialogWrapper::ShowOpenFolderDialogNFD() {
|
||||
#if defined(YAZE_ENABLE_NFD) && YAZE_ENABLE_NFD
|
||||
NFD_Init();
|
||||
nfdu8char_t *out_path = NULL;
|
||||
nfdresult_t result = NFD_PickFolderU8(&out_path, NULL);
|
||||
if (result == NFD_OKAY) {
|
||||
std::string folder_path(out_path);
|
||||
NFD_FreePath(out_path);
|
||||
NFD_Quit();
|
||||
return folder_path;
|
||||
} else if (result == NFD_CANCEL) {
|
||||
NFD_Quit();
|
||||
return "";
|
||||
}
|
||||
NFD_Quit();
|
||||
return "";
|
||||
#else
|
||||
// NFD not available - fallback to bespoke
|
||||
return ShowOpenFolderDialogBespoke();
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string FileDialogWrapper::ShowOpenFolderDialogBespoke() {
|
||||
// Use Windows COM-based IFileOpenDialog in folder-picking mode as fallback
|
||||
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
|
||||
if (FAILED(hr)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string result;
|
||||
IFileOpenDialog *pFileOpen = NULL;
|
||||
|
||||
// Create the FileOpenDialog object
|
||||
hr = CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_ALL,
|
||||
IID_IFileOpenDialog, reinterpret_cast<void**>(&pFileOpen));
|
||||
|
||||
if (SUCCEEDED(hr)) {
|
||||
// Set options to pick folders instead of files
|
||||
DWORD dwOptions;
|
||||
hr = pFileOpen->GetOptions(&dwOptions);
|
||||
if (SUCCEEDED(hr)) {
|
||||
hr = pFileOpen->SetOptions(dwOptions | FOS_PICKFOLDERS);
|
||||
}
|
||||
|
||||
if (SUCCEEDED(hr)) {
|
||||
// Show the Open dialog
|
||||
hr = pFileOpen->Show(NULL);
|
||||
|
||||
if (SUCCEEDED(hr)) {
|
||||
// Get the folder path from the dialog
|
||||
IShellItem *pItem;
|
||||
hr = pFileOpen->GetResult(&pItem);
|
||||
if (SUCCEEDED(hr)) {
|
||||
PWSTR pszFolderPath;
|
||||
hr = pItem->GetDisplayName(SIGDN_FILESYSPATH, &pszFolderPath);
|
||||
|
||||
if (SUCCEEDED(hr)) {
|
||||
// Convert wide string to narrow string
|
||||
int size_needed = WideCharToMultiByte(CP_UTF8, 0, pszFolderPath, -1, NULL, 0, NULL, NULL);
|
||||
if (size_needed > 0) {
|
||||
std::vector<char> buffer(size_needed);
|
||||
WideCharToMultiByte(CP_UTF8, 0, pszFolderPath, -1, buffer.data(), size_needed, NULL, NULL);
|
||||
result = std::string(buffer.data());
|
||||
}
|
||||
CoTaskMemFree(pszFolderPath);
|
||||
}
|
||||
pItem->Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
pFileOpen->Release();
|
||||
}
|
||||
|
||||
CoUninitialize();
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<std::string> FileDialogWrapper::GetSubdirectoriesInFolder(
|
||||
const std::string &folder_path) {
|
||||
std::vector<std::string> subdirectories;
|
||||
WIN32_FIND_DATA findFileData;
|
||||
HANDLE hFind = FindFirstFile((folder_path + "\\*").c_str(), &findFileData);
|
||||
if (hFind != INVALID_HANDLE_VALUE) {
|
||||
do {
|
||||
if (findFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
|
||||
if (strcmp(findFileData.cFileName, ".") != 0 &&
|
||||
strcmp(findFileData.cFileName, "..") != 0) {
|
||||
subdirectories.emplace_back(findFileData.cFileName);
|
||||
}
|
||||
}
|
||||
} while (FindNextFile(hFind, &findFileData) != 0);
|
||||
FindClose(hFind);
|
||||
}
|
||||
return subdirectories;
|
||||
}
|
||||
|
||||
std::vector<std::string> FileDialogWrapper::GetFilesInFolder(
|
||||
const std::string &folder_path) {
|
||||
std::vector<std::string> files;
|
||||
WIN32_FIND_DATA findFileData;
|
||||
HANDLE hFind = FindFirstFile((folder_path + "\\*").c_str(), &findFileData);
|
||||
if (hFind != INVALID_HANDLE_VALUE) {
|
||||
do {
|
||||
if (!(findFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
|
||||
files.emplace_back(findFileData.cFileName);
|
||||
}
|
||||
} while (FindNextFile(hFind, &findFileData) != 0);
|
||||
FindClose(hFind);
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
#elif defined(__linux__)
|
||||
|
||||
#if defined(YAZE_ENABLE_NFD) && YAZE_ENABLE_NFD
|
||||
#include <nfd.h>
|
||||
#endif
|
||||
|
||||
std::string FileDialogWrapper::ShowOpenFileDialog() {
|
||||
// Use global feature flag to choose implementation
|
||||
if (core::FeatureFlags::get().kUseNativeFileDialog) {
|
||||
return ShowOpenFileDialogNFD();
|
||||
} else {
|
||||
return ShowOpenFileDialogBespoke();
|
||||
}
|
||||
}
|
||||
|
||||
std::string FileDialogWrapper::ShowOpenFileDialogNFD() {
|
||||
#if defined(YAZE_ENABLE_NFD) && YAZE_ENABLE_NFD
|
||||
NFD_Init();
|
||||
nfdu8char_t *out_path = NULL;
|
||||
nfdu8filteritem_t filters[1] = {{"Rom File", "sfc,smc"}};
|
||||
nfdopendialogu8args_t args = {0};
|
||||
args.filterList = filters;
|
||||
args.filterCount = 1;
|
||||
nfdresult_t result = NFD_OpenDialogU8_With(&out_path, &args);
|
||||
if (result == NFD_OKAY) {
|
||||
std::string file_path_linux(out_path);
|
||||
NFD_FreePath(out_path);
|
||||
NFD_Quit();
|
||||
return file_path_linux;
|
||||
} else if (result == NFD_CANCEL) {
|
||||
NFD_Quit();
|
||||
return "";
|
||||
}
|
||||
NFD_Quit();
|
||||
return "Error: NFD_OpenDialog";
|
||||
#else
|
||||
// NFD not available - fallback to bespoke
|
||||
return ShowOpenFileDialogBespoke();
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string FileDialogWrapper::ShowOpenFileDialogBespoke() {
|
||||
// Implement bespoke file dialog or return placeholder
|
||||
// This would contain the custom Linux implementation
|
||||
return ""; // Placeholder for bespoke implementation
|
||||
}
|
||||
|
||||
std::string FileDialogWrapper::ShowSaveFileDialogNFD(const std::string& default_name,
|
||||
const std::string& default_extension) {
|
||||
#if defined(YAZE_ENABLE_NFD) && YAZE_ENABLE_NFD
|
||||
NFD_Init();
|
||||
nfdu8char_t *out_path = NULL;
|
||||
|
||||
nfdsavedialogu8args_t args = {0};
|
||||
if (!default_extension.empty()) {
|
||||
// Create filter for the save dialog
|
||||
static nfdu8filteritem_t filters[3] = {
|
||||
{"Theme File", "theme"},
|
||||
{"Project File", "yaze"},
|
||||
{"ROM File", "sfc,smc"}
|
||||
};
|
||||
|
||||
if (default_extension == "theme") {
|
||||
args.filterList = &filters[0];
|
||||
args.filterCount = 1;
|
||||
} else if (default_extension == "yaze") {
|
||||
args.filterList = &filters[1];
|
||||
args.filterCount = 1;
|
||||
} else if (default_extension == "sfc" || default_extension == "smc") {
|
||||
args.filterList = &filters[2];
|
||||
args.filterCount = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!default_name.empty()) {
|
||||
args.defaultName = default_name.c_str();
|
||||
}
|
||||
|
||||
nfdresult_t result = NFD_SaveDialogU8_With(&out_path, &args);
|
||||
if (result == NFD_OKAY) {
|
||||
std::string file_path(out_path);
|
||||
NFD_FreePath(out_path);
|
||||
NFD_Quit();
|
||||
return file_path;
|
||||
} else if (result == NFD_CANCEL) {
|
||||
NFD_Quit();
|
||||
return "";
|
||||
}
|
||||
NFD_Quit();
|
||||
return "";
|
||||
#else
|
||||
// NFD not available - fallback to bespoke
|
||||
return ShowSaveFileDialogBespoke(default_name, default_extension);
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string FileDialogWrapper::ShowSaveFileDialogBespoke(const std::string& default_name,
|
||||
const std::string& default_extension) {
|
||||
// Basic Linux implementation using system command
|
||||
// For CI/CD, just return a placeholder path
|
||||
if (!default_name.empty() && !default_extension.empty()) {
|
||||
return default_name + "." + default_extension;
|
||||
}
|
||||
return ""; // For now return empty - full implementation can be added later
|
||||
}
|
||||
|
||||
std::string FileDialogWrapper::ShowSaveFileDialog(const std::string& default_name,
|
||||
const std::string& default_extension) {
|
||||
// Use global feature flag to choose implementation
|
||||
if (core::FeatureFlags::get().kUseNativeFileDialog) {
|
||||
return ShowSaveFileDialogNFD(default_name, default_extension);
|
||||
} else {
|
||||
return ShowSaveFileDialogBespoke(default_name, default_extension);
|
||||
}
|
||||
}
|
||||
|
||||
std::string FileDialogWrapper::ShowOpenFolderDialog() {
|
||||
// Use global feature flag to choose implementation
|
||||
if (core::FeatureFlags::get().kUseNativeFileDialog) {
|
||||
return ShowOpenFolderDialogNFD();
|
||||
} else {
|
||||
return ShowOpenFolderDialogBespoke();
|
||||
}
|
||||
}
|
||||
|
||||
std::string FileDialogWrapper::ShowOpenFolderDialogNFD() {
|
||||
#if defined(YAZE_ENABLE_NFD) && YAZE_ENABLE_NFD
|
||||
NFD_Init();
|
||||
nfdu8char_t *out_path = NULL;
|
||||
nfdresult_t result = NFD_PickFolderU8(&out_path, NULL);
|
||||
if (result == NFD_OKAY) {
|
||||
std::string folder_path_linux(out_path);
|
||||
NFD_FreePath(out_path);
|
||||
NFD_Quit();
|
||||
return folder_path_linux;
|
||||
} else if (result == NFD_CANCEL) {
|
||||
NFD_Quit();
|
||||
return "";
|
||||
}
|
||||
NFD_Quit();
|
||||
return "Error: NFD_PickFolder";
|
||||
#else
|
||||
// NFD not available - fallback to bespoke
|
||||
return ShowOpenFolderDialogBespoke();
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string FileDialogWrapper::ShowOpenFolderDialogBespoke() {
|
||||
// Implement bespoke folder dialog or return placeholder
|
||||
// This would contain the custom macOS implementation
|
||||
return ""; // Placeholder for bespoke implementation
|
||||
}
|
||||
|
||||
std::vector<std::string> FileDialogWrapper::GetSubdirectoriesInFolder(
|
||||
const std::string &folder_path) {
|
||||
std::vector<std::string> subdirectories;
|
||||
DIR *dir;
|
||||
struct dirent *ent;
|
||||
if ((dir = opendir(folder_path.c_str())) != NULL) {
|
||||
while ((ent = readdir(dir)) != NULL) {
|
||||
if (ent->d_type == DT_DIR) {
|
||||
if (strcmp(ent->d_name, ".") != 0 && strcmp(ent->d_name, "..") != 0) {
|
||||
subdirectories.push_back(ent->d_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
closedir(dir);
|
||||
}
|
||||
return subdirectories;
|
||||
}
|
||||
|
||||
std::vector<std::string> FileDialogWrapper::GetFilesInFolder(
|
||||
const std::string &folder_path) {
|
||||
std::vector<std::string> files;
|
||||
DIR *dir;
|
||||
struct dirent *ent;
|
||||
if ((dir = opendir(folder_path.c_str())) != NULL) {
|
||||
while ((ent = readdir(dir)) != NULL) {
|
||||
if (ent->d_type == DT_REG) {
|
||||
files.push_back(ent->d_name);
|
||||
}
|
||||
}
|
||||
closedir(dir);
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
#endif
|
||||
// Note: FileDialogWrapper implementations are in src/app/platform/file_dialog.mm
|
||||
// (platform-specific implementations to avoid duplicate symbols)
|
||||
|
||||
} // namespace util
|
||||
} // namespace yaze
|
||||
@@ -51,16 +51,46 @@ std::string GetBundleResourcePath();
|
||||
|
||||
enum class Platform { kUnknown, kMacOS, kiOS, kWindows, kLinux };
|
||||
|
||||
/**
|
||||
* @brief Gets the file extension from a filename.
|
||||
*
|
||||
* Uses std::filesystem for cross-platform consistency.
|
||||
*
|
||||
* @param filename The name of the file.
|
||||
* @return The file extension, or an empty string if none is found.
|
||||
*/
|
||||
std::string GetFileExtension(const std::string &filename);
|
||||
|
||||
/**
|
||||
* @brief Gets the filename from a full path.
|
||||
*
|
||||
* Uses std::filesystem for cross-platform consistency.
|
||||
*
|
||||
* @param filename The full path to the file.
|
||||
* @return The filename, including the extension.
|
||||
*/
|
||||
std::string GetFileName(const std::string &filename);
|
||||
std::string LoadFile(const std::string &filename);
|
||||
std::string LoadConfigFile(const std::string &filename);
|
||||
std::string GetConfigDirectory();
|
||||
bool EnsureConfigDirectoryExists();
|
||||
std::string ExpandHomePath(const std::string& path);
|
||||
std::string GetResourcePath(const std::string &resource_path);
|
||||
void SaveFile(const std::string &filename, const std::string &data);
|
||||
|
||||
/**
|
||||
* @brief Loads the entire contents of a file into a string.
|
||||
*
|
||||
* Throws a std::runtime_error if the file cannot be opened.
|
||||
*
|
||||
* @param filename The full, absolute path to the file.
|
||||
* @return The contents of the file as a string.
|
||||
*/
|
||||
std::string LoadFile(const std::string &filename);
|
||||
|
||||
/**
|
||||
* @brief Loads a file from the user's config directory.
|
||||
*
|
||||
* @param filename The name of the file inside the config directory.
|
||||
* @return The contents of the file, or an empty string if not found.
|
||||
*/
|
||||
std::string LoadFileFromConfigDir(const std::string &filename);
|
||||
|
||||
} // namespace util
|
||||
} // namespace yaze
|
||||
|
||||
|
||||
@@ -52,15 +52,53 @@ std::filesystem::path PlatformPaths::GetHomeDirectory() {
|
||||
}
|
||||
|
||||
absl::StatusOr<std::filesystem::path> PlatformPaths::GetAppDataDirectory() {
|
||||
#ifdef _WIN32
|
||||
wchar_t path[MAX_PATH];
|
||||
if (SUCCEEDED(SHGetFolderPathW(NULL, CSIDL_APPDATA, NULL, 0, path))) {
|
||||
std::filesystem::path app_data = std::filesystem::path(path) / "yaze";
|
||||
auto status = EnsureDirectoryExists(app_data);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
return app_data;
|
||||
}
|
||||
// Fallback if SHGetFolderPathW fails
|
||||
std::filesystem::path home = GetHomeDirectory();
|
||||
std::filesystem::path app_data = home / ".yaze";
|
||||
|
||||
std::filesystem::path app_data = home / "yaze_data";
|
||||
auto status = EnsureDirectoryExists(app_data);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
return app_data;
|
||||
#elif defined(__APPLE__)
|
||||
std::filesystem::path home = GetHomeDirectory();
|
||||
std::filesystem::path app_data = home / "Library" / "Application Support" / "yaze";
|
||||
auto status = EnsureDirectoryExists(app_data);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
return app_data;
|
||||
#else // Linux and other Unix-like systems
|
||||
const char* xdg_config_home = std::getenv("XDG_CONFIG_HOME");
|
||||
std::filesystem::path config_dir;
|
||||
if (xdg_config_home && *xdg_config_home) {
|
||||
config_dir = std::filesystem::path(xdg_config_home);
|
||||
} else {
|
||||
config_dir = GetHomeDirectory() / ".config";
|
||||
}
|
||||
std::filesystem::path app_data = config_dir / "yaze";
|
||||
auto status = EnsureDirectoryExists(app_data);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
return app_data;
|
||||
#endif
|
||||
}
|
||||
|
||||
absl::StatusOr<std::filesystem::path> PlatformPaths::GetConfigDirectory() {
|
||||
// For yaze, config and data directories are the same.
|
||||
// This provides a semantically clearer API for config access.
|
||||
return GetAppDataDirectory();
|
||||
}
|
||||
|
||||
absl::StatusOr<std::filesystem::path> PlatformPaths::GetAppDataSubdirectory(
|
||||
|
||||
@@ -29,16 +29,34 @@ class PlatformPaths {
|
||||
static std::filesystem::path GetHomeDirectory();
|
||||
|
||||
/**
|
||||
* @brief Get application data directory for YAZE
|
||||
*
|
||||
* Creates the directory if it doesn't exist.
|
||||
*
|
||||
* - Windows: %USERPROFILE%\.yaze
|
||||
* - Unix/macOS: $HOME/.yaze
|
||||
*
|
||||
* @return StatusOr with path to data directory
|
||||
* @brief Get the user-specific application data directory for YAZE.
|
||||
*
|
||||
* This is the standard location for storing user-specific data, settings,
|
||||
* and cache files. The directory is created if it doesn't exist.
|
||||
*
|
||||
* - Windows: `%APPDATA%\yaze` (e.g., C:\Users\user\AppData\Roaming\yaze)
|
||||
* - macOS: `~/Library/Application Support/yaze`
|
||||
* - Linux: `$XDG_CONFIG_HOME/yaze` or `~/.config/yaze`
|
||||
*
|
||||
* @return StatusOr with path to the application data directory.
|
||||
*/
|
||||
static absl::StatusOr<std::filesystem::path> GetAppDataDirectory();
|
||||
|
||||
/**
|
||||
* @brief Get the user-specific configuration directory for YAZE.
|
||||
*
|
||||
* This is the standard location for storing user configuration files.
|
||||
* It often maps to the same location as GetAppDataDirectory but provides
|
||||
* a more semantically correct API for config files.
|
||||
* The directory is created if it doesn't exist.
|
||||
*
|
||||
* - Windows: `%APPDATA%\yaze`
|
||||
* - macOS: `~/Library/Application Support/yaze`
|
||||
* - Linux: `$XDG_CONFIG_HOME/yaze` or `~/.config/yaze`
|
||||
*
|
||||
* @return StatusOr with path to the configuration directory.
|
||||
*/
|
||||
static absl::StatusOr<std::filesystem::path> GetConfigDirectory();
|
||||
|
||||
/**
|
||||
* @brief Get a subdirectory within the app data folder
|
||||
|
||||
@@ -17,6 +17,7 @@ set(YAZE_UTIL_SRC
|
||||
util/log.cc
|
||||
util/platform_paths.cc
|
||||
util/file_util.cc
|
||||
util/platform_paths.cc
|
||||
)
|
||||
|
||||
add_library(yaze_util STATIC ${YAZE_UTIL_SRC})
|
||||
|
||||
Reference in New Issue
Block a user