qt6windows7/src/plugins/platforms/windows/qwindowscontext.cpp
2023-10-29 23:33:08 +01:00

1548 lines
57 KiB
C++

// Copyright (C) 2013 Samuel Gaist <samuel.gaist@edeltech.ch>
// 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 "qwindowscontext.h"
#include "qwindowsintegration.h"
#include "qwindowswindow.h"
#include "qwindowskeymapper.h"
#include "qwindowsnativeinterface.h"
#include "qwindowsmousehandler.h"
#include "qwindowspointerhandler.h"
#include "qtwindowsglobal.h"
#include "qwindowsmenu.h"
#include "qwindowsmimeregistry.h"
#include "qwindowsinputcontext.h"
#if QT_CONFIG(tabletevent)
# include "qwindowstabletsupport.h"
#endif
#include "qwindowstheme.h"
#include <private/qguiapplication_p.h>
#if QT_CONFIG(accessibility)
# include "uiautomation/qwindowsuiaaccessibility.h"
#endif
#if QT_CONFIG(sessionmanager)
# include <private/qsessionmanager_p.h>
# include "qwindowssessionmanager.h"
#endif
#include "qwindowsscreen.h"
#include "qwindowstheme.h"
#include <QtGui/qwindow.h>
#include <qpa/qwindowsysteminterface.h>
#include <qpa/qwindowsysteminterface_p.h>
#include <qpa/qplatformnativeinterface.h>
#include <QtGui/qguiapplication.h>
#include <QtGui/qopenglcontext.h>
#include <QtGui/qpointingdevice.h>
#include <QtCore/qset.h>
#include <QtCore/qhash.h>
#include <QtCore/qlibraryinfo.h>
#include <QtCore/qstringlist.h>
#include <QtCore/qdebug.h>
#include <QtCore/qsysinfo.h>
#include <QtCore/qscopedpointer.h>
#include <QtCore/quuid.h>
#include <QtCore/private/qwinregistry_p.h>
#include <QtCore/private/qfactorycacheregistration_p.h>
#include <QtCore/private/qsystemerror_p.h>
#include <QtGui/private/qwindowsguieventdispatcher_p.h>
#include <stdlib.h>
#include <stdio.h>
#include <windowsx.h>
#include <dbt.h>
#include <wtsapi32.h>
#include <shellscalingapi.h>
QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
Q_LOGGING_CATEGORY(lcQpaWindow, "qt.qpa.window")
Q_LOGGING_CATEGORY(lcQpaEvents, "qt.qpa.events")
Q_LOGGING_CATEGORY(lcQpaGl, "qt.qpa.gl")
Q_LOGGING_CATEGORY(lcQpaMime, "qt.qpa.mime")
Q_LOGGING_CATEGORY(lcQpaInputMethods, "qt.qpa.input.methods")
Q_LOGGING_CATEGORY(lcQpaDialogs, "qt.qpa.dialogs")
Q_LOGGING_CATEGORY(lcQpaMenus, "qt.qpa.menus")
Q_LOGGING_CATEGORY(lcQpaTablet, "qt.qpa.input.tablet")
Q_LOGGING_CATEGORY(lcQpaAccessibility, "qt.qpa.accessibility")
Q_LOGGING_CATEGORY(lcQpaUiAutomation, "qt.qpa.uiautomation")
Q_LOGGING_CATEGORY(lcQpaTrayIcon, "qt.qpa.trayicon")
Q_LOGGING_CATEGORY(lcQpaScreen, "qt.qpa.screen")
int QWindowsContext::verbose = 0;
#if !defined(LANG_SYRIAC)
# define LANG_SYRIAC 0x5a
#endif
static inline bool useRTL_Extensions()
{
// Since the IsValidLanguageGroup/IsValidLocale functions always return true on
// Vista, check the Keyboard Layouts for enabling RTL.
if (const int nLayouts = GetKeyboardLayoutList(0, nullptr)) {
QScopedArrayPointer<HKL> lpList(new HKL[nLayouts]);
GetKeyboardLayoutList(nLayouts, lpList.data());
for (int i = 0; i < nLayouts; ++i) {
switch (PRIMARYLANGID((quintptr)lpList[i])) {
case LANG_ARABIC:
case LANG_HEBREW:
case LANG_FARSI:
case LANG_SYRIAC:
return true;
default:
break;
}
}
}
return false;
}
#if QT_CONFIG(sessionmanager)
static inline QWindowsSessionManager *platformSessionManager()
{
auto *guiPrivate = static_cast<QGuiApplicationPrivate*>(QObjectPrivate::get(qApp));
auto *managerPrivate = static_cast<QSessionManagerPrivate*>(QObjectPrivate::get(guiPrivate->session_manager));
return static_cast<QWindowsSessionManager *>(managerPrivate->platformSessionManager);
}
static inline bool sessionManagerInteractionBlocked()
{
return platformSessionManager()->isInteractionBlocked();
}
#else // QT_CONFIG(sessionmanager)
static inline bool sessionManagerInteractionBlocked() { return false; }
#endif
static inline int windowDpiAwareness(HWND hwnd)
{
return static_cast<int>(GetAwarenessFromDpiAwarenessContext(GetWindowDpiAwarenessContext(hwnd)));
}
// Note: This only works within WM_NCCREATE
static bool enableNonClientDpiScaling(HWND hwnd)
{
bool result = false;
if (windowDpiAwareness(hwnd) == 2) {
result = EnableNonClientDpiScaling(hwnd) != FALSE;
if (!result) {
const DWORD errorCode = GetLastError();
qErrnoWarning(int(errorCode), "EnableNonClientDpiScaling() failed for HWND %p (%lu)",
hwnd, errorCode);
}
}
return result;
}
QWindowsContext *QWindowsContext::m_instance = nullptr;
/*!
\class QWindowsContext
\brief Singleton container for all relevant information.
Holds state information formerly stored in \c qapplication_win.cpp.
\internal
*/
struct QWindowsContextPrivate {
QWindowsContextPrivate();
unsigned m_systemInfo = 0;
QSet<QString> m_registeredWindowClassNames;
QWindowsContext::HandleBaseWindowHash m_windows;
HDC m_displayContext = nullptr;
int m_defaultDPI = 96;
QWindowsKeyMapper m_keyMapper;
QWindowsMouseHandler m_mouseHandler;
QWindowsPointerHandler m_pointerHandler;
QWindowsMimeRegistry m_mimeConverter;
QWindowsScreenManager m_screenManager;
QSharedPointer<QWindowCreationContext> m_creationContext;
#if QT_CONFIG(tabletevent)
QScopedPointer<QWindowsTabletSupport> m_tabletSupport;
#endif
const HRESULT m_oleInitializeResult;
QWindow *m_lastActiveWindow = nullptr;
bool m_asyncExpose = false;
HPOWERNOTIFY m_powerNotification = nullptr;
HWND m_powerDummyWindow = nullptr;
static bool m_darkMode;
static bool m_v2DpiAware;
};
bool QWindowsContextPrivate::m_darkMode = false;
bool QWindowsContextPrivate::m_v2DpiAware = false;
QWindowsContextPrivate::QWindowsContextPrivate()
: m_oleInitializeResult(OleInitialize(nullptr))
{
if (m_pointerHandler.touchDevice() || m_mouseHandler.touchDevice())
m_systemInfo |= QWindowsContext::SI_SupportsTouch;
m_displayContext = GetDC(nullptr);
m_defaultDPI = GetDeviceCaps(m_displayContext, LOGPIXELSY);
if (useRTL_Extensions()) {
m_systemInfo |= QWindowsContext::SI_RTL_Extensions;
m_keyMapper.setUseRTLExtensions(true);
}
m_darkMode = QWindowsTheme::queryDarkMode();
if (FAILED(m_oleInitializeResult)) {
qWarning() << "QWindowsContext: OleInitialize() failed: "
<< QSystemError::windowsComString(m_oleInitializeResult);
}
}
QWindowsContext::QWindowsContext() :
d(new QWindowsContextPrivate)
{
#ifdef Q_CC_MSVC
# pragma warning( disable : 4996 )
#endif
m_instance = this;
// ### FIXME: Remove this once the logging system has other options of configurations.
const QByteArray bv = qgetenv("QT_QPA_VERBOSE");
if (!bv.isEmpty())
QLoggingCategory::setFilterRules(QString::fromLocal8Bit(bv));
}
QWindowsContext::~QWindowsContext()
{
#if QT_CONFIG(tabletevent)
d->m_tabletSupport.reset(); // Destroy internal window before unregistering classes.
#endif
if (d->m_powerNotification)
UnregisterPowerSettingNotification(d->m_powerNotification);
if (d->m_powerDummyWindow)
DestroyWindow(d->m_powerDummyWindow);
unregisterWindowClasses();
if (d->m_oleInitializeResult == S_OK || d->m_oleInitializeResult == S_FALSE) {
#ifdef QT_USE_FACTORY_CACHE_REGISTRATION
detail::QWinRTFactoryCacheRegistration::clearAllCaches();
#endif
OleUninitialize();
}
d->m_screenManager.clearScreens(); // Order: Potentially calls back to the windows.
if (d->m_displayContext)
ReleaseDC(nullptr, d->m_displayContext);
m_instance = nullptr;
}
bool QWindowsContext::initTouch()
{
return initTouch(QWindowsIntegration::instance()->options());
}
bool QWindowsContext::initTouch(unsigned integrationOptions)
{
if (d->m_systemInfo & QWindowsContext::SI_SupportsTouch)
return true;
const bool usePointerHandler = (d->m_systemInfo & QWindowsContext::SI_SupportsPointer) != 0;
auto touchDevice = usePointerHandler ? d->m_pointerHandler.touchDevice() : d->m_mouseHandler.touchDevice();
if (touchDevice.isNull()) {
const bool mouseEmulation =
(integrationOptions & QWindowsIntegration::DontPassOsMouseEventsSynthesizedFromTouch) == 0;
touchDevice = QWindowsPointerHandler::createTouchDevice(mouseEmulation);
}
if (touchDevice.isNull())
return false;
d->m_pointerHandler.setTouchDevice(touchDevice);
d->m_mouseHandler.setTouchDevice(touchDevice);
QWindowSystemInterface::registerInputDevice(touchDevice.data());
d->m_systemInfo |= QWindowsContext::SI_SupportsTouch;
// A touch device was plugged while the app is running. Register all windows for touch.
registerTouchWindows();
return true;
}
void QWindowsContext::registerTouchWindows()
{
if (QGuiApplicationPrivate::is_app_running
&& (d->m_systemInfo & QWindowsContext::SI_SupportsTouch) != 0) {
for (QWindowsWindow *w : std::as_const(d->m_windows))
w->registerTouchWindow();
}
}
bool QWindowsContext::initTablet()
{
#if QT_CONFIG(tabletevent)
d->m_tabletSupport.reset(QWindowsTabletSupport::create());
return true;
#else
return false;
#endif
}
bool QWindowsContext::disposeTablet()
{
#if QT_CONFIG(tabletevent)
d->m_tabletSupport.reset();
return true;
#else
return false;
#endif
}
bool QWindowsContext::initPointer(unsigned integrationOptions)
{
if (integrationOptions & QWindowsIntegration::DontUseWMPointer)
return false;
d->m_systemInfo |= QWindowsContext::SI_SupportsPointer;
return true;
}
extern "C" LRESULT QT_WIN_CALLBACK qWindowsPowerWindowProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
if (message != WM_POWERBROADCAST || wParam != PBT_POWERSETTINGCHANGE)
return DefWindowProc(hwnd, message, wParam, lParam);
static bool initialized = false; // ignore the initial change
if (!initialized) {
initialized = true;
return DefWindowProc(hwnd, message, wParam, lParam);
}
auto setting = reinterpret_cast<const POWERBROADCAST_SETTING *>(lParam);
if (setting) {
auto data = reinterpret_cast<const DWORD *>(&setting->Data);
if (*data == 1) {
// Repaint the windows when returning from sleeping display mode.
const auto tlw = QGuiApplication::topLevelWindows();
for (auto w : tlw) {
if (w->isVisible() && w->windowState() != Qt::WindowMinimized) {
if (auto tw = QWindowsWindow::windowsWindowOf(w)) {
if (HWND hwnd = tw->handle()) {
InvalidateRect(hwnd, nullptr, false);
}
}
}
}
}
}
return DefWindowProc(hwnd, message, wParam, lParam);
}
bool QWindowsContext::initPowerNotificationHandler()
{
if (d->m_powerNotification)
return false;
d->m_powerDummyWindow = createDummyWindow(QStringLiteral("PowerDummyWindow"), L"QtPowerDummyWindow", qWindowsPowerWindowProc);
if (!d->m_powerDummyWindow)
return false;
d->m_powerNotification = RegisterPowerSettingNotification(d->m_powerDummyWindow, &GUID_MONITOR_POWER_ON, DEVICE_NOTIFY_WINDOW_HANDLE);
if (!d->m_powerNotification) {
DestroyWindow(d->m_powerDummyWindow);
d->m_powerDummyWindow = nullptr;
return false;
}
return true;
}
void QWindowsContext::setTabletAbsoluteRange(int a)
{
#if QT_CONFIG(tabletevent)
QWindowsTabletSupport::setAbsoluteRange(a);
#else
Q_UNUSED(a);
#endif
}
void QWindowsContext::setDetectAltGrModifier(bool a)
{
d->m_keyMapper.setDetectAltGrModifier(a);
}
int QWindowsContext::processDpiAwareness()
{
PROCESS_DPI_AWARENESS result;
if (SUCCEEDED(GetProcessDpiAwareness(nullptr, &result))) {
return static_cast<int>(result);
}
return -1;
}
void QWindowsContext::setProcessDpiAwareness(QtWindows::ProcessDpiAwareness dpiAwareness)
{
qCDebug(lcQpaWindow) << __FUNCTION__ << dpiAwareness;
if (processDpiAwareness() == int(dpiAwareness))
return;
const HRESULT hr = SetProcessDpiAwareness(static_cast<PROCESS_DPI_AWARENESS>(dpiAwareness));
if (FAILED(hr)) {
qCWarning(lcQpaWindow).noquote().nospace() << "SetProcessDpiAwareness("
<< dpiAwareness << ") failed: " << QSystemError::windowsComString(hr) << ", using "
<< QWindowsContext::processDpiAwareness() << "\nQt's fallback DPI awareness is "
<< "PROCESS_DPI_AWARENESS. If you know what you are doing consider an override in qt.conf";
}
}
bool QWindowsContext::setProcessDpiV2Awareness()
{
qCDebug(lcQpaWindow) << __FUNCTION__;
auto dpiContext = GetThreadDpiAwarenessContext();
if (AreDpiAwarenessContextsEqual(dpiContext, DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2))
return true;
const BOOL ok = SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
if (!ok) {
const DWORD dwError = GetLastError();
qCWarning(lcQpaWindow).noquote().nospace()
<< "SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) failed: "
<< QSystemError::windowsComString(HRESULT(dwError)) << "\nQt's default DPI awareness "
<< "context is DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2. If you know what you "
<< "are doing you can overwrite this default using qt.conf "
<< "(https://doc.qt.io/qt-6/highdpi.html#configuring-windows)";
return false;
}
QWindowsContextPrivate::m_v2DpiAware = true;
return true;
}
bool QWindowsContext::isDarkMode()
{
return QWindowsContextPrivate::m_darkMode;
}
QWindowsContext *QWindowsContext::instance()
{
return m_instance;
}
unsigned QWindowsContext::systemInfo() const
{
return d->m_systemInfo;
}
bool QWindowsContext::useRTLExtensions() const
{
return d->m_keyMapper.useRTLExtensions();
}
QList<int> QWindowsContext::possibleKeys(const QKeyEvent *e) const
{
return d->m_keyMapper.possibleKeys(e);
}
QWindowsContext::HandleBaseWindowHash &QWindowsContext::windows()
{
return d->m_windows;
}
QSharedPointer<QWindowCreationContext> QWindowsContext::setWindowCreationContext(const QSharedPointer<QWindowCreationContext> &ctx)
{
const QSharedPointer<QWindowCreationContext> old = d->m_creationContext;
d->m_creationContext = ctx;
return old;
}
QSharedPointer<QWindowCreationContext> QWindowsContext::windowCreationContext() const
{
return d->m_creationContext;
}
int QWindowsContext::defaultDPI() const
{
return d->m_defaultDPI;
}
HDC QWindowsContext::displayContext() const
{
return d->m_displayContext;
}
QWindow *QWindowsContext::keyGrabber() const
{
return d->m_keyMapper.keyGrabber();
}
void QWindowsContext::setKeyGrabber(QWindow *w)
{
d->m_keyMapper.setKeyGrabber(w);
}
QString QWindowsContext::classNamePrefix()
{
static QString result;
if (result.isEmpty()) {
QTextStream str(&result);
str << "Qt" << QT_VERSION_MAJOR << QT_VERSION_MINOR << QT_VERSION_PATCH;
if (QLibraryInfo::isDebugBuild())
str << 'd';
#ifdef QT_NAMESPACE
# define xstr(s) str(s)
# define str(s) #s
str << xstr(QT_NAMESPACE);
#endif
}
return result;
}
// Window class registering code (from qapplication_win.cpp)
QString QWindowsContext::registerWindowClass(const QWindow *w)
{
Q_ASSERT(w);
const Qt::WindowFlags flags = w->flags();
const Qt::WindowFlags type = flags & Qt::WindowType_Mask;
// Determine style and icon.
uint style = CS_DBLCLKS;
bool icon = true;
// The following will not set CS_OWNDC for any widget window, even if it contains a
// QOpenGLWidget or QQuickWidget later on. That cannot be detected at this stage.
if (w->surfaceType() == QSurface::OpenGLSurface || (flags & Qt::MSWindowsOwnDC))
style |= CS_OWNDC;
if (!(flags & Qt::NoDropShadowWindowHint)
&& (type == Qt::Popup || w->property("_q_windowsDropShadow").toBool())) {
style |= CS_DROPSHADOW;
}
switch (type) {
case Qt::Tool:
case Qt::ToolTip:
case Qt::Popup:
style |= CS_SAVEBITS; // Save/restore background
icon = false;
break;
case Qt::Dialog:
if (!(flags & Qt::WindowSystemMenuHint))
icon = false; // QTBUG-2027, dialogs without system menu.
break;
}
// Create a unique name for the flag combination
QString cname = classNamePrefix();
cname += "QWindow"_L1;
switch (type) {
case Qt::Tool:
cname += "Tool"_L1;
break;
case Qt::ToolTip:
cname += "ToolTip"_L1;
break;
case Qt::Popup:
cname += "Popup"_L1;
break;
default:
break;
}
if (style & CS_DROPSHADOW)
cname += "DropShadow"_L1;
if (style & CS_SAVEBITS)
cname += "SaveBits"_L1;
if (style & CS_OWNDC)
cname += "OwnDC"_L1;
if (icon)
cname += "Icon"_L1;
return registerWindowClass(cname, qWindowsWndProc, style, nullptr, icon);
}
QString QWindowsContext::registerWindowClass(QString cname,
WNDPROC proc,
unsigned style,
HBRUSH brush,
bool icon)
{
// since multiple Qt versions can be used in one process
// each one has to have window class names with a unique name
// The first instance gets the unmodified name; if the class
// has already been registered by another instance of Qt then
// add a UUID. The check needs to be performed for each name
// in case new message windows are added (QTBUG-81347).
// Note: GetClassInfo() returns != 0 when a class exists.
const auto appInstance = static_cast<HINSTANCE>(GetModuleHandle(nullptr));
WNDCLASS wcinfo;
const bool classExists = GetClassInfo(appInstance, reinterpret_cast<LPCWSTR>(cname.utf16()), &wcinfo) != FALSE
&& wcinfo.lpfnWndProc != proc;
if (classExists)
cname += QUuid::createUuid().toString();
if (d->m_registeredWindowClassNames.contains(cname)) // already registered in our list
return cname;
WNDCLASSEX wc;
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = style;
wc.lpfnWndProc = proc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = appInstance;
wc.hCursor = nullptr;
wc.hbrBackground = brush;
if (icon) {
wc.hIcon = static_cast<HICON>(LoadImage(appInstance, L"IDI_ICON1", IMAGE_ICON, 0, 0, LR_DEFAULTSIZE));
if (wc.hIcon) {
int sw = GetSystemMetrics(SM_CXSMICON);
int sh = GetSystemMetrics(SM_CYSMICON);
wc.hIconSm = static_cast<HICON>(LoadImage(appInstance, L"IDI_ICON1", IMAGE_ICON, sw, sh, 0));
} else {
wc.hIcon = static_cast<HICON>(LoadImage(nullptr, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_SHARED));
wc.hIconSm = nullptr;
}
} else {
wc.hIcon = nullptr;
wc.hIconSm = nullptr;
}
wc.lpszMenuName = nullptr;
wc.lpszClassName = reinterpret_cast<LPCWSTR>(cname.utf16());
ATOM atom = RegisterClassEx(&wc);
if (!atom)
qErrnoWarning("QApplication::regClass: Registering window class '%s' failed.",
qPrintable(cname));
d->m_registeredWindowClassNames.insert(cname);
qCDebug(lcQpaWindow).nospace() << __FUNCTION__ << ' ' << cname
<< " style=0x" << Qt::hex << style << Qt::dec
<< " brush=" << brush << " icon=" << icon << " atom=" << atom;
return cname;
}
void QWindowsContext::unregisterWindowClasses()
{
const auto appInstance = static_cast<HINSTANCE>(GetModuleHandle(nullptr));
for (const QString &name : std::as_const(d->m_registeredWindowClassNames)) {
if (!UnregisterClass(reinterpret_cast<LPCWSTR>(name.utf16()), appInstance) && QWindowsContext::verbose)
qErrnoWarning("UnregisterClass failed for '%s'", qPrintable(name));
}
d->m_registeredWindowClassNames.clear();
}
int QWindowsContext::screenDepth() const
{
return GetDeviceCaps(d->m_displayContext, BITSPIXEL);
}
void QWindowsContext::addWindow(HWND hwnd, QWindowsWindow *w)
{
d->m_windows.insert(hwnd, w);
}
void QWindowsContext::removeWindow(HWND hwnd)
{
const HandleBaseWindowHash::iterator it = d->m_windows.find(hwnd);
if (it != d->m_windows.end()) {
if (d->m_keyMapper.keyGrabber() == it.value()->window())
d->m_keyMapper.setKeyGrabber(nullptr);
d->m_windows.erase(it);
}
}
QWindowsWindow *QWindowsContext::findPlatformWindow(const QWindowsMenuBar *mb) const
{
for (auto it = d->m_windows.cbegin(), end = d->m_windows.cend(); it != end; ++it) {
if ((*it)->menuBar() == mb)
return *it;
}
return nullptr;
}
QWindowsWindow *QWindowsContext::findPlatformWindow(HWND hwnd) const
{
return d->m_windows.value(hwnd);
}
QWindowsWindow *QWindowsContext::findClosestPlatformWindow(HWND hwnd) const
{
QWindowsWindow *window = d->m_windows.value(hwnd);
// Requested hwnd may also be a child of a platform window in case of embedded native windows.
// Find the closest parent that has a platform window.
if (!window) {
for (HWND w = hwnd; w; w = GetParent(w)) {
window = d->m_windows.value(w);
if (window)
break;
}
}
return window;
}
QWindow *QWindowsContext::findWindow(HWND hwnd) const
{
if (const QWindowsWindow *bw = findPlatformWindow(hwnd))
return bw->window();
return nullptr;
}
QWindow *QWindowsContext::windowUnderMouse() const
{
return (d->m_systemInfo & QWindowsContext::SI_SupportsPointer) ?
d->m_pointerHandler.windowUnderMouse() : d->m_mouseHandler.windowUnderMouse();
}
void QWindowsContext::clearWindowUnderMouse()
{
if (d->m_systemInfo & QWindowsContext::SI_SupportsPointer)
d->m_pointerHandler.clearWindowUnderMouse();
else
d->m_mouseHandler.clearWindowUnderMouse();
}
/*!
\brief Find a child window at a screen point.
Deep search for a QWindow at global point, skipping non-owned
windows (accessibility?). Implemented using ChildWindowFromPointEx()
instead of (historically used) WindowFromPoint() to get a well-defined
behaviour for hidden/transparent windows.
\a cwex_flags are flags of ChildWindowFromPointEx().
\a parent is the parent window, pass GetDesktopWindow() for top levels.
*/
static inline bool findPlatformWindowHelper(const POINT &screenPoint, unsigned cwexFlags,
const QWindowsContext *context,
HWND *hwnd, QWindowsWindow **result)
{
POINT point = screenPoint;
screenToClient(*hwnd, &point);
// Returns parent if inside & none matched.
const HWND child = ChildWindowFromPointEx(*hwnd, point, cwexFlags);
if (!child || child == *hwnd)
return false;
if (QWindowsWindow *window = context->findPlatformWindow(child)) {
*result = window;
*hwnd = child;
return true;
}
// QTBUG-40555: despite CWP_SKIPINVISIBLE, it is possible to hit on invisible
// full screen windows of other applications that have WS_EX_TRANSPARENT set
// (for example created by screen sharing applications). In that case, try to
// find a Qt window by searching again with CWP_SKIPTRANSPARENT.
// Note that Qt 5 uses WS_EX_TRANSPARENT for Qt::WindowTransparentForInput
// as well.
if (!(cwexFlags & CWP_SKIPTRANSPARENT)
&& (GetWindowLongPtr(child, GWL_EXSTYLE) & WS_EX_TRANSPARENT)) {
const HWND nonTransparentChild = ChildWindowFromPointEx(*hwnd, point, cwexFlags | CWP_SKIPTRANSPARENT);
if (!nonTransparentChild || nonTransparentChild == *hwnd)
return false;
if (QWindowsWindow *nonTransparentWindow = context->findPlatformWindow(nonTransparentChild)) {
*result = nonTransparentWindow;
*hwnd = nonTransparentChild;
return true;
}
}
*hwnd = child;
return true;
}
QWindowsWindow *QWindowsContext::findPlatformWindowAt(HWND parent,
const QPoint &screenPointIn,
unsigned cwex_flags) const
{
QWindowsWindow *result = nullptr;
const POINT screenPoint = { screenPointIn.x(), screenPointIn.y() };
while (findPlatformWindowHelper(screenPoint, cwex_flags, this, &parent, &result)) {}
// QTBUG-40815: ChildWindowFromPointEx() can hit on special windows from
// screen recorder applications like ScreenToGif. Fall back to WindowFromPoint().
if (result == nullptr) {
if (const HWND window = WindowFromPoint(screenPoint))
result = findPlatformWindow(window);
}
return result;
}
bool QWindowsContext::isSessionLocked()
{
bool result = false;
const DWORD sessionId = WTSGetActiveConsoleSessionId();
if (sessionId != 0xFFFFFFFF) {
LPTSTR buffer = nullptr;
DWORD size = 0;
#if !defined(Q_CC_MINGW)
if (WTSQuerySessionInformation(WTS_CURRENT_SERVER_HANDLE, sessionId,
WTSSessionInfoEx, &buffer, &size) == TRUE
&& size > 0) {
const WTSINFOEXW *info = reinterpret_cast<WTSINFOEXW *>(buffer);
result = info->Level == 1 && info->Data.WTSInfoExLevel1.SessionFlags == WTS_SESSIONSTATE_LOCK;
WTSFreeMemory(buffer);
}
#else // MinGW as of 7.3 does not have WTSINFOEXW in wtsapi32.h
// Retrieve the flags which are at offset 16 due to padding for 32/64bit alike.
if (WTSQuerySessionInformation(WTS_CURRENT_SERVER_HANDLE, sessionId,
WTS_INFO_CLASS(25), &buffer, &size) == TRUE
&& size >= 20) {
const DWORD *p = reinterpret_cast<DWORD *>(buffer);
const DWORD level = *p;
const DWORD sessionFlags = *(p + 4);
result = level == 1 && sessionFlags == 1;
WTSFreeMemory(buffer);
}
#endif // Q_CC_MINGW
}
return result;
}
QWindowsMimeRegistry &QWindowsContext::mimeConverter() const
{
return d->m_mimeConverter;
}
QWindowsScreenManager &QWindowsContext::screenManager()
{
return d->m_screenManager;
}
QWindowsTabletSupport *QWindowsContext::tabletSupport() const
{
#if QT_CONFIG(tabletevent)
return d->m_tabletSupport.data();
#else
return 0;
#endif
}
/*!
\brief Convenience to create a non-visible, message-only dummy
window for example used as clipboard watcher or for GL.
*/
HWND QWindowsContext::createDummyWindow(const QString &classNameIn,
const wchar_t *windowName,
WNDPROC wndProc, DWORD style)
{
if (!wndProc)
wndProc = DefWindowProc;
QString className = registerWindowClass(classNamePrefix() + classNameIn, wndProc);
return CreateWindowEx(0, reinterpret_cast<LPCWSTR>(className.utf16()),
windowName, style,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
HWND_MESSAGE, nullptr, static_cast<HINSTANCE>(GetModuleHandle(nullptr)), nullptr);
}
void QWindowsContext::forceNcCalcSize(HWND hwnd)
{
// Force WM_NCCALCSIZE to adjust margin
SetWindowPos(hwnd, nullptr, 0, 0, 0, 0,
SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER);
}
bool QWindowsContext::systemParametersInfo(unsigned action, unsigned param, void *out,
unsigned dpi)
{
const BOOL result = dpi != 0
? SystemParametersInfoForDpi(action, param, out, 0, dpi)
: SystemParametersInfo(action, param, out, 0);
return result == TRUE;
}
bool QWindowsContext::systemParametersInfoForScreen(unsigned action, unsigned param, void *out,
const QPlatformScreen *screen)
{
return systemParametersInfo(action, param, out, screen ? unsigned(screen->logicalDpi().first) : 0u);
}
bool QWindowsContext::systemParametersInfoForWindow(unsigned action, unsigned param, void *out,
const QPlatformWindow *win)
{
return systemParametersInfoForScreen(action, param, out, win ? win->screen() : nullptr);
}
bool QWindowsContext::nonClientMetrics(NONCLIENTMETRICS *ncm, unsigned dpi)
{
memset(ncm, 0, sizeof(NONCLIENTMETRICS));
ncm->cbSize = sizeof(NONCLIENTMETRICS);
return systemParametersInfo(SPI_GETNONCLIENTMETRICS, ncm->cbSize, ncm, dpi);
}
bool QWindowsContext::nonClientMetricsForScreen(NONCLIENTMETRICS *ncm,
const QPlatformScreen *screen)
{
const int dpi = screen ? qRound(screen->logicalDpi().first) : 0;
return nonClientMetrics(ncm, unsigned(dpi));
}
bool QWindowsContext::nonClientMetricsForWindow(NONCLIENTMETRICS *ncm, const QPlatformWindow *win)
{
return nonClientMetricsForScreen(ncm, win ? win->screen() : nullptr);
}
static inline QWindowsInputContext *windowsInputContext()
{
return qobject_cast<QWindowsInputContext *>(QWindowsIntegration::instance()->inputContext());
}
bool QWindowsContext::shouldHaveNonClientDpiScaling(const QWindow *window)
{
// DPI aware V2 processes always have NonClientDpiScaling enabled.
if (QWindowsContextPrivate::m_v2DpiAware)
return true;
return window->isTopLevel()
&& !window->property(QWindowsWindow::embeddedNativeParentHandleProperty).isValid()
#if QT_CONFIG(opengl) // /QTBUG-62901, EnableNonClientDpiScaling has problems with GL
&& (window->surfaceType() != QSurface::OpenGLSurface
|| QOpenGLContext::openGLModuleType() != QOpenGLContext::LibGL)
#endif
;
}
static inline bool isInputMessage(UINT m)
{
switch (m) {
case WM_IME_STARTCOMPOSITION:
case WM_IME_ENDCOMPOSITION:
case WM_IME_COMPOSITION:
case WM_INPUT:
case WM_TOUCH:
case WM_MOUSEHOVER:
case WM_MOUSELEAVE:
case WM_NCMOUSEHOVER:
case WM_NCMOUSELEAVE:
case WM_SIZING:
case WM_MOVING:
case WM_SYSCOMMAND:
case WM_COMMAND:
case WM_DWMNCRENDERINGCHANGED:
case WM_PAINT:
return true;
default:
break;
}
return (m >= WM_MOUSEFIRST && m <= WM_MOUSELAST)
|| (m >= WM_NCMOUSEMOVE && m <= WM_NCXBUTTONDBLCLK)
|| (m >= WM_KEYFIRST && m <= WM_KEYLAST);
}
/*!
\brief Main windows procedure registered for windows.
\sa QWindowsGuiEventDispatcher
*/
bool QWindowsContext::windowsProc(HWND hwnd, UINT message,
QtWindows::WindowsEventType et,
WPARAM wParam, LPARAM lParam,
LRESULT *result,
QWindowsWindow **platformWindowPtr)
{
*result = 0;
MSG msg;
msg.hwnd = hwnd; // re-create MSG structure
msg.message = message; // time and pt fields ignored
msg.wParam = wParam;
msg.lParam = lParam;
msg.pt.x = msg.pt.y = 0;
if (et != QtWindows::CursorEvent && (et & (QtWindows::MouseEventFlag | QtWindows::NonClientEventFlag))) {
msg.pt.x = GET_X_LPARAM(lParam);
msg.pt.y = GET_Y_LPARAM(lParam);
// For non-client-area messages, these are screen coordinates (as expected
// in the MSG structure), otherwise they are client coordinates.
if (!(et & QtWindows::NonClientEventFlag)) {
clientToScreen(msg.hwnd, &msg.pt);
}
} else {
GetCursorPos(&msg.pt);
}
QWindowsWindow *platformWindow = findPlatformWindow(hwnd);
*platformWindowPtr = platformWindow;
// Run the native event filters. QTBUG-67095: Exclude input messages which are sent
// by QEventDispatcherWin32::processEvents()
if (!isInputMessage(msg.message) && filterNativeEvent(&msg, result))
return true;
if (platformWindow && filterNativeEvent(platformWindow->window(), &msg, result))
return true;
if (et & QtWindows::InputMethodEventFlag) {
QWindowsInputContext *windowsInputContext = ::windowsInputContext();
// Disable IME assuming this is a special implementation hooking into keyboard input.
// "Real" IME implementations should use a native event filter intercepting IME events.
if (!windowsInputContext) {
QWindowsInputContext::setWindowsImeEnabled(platformWindow, false);
return false;
}
switch (et) {
case QtWindows::InputMethodStartCompositionEvent:
return windowsInputContext->startComposition(hwnd);
case QtWindows::InputMethodCompositionEvent:
return windowsInputContext->composition(hwnd, lParam);
case QtWindows::InputMethodEndCompositionEvent:
return windowsInputContext->endComposition(hwnd);
case QtWindows::InputMethodRequest:
return windowsInputContext->handleIME_Request(wParam, lParam, result);
default:
break;
}
} // InputMethodEventFlag
switch (et) {
case QtWindows::GestureEvent:
if (!(d->m_systemInfo & QWindowsContext::SI_SupportsPointer))
return sessionManagerInteractionBlocked() || d->m_mouseHandler.translateGestureEvent(platformWindow->window(), hwnd, et, msg, result);
break;
case QtWindows::InputMethodOpenCandidateWindowEvent:
case QtWindows::InputMethodCloseCandidateWindowEvent:
// TODO: Release/regrab mouse if a popup has mouse grab.
return false;
case QtWindows::DestroyEvent:
if (platformWindow && !platformWindow->testFlag(QWindowsWindow::WithinDestroy)) {
qWarning() << "External WM_DESTROY received for " << platformWindow->window()
<< ", parent: " << platformWindow->window()->parent()
<< ", transient parent: " << platformWindow->window()->transientParent();
}
return false;
case QtWindows::ClipboardEvent:
return false;
case QtWindows::CursorEvent: // Sent to windows that do not have capture (see QTBUG-58590).
if (QWindowsCursor::hasOverrideCursor()) {
QWindowsCursor::enforceOverrideCursor();
return true;
}
break;
case QtWindows::UnknownEvent:
return false;
case QtWindows::AccessibleObjectFromWindowRequest:
#if QT_CONFIG(accessibility)
return QWindowsUiaAccessibility::handleWmGetObject(hwnd, wParam, lParam, result);
#else
return false;
#endif
case QtWindows::SettingChangedEvent: {
QWindowsWindow::settingsChanged();
// Only refresh the window theme if the user changes the personalize settings.
if ((wParam == 0) && (lParam != 0) // lParam sometimes may be NULL.
&& (wcscmp(reinterpret_cast<LPCWSTR>(lParam), L"ImmersiveColorSet") == 0)) {
const bool darkMode = QWindowsTheme::queryDarkMode();
const bool darkModeChanged = darkMode != QWindowsContextPrivate::m_darkMode;
QWindowsContextPrivate::m_darkMode = darkMode;
auto integration = QWindowsIntegration::instance();
integration->updateApplicationBadge();
if (integration->darkModeHandling().testFlag(QWindowsApplication::DarkModeStyle)) {
QWindowsTheme::instance()->refresh();
QWindowSystemInterface::handleThemeChange();
}
if (darkModeChanged) {
if (integration->darkModeHandling().testFlag(QWindowsApplication::DarkModeWindowFrames)) {
for (QWindowsWindow *w : d->m_windows)
w->setDarkBorder(QWindowsContextPrivate::m_darkMode);
}
}
}
return d->m_screenManager.handleScreenChanges();
}
default:
break;
}
// Before CreateWindowEx() returns, some events are sent,
// for example WM_GETMINMAXINFO asking for size constraints for top levels.
// Pass on to current creation context
if (!platformWindow && !d->m_creationContext.isNull()) {
switch (et) {
case QtWindows::QuerySizeHints:
d->m_creationContext->applyToMinMaxInfo(reinterpret_cast<MINMAXINFO *>(lParam));
return true;
case QtWindows::ResizeEvent:
d->m_creationContext->obtainedSize = QSize(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
return true;
case QtWindows::MoveEvent:
d->m_creationContext->obtainedPos = QPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
return true;
case QtWindows::NonClientCreate:
if (shouldHaveNonClientDpiScaling(d->m_creationContext->window) &&
// DPI aware V2 processes always have NonClientDpiScaling enabled
// and there is no need to make an API call to manually enable.
!QWindowsContextPrivate::m_v2DpiAware) {
enableNonClientDpiScaling(msg.hwnd);
}
return false;
case QtWindows::CalculateSize:
return QWindowsGeometryHint::handleCalculateSize(d->m_creationContext->customMargins, msg, result);
case QtWindows::GeometryChangingEvent:
return QWindowsWindow::handleGeometryChangingMessage(&msg, d->m_creationContext->window,
d->m_creationContext->margins + d->m_creationContext->customMargins);
default:
break;
}
}
if (platformWindow) {
// Suppress events sent during DestroyWindow() for native children.
if (platformWindow->testFlag(QWindowsWindow::WithinDestroy))
return false;
if (QWindowsContext::verbose > 1)
qCDebug(lcQpaEvents) << "Event window: " << platformWindow->window();
} else {
qWarning("%s: No Qt Window found for event 0x%x (%s), hwnd=0x%p.",
__FUNCTION__, message,
QWindowsGuiEventDispatcher::windowsMessageName(message), hwnd);
return false;
}
switch (et) {
case QtWindows::DeviceChangeEvent:
if (d->m_systemInfo & QWindowsContext::SI_SupportsTouch)
break;
// See if there are any touch devices added
if (wParam == DBT_DEVNODES_CHANGED)
initTouch();
break;
case QtWindows::KeyboardLayoutChangeEvent:
if (QWindowsInputContext *wic = windowsInputContext())
wic->handleInputLanguageChanged(wParam, lParam);
Q_FALLTHROUGH();
case QtWindows::KeyDownEvent:
case QtWindows::KeyEvent:
case QtWindows::InputMethodKeyEvent:
case QtWindows::InputMethodKeyDownEvent:
case QtWindows::AppCommandEvent:
return sessionManagerInteractionBlocked() || d->m_keyMapper.translateKeyEvent(platformWindow->window(), hwnd, msg, result);
case QtWindows::MenuAboutToShowEvent:
if (sessionManagerInteractionBlocked())
return true;
if (QWindowsPopupMenu::notifyAboutToShow(reinterpret_cast<HMENU>(wParam)))
return true;
if (platformWindow == nullptr || platformWindow->menuBar() == nullptr)
return false;
return platformWindow->menuBar()->notifyAboutToShow(reinterpret_cast<HMENU>(wParam));
case QtWindows::MenuCommandEvent:
if (sessionManagerInteractionBlocked())
return true;
if (QWindowsPopupMenu::notifyTriggered(LOWORD(wParam)))
return true;
if (platformWindow == nullptr || platformWindow->menuBar() == nullptr)
return false;
return platformWindow->menuBar()->notifyTriggered(LOWORD(wParam));
case QtWindows::MoveEvent:
platformWindow->handleMoved();
return true;
case QtWindows::ResizeEvent:
platformWindow->handleResized(static_cast<int>(wParam), lParam);
return true;
case QtWindows::QuerySizeHints:
platformWindow->getSizeHints(reinterpret_cast<MINMAXINFO *>(lParam));
return true;// maybe available on some SDKs revisit WM_NCCALCSIZE
case QtWindows::CalculateSize:
return QWindowsGeometryHint::handleCalculateSize(platformWindow->customMargins(), msg, result);
case QtWindows::NonClientHitTest:
return platformWindow->handleNonClientHitTest(QPoint(msg.pt.x, msg.pt.y), result);
case QtWindows::GeometryChangingEvent:
return platformWindow->handleGeometryChanging(&msg);
case QtWindows::ExposeEvent:
return platformWindow->handleWmPaint(hwnd, message, wParam, lParam, result);
case QtWindows::NonClientMouseEvent:
if (!platformWindow->frameStrutEventsEnabled())
break;
if ((d->m_systemInfo & QWindowsContext::SI_SupportsPointer))
return sessionManagerInteractionBlocked() || d->m_pointerHandler.translateMouseEvent(platformWindow->window(), hwnd, et, msg, result);
else
return sessionManagerInteractionBlocked() || d->m_mouseHandler.translateMouseEvent(platformWindow->window(), hwnd, et, msg, result);
case QtWindows::NonClientPointerEvent:
if (!platformWindow->frameStrutEventsEnabled())
break;
if ((d->m_systemInfo & QWindowsContext::SI_SupportsPointer))
return sessionManagerInteractionBlocked() || d->m_pointerHandler.translatePointerEvent(platformWindow->window(), hwnd, et, msg, result);
break;
case QtWindows::EnterSizeMoveEvent:
platformWindow->setFlag(QWindowsWindow::ResizeMoveActive);
if (!IsZoomed(hwnd))
platformWindow->updateRestoreGeometry();
return true;
case QtWindows::ExitSizeMoveEvent:
platformWindow->clearFlag(QWindowsWindow::ResizeMoveActive);
platformWindow->checkForScreenChanged();
handleExitSizeMove(platformWindow->window());
if (!IsZoomed(hwnd))
platformWindow->updateRestoreGeometry();
return true;
case QtWindows::ScrollEvent:
if (!(d->m_systemInfo & QWindowsContext::SI_SupportsPointer))
return sessionManagerInteractionBlocked() || d->m_mouseHandler.translateScrollEvent(platformWindow->window(), hwnd, msg, result);
break;
case QtWindows::MouseWheelEvent:
case QtWindows::MouseEvent:
case QtWindows::LeaveEvent:
{
QWindow *window = platformWindow->window();
while (window && (window->flags() & Qt::WindowTransparentForInput))
window = window->parent();
if (!window)
return false;
if (d->m_systemInfo & QWindowsContext::SI_SupportsPointer)
return sessionManagerInteractionBlocked() || d->m_pointerHandler.translateMouseEvent(window, hwnd, et, msg, result);
else
return sessionManagerInteractionBlocked() || d->m_mouseHandler.translateMouseEvent(window, hwnd, et, msg, result);
}
break;
case QtWindows::TouchEvent:
if (!(d->m_systemInfo & QWindowsContext::SI_SupportsPointer))
return sessionManagerInteractionBlocked() || d->m_mouseHandler.translateTouchEvent(platformWindow->window(), hwnd, et, msg, result);
break;
case QtWindows::PointerEvent:
if (d->m_systemInfo & QWindowsContext::SI_SupportsPointer)
return sessionManagerInteractionBlocked() || d->m_pointerHandler.translatePointerEvent(platformWindow->window(), hwnd, et, msg, result);
break;
case QtWindows::FocusInEvent: // see QWindowsWindow::requestActivateWindow().
case QtWindows::FocusOutEvent:
handleFocusEvent(et, platformWindow);
return true;
case QtWindows::ShowEventOnParentRestoring: // QTBUG-40696, prevent Windows from re-showing hidden transient children (dialogs).
if (!platformWindow->window()->isVisible()) {
*result = 0;
return true;
}
break;
case QtWindows::HideEvent:
platformWindow->handleHidden();
return false;// Indicate transient children should be hidden by windows (SW_PARENTCLOSING)
case QtWindows::CloseEvent:
QWindowSystemInterface::handleCloseEvent(platformWindow->window());
return true;
case QtWindows::ThemeChanged: {
// Switch from Aero to Classic changes margins.
if (QWindowsTheme *theme = QWindowsTheme::instance())
theme->windowsThemeChanged(platformWindow->window());
return true;
}
case QtWindows::CompositionSettingsChanged:
platformWindow->handleCompositionSettingsChanged();
return true;
case QtWindows::ActivateWindowEvent:
if (platformWindow->window()->flags() & Qt::WindowDoesNotAcceptFocus) {
*result = LRESULT(MA_NOACTIVATE);
return true;
}
#if QT_CONFIG(tabletevent)
if (!d->m_tabletSupport.isNull())
d->m_tabletSupport->notifyActivate();
#endif // QT_CONFIG(tabletevent)
if (platformWindow->testFlag(QWindowsWindow::BlockedByModal))
if (const QWindow *modalWindow = QGuiApplication::modalWindow()) {
QWindowsWindow *platformWindow = QWindowsWindow::windowsWindowOf(modalWindow);
Q_ASSERT(platformWindow);
platformWindow->alertWindow();
}
break;
case QtWindows::MouseActivateWindowEvent:
case QtWindows::PointerActivateWindowEvent:
if (platformWindow->window()->flags() & Qt::WindowDoesNotAcceptFocus) {
*result = LRESULT(MA_NOACTIVATE);
return true;
}
break;
#ifndef QT_NO_CONTEXTMENU
case QtWindows::ContextMenu:
return handleContextMenuEvent(platformWindow->window(), msg);
#endif
case QtWindows::WhatsThisEvent: {
#ifndef QT_NO_WHATSTHIS
QWindowSystemInterface::handleEnterWhatsThisEvent();
return true;
#endif
} break;
case QtWindows::DpiScaledSizeEvent:
platformWindow->handleDpiScaledSize(wParam, lParam, result);
return true;
case QtWindows::DpiChangedEvent:
platformWindow->handleDpiChanged(hwnd, wParam, lParam);
return true;
case QtWindows::DpiChangedAfterParentEvent:
platformWindow->handleDpiChangedAfterParent(hwnd);
return true;
#if QT_CONFIG(sessionmanager)
case QtWindows::QueryEndSessionApplicationEvent: {
QWindowsSessionManager *sessionManager = platformSessionManager();
if (sessionManager->isActive()) { // bogus message from windows
*result = sessionManager->wasCanceled() ? 0 : 1;
return true;
}
sessionManager->setActive(true);
sessionManager->blocksInteraction();
sessionManager->clearCancellation();
auto *qGuiAppPriv = static_cast<QGuiApplicationPrivate*>(QObjectPrivate::get(qApp));
qGuiAppPriv->commitData();
if (lParam & ENDSESSION_LOGOFF)
fflush(nullptr);
*result = sessionManager->wasCanceled() ? 0 : 1;
return true;
}
case QtWindows::EndSessionApplicationEvent: {
QWindowsSessionManager *sessionManager = platformSessionManager();
sessionManager->setActive(false);
sessionManager->allowsInteraction();
const bool endsession = wParam != 0;
// we receive the message for each toplevel window included internal hidden ones,
// but the aboutToQuit signal should be emitted only once.
auto *qGuiAppPriv = static_cast<QGuiApplicationPrivate*>(QObjectPrivate::get(qApp));
if (endsession && !qGuiAppPriv->aboutToQuitEmitted) {
qGuiAppPriv->aboutToQuitEmitted = true;
int index = QGuiApplication::staticMetaObject.indexOfSignal("aboutToQuit()");
qApp->qt_metacall(QMetaObject::InvokeMetaMethod, index, nullptr);
// since the process will be killed immediately quit() has no real effect
QGuiApplication::quit();
}
return true;
}
#endif // !defined(QT_NO_SESSIONMANAGER)
case QtWindows::TaskbarButtonCreated:
// Apply application badge if this is the first time we have a taskbar
// button, or after Explorer restart.
QWindowsIntegration::instance()->updateApplicationBadge();
break;
default:
break;
}
return false;
}
/* Compress activation events. If the next focus window is already known
* at the time the current one receives focus-out, pass that to
* QWindowSystemInterface instead of sending 0 and ignore its consecutive
* focus-in event.
* This helps applications that do handling in focus-out events. */
void QWindowsContext::handleFocusEvent(QtWindows::WindowsEventType et,
QWindowsWindow *platformWindow)
{
QWindow *nextActiveWindow = nullptr;
if (et == QtWindows::FocusInEvent) {
QWindow *topWindow = QWindowsWindow::topLevelOf(platformWindow->window());
QWindow *modalWindow = nullptr;
if (QGuiApplicationPrivate::instance()->isWindowBlocked(topWindow, &modalWindow) && topWindow != modalWindow) {
modalWindow->requestActivate();
return;
}
// QTBUG-32867: Invoking WinAPI SetParent() can cause focus-in for the
// window which is not desired for native child widgets.
if (platformWindow->testFlag(QWindowsWindow::WithinSetParent)) {
QWindow *currentFocusWindow = QGuiApplication::focusWindow();
if (currentFocusWindow && currentFocusWindow != platformWindow->window()) {
currentFocusWindow->requestActivate();
return;
}
}
nextActiveWindow = platformWindow->window();
} else {
// Focus out: Is the next window known and different
// from the receiving the focus out.
if (const HWND nextActiveHwnd = GetFocus())
if (QWindowsWindow *nextActivePlatformWindow = findClosestPlatformWindow(nextActiveHwnd))
if (nextActivePlatformWindow != platformWindow)
nextActiveWindow = nextActivePlatformWindow->window();
}
if (nextActiveWindow != d->m_lastActiveWindow) {
d->m_lastActiveWindow = nextActiveWindow;
QWindowSystemInterface::handleWindowActivated(nextActiveWindow, Qt::ActiveWindowFocusReason);
}
}
#ifndef QT_NO_CONTEXTMENU
bool QWindowsContext::handleContextMenuEvent(QWindow *window, const MSG &msg)
{
bool mouseTriggered = false;
QPoint globalPos;
QPoint pos;
if (msg.lParam != int(0xffffffff)) {
mouseTriggered = true;
globalPos.setX(msg.pt.x);
globalPos.setY(msg.pt.y);
pos = QWindowsGeometryHint::mapFromGlobal(msg.hwnd, globalPos);
RECT clientRect;
if (GetClientRect(msg.hwnd, &clientRect)) {
if (pos.x() < clientRect.left || pos.x() >= clientRect.right ||
pos.y() < clientRect.top || pos.y() >= clientRect.bottom)
{
// This is the case that user has right clicked in the window's caption,
// We should call DefWindowProc() to display a default shortcut menu
// instead of sending a Qt window system event.
return false;
}
}
}
QWindowSystemInterface::handleContextMenuEvent(window, mouseTriggered, pos, globalPos,
QWindowsKeyMapper::queryKeyboardModifiers());
return true;
}
#endif
void QWindowsContext::handleExitSizeMove(QWindow *window)
{
// Windows can be moved/resized by:
// 1) User moving a window by dragging the title bar: Causes a sequence
// of WM_NCLBUTTONDOWN, WM_NCMOUSEMOVE but no WM_NCLBUTTONUP,
// leaving the left mouse button 'pressed'
// 2) User choosing Resize/Move from System menu and using mouse/cursor keys:
// No mouse events are received
// 3) Programmatically via QSizeGrip calling QPlatformWindow::startSystemResize/Move():
// Mouse is left in pressed state after press on size grip (inside window),
// no further mouse events are received
// For cases 1,3, intercept WM_EXITSIZEMOVE to sync the buttons.
const Qt::MouseButtons currentButtons = QWindowsMouseHandler::queryMouseButtons();
const Qt::MouseButtons appButtons = QGuiApplication::mouseButtons();
if (currentButtons == appButtons)
return;
const Qt::KeyboardModifiers keyboardModifiers = QWindowsKeyMapper::queryKeyboardModifiers();
const QPoint globalPos = QWindowsCursor::mousePosition();
const QPlatformWindow *platWin = window->handle();
const QPoint localPos = platWin->mapFromGlobal(globalPos);
const QEvent::Type type = platWin->geometry().contains(globalPos)
? QEvent::MouseButtonRelease : QEvent::NonClientAreaMouseButtonRelease;
for (Qt::MouseButton button : {Qt::LeftButton, Qt::RightButton, Qt::MiddleButton}) {
if (appButtons.testFlag(button) && !currentButtons.testFlag(button)) {
if (type == QEvent::NonClientAreaMouseButtonRelease) {
QWindowSystemInterface::handleFrameStrutMouseEvent(window, localPos, globalPos,
currentButtons, button, type, keyboardModifiers);
} else {
QWindowSystemInterface::handleMouseEvent(window, localPos, globalPos,
currentButtons, button, type, keyboardModifiers);
}
}
}
if (d->m_systemInfo & QWindowsContext::SI_SupportsPointer)
d->m_pointerHandler.clearEvents();
else
d->m_mouseHandler.clearEvents();
}
bool QWindowsContext::asyncExpose() const
{
return d->m_asyncExpose;
}
void QWindowsContext::setAsyncExpose(bool value)
{
d->m_asyncExpose = value;
}
DWORD QWindowsContext::readAdvancedExplorerSettings(const wchar_t *subKey, DWORD defaultValue)
{
const auto value =
QWinRegistryKey(HKEY_CURRENT_USER,
LR"(Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced)")
.dwordValue(subKey);
return value.second ? value.first : defaultValue;
}
static inline bool isEmptyRect(const RECT &rect)
{
return rect.right - rect.left == 0 && rect.bottom - rect.top == 0;
}
static inline QMargins marginsFromRects(const RECT &frame, const RECT &client)
{
return QMargins(client.left - frame.left, client.top - frame.top,
frame.right - client.right, frame.bottom - client.bottom);
}
static RECT rectFromNcCalcSize(UINT message, WPARAM wParam, LPARAM lParam, int n)
{
RECT result = {0, 0, 0, 0};
if (message == WM_NCCALCSIZE && wParam)
result = reinterpret_cast<const NCCALCSIZE_PARAMS *>(lParam)->rgrc[n];
return result;
}
static inline bool isMinimized(HWND hwnd)
{
WINDOWPLACEMENT windowPlacement;
windowPlacement.length = sizeof(WINDOWPLACEMENT);
return GetWindowPlacement(hwnd, &windowPlacement) && windowPlacement.showCmd == SW_SHOWMINIMIZED;
}
static inline bool isTopLevel(HWND hwnd)
{
return (GetWindowLongPtr(hwnd, GWL_STYLE) & WS_CHILD) == 0;
}
/*!
\brief Windows functions for actual windows.
There is another one for timers, sockets, etc in
QEventDispatcherWin32.
*/
extern "C" LRESULT QT_WIN_CALLBACK qWindowsWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
LRESULT result;
const QtWindows::WindowsEventType et = windowsEventType(message, wParam, lParam);
QWindowsWindow *platformWindow = nullptr;
const RECT ncCalcSizeFrame = rectFromNcCalcSize(message, wParam, lParam, 0);
const bool handled = QWindowsContext::instance()->windowsProc(hwnd, message, et, wParam, lParam, &result, &platformWindow);
if (QWindowsContext::verbose > 1 && lcQpaEvents().isDebugEnabled()) {
if (const char *eventName = QWindowsGuiEventDispatcher::windowsMessageName(message)) {
qCDebug(lcQpaEvents).nospace() << "EVENT: hwd=" << hwnd << ' ' << eventName
<< " msg=0x" << Qt::hex << message << " et=0x" << et << Qt::dec << " wp="
<< int(wParam) << " at " << GET_X_LPARAM(lParam) << ','
<< GET_Y_LPARAM(lParam) << " handled=" << handled;
}
}
if (!handled)
result = DefWindowProc(hwnd, message, wParam, lParam);
// Capture WM_NCCALCSIZE on top level windows and obtain the window margins by
// subtracting the rectangles before and after processing. This will correctly
// capture client code overriding the message and allow for per-monitor margins
// for High DPI (QTBUG-53255, QTBUG-40578).
if (message == WM_NCCALCSIZE && !isEmptyRect(ncCalcSizeFrame) && isTopLevel(hwnd) && !isMinimized(hwnd)) {
const QMargins margins =
marginsFromRects(ncCalcSizeFrame, rectFromNcCalcSize(message, wParam, lParam, 0));
if (margins.left() >= 0) {
if (platformWindow) {
qCDebug(lcQpaWindow) << __FUNCTION__ << "WM_NCCALCSIZE for" << hwnd << margins;
platformWindow->setFullFrameMargins(margins);
} else {
const QSharedPointer<QWindowCreationContext> ctx = QWindowsContext::instance()->windowCreationContext();
if (!ctx.isNull())
ctx->margins = margins;
}
}
}
return result;
}
static inline QByteArray nativeEventType() { return QByteArrayLiteral("windows_generic_MSG"); }
// Send to QAbstractEventDispatcher
bool QWindowsContext::filterNativeEvent(MSG *msg, LRESULT *result)
{
QAbstractEventDispatcher *dispatcher = QAbstractEventDispatcher::instance();
qintptr filterResult = 0;
if (dispatcher && dispatcher->filterNativeEvent(nativeEventType(), msg, &filterResult)) {
*result = LRESULT(filterResult);
return true;
}
return false;
}
// Send to QWindowSystemInterface
bool QWindowsContext::filterNativeEvent(QWindow *window, MSG *msg, LRESULT *result)
{
qintptr filterResult = 0;
if (QWindowSystemInterface::handleNativeEvent(window, nativeEventType(), msg, &filterResult)) {
*result = LRESULT(filterResult);
return true;
}
return false;
}
QT_END_NAMESPACE