| 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 | |
| 28 | namespace QCA { |
| 29 | |
| 30 | #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) |
| 31 | int methodReturnType(const QMetaObject *obj, const QByteArray &method, const QList<QByteArray> &argTypes) |
| 32 | #else |
| 33 | QByteArray 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(ch: '('); |
| 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 | |
| 65 | bool 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 | //---------------------------------------------------------------------------- |
| 136 | class SyncThreadAgent; |
| 137 | |
| 138 | class SyncThread::Private : public QObject |
| 139 | { |
| 140 | Q_OBJECT |
| 141 | public: |
| 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 | |
| 158 | public Q_SLOTS: |
| 159 | void agent_started(); |
| 160 | void agent_call_ret(bool success, const QVariant &ret); |
| 161 | }; |
| 162 | |
| 163 | class SyncThreadAgent : public QObject |
| 164 | { |
| 165 | Q_OBJECT |
| 166 | public: |
| 167 | SyncThreadAgent(QObject *parent = nullptr) |
| 168 | : QObject(parent) |
| 169 | { |
| 170 | QMetaObject::invokeMethod(obj: this, member: "started" , c: Qt::QueuedConnection); |
| 171 | } |
| 172 | |
| 173 | Q_SIGNALS: |
| 174 | void started(); |
| 175 | void call_ret(bool success, const QVariant &ret); |
| 176 | |
| 177 | public 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 | |
| 186 | SyncThread::SyncThread(QObject *parent) |
| 187 | : QThread(parent) |
| 188 | { |
| 189 | d = new Private(this); |
| 190 | qRegisterMetaType<QVariant>(typeName: "QVariant" ); |
| 191 | qRegisterMetaType<QVariantList>(typeName: "QVariantList" ); |
| 192 | } |
| 193 | |
| 194 | SyncThread::~SyncThread() |
| 195 | { |
| 196 | stop(); |
| 197 | delete d; |
| 198 | } |
| 199 | |
| 200 | void 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 | |
| 208 | void 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 | |
| 218 | QVariant 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 | |
| 238 | void 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 | |
| 256 | void SyncThread::Private::agent_started() |
| 257 | { |
| 258 | q->atStart(); |
| 259 | w.wakeOne(); |
| 260 | m.unlock(); |
| 261 | } |
| 262 | |
| 263 | void 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 | |