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.cpp1228
-rw-r--r--src/corelib/platform/android/qandroidextras_p.h246
-rw-r--r--src/corelib/platform/android/qandroidnativeinterface.cpp246
-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.cpp819
-rw-r--r--src/corelib/platform/wasm/qstdweb_p.h274
-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, 3766 insertions, 158 deletions
diff --git a/src/corelib/platform/android/qandroidextras.cpp b/src/corelib/platform/android/qandroidextras.cpp
new file mode 100644
index 0000000000..aa0c3fd093
--- /dev/null
+++ b/src/corelib/platform/android/qandroidextras.cpp
@@ -0,0 +1,1228 @@
+// Copyright (C) 2021 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 "qandroidextras_p.h"
+
+#include <QtCore/qbuffer.h>
+#include <QtCore/qdatastream.h>
+#include <QtCore/qjnienvironment.h>
+#include <QtCore/qvariant.h>
+#include <QtCore/qmutex.h>
+#include <QtCore/qtimer.h>
+#include <QtCore/qset.h>
+
+#if QT_CONFIG(future)
+#include <QtCore/qpromise.h>
+#endif
+
+QT_BEGIN_NAMESPACE
+
+class QAndroidParcelPrivate
+{
+public:
+ QAndroidParcelPrivate();
+ explicit QAndroidParcelPrivate(const QJniObject& parcel);
+
+ void writeData(const QByteArray &data) const;
+ void writeBinder(const QAndroidBinder &binder) const;
+ void writeFileDescriptor(int fd) const;
+
+ QByteArray readData() const;
+ int readFileDescriptor() const;
+ QAndroidBinder readBinder() const;
+
+private:
+ friend class QAndroidBinder;
+ friend class QAndroidParcel;
+ QJniObject handle;
+};
+
+struct FileDescriptor
+{
+ explicit FileDescriptor(int fd = -1)
+ : handle("java/io/FileDescriptor")
+ {
+ QJniEnvironment().checkAndClearExceptions();
+ handle.setField("descriptor", fd);
+ }
+
+ QJniObject handle;
+};
+
+QAndroidParcelPrivate::QAndroidParcelPrivate()
+ : handle(QJniObject::callStaticObjectMethod("android/os/Parcel","obtain",
+ "()Landroid/os/Parcel;").object())
+{}
+
+QAndroidParcelPrivate::QAndroidParcelPrivate(const QJniObject &parcel)
+ : handle(parcel)
+{}
+
+void QAndroidParcelPrivate::writeData(const QByteArray &data) const
+{
+ if (data.isEmpty())
+ return;
+
+ QJniEnvironment().checkAndClearExceptions();
+ QJniEnvironment env;
+ jbyteArray array = env->NewByteArray(data.size());
+ env->SetByteArrayRegion(array, 0, data.length(),
+ reinterpret_cast<const jbyte*>(data.constData()));
+ handle.callMethod<void>("writeByteArray", "([B)V", array);
+ env->DeleteLocalRef(array);
+}
+
+void QAndroidParcelPrivate::writeBinder(const QAndroidBinder &binder) const
+{
+ QJniEnvironment().checkAndClearExceptions();
+ handle.callMethod<void>("writeStrongBinder", "(Landroid/os/IBinder;)V",
+ binder.handle().object());
+}
+
+void QAndroidParcelPrivate::writeFileDescriptor(int fd) const
+{
+ QJniEnvironment().checkAndClearExceptions();
+ handle.callMethod<void>("writeFileDescriptor", "(Ljava/io/FileDescriptor;)V",
+ FileDescriptor(fd).handle.object());
+}
+
+QByteArray QAndroidParcelPrivate::readData() const
+{
+ QJniEnvironment().checkAndClearExceptions();
+ auto array = handle.callObjectMethod("createByteArray", "()[B");
+ QJniEnvironment env;
+ auto sz = env->GetArrayLength(jbyteArray(array.object()));
+ QByteArray res(sz, Qt::Initialization::Uninitialized);
+ env->GetByteArrayRegion(jbyteArray(array.object()), 0, sz,
+ reinterpret_cast<jbyte *>(res.data()));
+ return res;
+}
+
+int QAndroidParcelPrivate::readFileDescriptor() const
+{
+ QJniEnvironment().checkAndClearExceptions();
+ auto parcelFD = handle.callObjectMethod("readFileDescriptor",
+ "()Landroid/os/ParcelFileDescriptor;");
+ if (parcelFD.isValid())
+ return parcelFD.callMethod<jint>("getFd", "()I");
+ return -1;
+}
+
+QAndroidBinder QAndroidParcelPrivate::readBinder() const
+{
+ QJniEnvironment().checkAndClearExceptions();
+ auto strongBinder = handle.callObjectMethod("readStrongBinder", "()Landroid/os/IBinder;");
+ return QAndroidBinder(strongBinder.object());
+}
+
+/*!
+ \class QAndroidParcel
+ \inheaderfile QtCore/private/qandroidextras_p.h
+ \preliminary
+ \inmodule QtCorePrivate
+ \brief Wraps the most important methods of Android Parcel class.
+
+ The QAndroidParcel is a convenience class that wraps the most important
+ \l {https://developer.android.com/reference/android/os/Parcel.html}{Android Parcel}
+ methods.
+
+ \include qtcore.qdoc qtcoreprivate-usage
+
+ \since 6.2
+*/
+
+/*!
+ Creates a new object.
+ */
+QAndroidParcel::QAndroidParcel()
+ : d(new QAndroidParcelPrivate())
+{
+}
+
+/*!
+ Wraps the \a parcel object.
+ */
+QAndroidParcel::QAndroidParcel(const QJniObject& parcel)
+ : d(new QAndroidParcelPrivate(parcel))
+{
+
+}
+
+QAndroidParcel::~QAndroidParcel()
+{
+}
+
+/*!
+ Writes the provided \a data as a byte array
+ */
+void QAndroidParcel::writeData(const QByteArray &data) const
+{
+ d->writeData(data);
+}
+
+/*!
+ Writes the provided \a value. The value is converted into a
+ QByteArray before is written.
+ */
+void QAndroidParcel::writeVariant(const QVariant &value) const
+{
+ QByteArray buff;
+ QDataStream stream(&buff, QIODevice::WriteOnly);
+ stream << value;
+ d->writeData(buff);
+}
+
+/*!
+ Writes a \a binder object. This is useful for a client to
+ send to a server a binder which can be used by the server callback the client.
+ */
+void QAndroidParcel::writeBinder(const QAndroidBinder &binder) const
+{
+ d->writeBinder(binder);
+}
+
+/*!
+ Writes the provided \a fd.
+ */
+void QAndroidParcel::writeFileDescriptor(int fd) const
+{
+ d->writeFileDescriptor(fd);
+}
+
+/*!
+ Returns the data as a QByteArray
+ */
+QByteArray QAndroidParcel::readData() const
+{
+ return d->readData();
+}
+
+/*!
+ Returns the data as a QVariant
+ */
+QVariant QAndroidParcel::readVariant() const
+{
+ QDataStream stream(d->readData());
+ QVariant res;
+ stream >> res;
+ return res;
+}
+
+/*!
+ Returns the binder as a QAndroidBinder
+ */
+QAndroidBinder QAndroidParcel::readBinder() const
+{
+ return d->readBinder();
+}
+
+/*!
+ Returns the file descriptor
+ */
+int QAndroidParcel::readFileDescriptor() const
+{
+ return d->readFileDescriptor();
+}
+
+/*!
+ The return value is useful to call other Java API which are not covered by this wrapper
+ */
+QJniObject QAndroidParcel::handle() const
+{
+ return d->handle;
+}
+
+
+
+/*!
+ \class QAndroidBinder
+ \inheaderfile QtCore/private/qandroidextras_p.h
+ \preliminary
+ \inmodule QtCorePrivate
+ \brief Wraps the most important methods of Android Binder class.
+
+ The QAndroidBinder is a convenience class that wraps the most important
+ \l {https://developer.android.com/reference/android/os/Binder.html}{Android Binder}
+ methods.
+
+ \include qtcore.qdoc qtcoreprivate-usage
+
+ \since 6.2
+*/
+
+
+/*!
+ \enum QAndroidBinder::CallType
+
+ This enum is used with \l QAndroidBinder::transact() to describe the mode in which the
+ IPC call is performed.
+
+ \value Normal normal IPC, meaning that the caller waits the result from the callee
+ \value OneWay one-way IPC, meaning that the caller returns immediately, without waiting
+ for a result from the callee
+*/
+
+
+class QAndroidBinderPrivate
+{
+public:
+ explicit QAndroidBinderPrivate(QAndroidBinder *binder)
+ : handle("org/qtproject/qt/android/extras/QtAndroidBinder", "(J)V", jlong(binder))
+ , m_isQtAndroidBinder(true)
+ {
+ QJniEnvironment().checkAndClearExceptions();
+ }
+
+ explicit QAndroidBinderPrivate(const QJniObject &binder)
+ : handle(binder), m_isQtAndroidBinder(false) {};
+ void setDeleteListener(const std::function<void()> &func) { m_deleteListener = func; }
+ ~QAndroidBinderPrivate()
+ {
+ if (m_isQtAndroidBinder) {
+ QJniEnvironment().checkAndClearExceptions();
+ handle.callMethod<void>("setId", "(J)V", jlong(0));
+ if (m_deleteListener)
+ m_deleteListener();
+ }
+ }
+
+private:
+ QJniObject handle;
+ std::function<void()> m_deleteListener;
+ bool m_isQtAndroidBinder;
+ friend class QAndroidBinder;
+};
+
+/*!
+ Creates a new object which can be used to perform IPC.
+
+ \sa onTransact, transact
+ */
+QAndroidBinder::QAndroidBinder()
+ : d(new QAndroidBinderPrivate(this))
+{
+}
+
+/*!
+ Creates a new object from the \a binder Java object.
+
+ \sa transact
+ */
+QAndroidBinder::QAndroidBinder(const QJniObject &binder)
+ : d(new QAndroidBinderPrivate(binder))
+{
+}
+
+QAndroidBinder::~QAndroidBinder()
+{
+}
+
+/*!
+ Default implementation is a stub that returns false.
+ The user should override this method to get the transact data from the caller.
+
+ The \a code is the action to perform.
+ The \a data is the marshaled data sent by the caller.\br
+ The \a reply is the marshaled data to be sent to the caller.\br
+ The \a flags are the additional operation flags.\br
+
+ \warning This method is called from Binder's thread which is different
+ from the thread that this object was created.
+
+ \sa transact
+ */
+bool QAndroidBinder::onTransact(int /*code*/, const QAndroidParcel &/*data*/,
+ const QAndroidParcel &/*reply*/, CallType /*flags*/)
+{
+ return false;
+}
+
+/*!
+ Performs an IPC call
+
+ The \a code is the action to perform. Should be between
+ \l {https://developer.android.com/reference/android/os/IBinder.html#FIRST_CALL_TRANSACTION}
+ {FIRST_CALL_TRANSACTION} and
+ \l {https://developer.android.com/reference/android/os/IBinder.html#LAST_CALL_TRANSACTION}
+ {LAST_CALL_TRANSACTION}.\br
+ The \a data is the marshaled data to send to the target.\br
+ The \a reply (if specified) is the marshaled data to be received from the target.
+ May be \b nullptr if you are not interested in the return value.\br
+ The \a flags are the additional operation flags.\br
+
+ \return true on success
+ */
+bool QAndroidBinder::transact(int code, const QAndroidParcel &data,
+ QAndroidParcel *reply, CallType flags) const
+{
+ QJniEnvironment().checkAndClearExceptions();
+ return d->handle.callMethod<jboolean>("transact",
+ "(ILandroid/os/Parcel;Landroid/os/Parcel;I)Z",
+ jint(code), data.d->handle.object(),
+ reply ? reply->d->handle.object() : nullptr,
+ jint(flags));
+}
+
+/*!
+ The return value is useful to call other Java API which are not covered by this wrapper
+ */
+QJniObject QAndroidBinder::handle() const
+{
+ return d->handle;
+}
+
+
+
+
+/*!
+ \class QAndroidServiceConnection
+ \inheaderfile QtCore/private/qandroidextras_p.h
+ \preliminary
+ \inmodule QtCorePrivate
+ \brief Wraps the most important methods of Android ServiceConnection class.
+
+ The QAndroidServiceConnection is a convenience abstract class which wraps the
+ \l {https://developer.android.com/reference/android/content/ServiceConnection.html}{AndroidServiceConnection}
+ interface.
+
+ It is useful when you perform a QtAndroidPrivate::bindService operation.
+
+ \include qtcore.qdoc qtcoreprivate-usage
+
+ \since 6.2
+*/
+
+/*!
+ Creates a new object
+ */
+QAndroidServiceConnection::QAndroidServiceConnection()
+ : m_handle("org/qtproject/qt/android/extras/QtAndroidServiceConnection", "(J)V", jlong(this))
+{
+}
+
+/*!
+ Creates a new object from an existing \a serviceConnection.
+
+ It's useful when you have your own Java implementation.
+ Of course onServiceConnected()/onServiceDisconnected()
+ will not be called anymore.
+ */
+QAndroidServiceConnection::QAndroidServiceConnection(const QJniObject &serviceConnection)
+ : m_handle(serviceConnection)
+{
+}
+
+QAndroidServiceConnection::~QAndroidServiceConnection()
+{
+ m_handle.callMethod<void>("setId", "(J)V", jlong(this));
+}
+
+/*!
+ returns the underline QJniObject
+ */
+QJniObject QAndroidServiceConnection::handle() const
+{
+ return m_handle;
+}
+
+/*!
+ \fn void QAndroidServiceConnection::onServiceConnected(const QString &name, const QAndroidBinder &serviceBinder)
+
+ This notification is called when the client managed to connect to the service.
+ The \a name contains the server name, the \a serviceBinder is the binder that the client
+ uses to perform IPC operations.
+
+ \warning This method is called from Binder's thread which is different
+ from the thread that this object was created.
+
+ returns the underline QJniObject
+ */
+
+/*!
+ \fn void QAndroidServiceConnection::onServiceDisconnected(const QString &name)
+
+ Called when a connection to the Service has been lost.
+ The \a name parameter specifies which connectioen was lost.
+
+ \warning This method is called from Binder's thread which is different
+ from the thread that this object was created.
+
+ returns the underline QJniObject
+ */
+
+
+Q_CONSTINIT static QBasicAtomicInteger<uint> nextUniqueActivityRequestCode = Q_BASIC_ATOMIC_INITIALIZER(0);
+
+// Get a unique activity request code.
+static int uniqueActivityRequestCode()
+{
+ constexpr uint ReservedForQtOffset = 0x1000; // Reserve all request codes under 0x1000 for Qt
+
+ const uint requestCodeBase = nextUniqueActivityRequestCode.fetchAndAddRelaxed(1);
+ if (requestCodeBase == uint(INT_MAX) - ReservedForQtOffset)
+ qWarning("Unique activity request code has wrapped. Unexpected behavior may occur.");
+
+ const int requestCode = static_cast<int>(requestCodeBase + ReservedForQtOffset);
+ return requestCode;
+}
+
+class QAndroidActivityResultReceiverPrivate: public QtAndroidPrivate::ActivityResultListener
+{
+public:
+ QAndroidActivityResultReceiver *q;
+ mutable QHash<int, int> localToGlobalRequestCode;
+ mutable QHash<int, int> globalToLocalRequestCode;
+
+ int globalRequestCode(int localRequestCode) const
+ {
+ const auto oldSize = localToGlobalRequestCode.size();
+ auto &e = localToGlobalRequestCode[localRequestCode];
+ if (localToGlobalRequestCode.size() != oldSize) {
+ // new entry, populate:
+ int globalRequestCode = uniqueActivityRequestCode();
+ e = globalRequestCode;
+ globalToLocalRequestCode[globalRequestCode] = localRequestCode;
+ }
+ return e;
+ }
+
+ bool handleActivityResult(jint requestCode, jint resultCode, jobject data)
+ {
+ const auto it = std::as_const(globalToLocalRequestCode).find(requestCode);
+ if (it != globalToLocalRequestCode.cend()) {
+ q->handleActivityResult(*it, resultCode, QJniObject(data));
+ return true;
+ }
+
+ return false;
+ }
+
+ static QAndroidActivityResultReceiverPrivate *get(QAndroidActivityResultReceiver *publicObject)
+ {
+ return publicObject->d.data();
+ }
+};
+
+/*!
+ \class QAndroidActivityResultReceiver
+ \inheaderfile QtCore/private/qandroidextras_p.h
+ \preliminary
+ \inmodule QtCorePrivate
+ \since 6.2
+ \brief Interface used for callbacks from onActivityResult() in the main Android activity.
+
+ 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
+ */
+
+/*!
+ \internal
+*/
+QAndroidActivityResultReceiver::QAndroidActivityResultReceiver()
+ : d(new QAndroidActivityResultReceiverPrivate)
+{
+ d->q = this;
+ QtAndroidPrivate::registerActivityResultListener(d.data());
+}
+
+/*!
+ \internal
+*/
+QAndroidActivityResultReceiver::~QAndroidActivityResultReceiver()
+{
+ QtAndroidPrivate::unregisterActivityResultListener(d.data());
+}
+
+/*!
+ \fn void QAndroidActivityResultReceiver::handleActivityResult(int receiverRequestCode, int resultCode, const QJniObject &data)
+
+ Reimplement this function to get activity results after starting an activity using
+ either QtAndroidPrivate::startActivity() or QtAndroidPrivate::startIntentSender().
+ The \a receiverRequestCode is the request code unique to this receiver which was
+ originally passed to the startActivity() or startIntentSender() functions. The
+ \a resultCode is the result returned by the activity, and \a data is either null
+ or a Java object of the class android.content.Intent. Both the last to arguments
+ are identical to the arguments passed to onActivityResult().
+*/
+
+
+
+class QAndroidServicePrivate : public QObject, public QtAndroidPrivate::OnBindListener
+{
+public:
+ QAndroidServicePrivate(QAndroidService *service,
+ const std::function<QAndroidBinder*(const QAndroidIntent&)> &binder ={})
+ : m_service(service)
+ , m_binder(binder)
+ {
+ QTimer::singleShot(0,this, [this]{ QtAndroidPrivate::setOnBindListener(this);});
+ }
+
+ ~QAndroidServicePrivate()
+ {
+ QMutexLocker lock(&m_bindersMutex);
+ while (!m_binders.empty()) {
+ auto it = m_binders.begin();
+ lock.unlock();
+ delete (*it);
+ lock.relock();
+ }
+ }
+
+ // OnBindListener interface
+ jobject onBind(jobject intent) override
+ {
+ auto qai = QAndroidIntent(intent);
+ auto binder = m_binder ? m_binder(qai) : m_service->onBind(qai);
+ if (binder) {
+ {
+ QMutexLocker lock(&m_bindersMutex);
+ binder->d->setDeleteListener([this, binder]{binderDestroied(binder);});
+ m_binders.insert(binder);
+ }
+ return binder->handle().object();
+ }
+ return nullptr;
+ }
+
+private:
+ void binderDestroied(QAndroidBinder* obj)
+ {
+ QMutexLocker lock(&m_bindersMutex);
+ m_binders.remove(obj);
+ }
+
+public:
+ QAndroidService *m_service = nullptr;
+ std::function<QAndroidBinder *(const QAndroidIntent &)> m_binder;
+ QMutex m_bindersMutex;
+ QSet<QAndroidBinder*> m_binders;
+};
+
+/*!
+ \class QAndroidService
+ \inheaderfile QtCore/private/qandroidextras_p.h
+ \preliminary
+ \inmodule QtCorePrivate
+ \brief Wraps the most important methods of Android Service class.
+
+ The QAndroidService is a convenience class that wraps the most important
+ \l {https://developer.android.com/reference/android/app/Service.html}{Android Service}
+ methods.
+
+ \include qtcore.qdoc qtcoreprivate-usage
+
+ \since 6.2
+*/
+
+
+/*!
+ \fn QAndroidService::QAndroidService(int &argc, char **argv)
+
+ Creates a new Android service, passing \a argc and \a argv as parameters.
+
+ //! Parameter \a flags is omitted in the documentation.
+
+ \sa QCoreApplication
+ */
+QAndroidService::QAndroidService(int &argc, char **argv, int flags)
+ : QCoreApplication (argc, argv, QtAndroidPrivate::acuqireServiceSetup(flags))
+ , d(new QAndroidServicePrivate{this})
+{
+}
+
+/*!
+ \fn QAndroidService::QAndroidService(int &argc, char **argv, const std::function<QAndroidBinder *(const QAndroidIntent &)> &binder)
+
+ Creates a new Android service, passing \a argc and \a argv as parameters.
+
+ \a binder is used to create a \l {QAndroidBinder}{binder} when needed.
+
+ //! Parameter \a flags is omitted in the documentation.
+
+ \sa QCoreApplication
+ */
+QAndroidService::QAndroidService(int &argc, char **argv,
+ const std::function<QAndroidBinder*(const QAndroidIntent&)> &binder,
+ int flags)
+ : QCoreApplication (argc, argv, QtAndroidPrivate::acuqireServiceSetup(flags))
+ , d(new QAndroidServicePrivate{this, binder})
+{
+}
+
+QAndroidService::~QAndroidService()
+{}
+
+/*!
+ The user must override this method and to return a binder.
+
+ The \a intent parameter contains all the caller information.
+
+ The returned binder is used by the caller to perform IPC calls.
+
+ \warning This method is called from Binder's thread which is different
+ from the thread that this object was created.
+
+ \sa QAndroidBinder::onTransact, QAndroidBinder::transact
+ */
+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.
+
+ The QAndroidIntent is a convenience class that wraps the most important
+ \l {https://developer.android.com/reference/android/content/Intent.html}{Android Intent}
+ methods.
+
+ \include qtcore.qdoc qtcoreprivate-usage
+
+ \since 6.2
+*/
+
+/*!
+ Create a new intent
+ */
+QAndroidIntent::QAndroidIntent()
+ : m_handle("android.content.Intent", "()V")
+{
+
+}
+
+QAndroidIntent::~QAndroidIntent()
+{}
+
+/*!
+ Wraps the provided \a intent java object.
+ */
+QAndroidIntent::QAndroidIntent(const QJniObject &intent)
+ : m_handle(intent)
+{
+}
+
+/*!
+ Creates a new intent and sets the provided \a action.
+ */
+QAndroidIntent::QAndroidIntent(const QString &action)
+ : m_handle("android.content.Intent", "(Ljava/lang/String;)V",
+ QJniObject::fromString(action).object())
+{
+ QJniEnvironment().checkAndClearExceptions();
+}
+
+/*!
+ Creates a new intent and sets the provided \a packageContext and the service \a className.
+ Example:
+ \code
+ auto serviceIntent = QAndroidIntent(QtAndroidPrivate::androidActivity().object(), "com.example.MyService");
+ \endcode
+
+ \sa QtAndroidPrivate::bindService
+ */
+QAndroidIntent::QAndroidIntent(const QJniObject &packageContext, const char *className)
+ : m_handle("android/content/Intent", "(Landroid/content/Context;Ljava/lang/Class;)V",
+ packageContext.object(), QJniEnvironment().findClass(className))
+{
+ QJniEnvironment().checkAndClearExceptions();
+}
+
+/*!
+ Sets the \a key with the \a data in the Intent extras
+ */
+void QAndroidIntent::putExtra(const QString &key, const QByteArray &data)
+{
+ QJniEnvironment().checkAndClearExceptions();
+ QJniEnvironment env;
+ jbyteArray array = env->NewByteArray(data.size());
+ env->SetByteArrayRegion(array, 0, data.length(),
+ reinterpret_cast<const jbyte*>(data.constData()));
+ m_handle.callObjectMethod("putExtra", "(Ljava/lang/String;[B)Landroid/content/Intent;",
+ QJniObject::fromString(key).object(), array);
+ env->DeleteLocalRef(array);
+ QJniEnvironment().checkAndClearExceptions();
+}
+
+/*!
+ Returns the extra \a key data from the Intent extras
+ */
+QByteArray QAndroidIntent::extraBytes(const QString &key)
+{
+ QJniEnvironment().checkAndClearExceptions();
+ auto array = m_handle.callObjectMethod("getByteArrayExtra", "(Ljava/lang/String;)[B",
+ QJniObject::fromString(key).object());
+ if (!array.isValid() || !array.object())
+ return QByteArray();
+ QJniEnvironment env;
+ auto sz = env->GetArrayLength(jarray(array.object()));
+ QByteArray res(sz, Qt::Initialization::Uninitialized);
+ env->GetByteArrayRegion(jbyteArray(array.object()), 0, sz,
+ reinterpret_cast<jbyte *>(res.data()));
+ QJniEnvironment().checkAndClearExceptions();
+
+ return res;
+}
+
+/*!
+ Sets the \a key with the \a value in the Intent extras.
+ */
+void QAndroidIntent::putExtra(const QString &key, const QVariant &value)
+{
+ QByteArray buff;
+ QDataStream stream(&buff, QIODevice::WriteOnly);
+ stream << value;
+ putExtra(key, buff);
+}
+
+/*!
+ Returns the extra \a key data from the Intent extras as a QVariant
+ */
+QVariant QAndroidIntent::extraVariant(const QString &key)
+{
+ QDataStream stream(extraBytes(key));
+ QVariant res;
+ stream >> res;
+ return res;
+}
+
+/*!
+ The return value is useful to call other Java API which are not covered by this wrapper
+ */
+QJniObject QAndroidIntent::handle() const
+{
+ return m_handle;
+}
+
+
+
+/*!
+ \namespace QtAndroidPrivate
+ \preliminary
+ \inmodule QtCorePrivate
+ \since 6.2
+ \brief The QtAndroidPrivate namespace provides miscellaneous functions
+ to aid Android development.
+ \inheaderfile QtCore/private/qandroidextras_p.h
+
+ \include qtcore.qdoc qtcoreprivate-usage
+*/
+
+/*!
+ \since 6.2
+ \enum QtAndroidPrivate::BindFlag
+
+ This enum is used with QtAndroidPrivate::bindService to describe the mode in which the
+ binding is performed.
+
+ \value None No options.
+ \value AutoCreate Automatically creates the service as long as the binding exist.
+ See \l {https://developer.android.com/reference/android/content/Context.html#BIND_AUTO_CREATE}
+ {BIND_AUTO_CREATE} documentation for more details.
+ \value DebugUnbind Include debugging help for mismatched calls to unbind.
+ See \l {https://developer.android.com/reference/android/content/Context.html#BIND_DEBUG_UNBIND}
+ {BIND_DEBUG_UNBIND} documentation for more details.
+ \value NotForeground Don't allow this binding to raise the target service's process to the foreground scheduling priority.
+ See \l {https://developer.android.com/reference/android/content/Context.html#BIND_NOT_FOREGROUND}
+ {BIND_NOT_FOREGROUND} documentation for more details.
+ \value AboveClient Indicates that the client application binding to this service considers the service to be more important than the app itself.
+ See \l {https://developer.android.com/reference/android/content/Context.html#BIND_ABOVE_CLIENT}
+ {BIND_ABOVE_CLIENT} documentation for more details.
+ \value AllowOomManagement Allow the process hosting the bound service to go through its normal memory management.
+ See \l {https://developer.android.com/reference/android/content/Context.html#BIND_ALLOW_OOM_MANAGEMENT}
+ {BIND_ALLOW_OOM_MANAGEMENT} documentation for more details.
+ \value WaivePriority Don't impact the scheduling or memory management priority of the target service's hosting process.
+ See \l {https://developer.android.com/reference/android/content/Context.html#BIND_WAIVE_PRIORITY}
+ {BIND_WAIVE_PRIORITY} documentation for more details.
+ \value Important This service is assigned a higher priority so that it is available to the client when needed.
+ See \l {https://developer.android.com/reference/android/content/Context.html#BIND_IMPORTANT}
+ {BIND_IMPORTANT} documentation for more details.
+ \value AdjustWithActivity If binding from an activity, allow the target service's process importance to be raised based on whether the activity is visible to the user.
+ See \l {https://developer.android.com/reference/android/content/Context.html#BIND_ADJUST_WITH_ACTIVITY}
+ {BIND_ADJUST_WITH_ACTIVITY} documentation for more details.
+ \value ExternalService The service being bound is an isolated, external service.
+ See \l {https://developer.android.com/reference/android/content/Context.html#BIND_EXTERNAL_SERVICE}
+ {BIND_EXTERNAL_SERVICE} documentation for more details.
+*/
+
+/*!
+ \since 6.2
+
+ Starts the activity given by \a intent and provides the result asynchronously through the
+ \a resultReceiver if this is non-null.
+
+ If \a resultReceiver is null, then the \c startActivity() method in the \c androidActivity()
+ will be called. Otherwise \c startActivityForResult() will be called.
+
+ The \a receiverRequestCode is a request code unique to the \a resultReceiver, and will be
+ returned along with the result, making it possible to use the same receiver for more than
+ one intent.
+
+ */
+void QtAndroidPrivate::startActivity(const QJniObject &intent,
+ int receiverRequestCode,
+ QAndroidActivityResultReceiver *resultReceiver)
+{
+ QJniObject activity = QtAndroidPrivate::activity();
+ if (resultReceiver != 0) {
+ QAndroidActivityResultReceiverPrivate *resultReceiverD =
+ QAndroidActivityResultReceiverPrivate::get(resultReceiver);
+ activity.callMethod<void>("startActivityForResult",
+ "(Landroid/content/Intent;I)V",
+ intent.object<jobject>(),
+ resultReceiverD->globalRequestCode(receiverRequestCode));
+ } else {
+ activity.callMethod<void>("startActivity",
+ "(Landroid/content/Intent;)V",
+ intent.object<jobject>());
+ }
+}
+
+/*!
+ \since 6.2
+
+ Starts the activity given by \a intent and provides the result asynchronously through the
+ \a resultReceiver if this is non-null.
+
+ If \a resultReceiver is null, then the \c startActivity() method in the \c androidActivity()
+ will be called. Otherwise \c startActivityForResult() will be called.
+
+ The \a receiverRequestCode is a request code unique to the \a resultReceiver, and will be
+ returned along with the result, making it possible to use the same receiver for more than
+ one intent.
+
+ */
+void QtAndroidPrivate::startActivity(const QAndroidIntent &intent,
+ int receiverRequestCode,
+ QAndroidActivityResultReceiver *resultReceiver)
+{
+ startActivity(intent.handle(), receiverRequestCode, resultReceiver);
+}
+
+/*!
+ \since 6.2
+
+ Starts the activity given by \a intent, using the request code \a receiverRequestCode,
+ and provides the result by calling \a callbackFunc.
+*/
+void QtAndroidPrivate::startActivity(const QJniObject &intent,
+ int receiverRequestCode,
+ std::function<void(int, int, const QJniObject &data)> callbackFunc)
+{
+ QJniObject activity = QtAndroidPrivate::activity();
+ QAndroidActivityCallbackResultReceiver::instance()->registerCallback(receiverRequestCode,
+ callbackFunc);
+ startActivity(intent, receiverRequestCode, QAndroidActivityCallbackResultReceiver::instance());
+}
+
+/*!
+ \since 6.2
+
+ Starts the activity given by \a intentSender and provides the result asynchronously through the
+ \a resultReceiver if this is non-null.
+
+ If \a resultReceiver is null, then the \c startIntentSender() method in the \c androidActivity()
+ will be called. Otherwise \c startIntentSenderForResult() will be called.
+
+ The \a receiverRequestCode is a request code unique to the \a resultReceiver, and will be
+ returned along with the result, making it possible to use the same receiver for more than
+ one intent.
+
+*/
+void QtAndroidPrivate::startIntentSender(const QJniObject &intentSender,
+ int receiverRequestCode,
+ QAndroidActivityResultReceiver *resultReceiver)
+{
+ QJniObject activity = QtAndroidPrivate::activity();
+ if (resultReceiver != 0) {
+ QAndroidActivityResultReceiverPrivate *resultReceiverD =
+ QAndroidActivityResultReceiverPrivate::get(resultReceiver);
+ activity.callMethod<void>("startIntentSenderForResult",
+ "(Landroid/content/IntentSender;ILandroid/content/Intent;III)V",
+ intentSender.object<jobject>(),
+ resultReceiverD->globalRequestCode(receiverRequestCode),
+ 0, // fillInIntent
+ 0, // flagsMask
+ 0, // flagsValues
+ 0); // extraFlags
+ } else {
+ activity.callMethod<void>("startIntentSender",
+ "(Landroid/content/IntentSender;Landroid/content/Intent;III)V",
+ intentSender.object<jobject>(),
+ 0, // fillInIntent
+ 0, // flagsMask
+ 0, // flagsValues
+ 0); // extraFlags
+
+ }
+
+}
+
+/*!
+ \since 6.2
+ \fn bool QtAndroidPrivate::bindService(const QAndroidIntent &serviceIntent, const QAndroidServiceConnection &serviceConnection, BindFlags flags = BindFlag::None)
+
+ Binds the service given by \a serviceIntent, \a serviceConnection and \a flags.
+ The \a serviceIntent object identifies the service to connect to.
+ The \a serviceConnection is a listener that receives the information as the service
+ is started and stopped.
+
+ \return true on success
+
+ See \l {https://developer.android.com/reference/android/content/Context.html#bindService%28android.content.Intent,%20android.content.ServiceConnection,%20int%29}
+ {Android documentation} documentation for more details.
+
+ \sa QAndroidIntent, QAndroidServiceConnection, BindFlag
+*/
+bool QtAndroidPrivate::bindService(const QAndroidIntent &serviceIntent,
+ const QAndroidServiceConnection &serviceConnection, BindFlags flags)
+{
+ QJniEnvironment().checkAndClearExceptions();
+ QJniObject contextObj = QtAndroidPrivate::context();
+ return contextObj.callMethod<jboolean>(
+ "bindService",
+ "(Landroid/content/Intent;Landroid/content/ServiceConnection;I)Z",
+ serviceIntent.handle().object(),
+ serviceConnection.handle().object(),
+ jint(flags));
+}
+
+QAndroidActivityCallbackResultReceiver * QAndroidActivityCallbackResultReceiver::s_instance = nullptr;
+
+QAndroidActivityCallbackResultReceiver::QAndroidActivityCallbackResultReceiver()
+ : QAndroidActivityResultReceiver()
+ , callbackMap()
+{
+}
+
+void QAndroidActivityCallbackResultReceiver::handleActivityResult(int receiverRequestCode,
+ int resultCode,
+ const QJniObject &intent)
+{
+ callbackMap[receiverRequestCode](receiverRequestCode, resultCode, intent);
+ callbackMap.remove(receiverRequestCode);
+}
+
+QAndroidActivityCallbackResultReceiver * QAndroidActivityCallbackResultReceiver::instance() {
+ if (!s_instance) {
+ s_instance = new QAndroidActivityCallbackResultReceiver();
+ }
+ return s_instance;
+}
+
+void QAndroidActivityCallbackResultReceiver::registerCallback(
+ int receiverRequestCode,
+ std::function<void(int, int, const QJniObject &data)> callbackFunc)
+{
+ callbackMap.insert(receiverRequestCode, callbackFunc);
+}
+
+// Permissions API
+
+static const char qtNativeClassName[] = "org/qtproject/qt/android/QtNative";
+
+QtAndroidPrivate::PermissionResult resultFromAndroid(jint value)
+{
+ return value == 0 ? QtAndroidPrivate::Authorized : QtAndroidPrivate::Denied;
+}
+
+using PendingPermissionRequestsHash
+ = QHash<int, QSharedPointer<QPromise<QtAndroidPrivate::PermissionResult>>>;
+Q_GLOBAL_STATIC(PendingPermissionRequestsHash, g_pendingPermissionRequests);
+Q_CONSTINIT static QBasicMutex g_pendingPermissionRequestsMutex;
+
+static int nextRequestCode()
+{
+ Q_CONSTINIT static QBasicAtomicInt counter = Q_BASIC_ATOMIC_INITIALIZER(0);
+ return counter.fetchAndAddRelaxed(1);
+}
+
+/*!
+ \internal
+
+ This function is called when the result of the permission request is available.
+ Once a permission is requested, the result is braodcast by the OS and listened
+ to by QtActivity which passes it to C++ through a native JNI method call.
+ */
+static void sendRequestPermissionsResult(JNIEnv *env, jobject *obj, jint requestCode,
+ jobjectArray permissions, jintArray grantResults)
+{
+ Q_UNUSED(obj);
+
+ QMutexLocker locker(&g_pendingPermissionRequestsMutex);
+ auto it = g_pendingPermissionRequests->constFind(requestCode);
+ if (it == g_pendingPermissionRequests->constEnd()) {
+ qWarning() << "Found no valid pending permission request for request code" << requestCode;
+ return;
+ }
+
+ auto request = *it;
+ g_pendingPermissionRequests->erase(it);
+ locker.unlock();
+
+ const int size = env->GetArrayLength(permissions);
+ std::unique_ptr<jint[]> results(new jint[size]);
+ env->GetIntArrayRegion(grantResults, 0, size, results.get());
+
+ for (int i = 0 ; i < size; ++i) {
+ QtAndroidPrivate::PermissionResult result = resultFromAndroid(results[i]);
+ request->addResult(result, i);
+ }
+
+ QtAndroidPrivate::releaseAndroidDeadlockProtector();
+ request->finish();
+}
+
+QFuture<QtAndroidPrivate::PermissionResult>
+requestPermissionsInternal(const QStringList &permissions)
+{
+ // 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) {
+ 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;
+ jclass clazz = env.findClass("java/lang/String");
+ auto array = env->NewObjectArray(permissions.size(), clazz, nullptr);
+ int index = 0;
+
+ for (auto &perm : permissions)
+ env->SetObjectArrayElement(array, index++, QJniObject::fromString(perm).object());
+
+ QJniObject(QtAndroidPrivate::activity()).callMethod<void>("requestPermissions",
+ "([Ljava/lang/String;I)V",
+ array,
+ requestCode);
+ env->DeleteLocalRef(array);
+ });
+
+ return future;
+}
+
+/*!
+ \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(const QString &permission)
+{
+ return requestPermissions({permission});
+}
+
+QFuture<QtAndroidPrivate::PermissionResult>
+QtAndroidPrivate::requestPermissions(const QStringList &permissions)
+{
+ // avoid the uneccessary call and response to an empty permission string
+ if (permissions.isEmpty())
+ return QtFuture::makeReadyValueFuture(QtAndroidPrivate::Denied);
+ return requestPermissionsInternal(permissions);
+}
+
+/*!
+ \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(const QString &permission)
+{
+ QtAndroidPrivate::PermissionResult result = Denied;
+ if (!permission.isEmpty()) {
+ auto res = QJniObject::callStaticMethod<jint>(qtNativeClassName,
+ "checkSelfPermission",
+ "(Ljava/lang/String;)I",
+ QJniObject::fromString(permission).object());
+ result = resultFromAndroid(res);
+ }
+ return QtFuture::makeReadyValueFuture(result);
+}
+
+bool QtAndroidPrivate::registerPermissionNatives(QJniEnvironment &env)
+{
+ if (QtAndroidPrivate::androidSdkVersion() < 23)
+ return true;
+
+ const JNINativeMethod methods[] = {
+ {"sendRequestPermissionsResult", "(I[Ljava/lang/String;[I)V",
+ reinterpret_cast<void *>(sendRequestPermissionsResult)
+ }};
+
+ 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
new file mode 100644
index 0000000000..efdc6cf74f
--- /dev/null
+++ b/src/corelib/platform/android/qandroidextras_p.h
@@ -0,0 +1,246 @@
+// Copyright (C) 2016 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 QANDROIDEXTRAS_H
+#define QANDROIDEXTRAS_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 <jni.h>
+#include <functional>
+
+#include <QtCore/private/qglobal_p.h>
+#include <QtCore/qjniobject.h>
+#include <QtCore/private/qjnihelpers_p.h>
+#include <QtCore/qcoreapplication.h>
+#include <QtCore/qmap.h>
+
+#if QT_CONFIG(future)
+#include <QtCore/qfuture.h>
+#endif
+
+QT_BEGIN_NAMESPACE
+
+class QAndroidParcel;
+class QAndroidBinderPrivate;
+class QAndroidBinder;
+
+class Q_CORE_EXPORT QAndroidBinder
+{
+public:
+ enum class CallType {
+ Normal = 0,
+ OneWay = 1
+ };
+
+public:
+ explicit QAndroidBinder();
+ QAndroidBinder(const QJniObject &binder);
+
+ virtual ~QAndroidBinder();
+
+ virtual bool onTransact(int code, const QAndroidParcel &data,
+ const QAndroidParcel &reply, CallType flags);
+ bool transact(int code, const QAndroidParcel &data,
+ QAndroidParcel *reply = nullptr, CallType flags = CallType::Normal) const;
+
+ QJniObject handle() const;
+
+private:
+ friend class QAndroidBinderPrivate;
+ friend class QAndroidParcelPrivate;
+ friend class QAndroidServicePrivate;
+ QSharedPointer<QAndroidBinderPrivate> d;
+};
+
+class QAndroidParcelPrivate;
+
+class Q_CORE_EXPORT QAndroidParcel
+{
+public:
+ QAndroidParcel();
+ explicit QAndroidParcel(const QJniObject& parcel);
+ virtual ~QAndroidParcel();
+
+ void writeData(const QByteArray &data) const;
+ void writeVariant(const QVariant &value) const;
+ void writeBinder(const QAndroidBinder &binder) const;
+ void writeFileDescriptor(int fd) const;
+
+ QByteArray readData() const;
+ QVariant readVariant() const;
+ QAndroidBinder readBinder() const;
+ int readFileDescriptor() const;
+
+ QJniObject handle() const;
+
+private:
+ friend class QAndroidParcelPrivate;
+ friend class QAndroidBinder;
+ QSharedPointer<QAndroidParcelPrivate> d;
+};
+
+class QAndroidActivityResultReceiverPrivate;
+
+class Q_CORE_EXPORT QAndroidActivityResultReceiver
+{
+public:
+ QAndroidActivityResultReceiver();
+ virtual ~QAndroidActivityResultReceiver();
+ virtual void handleActivityResult(int receiverRequestCode, int resultCode,
+ const QJniObject &data) = 0;
+
+private:
+ friend class QAndroidActivityResultReceiverPrivate;
+ Q_DISABLE_COPY(QAndroidActivityResultReceiver)
+
+ QScopedPointer<QAndroidActivityResultReceiverPrivate> d;
+};
+
+class Q_CORE_EXPORT QAndroidServiceConnection
+{
+public:
+ QAndroidServiceConnection();
+ explicit QAndroidServiceConnection(const QJniObject &serviceConnection);
+ virtual ~QAndroidServiceConnection();
+
+ virtual void onServiceConnected(const QString &name,
+ const QAndroidBinder &serviceBinder) = 0;
+ virtual void onServiceDisconnected(const QString &name) = 0;
+
+ QJniObject handle() const;
+private:
+ Q_DISABLE_COPY(QAndroidServiceConnection)
+ QJniObject m_handle;
+};
+
+class Q_CORE_EXPORT QAndroidIntent
+{
+public:
+ QAndroidIntent();
+ virtual ~QAndroidIntent();
+ explicit QAndroidIntent(const QJniObject &intent);
+ explicit QAndroidIntent(const QString &action);
+ explicit QAndroidIntent(const QJniObject &packageContext, const char *className);
+
+ void putExtra(const QString &key, const QByteArray &data);
+ QByteArray extraBytes(const QString &key);
+
+ void putExtra(const QString &key, const QVariant &value);
+ QVariant extraVariant(const QString &key);
+
+ QJniObject handle() const;
+
+private:
+ QJniObject m_handle;
+};
+
+class QAndroidServicePrivate;
+
+class Q_CORE_EXPORT QAndroidService : public QCoreApplication
+{
+ Q_OBJECT
+
+public:
+ QAndroidService(int &argc, char **argv
+#ifndef Q_QDOC
+ , int flags = ApplicationFlags
+#endif
+ );
+ QAndroidService(int &argc, char **argv,
+ const std::function<QAndroidBinder*(const QAndroidIntent &intent)> & binder
+#ifndef Q_QDOC
+ , int flags = ApplicationFlags
+#endif
+ );
+ virtual ~QAndroidService();
+
+ virtual QAndroidBinder* onBind(const QAndroidIntent &intent);
+
+private:
+ friend class QAndroidServicePrivate;
+ Q_DISABLE_COPY(QAndroidService)
+
+ QScopedPointer<QAndroidServicePrivate> d;
+};
+
+class QAndroidActivityCallbackResultReceiver: public QAndroidActivityResultReceiver
+{
+public:
+ QAndroidActivityCallbackResultReceiver();
+ void handleActivityResult(int receiverRequestCode, int resultCode,
+ const QJniObject &intent) override;
+ void registerCallback(int receiverRequestCode,
+ std::function<void(int, int, const QJniObject &)> callbackFunc);
+
+ static QAndroidActivityCallbackResultReceiver *instance();
+private:
+ QMap<int, std::function<void(int, int, const QJniObject &data)>> callbackMap;
+
+ static QAndroidActivityCallbackResultReceiver *s_instance;
+};
+
+namespace QtAndroidPrivate
+{
+ Q_CORE_EXPORT void startIntentSender(const QJniObject &intentSender,
+ int receiverRequestCode,
+ QAndroidActivityResultReceiver *resultReceiver = nullptr);
+ Q_CORE_EXPORT void startActivity(const QJniObject &intent,
+ int receiverRequestCode,
+ QAndroidActivityResultReceiver *resultReceiver = nullptr);
+ Q_CORE_EXPORT void startActivity(const QAndroidIntent &intent,
+ int receiverRequestCode,
+ QAndroidActivityResultReceiver *resultReceiver = nullptr);
+ Q_CORE_EXPORT void startActivity(const QJniObject &intent,
+ int receiverRequestCode,
+ std::function<void(int, int, const QJniObject &data)>
+ callbackFunc);
+
+ enum class BindFlag {
+ None = 0x00000000,
+ AutoCreate = 0x00000001,
+ DebugUnbind = 0x00000002,
+ NotForeground = 0x00000004,
+ AboveClient = 0x00000008,
+ AllowOomManagement = 0x00000010,
+ WaivePriority = 0x00000020,
+ Important = 0x00000040,
+ AdjustWithActivity = 0x00000080,
+ ExternalService = -2147483648 // 0x80000000
+
+ };
+ Q_DECLARE_FLAGS(BindFlags, BindFlag)
+
+ Q_CORE_EXPORT bool bindService(const QAndroidIntent &serviceIntent,
+ const QAndroidServiceConnection &serviceConnection,
+ BindFlags flags = BindFlag::None);
+
+#if QT_CONFIG(future)
+ enum PermissionResult {
+ Undetermined,
+ Authorized,
+ Denied
+ };
+
+ Q_CORE_EXPORT QFuture<QtAndroidPrivate::PermissionResult>
+ requestPermission(const QString &permission);
+ QFuture<QtAndroidPrivate::PermissionResult>
+ requestPermissions(const QStringList &permissions);
+ Q_CORE_EXPORT QFuture<QtAndroidPrivate::PermissionResult>
+ checkPermission(const QString &permission);
+#endif
+
+}
+
+QT_END_NAMESPACE
+
+#endif // QANDROIDEXTRAS_H
diff --git a/src/corelib/platform/android/qandroidnativeinterface.cpp b/src/corelib/platform/android/qandroidnativeinterface.cpp
index 74d21c5d4c..fc3a09c78b 100644
--- a/src/corelib/platform/android/qandroidnativeinterface.cpp
+++ b/src/corelib/platform/android/qandroidnativeinterface.cpp
@@ -1,47 +1,36 @@
-/****************************************************************************
-**
-** Copyright (C) 2021 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the QtCore module of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
-
-#include <QtCore/qcoreapplication.h>
+// Copyright (C) 2021 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 <QtCore/qcoreapplication_platform.h>
+
+#include <QtCore/private/qnativeinterface_p.h>
#include <QtCore/private/qjnihelpers_p.h>
+#include <QtCore/qjniobject.h>
+#if QT_CONFIG(future) && !defined(QT_NO_QOBJECT)
+#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
+#if QT_CONFIG(future) && !defined(QT_NO_QOBJECT)
+static const char qtNativeClassName[] = "org/qtproject/qt/android/QtNative";
+
+struct PendingRunnable {
+ std::function<QVariant()> function;
+ std::shared_ptr<QPromise<QVariant>> promise;
+};
+
+using PendingRunnables = std::deque<PendingRunnable>;
+Q_GLOBAL_STATIC(PendingRunnables, g_pendingRunnables);
+Q_CONSTINIT static QBasicMutex g_pendingRunnablesMutex;
+#endif
+
/*!
\class QNativeInterface::QAndroidApplication
\since 6.2
@@ -57,14 +46,14 @@ QT_BEGIN_NAMESPACE
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
*/
-jobject QNativeInterface::QAndroidApplication::context()
+QtJniTypes::Context QNativeInterface::QAndroidApplication::context()
{
return QtAndroidPrivate::context();
}
@@ -79,7 +68,176 @@ jobject QNativeInterface::QAndroidApplication::context()
*/
bool QNativeInterface::QAndroidApplication::isActivityContext()
{
- return QtAndroidPrivate::activity();
+ return QtAndroidPrivate::activity().isValid();
+}
+
+/*!
+ \fn int QNativeInterface::QAndroidApplication::sdkVersion()
+
+ Returns the Android SDK version. This is also known as the API level.
+
+ \since 6.2
+*/
+int QNativeInterface::QAndroidApplication::sdkVersion()
+{
+ return QtAndroidPrivate::androidSdkVersion();
+}
+
+/*!
+ \fn void QNativeInterface::QAndroidApplication::hideSplashScreen(int duration)
+
+ Hides the splash screen by using a fade effect for the given \a duration.
+ If \a duration is not provided (default is 0) the splash screen is hidden
+ immedetiately after the app starts.
+
+ \since 6.2
+*/
+void QNativeInterface::QAndroidApplication::hideSplashScreen(int duration)
+{
+ QtAndroidPrivate::activity().callMethod<void>("hideSplashScreen", duration);
+}
+
+/*!
+ Posts the function \a runnable to the Android thread. The function will be
+ queued and executed on the Android UI thread. If the call is made on the
+ Android UI thread \a runnable will be executed immediately. If the Android
+ app is paused or the main Activity is null, \c runnable is added to the
+ Android main thread's queue.
+
+ This call returns a QFuture<QVariant> which allows doing both synchronous
+ and asynchronous calls, and can handle any return type. However, to get
+ a result back from the QFuture::result(), QVariant::value() should be used.
+
+ If the \a runnable execution takes longer than the period of \a timeout,
+ the blocking calls \l QFuture::waitForFinished() and \l QFuture::result()
+ are ended once \a timeout has elapsed. However, if \a runnable has already
+ started execution, it won't be cancelled.
+
+ The following example shows how to run an asynchronous call that expects
+ a return type:
+
+ \code
+ auto task = QNativeInterface::QAndroidApplication::runOnAndroidMainThread([=]() {
+ QJniObject surfaceView;
+ if (!surfaceView.isValid())
+ qDebug() << "SurfaceView object is not valid yet";
+
+ surfaceView = QJniObject("android/view/SurfaceView",
+ "(Landroid/content/Context;)V",
+ QNativeInterface::QAndroidApplication::context());
+
+ return QVariant::fromValue(surfaceView);
+ }).then([](QFuture<QVariant> future) {
+ auto surfaceView = future.result().value<QJniObject>();
+ if (surfaceView.isValid())
+ qDebug() << "Retrieved SurfaceView object is valid";
+ });
+ \endcode
+
+ The following example shows how to run a synchronous call with a void
+ return type:
+
+ \code
+ QNativeInterface::QAndroidApplication::runOnAndroidMainThread([]() {
+ QJniObject activity = QNativeInterface::QAndroidApplication::context();
+ // Hide system ui elements or go full screen
+ activity.callObjectMethod("getWindow", "()Landroid/view/Window;")
+ .callObjectMethod("getDecorView", "()Landroid/view/View;")
+ .callMethod<void>("setSystemUiVisibility", "(I)V", 0xffffffff);
+ }).waitForFinished();
+ \endcode
+
+ \note Becareful about the type of operations you do on the Android's main
+ thread, as any long operation can block the app's UI rendering and input
+ handling. If the function is expected to have long execution time, it's
+ also good to use a \l QDeadlineTimer in your \a runnable to manage
+ the execution and make sure it doesn't block the UI thread. Usually,
+ any operation longer than 5 seconds might block the app's UI. For more
+ information, see \l {Android: Keeping your app responsive}{Keeping your app responsive}.
+
+ \since 6.2
+*/
+#if QT_CONFIG(future) && !defined(QT_NO_QOBJECT)
+QFuture<QVariant> QNativeInterface::QAndroidApplication::runOnAndroidMainThread(
+ const std::function<QVariant()> &runnable,
+ const QDeadlineTimer timeout)
+{
+ auto promise = std::make_shared<QPromise<QVariant>>();
+ QFuture<QVariant> future = promise->future();
+ promise->start();
+
+ if (!timeout.isForever()) {
+ QThreadPool::globalInstance()->start([=]() mutable {
+ QEventLoop loop;
+ QTimer::singleShot(timeout.remainingTime(), &loop, [&]() {
+ future.cancel();
+ promise->finish();
+ loop.quit();
+ });
+
+ QFutureWatcher<QVariant> watcher;
+ QObject::connect(&watcher, &QFutureWatcher<QVariant>::finished, &loop, [&]() {
+ loop.quit();
+ });
+ QObject::connect(&watcher, &QFutureWatcher<QVariant>::canceled, &loop, [&]() {
+ 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);
+#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,
+ "runPendingCppRunnablesOnAndroidThread",
+ "()V");
+ return future;
+}
+
+// function called from Java from Android UI thread
+static void runPendingCppRunnables(JNIEnv */*env*/, jobject /*obj*/)
+{
+ // run all posted runnables
+ for (;;) {
+ QMutexLocker locker(&g_pendingRunnablesMutex);
+ if (g_pendingRunnables->empty())
+ break;
+
+ PendingRunnable r = std::move(g_pendingRunnables->front());
+ g_pendingRunnables->pop_front();
+ locker.unlock();
+
+ // run the runnable outside the sync block!
+ if (!r.promise->isCanceled())
+ r.promise->addResult(r.function());
+ r.promise->finish();
+ }
+}
+#endif
+
+bool QtAndroidPrivate::registerNativeInterfaceNatives(QJniEnvironment &env)
+{
+#if QT_CONFIG(future) && !defined(QT_NO_QOBJECT)
+ const JNINativeMethod methods = {"runPendingCppRunnables", "()V", (void *)runPendingCppRunnables};
+ return env.registerNativeMethods(qtNativeClassName, &methods, 1);
+#else
+ return true;
+#endif
}
QT_END_NAMESPACE
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 198ce897ca..75e76a6806 100644
--- a/src/corelib/platform/wasm/qstdweb.cpp
+++ b/src/corelib/platform/wasm/qstdweb.cpp
@@ -1,53 +1,384 @@
-/****************************************************************************
-**
-** Copyright (C) 2019 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the QtCore module of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
+// Copyright (C) 2019 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 "qstdweb_p.h"
+#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>
+#include <unordered_map>
+
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,
+ Finally,
+};
+
+void validateCallbacks(const PromiseCallbacks& callbacks) {
+ Q_ASSERT(!!callbacks.catchFunc || !!callbacks.finallyFunc || !!callbacks.thenFunc);
+}
+
+using ThunkId = int;
+
+#define THUNK_NAME(type, i) callbackThunk##type##i
+
+// A resource pool for exported promise thunk functions. ThunkPool::poolSize sets of
+// 3 promise thunks (then, catch, finally) are exported and can be used by promises
+// in C++. To allocate a thunk, call allocateThunk. When a thunk is ready for use,
+// a callback with allocation RAII object ThunkAllocation will be returned. Deleting
+// the object frees the thunk and automatically makes any pending allocateThunk call
+// run its callback with a free thunk slot.
+class ThunkPool {
+public:
+ static constexpr size_t poolSize = 4;
+
+ // An allocation for a thunk function set. Following the RAII pattern, destruction of
+ // this objects frees a corresponding thunk pool entry.
+ // To actually make the thunks react to a js promise's callbacks, call bindToPromise.
+ class ThunkAllocation {
+ public:
+ ThunkAllocation(int thunkId, ThunkPool* pool) : m_thunkId(thunkId), m_pool(pool) {}
+ ~ThunkAllocation() {
+ m_pool->free(m_thunkId);
+ }
+
+ // The id of the underlaying thunk set
+ int id() const { return m_thunkId; }
+
+ // Binds the corresponding thunk set to the js promise 'target'.
+ void bindToPromise(emscripten::val target, const PromiseCallbacks& callbacks) {
+ using namespace emscripten;
+
+ if (Q_LIKELY(callbacks.thenFunc)) {
+ target = target.call<val>(
+ "then",
+ emscripten::val::module_property(thunkName(CallbackType::Then, id()).data()));
+ }
+ if (callbacks.catchFunc) {
+ target = target.call<val>(
+ "catch",
+ emscripten::val::module_property(thunkName(CallbackType::Catch, 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:
+ int m_thunkId;
+ ThunkPool* m_pool;
+ };
+
+ ThunkPool() {
+ std::iota(m_free.begin(), m_free.end(), 0);
+ }
+
+ void setThunkCallback(std::function<void(int, CallbackType, emscripten::val)> callback) {
+ m_callback = std::move(callback);
+ }
+
+ void allocateThunk(std::function<void(std::unique_ptr<ThunkAllocation>)> onAllocated) {
+ if (m_free.empty()) {
+ m_pendingAllocations.push_back(std::move(onAllocated));
+ return;
+ }
+
+ const int thunkId = m_free.back();
+ m_free.pop_back();
+ onAllocated(std::make_unique<ThunkAllocation>(thunkId, this));
+ }
+
+ static QByteArray thunkName(CallbackType type, size_t i) {
+ return QStringLiteral("promiseCallback%1%2").arg([type]() -> QString {
+ switch (type) {
+ case CallbackType::Then:
+ return QStringLiteral("Then");
+ case CallbackType::Catch:
+ return QStringLiteral("Catch");
+ case CallbackType::Finally:
+ return QStringLiteral("Finally");
+ }
+ }()).arg(i).toLatin1();
+ }
+
+ static ThunkPool* get();
+
+#define THUNK(i) \
+ static void THUNK_NAME(Then, i)(emscripten::val result) \
+ { \
+ get()->onThunkCalled(i, CallbackType::Then, std::move(result)); \
+ } \
+ static void THUNK_NAME(Catch, i)(emscripten::val result) \
+ { \
+ get()->onThunkCalled(i, CallbackType::Catch, std::move(result)); \
+ } \
+ static void THUNK_NAME(Finally, i)() \
+ { \
+ get()->onThunkCalled(i, CallbackType::Finally, emscripten::val::undefined()); \
+ }
+
+ THUNK(0);
+ THUNK(1);
+ THUNK(2);
+ THUNK(3);
+
+#undef THUNK
+
+private:
+ void onThunkCalled(int index, CallbackType type, emscripten::val result) {
+ m_callback(index, type, std::move(result));
+ }
+
+ void free(int thunkId) {
+ if (m_pendingAllocations.empty()) {
+ // Return the thunk to the free pool
+ m_free.push_back(thunkId);
+ return;
+ }
+
+ // Take the next enqueued allocation and reuse the thunk
+ auto allocation = m_pendingAllocations.back();
+ m_pendingAllocations.pop_back();
+ allocation(std::make_unique<ThunkAllocation>(thunkId, this));
+ }
+
+ std::function<void(int, CallbackType, emscripten::val)> m_callback;
+
+ std::vector<int> m_free = std::vector<int>(poolSize);
+ std::vector<std::function<void(std::unique_ptr<ThunkAllocation>)>> m_pendingAllocations;
+};
+
+Q_GLOBAL_STATIC(ThunkPool, g_thunkPool)
+
+ThunkPool* ThunkPool::get()
+{
+ return g_thunkPool;
+}
+
+#define CALLBACK_BINDING(i) \
+ emscripten::function(ThunkPool::thunkName(CallbackType::Then, i).data(), \
+ &ThunkPool::THUNK_NAME(Then, i)); \
+ emscripten::function(ThunkPool::thunkName(CallbackType::Catch, i).data(), \
+ &ThunkPool::THUNK_NAME(Catch, i)); \
+ emscripten::function(ThunkPool::thunkName(CallbackType::Finally, i).data(), \
+ &ThunkPool::THUNK_NAME(Finally, i));
+
+EMSCRIPTEN_BINDINGS(qtThunkPool) {
+ CALLBACK_BINDING(0)
+ CALLBACK_BINDING(1)
+ CALLBACK_BINDING(2)
+ CALLBACK_BINDING(3)
+}
+
+#undef CALLBACK_BINDING
+#undef THUNK_NAME
+
+class WebPromiseManager
+{
+public:
+ WebPromiseManager();
+ ~WebPromiseManager();
+
+ WebPromiseManager(const WebPromiseManager& other) = delete;
+ WebPromiseManager(WebPromiseManager&& other) = delete;
+ WebPromiseManager& operator=(const WebPromiseManager& other) = delete;
+ WebPromiseManager& operator=(WebPromiseManager&& other) = delete;
+
+ void adoptPromise(emscripten::val target, PromiseCallbacks callbacks);
+
+ static WebPromiseManager* get();
+
+private:
+ struct RegistryEntry {
+ PromiseCallbacks callbacks;
+ std::unique_ptr<ThunkPool::ThunkAllocation> allocation;
+ };
+
+ static std::optional<CallbackType> parseCallbackType(emscripten::val callbackType);
+
+ void subscribeToJsPromiseCallbacks(int i, const PromiseCallbacks& callbacks, emscripten::val jsContextfulPromise);
+ void promiseThunkCallback(int i, CallbackType type, emscripten::val result);
+
+ void registerPromise(std::unique_ptr<ThunkPool::ThunkAllocation> allocation, PromiseCallbacks promise);
+ void unregisterPromise(ThunkId context);
+
+ std::array<RegistryEntry, ThunkPool::poolSize> m_promiseRegistry;
+};
+
+Q_GLOBAL_STATIC(WebPromiseManager, webPromiseManager)
+
+WebPromiseManager::WebPromiseManager()
+{
+ ThunkPool::get()->setThunkCallback(std::bind(
+ &WebPromiseManager::promiseThunkCallback, this,
+ std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
+}
+
+std::optional<CallbackType>
+WebPromiseManager::parseCallbackType(emscripten::val callbackType)
+{
+ if (!callbackType.isString())
+ return std::nullopt;
+
+ const std::string data = callbackType.as<std::string>();
+ if (data == "then")
+ return CallbackType::Then;
+ if (data == "catch")
+ return CallbackType::Catch;
+ if (data == "finally")
+ return CallbackType::Finally;
+ return std::nullopt;
+}
+
+WebPromiseManager::~WebPromiseManager() = default;
+
+WebPromiseManager *WebPromiseManager::get()
+{
+ return webPromiseManager();
+}
+
+void WebPromiseManager::promiseThunkCallback(int context, CallbackType type, emscripten::val result)
+{
+ auto* promiseState = &m_promiseRegistry[context];
+
+ auto* callbacks = &promiseState->callbacks;
+ switch (type) {
+ case CallbackType::Then:
+ callbacks->thenFunc(result);
+ break;
+ case CallbackType::Catch:
+ callbacks->catchFunc(result);
+ break;
+ case CallbackType::Finally:
+ // Final callback may be empty, used solely for promise unregistration
+ if (callbacks->finallyFunc) {
+ callbacks->finallyFunc();
+ }
+ unregisterPromise(context);
+ break;
+ }
+}
+
+void WebPromiseManager::registerPromise(
+ std::unique_ptr<ThunkPool::ThunkAllocation> allocation,
+ PromiseCallbacks callbacks)
+{
+ const ThunkId id = allocation->id();
+ m_promiseRegistry[id] =
+ RegistryEntry {std::move(callbacks), std::move(allocation)};
+}
+
+void WebPromiseManager::unregisterPromise(ThunkId context)
+{
+ m_promiseRegistry[context] = {};
+}
+
+void WebPromiseManager::adoptPromise(emscripten::val target, PromiseCallbacks callbacks) {
+ ThunkPool::get()->allocateThunk([=](std::unique_ptr<ThunkPool::ThunkAllocation> allocation) {
+ allocation->bindToPromise(std::move(target), callbacks);
+ registerPromise(std::move(allocation), std::move(callbacks));
+ });
+}
+#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)
+{
+ m_arrayBuffer = emscripten::val::global("ArrayBuffer").new_(size);
+}
ArrayBuffer::ArrayBuffer(const emscripten::val &arrayBuffer)
:m_arrayBuffer(arrayBuffer)
@@ -63,23 +394,92 @@ uint32_t ArrayBuffer::byteLength() const
return m_arrayBuffer["byteLength"].as<uint32_t>();
}
+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;
+}
+
Blob::Blob(const emscripten::val &blob)
:m_blob(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>();
}
+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", std::move(mimeType));
+ return Blob(emscripten::val::global("Blob").new_(contentArray, type));
+}
+
+// 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;
+}
+
File::File(const emscripten::val &file)
:m_file(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)));
@@ -95,6 +495,52 @@ uint64_t File::size() const
return uint64_t(m_file["size"].as<uint53_t>());
}
+std::string Blob::type() const
+{
+ return m_blob["type"].as<std::string>();
+}
+
+// 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,
+ std::function<void()> completed) const
+{
+ 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, std::function<void()> completed) const
+{
+ stream(0, size(), buffer, std::move(completed));
+}
+
+std::string File::type() const
+{
+ return m_file["type"].as<std::string>();
+}
+
+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)
{
@@ -116,6 +562,11 @@ File FileList::operator[](int index) const
return item(index);
}
+emscripten::val FileList::val() const
+{
+ return m_fileList;
+}
+
ArrayBuffer FileReader::result() const
{
return ArrayBuffer(m_fileReader["result"]);
@@ -126,19 +577,27 @@ void FileReader::readAsArrayBuffer(const Blob &blob) const
m_fileReader.call<void>("readAsArrayBuffer", blob.m_blob);
}
-void FileReader::onLoad(const std::function<void ()> &onLoad)
+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 ()> &onError)
+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 ()> &onAbort)
+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() const
+{
+ return m_fileReader;
}
Uint8Array Uint8Array::heap()
@@ -146,26 +605,37 @@ Uint8Array Uint8Array::heap()
return Uint8Array(heap_());
}
+// Constructs a Uint8Array which references the given emscripten::val, which must contain a JS Unit8Array
Uint8Array::Uint8Array(const emscripten::val &uint8Array)
: m_uint8Array(uint8Array)
{
}
+// Constructs a Uint8Array which references an ArrayBuffer
Uint8Array::Uint8Array(const ArrayBuffer &buffer)
: m_uint8Array(Uint8Array::constructor_().new_(buffer.m_arrayBuffer))
{
}
+// Constructs a Uint8Array which references a view into an ArrayBuffer
Uint8Array::Uint8Array(const ArrayBuffer &buffer, uint32_t offset, uint32_t length)
: m_uint8Array(Uint8Array::constructor_().new_(buffer.m_arrayBuffer, offset, length))
{
}
-Uint8Array::Uint8Array(char *buffer, uint32_t size)
-:m_uint8Array(Uint8Array::constructor_().new_(Uint8Array::heap().buffer().m_arrayBuffer, uint32_t(buffer), size))
+// 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, uintptr_t(buffer), size))
+{
+
+}
+
+// Constructs a Uint8Array which allocates and references a new ArrayBuffer with the given size.
+Uint8Array::Uint8Array(uint32_t size)
+: m_uint8Array(Uint8Array::constructor_().new_(size))
{
}
@@ -185,16 +655,56 @@ 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)
{
Uint8Array(destination, source.length()).set(source);
}
+// Copies content from a source on the heap to a new Uint8Array object
+Uint8Array Uint8Array::copyFrom(const char *buffer, uint32_t size)
+{
+ Uint8Array contentCopy(size);
+ contentCopy.set(Uint8Array(buffer, size));
+ return contentCopy;
+}
+
+// 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;
+}
+
emscripten::val Uint8Array::heap_()
{
return emscripten::val::module_property("HEAPU8");
@@ -205,30 +715,221 @@ 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(emscripten::val element, const std::string &name, const std::function<void ()> &fn)
-:m_fn(fn)
+EventCallback::~EventCallback()
+{
+ m_element.call<void>("removeEventListener", m_eventName, m_eventListener);
+}
+
+EventCallback::EventCallback(emscripten::val element, const std::string &name, const std::function<void(emscripten::val)> &handler)
+ :m_element(element)
+ ,m_eventName(name)
+ ,m_handler(std::make_unique<std::function<void(emscripten::val)>>(handler))
+{
+ 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::class_<EventListener>("QtEventListener")
+ .constructor<uintptr_t>()
+ .function("handleEvent", &EventListener::handleEvent);
+}
+
+namespace Promise {
+ void adoptPromise(emscripten::val promiseObject, PromiseCallbacks callbacks) {
+ validateCallbacks(callbacks);
+
+ WebPromiseManager::get()->adoptPromise(
+ std::move(promiseObject), std::move(callbacks));
+ }
+
+ void all(std::vector<emscripten::val> promises, PromiseCallbacks callbacks) {
+ struct State {
+ std::map<int, emscripten::val> results;
+ int remainingThenCallbacks;
+ int remainingFinallyCallbacks;
+ };
+
+ validateCallbacks(callbacks);
+
+ auto state = std::make_shared<State>();
+ state->remainingThenCallbacks = state->remainingFinallyCallbacks = promises.size();
+
+ for (size_t i = 0; i < promises.size(); ++i) {
+ PromiseCallbacks individualPromiseCallback;
+ if (callbacks.thenFunc) {
+ individualPromiseCallback.thenFunc = [i, state, callbacks](emscripten::val partialResult) mutable {
+ state->results.emplace(i, std::move(partialResult));
+ if (!--(state->remainingThenCallbacks)) {
+ std::vector<emscripten::val> transformed;
+ for (auto& data : state->results) {
+ transformed.push_back(std::move(data.second));
+ }
+ callbacks.thenFunc(emscripten::val::array(std::move(transformed)));
+ }
+ };
+ }
+ if (callbacks.catchFunc) {
+ individualPromiseCallback.catchFunc = [state, callbacks](emscripten::val error) mutable {
+ callbacks.catchFunc(error);
+ };
+ }
+ individualPromiseCallback.finallyFunc = [state, callbacks]() mutable {
+ if (!--(state->remainingFinallyCallbacks)) {
+ if (callbacks.finallyFunc)
+ callbacks.finallyFunc();
+ // Explicitly reset here for verbosity, this would have been done automatically with the
+ // destruction of the adopted promise in WebPromiseManager.
+ state.reset();
+ }
+ };
+
+ adoptPromise(std::move(promises.at(i)), std::move(individualPromiseCallback));
+ }
+ }
+}
+
+// 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()
{
- element.set(contextPropertyName(name).c_str(), emscripten::val(intptr_t(this)));
- element.set((std::string("on") + name).c_str(), emscripten::val::module_property("qtStdWebEventCallbackActivate"));
+ static bool HaveJspi = jsHaveJspi();
+ return HaveJspi;
}
-void EventCallback::activate(emscripten::val event)
+bool haveAsyncify()
{
- 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();
+ static bool HaveAsyncify = jsHaveAsyncify() || haveJspi();
+ return HaveAsyncify;
}
-std::string EventCallback::contextPropertyName(const std::string &eventName)
+bool canBlockCallingThread()
{
- return std::string("data-qtEventCallbackContext") + eventName;
+ return haveAsyncify() || !emscripten_is_main_runtime_thread();
}
-EMSCRIPTEN_BINDINGS(qtStdwebCalback) {
- emscripten::function("qtStdWebEventCallbackActivate", &EventCallback::activate);
+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
diff --git a/src/corelib/platform/wasm/qstdweb_p.h b/src/corelib/platform/wasm/qstdweb_p.h
index 75c2ec34b1..a3b5bd5b6b 100644
--- a/src/corelib/platform/wasm/qstdweb_p.h
+++ b/src/corelib/platform/wasm/qstdweb_p.h
@@ -1,41 +1,5 @@
-/****************************************************************************
-**
-** Copyright (C) 2019 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the QtCore module of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
+// Copyright (C) 2021 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 QSTDWEB_P_H
#define QSTDWEB_P_H
@@ -51,17 +15,36 @@
// We mean it.
//
-#include <qglobal.h>
+#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 <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[];
// DOM API in C++, implemented using emscripten val.h and bind.h.
- // This is private API and can be extened and changed as needed.
+ // This is private API and can be extended and changed as needed.
+ // The API mirrors that of the native API, with some extensions
+ // to ease usage from C++ code.
class ArrayBuffer;
class Blob;
@@ -71,40 +54,84 @@ namespace qstdweb {
class Uint8Array;
class EventCallback;
- class ArrayBuffer {
+ class Q_CORE_EXPORT ArrayBuffer {
public:
+ explicit ArrayBuffer(uint32_t size);
explicit ArrayBuffer(const emscripten::val &arrayBuffer);
uint32_t byteLength() const;
+ ArrayBuffer slice(uint32_t begin, uint32_t end) const;
+ emscripten::val val() const;
private:
friend class Uint8Array;
emscripten::val m_arrayBuffer = emscripten::val::undefined();
};
- class Blob {
+ 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);
+ Blob slice(uint32_t begin, uint32_t end) const;
+ ArrayBuffer arrayBuffer_sync() const;
+ emscripten::val val() const;
+ std::string type() const;
private:
friend class FileReader;
emscripten::val m_blob = emscripten::val::undefined();
};
- class File {
+ class Q_CORE_EXPORT File {
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,
+ 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 FileList {
+ 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;
explicit FileList(const emscripten::val &fileList);
@@ -112,19 +139,21 @@ namespace qstdweb {
int length() const;
File item(int index) const;
File operator[](int index) const;
+ emscripten::val val() const;
private:
emscripten::val m_fileList = emscripten::val::undefined();
};
- class FileReader {
+ class Q_CORE_EXPORT FileReader {
public:
ArrayBuffer result() const;
void readAsArrayBuffer(const Blob &blob) const;
- void onLoad(const std::function<void ()> &onLoad);
- void onError(const std::function<void ()> &onError);
- void onAbort(const std::function<void ()> &onAbort);
+ 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() const;
private:
emscripten::val m_fileReader = emscripten::val::global("FileReader").new_();
@@ -133,35 +162,170 @@ namespace qstdweb {
std::unique_ptr<EventCallback> m_onAbort;
};
- class Uint8Array {
+ class Q_CORE_EXPORT Uint8Array {
public:
static Uint8Array heap();
explicit Uint8Array(const emscripten::val &uint8Array);
explicit Uint8Array(const ArrayBuffer &buffer);
+ explicit Uint8Array(uint32_t size);
Uint8Array(const ArrayBuffer &buffer, uint32_t offset, uint32_t length);
- Uint8Array(char *buffer, uint32_t size);
+ Uint8Array(const char *buffer, uint32_t size);
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);
+ static Uint8Array copyFrom(const QByteArray &buffer);
+ emscripten::val val() const;
+
private:
static emscripten::val heap_();
static emscripten::val constructor_();
emscripten::val m_uint8Array = emscripten::val::undefined();
};
- class EventCallback
+ class Q_CORE_EXPORT EventCallback
+ {
+ public:
+ EventCallback() = default;
+ ~EventCallback();
+ EventCallback(EventCallback const&) = delete;
+ EventCallback& operator=(EventCallback const&) = delete;
+ EventCallback(emscripten::val element, const std::string &name,
+ const std::function<void(emscripten::val)> &fn);
+
+ private:
+ emscripten::val m_element = emscripten::val::undefined();
+ std::string m_eventName;
+ std::unique_ptr<std::function<void(emscripten::val)>> m_handler;
+ emscripten::val m_eventListener = emscripten::val::undefined();
+ };
+
+ struct PromiseCallbacks
+ {
+ std::function<void(emscripten::val)> thenFunc;
+ std::function<void(emscripten::val)> catchFunc;
+ std::function<void()> finallyFunc;
+ };
+
+ namespace Promise {
+ void Q_CORE_EXPORT adoptPromise(emscripten::val promise, PromiseCallbacks callbacks);
+
+ template<typename... Args>
+ void make(emscripten::val target,
+ QString methodName,
+ PromiseCallbacks callbacks,
+ Args... args)
+ {
+ emscripten::val promiseObject = target.call<emscripten::val>(
+ methodName.toStdString().c_str(), std::forward<Args>(args)...);
+ if (promiseObject.isUndefined() || promiseObject["constructor"]["name"].as<std::string>() != "Promise") {
+ qFatal("This function did not return a promise");
+ }
+
+ adoptPromise(std::move(promiseObject), std::move(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:
- EventCallback(emscripten::val element, const std::string &name, const std::function<void ()> &fn);
- static void activate(emscripten::val event);
+ 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:
- static std::string contextPropertyName(const std::string &eventName);
- std::function<void ()> m_fn;
+ 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()
+ {
+ 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