i think i got everything

This commit is contained in:
Mars 2025-04-23 02:17:26 -04:00
parent 8293ef42b6
commit cf51e3e569
Signed by: pupbrained
GPG key ID: 0FF5B8826803F895
19 changed files with 671 additions and 805 deletions

View file

@ -1,6 +1,7 @@
# This file was generated by nvfetcher, please do not modify it manually. # This file was generated by nvfetcher, please do not modify it manually.
{ fetchgit, fetchurl, fetchFromGitHub, dockerTools }:
{ {
fetchFromGitHub,
}: {
dbus-cxx = { dbus-cxx = {
pname = "dbus-cxx"; pname = "dbus-cxx";
version = "2.5.2"; version = "2.5.2";

View file

@ -34,36 +34,13 @@
dbus-cxx = stdenv.mkDerivation { dbus-cxx = stdenv.mkDerivation {
inherit (sources.dbus-cxx) pname version src; inherit (sources.dbus-cxx) pname version src;
nativeBuildInputs = [ nativeBuildInputs = with pkgs; [cmake pkg-config];
pkgs.cmake
pkgs.pkg-config
];
buildInputs = [ buildInputs = with pkgs.pkgsStatic; [libsigcxx30];
pkgs.libsigcxx30
];
preConfigure = '' prePatch = ''
set -x # Print commands being run substituteInPlace CMakeLists.txt --replace "add_library( dbus-cxx SHARED" "add_library( dbus-cxx STATIC"
echo "--- Checking pkg-config paths ---"
echo "PKG_CONFIG_PATH: $PKG_CONFIG_PATH"
echo "--- Searching for sigc++ pc files ---"
find $PKG_CONFIG_PATH -name '*.pc' | grep sigc || echo "No sigc pc file found in PKG_CONFIG_PATH"
echo "--- Running pkg-config check ---"
pkg-config --exists --print-errors 'sigc++-3.0'
if [ $? -ne 0 ]; then
echo "ERROR: pkg-config check for sigc++-3.0 failed!"
# Optionally list all available packages known to pkg-config:
# pkg-config --list-all
fi
echo "--- End Debug ---"
set +x
''; '';
cmakeFlags = [
"-DENABLE_QT_SUPPORT=OFF"
"-DENABLE_UV_SUPPORT=OFF"
];
}; };
deps = with pkgs; deps = with pkgs;
@ -84,12 +61,11 @@
linuxPkgs = nixpkgs.lib.optionals stdenv.isLinux (with pkgs; linuxPkgs = nixpkgs.lib.optionals stdenv.isLinux (with pkgs;
[ [
libsigcxx30
valgrind valgrind
] ]
++ (with pkgsStatic; [ ++ (with pkgsStatic; [
dbus
dbus-cxx dbus-cxx
libsigcxx30
sqlitecpp sqlitecpp
xorg.libX11 xorg.libX11
wayland wayland

View file

@ -87,8 +87,7 @@ add_project_arguments(common_cpp_args, language: 'cpp')
base_sources = files('src/config/config.cpp', 'src/config/weather.cpp', 'src/main.cpp') base_sources = files('src/config/config.cpp', 'src/config/weather.cpp', 'src/main.cpp')
platform_sources = { platform_sources = {
'linux': ['src/os/linux.cpp', 'src/os/linux/issetugid_stub.cpp'], 'linux': ['src/os/linux.cpp', 'src/os/linux/issetugid_stub.cpp', 'src/os/linux/display_guards.cpp'],
'freebsd': ['src/os/freebsd.cpp'],
'darwin': ['src/os/macos.cpp', 'src/os/macos/bridge.mm'], 'darwin': ['src/os/macos.cpp', 'src/os/macos/bridge.mm'],
'windows': ['src/os/windows.cpp'], 'windows': ['src/os/windows.cpp'],
} }
@ -121,7 +120,7 @@ elif host_system == 'windows'
cpp.find_library('dwmapi'), cpp.find_library('dwmapi'),
cpp.find_library('windowsapp'), cpp.find_library('windowsapp'),
] ]
elif host_system == 'linux' or host_system == 'freebsd' elif host_system == 'linux'
platform_deps += [ platform_deps += [
dependency('SQLiteCpp'), dependency('SQLiteCpp'),
dependency('x11'), dependency('x11'),

View file

@ -11,26 +11,26 @@ namespace fs = std::filesystem;
namespace { namespace {
fn GetConfigPath() -> fs::path { fn GetConfigPath() -> fs::path {
std::vector<fs::path> possiblePaths; Vec<fs::path> possiblePaths;
#ifdef _WIN32 #ifdef _WIN32
if (auto result = GetEnv("LOCALAPPDATA"); result) if (auto result = GetEnv("LOCALAPPDATA"))
possiblePaths.push_back(fs::path(*result) / "draconis++" / "config.toml"); possiblePaths.push_back(fs::path(*result) / "draconis++" / "config.toml");
if (auto result = GetEnv("USERPROFILE"); result) { if (auto result = GetEnv("USERPROFILE")) {
possiblePaths.push_back(fs::path(*result) / ".config" / "draconis++" / "config.toml"); possiblePaths.push_back(fs::path(*result) / ".config" / "draconis++" / "config.toml");
possiblePaths.push_back(fs::path(*result) / "AppData" / "Local" / "draconis++" / "config.toml"); possiblePaths.push_back(fs::path(*result) / "AppData" / "Local" / "draconis++" / "config.toml");
} }
if (auto result = GetEnv("APPDATA"); result) 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"); possiblePaths.push_back(fs::path(".") / "config.toml");
#else #else
if (auto result = GetEnv("XDG_CONFIG_HOME"); result) if (Result<String, EnvError> result = GetEnv("XDG_CONFIG_HOME"))
possiblePaths.emplace_back(fs::path(*result) / "draconis++" / "config.toml"); possiblePaths.emplace_back(fs::path(*result) / "draconis++" / "config.toml");
if (auto result = GetEnv("HOME"); result) { if (Result<String, EnvError> result = GetEnv("HOME")) {
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");
} }
@ -38,7 +38,7 @@ namespace {
possiblePaths.emplace_back("/etc/draconis++/config.toml"); possiblePaths.emplace_back("/etc/draconis++/config.toml");
#endif #endif
for (const auto& path : possiblePaths) for (const fs::path& path : possiblePaths)
if (std::error_code errc; exists(path, errc) && !errc) if (std::error_code errc; exists(path, errc) && !errc)
return path; return path;
@ -68,18 +68,20 @@ namespace {
toml::table root; toml::table root;
String defaultName;
#ifdef _WIN32 #ifdef _WIN32
std::array<char, 256> username; Array<char, 256> username;
DWORD size = sizeof(username);
defaultName = GetUserNameA(username.data(), &size) ? username.data() : "User"; DWORD size = sizeof(username);
String defaultName = GetUserNameA(username.data(), &size) ? username.data() : "User";
#else #else
if (struct passwd* pwd = getpwuid(getuid()); pwd) const struct passwd* pwd = getpwuid(getuid());
defaultName = pwd->pw_name; CStr pwdName = pwd ? pwd->pw_name : nullptr;
else if (const char* envUser = getenv("USER"))
defaultName = envUser; const Result<String, EnvError> envUser = GetEnv("USER");
else const Result<String, EnvError> envLogname = GetEnv("LOGNAME");
defaultName = "User";
String defaultName = (pwdName) ? pwdName : (envUser) ? *envUser : (envLogname) ? *envLogname : "User";
#endif #endif
toml::table* general = root.insert("general", toml::table {}).first->second.as_table(); toml::table* general = root.insert("general", toml::table {}).first->second.as_table();

View file

@ -17,15 +17,15 @@ using Location = std::variant<String, Coords>;
struct General { struct General {
String name = []() -> String { String name = []() -> String {
#ifdef _WIN32 #ifdef _WIN32
std::array<char, 256> username; Array<char, 256> username;
DWORD size = sizeof(username); DWORD size = sizeof(username);
return GetUserNameA(username.data(), &size) ? username.data() : "User"; return GetUserNameA(username.data(), &size) ? username.data() : "User";
#else #else
if (struct passwd* pwd = getpwuid(getuid()); pwd) if (struct passwd* pwd = getpwuid(getuid()))
return pwd->pw_name; return pwd->pw_name;
if (const char* envUser = getenv("USER")) if (Result<String, EnvError> envUser = GetEnv("USER"))
return envUser; return *envUser;
return "User"; return "User";
#endif #endif
@ -33,22 +33,16 @@ struct General {
static fn fromToml(const toml::table& tbl) -> General { static fn fromToml(const toml::table& tbl) -> General {
General gen; General gen;
return {
if (const std::optional<String> name = tbl["name"].value<String>()) .name = tbl["name"].value_or(gen.name),
gen.name = *name; };
return gen;
} }
}; };
struct NowPlaying { struct NowPlaying {
bool enabled = false; bool enabled = false;
static fn fromToml(const toml::table& tbl) -> NowPlaying { static fn fromToml(const toml::table& tbl) -> NowPlaying { return { .enabled = tbl["enabled"].value_or(false) }; }
NowPlaying nowPlaying;
nowPlaying.enabled = tbl["enabled"].value<bool>().value_or(false);
return nowPlaying;
}
}; };
struct Weather { struct Weather {
@ -60,37 +54,28 @@ struct Weather {
static fn fromToml(const toml::table& tbl) -> Weather { static fn fromToml(const toml::table& tbl) -> Weather {
Weather weather; Weather weather;
weather.enabled = tbl["enabled"].value_or<bool>(false);
if (auto apiKey = tbl["api_key"].value<String>()) { Option<String> apiKey = tbl["api_key"].value<String>();
const String& keyVal = apiKey.value();
if (keyVal.empty()) weather.enabled = tbl["enabled"].value_or<bool>(false) && apiKey;
weather.enabled = false;
weather.api_key = keyVal;
} else {
weather.enabled = false;
}
if (!weather.enabled) if (!weather.enabled)
return weather; return weather;
weather.show_town_name = tbl["show_town_name"].value_or<bool>(false); weather.api_key = *apiKey;
weather.units = tbl["units"].value<String>().value_or("metric"); weather.show_town_name = tbl["show_town_name"].value_or(false);
weather.units = tbl["units"].value_or("metric");
if (const toml::node_view<const toml::node> location = tbl["location"]) { if (const toml::node_view<const toml::node> location = tbl["location"]) {
if (location.is_string()) { if (location.is_string())
weather.location = location.value<String>().value(); weather.location = *location.value<String>();
} else if (location.is_table()) { else if (location.is_table())
const auto* coord = location.as_table(); weather.location = Coords {
Coords coords; .lat = *location.as_table()->get("lat")->value<double>(),
coords.lat = coord->get("lat")->value<double>().value(); .lon = *location.as_table()->get("lon")->value<double>(),
coords.lon = coord->get("lon")->value<double>().value(); };
weather.location = coords; else
} else {
throw std::runtime_error("Invalid location type"); throw std::runtime_error("Invalid location type");
}
} }
return weather; return weather;
@ -105,18 +90,15 @@ struct Config {
Weather weather; Weather weather;
static fn fromToml(const toml::table& tbl) -> Config { static fn fromToml(const toml::table& tbl) -> Config {
Config cfg; const toml::node_view genTbl = tbl["general"];
const toml::node_view npTbl = tbl["now_playing"];
const toml::node_view wthTbl = tbl["weather"];
if (const auto* general = tbl["general"].as_table()) return {
cfg.general = General::fromToml(*general); .general = genTbl.is_table() ? General::fromToml(*genTbl.as_table()) : General {},
.now_playing = npTbl.is_table() ? NowPlaying::fromToml(*npTbl.as_table()) : NowPlaying {},
if (const auto* nowPlaying = tbl["now_playing"].as_table()) .weather = wthTbl.is_table() ? Weather::fromToml(*wthTbl.as_table()) : Weather {},
cfg.now_playing = NowPlaying::fromToml(*nowPlaying); };
if (const auto* weather = tbl["weather"].as_table())
cfg.weather = Weather::fromToml(*weather);
return cfg;
} }
static fn getInstance() -> Config; static fn getInstance() -> Config;

View file

@ -1,6 +1,5 @@
#include <chrono> #include <chrono>
#include <curl/curl.h> #include <curl/curl.h>
#include <expected>
#include <filesystem> #include <filesystem>
#include <fstream> #include <fstream>
@ -15,27 +14,27 @@ using namespace std::string_literals;
namespace { namespace {
constexpr glz::opts glaze_opts = { .error_on_unknown_keys = false }; constexpr glz::opts glaze_opts = { .error_on_unknown_keys = false };
fn GetCachePath() -> std::expected<fs::path, String> { fn GetCachePath() -> Result<fs::path, String> {
std::error_code errc; std::error_code errc;
fs::path cachePath = fs::temp_directory_path(errc); fs::path cachePath = fs::temp_directory_path(errc);
if (errc) if (errc)
return std::unexpected("Failed to get temp directory: " + errc.message()); return Err("Failed to get temp directory: " + errc.message());
cachePath /= "weather_cache.json"; cachePath /= "weather_cache.json";
return cachePath; return cachePath;
} }
fn ReadCacheFromFile() -> std::expected<WeatherOutput, String> { fn ReadCacheFromFile() -> Result<WeatherOutput, String> {
std::expected<fs::path, String> cachePath = GetCachePath(); Result<fs::path, String> cachePath = GetCachePath();
if (!cachePath) if (!cachePath)
return std::unexpected(cachePath.error()); return Err(cachePath.error());
std::ifstream ifs(*cachePath, std::ios::binary); std::ifstream ifs(*cachePath, std::ios::binary);
if (!ifs.is_open()) if (!ifs.is_open())
return std::unexpected("Cache file not found: " + cachePath->string()); return Err("Cache file not found: " + cachePath->string());
DEBUG_LOG("Reading from cache file..."); DEBUG_LOG("Reading from cache file...");
@ -44,18 +43,18 @@ namespace {
WeatherOutput result; WeatherOutput result;
if (const glz::error_ctx errc = glz::read<glaze_opts>(result, content); errc.ec != glz::error_code::none) if (const glz::error_ctx errc = glz::read<glaze_opts>(result, content); errc.ec != glz::error_code::none)
return std::unexpected("JSON parse error: " + glz::format_error(errc, content)); return Err("JSON parse error: " + glz::format_error(errc, content));
DEBUG_LOG("Successfully read from cache file."); DEBUG_LOG("Successfully read from cache file.");
return result; return result;
} catch (const std::exception& e) { return std::unexpected("Error reading cache: "s + e.what()); } } catch (const std::exception& e) { return Err("Error reading cache: "s + e.what()); }
} }
fn WriteCacheToFile(const WeatherOutput& data) -> std::expected<void, String> { fn WriteCacheToFile(const WeatherOutput& data) -> Result<void, String> {
std::expected<fs::path, String> cachePath = GetCachePath(); Result<fs::path, String> cachePath = GetCachePath();
if (!cachePath) if (!cachePath)
return std::unexpected(cachePath.error()); return Err(cachePath.error());
DEBUG_LOG("Writing to cache file..."); DEBUG_LOG("Writing to cache file...");
fs::path tempPath = *cachePath; fs::path tempPath = *cachePath;
@ -65,28 +64,28 @@ namespace {
{ {
std::ofstream ofs(tempPath, std::ios::binary | std::ios::trunc); std::ofstream ofs(tempPath, std::ios::binary | std::ios::trunc);
if (!ofs.is_open()) if (!ofs.is_open())
return std::unexpected("Failed to open temp file: " + tempPath.string()); return Err("Failed to open temp file: " + tempPath.string());
String jsonStr; String jsonStr;
if (const glz::error_ctx errc = glz::write_json(data, jsonStr); errc.ec != glz::error_code::none) if (const glz::error_ctx errc = glz::write_json(data, jsonStr); errc.ec != glz::error_code::none)
return std::unexpected("JSON serialization error: " + glz::format_error(errc, jsonStr)); return Err("JSON serialization error: " + glz::format_error(errc, jsonStr));
ofs << jsonStr; ofs << jsonStr;
if (!ofs) if (!ofs)
return std::unexpected("Failed to write to temp file"); return Err("Failed to write to temp file");
} }
std::error_code errc; std::error_code errc;
fs::rename(tempPath, *cachePath, errc); fs::rename(tempPath, *cachePath, errc);
if (errc) { if (errc) {
fs::remove(tempPath, errc); fs::remove(tempPath, errc);
return std::unexpected("Failed to replace cache file: " + errc.message()); return Err("Failed to replace cache file: " + errc.message());
} }
DEBUG_LOG("Successfully wrote to cache file."); DEBUG_LOG("Successfully wrote to cache file.");
return {}; return {};
} catch (const std::exception& e) { return std::unexpected("File operation error: "s + e.what()); } } catch (const std::exception& e) { return Err("File operation error: "s + e.what()); }
} }
fn WriteCallback(void* contents, const size_t size, const size_t nmemb, String* str) -> size_t { fn WriteCallback(void* contents, const size_t size, const size_t nmemb, String* str) -> size_t {
@ -95,13 +94,13 @@ namespace {
return totalSize; return totalSize;
} }
fn MakeApiRequest(const String& url) -> std::expected<WeatherOutput, String> { fn MakeApiRequest(const String& url) -> Result<WeatherOutput, String> {
DEBUG_LOG("Making API request to URL: {}", url); DEBUG_LOG("Making API request to URL: {}", url);
CURL* curl = curl_easy_init(); CURL* curl = curl_easy_init();
String responseBuffer; String responseBuffer;
if (!curl) if (!curl)
return std::unexpected("Failed to initialize cURL"); return Err("Failed to initialize cURL");
curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
@ -113,12 +112,12 @@ namespace {
curl_easy_cleanup(curl); curl_easy_cleanup(curl);
if (res != CURLE_OK) if (res != CURLE_OK)
return std::unexpected(std::format("cURL error: {}", curl_easy_strerror(res))); return Err(std::format("cURL error: {}", curl_easy_strerror(res)));
WeatherOutput output; WeatherOutput output;
if (const glz::error_ctx errc = glz::read<glaze_opts>(output, responseBuffer); errc.ec != glz::error_code::none) if (const glz::error_ctx errc = glz::read<glaze_opts>(output, responseBuffer); errc.ec != glz::error_code::none)
return std::unexpected("API response parse error: " + glz::format_error(errc, responseBuffer)); return Err("API response parse error: " + glz::format_error(errc, responseBuffer));
return std::move(output); return std::move(output);
} }
@ -127,7 +126,7 @@ namespace {
fn Weather::getWeatherInfo() const -> WeatherOutput { fn Weather::getWeatherInfo() const -> WeatherOutput {
using namespace std::chrono; using namespace std::chrono;
if (std::expected<WeatherOutput, String> data = ReadCacheFromFile()) { if (Result<WeatherOutput, String> data = ReadCacheFromFile()) {
const WeatherOutput& dataVal = *data; const WeatherOutput& dataVal = *data;
if (const duration<double> cacheAge = system_clock::now() - system_clock::time_point(seconds(dataVal.dt)); if (const duration<double> cacheAge = system_clock::now() - system_clock::time_point(seconds(dataVal.dt));
@ -141,13 +140,13 @@ fn Weather::getWeatherInfo() const -> WeatherOutput {
DEBUG_LOG("Cache error: {}", data.error()); DEBUG_LOG("Cache error: {}", data.error());
} }
fn handleApiResult = [](const std::expected<WeatherOutput, String>& result) -> WeatherOutput { fn handleApiResult = [](const Result<WeatherOutput, String>& result) -> WeatherOutput {
if (!result) { if (!result) {
ERROR_LOG("API request failed: {}", result.error()); ERROR_LOG("API request failed: {}", result.error());
return WeatherOutput {}; return WeatherOutput {};
} }
if (std::expected<void, String> writeResult = WriteCacheToFile(*result); !writeResult) if (Result<void, String> writeResult = WriteCacheToFile(*result); !writeResult)
ERROR_LOG("Failed to write cache: {}", writeResult.error()); ERROR_LOG("Failed to write cache: {}", writeResult.error());
return *result; return *result;
@ -155,7 +154,7 @@ fn Weather::getWeatherInfo() const -> WeatherOutput {
if (std::holds_alternative<String>(location)) { if (std::holds_alternative<String>(location)) {
const auto& city = std::get<String>(location); const auto& city = std::get<String>(location);
char* escaped = curl_easy_escape(nullptr, city.c_str(), static_cast<int>(city.length())); char* escaped = curl_easy_escape(nullptr, city.c_str(), static_cast<i32>(city.length()));
DEBUG_LOG("Requesting city: {}", escaped); DEBUG_LOG("Requesting city: {}", escaped);
const String apiUrl = const String apiUrl =

View file

@ -4,13 +4,14 @@
#include "../util/types.h" #include "../util/types.h"
// NOLINTBEGIN(readability-identifier-naming) // NOLINTBEGIN(readability-identifier-naming) - Needs to specifically use `glaze`
struct Condition { struct Condition {
String description; String description;
struct glaze { struct glaze {
using T = Condition; using T = Condition;
static constexpr auto value = glz::object("description", &T::description);
static constexpr glz::detail::Object value = glz::object("description", &T::description);
}; };
}; };
@ -18,8 +19,9 @@ struct Main {
f64 temp; f64 temp;
struct glaze { struct glaze {
using T = Main; using T = Main;
static constexpr auto value = glz::object("temp", &T::temp);
static constexpr glz::detail::Object value = glz::object("temp", &T::temp);
}; };
}; };
@ -29,14 +31,16 @@ struct Coords {
}; };
struct WeatherOutput { struct WeatherOutput {
Main main; Main main;
String name; String name;
std::vector<Condition> weather; Vec<Condition> weather;
usize dt; usize dt;
struct glaze { struct glaze {
using T = WeatherOutput; using T = WeatherOutput;
static constexpr auto value = glz::object("main", &T::main, "name", &T::name, "weather", &T::weather, "dt", &T::dt);
static constexpr glz::detail::Object value =
glz::object("main", &T::main, "name", &T::name, "weather", &T::weather, "dt", &T::dt);
}; };
}; };
// NOLINTEND(readability-identifier-naming) // NOLINTEND(readability-identifier-naming)

View file

@ -1,5 +1,4 @@
#include <chrono> #include <chrono>
#include <expected>
#include <ftxui/dom/elements.hpp> #include <ftxui/dom/elements.hpp>
#include <ftxui/screen/color.hpp> #include <ftxui/screen/color.hpp>
#include <ftxui/screen/screen.hpp> #include <ftxui/screen/screen.hpp>
@ -22,7 +21,7 @@ constexpr u64 GIB = 1'073'741'824;
template <> template <>
struct std::formatter<BytesToGiB> : std::formatter<double> { struct std::formatter<BytesToGiB> : std::formatter<double> {
auto format(const BytesToGiB& BTG, auto& ctx) const { fn format(const BytesToGiB& BTG, auto& ctx) const {
return std::format_to(ctx.out(), "{:.2f}GiB", static_cast<f64>(BTG.value) / GIB); return std::format_to(ctx.out(), "{:.2f}GiB", static_cast<f64>(BTG.value) / GIB);
} }
}; };
@ -30,7 +29,7 @@ struct std::formatter<BytesToGiB> : std::formatter<double> {
namespace ui { namespace ui {
using ftxui::Color; using ftxui::Color;
constexpr int MAX_PARAGRAPH_LENGTH = 30; constexpr i32 MAX_PARAGRAPH_LENGTH = 30;
// Color themes // Color themes
struct Theme { struct Theme {
@ -101,7 +100,7 @@ namespace ui {
namespace { namespace {
template <typename T, typename E, typename ValueFunc, typename ErrorFunc> template <typename T, typename E, typename ValueFunc, typename ErrorFunc>
fn expected_visit(const std::expected<T, E>& exp, ValueFunc value_func, ErrorFunc error_func) { fn visit_result(const Result<T, E>& exp, ValueFunc value_func, ErrorFunc error_func) {
if (exp.has_value()) if (exp.has_value())
return value_func(*exp); return value_func(*exp);
@ -117,7 +116,7 @@ namespace {
u32 day = static_cast<u32>(ymd.day()); u32 day = static_cast<u32>(ymd.day());
const char* suffix = static_cast<const char*>( CStr suffix = static_cast<CStr>(
(day >= 11 && day <= 13) ? "th" (day >= 11 && day <= 13) ? "th"
: (day % 10 == 1) ? "st" : (day % 10 == 1) ? "st"
: (day % 10 == 2) ? "nd" : (day % 10 == 2) ? "nd"
@ -129,18 +128,18 @@ namespace {
} }
struct SystemData { struct SystemData {
String date; String date;
String host; String host;
String kernel_version; String kernel_version;
std::expected<String, String> os_version; Result<String, String> os_version;
std::expected<u64, String> mem_info; Result<u64, String> mem_info;
std::optional<String> desktop_environment; Option<String> desktop_environment;
String window_manager; String window_manager;
std::optional<std::expected<String, NowPlayingError>> now_playing; Option<Result<String, NowPlayingError>> now_playing;
std::optional<WeatherOutput> weather_info; Option<WeatherOutput> weather_info;
u64 disk_used; u64 disk_used;
u64 disk_total; u64 disk_total;
String shell; String shell;
static fn fetchSystemData(const Config& config) -> SystemData { static fn fetchSystemData(const Config& config) -> SystemData {
SystemData data; SystemData data;
@ -163,8 +162,8 @@ namespace {
}); });
// Conditional tasks // Conditional tasks
std::future<WeatherOutput> weather; std::future<WeatherOutput> weather;
std::future<std::expected<String, NowPlayingError>> nowPlaying; std::future<Result<String, NowPlayingError>> nowPlaying;
if (config.weather.enabled) if (config.weather.enabled)
weather = std::async(std::launch::async, [&config] { return config.weather.getWeatherInfo(); }); weather = std::async(std::launch::async, [&config] { return config.weather.getWeatherInfo(); });
@ -191,7 +190,7 @@ namespace {
fn CreateColorCircles() -> Element { fn CreateColorCircles() -> Element {
return hbox( return hbox(
std::views::iota(0, 16) | std::views::transform([](int colorIndex) { std::views::iota(0, 16) | std::views::transform([](i32 colorIndex) {
return hbox({ text("") | bold | color(static_cast<Color::Palette256>(colorIndex)), text(" ") }); return hbox({ text("") | bold | color(static_cast<Color::Palette256>(colorIndex)), text(" ") });
}) | }) |
std::ranges::to<Elements>() std::ranges::to<Elements>()
@ -224,7 +223,7 @@ namespace {
return hbox( return hbox(
{ {
text(String(icon)) | color(ui::DEFAULT_THEME.icon), text(String(icon)) | color(ui::DEFAULT_THEME.icon),
text(String(static_cast<const char*>(label))) | color(ui::DEFAULT_THEME.label), text(String(static_cast<CStr>(label))) | color(ui::DEFAULT_THEME.label),
filler(), filler(),
text(String(value)) | color(ui::DEFAULT_THEME.value), text(String(value)) | color(ui::DEFAULT_THEME.value),
text(" "), text(" "),
@ -283,13 +282,13 @@ namespace {
if (!data.kernel_version.empty()) if (!data.kernel_version.empty())
content.push_back(createRow(kernelIcon, "Kernel", data.kernel_version)); content.push_back(createRow(kernelIcon, "Kernel", data.kernel_version));
expected_visit( visit_result(
data.os_version, data.os_version,
[&](const String& version) { content.push_back(createRow(String(osIcon), "OS", version)); }, [&](const String& version) { content.push_back(createRow(String(osIcon), "OS", version)); },
[](const String& error) { ERROR_LOG("Failed to get OS version: {}", error); } [](const String& error) { ERROR_LOG("Failed to get OS version: {}", error); }
); );
expected_visit( visit_result(
data.mem_info, data.mem_info,
[&](const u64& mem) { content.push_back(createRow(memoryIcon, "RAM", std::format("{}", BytesToGiB { mem }))); }, [&](const u64& mem) { content.push_back(createRow(memoryIcon, "RAM", std::format("{}", BytesToGiB { mem }))); },
[](const String& error) { ERROR_LOG("Failed to get memory info: {}", error); } [](const String& error) { ERROR_LOG("Failed to get memory info: {}", error); }
@ -312,8 +311,7 @@ namespace {
// Now Playing row // Now Playing row
if (nowPlayingEnabled && data.now_playing.has_value()) { if (nowPlayingEnabled && data.now_playing.has_value()) {
if (const std::expected<String, NowPlayingError>& nowPlayingResult = *data.now_playing; if (const Result<String, NowPlayingError>& nowPlayingResult = *data.now_playing; nowPlayingResult.has_value()) {
nowPlayingResult.has_value()) {
const String& npText = *nowPlayingResult; const String& npText = *nowPlayingResult;
content.push_back(separator() | color(ui::DEFAULT_THEME.border)); content.push_back(separator() | color(ui::DEFAULT_THEME.border));
@ -345,19 +343,12 @@ namespace {
#pragma clang diagnostic pop #pragma clang diagnostic pop
} }
#ifdef __linux__
if (std::holds_alternative<LinuxError>(error))
DEBUG_LOG("DBus error: {}", std::get<LinuxError>(error));
#endif
#ifdef _WIN32 #ifdef _WIN32
if (std::holds_alternative<WindowsError>(error)) if (std::holds_alternative<WindowsError>(error))
DEBUG_LOG("WinRT error: {}", to_string(std::get<WindowsError>(error).message())); DEBUG_LOG("WinRT error: {}", to_string(std::get<WindowsError>(error).message()));
#endif #else
if (std::holds_alternative<String>(error))
#ifdef __APPLE__ DEBUG_LOG("NowPlaying error: {}", std::get<String>(error));
if (std::holds_alternative<MacError>(error))
DEBUG_LOG("CoreAudio error: {}", std::get<MacError>(error));
#endif #endif
} }
} }

View file

@ -1,111 +0,0 @@
#ifdef __FreeBSD__
#include <fstream>
#include <iostream>
#include <sdbus-c++/sdbus-c++.h>
#include <sys/sysctl.h>
#include "os.h"
fn GetMemInfo() -> u64 {
u64 mem = 0;
usize size = sizeof(mem);
sysctlbyname("hw.physmem", &mem, &size, nullptr, 0);
return mem;
}
fn GetOSVersion() -> string {
std::ifstream file("/etc/os-release");
if (!file.is_open()) {
std::cerr << "Failed to open /etc/os-release" << std::endl;
return ""; // Return empty string indicating failure
}
string line;
const string prefix = "PRETTY_NAME=";
while (std::getline(file, line)) {
if (line.find(prefix) == 0) {
string prettyName = line.substr(prefix.size());
// Remove surrounding quotes if present
if (!prettyName.empty() && prettyName.front() == '"' && prettyName.back() == '"')
prettyName = prettyName.substr(1, prettyName.size() - 2);
return prettyName;
}
}
return ""; // Return empty string if PRETTY_NAME= line not found
}
fn GetMprisPlayers(sdbus::IConnection& connection) -> std::vector<string> {
const sdbus::ServiceName dbusInterface = sdbus::ServiceName("org.freedesktop.DBus");
const sdbus::ObjectPath dbusObjectPath = sdbus::ObjectPath("/org/freedesktop/DBus");
const char* dbusMethodListNames = "ListNames";
const std::unique_ptr<sdbus::IProxy> dbusProxy = createProxy(connection, dbusInterface, dbusObjectPath);
std::vector<string> names;
dbusProxy->callMethod(dbusMethodListNames).onInterface(dbusInterface).storeResultsTo(names);
std::vector<string> mprisPlayers;
for (const std::basic_string<char>& name : names)
if (const char* mprisInterfaceName = "org.mpris.MediaPlayer2"; name.find(mprisInterfaceName) != String::npos)
mprisPlayers.push_back(name);
return mprisPlayers;
}
fn GetActivePlayer(const std::vector<string>& mprisPlayers) -> string {
if (!mprisPlayers.empty())
return mprisPlayers.front();
return "";
}
fn GetNowPlaying() -> string {
try {
const char *playerObjectPath = "/org/mpris/MediaPlayer2", *playerInterfaceName = "org.mpris.MediaPlayer2.Player";
std::unique_ptr<sdbus::IConnection> connection = sdbus::createSessionBusConnection();
std::vector<string> mprisPlayers = GetMprisPlayers(*connection);
if (mprisPlayers.empty())
return "";
string activePlayer = GetActivePlayer(mprisPlayers);
if (activePlayer.empty())
return "";
auto playerProxy =
sdbus::createProxy(*connection, sdbus::ServiceName(activePlayer), sdbus::ObjectPath(playerObjectPath));
sdbus::Variant metadataVariant = playerProxy->getProperty("Metadata").onInterface(playerInterfaceName);
if (metadataVariant.containsValueOfType<std::map<String, sdbus::Variant>>()) {
const auto& metadata = metadataVariant.get<std::map<String, sdbus::Variant>>();
auto iter = metadata.find("xesam:title");
if (iter != metadata.end() && iter->second.containsValueOfType<String>())
return iter->second.get<String>();
}
} catch (const sdbus::Error& e) {
if (e.getName() != "com.github.altdesktop.playerctld.NoActivePlayer")
return std::format("Error: {}", e.what());
return "No active player";
}
return "";
}
#endif

View file

@ -1,109 +1,68 @@
#ifdef __linux__ #ifdef __linux__
// clang-format off
#include <dbus-cxx.h> // needs to be at top for Success/None
// clang-format on
#include <X11/Xatom.h> #include <X11/Xatom.h>
#include <X11/Xlib.h> #include <X11/Xlib.h>
#include <algorithm>
#include <cstring>
#include <dirent.h>
#include <expected>
#include <filesystem>
#include <fstream> #include <fstream>
#include <memory>
#include <mutex>
#include <optional>
#include <ranges>
#include <sys/socket.h> #include <sys/socket.h>
#include <sys/statvfs.h> #include <sys/statvfs.h>
#include <sys/sysinfo.h>
#include <sys/utsname.h> #include <sys/utsname.h>
#include <unistd.h> #include <system_error>
#include <vector>
#include <wayland-client.h> #include <wayland-client.h>
#include "os.h" #include "os.h"
#include "src/os/linux/display_guards.h"
#include "src/util/macros.h" #include "src/util/macros.h"
#ifdef Success
#undef Success
#endif
#ifdef None
#undef None
#endif
#include <dbus-cxx.h>
using std::expected;
using std::optional;
namespace fs = std::filesystem; namespace fs = std::filesystem;
using namespace std::literals::string_view_literals;
namespace { namespace {
using std::array; using os::linux::DisplayGuard;
using std::bit_cast; using os::linux::WaylandDisplayGuard;
using std::getenv;
using std::ifstream;
using std::istreambuf_iterator;
using std::less;
using std::lock_guard;
using std::mutex;
using std::nullopt;
using std::ofstream;
using std::pair;
using std::string_view;
using std::to_string;
using std::unexpected;
using std::vector;
using std::ranges::is_sorted;
using std::ranges::lower_bound;
using std::ranges::replace;
using std::ranges::subrange;
using std::ranges::transform;
fn GetX11WindowManager() -> String { fn GetX11WindowManager() -> String {
Display* display = XOpenDisplay(nullptr); DisplayGuard display;
// If XOpenDisplay fails, likely in a TTY
if (!display) if (!display)
return ""; return "";
Atom supportingWmCheck = XInternAtom(display, "_NET_SUPPORTING_WM_CHECK", False); Atom supportingWmCheck = XInternAtom(display.get(), "_NET_SUPPORTING_WM_CHECK", False);
Atom wmName = XInternAtom(display, "_NET_WM_NAME", False); Atom wmName = XInternAtom(display.get(), "_NET_WM_NAME", False);
Atom utf8String = XInternAtom(display, "UTF8_STRING", False); Atom utf8String = XInternAtom(display.get(), "UTF8_STRING", False);
#pragma clang diagnostic push Window root = display.defaultRootWindow();
#pragma clang diagnostic ignored "-Wold-style-cast"
#pragma clang diagnostic ignored "-Wunsafe-buffer-usage"
Window root = DefaultRootWindow(display); // NOLINT
#pragma clang diagnostic pop
Window wmWindow = 0; Window wmWindow = 0;
Atom actualType = 0; Atom actualType = 0;
int actualFormat = 0; i32 actualFormat = 0;
unsigned long nitems = 0, bytesAfter = 0; u64 nitems = 0, bytesAfter = 0;
unsigned char* data = nullptr; u8* data = nullptr;
if (XGetWindowProperty( if (XGetWindowProperty(
display, display.get(),
root, root,
supportingWmCheck, supportingWmCheck,
0, 0,
1, 1,
False, False,
// XA_WINDOW XA_WINDOW,
static_cast<Atom>(33),
&actualType, &actualType,
&actualFormat, &actualFormat,
&nitems, &nitems,
&bytesAfter, &bytesAfter,
&data &data
) == 0 && ) == Success &&
data) { data) {
wmWindow = *bit_cast<Window*>(data); UniquePointer<u8, decltype(&XFree)> dataGuard(data, XFree);
XFree(data); wmWindow = *std::bit_cast<Window*>(data);
data = nullptr;
u8* nameData = nullptr;
if (XGetWindowProperty( if (XGetWindowProperty(
display, display.get(),
wmWindow, wmWindow,
wmName, wmName,
0, 0,
@ -114,525 +73,399 @@ namespace {
&actualFormat, &actualFormat,
&nitems, &nitems,
&bytesAfter, &bytesAfter,
&data &nameData
) == 0 && ) == Success &&
data) { nameData) {
String name(bit_cast<char*>(data)); UniquePointer<u8, decltype(&XFree)> nameGuard(nameData, XFree);
XFree(data); return std::bit_cast<char*>(nameData);
XCloseDisplay(display);
return name;
} }
} }
XCloseDisplay(display);
return "Unknown (X11)"; return "Unknown (X11)";
} }
fn TrimHyprlandWrapper(const String& input) -> String { fn ReadProcessCmdline(i32 pid) -> String {
if (input.contains("hyprland")) std::ifstream cmdlineFile("/proc/" + std::to_string(pid) + "/cmdline");
return "Hyprland"; String cmdline;
return input;
}
fn ReadProcessCmdline(int pid) -> String {
String path = "/proc/" + to_string(pid) + "/cmdline";
ifstream cmdlineFile(path);
String cmdline;
if (getline(cmdlineFile, cmdline)) { if (getline(cmdlineFile, cmdline)) {
// Replace null bytes with spaces std::ranges::replace(cmdline, '\0', ' ');
replace(cmdline, '\0', ' ');
return cmdline; return cmdline;
} }
return ""; return "";
} }
fn DetectHyprlandSpecific() -> String { fn DetectHyprlandSpecific() -> String {
// Check environment variables first Result<String, EnvError> xdgCurrentDesktop = GetEnv("XDG_CURRENT_DESKTOP");
const char* xdgCurrentDesktop = getenv("XDG_CURRENT_DESKTOP");
if (xdgCurrentDesktop && strcasestr(xdgCurrentDesktop, "hyprland")) if (xdgCurrentDesktop) {
std::ranges::transform(*xdgCurrentDesktop, xdgCurrentDesktop->begin(), ::tolower);
if (xdgCurrentDesktop->contains("hyprland"))
return "Hyprland";
}
if (GetEnv("HYPRLAND_INSTANCE_SIGNATURE"))
return "Hyprland"; return "Hyprland";
// Check for Hyprland's specific environment variable if (fs::exists("/run/user/" + std::to_string(getuid()) + "/hypr"))
if (getenv("HYPRLAND_INSTANCE_SIGNATURE"))
return "Hyprland";
// Check for Hyprland socket
if (fs::exists("/run/user/" + to_string(getuid()) + "/hypr"))
return "Hyprland"; return "Hyprland";
return ""; return "";
} }
fn GetWaylandCompositor() -> String { fn GetWaylandCompositor() -> String {
// First try Hyprland-specific detection
String hypr = DetectHyprlandSpecific(); String hypr = DetectHyprlandSpecific();
if (!hypr.empty()) if (!hypr.empty())
return hypr; return hypr;
// Then try the standard Wayland detection WaylandDisplayGuard display;
wl_display* display = wl_display_connect(nullptr);
if (!display) if (!display)
return ""; return "";
int fileDescriptor = wl_display_get_fd(display); i32 fileDescriptor = display.fd();
struct ucred cred; struct ucred cred;
socklen_t len = sizeof(cred); socklen_t len = sizeof(cred);
if (getsockopt(fileDescriptor, SOL_SOCKET, SO_PEERCRED, &cred, &len) == -1) { if (getsockopt(fileDescriptor, SOL_SOCKET, SO_PEERCRED, &cred, &len) == -1)
wl_display_disconnect(display);
return ""; return "";
}
// Read both comm and cmdline
String compositorName; String compositorName;
// 1. Check comm (might be wrapped) String commPath = "/proc/" + std::to_string(cred.pid) + "/comm";
String commPath = "/proc/" + to_string(cred.pid) + "/comm"; std::ifstream commFile(commPath);
ifstream commFile(commPath);
if (commFile >> compositorName) { if (commFile >> compositorName) {
subrange removedRange = std::ranges::remove(compositorName, '\n'); std::ranges::subrange removedRange = std::ranges::remove(compositorName, '\n');
compositorName.erase(removedRange.begin(), compositorName.end()); compositorName.erase(removedRange.begin(), removedRange.end());
} }
// 2. Check cmdline for actual binary reference
String cmdline = ReadProcessCmdline(cred.pid); String cmdline = ReadProcessCmdline(cred.pid);
if (cmdline.contains("hyprland")) { if (cmdline.contains("hyprland"))
wl_display_disconnect(display);
return "Hyprland"; return "Hyprland";
}
// 3. Check exe symlink String exePath = "/proc/" + std::to_string(cred.pid) + "/exe";
String exePath = "/proc/" + to_string(cred.pid) + "/exe"; Array<char, PATH_MAX> buf;
array<char, PATH_MAX> buf;
ssize_t lenBuf = readlink(exePath.c_str(), buf.data(), buf.size() - 1); ssize_t lenBuf = readlink(exePath.c_str(), buf.data(), buf.size() - 1);
if (lenBuf != -1) { if (lenBuf != -1) {
buf.at(static_cast<usize>(lenBuf)) = '\0'; buf.at(static_cast<usize>(lenBuf)) = '\0';
String exe(buf.data()); String exe(buf.data());
if (exe.contains("hyprland")) { if (exe.contains("hyprland"))
wl_display_disconnect(display);
return "Hyprland"; return "Hyprland";
}
} }
wl_display_disconnect(display); return compositorName.contains("hyprland") ? "Hyprland" : compositorName;
// Final cleanup of wrapper names
return TrimHyprlandWrapper(compositorName);
} }
fn DetectFromEnvVars() -> optional<String> { fn DetectFromEnvVars() -> Option<String> {
// Use RAII to guard against concurrent env modifications if (Result<String, EnvError> xdgCurrentDesktop = GetEnv("XDG_CURRENT_DESKTOP")) {
static mutex EnvMutex; const size_t colon = xdgCurrentDesktop->find(':');
lock_guard<mutex> lock(EnvMutex);
// XDG_CURRENT_DESKTOP if (colon != String::npos)
if (const char* xdgCurrentDesktop = getenv("XDG_CURRENT_DESKTOP")) { return xdgCurrentDesktop->substr(0, colon);
const string_view sview(xdgCurrentDesktop);
const size_t colon = sview.find(':'); return *xdgCurrentDesktop;
return String(sview.substr(0, colon)); // Direct construct from view
} }
// DESKTOP_SESSION if (Result<String, EnvError> desktopSession = GetEnv("DESKTOP_SESSION"))
if (const char* desktopSession = getenv("DESKTOP_SESSION")) return *desktopSession;
return String(string_view(desktopSession)); // Avoid intermediate view storage
return nullopt; return None;
} }
fn DetectFromSessionFiles() -> optional<String> { fn DetectFromSessionFiles() -> Option<String> {
static constexpr array<pair<string_view, string_view>, 12> DE_PATTERNS = { // clang-format off
// clang-format off static constexpr Array<Pair<StringView, StringView>, 12> DE_PATTERNS = {{
pair { "Budgie"sv, "budgie"sv }, { "budgie", "Budgie" },
pair { "Cinnamon"sv, "cinnamon"sv }, { "cinnamon", "Cinnamon" },
pair { "LXQt"sv, "lxqt"sv }, { "lxqt", "LXQt" },
pair { "MATE"sv, "mate"sv }, { "mate", "MATE" },
pair { "Unity"sv, "unity"sv }, { "unity", "Unity" },
pair { "gnome"sv, "GNOME"sv }, { "gnome", "GNOME" },
pair { "gnome-wayland"sv, "GNOME"sv }, { "gnome-wayland", "GNOME" },
pair { "gnome-xorg"sv, "GNOME"sv }, { "gnome-xorg", "GNOME" },
pair { "kde"sv, "KDE"sv }, { "kde", "KDE" },
pair { "plasma"sv, "KDE"sv }, { "plasma", "KDE" },
pair { "plasmax11"sv, "KDE"sv }, { "plasmax11", "KDE" },
pair { "xfce"sv, "XFCE"sv }, { "xfce", "XFCE" },
// clang-format on }};
}; // clang-format on
static_assert(is_sorted(DE_PATTERNS, {}, &pair<string_view, string_view>::first)); static constexpr Array<StringView, 2> SESSION_PATHS = { "/usr/share/xsessions", "/usr/share/wayland-sessions" };
// Precomputed session paths for (const StringView& path : SESSION_PATHS) {
static constexpr array<string_view, 2> SESSION_PATHS = { "/usr/share/xsessions", "/usr/share/wayland-sessions" };
// Single memory reserve for lowercase conversions
String lowercaseStem;
lowercaseStem.reserve(32);
for (const auto& path : SESSION_PATHS) {
if (!fs::exists(path)) if (!fs::exists(path))
continue; continue;
for (const auto& entry : fs::directory_iterator(path)) { for (const fs::directory_entry& entry : fs::directory_iterator(path)) {
if (!entry.is_regular_file()) if (!entry.is_regular_file())
continue; continue;
// Reuse buffer String lowercaseStem = entry.path().stem().string();
lowercaseStem = entry.path().stem().string(); std::ranges::transform(lowercaseStem, lowercaseStem.begin(), ::tolower);
transform(lowercaseStem, lowercaseStem.begin(), ::tolower);
// Modern ranges version for (const Pair pattern : DE_PATTERNS)
const pair<string_view, string_view>* const patternIter = lower_bound( if (pattern.first == lowercaseStem)
DE_PATTERNS, lowercaseStem, less {}, &pair<string_view, string_view>::first // Projection return String(pattern.second);
);
if (patternIter != DE_PATTERNS.end() && patternIter->first == lowercaseStem)
return String(patternIter->second);
} }
} }
return nullopt; return None;
} }
fn DetectFromProcesses() -> optional<String> { fn DetectFromProcesses() -> Option<String> {
const array processChecks = { // clang-format off
// clang-format off const Array<Pair<StringView, StringView>, 7> processChecks = {{
pair { "plasmashell"sv, "KDE"sv }, { "plasmashell", "KDE" },
pair { "gnome-shell"sv, "GNOME"sv }, { "gnome-shell", "GNOME" },
pair { "xfce4-session"sv, "XFCE"sv }, { "xfce4-session", "XFCE" },
pair { "mate-session"sv, "MATE"sv }, { "mate-session", "MATE" },
pair { "cinnamon-sessio"sv, "Cinnamon"sv }, { "cinnamon-session", "Cinnamon" },
pair { "budgie-wm"sv, "Budgie"sv }, { "budgie-wm", "Budgie" },
pair { "lxqt-session"sv, "LXQt"sv }, { "lxqt-session", "LXQt" },
// clang-format on }};
}; // clang-format on
ifstream cmdline("/proc/self/environ"); std::ifstream cmdline("/proc/self/environ");
String envVars((istreambuf_iterator<char>(cmdline)), istreambuf_iterator<char>()); String envVars((std::istreambuf_iterator<char>(cmdline)), std::istreambuf_iterator<char>());
for (const auto& [process, deName] : processChecks) for (const auto& [process, deName] : processChecks)
if (envVars.contains(process)) if (envVars.contains(process))
return String(deName); return String(deName);
return nullopt; return None;
} }
fn GetMprisPlayers(const std::shared_ptr<DBus::Connection>& connection) -> expected<vector<String>, NowPlayingError> { fn GetMprisPlayers(const SharedPointer<DBus::Connection>& connection) -> Result<Vec<String>, NowPlayingError> {
try { try {
// Create the method call object SharedPointer<DBus::CallMessage> call =
std::shared_ptr<DBus::CallMessage> call =
DBus::CallMessage::create("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "ListNames"); DBus::CallMessage::create("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "ListNames");
// Send the message synchronously and get the reply SharedPointer<DBus::Message> reply = connection->send_with_reply_blocking(call, 500);
// Timeout parameter might be needed (e.g., 5000 ms)
std::shared_ptr<DBus::Message> reply = connection->send_with_reply_blocking(call, 5000);
// Check if the reply itself is an error type if (!reply) {
if (reply) {
ERROR_LOG("DBus timeout or null reply in ListNames"); ERROR_LOG("DBus timeout or null reply in ListNames");
return unexpected(LinuxError("DBus timeout in ListNames")); return Err("DBus timeout in ListNames");
} }
vector<String> allNamesStd; Vec<String> allNamesStd;
DBus::MessageIterator reader(*reply); DBus::MessageIterator reader(*reply);
reader >> allNamesStd; reader >> allNamesStd;
// Filter for MPRIS players Vec<String> mprisPlayers;
vector<String> mprisPlayers; // Use std::string as String=std::string for (const String& name : allNamesStd)
for (const auto& name : allNamesStd) { if (StringView(name).contains("org.mpris.MediaPlayer2"))
if (string_view(name).contains("org.mpris.MediaPlayer2")) {
mprisPlayers.emplace_back(name); mprisPlayers.emplace_back(name);
}
}
return mprisPlayers; return mprisPlayers;
} catch (const DBus::Error& e) { // Catch specific dbus-cxx exceptions } catch (const DBus::Error& e) {
ERROR_LOG("DBus::Error exception in ListNames: {}", e.what()); ERROR_LOG("DBus::Error exception in ListNames: {}", e.what());
return unexpected(LinuxError(e.what())); return Err(e.what());
} catch (const std::exception& e) { // Catch other potential standard exceptions } catch (const Exception& e) {
ERROR_LOG("Standard exception getting MPRIS players: {}", e.what()); ERROR_LOG("Standard exception getting MPRIS players: {}", e.what());
return unexpected(String(e.what())); return Err(e.what());
} }
} }
// --- Logic remains the same ---
fn GetActivePlayer(const vector<String>& mprisPlayers) -> optional<String> {
if (!mprisPlayers.empty())
return mprisPlayers.front();
return nullopt;
}
} }
fn GetOSVersion() -> expected<String, String> { fn GetOSVersion() -> Result<String, String> {
constexpr const char* path = "/etc/os-release"; constexpr CStr path = "/etc/os-release";
ifstream file(path); std::ifstream file(path);
if (!file.is_open()) if (!file)
return unexpected("Failed to open " + String(path)); return Err(std::format("Failed to open {}", path));
String line; String line;
const String prefix = "PRETTY_NAME="; const String prefix = "PRETTY_NAME=";
while (getline(file, line)) while (getline(file, line))
if (line.starts_with(prefix)) { if (line.starts_with(prefix)) {
String prettyName = line.substr(prefix.size()); StringView valueView = StringView(line).substr(prefix.size());
if (!prettyName.empty() && prettyName.front() == '"' && prettyName.back() == '"') if (!valueView.empty() && valueView.front() == '"' && valueView.back() == '"') {
return prettyName.substr(1, prettyName.size() - 2); valueView.remove_prefix(1);
valueView.remove_suffix(1);
}
return prettyName; return String(valueView);
} }
return unexpected("PRETTY_NAME line not found in " + String(path)); return Err(std::format("PRETTY_NAME line not found in {}", path));
} }
fn GetMemInfo() -> expected<u64, String> { fn GetMemInfo() -> Result<u64, String> {
using std::from_chars, std::errc; struct sysinfo info;
constexpr const char* path = "/proc/meminfo"; if (sysinfo(&info) != 0)
return Err(std::format("sysinfo failed: {}", std::error_code(errno, std::generic_category()).message()));
ifstream input(path); return static_cast<u64>(info.totalram * info.mem_unit);
if (!input.is_open())
return unexpected("Failed to open " + String(path));
String line;
while (getline(input, line)) {
if (line.starts_with("MemTotal")) {
const size_t colonPos = line.find(':');
if (colonPos == String::npos)
return unexpected("Invalid MemTotal line: no colon found");
string_view view(line);
view.remove_prefix(colonPos + 1);
// Trim leading whitespace
const size_t firstNonSpace = view.find_first_not_of(' ');
if (firstNonSpace == string_view::npos)
return unexpected("No number found after colon in MemTotal line");
view.remove_prefix(firstNonSpace);
// Find the end of the numeric part
const size_t end = view.find_first_not_of("0123456789");
if (end != string_view::npos)
view = view.substr(0, end);
// Get pointers via iterators
const char* startPtr = &*view.begin();
const char* endPtr = &*view.end();
u64 value = 0;
const auto result = from_chars(startPtr, endPtr, value);
if (result.ec != errc() || result.ptr != endPtr)
return unexpected("Failed to parse number in MemTotal line");
return value * 1024;
}
}
return unexpected("MemTotal line not found in " + String(path));
} }
fn GetNowPlaying() -> expected<String, NowPlayingError> { fn GetNowPlaying() -> Result<String, NowPlayingError> {
try { try {
// 1. Get Dispatcher and Session Bus Connection SharedPointer<DBus::Dispatcher> dispatcher = DBus::StandaloneDispatcher::create();
std::shared_ptr<DBus::Dispatcher> dispatcher = DBus::StandaloneDispatcher::create();
if (!dispatcher) if (!dispatcher)
return unexpected(LinuxError("Failed to create DBus dispatcher")); return Err("Failed to create DBus dispatcher");
std::shared_ptr<DBus::Connection> connection = dispatcher->create_connection(DBus::BusType::SESSION); SharedPointer<DBus::Connection> connection = dispatcher->create_connection(DBus::BusType::SESSION);
if (!connection) if (!connection)
return unexpected(LinuxError("Failed to connect to session bus")); return Err("Failed to connect to session bus");
// 2. Get list of MPRIS players Result<Vec<String>, NowPlayingError> mprisPlayersResult = GetMprisPlayers(connection);
auto mprisPlayersResult = GetMprisPlayers(connection);
if (!mprisPlayersResult) if (!mprisPlayersResult)
return unexpected(mprisPlayersResult.error()); // Forward the error return Err(mprisPlayersResult.error());
const vector<String>& mprisPlayers = *mprisPlayersResult; const Vec<String>& mprisPlayers = *mprisPlayersResult;
if (mprisPlayers.empty()) if (mprisPlayers.empty())
return unexpected(NowPlayingError { NowPlayingCode::NoPlayers }); return Err(NowPlayingCode::NoPlayers);
// 3. Determine active player String activePlayer = mprisPlayers.front();
optional<String> activePlayerOpt = GetActivePlayer(mprisPlayers);
if (!activePlayerOpt)
return unexpected(NowPlayingError { NowPlayingCode::NoActivePlayer });
// Use std::string for D-Bus service name SharedPointer<DBus::CallMessage> metadataCall =
const String& activePlayerService = *activePlayerOpt; DBus::CallMessage::create(activePlayer, "/org/mpris/MediaPlayer2", "org.freedesktop.DBus.Properties", "Get");
// 4. Call Properties.Get for Metadata (*metadataCall) << "org.mpris.MediaPlayer2.Player" << "Metadata";
const String interfaceNameStd = "org.mpris.MediaPlayer2.Player";
const String propertyNameStd = "Metadata";
// Create call message SharedPointer<DBus::Message> metadataReply = connection->send_with_reply_blocking(metadataCall, 5000);
auto call = DBus::CallMessage::create(
activePlayerService, // Target service
"/org/mpris/MediaPlayer2", // Object path
"org.freedesktop.DBus.Properties", // Interface
"Get"
); // Method name
(*call) << interfaceNameStd << propertyNameStd; String title;
String artist;
// Send message and get reply if (metadataReply && metadataReply->is_valid()) {
std::shared_ptr<DBus::Message> replyMsg = connection->send_with_reply_blocking(call, 5000); // Use a timeout try {
DBus::MessageIterator iter(*metadataReply);
DBus::Variant metadataVariant;
iter >> metadataVariant;
if (!replyMsg) { if (metadataVariant.type() == DBus::DataType::ARRAY) {
ERROR_LOG("DBus timeout or null reply in Properties.Get"); Map<String, DBus::Variant> metadata = metadataVariant.to_map<String, DBus::Variant>();
return unexpected(LinuxError("DBus timeout in Properties.Get"));
auto titleIter = metadata.find("xesam:title");
if (titleIter != metadata.end() && titleIter->second.type() == DBus::DataType::STRING)
title = titleIter->second.to_string();
auto artistIter = metadata.find("xesam:artist");
if (artistIter != metadata.end()) {
if (artistIter->second.type() == DBus::DataType::ARRAY) {
if (Vec<String> artists = artistIter->second.to_vector<String>(); !artists.empty())
artist = artists[0];
} else if (artistIter->second.type() == DBus::DataType::STRING)
artist = artistIter->second.to_string();
}
} else {
ERROR_LOG(
"Metadata variant is not the expected type, expected a{{sv}} but got {}", metadataVariant.signature().str()
);
}
} catch (const DBus::Error& e) {
ERROR_LOG("DBus error processing metadata reply: {}", e.what());
} catch (const Exception& e) { ERROR_LOG("Error processing metadata reply: {}", e.what()); }
} }
// 5. Parse the reply return std::format("{}{}{}", artist, (!artist.empty() && !title.empty()) ? " - " : "", title);
DBus::Variant metadataVariant; } catch (const DBus::Error& e) { return Err(std::format("DBus error: {}", e.what())); } catch (const Exception& e) {
// Create reader/iterator from the message return Err(std::format("General error: {}", e.what()));
DBus::MessageIterator reader(*replyMsg); // Use constructor
// *** Correction: Use get<T> on iterator instead of operator>> ***
reader >> metadataVariant;
// Check the variant's signature
if (metadataVariant.to_signature() != "a{sv}") {
return unexpected("Unexpected reply type for Metadata");
}
String artistStd;
String titleStd;
// Get the dictionary using the templated get<T>() method
std::map<String, DBus::Variant> metadataMap;
auto titleIter = metadataMap.find("xesam:title");
if (titleIter != metadataMap.end() && titleIter->second.to_signature() == "s") {
// Use the cast operator on variant to string
titleStd = static_cast<std::string>(titleIter->second);
}
// For line 525-534
auto artistIter = metadataMap.find("xesam:artist");
if (artistIter != metadataMap.end() && artistIter->second.to_signature() == "as") {
// Cast to vector<String>
std::vector<String> artistsStd = static_cast<std::vector<String>>(artistIter->second);
if (!artistsStd.empty()) {
artistStd = artistsStd.front();
}
}
// 6. Construct result string
String result;
if (!artistStd.empty() && !titleStd.empty())
result = artistStd + " - " + titleStd;
else if (!titleStd.empty())
result = titleStd;
else if (!artistStd.empty())
result = artistStd;
return result;
} catch (const DBus::Error& e) { // Catch specific dbus-cxx exceptions
ERROR_LOG("DBus::Error exception in GetNowPlaying: {}", e.what());
return unexpected(LinuxError(e.what()));
} catch (const std::exception& e) { // Catch other potential standard exceptions
ERROR_LOG("Standard exception in GetNowPlaying: {}", e.what());
return unexpected(String(e.what()));
} }
} }
fn GetWindowManager() -> String { fn GetWindowManager() -> String {
// Check environment variables first const Result<String, EnvError> waylandDisplay = GetEnv("WAYLAND_DISPLAY");
const char* xdgSessionType = getenv("XDG_SESSION_TYPE"); const Result<String, EnvError> xdgSessionType = GetEnv("XDG_SESSION_TYPE");
const char* waylandDisplay = getenv("WAYLAND_DISPLAY");
// Prefer Wayland detection if Wayland session if (waylandDisplay || (xdgSessionType && xdgSessionType->contains("wayland"))) {
if ((waylandDisplay != nullptr) || (xdgSessionType && string_view(xdgSessionType).contains("wayland"))) {
String compositor = GetWaylandCompositor(); String compositor = GetWaylandCompositor();
if (!compositor.empty()) if (!compositor.empty())
return compositor; return compositor;
// Fallback environment check if (const Result<String, EnvError> xdgCurrentDesktop = GetEnv("XDG_CURRENT_DESKTOP")) {
const char* xdgCurrentDesktop = getenv("XDG_CURRENT_DESKTOP"); std::ranges::transform(compositor, compositor.begin(), ::tolower);
if (xdgCurrentDesktop) { if (xdgCurrentDesktop->contains("hyprland"))
String desktop(xdgCurrentDesktop); return "Hyprland";
transform(compositor, compositor.begin(), ::tolower);
if (desktop.contains("hyprland"))
return "hyprland";
} }
} }
// X11 detection if (String x11wm = GetX11WindowManager(); !x11wm.empty())
String x11wm = GetX11WindowManager();
if (!x11wm.empty())
return x11wm; return x11wm;
return "Unknown"; return "Unknown";
} }
fn GetDesktopEnvironment() -> optional<String> { fn GetDesktopEnvironment() -> Option<String> {
// Try environment variables first if (Option<String> desktopEnvironment = DetectFromEnvVars())
if (auto desktopEnvironment = DetectFromEnvVars(); desktopEnvironment.has_value())
return desktopEnvironment; return desktopEnvironment;
// Try session files next if (Option<String> desktopEnvironment = DetectFromSessionFiles())
if (auto desktopEnvironment = DetectFromSessionFiles(); desktopEnvironment.has_value())
return desktopEnvironment; return desktopEnvironment;
// Fallback to process detection
return DetectFromProcesses(); return DetectFromProcesses();
} }
fn GetShell() -> String { fn GetShell() -> String {
const string_view shell = getenv("SHELL"); const Vec<Pair<String, String>> shellMap {
{ "bash", "Bash" },
{ "zsh", "Zsh" },
{ "fish", "Fish" },
{ "nu", "Nushell" },
{ "sh", "SH" }, // sh last because other shells contain "sh"
};
if (shell.ends_with("bash")) if (const Result<String, EnvError> shellPath = GetEnv("SHELL")) {
return "Bash"; for (const auto& shellPair : shellMap)
if (shell.ends_with("zsh")) if (shellPath->contains(shellPair.first))
return "Zsh"; return shellPair.second;
if (shell.ends_with("fish"))
return "Fish";
if (shell.ends_with("nu"))
return "Nushell";
if (shell.ends_with("sh"))
return "SH";
return !shell.empty() ? String(shell) : ""; return *shellPath; // fallback to the raw shell path
}
return "";
} }
fn GetHost() -> String { fn GetHost() -> String {
constexpr const char* path = "/sys/class/dmi/id/product_family"; constexpr CStr path = "/sys/class/dmi/id/product_family";
ifstream file(path); std::ifstream file(path);
if (!file.is_open()) {
if (!file) {
ERROR_LOG("Failed to open {}", path); ERROR_LOG("Failed to open {}", path);
return ""; return "";
} }
String productFamily; String productFamily;
if (!getline(file, productFamily)) { if (!getline(file, productFamily)) {
ERROR_LOG("Failed to read from {}", path); ERROR_LOG("Failed to read from {} (is it empty?)", path);
return ""; return "";
} }
return productFamily; return productFamily.erase(productFamily.find_last_not_of(" \t\n\r") + 1);
} }
fn GetKernelVersion() -> String { fn GetKernelVersion() -> String {
struct utsname uts; struct utsname uts;
if (uname(&uts) == -1) { if (uname(&uts) == -1) {
ERROR_LOG("uname() failed: {}", strerror(errno)); ERROR_LOG("uname() failed: {}", std::error_code(errno, std::generic_category()).message());
return ""; return "";
} }
return static_cast<const char*>(uts.release); return static_cast<CStr>(uts.release);
} }
fn GetDiskUsage() -> pair<u64, u64> { fn GetDiskUsage() -> Pair<u64, u64> {
struct statvfs stat; struct statvfs stat;
if (statvfs("/", &stat) == -1) { if (statvfs("/", &stat) == -1) {
ERROR_LOG("statvfs() failed: {}", strerror(errno)); ERROR_LOG("statvfs() failed: {}", std::error_code(errno, std::generic_category()).message());
return { 0, 0 }; return { 0, 0 };
} }
return { (stat.f_blocks * stat.f_frsize) - (stat.f_bfree * stat.f_frsize), stat.f_blocks * stat.f_frsize }; return { (stat.f_blocks * stat.f_frsize) - (stat.f_bfree * stat.f_frsize), stat.f_blocks * stat.f_frsize };
} }

View file

@ -0,0 +1,69 @@
#ifdef __linux__
#include <utility> // for std::exchange
#include "display_guards.h"
#include "src/util/macros.h"
namespace os::linux {
DisplayGuard::DisplayGuard(CStr name) : m_Display(XOpenDisplay(name)) {}
DisplayGuard::~DisplayGuard() {
if (m_Display)
XCloseDisplay(m_Display);
}
DisplayGuard::DisplayGuard(DisplayGuard&& other) noexcept : m_Display(std::exchange(other.m_Display, nullptr)) {}
fn DisplayGuard::operator=(DisplayGuard&& other) noexcept -> DisplayGuard& {
if (this != &other) {
if (m_Display)
XCloseDisplay(m_Display);
m_Display = std::exchange(other.m_Display, nullptr);
}
return *this;
}
DisplayGuard::operator bool() const { return m_Display != nullptr; }
fn DisplayGuard::get() const -> Display* { return m_Display; }
fn DisplayGuard::defaultRootWindow() const -> Window {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunsafe-buffer-usage"
return DefaultRootWindow(m_Display);
#pragma clang diagnostic pop
}
WaylandDisplayGuard::WaylandDisplayGuard() : m_Display(wl_display_connect(nullptr)) {}
WaylandDisplayGuard::~WaylandDisplayGuard() {
if (m_Display)
wl_display_disconnect(m_Display);
}
WaylandDisplayGuard::WaylandDisplayGuard(WaylandDisplayGuard&& other) noexcept
: m_Display(std::exchange(other.m_Display, nullptr)) {}
fn WaylandDisplayGuard::operator=(WaylandDisplayGuard&& other) noexcept -> WaylandDisplayGuard& {
if (this != &other) {
if (m_Display)
wl_display_disconnect(m_Display);
m_Display = std::exchange(other.m_Display, nullptr);
}
return *this;
}
WaylandDisplayGuard::operator bool() const { return m_Display != nullptr; }
fn WaylandDisplayGuard::get() const -> wl_display* { return m_Display; }
fn WaylandDisplayGuard::fd() const -> i32 { return m_Display ? wl_display_get_fd(m_Display) : -1; }
}
#endif

View file

@ -0,0 +1,69 @@
#pragma once
#ifdef __linux__
#include <X11/Xlib.h>
#include <wayland-client.h>
#include "src/util/macros.h"
namespace os::linux {
/**
* RAII wrapper for X11 Display connections
* Automatically handles resource acquisition and cleanup
*/
class DisplayGuard {
private:
Display* m_Display;
public:
/**
* Opens an X11 display connection
* @param name Display name (nullptr for default)
*/
explicit DisplayGuard(CStr name = nullptr);
~DisplayGuard();
// Non-copyable
DisplayGuard(const DisplayGuard&) = delete;
fn operator=(const DisplayGuard&)->DisplayGuard& = delete;
// Movable
DisplayGuard(DisplayGuard&& other) noexcept;
fn operator=(DisplayGuard&& other) noexcept -> DisplayGuard&;
[[nodiscard]] operator bool() const;
[[nodiscard]] fn get() const -> Display*;
[[nodiscard]] fn defaultRootWindow() const -> Window;
};
/**
* RAII wrapper for Wayland display connections
* Automatically handles resource acquisition and cleanup
*/
class WaylandDisplayGuard {
private:
wl_display* m_Display;
public:
/**
* Opens a Wayland display connection
*/
WaylandDisplayGuard();
~WaylandDisplayGuard();
// Non-copyable
WaylandDisplayGuard(const WaylandDisplayGuard&) = delete;
fn operator=(const WaylandDisplayGuard&)->WaylandDisplayGuard& = delete;
// Movable
WaylandDisplayGuard(WaylandDisplayGuard&& other) noexcept;
fn operator=(WaylandDisplayGuard&& other) noexcept -> WaylandDisplayGuard&;
[[nodiscard]] operator bool() const;
[[nodiscard]] fn get() const -> wl_display*;
[[nodiscard]] fn fd() const -> i32;
};
}
#endif

View file

@ -1,9 +1 @@
#include "src/os/linux/pkg_count.h" #include "src/os/linux/pkg_count.h"
namespace fs = std::filesystem;
fn GetApkPackageCount() -> optional<usize> {
fs::path apkDbPath("/lib/apk/db/installed");
return std::nullopt;
}

View file

@ -2,37 +2,35 @@
#include "src/util/macros.h" #include "src/util/macros.h"
using std::optional;
// Get package count from dpkg (Debian/Ubuntu) // Get package count from dpkg (Debian/Ubuntu)
fn GetDpkgPackageCount() -> optional<usize>; fn GetDpkgPackageCount() -> Option<usize>;
// Get package count from RPM (Red Hat/Fedora/CentOS) // Get package count from RPM (Red Hat/Fedora/CentOS)
fn GetRpmPackageCount() -> std::optional<usize>; fn GetRpmPackageCount() -> Option<usize>;
// Get package count from pacman (Arch Linux) // Get package count from pacman (Arch Linux)
fn GetPacmanPackageCount() -> std::optional<usize>; fn GetPacmanPackageCount() -> Option<usize>;
// Get package count from Portage (Gentoo) // Get package count from Portage (Gentoo)
fn GetPortagePackageCount() -> std::optional<usize>; fn GetPortagePackageCount() -> Option<usize>;
// Get package count from zypper (openSUSE) // Get package count from zypper (openSUSE)
fn GetZypperPackageCount() -> std::optional<usize>; fn GetZypperPackageCount() -> Option<usize>;
// Get package count from apk (Alpine) // Get package count from apk (Alpine)
fn GetApkPackageCount() -> std::optional<usize>; fn GetApkPackageCount() -> Option<usize>;
// Get package count from nix // Get package count from nix
fn GetNixPackageCount() -> std::optional<usize>; fn GetNixPackageCount() -> Option<usize>;
// Get package count from flatpak // Get package count from flatpak
fn GetFlatpakPackageCount() -> std::optional<usize>; fn GetFlatpakPackageCount() -> Option<usize>;
// Get package count from snap // Get package count from snap
fn GetSnapPackageCount() -> std::optional<usize>; fn GetSnapPackageCount() -> Option<usize>;
// Get package count from AppImage // Get package count from AppImage
fn GetAppimagePackageCount() -> std::optional<usize>; fn GetAppimagePackageCount() -> Option<usize>;
// Get total package count from all available package managers // Get total package count from all available package managers
fn GetTotalPackageCount() -> std::optional<usize>; fn GetTotalPackageCount() -> Option<usize>;

View file

@ -1,6 +1,5 @@
#ifdef __APPLE__ #ifdef __APPLE__
#include <expected>
#include <flat_map> #include <flat_map>
#include <span> #include <span>
#include <sys/statvfs.h> #include <sys/statvfs.h>
@ -10,21 +9,21 @@
#include "os.h" #include "os.h"
#include "src/util/types.h" #include "src/util/types.h"
fn GetMemInfo() -> expected<u64, String> { fn GetMemInfo() -> Result<u64, String> {
u64 mem = 0; u64 mem = 0;
usize size = sizeof(mem); usize size = sizeof(mem);
if (sysctlbyname("hw.memsize", &mem, &size, nullptr, 0) == -1) if (sysctlbyname("hw.memsize", &mem, &size, nullptr, 0) == -1)
return std::unexpected(std::format("sysctlbyname failed: {}", strerror(errno))); return Err(std::format("sysctlbyname failed: {}", strerror(errno)));
return mem; return mem;
} }
fn GetNowPlaying() -> expected<String, NowPlayingError> { return GetCurrentPlayingInfo(); } fn GetNowPlaying() -> Result<String, NowPlayingError> { return GetCurrentPlayingInfo(); }
fn GetOSVersion() -> expected<String, String> { return GetMacOSVersion(); } fn GetOSVersion() -> Result<String, String> { return GetMacOSVersion(); }
fn GetDesktopEnvironment() -> optional<String> { return std::nullopt; } fn GetDesktopEnvironment() -> Option<String> { return None; }
fn GetWindowManager() -> String { return "Yabai"; } fn GetWindowManager() -> String { return "Yabai"; }
@ -196,7 +195,6 @@ fn GetHost() -> String {
return String(modelNameByHwModel[hwModel.data()]); return String(modelNameByHwModel[hwModel.data()]);
} }
// returns free/total
fn GetDiskUsage() -> std::pair<u64, u64> { fn GetDiskUsage() -> std::pair<u64, u64> {
struct statvfs vfs; struct statvfs vfs;

View file

@ -1,31 +1,27 @@
#pragma once #pragma once
#include <expected>
#include "../util/macros.h" #include "../util/macros.h"
#include "../util/types.h" #include "../util/types.h"
using std::optional, std::expected;
/** /**
* @brief Get the amount of installed RAM in bytes. * @brief Get the amount of installed RAM in bytes.
*/ */
fn GetMemInfo() -> expected<u64, String>; fn GetMemInfo() -> Result<u64, String>;
/** /**
* @brief Get the currently playing song metadata. * @brief Get the currently playing song metadata.
*/ */
fn GetNowPlaying() -> expected<String, NowPlayingError>; fn GetNowPlaying() -> Result<String, NowPlayingError>;
/** /**
* @brief Get the OS version. * @brief Get the OS version.
*/ */
fn GetOSVersion() -> expected<String, String>; fn GetOSVersion() -> Result<String, String>;
/** /**
* @brief Get the current desktop environment. * @brief Get the current desktop environment.
*/ */
fn GetDesktopEnvironment() -> optional<String>; fn GetDesktopEnvironment() -> Option<String>;
/** /**
* @brief Get the current window manager. * @brief Get the current window manager.
@ -56,4 +52,4 @@ fn GetPackageCount() -> u64;
* @brief Get the current disk usage. * @brief Get the current disk usage.
* @return std::pair<u64, u64> Used space/total space * @return std::pair<u64, u64> Used space/total space
*/ */
fn GetDiskUsage() -> std::pair<u64, u64>; fn GetDiskUsage() -> Pair<u64, u64>;

View file

@ -8,11 +8,8 @@
#include <tlhelp32.h> #include <tlhelp32.h>
// clang-format on // clang-format on
#include <algorithm>
#include <cstring> #include <cstring>
#include <guiddef.h> #include <guiddef.h>
#include <ranges>
#include <vector>
#include <winrt/Windows.Foundation.h> #include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Media.Control.h> #include <winrt/Windows.Media.Control.h>
#include <winrt/Windows.Storage.h> #include <winrt/Windows.Storage.h>
@ -22,7 +19,6 @@
#include "os.h" #include "os.h"
using std::string_view;
using RtlGetVersionPtr = NTSTATUS(WINAPI*)(PRTL_OSVERSIONINFOW); using RtlGetVersionPtr = NTSTATUS(WINAPI*)(PRTL_OSVERSIONINFOW);
// NOLINTBEGIN(*-pro-type-cstyle-cast,*-no-int-to-ptr,*-pro-type-reinterpret-cast) // NOLINTBEGIN(*-pro-type-cstyle-cast,*-no-int-to-ptr,*-pro-type-reinterpret-cast)
@ -43,8 +39,8 @@ namespace {
[[nodiscard]] fn isValid() const -> bool { return h_snapshot != INVALID_HANDLE_VALUE; } [[nodiscard]] fn isValid() const -> bool { return h_snapshot != INVALID_HANDLE_VALUE; }
[[nodiscard]] fn getProcesses() const -> std::vector<std::pair<DWORD, string>> { [[nodiscard]] fn getProcesses() const -> std::vector<std::pair<DWORD, String>> {
std::vector<std::pair<DWORD, string>> processes; std::vector<std::pair<DWORD, String>> processes;
if (!isValid()) if (!isValid())
return processes; return processes;
@ -58,11 +54,11 @@ namespace {
// Get first process // Get first process
if (Process32First(h_snapshot, &pe32)) { if (Process32First(h_snapshot, &pe32)) {
// Add first process to vector // Add first process to vector
processes.emplace_back(pe32.th32ProcessID, string(reinterpret_cast<const char*>(pe32.szExeFile))); processes.emplace_back(pe32.th32ProcessID, String(reinterpret_cast<const char*>(pe32.szExeFile)));
// Add remaining processes // Add remaining processes
while (Process32Next(h_snapshot, &pe32)) while (Process32Next(h_snapshot, &pe32))
processes.emplace_back(pe32.th32ProcessID, string(reinterpret_cast<const char*>(pe32.szExeFile))); processes.emplace_back(pe32.th32ProcessID, String(reinterpret_cast<const char*>(pe32.szExeFile)));
} }
return processes; return processes;
@ -71,7 +67,7 @@ namespace {
HANDLE h_snapshot; HANDLE h_snapshot;
}; };
fn GetRegistryValue(const HKEY& hKey, const string& subKey, const string& valueName) -> string { fn GetRegistryValue(const HKEY& hKey, const String& subKey, const String& valueName) -> String {
HKEY key = nullptr; HKEY key = nullptr;
if (RegOpenKeyExA(hKey, subKey.c_str(), 0, KEY_READ, &key) != ERROR_SUCCESS) if (RegOpenKeyExA(hKey, subKey.c_str(), 0, KEY_READ, &key) != ERROR_SUCCESS)
return ""; return "";
@ -84,7 +80,7 @@ namespace {
} }
// For string values, allocate one less byte to avoid the null terminator // For string values, allocate one less byte to avoid the null terminator
string value((type == REG_SZ || type == REG_EXPAND_SZ) ? dataSize - 1 : dataSize, '\0'); String value((type == REG_SZ || type == REG_EXPAND_SZ) ? dataSize - 1 : dataSize, '\0');
if (RegQueryValueExA(key, valueName.c_str(), nullptr, nullptr, std::bit_cast<LPBYTE>(value.data()), &dataSize) != if (RegQueryValueExA(key, valueName.c_str(), nullptr, nullptr, std::bit_cast<LPBYTE>(value.data()), &dataSize) !=
ERROR_SUCCESS) { ERROR_SUCCESS) {
@ -96,13 +92,13 @@ namespace {
return value; return value;
} }
fn GetProcessInfo() -> std::vector<std::pair<DWORD, string>> { fn GetProcessInfo() -> std::vector<std::pair<DWORD, String>> {
const ProcessSnapshot snapshot; const ProcessSnapshot snapshot;
return snapshot.isValid() ? snapshot.getProcesses() : std::vector<std::pair<DWORD, string>> {}; return snapshot.isValid() ? snapshot.getProcesses() : std::vector<std::pair<DWORD, String>> {};
} }
fn IsProcessRunning(const std::vector<string>& processes, const string& name) -> bool { fn IsProcessRunning(const std::vector<String>& processes, const String& name) -> bool {
return std::ranges::any_of(processes, [&name](const string& proc) -> bool { return std::ranges::any_of(processes, [&name](const String& proc) -> bool {
return _stricmp(proc.c_str(), name.c_str()) == 0; return _stricmp(proc.c_str(), name.c_str()) == 0;
}); });
} }
@ -127,7 +123,7 @@ namespace {
return 0; return 0;
} }
fn GetProcessName(const DWORD pid) -> string { fn GetProcessName(const DWORD pid) -> String {
const ProcessSnapshot snapshot; const ProcessSnapshot snapshot;
if (!snapshot.isValid()) if (!snapshot.isValid())
return ""; return "";
@ -149,17 +145,17 @@ namespace {
} }
} }
fn GetMemInfo() -> expected<u64, string> { fn GetMemInfo() -> Result<u64, String> {
try { try {
using namespace winrt::Windows::System::Diagnostics; using namespace winrt::Windows::System::Diagnostics;
const SystemDiagnosticInfo diag = SystemDiagnosticInfo::GetForCurrentSystem(); const SystemDiagnosticInfo diag = SystemDiagnosticInfo::GetForCurrentSystem();
return diag.MemoryUsage().GetReport().TotalPhysicalSizeInBytes(); return diag.MemoryUsage().GetReport().TotalPhysicalSizeInBytes();
} catch (const winrt::hresult_error& e) { } catch (const winrt::hresult_error& e) {
return std::unexpected("Failed to get memory info: " + to_string(e.message())); return Err(std::format("Failed to get memory info: {}", to_string(e.message())));
} }
} }
fn GetNowPlaying() -> expected<string, NowPlayingError> { fn GetNowPlaying() -> Result<String, NowPlayingError> {
using namespace winrt::Windows::Media::Control; using namespace winrt::Windows::Media::Control;
using namespace winrt::Windows::Foundation; using namespace winrt::Windows::Foundation;
@ -181,36 +177,31 @@ fn GetNowPlaying() -> expected<string, NowPlayingError> {
} }
// If we reach this point, there is no current session // If we reach this point, there is no current session
return std::unexpected(NowPlayingError { NowPlayingCode::NoActivePlayer }); return Err(NowPlayingCode::NoActivePlayer);
} catch (const winrt::hresult_error& e) { return std::unexpected(NowPlayingError { e }); } } catch (const winrt::hresult_error& e) { return Err(e); }
} }
fn GetOSVersion() -> expected<string, string> { fn GetOSVersion() -> Result<String, String> {
// First try using the native Windows API
constexpr OSVERSIONINFOEXW osvi = { sizeof(OSVERSIONINFOEXW), 0, 0, 0, 0, { 0 }, 0, 0, 0, 0, 0 }; constexpr OSVERSIONINFOEXW osvi = { sizeof(OSVERSIONINFOEXW), 0, 0, 0, 0, { 0 }, 0, 0, 0, 0, 0 };
NTSTATUS status = 0; NTSTATUS status = 0;
// Get RtlGetVersion function from ntdll.dll (not affected by application manifest)
if (const HMODULE ntdllHandle = GetModuleHandleW(L"ntdll.dll")) if (const HMODULE ntdllHandle = GetModuleHandleW(L"ntdll.dll"))
if (const auto rtlGetVersion = std::bit_cast<RtlGetVersionPtr>(GetProcAddress(ntdllHandle, "RtlGetVersion"))) if (const auto rtlGetVersion = std::bit_cast<RtlGetVersionPtr>(GetProcAddress(ntdllHandle, "RtlGetVersion")))
status = rtlGetVersion(std::bit_cast<PRTL_OSVERSIONINFOW>(&osvi)); status = rtlGetVersion(std::bit_cast<PRTL_OSVERSIONINFOW>(&osvi));
string productName; String productName;
string edition; String edition;
if (status == 0) { // STATUS_SUCCESS if (status == 0) {
// We need to get the edition information which isn't available from version API
// Use GetProductInfo which is available since Vista
DWORD productType = 0; DWORD productType = 0;
if (GetProductInfo( if (GetProductInfo(
osvi.dwMajorVersion, osvi.dwMinorVersion, osvi.wServicePackMajor, osvi.wServicePackMinor, &productType osvi.dwMajorVersion, osvi.dwMinorVersion, osvi.wServicePackMajor, osvi.wServicePackMinor, &productType
)) { )) {
if (osvi.dwMajorVersion == 10) { if (osvi.dwMajorVersion == 10) {
if (osvi.dwBuildNumber >= 22000) { if (osvi.dwBuildNumber >= 22000)
productName = "Windows 11"; productName = "Windows 11";
} else { else
productName = "Windows 10"; productName = "Windows 10";
}
switch (productType) { switch (productType) {
case PRODUCT_PROFESSIONAL: case PRODUCT_PROFESSIONAL:
@ -235,22 +226,20 @@ fn GetOSVersion() -> expected<string, string> {
} }
} }
} else { } else {
// Fallback to registry method if the API approach fails
productName = productName =
GetRegistryValue(HKEY_LOCAL_MACHINE, R"(SOFTWARE\Microsoft\Windows NT\CurrentVersion)", "ProductName"); GetRegistryValue(HKEY_LOCAL_MACHINE, R"(SOFTWARE\Microsoft\Windows NT\CurrentVersion)", "ProductName");
// Check for Windows 11
if (const i32 buildNumber = stoi( if (const i32 buildNumber = stoi(
GetRegistryValue(HKEY_LOCAL_MACHINE, R"(SOFTWARE\Microsoft\Windows NT\CurrentVersion)", "CurrentBuildNumber") GetRegistryValue(HKEY_LOCAL_MACHINE, R"(SOFTWARE\Microsoft\Windows NT\CurrentVersion)", "CurrentBuildNumber")
); );
buildNumber >= 22000 && productName.find("Windows 10") != string::npos) buildNumber >= 22000 && productName.find("Windows 10") != String::npos)
productName.replace(productName.find("Windows 10"), 10, "Windows 11"); productName.replace(productName.find("Windows 10"), 10, "Windows 11");
} }
if (!productName.empty()) { if (!productName.empty()) {
string result = productName + edition; String result = productName + edition;
const string displayVersion = const String displayVersion =
GetRegistryValue(HKEY_LOCAL_MACHINE, R"(SOFTWARE\Microsoft\Windows NT\CurrentVersion)", "DisplayVersion"); GetRegistryValue(HKEY_LOCAL_MACHINE, R"(SOFTWARE\Microsoft\Windows NT\CurrentVersion)", "DisplayVersion");
if (!displayVersion.empty()) if (!displayVersion.empty())
@ -262,13 +251,13 @@ fn GetOSVersion() -> expected<string, string> {
return "Windows"; return "Windows";
} }
fn GetHost() -> string { fn GetHost() -> String {
string hostName = GetRegistryValue(HKEY_LOCAL_MACHINE, R"(SYSTEM\HardwareConfig\Current)", "SystemFamily"); String hostName = GetRegistryValue(HKEY_LOCAL_MACHINE, R"(SYSTEM\HardwareConfig\Current)", "SystemFamily");
return hostName; return hostName;
} }
fn GetKernelVersion() -> string { fn GetKernelVersion() -> String {
// ReSharper disable once CppLocalVariableMayBeConst // ReSharper disable once CppLocalVariableMayBeConst
if (HMODULE ntdllHandle = GetModuleHandleW(L"ntdll.dll")) { if (HMODULE ntdllHandle = GetModuleHandleW(L"ntdll.dll")) {
if (const auto rtlGetVersion = std::bit_cast<RtlGetVersionPtr>(GetProcAddress(ntdllHandle, "RtlGetVersion"))) { if (const auto rtlGetVersion = std::bit_cast<RtlGetVersionPtr>(GetProcAddress(ntdllHandle, "RtlGetVersion"))) {
@ -286,28 +275,24 @@ fn GetKernelVersion() -> string {
return ""; return "";
} }
fn GetWindowManager() -> string { fn GetWindowManager() -> String {
// Get process information once and reuse it
const auto processInfo = GetProcessInfo(); const auto processInfo = GetProcessInfo();
std::vector<string> processNames; std::vector<String> processNames;
processNames.reserve(processInfo.size()); processNames.reserve(processInfo.size());
for (const auto& name : processInfo | std::views::values) processNames.push_back(name); for (const auto& name : processInfo | std::views::values) processNames.push_back(name);
// Check for third-party WMs using a map for cleaner code const std::unordered_map<String, String> wmProcesses = {
const std::unordered_map<string, string> wmProcesses = {
{ "glazewm.exe", "GlazeWM" }, { "glazewm.exe", "GlazeWM" },
{ "fancywm.exe", "FancyWM" }, { "fancywm.exe", "FancyWM" },
{ "komorebi.exe", "Komorebi" }, { "komorebi.exe", "Komorebi" },
{ "komorebic.exe", "Komorebi" } { "komorebic.exe", "Komorebi" }
}; };
for (const auto& [processName, wmName] : wmProcesses) { for (const auto& [processName, wmName] : wmProcesses)
if (IsProcessRunning(processNames, processName)) if (IsProcessRunning(processNames, processName))
return wmName; return wmName;
}
// Fallback to DWM detection
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)";
@ -315,9 +300,8 @@ fn GetWindowManager() -> string {
return "Windows Manager"; return "Windows Manager";
} }
fn GetDesktopEnvironment() -> optional<string> { fn GetDesktopEnvironment() -> Option<String> {
// Get version information from registry 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");
if (buildStr.empty()) { if (buildStr.empty()) {
@ -339,10 +323,10 @@ fn GetDesktopEnvironment() -> optional<string> {
// Windows 8.1/10 Metro Era // Windows 8.1/10 Metro Era
if (build >= 9200) { // Windows 8+ if (build >= 9200) { // Windows 8+
// Distinguish between Windows 8 and 10 // Distinguish between Windows 8 and 10
const string productName = const String productName =
GetRegistryValue(HKEY_LOCAL_MACHINE, R"(SOFTWARE\Microsoft\Windows NT\CurrentVersion)", "ProductName"); GetRegistryValue(HKEY_LOCAL_MACHINE, R"(SOFTWARE\Microsoft\Windows NT\CurrentVersion)", "ProductName");
if (productName.find("Windows 10") != string::npos) if (productName.find("Windows 10") != String::npos)
return "Metro (Windows 10)"; return "Metro (Windows 10)";
if (build >= 9600) if (build >= 9600)
@ -355,7 +339,7 @@ fn GetDesktopEnvironment() -> optional<string> {
if (build >= 7600) if (build >= 7600)
return "Aero (Windows 7)"; return "Aero (Windows 7)";
// Older versions // Pre-Win7
return "Classic"; return "Classic";
} catch (...) { } catch (...) {
DEBUG_LOG("Failed to parse CurrentBuildNumber"); DEBUG_LOG("Failed to parse CurrentBuildNumber");
@ -363,9 +347,10 @@ fn GetDesktopEnvironment() -> optional<string> {
} }
} }
fn GetShell() -> string { fn GetShell() -> String {
// Define known shells map once for reuse // TODO: update this to use GetEnv
const std::unordered_map<string, string> knownShells = {
const std::unordered_map<String, String> knownShells = {
{ "cmd.exe", "Command Prompt" }, { "cmd.exe", "Command Prompt" },
{ "powershell.exe", "PowerShell" }, { "powershell.exe", "PowerShell" },
{ "pwsh.exe", "PowerShell Core" }, { "pwsh.exe", "PowerShell Core" },
@ -374,18 +359,15 @@ fn GetShell() -> string {
{ "bash.exe", "Windows Subsystem for Linux" } { "bash.exe", "Windows Subsystem for Linux" }
}; };
// Detect MSYS2/MinGW shells
char* msystemEnv = nullptr; char* msystemEnv = nullptr;
if (_dupenv_s(&msystemEnv, nullptr, "MSYSTEM") == 0 && msystemEnv != nullptr) { if (_dupenv_s(&msystemEnv, nullptr, "MSYSTEM") == 0 && msystemEnv != nullptr) {
const std::unique_ptr<char, decltype(&free)> msystemEnvGuard(msystemEnv, free); const std::unique_ptr<char, decltype(&free)> msystemEnvGuard(msystemEnv, free);
// Get shell from environment variables
char* shell = nullptr; char* shell = nullptr;
size_t shellLen = 0; size_t shellLen = 0;
_dupenv_s(&shell, &shellLen, "SHELL"); _dupenv_s(&shell, &shellLen, "SHELL");
const std::unique_ptr<char, decltype(&free)> shellGuard(shell, free); const std::unique_ptr<char, decltype(&free)> shellGuard(shell, free);
// If SHELL is empty, try LOGINSHELL
if (!shell || strlen(shell) == 0) { if (!shell || strlen(shell) == 0) {
char* loginShell = nullptr; char* loginShell = nullptr;
size_t loginShellLen = 0; size_t loginShellLen = 0;
@ -395,36 +377,35 @@ fn GetShell() -> string {
} }
if (shell) { if (shell) {
string shellExe; String shellExe;
const string shellPath = shell; const String shellPath = shell;
const size_t lastSlash = shellPath.find_last_of("\\/"); const size_t lastSlash = shellPath.find_last_of("\\/");
shellExe = (lastSlash != string::npos) ? shellPath.substr(lastSlash + 1) : shellPath; shellExe = (lastSlash != String::npos) ? shellPath.substr(lastSlash + 1) : shellPath;
std::ranges::transform(shellExe, shellExe.begin(), ::tolower); std::ranges::transform(shellExe, shellExe.begin(), ::tolower);
// Use a map for shell name lookup instead of multiple if statements // Use a map for shell name lookup instead of multiple if statements
const std::unordered_map<string_view, string> shellNames = { const std::unordered_map<StringView, String> shellNames = {
{ "bash", "Bash" }, { "bash", "Bash" },
{ "zsh", "Zsh" }, { "zsh", "Zsh" },
{ "fish", "Fish" } { "fish", "Fish" }
}; };
for (const auto& [pattern, name] : shellNames) { for (const auto& [pattern, name] : shellNames) {
if (shellExe.find(pattern) != string::npos) if (shellExe.find(pattern) != String::npos)
return name; return name;
} }
return shellExe.empty() ? "MSYS2" : "MSYS2/" + shellExe; return shellExe.empty() ? "MSYS2" : "MSYS2/" + shellExe;
} }
// Fallback to process ancestry with cached process info
const auto processInfo = GetProcessInfo(); const auto processInfo = GetProcessInfo();
DWORD pid = GetCurrentProcessId(); DWORD pid = GetCurrentProcessId();
while (pid != 0) { while (pid != 0) {
string processName = GetProcessName(pid); String processName = GetProcessName(pid);
std::ranges::transform(processName, processName.begin(), ::tolower); std::ranges::transform(processName, processName.begin(), ::tolower);
const std::unordered_map<string, string> msysShells = { const std::unordered_map<String, String> msysShells = {
{ "bash.exe", "Bash" }, { "bash.exe", "Bash" },
{ "zsh.exe", "Zsh" }, { "zsh.exe", "Zsh" },
{ "fish.exe", "Fish" }, { "fish.exe", "Fish" },
@ -442,10 +423,9 @@ fn GetShell() -> string {
return "MSYS2"; return "MSYS2";
} }
// Detect Windows shells
DWORD pid = GetCurrentProcessId(); DWORD pid = GetCurrentProcessId();
while (pid != 0) { while (pid != 0) {
string processName = GetProcessName(pid); String processName = GetProcessName(pid);
std::ranges::transform(processName, processName.begin(), ::tolower); std::ranges::transform(processName, processName.begin(), ::tolower);
if (auto shellIterator = knownShells.find(processName); shellIterator != knownShells.end()) if (auto shellIterator = knownShells.find(processName); shellIterator != knownShells.end())

View file

@ -15,6 +15,11 @@
#define fn auto #define fn auto
#ifdef None
#undef None
#define None std::nullopt
#endif
namespace term { namespace term {
enum class Emphasis : u8 { none = 0, bold = 1, italic = 2 }; enum class Emphasis : u8 { none = 0, bold = 1, italic = 2 };
@ -75,7 +80,7 @@ namespace term {
constexpr fn operator|(Emphasis emph, FgColor fgColor)->Style { return { .emph = emph, .fg_col = fgColor }; } constexpr fn operator|(Emphasis emph, FgColor fgColor)->Style { return { .emph = emph, .fg_col = fgColor }; }
constexpr fn operator|(FgColor fgColor, Emphasis emph)->Style { return { .emph = emph, .fg_col = fgColor }; } constexpr fn operator|(FgColor fgColor, Emphasis emph)->Style { return { .emph = emph, .fg_col = fgColor }; }
constexpr const char* reset = "\033[0m"; constexpr CStr reset = "\033[0m";
template <typename... Args> template <typename... Args>
fn Print(const Style& style, std::format_string<Args...> fmt, Args&&... args) -> void { fn Print(const Style& style, std::format_string<Args...> fmt, Args&&... args) -> void {

View file

@ -1,10 +1,17 @@
#pragma once #pragma once
#include <array>
#include <cstddef> #include <cstddef>
#include <cstdint> #include <cstdint>
#include <cstdlib> #include <cstdlib>
#include <expected> #include <expected>
#include <map>
#include <memory>
#include <optional>
#include <string> #include <string>
#include <unordered_map>
#include <utility>
#include <vector>
#ifdef _WIN32 #ifdef _WIN32
// ReSharper disable once CppUnusedIncludeDirective // ReSharper disable once CppUnusedIncludeDirective
@ -131,11 +138,101 @@ using usize = std::size_t;
using isize = std::ptrdiff_t; using isize = std::ptrdiff_t;
/** /**
* @typedef string * @typedef String
* @brief Represents a string. * @brief Represents a string.
*/ */
using String = std::string; using String = std::string;
/**
* @typedef StringView
* @brief Represents a string view.
*
* This type alias is used for non-owning views of strings, allowing for efficient string manipulation
* without copying the underlying data.
*/
using StringView = std::string_view;
/**
* @typedef Exception
* @brief Represents a generic exception type.
*/
using Exception = std::exception;
/**
* @typedef Expected
* @brief Represents an expected value or an error.
*/
template <typename Tp, typename Er>
using Result = std::expected<Tp, Er>;
/**
* @typedef Unexpected
* @brief Represents an unexpected error.
*/
template <typename Er>
using Err = std::unexpected<Er>;
/**
* @typedef Optional
* @brief Represents an optional value.
*/
template <typename Tp>
using Option = std::optional<Tp>;
/**
* @typedef Array
* @brief Represents a fixed-size array.
*/
template <typename Tp, std::size_t nm>
using Array = std::array<Tp, nm>;
/**
* @typedef Vec
* @brief Represents a dynamic array (vector).
*/
template <typename Tp>
using Vec = std::vector<Tp>;
/**
* @typedef Pair
* @brief Represents a pair of values.
*/
template <typename T1, typename T2>
using Pair = std::pair<T1, T2>;
/**
* @typedef Map
* @brief Represents a map (dictionary) of key-value pairs.
*/
template <typename Key, typename Val>
using Map = std::map<Key, Val>;
/**
* @typedef SharedPointer
* @brief Represents a shared pointer.
*
* This type alias is used for shared ownership of dynamically allocated objects.
*/
template <typename Tp>
using SharedPointer = std::shared_ptr<Tp>;
/**
* @typedef UniquePointer
* @brief Represents a unique pointer.
*
* This type alias is used for unique ownership of dynamically allocated objects.
*/
template <typename Tp, typename Dp>
using UniquePointer = std::unique_ptr<Tp, Dp>;
/**
* @typedef CStr
* @brief Represents a C string (const char*).
*
* This type alias is used for C-style strings, which are null-terminated arrays of characters.
*/
using CStr = const char*;
/** /**
* @enum NowPlayingCode * @enum NowPlayingCode
* @brief Represents error codes for Now Playing functionality. * @brief Represents error codes for Now Playing functionality.
@ -145,20 +242,7 @@ enum class NowPlayingCode : u8 {
NoActivePlayer, NoActivePlayer,
}; };
// Platform-specific error details #ifdef _WIN32
#ifdef __linux__
/**
* @typedef LinuxError
* @brief Represents a Linux-specific error.
*/
using LinuxError = String;
#elif defined(__APPLE__)
/**
* @typedef MacError
* @brief Represents a macOS-specific error.
*/
using MacError = String;
#elif defined(_WIN32)
/** /**
* @typedef WindowsError * @typedef WindowsError
* @brief Represents a Windows-specific error. * @brief Represents a Windows-specific error.
@ -169,18 +253,16 @@ using WindowsError = winrt::hresult_error;
// Unified error type // Unified error type
using NowPlayingError = std::variant< using NowPlayingError = std::variant<
NowPlayingCode, NowPlayingCode,
#ifdef __linux__ #ifdef _WIN32
LinuxError
#elif defined(__APPLE__)
MacError
#elif defined(_WIN32)
WindowsError WindowsError
#else
String
#endif #endif
>; >;
enum class EnvError : u8 { NotFound, AccessError }; enum class EnvError : u8 { NotFound, AccessError };
inline auto GetEnv(const String& name) -> std::expected<String, EnvError> { inline auto GetEnv(const String& name) -> Result<String, EnvError> {
#ifdef _WIN32 #ifdef _WIN32
char* rawPtr = nullptr; char* rawPtr = nullptr;
size_t bufferSize = 0; size_t bufferSize = 0;
@ -195,9 +277,10 @@ inline auto GetEnv(const String& name) -> std::expected<String, EnvError> {
free(rawPtr); free(rawPtr);
return result; return result;
#else #else
const char* value = std::getenv(name.c_str()); CStr value = std::getenv(name.c_str());
if (!value) if (!value)
return std::unexpected(EnvError::NotFound); return Err(EnvError::NotFound);
return String(value); return String(value);
#endif #endif