summaryrefslogtreecommitdiffstats
path: root/src/corelib/platform
diff options
context:
space:
mode:
Diffstat (limited to 'src/corelib/platform')
-rw-r--r--src/corelib/platform/android/qandroidextras.cpp243
-rw-r--r--src/corelib/platform/android/qandroidextras_p.h22
-rw-r--r--src/corelib/platform/android/qandroidnativeinterface.cpp62
-rw-r--r--src/corelib/platform/darwin/qdarwinpermissionplugin.mm90
-rw-r--r--src/corelib/platform/darwin/qdarwinpermissionplugin_bluetooth.mm86
-rw-r--r--src/corelib/platform/darwin/qdarwinpermissionplugin_calendar.mm71
-rw-r--r--src/corelib/platform/darwin/qdarwinpermissionplugin_camera.mm42
-rw-r--r--src/corelib/platform/darwin/qdarwinpermissionplugin_contacts.mm58
-rw-r--r--src/corelib/platform/darwin/qdarwinpermissionplugin_location.mm263
-rw-r--r--src/corelib/platform/darwin/qdarwinpermissionplugin_microphone.mm42
-rw-r--r--src/corelib/platform/darwin/qdarwinpermissionplugin_p.h58
-rw-r--r--src/corelib/platform/darwin/qdarwinpermissionplugin_p_p.h104
-rw-r--r--src/corelib/platform/ios/PrivacyInfo.xcprivacy31
-rw-r--r--src/corelib/platform/wasm/qstdweb.cpp437
-rw-r--r--src/corelib/platform/wasm/qstdweb_p.h167
-rw-r--r--src/corelib/platform/windows/qcomobject_p.h127
-rw-r--r--src/corelib/platform/windows/qfactorycacheregistration.cpp53
-rw-r--r--src/corelib/platform/windows/qfactorycacheregistration_p.h52
-rw-r--r--src/corelib/platform/windows/qt_winrtbase_p.h34
19 files changed, 1747 insertions, 295 deletions
diff --git a/src/corelib/platform/android/qandroidextras.cpp b/src/corelib/platform/android/qandroidextras.cpp
index 0fa67eacaf..aa0c3fd093 100644
--- a/src/corelib/platform/android/qandroidextras.cpp
+++ b/src/corelib/platform/android/qandroidextras.cpp
@@ -117,6 +117,7 @@ QAndroidBinder QAndroidParcelPrivate::readBinder() const
/*!
\class QAndroidParcel
+ \inheaderfile QtCore/private/qandroidextras_p.h
\preliminary
\inmodule QtCorePrivate
\brief Wraps the most important methods of Android Parcel class.
@@ -125,6 +126,8 @@ QAndroidBinder QAndroidParcelPrivate::readBinder() const
\l {https://developer.android.com/reference/android/os/Parcel.html}{Android Parcel}
methods.
+ \include qtcore.qdoc qtcoreprivate-usage
+
\since 6.2
*/
@@ -233,6 +236,7 @@ QJniObject QAndroidParcel::handle() const
/*!
\class QAndroidBinder
+ \inheaderfile QtCore/private/qandroidextras_p.h
\preliminary
\inmodule QtCorePrivate
\brief Wraps the most important methods of Android Binder class.
@@ -241,6 +245,8 @@ QJniObject QAndroidParcel::handle() const
\l {https://developer.android.com/reference/android/os/Binder.html}{Android Binder}
methods.
+ \include qtcore.qdoc qtcoreprivate-usage
+
\since 6.2
*/
@@ -370,6 +376,7 @@ QJniObject QAndroidBinder::handle() const
/*!
\class QAndroidServiceConnection
+ \inheaderfile QtCore/private/qandroidextras_p.h
\preliminary
\inmodule QtCorePrivate
\brief Wraps the most important methods of Android ServiceConnection class.
@@ -380,6 +387,8 @@ QJniObject QAndroidBinder::handle() const
It is useful when you perform a QtAndroidPrivate::bindService operation.
+ \include qtcore.qdoc qtcoreprivate-usage
+
\since 6.2
*/
@@ -442,7 +451,7 @@ QJniObject QAndroidServiceConnection::handle() const
*/
-static QBasicAtomicInteger<uint> nextUniqueActivityRequestCode = Q_BASIC_ATOMIC_INITIALIZER(0);
+Q_CONSTINIT static QBasicAtomicInteger<uint> nextUniqueActivityRequestCode = Q_BASIC_ATOMIC_INITIALIZER(0);
// Get a unique activity request code.
static int uniqueActivityRequestCode()
@@ -496,6 +505,7 @@ public:
/*!
\class QAndroidActivityResultReceiver
+ \inheaderfile QtCore/private/qandroidextras_p.h
\preliminary
\inmodule QtCorePrivate
\since 6.2
@@ -503,6 +513,8 @@ public:
Create a subclass of this class to be notified of the results when using the
\c QtAndroidPrivate::startActivity() and \c QtAndroidPrivate::startIntentSender() APIs.
+
+ \include qtcore.qdoc qtcoreprivate-usage
*/
/*!
@@ -591,6 +603,7 @@ public:
/*!
\class QAndroidService
+ \inheaderfile QtCore/private/qandroidextras_p.h
\preliminary
\inmodule QtCorePrivate
\brief Wraps the most important methods of Android Service class.
@@ -599,6 +612,8 @@ public:
\l {https://developer.android.com/reference/android/app/Service.html}{Android Service}
methods.
+ \include qtcore.qdoc qtcoreprivate-usage
+
\since 6.2
*/
@@ -657,9 +672,49 @@ QAndroidBinder* QAndroidService::onBind(const QAndroidIntent &/*intent*/)
return nullptr;
}
+static jboolean onTransact(JNIEnv */*env*/, jclass /*cls*/, jlong id, jint code, jobject data,
+ jobject reply, jint flags)
+{
+ if (!id)
+ return false;
+
+ return reinterpret_cast<QAndroidBinder*>(id)->onTransact(
+ code, QAndroidParcel(data), QAndroidParcel(reply), QAndroidBinder::CallType(flags));
+}
+
+static void onServiceConnected(JNIEnv */*env*/, jclass /*cls*/, jlong id, jstring name,
+ jobject service)
+{
+ if (!id)
+ return;
+
+ return reinterpret_cast<QAndroidServiceConnection *>(id)->onServiceConnected(
+ QJniObject(name).toString(), QAndroidBinder(service));
+}
+
+static void onServiceDisconnected(JNIEnv */*env*/, jclass /*cls*/, jlong id, jstring name)
+{
+ if (!id)
+ return;
+
+ return reinterpret_cast<QAndroidServiceConnection *>(id)->onServiceDisconnected(
+ QJniObject(name).toString());
+}
+
+bool QtAndroidPrivate::registerExtrasNatives(QJniEnvironment &env)
+{
+ static const JNINativeMethod methods[] = {
+ {"onTransact", "(JILandroid/os/Parcel;Landroid/os/Parcel;I)Z", (void *)onTransact},
+ {"onServiceConnected", "(JLjava/lang/String;Landroid/os/IBinder;)V", (void *)onServiceConnected},
+ {"onServiceDisconnected", "(JLjava/lang/String;)V", (void *)onServiceDisconnected}
+ };
+
+ return env.registerNativeMethods("org/qtproject/qt/android/extras/QtNative", methods, 3);
+}
/*!
\class QAndroidIntent
+ \inheaderfile QtCore/private/qandroidextras_p.h
\preliminary
\inmodule QtCorePrivate
\brief Wraps the most important methods of Android Intent class.
@@ -668,6 +723,8 @@ QAndroidBinder* QAndroidService::onBind(const QAndroidIntent &/*intent*/)
\l {https://developer.android.com/reference/android/content/Intent.html}{Android Intent}
methods.
+ \include qtcore.qdoc qtcoreprivate-usage
+
\since 6.2
*/
@@ -793,6 +850,8 @@ QJniObject QAndroidIntent::handle() const
\brief The QtAndroidPrivate namespace provides miscellaneous functions
to aid Android development.
\inheaderfile QtCore/private/qandroidextras_p.h
+
+ \include qtcore.qdoc qtcoreprivate-usage
*/
/*!
@@ -1016,62 +1075,14 @@ QtAndroidPrivate::PermissionResult resultFromAndroid(jint value)
using PendingPermissionRequestsHash
= QHash<int, QSharedPointer<QPromise<QtAndroidPrivate::PermissionResult>>>;
Q_GLOBAL_STATIC(PendingPermissionRequestsHash, g_pendingPermissionRequests);
-static QBasicMutex g_pendingPermissionRequestsMutex;
+Q_CONSTINIT static QBasicMutex g_pendingPermissionRequestsMutex;
static int nextRequestCode()
{
- static QBasicAtomicInt counter = Q_BASIC_ATOMIC_INITIALIZER(0);
+ Q_CONSTINIT static QBasicAtomicInt counter = Q_BASIC_ATOMIC_INITIALIZER(0);
return counter.fetchAndAddRelaxed(1);
}
-static QStringList nativeStringsFromPermission(QtAndroidPrivate::PermissionType permission)
-{
- static const auto precisePerm = QStringLiteral("android.permission.ACCESS_FINE_LOCATION");
- static const auto coarsePerm = QStringLiteral("android.permission.ACCESS_COARSE_LOCATION");
- static const auto backgroundPerm =
- QStringLiteral("android.permission.ACCESS_BACKGROUND_LOCATION");
-
- switch (permission) {
- case QtAndroidPrivate::Location:
- return {coarsePerm};
- case QtAndroidPrivate::PreciseLocation:
- return {precisePerm};
- case QtAndroidPrivate::BackgroundLocation:
- // Keep the background permission first to be able to use .first()
- // in checkPermission because it takes single permission
- if (QtAndroidPrivate::androidSdkVersion() >= 29)
- return {backgroundPerm, coarsePerm};
- return {coarsePerm};
- case QtAndroidPrivate::PreciseBackgroundLocation:
- // Keep the background permission first to be able to use .first()
- // in checkPermission because it takes single permission
- if (QtAndroidPrivate::androidSdkVersion() >= 29)
- return {backgroundPerm, precisePerm};
- return {precisePerm};
- case QtAndroidPrivate::Camera:
- return {QStringLiteral("android.permission.CAMERA")};
- case QtAndroidPrivate::Microphone:
- return {QStringLiteral("android.permission.RECORD_AUDIO")};
- case QtAndroidPrivate::Bluetooth:
- return { QStringLiteral("android.permission.BLUETOOTH") };
- case QtAndroidPrivate::BodySensors:
- return {QStringLiteral("android.permission.BODY_SENSORS")};
- case QtAndroidPrivate::PhysicalActivity:
- return {QStringLiteral("android.permission.ACTIVITY_RECOGNITION")};
- case QtAndroidPrivate::Contacts:
- return {QStringLiteral("android.permission.READ_CONTACTS"),
- QStringLiteral("android.permission.WRITE_CONTACTS")};
- case QtAndroidPrivate::Storage:
- return {QStringLiteral("android.permission.READ_EXTERNAL_STORAGE"),
- QStringLiteral("android.permission.WRITE_EXTERNAL_STORAGE")};
- case QtAndroidPrivate::Calendar:
- return {QStringLiteral("android.permission.READ_CALENDAR"),
- QStringLiteral("android.permission.WRITE_CALENDAR")};
- }
-
- return {};
-}
-
/*!
\internal
@@ -1104,29 +1115,36 @@ static void sendRequestPermissionsResult(JNIEnv *env, jobject *obj, jint request
request->addResult(result, i);
}
+ QtAndroidPrivate::releaseAndroidDeadlockProtector();
request->finish();
}
QFuture<QtAndroidPrivate::PermissionResult>
requestPermissionsInternal(const QStringList &permissions)
{
- QSharedPointer<QPromise<QtAndroidPrivate::PermissionResult>> promise;
- promise.reset(new QPromise<QtAndroidPrivate::PermissionResult>());
- QFuture<QtAndroidPrivate::PermissionResult> future = promise->future();
- promise->start();
-
// No mechanism to request permission for SDK version below 23, because
// permissions defined in the manifest are granted at install time.
if (QtAndroidPrivate::androidSdkVersion() < 23) {
- for (int i = 0; i < permissions.size(); ++i)
- promise->addResult(QtAndroidPrivate::checkPermission(permissions.at(i)).result(), i);
- promise->finish();
- return future;
+ QList<QtAndroidPrivate::PermissionResult> result;
+ result.reserve(permissions.size());
+ // ### can we kick off all checkPermission()s, and whenAll() collect results?
+ for (const QString &permission : permissions)
+ result.push_back(QtAndroidPrivate::checkPermission(permission).result());
+ return QtFuture::makeReadyRangeFuture(result);
}
+ if (!QtAndroidPrivate::acquireAndroidDeadlockProtector())
+ return QtFuture::makeReadyValueFuture(QtAndroidPrivate::Denied);
+
+ QSharedPointer<QPromise<QtAndroidPrivate::PermissionResult>> promise;
+ promise.reset(new QPromise<QtAndroidPrivate::PermissionResult>());
+ QFuture<QtAndroidPrivate::PermissionResult> future = promise->future();
+ promise->start();
+
const int requestCode = nextRequestCode();
QMutexLocker locker(&g_pendingPermissionRequestsMutex);
g_pendingPermissionRequests->insert(requestCode, promise);
+ locker.unlock();
QNativeInterface::QAndroidApplication::runOnAndroidMainThread([permissions, requestCode] {
QJniEnvironment env;
@@ -1158,65 +1176,16 @@ requestPermissionsInternal(const QStringList &permissions)
QFuture<QtAndroidPrivate::PermissionResult>
QtAndroidPrivate::requestPermission(const QString &permission)
{
- // avoid the uneccessary call and response to an empty permission string
- if (permission.size() > 0)
- return requestPermissionsInternal({permission});
-
- QPromise<QtAndroidPrivate::PermissionResult> promise;
- QFuture<QtAndroidPrivate::PermissionResult> future = promise.future();
- promise.start();
- promise.addResult(QtAndroidPrivate::Denied);
- promise.finish();
- return future;
-}
-
-static bool isBackgroundLocationApi29(QtAndroidPrivate::PermissionType permission)
-{
- return QNativeInterface::QAndroidApplication::sdkVersion() >= 29
- && (permission == QtAndroidPrivate::BackgroundLocation
- || permission == QtAndroidPrivate::PreciseBackgroundLocation);
+ return requestPermissions({permission});
}
-/*!
- \preliminary
-
- Requests the \a permission and returns a QFuture representing the
- result of the request.
-
- \since 6.2
- \sa checkPermission()
-*/
QFuture<QtAndroidPrivate::PermissionResult>
-QtAndroidPrivate::requestPermission(QtAndroidPrivate::PermissionType permission)
+QtAndroidPrivate::requestPermissions(const QStringList &permissions)
{
- QSharedPointer<QPromise<QtAndroidPrivate::PermissionResult>> promise;
- promise.reset(new QPromise<QtAndroidPrivate::PermissionResult>());
- QFuture<QtAndroidPrivate::PermissionResult> future = promise->future();
- promise->start();
- const auto nativePermissions = nativeStringsFromPermission(permission);
-
- if (nativePermissions.size() > 0 && QtAndroidPrivate::acquireAndroidDeadlockProtector()) {
- requestPermissionsInternal(nativePermissions).then(
- [promise, permission](QFuture<QtAndroidPrivate::PermissionResult> future) {
- auto AuthorizedCount = future.results().count(QtAndroidPrivate::Authorized);
- if (AuthorizedCount > 0) {
- if (isBackgroundLocationApi29(permission))
- promise->addResult(future.resultAt(0), 0);
- else
- promise->addResult(QtAndroidPrivate::Authorized, 0);
- } else {
- promise->addResult(QtAndroidPrivate::Denied, 0);
- }
- QtAndroidPrivate::releaseAndroidDeadlockProtector();
- promise->finish();
- });
-
- return future;
- }
-
- promise->addResult(QtAndroidPrivate::Denied);
- promise->finish();
- return future;
+ // avoid the uneccessary call and response to an empty permission string
+ if (permissions.isEmpty())
+ return QtFuture::makeReadyValueFuture(QtAndroidPrivate::Denied);
+ return requestPermissionsInternal(permissions);
}
/*!
@@ -1230,49 +1199,18 @@ QtAndroidPrivate::requestPermission(QtAndroidPrivate::PermissionType permission)
QFuture<QtAndroidPrivate::PermissionResult>
QtAndroidPrivate::checkPermission(const QString &permission)
{
- QPromise<QtAndroidPrivate::PermissionResult> promise;
- QFuture<QtAndroidPrivate::PermissionResult> future = promise.future();
- promise.start();
-
- if (permission.size() > 0) {
+ QtAndroidPrivate::PermissionResult result = Denied;
+ if (!permission.isEmpty()) {
auto res = QJniObject::callStaticMethod<jint>(qtNativeClassName,
"checkSelfPermission",
"(Ljava/lang/String;)I",
QJniObject::fromString(permission).object());
- promise.addResult(resultFromAndroid(res));
- } else {
- promise.addResult(QtAndroidPrivate::Denied);
+ result = resultFromAndroid(res);
}
-
- promise.finish();
- return future;
-}
-
-/*!
- \preliminary
- Checks whether this process has the named \a permission and returns a QFuture
- representing the result of the check.
-
- \since 6.2
- \sa requestPermission()
-*/
-QFuture<QtAndroidPrivate::PermissionResult>
-QtAndroidPrivate::checkPermission(QtAndroidPrivate::PermissionType permission)
-{
- const auto nativePermissions = nativeStringsFromPermission(permission);
-
- if (nativePermissions.size() > 0)
- return checkPermission(nativePermissions.first());
-
- QPromise<QtAndroidPrivate::PermissionResult> promise;
- QFuture<QtAndroidPrivate::PermissionResult> future = promise.future();
- promise.start();
- promise.addResult(QtAndroidPrivate::Denied);
- promise.finish();
- return future;
+ return QtFuture::makeReadyValueFuture(result);
}
-bool QtAndroidPrivate::registerPermissionNatives()
+bool QtAndroidPrivate::registerPermissionNatives(QJniEnvironment &env)
{
if (QtAndroidPrivate::androidSdkVersion() < 23)
return true;
@@ -1282,8 +1220,9 @@ bool QtAndroidPrivate::registerPermissionNatives()
reinterpret_cast<void *>(sendRequestPermissionsResult)
}};
- QJniEnvironment env;
return env.registerNativeMethods(qtNativeClassName, methods, 1);
}
QT_END_NAMESPACE
+
+#include "moc_qandroidextras_p.cpp"
diff --git a/src/corelib/platform/android/qandroidextras_p.h b/src/corelib/platform/android/qandroidextras_p.h
index 00d1f74a47..efdc6cf74f 100644
--- a/src/corelib/platform/android/qandroidextras_p.h
+++ b/src/corelib/platform/android/qandroidextras_p.h
@@ -225,21 +225,6 @@ namespace QtAndroidPrivate
BindFlags flags = BindFlag::None);
#if QT_CONFIG(future)
- enum PermissionType {
- Camera,
- Microphone,
- Bluetooth,
- Location,
- PreciseLocation,
- BackgroundLocation,
- PreciseBackgroundLocation,
- BodySensors,
- PhysicalActivity,
- Contacts,
- Storage,
- Calendar
- };
-
enum PermissionResult {
Undetermined,
Authorized,
@@ -247,12 +232,9 @@ namespace QtAndroidPrivate
};
Q_CORE_EXPORT QFuture<QtAndroidPrivate::PermissionResult>
- requestPermission(QtAndroidPrivate::PermissionType permission);
- Q_CORE_EXPORT QFuture<QtAndroidPrivate::PermissionResult>
requestPermission(const QString &permission);
-
- Q_CORE_EXPORT QFuture<QtAndroidPrivate::PermissionResult>
- checkPermission(QtAndroidPrivate::PermissionType permission);
+ QFuture<QtAndroidPrivate::PermissionResult>
+ requestPermissions(const QStringList &permissions);
Q_CORE_EXPORT QFuture<QtAndroidPrivate::PermissionResult>
checkPermission(const QString &permission);
#endif
diff --git a/src/corelib/platform/android/qandroidnativeinterface.cpp b/src/corelib/platform/android/qandroidnativeinterface.cpp
index a93844139b..fc3a09c78b 100644
--- a/src/corelib/platform/android/qandroidnativeinterface.cpp
+++ b/src/corelib/platform/android/qandroidnativeinterface.cpp
@@ -7,9 +7,13 @@
#include <QtCore/private/qjnihelpers_p.h>
#include <QtCore/qjniobject.h>
#if QT_CONFIG(future) && !defined(QT_NO_QOBJECT)
-#include <QtConcurrent/QtConcurrent>
+#include <QtCore/qfuture.h>
+#include <QtCore/qfuturewatcher.h>
#include <QtCore/qpromise.h>
+#include <QtCore/qtimer.h>
+#include <QtCore/qthreadpool.h>
#include <deque>
+#include <memory>
#endif
QT_BEGIN_NAMESPACE
@@ -17,10 +21,14 @@ QT_BEGIN_NAMESPACE
#if QT_CONFIG(future) && !defined(QT_NO_QOBJECT)
static const char qtNativeClassName[] = "org/qtproject/qt/android/QtNative";
-typedef std::pair<std::function<QVariant()>, QSharedPointer<QPromise<QVariant>>> RunnablePair;
-typedef std::deque<RunnablePair> PendingRunnables;
+struct PendingRunnable {
+ std::function<QVariant()> function;
+ std::shared_ptr<QPromise<QVariant>> promise;
+};
+
+using PendingRunnables = std::deque<PendingRunnable>;
Q_GLOBAL_STATIC(PendingRunnables, g_pendingRunnables);
-static QBasicMutex g_pendingRunnablesMutex;
+Q_CONSTINIT static QBasicMutex g_pendingRunnablesMutex;
#endif
/*!
@@ -38,9 +46,9 @@ static QBasicMutex g_pendingRunnablesMutex;
QT_DEFINE_NATIVE_INTERFACE(QAndroidApplication);
/*!
- \fn jobject QNativeInterface::QAndroidApplication::context()
+ \fn QJniObject QNativeInterface::QAndroidApplication::context()
- Returns the Android context as a \c jobject. The context is an \c Activity
+ Returns the Android context as a \c QJniObject. The context is an \c Activity
if the main activity object is valid. Otherwise, the context is a \c Service.
\since 6.2
@@ -60,7 +68,7 @@ QtJniTypes::Context QNativeInterface::QAndroidApplication::context()
*/
bool QNativeInterface::QAndroidApplication::isActivityContext()
{
- return QtAndroidPrivate::activity();
+ return QtAndroidPrivate::activity().isValid();
}
/*!
@@ -86,8 +94,7 @@ int QNativeInterface::QAndroidApplication::sdkVersion()
*/
void QNativeInterface::QAndroidApplication::hideSplashScreen(int duration)
{
- QJniObject::callStaticMethod<void>("org/qtproject/qt/android/QtNative",
- "hideSplashScreen", "(I)V", duration);
+ QtAndroidPrivate::activity().callMethod<void>("hideSplashScreen", duration);
}
/*!
@@ -155,12 +162,12 @@ QFuture<QVariant> QNativeInterface::QAndroidApplication::runOnAndroidMainThread(
const std::function<QVariant()> &runnable,
const QDeadlineTimer timeout)
{
- QSharedPointer<QPromise<QVariant>> promise(new QPromise<QVariant>());
+ auto promise = std::make_shared<QPromise<QVariant>>();
QFuture<QVariant> future = promise->future();
promise->start();
- (void) QtConcurrent::run([=, &future]() {
- if (!timeout.isForever()) {
+ if (!timeout.isForever()) {
+ QThreadPool::globalInstance()->start([=]() mutable {
QEventLoop loop;
QTimer::singleShot(timeout.remainingTime(), &loop, [&]() {
future.cancel();
@@ -176,12 +183,24 @@ QFuture<QVariant> QNativeInterface::QAndroidApplication::runOnAndroidMainThread(
loop.quit();
});
watcher.setFuture(future);
+
+ // we're going to sleep, make sure we don't block
+ // QThreadPool::globalInstance():
+
+ QThreadPool::globalInstance()->releaseThread();
+ const auto sg = qScopeGuard([] {
+ QThreadPool::globalInstance()->reserveThread();
+ });
loop.exec();
- }
- });
+ });
+ }
QMutexLocker locker(&g_pendingRunnablesMutex);
- g_pendingRunnables->push_back(std::pair(runnable, promise));
+#ifdef __cpp_aggregate_paren_init
+ g_pendingRunnables->emplace_back(runnable, std::move(promise));
+#else
+ g_pendingRunnables->push_back({runnable, std::move(promise)});
+#endif
locker.unlock();
QJniObject::callStaticMethod<void>(qtNativeClassName,
@@ -199,24 +218,23 @@ static void runPendingCppRunnables(JNIEnv */*env*/, jobject /*obj*/)
if (g_pendingRunnables->empty())
break;
- std::pair pair = std::move(g_pendingRunnables->front());
+ PendingRunnable r = std::move(g_pendingRunnables->front());
g_pendingRunnables->pop_front();
locker.unlock();
// run the runnable outside the sync block!
- auto promise = pair.second;
- if (!promise->isCanceled())
- promise->addResult(pair.first());
- promise->finish();
+ if (!r.promise->isCanceled())
+ r.promise->addResult(r.function());
+ r.promise->finish();
}
}
#endif
-bool QtAndroidPrivate::registerNativeInterfaceNatives()
+bool QtAndroidPrivate::registerNativeInterfaceNatives(QJniEnvironment &env)
{
#if QT_CONFIG(future) && !defined(QT_NO_QOBJECT)
const JNINativeMethod methods = {"runPendingCppRunnables", "()V", (void *)runPendingCppRunnables};
- return QJniEnvironment().registerNativeMethods(qtNativeClassName, &methods, 1);
+ return env.registerNativeMethods(qtNativeClassName, &methods, 1);
#else
return true;
#endif
diff --git a/src/corelib/platform/darwin/qdarwinpermissionplugin.mm b/src/corelib/platform/darwin/qdarwinpermissionplugin.mm
new file mode 100644
index 0000000000..5c527f396c
--- /dev/null
+++ b/src/corelib/platform/darwin/qdarwinpermissionplugin.mm
@@ -0,0 +1,90 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qdarwinpermissionplugin_p.h"
+
+QT_BEGIN_NAMESPACE
+
+QDarwinPermissionPlugin::QDarwinPermissionPlugin(QDarwinPermissionHandler *handler)
+ : QPermissionPlugin()
+ , m_handler(handler)
+{
+}
+
+QDarwinPermissionPlugin::~QDarwinPermissionPlugin()
+{
+ [m_handler release];
+}
+
+Qt::PermissionStatus QDarwinPermissionPlugin::checkPermission(const QPermission &permission)
+{
+ return [m_handler checkPermission:permission];
+}
+
+void QDarwinPermissionPlugin::requestPermission(const QPermission &permission, const PermissionCallback &callback)
+{
+ if (!verifyUsageDescriptions(permission)) {
+ callback(Qt::PermissionStatus::Denied);
+ return;
+ }
+
+ [m_handler requestPermission:permission withCallback:[=](Qt::PermissionStatus status) {
+ // In case the callback comes in on a secondary thread we need to marshal it
+ // back to the main thread. And if it doesn't, we still want to propagate it
+ // via an event, to avoid any GCD locks deadlocking the application on iOS
+ // if the user responds to the result by running a nested event loop.
+ // Luckily Qt::QueuedConnection gives us exactly what we need.
+ QMetaObject::invokeMethod(this, "permissionUpdated", Qt::QueuedConnection,
+ Q_ARG(Qt::PermissionStatus, status), Q_ARG(PermissionCallback, callback));
+ }];
+}
+
+void QDarwinPermissionPlugin::permissionUpdated(Qt::PermissionStatus status, const PermissionCallback &callback)
+{
+ callback(status);
+}
+
+bool QDarwinPermissionPlugin::verifyUsageDescriptions(const QPermission &permission)
+{
+ // FIXME: Look up the responsible process and inspect that,
+ // as that's what needs to have the usage descriptions.
+ // FIXME: Verify entitlements if the process is sandboxed.
+ auto *infoDictionary = NSBundle.mainBundle.infoDictionary;
+ for (auto description : [m_handler usageDescriptionsFor:permission]) {
+ if (!infoDictionary[description.toNSString()]) {
+ qCWarning(lcPermissions) <<
+ "Requesting" << permission.type().name() <<
+ "requires" << description << "in Info.plist";
+ return false;
+ }
+ }
+ return true;
+}
+
+QT_END_NAMESPACE
+
+QT_USE_NAMESPACE
+
+@implementation QDarwinPermissionHandler
+
+- (Qt::PermissionStatus)checkPermission:(QPermission)permission
+{
+ Q_UNREACHABLE(); // All handlers should at least provide a check
+}
+
+- (void)requestPermission:(QPermission)permission withCallback:(PermissionCallback)callback
+{
+ Q_UNUSED(permission);
+ qCWarning(lcPermissions).nospace() << "Could not request " << permission.type().name() << ". "
+ << "Please make sure you have included the required usage description in your Info.plist";
+ callback(Qt::PermissionStatus::Denied);
+}
+
+- (QStringList)usageDescriptionsFor:(QPermission)permission
+{
+ return {};
+}
+
+@end
+
+#include "moc_qdarwinpermissionplugin_p.cpp"
diff --git a/src/corelib/platform/darwin/qdarwinpermissionplugin_bluetooth.mm b/src/corelib/platform/darwin/qdarwinpermissionplugin_bluetooth.mm
new file mode 100644
index 0000000000..0cd375561f
--- /dev/null
+++ b/src/corelib/platform/darwin/qdarwinpermissionplugin_bluetooth.mm
@@ -0,0 +1,86 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qdarwinpermissionplugin_p_p.h"
+
+#include <deque>
+
+#include <CoreBluetooth/CoreBluetooth.h>
+
+@interface QDarwinBluetoothPermissionHandler () <CBCentralManagerDelegate>
+@property (nonatomic, retain) CBCentralManager *manager;
+@end
+
+@implementation QDarwinBluetoothPermissionHandler {
+ std::deque<PermissionCallback> m_callbacks;
+}
+
+- (instancetype)init
+{
+ if ((self = [super init]))
+ self.manager = nil;
+
+ return self;
+}
+
+- (Qt::PermissionStatus)checkPermission:(QPermission)permission
+{
+ Q_UNUSED(permission);
+ return [self currentStatus];
+}
+
+- (Qt::PermissionStatus)currentStatus
+{
+ auto status = CBCentralManager.authorization;
+ switch (status) {
+ case CBManagerAuthorizationNotDetermined:
+ return Qt::PermissionStatus::Undetermined;
+ case CBManagerAuthorizationRestricted:
+ case CBManagerAuthorizationDenied:
+ return Qt::PermissionStatus::Denied;
+ case CBManagerAuthorizationAllowedAlways:
+ return Qt::PermissionStatus::Granted;
+ }
+
+ qCWarning(lcPermissions) << "Unknown permission status" << status << "detected in" << self;
+ return Qt::PermissionStatus::Denied;
+}
+
+- (void)requestPermission:(QPermission)permission withCallback:(PermissionCallback)callback
+{
+ m_callbacks.push_back(callback);
+ if (!self.manager) {
+ self.manager = [[[CBCentralManager alloc]
+ initWithDelegate:self queue:dispatch_get_main_queue()] autorelease];
+ }
+}
+
+- (void)centralManagerDidUpdateState:(CBCentralManager *)manager
+{
+ Q_ASSERT(manager == self.manager);
+ Q_ASSERT(!m_callbacks.empty());
+
+ auto status = [self currentStatus];
+
+ for (auto callback : m_callbacks)
+ callback(status);
+
+ m_callbacks = {};
+ self.manager = nil;
+}
+
+- (QStringList)usageDescriptionsFor:(QPermission)permission
+{
+ Q_UNUSED(permission);
+#ifdef Q_OS_MACOS
+ if (QOperatingSystemVersion::current() > QOperatingSystemVersion::MacOSBigSur)
+#endif
+ {
+ return { "NSBluetoothAlwaysUsageDescription" };
+ }
+
+ return {};
+}
+@end
+
+#include "moc_qdarwinpermissionplugin_p_p.cpp"
diff --git a/src/corelib/platform/darwin/qdarwinpermissionplugin_calendar.mm b/src/corelib/platform/darwin/qdarwinpermissionplugin_calendar.mm
new file mode 100644
index 0000000000..a3eddd6d8f
--- /dev/null
+++ b/src/corelib/platform/darwin/qdarwinpermissionplugin_calendar.mm
@@ -0,0 +1,71 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qdarwinpermissionplugin_p_p.h"
+
+#include <EventKit/EventKit.h>
+
+@interface QDarwinCalendarPermissionHandler ()
+@property (nonatomic, retain) EKEventStore *eventStore;
+@end
+
+@implementation QDarwinCalendarPermissionHandler
+- (Qt::PermissionStatus)checkPermission:(QPermission)permission
+{
+ Q_UNUSED(permission);
+ return [self currentStatus];
+}
+
+- (Qt::PermissionStatus)currentStatus
+{
+ auto status = [EKEventStore authorizationStatusForEntityType:EKEntityTypeEvent];
+ switch (status) {
+ case EKAuthorizationStatusNotDetermined:
+ return Qt::PermissionStatus::Undetermined;
+ case EKAuthorizationStatusRestricted:
+ case EKAuthorizationStatusDenied:
+ return Qt::PermissionStatus::Denied;
+ case EKAuthorizationStatusAuthorized:
+ return Qt::PermissionStatus::Granted;
+#if QT_MACOS_IOS_PLATFORM_SDK_EQUAL_OR_ABOVE(140000, 170000)
+ case EKAuthorizationStatusWriteOnly:
+ // FIXME: Add WriteOnly AccessMode
+ return Qt::PermissionStatus::Denied;
+#endif
+ }
+
+ qCWarning(lcPermissions) << "Unknown permission status" << status << "detected in" << self;
+ return Qt::PermissionStatus::Denied;
+}
+
+- (QStringList)usageDescriptionsFor:(QPermission)permission
+{
+ Q_UNUSED(permission);
+ return { "NSCalendarsUsageDescription" };
+}
+
+- (void)requestPermission:(QPermission)permission withCallback:(PermissionCallback)callback
+{
+ if (!self.eventStore) {
+ // Note: Creating the EKEventStore results in warnings in the
+ // console about "An error occurred in the persistent store".
+ // This seems like a EventKit API bug.
+ self.eventStore = [[EKEventStore new] autorelease];
+ }
+
+ [self.eventStore requestAccessToEntityType:EKEntityTypeEvent
+ completion:^(BOOL granted, NSError * _Nullable error) {
+ Q_UNUSED(granted); // We use status instead
+ // Permission denied will result in an error, which we don't
+ // want to report/log, so we ignore the error and just report
+ // the status.
+ Q_UNUSED(error);
+
+ callback([self currentStatus]);
+ }
+ ];
+}
+
+@end
+
+#include "moc_qdarwinpermissionplugin_p_p.cpp"
diff --git a/src/corelib/platform/darwin/qdarwinpermissionplugin_camera.mm b/src/corelib/platform/darwin/qdarwinpermissionplugin_camera.mm
new file mode 100644
index 0000000000..51c517d6f3
--- /dev/null
+++ b/src/corelib/platform/darwin/qdarwinpermissionplugin_camera.mm
@@ -0,0 +1,42 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qdarwinpermissionplugin_p_p.h"
+
+#include <AVFoundation/AVFoundation.h>
+
+QT_DEFINE_PERMISSION_STATUS_CONVERTER(AVAuthorizationStatus);
+
+#ifndef BUILDING_PERMISSION_REQUEST
+
+@implementation QDarwinCameraPermissionHandler
+- (Qt::PermissionStatus)checkPermission:(QPermission)permission
+{
+ const auto status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
+ return nativeStatusToQtStatus(status);
+}
+
+- (QStringList)usageDescriptionsFor:(QPermission)permission
+{
+ Q_UNUSED(permission);
+ return { "NSCameraUsageDescription" };
+}
+@end
+
+#include "moc_qdarwinpermissionplugin_p_p.cpp"
+
+#else // Building request
+
+@implementation QDarwinCameraPermissionHandler (Request)
+- (void)requestPermission:(QPermission)permission withCallback:(PermissionCallback)callback
+{
+ [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted)
+ {
+ Q_UNUSED(granted); // We use status instead
+ const auto status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
+ callback(nativeStatusToQtStatus(status));
+ }];
+}
+@end
+
+#endif // BUILDING_PERMISSION_REQUEST
diff --git a/src/corelib/platform/darwin/qdarwinpermissionplugin_contacts.mm b/src/corelib/platform/darwin/qdarwinpermissionplugin_contacts.mm
new file mode 100644
index 0000000000..3221b6dc1d
--- /dev/null
+++ b/src/corelib/platform/darwin/qdarwinpermissionplugin_contacts.mm
@@ -0,0 +1,58 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qdarwinpermissionplugin_p_p.h"
+
+#include <Contacts/Contacts.h>
+
+QT_DEFINE_PERMISSION_STATUS_CONVERTER(CNAuthorizationStatus);
+
+@interface QDarwinContactsPermissionHandler ()
+@property (nonatomic, retain) CNContactStore *contactStore;
+@end
+
+@implementation QDarwinContactsPermissionHandler
+- (Qt::PermissionStatus)checkPermission:(QPermission)permission
+{
+ Q_UNUSED(permission);
+ return [self currentStatus];
+}
+
+- (Qt::PermissionStatus)currentStatus
+{
+ const auto status = [CNContactStore authorizationStatusForEntityType:CNEntityTypeContacts];
+ return nativeStatusToQtStatus(status);
+}
+
+- (QStringList)usageDescriptionsFor:(QPermission)permission
+{
+ Q_UNUSED(permission);
+ return { "NSContactsUsageDescription" };
+}
+
+- (void)requestPermission:(QPermission)permission withCallback:(PermissionCallback)callback
+{
+ if (!self.contactStore) {
+ // Note: Creating the CNContactStore results in warnings in the
+ // console about "Attempted to register account monitor for types
+ // client is not authorized to access", mentioning CardDAV, LDAP,
+ // and Exchange. This seems like a Contacts API bug.
+ self.contactStore = [[CNContactStore new] autorelease];
+ }
+
+ [self.contactStore requestAccessForEntityType:CNEntityTypeContacts
+ completionHandler:^(BOOL granted, NSError * _Nullable error) {
+ Q_UNUSED(granted); // We use status instead
+ // Permission denied will result in an error, which we don't
+ // want to report/log, so we ignore the error and just report
+ // the status.
+ Q_UNUSED(error);
+
+ callback([self currentStatus]);
+ }
+ ];
+}
+
+@end
+
+#include "moc_qdarwinpermissionplugin_p_p.cpp"
diff --git a/src/corelib/platform/darwin/qdarwinpermissionplugin_location.mm b/src/corelib/platform/darwin/qdarwinpermissionplugin_location.mm
new file mode 100644
index 0000000000..1d32c0fcac
--- /dev/null
+++ b/src/corelib/platform/darwin/qdarwinpermissionplugin_location.mm
@@ -0,0 +1,263 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qdarwinpermissionplugin_p_p.h"
+
+#include <deque>
+
+#include <CoreLocation/CoreLocation.h>
+
+@interface QDarwinLocationPermissionHandler () <CLLocationManagerDelegate>
+@property (nonatomic, retain) CLLocationManager *manager;
+@end
+
+Q_LOGGING_CATEGORY(lcLocationPermission, "qt.permissions.location");
+
+void warmUpLocationServices()
+{
+ // After creating a CLLocationManager the authorizationStatus
+ // will initially be kCLAuthorizationStatusNotDetermined. The
+ // status will then update to an actual status if the app was
+ // previously authorized/denied once the location services
+ // do some initial book-keeping in the background. By kicking
+ // off a CLLocationManager early on here, we ensure that by
+ // the time the user calls checkPermission the authorization
+ // status has been resolved.
+ qCDebug(lcLocationPermission) << "Warming up location services";
+ [[CLLocationManager new] release];
+}
+
+Q_CONSTRUCTOR_FUNCTION(warmUpLocationServices);
+
+struct PermissionRequest
+{
+ QPermission permission;
+ PermissionCallback callback;
+};
+
+@implementation QDarwinLocationPermissionHandler {
+ std::deque<PermissionRequest> m_requests;
+}
+
+- (instancetype)init
+{
+ if ((self = [super init])) {
+ // The delegate callbacks will come in on the thread that
+ // the CLLocationManager is created on, and we want those
+ // to come in on the main thread, so we defer creation
+ // of the manger until requestPermission, where we know
+ // we are on the main thread.
+ self.manager = nil;
+ }
+
+ return self;
+}
+
+- (Qt::PermissionStatus)checkPermission:(QPermission)permission
+{
+ const auto locationPermission = *permission.value<QLocationPermission>();
+
+ auto status = [self authorizationStatus:locationPermission];
+ if (status != Qt::PermissionStatus::Granted)
+ return status;
+
+ return [self accuracyAuthorization:locationPermission];
+}
+
+- (Qt::PermissionStatus)authorizationStatus:(QLocationPermission)permission
+{
+ NSString *bundleIdentifier = NSBundle.mainBundle.bundleIdentifier;
+ if (!bundleIdentifier || !bundleIdentifier.length) {
+ qCWarning(lcLocationPermission) << "Missing bundle identifier"
+ << "in Info.plist. Can not use location permissions.";
+ return Qt::PermissionStatus::Denied;
+ }
+
+#if defined(Q_OS_VISIONOS)
+ if (permission.availability() == QLocationPermission::Always)
+ return Qt::PermissionStatus::Denied;
+#endif
+
+ auto status = [self authorizationStatus];
+ switch (status) {
+ case kCLAuthorizationStatusRestricted:
+ case kCLAuthorizationStatusDenied:
+ return Qt::PermissionStatus::Denied;
+ case kCLAuthorizationStatusNotDetermined:
+ return Qt::PermissionStatus::Undetermined;
+#if !defined(Q_OS_VISIONOS)
+ case kCLAuthorizationStatusAuthorizedAlways:
+ return Qt::PermissionStatus::Granted;
+#endif
+#if defined(Q_OS_IOS) || defined(Q_OS_VISIONOS)
+ case kCLAuthorizationStatusAuthorizedWhenInUse:
+ if (permission.availability() == QLocationPermission::Always)
+ return Qt::PermissionStatus::Denied;
+ return Qt::PermissionStatus::Granted;
+#endif
+ }
+
+ qCWarning(lcPermissions) << "Unknown permission status" << status << "detected in" << self;
+ return Qt::PermissionStatus::Denied;
+}
+
+- (CLAuthorizationStatus)authorizationStatus
+{
+ if (self.manager) {
+ if (@available(macOS 11, iOS 14, *))
+ return self.manager.authorizationStatus;
+ }
+
+ return QT_IGNORE_DEPRECATIONS(CLLocationManager.authorizationStatus);
+}
+
+- (Qt::PermissionStatus)accuracyAuthorization:(QLocationPermission)permission
+{
+ auto status = CLAccuracyAuthorizationReducedAccuracy;
+ if (@available(macOS 11, iOS 14, *))
+ status = self.manager.accuracyAuthorization;
+
+ switch (status) {
+ case CLAccuracyAuthorizationFullAccuracy:
+ return Qt::PermissionStatus::Granted;
+ case CLAccuracyAuthorizationReducedAccuracy:
+ if (permission.accuracy() == QLocationPermission::Approximate)
+ return Qt::PermissionStatus::Granted;
+ else
+ return Qt::PermissionStatus::Denied;
+ }
+
+ qCWarning(lcPermissions) << "Unknown accuracy status" << status << "detected in" << self;
+ return Qt::PermissionStatus::Denied;
+}
+
+- (QStringList)usageDescriptionsFor:(QPermission)permission
+{
+#if defined(Q_OS_MACOS)
+ return { "NSLocationUsageDescription" };
+#else // iOS 11 and above
+ QStringList usageDescriptions = { "NSLocationWhenInUseUsageDescription" };
+ const auto locationPermission = *permission.value<QLocationPermission>();
+ if (locationPermission.availability() == QLocationPermission::Always)
+ usageDescriptions << "NSLocationAlwaysAndWhenInUseUsageDescription";
+ return usageDescriptions;
+#endif
+}
+
+- (void)requestPermission:(QPermission)permission withCallback:(PermissionCallback)callback
+{
+ const bool requestAlreadyInFlight = !m_requests.empty();
+
+ m_requests.push_back({ permission, callback });
+
+ if (requestAlreadyInFlight) {
+ qCDebug(lcLocationPermission).nospace() << "Already processing "
+ << m_requests.front().permission << ". Deferring request";
+ } else {
+ [self requestQueuedPermission];
+ }
+}
+
+- (void)requestQueuedPermission
+{
+ Q_ASSERT(!m_requests.empty());
+ const auto permission = m_requests.front().permission;
+
+ qCDebug(lcLocationPermission) << "Requesting" << permission;
+
+ if (!self.manager) {
+ self.manager = [[CLLocationManager new] autorelease];
+ self.manager.delegate = self;
+ }
+
+ const auto locationPermission = *permission.value<QLocationPermission>();
+ switch (locationPermission.availability()) {
+ case QLocationPermission::WhenInUse:
+ // The documentation specifies that requestWhenInUseAuthorization can
+ // only be called when the current authorization status is undetermined.
+ switch ([self authorizationStatus]) {
+ case kCLAuthorizationStatusNotDetermined:
+ [self.manager requestWhenInUseAuthorization];
+ break;
+ default:
+ [self deliverResult];
+ }
+ break;
+ case QLocationPermission::Always:
+#if defined(Q_OS_VISIONOS)
+ [self deliverResult]; // Not supported
+#else
+ // The documentation specifies that requestAlwaysAuthorization can only
+ // be called when the current authorization status is either undetermined,
+ // or authorized when in use.
+ switch ([self authorizationStatus]) {
+ case kCLAuthorizationStatusNotDetermined:
+ [self.manager requestAlwaysAuthorization];
+ break;
+#ifdef Q_OS_IOS
+ case kCLAuthorizationStatusAuthorizedWhenInUse:
+ // Unfortunately when asking for AlwaysAuthorization when in
+ // the WhenInUse state, to "upgrade" the permission, the iOS
+ // location system will not give us a callback if the user
+ // denies the request, leaving us hanging without a way to
+ // respond to the permission request.
+ qCWarning(lcLocationPermission) << "QLocationPermission::WhenInUse"
+ << "can not be upgraded to QLocationPermission::Always on iOS."
+ << "Please request QLocationPermission::Always directly.";
+ Q_FALLTHROUGH();
+#endif
+ default:
+ [self deliverResult];
+ }
+#endif
+ break;
+ }
+}
+
+- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status
+{
+ qCDebug(lcLocationPermission) << "Processing authorization"
+ << "update with status" << status;
+
+ if (m_requests.empty()) {
+ qCDebug(lcLocationPermission) << "No requests in flight. Ignoring.";
+ return;
+ }
+
+ if (status == kCLAuthorizationStatusNotDetermined) {
+ // Initializing a CLLocationManager will result in an initial
+ // callback to the delegate even before we've requested any
+ // location permissions. Normally we would ignore this callback
+ // due to the request queue check above, but if this callback
+ // comes in after the application has requested a permission
+ // we don't want to report the undetermined status, but rather
+ // wait for the actual result to come in.
+ qCDebug(lcLocationPermission) << "Ignoring delegate callback"
+ << "with status kCLAuthorizationStatusNotDetermined";
+ return;
+ }
+
+ [self deliverResult];
+}
+
+- (void)deliverResult
+{
+ auto request = m_requests.front();
+ m_requests.pop_front();
+
+ auto status = [self checkPermission:request.permission];
+ qCDebug(lcLocationPermission) << "Result for"
+ << request.permission << "was" << status;
+
+ request.callback(status);
+
+ if (!m_requests.empty()) {
+ qCDebug(lcLocationPermission) << "Still have"
+ << m_requests.size() << "deferred request(s)";
+ [self requestQueuedPermission];
+ }
+}
+
+@end
+
+#include "moc_qdarwinpermissionplugin_p_p.cpp"
diff --git a/src/corelib/platform/darwin/qdarwinpermissionplugin_microphone.mm b/src/corelib/platform/darwin/qdarwinpermissionplugin_microphone.mm
new file mode 100644
index 0000000000..5dc434309d
--- /dev/null
+++ b/src/corelib/platform/darwin/qdarwinpermissionplugin_microphone.mm
@@ -0,0 +1,42 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qdarwinpermissionplugin_p_p.h"
+
+#include <AVFoundation/AVFoundation.h>
+
+QT_DEFINE_PERMISSION_STATUS_CONVERTER(AVAuthorizationStatus);
+
+#ifndef BUILDING_PERMISSION_REQUEST
+
+@implementation QDarwinMicrophonePermissionHandler
+- (Qt::PermissionStatus)checkPermission:(QPermission)permission
+{
+ const auto status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio];
+ return nativeStatusToQtStatus(status);
+}
+
+- (QStringList)usageDescriptionsFor:(QPermission)permission
+{
+ Q_UNUSED(permission);
+ return { "NSMicrophoneUsageDescription" };
+}
+@end
+
+#include "moc_qdarwinpermissionplugin_p_p.cpp"
+
+#else // Building request
+
+@implementation QDarwinMicrophonePermissionHandler (Request)
+- (void)requestPermission:(QPermission)permission withCallback:(PermissionCallback)callback
+{
+ [AVCaptureDevice requestAccessForMediaType:AVMediaTypeAudio completionHandler:^(BOOL granted)
+ {
+ Q_UNUSED(granted); // We use status instead
+ const auto status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio];
+ callback(nativeStatusToQtStatus(status));
+ }];
+}
+@end
+
+#endif // BUILDING_PERMISSION_REQUEST
diff --git a/src/corelib/platform/darwin/qdarwinpermissionplugin_p.h b/src/corelib/platform/darwin/qdarwinpermissionplugin_p.h
new file mode 100644
index 0000000000..03530133ad
--- /dev/null
+++ b/src/corelib/platform/darwin/qdarwinpermissionplugin_p.h
@@ -0,0 +1,58 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QDARWINPERMISSIONPLUGIN_P_H
+#define QDARWINPERMISSIONPLUGIN_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. This header file may change
+// from version to version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/qnamespace.h>
+#include <QtCore/private/qpermissions_p.h>
+#include <QtCore/private/qcore_mac_p.h>
+
+#if defined(__OBJC__)
+#include <Foundation/NSObject.h>
+#endif
+
+QT_USE_NAMESPACE
+
+using namespace QPermissions::Private;
+
+#if defined(__OBJC__)
+Q_CORE_EXPORT
+#endif
+QT_DECLARE_NAMESPACED_OBJC_INTERFACE(QDarwinPermissionHandler, NSObject
+- (Qt::PermissionStatus)checkPermission:(QPermission)permission;
+- (void)requestPermission:(QPermission)permission withCallback:(PermissionCallback)callback;
+- (QStringList)usageDescriptionsFor:(QPermission)permission;
+)
+
+QT_BEGIN_NAMESPACE
+
+class Q_CORE_EXPORT QDarwinPermissionPlugin : public QPermissionPlugin
+{
+ Q_OBJECT
+public:
+ QDarwinPermissionPlugin(QDarwinPermissionHandler *handler);
+ ~QDarwinPermissionPlugin();
+
+ Qt::PermissionStatus checkPermission(const QPermission &permission) override;
+ void requestPermission(const QPermission &permission, const PermissionCallback &callback) override;
+
+private:
+ Q_SLOT void permissionUpdated(Qt::PermissionStatus status, const PermissionCallback &callback);
+ bool verifyUsageDescriptions(const QPermission &permission);
+ QDarwinPermissionHandler *m_handler = nullptr;
+};
+
+QT_END_NAMESPACE
+
+#endif // QDARWINPERMISSIONPLUGIN_P_H
diff --git a/src/corelib/platform/darwin/qdarwinpermissionplugin_p_p.h b/src/corelib/platform/darwin/qdarwinpermissionplugin_p_p.h
new file mode 100644
index 0000000000..649af06507
--- /dev/null
+++ b/src/corelib/platform/darwin/qdarwinpermissionplugin_p_p.h
@@ -0,0 +1,104 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QDARWINPERMISSIONPLUGIN_P_P_H
+#define QDARWINPERMISSIONPLUGIN_P_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. This header file may change
+// from version to version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#if !defined(QT_DARWIN_PERMISSION_PLUGIN)
+#error "This header should only be included from permission plugins"
+#endif
+
+#include <QtCore/qnamespace.h>
+#include <QtCore/private/qpermissions_p.h>
+#include <QtCore/private/qcore_mac_p.h>
+
+#include "qdarwinpermissionplugin_p.h"
+
+using namespace QPermissions::Private;
+
+#ifndef QT_JOIN
+#define QT_JOIN_IMPL(A, B) A ## B
+#define QT_JOIN(A, B) QT_JOIN_IMPL(A, B)
+#endif
+
+#define PERMISSION_PLUGIN_NAME(SUFFIX) \
+ QT_JOIN(QT_JOIN(QT_JOIN( \
+ QDarwin, QT_DARWIN_PERMISSION_PLUGIN), Permission), SUFFIX)
+
+#define PERMISSION_PLUGIN_CLASSNAME PERMISSION_PLUGIN_NAME(Plugin)
+#define PERMISSION_PLUGIN_HANDLER PERMISSION_PLUGIN_NAME(Handler)
+
+QT_DECLARE_NAMESPACED_OBJC_INTERFACE(
+ PERMISSION_PLUGIN_HANDLER,
+ QDarwinPermissionHandler
+)
+
+QT_BEGIN_NAMESPACE
+
+class Q_CORE_EXPORT PERMISSION_PLUGIN_CLASSNAME : public QDarwinPermissionPlugin
+{
+ Q_OBJECT
+ Q_PLUGIN_METADATA(
+ IID QPermissionPluginInterface_iid
+ FILE "QDarwin" QT_STRINGIFY(QT_DARWIN_PERMISSION_PLUGIN) "PermissionPlugin.json")
+public:
+ PERMISSION_PLUGIN_CLASSNAME()
+ : QDarwinPermissionPlugin([[PERMISSION_PLUGIN_HANDLER alloc] init])
+ {}
+};
+
+QT_END_NAMESPACE
+
+// Request
+#if defined(BUILDING_PERMISSION_REQUEST)
+extern "C" void PERMISSION_PLUGIN_NAME(Request)() {}
+#endif
+
+// -------------------------------------------------------
+
+namespace {
+template <typename NativeStatus>
+struct NativeStatusHelper;
+
+template <typename NativeStatus>
+Qt::PermissionStatus nativeStatusToQtStatus(NativeStatus status)
+{
+ using Converter = NativeStatusHelper<NativeStatus>;
+ switch (status) {
+ case Converter::Authorized:
+ return Qt::PermissionStatus::Granted;
+ case Converter::Denied:
+ case Converter::Restricted:
+ return Qt::PermissionStatus::Denied;
+ case Converter::Undetermined:
+ return Qt::PermissionStatus::Undetermined;
+ }
+ qCWarning(lcPermissions) << "Unknown permission status" << status << "detected in"
+ << QT_STRINGIFY(QT_DARWIN_PERMISSION_PLUGIN);
+ return Qt::PermissionStatus::Denied;
+}
+} // namespace
+
+#define QT_DEFINE_PERMISSION_STATUS_CONVERTER(NativeStatus) \
+namespace { template<> \
+struct NativeStatusHelper<NativeStatus> \
+{\
+ enum { \
+ Authorized = NativeStatus##Authorized, \
+ Denied = NativeStatus##Denied, \
+ Restricted = NativeStatus##Restricted, \
+ Undetermined = NativeStatus##NotDetermined \
+ }; \
+}; }
+
+#endif // QDARWINPERMISSIONPLUGIN_P_P_H
diff --git a/src/corelib/platform/ios/PrivacyInfo.xcprivacy b/src/corelib/platform/ios/PrivacyInfo.xcprivacy
new file mode 100644
index 0000000000..5f84a229a5
--- /dev/null
+++ b/src/corelib/platform/ios/PrivacyInfo.xcprivacy
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>NSPrivacyTracking</key>
+ <false/>
+ <key>NSPrivacyCollectedDataTypes</key>
+ <array/>
+ <key>NSPrivacyTrackingDomains</key>
+ <array/>
+ <key>NSPrivacyAccessedAPITypes</key>
+ <array>
+ <dict>
+ <key>NSPrivacyAccessedAPIType</key>
+ <string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
+ <key>NSPrivacyAccessedAPITypeReasons</key>
+ <array>
+ <string>0A2A.1</string> <!-- QFileInfo -->
+ </array>
+ </dict>
+ <dict>
+ <key>NSPrivacyAccessedAPIType</key>
+ <string>NSPrivacyAccessedAPICategoryDiskSpace</string>
+ <key>NSPrivacyAccessedAPITypeReasons</key>
+ <array>
+ <string>85F4.1</string> <!-- QStorageInfo -->
+ </array>
+ </dict>
+ </array>
+</dict>
+</plist>
diff --git a/src/corelib/platform/wasm/qstdweb.cpp b/src/corelib/platform/wasm/qstdweb.cpp
index 486bfaf485..75e76a6806 100644
--- a/src/corelib/platform/wasm/qstdweb.cpp
+++ b/src/corelib/platform/wasm/qstdweb.cpp
@@ -5,8 +5,13 @@
#include <QtCore/qcoreapplication.h>
#include <QtCore/qfile.h>
+#include <QtCore/qmimedata.h>
+
#include <emscripten/bind.h>
#include <emscripten/emscripten.h>
+#include <emscripten/html5.h>
+#include <emscripten/threading.h>
+
#include <cstdint>
#include <iostream>
@@ -14,10 +19,74 @@
QT_BEGIN_NAMESPACE
+using namespace Qt::Literals::StringLiterals;
+
namespace qstdweb {
+static void usePotentialyUnusedSymbols()
+{
+ // Using this adds a reference on JSEvents and specialHTMLTargets which are always exported.
+ // This hack is needed as it is currently impossible to specify a dollar sign in
+ // target_link_options. The following is impossible:
+ // DEFAULT_LIBRARY_FUNCS_TO_INCLUDE=$JSEvents
+ // TODO(mikolajboc): QTBUG-108444, review this when cmake gets fixed.
+ // Volatile is to make this unoptimizable, so that the function is referenced, but is not
+ // called at runtime.
+ volatile bool doIt = false;
+ if (doIt)
+ emscripten_set_wheel_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, 0, NULL);
+}
+
+Q_CONSTRUCTOR_FUNCTION(usePotentialyUnusedSymbols)
typedef double uint53_t; // see Number.MAX_SAFE_INTEGER
namespace {
+// Reads file in chunks in order to avoid holding two copies in memory at the same time
+struct ChunkedFileReader
+{
+public:
+ static void read(File file, char *buffer, uint32_t offset, uint32_t end,
+ std::function<void()> onCompleted)
+ {
+ (new ChunkedFileReader(end, std::move(onCompleted), std::move(file)))
+ ->readNextChunk(offset, buffer);
+ }
+
+private:
+ ChunkedFileReader(uint32_t end, std::function<void()> onCompleted, File file)
+ : end(end), onCompleted(std::move(onCompleted)), file(std::move(file))
+ {
+ }
+
+ void readNextChunk(uint32_t chunkBegin, char *chunkBuffer)
+ {
+ // Copy current chunk from JS memory to Wasm memory
+ qstdweb::ArrayBuffer result = fileReader.result();
+ qstdweb::Uint8Array(result).copyTo(chunkBuffer);
+
+ // Read next chunk if not at buffer end
+ const uint32_t nextChunkBegin = std::min(chunkBegin + result.byteLength(), end);
+ if (nextChunkBegin == end) {
+ onCompleted();
+ delete this;
+ return;
+ }
+ char *nextChunkBuffer = chunkBuffer + result.byteLength();
+ fileReader.onLoad([this, nextChunkBegin, nextChunkBuffer](emscripten::val) {
+ readNextChunk(nextChunkBegin, nextChunkBuffer);
+ });
+ const uint32_t nextChunkEnd = std::min(nextChunkBegin + chunkSize, end);
+ qstdweb::Blob blob = file.slice(nextChunkBegin, nextChunkEnd);
+ fileReader.readAsArrayBuffer(blob);
+ }
+
+ static constexpr uint32_t chunkSize = 256 * 1024;
+
+ qstdweb::FileReader fileReader;
+ uint32_t end;
+ std::function<void()> onCompleted;
+ File file;
+};
+
enum class CallbackType {
Then,
Catch,
@@ -69,11 +138,12 @@ public:
"catch",
emscripten::val::module_property(thunkName(CallbackType::Catch, id()).data()));
}
- if (callbacks.finallyFunc) {
- target = target.call<val>(
- "finally",
- emscripten::val::module_property(thunkName(CallbackType::Finally, id()).data()));
- }
+ // Guarantee the invocation of at least one callback by always
+ // registering 'finally'. This is required by WebPromiseManager
+ // design
+ target = target.call<val>(
+ "finally", emscripten::val::module_property(
+ thunkName(CallbackType::Finally, id()).data()));
}
private:
@@ -254,25 +324,21 @@ void WebPromiseManager::promiseThunkCallback(int context, CallbackType type, ems
auto* promiseState = &m_promiseRegistry[context];
auto* callbacks = &promiseState->callbacks;
- bool expectingOtherCallbacks;
switch (type) {
case CallbackType::Then:
callbacks->thenFunc(result);
- // At this point, if there is no finally function, we are sure that the Catch callback won't be issued.
- expectingOtherCallbacks = !!callbacks->finallyFunc;
break;
case CallbackType::Catch:
callbacks->catchFunc(result);
- expectingOtherCallbacks = !!callbacks->finallyFunc;
break;
case CallbackType::Finally:
- callbacks->finallyFunc();
- expectingOtherCallbacks = false;
+ // Final callback may be empty, used solely for promise unregistration
+ if (callbacks->finallyFunc) {
+ callbacks->finallyFunc();
+ }
+ unregisterPromise(context);
break;
- }
-
- if (!expectingOtherCallbacks)
- unregisterPromise(context);
+ }
}
void WebPromiseManager::registerPromise(
@@ -295,7 +361,19 @@ void WebPromiseManager::adoptPromise(emscripten::val target, PromiseCallbacks ca
registerPromise(std::move(allocation), std::move(callbacks));
});
}
-} // namespace
+#if defined(QT_STATIC)
+
+EM_JS(bool, jsHaveAsyncify, (), { return typeof Asyncify !== "undefined"; });
+EM_JS(bool, jsHaveJspi, (),
+ { return typeof Asyncify !== "undefined" && !!Asyncify.makeAsyncFunction && !!WebAssembly.Function; });
+
+#else
+
+bool jsHaveAsyncify() { return false; }
+bool jsHaveJspi() { return false; }
+
+#endif
+} // namespace
ArrayBuffer::ArrayBuffer(uint32_t size)
{
@@ -316,7 +394,12 @@ uint32_t ArrayBuffer::byteLength() const
return m_arrayBuffer["byteLength"].as<uint32_t>();
}
-emscripten::val ArrayBuffer::val()
+ArrayBuffer ArrayBuffer::slice(uint32_t begin, uint32_t end) const
+{
+ return ArrayBuffer(m_arrayBuffer.call<emscripten::val>("slice", begin, end));
+}
+
+emscripten::val ArrayBuffer::val() const
{
return m_arrayBuffer;
}
@@ -327,24 +410,55 @@ Blob::Blob(const emscripten::val &blob)
}
+Blob Blob::fromArrayBuffer(const ArrayBuffer &arrayBuffer)
+{
+ auto array = emscripten::val::array();
+ array.call<void>("push", arrayBuffer.val());
+ return Blob(emscripten::val::global("Blob").new_(array));
+}
+
uint32_t Blob::size() const
{
return m_blob["size"].as<uint32_t>();
}
-// Copies content from the given buffer into a Blob object
-Blob Blob::copyFrom(const char *buffer, uint32_t size)
+Blob Blob::copyFrom(const char *buffer, uint32_t size, std::string mimeType)
{
Uint8Array contentCopy = Uint8Array::copyFrom(buffer, size);
emscripten::val contentArray = emscripten::val::array();
contentArray.call<void>("push", contentCopy.val());
emscripten::val type = emscripten::val::object();
- type.set("type","application/octet-stream");
+ type.set("type", std::move(mimeType));
return Blob(emscripten::val::global("Blob").new_(contentArray, type));
}
-emscripten::val Blob::val()
+// Copies content from the given buffer into a Blob object
+Blob Blob::copyFrom(const char *buffer, uint32_t size)
+{
+ return copyFrom(buffer, size, "application/octet-stream");
+}
+
+Blob Blob::slice(uint32_t begin, uint32_t end) const
+{
+ return Blob(m_blob.call<emscripten::val>("slice", begin, end));
+}
+
+ArrayBuffer Blob::arrayBuffer_sync() const
+{
+ QEventLoop loop;
+ emscripten::val buffer;
+ qstdweb::Promise::make(m_blob, QStringLiteral("arrayBuffer"), {
+ .thenFunc = [&loop, &buffer](emscripten::val arrayBuffer) {
+ buffer = arrayBuffer;
+ loop.quit();
+ }
+ });
+ loop.exec();
+ return ArrayBuffer(buffer);
+}
+
+emscripten::val Blob::val() const
{
return m_blob;
}
@@ -355,6 +469,17 @@ File::File(const emscripten::val &file)
}
+File::~File() = default;
+
+File::File(const File &other) = default;
+
+File::File(File &&other) = default;
+
+File &File::operator=(const File &other) = default;
+
+File &File::operator=(File &&other) = default;
+
+
Blob File::slice(uint64_t begin, uint64_t end) const
{
return Blob(m_file.call<emscripten::val>("slice", uint53_t(begin), uint53_t(end)));
@@ -377,47 +502,17 @@ std::string Blob::type() const
// Streams partial file content into the given buffer asynchronously. The completed
// callback is called on completion.
-void File::stream(uint32_t offset, uint32_t length, char *buffer, const std::function<void ()> &completed) const
+void File::stream(uint32_t offset, uint32_t length, char *buffer,
+ std::function<void()> completed) const
{
- // Read file in chunks in order to avoid holding two copies in memory at the same time
- const uint32_t chunkSize = 256 * 1024;
- const uint32_t end = offset + length;
- // assert end < file.size
- auto fileReader = std::make_shared<qstdweb::FileReader>();
-
- // "this" is valid now, but may not be by the time the chunkCompleted callback
- // below is made. Make a copy of the file handle.
- const File fileHandle = *this;
- auto chunkCompleted = std::make_shared<std::function<void (uint32_t, char *buffer)>>();
- *chunkCompleted = [=](uint32_t chunkBegin, char *chunkBuffer) mutable {
-
- // Copy current chunk from JS memory to Wasm memory
- qstdweb::ArrayBuffer result = fileReader->result();
- qstdweb::Uint8Array(result).copyTo(chunkBuffer);
-
- // Read next chunk if not at buffer end
- uint32_t nextChunkBegin = std::min(chunkBegin + result.byteLength(), end);
- uint32_t nextChunkEnd = std::min(nextChunkBegin + chunkSize, end);
- if (nextChunkBegin == end) {
- completed();
- chunkCompleted.reset();
- return;
- }
- char *nextChunkBuffer = chunkBuffer + result.byteLength();
- fileReader->onLoad([=](emscripten::val) { (*chunkCompleted)(nextChunkBegin, nextChunkBuffer); });
- qstdweb::Blob blob = fileHandle.slice(nextChunkBegin, nextChunkEnd);
- fileReader->readAsArrayBuffer(blob);
- };
-
- // Read first chunk. First iteration is a dummy iteration with no available data.
- (*chunkCompleted)(offset, buffer);
+ ChunkedFileReader::read(*this, buffer, offset, offset + length, std::move(completed));
}
// Streams file content into the given buffer asynchronously. The completed
// callback is called on completion.
-void File::stream(char *buffer, const std::function<void ()> &completed) const
+void File::stream(char *buffer, std::function<void()> completed) const
{
- stream(0, size(), buffer, completed);
+ stream(0, size(), buffer, std::move(completed));
}
std::string File::type() const
@@ -425,11 +520,27 @@ std::string File::type() const
return m_file["type"].as<std::string>();
}
-emscripten::val File::val()
+emscripten::val File::val() const
{
return m_file;
}
+FileUrlRegistration::FileUrlRegistration(File file)
+{
+ m_path = QString::fromStdString(emscripten::val::global("window")["URL"].call<std::string>(
+ "createObjectURL", file.file()));
+}
+
+FileUrlRegistration::~FileUrlRegistration()
+{
+ emscripten::val::global("window")["URL"].call<void>("revokeObjectURL",
+ emscripten::val(m_path.toStdString()));
+}
+
+FileUrlRegistration::FileUrlRegistration(FileUrlRegistration &&other) = default;
+
+FileUrlRegistration &FileUrlRegistration::operator=(FileUrlRegistration &&other) = default;
+
FileList::FileList(const emscripten::val &fileList)
:m_fileList(fileList)
{
@@ -468,20 +579,23 @@ void FileReader::readAsArrayBuffer(const Blob &blob) const
void FileReader::onLoad(const std::function<void(emscripten::val)> &onLoad)
{
- m_onLoad.reset(new EventCallback(m_fileReader, "load", onLoad));
+ m_onLoad.reset();
+ m_onLoad = std::make_unique<EventCallback>(m_fileReader, "load", onLoad);
}
void FileReader::onError(const std::function<void(emscripten::val)> &onError)
{
- m_onError.reset(new EventCallback(m_fileReader, "error", onError));
+ m_onError.reset();
+ m_onError = std::make_unique<EventCallback>(m_fileReader, "error", onError);
}
void FileReader::onAbort(const std::function<void(emscripten::val)> &onAbort)
{
- m_onAbort.reset(new EventCallback(m_fileReader, "abort", onAbort));
+ m_onAbort.reset();
+ m_onAbort = std::make_unique<EventCallback>(m_fileReader, "abort", onAbort);
}
-emscripten::val FileReader::val()
+emscripten::val FileReader::val() const
{
return m_fileReader;
}
@@ -514,7 +628,7 @@ Uint8Array::Uint8Array(const ArrayBuffer &buffer, uint32_t offset, uint32_t leng
// Constructs a Uint8Array which references an area on the heap.
Uint8Array::Uint8Array(const char *buffer, uint32_t size)
-:m_uint8Array(Uint8Array::constructor_().new_(Uint8Array::heap().buffer().m_arrayBuffer, uint32_t(buffer), size))
+:m_uint8Array(Uint8Array::constructor_().new_(Uint8Array::heap().buffer().m_arrayBuffer, uintptr_t(buffer), size))
{
}
@@ -541,12 +655,31 @@ void Uint8Array::set(const Uint8Array &source)
m_uint8Array.call<void>("set", source.m_uint8Array); // copies source content
}
+Uint8Array Uint8Array::subarray(uint32_t begin, uint32_t end)
+{
+ // Note: using uint64_t here errors with "Cannot convert a BigInt value to a number"
+ // (see JS BigInt and Number types). Use uint32_t for now.
+ return Uint8Array(m_uint8Array.call<emscripten::val>("subarray", begin, end));
+}
+
// Copies the Uint8Array content to a destination on the heap
void Uint8Array::copyTo(char *destination) const
{
Uint8Array(destination, length()).set(*this);
}
+// Copies the Uint8Array content to a destination QByteArray
+QByteArray Uint8Array::copyToQByteArray() const
+{
+ if (length() > std::numeric_limits<qsizetype>::max())
+ return QByteArray();
+
+ QByteArray destinationArray;
+ destinationArray.resize(length());
+ copyTo(destinationArray.data());
+ return destinationArray;
+}
+
// Copies the Uint8Array content to a destination on the heap
void Uint8Array::copy(char *destination, const Uint8Array &source)
{
@@ -561,7 +694,13 @@ Uint8Array Uint8Array::copyFrom(const char *buffer, uint32_t size)
return contentCopy;
}
-emscripten::val Uint8Array::val()
+// Copies content from a QByteArray to a new Uint8Array object
+Uint8Array Uint8Array::copyFrom(const QByteArray &buffer)
+{
+ return copyFrom(buffer.constData(), buffer.size());
+}
+
+emscripten::val Uint8Array::val() const
{
return m_uint8Array;
}
@@ -576,41 +715,45 @@ emscripten::val Uint8Array::constructor_()
return emscripten::val::global("Uint8Array");
}
+class EventListener {
+public:
+ EventListener(uintptr_t handler)
+ :m_handler(handler)
+ {
+
+ }
+
+ // Special function - addEventListender() allows adding an object with a
+ // handleEvent() function which eceives the event.
+ void handleEvent(emscripten::val event) {
+ auto handlerPtr = reinterpret_cast<std::function<void(emscripten::val)> *>(m_handler);
+ (*handlerPtr)(event);
+ }
+
+ uintptr_t m_handler;
+};
+
// Registers a callback function for a named event on the given element. The event
// name must be the name as returned by the Event.type property: e.g. "load", "error".
EventCallback::~EventCallback()
{
- // Clean up if this instance's callback is still installed on the element
- if (m_element[contextPropertyName(m_eventName).c_str()].as<intptr_t>() == intptr_t(this)) {
- m_element.set(contextPropertyName(m_eventName).c_str(), emscripten::val::undefined());
- m_element.set((std::string("on") + m_eventName).c_str(), emscripten::val::undefined());
- }
+ m_element.call<void>("removeEventListener", m_eventName, m_eventListener);
}
-EventCallback::EventCallback(emscripten::val element, const std::string &name, const std::function<void(emscripten::val)> &fn)
+EventCallback::EventCallback(emscripten::val element, const std::string &name, const std::function<void(emscripten::val)> &handler)
:m_element(element)
,m_eventName(name)
- ,m_fn(fn)
+ ,m_handler(std::make_unique<std::function<void(emscripten::val)>>(handler))
{
- m_element.set(contextPropertyName(m_eventName).c_str(), emscripten::val(intptr_t(this)));
- m_element.set((std::string("on") + m_eventName).c_str(), emscripten::val::module_property("qtStdWebEventCallbackActivate"));
-}
-
-void EventCallback::activate(emscripten::val event)
-{
- emscripten::val target = event["target"];
- std::string eventName = event["type"].as<std::string>();
- EventCallback *that = reinterpret_cast<EventCallback *>(target[contextPropertyName(eventName).c_str()].as<intptr_t>());
- that->m_fn(event);
-}
-
-std::string EventCallback::contextPropertyName(const std::string &eventName)
-{
- return std::string("data-qtEventCallbackContext") + eventName;
+ uintptr_t handlerUint = reinterpret_cast<uintptr_t>(m_handler.get()); // FIXME: pass pointer directly instead
+ m_eventListener = emscripten::val::module_property("QtEventListener").new_(handlerUint);
+ m_element.call<void>("addEventListener", m_eventName, m_eventListener);
}
EMSCRIPTEN_BINDINGS(qtStdwebCalback) {
- emscripten::function("qtStdWebEventCallbackActivate", &EventCallback::activate);
+ emscripten::class_<EventListener>("QtEventListener")
+ .constructor<uintptr_t>()
+ .function("handleEvent", &EventListener::handleEvent);
}
namespace Promise {
@@ -667,6 +810,128 @@ namespace Promise {
}
}
+// Asyncify and thread blocking: Normally, it's not possible to block the main
+// thread, except if asyncify is enabled. Secondary threads can always block.
+//
+// haveAsyncify(): returns true if the main thread can block on QEventLoop::exec(),
+// if either asyncify 1 or 2 (JSPI) is available.
+//
+// haveJspi(): returns true if asyncify 2 (JSPI) is available.
+//
+// canBlockCallingThread(): returns true if the calling thread can block on
+// QEventLoop::exec(), using either asyncify or as a seconarday thread.
+bool haveJspi()
+{
+ static bool HaveJspi = jsHaveJspi();
+ return HaveJspi;
+}
+
+bool haveAsyncify()
+{
+ static bool HaveAsyncify = jsHaveAsyncify() || haveJspi();
+ return HaveAsyncify;
+}
+
+bool canBlockCallingThread()
+{
+ return haveAsyncify() || !emscripten_is_main_runtime_thread();
+}
+
+BlobIODevice::BlobIODevice(Blob blob)
+ : m_blob(blob)
+{
+
+}
+
+bool BlobIODevice::open(QIODevice::OpenMode mode)
+{
+ if (mode.testFlag(QIODevice::WriteOnly))
+ return false;
+ return QIODevice::open(mode);
+}
+
+bool BlobIODevice::isSequential() const
+{
+ return false;
+}
+
+qint64 BlobIODevice::size() const
+{
+ return m_blob.size();
+}
+
+bool BlobIODevice::seek(qint64 pos)
+{
+ if (pos >= size())
+ return false;
+ return QIODevice::seek(pos);
+}
+
+qint64 BlobIODevice::readData(char *data, qint64 maxSize)
+{
+ uint64_t begin = QIODevice::pos();
+ uint64_t end = std::min<uint64_t>(begin + maxSize, size());
+ uint64_t size = end - begin;
+ if (size > 0) {
+ qstdweb::ArrayBuffer buffer = m_blob.slice(begin, end).arrayBuffer_sync();
+ qstdweb::Uint8Array(buffer).copyTo(data);
+ }
+ return size;
+}
+
+qint64 BlobIODevice::writeData(const char *, qint64)
+{
+ Q_UNREACHABLE();
+}
+
+Uint8ArrayIODevice::Uint8ArrayIODevice(Uint8Array array)
+ : m_array(array)
+{
+
+}
+
+bool Uint8ArrayIODevice::open(QIODevice::OpenMode mode)
+{
+ return QIODevice::open(mode);
+}
+
+bool Uint8ArrayIODevice::isSequential() const
+{
+ return false;
+}
+
+qint64 Uint8ArrayIODevice::size() const
+{
+ return m_array.length();
+}
+
+bool Uint8ArrayIODevice::seek(qint64 pos)
+{
+ if (pos >= size())
+ return false;
+ return QIODevice::seek(pos);
+}
+
+qint64 Uint8ArrayIODevice::readData(char *data, qint64 maxSize)
+{
+ uint64_t begin = QIODevice::pos();
+ uint64_t end = std::min<uint64_t>(begin + maxSize, size());
+ uint64_t size = end - begin;
+ if (size > 0)
+ m_array.subarray(begin, end).copyTo(data);
+ return size;
+}
+
+qint64 Uint8ArrayIODevice::writeData(const char *data, qint64 maxSize)
+{
+ uint64_t begin = QIODevice::pos();
+ uint64_t end = std::min<uint64_t>(begin + maxSize, size());
+ uint64_t size = end - begin;
+ if (size > 0)
+ m_array.subarray(begin, end).set(Uint8Array(data, size));
+ return size;
+}
+
} // namespace qstdweb
QT_END_NAMESPACE
diff --git a/src/corelib/platform/wasm/qstdweb_p.h b/src/corelib/platform/wasm/qstdweb_p.h
index 70f58cb85c..a3b5bd5b6b 100644
--- a/src/corelib/platform/wasm/qstdweb_p.h
+++ b/src/corelib/platform/wasm/qstdweb_p.h
@@ -16,15 +16,28 @@
//
#include <private/qglobal_p.h>
+#include <QtCore/qglobal.h>
+#include "QtCore/qhash.h"
+#include "QtCore/qiodevice.h"
+
#include <emscripten/val.h>
+
#include <cstdint>
#include <functional>
-#include "initializer_list"
-#include <QtCore/qglobal.h>
-#include "QtCore/qhash.h"
+#include <initializer_list>
+#include <memory>
+#include <string>
+#include <utility>
+
+#if QT_CONFIG(thread)
+#include <emscripten/proxying.h>
+#include <emscripten/threading.h>
+#endif // #if QT_CONFIG(thread)
QT_BEGIN_NAMESPACE
+class QMimeData;
+
namespace qstdweb {
extern const char makeContextfulPromiseFunctionName[];
@@ -46,7 +59,8 @@ namespace qstdweb {
explicit ArrayBuffer(uint32_t size);
explicit ArrayBuffer(const emscripten::val &arrayBuffer);
uint32_t byteLength() const;
- emscripten::val val();
+ ArrayBuffer slice(uint32_t begin, uint32_t end) const;
+ emscripten::val val() const;
private:
friend class Uint8Array;
@@ -56,9 +70,13 @@ namespace qstdweb {
class Q_CORE_EXPORT Blob {
public:
explicit Blob(const emscripten::val &blob);
+ static Blob fromArrayBuffer(const ArrayBuffer &arrayBuffer);
uint32_t size() const;
+ static Blob copyFrom(const char *buffer, uint32_t size, std::string mimeType);
static Blob copyFrom(const char *buffer, uint32_t size);
- emscripten::val val();
+ Blob slice(uint32_t begin, uint32_t end) const;
+ ArrayBuffer arrayBuffer_sync() const;
+ emscripten::val val() const;
std::string type() const;
private:
@@ -70,19 +88,49 @@ namespace qstdweb {
public:
File() = default;
explicit File(const emscripten::val &file);
+ ~File();
+
+ File(const File &other);
+ File(File &&other);
+ File &operator=(const File &other);
+ File &operator=(File &&other);
Blob slice(uint64_t begin, uint64_t end) const;
std::string name() const;
uint64_t size() const;
std::string type() const;
- void stream(uint32_t offset, uint32_t length, char *buffer, const std::function<void ()> &completed) const;
- void stream(char *buffer, const std::function<void ()> &completed) const;
- emscripten::val val();
+ void stream(uint32_t offset, uint32_t length, char *buffer,
+ std::function<void()> completed) const;
+ void stream(char *buffer, std::function<void()> completed) const;
+ emscripten::val val() const;
+ void fileUrlRegistration() const;
+ const QString &fileUrlPath() const { return m_urlPath; }
+ emscripten::val file() const { return m_file; }
private:
emscripten::val m_file = emscripten::val::undefined();
+ QString m_urlPath;
};
+ class Q_CORE_EXPORT FileUrlRegistration
+ {
+ public:
+ explicit FileUrlRegistration(File file);
+ ~FileUrlRegistration();
+
+ FileUrlRegistration(const FileUrlRegistration &other) = delete;
+ FileUrlRegistration(FileUrlRegistration &&other);
+ FileUrlRegistration &operator=(const FileUrlRegistration &other) = delete;
+ FileUrlRegistration &operator=(FileUrlRegistration &&other);
+
+ const QString &path() const { return m_path; }
+
+ private:
+ QString m_path;
+ };
+
+ using FileUrlRegistrations = std::vector<std::unique_ptr<FileUrlRegistration>>;
+
class Q_CORE_EXPORT FileList {
public:
FileList() = default;
@@ -105,7 +153,7 @@ namespace qstdweb {
void onLoad(const std::function<void(emscripten::val)> &onLoad);
void onError(const std::function<void(emscripten::val)> &onError);
void onAbort(const std::function<void(emscripten::val)> &onAbort);
- emscripten::val val();
+ emscripten::val val() const;
private:
emscripten::val m_fileReader = emscripten::val::global("FileReader").new_();
@@ -126,11 +174,15 @@ namespace qstdweb {
ArrayBuffer buffer() const;
uint32_t length() const;
void set(const Uint8Array &source);
+ Uint8Array subarray(uint32_t begin, uint32_t end);
void copyTo(char *destination) const;
+ QByteArray copyToQByteArray() const;
+
static void copy(char *destination, const Uint8Array &source);
static Uint8Array copyFrom(const char *buffer, uint32_t size);
- emscripten::val val();
+ static Uint8Array copyFrom(const QByteArray &buffer);
+ emscripten::val val() const;
private:
static emscripten::val heap_();
@@ -147,13 +199,12 @@ namespace qstdweb {
EventCallback& operator=(EventCallback const&) = delete;
EventCallback(emscripten::val element, const std::string &name,
const std::function<void(emscripten::val)> &fn);
- static void activate(emscripten::val event);
private:
- static std::string contextPropertyName(const std::string &eventName);
emscripten::val m_element = emscripten::val::undefined();
std::string m_eventName;
- std::function<void(emscripten::val)> m_fn;
+ std::unique_ptr<std::function<void(emscripten::val)>> m_handler;
+ emscripten::val m_eventListener = emscripten::val::undefined();
};
struct PromiseCallbacks
@@ -164,7 +215,7 @@ namespace qstdweb {
};
namespace Promise {
- void adoptPromise(emscripten::val promise, PromiseCallbacks callbacks);
+ void Q_CORE_EXPORT adoptPromise(emscripten::val promise, PromiseCallbacks callbacks);
template<typename... Args>
void make(emscripten::val target,
@@ -181,7 +232,47 @@ namespace qstdweb {
adoptPromise(std::move(promiseObject), std::move(callbacks));
}
- void all(std::vector<emscripten::val> promises, PromiseCallbacks callbacks);
+ void Q_CORE_EXPORT all(std::vector<emscripten::val> promises, PromiseCallbacks callbacks);
+ };
+
+ template<class F>
+ decltype(auto) bindForever(F wrappedCallback)
+ {
+ return wrappedCallback;
+ }
+
+ class Q_CORE_EXPORT BlobIODevice: public QIODevice
+ {
+ public:
+ BlobIODevice(Blob blob);
+ bool open(QIODeviceBase::OpenMode mode) override;
+ bool isSequential() const override;
+ qint64 size() const override;
+ bool seek(qint64 pos) override;
+
+ protected:
+ qint64 readData(char *data, qint64 maxSize) override;
+ qint64 writeData(const char *, qint64) override;
+
+ private:
+ Blob m_blob;
+ };
+
+ class Uint8ArrayIODevice: public QIODevice
+ {
+ public:
+ Uint8ArrayIODevice(Uint8Array array);
+ bool open(QIODevice::OpenMode mode) override;
+ bool isSequential() const override;
+ qint64 size() const override;
+ bool seek(qint64 pos) override;
+
+ protected:
+ qint64 readData(char *data, qint64 maxSize) override;
+ qint64 writeData(const char *data, qint64 size) override;
+
+ private:
+ Uint8Array m_array;
};
inline emscripten::val window()
@@ -189,6 +280,52 @@ namespace qstdweb {
static emscripten::val savedWindow = emscripten::val::global("window");
return savedWindow;
}
+
+ bool haveAsyncify();
+ bool Q_CORE_EXPORT haveJspi();
+ bool canBlockCallingThread();
+
+ struct CancellationFlag
+ {
+ };
+
+#if QT_CONFIG(thread)
+ template<class T>
+ T proxyCall(std::function<T()> task, emscripten::ProxyingQueue *queue)
+ {
+ T result;
+ queue->proxySync(emscripten_main_runtime_thread_id(),
+ [task, result = &result]() { *result = task(); });
+ return result;
+ }
+
+ template<>
+ inline void proxyCall<void>(std::function<void()> task, emscripten::ProxyingQueue *queue)
+ {
+ queue->proxySync(emscripten_main_runtime_thread_id(), task);
+ }
+
+ template<class T>
+ T runTaskOnMainThread(std::function<T()> task, emscripten::ProxyingQueue *queue)
+ {
+ return emscripten_is_main_runtime_thread() ? task() : proxyCall<T>(std::move(task), queue);
+ }
+
+ template<class T>
+ T runTaskOnMainThread(std::function<T()> task)
+ {
+ emscripten::ProxyingQueue singleUseQueue;
+ return runTaskOnMainThread<T>(task, &singleUseQueue);
+ }
+
+#else
+ template<class T>
+ T runTaskOnMainThread(std::function<T()> task)
+ {
+ return task();
+ }
+#endif // QT_CONFIG(thread)
+
}
QT_END_NAMESPACE
diff --git a/src/corelib/platform/windows/qcomobject_p.h b/src/corelib/platform/windows/qcomobject_p.h
new file mode 100644
index 0000000000..8f27a18ff6
--- /dev/null
+++ b/src/corelib/platform/windows/qcomobject_p.h
@@ -0,0 +1,127 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QCOMOBJECT_P_H
+#define QCOMOBJECT_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/private/qglobal_p.h>
+
+#if defined(Q_OS_WIN) || defined(Q_QDOC)
+
+# include <QtCore/qt_windows.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace QtPrivate {
+
+template <typename... TInterfaces>
+struct QComObjectTraits
+{
+ static constexpr bool isGuidOf(REFIID riid) noexcept
+ {
+ return ((riid == __uuidof(TInterfaces)) || ...);
+ }
+};
+
+} // namespace QtPrivate
+
+// NOTE: In order to be able to query the intermediate interface, i.e. the one you do not specify in
+// QComObject interface list (TFirstInterface, TInterfaces) but that is a base for any of them
+// (except IUnknown) you need to provide an explicit specialization of function
+// QComObjectTraits<...>::isGuidOf for that type. For example, if you want to inherit interface
+// IMFSampleGrabberSinkCallback which inherits IMFClockStateSink and you want to be able to query
+// the latter one you need to provide this explicit specialization:
+//
+// class SinkCallback : public QComObject<IMFSampleGrabberSinkCallback>
+// {
+// ...
+// };
+//
+// namespace QtPrivate {
+//
+// template <>
+// struct QComObjectTraits<IMFSampleGrabberSinkCallback>
+// {
+// static constexpr bool isGuidOf(REFIID riid) noexcept
+// {
+// return QComObjectTraits<IMFSampleGrabberSinkCallback, IMFClockStateSink>::isGuidOf(riid);
+// }
+// };
+//
+// }
+
+template <typename TFirstInterface, typename... TAdditionalInterfaces>
+class QComObject : public TFirstInterface, public TAdditionalInterfaces...
+{
+public:
+ STDMETHODIMP QueryInterface(REFIID riid, void **ppvObject) override
+ {
+ if (!ppvObject)
+ return E_POINTER;
+
+ if (riid == __uuidof(IUnknown)) {
+ *ppvObject = static_cast<IUnknown *>(static_cast<TFirstInterface *>(this));
+ AddRef();
+
+ return S_OK;
+ }
+
+ return tryQueryInterface<TFirstInterface, TAdditionalInterfaces...>(riid, ppvObject);
+ }
+
+ STDMETHODIMP_(ULONG) AddRef() override { return ++m_referenceCount; }
+
+ STDMETHODIMP_(ULONG) Release() override
+ {
+ const LONG referenceCount = --m_referenceCount;
+ if (referenceCount == 0)
+ delete this;
+
+ return referenceCount;
+ }
+
+protected:
+ QComObject() = default;
+
+ // Destructor is not public. Caller should call Release.
+ // Derived class should make its destructor private to force this behavior.
+ virtual ~QComObject() = default;
+
+private:
+ template <typename TInterface, typename... TRest>
+ HRESULT tryQueryInterface(REFIID riid, void **ppvObject)
+ {
+ if (QtPrivate::QComObjectTraits<TInterface>::isGuidOf(riid)) {
+ *ppvObject = static_cast<TInterface *>(this);
+ AddRef();
+
+ return S_OK;
+ }
+
+ if constexpr (sizeof...(TRest) > 0)
+ return tryQueryInterface<TRest...>(riid, ppvObject);
+
+ *ppvObject = nullptr;
+
+ return E_NOINTERFACE;
+ }
+
+ std::atomic<LONG> m_referenceCount = 1;
+};
+
+QT_END_NAMESPACE
+
+#endif // Q_OS_WIN
+
+#endif // QCOMOBJECT_P_H
diff --git a/src/corelib/platform/windows/qfactorycacheregistration.cpp b/src/corelib/platform/windows/qfactorycacheregistration.cpp
new file mode 100644
index 0000000000..6bd69c66d1
--- /dev/null
+++ b/src/corelib/platform/windows/qfactorycacheregistration.cpp
@@ -0,0 +1,53 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qfactorycacheregistration_p.h"
+
+#include <QtCore/QMutex>
+
+QT_BEGIN_NAMESPACE
+
+#ifdef QT_USE_FACTORY_CACHE_REGISTRATION
+
+static QBasicMutex registrationMutex;
+static detail::QWinRTFactoryCacheRegistration *firstElement;
+
+detail::QWinRTFactoryCacheRegistration::QWinRTFactoryCacheRegistration(
+ QFunctionPointer clearFunction)
+ : m_clearFunction(clearFunction)
+{
+ QMutexLocker lock(&registrationMutex);
+
+ // forward pointers
+ m_next = std::exchange(firstElement, this);
+
+ // backward pointers
+ m_prevNext = &firstElement;
+ if (m_next)
+ m_next->m_prevNext = &m_next;
+}
+
+detail::QWinRTFactoryCacheRegistration::~QWinRTFactoryCacheRegistration()
+{
+ QMutexLocker lock(&registrationMutex);
+
+ *m_prevNext = m_next;
+
+ if (m_next)
+ m_next->m_prevNext = m_prevNext;
+}
+
+void detail::QWinRTFactoryCacheRegistration::clearAllCaches()
+{
+ QMutexLocker lock(&registrationMutex);
+
+ detail::QWinRTFactoryCacheRegistration *element;
+
+ for (element = firstElement; element != nullptr; element = element->m_next) {
+ element->m_clearFunction();
+ }
+}
+
+#endif
+
+QT_END_NAMESPACE
diff --git a/src/corelib/platform/windows/qfactorycacheregistration_p.h b/src/corelib/platform/windows/qfactorycacheregistration_p.h
new file mode 100644
index 0000000000..d0b19b995b
--- /dev/null
+++ b/src/corelib/platform/windows/qfactorycacheregistration_p.h
@@ -0,0 +1,52 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QFACTORYCACHEREGISTRATION_P_H
+#define QFACTORYCACHEREGISTRATION_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/qglobal.h>
+
+#if !defined(QT_BOOTSTRAPPED) && QT_CONFIG(cpp_winrt)
+# define QT_USE_FACTORY_CACHE_REGISTRATION
+#endif
+
+#ifdef QT_USE_FACTORY_CACHE_REGISTRATION
+
+#include "qt_winrtbase_p.h"
+
+QT_BEGIN_NAMESPACE
+
+namespace detail {
+
+class QWinRTFactoryCacheRegistration
+{
+public:
+ Q_CORE_EXPORT explicit QWinRTFactoryCacheRegistration(QFunctionPointer clearFunction);
+ Q_CORE_EXPORT ~QWinRTFactoryCacheRegistration();
+ Q_CORE_EXPORT static void clearAllCaches();
+
+ Q_DISABLE_COPY_MOVE(QWinRTFactoryCacheRegistration)
+private:
+ QWinRTFactoryCacheRegistration **m_prevNext = nullptr;
+ QWinRTFactoryCacheRegistration *m_next = nullptr;
+ QFunctionPointer m_clearFunction;
+};
+
+inline QWinRTFactoryCacheRegistration reg([]() noexcept { winrt::clear_factory_cache(); });
+}
+
+QT_END_NAMESPACE
+
+#endif
+#endif // QFACTORYCACHEREGISTRATION_P_H
diff --git a/src/corelib/platform/windows/qt_winrtbase_p.h b/src/corelib/platform/windows/qt_winrtbase_p.h
new file mode 100644
index 0000000000..fb7366f93d
--- /dev/null
+++ b/src/corelib/platform/windows/qt_winrtbase_p.h
@@ -0,0 +1,34 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QT_WINRTBASE_P_H
+#define QT_WINRTBASE_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/qglobal.h>
+
+#if QT_CONFIG(cpp_winrt)
+# include <winrt/base.h>
+# include <QtCore/private/qfactorycacheregistration_p.h>
+// Workaround for Windows SDK bug.
+// See https://github.com/microsoft/Windows.UI.Composition-Win32-Samples/issues/47
+namespace winrt::impl
+{
+ template <typename Async>
+ auto wait_for(Async const& async, Windows::Foundation::TimeSpan const& timeout);
+}
+// See https://learn.microsoft.com/en-us/windows/uwp/cpp-and-winrt-apis/faq#how-do-i-resolve-ambiguities-with-getcurrenttime-and-or-try-
+// for more workarounds.
+#endif // QT_CONFIG(cpp/winrt)
+
+#endif // QT_WINRTBASE_P_H