i have no idea if any of this is better
This commit is contained in:
parent
ea6bf62c90
commit
a02ffbed47
|
@ -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
|
||||
|
|
143
src/main.cpp
143
src/main.cpp
|
@ -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);
|
||||
|
|
368
src/os/linux.cpp
368
src/os/linux.cpp
|
@ -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 "";
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>;
|
||||
|
|
10
src/os/os.h
10
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<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.
|
||||
|
|
|
@ -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
|
||||
>;
|
||||
|
|
Loading…
Reference in a new issue