mirror of
https://github.com/crystalidea/qt6windows7.git
synced 2024-11-26 06:07:04 +08:00
qt 6.6.1 original sources files (to be patched)
This commit is contained in:
parent
db7d18b23c
commit
b752f623ec
242
qtbase/src/corelib/io/qstandardpaths_win.cpp
Normal file
242
qtbase/src/corelib/io/qstandardpaths_win.cpp
Normal file
@ -0,0 +1,242 @@
|
||||
// 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 "qstandardpaths.h"
|
||||
|
||||
#include <qdir.h>
|
||||
#include <qstringlist.h>
|
||||
|
||||
#ifndef QT_BOOTSTRAPPED
|
||||
#include <qcoreapplication.h>
|
||||
#endif
|
||||
|
||||
#include <qt_windows.h>
|
||||
#include <shlobj.h>
|
||||
#include <intshcut.h>
|
||||
#include <qvarlengtharray.h>
|
||||
|
||||
#ifndef QT_NO_STANDARDPATHS
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
static QString convertCharArray(const wchar_t *path)
|
||||
{
|
||||
return QDir::fromNativeSeparators(QString::fromWCharArray(path));
|
||||
}
|
||||
|
||||
static inline bool isGenericConfigLocation(QStandardPaths::StandardLocation type)
|
||||
{
|
||||
return type == QStandardPaths::GenericConfigLocation || type == QStandardPaths::GenericDataLocation;
|
||||
}
|
||||
|
||||
static inline bool isConfigLocation(QStandardPaths::StandardLocation type)
|
||||
{
|
||||
return type == QStandardPaths::ConfigLocation || type == QStandardPaths::AppConfigLocation
|
||||
|| type == QStandardPaths::AppDataLocation || type == QStandardPaths::AppLocalDataLocation
|
||||
|| isGenericConfigLocation(type);
|
||||
}
|
||||
|
||||
static void appendOrganizationAndApp(QString &path) // Courtesy qstandardpaths_unix.cpp
|
||||
{
|
||||
#ifndef QT_BOOTSTRAPPED
|
||||
const QString &org = QCoreApplication::organizationName();
|
||||
if (!org.isEmpty())
|
||||
path += u'/' + org;
|
||||
const QString &appName = QCoreApplication::applicationName();
|
||||
if (!appName.isEmpty())
|
||||
path += u'/' + appName;
|
||||
#else // !QT_BOOTSTRAPPED
|
||||
Q_UNUSED(path);
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline void appendTestMode(QString &path)
|
||||
{
|
||||
if (QStandardPaths::isTestModeEnabled())
|
||||
path += "/qttest"_L1;
|
||||
}
|
||||
|
||||
static bool isProcessLowIntegrity()
|
||||
{
|
||||
// same as GetCurrentProcessToken()
|
||||
const auto process_token = HANDLE(quintptr(-4));
|
||||
|
||||
QVarLengthArray<char,256> token_info_buf(256);
|
||||
auto* token_info = reinterpret_cast<TOKEN_MANDATORY_LABEL*>(token_info_buf.data());
|
||||
DWORD token_info_length = token_info_buf.size();
|
||||
if (!GetTokenInformation(process_token, TokenIntegrityLevel, token_info, token_info_length, &token_info_length)) {
|
||||
// grow buffer and retry GetTokenInformation
|
||||
token_info_buf.resize(token_info_length);
|
||||
token_info = reinterpret_cast<TOKEN_MANDATORY_LABEL*>(token_info_buf.data());
|
||||
if (!GetTokenInformation(process_token, TokenIntegrityLevel, token_info, token_info_length, &token_info_length))
|
||||
return false; // assume "normal" process
|
||||
}
|
||||
|
||||
// The GetSidSubAuthorityCount return-code is undefined on failure, so
|
||||
// there's no point in checking before dereferencing
|
||||
DWORD integrity_level = *GetSidSubAuthority(token_info->Label.Sid, *GetSidSubAuthorityCount(token_info->Label.Sid) - 1);
|
||||
return (integrity_level < SECURITY_MANDATORY_MEDIUM_RID);
|
||||
}
|
||||
|
||||
// Map QStandardPaths::StandardLocation to KNOWNFOLDERID of SHGetKnownFolderPath()
|
||||
static GUID writableSpecialFolderId(QStandardPaths::StandardLocation type)
|
||||
{
|
||||
// folders for medium & high integrity processes
|
||||
static const GUID folderIds[] = {
|
||||
FOLDERID_Desktop, // DesktopLocation
|
||||
FOLDERID_Documents, // DocumentsLocation
|
||||
FOLDERID_Fonts, // FontsLocation
|
||||
FOLDERID_Programs, // ApplicationsLocation
|
||||
FOLDERID_Music, // MusicLocation
|
||||
FOLDERID_Videos, // MoviesLocation
|
||||
FOLDERID_Pictures, // PicturesLocation
|
||||
GUID(), GUID(), // TempLocation/HomeLocation
|
||||
FOLDERID_LocalAppData, // AppLocalDataLocation ("Local" path)
|
||||
GUID(), // CacheLocation
|
||||
FOLDERID_LocalAppData, // GenericDataLocation ("Local" path)
|
||||
GUID(), // RuntimeLocation
|
||||
FOLDERID_LocalAppData, // ConfigLocation ("Local" path)
|
||||
FOLDERID_Downloads, // DownloadLocation
|
||||
GUID(), // GenericCacheLocation
|
||||
FOLDERID_LocalAppData, // GenericConfigLocation ("Local" path)
|
||||
FOLDERID_RoamingAppData,// AppDataLocation ("Roaming" path)
|
||||
FOLDERID_LocalAppData, // AppConfigLocation ("Local" path)
|
||||
FOLDERID_Public, // PublicShareLocation
|
||||
FOLDERID_Templates, // TemplatesLocation
|
||||
};
|
||||
static_assert(sizeof(folderIds) / sizeof(folderIds[0]) == size_t(QStandardPaths::TemplatesLocation + 1));
|
||||
|
||||
// folders for low integrity processes
|
||||
static const GUID folderIds_li[] = {
|
||||
FOLDERID_Desktop, // DesktopLocation
|
||||
FOLDERID_Documents, // DocumentsLocation
|
||||
FOLDERID_Fonts, // FontsLocation
|
||||
FOLDERID_Programs, // ApplicationsLocation
|
||||
FOLDERID_Music, // MusicLocation
|
||||
FOLDERID_Videos, // MoviesLocation
|
||||
FOLDERID_Pictures, // PicturesLocation
|
||||
GUID(), GUID(), // TempLocation/HomeLocation
|
||||
FOLDERID_LocalAppDataLow,// AppLocalDataLocation ("Local" path)
|
||||
GUID(), // CacheLocation
|
||||
FOLDERID_LocalAppDataLow,// GenericDataLocation ("Local" path)
|
||||
GUID(), // RuntimeLocation
|
||||
FOLDERID_LocalAppDataLow,// ConfigLocation ("Local" path)
|
||||
FOLDERID_Downloads, // DownloadLocation
|
||||
GUID(), // GenericCacheLocation
|
||||
FOLDERID_LocalAppDataLow,// GenericConfigLocation ("Local" path)
|
||||
FOLDERID_RoamingAppData, // AppDataLocation ("Roaming" path)
|
||||
FOLDERID_LocalAppDataLow,// AppConfigLocation ("Local" path)
|
||||
FOLDERID_Public, // PublicShareLocation
|
||||
FOLDERID_Templates, // TemplatesLocation
|
||||
};
|
||||
static_assert(sizeof(folderIds_li) == sizeof(folderIds));
|
||||
|
||||
static bool low_integrity_process = isProcessLowIntegrity();
|
||||
if (size_t(type) < sizeof(folderIds) / sizeof(folderIds[0]))
|
||||
return low_integrity_process ? folderIds_li[type] : folderIds[type];
|
||||
return GUID();
|
||||
}
|
||||
|
||||
// Convenience for SHGetKnownFolderPath().
|
||||
static QString sHGetKnownFolderPath(const GUID &clsid)
|
||||
{
|
||||
QString result;
|
||||
LPWSTR path;
|
||||
if (Q_LIKELY(SUCCEEDED(SHGetKnownFolderPath(clsid, KF_FLAG_DONT_VERIFY, 0, &path)))) {
|
||||
result = convertCharArray(path);
|
||||
CoTaskMemFree(path);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
QString QStandardPaths::writableLocation(StandardLocation type)
|
||||
{
|
||||
QString result;
|
||||
switch (type) {
|
||||
case CacheLocation:
|
||||
// Although Microsoft has a Cache key it is a pointer to IE's cache, not a cache
|
||||
// location for everyone. Most applications seem to be using a
|
||||
// cache directory located in their AppData directory
|
||||
result = sHGetKnownFolderPath(writableSpecialFolderId(AppLocalDataLocation));
|
||||
if (!result.isEmpty()) {
|
||||
appendTestMode(result);
|
||||
appendOrganizationAndApp(result);
|
||||
result += "/cache"_L1;
|
||||
}
|
||||
break;
|
||||
|
||||
case GenericCacheLocation:
|
||||
result = sHGetKnownFolderPath(writableSpecialFolderId(GenericDataLocation));
|
||||
if (!result.isEmpty()) {
|
||||
appendTestMode(result);
|
||||
result += "/cache"_L1;
|
||||
}
|
||||
break;
|
||||
|
||||
case RuntimeLocation:
|
||||
case HomeLocation:
|
||||
result = QDir::homePath();
|
||||
break;
|
||||
|
||||
case TempLocation:
|
||||
result = QDir::tempPath();
|
||||
break;
|
||||
|
||||
default:
|
||||
result = sHGetKnownFolderPath(writableSpecialFolderId(type));
|
||||
if (!result.isEmpty() && isConfigLocation(type)) {
|
||||
appendTestMode(result);
|
||||
if (!isGenericConfigLocation(type))
|
||||
appendOrganizationAndApp(result);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
#ifndef QT_BOOTSTRAPPED
|
||||
extern QString qAppFileName();
|
||||
#endif
|
||||
|
||||
QStringList QStandardPaths::standardLocations(StandardLocation type)
|
||||
{
|
||||
QStringList dirs;
|
||||
const QString localDir = writableLocation(type);
|
||||
if (!localDir.isEmpty())
|
||||
dirs.append(localDir);
|
||||
|
||||
// type-specific handling goes here
|
||||
if (isConfigLocation(type)) {
|
||||
QString programData = sHGetKnownFolderPath(FOLDERID_ProgramData);
|
||||
if (!programData.isEmpty()) {
|
||||
if (!isGenericConfigLocation(type))
|
||||
appendOrganizationAndApp(programData);
|
||||
dirs.append(programData);
|
||||
}
|
||||
#ifndef QT_BOOTSTRAPPED
|
||||
// Note: QCoreApplication::applicationDirPath(), while static, requires
|
||||
// an application instance. But we might need to resolve the standard
|
||||
// locations earlier than that, so we fall back to qAppFileName().
|
||||
QString applicationDirPath = qApp ? QCoreApplication::applicationDirPath()
|
||||
: QFileInfo(qAppFileName()).path();
|
||||
dirs.append(applicationDirPath);
|
||||
const QString dataDir = applicationDirPath + "/data"_L1;
|
||||
dirs.append(dataDir);
|
||||
|
||||
if (!isGenericConfigLocation(type)) {
|
||||
QString appDataDir = dataDir;
|
||||
appendOrganizationAndApp(appDataDir);
|
||||
if (appDataDir != dataDir)
|
||||
dirs.append(appDataDir);
|
||||
}
|
||||
#endif // !QT_BOOTSTRAPPED
|
||||
} // isConfigLocation()
|
||||
|
||||
return dirs;
|
||||
}
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
#endif // QT_NO_STANDARDPATHS
|
908
qtbase/src/corelib/kernel/qeventdispatcher_win.cpp
Normal file
908
qtbase/src/corelib/kernel/qeventdispatcher_win.cpp
Normal file
@ -0,0 +1,908 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// Copyright (C) 2016 Intel Corporation.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
|
||||
|
||||
#include "qeventdispatcher_win_p.h"
|
||||
|
||||
#include "qcoreapplication.h"
|
||||
#include <private/qsystemlibrary_p.h>
|
||||
#include "qoperatingsystemversion.h"
|
||||
#include "qpair.h"
|
||||
#include "qset.h"
|
||||
#include "qsocketnotifier.h"
|
||||
#include "qvarlengtharray.h"
|
||||
|
||||
#include "qelapsedtimer.h"
|
||||
#include "qcoreapplication_p.h"
|
||||
#include <private/qthread_p.h>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
#ifndef TIME_KILL_SYNCHRONOUS
|
||||
# define TIME_KILL_SYNCHRONOUS 0x0100
|
||||
#endif
|
||||
|
||||
#ifndef QS_RAWINPUT
|
||||
# define QS_RAWINPUT 0x0400
|
||||
#endif
|
||||
|
||||
#ifndef WM_TOUCH
|
||||
# define WM_TOUCH 0x0240
|
||||
#endif
|
||||
#ifndef QT_NO_GESTURES
|
||||
#ifndef WM_GESTURE
|
||||
# define WM_GESTURE 0x0119
|
||||
#endif
|
||||
#ifndef WM_GESTURENOTIFY
|
||||
# define WM_GESTURENOTIFY 0x011A
|
||||
#endif
|
||||
#endif // QT_NO_GESTURES
|
||||
|
||||
enum {
|
||||
WM_QT_SOCKETNOTIFIER = WM_USER,
|
||||
WM_QT_SENDPOSTEDEVENTS = WM_USER + 1,
|
||||
WM_QT_ACTIVATENOTIFIERS = WM_USER + 2
|
||||
};
|
||||
|
||||
enum {
|
||||
SendPostedEventsTimerId = ~1u
|
||||
};
|
||||
|
||||
class QEventDispatcherWin32Private;
|
||||
|
||||
#if !defined(DWORD_PTR) && !defined(Q_OS_WIN64)
|
||||
#define DWORD_PTR DWORD
|
||||
#endif
|
||||
|
||||
LRESULT QT_WIN_CALLBACK qt_internal_proc(HWND hwnd, UINT message, WPARAM wp, LPARAM lp);
|
||||
|
||||
static quint64 qt_msectime()
|
||||
{
|
||||
using namespace std::chrono;
|
||||
auto t = duration_cast<milliseconds>(steady_clock::now().time_since_epoch());
|
||||
return t.count();
|
||||
}
|
||||
|
||||
QEventDispatcherWin32Private::QEventDispatcherWin32Private()
|
||||
: interrupt(false), internalHwnd(0),
|
||||
sendPostedEventsTimerId(0), wakeUps(0),
|
||||
activateNotifiersPosted(false)
|
||||
{
|
||||
}
|
||||
|
||||
QEventDispatcherWin32Private::~QEventDispatcherWin32Private()
|
||||
{
|
||||
if (internalHwnd)
|
||||
DestroyWindow(internalHwnd);
|
||||
}
|
||||
|
||||
// This function is called by a workerthread
|
||||
void WINAPI QT_WIN_CALLBACK qt_fast_timer_proc(uint timerId, uint /*reserved*/, DWORD_PTR user, DWORD_PTR /*reserved*/, DWORD_PTR /*reserved*/)
|
||||
{
|
||||
if (!timerId) // sanity check
|
||||
return;
|
||||
auto t = reinterpret_cast<WinTimerInfo*>(user);
|
||||
Q_ASSERT(t);
|
||||
QCoreApplication::postEvent(t->dispatcher, new QTimerEvent(t->timerId));
|
||||
}
|
||||
|
||||
LRESULT QT_WIN_CALLBACK qt_internal_proc(HWND hwnd, UINT message, WPARAM wp, LPARAM lp)
|
||||
{
|
||||
if (message == WM_NCCREATE)
|
||||
return true;
|
||||
|
||||
MSG msg;
|
||||
msg.hwnd = hwnd;
|
||||
msg.message = message;
|
||||
msg.wParam = wp;
|
||||
msg.lParam = lp;
|
||||
QAbstractEventDispatcher* dispatcher = QAbstractEventDispatcher::instance();
|
||||
qintptr result;
|
||||
if (!dispatcher) {
|
||||
if (message == WM_TIMER)
|
||||
KillTimer(hwnd, wp);
|
||||
return 0;
|
||||
}
|
||||
if (dispatcher->filterNativeEvent(QByteArrayLiteral("windows_dispatcher_MSG"), &msg, &result))
|
||||
return result;
|
||||
|
||||
auto q = reinterpret_cast<QEventDispatcherWin32 *>(GetWindowLongPtr(hwnd, GWLP_USERDATA));
|
||||
QEventDispatcherWin32Private *d = nullptr;
|
||||
if (q != nullptr)
|
||||
d = q->d_func();
|
||||
|
||||
switch (message) {
|
||||
case WM_QT_SOCKETNOTIFIER: {
|
||||
// socket notifier message
|
||||
int type = -1;
|
||||
switch (WSAGETSELECTEVENT(lp)) {
|
||||
case FD_READ:
|
||||
case FD_ACCEPT:
|
||||
type = 0;
|
||||
break;
|
||||
case FD_WRITE:
|
||||
case FD_CONNECT:
|
||||
type = 1;
|
||||
break;
|
||||
case FD_OOB:
|
||||
type = 2;
|
||||
break;
|
||||
case FD_CLOSE:
|
||||
type = 3;
|
||||
break;
|
||||
}
|
||||
if (type >= 0) {
|
||||
Q_ASSERT(d != nullptr);
|
||||
QSNDict *sn_vec[4] = { &d->sn_read, &d->sn_write, &d->sn_except, &d->sn_read };
|
||||
QSNDict *dict = sn_vec[type];
|
||||
|
||||
QSockNot *sn = dict ? dict->value(wp) : 0;
|
||||
if (sn == nullptr) {
|
||||
d->postActivateSocketNotifiers();
|
||||
} else {
|
||||
Q_ASSERT(d->active_fd.contains(sn->fd));
|
||||
QSockFd &sd = d->active_fd[sn->fd];
|
||||
if (sd.selected) {
|
||||
Q_ASSERT(sd.mask == 0);
|
||||
d->doWsaAsyncSelect(sn->fd, 0);
|
||||
sd.selected = false;
|
||||
}
|
||||
d->postActivateSocketNotifiers();
|
||||
|
||||
// Ignore the message if a notification with the same type was
|
||||
// received previously. Suppressed message is definitely spurious.
|
||||
const long eventCode = WSAGETSELECTEVENT(lp);
|
||||
if ((sd.mask & eventCode) != eventCode) {
|
||||
sd.mask |= eventCode;
|
||||
QEvent event(type < 3 ? QEvent::SockAct : QEvent::SockClose);
|
||||
QCoreApplication::sendEvent(sn->obj, &event);
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
case WM_QT_ACTIVATENOTIFIERS: {
|
||||
Q_ASSERT(d != nullptr);
|
||||
|
||||
// Postpone activation if we have unhandled socket notifier messages
|
||||
// in the queue. WM_QT_ACTIVATENOTIFIERS will be posted again as a result of
|
||||
// event processing.
|
||||
MSG msg;
|
||||
if (!PeekMessage(&msg, d->internalHwnd,
|
||||
WM_QT_SOCKETNOTIFIER, WM_QT_SOCKETNOTIFIER, PM_NOREMOVE)
|
||||
&& d->queuedSocketEvents.isEmpty()) {
|
||||
// register all socket notifiers
|
||||
for (QSFDict::iterator it = d->active_fd.begin(), end = d->active_fd.end();
|
||||
it != end; ++it) {
|
||||
QSockFd &sd = it.value();
|
||||
if (!sd.selected) {
|
||||
d->doWsaAsyncSelect(it.key(), sd.event);
|
||||
// allow any event to be accepted
|
||||
sd.mask = 0;
|
||||
sd.selected = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
d->activateNotifiersPosted = false;
|
||||
return 0;
|
||||
}
|
||||
case WM_TIMER:
|
||||
Q_ASSERT(d != nullptr);
|
||||
|
||||
if (wp == d->sendPostedEventsTimerId)
|
||||
q->sendPostedEvents();
|
||||
else
|
||||
d->sendTimerEvent(wp);
|
||||
return 0;
|
||||
case WM_QT_SENDPOSTEDEVENTS:
|
||||
Q_ASSERT(d != nullptr);
|
||||
|
||||
// We send posted events manually, if the window procedure was invoked
|
||||
// by the foreign event loop (e.g. from the native modal dialog).
|
||||
// Skip sending, if the message queue is not empty.
|
||||
// sendPostedEventsTimer will deliver posted events later.
|
||||
static const UINT mask = QS_ALLEVENTS;
|
||||
if (HIWORD(GetQueueStatus(mask)) == 0)
|
||||
q->sendPostedEvents();
|
||||
else
|
||||
d->startPostedEventsTimer();
|
||||
return 0;
|
||||
} // switch (message)
|
||||
|
||||
return DefWindowProc(hwnd, message, wp, lp);
|
||||
}
|
||||
|
||||
void QEventDispatcherWin32Private::startPostedEventsTimer()
|
||||
{
|
||||
// we received WM_QT_SENDPOSTEDEVENTS, so allow posting it again
|
||||
wakeUps.storeRelaxed(0);
|
||||
if (sendPostedEventsTimerId == 0) {
|
||||
// Start a timer to deliver posted events when the message queue is emptied.
|
||||
sendPostedEventsTimerId = SetTimer(internalHwnd, SendPostedEventsTimerId,
|
||||
USER_TIMER_MINIMUM, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
// Provide class name and atom for the message window used by
|
||||
// QEventDispatcherWin32Private via Q_GLOBAL_STATIC shared between threads.
|
||||
struct QWindowsMessageWindowClassContext
|
||||
{
|
||||
QWindowsMessageWindowClassContext();
|
||||
~QWindowsMessageWindowClassContext();
|
||||
|
||||
ATOM atom;
|
||||
wchar_t *className;
|
||||
};
|
||||
|
||||
QWindowsMessageWindowClassContext::QWindowsMessageWindowClassContext()
|
||||
: atom(0), className(0)
|
||||
{
|
||||
// make sure that multiple Qt's can coexist in the same process
|
||||
const QString qClassName = QStringLiteral("QEventDispatcherWin32_Internal_Widget")
|
||||
+ QString::number(quintptr(qt_internal_proc));
|
||||
className = new wchar_t[qClassName.size() + 1];
|
||||
qClassName.toWCharArray(className);
|
||||
className[qClassName.size()] = 0;
|
||||
|
||||
WNDCLASS wc;
|
||||
wc.style = 0;
|
||||
wc.lpfnWndProc = qt_internal_proc;
|
||||
wc.cbClsExtra = 0;
|
||||
wc.cbWndExtra = 0;
|
||||
wc.hInstance = GetModuleHandle(0);
|
||||
wc.hIcon = 0;
|
||||
wc.hCursor = 0;
|
||||
wc.hbrBackground = 0;
|
||||
wc.lpszMenuName = NULL;
|
||||
wc.lpszClassName = className;
|
||||
atom = RegisterClass(&wc);
|
||||
if (!atom) {
|
||||
qErrnoWarning("%ls RegisterClass() failed", qUtf16Printable(qClassName));
|
||||
delete [] className;
|
||||
className = 0;
|
||||
}
|
||||
}
|
||||
|
||||
QWindowsMessageWindowClassContext::~QWindowsMessageWindowClassContext()
|
||||
{
|
||||
if (className) {
|
||||
UnregisterClass(className, GetModuleHandle(0));
|
||||
delete [] className;
|
||||
}
|
||||
}
|
||||
|
||||
Q_GLOBAL_STATIC(QWindowsMessageWindowClassContext, qWindowsMessageWindowClassContext)
|
||||
|
||||
static HWND qt_create_internal_window(const QEventDispatcherWin32 *eventDispatcher)
|
||||
{
|
||||
QWindowsMessageWindowClassContext *ctx = qWindowsMessageWindowClassContext();
|
||||
if (!ctx->atom)
|
||||
return 0;
|
||||
HWND wnd = CreateWindow(ctx->className, // classname
|
||||
ctx->className, // window name
|
||||
0, // style
|
||||
0, 0, 0, 0, // geometry
|
||||
HWND_MESSAGE, // parent
|
||||
0, // menu handle
|
||||
GetModuleHandle(0), // application
|
||||
0); // windows creation data.
|
||||
|
||||
if (!wnd) {
|
||||
qErrnoWarning("CreateWindow() for QEventDispatcherWin32 internal window failed");
|
||||
return 0;
|
||||
}
|
||||
|
||||
SetWindowLongPtr(wnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(eventDispatcher));
|
||||
|
||||
return wnd;
|
||||
}
|
||||
|
||||
static ULONG calculateNextTimeout(WinTimerInfo *t, quint64 currentTime)
|
||||
{
|
||||
uint interval = t->interval;
|
||||
ULONG tolerance = TIMERV_DEFAULT_COALESCING;
|
||||
switch (t->timerType) {
|
||||
case Qt::PreciseTimer:
|
||||
// high precision timer is based on millisecond precision
|
||||
// so no adjustment is necessary
|
||||
break;
|
||||
|
||||
case Qt::CoarseTimer:
|
||||
// this timer has up to 5% coarseness
|
||||
// so our boundaries are 20 ms and 20 s
|
||||
// below 20 ms, 5% inaccuracy is below 1 ms, so we convert to high precision
|
||||
// above 20 s, 5% inaccuracy is above 1 s, so we convert to VeryCoarseTimer
|
||||
if (interval >= 20000) {
|
||||
t->timerType = Qt::VeryCoarseTimer;
|
||||
} else if (interval <= 20) {
|
||||
// no adjustment necessary
|
||||
t->timerType = Qt::PreciseTimer;
|
||||
break;
|
||||
} else {
|
||||
tolerance = interval / 20;
|
||||
break;
|
||||
}
|
||||
Q_FALLTHROUGH();
|
||||
case Qt::VeryCoarseTimer:
|
||||
// the very coarse timer is based on full second precision,
|
||||
// so we round to closest second (but never to zero)
|
||||
tolerance = 1000;
|
||||
if (interval < 1000)
|
||||
interval = 1000;
|
||||
else
|
||||
interval = (interval + 500) / 1000 * 1000;
|
||||
currentTime = currentTime / 1000 * 1000;
|
||||
break;
|
||||
}
|
||||
|
||||
t->interval = interval;
|
||||
t->timeout = currentTime + interval;
|
||||
return tolerance;
|
||||
}
|
||||
|
||||
void QEventDispatcherWin32Private::registerTimer(WinTimerInfo *t)
|
||||
{
|
||||
Q_ASSERT(internalHwnd);
|
||||
|
||||
Q_Q(QEventDispatcherWin32);
|
||||
|
||||
bool ok = false;
|
||||
ULONG tolerance = calculateNextTimeout(t, qt_msectime());
|
||||
uint interval = t->interval;
|
||||
if (interval == 0u) {
|
||||
// optimization for single-shot-zero-timer
|
||||
QCoreApplication::postEvent(q, new QZeroTimerEvent(t->timerId));
|
||||
ok = true;
|
||||
} else if (tolerance == TIMERV_DEFAULT_COALESCING) {
|
||||
// 3/2016: Although MSDN states timeSetEvent() is deprecated, the function
|
||||
// is still deemed to be the most reliable precision timer.
|
||||
t->fastTimerId = timeSetEvent(interval, 1, qt_fast_timer_proc, DWORD_PTR(t),
|
||||
TIME_CALLBACK_FUNCTION | TIME_PERIODIC | TIME_KILL_SYNCHRONOUS);
|
||||
ok = t->fastTimerId;
|
||||
}
|
||||
|
||||
if (!ok) {
|
||||
// user normal timers for (Very)CoarseTimers, or if no more multimedia timers available
|
||||
ok = SetCoalescableTimer(internalHwnd, t->timerId, interval, nullptr, tolerance);
|
||||
}
|
||||
if (!ok)
|
||||
ok = SetTimer(internalHwnd, t->timerId, interval, nullptr);
|
||||
|
||||
if (!ok)
|
||||
qErrnoWarning("QEventDispatcherWin32::registerTimer: Failed to create a timer");
|
||||
}
|
||||
|
||||
void QEventDispatcherWin32Private::unregisterTimer(WinTimerInfo *t)
|
||||
{
|
||||
if (t->interval == 0) {
|
||||
QCoreApplicationPrivate::removePostedTimerEvent(t->dispatcher, t->timerId);
|
||||
} else if (t->fastTimerId != 0) {
|
||||
timeKillEvent(t->fastTimerId);
|
||||
QCoreApplicationPrivate::removePostedTimerEvent(t->dispatcher, t->timerId);
|
||||
} else {
|
||||
KillTimer(internalHwnd, t->timerId);
|
||||
}
|
||||
t->timerId = -1;
|
||||
if (!t->inTimerEvent)
|
||||
delete t;
|
||||
}
|
||||
|
||||
void QEventDispatcherWin32Private::sendTimerEvent(int timerId)
|
||||
{
|
||||
WinTimerInfo *t = timerDict.value(timerId);
|
||||
if (t && !t->inTimerEvent) {
|
||||
// send event, but don't allow it to recurse
|
||||
t->inTimerEvent = true;
|
||||
|
||||
// recalculate next emission
|
||||
calculateNextTimeout(t, qt_msectime());
|
||||
|
||||
QTimerEvent e(t->timerId);
|
||||
QCoreApplication::sendEvent(t->obj, &e);
|
||||
|
||||
// timer could have been removed
|
||||
if (t->timerId == -1) {
|
||||
delete t;
|
||||
} else {
|
||||
t->inTimerEvent = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void QEventDispatcherWin32Private::doWsaAsyncSelect(int socket, long event)
|
||||
{
|
||||
Q_ASSERT(internalHwnd);
|
||||
// BoundsChecker may emit a warning for WSAAsyncSelect when event == 0
|
||||
// This is a BoundsChecker bug and not a Qt bug
|
||||
WSAAsyncSelect(socket, internalHwnd, event ? int(WM_QT_SOCKETNOTIFIER) : 0, event);
|
||||
}
|
||||
|
||||
void QEventDispatcherWin32Private::postActivateSocketNotifiers()
|
||||
{
|
||||
if (!activateNotifiersPosted)
|
||||
activateNotifiersPosted = PostMessage(internalHwnd, WM_QT_ACTIVATENOTIFIERS, 0, 0);
|
||||
}
|
||||
|
||||
QEventDispatcherWin32::QEventDispatcherWin32(QObject *parent)
|
||||
: QEventDispatcherWin32(*new QEventDispatcherWin32Private, parent)
|
||||
{
|
||||
}
|
||||
|
||||
QEventDispatcherWin32::QEventDispatcherWin32(QEventDispatcherWin32Private &dd, QObject *parent)
|
||||
: QAbstractEventDispatcher(dd, parent)
|
||||
{
|
||||
Q_D(QEventDispatcherWin32);
|
||||
|
||||
d->internalHwnd = qt_create_internal_window(this);
|
||||
}
|
||||
|
||||
QEventDispatcherWin32::~QEventDispatcherWin32()
|
||||
{
|
||||
}
|
||||
|
||||
static bool isUserInputMessage(UINT message)
|
||||
{
|
||||
return (message >= WM_KEYFIRST && message <= WM_KEYLAST)
|
||||
|| (message >= WM_MOUSEFIRST && message <= WM_MOUSELAST)
|
||||
|| message == WM_MOUSEWHEEL
|
||||
|| message == WM_MOUSEHWHEEL
|
||||
|| message == WM_TOUCH
|
||||
#ifndef QT_NO_GESTURES
|
||||
|| message == WM_GESTURE
|
||||
|| message == WM_GESTURENOTIFY
|
||||
#endif
|
||||
// Pointer input: Exclude WM_NCPOINTERUPDATE .. WM_POINTERROUTEDRELEASED
|
||||
|| (message >= 0x0241 && message <= 0x0253)
|
||||
|| message == WM_CLOSE;
|
||||
}
|
||||
|
||||
bool QEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlags flags)
|
||||
{
|
||||
Q_D(QEventDispatcherWin32);
|
||||
|
||||
// We don't know _when_ the interrupt occurred so we have to honor it.
|
||||
const bool wasInterrupted = d->interrupt.fetchAndStoreRelaxed(false);
|
||||
emit awake();
|
||||
|
||||
// To prevent livelocks, send posted events once per iteration.
|
||||
// QCoreApplication::sendPostedEvents() takes care about recursions.
|
||||
sendPostedEvents();
|
||||
|
||||
if (wasInterrupted)
|
||||
return false;
|
||||
|
||||
auto threadData = d->threadData.loadRelaxed();
|
||||
bool canWait;
|
||||
bool retVal = false;
|
||||
do {
|
||||
QVarLengthArray<MSG> processedTimers;
|
||||
while (!d->interrupt.loadRelaxed()) {
|
||||
MSG msg;
|
||||
|
||||
if (!(flags & QEventLoop::ExcludeUserInputEvents) && !d->queuedUserInputEvents.isEmpty()) {
|
||||
// process queued user input events
|
||||
msg = d->queuedUserInputEvents.takeFirst();
|
||||
} else if (!(flags & QEventLoop::ExcludeSocketNotifiers) && !d->queuedSocketEvents.isEmpty()) {
|
||||
// process queued socket events
|
||||
msg = d->queuedSocketEvents.takeFirst();
|
||||
} else if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) {
|
||||
if (flags.testFlag(QEventLoop::ExcludeUserInputEvents)
|
||||
&& isUserInputMessage(msg.message)) {
|
||||
// queue user input events for later processing
|
||||
d->queuedUserInputEvents.append(msg);
|
||||
continue;
|
||||
}
|
||||
if ((flags & QEventLoop::ExcludeSocketNotifiers)
|
||||
&& (msg.message == WM_QT_SOCKETNOTIFIER && msg.hwnd == d->internalHwnd)) {
|
||||
// queue socket events for later processing
|
||||
d->queuedSocketEvents.append(msg);
|
||||
continue;
|
||||
}
|
||||
} else if (MsgWaitForMultipleObjectsEx(0, NULL, 0, QS_ALLINPUT, MWMO_ALERTABLE)
|
||||
== WAIT_OBJECT_0) {
|
||||
// a new message has arrived, process it
|
||||
continue;
|
||||
} else {
|
||||
// nothing to do, so break
|
||||
break;
|
||||
}
|
||||
|
||||
if (d->internalHwnd == msg.hwnd && msg.message == WM_QT_SENDPOSTEDEVENTS) {
|
||||
d->startPostedEventsTimer();
|
||||
// Set result to 'true' because the message was sent by wakeUp().
|
||||
retVal = true;
|
||||
continue;
|
||||
}
|
||||
if (msg.message == WM_TIMER) {
|
||||
// Skip timer event intended for use inside foreign loop.
|
||||
if (d->internalHwnd == msg.hwnd && msg.wParam == d->sendPostedEventsTimerId)
|
||||
continue;
|
||||
|
||||
// avoid live-lock by keeping track of the timers we've already sent
|
||||
bool found = false;
|
||||
for (int i = 0; !found && i < processedTimers.count(); ++i) {
|
||||
const MSG processed = processedTimers.constData()[i];
|
||||
found = (processed.wParam == msg.wParam && processed.hwnd == msg.hwnd && processed.lParam == msg.lParam);
|
||||
}
|
||||
if (found)
|
||||
continue;
|
||||
processedTimers.append(msg);
|
||||
} else if (msg.message == WM_QUIT) {
|
||||
if (QCoreApplication::instance())
|
||||
QCoreApplication::instance()->quit();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!filterNativeEvent(QByteArrayLiteral("windows_generic_MSG"), &msg, 0)) {
|
||||
TranslateMessage(&msg);
|
||||
DispatchMessage(&msg);
|
||||
}
|
||||
retVal = true;
|
||||
}
|
||||
|
||||
// wait for message
|
||||
canWait = (!retVal
|
||||
&& !d->interrupt.loadRelaxed()
|
||||
&& flags.testFlag(QEventLoop::WaitForMoreEvents)
|
||||
&& threadData->canWaitLocked());
|
||||
if (canWait) {
|
||||
emit aboutToBlock();
|
||||
MsgWaitForMultipleObjectsEx(0, NULL, INFINITE, QS_ALLINPUT, MWMO_ALERTABLE | MWMO_INPUTAVAILABLE);
|
||||
emit awake();
|
||||
}
|
||||
} while (canWait);
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
void QEventDispatcherWin32::registerSocketNotifier(QSocketNotifier *notifier)
|
||||
{
|
||||
Q_ASSERT(notifier);
|
||||
int sockfd = notifier->socket();
|
||||
int type = notifier->type();
|
||||
#ifndef QT_NO_DEBUG
|
||||
if (sockfd < 0) {
|
||||
qWarning("QEventDispatcherWin32::registerSocketNotifier: invalid socket identifier");
|
||||
return;
|
||||
}
|
||||
if (notifier->thread() != thread() || thread() != QThread::currentThread()) {
|
||||
qWarning("QEventDispatcherWin32: socket notifiers cannot be enabled from another thread");
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
Q_D(QEventDispatcherWin32);
|
||||
QSNDict *sn_vec[3] = { &d->sn_read, &d->sn_write, &d->sn_except };
|
||||
QSNDict *dict = sn_vec[type];
|
||||
|
||||
if (QCoreApplication::closingDown()) // ### d->exitloop?
|
||||
return; // after sn_cleanup, don't reinitialize.
|
||||
|
||||
if (dict->contains(sockfd)) {
|
||||
const char *t[] = { "Read", "Write", "Exception" };
|
||||
/* Variable "socket" below is a function pointer. */
|
||||
qWarning("QSocketNotifier: Multiple socket notifiers for "
|
||||
"same socket %d and type %s", sockfd, t[type]);
|
||||
}
|
||||
|
||||
QSockNot *sn = new QSockNot;
|
||||
sn->obj = notifier;
|
||||
sn->fd = sockfd;
|
||||
dict->insert(sn->fd, sn);
|
||||
|
||||
long event = 0;
|
||||
if (d->sn_read.contains(sockfd))
|
||||
event |= FD_READ | FD_CLOSE | FD_ACCEPT;
|
||||
if (d->sn_write.contains(sockfd))
|
||||
event |= FD_WRITE | FD_CONNECT;
|
||||
if (d->sn_except.contains(sockfd))
|
||||
event |= FD_OOB;
|
||||
|
||||
QSFDict::iterator it = d->active_fd.find(sockfd);
|
||||
if (it != d->active_fd.end()) {
|
||||
QSockFd &sd = it.value();
|
||||
if (sd.selected) {
|
||||
d->doWsaAsyncSelect(sockfd, 0);
|
||||
sd.selected = false;
|
||||
}
|
||||
sd.event |= event;
|
||||
} else {
|
||||
// Although WSAAsyncSelect(..., 0), which is called from
|
||||
// unregisterSocketNotifier(), immediately disables event message
|
||||
// posting for the socket, it is possible that messages could be
|
||||
// waiting in the application message queue even if the socket was
|
||||
// closed. Also, some events could be implicitly re-enabled due
|
||||
// to system calls. Ignore these superfluous events until all
|
||||
// pending notifications have been suppressed. Next activation of
|
||||
// socket notifiers will reset the mask.
|
||||
d->active_fd.insert(sockfd, QSockFd(event, FD_READ | FD_CLOSE | FD_ACCEPT | FD_WRITE
|
||||
| FD_CONNECT | FD_OOB));
|
||||
}
|
||||
|
||||
d->postActivateSocketNotifiers();
|
||||
}
|
||||
|
||||
void QEventDispatcherWin32::unregisterSocketNotifier(QSocketNotifier *notifier)
|
||||
{
|
||||
Q_ASSERT(notifier);
|
||||
#ifndef QT_NO_DEBUG
|
||||
int sockfd = notifier->socket();
|
||||
if (sockfd < 0) {
|
||||
qWarning("QEventDispatcherWin32::unregisterSocketNotifier: invalid socket identifier");
|
||||
return;
|
||||
}
|
||||
if (notifier->thread() != thread() || thread() != QThread::currentThread()) {
|
||||
qWarning("QEventDispatcherWin32: socket notifiers cannot be disabled from another thread");
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
doUnregisterSocketNotifier(notifier);
|
||||
}
|
||||
|
||||
void QEventDispatcherWin32::doUnregisterSocketNotifier(QSocketNotifier *notifier)
|
||||
{
|
||||
Q_D(QEventDispatcherWin32);
|
||||
int type = notifier->type();
|
||||
int sockfd = notifier->socket();
|
||||
Q_ASSERT(sockfd >= 0);
|
||||
|
||||
QSFDict::iterator it = d->active_fd.find(sockfd);
|
||||
if (it != d->active_fd.end()) {
|
||||
QSockFd &sd = it.value();
|
||||
if (sd.selected)
|
||||
d->doWsaAsyncSelect(sockfd, 0);
|
||||
const long event[3] = { FD_READ | FD_CLOSE | FD_ACCEPT, FD_WRITE | FD_CONNECT, FD_OOB };
|
||||
sd.event ^= event[type];
|
||||
if (sd.event == 0) {
|
||||
d->active_fd.erase(it);
|
||||
} else if (sd.selected) {
|
||||
sd.selected = false;
|
||||
d->postActivateSocketNotifiers();
|
||||
}
|
||||
}
|
||||
|
||||
QSNDict *sn_vec[3] = { &d->sn_read, &d->sn_write, &d->sn_except };
|
||||
QSNDict *dict = sn_vec[type];
|
||||
QSockNot *sn = dict->value(sockfd);
|
||||
if (!sn)
|
||||
return;
|
||||
|
||||
dict->remove(sockfd);
|
||||
delete sn;
|
||||
}
|
||||
|
||||
void QEventDispatcherWin32::registerTimer(int timerId, qint64 interval, Qt::TimerType timerType, QObject *object)
|
||||
{
|
||||
#ifndef QT_NO_DEBUG
|
||||
if (timerId < 1 || interval < 0 || !object) {
|
||||
qWarning("QEventDispatcherWin32::registerTimer: invalid arguments");
|
||||
return;
|
||||
}
|
||||
if (object->thread() != thread() || thread() != QThread::currentThread()) {
|
||||
qWarning("QEventDispatcherWin32::registerTimer: timers cannot be started from another thread");
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
Q_D(QEventDispatcherWin32);
|
||||
|
||||
// exiting ... do not register new timers
|
||||
// (QCoreApplication::closingDown() is set too late to be used here)
|
||||
if (d->closingDown)
|
||||
return;
|
||||
|
||||
WinTimerInfo *t = new WinTimerInfo;
|
||||
t->dispatcher = this;
|
||||
t->timerId = timerId;
|
||||
t->interval = interval;
|
||||
t->timerType = timerType;
|
||||
t->obj = object;
|
||||
t->inTimerEvent = false;
|
||||
t->fastTimerId = 0;
|
||||
|
||||
d->registerTimer(t);
|
||||
|
||||
d->timerDict.insert(t->timerId, t); // store timers in dict
|
||||
}
|
||||
|
||||
bool QEventDispatcherWin32::unregisterTimer(int timerId)
|
||||
{
|
||||
#ifndef QT_NO_DEBUG
|
||||
if (timerId < 1) {
|
||||
qWarning("QEventDispatcherWin32::unregisterTimer: invalid argument");
|
||||
return false;
|
||||
}
|
||||
if (thread() != QThread::currentThread()) {
|
||||
qWarning("QEventDispatcherWin32::unregisterTimer: timers cannot be stopped from another thread");
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
Q_D(QEventDispatcherWin32);
|
||||
|
||||
WinTimerInfo *t = d->timerDict.take(timerId);
|
||||
if (!t)
|
||||
return false;
|
||||
|
||||
d->unregisterTimer(t);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool QEventDispatcherWin32::unregisterTimers(QObject *object)
|
||||
{
|
||||
#ifndef QT_NO_DEBUG
|
||||
if (!object) {
|
||||
qWarning("QEventDispatcherWin32::unregisterTimers: invalid argument");
|
||||
return false;
|
||||
}
|
||||
if (object->thread() != thread() || thread() != QThread::currentThread()) {
|
||||
qWarning("QEventDispatcherWin32::unregisterTimers: timers cannot be stopped from another thread");
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
Q_D(QEventDispatcherWin32);
|
||||
if (d->timerDict.isEmpty())
|
||||
return false;
|
||||
|
||||
auto it = d->timerDict.begin();
|
||||
while (it != d->timerDict.end()) {
|
||||
WinTimerInfo *t = it.value();
|
||||
Q_ASSERT(t);
|
||||
if (t->obj == object) {
|
||||
it = d->timerDict.erase(it);
|
||||
d->unregisterTimer(t);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
QList<QEventDispatcherWin32::TimerInfo>
|
||||
QEventDispatcherWin32::registeredTimers(QObject *object) const
|
||||
{
|
||||
#ifndef QT_NO_DEBUG
|
||||
if (!object) {
|
||||
qWarning("QEventDispatcherWin32:registeredTimers: invalid argument");
|
||||
return QList<TimerInfo>();
|
||||
}
|
||||
#endif
|
||||
|
||||
Q_D(const QEventDispatcherWin32);
|
||||
QList<TimerInfo> list;
|
||||
for (WinTimerInfo *t : std::as_const(d->timerDict)) {
|
||||
Q_ASSERT(t);
|
||||
if (t->obj == object)
|
||||
list << TimerInfo(t->timerId, t->interval, t->timerType);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
int QEventDispatcherWin32::remainingTime(int timerId)
|
||||
{
|
||||
#ifndef QT_NO_DEBUG
|
||||
if (timerId < 1) {
|
||||
qWarning("QEventDispatcherWin32::remainingTime: invalid argument");
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
Q_D(QEventDispatcherWin32);
|
||||
|
||||
quint64 currentTime = qt_msectime();
|
||||
|
||||
WinTimerInfo *t = d->timerDict.value(timerId);
|
||||
if (t) {
|
||||
// timer found, return time to wait
|
||||
return t->timeout > currentTime ? t->timeout - currentTime : 0;
|
||||
}
|
||||
|
||||
#ifndef QT_NO_DEBUG
|
||||
qWarning("QEventDispatcherWin32::remainingTime: timer id %d not found", timerId);
|
||||
#endif
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void QEventDispatcherWin32::wakeUp()
|
||||
{
|
||||
Q_D(QEventDispatcherWin32);
|
||||
if (d->wakeUps.testAndSetRelaxed(0, 1)) {
|
||||
// post a WM_QT_SENDPOSTEDEVENTS to this thread if there isn't one already pending
|
||||
if (!PostMessage(d->internalHwnd, WM_QT_SENDPOSTEDEVENTS, 0, 0))
|
||||
qErrnoWarning("QEventDispatcherWin32::wakeUp: Failed to post a message");
|
||||
}
|
||||
}
|
||||
|
||||
void QEventDispatcherWin32::interrupt()
|
||||
{
|
||||
Q_D(QEventDispatcherWin32);
|
||||
d->interrupt.storeRelaxed(true);
|
||||
wakeUp();
|
||||
}
|
||||
|
||||
void QEventDispatcherWin32::startingUp()
|
||||
{ }
|
||||
|
||||
void QEventDispatcherWin32::closingDown()
|
||||
{
|
||||
Q_D(QEventDispatcherWin32);
|
||||
|
||||
// clean up any socketnotifiers
|
||||
while (!d->sn_read.isEmpty())
|
||||
doUnregisterSocketNotifier((*(d->sn_read.begin()))->obj);
|
||||
while (!d->sn_write.isEmpty())
|
||||
doUnregisterSocketNotifier((*(d->sn_write.begin()))->obj);
|
||||
while (!d->sn_except.isEmpty())
|
||||
doUnregisterSocketNotifier((*(d->sn_except.begin()))->obj);
|
||||
Q_ASSERT(d->active_fd.isEmpty());
|
||||
|
||||
// clean up any timers
|
||||
for (WinTimerInfo *t : std::as_const(d->timerDict))
|
||||
d->unregisterTimer(t);
|
||||
d->timerDict.clear();
|
||||
|
||||
d->closingDown = true;
|
||||
|
||||
if (d->sendPostedEventsTimerId != 0)
|
||||
KillTimer(d->internalHwnd, d->sendPostedEventsTimerId);
|
||||
d->sendPostedEventsTimerId = 0;
|
||||
}
|
||||
|
||||
bool QEventDispatcherWin32::event(QEvent *e)
|
||||
{
|
||||
Q_D(QEventDispatcherWin32);
|
||||
switch (e->type()) {
|
||||
case QEvent::ZeroTimerEvent: {
|
||||
QZeroTimerEvent *zte = static_cast<QZeroTimerEvent*>(e);
|
||||
WinTimerInfo *t = d->timerDict.value(zte->timerId());
|
||||
if (t) {
|
||||
t->inTimerEvent = true;
|
||||
|
||||
QTimerEvent te(zte->timerId());
|
||||
QCoreApplication::sendEvent(t->obj, &te);
|
||||
|
||||
// timer could have been removed
|
||||
if (t->timerId == -1) {
|
||||
delete t;
|
||||
} else {
|
||||
if (t->interval == 0 && t->inTimerEvent) {
|
||||
// post the next zero timer event as long as the timer was not restarted
|
||||
QCoreApplication::postEvent(this, new QZeroTimerEvent(zte->timerId()));
|
||||
}
|
||||
|
||||
t->inTimerEvent = false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case QEvent::Timer:
|
||||
d->sendTimerEvent(static_cast<const QTimerEvent*>(e)->timerId());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return QAbstractEventDispatcher::event(e);
|
||||
}
|
||||
|
||||
void QEventDispatcherWin32::sendPostedEvents()
|
||||
{
|
||||
Q_D(QEventDispatcherWin32);
|
||||
|
||||
if (d->sendPostedEventsTimerId != 0)
|
||||
KillTimer(d->internalHwnd, d->sendPostedEventsTimerId);
|
||||
d->sendPostedEventsTimerId = 0;
|
||||
|
||||
// Allow posting WM_QT_SENDPOSTEDEVENTS message.
|
||||
d->wakeUps.storeRelaxed(0);
|
||||
|
||||
QCoreApplicationPrivate::sendPostedEvents(0, 0, d->threadData.loadRelaxed());
|
||||
}
|
||||
|
||||
HWND QEventDispatcherWin32::internalHwnd()
|
||||
{
|
||||
return d_func()->internalHwnd;
|
||||
}
|
||||
|
||||
QT_END_NAMESPACE
|
65
qtbase/src/corelib/kernel/qfunctions_win.cpp
Normal file
65
qtbase/src/corelib/kernel/qfunctions_win.cpp
Normal file
@ -0,0 +1,65 @@
|
||||
// Copyright (C) 2022 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
|
||||
|
||||
#include "qfunctions_win_p.h"
|
||||
|
||||
#include <QtCore/qdebug.h>
|
||||
|
||||
#include <combaseapi.h>
|
||||
#include <objbase.h>
|
||||
|
||||
#if __has_include(<appmodel.h>)
|
||||
# include <appmodel.h>
|
||||
# define HAS_APPMODEL
|
||||
#endif
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
QComHelper::QComHelper(COINIT concurrencyModel)
|
||||
{
|
||||
// Avoid overhead of initializing and using obsolete technology
|
||||
concurrencyModel = COINIT(concurrencyModel | COINIT_DISABLE_OLE1DDE);
|
||||
|
||||
m_initResult = CoInitializeEx(nullptr, concurrencyModel);
|
||||
|
||||
if (FAILED(m_initResult))
|
||||
qErrnoWarning(m_initResult, "Failed to initialize COM library");
|
||||
}
|
||||
|
||||
QComHelper::~QComHelper()
|
||||
{
|
||||
if (SUCCEEDED(m_initResult))
|
||||
CoUninitialize();
|
||||
}
|
||||
|
||||
/*!
|
||||
\internal
|
||||
Checks if the application has a \e{package identity}
|
||||
|
||||
Having a \e{package identity} is required to use many modern
|
||||
Windows APIs.
|
||||
|
||||
https://docs.microsoft.com/en-us/windows/apps/desktop/modernize/modernize-packaged-apps
|
||||
*/
|
||||
bool qt_win_hasPackageIdentity()
|
||||
{
|
||||
#if defined(HAS_APPMODEL)
|
||||
static const bool hasPackageIdentity = []() {
|
||||
UINT32 length = 0;
|
||||
switch (const auto result = GetCurrentPackageFullName(&length, nullptr)) {
|
||||
case ERROR_INSUFFICIENT_BUFFER:
|
||||
return true;
|
||||
case APPMODEL_ERROR_NO_PACKAGE:
|
||||
return false;
|
||||
default:
|
||||
qWarning("Failed to resolve package identity (error code %ld)", result);
|
||||
return false;
|
||||
}
|
||||
}();
|
||||
return hasPackageIdentity;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
QT_END_NAMESPACE
|
150
qtbase/src/corelib/thread/qfutex_p.h
Normal file
150
qtbase/src/corelib/thread/qfutex_p.h
Normal file
@ -0,0 +1,150 @@
|
||||
// Copyright (C) 2017 Intel Corporation.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
|
||||
|
||||
#ifndef QFUTEX_P_H
|
||||
#define QFUTEX_P_H
|
||||
|
||||
//
|
||||
// W A R N I N G
|
||||
// -------------
|
||||
//
|
||||
// This file is not part of the Qt API. It exists purely as an
|
||||
// implementation detail. This header file may change from version to
|
||||
// version without notice, or even be removed.
|
||||
//
|
||||
// We mean it.
|
||||
//
|
||||
|
||||
#include <private/qglobal_p.h>
|
||||
#include <QtCore/qtsan_impl.h>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
namespace QtDummyFutex {
|
||||
constexpr inline bool futexAvailable() { return false; }
|
||||
template <typename Atomic>
|
||||
inline bool futexWait(Atomic &, typename Atomic::Type, int = 0)
|
||||
{ Q_UNREACHABLE_RETURN(false); }
|
||||
template <typename Atomic> inline void futexWakeOne(Atomic &)
|
||||
{ Q_UNREACHABLE(); }
|
||||
template <typename Atomic> inline void futexWakeAll(Atomic &)
|
||||
{ Q_UNREACHABLE(); }
|
||||
}
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
#if defined(Q_OS_LINUX) && !defined(QT_LINUXBASE)
|
||||
// use Linux mutexes everywhere except for LSB builds
|
||||
# include <sys/syscall.h>
|
||||
# include <errno.h>
|
||||
# include <limits.h>
|
||||
# include <unistd.h>
|
||||
# include <asm/unistd.h>
|
||||
# include <linux/futex.h>
|
||||
# define QT_ALWAYS_USE_FUTEX
|
||||
|
||||
// if not defined in linux/futex.h
|
||||
# define FUTEX_PRIVATE_FLAG 128 // added in v2.6.22
|
||||
|
||||
// RISC-V does not supply __NR_futex
|
||||
# ifndef __NR_futex
|
||||
# define __NR_futex __NR_futex_time64
|
||||
# endif
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
namespace QtLinuxFutex {
|
||||
constexpr inline bool futexAvailable() { return true; }
|
||||
inline int _q_futex(int *addr, int op, int val, quintptr val2 = 0,
|
||||
int *addr2 = nullptr, int val3 = 0) noexcept
|
||||
{
|
||||
QtTsan::futexRelease(addr, addr2);
|
||||
|
||||
// we use __NR_futex because some libcs (like Android's bionic) don't
|
||||
// provide SYS_futex etc.
|
||||
int result = syscall(__NR_futex, addr, op | FUTEX_PRIVATE_FLAG, val, val2, addr2, val3);
|
||||
|
||||
QtTsan::futexAcquire(addr, addr2);
|
||||
|
||||
return result;
|
||||
}
|
||||
template <typename T> int *addr(T *ptr)
|
||||
{
|
||||
int *int_addr = reinterpret_cast<int *>(ptr);
|
||||
#if Q_BYTE_ORDER == Q_BIG_ENDIAN
|
||||
if (sizeof(T) > sizeof(int))
|
||||
int_addr++; //We want a pointer to the least significant half
|
||||
#endif
|
||||
return int_addr;
|
||||
}
|
||||
|
||||
template <typename Atomic>
|
||||
inline void futexWait(Atomic &futex, typename Atomic::Type expectedValue)
|
||||
{
|
||||
_q_futex(addr(&futex), FUTEX_WAIT, qintptr(expectedValue));
|
||||
}
|
||||
template <typename Atomic>
|
||||
inline bool futexWait(Atomic &futex, typename Atomic::Type expectedValue, qint64 nstimeout)
|
||||
{
|
||||
struct timespec ts;
|
||||
ts.tv_sec = nstimeout / 1000 / 1000 / 1000;
|
||||
ts.tv_nsec = nstimeout % (1000 * 1000 * 1000);
|
||||
int r = _q_futex(addr(&futex), FUTEX_WAIT, qintptr(expectedValue), quintptr(&ts));
|
||||
return r == 0 || errno != ETIMEDOUT;
|
||||
}
|
||||
template <typename Atomic> inline void futexWakeOne(Atomic &futex)
|
||||
{
|
||||
_q_futex(addr(&futex), FUTEX_WAKE, 1);
|
||||
}
|
||||
template <typename Atomic> inline void futexWakeAll(Atomic &futex)
|
||||
{
|
||||
_q_futex(addr(&futex), FUTEX_WAKE, INT_MAX);
|
||||
}
|
||||
template <typename Atomic> inline
|
||||
void futexWakeOp(Atomic &futex1, int wake1, int wake2, Atomic &futex2, quint32 op)
|
||||
{
|
||||
_q_futex(addr(&futex1), FUTEX_WAKE_OP, wake1, wake2, addr(&futex2), op);
|
||||
}
|
||||
}
|
||||
namespace QtFutex = QtLinuxFutex;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
#elif defined(Q_OS_WIN)
|
||||
# include <qt_windows.h>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
namespace QtWindowsFutex {
|
||||
#define QT_ALWAYS_USE_FUTEX
|
||||
constexpr inline bool futexAvailable() { return true; }
|
||||
|
||||
template <typename Atomic>
|
||||
inline void futexWait(Atomic &futex, typename Atomic::Type expectedValue)
|
||||
{
|
||||
QtTsan::futexRelease(&futex);
|
||||
WaitOnAddress(&futex, &expectedValue, sizeof(expectedValue), INFINITE);
|
||||
QtTsan::futexAcquire(&futex);
|
||||
}
|
||||
template <typename Atomic>
|
||||
inline bool futexWait(Atomic &futex, typename Atomic::Type expectedValue, qint64 nstimeout)
|
||||
{
|
||||
BOOL r = WaitOnAddress(&futex, &expectedValue, sizeof(expectedValue), DWORD(nstimeout / 1000 / 1000));
|
||||
return r || GetLastError() != ERROR_TIMEOUT;
|
||||
}
|
||||
template <typename Atomic> inline void futexWakeAll(Atomic &futex)
|
||||
{
|
||||
WakeByAddressAll(&futex);
|
||||
}
|
||||
template <typename Atomic> inline void futexWakeOne(Atomic &futex)
|
||||
{
|
||||
WakeByAddressSingle(&futex);
|
||||
}
|
||||
}
|
||||
namespace QtFutex = QtWindowsFutex;
|
||||
QT_END_NAMESPACE
|
||||
#else
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
namespace QtFutex = QtDummyFutex;
|
||||
QT_END_NAMESPACE
|
||||
#endif
|
||||
|
||||
#endif // QFUTEX_P_H
|
922
qtbase/src/corelib/thread/qmutex.cpp
Normal file
922
qtbase/src/corelib/thread/qmutex.cpp
Normal file
@ -0,0 +1,922 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// Copyright (C) 2016 Intel Corporation.
|
||||
// Copyright (C) 2012 Olivier Goffart <ogoffart@woboq.com>
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
|
||||
|
||||
#include "global/qglobal.h"
|
||||
#include "qplatformdefs.h"
|
||||
#include "qmutex.h"
|
||||
#include <qdebug.h>
|
||||
#include "qatomic.h"
|
||||
#include "qelapsedtimer.h"
|
||||
#include "qfutex_p.h"
|
||||
#include "qthread.h"
|
||||
#include "qmutex_p.h"
|
||||
|
||||
#ifndef QT_ALWAYS_USE_FUTEX
|
||||
#include "private/qfreelist_p.h"
|
||||
#endif
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
using namespace QtFutex;
|
||||
static inline QMutexPrivate *dummyFutexValue()
|
||||
{
|
||||
return reinterpret_cast<QMutexPrivate *>(quintptr(3));
|
||||
}
|
||||
|
||||
/*
|
||||
\class QBasicMutex
|
||||
\inmodule QtCore
|
||||
\brief QMutex POD
|
||||
\internal
|
||||
|
||||
\ingroup thread
|
||||
|
||||
- Can be used as global static object.
|
||||
- Always non-recursive
|
||||
- Do not use tryLock with timeout > 0, else you can have a leak (see the ~QMutex destructor)
|
||||
*/
|
||||
|
||||
/*!
|
||||
\class QMutex
|
||||
\inmodule QtCore
|
||||
\brief The QMutex class provides access serialization between threads.
|
||||
|
||||
\threadsafe
|
||||
|
||||
\ingroup thread
|
||||
|
||||
The purpose of a QMutex is to protect an object, data structure or
|
||||
section of code so that only one thread can access it at a time
|
||||
(this is similar to the Java \c synchronized keyword). It is
|
||||
usually best to use a mutex with a QMutexLocker since this makes
|
||||
it easy to ensure that locking and unlocking are performed
|
||||
consistently.
|
||||
|
||||
For example, say there is a method that prints a message to the
|
||||
user on two lines:
|
||||
|
||||
\snippet code/src_corelib_thread_qmutex.cpp 0
|
||||
|
||||
If these two methods are called in succession, the following happens:
|
||||
|
||||
\snippet code/src_corelib_thread_qmutex.cpp 1
|
||||
|
||||
If these two methods are called simultaneously from two threads then the
|
||||
following sequence could result:
|
||||
|
||||
\snippet code/src_corelib_thread_qmutex.cpp 2
|
||||
|
||||
If we add a mutex, we should get the result we want:
|
||||
|
||||
\snippet code/src_corelib_thread_qmutex.cpp 3
|
||||
|
||||
Then only one thread can modify \c number at any given time and
|
||||
the result is correct. This is a trivial example, of course, but
|
||||
applies to any other case where things need to happen in a
|
||||
particular sequence.
|
||||
|
||||
When you call lock() in a thread, other threads that try to call
|
||||
lock() in the same place will block until the thread that got the
|
||||
lock calls unlock(). A non-blocking alternative to lock() is
|
||||
tryLock().
|
||||
|
||||
QMutex is optimized to be fast in the non-contended case. It
|
||||
will not allocate memory if there is no contention on that mutex.
|
||||
It is constructed and destroyed with almost no overhead,
|
||||
which means it is fine to have many mutexes as part of other classes.
|
||||
|
||||
\sa QRecursiveMutex, QMutexLocker, QReadWriteLock, QSemaphore, QWaitCondition
|
||||
*/
|
||||
|
||||
/*!
|
||||
\fn QMutex::QMutex()
|
||||
|
||||
Constructs a new mutex. The mutex is created in an unlocked state.
|
||||
*/
|
||||
|
||||
/*! \fn QMutex::~QMutex()
|
||||
|
||||
Destroys the mutex.
|
||||
|
||||
\warning Destroying a locked mutex may result in undefined behavior.
|
||||
*/
|
||||
void QBasicMutex::destroyInternal(QMutexPrivate *d)
|
||||
{
|
||||
if (!d)
|
||||
return;
|
||||
if (!futexAvailable()) {
|
||||
if (d != dummyLocked() && d->possiblyUnlocked.loadRelaxed() && tryLock()) {
|
||||
unlock();
|
||||
return;
|
||||
}
|
||||
}
|
||||
qWarning("QMutex: destroying locked mutex");
|
||||
}
|
||||
|
||||
/*! \fn void QMutex::lock()
|
||||
|
||||
Locks the mutex. If another thread has locked the mutex then this
|
||||
call will block until that thread has unlocked it.
|
||||
|
||||
Calling this function multiple times on the same mutex from the
|
||||
same thread will cause a \e dead-lock.
|
||||
|
||||
\sa unlock()
|
||||
*/
|
||||
|
||||
/*! \fn bool QMutex::tryLock(int timeout)
|
||||
|
||||
Attempts to lock the mutex. This function returns \c true if the lock
|
||||
was obtained; otherwise it returns \c false. If another thread has
|
||||
locked the mutex, this function will wait for at most \a timeout
|
||||
milliseconds for the mutex to become available.
|
||||
|
||||
Note: Passing a negative number as the \a timeout is equivalent to
|
||||
calling lock(), i.e. this function will wait forever until mutex
|
||||
can be locked if \a timeout is negative.
|
||||
|
||||
If the lock was obtained, the mutex must be unlocked with unlock()
|
||||
before another thread can successfully lock it.
|
||||
|
||||
Calling this function multiple times on the same mutex from the
|
||||
same thread will cause a \e dead-lock.
|
||||
|
||||
\sa lock(), unlock()
|
||||
*/
|
||||
|
||||
/*! \fn bool QMutex::tryLock(QDeadlineTimer timer)
|
||||
\since 6.6
|
||||
|
||||
Attempts to lock the mutex. This function returns \c true if the lock
|
||||
was obtained; otherwise it returns \c false. If another thread has
|
||||
locked the mutex, this function will wait until \a timer expires
|
||||
for the mutex to become available.
|
||||
|
||||
If the lock was obtained, the mutex must be unlocked with unlock()
|
||||
before another thread can successfully lock it.
|
||||
|
||||
Calling this function multiple times on the same mutex from the
|
||||
same thread will cause a \e dead-lock.
|
||||
|
||||
\sa lock(), unlock()
|
||||
*/
|
||||
|
||||
/*! \fn bool QMutex::tryLock()
|
||||
\overload
|
||||
|
||||
Attempts to lock the mutex. This function returns \c true if the lock
|
||||
was obtained; otherwise it returns \c false.
|
||||
|
||||
If the lock was obtained, the mutex must be unlocked with unlock()
|
||||
before another thread can successfully lock it.
|
||||
|
||||
Calling this function multiple times on the same mutex from the
|
||||
same thread will cause a \e dead-lock.
|
||||
|
||||
\sa lock(), unlock()
|
||||
*/
|
||||
|
||||
/*! \fn bool QMutex::try_lock()
|
||||
\since 5.8
|
||||
|
||||
Attempts to lock the mutex. This function returns \c true if the lock
|
||||
was obtained; otherwise it returns \c false.
|
||||
|
||||
This function is provided for compatibility with the Standard Library
|
||||
concept \c Lockable. It is equivalent to tryLock().
|
||||
*/
|
||||
|
||||
/*! \fn template <class Rep, class Period> bool QMutex::try_lock_for(std::chrono::duration<Rep, Period> duration)
|
||||
\since 5.8
|
||||
|
||||
Attempts to lock the mutex. This function returns \c true if the lock
|
||||
was obtained; otherwise it returns \c false. If another thread has
|
||||
locked the mutex, this function will wait for at least \a duration
|
||||
for the mutex to become available.
|
||||
|
||||
Note: Passing a negative duration as the \a duration is equivalent to
|
||||
calling try_lock(). This behavior differs from tryLock().
|
||||
|
||||
If the lock was obtained, the mutex must be unlocked with unlock()
|
||||
before another thread can successfully lock it.
|
||||
|
||||
Calling this function multiple times on the same mutex from the
|
||||
same thread will cause a \e dead-lock.
|
||||
|
||||
\sa lock(), unlock()
|
||||
*/
|
||||
|
||||
/*! \fn template<class Clock, class Duration> bool QMutex::try_lock_until(std::chrono::time_point<Clock, Duration> timePoint)
|
||||
\since 5.8
|
||||
|
||||
Attempts to lock the mutex. This function returns \c true if the lock
|
||||
was obtained; otherwise it returns \c false. If another thread has
|
||||
locked the mutex, this function will wait at least until \a timePoint
|
||||
for the mutex to become available.
|
||||
|
||||
Note: Passing a \a timePoint which has already passed is equivalent
|
||||
to calling try_lock(). This behavior differs from tryLock().
|
||||
|
||||
If the lock was obtained, the mutex must be unlocked with unlock()
|
||||
before another thread can successfully lock it.
|
||||
|
||||
Calling this function multiple times on the same mutex from the
|
||||
same thread will cause a \e dead-lock.
|
||||
|
||||
\sa lock(), unlock()
|
||||
*/
|
||||
|
||||
/*! \fn void QMutex::unlock()
|
||||
|
||||
Unlocks the mutex. Attempting to unlock a mutex in a different
|
||||
thread to the one that locked it results in an error. Unlocking a
|
||||
mutex that is not locked results in undefined behavior.
|
||||
|
||||
\sa lock()
|
||||
*/
|
||||
|
||||
/*!
|
||||
\class QRecursiveMutex
|
||||
\inmodule QtCore
|
||||
\since 5.14
|
||||
\brief The QRecursiveMutex class provides access serialization between threads.
|
||||
|
||||
\threadsafe
|
||||
|
||||
\ingroup thread
|
||||
|
||||
The QRecursiveMutex class is a mutex, like QMutex, with which it is
|
||||
API-compatible. It differs from QMutex by accepting lock() calls from
|
||||
the same thread any number of times. QMutex would deadlock in this situation.
|
||||
|
||||
QRecursiveMutex is much more expensive to construct and operate on, so
|
||||
use a plain QMutex whenever you can. Sometimes, one public function,
|
||||
however, calls another public function, and they both need to lock the
|
||||
same mutex. In this case, you have two options:
|
||||
|
||||
\list
|
||||
\li Factor the code that needs mutex protection into private functions,
|
||||
which assume that the mutex is held when they are called, and lock a
|
||||
plain QMutex in the public functions before you call the private
|
||||
implementation ones.
|
||||
\li Or use a recursive mutex, so it doesn't matter that the first public
|
||||
function has already locked the mutex when the second one wishes to do so.
|
||||
\endlist
|
||||
|
||||
\sa QMutex, QMutexLocker, QReadWriteLock, QSemaphore, QWaitCondition
|
||||
*/
|
||||
|
||||
/*! \fn QRecursiveMutex::QRecursiveMutex()
|
||||
|
||||
Constructs a new recursive mutex. The mutex is created in an unlocked state.
|
||||
|
||||
\sa lock(), unlock()
|
||||
*/
|
||||
|
||||
/*!
|
||||
Destroys the mutex.
|
||||
|
||||
\warning Destroying a locked mutex may result in undefined behavior.
|
||||
*/
|
||||
QRecursiveMutex::~QRecursiveMutex()
|
||||
{
|
||||
}
|
||||
|
||||
/*! \fn void QRecursiveMutex::lock()
|
||||
|
||||
Locks the mutex. If another thread has locked the mutex then this
|
||||
call will block until that thread has unlocked it.
|
||||
|
||||
Calling this function multiple times on the same mutex from the
|
||||
same thread is allowed.
|
||||
|
||||
\sa unlock()
|
||||
*/
|
||||
|
||||
/*!
|
||||
\fn QRecursiveMutex::tryLock(int timeout)
|
||||
|
||||
Attempts to lock the mutex. This function returns \c true if the lock
|
||||
was obtained; otherwise it returns \c false. If another thread has
|
||||
locked the mutex, this function will wait for at most \a timeout
|
||||
milliseconds for the mutex to become available.
|
||||
|
||||
Note: Passing a negative number as the \a timeout is equivalent to
|
||||
calling lock(), i.e. this function will wait forever until mutex
|
||||
can be locked if \a timeout is negative.
|
||||
|
||||
If the lock was obtained, the mutex must be unlocked with unlock()
|
||||
before another thread can successfully lock it.
|
||||
|
||||
Calling this function multiple times on the same mutex from the
|
||||
same thread is allowed.
|
||||
|
||||
\sa lock(), unlock()
|
||||
*/
|
||||
|
||||
/*!
|
||||
\since 6.6
|
||||
|
||||
Attempts to lock the mutex. This function returns \c true if the lock
|
||||
was obtained; otherwise it returns \c false. If another thread has
|
||||
locked the mutex, this function will wait until \a timeout expires
|
||||
for the mutex to become available.
|
||||
|
||||
If the lock was obtained, the mutex must be unlocked with unlock()
|
||||
before another thread can successfully lock it.
|
||||
|
||||
Calling this function multiple times on the same mutex from the
|
||||
same thread is allowed.
|
||||
|
||||
\sa lock(), unlock()
|
||||
*/
|
||||
bool QRecursiveMutex::tryLock(QDeadlineTimer timeout) QT_MUTEX_LOCK_NOEXCEPT
|
||||
{
|
||||
unsigned tsanFlags = QtTsan::MutexWriteReentrant | QtTsan::TryLock;
|
||||
QtTsan::mutexPreLock(this, tsanFlags);
|
||||
|
||||
Qt::HANDLE self = QThread::currentThreadId();
|
||||
if (owner.loadRelaxed() == self) {
|
||||
++count;
|
||||
Q_ASSERT_X(count != 0, "QMutex::lock", "Overflow in recursion counter");
|
||||
QtTsan::mutexPostLock(this, tsanFlags, 0);
|
||||
return true;
|
||||
}
|
||||
bool success = true;
|
||||
if (timeout.isForever()) {
|
||||
mutex.lock();
|
||||
} else {
|
||||
success = mutex.tryLock(timeout);
|
||||
}
|
||||
|
||||
if (success)
|
||||
owner.storeRelaxed(self);
|
||||
else
|
||||
tsanFlags |= QtTsan::TryLockFailed;
|
||||
|
||||
QtTsan::mutexPostLock(this, tsanFlags, 0);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
/*! \fn bool QRecursiveMutex::try_lock()
|
||||
\since 5.8
|
||||
|
||||
Attempts to lock the mutex. This function returns \c true if the lock
|
||||
was obtained; otherwise it returns \c false.
|
||||
|
||||
This function is provided for compatibility with the Standard Library
|
||||
concept \c Lockable. It is equivalent to tryLock().
|
||||
*/
|
||||
|
||||
/*! \fn template <class Rep, class Period> bool QRecursiveMutex::try_lock_for(std::chrono::duration<Rep, Period> duration)
|
||||
\since 5.8
|
||||
|
||||
Attempts to lock the mutex. This function returns \c true if the lock
|
||||
was obtained; otherwise it returns \c false. If another thread has
|
||||
locked the mutex, this function will wait for at least \a duration
|
||||
for the mutex to become available.
|
||||
|
||||
Note: Passing a negative duration as the \a duration is equivalent to
|
||||
calling try_lock(). This behavior differs from tryLock().
|
||||
|
||||
If the lock was obtained, the mutex must be unlocked with unlock()
|
||||
before another thread can successfully lock it.
|
||||
|
||||
Calling this function multiple times on the same mutex from the
|
||||
same thread is allowed.
|
||||
|
||||
\sa lock(), unlock()
|
||||
*/
|
||||
|
||||
/*! \fn template<class Clock, class Duration> bool QRecursiveMutex::try_lock_until(std::chrono::time_point<Clock, Duration> timePoint)
|
||||
\since 5.8
|
||||
|
||||
Attempts to lock the mutex. This function returns \c true if the lock
|
||||
was obtained; otherwise it returns \c false. If another thread has
|
||||
locked the mutex, this function will wait at least until \a timePoint
|
||||
for the mutex to become available.
|
||||
|
||||
Note: Passing a \a timePoint which has already passed is equivalent
|
||||
to calling try_lock(). This behavior differs from tryLock().
|
||||
|
||||
If the lock was obtained, the mutex must be unlocked with unlock()
|
||||
before another thread can successfully lock it.
|
||||
|
||||
Calling this function multiple times on the same mutex from the
|
||||
same thread is allowed.
|
||||
|
||||
\sa lock(), unlock()
|
||||
*/
|
||||
|
||||
/*!
|
||||
Unlocks the mutex. Attempting to unlock a mutex in a different
|
||||
thread to the one that locked it results in an error. Unlocking a
|
||||
mutex that is not locked results in undefined behavior.
|
||||
|
||||
\sa lock()
|
||||
*/
|
||||
void QRecursiveMutex::unlock() noexcept
|
||||
{
|
||||
Q_ASSERT(owner.loadRelaxed() == QThread::currentThreadId());
|
||||
QtTsan::mutexPreUnlock(this, 0u);
|
||||
|
||||
if (count > 0) {
|
||||
count--;
|
||||
} else {
|
||||
owner.storeRelaxed(nullptr);
|
||||
mutex.unlock();
|
||||
}
|
||||
|
||||
QtTsan::mutexPostUnlock(this, 0u);
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
\class QMutexLocker
|
||||
\inmodule QtCore
|
||||
\brief The QMutexLocker class is a convenience class that simplifies
|
||||
locking and unlocking mutexes.
|
||||
|
||||
\threadsafe
|
||||
|
||||
\ingroup thread
|
||||
|
||||
Locking and unlocking a QMutex or QRecursiveMutex in complex functions and
|
||||
statements or in exception handling code is error-prone and
|
||||
difficult to debug. QMutexLocker can be used in such situations
|
||||
to ensure that the state of the mutex is always well-defined.
|
||||
|
||||
QMutexLocker should be created within a function where a
|
||||
QMutex needs to be locked. The mutex is locked when QMutexLocker
|
||||
is created. You can unlock and relock the mutex with \c unlock()
|
||||
and \c relock(). If locked, the mutex will be unlocked when the
|
||||
QMutexLocker is destroyed.
|
||||
|
||||
For example, this complex function locks a QMutex upon entering
|
||||
the function and unlocks the mutex at all the exit points:
|
||||
|
||||
\snippet code/src_corelib_thread_qmutex.cpp 4
|
||||
|
||||
This example function will get more complicated as it is
|
||||
developed, which increases the likelihood that errors will occur.
|
||||
|
||||
Using QMutexLocker greatly simplifies the code, and makes it more
|
||||
readable:
|
||||
|
||||
\snippet code/src_corelib_thread_qmutex.cpp 5
|
||||
|
||||
Now, the mutex will always be unlocked when the QMutexLocker
|
||||
object is destroyed (when the function returns since \c locker is
|
||||
an auto variable).
|
||||
|
||||
The same principle applies to code that throws and catches
|
||||
exceptions. An exception that is not caught in the function that
|
||||
has locked the mutex has no way of unlocking the mutex before the
|
||||
exception is passed up the stack to the calling function.
|
||||
|
||||
QMutexLocker also provides a \c mutex() member function that returns
|
||||
the mutex on which the QMutexLocker is operating. This is useful
|
||||
for code that needs access to the mutex, such as
|
||||
QWaitCondition::wait(). For example:
|
||||
|
||||
\snippet code/src_corelib_thread_qmutex.cpp 6
|
||||
|
||||
\sa QReadLocker, QWriteLocker, QMutex
|
||||
*/
|
||||
|
||||
/*!
|
||||
\fn template <typename Mutex> QMutexLocker<Mutex>::QMutexLocker(Mutex *mutex) noexcept
|
||||
|
||||
Constructs a QMutexLocker and locks \a mutex. The mutex will be
|
||||
unlocked when the QMutexLocker is destroyed. If \a mutex is \nullptr,
|
||||
QMutexLocker does nothing.
|
||||
|
||||
\sa QMutex::lock()
|
||||
*/
|
||||
|
||||
/*!
|
||||
\fn template <typename Mutex> QMutexLocker<Mutex>::QMutexLocker(QMutexLocker &&other) noexcept
|
||||
\since 6.4
|
||||
|
||||
Move-constructs a QMutexLocker from \a other. The mutex and the
|
||||
state of \a other is transferred to the newly constructed instance.
|
||||
After the move, \a other will no longer be managing any mutex.
|
||||
|
||||
\sa QMutex::lock()
|
||||
*/
|
||||
|
||||
/*!
|
||||
\fn template <typename Mutex> QMutexLocker<Mutex> &QMutexLocker<Mutex>::operator=(QMutexLocker &&other) noexcept
|
||||
\since 6.4
|
||||
|
||||
Move-assigns \a other onto this QMutexLocker. If this QMutexLocker
|
||||
was holding a locked mutex before the assignment, the mutex will be
|
||||
unlocked. The mutex and the state of \a other is then transferred
|
||||
to this QMutexLocker. After the move, \a other will no longer be
|
||||
managing any mutex.
|
||||
|
||||
\sa QMutex::lock()
|
||||
*/
|
||||
|
||||
/*!
|
||||
\fn template <typename Mutex> void QMutexLocker<Mutex>::swap(QMutexLocker &other) noexcept
|
||||
\since 6.4
|
||||
|
||||
Swaps the mutex and the state of this QMutexLocker with \a other.
|
||||
This operation is very fast and never fails.
|
||||
|
||||
\sa QMutex::lock()
|
||||
*/
|
||||
|
||||
/*!
|
||||
\fn template <typename Mutex> QMutexLocker<Mutex>::~QMutexLocker() noexcept
|
||||
|
||||
Destroys the QMutexLocker and unlocks the mutex that was locked
|
||||
in the constructor.
|
||||
|
||||
\sa QMutex::unlock()
|
||||
*/
|
||||
|
||||
/*!
|
||||
\fn template <typename Mutex> bool QMutexLocker<Mutex>::isLocked() const noexcept
|
||||
\since 6.4
|
||||
|
||||
Returns true if this QMutexLocker is currently locking its associated
|
||||
mutex, or false otherwise.
|
||||
*/
|
||||
|
||||
/*!
|
||||
\fn template <typename Mutex> void QMutexLocker<Mutex>::unlock() noexcept
|
||||
|
||||
Unlocks this mutex locker. You can use \c relock() to lock
|
||||
it again. It does not need to be locked when destroyed.
|
||||
|
||||
\sa relock()
|
||||
*/
|
||||
|
||||
/*!
|
||||
\fn template <typename Mutex> void QMutexLocker<Mutex>::relock() noexcept
|
||||
|
||||
Relocks an unlocked mutex locker.
|
||||
|
||||
\sa unlock()
|
||||
*/
|
||||
|
||||
/*!
|
||||
\fn template <typename Mutex> QMutex *QMutexLocker<Mutex>::mutex() const
|
||||
|
||||
Returns the mutex on which the QMutexLocker is operating.
|
||||
|
||||
*/
|
||||
|
||||
/*
|
||||
For a rough introduction on how this works, refer to
|
||||
http://woboq.com/blog/internals-of-qmutex-in-qt5.html
|
||||
which explains a slightly simplified version of it.
|
||||
The differences are that here we try to work with timeout (requires the
|
||||
possiblyUnlocked flag) and that we only wake one thread when unlocking
|
||||
(requires maintaining the waiters count)
|
||||
We also support recursive mutexes which always have a valid d_ptr.
|
||||
|
||||
The waiters flag represents the number of threads that are waiting or about
|
||||
to wait on the mutex. There are two tricks to keep in mind:
|
||||
We don't want to increment waiters after we checked no threads are waiting
|
||||
(waiters == 0). That's why we atomically set the BigNumber flag on waiters when
|
||||
we check waiters. Similarly, if waiters is decremented right after we checked,
|
||||
the mutex would be unlocked (d->wakeUp() has (or will) be called), but there is
|
||||
no thread waiting. This is only happening if there was a timeout in tryLock at the
|
||||
same time as the mutex is unlocked. So when there was a timeout, we set the
|
||||
possiblyUnlocked flag.
|
||||
*/
|
||||
|
||||
/*
|
||||
* QBasicMutex implementation with futexes (Linux, Windows 10)
|
||||
*
|
||||
* QBasicMutex contains one pointer value, which can contain one of four
|
||||
* different values:
|
||||
* 0x0 unlocked
|
||||
* 0x1 locked, no waiters
|
||||
* 0x3 locked, at least one waiter
|
||||
*
|
||||
* LOCKING:
|
||||
*
|
||||
* A starts in the 0x0 state, indicating that it's unlocked. When the first
|
||||
* thread attempts to lock it, it will perform a testAndSetAcquire
|
||||
* from 0x0 to 0x1. If that succeeds, the caller concludes that it
|
||||
* successfully locked the mutex. That happens in fastTryLock().
|
||||
*
|
||||
* If that testAndSetAcquire fails, QBasicMutex::lockInternal is called.
|
||||
*
|
||||
* lockInternal will examine the value of the pointer. Otherwise, it will use
|
||||
* futexes to sleep and wait for another thread to unlock. To do that, it needs
|
||||
* to set a pointer value of 0x3, which indicates that thread is waiting. It
|
||||
* does that by a simple fetchAndStoreAcquire operation.
|
||||
*
|
||||
* If the pointer value was 0x0, it means we succeeded in acquiring the mutex.
|
||||
* For other values, it will then call FUTEX_WAIT and with an expected value of
|
||||
* 0x3.
|
||||
*
|
||||
* If the pointer value changed before futex(2) managed to sleep, it will
|
||||
* return -1 / EWOULDBLOCK, in which case we have to start over. And even if we
|
||||
* are woken up directly by a FUTEX_WAKE, we need to acquire the mutex, so we
|
||||
* start over again.
|
||||
*
|
||||
* UNLOCKING:
|
||||
*
|
||||
* To unlock, we need to set a value of 0x0 to indicate it's unlocked. The
|
||||
* first attempt is a testAndSetRelease operation from 0x1 to 0x0. If that
|
||||
* succeeds, we're done.
|
||||
*
|
||||
* If it fails, unlockInternal() is called. The only possibility is that the
|
||||
* mutex value was 0x3, which indicates some other thread is waiting or was
|
||||
* waiting in the past. We then set the mutex to 0x0 and perform a FUTEX_WAKE.
|
||||
*/
|
||||
|
||||
/*!
|
||||
\internal helper for lock()
|
||||
*/
|
||||
void QBasicMutex::lockInternal() QT_MUTEX_LOCK_NOEXCEPT
|
||||
{
|
||||
if (futexAvailable()) {
|
||||
// note we must set to dummyFutexValue because there could be other threads
|
||||
// also waiting
|
||||
while (d_ptr.fetchAndStoreAcquire(dummyFutexValue()) != nullptr) {
|
||||
// successfully set the waiting bit, now sleep
|
||||
futexWait(d_ptr, dummyFutexValue());
|
||||
|
||||
// we got woken up, so try to acquire the mutex
|
||||
}
|
||||
Q_ASSERT(d_ptr.loadRelaxed());
|
||||
} else {
|
||||
lockInternal(-1);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
\internal helper for lock(int)
|
||||
*/
|
||||
#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
|
||||
bool QBasicMutex::lockInternal(int timeout) QT_MUTEX_LOCK_NOEXCEPT
|
||||
{
|
||||
if (timeout == 0)
|
||||
return false;
|
||||
|
||||
return lockInternal(QDeadlineTimer(timeout));
|
||||
}
|
||||
#endif
|
||||
|
||||
/*!
|
||||
\internal helper for tryLock(QDeadlineTimer)
|
||||
*/
|
||||
bool QBasicMutex::lockInternal(QDeadlineTimer deadlineTimer) QT_MUTEX_LOCK_NOEXCEPT
|
||||
{
|
||||
qint64 remainingTime = deadlineTimer.remainingTimeNSecs();
|
||||
if (remainingTime == 0)
|
||||
return false;
|
||||
|
||||
if (futexAvailable()) {
|
||||
if (Q_UNLIKELY(remainingTime < 0)) { // deadlineTimer.isForever()
|
||||
lockInternal();
|
||||
return true;
|
||||
}
|
||||
|
||||
// The mutex is already locked, set a bit indicating we're waiting.
|
||||
// Note we must set to dummyFutexValue because there could be other threads
|
||||
// also waiting.
|
||||
if (d_ptr.fetchAndStoreAcquire(dummyFutexValue()) == nullptr)
|
||||
return true;
|
||||
|
||||
Q_FOREVER {
|
||||
if (!futexWait(d_ptr, dummyFutexValue(), remainingTime))
|
||||
return false;
|
||||
|
||||
// We got woken up, so must try to acquire the mutex. We must set
|
||||
// to dummyFutexValue() again because there could be other threads
|
||||
// waiting.
|
||||
if (d_ptr.fetchAndStoreAcquire(dummyFutexValue()) == nullptr)
|
||||
return true;
|
||||
|
||||
// calculate the remaining time
|
||||
remainingTime = deadlineTimer.remainingTimeNSecs();
|
||||
if (remainingTime <= 0)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#if !defined(QT_ALWAYS_USE_FUTEX)
|
||||
while (!fastTryLock()) {
|
||||
QMutexPrivate *copy = d_ptr.loadAcquire();
|
||||
if (!copy) // if d is 0, the mutex is unlocked
|
||||
continue;
|
||||
|
||||
if (copy == dummyLocked()) {
|
||||
if (remainingTime == 0)
|
||||
return false;
|
||||
// The mutex is locked but does not have a QMutexPrivate yet.
|
||||
// we need to allocate a QMutexPrivate
|
||||
QMutexPrivate *newD = QMutexPrivate::allocate();
|
||||
if (!d_ptr.testAndSetOrdered(dummyLocked(), newD)) {
|
||||
//Either the mutex is already unlocked, or another thread already set it.
|
||||
newD->deref();
|
||||
continue;
|
||||
}
|
||||
copy = newD;
|
||||
//the d->refCount is already 1 the deref will occurs when we unlock
|
||||
}
|
||||
|
||||
QMutexPrivate *d = static_cast<QMutexPrivate *>(copy);
|
||||
if (remainingTime == 0 && !d->possiblyUnlocked.loadRelaxed())
|
||||
return false;
|
||||
|
||||
// At this point we have a pointer to a QMutexPrivate. But the other thread
|
||||
// may unlock the mutex at any moment and release the QMutexPrivate to the pool.
|
||||
// We will try to reference it to avoid unlock to release it to the pool to make
|
||||
// sure it won't be released. But if the refcount is already 0 it has been released.
|
||||
if (!d->ref())
|
||||
continue; //that QMutexPrivate was already released
|
||||
|
||||
// We now hold a reference to the QMutexPrivate. It won't be released and re-used.
|
||||
// But it is still possible that it was already re-used by another QMutex right before
|
||||
// we did the ref(). So check if we still hold a pointer to the right mutex.
|
||||
if (d != d_ptr.loadAcquire()) {
|
||||
//Either the mutex is already unlocked, or relocked with another mutex
|
||||
d->deref();
|
||||
continue;
|
||||
}
|
||||
|
||||
// In this part, we will try to increment the waiters count.
|
||||
// We just need to take care of the case in which the old_waiters
|
||||
// is set to the BigNumber magic value set in unlockInternal()
|
||||
int old_waiters;
|
||||
do {
|
||||
old_waiters = d->waiters.loadAcquire();
|
||||
if (old_waiters == -QMutexPrivate::BigNumber) {
|
||||
// we are unlocking, and the thread that unlocks is about to change d to 0
|
||||
// we try to acquire the mutex by changing to dummyLocked()
|
||||
if (d_ptr.testAndSetAcquire(d, dummyLocked())) {
|
||||
// Mutex acquired
|
||||
d->deref();
|
||||
return true;
|
||||
} else {
|
||||
Q_ASSERT(d != d_ptr.loadRelaxed()); //else testAndSetAcquire should have succeeded
|
||||
// Mutex is likely to bo 0, we should continue the outer-loop,
|
||||
// set old_waiters to the magic value of BigNumber
|
||||
old_waiters = QMutexPrivate::BigNumber;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while (!d->waiters.testAndSetRelaxed(old_waiters, old_waiters + 1));
|
||||
|
||||
if (d != d_ptr.loadAcquire()) {
|
||||
// The mutex was unlocked before we incremented waiters.
|
||||
if (old_waiters != QMutexPrivate::BigNumber) {
|
||||
//we did not break the previous loop
|
||||
Q_ASSERT(d->waiters.loadRelaxed() >= 1);
|
||||
d->waiters.deref();
|
||||
}
|
||||
d->deref();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (d->wait(deadlineTimer)) {
|
||||
// reset the possiblyUnlocked flag if needed (and deref its corresponding reference)
|
||||
if (d->possiblyUnlocked.loadRelaxed() && d->possiblyUnlocked.testAndSetRelaxed(true, false))
|
||||
d->deref();
|
||||
d->derefWaiters(1);
|
||||
//we got the lock. (do not deref)
|
||||
Q_ASSERT(d == d_ptr.loadRelaxed());
|
||||
return true;
|
||||
} else {
|
||||
Q_ASSERT(remainingTime >= 0);
|
||||
// timed out
|
||||
d->derefWaiters(1);
|
||||
//There may be a race in which the mutex is unlocked right after we timed out,
|
||||
// and before we deref the waiters, so maybe the mutex is actually unlocked.
|
||||
// Set the possiblyUnlocked flag to indicate this possibility.
|
||||
if (!d->possiblyUnlocked.testAndSetRelaxed(false, true)) {
|
||||
// We keep a reference when possiblyUnlocked is true.
|
||||
// but if possiblyUnlocked was already true, we don't need to keep the reference.
|
||||
d->deref();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Q_ASSERT(d_ptr.loadRelaxed() != 0);
|
||||
return true;
|
||||
#else
|
||||
Q_UNREACHABLE();
|
||||
#endif
|
||||
}
|
||||
|
||||
/*!
|
||||
\internal
|
||||
*/
|
||||
void QBasicMutex::unlockInternal() noexcept
|
||||
{
|
||||
QMutexPrivate *copy = d_ptr.loadAcquire();
|
||||
Q_ASSERT(copy); //we must be locked
|
||||
Q_ASSERT(copy != dummyLocked()); // testAndSetRelease(dummyLocked(), 0) failed
|
||||
|
||||
if (futexAvailable()) {
|
||||
d_ptr.storeRelease(nullptr);
|
||||
return futexWakeOne(d_ptr);
|
||||
}
|
||||
|
||||
#if !defined(QT_ALWAYS_USE_FUTEX)
|
||||
QMutexPrivate *d = reinterpret_cast<QMutexPrivate *>(copy);
|
||||
|
||||
// If no one is waiting for the lock anymore, we should reset d to 0x0.
|
||||
// Using fetchAndAdd, we atomically check that waiters was equal to 0, and add a flag
|
||||
// to the waiters variable (BigNumber). That way, we avoid the race in which waiters is
|
||||
// incremented right after we checked, because we won't increment waiters if is
|
||||
// equal to -BigNumber
|
||||
if (d->waiters.fetchAndAddRelease(-QMutexPrivate::BigNumber) == 0) {
|
||||
//there is no one waiting on this mutex anymore, set the mutex as unlocked (d = 0)
|
||||
if (d_ptr.testAndSetRelease(d, 0)) {
|
||||
// reset the possiblyUnlocked flag if needed (and deref its corresponding reference)
|
||||
if (d->possiblyUnlocked.loadRelaxed() && d->possiblyUnlocked.testAndSetRelaxed(true, false))
|
||||
d->deref();
|
||||
}
|
||||
d->derefWaiters(0);
|
||||
} else {
|
||||
d->derefWaiters(0);
|
||||
//there are thread waiting, transfer the lock.
|
||||
d->wakeUp();
|
||||
}
|
||||
d->deref();
|
||||
#else
|
||||
Q_UNUSED(copy);
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !defined(QT_ALWAYS_USE_FUTEX)
|
||||
//The freelist management
|
||||
namespace {
|
||||
struct FreeListConstants : QFreeListDefaultConstants {
|
||||
enum { BlockCount = 4, MaxIndex=0xffff };
|
||||
static const int Sizes[BlockCount];
|
||||
};
|
||||
Q_CONSTINIT const int FreeListConstants::Sizes[FreeListConstants::BlockCount] = {
|
||||
16,
|
||||
128,
|
||||
1024,
|
||||
FreeListConstants::MaxIndex - (16 + 128 + 1024)
|
||||
};
|
||||
|
||||
typedef QFreeList<QMutexPrivate, FreeListConstants> FreeList;
|
||||
// We cannot use Q_GLOBAL_STATIC because it uses QMutex
|
||||
Q_CONSTINIT static FreeList freeList_;
|
||||
FreeList *freelist()
|
||||
{
|
||||
return &freeList_;
|
||||
}
|
||||
}
|
||||
|
||||
QMutexPrivate *QMutexPrivate::allocate()
|
||||
{
|
||||
int i = freelist()->next();
|
||||
QMutexPrivate *d = &(*freelist())[i];
|
||||
d->id = i;
|
||||
Q_ASSERT(d->refCount.loadRelaxed() == 0);
|
||||
Q_ASSERT(!d->possiblyUnlocked.loadRelaxed());
|
||||
Q_ASSERT(d->waiters.loadRelaxed() == 0);
|
||||
d->refCount.storeRelaxed(1);
|
||||
return d;
|
||||
}
|
||||
|
||||
void QMutexPrivate::release()
|
||||
{
|
||||
Q_ASSERT(refCount.loadRelaxed() == 0);
|
||||
Q_ASSERT(!possiblyUnlocked.loadRelaxed());
|
||||
Q_ASSERT(waiters.loadRelaxed() == 0);
|
||||
freelist()->release(id);
|
||||
}
|
||||
|
||||
// atomically subtract "value" to the waiters, and remove the QMutexPrivate::BigNumber flag
|
||||
void QMutexPrivate::derefWaiters(int value) noexcept
|
||||
{
|
||||
int old_waiters;
|
||||
int new_waiters;
|
||||
do {
|
||||
old_waiters = waiters.loadRelaxed();
|
||||
new_waiters = old_waiters;
|
||||
if (new_waiters < 0) {
|
||||
new_waiters += QMutexPrivate::BigNumber;
|
||||
}
|
||||
new_waiters -= value;
|
||||
} while (!waiters.testAndSetRelaxed(old_waiters, new_waiters));
|
||||
}
|
||||
#endif
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
#if defined(QT_ALWAYS_USE_FUTEX)
|
||||
// nothing
|
||||
#elif defined(Q_OS_DARWIN)
|
||||
# include "qmutex_mac.cpp"
|
||||
#else
|
||||
# include "qmutex_unix.cpp"
|
||||
#endif
|
94
qtbase/src/corelib/thread/qmutex_p.h
Normal file
94
qtbase/src/corelib/thread/qmutex_p.h
Normal file
@ -0,0 +1,94 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// Copyright (C) 2016 Intel Corporation.
|
||||
// Copyright (C) 2012 Olivier Goffart <ogoffart@woboq.com>
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
|
||||
|
||||
#ifndef QMUTEX_P_H
|
||||
#define QMUTEX_P_H
|
||||
|
||||
//
|
||||
// W A R N I N G
|
||||
// -------------
|
||||
//
|
||||
// This file is not part of the Qt API. It exists for the convenience of
|
||||
// qmutex.cpp and qmutex_unix.cpp. This header file may change from version to
|
||||
// version without notice, or even be removed.
|
||||
//
|
||||
// We mean it.
|
||||
//
|
||||
|
||||
#include <QtCore/private/qglobal_p.h>
|
||||
#include <QtCore/qnamespace.h>
|
||||
#include <QtCore/qmutex.h>
|
||||
#include <QtCore/qatomic.h>
|
||||
#include <QtCore/qdeadlinetimer.h>
|
||||
|
||||
#include "qplatformdefs.h" // _POSIX_VERSION
|
||||
|
||||
#if defined(Q_OS_DARWIN)
|
||||
# include <mach/semaphore.h>
|
||||
#elif defined(Q_OS_UNIX)
|
||||
# include <semaphore.h>
|
||||
#endif
|
||||
|
||||
struct timespec;
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
// We manipulate the pointer to this class in inline, atomic code,
|
||||
// so syncqt mustn't mark them as private, so ELFVERSION:ignore-next
|
||||
class QMutexPrivate
|
||||
{
|
||||
public:
|
||||
~QMutexPrivate();
|
||||
QMutexPrivate();
|
||||
|
||||
bool wait(QDeadlineTimer timeout = QDeadlineTimer::Forever);
|
||||
void wakeUp() noexcept;
|
||||
|
||||
// Control the lifetime of the privates
|
||||
QAtomicInt refCount;
|
||||
int id;
|
||||
|
||||
bool ref()
|
||||
{
|
||||
Q_ASSERT(refCount.loadRelaxed() >= 0);
|
||||
int c;
|
||||
do {
|
||||
c = refCount.loadRelaxed();
|
||||
if (c == 0)
|
||||
return false;
|
||||
} while (!refCount.testAndSetRelaxed(c, c + 1));
|
||||
Q_ASSERT(refCount.loadRelaxed() >= 0);
|
||||
return true;
|
||||
}
|
||||
void deref()
|
||||
{
|
||||
Q_ASSERT(refCount.loadRelaxed() >= 0);
|
||||
if (!refCount.deref())
|
||||
release();
|
||||
Q_ASSERT(refCount.loadRelaxed() >= 0);
|
||||
}
|
||||
void release();
|
||||
static QMutexPrivate *allocate();
|
||||
|
||||
QAtomicInt waiters; // Number of threads waiting on this mutex. (may be offset by -BigNumber)
|
||||
QAtomicInt possiblyUnlocked; /* Boolean indicating that a timed wait timed out.
|
||||
When it is true, a reference is held.
|
||||
It is there to avoid a race that happens if unlock happens right
|
||||
when the mutex is unlocked.
|
||||
*/
|
||||
enum { BigNumber = 0x100000 }; //Must be bigger than the possible number of waiters (number of threads)
|
||||
void derefWaiters(int value) noexcept;
|
||||
|
||||
//platform specific stuff
|
||||
#if defined(Q_OS_DARWIN)
|
||||
semaphore_t mach_semaphore;
|
||||
#elif defined(Q_OS_UNIX)
|
||||
sem_t semaphore;
|
||||
#endif
|
||||
};
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
#endif // QMUTEX_P_H
|
5270
qtbase/src/gui/rhi/qrhid3d11.cpp
Normal file
5270
qtbase/src/gui/rhi/qrhid3d11.cpp
Normal file
File diff suppressed because it is too large
Load Diff
853
qtbase/src/gui/rhi/qrhid3d11_p.h
Normal file
853
qtbase/src/gui/rhi/qrhid3d11_p.h
Normal file
@ -0,0 +1,853 @@
|
||||
// Copyright (C) 2023 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
|
||||
|
||||
#ifndef QRHID3D11_P_H
|
||||
#define QRHID3D11_P_H
|
||||
|
||||
//
|
||||
// W A R N I N G
|
||||
// -------------
|
||||
//
|
||||
// This file is not part of the Qt API. It exists purely as an
|
||||
// implementation detail. This header file may change from version to
|
||||
// version without notice, or even be removed.
|
||||
//
|
||||
// We mean it.
|
||||
//
|
||||
|
||||
#include "qrhi_p.h"
|
||||
#include <rhi/qshaderdescription.h>
|
||||
#include <QWindow>
|
||||
|
||||
#include <d3d11_1.h>
|
||||
#include <dxgi1_6.h>
|
||||
#include <dcomp.h>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
class QRhiD3D11;
|
||||
|
||||
struct QD3D11Buffer : public QRhiBuffer
|
||||
{
|
||||
QD3D11Buffer(QRhiImplementation *rhi, Type type, UsageFlags usage, quint32 size);
|
||||
~QD3D11Buffer();
|
||||
void destroy() override;
|
||||
bool create() override;
|
||||
QRhiBuffer::NativeBuffer nativeBuffer() override;
|
||||
char *beginFullDynamicBufferUpdateForCurrentFrame() override;
|
||||
void endFullDynamicBufferUpdateForCurrentFrame() override;
|
||||
|
||||
ID3D11UnorderedAccessView *unorderedAccessView(quint32 offset);
|
||||
|
||||
ID3D11Buffer *buffer = nullptr;
|
||||
char *dynBuf = nullptr;
|
||||
bool hasPendingDynamicUpdates = false;
|
||||
QHash<quint32, ID3D11UnorderedAccessView *> uavs;
|
||||
uint generation = 0;
|
||||
friend class QRhiD3D11;
|
||||
};
|
||||
|
||||
struct QD3D11RenderBuffer : public QRhiRenderBuffer
|
||||
{
|
||||
QD3D11RenderBuffer(QRhiImplementation *rhi, Type type, const QSize &pixelSize,
|
||||
int sampleCount, QRhiRenderBuffer::Flags flags,
|
||||
QRhiTexture::Format backingFormatHint);
|
||||
~QD3D11RenderBuffer();
|
||||
void destroy() override;
|
||||
bool create() override;
|
||||
QRhiTexture::Format backingFormat() const override;
|
||||
|
||||
ID3D11Texture2D *tex = nullptr;
|
||||
ID3D11DepthStencilView *dsv = nullptr;
|
||||
ID3D11RenderTargetView *rtv = nullptr;
|
||||
DXGI_FORMAT dxgiFormat;
|
||||
DXGI_SAMPLE_DESC sampleDesc;
|
||||
uint generation = 0;
|
||||
friend class QRhiD3D11;
|
||||
};
|
||||
|
||||
struct QD3D11Texture : public QRhiTexture
|
||||
{
|
||||
QD3D11Texture(QRhiImplementation *rhi, Format format, const QSize &pixelSize, int depth,
|
||||
int arraySize, int sampleCount, Flags flags);
|
||||
~QD3D11Texture();
|
||||
void destroy() override;
|
||||
bool create() override;
|
||||
bool createFrom(NativeTexture src) override;
|
||||
NativeTexture nativeTexture() override;
|
||||
|
||||
bool prepareCreate(QSize *adjustedSize = nullptr);
|
||||
bool finishCreate();
|
||||
ID3D11UnorderedAccessView *unorderedAccessViewForLevel(int level);
|
||||
ID3D11Resource *textureResource() const
|
||||
{
|
||||
if (tex)
|
||||
return tex;
|
||||
else if (tex1D)
|
||||
return tex1D;
|
||||
return tex3D;
|
||||
}
|
||||
|
||||
ID3D11Texture2D *tex = nullptr;
|
||||
ID3D11Texture3D *tex3D = nullptr;
|
||||
ID3D11Texture1D *tex1D = nullptr;
|
||||
bool owns = true;
|
||||
ID3D11ShaderResourceView *srv = nullptr;
|
||||
DXGI_FORMAT dxgiFormat;
|
||||
uint mipLevelCount = 0;
|
||||
DXGI_SAMPLE_DESC sampleDesc;
|
||||
ID3D11UnorderedAccessView *perLevelViews[QRhi::MAX_MIP_LEVELS];
|
||||
uint generation = 0;
|
||||
friend class QRhiD3D11;
|
||||
};
|
||||
|
||||
struct QD3D11Sampler : public QRhiSampler
|
||||
{
|
||||
QD3D11Sampler(QRhiImplementation *rhi, Filter magFilter, Filter minFilter, Filter mipmapMode,
|
||||
AddressMode u, AddressMode v, AddressMode w);
|
||||
~QD3D11Sampler();
|
||||
void destroy() override;
|
||||
bool create() override;
|
||||
|
||||
ID3D11SamplerState *samplerState = nullptr;
|
||||
uint generation = 0;
|
||||
friend class QRhiD3D11;
|
||||
};
|
||||
|
||||
struct QD3D11RenderPassDescriptor : public QRhiRenderPassDescriptor
|
||||
{
|
||||
QD3D11RenderPassDescriptor(QRhiImplementation *rhi);
|
||||
~QD3D11RenderPassDescriptor();
|
||||
void destroy() override;
|
||||
bool isCompatible(const QRhiRenderPassDescriptor *other) const override;
|
||||
QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() const override;
|
||||
QVector<quint32> serializedFormat() const override;
|
||||
};
|
||||
|
||||
struct QD3D11RenderTargetData
|
||||
{
|
||||
QD3D11RenderTargetData(QRhiImplementation *)
|
||||
{
|
||||
for (int i = 0; i < MAX_COLOR_ATTACHMENTS; ++i)
|
||||
rtv[i] = nullptr;
|
||||
}
|
||||
|
||||
QD3D11RenderPassDescriptor *rp = nullptr;
|
||||
QSize pixelSize;
|
||||
float dpr = 1;
|
||||
int sampleCount = 1;
|
||||
int colorAttCount = 0;
|
||||
int dsAttCount = 0;
|
||||
|
||||
static const int MAX_COLOR_ATTACHMENTS = 8;
|
||||
ID3D11RenderTargetView *rtv[MAX_COLOR_ATTACHMENTS];
|
||||
ID3D11DepthStencilView *dsv = nullptr;
|
||||
|
||||
QRhiRenderTargetAttachmentTracker::ResIdList currentResIdList;
|
||||
};
|
||||
|
||||
struct QD3D11SwapChainRenderTarget : public QRhiSwapChainRenderTarget
|
||||
{
|
||||
QD3D11SwapChainRenderTarget(QRhiImplementation *rhi, QRhiSwapChain *swapchain);
|
||||
~QD3D11SwapChainRenderTarget();
|
||||
void destroy() override;
|
||||
|
||||
QSize pixelSize() const override;
|
||||
float devicePixelRatio() const override;
|
||||
int sampleCount() const override;
|
||||
|
||||
QD3D11RenderTargetData d;
|
||||
};
|
||||
|
||||
struct QD3D11TextureRenderTarget : public QRhiTextureRenderTarget
|
||||
{
|
||||
QD3D11TextureRenderTarget(QRhiImplementation *rhi, const QRhiTextureRenderTargetDescription &desc, Flags flags);
|
||||
~QD3D11TextureRenderTarget();
|
||||
void destroy() override;
|
||||
|
||||
QSize pixelSize() const override;
|
||||
float devicePixelRatio() const override;
|
||||
int sampleCount() const override;
|
||||
|
||||
QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() override;
|
||||
bool create() override;
|
||||
|
||||
QD3D11RenderTargetData d;
|
||||
bool ownsRtv[QD3D11RenderTargetData::MAX_COLOR_ATTACHMENTS];
|
||||
ID3D11RenderTargetView *rtv[QD3D11RenderTargetData::MAX_COLOR_ATTACHMENTS];
|
||||
bool ownsDsv = false;
|
||||
ID3D11DepthStencilView *dsv = nullptr;
|
||||
friend class QRhiD3D11;
|
||||
};
|
||||
|
||||
struct QD3D11ShaderResourceBindings : public QRhiShaderResourceBindings
|
||||
{
|
||||
QD3D11ShaderResourceBindings(QRhiImplementation *rhi);
|
||||
~QD3D11ShaderResourceBindings();
|
||||
void destroy() override;
|
||||
bool create() override;
|
||||
void updateResources(UpdateFlags flags) override;
|
||||
|
||||
bool hasDynamicOffset = false;
|
||||
QVarLengthArray<QRhiShaderResourceBinding, 8> sortedBindings;
|
||||
uint generation = 0;
|
||||
|
||||
// Keep track of the generation number of each referenced QRhi* to be able
|
||||
// to detect that the batched bindings are out of date.
|
||||
struct BoundUniformBufferData {
|
||||
quint64 id;
|
||||
uint generation;
|
||||
};
|
||||
struct BoundSampledTextureData {
|
||||
int count;
|
||||
struct {
|
||||
quint64 texId;
|
||||
uint texGeneration;
|
||||
quint64 samplerId;
|
||||
uint samplerGeneration;
|
||||
} d[QRhiShaderResourceBinding::Data::MAX_TEX_SAMPLER_ARRAY_SIZE];
|
||||
};
|
||||
struct BoundStorageImageData {
|
||||
quint64 id;
|
||||
uint generation;
|
||||
};
|
||||
struct BoundStorageBufferData {
|
||||
quint64 id;
|
||||
uint generation;
|
||||
};
|
||||
struct BoundResourceData {
|
||||
union {
|
||||
BoundUniformBufferData ubuf;
|
||||
BoundSampledTextureData stex;
|
||||
BoundStorageImageData simage;
|
||||
BoundStorageBufferData sbuf;
|
||||
};
|
||||
};
|
||||
QVarLengthArray<BoundResourceData, 8> boundResourceData;
|
||||
|
||||
struct StageUniformBufferBatches {
|
||||
bool present = false;
|
||||
QRhiBatchedBindings<ID3D11Buffer *> ubufs;
|
||||
QRhiBatchedBindings<UINT> ubuforigbindings;
|
||||
QRhiBatchedBindings<UINT> ubufoffsets;
|
||||
QRhiBatchedBindings<UINT> ubufsizes;
|
||||
void finish() {
|
||||
present = ubufs.finish();
|
||||
ubuforigbindings.finish();
|
||||
ubufoffsets.finish();
|
||||
ubufsizes.finish();
|
||||
}
|
||||
void clear() {
|
||||
ubufs.clear();
|
||||
ubuforigbindings.clear();
|
||||
ubufoffsets.clear();
|
||||
ubufsizes.clear();
|
||||
}
|
||||
};
|
||||
|
||||
struct StageSamplerBatches {
|
||||
bool present = false;
|
||||
QRhiBatchedBindings<ID3D11SamplerState *> samplers;
|
||||
QRhiBatchedBindings<ID3D11ShaderResourceView *> shaderresources;
|
||||
void finish() {
|
||||
present = samplers.finish();
|
||||
shaderresources.finish();
|
||||
}
|
||||
void clear() {
|
||||
samplers.clear();
|
||||
shaderresources.clear();
|
||||
}
|
||||
};
|
||||
|
||||
struct StageUavBatches {
|
||||
bool present = false;
|
||||
QRhiBatchedBindings<ID3D11UnorderedAccessView *> uavs;
|
||||
void finish() {
|
||||
present = uavs.finish();
|
||||
}
|
||||
void clear() {
|
||||
uavs.clear();
|
||||
}
|
||||
};
|
||||
|
||||
StageUniformBufferBatches vsUniformBufferBatches;
|
||||
StageUniformBufferBatches hsUniformBufferBatches;
|
||||
StageUniformBufferBatches dsUniformBufferBatches;
|
||||
StageUniformBufferBatches gsUniformBufferBatches;
|
||||
StageUniformBufferBatches fsUniformBufferBatches;
|
||||
StageUniformBufferBatches csUniformBufferBatches;
|
||||
|
||||
StageSamplerBatches vsSamplerBatches;
|
||||
StageSamplerBatches hsSamplerBatches;
|
||||
StageSamplerBatches dsSamplerBatches;
|
||||
StageSamplerBatches gsSamplerBatches;
|
||||
StageSamplerBatches fsSamplerBatches;
|
||||
StageSamplerBatches csSamplerBatches;
|
||||
|
||||
StageUavBatches csUavBatches;
|
||||
|
||||
friend class QRhiD3D11;
|
||||
};
|
||||
|
||||
Q_DECLARE_TYPEINFO(QD3D11ShaderResourceBindings::BoundResourceData, Q_RELOCATABLE_TYPE);
|
||||
|
||||
struct QD3D11GraphicsPipeline : public QRhiGraphicsPipeline
|
||||
{
|
||||
QD3D11GraphicsPipeline(QRhiImplementation *rhi);
|
||||
~QD3D11GraphicsPipeline();
|
||||
void destroy() override;
|
||||
bool create() override;
|
||||
|
||||
ID3D11DepthStencilState *dsState = nullptr;
|
||||
ID3D11BlendState *blendState = nullptr;
|
||||
struct {
|
||||
ID3D11VertexShader *shader = nullptr;
|
||||
QShader::NativeResourceBindingMap nativeResourceBindingMap;
|
||||
} vs;
|
||||
struct {
|
||||
ID3D11HullShader *shader = nullptr;
|
||||
QShader::NativeResourceBindingMap nativeResourceBindingMap;
|
||||
} hs;
|
||||
struct {
|
||||
ID3D11DomainShader *shader = nullptr;
|
||||
QShader::NativeResourceBindingMap nativeResourceBindingMap;
|
||||
} ds;
|
||||
struct {
|
||||
ID3D11GeometryShader *shader = nullptr;
|
||||
QShader::NativeResourceBindingMap nativeResourceBindingMap;
|
||||
} gs;
|
||||
struct {
|
||||
ID3D11PixelShader *shader = nullptr;
|
||||
QShader::NativeResourceBindingMap nativeResourceBindingMap;
|
||||
} fs;
|
||||
ID3D11InputLayout *inputLayout = nullptr;
|
||||
D3D11_PRIMITIVE_TOPOLOGY d3dTopology = D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
|
||||
ID3D11RasterizerState *rastState = nullptr;
|
||||
uint generation = 0;
|
||||
friend class QRhiD3D11;
|
||||
};
|
||||
|
||||
struct QD3D11ComputePipeline : public QRhiComputePipeline
|
||||
{
|
||||
QD3D11ComputePipeline(QRhiImplementation *rhi);
|
||||
~QD3D11ComputePipeline();
|
||||
void destroy() override;
|
||||
bool create() override;
|
||||
|
||||
struct {
|
||||
ID3D11ComputeShader *shader = nullptr;
|
||||
QShader::NativeResourceBindingMap nativeResourceBindingMap;
|
||||
} cs;
|
||||
uint generation = 0;
|
||||
friend class QRhiD3D11;
|
||||
};
|
||||
|
||||
struct QD3D11SwapChain;
|
||||
|
||||
struct QD3D11CommandBuffer : public QRhiCommandBuffer
|
||||
{
|
||||
QD3D11CommandBuffer(QRhiImplementation *rhi);
|
||||
~QD3D11CommandBuffer();
|
||||
void destroy() override;
|
||||
|
||||
// these must be kept at a reasonably low value otherwise sizeof Command explodes
|
||||
static const int MAX_DYNAMIC_OFFSET_COUNT = 8;
|
||||
static const int MAX_VERTEX_BUFFER_BINDING_COUNT = 8;
|
||||
|
||||
struct Command {
|
||||
enum Cmd {
|
||||
ResetShaderResources,
|
||||
SetRenderTarget,
|
||||
Clear,
|
||||
Viewport,
|
||||
Scissor,
|
||||
BindVertexBuffers,
|
||||
BindIndexBuffer,
|
||||
BindGraphicsPipeline,
|
||||
BindShaderResources,
|
||||
StencilRef,
|
||||
BlendConstants,
|
||||
Draw,
|
||||
DrawIndexed,
|
||||
UpdateSubRes,
|
||||
CopySubRes,
|
||||
ResolveSubRes,
|
||||
GenMip,
|
||||
DebugMarkBegin,
|
||||
DebugMarkEnd,
|
||||
DebugMarkMsg,
|
||||
BindComputePipeline,
|
||||
Dispatch
|
||||
};
|
||||
enum ClearFlag { Color = 1, Depth = 2, Stencil = 4 };
|
||||
Cmd cmd;
|
||||
|
||||
// QRhi*/QD3D11* references should be kept at minimum (so no
|
||||
// QRhiTexture/Buffer/etc. pointers).
|
||||
union Args {
|
||||
struct {
|
||||
QRhiRenderTarget *rt;
|
||||
} setRenderTarget;
|
||||
struct {
|
||||
QRhiRenderTarget *rt;
|
||||
int mask;
|
||||
float c[4];
|
||||
float d;
|
||||
quint32 s;
|
||||
} clear;
|
||||
struct {
|
||||
float x, y, w, h;
|
||||
float d0, d1;
|
||||
} viewport;
|
||||
struct {
|
||||
int x, y, w, h;
|
||||
} scissor;
|
||||
struct {
|
||||
int startSlot;
|
||||
int slotCount;
|
||||
ID3D11Buffer *buffers[MAX_VERTEX_BUFFER_BINDING_COUNT];
|
||||
UINT offsets[MAX_VERTEX_BUFFER_BINDING_COUNT];
|
||||
UINT strides[MAX_VERTEX_BUFFER_BINDING_COUNT];
|
||||
} bindVertexBuffers;
|
||||
struct {
|
||||
ID3D11Buffer *buffer;
|
||||
quint32 offset;
|
||||
DXGI_FORMAT format;
|
||||
} bindIndexBuffer;
|
||||
struct {
|
||||
QD3D11GraphicsPipeline *ps;
|
||||
} bindGraphicsPipeline;
|
||||
struct {
|
||||
QD3D11ShaderResourceBindings *srb;
|
||||
bool offsetOnlyChange;
|
||||
int dynamicOffsetCount;
|
||||
uint dynamicOffsetPairs[MAX_DYNAMIC_OFFSET_COUNT * 2]; // binding, offsetInConstants
|
||||
} bindShaderResources;
|
||||
struct {
|
||||
QD3D11GraphicsPipeline *ps;
|
||||
quint32 ref;
|
||||
} stencilRef;
|
||||
struct {
|
||||
QD3D11GraphicsPipeline *ps;
|
||||
float c[4];
|
||||
} blendConstants;
|
||||
struct {
|
||||
QD3D11GraphicsPipeline *ps;
|
||||
quint32 vertexCount;
|
||||
quint32 instanceCount;
|
||||
quint32 firstVertex;
|
||||
quint32 firstInstance;
|
||||
} draw;
|
||||
struct {
|
||||
QD3D11GraphicsPipeline *ps;
|
||||
quint32 indexCount;
|
||||
quint32 instanceCount;
|
||||
quint32 firstIndex;
|
||||
qint32 vertexOffset;
|
||||
quint32 firstInstance;
|
||||
} drawIndexed;
|
||||
struct {
|
||||
ID3D11Resource *dst;
|
||||
UINT dstSubRes;
|
||||
bool hasDstBox;
|
||||
D3D11_BOX dstBox;
|
||||
const void *src; // must come from retain*()
|
||||
UINT srcRowPitch;
|
||||
} updateSubRes;
|
||||
struct {
|
||||
ID3D11Resource *dst;
|
||||
UINT dstSubRes;
|
||||
UINT dstX;
|
||||
UINT dstY;
|
||||
UINT dstZ;
|
||||
ID3D11Resource *src;
|
||||
UINT srcSubRes;
|
||||
bool hasSrcBox;
|
||||
D3D11_BOX srcBox;
|
||||
} copySubRes;
|
||||
struct {
|
||||
ID3D11Resource *dst;
|
||||
UINT dstSubRes;
|
||||
ID3D11Resource *src;
|
||||
UINT srcSubRes;
|
||||
DXGI_FORMAT format;
|
||||
} resolveSubRes;
|
||||
struct {
|
||||
ID3D11ShaderResourceView *srv;
|
||||
} genMip;
|
||||
struct {
|
||||
char s[64];
|
||||
} debugMark;
|
||||
struct {
|
||||
QD3D11ComputePipeline *ps;
|
||||
} bindComputePipeline;
|
||||
struct {
|
||||
UINT x;
|
||||
UINT y;
|
||||
UINT z;
|
||||
} dispatch;
|
||||
} args;
|
||||
};
|
||||
|
||||
enum PassType {
|
||||
NoPass,
|
||||
RenderPass,
|
||||
ComputePass
|
||||
};
|
||||
|
||||
QRhiBackendCommandList<Command> commands;
|
||||
PassType recordingPass;
|
||||
double lastGpuTime = 0;
|
||||
QRhiRenderTarget *currentTarget;
|
||||
QRhiGraphicsPipeline *currentGraphicsPipeline;
|
||||
QRhiComputePipeline *currentComputePipeline;
|
||||
uint currentPipelineGeneration;
|
||||
QRhiShaderResourceBindings *currentGraphicsSrb;
|
||||
QRhiShaderResourceBindings *currentComputeSrb;
|
||||
uint currentSrbGeneration;
|
||||
ID3D11Buffer *currentIndexBuffer;
|
||||
quint32 currentIndexOffset;
|
||||
DXGI_FORMAT currentIndexFormat;
|
||||
ID3D11Buffer *currentVertexBuffers[D3D11_IA_VERTEX_INPUT_RESOURCE_SLOT_COUNT];
|
||||
quint32 currentVertexOffsets[D3D11_IA_VERTEX_INPUT_RESOURCE_SLOT_COUNT];
|
||||
|
||||
QVarLengthArray<QByteArray, 4> dataRetainPool;
|
||||
QVarLengthArray<QRhiBufferData, 4> bufferDataRetainPool;
|
||||
QVarLengthArray<QImage, 4> imageRetainPool;
|
||||
|
||||
// relies heavily on implicit sharing (no copies of the actual data will be made)
|
||||
const uchar *retainData(const QByteArray &data) {
|
||||
dataRetainPool.append(data);
|
||||
return reinterpret_cast<const uchar *>(dataRetainPool.last().constData());
|
||||
}
|
||||
const uchar *retainBufferData(const QRhiBufferData &data) {
|
||||
bufferDataRetainPool.append(data);
|
||||
return reinterpret_cast<const uchar *>(bufferDataRetainPool.last().constData());
|
||||
}
|
||||
const uchar *retainImage(const QImage &image) {
|
||||
imageRetainPool.append(image);
|
||||
return imageRetainPool.last().constBits();
|
||||
}
|
||||
void resetCommands() {
|
||||
commands.reset();
|
||||
dataRetainPool.clear();
|
||||
bufferDataRetainPool.clear();
|
||||
imageRetainPool.clear();
|
||||
}
|
||||
void resetState() {
|
||||
recordingPass = NoPass;
|
||||
// do not zero lastGpuTime
|
||||
currentTarget = nullptr;
|
||||
resetCommands();
|
||||
resetCachedState();
|
||||
}
|
||||
void resetCachedState() {
|
||||
currentGraphicsPipeline = nullptr;
|
||||
currentComputePipeline = nullptr;
|
||||
currentPipelineGeneration = 0;
|
||||
currentGraphicsSrb = nullptr;
|
||||
currentComputeSrb = nullptr;
|
||||
currentSrbGeneration = 0;
|
||||
currentIndexBuffer = nullptr;
|
||||
currentIndexOffset = 0;
|
||||
currentIndexFormat = DXGI_FORMAT_R16_UINT;
|
||||
memset(currentVertexBuffers, 0, sizeof(currentVertexBuffers));
|
||||
memset(currentVertexOffsets, 0, sizeof(currentVertexOffsets));
|
||||
}
|
||||
};
|
||||
|
||||
static const int QD3D11_SWAPCHAIN_BUFFER_COUNT = 2;
|
||||
|
||||
struct QD3D11Timestamps
|
||||
{
|
||||
static const int MAX_TIMESTAMP_PAIRS = QD3D11_SWAPCHAIN_BUFFER_COUNT;
|
||||
bool active[MAX_TIMESTAMP_PAIRS] = {};
|
||||
ID3D11Query *disjointQuery[MAX_TIMESTAMP_PAIRS] = {};
|
||||
ID3D11Query *query[MAX_TIMESTAMP_PAIRS * 2] = {};
|
||||
int pairCount = 0;
|
||||
|
||||
bool prepare(int pairCount, QRhiD3D11 *rhiD);
|
||||
void destroy();
|
||||
bool tryQueryTimestamps(int idx, ID3D11DeviceContext *context, double *elapsedSec);
|
||||
};
|
||||
|
||||
struct QD3D11SwapChain : public QRhiSwapChain
|
||||
{
|
||||
QD3D11SwapChain(QRhiImplementation *rhi);
|
||||
~QD3D11SwapChain();
|
||||
void destroy() override;
|
||||
|
||||
QRhiCommandBuffer *currentFrameCommandBuffer() override;
|
||||
QRhiRenderTarget *currentFrameRenderTarget() override;
|
||||
|
||||
QSize surfacePixelSize() override;
|
||||
bool isFormatSupported(Format f) override;
|
||||
QRhiSwapChainHdrInfo hdrInfo() override;
|
||||
|
||||
QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() override;
|
||||
bool createOrResize() override;
|
||||
|
||||
void releaseBuffers();
|
||||
bool newColorBuffer(const QSize &size, DXGI_FORMAT format, DXGI_SAMPLE_DESC sampleDesc,
|
||||
ID3D11Texture2D **tex, ID3D11RenderTargetView **rtv) const;
|
||||
|
||||
QWindow *window = nullptr;
|
||||
QSize pixelSize;
|
||||
QD3D11SwapChainRenderTarget rt;
|
||||
QD3D11CommandBuffer cb;
|
||||
DXGI_FORMAT colorFormat;
|
||||
DXGI_FORMAT srgbAdjustedColorFormat;
|
||||
IDXGISwapChain *swapChain = nullptr;
|
||||
UINT swapChainFlags = 0;
|
||||
ID3D11Texture2D *backBufferTex;
|
||||
ID3D11RenderTargetView *backBufferRtv;
|
||||
static const int BUFFER_COUNT = QD3D11_SWAPCHAIN_BUFFER_COUNT;
|
||||
ID3D11Texture2D *msaaTex[BUFFER_COUNT];
|
||||
ID3D11RenderTargetView *msaaRtv[BUFFER_COUNT];
|
||||
DXGI_SAMPLE_DESC sampleDesc;
|
||||
int currentFrameSlot = 0;
|
||||
int frameCount = 0;
|
||||
QD3D11RenderBuffer *ds = nullptr;
|
||||
UINT swapInterval = 1;
|
||||
IDCompositionTarget *dcompTarget = nullptr;
|
||||
IDCompositionVisual *dcompVisual = nullptr;
|
||||
QD3D11Timestamps timestamps;
|
||||
};
|
||||
|
||||
class QRhiD3D11 : public QRhiImplementation
|
||||
{
|
||||
public:
|
||||
QRhiD3D11(QRhiD3D11InitParams *params, QRhiD3D11NativeHandles *importDevice = nullptr);
|
||||
|
||||
bool create(QRhi::Flags flags) override;
|
||||
void destroy() override;
|
||||
|
||||
QRhiGraphicsPipeline *createGraphicsPipeline() override;
|
||||
QRhiComputePipeline *createComputePipeline() override;
|
||||
QRhiShaderResourceBindings *createShaderResourceBindings() override;
|
||||
QRhiBuffer *createBuffer(QRhiBuffer::Type type,
|
||||
QRhiBuffer::UsageFlags usage,
|
||||
quint32 size) override;
|
||||
QRhiRenderBuffer *createRenderBuffer(QRhiRenderBuffer::Type type,
|
||||
const QSize &pixelSize,
|
||||
int sampleCount,
|
||||
QRhiRenderBuffer::Flags flags,
|
||||
QRhiTexture::Format backingFormatHint) override;
|
||||
QRhiTexture *createTexture(QRhiTexture::Format format,
|
||||
const QSize &pixelSize,
|
||||
int depth,
|
||||
int arraySize,
|
||||
int sampleCount,
|
||||
QRhiTexture::Flags flags) override;
|
||||
QRhiSampler *createSampler(QRhiSampler::Filter magFilter,
|
||||
QRhiSampler::Filter minFilter,
|
||||
QRhiSampler::Filter mipmapMode,
|
||||
QRhiSampler:: AddressMode u,
|
||||
QRhiSampler::AddressMode v,
|
||||
QRhiSampler::AddressMode w) override;
|
||||
|
||||
QRhiTextureRenderTarget *createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc,
|
||||
QRhiTextureRenderTarget::Flags flags) override;
|
||||
|
||||
QRhiSwapChain *createSwapChain() override;
|
||||
QRhi::FrameOpResult beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags) override;
|
||||
QRhi::FrameOpResult endFrame(QRhiSwapChain *swapChain, QRhi::EndFrameFlags flags) override;
|
||||
QRhi::FrameOpResult beginOffscreenFrame(QRhiCommandBuffer **cb, QRhi::BeginFrameFlags flags) override;
|
||||
QRhi::FrameOpResult endOffscreenFrame(QRhi::EndFrameFlags flags) override;
|
||||
QRhi::FrameOpResult finish() override;
|
||||
|
||||
void resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override;
|
||||
|
||||
void beginPass(QRhiCommandBuffer *cb,
|
||||
QRhiRenderTarget *rt,
|
||||
const QColor &colorClearValue,
|
||||
const QRhiDepthStencilClearValue &depthStencilClearValue,
|
||||
QRhiResourceUpdateBatch *resourceUpdates,
|
||||
QRhiCommandBuffer::BeginPassFlags flags) override;
|
||||
void endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override;
|
||||
|
||||
void setGraphicsPipeline(QRhiCommandBuffer *cb,
|
||||
QRhiGraphicsPipeline *ps) override;
|
||||
|
||||
void setShaderResources(QRhiCommandBuffer *cb,
|
||||
QRhiShaderResourceBindings *srb,
|
||||
int dynamicOffsetCount,
|
||||
const QRhiCommandBuffer::DynamicOffset *dynamicOffsets) override;
|
||||
|
||||
void setVertexInput(QRhiCommandBuffer *cb,
|
||||
int startBinding, int bindingCount, const QRhiCommandBuffer::VertexInput *bindings,
|
||||
QRhiBuffer *indexBuf, quint32 indexOffset,
|
||||
QRhiCommandBuffer::IndexFormat indexFormat) override;
|
||||
|
||||
void setViewport(QRhiCommandBuffer *cb, const QRhiViewport &viewport) override;
|
||||
void setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor) override;
|
||||
void setBlendConstants(QRhiCommandBuffer *cb, const QColor &c) override;
|
||||
void setStencilRef(QRhiCommandBuffer *cb, quint32 refValue) override;
|
||||
|
||||
void draw(QRhiCommandBuffer *cb, quint32 vertexCount,
|
||||
quint32 instanceCount, quint32 firstVertex, quint32 firstInstance) override;
|
||||
|
||||
void drawIndexed(QRhiCommandBuffer *cb, quint32 indexCount,
|
||||
quint32 instanceCount, quint32 firstIndex,
|
||||
qint32 vertexOffset, quint32 firstInstance) override;
|
||||
|
||||
void debugMarkBegin(QRhiCommandBuffer *cb, const QByteArray &name) override;
|
||||
void debugMarkEnd(QRhiCommandBuffer *cb) override;
|
||||
void debugMarkMsg(QRhiCommandBuffer *cb, const QByteArray &msg) override;
|
||||
|
||||
void beginComputePass(QRhiCommandBuffer *cb,
|
||||
QRhiResourceUpdateBatch *resourceUpdates,
|
||||
QRhiCommandBuffer::BeginPassFlags flags) override;
|
||||
void endComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override;
|
||||
void setComputePipeline(QRhiCommandBuffer *cb, QRhiComputePipeline *ps) override;
|
||||
void dispatch(QRhiCommandBuffer *cb, int x, int y, int z) override;
|
||||
|
||||
const QRhiNativeHandles *nativeHandles(QRhiCommandBuffer *cb) override;
|
||||
void beginExternal(QRhiCommandBuffer *cb) override;
|
||||
void endExternal(QRhiCommandBuffer *cb) override;
|
||||
double lastCompletedGpuTime(QRhiCommandBuffer *cb) override;
|
||||
|
||||
QList<int> supportedSampleCounts() const override;
|
||||
int ubufAlignment() const override;
|
||||
bool isYUpInFramebuffer() const override;
|
||||
bool isYUpInNDC() const override;
|
||||
bool isClipDepthZeroToOne() const override;
|
||||
QMatrix4x4 clipSpaceCorrMatrix() const override;
|
||||
bool isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags) const override;
|
||||
bool isFeatureSupported(QRhi::Feature feature) const override;
|
||||
int resourceLimit(QRhi::ResourceLimit limit) const override;
|
||||
const QRhiNativeHandles *nativeHandles() override;
|
||||
QRhiDriverInfo driverInfo() const override;
|
||||
QRhiStats statistics() override;
|
||||
bool makeThreadLocalNativeContextCurrent() override;
|
||||
void releaseCachedResources() override;
|
||||
bool isDeviceLost() const override;
|
||||
|
||||
QByteArray pipelineCacheData() override;
|
||||
void setPipelineCacheData(const QByteArray &data) override;
|
||||
|
||||
void enqueueSubresUpload(QD3D11Texture *texD, QD3D11CommandBuffer *cbD,
|
||||
int layer, int level, const QRhiTextureSubresourceUploadDescription &subresDesc);
|
||||
void enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates);
|
||||
void updateShaderResourceBindings(QD3D11ShaderResourceBindings *srbD,
|
||||
const QShader::NativeResourceBindingMap *nativeResourceBindingMaps[]);
|
||||
void executeBufferHostWrites(QD3D11Buffer *bufD);
|
||||
void bindShaderResources(QD3D11ShaderResourceBindings *srbD,
|
||||
const uint *dynOfsPairs, int dynOfsPairCount,
|
||||
bool offsetOnlyChange);
|
||||
void resetShaderResources();
|
||||
void executeCommandBuffer(QD3D11CommandBuffer *cbD, QD3D11SwapChain *timestampSwapChain = nullptr);
|
||||
DXGI_SAMPLE_DESC effectiveSampleCount(int sampleCount) const;
|
||||
void finishActiveReadbacks();
|
||||
void reportLiveObjects(ID3D11Device *device);
|
||||
void clearShaderCache();
|
||||
QByteArray compileHlslShaderSource(const QShader &shader, QShader::Variant shaderVariant, uint flags,
|
||||
QString *error, QShaderKey *usedShaderKey);
|
||||
bool ensureDirectCompositionDevice();
|
||||
|
||||
QRhi::Flags rhiFlags;
|
||||
bool debugLayer = false;
|
||||
bool importedDeviceAndContext = false;
|
||||
ID3D11Device *dev = nullptr;
|
||||
ID3D11DeviceContext1 *context = nullptr;
|
||||
D3D_FEATURE_LEVEL featureLevel = D3D_FEATURE_LEVEL(0);
|
||||
LUID adapterLuid = {};
|
||||
ID3DUserDefinedAnnotation *annotations = nullptr;
|
||||
IDXGIAdapter1 *activeAdapter = nullptr;
|
||||
IDXGIFactory1 *dxgiFactory = nullptr;
|
||||
IDCompositionDevice *dcompDevice = nullptr;
|
||||
bool supportsAllowTearing = false;
|
||||
bool useLegacySwapchainModel = false;
|
||||
bool deviceLost = false;
|
||||
QRhiD3D11NativeHandles nativeHandlesStruct;
|
||||
QRhiDriverInfo driverInfoStruct;
|
||||
|
||||
struct {
|
||||
int vsHighestActiveVertexBufferBinding = -1;
|
||||
bool vsHasIndexBufferBound = false;
|
||||
int vsHighestActiveSrvBinding = -1;
|
||||
int hsHighestActiveSrvBinding = -1;
|
||||
int dsHighestActiveSrvBinding = -1;
|
||||
int gsHighestActiveSrvBinding = -1;
|
||||
int fsHighestActiveSrvBinding = -1;
|
||||
int csHighestActiveSrvBinding = -1;
|
||||
int csHighestActiveUavBinding = -1;
|
||||
QD3D11SwapChain *currentSwapChain = nullptr;
|
||||
} contextState;
|
||||
|
||||
struct OffscreenFrame {
|
||||
OffscreenFrame(QRhiImplementation *rhi) : cbWrapper(rhi) { }
|
||||
bool active = false;
|
||||
QD3D11CommandBuffer cbWrapper;
|
||||
QD3D11Timestamps timestamps;
|
||||
int timestampIdx = 0;
|
||||
} ofr;
|
||||
|
||||
struct TextureReadback {
|
||||
QRhiReadbackDescription desc;
|
||||
QRhiReadbackResult *result;
|
||||
ID3D11Texture2D *stagingTex;
|
||||
quint32 byteSize;
|
||||
quint32 bpl;
|
||||
QSize pixelSize;
|
||||
QRhiTexture::Format format;
|
||||
};
|
||||
QVarLengthArray<TextureReadback, 2> activeTextureReadbacks;
|
||||
struct BufferReadback {
|
||||
QRhiReadbackResult *result;
|
||||
quint32 byteSize;
|
||||
ID3D11Buffer *stagingBuf;
|
||||
};
|
||||
QVarLengthArray<BufferReadback, 2> activeBufferReadbacks;
|
||||
|
||||
struct Shader {
|
||||
Shader() = default;
|
||||
Shader(IUnknown *s, const QByteArray &bytecode, const QShader::NativeResourceBindingMap &rbm)
|
||||
: s(s), bytecode(bytecode), nativeResourceBindingMap(rbm) { }
|
||||
IUnknown *s;
|
||||
QByteArray bytecode;
|
||||
QShader::NativeResourceBindingMap nativeResourceBindingMap;
|
||||
};
|
||||
QHash<QRhiShaderStage, Shader> m_shaderCache;
|
||||
|
||||
// This is what gets exposed as the "pipeline cache", not that that concept
|
||||
// applies anyway. Here we are just storing the DX bytecode for a shader so
|
||||
// we can skip the HLSL->DXBC compilation when the QShader has HLSL source
|
||||
// code and the same shader source has already been compiled before.
|
||||
// m_shaderCache seemingly does the same, but this here does not care about
|
||||
// the ID3D11*Shader, this is just about the bytecode and about allowing
|
||||
// the data to be serialized to persistent storage and then reloaded in
|
||||
// future runs of the app, or when creating another QRhi, etc.
|
||||
struct BytecodeCacheKey {
|
||||
QByteArray sourceHash;
|
||||
QByteArray target;
|
||||
QByteArray entryPoint;
|
||||
uint compileFlags;
|
||||
};
|
||||
QHash<BytecodeCacheKey, QByteArray> m_bytecodeCache;
|
||||
};
|
||||
|
||||
Q_DECLARE_TYPEINFO(QRhiD3D11::TextureReadback, Q_RELOCATABLE_TYPE);
|
||||
Q_DECLARE_TYPEINFO(QRhiD3D11::BufferReadback, Q_RELOCATABLE_TYPE);
|
||||
|
||||
inline bool operator==(const QRhiD3D11::BytecodeCacheKey &a, const QRhiD3D11::BytecodeCacheKey &b) noexcept
|
||||
{
|
||||
return a.sourceHash == b.sourceHash
|
||||
&& a.target == b.target
|
||||
&& a.entryPoint == b.entryPoint
|
||||
&& a.compileFlags == b.compileFlags;
|
||||
}
|
||||
|
||||
inline bool operator!=(const QRhiD3D11::BytecodeCacheKey &a, const QRhiD3D11::BytecodeCacheKey &b) noexcept
|
||||
{
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
inline size_t qHash(const QRhiD3D11::BytecodeCacheKey &k, size_t seed = 0) noexcept
|
||||
{
|
||||
return qHash(k.sourceHash, seed) ^ qHash(k.target) ^ qHash(k.entryPoint) ^ k.compileFlags;
|
||||
}
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
#endif
|
6166
qtbase/src/gui/rhi/qrhid3d12.cpp
Normal file
6166
qtbase/src/gui/rhi/qrhid3d12.cpp
Normal file
File diff suppressed because it is too large
Load Diff
921
qtbase/src/gui/text/windows/qwindowsfontdatabasebase.cpp
Normal file
921
qtbase/src/gui/text/windows/qwindowsfontdatabasebase.cpp
Normal file
@ -0,0 +1,921 @@
|
||||
// Copyright (C) 2020 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 "qwindowsfontdatabasebase_p.h"
|
||||
#include "qwindowsfontdatabase_p.h"
|
||||
|
||||
#include <QtCore/QThreadStorage>
|
||||
#include <QtCore/QtEndian>
|
||||
|
||||
#if QT_CONFIG(directwrite)
|
||||
# if QT_CONFIG(directwrite3)
|
||||
# include <dwrite_3.h>
|
||||
# else
|
||||
# include <dwrite_2.h>
|
||||
# endif
|
||||
# include <d2d1.h>
|
||||
# include "qwindowsfontenginedirectwrite_p.h"
|
||||
#endif
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
// Helper classes for creating font engines directly from font data
|
||||
namespace {
|
||||
|
||||
# pragma pack(1)
|
||||
|
||||
// Common structure for all formats of the "name" table
|
||||
struct NameTable
|
||||
{
|
||||
quint16 format;
|
||||
quint16 count;
|
||||
quint16 stringOffset;
|
||||
};
|
||||
|
||||
struct NameRecord
|
||||
{
|
||||
quint16 platformID;
|
||||
quint16 encodingID;
|
||||
quint16 languageID;
|
||||
quint16 nameID;
|
||||
quint16 length;
|
||||
quint16 offset;
|
||||
};
|
||||
|
||||
struct OffsetSubTable
|
||||
{
|
||||
quint32 scalerType;
|
||||
quint16 numTables;
|
||||
quint16 searchRange;
|
||||
quint16 entrySelector;
|
||||
quint16 rangeShift;
|
||||
};
|
||||
|
||||
struct TableDirectory : public QWindowsFontDatabaseBase::FontTable
|
||||
{
|
||||
quint32 identifier;
|
||||
quint32 checkSum;
|
||||
quint32 offset;
|
||||
quint32 length;
|
||||
};
|
||||
|
||||
struct OS2Table
|
||||
{
|
||||
quint16 version;
|
||||
qint16 avgCharWidth;
|
||||
quint16 weightClass;
|
||||
quint16 widthClass;
|
||||
quint16 type;
|
||||
qint16 subscriptXSize;
|
||||
qint16 subscriptYSize;
|
||||
qint16 subscriptXOffset;
|
||||
qint16 subscriptYOffset;
|
||||
qint16 superscriptXSize;
|
||||
qint16 superscriptYSize;
|
||||
qint16 superscriptXOffset;
|
||||
qint16 superscriptYOffset;
|
||||
qint16 strikeOutSize;
|
||||
qint16 strikeOutPosition;
|
||||
qint16 familyClass;
|
||||
quint8 panose[10];
|
||||
quint32 unicodeRanges[4];
|
||||
quint8 vendorID[4];
|
||||
quint16 selection;
|
||||
quint16 firstCharIndex;
|
||||
quint16 lastCharIndex;
|
||||
qint16 typoAscender;
|
||||
qint16 typoDescender;
|
||||
qint16 typoLineGap;
|
||||
quint16 winAscent;
|
||||
quint16 winDescent;
|
||||
quint32 codepageRanges[2];
|
||||
qint16 height;
|
||||
qint16 capHeight;
|
||||
quint16 defaultChar;
|
||||
quint16 breakChar;
|
||||
quint16 maxContext;
|
||||
};
|
||||
|
||||
# pragma pack()
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
QWindowsFontDatabaseBase::FontTable *QWindowsFontDatabaseBase::EmbeddedFont::tableDirectoryEntry(const QByteArray &tagName)
|
||||
{
|
||||
Q_ASSERT(tagName.size() == 4);
|
||||
quint32 tagId = *(reinterpret_cast<const quint32 *>(tagName.constData()));
|
||||
const size_t fontDataSize = m_fontData.size();
|
||||
if (Q_UNLIKELY(fontDataSize < sizeof(OffsetSubTable)))
|
||||
return nullptr;
|
||||
|
||||
OffsetSubTable *offsetSubTable = reinterpret_cast<OffsetSubTable *>(m_fontData.data());
|
||||
TableDirectory *tableDirectory = reinterpret_cast<TableDirectory *>(offsetSubTable + 1);
|
||||
|
||||
const size_t tableCount = qFromBigEndian<quint16>(offsetSubTable->numTables);
|
||||
if (Q_UNLIKELY(fontDataSize < sizeof(OffsetSubTable) + sizeof(TableDirectory) * tableCount))
|
||||
return nullptr;
|
||||
|
||||
TableDirectory *tableDirectoryEnd = tableDirectory + tableCount;
|
||||
for (TableDirectory *entry = tableDirectory; entry < tableDirectoryEnd; ++entry) {
|
||||
if (entry->identifier == tagId)
|
||||
return entry;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QString QWindowsFontDatabaseBase::EmbeddedFont::familyName(QWindowsFontDatabaseBase::FontTable *directoryEntry)
|
||||
{
|
||||
QString name;
|
||||
|
||||
TableDirectory *nameTableDirectoryEntry = static_cast<TableDirectory *>(directoryEntry);
|
||||
if (nameTableDirectoryEntry == nullptr)
|
||||
nameTableDirectoryEntry = static_cast<TableDirectory *>(tableDirectoryEntry("name"));
|
||||
|
||||
if (nameTableDirectoryEntry != nullptr) {
|
||||
quint32 offset = qFromBigEndian<quint32>(nameTableDirectoryEntry->offset);
|
||||
if (Q_UNLIKELY(quint32(m_fontData.size()) < offset + sizeof(NameTable)))
|
||||
return QString();
|
||||
|
||||
NameTable *nameTable = reinterpret_cast<NameTable *>(m_fontData.data() + offset);
|
||||
NameRecord *nameRecord = reinterpret_cast<NameRecord *>(nameTable + 1);
|
||||
|
||||
quint16 nameTableCount = qFromBigEndian<quint16>(nameTable->count);
|
||||
if (Q_UNLIKELY(quint32(m_fontData.size()) < offset + sizeof(NameRecord) * nameTableCount))
|
||||
return QString();
|
||||
|
||||
for (int i = 0; i < nameTableCount; ++i, ++nameRecord) {
|
||||
if (qFromBigEndian<quint16>(nameRecord->nameID) == 1
|
||||
&& qFromBigEndian<quint16>(nameRecord->platformID) == 3 // Windows
|
||||
&& qFromBigEndian<quint16>(nameRecord->languageID) == 0x0409) { // US English
|
||||
quint16 stringOffset = qFromBigEndian<quint16>(nameTable->stringOffset);
|
||||
quint16 nameOffset = qFromBigEndian<quint16>(nameRecord->offset);
|
||||
quint16 nameLength = qFromBigEndian<quint16>(nameRecord->length);
|
||||
|
||||
if (Q_UNLIKELY(quint32(m_fontData.size()) < offset + stringOffset + nameOffset + nameLength))
|
||||
return QString();
|
||||
|
||||
const void *ptr = reinterpret_cast<const quint8 *>(nameTable)
|
||||
+ stringOffset
|
||||
+ nameOffset;
|
||||
|
||||
const quint16 *s = reinterpret_cast<const quint16 *>(ptr);
|
||||
const quint16 *e = s + nameLength / sizeof(quint16);
|
||||
while (s != e)
|
||||
name += QChar( qFromBigEndian<quint16>(*s++));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
void QWindowsFontDatabaseBase::EmbeddedFont::updateFromOS2Table(QFontEngine *fontEngine)
|
||||
{
|
||||
TableDirectory *os2TableEntry = static_cast<TableDirectory *>(tableDirectoryEntry("OS/2"));
|
||||
if (os2TableEntry != nullptr) {
|
||||
const OS2Table *os2Table =
|
||||
reinterpret_cast<const OS2Table *>(m_fontData.constData()
|
||||
+ qFromBigEndian<quint32>(os2TableEntry->offset));
|
||||
|
||||
bool italic = qFromBigEndian<quint16>(os2Table->selection) & (1 << 0);
|
||||
bool oblique = qFromBigEndian<quint16>(os2Table->selection) & (1 << 9);
|
||||
|
||||
if (italic)
|
||||
fontEngine->fontDef.style = QFont::StyleItalic;
|
||||
else if (oblique)
|
||||
fontEngine->fontDef.style = QFont::StyleOblique;
|
||||
else
|
||||
fontEngine->fontDef.style = QFont::StyleNormal;
|
||||
|
||||
fontEngine->fontDef.weight = qFromBigEndian<quint16>(os2Table->weightClass);
|
||||
}
|
||||
}
|
||||
|
||||
QString QWindowsFontDatabaseBase::EmbeddedFont::changeFamilyName(const QString &newFamilyName)
|
||||
{
|
||||
TableDirectory *nameTableDirectoryEntry = static_cast<TableDirectory *>(tableDirectoryEntry("name"));
|
||||
if (nameTableDirectoryEntry == nullptr)
|
||||
return QString();
|
||||
|
||||
QString oldFamilyName = familyName(nameTableDirectoryEntry);
|
||||
|
||||
// Reserve size for name table header, five required name records and string
|
||||
const int requiredRecordCount = 5;
|
||||
quint16 nameIds[requiredRecordCount] = { 1, 2, 3, 4, 6 };
|
||||
|
||||
int sizeOfHeader = sizeof(NameTable) + sizeof(NameRecord) * requiredRecordCount;
|
||||
int newFamilyNameSize = newFamilyName.size() * int(sizeof(quint16));
|
||||
|
||||
const QString regularString = QString::fromLatin1("Regular");
|
||||
int regularStringSize = regularString.size() * int(sizeof(quint16));
|
||||
|
||||
// Align table size of table to 32 bits (pad with 0)
|
||||
int fullSize = ((sizeOfHeader + newFamilyNameSize + regularStringSize) & ~3) + 4;
|
||||
|
||||
QByteArray newNameTable(fullSize, char(0));
|
||||
|
||||
{
|
||||
NameTable *nameTable = reinterpret_cast<NameTable *>(newNameTable.data());
|
||||
nameTable->count = qbswap<quint16>(requiredRecordCount);
|
||||
nameTable->stringOffset = qbswap<quint16>(sizeOfHeader);
|
||||
|
||||
NameRecord *nameRecord = reinterpret_cast<NameRecord *>(nameTable + 1);
|
||||
for (int i = 0; i < requiredRecordCount; ++i, nameRecord++) {
|
||||
nameRecord->nameID = qbswap<quint16>(nameIds[i]);
|
||||
nameRecord->encodingID = qbswap<quint16>(1);
|
||||
nameRecord->languageID = qbswap<quint16>(0x0409);
|
||||
nameRecord->platformID = qbswap<quint16>(3);
|
||||
nameRecord->length = qbswap<quint16>(newFamilyNameSize);
|
||||
|
||||
// Special case for sub-family
|
||||
if (nameIds[i] == 4) {
|
||||
nameRecord->offset = qbswap<quint16>(newFamilyNameSize);
|
||||
nameRecord->length = qbswap<quint16>(regularStringSize);
|
||||
}
|
||||
}
|
||||
|
||||
// nameRecord now points to string data
|
||||
quint16 *stringStorage = reinterpret_cast<quint16 *>(nameRecord);
|
||||
for (QChar ch : newFamilyName)
|
||||
*stringStorage++ = qbswap<quint16>(quint16(ch.unicode()));
|
||||
|
||||
for (QChar ch : regularString)
|
||||
*stringStorage++ = qbswap<quint16>(quint16(ch.unicode()));
|
||||
}
|
||||
|
||||
quint32 *p = reinterpret_cast<quint32 *>(newNameTable.data());
|
||||
quint32 *tableEnd = reinterpret_cast<quint32 *>(newNameTable.data() + fullSize);
|
||||
|
||||
quint32 checkSum = 0;
|
||||
while (p < tableEnd)
|
||||
checkSum += qFromBigEndian<quint32>(*(p++));
|
||||
|
||||
nameTableDirectoryEntry->checkSum = qbswap<quint32>(checkSum);
|
||||
nameTableDirectoryEntry->offset = qbswap<quint32>(m_fontData.size());
|
||||
nameTableDirectoryEntry->length = qbswap<quint32>(fullSize);
|
||||
|
||||
m_fontData.append(newNameTable);
|
||||
|
||||
return oldFamilyName;
|
||||
}
|
||||
|
||||
#if QT_CONFIG(directwrite) && QT_CONFIG(direct2d)
|
||||
|
||||
namespace {
|
||||
class DirectWriteFontFileStream: public IDWriteFontFileStream
|
||||
{
|
||||
Q_DISABLE_COPY(DirectWriteFontFileStream)
|
||||
public:
|
||||
DirectWriteFontFileStream(const QByteArray &fontData)
|
||||
: m_fontData(fontData)
|
||||
, m_referenceCount(0)
|
||||
{
|
||||
}
|
||||
virtual ~DirectWriteFontFileStream()
|
||||
{
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void **object) override;
|
||||
ULONG STDMETHODCALLTYPE AddRef() override;
|
||||
ULONG STDMETHODCALLTYPE Release() override;
|
||||
|
||||
HRESULT STDMETHODCALLTYPE ReadFileFragment(const void **fragmentStart, UINT64 fileOffset,
|
||||
UINT64 fragmentSize, OUT void **fragmentContext) override;
|
||||
void STDMETHODCALLTYPE ReleaseFileFragment(void *fragmentContext) override;
|
||||
HRESULT STDMETHODCALLTYPE GetFileSize(OUT UINT64 *fileSize) override;
|
||||
HRESULT STDMETHODCALLTYPE GetLastWriteTime(OUT UINT64 *lastWriteTime) override;
|
||||
|
||||
private:
|
||||
QByteArray m_fontData;
|
||||
ULONG m_referenceCount;
|
||||
};
|
||||
|
||||
HRESULT STDMETHODCALLTYPE DirectWriteFontFileStream::QueryInterface(REFIID iid, void **object)
|
||||
{
|
||||
if (iid == IID_IUnknown || iid == __uuidof(IDWriteFontFileStream)) {
|
||||
*object = this;
|
||||
AddRef();
|
||||
return S_OK;
|
||||
} else {
|
||||
*object = NULL;
|
||||
return E_NOINTERFACE;
|
||||
}
|
||||
}
|
||||
|
||||
ULONG STDMETHODCALLTYPE DirectWriteFontFileStream::AddRef()
|
||||
{
|
||||
return InterlockedIncrement(&m_referenceCount);
|
||||
}
|
||||
|
||||
ULONG STDMETHODCALLTYPE DirectWriteFontFileStream::Release()
|
||||
{
|
||||
ULONG newCount = InterlockedDecrement(&m_referenceCount);
|
||||
if (newCount == 0)
|
||||
delete this;
|
||||
return newCount;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE DirectWriteFontFileStream::ReadFileFragment(
|
||||
const void **fragmentStart,
|
||||
UINT64 fileOffset,
|
||||
UINT64 fragmentSize,
|
||||
OUT void **fragmentContext)
|
||||
{
|
||||
*fragmentContext = NULL;
|
||||
if (fileOffset + fragmentSize <= quint64(m_fontData.size())) {
|
||||
*fragmentStart = m_fontData.data() + fileOffset;
|
||||
return S_OK;
|
||||
} else {
|
||||
*fragmentStart = NULL;
|
||||
return E_FAIL;
|
||||
}
|
||||
}
|
||||
|
||||
void STDMETHODCALLTYPE DirectWriteFontFileStream::ReleaseFileFragment(void *)
|
||||
{
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE DirectWriteFontFileStream::GetFileSize(UINT64 *fileSize)
|
||||
{
|
||||
*fileSize = m_fontData.size();
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE DirectWriteFontFileStream::GetLastWriteTime(UINT64 *lastWriteTime)
|
||||
{
|
||||
*lastWriteTime = 0;
|
||||
return E_NOTIMPL;
|
||||
}
|
||||
|
||||
class DirectWriteFontFileLoader: public IDWriteFontFileLoader
|
||||
{
|
||||
public:
|
||||
DirectWriteFontFileLoader() : m_referenceCount(0) {}
|
||||
virtual ~DirectWriteFontFileLoader()
|
||||
{
|
||||
}
|
||||
|
||||
inline void addKey(const void *key, const QByteArray &fontData)
|
||||
{
|
||||
Q_ASSERT(!m_fontDatas.contains(key));
|
||||
m_fontDatas.insert(key, fontData);
|
||||
}
|
||||
|
||||
inline void removeKey(const void *key)
|
||||
{
|
||||
m_fontDatas.remove(key);
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void **object) override;
|
||||
ULONG STDMETHODCALLTYPE AddRef() override;
|
||||
ULONG STDMETHODCALLTYPE Release() override;
|
||||
|
||||
HRESULT STDMETHODCALLTYPE CreateStreamFromKey(void const *fontFileReferenceKey,
|
||||
UINT32 fontFileReferenceKeySize,
|
||||
OUT IDWriteFontFileStream **fontFileStream) override;
|
||||
|
||||
private:
|
||||
ULONG m_referenceCount;
|
||||
QHash<const void *, QByteArray> m_fontDatas;
|
||||
};
|
||||
|
||||
HRESULT STDMETHODCALLTYPE DirectWriteFontFileLoader::QueryInterface(const IID &iid,
|
||||
void **object)
|
||||
{
|
||||
if (iid == IID_IUnknown || iid == __uuidof(IDWriteFontFileLoader)) {
|
||||
*object = this;
|
||||
AddRef();
|
||||
return S_OK;
|
||||
} else {
|
||||
*object = NULL;
|
||||
return E_NOINTERFACE;
|
||||
}
|
||||
}
|
||||
|
||||
ULONG STDMETHODCALLTYPE DirectWriteFontFileLoader::AddRef()
|
||||
{
|
||||
return InterlockedIncrement(&m_referenceCount);
|
||||
}
|
||||
|
||||
ULONG STDMETHODCALLTYPE DirectWriteFontFileLoader::Release()
|
||||
{
|
||||
ULONG newCount = InterlockedDecrement(&m_referenceCount);
|
||||
if (newCount == 0)
|
||||
delete this;
|
||||
return newCount;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE DirectWriteFontFileLoader::CreateStreamFromKey(
|
||||
void const *fontFileReferenceKey,
|
||||
UINT32 fontFileReferenceKeySize,
|
||||
IDWriteFontFileStream **fontFileStream)
|
||||
{
|
||||
Q_UNUSED(fontFileReferenceKeySize);
|
||||
|
||||
if (fontFileReferenceKeySize != sizeof(const void *)) {
|
||||
qWarning("%s: Wrong key size", __FUNCTION__);
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
const void *key = *reinterpret_cast<void * const *>(fontFileReferenceKey);
|
||||
*fontFileStream = NULL;
|
||||
auto it = m_fontDatas.constFind(key);
|
||||
if (it == m_fontDatas.constEnd())
|
||||
return E_FAIL;
|
||||
|
||||
QByteArray fontData = it.value();
|
||||
DirectWriteFontFileStream *stream = new DirectWriteFontFileStream(fontData);
|
||||
stream->AddRef();
|
||||
*fontFileStream = stream;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
class CustomFontFileLoader
|
||||
{
|
||||
public:
|
||||
CustomFontFileLoader(IDWriteFactory *factory)
|
||||
{
|
||||
m_directWriteFactory = factory;
|
||||
|
||||
if (m_directWriteFactory) {
|
||||
m_directWriteFactory->AddRef();
|
||||
|
||||
m_directWriteFontFileLoader = new DirectWriteFontFileLoader();
|
||||
m_directWriteFactory->RegisterFontFileLoader(m_directWriteFontFileLoader);
|
||||
}
|
||||
}
|
||||
|
||||
~CustomFontFileLoader()
|
||||
{
|
||||
if (m_directWriteFactory != nullptr && m_directWriteFontFileLoader != nullptr)
|
||||
m_directWriteFactory->UnregisterFontFileLoader(m_directWriteFontFileLoader);
|
||||
|
||||
if (m_directWriteFactory != nullptr)
|
||||
m_directWriteFactory->Release();
|
||||
}
|
||||
|
||||
void addKey(const void *key, const QByteArray &fontData)
|
||||
{
|
||||
if (m_directWriteFontFileLoader != nullptr)
|
||||
m_directWriteFontFileLoader->addKey(key, fontData);
|
||||
}
|
||||
|
||||
void removeKey(const void *key)
|
||||
{
|
||||
if (m_directWriteFontFileLoader != nullptr)
|
||||
m_directWriteFontFileLoader->removeKey(key);
|
||||
}
|
||||
|
||||
IDWriteFontFileLoader *loader() const
|
||||
{
|
||||
return m_directWriteFontFileLoader;
|
||||
}
|
||||
|
||||
private:
|
||||
IDWriteFactory *m_directWriteFactory = nullptr;
|
||||
DirectWriteFontFileLoader *m_directWriteFontFileLoader = nullptr;
|
||||
};
|
||||
} // Anonymous namespace
|
||||
|
||||
#endif // directwrite && direct2d
|
||||
|
||||
|
||||
QWindowsFontEngineData::~QWindowsFontEngineData()
|
||||
{
|
||||
if (hdc)
|
||||
DeleteDC(hdc);
|
||||
|
||||
#if QT_CONFIG(directwrite) && QT_CONFIG(direct2d)
|
||||
if (directWriteGdiInterop)
|
||||
directWriteGdiInterop->Release();
|
||||
if (directWriteFactory)
|
||||
directWriteFactory->Release();
|
||||
#endif
|
||||
}
|
||||
|
||||
QWindowsFontDatabaseBase::QWindowsFontDatabaseBase()
|
||||
{
|
||||
}
|
||||
|
||||
QWindowsFontDatabaseBase::~QWindowsFontDatabaseBase()
|
||||
{
|
||||
}
|
||||
|
||||
typedef QSharedPointer<QWindowsFontEngineData> QWindowsFontEngineDataPtr;
|
||||
typedef QThreadStorage<QWindowsFontEngineDataPtr> FontEngineThreadLocalData;
|
||||
Q_GLOBAL_STATIC(FontEngineThreadLocalData, fontEngineThreadLocalData)
|
||||
|
||||
QSharedPointer<QWindowsFontEngineData> QWindowsFontDatabaseBase::data()
|
||||
{
|
||||
FontEngineThreadLocalData *data = fontEngineThreadLocalData();
|
||||
if (!data->hasLocalData())
|
||||
data->setLocalData(QSharedPointer<QWindowsFontEngineData>::create());
|
||||
|
||||
if (!init(data->localData()))
|
||||
qCWarning(lcQpaFonts) << "Cannot initialize common font database data";
|
||||
|
||||
return data->localData();
|
||||
}
|
||||
|
||||
bool QWindowsFontDatabaseBase::init(QSharedPointer<QWindowsFontEngineData> d)
|
||||
{
|
||||
#if QT_CONFIG(directwrite) && QT_CONFIG(direct2d)
|
||||
if (!d->directWriteFactory) {
|
||||
createDirectWriteFactory(&d->directWriteFactory);
|
||||
if (!d->directWriteFactory)
|
||||
return false;
|
||||
}
|
||||
if (!d->directWriteGdiInterop) {
|
||||
const HRESULT hr = d->directWriteFactory->GetGdiInterop(&d->directWriteGdiInterop);
|
||||
if (FAILED(hr)) {
|
||||
qErrnoWarning("%s: GetGdiInterop failed", __FUNCTION__);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#else
|
||||
Q_UNUSED(d);
|
||||
#endif // directwrite && direct2d
|
||||
return true;
|
||||
}
|
||||
|
||||
#if QT_CONFIG(directwrite) && QT_CONFIG(direct2d)
|
||||
void QWindowsFontDatabaseBase::createDirectWriteFactory(IDWriteFactory **factory)
|
||||
{
|
||||
*factory = nullptr;
|
||||
IUnknown *result = nullptr;
|
||||
|
||||
# if QT_CONFIG(directwrite3)
|
||||
DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory3), &result);
|
||||
# endif
|
||||
if (result == nullptr)
|
||||
DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory2), &result);
|
||||
|
||||
if (result == nullptr) {
|
||||
if (FAILED(DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), &result))) {
|
||||
qErrnoWarning("DWriteCreateFactory failed");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
*factory = static_cast<IDWriteFactory *>(result);
|
||||
}
|
||||
#endif // directwrite && direct2d
|
||||
|
||||
int QWindowsFontDatabaseBase::defaultVerticalDPI()
|
||||
{
|
||||
return 96;
|
||||
}
|
||||
|
||||
LOGFONT QWindowsFontDatabaseBase::fontDefToLOGFONT(const QFontDef &request, const QString &faceName)
|
||||
{
|
||||
LOGFONT lf;
|
||||
memset(&lf, 0, sizeof(LOGFONT));
|
||||
|
||||
lf.lfHeight = -qRound(request.pixelSize);
|
||||
lf.lfWidth = 0;
|
||||
lf.lfEscapement = 0;
|
||||
lf.lfOrientation = 0;
|
||||
if (request.weight == QFont::Normal)
|
||||
lf.lfWeight = FW_DONTCARE;
|
||||
else
|
||||
lf.lfWeight = request.weight;
|
||||
lf.lfItalic = request.style != QFont::StyleNormal;
|
||||
lf.lfCharSet = DEFAULT_CHARSET;
|
||||
|
||||
int strat = OUT_DEFAULT_PRECIS;
|
||||
if (request.styleStrategy & QFont::PreferBitmap) {
|
||||
strat = OUT_RASTER_PRECIS;
|
||||
} else if (request.styleStrategy & QFont::PreferDevice) {
|
||||
strat = OUT_DEVICE_PRECIS;
|
||||
} else if (request.styleStrategy & QFont::PreferOutline) {
|
||||
strat = OUT_OUTLINE_PRECIS;
|
||||
} else if (request.styleStrategy & QFont::ForceOutline) {
|
||||
strat = OUT_TT_ONLY_PRECIS;
|
||||
}
|
||||
|
||||
lf.lfOutPrecision = strat;
|
||||
|
||||
int qual = DEFAULT_QUALITY;
|
||||
|
||||
if (request.styleStrategy & QFont::PreferMatch)
|
||||
qual = DRAFT_QUALITY;
|
||||
else if (request.styleStrategy & QFont::PreferQuality)
|
||||
qual = PROOF_QUALITY;
|
||||
|
||||
if (request.styleStrategy & QFont::PreferAntialias) {
|
||||
qual = (request.styleStrategy & QFont::NoSubpixelAntialias) == 0
|
||||
? CLEARTYPE_QUALITY : ANTIALIASED_QUALITY;
|
||||
} else if (request.styleStrategy & QFont::NoAntialias) {
|
||||
qual = NONANTIALIASED_QUALITY;
|
||||
} else if ((request.styleStrategy & QFont::NoSubpixelAntialias) && data()->clearTypeEnabled) {
|
||||
qual = ANTIALIASED_QUALITY;
|
||||
}
|
||||
|
||||
lf.lfQuality = qual;
|
||||
|
||||
lf.lfClipPrecision = CLIP_DEFAULT_PRECIS;
|
||||
|
||||
int hint = FF_DONTCARE;
|
||||
switch (request.styleHint) {
|
||||
case QFont::Helvetica:
|
||||
hint = FF_SWISS;
|
||||
break;
|
||||
case QFont::Times:
|
||||
hint = FF_ROMAN;
|
||||
break;
|
||||
case QFont::Courier:
|
||||
hint = FF_MODERN;
|
||||
break;
|
||||
case QFont::OldEnglish:
|
||||
hint = FF_DECORATIVE;
|
||||
break;
|
||||
case QFont::System:
|
||||
hint = FF_MODERN;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
lf.lfPitchAndFamily = DEFAULT_PITCH | hint;
|
||||
|
||||
QString fam = faceName;
|
||||
if (fam.isEmpty())
|
||||
fam = request.families.first();
|
||||
if (Q_UNLIKELY(fam.size() >= LF_FACESIZE)) {
|
||||
qCritical("%s: Family name '%s' is too long.", __FUNCTION__, qPrintable(fam));
|
||||
fam.truncate(LF_FACESIZE - 1);
|
||||
}
|
||||
|
||||
memcpy(lf.lfFaceName, fam.utf16(), fam.size() * sizeof(wchar_t));
|
||||
|
||||
return lf;
|
||||
}
|
||||
|
||||
QFont QWindowsFontDatabaseBase::LOGFONT_to_QFont(const LOGFONT& logFont, int verticalDPI_In)
|
||||
{
|
||||
if (verticalDPI_In <= 0)
|
||||
verticalDPI_In = defaultVerticalDPI();
|
||||
QFont qFont(QString::fromWCharArray(logFont.lfFaceName));
|
||||
qFont.setItalic(logFont.lfItalic);
|
||||
if (logFont.lfWeight != FW_DONTCARE)
|
||||
qFont.setWeight(QFont::Weight(logFont.lfWeight));
|
||||
const qreal logFontHeight = qAbs(logFont.lfHeight);
|
||||
qFont.setPointSizeF(logFontHeight * 72.0 / qreal(verticalDPI_In));
|
||||
qFont.setUnderline(logFont.lfUnderline);
|
||||
qFont.setOverline(false);
|
||||
qFont.setStrikeOut(logFont.lfStrikeOut);
|
||||
return qFont;
|
||||
}
|
||||
|
||||
// ### fixme Qt 6 (QTBUG-58610): See comment at QWindowsFontDatabase::systemDefaultFont()
|
||||
HFONT QWindowsFontDatabaseBase::systemFont()
|
||||
{
|
||||
static const auto stock_sysfont =
|
||||
reinterpret_cast<HFONT>(GetStockObject(DEFAULT_GUI_FONT));
|
||||
return stock_sysfont;
|
||||
}
|
||||
|
||||
QFont QWindowsFontDatabaseBase::systemDefaultFont()
|
||||
{
|
||||
// Qt 6: Obtain default GUI font (typically "Segoe UI, 9pt", see QTBUG-58610)
|
||||
NONCLIENTMETRICS ncm = {};
|
||||
ncm.cbSize = sizeof(ncm);
|
||||
SystemParametersInfoForDpi(SPI_GETNONCLIENTMETRICS, ncm.cbSize, &ncm, 0, defaultVerticalDPI());
|
||||
const QFont systemFont = QWindowsFontDatabase::LOGFONT_to_QFont(ncm.lfMessageFont);
|
||||
qCDebug(lcQpaFonts) << __FUNCTION__ << systemFont;
|
||||
return systemFont;
|
||||
}
|
||||
|
||||
#if QT_CONFIG(directwrite) && QT_CONFIG(direct2d)
|
||||
IDWriteFontFace *QWindowsFontDatabaseBase::createDirectWriteFace(const QByteArray &fontData) const
|
||||
{
|
||||
QSharedPointer<QWindowsFontEngineData> fontEngineData = data();
|
||||
if (fontEngineData->directWriteFactory == nullptr) {
|
||||
qCWarning(lcQpaFonts) << "DirectWrite factory not created in QWindowsFontDatabaseBase::createDirectWriteFace()";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
CustomFontFileLoader fontFileLoader(fontEngineData->directWriteFactory);
|
||||
fontFileLoader.addKey(this, fontData);
|
||||
|
||||
IDWriteFontFile *fontFile = nullptr;
|
||||
const void *key = this;
|
||||
|
||||
HRESULT hres = fontEngineData->directWriteFactory->CreateCustomFontFileReference(&key,
|
||||
sizeof(void *),
|
||||
fontFileLoader.loader(),
|
||||
&fontFile);
|
||||
if (FAILED(hres)) {
|
||||
qErrnoWarning(hres, "%s: CreateCustomFontFileReference failed", __FUNCTION__);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
BOOL isSupportedFontType;
|
||||
DWRITE_FONT_FILE_TYPE fontFileType;
|
||||
DWRITE_FONT_FACE_TYPE fontFaceType;
|
||||
UINT32 numberOfFaces;
|
||||
fontFile->Analyze(&isSupportedFontType, &fontFileType, &fontFaceType, &numberOfFaces);
|
||||
if (!isSupportedFontType) {
|
||||
fontFile->Release();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// ### Currently no support for .ttc, but we could easily return a list here.
|
||||
IDWriteFontFace *directWriteFontFace = nullptr;
|
||||
hres = fontEngineData->directWriteFactory->CreateFontFace(fontFaceType,
|
||||
1,
|
||||
&fontFile,
|
||||
0,
|
||||
DWRITE_FONT_SIMULATIONS_NONE,
|
||||
&directWriteFontFace);
|
||||
if (FAILED(hres)) {
|
||||
qErrnoWarning(hres, "%s: CreateFontFace failed", __FUNCTION__);
|
||||
fontFile->Release();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
fontFile->Release();
|
||||
return directWriteFontFace;
|
||||
}
|
||||
#endif // directwrite && direct2d
|
||||
|
||||
QFontEngine *QWindowsFontDatabaseBase::fontEngine(const QFontDef &fontDef, void *handle)
|
||||
{
|
||||
// This function was apparently not used before, and probably isn't now either,
|
||||
// call the base implementation which just prints that it's not supported.
|
||||
return QPlatformFontDatabase::fontEngine(fontDef, handle);
|
||||
}
|
||||
|
||||
QFontEngine *QWindowsFontDatabaseBase::fontEngine(const QByteArray &fontData, qreal pixelSize, QFont::HintingPreference hintingPreference)
|
||||
{
|
||||
QFontEngine *fontEngine = nullptr;
|
||||
|
||||
#if QT_CONFIG(directwrite) && QT_CONFIG(direct2d)
|
||||
QSharedPointer<QWindowsFontEngineData> fontEngineData = data();
|
||||
if (fontEngineData->directWriteFactory == nullptr)
|
||||
return nullptr;
|
||||
|
||||
IDWriteFontFace *directWriteFontFace = createDirectWriteFace(fontData);
|
||||
fontEngine = new QWindowsFontEngineDirectWrite(directWriteFontFace,
|
||||
pixelSize,
|
||||
fontEngineData);
|
||||
|
||||
// Get font family from font data
|
||||
EmbeddedFont font(fontData);
|
||||
font.updateFromOS2Table(fontEngine);
|
||||
fontEngine->fontDef.families = QStringList(font.familyName());
|
||||
fontEngine->fontDef.hintingPreference = hintingPreference;
|
||||
|
||||
directWriteFontFace->Release();
|
||||
#else // directwrite && direct2d
|
||||
Q_UNUSED(fontData);
|
||||
Q_UNUSED(pixelSize);
|
||||
Q_UNUSED(hintingPreference);
|
||||
#endif
|
||||
|
||||
return fontEngine;
|
||||
}
|
||||
|
||||
QString QWindowsFontDatabaseBase::familyForStyleHint(QFont::StyleHint styleHint)
|
||||
{
|
||||
switch (styleHint) {
|
||||
case QFont::Times:
|
||||
return QStringLiteral("Times New Roman");
|
||||
case QFont::Courier:
|
||||
return QStringLiteral("Courier New");
|
||||
case QFont::Monospace:
|
||||
return QStringLiteral("Courier New");
|
||||
case QFont::Cursive:
|
||||
return QStringLiteral("Comic Sans MS");
|
||||
case QFont::Fantasy:
|
||||
return QStringLiteral("Impact");
|
||||
case QFont::Decorative:
|
||||
return QStringLiteral("Old English");
|
||||
case QFont::Helvetica:
|
||||
return QStringLiteral("Arial");
|
||||
case QFont::System:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return QStringLiteral("Tahoma");
|
||||
}
|
||||
|
||||
// Creation functions
|
||||
|
||||
static const char *other_tryFonts[] = {
|
||||
"Arial",
|
||||
"MS UI Gothic",
|
||||
"Gulim",
|
||||
"SimSun",
|
||||
"PMingLiU",
|
||||
"Arial Unicode MS",
|
||||
0
|
||||
};
|
||||
|
||||
static const char *jp_tryFonts [] = {
|
||||
"Yu Gothic UI",
|
||||
"MS UI Gothic",
|
||||
"Arial",
|
||||
"Gulim",
|
||||
"SimSun",
|
||||
"PMingLiU",
|
||||
"Arial Unicode MS",
|
||||
0
|
||||
};
|
||||
|
||||
static const char *ch_CN_tryFonts [] = {
|
||||
"SimSun",
|
||||
"Arial",
|
||||
"PMingLiU",
|
||||
"Gulim",
|
||||
"MS UI Gothic",
|
||||
"Arial Unicode MS",
|
||||
0
|
||||
};
|
||||
|
||||
static const char *ch_TW_tryFonts [] = {
|
||||
"PMingLiU",
|
||||
"Arial",
|
||||
"SimSun",
|
||||
"Gulim",
|
||||
"MS UI Gothic",
|
||||
"Arial Unicode MS",
|
||||
0
|
||||
};
|
||||
|
||||
static const char *kr_tryFonts[] = {
|
||||
"Gulim",
|
||||
"Arial",
|
||||
"PMingLiU",
|
||||
"SimSun",
|
||||
"MS UI Gothic",
|
||||
"Arial Unicode MS",
|
||||
0
|
||||
};
|
||||
|
||||
static const char **tryFonts = nullptr;
|
||||
|
||||
QStringList QWindowsFontDatabaseBase::extraTryFontsForFamily(const QString &family)
|
||||
{
|
||||
QStringList result;
|
||||
if (!QFontDatabase::writingSystems(family).contains(QFontDatabase::Symbol)) {
|
||||
if (!tryFonts) {
|
||||
LANGID lid = GetUserDefaultLangID();
|
||||
switch (lid&0xff) {
|
||||
case LANG_CHINESE: // Chinese
|
||||
if ( lid == 0x0804 || lid == 0x1004) // China mainland and Singapore
|
||||
tryFonts = ch_CN_tryFonts;
|
||||
else
|
||||
tryFonts = ch_TW_tryFonts; // Taiwan, Hong Kong and Macau
|
||||
break;
|
||||
case LANG_JAPANESE:
|
||||
tryFonts = jp_tryFonts;
|
||||
break;
|
||||
case LANG_KOREAN:
|
||||
tryFonts = kr_tryFonts;
|
||||
break;
|
||||
default:
|
||||
tryFonts = other_tryFonts;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const QStringList families = QFontDatabase::families();
|
||||
const char **tf = tryFonts;
|
||||
while (tf && *tf) {
|
||||
// QTBUG-31689, family might be an English alias for a localized font name.
|
||||
const QString family = QString::fromLatin1(*tf);
|
||||
if (families.contains(family) || QFontDatabase::hasFamily(family))
|
||||
result << family;
|
||||
++tf;
|
||||
}
|
||||
}
|
||||
result.append(QStringLiteral("Segoe UI Emoji"));
|
||||
result.append(QStringLiteral("Segoe UI Symbol"));
|
||||
return result;
|
||||
}
|
||||
|
||||
QFontDef QWindowsFontDatabaseBase::sanitizeRequest(QFontDef request) const
|
||||
{
|
||||
QFontDef req = request;
|
||||
const QString fam = request.families.front();
|
||||
if (fam.isEmpty())
|
||||
req.families[0] = QStringLiteral("MS Sans Serif");
|
||||
|
||||
if (fam == "MS Sans Serif"_L1) {
|
||||
int height = -qRound(request.pixelSize);
|
||||
// MS Sans Serif has bearing problems in italic, and does not scale
|
||||
if (request.style == QFont::StyleItalic || (height > 18 && height != 24))
|
||||
req.families[0] = QStringLiteral("Arial");
|
||||
}
|
||||
|
||||
if (!(request.styleStrategy & QFont::StyleStrategy::PreferBitmap) && fam == u"Courier")
|
||||
req.families[0] = QStringLiteral("Courier New");
|
||||
return req;
|
||||
}
|
||||
|
||||
QT_END_NAMESPACE
|
176
qtbase/src/network/kernel/qdnslookup_win.cpp
Normal file
176
qtbase/src/network/kernel/qdnslookup_win.cpp
Normal file
@ -0,0 +1,176 @@
|
||||
// Copyright (C) 2012 Jeremy Lainé <jeremy.laine@m4x.org>
|
||||
// Copyright (C) 2023 Intel Corporation.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
|
||||
|
||||
#include <winsock2.h>
|
||||
#include "qdnslookup_p.h"
|
||||
|
||||
#include <qurl.h>
|
||||
#include <private/qnativesocketengine_p.h>
|
||||
#include <private/qsystemerror_p.h>
|
||||
|
||||
#include <qt_windows.h>
|
||||
#include <windns.h>
|
||||
#include <memory.h>
|
||||
|
||||
#ifndef DNS_ADDR_MAX_SOCKADDR_LENGTH
|
||||
// MinGW headers are missing almost all of this
|
||||
typedef struct Qt_DnsAddr {
|
||||
CHAR MaxSa[32];
|
||||
DWORD DnsAddrUserDword[8];
|
||||
} DNS_ADDR, *PDNS_ADDR;
|
||||
typedef struct Qt_DnsAddrArray {
|
||||
DWORD MaxCount;
|
||||
DWORD AddrCount;
|
||||
DWORD Tag;
|
||||
WORD Family;
|
||||
WORD WordReserved;
|
||||
DWORD Flags;
|
||||
DWORD MatchFlag;
|
||||
DWORD Reserved1;
|
||||
DWORD Reserved2;
|
||||
DNS_ADDR AddrArray[];
|
||||
} DNS_ADDR_ARRAY, *PDNS_ADDR_ARRAY;
|
||||
# ifndef DNS_QUERY_RESULTS_VERSION1
|
||||
typedef struct Qt_DNS_QUERY_RESULT {
|
||||
ULONG Version;
|
||||
DNS_STATUS QueryStatus;
|
||||
ULONG64 QueryOptions;
|
||||
PDNS_RECORD pQueryRecords;
|
||||
PVOID Reserved;
|
||||
} DNS_QUERY_RESULT, *PDNS_QUERY_RESULT;
|
||||
typedef VOID WINAPI DNS_QUERY_COMPLETION_ROUTINE(PVOID pQueryContext,PDNS_QUERY_RESULT pQueryResults);
|
||||
typedef DNS_QUERY_COMPLETION_ROUTINE *PDNS_QUERY_COMPLETION_ROUTINE;
|
||||
# endif
|
||||
typedef struct Qt_DNS_QUERY_REQUEST {
|
||||
ULONG Version;
|
||||
PCWSTR QueryName;
|
||||
WORD QueryType;
|
||||
ULONG64 QueryOptions;
|
||||
PDNS_ADDR_ARRAY pDnsServerList;
|
||||
ULONG InterfaceIndex;
|
||||
PDNS_QUERY_COMPLETION_ROUTINE pQueryCompletionCallback;
|
||||
PVOID pQueryContext;
|
||||
} DNS_QUERY_REQUEST, *PDNS_QUERY_REQUEST;
|
||||
|
||||
typedef void *PDNS_QUERY_CANCEL; // not really, but we don't need it
|
||||
extern "C" {
|
||||
DNS_STATUS WINAPI DnsQueryEx(PDNS_QUERY_REQUEST pQueryRequest,
|
||||
PDNS_QUERY_RESULT pQueryResults,
|
||||
PDNS_QUERY_CANCEL pCancelHandle);
|
||||
}
|
||||
#endif
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
void QDnsLookupRunnable::query(QDnsLookupReply *reply)
|
||||
{
|
||||
// Perform DNS query.
|
||||
alignas(DNS_ADDR_ARRAY) uchar dnsAddresses[sizeof(DNS_ADDR_ARRAY) + sizeof(DNS_ADDR)];
|
||||
DNS_QUERY_REQUEST request = {};
|
||||
request.Version = 1;
|
||||
request.QueryName = reinterpret_cast<const wchar_t *>(requestName.constData());
|
||||
request.QueryType = requestType;
|
||||
request.QueryOptions = DNS_QUERY_STANDARD | DNS_QUERY_TREAT_AS_FQDN;
|
||||
|
||||
if (!nameserver.isNull()) {
|
||||
memset(dnsAddresses, 0, sizeof(dnsAddresses));
|
||||
request.pDnsServerList = new (dnsAddresses) DNS_ADDR_ARRAY;
|
||||
auto addr = new (request.pDnsServerList->AddrArray) DNS_ADDR[1];
|
||||
auto sa = new (addr[0].MaxSa) sockaddr;
|
||||
request.pDnsServerList->MaxCount = sizeof(dnsAddresses);
|
||||
request.pDnsServerList->AddrCount = 1;
|
||||
// ### setting port 53 seems to cause some systems to fail
|
||||
setSockaddr(sa, nameserver, port == DnsPort ? 0 : port);
|
||||
request.pDnsServerList->Family = sa->sa_family;
|
||||
}
|
||||
|
||||
DNS_QUERY_RESULT results = {};
|
||||
results.Version = 1;
|
||||
const DNS_STATUS status = DnsQueryEx(&request, &results, nullptr);
|
||||
if (status >= DNS_ERROR_RCODE_FORMAT_ERROR && status <= DNS_ERROR_RCODE_LAST)
|
||||
return reply->makeDnsRcodeError(status - DNS_ERROR_RCODE_FORMAT_ERROR + 1);
|
||||
else if (status == ERROR_TIMEOUT)
|
||||
return reply->makeTimeoutError();
|
||||
else if (status != ERROR_SUCCESS)
|
||||
return reply->makeResolverSystemError(status);
|
||||
|
||||
QStringView lastEncodedName;
|
||||
QString cachedDecodedName;
|
||||
auto extractAndCacheHost = [&](QStringView name) -> const QString & {
|
||||
lastEncodedName = name;
|
||||
cachedDecodedName = decodeLabel(name);
|
||||
return cachedDecodedName;
|
||||
};
|
||||
auto extractMaybeCachedHost = [&](QStringView name) -> const QString & {
|
||||
return lastEncodedName == name ? cachedDecodedName : extractAndCacheHost(name);
|
||||
};
|
||||
|
||||
// Extract results.
|
||||
for (PDNS_RECORD ptr = results.pQueryRecords; ptr != NULL; ptr = ptr->pNext) {
|
||||
// warning: always assign name to the record before calling extractXxxHost() again
|
||||
const QString &name = extractMaybeCachedHost(ptr->pName);
|
||||
if (ptr->wType == QDnsLookup::A) {
|
||||
QDnsHostAddressRecord record;
|
||||
record.d->name = name;
|
||||
record.d->timeToLive = ptr->dwTtl;
|
||||
record.d->value = QHostAddress(ntohl(ptr->Data.A.IpAddress));
|
||||
reply->hostAddressRecords.append(record);
|
||||
} else if (ptr->wType == QDnsLookup::AAAA) {
|
||||
Q_IPV6ADDR addr;
|
||||
memcpy(&addr, &ptr->Data.AAAA.Ip6Address, sizeof(Q_IPV6ADDR));
|
||||
|
||||
QDnsHostAddressRecord record;
|
||||
record.d->name = name;
|
||||
record.d->timeToLive = ptr->dwTtl;
|
||||
record.d->value = QHostAddress(addr);
|
||||
reply->hostAddressRecords.append(record);
|
||||
} else if (ptr->wType == QDnsLookup::CNAME) {
|
||||
QDnsDomainNameRecord record;
|
||||
record.d->name = name;
|
||||
record.d->timeToLive = ptr->dwTtl;
|
||||
record.d->value = extractAndCacheHost(ptr->Data.Cname.pNameHost);
|
||||
reply->canonicalNameRecords.append(record);
|
||||
} else if (ptr->wType == QDnsLookup::MX) {
|
||||
QDnsMailExchangeRecord record;
|
||||
record.d->name = name;
|
||||
record.d->exchange = decodeLabel(QStringView(ptr->Data.Mx.pNameExchange));
|
||||
record.d->preference = ptr->Data.Mx.wPreference;
|
||||
record.d->timeToLive = ptr->dwTtl;
|
||||
reply->mailExchangeRecords.append(record);
|
||||
} else if (ptr->wType == QDnsLookup::NS) {
|
||||
QDnsDomainNameRecord record;
|
||||
record.d->name = name;
|
||||
record.d->timeToLive = ptr->dwTtl;
|
||||
record.d->value = decodeLabel(QStringView(ptr->Data.Ns.pNameHost));
|
||||
reply->nameServerRecords.append(record);
|
||||
} else if (ptr->wType == QDnsLookup::PTR) {
|
||||
QDnsDomainNameRecord record;
|
||||
record.d->name = name;
|
||||
record.d->timeToLive = ptr->dwTtl;
|
||||
record.d->value = decodeLabel(QStringView(ptr->Data.Ptr.pNameHost));
|
||||
reply->pointerRecords.append(record);
|
||||
} else if (ptr->wType == QDnsLookup::SRV) {
|
||||
QDnsServiceRecord record;
|
||||
record.d->name = name;
|
||||
record.d->target = decodeLabel(QStringView(ptr->Data.Srv.pNameTarget));
|
||||
record.d->port = ptr->Data.Srv.wPort;
|
||||
record.d->priority = ptr->Data.Srv.wPriority;
|
||||
record.d->timeToLive = ptr->dwTtl;
|
||||
record.d->weight = ptr->Data.Srv.wWeight;
|
||||
reply->serviceRecords.append(record);
|
||||
} else if (ptr->wType == QDnsLookup::TXT) {
|
||||
QDnsTextRecord record;
|
||||
record.d->name = name;
|
||||
record.d->timeToLive = ptr->dwTtl;
|
||||
for (unsigned int i = 0; i < ptr->Data.Txt.dwStringCount; ++i) {
|
||||
record.d->values << QStringView(ptr->Data.Txt.pStringArray[i]).toLatin1();
|
||||
}
|
||||
reply->textRecords.append(record);
|
||||
}
|
||||
}
|
||||
|
||||
DnsRecordListFree(results.pQueryRecords, DnsFreeRecordList);
|
||||
}
|
||||
|
||||
QT_END_NAMESPACE
|
100
qtbase/src/plugins/platforms/windows/qwin10helpers.cpp
Normal file
100
qtbase/src/plugins/platforms/windows/qwin10helpers.cpp
Normal file
@ -0,0 +1,100 @@
|
||||
// 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 "qwin10helpers.h"
|
||||
|
||||
#include <QtCore/qdebug.h>
|
||||
#include <winstring.h>
|
||||
#include <roapi.h>
|
||||
|
||||
#if defined(Q_CC_MINGW) || defined(Q_CC_CLANG)
|
||||
# define HAS_UI_VIEW_SETTINGS_INTEROP
|
||||
// Present from MSVC2015 + SDK 10 onwards
|
||||
#elif (!defined(Q_CC_MSVC) || _MSC_VER >= 1900) && WINVER >= 0x0A00
|
||||
# define HAS_UI_VIEW_SETTINGS_INTEROP
|
||||
# define HAS_UI_VIEW_SETTINGS
|
||||
#endif
|
||||
|
||||
#include <inspectable.h>
|
||||
|
||||
#ifdef HAS_UI_VIEW_SETTINGS
|
||||
# include <windows.ui.viewmanagement.h>
|
||||
#endif
|
||||
|
||||
#ifdef HAS_UI_VIEW_SETTINGS_INTEROP
|
||||
# include <uiviewsettingsinterop.h>
|
||||
#endif
|
||||
|
||||
#ifndef HAS_UI_VIEW_SETTINGS_INTEROP
|
||||
MIDL_INTERFACE("3694dbf9-8f68-44be-8ff5-195c98ede8a6")
|
||||
IUIViewSettingsInterop : public IInspectable
|
||||
{
|
||||
public:
|
||||
virtual HRESULT STDMETHODCALLTYPE GetForWindow(
|
||||
__RPC__in HWND hwnd,
|
||||
__RPC__in REFIID riid,
|
||||
__RPC__deref_out_opt void **ppv) = 0;
|
||||
};
|
||||
#endif // !HAS_UI_VIEW_SETTINGS_INTEROP
|
||||
|
||||
#ifndef HAS_UI_VIEW_SETTINGS
|
||||
namespace ABI {
|
||||
namespace Windows {
|
||||
namespace UI {
|
||||
namespace ViewManagement {
|
||||
|
||||
enum UserInteractionMode { Mouse, Touch };
|
||||
|
||||
MIDL_INTERFACE("C63657F6-8850-470D-88F8-455E16EA2C26")
|
||||
IUIViewSettings : public IInspectable
|
||||
{
|
||||
public:
|
||||
virtual HRESULT STDMETHODCALLTYPE get_UserInteractionMode(UserInteractionMode *value) = 0;
|
||||
};
|
||||
|
||||
} // namespace ViewManagement
|
||||
} // namespace UI
|
||||
} // namespace Windows
|
||||
} // namespace ABI
|
||||
#endif // HAS_UI_VIEW_SETTINGS
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
// Return tablet mode, note: Does not work for GetDesktopWindow().
|
||||
bool qt_windowsIsTabletMode(HWND hwnd)
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
const wchar_t uiViewSettingsId[] = L"Windows.UI.ViewManagement.UIViewSettings";
|
||||
HSTRING_HEADER uiViewSettingsIdRefHeader;
|
||||
HSTRING uiViewSettingsIdHs = nullptr;
|
||||
const auto uiViewSettingsIdLen = UINT32(sizeof(uiViewSettingsId) / sizeof(uiViewSettingsId[0]) - 1);
|
||||
if (FAILED(WindowsCreateStringReference(uiViewSettingsId, uiViewSettingsIdLen, &uiViewSettingsIdRefHeader, &uiViewSettingsIdHs)))
|
||||
return false;
|
||||
|
||||
IUIViewSettingsInterop *uiViewSettingsInterop = nullptr;
|
||||
// __uuidof(IUIViewSettingsInterop);
|
||||
const GUID uiViewSettingsInteropRefId = {0x3694dbf9, 0x8f68, 0x44be,{0x8f, 0xf5, 0x19, 0x5c, 0x98, 0xed, 0xe8, 0xa6}};
|
||||
|
||||
HRESULT hr = RoGetActivationFactory(uiViewSettingsIdHs, uiViewSettingsInteropRefId,
|
||||
reinterpret_cast<void **>(&uiViewSettingsInterop));
|
||||
if (FAILED(hr))
|
||||
return false;
|
||||
|
||||
// __uuidof(ABI::Windows::UI::ViewManagement::IUIViewSettings);
|
||||
const GUID uiViewSettingsRefId = {0xc63657f6, 0x8850, 0x470d,{0x88, 0xf8, 0x45, 0x5e, 0x16, 0xea, 0x2c, 0x26}};
|
||||
ABI::Windows::UI::ViewManagement::IUIViewSettings *viewSettings = nullptr;
|
||||
hr = uiViewSettingsInterop->GetForWindow(hwnd, uiViewSettingsRefId,
|
||||
reinterpret_cast<void **>(&viewSettings));
|
||||
if (SUCCEEDED(hr)) {
|
||||
ABI::Windows::UI::ViewManagement::UserInteractionMode currentMode;
|
||||
hr = viewSettings->get_UserInteractionMode(¤tMode);
|
||||
if (SUCCEEDED(hr))
|
||||
result = currentMode == 1; // Touch, 1
|
||||
viewSettings->Release();
|
||||
}
|
||||
uiViewSettingsInterop->Release();
|
||||
return result;
|
||||
}
|
||||
|
||||
QT_END_NAMESPACE
|
1606
qtbase/src/plugins/platforms/windows/qwindowscontext.cpp
Normal file
1606
qtbase/src/plugins/platforms/windows/qwindowscontext.cpp
Normal file
File diff suppressed because it is too large
Load Diff
178
qtbase/src/plugins/platforms/windows/qwindowscontext.h
Normal file
178
qtbase/src/plugins/platforms/windows/qwindowscontext.h
Normal file
@ -0,0 +1,178 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
|
||||
|
||||
#ifndef QWINDOWSCONTEXT_H
|
||||
#define QWINDOWSCONTEXT_H
|
||||
|
||||
#include "qtwindowsglobal.h"
|
||||
#include <QtCore/qt_windows.h>
|
||||
|
||||
#include <QtCore/qscopedpointer.h>
|
||||
#include <QtCore/qsharedpointer.h>
|
||||
#include <QtCore/qloggingcategory.h>
|
||||
|
||||
#define STRICT_TYPED_ITEMIDS
|
||||
#include <shlobj.h>
|
||||
#include <shlwapi.h>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
Q_DECLARE_LOGGING_CATEGORY(lcQpaWindow)
|
||||
Q_DECLARE_LOGGING_CATEGORY(lcQpaEvents)
|
||||
Q_DECLARE_LOGGING_CATEGORY(lcQpaGl)
|
||||
Q_DECLARE_LOGGING_CATEGORY(lcQpaMime)
|
||||
Q_DECLARE_LOGGING_CATEGORY(lcQpaInputMethods)
|
||||
Q_DECLARE_LOGGING_CATEGORY(lcQpaDialogs)
|
||||
Q_DECLARE_LOGGING_CATEGORY(lcQpaMenus)
|
||||
Q_DECLARE_LOGGING_CATEGORY(lcQpaTablet)
|
||||
Q_DECLARE_LOGGING_CATEGORY(lcQpaAccessibility)
|
||||
Q_DECLARE_LOGGING_CATEGORY(lcQpaUiAutomation)
|
||||
Q_DECLARE_LOGGING_CATEGORY(lcQpaTrayIcon)
|
||||
Q_DECLARE_LOGGING_CATEGORY(lcQpaScreen)
|
||||
|
||||
class QWindow;
|
||||
class QPlatformScreen;
|
||||
class QPlatformWindow;
|
||||
class QWindowsMenuBar;
|
||||
class QWindowsScreenManager;
|
||||
class QWindowsTabletSupport;
|
||||
class QWindowsWindow;
|
||||
class QWindowsMimeRegistry;
|
||||
struct QWindowCreationContext;
|
||||
struct QWindowsContextPrivate;
|
||||
class QPoint;
|
||||
class QKeyEvent;
|
||||
class QPointingDevice;
|
||||
|
||||
class QWindowsContext
|
||||
{
|
||||
Q_DISABLE_COPY_MOVE(QWindowsContext)
|
||||
public:
|
||||
using HandleBaseWindowHash = QHash<HWND, QWindowsWindow *>;
|
||||
|
||||
enum SystemInfoFlags
|
||||
{
|
||||
SI_RTL_Extensions = 0x1,
|
||||
SI_SupportsTouch = 0x2,
|
||||
SI_SupportsPointer = 0x4,
|
||||
};
|
||||
|
||||
// Verbose flag set by environment variable QT_QPA_VERBOSE
|
||||
static int verbose;
|
||||
|
||||
explicit QWindowsContext();
|
||||
~QWindowsContext();
|
||||
|
||||
bool initTouch();
|
||||
bool initTouch(unsigned integrationOptions); // For calls from QWindowsIntegration::QWindowsIntegration() only.
|
||||
void registerTouchWindows();
|
||||
bool initTablet();
|
||||
bool initPointer(unsigned integrationOptions);
|
||||
bool disposeTablet();
|
||||
|
||||
bool initPowerNotificationHandler();
|
||||
|
||||
int defaultDPI() const;
|
||||
|
||||
static QString classNamePrefix();
|
||||
QString registerWindowClass(const QWindow *w);
|
||||
QString registerWindowClass(QString cname, WNDPROC proc,
|
||||
unsigned style = 0, HBRUSH brush = nullptr,
|
||||
bool icon = false);
|
||||
HWND createDummyWindow(const QString &classNameIn,
|
||||
const wchar_t *windowName,
|
||||
WNDPROC wndProc = nullptr, DWORD style = WS_OVERLAPPED);
|
||||
|
||||
HDC displayContext() const;
|
||||
int screenDepth() const;
|
||||
|
||||
static QWindowsContext *instance();
|
||||
|
||||
void addWindow(HWND, QWindowsWindow *w);
|
||||
void removeWindow(HWND);
|
||||
|
||||
QWindowsWindow *findClosestPlatformWindow(HWND) const;
|
||||
QWindowsWindow *findPlatformWindow(HWND) const;
|
||||
QWindowsWindow *findPlatformWindow(const QWindowsMenuBar *mb) const;
|
||||
QWindow *findWindow(HWND) const;
|
||||
QWindowsWindow *findPlatformWindowAt(HWND parent, const QPoint &screenPoint,
|
||||
unsigned cwex_flags) const;
|
||||
|
||||
static bool shouldHaveNonClientDpiScaling(const QWindow *window);
|
||||
|
||||
QWindow *windowUnderMouse() const;
|
||||
void clearWindowUnderMouse();
|
||||
|
||||
inline bool windowsProc(HWND hwnd, UINT message,
|
||||
QtWindows::WindowsEventType et,
|
||||
WPARAM wParam, LPARAM lParam, LRESULT *result,
|
||||
QWindowsWindow **platformWindowPtr);
|
||||
|
||||
QWindow *keyGrabber() const;
|
||||
void setKeyGrabber(QWindow *hwnd);
|
||||
|
||||
QSharedPointer<QWindowCreationContext> setWindowCreationContext(const QSharedPointer<QWindowCreationContext> &ctx);
|
||||
QSharedPointer<QWindowCreationContext> windowCreationContext() const;
|
||||
|
||||
static void setTabletAbsoluteRange(int a);
|
||||
|
||||
static bool setProcessDpiAwareness(QtWindows::DpiAwareness dpiAwareness);
|
||||
static QtWindows::DpiAwareness processDpiAwareness();
|
||||
static QtWindows::DpiAwareness windowDpiAwareness(HWND hwnd);
|
||||
|
||||
static bool isDarkMode();
|
||||
|
||||
void setDetectAltGrModifier(bool a);
|
||||
|
||||
// Returns a combination of SystemInfoFlags
|
||||
unsigned systemInfo() const;
|
||||
|
||||
bool useRTLExtensions() const;
|
||||
QList<int> possibleKeys(const QKeyEvent *e) const;
|
||||
|
||||
HandleBaseWindowHash &windows();
|
||||
|
||||
static bool isSessionLocked();
|
||||
|
||||
QWindowsMimeRegistry &mimeConverter() const;
|
||||
QWindowsScreenManager &screenManager();
|
||||
QWindowsTabletSupport *tabletSupport() const;
|
||||
|
||||
bool asyncExpose() const;
|
||||
void setAsyncExpose(bool value);
|
||||
|
||||
static void forceNcCalcSize(HWND hwnd);
|
||||
|
||||
static bool systemParametersInfo(unsigned action, unsigned param, void *out, unsigned dpi = 0);
|
||||
static bool systemParametersInfoForScreen(unsigned action, unsigned param, void *out,
|
||||
const QPlatformScreen *screen = nullptr);
|
||||
static bool systemParametersInfoForWindow(unsigned action, unsigned param, void *out,
|
||||
const QPlatformWindow *win = nullptr);
|
||||
static bool nonClientMetrics(NONCLIENTMETRICS *ncm, unsigned dpi = 0);
|
||||
static bool nonClientMetricsForScreen(NONCLIENTMETRICS *ncm,
|
||||
const QPlatformScreen *screen = nullptr);
|
||||
static bool nonClientMetricsForWindow(NONCLIENTMETRICS *ncm,
|
||||
const QPlatformWindow *win = nullptr);
|
||||
|
||||
static DWORD readAdvancedExplorerSettings(const wchar_t *subKey, DWORD defaultValue);
|
||||
|
||||
static bool filterNativeEvent(MSG *msg, LRESULT *result);
|
||||
static bool filterNativeEvent(QWindow *window, MSG *msg, LRESULT *result);
|
||||
|
||||
private:
|
||||
void handleFocusEvent(QtWindows::WindowsEventType et, QWindowsWindow *w);
|
||||
#ifndef QT_NO_CONTEXTMENU
|
||||
bool handleContextMenuEvent(QWindow *window, const MSG &msg);
|
||||
#endif
|
||||
void handleExitSizeMove(QWindow *window);
|
||||
void unregisterWindowClasses();
|
||||
|
||||
QScopedPointer<QWindowsContextPrivate> d;
|
||||
static QWindowsContext *m_instance;
|
||||
};
|
||||
|
||||
extern "C" LRESULT QT_WIN_CALLBACK qWindowsWndProc(HWND, UINT, WPARAM, LPARAM);
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
#endif // QWINDOWSCONTEXT_H
|
784
qtbase/src/plugins/platforms/windows/qwindowsdrag.cpp
Normal file
784
qtbase/src/plugins/platforms/windows/qwindowsdrag.cpp
Normal file
@ -0,0 +1,784 @@
|
||||
// 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 <QtCore/qt_windows.h>
|
||||
#include "qwindowsdrag.h"
|
||||
#include "qwindowscontext.h"
|
||||
#include "qwindowsscreen.h"
|
||||
#if QT_CONFIG(clipboard)
|
||||
# include "qwindowsclipboard.h"
|
||||
#endif
|
||||
#include "qwindowsintegration.h"
|
||||
#include "qwindowsdropdataobject.h"
|
||||
#include "qwindowswindow.h"
|
||||
#include "qwindowsmousehandler.h"
|
||||
#include "qwindowscursor.h"
|
||||
#include "qwindowskeymapper.h"
|
||||
|
||||
#include <QtGui/qevent.h>
|
||||
#include <QtGui/qpixmap.h>
|
||||
#include <QtGui/qpainter.h>
|
||||
#include <QtGui/qrasterwindow.h>
|
||||
#include <QtGui/qguiapplication.h>
|
||||
#include <qpa/qwindowsysteminterface_p.h>
|
||||
#include <QtGui/private/qdnd_p.h>
|
||||
#include <QtGui/private/qguiapplication_p.h>
|
||||
#include <QtGui/private/qhighdpiscaling_p.h>
|
||||
|
||||
#include <QtCore/qdebug.h>
|
||||
#include <QtCore/qbuffer.h>
|
||||
#include <QtCore/qpoint.h>
|
||||
|
||||
#include <shlobj.h>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
/*!
|
||||
\class QWindowsDragCursorWindow
|
||||
\brief A toplevel window showing the drag icon in case of touch drag.
|
||||
|
||||
\sa QWindowsOleDropSource
|
||||
\internal
|
||||
*/
|
||||
|
||||
class QWindowsDragCursorWindow : public QRasterWindow
|
||||
{
|
||||
public:
|
||||
explicit QWindowsDragCursorWindow(QWindow *parent = nullptr);
|
||||
|
||||
void setPixmap(const QPixmap &p);
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *) override
|
||||
{
|
||||
QPainter painter(this);
|
||||
painter.drawPixmap(0, 0, m_pixmap);
|
||||
}
|
||||
|
||||
private:
|
||||
QPixmap m_pixmap;
|
||||
};
|
||||
|
||||
QWindowsDragCursorWindow::QWindowsDragCursorWindow(QWindow *parent)
|
||||
: QRasterWindow(parent)
|
||||
{
|
||||
QSurfaceFormat windowFormat = format();
|
||||
windowFormat.setAlphaBufferSize(8);
|
||||
setFormat(windowFormat);
|
||||
setObjectName(QStringLiteral("QWindowsDragCursorWindow"));
|
||||
setFlags(Qt::Popup | Qt::NoDropShadowWindowHint
|
||||
| Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint
|
||||
| Qt::WindowDoesNotAcceptFocus | Qt::WindowTransparentForInput);
|
||||
}
|
||||
|
||||
void QWindowsDragCursorWindow::setPixmap(const QPixmap &p)
|
||||
{
|
||||
if (p.cacheKey() == m_pixmap.cacheKey())
|
||||
return;
|
||||
const QSize oldSize = m_pixmap.size();
|
||||
QSize newSize = p.size();
|
||||
qCDebug(lcQpaMime) << __FUNCTION__ << p.cacheKey() << newSize;
|
||||
m_pixmap = p;
|
||||
if (oldSize != newSize) {
|
||||
const qreal pixDevicePixelRatio = p.devicePixelRatio();
|
||||
if (pixDevicePixelRatio > 1.0 && qFuzzyCompare(pixDevicePixelRatio, devicePixelRatio()))
|
||||
newSize /= qRound(pixDevicePixelRatio);
|
||||
resize(newSize);
|
||||
}
|
||||
if (isVisible())
|
||||
update();
|
||||
}
|
||||
|
||||
/*!
|
||||
\class QWindowsDropMimeData
|
||||
\brief Special mime data class for data retrieval from Drag operations.
|
||||
|
||||
Implementation of QWindowsInternalMimeDataBase which retrieves the
|
||||
current drop data object from QWindowsDrag.
|
||||
|
||||
\sa QWindowsDrag
|
||||
\internal
|
||||
*/
|
||||
|
||||
IDataObject *QWindowsDropMimeData::retrieveDataObject() const
|
||||
{
|
||||
return QWindowsDrag::instance()->dropDataObject();
|
||||
}
|
||||
|
||||
static inline Qt::DropActions translateToQDragDropActions(DWORD pdwEffects)
|
||||
{
|
||||
Qt::DropActions actions = Qt::IgnoreAction;
|
||||
if (pdwEffects & DROPEFFECT_LINK)
|
||||
actions |= Qt::LinkAction;
|
||||
if (pdwEffects & DROPEFFECT_COPY)
|
||||
actions |= Qt::CopyAction;
|
||||
if (pdwEffects & DROPEFFECT_MOVE)
|
||||
actions |= Qt::MoveAction;
|
||||
return actions;
|
||||
}
|
||||
|
||||
static inline Qt::DropAction translateToQDragDropAction(DWORD pdwEffect)
|
||||
{
|
||||
if (pdwEffect & DROPEFFECT_LINK)
|
||||
return Qt::LinkAction;
|
||||
if (pdwEffect & DROPEFFECT_COPY)
|
||||
return Qt::CopyAction;
|
||||
if (pdwEffect & DROPEFFECT_MOVE)
|
||||
return Qt::MoveAction;
|
||||
return Qt::IgnoreAction;
|
||||
}
|
||||
|
||||
static inline DWORD translateToWinDragEffects(Qt::DropActions action)
|
||||
{
|
||||
DWORD effect = DROPEFFECT_NONE;
|
||||
if (action & Qt::LinkAction)
|
||||
effect |= DROPEFFECT_LINK;
|
||||
if (action & Qt::CopyAction)
|
||||
effect |= DROPEFFECT_COPY;
|
||||
if (action & Qt::MoveAction)
|
||||
effect |= DROPEFFECT_MOVE;
|
||||
return effect;
|
||||
}
|
||||
|
||||
static inline Qt::KeyboardModifiers toQtKeyboardModifiers(DWORD keyState)
|
||||
{
|
||||
Qt::KeyboardModifiers modifiers = Qt::NoModifier;
|
||||
|
||||
if (keyState & MK_SHIFT)
|
||||
modifiers |= Qt::ShiftModifier;
|
||||
if (keyState & MK_CONTROL)
|
||||
modifiers |= Qt::ControlModifier;
|
||||
if (keyState & MK_ALT)
|
||||
modifiers |= Qt::AltModifier;
|
||||
|
||||
return modifiers;
|
||||
}
|
||||
|
||||
static Qt::KeyboardModifiers lastModifiers = Qt::NoModifier;
|
||||
static Qt::MouseButtons lastButtons = Qt::NoButton;
|
||||
|
||||
/*!
|
||||
\class QWindowsOleDropSource
|
||||
\brief Implementation of IDropSource
|
||||
|
||||
Used for drag operations.
|
||||
|
||||
\sa QWindowsDrag
|
||||
\internal
|
||||
*/
|
||||
|
||||
class QWindowsOleDropSource : public QWindowsComBase<IDropSource>
|
||||
{
|
||||
public:
|
||||
enum Mode {
|
||||
MouseDrag,
|
||||
TouchDrag // Mouse cursor suppressed, use window as cursor.
|
||||
};
|
||||
|
||||
explicit QWindowsOleDropSource(QWindowsDrag *drag);
|
||||
~QWindowsOleDropSource() override;
|
||||
|
||||
void createCursors();
|
||||
|
||||
// IDropSource methods
|
||||
STDMETHOD(QueryContinueDrag)(BOOL fEscapePressed, DWORD grfKeyState) override;
|
||||
STDMETHOD(GiveFeedback)(DWORD dwEffect) override;
|
||||
|
||||
private:
|
||||
struct CursorEntry {
|
||||
CursorEntry() : cacheKey(0) {}
|
||||
CursorEntry(const QPixmap &p, qint64 cK, const CursorHandlePtr &c, const QPoint &h) :
|
||||
pixmap(p), cacheKey(cK), cursor(c), hotSpot(h) {}
|
||||
|
||||
QPixmap pixmap;
|
||||
qint64 cacheKey; // Cache key of cursor
|
||||
CursorHandlePtr cursor;
|
||||
QPoint hotSpot;
|
||||
};
|
||||
|
||||
typedef QMap<Qt::DropAction, CursorEntry> ActionCursorMap;
|
||||
|
||||
Mode m_mode;
|
||||
QWindowsDrag *m_drag;
|
||||
QPointer<QWindow> m_windowUnderMouse;
|
||||
Qt::MouseButtons m_currentButtons;
|
||||
ActionCursorMap m_cursors;
|
||||
QWindowsDragCursorWindow *m_touchDragWindow;
|
||||
|
||||
#ifndef QT_NO_DEBUG_STREAM
|
||||
friend QDebug operator<<(QDebug, const QWindowsOleDropSource::CursorEntry &);
|
||||
#endif
|
||||
};
|
||||
|
||||
QWindowsOleDropSource::QWindowsOleDropSource(QWindowsDrag *drag)
|
||||
: m_mode(QWindowsCursor::cursorState() != QWindowsCursor::State::Suppressed ? MouseDrag : TouchDrag)
|
||||
, m_drag(drag)
|
||||
, m_windowUnderMouse(QWindowsContext::instance()->windowUnderMouse())
|
||||
, m_currentButtons(Qt::NoButton)
|
||||
, m_touchDragWindow(nullptr)
|
||||
{
|
||||
qCDebug(lcQpaMime) << __FUNCTION__ << m_mode;
|
||||
}
|
||||
|
||||
QWindowsOleDropSource::~QWindowsOleDropSource()
|
||||
{
|
||||
m_cursors.clear();
|
||||
delete m_touchDragWindow;
|
||||
qCDebug(lcQpaMime) << __FUNCTION__;
|
||||
}
|
||||
|
||||
#ifndef QT_NO_DEBUG_STREAM
|
||||
QDebug operator<<(QDebug d, const QWindowsOleDropSource::CursorEntry &e)
|
||||
{
|
||||
d << "CursorEntry:" << e.pixmap.size() << '#' << e.cacheKey
|
||||
<< "HCURSOR" << e.cursor->handle() << "hotspot:" << e.hotSpot;
|
||||
return d;
|
||||
}
|
||||
#endif // !QT_NO_DEBUG_STREAM
|
||||
|
||||
/*!
|
||||
\brief Blend custom pixmap with cursors.
|
||||
*/
|
||||
|
||||
void QWindowsOleDropSource::createCursors()
|
||||
{
|
||||
const QDrag *drag = m_drag->currentDrag();
|
||||
const QPixmap pixmap = drag->pixmap();
|
||||
const bool hasPixmap = !pixmap.isNull();
|
||||
|
||||
// Find screen for drag. Could be obtained from QDrag::source(), but that might be a QWidget.
|
||||
const QPlatformScreen *platformScreen = QWindowsContext::instance()->screenManager().screenAtDp(QWindowsCursor::mousePosition());
|
||||
if (!platformScreen) {
|
||||
if (const QScreen *primaryScreen = QGuiApplication::primaryScreen())
|
||||
platformScreen = primaryScreen->handle();
|
||||
}
|
||||
Q_ASSERT(platformScreen);
|
||||
QPlatformCursor *platformCursor = platformScreen->cursor();
|
||||
|
||||
if (GetSystemMetrics (SM_REMOTESESSION) != 0) {
|
||||
/* Workaround for RDP issues with large cursors.
|
||||
* Touch drag window seems to work just fine...
|
||||
* 96 pixel is a 'large' mouse cursor, according to RDP spec */
|
||||
const int rdpLargeCursor = qRound(qreal(96) / QHighDpiScaling::factor(platformScreen));
|
||||
if (pixmap.width() > rdpLargeCursor || pixmap.height() > rdpLargeCursor)
|
||||
m_mode = TouchDrag;
|
||||
}
|
||||
|
||||
qreal pixmapScaleFactor = 1;
|
||||
qreal hotSpotScaleFactor = 1;
|
||||
if (m_mode != TouchDrag) { // Touch drag: pixmap is shown in a separate QWindow, which will be scaled.)
|
||||
hotSpotScaleFactor = QHighDpiScaling::factor(platformScreen);
|
||||
pixmapScaleFactor = hotSpotScaleFactor / pixmap.devicePixelRatio();
|
||||
}
|
||||
QPixmap scaledPixmap = qFuzzyCompare(pixmapScaleFactor, 1.0)
|
||||
? pixmap
|
||||
: pixmap.scaled((QSizeF(pixmap.size()) * pixmapScaleFactor).toSize(),
|
||||
Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
scaledPixmap.setDevicePixelRatio(1);
|
||||
|
||||
Qt::DropAction actions[] = { Qt::MoveAction, Qt::CopyAction, Qt::LinkAction, Qt::IgnoreAction };
|
||||
int actionCount = int(sizeof(actions) / sizeof(actions[0]));
|
||||
if (!hasPixmap)
|
||||
--actionCount; // No Qt::IgnoreAction unless pixmap
|
||||
const QPoint hotSpot = qFuzzyCompare(hotSpotScaleFactor, 1.0)
|
||||
? drag->hotSpot()
|
||||
: (QPointF(drag->hotSpot()) * hotSpotScaleFactor).toPoint();
|
||||
for (int cnum = 0; cnum < actionCount; ++cnum) {
|
||||
const Qt::DropAction action = actions[cnum];
|
||||
QPixmap cursorPixmap = drag->dragCursor(action);
|
||||
if (cursorPixmap.isNull() && platformCursor)
|
||||
cursorPixmap = static_cast<QWindowsCursor *>(platformCursor)->dragDefaultCursor(action);
|
||||
const qint64 cacheKey = cursorPixmap.cacheKey();
|
||||
const auto it = m_cursors.find(action);
|
||||
if (it != m_cursors.end() && it.value().cacheKey == cacheKey)
|
||||
continue;
|
||||
if (cursorPixmap.isNull()) {
|
||||
qWarning("%s: Unable to obtain drag cursor for %d.", __FUNCTION__, action);
|
||||
continue;
|
||||
}
|
||||
|
||||
QPoint newHotSpot(0, 0);
|
||||
QPixmap newPixmap = cursorPixmap;
|
||||
|
||||
if (hasPixmap) {
|
||||
const int x1 = qMin(-hotSpot.x(), 0);
|
||||
const int x2 = qMax(scaledPixmap.width() - hotSpot.x(), cursorPixmap.width());
|
||||
const int y1 = qMin(-hotSpot.y(), 0);
|
||||
const int y2 = qMax(scaledPixmap.height() - hotSpot.y(), cursorPixmap.height());
|
||||
QPixmap newCursor(x2 - x1 + 1, y2 - y1 + 1);
|
||||
newCursor.fill(Qt::transparent);
|
||||
QPainter p(&newCursor);
|
||||
const QPoint pmDest = QPoint(qMax(0, -hotSpot.x()), qMax(0, -hotSpot.y()));
|
||||
p.drawPixmap(pmDest, scaledPixmap);
|
||||
p.drawPixmap(qMax(0, hotSpot.x()),qMax(0, hotSpot.y()), cursorPixmap);
|
||||
newPixmap = newCursor;
|
||||
newHotSpot = QPoint(qMax(0, hotSpot.x()), qMax(0, hotSpot.y()));
|
||||
}
|
||||
|
||||
if (const HCURSOR sysCursor = QWindowsCursor::createPixmapCursor(newPixmap, newHotSpot)) {
|
||||
const CursorEntry entry(newPixmap, cacheKey, CursorHandlePtr(new CursorHandle(sysCursor)), newHotSpot);
|
||||
if (it == m_cursors.end())
|
||||
m_cursors.insert(action, entry);
|
||||
else
|
||||
it.value() = entry;
|
||||
}
|
||||
}
|
||||
#ifndef QT_NO_DEBUG_OUTPUT
|
||||
if (lcQpaMime().isDebugEnabled())
|
||||
qCDebug(lcQpaMime) << __FUNCTION__ << "pixmap" << pixmap.size() << m_cursors.size() << "cursors:\n" << m_cursors;
|
||||
#endif // !QT_NO_DEBUG_OUTPUT
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief Check for cancel.
|
||||
*/
|
||||
|
||||
QT_ENSURE_STACK_ALIGNED_FOR_SSE STDMETHODIMP
|
||||
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();
|
||||
|
||||
SCODE result = S_OK;
|
||||
if (fEscapePressed || QWindowsDrag::isCanceled()) {
|
||||
result = DRAGDROP_S_CANCEL;
|
||||
buttons = Qt::NoButton;
|
||||
} else {
|
||||
if (buttons && !m_currentButtons) {
|
||||
m_currentButtons = buttons;
|
||||
} else if (m_currentButtons != buttons) { // Button changed: Complete Drop operation.
|
||||
result = DRAGDROP_S_DROP;
|
||||
}
|
||||
}
|
||||
|
||||
switch (result) {
|
||||
case DRAGDROP_S_DROP:
|
||||
case DRAGDROP_S_CANCEL:
|
||||
if (!m_windowUnderMouse.isNull() && m_mode != TouchDrag && fEscapePressed == FALSE
|
||||
&& buttons != lastButtons) {
|
||||
// QTBUG 66447: Synthesize a mouse release to the window under mouse at
|
||||
// start of the DnD operation as Windows does not send any.
|
||||
const QPoint globalPos = QWindowsCursor::mousePosition();
|
||||
const QPoint localPos = m_windowUnderMouse->handle()->mapFromGlobal(globalPos);
|
||||
QWindowSystemInterface::handleMouseEvent(m_windowUnderMouse.data(),
|
||||
QPointF(localPos), QPointF(globalPos),
|
||||
QWindowsMouseHandler::queryMouseButtons(),
|
||||
Qt::LeftButton, QEvent::MouseButtonRelease);
|
||||
}
|
||||
m_currentButtons = Qt::NoButton;
|
||||
break;
|
||||
|
||||
default:
|
||||
QGuiApplication::processEvents();
|
||||
break;
|
||||
}
|
||||
|
||||
if (QWindowsContext::verbose > 1 || result != S_OK) {
|
||||
qCDebug(lcQpaMime) << __FUNCTION__ << "fEscapePressed=" << fEscapePressed
|
||||
<< "grfKeyState=" << grfKeyState << "buttons" << m_currentButtons
|
||||
<< "returns 0x" << Qt::hex << int(result) << Qt::dec;
|
||||
}
|
||||
return ResultFromScode(result);
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief Give feedback: Change cursor according to action.
|
||||
*/
|
||||
|
||||
QT_ENSURE_STACK_ALIGNED_FOR_SSE STDMETHODIMP
|
||||
QWindowsOleDropSource::GiveFeedback(DWORD dwEffect)
|
||||
{
|
||||
const Qt::DropAction action = translateToQDragDropAction(dwEffect);
|
||||
m_drag->updateAction(action);
|
||||
|
||||
const qint64 currentCacheKey = m_drag->currentDrag()->dragCursor(action).cacheKey();
|
||||
auto it = m_cursors.constFind(action);
|
||||
// If a custom drag cursor is set, check its cache key to detect changes.
|
||||
if (it == m_cursors.constEnd() || (currentCacheKey && currentCacheKey != it.value().cacheKey)) {
|
||||
createCursors();
|
||||
it = m_cursors.constFind(action);
|
||||
}
|
||||
|
||||
if (it != m_cursors.constEnd()) {
|
||||
const CursorEntry &e = it.value();
|
||||
switch (m_mode) {
|
||||
case MouseDrag:
|
||||
SetCursor(e.cursor->handle());
|
||||
break;
|
||||
case TouchDrag:
|
||||
// "Touch drag" with an unsuppressed cursor may happen with RDP (see createCursors())
|
||||
if (QWindowsCursor::cursorState() != QWindowsCursor::State::Suppressed)
|
||||
SetCursor(nullptr);
|
||||
if (!m_touchDragWindow)
|
||||
m_touchDragWindow = new QWindowsDragCursorWindow;
|
||||
m_touchDragWindow->setPixmap(e.pixmap);
|
||||
m_touchDragWindow->setFramePosition(QCursor::pos() - e.hotSpot);
|
||||
if (!m_touchDragWindow->isVisible())
|
||||
m_touchDragWindow->show();
|
||||
break;
|
||||
}
|
||||
return ResultFromScode(S_OK);
|
||||
}
|
||||
|
||||
return ResultFromScode(DRAGDROP_S_USEDEFAULTCURSORS);
|
||||
}
|
||||
|
||||
/*!
|
||||
\class QWindowsOleDropTarget
|
||||
\brief Implementation of IDropTarget
|
||||
|
||||
To be registered for each window. Currently, drop sites
|
||||
are enabled for top levels. The child window handling
|
||||
(sending DragEnter/Leave, etc) is handled in here.
|
||||
|
||||
\sa QWindowsDrag
|
||||
\internal
|
||||
*/
|
||||
|
||||
QWindowsOleDropTarget::QWindowsOleDropTarget(QWindow *w) : m_window(w)
|
||||
{
|
||||
qCDebug(lcQpaMime) << __FUNCTION__ << this << w;
|
||||
}
|
||||
|
||||
QWindowsOleDropTarget::~QWindowsOleDropTarget()
|
||||
{
|
||||
qCDebug(lcQpaMime) << __FUNCTION__ << this;
|
||||
}
|
||||
|
||||
void QWindowsOleDropTarget::handleDrag(QWindow *window, DWORD grfKeyState,
|
||||
const QPoint &point, LPDWORD pdwEffect)
|
||||
{
|
||||
Q_ASSERT(window);
|
||||
m_lastPoint = point;
|
||||
m_lastKeyState = grfKeyState;
|
||||
|
||||
QWindowsDrag *windowsDrag = QWindowsDrag::instance();
|
||||
const Qt::DropActions actions = translateToQDragDropActions(*pdwEffect);
|
||||
|
||||
lastModifiers = toQtKeyboardModifiers(grfKeyState);
|
||||
lastButtons = QWindowsMouseHandler::queryMouseButtons();
|
||||
|
||||
const QPlatformDragQtResponse response =
|
||||
QWindowSystemInterface::handleDrag(window, windowsDrag->dropData(),
|
||||
m_lastPoint, actions,
|
||||
lastButtons, lastModifiers);
|
||||
|
||||
m_answerRect = response.answerRect();
|
||||
const Qt::DropAction action = response.acceptedAction();
|
||||
if (response.isAccepted()) {
|
||||
m_chosenEffect = translateToWinDragEffects(action);
|
||||
} else {
|
||||
m_chosenEffect = DROPEFFECT_NONE;
|
||||
}
|
||||
*pdwEffect = m_chosenEffect;
|
||||
qCDebug(lcQpaMime) << __FUNCTION__ << m_window
|
||||
<< windowsDrag->dropData() << " supported actions=" << actions
|
||||
<< " mods=" << lastModifiers << " mouse=" << lastButtons
|
||||
<< " accepted: " << response.isAccepted() << action
|
||||
<< m_answerRect << " effect" << *pdwEffect;
|
||||
}
|
||||
|
||||
QT_ENSURE_STACK_ALIGNED_FOR_SSE STDMETHODIMP
|
||||
QWindowsOleDropTarget::DragEnter(LPDATAOBJECT pDataObj, DWORD grfKeyState,
|
||||
POINTL pt, LPDWORD pdwEffect)
|
||||
{
|
||||
if (IDropTargetHelper* dh = QWindowsDrag::instance()->dropHelper())
|
||||
dh->DragEnter(reinterpret_cast<HWND>(m_window->winId()), pDataObj, reinterpret_cast<POINT*>(&pt), *pdwEffect);
|
||||
|
||||
qCDebug(lcQpaMime) << __FUNCTION__ << "widget=" << m_window << " key=" << grfKeyState
|
||||
<< "pt=" << pt.x << pt.y;
|
||||
|
||||
QWindowsDrag::instance()->setDropDataObject(pDataObj);
|
||||
pDataObj->AddRef();
|
||||
const QPoint point = QWindowsGeometryHint::mapFromGlobal(m_window, QPoint(pt.x,pt.y));
|
||||
handleDrag(m_window, grfKeyState, point, pdwEffect);
|
||||
return NOERROR;
|
||||
}
|
||||
|
||||
QT_ENSURE_STACK_ALIGNED_FOR_SSE STDMETHODIMP
|
||||
QWindowsOleDropTarget::DragOver(DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect)
|
||||
{
|
||||
if (IDropTargetHelper* dh = QWindowsDrag::instance()->dropHelper())
|
||||
dh->DragOver(reinterpret_cast<POINT*>(&pt), *pdwEffect);
|
||||
|
||||
qCDebug(lcQpaMime) << __FUNCTION__ << "m_window" << m_window << "key=" << grfKeyState
|
||||
<< "pt=" << pt.x << pt.y;
|
||||
const QPoint tmpPoint = QWindowsGeometryHint::mapFromGlobal(m_window, QPoint(pt.x,pt.y));
|
||||
// see if we should compress this event
|
||||
if ((tmpPoint == m_lastPoint || m_answerRect.contains(tmpPoint))
|
||||
&& m_lastKeyState == grfKeyState) {
|
||||
*pdwEffect = m_chosenEffect;
|
||||
qCDebug(lcQpaMime) << __FUNCTION__ << "compressed event";
|
||||
return NOERROR;
|
||||
}
|
||||
|
||||
handleDrag(m_window, grfKeyState, tmpPoint, pdwEffect);
|
||||
return NOERROR;
|
||||
}
|
||||
|
||||
QT_ENSURE_STACK_ALIGNED_FOR_SSE STDMETHODIMP
|
||||
QWindowsOleDropTarget::DragLeave()
|
||||
{
|
||||
if (IDropTargetHelper* dh = QWindowsDrag::instance()->dropHelper())
|
||||
dh->DragLeave();
|
||||
|
||||
qCDebug(lcQpaMime) << __FUNCTION__ << ' ' << m_window;
|
||||
|
||||
lastModifiers = QWindowsKeyMapper::queryKeyboardModifiers();
|
||||
lastButtons = QWindowsMouseHandler::queryMouseButtons();
|
||||
|
||||
QWindowSystemInterface::handleDrag(m_window, nullptr, QPoint(), Qt::IgnoreAction,
|
||||
Qt::NoButton, Qt::NoModifier);
|
||||
|
||||
if (!QDragManager::self()->source())
|
||||
m_lastKeyState = 0;
|
||||
QWindowsDrag::instance()->releaseDropDataObject();
|
||||
|
||||
return NOERROR;
|
||||
}
|
||||
|
||||
#define KEY_STATE_BUTTON_MASK (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON)
|
||||
|
||||
QT_ENSURE_STACK_ALIGNED_FOR_SSE STDMETHODIMP
|
||||
QWindowsOleDropTarget::Drop(LPDATAOBJECT pDataObj, DWORD grfKeyState,
|
||||
POINTL pt, LPDWORD pdwEffect)
|
||||
{
|
||||
if (IDropTargetHelper* dh = QWindowsDrag::instance()->dropHelper())
|
||||
dh->Drop(pDataObj, reinterpret_cast<POINT*>(&pt), *pdwEffect);
|
||||
|
||||
qCDebug(lcQpaMime) << __FUNCTION__ << ' ' << m_window
|
||||
<< "keys=" << grfKeyState << "pt=" << pt.x << ',' << pt.y;
|
||||
|
||||
m_lastPoint = QWindowsGeometryHint::mapFromGlobal(m_window, QPoint(pt.x,pt.y));
|
||||
|
||||
QWindowsDrag *windowsDrag = QWindowsDrag::instance();
|
||||
|
||||
lastModifiers = toQtKeyboardModifiers(grfKeyState);
|
||||
lastButtons = QWindowsMouseHandler::queryMouseButtons();
|
||||
|
||||
const QPlatformDropQtResponse response =
|
||||
QWindowSystemInterface::handleDrop(m_window, windowsDrag->dropData(),
|
||||
m_lastPoint,
|
||||
translateToQDragDropActions(*pdwEffect),
|
||||
lastButtons,
|
||||
lastModifiers);
|
||||
|
||||
m_lastKeyState = grfKeyState;
|
||||
|
||||
if (response.isAccepted()) {
|
||||
const Qt::DropAction action = response.acceptedAction();
|
||||
if (action == Qt::MoveAction || action == Qt::TargetMoveAction) {
|
||||
if (action == Qt::MoveAction)
|
||||
m_chosenEffect = DROPEFFECT_MOVE;
|
||||
else
|
||||
m_chosenEffect = DROPEFFECT_COPY;
|
||||
HGLOBAL hData = GlobalAlloc(0, sizeof(DWORD));
|
||||
if (hData) {
|
||||
auto *moveEffect = reinterpret_cast<DWORD *>(GlobalLock(hData));
|
||||
*moveEffect = DROPEFFECT_MOVE;
|
||||
GlobalUnlock(hData);
|
||||
STGMEDIUM medium;
|
||||
memset(&medium, 0, sizeof(STGMEDIUM));
|
||||
medium.tymed = TYMED_HGLOBAL;
|
||||
medium.hGlobal = hData;
|
||||
FORMATETC format;
|
||||
format.cfFormat = CLIPFORMAT(RegisterClipboardFormat(CFSTR_PERFORMEDDROPEFFECT));
|
||||
format.tymed = TYMED_HGLOBAL;
|
||||
format.ptd = nullptr;
|
||||
format.dwAspect = 1;
|
||||
format.lindex = -1;
|
||||
windowsDrag->dropDataObject()->SetData(&format, &medium, true);
|
||||
}
|
||||
} else {
|
||||
m_chosenEffect = translateToWinDragEffects(action);
|
||||
}
|
||||
} else {
|
||||
m_chosenEffect = DROPEFFECT_NONE;
|
||||
}
|
||||
*pdwEffect = m_chosenEffect;
|
||||
|
||||
windowsDrag->releaseDropDataObject();
|
||||
return NOERROR;
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
\class QWindowsDrag
|
||||
\brief Windows drag implementation.
|
||||
\internal
|
||||
*/
|
||||
|
||||
bool QWindowsDrag::m_canceled = false;
|
||||
bool QWindowsDrag::m_dragging = false;
|
||||
|
||||
QWindowsDrag::QWindowsDrag() = default;
|
||||
|
||||
QWindowsDrag::~QWindowsDrag()
|
||||
{
|
||||
if (m_cachedDropTargetHelper)
|
||||
m_cachedDropTargetHelper->Release();
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief Return data for a drop in process. If it stems from a current drag, use a shortcut.
|
||||
*/
|
||||
|
||||
QMimeData *QWindowsDrag::dropData()
|
||||
{
|
||||
if (const QDrag *drag = currentDrag())
|
||||
return drag->mimeData();
|
||||
return &m_dropData;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief May be used to handle extended cursors functionality for drags from outside the app.
|
||||
*/
|
||||
IDropTargetHelper* QWindowsDrag::dropHelper() {
|
||||
if (!m_cachedDropTargetHelper) {
|
||||
CoCreateInstance(CLSID_DragDropHelper, nullptr, CLSCTX_INPROC_SERVER,
|
||||
IID_IDropTargetHelper,
|
||||
reinterpret_cast<void**>(&m_cachedDropTargetHelper));
|
||||
}
|
||||
return m_cachedDropTargetHelper;
|
||||
}
|
||||
|
||||
// Workaround for DoDragDrop() not working with touch/pen input, causing DnD to hang until the mouse is moved.
|
||||
// We process pointer messages for touch/pen and generate mouse input through SendInput() to trigger DoDragDrop()
|
||||
static HRESULT startDoDragDrop(LPDATAOBJECT pDataObj, LPDROPSOURCE pDropSource, DWORD dwOKEffects, LPDWORD pdwEffect)
|
||||
{
|
||||
QWindow *underMouse = QWindowsContext::instance()->windowUnderMouse();
|
||||
const HWND hwnd = underMouse ? reinterpret_cast<HWND>(underMouse->winId()) : ::GetFocus();
|
||||
bool starting = false;
|
||||
|
||||
for (;;) {
|
||||
MSG msg{};
|
||||
if (::GetMessage(&msg, hwnd, 0, 0) > 0) {
|
||||
|
||||
if (msg.message == WM_MOUSEMOVE) {
|
||||
|
||||
// Only consider the first simulated event, or actual mouse messages.
|
||||
if (!starting && (msg.wParam & (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON | MK_XBUTTON1 | MK_XBUTTON2)) == 0)
|
||||
return E_FAIL;
|
||||
|
||||
return ::DoDragDrop(pDataObj, pDropSource, dwOKEffects, pdwEffect);
|
||||
}
|
||||
|
||||
if (msg.message == WM_POINTERUPDATE) {
|
||||
|
||||
const quint32 pointerId = GET_POINTERID_WPARAM(msg.wParam);
|
||||
|
||||
POINTER_INFO pointerInfo{};
|
||||
if (!GetPointerInfo(pointerId, &pointerInfo))
|
||||
return E_FAIL;
|
||||
|
||||
if (pointerInfo.pointerFlags & POINTER_FLAG_PRIMARY) {
|
||||
|
||||
DWORD flags = MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_VIRTUALDESK | MOUSEEVENTF_MOVE;
|
||||
if (IS_POINTER_FIRSTBUTTON_WPARAM(msg.wParam))
|
||||
flags |= MOUSEEVENTF_LEFTDOWN;
|
||||
if (IS_POINTER_SECONDBUTTON_WPARAM(msg.wParam))
|
||||
flags |= MOUSEEVENTF_RIGHTDOWN;
|
||||
if (IS_POINTER_THIRDBUTTON_WPARAM(msg.wParam))
|
||||
flags |= MOUSEEVENTF_MIDDLEDOWN;
|
||||
|
||||
if (!starting) {
|
||||
POINT pt{};
|
||||
if (::GetCursorPos(&pt)) {
|
||||
|
||||
// Send mouse input that can generate a WM_MOUSEMOVE message.
|
||||
if ((flags & MOUSEEVENTF_LEFTDOWN || flags & MOUSEEVENTF_RIGHTDOWN || flags & MOUSEEVENTF_MIDDLEDOWN)
|
||||
&& (pt.x != pointerInfo.ptPixelLocation.x || pt.y != pointerInfo.ptPixelLocation.y)) {
|
||||
|
||||
const int origin_x = ::GetSystemMetrics(SM_XVIRTUALSCREEN);
|
||||
const int origin_y = ::GetSystemMetrics(SM_YVIRTUALSCREEN);
|
||||
const int virt_w = ::GetSystemMetrics(SM_CXVIRTUALSCREEN);
|
||||
const int virt_h = ::GetSystemMetrics(SM_CYVIRTUALSCREEN);
|
||||
const int virt_x = pointerInfo.ptPixelLocation.x - origin_x;
|
||||
const int virt_y = pointerInfo.ptPixelLocation.y - origin_y;
|
||||
|
||||
INPUT input{};
|
||||
input.type = INPUT_MOUSE;
|
||||
input.mi.dx = static_cast<DWORD>(virt_x * (65535.0 / virt_w));
|
||||
input.mi.dy = static_cast<DWORD>(virt_y * (65535.0 / virt_h));
|
||||
input.mi.dwFlags = flags;
|
||||
|
||||
::SendInput(1, &input, sizeof(input));
|
||||
starting = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Handle other messages.
|
||||
qWindowsWndProc(msg.hwnd, msg.message, msg.wParam, msg.lParam);
|
||||
|
||||
if (msg.message == WM_POINTERLEAVE)
|
||||
return E_FAIL;
|
||||
}
|
||||
} else {
|
||||
return E_FAIL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Qt::DropAction QWindowsDrag::drag(QDrag *drag)
|
||||
{
|
||||
// TODO: Accessibility handling?
|
||||
QMimeData *dropData = drag->mimeData();
|
||||
Qt::DropAction dragResult = Qt::IgnoreAction;
|
||||
|
||||
DWORD resultEffect;
|
||||
QWindowsDrag::m_canceled = false;
|
||||
auto *windowDropSource = new QWindowsOleDropSource(this);
|
||||
windowDropSource->createCursors();
|
||||
auto *dropDataObject = new QWindowsDropDataObject(dropData);
|
||||
const Qt::DropActions possibleActions = drag->supportedActions();
|
||||
const DWORD allowedEffects = translateToWinDragEffects(possibleActions);
|
||||
qCDebug(lcQpaMime) << '>' << __FUNCTION__ << "possible Actions=0x"
|
||||
<< Qt::hex << int(possibleActions) << "effects=0x" << allowedEffects << Qt::dec;
|
||||
// Indicate message handlers we are in DoDragDrop() event loop.
|
||||
QWindowsDrag::m_dragging = true;
|
||||
const HRESULT r = startDoDragDrop(dropDataObject, windowDropSource, allowedEffects, &resultEffect);
|
||||
QWindowsDrag::m_dragging = false;
|
||||
const DWORD reportedPerformedEffect = dropDataObject->reportedPerformedEffect();
|
||||
if (r == DRAGDROP_S_DROP) {
|
||||
if (reportedPerformedEffect == DROPEFFECT_MOVE && resultEffect != DROPEFFECT_MOVE) {
|
||||
dragResult = Qt::TargetMoveAction;
|
||||
resultEffect = DROPEFFECT_MOVE;
|
||||
} else {
|
||||
dragResult = translateToQDragDropAction(resultEffect);
|
||||
}
|
||||
// Force it to be a copy if an unsupported operation occurred.
|
||||
// This indicates a bug in the drop target.
|
||||
if (resultEffect != DROPEFFECT_NONE && !(resultEffect & allowedEffects)) {
|
||||
qWarning("%s: Forcing Qt::CopyAction", __FUNCTION__);
|
||||
dragResult = Qt::CopyAction;
|
||||
}
|
||||
}
|
||||
// clean up
|
||||
dropDataObject->releaseQt();
|
||||
dropDataObject->Release(); // Will delete obj if refcount becomes 0
|
||||
windowDropSource->Release(); // Will delete src if refcount becomes 0
|
||||
qCDebug(lcQpaMime) << '<' << __FUNCTION__ << Qt::hex << "allowedEffects=0x" << allowedEffects
|
||||
<< "reportedPerformedEffect=0x" << reportedPerformedEffect
|
||||
<< " resultEffect=0x" << resultEffect << "hr=0x" << int(r) << Qt::dec << "dropAction=" << dragResult;
|
||||
return dragResult;
|
||||
}
|
||||
|
||||
QWindowsDrag *QWindowsDrag::instance()
|
||||
{
|
||||
return static_cast<QWindowsDrag *>(QWindowsIntegration::instance()->drag());
|
||||
}
|
||||
|
||||
void QWindowsDrag::releaseDropDataObject()
|
||||
{
|
||||
qCDebug(lcQpaMime) << __FUNCTION__ << m_dropDataObject;
|
||||
if (m_dropDataObject) {
|
||||
m_dropDataObject->Release();
|
||||
m_dropDataObject = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
QT_END_NAMESPACE
|
1400
qtbase/src/plugins/platforms/windows/qwindowskeymapper.cpp
Normal file
1400
qtbase/src/plugins/platforms/windows/qwindowskeymapper.cpp
Normal file
File diff suppressed because it is too large
Load Diff
864
qtbase/src/plugins/platforms/windows/qwindowspointerhandler.cpp
Normal file
864
qtbase/src/plugins/platforms/windows/qwindowspointerhandler.cpp
Normal file
@ -0,0 +1,864 @@
|
||||
// Copyright (C) 2019 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
|
||||
|
||||
#include <QtCore/qt_windows.h>
|
||||
|
||||
#include "qwindowspointerhandler.h"
|
||||
#include "qwindowsmousehandler.h"
|
||||
#if QT_CONFIG(tabletevent)
|
||||
# include "qwindowstabletsupport.h"
|
||||
#endif
|
||||
#include "qwindowskeymapper.h"
|
||||
#include "qwindowscontext.h"
|
||||
#include "qwindowswindow.h"
|
||||
#include "qwindowsintegration.h"
|
||||
#include "qwindowsscreen.h"
|
||||
|
||||
#include <QtGui/qguiapplication.h>
|
||||
#include <QtGui/qscreen.h>
|
||||
#include <QtGui/qpointingdevice.h>
|
||||
#include <QtGui/qwindow.h>
|
||||
#include <QtGui/private/qguiapplication_p.h>
|
||||
#include <QtCore/qvarlengtharray.h>
|
||||
#include <QtCore/qloggingcategory.h>
|
||||
#include <QtCore/qqueue.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <windowsx.h>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
enum {
|
||||
QT_PT_POINTER = 1,
|
||||
QT_PT_TOUCH = 2,
|
||||
QT_PT_PEN = 3,
|
||||
QT_PT_MOUSE = 4,
|
||||
QT_PT_TOUCHPAD = 5, // MinGW is missing PT_TOUCHPAD
|
||||
};
|
||||
|
||||
qint64 QWindowsPointerHandler::m_nextInputDeviceId = 1;
|
||||
|
||||
QWindowsPointerHandler::~QWindowsPointerHandler()
|
||||
{
|
||||
}
|
||||
|
||||
bool QWindowsPointerHandler::translatePointerEvent(QWindow *window, HWND hwnd, QtWindows::WindowsEventType et, MSG msg, LRESULT *result)
|
||||
{
|
||||
*result = 0;
|
||||
const quint32 pointerId = GET_POINTERID_WPARAM(msg.wParam);
|
||||
|
||||
if (!GetPointerType(pointerId, &m_pointerType)) {
|
||||
qWarning() << "GetPointerType() failed:" << qt_error_string();
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (m_pointerType) {
|
||||
case QT_PT_POINTER:
|
||||
case QT_PT_MOUSE:
|
||||
case QT_PT_TOUCHPAD: {
|
||||
// Let Mouse/TouchPad be handled using legacy messages.
|
||||
return false;
|
||||
}
|
||||
case QT_PT_TOUCH: {
|
||||
quint32 pointerCount = 0;
|
||||
if (!GetPointerFrameTouchInfo(pointerId, &pointerCount, nullptr)) {
|
||||
qWarning() << "GetPointerFrameTouchInfo() failed:" << qt_error_string();
|
||||
return false;
|
||||
}
|
||||
QVarLengthArray<POINTER_TOUCH_INFO, 10> touchInfo(pointerCount);
|
||||
if (!GetPointerFrameTouchInfo(pointerId, &pointerCount, touchInfo.data())) {
|
||||
qWarning() << "GetPointerFrameTouchInfo() failed:" << qt_error_string();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!pointerCount)
|
||||
return false;
|
||||
|
||||
// The history count is the same for all the touchpoints in touchInfo
|
||||
quint32 historyCount = touchInfo[0].pointerInfo.historyCount;
|
||||
// dispatch any skipped frames if event compression is disabled by the app
|
||||
if (historyCount > 1 && !QCoreApplication::testAttribute(Qt::AA_CompressHighFrequencyEvents)) {
|
||||
touchInfo.resize(pointerCount * historyCount);
|
||||
if (!GetPointerFrameTouchInfoHistory(pointerId,
|
||||
&historyCount,
|
||||
&pointerCount,
|
||||
touchInfo.data())) {
|
||||
qWarning() << "GetPointerFrameTouchInfoHistory() failed:" << qt_error_string();
|
||||
return false;
|
||||
}
|
||||
|
||||
// history frames are returned with the most recent frame first so we iterate backwards
|
||||
bool result = true;
|
||||
for (auto it = touchInfo.rbegin(), end = touchInfo.rend(); it != end; it += pointerCount) {
|
||||
result &= translateTouchEvent(window, hwnd, et, msg,
|
||||
&(*(it + (pointerCount - 1))), pointerCount);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
return translateTouchEvent(window, hwnd, et, msg, touchInfo.data(), pointerCount);
|
||||
}
|
||||
case QT_PT_PEN: {
|
||||
POINTER_PEN_INFO penInfo;
|
||||
if (!GetPointerPenInfo(pointerId, &penInfo)) {
|
||||
qWarning() << "GetPointerPenInfo() failed:" << qt_error_string();
|
||||
return false;
|
||||
}
|
||||
|
||||
quint32 historyCount = penInfo.pointerInfo.historyCount;
|
||||
// dispatch any skipped frames if generic or tablet event compression is disabled by the app
|
||||
if (historyCount > 1
|
||||
&& (!QCoreApplication::testAttribute(Qt::AA_CompressHighFrequencyEvents)
|
||||
|| !QCoreApplication::testAttribute(Qt::AA_CompressTabletEvents))) {
|
||||
QVarLengthArray<POINTER_PEN_INFO, 10> penInfoHistory(historyCount);
|
||||
|
||||
if (!GetPointerPenInfoHistory(pointerId, &historyCount, penInfoHistory.data())) {
|
||||
qWarning() << "GetPointerPenInfoHistory() failed:" << qt_error_string();
|
||||
return false;
|
||||
}
|
||||
|
||||
// history frames are returned with the most recent frame first so we iterate backwards
|
||||
bool result = true;
|
||||
for (auto it = penInfoHistory.rbegin(), end = penInfoHistory.rend(); it != end; ++it) {
|
||||
result &= translatePenEvent(window, hwnd, et, msg, &(*(it)));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
return translatePenEvent(window, hwnd, et, msg, &penInfo);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
namespace {
|
||||
struct MouseEvent {
|
||||
QEvent::Type type;
|
||||
Qt::MouseButton button;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
static inline Qt::MouseButton extraButton(WPARAM wParam) // for WM_XBUTTON...
|
||||
{
|
||||
return GET_XBUTTON_WPARAM(wParam) == XBUTTON1 ? Qt::BackButton : Qt::ForwardButton;
|
||||
}
|
||||
|
||||
static inline MouseEvent eventFromMsg(const MSG &msg)
|
||||
{
|
||||
switch (msg.message) {
|
||||
case WM_MOUSEMOVE:
|
||||
return {QEvent::MouseMove, Qt::NoButton};
|
||||
case WM_LBUTTONDOWN:
|
||||
return {QEvent::MouseButtonPress, Qt::LeftButton};
|
||||
case WM_LBUTTONUP:
|
||||
return {QEvent::MouseButtonRelease, Qt::LeftButton};
|
||||
case WM_LBUTTONDBLCLK: // Qt QPA does not handle double clicks, send as press
|
||||
return {QEvent::MouseButtonPress, Qt::LeftButton};
|
||||
case WM_MBUTTONDOWN:
|
||||
return {QEvent::MouseButtonPress, Qt::MiddleButton};
|
||||
case WM_MBUTTONUP:
|
||||
return {QEvent::MouseButtonRelease, Qt::MiddleButton};
|
||||
case WM_MBUTTONDBLCLK:
|
||||
return {QEvent::MouseButtonPress, Qt::MiddleButton};
|
||||
case WM_RBUTTONDOWN:
|
||||
return {QEvent::MouseButtonPress, Qt::RightButton};
|
||||
case WM_RBUTTONUP:
|
||||
return {QEvent::MouseButtonRelease, Qt::RightButton};
|
||||
case WM_RBUTTONDBLCLK:
|
||||
return {QEvent::MouseButtonPress, Qt::RightButton};
|
||||
case WM_XBUTTONDOWN:
|
||||
return {QEvent::MouseButtonPress, extraButton(msg.wParam)};
|
||||
case WM_XBUTTONUP:
|
||||
return {QEvent::MouseButtonRelease, extraButton(msg.wParam)};
|
||||
case WM_XBUTTONDBLCLK:
|
||||
return {QEvent::MouseButtonPress, extraButton(msg.wParam)};
|
||||
case WM_NCMOUSEMOVE:
|
||||
return {QEvent::NonClientAreaMouseMove, Qt::NoButton};
|
||||
case WM_NCLBUTTONDOWN:
|
||||
return {QEvent::NonClientAreaMouseButtonPress, Qt::LeftButton};
|
||||
case WM_NCLBUTTONUP:
|
||||
return {QEvent::NonClientAreaMouseButtonRelease, Qt::LeftButton};
|
||||
case WM_NCLBUTTONDBLCLK:
|
||||
return {QEvent::NonClientAreaMouseButtonPress, Qt::LeftButton};
|
||||
case WM_NCMBUTTONDOWN:
|
||||
return {QEvent::NonClientAreaMouseButtonPress, Qt::MiddleButton};
|
||||
case WM_NCMBUTTONUP:
|
||||
return {QEvent::NonClientAreaMouseButtonRelease, Qt::MiddleButton};
|
||||
case WM_NCMBUTTONDBLCLK:
|
||||
return {QEvent::NonClientAreaMouseButtonPress, Qt::MiddleButton};
|
||||
case WM_NCRBUTTONDOWN:
|
||||
return {QEvent::NonClientAreaMouseButtonPress, Qt::RightButton};
|
||||
case WM_NCRBUTTONUP:
|
||||
return {QEvent::NonClientAreaMouseButtonRelease, Qt::RightButton};
|
||||
case WM_NCRBUTTONDBLCLK:
|
||||
return {QEvent::NonClientAreaMouseButtonPress, Qt::RightButton};
|
||||
default: // WM_MOUSELEAVE
|
||||
break;
|
||||
}
|
||||
return {QEvent::None, Qt::NoButton};
|
||||
}
|
||||
|
||||
static Qt::MouseButtons mouseButtonsFromKeyState(WPARAM keyState)
|
||||
{
|
||||
Qt::MouseButtons result = Qt::NoButton;
|
||||
if (keyState & MK_LBUTTON)
|
||||
result |= Qt::LeftButton;
|
||||
if (keyState & MK_RBUTTON)
|
||||
result |= Qt::RightButton;
|
||||
if (keyState & MK_MBUTTON)
|
||||
result |= Qt::MiddleButton;
|
||||
if (keyState & MK_XBUTTON1)
|
||||
result |= Qt::XButton1;
|
||||
if (keyState & MK_XBUTTON2)
|
||||
result |= Qt::XButton2;
|
||||
return result;
|
||||
}
|
||||
|
||||
static Qt::MouseButtons queryMouseButtons()
|
||||
{
|
||||
Qt::MouseButtons result = Qt::NoButton;
|
||||
const bool mouseSwapped = GetSystemMetrics(SM_SWAPBUTTON);
|
||||
if (GetAsyncKeyState(VK_LBUTTON) < 0)
|
||||
result |= mouseSwapped ? Qt::RightButton: Qt::LeftButton;
|
||||
if (GetAsyncKeyState(VK_RBUTTON) < 0)
|
||||
result |= mouseSwapped ? Qt::LeftButton : Qt::RightButton;
|
||||
if (GetAsyncKeyState(VK_MBUTTON) < 0)
|
||||
result |= Qt::MiddleButton;
|
||||
if (GetAsyncKeyState(VK_XBUTTON1) < 0)
|
||||
result |= Qt::XButton1;
|
||||
if (GetAsyncKeyState(VK_XBUTTON2) < 0)
|
||||
result |= Qt::XButton2;
|
||||
return result;
|
||||
}
|
||||
|
||||
static QWindow *getWindowUnderPointer(QWindow *window, QPoint globalPos)
|
||||
{
|
||||
QWindowsWindow *platformWindow = static_cast<QWindowsWindow *>(window->handle());
|
||||
|
||||
QWindow *currentWindowUnderPointer = platformWindow->hasMouseCapture() ?
|
||||
QWindowsScreen::windowAt(globalPos, CWP_SKIPINVISIBLE | CWP_SKIPTRANSPARENT) : window;
|
||||
|
||||
while (currentWindowUnderPointer && currentWindowUnderPointer->flags() & Qt::WindowTransparentForInput)
|
||||
currentWindowUnderPointer = currentWindowUnderPointer->parent();
|
||||
|
||||
// QTBUG-44332: When Qt is running at low integrity level and
|
||||
// a Qt Window is parented on a Window of a higher integrity process
|
||||
// using QWindow::fromWinId() (for example, Qt running in a browser plugin)
|
||||
// ChildWindowFromPointEx() may not find the Qt window (failing with ERROR_ACCESS_DENIED)
|
||||
if (!currentWindowUnderPointer) {
|
||||
const QRect clientRect(QPoint(0, 0), window->size());
|
||||
if (clientRect.contains(globalPos))
|
||||
currentWindowUnderPointer = window;
|
||||
}
|
||||
return currentWindowUnderPointer;
|
||||
}
|
||||
|
||||
static bool trackLeave(HWND hwnd)
|
||||
{
|
||||
TRACKMOUSEEVENT tme;
|
||||
tme.cbSize = sizeof(TRACKMOUSEEVENT);
|
||||
tme.dwFlags = TME_LEAVE;
|
||||
tme.hwndTrack = hwnd;
|
||||
tme.dwHoverTime = HOVER_DEFAULT;
|
||||
return TrackMouseEvent(&tme);
|
||||
}
|
||||
|
||||
static bool isValidWheelReceiver(QWindow *candidate)
|
||||
{
|
||||
if (candidate) {
|
||||
const QWindow *toplevel = QWindowsWindow::topLevelOf(candidate);
|
||||
if (toplevel->handle() && toplevel->handle()->isForeignWindow())
|
||||
return true;
|
||||
if (const QWindowsWindow *ww = QWindowsWindow::windowsWindowOf(toplevel))
|
||||
return !ww->testFlag(QWindowsWindow::BlockedByModal);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
QWindowsPointerHandler::QPointingDevicePtr QWindowsPointerHandler::createTouchDevice(bool mouseEmulation)
|
||||
{
|
||||
const int digitizers = GetSystemMetrics(SM_DIGITIZER);
|
||||
if (!(digitizers & (NID_INTEGRATED_TOUCH | NID_EXTERNAL_TOUCH)))
|
||||
return nullptr;
|
||||
const int tabletPc = GetSystemMetrics(SM_TABLETPC);
|
||||
const int maxTouchPoints = GetSystemMetrics(SM_MAXIMUMTOUCHES);
|
||||
const QPointingDevice::DeviceType type = (digitizers & NID_INTEGRATED_TOUCH)
|
||||
? QInputDevice::DeviceType::TouchScreen : QInputDevice::DeviceType::TouchPad;
|
||||
QInputDevice::Capabilities capabilities = QInputDevice::Capability::Position
|
||||
| QInputDevice::Capability::Area
|
||||
| QInputDevice::Capability::NormalizedPosition;
|
||||
if (type != QInputDevice::DeviceType::TouchScreen) {
|
||||
capabilities.setFlag(QInputDevice::Capability::MouseEmulation);
|
||||
capabilities.setFlag(QInputDevice::Capability::Scroll);
|
||||
} else if (mouseEmulation) {
|
||||
capabilities.setFlag(QInputDevice::Capability::MouseEmulation);
|
||||
}
|
||||
|
||||
const int flags = digitizers & ~NID_READY;
|
||||
qCDebug(lcQpaEvents) << "Digitizers:" << Qt::hex << Qt::showbase << flags
|
||||
<< "Ready:" << (digitizers & NID_READY) << Qt::dec << Qt::noshowbase
|
||||
<< "Tablet PC:" << tabletPc << "Max touch points:" << maxTouchPoints << "Capabilities:" << capabilities;
|
||||
|
||||
const int buttonCount = type == QInputDevice::DeviceType::TouchScreen ? 1 : 3;
|
||||
// TODO: use system-provided name and device ID rather than empty-string and m_nextInputDeviceId
|
||||
const qint64 systemId = m_nextInputDeviceId++ | (qint64(flags << 2));
|
||||
auto d = new QPointingDevice(QString(), systemId, type,
|
||||
QPointingDevice::PointerType::Finger,
|
||||
capabilities, maxTouchPoints, buttonCount,
|
||||
QString(), QPointingDeviceUniqueId::fromNumericId(systemId));
|
||||
return QPointingDevicePtr(d);
|
||||
}
|
||||
|
||||
void QWindowsPointerHandler::clearEvents()
|
||||
{
|
||||
m_lastEventType = QEvent::None;
|
||||
m_lastEventButton = Qt::NoButton;
|
||||
}
|
||||
|
||||
void QWindowsPointerHandler::handleCaptureRelease(QWindow *window,
|
||||
QWindow *currentWindowUnderPointer,
|
||||
HWND hwnd,
|
||||
QEvent::Type eventType,
|
||||
Qt::MouseButtons mouseButtons)
|
||||
{
|
||||
auto *platformWindow = static_cast<QWindowsWindow *>(window->handle());
|
||||
|
||||
// Qt expects the platform plugin to capture the mouse on any button press until release.
|
||||
if (!platformWindow->hasMouseCapture() && eventType == QEvent::MouseButtonPress) {
|
||||
|
||||
platformWindow->setMouseGrabEnabled(true);
|
||||
platformWindow->setFlag(QWindowsWindow::AutoMouseCapture);
|
||||
qCDebug(lcQpaEvents) << "Automatic mouse capture " << window;
|
||||
|
||||
// Implement "Click to focus" for native child windows (unless it is a native widget window).
|
||||
if (!window->isTopLevel() && !window->inherits("QWidgetWindow") && QGuiApplication::focusWindow() != window)
|
||||
window->requestActivate();
|
||||
|
||||
} else if (platformWindow->hasMouseCapture()
|
||||
&& platformWindow->testFlag(QWindowsWindow::AutoMouseCapture)
|
||||
&& eventType == QEvent::MouseButtonRelease
|
||||
&& !mouseButtons) {
|
||||
|
||||
platformWindow->setMouseGrabEnabled(false);
|
||||
qCDebug(lcQpaEvents) << "Releasing automatic mouse capture " << window;
|
||||
}
|
||||
|
||||
// Enter new window: track to generate leave event.
|
||||
// If there is an active capture, only track if the current window is capturing,
|
||||
// so we don't get extra leave when cursor leaves the application.
|
||||
if (window != m_currentWindow &&
|
||||
(!platformWindow->hasMouseCapture() || currentWindowUnderPointer == window)) {
|
||||
trackLeave(hwnd);
|
||||
m_currentWindow = window;
|
||||
}
|
||||
}
|
||||
|
||||
void QWindowsPointerHandler::handleEnterLeave(QWindow *window,
|
||||
QWindow *currentWindowUnderPointer,
|
||||
QPoint globalPos)
|
||||
{
|
||||
auto *platformWindow = static_cast<QWindowsWindow *>(window->handle());
|
||||
const bool hasCapture = platformWindow->hasMouseCapture();
|
||||
|
||||
// No enter or leave events are sent as long as there is an autocapturing window.
|
||||
if (!hasCapture || !platformWindow->testFlag(QWindowsWindow::AutoMouseCapture)) {
|
||||
|
||||
// Leave is needed if:
|
||||
// 1) There is no capture and we move from a window to another window.
|
||||
// Note: Leaving the application entirely is handled in translateMouseEvent(WM_MOUSELEAVE).
|
||||
// 2) There is capture and we move out of the capturing window.
|
||||
// 3) There is a new capture and we were over another window.
|
||||
if ((m_windowUnderPointer && m_windowUnderPointer != currentWindowUnderPointer
|
||||
&& (!hasCapture || window == m_windowUnderPointer))
|
||||
|| (hasCapture && m_previousCaptureWindow != window && m_windowUnderPointer
|
||||
&& m_windowUnderPointer != window)) {
|
||||
|
||||
qCDebug(lcQpaEvents) << "Leaving window " << m_windowUnderPointer;
|
||||
QWindowSystemInterface::handleLeaveEvent(m_windowUnderPointer);
|
||||
|
||||
if (hasCapture && currentWindowUnderPointer != window) {
|
||||
// Clear tracking if capturing and current window is not the capturing window
|
||||
// to avoid leave when mouse actually leaves the application.
|
||||
m_currentWindow = nullptr;
|
||||
// We are not officially in any window, but we need to set some cursor to clear
|
||||
// whatever cursor the left window had, so apply the cursor of the capture window.
|
||||
platformWindow->applyCursor();
|
||||
}
|
||||
}
|
||||
|
||||
// Enter is needed if:
|
||||
// 1) There is no capture and we move to a new window.
|
||||
// 2) There is capture and we move into the capturing window.
|
||||
// 3) The capture just ended and we are over non-capturing window.
|
||||
if ((currentWindowUnderPointer && m_windowUnderPointer != currentWindowUnderPointer
|
||||
&& (!hasCapture || currentWindowUnderPointer == window))
|
||||
|| (m_previousCaptureWindow && !hasCapture && currentWindowUnderPointer
|
||||
&& currentWindowUnderPointer != m_previousCaptureWindow)) {
|
||||
|
||||
QPoint wumLocalPos;
|
||||
if (QWindowsWindow *wumPlatformWindow = QWindowsWindow::windowsWindowOf(currentWindowUnderPointer)) {
|
||||
wumLocalPos = wumPlatformWindow->mapFromGlobal(globalPos);
|
||||
wumPlatformWindow->applyCursor();
|
||||
}
|
||||
qCDebug(lcQpaEvents) << "Entering window " << currentWindowUnderPointer;
|
||||
QWindowSystemInterface::handleEnterEvent(currentWindowUnderPointer, wumLocalPos, globalPos);
|
||||
}
|
||||
|
||||
// We need to track m_windowUnderPointer separately from m_currentWindow, as Windows
|
||||
// mouse tracking will not trigger WM_MOUSELEAVE for leaving window when mouse capture is set.
|
||||
m_windowUnderPointer = currentWindowUnderPointer;
|
||||
}
|
||||
|
||||
m_previousCaptureWindow = hasCapture ? window : nullptr;
|
||||
}
|
||||
|
||||
bool QWindowsPointerHandler::translateTouchEvent(QWindow *window, HWND hwnd,
|
||||
QtWindows::WindowsEventType et,
|
||||
MSG msg, PVOID vTouchInfo, quint32 count)
|
||||
{
|
||||
Q_UNUSED(hwnd);
|
||||
|
||||
auto *touchInfo = static_cast<POINTER_TOUCH_INFO *>(vTouchInfo);
|
||||
|
||||
if (et & QtWindows::NonClientEventFlag)
|
||||
return false; // Let DefWindowProc() handle Non Client messages.
|
||||
|
||||
if (count < 1)
|
||||
return false;
|
||||
|
||||
if (msg.message == WM_POINTERCAPTURECHANGED) {
|
||||
QWindowSystemInterface::handleTouchCancelEvent(window, m_touchDevice.data(),
|
||||
QWindowsKeyMapper::queryKeyboardModifiers());
|
||||
m_lastTouchPoints.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (msg.message == WM_POINTERLEAVE) {
|
||||
for (quint32 i = 0; i < count; ++i) {
|
||||
const quint32 pointerId = touchInfo[i].pointerInfo.pointerId;
|
||||
int id = m_touchInputIDToTouchPointID.value(pointerId, -1);
|
||||
if (id != -1)
|
||||
m_lastTouchPoints.remove(id);
|
||||
}
|
||||
// Send LeaveEvent to reset hover when the last finger leaves the touch screen (QTBUG-62912)
|
||||
QWindowSystemInterface::handleEnterLeaveEvent(nullptr, window);
|
||||
}
|
||||
|
||||
// Only handle down/up/update, ignore others like WM_POINTERENTER, WM_POINTERLEAVE, etc.
|
||||
if (msg.message > WM_POINTERUP)
|
||||
return false;
|
||||
|
||||
const QScreen *screen = window->screen();
|
||||
if (!screen)
|
||||
screen = QGuiApplication::primaryScreen();
|
||||
if (!screen)
|
||||
return false;
|
||||
|
||||
const QRect screenGeometry = screen->geometry();
|
||||
|
||||
QList<QWindowSystemInterface::TouchPoint> touchPoints;
|
||||
|
||||
if (QWindowsContext::verbose > 1)
|
||||
qCDebug(lcQpaEvents).noquote().nospace() << Qt::showbase
|
||||
<< __FUNCTION__
|
||||
<< " message=" << Qt::hex << msg.message
|
||||
<< " count=" << Qt::dec << count;
|
||||
|
||||
QEventPoint::States allStates;
|
||||
QSet<int> inputIds;
|
||||
|
||||
for (quint32 i = 0; i < count; ++i) {
|
||||
if (QWindowsContext::verbose > 1)
|
||||
qCDebug(lcQpaEvents).noquote().nospace() << Qt::showbase
|
||||
<< " TouchPoint id=" << touchInfo[i].pointerInfo.pointerId
|
||||
<< " frame=" << touchInfo[i].pointerInfo.frameId
|
||||
<< " flags=" << Qt::hex << touchInfo[i].pointerInfo.pointerFlags;
|
||||
|
||||
QWindowSystemInterface::TouchPoint touchPoint;
|
||||
const quint32 pointerId = touchInfo[i].pointerInfo.pointerId;
|
||||
int id = m_touchInputIDToTouchPointID.value(pointerId, -1);
|
||||
if (id == -1) {
|
||||
// Start tracking after fingers touch the screen. Ignore bogus updates after touch is released.
|
||||
if ((touchInfo[i].pointerInfo.pointerFlags & POINTER_FLAG_DOWN) == 0)
|
||||
continue;
|
||||
id = m_touchInputIDToTouchPointID.size();
|
||||
m_touchInputIDToTouchPointID.insert(pointerId, id);
|
||||
}
|
||||
touchPoint.id = id;
|
||||
touchPoint.pressure = (touchInfo[i].touchMask & TOUCH_MASK_PRESSURE) ?
|
||||
touchInfo[i].pressure / 1024.0 : 1.0;
|
||||
if (m_lastTouchPoints.contains(touchPoint.id))
|
||||
touchPoint.normalPosition = m_lastTouchPoints.value(touchPoint.id).normalPosition;
|
||||
|
||||
const QPointF screenPos = QPointF(touchInfo[i].pointerInfo.ptPixelLocation.x,
|
||||
touchInfo[i].pointerInfo.ptPixelLocation.y);
|
||||
|
||||
if (touchInfo[i].touchMask & TOUCH_MASK_CONTACTAREA)
|
||||
touchPoint.area.setSize(QSizeF(touchInfo[i].rcContact.right - touchInfo[i].rcContact.left,
|
||||
touchInfo[i].rcContact.bottom - touchInfo[i].rcContact.top));
|
||||
touchPoint.area.moveCenter(screenPos);
|
||||
QPointF normalPosition = QPointF(screenPos.x() / screenGeometry.width(),
|
||||
screenPos.y() / screenGeometry.height());
|
||||
const bool stationaryTouchPoint = (normalPosition == touchPoint.normalPosition);
|
||||
touchPoint.normalPosition = normalPosition;
|
||||
|
||||
if (touchInfo[i].pointerInfo.pointerFlags & POINTER_FLAG_DOWN) {
|
||||
touchPoint.state = QEventPoint::State::Pressed;
|
||||
m_lastTouchPoints.insert(touchPoint.id, touchPoint);
|
||||
} else if (touchInfo[i].pointerInfo.pointerFlags & POINTER_FLAG_UP) {
|
||||
touchPoint.state = QEventPoint::State::Released;
|
||||
m_lastTouchPoints.remove(touchPoint.id);
|
||||
} else {
|
||||
touchPoint.state = stationaryTouchPoint ? QEventPoint::State::Stationary : QEventPoint::State::Updated;
|
||||
m_lastTouchPoints.insert(touchPoint.id, touchPoint);
|
||||
}
|
||||
allStates |= touchPoint.state;
|
||||
|
||||
touchPoints.append(touchPoint);
|
||||
inputIds.insert(touchPoint.id);
|
||||
|
||||
// Avoid getting repeated messages for this frame if there are multiple pointerIds
|
||||
SkipPointerFrameMessages(touchInfo[i].pointerInfo.pointerId);
|
||||
}
|
||||
|
||||
// Some devices send touches for each finger in a different message/frame, instead of consolidating
|
||||
// them in the same frame as we were expecting. We account for missing unreleased touches here.
|
||||
for (auto tp : std::as_const(m_lastTouchPoints)) {
|
||||
if (!inputIds.contains(tp.id)) {
|
||||
tp.state = QEventPoint::State::Stationary;
|
||||
allStates |= tp.state;
|
||||
touchPoints.append(tp);
|
||||
}
|
||||
}
|
||||
|
||||
if (touchPoints.count() == 0)
|
||||
return false;
|
||||
|
||||
// all touch points released, forget the ids we've seen.
|
||||
if (allStates == QEventPoint::State::Released)
|
||||
m_touchInputIDToTouchPointID.clear();
|
||||
|
||||
QWindowSystemInterface::handleTouchEvent(window, m_touchDevice.data(), touchPoints,
|
||||
QWindowsKeyMapper::queryKeyboardModifiers());
|
||||
return false; // Allow mouse messages to be generated.
|
||||
}
|
||||
|
||||
#if QT_CONFIG(tabletevent)
|
||||
QWindowsPointerHandler::QPointingDevicePtr QWindowsPointerHandler::findTabletDevice(QPointingDevice::PointerType pointerType) const
|
||||
{
|
||||
for (const auto &d : m_tabletDevices) {
|
||||
if (d->pointerType() == pointerType)
|
||||
return d;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
#endif
|
||||
|
||||
bool QWindowsPointerHandler::translatePenEvent(QWindow *window, HWND hwnd, QtWindows::WindowsEventType et,
|
||||
MSG msg, PVOID vPenInfo)
|
||||
{
|
||||
#if QT_CONFIG(tabletevent)
|
||||
if (et & QtWindows::NonClientEventFlag)
|
||||
return false; // Let DefWindowProc() handle Non Client messages.
|
||||
|
||||
auto *penInfo = static_cast<POINTER_PEN_INFO *>(vPenInfo);
|
||||
|
||||
RECT pRect, dRect;
|
||||
if (!GetPointerDeviceRects(penInfo->pointerInfo.sourceDevice, &pRect, &dRect))
|
||||
return false;
|
||||
|
||||
const auto systemId = (qint64)penInfo->pointerInfo.sourceDevice;
|
||||
const QPoint globalPos = QPoint(penInfo->pointerInfo.ptPixelLocation.x, penInfo->pointerInfo.ptPixelLocation.y);
|
||||
const QPoint localPos = QWindowsGeometryHint::mapFromGlobal(hwnd, globalPos);
|
||||
const QPointF hiResGlobalPos = QPointF(dRect.left + qreal(penInfo->pointerInfo.ptHimetricLocation.x - pRect.left)
|
||||
/ (pRect.right - pRect.left) * (dRect.right - dRect.left),
|
||||
dRect.top + qreal(penInfo->pointerInfo.ptHimetricLocation.y - pRect.top)
|
||||
/ (pRect.bottom - pRect.top) * (dRect.bottom - dRect.top));
|
||||
const bool hasPressure = (penInfo->penMask & PEN_MASK_PRESSURE) != 0;
|
||||
const bool hasRotation = (penInfo->penMask & PEN_MASK_ROTATION) != 0;
|
||||
const qreal pressure = hasPressure ? qreal(penInfo->pressure) / 1024.0 : 0.5;
|
||||
const qreal rotation = hasRotation ? qreal(penInfo->rotation) : 0.0;
|
||||
const qreal tangentialPressure = 0.0;
|
||||
const bool hasTiltX = (penInfo->penMask & PEN_MASK_TILT_X) != 0;
|
||||
const bool hasTiltY = (penInfo->penMask & PEN_MASK_TILT_Y) != 0;
|
||||
const int xTilt = hasTiltX ? penInfo->tiltX : 0;
|
||||
const int yTilt = hasTiltY ? penInfo->tiltY : 0;
|
||||
const int z = 0;
|
||||
|
||||
if (QWindowsContext::verbose > 1)
|
||||
qCDebug(lcQpaEvents).noquote().nospace() << Qt::showbase
|
||||
<< __FUNCTION__ << " systemId=" << systemId
|
||||
<< " globalPos=" << globalPos << " localPos=" << localPos << " hiResGlobalPos=" << hiResGlobalPos
|
||||
<< " message=" << Qt::hex << msg.message
|
||||
<< " flags=" << Qt::hex << penInfo->pointerInfo.pointerFlags;
|
||||
|
||||
QPointingDevice::PointerType type;
|
||||
// Since it may be the middle button, so if the checks fail then it should
|
||||
// be set to Middle if it was used.
|
||||
Qt::MouseButtons mouseButtons = queryMouseButtons();
|
||||
|
||||
const bool pointerInContact = IS_POINTER_INCONTACT_WPARAM(msg.wParam);
|
||||
if (pointerInContact)
|
||||
mouseButtons = Qt::LeftButton;
|
||||
|
||||
if (penInfo->penFlags & (PEN_FLAG_ERASER | PEN_FLAG_INVERTED)) {
|
||||
type = QPointingDevice::PointerType::Eraser;
|
||||
} else {
|
||||
type = QPointingDevice::PointerType::Pen;
|
||||
if (pointerInContact && penInfo->penFlags & PEN_FLAG_BARREL)
|
||||
mouseButtons = Qt::RightButton; // Either left or right, not both
|
||||
}
|
||||
|
||||
auto device = findTabletDevice(type);
|
||||
if (device.isNull()) {
|
||||
QInputDevice::Capabilities caps(QInputDevice::Capability::Position
|
||||
| QInputDevice::Capability::MouseEmulation
|
||||
| QInputDevice::Capability::Hover);
|
||||
if (hasPressure)
|
||||
caps |= QInputDevice::Capability::Pressure;
|
||||
if (hasRotation)
|
||||
caps |= QInputDevice::Capability::Rotation;
|
||||
if (hasTiltX)
|
||||
caps |= QInputDevice::Capability::XTilt;
|
||||
if (hasTiltY)
|
||||
caps |= QInputDevice::Capability::YTilt;
|
||||
const qint64 uniqueId = systemId | (qint64(type) << 32L);
|
||||
device.reset(new QPointingDevice(QStringLiteral("wmpointer"),
|
||||
systemId, QInputDevice::DeviceType::Stylus,
|
||||
type, caps, 1, 3, QString(),
|
||||
QPointingDeviceUniqueId::fromNumericId(uniqueId)));
|
||||
QWindowSystemInterface::registerInputDevice(device.data());
|
||||
m_tabletDevices.append(device);
|
||||
}
|
||||
|
||||
const auto uniqueId = device->uniqueId().numericId();
|
||||
m_activeTabletDevice = device;
|
||||
|
||||
switch (msg.message) {
|
||||
case WM_POINTERENTER: {
|
||||
QWindowSystemInterface::handleTabletEnterLeaveProximityEvent(window, device.data(), true);
|
||||
m_windowUnderPointer = window;
|
||||
// The local coordinates may fall outside the window.
|
||||
// Wait until the next update to send the enter event.
|
||||
m_needsEnterOnPointerUpdate = true;
|
||||
break;
|
||||
}
|
||||
case WM_POINTERLEAVE:
|
||||
if (m_windowUnderPointer && m_windowUnderPointer == m_currentWindow) {
|
||||
QWindowSystemInterface::handleLeaveEvent(m_windowUnderPointer);
|
||||
m_windowUnderPointer = nullptr;
|
||||
m_currentWindow = nullptr;
|
||||
}
|
||||
QWindowSystemInterface::handleTabletEnterLeaveProximityEvent(window, device.data(), false);
|
||||
break;
|
||||
case WM_POINTERDOWN:
|
||||
case WM_POINTERUP:
|
||||
case WM_POINTERUPDATE: {
|
||||
QWindow *target = QGuiApplicationPrivate::tabletDevicePoint(uniqueId).target; // Pass to window that grabbed it.
|
||||
if (!target && m_windowUnderPointer)
|
||||
target = m_windowUnderPointer;
|
||||
if (!target)
|
||||
target = window;
|
||||
|
||||
if (m_needsEnterOnPointerUpdate) {
|
||||
m_needsEnterOnPointerUpdate = false;
|
||||
if (window != m_currentWindow) {
|
||||
// make sure we subscribe to leave events for this window
|
||||
trackLeave(hwnd);
|
||||
|
||||
QWindowSystemInterface::handleEnterEvent(window, localPos, globalPos);
|
||||
m_currentWindow = window;
|
||||
if (QWindowsWindow *wumPlatformWindow = QWindowsWindow::windowsWindowOf(target))
|
||||
wumPlatformWindow->applyCursor();
|
||||
}
|
||||
}
|
||||
const Qt::KeyboardModifiers keyModifiers = QWindowsKeyMapper::queryKeyboardModifiers();
|
||||
|
||||
QWindowSystemInterface::handleTabletEvent(target, device.data(),
|
||||
localPos, hiResGlobalPos, mouseButtons,
|
||||
pressure, xTilt, yTilt, tangentialPressure,
|
||||
rotation, z, keyModifiers);
|
||||
return false; // Allow mouse messages to be generated.
|
||||
}
|
||||
}
|
||||
return true;
|
||||
#else
|
||||
Q_UNUSED(window);
|
||||
Q_UNUSED(hwnd);
|
||||
Q_UNUSED(et);
|
||||
Q_UNUSED(msg);
|
||||
Q_UNUSED(vPenInfo);
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline bool isMouseEventSynthesizedFromPenOrTouch()
|
||||
{
|
||||
// For details, see
|
||||
// https://docs.microsoft.com/en-us/windows/desktop/tablet/system-events-and-mouse-messages
|
||||
const LONG_PTR SIGNATURE_MASK = 0xFFFFFF00;
|
||||
const LONG_PTR MI_WP_SIGNATURE = 0xFF515700;
|
||||
|
||||
return ((::GetMessageExtraInfo() & SIGNATURE_MASK) == MI_WP_SIGNATURE);
|
||||
}
|
||||
|
||||
bool QWindowsPointerHandler::translateMouseWheelEvent(QWindow *window,
|
||||
QWindow *currentWindowUnderPointer,
|
||||
MSG msg,
|
||||
QPoint globalPos,
|
||||
Qt::KeyboardModifiers keyModifiers)
|
||||
{
|
||||
QWindow *receiver = currentWindowUnderPointer;
|
||||
if (!isValidWheelReceiver(receiver))
|
||||
receiver = window;
|
||||
if (!isValidWheelReceiver(receiver))
|
||||
return true;
|
||||
|
||||
int delta = GET_WHEEL_DELTA_WPARAM(msg.wParam);
|
||||
|
||||
// Qt horizontal wheel rotation orientation is opposite to the one in WM_MOUSEHWHEEL
|
||||
if (msg.message == WM_MOUSEHWHEEL)
|
||||
delta = -delta;
|
||||
|
||||
const QPoint angleDelta = (msg.message == WM_MOUSEHWHEEL || (keyModifiers & Qt::AltModifier)) ?
|
||||
QPoint(delta, 0) : QPoint(0, delta);
|
||||
|
||||
QPoint localPos = QWindowsGeometryHint::mapFromGlobal(receiver, globalPos);
|
||||
|
||||
QWindowSystemInterface::handleWheelEvent(receiver, localPos, globalPos, QPoint(), angleDelta, keyModifiers);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Process legacy mouse messages here.
|
||||
bool QWindowsPointerHandler::translateMouseEvent(QWindow *window,
|
||||
HWND hwnd,
|
||||
QtWindows::WindowsEventType et,
|
||||
MSG msg,
|
||||
LRESULT *result)
|
||||
{
|
||||
*result = 0;
|
||||
|
||||
QPoint localPos;
|
||||
QPoint globalPos;
|
||||
QPoint eventPos(GET_X_LPARAM(msg.lParam), GET_Y_LPARAM(msg.lParam));
|
||||
|
||||
if ((et == QtWindows::MouseWheelEvent) || (et & QtWindows::NonClientEventFlag)) {
|
||||
globalPos = eventPos;
|
||||
localPos = QWindowsGeometryHint::mapFromGlobal(hwnd, eventPos);
|
||||
} else {
|
||||
if (QWindowsBaseWindow::isRtlLayout(hwnd)) {
|
||||
RECT clientArea;
|
||||
GetClientRect(hwnd, &clientArea);
|
||||
eventPos.setX(clientArea.right - eventPos.x());
|
||||
}
|
||||
|
||||
globalPos = QWindowsGeometryHint::mapToGlobal(hwnd, eventPos);
|
||||
auto targetHwnd = hwnd;
|
||||
if (auto *pw = window->handle())
|
||||
targetHwnd = HWND(pw->winId());
|
||||
localPos = targetHwnd == hwnd
|
||||
? eventPos
|
||||
: QWindowsGeometryHint::mapFromGlobal(targetHwnd, globalPos);
|
||||
}
|
||||
|
||||
const Qt::KeyboardModifiers keyModifiers = QWindowsKeyMapper::queryKeyboardModifiers();
|
||||
QWindow *currentWindowUnderPointer = getWindowUnderPointer(window, globalPos);
|
||||
|
||||
if (et == QtWindows::MouseWheelEvent)
|
||||
return translateMouseWheelEvent(window, currentWindowUnderPointer, msg, globalPos, keyModifiers);
|
||||
|
||||
// Windows sends a mouse move with no buttons pressed to signal "Enter"
|
||||
// when a window is shown over the cursor. Discard the event and only use
|
||||
// it for generating QEvent::Enter to be consistent with other platforms -
|
||||
// X11 and macOS.
|
||||
bool discardEvent = false;
|
||||
if (msg.message == WM_MOUSEMOVE) {
|
||||
Q_CONSTINIT static QPoint lastMouseMovePos;
|
||||
if (msg.wParam == 0 && (m_windowUnderPointer.isNull() || globalPos == lastMouseMovePos))
|
||||
discardEvent = true;
|
||||
lastMouseMovePos = globalPos;
|
||||
}
|
||||
|
||||
Qt::MouseEventSource source = Qt::MouseEventNotSynthesized;
|
||||
const QPointingDevice *device = QWindowsMouseHandler::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
|
||||
// the extra msg info for checking if synthesized for touch does not work,
|
||||
// so we use the pointer type of the last pointer message.
|
||||
if (isMouseEventSynthesizedFromPenOrTouch()) {
|
||||
switch (m_pointerType) {
|
||||
case QT_PT_TOUCH:
|
||||
if (QWindowsIntegration::instance()->options() & QWindowsIntegration::DontPassOsMouseEventsSynthesizedFromTouch)
|
||||
return false;
|
||||
source = Qt::MouseEventSynthesizedBySystem;
|
||||
if (!m_touchDevice.isNull())
|
||||
device = m_touchDevice.data();
|
||||
break;
|
||||
case QT_PT_PEN:
|
||||
#if QT_CONFIG(tabletevent)
|
||||
qCDebug(lcQpaTablet) << "ignoring synth-mouse event for tablet event from" << device;
|
||||
return false;
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const MouseEvent mouseEvent = eventFromMsg(msg);
|
||||
Qt::MouseButtons mouseButtons;
|
||||
|
||||
if (mouseEvent.type >= QEvent::NonClientAreaMouseMove && mouseEvent.type <= QEvent::NonClientAreaMouseButtonDblClick)
|
||||
mouseButtons = queryMouseButtons();
|
||||
else
|
||||
mouseButtons = mouseButtonsFromKeyState(msg.wParam);
|
||||
|
||||
// When the left/right mouse buttons are pressed over the window title bar
|
||||
// WM_NCLBUTTONDOWN/WM_NCRBUTTONDOWN messages are received. But no UP
|
||||
// messages are received on release, only WM_NCMOUSEMOVE/WM_MOUSEMOVE.
|
||||
// We detect it and generate the missing release events here. (QTBUG-75678)
|
||||
// The last event vars are cleared on QWindowsContext::handleExitSizeMove()
|
||||
// to avoid generating duplicated release events.
|
||||
if (m_lastEventType == QEvent::NonClientAreaMouseButtonPress
|
||||
&& (mouseEvent.type == QEvent::NonClientAreaMouseMove || mouseEvent.type == QEvent::MouseMove)
|
||||
&& (m_lastEventButton & mouseButtons) == 0) {
|
||||
auto releaseType = mouseEvent.type == QEvent::NonClientAreaMouseMove ?
|
||||
QEvent::NonClientAreaMouseButtonRelease : QEvent::MouseButtonRelease;
|
||||
QWindowSystemInterface::handleMouseEvent(window, 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,
|
||||
mouseEvent.button, mouseEvent.type, keyModifiers, source);
|
||||
return false; // Allow further event processing
|
||||
}
|
||||
|
||||
if (msg.message == WM_MOUSELEAVE) {
|
||||
if (window == m_currentWindow) {
|
||||
QWindow *leaveTarget = m_windowUnderPointer ? m_windowUnderPointer : m_currentWindow;
|
||||
qCDebug(lcQpaEvents) << "Leaving window " << leaveTarget;
|
||||
QWindowSystemInterface::handleLeaveEvent(leaveTarget);
|
||||
m_windowUnderPointer = nullptr;
|
||||
m_currentWindow = nullptr;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
handleCaptureRelease(window, currentWindowUnderPointer, hwnd, mouseEvent.type, mouseButtons);
|
||||
handleEnterLeave(window, currentWindowUnderPointer, globalPos);
|
||||
|
||||
if (!discardEvent && mouseEvent.type != QEvent::None) {
|
||||
QWindowSystemInterface::handleMouseEvent(window, device, localPos, globalPos, mouseButtons,
|
||||
mouseEvent.button, mouseEvent.type, keyModifiers, source);
|
||||
}
|
||||
|
||||
// QTBUG-48117, force synchronous handling for the extra buttons so that WM_APPCOMMAND
|
||||
// is sent for unhandled WM_XBUTTONDOWN.
|
||||
return (msg.message != WM_XBUTTONUP && msg.message != WM_XBUTTONDOWN && msg.message != WM_XBUTTONDBLCLK)
|
||||
|| QWindowSystemInterface::flushWindowSystemEvents();
|
||||
}
|
||||
|
||||
QT_END_NAMESPACE
|
842
qtbase/src/plugins/platforms/windows/qwindowsscreen.cpp
Normal file
842
qtbase/src/plugins/platforms/windows/qwindowsscreen.cpp
Normal file
@ -0,0 +1,842 @@
|
||||
// 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 "qwindowsscreen.h"
|
||||
#include "qwindowscontext.h"
|
||||
#include "qwindowswindow.h"
|
||||
#include "qwindowsintegration.h"
|
||||
#include "qwindowscursor.h"
|
||||
#include "qwindowstheme.h"
|
||||
|
||||
#include <QtCore/qt_windows.h>
|
||||
|
||||
#include <QtCore/qsettings.h>
|
||||
#include <QtGui/qpixmap.h>
|
||||
#include <QtGui/qguiapplication.h>
|
||||
#include <qpa/qwindowsysteminterface.h>
|
||||
#include <QtCore/private/qsystemerror_p.h>
|
||||
#include <QtGui/private/qedidparser_p.h>
|
||||
#include <private/qhighdpiscaling_p.h>
|
||||
#include <private/qwindowsfontdatabasebase_p.h>
|
||||
#include <private/qpixmap_win_p.h>
|
||||
|
||||
#include <QtGui/qscreen.h>
|
||||
|
||||
#include <QtCore/qdebug.h>
|
||||
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
|
||||
#include <cfgmgr32.h>
|
||||
#include <setupapi.h>
|
||||
#include <shellscalingapi.h>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
static inline QDpi deviceDPI(HDC hdc)
|
||||
{
|
||||
return QDpi(GetDeviceCaps(hdc, LOGPIXELSX), GetDeviceCaps(hdc, LOGPIXELSY));
|
||||
}
|
||||
|
||||
static inline QDpi monitorDPI(HMONITOR hMonitor)
|
||||
{
|
||||
UINT dpiX;
|
||||
UINT dpiY;
|
||||
if (SUCCEEDED(GetDpiForMonitor(hMonitor, MDT_EFFECTIVE_DPI, &dpiX, &dpiY)))
|
||||
return QDpi(dpiX, dpiY);
|
||||
return {0, 0};
|
||||
}
|
||||
|
||||
static std::vector<DISPLAYCONFIG_PATH_INFO> getPathInfo(const MONITORINFOEX &viewInfo)
|
||||
{
|
||||
// We might want to consider storing adapterId/id from DISPLAYCONFIG_PATH_TARGET_INFO.
|
||||
std::vector<DISPLAYCONFIG_PATH_INFO> pathInfos;
|
||||
std::vector<DISPLAYCONFIG_MODE_INFO> modeInfos;
|
||||
|
||||
// Fetch paths
|
||||
LONG result;
|
||||
UINT32 numPathArrayElements;
|
||||
UINT32 numModeInfoArrayElements;
|
||||
do {
|
||||
// QueryDisplayConfig documentation doesn't say the number of needed elements is updated
|
||||
// when the call fails with ERROR_INSUFFICIENT_BUFFER, so we need a separate call to
|
||||
// look up the needed buffer sizes.
|
||||
if (GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &numPathArrayElements,
|
||||
&numModeInfoArrayElements) != ERROR_SUCCESS) {
|
||||
return {};
|
||||
}
|
||||
pathInfos.resize(numPathArrayElements);
|
||||
modeInfos.resize(numModeInfoArrayElements);
|
||||
result = QueryDisplayConfig(QDC_ONLY_ACTIVE_PATHS, &numPathArrayElements, pathInfos.data(),
|
||||
&numModeInfoArrayElements, modeInfos.data(), nullptr);
|
||||
} while (result == ERROR_INSUFFICIENT_BUFFER);
|
||||
|
||||
if (result != ERROR_SUCCESS)
|
||||
return {};
|
||||
|
||||
// Find paths matching monitor name
|
||||
auto discardThese =
|
||||
std::remove_if(pathInfos.begin(), pathInfos.end(), [&](const auto &path) -> bool {
|
||||
DISPLAYCONFIG_SOURCE_DEVICE_NAME deviceName;
|
||||
deviceName.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME;
|
||||
deviceName.header.size = sizeof(DISPLAYCONFIG_SOURCE_DEVICE_NAME);
|
||||
deviceName.header.adapterId = path.sourceInfo.adapterId;
|
||||
deviceName.header.id = path.sourceInfo.id;
|
||||
if (DisplayConfigGetDeviceInfo(&deviceName.header) == ERROR_SUCCESS) {
|
||||
return wcscmp(viewInfo.szDevice, deviceName.viewGdiDeviceName) != 0;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
pathInfos.erase(discardThese, pathInfos.end());
|
||||
|
||||
return pathInfos;
|
||||
}
|
||||
|
||||
#if 0
|
||||
// Needed later for HDR support
|
||||
static float getMonitorSDRWhiteLevel(DISPLAYCONFIG_PATH_TARGET_INFO *targetInfo)
|
||||
{
|
||||
const float defaultSdrWhiteLevel = 200.0;
|
||||
if (!targetInfo)
|
||||
return defaultSdrWhiteLevel;
|
||||
|
||||
DISPLAYCONFIG_SDR_WHITE_LEVEL whiteLevel = {};
|
||||
whiteLevel.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SDR_WHITE_LEVEL;
|
||||
whiteLevel.header.size = sizeof(DISPLAYCONFIG_SDR_WHITE_LEVEL);
|
||||
whiteLevel.header.adapterId = targetInfo->adapterId;
|
||||
whiteLevel.header.id = targetInfo->id;
|
||||
if (DisplayConfigGetDeviceInfo(&whiteLevel.header) != ERROR_SUCCESS)
|
||||
return defaultSdrWhiteLevel;
|
||||
return whiteLevel.SDRWhiteLevel * 80.0 / 1000.0;
|
||||
}
|
||||
#endif
|
||||
|
||||
using WindowsScreenDataList = QList<QWindowsScreenData>;
|
||||
|
||||
struct RegistryHandleDeleter
|
||||
{
|
||||
void operator()(HKEY handle) const noexcept
|
||||
{
|
||||
if (handle != nullptr && handle != INVALID_HANDLE_VALUE)
|
||||
RegCloseKey(handle);
|
||||
}
|
||||
};
|
||||
|
||||
using RegistryHandlePtr = std::unique_ptr<std::remove_pointer_t<HKEY>, RegistryHandleDeleter>;
|
||||
|
||||
static void setMonitorDataFromSetupApi(QWindowsScreenData &data,
|
||||
const std::vector<DISPLAYCONFIG_PATH_INFO> &pathGroup)
|
||||
{
|
||||
if (pathGroup.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The only property shared among monitors in a clone group is deviceName
|
||||
{
|
||||
DISPLAYCONFIG_TARGET_DEVICE_NAME deviceName = {};
|
||||
deviceName.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME;
|
||||
deviceName.header.size = sizeof(DISPLAYCONFIG_TARGET_DEVICE_NAME);
|
||||
// The first element in the clone group is the main monitor.
|
||||
deviceName.header.adapterId = pathGroup[0].targetInfo.adapterId;
|
||||
deviceName.header.id = pathGroup[0].targetInfo.id;
|
||||
if (DisplayConfigGetDeviceInfo(&deviceName.header) == ERROR_SUCCESS) {
|
||||
data.devicePath = QString::fromWCharArray(deviceName.monitorDevicePath);
|
||||
} else {
|
||||
qCWarning(lcQpaScreen)
|
||||
<< u"Unable to get device information for %1:"_s.arg(pathGroup[0].targetInfo.id)
|
||||
<< QSystemError::windowsString();
|
||||
}
|
||||
}
|
||||
|
||||
// The rest must be concatenated into the resulting property
|
||||
QStringList names;
|
||||
QStringList manufacturers;
|
||||
QStringList models;
|
||||
QStringList serialNumbers;
|
||||
|
||||
for (const auto &path : pathGroup) {
|
||||
DISPLAYCONFIG_TARGET_DEVICE_NAME deviceName = {};
|
||||
deviceName.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME;
|
||||
deviceName.header.size = sizeof(DISPLAYCONFIG_TARGET_DEVICE_NAME);
|
||||
deviceName.header.adapterId = path.targetInfo.adapterId;
|
||||
deviceName.header.id = path.targetInfo.id;
|
||||
if (DisplayConfigGetDeviceInfo(&deviceName.header) != ERROR_SUCCESS) {
|
||||
qCWarning(lcQpaScreen)
|
||||
<< u"Unable to get device information for %1:"_s.arg(path.targetInfo.id)
|
||||
<< QSystemError::windowsString();
|
||||
continue;
|
||||
}
|
||||
|
||||
// https://learn.microsoft.com/en-us/windows-hardware/drivers/install/guid-devinterface-monitor
|
||||
constexpr GUID GUID_DEVINTERFACE_MONITOR = {
|
||||
0xe6f07b5f, 0xee97, 0x4a90, { 0xb0, 0x76, 0x33, 0xf5, 0x7b, 0xf4, 0xea, 0xa7 }
|
||||
};
|
||||
const HDEVINFO devInfo = SetupDiGetClassDevs(&GUID_DEVINTERFACE_MONITOR, nullptr, nullptr,
|
||||
DIGCF_DEVICEINTERFACE);
|
||||
|
||||
SP_DEVICE_INTERFACE_DATA deviceInterfaceData{};
|
||||
deviceInterfaceData.cbSize = sizeof(deviceInterfaceData);
|
||||
|
||||
if (!SetupDiOpenDeviceInterfaceW(devInfo, deviceName.monitorDevicePath, DIODI_NO_ADD,
|
||||
&deviceInterfaceData)) {
|
||||
qCWarning(lcQpaScreen)
|
||||
<< u"Unable to open monitor interface to %1:"_s.arg(data.deviceName)
|
||||
<< QSystemError::windowsString();
|
||||
continue;
|
||||
}
|
||||
|
||||
DWORD requiredSize{ 0 };
|
||||
if (SetupDiGetDeviceInterfaceDetailW(devInfo, &deviceInterfaceData, nullptr, 0,
|
||||
&requiredSize, nullptr)
|
||||
|| GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::unique_ptr<std::byte[]> storage(new std::byte[requiredSize]);
|
||||
auto *devicePath = reinterpret_cast<SP_DEVICE_INTERFACE_DETAIL_DATA_W *>(storage.get());
|
||||
devicePath->cbSize = sizeof(std::remove_pointer_t<decltype(devicePath)>);
|
||||
SP_DEVINFO_DATA deviceInfoData{};
|
||||
deviceInfoData.cbSize = sizeof(deviceInfoData);
|
||||
if (!SetupDiGetDeviceInterfaceDetailW(devInfo, &deviceInterfaceData, devicePath,
|
||||
requiredSize, nullptr, &deviceInfoData)) {
|
||||
qCDebug(lcQpaScreen) << u"Unable to get monitor metadata for %1:"_s.arg(data.deviceName)
|
||||
<< QSystemError::windowsString();
|
||||
continue;
|
||||
}
|
||||
|
||||
const RegistryHandlePtr edidRegistryKey{ SetupDiOpenDevRegKey(
|
||||
devInfo, &deviceInfoData, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ) };
|
||||
|
||||
if (!edidRegistryKey || edidRegistryKey.get() == INVALID_HANDLE_VALUE)
|
||||
continue;
|
||||
|
||||
DWORD edidDataSize{ 0 };
|
||||
if (RegQueryValueExW(edidRegistryKey.get(), L"EDID", nullptr, nullptr, nullptr,
|
||||
&edidDataSize)
|
||||
!= ERROR_SUCCESS) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QByteArray edidData;
|
||||
edidData.resize(edidDataSize);
|
||||
|
||||
if (RegQueryValueExW(edidRegistryKey.get(), L"EDID", nullptr, nullptr,
|
||||
reinterpret_cast<unsigned char *>(edidData.data()), &edidDataSize)
|
||||
!= ERROR_SUCCESS) {
|
||||
qCDebug(lcQpaScreen) << u"Unable to get EDID from the Registry for %1:"_s.arg(
|
||||
data.deviceName)
|
||||
<< QSystemError::windowsString();
|
||||
continue;
|
||||
}
|
||||
|
||||
QEdidParser edid;
|
||||
|
||||
if (!edid.parse(edidData)) {
|
||||
qCDebug(lcQpaScreen) << "Invalid EDID blob for" << data.deviceName;
|
||||
continue;
|
||||
}
|
||||
|
||||
// We skip edid.identifier because it is unreliable, and a better option
|
||||
// is already available through DisplayConfigGetDeviceInfo (see below).
|
||||
names << QString::fromWCharArray(deviceName.monitorFriendlyDeviceName);
|
||||
manufacturers << edid.manufacturer;
|
||||
models << edid.model;
|
||||
serialNumbers << edid.serialNumber;
|
||||
}
|
||||
|
||||
data.name = names.join(u"|"_s);
|
||||
data.manufacturer = manufacturers.join(u"|"_s);
|
||||
data.model = models.join(u"|"_s);
|
||||
data.serialNumber = serialNumbers.join(u"|"_s);
|
||||
}
|
||||
|
||||
static bool monitorData(HMONITOR hMonitor, QWindowsScreenData *data)
|
||||
{
|
||||
MONITORINFOEX info;
|
||||
memset(&info, 0, sizeof(MONITORINFOEX));
|
||||
info.cbSize = sizeof(MONITORINFOEX);
|
||||
if (GetMonitorInfo(hMonitor, &info) == FALSE)
|
||||
return false;
|
||||
|
||||
data->hMonitor = hMonitor;
|
||||
data->geometry = QRect(QPoint(info.rcMonitor.left, info.rcMonitor.top), QPoint(info.rcMonitor.right - 1, info.rcMonitor.bottom - 1));
|
||||
data->availableGeometry = QRect(QPoint(info.rcWork.left, info.rcWork.top), QPoint(info.rcWork.right - 1, info.rcWork.bottom - 1));
|
||||
data->deviceName = QString::fromWCharArray(info.szDevice);
|
||||
const auto pathGroup = getPathInfo(info);
|
||||
if (!pathGroup.empty()) {
|
||||
setMonitorDataFromSetupApi(*data, pathGroup);
|
||||
}
|
||||
if (data->name.isEmpty())
|
||||
data->name = data->deviceName;
|
||||
if (data->deviceName == u"WinDisc") {
|
||||
data->flags |= QWindowsScreenData::LockScreen;
|
||||
} else {
|
||||
if (const HDC hdc = CreateDC(info.szDevice, nullptr, nullptr, nullptr)) {
|
||||
const QDpi dpi = monitorDPI(hMonitor);
|
||||
data->dpi = dpi.first > 0 ? dpi : deviceDPI(hdc);
|
||||
data->depth = GetDeviceCaps(hdc, BITSPIXEL);
|
||||
data->format = data->depth == 16 ? QImage::Format_RGB16 : QImage::Format_RGB32;
|
||||
data->physicalSizeMM = QSizeF(GetDeviceCaps(hdc, HORZSIZE), GetDeviceCaps(hdc, VERTSIZE));
|
||||
const int refreshRate = GetDeviceCaps(hdc, VREFRESH);
|
||||
if (refreshRate > 1) // 0,1 means hardware default.
|
||||
data->refreshRateHz = refreshRate;
|
||||
DeleteDC(hdc);
|
||||
} else {
|
||||
qWarning("%s: Unable to obtain handle for monitor '%s', defaulting to %g DPI.",
|
||||
__FUNCTION__, qPrintable(data->deviceName),
|
||||
data->dpi.first);
|
||||
} // CreateDC() failed
|
||||
} // not lock screen
|
||||
|
||||
// ### We might want to consider storing adapterId/id from DISPLAYCONFIG_PATH_TARGET_INFO,
|
||||
// if we are going to use DISPLAYCONFIG lookups more.
|
||||
if (!pathGroup.empty()) {
|
||||
// The first element in the clone group is the main monitor.
|
||||
const auto &pathInfo = pathGroup[0];
|
||||
switch (pathInfo.targetInfo.rotation) {
|
||||
case DISPLAYCONFIG_ROTATION_IDENTITY:
|
||||
data->orientation = Qt::LandscapeOrientation;
|
||||
break;
|
||||
case DISPLAYCONFIG_ROTATION_ROTATE90:
|
||||
data->orientation = Qt::PortraitOrientation;
|
||||
break;
|
||||
case DISPLAYCONFIG_ROTATION_ROTATE180:
|
||||
data->orientation = Qt::InvertedLandscapeOrientation;
|
||||
break;
|
||||
case DISPLAYCONFIG_ROTATION_ROTATE270:
|
||||
data->orientation = Qt::InvertedPortraitOrientation;
|
||||
break;
|
||||
case DISPLAYCONFIG_ROTATION_FORCE_UINT32:
|
||||
Q_UNREACHABLE();
|
||||
break;
|
||||
}
|
||||
if (pathInfo.targetInfo.refreshRate.Numerator && pathInfo.targetInfo.refreshRate.Denominator)
|
||||
data->refreshRateHz = static_cast<qreal>(pathInfo.targetInfo.refreshRate.Numerator)
|
||||
/ pathInfo.targetInfo.refreshRate.Denominator;
|
||||
} else {
|
||||
data->orientation = data->geometry.height() > data->geometry.width()
|
||||
? Qt::PortraitOrientation
|
||||
: Qt::LandscapeOrientation;
|
||||
}
|
||||
// EnumDisplayMonitors (as opposed to EnumDisplayDevices) enumerates only
|
||||
// virtual desktop screens.
|
||||
data->flags |= QWindowsScreenData::VirtualDesktop;
|
||||
if (info.dwFlags & MONITORINFOF_PRIMARY)
|
||||
data->flags |= QWindowsScreenData::PrimaryScreen;
|
||||
return true;
|
||||
}
|
||||
|
||||
// from monitorData, taking WindowsScreenDataList as LPARAM
|
||||
BOOL QT_WIN_CALLBACK monitorEnumCallback(HMONITOR hMonitor, HDC, LPRECT, LPARAM p)
|
||||
{
|
||||
QWindowsScreenData data;
|
||||
if (monitorData(hMonitor, &data)) {
|
||||
auto *result = reinterpret_cast<WindowsScreenDataList *>(p);
|
||||
auto it = std::find_if(result->rbegin(), result->rend(),
|
||||
[&data](QWindowsScreenData i){ return i.name == data.name; });
|
||||
if (it != result->rend()) {
|
||||
int previousIndex = 1;
|
||||
if (it->deviceIndex.has_value())
|
||||
previousIndex = it->deviceIndex.value();
|
||||
else
|
||||
(*it).deviceIndex = 1;
|
||||
data.deviceIndex = previousIndex + 1;
|
||||
}
|
||||
// QWindowSystemInterface::handleScreenAdded() documentation specifies that first
|
||||
// added screen will be the primary screen, so order accordingly.
|
||||
// Note that the side effect of this policy is that there is no way to change primary
|
||||
// screen reported by Qt, unless we want to delete all existing screens and add them
|
||||
// again whenever primary screen changes.
|
||||
if (data.flags & QWindowsScreenData::PrimaryScreen)
|
||||
result->prepend(data);
|
||||
else
|
||||
result->append(data);
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static inline WindowsScreenDataList monitorData()
|
||||
{
|
||||
WindowsScreenDataList result;
|
||||
EnumDisplayMonitors(nullptr, nullptr, monitorEnumCallback, reinterpret_cast<LPARAM>(&result));
|
||||
return result;
|
||||
}
|
||||
|
||||
#ifndef QT_NO_DEBUG_STREAM
|
||||
static QDebug operator<<(QDebug dbg, const QWindowsScreenData &d)
|
||||
{
|
||||
QDebugStateSaver saver(dbg);
|
||||
dbg.nospace();
|
||||
dbg.noquote();
|
||||
dbg << "Screen \"" << d.name << "\" " << d.geometry.width() << 'x' << d.geometry.height() << '+'
|
||||
<< d.geometry.x() << '+' << d.geometry.y() << " avail: " << d.availableGeometry.width()
|
||||
<< 'x' << d.availableGeometry.height() << '+' << d.availableGeometry.x() << '+'
|
||||
<< d.availableGeometry.y() << " physical: " << d.physicalSizeMM.width() << 'x'
|
||||
<< d.physicalSizeMM.height() << " DPI: " << d.dpi.first << 'x' << d.dpi.second
|
||||
<< " Depth: " << d.depth << " Format: " << d.format << " hMonitor: " << d.hMonitor
|
||||
<< " device name: " << d.deviceName << " manufacturer: " << d.manufacturer
|
||||
<< " model: " << d.model << " serial number: " << d.serialNumber;
|
||||
if (d.flags & QWindowsScreenData::PrimaryScreen)
|
||||
dbg << " primary";
|
||||
if (d.flags & QWindowsScreenData::VirtualDesktop)
|
||||
dbg << " virtual desktop";
|
||||
if (d.flags & QWindowsScreenData::LockScreen)
|
||||
dbg << " lock screen";
|
||||
return dbg;
|
||||
}
|
||||
#endif // !QT_NO_DEBUG_STREAM
|
||||
|
||||
/*!
|
||||
\class QWindowsScreen
|
||||
\brief Windows screen.
|
||||
\sa QWindowsScreenManager
|
||||
\internal
|
||||
*/
|
||||
|
||||
QWindowsScreen::QWindowsScreen(const QWindowsScreenData &data) :
|
||||
m_data(data)
|
||||
#ifndef QT_NO_CURSOR
|
||||
, m_cursor(new QWindowsCursor(this))
|
||||
#endif
|
||||
{
|
||||
}
|
||||
|
||||
QString QWindowsScreen::name() const
|
||||
{
|
||||
return m_data.deviceIndex.has_value()
|
||||
? (u"%1 (%2)"_s).arg(m_data.name, QString::number(m_data.deviceIndex.value()))
|
||||
: m_data.name;
|
||||
}
|
||||
|
||||
QPixmap QWindowsScreen::grabWindow(WId window, int xIn, int yIn, int width, int height) const
|
||||
{
|
||||
QSize windowSize;
|
||||
int x = xIn;
|
||||
int y = yIn;
|
||||
HWND hwnd = reinterpret_cast<HWND>(window);
|
||||
if (hwnd) {
|
||||
RECT r;
|
||||
GetClientRect(hwnd, &r);
|
||||
windowSize = QSize(r.right - r.left, r.bottom - r.top);
|
||||
} else {
|
||||
// Grab current screen. The client rectangle of GetDesktopWindow() is the
|
||||
// primary screen, but it is possible to grab other screens from it.
|
||||
hwnd = GetDesktopWindow();
|
||||
const QRect screenGeometry = geometry();
|
||||
windowSize = screenGeometry.size();
|
||||
x += screenGeometry.x();
|
||||
y += screenGeometry.y();
|
||||
}
|
||||
|
||||
if (width < 0)
|
||||
width = windowSize.width() - xIn;
|
||||
if (height < 0)
|
||||
height = windowSize.height() - yIn;
|
||||
|
||||
// Create and setup bitmap
|
||||
HDC display_dc = GetDC(nullptr);
|
||||
HDC bitmap_dc = CreateCompatibleDC(display_dc);
|
||||
HBITMAP bitmap = CreateCompatibleBitmap(display_dc, width, height);
|
||||
HGDIOBJ null_bitmap = SelectObject(bitmap_dc, bitmap);
|
||||
|
||||
// copy data
|
||||
HDC window_dc = GetDC(hwnd);
|
||||
BitBlt(bitmap_dc, 0, 0, width, height, window_dc, x, y, SRCCOPY | CAPTUREBLT);
|
||||
|
||||
// clean up all but bitmap
|
||||
ReleaseDC(hwnd, window_dc);
|
||||
SelectObject(bitmap_dc, null_bitmap);
|
||||
DeleteDC(bitmap_dc);
|
||||
|
||||
const QPixmap pixmap = qt_pixmapFromWinHBITMAP(bitmap);
|
||||
|
||||
DeleteObject(bitmap);
|
||||
ReleaseDC(nullptr, display_dc);
|
||||
|
||||
return pixmap;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief Find a top level window taking the flags of ChildWindowFromPointEx.
|
||||
*/
|
||||
|
||||
QWindow *QWindowsScreen::topLevelAt(const QPoint &point) const
|
||||
{
|
||||
QWindow *result = nullptr;
|
||||
if (QWindow *child = QWindowsScreen::windowAt(point, CWP_SKIPINVISIBLE))
|
||||
result = QWindowsWindow::topLevelOf(child);
|
||||
if (QWindowsContext::verbose > 1)
|
||||
qCDebug(lcQpaScreen) <<__FUNCTION__ << point << result;
|
||||
return result;
|
||||
}
|
||||
|
||||
QWindow *QWindowsScreen::windowAt(const QPoint &screenPoint, unsigned flags)
|
||||
{
|
||||
QWindow* result = nullptr;
|
||||
if (QPlatformWindow *bw = QWindowsContext::instance()->
|
||||
findPlatformWindowAt(GetDesktopWindow(), screenPoint, flags))
|
||||
result = bw->window();
|
||||
if (QWindowsContext::verbose > 1)
|
||||
qCDebug(lcQpaScreen) <<__FUNCTION__ << screenPoint << " returns " << result;
|
||||
return result;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief Determine siblings in a virtual desktop system.
|
||||
|
||||
Self is by definition a sibling, else collect all screens
|
||||
within virtual desktop.
|
||||
*/
|
||||
|
||||
QList<QPlatformScreen *> QWindowsScreen::virtualSiblings() const
|
||||
{
|
||||
QList<QPlatformScreen *> result;
|
||||
if (m_data.flags & QWindowsScreenData::VirtualDesktop) {
|
||||
const QWindowsScreenManager::WindowsScreenList screens
|
||||
= QWindowsContext::instance()->screenManager().screens();
|
||||
for (QWindowsScreen *screen : screens) {
|
||||
if (screen->data().flags & QWindowsScreenData::VirtualDesktop)
|
||||
result.push_back(screen);
|
||||
}
|
||||
} else {
|
||||
result.push_back(const_cast<QWindowsScreen *>(this));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief Notify QWindowSystemInterface about changes of a screen and synchronize data.
|
||||
*/
|
||||
|
||||
void QWindowsScreen::handleChanges(const QWindowsScreenData &newData)
|
||||
{
|
||||
m_data.physicalSizeMM = newData.physicalSizeMM;
|
||||
|
||||
if (m_data.hMonitor != newData.hMonitor) {
|
||||
qCDebug(lcQpaScreen) << "Monitor" << m_data.name
|
||||
<< "has had its hMonitor handle changed from"
|
||||
<< m_data.hMonitor << "to" << newData.hMonitor;
|
||||
m_data.hMonitor = newData.hMonitor;
|
||||
}
|
||||
|
||||
// QGuiApplicationPrivate::processScreenGeometryChange() checks and emits
|
||||
// DPI and orientation as well, so, assign new values and emit DPI first.
|
||||
const bool geometryChanged = m_data.geometry != newData.geometry
|
||||
|| m_data.availableGeometry != newData.availableGeometry;
|
||||
const bool dpiChanged = !qFuzzyCompare(m_data.dpi.first, newData.dpi.first)
|
||||
|| !qFuzzyCompare(m_data.dpi.second, newData.dpi.second);
|
||||
const bool orientationChanged = m_data.orientation != newData.orientation;
|
||||
const bool primaryChanged = (newData.flags & QWindowsScreenData::PrimaryScreen)
|
||||
&& !(m_data.flags & QWindowsScreenData::PrimaryScreen);
|
||||
m_data.dpi = newData.dpi;
|
||||
m_data.orientation = newData.orientation;
|
||||
m_data.geometry = newData.geometry;
|
||||
m_data.availableGeometry = newData.availableGeometry;
|
||||
m_data.flags = (m_data.flags & ~QWindowsScreenData::PrimaryScreen)
|
||||
| (newData.flags & QWindowsScreenData::PrimaryScreen);
|
||||
|
||||
if (dpiChanged) {
|
||||
QWindowSystemInterface::handleScreenLogicalDotsPerInchChange(screen(),
|
||||
newData.dpi.first,
|
||||
newData.dpi.second);
|
||||
}
|
||||
if (orientationChanged)
|
||||
QWindowSystemInterface::handleScreenOrientationChange(screen(), newData.orientation);
|
||||
if (geometryChanged) {
|
||||
QWindowSystemInterface::handleScreenGeometryChange(screen(),
|
||||
newData.geometry, newData.availableGeometry);
|
||||
}
|
||||
if (primaryChanged)
|
||||
QWindowSystemInterface::handlePrimaryScreenChanged(this);
|
||||
}
|
||||
|
||||
HMONITOR QWindowsScreen::handle() const
|
||||
{
|
||||
return m_data.hMonitor;
|
||||
}
|
||||
|
||||
QRect QWindowsScreen::virtualGeometry(const QPlatformScreen *screen) // cf QScreen::virtualGeometry()
|
||||
{
|
||||
QRect result;
|
||||
const auto siblings = screen->virtualSiblings();
|
||||
for (const QPlatformScreen *sibling : siblings)
|
||||
result |= sibling->geometry();
|
||||
return result;
|
||||
}
|
||||
|
||||
bool QWindowsScreen::setOrientationPreference(Qt::ScreenOrientation o)
|
||||
{
|
||||
bool result = false;
|
||||
ORIENTATION_PREFERENCE orientationPreference = ORIENTATION_PREFERENCE_NONE;
|
||||
switch (o) {
|
||||
case Qt::PrimaryOrientation:
|
||||
break;
|
||||
case Qt::PortraitOrientation:
|
||||
orientationPreference = ORIENTATION_PREFERENCE_PORTRAIT;
|
||||
break;
|
||||
case Qt::LandscapeOrientation:
|
||||
orientationPreference = ORIENTATION_PREFERENCE_LANDSCAPE;
|
||||
break;
|
||||
case Qt::InvertedPortraitOrientation:
|
||||
orientationPreference = ORIENTATION_PREFERENCE_PORTRAIT_FLIPPED;
|
||||
break;
|
||||
case Qt::InvertedLandscapeOrientation:
|
||||
orientationPreference = ORIENTATION_PREFERENCE_LANDSCAPE_FLIPPED;
|
||||
break;
|
||||
}
|
||||
result = SetDisplayAutoRotationPreferences(orientationPreference);
|
||||
return result;
|
||||
}
|
||||
|
||||
Qt::ScreenOrientation QWindowsScreen::orientationPreference()
|
||||
{
|
||||
Qt::ScreenOrientation result = Qt::PrimaryOrientation;
|
||||
ORIENTATION_PREFERENCE orientationPreference = ORIENTATION_PREFERENCE_NONE;
|
||||
if (GetDisplayAutoRotationPreferences(&orientationPreference)) {
|
||||
switch (orientationPreference) {
|
||||
case ORIENTATION_PREFERENCE_NONE:
|
||||
break;
|
||||
case ORIENTATION_PREFERENCE_LANDSCAPE:
|
||||
result = Qt::LandscapeOrientation;
|
||||
break;
|
||||
case ORIENTATION_PREFERENCE_PORTRAIT:
|
||||
result = Qt::PortraitOrientation;
|
||||
break;
|
||||
case ORIENTATION_PREFERENCE_LANDSCAPE_FLIPPED:
|
||||
result = Qt::InvertedLandscapeOrientation;
|
||||
break;
|
||||
case ORIENTATION_PREFERENCE_PORTRAIT_FLIPPED:
|
||||
result = Qt::InvertedPortraitOrientation;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief Queries ClearType settings to check the pixel layout
|
||||
*/
|
||||
QPlatformScreen::SubpixelAntialiasingType QWindowsScreen::subpixelAntialiasingTypeHint() const
|
||||
{
|
||||
QPlatformScreen::SubpixelAntialiasingType type = QPlatformScreen::subpixelAntialiasingTypeHint();
|
||||
if (type == QPlatformScreen::Subpixel_None) {
|
||||
QSettings settings(R"(HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Avalon.Graphics\DISPLAY1)"_L1,
|
||||
QSettings::NativeFormat);
|
||||
int registryValue = settings.value("PixelStructure"_L1, -1).toInt();
|
||||
switch (registryValue) {
|
||||
case 0:
|
||||
type = QPlatformScreen::Subpixel_None;
|
||||
break;
|
||||
case 1:
|
||||
type = QPlatformScreen::Subpixel_RGB;
|
||||
break;
|
||||
case 2:
|
||||
type = QPlatformScreen::Subpixel_BGR;
|
||||
break;
|
||||
default:
|
||||
type = QPlatformScreen::Subpixel_None;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
/*!
|
||||
\class QWindowsScreenManager
|
||||
\brief Manages a list of QWindowsScreen.
|
||||
|
||||
Listens for changes and notifies QWindowSystemInterface about changed/
|
||||
added/deleted screens.
|
||||
|
||||
\sa QWindowsScreen
|
||||
\internal
|
||||
*/
|
||||
|
||||
extern "C" LRESULT QT_WIN_CALLBACK qDisplayChangeObserverWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
if (message == WM_DISPLAYCHANGE) {
|
||||
qCDebug(lcQpaScreen) << "Handling WM_DISPLAYCHANGE";
|
||||
if (QWindowsTheme *t = QWindowsTheme::instance())
|
||||
t->displayChanged();
|
||||
QWindowsWindow::displayChanged();
|
||||
if (auto *context = QWindowsContext::instance())
|
||||
context->screenManager().handleScreenChanges();
|
||||
}
|
||||
|
||||
return DefWindowProc(hwnd, message, wParam, lParam);
|
||||
}
|
||||
|
||||
QWindowsScreenManager::QWindowsScreenManager() = default;
|
||||
|
||||
void QWindowsScreenManager::initialize()
|
||||
{
|
||||
qCDebug(lcQpaScreen) << "Initializing screen manager";
|
||||
|
||||
auto className = QWindowsContext::instance()->registerWindowClass(
|
||||
QWindowsContext::classNamePrefix() + QLatin1String("ScreenChangeObserverWindow"),
|
||||
qDisplayChangeObserverWndProc);
|
||||
|
||||
// HWND_MESSAGE windows do not get WM_DISPLAYCHANGE, so we need to create
|
||||
// a real top level window that we never show.
|
||||
m_displayChangeObserver = CreateWindowEx(0, reinterpret_cast<LPCWSTR>(className.utf16()),
|
||||
nullptr, WS_TILED, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
|
||||
nullptr, nullptr, GetModuleHandle(nullptr), nullptr);
|
||||
Q_ASSERT(m_displayChangeObserver);
|
||||
|
||||
qCDebug(lcQpaScreen) << "Created display change observer" << m_displayChangeObserver;
|
||||
|
||||
handleScreenChanges();
|
||||
}
|
||||
|
||||
QWindowsScreenManager::~QWindowsScreenManager()
|
||||
{
|
||||
DestroyWindow(m_displayChangeObserver);
|
||||
}
|
||||
|
||||
bool QWindowsScreenManager::isSingleScreen()
|
||||
{
|
||||
return QWindowsContext::instance()->screenManager().screens().size() < 2;
|
||||
}
|
||||
|
||||
static inline int indexOfMonitor(const QWindowsScreenManager::WindowsScreenList &screens,
|
||||
const QString &deviceName)
|
||||
{
|
||||
for (int i= 0; i < screens.size(); ++i)
|
||||
if (screens.at(i)->data().deviceName == deviceName)
|
||||
return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static inline int indexOfMonitor(const WindowsScreenDataList &screenData,
|
||||
const QString &deviceName)
|
||||
{
|
||||
for (int i = 0; i < screenData.size(); ++i)
|
||||
if (screenData.at(i).deviceName == deviceName)
|
||||
return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Move a window to a new virtual screen, accounting for varying sizes.
|
||||
static void moveToVirtualScreen(QWindow *w, const QScreen *newScreen)
|
||||
{
|
||||
QRect geometry = w->geometry();
|
||||
const QRect oldScreenGeometry = w->screen()->geometry();
|
||||
const QRect newScreenGeometry = newScreen->geometry();
|
||||
QPoint relativePosition = geometry.topLeft() - oldScreenGeometry.topLeft();
|
||||
if (oldScreenGeometry.size() != newScreenGeometry.size()) {
|
||||
const qreal factor =
|
||||
qreal(QPoint(newScreenGeometry.width(), newScreenGeometry.height()).manhattanLength()) /
|
||||
qreal(QPoint(oldScreenGeometry.width(), oldScreenGeometry.height()).manhattanLength());
|
||||
relativePosition = (QPointF(relativePosition) * factor).toPoint();
|
||||
}
|
||||
geometry.moveTopLeft(relativePosition);
|
||||
w->setGeometry(geometry);
|
||||
}
|
||||
|
||||
void QWindowsScreenManager::removeScreen(int index)
|
||||
{
|
||||
qCDebug(lcQpaScreen) << "Removing Monitor:" << m_screens.at(index)->data();
|
||||
QScreen *screen = m_screens.at(index)->screen();
|
||||
QScreen *primaryScreen = QGuiApplication::primaryScreen();
|
||||
// QTBUG-38650: When a screen is disconnected, Windows will automatically
|
||||
// move the Window to another screen. This will trigger a geometry change
|
||||
// event, but unfortunately after the screen destruction signal. To prevent
|
||||
// QtGui from automatically hiding the QWindow, pretend all Windows move to
|
||||
// the primary screen first (which is likely the correct, final screen).
|
||||
// QTBUG-39320: Windows does not automatically move WS_EX_TOOLWINDOW (dock) windows;
|
||||
// move those manually.
|
||||
if (screen != primaryScreen) {
|
||||
unsigned movedWindowCount = 0;
|
||||
const QWindowList tlws = QGuiApplication::topLevelWindows();
|
||||
for (QWindow *w : tlws) {
|
||||
if (w->screen() == screen && w->handle() && w->type() != Qt::Desktop) {
|
||||
if (w->isVisible() && w->windowState() != Qt::WindowMinimized
|
||||
&& (QWindowsWindow::baseWindowOf(w)->exStyle() & WS_EX_TOOLWINDOW)) {
|
||||
moveToVirtualScreen(w, primaryScreen);
|
||||
} else {
|
||||
QWindowSystemInterface::handleWindowScreenChanged(w, primaryScreen);
|
||||
}
|
||||
++movedWindowCount;
|
||||
}
|
||||
}
|
||||
if (movedWindowCount)
|
||||
QWindowSystemInterface::flushWindowSystemEvents();
|
||||
}
|
||||
QWindowSystemInterface::handleScreenRemoved(m_screens.takeAt(index));
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief Synchronizes the screen list, adds new screens, removes deleted
|
||||
ones and propagates resolution changes to QWindowSystemInterface.
|
||||
*/
|
||||
|
||||
bool QWindowsScreenManager::handleScreenChanges()
|
||||
{
|
||||
// Look for changed monitors, add new ones
|
||||
const WindowsScreenDataList newDataList = monitorData();
|
||||
const bool lockScreen = newDataList.size() == 1 && (newDataList.front().flags & QWindowsScreenData::LockScreen);
|
||||
bool primaryScreenChanged = false;
|
||||
for (const QWindowsScreenData &newData : newDataList) {
|
||||
const int existingIndex = indexOfMonitor(m_screens, newData.deviceName);
|
||||
if (existingIndex != -1) {
|
||||
m_screens.at(existingIndex)->handleChanges(newData);
|
||||
if (existingIndex == 0)
|
||||
primaryScreenChanged = true;
|
||||
} else {
|
||||
auto *newScreen = new QWindowsScreen(newData);
|
||||
m_screens.push_back(newScreen);
|
||||
QWindowSystemInterface::handleScreenAdded(newScreen,
|
||||
newData.flags & QWindowsScreenData::PrimaryScreen);
|
||||
qCDebug(lcQpaScreen) << "New Monitor: " << newData;
|
||||
} // exists
|
||||
} // for new screens.
|
||||
// Remove deleted ones but keep main monitors if we get only the
|
||||
// temporary lock screen to avoid window recreation (QTBUG-33062).
|
||||
if (!lockScreen) {
|
||||
for (int i = m_screens.size() - 1; i >= 0; --i) {
|
||||
if (indexOfMonitor(newDataList, m_screens.at(i)->data().deviceName) == -1)
|
||||
removeScreen(i);
|
||||
} // for existing screens
|
||||
} // not lock screen
|
||||
if (primaryScreenChanged) {
|
||||
if (auto theme = QWindowsTheme::instance()) // QTBUG-85734/Wine
|
||||
theme->refreshFonts();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void QWindowsScreenManager::clearScreens()
|
||||
{
|
||||
// Delete screens in reverse order to avoid crash in case of multiple screens
|
||||
while (!m_screens.isEmpty())
|
||||
QWindowSystemInterface::handleScreenRemoved(m_screens.takeLast());
|
||||
}
|
||||
|
||||
const QWindowsScreen *QWindowsScreenManager::screenAtDp(const QPoint &p) const
|
||||
{
|
||||
for (QWindowsScreen *scr : m_screens) {
|
||||
if (scr->geometry().contains(p))
|
||||
return scr;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const QWindowsScreen *QWindowsScreenManager::screenForHwnd(HWND hwnd) const
|
||||
{
|
||||
HMONITOR hMonitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONULL);
|
||||
if (hMonitor == nullptr)
|
||||
return nullptr;
|
||||
const auto it =
|
||||
std::find_if(m_screens.cbegin(), m_screens.cend(),
|
||||
[hMonitor](const QWindowsScreen *s)
|
||||
{
|
||||
return s->data().hMonitor == hMonitor
|
||||
&& (s->data().flags & QWindowsScreenData::VirtualDesktop) != 0;
|
||||
});
|
||||
return it != m_screens.cend() ? *it : nullptr;
|
||||
}
|
||||
|
||||
QT_END_NAMESPACE
|
1144
qtbase/src/plugins/platforms/windows/qwindowstheme.cpp
Normal file
1144
qtbase/src/plugins/platforms/windows/qwindowstheme.cpp
Normal file
File diff suppressed because it is too large
Load Diff
3476
qtbase/src/plugins/platforms/windows/qwindowswindow.cpp
Normal file
3476
qtbase/src/plugins/platforms/windows/qwindowswindow.cpp
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user