QXmpp::Hooks Class

class QXmpp::Hooks

Registers handlers on hooks and runs hooks. More...

Header: #include <QXmppHooks.h>
Since: QXmpp 1.18

Public Functions

Hooks(QXmpp::Private::HookRegistry *registry, QObject *owner)
void add(Handler &&handler, int sequence = HookSequence::Default) const
void add(Object *object, Method method, int sequence = HookSequence::Default) const
void add(const typename Hook::Key &key, Handler &&handler, int sequence = HookSequence::Default) const
void add(const typename Hook::Key &key, Object *object, Method method, int sequence = HookSequence::Default) const
bool hasHandlers() const
bool hasHandlers(const typename Hook::Key &key) const
void removeAll() const
auto run(Args &&... args) const
auto run(const typename Hook::Key &key, Args &&... args) const

Detailed Description

Hooks connect extensions without requiring a common base class or virtual functions in public headers. Any extension (or the application) can define a hook by declaring a type. Other extensions add handlers to it using the hook's type. Nothing needs to be declared in QXmpp itself and handlers can be added before the extension defining the hook is added to the client.

A hook is defined by a type with a static Name and a Signature. The name identifies the hook, the signature is the type of the handlers. The return type of the signature decides how the handlers are run:

  • void: all handlers are run.
  • QXmpp::HookResult: the handlers are run until one returns QXmpp::HookResult::Stop.
  • Any other type: the hook has a single responder. Only the first handler is run and run() returns its result as std::optional, std::nullopt if there is no handler. Adding another handler logs a warning, it is still added and the sequence decides which one is used, e.g. an application can replace a handler of QXmpp with HookSequence::Early.

Hooks whose signature returns a QXmppTask are asynchronous: a handler can wait for a result (e.g. a database query) before the next handler is run. Handlers of asynchronous hooks may also return the value directly. If the owner of the single responder of an asynchronous hook is destroyed or its task is dropped, the result is std::nullopt; the next handler is not run instead.

Names use a reverse domain prefix, so hooks of different projects don't collide. Hooks of QXmpp start with org.qxmpp.

struct AffiliationChanged {
    static constexpr QStringView Name = u"org.qxmpp.muc.affiliation-changed";
    using Signature = void(const QString &roomJid, const QXmpp::Muc::Item &item);
};

A Hooks object is a lightweight handle to the hooks of a client together with an owner. Handlers added through it are removed when the owner is destroyed. Extensions usually add their handlers in their constructor using QXmppClientExtension::hooks():

MyManager::MyManager()
    : d(std::make_unique<MyManagerPrivate>())
{
    hooks().add<QXmpp::Muc::AffiliationChanged>(d.get(), &MyManagerPrivate::onAffiliationChanged);
}

The extension defining a hook runs it:

hooks().run<QXmpp::Muc::AffiliationChanged>(roomJid, item);

To collect values from multiple handlers (a fold), pass a mutable reference as argument, like QXmpp::Disco::CollectInfo does.

Keys

A hook can have a Key type. Handlers are then added for a key and run() only runs the handlers of the given key. Keys can be strings, enums of QXmpp with a string representation and tuples of them, e.g. the tag name and namespace of an element.

struct EventReceived {
    static constexpr QStringView Name = u"org.qxmpp.pubsub.event-received";
    using Key = QStringView;
    using Signature = QXmpp::HookResult(const QDomElement &element, const QString &service, const QString &node);
};

hooks().add<EventReceived>(u"urn:xmpp:mix:nodes:info", this, &MyManager::handleEvent);
hooks().run<EventReceived>(node, element, service, node);

Registries

Registries can be built on hooks, so they share the lifetime rules of handlers. For example, QXmpp::E2ee::Providers stores each end-to-end encryption provider as the single responder of a keyed hook:

struct PackLookup {
    static constexpr QStringView Name = u"org.example.stickers.pack-lookup";
    using Key = QString;
    using Signature = StickerPack();
};

class StickerPacks
{
public:
    explicit StickerPacks(QXmpp::Hooks hooks) : m_hooks(std::move(hooks)) { }

    void add(StickerPack pack) const
    {
        const auto id = pack.id;
        m_hooks.add<PackLookup>(id, [pack = std::move(pack)] { return pack; });
    }
    std::optional<StickerPack> find(const QString &id) const
    {
        return m_hooks.run<PackLookup>(id);
    }

private:
    QXmpp::Hooks m_hooks;
};

// in an extension, e.g. in its constructor
StickerPacks(hooks()).add(pack);

Adapted hooks

A type with a Target hook and a static adapt() function adds typed handlers to another hook, e.g. a hook working on serialized data. adapt() turns the handler into a handler of the target. For a keyed target, the static key() function returns the key. Handlers are added with add() like to other hooks, the target is run with run():

struct ConfigChanged {
    static constexpr QStringView Name = u"org.example.config-changed";
    using Key = QString;
    using Signature = void(const QString &json);
};

template<typename T>
struct Changed {
    using Target = ConfigChanged;
    static QString key() { return T::Id; }

    template<typename Handler>
        requires std::invocable<Handler &, T>
    static auto adapt(Handler &&handler)
    {
        return [handler = std::forward<Handler>(handler)](const QString &json) mutable {
            handler(T::fromJson(json));
        };
    }
};

hooks().add<Changed<ProxySettings>>([](ProxySettings &&settings) { ... });

Hook names should be prefixed to avoid collisions, QXmpp uses "org.qxmpp.". Handlers with a signature or key type differing from the one of the existing handlers of the same hook are rejected.

Member Function Documentation

Hooks::Hooks(QXmpp::Private::HookRegistry *registry, QObject *owner)

Constructs a handle to the hooks in registry with owner as the owner of added handlers.

Prefer QXmppClient::hooks() and QXmppClientExtension::hooks() over this constructor.

template <UnkeyedHookDefinition Hook, typename Handler> requires (Private::HookSignature<typename Hook::Signature>::template isHandler<std::decay_t<Handler>>()) void Hooks::add(Handler &&handler, int sequence = HookSequence::Default) const

Adds handler to the hook Hook.

The handler is removed when the owner of this Hooks object is destroyed or, for QXmppClientExtension, when the extension is removed from the client. sequence decides the position of the handler, see QXmpp::HookSequence.

Hook can also be an adapted hook definition: Hook::adapt() then turns handler into a handler of Hook::Target, for keyed targets with the key Hook::key().

template < UnkeyedHookDefinition Hook, typename Object, typename Method > requires std::is_member_function_pointer_v<Method> void Hooks::add(Object *object, Method method, int sequence = HookSequence::Default) const

Adds the member function method of object to the hook Hook.

object is not required to be a QObject, it can also be the private class of a manager. It must outlive the owner of this Hooks object. sequence decides the position of the handler, see QXmpp::HookSequence. Hook can also be an adapted hook definition.

template <KeyedHookDefinition Hook, typename Handler> requires (Private::HookSignature<typename Hook::Signature>::template isHandler<std::decay_t<Handler>>()) void Hooks::add(const typename Hook::Key &key, Handler &&handler, int sequence = HookSequence::Default) const

Adds handler for key to the keyed hook Hook. It is only run for this key. sequence decides the position of the handler, see QXmpp::HookSequence.

template < KeyedHookDefinition Hook, typename Object, typename Method > requires std::is_member_function_pointer_v<Method> void Hooks::add(const typename Hook::Key &key, Object *object, Method method, int sequence = HookSequence::Default) const

Adds the member function method of object for key to the keyed hook Hook. sequence decides the position of the handler, see QXmpp::HookSequence.

template <typename Hook> requires HookDefinition<Hook> || AdaptedHookDefinition<Hook> bool Hooks::hasHandlers() const

Returns whether the hook Hook has any handlers, for keyed hooks for any key.

template <KeyedHookDefinition Hook> bool Hooks::hasHandlers(const typename Hook::Key &key) const

Returns whether the keyed hook Hook has handlers for key.

void Hooks::removeAll() const

Removes all handlers that have been added with the owner of this object.

template <UnkeyedHookDefinition Hook, typename... Args> auto Hooks::run(Args &&... args) const

Runs the handlers of the hook Hook with args.

For hooks returning QXmpp::HookResult, the handlers are run until one returns HookResult::Stop, which is then returned. For hooks returning void, all handlers are run. For hooks returning another type, only the first handler is run and its result is returned, std::nullopt if there is no handler.

Asynchronous hooks return a QXmppTask that finishes after the last handler. The handlers are run one after another. Arguments passed by reference must stay valid until the task has finished, arguments passed by value are copied. A handler is skipped when its owner is destroyed or its task is dropped before it has finished; the result of a single responder is then std::nullopt. The task finishes immediately if no handler waits for anything.

template <KeyedHookDefinition Hook, typename... Args> auto Hooks::run(const typename Hook::Key &key, Args &&... args) const

Runs the handlers of the keyed hook Hook for key with args, see run().