1/*
2 * Copyright (C) 2006 Justin Karneges <justin@affinix.com>
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17 * 02110-1301 USA
18 *
19 */
20
21#include "qca_support.h"
22
23#include <QEventLoop>
24#include <QMetaMethod>
25#include <QMutexLocker>
26#include <QWaitCondition>
27
28namespace QCA {
29
30#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
31int methodReturnType(const QMetaObject *obj, const QByteArray &method, const QList<QByteArray> &argTypes)
32#else
33QByteArray methodReturnType(
34 const QMetaObject *obj,
35 const QByteArray &method,
36 const QList<QByteArray> argTypes) // clazy:exclude=function-args-by-ref NOLINT(performance-unnecessary-value-param)
37 // TODO make argTypes const & when we break ABI
38#endif
39{
40 for (int n = 0; n < obj->methodCount(); ++n) {
41 QMetaMethod m = obj->method(index: n);
42 const QByteArray sig = m.methodSignature();
43 int offset = sig.indexOf(c: '(');
44 if (offset == -1)
45 continue;
46 const QByteArray name = sig.mid(index: 0, len: offset);
47 if (name != method)
48 continue;
49 if (m.parameterTypes() != argTypes)
50 continue;
51
52#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
53 return m.returnType();
54#else
55 return m.typeName();
56#endif
57 }
58#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
59 return QMetaType::UnknownType;
60#else
61 return QByteArray();
62#endif
63}
64
65bool invokeMethodWithVariants(QObject *obj,
66 const QByteArray &method,
67 const QVariantList &args,
68 QVariant *ret,
69 Qt::ConnectionType type)
70{
71 // QMetaObject::invokeMethod() has a 10 argument maximum
72 if (args.count() > 10)
73 return false;
74
75 QList<QByteArray> argTypes;
76 for (int n = 0; n < args.count(); ++n) {
77 argTypes += args[n].typeName();
78 }
79
80 // get return type
81#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
82 const auto metatype = methodReturnType(obj: obj->metaObject(), method, argTypes);
83 if (metatype == QMetaType::UnknownType) {
84 return false;
85 }
86#else
87 int metatype = QMetaType::Void;
88 const QByteArray retTypeName = methodReturnType(obj->metaObject(), method, argTypes);
89 if (!retTypeName.isEmpty() && retTypeName != "void") {
90 metatype = QMetaType::type(retTypeName.data());
91 if (metatype == QMetaType::UnknownType) // lookup failed
92 return false;
93 }
94#endif
95
96 QGenericArgument arg[10];
97 for (int n = 0; n < args.count(); ++n)
98 arg[n] = QGenericArgument(args[n].typeName(), args[n].constData());
99
100 QGenericReturnArgument retarg;
101 QVariant retval;
102
103 if (metatype != QMetaType::Void) {
104#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
105 retval = QVariant(QMetaType {metatype}, (const void *)nullptr);
106#else
107 retval = QVariant(metatype, (const void *)nullptr);
108#endif
109 retarg = QGenericReturnArgument(retval.typeName(), retval.data());
110 }
111
112 if (!QMetaObject::invokeMethod(obj,
113 member: method.data(),
114 type,
115 ret: retarg,
116 val0: arg[0],
117 val1: arg[1],
118 val2: arg[2],
119 val3: arg[3],
120 val4: arg[4],
121 val5: arg[5],
122 val6: arg[6],
123 val7: arg[7],
124 val8: arg[8],
125 val9: arg[9]))
126 return false;
127
128 if (retval.isValid() && ret)
129 *ret = retval;
130 return true;
131}
132
133//----------------------------------------------------------------------------
134// SyncThread
135//----------------------------------------------------------------------------
136class SyncThreadAgent;
137
138class SyncThread::Private : public QObject
139{
140 Q_OBJECT
141public:
142 SyncThread *q;
143 QMutex m;
144 QWaitCondition w;
145 QEventLoop *loop;
146 SyncThreadAgent *agent;
147 bool last_success;
148 QVariant last_ret;
149
150 Private(SyncThread *_q)
151 : QObject(_q)
152 , q(_q)
153 {
154 loop = nullptr;
155 agent = nullptr;
156 }
157
158public Q_SLOTS:
159 void agent_started();
160 void agent_call_ret(bool success, const QVariant &ret);
161};
162
163class SyncThreadAgent : public QObject
164{
165 Q_OBJECT
166public:
167 SyncThreadAgent(QObject *parent = nullptr)
168 : QObject(parent)
169 {
170 QMetaObject::invokeMethod(obj: this, member: "started", c: Qt::QueuedConnection);
171 }
172
173Q_SIGNALS:
174 void started();
175 void call_ret(bool success, const QVariant &ret);
176
177public Q_SLOTS:
178 void call_do(QObject *obj, const QByteArray &method, const QVariantList &args)
179 {
180 QVariant ret;
181 bool ok = invokeMethodWithVariants(obj, method, args, ret: &ret, type: Qt::DirectConnection);
182 emit call_ret(success: ok, ret);
183 }
184};
185
186SyncThread::SyncThread(QObject *parent)
187 : QThread(parent)
188{
189 d = new Private(this);
190 qRegisterMetaType<QVariant>(typeName: "QVariant");
191 qRegisterMetaType<QVariantList>(typeName: "QVariantList");
192}
193
194SyncThread::~SyncThread()
195{
196 stop();
197 delete d;
198}
199
200void SyncThread::start()
201{
202 QMutexLocker locker(&d->m);
203 Q_ASSERT(!d->loop);
204 QThread::start();
205 d->w.wait(lockedMutex: &d->m);
206}
207
208void SyncThread::stop()
209{
210 QMutexLocker locker(&d->m);
211 if (!d->loop)
212 return;
213 QMetaObject::invokeMethod(obj: d->loop, member: "quit");
214 d->w.wait(lockedMutex: &d->m);
215 wait();
216}
217
218QVariant SyncThread::call(QObject *obj, const QByteArray &method, const QVariantList &args, bool *ok)
219{
220 QMutexLocker locker(&d->m);
221 bool ret;
222 Q_UNUSED(ret); // In really ret is used. I use this hack to suppress a compiler warning
223 // clang-format off
224 // Otherwise the QObject* gets turned into Object * that is not normalized and is slightly slower
225 ret = QMetaObject::invokeMethod(obj: d->agent, member: "call_do",
226 c: Qt::QueuedConnection, Q_ARG(QObject*, obj),
227 Q_ARG(QByteArray, method), Q_ARG(QVariantList, args));
228 // clang-format on
229 Q_ASSERT(ret);
230 d->w.wait(lockedMutex: &d->m);
231 if (ok)
232 *ok = d->last_success;
233 QVariant v = d->last_ret;
234 d->last_ret = QVariant();
235 return v;
236}
237
238void SyncThread::run()
239{
240 d->m.lock();
241 d->loop = new QEventLoop;
242 d->agent = new SyncThreadAgent;
243 connect(sender: d->agent, signal: &SyncThreadAgent::started, context: d, slot: &Private::agent_started, type: Qt::DirectConnection);
244 connect(sender: d->agent, signal: &SyncThreadAgent::call_ret, context: d, slot: &Private::agent_call_ret, type: Qt::DirectConnection);
245 d->loop->exec();
246 d->m.lock();
247 atEnd();
248 delete d->agent;
249 delete d->loop;
250 d->agent = nullptr;
251 d->loop = nullptr;
252 d->w.wakeOne();
253 d->m.unlock();
254}
255
256void SyncThread::Private::agent_started()
257{
258 q->atStart();
259 w.wakeOne();
260 m.unlock();
261}
262
263void SyncThread::Private::agent_call_ret(bool success, const QVariant &ret)
264{
265 QMutexLocker locker(&m);
266 last_success = success;
267 last_ret = ret;
268 w.wakeOne();
269}
270
271}
272
273#include "syncthread.moc"
274

source code of qca/src/support/syncthread.cpp