This commit is contained in:
Mars 2025-04-29 01:31:35 -04:00
parent 6d9390f02c
commit 33881d007e
7 changed files with 505 additions and 554 deletions

View file

@ -73,7 +73,7 @@ endif
if cpp.get_id() in ['msvc', 'clang-cl'] if cpp.get_id() in ['msvc', 'clang-cl']
common_cpp_args = common_cpp_flags['msvc'] common_cpp_args = common_cpp_flags['msvc']
if cpp.get_id() == 'clang-cl' if cpp.get_id() == 'clang-cl'
common_cpp_args += common_warning_flags + common_cpp_flags['common'] common_cpp_args += common_warning_flags + common_cpp_flags['common'] + ['-fcolor-diagnostics', '-fdiagnostics-format=clang']
endif endif
else else
common_cpp_args = common_warning_flags + common_cpp_flags['common'] + common_cpp_flags['unix_extra'] common_cpp_args = common_warning_flags + common_cpp_flags['common'] + common_cpp_flags['unix_extra']

View file

@ -7,12 +7,36 @@
#include <toml++/impl/parser.hpp> // toml::{parse_file, parse_result} #include <toml++/impl/parser.hpp> // toml::{parse_file, parse_result}
#include <utility> // std::pair (Pair) #include <utility> // std::pair (Pair)
#include "src/core/util/helpers.hpp"
#include "src/core/util/logging.hpp" #include "src/core/util/logging.hpp"
namespace fs = std::filesystem; namespace fs = std::filesystem;
namespace { namespace {
using util::types::Vec, util::types::CStr, util::types::Exception; using util::types::Vec, util::types::CStr, util::types::Exception;
constexpr const char* defaultConfigTemplate = R"cfg(# Draconis++ Configuration File
# General settings
[general]
name = "{}" # Your display name
# Now Playing integration
[now_playing]
enabled = false # Set to true to enable media integration
# Weather settings
[weather]
enabled = false # Set to true to enable weather display
show_town_name = false # Show location name in weather display
api_key = "" # Your weather API key
units = "metric" # Use "metric" for °C or "imperial" for °F
location = "London" # Your city name
# Alternatively, you can specify coordinates instead of a city name:
# [weather.location]
# lat = 51.5074
# lon = -0.1278
)cfg";
fn GetConfigPath() -> fs::path { fn GetConfigPath() -> fs::path {
using util::helpers::GetEnv; using util::helpers::GetEnv;
@ -30,8 +54,6 @@ namespace {
if (auto result = GetEnv("APPDATA")) if (auto result = GetEnv("APPDATA"))
possiblePaths.push_back(fs::path(*result) / "draconis++" / "config.toml"); possiblePaths.push_back(fs::path(*result) / "draconis++" / "config.toml");
possiblePaths.push_back(fs::path(".") / "config.toml");
#else #else
if (Result<String, DraconisError> result = GetEnv("XDG_CONFIG_HOME")) if (Result<String, DraconisError> result = GetEnv("XDG_CONFIG_HOME"))
possiblePaths.emplace_back(fs::path(*result) / "draconis++" / "config.toml"); possiblePaths.emplace_back(fs::path(*result) / "draconis++" / "config.toml");
@ -40,18 +62,18 @@ namespace {
possiblePaths.emplace_back(fs::path(*result) / ".config" / "draconis++" / "config.toml"); possiblePaths.emplace_back(fs::path(*result) / ".config" / "draconis++" / "config.toml");
possiblePaths.emplace_back(fs::path(*result) / ".draconis++" / "config.toml"); possiblePaths.emplace_back(fs::path(*result) / ".draconis++" / "config.toml");
} }
possiblePaths.emplace_back("/etc/draconis++/config.toml");
#endif #endif
possiblePaths.push_back(fs::path(".") / "config.toml");
for (const fs::path& path : possiblePaths) for (const fs::path& path : possiblePaths)
if (std::error_code errc; exists(path, errc) && !errc) if (std::error_code errc; fs::exists(path, errc) && !errc)
return path; return path;
if (!possiblePaths.empty()) { if (!possiblePaths.empty()) {
const fs::path defaultDir = possiblePaths[0].parent_path(); const fs::path defaultDir = possiblePaths[0].parent_path();
if (std::error_code errc; !exists(defaultDir, errc) && !errc) { if (std::error_code errc; !fs::exists(defaultDir, errc) || !errc) {
create_directories(defaultDir, errc); create_directories(defaultDir, errc);
if (errc) if (errc)
warn_log("Warning: Failed to create config directory: {}", errc.message()); warn_log("Warning: Failed to create config directory: {}", errc.message());
@ -60,26 +82,33 @@ namespace {
return possiblePaths[0]; return possiblePaths[0];
} }
throw std::runtime_error("Could not determine a valid config path"); warn_log("Could not determine a preferred config path. Falling back to './config.toml'");
return fs::path(".") / "config.toml";
} }
fn CreateDefaultConfig(const fs::path& configPath) -> bool { fn CreateDefaultConfig(const fs::path& configPath) -> bool {
try { try {
std::error_code errc; std::error_code errc;
create_directories(configPath.parent_path(), errc); create_directories(configPath.parent_path(), errc);
if (errc) { if (errc) {
error_log("Failed to create config directory: {}", errc.message()); error_log("Failed to create config directory: {}", errc.message());
return false; return false;
} }
toml::table root; String defaultName;
#ifdef _WIN32 #ifdef _WIN32
Array<char, 256> username; Array<char, 256> username;
DWORD size = sizeof(username); DWORD size = sizeof(username);
String defaultName = GetUserNameA(username.data(), &size) ? username.data() : "User"; if (GetUserNameA(username.data(), &size)) {
defaultName = username.data();
} else {
debug_log("Failed to get username: {}", GetLastError());
defaultName = "User";
}
#else #else
const passwd* pwd = getpwuid(getuid()); const passwd* pwd = getpwuid(getuid());
CStr pwdName = pwd ? pwd->pw_name : nullptr; CStr pwdName = pwd ? pwd->pw_name : nullptr;
@ -87,79 +116,91 @@ namespace {
const Result<String, DraconisError> envUser = util::helpers::GetEnv("USER"); const Result<String, DraconisError> envUser = util::helpers::GetEnv("USER");
const Result<String, DraconisError> envLogname = util::helpers::GetEnv("LOGNAME"); const Result<String, DraconisError> envLogname = util::helpers::GetEnv("LOGNAME");
String defaultName = pwdName ? pwdName : envUser ? *envUser : envLogname ? *envLogname : "User"; defaultName = pwdName ? pwdName : envUser ? *envUser : envLogname ? *envLogname : "User";
#endif #endif
toml::table* general = root.insert("general", toml::table {}).first->second.as_table();
general->insert("name", defaultName);
toml::table* nowPlaying = root.insert("now_playing", toml::table {}).first->second.as_table();
nowPlaying->insert("enabled", false);
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");
std::ofstream file(configPath); std::ofstream file(configPath);
if (!file) { if (!file) {
error_log("Failed to open config file for writing: {}", configPath.string()); error_log("Failed to open config file for writing: {}", configPath.string());
return false; return false;
} }
file << "# Draconis++ Configuration File\n\n"; try {
const std::string formattedConfig = std::format(defaultConfigTemplate, defaultName);
file << formattedConfig;
} catch (const std::format_error& fmt_err) {
error_log("Failed to format default config string: {}. Using fallback name 'User'.", fmt_err.what());
file << "# General settings\n"; try {
file << "[general]\n"; const std::string fallbackConfig = std::format(defaultConfigTemplate, "User");
file << "name = \"" << defaultName << "\" # Your display name\n\n"; file << fallbackConfig;
} catch (...) {
error_log("Failed to format default config even with fallback name.");
return false;
}
}
file << "# Now Playing integration\n"; if (!file) {
file << "[now_playing]\n"; error_log("Failed to write to config file: {}", configPath.string());
file << "enabled = false # Set to true to enable media integration\n\n"; return false;
}
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()); info_log("Created default config file at {}", configPath.string());
return true; return true;
} catch (const fs::filesystem_error& fs_err) {
error_log("Filesystem error during default config creation: {}", fs_err.what());
return false;
} catch (const Exception& e) { } catch (const Exception& e) {
error_log("Failed to create default config file: {}", e.what()); error_log("Failed to create default config file: {}", e.what());
return false; return false;
} catch (...) {
error_log("An unexpected error occurred during default config creation.");
return false;
} }
} }
} // namespace } // namespace
Config::Config(const toml::table& tbl) {
const toml::node_view genTbl = tbl["general"];
const toml::node_view npTbl = tbl["now_playing"];
const toml::node_view wthTbl = tbl["weather"];
this->general = genTbl.is_table() ? General::fromToml(*genTbl.as_table()) : General {};
this->now_playing = npTbl.is_table() ? NowPlaying::fromToml(*npTbl.as_table()) : NowPlaying {};
this->weather = wthTbl.is_table() ? Weather::fromToml(*wthTbl.as_table()) : Weather {};
}
fn Config::getInstance() -> Config { fn Config::getInstance() -> Config {
try { try {
const fs::path configPath = GetConfigPath(); const fs::path configPath = GetConfigPath();
if (!exists(configPath)) { std::error_code ec;
info_log("Config file not found, creating defaults at {}", configPath.string());
const bool exists = fs::exists(configPath, ec);
if (ec)
warn_log(
"Failed to check if config file exists at {}: {}. Assuming it doesn't.", configPath.string(), ec.message()
);
if (!exists) {
info_log("Config file not found at {}, creating defaults.", configPath.string());
if (!CreateDefaultConfig(configPath)) { if (!CreateDefaultConfig(configPath)) {
warn_log("Failed to create default config, using in-memory defaults"); warn_log("Failed to create default config file at {}. Using in-memory defaults.", configPath.string());
return {}; return {};
} }
} }
const toml::parse_result config = toml::parse_file(configPath.string()); const toml::table config = toml::parse_file(configPath.string());
debug_log("Config loaded from {}", configPath.string()); debug_log("Config loaded from {}", configPath.string());
return fromToml(config); return Config(config);
} catch (const Exception& e) { } catch (const Exception& e) {
debug_log("Config loading failed: {}, using defaults", e.what()); debug_log("Config loading failed: {}, using defaults", e.what());
return {}; return {};
} catch (...) {
error_log("An unexpected error occurred during config loading. Using in-memory defaults.");
return {};
} }
} }

View file

@ -16,7 +16,7 @@
#include "src/core/util/defs.hpp" #include "src/core/util/defs.hpp"
#include "src/core/util/error.hpp" #include "src/core/util/error.hpp"
#include "src/core/util/helpers.hpp" #include "src/core/util/logging.hpp"
#include "src/core/util/types.hpp" #include "src/core/util/types.hpp"
#include "weather.hpp" #include "weather.hpp"
@ -132,8 +132,10 @@ struct Weather {
.lat = *location.as_table()->get("lat")->value<double>(), .lat = *location.as_table()->get("lat")->value<double>(),
.lon = *location.as_table()->get("lon")->value<double>(), .lon = *location.as_table()->get("lon")->value<double>(),
}; };
else else {
throw std::runtime_error("Invalid location type"); error_log("Invalid location format in config.");
weather.enabled = false;
}
} }
return weather; return weather;
@ -159,22 +161,9 @@ struct Config {
Weather weather; ///< Weather configuration settings.` Weather weather; ///< Weather configuration settings.`
NowPlaying now_playing; ///< Now Playing configuration settings. NowPlaying now_playing; ///< Now Playing configuration settings.
/** Config() = default;
* @brief Parses a TOML table to create a Config instance.
* @param tbl The TOML table to parse, containing [general], [now_playing], and [weather].
* @return A Config instance with the parsed values, or defaults otherwise.
*/
static fn fromToml(const toml::table& tbl) -> Config {
const toml::node_view genTbl = tbl["general"];
const toml::node_view npTbl = tbl["now_playing"];
const toml::node_view wthTbl = tbl["weather"];
return { explicit Config(const toml::table& tbl);
.general = genTbl.is_table() ? General::fromToml(*genTbl.as_table()) : General {},
.weather = wthTbl.is_table() ? Weather::fromToml(*wthTbl.as_table()) : Weather {},
.now_playing = npTbl.is_table() ? NowPlaying::fromToml(*npTbl.as_table()) : NowPlaying {},
};
}
/** /**
* @brief Retrieves the path to the configuration file. * @brief Retrieves the path to the configuration file.

View file

@ -26,52 +26,59 @@ namespace {
return std::format(std::locale::classic(), "{:%B %d}", ymd); return std::format(std::locale::classic(), "{:%B %d}", ymd);
} }
} }
fn log_timing(const std::string& name, const std::chrono::steady_clock::duration& duration) -> void {
const auto millis = std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(duration);
debug_log("{} took: {} ms", name, millis.count());
};
template <typename Func>
fn time_execution(const std::string& name, Func&& func) {
const auto start = std::chrono::steady_clock::now();
if constexpr (std::is_void_v<decltype(func())>) {
func();
const auto end = std::chrono::steady_clock::now();
log_timing(name, end - start);
} else {
auto result = func();
const auto end = std::chrono::steady_clock::now();
log_timing(name, end - start);
return result;
}
}
} // namespace } // namespace
fn SystemData::fetchSystemData(const Config& config) -> SystemData { fn SystemData::fetchSystemData(const Config& config) -> SystemData {
using util::types::None; using util::types::None, util::types::Exception;
using namespace os;
SystemData data { SystemData data {
.date = GetDate(), .date = time_execution("GetDate", GetDate),
.host = os::GetHost(), .host = time_execution("GetHost", GetHost),
.kernel_version = os::GetKernelVersion(), .kernel_version = time_execution("GetKernelVersion", GetKernelVersion),
.os_version = os::GetOSVersion(), .os_version = time_execution("GetOSVersion", GetOSVersion),
.mem_info = os::GetMemInfo(), .mem_info = time_execution("GetMemInfo", GetMemInfo),
.desktop_environment = os::GetDesktopEnvironment(), .desktop_environment = time_execution("GetDesktopEnvironment", GetDesktopEnvironment),
.window_manager = os::GetWindowManager(), .window_manager = time_execution("GetWindowManager", GetWindowManager),
.disk_usage = {}, .disk_usage = time_execution("GetDiskUsage", GetDiskUsage),
.shell = None, .shell = time_execution("GetShell", GetShell),
.now_playing = None,
.weather_info = None,
}; };
auto diskShellFuture = std::async(std::launch::async, [] { if (const Result<MediaInfo, DraconisError>& nowPlayingResult = time_execution("GetNowPlaying", os::GetNowPlaying)) {
Result<DiskSpace, DraconisError> diskResult = os::GetDiskUsage(); data.now_playing = nowPlayingResult;
Option<String> shellOption = os::GetShell(); } else {
return std::make_tuple(std::move(diskResult), std::move(shellOption)); data.now_playing = None;
}); }
std::future<weather::Output> weatherFuture; const auto start = std::chrono::steady_clock::now();
std::future<Result<MediaInfo, DraconisError>> nowPlayingFuture; data.weather_info = config.weather.getWeatherInfo();
const auto end = std::chrono::steady_clock::now();
if (config.weather.enabled) log_timing("config.weather.getWeatherInfo", end - start);
weatherFuture = std::async(std::launch::async, [&config] { return config.weather.getWeatherInfo(); });
if (config.now_playing.enabled)
nowPlayingFuture = std::async(std::launch::async, os::GetNowPlaying);
auto [diskResult, shellOption] = diskShellFuture.get();
data.disk_usage = std::move(diskResult);
data.shell = std::move(shellOption);
if (weatherFuture.valid())
try {
data.weather_info = weatherFuture.get();
} catch (const std::exception& e) { data.weather_info = None; }
if (nowPlayingFuture.valid())
data.now_playing = nowPlayingFuture.get();
return data; return data;
} }

View file

@ -3,189 +3,135 @@
#include <chrono> // std::chrono::{days, floor, seconds, system_clock} #include <chrono> // std::chrono::{days, floor, seconds, system_clock}
#include <filesystem> // std::filesystem::path #include <filesystem> // std::filesystem::path
#include <format> // std::format #include <format> // std::format
#include <ftxui/screen/color.hpp> // ftxui::Color
#include <mutex> // std::mutex
#include <print> // std::print #include <print> // std::print
#include <source_location> // std::source_location #include <source_location> // std::source_location
#include <utility> // std::{forward, make_pair} #include <utility> // std::forward
#include "src/core/util/defs.hpp" #include "src/core/util/defs.hpp"
#include "src/core/util/error.hpp" #include "src/core/util/error.hpp"
#include "src/core/util/types.hpp" #include "src/core/util/types.hpp"
namespace util::logging { namespace util::logging {
using types::u8, types::i32, types::String, types::StringView, types::Option, types::None; using types::usize, types::u8, types::i32, types::i64, types::CStr, types::String, types::StringView, types::Array,
types::Option, types::None;
/** // Store all compile-time constants in a struct
* @namespace term struct LogLevelConst {
* @brief Provides terminal-related utilities, including color and style formatting. // ANSI color codes
*/ // clang-format off
namespace term { static constexpr Array<const char*, 16> COLOR_CODE_LITERALS = {
/** "\033[38;5;0m", "\033[38;5;1m", "\033[38;5;2m", "\033[38;5;3m",
* @enum Emphasis "\033[38;5;4m", "\033[38;5;5m", "\033[38;5;6m", "\033[38;5;7m",
* @brief Represents text emphasis styles. "\033[38;5;8m", "\033[38;5;9m", "\033[38;5;10m", "\033[38;5;11m",
* "\033[38;5;12m", "\033[38;5;13m", "\033[38;5;14m", "\033[38;5;15m",
* Enum values can be combined using bitwise OR to apply multiple styles at once.
*/
enum class Emphasis : u8 {
Bold, ///< Bold text.
Italic ///< Italic text.
}; };
// clang-format on
/** // ANSI formatting constants
* @enum Color static constexpr const char* RESET_CODE = "\033[0m";
* @brief Represents ANSI color codes for terminal output. static constexpr const char* BOLD_START = "\033[1m";
* static constexpr const char* BOLD_END = "\033[22m";
* Color codes can be used to format terminal output. static constexpr const char* ITALIC_START = "\033[3m";
*/ static constexpr const char* ITALIC_END = "\033[23m";
enum class Color : u8 {
Black = 30, ///< Black color. // Log level text constants
Red = 31, ///< Red color. static constexpr StringView DEBUG_STR = "DEBUG";
Green = 32, ///< Green color. static constexpr StringView INFO_STR = "INFO ";
Yellow = 33, ///< Yellow color. static constexpr StringView WARN_STR = "WARN ";
Blue = 34, ///< Blue color. static constexpr StringView ERROR_STR = "ERROR";
Magenta = 35, ///< Magenta color.
Cyan = 36, ///< Cyan color. // Log level color constants
White = 37, ///< White color. static constexpr ftxui::Color::Palette16 DEBUG_COLOR = ftxui::Color::Palette16::Cyan;
BrightBlack = 90, ///< Bright black (gray) color. static constexpr ftxui::Color::Palette16 INFO_COLOR = ftxui::Color::Palette16::Green;
BrightRed = 91, ///< Bright red color. static constexpr ftxui::Color::Palette16 WARN_COLOR = ftxui::Color::Palette16::Yellow;
BrightGreen = 92, ///< Bright green color. static constexpr ftxui::Color::Palette16 ERROR_COLOR = ftxui::Color::Palette16::Red;
BrightYellow = 93, ///< Bright yellow color. static constexpr ftxui::Color::Palette16 DEBUG_INFO_COLOR = ftxui::Color::Palette16::GrayLight;
BrightBlue = 94, ///< Bright blue color.
BrightMagenta = 95, ///< Bright magenta color. static constexpr CStr TIMESTAMP_FORMAT = "{:%X}";
BrightCyan = 96, ///< Bright cyan color. static constexpr CStr LOG_FORMAT = "{} {} {}";
BrightWhite = 97, ///< Bright white color.
#ifndef NDEBUG
static constexpr CStr DEBUG_INFO_FORMAT = "{}{}{}\n";
static constexpr CStr FILE_LINE_FORMAT = "{}:{}";
static constexpr CStr DEBUG_LINE_PREFIX = " ╰── ";
#endif
}; };
/**
* @brief Combines two emphasis styles using bitwise OR.
* @param emphA The first emphasis style.
* @param emphB The second emphasis style.
* @return The combined emphasis style.
*/
constexpr fn operator|(Emphasis emphA, Emphasis emphB)->Emphasis {
return static_cast<Emphasis>(static_cast<u8>(emphA) | static_cast<u8>(emphB));
}
/**
* @brief Checks if two emphasis styles are equal using bitwise AND.
* @param emphA The first emphasis style.
* @param emphB The second emphasis style.
* @return The result of the bitwise AND operation.
*/
constexpr fn operator&(Emphasis emphA, Emphasis emphB)->u8 {
return static_cast<u8>(emphA) & static_cast<u8>(emphB);
}
/**
* @struct Style
* @brief Represents a combination of text styles.
*
* Emphasis and color are both optional, allowing for flexible styling.
*/
struct Style {
Option<Emphasis> emph; ///< Optional emphasis style.
Option<Color> fg_col; ///< Optional foreground color style.
/**
* @brief Generates the ANSI escape code for the combined styles.
* @return The ANSI escape code for the combined styles.
*/
[[nodiscard]] fn ansiCode() const -> String {
String result;
if (emph) {
if ((*emph & Emphasis::Bold) != 0)
result += "\033[1m";
if ((*emph & Emphasis::Italic) != 0)
result += "\033[3m";
}
if (fg_col)
result += std::format("\033[{}m", static_cast<u8>(*fg_col));
return result;
}
};
/**
* @brief Combines an emphasis style and a foreground color into a Style.
* @param emph The emphasis style to apply.
* @param fgColor The foreground color to apply.
* @return The combined style.
*/
// ReSharper disable CppDFAConstantParameter
constexpr fn operator|(const Emphasis emph, const Color fgColor)->Style {
return { .emph = emph, .fg_col = fgColor };
}
// ReSharper restore CppDFAConstantParameter
/**
* @brief Combines a foreground color and an emphasis style into a Style.
* @param fgColor The foreground color to apply.
* @param emph The emphasis style to apply.
* @return The combined style.
*/
constexpr fn operator|(const Color fgColor, const Emphasis emph)->Style {
return { .emph = emph, .fg_col = fgColor };
}
/**
* @brief Prints formatted text with the specified style.
* @tparam Args Parameter pack for format arguments.
* @param style The Style object containing emphasis and/or color.
* @param fmt The format string.
* @param args The arguments for the format string.
*/
template <typename... Args>
fn Print(const Style& style, std::format_string<Args...> fmt, Args&&... args) -> void {
if (const String styleCode = style.ansiCode(); styleCode.empty())
std::print(fmt, std::forward<Args>(args)...);
else
std::print("{}{}{}", styleCode, std::format(fmt, std::forward<Args>(args)...), "\033[0m");
}
/**
* @brief Prints formatted text with the specified foreground color.
* @tparam Args Parameter pack for format arguments.
* @param fgColor The foreground color to apply.
* @param fmt The format string.
* @param args The arguments for the format string.
*/
template <typename... Args>
fn Print(const Color& fgColor, std::format_string<Args...> fmt, Args&&... args) -> void {
Print({ .emph = None, .fg_col = fgColor }, fmt, std::forward<Args>(args)...);
}
/**
* @brief Prints formatted text with the specified emphasis style.
* @tparam Args Parameter pack for format arguments.
* @param emph The emphasis style to apply.
* @param fmt The format string.
* @param args The arguments for the format string.
*/
template <typename... Args>
fn Print(const Emphasis emph, std::format_string<Args...> fmt, Args&&... args) -> void {
Print({ .emph = emph, .fg_col = None }, fmt, std::forward<Args>(args)...);
}
/**
* @brief Prints formatted text with no specific style (default terminal style).
* @tparam Args Parameter pack for format arguments.
* @param fmt The format string.
* @param args The arguments for the format string.
*/
template <typename... Args>
fn Print(std::format_string<Args...> fmt, Args&&... args) -> void {
// Directly use std::print for unstyled output
std::print(fmt, std::forward<Args>(args)...);
}
} // namespace term
/** /**
* @enum LogLevel * @enum LogLevel
* @brief Represents different log levels. * @brief Represents different log levels.
*/ */
enum class LogLevel : u8 { Debug, Info, Warn, Error }; enum class LogLevel : u8 { Debug, Info, Warn, Error };
/**
* @brief Directly applies ANSI color codes to text
* @param text The text to colorize
* @param color The FTXUI color
* @return Styled string with ANSI codes
*/
inline fn Colorize(const String& text, const ftxui::Color::Palette16& color) -> String {
return String(LogLevelConst::COLOR_CODE_LITERALS[static_cast<int>(color)]) + text + LogLevelConst::RESET_CODE;
}
/**
* @brief Make text bold with ANSI codes
* @param text The text to make bold
* @return Bold text
*/
inline fn Bold(const StringView text) -> String {
return String(LogLevelConst::BOLD_START) + String(text) + String(LogLevelConst::BOLD_END);
}
/**
* @brief Make text italic with ANSI codes
* @param text The text to make italic
* @return Italic text
*/
inline fn Italic(const StringView text) -> String {
return String(LogLevelConst::ITALIC_START) + String(text) + String(LogLevelConst::ITALIC_END);
}
// Initialize at runtime using the constexpr static values
// This can't be constexpr itself due to the string operations
inline const Array<String, 4> LEVEL_INFO = {
Bold(Colorize(LogLevelConst::DEBUG_STR.data(), LogLevelConst::DEBUG_COLOR)),
Bold(Colorize(LogLevelConst::INFO_STR.data(), LogLevelConst::INFO_COLOR)),
Bold(Colorize(LogLevelConst::WARN_STR.data(), LogLevelConst::WARN_COLOR)),
Bold(Colorize(LogLevelConst::ERROR_STR.data(), LogLevelConst::ERROR_COLOR)),
};
/**
* @brief Returns FTXUI color representation for a log level
* @param level The log level
* @return FTXUI color code
*/
constexpr fn GetLevelColor(const LogLevel level) -> ftxui::Color::Palette16 {
switch (level) {
case LogLevel::Debug: return LogLevelConst::DEBUG_COLOR;
case LogLevel::Info: return LogLevelConst::INFO_COLOR;
case LogLevel::Warn: return LogLevelConst::WARN_COLOR;
case LogLevel::Error: return LogLevelConst::ERROR_COLOR;
default: std::unreachable();
}
}
/**
* @brief Returns string representation of a log level
* @param level The log level
* @return String representation
*/
constexpr fn GetLevelString(const LogLevel level) -> String {
switch (level) {
case LogLevel::Debug: return LogLevelConst::DEBUG_STR.data();
case LogLevel::Info: return LogLevelConst::INFO_STR.data();
case LogLevel::Warn: return LogLevelConst::WARN_STR.data();
case LogLevel::Error: return LogLevelConst::ERROR_STR.data();
default: std::unreachable();
}
}
/** /**
* @brief Logs a message with the specified log level, source location, and format string. * @brief Logs a message with the specified log level, source location, and format string.
* @tparam Args Parameter pack for format arguments. * @tparam Args Parameter pack for format arguments.
@ -197,37 +143,45 @@ namespace util::logging {
template <typename... Args> template <typename... Args>
fn LogImpl(const LogLevel level, const std::source_location& loc, std::format_string<Args...> fmt, Args&&... args) { fn LogImpl(const LogLevel level, const std::source_location& loc, std::format_string<Args...> fmt, Args&&... args) {
using namespace std::chrono; using namespace std::chrono;
using namespace term; using std::filesystem::path;
const auto [color, levelStr] = [&] { using Buffer = Array<char, 512>;
switch (level) {
case LogLevel::Debug: return std::make_pair(Color::Cyan, "DEBUG");
case LogLevel::Info: return std::make_pair(Color::Green, "INFO ");
case LogLevel::Warn: return std::make_pair(Color::Yellow, "WARN ");
case LogLevel::Error: return std::make_pair(Color::Red, "ERROR");
default: std::unreachable();
}
}();
Print(Color::BrightWhite, "[{:%X}] ", std::chrono::floor<seconds>(system_clock::now())); // Dynamic parts (runtime)
Print(Emphasis::Bold | color, "{} ", levelStr); const time_point<system_clock, duration<i64>> now = floor<seconds>(system_clock::now());
Print(fmt, std::forward<Args>(args)...);
const String timestamp = std::format(LogLevelConst::TIMESTAMP_FORMAT, now);
const String message = std::format(fmt, std::forward<Args>(args)...);
Buffer buffer {};
Buffer::iterator iter = std::format_to(
buffer.begin(),
LogLevelConst::LOG_FORMAT,
Colorize("[" + timestamp + "]", LogLevelConst::DEBUG_INFO_COLOR),
LEVEL_INFO[static_cast<usize>(level)],
message
);
#ifndef NDEBUG #ifndef NDEBUG
Print(Color::BrightWhite, "\n{:>14} ", "╰──"); iter = std::format_to(
Print( iter,
Emphasis::Italic | Color::BrightWhite, "\n{}",
"{}:{}", Italic(Colorize(
std::filesystem::path(loc.file_name()).lexically_normal().string(), LogLevelConst::DEBUG_LINE_PREFIX +
loc.line() std::format("{}:{}", path(loc.file_name()).lexically_normal().string(), std::to_string(loc.line())),
LogLevelConst::DEBUG_INFO_COLOR
))
); );
#endif // !NDEBUG #endif
Print("\n"); const usize length = std::distance(buffer.begin(), iter);
std::println("{}", std::string_view(buffer.data(), length));
} }
template <typename ErrorType> template <typename ErrorType>
fn LogAppError(const LogLevel level, const ErrorType& error_obj) { fn LogError(const LogLevel level, const ErrorType& error_obj) {
using DecayedErrorType = std::decay_t<ErrorType>; using DecayedErrorType = std::decay_t<ErrorType>;
std::source_location logLocation; std::source_location logLocation;
@ -254,7 +208,7 @@ namespace util::logging {
::util::logging::LogImpl( \ ::util::logging::LogImpl( \
::util::logging::LogLevel::Debug, std::source_location::current(), fmt __VA_OPT__(, ) __VA_ARGS__ \ ::util::logging::LogLevel::Debug, std::source_location::current(), fmt __VA_OPT__(, ) __VA_ARGS__ \
) )
#define debug_at(error_obj) ::util::logging::LogAppError(::util::logging::LogLevel::Debug, error_obj); #define debug_at(error_obj) ::util::logging::LogError(::util::logging::LogLevel::Debug, error_obj);
#else #else
#define debug_log(...) ((void)0) #define debug_log(...) ((void)0)
#define debug_at(...) ((void)0) #define debug_at(...) ((void)0)
@ -264,17 +218,17 @@ namespace util::logging {
::util::logging::LogImpl( \ ::util::logging::LogImpl( \
::util::logging::LogLevel::Info, std::source_location::current(), fmt __VA_OPT__(, ) __VA_ARGS__ \ ::util::logging::LogLevel::Info, std::source_location::current(), fmt __VA_OPT__(, ) __VA_ARGS__ \
) )
#define info_at(error_obj) ::util::logging::LogAppError(::util::logging::LogLevel::Info, error_obj); #define info_at(error_obj) ::util::logging::LogError(::util::logging::LogLevel::Info, error_obj);
#define warn_log(fmt, ...) \ #define warn_log(fmt, ...) \
::util::logging::LogImpl( \ ::util::logging::LogImpl( \
::util::logging::LogLevel::Warn, std::source_location::current(), fmt __VA_OPT__(, ) __VA_ARGS__ \ ::util::logging::LogLevel::Warn, std::source_location::current(), fmt __VA_OPT__(, ) __VA_ARGS__ \
) )
#define warn_at(error_obj) ::util::logging::LogAppError(::util::logging::LogLevel::Warn, error_obj); #define warn_at(error_obj) ::util::logging::LogError(::util::logging::LogLevel::Warn, error_obj);
#define error_log(fmt, ...) \ #define error_log(fmt, ...) \
::util::logging::LogImpl( \ ::util::logging::LogImpl( \
::util::logging::LogLevel::Error, std::source_location::current(), fmt __VA_OPT__(, ) __VA_ARGS__ \ ::util::logging::LogLevel::Error, std::source_location::current(), fmt __VA_OPT__(, ) __VA_ARGS__ \
) )
#define error_at(error_obj) ::util::logging::LogAppError(::util::logging::LogLevel::Error, error_obj); #define error_at(error_obj) ::util::logging::LogError(::util::logging::LogLevel::Error, error_obj);
} // namespace util::logging } // namespace util::logging

View file

@ -20,8 +20,6 @@ namespace ui {
using ftxui::Color; using ftxui::Color;
using util::types::StringView, util::types::i32; using util::types::StringView, util::types::i32;
static constexpr inline StringView ICON_TYPE = "EMOJI";
static constexpr i32 MAX_PARAGRAPH_LENGTH = 30; static constexpr i32 MAX_PARAGRAPH_LENGTH = 30;
// Color themes // Color themes
@ -55,7 +53,7 @@ namespace ui {
StringView window_manager; StringView window_manager;
}; };
static constexpr Icons NONE = { [[maybe_unused]] static constexpr Icons NONE = {
.user = "", .user = "",
.palette = "", .palette = "",
.calendar = "", .calendar = "",
@ -71,7 +69,7 @@ namespace ui {
.window_manager = "", .window_manager = "",
}; };
static constexpr Icons NERD = { [[maybe_unused]] static constexpr Icons NERD = {
.user = "", .user = "",
.palette = "", .palette = "",
.calendar = "", .calendar = "",
@ -87,7 +85,7 @@ namespace ui {
.window_manager = "  ", .window_manager = "  ",
}; };
static constexpr Icons EMOJI = { [[maybe_unused]] static constexpr Icons EMOJI = {
.user = " 👤 ", .user = " 👤 ",
.palette = " 🎨 ", .palette = " 🎨 ",
.calendar = " 📅 ", .calendar = " 📅 ",
@ -102,6 +100,8 @@ namespace ui {
.desktop = " 🖥️ ", .desktop = " 🖥️ ",
.window_manager = " 🪟 ", .window_manager = " 🪟 ",
}; };
static constexpr inline Icons ICON_TYPE = NERD;
} // namespace ui } // namespace ui
namespace { namespace {
@ -122,9 +122,7 @@ namespace {
const Weather weather = config.weather; const Weather weather = config.weather;
const auto& [userIcon, paletteIcon, calendarIcon, hostIcon, kernelIcon, osIcon, memoryIcon, weatherIcon, musicIcon, diskIcon, shellIcon, deIcon, wmIcon] = const auto& [userIcon, paletteIcon, calendarIcon, hostIcon, kernelIcon, osIcon, memoryIcon, weatherIcon, musicIcon, diskIcon, shellIcon, deIcon, wmIcon] =
ui::ICON_TYPE == "NERD" ? ui::NERD ui::ICON_TYPE;
: ui::ICON_TYPE == "EMOJI" ? ui::EMOJI
: ui::NONE;
Elements content; Elements content;
@ -264,6 +262,10 @@ namespace {
} // namespace } // namespace
fn main() -> i32 { fn main() -> i32 {
#ifdef _WIN32
winrt::init_apartment();
#endif
const Config& config = Config::getInstance(); const Config& config = Config::getInstance();
const SystemData data = SystemData::fetchSystemData(config); const SystemData data = SystemData::fetchSystemData(config);

View file

@ -5,14 +5,14 @@
#include <windows.h> #include <windows.h>
#include <wincrypt.h> #include <wincrypt.h>
#include <dwmapi.h> #include <dwmapi.h>
#include <tlhelp32.h>
#include <cstring>
#include <ranges> #include <ranges>
#include <winrt/Windows.Foundation.Collections.h> #include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.Media.Control.h> #include <winrt/Windows.Media.Control.h>
#include <winrt/Windows.Storage.h> #include <winrt/Windows.Storage.h>
#include <winrt/Windows.System.Diagnostics.h>
#include <winrt/Windows.System.Profile.h> #include <winrt/Windows.System.Profile.h>
#include <winrt/Windows.Management.Deployment.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>
@ -24,33 +24,15 @@
#include "os.hpp" #include "os.hpp"
// clang-format on // clang-format on
using RtlGetVersionPtr = NTSTATUS(WINAPI*)(PRTL_OSVERSIONINFOW);
namespace { namespace {
using util::error::DraconisError, util::error::DraconisErrorCode; using util::error::DraconisError, util::error::DraconisErrorCode;
using namespace util::types; using namespace util::types;
struct OSVersion { struct ProcessData {
u16 major; DWORD parentPid = 0;
u16 minor; String baseExeNameLower;
u16 build;
u16 revision;
static fn parseDeviceFamilyVersion(const winrt::hstring& versionString) -> OSVersion {
try {
const u64 versionUl = std::stoull(winrt::to_string(versionString));
return {
.major = static_cast<u16>((versionUl >> 48) & 0xFFFF),
.minor = static_cast<u16>((versionUl >> 32) & 0xFFFF),
.build = static_cast<u16>((versionUl >> 16) & 0xFFFF),
.revision = static_cast<u16>(versionUl & 0xFFFF),
};
} catch (const std::invalid_argument& e) {
error_log("Invalid argument: {}", e.what());
} catch (const std::out_of_range& e) {
error_log("Value out of range: {}", e.what());
} catch (const winrt::hresult_error& e) { error_log("Windows error: {}", winrt::to_string(e.message())); }
return { .major = 0, .minor = 0, .build = 0, .revision = 0 };
}
}; };
// clang-format off // clang-format off
@ -91,73 +73,75 @@ namespace {
return value; return value;
} }
fn GetProcessInfo() -> Result<Vec<Pair<DWORD, String>>, DraconisError> {
try {
using namespace winrt::Windows::System::Diagnostics;
using namespace winrt::Windows::Foundation::Collections;
const IVectorView<ProcessDiagnosticInfo> processInfos = ProcessDiagnosticInfo::GetForProcesses();
Vec<Pair<DWORD, String>> processes;
processes.reserve(processInfos.Size());
for (const auto& processInfo : processInfos)
processes.emplace_back(processInfo.ProcessId(), winrt::to_string(processInfo.ExecutableFileName()));
return processes;
} catch (const winrt::hresult_error& e) { return Err(DraconisError(e)); } catch (const std::exception& e) {
return Err(DraconisError(e));
}
}
fn IsProcessRunning(const Vec<String>& processNames, const String& name) -> bool {
return std::ranges::any_of(processNames, [&name](const String& proc) -> bool {
return _stricmp(proc.c_str(), name.c_str()) == 0;
});
}
template <usize sz> template <usize sz>
fn FindShellInProcessTree(const DWORD startPid, const Array<Pair<StringView, StringView>, sz>& shellMap) fn FindShellInProcessTree(const DWORD startPid, const Array<Pair<StringView, StringView>, sz>& shellMap)
-> Option<String> { -> Option<String> {
if (startPid == 0) if (startPid == 0)
return None; return None;
try { std::unordered_map<DWORD, ProcessData> processMap;
using namespace winrt::Windows::System::Diagnostics;
ProcessDiagnosticInfo currentProcessInfo = nullptr; const HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
try { if (hSnap == INVALID_HANDLE_VALUE) {
currentProcessInfo = ProcessDiagnosticInfo::TryGetForProcessId(startPid); error_log("FindShellInProcessTree: Failed snapshot, error {}", GetLastError());
} catch (const winrt::hresult_error& e) {
error_log("Failed to get process info for PID {}: {}", startPid, winrt::to_string(e.message()));
return None; return None;
} }
while (currentProcessInfo) { PROCESSENTRY32 pe32;
String processName = winrt::to_string(currentProcessInfo.ExecutableFileName()); pe32.dwSize = sizeof(PROCESSENTRY32);
if (!processName.empty()) { if (Process32First(hSnap, &pe32)) {
std::ranges::transform(processName, processName.begin(), [](const u8 character) { do {
return static_cast<char>(std::tolower(static_cast<unsigned char>(character))); String fullName = pe32.szExeFile;
String baseName;
const size_t lastSlash = fullName.find_last_of("\\/");
baseName = (lastSlash == String::npos) ? fullName : fullName.substr(lastSlash + 1);
std::transform(baseName.begin(), baseName.end(), baseName.begin(), [](const u8 character) {
return std::tolower(character);
}); });
if (processName.length() > 4 && processName.ends_with(".exe")) if (baseName.length() > 4 && baseName.ends_with(".exe"))
processName.resize(processName.length() - 4); baseName.resize(baseName.length() - 4);
auto iter = processMap[pe32.th32ProcessID] = ProcessData { pe32.th32ParentProcessID, std::move(baseName) };
} while (Process32Next(hSnap, &pe32));
} else {
error_log("FindShellInProcessTree: Process32First failed, error {}", GetLastError());
}
CloseHandle(hSnap);
DWORD currentPid = startPid;
i32 depth = 0;
constexpr int maxDepth = 32;
while (currentPid != 0 && depth < maxDepth) {
auto procIt = processMap.find(currentPid);
if (procIt == processMap.end())
break;
const String& processName = procIt->second.baseExeNameLower;
auto mapIter =
std::ranges::find_if(shellMap, [&](const auto& pair) { return StringView { processName } == pair.first; }); std::ranges::find_if(shellMap, [&](const auto& pair) { return StringView { processName } == pair.first; });
if (iter != std::ranges::end(shellMap)) if (mapIter != std::ranges::end(shellMap))
return String { iter->second }; return String { mapIter->second };
currentPid = procIt->second.parentPid;
depth++;
} }
currentProcessInfo = currentProcessInfo.Parent(); if (depth >= maxDepth)
} error_log("FindShellInProcessTree: Reached max depth limit ({}) walking parent PIDs from {}", maxDepth, startPid);
} catch (const winrt::hresult_error& e) {
error_log("WinRT error during process tree walk (start PID {}): {}", startPid, winrt::to_string(e.message()));
} catch (const std::exception& e) {
error_log("Standard exception during process tree walk (start PID {}): {}", startPid, e.what());
}
return None; return None;
} }
@ -180,16 +164,21 @@ namespace {
} }
} // namespace } // namespace
fn os::GetMemInfo() -> Result<u64, DraconisError> { namespace os {
try { fn GetMemInfo() -> Result<u64, DraconisError> {
return winrt::Windows::System::Diagnostics::SystemDiagnosticInfo::GetForCurrentSystem() MEMORYSTATUSEX memInfo;
.MemoryUsage() memInfo.dwLength = sizeof(MEMORYSTATUSEX);
.GetReport()
.TotalPhysicalSizeInBytes(); if (GlobalMemoryStatusEx(&memInfo))
} catch (const winrt::hresult_error& e) { return Err(DraconisError(e)); } return memInfo.ullTotalPhys;
DWORD lastError = GetLastError();
return Err(DraconisError(
DraconisErrorCode::PlatformSpecific, std::format("GlobalMemoryStatusEx failed with error code {}", lastError)
));
} }
fn os::GetNowPlaying() -> Result<MediaInfo, DraconisError> { fn GetNowPlaying() -> Result<MediaInfo, DraconisError> {
using namespace winrt::Windows::Media::Control; using namespace winrt::Windows::Media::Control;
using namespace winrt::Windows::Foundation; using namespace winrt::Windows::Foundation;
@ -213,7 +202,7 @@ fn os::GetNowPlaying() -> Result<MediaInfo, DraconisError> {
} catch (const winrt::hresult_error& e) { return Err(DraconisError(e)); } } catch (const winrt::hresult_error& e) { return Err(DraconisError(e)); }
} }
fn os::GetOSVersion() -> Result<String, DraconisError> { fn GetOSVersion() -> Result<String, DraconisError> {
try { try {
const String regSubKey = R"(SOFTWARE\Microsoft\Windows NT\CurrentVersion)"; const String regSubKey = R"(SOFTWARE\Microsoft\Windows NT\CurrentVersion)";
@ -243,63 +232,38 @@ fn os::GetOSVersion() -> Result<String, DraconisError> {
} catch (const std::exception& e) { return Err(DraconisError(e)); } } catch (const std::exception& e) { return Err(DraconisError(e)); }
} }
fn os::GetHost() -> Result<String, DraconisError> { fn GetHost() -> Result<String, DraconisError> {
return GetRegistryValue(HKEY_LOCAL_MACHINE, R"(SYSTEM\HardwareConfig\Current)", "SystemFamily"); return GetRegistryValue(HKEY_LOCAL_MACHINE, R"(SYSTEM\HardwareConfig\Current)", "SystemFamily");
} }
fn os::GetKernelVersion() -> Result<String, DraconisError> { fn GetKernelVersion() -> Result<String, DraconisError> {
try { if (const HMODULE ntdllHandle = GetModuleHandleW(L"ntdll.dll")) {
using namespace winrt::Windows::System::Profile; if (const auto rtlGetVersion = std::bit_cast<RtlGetVersionPtr>(GetProcAddress(ntdllHandle, "RtlGetVersion"))) {
RTL_OSVERSIONINFOW osInfo = {};
osInfo.dwOSVersionInfoSize = sizeof(osInfo);
const AnalyticsVersionInfo versionInfo = AnalyticsInfo::VersionInfo(); if (rtlGetVersion(&osInfo) == 0)
return std::format(
if (const winrt::hstring familyVersion = versionInfo.DeviceFamilyVersion(); !familyVersion.empty()) "{}.{}.{}.{}", osInfo.dwMajorVersion, osInfo.dwMinorVersion, osInfo.dwBuildNumber, osInfo.dwPlatformId
if (auto [major, minor, build, revision] = OSVersion::parseDeviceFamilyVersion(familyVersion); build > 0) );
return std::format("{}.{}.{}.{}", major, minor, build, revision);
} catch (const winrt::hresult_error& e) { return Err(DraconisError(e)); } catch (const Exception& e) {
return Err(DraconisError(e));
}
return Err(DraconisError(DraconisErrorCode::NotFound, "Could not determine kernel version"));
}
fn os::GetWindowManager() -> Option<String> {
if (const Result<Vec<Pair<DWORD, String>>, DraconisError> processInfoResult = GetProcessInfo()) {
const Vec<Pair<DWORD, String>>& processInfo = *processInfoResult;
Vec<String> processNames;
processNames.reserve(processInfo.size());
for (const String& val : processInfo | std::views::values) {
if (!val.empty()) {
const usize lastSlash = val.find_last_of("/\\");
processNames.push_back(lastSlash == String::npos ? val : val.substr(lastSlash + 1));
} }
} }
const std::unordered_map<String, String> wmProcesses = { return Err(DraconisError(DraconisErrorCode::NotFound, "Could not determine kernel version using RtlGetVersion"));
{ "glazewm.exe", "GlazeWM" },
{ "fancywm.exe", "FancyWM" },
{ "komorebi.exe", "Komorebi" },
{ "komorebic.exe", "Komorebi" },
};
for (const auto& [processExe, wmName] : wmProcesses)
if (IsProcessRunning(processNames, processExe))
return wmName;
} else {
error_log("Failed to get process info for WM detection: {}", processInfoResult.error().message);
} }
fn GetWindowManager() -> Option<String> {
BOOL compositionEnabled = FALSE; BOOL compositionEnabled = FALSE;
if (SUCCEEDED(DwmIsCompositionEnabled(&compositionEnabled))) {
if (SUCCEEDED(DwmIsCompositionEnabled(&compositionEnabled)))
return compositionEnabled ? "DWM" : "Windows Manager (Basic)"; return compositionEnabled ? "DWM" : "Windows Manager (Basic)";
}
error_log("GetWindowManager: DwmIsCompositionEnabled failed");
return None; return None;
} }
fn os::GetDesktopEnvironment() -> Option<String> { fn GetDesktopEnvironment() -> Option<String> {
const String buildStr = const String buildStr =
GetRegistryValue(HKEY_LOCAL_MACHINE, R"(SOFTWARE\Microsoft\Windows NT\CurrentVersion)", "CurrentBuildNumber"); GetRegistryValue(HKEY_LOCAL_MACHINE, R"(SOFTWARE\Microsoft\Windows NT\CurrentVersion)", "CurrentBuildNumber");
@ -346,57 +310,48 @@ fn os::GetDesktopEnvironment() -> Option<String> {
} }
} }
fn os::GetShell() -> Option<String> { fn GetShell() -> Option<String> {
using util::helpers::GetEnv; using util::helpers::GetEnv;
try {
const DWORD currentPid =
winrt::Windows::System::Diagnostics::ProcessDiagnosticInfo::GetForCurrentProcess().ProcessId();
if (const Result<String, DraconisError> msystemResult = GetEnv("MSYSTEM"); if (const Result<String, DraconisError> msystemResult = GetEnv("MSYSTEM");
msystemResult && !msystemResult->empty()) { msystemResult && !msystemResult->empty()) {
String shellPath; String shellPath;
if (const Result<String, DraconisError> shellResult = GetEnv("SHELL"); shellResult && !shellResult->empty()) if (const Result<String, DraconisError> shellResult = GetEnv("SHELL"); shellResult && !shellResult->empty()) {
shellPath = *shellResult; shellPath = *shellResult;
else if (const Result<String, DraconisError> loginShellResult = GetEnv("LOGINSHELL"); } else if (const Result<String, DraconisError> loginShellResult = GetEnv("LOGINSHELL");
loginShellResult && !loginShellResult->empty()) loginShellResult && !loginShellResult->empty()) {
shellPath = *loginShellResult; shellPath = *loginShellResult;
}
if (!shellPath.empty()) { if (!shellPath.empty()) {
const usize lastSlash = shellPath.find_last_of("\\/"); const usize lastSlash = shellPath.find_last_of("\\/");
String shellExe = (lastSlash != String::npos) ? shellPath.substr(lastSlash + 1) : shellPath; String shellExe = (lastSlash != String::npos) ? shellPath.substr(lastSlash + 1) : shellPath;
std::ranges::transform(shellExe, shellExe.begin(), [](const u8 character) { return std::tolower(character); });
std::ranges::transform(shellExe, shellExe.begin(), [](const u8 character) {
return static_cast<char>(std::tolower(static_cast<unsigned char>(character)));
});
if (shellExe.ends_with(".exe")) if (shellExe.ends_with(".exe"))
shellExe.resize(shellExe.length() - 4); shellExe.resize(shellExe.length() - 4);
const auto iter = const auto iter =
std::ranges::find_if(msysShellMap, [&](const auto& pair) { return StringView { shellExe } == pair.first; }); std::ranges::find_if(msysShellMap, [&](const auto& pair) { return StringView { shellExe } == pair.first; });
if (iter != std::ranges::end(msysShellMap)) if (iter != std::ranges::end(msysShellMap))
return String { iter->second }; return String { iter->second };
} }
const DWORD currentPid = GetCurrentProcessId();
if (const Option<String> msysShell = FindShellInProcessTree(currentPid, msysShellMap)) if (const Option<String> msysShell = FindShellInProcessTree(currentPid, msysShellMap))
return *msysShell; return msysShell;
return "MSYS2 Environment"; return "MSYS2 Environment";
} }
const DWORD currentPid = GetCurrentProcessId();
if (const Option<String> windowsShell = FindShellInProcessTree(currentPid, windowsShellMap)) if (const Option<String> windowsShell = FindShellInProcessTree(currentPid, windowsShellMap))
return *windowsShell; return windowsShell;
} catch (const winrt::hresult_error& e) {
error_log("WinRT error during shell detection: {}", winrt::to_string(e.message()));
} catch (const std::exception& e) { error_log("Standard exception during shell detection: {}", e.what()); }
return None; return None;
} }
fn os::GetDiskUsage() -> Result<DiskSpace, DraconisError> { fn GetDiskUsage() -> Result<DiskSpace, DraconisError> {
ULARGE_INTEGER freeBytes, totalBytes; ULARGE_INTEGER freeBytes, totalBytes;
if (GetDiskFreeSpaceExW(L"C:\\", nullptr, &totalBytes, &freeBytes)) if (GetDiskFreeSpaceExW(L"C:\\", nullptr, &totalBytes, &freeBytes))
@ -405,8 +360,11 @@ fn os::GetDiskUsage() -> Result<DiskSpace, DraconisError> {
return Err(DraconisError(util::error::DraconisErrorCode::NotFound, "Failed to get disk usage")); return Err(DraconisError(util::error::DraconisErrorCode::NotFound, "Failed to get disk usage"));
} }
fn os::GetPackageCount() -> Result<u64, DraconisError> { fn GetPackageCount() -> Result<u64, DraconisError> {
return Err(DraconisError(DraconisErrorCode::NotFound, "GetPackageCount not implemented")); try {
return std::ranges::distance(winrt::Windows::Management::Deployment::PackageManager().FindPackagesForUser(L""));
} catch (const winrt::hresult_error& e) { return Err(DraconisError(e)); }
} }
} // namespace os
#endif #endif