/**************************************************************************** ** ** 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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, IDispatchedHandler, IAgileObject> { public: AgileDispatchedHandler(const std::function &delegate) : delegate(delegate) { } HRESULT __stdcall Invoke() { return delegate(); } private: std::function delegate; }; class QWorkHandler : public IWorkItemHandler { public: QWorkHandler(const std::function &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 m_delegate; ULONG m_refs{0}; }; class QEventDispatcherWinRTPrivate : public QAbstractEventDispatcherPrivate { Q_DECLARE_PUBLIC(QEventDispatcherWinRT) public: QEventDispatcherWinRTPrivate(); ~QEventDispatcherWinRTPrivate(); private: QHash timerIdToObject; QVector timerInfos; mutable QMutex timerInfoLock; QHash timerHandleToId; QHash timerIdToHandle; QHash 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 &delegate, bool waitForRun) { static __declspec(thread) ICoreDispatcher *dispatcher = nullptr; HRESULT hr; if (!dispatcher) { ComPtr application; hr = RoGetActivationFactory(HString::MakeReference(RuntimeClass_Windows_ApplicationModel_Core_CoreApplication).Get(), IID_PPV_ARGS(&application)); ComPtr view; hr = application->get_MainView(&view); if (SUCCEEDED(hr) && view) { ComPtr 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> 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 tpStatics; hr = RoGetActivationFactory(HString::MakeReference(RuntimeClass_Windows_System_Threading_ThreadPool).Get(), IID_PPV_ARGS(&tpStatics)); ComPtr 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 op; hr = dispatcher->RunAsync(CoreDispatcherPriority_Normal, Make(delegate).Get(), &op); if (FAILED(hr) || !waitForRun) return hr; return QWinRTFunctions::await(op); } HRESULT QEventDispatcherWinRT::runOnMainThread(const std::function &delegate, int timeout) { if (QThread::currentThread() == QCoreApplication::instance()->thread()) return delegate(); struct State { QSemaphore semaphore; HRESULT result; }; const auto state = std::make_shared(); 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 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 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([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([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 QEventDispatcherWinRT::registeredTimers(QObject *object) const { if (!object) { #ifndef QT_NO_DEBUG qWarning("QEventDispatcherWinRT:registeredTimers: invalid argument"); #endif return QList(); } Q_D(const QEventDispatcherWinRT); QMutexLocker locker(&d->timerInfoLock); QList 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(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