diff --git a/.clang-tidy b/.clang-tidy index 7648077..6cc7916 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -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 diff --git a/src/main.cpp b/src/main.cpp index 077e092..d9049d3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include "config/config.h" #include "ftxui/screen/color.hpp" @@ -25,13 +26,7 @@ template <> struct fmt::formatter : fmt::formatter { template constexpr fn format(const BytesToGiB& BTG, FmtCtx& ctx) const -> typename FmtCtx::iterator { - typename FmtCtx::iterator out = fmt::formatter::format(static_cast(BTG.value) / GIB, ctx); - - *out++ = 'G'; - *out++ = 'i'; - *out++ = 'B'; - - return out; + return fmt::format_to(fmt::formatter::format(static_cast(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 mem_info; - std::string desktop_environment; - std::string window_manager; - std::optional now_playing; - std::optional weather_info; + std::string date; + std::string host; + std::string kernel_version; + std::expected os_version; + std::expected mem_info; + std::optional desktop_environment; + std::string window_manager; + std::optional> now_playing; + std::optional 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 futureDate = std::async(launchPolicy, GetDate); - std::future futureHost = std::async(launchPolicy, GetHost); - std::future futureKernel = std::async(launchPolicy, GetKernelVersion); - std::future futureOs = std::async(launchPolicy, GetOSVersion); - std::future> futureMem = std::async(launchPolicy, GetMemInfo); - std::future futureDe = std::async(launchPolicy, GetDesktopEnvironment); - std::future futureWm = std::async(launchPolicy, GetWindowManager); - - std::future futureWeather; + // Conditional async tasks + std::future weather; + std::future> nowPlaying; if (config.weather.get().enabled) - futureWeather = std::async(std::launch::async, [&config]() { return config.weather.get().getWeatherInfo(); }); - - std::future 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& 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(error)) + switch (std::get(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(error)) + DEBUG_LOG("DBus error: {}", std::get(error).getMessage()); +#endif + } } return vbox(content) | borderRounded | color(Color::White); diff --git a/src/os/linux.cpp b/src/os/linux.cpp index 6ffc349..f954044 100644 --- a/src/os/linux.cpp +++ b/src/os/linux.cpp @@ -10,7 +10,10 @@ #include #include #include +#include +#include #include +#include #include #include #include @@ -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 { + fn GetMprisPlayers(sdbus::IConnection& connection) -> vector { 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 dbusProxy = createProxy(connection, dbusInterface, dbusObjectPath); + const unique_ptr dbusProxy = createProxy(connection, dbusInterface, dbusObjectPath); - std::vector names; + vector names; dbusProxy->callMethod(dbusMethodListNames).onInterface(dbusInterface).storeResultsTo(names); - std::vector mprisPlayers; + vector mprisPlayers; - for (const std::basic_string& 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& mprisPlayers) -> string { + fn GetActivePlayer(const vector& mprisPlayers) -> optional { if (!mprisPlayers.empty()) return mprisPlayers.front(); - return ""; + return nullopt; } fn GetX11WindowManager() -> string { @@ -92,7 +103,7 @@ namespace { &data ) == Success && data) { - wmWindow = *std::bit_cast(data); + memcpy(&wmWindow, data, sizeof(Window)); XFree(data); data = nullptr; @@ -111,7 +122,7 @@ namespace { &data ) == Success && data) { - string name(std::bit_cast(data)); + string name(bit_cast(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 buf; - ssize_t lenBuf = readlink(exePath.c_str(), buf.data(), buf.size() - 1); + string exePath = "/proc/" + to_string(cred.pid) + "/exe"; + array buf; + ssize_t lenBuf = readlink(exePath.c_str(), buf.data(), buf.size() - 1); if (lenBuf != -1) { buf.at(static_cast(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 { + // Use RAII to guard against concurrent env modifications + static mutex EnvMutex; + lock_guard lock(EnvMutex); - fn ContainsAny(std::string_view haystack, const std::vector& 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 sessionPaths = { "/usr/share/xsessions", "/usr/share/wayland-sessions" }; - - const std::vector>> 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 { + static constexpr array, 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::first)); + + // Precomputed session paths + static constexpr array 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* const patternIter = lower_bound( + DE_PATTERNS, lowercaseStem, less {}, &pair::first // Projection + ); + + if (patternIter != DE_PATTERNS.end() && patternIter->first == lowercaseStem) + return string(patternIter->second); } } - return ""; + + return nullopt; } - fn DetectFromProcesses() -> string { - const std::vector> processChecks = { - { "plasmashell", "KDE" }, - { "gnome-shell", "GNOME" }, - { "xfce4-session", "XFCE" }, - { "mate-session", "MATE" }, - { "cinnamon-sessio", "Cinnamon" }, - { "budgie-wm", "Budgie" }, - { "lxqt-session", "LXQt" } + fn DetectFromProcesses() -> optional { + 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(cmdline)), std::istreambuf_iterator()); + ifstream cmdline("/proc/self/environ"); + string envVars((istreambuf_iterator(cmdline)), istreambuf_iterator()); 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 { - 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 { + 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 { + fn CountNixWithCache() noexcept -> optional { 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(&count), sizeof(count)); - return cache ? std::optional(count) : std::nullopt; + ifstream cache(cachePath, std::ios::binary); + size_t count = 0; + cache.read(bit_cast(&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 count = CountNix(); + const optional 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(&*count), sizeof(*count)); + ofstream tmp(tmpPath, std::ios::binary | std::ios::trunc); + tmp.write(bit_cast(&*count), sizeof(*count)); } fs::rename(tmpPath, cachePath); @@ -371,20 +389,18 @@ namespace { } } -fn GetOSVersion() -> string { +fn GetOSVersion() -> expected { 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 { +fn GetMemInfo() -> expected { 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 { 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 { try { const char *playerObjectPath = "/org/mpris/MediaPlayer2", *playerInterfaceName = "org.mpris.MediaPlayer2.Player"; - std::unique_ptr connection = sdbus::createSessionBusConnection(); + unique_ptr connection = sdbus::createSessionBusConnection(); - std::vector mprisPlayers = GetMprisPlayers(*connection); + vector 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 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 playerProxy = - sdbus::createProxy(*connection, sdbus::ServiceName(activePlayer), sdbus::ObjectPath(playerObjectPath)); + unique_ptr playerProxy = + sdbus::createProxy(*connection, sdbus::ServiceName(*activePlayer), sdbus::ObjectPath(playerObjectPath)); sdbus::Variant metadataVariant = playerProxy->getProperty("Metadata").onInterface(playerInterfaceName); - if (metadataVariant.containsValueOfType>()) { - const std::map, sdbus::Variant>& metadata = - metadataVariant.get>(); + if (metadataVariant.containsValueOfType>()) { + const map& metadata = metadataVariant.get>(); string title; auto titleIter = metadata.find("xesam:title"); - if (titleIter != metadata.end() && titleIter->second.containsValueOfType()) { - title = titleIter->second.get(); - } + if (titleIter != metadata.end() && titleIter->second.containsValueOfType()) + title = titleIter->second.get(); string artist; auto artistIter = metadata.find("xesam:artist"); - if (artistIter != metadata.end() && artistIter->second.containsValueOfType>()) { - auto artists = artistIter->second.get>(); - if (!artists.empty()) { + if (artistIter != metadata.end() && artistIter->second.containsValueOfType>()) { + auto artists = artistIter->second.get>(); + 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 { // 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 ""; } diff --git a/src/os/linux/pkg_count.cpp b/src/os/linux/pkg_count.cpp index 573b5f2..0d7c6a6 100644 --- a/src/os/linux/pkg_count.cpp +++ b/src/os/linux/pkg_count.cpp @@ -2,7 +2,7 @@ namespace fs = std::filesystem; -fn GetApkPackageCount() -> std::optional { +fn GetApkPackageCount() -> optional { fs::path apkDbPath("/lib/apk/db/installed"); return std::nullopt; diff --git a/src/os/linux/pkg_count.h b/src/os/linux/pkg_count.h index 3283dbc..00c3a5b 100644 --- a/src/os/linux/pkg_count.h +++ b/src/os/linux/pkg_count.h @@ -2,8 +2,10 @@ #include "src/util/macros.h" +using std::optional; + // Get package count from dpkg (Debian/Ubuntu) -fn GetDpkgPackageCount() -> std::optional; +fn GetDpkgPackageCount() -> optional; // Get package count from RPM (Red Hat/Fedora/CentOS) fn GetRpmPackageCount() -> std::optional; diff --git a/src/os/os.h b/src/os/os.h index a38ec86..f7a1ded 100644 --- a/src/os/os.h +++ b/src/os/os.h @@ -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; +fn GetMemInfo() -> expected; /** * @brief Get the currently playing song metadata. */ -fn GetNowPlaying() -> string; +fn GetNowPlaying() -> expected; /** * @brief Get the OS version. */ -fn GetOSVersion() -> string; +fn GetOSVersion() -> expected; /** * @brief Get the current desktop environment. */ -fn GetDesktopEnvironment() -> string; +fn GetDesktopEnvironment() -> optional; /** * @brief Get the current window manager. diff --git a/src/util/types.h b/src/util/types.h index 3bad7d4..19be292 100644 --- a/src/util/types.h +++ b/src/util/types.h @@ -4,6 +4,10 @@ #include #include +#ifdef __linux__ +#include +#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 + >;