AUI Framework  develop
Cross-platform base for C++ UI apps
Loading...
Searching...
No Matches
ADBus.h
    1/*
    2 * AUI Framework - Declarative UI toolkit for modern C++20
    3 * Copyright (C) 2020-2025 Alex2772 and Contributors
    4 *
    5 * SPDX-License-Identifier: MPL-2.0
    6 *
    7 * This Source Code Form is subject to the terms of the Mozilla Public
    8 * License, v. 2.0. If a copy of the MPL was not distributed with this
    9 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
   10 */
   11
   12#pragma once
   13
   14#include <AUI/Common/AObject.h>
   15#include <AUI/Traits/concepts.h>
   16#include <AUI/Util/APimpl.h>
   17#include <AUI/Util/ARaiiHelper.h>
   18#include "AUI/Common/AException.h"
   19#include <dbus/dbus.h>
   20#include <list>
   21
   22struct DBusError;
   23struct DBusConnection;
   24
   25namespace aui::dbus {
   26    class ObjectPath: public std::string {
   27        using std::string::string;
   28    };
   29
   30    template<typename T>
   31    struct converter;
   32
   33    template<typename T>
   34    concept convertible = requires(T t) {
   35        { converter<T>::iter_append(static_cast<DBusMessageIter*>(nullptr), t) };
   37    };
   38
   39    struct Unknown {
   40    public:
   41        explicit Unknown(const DBusMessageIter& mIter) : mIter(mIter) {}
   42
   43        template<convertible T>
   44        T as();
   45
   46        template<convertible T>
   47        bool as(T& v);
   48
   49    private:
   50        DBusMessageIter mIter;
   51    };
   52
   53    using VariantImpl = std::variant<
   54            std::nullopt_t,         // DBUS_TYPE_INVALID
   55            std::uint8_t,           // DBUS_TYPE_BYTE
   56            bool,                   // DBUS_TYPE_BOOLEAN
   57            std::int16_t,           // DBUS_TYPE_INT16
   58            std::uint16_t,          // DBUS_TYPE_UINT16
   59            std::int32_t,           // DBUS_TYPE_INT32
   60            std::uint32_t,          // DBUS_TYPE_UINT32
   61            std::int64_t,           // DBUS_TYPE_INT64
   62            std::uint64_t,          // DBUS_TYPE_UINT64
   63            double,                 // DBUS_TYPE_DOUBLE
   64            std::string,            // DBUS_TYPE_STRING
   65            ObjectPath,             // DBUS_TYPE_OBJECT_PATH
   66            AVector<Unknown>,       // DBUS_TYPE_ARRAY
   67            AVector<std::uint8_t>   // DBUS_TYPE_ARRAY
   68    >;
   69    struct Variant: VariantImpl {
   70        using VariantImpl::variant;
   71        Variant(): VariantImpl(std::nullopt) {}
   72    };
   73
   74    template<typename T>
   75    concept convertible_or_void = convertible<T> || std::is_void_v<T>;
   76
   77
   78    template<convertible T>
   79    void iter_append(DBusMessageIter* iter, const T& value) {
   80        converter<T>::iter_append(iter, value);
   81    }
   82    template<convertible T>
   83    T iter_get(DBusMessageIter* iter) {
   84        return converter<T>::iter_get(iter);
   85    }
   86
   87    template<convertible T>
   88    T Unknown::as() {
   89        return aui::dbus::iter_get<T>(&mIter);
   90    }
   91
   92    template<convertible T>
   93    bool Unknown::as(T& v) {
   94        if (auto got = dbus_message_iter_get_arg_type(&mIter); got != converter<T>::signature[0]) {
   95            return false;
   96        }
   97
   98        v = aui::dbus::iter_get<T>(&mIter);
   99        return true;
  100    }
  101
  102
  103    namespace impl {
  104        template<typename T, char dbusType = DBUS_TYPE_INVALID>
  105        struct basic_converter {
  106            static inline std::string signature = fmt::format("{}", dbusType);
  107
  108            static void iter_append(DBusMessageIter* iter, const T& t) {
  109                dbus_message_iter_append_basic(iter, dbusType, &t);
  110            }
  111            static T iter_get(DBusMessageIter* iter) {
  112                if (auto got = dbus_message_iter_get_arg_type(iter); got != dbusType) {
  113                    throw AException("type error: expected '{:c}', got '{:c}'"_format(dbusType, got));
  114                }
  115                T t;
  116                dbus_message_iter_get_basic(iter, &t);
  117                return t;
  118            }
  119        };
  120    }
  121
  122    template<> struct converter<std::nullopt_t> { // -> DBUS_TYPE_INVALID
  123        static inline std::string signature = DBUS_TYPE_INVALID_AS_STRING;
  124
  125        static void iter_append(DBusMessageIter* iter, std::nullopt_t) {
  126            int v = 0;
  127            dbus_message_iter_append_basic(iter, DBUS_TYPE_INVALID, &v);
  128        }
  129
  130        static auto iter_get(DBusMessageIter* iter) {
  131            if (auto got = dbus_message_iter_get_arg_type(iter); got != DBUS_TYPE_INVALID) {
  132                throw AException("type error: expected '{:c}', got '{:c}'"_format(DBUS_TYPE_INVALID, got));
  133            }
  134            return std::nullopt;
  135        }
  136    };
  137
  138    template<> struct converter<std::uint8_t  >: impl::basic_converter<std::uint8_t  , DBUS_TYPE_BYTE> {};
  139    template<> struct converter<std::int16_t  >: impl::basic_converter<std::int16_t  , DBUS_TYPE_INT16> {};
  140    template<> struct converter<std::uint16_t >: impl::basic_converter<std::uint16_t , DBUS_TYPE_UINT16> {};
  141    template<> struct converter<std::int32_t  >: impl::basic_converter<std::int32_t  , DBUS_TYPE_INT32> {};
  142    template<> struct converter<std::uint32_t >: impl::basic_converter<std::uint32_t , DBUS_TYPE_UINT32> {};
  143    template<> struct converter<std::int64_t  >: impl::basic_converter<std::int64_t  , DBUS_TYPE_INT64> {};
  144    template<> struct converter<std::uint64_t >: impl::basic_converter<std::uint64_t , DBUS_TYPE_UINT64> {};
  145    template<> struct converter<double        >: impl::basic_converter<double        , DBUS_TYPE_DOUBLE> {};
  146    template<> struct converter<const char*   >: impl::basic_converter<const char*   , DBUS_TYPE_STRING> {};
  147    template<> struct converter<bool> {
  148        static inline std::string signature = DBUS_TYPE_BOOLEAN_AS_STRING;
  149
  150        static void iter_append(DBusMessageIter* iter, const bool& t) {
  151            impl::basic_converter<std::int32_t, DBUS_TYPE_BOOLEAN>::iter_append(iter, t ? 1 : 0);
  152        }
  153        static bool iter_get(DBusMessageIter* iter) {
  154            return impl::basic_converter<std::int32_t, DBUS_TYPE_BOOLEAN>::iter_get(iter);
  155        }
  156    };
  157
  158    template<> struct converter<std::string>: converter<const char*> {
  159        static void iter_append(DBusMessageIter* iter, const std::string& t) {
  160            converter<const char*>::iter_append(iter, t.c_str());
  161        }
  162        static std::string iter_get(DBusMessageIter* iter) {
  163            return converter<const char*>::iter_get(iter);
  164        }
  165    };
  166
  167    template<> struct converter<AString>: converter<std::string> {
  168        static void iter_append(DBusMessageIter* iter, const AString& t) {
  169            converter<std::string>::iter_append(iter, t.toStdString());
  170        }
  171        static AString iter_get(DBusMessageIter* iter) {
  172            return converter<const char*>::iter_get(iter);
  173        }
  174    };
  175    template<> struct converter<ObjectPath>: impl::basic_converter<const char*, DBUS_TYPE_OBJECT_PATH> {
  176        using super = impl::basic_converter<const char*, DBUS_TYPE_OBJECT_PATH>;
  177        static void iter_append(DBusMessageIter* iter, const ObjectPath& t) {
  178            super::iter_append(iter, t.c_str());
  179        }
  180
  181        static ObjectPath iter_get(DBusMessageIter* iter) {
  182            return super::iter_get(iter);
  183        }
  184    };
  185    template<std::size_t N> struct converter<char[N]>: converter<const char*> {};
  186
  187
  188    template<convertible T>
  189    struct converter<AVector<T>> {
  190        static inline std::string signature = fmt::format("a{}", converter<T>::signature);
  191
  192        static void iter_append(DBusMessageIter* iter, const AVector<T>& t) {
  193            DBusMessageIter sub;
  194            const auto s = converter<T>::signature.c_str();
  195            if (!dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, s, &sub)) {
  196                throw AException("dbus_message_iter_open_container failed");
  197            }
  198            ARaiiHelper h = [&] {
  199                dbus_message_iter_close_container(iter, &sub);
  200            };
  201
  202            for (const auto& v : t) {
  203                aui::dbus::iter_append(&sub, v);
  204            }
  205        }
  206        static AVector<T> iter_get(DBusMessageIter* iter) {
  207            if (auto got = dbus_message_iter_get_arg_type(iter); got != DBUS_TYPE_ARRAY) {
  208                throw AException("type error: array expected, got '{:c}'"_format(got));
  209            }
  210            DBusMessageIter sub;
  211            dbus_message_iter_recurse(iter, &sub);
  212
  213            AVector<T> result;
  214            for (;;) {
  215                result << aui::dbus::iter_get<T>(&sub);
  216                if (!dbus_message_iter_next(&sub)) break;
  217            }
  218
  219            return result;
  220        }
  221    };
  222
  223    template<convertible K, convertible V>
  224    struct converter<AMap<K, V>> {
  225        static inline std::string signature = fmt::format("a{{{}{}}}", converter<K>::signature, converter<V>::signature);
  226
  227        static void iter_append(DBusMessageIter* iter, const AMap<K, V>& t) {
  228            DBusMessageIter sub;
  229            if (!dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, fmt::format("{{{}{}}}", converter<K>::signature, converter<V>::signature).c_str(), &sub)) {
  230                throw AException("dbus_message_iter_open_container failed");
  231            }
  232            ARaiiHelper h = [&] {
  233                dbus_message_iter_close_container(iter, &sub);
  234            };
  235
  236            for (const auto&[k, v] : t) {
  237                DBusMessageIter item;
  238                dbus_message_iter_open_container(&sub, DBUS_TYPE_DICT_ENTRY, nullptr, &item);
  239                ARaiiHelper r = [&] {
  240                    dbus_message_iter_close_container(&sub, &item);
  241                };
  242
  243                aui::dbus::iter_append(&item, k);
  244                aui::dbus::iter_append(&item, v);
  245            }
  246        }
  247        static AMap<K, V> iter_get(DBusMessageIter* iter) {
  248            if (auto got = dbus_message_iter_get_arg_type(iter); got != DBUS_TYPE_ARRAY) {
  249                throw AException("type error: array expected, got '{:c}'"_format(got));
  250            }
  251            DBusMessageIter sub;
  252            dbus_message_iter_recurse(iter, &sub);
  253
  254            AMap<K, V> result;
  255            for (;;) {
  256                AUI_ASSERT(dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_DICT_ENTRY);
  257                DBusMessageIter item;
  258                dbus_message_iter_recurse(&sub, &item);
  259                auto k = aui::dbus::iter_get<K>(&item);
  260                if (!dbus_message_iter_next(&item)) {
  261                    throw AException("bad dict");
  262                }
  263                auto v = aui::dbus::iter_get<V>(&item);
  264                result[k] = v;
  265                if (!dbus_message_iter_next(&sub)) break;
  266            }
  267
  268            return result;
  269        }
  270    };
  271
  272    template<convertible... Types>
  273    struct converter<std::tuple<Types...>> {
  274        static inline std::string signature = "(" + (... + converter<Types>::signature) + ")";
  275
  276        static void iter_append(DBusMessageIter* iter, const std::tuple<Types...>& t) {
  277            if (auto got = dbus_message_iter_get_arg_type(iter); got != DBUS_TYPE_STRUCT) {
  278                throw AException("type error: struct expected, got '{:c}'"_format(got));
  279            }
  280
  281            DBusMessageIter sub;
  282            dbus_message_iter_recurse(iter, &sub);
  283
  284            std::apply([&](const auto&... args){
  285                (..., aui::dbus::iter_append(&sub, args));
  286            }, t);
  287        }
  288
  289        static std::tuple<Types...> iter_get(DBusMessageIter* iter) {
  290            if (auto got = dbus_message_iter_get_arg_type(iter); got != DBUS_TYPE_STRUCT) {
  291                throw AException("type error: struct expected, got '{:c}'"_format(got));
  292            }
  293
  294            DBusMessageIter sub;
  295            dbus_message_iter_recurse(iter, &sub);
  296
  297            bool hasNext = true;
  298            return aui::tuple_visitor<std::tuple<Types...>>::for_each_make_tuple([&]<typename T>() {
  299                if (!hasNext) {
  300                    throw AException("too few arguments");
  301                }
  302                auto v = aui::dbus::iter_get<T>(&sub);
  303                hasNext = dbus_message_iter_next(&sub);
  304                return v;
  305            });
  306        }
  307    };
  308
  309    template<>
  310    struct API_AUI_VIEWS converter<Variant> {
  311        static inline std::string signature = "v";
  312
  313        static void iter_append(DBusMessageIter* iter, const Variant& t);
  314
  315        static Variant iter_get(DBusMessageIter* iter);
  316    };
  317
  318    template<>
  319    struct API_AUI_VIEWS converter<Unknown> {
  320        static inline std::string signature = "";
  321
  322        static void iter_append(DBusMessageIter* iter, const Unknown& t) {
  323            // TODO
  324            AUI_ASSERT(0);
  325        }
  326
  327        static Unknown iter_get(DBusMessageIter* iter);
  328    };
  329}
  330
  335class API_AUI_VIEWS ADBus: public aui::noncopyable {
  336public:
  337
  341    class Exception: public AException {
  342        using AException::AException;
  343    };
  344
  345    static ADBus& inst();
  346
  347    template<aui::dbus::convertible_or_void Return = void, aui::dbus::convertible... Args>
  348    Return callBlocking(const AString& bus,
  349                        const AString& path,
  350                        const AString& interface,
  351                        const AString& method,
  352                        const Args&... args) {
  353        auto msg = aui::ptr::make_unique_with_deleter(dbus_message_new_method_call(bus.toStdString().c_str(),
  354                                                                                   path.toStdString().c_str(),
  355                                                                                   interface.toStdString().c_str(),
  356                                                                                   method.toStdString().c_str()),
  357                                                      dbus_message_unref);
  358
  359        auto formatError = [&](const AString& info) {
  360            return "unable to invoke {};{};{};{}: {}"_format(bus, path, interface, method, info);
  361        };
  362
  363        if (!msg) {
  364            throw ADBus::Exception(formatError("message is null"));
  365        }
  366
  367        DBusMessageIter dbusArgs;
  368        dbus_message_iter_init_append(msg.get(), &dbusArgs);
  369
  370        aui::parameter_pack::for_each([&](const auto& v) {
  371            aui::dbus::iter_append(&dbusArgs, v);
  372        }, args...);
  373
  374        auto pending = aui::ptr::make_unique_with_deleter([&] {
  375            DBusPendingCall* pending;
  376            if (!dbus_connection_send_with_reply(mConnection, msg.get(), &pending, -1)) { // -1 is default timeout
  377                throw ADBus::Exception(formatError("dbus_connection_send_with_reply failed"));
  378            }
  379            return pending;
  380        }(), dbus_pending_call_unref);
  381
  382        // block until we receive a reply
  383        dbus_pending_call_block(pending.get());
  384
  385        // get the reply message
  386        msg.reset(dbus_pending_call_steal_reply(pending.get()));
  387
  388        if (dbus_message_get_type(msg.get()) == DBUS_MESSAGE_TYPE_ERROR) {
  389            if (auto e = dbus_message_get_error_name(msg.get())) {
  390                throw ADBus::Exception(formatError(e));
  391            }
  392            throw ADBus::Exception(formatError("dbus replied unknown error"));
  393        }
  394
  395        if constexpr (!std::is_void_v<Return>) {
  396            if (!dbus_message_iter_init(msg.get(), &dbusArgs)) {
  397                throw ADBus::Exception(formatError("dbus replied no arguments"));
  398            }
  399            return aui::dbus::iter_get<Return>(&dbusArgs);
  400        }
  401    }
  402
  403    template<aui::not_overloaded_lambda Callback>
  404    std::function<void()> addSignalListener(aui::dbus::ObjectPath object, const AString& interface, const AString& signal, Callback&& callback) {
  405        return addListener([object = std::move(object),
  406                     interface = interface.toStdString(),
  407                     signal = signal.toStdString(),
  408                     callback = std::forward<Callback>(callback)](DBusMessage* msg) {
  409            if (dbus_message_is_signal(msg, DBUS_INTERFACE_LOCAL, "Disconnected")) {
  410                return DBUS_HANDLER_RESULT_HANDLED;
  411            }
  412
  413            if (!dbus_message_is_signal(msg, interface.c_str(), signal.c_str())) {
  414                return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
  415            }
  416
  417            if (object != std::string_view(dbus_message_get_path(msg))) {
  418                return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
  419            }
  420
  421            using argz = typename aui::lambda_info<Callback>::args;
  422            if constexpr (std::tuple_size_v<argz> > 0) {
  423                DBusMessageIter dbusArgs;
  424                if (!dbus_message_iter_init(msg, &dbusArgs)) {
  425                    throw ADBus::Exception("dbus replied no arguments");
  426                }
  427
  428                bool hasNext = true;
  429                auto args = aui::tuple_visitor<argz>::for_each_make_tuple([&]<typename T>() {
  430                    if (!hasNext) {
  431                        throw ADBus::Exception("too few arguments");
  432                    }
  433                    auto v = aui::dbus::iter_get<T>(&dbusArgs);
  434                    hasNext = dbus_message_iter_next(&dbusArgs);
  435                    return v;
  436                });
  437
  438                std::apply(callback, std::move(args));
  439            } else {
  440                callback();
  441            }
  442
  443
  444            return DBUS_HANDLER_RESULT_HANDLED;
  445        });
  446    }
  447
  448    void processMessages();
  449
  450private:
  451    struct RawMessageListener {
  452        ADBus* parent;
  453        using Callback = std::function<DBusHandlerResult(DBusMessage* message)>;
  454        Callback function;
  455    };
  456    std::list<RawMessageListener> mListeners; // guarantees safe pointers to it's elements
  457    aui::fast_pimpl<DBusError, sizeof(void*) * 3 + 20, alignof(void*)> mError;
  458    DBusConnection* mConnection = nullptr;
  459    std::atomic_bool mProcessingScheduled = false;
  460
  461    ADBus();
  462    ~ADBus();
  463
  464    template <aui::invocable Callback>
  465    void throwExceptionOnError(Callback&& callback);
  466    std::function<void()> addListener(RawMessageListener::Callback listener);
  467    static dbus_bool_t addWatch(DBusWatch* watch, void* data);
  468
  469    static DBusHandlerResult listener(DBusConnection     *connection,
  470                                      DBusMessage        *message,
  471                                      void               *user_data) noexcept;
  472    static void deleter(void* userData) noexcept;
  473};
Exception thrown on dbus errors.
Definition ADBus.h:341
Abstract AUI exception.
Definition AException.h:28
A std::map with AUI extensions.
Definition AMap.h:218
Definition ARaiiHelper.h:17
Represents a Unicode character string.
Definition AString.h:38
std::string toStdString() const noexcept
A std::vector with AUI extensions.
Definition AVector.h:39
Definition ADBus.h:26
Definition concepts.h:42
Definition ADBus.h:75
Definition ADBus.h:34
API_AUI_CORE const ACommandLineArgs & args() noexcept
#define AUI_ASSERT(condition)
Asserts that the passed condition evaluates to true.
Definition Assert.h:55
Definition ADBus.h:39
Definition ADBus.h:69
Definition ADBus.h:31
Definition ADBus.h:105
Forbids copy of your class.
Definition values.h:45
static _unique< T, Deleter > make_unique_with_deleter(T *ptr, Deleter deleter=Deleter{})
Creates unique_ptr from raw pointer and a deleter.
Definition SharedPtrTypes.h:126
Definition parameter_pack.h:76