diff --git a/src/os/linux.cpp b/src/os/linux.cpp index 7b82fc0..612fd2d 100644 --- a/src/os/linux.cpp +++ b/src/os/linux.cpp @@ -1,29 +1,28 @@ #ifdef __linux__ // clang-format off -#include // std::strlen -#include -#include // std::{unexpected, expected} -#include // std::{format, format_to_n} -#include // std::ifstream -#include // PATH_MAX -#include // std::numeric_limits -#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::strlen +#include // DBus::{DBusConnection, DBusMessage, DBusMessageIter, etc.} +#include // std::{unexpected, expected} +#include // std::{format, format_to_n} +#include // std::ifstream +#include // std::numeric_limits +#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 "src/core/util/helpers.hpp" #include "src/core/util/logging.hpp" - +#include "src/wrappers/dbus.hpp" #include "src/wrappers/wayland.hpp" #include "src/wrappers/xcb.hpp" -#include "os.hpp" #include "linux/pkg_count.hpp" +#include "os.hpp" // clang-format on using namespace util::types; @@ -179,81 +178,6 @@ namespace { return String(compositorNameView); } - - #pragma clang diagnostic push - #pragma clang diagnostic ignored "-Wold-style-cast" - fn GetMprisPlayers(DBusConnection* connection) -> Vec { - Vec mprisPlayers; - DBusError err; - dbus_error_init(&err); - - // Create a method call to org.freedesktop.DBus.ListNames - DBusMessage* msg = dbus_message_new_method_call( - "org.freedesktop.DBus", // target service - "/org/freedesktop/DBus", // object path - "org.freedesktop.DBus", // interface name - "ListNames" // method name - ); - - if (!msg) { - debug_log("Failed to create message for ListNames."); - return mprisPlayers; - } - - // Send the message and block until we get a reply. - DBusMessage* reply = dbus_connection_send_with_reply_and_block(connection, msg, -1, &err); - dbus_message_unref(msg); - - if (dbus_error_is_set(&err)) { - debug_log("DBus error in ListNames: {}", err.message); - dbus_error_free(&err); - return mprisPlayers; - } - - if (!reply) { - debug_log("No reply received for ListNames."); - return mprisPlayers; - } - - // The expected reply signature is "as" (an array of strings) - DBusMessageIter iter; - - if (!dbus_message_iter_init(reply, &iter)) { - debug_log("Reply has no arguments."); - dbus_message_unref(reply); - return mprisPlayers; - } - - if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) { - debug_log("Reply argument is not an array."); - dbus_message_unref(reply); - return mprisPlayers; - } - - // Iterate over the array of strings - DBusMessageIter subIter; - dbus_message_iter_recurse(&iter, &subIter); - - while (dbus_message_iter_get_arg_type(&subIter) != DBUS_TYPE_INVALID) { - if (dbus_message_iter_get_arg_type(&subIter) == DBUS_TYPE_STRING) { - const char* name = nullptr; - dbus_message_iter_get_basic(&subIter, static_cast(&name)); - if (name && std::string_view(name).contains("org.mpris.MediaPlayer2")) - mprisPlayers.emplace_back(name); - } - dbus_message_iter_next(&subIter); - } - - dbus_message_unref(reply); - return mprisPlayers; - } - #pragma clang diagnostic pop - - fn GetActivePlayer(const Vec& mprisPlayers) -> Option { - if (!mprisPlayers.empty()) - return mprisPlayers.front(); - return None; - } } // namespace namespace os { @@ -306,166 +230,134 @@ namespace os { return info.totalram * info.mem_unit; } - #pragma clang diagnostic push - #pragma clang diagnostic ignored "-Wold-style-cast" fn GetNowPlaying() -> Result { - DBusError err; - dbus_error_init(&err); + Result connectionResult = dbus::BusGet(DBUS_BUS_SESSION); - // Connect to the session bus - DBusConnection* connection = dbus_bus_get(DBUS_BUS_SESSION, &err); + if (!connectionResult) + return Err(connectionResult.error()); - if (!connection) - if (dbus_error_is_set(&err)) { - error_log("DBus connection error: {}", err.message); + dbus::ConnectionGuard& connection = *connectionResult; - DraconisError error = DraconisError(DraconisErrorCode::ApiUnavailable, err.message); - dbus_error_free(&err); + Option activePlayer = None; - return Err(error); + { + Result listNamesResult = dbus::MessageNewMethodCall( + "org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "ListNames" + ); + if (!listNamesResult) + return Err(listNamesResult.error()); + + dbus::MessageGuard& listNames = *listNamesResult; + + Result listNamesReplyResult = + dbus::ConnectionSendWithReplyAndBlock(connection, listNames, 100); + + if (!listNamesReplyResult) + return Err(listNamesReplyResult.error()); + + dbus::MessageGuard& listNamesReply = *listNamesReplyResult; + + dbus::MessageIter iter; + + if (dbus::MessageIterInit(listNamesReply, &iter) && dbus::MessageIterGetArgType(&iter) == DBUS_TYPE_ARRAY) { + dbus::MessageIter subIter; + dbus::MessageIterRecurse(&iter, &subIter); + + while (dbus::MessageIterGetArgType(&subIter) != DBUS_TYPE_INVALID) { + if (Option name = dbus::MessageIterGetString(&subIter)) + if (name->find("org.mpris.MediaPlayer2") != String::npos) { + activePlayer = std::move(*name); + break; + } + + dbus::MessageIterNext(&subIter); + } + } else { + return Err(DraconisError(DraconisErrorCode::ParseError, "Invalid DBus ListNames reply format")); } - - Vec mprisPlayers = GetMprisPlayers(connection); - - if (mprisPlayers.empty()) { - dbus_connection_unref(connection); - return Err(DraconisError(DraconisErrorCode::NotFound, "No MPRIS players found")); } - Option activePlayer = GetActivePlayer(mprisPlayers); + if (!activePlayer) + return Err(DraconisError(DraconisErrorCode::NotFound, "No active MPRIS players found")); - if (!activePlayer.has_value()) { - dbus_connection_unref(connection); - return Err(DraconisError(DraconisErrorCode::NotFound, "No active MPRIS player found")); - } - - // Prepare a call to the Properties.Get method to fetch "Metadata" - DBusMessage* msg = dbus_message_new_method_call( - activePlayer->c_str(), // target service (active player) - "/org/mpris/MediaPlayer2", // object path - "org.freedesktop.DBus.Properties", // interface - "Get" // method name + Result msgResult = dbus::MessageNewMethodCall( + activePlayer->c_str(), "/org/mpris/MediaPlayer2", "org.freedesktop.DBus.Properties", "Get" ); - if (!msg) { - dbus_connection_unref(connection); - return Err(DraconisError(DraconisErrorCode::InternalError, "Failed to create DBus message")); - } + if (!msgResult) + return Err(msgResult.error()); - const char* interfaceName = "org.mpris.MediaPlayer2.Player"; - const char* propertyName = "Metadata"; + dbus::MessageGuard& msg = *msgResult; - if (!dbus_message_append_args( - msg, DBUS_TYPE_STRING, &interfaceName, DBUS_TYPE_STRING, &propertyName, DBUS_TYPE_INVALID - )) { - dbus_message_unref(msg); - dbus_connection_unref(connection); + if (!dbus::MessageAppendArgs( + msg, DBUS_TYPE_STRING, "org.mpris.MediaPlayer2.Player", DBUS_TYPE_STRING, "Metadata", DBUS_TYPE_INVALID + )) return Err(DraconisError(DraconisErrorCode::InternalError, "Failed to append arguments to DBus message")); - } - // Call the method and block until reply is received. - DBusMessage* reply = dbus_connection_send_with_reply_and_block(connection, msg, -1, &err); - dbus_message_unref(msg); + Result replyResult = + dbus::ConnectionSendWithReplyAndBlock(connection, msg, 100); - if (dbus_error_is_set(&err)) { - error_log("DBus error in Properties.Get: {}", err.message); + if (!replyResult) + return Err(replyResult.error()); - DraconisError error = DraconisError(DraconisErrorCode::ApiUnavailable, err.message); - dbus_error_free(&err); - dbus_connection_unref(connection); + dbus::MessageGuard& reply = *replyResult; - return Err(error); - } + Option title = None; + Option artist = None; - if (!reply) { - dbus_connection_unref(connection); - return Err(DraconisError(DraconisErrorCode::ApiUnavailable, "No reply received for Properties.Get")); - } + dbus::MessageIter propIter; + if (!dbus::MessageIterInit(reply, &propIter)) + return Err(DraconisError(DraconisErrorCode::ParseError, "Properties.Get reply has no arguments")); - // The reply should contain a variant holding a dictionary ("a{sv}") - DBusMessageIter iter; + if (dbus::MessageIterGetArgType(&propIter) != DBUS_TYPE_VARIANT) + return Err(DraconisError(DraconisErrorCode::ParseError, "Properties.Get reply argument is not a variant")); - if (!dbus_message_iter_init(reply, &iter)) { - dbus_message_unref(reply); - dbus_connection_unref(connection); - return Err(DraconisError(DraconisErrorCode::InternalError, "Reply has no arguments")); - } + dbus::MessageIter variantIter; + dbus::MessageIterRecurse(&propIter, &variantIter); - if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) { - dbus_message_unref(reply); - dbus_connection_unref(connection); - return Err(DraconisError(DraconisErrorCode::InternalError, "Reply argument is not a variant")); - } + if (dbus::MessageIterGetArgType(&variantIter) != DBUS_TYPE_ARRAY || + dbus_message_iter_get_element_type(&variantIter) != DBUS_TYPE_DICT_ENTRY) + return Err( + DraconisError(DraconisErrorCode::ParseError, "Metadata variant content is not a dictionary array (a{sv})") + ); - // Recurse into the variant to get the dictionary - DBusMessageIter variantIter; - dbus_message_iter_recurse(&iter, &variantIter); + dbus::MessageIter dictIter; + dbus::MessageIterRecurse(&variantIter, &dictIter); - if (dbus_message_iter_get_arg_type(&variantIter) != DBUS_TYPE_ARRAY) { - dbus_message_unref(reply); - dbus_connection_unref(connection); - return Err(DraconisError(DraconisErrorCode::InternalError, "Variant argument is not an array")); - } + while (dbus::MessageIterGetArgType(&dictIter) == DBUS_TYPE_DICT_ENTRY) { + dbus::MessageIter entryIter; + dbus::MessageIterRecurse(&dictIter, &entryIter); - String title; - String artist; - DBusMessageIter arrayIter; - dbus_message_iter_recurse(&variantIter, &arrayIter); + Option key = dbus::MessageIterGetString(&entryIter); - // Iterate over each dictionary entry (each entry is of type dict entry) - while (dbus_message_iter_get_arg_type(&arrayIter) != DBUS_TYPE_INVALID) { - if (dbus_message_iter_get_arg_type(&arrayIter) == DBUS_TYPE_DICT_ENTRY) { - DBusMessageIter dictEntry; - dbus_message_iter_recurse(&arrayIter, &dictEntry); - - // Get the key (a string) - const char* key = nullptr; - - if (dbus_message_iter_get_arg_type(&dictEntry) == DBUS_TYPE_STRING) - dbus_message_iter_get_basic(&dictEntry, static_cast(&key)); - - // Move to the value (a variant) - dbus_message_iter_next(&dictEntry); - - if (dbus_message_iter_get_arg_type(&dictEntry) == DBUS_TYPE_VARIANT) { - DBusMessageIter valueIter; - dbus_message_iter_recurse(&dictEntry, &valueIter); - - if (key && std::string_view(key) == "xesam:title") { - if (dbus_message_iter_get_arg_type(&valueIter) == DBUS_TYPE_STRING) { - const char* val = nullptr; - dbus_message_iter_get_basic(&valueIter, static_cast(&val)); - - if (val) - title = val; - } - } else if (key && std::string_view(key) == "xesam:artist") { - // Expect an array of strings - if (dbus_message_iter_get_arg_type(&valueIter) == DBUS_TYPE_ARRAY) { - DBusMessageIter subIter; - dbus_message_iter_recurse(&valueIter, &subIter); - - if (dbus_message_iter_get_arg_type(&subIter) == DBUS_TYPE_STRING) { - const char* val = nullptr; - dbus_message_iter_get_basic(&subIter, static_cast(&val)); - - if (val) - artist = val; - } - } - } - } + if (!key) { + dbus::MessageIterNext(&dictIter); + continue; } - dbus_message_iter_next(&arrayIter); + if (!dbus::MessageIterNext(&entryIter) || dbus::MessageIterGetArgType(&entryIter) != DBUS_TYPE_VARIANT) { + dbus::MessageIterNext(&dictIter); + continue; + } + + dbus::MessageIter valueVariantIter; + dbus::MessageIterRecurse(&entryIter, &valueVariantIter); + + if (*key == "xesam:title") + title = dbus::MessageIterGetString(&valueVariantIter); + else if (*key == "xesam:artist") + if (dbus::MessageIterGetArgType(&valueVariantIter) == DBUS_TYPE_ARRAY && + dbus_message_iter_get_element_type(&valueVariantIter) == DBUS_TYPE_STRING) { + dbus::MessageIter artistArrayIter; + dbus::MessageIterRecurse(&valueVariantIter, &artistArrayIter); + artist = dbus::MessageIterGetString(&artistArrayIter); + } + + dbus::MessageIterNext(&dictIter); } - dbus_message_unref(reply); - dbus_connection_unref(connection); - - return MediaInfo(artist, title); + return MediaInfo(std::move(title), std::move(artist)); } - #pragma clang diagnostic pop fn GetWindowManager() -> Option { if (Result waylandResult = GetWaylandCompositor()) diff --git a/src/os/linux/pkg_count.cpp b/src/os/linux/pkg_count.cpp index e1340d0..e500b34 100644 --- a/src/os/linux/pkg_count.cpp +++ b/src/os/linux/pkg_count.cpp @@ -1,3 +1,6 @@ +#ifdef __linux__ + +// clang-format off #include "src/os/linux/pkg_count.hpp" #include @@ -9,6 +12,7 @@ #include "src/core/util/logging.hpp" #include "src/core/util/types.hpp" +// clang-format on using util::error::DraconisError, util::error::DraconisErrorCode; using util::types::u64, util::types::i64, util::types::Result, util::types::Err, util::types::String, @@ -199,10 +203,9 @@ namespace { cacheTimePoint.time_since_epoch() >= dbModTime.time_since_epoch()) { // Use cacheTimePoint for logging as well debug_log( - "Using valid {} package count cache (DB file unchanged since {}). Count: {}", + "Using valid {} package count cache (DB file unchanged since {}).", pmId, - std::format("{:%F %T %Z}", floor(cacheTimePoint)), // Format the time_point - count + std::format("{:%F %T %Z}", floor(cacheTimePoint)) ); return count; } @@ -337,4 +340,6 @@ namespace os::linux { return totalCount; } -} // namespace os::linux \ No newline at end of file +} // namespace os::linux + +#endif // __linux__ diff --git a/src/os/linux/pkg_count.hpp b/src/os/linux/pkg_count.hpp index 0a850b4..8e11879 100644 --- a/src/os/linux/pkg_count.hpp +++ b/src/os/linux/pkg_count.hpp @@ -1,10 +1,14 @@ #pragma once +#ifdef __linux__ + +// clang-format off #include #include "src/core/util/defs.hpp" #include "src/core/util/error.hpp" #include "src/core/util/types.hpp" +// clang-format on namespace os::linux { using util::error::DraconisError; @@ -51,4 +55,6 @@ namespace os::linux { // Get total package count from all available package managers fn GetTotalPackageCount() -> Result; -} // namespace os::linux \ No newline at end of file +} // namespace os::linux + +#endif // __linux__ diff --git a/src/wrappers/dbus.hpp b/src/wrappers/dbus.hpp new file mode 100644 index 0000000..f04c076 --- /dev/null +++ b/src/wrappers/dbus.hpp @@ -0,0 +1,282 @@ +#pragma once + +#ifdef __linux__ + +// clang-format off +#include // DBus Library +#include // std::exchange +#include // std::format +#include // va_list, va_start, va_end + +#include "src/core/util/defs.hpp" +#include "src/core/util/error.hpp" +#include "src/core/util/types.hpp" +// clang-format on + +namespace dbus { + using util::error::DraconisError, util::error::DraconisErrorCode; + using util::types::Option, util::types::Result, util::types::Err, util::types::String, util::types::i32, + util::types::None; + + /** + * @brief RAII wrapper for DBusError. Automatically initializes and frees. + */ + class ErrorGuard { + DBusError m_Err {}; + bool m_IsInitialized = false; + + public: + ErrorGuard() : m_IsInitialized(true) { dbus_error_init(&m_Err); } + + ~ErrorGuard() { + if (m_IsInitialized) + dbus_error_free(&m_Err); + } + + ErrorGuard(const ErrorGuard&) = delete; + fn operator=(const ErrorGuard&)->ErrorGuard& = delete; + + ErrorGuard(ErrorGuard&& other) noexcept : m_Err(other.m_Err), m_IsInitialized(other.m_IsInitialized) { + other.m_IsInitialized = false; + dbus_error_init(&other.m_Err); + } + + fn operator=(ErrorGuard&& other) noexcept -> ErrorGuard& { + 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; + } + + [[nodiscard]] fn isSet() const -> bool { return m_IsInitialized && dbus_error_is_set(&m_Err); } + + [[nodiscard]] fn message() const -> const char* { return isSet() ? m_Err.message : ""; } + + [[nodiscard]] fn name() const -> const char* { return isSet() ? m_Err.name : ""; } + + [[nodiscard]] fn get() -> DBusError* { return &m_Err; } + [[nodiscard]] fn get() const -> const DBusError* { return &m_Err; } + + [[nodiscard]] fn toDraconisError(const DraconisErrorCode code = DraconisErrorCode::PlatformSpecific) const + -> DraconisError { + if (isSet()) + return { code, std::format("D-Bus Error: {} ({})", message(), name()) }; + + return { DraconisErrorCode::InternalError, "Attempted to convert non-set DBusErrorGuard" }; + } + }; + + /** + * @brief RAII wrapper for DBusConnection. Automatically unrefs. + */ + class ConnectionGuard { + DBusConnection* m_Conn = nullptr; + + public: + explicit ConnectionGuard(DBusConnection* conn = nullptr) : m_Conn(conn) {} + + ~ConnectionGuard() { + if (m_Conn) + dbus_connection_unref(m_Conn); + } + + ConnectionGuard(const ConnectionGuard&) = delete; + fn operator=(const ConnectionGuard&)->ConnectionGuard& = delete; + + ConnectionGuard(ConnectionGuard&& other) noexcept : m_Conn(std::exchange(other.m_Conn, nullptr)) {} + fn operator=(ConnectionGuard&& other) noexcept -> ConnectionGuard& { + if (this != &other) { + if (m_Conn) + dbus_connection_unref(m_Conn); + m_Conn = std::exchange(other.m_Conn, nullptr); + } + + return *this; + } + + [[nodiscard]] fn get() const -> DBusConnection* { return m_Conn; } + explicit operator bool() const { return m_Conn != nullptr; } + + [[nodiscard]] fn release() -> DBusConnection* { return std::exchange(m_Conn, nullptr); } + }; + + /** + * @brief RAII wrapper for DBusMessage. Automatically unrefs. + */ + class MessageGuard { + DBusMessage* m_Msg = nullptr; + + public: + explicit MessageGuard(DBusMessage* msg = nullptr) : m_Msg(msg) {} + + ~MessageGuard() { + if (m_Msg) + dbus_message_unref(m_Msg); + } + + MessageGuard(const MessageGuard&) = delete; + fn operator=(const MessageGuard&)->MessageGuard& = delete; + + MessageGuard(MessageGuard&& other) noexcept : m_Msg(std::exchange(other.m_Msg, nullptr)) {} + fn operator=(MessageGuard&& other) noexcept -> MessageGuard& { + if (this != &other) { + if (m_Msg) + dbus_message_unref(m_Msg); + m_Msg = std::exchange(other.m_Msg, nullptr); + } + + return *this; + } + + [[nodiscard]] fn get() const -> DBusMessage* { return m_Msg; } + explicit operator bool() const { return m_Msg != nullptr; } + + [[nodiscard]] fn release() -> DBusMessage* { return std::exchange(m_Msg, nullptr); } + }; + + /** + * @brief Connects to a D-Bus bus type. + * @param bus_type The type of bus (e.g., DBUS_BUS_SESSION, DBUS_BUS_SYSTEM). + * @return Result containing a DBusConnectionGuard on success, or DraconisError on failure. + */ + inline fn BusGet(const DBusBusType bus_type) -> Result { + ErrorGuard err; + DBusConnection* rawConn = dbus_bus_get(bus_type, err.get()); + + if (err.isSet()) + return Err(err.toDraconisError(DraconisErrorCode::ApiUnavailable)); + + if (!rawConn) + return Err(DraconisError(DraconisErrorCode::ApiUnavailable, "dbus_bus_get returned null without setting error")); + + return ConnectionGuard(rawConn); + } + + /** + * @brief Creates a new D-Bus method call message. + * @param destination Service name (e.g., "org.freedesktop.Notifications"). + * @param path Object path (e.g., "/org/freedesktop/Notifications"). + * @param interface Interface name (e.g., "org.freedesktop.Notifications"). + * @param method Method name (e.g., "Notify"). + * @return Result containing a DBusMessageGuard on success, or DraconisError on failure. + */ + inline fn MessageNewMethodCall(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(DraconisError(DraconisErrorCode::InternalError, "dbus_message_new_method_call failed (allocation?)")); + + return MessageGuard(rawMsg); + } + + /** + * @brief Sends a message and waits for a reply. + * @param connection The D-Bus connection guard. + * @param message The D-Bus message guard to send. + * @param timeout_milliseconds Timeout duration. + * @return Result containing the reply DBusMessageGuard on success, or DraconisError on failure. + */ + inline fn ConnectionSendWithReplyAndBlock( + const ConnectionGuard& connection, + const MessageGuard& message, + const i32 timeout_milliseconds = 100 + ) -> Result { + ErrorGuard err; + DBusMessage* rawReply = + dbus_connection_send_with_reply_and_block(connection.get(), message.get(), timeout_milliseconds, err.get()); + + if (err.isSet()) + return Err(err.toDraconisError(DraconisErrorCode::ApiUnavailable)); + + if (!rawReply) + return Err(DraconisError( + DraconisErrorCode::ApiUnavailable, + "dbus_connection_send_with_reply_and_block returned null without setting error" + )); + + return MessageGuard(rawReply); + } + + /** + * @brief Appends arguments to a D-Bus message using varargs. + * @param message The message guard. + * @param first_arg_type The D-Bus type code of the first argument. + * @param ... Subsequent arguments (type code, value pointer, type code, value pointer...). + * End with DBUS_TYPE_INVALID. + * @return True on success, false on failure (e.g., allocation error). + */ + inline fn MessageAppendArgs(const MessageGuard& message, const int first_arg_type, ...) -> bool { + va_list args; + va_start(args, first_arg_type); + const bool result = dbus_message_append_args_valist(message.get(), first_arg_type, args); + va_end(args); + return result; + } + + using MessageIter = DBusMessageIter; + + /** + * @brief Initializes a message iterator. + * @param message The message guard. + * @param iter Pointer to the iterator to initialize. + * @return True if iterator is valid, false otherwise. + */ + inline fn MessageIterInit(const MessageGuard& message, MessageIter* iter) -> bool { + return dbus_message_iter_init(message.get(), iter); + } + + /** + * @brief Recurses into a container-type argument. + * @param iter The current iterator. + * @param sub_iter Pointer to the sub-iterator to initialize. + */ + inline fn MessageIterRecurse(MessageIter* iter, MessageIter* sub_iter) -> void { + dbus_message_iter_recurse(iter, sub_iter); + } + + /** + * @brief Gets the D-Bus type code of the current argument. + * @param iter The iterator. + * @return The type code (e.g., DBUS_TYPE_STRING, DBUS_TYPE_INVALID). + */ + inline fn MessageIterGetArgType(MessageIter* iter) -> int { return dbus_message_iter_get_arg_type(iter); } + + /** + * @brief Gets the value of a basic-typed argument. + * @param iter The iterator. + * @param value Pointer to store the retrieved value. Must match the argument type. + */ + inline fn MessageIterGetBasic(MessageIter* iter, void* value) -> void { dbus_message_iter_get_basic(iter, value); } + + /** + * @brief Advances the iterator to the next argument. + * @param iter The iterator. + * @return True if successful, false if at the end. + */ + inline fn MessageIterNext(MessageIter* iter) -> bool { return dbus_message_iter_next(iter); } + + /** + * @brief Helper to safely get a string argument from an iterator. + * @param iter The iterator positioned at a DBUS_TYPE_STRING argument. + * @return An Option containing the string value, or None if the type is wrong or value is null. + */ + inline fn MessageIterGetString(MessageIter* iter) -> Option { + if (MessageIterGetArgType(iter) == DBUS_TYPE_STRING) { + const char* strPtr = nullptr; + MessageIterGetBasic(iter, static_cast(&strPtr)); + if (strPtr) + return String(strPtr); + } + + return None; + } +} // namespace dbus + +#endif // __linux__ diff --git a/src/wrappers/wayland.hpp b/src/wrappers/wayland.hpp index d3b1f46..500f1e2 100644 --- a/src/wrappers/wayland.hpp +++ b/src/wrappers/wayland.hpp @@ -1,9 +1,13 @@ #pragma once -#include +#ifdef __linux__ + +// clang-format off +#include // Wayland client library #include "src/core/util/defs.hpp" #include "src/core/util/types.hpp" +// clang-format on struct wl_display; @@ -56,3 +60,5 @@ namespace wl { [[nodiscard]] fn fd() const -> util::types::i32 { return get_fd(m_Display); } }; } // namespace wl + +#endif // __linux__ diff --git a/src/wrappers/xcb.hpp b/src/wrappers/xcb.hpp index 5af09b7..ae3ffcd 100644 --- a/src/wrappers/xcb.hpp +++ b/src/wrappers/xcb.hpp @@ -1,9 +1,13 @@ #pragma once -#include +#ifdef __linux__ + +// clang-format off +#include // XCB library #include "src/core/util/defs.hpp" #include "src/core/util/types.hpp" +// clang-format on namespace xcb { using util::types::u8, util::types::i32, util::types::CStr, util::types::None; @@ -166,3 +170,5 @@ namespace xcb { [[nodiscard]] fn operator*() const->T& { return *m_Reply; } }; } // namespace xcb + +#endif // __linux__