myhead. hort
This commit is contained in:
parent
88c242b34b
commit
39e5a6a509
7 changed files with 120 additions and 166 deletions
|
@ -4,6 +4,7 @@ AlignConsecutiveAssignments: true
|
|||
AlignConsecutiveShortCaseStatements:
|
||||
Enabled: true
|
||||
AlignConsecutiveDeclarations: true
|
||||
AlignOperands: DontAlign
|
||||
AllowShortBlocksOnASingleLine: Always
|
||||
AllowShortCaseLabelsOnASingleLine: true
|
||||
AllowShortEnumsOnASingleLine: true
|
||||
|
|
|
@ -31,6 +31,7 @@ Checks: >
|
|||
-readability-isolate-declaration,
|
||||
-readability-magic-numbers
|
||||
CheckOptions:
|
||||
cppcoreguidelines-avoid-do-while.IgnoreMacros: "true"
|
||||
readability-identifier-naming.ClassCase: CamelCase
|
||||
readability-identifier-naming.EnumCase: CamelCase
|
||||
readability-identifier-naming.LocalConstantCase: camelBack
|
||||
|
|
|
@ -65,7 +65,7 @@ struct SystemData {
|
|||
Result<String, String> os_version; ///< OS pretty name (e.g., "Ubuntu 22.04 LTS") or an error message.
|
||||
Result<u64, String> mem_info; ///< Total physical RAM in bytes or an error message.
|
||||
Option<String> desktop_environment; ///< Detected desktop environment (e.g., "GNOME", "KDE", "Fluent (Windows 11)"). Might be None.
|
||||
String window_manager; ///< Detected window manager (e.g., "Mutter", "KWin", "DWM").
|
||||
Option<String> window_manager; ///< Detected window manager (e.g., "Mutter", "KWin", "DWM").
|
||||
NowPlayingResult now_playing; ///< Currently playing media ("Artist - Title") or an error/None if disabled/unavailable.
|
||||
Option<WeatherOutput> weather_info; ///< Weather information or None if disabled/unavailable.
|
||||
u64 disk_used; ///< Used disk space in bytes for the root filesystem.
|
||||
|
|
|
@ -199,8 +199,8 @@ namespace {
|
|||
if (data.desktop_environment && *data.desktop_environment != data.window_manager)
|
||||
content.push_back(createRow(deIcon, "DE", *data.desktop_environment));
|
||||
|
||||
if (!data.window_manager.empty())
|
||||
content.push_back(createRow(wmIcon, "WM", data.window_manager));
|
||||
if (data.window_manager)
|
||||
content.push_back(createRow(wmIcon, "WM", *data.window_manager));
|
||||
|
||||
// Now Playing row
|
||||
if (nowPlayingEnabled && data.now_playing) {
|
||||
|
|
258
src/os/linux.cpp
258
src/os/linux.cpp
|
@ -19,28 +19,17 @@
|
|||
#include "src/os/linux/display_guards.h"
|
||||
#include "src/util/macros.h"
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
using namespace std::string_view_literals;
|
||||
|
||||
namespace {
|
||||
constexpr auto Trim(StringView sview) -> 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(sview, isSpace);
|
||||
const borrowed_iterator_t<reverse_view<StringView>> rstart = find_if_not(sview | views::reverse, isSpace);
|
||||
|
||||
return sview.substr(start - sview.begin(), sview.size() - (rstart - sview.rbegin()));
|
||||
}
|
||||
|
||||
fn GetX11WindowManager() -> String {
|
||||
fn GetX11WindowManager() -> Option<String> {
|
||||
using os::linux::XcbReplyGuard;
|
||||
using os::linux::XorgDisplayGuard;
|
||||
|
||||
const XorgDisplayGuard conn;
|
||||
|
||||
if (!conn)
|
||||
return "";
|
||||
RETURN_ERR("Failed to open X11 display");
|
||||
|
||||
fn internAtom = [&conn](const StringView name) -> XcbReplyGuard<xcb_intern_atom_reply_t> {
|
||||
const auto cookie = xcb_intern_atom(conn.get(), 0, static_cast<uint16_t>(name.size()), name.data());
|
||||
|
@ -52,153 +41,108 @@ namespace {
|
|||
const XcbReplyGuard<xcb_intern_atom_reply_t> utf8String = internAtom("UTF8_STRING");
|
||||
|
||||
if (!supportingWmCheck || !wmName || !utf8String)
|
||||
return "Unknown (X11)";
|
||||
RETURN_ERR("Failed to get X11 atoms");
|
||||
|
||||
const xcb_window_t root = conn.rootScreen()->root;
|
||||
|
||||
fn getProperty = [&conn](
|
||||
const xcb_window_t window,
|
||||
const xcb_atom_t property,
|
||||
const xcb_atom_t type,
|
||||
const uint32_t offset,
|
||||
const uint32_t length
|
||||
) -> XcbReplyGuard<xcb_get_property_reply_t> {
|
||||
const xcb_get_property_cookie_t cookie = xcb_get_property(conn.get(), 0, window, property, type, offset, length);
|
||||
return XcbReplyGuard(xcb_get_property_reply(conn.get(), cookie, nullptr));
|
||||
};
|
||||
|
||||
const XcbReplyGuard<xcb_get_property_reply_t> wmWindowReply =
|
||||
getProperty(root, supportingWmCheck->atom, XCB_ATOM_WINDOW, 0, 1);
|
||||
const XcbReplyGuard<xcb_get_property_reply_t> wmWindowReply(xcb_get_property_reply(
|
||||
conn.get(),
|
||||
xcb_get_property(conn.get(), 0, conn.rootScreen()->root, supportingWmCheck->atom, XCB_ATOM_WINDOW, 0, 1),
|
||||
nullptr
|
||||
));
|
||||
|
||||
if (!wmWindowReply || wmWindowReply->type != XCB_ATOM_WINDOW || wmWindowReply->format != 32 ||
|
||||
xcb_get_property_value_length(wmWindowReply.get()) == 0)
|
||||
return "Unknown (X11)";
|
||||
RETURN_ERR("Failed to get _NET_SUPPORTING_WM_CHECK property");
|
||||
|
||||
const xcb_window_t wmWindow = *static_cast<xcb_window_t*>(xcb_get_property_value(wmWindowReply.get()));
|
||||
|
||||
const XcbReplyGuard<xcb_get_property_reply_t> wmNameReply =
|
||||
getProperty(wmWindow, wmName->atom, utf8String->atom, 0, 1024);
|
||||
const XcbReplyGuard<xcb_get_property_reply_t> wmNameReply(xcb_get_property_reply(
|
||||
conn.get(),
|
||||
xcb_get_property(
|
||||
conn.get(),
|
||||
0,
|
||||
*static_cast<xcb_window_t*>(xcb_get_property_value(wmWindowReply.get())),
|
||||
wmName->atom,
|
||||
utf8String->atom,
|
||||
0,
|
||||
1024
|
||||
),
|
||||
nullptr
|
||||
));
|
||||
|
||||
if (!wmNameReply || wmNameReply->type != utf8String->atom || xcb_get_property_value_length(wmNameReply.get()) == 0)
|
||||
return "Unknown (X11)";
|
||||
RETURN_ERR("Failed to get _NET_WM_NAME property");
|
||||
|
||||
const char* nameData = static_cast<const char*>(xcb_get_property_value(wmNameReply.get()));
|
||||
const usize length = xcb_get_property_value_length(wmNameReply.get());
|
||||
|
||||
return { nameData, length };
|
||||
return String(nameData, length);
|
||||
}
|
||||
|
||||
fn ReadProcessCmdline(const i32 pid) -> String {
|
||||
std::ifstream cmdlineFile("/proc/" + std::to_string(pid) + "/cmdline");
|
||||
|
||||
if (String cmdline; getline(cmdlineFile, cmdline)) {
|
||||
std::ranges::replace(cmdline, '\0', ' ');
|
||||
return cmdline;
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
fn DetectHyprlandSpecific() -> Option<String> {
|
||||
if (Result<String, EnvError> xdgCurrentDesktop = GetEnv("XDG_CURRENT_DESKTOP")) {
|
||||
std::ranges::transform(*xdgCurrentDesktop, xdgCurrentDesktop->begin(), tolower);
|
||||
|
||||
if (xdgCurrentDesktop->contains("hyprland"sv))
|
||||
return "Hyprland";
|
||||
}
|
||||
|
||||
if (GetEnv("HYPRLAND_INSTANCE_SIGNATURE"))
|
||||
return "Hyprland";
|
||||
|
||||
if (fs::exists(std::format("/run/user/{}/hypr", getuid())))
|
||||
return "Hyprland";
|
||||
|
||||
return None;
|
||||
}
|
||||
|
||||
fn GetWaylandCompositor() -> String {
|
||||
fn GetWaylandCompositor() -> Option<String> {
|
||||
using os::linux::WaylandDisplayGuard;
|
||||
|
||||
if (const Option<String> hypr = DetectHyprlandSpecific())
|
||||
return *hypr;
|
||||
|
||||
const WaylandDisplayGuard display;
|
||||
|
||||
if (!display)
|
||||
return "";
|
||||
RETURN_ERR("Failed to open Wayland display");
|
||||
|
||||
const i32 fileDescriptor = display.fd();
|
||||
if (fileDescriptor < 0)
|
||||
RETURN_ERR("Failed to get Wayland file descriptor");
|
||||
|
||||
ucred cred;
|
||||
u32 len = sizeof(cred);
|
||||
socklen_t len = sizeof(cred);
|
||||
|
||||
if (getsockopt(fileDescriptor, SOL_SOCKET, SO_PEERCRED, &cred, &len) == -1)
|
||||
return "";
|
||||
RETURN_ERR("Failed to get socket credentials: {}", std::error_code(errno, std::generic_category()).message());
|
||||
|
||||
Array<char, 128> exeLinkPathBuf;
|
||||
|
||||
auto [out, size] = std::format_to_n(exeLinkPathBuf.data(), exeLinkPathBuf.size() - 1, "/proc/{}/exe", cred.pid);
|
||||
|
||||
if (out >= exeLinkPathBuf.data() + exeLinkPathBuf.size() - 1)
|
||||
RETURN_ERR("Failed to format /proc path (PID too large?)");
|
||||
|
||||
*out = '\0';
|
||||
|
||||
const char* exeLinkPath = exeLinkPathBuf.data();
|
||||
|
||||
Array<char, PATH_MAX> exeRealPathBuf;
|
||||
|
||||
const isize bytesRead = readlink(exeLinkPath, exeRealPathBuf.data(), exeRealPathBuf.size() - 1);
|
||||
|
||||
if (bytesRead == -1)
|
||||
RETURN_ERR("Failed to read link {}: {}", exeLinkPath, std::error_code(errno, std::generic_category()).message());
|
||||
|
||||
exeRealPathBuf.at(bytesRead) = '\0';
|
||||
|
||||
String compositorName;
|
||||
|
||||
const String commPath = std::format("/proc/{}/comm", cred.pid);
|
||||
if (std::ifstream commFile(commPath); commFile >> compositorName) {
|
||||
const std::ranges::subrange removedRange = std::ranges::remove(compositorName, '\n');
|
||||
compositorName.erase(removedRange.begin(), removedRange.end());
|
||||
try {
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
const fs::path exePath(exeRealPathBuf.data());
|
||||
|
||||
compositorName = exePath.filename().string();
|
||||
} catch (const std::filesystem::filesystem_error& e) {
|
||||
RETURN_ERR("Error getting compositor name from path '{}': {}", exeRealPathBuf.data(), e.what());
|
||||
} catch (...) { RETURN_ERR("Unknown error getting compositor name"); }
|
||||
|
||||
if (compositorName.empty() || compositorName == "." || compositorName == "/")
|
||||
RETURN_ERR("Empty or invalid compositor name {}", compositorName);
|
||||
|
||||
const StringView compositorNameView = compositorName;
|
||||
|
||||
if (constexpr StringView wrappedSuffix = "-wrapped"; compositorNameView.length() > 1 + wrappedSuffix.length() &&
|
||||
compositorNameView[0] == '.' && compositorNameView.ends_with(wrappedSuffix)) {
|
||||
const StringView cleanedView =
|
||||
compositorNameView.substr(1, compositorNameView.length() - 1 - wrappedSuffix.length());
|
||||
|
||||
if (cleanedView.empty())
|
||||
RETURN_ERR("Compositor name invalid after heuristic: original='%s'\n", compositorName.c_str());
|
||||
|
||||
return String(cleanedView);
|
||||
}
|
||||
|
||||
if (const String cmdline = ReadProcessCmdline(cred.pid); cmdline.contains("hyprland"sv))
|
||||
return "Hyprland";
|
||||
|
||||
const String exePath = std::format("/proc/{}/exe", cred.pid);
|
||||
|
||||
Array<char, PATH_MAX> buf;
|
||||
if (const isize lenBuf = readlink(exePath.c_str(), buf.data(), buf.size() - 1); lenBuf != -1) {
|
||||
buf.at(static_cast<usize>(lenBuf)) = '\0';
|
||||
if (const String exe(buf.data()); exe.contains("hyprland"sv))
|
||||
return "Hyprland";
|
||||
}
|
||||
|
||||
return compositorName.contains("hyprland"sv) ? "Hyprland" : compositorName;
|
||||
}
|
||||
|
||||
fn DetectFromEnvVars() -> Option<String> {
|
||||
if (Result<String, EnvError> xdgCurrentDesktop = GetEnv("XDG_CURRENT_DESKTOP")) {
|
||||
if (const usize colon = xdgCurrentDesktop->find(':'); colon != String::npos)
|
||||
return xdgCurrentDesktop->substr(0, colon);
|
||||
|
||||
DEBUG_LOG("Found XDG_CURRENT_DESKTOP: {}", *xdgCurrentDesktop);
|
||||
|
||||
return *xdgCurrentDesktop;
|
||||
}
|
||||
|
||||
if (Result<String, EnvError> desktopSession = GetEnv("DESKTOP_SESSION")) {
|
||||
DEBUG_LOG("Found DESKTOP_SESSION: {}", *desktopSession);
|
||||
return *desktopSession;
|
||||
}
|
||||
|
||||
return None;
|
||||
}
|
||||
|
||||
fn DetectFromProcesses() -> Option<String> {
|
||||
// clang-format off
|
||||
const Array<Pair<StringView, StringView>, 7> processChecks = {{
|
||||
{ "plasmashell", "KDE" },
|
||||
{ "gnome-shell", "GNOME" },
|
||||
{ "xfce4-session", "XFCE" },
|
||||
{ "mate-session", "MATE" },
|
||||
{ "cinnamon-session", "Cinnamon" },
|
||||
{ "budgie-wm", "Budgie" },
|
||||
{ "lxqt-session", "LXQt" },
|
||||
}};
|
||||
// clang-format on
|
||||
|
||||
std::ifstream cmdline("/proc/self/environ");
|
||||
const String envVars((std::istreambuf_iterator(cmdline)), std::istreambuf_iterator<char>());
|
||||
|
||||
for (const auto& [process, deName] : processChecks)
|
||||
if (envVars.contains(process)) {
|
||||
DEBUG_LOG("Found from process check: {}", deName);
|
||||
return String(deName);
|
||||
}
|
||||
|
||||
return None;
|
||||
return compositorName;
|
||||
}
|
||||
|
||||
fn GetMprisPlayers(const SharedPointer<DBus::Connection>& connection) -> Result<Vec<String>, NowPlayingError> {
|
||||
|
@ -275,6 +219,7 @@ fn os::GetNowPlaying() -> Result<String, NowPlayingError> {
|
|||
return Err("Failed to create DBus dispatcher");
|
||||
|
||||
const SharedPointer<DBus::Connection> connection = dispatcher->create_connection(DBus::BusType::SESSION);
|
||||
|
||||
if (!connection)
|
||||
return Err("Failed to connect to session bus");
|
||||
|
||||
|
@ -335,36 +280,31 @@ fn os::GetNowPlaying() -> Result<String, NowPlayingError> {
|
|||
}
|
||||
}
|
||||
|
||||
fn os::GetWindowManager() -> String {
|
||||
const Result<String, EnvError> waylandDisplay = GetEnv("WAYLAND_DISPLAY");
|
||||
|
||||
if (const Result<String, EnvError> xdgSessionType = GetEnv("XDG_SESSION_TYPE");
|
||||
waylandDisplay || (xdgSessionType && xdgSessionType->contains("wayland"sv))) {
|
||||
String compositor = GetWaylandCompositor();
|
||||
|
||||
if (!compositor.empty()) {
|
||||
DEBUG_LOG("Found compositor: {}", compositor);
|
||||
return compositor;
|
||||
}
|
||||
|
||||
if (const Result<String, EnvError> xdgCurrentDesktop = GetEnv("XDG_CURRENT_DESKTOP")) {
|
||||
std::ranges::transform(compositor, compositor.begin(), tolower);
|
||||
if (xdgCurrentDesktop->contains("hyprland"sv))
|
||||
return "Hyprland";
|
||||
}
|
||||
}
|
||||
|
||||
if (String x11wm = GetX11WindowManager(); !x11wm.empty())
|
||||
return x11wm;
|
||||
|
||||
return "Unknown";
|
||||
fn os::GetWindowManager() -> Option<String> {
|
||||
// clang-format off
|
||||
return GetWaylandCompositor()
|
||||
.or_else([] { return GetX11WindowManager(); })
|
||||
.and_then([](const String& windowManager) -> Option<String> {
|
||||
DEBUG_LOG("Found window manager: {}", windowManager);
|
||||
return windowManager;
|
||||
});
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
fn os::GetDesktopEnvironment() -> Option<String> {
|
||||
if (Option<String> desktopEnvironment = DetectFromEnvVars())
|
||||
return desktopEnvironment;
|
||||
return GetEnv("XDG_CURRENT_DESKTOP")
|
||||
.transform([](const String& xdgDesktop) -> String {
|
||||
if (const usize colon = xdgDesktop.find(':'); colon != String::npos)
|
||||
return xdgDesktop.substr(0, colon);
|
||||
|
||||
return DetectFromProcesses();
|
||||
return xdgDesktop;
|
||||
})
|
||||
.or_else([](const EnvError&) -> Result<String, EnvError> { return GetEnv("DESKTOP_SESSION"); })
|
||||
.transform([](const String& finalValue) -> Option<String> {
|
||||
DEBUG_LOG("Found desktop environment: {}", finalValue);
|
||||
return finalValue;
|
||||
})
|
||||
.value_or(None);
|
||||
}
|
||||
|
||||
fn os::GetShell() -> String {
|
||||
|
@ -406,7 +346,7 @@ fn os::GetHost() -> String {
|
|||
return "";
|
||||
}
|
||||
|
||||
return String(Trim(productFamily));
|
||||
return productFamily;
|
||||
}
|
||||
|
||||
fn os::GetKernelVersion() -> String {
|
||||
|
|
|
@ -48,9 +48,9 @@ namespace os {
|
|||
* @brief Attempts to retrieve the window manager.
|
||||
* @details On Linux, checks Wayland compositor or X11 WM properties. On Windows, returns "DWM" or similar.
|
||||
* On macOS, might return "Quartz Compositor" or a specific tiling WM name if active.
|
||||
* @return A String containing the detected WM name. Might return "Unknown" or a default value if detection fails.
|
||||
* @return A String containing the detected WM name, or None if detection fails or is not applicable.
|
||||
*/
|
||||
fn GetWindowManager() -> String;
|
||||
fn GetWindowManager() -> Option<String>;
|
||||
|
||||
/**
|
||||
* @brief Attempts to detect the current user shell.
|
||||
|
|
|
@ -217,7 +217,7 @@ fn LogImpl(const LogLevel level, const std::source_location& loc, std::format_st
|
|||
}
|
||||
}();
|
||||
|
||||
Print(BrightWhite, "[{:%X}] ", zoned_time { current_zone(), std::chrono::floor<seconds>(system_clock::now()) });
|
||||
Print(BrightWhite, "[{:%X}] ", std::chrono::floor<seconds>(system_clock::now()));
|
||||
Print(Emphasis::Bold | color, "{} ", levelStr);
|
||||
Print(fmt, std::forward<Args>(args)...);
|
||||
|
||||
|
@ -277,6 +277,18 @@ fn LogImpl(const LogLevel level, const std::source_location& loc, std::format_st
|
|||
*/
|
||||
#define ERROR_LOG(...) LogImpl(LogLevel::ERROR, std::source_location::current(), __VA_ARGS__)
|
||||
|
||||
/**
|
||||
* @def RETURN_ERR(...)
|
||||
* @brief Logs an error message and returns a value.
|
||||
* @details Logs the error message with the ERROR log level and returns the specified value.
|
||||
* @param ... Format string and arguments for the error message.
|
||||
*/
|
||||
#define RETURN_ERR(...) \
|
||||
do { \
|
||||
ERROR_LOG(__VA_ARGS__); \
|
||||
return None; \
|
||||
} while (0)
|
||||
|
||||
#ifdef __clang__
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue