File indexing completed on 2024-12-08 07:21:05

0001 /*
0002  * Copyright (C) 2006  Justin Karneges <justin@affinix.com>
0003  *
0004  * This library is free software; you can redistribute it and/or
0005  * modify it under the terms of the GNU Lesser General Public
0006  * License as published by the Free Software Foundation; either
0007  * version 2.1 of the License, or (at your option) any later version.
0008  *
0009  * This library is distributed in the hope that it will be useful,
0010  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0011  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0012  * Lesser General Public License for more details.
0013  *
0014  * You should have received a copy of the GNU Lesser General Public
0015  * License along with this library; if not, write to the Free Software
0016  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
0017  * 02110-1301  USA
0018  *
0019  */
0020 
0021 #include "qca_support.h"
0022 
0023 #include <QEventLoop>
0024 #include <QMetaMethod>
0025 #include <QMutexLocker>
0026 #include <QWaitCondition>
0027 
0028 namespace QCA {
0029 
0030 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
0031 int methodReturnType(const QMetaObject *obj, const QByteArray &method, const QList<QByteArray> &argTypes)
0032 #else
0033 QByteArray methodReturnType(
0034     const QMetaObject      *obj,
0035     const QByteArray       &method,
0036     const QList<QByteArray> argTypes) // clazy:exclude=function-args-by-ref NOLINT(performance-unnecessary-value-param)
0037                                       // TODO make argTypes const & when we break ABI
0038 #endif
0039 {
0040     for (int n = 0; n < obj->methodCount(); ++n) {
0041         QMetaMethod      m      = obj->method(n);
0042         const QByteArray sig    = m.methodSignature();
0043         int              offset = sig.indexOf('(');
0044         if (offset == -1)
0045             continue;
0046         const QByteArray name = sig.mid(0, offset);
0047         if (name != method)
0048             continue;
0049         if (m.parameterTypes() != argTypes)
0050             continue;
0051 
0052 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
0053         return m.returnType();
0054 #else
0055         return m.typeName();
0056 #endif
0057     }
0058 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
0059     return QMetaType::UnknownType;
0060 #else
0061     return QByteArray();
0062 #endif
0063 }
0064 
0065 bool invokeMethodWithVariants(QObject            *obj,
0066                               const QByteArray   &method,
0067                               const QVariantList &args,
0068                               QVariant           *ret,
0069                               Qt::ConnectionType  type)
0070 {
0071     // QMetaObject::invokeMethod() has a 10 argument maximum
0072     if (args.count() > 10)
0073         return false;
0074 
0075     QList<QByteArray> argTypes;
0076     for (int n = 0; n < args.count(); ++n) {
0077         argTypes += args[n].typeName();
0078     }
0079 
0080     // get return type
0081 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
0082     const auto metatype = methodReturnType(obj->metaObject(), method, argTypes);
0083     if (metatype == QMetaType::UnknownType) {
0084         return false;
0085     }
0086 #else
0087     int              metatype    = QMetaType::Void;
0088     const QByteArray retTypeName = methodReturnType(obj->metaObject(), method, argTypes);
0089     if (!retTypeName.isEmpty() && retTypeName != "void") {
0090         metatype = QMetaType::type(retTypeName.data());
0091         if (metatype == QMetaType::UnknownType) // lookup failed
0092             return false;
0093     }
0094 #endif
0095 
0096     QGenericArgument arg[10];
0097     for (int n = 0; n < args.count(); ++n)
0098         arg[n] = QGenericArgument(args[n].typeName(), args[n].constData());
0099 
0100     QGenericReturnArgument retarg;
0101     QVariant               retval;
0102 
0103     if (metatype != QMetaType::Void) {
0104 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
0105         retval = QVariant(QMetaType {metatype}, (const void *)nullptr);
0106 #else
0107         retval = QVariant(metatype, (const void *)nullptr);
0108 #endif
0109         retarg = QGenericReturnArgument(retval.typeName(), retval.data());
0110     }
0111 
0112     if (!QMetaObject::invokeMethod(obj,
0113                                    method.data(),
0114                                    type,
0115                                    retarg,
0116                                    arg[0],
0117                                    arg[1],
0118                                    arg[2],
0119                                    arg[3],
0120                                    arg[4],
0121                                    arg[5],
0122                                    arg[6],
0123                                    arg[7],
0124                                    arg[8],
0125                                    arg[9]))
0126         return false;
0127 
0128     if (retval.isValid() && ret)
0129         *ret = retval;
0130     return true;
0131 }
0132 
0133 //----------------------------------------------------------------------------
0134 // SyncThread
0135 //----------------------------------------------------------------------------
0136 class SyncThreadAgent;
0137 
0138 class SyncThread::Private : public QObject
0139 {
0140     Q_OBJECT
0141 public:
0142     SyncThread      *q;
0143     QMutex           m;
0144     QWaitCondition   w;
0145     QEventLoop      *loop;
0146     SyncThreadAgent *agent;
0147     bool             last_success;
0148     QVariant         last_ret;
0149 
0150     Private(SyncThread *_q)
0151         : QObject(_q)
0152         , q(_q)
0153     {
0154         loop  = nullptr;
0155         agent = nullptr;
0156     }
0157 
0158 public Q_SLOTS:
0159     void agent_started();
0160     void agent_call_ret(bool success, const QVariant &ret);
0161 };
0162 
0163 class SyncThreadAgent : public QObject
0164 {
0165     Q_OBJECT
0166 public:
0167     SyncThreadAgent(QObject *parent = nullptr)
0168         : QObject(parent)
0169     {
0170         QMetaObject::invokeMethod(this, "started", Qt::QueuedConnection);
0171     }
0172 
0173 Q_SIGNALS:
0174     void started();
0175     void call_ret(bool success, const QVariant &ret);
0176 
0177 public Q_SLOTS:
0178     void call_do(QObject *obj, const QByteArray &method, const QVariantList &args)
0179     {
0180         QVariant ret;
0181         bool     ok = invokeMethodWithVariants(obj, method, args, &ret, Qt::DirectConnection);
0182         emit     call_ret(ok, ret);
0183     }
0184 };
0185 
0186 SyncThread::SyncThread(QObject *parent)
0187     : QThread(parent)
0188 {
0189     d = new Private(this);
0190     qRegisterMetaType<QVariant>("QVariant");
0191     qRegisterMetaType<QVariantList>("QVariantList");
0192 }
0193 
0194 SyncThread::~SyncThread()
0195 {
0196     stop();
0197     delete d;
0198 }
0199 
0200 void SyncThread::start()
0201 {
0202     QMutexLocker locker(&d->m);
0203     Q_ASSERT(!d->loop);
0204     QThread::start();
0205     d->w.wait(&d->m);
0206 }
0207 
0208 void SyncThread::stop()
0209 {
0210     QMutexLocker locker(&d->m);
0211     if (!d->loop)
0212         return;
0213     QMetaObject::invokeMethod(d->loop, "quit");
0214     d->w.wait(&d->m);
0215     wait();
0216 }
0217 
0218 QVariant SyncThread::call(QObject *obj, const QByteArray &method, const QVariantList &args, bool *ok)
0219 {
0220     QMutexLocker locker(&d->m);
0221     bool         ret;
0222     Q_UNUSED(ret); // In really ret is used. I use this hack to suppress a compiler warning
0223     // clang-format off
0224     // Otherwise the QObject* gets turned into Object * that is not normalized and is slightly slower
0225     ret = QMetaObject::invokeMethod(d->agent, "call_do",
0226                                     Qt::QueuedConnection, Q_ARG(QObject*, obj),
0227                                     Q_ARG(QByteArray, method), Q_ARG(QVariantList, args));
0228     // clang-format on
0229     Q_ASSERT(ret);
0230     d->w.wait(&d->m);
0231     if (ok)
0232         *ok = d->last_success;
0233     QVariant v  = d->last_ret;
0234     d->last_ret = QVariant();
0235     return v;
0236 }
0237 
0238 void SyncThread::run()
0239 {
0240     d->m.lock();
0241     d->loop  = new QEventLoop;
0242     d->agent = new SyncThreadAgent;
0243     connect(d->agent, &SyncThreadAgent::started, d, &Private::agent_started, Qt::DirectConnection);
0244     connect(d->agent, &SyncThreadAgent::call_ret, d, &Private::agent_call_ret, Qt::DirectConnection);
0245     d->loop->exec();
0246     d->m.lock();
0247     atEnd();
0248     delete d->agent;
0249     delete d->loop;
0250     d->agent = nullptr;
0251     d->loop  = nullptr;
0252     d->w.wakeOne();
0253     d->m.unlock();
0254 }
0255 
0256 void SyncThread::Private::agent_started()
0257 {
0258     q->atStart();
0259     w.wakeOne();
0260     m.unlock();
0261 }
0262 
0263 void SyncThread::Private::agent_call_ret(bool success, const QVariant &ret)
0264 {
0265     QMutexLocker locker(&m);
0266     last_success = success;
0267     last_ret     = ret;
0268     w.wakeOne();
0269 }
0270 
0271 }
0272 
0273 #include "syncthread.moc"