diff --git a/flake.nix b/flake.nix index 12c3e7b..c340c63 100644 --- a/flake.nix +++ b/flake.nix @@ -71,14 +71,17 @@ ++ linuxPkgs ++ darwinPkgs; - linuxPkgs = nixpkgs.lib.optionals stdenv.isLinux (with pkgs; [ - pkgsStatic.glib - systemdLibs - sdbus-cpp - valgrind - xorg.libX11 - wayland - ]); + linuxPkgs = + nixpkgs.lib.optionals stdenv.isLinux (with pkgs; [ + pkgsStatic.glib + systemdLibs + sdbus-cpp + valgrind + xorg.libX11 + ]) + ++ (with pkgs.pkgsStatic; [ + wayland + ]); darwinPkgs = nixpkgs.lib.optionals stdenv.isDarwin (with pkgs.pkgsStatic.darwin.apple_sdk.frameworks; [ Foundation diff --git a/meson.build b/meson.build index 161bf49..f01495d 100644 --- a/meson.build +++ b/meson.build @@ -114,4 +114,4 @@ executable( objc_args: objc_args, link_args: link_args, dependencies: deps, -) +) \ No newline at end of file diff --git a/src/config/config.cpp b/src/config/config.cpp index b3d4f31..82aa653 100644 --- a/src/config/config.cpp +++ b/src/config/config.cpp @@ -8,22 +8,24 @@ using rfl::Result; namespace fs = std::filesystem; -inline fn GetConfigPath() -> fs::path { +namespace { + inline fn GetConfigPath() -> fs::path { #ifdef _WIN32 - const char* localAppData = std::getenv("LOCALAPPDATA"); + const char* localAppData = std::getenv("LOCALAPPDATA"); - if (!localAppData) - throw std::runtime_error("Environment variable LOCALAPPDATA is not set"); + if (!localAppData) + throw std::runtime_error("Environment variable LOCALAPPDATA is not set"); - return fs::path(localAppData); + return fs::path(localAppData); #else - const char* home = std::getenv("HOME"); + const char* home = std::getenv("HOME"); - if (!home) - throw std::runtime_error("Environment variable HOME is not set"); + if (!home) + throw std::runtime_error("Environment variable HOME is not set"); - return fs::path(home) / ".config"; + return fs::path(home) / ".config"; #endif + } } fn Config::getInstance() -> Config { diff --git a/src/main.cpp b/src/main.cpp index fce1eee..c34f49d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -8,6 +8,7 @@ #include #include "config/config.h" +#include "ftxui/screen/color.hpp" #include "os/os.h" constexpr const bool SHOW_ICONS = true; @@ -73,17 +74,17 @@ namespace { fn SystemInfoBox(const Config& config) -> Element { // Fetch data - const std::string& name = config.general.get().name.get(); - const std::string& date = GetDate(); - const Weather weather = config.weather.get(); - const std::string& host = GetHost(); - const std::string& kernelVersion = GetKernelVersion(); - const std::string& osVersion = GetOSVersion(); - const u64 memInfo = GetMemInfo(); - const std::string& desktopEnvironment = GetDesktopEnvironment(); - const std::string& windowManager = GetWindowManager(); - const bool nowPlayingEnabled = config.now_playing.get().enabled; - const std::string& nowPlaying = nowPlayingEnabled ? GetNowPlaying() : ""; + const string& name = config.general.get().name.get(); + const string& date = GetDate(); + const Weather weather = config.weather.get(); + const string& host = GetHost(); + const string& kernelVersion = GetKernelVersion(); + const string& osVersion = GetOSVersion(); + const u64 memInfo = GetMemInfo(); + const string& desktopEnvironment = GetDesktopEnvironment(); + const string& windowManager = GetWindowManager(); + const bool nowPlayingEnabled = config.now_playing.get().enabled; + const string& nowPlaying = nowPlayingEnabled ? GetNowPlaying() : ""; const char *calendarIcon = "", *hostIcon = "", *kernelIcon = "", *osIcon = "", *memoryIcon = "", *weatherIcon = "", *musicIcon = ""; diff --git a/src/os/linux.cpp b/src/os/linux.cpp index 5bcee52..1d6c038 100644 --- a/src/os/linux.cpp +++ b/src/os/linux.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -19,6 +20,8 @@ #include "os.h" #include "src/util/macros.h" +namespace fs = std::filesystem; + enum SessionType : u8 { Wayland, X11, TTY, Unknown }; namespace { @@ -31,7 +34,7 @@ namespace { return 0; } - std::string line; + string line; while (std::getline(input, line)) { if (line.starts_with("MemTotal")) { const size_t colonPos = line.find(':'); @@ -104,17 +107,19 @@ namespace { fn GetX11WindowManager() -> string { Display* display = XOpenDisplay(nullptr); + + // If XOpenDisplay fails, likely in a TTY if (!display) - return "Unknown (X11)"; + return ""; Atom supportingWmCheck = XInternAtom(display, "_NET_SUPPORTING_WM_CHECK", False); Atom wmName = XInternAtom(display, "_NET_WM_NAME", False); Atom utf8String = XInternAtom(display, "UTF8_STRING", False); - // ignore unsafe buffer access warning, can't really get around it #pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wold-style-cast" #pragma clang diagnostic ignored "-Wunsafe-buffer-usage" - Window root = DefaultRootWindow(display); + Window root = DefaultRootWindow(display); // NOLINT #pragma clang diagnostic pop Window wmWindow = 0; @@ -130,7 +135,8 @@ namespace { 0, 1, False, - XA_WINDOW, + // XA_WINDOW + static_cast(33), &actualType, &actualFormat, &nitems, @@ -157,7 +163,7 @@ namespace { &data ) == Success && data) { - std::string name(std::bit_cast(data)); + string name(std::bit_cast(data)); XFree(data); XCloseDisplay(display); return name; @@ -166,19 +172,19 @@ namespace { XCloseDisplay(display); - return "Unknown (X11)"; + return "Unknown (X11)"; // Changed to empty string } - fn TrimHyprlandWrapper(const std::string& input) -> std::string { + fn TrimHyprlandWrapper(const std::string& input) -> string { if (input.find("hyprland") != std::string::npos) return "Hyprland"; return input; } - fn ReadProcessCmdline(int pid) -> std::string { - std::string path = "/proc/" + std::to_string(pid) + "/cmdline"; + fn ReadProcessCmdline(int pid) -> string { + string path = "/proc/" + std::to_string(pid) + "/cmdline"; std::ifstream cmdlineFile(path); - std::string cmdline; + string cmdline; if (std::getline(cmdlineFile, cmdline)) { // Replace null bytes with spaces std::ranges::replace(cmdline, '\0', ' '); @@ -187,30 +193,26 @@ namespace { return ""; } - fn DetectHyprlandSpecific() -> std::string { + fn DetectHyprlandSpecific() -> string { // Check environment variables first const char* xdgCurrentDesktop = std::getenv("XDG_CURRENT_DESKTOP"); - if (xdgCurrentDesktop && strcasestr(xdgCurrentDesktop, "hyprland")) { + if (xdgCurrentDesktop && strcasestr(xdgCurrentDesktop, "hyprland")) return "Hyprland"; - } // Check for Hyprland's specific environment variable - if (std::getenv("HYPRLAND_INSTANCE_SIGNATURE")) { + if (std::getenv("HYPRLAND_INSTANCE_SIGNATURE")) return "Hyprland"; - } // Check for Hyprland socket - std::string socketPath = "/run/user/" + std::to_string(getuid()) + "/hypr"; - if (std::filesystem::exists(socketPath)) { + if (fs::exists("/run/user/" + std::to_string(getuid()) + "/hypr")) return "Hyprland"; - } return ""; } - fn GetWaylandCompositor() -> std::string { + fn GetWaylandCompositor() -> string { // First try Hyprland-specific detection - std::string hypr = DetectHyprlandSpecific(); + string hypr = DetectHyprlandSpecific(); if (!hypr.empty()) return hypr; @@ -229,10 +231,10 @@ namespace { } // Read both comm and cmdline - std::string compositorName; + string compositorName; // 1. Check comm (might be wrapped) - std::string commPath = "/proc/" + std::to_string(cred.pid) + "/comm"; + string commPath = "/proc/" + std::to_string(cred.pid) + "/comm"; std::ifstream commFile(commPath); if (commFile >> compositorName) { std::ranges::subrange removedRange = std::ranges::remove(compositorName, '\n'); @@ -240,19 +242,19 @@ namespace { } // 2. Check cmdline for actual binary reference - std::string cmdline = ReadProcessCmdline(cred.pid); + string cmdline = ReadProcessCmdline(cred.pid); if (cmdline.find("hyprland") != std::string::npos) { wl_display_disconnect(display); return "Hyprland"; } // 3. Check exe symlink - std::string exePath = "/proc/" + std::to_string(cred.pid) + "/exe"; + string exePath = "/proc/" + std::to_string(cred.pid) + "/exe"; std::array buf; ssize_t lenBuf = readlink(exePath.c_str(), buf.data(), buf.size() - 1); if (lenBuf != -1) { buf.at(static_cast(lenBuf)) = '\0'; - std::string exe(buf.data()); + string exe(buf.data()); if (exe.find("hyprland") != std::string::npos) { wl_display_disconnect(display); return "Hyprland"; @@ -266,7 +268,7 @@ namespace { } // Helper functions - fn ToLowercase(std::string str) -> std::string { + fn ToLowercase(string str) -> string { std::ranges::transform(str, str.begin(), ::tolower); return str; } @@ -275,12 +277,12 @@ namespace { return std::ranges::any_of(needles, [&](auto& n) { return haystack.find(n) != std::string_view::npos; }); } - fn DetectFromEnvVars() -> std::string { + fn DetectFromEnvVars() -> string { // Check XDG_CURRENT_DESKTOP if (const char* xdgDe = std::getenv("XDG_CURRENT_DESKTOP")) { std::string_view sview(xdgDe); if (!sview.empty()) { - std::string deStr(sview); + string deStr(sview); if (size_t colon = deStr.find(':'); colon != std::string::npos) deStr.erase(colon); if (!deStr.empty()) @@ -298,8 +300,7 @@ namespace { return ""; } - fn DetectFromSessionFiles() -> std::string { - namespace fs = std::filesystem; + fn DetectFromSessionFiles() -> string { const std::vector sessionPaths = { "/usr/share/xsessions", "/usr/share/wayland-sessions" }; const std::vector>> dePatterns = { @@ -321,8 +322,8 @@ namespace { if (!entry.is_regular_file()) continue; - const std::string filename = entry.path().stem(); - auto lowerFilename = ToLowercase(filename); + const string filename = entry.path().stem(); + auto lowerFilename = ToLowercase(filename); for (const auto& [deName, patterns] : dePatterns) { if (ContainsAny(lowerFilename, patterns)) @@ -333,7 +334,7 @@ namespace { return ""; } - fn DetectFromProcesses() -> std::string { + fn DetectFromProcesses() -> string { const std::vector> processChecks = { { "plasmashell", "KDE" }, { "gnome-shell", "GNOME" }, @@ -345,7 +346,7 @@ namespace { }; std::ifstream cmdline("/proc/self/environ"); - std::string envVars((std::istreambuf_iterator(cmdline)), std::istreambuf_iterator()); + string envVars((std::istreambuf_iterator(cmdline)), std::istreambuf_iterator()); for (const auto& [process, deName] : processChecks) if (envVars.find(process) != std::string::npos) @@ -354,23 +355,22 @@ namespace { return "Unknown"; } - fn CountNix() noexcept -> std::optional { + 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;"; sqlite3* sqlDB = nullptr; sqlite3_stmt* stmt = nullptr; - size_t count = 0; + usize count = 0; // 1. Direct URI construction without string concatenation - const std::string uri = + const string uri = fmt::format("file:{}{}immutable=1", dbPath, (dbPath.find('?') != std::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) { + SQLITE_OK) return std::nullopt; - } // 3. Configure database for maximum read performance sqlite3_exec(sqlDB, "PRAGMA journal_mode=OFF; PRAGMA mmap_size=268435456;", nullptr, nullptr, nullptr); @@ -378,9 +378,9 @@ namespace { // 4. Single-step prepared statement execution if (sqlite3_prepare_v3(sqlDB, querySql.data(), querySql.size(), SQLITE_PREPARE_PERSISTENT, &stmt, nullptr) == SQLITE_OK) { - if (sqlite3_step(stmt) == SQLITE_ROW) { - count = static_cast(sqlite3_column_int64(stmt, 0)); - } + if (sqlite3_step(stmt) == SQLITE_ROW) + count = static_cast(sqlite3_column_int64(stmt, 0)); + sqlite3_finalize(stmt); } @@ -389,42 +389,41 @@ namespace { } fn CountNixWithCache() noexcept -> std::optional { - constexpr std::string_view dbPath = "/nix/var/nix/db/db.sqlite"; - constexpr std::string_view cachePath = "/tmp/nix_pkg_count.cache"; + constexpr const char* dbPath = "/nix/var/nix/db/db.sqlite"; + constexpr const char* cachePath = "/tmp/nix_pkg_count.cache"; - // 1. Check cache validity atomically try { - const auto dbMtime = std::filesystem::last_write_time(dbPath); - const auto cacheMtime = std::filesystem::last_write_time(cachePath); + using mtime = fs::file_time_type; - if (std::filesystem::exists(cachePath) && dbMtime <= cacheMtime) { - // Read cached value (atomic read) - std::ifstream cache(cachePath.data(), std::ios::binary); + const mtime dbMtime = fs::last_write_time(dbPath); + 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; } - } catch (...) {} // Ignore errors, fall through to rebuild cache + } catch (const std::exception& e) { DEBUG_LOG("Cache access failed: {}, rebuilding...", e.what()); } - // 2. Compute fresh value - const auto count = CountNix(); // Original optimized function + const std::optional count = CountNix(); - // 3. Update cache atomically (write+rename pattern) if (count) { - constexpr std::string_view tmpPath = "/tmp/nix_pkg_count.tmp"; - { - std::ofstream tmp(tmpPath.data(), std::ios::binary | std::ios::trunc); - tmp.write(std::bit_cast(&*count), sizeof(*count)); - } // RAII close + constexpr const char* tmpPath = "/tmp/nix_pkg_count.tmp"; - std::filesystem::rename(tmpPath, cachePath); + { + std::ofstream tmp(tmpPath, std::ios::binary | std::ios::trunc); + tmp.write(std::bit_cast(&*count), sizeof(*count)); + } + + fs::rename(tmpPath, cachePath); } return count; } } -fn GetOSVersion() -> std::string { +fn GetOSVersion() -> string { constexpr const char* path = "/etc/os-release"; std::ifstream file(path); @@ -482,14 +481,14 @@ fn GetNowPlaying() -> string { const std::map, sdbus::Variant>& metadata = metadataVariant.get>(); - std::string title; - auto titleIter = metadata.find("xesam:title"); + string title; + auto titleIter = metadata.find("xesam:title"); if (titleIter != metadata.end() && titleIter->second.containsValueOfType()) { title = titleIter->second.get(); } - std::string artist; - auto artistIter = metadata.find("xesam:artist"); + string artist; + auto artistIter = metadata.find("xesam:artist"); if (artistIter != metadata.end() && artistIter->second.containsValueOfType>()) { auto artists = artistIter->second.get>(); if (!artists.empty()) { @@ -497,7 +496,7 @@ fn GetNowPlaying() -> string { } } - std::string result; + string result; if (!artist.empty() && !title.empty()) { result = artist + " - " + title; } else if (!title.empty()) { @@ -528,14 +527,14 @@ fn GetWindowManager() -> string { // Prefer Wayland detection if Wayland session if ((waylandDisplay != nullptr) || (xdgSessionType && strstr(xdgSessionType, "wayland"))) { - std::string compositor = GetWaylandCompositor(); + string compositor = GetWaylandCompositor(); if (!compositor.empty()) return compositor; // Fallback environment check const char* xdgCurrentDesktop = std::getenv("XDG_CURRENT_DESKTOP"); if (xdgCurrentDesktop) { - std::string desktop(xdgCurrentDesktop); + string desktop(xdgCurrentDesktop); std::ranges::transform(compositor, compositor.begin(), ::tolower); if (desktop.find("hyprland") != std::string::npos) return "hyprland"; @@ -543,7 +542,7 @@ fn GetWindowManager() -> string { } // X11 detection - std::string x11wm = GetX11WindowManager(); + string x11wm = GetX11WindowManager(); if (!x11wm.empty()) return x11wm; @@ -578,7 +577,7 @@ fn GetHost() -> string { return ""; } - std::string productFamily; + string productFamily; if (!std::getline(file, productFamily)) { ERROR_LOG("Failed to read from {}", path); return ""; diff --git a/src/os/linux/pkg_count.cpp b/src/os/linux/pkg_count.cpp new file mode 100644 index 0000000..e69de29 diff --git a/src/os/linux/pkg_count.h b/src/os/linux/pkg_count.h new file mode 100644 index 0000000..3f71bbc --- /dev/null +++ b/src/os/linux/pkg_count.h @@ -0,0 +1,28 @@ +#pragma once + +// Get package count from dpkg (Debian/Ubuntu) +int get_dpkg_package_count(); + +// Get package count from RPM (Red Hat/Fedora/CentOS) +int get_rpm_package_count(); + +// Get package count from pacman (Arch Linux) +int get_pacman_package_count(); + +// Get package count from Portage (Gentoo) +int get_portage_package_count(); + +// Get package count from zypper (openSUSE) +int get_zypper_package_count(); + +// Get package count from flatpak +int get_flatpak_package_count(); + +// Get package count from snap +int get_snap_package_count(); + +// Get package count from AppImage +int get_appimage_package_count(); + +// Get total package count from all available package managers +fn GetTotalPackageCount() -> int; diff --git a/src/os/os.h b/src/os/os.h index 0464c5d..d240a3b 100644 --- a/src/os/os.h +++ b/src/os/os.h @@ -42,3 +42,8 @@ fn GetHost() -> string; * @brief Get the kernel version. */ fn GetKernelVersion() -> string; + +/** + * @brief Get the number of installed packages. + */ +fn GetPackageCount() -> u64; diff --git a/src/util/macros.h b/src/util/macros.h index 6212a4d..2350532 100644 --- a/src/util/macros.h +++ b/src/util/macros.h @@ -38,8 +38,8 @@ void LogImpl(LogLevel level, const std::source_location& loc, fmt::format_string } }(); - const std::string filename = std::filesystem::path(loc.file_name()).lexically_normal().string(); - const struct tm time = *std::localtime(&now); + const string filename = std::filesystem::path(loc.file_name()).lexically_normal().string(); + const struct tm time = *std::localtime(&now); // Timestamp and level fmt::print(fg(log_colors::timestamp), "[{:%H:%M:%S}] ", time); @@ -60,7 +60,7 @@ void LogImpl(LogLevel level, const std::source_location& loc, fmt::format_string #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-macros" #ifdef NDEBUG -#define DEBUG_LOG(...) (void)0 +#define DEBUG_LOG(...) static_cast(0) #else #define DEBUG_LOG(...) LogImpl(LogLevel::DEBUG, std::source_location::current(), __VA_ARGS__) #endif