diff --git a/meson.build b/meson.build index 142bf4b..9baa0f3 100644 --- a/meson.build +++ b/meson.build @@ -109,12 +109,12 @@ base_sources = files( platform_sources = { 'darwin': ['src/OS/macOS.cpp', 'src/OS/macOS/bridge.mm'], - 'dragonfly': ['src/OS/bsd.cpp'], - 'freebsd': ['src/OS/bsd.cpp'], - 'haiku': ['src/OS/haiku.cpp'], - 'linux': ['src/OS/linux.cpp'], - 'netbsd': ['src/OS/bsd.cpp'], - 'serenity': ['src/OS/serenity.cpp'], + 'dragonfly': ['src/OS/BSD.cpp'], + 'freebsd': ['src/OS/BSD.cpp'], + 'haiku': ['src/OS/Haiku.cpp'], + 'linux': ['src/OS/Linux.cpp'], + 'netbsd': ['src/OS/BSD.cpp'], + 'serenity': ['src/OS/Serenity.cpp'], 'windows': ['src/OS/Windows.cpp'], } @@ -230,4 +230,4 @@ executable( link_args: link_args, dependencies: deps, install: true, -) \ No newline at end of file +) diff --git a/src/OS/linux.cpp b/src/OS/linux.cpp deleted file mode 100644 index aa11e98..0000000 --- a/src/OS/linux.cpp +++ /dev/null @@ -1,657 +0,0 @@ -#ifdef __linux__ - -// clang-format off -#include // SQLite::{Database, OPEN_READONLY} -#include // SQLite::Exception -#include // SQLite::Statement -#include // PATH_MAX -#include // std::strlen -#include // std::{unexpected, expected} -#include // std::filesystem::{current_path, directory_entry, directory_iterator, etc.} -#include // std::{format, format_to_n} -#include // std::ifstream -#include // glz::read_beve -#include // glz::write_beve -#include // std::numeric_limits -#include // pugi::xml_document -#include // std::{getline, string (String)} -#include // std::string_view (StringView) -#include // ucred, getsockopt, SOL_SOCKET, SO_PEERCRED -#include // statvfs -#include // sysinfo -#include // utsname, uname -#include // readlink -#include // std::move - -#include "src/core/package.hpp" -#include "src/util/cache.hpp" -#include "src/util/defs.hpp" -#include "src/util/error.hpp" -#include "src/util/helpers.hpp" -#include "src/util/logging.hpp" -#include "src/util/types.hpp" -#include "src/wrappers/dbus.hpp" -#include "src/wrappers/wayland.hpp" -#include "src/wrappers/xcb.hpp" - -#include "include/matchit.hpp" - -#include "os.hpp" -// clang-format on - -using util::error::DracError, util::error::DracErrorCode; -using util::types::String, util::types::Result, util::types::Err, util::types::usize; - -namespace { - #ifdef HAVE_XCB - fn GetX11WindowManager() -> Result { - using namespace xcb; - using namespace matchit; - using enum ConnError; - using util::types::StringView; - - const DisplayGuard conn; - - if (!conn) - if (const i32 err = ConnectionHasError(conn.get())) - return Err( - DracError( - DracErrorCode::ApiUnavailable, - match(err)( - is | Generic = "Stream/Socket/Pipe Error", - is | ExtNotSupported = "Extension Not Supported", - is | MemInsufficient = "Insufficient Memory", - is | ReqLenExceed = "Request Length Exceeded", - is | ParseErr = "Display String Parse Error", - is | InvalidScreen = "Invalid Screen", - is | FdPassingFailed = "FD Passing Failed", - is | _ = std::format("Unknown Error Code ({})", err) - ) - ) - ); - - fn internAtom = [&conn](const StringView name) -> Result { - using util::types::u16; - - const ReplyGuard reply(InternAtomReply(conn.get(), InternAtom(conn.get(), 0, static_cast(name.size()), name.data()), nullptr)); - - if (!reply) - return Err(DracError(DracErrorCode::PlatformSpecific, std::format("Failed to get X11 atom reply for '{}'", name))); - - return reply->atom; - }; - - const Result supportingWmCheckAtom = internAtom("_NET_SUPPORTING_WM_CHECK"); - const Result wmNameAtom = internAtom("_NET_WM_NAME"); - const Result utf8StringAtom = internAtom("UTF8_STRING"); - - if (!supportingWmCheckAtom || !wmNameAtom || !utf8StringAtom) { - if (!supportingWmCheckAtom) - error_log("Failed to get _NET_SUPPORTING_WM_CHECK atom"); - - if (!wmNameAtom) - error_log("Failed to get _NET_WM_NAME atom"); - - if (!utf8StringAtom) - error_log("Failed to get UTF8_STRING atom"); - - return Err(DracError(DracErrorCode::PlatformSpecific, "Failed to get X11 atoms")); - } - - const ReplyGuard wmWindowReply(GetPropertyReply( - conn.get(), - GetProperty(conn.get(), 0, conn.rootScreen()->root, *supportingWmCheckAtom, ATOM_WINDOW, 0, 1), - nullptr - )); - - if (!wmWindowReply || wmWindowReply->type != ATOM_WINDOW || wmWindowReply->format != 32 || - GetPropertyValueLength(wmWindowReply.get()) == 0) - return Err(DracError(DracErrorCode::NotFound, "Failed to get _NET_SUPPORTING_WM_CHECK property")); - - const window_t wmRootWindow = *static_cast(GetPropertyValue(wmWindowReply.get())); - - const ReplyGuard wmNameReply(GetPropertyReply( - conn.get(), GetProperty(conn.get(), 0, wmRootWindow, *wmNameAtom, *utf8StringAtom, 0, 1024), nullptr - )); - - if (!wmNameReply || wmNameReply->type != *utf8StringAtom || GetPropertyValueLength(wmNameReply.get()) == 0) - return Err(DracError(DracErrorCode::NotFound, "Failed to get _NET_WM_NAME property")); - - const char* nameData = static_cast(GetPropertyValue(wmNameReply.get())); - const usize length = GetPropertyValueLength(wmNameReply.get()); - - return String(nameData, length); - } - #else - fn GetX11WindowManager() -> Result { - return Err(DracError(DracErrorCode::NotSupported, "XCB (X11) support not available")); - } - #endif - - #ifdef HAVE_WAYLAND - fn GetWaylandCompositor() -> Result { - using util::types::i32, util::types::Array, util::types::isize, util::types::StringView; - - const wl::DisplayGuard display; - - if (!display) - return Err(DracError(DracErrorCode::NotFound, "Failed to connect to display (is Wayland running?)")); - - const i32 fileDescriptor = display.fd(); - if (fileDescriptor < 0) - return Err(DracError(DracErrorCode::ApiUnavailable, "Failed to get Wayland file descriptor")); - - ucred cred; - socklen_t len = sizeof(cred); - - if (getsockopt(fileDescriptor, SOL_SOCKET, SO_PEERCRED, &cred, &len) == -1) - return Err(DracError("Failed to get socket credentials (SO_PEERCRED)")); - - Array 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(DracError(DracErrorCode::InternalError, "Failed to format /proc path (PID too large?)")); - - *out = '\0'; - - const char* exeLinkPath = exeLinkPathBuf.data(); - - Array exeRealPathBuf {}; // NOLINT(misc-include-cleaner) - PATH_MAX is in - - const isize bytesRead = readlink(exeLinkPath, exeRealPathBuf.data(), exeRealPathBuf.size() - 1); - - if (bytesRead == -1) - return Err(DracError(std::format("Failed to read link '{}'", exeLinkPath))); - - exeRealPathBuf.at(bytesRead) = '\0'; - - StringView compositorNameView; - - const StringView pathView(exeRealPathBuf.data(), bytesRead); - - StringView filenameView; - - if (const usize lastCharPos = pathView.find_last_not_of('/'); lastCharPos != StringView::npos) { - const StringView relevantPart = pathView.substr(0, lastCharPos + 1); - - if (const usize separatorPos = relevantPart.find_last_of('/'); separatorPos == StringView::npos) - filenameView = relevantPart; - else - filenameView = relevantPart.substr(separatorPos + 1); - } - - if (!filenameView.empty()) - compositorNameView = filenameView; - - if (compositorNameView.empty() || compositorNameView == "." || compositorNameView == "/") - return Err(DracError(DracErrorCode::NotFound, "Failed to get compositor name from path")); - - 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(DracError(DracErrorCode::NotFound, "Compositor name invalid after heuristic")); - - return String(cleanedView); - } - - return String(compositorNameView); - } - #else - fn GetWaylandCompositor() -> Result { - return Err(DracError(DracErrorCode::NotSupported, "Wayland support not available")); - } - #endif -} // namespace - -namespace os { - using util::helpers::GetEnv; - - fn GetOSVersion() -> Result { - using util::types::StringView; - - std::ifstream file("/etc/os-release"); - - if (!file) - return Err(DracError(DracErrorCode::NotFound, std::format("Failed to open /etc/os-release"))); - - String line; - constexpr StringView prefix = "PRETTY_NAME="; - - while (std::getline(file, line)) { - if (StringView(line).starts_with(prefix)) { - String value = line.substr(prefix.size()); - - if ((value.length() >= 2 && value.front() == '"' && value.back() == '"') || - (value.length() >= 2 && value.front() == '\'' && value.back() == '\'')) - value = value.substr(1, value.length() - 2); - - if (value.empty()) - return Err( - DracError(DracErrorCode::ParseError, std::format("PRETTY_NAME value is empty or only quotes in /etc/os-release")) - ); - - return value; - } - } - - return Err(DracError(DracErrorCode::NotFound, "PRETTY_NAME line not found in /etc/os-release")); - } - - fn GetMemInfo() -> Result { - struct sysinfo info; - - if (sysinfo(&info) != 0) - return Err(DracError("sysinfo call failed")); - - const u64 totalRam = info.totalram; - const u64 memUnit = info.mem_unit; - - if (memUnit == 0) - return Err(DracError(DracErrorCode::InternalError, "sysinfo returned mem_unit of zero")); - - if (totalRam > std::numeric_limits::max() / memUnit) - return Err(DracError(DracErrorCode::InternalError, "Potential overflow calculating total RAM")); - - return info.totalram * info.mem_unit; - } - - fn GetNowPlaying() -> Result { - #ifdef HAVE_DBUS - using namespace dbus; - - Result connectionResult = Connection::busGet(DBUS_BUS_SESSION); - if (!connectionResult) - return Err(connectionResult.error()); - - const Connection& connection = *connectionResult; - - Option activePlayer = None; - - { - Result listNamesResult = Message::newMethodCall("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "ListNames"); - - if (!listNamesResult) - return Err(listNamesResult.error()); - - Result listNamesReplyResult = connection.sendWithReplyAndBlock(*listNamesResult, 100); - - if (!listNamesReplyResult) - return Err(listNamesReplyResult.error()); - - MessageIter iter = listNamesReplyResult->iterInit(); - - if (!iter.isValid() || iter.getArgType() != DBUS_TYPE_ARRAY) - return Err(DracError(DracErrorCode::ParseError, "Invalid DBus ListNames reply format: Expected array")); - - MessageIter subIter = iter.recurse(); - - if (!subIter.isValid()) - return Err( - DracError(DracErrorCode::ParseError, "Invalid DBus ListNames reply format: Could not recurse into array") - ); - - while (subIter.getArgType() != DBUS_TYPE_INVALID) { - if (Option name = subIter.getString()) - if (name->starts_with("org.mpris.MediaPlayer2.")) { - activePlayer = std::move(*name); - break; - } - if (!subIter.next()) - break; - } - } - - if (!activePlayer) - return Err(DracError(DracErrorCode::NotFound, "No active MPRIS players found")); - - Result msgResult = Message::newMethodCall(activePlayer->c_str(), "/org/mpris/MediaPlayer2", "org.freedesktop.DBus.Properties", "Get"); - - if (!msgResult) - return Err(msgResult.error()); - - Message& msg = *msgResult; - - if (!msg.appendArgs("org.mpris.MediaPlayer2.Player", "Metadata")) - return Err(DracError(DracErrorCode::InternalError, "Failed to append arguments to Properties.Get message")); - - Result replyResult = connection.sendWithReplyAndBlock(msg, 100); - - if (!replyResult) - return Err(replyResult.error()); - - Option title = None; - Option artist = None; - - MessageIter propIter = replyResult->iterInit(); - - if (!propIter.isValid()) - return Err(DracError(DracErrorCode::ParseError, "Properties.Get reply has no arguments or invalid iterator")); - - if (propIter.getArgType() != DBUS_TYPE_VARIANT) - return Err(DracError(DracErrorCode::ParseError, "Properties.Get reply argument is not a variant")); - - MessageIter variantIter = propIter.recurse(); - - if (!variantIter.isValid()) - return Err(DracError(DracErrorCode::ParseError, "Could not recurse into variant")); - - if (variantIter.getArgType() != DBUS_TYPE_ARRAY || variantIter.getElementType() != DBUS_TYPE_DICT_ENTRY) - return Err(DracError(DracErrorCode::ParseError, "Metadata variant content is not a dictionary array (a{sv})")); - - MessageIter dictIter = variantIter.recurse(); - - if (!dictIter.isValid()) - return Err(DracError(DracErrorCode::ParseError, "Could not recurse into metadata dictionary array")); - - while (dictIter.getArgType() == DBUS_TYPE_DICT_ENTRY) { - MessageIter entryIter = dictIter.recurse(); - - if (!entryIter.isValid()) { - debug_log("Warning: Could not recurse into dict entry, skipping."); - if (!dictIter.next()) - break; - continue; - } - - Option key = entryIter.getString(); - - if (!key) { - debug_log("Warning: Could not get key string from dict entry, skipping."); - if (!dictIter.next()) - break; - continue; - } - - if (!entryIter.next() || entryIter.getArgType() != DBUS_TYPE_VARIANT) { - if (!dictIter.next()) - break; - continue; - } - - MessageIter valueVariantIter = entryIter.recurse(); - - if (!valueVariantIter.isValid()) { - if (!dictIter.next()) - break; - continue; - } - - if (*key == "xesam:title") { - title = valueVariantIter.getString(); - } else if (*key == "xesam:artist") { - if (valueVariantIter.getArgType() == DBUS_TYPE_ARRAY && valueVariantIter.getElementType() == DBUS_TYPE_STRING) { - if (MessageIter artistArrayIter = valueVariantIter.recurse(); artistArrayIter.isValid()) - artist = artistArrayIter.getString(); - } else - debug_log("Warning: Artist value was not an array of strings as expected."); - } - - if (!dictIter.next()) - break; - } - - return MediaInfo(std::move(title), std::move(artist)); - #else - return Err(DracError(DracErrorCode::NotSupported, "DBus support not available")); - #endif - } - - fn GetWindowManager() -> Result { - #if !defined(HAVE_WAYLAND) && !defined(HAVE_XCB) - return Err(DracError(DracErrorCode::NotSupported, "Wayland or XCB support not available")); - #else - if (Result waylandResult = GetWaylandCompositor()) - return *waylandResult; - - if (Result x11Result = GetX11WindowManager()) - return *x11Result; - - return Err(DracError(DracErrorCode::NotFound, "Could not detect window manager (Wayland/X11) or both failed")); - #endif - } - - fn GetDesktopEnvironment() -> Result { - return GetEnv("XDG_CURRENT_DESKTOP") - .transform([](String xdgDesktop) -> String { - if (const usize colon = xdgDesktop.find(':'); colon != String::npos) - xdgDesktop.resize(colon); - - return xdgDesktop; - }) - .or_else([](const DracError&) -> Result { return GetEnv("DESKTOP_SESSION"); }); - } - - fn GetShell() -> Result { - using util::types::Pair, util::types::Array, util::types::StringView; - - if (const Result shellPath = GetEnv("SHELL")) { - // clang-format off - constexpr Array, 5> shellMap {{ - { "bash", "Bash" }, - { "zsh", "Zsh" }, - { "fish", "Fish" }, - { "nu", "Nushell" }, - { "sh", "SH" }, // sh last because other shells contain "sh" - }}; - // clang-format on - - for (const auto& [exe, name] : shellMap) - if (shellPath->contains(exe)) - return String(name); - - return *shellPath; // fallback to the raw shell path - } - - return Err(DracError(DracErrorCode::NotFound, "Could not find SHELL environment variable")); - } - - fn GetHost() -> Result { - using util::types::CStr; - - constexpr CStr primaryPath = "/sys/class/dmi/id/product_family"; - constexpr CStr fallbackPath = "/sys/class/dmi/id/product_name"; - - fn readFirstLine = [&](const String& path) -> Result { - std::ifstream file(path); - String line; - - if (!file) - return Err(DracError(DracErrorCode::NotFound, std::format("Failed to open DMI product identifier file '{}'", path))); - - if (!std::getline(file, line) || line.empty()) - return Err(DracError(DracErrorCode::ParseError, std::format("DMI product identifier file ('{}') is empty", path))); - - return line; - }; - - Result primaryResult = readFirstLine(primaryPath); - - if (primaryResult) - return primaryResult; - - DracError primaryError = primaryResult.error(); - - Result fallbackResult = readFirstLine(fallbackPath); - - if (fallbackResult) - return fallbackResult; - - DracError fallbackError = fallbackResult.error(); - - return Err(DracError( - DracErrorCode::InternalError, - std::format( - "Failed to get host identifier. Primary ('{}'): {}. Fallback ('{}'): {}", - primaryPath, - primaryError.message, - fallbackPath, - fallbackError.message - ) - )); - } - - fn GetKernelVersion() -> Result { - utsname uts; - - if (uname(&uts) == -1) - return Err(DracError("uname call failed")); - - if (std::strlen(uts.release) == 0) - return Err(DracError(DracErrorCode::ParseError, "uname returned null kernel release")); - - return uts.release; - } - - fn GetDiskUsage() -> Result { - struct statvfs stat; - - if (statvfs("/", &stat) == -1) - return Err(DracError("Failed to get filesystem stats for '/' (statvfs call failed)")); - - return DiskSpace { - .usedBytes = (stat.f_blocks * stat.f_frsize) - (stat.f_bfree * stat.f_frsize), - .totalBytes = stat.f_blocks * stat.f_frsize, - }; - } -} // namespace os - -namespace package { - using namespace std::string_literals; - - fn CountApk() -> Result { - using namespace util::cache; - - const String pmId = "apk"; - const fs::path apkDbPath = "/lib/apk/db/installed"; - const String cacheKey = "pkg_count_" + pmId; - - std::error_code fsErrCode; - - if (!fs::exists(apkDbPath, fsErrCode)) { - if (fsErrCode) { - warn_log("Filesystem error checking for Apk DB at '{}': {}", apkDbPath.string(), fsErrCode.message()); - return Err(DracError(DracErrorCode::IoError, "Filesystem error checking Apk DB: " + fsErrCode.message())); - } - - return Err(DracError(DracErrorCode::NotFound, std::format("Apk database path '{}' does not exist", apkDbPath.string()))); - } - - if (Result cachedDataResult = ReadCache(cacheKey)) { - const auto& [cachedCount, timestamp] = *cachedDataResult; - std::error_code modTimeErrCode; - const fs::file_time_type dbModTime = fs::last_write_time(apkDbPath, modTimeErrCode); - - if (modTimeErrCode) { - warn_log( - "Could not get modification time for '{}': {}. Invalidating {} cache.", - apkDbPath.string(), - modTimeErrCode.message(), - pmId - ); - } else { - using std::chrono::system_clock, std::chrono::seconds, std::chrono::floor; - const system_clock::time_point cacheTimePoint = system_clock::time_point(seconds(timestamp)); - - if (cacheTimePoint.time_since_epoch() >= dbModTime.time_since_epoch()) { - debug_log("Using valid {} package count cache (DB file unchanged since {}). Count: {}", pmId, std::format("{:%F %T %Z}", floor(cacheTimePoint)), cachedCount); - return cachedCount; - } - - debug_log("{} package count cache stale (DB file modified).", pmId); - } - } else { - if (cachedDataResult.error().code != DracErrorCode::NotFound) - debug_at(cachedDataResult.error()); - debug_log("{} package count cache not found or unreadable.", pmId); - } - - debug_log("Fetching fresh {} package count from file: {}", pmId, apkDbPath.string()); - - std::ifstream file(apkDbPath); - if (!file.is_open()) - return Err(DracError(DracErrorCode::IoError, std::format("Failed to open Apk database file '{}'", apkDbPath.string()))); - - String line; - - u64 count = 0; - - try { - while (std::getline(file, line)) - if (line.empty()) - count++; - } catch (const std::ios_base::failure& e) { - return Err(DracError( - DracErrorCode::IoError, - std::format("Error reading Apk database file '{}': {}", apkDbPath.string(), e.what()) - )); - } - - if (file.bad()) - return Err(DracError(DracErrorCode::IoError, std::format("IO error while reading Apk database file '{}'", apkDbPath.string()))); - - { - using std::chrono::duration_cast, std::chrono::system_clock, std::chrono::seconds; - - const i64 timestampEpochSeconds = duration_cast(system_clock::now().time_since_epoch()).count(); - - const PkgCountCacheData dataToCache(count, timestampEpochSeconds); - - if (Result writeResult = WriteCache(cacheKey, dataToCache); !writeResult) - debug_at(writeResult.error()); - } - - return count; - } - - fn CountDpkg() -> Result { - return GetCountFromDirectory("Dpkg", fs::current_path().root_path() / "var" / "lib" / "dpkg" / "info", ".list"s); - } - - fn CountMoss() -> Result { - Result countResult = GetCountFromDb("moss", "/.moss/db/install", "SELECT COUNT(*) FROM meta"); - - if (countResult) - if (*countResult > 0) - return *countResult - 1; - - return countResult; - } - - fn CountPacman() -> Result { - return GetCountFromDirectory("Pacman", fs::current_path().root_path() / "var" / "lib" / "pacman" / "local", true); - } - - fn CountRpm() -> Result { - return GetCountFromDb("rpm", "/var/lib/rpm/rpmdb.sqlite", "SELECT COUNT(*) FROM Installtid"); - } - - fn CountXbps() -> Result { - using util::types::CStr; - - const CStr xbpsDbPath = "/var/db/xbps"; - - if (!fs::exists(xbpsDbPath)) - return Err(DracError(DracErrorCode::NotFound, std::format("Xbps database path '{}' does not exist", xbpsDbPath))); - - fs::path plistPath; - for (const fs::directory_entry& entry : fs::directory_iterator(xbpsDbPath)) { - const String filename = entry.path().filename().string(); - if (filename.starts_with("pkgdb-") && filename.ends_with(".plist")) { - plistPath = entry.path(); - break; - } - } - - if (plistPath.empty()) - return Err(DracError(DracErrorCode::NotFound, "No Xbps database found")); - - return GetCountFromPlist("xbps", plistPath); - } -} // namespace package - -#endif // __linux__ diff --git a/src/UI/UI.cpp b/src/UI/UI.cpp index 428390e..9db0317 100644 --- a/src/UI/UI.cpp +++ b/src/UI/UI.cpp @@ -1,5 +1,6 @@ #include "UI.hpp" +#include "OS/OperatingSystem.hpp" #include "Util/Types.hpp" namespace ui { @@ -101,13 +102,11 @@ namespace ui { }}; // clang-format on - fn GetDistroIcon() -> Option { + fn GetDistroIcon(StringView distro) -> Option { using namespace matchit; - const Result distro = os::GetOSVersion(); - for (const auto& [distroName, distroIcon] : distro_icons) - if (distro->contains(distroName)) + if (distro.contains(distroName)) return distroIcon; return None; @@ -190,7 +189,7 @@ namespace ui { if (data.osVersion) { systemInfoRows.push_back({ #ifdef __linux__ - .icon = GetDistroIcon().value_or(osIcon), + .icon = GetDistroIcon(*data.osVersion).value_or(osIcon), #else .icon = osIcon, #endif diff --git a/src/main.cpp b/src/main.cpp index 6100c01..b1d7fa5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -19,86 +19,83 @@ using util::types::i32, util::types::Exception; -fn main(const i32 argc, char* argv[]) -> i32 { - try { +fn main(const i32 argc, char* argv[]) -> i32 try { #ifdef _WIN32 - winrt::init_apartment(); + winrt::init_apartment(); #endif + { + using argparse::ArgumentParser; + + ArgumentParser parser("draconis", "0.1.0"); + + parser + .add_argument("--log-level") + .help("Set the log level") + .default_value("info") + .choices("debug", "info", "warn", "error"); + + parser + .add_argument("-V", "--verbose") + .help("Enable verbose logging. Overrides --log-level.") + .flag(); + + if (Result result = parser.parse_args(argc, argv); !result) { + error_at(result.error()); + return EXIT_FAILURE; + } + + bool verbose = parser.get("-V").value_or(false) || parser.get("--verbose").value_or(false); + Result logLevelStr = verbose ? "debug" : parser.get("--log-level"); + { - using argparse::ArgumentParser; + using matchit::match, matchit::is, matchit::_; + using util::logging::LogLevel; + using enum util::logging::LogLevel; - ArgumentParser parser("draconis", "0.1.0"); - - parser - .add_argument("--log-level") - .help("Set the log level") - .default_value("info") - .choices("debug", "info", "warn", "error"); - - parser - .add_argument("-V", "--verbose") - .help("Enable verbose logging. Overrides --log-level.") - .flag(); - - if (Result result = parser.parse_args(argc, argv); !result) { - error_at(result.error()); - return EXIT_FAILURE; - } - - bool verbose = parser.get("-V").value_or(false) || parser.get("--verbose").value_or(false); - Result logLevelStr = verbose ? "debug" : parser.get("--log-level"); - - { - using matchit::match, matchit::is, matchit::_; - using util::logging::LogLevel; - using enum util::logging::LogLevel; - - LogLevel minLevel = match(logLevelStr)( - is | "debug" = Debug, - is | "info" = Info, - is | "warn" = Warn, - is | "error" = Error, + LogLevel minLevel = match(logLevelStr)( + is | "debug" = Debug, + is | "info" = Info, + is | "warn" = Warn, + is | "error" = Error, #ifndef NDEBUG - is | _ = Debug + is | _ = Debug #else - is | _ = Info + is | _ = Info #endif - ); + ); - SetRuntimeLogLevel(minLevel); - } + SetRuntimeLogLevel(minLevel); } - - const Config& config = Config::getInstance(); - - const os::SystemData data = os::SystemData(config); - - { - using ftxui::Element, ftxui::Screen, ftxui::Render; - using ftxui::Dimension::Full, ftxui::Dimension::Fit; - - Element document = ui::CreateUI(config, data); - - Screen screen = Screen::Create(Full(), Fit(document)); - Render(screen, document); - screen.Print(); - } - - // Running the program as part of the shell's startup will cut - // off the last line of output, so we need to add a newline here. -#ifdef __cpp_lib_print - std::println(); -#else - std::cout << '\n'; -#endif - } catch (const DracError& e) { - error_at(e); - return EXIT_FAILURE; - } catch (const Exception& e) { - error_at(e); - return EXIT_FAILURE; } + const Config& config = Config::getInstance(); + + const os::SystemData data = os::SystemData(config); + + { + using ftxui::Element, ftxui::Screen, ftxui::Render; + using ftxui::Dimension::Full, ftxui::Dimension::Fit; + + Element document = ui::CreateUI(config, data); + + Screen screen = Screen::Create(Full(), Fit(document)); + Render(screen, document); + screen.Print(); + } + + // Running the program as part of the shell's startup will cut + // off the last line of output, so we need to add a newline here. +#ifdef __cpp_lib_print + std::println(); +#else + std::cout << '\n'; +#endif return EXIT_SUCCESS; +} catch (const DracError& e) { + error_at(e); + return EXIT_FAILURE; +} catch (const Exception& e) { + error_at(e); + return EXIT_FAILURE; } diff --git a/src/wrappers/DBus.hpp b/src/wrappers/DBus.hpp deleted file mode 100644 index 66d7b3c..0000000 --- a/src/wrappers/DBus.hpp +++ /dev/null @@ -1,416 +0,0 @@ -#pragma once - -#if defined(__linux__) || defined(__FreeBSD__) || defined(__DragonFly__) || defined(__NetBSD__) - -// clang-format off -#include -#include // DBus Library -#include // std::exchange, std::forward -#include // std::format -#include // std::is_convertible_v - -#include "src/util/defs.hpp" -#include "src/util/error.hpp" -#include "src/util/types.hpp" -// clang-format on - -namespace dbus { - using util::error::DracError, util::error::DracErrorCode; - using util::types::Option, util::types::Result, util::types::Err, util::types::String, util::types::i32, - util::types::u32, util::types::None; - - /** - * @brief RAII wrapper for DBusError. Automatically initializes and frees. - */ - class Error { - DBusError m_err {}; - bool m_isInitialized = false; - - public: - Error() - : m_isInitialized(true) { - dbus_error_init(&m_err); - } - - ~Error() { - if (m_isInitialized) - dbus_error_free(&m_err); - } - - Error(const Error&) = delete; - fn operator=(const Error&)->Error& = delete; - - Error(Error&& other) noexcept - : m_err(other.m_err), m_isInitialized(other.m_isInitialized) { - other.m_isInitialized = false; - dbus_error_init(&other.m_err); - } - - fn operator=(Error&& other) noexcept -> Error& { - if (this != &other) { - if (m_isInitialized) - dbus_error_free(&m_err); - - m_err = other.m_err; - m_isInitialized = other.m_isInitialized; - - other.m_isInitialized = false; - dbus_error_init(&other.m_err); - } - return *this; - } - - /** - * @brief Checks if the D-Bus error is set. - * @return True if an error is set, false otherwise. - */ - [[nodiscard]] fn isSet() const -> bool { - return m_isInitialized && dbus_error_is_set(&m_err); - } - - /** - * @brief Gets the error message. - * @return The error message string, or "" if not set or not initialized. - */ - [[nodiscard]] fn message() const -> const char* { - return isSet() ? m_err.message : ""; - } - - /** - * @brief Gets the error name. - * @return The error name string (e.g., "org.freedesktop.DBus.Error.Failed"), or "" if not set or not initialized. - */ - [[nodiscard]] fn name() const -> const char* { - return isSet() ? m_err.name : ""; - } - - /** - * @brief Gets a pointer to the underlying DBusError. Use with caution. - * @return Pointer to the DBusError struct. - */ - [[nodiscard]] fn get() -> DBusError* { - return &m_err; - } - /** - * @brief Gets a const pointer to the underlying DBusError. - * @return Const pointer to the DBusError struct. - */ - [[nodiscard]] fn get() const -> const DBusError* { - return &m_err; - } - - /** - * @brief Converts the D-Bus error to a DraconisError. - * @param code The DraconisError code to use if the D-Bus error is set. - * @return A DraconisError representing the D-Bus error, or an internal error if called when no D-Bus error is set. - */ - [[nodiscard]] fn toDraconisError(const DracErrorCode code = DracErrorCode::PlatformSpecific) const -> DracError { - if (isSet()) - return { code, std::format("D-Bus Error: {} ({})", message(), name()) }; - - return { DracErrorCode::InternalError, "Attempted to convert non-set ErrorGuard" }; - } - }; - - /** - * @brief RAII wrapper for DBusMessageIter. Encapsulates iterator operations. - * Note: This wrapper does *not* own the message, only the iterator state. - * It's designed to be used within the scope where the MessageGuard is valid. - */ - class MessageIter { - DBusMessageIter m_iter {}; - bool m_isValid = false; - - explicit MessageIter(const DBusMessageIter& iter, const bool isValid) - : m_iter(iter), m_isValid(isValid) {} - - friend class Message; - - /** - * @brief Gets the value of a basic-typed argument. - * Unsafe: Caller must ensure 'value' points to memory suitable for the actual argument type. - * @param value Pointer to store the retrieved value. - */ - fn getBasic(void* value) -> void { - if (m_isValid) - dbus_message_iter_get_basic(&m_iter, value); - } - - public: - MessageIter(const MessageIter&) = delete; - fn operator=(const MessageIter&)->MessageIter& = delete; - MessageIter(MessageIter&&) = delete; - fn operator=(MessageIter&&)->MessageIter& = delete; - ~MessageIter() = default; - - /** - * @brief Checks if the iterator is validly initialized. - */ - [[nodiscard]] fn isValid() const -> bool { - return m_isValid; - } - - /** - * @brief Gets the D-Bus type code of the current argument. - * @return The D-Bus type code, or DBUS_TYPE_INVALID otherwise. - */ - [[nodiscard]] fn getArgType() -> int { - return m_isValid ? dbus_message_iter_get_arg_type(&m_iter) : DBUS_TYPE_INVALID; - } - - /** - * @brief Gets the element type of the container pointed to by the iterator. - * Only valid if the iterator points to an ARRAY or VARIANT. - * @return The D-Bus type code of the elements, or DBUS_TYPE_INVALID otherwise. - */ - [[nodiscard]] fn getElementType() -> int { - return m_isValid ? dbus_message_iter_get_element_type(&m_iter) : DBUS_TYPE_INVALID; - } - - /** - * @brief Advances the iterator to the next argument. - * @return True if successful (moved to a next element), false if at the end or iterator is invalid. - */ - fn next() -> bool { - return m_isValid && dbus_message_iter_next(&m_iter); - } - - /** - * @brief Recurses into a container-type argument (e.g., array, struct, variant). - * @return A new MessageIterGuard for the sub-container. The returned iterator might be invalid - * if the current element is not a container or the main iterator is invalid. - */ - [[nodiscard]] fn recurse() -> MessageIter { - if (!m_isValid) - return MessageIter({}, false); - - DBusMessageIter subIter; - dbus_message_iter_recurse(&m_iter, &subIter); - - return MessageIter(subIter, true); - } - - /** - * @brief Helper to safely get a string argument from the iterator. - * @return An Option containing the string value if the current arg is a valid string, or None otherwise. - */ - [[nodiscard]] fn getString() -> Option { - if (m_isValid && getArgType() == DBUS_TYPE_STRING) { - const char* strPtr = nullptr; - - // ReSharper disable once CppRedundantCastExpression - getBasic(static_cast(&strPtr)); - - if (strPtr) - return String(strPtr); - } - - return None; - } - }; - - /** - * @brief RAII wrapper for DBusMessage. Automatically unrefs. - */ - class Message { - DBusMessage* m_msg = nullptr; - - public: - explicit Message(DBusMessage* msg = nullptr) - : m_msg(msg) {} - - ~Message() { - if (m_msg) - dbus_message_unref(m_msg); - } - - Message(const Message&) = delete; - fn operator=(const Message&)->Message& = delete; - - Message(Message&& other) noexcept - : m_msg(std::exchange(other.m_msg, nullptr)) {} - - fn operator=(Message&& other) noexcept -> Message& { - if (this != &other) { - if (m_msg) - dbus_message_unref(m_msg); - m_msg = std::exchange(other.m_msg, nullptr); - } - return *this; - } - - /** - * @brief Gets the underlying DBusMessage pointer. Use with caution. - * @return The raw DBusMessage pointer, or nullptr if not holding a message. - */ - [[nodiscard]] fn get() const -> DBusMessage* { - return m_msg; - } - - /** - * @brief Initializes a message iterator for reading arguments from this message. - * @return A MessageIterGuard. Check iter.isValid() before use. - */ - [[nodiscard]] fn iterInit() const -> MessageIter { - if (!m_msg) - return MessageIter({}, false); - - DBusMessageIter iter; - const bool isValid = dbus_message_iter_init(m_msg, &iter); - return MessageIter(iter, isValid); - } - - /** - * @brief Appends arguments of basic types to the message. - * @tparam Args Types of the arguments to append. - * @param args The arguments to append. - * @return True if all arguments were appended successfully, false otherwise (e.g., allocation error). - */ - template - [[nodiscard]] fn appendArgs(Args&&... args) -> bool { - if (!m_msg) - return false; - - DBusMessageIter iter; - dbus_message_iter_init_append(m_msg, &iter); - - bool success = true; - ((success = success && appendArgInternal(iter, std::forward(args))), ...); // NOLINT - return success; - } - - /** - * @brief Creates a new D-Bus method call message. - * @param destination Service name (e.g., "org.freedesktop.Notifications"). Can be null. - * @param path Object path (e.g., "/org/freedesktop/Notifications"). Must not be null. - * @param interface Interface name (e.g., "org.freedesktop.Notifications"). Can be null. - * @param method Method name (e.g., "Notify"). Must not be null. - * @return Result containing a MessageGuard on success, or DraconisError on failure. - */ - static fn newMethodCall(const char* destination, const char* path, const char* interface, const char* method) - -> Result { - DBusMessage* rawMsg = dbus_message_new_method_call(destination, path, interface, method); - - if (!rawMsg) - return Err(DracError(DracErrorCode::OutOfMemory, "dbus_message_new_method_call failed (allocation failed?)")); - - return Message(rawMsg); - } - - private: - template - fn appendArgInternal(DBusMessageIter& iter, T&& arg) -> bool { - using DecayedT = std::decay_t; - - if constexpr (std::is_convertible_v) { - const char* valuePtr = static_cast(std::forward(arg)); - return dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, static_cast(&valuePtr)); - } else { - static_assert(!sizeof(T*), "Unsupported type passed to appendArgs"); - return false; - } - } - }; - - /** - * @brief RAII wrapper for DBusConnection. Automatically unrefs. - */ - class Connection { - DBusConnection* m_conn = nullptr; - - public: - explicit Connection(DBusConnection* conn = nullptr) - : m_conn(conn) {} - - ~Connection() { - if (m_conn) - dbus_connection_unref(m_conn); - } - - Connection(const Connection&) = delete; - fn operator=(const Connection&)->Connection& = delete; - - Connection(Connection&& other) noexcept - : m_conn(std::exchange(other.m_conn, nullptr)) {} - - fn operator=(Connection&& other) noexcept -> Connection& { - if (this != &other) { - if (m_conn) - dbus_connection_unref(m_conn); - - m_conn = std::exchange(other.m_conn, nullptr); - } - return *this; - } - - /** - * @brief Gets the underlying DBusConnection pointer. Use with caution. - * @return The raw DBusConnection pointer, or nullptr if not holding a connection. - */ - [[nodiscard]] fn get() const -> DBusConnection* { - return m_conn; - } - - /** - * @brief Sends a message and waits for a reply, blocking execution. - * @param message The D-Bus message guard to send. - * @param timeout_milliseconds Timeout duration in milliseconds. - * @return Result containing the reply MessageGuard on success, or DraconisError on failure. - */ - [[nodiscard]] fn sendWithReplyAndBlock(const Message& message, const i32 timeout_milliseconds = 1000) const - -> Result { - if (!m_conn || !message.get()) - return Err( - DracError(DracErrorCode::InvalidArgument, "Invalid connection or message provided to sendWithReplyAndBlock") - ); - - Error err; - DBusMessage* rawReply = - dbus_connection_send_with_reply_and_block(m_conn, message.get(), timeout_milliseconds, err.get()); - - if (err.isSet()) { - if (const char* errName = err.name()) { - if (strcmp(errName, DBUS_ERROR_TIMEOUT) == 0 || strcmp(errName, DBUS_ERROR_NO_REPLY) == 0) - return Err(err.toDraconisError(DracErrorCode::Timeout)); - - if (strcmp(errName, DBUS_ERROR_SERVICE_UNKNOWN) == 0) - return Err(err.toDraconisError(DracErrorCode::NotFound)); - - if (strcmp(errName, DBUS_ERROR_ACCESS_DENIED) == 0) - return Err(err.toDraconisError(DracErrorCode::PermissionDenied)); - } - - return Err(err.toDraconisError(DracErrorCode::PlatformSpecific)); - } - - if (!rawReply) - return Err(DracError( - DracErrorCode::ApiUnavailable, - "dbus_connection_send_with_reply_and_block returned null without setting error (likely timeout or " - "disconnected)" - )); - - return Message(rawReply); - } - - /** - * @brief Connects to a D-Bus bus type (Session or System). - * @param bus_type The type of bus (DBUS_BUS_SESSION or DBUS_BUS_SYSTEM). - * @return Result containing a ConnectionGuard on success, or DraconisError on failure. - */ - static fn busGet(const DBusBusType bus_type) -> Result { - Error err; - DBusConnection* rawConn = dbus_bus_get(bus_type, err.get()); - - if (err.isSet()) - return Err(err.toDraconisError(DracErrorCode::ApiUnavailable)); - - if (!rawConn) - return Err(DracError(DracErrorCode::ApiUnavailable, "dbus_bus_get returned null without setting error")); - - return Connection(rawConn); - } - }; -} // namespace dbus - -#endif // __linux__ || __FreeBSD__ || __DragonFly__ || __NetBSD__ diff --git a/src/wrappers/Wayland.hpp b/src/wrappers/Wayland.hpp deleted file mode 100644 index ed06709..0000000 --- a/src/wrappers/Wayland.hpp +++ /dev/null @@ -1,110 +0,0 @@ -#pragma once - -#if defined(__linux__) || defined(__FreeBSD__) || defined(__DragonFly__) || defined(__NetBSD__) - -// clang-format off -#include // Wayland client library - -#include "src/util/defs.hpp" -#include "src/util/logging.hpp" -#include "src/util/types.hpp" -// clang-format on - -struct wl_display; - -namespace wl { - using display = wl_display; - - inline fn Connect(const char* name) -> display* { - return wl_display_connect(name); - } - inline fn Disconnect(display* display) -> void { - wl_display_disconnect(display); - } - inline fn GetFd(display* display) -> int { - return wl_display_get_fd(display); - } - - /** - * RAII wrapper for Wayland display connections - * Automatically handles resource acquisition and cleanup - */ - class DisplayGuard { - display* m_display; - - public: - /** - * Opens a Wayland display connection - */ - DisplayGuard() { - wl_log_set_handler_client([](const char* fmt, va_list args) -> void { - using util::types::i32, util::types::StringView; - - va_list argsCopy; - va_copy(argsCopy, args); - i32 size = std::vsnprintf(nullptr, 0, fmt, argsCopy); - va_end(argsCopy); - - if (size < 0) { - error_log("Wayland: Internal log formatting error (vsnprintf size check failed)."); - return; - } - - std::vector buffer(static_cast(size) + 1); - - i32 writeSize = std::vsnprintf(buffer.data(), buffer.size(), fmt, args); - - if (writeSize < 0 || writeSize >= static_cast(buffer.size())) { - error_log("Wayland: Internal log formatting error (vsnprintf write failed)."); - return; - } - - StringView msgView(buffer.data(), static_cast(writeSize)); - - if (!msgView.empty() && msgView.back() == '\n') - msgView.remove_suffix(1); - - debug_log("Wayland {}", msgView); - }); - - // NOLINTNEXTLINE(cppcoreguidelines-prefer-member-initializer) - needs to come after wl_log_set_handler_client - m_display = Connect(nullptr); - } - - ~DisplayGuard() { - if (m_display) - Disconnect(m_display); - } - - // Non-copyable - DisplayGuard(const DisplayGuard&) = delete; - fn operator=(const DisplayGuard&)->DisplayGuard& = delete; - - // Movable - DisplayGuard(DisplayGuard&& other) noexcept - : m_display(std::exchange(other.m_display, nullptr)) {} - fn operator=(DisplayGuard&& other) noexcept -> DisplayGuard& { - if (this != &other) { - if (m_display) - Disconnect(m_display); - - m_display = std::exchange(other.m_display, nullptr); - } - - return *this; - } - - [[nodiscard]] explicit operator bool() const { - return m_display != nullptr; - } - - [[nodiscard]] fn get() const -> display* { - return m_display; - } - [[nodiscard]] fn fd() const -> util::types::i32 { - return GetFd(m_display); - } - }; -} // namespace wl - -#endif // __linux__ || __FreeBSD__ || __DragonFly__ || __NetBSD__ diff --git a/src/wrappers/XCB.hpp b/src/wrappers/XCB.hpp deleted file mode 100644 index 7a271ad..0000000 --- a/src/wrappers/XCB.hpp +++ /dev/null @@ -1,340 +0,0 @@ -#pragma once - -#if defined(__linux__) || defined(__FreeBSD__) || defined(__DragonFly__) || defined(__NetBSD__) - -// clang-format off -#include // XCB library - -#include "src/util/defs.hpp" -#include "src/util/types.hpp" -// clang-format on - -namespace xcb { - using util::types::u8, util::types::i32, util::types::CStr, util::types::None; - - using connection_t = xcb_connection_t; - using setup_t = xcb_setup_t; - using screen_t = xcb_screen_t; - using window_t = xcb_window_t; - using atom_t = xcb_atom_t; - - using generic_error_t = xcb_generic_error_t; - using intern_atom_cookie_t = xcb_intern_atom_cookie_t; - using intern_atom_reply_t = xcb_intern_atom_reply_t; - using get_property_cookie_t = xcb_get_property_cookie_t; - using get_property_reply_t = xcb_get_property_reply_t; - - constexpr atom_t ATOM_WINDOW = XCB_ATOM_WINDOW; ///< Window atom - - /** - * @brief Enum representing different types of connection errors - * - * This enum defines the possible types of errors that can occur when - * establishing or maintaining an XCB connection. Each error type - * corresponds to a specific error code defined in the XCB library. - */ - enum ConnError : u8 { - Generic = XCB_CONN_ERROR, ///< Generic connection error - ExtNotSupported = XCB_CONN_CLOSED_EXT_NOTSUPPORTED, ///< Extension not supported - MemInsufficient = XCB_CONN_CLOSED_MEM_INSUFFICIENT, ///< Memory insufficient - ReqLenExceed = XCB_CONN_CLOSED_REQ_LEN_EXCEED, ///< Request length exceed - ParseErr = XCB_CONN_CLOSED_PARSE_ERR, ///< Parse error - InvalidScreen = XCB_CONN_CLOSED_INVALID_SCREEN, ///< Invalid screen - FdPassingFailed = XCB_CONN_CLOSED_FDPASSING_FAILED, ///< FD passing failed - }; - - /** - * @brief Connect to an XCB display - * - * This function establishes a connection to an XCB display. It takes a - * display name and a pointer to an integer that will store the screen - * number. - * - * @param displayname The name of the display to connect to - * @param screenp Pointer to an integer that will store the screen number - * @return A pointer to the connection object - */ - inline fn Connect(const char* displayname, int* screenp) -> connection_t* { - return xcb_connect(displayname, screenp); - } - - /** - * @brief Disconnect from an XCB display - * - * This function disconnects from an XCB display. It takes a pointer to - * the connection object. - * - * @param conn The connection object to disconnect from - */ - inline fn Disconnect(connection_t* conn) -> void { - xcb_disconnect(conn); - } - - /** - * @brief Check if a connection has an error - * - * This function checks if a connection has an error. It takes a pointer - * to the connection object. - * - * @param conn The connection object to check - * @return 1 if the connection has an error, 0 otherwise - */ - inline fn ConnectionHasError(connection_t* conn) -> int { - return xcb_connection_has_error(conn); - } - - /** - * @brief Intern an atom - * - * This function interns an atom. It takes a connection object, a flag - * - * @param conn The connection object to intern the atom on - * @param only_if_exists The flag to check if the atom exists - * @param name_len The length of the atom name - * @param name The name of the atom - * @return The cookie for the atom - */ - inline fn InternAtom(connection_t* conn, const uint8_t only_if_exists, const uint16_t name_len, const char* name) - -> intern_atom_cookie_t { - return xcb_intern_atom(conn, only_if_exists, name_len, name); - } - - /** - * @brief Get the reply for an interned atom - * - * This function gets the reply for an interned atom. It takes a connection - * object, a cookie, and a pointer to a generic error. - * - * @param conn The connection object - * @param cookie The cookie for the atom - * @param err The pointer to the generic error - * @return The reply for the atom - */ - inline fn InternAtomReply(connection_t* conn, const intern_atom_cookie_t cookie, generic_error_t** err) - -> intern_atom_reply_t* { - return xcb_intern_atom_reply(conn, cookie, err); - } - - /** - * @brief Get a property - * - * This function gets a property. It takes a connection object, a flag, - * a window, a property, a type, a long offset, and a long length. - * - * @param conn The connection object - * @param _delete The flag - * @param window The window - * @param property The property - * @param type The type - */ - inline fn GetProperty( - connection_t* conn, - const uint8_t _delete, - const window_t window, - const atom_t property, - const atom_t type, - const uint32_t long_offset, - const uint32_t long_length - ) -> get_property_cookie_t { - return xcb_get_property(conn, _delete, window, property, type, long_offset, long_length); - } - - /** - * @brief Get the reply for a property - * - * This function gets the reply for a property. It takes a connection - * object, a cookie, and a pointer to a generic error. - * - * @param conn The connection object - * @param cookie The cookie for the property - * @param err The pointer to the generic error - * @return The reply for the property - */ - inline fn GetPropertyReply(connection_t* conn, const get_property_cookie_t cookie, generic_error_t** err) - -> get_property_reply_t* { - return xcb_get_property_reply(conn, cookie, err); - } - - /** - * @brief Get the value length for a property - * - * @param reply The reply for the property - * @return The value length for the property - */ - inline fn GetPropertyValueLength(const get_property_reply_t* reply) -> int { - return xcb_get_property_value_length(reply); - } - - /** - * @brief Get the value for a property - * - * @param reply The reply for the property - * @return The value for the property - */ - inline fn GetPropertyValue(const get_property_reply_t* reply) -> void* { - return xcb_get_property_value(reply); - } - - /** - * RAII wrapper for X11 Display connections - * Automatically handles resource acquisition and cleanup - */ - class DisplayGuard { - connection_t* m_connection = nullptr; ///< The connection to the display - - public: - /** - * Opens an XCB connection - * @param name Display name (nullptr for default) - */ - explicit DisplayGuard(const util::types::CStr name = nullptr) - : m_connection(Connect(name, nullptr)) {} - ~DisplayGuard() { - if (m_connection) - Disconnect(m_connection); - } - - // Non-copyable - DisplayGuard(const DisplayGuard&) = delete; - fn operator=(const DisplayGuard&)->DisplayGuard& = delete; - - // Movable - DisplayGuard(DisplayGuard&& other) noexcept - : m_connection(std::exchange(other.m_connection, nullptr)) {} - - /** - * Move assignment operator - * @param other The other display guard - * @return The moved display guard - */ - fn operator=(DisplayGuard&& other) noexcept -> DisplayGuard& { - if (this != &other) { - if (m_connection) - Disconnect(m_connection); - - m_connection = std::exchange(other.m_connection, nullptr); - } - return *this; - } - - /** - * @brief Check if the display guard is valid - * @return True if the display guard is valid, false otherwise - */ - [[nodiscard]] explicit operator bool() const { - return m_connection && !ConnectionHasError(m_connection); - } - - /** - * @brief Get the connection to the display - * @return The connection to the display - */ - [[nodiscard]] fn get() const -> connection_t* { - return m_connection; - } - - /** - * @brief Get the setup for the display - * @return The setup for the display - */ - [[nodiscard]] fn setup() const -> const setup_t* { - return m_connection ? xcb_get_setup(m_connection) : nullptr; - } - - /** - * @brief Get the root screen for the display - * @return The root screen for the display - */ - [[nodiscard]] fn rootScreen() const -> screen_t* { - const setup_t* setup = this->setup(); - return setup ? xcb_setup_roots_iterator(setup).data : nullptr; - } - }; - - /** - * RAII wrapper for XCB replies - * Handles automatic cleanup of various XCB reply objects - */ - template - class ReplyGuard { - T* m_reply = nullptr; ///< The reply to the XCB request - - public: - /** - * @brief Default constructor - */ - ReplyGuard() = default; - - /** - * @brief Constructor with a reply - * @param reply The reply to the XCB request - */ - explicit ReplyGuard(T* reply) - : m_reply(reply) {} - - /** - * @brief Destructor - */ - ~ReplyGuard() { - if (m_reply) - free(m_reply); - } - - // Non-copyable - ReplyGuard(const ReplyGuard&) = delete; - fn operator=(const ReplyGuard&)->ReplyGuard& = delete; - - // Movable - ReplyGuard(ReplyGuard&& other) noexcept - : m_reply(std::exchange(other.m_reply, nullptr)) {} - - /** - * @brief Move assignment operator - * @param other The other reply guard - * @return The moved reply guard - */ - fn operator=(ReplyGuard&& other) noexcept -> ReplyGuard& { - if (this != &other) { - if (m_reply) - free(m_reply); - - m_reply = std::exchange(other.m_reply, nullptr); - } - return *this; - } - - /** - * @brief Check if the reply guard is valid - * @return True if the reply guard is valid, false otherwise - */ - [[nodiscard]] explicit operator bool() const { - return m_reply != nullptr; - } - - /** - * @brief Get the reply - * @return The reply - */ - [[nodiscard]] fn get() const -> T* { - return m_reply; - } - - /** - * @brief Get the reply - * @return The reply - */ - [[nodiscard]] fn operator->() const->T* { - return m_reply; - } - - /** - * @brief Dereference the reply - * @return The reply - */ - [[nodiscard]] fn operator*() const->T& { - return *m_reply; - } - }; -} // namespace xcb - -#endif // __linux__ || __FreeBSD__ || __DragonFly__ || __NetBSD__