120 lines
3.6 KiB
C++
120 lines
3.6 KiB
C++
#include "platform.hpp"
|
|
|
|
#include "config.hpp"
|
|
|
|
#include <cstdlib>
|
|
#include <filesystem>
|
|
|
|
namespace connector {
|
|
namespace {
|
|
|
|
std::string environment(const char* name) {
|
|
const char* value = std::getenv(name);
|
|
return value ? value : "";
|
|
}
|
|
|
|
void append_path_candidates(std::vector<std::filesystem::path>& candidates) {
|
|
const std::string value = environment("PATH");
|
|
#ifdef _WIN32
|
|
constexpr char delimiter = ';';
|
|
constexpr const char* name = "Spine.com";
|
|
#else
|
|
constexpr char delimiter = ':';
|
|
constexpr const char* name = "Spine";
|
|
#endif
|
|
std::size_t start = 0;
|
|
while (start <= value.size()) {
|
|
const auto end = value.find(delimiter, start);
|
|
std::string item = value.substr(start, end - start);
|
|
if (item.size() >= 2 && item.front() == '"' && item.back() == '"') item = item.substr(1, item.size() - 2);
|
|
if (!item.empty()) {
|
|
const auto path = to_path(item);
|
|
if (!path.empty()) candidates.push_back(path / name);
|
|
}
|
|
if (end == std::string::npos) break;
|
|
start = end + 1;
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
std::filesystem::path to_path(const std::string& value) {
|
|
try {
|
|
return std::filesystem::u8path(value);
|
|
} catch (...) {
|
|
// 输入不是合法 UTF-8 时回退到本地代码页构造。
|
|
try {
|
|
return std::filesystem::path(value);
|
|
} catch (...) {
|
|
// 本地代码页也无法转换时返回空路径,由调用方跳过。
|
|
return std::filesystem::path();
|
|
}
|
|
}
|
|
}
|
|
|
|
std::string platform_name() {
|
|
#ifdef _WIN32
|
|
return "win32";
|
|
#elif defined(__APPLE__)
|
|
return "darwin";
|
|
#else
|
|
return "linux";
|
|
#endif
|
|
}
|
|
|
|
bool valid_spine_executable(const std::string& path) {
|
|
if (path.empty()) return false;
|
|
std::error_code error;
|
|
const auto status = std::filesystem::status(to_path(path), error);
|
|
return !error && std::filesystem::is_regular_file(status);
|
|
}
|
|
|
|
std::string find_spine_executable() {
|
|
try {
|
|
std::vector<std::filesystem::path> candidates;
|
|
const std::string environment_path = environment("SPINE_EXECUTABLE");
|
|
if (!environment_path.empty()) {
|
|
const auto path = to_path(environment_path);
|
|
if (!path.empty()) candidates.push_back(path);
|
|
}
|
|
const ConnectorConfig config = load_config();
|
|
if (!config.spine_executable.empty()) {
|
|
const auto path = to_path(config.spine_executable);
|
|
if (!path.empty()) candidates.push_back(path);
|
|
}
|
|
#ifdef _WIN32
|
|
for (const char* variable : {"ProgramFiles", "ProgramFiles(x86)", "LOCALAPPDATA"}) {
|
|
const std::string root = environment(variable);
|
|
if (root.empty()) continue;
|
|
const auto root_path = to_path(root);
|
|
if (root_path.empty()) continue;
|
|
candidates.push_back(root_path / "Spine" / "Spine.com");
|
|
candidates.push_back(root_path / "Esoteric Software" / "Spine" / "Spine.com");
|
|
candidates.push_back(root_path / "Programs" / "Spine" / "Spine.com");
|
|
}
|
|
#elif defined(__APPLE__)
|
|
candidates.emplace_back("/Applications/Spine.app/Contents/MacOS/Spine");
|
|
const std::string home = environment("HOME");
|
|
if (!home.empty()) {
|
|
const auto path = to_path(home);
|
|
if (!path.empty()) candidates.push_back(path / "Applications" / "Spine.app" / "Contents" / "MacOS" / "Spine");
|
|
}
|
|
#else
|
|
candidates.emplace_back("/opt/Spine/Spine.sh");
|
|
candidates.emplace_back("/usr/local/bin/Spine");
|
|
candidates.emplace_back("/usr/bin/Spine");
|
|
#endif
|
|
append_path_candidates(candidates);
|
|
for (const auto& candidate : candidates) {
|
|
const std::string path = candidate.u8string();
|
|
if (valid_spine_executable(path)) return path;
|
|
}
|
|
return "";
|
|
} catch (...) {
|
|
// 任何异常都不应导致程序崩溃,返回未找到。
|
|
return "";
|
|
}
|
|
}
|
|
|
|
} // namespace connector
|