i have no idea if any of this is better

This commit is contained in:
Mars 2025-02-18 20:01:12 -05:00
parent ea6bf62c90
commit a02ffbed47
Signed by: pupbrained
GPG key ID: 0FF5B8826803F895
7 changed files with 326 additions and 248 deletions

View file

@ -23,6 +23,7 @@ Checks: >
-llvmlibc-*,
-misc-non-private-member-variables-in-classes,
-readability-braces-around-statements,
-readability-function-cognitive-complexity,
-readability-implicit-bool-conversion,
-readability-isolate-declaration,
-readability-magic-numbers

View file

@ -7,6 +7,7 @@
#include <ftxui/screen/screen.hpp>
#include <future>
#include <string>
#include <variant>
#include "config/config.h"
#include "ftxui/screen/color.hpp"
@ -25,13 +26,7 @@ template <>
struct fmt::formatter<BytesToGiB> : fmt::formatter<double> {
template <typename FmtCtx>
constexpr fn format(const BytesToGiB& BTG, FmtCtx& ctx) const -> typename FmtCtx::iterator {
typename FmtCtx::iterator out = fmt::formatter<double>::format(static_cast<double>(BTG.value) / GIB, ctx);
*out++ = 'G';
*out++ = 'i';
*out++ = 'B';
return out;
return fmt::format_to(fmt::formatter<double>::format(static_cast<double>(BTG.value) / GIB, ctx), "GiB");
}
};
@ -70,57 +65,56 @@ namespace {
}
struct SystemData {
std::string date;
std::string host;
std::string kernel_version;
std::string os_version;
std::expected<u64, std::string> mem_info;
std::string desktop_environment;
std::string window_manager;
std::optional<std::string> now_playing;
std::optional<WeatherOutput> weather_info;
std::string date;
std::string host;
std::string kernel_version;
std::expected<string, string> os_version;
std::expected<u64, std::string> mem_info;
std::optional<string> desktop_environment;
std::string window_manager;
std::optional<std::expected<string, NowPlayingError>> now_playing;
std::optional<WeatherOutput> weather_info;
static fn fetchSystemData(const Config& config) -> SystemData {
SystemData data;
std::launch launchPolicy = std::launch::async | std::launch::deferred;
// Group tasks by dependency/type
auto [date, host, kernel] = std::tuple(
std::async(std::launch::async, GetDate),
std::async(std::launch::async, GetHost),
std::async(std::launch::async, GetKernelVersion)
);
if (std::thread::hardware_concurrency() >= 8)
launchPolicy = std::launch::async;
auto [os, mem, de, wm] = std::tuple(
std::async(std::launch::async, GetOSVersion),
std::async(std::launch::async, GetMemInfo),
std::async(std::launch::async, GetDesktopEnvironment),
std::async(std::launch::async, GetWindowManager)
);
// Launch async tasks
std::future<string> futureDate = std::async(launchPolicy, GetDate);
std::future<string> futureHost = std::async(launchPolicy, GetHost);
std::future<string> futureKernel = std::async(launchPolicy, GetKernelVersion);
std::future<string> futureOs = std::async(launchPolicy, GetOSVersion);
std::future<std::expected<u64, string>> futureMem = std::async(launchPolicy, GetMemInfo);
std::future<string> futureDe = std::async(launchPolicy, GetDesktopEnvironment);
std::future<string> futureWm = std::async(launchPolicy, GetWindowManager);
std::future<WeatherOutput> futureWeather;
// Conditional async tasks
std::future<WeatherOutput> weather;
std::future<std::expected<string, NowPlayingError>> nowPlaying;
if (config.weather.get().enabled)
futureWeather = std::async(std::launch::async, [&config]() { return config.weather.get().getWeatherInfo(); });
std::future<std::string> futureNowPlaying;
weather = std::async(std::launch::async, [&] { return config.weather.get().getWeatherInfo(); });
if (config.now_playing.get().enabled)
futureNowPlaying = std::async(std::launch::async, GetNowPlaying);
nowPlaying = std::async(std::launch::async, GetNowPlaying);
// Collect results
data.date = futureDate.get();
data.host = futureHost.get();
data.kernel_version = futureKernel.get();
data.os_version = futureOs.get();
data.mem_info = futureMem.get();
data.desktop_environment = futureDe.get();
data.window_manager = futureWm.get();
// Ordered wait for fastest completion
data.date = date.get();
data.host = host.get();
data.kernel_version = kernel.get();
data.os_version = os.get();
data.mem_info = mem.get();
data.desktop_environment = de.get();
data.window_manager = wm.get();
if (config.weather.get().enabled)
data.weather_info = futureWeather.get();
if (config.now_playing.get().enabled)
data.now_playing = futureNowPlaying.get();
if (weather.valid())
data.weather_info = weather.get();
if (nowPlaying.valid())
data.now_playing = nowPlaying.get();
return data;
}
@ -228,37 +222,58 @@ namespace {
if (!data.kernel_version.empty())
content.push_back(createRow(kernelIcon, "Kernel", data.kernel_version));
if (!data.os_version.empty())
content.push_back(createRow(osIcon, "OS", data.os_version));
if (data.os_version.has_value())
content.push_back(createRow(osIcon, "OS", *data.os_version));
else
ERROR_LOG("Failed to get OS version: {}", data.os_version.error());
if (data.mem_info.has_value())
content.push_back(createRow(memoryIcon, "RAM", fmt::format("{:.2f}", BytesToGiB { data.mem_info.value() })));
content.push_back(createRow(memoryIcon, "RAM", fmt::format("{:.2f}", BytesToGiB { *data.mem_info })));
else
ERROR_LOG("Failed to get memory info: {}", data.mem_info.error());
content.push_back(separator() | color(borderColor));
if (!data.desktop_environment.empty() && data.desktop_environment != data.window_manager)
content.push_back(createRow(" 󰇄 ", "DE", data.desktop_environment));
if (data.desktop_environment.has_value() && *data.desktop_environment != data.window_manager)
content.push_back(createRow(" 󰇄 ", "DE", *data.desktop_environment));
if (!data.window_manager.empty())
content.push_back(createRow("  ", "WM", data.window_manager));
// Now Playing row
if (nowPlayingEnabled && data.now_playing.has_value()) {
content.push_back(separator() | color(borderColor));
content.push_back(hbox({
text(musicIcon) | color(iconColor),
text("Music") | color(labelColor),
text(" "),
filler(),
text(
data.now_playing.value().length() > 30 ? data.now_playing.value().substr(0, 30) + "..."
: data.now_playing.value()
) |
color(Color::Magenta),
text(" "),
}));
const std::expected<string, NowPlayingError>& nowPlayingResult = *data.now_playing;
if (nowPlayingResult.has_value()) {
const std::string& npText = *nowPlayingResult;
content.push_back(separator() | color(borderColor));
content.push_back(hbox({
text(musicIcon) | color(iconColor),
text("Playing") | color(labelColor),
text(" "),
filler(),
paragraph(npText) | color(Color::Magenta) | size(WIDTH, LESS_THAN, 30),
text(" "),
}));
} else {
const NowPlayingError& error = nowPlayingResult.error();
if (std::holds_alternative<NowPlayingCode>(error))
switch (std::get<NowPlayingCode>(error)) {
case NowPlayingCode::NoPlayers:
DEBUG_LOG("No MPRIS players found");
break;
case NowPlayingCode::NoActivePlayer:
DEBUG_LOG("No active MPRIS player found");
break;
}
#ifdef __linux__
if (std::holds_alternative<LinuxError>(error))
DEBUG_LOG("DBus error: {}", std::get<LinuxError>(error).getMessage());
#endif
}
}
return vbox(content) | borderRounded | color(Color::White);

View file

@ -10,7 +10,10 @@
#include <filesystem>
#include <fmt/format.h>
#include <fstream>
#include <mutex>
#include <optional>
#include <ranges>
#include <sdbus-c++/Error.h>
#include <sdbus-c++/sdbus-c++.h>
#include <sqlite3.h>
#include <sys/socket.h>
@ -21,36 +24,44 @@
#include "os.h"
#include "src/util/macros.h"
using std::errc, std::exception, std::expected, std::from_chars, std::getline, std::istreambuf_iterator, std::less,
std::lock_guard, std::map, std::mutex, std::ofstream, std::pair, std::string_view, std::vector, std::nullopt,
std::array, std::unique_ptr, std::optional, std::bit_cast, std::to_string, std::ifstream, std::getenv, std::string,
std::unexpected, std::ranges::is_sorted, std::ranges::lower_bound, std::ranges::replace, std::ranges::subrange,
std::ranges::transform;
using namespace std::literals::string_view_literals;
namespace fs = std::filesystem;
enum SessionType : u8 { Wayland, X11, TTY, Unknown };
namespace {
fn GetMprisPlayers(sdbus::IConnection& connection) -> std::vector<string> {
fn GetMprisPlayers(sdbus::IConnection& connection) -> 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);
const unique_ptr<sdbus::IProxy> dbusProxy = createProxy(connection, dbusInterface, dbusObjectPath);
std::vector<string> names;
vector<string> names;
dbusProxy->callMethod(dbusMethodListNames).onInterface(dbusInterface).storeResultsTo(names);
std::vector<string> mprisPlayers;
vector<string> mprisPlayers;
for (const std::basic_string<char>& name : names)
if (const char* mprisInterfaceName = "org.mpris.MediaPlayer2"; name.find(mprisInterfaceName) != std::string::npos)
for (const string& 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 {
fn GetActivePlayer(const vector<string>& mprisPlayers) -> optional<string> {
if (!mprisPlayers.empty())
return mprisPlayers.front();
return "";
return nullopt;
}
fn GetX11WindowManager() -> string {
@ -92,7 +103,7 @@ namespace {
&data
) == Success &&
data) {
wmWindow = *std::bit_cast<Window*>(data);
memcpy(&wmWindow, data, sizeof(Window));
XFree(data);
data = nullptr;
@ -111,7 +122,7 @@ namespace {
&data
) == Success &&
data) {
string name(std::bit_cast<char*>(data));
string name(bit_cast<char*>(data));
XFree(data);
XCloseDisplay(display);
return name;
@ -123,19 +134,19 @@ namespace {
return "Unknown (X11)"; // Changed to empty string
}
fn TrimHyprlandWrapper(const std::string& input) -> string {
if (input.find("hyprland") != std::string::npos)
fn TrimHyprlandWrapper(const string& input) -> string {
if (input.find("hyprland") != string::npos)
return "Hyprland";
return input;
}
fn ReadProcessCmdline(int pid) -> string {
string path = "/proc/" + std::to_string(pid) + "/cmdline";
std::ifstream cmdlineFile(path);
string cmdline;
if (std::getline(cmdlineFile, cmdline)) {
string path = "/proc/" + to_string(pid) + "/cmdline";
ifstream cmdlineFile(path);
string cmdline;
if (getline(cmdlineFile, cmdline)) {
// Replace null bytes with spaces
std::ranges::replace(cmdline, '\0', ' ');
replace(cmdline, '\0', ' ');
return cmdline;
}
return "";
@ -143,16 +154,16 @@ namespace {
fn DetectHyprlandSpecific() -> string {
// Check environment variables first
const char* xdgCurrentDesktop = std::getenv("XDG_CURRENT_DESKTOP");
const char* xdgCurrentDesktop = getenv("XDG_CURRENT_DESKTOP");
if (xdgCurrentDesktop && strcasestr(xdgCurrentDesktop, "hyprland"))
return "Hyprland";
// Check for Hyprland's specific environment variable
if (std::getenv("HYPRLAND_INSTANCE_SIGNATURE"))
if (getenv("HYPRLAND_INSTANCE_SIGNATURE"))
return "Hyprland";
// Check for Hyprland socket
if (fs::exists("/run/user/" + std::to_string(getuid()) + "/hypr"))
if (fs::exists("/run/user/" + to_string(getuid()) + "/hypr"))
return "Hyprland";
return "";
@ -166,6 +177,7 @@ namespace {
// Then try the standard Wayland detection
wl_display* display = wl_display_connect(nullptr);
if (!display)
return "";
@ -173,6 +185,7 @@ namespace {
struct ucred cred;
socklen_t len = sizeof(cred);
if (getsockopt(fileDescriptor, SOL_SOCKET, SO_PEERCRED, &cred, &len) == -1) {
wl_display_disconnect(display);
return "";
@ -182,28 +195,28 @@ namespace {
string compositorName;
// 1. Check comm (might be wrapped)
string commPath = "/proc/" + std::to_string(cred.pid) + "/comm";
std::ifstream commFile(commPath);
string commPath = "/proc/" + to_string(cred.pid) + "/comm";
ifstream commFile(commPath);
if (commFile >> compositorName) {
std::ranges::subrange removedRange = std::ranges::remove(compositorName, '\n');
subrange removedRange = std::ranges::remove(compositorName, '\n');
compositorName.erase(removedRange.begin(), compositorName.end());
}
// 2. Check cmdline for actual binary reference
string cmdline = ReadProcessCmdline(cred.pid);
if (cmdline.find("hyprland") != std::string::npos) {
if (cmdline.find("hyprland") != string::npos) {
wl_display_disconnect(display);
return "Hyprland";
}
// 3. Check exe symlink
string exePath = "/proc/" + std::to_string(cred.pid) + "/exe";
std::array<char, PATH_MAX> buf;
ssize_t lenBuf = readlink(exePath.c_str(), buf.data(), buf.size() - 1);
string exePath = "/proc/" + to_string(cred.pid) + "/exe";
array<char, PATH_MAX> buf;
ssize_t lenBuf = readlink(exePath.c_str(), buf.data(), buf.size() - 1);
if (lenBuf != -1) {
buf.at(static_cast<usize>(lenBuf)) = '\0';
string exe(buf.data());
if (exe.find("hyprland") != std::string::npos) {
if (exe.find("hyprland") != string::npos) {
wl_display_disconnect(display);
return "Hyprland";
}
@ -215,110 +228,115 @@ namespace {
return TrimHyprlandWrapper(compositorName);
}
// Helper functions
fn ToLowercase(string str) -> string {
std::ranges::transform(str, str.begin(), ::tolower);
return str;
}
fn DetectFromEnvVars() -> optional<string> {
// Use RAII to guard against concurrent env modifications
static mutex EnvMutex;
lock_guard<mutex> lock(EnvMutex);
fn ContainsAny(std::string_view haystack, const std::vector<std::string>& needles) -> bool {
return std::ranges::any_of(needles, [&](auto& n) { return haystack.find(n) != std::string_view::npos; });
}
fn DetectFromEnvVars() -> string {
// Check XDG_CURRENT_DESKTOP
if (const char* xdgDe = std::getenv("XDG_CURRENT_DESKTOP")) {
std::string_view sview(xdgDe);
if (!sview.empty()) {
string deStr(sview);
if (size_t colon = deStr.find(':'); colon != std::string::npos)
deStr.erase(colon);
if (!deStr.empty())
return deStr;
}
// XDG_CURRENT_DESKTOP
if (const char* xdgCurrentDesktop = getenv("XDG_CURRENT_DESKTOP")) {
const string_view sview(xdgCurrentDesktop);
const size_t colon = sview.find(':');
return string(sview.substr(0, colon)); // Direct construct from view
}
// Check DESKTOP_SESSION
if (const char* desktopSession = std::getenv("DESKTOP_SESSION")) {
std::string_view sview(desktopSession);
if (!sview.empty())
return std::string(sview);
}
// DESKTOP_SESSION
if (const char* desktopSession = getenv("DESKTOP_SESSION"))
return string(string_view(desktopSession)); // Avoid intermediate view storage
return "";
return nullopt;
}
fn DetectFromSessionFiles() -> string {
const std::vector<fs::path> sessionPaths = { "/usr/share/xsessions", "/usr/share/wayland-sessions" };
const std::vector<std::pair<std::string, std::vector<std::string>>> dePatterns = {
{ "KDE", { "plasma", "plasmax11", "kde" } },
{ "GNOME", { "gnome", "gnome-xorg", "gnome-wayland" } },
{ "XFCE", { "xfce" } },
{ "MATE", { "mate" } },
{ "Cinnamon", { "cinnamon" } },
{ "Budgie", { "budgie" } },
{ "LXQt", { "lxqt" } },
{ "Unity", { "unity" } }
fn DetectFromSessionFiles() -> optional<string> {
static constexpr array<pair<string_view, string_view>, 12> DE_PATTERNS = {
// clang-format off
pair { "Budgie"sv, "budgie"sv },
pair { "Cinnamon"sv, "cinnamon"sv },
pair { "LXQt"sv, "lxqt"sv },
pair { "MATE"sv, "mate"sv },
pair { "Unity"sv, "unity"sv },
pair { "gnome"sv, "GNOME"sv },
pair { "gnome-wayland"sv, "GNOME"sv },
pair { "gnome-xorg"sv, "GNOME"sv },
pair { "kde"sv, "KDE"sv },
pair { "plasma"sv, "KDE"sv },
pair { "plasmax11"sv, "KDE"sv },
pair { "xfce"sv, "XFCE"sv },
// clang-format on
};
for (const auto& sessionPath : sessionPaths) {
if (!fs::exists(sessionPath))
static_assert(is_sorted(DE_PATTERNS, {}, &pair<string_view, string_view>::first));
// Precomputed 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))
continue;
for (const auto& entry : fs::directory_iterator(sessionPath)) {
for (const auto& entry : fs::directory_iterator(path)) {
if (!entry.is_regular_file())
continue;
const string filename = entry.path().stem();
auto lowerFilename = ToLowercase(filename);
// Reuse buffer
lowercaseStem = entry.path().stem().string();
transform(lowercaseStem, lowercaseStem.begin(), ::tolower);
for (const auto& [deName, patterns] : dePatterns) {
if (ContainsAny(lowerFilename, patterns))
return deName;
}
// Modern ranges version
const pair<string_view, string_view>* const patternIter = lower_bound(
DE_PATTERNS, lowercaseStem, less {}, &pair<string_view, string_view>::first // Projection
);
if (patternIter != DE_PATTERNS.end() && patternIter->first == lowercaseStem)
return string(patternIter->second);
}
}
return "";
return nullopt;
}
fn DetectFromProcesses() -> string {
const std::vector<std::pair<std::string, std::string>> processChecks = {
{ "plasmashell", "KDE" },
{ "gnome-shell", "GNOME" },
{ "xfce4-session", "XFCE" },
{ "mate-session", "MATE" },
{ "cinnamon-sessio", "Cinnamon" },
{ "budgie-wm", "Budgie" },
{ "lxqt-session", "LXQt" }
fn DetectFromProcesses() -> optional<string> {
const array processChecks = {
// clang-format off
pair { "plasmashell"sv, "KDE"sv },
pair { "gnome-shell"sv, "GNOME"sv },
pair { "xfce4-session"sv, "XFCE"sv },
pair { "mate-session"sv, "MATE"sv },
pair { "cinnamon-sessio"sv, "Cinnamon"sv },
pair { "budgie-wm"sv, "Budgie"sv },
pair { "lxqt-session"sv, "LXQt"sv },
// clang-format on
};
std::ifstream cmdline("/proc/self/environ");
string envVars((std::istreambuf_iterator<char>(cmdline)), std::istreambuf_iterator<char>());
ifstream cmdline("/proc/self/environ");
string envVars((istreambuf_iterator<char>(cmdline)), istreambuf_iterator<char>());
for (const auto& [process, deName] : processChecks)
if (envVars.find(process) != std::string::npos)
return deName;
if (envVars.find(process) != string::npos)
return string(deName);
return "Unknown";
return nullopt;
}
fn CountNix() noexcept -> std::optional<usize> {
constexpr std::string_view dbPath = "/nix/var/nix/db/db.sqlite";
constexpr std::string_view querySql = "SELECT COUNT(*) FROM ValidPaths WHERE sigs IS NOT NULL;";
fn CountNix() noexcept -> optional<usize> {
constexpr string_view dbPath = "/nix/var/nix/db/db.sqlite";
constexpr string_view querySql = "SELECT COUNT(*) FROM ValidPaths WHERE sigs IS NOT NULL;";
sqlite3* sqlDB = nullptr;
sqlite3_stmt* stmt = nullptr;
usize count = 0;
// 1. Direct URI construction without string concatenation
const string uri =
fmt::format("file:{}{}immutable=1", dbPath, (dbPath.find('?') != std::string_view::npos) ? "&" : "?");
const string uri = fmt::format("file:{}{}immutable=1", dbPath, (dbPath.find('?') != string_view::npos) ? "&" : "?");
// 2. Open database with optimized flags
if (sqlite3_open_v2(uri.c_str(), &sqlDB, SQLITE_OPEN_READONLY | SQLITE_OPEN_URI | SQLITE_OPEN_NOMUTEX, nullptr) !=
SQLITE_OK)
return std::nullopt;
return nullopt;
// 3. Configure database for maximum read performance
sqlite3_exec(sqlDB, "PRAGMA journal_mode=OFF; PRAGMA mmap_size=268435456;", nullptr, nullptr, nullptr);
@ -333,10 +351,10 @@ namespace {
}
sqlite3_close(sqlDB);
return count ? std::optional { count } : std::nullopt;
return count ? optional { count } : nullopt;
}
fn CountNixWithCache() noexcept -> std::optional<size_t> {
fn CountNixWithCache() noexcept -> optional<size_t> {
constexpr const char* dbPath = "/nix/var/nix/db/db.sqlite";
constexpr const char* cachePath = "/tmp/nix_pkg_count.cache";
@ -347,21 +365,21 @@ namespace {
const mtime cacheMtime = fs::last_write_time(cachePath);
if (fs::exists(cachePath) && dbMtime <= cacheMtime) {
std::ifstream cache(cachePath, std::ios::binary);
size_t count = 0;
cache.read(std::bit_cast<char*>(&count), sizeof(count));
return cache ? std::optional(count) : std::nullopt;
ifstream cache(cachePath, std::ios::binary);
size_t count = 0;
cache.read(bit_cast<char*>(&count), sizeof(count));
return cache ? optional(count) : nullopt;
}
} catch (const std::exception& e) { DEBUG_LOG("Cache access failed: {}, rebuilding...", e.what()); }
} catch (const exception& e) { DEBUG_LOG("Cache access failed: {}, rebuilding...", e.what()); }
const std::optional<usize> count = CountNix();
const optional<usize> count = CountNix();
if (count) {
constexpr const char* tmpPath = "/tmp/nix_pkg_count.tmp";
{
std::ofstream tmp(tmpPath, std::ios::binary | std::ios::trunc);
tmp.write(std::bit_cast<const char*>(&*count), sizeof(*count));
ofstream tmp(tmpPath, std::ios::binary | std::ios::trunc);
tmp.write(bit_cast<const char*>(&*count), sizeof(*count));
}
fs::rename(tmpPath, cachePath);
@ -371,20 +389,18 @@ namespace {
}
}
fn GetOSVersion() -> string {
fn GetOSVersion() -> expected<string, string> {
constexpr const char* path = "/etc/os-release";
std::ifstream file(path);
ifstream file(path);
if (!file.is_open()) {
ERROR_LOG("Failed to open {}", path);
return "";
}
if (!file.is_open())
return unexpected("Failed to open " + string(path));
string line;
const string prefix = "PRETTY_NAME=";
while (std::getline(file, line))
while (getline(file, line))
if (line.starts_with(prefix)) {
string prettyName = line.substr(prefix.size());
@ -394,36 +410,39 @@ fn GetOSVersion() -> string {
return prettyName;
}
ERROR_LOG("PRETTY_NAME line not found in {}", path);
return "";
return unexpected("PRETTY_NAME line not found in " + string(path));
}
fn GetMemInfo() -> std::expected<u64, string> {
fn GetMemInfo() -> expected<u64, string> {
constexpr const char* path = "/proc/meminfo";
std::ifstream input(path);
if (!input.is_open())
return std::unexpected("Failed to open " + std::string(path));
ifstream input(path);
std::string line;
while (std::getline(input, line)) {
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 == std::string::npos)
return std::unexpected("Invalid MemTotal line: no colon found");
std::string_view view(line);
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 == std::string_view::npos)
return std::unexpected("No number found after colon in MemTotal line");
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 != std::string_view::npos)
if (end != string_view::npos)
view = view.substr(0, end);
// Get pointers via iterators
@ -431,89 +450,82 @@ fn GetMemInfo() -> std::expected<u64, string> {
const char* endPtr = &*view.end();
u64 value = 0;
const auto result = std::from_chars(startPtr, endPtr, value);
if (result.ec != std::errc() || result.ptr != endPtr)
return std::unexpected("Failed to parse number in MemTotal line");
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 std::unexpected("MemTotal line not found in " + std::string(path));
return unexpected("MemTotal line not found in " + string(path));
}
fn GetNowPlaying() -> string {
fn GetNowPlaying() -> expected<string, NowPlayingError> {
try {
const char *playerObjectPath = "/org/mpris/MediaPlayer2", *playerInterfaceName = "org.mpris.MediaPlayer2.Player";
std::unique_ptr<sdbus::IConnection> connection = sdbus::createSessionBusConnection();
unique_ptr<sdbus::IConnection> connection = sdbus::createSessionBusConnection();
std::vector<string> mprisPlayers = GetMprisPlayers(*connection);
vector<string> mprisPlayers = GetMprisPlayers(*connection);
if (mprisPlayers.empty()) {
DEBUG_LOG("No MPRIS players found");
return "";
}
if (mprisPlayers.empty())
return unexpected(NowPlayingError { NowPlayingCode::NoPlayers });
string activePlayer = GetActivePlayer(mprisPlayers);
optional<string> activePlayer = GetActivePlayer(mprisPlayers);
if (activePlayer.empty()) {
DEBUG_LOG("No active player found");
return "";
}
if (!activePlayer.has_value())
return unexpected(NowPlayingError { NowPlayingCode::NoActivePlayer });
std::unique_ptr<sdbus::IProxy> playerProxy =
sdbus::createProxy(*connection, sdbus::ServiceName(activePlayer), sdbus::ObjectPath(playerObjectPath));
unique_ptr<sdbus::IProxy> playerProxy =
sdbus::createProxy(*connection, sdbus::ServiceName(*activePlayer), sdbus::ObjectPath(playerObjectPath));
sdbus::Variant metadataVariant = playerProxy->getProperty("Metadata").onInterface(playerInterfaceName);
if (metadataVariant.containsValueOfType<std::map<std::string, sdbus::Variant>>()) {
const std::map<std::basic_string<char>, sdbus::Variant>& metadata =
metadataVariant.get<std::map<std::string, sdbus::Variant>>();
if (metadataVariant.containsValueOfType<map<string, sdbus::Variant>>()) {
const map<string, sdbus::Variant>& metadata = metadataVariant.get<map<string, sdbus::Variant>>();
string title;
auto titleIter = metadata.find("xesam:title");
if (titleIter != metadata.end() && titleIter->second.containsValueOfType<std::string>()) {
title = titleIter->second.get<std::string>();
}
if (titleIter != metadata.end() && titleIter->second.containsValueOfType<string>())
title = titleIter->second.get<string>();
string artist;
auto artistIter = metadata.find("xesam:artist");
if (artistIter != metadata.end() && artistIter->second.containsValueOfType<std::vector<std::string>>()) {
auto artists = artistIter->second.get<std::vector<std::string>>();
if (!artists.empty()) {
if (artistIter != metadata.end() && artistIter->second.containsValueOfType<vector<string>>()) {
auto artists = artistIter->second.get<vector<string>>();
if (!artists.empty())
artist = artists[0];
}
}
string result;
if (!artist.empty() && !title.empty()) {
if (!artist.empty() && !title.empty())
result = artist + " - " + title;
} else if (!title.empty()) {
else if (!title.empty())
result = title;
} else if (!artist.empty()) {
else if (!artist.empty())
result = artist;
} else {
else
result = "";
}
return result;
}
} catch (const sdbus::Error& e) {
if (e.getName() != "com.github.altdesktop.playerctld.NoActivePlayer") {
ERROR_LOG("Error: {}", e.what());
return "";
}
if (e.getName() != "com.github.altdesktop.playerctld.NoActivePlayer")
return unexpected(NowPlayingError { LinuxError(e) });
return "No active player";
return unexpected(NowPlayingError { NowPlayingCode::NoActivePlayer });
}
return "";
}
fn GetWindowManager() -> string {
// Check environment variables first
const char* xdgSessionType = std::getenv("XDG_SESSION_TYPE");
const char* waylandDisplay = std::getenv("WAYLAND_DISPLAY");
const char* xdgSessionType = getenv("XDG_SESSION_TYPE");
const char* waylandDisplay = getenv("WAYLAND_DISPLAY");
// Prefer Wayland detection if Wayland session
if ((waylandDisplay != nullptr) || (xdgSessionType && strstr(xdgSessionType, "wayland"))) {
@ -522,11 +534,11 @@ fn GetWindowManager() -> string {
return compositor;
// Fallback environment check
const char* xdgCurrentDesktop = std::getenv("XDG_CURRENT_DESKTOP");
const char* xdgCurrentDesktop = getenv("XDG_CURRENT_DESKTOP");
if (xdgCurrentDesktop) {
string desktop(xdgCurrentDesktop);
std::ranges::transform(compositor, compositor.begin(), ::tolower);
if (desktop.find("hyprland") != std::string::npos)
transform(compositor, compositor.begin(), ::tolower);
if (desktop.find("hyprland") != string::npos)
return "hyprland";
}
}
@ -539,13 +551,13 @@ fn GetWindowManager() -> string {
return "Unknown";
}
fn GetDesktopEnvironment() -> string {
fn GetDesktopEnvironment() -> optional<string> {
// Try environment variables first
if (auto desktopEnvironment = DetectFromEnvVars(); !desktopEnvironment.empty())
if (auto desktopEnvironment = DetectFromEnvVars(); desktopEnvironment.has_value())
return desktopEnvironment;
// Try session files next
if (auto desktopEnvironment = DetectFromSessionFiles(); !desktopEnvironment.empty())
if (auto desktopEnvironment = DetectFromSessionFiles(); desktopEnvironment.has_value())
return desktopEnvironment;
// Fallback to process detection
@ -553,7 +565,7 @@ fn GetDesktopEnvironment() -> string {
}
fn GetShell() -> string {
const char* shell = std::getenv("SHELL");
const char* shell = getenv("SHELL");
return shell ? shell : "";
}
@ -561,14 +573,14 @@ fn GetShell() -> string {
fn GetHost() -> string {
constexpr const char* path = "/sys/class/dmi/id/product_family";
std::ifstream file(path);
ifstream file(path);
if (!file.is_open()) {
ERROR_LOG("Failed to open {}", path);
return "";
}
string productFamily;
if (!std::getline(file, productFamily)) {
if (!getline(file, productFamily)) {
ERROR_LOG("Failed to read from {}", path);
return "";
}
@ -580,7 +592,7 @@ fn GetKernelVersion() -> string {
struct utsname uts;
if (uname(&uts) == -1) {
ERROR_LOG("uname() failed: {}", std::strerror(errno));
ERROR_LOG("uname() failed: {}", strerror(errno));
return "";
}

View file

@ -2,7 +2,7 @@
namespace fs = std::filesystem;
fn GetApkPackageCount() -> std::optional<usize> {
fn GetApkPackageCount() -> optional<usize> {
fs::path apkDbPath("/lib/apk/db/installed");
return std::nullopt;

View file

@ -2,8 +2,10 @@
#include "src/util/macros.h"
using std::optional;
// Get package count from dpkg (Debian/Ubuntu)
fn GetDpkgPackageCount() -> std::optional<usize>;
fn GetDpkgPackageCount() -> optional<usize>;
// Get package count from RPM (Red Hat/Fedora/CentOS)
fn GetRpmPackageCount() -> std::optional<usize>;

View file

@ -5,25 +5,27 @@
#include "../util/macros.h"
#include "../util/types.h"
using std::optional, std::expected;
/**
* @brief Get the amount of installed RAM in bytes.
*/
fn GetMemInfo() -> std::expected<u64, string>;
fn GetMemInfo() -> expected<u64, string>;
/**
* @brief Get the currently playing song metadata.
*/
fn GetNowPlaying() -> string;
fn GetNowPlaying() -> expected<string, NowPlayingError>;
/**
* @brief Get the OS version.
*/
fn GetOSVersion() -> string;
fn GetOSVersion() -> expected<string, string>;
/**
* @brief Get the current desktop environment.
*/
fn GetDesktopEnvironment() -> string;
fn GetDesktopEnvironment() -> optional<string>;
/**
* @brief Get the current window manager.

View file

@ -4,6 +4,10 @@
#include <cstdint>
#include <string>
#ifdef __linux__
#include <sdbus-c++/sdbus-c++.h>
#endif
/**
* @typedef u8
* @brief Represents an 8-bit unsigned integer.
@ -124,3 +128,45 @@ using isize = std::ptrdiff_t;
* @brief Represents a string.
*/
using string = std::string;
/**
* @enum NowPlayingCode
* @brief Represents error codes for Now Playing functionality.
*/
enum class NowPlayingCode : u8 {
NoPlayers,
NoActivePlayer,
};
// Platform-specific error details
#ifdef __linux__
/**
* @typedef LinuxError
* @brief Represents a Linux-specific error.
*/
using LinuxError = sdbus::Error;
#elif defined(__APPLE__)
/**
* @typedef MacError
* @brief Represents a macOS-specific error.
*/
using MacError = std::string;
#elif defined(__WIN32__)
/**
* @typedef WindowsError
* @brief Represents a Windows-specific error.
*/
using WindowsError = winrt::hresult_error;
#endif
// Unified error type
using NowPlayingError = std::variant<
NowPlayingCode,
#ifdef __linux__
LinuxError
#elif defined(__APPLE__)
MacError
#elif defined(__WIN32__)
WindowsError
#endif
>;