This commit is contained in:
Mars 2025-04-24 00:10:10 -04:00
parent 2219182539
commit 9695ceec8a
Signed by: pupbrained
GPG key ID: 0FF5B8826803F895
13 changed files with 261 additions and 262 deletions

View file

@ -12,6 +12,7 @@ Checks: >
-cppcoreguidelines-avoid-magic-numbers, -cppcoreguidelines-avoid-magic-numbers,
-cppcoreguidelines-macro-usage, -cppcoreguidelines-macro-usage,
-cppcoreguidelines-owning-memory, -cppcoreguidelines-owning-memory,
-cppcoreguidelines-pro-bounds-array-to-pointer-decay,
-cppcoreguidelines-pro-type-member-init, -cppcoreguidelines-pro-type-member-init,
-cppcoreguidelines-pro-type-vararg, -cppcoreguidelines-pro-type-vararg,
-fuchsia-*, -fuchsia-*,

View file

@ -89,7 +89,7 @@ add_project_arguments(common_cpp_args, language : 'cpp')
# ------- # # ------- #
# Files # # Files #
# ------- # # ------- #
base_sources = files('src/config/config.cpp', 'src/config/weather.cpp', 'src/main.cpp') base_sources = files('src/core/system_data.cpp', 'src/config/config.cpp', 'src/config/weather.cpp', 'src/main.cpp')
platform_sources = { platform_sources = {
'linux' : ['src/os/linux.cpp', 'src/os/linux/issetugid_stub.cpp', 'src/os/linux/display_guards.cpp'], 'linux' : ['src/os/linux.cpp', 'src/os/linux/issetugid_stub.cpp', 'src/os/linux/display_guards.cpp'],

View file

@ -74,13 +74,13 @@ namespace {
String defaultName = GetUserNameA(username.data(), &size) ? username.data() : "User"; String defaultName = GetUserNameA(username.data(), &size) ? username.data() : "User";
#else #else
const struct passwd* pwd = getpwuid(getuid()); const passwd* pwd = getpwuid(getuid());
CStr pwdName = pwd ? pwd->pw_name : nullptr; CStr pwdName = pwd ? pwd->pw_name : nullptr;
const Result<String, EnvError> envUser = GetEnv("USER"); const Result<String, EnvError> envUser = GetEnv("USER");
const Result<String, EnvError> envLogname = GetEnv("LOGNAME"); const Result<String, EnvError> envLogname = GetEnv("LOGNAME");
String defaultName = (pwdName) ? pwdName : (envUser) ? *envUser : (envLogname) ? *envLogname : "User"; String defaultName = pwdName ? pwdName : envUser ? *envUser : envLogname ? *envLogname : "User";
#endif #endif
toml::table* general = root.insert("general", toml::table {}).first->second.as_table(); toml::table* general = root.insert("general", toml::table {}).first->second.as_table();

View file

@ -21,7 +21,7 @@ struct General {
DWORD size = sizeof(username); DWORD size = sizeof(username);
return GetUserNameA(username.data(), &size) ? username.data() : "User"; return GetUserNameA(username.data(), &size) ? username.data() : "User";
#else #else
if (struct passwd* pwd = getpwuid(getuid())) if (const passwd* pwd = getpwuid(getuid()))
return pwd->pw_name; return pwd->pw_name;
if (Result<String, EnvError> envUser = GetEnv("USER")) if (Result<String, EnvError> envUser = GetEnv("USER"))
@ -55,7 +55,7 @@ struct Weather {
static fn fromToml(const toml::table& tbl) -> Weather { static fn fromToml(const toml::table& tbl) -> Weather {
Weather weather; Weather weather;
Option<String> apiKey = tbl["api_key"].value<String>(); const Option<String> apiKey = tbl["api_key"].value<String>();
weather.enabled = tbl["enabled"].value_or<bool>(false) && apiKey; weather.enabled = tbl["enabled"].value_or<bool>(false) && apiKey;

View file

@ -39,7 +39,7 @@ namespace {
DEBUG_LOG("Reading from cache file..."); DEBUG_LOG("Reading from cache file...");
try { try {
const String content((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>()); const String content((std::istreambuf_iterator(ifs)), std::istreambuf_iterator<char>());
WeatherOutput result; WeatherOutput result;
if (const glz::error_ctx errc = glz::read<glaze_opts>(result, content); errc.ec != glz::error_code::none) if (const glz::error_ctx errc = glz::read<glaze_opts>(result, content); errc.ec != glz::error_code::none)

64
src/core/system_data.cpp Normal file
View file

@ -0,0 +1,64 @@
#include <future>
#include "system_data.h"
#include "src/config/config.h"
#include "src/os/os.h"
namespace {
fn GetDate() -> String {
using namespace std::chrono;
const year_month_day ymd = year_month_day { floor<days>(system_clock::now()) };
String month = std::format("{:%B}", ymd);
u32 day = static_cast<u32>(ymd.day());
CStr suffix = day >= 11 && day <= 13 ? "th"
: day % 10 == 1 ? "st"
: day % 10 == 2 ? "nd"
: day % 10 == 3 ? "rd"
: "th";
return std::format("{} {}{}", month, day, suffix);
}
}
fn SystemData::fetchSystemData(const Config& config) -> SystemData {
SystemData data {
.date = GetDate(),
.host = os::GetHost(),
.kernel_version = os::GetKernelVersion(),
.os_version = os::GetOSVersion(),
.mem_info = os::GetMemInfo(),
.desktop_environment = os::GetDesktopEnvironment(),
.window_manager = os::GetWindowManager(),
};
auto diskShell = std::async(std::launch::async, [] {
auto [used, total] = os::GetDiskUsage();
return std::make_tuple(used, total, os::GetShell());
});
std::future<WeatherOutput> weather;
std::future<Result<String, NowPlayingError>> nowPlaying;
if (config.weather.enabled)
weather = std::async(std::launch::async, [&config] { return config.weather.getWeatherInfo(); });
if (config.now_playing.enabled)
nowPlaying = std::async(std::launch::async, os::GetNowPlaying);
auto [used, total, shell] = diskShell.get();
data.disk_used = used;
data.disk_total = total;
data.shell = shell;
if (weather.valid())
data.weather_info = weather.get();
if (nowPlaying.valid())
data.now_playing = nowPlaying.get();
return data;
}

36
src/core/system_data.h Normal file
View file

@ -0,0 +1,36 @@
#pragma once
#include "src/config/config.h"
#include "src/util/types.h"
struct BytesToGiB {
u64 value;
};
constexpr u64 GIB = 1'073'741'824;
template <>
struct std::formatter<BytesToGiB> : std::formatter<double> {
fn format(const BytesToGiB& BTG, auto& ctx) const {
return std::format_to(ctx.out(), "{:.2f}GiB", static_cast<f64>(BTG.value) / GIB);
}
};
// Structure to hold the collected system data
struct SystemData {
String date;
String host;
String kernel_version;
Result<String, String> os_version;
Result<u64, String> mem_info;
Option<String> desktop_environment;
String window_manager;
Option<Result<String, NowPlayingError>> now_playing;
Option<WeatherOutput> weather_info;
u64 disk_used;
u64 disk_total;
String shell;
// Static function to fetch the data
static fn fetchSystemData(const Config& config) -> SystemData;
};

View file

@ -8,24 +8,11 @@
#include <variant> #include <variant>
#include "config/config.h" #include "config/config.h"
#include "core/system_data.h"
#include "os/os.h" #include "os/os.h"
constexpr inline bool SHOW_ICONS = true; constexpr inline bool SHOW_ICONS = true;
struct BytesToGiB {
u64 value;
};
// 1024^3 (size of 1 GiB)
constexpr u64 GIB = 1'073'741'824;
template <>
struct std::formatter<BytesToGiB> : std::formatter<double> {
fn format(const BytesToGiB& BTG, auto& ctx) const {
return std::format_to(ctx.out(), "{:.2f}GiB", static_cast<f64>(BTG.value) / GIB);
}
};
namespace ui { namespace ui {
using ftxui::Color; using ftxui::Color;
@ -99,93 +86,6 @@ namespace ui {
} }
namespace { namespace {
template <typename T, typename E, typename ValueFunc, typename ErrorFunc>
fn visit_result(const Result<T, E>& exp, ValueFunc value_func, ErrorFunc error_func) {
if (exp.has_value())
return value_func(*exp);
return error_func(exp.error());
}
fn GetDate() -> String {
using namespace std::chrono;
const year_month_day ymd = year_month_day { floor<days>(system_clock::now()) };
String month = std::format("{:%B}", ymd);
u32 day = static_cast<u32>(ymd.day());
CStr suffix = static_cast<CStr>(
(day >= 11 && day <= 13) ? "th"
: (day % 10 == 1) ? "st"
: (day % 10 == 2) ? "nd"
: (day % 10 == 3) ? "rd"
: "th"
);
return std::format("{} {}{}", month, day, suffix);
}
struct SystemData {
String date;
String host;
String kernel_version;
Result<String, String> os_version;
Result<u64, String> mem_info;
Option<String> desktop_environment;
String window_manager;
Option<Result<String, NowPlayingError>> now_playing;
Option<WeatherOutput> weather_info;
u64 disk_used;
u64 disk_total;
String shell;
static fn fetchSystemData(const Config& config) -> SystemData {
SystemData data;
// Single-threaded execution for core system info (faster on Windows)
data.date = GetDate();
data.host = os::GetHost();
data.kernel_version = os::GetKernelVersion();
data.os_version = os::GetOSVersion();
data.mem_info = os::GetMemInfo();
// Desktop environment info
data.desktop_environment = os::GetDesktopEnvironment();
data.window_manager = os::GetWindowManager();
// Parallel execution for disk/shell only
auto diskShell = std::async(std::launch::async, [] {
auto [used, total] = os::GetDiskUsage();
return std::make_tuple(used, total, os::GetShell());
});
// Conditional tasks
std::future<WeatherOutput> weather;
std::future<Result<String, NowPlayingError>> nowPlaying;
if (config.weather.enabled)
weather = std::async(std::launch::async, [&config] { return config.weather.getWeatherInfo(); });
if (config.now_playing.enabled)
nowPlaying = std::async(std::launch::async, os::GetNowPlaying);
// Get remaining results
auto [used, total, shell] = diskShell.get();
data.disk_used = used;
data.disk_total = total;
data.shell = shell;
if (weather.valid())
data.weather_info = weather.get();
if (nowPlaying.valid())
data.now_playing = nowPlaying.get();
return data;
}
};
using namespace ftxui; using namespace ftxui;
fn CreateColorCircles() -> Element { fn CreateColorCircles() -> Element {
@ -198,12 +98,12 @@ namespace {
} }
fn SystemInfoBox(const Config& config, const SystemData& data) -> Element { fn SystemInfoBox(const Config& config, const SystemData& data) -> Element {
// Fetch data
const String& name = config.general.name; const String& name = config.general.name;
const Weather weather = config.weather; const Weather weather = config.weather;
const bool nowPlayingEnabled = config.now_playing.enabled; const bool nowPlayingEnabled = config.now_playing.enabled;
const auto& [userIcon, paletteIcon, calendarIcon, hostIcon, kernelIcon, osIcon, memoryIcon, weatherIcon, musicIcon, diskIcon, shellIcon, deIcon, wmIcon] = const auto& [userIcon, paletteIcon, calendarIcon, hostIcon, kernelIcon, osIcon, memoryIcon, weatherIcon, musicIcon, diskIcon, shellIcon, deIcon, wmIcon] =
// ReSharper disable once CppDFAUnreachableCode
SHOW_ICONS ? ui::NERD_ICONS : ui::EMPTY_ICONS; SHOW_ICONS ? ui::NERD_ICONS : ui::EMPTY_ICONS;
Elements content; Elements content;
@ -282,17 +182,15 @@ namespace {
if (!data.kernel_version.empty()) if (!data.kernel_version.empty())
content.push_back(createRow(kernelIcon, "Kernel", data.kernel_version)); content.push_back(createRow(kernelIcon, "Kernel", data.kernel_version));
visit_result( if (data.os_version)
data.os_version, content.push_back(createRow(String(osIcon), "OS", *data.os_version));
[&](const String& version) { content.push_back(createRow(String(osIcon), "OS", version)); }, else
[](const String& error) { ERROR_LOG("Failed to get OS version: {}", error); } ERROR_LOG("Failed to get OS version: {}", data.os_version.error());
);
visit_result( if (data.mem_info)
data.mem_info, content.push_back(createRow(memoryIcon, "RAM", std::format("{}", BytesToGiB { *data.mem_info })));
[&](const u64& mem) { content.push_back(createRow(memoryIcon, "RAM", std::format("{}", BytesToGiB { mem }))); }, else
[](const String& error) { ERROR_LOG("Failed to get memory info: {}", error); } ERROR_LOG("Failed to get memory info: {}", data.mem_info.error());
);
// Add Disk usage row // Add Disk usage row
content.push_back( content.push_back(
@ -303,14 +201,14 @@ namespace {
content.push_back(separator() | color(ui::DEFAULT_THEME.border)); content.push_back(separator() | color(ui::DEFAULT_THEME.border));
if (data.desktop_environment.has_value() && *data.desktop_environment != data.window_manager) if (data.desktop_environment && *data.desktop_environment != data.window_manager)
content.push_back(createRow(deIcon, "DE", *data.desktop_environment)); content.push_back(createRow(deIcon, "DE", *data.desktop_environment));
if (!data.window_manager.empty()) if (!data.window_manager.empty())
content.push_back(createRow(wmIcon, "WM", data.window_manager)); content.push_back(createRow(wmIcon, "WM", data.window_manager));
// Now Playing row // Now Playing row
if (nowPlayingEnabled && data.now_playing.has_value()) { if (nowPlayingEnabled && data.now_playing) {
if (const Result<String, NowPlayingError>& nowPlayingResult = *data.now_playing; nowPlayingResult.has_value()) { if (const Result<String, NowPlayingError>& nowPlayingResult = *data.now_playing; nowPlayingResult.has_value()) {
const String& npText = *nowPlayingResult; const String& npText = *nowPlayingResult;

View file

@ -6,6 +6,7 @@
#include <X11/Xatom.h> #include <X11/Xatom.h>
#include <X11/Xlib.h> #include <X11/Xlib.h>
#include <fstream> #include <fstream>
#include <ranges>
#include <sys/socket.h> #include <sys/socket.h>
#include <sys/statvfs.h> #include <sys/statvfs.h>
#include <sys/sysinfo.h> #include <sys/sysinfo.h>
@ -18,24 +19,35 @@
#include "src/util/macros.h" #include "src/util/macros.h"
namespace fs = std::filesystem; namespace fs = std::filesystem;
using namespace std::string_view_literals;
namespace { namespace {
using os::linux::DisplayGuard; using os::linux::DisplayGuard;
using os::linux::WaylandDisplayGuard; using os::linux::WaylandDisplayGuard;
constexpr auto Trim(StringView sv) -> StringView {
using namespace std::ranges;
constexpr auto isSpace = [](const char character) { return std::isspace(static_cast<unsigned char>(character)); };
const borrowed_iterator_t<StringView&> start = find_if_not(sv, isSpace);
const borrowed_iterator_t<reverse_view<StringView>> rstart = find_if_not(sv | views::reverse, isSpace);
return sv.substr(start - sv.begin(), sv.size() - (rstart - sv.rbegin()));
}
fn GetX11WindowManager() -> String { fn GetX11WindowManager() -> String {
DisplayGuard display; const DisplayGuard display;
if (!display) if (!display)
return ""; return "";
Atom supportingWmCheck = XInternAtom(display.get(), "_NET_SUPPORTING_WM_CHECK", False); const Atom supportingWmCheck = XInternAtom(display.get(), "_NET_SUPPORTING_WM_CHECK", False);
Atom wmName = XInternAtom(display.get(), "_NET_WM_NAME", False); const Atom wmName = XInternAtom(display.get(), "_NET_WM_NAME", False);
Atom utf8String = XInternAtom(display.get(), "UTF8_STRING", False); const Atom utf8String = XInternAtom(display.get(), "UTF8_STRING", False);
Window root = display.defaultRootWindow(); const Window root = display.defaultRootWindow();
Window wmWindow = 0;
Atom actualType = 0; Atom actualType = 0;
i32 actualFormat = 0; i32 actualFormat = 0;
u64 nitems = 0, bytesAfter = 0; u64 nitems = 0, bytesAfter = 0;
@ -56,14 +68,13 @@ namespace {
&data &data
) == Success && ) == Success &&
data) { data) {
UniquePointer<u8, decltype(&XFree)> dataGuard(data, XFree); const UniquePointer<u8, decltype(&XFree)> dataGuard(data, XFree);
wmWindow = *std::bit_cast<Window*>(data);
u8* nameData = nullptr; u8* nameData = nullptr;
if (XGetWindowProperty( if (XGetWindowProperty(
display.get(), display.get(),
wmWindow, *reinterpret_cast<Window*>(data),
wmName, wmName,
0, 0,
1024, 1024,
@ -76,19 +87,18 @@ namespace {
&nameData &nameData
) == Success && ) == Success &&
nameData) { nameData) {
UniquePointer<u8, decltype(&XFree)> nameGuard(nameData, XFree); const UniquePointer<u8, decltype(&XFree)> nameGuard(nameData, XFree);
return std::bit_cast<char*>(nameData); return reinterpret_cast<char*>(nameData);
} }
} }
return "Unknown (X11)"; return "Unknown (X11)";
} }
fn ReadProcessCmdline(i32 pid) -> String { fn ReadProcessCmdline(const i32 pid) -> String {
std::ifstream cmdlineFile("/proc/" + std::to_string(pid) + "/cmdline"); std::ifstream cmdlineFile("/proc/" + std::to_string(pid) + "/cmdline");
String cmdline;
if (getline(cmdlineFile, cmdline)) { if (String cmdline; getline(cmdlineFile, cmdline)) {
std::ranges::replace(cmdline, '\0', ' '); std::ranges::replace(cmdline, '\0', ' ');
return cmdline; return cmdline;
} }
@ -96,75 +106,66 @@ namespace {
return ""; return "";
} }
fn DetectHyprlandSpecific() -> String { fn DetectHyprlandSpecific() -> Option<String> {
Result<String, EnvError> xdgCurrentDesktop = GetEnv("XDG_CURRENT_DESKTOP"); if (Result<String, EnvError> xdgCurrentDesktop = GetEnv("XDG_CURRENT_DESKTOP")) {
std::ranges::transform(*xdgCurrentDesktop, xdgCurrentDesktop->begin(), tolower);
if (xdgCurrentDesktop) { if (xdgCurrentDesktop->contains("hyprland"sv))
std::ranges::transform(*xdgCurrentDesktop, xdgCurrentDesktop->begin(), ::tolower);
if (xdgCurrentDesktop->contains("hyprland"))
return "Hyprland"; return "Hyprland";
} }
if (GetEnv("HYPRLAND_INSTANCE_SIGNATURE")) if (GetEnv("HYPRLAND_INSTANCE_SIGNATURE"))
return "Hyprland"; return "Hyprland";
if (fs::exists("/run/user/" + std::to_string(getuid()) + "/hypr")) if (fs::exists(std::format("/run/user/{}/hypr", getuid())))
return "Hyprland"; return "Hyprland";
return ""; return None;
} }
fn GetWaylandCompositor() -> String { fn GetWaylandCompositor() -> String {
String hypr = DetectHyprlandSpecific(); if (const Option<String> hypr = DetectHyprlandSpecific())
return *hypr;
if (!hypr.empty()) const WaylandDisplayGuard display;
return hypr;
WaylandDisplayGuard display;
if (!display) if (!display)
return ""; return "";
i32 fileDescriptor = display.fd(); const i32 fileDescriptor = display.fd();
struct ucred cred; ucred cred;
socklen_t len = sizeof(cred); u32 len = sizeof(cred);
if (getsockopt(fileDescriptor, SOL_SOCKET, SO_PEERCRED, &cred, &len) == -1) if (getsockopt(fileDescriptor, SOL_SOCKET, SO_PEERCRED, &cred, &len) == -1)
return ""; return "";
String compositorName; String compositorName;
String commPath = "/proc/" + std::to_string(cred.pid) + "/comm"; const String commPath = std::format("/proc/{}/comm", cred.pid);
std::ifstream commFile(commPath); if (std::ifstream commFile(commPath); commFile >> compositorName) {
if (commFile >> compositorName) { const std::ranges::subrange removedRange = std::ranges::remove(compositorName, '\n');
std::ranges::subrange removedRange = std::ranges::remove(compositorName, '\n');
compositorName.erase(removedRange.begin(), removedRange.end()); compositorName.erase(removedRange.begin(), removedRange.end());
} }
String cmdline = ReadProcessCmdline(cred.pid); if (const String cmdline = ReadProcessCmdline(cred.pid); cmdline.contains("hyprland"sv))
if (cmdline.contains("hyprland"))
return "Hyprland"; return "Hyprland";
String exePath = "/proc/" + std::to_string(cred.pid) + "/exe"; const String exePath = std::format("/proc/{}/exe", cred.pid);
Array<char, PATH_MAX> buf; Array<char, PATH_MAX> buf;
ssize_t lenBuf = readlink(exePath.c_str(), buf.data(), buf.size() - 1); if (const isize lenBuf = readlink(exePath.c_str(), buf.data(), buf.size() - 1); lenBuf != -1) {
if (lenBuf != -1) {
buf.at(static_cast<usize>(lenBuf)) = '\0'; buf.at(static_cast<usize>(lenBuf)) = '\0';
String exe(buf.data()); if (const String exe(buf.data()); exe.contains("hyprland"sv))
if (exe.contains("hyprland"))
return "Hyprland"; return "Hyprland";
} }
return compositorName.contains("hyprland") ? "Hyprland" : compositorName; return compositorName.contains("hyprland"sv) ? "Hyprland" : compositorName;
} }
fn DetectFromEnvVars() -> Option<String> { fn DetectFromEnvVars() -> Option<String> {
if (Result<String, EnvError> xdgCurrentDesktop = GetEnv("XDG_CURRENT_DESKTOP")) { if (Result<String, EnvError> xdgCurrentDesktop = GetEnv("XDG_CURRENT_DESKTOP")) {
const size_t colon = xdgCurrentDesktop->find(':'); if (const usize colon = xdgCurrentDesktop->find(':'); colon != String::npos)
if (colon != String::npos)
return xdgCurrentDesktop->substr(0, colon); return xdgCurrentDesktop->substr(0, colon);
return *xdgCurrentDesktop; return *xdgCurrentDesktop;
@ -205,11 +206,11 @@ namespace {
continue; continue;
String lowercaseStem = entry.path().stem().string(); String lowercaseStem = entry.path().stem().string();
std::ranges::transform(lowercaseStem, lowercaseStem.begin(), ::tolower); std::ranges::transform(lowercaseStem, lowercaseStem.begin(), tolower);
for (const Pair pattern : DE_PATTERNS) for (const auto [fst, snd] : DE_PATTERNS)
if (pattern.first == lowercaseStem) if (fst == lowercaseStem)
return String(pattern.second); return String(snd);
} }
} }
@ -230,7 +231,7 @@ namespace {
// clang-format on // clang-format on
std::ifstream cmdline("/proc/self/environ"); std::ifstream cmdline("/proc/self/environ");
String envVars((std::istreambuf_iterator<char>(cmdline)), std::istreambuf_iterator<char>()); const String envVars((std::istreambuf_iterator(cmdline)), std::istreambuf_iterator<char>());
for (const auto& [process, deName] : processChecks) for (const auto& [process, deName] : processChecks)
if (envVars.contains(process)) if (envVars.contains(process))
@ -241,10 +242,10 @@ namespace {
fn GetMprisPlayers(const SharedPointer<DBus::Connection>& connection) -> Result<Vec<String>, NowPlayingError> { fn GetMprisPlayers(const SharedPointer<DBus::Connection>& connection) -> Result<Vec<String>, NowPlayingError> {
try { try {
SharedPointer<DBus::CallMessage> call = const SharedPointer<DBus::CallMessage> call =
DBus::CallMessage::create("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "ListNames"); DBus::CallMessage::create("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "ListNames");
SharedPointer<DBus::Message> reply = connection->send_with_reply_blocking(call, 500); const SharedPointer<DBus::Message> reply = connection->send_with_reply_blocking(call, 500);
if (!reply) { if (!reply) {
ERROR_LOG("DBus timeout or null reply in ListNames"); ERROR_LOG("DBus timeout or null reply in ListNames");
@ -257,7 +258,7 @@ namespace {
Vec<String> mprisPlayers; Vec<String> mprisPlayers;
for (const String& name : allNamesStd) for (const String& name : allNamesStd)
if (StringView(name).contains("org.mpris.MediaPlayer2")) if (StringView(name).contains("org.mpris.MediaPlayer2"sv))
mprisPlayers.emplace_back(name); mprisPlayers.emplace_back(name);
return mprisPlayers; return mprisPlayers;
@ -269,10 +270,9 @@ namespace {
return Err(e.what()); return Err(e.what());
} }
} }
} }
fn GetOSVersion() -> Result<String, String> { fn os::GetOSVersion() -> Result<String, String> {
constexpr CStr path = "/etc/os-release"; constexpr CStr path = "/etc/os-release";
std::ifstream file(path); std::ifstream file(path);
@ -298,22 +298,22 @@ fn GetOSVersion() -> Result<String, String> {
return Err(std::format("PRETTY_NAME line not found in {}", path)); return Err(std::format("PRETTY_NAME line not found in {}", path));
} }
fn GetMemInfo() -> Result<u64, String> { fn os::GetMemInfo() -> Result<u64, String> {
struct sysinfo info; struct sysinfo info;
if (sysinfo(&info) != 0) if (sysinfo(&info) != 0)
return Err(std::format("sysinfo failed: {}", std::error_code(errno, std::generic_category()).message())); return Err(std::format("sysinfo failed: {}", std::error_code(errno, std::generic_category()).message()));
return static_cast<u64>(info.totalram * info.mem_unit); return info.totalram * info.mem_unit;
} }
fn GetNowPlaying() -> Result<String, NowPlayingError> { fn os::GetNowPlaying() -> Result<String, NowPlayingError> {
try { try {
SharedPointer<DBus::Dispatcher> dispatcher = DBus::StandaloneDispatcher::create(); const SharedPointer<DBus::Dispatcher> dispatcher = DBus::StandaloneDispatcher::create();
if (!dispatcher) if (!dispatcher)
return Err("Failed to create DBus dispatcher"); return Err("Failed to create DBus dispatcher");
SharedPointer<DBus::Connection> connection = dispatcher->create_connection(DBus::BusType::SESSION); const SharedPointer<DBus::Connection> connection = dispatcher->create_connection(DBus::BusType::SESSION);
if (!connection) if (!connection)
return Err("Failed to connect to session bus"); return Err("Failed to connect to session bus");
@ -326,14 +326,14 @@ fn GetNowPlaying() -> Result<String, NowPlayingError> {
if (mprisPlayers.empty()) if (mprisPlayers.empty())
return Err(NowPlayingCode::NoPlayers); return Err(NowPlayingCode::NoPlayers);
String activePlayer = mprisPlayers.front(); const String activePlayer = mprisPlayers.front();
SharedPointer<DBus::CallMessage> metadataCall = const SharedPointer<DBus::CallMessage> metadataCall =
DBus::CallMessage::create(activePlayer, "/org/mpris/MediaPlayer2", "org.freedesktop.DBus.Properties", "Get"); DBus::CallMessage::create(activePlayer, "/org/mpris/MediaPlayer2", "org.freedesktop.DBus.Properties", "Get");
(*metadataCall) << "org.mpris.MediaPlayer2.Player" << "Metadata"; *metadataCall << "org.mpris.MediaPlayer2.Player" << "Metadata";
SharedPointer<DBus::Message> metadataReply = connection->send_with_reply_blocking(metadataCall, 5000); const SharedPointer<DBus::Message> metadataReply = connection->send_with_reply_blocking(metadataCall, 5000);
String title; String title;
String artist; String artist;
@ -347,12 +347,11 @@ fn GetNowPlaying() -> Result<String, NowPlayingError> {
if (metadataVariant.type() == DBus::DataType::ARRAY) { if (metadataVariant.type() == DBus::DataType::ARRAY) {
Map<String, DBus::Variant> metadata = metadataVariant.to_map<String, DBus::Variant>(); Map<String, DBus::Variant> metadata = metadataVariant.to_map<String, DBus::Variant>();
auto titleIter = metadata.find("xesam:title"); if (auto titleIter = metadata.find("xesam:title");
if (titleIter != metadata.end() && titleIter->second.type() == DBus::DataType::STRING) titleIter != metadata.end() && titleIter->second.type() == DBus::DataType::STRING)
title = titleIter->second.to_string(); title = titleIter->second.to_string();
auto artistIter = metadata.find("xesam:artist"); if (auto artistIter = metadata.find("xesam:artist"); artistIter != metadata.end()) {
if (artistIter != metadata.end()) {
if (artistIter->second.type() == DBus::DataType::ARRAY) { if (artistIter->second.type() == DBus::DataType::ARRAY) {
if (Vec<String> artists = artistIter->second.to_vector<String>(); !artists.empty()) if (Vec<String> artists = artistIter->second.to_vector<String>(); !artists.empty())
artist = artists[0]; artist = artists[0];
@ -369,24 +368,24 @@ fn GetNowPlaying() -> Result<String, NowPlayingError> {
} catch (const Exception& e) { ERROR_LOG("Error processing metadata reply: {}", e.what()); } } catch (const Exception& e) { ERROR_LOG("Error processing metadata reply: {}", e.what()); }
} }
return std::format("{}{}{}", artist, (!artist.empty() && !title.empty()) ? " - " : "", title); return std::format("{}{}{}", artist, !artist.empty() && !title.empty() ? " - " : "", title);
} catch (const DBus::Error& e) { return Err(std::format("DBus error: {}", e.what())); } catch (const Exception& e) { } catch (const DBus::Error& e) { return Err(std::format("DBus error: {}", e.what())); } catch (const Exception& e) {
return Err(std::format("General error: {}", e.what())); return Err(std::format("General error: {}", e.what()));
} }
} }
fn GetWindowManager() -> String { fn os::GetWindowManager() -> String {
const Result<String, EnvError> waylandDisplay = GetEnv("WAYLAND_DISPLAY"); const Result<String, EnvError> waylandDisplay = GetEnv("WAYLAND_DISPLAY");
const Result<String, EnvError> xdgSessionType = GetEnv("XDG_SESSION_TYPE");
if (waylandDisplay || (xdgSessionType && xdgSessionType->contains("wayland"))) { if (const Result<String, EnvError> xdgSessionType = GetEnv("XDG_SESSION_TYPE");
waylandDisplay || (xdgSessionType && xdgSessionType->contains("wayland"sv))) {
String compositor = GetWaylandCompositor(); String compositor = GetWaylandCompositor();
if (!compositor.empty()) if (!compositor.empty())
return compositor; return compositor;
if (const Result<String, EnvError> xdgCurrentDesktop = GetEnv("XDG_CURRENT_DESKTOP")) { if (const Result<String, EnvError> xdgCurrentDesktop = GetEnv("XDG_CURRENT_DESKTOP")) {
std::ranges::transform(compositor, compositor.begin(), ::tolower); std::ranges::transform(compositor, compositor.begin(), tolower);
if (xdgCurrentDesktop->contains("hyprland")) if (xdgCurrentDesktop->contains("hyprland"sv))
return "Hyprland"; return "Hyprland";
} }
} }
@ -397,7 +396,7 @@ fn GetWindowManager() -> String {
return "Unknown"; return "Unknown";
} }
fn GetDesktopEnvironment() -> Option<String> { fn os::GetDesktopEnvironment() -> Option<String> {
if (Option<String> desktopEnvironment = DetectFromEnvVars()) if (Option<String> desktopEnvironment = DetectFromEnvVars())
return desktopEnvironment; return desktopEnvironment;
@ -407,19 +406,21 @@ fn GetDesktopEnvironment() -> Option<String> {
return DetectFromProcesses(); return DetectFromProcesses();
} }
fn GetShell() -> String { fn os::GetShell() -> String {
const Vec<Pair<String, String>> shellMap { if (const Result<String, EnvError> shellPath = GetEnv("SHELL")) {
// clang-format off
constexpr Array<Pair<StringView, StringView>, 5> shellMap {{
{ "bash", "Bash" }, { "bash", "Bash" },
{ "zsh", "Zsh" }, { "zsh", "Zsh" },
{ "fish", "Fish" }, { "fish", "Fish" },
{ "nu", "Nushell" }, { "nu", "Nushell" },
{ "sh", "SH" }, // sh last because other shells contain "sh" { "sh", "SH" }, // sh last because other shells contain "sh"
}; }};
// clang-format on
if (const Result<String, EnvError> shellPath = GetEnv("SHELL")) { for (const auto& [exe, name] : shellMap)
for (const auto& shellPair : shellMap) if (shellPath->contains(exe))
if (shellPath->contains(shellPair.first)) return String(name);
return shellPair.second;
return *shellPath; // fallback to the raw shell path return *shellPath; // fallback to the raw shell path
} }
@ -427,7 +428,7 @@ fn GetShell() -> String {
return ""; return "";
} }
fn GetHost() -> String { fn os::GetHost() -> String {
constexpr CStr path = "/sys/class/dmi/id/product_family"; constexpr CStr path = "/sys/class/dmi/id/product_family";
std::ifstream file(path); std::ifstream file(path);
@ -444,21 +445,21 @@ fn GetHost() -> String {
return ""; return "";
} }
return productFamily.erase(productFamily.find_last_not_of(" \t\n\r") + 1); return String(Trim(productFamily));
} }
fn GetKernelVersion() -> String { fn os::GetKernelVersion() -> String {
struct utsname uts; utsname uts;
if (uname(&uts) == -1) { if (uname(&uts) == -1) {
ERROR_LOG("uname() failed: {}", std::error_code(errno, std::generic_category()).message()); ERROR_LOG("uname() failed: {}", std::error_code(errno, std::generic_category()).message());
return ""; return "";
} }
return static_cast<CStr>(uts.release); return uts.release;
} }
fn GetDiskUsage() -> Pair<u64, u64> { fn os::GetDiskUsage() -> Pair<u64, u64> {
struct statvfs stat; struct statvfs stat;
if (statvfs("/", &stat) == -1) { if (statvfs("/", &stat) == -1) {
@ -466,7 +467,9 @@ fn GetDiskUsage() -> Pair<u64, u64> {
return { 0, 0 }; return { 0, 0 };
} }
// ReSharper disable CppRedundantParentheses
return { (stat.f_blocks * stat.f_frsize) - (stat.f_bfree * stat.f_frsize), stat.f_blocks * stat.f_frsize }; return { (stat.f_blocks * stat.f_frsize) - (stat.f_bfree * stat.f_frsize), stat.f_blocks * stat.f_frsize };
// ReSharper restore CppRedundantParentheses
} }
#endif #endif

View file

@ -1,13 +1,13 @@
#ifdef __linux__ #ifdef __linux__
#include <utility> // for std::exchange #include <utility>
#include "display_guards.h" #include "display_guards.h"
#include "src/util/macros.h" #include "src/util/macros.h"
namespace os::linux { namespace os::linux {
DisplayGuard::DisplayGuard(CStr name) : m_Display(XOpenDisplay(name)) {} DisplayGuard::DisplayGuard(const CStr name) : m_Display(XOpenDisplay(name)) {}
DisplayGuard::~DisplayGuard() { DisplayGuard::~DisplayGuard() {
if (m_Display) if (m_Display)

View file

@ -13,7 +13,6 @@ namespace os::linux {
* Automatically handles resource acquisition and cleanup * Automatically handles resource acquisition and cleanup
*/ */
class DisplayGuard { class DisplayGuard {
private:
Display* m_Display; Display* m_Display;
public: public:
@ -32,7 +31,7 @@ namespace os::linux {
DisplayGuard(DisplayGuard&& other) noexcept; DisplayGuard(DisplayGuard&& other) noexcept;
fn operator=(DisplayGuard&& other) noexcept -> DisplayGuard&; fn operator=(DisplayGuard&& other) noexcept -> DisplayGuard&;
[[nodiscard]] operator bool() const; [[nodiscard]] explicit operator bool() const;
[[nodiscard]] fn get() const -> Display*; [[nodiscard]] fn get() const -> Display*;
[[nodiscard]] fn defaultRootWindow() const -> Window; [[nodiscard]] fn defaultRootWindow() const -> Window;
}; };
@ -42,7 +41,6 @@ namespace os::linux {
* Automatically handles resource acquisition and cleanup * Automatically handles resource acquisition and cleanup
*/ */
class WaylandDisplayGuard { class WaylandDisplayGuard {
private:
wl_display* m_Display; wl_display* m_Display;
public: public:
@ -60,7 +58,7 @@ namespace os::linux {
WaylandDisplayGuard(WaylandDisplayGuard&& other) noexcept; WaylandDisplayGuard(WaylandDisplayGuard&& other) noexcept;
fn operator=(WaylandDisplayGuard&& other) noexcept -> WaylandDisplayGuard&; fn operator=(WaylandDisplayGuard&& other) noexcept -> WaylandDisplayGuard&;
[[nodiscard]] operator bool() const; [[nodiscard]] explicit operator bool() const;
[[nodiscard]] fn get() const -> wl_display*; [[nodiscard]] fn get() const -> wl_display*;
[[nodiscard]] fn fd() const -> i32; [[nodiscard]] fn fd() const -> i32;
}; };

View file

@ -121,8 +121,7 @@ fn LogImpl(const LogLevel level, const std::source_location& loc, std::format_st
-> void { -> void {
using namespace std::chrono; using namespace std::chrono;
const time_point<system_clock, duration<long long, std::ratio<1, 1>>> now = const time_point<system_clock, duration<i64>> now = std::chrono::floor<seconds>(system_clock::now());
std::chrono::floor<std::chrono::seconds>(std::chrono::system_clock::now());
const auto [color, levelStr] = [&] { const auto [color, levelStr] = [&] {
switch (level) { switch (level) {

View file

@ -259,12 +259,12 @@ using NowPlayingError = std::variant<
enum class EnvError : u8 { NotFound, AccessError }; enum class EnvError : u8 { NotFound, AccessError };
inline auto GetEnv(const String& name) -> Result<String, EnvError> { inline auto GetEnv(CStr name) -> Result<String, EnvError> {
#ifdef _WIN32 #ifdef _WIN32
char* rawPtr = nullptr; char* rawPtr = nullptr;
usize bufferSize = 0; usize bufferSize = 0;
const i32 err = _dupenv_s(&rawPtr, &bufferSize, name.c_str()); const i32 err = _dupenv_s(&rawPtr, &bufferSize, name);
const UniquePointer<char, decltype(&free)> ptrManager(rawPtr, free); const UniquePointer<char, decltype(&free)> ptrManager(rawPtr, free);
@ -276,7 +276,7 @@ inline auto GetEnv(const String& name) -> Result<String, EnvError> {
return ptrManager.get(); return ptrManager.get();
#else #else
CStr value = std::getenv(name.c_str()); const CStr value = std::getenv(name);
if (!value) if (!value)
return Err(EnvError::NotFound); return Err(EnvError::NotFound);