suckle
This commit is contained in:
parent
6d9390f02c
commit
33881d007e
7 changed files with 505 additions and 554 deletions
|
@ -73,7 +73,7 @@ endif
|
|||
if cpp.get_id() in ['msvc', 'clang-cl']
|
||||
common_cpp_args = common_cpp_flags['msvc']
|
||||
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
|
||||
else
|
||||
common_cpp_args = common_warning_flags + common_cpp_flags['common'] + common_cpp_flags['unix_extra']
|
||||
|
|
|
@ -7,12 +7,36 @@
|
|||
#include <toml++/impl/parser.hpp> // toml::{parse_file, parse_result}
|
||||
#include <utility> // std::pair (Pair)
|
||||
|
||||
#include "src/core/util/helpers.hpp"
|
||||
#include "src/core/util/logging.hpp"
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace {
|
||||
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 {
|
||||
using util::helpers::GetEnv;
|
||||
|
@ -30,8 +54,6 @@ namespace {
|
|||
|
||||
if (auto result = GetEnv("APPDATA"))
|
||||
possiblePaths.push_back(fs::path(*result) / "draconis++" / "config.toml");
|
||||
|
||||
possiblePaths.push_back(fs::path(".") / "config.toml");
|
||||
#else
|
||||
if (Result<String, DraconisError> result = GetEnv("XDG_CONFIG_HOME"))
|
||||
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) / ".draconis++" / "config.toml");
|
||||
}
|
||||
|
||||
possiblePaths.emplace_back("/etc/draconis++/config.toml");
|
||||
#endif
|
||||
|
||||
possiblePaths.push_back(fs::path(".") / "config.toml");
|
||||
|
||||
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;
|
||||
|
||||
if (!possiblePaths.empty()) {
|
||||
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);
|
||||
if (errc)
|
||||
warn_log("Warning: Failed to create config directory: {}", errc.message());
|
||||
|
@ -60,26 +82,33 @@ namespace {
|
|||
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 {
|
||||
try {
|
||||
std::error_code errc;
|
||||
create_directories(configPath.parent_path(), errc);
|
||||
|
||||
if (errc) {
|
||||
error_log("Failed to create config directory: {}", errc.message());
|
||||
return false;
|
||||
}
|
||||
|
||||
toml::table root;
|
||||
String defaultName;
|
||||
|
||||
#ifdef _WIN32
|
||||
Array<char, 256> 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
|
||||
const passwd* pwd = getpwuid(getuid());
|
||||
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> envLogname = util::helpers::GetEnv("LOGNAME");
|
||||
|
||||
String defaultName = pwdName ? pwdName : envUser ? *envUser : envLogname ? *envLogname : "User";
|
||||
defaultName = pwdName ? pwdName : envUser ? *envUser : envLogname ? *envLogname : "User";
|
||||
#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);
|
||||
if (!file) {
|
||||
error_log("Failed to open config file for writing: {}", configPath.string());
|
||||
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";
|
||||
file << "[general]\n";
|
||||
file << "name = \"" << defaultName << "\" # Your display name\n\n";
|
||||
try {
|
||||
const std::string fallbackConfig = std::format(defaultConfigTemplate, "User");
|
||||
file << fallbackConfig;
|
||||
} catch (...) {
|
||||
error_log("Failed to format default config even with fallback name.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
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";
|
||||
if (!file) {
|
||||
error_log("Failed to write to config file: {}", configPath.string());
|
||||
return false;
|
||||
}
|
||||
|
||||
info_log("Created default config file at {}", configPath.string());
|
||||
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) {
|
||||
error_log("Failed to create default config file: {}", e.what());
|
||||
return false;
|
||||
} catch (...) {
|
||||
error_log("An unexpected error occurred during default config creation.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} // 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 {
|
||||
try {
|
||||
const fs::path configPath = GetConfigPath();
|
||||
|
||||
if (!exists(configPath)) {
|
||||
info_log("Config file not found, creating defaults at {}", configPath.string());
|
||||
std::error_code ec;
|
||||
|
||||
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)) {
|
||||
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 {};
|
||||
}
|
||||
}
|
||||
|
||||
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());
|
||||
return fromToml(config);
|
||||
return Config(config);
|
||||
} catch (const Exception& e) {
|
||||
debug_log("Config loading failed: {}, using defaults", e.what());
|
||||
return {};
|
||||
} catch (...) {
|
||||
error_log("An unexpected error occurred during config loading. Using in-memory defaults.");
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
#include "src/core/util/defs.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 "weather.hpp"
|
||||
|
@ -132,8 +132,10 @@ struct Weather {
|
|||
.lat = *location.as_table()->get("lat")->value<double>(),
|
||||
.lon = *location.as_table()->get("lon")->value<double>(),
|
||||
};
|
||||
else
|
||||
throw std::runtime_error("Invalid location type");
|
||||
else {
|
||||
error_log("Invalid location format in config.");
|
||||
weather.enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
return weather;
|
||||
|
@ -159,22 +161,9 @@ struct Config {
|
|||
Weather weather; ///< Weather configuration settings.`
|
||||
NowPlaying now_playing; ///< Now Playing configuration settings.
|
||||
|
||||
/**
|
||||
* @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"];
|
||||
Config() = default;
|
||||
|
||||
return {
|
||||
.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 {},
|
||||
};
|
||||
}
|
||||
explicit Config(const toml::table& tbl);
|
||||
|
||||
/**
|
||||
* @brief Retrieves the path to the configuration file.
|
||||
|
|
|
@ -26,52 +26,59 @@ namespace {
|
|||
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
|
||||
|
||||
fn SystemData::fetchSystemData(const Config& config) -> SystemData {
|
||||
using util::types::None;
|
||||
using util::types::None, util::types::Exception;
|
||||
using namespace os;
|
||||
|
||||
SystemData data {
|
||||
.date = GetDate(),
|
||||
.host = os::GetHost(),
|
||||
.kernel_version = os::GetKernelVersion(),
|
||||
.os_version = os::GetOSVersion(),
|
||||
.mem_info = os::GetMemInfo(),
|
||||
.desktop_environment = os::GetDesktopEnvironment(),
|
||||
.window_manager = os::GetWindowManager(),
|
||||
.disk_usage = {},
|
||||
.shell = None,
|
||||
.now_playing = None,
|
||||
.weather_info = None,
|
||||
.date = time_execution("GetDate", GetDate),
|
||||
.host = time_execution("GetHost", GetHost),
|
||||
.kernel_version = time_execution("GetKernelVersion", GetKernelVersion),
|
||||
.os_version = time_execution("GetOSVersion", GetOSVersion),
|
||||
.mem_info = time_execution("GetMemInfo", GetMemInfo),
|
||||
.desktop_environment = time_execution("GetDesktopEnvironment", GetDesktopEnvironment),
|
||||
.window_manager = time_execution("GetWindowManager", GetWindowManager),
|
||||
.disk_usage = time_execution("GetDiskUsage", GetDiskUsage),
|
||||
.shell = time_execution("GetShell", GetShell),
|
||||
};
|
||||
|
||||
auto diskShellFuture = std::async(std::launch::async, [] {
|
||||
Result<DiskSpace, DraconisError> diskResult = os::GetDiskUsage();
|
||||
Option<String> shellOption = os::GetShell();
|
||||
return std::make_tuple(std::move(diskResult), std::move(shellOption));
|
||||
});
|
||||
if (const Result<MediaInfo, DraconisError>& nowPlayingResult = time_execution("GetNowPlaying", os::GetNowPlaying)) {
|
||||
data.now_playing = nowPlayingResult;
|
||||
} else {
|
||||
data.now_playing = None;
|
||||
}
|
||||
|
||||
std::future<weather::Output> weatherFuture;
|
||||
std::future<Result<MediaInfo, DraconisError>> nowPlayingFuture;
|
||||
const auto start = std::chrono::steady_clock::now();
|
||||
data.weather_info = config.weather.getWeatherInfo();
|
||||
const auto end = std::chrono::steady_clock::now();
|
||||
|
||||
if (config.weather.enabled)
|
||||
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();
|
||||
log_timing("config.weather.getWeatherInfo", end - start);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
|
|
@ -3,189 +3,135 @@
|
|||
#include <chrono> // std::chrono::{days, floor, seconds, system_clock}
|
||||
#include <filesystem> // std::filesystem::path
|
||||
#include <format> // std::format
|
||||
#include <ftxui/screen/color.hpp> // ftxui::Color
|
||||
#include <mutex> // std::mutex
|
||||
#include <print> // std::print
|
||||
#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/error.hpp"
|
||||
#include "src/core/util/types.hpp"
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* @namespace term
|
||||
* @brief Provides terminal-related utilities, including color and style formatting.
|
||||
*/
|
||||
namespace term {
|
||||
/**
|
||||
* @enum Emphasis
|
||||
* @brief Represents text emphasis styles.
|
||||
*
|
||||
* Enum values can be combined using bitwise OR to apply multiple styles at once.
|
||||
*/
|
||||
enum class Emphasis : u8 {
|
||||
Bold, ///< Bold text.
|
||||
Italic ///< Italic text.
|
||||
// Store all compile-time constants in a struct
|
||||
struct LogLevelConst {
|
||||
// ANSI color codes
|
||||
// clang-format off
|
||||
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",
|
||||
"\033[38;5;4m", "\033[38;5;5m", "\033[38;5;6m", "\033[38;5;7m",
|
||||
"\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",
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
/**
|
||||
* @enum Color
|
||||
* @brief Represents ANSI color codes for terminal output.
|
||||
*
|
||||
* Color codes can be used to format terminal output.
|
||||
*/
|
||||
enum class Color : u8 {
|
||||
Black = 30, ///< Black color.
|
||||
Red = 31, ///< Red color.
|
||||
Green = 32, ///< Green color.
|
||||
Yellow = 33, ///< Yellow color.
|
||||
Blue = 34, ///< Blue color.
|
||||
Magenta = 35, ///< Magenta color.
|
||||
Cyan = 36, ///< Cyan color.
|
||||
White = 37, ///< White color.
|
||||
BrightBlack = 90, ///< Bright black (gray) color.
|
||||
BrightRed = 91, ///< Bright red color.
|
||||
BrightGreen = 92, ///< Bright green color.
|
||||
BrightYellow = 93, ///< Bright yellow color.
|
||||
BrightBlue = 94, ///< Bright blue color.
|
||||
BrightMagenta = 95, ///< Bright magenta color.
|
||||
BrightCyan = 96, ///< Bright cyan color.
|
||||
BrightWhite = 97, ///< Bright white color.
|
||||
// ANSI formatting constants
|
||||
static constexpr const char* RESET_CODE = "\033[0m";
|
||||
static constexpr const char* BOLD_START = "\033[1m";
|
||||
static constexpr const char* BOLD_END = "\033[22m";
|
||||
static constexpr const char* ITALIC_START = "\033[3m";
|
||||
static constexpr const char* ITALIC_END = "\033[23m";
|
||||
|
||||
// Log level text constants
|
||||
static constexpr StringView DEBUG_STR = "DEBUG";
|
||||
static constexpr StringView INFO_STR = "INFO ";
|
||||
static constexpr StringView WARN_STR = "WARN ";
|
||||
static constexpr StringView ERROR_STR = "ERROR";
|
||||
|
||||
// Log level color constants
|
||||
static constexpr ftxui::Color::Palette16 DEBUG_COLOR = ftxui::Color::Palette16::Cyan;
|
||||
static constexpr ftxui::Color::Palette16 INFO_COLOR = ftxui::Color::Palette16::Green;
|
||||
static constexpr ftxui::Color::Palette16 WARN_COLOR = ftxui::Color::Palette16::Yellow;
|
||||
static constexpr ftxui::Color::Palette16 ERROR_COLOR = ftxui::Color::Palette16::Red;
|
||||
static constexpr ftxui::Color::Palette16 DEBUG_INFO_COLOR = ftxui::Color::Palette16::GrayLight;
|
||||
|
||||
static constexpr CStr TIMESTAMP_FORMAT = "{:%X}";
|
||||
static constexpr CStr LOG_FORMAT = "{} {} {}";
|
||||
|
||||
#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
|
||||
* @brief Represents different log levels.
|
||||
*/
|
||||
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.
|
||||
* @tparam Args Parameter pack for format arguments.
|
||||
|
@ -197,37 +143,45 @@ namespace util::logging {
|
|||
template <typename... Args>
|
||||
fn LogImpl(const LogLevel level, const std::source_location& loc, std::format_string<Args...> fmt, Args&&... args) {
|
||||
using namespace std::chrono;
|
||||
using namespace term;
|
||||
using std::filesystem::path;
|
||||
|
||||
const auto [color, levelStr] = [&] {
|
||||
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();
|
||||
}
|
||||
}();
|
||||
using Buffer = Array<char, 512>;
|
||||
|
||||
Print(Color::BrightWhite, "[{:%X}] ", std::chrono::floor<seconds>(system_clock::now()));
|
||||
Print(Emphasis::Bold | color, "{} ", levelStr);
|
||||
Print(fmt, std::forward<Args>(args)...);
|
||||
// Dynamic parts (runtime)
|
||||
const time_point<system_clock, duration<i64>> now = floor<seconds>(system_clock::now());
|
||||
|
||||
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
|
||||
Print(Color::BrightWhite, "\n{:>14} ", "╰──");
|
||||
Print(
|
||||
Emphasis::Italic | Color::BrightWhite,
|
||||
"{}:{}",
|
||||
std::filesystem::path(loc.file_name()).lexically_normal().string(),
|
||||
loc.line()
|
||||
iter = std::format_to(
|
||||
iter,
|
||||
"\n{}",
|
||||
Italic(Colorize(
|
||||
LogLevelConst::DEBUG_LINE_PREFIX +
|
||||
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>
|
||||
fn LogAppError(const LogLevel level, const ErrorType& error_obj) {
|
||||
fn LogError(const LogLevel level, const ErrorType& error_obj) {
|
||||
using DecayedErrorType = std::decay_t<ErrorType>;
|
||||
|
||||
std::source_location logLocation;
|
||||
|
@ -254,7 +208,7 @@ namespace util::logging {
|
|||
::util::logging::LogImpl( \
|
||||
::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
|
||||
#define debug_log(...) ((void)0)
|
||||
#define debug_at(...) ((void)0)
|
||||
|
@ -264,17 +218,17 @@ namespace util::logging {
|
|||
::util::logging::LogImpl( \
|
||||
::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, ...) \
|
||||
::util::logging::LogImpl( \
|
||||
::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, ...) \
|
||||
::util::logging::LogImpl( \
|
||||
::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
|
||||
|
|
18
src/main.cpp
18
src/main.cpp
|
@ -20,8 +20,6 @@ namespace ui {
|
|||
using ftxui::Color;
|
||||
using util::types::StringView, util::types::i32;
|
||||
|
||||
static constexpr inline StringView ICON_TYPE = "EMOJI";
|
||||
|
||||
static constexpr i32 MAX_PARAGRAPH_LENGTH = 30;
|
||||
|
||||
// Color themes
|
||||
|
@ -55,7 +53,7 @@ namespace ui {
|
|||
StringView window_manager;
|
||||
};
|
||||
|
||||
static constexpr Icons NONE = {
|
||||
[[maybe_unused]] static constexpr Icons NONE = {
|
||||
.user = "",
|
||||
.palette = "",
|
||||
.calendar = "",
|
||||
|
@ -71,7 +69,7 @@ namespace ui {
|
|||
.window_manager = "",
|
||||
};
|
||||
|
||||
static constexpr Icons NERD = {
|
||||
[[maybe_unused]] static constexpr Icons NERD = {
|
||||
.user = " ",
|
||||
.palette = " ",
|
||||
.calendar = " ",
|
||||
|
@ -87,7 +85,7 @@ namespace ui {
|
|||
.window_manager = " ",
|
||||
};
|
||||
|
||||
static constexpr Icons EMOJI = {
|
||||
[[maybe_unused]] static constexpr Icons EMOJI = {
|
||||
.user = " 👤 ",
|
||||
.palette = " 🎨 ",
|
||||
.calendar = " 📅 ",
|
||||
|
@ -102,6 +100,8 @@ namespace ui {
|
|||
.desktop = " 🖥️ ",
|
||||
.window_manager = " 🪟 ",
|
||||
};
|
||||
|
||||
static constexpr inline Icons ICON_TYPE = NERD;
|
||||
} // namespace ui
|
||||
|
||||
namespace {
|
||||
|
@ -122,9 +122,7 @@ namespace {
|
|||
const Weather weather = config.weather;
|
||||
|
||||
const auto& [userIcon, paletteIcon, calendarIcon, hostIcon, kernelIcon, osIcon, memoryIcon, weatherIcon, musicIcon, diskIcon, shellIcon, deIcon, wmIcon] =
|
||||
ui::ICON_TYPE == "NERD" ? ui::NERD
|
||||
: ui::ICON_TYPE == "EMOJI" ? ui::EMOJI
|
||||
: ui::NONE;
|
||||
ui::ICON_TYPE;
|
||||
|
||||
Elements content;
|
||||
|
||||
|
@ -264,6 +262,10 @@ namespace {
|
|||
} // namespace
|
||||
|
||||
fn main() -> i32 {
|
||||
#ifdef _WIN32
|
||||
winrt::init_apartment();
|
||||
#endif
|
||||
|
||||
const Config& config = Config::getInstance();
|
||||
const SystemData data = SystemData::fetchSystemData(config);
|
||||
|
||||
|
|
|
@ -5,14 +5,14 @@
|
|||
#include <windows.h>
|
||||
#include <wincrypt.h>
|
||||
#include <dwmapi.h>
|
||||
#include <tlhelp32.h>
|
||||
|
||||
#include <cstring>
|
||||
#include <ranges>
|
||||
#include <winrt/Windows.Foundation.Collections.h>
|
||||
#include <winrt/Windows.Media.Control.h>
|
||||
#include <winrt/Windows.Storage.h>
|
||||
#include <winrt/Windows.System.Diagnostics.h>
|
||||
#include <winrt/Windows.System.Profile.h>
|
||||
#include <winrt/Windows.Management.Deployment.h>
|
||||
#include <winrt/base.h>
|
||||
#include <winrt/impl/Windows.Media.Control.2.h>
|
||||
|
||||
|
@ -24,33 +24,15 @@
|
|||
#include "os.hpp"
|
||||
// clang-format on
|
||||
|
||||
using RtlGetVersionPtr = NTSTATUS(WINAPI*)(PRTL_OSVERSIONINFOW);
|
||||
|
||||
namespace {
|
||||
using util::error::DraconisError, util::error::DraconisErrorCode;
|
||||
using namespace util::types;
|
||||
|
||||
struct OSVersion {
|
||||
u16 major;
|
||||
u16 minor;
|
||||
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 };
|
||||
}
|
||||
struct ProcessData {
|
||||
DWORD parentPid = 0;
|
||||
String baseExeNameLower;
|
||||
};
|
||||
|
||||
// clang-format off
|
||||
|
@ -91,73 +73,75 @@ namespace {
|
|||
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>
|
||||
fn FindShellInProcessTree(const DWORD startPid, const Array<Pair<StringView, StringView>, sz>& shellMap)
|
||||
-> Option<String> {
|
||||
if (startPid == 0)
|
||||
return None;
|
||||
|
||||
try {
|
||||
using namespace winrt::Windows::System::Diagnostics;
|
||||
std::unordered_map<DWORD, ProcessData> processMap;
|
||||
|
||||
ProcessDiagnosticInfo currentProcessInfo = nullptr;
|
||||
const HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
||||
|
||||
try {
|
||||
currentProcessInfo = ProcessDiagnosticInfo::TryGetForProcessId(startPid);
|
||||
} catch (const winrt::hresult_error& e) {
|
||||
error_log("Failed to get process info for PID {}: {}", startPid, winrt::to_string(e.message()));
|
||||
if (hSnap == INVALID_HANDLE_VALUE) {
|
||||
error_log("FindShellInProcessTree: Failed snapshot, error {}", GetLastError());
|
||||
return None;
|
||||
}
|
||||
|
||||
while (currentProcessInfo) {
|
||||
String processName = winrt::to_string(currentProcessInfo.ExecutableFileName());
|
||||
PROCESSENTRY32 pe32;
|
||||
pe32.dwSize = sizeof(PROCESSENTRY32);
|
||||
|
||||
if (!processName.empty()) {
|
||||
std::ranges::transform(processName, processName.begin(), [](const u8 character) {
|
||||
return static_cast<char>(std::tolower(static_cast<unsigned char>(character)));
|
||||
if (Process32First(hSnap, &pe32)) {
|
||||
do {
|
||||
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"))
|
||||
processName.resize(processName.length() - 4);
|
||||
if (baseName.length() > 4 && baseName.ends_with(".exe"))
|
||||
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; });
|
||||
|
||||
if (iter != std::ranges::end(shellMap))
|
||||
return String { iter->second };
|
||||
if (mapIter != std::ranges::end(shellMap))
|
||||
return String { mapIter->second };
|
||||
|
||||
currentPid = procIt->second.parentPid;
|
||||
|
||||
depth++;
|
||||
}
|
||||
|
||||
currentProcessInfo = currentProcessInfo.Parent();
|
||||
}
|
||||
} 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());
|
||||
}
|
||||
if (depth >= maxDepth)
|
||||
error_log("FindShellInProcessTree: Reached max depth limit ({}) walking parent PIDs from {}", maxDepth, startPid);
|
||||
|
||||
return None;
|
||||
}
|
||||
|
@ -180,16 +164,21 @@ namespace {
|
|||
}
|
||||
} // namespace
|
||||
|
||||
fn os::GetMemInfo() -> Result<u64, DraconisError> {
|
||||
try {
|
||||
return winrt::Windows::System::Diagnostics::SystemDiagnosticInfo::GetForCurrentSystem()
|
||||
.MemoryUsage()
|
||||
.GetReport()
|
||||
.TotalPhysicalSizeInBytes();
|
||||
} catch (const winrt::hresult_error& e) { return Err(DraconisError(e)); }
|
||||
namespace os {
|
||||
fn GetMemInfo() -> Result<u64, DraconisError> {
|
||||
MEMORYSTATUSEX memInfo;
|
||||
memInfo.dwLength = sizeof(MEMORYSTATUSEX);
|
||||
|
||||
if (GlobalMemoryStatusEx(&memInfo))
|
||||
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::Foundation;
|
||||
|
||||
|
@ -213,7 +202,7 @@ fn os::GetNowPlaying() -> Result<MediaInfo, DraconisError> {
|
|||
} catch (const winrt::hresult_error& e) { return Err(DraconisError(e)); }
|
||||
}
|
||||
|
||||
fn os::GetOSVersion() -> Result<String, DraconisError> {
|
||||
fn GetOSVersion() -> Result<String, DraconisError> {
|
||||
try {
|
||||
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)); }
|
||||
}
|
||||
|
||||
fn os::GetHost() -> Result<String, DraconisError> {
|
||||
fn GetHost() -> Result<String, DraconisError> {
|
||||
return GetRegistryValue(HKEY_LOCAL_MACHINE, R"(SYSTEM\HardwareConfig\Current)", "SystemFamily");
|
||||
}
|
||||
|
||||
fn os::GetKernelVersion() -> Result<String, DraconisError> {
|
||||
try {
|
||||
using namespace winrt::Windows::System::Profile;
|
||||
fn GetKernelVersion() -> Result<String, DraconisError> {
|
||||
if (const HMODULE ntdllHandle = GetModuleHandleW(L"ntdll.dll")) {
|
||||
if (const auto rtlGetVersion = std::bit_cast<RtlGetVersionPtr>(GetProcAddress(ntdllHandle, "RtlGetVersion"))) {
|
||||
RTL_OSVERSIONINFOW osInfo = {};
|
||||
osInfo.dwOSVersionInfoSize = sizeof(osInfo);
|
||||
|
||||
const AnalyticsVersionInfo versionInfo = AnalyticsInfo::VersionInfo();
|
||||
|
||||
if (const winrt::hstring familyVersion = versionInfo.DeviceFamilyVersion(); !familyVersion.empty())
|
||||
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));
|
||||
if (rtlGetVersion(&osInfo) == 0)
|
||||
return std::format(
|
||||
"{}.{}.{}.{}", osInfo.dwMajorVersion, osInfo.dwMinorVersion, osInfo.dwBuildNumber, osInfo.dwPlatformId
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const std::unordered_map<String, String> wmProcesses = {
|
||||
{ "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);
|
||||
return Err(DraconisError(DraconisErrorCode::NotFound, "Could not determine kernel version using RtlGetVersion"));
|
||||
}
|
||||
|
||||
fn GetWindowManager() -> Option<String> {
|
||||
BOOL compositionEnabled = FALSE;
|
||||
|
||||
if (SUCCEEDED(DwmIsCompositionEnabled(&compositionEnabled)))
|
||||
if (SUCCEEDED(DwmIsCompositionEnabled(&compositionEnabled))) {
|
||||
return compositionEnabled ? "DWM" : "Windows Manager (Basic)";
|
||||
}
|
||||
|
||||
error_log("GetWindowManager: DwmIsCompositionEnabled failed");
|
||||
|
||||
return None;
|
||||
}
|
||||
|
||||
fn os::GetDesktopEnvironment() -> Option<String> {
|
||||
fn GetDesktopEnvironment() -> Option<String> {
|
||||
const String buildStr =
|
||||
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;
|
||||
|
||||
try {
|
||||
const DWORD currentPid =
|
||||
winrt::Windows::System::Diagnostics::ProcessDiagnosticInfo::GetForCurrentProcess().ProcessId();
|
||||
|
||||
if (const Result<String, DraconisError> msystemResult = GetEnv("MSYSTEM");
|
||||
msystemResult && !msystemResult->empty()) {
|
||||
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;
|
||||
else if (const Result<String, DraconisError> loginShellResult = GetEnv("LOGINSHELL");
|
||||
loginShellResult && !loginShellResult->empty())
|
||||
} else if (const Result<String, DraconisError> loginShellResult = GetEnv("LOGINSHELL");
|
||||
loginShellResult && !loginShellResult->empty()) {
|
||||
shellPath = *loginShellResult;
|
||||
}
|
||||
|
||||
if (!shellPath.empty()) {
|
||||
const usize lastSlash = shellPath.find_last_of("\\/");
|
||||
String shellExe = (lastSlash != String::npos) ? shellPath.substr(lastSlash + 1) : shellPath;
|
||||
|
||||
std::ranges::transform(shellExe, shellExe.begin(), [](const u8 character) {
|
||||
return static_cast<char>(std::tolower(static_cast<unsigned char>(character)));
|
||||
});
|
||||
|
||||
std::ranges::transform(shellExe, shellExe.begin(), [](const u8 character) { return std::tolower(character); });
|
||||
if (shellExe.ends_with(".exe"))
|
||||
shellExe.resize(shellExe.length() - 4);
|
||||
|
||||
const auto iter =
|
||||
std::ranges::find_if(msysShellMap, [&](const auto& pair) { return StringView { shellExe } == pair.first; });
|
||||
|
||||
if (iter != std::ranges::end(msysShellMap))
|
||||
return String { iter->second };
|
||||
}
|
||||
|
||||
const DWORD currentPid = GetCurrentProcessId();
|
||||
if (const Option<String> msysShell = FindShellInProcessTree(currentPid, msysShellMap))
|
||||
return *msysShell;
|
||||
return msysShell;
|
||||
|
||||
return "MSYS2 Environment";
|
||||
}
|
||||
|
||||
const DWORD currentPid = GetCurrentProcessId();
|
||||
if (const Option<String> windowsShell = FindShellInProcessTree(currentPid, windowsShellMap))
|
||||
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 windowsShell;
|
||||
|
||||
return None;
|
||||
}
|
||||
|
||||
fn os::GetDiskUsage() -> Result<DiskSpace, DraconisError> {
|
||||
fn GetDiskUsage() -> Result<DiskSpace, DraconisError> {
|
||||
ULARGE_INTEGER freeBytes, totalBytes;
|
||||
|
||||
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"));
|
||||
}
|
||||
|
||||
fn os::GetPackageCount() -> Result<u64, DraconisError> {
|
||||
return Err(DraconisError(DraconisErrorCode::NotFound, "GetPackageCount not implemented"));
|
||||
fn GetPackageCount() -> Result<u64, DraconisError> {
|
||||
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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue