i should really work on this more
This commit is contained in:
parent
c2536c361e
commit
a8e175a5f9
9 changed files with 230 additions and 78 deletions
|
@ -1,6 +1,7 @@
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <fmt/core.h>
|
#include <fmt/core.h>
|
||||||
|
#include <iostream>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
@ -11,34 +12,165 @@ namespace fs = std::filesystem;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
fn GetConfigPath() -> fs::path {
|
fn GetConfigPath() -> fs::path {
|
||||||
|
std::vector<fs::path> possiblePaths;
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
char* rawPtr = nullptr;
|
// Windows possible paths in order of preference
|
||||||
size_t bufferSize = 0;
|
if (auto result = GetEnv("LOCALAPPDATA"); result)
|
||||||
if (_dupenv_s(&rawPtr, &bufferSize, "LOCALAPPDATA") != 0 || !rawPtr)
|
possiblePaths.push_back(fs::path(*result) / "draconis++" / "config.toml");
|
||||||
throw std::runtime_error("LOCALAPPDATA env var not found");
|
|
||||||
std::unique_ptr<char, decltype(&free)> localAppData(rawPtr, free);
|
if (auto result = GetEnv("USERPROFILE"); result) {
|
||||||
return fs::path(localAppData.get()) / "draconis++" / "config.toml";
|
// Support for .config style on Windows (some users prefer this)
|
||||||
|
possiblePaths.push_back(fs::path(*result) / ".config" / "draconis++" / "config.toml");
|
||||||
|
// Traditional Windows location alternative
|
||||||
|
possiblePaths.push_back(fs::path(*result) / "AppData" / "Local" / "draconis++" / "config.toml");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto result = GetEnv("APPDATA"); result)
|
||||||
|
possiblePaths.push_back(fs::path(*result) / "draconis++" / "config.toml");
|
||||||
|
|
||||||
|
// Portable app option - config in same directory as executable
|
||||||
|
possiblePaths.push_back(fs::path(".") / "config.toml");
|
||||||
#else
|
#else
|
||||||
const char* home = std::getenv("HOME");
|
// Unix/Linux paths in order of preference
|
||||||
if (!home)
|
if (auto result = getEnv("XDG_CONFIG_HOME"); result)
|
||||||
throw std::runtime_error("HOME env var not found");
|
possiblePaths.push_back(fs::path(*result) / "draconis++" / "config.toml");
|
||||||
return fs::path(home) / ".config" / "draconis++" / "config.toml";
|
|
||||||
|
if (auto result = getEnv("HOME"); result) {
|
||||||
|
possiblePaths.push_back(fs::path(*result) / ".config" / "draconis++" / "config.toml");
|
||||||
|
possiblePaths.push_back(fs::path(*result) / ".draconis++" / "config.toml");
|
||||||
|
}
|
||||||
|
|
||||||
|
// System-wide config
|
||||||
|
possiblePaths.push_back(fs::path("/etc/draconis++/config.toml"));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Check if any of these configs already exist
|
||||||
|
for (const auto& path : possiblePaths)
|
||||||
|
if (std::error_code errc; exists(path, errc) && !errc)
|
||||||
|
return path;
|
||||||
|
|
||||||
|
// If no config exists yet, return the default (first in priority)
|
||||||
|
if (!possiblePaths.empty()) {
|
||||||
|
// Create directory structure for the default path
|
||||||
|
const fs::path defaultDir = possiblePaths[0].parent_path();
|
||||||
|
|
||||||
|
if (std::error_code errc; !exists(defaultDir, errc) && !errc) {
|
||||||
|
create_directories(defaultDir, errc);
|
||||||
|
if (errc)
|
||||||
|
WARN_LOG("Warning: Failed to create config directory: {}", errc.message());
|
||||||
|
}
|
||||||
|
|
||||||
|
return possiblePaths[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ultimate fallback if somehow we have no paths
|
||||||
|
throw std::runtime_error("Could not determine a valid config path");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn CreateDefaultConfig(const fs::path& configPath) -> bool {
|
||||||
|
try {
|
||||||
|
// Ensure the directory exists
|
||||||
|
std::error_code errc;
|
||||||
|
create_directories(configPath.parent_path(), errc);
|
||||||
|
if (errc) {
|
||||||
|
ERROR_LOG("Failed to create config directory: {}", errc.message());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a default TOML document
|
||||||
|
toml::table root;
|
||||||
|
|
||||||
|
// Get default username for General section
|
||||||
|
std::string defaultName;
|
||||||
|
#ifdef _WIN32
|
||||||
|
std::array<char, 256> username;
|
||||||
|
DWORD size = sizeof(username);
|
||||||
|
defaultName = GetUserNameA(username.data(), &size) ? username.data() : "User";
|
||||||
|
#else
|
||||||
|
if (struct passwd* pwd = getpwuid(getuid()); pwd)
|
||||||
|
defaultName = pwd->pw_name;
|
||||||
|
else if (const char* envUser = getenv("USER"))
|
||||||
|
defaultName = envUser;
|
||||||
|
else
|
||||||
|
defaultName = "User";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// General section
|
||||||
|
toml::table* general = root.insert("general", toml::table {}).first->second.as_table();
|
||||||
|
general->insert("name", defaultName);
|
||||||
|
|
||||||
|
// Now Playing section
|
||||||
|
toml::table* nowPlaying = root.insert("now_playing", toml::table {}).first->second.as_table();
|
||||||
|
nowPlaying->insert("enabled", false);
|
||||||
|
|
||||||
|
// Weather section
|
||||||
|
toml::table* weather = root.insert("weather", toml::table {}).first->second.as_table();
|
||||||
|
weather->insert("enabled", false);
|
||||||
|
weather->insert("show_town_name", false);
|
||||||
|
weather->insert("api_key", "");
|
||||||
|
weather->insert("units", "metric");
|
||||||
|
weather->insert("location", "London");
|
||||||
|
|
||||||
|
// Write to file (using a stringstream for comments + TOML)
|
||||||
|
std::ofstream file(configPath);
|
||||||
|
if (!file) {
|
||||||
|
ERROR_LOG("Failed to open config file for writing: {}", configPath.string());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
file << "# Draconis++ Configuration File\n\n";
|
||||||
|
|
||||||
|
file << "# General settings\n";
|
||||||
|
file << "[general]\n";
|
||||||
|
file << "name = \"" << defaultName << "\" # Your display name\n\n";
|
||||||
|
|
||||||
|
file << "# Now Playing integration\n";
|
||||||
|
file << "[now_playing]\n";
|
||||||
|
file << "enabled = false # Set to true to enable media integration\n\n";
|
||||||
|
|
||||||
|
file << "# Weather settings\n";
|
||||||
|
file << "[weather]\n";
|
||||||
|
file << "enabled = false # Set to true to enable weather display\n";
|
||||||
|
file << "show_town_name = false # Show location name in weather display\n";
|
||||||
|
file << "api_key = \"\" # Your weather API key\n";
|
||||||
|
file << "units = \"metric\" # Use \"metric\" for °C or \"imperial\" for °F\n";
|
||||||
|
file << "location = \"London\" # Your city name\n\n";
|
||||||
|
|
||||||
|
file << "# Alternatively, you can specify coordinates instead of a city name:\n";
|
||||||
|
file << "# [weather.location]\n";
|
||||||
|
file << "# lat = 51.5074\n";
|
||||||
|
file << "# lon = -0.1278\n";
|
||||||
|
|
||||||
|
INFO_LOG("Created default config file at {}", configPath.string());
|
||||||
|
return true;
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
ERROR_LOG("Failed to create default config file: {}", e.what());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn Config::getInstance() -> Config {
|
fn Config::getInstance() -> Config {
|
||||||
try {
|
try {
|
||||||
const fs::path configPath = GetConfigPath();
|
const fs::path configPath = GetConfigPath();
|
||||||
if (!fs::exists(configPath)) {
|
|
||||||
WARN_LOG("Config file not found, using defaults");
|
// Check if the config file exists
|
||||||
return Config {};
|
if (!exists(configPath)) {
|
||||||
|
INFO_LOG("Config file not found, creating defaults at {}", configPath.string());
|
||||||
|
|
||||||
|
// Create default config
|
||||||
|
if (!CreateDefaultConfig(configPath)) {
|
||||||
|
WARN_LOG("Failed to create default config, using in-memory defaults");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto config = toml::parse_file(configPath.string());
|
// Now we should have a config file to read
|
||||||
return Config::fromToml(config);
|
const toml::parse_result config = toml::parse_file(configPath.string());
|
||||||
|
return fromToml(config);
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
DEBUG_LOG("Config loading failed: {}, using defaults", e.what());
|
DEBUG_LOG("Config loading failed: {}, using defaults", e.what());
|
||||||
return Config {};
|
return {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,7 @@ struct General {
|
||||||
static fn fromToml(const toml::table& tbl) -> General {
|
static fn fromToml(const toml::table& tbl) -> General {
|
||||||
General gen;
|
General gen;
|
||||||
|
|
||||||
if (std::optional<string> name = tbl["name"].value<string>())
|
if (const std::optional<string> name = tbl["name"].value<string>())
|
||||||
gen.name = *name;
|
gen.name = *name;
|
||||||
|
|
||||||
return gen;
|
return gen;
|
||||||
|
@ -65,7 +65,7 @@ struct Weather {
|
||||||
weather.api_key = tbl["api_key"].value<string>().value_or("");
|
weather.api_key = tbl["api_key"].value<string>().value_or("");
|
||||||
weather.units = tbl["units"].value<string>().value_or("metric");
|
weather.units = tbl["units"].value<string>().value_or("metric");
|
||||||
|
|
||||||
if (auto location = tbl["location"]) {
|
if (const toml::node_view<const toml::node> location = tbl["location"]) {
|
||||||
if (location.is_string()) {
|
if (location.is_string()) {
|
||||||
weather.location = location.value<string>().value();
|
weather.location = location.value<string>().value();
|
||||||
} else if (location.is_table()) {
|
} else if (location.is_table()) {
|
||||||
|
|
|
@ -13,13 +13,10 @@
|
||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
using namespace std::string_literals;
|
using namespace std::string_literals;
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
using Result = std::expected<T, string>;
|
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
constexpr glz::opts glaze_opts = { .error_on_unknown_keys = false };
|
constexpr glz::opts glaze_opts = { .error_on_unknown_keys = false };
|
||||||
|
|
||||||
fn GetCachePath() -> Result<fs::path> {
|
fn GetCachePath() -> std::expected<fs::path, string> {
|
||||||
std::error_code errc;
|
std::error_code errc;
|
||||||
fs::path cachePath = fs::temp_directory_path(errc);
|
fs::path cachePath = fs::temp_directory_path(errc);
|
||||||
|
|
||||||
|
@ -30,8 +27,8 @@ namespace {
|
||||||
return cachePath;
|
return cachePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ReadCacheFromFile() -> Result<WeatherOutput> {
|
fn ReadCacheFromFile() -> std::expected<WeatherOutput, string> {
|
||||||
Result<fs::path> cachePath = GetCachePath();
|
std::expected<fs::path, string> cachePath = GetCachePath();
|
||||||
|
|
||||||
if (!cachePath)
|
if (!cachePath)
|
||||||
return std::unexpected(cachePath.error());
|
return std::unexpected(cachePath.error());
|
||||||
|
@ -44,11 +41,10 @@ namespace {
|
||||||
DEBUG_LOG("Reading from cache file...");
|
DEBUG_LOG("Reading from cache file...");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const string content((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>());
|
const string content((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>());
|
||||||
WeatherOutput result;
|
WeatherOutput result;
|
||||||
glz::error_ctx errc = glz::read<glaze_opts>(result, content);
|
|
||||||
|
|
||||||
if (errc.ec != glz::error_code::none)
|
if (const glz::error_ctx errc = glz::read<glaze_opts>(result, content); errc.ec != glz::error_code::none)
|
||||||
return std::unexpected("JSON parse error: " + glz::format_error(errc, content));
|
return std::unexpected("JSON parse error: " + glz::format_error(errc, content));
|
||||||
|
|
||||||
DEBUG_LOG("Successfully read from cache file.");
|
DEBUG_LOG("Successfully read from cache file.");
|
||||||
|
@ -56,8 +52,8 @@ namespace {
|
||||||
} catch (const std::exception& e) { return std::unexpected("Error reading cache: "s + e.what()); }
|
} catch (const std::exception& e) { return std::unexpected("Error reading cache: "s + e.what()); }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn WriteCacheToFile(const WeatherOutput& data) -> Result<void> {
|
fn WriteCacheToFile(const WeatherOutput& data) -> std::expected<void, string> {
|
||||||
Result<fs::path> cachePath = GetCachePath();
|
std::expected<fs::path, string> cachePath = GetCachePath();
|
||||||
|
|
||||||
if (!cachePath)
|
if (!cachePath)
|
||||||
return std::unexpected(cachePath.error());
|
return std::unexpected(cachePath.error());
|
||||||
|
@ -72,10 +68,9 @@ namespace {
|
||||||
if (!ofs.is_open())
|
if (!ofs.is_open())
|
||||||
return std::unexpected("Failed to open temp file: " + tempPath.string());
|
return std::unexpected("Failed to open temp file: " + tempPath.string());
|
||||||
|
|
||||||
string jsonStr;
|
string jsonStr;
|
||||||
glz::error_ctx errc = glz::write_json(data, jsonStr);
|
|
||||||
|
|
||||||
if (errc.ec != glz::error_code::none)
|
if (const glz::error_ctx errc = glz::write_json(data, jsonStr); errc.ec != glz::error_code::none)
|
||||||
return std::unexpected("JSON serialization error: " + glz::format_error(errc, jsonStr));
|
return std::unexpected("JSON serialization error: " + glz::format_error(errc, jsonStr));
|
||||||
|
|
||||||
ofs << jsonStr;
|
ofs << jsonStr;
|
||||||
|
@ -101,7 +96,7 @@ namespace {
|
||||||
return totalSize;
|
return totalSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn MakeApiRequest(const string& url) -> const Result<WeatherOutput> {
|
fn MakeApiRequest(const string& url) -> std::expected<WeatherOutput, string> {
|
||||||
DEBUG_LOG("Making API request to URL: {}", url);
|
DEBUG_LOG("Making API request to URL: {}", url);
|
||||||
CURL* curl = curl_easy_init();
|
CURL* curl = curl_easy_init();
|
||||||
string responseBuffer;
|
string responseBuffer;
|
||||||
|
@ -121,10 +116,9 @@ namespace {
|
||||||
if (res != CURLE_OK)
|
if (res != CURLE_OK)
|
||||||
return std::unexpected(fmt::format("cURL error: {}", curl_easy_strerror(res)));
|
return std::unexpected(fmt::format("cURL error: {}", curl_easy_strerror(res)));
|
||||||
|
|
||||||
WeatherOutput output;
|
WeatherOutput output;
|
||||||
glz::error_ctx errc = glz::read<glaze_opts>(output, responseBuffer);
|
|
||||||
|
|
||||||
if (errc.ec != glz::error_code::none)
|
if (const glz::error_ctx errc = glz::read<glaze_opts>(output, responseBuffer); errc.ec != glz::error_code::none)
|
||||||
return std::unexpected("API response parse error: " + glz::format_error(errc, responseBuffer));
|
return std::unexpected("API response parse error: " + glz::format_error(errc, responseBuffer));
|
||||||
|
|
||||||
return std::move(output);
|
return std::move(output);
|
||||||
|
@ -134,11 +128,11 @@ namespace {
|
||||||
fn Weather::getWeatherInfo() const -> WeatherOutput {
|
fn Weather::getWeatherInfo() const -> WeatherOutput {
|
||||||
using namespace std::chrono;
|
using namespace std::chrono;
|
||||||
|
|
||||||
if (Result<WeatherOutput> data = ReadCacheFromFile()) {
|
if (std::expected<WeatherOutput, string> data = ReadCacheFromFile()) {
|
||||||
const WeatherOutput& dataVal = *data;
|
const WeatherOutput& dataVal = *data;
|
||||||
const duration<double> cacheAge = system_clock::now() - system_clock::time_point(seconds(dataVal.dt));
|
|
||||||
|
|
||||||
if (cacheAge < 10min) {
|
if (const duration<double> cacheAge = system_clock::now() - system_clock::time_point(seconds(dataVal.dt));
|
||||||
|
cacheAge < 10min) {
|
||||||
DEBUG_LOG("Using valid cache");
|
DEBUG_LOG("Using valid cache");
|
||||||
return dataVal;
|
return dataVal;
|
||||||
}
|
}
|
||||||
|
@ -148,13 +142,13 @@ fn Weather::getWeatherInfo() const -> WeatherOutput {
|
||||||
DEBUG_LOG("Cache error: {}", data.error());
|
DEBUG_LOG("Cache error: {}", data.error());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handleApiResult = [](const Result<WeatherOutput>& result) -> WeatherOutput {
|
fn handleApiResult = [](const std::expected<WeatherOutput, string>& result) -> WeatherOutput {
|
||||||
if (!result) {
|
if (!result) {
|
||||||
ERROR_LOG("API request failed: {}", result.error());
|
ERROR_LOG("API request failed: {}", result.error());
|
||||||
return WeatherOutput {};
|
return WeatherOutput {};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Result<void> writeResult = WriteCacheToFile(*result); !writeResult)
|
if (std::expected<void, string> writeResult = WriteCacheToFile(*result); !writeResult)
|
||||||
ERROR_LOG("Failed to write cache: {}", writeResult.error());
|
ERROR_LOG("Failed to write cache: {}", writeResult.error());
|
||||||
|
|
||||||
return *result;
|
return *result;
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
#include "../util/types.h"
|
#include "../util/types.h"
|
||||||
|
|
||||||
// NOLINTBEGIN(readability-identifier-naming)
|
// NOLINTBEGIN(readability-identifier-naming)
|
||||||
|
|
||||||
struct Condition {
|
struct Condition {
|
||||||
string description;
|
string description;
|
||||||
|
|
||||||
|
@ -40,5 +39,4 @@ struct WeatherOutput {
|
||||||
static constexpr auto value = glz::object("main", &T::main, "name", &T::name, "weather", &T::weather, "dt", &T::dt);
|
static constexpr auto value = glz::object("main", &T::main, "name", &T::name, "weather", &T::weather, "dt", &T::dt);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// NOLINTEND(readability-identifier-naming)
|
// NOLINTEND(readability-identifier-naming)
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
#include <expected>
|
#include <expected>
|
||||||
#include <fmt/chrono.h>
|
#include <fmt/chrono.h>
|
||||||
#include <fmt/color.h>
|
#include <fmt/color.h>
|
||||||
#include <fmt/core.h>
|
|
||||||
#include <fmt/format.h>
|
#include <fmt/format.h>
|
||||||
#include <ftxui/dom/elements.hpp>
|
#include <ftxui/dom/elements.hpp>
|
||||||
#include <ftxui/screen/screen.hpp>
|
#include <ftxui/screen/screen.hpp>
|
||||||
|
@ -29,7 +28,7 @@ struct fmt::formatter<BytesToGiB> : fmt::formatter<double> {
|
||||||
template <typename FmtCtx>
|
template <typename FmtCtx>
|
||||||
constexpr fn format(const BytesToGiB& BTG, FmtCtx& ctx) const -> typename FmtCtx::iterator {
|
constexpr fn format(const BytesToGiB& BTG, FmtCtx& ctx) const -> typename FmtCtx::iterator {
|
||||||
// Format as double with GiB suffix, no space
|
// Format as double with GiB suffix, no space
|
||||||
return fmt::format_to(ctx.out(), "{:.2f}GiB", static_cast<double>(BTG.value) / GIB);
|
return fmt::format_to(ctx.out(), "{:.2f}GiB", static_cast<f64>(BTG.value) / GIB);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -265,9 +264,8 @@ namespace {
|
||||||
|
|
||||||
// Now Playing row
|
// Now Playing row
|
||||||
if (nowPlayingEnabled && data.now_playing.has_value()) {
|
if (nowPlayingEnabled && data.now_playing.has_value()) {
|
||||||
const std::expected<string, NowPlayingError>& nowPlayingResult = *data.now_playing;
|
if (const std::expected<string, NowPlayingError>& nowPlayingResult = *data.now_playing;
|
||||||
|
nowPlayingResult.has_value()) {
|
||||||
if (nowPlayingResult.has_value()) {
|
|
||||||
const std::string& npText = *nowPlayingResult;
|
const std::string& npText = *nowPlayingResult;
|
||||||
|
|
||||||
content.push_back(separator() | color(borderColor));
|
content.push_back(separator() | color(borderColor));
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
#include <ranges>
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
|
|
||||||
// clang-format off
|
// clang-format off
|
||||||
|
@ -14,6 +15,8 @@
|
||||||
#include <guiddef.h>
|
#include <guiddef.h>
|
||||||
#include <winrt/Windows.Foundation.h>
|
#include <winrt/Windows.Foundation.h>
|
||||||
#include <winrt/Windows.Media.Control.h>
|
#include <winrt/Windows.Media.Control.h>
|
||||||
|
#include <winrt/Windows.Storage.h>
|
||||||
|
#include <winrt/Windows.System.Diagnostics.h>
|
||||||
#include <winrt/base.h>
|
#include <winrt/base.h>
|
||||||
#include <winrt/impl/Windows.Media.Control.2.h>
|
#include <winrt/impl/Windows.Media.Control.2.h>
|
||||||
|
|
||||||
|
@ -22,7 +25,7 @@
|
||||||
using std::string_view;
|
using std::string_view;
|
||||||
using RtlGetVersionPtr = NTSTATUS(WINAPI*)(PRTL_OSVERSIONINFOW);
|
using RtlGetVersionPtr = NTSTATUS(WINAPI*)(PRTL_OSVERSIONINFOW);
|
||||||
|
|
||||||
// NOLINTBEGIN(*-pro-type-cstyle-cast,*-no-int-to-ptr)
|
// NOLINTBEGIN(*-pro-type-cstyle-cast,*-no-int-to-ptr,*-pro-type-reinterpret-cast)
|
||||||
namespace {
|
namespace {
|
||||||
class ProcessSnapshot {
|
class ProcessSnapshot {
|
||||||
public:
|
public:
|
||||||
|
@ -55,11 +58,11 @@ namespace {
|
||||||
// Get first process
|
// Get first process
|
||||||
if (Process32First(h_snapshot, &pe32)) {
|
if (Process32First(h_snapshot, &pe32)) {
|
||||||
// Add first process to vector
|
// Add first process to vector
|
||||||
processes.emplace_back(pe32.th32ProcessID, string(static_cast<const char*>(pe32.szExeFile)));
|
processes.emplace_back(pe32.th32ProcessID, string(reinterpret_cast<const char*>(pe32.szExeFile)));
|
||||||
|
|
||||||
// Add remaining processes
|
// Add remaining processes
|
||||||
while (Process32Next(h_snapshot, &pe32))
|
while (Process32Next(h_snapshot, &pe32))
|
||||||
processes.emplace_back(pe32.th32ProcessID, string(static_cast<const char*>(pe32.szExeFile)));
|
processes.emplace_back(pe32.th32ProcessID, string(reinterpret_cast<const char*>(pe32.szExeFile)));
|
||||||
}
|
}
|
||||||
|
|
||||||
return processes;
|
return processes;
|
||||||
|
@ -94,7 +97,7 @@ namespace {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn GetProcessInfo() -> std::vector<std::pair<DWORD, string>> {
|
fn GetProcessInfo() -> std::vector<std::pair<DWORD, string>> {
|
||||||
ProcessSnapshot snapshot;
|
const ProcessSnapshot snapshot;
|
||||||
return snapshot.isValid() ? snapshot.getProcesses() : std::vector<std::pair<DWORD, string>> {};
|
return snapshot.isValid() ? snapshot.getProcesses() : std::vector<std::pair<DWORD, string>> {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,8 +107,8 @@ namespace {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn GetParentProcessId(DWORD pid) -> DWORD {
|
fn GetParentProcessId(const DWORD pid) -> DWORD {
|
||||||
ProcessSnapshot snapshot;
|
const ProcessSnapshot snapshot;
|
||||||
if (!snapshot.isValid())
|
if (!snapshot.isValid())
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
@ -126,7 +129,7 @@ namespace {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn GetProcessName(const DWORD pid) -> string {
|
fn GetProcessName(const DWORD pid) -> string {
|
||||||
ProcessSnapshot snapshot;
|
const ProcessSnapshot snapshot;
|
||||||
if (!snapshot.isValid())
|
if (!snapshot.isValid())
|
||||||
return "";
|
return "";
|
||||||
|
|
||||||
|
@ -137,24 +140,24 @@ namespace {
|
||||||
return "";
|
return "";
|
||||||
|
|
||||||
if (pe32.th32ProcessID == pid)
|
if (pe32.th32ProcessID == pid)
|
||||||
return { static_cast<const char*>(pe32.szExeFile) };
|
return reinterpret_cast<const char*>(pe32.szExeFile);
|
||||||
|
|
||||||
while (Process32Next(snapshot.h_snapshot, &pe32))
|
while (Process32Next(snapshot.h_snapshot, &pe32))
|
||||||
if (pe32.th32ProcessID == pid)
|
if (pe32.th32ProcessID == pid)
|
||||||
return { static_cast<const char*>(pe32.szExeFile) };
|
return reinterpret_cast<const char*>(pe32.szExeFile);
|
||||||
|
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn GetMemInfo() -> expected<u64, string> {
|
fn GetMemInfo() -> expected<u64, string> {
|
||||||
MEMORYSTATUSEX memInfo;
|
try {
|
||||||
memInfo.dwLength = sizeof(MEMORYSTATUSEX);
|
using namespace winrt::Windows::System::Diagnostics;
|
||||||
|
const SystemDiagnosticInfo diag = SystemDiagnosticInfo::GetForCurrentSystem();
|
||||||
if (!GlobalMemoryStatusEx(&memInfo))
|
return diag.MemoryUsage().GetReport().TotalPhysicalSizeInBytes();
|
||||||
return std::unexpected("Failed to get memory status");
|
} catch (const winrt::hresult_error& e) {
|
||||||
|
return std::unexpected("Failed to get memory info: " + to_string(e.message()));
|
||||||
return memInfo.ullTotalPhys;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn GetNowPlaying() -> expected<string, NowPlayingError> {
|
fn GetNowPlaying() -> expected<string, NowPlayingError> {
|
||||||
|
@ -185,11 +188,11 @@ fn GetNowPlaying() -> expected<string, NowPlayingError> {
|
||||||
|
|
||||||
fn GetOSVersion() -> expected<string, string> {
|
fn GetOSVersion() -> expected<string, string> {
|
||||||
// First try using the native Windows API
|
// First try using the native Windows API
|
||||||
OSVERSIONINFOEXW osvi = { sizeof(OSVERSIONINFOEXW), 0, 0, 0, 0, { 0 }, 0, 0, 0, 0, 0 };
|
constexpr OSVERSIONINFOEXW osvi = { sizeof(OSVERSIONINFOEXW), 0, 0, 0, 0, { 0 }, 0, 0, 0, 0, 0 };
|
||||||
NTSTATUS status = 0;
|
NTSTATUS status = 0;
|
||||||
|
|
||||||
// Get RtlGetVersion function from ntdll.dll (not affected by application manifest)
|
// Get RtlGetVersion function from ntdll.dll (not affected by application manifest)
|
||||||
if (HMODULE ntdllHandle = GetModuleHandleW(L"ntdll.dll"))
|
if (const HMODULE ntdllHandle = GetModuleHandleW(L"ntdll.dll"))
|
||||||
if (const auto rtlGetVersion = std::bit_cast<RtlGetVersionPtr>(GetProcAddress(ntdllHandle, "RtlGetVersion")))
|
if (const auto rtlGetVersion = std::bit_cast<RtlGetVersionPtr>(GetProcAddress(ntdllHandle, "RtlGetVersion")))
|
||||||
status = rtlGetVersion(std::bit_cast<PRTL_OSVERSIONINFOW>(&osvi));
|
status = rtlGetVersion(std::bit_cast<PRTL_OSVERSIONINFOW>(&osvi));
|
||||||
|
|
||||||
|
@ -290,7 +293,7 @@ fn GetWindowManager() -> string {
|
||||||
std::vector<string> processNames;
|
std::vector<string> processNames;
|
||||||
|
|
||||||
processNames.reserve(processInfo.size());
|
processNames.reserve(processInfo.size());
|
||||||
for (const auto& [pid, name] : processInfo) processNames.push_back(name);
|
for (const auto& name : processInfo | std::views::values) processNames.push_back(name);
|
||||||
|
|
||||||
// Check for third-party WMs using a map for cleaner code
|
// Check for third-party WMs using a map for cleaner code
|
||||||
const std::unordered_map<string, string> wmProcesses = {
|
const std::unordered_map<string, string> wmProcesses = {
|
||||||
|
@ -382,7 +385,6 @@ fn GetShell() -> string {
|
||||||
size_t shellLen = 0;
|
size_t shellLen = 0;
|
||||||
_dupenv_s(&shell, &shellLen, "SHELL");
|
_dupenv_s(&shell, &shellLen, "SHELL");
|
||||||
const std::unique_ptr<char, decltype(&free)> shellGuard(shell, free);
|
const std::unique_ptr<char, decltype(&free)> shellGuard(shell, free);
|
||||||
string shellExe;
|
|
||||||
|
|
||||||
// If SHELL is empty, try LOGINSHELL
|
// If SHELL is empty, try LOGINSHELL
|
||||||
if (!shell || strlen(shell) == 0) {
|
if (!shell || strlen(shell) == 0) {
|
||||||
|
@ -394,6 +396,7 @@ fn GetShell() -> string {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shell) {
|
if (shell) {
|
||||||
|
string shellExe;
|
||||||
const string shellPath = shell;
|
const string shellPath = shell;
|
||||||
const size_t lastSlash = shellPath.find_last_of("\\/");
|
const size_t lastSlash = shellPath.find_last_of("\\/");
|
||||||
shellExe = (lastSlash != string::npos) ? shellPath.substr(lastSlash + 1) : shellPath;
|
shellExe = (lastSlash != string::npos) ? shellPath.substr(lastSlash + 1) : shellPath;
|
||||||
|
@ -463,5 +466,6 @@ fn GetDiskUsage() -> std::pair<u64, u64> {
|
||||||
|
|
||||||
return { 0, 0 };
|
return { 0, 0 };
|
||||||
}
|
}
|
||||||
// NOLINTEND(*-pro-type-cstyle-cast,*-no-int-to-ptr)
|
// NOLINTEND(*-pro-type-cstyle-cast,*-no-int-to-ptr,*-pro-type-reinterpret-cast)
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
// probably stupid but it fixes the issue with windows.h defining ERROR
|
// probably stupid but it fixes the issue with windows.h defining ERROR
|
||||||
#include <utility>
|
|
||||||
#undef ERROR
|
#undef ERROR
|
||||||
|
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
|
|
@ -5,6 +5,8 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
|
#include <expected>
|
||||||
|
// ReSharper disable once CppUnusedIncludeDirective
|
||||||
#include <guiddef.h>
|
#include <guiddef.h>
|
||||||
#include <variant>
|
#include <variant>
|
||||||
#include <winrt/base.h>
|
#include <winrt/base.h>
|
||||||
|
@ -149,13 +151,13 @@ enum class NowPlayingCode : u8 {
|
||||||
* @brief Represents a Linux-specific error.
|
* @brief Represents a Linux-specific error.
|
||||||
*/
|
*/
|
||||||
using LinuxError = std::string;
|
using LinuxError = std::string;
|
||||||
#elifdef __APPLE__
|
#elif defined(__APPLE__)
|
||||||
/**
|
/**
|
||||||
* @typedef MacError
|
* @typedef MacError
|
||||||
* @brief Represents a macOS-specific error.
|
* @brief Represents a macOS-specific error.
|
||||||
*/
|
*/
|
||||||
using MacError = std::string;
|
using MacError = std::string;
|
||||||
#elifdef _WIN32
|
#elif defined(_WIN32)
|
||||||
/**
|
/**
|
||||||
* @typedef WindowsError
|
* @typedef WindowsError
|
||||||
* @brief Represents a Windows-specific error.
|
* @brief Represents a Windows-specific error.
|
||||||
|
@ -168,9 +170,34 @@ using NowPlayingError = std::variant<
|
||||||
NowPlayingCode,
|
NowPlayingCode,
|
||||||
#ifdef __linux__
|
#ifdef __linux__
|
||||||
LinuxError
|
LinuxError
|
||||||
#elifdef __APPLE__
|
#elif defined(__APPLE__)
|
||||||
MacError
|
MacError
|
||||||
#elifdef _WIN32
|
#elif defined(_WIN32)
|
||||||
WindowsError
|
WindowsError
|
||||||
#endif
|
#endif
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
enum class EnvError : u8 { NotFound, AccessError };
|
||||||
|
|
||||||
|
inline auto GetEnv(const std::string& name) -> std::expected<std::string, EnvError> {
|
||||||
|
#ifdef _WIN32
|
||||||
|
char* rawPtr = nullptr;
|
||||||
|
size_t bufferSize = 0;
|
||||||
|
|
||||||
|
if (_dupenv_s(&rawPtr, &bufferSize, name.c_str()) != 0)
|
||||||
|
return std::unexpected(EnvError::AccessError);
|
||||||
|
|
||||||
|
if (!rawPtr)
|
||||||
|
return std::unexpected(EnvError::NotFound);
|
||||||
|
|
||||||
|
const std::string result(rawPtr);
|
||||||
|
free(rawPtr);
|
||||||
|
return result;
|
||||||
|
#else
|
||||||
|
const char* value = std::getenv(name.c_str());
|
||||||
|
if (!value)
|
||||||
|
return std::unexpected(EnvError::NotFound);
|
||||||
|
|
||||||
|
return std::string(value);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit cfa3c5838cc04e2f179faddf8e4757f90fa5fbe7
|
Subproject commit bad0345d6358a649d5f72e90ada2be75d04b75cd
|
Loading…
Add table
Reference in a new issue