qt 6.6.1 original sources files (to be patched)

This commit is contained in:
kleuter 2023-12-06 14:34:14 +01:00
parent db7d18b23c
commit b752f623ec
20 changed files with 26161 additions and 0 deletions

View 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

View 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

View 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

View 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

View 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

View 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

File diff suppressed because it is too large Load Diff

View 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

File diff suppressed because it is too large Load Diff

View 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

View 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

View 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(&currentMode);
if (SUCCEEDED(hr))
result = currentMode == 1; // Touch, 1
viewSettings->Release();
}
uiViewSettingsInterop->Release();
return result;
}
QT_END_NAMESPACE

File diff suppressed because it is too large Load Diff

View 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

View 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

File diff suppressed because it is too large Load Diff

View 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

View 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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff