diff --git a/src/corelib/kernel/qcoreapplication.cpp b/src/corelib/kernel/qcoreapplication.cpp index e40cab05ba9..f9163ac3062 100644 --- a/src/corelib/kernel/qcoreapplication.cpp +++ b/src/corelib/kernel/qcoreapplication.cpp @@ -2820,8 +2820,9 @@ Qt::PermissionStatus QCoreApplication::checkPermission(const QPermission &permis qApp->requestPermission(QCameraPermission{}, this, &CamerWidget::permissionUpdated); \endcode - If \a context is destroyed before the request completes, - the \a functor will not be called. + The \a functor will be called in the thread of the \a context object. If + \a context is destroyed before the request completes, the \a functor will + not be called. \include permissions.qdocinc requestPermission-postamble @@ -2848,6 +2849,43 @@ void QCoreApplication::requestPermission(const QPermission &requestedPermission, Q_ASSERT(slotObj); + // If we have a context object, then we dispatch the permission response + // asynchronously through a received object that lives in the same thread + // as the context object. Otherwise we call the functor synchronously when + // we get a response (which might still be asynchronous for the caller). + class PermissionReceiver : public QObject + { + public: + PermissionReceiver(QtPrivate::QSlotObjectBase *slotObject, const QObject *context) + : slotObject(slotObject), context(context) + {} + protected: + bool event(QEvent *event) override { + if (event->type() == QEvent::MetaCall) { + auto metaCallEvent = static_cast(event); + if (metaCallEvent->id() == ushort(-1)) { + Q_ASSERT(slotObject); + // only execute if context object is still alive + if (context) + slotObject->call(const_cast(context.data()), metaCallEvent->args()); + slotObject->destroyIfLastRef(); + deleteLater(); + + return true; + } + } + return QObject::event(event); + } + private: + QtPrivate::QSlotObjectBase *slotObject; + QPointer context; + }; + PermissionReceiver *receiver = nullptr; + if (context) { + receiver = new PermissionReceiver(slotObj, context); + receiver->moveToThread(context->thread()); + } + QPermissions::Private::requestPermission(requestedPermission, [=](Qt::PermissionStatus status) { Q_ASSERT_X(status != Qt::PermissionStatus::Undetermined, "QPermission", "QCoreApplication::requestPermission() should never return Undetermined"); @@ -2858,11 +2896,28 @@ void QCoreApplication::requestPermission(const QPermission &requestedPermission, QPermission permission = requestedPermission; permission.m_status = status; - void *argv[] = { nullptr, &permission }; - slotObj->call(const_cast(context), argv); + if (receiver) { + const int nargs = 2; + auto metaCallEvent = new QMetaCallEvent(slotObj, qApp, ushort(-1), nargs); + Q_CHECK_PTR(metaCallEvent); + void **args = metaCallEvent->args(); + QMetaType *types = metaCallEvent->types(); + const auto voidType = QMetaType::fromType(); + const auto permissionType = QMetaType::fromType(); + types[0] = voidType; + types[1] = permissionType; + args[0] = nullptr; + args[1] = permissionType.create(&permission); + Q_CHECK_PTR(args[1]); + qApp->postEvent(receiver, metaCallEvent); + } else { + void *argv[] = { nullptr, &permission }; + slotObj->call(const_cast(context), argv); + } } - slotObj->destroyIfLastRef(); + if (!receiver) + slotObj->destroyIfLastRef(); }); } diff --git a/tests/auto/corelib/kernel/qpermission/tst_qpermission.cpp b/tests/auto/corelib/kernel/qpermission/tst_qpermission.cpp index 907fc2a5c3a..b6989696521 100644 --- a/tests/auto/corelib/kernel/qpermission/tst_qpermission.cpp +++ b/tests/auto/corelib/kernel/qpermission/tst_qpermission.cpp @@ -25,6 +25,10 @@ private Q_SLOTS: void converting_Bluetooth() const { return converting_impl(); } void conversionMaintainsState() const; + + void functorWithContextInThread(); + void receiverInThread(); + void destroyedContextObject(); private: template void converting_impl() const; @@ -141,5 +145,93 @@ void tst_QPermission::conversionMaintainsState() const } } +void tst_QPermission::functorWithContextInThread() +{ + int argc = 0; + char *argv = nullptr; + QCoreApplication app(argc, &argv); + QThread::currentThread()->setObjectName("main thread"); + QThread receiverThread; + receiverThread.setObjectName("receiverThread"); + QObject receiver; + receiver.moveToThread(&receiverThread); + receiverThread.start(); + auto guard = qScopeGuard([&receiverThread]{ + receiverThread.quit(); + QVERIFY(receiverThread.wait(1000)); + }); + + DummyPermission dummy; +#ifdef Q_OS_DARWIN + QTest::ignoreMessage(QtWarningMsg, QRegularExpression(".*Could not find permission plugin for DummyPermission.*")); +#endif + QThread *permissionReceiverThread = nullptr; + qApp->requestPermission(dummy, &receiver, [&](const QPermission &permission){ + auto dummy = permission.value(); + QVERIFY(dummy); + permissionReceiverThread = QThread::currentThread(); + }); + QTRY_COMPARE(permissionReceiverThread, &receiverThread); +} + +void tst_QPermission::receiverInThread() +{ + int argc = 0; + char *argv = nullptr; + QCoreApplication app(argc, &argv); + QThread::currentThread()->setObjectName("main thread"); + QThread receiverThread; + receiverThread.setObjectName("receiverThread"); + class Receiver : public QObject + { + public: + using QObject::QObject; + void handlePermission(const QPermission &permission) + { + auto dummy = permission.value(); + QVERIFY(dummy); + permissionReceiverThread = QThread::currentThread(); + } + + QThread *permissionReceiverThread = nullptr; + } receiver; + receiver.moveToThread(&receiverThread); + receiverThread.start(); + auto guard = qScopeGuard([&receiverThread]{ + receiverThread.quit(); + QVERIFY(receiverThread.wait(1000)); + }); + + DummyPermission dummy; +#ifdef Q_OS_DARWIN + QTest::ignoreMessage(QtWarningMsg, QRegularExpression(".*Could not find permission plugin for DummyPermission.*")); +#endif + + qApp->requestPermission(dummy, &receiver, &Receiver::handlePermission); + QTRY_COMPARE(receiver.permissionReceiverThread, &receiverThread); +} + +void tst_QPermission::destroyedContextObject() +{ + int argc = 0; + char *argv = nullptr; + QCoreApplication app(argc, &argv); + + QObject *context = new QObject; + + DummyPermission dummy; +#ifdef Q_OS_DARWIN + QTest::ignoreMessage(QtWarningMsg, QRegularExpression(".*Could not find permission plugin for DummyPermission.*")); +#endif + bool permissionReceived = false; + qApp->requestPermission(dummy, context, [&]{ + permissionReceived = true; + }); + QVERIFY2(!permissionReceived, "Permission received synchronously"); + delete context; + QTest::qWait(100); + QVERIFY(!permissionReceived); +} + QTEST_APPLESS_MAIN(tst_QPermission) #include "tst_qpermission.moc"