mirror of
https://github.com/crystalidea/qt6windows7.git
synced 2024-12-18 08:26:29 +08:00
qt 6.8.0 support
This commit is contained in:
parent
c4707639b4
commit
09a199127f
@ -1,11 +1,11 @@
|
||||
This is Qt 6.7.3 **qtbase module** backport that runs on Windows 7/8. The repository contains patched source files from the qtbase module.
|
||||
This is Qt 6.8.0 **qtbase module** backport that runs on Windows 7/8. The repository contains patched source files from the qtbase module.
|
||||
Approach is based on this [forum thread](https://forum.qt.io/topic/133002/qt-creator-6-0-1-and-qt-6-2-2-running-on-windows-7/60) but better: many improvements amongst important fallbacks to default Qt 6 behaviour when running on newer Windows.
|
||||
|
||||
- After replacing qtbase source files with the patched ones from this repository, you can compile Qt yourself using compiler and build options you need.
|
||||
- You can use our [compile_win.pl](https://github.com/crystalidea/qt-build-tools/tree/master/6.7.3) build script (uses Visual C++ 2022 with OpenSSL 3.0.13 statically linked)
|
||||
- You can use our [compile_win.pl](https://github.com/crystalidea/qt-build-tools/tree/master/6.8.0) build script (uses Visual C++ 2022 with OpenSSL 3.0.13 statically linked)
|
||||
- You can download our [prebuild Qt dlls](https://github.com/crystalidea/qt6windows7/releases) which also have Qt Designer binary for demonstration
|
||||
|
||||
**Qt 6.7.3 designer running on Windows 7**:
|
||||
**Qt 6.8.0 designer running on Windows 7**:
|
||||
|
||||
![Qt Designer](designer.png)
|
||||
|
||||
@ -15,6 +15,8 @@ Approach is based on this [forum thread](https://forum.qt.io/topic/133002/qt-cre
|
||||
|
||||
### Older versions:
|
||||
|
||||
- [Qt 6.7.3](https://github.com/crystalidea/qt6windows7/releases/tag/v6.7.3)
|
||||
- [Qt 6.7.2](https://github.com/crystalidea/qt6windows7/releases/tag/v6.7.2)
|
||||
- [Qt 6.6.3](https://github.com/crystalidea/qt6windows7/releases/tag/v6.6.3)
|
||||
- [Qt 6.6.2](https://github.com/crystalidea/qt6windows7/releases/tag/v6.6.2)
|
||||
- [Qt 6.6.1](https://github.com/crystalidea/qt6windows7/releases/tag/v6.6.1)
|
||||
|
BIN
designer.png
BIN
designer.png
Binary file not shown.
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 72 KiB |
@ -28,6 +28,7 @@ QComHelper::QComHelper(COINIT concurrencyModel)
|
||||
|
||||
QComHelper::~QComHelper()
|
||||
{
|
||||
Q_ASSERT(m_threadId == GetCurrentThreadId());
|
||||
if (SUCCEEDED(m_initResult))
|
||||
CoUninitialize();
|
||||
}
|
||||
|
648
qtbase/src/corelib/thread/qthread_win.cpp
Normal file
648
qtbase/src/corelib/thread/qthread_win.cpp
Normal file
@ -0,0 +1,648 @@
|
||||
// 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
|
||||
|
||||
#include "qthread.h"
|
||||
#include "qthread_p.h"
|
||||
#include "qthreadstorage.h"
|
||||
#include "qmutex.h"
|
||||
|
||||
#include <qcoreapplication.h>
|
||||
#include <qpointer.h>
|
||||
|
||||
#include <private/qcoreapplication_p.h>
|
||||
#include <private/qeventdispatcher_win_p.h>
|
||||
|
||||
#include <qt_windows.h>
|
||||
|
||||
#ifndef _MT
|
||||
# define _MT
|
||||
#endif // _MT
|
||||
#include <process.h>
|
||||
|
||||
extern "C" {
|
||||
// MinGW is missing the declaration of SetThreadDescription:
|
||||
WINBASEAPI
|
||||
HRESULT
|
||||
WINAPI
|
||||
SetThreadDescription(
|
||||
_In_ HANDLE hThread,
|
||||
_In_ PCWSTR lpThreadDescription
|
||||
);
|
||||
}
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
#if QT_CONFIG(thread)
|
||||
|
||||
void qt_watch_adopted_thread(const HANDLE adoptedThreadHandle, QThread *qthread);
|
||||
DWORD WINAPI qt_adopted_thread_watcher_function(LPVOID);
|
||||
|
||||
static DWORD qt_current_thread_data_tls_index = TLS_OUT_OF_INDEXES;
|
||||
void qt_create_tls()
|
||||
{
|
||||
if (qt_current_thread_data_tls_index != TLS_OUT_OF_INDEXES)
|
||||
return;
|
||||
Q_CONSTINIT static QBasicMutex mutex;
|
||||
QMutexLocker locker(&mutex);
|
||||
if (qt_current_thread_data_tls_index != TLS_OUT_OF_INDEXES)
|
||||
return;
|
||||
qt_current_thread_data_tls_index = TlsAlloc();
|
||||
}
|
||||
|
||||
static void qt_free_tls()
|
||||
{
|
||||
if (qt_current_thread_data_tls_index != TLS_OUT_OF_INDEXES) {
|
||||
TlsFree(qt_current_thread_data_tls_index);
|
||||
qt_current_thread_data_tls_index = TLS_OUT_OF_INDEXES;
|
||||
}
|
||||
}
|
||||
Q_DESTRUCTOR_FUNCTION(qt_free_tls)
|
||||
|
||||
/*
|
||||
QThreadData
|
||||
*/
|
||||
void QThreadData::clearCurrentThreadData()
|
||||
{
|
||||
TlsSetValue(qt_current_thread_data_tls_index, 0);
|
||||
}
|
||||
|
||||
QThreadData *QThreadData::current(bool createIfNecessary)
|
||||
{
|
||||
qt_create_tls();
|
||||
QThreadData *threadData = reinterpret_cast<QThreadData *>(TlsGetValue(qt_current_thread_data_tls_index));
|
||||
if (!threadData && createIfNecessary) {
|
||||
threadData = new QThreadData;
|
||||
// This needs to be called prior to new AdoptedThread() to
|
||||
// avoid recursion.
|
||||
TlsSetValue(qt_current_thread_data_tls_index, threadData);
|
||||
QT_TRY {
|
||||
threadData->thread = new QAdoptedThread(threadData);
|
||||
} QT_CATCH(...) {
|
||||
TlsSetValue(qt_current_thread_data_tls_index, 0);
|
||||
threadData->deref();
|
||||
threadData = 0;
|
||||
QT_RETHROW;
|
||||
}
|
||||
threadData->deref();
|
||||
threadData->isAdopted = true;
|
||||
threadData->threadId.storeRelaxed(reinterpret_cast<Qt::HANDLE>(quintptr(GetCurrentThreadId())));
|
||||
|
||||
if (!QCoreApplicationPrivate::theMainThreadId) {
|
||||
auto *mainThread = threadData->thread.loadRelaxed();
|
||||
mainThread->setObjectName("Qt mainThread");
|
||||
QCoreApplicationPrivate::theMainThread.storeRelease(mainThread);
|
||||
QCoreApplicationPrivate::theMainThreadId.storeRelaxed(threadData->threadId.loadRelaxed());
|
||||
} else {
|
||||
HANDLE realHandle = INVALID_HANDLE_VALUE;
|
||||
DuplicateHandle(GetCurrentProcess(),
|
||||
GetCurrentThread(),
|
||||
GetCurrentProcess(),
|
||||
&realHandle,
|
||||
0,
|
||||
FALSE,
|
||||
DUPLICATE_SAME_ACCESS);
|
||||
qt_watch_adopted_thread(realHandle, threadData->thread);
|
||||
}
|
||||
}
|
||||
return threadData;
|
||||
}
|
||||
|
||||
void QAdoptedThread::init()
|
||||
{
|
||||
d_func()->handle = GetCurrentThread();
|
||||
d_func()->id = GetCurrentThreadId();
|
||||
}
|
||||
|
||||
static QList<HANDLE> qt_adopted_thread_handles;
|
||||
static QList<QThread *> qt_adopted_qthreads;
|
||||
Q_CONSTINIT static QBasicMutex qt_adopted_thread_watcher_mutex;
|
||||
static DWORD qt_adopted_thread_watcher_id = 0;
|
||||
static HANDLE qt_adopted_thread_wakeup = 0;
|
||||
|
||||
/*!
|
||||
\internal
|
||||
Adds an adopted thread to the list of threads that Qt watches to make sure
|
||||
the thread data is properly cleaned up. This function starts the watcher
|
||||
thread if necessary.
|
||||
*/
|
||||
void qt_watch_adopted_thread(const HANDLE adoptedThreadHandle, QThread *qthread)
|
||||
{
|
||||
QMutexLocker lock(&qt_adopted_thread_watcher_mutex);
|
||||
|
||||
if (GetCurrentThreadId() == qt_adopted_thread_watcher_id) {
|
||||
CloseHandle(adoptedThreadHandle);
|
||||
return;
|
||||
}
|
||||
|
||||
qt_adopted_thread_handles.append(adoptedThreadHandle);
|
||||
qt_adopted_qthreads.append(qthread);
|
||||
|
||||
// Start watcher thread if it is not already running.
|
||||
if (qt_adopted_thread_watcher_id == 0) {
|
||||
if (qt_adopted_thread_wakeup == 0) {
|
||||
qt_adopted_thread_wakeup = CreateEvent(0, false, false, 0);
|
||||
qt_adopted_thread_handles.prepend(qt_adopted_thread_wakeup);
|
||||
}
|
||||
|
||||
CloseHandle(CreateThread(0, 0, qt_adopted_thread_watcher_function, 0, 0, &qt_adopted_thread_watcher_id));
|
||||
} else {
|
||||
SetEvent(qt_adopted_thread_wakeup);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
This function loops and waits for native adopted threads to finish.
|
||||
When this happens it derefs the QThreadData for the adopted thread
|
||||
to make sure it gets cleaned up properly.
|
||||
*/
|
||||
DWORD WINAPI qt_adopted_thread_watcher_function(LPVOID)
|
||||
{
|
||||
forever {
|
||||
qt_adopted_thread_watcher_mutex.lock();
|
||||
|
||||
if (qt_adopted_thread_handles.count() == 1) {
|
||||
qt_adopted_thread_watcher_id = 0;
|
||||
qt_adopted_thread_watcher_mutex.unlock();
|
||||
break;
|
||||
}
|
||||
|
||||
QList<HANDLE> handlesCopy = qt_adopted_thread_handles;
|
||||
qt_adopted_thread_watcher_mutex.unlock();
|
||||
|
||||
DWORD ret = WAIT_TIMEOUT;
|
||||
int count;
|
||||
int offset;
|
||||
int loops = handlesCopy.size() / MAXIMUM_WAIT_OBJECTS;
|
||||
if (handlesCopy.size() % MAXIMUM_WAIT_OBJECTS)
|
||||
++loops;
|
||||
if (loops == 1) {
|
||||
// no need to loop, no timeout
|
||||
offset = 0;
|
||||
count = handlesCopy.count();
|
||||
ret = WaitForMultipleObjects(handlesCopy.count(), handlesCopy.constData(), false, INFINITE);
|
||||
} else {
|
||||
int loop = 0;
|
||||
do {
|
||||
offset = loop * MAXIMUM_WAIT_OBJECTS;
|
||||
count = qMin(handlesCopy.count() - offset, MAXIMUM_WAIT_OBJECTS);
|
||||
ret = WaitForMultipleObjects(count, handlesCopy.constData() + offset, false, 100);
|
||||
loop = (loop + 1) % loops;
|
||||
} while (ret == WAIT_TIMEOUT);
|
||||
}
|
||||
|
||||
if (ret == WAIT_FAILED || ret >= WAIT_OBJECT_0 + uint(count)) {
|
||||
qWarning("QThread internal error while waiting for adopted threads: %d", int(GetLastError()));
|
||||
continue;
|
||||
}
|
||||
|
||||
const int handleIndex = offset + ret - WAIT_OBJECT_0;
|
||||
if (handleIndex == 0) // New handle to watch was added.
|
||||
continue;
|
||||
const int qthreadIndex = handleIndex - 1;
|
||||
|
||||
qt_adopted_thread_watcher_mutex.lock();
|
||||
QThreadData *data = QThreadData::get2(qt_adopted_qthreads.at(qthreadIndex));
|
||||
qt_adopted_thread_watcher_mutex.unlock();
|
||||
if (data->isAdopted) {
|
||||
QThread *thread = data->thread;
|
||||
Q_ASSERT(thread);
|
||||
auto thread_p = static_cast<QThreadPrivate *>(QObjectPrivate::get(thread));
|
||||
Q_UNUSED(thread_p);
|
||||
Q_ASSERT(!thread_p->finished);
|
||||
QThreadPrivate::finish(thread);
|
||||
}
|
||||
data->deref();
|
||||
|
||||
QMutexLocker lock(&qt_adopted_thread_watcher_mutex);
|
||||
CloseHandle(qt_adopted_thread_handles.at(handleIndex));
|
||||
qt_adopted_thread_handles.remove(handleIndex);
|
||||
qt_adopted_qthreads.remove(qthreadIndex);
|
||||
}
|
||||
|
||||
QThreadData *threadData = reinterpret_cast<QThreadData *>(TlsGetValue(qt_current_thread_data_tls_index));
|
||||
if (threadData)
|
||||
threadData->deref();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifndef Q_OS_WIN64
|
||||
# define ULONG_PTR DWORD
|
||||
#endif
|
||||
|
||||
typedef struct tagTHREADNAME_INFO
|
||||
{
|
||||
DWORD dwType; // must be 0x1000
|
||||
LPCSTR szName; // pointer to name (in user addr space)
|
||||
HANDLE dwThreadID; // thread ID (-1=caller thread)
|
||||
DWORD dwFlags; // reserved for future use, must be zero
|
||||
} THREADNAME_INFO;
|
||||
|
||||
typedef HRESULT(WINAPI* SetThreadDescriptionFunc)(HANDLE, PCWSTR);
|
||||
|
||||
// Helper function to set the thread name using RaiseException and __try
|
||||
static void setThreadNameUsingException(HANDLE threadId, LPCSTR threadName) {
|
||||
THREADNAME_INFO info;
|
||||
info.dwType = 0x1000;
|
||||
info.szName = threadName;
|
||||
info.dwThreadID = threadId;
|
||||
info.dwFlags = 0;
|
||||
|
||||
__try {
|
||||
RaiseException(0x406D1388, 0, sizeof(info) / sizeof(DWORD),
|
||||
reinterpret_cast<const ULONG_PTR*>(&info));
|
||||
}
|
||||
__except (EXCEPTION_CONTINUE_EXECUTION) {
|
||||
}
|
||||
}
|
||||
|
||||
void qt_set_thread_name(HANDLE threadId, const QString &name)
|
||||
{
|
||||
HMODULE hKernel32 = GetModuleHandleW(L"Kernel32.dll");
|
||||
|
||||
SetThreadDescriptionFunc pSetThreadDescription =
|
||||
reinterpret_cast<SetThreadDescriptionFunc>(GetProcAddress(hKernel32, "SetThreadDescription"));
|
||||
|
||||
if (pSetThreadDescription) {
|
||||
pSetThreadDescription(threadId, reinterpret_cast<const wchar_t *>(name.utf16()) );
|
||||
}
|
||||
else {
|
||||
|
||||
#if defined(Q_CC_MSVC)
|
||||
|
||||
std::string stdStr = name.toStdString();
|
||||
LPCSTR threadName = stdStr.c_str();
|
||||
setThreadNameUsingException(threadId, threadName);
|
||||
|
||||
#endif // Q_CC_MSVC
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
** QThreadPrivate
|
||||
*************************************************************************/
|
||||
|
||||
#endif // QT_CONFIG(thread)
|
||||
|
||||
QAbstractEventDispatcher *QThreadPrivate::createEventDispatcher(QThreadData *data)
|
||||
{
|
||||
Q_UNUSED(data);
|
||||
return new QEventDispatcherWin32;
|
||||
}
|
||||
|
||||
#if QT_CONFIG(thread)
|
||||
|
||||
unsigned int __stdcall QT_ENSURE_STACK_ALIGNED_FOR_SSE QThreadPrivate::start(void *arg) noexcept
|
||||
{
|
||||
QThread *thr = reinterpret_cast<QThread *>(arg);
|
||||
QThreadData *data = QThreadData::get2(thr);
|
||||
|
||||
qt_create_tls();
|
||||
TlsSetValue(qt_current_thread_data_tls_index, data);
|
||||
data->threadId.storeRelaxed(reinterpret_cast<Qt::HANDLE>(quintptr(GetCurrentThreadId())));
|
||||
|
||||
QThread::setTerminationEnabled(false);
|
||||
|
||||
{
|
||||
QMutexLocker locker(&thr->d_func()->mutex);
|
||||
data->quitNow = thr->d_func()->exited;
|
||||
}
|
||||
|
||||
data->ensureEventDispatcher();
|
||||
data->eventDispatcher.loadRelaxed()->startingUp();
|
||||
|
||||
// sets the name of the current thread.
|
||||
QString threadName = std::exchange(thr->d_func()->objectName, {});
|
||||
if (Q_LIKELY(threadName.isEmpty()))
|
||||
threadName = QString::fromUtf8(thr->metaObject()->className());
|
||||
qt_set_thread_name(GetCurrentThread(), threadName);
|
||||
|
||||
emit thr->started(QThread::QPrivateSignal());
|
||||
QThread::setTerminationEnabled(true);
|
||||
thr->run();
|
||||
|
||||
finish(arg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
For regularly terminating threads, this will be called and executed by the thread as the
|
||||
last code before the thread exits. In that case, \a arg is the current QThread.
|
||||
|
||||
However, this function will also be called by QThread::terminate (as well as wait() and
|
||||
setTerminationEnabled) to give Qt a chance to update the terminated thread's state and
|
||||
process pending DeleteLater events for objects that live in the terminated thread. And for
|
||||
adopted thread, this method is called by the thread watcher.
|
||||
|
||||
In those cases, \a arg will not be the current thread.
|
||||
*/
|
||||
void QThreadPrivate::finish(void *arg, bool lockAnyway) noexcept
|
||||
{
|
||||
QThread *thr = reinterpret_cast<QThread *>(arg);
|
||||
QThreadPrivate *d = thr->d_func();
|
||||
|
||||
QMutexLocker locker(lockAnyway ? &d->mutex : nullptr);
|
||||
d->isInFinish = true;
|
||||
d->priority = QThread::InheritPriority;
|
||||
void **tls_data = reinterpret_cast<void **>(&d->data->tls);
|
||||
if (lockAnyway)
|
||||
locker.unlock();
|
||||
emit thr->finished(QThread::QPrivateSignal());
|
||||
qCDebug(lcDeleteLater) << "Sending deferred delete events as part of finishing thread" << thr;
|
||||
QCoreApplicationPrivate::sendPostedEvents(nullptr, QEvent::DeferredDelete, d->data);
|
||||
QThreadStorageData::finish(tls_data);
|
||||
if (lockAnyway)
|
||||
locker.relock();
|
||||
|
||||
QAbstractEventDispatcher *eventDispatcher = d->data->eventDispatcher.loadRelaxed();
|
||||
if (eventDispatcher) {
|
||||
d->data->eventDispatcher = 0;
|
||||
if (lockAnyway)
|
||||
locker.unlock();
|
||||
eventDispatcher->closingDown();
|
||||
delete eventDispatcher;
|
||||
if (lockAnyway)
|
||||
locker.relock();
|
||||
}
|
||||
|
||||
d->running = false;
|
||||
d->finished = true;
|
||||
d->isInFinish = false;
|
||||
d->interruptionRequested.store(false, std::memory_order_relaxed);
|
||||
|
||||
if (!d->waiters) {
|
||||
CloseHandle(d->handle);
|
||||
d->handle = 0;
|
||||
}
|
||||
|
||||
d->id = 0;
|
||||
}
|
||||
|
||||
/**************************************************************************
|
||||
** QThread
|
||||
*************************************************************************/
|
||||
|
||||
Qt::HANDLE QThread::currentThreadIdImpl() noexcept
|
||||
{
|
||||
return reinterpret_cast<Qt::HANDLE>(quintptr(GetCurrentThreadId()));
|
||||
}
|
||||
|
||||
int QThread::idealThreadCount() noexcept
|
||||
{
|
||||
SYSTEM_INFO sysinfo;
|
||||
GetSystemInfo(&sysinfo);
|
||||
return sysinfo.dwNumberOfProcessors;
|
||||
}
|
||||
|
||||
void QThread::yieldCurrentThread()
|
||||
{
|
||||
SwitchToThread();
|
||||
}
|
||||
|
||||
#endif // QT_CONFIG(thread)
|
||||
|
||||
void QThread::sleep(std::chrono::nanoseconds nsecs)
|
||||
{
|
||||
using namespace std::chrono;
|
||||
::Sleep(DWORD(duration_cast<milliseconds>(nsecs).count()));
|
||||
}
|
||||
|
||||
void QThread::sleep(unsigned long secs)
|
||||
{
|
||||
::Sleep(secs * 1000);
|
||||
}
|
||||
|
||||
void QThread::msleep(unsigned long msecs)
|
||||
{
|
||||
::Sleep(msecs);
|
||||
}
|
||||
|
||||
void QThread::usleep(unsigned long usecs)
|
||||
{
|
||||
::Sleep((usecs / 1000) + 1);
|
||||
}
|
||||
|
||||
#if QT_CONFIG(thread)
|
||||
|
||||
void QThread::start(Priority priority)
|
||||
{
|
||||
Q_D(QThread);
|
||||
QMutexLocker locker(&d->mutex);
|
||||
|
||||
if (d->isInFinish) {
|
||||
locker.unlock();
|
||||
wait();
|
||||
locker.relock();
|
||||
}
|
||||
|
||||
if (d->running)
|
||||
return;
|
||||
|
||||
// avoid interacting with the binding system
|
||||
d->objectName = d->extraData ? d->extraData->objectName.valueBypassingBindings()
|
||||
: QString();
|
||||
d->running = true;
|
||||
d->finished = false;
|
||||
d->exited = false;
|
||||
d->returnCode = 0;
|
||||
d->interruptionRequested.store(false, std::memory_order_relaxed);
|
||||
|
||||
/*
|
||||
NOTE: we create the thread in the suspended state, set the
|
||||
priority and then resume the thread.
|
||||
|
||||
since threads are created with normal priority by default, we
|
||||
could get into a case where a thread (with priority less than
|
||||
NormalPriority) tries to create a new thread (also with priority
|
||||
less than NormalPriority), but the newly created thread preempts
|
||||
its 'parent' and runs at normal priority.
|
||||
*/
|
||||
#if defined(Q_CC_MSVC) && !defined(_DLL)
|
||||
// MSVC -MT or -MTd build
|
||||
d->handle = (Qt::HANDLE) _beginthreadex(NULL, d->stackSize, QThreadPrivate::start,
|
||||
this, CREATE_SUSPENDED, &(d->id));
|
||||
#else
|
||||
// MSVC -MD or -MDd or MinGW build
|
||||
d->handle = CreateThread(nullptr, d->stackSize,
|
||||
reinterpret_cast<LPTHREAD_START_ROUTINE>(QThreadPrivate::start),
|
||||
this, CREATE_SUSPENDED, reinterpret_cast<LPDWORD>(&d->id));
|
||||
#endif
|
||||
|
||||
if (!d->handle) {
|
||||
qErrnoWarning("QThread::start: Failed to create thread");
|
||||
d->running = false;
|
||||
d->finished = true;
|
||||
return;
|
||||
}
|
||||
|
||||
int prio;
|
||||
d->priority = priority;
|
||||
switch (priority) {
|
||||
case IdlePriority:
|
||||
prio = THREAD_PRIORITY_IDLE;
|
||||
break;
|
||||
|
||||
case LowestPriority:
|
||||
prio = THREAD_PRIORITY_LOWEST;
|
||||
break;
|
||||
|
||||
case LowPriority:
|
||||
prio = THREAD_PRIORITY_BELOW_NORMAL;
|
||||
break;
|
||||
|
||||
case NormalPriority:
|
||||
prio = THREAD_PRIORITY_NORMAL;
|
||||
break;
|
||||
|
||||
case HighPriority:
|
||||
prio = THREAD_PRIORITY_ABOVE_NORMAL;
|
||||
break;
|
||||
|
||||
case HighestPriority:
|
||||
prio = THREAD_PRIORITY_HIGHEST;
|
||||
break;
|
||||
|
||||
case TimeCriticalPriority:
|
||||
prio = THREAD_PRIORITY_TIME_CRITICAL;
|
||||
break;
|
||||
|
||||
case InheritPriority:
|
||||
default:
|
||||
prio = GetThreadPriority(GetCurrentThread());
|
||||
break;
|
||||
}
|
||||
|
||||
if (!SetThreadPriority(d->handle, prio)) {
|
||||
qErrnoWarning("QThread::start: Failed to set thread priority");
|
||||
}
|
||||
|
||||
if (ResumeThread(d->handle) == (DWORD) -1) {
|
||||
qErrnoWarning("QThread::start: Failed to resume new thread");
|
||||
}
|
||||
}
|
||||
|
||||
void QThread::terminate()
|
||||
{
|
||||
Q_D(QThread);
|
||||
QMutexLocker locker(&d->mutex);
|
||||
if (!d->running)
|
||||
return;
|
||||
if (!d->terminationEnabled) {
|
||||
d->terminatePending = true;
|
||||
return;
|
||||
}
|
||||
|
||||
TerminateThread(d->handle, 0);
|
||||
QThreadPrivate::finish(this, false);
|
||||
}
|
||||
|
||||
bool QThread::wait(QDeadlineTimer deadline)
|
||||
{
|
||||
Q_D(QThread);
|
||||
QMutexLocker locker(&d->mutex);
|
||||
|
||||
if (d->id == GetCurrentThreadId()) {
|
||||
qWarning("QThread::wait: Thread tried to wait on itself");
|
||||
return false;
|
||||
}
|
||||
if (d->finished || !d->running)
|
||||
return true;
|
||||
|
||||
++d->waiters;
|
||||
locker.mutex()->unlock();
|
||||
|
||||
bool ret = false;
|
||||
switch (WaitForSingleObject(d->handle, deadline.remainingTime())) {
|
||||
case WAIT_OBJECT_0:
|
||||
ret = true;
|
||||
break;
|
||||
case WAIT_FAILED:
|
||||
qErrnoWarning("QThread::wait: Thread wait failure");
|
||||
break;
|
||||
case WAIT_ABANDONED:
|
||||
case WAIT_TIMEOUT:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
locker.mutex()->lock();
|
||||
--d->waiters;
|
||||
|
||||
if (ret && !d->finished) {
|
||||
// thread was terminated by someone else
|
||||
|
||||
QThreadPrivate::finish(this, false);
|
||||
}
|
||||
|
||||
if (d->finished && !d->waiters) {
|
||||
CloseHandle(d->handle);
|
||||
d->handle = 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void QThread::setTerminationEnabled(bool enabled)
|
||||
{
|
||||
QThread *thr = currentThread();
|
||||
Q_ASSERT_X(thr != 0, "QThread::setTerminationEnabled()",
|
||||
"Current thread was not started with QThread.");
|
||||
QThreadPrivate *d = thr->d_func();
|
||||
QMutexLocker locker(&d->mutex);
|
||||
d->terminationEnabled = enabled;
|
||||
if (enabled && d->terminatePending) {
|
||||
QThreadPrivate::finish(thr, false);
|
||||
locker.unlock(); // don't leave the mutex locked!
|
||||
_endthreadex(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Caller must hold the mutex
|
||||
void QThreadPrivate::setPriority(QThread::Priority threadPriority)
|
||||
{
|
||||
// copied from start() with a few modifications:
|
||||
|
||||
int prio;
|
||||
priority = threadPriority;
|
||||
switch (threadPriority) {
|
||||
case QThread::IdlePriority:
|
||||
prio = THREAD_PRIORITY_IDLE;
|
||||
break;
|
||||
|
||||
case QThread::LowestPriority:
|
||||
prio = THREAD_PRIORITY_LOWEST;
|
||||
break;
|
||||
|
||||
case QThread::LowPriority:
|
||||
prio = THREAD_PRIORITY_BELOW_NORMAL;
|
||||
break;
|
||||
|
||||
case QThread::NormalPriority:
|
||||
prio = THREAD_PRIORITY_NORMAL;
|
||||
break;
|
||||
|
||||
case QThread::HighPriority:
|
||||
prio = THREAD_PRIORITY_ABOVE_NORMAL;
|
||||
break;
|
||||
|
||||
case QThread::HighestPriority:
|
||||
prio = THREAD_PRIORITY_HIGHEST;
|
||||
break;
|
||||
|
||||
case QThread::TimeCriticalPriority:
|
||||
prio = THREAD_PRIORITY_TIME_CRITICAL;
|
||||
break;
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
if (!SetThreadPriority(handle, prio)) {
|
||||
qErrnoWarning("QThread::setPriority: Failed to set thread priority");
|
||||
}
|
||||
}
|
||||
|
||||
#endif // QT_CONFIG(thread)
|
||||
|
||||
QT_END_NAMESPACE
|
@ -10,6 +10,8 @@
|
||||
#include <QtCore/private/qsystemerror_p.h>
|
||||
#include "qrhid3dhelpers_p.h"
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
#include <VersionHelpers.h>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
@ -28,7 +30,8 @@ using namespace Qt::StringLiterals;
|
||||
|
||||
/*!
|
||||
\class QRhiD3D11InitParams
|
||||
\inmodule QtGui
|
||||
\inmodule QtGuiPrivate
|
||||
\inheaderfile rhi/qrhi.h
|
||||
\since 6.6
|
||||
\brief Direct3D 11 specific initialization parameters.
|
||||
|
||||
@ -81,7 +84,8 @@ using namespace Qt::StringLiterals;
|
||||
|
||||
/*!
|
||||
\class QRhiD3D11NativeHandles
|
||||
\inmodule QtGui
|
||||
\inmodule QtGuiPrivate
|
||||
\inheaderfile rhi/qrhi.h
|
||||
\since 6.6
|
||||
\brief Holds the D3D device and device context used by the QRhi.
|
||||
|
||||
@ -233,9 +237,19 @@ bool QRhiD3D11::create(QRhi::Flags flags)
|
||||
// there. (some features are not supported then, however)
|
||||
useLegacySwapchainModel = qEnvironmentVariableIntValue("QT_D3D_NO_FLIP");
|
||||
|
||||
qCDebug(QRHI_LOG_INFO, "FLIP_* swapchain supported = true, ALLOW_TEARING supported = %s, use legacy (non-FLIP) model = %s",
|
||||
if (!useLegacySwapchainModel) {
|
||||
if (qEnvironmentVariableIsSet("QT_D3D_MAX_FRAME_LATENCY"))
|
||||
maxFrameLatency = UINT(qMax(0, qEnvironmentVariableIntValue("QT_D3D_MAX_FRAME_LATENCY")));
|
||||
} else {
|
||||
maxFrameLatency = 0;
|
||||
}
|
||||
|
||||
qCDebug(QRHI_LOG_INFO, "FLIP_* swapchain supported = true, ALLOW_TEARING supported = %s, use legacy (non-FLIP) model = %s, max frame latency = %u",
|
||||
supportsAllowTearing ? "true" : "false",
|
||||
useLegacySwapchainModel ? "true" : "false");
|
||||
useLegacySwapchainModel ? "true" : "false",
|
||||
maxFrameLatency);
|
||||
if (maxFrameLatency == 0)
|
||||
qCDebug(QRHI_LOG_INFO, "Disabling FRAME_LATENCY_WAITABLE_OBJECT usage");
|
||||
|
||||
if (!importedDeviceAndContext) {
|
||||
IDXGIAdapter1 *adapter;
|
||||
@ -634,6 +648,10 @@ bool QRhiD3D11::isFeatureSupported(QRhi::Feature feature) const
|
||||
return true;
|
||||
case QRhi::MultiView:
|
||||
return false;
|
||||
case QRhi::TextureViewFormat:
|
||||
return false; // because we use fully typed formats for textures and relaxed casting is a D3D12 thing
|
||||
case QRhi::ResolveDepthStencil:
|
||||
return false;
|
||||
default:
|
||||
Q_UNREACHABLE();
|
||||
return false;
|
||||
@ -1347,6 +1365,10 @@ QRhi::FrameOpResult QRhiD3D11::beginFrame(QRhiSwapChain *swapChain, QRhi::BeginF
|
||||
contextState.currentSwapChain = swapChainD;
|
||||
const int currentFrameSlot = swapChainD->currentFrameSlot;
|
||||
|
||||
// if we have a waitable object, now is the time to wait on it
|
||||
if (swapChainD->frameLatencyWaitableObject)
|
||||
WaitForSingleObjectEx(swapChainD->frameLatencyWaitableObject, 1000, true);
|
||||
|
||||
swapChainD->cb.resetState();
|
||||
|
||||
swapChainD->rt.d.rtv[0] = swapChainD->sampleDesc.Count > 1 ?
|
||||
@ -2153,6 +2175,8 @@ void QRhiD3D11::endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resource
|
||||
cmd.args.resolveSubRes.srcSubRes = D3D11CalcSubresource(0, UINT(colorAtt.layer()), 1);
|
||||
cmd.args.resolveSubRes.format = dstTexD->dxgiFormat;
|
||||
}
|
||||
if (rtTex->m_desc.depthResolveTexture())
|
||||
qWarning("Resolving multisample depth-stencil buffers is not supported with D3D");
|
||||
}
|
||||
|
||||
cbD->recordingPass = QD3D11CommandBuffer::NoPass;
|
||||
@ -4250,6 +4274,22 @@ static inline DXGI_FORMAT toD3DAttributeFormat(QRhiVertexInputAttribute::Format
|
||||
return DXGI_FORMAT_R16G16_FLOAT;
|
||||
case QRhiVertexInputAttribute::Half:
|
||||
return DXGI_FORMAT_R16_FLOAT;
|
||||
case QRhiVertexInputAttribute::UShort4:
|
||||
// Note: D3D does not support UShort3. Pass through UShort3 as UShort4.
|
||||
case QRhiVertexInputAttribute::UShort3:
|
||||
return DXGI_FORMAT_R16G16B16A16_UINT;
|
||||
case QRhiVertexInputAttribute::UShort2:
|
||||
return DXGI_FORMAT_R16G16_UINT;
|
||||
case QRhiVertexInputAttribute::UShort:
|
||||
return DXGI_FORMAT_R16_UINT;
|
||||
case QRhiVertexInputAttribute::SShort4:
|
||||
// Note: D3D does not support SShort3. Pass through SShort3 as SShort4.
|
||||
case QRhiVertexInputAttribute::SShort3:
|
||||
return DXGI_FORMAT_R16G16B16A16_SINT;
|
||||
case QRhiVertexInputAttribute::SShort2:
|
||||
return DXGI_FORMAT_R16G16_SINT;
|
||||
case QRhiVertexInputAttribute::SShort:
|
||||
return DXGI_FORMAT_R16_SINT;
|
||||
default:
|
||||
Q_UNREACHABLE();
|
||||
return DXGI_FORMAT_R32G32B32A32_FLOAT;
|
||||
@ -4672,7 +4712,7 @@ bool QD3D11GraphicsPipeline::create()
|
||||
} else {
|
||||
QByteArray sem;
|
||||
sem.resize(16);
|
||||
qsnprintf(sem.data(), sem.size(), "TEXCOORD%d_", it->location() - matrixSlice);
|
||||
std::snprintf(sem.data(), sem.size(), "TEXCOORD%d_", it->location() - matrixSlice);
|
||||
matrixSliceSemantics.append(sem);
|
||||
desc.SemanticName = matrixSliceSemantics.last().constData();
|
||||
desc.SemanticIndex = UINT(matrixSlice);
|
||||
@ -4938,6 +4978,11 @@ void QD3D11SwapChain::destroy()
|
||||
dcompTarget = nullptr;
|
||||
}
|
||||
|
||||
if (frameLatencyWaitableObject) {
|
||||
CloseHandle(frameLatencyWaitableObject);
|
||||
frameLatencyWaitableObject = nullptr;
|
||||
}
|
||||
|
||||
QRHI_RES_RHI(QRhiD3D11);
|
||||
if (rhiD) {
|
||||
rhiD->unregisterResource(this);
|
||||
@ -5100,7 +5145,7 @@ bool QD3D11SwapChain::createOrResize()
|
||||
if (m_flags.testFlag(SurfaceHasPreMulAlpha) || m_flags.testFlag(SurfaceHasNonPreMulAlpha)) {
|
||||
if (!rhiD->useLegacySwapchainModel && rhiD->ensureDirectCompositionDevice()) {
|
||||
if (!dcompTarget) {
|
||||
hr = rhiD->dcompDevice->CreateTargetForHwnd(hwnd, true, &dcompTarget);
|
||||
hr = rhiD->dcompDevice->CreateTargetForHwnd(hwnd, false, &dcompTarget);
|
||||
if (FAILED(hr)) {
|
||||
qWarning("Failed to create Direct Compsition target for the window: %s",
|
||||
qPrintable(QSystemError::windowsComString(hr)));
|
||||
@ -5130,6 +5175,17 @@ bool QD3D11SwapChain::createOrResize()
|
||||
if (swapInterval == 0 && rhiD->supportsAllowTearing)
|
||||
swapChainFlags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING;
|
||||
|
||||
// maxFrameLatency 0 means no waitable object usage.
|
||||
// Ignore it also when NoVSync is on, and when using WARP.
|
||||
const bool useFrameLatencyWaitableObject = rhiD->maxFrameLatency != 0
|
||||
&& swapInterval != 0
|
||||
&& rhiD->driverInfoStruct.deviceType != QRhiDriverInfo::CpuDevice;
|
||||
|
||||
if (useFrameLatencyWaitableObject) {
|
||||
// the flag is not supported in real fullscreen on D3D11, but perhaps that's fine since we only do borderless
|
||||
swapChainFlags |= DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT;
|
||||
}
|
||||
|
||||
if (!swapChain) {
|
||||
sampleDesc = rhiD->effectiveSampleDesc(m_sampleCount);
|
||||
colorFormat = DEFAULT_FORMAT;
|
||||
@ -5215,16 +5271,31 @@ bool QD3D11SwapChain::createOrResize()
|
||||
|
||||
if (SUCCEEDED(hr)) {
|
||||
swapChain = sc1;
|
||||
if (m_format != SDR) {
|
||||
IDXGISwapChain3 *sc3 = nullptr;
|
||||
if (SUCCEEDED(sc1->QueryInterface(__uuidof(IDXGISwapChain3), reinterpret_cast<void **>(&sc3)))) {
|
||||
IDXGISwapChain3 *sc3 = nullptr;
|
||||
if (SUCCEEDED(sc1->QueryInterface(__uuidof(IDXGISwapChain3), reinterpret_cast<void **>(&sc3)))) {
|
||||
if (m_format != SDR) {
|
||||
hr = sc3->SetColorSpace1(hdrColorSpace);
|
||||
if (FAILED(hr))
|
||||
qWarning("Failed to set color space on swapchain: %s",
|
||||
qPrintable(QSystemError::windowsComString(hr)));
|
||||
sc3->Release();
|
||||
} else {
|
||||
qPrintable(QSystemError::windowsComString(hr)));
|
||||
}
|
||||
if (useFrameLatencyWaitableObject) {
|
||||
sc3->SetMaximumFrameLatency(rhiD->maxFrameLatency);
|
||||
frameLatencyWaitableObject = sc3->GetFrameLatencyWaitableObject();
|
||||
}
|
||||
sc3->Release();
|
||||
} else {
|
||||
if (m_format != SDR)
|
||||
qWarning("IDXGISwapChain3 not available, HDR swapchain will not work as expected");
|
||||
if (useFrameLatencyWaitableObject) {
|
||||
IDXGISwapChain2 *sc2 = nullptr;
|
||||
if (SUCCEEDED(sc1->QueryInterface(__uuidof(IDXGISwapChain2), reinterpret_cast<void **>(&sc2)))) {
|
||||
sc2->SetMaximumFrameLatency(rhiD->maxFrameLatency);
|
||||
frameLatencyWaitableObject = sc2->GetFrameLatencyWaitableObject();
|
||||
sc2->Release();
|
||||
} else { // this cannot really happen since we require DXGIFactory2
|
||||
qWarning("IDXGISwapChain2 not available, FrameLatencyWaitableObject cannot be used");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (dcompVisual) {
|
||||
|
@ -626,6 +626,7 @@ struct QD3D11SwapChain : public QRhiSwapChain
|
||||
IDCompositionVisual *dcompVisual = nullptr;
|
||||
QD3D11SwapChainTimestamps timestamps;
|
||||
int currentTimestampPairIndex = 0;
|
||||
HANDLE frameLatencyWaitableObject = nullptr;
|
||||
};
|
||||
|
||||
class QRhiD3D11 : public QRhiImplementation
|
||||
@ -761,6 +762,7 @@ public:
|
||||
|
||||
QRhi::Flags rhiFlags;
|
||||
bool debugLayer = false;
|
||||
UINT maxFrameLatency = 2; // 1-3, use 2 to keep CPU-GPU parallelism while reducing lag compared to tripple buffering
|
||||
bool importedDeviceAndContext = false;
|
||||
ID3D11Device *dev = nullptr;
|
||||
ID3D11DeviceContext1 *context = nullptr;
|
||||
|
@ -23,7 +23,8 @@ QT_BEGIN_NAMESPACE
|
||||
|
||||
/*!
|
||||
\class QRhiD3D12InitParams
|
||||
\inmodule QtGui
|
||||
\inmodule QtGuiPrivate
|
||||
\inheaderfile rhi/qrhi.h
|
||||
\brief Direct3D 12 specific initialization parameters.
|
||||
|
||||
\note This is a RHI API with limited compatibility guarantees, see \l QRhi
|
||||
@ -70,7 +71,8 @@ QT_BEGIN_NAMESPACE
|
||||
|
||||
/*!
|
||||
\class QRhiD3D12NativeHandles
|
||||
\inmodule QtGui
|
||||
\inmodule QtGuiPrivate
|
||||
\inheaderfile rhi/qrhi.h
|
||||
\brief Holds the D3D12 device used by the QRhi.
|
||||
|
||||
\note The class uses \c{void *} as the type since including the COM-based
|
||||
@ -129,7 +131,8 @@ QT_BEGIN_NAMESPACE
|
||||
|
||||
/*!
|
||||
\class QRhiD3D12CommandBufferNativeHandles
|
||||
\inmodule QtGui
|
||||
\inmodule QtGuiPrivate
|
||||
\inheaderfile rhi/qrhi.h
|
||||
\brief Holds the ID3D12GraphicsCommandList1 object that is backing a QRhiCommandBuffer.
|
||||
|
||||
\note The command list object is only guaranteed to be valid, and
|
||||
@ -239,6 +242,11 @@ bool QRhiD3D12::create(QRhi::Flags flags)
|
||||
}
|
||||
}
|
||||
|
||||
if (qEnvironmentVariableIsSet("QT_D3D_MAX_FRAME_LATENCY"))
|
||||
maxFrameLatency = UINT(qMax(0, qEnvironmentVariableIntValue("QT_D3D_MAX_FRAME_LATENCY")));
|
||||
if (maxFrameLatency != 0)
|
||||
qCDebug(QRHI_LOG_INFO, "Using frame latency waitable object with max frame latency %u", maxFrameLatency);
|
||||
|
||||
supportsAllowTearing = false;
|
||||
IDXGIFactory5 *factory5 = nullptr;
|
||||
if (SUCCEEDED(dxgiFactory->QueryInterface(__uuidof(IDXGIFactory5), reinterpret_cast<void **>(&factory5)))) {
|
||||
@ -501,9 +509,13 @@ bool QRhiD3D12::create(QRhi::Flags flags)
|
||||
memset(timestampReadbackArea.mem.p, 0, readbackBufSize);
|
||||
}
|
||||
|
||||
caps = {};
|
||||
D3D12_FEATURE_DATA_D3D12_OPTIONS3 options3 = {};
|
||||
if (SUCCEEDED(dev->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS3, &options3, sizeof(options3))))
|
||||
if (SUCCEEDED(dev->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS3, &options3, sizeof(options3)))) {
|
||||
caps.multiView = options3.ViewInstancingTier != D3D12_VIEW_INSTANCING_TIER_NOT_SUPPORTED;
|
||||
// https://microsoft.github.io/DirectX-Specs/d3d/RelaxedCasting.html
|
||||
caps.textureViewFormat = options3.CastingFullyTypedFormatSupported;
|
||||
}
|
||||
|
||||
deviceLost = false;
|
||||
offscreenActive = false;
|
||||
@ -748,6 +760,12 @@ bool QRhiD3D12::isFeatureSupported(QRhi::Feature feature) const
|
||||
return false; // we generate mipmaps ourselves with compute and this is not implemented
|
||||
case QRhi::MultiView:
|
||||
return caps.multiView;
|
||||
case QRhi::TextureViewFormat:
|
||||
return caps.textureViewFormat;
|
||||
case QRhi::ResolveDepthStencil:
|
||||
// there is no Multisample Resolve support for depth/stencil formats
|
||||
// https://learn.microsoft.com/en-us/windows/win32/direct3ddxgi/hardware-support-for-direct3d-12-1-formats
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -974,7 +992,7 @@ void QD3D12CommandBuffer::visitStorageImage(QD3D12Stage s,
|
||||
const bool isArray = texD->m_flags.testFlag(QRhiTexture::TextureArray);
|
||||
const bool is3D = texD->m_flags.testFlag(QRhiTexture::ThreeDimensional);
|
||||
D3D12_UNORDERED_ACCESS_VIEW_DESC uavDesc = {};
|
||||
uavDesc.Format = texD->dxgiFormat;
|
||||
uavDesc.Format = texD->rtFormat;
|
||||
if (isCube) {
|
||||
uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2DARRAY;
|
||||
uavDesc.Texture2DArray.MipSlice = UINT(d.level);
|
||||
@ -1533,6 +1551,9 @@ QRhi::FrameOpResult QRhiD3D12::beginFrame(QRhiSwapChain *swapChain, QRhi::BeginF
|
||||
for (QD3D12SwapChain *sc : std::as_const(swapchains))
|
||||
sc->waitCommandCompletionForFrameSlot(currentFrameSlot); // note: swapChainD->currentFrameSlot, not sc's
|
||||
|
||||
if (swapChainD->frameLatencyWaitableObject)
|
||||
WaitForSingleObjectEx(swapChainD->frameLatencyWaitableObject, 1000, true);
|
||||
|
||||
HRESULT hr = cmdAllocators[currentFrameSlot]->Reset();
|
||||
if (FAILED(hr)) {
|
||||
qWarning("Failed to reset command allocator: %s",
|
||||
@ -1996,7 +2017,8 @@ void QRhiD3D12::endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resource
|
||||
dstTexD->dxgiFormat);
|
||||
}
|
||||
}
|
||||
|
||||
if (rtTex->m_desc.depthResolveTexture())
|
||||
qWarning("Resolving multisample depth-stencil buffers is not supported with D3D");
|
||||
}
|
||||
|
||||
cbD->recordingPass = QD3D12CommandBuffer::NoPass;
|
||||
@ -4198,8 +4220,27 @@ bool QD3D12Texture::prepareCreate(QSize *adjustedSize)
|
||||
const QSize size = is1D ? QSize(qMax(1, m_pixelSize.width()), 1)
|
||||
: (m_pixelSize.isEmpty() ? QSize(1, 1) : m_pixelSize);
|
||||
|
||||
QRHI_RES_RHI(QRhiD3D12);
|
||||
dxgiFormat = toD3DTextureFormat(m_format, m_flags);
|
||||
if (isDepth) {
|
||||
srvFormat = toD3DDepthTextureSRVFormat(m_format);
|
||||
rtFormat = toD3DDepthTextureDSVFormat(m_format);
|
||||
} else {
|
||||
srvFormat = dxgiFormat;
|
||||
rtFormat = dxgiFormat;
|
||||
}
|
||||
if (m_writeViewFormat.format != UnknownFormat) {
|
||||
if (isDepth)
|
||||
rtFormat = toD3DDepthTextureDSVFormat(m_writeViewFormat.format);
|
||||
else
|
||||
rtFormat = toD3DTextureFormat(m_writeViewFormat.format, m_writeViewFormat.srgb ? sRGB : Flags());
|
||||
}
|
||||
if (m_readViewFormat.format != UnknownFormat) {
|
||||
if (isDepth)
|
||||
srvFormat = toD3DDepthTextureSRVFormat(m_readViewFormat.format);
|
||||
else
|
||||
srvFormat = toD3DTextureFormat(m_readViewFormat.format, m_readViewFormat.srgb ? sRGB : Flags());
|
||||
}
|
||||
QRHI_RES_RHI(QRhiD3D12);
|
||||
mipLevelCount = uint(hasMipMaps ? rhiD->q->mipLevelsForSize(size) : 1);
|
||||
sampleDesc = rhiD->effectiveSampleDesc(m_sampleCount, dxgiFormat);
|
||||
if (sampleDesc.Count > 1) {
|
||||
@ -4258,14 +4299,13 @@ bool QD3D12Texture::prepareCreate(QSize *adjustedSize)
|
||||
bool QD3D12Texture::finishCreate()
|
||||
{
|
||||
QRHI_RES_RHI(QRhiD3D12);
|
||||
const bool isDepth = isDepthTextureFormat(m_format);
|
||||
const bool isCube = m_flags.testFlag(CubeMap);
|
||||
const bool is3D = m_flags.testFlag(ThreeDimensional);
|
||||
const bool isArray = m_flags.testFlag(TextureArray);
|
||||
const bool is1D = m_flags.testFlag(OneDimensional);
|
||||
|
||||
D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
|
||||
srvDesc.Format = isDepth ? toD3DDepthTextureSRVFormat(m_format) : dxgiFormat;
|
||||
srvDesc.Format = srvFormat;
|
||||
srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
|
||||
|
||||
if (isCube) {
|
||||
@ -4624,7 +4664,7 @@ QRhiRenderPassDescriptor *QD3D12TextureRenderTarget::newCompatibleRenderPassDesc
|
||||
QD3D12Texture *texD = QRHI_RES(QD3D12Texture, it->texture());
|
||||
QD3D12RenderBuffer *rbD = QRHI_RES(QD3D12RenderBuffer, it->renderBuffer());
|
||||
if (texD)
|
||||
rpD->colorFormat[rpD->colorAttachmentCount] = texD->dxgiFormat;
|
||||
rpD->colorFormat[rpD->colorAttachmentCount] = texD->rtFormat;
|
||||
else if (rbD)
|
||||
rpD->colorFormat[rpD->colorAttachmentCount] = rbD->dxgiFormat;
|
||||
rpD->colorAttachmentCount += 1;
|
||||
@ -4675,7 +4715,7 @@ bool QD3D12TextureRenderTarget::create()
|
||||
const bool isMultiView = it->multiViewCount() >= 2;
|
||||
UINT layerCount = isMultiView ? UINT(it->multiViewCount()) : 1;
|
||||
D3D12_RENDER_TARGET_VIEW_DESC rtvDesc = {};
|
||||
rtvDesc.Format = toD3DTextureFormat(texD->format(), texD->flags());
|
||||
rtvDesc.Format = texD->rtFormat;
|
||||
if (texD->flags().testFlag(QRhiTexture::CubeMap)) {
|
||||
rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2DARRAY;
|
||||
rtvDesc.Texture2DArray.MipSlice = UINT(colorAtt.level());
|
||||
@ -4749,7 +4789,7 @@ bool QD3D12TextureRenderTarget::create()
|
||||
return false;
|
||||
}
|
||||
D3D12_DEPTH_STENCIL_VIEW_DESC dsvDesc = {};
|
||||
dsvDesc.Format = toD3DDepthTextureDSVFormat(depthTexD->format());
|
||||
dsvDesc.Format = depthTexD->rtFormat;
|
||||
dsvDesc.ViewDimension = depthTexD->sampleDesc.Count > 1 ? D3D12_DSV_DIMENSION_TEXTURE2DMS
|
||||
: D3D12_DSV_DIMENSION_TEXTURE2D;
|
||||
if (depthTexD->flags().testFlag(QRhiTexture::TextureArray)) {
|
||||
@ -5545,6 +5585,22 @@ static inline DXGI_FORMAT toD3DAttributeFormat(QRhiVertexInputAttribute::Format
|
||||
return DXGI_FORMAT_R16G16_FLOAT;
|
||||
case QRhiVertexInputAttribute::Half:
|
||||
return DXGI_FORMAT_R16_FLOAT;
|
||||
case QRhiVertexInputAttribute::UShort4:
|
||||
// Note: D3D does not support UShort3. Pass through UShort3 as UShort4.
|
||||
case QRhiVertexInputAttribute::UShort3:
|
||||
return DXGI_FORMAT_R16G16B16A16_UINT;
|
||||
case QRhiVertexInputAttribute::UShort2:
|
||||
return DXGI_FORMAT_R16G16_UINT;
|
||||
case QRhiVertexInputAttribute::UShort:
|
||||
return DXGI_FORMAT_R16_UINT;
|
||||
case QRhiVertexInputAttribute::SShort4:
|
||||
// Note: D3D does not support SShort3. Pass through SShort3 as SShort4.
|
||||
case QRhiVertexInputAttribute::SShort3:
|
||||
return DXGI_FORMAT_R16G16B16A16_SINT;
|
||||
case QRhiVertexInputAttribute::SShort2:
|
||||
return DXGI_FORMAT_R16G16_SINT;
|
||||
case QRhiVertexInputAttribute::SShort:
|
||||
return DXGI_FORMAT_R16_SINT;
|
||||
}
|
||||
Q_UNREACHABLE_RETURN(DXGI_FORMAT_R32G32B32A32_FLOAT);
|
||||
}
|
||||
@ -5678,7 +5734,7 @@ bool QD3D12GraphicsPipeline::create()
|
||||
} else {
|
||||
QByteArray sem;
|
||||
sem.resize(16);
|
||||
qsnprintf(sem.data(), sem.size(), "TEXCOORD%d_", it->location() - matrixSlice);
|
||||
std::snprintf(sem.data(), sem.size(), "TEXCOORD%d_", it->location() - matrixSlice);
|
||||
matrixSliceSemantics.append(sem);
|
||||
desc.SemanticName = matrixSliceSemantics.last().constData();
|
||||
desc.SemanticIndex = UINT(matrixSlice);
|
||||
@ -6105,6 +6161,11 @@ void QD3D12SwapChain::destroy()
|
||||
dcompTarget = nullptr;
|
||||
}
|
||||
|
||||
if (frameLatencyWaitableObject) {
|
||||
CloseHandle(frameLatencyWaitableObject);
|
||||
frameLatencyWaitableObject = nullptr;
|
||||
}
|
||||
|
||||
QRHI_RES_RHI(QRhiD3D12);
|
||||
if (rhiD) {
|
||||
rhiD->swapchains.remove(this);
|
||||
@ -6296,7 +6357,7 @@ bool QD3D12SwapChain::createOrResize()
|
||||
if (m_flags.testFlag(SurfaceHasPreMulAlpha) || m_flags.testFlag(SurfaceHasNonPreMulAlpha)) {
|
||||
if (rhiD->ensureDirectCompositionDevice()) {
|
||||
if (!dcompTarget) {
|
||||
hr = rhiD->dcompDevice->CreateTargetForHwnd(hwnd, true, &dcompTarget);
|
||||
hr = rhiD->dcompDevice->CreateTargetForHwnd(hwnd, false, &dcompTarget);
|
||||
if (FAILED(hr)) {
|
||||
qWarning("Failed to create Direct Composition target for the window: %s",
|
||||
qPrintable(QSystemError::windowsComString(hr)));
|
||||
@ -6321,6 +6382,14 @@ bool QD3D12SwapChain::createOrResize()
|
||||
if (swapInterval == 0 && rhiD->supportsAllowTearing)
|
||||
swapChainFlags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING;
|
||||
|
||||
// maxFrameLatency 0 means no waitable object usage.
|
||||
// Ignore it also when NoVSync is on, and when using WARP.
|
||||
const bool useFrameLatencyWaitableObject = rhiD->maxFrameLatency != 0
|
||||
&& swapInterval != 0
|
||||
&& rhiD->driverInfoStruct.deviceType != QRhiDriverInfo::CpuDevice;
|
||||
if (useFrameLatencyWaitableObject)
|
||||
swapChainFlags |= DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT;
|
||||
|
||||
if (!swapChain) {
|
||||
chooseFormats();
|
||||
|
||||
@ -6377,6 +6446,10 @@ bool QD3D12SwapChain::createOrResize()
|
||||
qPrintable(QSystemError::windowsComString(hr)));
|
||||
}
|
||||
}
|
||||
if (useFrameLatencyWaitableObject) {
|
||||
swapChain->SetMaximumFrameLatency(rhiD->maxFrameLatency);
|
||||
frameLatencyWaitableObject = swapChain->GetFrameLatencyWaitableObject();
|
||||
}
|
||||
if (dcompVisual) {
|
||||
hr = dcompVisual->SetContent(swapChain);
|
||||
if (SUCCEEDED(hr)) {
|
||||
|
@ -565,16 +565,27 @@ void QWindowsFontDatabaseBase::createDirectWriteFactory(IDWriteFactory **factory
|
||||
IUnknown *result = nullptr;
|
||||
|
||||
# if QT_CONFIG(directwrite3)
|
||||
DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory5), &result);
|
||||
|
||||
if (result == nullptr)
|
||||
DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory3), &result);
|
||||
# endif
|
||||
|
||||
if (result == nullptr)
|
||||
DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory2), &result);
|
||||
qCDebug(lcQpaFonts) << "Trying to create IDWriteFactory6";
|
||||
DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory6), &result);
|
||||
|
||||
if (result == nullptr) {
|
||||
qCDebug(lcQpaFonts) << "Trying to create IDWriteFactory5";
|
||||
DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory5), &result);
|
||||
}
|
||||
|
||||
if (result == nullptr) {
|
||||
qCDebug(lcQpaFonts) << "Trying to create IDWriteFactory3";
|
||||
DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory3), &result);
|
||||
}
|
||||
# endif
|
||||
|
||||
if (result == nullptr) {
|
||||
qCDebug(lcQpaFonts) << "Trying to create IDWriteFactory2";
|
||||
DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory2), &result);
|
||||
}
|
||||
|
||||
if (result == nullptr) {
|
||||
qCDebug(lcQpaFonts) << "Trying to create plain IDWriteFactory";
|
||||
if (FAILED(DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), &result))) {
|
||||
qErrnoWarning("DWriteCreateFactory failed");
|
||||
return;
|
||||
|
@ -7,7 +7,6 @@
|
||||
#include "qwindowswindow.h"
|
||||
#include "qwindowskeymapper.h"
|
||||
#include "qwindowsnativeinterface.h"
|
||||
#include "qwindowsmousehandler.h"
|
||||
#include "qwindowspointerhandler.h"
|
||||
#include "qtwindowsglobal.h"
|
||||
#include "qwindowsmenu.h"
|
||||
@ -215,7 +214,6 @@ struct QWindowsContextPrivate {
|
||||
HDC m_displayContext = nullptr;
|
||||
int m_defaultDPI = 96;
|
||||
QWindowsKeyMapper m_keyMapper;
|
||||
QWindowsMouseHandler m_mouseHandler;
|
||||
QWindowsPointerHandler m_pointerHandler;
|
||||
QWindowsMimeRegistry m_mimeConverter;
|
||||
QWindowsScreenManager m_screenManager;
|
||||
@ -228,11 +226,9 @@ struct QWindowsContextPrivate {
|
||||
bool m_asyncExpose = false;
|
||||
HPOWERNOTIFY m_powerNotification = nullptr;
|
||||
HWND m_powerDummyWindow = nullptr;
|
||||
static bool m_darkMode;
|
||||
static bool m_v2DpiAware;
|
||||
};
|
||||
|
||||
bool QWindowsContextPrivate::m_darkMode = false;
|
||||
bool QWindowsContextPrivate::m_v2DpiAware = false;
|
||||
|
||||
QWindowsContextPrivate::QWindowsContextPrivate()
|
||||
@ -241,7 +237,7 @@ QWindowsContextPrivate::QWindowsContextPrivate()
|
||||
QWindowsContext::user32dll.init();
|
||||
QWindowsContext::shcoredll.init();
|
||||
|
||||
if (m_pointerHandler.touchDevice() || m_mouseHandler.touchDevice())
|
||||
if (m_pointerHandler.touchDevice())
|
||||
m_systemInfo |= QWindowsContext::SI_SupportsTouch;
|
||||
m_displayContext = GetDC(nullptr);
|
||||
m_defaultDPI = GetDeviceCaps(m_displayContext, LOGPIXELSY);
|
||||
@ -249,7 +245,6 @@ QWindowsContextPrivate::QWindowsContextPrivate()
|
||||
m_systemInfo |= QWindowsContext::SI_RTL_Extensions;
|
||||
m_keyMapper.setUseRTLExtensions(true);
|
||||
}
|
||||
m_darkMode = QWindowsTheme::queryDarkMode();
|
||||
if (FAILED(m_oleInitializeResult)) {
|
||||
qWarning() << "QWindowsContext: OleInitialize() failed: "
|
||||
<< QSystemError::windowsComString(m_oleInitializeResult);
|
||||
@ -306,8 +301,7 @@ bool QWindowsContext::initTouch(unsigned integrationOptions)
|
||||
{
|
||||
if (d->m_systemInfo & QWindowsContext::SI_SupportsTouch)
|
||||
return true;
|
||||
const bool usePointerHandler = (d->m_systemInfo & QWindowsContext::SI_SupportsPointer) != 0;
|
||||
auto touchDevice = usePointerHandler ? d->m_pointerHandler.touchDevice() : d->m_mouseHandler.touchDevice();
|
||||
auto touchDevice = d->m_pointerHandler.touchDevice();
|
||||
if (touchDevice.isNull()) {
|
||||
const bool mouseEmulation =
|
||||
(integrationOptions & QWindowsIntegration::DontPassOsMouseEventsSynthesizedFromTouch) == 0;
|
||||
@ -316,7 +310,6 @@ bool QWindowsContext::initTouch(unsigned integrationOptions)
|
||||
if (touchDevice.isNull())
|
||||
return false;
|
||||
d->m_pointerHandler.setTouchDevice(touchDevice);
|
||||
d->m_mouseHandler.setTouchDevice(touchDevice);
|
||||
QWindowSystemInterface::registerInputDevice(touchDevice.data());
|
||||
|
||||
d->m_systemInfo |= QWindowsContext::SI_SupportsTouch;
|
||||
@ -356,21 +349,6 @@ bool QWindowsContext::disposeTablet()
|
||||
#endif
|
||||
}
|
||||
|
||||
bool QWindowsContext::initPointer(unsigned integrationOptions)
|
||||
{
|
||||
if (integrationOptions & QWindowsIntegration::DontUseWMPointer)
|
||||
return false;
|
||||
|
||||
if (QOperatingSystemVersion::current() < QOperatingSystemVersion::Windows8)
|
||||
return false;
|
||||
|
||||
if (!QWindowsContext::user32dll.supportsPointerApi())
|
||||
return false;
|
||||
|
||||
d->m_systemInfo |= QWindowsContext::SI_SupportsPointer;
|
||||
return true;
|
||||
}
|
||||
|
||||
extern "C" LRESULT QT_WIN_CALLBACK qWindowsPowerWindowProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
if (message != WM_POWERBROADCAST || wParam != PBT_POWERSETTINGCHANGE)
|
||||
@ -585,11 +563,6 @@ bool QWindowsContext::setProcessDpiAwareness(QtWindows::DpiAwareness dpiAwarenes
|
||||
return true;
|
||||
}
|
||||
|
||||
bool QWindowsContext::isDarkMode()
|
||||
{
|
||||
return QWindowsContextPrivate::m_darkMode;
|
||||
}
|
||||
|
||||
QWindowsContext *QWindowsContext::instance()
|
||||
{
|
||||
return m_instance;
|
||||
@ -857,16 +830,12 @@ QWindow *QWindowsContext::findWindow(HWND hwnd) const
|
||||
|
||||
QWindow *QWindowsContext::windowUnderMouse() const
|
||||
{
|
||||
return (d->m_systemInfo & QWindowsContext::SI_SupportsPointer) ?
|
||||
d->m_pointerHandler.windowUnderMouse() : d->m_mouseHandler.windowUnderMouse();
|
||||
return d->m_pointerHandler.windowUnderMouse();
|
||||
}
|
||||
|
||||
void QWindowsContext::clearWindowUnderMouse()
|
||||
{
|
||||
if (d->m_systemInfo & QWindowsContext::SI_SupportsPointer)
|
||||
d->m_pointerHandler.clearWindowUnderMouse();
|
||||
else
|
||||
d->m_mouseHandler.clearWindowUnderMouse();
|
||||
d->m_pointerHandler.clearWindowUnderMouse();
|
||||
}
|
||||
|
||||
/*!
|
||||
@ -1130,9 +1099,10 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message,
|
||||
|
||||
MSG msg;
|
||||
msg.hwnd = hwnd; // re-create MSG structure
|
||||
msg.message = message; // time and pt fields ignored
|
||||
msg.message = message;
|
||||
msg.wParam = wParam;
|
||||
msg.lParam = lParam;
|
||||
msg.time = GetMessageTime();
|
||||
msg.pt.x = msg.pt.y = 0;
|
||||
if (et != QtWindows::CursorEvent && (et & (QtWindows::MouseEventFlag | QtWindows::NonClientEventFlag))) {
|
||||
msg.pt.x = GET_X_LPARAM(lParam);
|
||||
@ -1181,8 +1151,7 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message,
|
||||
|
||||
switch (et) {
|
||||
case QtWindows::GestureEvent:
|
||||
if (!(d->m_systemInfo & QWindowsContext::SI_SupportsPointer))
|
||||
return sessionManagerInteractionBlocked() || d->m_mouseHandler.translateGestureEvent(platformWindow->window(), hwnd, et, msg, result);
|
||||
// TODO???
|
||||
break;
|
||||
case QtWindows::InputMethodOpenCandidateWindowEvent:
|
||||
case QtWindows::InputMethodCloseCandidateWindowEvent:
|
||||
@ -1216,21 +1185,7 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message,
|
||||
// Only refresh the window theme if the user changes the personalize settings.
|
||||
if ((wParam == 0) && (lParam != 0) // lParam sometimes may be NULL.
|
||||
&& (wcscmp(reinterpret_cast<LPCWSTR>(lParam), L"ImmersiveColorSet") == 0)) {
|
||||
const bool darkMode = QWindowsTheme::queryDarkMode();
|
||||
const bool darkModeChanged = darkMode != QWindowsContextPrivate::m_darkMode;
|
||||
QWindowsContextPrivate::m_darkMode = darkMode;
|
||||
auto integration = QWindowsIntegration::instance();
|
||||
integration->updateApplicationBadge();
|
||||
if (integration->darkModeHandling().testFlag(QWindowsApplication::DarkModeStyle)) {
|
||||
QWindowsTheme::instance()->refresh();
|
||||
QWindowSystemInterface::handleThemeChange();
|
||||
}
|
||||
if (darkModeChanged) {
|
||||
if (integration->darkModeHandling().testFlag(QWindowsApplication::DarkModeWindowFrames)) {
|
||||
for (QWindowsWindow *w : d->m_windows)
|
||||
w->setDarkBorder(QWindowsContextPrivate::m_darkMode);
|
||||
}
|
||||
}
|
||||
QWindowsTheme::handleSettingsChanged();
|
||||
}
|
||||
return d->m_screenManager.handleScreenChanges();
|
||||
}
|
||||
@ -1336,16 +1291,11 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message,
|
||||
case QtWindows::NonClientMouseEvent:
|
||||
if (!platformWindow->frameStrutEventsEnabled())
|
||||
break;
|
||||
if ((d->m_systemInfo & QWindowsContext::SI_SupportsPointer))
|
||||
return sessionManagerInteractionBlocked() || d->m_pointerHandler.translateMouseEvent(platformWindow->window(), hwnd, et, msg, result);
|
||||
else
|
||||
return sessionManagerInteractionBlocked() || d->m_mouseHandler.translateMouseEvent(platformWindow->window(), hwnd, et, msg, result);
|
||||
return sessionManagerInteractionBlocked() || d->m_pointerHandler.translateMouseEvent(platformWindow->window(), hwnd, et, msg, result);
|
||||
case QtWindows::NonClientPointerEvent:
|
||||
if (!platformWindow->frameStrutEventsEnabled())
|
||||
break;
|
||||
if ((d->m_systemInfo & QWindowsContext::SI_SupportsPointer))
|
||||
return sessionManagerInteractionBlocked() || d->m_pointerHandler.translatePointerEvent(platformWindow->window(), hwnd, et, msg, result);
|
||||
break;
|
||||
return sessionManagerInteractionBlocked() || d->m_pointerHandler.translatePointerEvent(platformWindow->window(), hwnd, et, msg, result);
|
||||
case QtWindows::EnterSizeMoveEvent:
|
||||
platformWindow->setFlag(QWindowsWindow::ResizeMoveActive);
|
||||
if (!IsZoomed(hwnd))
|
||||
@ -1359,8 +1309,7 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message,
|
||||
platformWindow->updateRestoreGeometry();
|
||||
return true;
|
||||
case QtWindows::ScrollEvent:
|
||||
if (!(d->m_systemInfo & QWindowsContext::SI_SupportsPointer))
|
||||
return sessionManagerInteractionBlocked() || d->m_mouseHandler.translateScrollEvent(platformWindow->window(), hwnd, msg, result);
|
||||
// TODO???
|
||||
break;
|
||||
case QtWindows::MouseWheelEvent:
|
||||
case QtWindows::MouseEvent:
|
||||
@ -1371,20 +1320,14 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message,
|
||||
window = window->parent();
|
||||
if (!window)
|
||||
return false;
|
||||
if (d->m_systemInfo & QWindowsContext::SI_SupportsPointer)
|
||||
return sessionManagerInteractionBlocked() || d->m_pointerHandler.translateMouseEvent(window, hwnd, et, msg, result);
|
||||
else
|
||||
return sessionManagerInteractionBlocked() || d->m_mouseHandler.translateMouseEvent(window, hwnd, et, msg, result);
|
||||
return sessionManagerInteractionBlocked() || d->m_pointerHandler.translateMouseEvent(window, hwnd, et, msg, result);
|
||||
}
|
||||
break;
|
||||
case QtWindows::TouchEvent:
|
||||
if (!(d->m_systemInfo & QWindowsContext::SI_SupportsPointer))
|
||||
return sessionManagerInteractionBlocked() || d->m_mouseHandler.translateTouchEvent(platformWindow->window(), hwnd, et, msg, result);
|
||||
// TODO???
|
||||
break;
|
||||
case QtWindows::PointerEvent:
|
||||
if (d->m_systemInfo & QWindowsContext::SI_SupportsPointer)
|
||||
return sessionManagerInteractionBlocked() || d->m_pointerHandler.translatePointerEvent(platformWindow->window(), hwnd, et, msg, result);
|
||||
break;
|
||||
return sessionManagerInteractionBlocked() || d->m_pointerHandler.translatePointerEvent(platformWindow->window(), hwnd, et, msg, result);
|
||||
case QtWindows::FocusInEvent: // see QWindowsWindow::requestActivateWindow().
|
||||
case QtWindows::FocusOutEvent:
|
||||
handleFocusEvent(et, platformWindow);
|
||||
@ -1588,7 +1531,7 @@ void QWindowsContext::handleExitSizeMove(QWindow *window)
|
||||
// Mouse is left in pressed state after press on size grip (inside window),
|
||||
// no further mouse events are received
|
||||
// For cases 1,3, intercept WM_EXITSIZEMOVE to sync the buttons.
|
||||
const Qt::MouseButtons currentButtons = QWindowsMouseHandler::queryMouseButtons();
|
||||
const Qt::MouseButtons currentButtons = QWindowsPointerHandler::queryMouseButtons();
|
||||
const Qt::MouseButtons appButtons = QGuiApplication::mouseButtons();
|
||||
if (currentButtons == appButtons)
|
||||
return;
|
||||
@ -1604,10 +1547,7 @@ void QWindowsContext::handleExitSizeMove(QWindow *window)
|
||||
currentButtons, button, type, keyboardModifiers);
|
||||
}
|
||||
}
|
||||
if (d->m_systemInfo & QWindowsContext::SI_SupportsPointer)
|
||||
d->m_pointerHandler.clearEvents();
|
||||
else
|
||||
d->m_mouseHandler.clearEvents();
|
||||
d->m_pointerHandler.clearEvents();
|
||||
}
|
||||
|
||||
bool QWindowsContext::asyncExpose() const
|
||||
|
@ -143,8 +143,7 @@ public:
|
||||
enum SystemInfoFlags
|
||||
{
|
||||
SI_RTL_Extensions = 0x1,
|
||||
SI_SupportsTouch = 0x2,
|
||||
SI_SupportsPointer = 0x4,
|
||||
SI_SupportsTouch = 0x2
|
||||
};
|
||||
|
||||
// Verbose flag set by environment variable QT_QPA_VERBOSE
|
||||
@ -157,7 +156,6 @@ public:
|
||||
bool initTouch(unsigned integrationOptions); // For calls from QWindowsIntegration::QWindowsIntegration() only.
|
||||
void registerTouchWindows();
|
||||
bool initTablet();
|
||||
bool initPointer(unsigned integrationOptions);
|
||||
bool disposeTablet();
|
||||
|
||||
bool initPowerNotificationHandler();
|
||||
@ -210,8 +208,6 @@ public:
|
||||
static QtWindows::DpiAwareness processDpiAwareness();
|
||||
static QtWindows::DpiAwareness windowDpiAwareness(HWND hwnd);
|
||||
|
||||
static bool isDarkMode();
|
||||
|
||||
void setDetectAltGrModifier(bool a);
|
||||
|
||||
// Returns a combination of SystemInfoFlags
|
||||
|
@ -11,7 +11,7 @@
|
||||
#include "qwindowsintegration.h"
|
||||
#include "qwindowsdropdataobject.h"
|
||||
#include "qwindowswindow.h"
|
||||
#include "qwindowsmousehandler.h"
|
||||
#include "qwindowspointerhandler.h"
|
||||
#include "qwindowscursor.h"
|
||||
#include "qwindowskeymapper.h"
|
||||
|
||||
@ -344,7 +344,7 @@ QWindowsOleDropSource::QueryContinueDrag(BOOL fEscapePressed, DWORD grfKeyState)
|
||||
// In some rare cases, when a mouse button is released but the mouse is static,
|
||||
// grfKeyState will not be updated with these released buttons until the mouse
|
||||
// is moved. So we use the async key state given by queryMouseButtons() instead.
|
||||
Qt::MouseButtons buttons = QWindowsMouseHandler::queryMouseButtons();
|
||||
Qt::MouseButtons buttons = QWindowsPointerHandler::queryMouseButtons();
|
||||
|
||||
SCODE result = S_OK;
|
||||
if (fEscapePressed || QWindowsDrag::isCanceled()) {
|
||||
@ -369,7 +369,7 @@ QWindowsOleDropSource::QueryContinueDrag(BOOL fEscapePressed, DWORD grfKeyState)
|
||||
const QPoint localPos = m_windowUnderMouse->handle()->mapFromGlobal(globalPos);
|
||||
QWindowSystemInterface::handleMouseEvent(m_windowUnderMouse.data(),
|
||||
QPointF(localPos), QPointF(globalPos),
|
||||
QWindowsMouseHandler::queryMouseButtons(),
|
||||
QWindowsPointerHandler::queryMouseButtons(),
|
||||
Qt::LeftButton, QEvent::MouseButtonRelease);
|
||||
}
|
||||
m_currentButtons = Qt::NoButton;
|
||||
@ -463,7 +463,7 @@ void QWindowsOleDropTarget::handleDrag(QWindow *window, DWORD grfKeyState,
|
||||
const Qt::DropActions actions = translateToQDragDropActions(*pdwEffect);
|
||||
|
||||
lastModifiers = toQtKeyboardModifiers(grfKeyState);
|
||||
lastButtons = QWindowsMouseHandler::queryMouseButtons();
|
||||
lastButtons = QWindowsPointerHandler::queryMouseButtons();
|
||||
|
||||
const QPlatformDragQtResponse response =
|
||||
QWindowSystemInterface::handleDrag(window, windowsDrag->dropData(),
|
||||
@ -533,7 +533,7 @@ QWindowsOleDropTarget::DragLeave()
|
||||
|
||||
const auto *keyMapper = QWindowsContext::instance()->keyMapper();
|
||||
lastModifiers = keyMapper->queryKeyboardModifiers();
|
||||
lastButtons = QWindowsMouseHandler::queryMouseButtons();
|
||||
lastButtons = QWindowsPointerHandler::queryMouseButtons();
|
||||
|
||||
QWindowSystemInterface::handleDrag(m_window, nullptr, QPoint(), Qt::IgnoreAction,
|
||||
Qt::NoButton, Qt::NoModifier);
|
||||
@ -562,7 +562,7 @@ QWindowsOleDropTarget::Drop(LPDATAOBJECT pDataObj, DWORD grfKeyState,
|
||||
QWindowsDrag *windowsDrag = QWindowsDrag::instance();
|
||||
|
||||
lastModifiers = toQtKeyboardModifiers(grfKeyState);
|
||||
lastButtons = QWindowsMouseHandler::queryMouseButtons();
|
||||
lastButtons = QWindowsPointerHandler::queryMouseButtons();
|
||||
|
||||
const QPlatformDropQtResponse response =
|
||||
QWindowSystemInterface::handleDrop(m_window, windowsDrag->dropData(),
|
||||
|
@ -806,7 +806,7 @@ static void showSystemMenu(QWindow* w)
|
||||
qWindowsWndProc(topLevelHwnd, WM_SYSCOMMAND, WPARAM(ret), 0);
|
||||
}
|
||||
|
||||
static inline void sendExtendedPressRelease(QWindow *w, int k,
|
||||
static inline void sendExtendedPressRelease(QWindow *w, unsigned long timestamp, int k,
|
||||
Qt::KeyboardModifiers mods,
|
||||
quint32 nativeScanCode,
|
||||
quint32 nativeVirtualKey,
|
||||
@ -815,8 +815,8 @@ static inline void sendExtendedPressRelease(QWindow *w, int k,
|
||||
bool autorep = false,
|
||||
ushort count = 1)
|
||||
{
|
||||
QWindowSystemInterface::handleExtendedKeyEvent(w, QEvent::KeyPress, k, mods, nativeScanCode, nativeVirtualKey, nativeModifiers, text, autorep, count);
|
||||
QWindowSystemInterface::handleExtendedKeyEvent(w, QEvent::KeyRelease, k, mods, nativeScanCode, nativeVirtualKey, nativeModifiers, text, autorep, count);
|
||||
QWindowSystemInterface::handleExtendedKeyEvent(w, timestamp, QEvent::KeyPress, k, mods, nativeScanCode, nativeVirtualKey, nativeModifiers, text, autorep, count);
|
||||
QWindowSystemInterface::handleExtendedKeyEvent(w, timestamp, QEvent::KeyRelease, k, mods, nativeScanCode, nativeVirtualKey, nativeModifiers, text, autorep, count);
|
||||
}
|
||||
|
||||
/*!
|
||||
@ -883,7 +883,7 @@ bool QWindowsKeyMapper::translateMultimediaKeyEventInternal(QWindow *window, con
|
||||
|
||||
const int qtKey = int(CmdTbl[cmd]);
|
||||
if (!skipPressRelease)
|
||||
sendExtendedPressRelease(receiver, qtKey, Qt::KeyboardModifier(state), 0, 0, 0);
|
||||
sendExtendedPressRelease(receiver, msg.time, qtKey, Qt::KeyboardModifier(state), 0, 0, 0);
|
||||
// QTBUG-43343: Make sure to return false if Qt does not handle the key, otherwise,
|
||||
// the keys are not passed to the active media player.
|
||||
# if QT_CONFIG(shortcut)
|
||||
@ -963,7 +963,7 @@ bool QWindowsKeyMapper::translateKeyEventInternal(QWindow *window, MSG msg,
|
||||
// A multi-character key or a Input method character
|
||||
// not found by our look-ahead
|
||||
if (msgType == WM_CHAR || msgType == WM_IME_CHAR) {
|
||||
sendExtendedPressRelease(receiver, 0, Qt::KeyboardModifier(state), scancode, 0, nModifiers, messageKeyText(msg), false);
|
||||
sendExtendedPressRelease(receiver, msg.time, 0, Qt::KeyboardModifier(state), scancode, 0, nModifiers, messageKeyText(msg), false);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -998,14 +998,14 @@ bool QWindowsKeyMapper::translateKeyEventInternal(QWindow *window, MSG msg,
|
||||
if (dirStatus == VK_LSHIFT
|
||||
&& ((msg.wParam == VK_SHIFT && GetKeyState(VK_LCONTROL))
|
||||
|| (msg.wParam == VK_CONTROL && GetKeyState(VK_LSHIFT)))) {
|
||||
sendExtendedPressRelease(receiver, Qt::Key_Direction_L, {},
|
||||
sendExtendedPressRelease(receiver, msg.time, Qt::Key_Direction_L, {},
|
||||
scancode, vk_key, nModifiers, QString(), false);
|
||||
result = true;
|
||||
dirStatus = 0;
|
||||
} else if (dirStatus == VK_RSHIFT
|
||||
&& ( (msg.wParam == VK_SHIFT && GetKeyState(VK_RCONTROL))
|
||||
|| (msg.wParam == VK_CONTROL && GetKeyState(VK_RSHIFT)))) {
|
||||
sendExtendedPressRelease(receiver, Qt::Key_Direction_R, {},
|
||||
sendExtendedPressRelease(receiver, msg.time, Qt::Key_Direction_R, {},
|
||||
scancode, vk_key, nModifiers, QString(), false);
|
||||
result = true;
|
||||
dirStatus = 0;
|
||||
@ -1218,9 +1218,9 @@ bool QWindowsKeyMapper::translateKeyEventInternal(QWindow *window, MSG msg,
|
||||
// so, we have an auto-repeating key
|
||||
if (rec) {
|
||||
if (code < Qt::Key_Shift || code > Qt::Key_ScrollLock) {
|
||||
QWindowSystemInterface::handleExtendedKeyEvent(receiver, QEvent::KeyRelease, code,
|
||||
QWindowSystemInterface::handleExtendedKeyEvent(receiver, msg.time, QEvent::KeyRelease, code,
|
||||
Qt::KeyboardModifier(state), scancode, quint32(msg.wParam), nModifiers, rec->text, true);
|
||||
QWindowSystemInterface::handleExtendedKeyEvent(receiver, QEvent::KeyPress, code,
|
||||
QWindowSystemInterface::handleExtendedKeyEvent(receiver, msg.time, QEvent::KeyPress, code,
|
||||
Qt::KeyboardModifier(state), scancode, quint32(msg.wParam), nModifiers, rec->text, true);
|
||||
result = true;
|
||||
}
|
||||
@ -1246,7 +1246,7 @@ bool QWindowsKeyMapper::translateKeyEventInternal(QWindow *window, MSG msg,
|
||||
if (msg.wParam == VK_PACKET)
|
||||
code = asciiToKeycode(char(uch.cell()), state);
|
||||
|
||||
QWindowSystemInterface::handleExtendedKeyEvent(receiver, QEvent::KeyPress, code,
|
||||
QWindowSystemInterface::handleExtendedKeyEvent(receiver, msg.time, QEvent::KeyPress, code,
|
||||
modifiers, scancode, quint32(msg.wParam), nModifiers, text, false);
|
||||
result =true;
|
||||
bool store = true;
|
||||
@ -1288,10 +1288,10 @@ bool QWindowsKeyMapper::translateKeyEventInternal(QWindow *window, MSG msg,
|
||||
if ((msg.lParam & 0x40000000) == 0 &&
|
||||
Qt::KeyboardModifier(state) == Qt::NoModifier &&
|
||||
((code == Qt::Key_F18) || (code == Qt::Key_F19) || (code == Qt::Key_F20))) {
|
||||
QWindowSystemInterface::handleExtendedKeyEvent(receiver, QEvent::KeyPress, code,
|
||||
QWindowSystemInterface::handleExtendedKeyEvent(receiver, msg.time, QEvent::KeyPress, code,
|
||||
Qt::MetaModifier, scancode,
|
||||
quint32(msg.wParam), MetaLeft);
|
||||
QWindowSystemInterface::handleExtendedKeyEvent(receiver, QEvent::KeyRelease, code,
|
||||
QWindowSystemInterface::handleExtendedKeyEvent(receiver, msg.time, QEvent::KeyRelease, code,
|
||||
Qt::NoModifier, scancode,
|
||||
quint32(msg.wParam), 0);
|
||||
result = true;
|
||||
@ -1303,7 +1303,7 @@ bool QWindowsKeyMapper::translateKeyEventInternal(QWindow *window, MSG msg,
|
||||
// Map SHIFT + Tab to SHIFT + BackTab, QShortcutMap knows about this translation
|
||||
if (code == Qt::Key_Tab && (state & Qt::ShiftModifier) == Qt::ShiftModifier)
|
||||
code = Qt::Key_Backtab;
|
||||
QWindowSystemInterface::handleExtendedKeyEvent(receiver, QEvent::KeyRelease, code,
|
||||
QWindowSystemInterface::handleExtendedKeyEvent(receiver, msg.time, QEvent::KeyRelease, code,
|
||||
Qt::KeyboardModifier(state), scancode, quint32(msg.wParam),
|
||||
nModifiers,
|
||||
(rec ? rec->text : QString()), false);
|
||||
|
@ -4,7 +4,6 @@
|
||||
#include <QtCore/qt_windows.h>
|
||||
|
||||
#include "qwindowspointerhandler.h"
|
||||
#include "qwindowsmousehandler.h"
|
||||
#if QT_CONFIG(tabletevent)
|
||||
# include "qwindowstabletsupport.h"
|
||||
#endif
|
||||
@ -41,6 +40,14 @@ enum {
|
||||
|
||||
qint64 QWindowsPointerHandler::m_nextInputDeviceId = 1;
|
||||
|
||||
const QPointingDevice *primaryMouse()
|
||||
{
|
||||
static QPointer<const QPointingDevice> result;
|
||||
if (!result)
|
||||
result = QPointingDevice::primaryPointingDevice();
|
||||
return result;
|
||||
}
|
||||
|
||||
QWindowsPointerHandler::~QWindowsPointerHandler()
|
||||
{
|
||||
}
|
||||
@ -240,7 +247,7 @@ static Qt::MouseButtons mouseButtonsFromKeyState(WPARAM keyState)
|
||||
return result;
|
||||
}
|
||||
|
||||
static Qt::MouseButtons queryMouseButtons()
|
||||
Qt::MouseButtons QWindowsPointerHandler::queryMouseButtons()
|
||||
{
|
||||
Qt::MouseButtons result = Qt::NoButton;
|
||||
const bool mouseSwapped = GetSystemMetrics(SM_SWAPBUTTON);
|
||||
@ -454,7 +461,7 @@ bool QWindowsPointerHandler::translateTouchEvent(QWindow *window, HWND hwnd,
|
||||
|
||||
if (msg.message == WM_POINTERCAPTURECHANGED) {
|
||||
const auto *keyMapper = QWindowsContext::instance()->keyMapper();
|
||||
QWindowSystemInterface::handleTouchCancelEvent(window, m_touchDevice.data(),
|
||||
QWindowSystemInterface::handleTouchCancelEvent(window, msg.time, m_touchDevice.data(),
|
||||
keyMapper->queryKeyboardModifiers());
|
||||
m_lastTouchPoints.clear();
|
||||
return true;
|
||||
@ -569,7 +576,7 @@ bool QWindowsPointerHandler::translateTouchEvent(QWindow *window, HWND hwnd,
|
||||
m_touchInputIDToTouchPointID.clear();
|
||||
|
||||
const auto *keyMapper = QWindowsContext::instance()->keyMapper();
|
||||
QWindowSystemInterface::handleTouchEvent(window, m_touchDevice.data(), touchPoints,
|
||||
QWindowSystemInterface::handleTouchEvent(window, msg.time, m_touchDevice.data(), touchPoints,
|
||||
keyMapper->queryKeyboardModifiers());
|
||||
return false; // Allow mouse messages to be generated.
|
||||
}
|
||||
@ -672,7 +679,7 @@ bool QWindowsPointerHandler::translatePenEvent(QWindow *window, HWND hwnd, QtWin
|
||||
|
||||
switch (msg.message) {
|
||||
case WM_POINTERENTER: {
|
||||
QWindowSystemInterface::handleTabletEnterLeaveProximityEvent(window, device.data(), true);
|
||||
QWindowSystemInterface::handleTabletEnterLeaveProximityEvent(window, msg.time, device.data(), true);
|
||||
m_windowUnderPointer = window;
|
||||
// The local coordinates may fall outside the window.
|
||||
// Wait until the next update to send the enter event.
|
||||
@ -685,7 +692,7 @@ bool QWindowsPointerHandler::translatePenEvent(QWindow *window, HWND hwnd, QtWin
|
||||
m_windowUnderPointer = nullptr;
|
||||
m_currentWindow = nullptr;
|
||||
}
|
||||
QWindowSystemInterface::handleTabletEnterLeaveProximityEvent(window, device.data(), false);
|
||||
QWindowSystemInterface::handleTabletEnterLeaveProximityEvent(window, msg.time, device.data(), false);
|
||||
break;
|
||||
case WM_POINTERDOWN:
|
||||
case WM_POINTERUP:
|
||||
@ -711,7 +718,7 @@ bool QWindowsPointerHandler::translatePenEvent(QWindow *window, HWND hwnd, QtWin
|
||||
const auto *keyMapper = QWindowsContext::instance()->keyMapper();
|
||||
const Qt::KeyboardModifiers keyModifiers = keyMapper->queryKeyboardModifiers();
|
||||
|
||||
QWindowSystemInterface::handleTabletEvent(target, device.data(),
|
||||
QWindowSystemInterface::handleTabletEvent(target, msg.time, device.data(),
|
||||
localPos, hiResGlobalPos, mouseButtons,
|
||||
pressure, xTilt, yTilt, tangentialPressure,
|
||||
rotation, z, keyModifiers);
|
||||
@ -762,7 +769,7 @@ bool QWindowsPointerHandler::translateMouseWheelEvent(QWindow *window,
|
||||
|
||||
QPoint localPos = QWindowsGeometryHint::mapFromGlobal(receiver, globalPos);
|
||||
|
||||
QWindowSystemInterface::handleWheelEvent(receiver, localPos, globalPos, QPoint(), angleDelta, keyModifiers);
|
||||
QWindowSystemInterface::handleWheelEvent(receiver, msg.time, localPos, globalPos, QPoint(), angleDelta, keyModifiers);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -818,7 +825,7 @@ bool QWindowsPointerHandler::translateMouseEvent(QWindow *window,
|
||||
}
|
||||
|
||||
Qt::MouseEventSource source = Qt::MouseEventNotSynthesized;
|
||||
const QPointingDevice *device = QWindowsMouseHandler::primaryMouse();
|
||||
const QPointingDevice *device = primaryMouse();
|
||||
|
||||
// Following the logic of the old mouse handler, only events synthesized
|
||||
// for touch screen are marked as such. On some systems, using the bit 7 of
|
||||
@ -861,14 +868,14 @@ bool QWindowsPointerHandler::translateMouseEvent(QWindow *window,
|
||||
&& (m_lastEventButton & mouseButtons) == 0) {
|
||||
auto releaseType = mouseEvent.type == QEvent::NonClientAreaMouseMove ?
|
||||
QEvent::NonClientAreaMouseButtonRelease : QEvent::MouseButtonRelease;
|
||||
QWindowSystemInterface::handleMouseEvent(window, device, localPos, globalPos, mouseButtons, m_lastEventButton,
|
||||
QWindowSystemInterface::handleMouseEvent(window, msg.time, device, localPos, globalPos, mouseButtons, m_lastEventButton,
|
||||
releaseType, keyModifiers, source);
|
||||
}
|
||||
m_lastEventType = mouseEvent.type;
|
||||
m_lastEventButton = mouseEvent.button;
|
||||
|
||||
if (mouseEvent.type >= QEvent::NonClientAreaMouseMove && mouseEvent.type <= QEvent::NonClientAreaMouseButtonDblClick) {
|
||||
QWindowSystemInterface::handleMouseEvent(window, device, localPos, globalPos, mouseButtons,
|
||||
QWindowSystemInterface::handleMouseEvent(window, msg.time, device, localPos, globalPos, mouseButtons,
|
||||
mouseEvent.button, mouseEvent.type, keyModifiers, source);
|
||||
return false; // Allow further event processing
|
||||
}
|
||||
@ -888,7 +895,7 @@ bool QWindowsPointerHandler::translateMouseEvent(QWindow *window,
|
||||
handleEnterLeave(window, currentWindowUnderPointer, globalPos);
|
||||
|
||||
if (!discardEvent && mouseEvent.type != QEvent::None) {
|
||||
QWindowSystemInterface::handleMouseEvent(window, device, localPos, globalPos, mouseButtons,
|
||||
QWindowSystemInterface::handleMouseEvent(window, msg.time, device, localPos, globalPos, mouseButtons,
|
||||
mouseEvent.button, mouseEvent.type, keyModifiers, source);
|
||||
}
|
||||
|
||||
|
@ -13,19 +13,20 @@
|
||||
# include "qwindowssystemtrayicon.h"
|
||||
#endif
|
||||
#include "qwindowsscreen.h"
|
||||
#include "qwindowswindow.h"
|
||||
#include <commctrl.h>
|
||||
#include <objbase.h>
|
||||
#ifndef Q_CC_MINGW
|
||||
# include <commoncontrols.h>
|
||||
#endif
|
||||
#include <commoncontrols.h>
|
||||
#include <shellapi.h>
|
||||
|
||||
#include <QtCore/qapplicationstatic.h>
|
||||
#include <QtCore/qvariant.h>
|
||||
#include <QtCore/qcoreapplication.h>
|
||||
#include <QtCore/qdebug.h>
|
||||
#include <QtCore/qsysinfo.h>
|
||||
#include <QtCore/qcache.h>
|
||||
#include <QtCore/qthread.h>
|
||||
#include <QtCore/qqueue.h>
|
||||
#include <QtCore/qmutex.h>
|
||||
#include <QtCore/qwaitcondition.h>
|
||||
#include <QtCore/qoperatingsystemversion.h>
|
||||
@ -52,10 +53,6 @@
|
||||
# include <winrt/Windows.UI.ViewManagement.h>
|
||||
#endif // QT_CONFIG(cpp_winrt)
|
||||
|
||||
#if defined(__IImageList_INTERFACE_DEFINED__) && defined(__IID_DEFINED__)
|
||||
# define USE_IIMAGELIST
|
||||
#endif
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
using namespace Qt::StringLiterals;
|
||||
@ -85,7 +82,11 @@ static inline QColor mixColors(const QColor &c1, const QColor &c2)
|
||||
|
||||
enum AccentColorLevel {
|
||||
AccentColorDarkest,
|
||||
AccentColorDarker,
|
||||
AccentColorDark,
|
||||
AccentColorNormal,
|
||||
AccentColorLight,
|
||||
AccentColorLighter,
|
||||
AccentColorLightest
|
||||
};
|
||||
|
||||
@ -100,6 +101,10 @@ static constexpr QColor getSysColor(winrt::Windows::UI::Color &&color)
|
||||
{
|
||||
QColor accent;
|
||||
QColor accentLight;
|
||||
QColor accentLighter;
|
||||
QColor accentLightest;
|
||||
QColor accentDark;
|
||||
QColor accentDarker;
|
||||
QColor accentDarkest;
|
||||
|
||||
#if QT_CONFIG(cpp_winrt)
|
||||
@ -109,6 +114,10 @@ static constexpr QColor getSysColor(winrt::Windows::UI::Color &&color)
|
||||
const auto settings = UISettings();
|
||||
accent = getSysColor(settings.GetColorValue(UIColorType::Accent));
|
||||
accentLight = getSysColor(settings.GetColorValue(UIColorType::AccentLight1));
|
||||
accentLighter = getSysColor(settings.GetColorValue(UIColorType::AccentLight2));
|
||||
accentLightest = getSysColor(settings.GetColorValue(UIColorType::AccentLight3));
|
||||
accentDark = getSysColor(settings.GetColorValue(UIColorType::AccentDark1));
|
||||
accentDarker = getSysColor(settings.GetColorValue(UIColorType::AccentDark2));
|
||||
accentDarkest = getSysColor(settings.GetColorValue(UIColorType::AccentDark3));
|
||||
}
|
||||
#endif
|
||||
@ -128,14 +137,30 @@ static constexpr QColor getSysColor(winrt::Windows::UI::Color &&color)
|
||||
return {};
|
||||
accent = QColor::fromRgb(abgr.blue(), abgr.green(), abgr.red(), abgr.alpha());
|
||||
accentLight = accent.lighter(120);
|
||||
accentLighter = accentLight.lighter(120);
|
||||
accentLightest = accentLighter.lighter(120);
|
||||
accentDarkest = accent.darker(120 * 120 * 120);
|
||||
accentDark = accent.darker(120);
|
||||
accentDarker = accentDark.darker(120);
|
||||
accentDarkest = accentDarker.darker(120);
|
||||
}
|
||||
|
||||
if (level == AccentColorDarkest)
|
||||
switch (level) {
|
||||
case AccentColorDarkest:
|
||||
return accentDarkest;
|
||||
else if (level == AccentColorLightest)
|
||||
case AccentColorDarker:
|
||||
return accentDarker;
|
||||
case AccentColorDark:
|
||||
return accentDark;
|
||||
case AccentColorLight:
|
||||
return accentLight;
|
||||
return accent;
|
||||
case AccentColorLighter:
|
||||
return accentLighter;
|
||||
case AccentColorLightest:
|
||||
return accentLightest;
|
||||
default:
|
||||
return accent;
|
||||
}
|
||||
}
|
||||
|
||||
static inline QColor getSysColor(int index)
|
||||
@ -148,107 +173,106 @@ static inline QColor getSysColor(int index)
|
||||
// models has been observed to trigger a WM_PAINT on the mainwindow. Suppress the
|
||||
// behavior by running it in a thread.
|
||||
|
||||
struct QShGetFileInfoParams
|
||||
{
|
||||
QShGetFileInfoParams(const QString &fn, DWORD a, SHFILEINFO *i, UINT f, bool *r)
|
||||
: fileName(fn), attributes(a), flags(f), info(i), result(r)
|
||||
{ }
|
||||
|
||||
const QString &fileName;
|
||||
const DWORD attributes;
|
||||
const UINT flags;
|
||||
SHFILEINFO *const info;
|
||||
bool *const result;
|
||||
};
|
||||
|
||||
class QShGetFileInfoThread : public QThread
|
||||
{
|
||||
public:
|
||||
explicit QShGetFileInfoThread()
|
||||
: QThread(), m_params(nullptr)
|
||||
struct Task
|
||||
{
|
||||
connect(this, &QThread::finished, this, &QObject::deleteLater);
|
||||
Task(const QString &fn, DWORD a, UINT f)
|
||||
: fileName(fn), attributes(a), flags(f)
|
||||
{}
|
||||
Q_DISABLE_COPY(Task)
|
||||
~Task()
|
||||
{
|
||||
DestroyIcon(hIcon);
|
||||
hIcon = 0;
|
||||
}
|
||||
// Request
|
||||
const QString fileName;
|
||||
const DWORD attributes;
|
||||
const UINT flags;
|
||||
// Result
|
||||
HICON hIcon = 0;
|
||||
int iIcon = -1;
|
||||
bool finished = false;
|
||||
bool resultValid() const { return hIcon != 0 && iIcon >= 0 && finished; }
|
||||
};
|
||||
|
||||
QShGetFileInfoThread()
|
||||
: QThread()
|
||||
{
|
||||
start();
|
||||
}
|
||||
|
||||
~QShGetFileInfoThread()
|
||||
{
|
||||
cancel();
|
||||
wait();
|
||||
}
|
||||
|
||||
QSharedPointer<Task> getNextTask()
|
||||
{
|
||||
QMutexLocker l(&m_waitForTaskMutex);
|
||||
while (!isInterruptionRequested()) {
|
||||
if (!m_taskQueue.isEmpty())
|
||||
return m_taskQueue.dequeue();
|
||||
m_waitForTaskCondition.wait(&m_waitForTaskMutex);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void run() override
|
||||
{
|
||||
QComHelper comHelper(COINIT_MULTITHREADED);
|
||||
|
||||
QMutexLocker readyLocker(&m_readyMutex);
|
||||
while (!m_cancelled.loadRelaxed()) {
|
||||
if (!m_params && !m_cancelled.loadRelaxed()
|
||||
&& !m_readyCondition.wait(&m_readyMutex, QDeadlineTimer(1000ll)))
|
||||
continue;
|
||||
|
||||
if (m_params) {
|
||||
const QString fileName = m_params->fileName;
|
||||
while (!isInterruptionRequested()) {
|
||||
auto task = getNextTask();
|
||||
if (task) {
|
||||
SHFILEINFO info;
|
||||
const bool result = SHGetFileInfo(reinterpret_cast<const wchar_t *>(fileName.utf16()),
|
||||
m_params->attributes, &info, sizeof(SHFILEINFO),
|
||||
m_params->flags);
|
||||
m_doneMutex.lock();
|
||||
if (!m_cancelled.loadRelaxed()) {
|
||||
*m_params->result = result;
|
||||
memcpy(m_params->info, &info, sizeof(SHFILEINFO));
|
||||
const bool result = SHGetFileInfo(reinterpret_cast<const wchar_t *>(task->fileName.utf16()),
|
||||
task->attributes, &info, sizeof(SHFILEINFO),
|
||||
task->flags);
|
||||
if (result) {
|
||||
task->hIcon = info.hIcon;
|
||||
task->iIcon = info.iIcon;
|
||||
}
|
||||
m_params = nullptr;
|
||||
|
||||
task->finished = true;
|
||||
m_doneCondition.wakeAll();
|
||||
m_doneMutex.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool runWithParams(QShGetFileInfoParams *params, qint64 timeOutMSecs)
|
||||
void runWithParams(const QSharedPointer<Task> &task,
|
||||
std::chrono::milliseconds timeout = std::chrono::milliseconds(5000))
|
||||
{
|
||||
{
|
||||
QMutexLocker l(&m_waitForTaskMutex);
|
||||
m_taskQueue.enqueue(task);
|
||||
m_waitForTaskCondition.wakeAll();
|
||||
}
|
||||
|
||||
QMutexLocker doneLocker(&m_doneMutex);
|
||||
|
||||
m_readyMutex.lock();
|
||||
m_params = params;
|
||||
m_readyCondition.wakeAll();
|
||||
m_readyMutex.unlock();
|
||||
|
||||
return m_doneCondition.wait(&m_doneMutex, QDeadlineTimer(timeOutMSecs));
|
||||
while (!task->finished && !isInterruptionRequested()) {
|
||||
if (!m_doneCondition.wait(&m_doneMutex, QDeadlineTimer(timeout)))
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void cancel()
|
||||
{
|
||||
QMutexLocker doneLocker(&m_doneMutex);
|
||||
m_cancelled.storeRelaxed(1);
|
||||
m_readyCondition.wakeAll();
|
||||
requestInterruption();
|
||||
m_doneCondition.wakeAll();
|
||||
m_waitForTaskCondition.wakeAll();
|
||||
}
|
||||
|
||||
private:
|
||||
QShGetFileInfoParams *m_params;
|
||||
QAtomicInt m_cancelled;
|
||||
QWaitCondition m_readyCondition;
|
||||
QQueue<QSharedPointer<Task>> m_taskQueue;
|
||||
QWaitCondition m_doneCondition;
|
||||
QMutex m_readyMutex;
|
||||
QWaitCondition m_waitForTaskCondition;
|
||||
QMutex m_doneMutex;
|
||||
QMutex m_waitForTaskMutex;
|
||||
};
|
||||
|
||||
static bool shGetFileInfoBackground(const QString &fileName, DWORD attributes,
|
||||
SHFILEINFO *info, UINT flags,
|
||||
qint64 timeOutMSecs = 5000)
|
||||
{
|
||||
static QShGetFileInfoThread *getFileInfoThread = nullptr;
|
||||
if (!getFileInfoThread) {
|
||||
getFileInfoThread = new QShGetFileInfoThread;
|
||||
getFileInfoThread->start();
|
||||
}
|
||||
|
||||
bool result = false;
|
||||
QShGetFileInfoParams params(fileName, attributes, info, flags, &result);
|
||||
if (!getFileInfoThread->runWithParams(¶ms, timeOutMSecs)) {
|
||||
// Cancel and reset getFileInfoThread. It'll
|
||||
// be reinitialized the next time we get called.
|
||||
getFileInfoThread->cancel();
|
||||
getFileInfoThread = nullptr;
|
||||
qWarning().noquote() << "SHGetFileInfo() timed out for " << fileName;
|
||||
return false;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
Q_APPLICATION_STATIC(QShGetFileInfoThread, s_shGetFileInfoThread)
|
||||
|
||||
// from QStyle::standardPalette
|
||||
static inline QPalette standardPalette()
|
||||
@ -277,15 +301,16 @@ static QColor placeHolderColor(QColor textColor)
|
||||
This is used when the theme is light mode, and when the theme is dark but the
|
||||
application doesn't support dark mode. In the latter case, we need to check.
|
||||
*/
|
||||
static void populateLightSystemBasePalette(QPalette &result)
|
||||
void QWindowsTheme::populateLightSystemBasePalette(QPalette &result)
|
||||
{
|
||||
const QColor background = getSysColor(COLOR_BTNFACE);
|
||||
const QColor textColor = getSysColor(COLOR_WINDOWTEXT);
|
||||
|
||||
const QColor accent = qt_accentColor(AccentColorNormal);
|
||||
const QColor accentDark = qt_accentColor(AccentColorDark);
|
||||
const QColor accentDarker = qt_accentColor(AccentColorDarker);
|
||||
const QColor accentDarkest = qt_accentColor(AccentColorDarkest);
|
||||
|
||||
const QColor linkColor = accent;
|
||||
const QColor linkColor = accentDarker;
|
||||
const QColor btnFace = background;
|
||||
const QColor btnHighlight = getSysColor(COLOR_BTNHIGHLIGHT);
|
||||
|
||||
@ -304,7 +329,7 @@ static void populateLightSystemBasePalette(QPalette &result)
|
||||
result.setColor(QPalette::Midlight, getSysColor(COLOR_3DLIGHT));
|
||||
result.setColor(QPalette::Shadow, getSysColor(COLOR_3DDKSHADOW));
|
||||
result.setColor(QPalette::HighlightedText, getSysColor(COLOR_HIGHLIGHTTEXT));
|
||||
result.setColor(QPalette::Accent, accent);
|
||||
result.setColor(QPalette::Accent, accentDark); // default accent color for controls on Light mode is AccentDark1
|
||||
|
||||
result.setColor(QPalette::Link, linkColor);
|
||||
result.setColor(QPalette::LinkVisited, accentDarkest);
|
||||
@ -317,7 +342,7 @@ static void populateLightSystemBasePalette(QPalette &result)
|
||||
result.setColor(QPalette::Midlight, result.button().color().lighter(110));
|
||||
}
|
||||
|
||||
static void populateDarkSystemBasePalette(QPalette &result)
|
||||
void QWindowsTheme::populateDarkSystemBasePalette(QPalette &result)
|
||||
{
|
||||
QColor foreground = Qt::white;
|
||||
QColor background = QColor(0x1E, 0x1E, 0x1E);
|
||||
@ -338,25 +363,27 @@ static void populateDarkSystemBasePalette(QPalette &result)
|
||||
// We have to craft a palette from these colors. The settings.UIElementColor(UIElementType) API
|
||||
// returns the old system colors, not the dark mode colors. If the background is black (which it
|
||||
// usually), then override it with a dark gray instead so that we can go up and down the lightness.
|
||||
foreground = getSysColor(settings.GetColorValue(UIColorType::Foreground));
|
||||
background = [&settings]() -> QColor {
|
||||
auto systemBackground = getSysColor(settings.GetColorValue(UIColorType::Background));
|
||||
if (systemBackground == Qt::black)
|
||||
systemBackground = QColor(0x1E, 0x1E, 0x1E);
|
||||
return systemBackground;
|
||||
}();
|
||||
|
||||
accent = getSysColor(settings.GetColorValue(UIColorType::Accent));
|
||||
accentDark = getSysColor(settings.GetColorValue(UIColorType::AccentDark1));
|
||||
accentDarker = getSysColor(settings.GetColorValue(UIColorType::AccentDark2));
|
||||
accentDarkest = getSysColor(settings.GetColorValue(UIColorType::AccentDark3));
|
||||
accentLight = getSysColor(settings.GetColorValue(UIColorType::AccentLight1));
|
||||
accentLighter = getSysColor(settings.GetColorValue(UIColorType::AccentLight2));
|
||||
accentLightest = getSysColor(settings.GetColorValue(UIColorType::AccentLight3));
|
||||
if (QWindowsTheme::queryColorScheme() == Qt::ColorScheme::Dark) {
|
||||
// the system is actually running in dark mode, so UISettings will give us dark colors
|
||||
foreground = getSysColor(settings.GetColorValue(UIColorType::Foreground));
|
||||
background = [&settings]() -> QColor {
|
||||
auto systemBackground = getSysColor(settings.GetColorValue(UIColorType::Background));
|
||||
if (systemBackground == Qt::black)
|
||||
systemBackground = QColor(0x1E, 0x1E, 0x1E);
|
||||
return systemBackground;
|
||||
}();
|
||||
accent = qt_accentColor(AccentColorNormal);
|
||||
accentDark = qt_accentColor(AccentColorDark);
|
||||
accentDarker = qt_accentColor(AccentColorDarker);
|
||||
accentDarkest = qt_accentColor(AccentColorDarkest);
|
||||
accentLight = qt_accentColor(AccentColorLight);
|
||||
accentLighter = qt_accentColor(AccentColorLighter);
|
||||
accentLightest = qt_accentColor(AccentColorLightest);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
const QColor linkColor = accent;
|
||||
const QColor linkColor = accentLightest;
|
||||
const QColor buttonColor = background.lighter(200);
|
||||
|
||||
result.setColor(QPalette::All, QPalette::WindowText, foreground);
|
||||
@ -377,12 +404,12 @@ static void populateDarkSystemBasePalette(QPalette &result)
|
||||
result.setColor(QPalette::All, QPalette::Highlight, accent);
|
||||
result.setColor(QPalette::All, QPalette::HighlightedText, accent.lightness() > 128 ? Qt::black : Qt::white);
|
||||
result.setColor(QPalette::All, QPalette::Link, linkColor);
|
||||
result.setColor(QPalette::All, QPalette::LinkVisited, accentDarkest);
|
||||
result.setColor(QPalette::All, QPalette::LinkVisited, accentLighter);
|
||||
result.setColor(QPalette::All, QPalette::AlternateBase, accentDarkest);
|
||||
result.setColor(QPalette::All, QPalette::ToolTipBase, buttonColor);
|
||||
result.setColor(QPalette::All, QPalette::ToolTipText, foreground.darker(120));
|
||||
result.setColor(QPalette::All, QPalette::PlaceholderText, placeHolderColor(foreground));
|
||||
result.setColor(QPalette::All, QPalette::Accent, accent);
|
||||
result.setColor(QPalette::All, QPalette::Accent, accentLighter);
|
||||
}
|
||||
|
||||
static inline QPalette toolTipPalette(const QPalette &systemPalette, bool light)
|
||||
@ -474,6 +501,7 @@ QWindowsTheme *QWindowsTheme::m_instance = nullptr;
|
||||
QWindowsTheme::QWindowsTheme()
|
||||
{
|
||||
m_instance = this;
|
||||
s_colorScheme = QWindowsTheme::queryColorScheme();
|
||||
std::fill(m_fonts, m_fonts + NFonts, nullptr);
|
||||
std::fill(m_palettes, m_palettes + NPalettes, nullptr);
|
||||
refresh();
|
||||
@ -562,10 +590,44 @@ QVariant QWindowsTheme::themeHint(ThemeHint hint) const
|
||||
}
|
||||
|
||||
Qt::ColorScheme QWindowsTheme::colorScheme() const
|
||||
{
|
||||
return QWindowsTheme::effectiveColorScheme();
|
||||
}
|
||||
|
||||
Qt::ColorScheme QWindowsTheme::effectiveColorScheme()
|
||||
{
|
||||
if (queryHighContrast())
|
||||
return Qt::ColorScheme::Unknown;
|
||||
return QWindowsContext::isDarkMode() ? Qt::ColorScheme::Dark : Qt::ColorScheme::Light;
|
||||
if (s_colorSchemeOverride != Qt::ColorScheme::Unknown)
|
||||
return s_colorSchemeOverride;
|
||||
if (s_colorScheme != Qt::ColorScheme::Unknown)
|
||||
return s_colorScheme;
|
||||
return queryColorScheme();
|
||||
}
|
||||
|
||||
void QWindowsTheme::requestColorScheme(Qt::ColorScheme scheme)
|
||||
{
|
||||
s_colorSchemeOverride = scheme;
|
||||
handleSettingsChanged();
|
||||
}
|
||||
|
||||
void QWindowsTheme::handleSettingsChanged()
|
||||
{
|
||||
const auto oldColorScheme = s_colorScheme;
|
||||
s_colorScheme = Qt::ColorScheme::Unknown; // make effectiveColorScheme() query registry
|
||||
const auto newColorScheme = effectiveColorScheme();
|
||||
const bool colorSchemeChanged = newColorScheme != oldColorScheme;
|
||||
s_colorScheme = newColorScheme;
|
||||
auto integration = QWindowsIntegration::instance();
|
||||
integration->updateApplicationBadge();
|
||||
if (integration->darkModeHandling().testFlag(QWindowsApplication::DarkModeStyle)) {
|
||||
QWindowsTheme::instance()->refresh();
|
||||
QWindowSystemInterface::handleThemeChange<QWindowSystemInterface::SynchronousDelivery>();
|
||||
}
|
||||
if (colorSchemeChanged) {
|
||||
for (QWindowsWindow *w : std::as_const(QWindowsContext::instance()->windows()))
|
||||
w->setDarkBorder(s_colorScheme == Qt::ColorScheme::Dark);
|
||||
}
|
||||
}
|
||||
|
||||
void QWindowsTheme::clearPalettes()
|
||||
@ -579,17 +641,17 @@ void QWindowsTheme::refreshPalettes()
|
||||
if (!QGuiApplication::desktopSettingsAware())
|
||||
return;
|
||||
const bool light =
|
||||
!QWindowsContext::isDarkMode()
|
||||
effectiveColorScheme() != Qt::ColorScheme::Dark
|
||||
|| !QWindowsIntegration::instance()->darkModeHandling().testFlag(QWindowsApplication::DarkModeStyle);
|
||||
clearPalettes();
|
||||
m_palettes[SystemPalette] = new QPalette(QWindowsTheme::systemPalette(light ? Qt::ColorScheme::Light : Qt::ColorScheme::Dark));
|
||||
m_palettes[SystemPalette] = new QPalette(QWindowsTheme::systemPalette(s_colorScheme));
|
||||
m_palettes[ToolTipPalette] = new QPalette(toolTipPalette(*m_palettes[SystemPalette], light));
|
||||
m_palettes[MenuPalette] = new QPalette(menuPalette(*m_palettes[SystemPalette], light));
|
||||
m_palettes[MenuBarPalette] = menuBarPalette(*m_palettes[MenuPalette], light);
|
||||
if (!light) {
|
||||
m_palettes[CheckBoxPalette] = new QPalette(*m_palettes[SystemPalette]);
|
||||
m_palettes[CheckBoxPalette]->setColor(QPalette::Active, QPalette::Base, qt_accentColor(AccentColorNormal));
|
||||
m_palettes[CheckBoxPalette]->setColor(QPalette::Active, QPalette::Button, qt_accentColor(AccentColorLightest));
|
||||
m_palettes[CheckBoxPalette]->setColor(QPalette::Active, QPalette::Button, qt_accentColor(AccentColorLighter));
|
||||
m_palettes[CheckBoxPalette]->setColor(QPalette::Inactive, QPalette::Base, qt_accentColor(AccentColorDarkest));
|
||||
m_palettes[RadioButtonPalette] = new QPalette(*m_palettes[CheckBoxPalette]);
|
||||
}
|
||||
@ -600,15 +662,15 @@ QPalette QWindowsTheme::systemPalette(Qt::ColorScheme colorScheme)
|
||||
QPalette result = standardPalette();
|
||||
|
||||
switch (colorScheme) {
|
||||
case Qt::ColorScheme::Unknown:
|
||||
// when a high-contrast theme is active or when we fail to read, assume light
|
||||
Q_FALLTHROUGH();
|
||||
case Qt::ColorScheme::Light:
|
||||
populateLightSystemBasePalette(result);
|
||||
break;
|
||||
case Qt::ColorScheme::Dark:
|
||||
populateDarkSystemBasePalette(result);
|
||||
break;
|
||||
default:
|
||||
qFatal("Unknown color scheme");
|
||||
break;
|
||||
}
|
||||
|
||||
if (result.window() != result.base()) {
|
||||
@ -750,11 +812,7 @@ void QWindowsTheme::refreshIconPixmapSizes()
|
||||
fileIconSizes[LargeFileIcon] + fileIconSizes[LargeFileIcon] / 2;
|
||||
fileIconSizes[JumboFileIcon] = 8 * fileIconSizes[LargeFileIcon]; // empirical, has not been observed to work
|
||||
|
||||
#ifdef USE_IIMAGELIST
|
||||
int *availEnd = fileIconSizes + JumboFileIcon + 1;
|
||||
#else
|
||||
int *availEnd = fileIconSizes + LargeFileIcon + 1;
|
||||
#endif // USE_IIMAGELIST
|
||||
m_fileIconSizes = QAbstractFileIconEngine::toSizeList(fileIconSizes, availEnd);
|
||||
qCDebug(lcQpaWindow) << __FUNCTION__ << m_fileIconSizes;
|
||||
}
|
||||
@ -948,10 +1006,10 @@ public:
|
||||
|
||||
// Shell image list helper functions.
|
||||
|
||||
static QPixmap pixmapFromShellImageList(int iImageList, const SHFILEINFO &info)
|
||||
static QPixmap pixmapFromShellImageList(int iImageList, int iIcon)
|
||||
{
|
||||
QPixmap result;
|
||||
#ifdef USE_IIMAGELIST
|
||||
|
||||
// For MinGW:
|
||||
static const IID iID_IImageList = {0x46eb5926, 0x582e, 0x4017, {0x9f, 0xdf, 0xe8, 0x99, 0x8d, 0xaa, 0x9, 0x50}};
|
||||
|
||||
@ -960,16 +1018,13 @@ static QPixmap pixmapFromShellImageList(int iImageList, const SHFILEINFO &info)
|
||||
if (hr != S_OK)
|
||||
return result;
|
||||
HICON hIcon;
|
||||
hr = imageList->GetIcon(info.iIcon, ILD_TRANSPARENT, &hIcon);
|
||||
hr = imageList->GetIcon(iIcon, ILD_TRANSPARENT, &hIcon);
|
||||
if (hr == S_OK) {
|
||||
result = qt_pixmapFromWinHICON(hIcon);
|
||||
DestroyIcon(hIcon);
|
||||
}
|
||||
imageList->Release();
|
||||
#else
|
||||
Q_UNUSED(iImageList);
|
||||
Q_UNUSED(info);
|
||||
#endif // USE_IIMAGELIST
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -1021,13 +1076,9 @@ QPixmap QWindowsFileIconEngine::filePixmap(const QSize &size, QIcon::Mode, QIcon
|
||||
const int width = int(size.width());
|
||||
const int iconSize = width > fileIconSizes[SmallFileIcon] ? SHGFI_LARGEICON : SHGFI_SMALLICON;
|
||||
const int requestedImageListSize =
|
||||
#ifdef USE_IIMAGELIST
|
||||
width > fileIconSizes[ExtraLargeFileIcon]
|
||||
? sHIL_JUMBO
|
||||
: (width > fileIconSizes[LargeFileIcon] ? sHIL_EXTRALARGE : 0);
|
||||
#else
|
||||
0;
|
||||
#endif // !USE_IIMAGELIST
|
||||
bool cacheableDirIcon = fileInfo().isDir() && !fileInfo().isRoot();
|
||||
if (cacheableDirIcon) {
|
||||
QMutexLocker locker(&mx);
|
||||
@ -1043,7 +1094,6 @@ QPixmap QWindowsFileIconEngine::filePixmap(const QSize &size, QIcon::Mode, QIcon
|
||||
}
|
||||
}
|
||||
|
||||
SHFILEINFO info;
|
||||
unsigned int flags = SHGFI_ICON | iconSize | SHGFI_SYSICONINDEX | SHGFI_ADDOVERLAYS | SHGFI_OVERLAYINDEX;
|
||||
DWORD attributes = 0;
|
||||
QString path = filePath;
|
||||
@ -1055,43 +1105,43 @@ QPixmap QWindowsFileIconEngine::filePixmap(const QSize &size, QIcon::Mode, QIcon
|
||||
flags |= SHGFI_USEFILEATTRIBUTES;
|
||||
attributes |= FILE_ATTRIBUTE_NORMAL;
|
||||
}
|
||||
const bool val = shGetFileInfoBackground(path, attributes, &info, flags);
|
||||
|
||||
auto task = QSharedPointer<QShGetFileInfoThread::Task>(
|
||||
new QShGetFileInfoThread::Task(path, attributes, flags));
|
||||
s_shGetFileInfoThread()->runWithParams(task);
|
||||
// Even if GetFileInfo returns a valid result, hIcon can be empty in some cases
|
||||
if (val && info.hIcon) {
|
||||
if (task->resultValid()) {
|
||||
QString key;
|
||||
if (cacheableDirIcon) {
|
||||
if (useDefaultFolderIcon && defaultFolderIIcon < 0)
|
||||
defaultFolderIIcon = info.iIcon;
|
||||
defaultFolderIIcon = task->iIcon;
|
||||
|
||||
//using the unique icon index provided by windows save us from duplicate keys
|
||||
key = dirIconPixmapCacheKey(info.iIcon, iconSize, requestedImageListSize);
|
||||
key = dirIconPixmapCacheKey(task->iIcon, iconSize, requestedImageListSize);
|
||||
QPixmapCache::find(key, &pixmap);
|
||||
if (!pixmap.isNull()) {
|
||||
QMutexLocker locker(&mx);
|
||||
dirIconEntryCache.insert(filePath, FakePointer<int>::create(info.iIcon));
|
||||
dirIconEntryCache.insert(filePath, FakePointer<int>::create(task->iIcon));
|
||||
}
|
||||
}
|
||||
|
||||
if (pixmap.isNull()) {
|
||||
if (requestedImageListSize) {
|
||||
pixmap = pixmapFromShellImageList(requestedImageListSize, info);
|
||||
pixmap = pixmapFromShellImageList(requestedImageListSize, task->iIcon);
|
||||
if (pixmap.isNull() && requestedImageListSize == sHIL_JUMBO)
|
||||
pixmap = pixmapFromShellImageList(sHIL_EXTRALARGE, info);
|
||||
pixmap = pixmapFromShellImageList(sHIL_EXTRALARGE, task->iIcon);
|
||||
}
|
||||
if (pixmap.isNull())
|
||||
pixmap = qt_pixmapFromWinHICON(info.hIcon);
|
||||
pixmap = qt_pixmapFromWinHICON(task->hIcon);
|
||||
if (!pixmap.isNull()) {
|
||||
if (cacheableDirIcon) {
|
||||
QMutexLocker locker(&mx);
|
||||
QPixmapCache::insert(key, pixmap);
|
||||
dirIconEntryCache.insert(filePath, FakePointer<int>::create(info.iIcon));
|
||||
dirIconEntryCache.insert(filePath, FakePointer<int>::create(task->iIcon));
|
||||
}
|
||||
} else {
|
||||
qWarning("QWindowsTheme::fileIconPixmap() no icon found");
|
||||
}
|
||||
}
|
||||
DestroyIcon(info.hIcon);
|
||||
}
|
||||
|
||||
return pixmap;
|
||||
@ -1131,14 +1181,14 @@ bool QWindowsTheme::useNativeMenus()
|
||||
return result;
|
||||
}
|
||||
|
||||
bool QWindowsTheme::queryDarkMode()
|
||||
Qt::ColorScheme QWindowsTheme::queryColorScheme()
|
||||
{
|
||||
if (queryHighContrast()) {
|
||||
return false;
|
||||
}
|
||||
if (queryHighContrast())
|
||||
return Qt::ColorScheme::Unknown;
|
||||
|
||||
const auto setting = QWinRegistryKey(HKEY_CURRENT_USER, LR"(Software\Microsoft\Windows\CurrentVersion\Themes\Personalize)")
|
||||
.dwordValue(L"AppsUseLightTheme");
|
||||
return setting.second && setting.first == 0;
|
||||
return setting.second && setting.first == 0 ? Qt::ColorScheme::Dark : Qt::ColorScheme::Light;
|
||||
}
|
||||
|
||||
bool QWindowsTheme::queryHighContrast()
|
||||
|
@ -5,6 +5,7 @@
|
||||
|
||||
#include "qwindowswindow.h"
|
||||
#include "qwindowscontext.h"
|
||||
#include "qwindowstheme.h"
|
||||
#if QT_CONFIG(draganddrop)
|
||||
# include "qwindowsdrag.h"
|
||||
#endif
|
||||
@ -867,7 +868,8 @@ static inline bool shouldApplyDarkFrame(const QWindow *w)
|
||||
{
|
||||
if (!w->isTopLevel() || w->flags().testFlag(Qt::FramelessWindowHint))
|
||||
return false;
|
||||
// the application has explicitly opted out of dark frames
|
||||
|
||||
// the user of the application has explicitly opted out of dark frames
|
||||
if (!QWindowsIntegration::instance()->darkModeHandling().testFlag(QWindowsApplication::DarkModeWindowFrames))
|
||||
return false;
|
||||
|
||||
@ -947,7 +949,7 @@ QWindowsWindowData
|
||||
return result;
|
||||
}
|
||||
|
||||
if (QWindowsContext::isDarkMode() && shouldApplyDarkFrame(w))
|
||||
if (QWindowsTheme::instance()->colorScheme() == Qt::ColorScheme::Dark && shouldApplyDarkFrame(w))
|
||||
QWindowsWindow::setDarkBorderToWindow(result.hwnd, true);
|
||||
|
||||
if (mirrorParentWidth != 0) {
|
||||
@ -1387,6 +1389,12 @@ QWindowsForeignWindow::QWindowsForeignWindow(QWindow *window, HWND hwnd)
|
||||
setParent(QPlatformWindow::parent());
|
||||
}
|
||||
|
||||
QWindowsForeignWindow::~QWindowsForeignWindow()
|
||||
{
|
||||
if (QPlatformWindow::parent())
|
||||
setParent(nullptr);
|
||||
}
|
||||
|
||||
void QWindowsForeignWindow::setParent(const QPlatformWindow *newParentWindow)
|
||||
{
|
||||
const bool wasTopLevel = isTopLevel_sys();
|
||||
@ -2739,7 +2747,7 @@ bool QWindowsWindow::windowEvent(QEvent *event)
|
||||
{
|
||||
switch (event->type()) {
|
||||
case QEvent::ApplicationPaletteChange:
|
||||
setDarkBorder(QWindowsContext::isDarkMode());
|
||||
setDarkBorder(QWindowsTheme::instance()->colorScheme() == Qt::ColorScheme::Dark);
|
||||
break;
|
||||
case QEvent::WindowBlocked: // Blocked by another modal window.
|
||||
setEnabled(false);
|
||||
@ -3335,17 +3343,6 @@ enum : WORD {
|
||||
DwmwaUseImmersiveDarkModeBefore20h1 = 19
|
||||
};
|
||||
|
||||
static bool queryDarkBorder(HWND hwnd)
|
||||
{
|
||||
BOOL result = FALSE;
|
||||
const bool ok =
|
||||
SUCCEEDED(DwmGetWindowAttribute(hwnd, DwmwaUseImmersiveDarkMode, &result, sizeof(result)))
|
||||
|| SUCCEEDED(DwmGetWindowAttribute(hwnd, DwmwaUseImmersiveDarkModeBefore20h1, &result, sizeof(result)));
|
||||
if (!ok)
|
||||
qCWarning(lcQpaWindow, "%s: Unable to retrieve dark window border setting.", __FUNCTION__);
|
||||
return result == TRUE;
|
||||
}
|
||||
|
||||
bool QWindowsWindow::setDarkBorderToWindow(HWND hwnd, bool d)
|
||||
{
|
||||
const BOOL darkBorder = d ? TRUE : FALSE;
|
||||
@ -3361,8 +3358,6 @@ void QWindowsWindow::setDarkBorder(bool d)
|
||||
{
|
||||
// respect explicit opt-out and incompatible palettes or styles
|
||||
d = d && shouldApplyDarkFrame(window());
|
||||
if (queryDarkBorder(m_data.hwnd) == d)
|
||||
return;
|
||||
|
||||
setDarkBorderToWindow(m_data.hwnd, d);
|
||||
}
|
||||
|
@ -0,0 +1,170 @@
|
||||
// Copyright (C) 2017 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 <QtGui/qtguiglobal.h>
|
||||
#if QT_CONFIG(accessibility)
|
||||
|
||||
#include "qwindowsuiaaccessibility.h"
|
||||
#include "qwindowsuiautomation.h"
|
||||
#include "qwindowsuiamainprovider.h"
|
||||
#include "qwindowsuiautils.h"
|
||||
|
||||
#include <QtGui/qaccessible.h>
|
||||
#include <QtGui/qwindow.h>
|
||||
#include <QtGui/qguiapplication.h>
|
||||
#include <QtGui/private/qguiapplication_p.h>
|
||||
#include <QtCore/qt_windows.h>
|
||||
#include <qpa/qplatformintegration.h>
|
||||
#include "qwindowsuiawrapper_p.h"
|
||||
#include "qwindowsuiawrapper.cpp"
|
||||
|
||||
#include <QtCore/private/qwinregistry_p.h>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
using namespace QWindowsUiAutomation;
|
||||
using namespace Qt::Literals::StringLiterals;
|
||||
|
||||
bool QWindowsUiaAccessibility::m_accessibleActive = false;
|
||||
|
||||
QWindowsUiaAccessibility::QWindowsUiaAccessibility()
|
||||
{
|
||||
}
|
||||
|
||||
QWindowsUiaAccessibility::~QWindowsUiaAccessibility()
|
||||
{
|
||||
}
|
||||
|
||||
// Handles UI Automation window messages.
|
||||
bool QWindowsUiaAccessibility::handleWmGetObject(HWND hwnd, WPARAM wParam, LPARAM lParam, LRESULT *lResult)
|
||||
{
|
||||
// Start handling accessibility internally
|
||||
QGuiApplicationPrivate::platformIntegration()->accessibility()->setActive(true);
|
||||
m_accessibleActive = true;
|
||||
|
||||
// Ignoring all requests while starting up / shutting down
|
||||
if (QCoreApplication::startingUp() || QCoreApplication::closingDown())
|
||||
return false;
|
||||
|
||||
if (QWindow *window = QWindowsContext::instance()->findWindow(hwnd)) {
|
||||
if (QAccessibleInterface *accessible = window->accessibleRoot()) {
|
||||
auto provider = QWindowsUiaMainProvider::providerForAccessible(accessible);
|
||||
*lResult = QWindowsUiaWrapper::instance()->returnRawElementProvider(hwnd, wParam, lParam, provider.Get());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Retrieve sound name by checking the icon property of a message box
|
||||
// should it be the event object.
|
||||
static QString alertSound(const QObject *object)
|
||||
{
|
||||
if (object->inherits("QMessageBox")) {
|
||||
enum MessageBoxIcon { // Keep in sync with QMessageBox::Icon
|
||||
Information = 1,
|
||||
Warning = 2,
|
||||
Critical = 3
|
||||
};
|
||||
switch (object->property("icon").toInt()) {
|
||||
case Information:
|
||||
return QStringLiteral("SystemAsterisk");
|
||||
case Warning:
|
||||
return QStringLiteral("SystemExclamation");
|
||||
case Critical:
|
||||
return QStringLiteral("SystemHand");
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
return QStringLiteral("SystemAsterisk");
|
||||
}
|
||||
|
||||
static QString soundFileName(const QString &soundName)
|
||||
{
|
||||
const QString key = "AppEvents\\Schemes\\Apps\\.Default\\"_L1
|
||||
+ soundName + "\\.Current"_L1;
|
||||
return QWinRegistryKey(HKEY_CURRENT_USER, key).stringValue(L"");
|
||||
}
|
||||
|
||||
static void playSystemSound(const QString &soundName)
|
||||
{
|
||||
if (!soundName.isEmpty() && !soundFileName(soundName).isEmpty()) {
|
||||
PlaySound(reinterpret_cast<const wchar_t *>(soundName.utf16()), nullptr,
|
||||
SND_ALIAS | SND_ASYNC | SND_NODEFAULT | SND_NOWAIT);
|
||||
}
|
||||
}
|
||||
|
||||
// Handles accessibility update notifications.
|
||||
void QWindowsUiaAccessibility::notifyAccessibilityUpdate(QAccessibleEvent *event)
|
||||
{
|
||||
if (!event)
|
||||
return;
|
||||
|
||||
// Always handle system sound events
|
||||
switch (event->type()) {
|
||||
case QAccessible::PopupMenuStart:
|
||||
playSystemSound(QStringLiteral("MenuPopup"));
|
||||
break;
|
||||
case QAccessible::MenuCommand:
|
||||
playSystemSound(QStringLiteral("MenuCommand"));
|
||||
break;
|
||||
case QAccessible::Alert:
|
||||
playSystemSound(alertSound(event->object()));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Ignore events sent before the first UI Automation
|
||||
// request or while QAccessible is being activated.
|
||||
if (!m_accessibleActive)
|
||||
return;
|
||||
|
||||
QAccessibleInterface *accessible = event->accessibleInterface();
|
||||
if (!isActive() || !accessible || !accessible->isValid())
|
||||
return;
|
||||
|
||||
// Ensures QWindowsUiaWrapper is properly initialized.
|
||||
if (!QWindowsUiaWrapper::instance()->ready())
|
||||
return;
|
||||
|
||||
// No need to do anything when nobody is listening.
|
||||
if (!QWindowsUiaWrapper::instance()->clientsAreListening())
|
||||
return;
|
||||
|
||||
switch (event->type()) {
|
||||
case QAccessible::Announcement:
|
||||
QWindowsUiaMainProvider::raiseNotification(static_cast<QAccessibleAnnouncementEvent *>(event));
|
||||
break;
|
||||
case QAccessible::Focus:
|
||||
QWindowsUiaMainProvider::notifyFocusChange(event);
|
||||
break;
|
||||
case QAccessible::StateChanged:
|
||||
QWindowsUiaMainProvider::notifyStateChange(static_cast<QAccessibleStateChangeEvent *>(event));
|
||||
break;
|
||||
case QAccessible::ValueChanged:
|
||||
QWindowsUiaMainProvider::notifyValueChange(static_cast<QAccessibleValueChangeEvent *>(event));
|
||||
break;
|
||||
case QAccessible::NameChanged:
|
||||
QWindowsUiaMainProvider::notifyNameChange(event);
|
||||
break;
|
||||
case QAccessible::SelectionAdd:
|
||||
QWindowsUiaMainProvider::notifySelectionChange(event);
|
||||
break;
|
||||
case QAccessible::TextAttributeChanged:
|
||||
case QAccessible::TextColumnChanged:
|
||||
case QAccessible::TextInserted:
|
||||
case QAccessible::TextRemoved:
|
||||
case QAccessible::TextUpdated:
|
||||
case QAccessible::TextSelectionChanged:
|
||||
case QAccessible::TextCaretMoved:
|
||||
QWindowsUiaMainProvider::notifyTextChange(event);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
#endif // QT_CONFIG(accessibility)
|
@ -0,0 +1,843 @@
|
||||
// Copyright (C) 2017 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 <QtGui/qtguiglobal.h>
|
||||
#if QT_CONFIG(accessibility)
|
||||
|
||||
#include "qwindowsuiamainprovider.h"
|
||||
#include "qwindowsuiavalueprovider.h"
|
||||
#include "qwindowsuiarangevalueprovider.h"
|
||||
#include "qwindowsuiatextprovider.h"
|
||||
#include "qwindowsuiatoggleprovider.h"
|
||||
#include "qwindowsuiainvokeprovider.h"
|
||||
#include "qwindowsuiaselectionprovider.h"
|
||||
#include "qwindowsuiaselectionitemprovider.h"
|
||||
#include "qwindowsuiatableprovider.h"
|
||||
#include "qwindowsuiatableitemprovider.h"
|
||||
#include "qwindowsuiagridprovider.h"
|
||||
#include "qwindowsuiagriditemprovider.h"
|
||||
#include "qwindowsuiawindowprovider.h"
|
||||
#include "qwindowsuiaexpandcollapseprovider.h"
|
||||
#include "qwindowscontext.h"
|
||||
#include "qwindowsuiautils.h"
|
||||
#include "qwindowsuiaprovidercache.h"
|
||||
#include "qwindowsuiawrapper_p.h"
|
||||
|
||||
#include <QtCore/qloggingcategory.h>
|
||||
#include <QtGui/private/qaccessiblebridgeutils_p.h>
|
||||
#include <QtGui/qaccessible.h>
|
||||
#include <QtGui/qguiapplication.h>
|
||||
#include <QtGui/qwindow.h>
|
||||
#include <qpa/qplatforminputcontextfactory_p.h>
|
||||
#include <QtCore/private/qcomvariant_p.h>
|
||||
|
||||
#if !defined(Q_CC_BOR) && !defined (Q_CC_GNU)
|
||||
#include <comdef.h>
|
||||
#endif
|
||||
|
||||
#include <QtCore/qt_windows.h>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
using namespace QWindowsUiAutomation;
|
||||
|
||||
// Returns a cached instance of the provider for a specific accessible interface.
|
||||
ComPtr<QWindowsUiaMainProvider> QWindowsUiaMainProvider::providerForAccessible(QAccessibleInterface *accessible)
|
||||
{
|
||||
if (!accessible)
|
||||
return nullptr;
|
||||
|
||||
QAccessible::Id id = QAccessible::uniqueId(accessible);
|
||||
QWindowsUiaProviderCache *providerCache = QWindowsUiaProviderCache::instance();
|
||||
ComPtr<QWindowsUiaMainProvider> provider = providerCache->providerForId(id);
|
||||
|
||||
if (!provider) {
|
||||
provider = makeComObject<QWindowsUiaMainProvider>(accessible);
|
||||
providerCache->insert(id, provider.Get()); // Cache holds weak references
|
||||
}
|
||||
return provider;
|
||||
}
|
||||
|
||||
QWindowsUiaMainProvider::QWindowsUiaMainProvider(QAccessibleInterface *a)
|
||||
: QWindowsUiaBaseProvider(QAccessible::uniqueId(a))
|
||||
{
|
||||
}
|
||||
|
||||
QWindowsUiaMainProvider::~QWindowsUiaMainProvider()
|
||||
{
|
||||
}
|
||||
|
||||
void QWindowsUiaMainProvider::notifyFocusChange(QAccessibleEvent *event)
|
||||
{
|
||||
if (QAccessibleInterface *accessible = event->accessibleInterface()) {
|
||||
// If this is a complex element, raise event for the focused child instead.
|
||||
if (accessible->childCount()) {
|
||||
if (QAccessibleInterface *child = accessible->focusChild())
|
||||
accessible = child;
|
||||
}
|
||||
if (auto provider = providerForAccessible(accessible))
|
||||
QWindowsUiaWrapper::instance()->raiseAutomationEvent(provider.Get(), UIA_AutomationFocusChangedEventId);
|
||||
}
|
||||
}
|
||||
|
||||
void QWindowsUiaMainProvider::notifyStateChange(QAccessibleStateChangeEvent *event)
|
||||
{
|
||||
if (QAccessibleInterface *accessible = event->accessibleInterface()) {
|
||||
if (event->changedStates().checked || event->changedStates().checkStateMixed) {
|
||||
// Notifies states changes in checkboxes.
|
||||
if (accessible->role() == QAccessible::CheckBox) {
|
||||
if (auto provider = providerForAccessible(accessible)) {
|
||||
long toggleState = ToggleState_Off;
|
||||
if (accessible->state().checked)
|
||||
toggleState = accessible->state().checkStateMixed ? ToggleState_Indeterminate : ToggleState_On;
|
||||
|
||||
QComVariant oldVal;
|
||||
QComVariant newVal{toggleState};
|
||||
QWindowsUiaWrapper::instance()->raiseAutomationPropertyChangedEvent(
|
||||
provider.Get(), UIA_ToggleToggleStatePropertyId, oldVal.get(), newVal.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (event->changedStates().active) {
|
||||
if (accessible->role() == QAccessible::Window) {
|
||||
// Notifies window opened/closed.
|
||||
if (auto provider = providerForAccessible(accessible)) {
|
||||
if (accessible->state().active) {
|
||||
QWindowsUiaWrapper::instance()->raiseAutomationEvent(provider.Get(), UIA_Window_WindowOpenedEventId);
|
||||
if (QAccessibleInterface *focused = accessible->focusChild()) {
|
||||
if (auto focusedProvider = providerForAccessible(focused)) {
|
||||
QWindowsUiaWrapper::instance()->raiseAutomationEvent(focusedProvider.Get(),
|
||||
UIA_AutomationFocusChangedEventId);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
QWindowsUiaWrapper::instance()->raiseAutomationEvent(provider.Get(), UIA_Window_WindowClosedEventId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void QWindowsUiaMainProvider::notifyValueChange(QAccessibleValueChangeEvent *event)
|
||||
{
|
||||
if (QAccessibleInterface *accessible = event->accessibleInterface()) {
|
||||
if (accessible->role() == QAccessible::ComboBox && accessible->childCount() > 0) {
|
||||
QAccessibleInterface *listacc = accessible->child(0);
|
||||
if (listacc && listacc->role() == QAccessible::List) {
|
||||
int count = listacc->childCount();
|
||||
for (int i = 0; i < count; ++i) {
|
||||
QAccessibleInterface *item = listacc->child(i);
|
||||
if (item && item->isValid() && item->text(QAccessible::Name) == event->value()) {
|
||||
if (!item->state().selected) {
|
||||
if (QAccessibleActionInterface *actionInterface = item->actionInterface())
|
||||
actionInterface->doAction(QAccessibleActionInterface::toggleAction());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (event->value().typeId() == QMetaType::QString) {
|
||||
if (auto provider = providerForAccessible(accessible)) {
|
||||
// Notifies changes in string values.
|
||||
const QComVariant oldVal;
|
||||
const QComVariant newVal{ event->value().toString() };
|
||||
QWindowsUiaWrapper::instance()->raiseAutomationPropertyChangedEvent(provider.Get(), UIA_ValueValuePropertyId,
|
||||
oldVal.get(), newVal.get());
|
||||
}
|
||||
} else if (QAccessibleValueInterface *valueInterface = accessible->valueInterface()) {
|
||||
if (auto provider = providerForAccessible(accessible)) {
|
||||
// Notifies changes in values of controls supporting the value interface.
|
||||
const QComVariant oldVal;
|
||||
const QComVariant newVal{ valueInterface->currentValue().toDouble() };
|
||||
QWindowsUiaWrapper::instance()->raiseAutomationPropertyChangedEvent(
|
||||
provider.Get(), UIA_RangeValueValuePropertyId, oldVal.get(), newVal.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void QWindowsUiaMainProvider::notifyNameChange(QAccessibleEvent *event)
|
||||
{
|
||||
if (QAccessibleInterface *accessible = event->accessibleInterface()) {
|
||||
// Restrict notification to combo boxes, which need it for accessibility,
|
||||
// in order to avoid slowdowns with unnecessary notifications.
|
||||
if (accessible->role() == QAccessible::ComboBox) {
|
||||
if (auto provider = providerForAccessible(accessible)) {
|
||||
QComVariant oldVal;
|
||||
QComVariant newVal{ accessible->text(QAccessible::Name) };
|
||||
QWindowsUiaWrapper::instance()->raiseAutomationPropertyChangedEvent(provider.Get(), UIA_NamePropertyId,
|
||||
oldVal.get(), newVal.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void QWindowsUiaMainProvider::notifySelectionChange(QAccessibleEvent *event)
|
||||
{
|
||||
if (QAccessibleInterface *accessible = event->accessibleInterface()) {
|
||||
if (auto provider = providerForAccessible(accessible))
|
||||
QWindowsUiaWrapper::instance()->raiseAutomationEvent(provider.Get(), UIA_SelectionItem_ElementSelectedEventId);
|
||||
}
|
||||
}
|
||||
|
||||
// Notifies changes in text content and selection state of text controls.
|
||||
void QWindowsUiaMainProvider::notifyTextChange(QAccessibleEvent *event)
|
||||
{
|
||||
if (QAccessibleInterface *accessible = event->accessibleInterface()) {
|
||||
if (accessible->textInterface()) {
|
||||
if (auto provider = providerForAccessible(accessible)) {
|
||||
if (event->type() == QAccessible::TextSelectionChanged) {
|
||||
QWindowsUiaWrapper::instance()->raiseAutomationEvent(provider.Get(), UIA_Text_TextSelectionChangedEventId);
|
||||
} else if (event->type() == QAccessible::TextCaretMoved) {
|
||||
if (!accessible->state().readOnly) {
|
||||
QWindowsUiaWrapper::instance()->raiseAutomationEvent(provider.Get(),
|
||||
UIA_Text_TextSelectionChangedEventId);
|
||||
}
|
||||
} else {
|
||||
QWindowsUiaWrapper::instance()->raiseAutomationEvent(provider.Get(), UIA_Text_TextChangedEventId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void QWindowsUiaMainProvider::raiseNotification(QAccessibleAnnouncementEvent *event)
|
||||
{
|
||||
if (QAccessibleInterface *accessible = event->accessibleInterface()) {
|
||||
if (auto provider = providerForAccessible(accessible)) {
|
||||
QBStr message{ event->message() };
|
||||
QAccessible::AnnouncementPoliteness prio = event->politeness();
|
||||
NotificationProcessing processing = (prio == QAccessible::AnnouncementPoliteness::Assertive)
|
||||
? NotificationProcessing_ImportantAll
|
||||
: NotificationProcessing_All;
|
||||
QBStr activityId{ QString::fromLatin1("") };
|
||||
QWindowsUiaWrapper::instance()->raiseNotificationEvent(provider.Get(), NotificationKind_Other, processing, message.bstr(),
|
||||
activityId.bstr());
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE QWindowsUiaMainProvider::QueryInterface(REFIID iid, LPVOID *iface)
|
||||
{
|
||||
HRESULT result = QComObject::QueryInterface(iid, iface);
|
||||
|
||||
if (SUCCEEDED(result) && iid == __uuidof(IRawElementProviderFragmentRoot)) {
|
||||
QAccessibleInterface *accessible = accessibleInterface();
|
||||
if (accessible && hwndForAccessible(accessible)) {
|
||||
result = S_OK;
|
||||
} else {
|
||||
Release();
|
||||
result = E_NOINTERFACE;
|
||||
*iface = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
HRESULT QWindowsUiaMainProvider::get_ProviderOptions(ProviderOptions *pRetVal)
|
||||
{
|
||||
if (!pRetVal)
|
||||
return E_INVALIDARG;
|
||||
// We are STA, (OleInitialize()).
|
||||
*pRetVal = static_cast<ProviderOptions>(ProviderOptions_ServerSideProvider | ProviderOptions_UseComThreading);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// Return providers for specific control patterns
|
||||
HRESULT QWindowsUiaMainProvider::GetPatternProvider(PATTERNID idPattern, IUnknown **pRetVal)
|
||||
{
|
||||
qCDebug(lcQpaUiAutomation) << __FUNCTION__ << idPattern;
|
||||
|
||||
if (!pRetVal)
|
||||
return E_INVALIDARG;
|
||||
*pRetVal = nullptr;
|
||||
|
||||
QAccessibleInterface *accessible = accessibleInterface();
|
||||
if (!accessible)
|
||||
return UIA_E_ELEMENTNOTAVAILABLE;
|
||||
|
||||
switch (idPattern) {
|
||||
case UIA_WindowPatternId:
|
||||
if (accessible->parent() && (accessible->parent()->role() == QAccessible::Application)) {
|
||||
*pRetVal = makeComObject<QWindowsUiaWindowProvider>(id()).Detach();
|
||||
}
|
||||
break;
|
||||
case UIA_TextPatternId:
|
||||
case UIA_TextPattern2Id:
|
||||
// All text controls.
|
||||
if (accessible->textInterface()) {
|
||||
*pRetVal = makeComObject<QWindowsUiaTextProvider>(id()).Detach();
|
||||
}
|
||||
break;
|
||||
case UIA_ValuePatternId:
|
||||
// All non-static controls support the Value pattern.
|
||||
if (accessible->role() != QAccessible::StaticText)
|
||||
*pRetVal = makeComObject<QWindowsUiaValueProvider>(id()).Detach();
|
||||
break;
|
||||
case UIA_RangeValuePatternId:
|
||||
// Controls providing a numeric value within a range (e.g., sliders, scroll bars, dials).
|
||||
if (accessible->valueInterface()) {
|
||||
*pRetVal = makeComObject<QWindowsUiaRangeValueProvider>(id()).Detach();
|
||||
}
|
||||
break;
|
||||
case UIA_TogglePatternId:
|
||||
// Checkboxes and other checkable controls.
|
||||
if (accessible->state().checkable)
|
||||
*pRetVal = makeComObject<QWindowsUiaToggleProvider>(id()).Detach();
|
||||
break;
|
||||
case UIA_SelectionPatternId:
|
||||
case UIA_SelectionPattern2Id:
|
||||
// Selections via QAccessibleSelectionInterface or lists of items.
|
||||
if (accessible->selectionInterface()
|
||||
|| accessible->role() == QAccessible::List
|
||||
|| accessible->role() == QAccessible::PageTabList) {
|
||||
*pRetVal = makeComObject<QWindowsUiaSelectionProvider>(id()).Detach();
|
||||
}
|
||||
break;
|
||||
case UIA_SelectionItemPatternId:
|
||||
// Parent supports selection interface or items within a list and radio buttons.
|
||||
if ((accessible->parent() && accessible->parent()->selectionInterface())
|
||||
|| (accessible->role() == QAccessible::RadioButton)
|
||||
|| (accessible->role() == QAccessible::ListItem)
|
||||
|| (accessible->role() == QAccessible::PageTab)) {
|
||||
*pRetVal = makeComObject<QWindowsUiaSelectionItemProvider>(id()).Detach();
|
||||
}
|
||||
break;
|
||||
case UIA_TablePatternId:
|
||||
// Table/tree.
|
||||
if (accessible->tableInterface()
|
||||
&& ((accessible->role() == QAccessible::Table) || (accessible->role() == QAccessible::Tree))) {
|
||||
*pRetVal = makeComObject<QWindowsUiaTableProvider>(id()).Detach();
|
||||
}
|
||||
break;
|
||||
case UIA_TableItemPatternId:
|
||||
// Item within a table/tree.
|
||||
if (accessible->tableCellInterface()
|
||||
&& ((accessible->role() == QAccessible::Cell) || (accessible->role() == QAccessible::TreeItem))) {
|
||||
*pRetVal = makeComObject<QWindowsUiaTableItemProvider>(id()).Detach();
|
||||
}
|
||||
break;
|
||||
case UIA_GridPatternId:
|
||||
// Table/tree.
|
||||
if (accessible->tableInterface()
|
||||
&& ((accessible->role() == QAccessible::Table) || (accessible->role() == QAccessible::Tree))) {
|
||||
*pRetVal = makeComObject<QWindowsUiaGridProvider>(id()).Detach();
|
||||
}
|
||||
break;
|
||||
case UIA_GridItemPatternId:
|
||||
// Item within a table/tree.
|
||||
if (accessible->tableCellInterface()
|
||||
&& ((accessible->role() == QAccessible::Cell) || (accessible->role() == QAccessible::TreeItem))) {
|
||||
*pRetVal = makeComObject<QWindowsUiaGridItemProvider>(id()).Detach();
|
||||
}
|
||||
break;
|
||||
case UIA_InvokePatternId:
|
||||
// Things that have an invokable action (e.g., simple buttons).
|
||||
if (accessible->actionInterface()) {
|
||||
*pRetVal = makeComObject<QWindowsUiaInvokeProvider>(id()).Detach();
|
||||
}
|
||||
break;
|
||||
case UIA_ExpandCollapsePatternId:
|
||||
// Menu items with submenus.
|
||||
if ((accessible->role() == QAccessible::MenuItem
|
||||
&& accessible->childCount() > 0
|
||||
&& accessible->child(0)->role() == QAccessible::PopupMenu)
|
||||
|| accessible->role() == QAccessible::ComboBox
|
||||
|| (accessible->role() == QAccessible::TreeItem && accessible->state().expandable)) {
|
||||
*pRetVal = makeComObject<QWindowsUiaExpandCollapseProvider>(id()).Detach();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
void QWindowsUiaMainProvider::fillVariantArrayForRelation(QAccessibleInterface* accessible,
|
||||
QAccessible::Relation relation, VARIANT *pRetVal)
|
||||
{
|
||||
Q_ASSERT(accessible);
|
||||
|
||||
typedef QPair<QAccessibleInterface*, QAccessible::Relation> RelationPair;
|
||||
const QList<RelationPair> relationInterfaces = accessible->relations(relation);
|
||||
if (relationInterfaces.empty())
|
||||
return;
|
||||
|
||||
SAFEARRAY *elements = SafeArrayCreateVector(VT_UNKNOWN, 0, relationInterfaces.size());
|
||||
for (LONG i = 0; i < relationInterfaces.size(); ++i) {
|
||||
if (ComPtr<IRawElementProviderSimple> provider =
|
||||
providerForAccessible(relationInterfaces.at(i).first)) {
|
||||
SafeArrayPutElement(elements, &i, provider.Get());
|
||||
}
|
||||
}
|
||||
|
||||
pRetVal->vt = VT_UNKNOWN | VT_ARRAY;
|
||||
pRetVal->parray = elements;
|
||||
}
|
||||
|
||||
void QWindowsUiaMainProvider::setAriaProperties(QAccessibleInterface *accessible, VARIANT *pRetVal)
|
||||
{
|
||||
Q_ASSERT(accessible);
|
||||
|
||||
QAccessibleAttributesInterface *attributesIface = accessible->attributesInterface();
|
||||
if (!attributesIface)
|
||||
return;
|
||||
|
||||
QString ariaString;
|
||||
const QList<QAccessible::Attribute> attrKeys = attributesIface->attributeKeys();
|
||||
for (qsizetype i = 0; i < attrKeys.size(); ++i) {
|
||||
if (i != 0)
|
||||
ariaString += QStringLiteral(";");
|
||||
const QAccessible::Attribute key = attrKeys.at(i);
|
||||
const QVariant value = attributesIface->attributeValue(key);
|
||||
// see "Core Accessibility API Mappings" spec: https://www.w3.org/TR/core-aam-1.2/
|
||||
switch (key) {
|
||||
case QAccessible::Attribute::Custom:
|
||||
{
|
||||
// forward custom attributes as-is
|
||||
Q_ASSERT((value.canConvert<QHash<QString, QString>>()));
|
||||
const QHash<QString, QString> attrMap = value.value<QHash<QString, QString>>();
|
||||
for (auto [name, val] : attrMap.asKeyValueRange()) {
|
||||
if (name != *attrMap.keyBegin())
|
||||
ariaString += QStringLiteral(";");
|
||||
ariaString += name + QStringLiteral("=") + val;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case QAccessible::Attribute::Level:
|
||||
Q_ASSERT(value.canConvert<int>());
|
||||
ariaString += QStringLiteral("level=") + QString::number(value.toInt());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
*pRetVal = QComVariant{ ariaString }.release();
|
||||
}
|
||||
|
||||
void QWindowsUiaMainProvider::setStyle(QAccessibleInterface *accessible, VARIANT *pRetVal)
|
||||
{
|
||||
Q_ASSERT(accessible);
|
||||
|
||||
QAccessibleAttributesInterface *attributesIface = accessible->attributesInterface();
|
||||
if (!attributesIface)
|
||||
return;
|
||||
|
||||
// currently, only heading styles are implemented here
|
||||
if (accessible->role() != QAccessible::Role::Heading)
|
||||
return;
|
||||
|
||||
const QVariant levelVariant = attributesIface->attributeValue(QAccessible::Attribute::Level);
|
||||
if (!levelVariant.isValid())
|
||||
return;
|
||||
|
||||
Q_ASSERT(levelVariant.canConvert<int>());
|
||||
// UIA only has styles for heading levels 1-9
|
||||
const int level = levelVariant.toInt();
|
||||
if (level < 1 || level > 9)
|
||||
return;
|
||||
|
||||
const long styleId = styleIdForHeadingLevel(level);
|
||||
*pRetVal = QComVariant{ styleId }.release();
|
||||
}
|
||||
|
||||
int QWindowsUiaMainProvider::styleIdForHeadingLevel(int headingLevel)
|
||||
{
|
||||
// only heading levels 1-9 have a corresponding UIA style ID
|
||||
Q_ASSERT(headingLevel > 0 && headingLevel <= 9);
|
||||
|
||||
static constexpr int styles[] = {
|
||||
StyleId_Heading1,
|
||||
StyleId_Heading2,
|
||||
StyleId_Heading3,
|
||||
StyleId_Heading4,
|
||||
StyleId_Heading5,
|
||||
StyleId_Heading6,
|
||||
StyleId_Heading7,
|
||||
StyleId_Heading8,
|
||||
StyleId_Heading9,
|
||||
};
|
||||
|
||||
return styles[headingLevel - 1];
|
||||
}
|
||||
|
||||
HRESULT QWindowsUiaMainProvider::GetPropertyValue(PROPERTYID idProp, VARIANT *pRetVal)
|
||||
{
|
||||
qCDebug(lcQpaUiAutomation) << __FUNCTION__ << idProp;
|
||||
|
||||
if (!pRetVal)
|
||||
return E_INVALIDARG;
|
||||
clearVariant(pRetVal);
|
||||
|
||||
QAccessibleInterface *accessible = accessibleInterface();
|
||||
if (!accessible)
|
||||
return UIA_E_ELEMENTNOTAVAILABLE;
|
||||
|
||||
bool topLevelWindow = accessible->parent() && (accessible->parent()->role() == QAccessible::Application);
|
||||
|
||||
switch (idProp) {
|
||||
case UIA_ProcessIdPropertyId:
|
||||
// PID
|
||||
*pRetVal = QComVariant{ static_cast<long>(GetCurrentProcessId()) }.release();
|
||||
break;
|
||||
case UIA_AccessKeyPropertyId:
|
||||
// Accelerator key.
|
||||
*pRetVal = QComVariant{ accessible->text(QAccessible::Accelerator) }.release();
|
||||
break;
|
||||
case UIA_AriaPropertiesPropertyId:
|
||||
setAriaProperties(accessible, pRetVal);
|
||||
break;
|
||||
case UIA_AutomationIdPropertyId:
|
||||
// Automation ID, which can be used by tools to select a specific control in the UI.
|
||||
*pRetVal = QComVariant{ QAccessibleBridgeUtils::accessibleId(accessible) }.release();
|
||||
break;
|
||||
case UIA_ClassNamePropertyId:
|
||||
// Class name.
|
||||
if (QObject *o = accessible->object()) {
|
||||
QString className = QLatin1StringView(o->metaObject()->className());
|
||||
*pRetVal = QComVariant{ className }.release();
|
||||
}
|
||||
break;
|
||||
case UIA_DescribedByPropertyId:
|
||||
fillVariantArrayForRelation(accessible, QAccessible::DescriptionFor, pRetVal);
|
||||
break;
|
||||
case UIA_FlowsFromPropertyId:
|
||||
fillVariantArrayForRelation(accessible, QAccessible::FlowsTo, pRetVal);
|
||||
break;
|
||||
case UIA_FlowsToPropertyId:
|
||||
fillVariantArrayForRelation(accessible, QAccessible::FlowsFrom, pRetVal);
|
||||
break;
|
||||
case UIA_FrameworkIdPropertyId:
|
||||
*pRetVal = QComVariant{ QStringLiteral("Qt") }.release();
|
||||
break;
|
||||
case UIA_ControlTypePropertyId:
|
||||
if (topLevelWindow) {
|
||||
// Reports a top-level widget as a window, instead of "custom".
|
||||
*pRetVal = QComVariant{ UIA_WindowControlTypeId }.release();
|
||||
} else {
|
||||
// Control type converted from role.
|
||||
auto controlType = roleToControlTypeId(accessible->role());
|
||||
|
||||
// The native OSK should be disabled if the Qt OSK is in use,
|
||||
// or if disabled via application attribute.
|
||||
static bool imModuleEmpty = QPlatformInputContextFactory::requested().isEmpty();
|
||||
bool nativeVKDisabled = QCoreApplication::testAttribute(Qt::AA_DisableNativeVirtualKeyboard);
|
||||
|
||||
// If we want to disable the native OSK auto-showing
|
||||
// we have to report text fields as non-editable.
|
||||
if (controlType == UIA_EditControlTypeId && (!imModuleEmpty || nativeVKDisabled))
|
||||
controlType = UIA_TextControlTypeId;
|
||||
|
||||
*pRetVal = QComVariant{ controlType }.release();
|
||||
}
|
||||
break;
|
||||
case UIA_HelpTextPropertyId:
|
||||
*pRetVal = QComVariant{ accessible->text(QAccessible::Help) }.release();
|
||||
break;
|
||||
case UIA_HasKeyboardFocusPropertyId:
|
||||
if (topLevelWindow) {
|
||||
// Windows set the active state to true when they are focused
|
||||
*pRetVal = QComVariant{ accessible->state().active ? true : false }.release();
|
||||
} else {
|
||||
*pRetVal = QComVariant{ accessible->state().focused ? true : false }.release();
|
||||
}
|
||||
break;
|
||||
case UIA_IsKeyboardFocusablePropertyId:
|
||||
if (topLevelWindow) {
|
||||
// Windows should always be focusable
|
||||
*pRetVal = QComVariant{ true }.release();
|
||||
} else {
|
||||
*pRetVal = QComVariant{ accessible->state().focusable ? true : false }.release();
|
||||
}
|
||||
break;
|
||||
case UIA_IsOffscreenPropertyId:
|
||||
*pRetVal = QComVariant{ accessible->state().offscreen ? true : false }.release();
|
||||
break;
|
||||
case UIA_IsContentElementPropertyId:
|
||||
*pRetVal = QComVariant{ true }.release();
|
||||
break;
|
||||
case UIA_IsControlElementPropertyId:
|
||||
*pRetVal = QComVariant{ true }.release();
|
||||
break;
|
||||
case UIA_IsEnabledPropertyId:
|
||||
*pRetVal = QComVariant{ !accessible->state().disabled }.release();
|
||||
break;
|
||||
case UIA_IsPasswordPropertyId:
|
||||
*pRetVal = QComVariant{ accessible->role() == QAccessible::EditableText
|
||||
&& accessible->state().passwordEdit }
|
||||
.release();
|
||||
break;
|
||||
case UIA_IsPeripheralPropertyId:
|
||||
// True for peripheral UIs.
|
||||
if (QWindow *window = windowForAccessible(accessible)) {
|
||||
const Qt::WindowType wt = window->type();
|
||||
*pRetVal = QComVariant{ wt == Qt::Popup || wt == Qt::ToolTip || wt == Qt::SplashScreen }
|
||||
.release();
|
||||
}
|
||||
break;
|
||||
case UIA_IsDialogPropertyId:
|
||||
*pRetVal = QComVariant{ accessible->role() == QAccessible::Dialog
|
||||
|| accessible->role() == QAccessible::AlertMessage }
|
||||
.release();
|
||||
break;
|
||||
case UIA_FullDescriptionPropertyId:
|
||||
*pRetVal = QComVariant{ accessible->text(QAccessible::Description) }.release();
|
||||
break;
|
||||
case UIA_NamePropertyId: {
|
||||
QString name = accessible->text(QAccessible::Name);
|
||||
if (name.isEmpty() && topLevelWindow)
|
||||
name = QCoreApplication::applicationName();
|
||||
*pRetVal = QComVariant{ name }.release();
|
||||
break;
|
||||
}
|
||||
case UIA_StyleIdAttributeId:
|
||||
setStyle(accessible, pRetVal);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT QWindowsUiaMainProvider::get_HostRawElementProvider(IRawElementProviderSimple **pRetVal)
|
||||
{
|
||||
qCDebug(lcQpaUiAutomation) << __FUNCTION__ << this;
|
||||
|
||||
if (!pRetVal)
|
||||
return E_INVALIDARG;
|
||||
*pRetVal = nullptr;
|
||||
|
||||
// Returns a host provider only for controls associated with a native window handle. Others should return NULL.
|
||||
if (QAccessibleInterface *accessible = accessibleInterface()) {
|
||||
if (HWND hwnd = hwndForAccessible(accessible)) {
|
||||
return QWindowsUiaWrapper::instance()->hostProviderFromHwnd(hwnd, pRetVal);
|
||||
}
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// Navigates within the tree of accessible controls.
|
||||
HRESULT QWindowsUiaMainProvider::Navigate(NavigateDirection direction, IRawElementProviderFragment **pRetVal)
|
||||
{
|
||||
qCDebug(lcQpaUiAutomation) << __FUNCTION__ << direction << " this: " << this;
|
||||
|
||||
if (!pRetVal)
|
||||
return E_INVALIDARG;
|
||||
*pRetVal = nullptr;
|
||||
|
||||
QAccessibleInterface *accessible = accessibleInterface();
|
||||
if (!accessible)
|
||||
return UIA_E_ELEMENTNOTAVAILABLE;
|
||||
|
||||
QAccessibleInterface *targetacc = nullptr;
|
||||
|
||||
if (direction == NavigateDirection_Parent) {
|
||||
if (QAccessibleInterface *parent = accessible->parent()) {
|
||||
// The Application's children are considered top level objects.
|
||||
if (parent->isValid() && parent->role() != QAccessible::Application) {
|
||||
targetacc = parent;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
QAccessibleInterface *parent = nullptr;
|
||||
int index = 0;
|
||||
int incr = 1;
|
||||
switch (direction) {
|
||||
case NavigateDirection_FirstChild:
|
||||
parent = accessible;
|
||||
index = 0;
|
||||
incr = 1;
|
||||
break;
|
||||
case NavigateDirection_LastChild:
|
||||
parent = accessible;
|
||||
index = accessible->childCount() - 1;
|
||||
incr = -1;
|
||||
break;
|
||||
case NavigateDirection_NextSibling:
|
||||
if ((parent = accessible->parent()))
|
||||
index = parent->indexOfChild(accessible) + 1;
|
||||
incr = 1;
|
||||
break;
|
||||
case NavigateDirection_PreviousSibling:
|
||||
if ((parent = accessible->parent()))
|
||||
index = parent->indexOfChild(accessible) - 1;
|
||||
incr = -1;
|
||||
break;
|
||||
default:
|
||||
Q_UNREACHABLE();
|
||||
break;
|
||||
}
|
||||
|
||||
if (parent && parent->isValid()) {
|
||||
for (int count = parent->childCount(); index >= 0 && index < count; index += incr) {
|
||||
if (QAccessibleInterface *child = parent->child(index)) {
|
||||
if (child->isValid() && !child->state().invisible) {
|
||||
targetacc = child;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (targetacc)
|
||||
*pRetVal = providerForAccessible(targetacc).Detach();
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// Returns a unique id assigned to the UI element, used as key by the UI Automation framework.
|
||||
HRESULT QWindowsUiaMainProvider::GetRuntimeId(SAFEARRAY **pRetVal)
|
||||
{
|
||||
qCDebug(lcQpaUiAutomation) << __FUNCTION__ << this;
|
||||
|
||||
if (!pRetVal)
|
||||
return E_INVALIDARG;
|
||||
*pRetVal = nullptr;
|
||||
|
||||
QAccessibleInterface *accessible = accessibleInterface();
|
||||
if (!accessible)
|
||||
return UIA_E_ELEMENTNOTAVAILABLE;
|
||||
|
||||
// The UiaAppendRuntimeId constant is used to make then ID unique
|
||||
// among multiple instances running on the system.
|
||||
int rtId[] = { UiaAppendRuntimeId, int(id()) };
|
||||
|
||||
if ((*pRetVal = SafeArrayCreateVector(VT_I4, 0, 2))) {
|
||||
for (LONG i = 0; i < 2; ++i)
|
||||
SafeArrayPutElement(*pRetVal, &i, &rtId[i]);
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// Returns the bounding rectangle for the accessible control.
|
||||
HRESULT QWindowsUiaMainProvider::get_BoundingRectangle(UiaRect *pRetVal)
|
||||
{
|
||||
qCDebug(lcQpaUiAutomation) << __FUNCTION__ << this;
|
||||
|
||||
if (!pRetVal)
|
||||
return E_INVALIDARG;
|
||||
|
||||
QAccessibleInterface *accessible = accessibleInterface();
|
||||
if (!accessible)
|
||||
return UIA_E_ELEMENTNOTAVAILABLE;
|
||||
|
||||
QWindow *window = windowForAccessible(accessible);
|
||||
if (!window)
|
||||
return UIA_E_ELEMENTNOTAVAILABLE;
|
||||
|
||||
rectToNativeUiaRect(accessible->rect(), window, pRetVal);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT QWindowsUiaMainProvider::GetEmbeddedFragmentRoots(SAFEARRAY **pRetVal)
|
||||
{
|
||||
qCDebug(lcQpaUiAutomation) << __FUNCTION__ << this;
|
||||
|
||||
if (!pRetVal)
|
||||
return E_INVALIDARG;
|
||||
*pRetVal = nullptr;
|
||||
// No embedded roots.
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// Sets focus to the control.
|
||||
HRESULT QWindowsUiaMainProvider::SetFocus()
|
||||
{
|
||||
qCDebug(lcQpaUiAutomation) << __FUNCTION__ << this;
|
||||
|
||||
QAccessibleInterface *accessible = accessibleInterface();
|
||||
if (!accessible)
|
||||
return UIA_E_ELEMENTNOTAVAILABLE;
|
||||
|
||||
QAccessibleActionInterface *actionInterface = accessible->actionInterface();
|
||||
if (!actionInterface)
|
||||
return UIA_E_ELEMENTNOTAVAILABLE;
|
||||
|
||||
actionInterface->doAction(QAccessibleActionInterface::setFocusAction());
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT QWindowsUiaMainProvider::get_FragmentRoot(IRawElementProviderFragmentRoot **pRetVal)
|
||||
{
|
||||
qCDebug(lcQpaUiAutomation) << __FUNCTION__ << this;
|
||||
|
||||
if (!pRetVal)
|
||||
return E_INVALIDARG;
|
||||
*pRetVal = nullptr;
|
||||
|
||||
// Our UI Automation implementation considers the window as the root for
|
||||
// non-native controls/fragments.
|
||||
if (QAccessibleInterface *accessible = accessibleInterface()) {
|
||||
if (QWindow *window = windowForAccessible(accessible)) {
|
||||
if (QAccessibleInterface *rootacc = window->accessibleRoot())
|
||||
*pRetVal = providerForAccessible(rootacc).Detach();
|
||||
}
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// Returns a provider for the UI element present at the specified screen coordinates.
|
||||
HRESULT QWindowsUiaMainProvider::ElementProviderFromPoint(double x, double y, IRawElementProviderFragment **pRetVal)
|
||||
{
|
||||
qCDebug(lcQpaUiAutomation) << __FUNCTION__ << x << y;
|
||||
|
||||
if (!pRetVal) {
|
||||
return E_INVALIDARG;
|
||||
}
|
||||
*pRetVal = nullptr;
|
||||
|
||||
QAccessibleInterface *accessible = accessibleInterface();
|
||||
if (!accessible)
|
||||
return UIA_E_ELEMENTNOTAVAILABLE;
|
||||
|
||||
QWindow *window = windowForAccessible(accessible);
|
||||
if (!window)
|
||||
return UIA_E_ELEMENTNOTAVAILABLE;
|
||||
|
||||
// Scales coordinates from High DPI screens.
|
||||
UiaPoint uiaPoint = {x, y};
|
||||
QPoint point;
|
||||
nativeUiaPointToPoint(uiaPoint, window, &point);
|
||||
|
||||
QAccessibleInterface *targetacc = accessible->childAt(point.x(), point.y());
|
||||
|
||||
if (targetacc) {
|
||||
QAccessibleInterface *acc = targetacc;
|
||||
// Controls can be embedded within grouping elements. By default returns the innermost control.
|
||||
while (acc) {
|
||||
targetacc = acc;
|
||||
// For accessibility tools it may be better to return the text element instead of its subcomponents.
|
||||
if (targetacc->textInterface()) break;
|
||||
acc = acc->childAt(point.x(), point.y());
|
||||
}
|
||||
*pRetVal = providerForAccessible(targetacc).Detach();
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// Returns the fragment with focus.
|
||||
HRESULT QWindowsUiaMainProvider::GetFocus(IRawElementProviderFragment **pRetVal)
|
||||
{
|
||||
qCDebug(lcQpaUiAutomation) << __FUNCTION__ << this;
|
||||
|
||||
if (!pRetVal)
|
||||
return E_INVALIDARG;
|
||||
*pRetVal = nullptr;
|
||||
|
||||
if (QAccessibleInterface *accessible = accessibleInterface()) {
|
||||
if (QAccessibleInterface *focusacc = accessible->focusChild()) {
|
||||
*pRetVal = providerForAccessible(focusacc).Detach();
|
||||
}
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
#endif // QT_CONFIG(accessibility)
|
@ -0,0 +1,89 @@
|
||||
// Copyright (C) 2017 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 <initguid.h>
|
||||
|
||||
#include "qwindowsuiawrapper_p.h"
|
||||
#include <QtCore/private/qsystemlibrary_p.h>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
// private constructor
|
||||
QWindowsUiaWrapper::QWindowsUiaWrapper()
|
||||
{
|
||||
QSystemLibrary uiaLib(QStringLiteral("UIAutomationCore"));
|
||||
if (uiaLib.load()) {
|
||||
m_pUiaReturnRawElementProvider = reinterpret_cast<PtrUiaReturnRawElementProvider>(uiaLib.resolve("UiaReturnRawElementProvider"));
|
||||
m_pUiaHostProviderFromHwnd = reinterpret_cast<PtrUiaHostProviderFromHwnd>(uiaLib.resolve("UiaHostProviderFromHwnd"));
|
||||
m_pUiaRaiseAutomationPropertyChangedEvent = reinterpret_cast<PtrUiaRaiseAutomationPropertyChangedEvent>(uiaLib.resolve("UiaRaiseAutomationPropertyChangedEvent"));
|
||||
m_pUiaRaiseAutomationEvent = reinterpret_cast<PtrUiaRaiseAutomationEvent>(uiaLib.resolve("UiaRaiseAutomationEvent"));
|
||||
m_pUiaRaiseNotificationEvent = reinterpret_cast<PtrUiaRaiseNotificationEvent>(uiaLib.resolve("UiaRaiseNotificationEvent"));
|
||||
m_pUiaClientsAreListening = reinterpret_cast<PtrUiaClientsAreListening>(uiaLib.resolve("UiaClientsAreListening"));
|
||||
}
|
||||
}
|
||||
|
||||
QWindowsUiaWrapper::~QWindowsUiaWrapper()
|
||||
{
|
||||
}
|
||||
|
||||
// shared instance
|
||||
QWindowsUiaWrapper *QWindowsUiaWrapper::instance()
|
||||
{
|
||||
static QWindowsUiaWrapper wrapper;
|
||||
return &wrapper;
|
||||
}
|
||||
|
||||
// True if all symbols resolved.
|
||||
BOOL QWindowsUiaWrapper::ready()
|
||||
{
|
||||
return m_pUiaReturnRawElementProvider
|
||||
&& m_pUiaHostProviderFromHwnd
|
||||
&& m_pUiaRaiseAutomationPropertyChangedEvent
|
||||
&& m_pUiaRaiseAutomationEvent
|
||||
&& m_pUiaClientsAreListening;
|
||||
}
|
||||
|
||||
BOOL QWindowsUiaWrapper::clientsAreListening()
|
||||
{
|
||||
if (!m_pUiaClientsAreListening)
|
||||
return FALSE;
|
||||
return m_pUiaClientsAreListening();
|
||||
}
|
||||
|
||||
LRESULT QWindowsUiaWrapper::returnRawElementProvider(HWND hwnd, WPARAM wParam, LPARAM lParam, IRawElementProviderSimple *el)
|
||||
{
|
||||
if (!m_pUiaReturnRawElementProvider)
|
||||
return static_cast<LRESULT>(NULL);
|
||||
return m_pUiaReturnRawElementProvider(hwnd, wParam, lParam, el);
|
||||
}
|
||||
|
||||
HRESULT QWindowsUiaWrapper::hostProviderFromHwnd(HWND hwnd, IRawElementProviderSimple **ppProvider)
|
||||
{
|
||||
if (!m_pUiaHostProviderFromHwnd)
|
||||
return UIA_E_NOTSUPPORTED;
|
||||
return m_pUiaHostProviderFromHwnd(hwnd, ppProvider);
|
||||
}
|
||||
|
||||
HRESULT QWindowsUiaWrapper::raiseAutomationPropertyChangedEvent(IRawElementProviderSimple *pProvider, PROPERTYID id, VARIANT oldValue, VARIANT newValue)
|
||||
{
|
||||
if (!m_pUiaRaiseAutomationPropertyChangedEvent)
|
||||
return UIA_E_NOTSUPPORTED;
|
||||
return m_pUiaRaiseAutomationPropertyChangedEvent(pProvider, id, oldValue, newValue);
|
||||
}
|
||||
|
||||
HRESULT QWindowsUiaWrapper::raiseAutomationEvent(IRawElementProviderSimple *pProvider, EVENTID id)
|
||||
{
|
||||
if (!m_pUiaRaiseAutomationEvent)
|
||||
return UIA_E_NOTSUPPORTED;
|
||||
return m_pUiaRaiseAutomationEvent(pProvider, id);
|
||||
}
|
||||
|
||||
HRESULT QWindowsUiaWrapper::raiseNotificationEvent(IRawElementProviderSimple *pProvider, NotificationKind notificationKind, NotificationProcessing notificationProcessing, BSTR displayString, BSTR activityId)
|
||||
{
|
||||
if (!m_pUiaRaiseNotificationEvent)
|
||||
return UIA_E_NOTSUPPORTED;
|
||||
return m_pUiaRaiseNotificationEvent(pProvider, notificationKind, notificationProcessing, displayString, activityId);
|
||||
}
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
@ -0,0 +1,57 @@
|
||||
// Copyright (C) 2017 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 QWINDOWSUIAWRAPPER_H
|
||||
#define QWINDOWSUIAWRAPPER_H
|
||||
|
||||
//
|
||||
// W A R N I N G
|
||||
// -------------
|
||||
//
|
||||
// This file is not part of the Qt API. It exists for the convenience
|
||||
// of other Qt classes. This header file may change from version to
|
||||
// version without notice, or even be removed.
|
||||
//
|
||||
// We mean it.
|
||||
//
|
||||
|
||||
#include <QtGui/private/qtguiglobal_p.h>
|
||||
|
||||
QT_REQUIRE_CONFIG(accessibility);
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
class QWindowsUiaWrapper
|
||||
{
|
||||
QWindowsUiaWrapper();
|
||||
virtual ~QWindowsUiaWrapper();
|
||||
public:
|
||||
static QWindowsUiaWrapper *instance();
|
||||
BOOL ready();
|
||||
BOOL clientsAreListening();
|
||||
LRESULT returnRawElementProvider(HWND hwnd, WPARAM wParam, LPARAM lParam, IRawElementProviderSimple *el);
|
||||
HRESULT hostProviderFromHwnd(HWND hwnd, IRawElementProviderSimple **ppProvider);
|
||||
HRESULT raiseAutomationPropertyChangedEvent(IRawElementProviderSimple *pProvider, PROPERTYID id, VARIANT oldValue, VARIANT newValue);
|
||||
HRESULT raiseAutomationEvent(IRawElementProviderSimple *pProvider, EVENTID id);
|
||||
HRESULT raiseNotificationEvent(IRawElementProviderSimple *pProvider, NotificationKind notificationKind, NotificationProcessing notificationProcessing, BSTR displayString, BSTR activityId);
|
||||
|
||||
private:
|
||||
typedef LRESULT (WINAPI *PtrUiaReturnRawElementProvider)(HWND, WPARAM, LPARAM, IRawElementProviderSimple *);
|
||||
typedef HRESULT (WINAPI *PtrUiaHostProviderFromHwnd)(HWND, IRawElementProviderSimple **);
|
||||
typedef HRESULT (WINAPI *PtrUiaRaiseAutomationPropertyChangedEvent)(IRawElementProviderSimple *, PROPERTYID, VARIANT, VARIANT);
|
||||
typedef HRESULT (WINAPI *PtrUiaRaiseAutomationEvent)(IRawElementProviderSimple *, EVENTID);
|
||||
typedef HRESULT (WINAPI *PtrUiaRaiseNotificationEvent)(IRawElementProviderSimple *, NotificationKind, NotificationProcessing, BSTR, BSTR);
|
||||
|
||||
typedef BOOL (WINAPI *PtrUiaClientsAreListening)();
|
||||
PtrUiaReturnRawElementProvider m_pUiaReturnRawElementProvider = nullptr;
|
||||
PtrUiaHostProviderFromHwnd m_pUiaHostProviderFromHwnd = nullptr;
|
||||
PtrUiaRaiseAutomationPropertyChangedEvent m_pUiaRaiseAutomationPropertyChangedEvent = nullptr;
|
||||
PtrUiaRaiseAutomationEvent m_pUiaRaiseAutomationEvent = nullptr;
|
||||
PtrUiaRaiseNotificationEvent m_pUiaRaiseNotificationEvent = nullptr;
|
||||
PtrUiaClientsAreListening m_pUiaClientsAreListening = nullptr;
|
||||
};
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
#endif //QWINDOWSUIAWRAPPER_H
|
||||
|
@ -90,11 +90,6 @@ enum QSliderDirection { SlUp, SlDown, SlLeft, SlRight };
|
||||
|
||||
QWindowsStylePrivate::QWindowsStylePrivate() = default;
|
||||
|
||||
qreal QWindowsStylePrivate::appDevicePixelRatio()
|
||||
{
|
||||
return qApp->devicePixelRatio();
|
||||
}
|
||||
|
||||
// Returns \c true if the toplevel parent of \a widget has seen the Alt-key
|
||||
bool QWindowsStylePrivate::hasSeenAlt(const QWidget *widget) const
|
||||
{
|
||||
@ -459,46 +454,6 @@ int QWindowsStyle::pixelMetric(PixelMetric pm, const QStyleOption *opt, const QW
|
||||
QPixmap QWindowsStyle::standardPixmap(StandardPixmap standardPixmap, const QStyleOption *opt,
|
||||
const QWidget *widget) const
|
||||
{
|
||||
#if defined(Q_OS_WIN)
|
||||
QPixmap desktopIcon;
|
||||
switch(standardPixmap) {
|
||||
case SP_DriveCDIcon:
|
||||
case SP_DriveDVDIcon:
|
||||
case SP_DriveNetIcon:
|
||||
case SP_DriveHDIcon:
|
||||
case SP_DriveFDIcon:
|
||||
case SP_FileIcon:
|
||||
case SP_FileLinkIcon:
|
||||
case SP_DirLinkIcon:
|
||||
case SP_DirClosedIcon:
|
||||
case SP_DesktopIcon:
|
||||
case SP_ComputerIcon:
|
||||
case SP_DirOpenIcon:
|
||||
case SP_FileDialogNewFolder:
|
||||
case SP_DirHomeIcon:
|
||||
case SP_TrashIcon:
|
||||
case SP_VistaShield:
|
||||
if (const QPlatformTheme *theme = QGuiApplicationPrivate::platformTheme()) {
|
||||
QPlatformTheme::StandardPixmap sp = static_cast<QPlatformTheme::StandardPixmap>(standardPixmap);
|
||||
desktopIcon = theme->standardPixmap(sp, QSizeF(16, 16));
|
||||
}
|
||||
break;
|
||||
case SP_MessageBoxInformation:
|
||||
case SP_MessageBoxWarning:
|
||||
case SP_MessageBoxCritical:
|
||||
case SP_MessageBoxQuestion:
|
||||
if (const QPlatformTheme *theme = QGuiApplicationPrivate::platformTheme()) {
|
||||
QPlatformTheme::StandardPixmap sp = static_cast<QPlatformTheme::StandardPixmap>(standardPixmap);
|
||||
desktopIcon = theme->standardPixmap(sp, QSizeF());
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (!desktopIcon.isNull()) {
|
||||
return desktopIcon;
|
||||
}
|
||||
#endif // Q_OS_WIN
|
||||
return QCommonStyle::standardPixmap(standardPixmap, opt, widget);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user