blob: f7a1f969a8f2075483ae63ccb2117843a9c632d2 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 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 "qeventdispatcher_winrt_p.h"
#include <QtCore/QCoreApplication>
#include <QtCore/QThread>
#include <QtCore/QHash>
#include <QtCore/QMutex>
#include <QtCore/QSemaphore>
#include <QtCore/qfunctions_winrt.h>
#include <private/qabstracteventdispatcher_p.h>
#include <private/qcoreapplication_p.h>
#include <functional>
#include <memory>
#include <wrl.h>
#include <windows.foundation.h>
#include <windows.system.threading.h>
#include <windows.ui.core.h>
#include <windows.applicationmodel.core.h>
using namespace Microsoft::WRL;
using namespace Microsoft::WRL::Wrappers;
using namespace ABI::Windows::System::Threading;
using namespace ABI::Windows::Foundation;
using namespace ABI::Windows::Foundation::Collections;
using namespace ABI::Windows::UI::Core;
using namespace ABI::Windows::ApplicationModel::Core;
QT_BEGIN_NAMESPACE
#define INTERRUPT_HANDLE 0
#define INVALID_TIMER_ID -1
struct WinRTTimerInfo : public QAbstractEventDispatcher::TimerInfo {
WinRTTimerInfo(int timerId = INVALID_TIMER_ID, int interval = 0, Qt::TimerType timerType = Qt::CoarseTimer,
QObject *obj = 0, quint64 tt = 0) :
QAbstractEventDispatcher::TimerInfo(timerId, interval, timerType),
inEvent(false), object(obj), targetTime(tt)
{
}
bool inEvent;
QObject *object;
quint64 targetTime;
};
class AgileDispatchedHandler : public RuntimeClass<RuntimeClassFlags<WinRtClassicComMix>, IDispatchedHandler, IAgileObject>
{
public:
AgileDispatchedHandler(const std::function<HRESULT()> &delegate)
: delegate(delegate)
{
}
HRESULT __stdcall Invoke()
{
return delegate();
}
private:
std::function<HRESULT()> delegate;
};
class QWorkHandler : public IWorkItemHandler
{
public:
QWorkHandler(const std::function<HRESULT()> &delegate)
: m_delegate(delegate)
{
}
STDMETHODIMP Invoke(ABI::Windows::Foundation::IAsyncAction *operation)
{
HRESULT res = m_delegate();
Q_UNUSED(operation);
return res;
}
STDMETHODIMP QueryInterface(REFIID riid, void FAR* FAR* ppvObj)
{
if (riid == IID_IUnknown || riid == IID_IWorkItemHandler) {
*ppvObj = this;
AddRef();
return NOERROR;
}
*ppvObj = NULL;
return ResultFromScode(E_NOINTERFACE);
}
STDMETHODIMP_(ULONG) AddRef(void)
{
return ++m_refs;
}
STDMETHODIMP_(ULONG) Release(void)
{
if (--m_refs == 0) {
delete this;
return 0;
}
return m_refs;
}
private:
std::function<HRESULT()> m_delegate;
ULONG m_refs{0};
};
class QEventDispatcherWinRTPrivate : public QAbstractEventDispatcherPrivate
{
Q_DECLARE_PUBLIC(QEventDispatcherWinRT)
public:
QEventDispatcherWinRTPrivate();
~QEventDispatcherWinRTPrivate();
private:
QHash<int, QObject *> timerIdToObject;
QVector<WinRTTimerInfo> timerInfos;
mutable QMutex timerInfoLock;
QHash<HANDLE, int> timerHandleToId;
QHash<int, HANDLE> timerIdToHandle;
QHash<int, HANDLE> timerIdToCancelHandle;
void addTimer(int id, int interval, Qt::TimerType type, QObject *obj,
HANDLE handle, HANDLE cancelHandle)
{
// Zero timer events do not need these handles.
if (interval > 0) {
timerHandleToId.insert(handle, id);
timerIdToHandle.insert(id, handle);
timerIdToCancelHandle.insert(id, cancelHandle);
}
const quint64 targetTime = qt_msectime() + interval;
const WinRTTimerInfo info(id, interval, type, obj, targetTime);
QMutexLocker locker(&timerInfoLock);
if (id >= timerInfos.size())
timerInfos.resize(id + 1);
timerInfos[id] = info;
timerIdToObject.insert(id, obj);
}
bool removeTimer(int id)
{
QMutexLocker locker(&timerInfoLock);
if (id >= timerInfos.size())
return false;
WinRTTimerInfo &info = timerInfos[id];
if (info.timerId == INVALID_TIMER_ID)
return false;
if (info.interval > 0 && (!timerIdToHandle.contains(id) || !timerIdToCancelHandle.contains(id)))
return false;
info.timerId = INVALID_TIMER_ID;
// Remove invalid timerinfos from the vector's end, if the timer with the highest id was removed
int lastTimer = timerInfos.size() - 1;
while (lastTimer >= 0 && timerInfos.at(lastTimer).timerId == INVALID_TIMER_ID)
--lastTimer;
if (lastTimer >= 0 && lastTimer != timerInfos.size() - 1)
timerInfos.resize(lastTimer + 1);
timerIdToObject.remove(id);
// ... remove handle from all lists
if (info.interval > 0) {
HANDLE handle = timerIdToHandle.take(id);
timerHandleToId.remove(handle);
SetEvent(timerIdToCancelHandle.take(id));
}
return true;
}
};
QEventDispatcherWinRT::QEventDispatcherWinRT(QObject *parent)
: QAbstractEventDispatcher(*new QEventDispatcherWinRTPrivate, parent)
{
}
QEventDispatcherWinRT::QEventDispatcherWinRT(QEventDispatcherWinRTPrivate &dd, QObject *parent)
: QAbstractEventDispatcher(dd, parent)
{ }
QEventDispatcherWinRT::~QEventDispatcherWinRT()
{
}
HRESULT QEventDispatcherWinRT::runOnXamlThread(const std::function<HRESULT ()> &delegate, bool waitForRun)
{
static __declspec(thread) ICoreDispatcher *dispatcher = nullptr;
HRESULT hr;
if (!dispatcher) {
ComPtr<ICoreImmersiveApplication> application;
hr = RoGetActivationFactory(HString::MakeReference(RuntimeClass_Windows_ApplicationModel_Core_CoreApplication).Get(),
IID_PPV_ARGS(&application));
ComPtr<ICoreApplicationView> view;
hr = application->get_MainView(&view);
if (SUCCEEDED(hr) && view) {
ComPtr<ICoreWindow> window;
hr = view->get_CoreWindow(&window);
Q_ASSERT_SUCCEEDED(hr);
if (!window) {
// In case the application is launched via activation
// there might not be a main view (eg ShareTarget).
// Hence iterate through the available views and try to find
// a dispatcher in there
ComPtr<IVectorView<CoreApplicationView*>> appViews;
hr = application->get_Views(&appViews);
Q_ASSERT_SUCCEEDED(hr);
quint32 count;
hr = appViews->get_Size(&count);
Q_ASSERT_SUCCEEDED(hr);
for (quint32 i = 0; i < count; ++i) {
hr = appViews->GetAt(i, &view);
Q_ASSERT_SUCCEEDED(hr);
hr = view->get_CoreWindow(&window);
Q_ASSERT_SUCCEEDED(hr);
if (window) {
hr = window->get_Dispatcher(&dispatcher);
Q_ASSERT_SUCCEEDED(hr);
if (dispatcher)
break;
}
}
} else {
hr = window->get_Dispatcher(&dispatcher);
Q_ASSERT_SUCCEEDED(hr);
}
}
}
if (Q_UNLIKELY(!dispatcher)) {
// In case the application is launched in a way that has no UI and
// also does not allow to create one, e.g. as a background task.
// Features like network operations do still work, others might cause
// errors in that case.
ComPtr<IThreadPoolStatics> tpStatics;
hr = RoGetActivationFactory(HString::MakeReference(RuntimeClass_Windows_System_Threading_ThreadPool).Get(),
IID_PPV_ARGS(&tpStatics));
ComPtr<IAsyncAction> op;
hr = tpStatics.Get()->RunAsync(new QWorkHandler(delegate), &op);
if (FAILED(hr) || !waitForRun)
return hr;
return QWinRTFunctions::await(op);
}
boolean onXamlThread;
hr = dispatcher->get_HasThreadAccess(&onXamlThread);
Q_ASSERT_SUCCEEDED(hr);
if (onXamlThread) // Already there
return delegate();
ComPtr<IAsyncAction> op;
hr = dispatcher->RunAsync(CoreDispatcherPriority_Normal, Make<AgileDispatchedHandler>(delegate).Get(), &op);
if (FAILED(hr) || !waitForRun)
return hr;
return QWinRTFunctions::await(op);
}
HRESULT QEventDispatcherWinRT::runOnMainThread(const std::function<HRESULT()> &delegate, int timeout)
{
if (QThread::currentThread() == QCoreApplication::instance()->thread())
return delegate();
struct State {
QSemaphore semaphore;
HRESULT result;
};
const auto state = std::make_shared<State>();
QMetaObject::invokeMethod(QCoreApplication::instance(), [delegate, state]() {
const QSemaphoreReleaser releaser{state->semaphore};
state->result = delegate();
}, nullptr);
return state->semaphore.tryAcquire(1, timeout) ? state->result : E_FAIL;
}
bool QEventDispatcherWinRT::processEvents(QEventLoop::ProcessEventsFlags flags)
{
Q_D(QEventDispatcherWinRT);
DWORD waitTime = 0;
do {
// Additional user events have to be handled before timer events, but the function may not
// return yet.
const bool userEventsSent = sendPostedEvents(flags);
const QVector<HANDLE> timerHandles = d->timerIdToHandle.values().toVector();
if (waitTime)
emit aboutToBlock();
bool timerEventsSent = false;
DWORD waitResult = WaitForMultipleObjectsEx(timerHandles.count(), timerHandles.constData(), FALSE, waitTime, TRUE);
while (waitResult >= WAIT_OBJECT_0 && waitResult < WAIT_OBJECT_0 + timerHandles.count()) {
timerEventsSent = true;
const HANDLE handle = timerHandles.value(waitResult - WAIT_OBJECT_0);
ResetEvent(handle);
const int timerId = d->timerHandleToId.value(handle);
if (timerId == INTERRUPT_HANDLE)
break;
{
QMutexLocker locker(&d->timerInfoLock);
WinRTTimerInfo &info = d->timerInfos[timerId];
Q_ASSERT(info.timerId != INVALID_TIMER_ID);
QCoreApplication::postEvent(this, new QTimerEvent(timerId));
// Update timer's targetTime
const quint64 targetTime = qt_msectime() + info.interval;
info.targetTime = targetTime;
}
waitResult = WaitForMultipleObjectsEx(timerHandles.count(), timerHandles.constData(), FALSE, 0, TRUE);
}
emit awake();
if (timerEventsSent || userEventsSent)
return true;
// We cannot wait infinitely like on other platforms, as
// WaitForMultipleObjectsEx might not return.
// For instance win32 uses MsgWaitForMultipleObjects to hook
// into the native event loop, while WinRT handles those
// via callbacks.
waitTime = 1;
} while (flags & QEventLoop::WaitForMoreEvents);
return false;
}
bool QEventDispatcherWinRT::sendPostedEvents(QEventLoop::ProcessEventsFlags flags)
{
Q_UNUSED(flags);
if (hasPendingEvents()) {
QCoreApplication::sendPostedEvents();
return true;
}
return false;
}
bool QEventDispatcherWinRT::hasPendingEvents()
{
return qGlobalPostedEventsCount();
}
void QEventDispatcherWinRT::registerSocketNotifier(QSocketNotifier *notifier)
{
Q_UNUSED(notifier);
Q_UNIMPLEMENTED();
}
void QEventDispatcherWinRT::unregisterSocketNotifier(QSocketNotifier *notifier)
{
Q_UNUSED(notifier);
Q_UNIMPLEMENTED();
}
void QEventDispatcherWinRT::registerTimer(int timerId, int interval, Qt::TimerType timerType, QObject *object)
{
Q_UNUSED(timerType);
if (timerId < 1 || interval < 0 || !object) {
#ifndef QT_NO_DEBUG
qWarning("QEventDispatcherWinRT::registerTimer: invalid arguments");
#endif
return;
} else if (object->thread() != thread() || thread() != QThread::currentThread()) {
#ifndef QT_NO_DEBUG
qWarning("QEventDispatcherWinRT::registerTimer: timers cannot be started from another thread");
#endif
return;
}
Q_D(QEventDispatcherWinRT);
// Don't use timer factory for zero-delay timers
if (interval == 0u) {
d->addTimer(timerId, interval, timerType, object, INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE);
QCoreApplication::postEvent(this, new QTimerEvent(timerId));
return;
}
TimeSpan period;
// TimeSpan is based on 100-nanosecond units
period.Duration = qMax(qint64(1), qint64(interval) * 10000);
const HANDLE handle = CreateEventEx(NULL, NULL, CREATE_EVENT_MANUAL_RESET, SYNCHRONIZE | EVENT_MODIFY_STATE);
const HANDLE cancelHandle = CreateEventEx(NULL, NULL, CREATE_EVENT_MANUAL_RESET, SYNCHRONIZE|EVENT_MODIFY_STATE);
HRESULT hr = runOnXamlThread([cancelHandle, handle, period]() {
static ComPtr<IThreadPoolTimerStatics> timerFactory;
HRESULT hr;
if (!timerFactory) {
hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_System_Threading_ThreadPoolTimer).Get(),
&timerFactory);
Q_ASSERT_SUCCEEDED(hr);
}
IThreadPoolTimer *timer;
hr = timerFactory->CreatePeriodicTimerWithCompletion(
Callback<ITimerElapsedHandler>([handle, cancelHandle](IThreadPoolTimer *timer) {
DWORD cancelResult = WaitForSingleObjectEx(cancelHandle, 0, TRUE);
if (cancelResult == WAIT_OBJECT_0) {
timer->Cancel();
return S_OK;
}
if (!SetEvent(handle)) {
Q_ASSERT_X(false, "QEventDispatcherWinRT::registerTimer",
"SetEvent should never fail here");
return S_OK;
}
return S_OK;
}).Get(), period,
Callback<ITimerDestroyedHandler>([handle, cancelHandle](IThreadPoolTimer *) {
CloseHandle(handle);
CloseHandle(cancelHandle);
return S_OK;
}).Get(), &timer);
RETURN_HR_IF_FAILED("Failed to create periodic timer");
return hr;
}, false);
if (FAILED(hr)) {
CloseHandle(handle);
CloseHandle(cancelHandle);
return;
}
d->addTimer(timerId, interval, timerType, object, handle, cancelHandle);
}
bool QEventDispatcherWinRT::unregisterTimer(int timerId)
{
if (timerId < 1) {
#ifndef QT_NO_DEBUG
qWarning("QEventDispatcherWinRT::unregisterTimer: invalid argument");
#endif
return false;
}
if (thread() != QThread::currentThread()) {
#ifndef QT_NO_DEBUG
qWarning("QEventDispatcherWinRT::unregisterTimer: timers cannot be stopped from another thread");
#endif
return false;
}
// As we post all timer events internally, they have to pe removed to prevent stray events
QCoreApplicationPrivate::removePostedTimerEvent(this, timerId);
Q_D(QEventDispatcherWinRT);
return d->removeTimer(timerId);
}
bool QEventDispatcherWinRT::unregisterTimers(QObject *object)
{
if (!object) {
#ifndef QT_NO_DEBUG
qWarning("QEventDispatcherWinRT::unregisterTimers: invalid argument");
#endif
return false;
}
QThread *currentThread = QThread::currentThread();
if (object->thread() != thread() || thread() != currentThread) {
#ifndef QT_NO_DEBUG
qWarning("QEventDispatcherWinRT::unregisterTimers: timers cannot be stopped from another thread");
#endif
return false;
}
Q_D(QEventDispatcherWinRT);
const auto timerIds = d->timerIdToObject.keys(); // ### FIXME: iterate over hash directly? But unregisterTimer() modifies the hash!
for (int id : timerIds) {
if (d->timerIdToObject.value(id) == object)
unregisterTimer(id);
}
return true;
}
QList<QAbstractEventDispatcher::TimerInfo> QEventDispatcherWinRT::registeredTimers(QObject *object) const
{
if (!object) {
#ifndef QT_NO_DEBUG
qWarning("QEventDispatcherWinRT:registeredTimers: invalid argument");
#endif
return QList<TimerInfo>();
}
Q_D(const QEventDispatcherWinRT);
QMutexLocker locker(&d->timerInfoLock);
QList<TimerInfo> timerInfos;
for (const WinRTTimerInfo &info : d->timerInfos) {
if (info.object == object && info.timerId != INVALID_TIMER_ID)
timerInfos.append(info);
}
return timerInfos;
}
bool QEventDispatcherWinRT::registerEventNotifier(QWinEventNotifier *notifier)
{
Q_UNUSED(notifier);
Q_UNIMPLEMENTED();
return false;
}
void QEventDispatcherWinRT::unregisterEventNotifier(QWinEventNotifier *notifier)
{
Q_UNUSED(notifier);
Q_UNIMPLEMENTED();
}
int QEventDispatcherWinRT::remainingTime(int timerId)
{
if (timerId < 1) {
#ifndef QT_NO_DEBUG
qWarning("QEventDispatcherWinRT::remainingTime: invalid argument");
#endif
return -1;
}
Q_D(QEventDispatcherWinRT);
QMutexLocker locker(&d->timerInfoLock);
const WinRTTimerInfo timerInfo = d->timerInfos.at(timerId);
if (timerInfo.timerId == INVALID_TIMER_ID) {
#ifndef QT_NO_DEBUG
qWarning("QEventDispatcherWinRT::remainingTime: timer id %d not found", timerId);
#endif
return -1;
}
const quint64 currentTime = qt_msectime();
if (currentTime < timerInfo.targetTime) {
// time to wait
return timerInfo.targetTime - currentTime;
} else {
return 0;
}
return -1;
}
void QEventDispatcherWinRT::wakeUp()
{
}
void QEventDispatcherWinRT::interrupt()
{
Q_D(QEventDispatcherWinRT);
SetEvent(d->timerIdToHandle.value(INTERRUPT_HANDLE));
}
void QEventDispatcherWinRT::flush()
{
}
void QEventDispatcherWinRT::startingUp()
{
}
void QEventDispatcherWinRT::closingDown()
{
}
bool QEventDispatcherWinRT::event(QEvent *e)
{
Q_D(QEventDispatcherWinRT);
switch (e->type()) {
case QEvent::Timer: {
QTimerEvent *timerEvent = static_cast<QTimerEvent *>(e);
const int id = timerEvent->timerId();
QMutexLocker locker(&d->timerInfoLock);
Q_ASSERT(id < d->timerInfos.size());
WinRTTimerInfo &info = d->timerInfos[id];
Q_ASSERT(info.timerId != INVALID_TIMER_ID);
if (info.inEvent) // but don't allow event to recurse
break;
info.inEvent = true;
QObject *timerObj = d->timerIdToObject.value(id);
locker.unlock();
QTimerEvent te(id);
QCoreApplication::sendEvent(timerObj, &te);
locker.relock();
// The timer might have been removed in the meanwhile. If the timer was
// the last one in the list, id is bigger than the list's size.
// Otherwise, the id will just be set to INVALID_TIMER_ID.
if (id >= d->timerInfos.size() || info.timerId == INVALID_TIMER_ID)
break;
if (info.interval == 0 && info.inEvent) {
// post the next zero timer event as long as the timer was not restarted
QCoreApplication::postEvent(this, new QTimerEvent(id));
}
info.inEvent = false;
}
default:
break;
}
return QAbstractEventDispatcher::event(e);
}
QEventDispatcherWinRTPrivate::QEventDispatcherWinRTPrivate()
{
const bool isGuiThread = QCoreApplication::instance() &&
QThread::currentThread() == QCoreApplication::instance()->thread();
CoInitializeEx(NULL, isGuiThread ? COINIT_APARTMENTTHREADED : COINIT_MULTITHREADED);
HANDLE interruptHandle = CreateEventEx(NULL, NULL, NULL, SYNCHRONIZE|EVENT_MODIFY_STATE);
timerIdToHandle.insert(INTERRUPT_HANDLE, interruptHandle);
timerHandleToId.insert(interruptHandle, INTERRUPT_HANDLE);
timerInfos.reserve(256);
}
QEventDispatcherWinRTPrivate::~QEventDispatcherWinRTPrivate()
{
CloseHandle(timerIdToHandle.value(INTERRUPT_HANDLE));
CoUninitialize();
}
QT_END_NAMESPACE