mirror of
https://github.com/crystalidea/qt6windows7.git
synced 2024-11-26 14:17:05 +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