This commit is contained in:
Mars 2025-02-16 22:43:04 -05:00
parent 95374d942d
commit f9a9491da3
Signed by: pupbrained
GPG key ID: 0FF5B8826803F895
9 changed files with 141 additions and 103 deletions

View file

@ -71,14 +71,17 @@
++ linuxPkgs ++ linuxPkgs
++ darwinPkgs; ++ darwinPkgs;
linuxPkgs = nixpkgs.lib.optionals stdenv.isLinux (with pkgs; [ linuxPkgs =
pkgsStatic.glib nixpkgs.lib.optionals stdenv.isLinux (with pkgs; [
systemdLibs pkgsStatic.glib
sdbus-cpp systemdLibs
valgrind sdbus-cpp
xorg.libX11 valgrind
wayland xorg.libX11
]); ])
++ (with pkgs.pkgsStatic; [
wayland
]);
darwinPkgs = nixpkgs.lib.optionals stdenv.isDarwin (with pkgs.pkgsStatic.darwin.apple_sdk.frameworks; [ darwinPkgs = nixpkgs.lib.optionals stdenv.isDarwin (with pkgs.pkgsStatic.darwin.apple_sdk.frameworks; [
Foundation Foundation

View file

@ -114,4 +114,4 @@ executable(
objc_args: objc_args, objc_args: objc_args,
link_args: link_args, link_args: link_args,
dependencies: deps, dependencies: deps,
) )

View file

@ -8,22 +8,24 @@
using rfl::Result; using rfl::Result;
namespace fs = std::filesystem; namespace fs = std::filesystem;
inline fn GetConfigPath() -> fs::path { namespace {
inline fn GetConfigPath() -> fs::path {
#ifdef _WIN32 #ifdef _WIN32
const char* localAppData = std::getenv("LOCALAPPDATA"); const char* localAppData = std::getenv("LOCALAPPDATA");
if (!localAppData) if (!localAppData)
throw std::runtime_error("Environment variable LOCALAPPDATA is not set"); throw std::runtime_error("Environment variable LOCALAPPDATA is not set");
return fs::path(localAppData); return fs::path(localAppData);
#else #else
const char* home = std::getenv("HOME"); const char* home = std::getenv("HOME");
if (!home) if (!home)
throw std::runtime_error("Environment variable HOME is not set"); throw std::runtime_error("Environment variable HOME is not set");
return fs::path(home) / ".config"; return fs::path(home) / ".config";
#endif #endif
}
} }
fn Config::getInstance() -> Config { fn Config::getInstance() -> Config {

View file

@ -8,6 +8,7 @@
#include <string> #include <string>
#include "config/config.h" #include "config/config.h"
#include "ftxui/screen/color.hpp"
#include "os/os.h" #include "os/os.h"
constexpr const bool SHOW_ICONS = true; constexpr const bool SHOW_ICONS = true;
@ -73,17 +74,17 @@ namespace {
fn SystemInfoBox(const Config& config) -> Element { fn SystemInfoBox(const Config& config) -> Element {
// Fetch data // Fetch data
const std::string& name = config.general.get().name.get(); const string& name = config.general.get().name.get();
const std::string& date = GetDate(); const string& date = GetDate();
const Weather weather = config.weather.get(); const Weather weather = config.weather.get();
const std::string& host = GetHost(); const string& host = GetHost();
const std::string& kernelVersion = GetKernelVersion(); const string& kernelVersion = GetKernelVersion();
const std::string& osVersion = GetOSVersion(); const string& osVersion = GetOSVersion();
const u64 memInfo = GetMemInfo(); const u64 memInfo = GetMemInfo();
const std::string& desktopEnvironment = GetDesktopEnvironment(); const string& desktopEnvironment = GetDesktopEnvironment();
const std::string& windowManager = GetWindowManager(); const string& windowManager = GetWindowManager();
const bool nowPlayingEnabled = config.now_playing.get().enabled; const bool nowPlayingEnabled = config.now_playing.get().enabled;
const std::string& nowPlaying = nowPlayingEnabled ? GetNowPlaying() : ""; const string& nowPlaying = nowPlayingEnabled ? GetNowPlaying() : "";
const char *calendarIcon = "", *hostIcon = "", *kernelIcon = "", *osIcon = "", *memoryIcon = "", *weatherIcon = "", const char *calendarIcon = "", *hostIcon = "", *kernelIcon = "", *osIcon = "", *memoryIcon = "", *weatherIcon = "",
*musicIcon = ""; *musicIcon = "";

View file

@ -6,6 +6,7 @@
#include <algorithm> #include <algorithm>
#include <cstring> #include <cstring>
#include <dirent.h> #include <dirent.h>
#include <filesystem>
#include <fmt/format.h> #include <fmt/format.h>
#include <fstream> #include <fstream>
#include <ranges> #include <ranges>
@ -19,6 +20,8 @@
#include "os.h" #include "os.h"
#include "src/util/macros.h" #include "src/util/macros.h"
namespace fs = std::filesystem;
enum SessionType : u8 { Wayland, X11, TTY, Unknown }; enum SessionType : u8 { Wayland, X11, TTY, Unknown };
namespace { namespace {
@ -31,7 +34,7 @@ namespace {
return 0; return 0;
} }
std::string line; string line;
while (std::getline(input, line)) { while (std::getline(input, line)) {
if (line.starts_with("MemTotal")) { if (line.starts_with("MemTotal")) {
const size_t colonPos = line.find(':'); const size_t colonPos = line.find(':');
@ -104,17 +107,19 @@ namespace {
fn GetX11WindowManager() -> string { fn GetX11WindowManager() -> string {
Display* display = XOpenDisplay(nullptr); Display* display = XOpenDisplay(nullptr);
// If XOpenDisplay fails, likely in a TTY
if (!display) if (!display)
return "Unknown (X11)"; return "";
Atom supportingWmCheck = XInternAtom(display, "_NET_SUPPORTING_WM_CHECK", False); Atom supportingWmCheck = XInternAtom(display, "_NET_SUPPORTING_WM_CHECK", False);
Atom wmName = XInternAtom(display, "_NET_WM_NAME", False); Atom wmName = XInternAtom(display, "_NET_WM_NAME", False);
Atom utf8String = XInternAtom(display, "UTF8_STRING", 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 push
#pragma clang diagnostic ignored "-Wold-style-cast"
#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" #pragma clang diagnostic ignored "-Wunsafe-buffer-usage"
Window root = DefaultRootWindow(display); Window root = DefaultRootWindow(display); // NOLINT
#pragma clang diagnostic pop #pragma clang diagnostic pop
Window wmWindow = 0; Window wmWindow = 0;
@ -130,7 +135,8 @@ namespace {
0, 0,
1, 1,
False, False,
XA_WINDOW, // XA_WINDOW
static_cast<Atom>(33),
&actualType, &actualType,
&actualFormat, &actualFormat,
&nitems, &nitems,
@ -157,7 +163,7 @@ namespace {
&data &data
) == Success && ) == Success &&
data) { data) {
std::string name(std::bit_cast<char*>(data)); string name(std::bit_cast<char*>(data));
XFree(data); XFree(data);
XCloseDisplay(display); XCloseDisplay(display);
return name; return name;
@ -166,19 +172,19 @@ namespace {
XCloseDisplay(display); 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) if (input.find("hyprland") != std::string::npos)
return "Hyprland"; return "Hyprland";
return input; return input;
} }
fn ReadProcessCmdline(int pid) -> std::string { fn ReadProcessCmdline(int pid) -> string {
std::string path = "/proc/" + std::to_string(pid) + "/cmdline"; string path = "/proc/" + std::to_string(pid) + "/cmdline";
std::ifstream cmdlineFile(path); std::ifstream cmdlineFile(path);
std::string cmdline; string cmdline;
if (std::getline(cmdlineFile, cmdline)) { if (std::getline(cmdlineFile, cmdline)) {
// Replace null bytes with spaces // Replace null bytes with spaces
std::ranges::replace(cmdline, '\0', ' '); std::ranges::replace(cmdline, '\0', ' ');
@ -187,30 +193,26 @@ namespace {
return ""; return "";
} }
fn DetectHyprlandSpecific() -> std::string { fn DetectHyprlandSpecific() -> string {
// Check environment variables first // Check environment variables first
const char* xdgCurrentDesktop = std::getenv("XDG_CURRENT_DESKTOP"); const char* xdgCurrentDesktop = std::getenv("XDG_CURRENT_DESKTOP");
if (xdgCurrentDesktop && strcasestr(xdgCurrentDesktop, "hyprland")) { if (xdgCurrentDesktop && strcasestr(xdgCurrentDesktop, "hyprland"))
return "Hyprland"; return "Hyprland";
}
// Check for Hyprland's specific environment variable // Check for Hyprland's specific environment variable
if (std::getenv("HYPRLAND_INSTANCE_SIGNATURE")) { if (std::getenv("HYPRLAND_INSTANCE_SIGNATURE"))
return "Hyprland"; return "Hyprland";
}
// Check for Hyprland socket // Check for Hyprland socket
std::string socketPath = "/run/user/" + std::to_string(getuid()) + "/hypr"; if (fs::exists("/run/user/" + std::to_string(getuid()) + "/hypr"))
if (std::filesystem::exists(socketPath)) {
return "Hyprland"; return "Hyprland";
}
return ""; return "";
} }
fn GetWaylandCompositor() -> std::string { fn GetWaylandCompositor() -> string {
// First try Hyprland-specific detection // First try Hyprland-specific detection
std::string hypr = DetectHyprlandSpecific(); string hypr = DetectHyprlandSpecific();
if (!hypr.empty()) if (!hypr.empty())
return hypr; return hypr;
@ -229,10 +231,10 @@ namespace {
} }
// Read both comm and cmdline // Read both comm and cmdline
std::string compositorName; string compositorName;
// 1. Check comm (might be wrapped) // 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); std::ifstream commFile(commPath);
if (commFile >> compositorName) { if (commFile >> compositorName) {
std::ranges::subrange removedRange = std::ranges::remove(compositorName, '\n'); std::ranges::subrange removedRange = std::ranges::remove(compositorName, '\n');
@ -240,19 +242,19 @@ namespace {
} }
// 2. Check cmdline for actual binary reference // 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) { if (cmdline.find("hyprland") != std::string::npos) {
wl_display_disconnect(display); wl_display_disconnect(display);
return "Hyprland"; return "Hyprland";
} }
// 3. Check exe symlink // 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<char, PATH_MAX> buf; std::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';
std::string exe(buf.data()); string exe(buf.data());
if (exe.find("hyprland") != std::string::npos) { if (exe.find("hyprland") != std::string::npos) {
wl_display_disconnect(display); wl_display_disconnect(display);
return "Hyprland"; return "Hyprland";
@ -266,7 +268,7 @@ namespace {
} }
// Helper functions // Helper functions
fn ToLowercase(std::string str) -> std::string { fn ToLowercase(string str) -> string {
std::ranges::transform(str, str.begin(), ::tolower); std::ranges::transform(str, str.begin(), ::tolower);
return str; return str;
} }
@ -275,12 +277,12 @@ namespace {
return std::ranges::any_of(needles, [&](auto& n) { return haystack.find(n) != std::string_view::npos; }); 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 // Check XDG_CURRENT_DESKTOP
if (const char* xdgDe = std::getenv("XDG_CURRENT_DESKTOP")) { if (const char* xdgDe = std::getenv("XDG_CURRENT_DESKTOP")) {
std::string_view sview(xdgDe); std::string_view sview(xdgDe);
if (!sview.empty()) { if (!sview.empty()) {
std::string deStr(sview); string deStr(sview);
if (size_t colon = deStr.find(':'); colon != std::string::npos) if (size_t colon = deStr.find(':'); colon != std::string::npos)
deStr.erase(colon); deStr.erase(colon);
if (!deStr.empty()) if (!deStr.empty())
@ -298,8 +300,7 @@ namespace {
return ""; return "";
} }
fn DetectFromSessionFiles() -> std::string { fn DetectFromSessionFiles() -> string {
namespace fs = std::filesystem;
const std::vector<fs::path> sessionPaths = { "/usr/share/xsessions", "/usr/share/wayland-sessions" }; 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 = { const std::vector<std::pair<std::string, std::vector<std::string>>> dePatterns = {
@ -321,8 +322,8 @@ namespace {
if (!entry.is_regular_file()) if (!entry.is_regular_file())
continue; continue;
const std::string filename = entry.path().stem(); const string filename = entry.path().stem();
auto lowerFilename = ToLowercase(filename); auto lowerFilename = ToLowercase(filename);
for (const auto& [deName, patterns] : dePatterns) { for (const auto& [deName, patterns] : dePatterns) {
if (ContainsAny(lowerFilename, patterns)) if (ContainsAny(lowerFilename, patterns))
@ -333,7 +334,7 @@ namespace {
return ""; return "";
} }
fn DetectFromProcesses() -> std::string { fn DetectFromProcesses() -> string {
const std::vector<std::pair<std::string, std::string>> processChecks = { const std::vector<std::pair<std::string, std::string>> processChecks = {
{ "plasmashell", "KDE" }, { "plasmashell", "KDE" },
{ "gnome-shell", "GNOME" }, { "gnome-shell", "GNOME" },
@ -345,7 +346,7 @@ namespace {
}; };
std::ifstream cmdline("/proc/self/environ"); std::ifstream cmdline("/proc/self/environ");
std::string envVars((std::istreambuf_iterator<char>(cmdline)), std::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.find(process) != std::string::npos) if (envVars.find(process) != std::string::npos)
@ -354,23 +355,22 @@ namespace {
return "Unknown"; return "Unknown";
} }
fn CountNix() noexcept -> std::optional<size_t> { fn CountNix() noexcept -> std::optional<usize> {
constexpr std::string_view dbPath = "/nix/var/nix/db/db.sqlite"; 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;"; constexpr std::string_view querySql = "SELECT COUNT(*) FROM ValidPaths WHERE sigs IS NOT NULL;";
sqlite3* sqlDB = nullptr; sqlite3* sqlDB = nullptr;
sqlite3_stmt* stmt = nullptr; sqlite3_stmt* stmt = nullptr;
size_t count = 0; usize count = 0;
// 1. Direct URI construction without string concatenation // 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) ? "&" : "?"); fmt::format("file:{}{}immutable=1", dbPath, (dbPath.find('?') != std::string_view::npos) ? "&" : "?");
// 2. Open database with optimized flags // 2. Open database with optimized flags
if (sqlite3_open_v2(uri.c_str(), &sqlDB, SQLITE_OPEN_READONLY | SQLITE_OPEN_URI | SQLITE_OPEN_NOMUTEX, nullptr) != 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; return std::nullopt;
}
// 3. Configure database for maximum read performance // 3. Configure database for maximum read performance
sqlite3_exec(sqlDB, "PRAGMA journal_mode=OFF; PRAGMA mmap_size=268435456;", nullptr, nullptr, nullptr); 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 // 4. Single-step prepared statement execution
if (sqlite3_prepare_v3(sqlDB, querySql.data(), querySql.size(), SQLITE_PREPARE_PERSISTENT, &stmt, nullptr) == if (sqlite3_prepare_v3(sqlDB, querySql.data(), querySql.size(), SQLITE_PREPARE_PERSISTENT, &stmt, nullptr) ==
SQLITE_OK) { SQLITE_OK) {
if (sqlite3_step(stmt) == SQLITE_ROW) { if (sqlite3_step(stmt) == SQLITE_ROW)
count = static_cast<size_t>(sqlite3_column_int64(stmt, 0)); count = static_cast<usize>(sqlite3_column_int64(stmt, 0));
}
sqlite3_finalize(stmt); sqlite3_finalize(stmt);
} }
@ -389,42 +389,41 @@ namespace {
} }
fn CountNixWithCache() noexcept -> std::optional<size_t> { fn CountNixWithCache() noexcept -> std::optional<size_t> {
constexpr std::string_view dbPath = "/nix/var/nix/db/db.sqlite"; constexpr const char* dbPath = "/nix/var/nix/db/db.sqlite";
constexpr std::string_view cachePath = "/tmp/nix_pkg_count.cache"; constexpr const char* cachePath = "/tmp/nix_pkg_count.cache";
// 1. Check cache validity atomically
try { try {
const auto dbMtime = std::filesystem::last_write_time(dbPath); using mtime = fs::file_time_type;
const auto cacheMtime = std::filesystem::last_write_time(cachePath);
if (std::filesystem::exists(cachePath) && dbMtime <= cacheMtime) { const mtime dbMtime = fs::last_write_time(dbPath);
// Read cached value (atomic read) const mtime cacheMtime = fs::last_write_time(cachePath);
std::ifstream cache(cachePath.data(), std::ios::binary);
if (fs::exists(cachePath) && dbMtime <= cacheMtime) {
std::ifstream cache(cachePath, std::ios::binary);
size_t count = 0; size_t count = 0;
cache.read(std::bit_cast<char*>(&count), sizeof(count)); cache.read(std::bit_cast<char*>(&count), sizeof(count));
return cache ? std::optional(count) : std::nullopt; 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 std::optional<usize> count = CountNix();
const auto count = CountNix(); // Original optimized function
// 3. Update cache atomically (write+rename pattern)
if (count) { if (count) {
constexpr std::string_view tmpPath = "/tmp/nix_pkg_count.tmp"; constexpr const char* tmpPath = "/tmp/nix_pkg_count.tmp";
{
std::ofstream tmp(tmpPath.data(), std::ios::binary | std::ios::trunc);
tmp.write(std::bit_cast<const char*>(&*count), sizeof(*count));
} // RAII close
std::filesystem::rename(tmpPath, cachePath); {
std::ofstream tmp(tmpPath, std::ios::binary | std::ios::trunc);
tmp.write(std::bit_cast<const char*>(&*count), sizeof(*count));
}
fs::rename(tmpPath, cachePath);
} }
return count; return count;
} }
} }
fn GetOSVersion() -> std::string { fn GetOSVersion() -> string {
constexpr const char* path = "/etc/os-release"; constexpr const char* path = "/etc/os-release";
std::ifstream file(path); std::ifstream file(path);
@ -482,14 +481,14 @@ fn GetNowPlaying() -> string {
const std::map<std::basic_string<char>, sdbus::Variant>& metadata = const std::map<std::basic_string<char>, sdbus::Variant>& metadata =
metadataVariant.get<std::map<std::string, sdbus::Variant>>(); metadataVariant.get<std::map<std::string, sdbus::Variant>>();
std::string title; string title;
auto titleIter = metadata.find("xesam:title"); auto titleIter = metadata.find("xesam:title");
if (titleIter != metadata.end() && titleIter->second.containsValueOfType<std::string>()) { if (titleIter != metadata.end() && titleIter->second.containsValueOfType<std::string>()) {
title = titleIter->second.get<std::string>(); title = titleIter->second.get<std::string>();
} }
std::string artist; string artist;
auto artistIter = metadata.find("xesam:artist"); auto artistIter = metadata.find("xesam:artist");
if (artistIter != metadata.end() && artistIter->second.containsValueOfType<std::vector<std::string>>()) { if (artistIter != metadata.end() && artistIter->second.containsValueOfType<std::vector<std::string>>()) {
auto artists = artistIter->second.get<std::vector<std::string>>(); auto artists = artistIter->second.get<std::vector<std::string>>();
if (!artists.empty()) { if (!artists.empty()) {
@ -497,7 +496,7 @@ fn GetNowPlaying() -> string {
} }
} }
std::string result; string result;
if (!artist.empty() && !title.empty()) { if (!artist.empty() && !title.empty()) {
result = artist + " - " + title; result = artist + " - " + title;
} else if (!title.empty()) { } else if (!title.empty()) {
@ -528,14 +527,14 @@ fn GetWindowManager() -> string {
// Prefer Wayland detection if Wayland session // Prefer Wayland detection if Wayland session
if ((waylandDisplay != nullptr) || (xdgSessionType && strstr(xdgSessionType, "wayland"))) { if ((waylandDisplay != nullptr) || (xdgSessionType && strstr(xdgSessionType, "wayland"))) {
std::string compositor = GetWaylandCompositor(); string compositor = GetWaylandCompositor();
if (!compositor.empty()) if (!compositor.empty())
return compositor; return compositor;
// Fallback environment check // Fallback environment check
const char* xdgCurrentDesktop = std::getenv("XDG_CURRENT_DESKTOP"); const char* xdgCurrentDesktop = std::getenv("XDG_CURRENT_DESKTOP");
if (xdgCurrentDesktop) { if (xdgCurrentDesktop) {
std::string desktop(xdgCurrentDesktop); string desktop(xdgCurrentDesktop);
std::ranges::transform(compositor, compositor.begin(), ::tolower); std::ranges::transform(compositor, compositor.begin(), ::tolower);
if (desktop.find("hyprland") != std::string::npos) if (desktop.find("hyprland") != std::string::npos)
return "hyprland"; return "hyprland";
@ -543,7 +542,7 @@ fn GetWindowManager() -> string {
} }
// X11 detection // X11 detection
std::string x11wm = GetX11WindowManager(); string x11wm = GetX11WindowManager();
if (!x11wm.empty()) if (!x11wm.empty())
return x11wm; return x11wm;
@ -578,7 +577,7 @@ fn GetHost() -> string {
return ""; return "";
} }
std::string productFamily; string productFamily;
if (!std::getline(file, productFamily)) { if (!std::getline(file, productFamily)) {
ERROR_LOG("Failed to read from {}", path); ERROR_LOG("Failed to read from {}", path);
return ""; return "";

View file

28
src/os/linux/pkg_count.h Normal file
View file

@ -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;

View file

@ -42,3 +42,8 @@ fn GetHost() -> string;
* @brief Get the kernel version. * @brief Get the kernel version.
*/ */
fn GetKernelVersion() -> string; fn GetKernelVersion() -> string;
/**
* @brief Get the number of installed packages.
*/
fn GetPackageCount() -> u64;

View file

@ -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 string filename = std::filesystem::path(loc.file_name()).lexically_normal().string();
const struct tm time = *std::localtime(&now); const struct tm time = *std::localtime(&now);
// Timestamp and level // Timestamp and level
fmt::print(fg(log_colors::timestamp), "[{:%H:%M:%S}] ", time); 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 push
#pragma clang diagnostic ignored "-Wunused-macros" #pragma clang diagnostic ignored "-Wunused-macros"
#ifdef NDEBUG #ifdef NDEBUG
#define DEBUG_LOG(...) (void)0 #define DEBUG_LOG(...) static_cast<void>(0)
#else #else
#define DEBUG_LOG(...) LogImpl(LogLevel::DEBUG, std::source_location::current(), __VA_ARGS__) #define DEBUG_LOG(...) LogImpl(LogLevel::DEBUG, std::source_location::current(), __VA_ARGS__)
#endif #endif